一、迭进
Kent Beck关于简单设计的四条原则:
- 运行所有测试
- 不可重复
- 表达程序猿的意图
- 尽可能减少类和方法的数量
- 以上顾着按其重要程度排序
1.1 简单设计规则1:运行所有测试
设计的首要目的是设计出如预期一般工作的系统。
全面测试并持续通过所有测试的系统,就是可测试系统。看似浅显,但却重要。不可测试的系统不可验证。不可验证的系统不可部署。紧耦合的代码难以编写测试。同样,测试编写越多,就越会遵循DIP之类的规则。
1.2 简单设计规则2~4:重构
有了测试,就能保持代码和类的整洁,方法就是递增式地重构代码。完成修改,运行测试,保证没有破坏任何东西。所以,<b>测试消除了清除代码就会破坏代码的恐惧</b>。
重构的过程中,可以应用有关优秀软件设计的一切知识。提升内聚性,降低耦合度,切分关注切面,模块化系统关注面,缩小函数和类的尺寸,选用更好的名称。即消除重复,保证表现力,尽可能较少类和方法数量。
1.3不可重复
重复是拥有良好设计系统的大敌。Repeat Code代表extra work,意味着extra risk。大量重复可以用模板方法来消除。
1.4表现力
这需要尝试,让后来者易于阅读代码。
1.5尽可能减少类和方法
这是为了防止教条主义,片面追求设计原则,从而导致过度切分。例如给每一个类编写接口,所有行为都切分为数据类和行为类。但是这是优先级最低的一条。
二、并发编程
对象是过程的抽象,线程是调度的抽象。——James O Coplien
并发是一种解耦策略。它帮助我们把做什么(目的)和何时(时机)做分解开。这种解耦能有效改进应用程序的吞吐量和结构。
并发编程很难,往往不细心,你会搞出不知道什么鬼的恶心东西出来。看看下面的迷思和误解:
- 并发总能改变性能
并发有时候能改变性能,但一般在多个线程或处理器之间能风向大量等待时间的时候管用。 - 编写并发程序无须修改设计
这是完全bullshit。目的和时机的解耦代表着往往对系统结构产生巨大的影响。 - 在采用Web或EJB容器的时候,理解并发问题其实不重要。
实际上你最好理解并发在干什么。
总结一下:并发有额外的开销;正确的并发是复杂的;并发缺陷并不总能重现,所以被当做是“基因突变”;并发往往需要对设计策略的根本性的修改。
2.1并发防御原则
2.1.1单一权责原则
SRP:方法、类、组件应当只有一个修改理由。
- 并发相关代码有自己的开发、修改和调优生命周期
- 并发相关代码有自己要对付的挑战,和并发相关代码不同
- 并发编程错误不可控,可能会导致game over、
建议:分离相关代码和其他代码。
2.1.2推论:限制数据作用域
如我们所见,连个线程修改共享对象的统一字段时。采用临界区域形式保护(synchronize)。限制临界区数目,临界区数目越多:
- 忘记保护一个或多个临界区——破坏了共享数据的代码。
- 花力气保护一切都受到保护。(遵循DRY原则:Don't repeat yourself)
- 很难找到错误源。
2.1.3推论:使用数据副本和线程尽可能独立
避免共享数据的方法就是一开始就避免共享数据。
让每个线程在自己的世界里存在,不与其他线程论短长。
2.2执行模型
基础定义:互斥、线程饥饿、死锁、活锁。
执行模型:
- 生产者-消费者:生产者和消费者之间的队列是一种限定资源。
- 读者-作者模型:当存在一个主要围读者线程提供资源的共享资源,其偶尔才被作者进行写。协调吞吐量很重要。
- 宴席哲学家:获得所有资源,执行,否则等待。
2.3警惕同步方法的依赖和保持同步区域微小
同步方法之间的依赖会导致并发代码中的狡猾缺陷。Java有synchronize关键字,保护单个方法,然而,如果再同一共享类中有多个同步方法,系统就可能写得不太正确了。
<b>建议:避免使用一个共享对象的多个方法。</b>
三种对写代码保护方法:
- 基于客户端锁定
- 基于服务端锁定
- 适配服务器
尽量减少同步区。
2.4很难编写正确的关闭代码和测试线程代码
关闭的建议:尽早考虑关闭问题,尽早令其工作。
尽管测试无法证明代码的正确性,但测试可以降低风险。
测试的建议:编写有潜力暴露问题的测试,在不同的环境下频繁运行。
- 将伪失败看做是线程的问题:不要讲系统错误归咎于“基因突变”。
- 先使非先线程代码可工作:不要同时最终非线程缺陷和线程缺陷。
- 编写可插拔的线程代码:在不同配置环境下运行。
- 编写可调整的线程代码。
- 运行多余处理器数量的线程。
- 在不同平台运行。
- 调整代码并强迫错误发生。