读《代码整洁之道》笔记

说明:
1、包括以下几个部分:命名、函数、注释、格式、对象与数据结构、错误处理、边界
2、该笔记会保持更新

一、命名

1、一旦发现有更好的名称,就换掉旧的。
2、它该告诉你,它为什么会存在、做什么事、怎么用。
3、如果名称需要注释来补充,那就不算是名副其实。
4、应该指明计量对象和计量单位的名称。
5、降低代码的模糊度:上下文在代码中未被明确体现的程度。
6、避免留下掩藏代码本意的错误线索。
7、废话都是冗余。Variable不应该出现在变量名中,Table不应该出现在表名中
8、使用可搜索的名称:单字母名称和数字常量很难在一大篇文字中找出来,如果被修改了,更难找到。
9、名称长短应与其作用域大小相对应
10、成员前缀:不必用m_前缀来表明成员变量应该把类和函数做得足够小,消除对成员前缀的需要。
11、接口和实现:前导字母I被滥用说好听点是干扰,不好听就是废话。如果接口和实现必须选一个来编码的话,可以使用xxImp,Cxx
12、类名和对象名应该是名词或名词短语。
13、方法名应该是动词或动词短语。属性访问器、修改器和断言应该根据其值命令,应依Javabean标准加上get、set、is前缀。重载构造器时,使用描述了参数的静态工程方法名
14、每个概念对应一个词:Eclipse和IntelliJ之类现代编程环境提供了与环境相关的线索,比如某个对象能调用的方法列表。不过要注意,列表中通常不会给出你为函数名和参数列表编写的注释。如果参数名称来自函数声明,你就太幸运了。函数名称应当独一无二,而且要保持一致,这样你才能不借助多余的浏览就找到正确的方法。
15、避免将同一单词用于不同的目的。同一术语用于不同的概念,基本上就是双关语。
16、如果不能用程序员熟悉的术语来给手头的工作命名,就采用从所涉问题领域而来的名称吧。至少,负责维护代码的程序员就能去请教领域专家了。
17、添加有意义的语境:可以给名称添加前缀,以便说明该名称所属一个大的结构中。更好的方案是创建名为Address的类。这样,即便是编译器也会知道这些变量隶属某个更大的概念了。
18、为什么要搞得IDE没法帮助你?

二、函数

