JavaWeb之谈论项目编码规范_Java版
1. 关于DDD项目结构约定
1.1 项目结构使用DDD整洁架构进行分包
maven项目结构遵从DDD整洁架构分为如下四个顶级包:
application - 应用层代码,一般为接口层定义API的实现类和一些结构转化,application不应该承载业务逻辑
domain - 领域层,包含应用的业务模型定义,全部业务逻辑,可以细分实体(entity)和领域服务(service)等子包
infrastructure - 基础设施层,包含配置、基础工具、切面、枚举、外部服务调用、消息、缓存等中间件
interfaces - 服务自身API的定义,以及与API定义相关的API结构(DTO)定义
单模块工程结构示例:
assignment-service
– src/main/java
---- com.huawei.it.hr.assignment
------ application
------ domain
------ infrastructure
------ interfaces
------ AssignmentApp.java 启动类
– pom.xml
多模块工程结构示例:
MetadaPayrollCalculation
– PayrollCalcService
---- src/main/java
------ com.huawei.it.hr.payroll
------ application
------ domain
------ infrastructure
------ interfaces
---- pom.xml 子模块构建配置文件
– PayrollCalcBase
---- src/main/java
------ com.huawei.it.hr.payroll
------ application
------ domain
------ infrastructure
------ interfaces
---- pom.xml 子模块构建配置文件
– pom.xml 父模块构建配置文件
各层级依赖关系为:
接口层通常不依赖应用层/领域层,可以依赖部分基础设施层工具、类型
应用层依赖接口层的API接口定义,并依据接口定义提供实现
应用层依赖领域层处理业务逻辑
应用层可以使用部分基础设施层工具、类型
领域层不反向依赖应用层和接口层。可以依赖部分基础设施层工具和类型
2. 禁止大面积拷贝
2.1 禁止拷贝反编译生成的代码
说明: Java源代码编译成class文件时,会保留一些公共部分,如类名,公共方法名等。但是JVM出于一些优化考虑,会调整一些细节部分,如将内部常量,变量,方法内联,省略形参变量名(仅保留类型),包装异常块等。因此,根据class文件反向编译出来的源代码,与生成class的原始源代码大相径庭。在代码中直接使用反向编译生成的勉强能工作的源代码,严重影响可读性,后续也极难维护,应该杜绝。
错误示例:
try {
try {
Object[] result = BusinessDataWriter.save(dataEntities[0].getDataEntityType(), dataEntities,
option);
if (Configuration.isEnable()) {
DTXServiceHelper.confirmXid(dbkey,
result != null ? DynamicObjectSerializeUtil.serialize(result,
dataEntities[0].getDynamicObjectType()) : null);
}
RecordSaveFormServiceHelper.recordSaveFormToCache(
dataEntities[0].getDataEntityType().getName());
var7 = result;
} catch (Throwable var17) {
throw var17;
}
} catch (Throwable var18) {
throw var18;
}
return var7;
#### 2.2 禁止拷贝第三方代码,需要引用/修改时应显式声明依赖后扩展
说明:直接拷贝第三方/开源代码,有版权问题,且直接拷贝源码,失去了后续迭代的一切可能性,在出现漏洞/升级时补救成本巨大。按照Open-Close原则,应该将依赖显式声明,在需要修改依赖包API行为时,使用装饰器,转换器等模式进行扩展。直接拷贝其他工程的代码,也及容易留下无用代码和产生重复代码,应该禁止。### 3. 命名和分包约束
#### 3.1 类型、变量和方法使用合适的命名
说明:类型,变量,方法签名必须具备一定的自解释性,也是代码可读性的重要基础。在《阿里巴巴编码规范》基础上,禁止使用 map,object, json, data, list 等非常泛化的命名#### 3.2 数据库映射对象(实体)命名以Entity/Pojo结尾,如 CalcPersonEntity/CalcPersonPojo
对于数据库映射对象,统一使用 *Entity/Pojo 的命名风格,放在 entity/pojo 包下#### 3.3 API结构定义对象 命名以Dto结尾,如 CalcPersonDto
对于API结构定义类对象,统一使用 *Dto 的命名风格(数据传输对象)#### 3.4 领域层一般模型,直接使用语义命名,放在 model 包下
领域层实体模型,按照3.2放在entity包下,其他非实体领域层模型,放在model包下#### 3.5 领域层一般模型,不建议保留古老的 VO/BO 等包名和命名后缀
VO原意至Value Object 或 View Object。值对象的含义已由Dto概括,View Object 在前后端分离架构中通常已经不复存在。BO 原意为 Business Object,名称太泛,不建议使用。#### 3.6 数据库查询类非持久化结构,使用 Query 结尾,如 CalPersonQuery,对于事务类结构,建议使用Command结尾或直接语义化命名
例如根据业务需要定制的查询参数结构,建议使用 *Query 的命名风格。对于事务类操作,可以使用*Command的命名风格,也可以直接按照上下文语义进行命名。#### 3.7 贯穿接口/领域层的枚举类型,放在 infrastructure 层定义,领域层内部的枚举,可以直接定义在 domain 层 model 包中
对于枚举类型,通常不适合在接口层和领域层重复定义,为保持层次依赖关系清晰,共用的枚举结构建议定义在基础设施层中作为公共结构。领域层专用枚举,可以直接放在domain层model包下。### 4 代码结构约定
#### 4.1 禁止使用动态类型描述固定结构
说明:JAVA是面向对象的语言,也是JAVA代码保持良好延展性的重要基石。在描述格式固定的结构时,使用类型和对象非常合适,可以有效的进行语义化表达和类型约束,让更多的错误在编译阶段即可识别和解决。而不至于留到运行时。Json,Map等动态类型仅在描述真正不确定的动态结构时是合理的,但是如果结构较为固定(如获取/设置固定字符串描述的Key),使用动态类型表达会绕过一些编译期检查,同时往往伴随着诸多类型转换,降低了代码可读性,重用性和健壮性。错误示例:Map param = new HashMap();
param.put("appId", ConfigPropertiesUtil.getContextProperty("application.appId"));
param.put("subAppId", ConfigPropertiesUtil.getContextProperty("application.subAppId"));
param.put("jobWorkerDuId", ConfigPropertiesUtil.getContextProperty("lite.job.client.jobworkerduid"));
param.put("jobName", ConfigPropertiesUtil.getContextProperty("lite.job.client.jobname"));
param.put("taskName", ConfigPropertiesUtil.getContextProperty("lite.job.client.taskname"));
JSONObject obj = new JSONObject(param);
// 此处Map中的结构是固定的,使用一个固定结构更为合适。
对比示例:
@Getter
@Setter
public class JobConfig {
private String appId;
private String subAppId;
private String jobWorkerDuId;
private String jobName;
private String taskName;
}
#### 4.2 代码块合理分段,确保结构清晰,避免出现过多代码块缩进嵌套
箭头函数是一个临时定义的方法,缺省了方法签名,在内容较少时比较适用。箭头函数的代码块中不宜存放过多代码内容,会导致更多复杂的缩进和嵌套,不利于理解。如果代码块中需要执行的指令较多,应该提取为单独的方法,进行合适的方法署名。错误示例:list.stream().forEach(beforeVO -> {try {LogRecordVo logRecordVo = LogRecordVo.builder().tableName(PayElementAttributeDomain.LOG_TABLE_NAME).entities(Arrays.asList(PayElementAttributeDomain.LOG_TABLE_NAME)).moduleName(PayElementAttributeDomain.LOG_MODULE_NAME_DELETE).businessNo(String.valueOf(beforeVO.getAttributeId())).operationEnum(LogOperationEnum.DELETE).beforeObject(beforeVO).afterObject(null).build();AsyncMessage asyncMessage = new AsyncMessage(LogRecordConstant.MSG_LOG_RECORD_SEND);asyncMessage.setContent(logRecordVo);messageSender.send(asyncMessage);} catch (Exception e) {log.error("log error: {}", e.getMessage());}
});
等价示例:
list.forEach(beforeVO -> sendMessage(beforeVO));
private void sendMessage(PayElementAttributeBusinessViewDto vo) {
try {
// … 原始发送消息相关代码
} catch (Exception exception) {
log.error(“log error: {}”, e.getMessage());
}
}
#### 4.3 基于SOLID原则,使用组合优于继承
SOLID原则包括的内容很多,其基本含义是职责单一(Single Responsibility),对扩展开放,对修改关闭(Open Close), 里氏替换(Interface Segregation),接口隔离(Interface Segregation)和依赖反转(Dependency Inversion)五部分,过多的使用继承关系来获取已有的父类功能和属性,不利于代码的可读性和可维护性,应该优先使用接口抽象和组合关系表达功能的组合。错误示例:public class PayrollCalcTaskRunVO extends HRBaseVO {//.... attributes
}
等价示例:
public class PayrollCalcTaskRunVO {
@JsonUnwrapped
private HRBaseVO baseInfo;
//.... attributes
}
**说明:在Mybatis等ORM框架中可以使用合适的配置写法将结果映射成复杂对象,如Mybatis中的accociation配置。在API中可以通过 @JsonUnwrapped 等注解将属性对象的内容展开到父级json块,达到与继承相同的JSON格式。**#### 4.4 减少switch语句的使用
switch是早期JVM提供的一个关键字,《Clean Code》中这样描述switch语句"写出只做一件事的switch语句也很难,switch天生就要做N件事"。switch语句本身很复杂,也不容易理解,一般时候应该避免/减少使用switch-case关键字的使用。错误示例:switch (operationType) {case "R" : // 授权addPermissions(rolePlanEntity, personInfoEntity.getGlobalUserId(), billDispatchRolePlanEntity);break;case "D" : // 延期break;case "C" : // 取消deleteRolePerson(personInfoEntity.getGlobalUserId(), billDispatchRolePlanEntity);break;default :log.error("IAuthServiceImpl operationType is null");break;
}
等价示例:
Map<String, Runnable> actionMap = newHashMap();
actionMap.put(“R”, () -> addPermissions(rolePlanEntity, personInfoEntity.getGlobalUserId(), billDispatchRolePlanEntity));
actionMap.put(“D”, () -> {});
actionMap.put(“C”, () -> deleteRolePerson(personInfoEntity.getGlobalUserId(), billDispatchRolePlanEntity));
Runnable defaultAction = () -> log.error(“IAuthServiceImpl operationType is null”);
actionMap.getOrDefault(operationType, defaultAction).run();
4.5 避免过多的if-else嵌套,switch式的if-else使用map进行简化
if-else嵌套时,会让代码的分指数显著增加,一般需要对一些代码块进行分割,并尽量共用重复逻辑部分。某些连续的 if-else 分支写法其实是类似于switch的表达,同样可以使用Map进行简化。
错误示例:
// 此处明显为固定结构,应该使用相应的类型进行描述,而不是使用Map。且变量名mp也明显缺乏含义
Map<String, Object> mp = new HashMap<String, Object>();
if(“1”.equals(search_type)){
mp.put(“rehiredatebegin”, search_param);
mp.put(“rehiredateend”, search_param1);
mp.put(“search_type”, search_type);
} else if (“2”.equals(search_type)){
mp.put(“oldnumber”, search_param);
mp.put(“search_type”, search_type);
} else if(“all”.equals(search_type)){
mp.put(“search_type”, search_type);
} else {
return null;
}
mp.put(“pageInfo”,“true”);
mp.put(“pageSize”,“3000”);//分页数
mp.put(“curPage”,“1”);//当前页
等价示例:
// 将searchType、pageInfo、pageSize、curPage等固定内容放在构造函数中统一初始化
RequestParam requestParam = new RequestParam(search_type);
Map<String, Runnable> typeActionMap = newHashMap();
typeActionMap.put(“1”, () -> injectParamAsDate(requestParam, search_param, search_param1));
typeActionMap.put(“2”, () -> injectParamAsNumber(requestParam, search_param));
typeActionMap.put(“all”, () -> {});
// 为重构需要,将退出语句更换为运行时异常结束
Runnable defaultAction = () -> { throw new InvalidSearchTypeException(“search type is not valid”); };
typeActionMap.getOrDefault(search_type, defaultAction).run();
4.6 逻辑关系简明化,避免过多 return true/ false的指令
返回 boolean类型的断言方法,一般只用专注与断言条件极其组合,而不需要太多分支控制,分支控制中的条件可以直接作为boolean的结果返回,让代码看起来更加简洁内聚
错误示例:
public boolean isContainsSpecilValue(String currentDeptCode, String path) {
String configValues = getRegistryValuesByPath(path);
if (!StringUtils.isEmpty(configValues)) {
String[] values = configValues.split(“,”);
if (values != null && values.length > 0) {
List list = deptQueryDao.findChildrensDeptsByCodes(Arrays.asList(values));
if (!CollectionUtils.isEmpty(list) && list.contains(currentDeptCode)) {
// 当前部门在此部门体系中,则允许异地纳税
return true;
} else {
// 不允许异地纳税
return false;
}
}
} else {
log.info(“指定的paht=” + path + “的值未配置值!”);
return false;
}
return true;
}
等价示例:
public boolean isContainsSpecilValue(String currentDeptCode, String path) {
String configValues = getRegistryValuesByPath(path);
return StringUtils.isNotEmpty(configValues) && deptQueryDao.findChildrensDeptsByCodes(newArrayList(configValues.split(“,”))).contains(currentDeptCode);
}
4.7 使用泛型时需要显式指定类型,避免代码含糊不清
错误示例
final Map subMap = parserToMap(array[i]); // 由于没有指定泛型类型,代码可读性变差,后续使用时通常需要一些强制转换
对比示例
final Map<String, String> subMap = parserToMap(array[i]);
// 实在不知道类型可以使用T或者?代替
Map<String, T> subMap = parserToMap(array[i]);
4.8 抛出业务异常时,异常信息必须考虑多语言,禁止中文硬编码
错误示例
if (!save) {
throw new SystemException(“保存业务属性配置失败!”);
}
对比示例
if(newlyResult>0) {
// 该异常中获取message时,会根据code查询 i18n 配置获取
throw new BusinessI18nException(AccumulatorCalcExceptionDesc.ACC_EXISTS_NEWLY_RESULT);
}
4.9 按照正确的格式打印日志
错误示例
logger.info("success: " + dto); //不符合使用习惯
logger.error(“failed: {}”, exception); // 不会打印异常栈
logger.error(“failed: {}”, exception.getMessage()) // 仅打印异常信息
对比示例logger.info("success : {}" , dto);
log.error("failed: ", exception); //该接口会打印异常栈### 5. 减少重复
#### 5.1 禁止脱离业务实际需要堆砌CRUD,禁止开发预留、测试、无用的API
说明:开发人员开始编写代码时,需要有清晰具象化的业务需求和边界,而不能基于模棱两可的诉求进行开发。开发阶段对功能进行测试,验证时,应优先使用单元测试,通过编写符合业务诉求的单元测试(开发者测试)来验证和确保功能实现的正确性,而不是在产品代码中留下一些测试、无用的代码。虽然大部分模型最终都会需要CRUD等接口,但是按Story开发功能需求时,也应该首先聚焦Story包含的实际功能和边界,而不是站在开发的角度无脑添加CRUD四个接口,如此做容易留下一些实际上不会用到的功能和接口。#### 5.2 减少重复代码,出现重复时及时重构
代码中明显存在重复的部分,应该及时重构,提取公共部分进行共用。框架性的公共代码考虑使用AOP等方式通过切面和注解统一处理。错误示例(在每个请求之前需要初始化苍穹平台的请求上下文)public SwitchPolicy listPolicies(RequestParam requestParam) {initHcmRequestContext();//... listLogic
}public SwitchPolicy createOrUpdate(SwitchPolicy switchPolicy) {initHcmRequestContext();//... createOrUpdateLogic
}private void initHcmRequestContext() {RequestContext ctx = RequestContext.getOrCreate();if (Objects.isNull(ctx.getAccountId())) {ctx.setAccountId(System.getProperty("requestContext.accountId"));}if (Objects.isNull(ctx.getTenantId())) {ctx.setTenantId(System.getProperty("requestContext.tenantId"));}
}等价示例:// 定义为切面,通过@HcmContext注解进行标注
@Slf4j
@Component
@Aspect
public class HcmContextAspect {@Around("@annotation(HcmContext)")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {initHcmRequestContext();log.info("Initialized HCM context ... ");return joinPoint.proceed();}private void initHcmRequestContext() {// init logic}
}#### 5.3 不同分支中存在较多相同逻辑时,分支只负责差异部分,相同的部分不重复书写。
避免在不同分支中出现太多重复逻辑,分支专注于差异的部分,公共部分抽取方法。错误示例if (privateBusiness.get(elementTypeCode) == null) {Set<String> privateBusinessSet = new HashSet<>();for (String string : strings) {if (string != null && !string.trim().equals("")) {privateBusinessSet.add(string);}}privateBusiness.put(elementTypeCode, privateBusinessSet);
} else {Set<String> privateBusinessSet = privateBusiness.get(elementTypeCode);for (String string : strings) {if (string != null && !string.trim().equals("")) {privateBusinessSet.add(string);}}privateBusiness.put(elementTypeCode, privateBusinessSet);
}等价示例:Set<String> privateBusinessSet = Optional.ofNullable(privateBusiness.get(elementTypeCode)).orElse(new HashSet<>());
for (String string : strings) {if (string != null && !string.trim().equals("")) {privateBusinessSet.add(string);}
}
privateBusiness.put(elementTypeCode, privateBusinessSet);### 6. 推荐工具和表现力较强的API
#### 6.1 使用stream接口替换常用的循环操作错误示范:boolean isAllow = false;
if (StringUtil.isNotNull(cities)) {String[] cityArr = cities.split(",");for (String city : cityArr) {city = city.trim();if (city.equals(tlasApplicationVO.getToResidentLocation()+"")) {isAllow = true;break;}}
}等价示例:boolean isAllow = StringUtil.isNotNull(cities) && Stream.of(cities.split(","))
.anyMatch(city -> city.trim().equals(tlasApplicationVO.getToResidentLocation() + ""));Stream常用操作附录:
| API | 作用| 使用场景| 案例代码|
|--|--|--|:--|
| map | 映射操作,将集合中每个元素转换为其他格式的元素 | 按照用户列表获取每个用户的常驻地 | List<Location> userLocations = userList.stream().map(User::getLocation).collect(toList()) |
| filter | 按照条件筛选集合中满足条件的元素 |筛选有效账号 |List<User> enabledUsers = userList.stream().filter(user -> Objects.equals("Y", user.getEnableFlag())).collect(toList()); |
| peek |在循环中做额外动作(如打日志),不改变输入输出 | 打印stream中的处理日志 | userList.stream().peek(user -> logUserName(user)).filter(user -> isValid(user)).collect(toList()) |
| reduce |聚合集合元素/特征,如求和 |计算用户列表的工资总额 | Integer totalSalary = userList.stream().map(user -> user.getSalary()).reduce(0, Integer::sum) |
| allMatch | 判断集合中是否每个元素都满足某条件 | 判断是否都是有效用户 | Boolean isAllValid = userList.stream().allMatch(user -> isValid(user)); |
| anyMatch |判断集合中是否有至少一个元素满足条件 |判断是否包含异常用户 | Boolean existAbnormalUser = userList.stream().anyMatch(user -> isAbnormal(user)); |
| Collectors.groupBy|按元素特征分类 |按用户类型进行分类 | Map<String, List<User>> groupedUser = userList.stream().collect(groupingBy(User::getType)); |#### 6.2 guava 和各种 Utils 工具包
guava是google提供的开发工具包,在Java8提供Lambda写法之前就已经存在,其提供的多种函数式编程接口非常流行。在Java8发布之后,许多原有的功能逐渐内置到了JVM中,但仍然存在一些方便的工具可供使用。以其集合包(还有数学等其他辅助工具包)中的一些常用接口举例如下:| 接口名 | 功能说明 | 举例 |
|--|--| --|
| Lists.newArrayList |构造一个List对象,可以接受数组,其他List,或者可变长的Item列表 | List<Person> personList = newArrayList(personOne, personTwo); |
|Lists.partition | 将List按照指定最大长度拆分为一些小的List | List<List<Person>> subLists = partition(personList, 100); |
|Sets.intersection / union |取两个集合的交集 / 并集 |Set<Integer> answer = Sets.intersection(set1, set2); |除guava外,还有一些Apache提供的Utils工具包,对于一些常见模式的问题提供了接口封装,如StringUtil,Json,XML等相关的常用工具等。### 7 使用框架能力解决通用问题
#### 7.1 对象转换工具 Mapstruct & spring BeanUtils
在不同的层次之间,经常需要进行类型转换,与其手写转换关系,不如使用一些常用的工具,如MapStruct,spring BeanUtils等。MapStruct会根据对象结构的字段名称,在编译阶段生成转换代码,类型安全,且自动生成空保护等语句,通过声明式配置指定转换关系。spring BeanUtils基于反射机制进行对象属性映射,也可以省略一部分手工编写的类型转换代码,但是需要注意copyProperties自身的特性,如需要类型匹配,以及不会忽略null属性等。错误示例:UserInfoBean userInfoBean = new UserInfoBean(null);
userInfoBean.setUid(user.getUid());
userInfoBean.setEmployeeNumber(user.getEmployeeNumber());
userInfoBean.setEmail(user.getEmail());
userInfoBean.setEmployeeType(user.getEmployeeType());
userInfoBean.setCn(user.getCn());
等价示例:@Mapper(componentModel = "spring")
public interface UserMapper {UserInfoBean toBean(User user)
}#### 7.2 使用框架Validator进行参数校验,减少手动实现
请求参数的非空性,有效性,长度范围,取值范围校验等属于常用的框架级校验,一般应该借助框架工具来控制,而不需要手动编写代码逐个字段进行手动校验错误示例:public Boolean submitVisaToDo(SubmitVisaAndSalaryTaxDto submitVisaAndSalaryTaxDto) {VisaInfoDto visaInfoDto = submitVisaAndSalaryTaxDto.getVisaInfoDto();// 签证数据基础非空校验if (visaInfoDto.getCategoryCode() == null) {throw new ResultException(I18nConstant.VISA_CATEGORY_NULL);}if (StringUtil.isNullOrEmpty(visaInfoDto.getTypeCode())) {throw new ResultException(I18nConstant.VISA_TYPE_NULL);}if (StringUtil.isNullOrEmpty(visaInfoDto.getComplianceApproval())) {throw new ResultException(I18nConstant.SALARY_TAX_COMPLANCE_APPROVAL_NULL);}if (!StringUtil.isNullOrEmpty(visaInfoDto.getDescription()) && visaInfoDto.getDescription().length() > ContractPlanEnum.ONE_THOUSAND.getCode()) {throw new ResultException(I18nConstant.SALARY_TAX_REMIND_BEYOND_ONE_THOUSAND);}//... business logic
}等价示例:public class VisaInfoDto {/*** 签证大类*/@ApiModelProperty("签证大类id")@NotBlank(message = I18nConstant.VISA_CATEGORY_NULL)private Long categoryCode;/*** 签证类型编码*/@ApiModelProperty("签证类型编码")@NotBlank(message = I18nConstant.VISA_TYPE_NULL)private String typeCode;/*** 是否涉及合格审批*/@ApiModelProperty("是否涉及合格审批")@NotBlank(message = I18nConstant.SALARY_TAX_COMPLANCE_APPROVAL_NULL)private String complianceApproval;/*** 重要提示*/@ApiModelProperty("重要提示")@Size(max = 1000, message = I18nConstant.SALARY_TAX_REMIND_BEYOND_ONE_THOUSAND)private String description;
}#### 7.3 使用統一异常处理器,取代每个API的单独手工处理
后台服务在发生异常时,通常需要对异常信息进行一定的处理和包装,结构上可与正常相应存在差异。在Spring框架中提供了统一的异常处理机制(@RestControllerAdvice),按照异常类型和code进行统一修饰。无需每个API单独处理一遍。错误示例:@Override
public BasicResponse<String> export(PayElementLabelPageDto queryDTO) {try {localExcelExportAssistant.submitExportTask("payrollCalcService.PayElementLabel", queryDTO);return BasicResponse.ok();} catch (ApplicationException e) {log.error("export payElementLabel error:", e);return BasicResponse.error();}
}等价示例:@Slf4j
@Provider
@Named("applicationExceptionHandler")
public class ApplicationExceptionHandler implements ExceptionMapper<ApplicationException> {@Overridepublic Response toResponse(ApplicationException exception) {log.error("A ApplicationException occurred during the request process:", exception);return Response.status(Response.Status.BAD_REQUEST).type("application/json;charset=UTF-8").entity(BasicResponse.error()).build();}
}#### 7.4 使用Builder模式构造对象
builder模式提供了一种可联连续设置属性的构造对象方式,可以较方便的将属性值构造成对象,且避免了临时变量的多次出现。错误示例PageDTO pageDTO = new PageDTO();
pageDTO.setPageNo(curPage);
pageDTO.setPageSize(pageSize);
等价示例:
PageDTO pageDto = PageDTO.builder.pageNo(curPage).pageSize(pageSize).build();
注意,使用Lombok注解@Builder时,会生成全参构造器覆盖默认的无参构造器,如果需要保留无参构造器,应叠加@NoArgsConstructor注解一起使用
7.5 使用Lombok注解生成模板代码
Lombok相关的注解,可以在编译期自动生成一下常用的模板代码,如构造函数,getter/setter等,能有效减少模板代码。
错误示例:
public class PageDTO implements Serializable {
private static final long serialVersionUID = 1211673654467855785L;
private Integer pageNo = 1;
private Integer pageSize = 15;
public PageDTO() {
}public Integer getPageNo() {return this.pageNo;
}public void setPageNo(Integer pageNoTemp) {this.pageNo = pageNoTemp;
}public Integer getPageSize() {return this.pageSize;
}public void setPageSize(Integer pageSizeTemp) {this.pageSize = pageSizeTemp;
}
}
等价示例:
@Getter
@Setter
public class PageDTO implements Serializable {
private static final long serialVersionUID = 1211673654467855785L;
private Integer pageNo = 1;
private Integer pageSize = 15;
}
说明:添加@Data注解时,除了生成getter/setter外,还会生成包含所有属性的equals和hashCode方法,在对象属性较多(接近1000个左右)时会无法编译。一般而言仅需要Getter/Setter而不需要进行对象比较时,可以只添加@Getter/@Setter注解,而不是直接使用 @Data。
7.6 使用和参考IDE提示进行必要的简化和重构
说明:Intellij是一个非常智能的IDE,在源代码中,会给出诸多优化建议和提示,如使用灰色文字表示没有被用到的变量/参数,使用黄色背景给出一些优化建议等。鼠标停留在提示的部分,IDE会给出相应的说明,使用Alt+Enter会给出对应的修改建议(等价于鼠标点击浮出的灯泡按钮)。IDE提示的范围非常广,不一一展开称述,编写代码时务必参考IDE进行一些必要的优化,举例如下:
错误示例:
// 集合类已经自己扩充了forEach接口,无需转换为stream便可遍历
resultDtos.stream().forEach
// IDE 提示可以使用Lambda写法,省略临时形参
batchPersonList.forEach(batchList -> save(batchList));
// IDE 提示可以使用anyMatch写法
public static boolean isValidState(int flag) {
for (PaymentMethodEnum value : PaymentMethodEnum.values()) {
if (flag == value.getFlag()) {
return true;
}
}
return false;
}
说明:Intellij中还包含许多功能强大的插件,代码检查、统计、生成等插件等,不展开具体说明。
8 注释的使用
8.1 废弃的代码,直接删除,不要通过注释标记废弃
说明:用于测试的代码,直接放到测试类中写成单元测试用例,不要遗留在产品代码中,弃用的代码直接删除,不要以注释/标注的方式遗留在代码中
错误示例:
//本地测试放开
// public void doGet(HttpServletRequest req, HttpServletResponse resp) {
// logger.info(“----------local—test—start------------”);
// AutoRetentionDataService autoRetentionDataService = SpringContextUtil.getBean(AutoRetentionDataService.class);
// autoRetentionDataService.asyncExecute();
// logger.info(“----------local—test—end--------------”);
// }
8.2 减少过多不必要的注释,使用单元测试用例描述复杂代码的行为
存在于代码中的注释,初衷是对代码进行说明,但是一般的开发习惯中,对于编译错误,运行错误更为关注,而对于说明性的文档通常难以时时确保其正确和及时更新,注释极易腐化。因此应该将精力花在编写可执行,可校验的单元测试代码上,而非撰写过多说明性注释。
错误示例:
//1、法人实体为发薪公司
// 1.1、查看法人实体Legal Entity Code(companyCode)在配置列表里
// 1.2、满足1 则查看COA对应的组织属于国内
// 1.3、则Legal Entity Code(companyCode)作为发薪公司
if(isHasLegalEntity && isHasCoa){
result = companyCode;
//2、coa为发薪公司
// 2.1、查看法人实体Legal Entity Code不在配置列表里
// 2.2、则查看COA,COA作为发薪公司
}else if(!isHasLegalEntity){
result = orgCoa;
//3、不识别发薪公司
// 3.1、查看法人实体Legal Entity Code(companyCode)在配置列表里
// 3.2、COA对应的组织不属于国内
}else{
}
等价示例:
@Test
public void should_use_company_code_as_payment_company_when_coa_is_chinese_org() {
// 场景先关的上下文准备
assertEquals(“test_company_code”, extractedMethod(someCondition));
}
@Test
public void should_use_coa_as_payment_company_when_legal_entity_is_not_configured() {
// 场景先关的上下文准备
assertEquals(“test_coa”, extractedMethod(someCondition));
}
@Test
public void should_use_empty_payment_company_when_legal_entity_is_not_configured_and_coa_is_not_chinese() {
// 场景先关的上下文准备
assertNull(extractedMethod(someCondition));
}
8.3 禁止在产品代码中添加仅用于测试的main方法
测试代码应该和产品代码严格区分,禁止在产品代码中添加静态main方法用于测试。使用产品代码中的main方法测试,不容易自动化,也存在污染产品代码的风险,需要单元测试时应该将测试用例代码放在test目录下
错误示例:
public static void main() {
LocalDateTime start = LocalDateTime.of(2022,7,01,12,20,21);
LocalDateTime end = LocalDateTime.of(2022,8,29,12,20,21);
Date date1 = LocalDateTimeToDate(start);
Date date2 = LocalDateTimeToDate(end);
System.out.println(“工作日:” + getTotalWorkdaysNum(date1, date2));
}
相关文章:
JavaWeb之谈论项目编码规范_Java版
1. 关于DDD项目结构约定 1.1 项目结构使用DDD整洁架构进行分包 maven项目结构遵从DDD整洁架构分为如下四个顶级包: application - 应用层代码,一般为接口层定义API的实现类和一些结构转化,application不应该承载业务逻辑 domain - 领域层&a…...
Map排序
(一)treeHap 特点:treeMap中的元素根据键的大小自然排序(默认是升序) 1、treeHap遍历测试 import java.io.IOException; import java.util.*; public class Main {public static void main(String[] args)throws IOException {…...
mycat读写分离
1.准备工作 tar包 http://dl.mycat.org.cn/2.0/install-template/mycat2-install-template-1.20.zip jar包 http://dl.mycat.org.cn/2.0/1.21-release/ (下载最新的jar包) 将下载好的jar放到tar中的lib目录下并放入linux系统中 2.创建逻辑库 连接mycat端口8066 账号root 密码12…...

[Linux]环境变量
目录 基本概念 常见的环境变量 PATH测试 HOME测试 SHELL测试 和环境变量相关的命令 main函数的三个参数 环境变量的组织方式 通过代码如何获取环境变量 通过系统调用获取或设置环境变量 基本概念 环境变量(environment variables)一般是指在操作系统中用来指定操作系…...

次优二叉查找树(次优查找树)_递归和非递归实现_20230414
次优二叉查找树(次优查找树)-递归和非递归实现 前言 当有序表中的各记录的查找概率相等的时候,采用折半查找效率可以提升查找性能;如果有序表中的各记录的查找概率不相等,那么折半查找就不再适用。 如果只考虑查找成功的情况&a…...

贯穿设计模式第八话--设计原则总结篇
🥳🥳🥳 茫茫人海千千万万,感谢这一刻你看到了我的文章,感谢观赏,大家好呀,我是最爱吃鱼罐头,大家可以叫鱼罐头呦~🥳🥳🥳 从今天开始,将…...
地理信息系统(ArcGIS)在水文水资源、水环境中的实践技术应用及案例分析
目录 专题一 ArcGIS:数据管理 专题二 ArcGIS:数据转换 专题三 ArcGIS:地图制作 专题四 水文水环境数据编辑与管理 专题五 水文水环境数据处理与分析 专题六 ArcGIS水文分析及流域特征提取 专题七 湖泊水库水环境监测及评价 专题八 河…...

部分国产水文水动力模型介绍
一、HydroMPM模型 1、模型介绍 2016年度自立项目HydroMPM系统开发与集成完成的洪水分析模拟软件等成果经权威专家鉴定整体达到国际领先水平,HydroMPM_FloodRisk入选国家防总《全国重点地区洪水风险图编制项目可选软件名录》。成果应用项目100余项,累计…...
HTTP请求
1、get请求工具类 public static String requestGet(String url) throws Exception { String strResult null; try { HttpClient httpsClient HttpsClient.getInstance(); HttpGet request new HttpGet(url); HttpResponse response http…...

网络威胁情报项目:为什么仍然很疯狂
大约五年前,向首席信息安全官( CISO)询问他们的网络威胁情报 (CTI) 计划时,得到了两种截然不同的回答。 资源丰富的大型企业正在投资他们的威胁情报计划,目的是为了战术、运营和战略目的更好地实施它。 规模较小、资…...
Linux系统下使用shell“多线程执行命令”
前言 在工作中常遇到如下场景: 系统未接入日志中心,系统本身使用集群部署,那么再查找日志的时候只能一台一台的去搜索关键字,后来运维同学发现这样一台一台效率太低了,于是有了升级版,升级之后的方式还是一…...

HighTec编译器错误记录
目录 1、HighTec安装后缺少Universal Debug Engine 2、HighTec工程改名后不能跳转函数定义,提示找不到定义。 3、HighTec工程重复编译 1、HighTec安装后缺少Universal Debug Engine 在HighTec安装后,没有调试UDE,重装系统后还是没有&#x…...
智慧校园大数据云平台(3)
技术详解 OTN技术OTN是以波分复用技术为基础、 在光层组织网络的传送网, 是下一代的骨干传送网。OTN是通过G.872、G.709、G.798等一系列ITU-T的建议所规范的新一代“数字传送体系”和“光传送体系”,将解决传统WDM网络无波长/子波长业务调度能力差、组网…...

《花雕学AI》15:BingGPT桌面端——尝鲜体验ChatGPT4.0同源技术新Bing的最新成果
引言: 本文将介绍 BingGPT桌面端的开发背景和目的,以及它与新 Bing 的关系和区别。本文还将说明BingGPT桌面端的主要功能和特点,以及如何下载、安装和使用。最后,本文将评价 BingGPT桌面端对于新 Bing 的人工智能聊天功能的推广和…...

反序列化漏洞及PHP魔法函数
目录 1、漏洞原理 2、序列化(以PHP语言为例) 3、反序列化 4、PHP魔法函数 (1)__wakeup() (2)__destruct() (3)__construct() (4)__toString() &…...

企业应用程序单点登录
企业每天都依赖于各种企业应用程序,包括云和本地应用程序。这意味着用户必须经常输入更多密码才能访问这些应用程序并完成他们的工作。为了提高用户的工作效率、减少密码疲劳并使身份管理更有效,您的组织需要部署高效的 SSO 解决方案。 AD360 提供企业 …...
前馈PID控制(热交换器/反应釜温度控制)
如何利用PID进行温度控制请参看下面博客文章: 博途PID 1200/1500PLC PID_Compact比例作用权重b微分作用权重c解读(PI-D控制器 I-PD控制器)_RXXW_Dor的博客-CSDN博客很多人会问PLC自带的PID指令和我们自己设计的PID有什么区别,这个问题要看你和什么PID控制器作对比,PID负反…...

Nginx配置ssl证书实现https安全访问
目录 一、Nginx的安装与配置 安装步骤 二、SSL证书获取 三、Nginx配置 前题条件,拥有服务器与可以解析到该服务器的自己的域名。 一、Nginx的安装与配置 若已安装好了Nginx,则需查看自己的Nginx是否开启了SSL的模块功能: ./nginx -V 显…...

大学生必备神器
大学生要掌握的办公软件因专业和工作需求而异,但是以下是一些普遍适用于大学生的办公软件,可以帮助提高学习和工作效率,今天就给大家推荐几款大学生常用的软件。 1.OneDrive 这是微软出品的云存储产品,与百度网盘有些类似&#…...

【MyBatis Plus】004 -- MyBatis Plus高级(AR、MP插件、自定义全局操作、自动填充、逻辑删除、枚举、代码生成器)
目录 1、ActiveRecord 1.1 开启AR之旅(根据主键 id 进行查询) 1.2 新增数据 1.3 更新操作 1.4 删除操作 1.5 根据条件查询 2、Oracle 主键 Sequence 2.1 部署Oracle环境 2.2 创建表以及序列 2.3 jdbc驱动包 2.4 修改application.properties 2.5 配置序列…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...

K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...

黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 
视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...

分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...

python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...