每一种开发语言都会有自己独特的风格规范(或指南)。遵循规范的开发人员可以带来显着的收益,有效促进团队合作,减少错误错误,降低维护成本等。Google的开源GoogleStyleGuides(https://google.github.io/styleguide/)为多种编程语言提供了风格指南,包括C++、Java、Python、JavaScript等。2022年11月,Go语言风格规范(https://google.github.io/styleguide/go/index)终于开源。Google发布的Go语言风格规范由四部分组成。概览文章:https://google.github.io/styleguide/go/index指导文章:https://google.github.io/styleguide/go/guide决策文章:https://google.github.io/styleguide/go/decisions最佳实践:https://google.github.io/styleguide/go/best-practices如果你的团队还没有形成系统的Go风格规范,不妨参考一下这篇指南。风格原则这里有一些通用原则,总结了关于如何编写可读的Go代码的思考。以下是按重要性排序的可读代码功能。清晰:代码的目的和基本原理对读者来说是清楚的。简单性:代码以尽可能简单的方式实现其目标。简单性:代码具有高信噪比。可维护性:编写的代码易于维护。一致性:代码与更广泛的Google代码库风格一致。1.清晰性可读性的核心目标是编写对读者来说清晰的代码。清晰主要是通过有效的命名、有用的注释和有效的代码组织来实现的。清晰度应该从读者的角度出发,而不是代码的作者。代码易于阅读比易于编写更重要。代码清晰度有两个不同的方面:代码实际上在做什么?为什么代码会做它做的事情?代码实际上在做什么?Go的设计使得它应该相对直接地看到代码在做什么。在存在不确定性或读者需要先验知识才能理解代码的情况下,值得花时间让未来的读者更清楚地了解代码的用途。例如,以下措施可能有助于提供更清晰的代码:使用更具描述性的变量名称添加额外的注释用空格和注释分解代码将代码重构为单独的函数/方法以使其更加模块化这里没有万能的,但这很重要在开发Go代码时优先考虑清晰度。为什么代码会做它做的事情?代码的基本原理通常通过变量、函数、方法或包的名称充分传达。如果没有,添加评论很重要。当代码包含读者可能不熟悉的细微差别时,解释“为什么”尤为重要。例如:语言上的微小差异,例如闭包采用循环变量,但代码写了很多行。业务逻辑的细微差别,例如需要区分真实用户和模拟用户的访问控制检查。API可能需要额外注意才能正确使用。例如,一段代码可能由于性能原因而令人费解和难以理解,或者一系列复杂的数学运算可能以意想不到的方式使用类型转换。在这些场景中,以及更多类似的场景中,重要的是附上评论和解释它们的文档,这样未来的维护者就不会犯错误,并且这些读者能够在不进行逆向工程的情况下理解代码。同样重要的是要注意,一些提供清晰度的尝试(例如添加额外的注释)可能会掩盖代码的实际目的,例如添加混淆、重复代码描述、与代码相矛盾的注释或注释以确保Up-to-维护负担增加的日期。让代码自己说话(例如,通过使用自我描述的符号名称)而不是添加多余的注释。注释通常最擅长解释为什么要做某事,而不是解释代码在做什么。Google代码库在很大程度上是统一和一致的。通常,不寻常的代码(例如,通过使用不熟悉的模式)这样做是有充分理由的,通常是性能方面的考虑。保留此功能很重要,可以让读者清楚地知道在阅读一段新代码时应该将注意力集中在哪里。.标准库包含许多实践此原则的示例。例如where:sort包中的维护者注释。在sort包中还有一些很好的工作示例,对用户(示例显示在[godoc](sortpackage-sort-GoPackages))和维护者(用于部分测试目的的示例)都有好处。strings.Cut只有四行代码,但它们提高了调用者使用的清晰度和正确性。2.简单性对于那些使用、阅读和维护它的人来说,你的Go代码应该是简单的。Go代码应该以最简单的方式编写以实现其目标,无论是在行为还是在性能方面。在GoogleGo代码库中,简单代码意味着:易于从上到下阅读不假设您已经提前知道它在做什么不假设您可以记住所有以前的代码没有不必要的抽象层次不导致人们担心常见事物的注意名称使读者清楚价值观和决策是如何传播的有评论解释为什么和不做什么,以及代码正在做什么以避免以后的偏差“聪明”的代码是相互排斥的。可以在代码简单性和API使用简单性之间进行权衡。例如,使代码更复杂可能是值得的,以便API的最终用户可以更轻松、更正确地调用API。相比之下,为API的最终用户留一些额外的工作可能也是值得的,这样代码仍然简单易懂。当代码需要复杂性时,应该有意添加。如果需要额外的性能,或者如果特定图书馆或服务有许多不同的用户,这通常是必要的。复杂性应该是合理的,但它应该附有文档,以便用户和未来的维护者能够理解和驾驭复杂性。这应该通过测试和示例来补充,以证明其正确用法,尤其是在同时存在“简单”和“复杂”代码使用方式的情况下。我们努力避免代码库中不必要的复杂性,以便当确实出现复杂性时,表明需要仔细理解和维护这些相关代码。理想情况下,应该有注释来解释基本原理并确定需要注意的事项。在优化代码以提高性能时经常会出现这种情况;这样做通常需要更复杂的方法,例如预先分配缓冲区并在goroutine的整个生命周期中重用它。当维护者看到它时,这应该是一个线索,表明有问题的代码对性能至关重要,以后对该代码的更改应该谨慎。另一方面,如果使用不当,这种复杂性会给那些将来需要阅读或更改代码的人带来负担。如果代码的目的很简单但最终的实现很复杂,这通常表明应该重新审视实现,看看是否有更简单的方法来实现相同的目的。最小机制如果有多种方式表达同一个想法,选择使用最标准的一种。存在复杂的机制,但不应无缘无故地使用。根据需要增加代码的复杂性很容易,但一旦发现不必要的复杂性,就很难消除现有的复杂性。当足以满足您的用例时,使用核心语言结构(例如切片、映射、循环或结构)。如果没有,请在标准库中寻找工具(例如HTTP客户端或模板引擎)。最后,在引入新的依赖或者自己发明轮子之前,考虑一下谷歌代码库中是否有对应的核心库。例如,考虑包含绑定到变量的标志的生产代码,该变量具有必须在测试中覆盖的默认值。除非您打算测试程序的命令行界面本身(例如,使用os/exec),否则直接覆盖绑定值比使用flag.Set更简单和更可取。同样,如果一段代码需要检查集合成员资格,布尔值映射(map[string]bool)通常就足够了。仅当需要map无法实现或过于复杂而无法使用的更复杂操作时,才应使用提供类集合类型和功能的库。3.简洁简洁的Go代码信噪比高。它应该很容易辨别相关细节,并通过命名和代码结构引导读者。在任何给定时间,有很多事情会阻止代码呈现最重要的细节:重复代码外来语法模糊名称不必要的抽象空间比较相似的代码行以查找更改。表格驱动的测试室是一种机制的一个很好的例子,它可以从每次重复的重要细节中简洁地提取通用代码,但是选择将哪些部分包含在表格中会影响它的易懂程度。在考虑组织代码的方式时,请考虑哪种方式可以使重要的细节最明显。理解和使用通用代码结构和真实用法对于保持高信噪比也很重要。例如下面的代码块在错误处理中是很常见的,读者可以很快理解这个块的用途。//Good:iferr:=doSomething();err!=nil{//...}如果代码看起来与此非常相似但略有不同,读者可能不会注意到变化。在这种情况下,值得通过添加注释以引起注意来有意“增强”错误检查信号。//好:如果错误:=doSomething();err==nil{//ifNOer??ror//...}4.可维护性代码被编辑的次数远远多于编写的次数。可读的代码不仅对试图理解其工作原理的读者有意义,而且对需要更改它的程序员也有意义。清晰度是关键。代码的可维护性:易于被未来的程序员正确修改拥有结构化的API,以便他们可以优雅地成长代码做出的清晰假设,选择与问题结构相对应的抽象,而不是代码结构避免不必要的耦合,不包括未使用的功能拥有一个全面的测试套件,以确保维持承诺的行为、重要逻辑正确,以及测试用例失败并提供清晰、可操作的诊断。当使用像接口和类型这样的抽象时,根据定义从它们使用的上下文中删除信息,重要的是要确保它们提供足够的好处。编辑器和IDE在使用具体类型时可以直接链接到方法定义并显示相应的文档,但在其他情况下只能引用相应的接口定义。接口是一个强大的工具,但使用它们是有代价的,因为维护者可能需要了解底层实现的细节才能正确使用它们,这必须在接口文档中或在调用时进行解释。可维护的代码也避免了将重要的细节隐藏在容易被忽视的地方。例如,在下面的代码示例中,单个字符的存在会对理解代码产生重大影响。//Bad://使用=而不是:=可以完全改变这一行。ifuser,err=db.UserByID(userID);err!=nil{//...}//错误://!在这一行的中间很容易错过。leap:=(year%4==0)&&(!(year%100==0)||(year%400==0))正确,但两者都可以以更明确的方式编写,或者可以附上注释以引起对重要行为的注意。//Good:u,err:=db.UserByID(userID)iferr!=nil{returnfmt.Errorf("invalidoriginuser:%s",err)}user=u//Good://公历闰年不只是year%4==0。//请参阅https://en.wikipedia.org/wiki/Leap_year#Algorithm.var(leap4=year%4==0leap100=year%100==0leap400=year%400==0)leap:=leap4&&(!leap100||leap400)同样,隐藏关键逻辑或重要边缘情况的辅助函数很容易做出可能无法正确解释的后续更改。可预测的命名是可维护代码的另一个特征。包的用户或一段代码的维护者应该能够预测给定上下文中的变量、方法或函数的名称。相同概念的函数参数和接收者通常应该共享相同的名称,这既使文档易于理解,又有助于以最小的开销进行代码重构。可维护代码应该最小化其依赖性(隐式和显式)。依赖更少的包意味着可以影响行为的代码行更少。避免依赖于内部或未记录的行为可以降低代码在未来行为发生变化时成为维护负担的可能性。在考虑如何组织或编写代码时,值得花时间思考代码可能随时间演变的方式。如果给定的方法更有利于将来进行更简单、更安全的更改,那通常是一个很好的权衡,即使这意味着设计稍微复杂一些。5.一致性一致的代码是在更广泛的代码库、团队或包的上下文中,甚至在同一文件中看起来、感觉和行为都相似的代码。一致性问题不会凌驾于上述任何原则之上,但如果天平必须倾斜,倾斜天平以保持一致性通常是有益的。包内一致性通常是最直接重要的一致性级别。如果同一个问题在一个包中以多种方式处理,或者如果同一个概念在一个文件中有多个名称,则可能会非常不一致。但是,即使这样也不应覆盖文档化的样式原则或全局一致性。核心指南这些指南收集了所有Go代码应遵循的Go风格的最重要方面。我们希望在编写可读代码时学习并遵循这些准则。这些指南预计不会经常更改,新增加的内容必须按照高标准进行审查。下面的指南扩展了EffectiveGo中的建议,它为整个社区的Go代码提供了一个共同的基线。格式化所有Go源文件必须符合gofmt工具输出的格式。此格式由Google代码存储库中的提交前检查强制执行。生成的代码通常也应该格式化(例如使用format.Source),因为它也可以在代码搜索中浏览。CamelCaseGo源代码在编写多词名称时使用MixedCaps或mixedCaps(CamelCase)而不是下划线(SnakeCase)。即使它打破了其他语言的约定,这也适用。例如,如果常量可导出,则常量为MaxLength(而非MAX_LENGTH),如果不可导出,则常量为maxLength(而非max_length)。为了资本化可导出的目的,局部变量被认为是未导出的。行长Go源代码没有固定的行长。如果一行感觉太长,您应该考虑重构而不是中断。如果它已经尽可能短,那么应该允许该行代码继续变长。不要拆分代码行:在缩进更改之前(例如函数声明、条件)将某个长字符串(例如URL)放入多个较短的行中命名与其说是一门科学,不如说是一门艺术。在Go中,名称往往比许多其他语言短一些,但适用相同的一般准则。命名时要注意:用起来不会有重复的感觉。考虑上下文,不要重复已经清楚的概念。您可以在[Decisions]中找到有关命名的更具体指导。本地一致性如果风格指南没有提到特定的风格点,作者可以自由选择他们喜欢的风格,除非代码非常接近(通常在同一个文件或包中,但有时在团队或项目目录中)在这个问题上采用了一致的风格。有效本地样式注意事项示例:使用%s或%v格式化打印错误使用缓存通道而不是互斥体无效本地样式注意事项示例:行长度限制使用基于断言的测试库,但可读性影响仅限于一个文件,它通常会在代码审查期间出现,并且一致的修复将超出相关CL(更改列表,更改列表)的范围。在这一点上,提交错误以跟踪该修复是合适的。如果更改加剧了现有的风格偏差,暴露在更多的API级别,增加了存在偏差的文件数量,或者引入了实际的错误,那么新代码就不再需要局部一致性。违反风格指南的正当理由。在这些情况下,作者应该清理同一个CL下的现有代码库,重构当前CL之前的代码,或者找到一个至少不会使局部问题变得更糟的替代方案。