1、函数的第一规则是要短小。第二条规则是还要更短小。函数不该有100行那么长,20行封顶最佳。
2、每个函数都依序把你带到下一个函数。
3、编写函数毕竟是为了把大一些的概念(换言之,函数的名称)拆分为另一抽象层上的一系列步骤。
4、要判断函数是否不止做了一件事,还有一个方法,就是看是否能再拆出一个函数。
5、让代码读起来像是一系列自顶向下的TO起头段落是保持抽象层级协调一致的有效技巧。
6、怎样简化switch:将switch语句埋到抽象工厂底下,不让任何人看到。该工厂使用switch语句为Employee的派生物创建适当的实体,而不同的函数,如calculatePay、isPayday和deliverPay等,则藉由Employee接口多态地接受派遣。
7、函数越短小、功能越集中,就越便于取个好名字。
8、命名方式要保持一致。使用与模块名一脉相承的短语、名词和动词给函数命名。
9、函数参数:最理想的参数数量是零(零参数函数),其次是一(单参数函数),再次是二(双参数函数),应尽量避免三(三参数函数)。有足够特殊的理由才能用三个以上参数(多参数函数)——所以无论如何也不要这么做。
从测试的角度看,参数甚至更叫人为难。想想看,要编写能确保参数的各种组合运行正常的测试用例,是多么困难的事。如果没有参数,就是小菜一碟。输出参数比输入参数还要难以理解。读函数时,我们惯于认为信息通过参数输入函数,通过返回值从函数中输出。我们不太期望信息通过参数输出。所以,输出参数往往让人苦思之后才恍然大悟。
10、极有用的单参数函数形式,那就是事件(event)。使用该参数修改系统状态。如果函数要对输入参数进行转换操作,转换结果就该体现为返回值。
11、避免使用布尔值作为函数传入,如果这样做,无非想说明函数不止做一件事。推荐分解该函数。
12、二元函数:即便是如assertEquals(expected,actual)这样的二元函数也有其问题。你有多少次会搞错actual和expected的位置呢?这两个参数没有自然的顺序。expected在前,actual在后,只是一种需要学习的约定罢了。
13、参数对象:如果函数看来需要两个、三个或三个以上参数,就说明其中一些参数应该封装为类了。
14、输出函数:应避免使用输出参数。如果函数必须要修改某种状态,就修改所属对象的状态吧。
15、分隔指令与询问:比如if的表达式中包含了set和is
16、使用异常替代返回错误码:如果使用异常替代返回错误码,错误处理代码就能从主路径代码中分离出来。
17、抽离Try/Catch代码块,它们搞乱了代码结构,把错误处理与正常流程混为一谈。最好把try和catch代码块的主体部分抽离出来,另外形成函数。
18、错误处理:函数应该只做一件事。错误处理就是一件事。因此,处理错误的函数不该做其他事。这意味着(如上例所示)如果关键字try在某个函数中存在,它就该是这个函数的第一个单词,而且在catch/finally代码块后面也不该有其他内容。
19、异常错误码类:返回错误码通常暗示某处有个类或是枚举,定义了所有错误码。其他许多类都得导入和使用它。当Error枚举修改时,所有这些其他的类都需要重新编译和部署。使用异常替代错误码,新异常就可以从异常类派生出来,无需重新编译或重新部署。
20、重复的危害:问题,因为代码因此而臃肿,且当算法改变时需要修改4处地方。而且也会增加4次放过错误的可能性。比如数据库范式都是为消灭数据重复而服务。面向对象编程将代码集中到基类,从而避免了冗余。
21、结构化编程:Dijkstra认为,每个函数、函数中的每个代码块都应该有一个入口、一个出口。遵循这些规则,意味着在每个函数中只该有一个return语句,循环中不能有break或continue语句,而且永永远远不能有任何goto语句。
只要函数保持短小,偶尔出现的return、break或continue语句没有坏处,甚至还比单入单出原则更具有表达力。

三、注释

1、如果你发现自己需要写注释,再想想看是否有办法翻盘,用代码来表达。
2、程序员不能坚持维护注释,因此注释会撒谎。代码在变动,在演化,注释常常会与其所描述的代码分离开来
3、程序员应当负责将注释保持在可维护、有关联、精确的高度。我同意这种说法。但我更主张把力气用在写清楚代码上,直接保证无须编写注释。
4、唯一真正好的注释是你想办法不去写的注释。
5、有时,用于警告其他程序员会出现某种后果的注释也是有用的。
比如的@Ignore属性来关闭测试用例。
6、有时,有理由用//TODO形式在源代码中放置要做的工作列表。
无论TODO的目的如何,它都不是在系统中留下糟糕的代码的借口。
你不会愿意代码因为TODO的存在而变成一堆垃圾,所以要定期查看,删除不再需要的。
7、日志式注释:在没有源代码控制系统的前提下,该类注释有价值,这种冗长的记录只会让模块变得凌乱不堪,应当全部删除。
8、位置标记:程序员喜欢在源代码中使用多个斜杠标记某个特别位置。如果标记栏不多,就会显而易见。所以,尽量少用标记栏,只在特别有价值的时候用。如果滥用标记栏,就会沉没在背景噪音中,被忽略掉。
9、括号后面的注释:在括号后面放置特殊的注释。比如在while语句结尾使用//endwhile。尽管这对于含有深度嵌套结构的长函数可能有意义,但只会给我们更愿意编写的短小、封装的函数带来混乱。如果你发现自己想标记右括号,其实应该做的是缩短函数。
10、归属与署名:有时会在代码中添加/Add by wxmgcs/,认为,这种注释大概有助于他人了解应该和谁讨论这段代码,时间一长,越来越不正确。源代码控制系统是这类信息最好的归属地。
11、删除注释掉的代码:优良的源代码控制系统可以为我们记住不要的代码。我们无需再用注释来标记,删掉即可。
12、假如你一定要写注释,请确保它描述了离它最近的代码。别在注释中添加有趣的历史性话题或者无关的细节描述。

