0%

代码整洁之道

代码质量与整洁度成正比
全员生产维护(TPM 一种质量保证升段):5S:

  • 整理(Seriri):即组织
  • 整顿(Seiton):所谓整齐
  • 清楚(Seiso):所谓清洁
  • 清洁(Seiketsu):所谓标准化
  • 身美(Shitsuke):所谓纪律(自律)

小处的东西往往能影响大局,比如简单的缩进风格对于软件质量的提高不逊于架构、编程语言等高级概念

衡量代码质量的唯一有效标准:WTF/min (笑)

整洁代码

认为机器自动生成代码代替人工是不科学的,代码永存

勒布朗法则:稍后等于永不

整洁的代码只做好一件事。它简单直接,从不隐藏设计者的意图,充满了干净利落的抽象和直截了当的控制语句

代码要尽量少,越小越好

童子军军规:让营地比你来时更干净

有意义的命名

一旦发现有更好的名称,就换掉旧的。

不要用xxList来指称一组东西,除非它真的是List类型,List一次有特殊意义,如果这个容器并非真的是个List,就会引起错误的判断,所以用XXGroup,或复数形式会更好,即便容器就是个List,最好也别在名称中写出容器类型名。

提防使用不同之处较小的名称,如XYZContorllerForEffcientHandlingOfStrings和XYZContorllerForEffcientStorageOfStrings

不要用误导性名称,如小写字母l和大写字母O作为变量名,看起来像数组1和0

如果只为满足编译器和解释器而改写代码就会制造麻烦,比如同一作用范围内两样不同的东西不能重名,可能会随手改变其中一个名称,或以错误的拼写充数,结果就是出现在更正拼写错误后编译器出错(如class已有他用,就给一个变量命名为klass,相当可怕的做法)

以数字系列命名(a1,a2.。。aN)这样的名称纯属误导。

废话都是冗余,variable一词永远不该出现在变量中。Table一词永远不该出现在表名中。NameString不会比Name更好(难道Name会是一个浮点数?)如果缺少明确约定,变量moneyAmount和money没区别,customerInfo和customer没区别,accountData和account没区别,theMessage和message没区别。要区分名称,就要以读者能鉴别不同之处的方式来区分。

使用可搜索的名称

单字母名称仅用于短方法中的本地变量,名称长短应该与其作用域大小相对应。

避免使用编码

把类型或作用域编进名称里,徒然增加了解码的负担。

  • java变量是强类型的,不需要匈牙利标记法
  • 不必用m_前缀来标明成员变量。应该把类和函数做的足够小,消除对成员前缀的需要。

类名

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

方法名

方法名应当是动词或动词短语,如postPayment、deletePage或save。属性访问器、修改器和断言应该根据其值命名,并依标准加上get、set、is前缀。

重载构造器时,使用描述了参数的静态工厂方法名通常好于构造器中直接传入意义不明的函数,如:

1
2
3
Complex f = Complex.FromRealNumber(23.0);
好于
Complex f = new Complex(23.0);

可以考虑将相应的构造器设置为private来强制使用这种命名手段。

名称不要耍宝

不要使用某一种文化特有的俚语

每个概念对应一个词

给每个抽象概念选一个词,并且一以贯之。例如,fetch、retrieve、get给多个类中的同种方法命名是可怕的,最好就选一个用到底

别用双关语

例如使用add,在多个类中都有add方法,该方法通过增加或连接两个现存值来获得新值。假设要写一个新类,其中有个方法,把单个参数放到群集中去,该把这个方法叫做add吗?这样做貌似和其他add方法保持一致,但实际上语义却不同。一个用insert或append之类的词,把该方法命名为add就是双关语了。

使用解决方案领域名称

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

添加有意义的语境

不要添加没用的语境

只有短名称足够清楚,就要把长名称好,别给名称添加不必要的语境

函数

短小

函数第一规则是短小,第二规则是还要更短小。
函数20行封顶最佳

if、else、while语句等,其中的代码块应该只有一行。该行应该是一个函数调用语句,这样不但能保持函数短小,且块内调用的函数拥有较具说明性的名称。

只做一件事

函数应该做一件事。做好这件事,只做这一件事。
要判断函数是否不止做了一件事,还有一个方法,就是看是否能再拆出一个函数,该函数不仅只是单纯地重新诠释其实现

每个函数一个抽象层级

例如,getHtml位于较高抽象层,PathParser.render中间抽象层,StringBuilder.append低抽象层。
函数中混杂不同抽象层级往往让人迷惑,可能无法判断某个表达式是基础概念还是细节。

自顶向下读代码:向下规则

我们想要让代码拥有自顶向下的阅读顺序,想要让每个函数后面都跟着位于下一个抽象层级的函数,这样一来,在查看函数列表时就能循抽象层级向下阅读了。

switch语句

写出短小的switch语句很难,不过还是能够确保每个switch都埋藏在比较低的抽象层级,而且用于不重复。

1
2
3
4
5
6
7
8
9
10
11
12
public Money calculatePay(Employee e){
switch(e.type){
case COMMISSONED:
return caculateCommissionedPay(e);
case HOURLY:
return caculateHourlyPay(e);
case SALARIED:
return caculateSalariedPay(e);
default:
throw new InvalidEmployeeType(e);
}
}

上述代码问题:首先它太长,如果出现新雇员类型,还会变的更长。其次,他名下做了不止一件事,第三,它违反了单一职责原则。第四,它违反了开放闭合原则,每当添加新类型时,就必须修改之。

该问题的解决方案,是将switch语句埋到抽象工厂低下,不让任何人看到,该工厂使用switch语句为Employee的派生物创建适当的实体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface EmployeeFactory{
public Employee makeEmployee(EmployeeRecord r)
}

public class EmployeeFactoryImpl implements EmployeeFactory{
public Employee makeEmployee(EmployeeRecord r){
switch(r.type){
case COMMISSONED:
return new ComissionedEmployee(r);
case HOURLY:
return new HourlyEmployee(r);
case SALARIED:
return new SalariedEmployee(r);
default:
throw new InvalidEmployeeType(e);
}
}
}

对于switch语句,推荐的规则是如果只出现一侧,用于创建多态对象,而且隐藏在某个继承关系中,在系统其它部分看不到,就还能接受,当然就事论事,有时也会部分或全部违反该规则

使用描述性的名称

沃德原则:如果每个例程都让你感到深合己意,那就是整洁代码

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

命名方式要保持一致。使用与模块名一脉相承的短语,名词和动词给函数命名。

函数参数

最理想的参数数量是0,其次是1,再次是2,应尽量避免3.有足够的理由才能使用三个以上参数。

标识参数丑陋不堪。向函数传入布尔值简直就是骇人听闻的做法。这样做,方法签名立刻变得复杂起来,宣布本函数不止做一件事,如果表示为true将会这样做,标识为false则会那样做。

如果函数看起来需要三个以上参数,就说明其中一些参数应该封装为类了

给函数取个好名字,能较好地解释函数的意图,以及参数的顺序和意图。对于一元函数,函数和参数应当形成一种非常良好的动词/名词对形式。例如write(name)

必须保证函数“只做一件事”,才能有效避免副作用。

函数要么做什么事,要么回答什么事,但二者不可兼得。函数应该修改某对象的状态,或是返回该对象的有关信息。两样都干会导致混乱。

使用异常代替返回错误码。如果这样做,错误处理代码就能从主路径代码中分离出来,得到简化
4c8bf45524e5691088c803b01e63b20f.png
25b17f5755b4cb67da2d9fbf52326a01.png

最好吧try-catch代码块从主体部分抽离处来,另外形成函数。

返回错误码通常暗示某处有个类或是枚举,定义了所有错误码。
这样的类就是一块依赖磁铁,其他许多类都得导入和使用它,当其枚举修改时,所有这些其他类都需要重新编译和部署。而使用异常代替错误码,新异常就可以从异常类派生出来,无需重新编译或重新部署。

尽量不要重复。重复可能是软件中一切邪恶的根源,许多原则和实践规则都是为控制与消除重复而创建。如数据库范式为了消灭数据重复而服务。OOP将代码集中到基类从而避免冗余。aop、cop(面向组件编程)多少也都是消除重复的一种策略。

只要函数保持短小,偶尔出现的return,break,continue语句没有坏处,甚至比单入单出原则更具有表达力。另一方面,goto只在大函数中才有道理,所以应该尽量避免使用。

写代码和写别的东西一样,先是想什么就写什么,然后再打磨它。初稿也许粗陋无序,就会斟酌推敲,直至满意。一开始都冗长而复杂,有太多缩进和嵌套循环。有过长的参数列表。名称是随意取得,也会有重复代码。然后打磨这些代码,分解函数,修改名称,消除重复。还会缩短和重新安置方法。有时还拆散类,同时保持测试通过。最后遵循规则组装好这些函数。
并不是一开始就按照规则写函数。这没人做得到。

