一、相关背景
说起java代码规范,可能每家企业的重视程度都不一样。对于一些管理层不懂技术的企业,可能就不会有这个东西。而大部分的企业虽然有开发规范手册,但大都是照抄阿里巴巴手册。甚至有的企业当新员工入职后问起开发规范,管理层直接把阿里巴巴手册甩给他说这就是公司的开发规范手册。诚然,笔者不是吐槽阿里巴巴手册不够好(它很好但很多条款仅限于微观上的,缺乏宏观视角),但是直接原封不动照搬作为公司开发规范,我觉得是管理层不负责任的表现!
很多公司管理层CTO或架构师,在制定公司java规范时往往喜欢用禁止xxx、严禁xxx等条款式的语句制度化地对员工代码行为进行约束并强制员工必须达到某种要求。但是在笔者看来,一套好的制度设计应该是符合人性的,是员工都能够自然地接受且乐意遵守的,且上层也能够听取员工意见进行规范改进! 如果一家企业硬要用强制性的条款来约束、压迫员工的代码行为,这往往说明他制定的代码规范根本就是不符合人性的,甚至违背常理的,这才必须采取强制手段!
笔者认为制定代码规范的目的,本质上不是为了约束 ! 恰恰相反,制定代码规范的目的是为了解放生产力!提高员工整体编码素质及能力!要让员工学习并了解到什么才是优秀的代码,要引导员工编写出优良的代码,而不是禁止员工怎么编写代码 (多采用肯定式的表达而非否定式表达)。制定代码规范的最终目是为了员工培养出优良的编程思想及编码习惯,提升整体代码质量(提高代码可阅读性、可扩展性、减少代码冗余),这样的规范才是优秀的符合人性的,且能够让员工自发性遵守的。
二、宏观代码开发规范
阿里巴巴的java手册提到了很多具体而细小的方面,比如代码注释规约、类名/方法名规约、日志、线程池使用、数据库索引等等!这些内容在笔者看来都是微观上的规约(大家可自行网上查阅手册文档),现在笔者想聊一点不一样的:那就是宏观上的代码规范!
所谓宏观上代码规范 ,就是跳出手册上具体的细节上的条条框框,从项目代码宏观角度来看,开发员工应该具备的基本素质及要求。接下来,笔者从宏观开发规范上进行阐述:
2.1、模块化开发规范(宏观)
某些公司虽然搬出了一套java的开发规范,但都仅限于微观上条条框框的。宏观上看项目居然没有按模块化进行开发(先分模块包再分controller 、service、mapper、model、util等)。而是先划分了controller 、service 、mapper 、model 、uitl 等包再到其下建立模块包,这种规范在大型web项目种非常的不实用、甚至用起来会非常难受! 由于同一个模块的代码被分散到不同的package中,想要阅读某个模块代码开发人员必须把每个某个的package都打开看一遍,非常的吃力且浪费时间。且项目中每新增一个功能,开发人员都需要把相关的代码分散到不同的packge下的目录,随着项目规模的增大,开发人员无论是查找自己的代码模块还是他人阅读他人写的模块代码都会非常的吃力! 优良的代码设计应该按照模块进行划分包结构,然后再模块package中再划分 controller、service 、mapper (含接口及xml文件)、 model(实体类) 、req (请求类)、res(响应类)、feign(feign接口声明)、util等子包,这样的设计才是符合人性且让大家乐于遵守的!
2.2、Java三层开发模型规范(宏观)
某些公司同样搬出了一套java的开发规范文档(但是不出意外文档上全是微观上的条条框框)。至于非常重要的Java三层开发模型规范的描述,通篇下来却连一个字都没有提及!那么什么是Java三层开发模型,为什么它非常的重要!
Java三层开发模型是一种分层架构模式,它将应用程序的业务逻辑按照职责划分为三个独立的、松耦合的层次。每一层都有明确的职责,并且只与其相邻的层进行通信,从而实现“高内聚、低耦合”的设计目标。
1、控制层 (Controller)
处理用户交互:接收用户的HTTP请求(如GET、POST)。
参数校验与绑定:验证请求参数的合法性(如是否为空、格式是否正确)。
调用业务层:将校验后的参数传递给业务逻辑层进行处理。
返回响应:将业务层处理的结果(通常是数据对象)封装成合适的格式(如JSON、HTML页面、XML)返回给前端。
具体到java服务端开发也就是web控制层。它是http请求的入口,可以理解为进入大楼前的安全门。 控制层的主要作用是接收用户请求,验证用户身份、web鉴权、请求参数验证等, 甚至会做一些安全性验证(如防重复提交),确保请求的安全性。
职责总结: 用户身份验证、web鉴权、必要的请求参数验证、以及一些访问安全性验证等
2. 业务逻辑层(Service)
业务层是接口的业务逻辑部分,也称之为业务内核。 它的作用是负责处理具体的业务操作。 业务层可通过调用dao层完成对数据层的交互(增删改查等)、也可以调用其他工具bean完成对外部接口的调用,还可以通过调用消息中间件投放和监听消息等。业务层的作用类似于人体的中枢大脑,它可以调动身体的各个部位完成负责的动作。
3、数据访问层(dao)
数据访问层,在mybatis 等ORM类框架体系中也称之为mapper层。
它的作用是封装对数据数的访问,以API的形式提供给service层使用。
dao层职责:完成对数据访问层的API封装,提供给service层代码复用。Dao层的具体内容比如
(1)、分页查询组装各种查询条件
(2)、根据各种不同查询条件修改表数据
(3)、查询统计指标(聚合查询)
(4)、多表关联查询
(5)、查询表别名as映射到java属性
.....
笔者发现,在大部分的企业的实际java开发中,控制层的职责大家都理解比较清晰,但是对于service和dao层的职责理解划分就有些模糊不清了,甚至经常将service当作dao使用,尤其是喜欢采用了mybatisplus的框架下,经常滥用mybatisplus的LambdaQuery !
1、案例1,多条件查询的场景 有些开发人员喜欢在Service的方法中编写很长很长的LambdaQuery,类似这样的代码不仅难于阅读理解且难以修改及维护!
LambdaQueryWrapper<Menu> lambdaQueryWrapper = new LambdaQueryWrapper<>();
if (!ObjectUtil.isEmpty(dto.getName())) {
lambdaQueryWrapper.like(Menu::getName, dto.getName());
}
if (!ObjectUtil.isEmpty(dto.getStatus())) {
lambdaQueryWrapper.eq(Menu::getStatus, dto.getStatus());
}
if (!ObjectUtil.isEmpty(dto.getCreateBy())) {
lambdaQueryWrapper.like(Menu::getCreateBy, dto.getCreateBy());
}
if (!ObjectUtil.isEmpty(dto.getOrgId())) {
lambdaQueryWrapper.eq(Menu::getOrgId, dto.getOrgId());
}
if (null != dto.getStatus()) {
lambdaQueryWrapper.eq(HmAppMenu::getStatus, dto.getStatus());
}
lambdaQueryWrapper.ne(Menu::getParentId, 0);
lambdaQueryWrapper.gt(Menu::getStatus, "-1");
//...
lambdaQueryWrapper.orderByDesc(Menu::getStatus).orderByAsc(Menu::getSortNumber);
List<Menu> data = baseMapper.selectList(lambdaQueryWrapper);
封装查询本来就是dao层职责,应当封装为mapper接口方法。
2、案例2 ,很多人觉得上面的案例是条件多太复杂才应该进行mapper封装,如果查询条件只有一两个可以在service写直接写, 这种观点也是不正确的!因为只封装的数据层的访问是dao层的职责而不是service层。
//根据 name+phone查询一个用户
LambdaQueryWrapper<Person> lambdaQueryWrapper =new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(Person::getName,req.getName());
lambdaQueryWrapper.eq(Person::getPhone,req.getPhone());
Person person = personMapper.selectOne(lambdaQueryWrapper);
在这个例子中开发人员需要根据用户姓名+手机号查询出一个用户,代码算是比较简单直接的开发人员直接Service类中写上这3行代码即可。 但是我们发现是根据姓名+手机号查询一个用户这样的场景可能不只是出现在当前模块service中,其他业务模块service类中也有这样的查询需求呢,难道那个service类中又要重复写着3行代码吗?
当然不是! 别忘了,我们的Dao层天然的职责就是封装对数据库访问的操作,并对外(通常是service层)提供dao层的api接口。 因此优雅的做法是将这个查询封装到PersonMapper接口中,方便所有的service层类进行复用(消除冗余代码)。
类似的滥用mybatisplus查询案例很多公司比比皆是。我们应尽量避免使用mybatisplus的lambda查询,因为这些查询往往都是应该被封装到mapper层接口上的。当我们习惯了封装mapper接口后,会慢慢发现我们Dao层的提供的API越多,越方便于service层的复用,这样就将service层从繁杂的sql语句处理中解放出来,让service层只专注于业务。
3、案例3 ,还有很多开发人员喜欢在自己Service类(A service类)中引入其他表的service(B service),其目的仅仅是为了查询b表数据或写b表数据。
这种做法显然也是非常不可取的! 想要操作查询或写入b表数据,我们优先考虑引入的是b表的mapper接口而不是b表的service类。 如果b表没有复杂的业务处理方法(只有简单的增删改查),那b表的service类完全可以省掉!(没必要通过代码生成工具生成所有表的service类)。
Service层类相互引入会导致各模块之间依赖关系变得非常复杂,各个service之间就像一张蜘蛛网错综复杂,而且很有可能导致循环引入依赖的问题(虽然高版本spring框架能够解决此问题但还是要尽量避免循环依赖)!!
三层模型中各层尽量避免引入同一层或上一层的bean,可引入下一层的bean
例如:controller 层可以引入service、dao层的bean,但是避免引入其他controller层的bean
service层可以引入dao层 或其他模块工具bean ,但是避免引入其他service,更不能引入controller层bean。
总而言之制定代码规范,是为了更好的培养(而不是约束),引导员工养成良好的的编程思想及编码习惯,才能极大提高他们的生产力!
1151

被折叠的 条评论
为什么被折叠?



