代码整洁之道1-7【读书笔记】

第一章

整洁代码的必要性和不同人对整洁代码的定义

第二章 (有意义的命名)

变量、函数、参数、类、封装包、源代码及源代码所在的目录、jar文件、war文件等等。

名副其实

如果名称需要注释来补充,那就不是名副其实。

避免误导

比如某些系统或者语言的预留关键字,或者选小写字母L和大写字母O,特别是组合使用想是常量壹和零。

做有意义的区分

比如a1、a2、a3…aN没有表名作者的意图用具体的参数名称比如source、destination会更好;

废话是另一种没有意义的区分,比如Product类、ProductInfo类、ProductData类,名称不同意义却无区别。info,data就像a
、an、the一样是意义含混的废话,Variable一词永远不要出现在变量名中。对比:NameString和Name,Custom类和CustomerObject类,money和moneyAmount,message和theMessage也没有区别。

使用读的出来的名称

就是正常的能念出来的名词,别是什么不通顺的 “皮埃死极翘” 整数

使用可搜索的名称

数字7不好找,但是常量MAX_CLASSES_PRE_STUDENT就好找,同样字母e也不好找,单字母名称仅用于短方法中的本地变量。名称长短应与其作用域大小相对应。若变量或常量多处使用,应便于搜索。

避免使用编码

匈牙利语标记法

增加阅读难度 ,BASIC早期版一个字母加上一个数字,Fortran首字母体现类型;

成员前缀

m_前缀等,人们很快学会无视前缀只看到名称中有意义的部分。

接口和实现

IShapeFactory和ShapeFactory 首字母I是干扰,改为ShapeFactory和ShapeFactoryImpl更好。

避免思维映射

不应当让读者在脑中把你的名称翻译为他们熟知的名称,常出现在是使用问题领域术语还是解决方案领域术语。明确是王道。

类名

类名和对象名称应该是名词或名词短语
如Customer、WikiPage、Account、AddressParser。避免使用Manager、Processor、Data、Info这样的类名,不应该是动词。

方法名

方法名应该是动词或动词短语。
如postPayment、deletePage、save。访问属性应该根基javabean标准加上get、set、is前缀。
可以考虑将相应的构造器设置为private,强制使用这种命名手段。

最好是类名加方法名是个完整的语义。

别扮可爱

宁可明确,毋为好玩,言到意到,意到言到。俚语,俗语等不可取。

每个概念对应一个词

fetch、retrieve和get是一个概念,不同的类中相同概念的方法用同一个词。controller,manager,driver是一个概念还是不同的概念?所以,一以贯之地命名吧。

别用双关语

不要同一单词用于不同目的,应遵循一词一义。多个类中有add方法,可以,但是这时有个类名是add就不妥。insert,append都可替换。同一术语用于不同概念就是双关。

使用解决方案领域名称

毕竟只有程序员才读代码。计算机科学术语、算法名、模式名、数学术语等。

使用源自所涉问题领域名称

如果不能使用程序员熟悉的术语命名就用所涉及问题领域的名称。

添加有意义的语境

多数名称不能自我说明,需要良好命名的类、函数或名称空间来给读者提供语境,实在不行,给名称添加前缀是最后一招。

不要添加无用的语境

在某个应用(GSD)里给每个类添加一样的GSD前缀就不好。只要短名称足够清楚,就比长名称好,别给名称添加不必要的语境。

总结

我们把代码写的就像词句篇章、至少是表和数据机构(词句并非总是呈现数据的最佳手段)。

第三章(函数)

短小

每个函数只说一件事,三四行,依序待到下一个函数。if、else、while语句等,其中的代码块应只有一行函数调用语句。这样保证函数短小且块内调用的函数拥有较具说明性的名称,增加文档的价值。所以函数的缩进层级不该对于一层或两层,抑郁阅读和理解。

只做一件事

做好这件事,只做这一件事。看能否再拆出一个函数。只做一件事的函数无法被合理地切分为多个区段。

每个函数一个抽象层级

要确保只做一件事,函数中的语句都要在同一抽象层级上。自订向下读代码,让每个函数后面跟着位于下一抽象层级的函数,这样查看函数列表就能循抽象层级向下阅读。就像一系列TO起头的段落,每一段都面熟当前抽象层级,并引用位于下一抽象层级的后续TO起头段落。