注释

注释的恰当用法是弥补用代码表达意图时遭遇的失败。注释总数一种失败,我们总无法找到不用注释就能表达自我的方法,所以要有注释。
但注释会撒谎,只是存在的时间越久,就离其描述的代码越远,越来越变得错误,原因很简单,程序员不能坚持维护注释。

不准确的注释比没有注释坏的多。真实只在一处地方有:代码。只有代码能忠实地告诉你它做的事。尽管有时也需要注释,但我们也该花心思尽量减少注释量。

注释不能美化糟糕的代码。

有些注释是必须的,也是有利的,不过要记住,唯一真正好的注释是你想办法不去写的注释
好的注释

  • 法律信息
  • 提供信息的注释
  • 对意图的解释
  • 阐释 即把某些晦涩难懂的参数或返回值的意义翻译为某种可读形式
  • 警示 警告其他程序员会出现某种后果
  • TODO注释:在源码中放置要做的工作列表。
  • 放大:注释可以用来放大某种看来不合理之物的重要性
  • 公共API中的javadoc

坏注释:

  • 喃喃自语

  • 多余的注释

  • 误导性注释

  • 循规式注释:所谓每个函数都要有javadoc或每个变量都要有注释的规矩是愚蠢可笑的。

  • 日志式注释

  • 废话式注释

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

  • 位置标记

  • 括号后面的注释

    1
    2
    3
    4
    5
    6
    7
    8
    9
    try{
    。。。
    while(){
    ...
    } //while
    }//try
    catch(Exception e){
    ...
    }//catch
  • 归属与签名:用签名搞脏代码很不好,合适的位置是代码版本控制工具(如git)

  • 注释掉的代码

  • html注释

  • 非本地信息:例如别在本地注释的上下文环境中给出系统级的信息,注释应该保证描述了离它最近的代码

  • 信息过多

  • 不明显的联系

  • 函数头:短函数不需要太多描述,选个好名字通常比写函数头注释好

  • 范例

格式

沟通是专业开发者的头等大事(而不是“让代码工作”)

垂直格式

一般的源码文件不要超过500行,否则可以考虑拆分
每个空白行都是一条线索,标识出新的独立概念。比如方法之间就应该有一个空白行。

紧密相关的代码应该互相靠近。

垂直距离

  • 变量声明:变量声明应尽可能靠近其使用位置。因为函数很短,本地变量应该在函数的顶部出现。
  • 循环中的控制变量应该总是在循环语句中声明。
  • 偶尔,在较长的函数中,变量可能在某个代码块顶部,或在循环之前声明。
  • 实体变量应该在类的顶部声明(java的习惯)
  • 相关函数:若某个函数调用了另一个,就应该把它们放到一起,而且调用者应该尽可能放到被调者的上面。
  • 概念相关:概念相关的代码应该放到一起。相关性越强,彼此之间的距离就该越短:相关性应建立在直接依赖的基础上,如函数间调用,或者执行相似操作的一组函数也是相关性很高的(如共同的命名模式,执行同一基础任务的不同变种)

垂直顺序

一般而言,被调用的函数应该放在执行调用的函数下面。最重要的概念应该先出来。

水平格式

一般来说,一行的上限为120个字符

空格字符可以强调其前面的运算符,如

1
2
xxx / yyy
b*b - 4*a*c

乘法因子之间没加空格,因为他们具有较高优先级,加减法运算符之间用空格隔开,因为其优先级较低。

现代语言可以用不对齐的声明和赋值,因为它们指出了重点。如果有较长的列表需要做对其处理,那么问题就是在列表的长度上而不是对齐上。(按正常的格式来即可)

缩进用来体现层级结构,有了缩进会使得程序更容易阅读,没有的话就会无法阅读。尽量不要违反缩进规则,即使是在短小的if,while中。

有时while或for语句的体为空,如果无法避免地使用,则要确保空范围体的缩进,用括号包围起来。不然很容易被while循环语句同一行末尾的分号所欺骗,除非把分号放到另一行再加以缩进。

1
2
3
4
5
6
7
8

while(xxx); //很容易忽略

while(xxx){
} //good

while(xxx)
; //good

作者的格式规则

