• 码出高效(一) Java 编程风格规约


    一.前言

            本文为《码出高效》系列博文第一篇,主要目的是统一和规范代码编程风格,改善应用程序的可读性,提高开发效率。规约包括命名、定义、函数、异常、排版等不同的场景,结合个人的实习经验和业界开发手册总结归纳,参考文档如下:

    二.编码规范

    1.系统分层架构

    1.1 分层架构设计

    (1)开放API接口层:可直接封装Service方法暴露成RPC接口;或通过Web封装成HTTP接口;进行网关安全控制、流量控制等。

    (2)WEB终端显示层:各个端的模板渲染并执行显示的层。当前主要包含velocity渲染,js渲染,JSP渲染,移动端展示等。

    (3)Controller层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。

    (4)Service层:相对具体的业务逻辑服务层,业务逻辑之间相互独立

    (5)Manager层:通用业务处理层,它有如下特征:

    • 对第三方平台封装的层,预处理返回结果及转化异常信息;

    • 对Service层通用能力的下沉,如:缓存方案、中间件通用处理;

    • 与DAO层交互,对多个DAO的组合复用;

    (6)DAO层:数据访问层,与底层MySQL、Oracle、Hbase等进行数据交互。

    (7)外部接口或第三方平台:包括其它部门RPC开放接口,基础平台,其它公司的HTTP接口。

    1.2 分层领域模型规约

    (1)模型分类

    • PO(Persistant Object):持久化数据对象,用于表示数据库中的一条记录映射成的java对象,与数据库表结构一一对应(与DO类似)。
    • DO(Data Object):在alibaba开发手册中,此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象(同PO)。在DDD领域驱动模型中,DO也可以称为Domain Object即领域对象(同BO)。
    • DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象。泛指用于展示层与服务层之间的数据传输对象。
    • BO(Business Object):业务对象,可以由 Service 层输出的封装业务逻辑的对象。有的团队则当做 Service 层内保存中间信息数据的 “DTO” 或者上下文对象来使用(本文采用这种理解)。
    • Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类来传输。
    • VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。它的作用是把某个指定页面(或组件)的所有数据封装起来,通常控制层将其作为JSON 返回给前端然后前端渲染。
    • POJO(Plain Ordinary Java Object):专指包括setter/getter/toString的简单类JavaBeans,包括DO/DTO/BO/VO等,但禁止直接命名成xxxPOJO。

    (2)示例代码:

    1. //1.Controller层:此层常见的转换为:DTO转VO
    2. public List getUsers(UserQuery userQuery);
    3. //2.Service/Manager层:在Service内部使用UserBO封装中间所需的逻辑对象,此层常见的转换为:PO转DTO,或PO转BO转DTO
    4. List getUsers(UserQuery userQuery);
    5. //3.DAO层
    6. List getUsers(UserQuery userQuery);

    1.3 分层视图

    参考文章:https://blog.51cto.com/knifeedge/5139389 

    (1)查询视图

    (2)返回视图

    1.4 总结

            在实际开发过程中,很多开发人员的查询参数Query喜欢通过 Map 或者 JSONObject 来封装;也有些人可能会认为划分这么多模型没有必要,因为通常各层模型的属性基本相同,而且各种类型的分层模型对象转换非常麻烦。但使用不同的分层领域模型能够让程序更加健壮、更容易拓展,可以降低系统各层的耦合度。

            分层模型的优势只有在系统较大时才体现得更加明显​。设想一下如果我们不想定义 DTO 和 VO,直接将 DO 用到数据访问层、服务层、控制层和外部访问接口上。此时该表删除或则修改一个字段,DO 必须同步修改,这种修改将会影响到各层,这并不符合高内聚低耦合的原则。通过定义不同的 DTO 可以控制对不同系统暴露不同的属性,通过属性映射还可以实现具体的字段名称的隐藏。不同业务使用不同的模型,当一个业务发生变更需要修改字段时,不需要考虑对其它业务的影响,如果使用同一个对象则可能因为 “不敢乱改” 而产生很多不优雅的兼容性行为。

            除此之外,如果我们不愿意定义 Param 对象,而使用 Map 来接收前端的参数,获取时如果采用 JSON 反序列化,则可能出现上一节所讲到的反序列化类型丢失问题;如果我们不使用 Query 对象而是 Map 对象来封装 DAO 的参数,设置和获取的 key 很可能因为粗心导致设置和获取时的 key 不一致而出现 BUG等等。

            但总的来说,上面只是给出一种参考,很多团队对部分分层模型的理解会有差异,实际的使用过程中根据自己团队的规模可以适当变通。比如有很多团队项目并不是特别大,为了降低复杂度,只用到了 DTO 、VO 、DO 三种分层领域模型。

    2.命名规范

    级别

    规则

    备注

    ​强制

    各个实体和类的命名规则

    层级

    实体

    接口层

    PersonController

    PersonVO

    PersonThriftService

    PersonDTO

    业务逻辑层

    接口类:PersonService

    实现类:PersonServiceImpl

    PersonBO

    领域层

    PersonDomainService

    PersonDO

    持久层

    PersonMapper

    PersonPO

    Enum命名规范:
    业务名+Enum:PersonAcitveEnum

    接口参数类命名规范:
    业务名+Command:VoucherQueryCommand

    内部参数类命名规范:
    业务名+Param:VoucherQueryParam


    result类命名规范:
    业务名+Result:VoucherOperateResult

    工具类命名规范:
    针对内容+Utils:VoucherUtils,命名注意尽量不要和已有的utils重复(包括本地和三方库),避免出现误用。

    抽象类命名使用 Abstract开头;

    异常类命名使用 Exception 结尾

    测试类命名以它要测试的类的名称开始,以 Test 结尾。
     
    设计模式中统一名称后缀
    Factory/Listener/Proxy/Observer/Builder/Handler等

    ​强制

    类名使用 UpperCamelCase 风格,以下情形例外:DO / PO / DTO / BO / VO / UID 等

    ​强制

    接口类不要用I开头

    ​强制

    方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格

    ​强制

    常量命名应该全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。

    ​强制

    包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。

    ​强制

    杜绝完全不规范的英文缩写,避免望文不知义。

    ​强制

    下层不依赖上层。

    ​建议

    命名应名副其实。变量、函数或类的名称应该告诉我们,它为什么会存在,它做什么事,应该怎么用。

    两个反例:

    一是数字系列。如a1,a2,a3..an。

    二是废话。假设你有一个Product类,如果还有一个ProductInfo或ProductData类,那他们的名称虽然不同,意思却无区别。

    ​建议

    类名和对象名应该是名词或名词短语。

    ​建议

    方法名应当是动词或动词短语。

    3.常量定义

    级别

    规则

    备注

    ​强制

    不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。

    if(status == 1)

    改成

    static final int VALID = 1;

    if(status == VALID)

    4.函数

    级别

    规则

    备注

    ​强制

    单行字符数限制不超过 120 个,超出需要换行。

    // 补充换行的推荐格式

    ​强制

    单个方法的总行数不超过60行。

    不超过显示屏一屏

    ​强制

    函数的缩进不该多于2层。

    ​强制

    方法内的变量声明应尽可能靠近其使用位置。

    ​建议

    if语句、else语句、while语句等,其中的代码块应该只有一行。该行应该是一个函数调用语句。

    ​建议

    无副作用。函数承诺只做一件事,不应该还做其他隐藏的事。

    ​建议

    分隔指令(写操作)与询问(读操作)。

    函数要么做什么事,要么回答什么事,但二者不可得兼。函数应该修改某对象的状态,或是返回该对象的有关信息。两样都干常会导致混乱。

    ​建议

    类中自上向下展示函数调用依赖顺序。

    被调用的函数应该尽可能放在执行调用的函数下面。

    我们期望在阅读函数时,最重要的概念和流程先展示出来,用包含最少细节的方式表述它们。底层细节最后出来。

    5.参数

    级别

    规则

    备注

    ​强制

    不要使用输出参数。

    如果函数要对输入参数进行转换操作,转换结果就该体现为返回值。

    ​建议

    提供最小的参数集合。

    反例:以10个字段的domain对象作为查询参数,但在函数内部只使用到了其中的3个字段。

    ​建议

    不在函数内部修改参数对象。

    6.异常

    级别

    规则

    备注

    ​强制

    给出异常发生的环境说明

    抛出的每个异常,都应当提供足够的环境说明。应创建信息充分的错误消息,并和异常一起传递出去。

    ​强制

    不使用大段的try catch。如果需要,应该在外层方法使用。

    ​强制

    不要吞掉异常

    应用内部使用异常而非返回码

    使用错误码的问题在于,调用者必须在调用之后立即检查错误。这个步骤很容易被遗忘。

    从指令式函数返回错误码轻微违反了指令与询问分隔的规则。

    当返回错误码时,就是在要求调用者立刻处理错误。另一方面,如果使用异常替代返回错误码,错误处理就能从主路径代码中分离出来,得到简化。

    ​建议

    thrift接口发生错误时,使用异常还是Result对象

    ​建议

    多使用运行时异常,少使用受检异常。

    受检异常的特点是调用方必须捕获并处理。适用场景:1.正常适用API也不能避免异常。2.一旦发生异常,程序可以立即采取有用的动作进行恢复。

    受检异常违反开放闭合原则。如果在方法中抛出受检异常,而catch语句在三个层级之上,你就得在catch语句和抛出异常之间的每个方法签名中声明该异常。这意味着较低层级的修改,都将波及较高层级的签名。

    7.类

    级别

    规则

    备注

    ​强制

    单一权责原则

    类或模块应有且只有一条加以修改的理由。

    系统应该由许多短小的类而不是少量巨大的类组成。每个小类封装一个权责,只有一个修改的原因,并与少数其他类一起协同达成期望的系统行为。

    反例:CommonService

    ​建议

    类应该从一组变量开始。公共函数应跟在变量列表之后。私有函数跟在公共函数后面。

    8.OOP规约

    级别

    规则

    备注

    ​强制

    构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。

    ​建议

    定义 DO / PO / DTO / VO 等 POJO 类时,不要设定任何属性默认值。

    9.控制语句

    级别

    规则

    备注

    ​建议

    不要在条件判断中包含复杂逻辑。将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。

    必须

    对于if „ else if „(后续可能有多个else if …)这种类型的条件判断,最后必须包含一个else分支,避免出现分支遗漏造成错误;每个switch-case语句都必须保证有default,避免出现分支遗漏,造成错误

    建议

    如果在分支中返回或中断,请尽量少用else分支,直接写成:if(true) {//do...; return;} 后边再写else逻辑

    必须

    条件判断要先易后难,先简后繁,率先作无效推定。禁止深层的if嵌套导致的箭头式代码

    10.注释规约

    级别

    规则

    备注

    ​强制

    所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数异常说明
    外,还必须指出该方法做什么事情,实现什么功能。

    ​强制

    所有的枚举类型字段必须要有注释,说明每个数据项的用途。

    ​建议

    与其用半吊子英文来注释,不如用中文注释说清楚。专有名词与关键字保持英文原文即可。

    11.线程安全

    序号

    内容

    要求

    1

    使用Lock类API加锁成功后,需在finally中显式释放Lock

    强制

    2

    线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,从而能更好的控制core、max 线程数及workQueue的大小, workQueue默认情况下是整形最大值,应合理设置大小,避免出现内存撑满

    强制

    3

    建议wait、await设置timeout时间,避免没有被notify一直阻塞

    建议

    4

    使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用countDown方法,线程执行代码注意catch异常,确保countDown方法被执行到,避免主线程无法执行至await方法,直到超时才返回结果

    强制

    5

    SimpleDateFormat 是线程不安全的类,一般不要定义为static变量。建议使用DateTimeFormatter

    建议

    6

    使用synchronized, lock等重量级锁是考虑是否用轻量级锁如AtomicXXX能实现。

    一般写多,冲突多:用重量级锁

    读多写少:用轻量级锁,或者读写锁

    建议

    12.代码设计

    序号

    内容

    要求

    1

    策略模式:当我们出现多种算法,需要自由切换时,可考虑策略模式

    建议

    2

    尝试应用设计模式

    代理模式:当系统出现隐藏或保护,控制目标对象,或是扩展目标对象能力,可考虑代理模式

    建议

    3

    单一职责原则:不要设计大而全的接口,接口的功能单一而具体的。

    建议

    4

    开闭原则。应尽可能按照开闭原则,对扩展开放,对修改封闭。

    • 把可变性封装起来,变成可以拓展的部分

    • 对不可变的部分,固化和放置在继承关系的上层

    建议

    5

    里氏替换:子类可以扩展父类的功能,但不能改变父类原有的功能

    建议

    6

    命令模式:当系统存在多个Action且Action中实现不一样时,可考虑引入命令模式

    建议

    7

    依赖倒置原则:基于接口编程而非实现编程,当我们实现service的时候,应把业务能力抽象成service接口

    建议

    8

    装饰模式:当系统存在想透明增强某个功能时,可考虑装饰模式

    建议

    9

    状态模式:当系统出现多于3个以上状态时,考虑是否可以引入状态模式。状态的流转实现在每种状态中。

    建议

    13.其他

    级别

    规则

    备注

    ​建议

    减少重复代码

    一旦有重复代码存在,阅读这些重复的代码时你就必须加倍仔细,留意其间细微的差异。如果要修改重复代码,你必须找出所有的副本来修改。

    ​建议

    不用sleep函数

    sleep的方案有以下几个问题:

    1. 处理时长变慢。

    2. 设置的时长难以确定

    3. 代码的影响范围难以评估。sleep代码在编写最初可能是为了解决一个简单的主从延迟场景问题。后来这段代码被多处依赖,维护者也频繁更换。最终新的维护者想要优化此处的处理时长时,既不敢删掉sleep代码,又不敢修改时间。因为他无法确定是否有其他的地方的代码是因为这里sleep几秒才正确运行。


    如果是为了解决主从延迟的问题,推荐用强制读主库的方式处理。

    如果是为了限制访问速率,推荐用专门的限流工具处理。

    ​建议

    不写没有业务特点的工具类

  • 相关阅读:
    【linux学习】存储结构与磁盘划分
    Vue.js结合ASP.NET Core构建用户登录与权限验证系统
    向量范数及其Python代码
    Integer使用不当
    MySQL数据库的存储引擎
    数学建模 | 灰色预测原理及python实现
    美创科技三重数据安全韧性,杜绝删库跑路
    爬虫HTTP代理:获取多种类型数据的神器
    【C++基于多设计模式下的同步&异步日志系统】
    VSCode 1.90版本 升级需谨慎~(Python)
  • 原文地址:https://blog.csdn.net/qq_40772692/article/details/131083600