switch语句

短小的switch很难,所以确保switch埋藏在较低的抽象层级,而且永远不重复。

使用描述性的名称

长而具有描述性的名称比短而让人费解的名称好,比描述性的长注释好。使用某种命名约定,让函数名称中的多个单词容易阅读,然后使用这些单词给函数取个能说清其功用的名称。

函数参数

理想的参数数量是零参,其次是一,再次是二,应避免三。有足够的理由才能用三个以上参数。

一元函数的普遍形式

1、转换:可能是操作改参数,将其转换为其他东西再输出返回值。
2、事件:有输入参数无输出参数,使用该参数修改系统状态。

标识参数

向函数传入布尔值简直是骇人听闻。表明该函数不止做一件事,应该把函数一分为二。

二元函数

不算恶劣,也得会编写,尽量利用一些机制转换一元函数。

三元函数

并不险恶,需要费点神,有时候也值得。

参数对象

如果需要两个、三个、或以上参数,说明其中一些参数应该封装为类。

参数列表

有时要向函数传入数量可变的参数。可变参数的函数可能是一元、二元、甚至三元。

动词与关键字

对于一元函数,函数和参数应当形成一种非常良好的动词/名词对形式。比如write(name)或者writeField(name)。
二元函数assertExpectedEqualsActual(expected,actual)。

无副作用

承诺只做一件事,但还是会做其他被藏起来的事。会对自己类中的变量做出未能预期的改动,或者把变量搞成反向函数传递的参数或是系统全局变量,导致时序性耦合及顺序依赖。应避免使用输出参数。如果函数必须要修改某种状态,就修改所属对象的状态吧。
------不理解可看书上举例

分割指令与询问。

函数要么做什么事,要么回答什么事,二者不可兼得。函数应该修改某对象的状态,或返回改对象的有关信息。两样都干会导致混乱。真正的解决方案是把指令与询问分隔开来。

使用异常替代返回错误码

***指令式函数***返回错误码违反了指令与询问分隔的规则,鼓励了在if语句判断中把指令当表达式使用,导致更深层次的嵌套结构,返回错误码就是要求调用者立刻处理错误了。如果使用异常替代,错误处理代码就能从主路径代码中分离出来,得到简化

抽离try/catch代码块

try/catch代码块会搞乱代码结构,把错误处理与正常流程混为一谈。最好把try/catch代码块抽离出来,另外形成函数。

错误处理就是一件事

错误处理的函数不该做其他事。

Error.java依赖磁铁

错误码意味着有个类或者枚举,定义了所有的错误码。这个类就是一块依赖磁铁,修改时需要重新便已部署,对Error类造成了负面压力。使用异常替代错误码,新异常可以从异常类派生出来,无需重新编译或重新部署。

别重复自己

造成代码臃肿,修改时需要多处修改。消除重复提高可读性。

结构化编程

每个函数,函数中的每个代码块都应该有一个入口一个出口,只有一个return,循环中不能有break和continue语句,永远不能有goto语句。但对于小函数这些规则助益不大,函数短小,偶尔出现return、break和continue没有坏处,比单入单出更具表达力。goto只在大函数中才有道理。

如何写出这样的函数

一开始就写冗长复杂,太多缩进,嵌套循环。写完遵从这些规则就打磨改吧。
我还是觉得如果一开始能抽取出来就抽取,不过后面可能也得调整,因为没写完可能存在考虑不周的情况。

第四章(注释)

注释的恰当使用是弥补我们用代码表达意图时遭遇的失败。注释总是一总失败,因为注释会撒谎。尽管有时也需要注释,但是应该尽量减少注释量。

注释不能美化糟糕的代码

与其花时间编写解释你搞出的代码的注释,不如花时间清洁那堆糟糕的代码。

用代码来阐述

用代码解释你大部分的意图,很多时候,简单到只需要创建一个与注释所言同一事物的函数即可。

好注释

法律信息

比如:版权及著作权声明。这类注释尽量指向一份标准许可或其他外部文档,而不要把所有条款放到注释中。

提供信息的注释

比如,解释某个抽象方法的返回值,或者正则表达式的解释等。

对意图的解释

注释不仅提供有关实现的有用信息,还提供某个决定后面的意图。

阐述

