feat: opensearch客户端初始化 feat: 索引模板接口 ai: 开发准则 chore: TDD流水线脚本
This commit is contained in:
975
.github/instructions/DEV_GUIDELINE.instructions.md
vendored
Normal file
975
.github/instructions/DEV_GUIDELINE.instructions.md
vendored
Normal file
@@ -0,0 +1,975 @@
|
||||
---
|
||||
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 标准库的实现方式。
|
||||
Reference in New Issue
Block a user