JetBrains AI 打零工(五)——重构技巧使用与代码可读性

遗留系统的痛点在于可维护性差,可维护性差除了没有测试网覆盖外就是可读性差,提升代码可读性可以有效降低工程复杂度,重构就是提升代码可读性的最佳实践。从很难看得懂,到可以看得懂,再到可以改得动,能改得动的代码才有维护价值。结合 Junie,重构变得更加简单,下面介绍两种简单而实用的重构技巧。

1、长函数拆分

面向对象语言使用最可怕的是没有面向对象思维,业务流程什么样代码就是什么样,也就是「面条代码」。这些动辄上千行的方法一般是很难做修改的,因为没有封装所以必须深入细节才能完全理解代码背后的含义,因为没有抽象大部分相同而又有细小差别的需求就变成了大段的代码拷贝,久而久之就变成了「祖传代码」。

要么投入资源修复,要么推倒重建,因为没有足够的代码设计,同样的问题很快又会重现。当然核心还是需要开发者文化的构建以及程序员工程能力的提升,但大模型在技术上提供的支持同样有助于代码治理。经过验证,长函数拆分是 Junie 非常擅长的一件事。让一个「大泥球」变成多个有结构的小代码块。

给 Junie 一个明确的重构指令,比如长函数拆分、表意接口声明、以及需要治理的坏味道:

重构选中的方法,返回结果使用对象而不是 json 字符串或 map,拆分该函数提升代码可读性

这种方法在面对冗长的函数很奏效,Junie 将长函数拆分成多个子函数,并且按照业务聚合度标记序号,如果子函数仍然很冗长就会继续拆分。从图中可以看出面对一个流程引擎处理函数,Junie 自动将处理步骤进行拆分并标记序号,代码可读性相对原来有的大幅提升。

对于大模型而言,函数拆分的好处也是显而易见的,可以有效控制大模型修改代码的范围,避免将一些正常的功能破坏。

如果使用 AI Assistant 也可以通过 Edit 模式完成长函数拆分,并且可以将提示词纳入到快捷处理中,方便再次调用。

右键或者添加快捷键可以快速唤醒提示词,不需要重复对话声明。

选择提示词作用范围,是一行代码、一个函数还是整个文件。

代码可读性对应着认知负载,如果一段代码过于复杂(往往也很长)意味着包含太多功能点,关注点分离是有效的应对方法,直观来看就是代码块变小了。当然,也可以直接让 AI Actions 的 Suggest Refactoring 来帮助分析并给出重构意见。

参数校验关注点分离

函数拆分背后的设计原则是关注点分离,保持代码职责单一,从而降低理解复杂度。这种设计思想贯穿 Java 编程语言中,Spring Boot 就有很多这样的设计,比如参数校验。

参数校验具备一定的通用价值,比如在 REST API 中对请求的校验,参数范围、参数格式等都会有要求。又往往需要与业务相结合,比如说解析请求参数联合服务校验请求合法性。这种项目内既有一定通用性又要与业务耦合的代码就适合抽离出来单独处理,减少业务实现复杂度,做到关注点分离。

举个例子,对流程引擎创建服务添加参数校验:

createTemplate添加请求参数校验: 标题名称1-20个字符,文本表单反馈说明最大50字符,文本反馈说明默认最大500字符并支持按照外部化配置调整最大3000字符,选项表单选项名称最大20字符,附加表单最多10个附件并且单个文件最大20M

对于不同的参数校验异常需要返回不同的结果说明,因此这部分代码不适合使用切面或者 ControllerAdvice 处理的。Junie 从已有代码中也分析出了这一点,于是给出了 Bean Validation 和 BindingResult 结合的方法。

简单的参数校验(非空、长度)直接通过 Bean Validation 完成,较复杂的参数校验(结合 mapper 判断值对象是否存在)抽取到 Validator 中实现,异常结果放在 Errors 里在 Controller 返回。从而实现参数校验与业务实现的关注点分离。

最后,让 Junie 总结下实现思路,添加到 Guidelines 中。

java 复制代码
## 参数校验最佳实践

### 参数校验模式

项目中采用了分离参数校验与业务逻辑的模式,主要通过以下组件实现:

1. **请求DTO类**: 使用标准Bean Validation注解(如@NotNull, @Size)进行基本约束验证
2. **自定义验证器**: 实现Spring的Validator接口,处理复杂的业务规则验证
3. **配置类**: 外部化验证规则,使其可配置
4. **控制器**: 使用@Valid注解和BindingResult处理验证结果
5. **统一响应**: 使用ResultBean封装错误信息返回给客户端

### 实现示例

以工单模板创建功能为例,其参数校验实现如下:

#### 1. 请求DTO (CreateTemplateRequest)

