`
T240178168
  • 浏览: 362617 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

读书:《代码整洁之道》

    博客分类:
  • java
阅读更多
1. 本书内容概要
核心观点:Bob大叔(即Robort.C.Martin,多本畅销书的作者,业界称其Bob大叔)认为软件质量,不仅依赖于架构及项目管理,而且与代码质量紧密相关。而代码质量与其整洁度成正比。
核心内容:Bob大师和Object Mentor的专家以这个强大团队十几年的经验,总结了如何在代码中达到clean code,即整洁和干净的代码的经验规则。
豹:从本书的字里行间,我可以感受到Bob大叔的语重心长、循循善诱、斩钉截铁:“专业性和技艺来自于驱动规程的价值观”,也能体会到大叔的严密谨慎也感受到了他的热情和激情。
豹:书中的这些规则条款显然是经过了实战检验,非常有效的。但我们依然不能忘记这些规则仍然是启发性规则,而不是金科玉律。如果执着于记住条款而不思考Bob大叔提出这些条款的理由,那么收获是很有限的。
2. 阅读建议和相关读物推荐
阅读建议:应该说本书关于命名、注释、格式、注释、类的整理是比较经典的,可以精读。17章是一个总结,可以拷贝重要内容在写程序时,回顾之。因为这些技巧并不高深,我们可以用实际例子来演练之,程序规模不要太大。500行左右即可。其他章节内容只是点到为止,并没有深入,其他章节内容涉及内容比较大而讲述并不深刻,可以快速阅读,了解重点。
推荐读物:这本书是敏捷系统的编程实践,可以与Bob大叔的另外一本《敏捷软件开发原则、模式、实践》结合阅读。具体的优化函数和类的技巧,可以参考《重构》,重构总结得比较全。涉及到设计模式的可以阅读《Head First设计模式》、《设计模式》(GOF)、《重构与模式》。另外也可与《.NET设计规范》一起对读,因为Bob在几个细微处的命名选择,提出了与微软大师们并不相同的建议,并且说明了这些取舍的理由。
3. 要整洁代码的代码
1> 将需求明确到机器可以执行的程度,就是编程要做的事,这种规约就是代码。
2> 糟糕的代码可能毁掉一家公司。
3> 混乱代码的代价是驱动生产力不断趋向零。
4> 整洁不仅与效率有关,而且关于企业的生存。
5> 什么样的代码是整洁代码。
4. 有意义的命名
4.1要“名副其实”;
1> 这件事情要严肃对待。
豹:的确如此啊。在起一个表意的名字上花时间是值得的,优秀程序员从细节做起。
2> 如果名称需要注释来补充,那就不是“名副其实”。
Demo:int d;//消逝的时间,以天计算
应该使用指明计量对象和计量单位的名称。
Int elapsedTimeInDays;
Int daysSinceCreation;
Int daysSinceModificatin;
Int fileAgeInDays’
3> 问题不在于代码的简洁度,而在于代码的“模糊度”。
豹:这里的意思是简短的代码,如果不能表达含义,也是不能做到“名副其实”。
Demo:
Java: pulic List<int[]> getThem(){
List<int[]> list1 = new ArrayList<int[]>();
For( int[] x: theList)
If(x[0]==4)
list1.add(x);
return list1;
}

这里的代码够简单了,但是没人知道 theList是什么东西、theList[0]的意思是什么、4是什么意义、以及返回list1该怎么用。
这就是作者所说的“模糊度”,因为意义比较模糊,所以这些代码也不“名副其实”。那么怎么改呢?应该根据这段代码的意图来修改这里的函数名,变量名,值的(4)含义(用常量)。
4.2命名要避免误导
   程序员必须避免留下掩藏代码本意的错误线索。
   Demo:accountList这个名字就不太好,因为list这个词在java中是一个类型,如果这个名字表达的类型或者含义不是list就不应该这样命名。
4.3做有意义的区分
       1> 不要用数字命名。
           Demo:a1,a2,a3。
           豹:某些特定的以数字命名还是可以的。比如byteArray2String。这里2同“To”。
        但一般在名称后在数字是不好的习惯。
2> 废话是另一种没有意义的区分。
     Demo:如有有一个类叫Product类,那么ProductInfo与ProctductData就是没有意义的区分,因为它们含义几乎一样。
     豹:如果约定Product是实体类,ProductInfo是纯数据,那么还是有意义的,可以接受的。但是同时还存在ProductData就明显不好了。并且这样还是增加了一些理解成本,因为从名字上来说是无法直接判断的。
    Demo: nameString不比name好。  
豹:原因很简单,name就其含义来说其类型一定是字符串。所以String是冗余的废话。这种程度的冗余我个人认为还是可以接受的,但是不能又出现nameStr、strName、string_name等。如果出现,说明命名规则有问题或者命名规则的执行有问题。
3> 使用读得出来的名字。
4> 使用搜索得出来的名字。
豹:比如数值型常量。可以起一个有意义的名字便于搜索。另外,一个好的名字往往可以更加准确的表达常量在特定场景里的含义。
5> 编避免使用编码。
a) 匈牙利命名法。
匈牙利命名法基本上没有必要使用。主要理由是它的长处在消减,而短处却十分明显。
长处减弱:现代的编译器已经比较智能了,完全可以无视类型的作用。
豹:比较赞同。但是对于弱类型的javascript来说,还是有其必要的。理由是因为它是弱类型,而为了避免类型转换自由带来的bug,保持类型前后缀还是有其作用的,这类类型前缀除了类型语义之外还有对其用法的约束。如果是纯粹的表达类型,那么大量的前后缀毫无疑问将对阅读带来障碍。
弱点:是个冗余。
豹:除了冗余之外,匈牙利命名法还有致命的缺点:就是变化可能使类型加入命名带来“副作用”:比如 phoneString或者是stringPhone。它强调了phone是string类型的,因此也就限制了phone。而phone可能需要设计或者转换为number型的。当这种转换发生时,可能需要更改phone的命名为phoneNumber,而导致重新命名的代价。或者需要与别的系统通信而导致phoneNumber与phoneString同时存在而导致理解偏差。
b) 成员前缀
不必使用m_前缀来标明成员变量。
豹:一般来说使用m_、_、m前缀来作为成员变量,主要的理由是便于在开发环境的智能提示中快速找到相应的变量。这个意图本身并不坏!但是当你出现需要命名前缀来提高查找速度的时候,往往说明了你的类过大了,违背了一些需要考虑的设计原则,比如“单一职责原则”。因此这个建议还是有深刻意义的,虽然我们不必遵守,但是要理解到这一点。
6> 接口和实现
               作者认为接口不需要以I开头,如果一定要选择一种表示接口的编码,作者宁愿选择用实现区分,而不是接口的前缀命名。
               Demo:IShapeFactory和SharpFactory之间,作者选择后者作为接口名。
豹:大家都知道按照《.net设计规范》来说,接口是应该用I前缀命名的。但是我们要理解到Bob大叔的理由:首先设计者可能虽然选择接口实现,但并不需要接口和抽象类的区分,他希望客户不关注这点(这里的设计者我理解为一般应用程序的设计者)。其次这也存在将前缀含义加入名称的一个“副作用”,当SharpFactory需要变化成抽象类时,前缀命名就是一个巨大的包袱了,你必须修改所有的名称。Bob大叔显然知道前缀保持一个约定习惯的好处,但是他更加看重可能发生变化时的缺点。而微软大牛们也显然知道前缀的“副作用”,但是他们站在整体.net架构的层次,他们可能认为作为接口、抽象类,这样的重要选择当然要慎重,这种慎重比判断错误时修改更加重要。
7> 避免思维映射
不要让读者在脑中把你的名称翻译为他们熟知的名称。
豹:这里的含义应该是避免这种思维固定翻译导致的程序误读。比如说一个循环去处理字符a,b,c,xxx。如果你把变量也取名为 a就不好了。
8> 每个概念对应一个词
给每个抽象概念选择一个词,并且一以贯之。
Demo:使用get,fetch,retrieve给多个类中的同种方法命名。
豹:这里其实也是一种前缀或者后缀命名法。但是意图不是表达类型,而是一类通用的语义,这个非常重要,可以极大的减少理解成本。大家注意《.net设计规范》中依然保留的一些前、后缀规范,主要目的是传达一致的语义。
9> 避免用双关语
避免将同一单词用于不同目的。统一术语用于不同概念,基本就是“双关语”了。
豹:这里是上8>的一个补充,8>运用不当就会产生这种“双关语”。同一个前后缀的语义必须是一致的。Demo:add表示增加或者连接两个值。那么集合中添加一个元素就不能用add了。因为含义已经不一样了。
10> 添加有意义的语境
使用前后缀给名称添加有意义的使用语境。
豹:这里再次说明前后缀还是有特定作用的,但是前后缀使用的原则是表达同一语义或者说语境。
4. 函数
4.1 函数要短小、再短小
1>  函数的第一条规则是要短小,第二条规则还是要短小。40年的经验告诉作者,函数就是要短小。
2> 20行封顶最佳。
3> 每个函数都一目了然,每个函数都做一件事。而且每个函数都依序把你带到下一个函数,这就是函数应该达到的短小程度。
4.2 只做一件事
如何判断函数是做了一件事?
豹:用作者的话说是函数所做的事情都在一个抽象层级,叫“只做一件事”。如果各个层级抽象混杂在一起,显然就做了不止一件事了。但是什么叫一个抽象层级呢?取决于函数解决特定问题、以及这个问题应有的“视角层次”和这个视角层次对应的“边界”。举个例子说明“视角层次”和“边界”:问“明源软件研发体系的整体组织架构是怎样的?” 观察对象:“明源软件公司”期望看到“研发体系的整组织架构”。答:“平台、陈立刚、产品、测试。”显然陈立刚在整体组织架构这个边界是看不到的,他是平台内部的。首先我们期望观察的结构是整体组织架构,因此站在这个视角层级来看,平台、产品、测试、这些都是一个整体。然后分别站在平台、产品、测试再往下看。作者推荐了使用层次的do来表达函数要做的事,这样,可以有效避免函数中不同的抽象层级混杂。
不管如果,函数只做一件事情是最重要的实践,需要好好理解。
4.3 每个函数一个抽象层级
函数中混杂不同的抽象层级,往往让人迷惑。读者无法判断哪些是基础概念哪些是细节。
豹:读者无法提纲挈领的代价是,它无法快速学习,快速理解。从而为更多的bug埋下隐患。
4.4 使用描述性名称
DEMO:testtableHtmlSetupTeardownIncluder.render。
豹:如果长一点的名称可以更加清晰,不要犹豫,用清晰的吧。
4.5 函数参数
最好是0,其次是1,再次是2,避免3个及以上的参数个数。
豹:参数过多会使得客户程序员上手的代价加大,优秀代码的可能性降低。

参数与函数名位于不同的抽象层级,它要求你必须了解目前并不特别重要的细节。

输出参数比输入参数更加难以理解。
豹:一般只有在特定的情况下才使用输出参数。比如C#中 Int.TryParse(),为了避免使用无必要的异常表达错误,提供了输出参数。

标识参数丑陋不堪。向函数传入布尔值简直就是骇人听闻的做法。
豹:这里的标志参数指的是通过特定的参数来控制函数的行为。比如 Log(xxxxx,true)。这里true/false控制函数是否记录日志。标识参数明显违背了函数只做一件事的原则。
另外,标识参数有一些变体,比如传输一个整型值,根据具体值来做判断到底该做何事。

二元参数比一元参数更难理解。
豹:作者举的例子很经典的。assertEquals(excepted,acutal)。经常会把参数的位置搞反!的确是的,有2个参数读者和客户就要考虑顺序问题,如果命名又不佳的话,就会经常弄错。

三元参数比二元更加难懂。

如果参数看起来需要两个、三个、或者三个以上参数就说明一些参数应该封装为类。
豹:的确如此,至少达到3个就要认真考虑是否应该合并参数,是否过于陷入细节了。

函数要无副作用
豹:不要做除了函数名表达含义以外的事。


4.6 抽离Try/Catch代码
将try/catch代码隔离出来,避免影响主程序逻辑。
Demo:
Try{
DeletePage(page);//DeletePage是一个方法
}
Catch(Exception e){
   LogError(e);//LogError是一个方法
}

错误处理就是一件事。
豹:try/catch总是单独出现的,里面可以最好不要包含普通语句,如上例。

别重复自己,重复是一切邪恶的根源。
豹:这个显然很重要,但是并不容易做到。重复有明显的重复,有隐蔽的逻辑重复,结构重复。总之如果能够在消除重复上更进一步、多做一些,对个人职业生涯和公司都有好处,并且可以很快感受到。
4.7 如何写出这样的函数
测试,编写代码完成功能,整理修改代码。
豹:测试是整理的基础,另外也不要一开始写代码就开始整理,可以完成功能后,花时间整理使之整洁、干净。对于一个新的项目或者一个大型项目整理代码的代价是完全不一样的。首先必须确认,单元测试是整理的基础,没有测试,整理就无从谈起。对于大型、巨大型项目来说,即使有测试来保证,整理旧代码还是有较大的成本的,这点不能无视。
5. 注释
5.1 核心观念
注释是一种必须的恶。注释的恰当用法是弥补我们在用代码表达意图时遭遇的失败
1 ) 注释会撒谎。
豹:这个比较令人费解。但是它真实的存在于我们的系统中,并且是大量的存在。原因很简单,程序员不能坚持维持注释,程序存在的时间越久,注释的可信度、可读程序就很低。作者认为,程序员虽然有保持注释精读的责任,但是更应该做的是整理代码,减少注释,注释过多往往是一种程序“腐化的坏味道”。
2) 注释不能美化糟糕的代码。
5.2 好的注释
1) 不可省略的涉及到法律的信息
豹:比如开源协议。
2) 提供信息的注释
豹:在作者的观念来看,提供信息的注释应该是用好名称不易传达的补救措施。
Demo:
// returns an instance of the responder being tested.
Protected abstract Responder responderInstance();
不如:
Protected abstract Responder responderBeingTested;
如果找不到responderBeingTested这样的好名字,则用注释提供信息是好的做法。但是应该首先尽力去想一个可以表达意图的有意义的名字。
3) 对意图进行解释
豹:对某些可以选择的实现决定进行解释。
4) 阐释
豹: 将一些晦涩难明的参数或者返回值的意义编译为更加可读的形式。
5) 警示
豹:这里指的是一些特定行为的代码的注释。比如某个测试可能会运行很长时间之类的注释。
6) TODO注释
豹:未完成的列表。完成后要删除掉。
7) 放大
豹:放大、明示某些特殊用法的合理性。
5.3 坏的注释
1) 喃喃自语
这种情况大量存在,属于程序员的只说自话,基本是垃圾代码的借口或者错误决策的修正。
2) 多余的注释
豹:大量存在,没有什么意义的废话注释。
3) 误导性注释
豹:不够精确或者干脆写错了
4) 循规试注释
豹:指的是应文档化工具的需求就添加的本来不需要注释的注释。
5) 日志试注释
豹:指的是本代码文件的修改历史类,将每天的修改记录写上,这完全没有必要,可以被现代源码管理工具取代。它影响了代码阅读。
6) 废话注释
7) 可怕的废话注释
豹:作者用了可怕,呵呵。
8) 能用函数或者变量的时候就不用注释
9) 位域标记
豹:这类注释用于标记一个特别的位置。这种用法应该在特定的情况下使用,但是多数属于不必要的滥用。
10) 括号后的注释
豹:这里指的是代码太长了,在}后添加注释表示×××代码段结束。这类注释一般是程序需要整理的标志。
11) 归属于署名
豹:现在源码管理可以取代,基本不需要了。
12) 注释掉的代码
豹:作者对此深恶痛绝。
13) HTML注释
豹:作者认为令人讨厌。
14) 非本地信息
15) 信息过多
16) 不明显的联系
6. 格式
豹:作者对格式的研究也是非常严谨的。以多个著名优秀开源项目的实际代码格式分析来研究诸如一行应该多少个字符,应该空几个这样的看似细小的问题。其中对“顺序”、“缩进”的处理、这些处理对读者理解之间的影响研究我个人觉得较有价值。通过对类的成员:共有、是有成员变量、方法进程“顺序排列”,通过“缩进”或者“不缩进”,表达语义的远近,还是非常有借鉴意义的。

7. 错误处理
豹:这部分内容作者写的比较浅显。可以参考《.net设计规范》的错误处理章节。另外也可以参考我早期一篇博文:《Map3.0我们该如何处理异常》:
http://bbs1.mysoft.net.cn/space/viewspacepost.aspx?postid=2154。
这里讨论范围相对深入、全面一些。

8. 边界&单元测试
豹:这2章节感觉凑字数。不够好。可以读作者另外一本书《敏捷软件开发原则、模式、实践》来了解。
9. 类
9.1 类的组织
公共静态常量、私有静态变量、私有实体变量、公共方法、私有工具函数。
9.2 类应该短小
对于函数,我们通过计算代码行数衡量大小。对于类,我们采用不同的衡量方式,计算权责。
豹:权责同职责,也同变化的原因。

1> 类应该符合单一权责原则
2> 类应该内聚
3> 保持内聚会得到需要短小的类
豹:关于类的组织这个话题比较大,比较原则性。注意理解不同设计原则之间的因果依赖关系。
10 . 其他章节
     其他章节内容都比较短,内容不够充实,不建议花过多时间阅读。








 






分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics