编码规范

145 阅读14分钟

1.命名规范

  1. 【强制】取名要有具体的含义,好的命名是最好的注释

  2. 【强制】命名严禁使用拼音与英文混合的方式,更不允许使用中文的方式 反例: getPingfenByName()

  3. 【强制】类名使用 UpperCamelCase 风格,必须遵从驼峰形式,但以下情形例外:DO / BO / DTO / VO / AO 正例: UserDO / TcpUdpDeal 反例: UserDo / TCPUDPDeal

  4. 【强制】方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase 风格,必须遵从驼峰形式。 正例: localValue / getHttpMessage()

  5. 【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。 正例:MAX_STOCK_COUNT 反例:MAX_COUNT

  6. 【建议】抽象类命名使用 Abstract 或 Base 开头;异常类命名使用Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。

  7. 【强制】POJO 类中布尔类型的变量,都不要加 is,否则部分框架解析会引起序列化错误。 反例:定义为基本数据类型Boolean isDeleted;的属性,它的方法也是isDeleted(),RPC框架在反向解析的时候,“以为”对应的属性名称是 deleted,导致属性获取不到,进而抛出异常。

  8. 【强制】包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。 正例: 应用工具类包名为com.alibaba.open.util、类名为MessageUtils(此规则参考 spring 的框架结构)

  9. 【强制】杜绝完全不规范的缩写,避免望文不知义。 反例:AbstractClass“缩写”命名成 AbsClass;condition“缩写”命名成 condi,此类随意缩写严重降低了代码的可阅读性。

  10. 【推荐】如果模块、接口、类、方法使用了设计模式,在命名时体现出具体模式。 说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。 正例:public class OrderFactory; public class LoginProxy;

  11. 【推荐】接口类中的方法和属性不要加任何修饰符号(public也不要加),保持代码的简洁性,并加上有效的 Javadoc 注释。尽量不要在接口里定义变量 正例:接口方法签名:void f(); 接口基础常量表示:String COMPANY = "alibaba"; 反例:接口方法定义:public abstract void f();

  12. 【推荐】业务类:Service类以Service结尾;实现类用 ServiceImpl 的后缀;Dao类以Dao为结尾;controller类以Controller结尾(Action类以Action结尾)等

  13. 【推荐】实体类:对应表的为entity:和表名对应;用在服务与web层之间的为dto:以DTO结尾;用在web层与页面之间的为vo:以VO结尾;

  14. 【推荐】 controller和action命名与pages中的命名对应。比如XXXController与之对应的主view就在pages下的XXX.ftl中

  15. 【推荐】如果是形容能力的接口名称,取对应的形容词做接口名(通常是–able 的形式)。 正例:AbstractTranslator 实现 Translatable。

  16. 【推荐】枚举类名建议带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。 正例:枚举名字为ProcessStatusEnum的成员名称:SUCCESS / UNKOWN_REASON。

  17. 【推荐】方法命名:

  18. 获取单个对象的方法用load/get做前缀。

  19. 获取多个对象的方法用find/list做前缀。

  20. 获取统计值的方法用count做前缀。

  21. 插入的方法用add/insert做前缀。

  22. 删除的方法用remove/delete做前缀。

  23. 修改的方法用update做前缀。

  24. 【强制】不允许任何魔法值(即未经定义的常量)直接出现在代码中。

  25. 【强制】long 或者 Long 初始赋值时,使用大写的 L,不能是小写的 l,小写容易跟数字 1 混 淆,造成误解。说明:Long a = 2l; 写的是数字的21,还是Long型的2?

2.代码注释

  1. 类和类成员属性、方法用文档注释: /** 注释 */;
  2. 方法中的代码段注释:/*注释*/;
  3. 方法中的代码行注释://注释;
  4. pigeon接口注释中加 ”服务的Url“,方法参数中的枚举加@link注清枚举详细包路径;
  5. 接口、类、方法、类字段必须有注释,代码块视复杂程度添加适量的逻辑解释注释;
  6. 不要将无用的代码注释掉,应该直接删除(git有历史记录,不用担心要用时找不回来)。
  7. 字段需要导出doc文档使用/** 注释 */
  8. 【强制】外部正在调用的接口,不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么。

3.方法规范

  1. 方法多考虑通用性,扩展性,提高重复利用率,不要添加个性化的定制业务;
  2. 超过3个参数可以考虑使用对象实体进行封装,方便扩展
  3. 单个方法代码长度控制在80行以内,超过要考虑拆出一些private子方法,这样确保主方法结构清晰易维护。
  4. 暴露出去的dubbo服务方法参数,如果有枚举类型,请用int,不直接用枚举类型(例如:feedType、newFeedStatus参数)。 服. 端返回的FriendDTO的对象,也不要带有枚举字段,如果在返回结果的枚举类型中添加了新的枚举值,会导致仍然在使用老的客户端的那些应用出现调用失败的情况!
  5. 对象实体记得实现可序列化并生成对应序列化ID,数据传输和添加缓存都依赖
  6. 跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess()方法、“错误码”、“错误简短信息”。
  7. 【参考】避免出现重复的代码(Don’t Repeat Yourself),即DRY原则。 说. : 随意复制和粘贴代码,会导致代码的重复,以后需要修改时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是组件化。
  8. 如果只有setter和getter方法,建议用lombok插件的@Data、@Setter、@Getter注解;
  9. 数据库查询映射使用entity(表名),字段要用包装类,不要用基本数据类型。

4.集合规范

  1. 【强制】在 subList场景中,高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、 删除均会产生ConcurrentModificationException 异常。
  2. 【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。
  3. 【推荐】集合初始化时,指定集合初始值大小。 说明:HashMap使用HashMap(int initialCapacity) 初始化, 正例:initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即 loader factor)默认为 0.75,如果暂时无法确定初始值大小,请设置为 16(即默认值)。 反例:HashMap 需要放置 1024 个元素,由于没有设置容量初始大小,随着元素不断增加,容量7次被迫扩大,resize需要重建 hash表,严重影响性能。
  4. 【推荐】使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。 说明:keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出 key 所对应的 value。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效 率更高。如果是 JDK8,使用 Map.foreach 方法。 正例:values()返回的是 V 值集合,是一个 list 集合对象;keySet()返回的是 K 值集合,是 一个 Set 集合对象;entrySet()返回的是 K-V 值组合集合。
  5. 【推荐】 Map 类集合 K/V 尽量不要存储 null 值的情况
  6. 【参考】 HashMap 在容量不够进行 resize 时由于高并发可能出现死链,导致 CPU 飙升,在 开发过程中可以使用其它数据结构或加锁来规避此风险。
  7. 【强制】 在 JDK7 版本及以上,Comparator 要满足如下三个条件,不然 Arrays.sort, Collections.sort 会报 IllegalArgumentException 异常。

说明:三个条件如下

  1. x,y的比较结果和y,x的比较结果相反
  2. x>y,y>z,则x>z。
  3. x=y,则x,z比较结果和y,z比较结果相同

5.并发规范

  1. 【强制】获取单例对象需要保证线程安全,其中的方法也要保证线程安全。

  2. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。

  3. 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

  4. 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors 返回的线程池对象的弊端如下:

  5. FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

  1. CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
  1. 【强制】SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为 static,必须加锁
  2. 【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能 锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。 说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法。
  3. 【推荐】使用 CountDownLatch 进行异步转同步操作,每个线程退出前必须调用 countDown 方法,线程执行代码注意 catch 异常,确保 countDown 方法被执行到,避免主线程无法执行 至 await 方法,需要设置最大等待时间。

6.异常处理

  1. 调用pigeon都应该try catch,确保服务异常不会导致系统瘫痪;非关键服务单独try catch,确保不影响主流程;
  2. 高QPS的服务(>100),要考虑采用降级+try catch。
  3. 不允许直接catch all语句,例如 catch Throwable,对于异常中的Error没法处理的。原则上最好也不要Catch Exception, 因为Exception里面有包含RuntimeException。
  4. 【强制】异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低。
  5. 【强制】对大段代码进行 try-catch,这是不负责任的表现。catch 时请分清稳定代码和非稳 定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分 异常类型,再做对应的异常处理。
  6. 【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请 将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的 内容。

