如何写出美的代码 1

54 阅读9分钟

相信很多程序员都有一样的烦恼,为什么别人的代码通俗易懂,简洁明了,自己的代码可读性差,不好维护等等问题。一切的根源都是因为我们最初没有养成良好的编程习惯,希望大家通过这篇文章,认识到自己的不足与改进,有则改之无则加勉~

  1. 大家一定要记住以下的标准与规范,每天做好一点点...
  • Simple, Poetic, Pithy
  • Don't communicate by sharing memory, share memory by communicating.
  • Concurrency is not parallelism.
  • Channels orchestrate; mutexes serialize.
  • The bigger the interface, the weaker the abstraction.
  • Make the zero value useful.
  • interface{} says nothing.
  • Gofmt's style is no one's favorite, yet gofmt is everyone's favorite.
  • A little copying is better than a little dependency.
  • Syscall must always be guarded with build tags.
  • Cgo must always be guarded with build tags.
  • Cgo is not Go.
  • With the unsafe package there are no guarantees.
  • Clear is better than clever.
  • Reflection is never clear.
  • Errors are values.
  • Don't just check errors, handle them gracefully.
  • Design the architecture, name the components, document the details.
  • Documentation is for users.
  • Don't panic.

2.五要素

  • 清晰:代码的目的和设计原理对读者来说是清楚的。
  • 简约:代码以最简单的方式来完成它的目的。
  • 简洁:代码具有很高的信噪比,即写出来的代码是有意义的,非可有可无的。
  • 可维护性:代码可以很容易地被维护。
  • 一致:代码与更广泛的 Google 代码库一致。

3.清晰

可读性的核心目标是写出对读者来说很清晰的代码。

清晰性主要是通过有效的命名、有用的注释和有效的代码组织来实现的。

清晰性要从读者的角度来看,而不是从代码的作者的角度来看,代码的易读性比易写性更重要。代码的清晰性有两个不同的方面:

  • 该代码实际上在做什么?
  • 为什么代码会这么做?

该代码实际上在做什么 

Go 被设计得应该是可以比较直接地看到代码在做什么。在不确定的情况下或者读者可能需要先验知识才能理解代码的情况下,我们值得投入时间以使代码的意图对未来的读者更加明确。例如,它可能有助于:

  • 使用更具描述性的变量名称
  • 添加额外的评论
  • 使用空白与注释来划分代码
  • 将代码重构为独立的函数/方法,使其更加模块化

这里没有一个放之四海而皆准的方法,但在开发 Go 代码时,优先考虑清晰性是很重要的。

为什么代码会这么做 

代码的逻辑通常由变量、函数、方法或包的名称充分传达。如果不是这样,添加注释是很重要的。当代码中包含读者可能不熟悉的细节时,“为什么?”就显得尤为重要,例如:

  • 编程语言中的细微差别,例如,一个闭包将捕获一个循环变量,但闭包在许多行之外
  • 业务逻辑的细微差别,例如,需要区分实际用户和虚假用户的访问控制检查

一个 API 可能需要小心翼翼才能正确使用。例如,由于性能原因,一段代码可能错综复杂,难以理解,或者一连串复杂的数学运算可能以一种意想不到的方式使用类型转换。在这些以及更多的情况下,附带的注释和文档对这些方面进行解释是很重要的,这样未来的维护者就不会犯错,读者也可以理解代码而不需要进行逆向工程。

同样重要的是,我们要意识到,一些基于清晰性考虑的尝试(如添加额外的注释),实际上会通过增加杂乱无章的内容、重述代码已经说过的内容、与代码相矛盾或增加维护负担来保持注释的最新性,以此来掩盖代码的目的。让代码自己说话(例如,通过代码中的名称本身进行描述),而不是添加多余的注释。通常情况下,注释最好是解释为什么要做某事,而不是解释代码在做什么。

4.简约 

你的 Go 代码对于使用、阅读和维护它的人来说应该是简单的。

Go 代码应该以最简单的方式编写,以实现其行为和性能方面的目标。在 Google Go 代码库中,简单的代码:

  • 从头至尾都易于阅读
  • 不预设你已经知道它在做什么
  • 不预设你能记住前面所有的代码
  • 不含非必要的抽象层次
  • 不含过于通用的命名
  • 让读者清楚地了解到传值与决定的传播情况
  • 有注释,解释为什么,而不是代码正在做什么,以避免未来的歧义
  • 有独立的文档
  • 包含有用的错误与失败用例测试
  • 往往不是看起来“聪明”的代码

