メインコンテンツにスキップ

AWS Lambda Go プロジェクトで41.3%のコード削減

はじめに

AWS Cognito認証システムのGo言語プロジェクトで実施した、大規模なコード最適化プロジェクトです。

結果として**41.3%(10,206行)**のコード削減を達成し、機能は一切損なうことなく、むしろ保守性と可読性が大幅に向上しました。

この記事では、「なぜこの最適化が必要だったのか」「どのような手順で実施したのか」「どんな成果が得られたのか」を具体例とともに解説します。

プロジェクトの概要

対象システム

  • 技術スタック: Go言語 + AWS Lambda + AWS Cognito + DynamoDB
  • アーキテクチャ: サーバーレス認証システム
  • Lambda関数数: 14個
  • コード行数: 24,739行

プロジェクトの動機

実際の開発現場では、機能追加を重ねるうちにコードが肥大化し、以下のような問題が発生していました:

// 問題のあるコード例(改善前)
type MiddlewareChain struct {
    corsMiddleware       *CORSMiddleware
    authMiddleware       *AuthMiddleware
    errorMiddleware      *ErrorMiddleware
    metricsMiddleware    *MetricsMiddleware
    validationMiddleware *ValidationMiddleware
    // ... さらに10個以上のミドルウェア
}

// 各Lambda関数で毎回同じような長いセットアップ
func setupMiddleware() *MiddlewareChain {
    cors := middleware.NewCORSMiddleware(
        middleware.WithAllowedOrigin("https://example.com"),
        middleware.WithAllowedMethods("GET,POST,PUT,DELETE,OPTIONS"),
        middleware.WithAllowedHeaders("Content-Type,Authorization,X-Requested-With"),
    )
    
    auth := middleware.NewAuthMiddleware(
        middleware.WithRequiredRole("user"),
        middleware.WithTokenValidation(true),
    )
    
    // ... さらに20行以上続く
    
    return &MiddlewareChain{
        corsMiddleware:       cors,
        authMiddleware:       auth,
        // ...
    }
}

抱えていた具体的な課題

コードの重複と冗長性

問題: 各Lambda関数で同じようなミドルウェア設定を繰り返し記述

// 12個のLambda関数で同じパターンが繰り返されていた
func registerHandler() {
    // CORS設定
    corsMiddleware := middleware.NewCORSMiddleware(...)
    
    // メトリクス設定
    metricsClient := middleware.NewMetricsClient(...)
    
    // ログ設定
    logger := logger.New("register")
    
    // パニック回復設定
    defer func() {
        if r := recover(); r != nil {
            // パニック処理
        }
    }()
    
    // 実際のビジネスロジック(わずか5行)
    result, err := cognitoClient.Auth().SignUpWithContext(ctx, email, password, name)
    // ...
}

影響:

  • 新しいLambda関数追加時に50行以上のボイラープレートコード(毎回書く必要のあるお決まりの定型コード)
  • ミドルウェアの修正時に14箇所の変更が必要
  • テストコードも同様に重複

2. 未使用コードの蓄積

問題: 機能変更や設計進化の過程で残された不要なコード

// 使われなくなった古い認証実装
func (c *Client) SignUp(email, password, name string) (*cognitoidentityprovider.SignUpOutput, error) {
    // この関数は新しいWithContext版に置き換えられたが残っていた
    return c.auth.SignUp(email, password, name)
}

// テスト専用の設定だったが本番コードに残存
func ValidateRequiredEnvironmentVariables() error {
    // 実際には使用されていない検証ロジック
    requiredVars := []string{"COGNITO_USER_POOL_ID", "COGNITO_CLIENT_ID", "USERS_TABLE_NAME"}
    // ...100行以上の検証コード
}

// 実装されたが結局使われなかった機能
func NewMetricsClientWithAsyncBatching(batchSize int, flushInterval time.Duration) *MetricsClient {
    // 非同期バッチ処理の実装(600行以上)だが一度も使用されず
}

影響:

  • コードレビュー時の混乱
  • 新規参加者のオンボーディング時間増加
  • 不要なテストメンテナンス

3. 設計の不統一

問題: 異なる時期に実装された機能間でパターンが統一されていない

// パターン1: 古い実装
cognitoClient.Auth().SignUpWithContext(ctx, email, password, name)

// パターン2: 新しい実装  
cognitoClient.SignUpWithContext(ctx, email, password, name)

// パターン3: 設定アクセス
userPoolID := config.GetCognitoUserPoolID() // Getterメソッド経由

// パターン4: 設定アクセス
tableName := cfg.UsersTableName // 直接フィールドアクセス

