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. 次のステップ
この経験を活かして、今後は以下に取り組む予定です:
予防的品質管理
- CI/CDパイプラインに未使用コード検出を組み込み
- 定期的なコード健全性チェック
設計原則の確立
- 共通ハンドラーパターンのベストプラクティス文書化
- チーム内でのコーディング規約更新
知見の共有
- 他プロジェクトへの適用
- チーム勉強会での経験共有


