1 整洁代码
没什么需要写的
2 有意义的命名
不必使用m_
前缀来标明成员变量。
接口名最好不要加额外的修饰,如果接口和实现必须选择一个加修饰的话,宁愿选择实现,如ShapeFactoryImp
,而不用IShapeFactory
作为接口名。
概念名称要一致,比如fetch
, retrieve
, get
和Controller
, Manager
。
避免使用双关语,比如add
既有加减乘除的意思,也有添加的意思。如果代码中同时出现这两个,后者就应该用insert
或者append
。
3 函数
函数应该尽可能短小。
将一个大函数抽象成多个层级,每个层级单独编写一个函数。
switch
可以借助多态埋藏在较低的抽象层级,比如原本calculatePay
函数用switch
计算不同类型Employee
的工资,如下
就应该把计算工资的函数埋藏在Employee
中,不同类型的Employee
都是一个单独的类,都有一个calculatePay
的函数,如下
函数命名不要害怕长名字。
函数参数要尽可能少,最好是0个,其次是1个,再次是2个。
单参数函数:函数名称要尽可能体现这会修改系统状态,还是仅仅是个无状态的转换操作。转换操作就应该使用返回值,而不是修改参数。
不要用标识函数(即单参数且这个参数是个布尔值),这样的函数应该直接被换成两个相应的函数。如render(Boolean isSuite)
改成renderForSuite()
和renderForSingleTest()
。
两参数的函数可以用。有些本来就是两参数,比如Point(1,2)
,但是writeField(outStream, name)
最好改为outStream.writeField(name)
。
如果参数中有多个参数是相关联的,就应该封装成一个类,如makeCircle(int x, int y, int radius)
就应该改为makeCircle(Point center, int radius)
。
writeField(name)
比write(name)
更好,因为前者说明了name
是一个“field”;assertExpectedEqualsActual(expected, actual)
比assertEqual(expected, actual)
更好,因为前者减轻了记忆参数顺序的负担。
函数应该尽可能避免副作用,如果确实有副作用,则应该在命名中体现出来。
函数参数中尽量避免用于输出的参数,如向一个List
类型的参数写入新元素。如果函数确实需要修改状态,那么最好用report.appendFooter()
而非appendFooter(report)
。
函数要么修改,要么查询,不要都做。
尽量使用异常代替错误码。这样的话,如果有新的异常,只需要从原先类中派生出来,而不需要修改错误码定义。
错误处理这件事就应该放在独立的函数中完成。把try-catch抽离出来,如下
可能一开始没有办法做到符合上述规则的函数,这得写好以后打磨,修改,使之符合上述规则。
4 注释
最好的注释就是代码。比如说函数responderInstance()
注释中写“返回一个被测试的Responder”,那就函数名可以直接改成responderBeingTested()
,就不需要注释了。
并不是所有函数都要加注释。
日志式注释不可取。日志式注释是把每次对代码的修改都记录下来,而现在已经有了版本管理系统,所以这是不必要的。
能用函数或变量时不要用注释,如下
可以改进为
没必要写//added by xxx
这样的署名注释,因为这可以从版本控制系统中得到。
要减少注释,可以考虑把一个大函数拆分成多个命名清晰的小函数。
5 格式
源文件开头应该有一个高层次的概念,细节从上往下展开。
不同内容之间要用空行隔开。
相关联的内容(比如一个类的两个成员变量)中间不应该加空行,应该挨在一起以表示它们的关联性。
一个类的成员变量放在哪里的问题:遵循相关编程语言的规则。如C++放在最下面,Java放在最上面。
互相调用的函数应该放在一起,调用者放在被调用者上面。这样从上往下读就可以自顶向下理解代码。
如果几个函数所做的事情是非常类似的,那就应该放在一起,即使没有互相调用。此时,前面互相调用的原则就是第二位的。
水平方向上,优先级高的运算符左右不加空格,优先级低的加空格,如(-b + x) / (2*a)
。
如下的对齐是不必要的
即使循环体为空,也不要省略。应该
while(condition)
;
团队应该有统一的编码规则,包括命名变量、方法等。
在作者的示例代码中,public和private函数并没有分开放置,而是根据功能需要放在一起。
6 对象和数据结构
合理地对接口进行抽象,避免暴露内部实现。
过程式(一组函数,每个函数都负责各种struct这样简单类的计算):添加新的类很麻烦,因为需要修改所有函数;添加新的函数很容易,所有类都不需要修改。
面向对象(一个接口定义多个函数,由子类实现这些接口):添加新的类很容易,其他类不需要任何修改;添加新的函数很麻烦,因为接口中增加一个函数会导致所有子类都需要增加这个函数的实现。
Demeter定律认为:一个方法不应该调用由任何函数返回的对象的方法。如下代码违反了这一点。
但是如果函数返回的是一个数据结构,而非对象,那就不会违反Demeter定律。因为数据结构的内部细节本来就是公开的。如下代码就不算是违反。