尽量给阅读的人一种如沐春风,赏心悦目的感觉。
取一个特别合适的名字是一件非常有挑战的事情
大到项目名、模块名、包名、对外暴露的接口,小到类名、函数名、变量名、参数名,只要是写代码,我们就逃不过”起名字”这一关。命名的好坏,对于代码的可读性来说非常重要。除此之外,命名能力也体现了一个程序员的基本编程素养。
想要起一个能准确达意的名字,没有足够的积累,确实挺费劲。
实际上,命名这件事说难也不难,对于影响范围比较大的命名,比如包名、接口、类名,我们一定要反复斟酌、推敲。可以去 GitHub 上用相关的关键词联想搜索一下,看看类似的代码是怎么命名的。或者经常看看别人写的代码,慢慢就会有自己的词库。
命名的时候,我们一定要学会换位思考,假设自己不熟悉这块代码,从代码阅读者的角度去考量命名是否足够直观。
项目module命名,结合COLA尽量保持统一风格
包名统一使用小写。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。例:util包,StringUtils类;类似的可以参考spring框架中的一些命名习惯,同时建议在每一个包里面加入package-info文件来说明这个包里面的内容
命名风格
类名使用UpperCamelCase风格,但以下情形例外:DO/BO/DTO/VO/AO/ PO / UID 等。
方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase风格
命名的长和短
长的命名可以包含更多的信息,更能准确直观地表达意图,但是,如果函数、变量的命名很长,那由它们组成的语句就会很长。在代码列长度有限制的情况下,就会经常出现一条语句被分割成两行的情况,这其实会影响代码可读性。
实际上,在足够表达其含义的情况下,命名当然是越短越好。但是,大部分情况下,短的命名都没有长的命名更能达意。所以,很多书籍或者文章都不推荐在命名时使用缩写。
对于一些默认的、大家都比较熟知的词,比较推荐用缩写。这样一方面能让命名短一些,另一方面又不影响阅读理解,比如,sec 表示 second、str 表示 string、num 表示 number、doc 表示 document。
除此之外,对于作用域比较小的变量,我们可以使用相对短的命名,比如一些函数内的临时变量。相反,对于类名这种作用域比较大的,更推荐用长的命名方式。
增删改查动作命名
获取单个对象的方法用 get 做前缀。
获取多个对象的方法用 list 做前缀,复数结尾,如:listObjects。
;获取统计值的方法用 count 做前缀。
插入的方法用 save/insert 做前缀。
删除的方法用 remove/delete 做前缀。
修改的方法用 update 做前缀。
分页的方法用 page 做前缀。
分层领域对象命名
数据对象:xxxDO,xxx 即为数据表名。DO( Data Object)
数据传输对象:xxxDTO,xxx 为业务领域相关的名称。DTO( Data Transfer Object)
展示对象:xxxVO,xxx 一般为网页名称。VO( View Object)
POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO。POJO( Plain Ordinary Java Object)
抽象类命名使用Abstract或Base开头;异常类命名使用Exception结尾;测试类 命名以它要测试的类的名称开始,以 Test 结尾。
接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的 Javadoc 注释。尽量不要在接口里定义变量,如果一定要定义变量,确定与接口方法相关,并且是整个应用的基础常量。
POJO类中的任何布尔类型的变量,建议都不要加is前缀,否则部分框架解析会引起序列化错误。
常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
杜绝完全不规范的缩写,避免望文不知义。
反例:AbstractClass“缩写”成 AbsClass;condition“缩写”成 condi;Function 缩写”成 Fu,此类 随意缩写严重降低了代码的可阅读性
在团队、项目中保持风格统一,让代码像同一个人写出来的,整齐划一。这样能减少阅读干扰,提高代码的可读性。这才是我们在实际工作中想要实现的目标。
善用空行分割单元块
对于比较长的函数,如果逻辑上可以分为几个独立的代码块,在不方便将这些独立的代码块抽取成小函数的情况下,为了让逻辑更加清晰,可以用总结性注释的方法之外,我们还可以使用空行来分割各个代码块。
除此之外,在类的成员变量与函数之间、静态成员变量与普通成员变量之间、各函数之间、甚至各成员变量之间,我们都可以通过添加空行的方式,让这些不同模块的代码之间,界限更加明确。写代码就类似写文章,善于应用空行,可以让代码的整体结构看起来更加有清晰、有条理。
类中成员的排列顺序
在类中,成员变量排在函数的前面。成员变量之间或函数之间,都是按照“先静态(静态函数或静态成员变量)、后普通(非静态函数或非静态成员变量)”的方式来排列的。
除此之外,成员变量之间或函数之间,还会按照作用域范围从大到小的顺序来排列,先写 public 成员变量或函数,然后是 protected 的,最后是 private 的。
实际上,还有另外一种排列习惯,那就是把有调用关系的函数放到一块。比如,一个 public 函数调用了另外一个 private 函数,那就把这两者放到一块。
IDE 的 text file encoding 设置为 UTF-8; IDE 中文件的换行符使用 Unix 格式
统一codeStyle:https://github.com/google/styleguide/blob/gh-pages/intellij-java-google-style.xml
统一checkStyle: https://www.jianshu.com/p/0c917f4cac1e;https://checkstyle.sourceforge.io/
注释的目的就是让代码更容易看懂。只要符合这个要求的内容,你就可以将它写到注释里。总结一下,注释的内容主要包含这样三个方面:做什么、为什么、怎么做。
函数和变量如果命名得好,确实可以不用再在注释中解释它是做什么的。对于有些比较复杂的类或者接口,我们可能还需要在注释中写清楚“如何用”;
对于逻辑比较复杂的代码或者比较长的函数,如果不好提炼、不好拆分成小的函数调用,那我们可以借助总结性的注释来让代码结构更清晰、更有条理。
注释太多和太少的问题,类和函数一定要写注释,而且要写得尽可能全面、详细,而函数内部的注释要相对少一些,一般都是靠好的命名、提炼函数、解释性变量、总结性注释来提高代码的可读性。
代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑 等的修改。
在类中删除未使用的任何字段、方法、内部类;在方法中删除未使用的任何参数声明 与内部变量
谨慎注释掉代码。在上方详细说明,而不是简单地注释掉。如果无用,则删除。 说明:代码被注释掉有两种可能性:1)后续会恢复此段代码逻辑。2)永久不用。前者如果没有备注信息, 难以知晓注释动机。后者建议直接删掉即可,假如需要查阅历史代码,登录代码仓库即可。
不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。
缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 SystemConfigConsts 下
不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。
常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包 内共享常量、类内共享常量,对于每种常量建议放到各自的包中
避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可
外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生 影响。接口过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么。
不能使用过时的类或方法。一般提供方都会说明新的实现是什么。
Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调equals,“test”.equals(object)
所有整型包装类对象之间值的比较,全部使用equals方法比较
任何货币金额,均以最小货币单位且整型类型来进行存储
BigDecimal 的等值比较应使用 compareTo()方法,而不是 equals()方法
禁止使用构造方法 BigDecimal(double)的方式把 double 值转化为 BigDecimal 对象
BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常,优先推荐入参为 String 的构造方法,或使用 BigDecimal 的 valueOf 方法
关于基本数据类型和包装数据类型
构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中
禁止在POJO类中同时存在对应属性xxx和isXxx()和getXxx()方法
getter/setter 方法中,不要增加业务逻辑,增加排查问题的难度,可以单独提供领域操作方法
类成员与方法访问控制从严
任何类、方法、参数、变量,严控访问范围。过于宽泛的访问范围,不利于模块解耦。思考:如果 是一个 private 的方法,想删除就删除,可是一个 public 的 service 成员方法或成员变量,删除一下,不得手心冒点汗吗?变量像自己的小孩,尽量在自己的视线内,变量作用域太大,无限制的到处跑。
重点是stream的使用注意
在使用java.util.stream.Collectors类的toMap()方法转为Map集合时,一定要使
用含有参数类型为 BinaryOperator,参数名为 mergeFunction 的方法,否则当出现相同key
值时会抛出 IllegalStateException 异常。
// 正例:
List<Pair<String, Double>> pairArrayList = new ArrayList<>(3);
pairArrayList.add(new Pair<>("version", 12.10));
pairArrayList.add(new Pair<>("version", 12.19));
pairArrayList.add(new Pair<>("version", 6.28));
Map<String, Double> map = pairArrayList.stream().collect(
// 生成的 map 集合中只有一个键值对:{version=6.28}
Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));
// 反例:
String[] departments = new String[] {"iERP", "iERP", "EIBU"};
// 抛出 IllegalStateException 异常
Map<Integer, String> map = Arrays.stream(departments)
.collect(Collectors.toMap(String::hashCode, str -> str));
使用java.util.stream.Collectors类的toMap()方法转为Map集合时,一定要注意当 value 为 null 时会抛 NPE 异常
// 说明:在 java.util.HashMap 的 merge 方法里会进行如下的判断:
if (value == null || remappingFunction == null)
throw new NullPointerException();
// 反例:
List<Pair<String, Double>> pairArrayList = new ArrayList<>(2);
pairArrayList.add(new Pair<>("version1", 8.3));
pairArrayList.add(new Pair<>("version2", null));
Map<String, Double> map = pairArrayList.stream().collect(
// 抛出 NullPointerException 异常
Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));
Collections 类返回的对象,如:emptyList()/singletonList()等都是 immutable list,不可对其进行添加或者删除的操作;
利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的 contains()进行遍历去重或者判断包含操作。
结合校验的框架和MVC中的序列化和反序列化自定义注解等类似的方式,尽量将校验的逻辑和业务处理分离且使用方便
下列情形,需要进行参数校验:
调用频次低的方法。
执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,那得不偿失。
需要极高稳定性和可用性的方法。
对外提供的开放接口,不管是 RPC/API/HTTP 接口。
敏感权限入口。
下列情形,不需要进行参数校验:
极有可能被循环调用的方法。但在方法说明里必须注明外部参数检查。
底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。一般DAO 层与 Service 层都在同一个应用中,部署在同一台服务器中,所以 DAO 的参数校验,可以省略。
被声明成 private 只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检 查或者肯定不会有问题,此时可以不校验参数。
URL 路径不能使用大写,单词如果需要分隔,统一使用下划线
在前后端交互的 JSON 格式数据中,所有的 key 必须为小写字母开始的 lowerCamelCase 风格,符合英文表达习惯,且表意完整
对于需要使用超大整数的场景,服务端一律使用 String 字符串类型返回,禁止使用Long类型
HTTP请求通过URL传递参数时,不能超过2048字节
HTTP 请求通过 body 传递内容时,必须控制长度,超出最大长度后,后端解析会出错。说明:nginx 默认限制是 1MB,tomcat 默认限制为2MB,当确实有业务需要传较大内容时,可以通过调大服务器端的限制
在翻页场景中,用户输入参数的小于1,则前端返回第一页参数给后端;后端发现用户输入的参数大于总页数,直接返回最后一页
好的单元测试能够最大限度地规避线上故障
单元测试不仅是个质量工具,更是一个效率工具
宏观上的设计和细节上的设计
重构时刻在进行,小跑前进,良好的单测是重构的保障
将繁杂的工作交给工具吧
ali-check
SonarLint
对于一个项目来说,保持一致性,不管是哪种风格,一致性,一致性,一致性,自己保持一致性相对容易,项目中就要和项目保持一致,项目有一致的风格,那就适当牺牲自己的偏好。
> 可在下面留言(需要有 GitHub 账号)