在代码的简单性和 API 使用的简单性之间可能会需要权衡。例如,让代码更复杂可能是值得的,这样 API 的终端用户可以更容易地正确调用 API。相反,把一些额外的工作留给 API 的终端用户也是值得的,这样代码就会保持简单和容易理解。

当代码需要复杂性时,应该有意地增加复杂性。如果需要额外的性能,或者一个特定的库或服务有多个不同的客户,这通常是必要的。复杂性可能是合理的,但它应该有相应的文档,以便客户和未来的维护者能够理解和驾驭这种复杂性。这应该用测试和例子来补充,以证明其正确的用法,特别是有“简单”和“复杂”两种方式来使用代码的情况下。

这一原则并不意味着复杂的代码不能或不应该用 Go 编写,也不意味着 Go 代码不允许复杂。我们努力使代码库避免不必要的复杂性,因此当复杂性出现时,它表明有关的代码需要仔细理解和维护。理想情况下,应该有相应的注释来解释其中的道理,并指出应该注意的地方。在优化代码以提高性能时,经常会出现这种情况;这样做往往需要更复杂的方法,比如预先分配一个缓冲区并在整个 goroutine 生命周期内重复使用它。当维护者看到这种情况时,应该是一个线索,说明相关的代码是基于性能的关键考虑,这应该影响到未来修改时的谨慎。另一方面,如果不必要地使用,这种复杂性会给那些需要在未来阅读或修改代码的人带来负担。

如果代码在其目的应该很简单时却变得非常复杂,这往往是一个我们可以重新审视代码实现的信号,看看是否有更简单的方法来完成同样的事情。

5 最小化机制

如果有几种方法来表达同一个想法,最好选择使用最标准工具的方法。复杂的机制经常存在,但不应该无缘无故地使用。根据需要增加代码的复杂性是很容易的,而在发现没有必要的情况下删除现有的复杂性则要难得多。

  • 当能够满足你的使用情况时,使用核心的语言结构(例如 channel、slice、map、loop 或 struct)
  • 如果没有,就在标准库中寻找一个工具(如 HTTP 客户端或模板引擎)
  • 最后,在引入新的依赖或创建自己的依赖之前,考虑 Google 代码库中是否有一个能够满足的核心库

例如,考虑生产代码包含一个绑定在变量上的标志,它的默认值必须在测试中被覆盖。除非打算测试程序的命令行界面本身(例如,用os/exec),否则直接覆盖绑定的值比使用 flag.Set 更简单,因此更可取。

同样,如果一段代码需要检查集合成员的资格,一个布尔值的映射(例如,map[string]bool)通常就足够了。只有在需要更复杂的操作,不能使用 map 或过于复杂时,才应使用提供类似集合类型和功能的库。

6 简洁 

简洁的 Go 代码具有很高的信噪比。它很容易分辨出相关的细节,而命名和结构则引导读者了解这些细节。

而有很多东西会常常会阻碍这些最突出的细节:

  • 重复代码
  • 无关的语法
  • 含义不明的名称
  • 不必要的抽象
  • 空白

重复代码尤其容易掩盖每个相似代码之间的差异,需要读者直观地比较相似的代码行来发现变化。表驱动测试是一个很好的例子,这种机制可以简明地从每个重复的重要细节中找出共同的代码,但是选择哪些部分囊括在表中,会对表格的易懂程度产生影响。

在考虑多种结构代码的方式时,值得考虑哪种方式能使重要的细节最显著。

  1. 可维护性 

代码被编辑的次数比它写它的次数多得多。可读的代码不仅对试图了解其工作原理的读者有意义,而且对需要改写它的程序员也有意义,清晰性很关键。

可维护的代码:

  • 容易让未来的程序员正确进行修改
  • 拥有结构化的 API,使其能够优雅地增加
  • 清楚代码预设条件,并选择映射到问题结构而不是代码结构的抽象
  • 避免不必要的耦合,不包括不使用的功能
  • 有一个全面的测试套件,以确保预期行为可控、重要逻辑正确,并且测试在失败的情况下提供清晰、可操作的诊断

8 一致

一致性的代码是指在更广泛的代码库中,在一个团队或包的范围内,甚至在一个文件中,看起来、感觉和行为都是类似的代码。

一致性的问题并不凌驾于上述的任何原则之上,但如果必须有所取舍,那往往有利于一致性的实现。

一个包内的一致性通常是最直接重要的一致性水平。如果同一个问题在一个包里有多种处理方式,或者同一个概念在一个文件里有很多名字,那就会非常不优雅。然而,即使这样,也不应该凌驾于文件的风格原则或全局一致性之上。