
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]
// ... 続く処理
}
何が問題だったのか?
- コードの重複: まさにDRY原則に反しています。もし認証の仕組みを少し変えたくなったら、同じようなコードが書かれている場所を全部探し出して、1つずつ修正しなければなりません。大変ですし、修正漏れがバグの原因になります。
- 隠れたバグ:
parts[0] != "Bearer":"bearer"のように小文字で送られてきたら、不正なフォーマットとして弾いてしまいます。strings.Split(authHeader, " "):"Bearer (スペースがいっぱい) token"のような予期せぬ入力に対応できず、うまくトークンを取り出せません。
- 保守性の低下: このコードを修正するのは、まるで地雷原を歩くようなものです。どこを直せばいいのか分かりにくく、変更による副作用も怖いですよね。
- テストのしにくさ: 認証処理とビジネスロジックがごちゃ混ぜになっていて、認証部分だけを抜き出してテストするのが困難です。
✅ 救世主発見:既存の優秀な共通関数
実は、プロジェクト内をよく探してみると、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行の分かりやすいコードに生まれ変わりました。
機能と品質の向上
| 機能 | 改善前 | 改善後 |
|---|---|---|
| 複数スペース対応 | ❌ | ✅ |
| 大文字小文字対応 | ❌ | ✅ |
| エラーの詳細度 | ❌ | ✅ |
| 堅牢性 | △ | ◎ |
共通関数を使ったことで、自分では気づかなかったような細かいケースにも対応できるようになり、コードの品質が格段に向上しました。
💡 この経験から学んだこと
- 重複コードの隠れたコストを侮らない: 「ちょっとくらいコピペしてもいいや」という気持ちが、将来の大きな負債になります。
- TDDは安全ネット: TDDのサイクルを回すことで、まるで安全ネットの上で作業するように、安心してコードの変更ができました。
- 「作る前に探す」は最高の時短術: 最高のコードは「書かないコード」です。チームの資産(共通関数など)を有効活用することが、品質とスピードを両立する鍵でした。
まとめ
今回は、認証処理のコード重複という身近な問題を題材に、TDDを用いたリファクタリングのプロセスを体験しました。
- 定量的成果: 94%のコード削減とパフォーマンス向上を達成。
- 品質的成果: 潜在的なバグを修正し、コードの保守性と堅牢性を大幅に向上。
- プロセス改善: TDDとDRY原則の威力を実感。


