代码自动文档生成——让 AI 补上项目里那些缺失的注释
代码自动文档生成——让 AI 补上项目里那些缺失的注释
适读人群:有遗留代码维护需求的工程师 | 阅读时长:约15分钟 | 核心价值:批量代码文档生成的工程实践,有脚本,有真实效果数据
我接过一个代码审计项目,甲方给了我一个代码仓库的访问权限。
仓库里有大概8万行Java代码,是一个运行了7年的ERP系统核心模块。代码写于2017年,原始开发团队早就散了,注释率不到5%,没有任何API文档,只有一些已经过时的Word格式设计文档。
我的任务是:评估这套代码的可维护性,给出改造建议。
看了两天,我彻底放弃手工理解这个代码的想法——光一个订单处理的核心类就有3000行,方法之间的调用关系乱得像意大利面。
于是我决定:先让AI把文档补上,再基于有注释的代码做分析。
这篇就是那次实验的完整记录:怎么做,效果如何,哪些代码AI文档生成质量好,哪些不好。
自动文档生成的核心思路
给代码自动生成文档,不是让AI猜代码的意图,而是让AI读懂代码然后用自然语言描述出来。
这件事AI确实能做,但有几个前提:
- 代码质量不能太差:如果连变量名都是
a、b、tmp1,AI也解读不了 - 函数要有合理的边界:一个函数做了五件事的,文档会很乱
- 上下文要完整:孤立的一个函数,AI很难理解其业务含义;看到调用它的地方,才能真正理解它做什么
我的批处理策略:不是逐函数孤立处理,而是带上类的全貌来处理每个函数。
批量处理脚本
以Java代码为例,完整的批处理脚本:
#!/usr/bin/env python3
"""
代码自动文档生成脚本
用法: python doc_generator.py --source /path/to/java --output /path/to/output
"""
import os
import re
import json
import time
import argparse
from pathlib import Path
from typing import Optional
from openai import OpenAI
# 使用DeepSeek API(成本低,代码理解能力够)
client = OpenAI(
api_key=os.environ.get("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com"
)
SYSTEM_PROMPT = """你是一名资深Java工程师,专门负责代码文档编写。
你的任务是为给定的Java代码添加标准的Javadoc注释。
要求:
1. 为每个public方法、public字段添加Javadoc注释
2. 注释必须描述方法的实际行为,而不是重复方法名
3. 标注所有@param、@return、@throws
4. 如果方法有明显的业务含义,在注释中体现业务背景
5. 不要修改任何代码逻辑,只添加注释
6. 保持原始代码格式(缩进、换行)不变"""
def extract_java_classes(filepath: str) -> list[dict]:
"""
从Java文件中提取类信息(类名、方法列表、字段列表)
返回便于处理的结构化信息
"""
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# 提取类名
class_match = re.search(r'(?:public\s+)?(?:class|interface|enum)\s+(\w+)', content)
class_name = class_match.group(1) if class_match else "Unknown"
# 统计现有注释
existing_comments = len(re.findall(r'/\*\*', content))
total_methods = len(re.findall(r'(?:public|protected|private)\s+\w+\s+\w+\s*\(', content))
return [{
"class_name": class_name,
"filepath": filepath,
"content": content,
"existing_comment_ratio": existing_comments / max(total_methods, 1),
"total_methods": total_methods
}]
def generate_docs_for_file(file_info: dict, max_tokens: int = 4000) -> Optional[str]:
"""
为单个文件生成文档注释
对于超长文件,分块处理
"""
content = file_info["content"]
# 超过6000行的文件,需要分块处理
if len(content) > 12000:
return generate_docs_chunked(file_info)
prompt = (
f"请为以下Java代码添加完整的Javadoc注释。\n\n"
f"类名:{file_info['class_name']}\n"
f"当前注释覆盖率:约{file_info['existing_comment_ratio']*100:.0f}%\n\n"
f"代码:\n```java\n{content}\n```\n\n"
f"重要:直接输出添加了注释的完整代码,不要有任何解释性文字。"
)
try:
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": prompt}
],
max_tokens=8000,
temperature=0.1 # 低temperature,保证输出稳定
)
result = response.choices[0].message.content
# 清理可能的代码块标记
result = re.sub(r'^```java\n', '', result, flags=re.MULTILINE)
result = re.sub(r'\n```$', '', result, flags=re.MULTILINE)
return result.strip()
except Exception as e:
print(f"处理 {file_info['class_name']} 失败: {e}")
return None
def generate_docs_chunked(file_info: dict) -> Optional[str]:
"""
对超长文件按方法分块处理
策略:提取每个方法,单独生成注释,然后合并回去
"""
content = file_info["content"]
# 简化版:按方法分割(实际项目建议用javalang库做AST解析)
method_pattern = re.compile(
r'((?:(?:public|protected|private|static|final|synchronized)\s+)*'
r'(?:\w+\s+)*\w+\s*\([^)]*\)\s*(?:throws\s+[\w,\s]+)?\s*\{)',
re.MULTILINE
)
# 这里简化处理:对大文件提取类信息,然后分批发送
chunks = [content[i:i+8000] for i in range(0, len(content), 7000)]
results = []
for i, chunk in enumerate(chunks):
prompt = (
f"这是一个大型Java类的第{i+1}/{len(chunks)}部分,请为其中的方法添加Javadoc注释:\n\n"
f"```java\n{chunk}\n```\n\n"
"直接输出添加了注释的代码,不要有任何说明。"
)
try:
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": prompt}
],
max_tokens=6000,
temperature=0.1
)
result = response.choices[0].message.content
result = re.sub(r'^```java\n?', '', result)
result = re.sub(r'\n?```$', '', result)
results.append(result.strip())
time.sleep(0.5) # 避免触发速率限制
except Exception as e:
print(f" 分块 {i+1} 处理失败: {e}")
results.append(chunk) # 失败时保留原始代码
return '\n\n'.join(results)
def process_directory(source_dir: str, output_dir: str, skip_ratio: float = 0.7):
"""
批量处理目录下所有Java文件
skip_ratio: 已有注释比例超过此值的文件跳过处理
"""
source_path = Path(source_dir)
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
java_files = list(source_path.rglob("*.java"))
print(f"找到 {len(java_files)} 个Java文件")
stats = {"processed": 0, "skipped": 0, "failed": 0}
for i, filepath in enumerate(java_files):
print(f"\n[{i+1}/{len(java_files)}] 处理: {filepath.name}")
file_infos = extract_java_classes(str(filepath))
file_info = file_infos[0]
# 跳过已有足够注释的文件
if file_info["existing_comment_ratio"] >= skip_ratio:
print(f" 跳过(现有注释率 {file_info['existing_comment_ratio']*100:.0f}% >= {skip_ratio*100:.0f}%)")
stats["skipped"] += 1
continue
print(f" 方法数: {file_info['total_methods']}, 注释率: {file_info['existing_comment_ratio']*100:.0f}%")
# 生成文档
documented_code = generate_docs_for_file(file_info)
if documented_code:
# 保持原始目录结构
relative_path = filepath.relative_to(source_path)
output_file = output_path / relative_path
output_file.parent.mkdir(parents=True, exist_ok=True)
output_file.write_text(documented_code, encoding='utf-8')
print(f" 完成 -> {output_file}")
stats["processed"] += 1
else:
stats["failed"] += 1
# 每处理10个文件稍作停顿,避免API限流
if (i + 1) % 10 == 0:
time.sleep(2)
print(f"\n处理完成: 处理{stats['processed']}个, 跳过{stats['skipped']}个, 失败{stats['failed']}个")
return stats
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Java代码文档自动生成")
parser.add_argument("--source", required=True, help="Java源码目录")
parser.add_argument("--output", required=True, help="输出目录")
parser.add_argument("--skip-ratio", type=float, default=0.7,
help="跳过已注释率超过此值的文件(默认0.7)")
args = parser.parse_args()
process_directory(args.source, args.output, args.skip_ratio)真实效果:哪些代码质量好,哪些不好
处理了那个ERP系统的核心模块(约300个Java类),我仔细检查了生成的文档,总结出几个规律:
AI生成文档质量好的代码特征:
1. 方法名和参数名具有语义
// AI生成的文档质量高的例子
public OrderStatus calculateOrderStatus(Order order, List<Payment> payments)
// AI能生成这样的文档:
/**
* 根据订单信息和支付记录计算当前订单状态。
* 当所有应付款项均已完成支付时,返回PAID;
* 存在部分支付时返回PARTIALLY_PAID;
* 超过支付截止日期未全额支付时返回OVERDUE。
*
* @param order 待计算状态的订单对象
* @param payments 与该订单关联的所有支付记录
* @return 计算得出的订单状态枚举值
* @throws IllegalArgumentException 当order为null时
*/2. 代码逻辑清晰,没有魔法数字
有业务含义的常量比硬编码数字更容易让AI理解:MAX_RETRY_COUNT = 3 比直接写 3 让AI明白这是重试逻辑。
AI生成文档质量差的情况:
1. 方法名语义不清
// 这种代码AI真的帮不了你
public void process(String s, int n, boolean b) {
// ...200行代码
}
// AI只能生成这种废话注释:
/**
* 处理方法。
* @param s 字符串参数
* @param n 整数参数
* @param b 布尔参数
*/2. 复杂的业务规则耦合
有个方法叫recalculatePrice,里面有6个if条件,涉及VIP等级、季节折扣、库存系数、历史购买量……AI能描述每个if分支在做什么,但没有办法给出这个价格计算逻辑的业务背景(为什么要这样算)。
3. 跨文件的业务流程
单个方法调用了5个service的8个方法,AI能描述调用链,但理解不了这个流程在业务上代表什么(比如"这是采购订单的审批流程")。这种业务背景必须靠人来补充。
我的评估结果
在那个8万行ERP代码库上,自动文档生成的实际效果:
| 指标 | 数据 |
|---|---|
| 处理的文件数 | 287个Java类 |
| 文档生成耗时 | 约6小时(DeepSeek API,约8元费用) |
| 生成文档的可用率(不需要人工修改直接可用) | 61% |
| 需要少量修改 | 24% |
| 需要大幅修改或重写 | 15% |
| 整体注释覆盖率从~5%提升到 | ~78% |
6小时、8元,把一个8万行代码库的注释覆盖率从5%提升到78%。这如果靠人工做,保守估计要3个工程师工作2个月。
代价是:生成的文档里有15%质量不可用,需要进一步处理。在一个代码理解任务里,低质量注释比没注释更危险——错误的注释会误导后续的维护者。
所以我额外做了一个验证步骤:对生成的注释做抽样人工审查,识别出明显错误的,打上// FIXME: AI-generated comment, needs review标记,让后续维护者知道这个注释的可信度。
Python代码的效果更好
同样的脚本我也在一个Python数据处理项目上跑了,生成Google Style Docstring:
PYTHON_SYSTEM_PROMPT = """你是一名Python专家,负责为代码添加Google Style的docstring注释。
格式要求:
def function_name(param1, param2):
\"\"\"一句话概括函数功能。
详细说明(如果需要)。
Args:
param1 (type): 参数说明
param2 (type): 参数说明
Returns:
type: 返回值说明
Raises:
ExceptionType: 触发条件说明
Examples:
>>> function_name(1, 2)
3
\"\"\"
"""Python代码文档生成的效果比Java好20%左右。原因:Python代码风格更统一,命名规范普遍更好,而且Python函数通常比Java方法更短、职责更单一。
一个省时间的小技巧
如果你只想快速给代码仓库加文档,不需要跑批处理脚本,可以用这个简单的命令行用法:
# 把一个Python文件的内容直接用API生成文档版本
python -c "
import anthropic, sys
client = anthropic.Anthropic()
code = open(sys.argv[1]).read()
resp = client.messages.create(
model='claude-3-5-sonnet-20241022',
max_tokens=4096,
messages=[{
'role': 'user',
'content': f'请为以下Python代码添加完整的docstring注释,只输出添加了注释的代码:\n\n{code}'
}]
)
print(resp.content[0].text)
" mymodule.py > mymodule_documented.py一行命令,处理单个文件。适合临时处理某个特别重要的模块。
代码文档自动生成是我做过的投入产出比最高的AI应用之一。不是因为AI生成的每一条注释都完美,而是它把一件"重要但总是被推后"的事情,从"可能要几个月"变成了"今天能完成"。完成了80%,剩下20%的人工补充,总比0%的注释要强得多。