7.方法性能

  1. 超过三个没有依赖关系的方法,采用异步调用;
  2. fast return 代替 if else;
  3. 有通用模块或者工具类,用已有的,不要重复劳动;
  4. 批量或者分页查询要做上限控制;
  5. 万不得已不要用pagemodel,而是用list;
  6. 超长的富文本拼接、过于频繁的字符串拼接(>10次以上)用StringBuffer;
  7. 一级入口、高qps(>100)服务加缓存,加缓存前评估数据量+命中率(大数据量、低命中率慎用缓存);
  8. 代码里读文件的时候不要把整个文件读到一个String里面再进行各种处理,很容易造成oom,应该bufferedReader.readLine() ,一行一行的读取。
  9. 并发调用dubbo服务不要另开线程池,强制使用Dubbo并发组件

8.空处理规范

  1. 调用对象方法属性之前,做空校验(除非有try catch 或者 确定非空);
  2. 暴露出去的服务方法参数要做空校验;
  3. 空校验尽量使用第三方权威的类库apache、guava等
  4. 服务方法返回值:集合默认返回empty,实体默认返回null(无值情况下);
  5. DAO不要用基本数据类型,应用包装类型。

9.格式化规范

  1. 【强制】包引入、类名、类属性、类方法 之间应该空一行;
  2. 【强制】=、>=、&等运算符号两边有一个空格,方法参数之间应该空一格;
  3. 【强制】if后面必须有{},即使只有一行代码;
  4. 【强制】源文件编码格式为UTF-8
  5. 【强制】源文件内空白字符只允许“空格”和“换行符”,缩进使用4个空格
  6. 源文件以其顶层类名(相对于内部类)来命名,大小写敏感,首字母大写
  7. 【强制】去除没有用到的import
  8. 尽可能不用static import
  9. 【强制】package和import语句不换行
  10. 【强制】import语句里面不允许出现*
  11. 【强制】switch代码要显式写出default语句
  12. 【强制】删除无用代码、IDE自动生成的无用注释、消除warning
  13. 一行只有一条语句或者一个注解,最好不超过140列(保证一屏显示得下)
  14. 大括号与if, else, for, do, while语句一起使用,即使只有一条语句(或是空),也应该把大括号写上。
  15. 【强制】禁止使用魔法数(直接使用数字),使用常量代替
  16. 各种IDE中的换行强制使用LF
  17. 【强制】新写代码不允许使用被丢弃的类

10.注解规范

  1. 【强制】如果使用注解,建议用@Resource,禁止用@Autowired
  2. 被spring托管的bean中注入方式使用@Resource或Setter,非托管类才可使用SpringLocator

11.maven规范

  1. maven项目中的pom.xml:如果打包插件是maven-assembly-plugin,必须写明version,不能偷懒不写(否则存在子pom中的jar无法覆盖父pom中jar的bug)。
  2. 线上禁止使用snapshot版本,上线之前一定要升正式版本
  3. api对应的pom能不依赖尽量不依赖其他包,即使要依赖也应尽量依赖基础通用的包,禁止依赖公司业务包

12.日志规范

  1. 【强制】新项目的log必须基于日志中心提供的日志组件,
  2. 【强制】避免打印敏感信息,如身份证号、银行卡号等,防止日志文件外泄导致用户敏感信息泄露。
  3. 如果用本地log不要输出太多日志信息,避免拖慢应用系统。
  4. 【强制】Debug/info级别的信息,信息本身需要计算或合并的,必须加 isXxxEnabled() 判断在前,或者使用参数化日志特性。因为在关闭日志的情况下,日志语句依然会执行,从而影响性能。
  5. 【强制】不得使用System.out, System.err进行日志记录,请改使用log.debug、log.error

13.Job规范

  1. job需要比web,service还要做更详细的注释,如作业功能,调度周期,数据范围等
  2. job如果涉及数据库的操作,需要额外注重数据库的压力和性能,写数据需要控制频率,通常写一部分数据,sleep一段时间;读数据需要做SQL分析,避免慢SQL。
  3. 作业设计的时候要做好幂等处理,支持重复运行
  4. 要避免同时把大批数据一次性加载到内存处理
  5. 需要记录详尽的log,对代码做try catch,避免某步处理出错整体执行失败

14、Kafka使用规范

  1. 【强制】消息体中需要包含二个字段timestamp:消息产生时间,eventId或messageId:消息的唯一Id。

15、其它规范

  1. 【参考】慎用Enum.valueOf(),Integer.valueOf()等类型转换方法,需要做异常捕获。