成员变量在最上面
构造函数
静态函数(根据调用关系排序,调用者在上,被调者在下)
成员函数(同上)
函数要尽可能短

对象和数据结构

我们一般不愿暴露数据细节,更愿意以抽象形态表述数据,这并不只是用接口或getter、setter就万事大吉。要以最好的方式呈现某个对象包含的数据,需要做严肃的思考

数据、对象和反对称性

》过程式代码难以添加新数据结构,因为必须修改所有涉及到相关操作的函数。
(比如对圆形、矩形的面积计算工具类中,如果要添加新的类型数据结构,则必须要修改工具类中所有根据类型来操作的具体函数。但如果要添加新的功能函数很简单,只需在工具中增加该新函数即可)
》面向对象代码难以添加新函数,因为必须修改所有类。
(同样,如果所有的形状类型都继承基类,并且每个都重写了其操作函数,添加新类型是很容易的,只需要重写相应的基类函数即可,但如果要添加新的功能函数,就必须对每一个实现类都添加该函数的实现)

德墨忒尔律(The Law of Demeter)

该规律认为,模块不应了解它所操作对象的内部情形。对象隐藏数据,暴露操作。这意味着对象不应通过存取器暴露其内部结构,因为这样更像是暴露而非隐藏其内部结构。
德墨忒尔律认为,类C的方法f只应该调用以下对象的方法:

  • C
  • 由f创建的对象
  • 作为参数传递给f的对象
  • 由C的实体变量持有的对象
    方法不应该调用由任何函数返回的对象的方法,即:只跟朋友谈话,不与陌生人谈话
    1
    2
    3
    String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

    //上述代码违反了德墨忒尔律,因为它调用了getOptions()返回值的getScratchDir()函数,又调用了getScratchDir()返回值的getAbsolutePath()函数。
  • 常用的调用链模式明显违反了该律,但却带来了编码上极大的方便,因此对待这种规律也得辩证去看,不能一味死守教条。没有通用的规律,只有具体的场景*

数据传送对象

只有一个公共变量,没有函数的类叫做数据传送对象(DTO)。更常见的是bean结构,它具有赋值器和取值器操作的私有变量。(一般的javabean)

错误处理

使用异常而非返回码

先写try-catch-finally语句

在编写可能抛出异常的代码时,最好先写出try-catch-finally语句,这能帮你定义代码的用户应该期待什么,无论代码块中执行的代码出什么错。

使用不可检查异常

可检查异常的代价是违反开放封闭原则。如果在方法中抛出可检查异常,而catch语句在三个层级之上,就意味着对软件中较低层级的修改,都将波及到高层级的签名。修改好的模块必须重新构建、发布,即使他们自身所关注的任何东西都没改动过。

给出异常发生的环境说明

应该创建信息充分的错误消息,并和异常一起传递出去,包括失败的操作和失败类型,而不应该仅仅是堆栈踪迹。

依调用者需要定义异常类

打包:当打包一个第三方API,就降低了对它的依赖,未来可以不太痛苦地改用其他代码库,也不用绑死在某个特定厂商的API设计上。

对于代码的某个特定区域,单一异常类通常可行。伴随异常发送出来的信息能够区分不同错误。一个想要捕获某个异常,并且放过其他异常,就使用不同的异常类。

别返回null值

特例模式:创建一个类或配置一个对象,用来处理特例,由底层来处理特例,客户代码就不用应付异常行为了。异常行为被封装到特例对象中。

如果打算在代码中返回null值,不如抛出异常,或是返回特例对象。如果在调用某个第三方API中可能返回null值的方法,可以考虑用新方法打包这个方法,在新方法中抛出异常或返回特例对象。

别传递null值

除非API要求传递null,否则要尽可能避免传递null值。

边界

尽量避免把容器(或在边界上的其他接口)在系统中传递。把这种边界接口留在类或近亲类中,避免从公共API中返回边界接口,或将边界接口作为参数传递给公共API。

学习性测试

学习第三方代码很难,整合第三方代码也很难,同时做这两件事难上加难。而如果采用不同的做法,不要在生产代码中实验新东西,而是编写测试来浏览和理解第三方代码,就有更好的理解效果。

不要一上来就看源码,或者整合源码。多写测试程序,去发现不同的用法,发现问题,有了问题后再以问题为导向去看源码中的解决方法,否则很容易就陷入到无穷无尽的“代码海”中

使用尚不存在的代码

