第2468篇:AI辅助的文档生成——让代码注释和API文档自动化
2026/4/30大约 6 分钟
第2468篇:AI辅助的文档生成——让代码注释和API文档自动化
适读人群:Java工程师、技术负责人 | 阅读时长:约14分钟 | 核心价值:用LLM自动生成高质量的Javadoc和OpenAPI文档,解决文档永远落后于代码的问题
我问过很多工程师一个问题:"你们的API文档和代码是同步更新的吗?"
回答基本上都是苦笑。
文档不更新的原因不是工程师懒,而是文档的维护成本太高——每次改了代码,还要去找对应的文档位置,更新描述,更新参数,更新示例……在需求快速迭代的环境下,文档维护总是被排在最后,然后就永远落后了。
文档生成的两种思路
思路一:代码即文档。在代码里写好Javadoc,然后自动生成HTML/PDF文档。这是传统做法,但工程师不爱写注释,所以这条路走不通。
思路二:AI写文档。给AI看代码,让AI生成注释。这条路可行,而且现在LLM的代码理解能力足够完成这个任务。
但AI写的文档有个问题:准确性。AI可能生成听起来合理但实际不准确的描述。所以AI生成文档的定位是"草稿生成器",不是"自动发布器"——AI生成初稿,工程师确认,然后落库。
核心实现
1. Javadoc生成
@Service
public class JavadocGeneratorService {
private final ChatClient chatClient;
private final JavaParser javaParser;
/**
* 为指定Java文件生成/补全Javadoc
* 只为缺少文档或文档不完整的方法生成
*/
public JavadocResult generateForFile(Path filePath) {
String originalCode = Files.readString(filePath);
CompilationUnit cu = javaParser.parse(filePath).getResult().orElseThrow();
// 找出缺少Javadoc的方法
List<MethodDeclaration> undocumentedMethods = cu.findAll(MethodDeclaration.class).stream()
.filter(m -> !m.isPrivate()) // 只处理public/protected
.filter(m -> !hasAdequateJavadoc(m))
.collect(toList());
if (undocumentedMethods.isEmpty()) {
return JavadocResult.noChanges(filePath);
}
// 批量生成(每批最多10个方法,控制token用量)
List<GeneratedJavadoc> generatedDocs = new ArrayList<>();
for (List<MethodDeclaration> batch : partitionList(undocumentedMethods, 10)) {
List<GeneratedJavadoc> batchDocs = generateForMethods(batch, cu);
generatedDocs.addAll(batchDocs);
}
// 把生成的Javadoc插入源码
String updatedCode = insertJavadoc(originalCode, generatedDocs);
return JavadocResult.updated(filePath, originalCode, updatedCode, generatedDocs.size());
}
private boolean hasAdequateJavadoc(MethodDeclaration method) {
Optional<Javadoc> javadoc = method.getJavadoc();
if (javadoc.isEmpty()) return false;
String description = javadoc.get().getDescription().toText();
// 至少有非空的描述
return description.trim().length() > 10;
}
private List<GeneratedJavadoc> generateForMethods(
List<MethodDeclaration> methods,
CompilationUnit cu) {
// 构建批量生成的prompt
StringBuilder methodsContext = new StringBuilder();
for (int i = 0; i < methods.size(); i++) {
MethodDeclaration method = methods.get(i);
methodsContext.append(String.format("\n### 方法 %d: %s\n", i + 1, method.getNameAsString()));
methodsContext.append("```java\n").append(method.toString()).append("\n```\n");
}
String className = cu.findFirst(ClassOrInterfaceDeclaration.class)
.map(c -> c.getNameAsString()).orElse("Unknown");
String prompt = """
为以下Java类中的方法生成Javadoc注释。
类名: %s
%s
要求:
1. 描述要准确反映方法的实际功能,不要空泛
2. 参数描述要说明参数的含义和约束(比如不能为null、范围等)
3. 返回值描述要说明返回什么,以及可能的null情况
4. 异常描述要说明什么情况下抛出
5. 使用中文
返回JSON格式:
{
"javadocs": [
{
"methodName": "方法名",
"methodSignature": "方法签名(用于匹配)",
"javadoc": "完整的Javadoc注释(包含/**和*/)"
}
]
}
""".formatted(className, methodsContext);
ChatResponse response = chatClient.call(new Prompt(
new UserMessage(prompt),
OpenAiChatOptions.builder()
.withModel("gpt-4o")
.withTemperature(0.2f)
.withResponseFormat(new ResponseFormat(ResponseFormat.Type.JSON_OBJECT))
.build()
));
return parseGeneratedJavadocs(response.getResult().getOutput().getContent(), methods);
}
}2. OpenAPI文档生成
对于REST API,自动生成OpenAPI Spec:
@Service
public class OpenAPIDocumentGenerator {
private final ChatClient chatClient;
public OpenAPISpec generateFromController(Path controllerFile) {
String sourceCode = Files.readString(controllerFile);
// 先提取Controller的基本信息
ControllerInfo controllerInfo = extractControllerInfo(sourceCode);
// 用LLM生成详细的API文档
String apiDocJson = generateAPIDoc(controllerInfo, sourceCode);
return parseOpenAPISpec(apiDocJson);
}
private ControllerInfo extractControllerInfo(String sourceCode) {
CompilationUnit cu = StaticJavaParser.parse(sourceCode);
return cu.findFirst(ClassOrInterfaceDeclaration.class)
.map(clazz -> {
// 提取@RequestMapping路径
String basePath = clazz.getAnnotationByName("RequestMapping")
.map(a -> extractAnnotationValue(a, "value"))
.orElse("/");
// 提取所有端点方法
List<EndpointInfo> endpoints = clazz.findAll(MethodDeclaration.class).stream()
.filter(m -> hasHttpMethodAnnotation(m))
.map(this::extractEndpointInfo)
.collect(toList());
return ControllerInfo.of(clazz.getNameAsString(), basePath, endpoints);
})
.orElseThrow(() -> new RuntimeException("无法解析Controller"));
}
private String generateAPIDoc(ControllerInfo controllerInfo, String sourceCode) {
String prompt = """
为以下Spring Boot Controller生成OpenAPI 3.0规范的JSON文档:
Controller名称: %s
基础路径: %s
Controller源码:
```java
%s
```
生成要求:
1. 为每个端点生成完整的path item(operationId, summary, description, parameters, requestBody, responses)
2. 根据参数注解判断参数位置(@PathVariable->path, @RequestParam->query, @RequestBody->body)
3. 根据返回类型生成response schema
4. HTTP状态码要合理(200/201/400/404/500)
5. 描述要有实际意义,说明业务功能而不只是技术描述
返回完整的OpenAPI 3.0 JSON。
""".formatted(
controllerInfo.getName(),
controllerInfo.getBasePath(),
sourceCode
);
return chatClient.call(new Prompt(
new UserMessage(prompt),
OpenAiChatOptions.builder()
.withModel("gpt-4o")
.withTemperature(0.2f)
.build()
)).getResult().getOutput().getContent();
}
}3. 文档同步检测
代码改了但文档没更新是个大问题。实现一个检测器:
@Service
public class DocumentSyncChecker {
private final LLMDocumentAnalyzer llmAnalyzer;
/**
* 检测方法签名和实现与Javadoc是否匹配
* 用于在CI中自动检测文档过时的情况
*/
public List<DocumentSyncIssue> check(Path javaFile) {
CompilationUnit cu = StaticJavaParser.parse(javaFile);
List<DocumentSyncIssue> issues = new ArrayList<>();
cu.findAll(MethodDeclaration.class).stream()
.filter(m -> !m.isPrivate())
.filter(m -> m.getJavadoc().isPresent())
.forEach(method -> {
List<DocumentSyncIssue> methodIssues = checkMethodDoc(method);
issues.addAll(methodIssues);
});
return issues;
}
private List<DocumentSyncIssue> checkMethodDoc(MethodDeclaration method) {
Javadoc javadoc = method.getJavadoc().get();
List<DocumentSyncIssue> issues = new ArrayList<>();
// 检查1:Javadoc里描述的参数和实际参数是否匹配
Set<String> documentedParams = javadoc.getBlockTags().stream()
.filter(tag -> tag.getType() == JavadocBlockTag.Type.PARAM)
.map(tag -> tag.getName().orElse(""))
.collect(toSet());
Set<String> actualParams = method.getParameters().stream()
.map(p -> p.getNameAsString())
.collect(toSet());
// 找出文档多余的参数
documentedParams.stream()
.filter(p -> !actualParams.contains(p))
.forEach(p -> issues.add(DocumentSyncIssue.stalePar(method.getNameAsString(), p)));
// 找出缺少文档的参数
actualParams.stream()
.filter(p -> !documentedParams.contains(p))
.forEach(p -> issues.add(DocumentSyncIssue.missingParam(method.getNameAsString(), p)));
// 检查2:如果有@return但方法返回void,或者没有@return但方法有非void返回值
boolean hasReturnDoc = javadoc.getBlockTags().stream()
.anyMatch(tag -> tag.getType() == JavadocBlockTag.Type.RETURN);
boolean returnsVoid = method.getType().asString().equals("void");
if (hasReturnDoc && returnsVoid) {
issues.add(DocumentSyncIssue.staleReturn(method.getNameAsString()));
} else if (!hasReturnDoc && !returnsVoid) {
issues.add(DocumentSyncIssue.missingReturn(method.getNameAsString()));
}
return issues;
}
}4. 文档质量评估
@Service
public class DocumentQualityEvaluator {
private final ChatClient chatClient;
public DocumentQualityReport evaluate(String javadoc, String methodCode) {
String prompt = """
评估以下Javadoc的质量:
Javadoc:
%s
对应的方法代码:
```java
%s
```
评估维度:
1. 准确性:文档描述和代码实现是否一致?
2. 完整性:重要信息是否都记录了(参数约束、异常情况、副作用)?
3. 清晰度:一个新人看了文档能否理解如何使用这个方法?
4. 示例:是否有必要提供使用示例?
返回JSON:
{
"score": 1-10,
"issues": ["问题列表"],
"suggestions": ["改进建议"],
"improvedJavadoc": "改进后的Javadoc(如果需要改进)"
}
""".formatted(javadoc, methodCode);
ChatResponse response = chatClient.call(new Prompt(new UserMessage(prompt)));
return parseQualityReport(response.getResult().getOutput().getContent());
}
}工程集成
把文档生成集成到开发工作流:
// Gradle插件示例:在build时检查文档同步
class DocumentSyncPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.tasks.register("checkDocSync", DocumentSyncTask) {
description = "检查代码和文档是否同步"
group = "verification"
// 在check任务之前运行
project.tasks.check.dependsOn it
}
project.tasks.register("generateJavadoc", JavadocGenerationTask) {
description = "用AI为缺少文档的方法生成Javadoc草稿"
group = "documentation"
}
}
}我的使用体验
用了3个月的文档自动生成,最大的收获不是文档量增加了,而是文档的质量变稳定了。
人工写文档,质量波动很大:有些人写得很好,有些人写"此方法执行计算操作"这种废话文档。AI写的文档虽然有时候显得"机械",但至少不会写废话,基本的参数描述、返回值描述、异常情况都会覆盖到。
关键是:AI生成的是草稿,工程师负责确认和补充业务语义。这个分工是合理的。
