Clean Code 阅读笔记(1)

读的技术书、社科书,现在想想总感觉能回忆起来的太少,看过也就当看过了,没有内化为自己的内容。所以这本书起开始做读书笔记,也便于以后查阅。看了下上篇博文距离现在竟然有一年多了,真是快废了,还好现在拾起来。

整洁代码:

外表或举止上令人愉悦的优美和雅观;令人愉悦的精致和简单 –Bjarne Stroustrup

如果每个例程都让你感到深合己意,那就是整洁代码。如果代码让编程语言看起来像是专为解决那个问题而存在,就可以称之为漂亮的代码。 –Ward Cunningham

这便是书写代码的最高境界,在实践过程中确实不易,唯有持之以恒的坚持自省,正如不易确实需要一个军规让营地比你来时更干净,在每次修改时心里默念这句,对看到繁琐的代码及时梳理使其变得简洁、清晰、明了,或许仅仅重命名就可以有这样的效果。

命名:

  • 名副其实:在上下文中能明确表示其含义,无歧义
  • 避免误导:命名准确,减少相似性,避免猜测
  • 做有意义的区分:如UserInfo比起User则显得没什么意义
  • 使用读的出来的名称:减少诡异的缩写,除非约定俗成。
  • 使用可搜索的名称:作用域越小,命名可以适当简单、短小
  • 类名为名词
  • 方法名为动词或者动词短语
  • 相同概念对应同一名词
  • 添加有意义的语境

命名让人感觉很简单,但是在实践过程中,却发现是如此的令人头痛,找到一个合理的字词、短语是如此的困难,对于理解上下文是如此的重要,当命名变得有意义,可以让我们减少大量注释去解释代码到底做了什么。当在代码里看到不合理的命名时,不要想着下次有时间再改,而是立马修改,下次相当于没有下次。烂代码就是这样慢慢腐烂,最后如沼泽一般,你想穿过时会一不小心陷入其中,难以脱身。所以现在便是改变的最好时机。

函数:

  • 短小:当大块的函数拆分后,命名有意义的短小函数时,便相当于注释,更容易理解其到底做了什么,怎么做,只要函数内做同级逻辑的事情便可无需拆分
  • 只做一件事:做好这件事
  • 每个函数一个抽象层级:描述当前抽象层级,并引用下一层级的后续
  • Switch:根据上下文可以将switch放置在抽象工厂,但是这样会造成代码量的增加,可以根据实际情况取舍
  • 使用描述性的名称命名函数,函数名称字数多也无所谓,OC函数名更长
  • 尽量使函数参数少于三个,对于过多的参数可以使用Builder模式

刚开始对于函数规模不好把握,可以先实现功能,然后再优化,使其看起来简洁明快,如沐春风,毕竟编程时很多时候在于浏览原来代码的逻辑,这样清晰的代码可以加快开发速度和愉悦身心。

注释:

  • 用代码阐述目的,而不是通过大量的注释
  • 好的注释:法律信息、意图的注释、警示
  • 坏的注释:没有表达具体的含义、误导性注释、日志式注释、废话玩笑式注释、滥用位置标记、归属和签名、注释掉的代码

对于坏的注释深有体会,当代码多个版本迭代后,注释和代码已经完全不是一个含义式,注释仍旧在,很容易误导对代码的理解。在版本管理工具完善的现在,对于无用代码完全不用担心删除以后要用到时找不到的尴尬,当然必须有个规范的Commit记录,便于找回代码。

格式:

  • 垂直方向:方法间空一行;函数调用互相靠近(调用者在前,被调用者在后);变量声明靠近其使用位置,实体变量声明在类顶部;相关概念代码 放在一起
  • 水平方向:设置代码显示最大列;当方法层级过多导致大量缩进时,抽取为方法,减少方法内缩进层级;

书写代码有良好的格式十分重要,有的项目里Tab和Space混用,导致不同文件格式看起来十分别扭,对于未格式化的代码堆积在一起,很难看清调用逻辑,写完代码格式化是个好的习惯,当然对于不完美的地方可以手动修改,当下次打开时会提高阅读速度,项目格式统一十分有必要,可以用CheckStyle等工具约束代码规范。

对象和数据结构:

  • 数据抽象:暴露实现,隐藏数据结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//具象
public class Point{
public double x;
public double y;
}
//抽象
public interface Point{
double getX();
double getY();
void setCartesian(double x,double y);
double getR();
double getTheta();
viod setPolar(double r,double theta);
}
  • 减少过程式编码,过程式编码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数,面向对象代码便于在不改动既有函数的前提下新类,反之亦然
  • 数据传送对象,例如避免使用ORM时,类内既有公共变量也有save、find等方法

在实现过程中要很把握这点确实很难,根据上下文环境进行相应的取舍,对于现实的编程确实更为有利点。

错误处理:

  • 使用异常而非错误码
  • 先写Try-Catch-Finally
  • 使用未受检异常
  • 别返回null值
  • 别传递null值

异常处理在实践过程中确实有时能救一命,但是很多的异常代码和正常逻辑混合,需要小心将其尽量分离,对于null传递,入参中做判空处理,以防NPE,在团队协作过程中最好有统一的定义怎样处理,大量的判空处理确实显得无意义但是有时没办法的事情。

边界:

在以往编程中确实很少注意边界问题,对于接入第三方,没有在意其影响而去编写相应的TestCase,当第三方api有较大的改动时,自己代码也会发生很大改动,并且再次回归测试,耗费大量时间。对待第三方接入应该尽量封装,免受它的控制。

单元测试

  • 保持测试整洁,明确、简洁有较高的可读性,遵循构造-操做-检验模式
  • FIRST:快速、独立、可重复、自足验证、及时

在平时开发中,对于单元测试实践的太少,最多只测试一些工具类,由于Android特殊性,相对测试用例编写不方便。

  • 符合自顶向下原则
  • 保持变量和工具函数的私有性,但不应执着于此
  • 单一权责原则(SRP),类或者模块应有且只有一条加以修改的理由
  • 内聚:类应只有少量的实体变量,内聚性应保持在较高的位置
  • 依赖倒置原则(DIP):类应当依赖于抽象而不是具体细节

对于SRP、DIP只有在了解业务情况下,多次重构得到合适的方案,时常保持重构会对代码质量有很大的帮助。