四、格式

1、在封包声明、导入声明和每个函数之间,都有空白行隔开。这条极其简单的规则极大地影响到代码的视觉外观。每个空白行都是一条线索,标识出新的独立概念。
2、关系密切的概念应该互相靠近。显然,这条规则并不适用于分布在不同文件中的概念。除非有很好的理由,否则就不要把关系密切的概念放到不同的文件中。实际上,这也是避免使用protected变量的理由之一。可以避免迫使读者在源文件和类中跳来跳去。
3、变量声明应尽可能靠近其使用位置。因为函数很短,本地变量应该在函数的顶部出现。
4、关于实体变量应该放在哪里,争论不断。在C++中,通常会采用所谓“剪刀原则”(scissorsrule),所有实体变量都放在底部。而在Java中,惯例是放在类的顶部。重点是在谁都知道的地方声明实体变量。大家都应该知道在哪儿能看到这些声明。
5、相关函数:若某个函数调用了另外一个,就应该把它们放到一起,而且调用者应该尽可能放在被调用者上面。若坚定地遵循这条约定,读者将能够确信函数声明总会在其调用后很快出现。
6、一行代码建议45个左右的字符。
7、空格字符加强了分隔效果。比如赋值语句的左边和右边
8、缩进规则
9、确保空范围体的缩进,用括号包围起来。
10、制订了一套编码风格:商定在什么地方放置括号,缩进几个字符,如何命名类、变量和方法,如此等等,然后,把这些规则编写进IDE的代码格式功能,接着就一直沿用。这些规则并非全是每位成员喜爱的;但它们是团队决定了的规则。

五、对象与数据结构

1、对象与数据结构之间的二分原理:过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数。面向对象代码便于在不改动既有函数的前提下添加新类。
过程式代码难以添加新数据结构,因为必须修改所有函数。面向对象代码难以添加新函数,因为必须修改所有类。
2、得墨忒耳律(TheLawofDemeter)认为,模块不应了解它所操作对象的内部情形。对象隐藏数据,曝露操作。这意味着对象不应通过存取器曝露其内部结构,因为这样更像是曝露而非隐藏其内部结构。更准确地说,得墨忒耳律认为,类C的方法f只应该调用以下对象的方法:由f创建的对象;作为参数传递给f的对象;由C的实体变量持有的对象。方法不应调用由任何函数返回的对象的方法。
3、混杂:混淆有时会不幸导致混合结构,一半是对象,一半是数据结构。这种结构拥有执行操作的函数,也有公共变量或公共访问器及改值器。
4、数据传送对象:最为精练的数据结构,是一个只有公共变量、没有函数的类。这种数据结构有时被称为数据传送对象,或DTO(DataTransferObjects)。DTO是非常有用的结构,尤其是在与数据库通信、或解析套接字传递的消息之类场景中。在应用程序代码里一系列将原始数据转换为数据库的翻译过程中。
5、对象曝露行为,隐藏数据。便于添加新对象类型而无需修改既有行为,同时也难以在既有对象中添加新行为。数据结构曝露数据,没有明显的行为。便于向既有数据结构添加新行为,同时也难以向既有函数添加新数据结构。

六、错误处理

