:bulb: ***Later equals never.*** #### 1.有意义的命名 软件中随处可见命名,我们给变量、函数、类和包命名,下面是起一个好名字应该遵从的规则。 ==名副其实== 选个好名字要花时间,但是省下来的时间比花掉的多,注意命名,而且一旦发现有更好的命名就换掉旧的,这样做,读代码的人都会更开心。 1. 指明计量单位和计量单位的名称,如:int elapsedTimeInDays、int fileAgeInDays 等。 2. 选择体现业务本意的名称,而不是字母(如:d、e),类型(如:theList、list1)等名称。 3. 利用类代替复杂的数据结构。 例如:应用中使用 int\[]\[] 数组表示坐标,如果在代码中大量使用这个二维数组,代码会变得极其繁杂,我们可以定义一个类 ~~~java public class Position { private int value; private int x; private int y; } ~~~ 4. 避免使用魔术变量(未经定义的常量),例如: ~~~java if(age == 5){ // do something } ~~~ 此时,我们应当将 5 这个魔术数放到文件前面并定义一个数字,如:`private static final int MIN_AGE = 5;`。 ==避免误导== 程序员必须避免留下掩藏代码本意的错误线索,避免使用与本意相悖的词。 1. 避免使用某些专有名称作为变量名,例如:aix、sco、bat 等,虽然看起来像是不错的缩写,但是极易引起误导。 2. 不要使用 accountList 来表示一组账号,除非它真的是 List 类型,否则会引起错误的判断,此时使用 accountGroup 或 accounts 都是更好的选择。 3. 不要使用外形相似度较高的名称,例如 XYZControllerForEfficientHandlingOfStrings 和 XYZControllerForEfficientStorageOfStrings,更有甚者使用小写的字母 l 和大写的字母 O,他们看起来完全像常量「壹」和「零」。 ==做有意义的区分== 在代码中,同一作用范围的两样不同的东西不能重名,如果我们只是为满足编译器或解释器的需要而修改代码,那么将会带来无尽麻烦。 1. 只是在命名后添加数字远远不够,每个不同的命名都应该能表达出不同的意义,如 a1、a2、a3、a4... 等名称纯属误导,完全没有提供正确的信息,例: ~~~java public void copyChars(char[] c1, char[] c2) { for (i = 0; i< c1.length; i++) { c2[i] = c1[i]; } } ~~~ 此时,两个参数名 c1 和 c2 完全没有任何意义,如果将其替换为 source 和 destination,函数就会好看许多。 2. 避免使用废话来区分命名,假设有一个 Product 类,还有一个 ProductData 和 ProductInfo 类,那么它们虽然名称不同,但是都是意义含混的废话;当我们面对 getActiveAccount、getActiveAccounts 和 getActiveAccountInfo 这 3 个函数时,也无法快速的分辨到底该调用哪一个函数。 3. 避免冗余的命名,例如 variable 永远不要出现在变量名中,table 永远不要出现在数据库表名中,nameString 也不会比 name 更好。 ==使用读得出来的名称== 人类擅长于记忆和使用单词,如果名称读不出来,在讨论的时候就会变得十分尴尬。 1. 不要使用字母组合命名,例如程序里面有一个 genymdhms 函数(生成年月日时分秒),这样的函数名完全无法读出来也无法一眼看出它的含义,如果换成 genTimestamp 这样的命名就会使函数读起来更像人话。 2. 不要使用不恰当的缩写,例如:DtaRcrd、Cstm、Acct 并不会比 DataRecord、Customer、Account 这些完整的单词更好更简洁。 ==使用可搜索的名称== 对于单字母名称和数字常量,有一个问题就是很难再一大段代码中搜索出来。 1. 搜索 MAX_CLASSES_PER_STUDENT 很容易,但是查找数字 7 就比较麻烦了,同时字母 e、s 等也不是一个便于搜索的好名称,我们应该尽量避免使用单字母名称和数字常量。 2. 名称长短应该与其作用域大小相对应,如果变量或常量可能在代码中多处使用,则应该赋予它们便于搜索的名称。 ==避免使用标记== 现代编程语言有丰富的类型系统,并且强制使用类型,所以把类型和作用域编进名称里面,只会突然增加负担。 1. 不必使用 m_ 来标记成员变量,我们要学会无视前缀或后缀,只看到名称中有意义的部分。 2. 在创建接口时,不要使用前导字母 I,比如 IShapeFactory 对于 ShapeFactory 来说,前面的 I 字母根本就是一句废话。 ==类名和方法名== 1. 类名和对象名应该是名称和名词短语,例如 Customer、Account 等,此外还要避免使用 Data、Info、Manager 这样语义广泛的名称。 2. 方法名应当是动词或动词短语,如 postPayment、deletePage 等。 ==一词一义== 1. 每个概念值对应一个词,假如一堆代码中的控制器有 controller、manager 和 driver,name就会让人产生困惑,我们的命名应当一以贯之。 2. 避免将同一单词用于不同目的(双关),例如:使用 add 用于向集合中添加元素,而在另外的地方用来拼接字符串,这种做法是强烈禁止的。 ==使用解决方案领域名称和问题领域名称== 1. 只有程序员才会读你的代码,所以我们可以尽情的使用那些计算机科学术语,算法名,模式名,没有程序员不知道 AccoutVisitor 的含义,同样,大多数程序员也都了解 JobQueue 的含义。 2. 如果不能使用程序员熟悉的术语进行命名,那么可以考虑从所涉及的问题的领域获取命名,与软件的实际功能更为贴近。 ==为代码添加语境== 很少有名称是能够自我说明的,如果不能自我说明,我们还剩最后一招,给名称添加前缀语境。 1. 当我们看见一堆单词如 state、city、street、name、houseNumber 时,我们很容易推断这是一个地址的描述,但是如果我们只看见一个孤零零的 name 变量呢,我们无法进行推断,但是我们可以添加前缀进行说明:addrName,这样我们就可以明白这个变量是更大结构的一部分。 2. 当需要使用到一堆相关联的变量时,可以使用一个类封装它们,类也是语境的一部分。 3. 不要添加无意义的语境,假如有一个名为 Gas Station Deluxe(加油站豪华版)的应用,如果给其中的每一个类都加上 GSD 前缀就不是什么好点子了;只要名称足够清楚,短名称就比长名称好。 --- #### 2.函数 在编程的早期岁月,系统由程序和子程序组成,后来到了 Fortran 和 PL/1 的年代,系统由程序、子程序和函数组成,如今只有函数保留了下来,函数是所有程序中的第一组代码。 ==短小== 函数的第一条规则是短小,第二条规则还是要短小。 1. 函数尽量不要写得过长,**20** 行封顶最佳。 > :gear: 个人不倾向于这这么短的函数,频繁的函数调用不仅不会降低代码编写难度,还会提高难度。一般情况下,**50-60** 行左右封顶为最佳。 2. if、else、while 等语句,其中的代码应该只占一行,该行一般为一个函数调用语句,这样能够很好的保持函数大小。 3. 函数中的代码嵌套层级(if、while 等嵌套)不宜过多,否则不易于阅读和理解,最好是一层或两层,不可超过三层。 ==只做一件事== 函数应该只做一件事,做好这件事,只做一件事。 如何判断函数只做了一件事,还是做了几件事?例: ~~~java public void buy() { // 1.走进商店 // 2.选好商品 // 3.掏出钱包 // 4.付钱 // 5.放回钱包 // 6.走出商店 } ~~~ 在这个函数中,模拟了一个买东西的情节,其中 1、2、6 步骤都是买东西过程下的抽象层中的一件事,但是 3、4、5 却是在买东西这个动作下的更细分的步骤,很明显不属于函数名下的抽象层,此时 3、4、5 就合一被拆出一个新函数「结账」。 > :yellow_heart: 向下原则:让每个函数后面跟着位于下一抽象层及的函数,在阅读时就能遵循抽象层级向下阅读。 ==使用具有描述性的名称== 为函数起一个具有描述性的名称,函数的功能越集中,就越便于起一个好名字。 不要害怕长名称,长名称要比短而费解的短名称好。 在同一个模块中,函数的命名方式要保持一致,使用与模块名一脉相承的短语、名词和动词给函数命名。 ==函数参数== 最理想的函数参数是 0,其次是 1,再次是 2,应当尽量避免 3 参数的函数(除非万不得已)。 1. 一般来说,信息通过参数输入函数,然后通过返回值进行输出,所以尽量不要在参数中输出函数结果。 2. 不要使用标识参数:向函数输入 boolean 值是一种不优雅的做法,相当于大声宣布本函数不只做一件事,在任何情况下我们都不要这样做;此时我们应该将这种情况拆分为 2 个函数 doXxxForXxx 和 doXxxForNonXxx。 3. 双参数函数: 1. 两个参数最好是某个值的有序组成部分,如:`new Point(0, 0);`。 2. 使用两个同类型的参数时,应额外注意其先后顺序,如:`assertEquals(expected, actual);`中一般约定期望值在前,实际值在后。 3. 尽量的转化双参数函数为单参数函数,可以使用诸如添加成员变量、新建一个类等方法。 4. 三参数函数:如果一个函数看起来需要 2 个、3 个及 3 个以上的参数,说明一些参数应该封装成类了,例: ~~~java Circle makeCircle(double x, double y, double radius); Circle makeCircle(Point center, double radius); ~~~ 5. 可变参数:在 Java 中,可以使用 ... 向函数传入可变长度的参数。 6. 函数和参数应当形成一种非常良好的动词/名词的对应关系,例如: `writeField(name);`。 ==分割指令和询问== 1. 函数要么做什么事,要么回答什么事,两者不可得兼,例: ~~~java boolean isSuccess = setAttribute("username", "unclebob"); --------------------------------------------------------- if(attributeExists("username")) { setAttribute("username", "unclebob"); } ~~~ 此两种代码明显后者更优。 2. 使用异常替代返回错误码,将询问以异常的形式抛出,能够很好的分割指令和询问。 1. 将 try/catch 代码块单独抽离乘一个函数,使之从主代码块中分离出来,代码就会变得更简洁。 2. 错误处理就是一件事,处理错误的函数不应该做其他的事,如果在抛出错误的时候需要做另外的一件事,那么应该好好考虑此处是否应该抛出错误。 3. 尽量使用异常代替错误码,返回错误码一般是一个枚举类,其他许多类都要依赖并使用它,使得在修改这个枚举类时造成了很大的负面压力,所以,尽量使用异常派生类。 --- #### 3.注释