我读<代码整洁之道>--读书笔记整理

时间:2021-06-10 16:19:18

第一章 整洁代码

  "我可以列出我留意到的整洁代码的所有特点,但其中有一条是根本性的,整洁的代码总是看起来像是某位特别在意他的人写的.几乎没有改进的余地,代码作者设么都想到了,如果你企图改进它,总会回到原点,赞叹某人留给你的代码" ---Michael Feathers

  "整洁的代码只做好一件事" ---Bjarne Stroustrup

第二章 有意义的命名

  1 变量名要名副其实

  即顾名思义,变量.函数或者类的名称应该能告诉你,它为什么会存在,他做什么事,应该怎么用,名称应该尽量不需要注释补充亦能表示其含义.

  类名和对象应该是名词或者名词短语,方法名应该是动词或者动词短语.

  2 避免误导

//不好的命名
public boolean stateMachine(int smStatus) {
//...
}
public boolean doAction() {
//...
}
//好的命名
public boolean moveStateMachineToStatus(int smStatus) {
//...
}
public boolean doNextStepInProviderTechnique() {
//...
}

  3 做有意义的区分

    4 使用能读得出来的名称

  名字中不要有冗余,定义一个List类型变量,xxxList不如xxx的复数形式简洁,List字眼就属于冗余字段.

   5 命名布尔变量

   使用常见的布尔含义的命名: 如done;error;found;success;

   可以添加前缀is,has来转换布尔,但是isSuccess不如success可读性好

   避免使用负向变量命名:如notFound,设想if(!notFound)是不是觉得别扭.负向条件比正向条件更加难于理解,应该尽量避免.

第三章    函数

  1 短小

  这里是指一个函数的代码量,不是指函数名称要短小.函数应该是在做一件从语义上无法再次拆解的事情.

  2 一个函数只做一件事

  函数应该做一件事,做好这一件事,而且只做这一件事.

写多长的函数比较合适?

  约定:不超过一屏幕,长度并不是问题,关键在于函数名称和函数体之前的语义距离,函数应该很短,也可以较长.只要保证函数只做这一件事.

拆分函数的技巧?

  寻找注释,如果函数中某部分代码前面有一行注释,一般可以把这段代码替换成一个函数.此外,当感觉需要注释来说明一些什么的时候,就要考虑把要说明的这些东西封装成一个独立的函数.

  3 每个函数一个抽象层级

  这一点需要多一些说明,要确保函数只做一件事,函数的语句要在同一个抽象层级上.

public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String fileId = request.getParameter("FILEID");
if (fileId == null) {
throw new ServletException("Invalid FileId"); }
String partNum = request.getParameter("PART"); int part = 1;
if (partNum != null) {
part = Integer.valueOf(partNum).intValue(); }
boolean isLast = "Y".equals(request.getParameter("LAST"));
boolean getName="Y".equals(request.getParameter("GETNAME"));
String fileName =
MitoDownloadCache.INSTANCE.getFileName(fileId, part);
if (fileName== null) {
throw new ServletException("Invalid FileName"); }
MitoConfig mitoCfg = new MitoConfig();
File file = new File(mitoCfg.getPrintClientDataPath()+"/"+fileName);
if (!file.exists()) {
throw new ServletException("File " + file.getAbsolutePath() + " not found");
}
if (getName) {
doDownloadFilename(request, response, file.getName());
} else {
if (isLast) {
MitoDownloadCache.INSTANCE.removeFileList(fileId); }
doDownload(request, response, file); file.delete();
}

  上面这个方法是我copy来的,不用研究这个函数的内容,只看一下结构,这个函数中既有比较高抽象层次的函数doDownloadFilename,doDownload,还有getFileName这种位于中间抽象层次的函数,更有很多像equals等较低抽象层次的概念.混杂了不同抽象层次,读起来就比较吃力,现在来改造一下:

public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
File file = fileToDownLoad(request);
if (downloadByName(request)) {
doDownloadFilename(request, response, file.getName());
} else {
removeFromCacheIfLast(request);
doDownload(request, response, fileToDownLoad(request));
file.delete();
} }

  将功能拆分成红能更小更细的函数,封装成私有方法,起一个见名知义的函数名,使整个方法能够位于同一个抽象层次,然后在每个函数内部都跟着下一级抽象层次的函数,这样能给阅读带来极大的便利.(上面函数中私有方法这里不再展示)

  4 使用描述性名称

   函数越小,功能越集中,就越容易取一个合适的名字.另外不要害怕长名称,描述性很好的长名称比含糊不清的短名称要好.不要害怕花时间给函数起名字,起一个合适的名字本身就是编程的一部分.

  5 函数输入参数

  最理想的函数参数是0个(无参函数),其次是一个,二个,应该尽量避免三个以上的参数.

尽量避免标示参数,即用一个boolean类型作为入参,比如下面这个函数,eat(true);这样读起来就有些摸不着头脑,可能读者需要去查看eat方法eat(boolean isHuntry)才能比较理解这个函数.

