1.整洁代码
这一章主要讲了糟糕的代码,混乱的代码带来的惨痛代价,影响开发进度,会导致代码难以维护(谁都不想去清理shi山),甚至有的公司因此而倒闭。
很多大佬例如C++语言发明者Bjarne Stroustrup的观点都不谋而合,“我喜欢优雅和高效的代码。代码逻辑应当直截了当,叫缺陷难以隐藏;尽量减少依赖关系,使之便于维护;依据某种分层战略完善错误处理代码;性能调至最优,省得引诱别人做没规矩的优化,搞出一堆混乱来。整洁的代码只做好一件事
2.有意义的命名
2.1介绍
软件中随处可见命名。我们给变量、函数、参数、类和封包命名。我们给源代码及源代码所在目录命名。我们给jar文件、war文件和ear文件命名。我们命名、命名,不断命名。既然有这么多命名要做,不妨做好它。下文列出了取个好名字的几条简单规则。
2.2名副其实(见名知意)
命名的时候尽量明确,让自己让他人见到这个命名就可以就可以大概知道这个变量的含义,让人更容易理解和修改代码。
2.3避免误导
程序员必须避免留下掩藏代码本意的错误线索。应当避免使用与本意相悖的词。不能用关键字或者一些专有的名词来进行命名,不要用类型最变量的结尾,即使是这个变量真属于这个类型。
提防使用不同之处较小的名称。
以同样的方式拼写出同样的概念才是信息。拼写前后不一致就是误导题。
2.4做有意义的区分
光是添加数字系列或是废话远远不够,即便这足以让编译器满意。如果名称必须相异,那其意思也应该不同才对。不仅仅是为了编译通过。注意,只要体现出有意义的区分,废话都是冗余。Variable一词永远不应当出现在变量名中。Table一词永远不应当出现在表名中。NameString 会比Name好吗?难道Name 会是一个浮点数不成?如果是这样,就触犯了关于误导的规则。设想有个名为Customer 的类,还有一个名为CustomerObject 的类。区别何在呢?哪一个是表示客户历史支付情况的最佳途径?
2.5使用读得出来的名称
人类长于记忆和使用单词。大脑的相当一部分就是用来容纳和处理单词的。单词能读得出来。人类进化到大脑中有那么大的一块地方用来处理言语,若不善加利用,实在是种耻辱。
2.6使用可搜索的名称
单字母名称和数字常量有个问题,就是很难在一大篇文字中找出来。找MAX_CLASSES_PER_STUDENT很容易,但想找数字7就麻烦了,它可能是某些文件名或其他常量定义的一部分,出现在因不同意图而采用的各种表达式中。如果该常量是个长数字,又被人错改过,就会逃过搜索,从而造成错误。
2.7避免使用编码
编码已经太多,无谓再自找麻烦。把类型或作用域编进名称里面,徒然增加了解码的负担。没理由要求每位新人都在弄清要应付的代码之外(那算是正常的),还要再搞懂另一种编码“语言”。这对于解决问题而言,纯属多余的负担。带编码的名称通常也不便发音,容易打错。比如匈牙利语标记法、成员前缀、接口和实现(实现类现在都是加后缀)
2.8避免思维映射
不应当让读者在脑中把你的名称翻译为他们熟知的名称。这种问题经常出现在选择是使用问题领域术语还是解决方案领域术语时。
2.9类名
类名和对象名应该是名词或名词短语,如 Customer、 WikiPage、Account和 AddressParser。避免使用Manager、Processor、Data或 Info这样的类名。类名不应当是动词。
2.10方法名
方法名应当是动词或动词短语,如postPayment、deletePage或save。属性访问器、修改器和断言应该根据其值命名,并依 Javabean标准1加上get、set和is前缀。
2.11不使用方言
2.12每个概念对应一个词
每个抽象概念定义一个词,并且一以贯之,并且避免将同一单词用于不同的概念
2.14涉及专业领域
2.15添加有意义的语境
很少有名称是能自我说明的——多数都不能。反之,你需要用有良好命名的类、函数或名称空间来放置名称,给读者提供语境。如果没这么做,给名称添加前缀就是最后一招了。
设想你有名为firstName、lastName、street、houseNumber、city、state和 zipcode的变量。当它们搁一块儿的时候,很明确是构成了一个地址。不过,假使只是在某个方法中看见孤零零一个state变量呢?你会理所当然推断那是某个地址的一部分吗?
3.函数
3.1短小
函数的第一规则是短小。第二规则还是短小(代码整洁之道作者的根据多年经验总结出)主要是针对代码块和缩进,if语句、else语句、while语句等,其中的代码块应该只有一行。该行大抵应该是一个函数调用语句。这样不但能保持函数短小,而且,因为块内调用的函数拥有较具说明性的名称,从而增加了文档上的价值。这也意味着函数不应该大到足以容纳嵌套结构。所以,函数的缩进层级不该多于一层或两层。当然,这样的函数易于阅读和理解。
3.2只做一件事
3.3每个函数一个抽象层级
要确保函数只做一件事,函数中的语句都要在同一抽象层级上。(视情况而定)
3.4switch语句
写出短小的switch语句很难’。即便是只有两种条件的 switch语句也要比我想要的单个代码块或函数大得多。写出只做一件事的switch语句也很难。Switch天生要做N件事。不幸我们总无法避开switch语句,不过还是能够确保每个switch都埋藏在较低的抽象层级,而且永远不重复。当然,我们利用多态来实现这一点。
3.5使用描述性的名称
函数越短小、功能越集中,就越便于取个好名字。
别害怕长名称。长而具有描述性的名称,要比短而令人费解的名称好。长而具有描述性的名称,要比描述性的长注释好。使用某种命名约定,让函数名称中的多个单词容易阅读,然后使用这些单词给函数取个能说清其功用的名称。
别害怕花时间取名字。你当尝试不同的名称,实测其阅读效果。在 Eclipse或IntelliJ等现代 IDE中改名称易如反掌。使用这些IDE测试不同名称,直至找到最具有描述性的那一个为止。
选择描述性的名称能理清你关于模块的设计思路,并帮你改进之。追索好名称,往往导致对代码的改善重构。
命名方式要保持一致。使用与模块名一脉相承的短语、名词和动词给函数命名。
3.6函数参数
最理想的参数数量是零(零参数函数),其次是一(单参数函数),再次是二(双参数函数),应尽量避免三(三参数函数)。有足够特殊的理由才能用三个以上参数(多参数函数)——所以无论如何也不要这么做。
参数不易对付。它们带有太多概念性。所以作者在代码范例中几乎不加参数。从测试的角度看,参数甚至更叫人为难。要编写能确保参数的各种组合运行正常的测试用例,是多么困难的事。如果没有参数,就是小菜一碟。如果只有一个参数,也不太困难。有两个参数,问题就麻烦多了。如果参数多于两个,测试覆盖所有可能值的组合简直让人生畏。
输出参数比输入参数还要难以理解。读函数时,我们惯于认为信息通过参数输入函数,通过返回值从函数输出。
3.6.1 一元函数的普通形式
3.6.2二元函数
3.6.3三元及以上的函数
如果函数看来需要两个、三个或三个以上参数,就说明其中一些参数应该封装为类了。例如
Circle makeCircle (double x, double y,double radius) ;
Circle makeCircle (Point center, double radius) ;
3.6.4参数列表
3.6.5动词与关键字
给函数取个好名字,能较好地解释函数的意图,以及参数的顺序和意图。
3.7分隔 指令与询问
函数要么做什么事,要么回答什么事,但二者不可得兼。函数应该修改某对象的状态,或是返回该对象的有关信息。两样都干常会导致混乱。
3.8使用异常代替返回错误码
从指令式函数返回错误码轻微违反了指令与询问分隔的规则。它鼓励了在if语句判断中把指令当作表达式使用。例如,if (deletePage(page)== E_OK)
这不会引起动词/形容词混淆,但却导致更深层次的嵌套结构。当返回错误码时,就是在要求调用者立刻处理错误。
if (deletePage(page) == E_OK) {
if (registry.deleteReference(page.name) == E_OK){
if (configKeys.deleteKey(page.name.makeKey() ) == E_OK){
logger.log ( "page deleted") ;
} else {
logger.log ( "configKey not deleted" ) ;
}
} else {
logger.log ( "deleteReference from registry failed" );
}
} else {
logger.log ( "delete failed");
return E_ERROR;
}
另一方面,如果使用异常替代返回错误码,错误处理代码就能从主路径代码中分离出来,得到简化:
try {
deletePage(page) ;
registry.deleteReference(page.name) ;
configKeys.deleteKey(page.name.makeKey());)
catch (Exception e){
l ogger.log(e.getMessage( ));
}
但是Try/catch代码它们搞乱了代码结构,把错误处理与正常流程混为一谈。最好把try和 catch代码块的主体部分抽离出来,另外形成函数。函数应该只做一件事。错误处理就是一件事。因此,处理错误的函数不该做其他事。这意味着(如上例所示)如果关键字try在某个函数中存在,它就该是这个函数的第一个单词,而且在catch/finally代码块后面也不该有其他内容。
public void delete (Page page) {
try {
deletePageAndAllReferences (page) ;
}
catch (Exception e){
logError (e);
}
}
private void deletePageAndAllReferences(Page page) throws Exception {
deletePage (page);
registry.deleteReference (page.name) ;
configKeys.deleteKey(page.name. makeKey());
}
private void logError(Exception e){
logger.log(e.getMessage());
}