一、有意义的命名及函数
- 类名:使用名词,方法名:动词
- 别用双关语:避免将同一单词用于不同目的。eg. add
- 使用解决方案领域名称。eg. JobQueue;如果不能使用程序员熟悉的术语来给手头的工作命名,就采用从所涉问题而来的名称。
- 函数应短小(每行150字符,不超过100行,20行封顶最佳)。
- 函数只做一件事,同设计模式一样,应尽量遵循单一权责原则及开闭原则。
- 每个函数中的语句,理应都要在同一抽象层级上。
- 沃德原则:如果每个例程都让你感到深合己意,那就是整洁代码(并且不要怕长名称方法名)
- 函数的参数:最理想的参数数量是零,其次是一,再次是二,尽量避免三。有足够特殊的理由才能用三个以上参数(阿里巴巴Java开发手册:相同参数类型,相同业务含义,才可以使用 Java 的可变参数,可变参数放在最后,尽量不用可变参数,避免使用 Object)
- 标识参数:丑陋不堪,render(Boolean isSuite)代表了该函数在标识为true会这样做,为false会那样做。应该一分为二——renderForSuite()和renderForSingleTest()
- 二元及三元函数:在当单个值的有序组成部分不确定时,多元函数并不是好的选择
- 参数对象:如果函数看来需要两个、三个或三个以上参数,就说明其中一些参数应该封装为类了。
Circle makeCircle(double x,double y,double radius)
Circle makeCircle(Point center,double radius)
后者优于前者
- 函数与参数:应是一种非常良好的动词/名词对形式
write(name) - 无副作用:在执行函数时,应不该造成其他部分的更改
- 分隔指令与询问:函数要么做什么事,要么回答什么事
- 使用异常代替返回错误代码,抽离Try/Catch代码块。Try/Catch代码块丑陋不堪,搞乱了代码结构,最好把其中内容单独写成一个函数。
二、注释
- 注释并不能美化糟糕的代码,最好用代码来阐述
- TODO 是一种程序员认为应该做,但由于某些原因目前还没做的工作,它可能是要提醒删除某个不必要的特性,或者要求他人注意某个问题等。
- 坏注释:
喃喃自语、多余的注释、误导性注释、循规式注释、日志式注释、废话注释。(p55-p61) - 不应当有的注释:位置标记、括号后面的注释、归属与署名、注释掉的代码、HTML注释、非本地信息、信息过多、不明显的联系(p62-p65)
- 非公共代码中的Javadoc
三、格式
- 垂直格式
单个文件代码行数fitnese多在200行到500行之间
(作者认为尽量精简代码在单个文件中,并且文件之间代码行数最小与最大值差距不要过大)
- 向报纸学习(自上而下,内容逐次展开)。实际编程中注意很难注意这个
-
适当的换行
- 方法与方法之间
- 变量与方法之间
- import 与类之间
- import 系统类和导入包中的类或者自定义类之间
-
垂直距离
- 变量声明应尽可能靠近其使用位置
- 实体变量应放在类的顶部声明
- 相关函数应尽量放在一起,一个方法调用了该类中的另一个方法,应尽量放在一块
- 概念相关的代码应该放在一起,重载的方法也算
-
横向距离
(一行代码所用字符 [阿里巴巴java开发手册中有定义],水品对齐,水平方向上的取个与靠近-不认同)- 同一层级的代码应该缩进至同一个层级(Python中采用强制缩进)
四、对象和数据结构
- 面向过程式代码与面向对象式代码(p89-p90)
对于面向对象较难的事,对于过程式代码却较容易,反之亦然。 -
得墨忒耳律认为,类 C 的方法f只应该调用以下对象的方法:
- C
- 由 f 创建的对象
- 作为参数传递给 f 的对象
- 由 C 的实体变量持有的对象
方法不应调用有任何函数返回的对象的方法,换言之,只跟朋友谈话,不与陌生人谈话。(实际生产环境用jfinal service 调 dao 是用的该实体类的静态内部对象,与ssm有不同)
- DTO(Data Transfer Objects) 里最好不要有业务规则代码。
五、错误处理
- 使用异常而非返回码
- 先写 try - catch - finally
- 某种意义上,try 代码块就像是事务,catch 代码块将程序维持在一中持续状态,无论 try 代码库中发生了什么均如此。
- 给出异常发生的环境说明。
- 别传递 null 值:可以用断言代替判空。 assert p1 != null: "p1 should not be null";
六、边界
- 学习 log4j
- 使用上尚不存在的代码:适配器模式(作者采用的)
七、类
- 类的组织:公共静态变量->私有静态变量->私有实体变量。很少出现公共实体变量
- 类应该短小:对于类,我们采用不同的衡量方法,计算权责。
- 单一权责原则:类或模块应有且只有一条加以修改的理由。
- 内聚:类应该只有少量实体变量,类中的每个方法都应该操作一个或多个这种变量。
- 文中举例说明了一个 Sql 类的重构。将一个 Sql 类中所有的 CRUD 方法抽象成一个 generate 方法,并由相应的 CRUD 类继承。(和现在 jfinal 的使用方法不一样,虽然违反了设计模式开闭原则。但我认为对中小型项目开发,反设计模式更为便捷)
八、系统
- 依赖注入(Dependency Injection DI),控制反转(Inversion of Control IOC):可以实现分离构造与使用,控制反转将第二权责从对象中拿出来,转义到另一个专注于此的对象中,从而遵循了单一权责原则。因为初始设置是一种全局问题,这种授权机制通常要么是 main 例程,要么是有特定的目的的容器(Spring 中采用 ConcurrentHashMap 作为该容器)。
- 扩容:为达到松耦合,采用 AOP 实现某些代码的行为修改。(最终的系统,系统内的模块达到高内聚低耦合的状态,并且易扩展)
- Java代理:详情 p148
九、并发编程
- 并发是一种解耦策略。它帮助我们把做什么(目的)和 何时(时机)做分解开。
- 常见的迷思与误解
- 并发总能改进性能?
并发有时候能改进性能,单只在多个线程或处理器之间能分享大量等待时间的时候管用。事情没那么简单。(编排考生时,又开了一个线程,编排所有考生) - 编写并发程序无需修改设计
事实上,并发算法的设计有可能与单线程系统的设计极不相同。目的与时间的解耦往往对系统结构产生巨大影响。 - 采用Web 或 EJB 容器的时候,理解并发问题并不重要(实际开发中,面向servlet开发。。。)
- 并发防御原则
- 单一权责原则
建议将并发相关代码与其他代码进行分离 - 限制数据作用域
产生并发问题的原因,其实就是共享变量,在 JMM 中,共享变量存在于主存中,多个线程从主存中拷贝共享变量到当前线程中,在线程内部进行对变量的操作。 CPU 给每个线程分配时间片,当线程 A 将共享变量刷回主存中前,线程 B 可能得到了 CPU 分配的时间片,由于 A 线程未及时刷新共享变量至主存,B 线程取得的是未经过 A 线程修改的值,会造成数据与理想情况不一致。最经典的多线程入门,进行自增操作时,如果开了多线程进行该操作,结果永远是小于等于 期望值。例如`
java
int num=0;
for(int i=0;i<100;i++){
num++;
}
}