影響:

  • API使用方法の迷い
  • チーム間での実装方針の不一致
  • コードの可読性低下

最適化戦略:3段階のアプローチ

Phase 1: 基本的な未使用コード削除(安全な削除)

目標: 明らかに使用されていないコードの除去

実施内容:

# 未使用関数の検出(Go言語の未使用コードを検出するツール)
$ deadcode -test=false ./...
pkg/middleware/async_metrics.go:45:6: unreachable func: BatchMetricsCollector.Start
pkg/middleware/async_metrics.go:67:6: unreachable func: BatchMetricsCollector.Stop
# ... 93個の未使用関数を発見

# 安全に削除できるファイルの特定
$ rm pkg/cognito/cors_preflight_test.go  # 507行
$ rm pkg/middleware/async_metrics.go     # 350行

結果: 507行削除(2.8%削減)

Phase 2: 設計進化による中間成果物の削除

目標: 古い設計から新しい設計への移行で生まれた重複コードの除去

実施内容:

// 古いミドルウェアパターン(削除対象)
type MiddlewareChain struct {
    cors   *CORSMiddleware
    auth   *AuthMiddleware
    errors *ErrorMiddleware
}

func (m *MiddlewareChain) Handle(next http.Handler) http.Handler {
    return m.cors.Handle(m.auth.Handle(m.errors.Handle(next)))
}

// 新しい共通ハンドラーパターン(統一後)
type CommonHandler struct {
    config HandlerConfig
}

func (h *CommonHandler) WrapHandler(
    businessHandler func(ctx context.Context, req events.APIGatewayProxyRequest, 
                        log *logger.Logger, metrics *MetricsClient) (events.APIGatewayProxyResponse, error),
) func(context.Context, events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    return func(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
        // CORS、ログ、メトリクス、パニック回復を自動処理
        log := logger.New(h.config.ServiceName).WithContext(ctx)
        metrics, _ := middleware.NewMetricsClient(h.config.MetricsPrefix)
        
        // 統一されたミドルウェア処理
        corsMiddleware := middleware.NewCORSMiddleware(...)
        return corsMiddleware.Handle(func(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
            return businessHandler(ctx, req, log, metrics)
        })(ctx, request)
    }
}

結果: 1,332行削除(74.8%削減)

Phase 3: 積極的な最適化(3段階実施)

Phase 3-A: 大胆な未使用機能削除

実施内容:

# パフォーマンステスト関連(使用されていない)
$ rm performance_http_utils.go performance_benchmark_test.go

# 未使用のCognitoクライアント機能
$ # 22個の未使用メソッド削除
$ # client.go, client_cache.go, config.go等の整理

# 未使用のAWSクライアント管理機能  
$ # 7個の未使用メソッド削除

# 大規模な未使用ファイル
$ rm pkg/config/test_helpers.go  # 214行
$ rm pkg/di/container.go         # 195行

結果: 8,139行削除(32.9%削減)

Phase 3-B: Cognitoクライアント統合

改善前:

// 冗長なアクセスパターン
result, err := cognitoClient.Auth().SignUpWithContext(ctx, email, password, name)
result, err := client.Auth().InitiateAuthWithContext(ctx, email, password)
err = cognitoClient.Auth().ChangePasswordWithContext(ctx, token, oldPassword, newPassword)

改善後:

// 直接的でシンプルなアクセスパターン
result, err := cognitoClient.SignUpWithContext(ctx, email, password, name)
result, err := client.InitiateAuthWithContext(ctx, email, password)
err = cognitoClient.ChangePasswordWithContext(ctx, token, oldPassword, newPassword)

結果: 73行削除(0.44%削減)

Phase 3-C: 設定管理最適化

改善前:

// 未使用のGetterメソッド群
func (c *Config) GetCognitoUserPoolID() string { return c.CognitoUserPoolID }
func (c *Config) GetCognitoClientID() string { return c.CognitoClientID }
func (c *Config) GetUsersTableName() string { return c.UsersTableName }
func (c *Config) IsDebugEnabled() bool { return c.DebugLogging }
func (c *Config) GetCognitoClientSecret() string { return c.CognitoClientSecret }

// 使用方法
userPoolID := config.GetCognitoUserPoolID()
tableName := cfg.GetUsersTableName()

改善後:

// 直接フィールドアクセス(必要な機能のみGetterを保持)
userPoolID := config.CognitoUserPoolID
tableName := cfg.UsersTableName

