跳到主要内容

构建一个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如何为实用开发工具提供支持,这些工具远远超出了简单的聊天机器人!