メインコンテンツにスキップ
Go Gopher
TDDによるリファクタリング

Authorizationヘッダー解析の重複コード除去:TDDで実現するDRY原則

Authorizationヘッダー解析の重複コード除去:TDDで実現するDRY原則

はじめに

こんにちは!この記事では、Go言語を使ったAWS Lambda開発でよくある「コードの重複」という問題を、スマートに解決する方法を紹介します。特に、多くのAPIで必要になる**認証部分(Authorizationヘッダーの解析)**に焦点を当てます。

「同じようなコードを何度も書いているな…」 と感じたことがある方、特にGoを学び始めたばかりの方や、**テスト駆動開発(TDD)**に興味がある方に、具体的な改善プロセスを体験していただける内容です。

この記事でやること

cmd/get-user/main.goというファイルで見つかった認証処理の重複コードを、TDDという開発スタイルでリファクタリング(改善)し、DRY原則を適用していく過程を、順を追って見ていきます。

【用語解説】

  • TDD (テスト駆動開発): Test-Driven Developmentの略。まず「失敗するテスト」を書き、そのテストをパスするための最小限のコードを実装し、最後にコードを綺麗に整える、というサイクルで開発を進める手法です。バグを減らし、設計をきれいに保つ効果があります。
  • DRY原則: Don’t Repeat Yourselfの略。「同じことを二度書くな」という、プログラミングにおける非常にシンプルで重要な原則です。
  • リファクタリング: 外部から見たときの挙動を変えずに、内部のコードをより良く、きれいに整理整頓することです。

🚨 問題発見:コピペが招いた悲劇

ある日、cmd/get-user/main.go の中で、50行以上にもわたる認証処理のコードが見つかりました。他の場所にも同じようなコードがあるかもしれません。

実際のコード

// ❌ 問題のコード(52行の重複実装)
func businessHandler(ctx context.Context, request events.APIGatewayProxyRequest, 
    log *logger.Logger, metricsClient *middleware.MetricsClient) (events.APIGatewayProxyResponse, error) {
    
    // Authorizationヘッダーを取り出す
    authHeader := request.Headers["Authorization"]
    if authHeader == "" {
        authHeader = request.Headers["authorization"] // 小文字も試す
    }

    // ...ログ出力処理...

    if authHeader == "" {
        // ヘッダーがない場合のエラー処理
        return utils.CreateErrorResponse(401, "Unauthorized", "Authorization header missing"), nil
    }

    // "Bearer <token>" の形式をパースする
    parts := strings.Split(authHeader, " ")
    if len(parts) != 2 || parts[0] != "Bearer" {
        // フォーマットが違う場合のエラー処理
        return utils.CreateErrorResponse(401, "Unauthorized", "Invalid authorization header format"), nil
    }

    accessToken := parts[1]
    // ... 続く処理
}

何が問題だったのか?

  1. コードの重複: まさにDRY原則に反しています。もし認証の仕組みを少し変えたくなったら、同じようなコードが書かれている場所を全部探し出して、1つずつ修正しなければなりません。大変ですし、修正漏れがバグの原因になります。
  2. 隠れたバグ:
    • parts[0] != "Bearer": "bearer" のように小文字で送られてきたら、不正なフォーマットとして弾いてしまいます。
    • strings.Split(authHeader, " "): "Bearer (スペースがいっぱい) token" のような予期せぬ入力に対応できず、うまくトークンを取り出せません。
  3. 保守性の低下: このコードを修正するのは、まるで地雷原を歩くようなものです。どこを直せばいいのか分かりにくく、変更による副作用も怖いですよね。
  4. テストのしにくさ: 認証処理とビジネスロジックがごちゃ混ぜになっていて、認証部分だけを抜き出してテストするのが困難です。

✅ 救世主発見:既存の優秀な共通関数

実は、プロジェクト内をよく探してみると、pkg/utils/auth.go に、この問題を完璧に解決してくれる、とても優秀な共通関数(ユーティリティ関数)が既に存在していました。

// ✅ 既存の優れた実装 in pkg/utils/auth.go
// ExtractTokenFromRequest は、リクエストのヘッダーからJWTトークンを抽出します。
// 大文字・小文字を区別せず、様々な形式のヘッダーに対応します。
func ExtractTokenFromRequest(request events.APIGatewayProxyRequest) (string, error) {
    // ...(中略)...
}

// ExtractTokenFromHeader は、"Bearer <token>"という文字列からトークン部分だけを抽出します。
func ExtractTokenFromHeader(authHeader string) (string, error) {
    // ...(中略)...
    // strings.Fields() を使っているので、複数スペースにも対応!
    parts := strings.Fields(authHeader) 
    // strings.ToLower() で小文字に変換しているので、"bearer"でもOK!
    if strings.ToLower(parts[0]) != "bearer" {
        return "", ErrInvalidAuthFormat
    }
    // ...(中略)...
}

なぜこちらの実装が優れているのか?

項目問題のコード既存の共通関数
複数スペース対応strings.Split()strings.Fields()
大文字小文字対応"Bearer" 固定strings.ToLower()
エラーの種類❌ ざっくり✅ 4種類の詳細なエラー
テスト❌ しにくい✅ 単体テスト完備
再利用性❌ コピペが必要✅ 関数を呼び出すだけ