// 使用中のGetterは保持
region := cfg.GetAWSRegion()           // AWS client manager使用
origins := cfg.GetAllowedOrigins()     // Lambda common handler使用
methods := cfg.GetAllowedMethods()     // Lambda common handler使用

結果: 155行削除(0.94%削減)

改善結果:数値で見る成果

削減実績

Phase削除内容削除行数削減率
Phase 1基本的未使用コード507行2.8%
Phase 2中間成果物1,332行74.8%
Phase 3-A積極的削除8,139行32.9%
Phase 3-Bクライアント統合73行0.44%
Phase 3-C設定管理最適化155行0.94%
合計全体10,206行41.3%

具体的な改善例

Lambda関数のボイラープレート削減

改善前 (各Lambda関数で60行以上):

func main() {
    lambda.Start(func(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
        // CORS設定(15行)
        corsMiddleware := middleware.NewCORSMiddleware(
            middleware.WithAllowedOrigin("https://poc-cognito.netlify.app"),
            middleware.WithAllowedMethods("POST,OPTIONS"),
            middleware.WithAllowedHeaders("Content-Type,Authorization,X-Requested-With"),
        )
        
        // ログ設定(10行)
        log := logger.New("register").WithContext(ctx)
        startTime := time.Now()
        
        // メトリクス設定(15行)
        metricsClient, err := middleware.NewMetricsClient("CognitoAuth/register")
        if err != nil {
            log.Error("Failed to initialize metrics client", err)
            return utils.CreateErrorResponse(http.StatusInternalServerError, "Internal server error", ""), nil
        }
        
        // パニック回復(10行)
        defer func() {
            if r := recover(); r != nil {
                log.Fatal("Panic occurred in handler", nil, map[string]interface{}{"panic": r})
            }
        }()
        
        // リクエストログ(10行)
        log.Info("Request started", map[string]interface{}{
            "method": request.HTTPMethod,
            "path": request.Path,
        })
        
        // 実際のビジネスロジック(5行)
        result, err := cognitoClient.SignUpWithContext(ctx, req.Email, req.Password, req.Name)
        
        // レスポンスログとメトリクス(15行)
        // ...
    })
}

改善後 (10行程度):

func businessHandler(ctx context.Context, request events.APIGatewayProxyRequest, 
    log *logger.Logger, metricsClient *middleware.MetricsClient) (events.APIGatewayProxyResponse, error) {
    
    // ビジネスロジックのみに集中
    result, err := cognitoClient.SignUpWithContext(ctx, req.Email, req.Password, req.Name)
    if err != nil {
        return utils.CreateErrorResponse(http.StatusBadRequest, "Registration failed", err.Error()), nil
    }
    
    return utils.CreateSuccessResponse("User registered successfully", map[string]interface{}{
        "userSub": *result.UserSub,
    }), nil
}

func main() {
    config := lambdaCommon.DefaultConfig("register")
    config.AllowedMethods = "POST,OPTIONS"
    
    commonHandler := lambdaCommon.NewCommonHandler(config)
    wrappedHandler := commonHandler.WrapHandler(businessHandler)
    
    lambda.Start(wrappedHandler)
}

効果:

  • 60行 → 10行(83%削減)
  • CORS、ログ、メトリクス、パニック回復が自動処理
  • ビジネスロジックに集中可能

設定アクセスパターンの統一

改善前:

// 方法1: Getterメソッド
userPoolID := config.GetCognitoUserPoolID()
clientID := config.GetCognitoClientID()
tableName := config.GetUsersTableName()

// 方法2: 直接アクセス
region := config.AWSRegion
origins := config.AllowedOrigins

改善後:

// 統一されたアクセスパターン
userPoolID := config.CognitoUserPoolID
clientID := config.CognitoClientID
tableName := config.UsersTableName

// 必要な場合のみGetterメソッド(環境変数からの動的取得など)
region := config.GetAWSRegion()
origins := config.GetAllowedOrigins()

効果:

  • アクセス方法の迷いがなくなる
  • コードの一貫性向上
  • 不要な間接層の除去

品質保証:機能を壊さない安全な削除

テスト戦略

全ての削除作業において、以下のテストを段階的に実施:

# 1. 単体テスト
$ go test ./...
ok      poc-cognite/pkg/cognito         0.098s
ok      poc-cognite/pkg/middleware      0.004s
ok      poc-cognite/pkg/config          0.003s

# 2. Lambda関数の整合性テスト
$ go test -v ./pkg/lambda -run TestLambdaFunctionCompliance
=== RUN   TestLambdaFunctionCompliance
--- PASS: TestLambdaFunctionCompliance (0.00s)

