程序员可以分为三个层次:普通程序员、工程师 、架构师
1、TDD三定律
定律一、在编写不能通过的单元测试前,不可编写生产代码。
定律二、只可编写刚好无法通过的单元测试,不能编译也算不过。
定律三、只可编写刚好足以通过当前失败测试的生产代码。
(1)测试与生产代码一起编写
2、保持测试整洁
(1)测试必须随生产代码的演进而修改,测试越脏就越难修改
(2)失去了测试,每次修改生产代码都有可能带来缺陷
3、整洁的测试
(1)整洁测试有三个要素:可读性、可读性和可读性
(2)在测试中,要尽可能少的文字表达大量的内容
(3)每个测试都清晰的拆分为三个环节:构造-操作-检验
第一环节构造测试数据;第二环节操作测试数据;第三环节检验操作是否得到期望的结果
(4)测试保证和增强了生产代码的可扩展性、可维护性和可复用性
4、每个测试一个断言
(1)可以利用模版方法模式消除重复代码
(2)单个测试中的断言数量应该最小化
5、F.I.R.S.T.
(1)快速(Fast) :测试应该够快。测试应该能快速运行。测试运行缓慢,你就不回想要频繁地运行它。如果你不频繁运行测试,就不能尽早发现问题,也无法轻易修正,从而也不能轻而易举地清理代码。最终,代码就会腐败。
(2)独立(Independent): 测试应该相互独立。某个测试不应该为下一个测试设定条件。你应该可以单独运行每个测试,以及任何顺序运行测试。当测试互相依赖时,头一个没通过就会导致一连串的测试失败,使问题诊断变得困难,隐藏了下级错粗。
(3)可重复(Repeatable) :测试应当可在任何环境中重复通过。你应该能够生产环境、质检环境中运行测试,也能够在无网络的列车上用笔记本电脑运行测试。如果测试不能在任何环境中重复,你就总会有个解释其失败的接口。当环境条件不具备时,你也会无法运行测试。
(4)自足验证(Self-Validating): 测试应该有布尔值输出。无论是通过或失败,你不应该查看日志文件来确认测试是否通过。你不应该手工对比两个不同文本文件来确认测试是否通过。如果测试不能自足验证,对失败的判断就会变得依赖主观,而运行测试也需要更长的手工操作时间。
(5)及时(Timely) :测试应及时编写。单元测试应该恰好在使其通过的生成代码之前编写。如果在编写生成代码之后编写测试,你会发现生成代码难以测试。你可能会认为某些生产代码本身难以测试。你可能不会去设计可测试的代码。
1、类的组织
(1)类应该从一组变量列表开始:公共静态常量---> 私有静态变量---> 私有实体变量---> 公共变量--->公共函数--->私有工具函数
(2)为了便于测试访问,有时会用到受保护(protected)变量或工具函数,除此之外的大部分情况保持变量和工具函数的私有性。
2、类应该短小
(1)类的名称应当描述其权责,类名越含糊,该类越有可能拥有过多的权责
(2)单一职责原则,类或模块有且只有一条加以修改的理由
(3)系统应该由许多短小的类而不是少量巨大的类组成
(4)类应该只有少量实体变量,方法操作的变量越多,就越粘聚到类上
(5)如果一个类中的每个变量都被每个方法所使用,则该类具有最大的内聚性
(6)创建极大化内聚类是既不可取也不可能的,保持函数和参数列表短小的策略
3、为了修改而组织
(1)具体类包含实现细节,抽象类只呈现概念
(2)依赖倒置原则:类应该依赖于抽象而不是依赖于具体细节
1、如何建造一个城市(系统层级上保持整洁)
2、将系统的构造与使用分开
(1)软件系统应该将起始过程和起始之后的运行时逻辑分离开,在起始过程中构建对象,也会存在相互缠结的依赖关系
(2)延迟初始化/赋值:真正用到对象之前,无需操心这种构造,启动时间也会更短
(3)分解main:将构造与使用分开,将全部构造过程搬迁至main或称之为main模块中,设计系统的其余部分时,假设所有对象都已经正确构造和设置。
(4)可以使用抽象工厂模式让应用自行控制何时创建对象,但构造的细节隔离于应用程序代码之外。
(5)依赖注入(DI)可以实现分离构造和使用,控制反转(IOC)是依赖管理中的一种手段。
(6)面向切面编程(AOP)是一种恢复横贯式关注面模块化的普适手段,常被用来处理持久化、安全和事务
3、JAVA代理适用于简单的情况,例如在单独的对象或类中包装方法调用。
(1)JDK提供的动态代理仅能与接口协同工作;
(2)对于代理类,得使用字节码操作库,比如CGLIB、ASM或Javassist
4、纯Java AOP 框架
(1)在数个Java框架中,代理都是内嵌的,如Spring AOP 和 JBoss AOP等,从而能够以纯Java代码(表示不使用AspectJ)实现面向编程。
(2)使用描述性配置文件或API,你把需要的应用程序架构组合起来,包括持久化、事务、安全、缓存、恢复等横贯性问题。
(3)annotation中有些或者全部持久化信息可以转移到XML部署描述中,只留下真正的纯POJO。
5、AspectJ的方面
(1)在80%~90%用到方面特性的情况下,SpringAOP 和JBoos AOP 提供的纯Java实现手段足够使用
(2)AspectJ的弱势在于,需要采用几种新工具,学习新语言构造和使用方式
6、测试驱动系统架构
(1)在代码层面与架构关注面分离开,就有可能真正用测试来驱动架构
(2)最佳的系统架构由模块化的关注面领域组成,每个关注面均用纯Java(或其他语言)对象实现
(3)不同的领域之间用最不具有侵害性的方面或类方面工具整合起来,这种架构能测试驱动
7、优化决策
(1)模块化和关注面切分成就了分散化管理和决策
(2)在巨大的系统中,最好是授权给最有资格的人
(3)延迟决策至最后一刻是一个好手段,让我们能够基于最有可能的信息作出选择
8、明智使用添加了可论证价值的标准
9、系统需要领域特定语言
(1)领域特定语言(Domain-Specific Language, DSL),是一种单独的小型脚本语言或以标准语言写就的API
(2)DSL在有效使用时能提升代码惯用性和设计模式之上的抽象层次
(3)DSL允许所有抽象层级和应用程序中的所有领域,从高级策略到底层细节,使用POJO来表达
1、通过迭进设计达到整洁目的
(1)Kent Beck 关于简单设计的规则:
以上规则按其重要程度排列
2、简单设计规则(运行所有测试)
(1)不可验证的系统,绝不应该部署
(2)只要系统可测试,就会导向保持类短小且目的单一的设计方案
(3)编写测试越多,就越会遵循DIP之类的规则,使用依赖注入、接口和抽象等工具尽可能减少耦合
3、简单设计规则(重构)
(1)测试消除了对清理代码就会破坏代码的恐惧
(2)优秀软件设计的体现:提升内聚性、降低耦合度、切分关注面、模块化系统性关注面、缩短函数和类的尺寸、选用更好的名称
4、不可重复
(1)重复意味着额外的工作和额外的风险和额外且不必要的复杂度
(2)重复有多种表现,极其雷同的代码或者实现上的重复等形态
int size() {} / boolean isEmpty() {} 这两个方法可以分别实现,为了减少重复,可以在isEmpty中使用size方法
(3)做共性抽取,小规模复用可大量降低系统复杂性
(4)要想实现大规模复用,必须理解如何实现小规模复用
(5)模版方法模式是一种移除高层级重复的通用技巧
abstract public class VacationPolicy{
public void accrueVacation(){
calculateBaseVacationHours();
alterForLegalMinimums();
applyToPayroll();
}
private void calculateBaseVacationHours(){ //implement logic... }
abstract protected void alterForLegalMinimums();
private void applyToPayroll(){ //implement logic... }
}
// 下面子类按照不同各自不同场景填充accrueVacation算法中的“空洞”(abstract)
// 重复信息直接继承,仅提供不重复信息
public class USVacationPolicy extends VacationPolicy{
@Override
protected void alterForLegalMinimums(){
// US Specific logic
}
}
public class EUVacationPolicy extends VacationPolicy{
@Override
protected void alterForLegalMinimums(){
// EU Specific logic
}
}
5、表达力
(1)软件项目的主要成本在于长期维护
(2)通过采用标准命名法来表达
(3)编写良好的单元测试,测试的主要目的之一就是通过实例起到文档的作用
6、尽可能少的类和方法
(1)不能为了保持类和函数短小,造出太多细小的类和方法,也要注意函数和类的数量
(2)在保持函数和类短小的同时,保持整个系统短小精悍
(3)尽管使类和函数的数量尽量少是很重要,但更重要的是测试、消除重复和表达力
1、为什么要并发
(1)并发是一种解耦策略,有利于把做什么(目的)和何时(时机)做分解开
(2)解耦目的与时机能明显地改进应用程序的吞吐量和结构
(3)若系统对响应时间和吞吐量有要求,需要并发解决
(4)单线程在Web套接字I/O上面消耗较大,通过同时访问多个站点的多线程算法能改进性能
(5)常见的迷思与误解
2、挑战
(1)由于多个线程在执行Java代码时有许多可能路径可行,有些路径会产生错误的结果
3、并发防御性原则
(1)单一职责原则:分离并发相关代码与其他生产代码
(2)限制数据作用域:严格限制对可能被共享的数据访问
问题:两个线程修改共享对象的同一字段时,导致未预期的行为
解决方案:采用同步机制,例如Synchronized关键字等锁机制
(3)使用数据复本:例如ThreadLocal实现机制
(4)线程应尽可能地独立:例如HttpServlet的子类接收所有以参数形式传递给doGet和doPost方法的信息
4、了解Java库
(1)线程安全群集:java.util.concurrent包中的群集对于多线程解决方案是安全的
掌握java.util.concurrent、java.util.concurrent.atomic 、java.util.concurrent.locks
(2)使用Excutor框架执行无关任务
(3)尽可能使用非锁定的解决方案
(4)有几个类并不是线程安全的
5、了解执行模型
(1)并发应用中的基础定义:限定资源、互斥、线程饥饿、死锁、活锁
(2)生产者-消费者模型
(3)读者-作者模型
(4)宴席哲学家:容易出现死锁、活锁、吞吐量和效率降低等问题
6、警惕同步方法之间的依赖:避免使用同一个共享对象的多个方法
7、保持同步区域微小:尽可能减小同步区域,降低锁带来的延迟和额外开销
8、很难编写正确的关闭代码:存在的问题与死锁有关,线程会等待永远不会到来的信号
9、测试线程代码
(1)将伪失败看作可能的线程问题:偶发事件被忽略的越久,代码就越可能搭建在不完善的基础上
(2)先使非线程代码可工作:不要同时追踪非线程缺陷和线程缺陷
(3)编写可插拔的线程代码,这样就能在不同的配置环境下运行
(4)编写可调整的线程代码:允许线程依据吞吐量和系统使用率自我调整
(5)运行多于处理器数量的线程,促使任务交换的发生,任务交换越频繁,越有可能找到错过临界区或导致死锁的代码
(6)在不同平台上运行:在不同环境中多线程代码的行为也不一样,应该在所有可能部署的环境中运行测试
(7)调整代码并强迫错误发生
(8)自动化:可以使用Aspect-Oriented Framework 、 CGLIB或ASM之类的工具
------------------------------------------------------------------------- end -------------------------------------------------------------------------------