```java
@Data
public class CreateTemplateRequest {
    @NotNull(message = "模板标题不能为空")
    @Size(min = 1, max = 20, message = "模板标题长度必须在1-20个字符之间")
    private String templateTitle;

    @NotNull(message = "模板表单项不能为空")
    @Valid
    private List<TemplateFormFieldDTO> templateForm;

    // 嵌套类也使用验证注解
    @Data
    public static class TemplateFormFieldDTO {
        @NotNull(message = "表单项名称不能为空")
        @Size(max = 20, message = "表单项名称最大20个字符")
        private String name;
        // ...
    }
}
```

#### 2. 自定义验证器 (TemplateRequestValidator)

```java
@Component
public class TemplateRequestValidator implements Validator {
    @Autowired
    private TemplateValidationConfig validationConfig;

    @Autowired
    private TicketTemplateBaseMapper templateBaseMapper;

    @Override
    public boolean supports(Class<?> clazz) {
        return CreateTemplateRequest.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        CreateTemplateRequest request = (CreateTemplateRequest) target;
        // 验证业务规则
        validateFormFields(request.getTemplateForm(), request.getTemplateTitle(), errors);
    }

    private void validateFormFields(List<TemplateFormFieldDTO> formFields, String templateTitle, Errors errors) {
        // 检查是否存在相同名称的模板
        LambdaQueryWrapper<TicketTemplateBase> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TicketTemplateBase::getTitle, templateTitle);
        Integer count = templateBaseMapper.selectCount(queryWrapper);
        if (count > 0) {
            errors.rejectValue("templateTitle", "templateTitle.exists", 
                    "模板名称已存在,无法再次创建");
            return;
        }

        // 其他业务规则验证...
    }
}
```

#### 3. 配置类 (TemplateValidationConfig)

```java
@Configuration
public class TemplateValidationConfig {
    @Value("${template.validation.maxTextFeedbackLength:50}")
    private int maxTextFeedbackLength;

    public int getMaxTextFeedbackLength() {
        return maxTextFeedbackLength;
    }
}
```

#### 4. 控制器 (TicketTemplateController)

```java
@RestController
@RequestMapping("/template")
public class TemplateController {
    @Autowired
    private TemplateRequestValidator templateRequestValidator;

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        if (binder.getTarget() != null && binder.getTarget() instanceof CreateTemplateRequest) {
            binder.addValidators(templateRequestValidator);
        }
    }

    @PostMapping("/create")
    public ResultBean createTemplate(@RequestBody @Valid CreateTemplateRequest request, BindingResult bindingResult) {
        // 处理验证错误
        if (bindingResult.hasErrors()) {
            String errorMsg = bindingResult.getAllErrors().get(0).getDefaultMessage();
            return ResultBean.fail(errorMsg);
        }

        // 验证通过,执行业务逻辑
        boolean result = ticketTemplateService.createTemplate(request);
        return ResultBean.success(result, "创建成功");
    }
}
```

### 参数校验最佳实践

1. **分离关注点**: 将参数校验与业务逻辑分离,使代码更清晰、更易维护
2. **分层验证**: 
   - 使用Bean Validation注解进行基本约束验证
   - 使用自定义验证器处理复杂业务规则验证
3. **明确错误信息**: 为每个验证规则提供清晰、具体的错误消息
4. **配置化验证规则**: 将验证规则参数外部化,便于调整
5. **统一错误处理**: 使用统一的响应格式返回验证错误
6. **级联验证**: 使用@Valid注解验证嵌套对象
7. **早期验证**: 在控制器层进行验证,避免无效请求进入业务逻辑层
相关推荐
Spider_Man6 小时前
《斗破·脑力焚天》开发全流程分享——用AI一键生成的游戏项目
ai编程·trae·vibecoding
curdcv_po6 小时前
(っ´▽`)っ,嘤嘤嘤,读书小精灵MCP🧚🏻‍♀️提醒你读书了哦~
ai编程·mcp·trae
lgldl7 小时前
AI自动生成复杂架构图,流程图,思维导图方案
aigc·ai编程
量子位7 小时前
我在 618 主场,和 3 位顶尖技术博士聊了聊
ai编程
盏灯8 小时前
🐍 人人都是AI码农——游戏开发,全解析 | HTML+CSS+JS三件套
ai编程·mcp·trae
晓极客解码AI算法8 小时前
Windsurf :AI编程中 Agent 模式的先驱,有什么新东西?
ai编程
子昕9 小时前
解放双手!Cursor一键接管Chrome的MCP神器
ai编程
Goboy11 小时前
Trae 设计电子签名工具,让每一个签名,都能体现你的风格
ai编程·trae
Captaincc13 小时前
Claude Code 如何在无索引机制下实现高效实时代码检索?
ai编程·claude