代码的命名不能以下划线或美元符号,也不能以下划线或美元符号结束
代码的命名禁止使用中文拼音和英文混用的形式,更不允许直接使用中文的方式,纯中文拼音的方式尽量避免采用,使用英文命名。注:国际通用拼音可以使用,如renminbi,alibaba
类名应当使用UpperCamelCase的形式,也就是首字母大写驼峰命名的形式。
方法名、参数名、成员变量、局部变量同意使用lowerCamelCase风格,必须遵循驼峰的形式
常量命名全部大写,单词之间用下划线隔开,力求语意表达请求,不要嫌名字长。正例:MAX_STOCK_COUNT 反例:MAX_COUNT
抽象类命名使用Abstract或Base开头;异常类命名使用Exception结尾;测试类命名以他要测试的类的名称开始,以Test结尾
类型与中括号紧挨相连表示数组;如 int[] array, 反例: String args[]
POJO类中的布尔类型变量都不要加is前缀,否则部分框架解析会引起序列化错误。在Mysql规约中的建表约定第一条,表达与是否的值采用is_xxx的命名方式,所以需要在设置is_xxx到xxx的映射关系
包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式。如工具类包名为com.alibaba.ai.util、类名为MessageUtils
避免在子父类的成员变量之间、或者不同代码块的局部变量之间采用完全相同的命名,使可读性降低。
public class ConfusingName { public int age;
// 非 setter/getter 的参数名称,不允许与本类成员变量同名
public void getData(String alibaba) { if(condition) {
final int money = 531;
// ...
}
for (int i = 0; i < 10; i++) {
// 在同一方法体中,不允许与其它代码块中的 money 命名相同 final int money = 615;
// ...
} }
}
class Son extends ConfusingName {
// 不允许与父类的成员变量名称相同 public int age;
}
杜绝完全不规范的缩写,避免望文不知意。
为了达到代码自解释的目标,任何自定义编程元素在命名时,尽量使用完整的单词组合来表达其意。
在常量与变量的命名时,表示类型的名称放在词尾,以提升辨识度。
正例:startTime / workQueue / nameList / TERMINATED_THREAD_COUNT
反例:startedAt / QueueOfWork / listName / COUNT_TERMINATED_THREAD
如果模块、接口、类、方法使用了设计模式,在命名时需要体现出具体的模式。比如OrderFactory,LoginProxy等。
接口类中的方法和属性不要加任何修饰符号(public也不要加),保持代码的简洁性,并加上有效的javadoc注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。
接口和实现类的命名有两套规则:
枚举类名带上Enum后缀,枚举成员名称需要全大写,单词见用下划线隔开。
各层命名规约
Service/DAO层方法命名规范
(个人定义:以最基本的mvc为基础,Controller引用Service,Service调用Repository,Repository调用DAO,Service中定义的应该是对某个业务服务实体的各种调用操作,如ActorService,对主播的各种操作,可能涉及多个Repository的操作,毕竟主播不可能只有一个关联表。Repository与DAO相对应,DAO与表sql相对应,Repository的作用就是对允许进出DAO查询的BO实体与DO实体进行转换,不要将数据库的表结构暴露到Service层中,仅转换成需要的数据返回即可。DAO层定义db的查询方式。进入查询的BO实体需要注意表查询的索引,尽量走索引,再转成DO进入查询。)
领域模型命名规约
不允许任何**魔法值(即未经预先定义的常量)**直接出现在代码中。
String key = "Id#taobao_" + tradeId;
cache.put(key, value);
// 缓存 get 时,由于在代码复制时,漏掉下划线,导致缓存击穿而出现问题
在long或者Long赋值时,数值后使用大写的L,不能是小写的l,小写容易跟数字1混淆,造成误解。
不要使用一个常量类群维护所有常量, 要按常量功能进行归类,分开维护。如缓存相关的常量放在CacheConsts中,系统配置相关常量放在ConfigConsts中。
常量的复用层次有5层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量。
private static final
定义如果一个变量值仅在一个固定范围内变化用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;
} }
避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
所有覆盖的方法,必须加@Override注解
相同参数类型,相同业务含义,才可以使用java的可变参数,避免使用Object,在业务上尽量避免使用Object可变参数
外部正在调用或者二方库依赖的接口,不被允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated注解,并清晰地说明采用的新接口或者新服务是什么。
不能使用过时的类或方法(过时的方法如果标注最好经过大家的一致同意,不要个人进行标注)
Object的equals方法容易抛空指针异常,应使用常量或者确定有值的对象来调用equals。
所有整型包装类对象之间的值比较,全部使用equals方法比较。
浮点数之间的等值判断,基本数据类型不能用==来比较,包装类型不能用equals来判断。
定义数据对象DO类时,属性类型要与数据库字段类型相匹配。
为了防止精度损失,禁止使用构造方法BigDecimal(double)的方式把double值转为BigDecimal对象。
关于基本数据类型与包装数据类型的使用标准如下:
定义DO/DTO/VO等POJO类时,不要设定任何初始值。
序列化类新增属性时,不要修改serialVersionUID字段,避免反序列化失败;如果 完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值。
构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。
POJO 类必须写 toString 方法。使用 IDE 中的工具:source> generate toString 时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString。
禁止在POJO类中,同时存在对应属性xxx的isXxx()和getXxx()方法。
使用索引访问用 String 的 split 方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛 IndexOutOfBoundsException 的风险。
当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起, 便于阅读,此条规则优先于下一条
类内方法定义的顺序依次是:公有方法或保护方法 > 私有方法 > getter / setter 方法。
setter 方法中,参数名称与类成员变量名称一致,this.成员名 = 参数名。在
getter/setter 方法中,不要增加业务逻辑,增加排查问题的难度。
循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。
final 可以声明类、成员变量、方法、以及本地变量,下列情况使用 final 关键字:
慎用 Object 的 clone 方法来拷贝对象。
类成员与方法访问控制从严:
关于hashCode和equals的处理,遵循如下规则:
ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCast Exception
是哟哦给Map的方法keySet()/values()/entrySet()返回集合对象时,不可以对其进行添加元素操作,否则会抛出UnsupportedOperationException异常。
Collections类返回的对象,如:emptyList()/singletonList()等都是immutablelist,不可对其进行添加或者删除元素的操作
在subList场景中,高度注意对原集合元素的增加或删除,均会导致子列表的遍 历、增加、删除产生 ConcurrentModificationException 异常。
使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一样,长度为0的空数组
List<String> list = new ArrayList<>(2); list.add("guan");
list.add("bao");
String[] array = list.toArray(new String[0]);
在使用Colletion接口任何实现类的addAll()方法时,都要对输入的集合参数进行NPE判断。不然会抛出空指针异常。
使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方 法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。
泛型通配符 extends T>来接收返回的数据,此写法的泛型集合不能使用add方 法,而 super T>不能使用 get 方法,作为接口调用赋值时易出错。
说明:扩展说一下 PECS(Producer Extends Consumer Super)原则:第一、频繁往外读取内容的,适合 用 extends T>。第二、经常往里插入的,适合用 super T>
在无泛型限制定义的集合赋值给泛型限制的集合时,在使用集合元素时, 需要进行instanceof判断,避免出现classCastException异常。
不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator的方式,如果并发操作,需要对Iterator对象加锁
在 JDK7 版本及以上,Comparator 实现类要满足如下三个条件,不然 Arrays.sort, Collections.sort 会抛 IllegalArgumentException 异常。
集合泛型定义时,在 JDK7 及以上,使用 diamond 语法或全省略。菱形泛型,即diamond,直接使用<>来指代前边已经指定的类型
集合初始化时,指定集合初始值大小
使用entrySet遍历Map类集合KV,而不是keySet方式进行遍历。说明:keySet其实遍历了2次,一次是转为Iterator对象,另一次是从hashMap中取出key所对应的value。而entrySet知识遍历了一次就把key和value都放在了entry中,效率更高。如果是jdk8,使用Map.forEach方法
高度注意Map类集合K/V能不能存储null值的情况。
合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和不稳定性(unorder)带来的负面影响。
利用Set元素唯一的特性,可以快速对一个集合进行去重操作,避免使用List的contains方法进行遍历、对比、去重操作。
获取单例对象需要保证线程安全,其中的方法也要保证线程安全
创建线程或线程池时请指定有意义的线程名称,方便出错时回溯
线程资源必须必须通过线程池提供,不允许在应用中自行显式创建线程
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学能够更加明确线程池的运行规则,规避资源耗尽的风险。
SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类。
注意线程安全,使用 DateUtils。亦推荐如下处理:
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() { @Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
} };
说明:如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe。
必须回收自定义的ThreadLocal变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的ThreadLocal变量,可能会影响后续业务逻辑和造成内存泄漏等问题。尽量在代理中使用try-finally块进行回收。
//一般在多方法间传输局部变量时可以用线程的ThreadLocal进行保存,但是使用完毕后要进行删除
objectThreadLocal.set(userInfo); try {
// ...
} finally {
objectThreadLocal.remove();
}
高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁住区块,就不要锁住整个方法体;能用对象锁,就不要用类锁。
对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。说明:线程一需要对三个资源ABC依次加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是ABC,否则可能会出现死锁。
在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在加锁方法与try代 码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁。 lock在try之中,如果异常,那么finally解锁时会调用AQS的tryRelease出现异常
在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁。锁的释放规则与锁的阻塞等待方式相同。在进行使用前先判断是否已经加锁tryLock(),不然在释放锁的时候会出现异常
并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用version作为更新依据。mysql的乐观锁:通过version记录版本号,在修改记录前先select version,通过update的where条件进行判断version,修改后使version + 1,由于update执行阶段的语句是不可分割的,以此保证了乐观锁。如果是for update 则是悲观锁了。一般的事务都是按公司级定义隔离级别,对应查询时的隔离等级,对修改的情况一般需要自己加锁
(暂不知)多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其他任务便会自动终止运行,如果在处理定时任务时使用ScheduledExecutorService则没有这个问题
资金相关的金融敏感信息,使用悲观锁策略;
乐观锁在获得锁的同时已经完成了更新操作,校验逻辑容易出现漏洞,另外,乐观锁对冲突的解决策略有较复杂的要求,处理不当容易造成系统压力或数据异常,所以资金相关的金融敏感信息不建议使用乐观锁更新
使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用countDown方法,线程执行代码注意catch异常,确保countDown方法被执行到,避免主线程无法执行至await方法,直到超时才返回结果。
避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed导致的性能下降。Random实例包括java.util.Random 的实例或者Math.random()的方式,在 JDK7 之后,可以直接使用 API ThreadLocalRandom,而在 JDK7 之前,需要编码保证每个线 程持有一个实例
在并发场景下,通过双重检查锁(double-checked locking)实现延迟初始化的优化 问题隐患(可参考 The “Double-Checked Locking is Broken” Declaration),推荐解决方案中较为 简单一种(适用于 JDK5 及以上版本),将目标属性声明为 volatile 型。
volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但 是如果多写,同样无法解决线程安全问题。
HashMap在容量不够时进行resize时由于高并发可能出现死链,导致CPU飙升,在开发过程中可以使用其他数据结构或加锁来规避此风险。
ThreadLocal 对象使用 static 修饰,ThreadLocal 无法解决共享对象的更新问题。
在一个switch块内,每个case要么通过continue/break/return等来终止,要么注释说明程序将继续执行到哪个case为止;在一个switch块内,都必须包含一个default语句并且放在最后,即使他什么也没有
当switch括号内的变量类型为String并且此变量为外部参数时,必须先进行null判断。
在if/else/for/while/do语句中必须使用大括号
在并发场景中,避免使用等于判断作为中断或退出的条件如果并发控制没有处理好,容易产生击穿的情况,使用大于或小于的区间判断来代替;你如奖品剩余为0的时候终止,并发错误瞬间变成负数则无法终止
表达异常的分支时,少用if-else,避免超过3层if else if,可以改写成if(condition) return obj;
超过三层的if-else的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现,其中卫语句即代码逻辑先考虑失败、异常、中断、退出等直接返回的情况,以方法多个出口的方式,解决代码中判断分支嵌套的问题。
public void findBoyfriend(Man man) {
if (man.isUgly()) { System.out.println("本姑娘是外貌协会的资深会员");
return; }
if (man.isPoor()) { System.out.println("贫贱夫妻百事哀");
return; }
if (man.isBadTemper()) { System.out.println("银河有多远,你就给我滚多远");
return; }
System.out.println("可以先交往一段时间看看"); }
除常用方法(如getXxx/isXxx)等外,不要在条件判断中执行其他复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。
不要在**其他表达式(尤其是条件表达式)**中,插入赋值语句。赋值点类似于人体的穴位,对于代码的理解至关重要,所以赋值语句需要清晰的单独成为一行。
public Lock getLock(boolean fair) {
// 算术表达式中出现赋值操作,容易忽略 count 值已经被改变
threshold = (count = Integer.MAX_VALUE) - 1;
// 条件表达式中出现赋值操作,容易误认为是 sync==fair
return (sync = fair) ? new FairSync() : new NonfairSync(); }
日志中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架SLF4J中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Test.class);
所有日志文件至少保存15天,因为有些异常具备以"周"为频次发生的特点。网络运行状态、安全相关信息、系统检测、管理后台操作、用户敏感操作需要留存相关的网络日志不少于6个月。
应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:appName_logType_logName.log。logType:日志类型,如stats/monitor/access等;logName:日志描述。这种命名的好处:通过文件名就可以知道日志文件属于什么应用,什么类型,什么目的。也有利于归类查找
在日志输出时,字符串变量之间的拼接使用占位符的方式。说明:因为String字符串的拼接会使用StringBuilder的append()方式,有一定的性能损耗。使用占位符仅是替换动作,可以有效提高性能
例子:logger.debug(“process trade id: {}”, id);
对于trace/debug/info级别的日志输出,必须进行日志级别的开关判断。
// 如果判断为真,那么可以输出 trace 和 debug 级别的日志 if (logger.isDebugEnabled()) {
logger.debug("Current ID is: {} and name is: {}", id, getName());
}
避免重复打印日志,浪费磁盘空间,务必在log4j.xml中设置additivity=false。正例:
异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字throws往上抛出。正例:logger.error(各类参数或者对象 toString() + “_” + e.getMessage(), e);
谨慎地记录日志。生产环境禁止输出debug日志;有选择地输出info日志;如果使用warn来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。
可以使用warn日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。如非必要,请不要在此场景打出error级别,避免频繁报警。
尽量使用英文来描述日志错误信息,如果日志信息中的错误信息用英文描述不清楚的话使用中文描述即可,否则容易产生歧义。
国际化团队或海外部署的服务器由于字符集的问题,使用全英文来注释和描述日志错误信息。
定义GAV遵从以下规则:
GroupID格式:com.{公司/BU}.业务线[.子业务线],最多4级。
{公司/BU} 例如:alibaba/taobao/tmall/aliexpress 等 BU 一级;子业务线可选。如com.taobao.jstorm, com.alibaba.dubbo.register
ArtifactID格式:产品线名-模块名。语义不重复不遗漏,先到中央仓库全查证一下。如dubbo-client / fastjson-api
Version:详细规定参考下方
二方库版本号命名方式:主版本号.次版本号.修订号
注意起始版本号必须为:1.0.0,而不是0.0.1,正式发布的类库必须先去中央仓库进行查证,使版本号有延续性,正式版本号不允许覆盖升级。如当前版本为1.3.3,那么下一个合理的版本号为1.3.4或1.4.0或2.0.0
线上应用不要依赖SNAPSHOT版本(安全包除外)。不依赖SNAPSHOT版本是保证应用发布的幂等性。另外,也可以加快编译时的打包构建
二方库的新增或升级,保持除功能点之外的其他jar包仲裁结果不变。如果有改变,必须明确评估和验证。说明:在升级时,进行 dependency:resolve 前后信息比对,如果仲裁结果完全不一致,那么通过dependency:tree 命令,找出差异点,进行排除 jar 包
二方库里可以定义枚举类型,参数可以使用枚举类型,但是接口返回值不允许使用枚举类型或包含枚举类型的POJO对象。
依赖一个二方库群时,必须定义一个统一的版本变量,避免版本号不一致。依赖springframework-core, -context, -beans,他们都是同一个版本,可以定义一个变量来保存版本:${spring.version},定义依赖的时候,引用该版本。
禁止在子项目的pom依赖中出现相同的GroupId,相同的ArtifactId,但是不同的Version。在本地调试时会使用各子项目指定的版本号,但是合并成一个war,只能有一个版本号出现在最后的lib目录中。可能出现线下调试是正确的,发布到线上却出现故障的问题。
底层基础技术框架、核心数据管理平台、或近硬件端系统谨慎引入第三方实现。
所有pom文件中的依赖声明放在语句块中,所有版本仲裁放在语句块中。里只是声明版本,并不实现引入,因此子项目需要显式的声明依赖,version和scope都读取自父pom。而所有声明在主pom的里的依赖都会自动引入,并默认被所有的子项目继承。
二方库不要有配置项,最低限度不要再增加配置项。
为避免应用二方库的依赖冲突问题,二方库发布者应当遵循以下原则: