如何优雅的书写代码
...大约 12 分钟
如何优雅的书写代码
第01章 前言
可能代码整洁之道不会让你立刻变成一名优秀的工程师,长期奉行它们也并不意味着能够高枕无忧不再犯错。但千里之行,始于足下。我们需要时常和同行们进行代码评审,从而不断优化自己的代码。不必严格遵守本文的所有原则,有时少遵守一些效果可能会更好,具体应根据实际情况决定。
第02章 前端
第03章 后端
命名风格
强制
代码中的命名均不能以下划线(_)和美元符号($)开始也不能以其结束。
反例:_name | __name | $name | name_ | name$ | name__
命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
正例:ali | alibaba | taobao | cainiao |aliyun | youku | hangzhou等国际通用的名称,可视同英文。
类名、接口名使用UpperCamelCase(大驼峰)风格,但 DO | BO | DTO | VO | AO | PO | UID 例外。
正例:ForceCode | UserDO | HtmlDTO | XmlService | TcpUdpDeal | TaPromotion
反例:forcecode | UserDo | HTMLDto | XMLService | TCPUDPDeal | TAPromotion
说明:DTO VO等类名应命名为UserDTO而不是UserDto
方法名、参数名、成员变量、局部变量统一使用lowerCamelCase(小驼峰)风格。
正例: localValue | getHttpMessage() | inputUserId
常量名全部大写,单词之间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
正例:MAX_STOCK_COUNT | CACHE_EXPIRED_TIME
反例:MAX_COUNT | EXPIRED_TIME
异常类命名使用Exception结尾;
抽象类名使用Abstract或Base开头;
测试类命名以需要测试的类的名称开始,以Test结尾;
类型与中括号紧挨相连来表示数组。
正例:定义整形数组int[] arrayDemo;
反例:在main参数中,使用String args[]来定义。
POJO实体类中的任何布尔类型的变量,都不要加is前缀,否则部分框架解析会引起序列化错误。
包名统一使用小写,点分隔符之间有且仅有一个自然语义的英文单词,包名统一使用单数形式;
类名如果有复数含义,可以使用复数形式。
避免在子父类的成员变量之间,或者不同代码块的局部变量之间采用完全相同的命名。
杜绝完全不规范的缩写,避免望文不知义。
反例:AbstractClass命名成AbsClass;condition命名成condi。
推荐
任何自定义编程元素在命名时,使用尽量完整的单词组合来表达。
反例:常见的方法内变量为 int a; 的定义方式
在常量与变量的命名时,表示类型的名词放在词尾,以提升辨识度。
正例:startTime | workQueue | nameList | TERMINATED_THREAD_COUNT
反例:startedAt | QueueOfWork | listName | COUNT_TERMINATED_THREAD
如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。
说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。
正例: public class LoginProxy;
public class OrderFactory;
public class ResourceObserver;
接口类中的方法和属性不要加任何修饰符号(public也不要加),保持代码的简洁性,并加上有效的JavaDoc注释。尽量不要在接口里定义变量,如果一定要定义变量,确定与接口方法相关,并且是整个应用的基础变量。
正例:接口方法签名 void commit();
接口基础常量 String COMPANY = "alibaba";
反例:接口方法定义 public abstract void f();
说明:JDK8中接口允许有默认实现,那么这个default方法,是对所有实现类都有价值的默认实现。
接口和实现类的命名规则
1)【强制】对于Service和DAO类,基于SOA的理念,暴露出来的服务一定是接口,内部的实现类用
Impl的后缀与接口区别。
正例:CacheServiceImpl 实现 CacheService 接口。
2)【推荐】如果是形容能力的接口名称,取对应的形容词为接口名(通常是–able的形容词)。
正例:AbstractTranslator 实现 Translatable 接口。
枚举类名带上Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开。
说明:枚举其实就是特殊的常量类,且构造方法被默认强制是私有。
正例:枚举名字为ProcessStatusEnum的成员名称:SUCCESS | UNKNOWN_REASON。
各层命名规约
A) Service/DAO 层方法命名规约
1) 获取单个对象的方法用get做前缀。
2) 获取多个对象的方法用list做前缀,复数结尾,如:listObjects。
3) 获取统计值的方法用count做前缀。
4) 插入的方法用save|insert做前缀。
5) 删除的方法用remove|delete做前缀。
6) 修改的方法用update做前缀。
B) 领域模型命名规约
1) 数据对象:xxxDO,xxx即为数据表名。
2) 数据传输对象:xxxDTO,xxx为业务领域相关的名称。
3) 展示对象:xxxVO,xxx一般为网页名称。
4) POJO是DO|DTO|BO|VO的统称,禁止命名成xxxPOJO。
常量定义
强制
不允许任何魔法值(即未经预先定义的常量)直接出现在代码中
反例:
//本例中同学A定义了缓存的key,然后缓存提取的同学B使用了Id#taobao来提取,少了下划线,导致故障。
String key = "Id#taobao_" + tradeId;
cache.put(key, value);
在Long赋值时,数值后使用大写的L,不能是小写的l,小写容易跟数字混淆,造成误解
说明:Long a = 2l; 这样写不能知道是数字21,还是Long型的2。应写成Long = 2L;
推荐
不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。
说明:大而全的常量类,杂乱无章,使用查找功能才能定位到修改的常量,不利于理解,也不利于维护。
正例:缓存相关常量放在类CacheConsts下;系统配置相关常量放在类ConfigConsts下。
常量的复用层次有五层:
跨应用共享常量:放置在二方库中,通常是client.jar中的constant目录下。
应用内共享常量:放置在一方库中,通常是子模块中的constant目录下。
子工程内共享常量:即在当前子工程的constant目录下。
包内共享常量:即在当前包下单独的constant目录下。
类内共享常量:直接在类内部使用private static final定义。
如果变量值仅在一个固定范围内变化用enum类型来定义。
说明:如果存在名称之外的延伸属性应使用enum类型,下面正例中的数字就是延伸信息,表示一年中的第几个季节。
正例:
public enum SeasonEnum {
SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
private int seq;
SeasonEnum(int seq) {
this.seq = seq;
}
public int getSeq() {
return seq;
}
}
代码格式
强制
如果是大括号内为空,则简洁地写成{}即可,大括号中间无需换行和空格;如果是非空代码块则:
1) 左大括号前不换行。
2) 左大括号后换行。
3) 右大括号前换行。
4) 右大括号后还有else等代码则不换行;表示终止的右大括号后必须换行。
如下
if (num > 10) {
} else {
}
左小括号和右边相邻字符之间不出现空格;右小括号和左边相邻字符之间也不出现空
格;而左大括号前需要加空格。详见第5条下方正例提示。
反例:if ( a == b )
正例:if (a == b)
if/for/while/switch/do 等保留字与括号之间都必须加空格。
任何二目、三目运算符的左右两边都需要加一个空格。
说明:包括赋值运算符=、逻辑运算符&&、加减乘除符号等。
正例如下:
int a = b;
if (a > b && a > c) {
}
int a = 20 - 10 * 2 + 30;
采用4个空格缩进,禁止使用tab字符。
说明:如果使用tab缩进,必须设置1个tab为4个空格。IDEA设置tab为4个空格时,请勿勾选Use tab character
正例:
public static void main(String[] args) {
// 缩进4个空格
String say = "hello";
// 运算符的左右必须有一个空格
int flag = 0;
// 关键词if与括号之间必须有一个空格,括号内的f与左括号,0与右括号不需要空格
if (flag == 0) {
System.out.println(say);
}
// 左大括号前加空格且不换行;左大括号后换行
if (flag == 1) {
System.out.println("world");
// 右大括号前换行,右大括号后有else,不用换行
} else {
System.out.println("ok");
// 在右大括号后直接结束,则必须换行
}
}
注释的双斜线与注释内容之间有且仅有一个空格。
正例:
// 这是示例注释,请注意在双斜线之后有一个空格
String commentString = new String();
在进行类型强制转换时,右括号与强制转换值之间不需要任何空格隔开。
正例:
long first = 1000000000000L;
int second = (int)first + 2;
单行字符数限制不超过120个,超出需要换行,换行时遵循如下原则:
1)第二行相对第一行缩进4个空格,从第三行开始,不再继续缩进,参考示例。
2)运算符与下文一起换行。
3)方法调用的点符号与下文一起换行。
4)方法调用中的多个参数需要换行时,在逗号后进行。
5)在括号前不要换行,见反例。
正例:
StringBuilder sb = new StringBuilder();
// 超过120个字符的情况下,换行缩进4个空格,并且方法前的点号一起换行
sb.append("zi").append("xin")...
.append("huang")...
.append("huang")...
.append("huang");
反例:
StringBuilder sb = new StringBuilder();
// 超过120个字符的情况下,不要在括号前换行
sb.append("you").append("are")...append
("lucky");
// 参数很多的方法调用可能超过120个字符,逗号后才是换行处
method(args1, args2, args3, ...
, argsX);
方法参数在定义和传入时,多个参数逗号后边必须加空格。
正例:下例中实参的args1,后边必须要有一个空格。
method(args1, args2, args3);
IDE的text file encoding 设置为UTF-8; IDE中文件的换行符使用Unix格式,不要使用Windows格式。
推荐
单个方法的总行数不超过80行
说明:除注释之外的方法签名、左右大括号、方法内代码、空行、回车及任何不可见字符的总行数不超过80行。
正例:代码逻辑分清红花和绿叶,个性和共性,绿叶逻辑单独出来成为额外方法,使主干代码更加清晰;共性逻辑抽取成为共性方法,便于复用和维护。
没有必要增加若干空格来使变量的赋值等号与上一行对应位置的等号对齐。
正例:
int one = 1;
long two = 2L;
float three = 3F;
StringBuilder sb = new StringBuilder();
说明:增加sb这个变量,如果需要对齐,则给one、two、three都要增加几个空格,在变量比较多的情况下,是非常累赘的事情。
不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性。
说明:任何情形,没有必要插入多个空行进行隔开。
OOP(面向对象) 规约
强制
避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
所有的覆写父类方法,必须加@Override注解。
说明:getObject()与 get0bject()的问题。一个是字母的O,一个是数字的0,加@Override可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。
相同参数类型,相同业务含义,才可以使用Java的可变参数,避免使用Object。
说明:可变参数必须放置在参数列表的最后。(尽量不用可变参数编程)
正例:public List<User> listUsers(String type, Long... ids) {...}
外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated注解,并清晰地说明采用的新接口或者新服务是什么。
不能使用过时的类或方法。
说明:java.net.URLDecoder 中的方法 decode(String encodeStr) 这个方法已经过时,应该使用双参数
decode(String source, String encode)。接口提供方既然明确是过时接口,那么有义务同时提供新的接口;
作为调用方来说,有义务去考证过时方法的新实现是什么。
Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。
正例:"test".equals(object);
反例:object.equals("test");
所有整型包装类对象之间值的比较,全部使用equals方法比较。
说明:对于Integer var = ? 在-128至127之间的赋值,Integer对象是在 IntegerCache.cache 产生,
会复用已有对象,这个区间内的Integer值可以直接使用 == 进行判断,但是这个区间之外的所有数据,都
会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。
任何货币金额,均以最小货币单位且整型类型来进行存储。
Powered by Waline v2.15.8