如果有的API还没设计出来,则我们可以先自己设计一套fake的,然后来根据它设计,一旦真实的设计出来后,就可以编写适配器来跨界,这样原先的逻辑几乎不用改动。所做的工作只是适配器

整洁的边界

单元测试

TDD:测试驱动开发

TDD三定律

  1. 在编写不能通过的单元测试前,不可编写生产代码。
  2. 只可编写刚好无法通过的单元测试,不能编译也算不通过
  3. 只可编写刚好足以通过当前失败测试的生产代码
    这三条定律将你限制在大概30s一个的循环中。测试和生成代码一起编写,测试只比生产代码早写几秒中

保持测试整洁

测试必须随生产代码的演进而修改。测试越脏,就越难修改,测试代码越缠结,你就越有可能话更多时间塞进新测试,而不是编写新生产代码。修改生产代码后,旧测试就会开始失败,而测试代码中乱七八糟的东西将阻碍代码再次通过,余数测试变得就像是不断翻番的债务。
测试代码和生产代码一样重要,他需要被思考,被设计和被照料,它该像生产代码一般保持整洁

整洁测试

整洁的测试的要素:可读性、可读性、还是可读性

测试应该遵循的模式:构造-操作-检验
第一个环节构造测试数据,第二个环节操作测试数据,第三个部分检验操作是否得到期望的结果

每个测试一个断言

每个测试函数应该有且只有一个断言,虽然苛刻,但方便理解(但不好做到)

更好一些的规律或许是每个测试函数只测试一个概念,不要超长的测试函数,测完这个又测那个。

最佳规则应该是尽可能减少每个概念的断言数量,每个测试函数只测试一个概念。

F.I.R.S.T

整洁的测试应遵循以下5条规则:

  • 快速:测试应该足够快
  • 独立:测试应该相互独立,某个测试不应为下一个测试设定条件
  • 可重复:测试应当可在任何环境中重复通过
  • 自足验证:测试应该有布尔输出,不论通过或失败,不应该查看日志文件,或手工对比不同的文件来确认测试是否通过
  • 及时:测试应该及时编写,单元测试应该恰好在使其通过的生产代码之前编写。

类的组织:

类应该从一组变量列表开始。先是公共静态常量,然后是私有静态变量,私有实体变量,最后是公共变量。
公共函数跟在变量列表之后。我们喜欢把某个公共函数调用的私有工具函数紧随在该公共函数后面。

类应该短小

单一权责原则(SRP)

类或模块应有且只有一条加以修改的理由,就是它只应有一个权责。
系统应该由许多短小的类而不是少量巨大的类组成。每个小类封装一个权责,只有一个修改的原因,并与少数其他类一起协同达成期望的系统行为。

内聚

类应该只有少量实体变量。类中的每个方法都应该操作一个或多个这种变量。方法操作的变量越多,就越粘聚到类上。说明内聚性越高。
保持函数和参数列表短小,有时会导致一组子集方法所用的实体变量数量增加。出现这种情况,往往意味着至少有一个类要从大类中挣扎出来。你应当尝试将这些变量和方法拆分到两个或多个类中,让新的类更内聚,

保持内聚性就会得到许多短小的类

如果类堆积了很多只为少量函数而共享的实体变量,就应该拆分该类,让小的类更内聚。所以,将大函数拆分为许多小函数,往往也是将类拆分为多个小类的时机。

为了修改而组织

开放封闭原则(OCP)

类应该对扩展开放,对修改封闭。一般是通过子类化手动端,重新架构的新类对新功能是开放的,同时可以不触及其他类。

依赖倒置原则(DIP)

类应该依赖于抽象而不是依赖于具体细节。

系统

将系统的构造与使用分开

软件系统应该将启始过程和启始过程之后的运行时逻辑分离开,在启始过程中构建应用对象,也会存在相互缠结的依赖关系。

分解main

方法之一是将全部构造过程搬迁到main或被称之为main的模块中

工厂

使用抽象工厂来创建具体的项目

依赖注入

在依赖管理的情景中,对象不应负责实体化对自身的依赖。反之,它应当将这份权责移交给其他“有权力”的机制,从而实现控制反转。因为初始设置是一种全局问题,这种授权机制通常要么是main例程,要么是有特定目的的容器。

扩容

