カレーを作るな、カレールーを入れた煮込みを作れ:抽象化と命名の入門
概要¶
Qiita 人気記事「カレーを作るな、カレールーを入れた煮込みを作れ」をベースにした、超新人エンジニア向けの抽象化と命名の入門。「カレーを作る」という言葉が抽象化された概念であるのと同様に、関数やメソッドの名前も「何をするか」ではなく「何であるか(抽象レベル)」で命名すべき、という考え方。
詳細¶
カレーのメタファー¶
「カレーを作る」と言うとき、人はそれが「玉ねぎを炒め、肉を炒め、水を入れて煮込み、カレールーを入れてさらに煮込む」という一連の操作であると分かった上で、それをまとめて「カレーを作る」と呼んでいる。
逆に「カレールーを入れた煮込み料理を作る」と言うと、実装の詳細が名前に漏れ出している。この名前では「カレーを作る」という概念レベルの理解がない。
プログラミングにおける命名も同じ。実装の詳細を名前に入れると、それは「抽象化できていない証拠」になる。
悪い命名:実装が見える名前¶
// 悪い例: 「MySQLのusersテーブルにユーザーを追加する」という実装が見えている
func AddUserToMySQLUsersTable(user User) error {
_, err := db.Exec("INSERT INTO users ...", user)
return err
}
// 呼び出し側
AddUserToMySQLUsersTable(newUser)
// → 呼び出し側が「MySQLのusersテーブル」という実装を知ってしまう
この命名の問題点: - DB を PostgreSQL に変えたら関数名が嘘になる - 呼び出し側が「実装の詳細」に依存する - 「ユーザーを登録する」という概念が見えない
良い命名:抽象化された名前¶
// 良い例: 「ユーザーを登録する」という概念レベルの名前
func RegisterUser(user User) error {
_, err := db.Exec("INSERT INTO users ...", user)
return err
}
// 呼び出し側
RegisterUser(newUser)
// → 呼び出し側は「ユーザーが登録される」という事実だけを知る
RegisterUser は DB の種類、テーブル名、SQL の書き方を知らなくていい。「ユーザーを登録する」という抽象概念だけを表現している。
抽象化とは「実装の詳細を隠す」こと¶
抽象化とは、複数の具体的な操作をまとめて「一つの概念」として扱えるようにすること。
// 抽象化前: 実装の詳細が散らばっている
rows, err := db.Query("SELECT * FROM users WHERE email = ?", email)
// ... rows を処理 ...
// ... エラーハンドリング ...
// → 呼び出し側が「DBのクエリ方法」を知っている
// 抽象化後: 概念として扱える
user, err := userRepo.FindByEmail(email)
// → 呼び出し側は「メールアドレスでユーザーを見つける」という概念だけを知る
呼び出し側が実装の詳細を知らなくていい状態にすることが、抽象化の本質。
命名は「理解の深さ」を反映する¶
良い命名ができないとき、それは「まだ問題を十分に理解していない」サインであることが多い。
例:
// 命名に詰まる例
func ProcessData(data []byte) error
// → 「処理する」「データ」という雑な言葉は理解が浅い証拠
// 理解が深まった後の命名
func ValidateAndStoreInvoice(invoice Invoice) error
// → 「何を」「どうする」が明確
「どう名前を付けるか分からない」「とりあえず Process にしておく」という状況は、その処理が何をしているのかを自分が分かっていないサイン。
抽象レベルを揃える¶
一つの関数・クラスの中では、同じ抽象レベルの操作を扱うべき。
// 抽象レベルがバラバラ(悪い例)
func CreateOrder(order Order) error {
// 抽象レベル高: ビジネスロジック
if !order.IsValid() {
return ErrInvalidOrder
}
// 抽象レベル低: SQL の詳細
_, err := db.Exec("INSERT INTO orders (id, user_id, ...) VALUES (?, ?, ...)", ...)
return err
}
// 抽象レベルが揃っている(良い例)
func CreateOrder(order Order) error {
if !order.IsValid() {
return ErrInvalidOrder
}
return orderRepo.Save(order) // ← 同じ抽象レベル
}
いつこの考え方を使うか¶
コードレビュー時: - 「この関数名、実装の詳細が漏れていませんか?」 - 「呼び出し側がこの名前から何を知ることになりますか?」
API 設計時:
- エンドポイント名が実装に依存していないか確認する
- POST /mysql-insert-user → POST /users のような抽象化
リファクタリング時: - 長い処理を関数に切り出すとき、「この処理は一言で何と言えるか」を先に考える
なぜ重要か / いつ使うか¶
- 新人エンジニアが「関数名の付け方が分からない」と感じているとき
- レビューで「これ何をしているか分かりにくい」というフィードバックが多いとき
- コードが「実装の変更のたびに呼び出し側も変わる」という問題が起きているとき
- DDD(ドメイン駆動設計)やクリーンアーキテクチャを学ぶ前の下地として
- 「抽象化とは何か」を人に説明しなければならないとき