Goのレスポンス生成、複雑にしすぎてませんか?YAGNI原則で劇的に改善する方法
はじめに:善意から生まれた「複雑すぎる」コード
「詳細なデバッグ情報があったほうが良い」「パフォーマンスも計測できるようにしよう」「将来の拡張性も考慮して…」
こうした開発者の善意や先見の明が、時としてコードを複雑怪奇なモンスターに育て上げてしまうことがあります。今回の物語は、まさにそのような経緯で生まれた、243行にも及ぶ「多機能すぎる」レスポンス生成処理を、設計原則に則って劇的に簡素化したリファクタリングの記録です。
問題のコード:メタデータ満載、243行のモンスター関数
問題のコードは、APIレスポンスを生成するためだけのパッケージでした。しかし、その内部は過剰な機能で肥大化していました。
// ❌ 悪い例:pkg/utils/response.go(243行の複雑な実装)
package utils
// ... (6つの複雑な構造体定義) ...
type EnhancedAPIResponse struct {
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
Metadata ResponseMetadata `json:"metadata"` // 誰が使う?
// ...
}
type ResponseMetadata struct {
StatusCode int `json:"status_code"`
ProcessingTime float64 `json:"processing_time_ms"` // 本当に必要?
ServerInfo ServerInfo `json:"server_info"`
DataValidation ValidationInfo `json:"data_validation"`
Performance PerformanceInfo `json:"performance"`
}
// ... (さらに4つのメタデータ用構造体) ...
// メイン関数(100行以上の複雑な処理)
func CreateEnhancedResponse(statusCode int, message string, data interface{}, err string) events.APIGatewayProxyResponse {
// 1. 複雑なメタデータ構築
// 2. reflectパッケージを駆使した過剰なデータ検証
// 3. 詳細すぎるログを6回も出力
// 4. 複雑なフォールバック処理
// 5. パフォーマンス情報を埋め込むためにレスポンスを再マーシャリング(!!)
// 6. 10個以上のHTTPヘッダーを設定
// ...
return events.APIGatewayProxyResponse{ /* ... */ }
}
なぜこのコードは「悪」なのか。
| 問題点 | 詳細 |
|---|---|
| パフォーマンス | 1回のレスポンス生成に50-70msもかかっていました。反射処理、過剰なログ、再マーシャリングなどが原因で、本来のビジネスロジックよりも遅いという本末転倒な事態に。 |
| 過剰な機能 | フロントエンドで実際に使われていたのはmessage, data, errorの3つだけ。苦労して実装したメタデータは誰にも使われていませんでした。 |
| 保守性の低さ | コードが長大で複雑なため、少し修正するだけでも全体を理解する必要があり、デバッグも困難。まさに「触りたくないコード」の典型です。 |
| テストの複雑さ | 50行以上のアサーションが必要な、複雑で実行の遅いテストになっていました。 |
解決策:YAGNI原則「本当に必要なものだけを作る」
この問題を解決する指針は、YAGNI (You Aren’t Gonna Need It) 原則です。「将来必要になるかもしれない」という予測に基づいて機能を実装するのではなく、「今、本当に必要なものだけを実装する」という考え方です。
Step 1: 本当に必要な機能を特定する
まず、このレスポンス生成処理に本当に必要な機能を洗い出しました。
- HTTPステータスコード
- メッセージ (
message) - 成功時のデータ (
data) - エラー時の詳細 (
error) - 基本的なHTTPヘッダー (
Content-Typeなど)
結論:メタデータ、パフォーマンス計測、過剰な検証はすべて不要。
Step 2: シンプルな構造体と基本関数を設計する
不要なものをすべて削ぎ落とし、必要最小限の構造体と基本関数を再設計しました。
// ✅ 改善案:シンプルで必要十分な実装
package utils
import (
"encoding/json"
"net/http"
"github.com/aws/aws-lambda-go/events"
)
// シンプルなレスポンス構造
type APIResponse struct {
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
// CreateResponseはレスポンス生成の基本となるシンプルな関数
func CreateResponse(statusCode int, message string, data interface{}, errStr string) events.APIGatewayProxyResponse {
response := APIResponse{
Message: message,
Data: data,
Error: errStr,
}
body, marshalErr := json.Marshal(response)
if marshalErr != nil {
// シンプルなフォールバック
statusCode = http.StatusInternalServerError
body = []byte(`{"message":"Response serialization failed","error":"JSON marshal error"}`)
}
return events.APIGatewayProxyResponse{
StatusCode: statusCode,
Headers: map[string]string{"Content-Type": "application/json"},
Body: string(body),
}
}
Step 3: 開発者体験(DX)を向上させるヘルパー関数を追加する
基本関数をラップし、よく使うパターンのための便利なヘルパー関数を用意します。これにより、APIハンドラー側のコードがより簡潔で読みやすくなります。
// ✅ 使いやすいヘルパー関数群
func CreateSuccessResponse(message string, data interface{}) events.APIGatewayProxyResponse {
return CreateResponse(http.StatusOK, message, data, "")
}
func CreateErrorResponse(statusCode int, message string, err string) events.APIGatewayProxyResponse {
if statusCode < 400 {
statusCode = http.StatusInternalServerError
}
return CreateResponse(statusCode, message, nil, err)
}
func CreateNotFoundResponse(message string) events.APIGatewayProxyResponse {
return CreateErrorResponse(http.StatusNotFound, message, "Resource not found")
}
// ... 他にも `CreateValidationErrorResponse` など、用途に応じたヘルパーを用意
Step 4: 簡潔になったハンドラーの実装
ヘルパー関数のおかげで、APIハンドラーのコードは驚くほどシンプルになります。
// ✅ 改善後のハンドラー:意図が明確で簡潔
func handleGetUser(userID string) events.APIGatewayProxyResponse {
user, err := getUserByID(userID)
if err != nil {
if errors.Is(err, ErrUserNotFound) {
return utils.CreateNotFoundResponse("User not found") // 1行でエラーレスポンス
}
return utils.CreateErrorResponse(500, "Failed to retrieve user", err.Error())
}
return utils.CreateSuccessResponse("User retrieved successfully", user) // 1行で成功レスポンス
}
劇的な改善効果:数値で見るシンプルさの価値
| 項目 | Before (複雑) | After (シンプル) | 改善率 |
|---|---|---|---|
| パフォーマンス | 50,000 ns/op | 1,200 ns/op | 42倍 高速化 |
| コード量 | 243行 | 110行 | 52% 削減 |
| メモリ使用量 | 1.5 MB/op | 0.1 MB/op | 93% 削減 |
| テストコード | 50行以上 | 15行 | 70% 削減 |
まとめ:「完璧さ」とは、何も削れなくなった状態である
完璧とは、これ以上付け加えるものがなくなった時ではなく、これ以上削るものがなくなった時に達成される。 ― アントワーヌ・ド・サン=テグジュペリ
今回のリファクタリングは、まさにこの言葉を体現するものでした。機能を「加える」のではなく、不要なものを「削る」ことで、コードはより速く、より軽く、より強くなったのです。
シンプル設計のためのチェックリスト
- YAGNI: その機能は「今」本当に必要か。「将来使うかも」という予測で作っていないか。
- KISS (Keep It Simple, Stupid): もっと単純な実装方法はないか。標準ライブラリで解決できないか。
- No Reflection: パフォーマンスが重要な箇所で、安易に
reflectパッケージを使っていないか。 - Measure, Don’t Guess: パフォーマンス改善は、推測ではなく計測に基づいて行っているか。
複雑さに立ち向かう最良の武器は、いつだってシンプルさです。


