在日常工作的过程中看到了形形色色的编码习惯,有大牛的规范,也有小白的不规范。所谓不规范对开发者自己来说其实影响不大,因为自己毕竟有自己的习惯,但是对于维护者来说简直就是崩溃。一个严格的代码规范对于项目的维护我觉得至少可以省20%的时间(个人看法),之前阿里出版了Java开发手册,看了之后感觉干货挺多,现在在此基础上总结一些更加细微的规范供大家谈论。
业务代码书写细节
- 集合类型的选择。经常我们在DAO层操作的时候需要传入一个列表或者在普通的业务操作中需要传入或者操作列表,但是目前我看到的同事的代码中普遍使用ArrayList,根本不分青红皂白习惯性的就使用ArrayList,而且很多时候对于列表的使用只涉及插入和遍历的操作,这时候我们应该看一下需要的列表使用场景,很多时候LinkedList明显是更好的选择。
- 集合大小的指定。我们在很多时候需要进行集合的转换,比我我将List转换为Map并且指定List中某个元素为Key,这时候明显的Map的size我们是可以预估的,我们在这里应该手动指定map的初始参数(默认的factor一般不需要改变),这样可以防止rehash也可以防止数组拷贝;在使用ArrayList的时候也存在类似的情况,如果可以预先知道将来的大小的话最好在初始化时候指定大小 。
- 数组与集合的转换操作。有的同学总是喜欢做重复工作,比如遍历数组然后存到List中取,或者把列表中的元素遍历到数组中去。这种高频的操作我们就不要重复造轮子了,Arrays.asList()和toArray()方法可以完美解决。不要重复造轮子!不要重复造轮子!不要重复造轮子!重要的事情讲三遍
- Service/Facade层的对象转换。我经常在业务处理层见到DTO->VO或者VO->DTO的操作,有的使用的次数多了会抽象出来一个工具类专门进行转换,有的可能只使用一次就索性这么写了(见下),这么写在写法上没有问题,但是会在业务逻辑中出现大片的get,set操作,影响整体阅读。我们在编写业务代码的时候力求的是通过方法调用就能知道这个逻辑具体干了什么,这样对于维护者来说他能更快的了解逻辑而不是花时间在无聊的get,set上。业务层要做的就是通过局部封装展示可读逻辑,即使是只使用了一次的代码,对理解业务没有帮助的话也要进行封装。比如下面的可以用方法:convertDemoVO2DemoDTO(demoVO);
DemoDTO demoDTO = new DemoDTO();
demoDTO.setA(demoVO.getA());
demoDTO.setB(demoVO.getB());
........重复性set操作
- 包装类型和基本类型的选择。在我们的服务边界(服务暴露,服务引用)应该尽量使用包装类型的参数,这样来防止将null错误解析成0这种异常,这种BUG我相信很多人都遇到过,明明对面没有传,但是我却收到了是0。。。但是包装类型不应该被滥用,我们在自己的服务内部是提倡使用基本类型的,无论是成员变量还是局部变量,毕竟基本类型是底层直接支持的,不需要在生成一个类文件,处理速度也肯定会包装类型更快。所以就是严控基本类型和包装类型的使用边界!!!
注释规范
- 每一个自己的方法上都必须包含:@author,@since/@date 。也就是说一定要加上代码书写人和书写时间,至于版本号一些其他的描述信息虽然在很多时候也是必要的,但是这两个是100%需要的,我们需要知道将来代码出问题了可以立马找到谁。但是这种情况下可以省略这两个注释,当一个个文件全部是由同一个开发者书写的,我么只需要在类的注释上写上自己的邮箱和时间即可。其他情况这两个核心信息都是必要的,特别是当我们修改了其他人的代码时候。
- 注释的解释。我们在编写注释的时候我认为不是所有的方法都需要极为详细的内容注释,比如:getUserById();我相信通过方法名称100%都能了解到方法是做什么的,所以这时候可以不进行方法解释的注释书写。还有部分@param的注释也是根据情况编写,比如getUserById(String userId);这个userId的注释我们只需要写@param userId即可,不需要进行过多的描述。但是,在使用@throws进行异常注释的时候,一定要进行异常抛出的情况描述,否则仅仅写一个@throws NullPointerException 很难知道在什么情况下会抛出NPT,所以需要在后面加上必要的描述信息。
- 对于严格注释规范的书写。我认为我们在日常工作中没有绝对的时间去编写十分详细的注释,比如什么时间使用<p>,<tt,<i>这种,我们能保证的就是对于后期维护者的友好。只在你有更加充足的时间基础上才去完善注释,否则还是花时间把业务代码写好再说吧。
- 最低标准。我上面讲的都是注释书写的最低标准,有的公司可能对注释有更严格的规范,但是上面的标准基本可以保证代码的可读性了。
单元测试
- 方法名称书写。单元测试要求测试方法名称为:需要测试的方法名称+Test。一定要把业务名称放在前面,因为我们习惯从左往右读取,这样做也是让我们快速辨别测试的方法。如果test+需要测试的方法名称的话,我们总是要读到第5个字母才知道测试的真是方法,累!
- 关于Assert还是System.out.println()。我个人是推荐Assert的。因为System.out.println()的话有时候会因为日志信息过多而覆盖掉我们想看的信息,成千上万的日志会让你疯掉的。
- 开发阶段DEBUG模式统一天下。开发阶段经常需要调试,我们应该尽量使用DEBUG模式去调试,不管是Tomcat还是JUnit,我们都应该第一之间选用DEBUG模式。我个人经常在JUnit里面公用DEBUG模式和Assert,出了问题随时打断点的感觉倍儿爽。
- 单元测试的维护。我们在系统迭代的过程中经常会改变某个方法的实现,然后忘记对应的JUnit,可能造成了原来的测试方法已经出现问题了,所以在进行方法维护的时候一定要看一下对应的JUnit。(公司已经为此出现好几次问题了)
- JUnit的覆盖率。这个问题我大概没有资格来讲,因为我平时的覆盖率估计也是在60%左右(啪啪啪打脸有木有),严格意义上是要有100%的覆盖率的,每一个自己的方法都要有相应的单元测试。以后努力改进!!
包命名
- 包的重复命名应该坚决杜绝。现在的情况是经常有一个Maven父工程下见到几个Maven子工程下的包名完全一致,一不小心就出现同名类了。这种情况在依赖中更长出现,我们曾经因为两个包下同一个类名字一样踩过很大的坑。现在这种问题依然存在,经常发现IDE提示找不到这个类,但是明明这个类就在目录下。这都是同名包让IDE抽风的案例,假设两个同样的包下出现了同样的类,我们又同时引用了,那么恭喜你,你要踩坑!!!
- 包命名建议。不同的子工程也要用不同标签进行区别,比如api包就用api作为包名的一部分,provider就用provider作为包名的一部分,依次类推。总结的话就是需要使用工程标签+模块标签进行区分。
方法位置
- 合适的方法放在合适的位置。我个人的感觉是总是倾向于在原有的类中新增,而对于新增类总是在上一个倾向之后。但是,我们对于方法的位置应该十分考究才是,类功能的单一性应该作为首要参考。明明是Category的操作为什么非要放到GoodsService中去呢?“因为单开一个类只有这一个方法感觉不太妥当”。这是我有时候的心声,其实是一种惰性的体现。因为单开类的成本比直接在原有的位置添加成本更高。但是我们应该克服自己的惰性,天上飘下五个字”功能单一性“。
- 位置审查。我们因该在完善基本的前提下进行几次类位置的审查,看看是否将方法放到了不合适的位置。可以在团队定期开展代码审核,大家一起评判xxx的代码,我相信这对于大家的能力提升是非常有帮助的。
工具方法
- 类命名。经常在工程中看到Util和Utils并存,这让我很是不舒服,工程中对于工具类都必须有统一的规范,如果Util 的话就不要Utils,反之亦然。 因为大多数开源框架中都是用Utils结尾,所以我也推荐工具类以Utils结尾。其实工具类还可以用Helper或者Converter等等结尾,但是他们的意思都一样,都可以翻译为XXX工具类,所以既然这样我们应该统一使用一种命名规范,也就是我推荐的Utils结尾。
- 方法书写。工具类是没有状态的!工具方法是没有状态的!工具方法是没有状态的!不能在工具类中出现跟实例挂钩的成员变量,更不能依赖其他服务,工具类作为一个可以被使用的单独的类,宗旨就是短小精悍,复用性高。如果我们的工具类也依赖了A服务和B服务,那他与平时的业务代码有什么区别?既然工具类没有状态,那就更不能进行new XXXUtils()了,如果是这样的话他就是个普通的Bean,不能称做工具类。还有就是,他不能被@Component注解修饰然后注入到其他类中,道理同上。(我在工作中见到有new工具类的,也有作为属性注入的,都是非常不合理的操作)。
- 工具类不宜过于庞大。不能在一个工具类中塞很多东西,否则就违背了它的易用性说明。
- 工具类是没有状态的!工具类是没有状态的!工具类是没有状态的!
注解统一
- @Autowired和@Resource选择其一。工程中混用二者的地方很多,虽然对功能上没有影响,但是因为其是两个派系,所以在使用的时候还是选择其中一个即可,不要一个A一会B!
- @Component组件命名。像@Service,@Controller这些注解都是可以使用ByName的,而且我也比较推荐ByName,因为一个接口有可能有多个实现,所以根据名字区分的粒度更加细微。但是对于value的命名一定要统一,个人认为如果是Service类的话就统一成XXXService,而不是XXXServiceImpl,因为前面的内容已经足够说明它是一个XXXService的Bean了,而且我们是面向接口编程的,多个了Impl四个字母无非就是多了工足量而已,个人认为没有必要。类似的像XXXDao,XXXFacade等等,尾部统统不需要加Impl。但是如果真的要加了也可以,那就是工程内所有的value都加impl。不要搞的有些加有些不加。
- 代码格式统一!代码格式统一!代码格式统一!
以上都是我在工作过程中遇到的问题以及一些思考,如果大家有不同的看法可以留言讨论。