构建一个AI代码审查助手
创建一个智能代码审查辅助工具,用于分析Go代码中的错误、风格问题和性能改进。
你将要构建的内容
一个命令行工具:
- 分析Go源文件中可能存在的问题
- 提出改进建议和最佳实践
- 集成到git以审查更改的文件
- 提供其建议的解释说明
先决条件
- Go 1.21+
- OpenAI或Anthropic API密钥
- Git已安装
步骤1:项目设置
mkdir ai-code-reviewer
cd ai-code-reviewer
go mod init code-reviewer
go get github.com/tmc/langchaingo
请务必保留所有格式符号和代码块。
第2步:核心评审结构
创建 main.go
:
package main
import (
"context"
"flag"
"fmt"
"go/ast"
"go/parser"
"go/token"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/tmc/langchaingo/llms"
"github.com/tmc/langchaingo/llms/openai"
"github.com/tmc/langchaingo/prompts"
)
type CodeReviewer struct {
llm llms.Model
template *prompts.PromptTemplate
}
func NewCodeReviewer() (*CodeReviewer, error) {
llm, err := openai.New()
if err != nil {
return nil, err
}
template := prompts.NewPromptTemplate(`
您是一位Go代码评审专家。请分析以下Go代码:
1. **错误和逻辑问题**:潜在的运行时错误、空指针解引用、竞态条件
2. **性能**:低效算法、不必要的分配、字符串连接问题
3. **风格**:Go惯用法、命名约定、错误处理模式
4. **安全**:输入验证、敏感数据处理
代码评审:
'''go
{{.code}}
'''
文件名: {{.filename}}
请提供具体的、可操作的反馈。对于每个问题:
- 解释为什么这是一个问题
- 展示如何通过代码示例进行修复
- 评估严重性:关键,警告,建议
首先关注最重要的问题。`,
[]string{"code", "filename"})
return &CodeReviewer{
llm: llm,
template: &template,
}, nil
}
func (cr *CodeReviewer) ReviewFile(filename string) error {
content, err := os.ReadFile(filename)
if err != nil {
return fmt.Errorf("读取文件: %w", err)
}
// 解析Go代码以确保其有效
fset := token.NewFileSet()
_, err = parser.ParseFile(fset, filename, content, parser.ParseComments)
if err != nil {
return fmt.Errorf("解析Go文件: %w", err)
}
prompt, err := cr.template.Format(map[string]any{
"code": string(content),
"filename": filename,
})
if err != nil {
return fmt.Errorf("格式化提示: %w", err)
}
ctx := context.Background()
response, err := cr.llm.GenerateContent(ctx, []llms.MessageContent{
llms.TextParts(llms.ChatMessageTypeHuman, prompt),
})
if err != nil {
return fmt.Errorf("生成评审: %w", err)
}
fmt.Printf("\n=== 评审文件 %s ===\n", filename)
fmt.Println(strings.Repeat("=", 80))
fmt.Println(response.Choices[0].Content)
fmt.Println(strings.Repeat("=", 80))
return nil
}
func main() {
var (
file = flag.String("file", "", "要评审的Go文件")
dir = flag.String("dir", "", "要评审的目录(所有 .go 文件)")
git = flag.Bool("git", false, "在Git工作区中评审更改的文件")
)
flag.Parse()
reviewer, err := NewCodeReviewer()
if err != nil {
log.Fatal(err)
}
switch {
case *file != "":
if err := reviewer.ReviewFile(*file); err != nil {
log.Fatal(err)
}
case *dir != "":
if err := reviewDirectory(reviewer, *dir); err != nil {
log.Fatal(err)
}
case *git:
if err := reviewGitChanges(reviewer); err != nil {
log.Fatal(err)
}
default:
fmt.Println("用法:")
fmt.Println(" code-reviewer -file=main.go")
fmt.Println(" code-reviewer -dir=./pkg")
fmt.Println(" code-reviewer -git")
os.Exit(1)
}
}
func reviewDirectory(reviewer *CodeReviewer, dir string) error {
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if strings.HasSuffix(path, ".go") && !strings.Contains(path, "vendor/") {
return reviewer.ReviewFile(path)
}
return nil
})
}
func reviewGitChanges(reviewer *CodeReviewer) error {
// 这是一个简化的版本 - 您应该使用一个合适的git库
cmd := exec.Command("git", "diff", "--name-only", "HEAD")
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("获取Git更改: %w", err)
}
files := strings.Split(strings.TrimSpace(string(output)), "\n")
for _, file := range files {
if strings.HasSuffix(file, ".go") && file != "" {
if err := reviewer.ReviewFile(file); err != nil {
log.Printf("评审文件 %s 时出错: %v", file, err)
}
}
}
return nil
}
第3步:使用示例代码测试
创建 sample.go
文件以进行测试:
package main
import "fmt"
func badCode() {
// 这里存在多个问题
var users []string
for i := 0; i < len(users); i++ {
fmt.Println(users[i]) // 潜在的索引越界错误
}
// 循环中的字符串连接
var result string
for i := 0; i < 1000; i++ {
result += fmt.Sprintf("item-%d,", i)
}
// 忽略错误
file, _ := os.Open("nonexistent.txt")
file.Read(make([]byte, 100))
}
步骤4:运行代码审查工具
export OPENAI_API_KEY="your-openai-api-key-here"
go run main.go -file=sample.go
步骤5:增强版带结构化输出
创建 reviewer.go
以进行更复杂的分析:
package main
import (
"encoding/json"
"fmt"
"go/ast"
"go/parser"
"go/token"
)
type Issue struct {
Severity string `json:"severity"`
Type string `json:"type"`
Line int `json:"line"`
Description string `json:"description"`
Suggestion string `json:"suggestion"`
}
type ReviewResult struct {
Filename string `json:"filename"`
Issues []Issue `json:"issues"`
Score int `json:"score"` // 0-100
}
func (cr *CodeReviewer) ReviewFileStructured(filename string) (*ReviewResult, error) {
content, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("读取文件: %w", err)
}
// 解析行号
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, filename, content, parser.ParseComments)
if err != nil {
return nil, fmt.Errorf("解析Go文件: %w", err)
}
template := prompts.NewPromptTemplate(`
分析这段Go代码,并返回具有以下结构的JSON响应:
{
"filename": "{{.filename}}",
"issues": [
{
"severity": "critical|warning|suggestion",
"type": "bug|performance|style|security",
"line": 42,
"description": "详细的错误描述",
"suggestion": "如何修复此问题"
}
],
"score": 85
}
需要分析的代码:
'''go
{{.code}}
'''
专注于实际的问题。评分:100 = 完美,0 = 许多严重问题。`,
[]string{"code", "filename"})
prompt, err := template.Format(map[string]any{
"code": string(content),
"filename": filename,
})
if err != nil {
return nil, fmt.Errorf("格式化提示: %w", err)
}
ctx := context.Background()
response, err := cr.llm.GenerateContent(ctx, []llms.MessageContent{
llms.TextParts(llms.ChatMessageTypeHuman, prompt),
}, llms.WithJSONMode())
if err != nil {
return nil, fmt.Errorf("生成审查: %w", err)
}
var result ReviewResult
if err := json.Unmarshal([]byte(response.Choices[0].Content), &result); err != nil {
return nil, fmt.Errorf("解析JSON响应: %w", err)
}
return &result, nil
}
步骤6:创建Git钩子
创建 .git/hooks/pre-commit
:
#!/bin/bash
echo "正在运行AI代码审查..."
./code-reviewer -git
if [ $? -ne 0 ]; then
echo "代码审查发现了一些问题。请修复这些问题或使用--no-verify跳过此步骤。"
exit 1
fi
echo "代码审查通过!"
使其可执行:
chmod +x .git/hooks/pre-commit
高级功能
自定义规则引擎
为您的代码库添加特定检查:
type RuleEngine struct {
rules []Rule
}
type Rule interface {
Check(node ast.Node, fset *token.FileSet) []Issue
}
type NoGlobalVarsRule struct{}
func (r NoGlobalVarsRule) Check(node ast.Node, fset *token.FileSet) []Issue {
var issues []Issue
ast.Inspect(node, func(n ast.Node) bool {
if genDecl, ok := n.(*ast.GenDecl); ok && genDecl.Tok == token.VAR {
for _, spec := range genDecl.Specs {
if valueSpec, ok := spec.(*ast.ValueSpec); ok {
pos := fset.Position(valueSpec.Pos())
issues = append(issues, Issue{
Severity: "warning",
Type: "style",
Line: pos.Line,
Description: "发现全局变量",
Suggestion: "请考虑使用依赖注入或配置结构体",
})
}
}
}
return true
})
return issues
}
集成CI/CD
创建 Dockerfile
:
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o code-reviewer
FROM alpine:latest
RUN apk --no-cache add git
WORKDIR /root/
COPY --from=builder /app/code-reviewer .
ENTRYPOINT ["./code-reviewer"]
在GitHub Actions中使用:
name: AI 代码审查
on: [pull_request]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: 运行AI代码审查
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
docker build -t code-reviewer .
docker run --rm -v $PWD:/code code-reviewer -dir=/code
下一步
- 支持其他语言
- 实现从反馈中学习的功能
- 创建团队审查的Web界面
- 集成到流行的IDE中
本教程展示了LangChainGo如何为实用开发工具提供支持,这些工具远远超出了简单的聊天机器人!