mouseGo

之前的所谓代码规范大部分是根据语言书的一些默认规则,我感觉基本就是习惯问题,不要太离谱即可。现在也做了一些项目,对规范的东西感触更深,好看好读的代码才是好代码。虽然阿里的开发手册不能说适用于业界所有Java项目,但是实际上来说,阿里现在的Java技术积累已经是很强了,这些开发者愿意来做这件事也是于业界有益的,很感谢他们。

在这里只记录个人之前没有意识到的点。毕竟里面有些规定也成了自己的习惯。

编程规约

命名风格

强制

  • 使用语意准确的英文变量名。包括潜在共识的名词(例如Alibaba)
  • 避免使用歧视性词汇(blackList->blockList/whiteList->allowList/slave->secondary),估计是与时俱进了
  • 类名一律首字母大写驼峰式
  • 方法名、参数名、成员变量、局部变量一律首字母小写驼峰式
  • 常量名全大写,_分隔。要注意语意,不怕长
  • 抽象类命名以Abstract/Base开头;异常类以Exception结尾;测试类以测试的类名称开始,Test结尾
  • 类型和[]紧挨来表示数组(int[] i)
  • POJO类中的任何布尔变量不使用isXXX的形式,会引起一些框架的序列化错误。MySql的是否变量采用is_xxx命名方式,在中要设置xxxis_xxx的映射关系
  • 包名统一小写,点分隔符之间只写一个自然语意的单词。包名统一用单数形式。类名如果有复数含义可以用复数
  • 子父类中的局部变量,非getter/setter方法的参数命名尽量不与成员变量的命名相同,子父类的成员变量最好不出现同名
  • 对于Service和DAO类,基于SOA理念,暴露出的一定是接口,其内部实现类用Impl的后缀与接口区别(CacheServiceImpl和CacheService)