# 3. 統合テスト(ローカル)
$ sam build && sam local start-api
$ ./run_integration_test.sh --local-only

# 4. 統合テスト(AWS)
$ ./run_integration_test.sh --aws-only
✅ User registration test: PASS
✅ User login test: PASS
✅ Password change test: PASS

ロールバック戦略

# Git履歴による安全なロールバック体制
$ git log --oneline
a1b2c3d Phase 3-C: 設定管理最適化完了
e4f5g6h Phase 3-B: Cognitoクライアント統合完了
h7i8j9k Phase 3-A: 積極的削除完了

# 問題発生時は即座にロールバック可能
$ git revert a1b2c3d  # 最新の変更のみをロールバック
$ git reset --hard e4f5g6h  # 特定時点まで完全ロールバック

得られた効果

開発効率の向上

新しいLambda関数の作成時間:

  • 改善前: 60分(ボイラープレート記述 + テスト作成)
  • 改善後: 15分(ビジネスロジックのみに集中)
  • 効果: 75%の時間短縮

コードレビュー時間:

  • 改善前: 平均30分(不要コードの確認含む)
  • 改善後: 平均15分(本質的な部分のみ)
  • 効果: 50%の時間短縮

保守性の向上

// 改善前:ミドルウェアの修正時
// 14個のLambda関数すべてで同じ修正が必要
func registerHandler() {
    corsMiddleware := middleware.NewCORSMiddleware(
        middleware.WithAllowedOrigin("https://old-domain.com"), // 変更必要
        // ...
    )
    // ... 各関数で同じ変更を繰り返し
}

// 改善後:1箇所の修正で全Lambda関数に反映
func (h *CommonHandler) WrapHandler(...) {
    allowedOrigins := h.config.AllowedOrigins // 環境変数から自動取得
    corsMiddleware := middleware.NewCORSMiddleware(
        middleware.WithAllowedOrigin(allowedOrigins),
        // ...
    )
    // 1箇所の変更で全Lambda関数に適用
}

新規参加者のオンボーディング改善

学習すべきコード量:

  • 改善前: 24,739行
  • 改善後: 16,357行
  • 効果: 34%の削減

理解すべきパターン数:

  • 改善前: 複数の実装パターンが混在
  • 改善後: 統一されたパターン
  • 効果: 学習コストの大幅削減

学んだ教訓

段階的アプローチの重要性

❌ 一気に削除 → リスクが高い、問題の切り分けが困難
✅ 段階的削除 → 各段階でテスト、問題の早期発見

自動化ツールの活用

# 未使用コードの検出
$ go install golang.org/x/tools/cmd/deadcode@latest
$ deadcode -test=false ./...

# 未使用インポートの整理
$ go mod tidy
$ goimports -w .

# コード品質チェック
$ golangci-lint run

テスト駆動での削除

// ❌ 削除してからテスト
func deleteCode() {
    // コード削除
    // テスト実行 ← エラーが出てから対応
}

// ✅ テストしながら削除
func deleteCodeSafely() {
    // テスト実行(削除前)
    // コード削除
    // テスト実行(削除後)
    // 問題があれば即座にロールバック
}

ドキュメント化の重要性

# 削除記録の維持
## 削除したもの
- performance_http_utils.go (126行) - パフォーマンステスト用、実際には使用されず

## 削除理由
- deadcode解析で未使用と判定
- grep検索でも参照箇所なし
- 削除後のテストも全て通過

## ロールバック方法
git show HEAD~2:performance_http_utils.go > performance_http_utils.go

9. まとめ

今回の最適化プロジェクトを通じて、以下のことを学びました:

技術的成果

  • 41.3%(10,206行)の削減を機能を損なうことなく実現
  • 開発効率を75%向上(新Lambda関数作成時間)
  • 保守コストを50%削減(コードレビュー時間)

プロセス的成果

  • 段階的アプローチによる安全な大規模リファクタリング
  • 自動化ツールによる効率的な未使用コード検出
  • 継続的テストによる品質保証

チーム的成果

  • 統一されたコーディングパターンによるチーム開発効率向上
  • 新規参加者の学習コスト34%削減
  • ドキュメント化文化の確立

10. 次のステップ

この経験を活かして、今後は以下に取り組む予定です:

  1. 予防的品質管理

    • CI/CDパイプラインに未使用コード検出を組み込み
    • 定期的なコード健全性チェック
  2. 設計原則の確立

    • 共通ハンドラーパターンのベストプラクティス文書化
    • チーム内でのコーディング規約更新
  3. 知見の共有

    • 他プロジェクトへの適用
    • チーム勉強会での経験共有