【DRY原則と責務】同じコードでも責務が異なる場合は共通化すべきではない
概要
同じコードブロックが頻出しているからといって必ずしも共通化すべきではなく、論理的に同じ責務を担うコードを共通化すべきです。
この記事では DRY原則 と 責務 という観点から、共通化すべきコードと共通化すべきでないコードを示します。
DRY原則とは?
DRY (Don't Repeat Yourself) 原則は「知識を重複させない」ことを目的としています。
重要なのは同じ知識を複数箇所に持たないことであり、単に「見た目のコードが似ている」かどうかは本質ではありません。
責務とは?
責務とは「そのコードが果たすべき役割・意味」です。
コードがどのような目的を持ち、どの場面で必ず同じ挙動をすべきかを考えることが、共通化すべきかどうかの判断材料になります。
共通化すべきコード
共通化すべきコードは、同じ責務を持ち、任意の空間で統一されるべき知識です。
例として「有効ユーザーを取得するスコープ」はどの機能でも同じ条件であるべきです。
# 共通化すべきコード
scope :active, -> { where(deleted_at: nil, status: :active) }
# 呼び出し側は必ずこれを使う
User.active
この場合、共通化していないと以下のリスクがあります。
- Aチームのコード:
User.where(status: :active) - Bチームのコード:
User.where(deleted_at: nil)
→ 結果として「どれが正しいのか分からない」「一部で論理が抜け落ちる」などの不整合が生まれる。
また、パスワードのバリデーションルールのように、様々な箇所で同じ振る舞いをすべきものは共通化が必要です。
# 共通化すべきパスワードの正規表現
PASSWORD_REGEX = /\A(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d]{8,}\z/
validates :password, format: { with: PASSWORD_REGEX }
これを共通化せずに各所でバラバラに定義してしまうと、
- ある画面では8文字以上、別の画面では6文字以上
- あるAPIでは記号が使えるが、別のAPIでは使えない
といった不整合が発生する可能性があります。
パスワードルールのように一貫性が求められる知識は、必ず共通化すべきです。
共通化すべきでないコード
一方で、偶然同じだけで、実際には責務が異なるコードは共通化すべきではありません。
# A機能: レポート用
Order.where(user_id: user.id).order(created_at: :desc).limit(10)
# B機能: ダッシュボード表示用
Order.where(user_id: user.id).order(created_at: :desc).limit(10)
一見同じですが、
- A機能は「分析のための最新注文を取得」
- B機能は「UI表示のための最新注文を取得」
という異なる責務を持っています。
この2つを共通化してしまうと「必ず同じであるべき」と誤解を招きます。
将来の仕様変更時に共通化を維持しようとして、関数の中に分岐が追加されるなど複雑性が増す恐れがあります。
# 悪い例: 不適切な共通化を維持しようとして複雑になった関数
def fetch_recent_orders(user, for_dashboard: false)
query = Order.where(user_id: user.id).order(created_at: :desc).limit(10)
if for_dashboard
# ダッシュボード用に追加の条件
query = query.where.not(status: :canceled)
end
query
end
このように、本来別々に存在してよかったコードを無理に共通化したことで、
- 不要な分岐が増えることでテスト容易性が下がりバグ発生リスクが高まる
- 認知負荷が上がり、可読性・保守性が低下する
- 仕様変更時や調査時に影響範囲を把握しづらくなる
といった問題を引き起こします。これはDRY原則に従うどころか、かえって複雑性と保守コストを高める結果となります。
判断基準
- 論理的に同じ責務を持つコード → 共通化すべき
- 偶然同じだけで責務が異なるコード → 共通化すべきでない
つまり、「同じコードがある」=「共通化すべき」ではなく、責務と知識の重複があるかどうかで判断することが重要です。
最後に
DRY原則を守ることは重要ですが、それは「コードを減らす」ためではなく「責務を整理し知識を一元化する」ためです。
コード量を減らすことに価値を感じだすと設計が歪むので、その考えは捨てましょう。
共通化するかどうかを判断する際は、
- このコードは同じ責務を担っているか?
- 偶然同じだけではないか?
- 将来の仕様変更で不要な分岐を生むことにならないか?
を意識することが大切です。
Discussion