一.前言

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

  • 《阿里巴巴Java开发手册》:https://github.com/alibaba/p3c
  • Alibaba Java Code Guidelines插件:https://github.com/alibaba/p3c/tree/master/idea-plugin

二.编码规范

1.系统分层

(1)分层架构设计

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

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

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

  • Service层:相对具体的业务逻辑服务层。

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

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

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

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

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

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

(2)分层领域模型规约

  • 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。

  •  大致示例代码:

    • Controller层:public List<UserVO> getUsers(UserQuery userQuery)。此层常见的转换为:DTO转VO

    • Service/Manager层:List<UserDTO> getUsers(UserQuery userQuery)。然后在Service内部使用UserBO封装中间所需的逻辑对象,此层常见的转换为:PO转DTO,或PO转BO转DTO

    • DAO层:List<UserPO> getUsers(UserQuery userQuery);

(3)分层视图

        ​​​​​​​参考文章(写得很好):https://blog.51cto.com/knifeedge/5139389​​​​​​​

  • 查询视图

  • 返回视图

 (4)总结

有的朋友查询参数喜欢通过 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几秒才正确运行。

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

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

​建议

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

实习记录(一) Java 编程风格规约相关推荐

  1. 腾讯华为等企业c/c++/java编程风格 及个人编码模板

    一.在程序的版式上 1.程序块要采用缩进风格编写 ,缩进的空格数为 4 个. 原因说明: 由开发工具自动生成的代码可能不一致,但如果开发工具可以配置,则应该统一配 置缩进为 4 个空格. 2.缩进或者 ...

  2. 谷歌 Java 编程风格指南

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | http://hawstein.com/201 ...

  3. Google JAVA编程风格

    目录 前言 源文件基础 源文件结构 格式 命名约定 编程实践 Javadoc 后记 前言 这份文档是Google Java编程风格规范的完整定义.当且仅当一个Java源文件符合此文档中的规则, 我们才 ...

  4. 深度解析Google Java 编程风格指南

    这份文档是Google Java编程风格规范的完整定义.当且仅当一个Java源文件符合此文档中的规则, 我们才认为它符合Google的Java编程风格. 与其它的编程风格指南一样,这里所讨论的不仅仅是 ...

  5. Google Java编程风格指南中文版

    作者:Hawstein 出处:http://hawstein.com/posts/google-java-style.html 声明:本文采用以下协议进行授权: 自由转载-非商用-非衍生-保持署名|C ...

  6. Google Java编程风格指南中文版(转)

    作者:Hawstein 出处:http://hawstein.com/posts/google-java-style.html 声明:本文采用以下协议进行授权: 自由转载-非商用-非衍生-保持署名|C ...

  7. Google Java编程风格指南(献给那些没有良好编码习惯的程序员们)

    作者:Hawstein 出处:http://hawstein.com/posts/google-java-style.html 声明:本文采用以下协议进行授权: 自由转载-非商用-非衍生-保持署名|C ...

  8. Google Java编程风格指南

    Hawstein's Blog Home Archive Categories Sitemap About Su 前言 源文件基础 源文件结构 格式 命名约定 编程实践 Javadoc 后记 前言 这 ...

  9. Java 编程风格军规,看这一篇就够了

    点击上方"IT牧场",选择"设为星标"技术干货每日送达 来源:谷歌 目录 源文件基础 源文件结构 格式 命名约定 编程实践 Javadoc 这份文档是Googl ...

最新文章

  1. python 释放变量所指向的内存_通俗易懂的Python垃圾回收机制及内存管理
  2. Redis 修改密码
  3. 给NavigationCtrl 增强动画.
  4. 3D空间中射线与三角形的交叉检测算法
  5. 剑指offer第二版(150M超清分享PDF+源码)(转)
  6. 网页设计html流水效果图,15例简单常用网页设计效果代码
  7. 【新手教程】从零搭建php动态网站
  8. Node包管理使用全教程
  9. 【概率论】极大似然估计和最大后验估计
  10. MySQL数据库实操教程(24)——用户管理
  11. 航空公司客户价值特征构建与分析k-means
  12. 破解navicat时出现No All Pattern Found File Already Patched
  13. 微服务消息传输过程中的一些问题
  14. 本地计算机无法设置共享文件夹,共享服务,详细教您win10共享文件夹无法访问怎么办...
  15. python语言程序设计项目_《Python语言程序设计》项目报告书Word版
  16. 人民币首度超过美元!
  17. 使用腾讯企业邮箱为什么强烈建议启用安全登录?怎么启用?
  18. 通用汽车新战略:“逃离”汽车制造,能否冲破“围城”之困
  19. mysql子查询:查出本网站goods_id最大的一条商品和每个栏目下id号最大的一条商品.
  20. 基于MATLAB图像处理的硬币个数识别系统

热门文章

  1. 中级计算机技术工程师报名资格,中级软件工程师证书报考条件有哪些要求?
  2. 为什么这么多人说 IDEA 比 Eclipse 更好?
  3. unity查找物体的几种方式
  4. 2022第十三届蓝桥杯JAVAB组省赛总结
  5. 矩阵的负二分之一怎么计算
  6. 警惕!程序员万字揭露被空姐骗到香港做传销的来龙去脉!
  7. 【OpenCV入门实战】利用电脑前置摄像头进行人脸检测
  8. 别再吐槽 notebook!分享4个解决版本控制的高效工具!
  9. 类的学习Java写查询星座小程序
  10. JSP实现登录删除添加星座等(带样式)