命名
命名规则
- 表明变量/方法的用途
- 不是List类型的变量不要用xxxList结尾,应该用xxxs命名:比如一组账号不要用accountList,应该用accounts
- 不要做没有意义的命名:比如moneyAmount与money没有区别,customerInfo和customer也没有区别,accountData与account没有区别
- 命名要能够读得出来(英文单词的组合)
- 可搜索的名称(名称长短应该对该变量作用域的大小)
- 类名命名规范:应该是名词或者名词短语,如Customer、Wikipage、AddressParser等,类名不应该是动词。
- 方法名应该是动词或者动词短语,对属性(成员变量)的访问用getXXX,修改用setXXX,判断用isXXXX。
- 对同一种概念应该用相同的名词后缀(不要使用近义词英文),比如控制器统一用xxxController不要一些用xxxController,一些用
- 给一组变量添加有意义的语境(比如把这些变量都放到一个类里面给读者提供语境,如果不能就加上前缀或者后缀)
函数
- 短:函数的缩进不应该多于一层或两层,if 、else、while语句中的代码应该只有一行
- 函数只做一件事:函数中的语句要在同一抽象层级上,这个函数应该引出下一个抽象层的函数。函数的功能可以解释为:要做xxxxx(),然后做xxx(),最后做xxx() 。这三个函数就是下一抽象层要做的东西
- switch语句中应该下沉到较低的抽象层,switch的每个分支中用工厂方法创造出不同的接口实例,然后上层函数对接口编程
- 长而明确的函数名比短而模糊的
- 函数入参(无 -> 一个参数 ->两个参数 -> 三个参数):
5.1 一元参数:①问关于该参数的问题boolean fileExists(“MyFile”) ②操作该参数,转换为其他东西 InputStream fileOpen(“MyFile”) ③事件,void passwordAttempFailedNtimes(int attempts) ,改变状态
尽量不要写不符合这三种情况的函数,比如用输出参数而不用返回值,如 StringBuffer transform(StringBuffer in) 好于 void transform(StringBuffer out) , 不要使用布尔值作为入参
5.2 超过三个参数以上考虑把其中某几个参数封装为一个类中的字段
5.3 避免使用输出参数 : 输出参数就是会被一个变量的应用被当作入参传入函数,然后在函数中改变了该引用所指向的对象。如report.appendFooter() 比appendFooter(report)要好 - 函数要不就是做什么,要不就是回答什么事,不能两件事同时做,所以尽量不要写做什么成功后返回true,失败后返回false的函数
- 使用异常代替错误码,同时,用throw 代替try catch , catch只在最外层抓住。
- 函数的编写方法,初稿可以先是能实现功能的混乱代码,写完后配上一套单元测试,然后再重构
注释
原则:能不写注释尽量别写
好的注释
- 提供函数基本信息,提供代码意图,解释一些难懂的函数返回结果,警示,TODO,放大某种不合理事物的重要性
格式
垂直距离
- 包名、引入、每个函数之间空一行
- 变量要靠近使用它的位置、函数本地变量要出现在顶部
- 被调用函数要在被调用函数下面
水平距离
- 赋值语句,加减运算符左右空格
格式规范例子
- 变量位于类顶部
- 构造函数位于变量之后
- 算术表达式前后有空格,布尔表达式前后有空格,函数声明 “{” 前有空格,for if while 后有空格,“:” 前后有空格
数据、对象的反对称性
- getter和setter的目的是为了把对对象数据的访问抽象化
- 数据结构与对象的区别:①数据结构暴露数据,没有提供有意义的函数;②对象封装函数,暴露操作数据的函数。这称为反对称性,数据结构便于增加方法,而对象便于增加类型。
- 得墨hao 定律:类C的方法 f 只应该调用以下对象的方法:①类C ②类C实例变量对象的方法 ③f 创建的对象 ④f 的参数对象
错误处理
- 使用异常而不是错误码
- 先写try catch finally语句
- 使用不可控异常:所谓不可控异常就是不被声明的,可控异常就是使用已知的异常,这样在底层网上抛的时候每一层都要捕获或者抛出这个异常
- 对于调用第三方的API,可以把其抛出的异常打包一个异常类
- 不要返回null值,用返回特例对象或者抛出异常代替
特例对象就是把下层调用中可能会出现Null的情况在下层调用做一个封装,或者把对第三方调用做一层封装,出现Null时创建特例对象,然后返回给上层 - 不要传递Null值,入参不要传递null,应该用函数重构处理掉
边界处理
1、对于第三方API , 写学习性单元测试,就是写关于这个API使用方式的单元测试。
2、对于外部调用服务API,通过adapter的方式转换为自己想要的服务
比如对于类Map<k,V> , map.get(k) 往往需要强制类型转化如下代码:
Map sensors = new HashMap();
Sensor s = (Sensor) sensors.get(sensorId);
//或者一般在创建的时候就会指定类型
Map<String, Sensor> sensors = new HashMap<String, Sensor>();
Sensor s = sensors.get(sensorId)
更好的做法是定义一个方法把显示转换写到方法里面,如:
public class Sensors {
private Map sensors = new HashMap();
public Sensor getById(String id) {
return (Sensor) sensors.get(id);
}
}
这样就不用每次调用这个map的get方法都要显示转换。
测试代码整洁性
1、每个测试方法应该只包含 数据构造 -> 调用方法 -> 判断(assert) 几步
2、每个测试方法只测试一个概念且断言(assert语句应该尽量少,最好只有一个)
3、测试代码的原则:first
F: fast - > 运行应该足够快
I: independent -> 测试应该独立的,相互之间没有时序依赖性
R: repeatable -> 在任何环境(线下 or 线上) 都是要可通过的
S: self-validating -> 测试一定要有断言assert判断对错,不通过日志确认是否通过测试
T: timely -> 及时的,测试在写其对应的生产代码前编写(实际操作有点难)
类的整洁性
- 单一权责原则(SRP) :一个类只有一个修改它的理由
- 类的内聚性不能太高:一个类中如果每个方法都会引用到一个类的所有成员变量,那这个类的内聚性是最高的。
- 当类丧失内聚性时就该把它拆分为更多的短小的类(让类的实例变量和调用这些变量的方法抽象到另一个类中)
- 开放-闭合原则(OCP):一个类应该对扩展开放(可被继承产生子类),对修改封闭
- 依赖倒置原则(DIP) :类应该依赖抽象(接口、抽象类) 而不应该依赖具体实现。
系统的整洁性
- 构造与使用分离:对象的初始化应该放在main()或者容器中,用工厂模式给应用自行确定创建时间。经典例子:Spring的依赖注入
迭进
四种原则去设计良好的软件:
- 运行所有的测试
- 不可重复:通过抽象方法或抽象基类的方法
- 表达力:命名;短小的类等
- 尽量减少类和函数的数量
并发
- 并发防御原则:
①单一权责原则(SRP) :尽量分离并发相关代码与其它代码
②限制数据作用域:用synchronized关键字限制作用域,这个作用域应该尽量小
③使用数据副本
④线程尽量独立:类的实体变量以函数参数的形式传递