有时,注释把某些晦涩难懂的参数或返回值的意义翻译为某种可读形式,也是有用的。通常,更好的方法是尽量让参数或返回值自身就足够清楚;但是如果参数或返回值是某个标准库的一部分,或是你不能修改的代码,帮助阐述其含义就会有用。

警示

用于警示其他程序员会出现某种后果的注释也是有用的。

TODO注释

用//TODO形式在源码中放置要做的工作列表。是一种程序员认为应该做,但由于某些原因目前还没做的工作。

放大

注释可以用来放大某种看起来不合理之物的重要性。

公共API中的Javadoc

没有什么比被良好描述的公共API更有用和令人满意了。如果你在编写公共API,就该为它编写良好的Javadoc

坏注释

大多数注释都属于此类。通常,坏注释都是糟糕的代码的支撑或借口,或者对错误决策的修正,基本上等于程序员的自说自话。

喃喃自语

如果只是你觉得应该或者因为过程需要就添加注释,那就是无谓之举。

多余的注释

并不能比代码本身提供更多的信息。

误导性注释

会导致代码的错误调用。

循规式注释

所谓每个函数都要有Javadoc或每个变量都要有注释的规矩是愚蠢可笑的。

日志式注释

有人会在每次编辑代码时,在模块开始处添加一条注释。这种注释就像是一种记录每次修改的日志。会让模块凌乱不堪。

废话式注释

用整理代码的决心替代创造废话的冲动吧。

可怕的废话

javadoc里也可能有废话。

能用函数或变量时就别用注释

重构代码呗。

位置标记

比如 //Actions ///

括号后面的注释

带来混乱 {
}//while 等

归属与署名

/added by ee/
源码控制系统是这类信息最好的归属地。

注释掉的代码

注释掉的代码以后可能有用,但是我们有源码控制系统啊,比如Git。

html注释

源代码注释中的HTML标记是一种厌物。
最好是抽取出来,展示到网页

非本地信息

请确保注释描述了离它最近的代码。

信息过多

别在注释中添加有趣的历史性话题或无关的细节描述。

不明显的关系

注释及其描述的代码之间的联系应该显而易见。

函数头

短函数不需要太多描述。

非公共代码中的Javadoc

虽然Javadoc对于公共API有用,但是非公共用途就让人厌恶了。

第五章(格式)

底层代码保持整洁,团队应一致统一采用一套简单的格式规则。

格式的目的

代码功能会被修改,但可读性却对修改、扩展、维护产生影响。

垂直格式

向报纸学习

从上往下,从左往右读。名称应当简单且一目了然。源文件最顶部应该给出高层次概念和算法。细节应该往下渐次展开,知道找到源文件中最底层的函数和细节。

概念间垂直方向上的区隔

每行展现一个表达式或一个句子,每组代码行展示一条完整的思路。这些思路用空白行区隔开来。

垂直方向上的靠近

空白行隔开了概念,那么靠近的代码行则暗示了它们之间的紧密关系。所以紧密相关的代码应该互相靠近。

垂直距离

变量声明应尽可能靠近其使用位置。
实体变量应该在类的顶部声明。
相关函数(某个函数调用了另一个)放一起。
概念相关的代码应该放一起。

垂直顺序

自上向下展示函数调用依赖顺序,被调用的函数应该放在执行调用的函数下面。

横向格式

应尽力保持代码行短小。

水平方向上的区隔与靠近

使用空格字符串将彼此紧密相关的事物连接到一起,也用空格字符把相关性弱的事物分隔开。赋值操作符周围加上空格字符,达到强调目的。不在函数名和左圆括号之间加空格。这是因为函数与其参数密切相关。函数调用括号中的参数一一隔开,强调逗号,表示参数互相分离。空格也可以强调其前面的运算符。乘法因子之间没加空格,因为他们具有较高优先级,加减法运算项之间用空格隔开,因为加减法优先级低。

水平对齐
缩进

文件顶层的类说明,不缩进;类中的方法相对该类缩进一个层级;方法的实现相对方法声明缩进一个层级;代码块的实现相对于其容器代码块缩进一个层级。

空范围

有时,while或for语句的语句体为空,如果无法避免,就确保空范围体的缩进,用大括号包围起来加分号。

团队规则