软件系统与物理系统可以类比,它们的架构都可以递增式地增长,只要我们持续将关注面恰当地切分。在aop中,被称为方面(aspect)的模块构造指明了系统哪些点的行为会以某种一致的方式被修改,从而支持某种特定的场景。行为的修改由AOP框架以无损方式在目标代码中进行。

下列是Java中三种方面或类似方面的机制

Java代理

jdk提供的动态代理仅能与接口协同工作。对于代理类,得使用字节码操作库,比如cglib,asm或javaassist
代码量和复杂度是代理的两大弱点,创建整洁代码变得很难。另外,代理也没有提供在系统范围内指定执行点的机制。而这正是真正的AOP解决方案所必须的。

纯JavaAOP框架

如spring

aspectJ的方面

通过方面来实现关注面切分的功能最全工具是aspectj语言,在80%到90%用到方面特性的情况下,springaop和jboss aop提供的手段已经足够,但aspectj是更强有力的工具,其弱势在于需要采用新工具,学习新语言构造和使用方式。

优化决策

模块化和关注面切分成就了分散化管理和决策,最好是授权给最有资格的人。

迭进

kent Beck简单设计四条规则

  • 运行所有测试
  • 不可重复
  • 表达了程序员的意图
  • 尽可能减少类和方法的数量
    以上规则按重要程度排列

并发编程

对象是对过程的抽象,线程是对调度的抽象

为什么要并发

并发是一种解耦策略,他帮助我们把做什么(目的)和何时(时机)分解开。单线程应用中,目的与时机紧密耦合,很多时候只要查看堆栈追踪即可断定应用程序的状态。

解耦目的和时机能明显地改进应用程序的吞吐量和结构

  • 并发并不总能改进性能
  • 并发算法的设计有可能与单线程系统的设计极不相同
  • 采用web或EJB容器时,最后了解并发问题,直到容器在做什么

关于编写并发软件的中肯说法:

  • 并发会在性能和编写额外代码上增加一些开销
  • 正确的并发是复杂的,即使对于简单的问题也是如此
  • 并发缺陷并非总能重现,所以常被看作偶发事件而忽略,未被当做真的缺陷看待;
  • 并发长处需要对设计策略的根本性修改。

并发防御原则:

单一权责原则:

应该分离并发相关代码和其他代码

限制数据作用域

谨记数据封装,严格限制对可能被共享的数据的访问

使用数据副本

使用副本,从一开始就避免共享数据

线程应尽可能独立

尽可能缩小同步区域

尽早考虑关闭问题,尽早令其工作正常。

一些建议:

  • 将伪失败看作可能的线程问题(伪失败:不可能失败的失败,几率极小会出现)
    最好假设偶发事件根本不存在,否则代码可能搭建于不完善的基础上
  • 先使非线程代码可工作
    不要同时追踪非线程缺陷和线程缺陷,确保代码在多线程之外可工作
  • 编写可插拔的线程代码
    这样就能在不同的配置环境下运行
  • 编写可调整的线程代码
  • 运行多于处理器数量的线程
    系统在切换任务时会发生一些事,为了促使任务交换的发生,运行多于处理器或处理器核心数量的线程。任务交换越频繁,越有可能找到错过临界区或导致死锁的二代吗
  • 在不同平台上运行
    尽早并经常在所有目标平台上运行线程代码
  • 装置试错代码
    有时候只有少数路径会真的导致失败,但经过几率非常低。可以考虑装置代码,增加对Object.wait()、Thread.sleep()、Thread.yield()、Object。priority()等方法的调用,改变代码执行顺序。这些方法都会影响执行顺序,从而增加了侦测到缺陷的可能性
    可以使用一些面向切面的框架来构建调用上述方面的切面,来做随机测试

逐步改进

对一个命令行参数解析程序的案例研究

本案例非常详细清晰地描述了如何重构一个命令行参数解析程序
作用:对例如“-l -p 50 -d abc”的字符串做解析
当调用getInt(‘p’)时能得到50

具体过程见书

整个过程体现了抽象、封装的思想,尽可能用基类去实现相似的功能来避免重复。

JUnit内幕

是一个比较两个字符串的工具的重构过程。和上一章内容类似,都是详细描述了重构的过程

不要用实现接口的形式去获得接口中的常量,应该使用静态导入的方法:

1
import static XXXContants.*;

根据java内存模型,32位值的赋值操作是原子的,不可中断的;而64位值的赋值需要两次32位赋值,这意味着第一次和第二次32位赋值之间,其他线程可能插进来,修改其中一个值。