如何重构多if语句以降低认知复杂度(SonarQube合规)
重构SonarQube认知复杂度超标的多if校验代码
问题背景
现有一段校验代码因大量重复if判断导致SonarQube认知复杂度超标,代码通过多个if判断body对象的各字段是否非空,若非空则调用rejectValueIf()方法校验字段大小是否超过配置的批量搜索上限。原代码示例如下:
if (body.getCprNumber() != null) rejectValueIf( body.getCprNumber().size() > contactIndexConfigurationProperties.getBulkSearchLimit(), errors, ValidationConstants.Fields.CPR_NUMBER, ValidationConstants.ErrorCodes.NUMBER_OF_SEARCHES_NOT_ALLOWED); if (body.getCvrNumber() != null) rejectValueIf( body.getCvrNumber().size() > contactIndexConfigurationProperties.getBulkSearchLimit(), errors, ValidationConstants.Fields.CVR_NUMBER, ValidationConstants.ErrorCodes.NUMBER_OF_SEARCHES_NOT_ALLOWED); if (body.getId() != null) rejectValueIf( body.getId().size() > contactIndexConfigurationProperties.getBulkSearchLimit(), errors, ValidationConstants.Fields.ID, ValidationConstants.ErrorCodes.NUMBER_OF_SEARCHES_NOT_ALLOWED); if (body.getSize() != null) rejectValueIf( body.getSize() > contactIndexConfigurationProperties.getBulkSearchLimit(), errors, ValidationConstants.Fields.SIZE, ValidationConstants.ErrorCodes.NUMBER_OF_SEARCHES_NOT_ALLOWED); if (body.getNext() != null) rejectValueIf( body.getNext().length() > contactIndexConfigurationProperties.getBulkSearchLimit(), errors, ValidationConstants.Fields.NEXT, ValidationConstants.ErrorCodes.NUMBER_OF_SEARCHES_NOT_ALLOWED); if (body.getPage() != null) rejectValueIf( body.getPage() > contactIndexConfigurationProperties.getBulkSearchLimit(), errors, ValidationConstants.Fields.PAGE, ValidationConstants.ErrorCodes.NUMBER_OF_SEARCHES_NOT_ALLOWED); if (body.getSortFields() != null) rejectValueIf( body.getSortFields().size() > contactIndexConfigurationProperties.getBulkSearchLimit(), errors, ValidationConstants.Fields.SORT_FIELDS, ValidationConstants.ErrorCodes.NUMBER_OF_SEARCHES_NOT_ALLOWED); if (body.getFields() != null) rejectValueIf( body.getFields().size() > contactIndexConfigurationProperties.getBulkSearchLimit(), errors, ValidationConstants.Fields.FIELDS_CONSTANT, ValidationConstants.ErrorCodes.NUMBER_OF_SEARCHES_NOT_ALLOWED); if (body.getSearchParameters() != null) rejectValueIf( body.getSearchParameters().size() > contactIndexConfigurationProperties.getBulkSearchLimit(), errors, ValidationConstants.Fields.SEARCH_PARAMETERS, ValidationConstants.ErrorCodes.NUMBER_OF_SEARCHES_NOT_ALLOWED); if (body.getLimitSearchToFields() != null) rejectValueIf( body.getLimitSearchToFields().size() > contactIndexConfigurationProperties.getBulkSearchLimit(), errors, ValidationConstants.Fields.LIMIT_SEARCH_TO_FIELDS, ValidationConstants.ErrorCodes.NUMBER_OF_SEARCHES_NOT_ALLOWED); if (body.getMaxSize() != null) rejectValueIf( body.getMaxSize() > contactIndexConfigurationProperties.getBulkSearchLimit(), errors, ValidationConstants.Fields.MAX_SIZE, ValidationConstants.ErrorCodes.NUMBER_OF_SEARCHES_NOT_ALLOWED); if (body.getDefaultSize() != null) rejectValueIf( body.getDefaultSize() > contactIndexConfigurationProperties.getBulkSearchLimit(), errors, ValidationConstants.Fields.DEFAULT_SIZE, ValidationConstants.ErrorCodes.NUMBER_OF_SEARCHES_NOT_ALLOWED);
最优重构方案
方案1:字段校验元数据列表+统一遍历处理
通过封装字段的获取逻辑、大小计算逻辑和字段名称到元数据对象,再统一遍历处理,彻底消除重复if,大幅降低认知复杂度。
步骤1:定义校验元数据记录(Java 16+)
// 用记录封装每个字段的校验元数据 private record FieldValidator<T>( Supplier<T> valueSupplier, // 字段值获取器 Function<T, Integer> sizeExtractor, // 字段大小计算逻辑 String fieldName // 校验错误对应的字段名 ) {}
步骤2:构建校验元数据列表
// 批量创建所有字段的校验元数据 List<FieldValidator<?>> validators = List.of( new FieldValidator<>(body::getCprNumber, Collection::size, ValidationConstants.Fields.CPR_NUMBER), new FieldValidator<>(body::getCvrNumber, Collection::size, ValidationConstants.Fields.CVR_NUMBER), new FieldValidator<>(body::getId, Collection::size, ValidationConstants.Fields.ID), new FieldValidator<>(body::getSize, size -> size, ValidationConstants.Fields.SIZE), new FieldValidator<>(body::getNext, String::length, ValidationConstants.Fields.NEXT), new FieldValidator<>(body::getPage, page -> page, ValidationConstants.Fields.PAGE), new FieldValidator<>(body::getSortFields, Collection::size, ValidationConstants.Fields.SORT_FIELDS), new FieldValidator<>(body::getFields, Collection::size, ValidationConstants.Fields.FIELDS_CONSTANT), new FieldValidator<>(body::getSearchParameters, Collection::size, ValidationConstants.Fields.SEARCH_PARAMETERS), new FieldValidator<>(body::getLimitSearchToFields, Collection::size, ValidationConstants.Fields.LIMIT_SEARCH_TO_FIELDS), new FieldValidator<>(body::getMaxSize, maxSize -> maxSize, ValidationConstants.Fields.MAX_SIZE), new FieldValidator<>(body::getDefaultSize, defaultSize -> defaultSize, ValidationConstants.Fields.DEFAULT_SIZE) );
步骤3:统一遍历处理校验
// 批量执行所有字段的校验逻辑 int bulkSearchLimit = contactIndexConfigurationProperties.getBulkSearchLimit(); validators.forEach(validator -> { Object value = validator.valueSupplier().get(); if (value != null) { int size = validator.sizeExtractor().apply(value); rejectValueIf( size > bulkSearchLimit, errors, validator.fieldName(), ValidationConstants.ErrorCodes.NUMBER_OF_SEARCHES_NOT_ALLOWED ); } });
方案2:利用Spring Validation自定义注解(Spring项目专属)
如果是Spring环境,通过自定义校验注解和校验器,将校验逻辑从业务代码中剥离,实现声明式校验,代码更简洁易维护。
步骤1:定义自定义校验注解
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = MaxAllowedSizeValidator.class) public @interface MaxAllowedSize { String message() default ValidationConstants.ErrorCodes.NUMBER_OF_SEARCHES_NOT_ALLOWED; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
步骤2:实现校验器逻辑
public class MaxAllowedSizeValidator implements ConstraintValidator<MaxAllowedSize, Object> { @Autowired private ContactIndexConfigurationProperties config; @Override public boolean isValid(Object value, ConstraintValidatorContext context) { if (value == null) { return true; // 空值不校验,对应原逻辑 } int size = calculateSize(value); return size <= config.getBulkSearchLimit(); } // 统一处理不同类型的大小计算 private int calculateSize(Object value) { if (value instanceof Collection) { return ((Collection<?>) value).size(); } else if (value instanceof String) { return ((String) value).length(); } else if (value instanceof Number) { return ((Number) value).intValue(); } throw new IllegalArgumentException("不支持的字段类型:" + value.getClass().getName()); } }
步骤3:在Body类字段上标注注解
public class Body { @MaxAllowedSize private List<String> cprNumber; @MaxAllowedSize private List<String> cvrNumber; @MaxAllowedSize private List<String> id; @MaxAllowedSize private Integer size; @MaxAllowedSize private String next; @MaxAllowedSize private Integer page; @MaxAllowedSize private List<String> sortFields; @MaxAllowedSize private List<String> fields; @MaxAllowedSize private Map<String, String> searchParameters; @MaxAllowedSize private List<String> limitSearchToFields; @MaxAllowedSize private Integer maxSize; @MaxAllowedSize private Integer defaultSize; // getter/setter 省略 }
步骤4:在Controller中启用校验
@PostMapping("/your-endpoint") public ResponseEntity<?> yourMethod(@Valid @RequestBody Body body, Errors errors) { if (errors.hasErrors()) { // 处理错误逻辑 } // 业务逻辑 return ResponseEntity.ok().build(); }
方案3:提取重复逻辑到工具方法(快速简化)
如果不想做大规模重构,可以将重复的校验逻辑提取到工具方法,减少代码重复,同时降低认知复杂度。
步骤1:定义工具方法
private <T> void validateField(Supplier<T> valueSupplier, Function<T, Integer> sizeExtractor, String fieldName) { T value = valueSupplier.get(); if (value != null) { int size = sizeExtractor.apply(value); rejectValueIf( size > contactIndexConfigurationProperties.getBulkSearchLimit(), errors, fieldName, ValidationConstants.ErrorCodes.NUMBER_OF_SEARCHES_NOT_ALLOWED ); } }
步骤2:调用工具方法
validateField(body::getCprNumber, Collection::size, ValidationConstants.Fields.CPR_NUMBER); validateField(body::getCvrNumber, Collection::size, ValidationConstants.Fields.CVR_NUMBER); validateField(body::getId, Collection::size, ValidationConstants.Fields.ID); validateField(body::getSize, size -> size, ValidationConstants.Fields.SIZE); validateField(body::getNext, String::length, ValidationConstants.Fields.NEXT); validateField(body::getPage, page -> page, ValidationConstants.Fields.PAGE); validateField(body::getSortFields, Collection::size, ValidationConstants.Fields.SORT_FIELDS); validateField(body::getFields, Collection::size, ValidationConstants.Fields.FIELDS_CONSTANT); validateField(body::getSearchParameters, Collection::size, ValidationConstants.Fields.SEARCH_PARAMETERS); validateField(body::getLimitSearchToFields, Collection::size, ValidationConstants.Fields.LIMIT_SEARCH_TO_FIELDS); validateField(body::getMaxSize, maxSize -> maxSize, ValidationConstants.Fields.MAX_SIZE); validateField(body::getDefaultSize, defaultSize -> defaultSize, ValidationConstants.Fields.DEFAULT_SIZE);
内容的提问来源于stack exchange,提问作者FilipA
相关产品推荐
相关产品推荐

