说明:[1] 强制 [2] 建议 [3] 参考。
[1] 类以 CamelCase 格式的单数形式的名词或者名词短语命名,例如 Person; 方法以 camelCase 格式的动词或者动词短语命名 ,例如 payBill。
代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
反例:name / name / $name / name / name$ / name
代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
类名以下情形例外:DO / BO / DTO / VO / AO / PO 等。
方法名要求的动词短语的形式是:doSomething,例如支付账单,那就是 payBill,不要使用 billPay。
[1] 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
正例:MAX_STOCK_COUNT 反例:MAX_COUNT
但是如果实在 Stock 类中(相当于有限界上下文),那么上述正反例就可以颠倒过来,正例是:MAX_COUNT,反例是 MAX_STOCK_COUNT
[1] 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类名开始,以 Test 结尾。
在以 JAVA8 为最低依赖 JDK 的时候,尽量减少抽象类的使用,而建议使用 interface 的 default 方法。
[1] POJO 类中布尔类型的变量,都不要加is/has前缀,否则部分框架解析会引起序列化错误。
反例:定义为基本数据类型 Boolean isDeleted;的属性,它的方法也是 isDeleted(),RPC 框架在反向解析的时候,误以为 对应的属性名称是 deleted,导致属性获取不到,进而抛出异常。
[1] 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式。
正例:应用工具类包名为 cn.xyz.abc.elf、类名为 MessageElf(此规则参考 spring 的框架结构)。其中 xyz 是公司缩写,abc 是项目缩写。
elf 小精灵,避免与常用的 Util 重复,比如 StringElf 等。
[2] 杜绝完全不规范的缩写,避免望文不知义。
反例:AbstractClass 缩写 命名成 AbsClass;condition 缩写 命名成 condi,此类随意缩写严重降低了代码的可阅读性。
[2] 为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达其意。
正例:从远程仓库拉取代码的类命名为 PullCodeFromRemoteRepository。
反例:变量 int a; 的随意命名方式。
一般规则:变量的使用范围越小,它的名字可以越短,而适用范围越大时,它的名字就应该越完整。
[2] 如果模块、接口、类、方法使用了设计模式,在命名时体现出具体模式。
将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。 例如 OrderFactory, LoginProxy, ResourceObserver.
[3] 接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性。
[3] 各层命名规约:
Service/DAO 层方
获取单个对象的方法用get作前缀。 获取多个对象的方法用list作前缀。 获取统计值的方法用 count 作前缀。 插入的方法用add作前缀。 删除的方法用del作前缀。 修改的方法用 modify 作前缀。
[1] 使用统一的格式化工具,在保存时自动格式化代码。
比如 idea 和 eclipse,都可直接安装 google-java-format 插件进行格式化。
[1] 所有源文件以及配置文件等,都使用 UTF-8 格式的编码形式。
此条主要针对 Windows 上的比如 eclipse 等 IDE,一定要事先设置。
[2] 不允许任何魔法常值(即未经预先定义的常量)直接出现在代码中。
[1] long/Long 初始赋值时,使用大写的L,小写的 l 容易跟数字 1 混淆,造成误解。
long a = 2l; // 写的是数字的 21,还是 Long 型的 2?
[1] 使用 IDE / 编辑器 ,设置2个空格的缩进,并且显式化空白字符。
[1] IDE的 text file encoding 设置为 UTF-8; IDE 中文件的换行符使用 Unix 格式,不要使用 Windows 格式。
[1] 空格:
if/for/while/switch/do 等保留字与括号之间都必须加空格. 任何二目、三目运算符的左右两边都需要加一个空格。 运算符包括赋值运算符=、逻辑运算符 &&、加减乘除符号等。 注释的双斜线与注释内容之间有且仅有一个空格。 左小括号和字符之间不出现空格;同样,右小括号和字符之间也不出现空格。 方法参数在定义和传入时,多个参数逗号后边必须加空格。
[1] 换行:
左大括号前不换行, 后换行。 右大括号前换行, 后还有 else 等代码则不换行, 表示终止的右大括号后必须换行。 没有必要插入多个换行进行隔开。 单行字符数限制不超过120个,超出需要换行,换行时遵循如下原则:
第二行相对第一行缩进2个空格,从第三行开始,不再继续缩进,参考示例。
运算符与下文一起换行。
方法调用的点符号与下文一起换行。
方法调用时,多个参数,需要换行时,在逗号后进行。
在括号前不要换行,见反例。
[1] 直接通过类名来访问此类的静态变量或静态方法,禁止使用类的实例(对象)来访问静态变量或者方法。
[1] 所有的覆写方法,必须加 @OverRide 注解。
[1] 相同参数类型,相同业务含义,才可以使用 Java 的可变参数,避免使用 Object。
[1] 所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。
说明:对于 Integer var = ? 在 -128 至 127 范围内的赋值,Integer 对象是在 IntegerCache.cache 产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。
[1] 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。
[1] 避免使用 String 的 split 方法。用 guava 的 Splitter 替代。
[2] 使用 lombok 来简化代码,查看 介绍( baeldung 商业网/intro-to-project-lombok)。
[2] 明确方法的功能,遵守 SRP 单一职责,一个方法只完成一个功能。
[2] 方法体不超过 100 行。
[2] 方法体缩进不超过5层。
[2] 方法参数不超过5个,并且遵从函数名称含义所隐含的自然顺序。
超过5个时,可以将相关参数,封装到一个对象之中;
自然顺序正例1:void writeStringToFile(String str, File file);
自然顺序正例2:void writeFileWithString(File file, String str);
[2] 方法参数不使用布尔类型。
// 你读到下面的代码,相信你会和我一样,要么石化了,要么凌乱了。
event.initKeyEvent("keypress", true, true, null, null, false, false, false, false, 9, 0);
// 你100%确认不会带来阅读上的问题,比如Java的 setVisible (bool).
// 你100%确认你想去写出无法维护很难阅读的代码。
[1] 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。
// 正例:
Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) {
String item = iterator.next();
if (删除元素的条件) {
iterator.remove();
}
}
// 反例:
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
[1] 在 JDK7 版本及以上,Comparator 要满足如下三个条件,不然 Arrays.sort,Collections.sort 会报 IllegalArgumentException 异常。
说明:三个条件如下:
a) x,y 的比较结果和 y,x 的比较结果相反。
b) x>y,y>z,则 x>z。
c) x=y,则 x,z 比较结果和 y,z 比较结果相同。
// 反例:下例中没有处理相等的情况,实际使用中可能会出现异常:
new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getId() > o2.getId() ? 1 : -1;
}
};
[2] 使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。
[2] 利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的 contains 方法进行遍历、对比、去重操作。
[1] 在一个 switch 块内,每个 case 要么通过 break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使空代码。
[1] 在 if/else/for/while/do 语句中必须使用大括号。即使只有一行代码,避免采用单行的编码方式。
[1] 在高并发场景中,避免使用”等于”判断作为中断或退出的条件。
如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件来代替。
反例:判断剩余奖品数量等于0时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变成了负数,这样的话,活动无法终止。
[2] 表达异常的分支时,少用 if-else 方式,这种方式可以改写成:
if (condition) {
...
return obj;
}
// 接着写else的业务逻辑代码;
[1] 所有的类都必须添加创建者和创建日期。
[1] 所有的枚举类型字段必须要有注释,说明每个数据项的用途。
[1] 方法内部单行注释,在被注释语句上方另起一行,使用 // 注释。方法内部多行注释使用 /* */ 注释,注意与代码对齐。
[3] 在要求高性能的情况下,谨慎使用异常做流程控制,条件控制。
异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多(差一个数量级)。
[1] 捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
如果捕获的异常,需要忽略,则把异常的变量名改为 ignore。
如果没有其他处理,常见则使用日志框架记录异常日志,带上业务上下文(输入参数等)、日志堆栈等。
[1] 应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 SLF4J 中的 API。建议使用 lombok 的 @slf4j 注解。
import lombok.*;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class MiniRestServer {
@SneakyThrows
public void service() {
// ...
log.info("Received request {} {} from host {}.", method, resource, host);
// ...
}
}
[1] 日志输出,必须使用使用占位符的方式。
[1] 日志输出时,尽量输出关键点的上下文信息,包括输入参数,中间变量等。
[1] 避免连续多行打印日志,而是合并到一行中。
[1] 避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。
[1] 可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。
[1] 核心业务、核心算法、核心应用、核心模块的代码必须有单元测试。
[2] 编写单元测试代码遵守 BCDE 原则,以保证被测试模块的交付质量。
[2] 单元测试作为一种质量保障手段,不建议项目发布后补充单元测试用例,建议在项目提测前完成单元测试。
[1] 隶属于用户个人的页面或者功能必须进行权限控制校验。
防止没有做水平权限校验就可随意访问、修改、删除别人的数据,比如查看他人的私信内容、修改他人的订单。
使用id查询订单,不只是使用订单id,还一起带上用户的 id(登录信息中获得的)查询订单。
[1] 用户请求传入的任何参数必须做有效性验证。
说明:忽略参数校验可能导致:
page size 过大导致内存溢出 恶意order by导致数据库慢查询 任意重定向 SQL注入 反序列化注入 正则输入源串拒绝服务 ReDoS,可以看 例子( github /pete911/redos)
Java 代码用正则来验证客户端的输入,有些正则写法验证普通用户输入没有问题,但是如果攻击人员使用的是特殊构造的字符串来验证,有可能导致死循环的结果。
[1] 在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放限制,如数量限制、疲劳度控制、验证码校验,避免被滥刷导致资损。
如注册时发送验证码到手机,如果没有限制次数和频率,那么可以利用此功能骚扰到其它用户,并造成短信平台资源浪费。
[2] 发贴、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词过滤等风控策略。
[1] SimpleDateFormat 是线程不安全的类,不要定义为 static 变量,建议使用 JodaDate 作为替代。
// 正例:注意线程安全,使用DateUtils。亦推荐如下处理:
public static final ThreadLocal<DateFormat> DATE_FORMAT = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
// java 8 写法
public static final ThreadLocal<DateFormat> DATE_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
[2] 尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法。
[1] 避免使用双重检查的懒惰型单例( cs.umd edu/~pugh/java/memoryModel/DoubleCheckedLocking.html),翻译( jianshu 商业网/p/e3652c4a236d)。
// 推荐懒惰型单例的写法
class Foo {
private static class FooSingletonHolder {
static Foo singleton = new Foo();
}
private Foo() {}
public static Foo getInstance() {
return FooSingletonHolder.singleton;
}
}
// 反例
class Foo {
private static volatile Foo instance = null;
private Foo() {}
public static Foo getInstance() {
if (instance == null) {
synchronized(Foo.class) {
if (instance == null)
instance = new Helper();
}
}
return instance;
}
}
[1] 对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。
线程一需要对表 A、B、C 依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是 A、B、C,否则可能出现死锁。
[1] volatile解决多线程内存不可见问题。
对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。
如果是 count++ 操作,使用如下类实现:
AtomicInteger count = new AtomicInteger();
count.addAndGet(1);
如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。
[2] ThreadLocal 无法解决共享对象的更新问题,ThreadLocal 对象建议使用static修饰。
这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享此静态变量 ,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可以操控这个变量。
[2] 避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一 seed 导致的性能下降。
说明:Random 实例包括 java.util.Random 的实例或者 Math.random() 的方式。
正例:在 JDK7 之后,可以直接使用 API ThreadLocalRandom,而在 JDK7之前,需要编码保证每个线程持有一个实例。
[2] 使用 CountDownLatch 进行异步转同步操作,每个线程退出前必须调用 countDown 方法,线程执行代码注意 catch 异常,确保 countDown 方法被执行到,避免主线程无法执行至 await 方法,直到超时才返回结果。
注意,子线程抛出异常堆栈,不能在主线程try-catch到。
[2] 使用 lombok 的 @Synchronized( projectlombok 组织网/features/Synchronized) 来安全地同步方法。
[2] 给 JVM 设置 -XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 碰到 OOM 场景时输出 dump 信息,可以参考 一只懂JVM参数的狐狸xxfox( xxfox.perfma 商业网/jvm/generate)。
[2] 调大服务器所支持的最大文件句柄数(File Descriptor,简写为 fd)。
主流操作系统的设计是将 TCP/UDP 连接采用与文件一样的方式去管理,即一个连接对应于一个 fd。
主流的linux服务器默认所支持最大fd数量为1024,当并发连接数很大时很 容易因为 fd 不足而出现 open too many files 错误,导致新的连接无法建立。
建议将linux服务器所支持的最大句柄数调高数倍(与服务器的内存数量相关)。
[2] 高并发服务器建议调小 TCP 协议的 time_wait 超时时间。
操作系统默认 240 秒后,才会关闭处于 time_wait 状态的连接,在高并发访问下,服务器端会因为处于 time_wait 的连接数太多,可能无法建立新的连接,所以需要在服务器上调小此等待值。
在linux服务器上请通过变更 /etc/sysctl.conf 文件去修改该缺省值(秒):net.ipv4.tcp_fin_timeout = 30
[2] 在线上生产环境,JVM 的 Xms 和 Xmx 设置一样大小的内存容量,避免在 GC 后调整堆大小带来的压力。
阿里巴巴编码规范( github 商业网/alibaba/p3c/blob/master/p3c-gitbook/SUMMARY.md)
华为 Java 编程军规( mp.weixin.qq /s/E9LMHVUkzTaNRXwP7V0vkQ)
Google Java编程风格指南( mp.weixin.qq /s/N3eo2RxxWpQTAZtM35LIzQ)
说明里有这么一句:
@synchronized( github 商业网/synchronized) is a safer variant of the synchronized method modifier.
对于我来说,有点不理解,为啥说更安全(safer)呢?
原来,是专款专用:
synchronized done right: Don't expose your locks.
如果不专款专用,会怎么样呢?有篇博客( interviewbubble 商业网/lombok-synchronized-annotations/)指出了存在的一些问题:
some evil code may steal your lock (very popular this one, also has an “accidentally” variant) all synchronized methods within the same class use the exact same lock, which reduces throughput you are (unnecessarily) exposing too much information.
然后,用 lombok 的 @synchronized( github /synchronized),就比较方便地解决了上述问题。何乐而不为呢!
以下哪种 Java 类名的命名是合理的?(选2项)
A. PersonInfo B. payBill C. PersonWorking D. $Person
以下哪种常量命名是推荐的?(单选)
A. MAX_STOCK_COUNT B. MAX_COUNT C. MaxStockCount D. MaxCount
以下哪种说法是错误的?(单选)
A. 抽象类命名使用Abstract或Base开头
B. 异常类命名使用Exception结尾
C. 测试类命名以它要测试的类名开始,以Test结尾
D. 具体实现类使用 Impl 或者 Implementation 结尾
以下哪种命名没有体现出了设计模式?(单选)
A. OrderFactory B. LoginFactory C. ResourceObserver D. CustomPattern
以下哪个命名表示获取单个对象的方法?(单选)
A. getOrder B. listOrders C. countOrders D. addOrder E. delOrder F. modifyOrder
具体业务代码中,以下哪些命名是差劲的命名?(选4项)
A. data B. data2 C. info D. deal E. lockChargeCard
分层领域模型规约中,以下哪个是表示与数据库表结构一一对应,通过DAO层向上传输数据源对象?(单选)
A. DO(Data Object) B. DTO(Data Transfer Object) C. BO(Business Object)D. VO(View Object)
关于代码格式的说法,以下哪些是错误的?(选2项)
A. 使用统一的格式化工具,在保存时自动格式化代码。
B. 所有源文件以及配置文件等,都使用操作系统默认的编码形式。
C. 使用IDE/编辑器 ,应当显式化空白字符。
D. 注释的双斜线与注释内容之间不必有空格。
关于面向对象编程,以下哪些说法是正确的?(单选)
A. 可以使用类的实例(对象)来访问静态变量或者方法。
B. 所有的覆写方法,必须加@OverRide( github 商业网/OverRide)注解。
C. 包装类对象之间值的比较,在Integer较小值时[-128,127],可以直接使用==来比较大小。
D. 分割字符串,推荐直接使用JDK的split方法。
关于简化代码,以下哪些说法是错误的?(单选)
A. 可以使用lombok来简化数据实体类的代码
B. 方法体行数不超过100行
C. 一个方法完成一次业务请求处理,即使业务请求相对复杂
D. 方法参数选择布尔类型,可能会带来阅读上的问题
以下哪些说法是错误的?(选3项)
A. 在foreach循环里可以进行元素的remove/add操作
B. 使用keySet方式对Map类集合进行遍历
C. 在集合项目较多时,推荐直接使用List的contains方法进行遍历、对比、去重操作
D. 具有元素唯一特性的集合,可以使用Set,Map。
在JDK7版本及以上,Comparator要满足如下哪三个条件,不然Arrays.sort,Collections.sort会报IllegalArgumentException异常?(选3项)
A. x,y的比较结果和y,x的比较结果相反。
B. x>y,y>z,则x>z。
C. x=y,则x,z比较结果和y,z比较结果相同。
D. x!=y, 则x, z比较结果和y,z比较结果不相同。
关于注释,以下哪些说法是错误的?(单选)
A. 注释也是代码,一旦写注释,就好好写,并且与相对应代码同步维护。
B. 所有的枚举类型字段必须要有注释,说明每个数据项的用途。
C. 注释应当标明代码的一些可能的副作用(比如非线程安全)等。
D. 为了让注释保持在总体代码量20%以上,应当多写注释。
关于日志,以下说法哪些是错误的?(单选)
A. 应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 SLF4J 中的 API。
B. 建议使用 lombok 的 @slf4j( github /slf4j) 注解,进一步简化日志的写法。
C.日志输出中包含参数的话,必须使用使用占位符的方式。
D. 如果输出内容较多或者输出参数较长,可以考虑多次调用日志打印来输出。
E. 日志输出时,尽量输出关键点的上下文信息,包括输入参数,中间变量等。
关于单元测试,以下哪些代码必须有单元测试?(选3项)
A. 核心业务代码
B. 核心算法代码
C. 调用频繁的代码
D. 核心数据模型代码
以下哪些技能更加体现了程序员的专业性,而不只是厉害(牛B)而已?(选3项)
A. 能写复杂的代码
B. 有娴熟的开发技能
C. 能写可读的代码
D. 能写可维护的代码
E. 整洁第一(Clarity is king)
好的方法/函数,有哪些特征?(选4项)
A. 像讲故事一样流畅
B. 短小且只做一件事情
C. 符合从下到上的原则
D. 符合 SLAP(单一抽象层次)原则
E. 理想情况下没有参数,最好不要超过3个
关于控制语句的使用,以下哪些说法是错误的?(单选)
A. 在一个 switch 块内,每个都case要通过 break/return 等来终止。
B. 在一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使空代码。
C. 在 if/else/for/while/do 语句中必须使用大括号。即使只有一行代码。
D. 函数中表达异常的分支时,尽量使用卫语句来提前返回或者抛出异常。
关于代码安全,以下哪些说法是错误的?(单选)
A. 用户请求输入的任何参数,都必须做有效性验证。
B. 终端用户使用ID查询订单详情时,只需要检查ID是否是符合长度字母数字等规则。
C. 隶属于用户个人的页面或者功能必须进行水平权限控制校验。
D. 使用平台资源(譬如短信),必须实现正确的防重放限制,如数量限制、疲劳度控制、验证码校验,避免被滥刷导致资损。
关于并发编程,以下哪些说法是错误的?(选3项)
A. SimpleDateFormat 是线程不安全的类,不要定义为 static 变量,建议使用 JodaDate 作为替代。
B. 尽可能使加锁的代码块工作量尽可能的完整,比如在锁代码块中调用RPC方法完成完整的业务逻辑。
C. 对多个资源、数据库表、对象同时加锁时,按照使用的先后顺序进行加锁,防止死锁。
D. 子线程抛出异常堆栈,可以在主线程 try-catch 到。

