Java编程规范
Java开发规范
前言 为什么需要编码规范1
为什么需要编码规范,借用《Java编程语言代码规范》一段开场白:
一个软件需要花费80%的生命周期成本去维护。
几乎没有任何软件的整个生命周期仅由其原作者来维护。
编码规范改善软件的可读性,让工程师更快更彻底地理解新的代码。
如果你将源代码转变为一个产品,那么您需要确保它和你创建的其它产品一样是干净且包装良好的。
好的代码结构和代码风格一般bug也相对少,当然除了编码规范,也少不了充分自测。[写代码一部分是完成工作用来给机器执行, 另一部分是给人使用的(我们都是创造者), 无论未来的维护这是不是你, 一段程序的健壮、扩展、灵活是很重要的一件事, 虽然它可有可无,也不会记录KPI,但是何必有杂念呢 — Sp!ke]
1 如何命名
代码就是最好的注释[我们是如何读文章的, 你又是怎么样读懂我写的这句话的 —Sp!ke]
1.1 命名
- 如果模块、接口、类、方法使用了设计模式,在命名时体现出具体模式。
- 变量名应该简短且有意义,并能够顾名思义。简单并不意味着越短越好,比如一个字符的变量名是不允许的,很影响代码的可读性。
[哪里是用缩写,哪里使用全拼,命名其他人能见词知义么,非英语母语的地区会有下一个问题select get query find | selectOne getData queryInfo | findAll queryList getDatas,是不是每个人都需一本字典才能开发,如果有是不是特定的字典 —Sp!ke]
1.1.1 命名风格
代码中的命名均不能以$\color{CornflowerBlue}{下划线或美元符号}$ 开始,也不能以$\color{CornflowerBlue}{下划线或美元}$符号结束
$\color{OrangeRed}{反例:} $_name / __name / $name / name_ / name$ / name__代码中的命名均不能包含$\color{CornflowerBlue}{魔法数字或无意义的数字}$, 但是允许有意义的
$\color{MediumSeaGreen}{正例:}$timeout3s / list2Map / string2List
$\color{OrangeRed}{反例:}$table1 / i1 / a123aaa / str1类名使用UpperCamelCase风格,但以下情形例外:DO / BO / DTO / VO / AO / PO / UID等
$\color{MediumSeaGreen}{正例:} $MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
$\color{OrangeRed}{反例:}$macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase风格,必须遵从驼峰形式
$\color{MediumSeaGreen}{正例:}$localValue / getHttpMessage() / inputUserId
[其实我并不是很热衷写这种用例, 虽然它很直观,但是阿里巴巴已经写好了,感兴趣可以直接下载附件,更多的是我们每书写一段代码是否有认真思考过,有些人没有命名意识会做错,有些人不知道但是拷贝的做会正确,有些人通过经验习惯做会正确,但是有没有停下来归纳一下正确的写法有哪些通用的,这会比写出一百两百个用例更有效 —Sp!ke]
1.1.2 常量、变量命名
[常量与变量命名并没有太多的点只是罗列几点 —Sp!ke]
变量:
- 驼峰命名规则
- 第一个首字母小写后续单词首字母大写
- 减少不通用的缩写
- 命名需要满足阅读顺序
actorListName->actorNameList,sourcePrefixActorList->actorSourcePerfixList - 不要加入无意义的魔法数字或者符号
str1->origin,table13->mainTable - 歧义的变量 例如:
loggerLOGGER根据实际情况处理
常量
- 全大写
- 单词之间使用下划线做分割
- 其他规则参考变量
1.1.3 类命名
[类的命名是很重要的,但是不单单是命名更多的是名字就是他的职责与功能,类有两个点需要时刻注意 1.类型 2.命名 —Sp!ke]
1.1.3.1 类型
[无论下面哪种类型在编译之后都会以.class形式存在, 那么为什么又要拆分成不同类型呢,不同类型负责的职责也不同,当然你也可以混用但是不建议,失去了真正职责的类相当于,使用饭碗装排泄物 —Sp!ke]
Class类 : 更泛泛的通用的容器,可以理解为 normalEnum枚举类 : 枚举的特性在于内部的强关联,以及元素之间的规律性RED, BLUE, BLACK可以做一组枚举, 但是RED, NAME, JAVA一组枚举就会莫名其妙Interface接口类 : 接口在Java中作用其一是弥补Java单继承的不足,作用其二是更好实现OOP的多态[我们的代码中有将常量放在interface里定义的情况,并不是说这种不可以,首先不建议因为失去了Interface的真正意义,其次如何给Interface命名呢UserConstant么,还是需要思考一下吧 —Sp!ke]Annotation注解类 : 注解类不做太多阐述,他的使用会使程序更加灵活,但也不可控[事物都存在双面性 —Sp!ke]
1.1.3.2 命名
[我们所期待的良好的类命名是为了看到名字就知道作用 —Sp!ke]
包名 (packages) 建议后面追加s, 例如Utils Constants Enums
基础层 [每一层完成他自己的事情,不要越级处理 —Sp!ke]
Controller$\color{CornflowerBlue}{后缀}$ Web控制层 负责请求分配, 具体交给Service还是重定向有具体业务判断, 建议只包含RequestResponse(可自定义) 数据的封装与下发 [上层、下层、底层、业务 Whatever —Sp!ke]Service$\color{CornflowerBlue}{后缀}$ 业务层 具体业务处理 由入口输入(ControllerExecuter等进入 ) 负责加工、执行、处理调用数据层(DAOManagerRedisTemplateService等)DAODao$\color{CornflowerBlue}{后缀}$ 数据持久层 与持久化层对接 只处理数据的CURD操作、以及数据源使用[我理解一个完整的DAO包含了MySQLRedisMongo等等的集成 而不是单一的处理 —Sp!ke]
常见类命名
Application$\color{CornflowerBlue}{后缀}$ 启动类 项目唯一主入口 [我建议业务类型服务只用Application命名即可,不需要加入具体业务前缀, 可以规范运维启动脚本 —Sp!ke]ConfigConfiguration$\color{CornflowerBlue}{后缀}$ 配置类Constant$\color{CornflowerBlue}{后缀}$ 常量类 建议同一个常量类中常量具有相关性,便于管理以及类的命名 [ 这个是否是常量int timeout = 3;—Sp!ke ]Enum$\color{CornflowerBlue}{后缀}$ 枚举类 内部强关联 便于数据整理相当于多维ConstantInterface$\color{CornflowerBlue}{后缀}$ 接口 [接口是用来实现的, 失去了行为的接口是没有意义的]BaseCommonSuperAbstract$\color{CornflowerBlue}{前后缀均可} $Abstract$\color{CornflowerBlue}{前缀}$父类或基础类Exception$\color{CornflowerBlue}{后缀}$ 异常类 异常类多指自定义异常Impl$\color{CornflowerBlue}{后缀}$ 实现类 与Interface相关,子母关系缺一不可 使用extends不要加Impl后缀Utils$\color{CornflowerBlue}{后缀}$ 工具类 工具类pulibc修饰应加入static内部private为特有DataEntityBeanDomainModelPOJO$\color{CornflowerBlue}{后缀}$ 实体
[有一种说法 Data Model 修饰不需要加, 这是一种累赘的命名 类似actorNameString —Sp!ke]
实体、数据对象[4] (#refer-anchor-4) [5] (#refer-anchor-5)
DataInfoDetail[不好说没有这个规则, 只能说能看懂 —Sp!ke]
Entity就是实体的意思,所以也是最常用到的,Entity包中的类是必须和数据库相对应的Model最早与WEB相关, 是为页面提供数据和数据校验的Domain代表一个对象模块, 这个包国外很多项目经常用到,字面意思是域的意思, 比如一个商城的项目,商城主要的模块就是用户,订单,商品三大模块,那么这三块数据就可以叫做三个域POJOPlain OrdinaryJava Object的缩写不错,但是它通指没有使用EntityBeans的普通java对象,可以把POJO作为支持业务逻辑的协助类VOView Object视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来DTOData Transfer Object数据传输对象,这个概念来源于J2EE的设计模式,原来的目的是为了EJB的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这里,我泛指用于展示层与服务层之间的数据传输对象DODomain Object领域对象,就是从现实世界中抽象出来的有形或无形的业务实体POPersistent Object持久化对象,它跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应PO的一个(或若干个)属性。
对于常量Class Interface进行了一些文献查询,还是众说纷纭,只能说建议
常量接口模式
The constant interface pattern is a poor use of interfaces . That a class uses some constants internally is an implementation detail.
Implementing a constant interface causes this implementation detail to leak into the class’s exported API.
It is of no consequence to the users of a class that the class implements a constant interface. In fact, it may even confuse them.
Worse, it represents a commitment: if in a future release the class is modified so that it no longer needs to use the constants, it still must implement the interface to ensure binary compatibility.
If a nonfinal class implements a constant interface, all of its subclasses will have their namespaces polluted by the constants in the interface.
There are several constant interfaces in the java platform libraries, such asjava.io.ObjectStreamConstants.
These interfaces should be regarded as anomalies and should not be emulated.
原文出自see Effective Java
一下连接仅供参考
- https://www.cnblogs.com/wanqieddy/p/9051568.html
- https://blog.csdn.net/voo00oov/article/details/50433672
- https://stackoverflow.com/questions/2659593/what-is-the-use-of-interface-constants
- https://docs.oracle.com/javase/tutorial/java/IandI/interfaceDef.html
也有一种优雅的写法
1 | public final class Constants { |
管理类命名
Pool$\color{CornflowerBlue}{后缀}$ 池ManagerMgr$\color{CornflowerBlue}{后缀}$ 管理器Group$\color{CornflowerBlue}{后缀}$ 群Proxy$\color{CornflowerBlue}{后缀}$ 代理类Balance$\color{CornflowerBlue}{后缀}$ 均衡器Container$\color{CornflowerBlue}{后缀}$ 容器
创建类命名
Generator$\color{CornflowerBlue}{后缀}$ 生成器Builder$\color{CornflowerBlue}{后缀}$ 构建器Factory$\color{CornflowerBlue}{后缀}$ 工厂
协议通讯相关功能
MsgAckReqResp$\color{CornflowerBlue}{前后缀均可,推荐后缀}$ 消息类HeaderBody$\color{CornflowerBlue}{后缀}$ 头部 主体ProtoProtobuf$\color{CornflowerBlue}{后缀}$ 协议类Sender$\color{CornflowerBlue}{后缀}$ 发送者Receiver$\color{CornflowerBlue}{后缀}$ 接收者
其他功能
Listener$\color{CornflowerBlue}{后缀}$ 监听Filter$\color{CornflowerBlue}{后缀}$ 过滤器 Java中起源于ServletInterceptor$\color{CornflowerBlue}{后缀}$ 拦截器 Java中起源于Spring
[从某种意义上来说FilterInterceptor并没有冲突 他们是两个容器的筛选 存在顺序关系 可以查看容器的结构 —Sp!ke]Executer$\color{CornflowerBlue}{后缀}$ 处理器 [Executer与Service在于Service可以拥有独立的规范函数Executer则具有集体特性 —Sp!ke]Templater$\color{CornflowerBlue}{后缀}$ 模板Converter$\color{CornflowerBlue}{后缀}$ 转换器Connector$\color{CornflowerBlue}{后缀}$ 连接器Recorder$\color{CornflowerBlue}{后缀}$ 记录器Monitor$\color{CornflowerBlue}{后缀}$ 监控器Server$\color{CornflowerBlue}{后缀}$ 服务器 注意Server代表一个大型的容器概念 ,Service代表某个具体的业务Client$\color{CornflowerBlue}{后缀}$ 终端类
er or 尾缀代表执行者关系类似 TaskPool Tasker或者ExecutorGroup Executor
[更多的命名可以参考设计模式名称, 核心是见词知意 —Sp!ke]
(Singleton Prototype Adapter Bridge Decorator Interpreter Command Mediator State Visitor Strategy Memento ...)[7] (#refer-anchor-7)
思考:
DAODao,DTODto模棱两可的命名如何处理?
1.2 注释
注释有利于帮助理解代码,如果使用不当,反而会影响代码的简洁性,不利于理解代码。注释在使用上笔者认为需要坚持三个原则:
- 保持代码干净,消除不必要的注释:好的代码本身就是最好的注释,只在必要时通过注释协助理解代码,目的是保持代码的简洁性,增强代码的可读性;
- 区分注释和JavaDoc:类、域、方法使用JavaDoc,方法内部使用注释;
- 注释及时更新:注释也是代码的一部分,如果代码发生变更,注释也要跟着改;
[我的理解注释的意义是补全,补全那些我们不能写成有效代码的片段、文字,也有人会指这我问为什么这个类没有一句注释, String userLastName = "zhang"; 需要注释么 —Sp!ke]
例:2 [3] (#refer-anchor-3)
1 | String unitAbbrev = "μs"; | 赞,即使没有注释也非常清晰 |
2 日志
不管是使用何种编程语言,日志输出几乎无处不在。总结起来,日志大致有以下几种用途:
* 问题跟踪:通过日志不仅仅包括我们程序的一些bug,也可以在安装配置时,通过日志可以发现问题。
* 状态监控:通过实时分析日志,可以监控系统的运行状态,做到早发现问题,早处理问题。
* 安全审计:审计主要体现在安全方面上,通过日志进行分析,可以发现是否存在非授权的操作。
2.1 级别
fatal- 严重的,造成服务中断的错误;error- 其他错误运行期错误;warn- 警告信息,如程序调用了一个即将作废的接口,接口的不当使用,运行状态不是期望的但仍可继续处理等;info- 有意义的事件信息,如程序启动,关闭事件,收到请求事件等;debug- 调试信息,可记录详细的业务处理到哪一步了,以及当前的变量状态;trace- 更详细的跟踪信息;
2.2 基本的Logger编码规范
- 在一个对象中通常只使用一个
Logger对象,Logger应该是static final的,只有在少数需要在构造函数中传递logger的情况下才使用private final。 - 输出
Exceptions的全部Throwable信息,因为logger.error(msg)和logger.error(msg,e.getMessage())这样的日志输出方法会丢失掉最重要的StackTrace信息。
1 | void foo() { |
- 不允许记录日志后又抛出异常,因为这样会多次记录日志,只允许记录一次日志。
1 | void foo() throws Exception { |
- 不允许出现System print(包括System.out.println和System.error.println)语句。
1 | void foo() { |
- 不允许出现printStackTrace。
1 | void foo() { |
- 日志性能的考虑,如果代码为核心代码,执行频率非常高,则输出日志建议增加判断,尤其是低级别的输出<
debug、info、warn>。
debug日志太多后可能会影响性能,有一种改进方法是:
1 | if (logger.isDebugEnabled()) { |
但更好的方法是Slf4j提供的最佳实践:
1 | logger.debug("returning content: " + content); |
一方面可以减少参数构造的开销,另一方面也不用多写两行代码。
- 有意义的日志
通常情况下在程序日志里记录一些比较有意义的状态数据:程序启动,退出的时间点;程序运行消耗时间;耗时程序的执行进度;重要变量的状态变化。
初次之外,在公共的日志里规避打印程序的调试或者提示信息。
2.3 日志的输出
2.3.1 什么时候输出
日志并不是越多越详细就越好。在分析运行日志,查找问题时,我们经常遇到该出现的日志没有,无用的日志一大堆,或者有效的日志被大量无意义的日志信息淹没,查找起来非常困难。那么什么时候输出日志呢?以下列出了一些常见的需要输出日志的情况,而且日志的级别基本都是
INFO,至于DEBUG级别日志的使用场景,需要具体情况具体分析,但也是要追求“恰如其分”,不是越多越好。
- 系统启动参数、环境变量 : 系统启动的参数、配置、环境变量、
System.Properties等信息对于软件的正常运行至关重要,这些信息的输出有助于安装配置人员通过日志快速定位问题,所以程序有必要在启动过程中把使用到的关键参数、变量在日志中输出出来。在输出时需要注意,不是一股脑的全部输出,而是将软件运行涉及到的配置信息输出出来。比如,如果软件对jvm的内存参数比较敏感,对最低配置有要求,那么就需要在日志中将-Xms -Xmx -XX:PermSize这几个参数的值输出出来。 - 异常捕获 : 在捕获异常处输出日志,大家在基本都能做到,唯一需要注意的是怎么输出一个简单明了的日志信息。这在后面的问题问题中有进一步说明。
- 函数获得期望之外的结果时 : 一个函数,尤其是供外部系统或远程调用的函数,通常都会有一个期望的结果,但如果内部系统或输出参数发生错误时,函数将无法返回期望的正确结果,此时就需要记录日志,日志的基本通常是
WARN。需要特别说明的是,这里的期望之外的结果不是说没有返回就不需要记录日志了,也不是说返回false就需要记录日志。比如函数:isXXXXX(),无论返回true、false记录日志都不是必须的,但是如果系统内部无法判断应该返回true还是false时,就需要记录日志,并且日志的级别应该至少是WARN。 - 关键操作 : 关键操作的日志一般是INFO级别,如果数量、频度很高,可以考虑使用DEBUG级别。以下是一些关键操作的举例,实际的关键操作肯定不止这么多。
2.3.2 输出什么
- 关键路径 : 日志并不是独立存在的,而是以一片文章阅读方式存在的
- 关键字段 :
UIDCIDAIDRequestID一个链路上可以通过唯一一个值,即可将整个I/O操作串联起来的Keyword, 扩展是跨项目、跨工程之间的思考 - 关键描述 : 时间、地点、人物、事件大家都玩过这个游戏吧
3 代码
代码! 代码! 代码!
Error=more code^2
4 如何优化
5 如何思考
6 如何避免陷入(牛角尖、瓶颈、自大、自卑、思维僵化)
参考
- [1] 浅谈Java编码规范
- [7] 设计模式|菜鸟教程
- [8] [阿里巴巴开发手册]
- [9] java Log日志规范
- [10] log日志输出规范
- 标题: Java编程规范
- 作者: Spike Zhang
- 创建于 : 2021-04-23 14:15:55
- 更新于 : 2024-07-13 09:46:17
- 链接: https://chaosbynn.github.io/2021/04/23/Java编程规范/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。