推荐

  • 命名时使用尽量完整的单词组
  • 常量与变量的命名中,类型放到最后(nameList/TERMINATE_THREAD_COUNT)
  • 将设计模式体现在类、模块、方法、接口的命名中
  • 接口类中所有方法不加修饰符(保持简洁),加上JavaDoc注释。尽量不在接口中定义变量,如果非要这样,要确定这个变量与接口方法有关,并且是整个应用的基础常量,JDK8中允许接口有默认实现的方法,此方法必须是对所有实现类有价值的默认实现
  • 功能性接口的命名最好使用对应的形容词作为接口名
  • 枚举类名要加上Enum后缀,枚举类的成员名全部大写,单词之间加上_(枚举就是特殊的常量类,构造方法默认是私有的

参考

  • Web开发中各层命名规约
    * Service和DAO层方法命名规约
    * 获取单个对象的方法用get做前缀
    * 获取多个对象的方法用list做前缀
    * 获取统计值的方法用count做前缀
    * 插入方法用save/insert做前缀(个人感觉insert更好)
    * 删除方法用remove/delete做前缀(个人感觉delete更好)
    * 修改方法用update做前缀
    * 领域模型命名
    * 数据对象,xxxDO,xxx为表名
    * 数据传输对象,xxxDTO,xxx为业务领域相关名称
    * 展示对象,xxxVO,xxx一般为网页名称
    * POJO是DO/DTO/BO/VO的统称,切忌使用xxxPOJO来命名

常量定义

强制

  • 不允许魔法值直接出现在代码里(所有未经定义的字面量)
  • long的数字定义要加L而非l

推荐

  • 按常量的功能划分为多个常量类来保存常量
  • 常量的复用层次(跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量)
    * 跨应用:二方库,通常在client.jar中的constant目录,专门做一个jar包来存储(注:一方库是本工程内模块和包依赖的;二方库是同一公司内部开发的库;三方是公共库)
    * 应用内:一方库,子模块下的constant目录,模块级别
    * 子工程内:当前子工程的constant目录
    * 包内:包下独立的constant目录
    * 类内:private static final
  • 如果变量值在一个范围内使用enum类型来定义(如四季)

代码格式

强制

  • 采用4空格缩进,不适用tab
  • 注释符号和内容间有且只有1个空格
  • 强制类型转换的右括号和被转换变量之间没有空格
  • 单行字符不超过120,超出需换行,换行原则
    * 第二行较第一行换行4个空格,后续不再继续缩进
    * 运算符与下文一起换行
    * 方法调用符号与下文一起换行
    * 方法调用的参数个数多,要在逗号后换行而不是连逗号一起换行
  • IDE的text file encoding设置为UTF-8,IDE中文件的换行符使用Unix而不是Windows

推荐

  • 单个方法行数不超过80行(注释除外),核心在于分清方法的红花和绿叶逻辑,注意方法的拆分,便于复用和维护
  • 不同逻辑、不同语义、不同业务的代码之间插入一个空行来分割提高可读性

OOP规约

强制

  • 避免通过对象来访问类的静态方法(无谓增加编译器解析成本),直接用类名访问
  • 所有覆写方法要加@Override注解(在抽象类中改了方法签名,覆写方法会马上报错)
  • 相同参数类型、相同业务含义才可以使用Java的可变参数,避免用Object
  • 外部或者二方库正在调用的接口不允许修改方法签名,过时的接口必须要加@Deprecated注解,并清楚说明新的接口或服务
  • 不能使用过时方法(接口提供方标明了过时接口就有义务提供新接口的说明,接口调用方有义务查明新方法的实现)
  • Object的equals方法容易抛空指针异常,宜用常量或有确定值的对象来调用equals方法(推荐JDK7提供的java.util.Object#equals(Object o1, Object o2)
  • 所有整型包装类对象之间值的比较,全用equals方法(Integer var = value;在-128-127的数值之间的赋值,Integer对象是在IntegerCache.cache中产生的,会复用已有对象,此时直接用==比较没有问题。但是这个区间之外的所有数据,都会在堆上产生,并不会复用对象)
  • 任何货币金额,都用最小货币单位并以整数存储(不用BigDecimal是因为精度问题--出问题几率大,数额巨大可以使用BigInteger
  • 浮点数之间的等值判断,基本类型不能用==,包装类型不能用equals(尾数+阶码的表示方式无法精确表示大部分小数),解决方法:
    1. 定义误差值,在此误差内即算相等
    2. 使用BigDecimal
  • BigDecimal的对象比较要用compareTo()方法而不是equals
  • DO类中,成员属性和数据库字段的类型要对应
  • 禁止使用BigDecimal类的BigDecimal(double)构造器来构建对象,这样有损失精度的风险,建议使用BigDecimal(String)来构造对象,或者用valueOf(double)方法,但是这个方法也是调用了DoubletoString()方法再转换
  • 基本数据类型和包装类型的使用说明
    * POJO类中的属性必须用包装类型
    * RPC方法返回值和参数类型必须用包装类型(包装类型的null值可以携带更多信息,例如调用失败,异常退出等)
  • 定义DO/DTO/VO等POJO类时不要设定任何属性默认值(例如创建日期字段默认为new Date(),更新别的字段会附带更新了这个字段,那么创建日期变成了当前日期)
  • 序列化类新增属性时,不要修改serialVersionUID字段,避免反序列化失败。如果是完全不兼容升级,那么修改serialVersionUIDserialVersionUID不一致会抛出运行时异常)
  • 构造方法中不写任何业务逻辑,所有的初始化逻辑要放到init()方法中
  • POJO类中必须写toString()方法,如果有继承的父类,要调用super.toString()(便于查错)
  • 进制再POJO类中写同一个属性xxx的两个方法isXxx()getXxx()(框架在调用属性xxx的提取方法时,并不能确定哪一个是被优先调用的)--不完全理解

推荐

  • 所有局部变量使用基本数据类型
  • 使用Stringsplit(String)时,要做最后一个分割符后是否有内容的检查,否则可能后续会出现数组越界的情况
  • 构造方法、多个同名方法最好放在一起显示
  • 类内方法顺序:公有方法或保护方法->私有方法->getter/setter
  • setter方法中,参数名和属性名相同。不在getter/setter中加入业务逻辑
  • 循环体内的字符串拼接使用StringBuilderappend()方法
  • 使用final的情况
    * 不允许被继承的类
    * 不允许被修改的域对象(POJO类的域变量)
    * 不允许被覆写的方法(POJO中的setter)
    * 不允许运行过程中重新复制的局部变量
    * 避免上下文使用同一个变量,final可以强制你创建新变量(这是不是有点过了)
  • 慎用Object的clone方法来拷贝对象(浅拷贝,实现神拷贝需要覆写clone方法,对域对象的深度遍历式拷贝)
  • 类成员和方法访问控制从严
    1. 如果不允许外部直接使用new来创建对象,构造器要使用private
    2. 工具类不允许有publicdefault构造方法
    3. 类非静态成员变量并且与子类共享,必须是protected
    4. 类非静态成员变量仅在本类使用,必须是private
    5. 类静态成员变量仅在本类使用,必须是private
    6. 若是静态变量,考虑其是否为final
    7. 类成员方法只供内部调用,必须是private
    8. 类成员方法只对继承类公开,必须是protected

日期时间

强制

  • 日期格式化的pattern中的年要用小写的y(大写Y在JDK7之后引用,代表的是当日所在当周所在的年份,一周为周六到周日,所以只要本周跨年那么返回的就是下一年)
  • 分清H、h、M、m的含义,H是24小时制的时间,h是12小时制的时间,M是月份,m是分钟
  • 获取当前毫秒数使用System.getCurrentTimeMillis()而不是new Date().getTime()(纳秒可以用System.nanoTime的方式,JDK8中统计时间相关推荐用Instant类)
  • 不允许在程序任何地方使用java.sql.Date java.sql.Time java.sql.TimeStamp(第一个不记录时间、第二个不记录日期,第三个构造方法super((time/1000)*1000),在Timestamp属性fastTimenanos分别存储秒和纳秒信息
  • 不在程序中将一年写死为365天(避免在公历闰年中出现日期转换错误或程序逻辑错误)

推荐

  • 避免公历闰年2月问题,闰年2月是29天,那么它的下一年2月一定不是29天
  • 使用枚举值来表示月份,如果使用数字,在Date、Calendar等类中月份是0-11

集合处理

强制

  • hashCodeequals的处理,遵循以下原则
    1. 只要覆写equals,就要覆写hashCode
    2. 因为Set存储的是不相同的对象,根据hashCodeequals方法来判断,所以Set存储的对象必须覆写这两个方法
    3. 如果自定义对象作为Map的键,那么必须覆写hashCodeequals
  • 判断集合为空使用isEmpty()而不是x.size() == 0(某些集合中前者时间复杂度为1,且可读性更好)
  • 在使用java.util.stream.Collectors类中的toMap()方法转为Map集合时,一定要使用含有参数类型为BinaryOperator,参数名为mergeFunction的方法,否则当出现相同key值时会抛出IllegalStateException异常(正常应该是保留最后一个键值对)
  • 在使用java.util.stream.Collectors类的toMap()方法转为Map集合时,一定要注意当valuenull时会抛NPE异常。(HashMapmerge方法中有:if (value == null || remappingFunction == null) throw new NullPointerException()

分类:

技术点:

相关文章: