はじめに
Tidy First? を読んだメモです
→ Oreilly - Tidy First ?
まとめ
複雑な理論よりも、実際に手を動かして理解できる 小さな整頓 を積み重ねることの重要性を説いた本。
重要なのは、小さく、安全に、繰り返す こと。整頓は目的ではなく、より良いコードを作るための手段 であること。
整頓は 小さく安全 に、必要なときに 行うことが重要。
整頓プロセス を チームに浸透 させ、持続可能な開発体制を構築する。
短期的なリターンだけでなく、 長期的なメンテナンス性向上 を意識する。
結合と凝集 は、コードの可読性・保守性を左右する重要な要素。
経済性の観点から整頓のタイミングと規模を判断し、最適なコストバランス を取る。
認知負荷 を意識した整頓により、開発者の生産性やモチベーションを維持できる。
整頓は単なる コードの美化 ではなく、長期的なプロジェクト成功のための戦略的行動 。
『Tidy First?』 読んだメモ
ソフトウェア設計の基本理念
ソフトウェア設計では「結合(Coupling)」と「凝集(Cohesion)」が重要な指標となる。
- 結合: コード間の依存関係が強いと、変更の影響範囲が広がり、保守性が低下する。
- 凝集: 関連性の高い機能やデータをまとめることで、コードの理解や変更が容易になる。
設計のゴールは「結合を弱め、凝集を高める」ことにより、柔軟で拡張性のあるシステムを作ること。
整頓(Tidy)の定義
整頓(Tidy)とは、コードの振る舞いを変えずに構造を改善する小さなリファクタリングを指す。具体的には以下のような作業を含む。
- ガード節の導入: ネストを避けてコードを読みやすくする。
- デッドコードの削除: 使用されていないコードを排除する。
- 変数の説明化: マジックナンバーを定数に置き換えたり、複雑な式を説明変数に置き換える。
- 明示的なパラメーター: 関数の引数を明示的にし、依存関係をわかりやすくする。
経験主義的ソフトウェア設計
設計のタイミングを絶対視せず、必要に応じて段階的に設計を進めるという考え方である、経験主義的ソフトウェア設計がおすすめ。
- 推測的設計: 先を見越して設計しすぎるリスクがある。
- 反応的設計: 必要最小限の設計で進め、後で修正するリスクがある。
- 経験主義的設計: 両者の中間を目指し、実際の開発状況に応じて最適なタイミングで設計を行う。
整頓(Tidy)の力とその意義
整頓(Tidy)は、ソフトウェア開発のコスト削減や品質向上に直結する。小さな整頓(Tidy)を積み重ねることで、以下の効果が期待できる。
- コードの可読性向上
- バグの発生リスク低減
- 開発速度の向上
- 将来的な変更コストの削減
整頓(Tidy)の基本パターン
ガード節(Guard Clause)
ネストを浅くし、条件分岐の可読性を向上させるための手法。
例:
// Before
if (user != null) {
if (user.isActive()) {
sendEmail(user);
}
}
// After
if (user == null) return;
if (!user.isActive()) return;
sendEmail(user);
→ 条件を早期に排除し、メインの処理をシンプルに。
デッドコードの削除
実行されないコードは即座に削除します。コードの安全性を保つため、削除前に利用状況をログで確認することを推奨。
ポイント: バージョン管理を活用すれば、必要に応じていつでも復元可能。
シンメトリーを揃える
似た処理は同じパターンで記述し、コードの一貫性を保つことで読み手の理解を助ける。
例:
# Before
if condition:
result = process_a()
else:
result = process_b()
# After
result = process_a() if condition else process_b()
説明変数と説明定数の活用
長い式や意味の不明瞭な数値・文字列リテラルは、説明変数や定数に置き換える。
例:
# Before
if order.total_price > 10000:
apply_discount()
# After
DISCOUNT_THRESHOLD = 10000
if order.total_price > DISCOUNT_THRESHOLD:
apply_discount()
→ 数値に意味を持たせることで、意図が明確に。
明示的なパラメーター化
関数の依存関係を明確にするため、必要な値は暗黙的にではなく、パラメーターとして渡す。
例:
// Before
function calculateTax() {
return order.price * TAX_RATE;
}
// After
function calculateTax(price, taxRate) {
return price * taxRate;
}
→ テストや再利用が容易に。
整頓(Tidy)の適用タイミングと判断基準
整頓(Tidy)を行うべきか、あるいは後回しにすべきかの判断基準。
-
整頓すべきタイミング
- コードの理解に時間がかかる場合
- 変更箇所が複数に分散している場合
- 将来的な機能追加や修正を見越している場合
-
整頓を避けるべきタイミング
- 緊急のバグ修正などで納期が逼迫している場合
- 整頓によって逆に複雑化しそうな場合
経験主義的な視点として、完璧を目指さず、今必要な最小限の整頓を行うことが推奨されている。
整頓の実践プロセス
以下のようなフローを意識して、整頓は「小さな一歩の積み重ね」として行う。
- コードを読む: まずはコードの意図と構造を理解。
- 小さく手を加える: ガード節の追加や説明変数の導入など、リスクの少ない整頓から着手。
- ビルドとテスト: 変更ごとに必ず実行して問題がないか確認。
- コミット: 整頓と機能変更は分けてコミットする。
- 繰り返す: 小さな改善を繰り返し、コードベース全体を改善していく。
「整頓」の効果と落とし穴
-
効果:
- コードの理解と修正が容易になる。
- 開発速度が向上し、バグが減少する。
- 新たな機能追加時の柔軟性が向上する。
-
落とし穴:
- 過剰な整頓(リファクタリング症候群)に注意。
- 「整頓」が目的化してしまわないように。
整頓の戦略的活用
先に整頓?それとも後に整頓?
「Tidy First?」のタイトルにある通り、「変更の前に整頓するべきか?」という疑問は、ソフトウェア開発者なら誰もが直面する問題となる。以下の基準で判断することを推奨している。
-
先に整頓する場合:
- コードが非常に読みづらく、意図を理解するだけで時間がかかるとき
- 変更作業が複雑で、既存コードの問題がその作業を妨げるとき
- 将来の変更を見据えて、長期的に見てメリットがある場合
-
後に整頓する場合:
- 変更の緊急性が高く、納期が優先される場合
- コードの現状が変更に大きく影響しない場合
- 短期的なリリース後に整頓を行う計画がある場合
実践アドバイス
整頓のタイミングは「ケースバイケース」。まずは影響範囲の小さい整頓から始め、必要に応じて大規模な整頓に踏み切りる。
整頓の「やめどき」
「整頓」に夢中になりすぎると、目的を見失いかねない。整頓の「やめどき」を見極めることが大切。
-
やめるべきタイミング:
- 必要以上に時間をかけていると感じたとき
- 整頓によって機能追加や修正が遅れているとき
- 整頓の効果が薄いと判断できるとき
チームでの整頓プロセスの管理
コミュニケーションの重要性
整頓は個人作業だけでなく、チーム全体に影響を与る。特に大規模なコードベースでは、整頓の影響範囲が広がりやすいため、チーム内での事前共有が重要。
-
整頓作業の共有方法:
- Pull Request (PR)の活用: 整頓内容とその目的をPRの説明欄に明記。
- コードレビュー: 整頓による影響範囲や副作用の有無をチェック。
- ミーティングでの共有: 大きな整頓作業は事前に周知し、スケジュールを調整。
整頓の粒度と優先順位付け
整頓は「小さく」「安全に」「確実に」を原則とする。しかし、実際には膨大な量のコードがあり、すべてを一気に整頓するのは非現実的。以下の優先順位付けを行う。
-
優先度の高い整頓対象:
- 頻繁に変更が加えられるコード
- バグ発生率が高い箇所
- 他の機能に影響を与えやすい中心的なロジック
-
優先度の低い整頓対象:
- ほとんど触れられない古いコード
- 短期間で廃止予定の機能
小さなバッチサイズで進める
大規模な整頓は避け、小さな変更を繰り返し行うことで安全性を確保する。これにより、バグの混入リスクを抑えつつ、コードベースの改善を進められる。
-
実践例:
- 1つの関数を整頓したら、テストを実行し、問題がないか確認してから次の整頓に進む。
- 大規模なクラスを一度に整頓するのではなく、機能ごとに分けて整頓する。
整頓をプロジェクトに組み込む方法
整頓のための時間を確保する
多くの開発チームでは、整頓が「後回し」にされがち。定期的に整頓のための時間を確保する。
-
整頓のためのタイムボックスを設ける:
- スプリントの最後に半日を整頓に充てる
- 「リファクタリングデー」を設けて集中的に整頓する
整頓のKPIを設定する
整頓の効果を数値として可視化することで、チーム全体のモチベーションを維持できる。
-
例:
- コードカバレッジ率の向上
- バグ発生率の低下
- コードレビューでの指摘事項の減少
整頓文化を育む
整頓を一過性のものにせず、日常の開発プロセスに組み込むことが理想。
-
整頓を推奨する文化の醸成:
- コードレビューで整頓の提案を歓迎する
- 小さな整頓でも積極的に称賛する
- 「汚れたコードを放置しない」意識を持つ
整頓が生む長期的なメリット
- 変更コストの削減: 整頓されたコードは変更の影響範囲が少なく、バグの発生率も低下する。
- 新機能の追加が容易に: 柔軟性の高い構造は、迅速な機能追加を可能にする。
- チーム全体の生産性向上: 読みやすいコードは、オンボーディングやレビューの効率化にもつながる。
深堀り
ソフトウェア設計の基本理論
結合(Coupling)
結合とは、コード同士の依存関係の強さを示すr。強い結合は保守性を下げ、弱い結合は柔軟性を高める。
-
強い結合の問題点:
- 変更の影響範囲が広がり、バグの温床となる。
- 再利用性が下がり、テストが困難になる。
-
弱い結合を実現する方法:
- インターフェースや抽象クラスの利用
- 依存性注入(Dependency Injection)
- イベント駆動設計(メッセージパッシングなど)
整頓のポイント:
結合が強い箇所は変更が難しくなるため、整頓の優先対象とする。可能な限り、コード間の依存関係を減らし、モジュール単位で独立性を高める。
凝集(Cohesion)
凝集は、モジュール内の要素がどれだけ密接に関連しているかを示す。凝集が高いほど、そのモジュールの責務が明確になる。
-
凝集度の高いコードの特徴:
- 単一の目的や責務に集中している。
- 変更やバグ修正の影響範囲が限定される。
-
凝集度を高める方法:
- 関数やクラスに単一責任の原則(SRP)を適用
- 関連するロジックを近くに配置
- モジュールの粒度を適切に保つ
整頓のポイント:
コードが分散している場合、凝集度を高めるために、関連するコードを近くに集めましょう。これにより、変更や拡張のコストが削減されます。
経済性と整頓
時間価値とオプショナリティ
-
時間価値の原則:
「今日の1ドルは明日の1ドルより価値がある」という考え方。整頓のタイミングを見極め、リターンが早く得られる部分から着手することが重要。 -
オプショナリティ(Optionality)の考え方:
整頓を行うことで、将来的な選択肢を増やすことができる。たとえば、適切な抽象化により、異なる技術への置き換えや機能追加が容易になる。
整頓の実践:
整頓の優先順位を決める際には、「将来的にどの程度の柔軟性が得られるか?」という観点で考える。
オプション vs キャッシュフロー
-
オプションの価値:
整頓によって新たな設計の選択肢が生まれ、将来的な機能追加や技術変更に柔軟に対応できるようになる。 -
キャッシュフロー:
一方で、整頓にはコストがかかるため、短期的なリターンを考慮した意思決定も必要。
意思決定のバランス:
整頓によるオプションの価値と、短期的なキャッシュフローのバランスを考慮し、プロジェクト全体の利益を最大化することが理想。
整頓と認知負荷
認知負荷理論
-
認知負荷が高いコードの問題点:
- コードの理解に時間がかかる。
- バグの発生率が高まる。
- 開発者がストレスを感じやすくなる。
整頓による解決策:
-
読みやすいコードを意識する:
ネストを減らし、条件分岐を明示化することで認知負荷を軽減。 -
意味のある命名を行う:
マジックナンバーや曖昧な変数名を避け、意図が伝わるコードに。
結合と凝集の心理的効果
- 高凝集・低結合のコードは、心理的負担が少なくなり、開発効率が向上する。
- バグ修正や機能追加がスムーズに行えるため、開発者のモチベーションも維持できる。
理論を応用した整頓の実践例
コードの再構成
例: モノリシックな関数を小さな関数に分割して凝集度を高め、結合を弱める。
Before:
def process_order(order):
if order.status != "completed":
print("Order not completed")
return
# 在庫確認
if not check_inventory(order):
print("Out of stock")
return
# 請求書の作成
invoice = create_invoice(order)
send_invoice(invoice)
After:
def process_order(order):
if not is_order_completed(order):
return
if not is_inventory_available(order):
return
handle_invoice(order)
def is_order_completed(order):
if order.status != "completed":
print("Order not completed")
return False
return True
def is_inventory_available(order):
if not check_inventory(order):
print("Out of stock")
return False
return True
def handle_invoice(order):
invoice = create_invoice(order)
send_invoice(invoice)
→ 凝集度が高まり、機能ごとの役割が明確化され、テストや保守が容易に。
オプショナリティを活かした設計
例えば、プラグインアーキテクチャを採用することで、将来的な機能追加の柔軟性を確保できる。
- 初期の開発では最小限のプラグインを実装。
- 後から必要に応じて機能を追加可能。
このように「オプション」を残す設計により、将来的な変更コストを削減できる。
次のステップ
- プロジェクト内の「整頓候補」をリストアップする。
- 小さな整頓から始め、実践を通じて整頓スキルを高める。
- チーム内で整頓の成功事例を共有し、継続的改善を推進する。
リンク
さいごに
勉強になりました。