要点:
1. 谷歌对待代码的态度
2. 什么是易理解的代码
3. 好的接口设计很难被误用
4. 不要痴迷于原始类型
1
谷歌对待代码的态度
良好的编码实践包含许多方面,但并不是任何代码问题都能由检查工具发现。
我们关心软件工程过程与实践。无论软件是如何编写的,都可能影响代码的可读性、可维护性、稳定性和简单性。我们将上面提到的这些方面定义为“代码健康”。
你如何确保工程师遵循这些实践,同时仍然允许他们独立做出合理的工程决策呢?
我们所做的工作是致力于改善工程师的日常工作状态,使他们能够以更短的迭代时间开发代码、减少开发工作、提高稳定性和改进性能。
每个人都希望他们的代码变得更容易理解,他们的代码库变得更简单。因为我们都知道,这些东西能让我们更快前进,制造更好的产品。
这并不是说我们对谷歌的工程实践有严格规定。我们仍然鼓励工程师对其项目做出自己认为最明智的决定。
因此,为了帮助每个人,我们计划在未来几周和几个月内发布文章,详细介绍具体的实践,我们鼓励在任何地方应用内部实践,以帮助你和你的团队!
2
什么是易理解的代码
当你看不懂别人提交给你审核的代码是什么意思时,你也许很容易会产生有一种感觉,觉得提交者比您更聪明。但实际上,如果这段代码很难理解,那么,很可能是它过于复杂了。假如您熟悉它所使用的编程语言,那么阅读健康的代码几乎就像用您的母语阅读一本书一样简单。
假设一个开发人员发给你下面这段Python代码,请你做代码审核,
尝试理解它,如果理解它的时间超过几秒钟,那就是一种坏味道。此时,你只需要添加一个代码评论,说"我很难理解这段代码”,或者更具体一点,比如“请在这里使用更具描述性的命名”。
再过一会,提交者可能已经修改了代码,并将其再次发送给您审核:
现在我们可以很容易地阅读它,这本身就是一个好处。
通常,只需要要求开发人员写出清晰的代码,就会带来根本性的改进。在上面的例子中,开发人员意识到了可能的改进,因为修改后的代码更容易理解——当数字不是素数时,函数可以更早返回了,并且循环变成为 n / 2 ,而不是 n。
现在我们可以很容易地理解这段代码了,还可以看到其中的很多问题。
例如,对于 0 和 1 ,该函数会有奇怪行为,当然还有其他一些问题。
但最重要的是,现在显而易见的是,应该删除整个函数,因为已经有预先定义好的函数库提供了这个函数所要实现的功能,即:检测数字是否为素数的函数。
澄清代码有助于开发人员和审阅者。
总之,不必浪费时间审查难以理解的代码,要求提交者将其澄清就好了。
事实上,这样的评论是代码评审员最有用和最重要的工具之一!
3
好的接口设计很难被误用
我们都试图避免代码中的错误。那么,使用你写的接口的调用者创建的错误呢?
良好的接口设计可以使调用者轻松做正确的事情,并且让调用者很难做错事。不要将你的代码责任推给调用者。
下面的代码中,你看出问题来了么?
如果调用者忘记了调用AddSlots()
,当Insert()
被调用时,就可能会发生不可预期的行为。因此,不要将复杂性通过接口推给它的调用者,不要将实现细节暴露给了调用者。
在这个类中, slots 的维护职责与调用者的可视行为无关。因此,不要将其暴露给外界。通过将增加席位的功能放在Insert()
函数中,使调用者不能触发不可预期的行为。
由编译器强制对协议(Contracts)进行检查,通常比运行时强制进行协议检查更好。更糟糕的情况是,调用者必须查看说明文档中的协议描述以后,才能正确的调用这个接口,执行正确的操作。
下面是另外一些容易发生接口误用的情况:
-
要求调用者调用一个初始化函数(替代方法:公开使对象完全初始化的工厂方法)。
-
要求调用者执行定制化的清理任务(替代方法:使用特定于语言的构造,以确保在对象超出范围时自动清理)。
-
允许创建没有必需参数对象的代码实现(例如,没有ID的用户)。
-
限制参数的取值范围,尤其是当该参数可以使用多种类型时(例如,使用
Duration timeout
, 而不是int timeout_in_millis)
我们可能无法 百分之百地保证,所有的接口都能设计得万无一失。
但在某些情况下,由于某些要求无法在接口中表达,此时必须做依赖静态分析或者写好文档(例如,某个回调函数必须是线程安全的)。
不要强制执行你不需要强制执行的操作,避免使用过于防御性的代码。
例如,功能参数的广泛验证会增加复杂性,并且可能会降低性能。
4
不要痴迷于原始类型
编程语言提供基本类型,例如整数,字符串和Map,这些类型在许多上下文中都很有用。例如,字符串可用于保存从人名到网页URL的所有内容。但是,过于依赖基本类型而非自定义抽象的代码(如Java语言中的Class)可能难以理解和维护。
原始类型的痴迷是指过度使用基本(“原始”)类型来表示更高层次的概念。例如,下面的代码使用基本类型来表示形状:
pair
在这里并不是一个正确的抽象层次,因为它的一般命名 first
和 second
两个参数被用于表示 X 和 Y,其中一处被用做标识 lower-left (或者, upper-left?) 而另外一处用作 upper-right (或, lower-right?) 。更糟糕的是,基本类型并没有对领域专属的代码进行封装,例如边框(bounding box)和面积(area)。
用更高级别的抽象可以产生更清晰且封装性更好的代码:
下面是一些原始类型痴迷的例子:
-
maps, lists, vectors等类型可以很方便地成为一个容器集,将值合并到自定义的更高级抽象中。
-
包含魔术键值的 vector 或 map ,例如 用 0,1 和 2 分别代表 name , address, and phone #,应该将其放入到更高一级的抽象中。
-
用一个字符串表示复杂或结构化的文本(例如一个日期)。更好的做法是使用更高层的抽象(如
Date
类型) ,它还提供自我说明的访问函数(例如GetMonth
) ,并且还保证正确性。
-
用一个整型或浮点数表过一个时间值,例如多少秒。更好的做法是使用结构化的时间戳 (timestamp) 或 时间段类型(duration)。
对于某个任务来说,任何类型都有可能过于原始,从最原始的int,到复杂的红黑树。
如果你看到代码使用了很多的基础类型,那么,使用更高级别的抽象更能够表达清楚,封装性也会更好。
你应该把它重构了,或者礼貌地提醒作者要 保持优雅!