前置き
新しい開発プロジェクトに取り組む中で、設計の重要性を再確認する必要があると感じました。
「良いコード/悪いコードで学ぶ設計入門」を読んで、設計に関するいくつかの重要な教訓を学びました。
このメモは、私が特に重要だと感じた設計原則を整理し、今後のプロジェクトにどう活かすかを考えるためのものです。
誰向けの記事か
これは自分用のメモですが、以下の方々にも役立つかもしれません。
- 設計の基本を学び直したいエンジニア
- 新しいプロジェクトに取り組んでいる開発者
- 設計の基本原則について知識を深めたい方
結論
「良いコードと悪いコードで学ぶ設計入門」から得た最大の学びは、日々の開発で我々が無意識に生み出す「設計の悪魔」の存在です。
デッドコード、過度な抽象化、神クラスなど、これらの悪魔はコードの品質を低下させ、プロジェクトの成功を脅かします。
重要なのは、悪魔が悪魔であると認識し日々退治をすることです。この書籍は第一歩として非常に読みやすく勉強になりました。
個人的ポイント
書籍を読んで、特に学びとなったポイントをいくつか挙げます。
1. 高凝集の実現
設計において、関連するデータとロジックを一箇所にまとめることの重要性を再確認しました。
高凝集を実現することで、以下のメリットがあります。
- コードが一貫しており、理解しやすくなる
- 修正が必要な際に影響範囲を明確にできる
- メンテナンスが容易になる
本書でも、データとその操作が分散していると、重複コードが増え、修正漏れが発生しやすくなると強調されています。
2. インターフェース設計と条件分岐
条件分岐は、コードの複雑化を招くことが多いです。
本書では、以下のアプローチが推奨されています。
- いきなり条件分岐を使うのではなく、まずインターフェースとして設計できないかを検討する
- 同じ条件のswitch文が複数存在する場合、インターフェースを使った設計に置き換える
インターフェースを使用することで、新しい支払い方法の追加が容易になり、既存のコードを変更せずに拡張できます。
サンプルコード
// 条件分岐を使用した例
function processPayment(method: string, amount: number) {
switch (method) {
case 'credit_card':
// クレジットカード決済処理
break;
case 'paypal':
// PayPal決済処理
break;
case 'bank_transfer':
// 銀行振込処理
break;
// 他の支払い方法...
}
}
// インターフェースを使用した例
interface PaymentProcessor {
process(amount: number): void;
}
class CreditCardProcessor implements PaymentProcessor {
process(amount: number) {
// クレジットカード決済処理
}
}
class PayPalProcessor implements PaymentProcessor {
process(amount: number) {
// PayPal決済処理
}
}
class BankTransferProcessor implements PaymentProcessor {
process(amount: number) {
// 銀行振込処理
}
}
function processPayment(processor: PaymentProcessor, amount: number) {
processor.process(amount);
}
// 使用例
const creditCardPayment = new CreditCardProcessor();
processPayment(creditCardPayment, 100);
著者の X ではわかりやすく動画として掲載されていました。
クソコード動画「switch文」 #ooc_2020 pic.twitter.com/USTrFcRCAS
— ミノ駆動 (@MinoDriven) February 16, 2020
3. DRY原則の正しい理解
DRY原則は単に「コードの重複を避ける」と間違った解釈されがちです。
- 「意味的に異なるが構造的に似ている」コードを無理に共通化するべきではない
- 似て非なるものを共通化すると、特定のユースケース専用の処理が共通ロジックに混ざり込み、コードが複雑化する
構造が似ているものを共通化すると私も間違った解釈をしていました。
悪魔が悪魔であることを知ることは大切ですね。
こちらも著者の X にてわかりやすく動画化されていました。
クソコード動画「共通化の罠」 pic.twitter.com/MM750CNXc2
— ミノ駆動 (@MinoDriven) May 12, 2019
4. 設計を妨げる開発プロセスとの戦い
本書を通じて、設計品質の低下を招く開発プロセスの問題についても学びました。
- 「早く終わらせたい」という心理が品質を低下させる罠になる
動くコードをとにかく早く書こうとすると、長期的な保守性や拡張性が犠牲になることがある - 時間をかけて設計を行い、品質を確保することが重要
設計品質度外視で、動くコードをとにかく早く書けるプログラマーが一部にはいます。コードが劣悪でも、動いている画面を見ると、非エンジニアを含む現場は喜んでしまいます。口々に「もう実装できたんですか、さすがですね!」ともてはやします。褒められたプログラマーはうれしいですし、早く書けることが、ある種正義のような雰囲気が醸成されていきます。しかし、それが罠なのです。
5. 設計の安全性を損なうたくさんの悪魔
- デッドコード
- 説明
- 使用されていないコードは混乱の元となり、メンテナンスコストを増加させる
- 対策
- 定期的なコードレビューと静的解析ツールの使用で検出し、積極的に削除する
- 説明
- YAGNI(You aren't going to need it)原則
- 説明
- 将来の要件を予測して実装すると、不要な複雑性を招き、実際には使用されないコードが増える
- 対策
- 現在の要件に焦点を当てる
- 説明
- マジックナンバー
- 省略
- 例外の握りつぶし
- 説明
- 例外を捕捉しても適切に処理しないと、バグの原因特定が困難になりる
- 対策
- 例外は適切にログに記録するか、より上位の層に伝播させて処理する
- 説明
- 神クラス
- 説明
- 多すぎる責務を持つクラスは理解が困難で、変更の影響範囲が広くなる
- 対策
- 単一責任の原則に従い、クラスを小さく、焦点を絞ったものに分割する
- 説明
- 退化コメント
- 説明
- コードの変更に追従していないコメントは、誤解や混乱を招く
- 対策
- コメントはコードと一緒に更新し、自明なコメントは削除しましょう。コードの「なぜ」を説明するコメントに注力する
- 説明
まとめ
「良いコードと悪いコードで学ぶ設計入門」から、設計の重要性を再認識しました。高凝集、適切なインターフェース設計、DRY原則の正しい適用、技術的負債の管理が品質の高いソフトウェア開発に不可欠です。
これらの原則を日々の開発に取り入れることで、コードの保守性向上、柔軟な拡張性、チームの開発効率アップが期待できます。ただし、原則は機械的に適用するのではなく、プロジェクトの文脈に応じて適切に判断することが重要です。
良い設計は継続的な学習、実践、チーム内での議論を通じて徐々に実現されます。この学びを活かし、より良いソフトウェア開発に取り組んでいきます。