如果函数必须要三个或三个以上的参数,就说明其中一些参数需要封装成类,如果很难封装成类,就说明这个函数的设计有问题.

  6 函数输出参数

  面向对象语言的方法应该尽量避免使用输出参数,当然那些util类型的工具类除外.this也有输出函数的意味,如果函数必须要修改某种状态,就修改所属对象的状态吧.

  7 抽离try/catch代码块

   try/catch代码块比较丑陋,搞乱了代码结构,最好把try/catch代码块的主体部分抽离出来,另外形成函数.

函数应该只做一件事,错误处理就是一件事,因此,如果处理错误(带try/catch)的函数应该只处理错误,该函数只有try/catch不应该再包含其他内容.

  8 使用更少的代码

//实例1:不好的代码格式
public int size() {
if (root == null) {
return 0;
}else {
return root.numSiblings();
}
}
//实例1:好的代码风格
public int size() {
return root != null ?root.numSiblings() : 0;
}
//实例2 不好的代码
List<Integer> list = Arrays.asList(1, 2, 3));
list = Collections.unmodifieableList(list);
return list;
//实例2 好的代码
import static java.util.Arrays.*;
import static java.util.Collections.*;
... return unmodifiableList(asList(1, 2, 3))

实例2中给出的例子有些函数式编程的味道,这里只是提供一种思路,但大多数情况我们一般写出来的都是上面的格式.

第四章 注释

  不写无意义的注释,什么是无意义的注释,如下:

  //当我写这段代码的时候,只有老天和我自己知道我在做什么

//现在,只剩老天知道了

...

//我不对以下代码负责

//使他们逼我写的,是违背我意愿的  

  注释尽量做到简洁

  最好不写注释

  代码结合命名应该良好的描述功能,写注释说明表达意图的失败以及对代码功能的不自信,糟糕的代码是注释存在的动机之一.

  什么是必须要写的注释

    警告他人 版权协议 公共API 

第五章 格式

  1 格式的目的

  代码格式很重要,代码格式关乎沟通,而沟通是开发者的头等大事.

  2 垂直格式

    阅读代码都是从上往下,从左往右读的.在一个类中,在封包声明,导入声明,和每个函数之前都应该使用一个空白行来隔开.这可以给阅读带来愉悦的感受.

  空白行隔开了概念,每个空白行都是一条线索,标示出一个新的独立的概念,阅读代码的时候,我们的目光总是容易停留在空白行的前一行或后一行.

    变量声明 :变量声明应该尽量靠近其使用位置.类变量应该声明在类的顶部,

    相关函数:某函数A调用了函数B,应该尽量把他们放到一起,让A位于B的上方.

    概念相关:概念相关的代码应该放到一起,相关性越强,他们之间的距离就应该越短.

    避免过度缩进(代码*):

//不好的代码风格
public void startCharging() {
if (customer.hasFunds()) {
if (!station.isCharging()) {
if (!station.currentlyBooked()) {
reallyStartCharging();
return;
}
}
}
throw new UnableToStartException();
}
//好的代码风格
public void startCharging() {
if (!customer.hasFunds()) throw new UnableToStartException
if (station.isCharging()) throw new UnableToStartException
if (station.currentlyBooked()) throw newUnableToStartException
reallyStartCharging();
}

  3 横向格式

  一行代码应该多宽?

  应当遵循无需拖动滚动条到右边的原则,每行代码控制在100个字符以内是良好的风格.

第六章 对象和数据结构

  数据抽象

  当我们构建一个实体类时,会为变量设置为private,再为变量提供set/get方法,想一想为什么要这么做?实际上,即使变量都是私有,我们通过变量的set/get方法操作变量时,实现仍然被曝光了,那为何不直接将变量设置为public,然后直接操作变量呢?

  隐藏实现并非只是在变量之间放上一个函数层那么简单,更大的意义在于抽象,类并不是简单的用set/get方法将变量推向外间,而是暴露抽象接口,使得用户无需了解数据的实现就能够操作数据本体.  

  德墨忒尔定律(The Law of Demeter)

  得墨特定律认为,模块不应该了解它所操作的对象的内部情况.对象隐藏数据,曝光操作.

  类C的方法f只应该调用以下对象的方法

  1. C
  2. 由f创建的对象
  3. 作为参数传递给f的对象 
  4. 由C的实体变量持有的对象

  方法不应该调用任何由任何函数返回的对象的方法,一个很经典的比喻就是:不要和陌生人说话,只和朋友说话,还有一个比喻就是,人可以命令狗走路,但人不能直接命令狗的腿去走路,而应该由狗来控制自己的腿去走路.

  如果代码中充斥着"."构成的链式编程风格的代码.意味着该定律被被违反了.当然如果调用者是数据结构,没有行为只有数据,就可以忽略.

 

 ...未完