1、错误处理很重要,但如果它搞乱了代码逻辑,就是错误的做法。
2、使用异常而非返回码:遇到错误时,最好抛出一个异常。
3、在某种意义上,try代码块就像是事务。catch代码块将程序维持在一种持续状态,无论try代码块中发生了什么均如此。所以,在编写可能抛出异常的代码时,最好先写出try-catch-finally语句。这能帮你定义代码的用户应该应该期待什么,无论try代码块中执行的代码出什么错都一样。
4、使用不可控异常:可控异常的代价就是违反开放/闭合原则。比如大型系统的调用层级。顶端函数调用它们之下的函数,逐级向下。假设某个位于最底层级的函数被修改为抛出一个异常。如果该异常是可控的,则函数签名就要添加throw子句。这意味着每个调用该函数的函数都要修改,捕获新异常,或在其签名中添加合适的throw子句。以此类推。最终得到的就是一个从软件最底端贯穿到最高端的修改链!封装被打破了,因为在抛出路径中的每个函数都要去了解下一层级的异常细节。既然异常旨在让你能在较远处处理错误,可控异常以这种方式破坏封装简直就是一种耻辱。
5、给出异常发生的环境说明:你抛出的每个异常,都应当提供足够的环境说明,以便判断错误的来源和处所。比如在Java中,你可以从任何异常里得到堆栈踪迹(stacktrace);然而,堆栈踪迹却无法告诉你该失败操作的初衷。应创建信息充分的错误消息,并和异常一起传递出去。在消息中,包括失败的操作和失败类型。如果你的应用程序有日志系统,传递足够的信息给catch块,并记录下来。
6、依调用者需要定义异常类:对错误分类有很多方式。可以依其来源分类:是来自组件还是其他地方?或依其类型分类:是设备错误、网络错误还是编程错误?不过,当我们在应用程序中定义异常类时,最重要的考虑应该是它们如何被捕获。
7、别返回null值:如果你打算在方法中返回null值,不如抛出异常,或是返回特例对象。如果你在调用某个第三方API中可能返回null值的方法,可以考虑用新方法打包这个方法,在新方法中抛出异常或返回特例对象。
比如使用空列表代替null。
8、别传递null值
9、整洁代码是可读的,但也要强固。可读与强固并不冲突。如果将错误处理隔离看待,独立于主要逻辑之外,就能写出强固而整洁的代码。做到这一步,我们就能单独处理它,也极大地提升了代码的可维护性。

七、边界

1、边界上的接口(Map)是隐藏的。它能随来自应用程序其他部分的极小的影响而变动。
建议不要将Map(或在边界上的其他接口)在系统中传递。如果你使用类似Map这样的边界接口,就把它保留在类或近亲类中。避免从公共API中返回边界接口,或将边界接口作为参数传递给公共API。
2、不要在生产代码中试验新东西,而是编写测试来遍览和理解第三方代码。JimNewkirk把这叫做学习性测试(learningtests)。在学习性测试中,我们如在应用中那样调用第三方代码。我们基本上是在通过核对试验来检测自己对那个API的理解程度。测试聚焦于我们想从API得到的东西。
3、无论如何我们都得学习要使用的API,而编写测试则是获得这些知识的容易而不会影响其他工作的途径。学习性测试是一种精确试验,帮助我们增进对API的理解。
4、当第三方程序包发布了新版本,我们可以运行学习性测试,看看程序包的行为有没有改变。边界。不使用这些边界测试来减轻迁移的劳力,我们可能会超出应有时限,长久地绑在旧版本上面。
5、使用尚不存在的代码:将已知和未知分隔开的边界。在代码中总有许多地方是我们的知识未及之处。有时,边界那边就是未知的(至少目前未知)。有时,我们并不往边界那边看过去。
编写我们想得到的接口,好处之一是它在我们控制之下。这有助于保持客户代码更可读,且集中于它该完成的工作。
比如ADAPTER封装了与API的互动,也提供了一个当API发生变动时唯一需要改动的地方。
6、边界上的代码需要清晰的分割和定义了期望的测试。应该避免我们的代码过多地了解第三方代码中的特定信息。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值