新しい機能を作るとき、**「もしかして、同じような機能がもうどこかにあるかも?」**と探してみる一手間が、未来の自分を救うことになります。

🧪 TDDスタイルで安全にリファクタリングしよう

それでは、いよいよTDDのサイクル「Red → Green → Refactor」に沿って、安全にコードを改善していきます。

🔴 Red Phase: わざとテストを失敗させる

目的:これから解決する問題を、テストコードで明確に「見える化」する。

まず、現在の実装の問題点をあぶり出すためのテストを書きます。この段階では、テストが失敗するのが成功です。なぜなら、それが「問題が正しく再現できている」証拠だからです。

// cmd/get-user/authorization_test.go
func TestAuthorizationExtraction_Consistency(t *testing.T) {
    testCases := []struct {
        name          string
        headers       map[string]string
        // ...
    }{
        // ...
        {
            name: "小文字のbearer(現在の実装では失敗するはず)",
            headers: map[string]string{"Authorization": "bearer my-token"},
            // ...
        },
        {
            name: "複数スペース(現在の実装では失敗するはず)",
            headers: map[string]string{"Authorization": "Bearer   my-token"},
            // ...
        },
    }

    // ... テスト実行ロジック ...
    // 期待:現在の実装(currentToken)ではエラーになり、共通関数(utilsToken)では成功する
}

このテストを実行すると、コンソールに「テストが失敗しました!」という赤い文字が表示されます。これで準備完了です。

🟢 Green Phase: テストをパスさせる最小の変更

目的:とにかくテストをパスさせる(緑にする)。完璧なコードは目指さない。

次に、先ほど失敗したテストを成功させるための、最小限のコード変更を実施します。ここでは、ごちゃごちゃした自前の実装を捨てて、先ほど見つけた優秀な共通関数 utils.ExtractTokenFromRequest を呼び出すように変更します。

// ✅ 改善後のコード(たったこれだけ!)
func businessHandler(ctx context.Context, request events.APIGatewayProxyRequest, 
    log *logger.Logger, metricsClient *middleware.MetricsClient) (events.APIGatewayProxyResponse, error) {
    
    // ...(前略)...

    // 改善ポイント:自前の50行以上の処理を、共通関数の呼び出し1行に置き換える!
    accessToken, err := utils.ExtractTokenFromRequest(request)
    if err != nil {
        // エラーハンドリングも、共通関数が返す詳細なエラーを使うことで、より丁寧になる
        // ...(エラー処理)...
        return response, nil
    }

    // ...(後略)...
}

この状態で再度テストを実行すると、今度は「テストが成功しました!」という緑の文字が表示されるはずです。やりましたね!

🔄 Refactor Phase: コードを整理整頓する

目的:テストが成功している状態を保ちながら、コードをより美しく、分かりやすくする。

Greenの段階でテストは通りましたが、エラーハンドリングの部分がまだ少し長くて読みにくいかもしれません。そこで、このエラー処理部分をさらに別の関数 handleAuthenticationError として切り出します。

// ✅ 最終的に短縮されたメインロジック
func businessHandler(...) (events.APIGatewayProxyResponse, error) {
    // ...(前略)...

    // 認証処理は、たったの3行に!
    accessToken, err := utils.ExtractTokenFromRequest(request)
    if err != nil {
        // エラー処理は別の関数に任せることで、メインの流れがスッキリ
        return handleAuthenticationError(err, ...), nil
    }

    // ...(後略)...
}

// ✅ エラーハンドリングを専門に行う関数
// こうすることで、他の場所でも同じエラー処理を使い回せるようになる
func handleAuthenticationError(...) events.APIGatewayProxyResponse {
    // ...(エラーの種類に応じて、適切なレスポンスを作成する処理)...
}

これで、メインの処理の流れが非常にスッキリし、誰が読んでも「ここで認証をやっているんだな」と一目で分かるようになりました。もちろん、この変更の後もテストがすべて成功することを確認します。

📊 驚きの改善結果!

今回のリファクタリングで、どれだけの効果があったか見てみましょう。

コード行数の劇的削減

項目改善前改善後削減率
認証処理のコード行数52行3行94%削減!

52行の複雑なコードが、たった3行の分かりやすいコードに生まれ変わりました。

機能と品質の向上

機能改善前改善後
複数スペース対応
大文字小文字対応
エラーの詳細度
堅牢性

共通関数を使ったことで、自分では気づかなかったような細かいケースにも対応できるようになり、コードの品質が格段に向上しました。

💡 この経験から学んだこと

  1. 重複コードの隠れたコストを侮らない: 「ちょっとくらいコピペしてもいいや」という気持ちが、将来の大きな負債になります。
  2. TDDは安全ネット: TDDのサイクルを回すことで、まるで安全ネットの上で作業するように、安心してコードの変更ができました。
  3. 「作る前に探す」は最高の時短術: 最高のコードは「書かないコード」です。チームの資産(共通関数など)を有効活用することが、品質とスピードを両立する鍵でした。

まとめ

今回は、認証処理のコード重複という身近な問題を題材に、TDDを用いたリファクタリングのプロセスを体験しました。

  • 定量的成果: 94%のコード削減とパフォーマンス向上を達成。
  • 品質的成果: 潜在的なバグを修正し、コードの保守性と堅牢性を大幅に向上。
  • プロセス改善: TDDとDRY原則の威力を実感。