--- applyTo: '**/*.go' --- # AI Agent 开发指令 本文档为 AI Agent(GitHub Copilot)提供开发本项目时的详细指令和指南。 ## 项目背景 这是一个用于测试和实验 AWS OpenSearch Service 的 Go 项目。项目目标是创建生产级别的代码,并为未来转化为可复用的依赖库做准备。 ## 核心原则 ### 1. 代码质量要求(最高优先级) **必须遵循 Go 官方最佳实践:** - **Effective Go**: https://go.dev/doc/effective_go - **Go Code Review Comments**: https://github.com/golang/go/wiki/CodeReviewComments - **Go 标准库风格**: 参考标准库的代码风格和设计模式 **具体要求:** ```go // ✅ 正确:导出的函数必须有文档注释,以函数名开头 // NewClient creates and initializes a new OpenSearch client with the provided configuration. // It returns an error if the connection cannot be established. func NewClient(cfg *Config) (*Client, error) { // ... } // ❌ 错误:缺少注释或注释不以函数名开头 func NewClient(cfg *Config) (*Client, error) { // ... } ``` ```go // ✅ 正确:错误处理不能忽略 result, err := client.Search(ctx, query) if err != nil { return nil, fmt.Errorf("search failed: %w", err) } // ❌ 错误:忽略错误 result, _ := client.Search(ctx, query) ``` ```go // ✅ 正确:使用有意义的变量名 var maxRetryCount = 3 var connectionTimeout = 30 * time.Second // ❌ 错误:使用无意义的缩写 var mrc = 3 var ct = 30 * time.Second ``` ### 2. 命名规范 **包名:** - 小写、简短、单数形式 - 避免使用下划线或驼峰命名 - 示例:`client`、`search`、`document`(不是 `opensearch_client`) **接口名:** - 单方法接口以 `-er` 结尾:`Reader`、`Writer`、`Searcher` - 多方法接口使用描述性名称:`IndexManager`、`DocumentService` **变量和函数名:** - 驼峰命名(camelCase 或 PascalCase) - 缩写词保持一致大写:`URL`、`HTTP`、`ID`、`JSON`(不是 `Url`、`Http`、`Id`、`Json`) - 示例:`clientID`、`httpClient`、`parseURL` **常量:** ```go // ✅ 正确:使用驼峰命名 const DefaultTimeout = 30 * time.Second const MaxRetries = 3 // 或者使用全大写(枚举类型) const ( StatusPending = "PENDING" StatusComplete = "COMPLETE" ) ``` ### 3. 错误处理规范 **基本错误处理:** ```go // ✅ 正确:包装错误,提供上下文信息 if err != nil { return fmt.Errorf("failed to create index %q: %w", indexName, err) } // ✅ 正确:自定义错误类型(当需要时) var ErrIndexNotFound = errors.New("index not found") var ErrInvalidQuery = errors.New("invalid query syntax") // ✅ 正确:错误检查使用 errors.Is 或 errors.As if errors.Is(err, ErrIndexNotFound) { // 处理索引不存在的情况 } // ❌ 错误:吞掉错误信息 if err != nil { return errors.New("something went wrong") } ``` **Defer 中的错误处理:** 对于 `defer` 语句中的 Close() 等清理操作,**不能**完全忽略错误: ```go // ✅ 正确:检查并记录 Close 错误 defer func() { if closeErr := res.Body.Close(); closeErr != nil { fmt.Fprintf(os.Stderr, "warning: failed to close response body: %v\n", closeErr) } }() // ❌ 错误:完全忽略错误(虽然能通过 linter,但不是最佳实践) defer func() { _ = res.Body.Close() }() // ❌ 错误:不处理错误(errcheck 警告) defer res.Body.Close() ``` **原因说明:** - HTTP Response Body 的 Close() 错误虽然通常不影响业务逻辑(数据已读取) - 但完全忽略错误不符合 Go 最佳实践 - 检查并记录到 stderr 便于调试和监控 - 这种方式既满足 linter 要求,又提供诊断信息 ``` ### 4. 注释规范 ```go // ✅ 正确:包注释(在 package 语句之前) // Package client provides a high-level interface for interacting with AWS OpenSearch Service. // It handles connection management, authentication, and request signing. package client // ✅ 正确:导出类型的注释 // Client represents an OpenSearch client that manages connections and operations. type Client struct { // ... } // ✅ 正确:导出方法的注释 // Search executes a search query against the specified index. // It returns the search results or an error if the operation fails. func (c *Client) Search(ctx context.Context, indexName string, query Query) (*SearchResult, error) { // ... } // ✅ 正确:复杂逻辑的内部注释 func processResults(data []byte) error { // Parse JSON response var resp Response if err := json.Unmarshal(data, &resp); err != nil { return err } // Validate response structure if resp.Total == 0 { return ErrNoResults } // Transform results // ... } ``` ### 5. 代码结构规范 **文件组织:** ```go // 1. 包声明和包注释 // Package client provides... package client // 2. 导入语句(分组:标准库、第三方库、本地包) import ( "context" "fmt" "time" "github.com/opensearch-project/opensearch-go/v2" "es-demo/config" "es-demo/models" ) // 3. 常量 const ( DefaultTimeout = 30 * time.Second ) // 4. 变量 var ( ErrInvalidConfig = errors.New("invalid configuration") ) // 5. 类型定义 type Client struct { // ... } // 6. 构造函数 func NewClient(cfg *Config) (*Client, error) { // ... } // 7. 方法(接收者方法) func (c *Client) Connect(ctx context.Context) error { // ... } // 8. 其他函数 func validateConfig(cfg *Config) error { // ... } ``` **接口设计:** ```go // ✅ 正确:接口定义在使用方,而非实现方 // 在 operations/search 包中定义 type DocumentStore interface { GetDocument(ctx context.Context, id string) (*Document, error) PutDocument(ctx context.Context, doc *Document) error } // ✅ 正确:保持接口小而专注 type Searcher interface { Search(ctx context.Context, query Query) (*Result, error) } type Indexer interface { CreateIndex(ctx context.Context, name string, settings map[string]any) error DeleteIndex(ctx context.Context, name string) error } ``` ### 6. 测试规范 **文件命名:** - 测试文件:`client_test.go`(对应 `client.go`) - 测试函数:`TestNewClient`、`TestClient_Search` **测试结构:** ```go func TestNewClient(t *testing.T) { // 使用表驱动测试 tests := []struct { name string config *Config want bool // 期望成功/失败 wantErr error }{ { name: "valid config", config: &Config{ Endpoint: "https://example.com", }, want: true, wantErr: nil, }, { name: "nil config", config: nil, want: false, wantErr: ErrInvalidConfig, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := NewClient(tt.config) if !errors.Is(err, tt.wantErr) { t.Errorf("NewClient() error = %v, wantErr %v", err, tt.wantErr) return } if (got != nil) != tt.want { t.Errorf("NewClient() got = %v, want %v", got != nil, tt.want) } }) } } // 集成测试(需要真实环境) func TestClient_Search_Integration(t *testing.T) { if testing.Short() { t.Skip("skipping integration test in short mode") } // 测试逻辑... } ``` **辅助函数:** ```go // 测试辅助函数以小写字母开头(不导出) func setupTestClient(t *testing.T) *Client { t.Helper() cfg := &Config{ Endpoint: "http://localhost:9200", } client, err := NewClient(cfg) if err != nil { t.Fatalf("failed to create test client: %v", err) } return client } func cleanupTestIndex(t *testing.T, client *Client, indexName string) { t.Helper() if err := client.DeleteIndex(context.Background(), indexName); err != nil { t.Logf("cleanup failed: %v", err) } } ``` ## 项目阶段考虑 ### 当前阶段(实验期) **允许的简化:** 1. **配置管理**:可以使用全局变量 ```go // config/config.go package config var ( // Global configuration (acceptable in experimental phase) Endpoint string AccessKey string SecretKey string Region string ) func Init() { // Simple initialization from environment variables Endpoint = os.Getenv("ES_ENDPOINT") AccessKey = os.Getenv("AWS_ACCESS_KEY_ID") SecretKey = os.Getenv("AWS_SECRET_ACCESS_KEY") Region = os.Getenv("AWS_REGION") } ``` 2. **日志系统**:基础的 log 包使用 ```go // utils/logger.go package utils import "log" var ( // Global logger (acceptable in experimental phase) Logger = log.New(os.Stdout, "[ES-Demo] ", log.LstdFlags) ) func Info(msg string, args ...any) { Logger.Printf("INFO: "+msg, args...) } func Error(msg string, args ...any) { Logger.Printf("ERROR: "+msg, args...) } ``` **但必须保持:** - 代码的可读性和可维护性 - 清晰的职责划分 - 完整的错误处理 - 充分的注释 ## 代码质量检查 ### 使用 Linter 进行自动化检查 本项目使用 `golangci-lint` 进行代码质量检查。在生成或修改代码后,**必须**确保代码通过 linter 检查。 **配置文件:** `.golangci.yml` **运行 Linter:** ```bash # 检查所有代码 golangci-lint run ./... # 自动修复部分问题 golangci-lint run --fix ./... ``` **处理 Linter 输出:** 1. 认真阅读每一条 linter 警告和错误 2. 理解问题的根本原因 3. 按照 Go 最佳实践修复问题 4. 不要通过禁用 linter 规则来"修复"问题(除非有充分理由) 5. 如需禁用某个规则,必须在配置文件中注释说明原因 **常见 Linter 问题及解决方案:** - `errcheck`: 错误未处理 → 添加错误处理逻辑 - `gofmt`: 格式不正确 → 运行 `go fmt ./...` - `govet`: 可疑构造 → 重构代码 - `ineffassign`: 无效赋值 → 移除或修正赋值 - `staticcheck`: 静态分析问题 → 按建议修改 **集成到开发流程:** - 在提交代码前,运行测试流水线(包含 linter 检查) - 确保 linter 零警告、零错误 - CI/CD 流水线应包含 linter 检查步骤 ## 常见模式和示例 ### 客户端初始化模式 ```go // Config holds the configuration for the OpenSearch client. type Config struct { Endpoint string Region string AccessKey string SecretKey string Timeout time.Duration } // Client represents an OpenSearch client. type Client struct { config *Config client *opensearch.Client } // NewClient creates a new OpenSearch client with the given configuration. func NewClient(cfg *Config) (*Client, error) { if cfg == nil { return nil, fmt.Errorf("config cannot be nil") } if err := validateConfig(cfg); err != nil { return nil, fmt.Errorf("invalid config: %w", err) } // Initialize OpenSearch client osClient, err := opensearch.NewClient(opensearch.Config{ Addresses: []string{cfg.Endpoint}, // ... other settings }) if err != nil { return nil, fmt.Errorf("failed to create opensearch client: %w", err) } return &Client{ config: cfg, client: osClient, }, nil } func validateConfig(cfg *Config) error { if cfg.Endpoint == "" { return errors.New("endpoint is required") } if cfg.Region == "" { return errors.New("region is required") } return nil } ``` ### 操作方法模式 ```go // SearchRequest represents a search query request. type SearchRequest struct { Index string Query map[string]any Size int From int } // SearchResult represents the response from a search operation. type SearchResult struct { Hits []Document Total int64 Took int64 } // Search executes a search query against the specified index. func (c *Client) Search(ctx context.Context, req *SearchRequest) (*SearchResult, error) { if req == nil { return nil, errors.New("search request cannot be nil") } if req.Index == "" { return nil, errors.New("index name is required") } // Build and execute search // ... return result, nil } ``` ### Context 使用模式 ```go // 所有可能阻塞或需要超时控制的操作都应接受 context func (c *Client) CreateIndex(ctx context.Context, name string, settings map[string]any) error { // 使用 context 控制超时 req, err := http.NewRequestWithContext(ctx, "PUT", c.buildURL(name), body) if err != nil { return fmt.Errorf("failed to create request: %w", err) } // ... } // 调用示例 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() err := client.CreateIndex(ctx, "my-index", settings) ``` ## 版本和依赖管理 **依赖选择原则:** 1. 优先使用最新稳定版本 2. 使用官方推荐的库(如 opensearch-go) 3. 定期更新依赖(使用 `go get -u` 和 `go mod tidy`) **更新依赖时检查:** ```bash # 查看可用更新 go list -u -m all # 更新特定依赖 go get -u github.com/opensearch-project/opensearch-go/v2@latest # 整理依赖 go mod tidy ``` ## 测试流水线 ### 流水线脚本要求 项目**必须**包含自动化测试流水线脚本,用于执行完整的代码质量检查流程。 **脚本命名规范:** - Windows (PowerShell): `test.ps1` - Linux/Mac (Bash): `test.sh` **流水线阶段(按顺序执行):** 1. **Linting** - 代码质量检查 ```bash golangci-lint run ./... ``` 2. **Build** - 编译验证 ```bash # 编译但不保留二进制文件 go build -o /dev/null . # Linux/Mac go build -o NUL . # Windows ``` 3. **Test** - 运行测试 ```bash go test -v -short -coverprofile=coverage.out ./... ``` 4. **Cleanup** - 清理构建产物 ```bash # 删除编译输出、覆盖率文件等 rm -f *.exe *.test coverage.out coverage.html ``` ### 流水线脚本检查 **在最终测试前,AI Agent 必须检查:** 1. 当前平台是否存在对应的流水线脚本 2. 如果不存在但有其他平台的脚本,则根据当前平台创建对应版本 **平台检测和脚本生成逻辑:** ```go // 伪代码示例 if platform == "Windows" { if !exists("test.ps1") { if exists("test.sh") { // 将 test.sh 翻译为 test.ps1 createWindowsScript() } else { // 创建新的 Windows 脚本 createDefaultWindowsScript() } } } else if platform == "Linux" || platform == "Mac" { if !exists("test.sh") { if exists("test.ps1") { // 将 test.ps1 翻译为 test.sh createUnixScript() } else { // 创建新的 Unix 脚本 createDefaultUnixScript() } } } ``` ### PowerShell 脚本示例 (test.ps1) ```powershell #!/usr/bin/env pwsh # 测试流水线脚本 - Windows PowerShell param( [switch]$SkipLint, [switch]$SkipBuild, [switch]$SkipTest, [switch]$Verbose ) $ErrorActionPreference = "Stop" # 1. Linting if (-not $SkipLint) { Write-Host "=== Linting ===" -ForegroundColor Yellow golangci-lint run ./... if ($LASTEXITCODE -ne 0) { exit 1 } } # 2. Build if (-not $SkipBuild) { Write-Host "=== Build ===" -ForegroundColor Yellow go build -o NUL . if ($LASTEXITCODE -ne 0) { exit 1 } } # 3. Test if (-not $SkipTest) { Write-Host "=== Test ===" -ForegroundColor Yellow go test -v -short -coverprofile=coverage.out ./... if ($LASTEXITCODE -ne 0) { exit 1 } go tool cover -func=coverage.out | Select-String "total:" } # 4. Cleanup Write-Host "=== Cleanup ===" -ForegroundColor Yellow Remove-Item -Path "*.exe","*.test","coverage.out","coverage.html" -ErrorAction SilentlyContinue Write-Host "✅ Pipeline PASSED" -ForegroundColor Green ``` ### Bash 脚本示例 (test.sh) ```bash #!/usr/bin/env bash # 测试流水线脚本 - Linux/Mac Bash set -euo pipefail SKIP_LINT=false SKIP_BUILD=false SKIP_TEST=false VERBOSE=false # 解析参数 while [[ $# -gt 0 ]]; do case $1 in --skip-lint) SKIP_LINT=true; shift ;; --skip-build) SKIP_BUILD=true; shift ;; --skip-test) SKIP_TEST=true; shift ;; --verbose) VERBOSE=true; shift ;; *) echo "Unknown option: $1"; exit 1 ;; esac done # 1. Linting if [ "$SKIP_LINT" = false ]; then echo "=== Linting ===" golangci-lint run ./... fi # 2. Build if [ "$SKIP_BUILD" = false ]; then echo "=== Build ===" go build -o /dev/null . fi # 3. Test if [ "$SKIP_TEST" = false ]; then echo "=== Test ===" go test -v -short -race -coverprofile=coverage.out ./... go tool cover -func=coverage.out | grep total: fi # 4. Cleanup echo "=== Cleanup ===" rm -f *.exe *.test coverage.out coverage.html echo "✅ Pipeline PASSED" ``` ### 使用流水线 **日常开发:** ```bash # Windows powershell -ExecutionPolicy Bypass -File .\test.ps1 # Linux/Mac chmod +x test.sh ./test.sh ``` **跳过特定阶段:** ```bash # Windows - 跳过 linting powershell -ExecutionPolicy Bypass -File .\test.ps1 -SkipLint # Linux/Mac - 跳过 build ./test.sh --skip-build ``` **CI/CD 集成:** ```yaml # GitHub Actions 示例 - name: Run test pipeline run: | if [ "$RUNNER_OS" == "Windows" ]; then pwsh -File test.ps1 else ./test.sh fi ``` ### 流水线最佳实践 1. **构建产物管理:** - 编译时不保留二进制文件(输出到 `/dev/null` 或 `NUL`) - 测试后自动清理所有临时文件 - 在 `.gitignore` 中排除所有构建产物 2. **失败快速原则:** - 任何阶段失败立即终止流水线 - 返回非零退出码 - 提供清晰的错误信息 3. **可配置性:** - 提供跳过特定阶段的选项 - 支持详细输出模式 - 允许自定义超时设置 4. **跨平台一致性:** - 确保 Windows 和 Linux/Mac 脚本功能一致 - 使用等效的命令和选项 - 测试结果应该相同 ## 文档要求 **代码文档:** - 所有导出的标识符必须有文档注释 - 复杂的内部逻辑添加解释性注释 - 注释应解释"为什么"而不仅仅是"是什么" **README 维护:** - 添加新功能时更新功能列表 - 添加示例代码展示用法 - 更新配置说明 **示例代码:** - 在 `examples/` 目录提供可运行的示例 - 示例代码必须可编译和运行 - 包含必要的错误处理 ## AI Agent 工作流程(TDD 原则) 当收到开发任务时,**必须严格遵循 TDD(测试驱动开发)流程**: ### 1. Red(红灯阶段)- 编写失败的测试 **理解需求:** - 明确要实现的功能和预期行为 - 确定输入、输出和边界条件 - 识别可能的错误场景 **设计 API:** - 定义函数签名和类型 - 设计清晰的接口 - 确定错误返回值 **编写测试(先于实现):** ```go // 示例:先写测试 func TestNewClient(t *testing.T) { tests := []struct { name string config *Config wantErr error }{ { name: "valid config", config: &Config{Endpoint: "https://example.com"}, wantErr: nil, }, { name: "nil config", config: nil, wantErr: ErrInvalidConfig, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := NewClient(tt.config) if !errors.Is(err, tt.wantErr) { t.Errorf("NewClient() error = %v, wantErr %v", err, tt.wantErr) } }) } } ``` **验证测试失败:** - 运行测试,确认测试失败(因为功能还未实现) - 确保测试失败的原因是正确的 ### 2. Green(绿灯阶段)- 编写最小实现 **实现功能:** - 编写最少的代码让测试通过 - 遵循本文档的所有规范 - 添加完整的文档注释 - 实现完整的错误处理 ```go // 示例:实现功能 func NewClient(cfg *Config) (*Client, error) { if cfg == nil { return nil, ErrInvalidConfig } // 最小实现 return &Client{config: cfg}, nil } ``` **运行测试:** - 确保所有测试通过 - 检查测试覆盖率 ### 3. Refactor(重构阶段)- 优化代码 **代码重构:** - 消除重复代码 - 改进命名和结构 - 优化性能(如需要) - 确保代码可读性 **运行 Linter:** ```bash golangci-lint run ./... ``` **修复所有 Linter 问题** **再次运行测试:** - 确保重构后测试仍然通过 - 验证代码行为未改变 ### 4. 验证和文档 **运行测试流水线:** ```bash # Windows powershell -ExecutionPolicy Bypass -File .\test.ps1 # Linux/Mac ./test.sh ``` **更新文档:** - 更新模块 README(如需要) - 更新项目 README(如需要) - 添加使用示例(如需要) ### TDD 循环总结 ``` ┌─────────────────────────────────────┐ │ 1. 编写失败的测试 (Red) │ │ - 定义预期行为 │ │ - 验证测试确实失败 │ └──────────────┬──────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 2. 编写最小实现 (Green) │ │ - 让测试通过 │ │ - 不过度设计 │ └──────────────┬──────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 3. 重构优化 (Refactor) │ │ - 改进代码质量 │ │ - 运行 Linter │ │ - 确保测试仍然通过 │ └──────────────┬──────────────────────┘ ↓ ┌─────────┐ │ 下一功能 │ └─────────┘ ``` **重要提醒:** - ❌ 不要先写实现再补测试 - ✅ 必须先写测试,然后写实现 - ✅ 每次只关注一个小功能 - ✅ 频繁运行测试,保持快速反馈 ## 特殊指令 ### 生成新模块时 1. 创建包目录和主文件 2. 创建对应的测试文件 3. 在 README 中更新项目结构 4. 在功能列表中标记进度 ### 重构代码时 1. 确保向后兼容(除非明确要求破坏性变更) 2. 更新所有相关测试 3. 更新文档和注释 4. 运行完整测试套件 ### 修复 Bug 时 1. 先编写复现 bug 的测试 2. 修复代码 3. 确保测试通过 4. 添加注释说明修复内容 ## 禁止事项 **绝对不允许:** - ❌ 忽略错误(使用 `_` 丢弃错误) - ❌ 导出的标识符没有注释 - ❌ 使用 `panic` 处理常规错误(除非确实是不可恢复的错误) - ❌ 在库代码中使用 `os.Exit` - ❌ 硬编码配置值(应使用配置文件或环境变量) - ❌ 使用全局可变状态(除了当前阶段允许的配置和日志) **应当避免:** - ⚠️ 过长的函数(超过 50 行考虑拆分) - ⚠️ 过深的嵌套(超过 3 层考虑重构) - ⚠️ 重复代码(提取为函数) - ⚠️ 神秘的数字和字符串(使用命名常量) ## 参考资源 - [Effective Go](https://go.dev/doc/effective_go) - [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) - [Go 标准库文档](https://pkg.go.dev/std) - [OpenSearch Go Client](https://github.com/opensearch-project/opensearch-go) - [AWS SDK for Go v2](https://aws.github.io/aws-sdk-go-v2/) --- **总结**: 作为 AI Agent,你的目标是生成生产级别的 Go 代码,遵循所有最佳实践,为将来转化为可复用库打下坚实基础。当有疑问时,参考 Go 标准库的实现方式。