每个程序员都有自己喜欢的格式规则,但如果在一个团队中工作,就是团队就是算。一组开发者应当认同一种风格。

第六章(对象和数据结构)

将变量设为私有是我们不想其他人依赖这些变量,还想能自由修改类型或实现。那么对对象自动添加赋值器和取值器就将私有变量公之于众,如同公共变量一般。

数据抽象

隐藏实现并非只是在变量之间放一个函数层那么简单,而是抽象。类并不简单地用取值器和赋值器将其变量推向外间,而是暴露抽象接口,以便用户无需了解数据的实现就能操作数据本体。

数据、对象的反对称性

对象把数据隐藏于抽象之后、暴露操作数据的函数。数据结构暴露其数据,没有提供有意义的函数。
过程是代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数。面向对象代码便于在不改动既有函数的前提下添加新类。在任何一个复杂系统中,都会有需要添加新数据类型而不是新函数的时候。这时,对象和面向对象就比较合适。另一方面,也会有想要添加新函数而不是数据类型的时候。这种情况下,过程式代码和数据结构更合适。

德墨忒耳律

模块不应该了解它所操作对象的内部情形。类C的方法f只应该调用以下对象的方法:

  • C
  • 由f创建的对象
  • 作为参数传递给f的对象
  • 由C的实体变量持有的对象
火车失事

连串的调用:
final String outputDir = ctxt.getOptions().getAcratchDir().getAbsolutePath();违反了德墨忒尔定律。下面的就不会:final String outputDir = ctxt.options.acratchDir.absolutePath;

混杂

一半是对象,一半是数据结构。这种结构拥有执行操作的函数,也有公共变量或公共访问器及改值器。公共改值器及访问器把私有变量公开化,诱导外部函数以过程式程序使用数据结构的方式使用这些变量。

隐藏结构

防止当前函数因浏览它不该知道的对象而违反德墨忒尔律。

数据传送对象

最为精炼的数据结构,是一个只有公共变量、没有函数的类。这种数据结构有事被称为数据传送对象,或DTO。是非常有用的结构,尤其是在与数据库通信、或解析套接字传递的消息之类场景中。

总结

对象暴露行为,影藏数据。便于添加新对象类型而无需修改既有行为,同时也难以在既有对象中添加新行为。数据结构暴露数据,没有明显的行为。便于向既有数据结构添加新行为,同时也难以向既有函数添加新数据结构。

第七章(错误处理)

当错误发生时,程序员有责任确保代码照常工作。凌乱的错误处理代码,如果搞乱了代码逻辑,就是错误的做法。

使用异常而非返回码

返回码需要在调用之后即刻检查错误,容易被遗忘。

先写Try-Catch-Finally语句

先构造try代码块的事务范围,而且也会帮助你维护好该范围的事务特征。执行try-catch-finally语句中try部分代码时,你是在表明可以随时取消执行,并在catch语句中继续。

使用不可控异常

(๑•ᴗ•๑) 可控和不可控的区别?

给出异常发生的环境说明

你抛出的每个异常,都应当提供足够的环境说明,以便判断错误的来源和处所。Java中可以从异常里得到堆栈踪迹,然而无法告诉你失败操作的初衷。应创建充分的错误消息,包括失败的操作和类型。传递足够的信息给catch块,记录下来。

依调用者需要定义异常类

错误分类可以依来源分类:是组件还是其它地方,或依类型分类:是设备错误、网络错误还是编程错误。最重要的考虑应该是它们如何被捕获。

定义常规流程

有时我们不想打包外部API以抛出自己的异常。特例模式:创建一个类或配置一个对象,用来处理特例。你来处理特例,客户代码就不用应付异常行为了。异常行为被封装到特例对象中。

别返回null值

过多检查null值的应用程序,坏透了,返回null值是在给自己增加工作量,也是在给调用者添乱。只要有一处没检查null值,应用程序就会失控,NullPointerException。如果你打算在方法中返回null值,不如抛出异常,或者返回特例对象。比如使用Collection.emptyList()方法返回一个预定义不可变列表,代替返回null,也就避免了空指针异常,代码也整洁了。

别传递null值

在方法中返回null值是糟糕的做法,但将null值传递给其他方法就更糟糕了。除非API要求你向它传递null值,否则就要尽可能避免传递null值。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值