📑
Tidy First? -個人で実践する経験主義的ソフトウェア設計-を読んで
Tidy First? -個人で実践する経験主義的ソフトウェア設計-を読んで
完読したので、整理に関するテクニックや読後の感想をまとめておく。
整頓術
ガード節は使いすぎない
- ガード節は使いすぎないこと
- 1 つのルーチンに 7 つも 8 つもあると読みにくくなるため
- 整頓後も条件が正しく満たせる場合だけ、ガード節にまとめる
-
if (条件)...のあとに別のコードが続く場合は、if と条件の処理をヘルパーメソッドに抽出すればガード節を使える - 例: dramatiq #470
デッドコードを少しずつ削除する
- 使用状況のログを取り、使われていなければ削除する
- コードは少しずつ削除する
- 万が一間違えても比較的簡単に元に戻せるため
シンメトリーを揃える
- 同じ部分と異なる部分を分離する
- 似ているが同一ではないルーチンを探す
- 余計なものが共通点を隠してしまうことがある
新しいインターフェイスで古い実装を呼ぶ
- 望むインターフェイスを新たに作り、それが古いインターフェイスを呼ぶようにする
- 呼び出し元を移行し終えたら古い実装をインライン化でき、振る舞いの変更が簡単になるため
- 関連する考え方:
- 後ろからコーディング: 必要な中間結果を仮定して最終行から始める
- テストファースト: 先に通るべきテストを書く
- ヘルパー設計: あることを行うルーチン、オブジェクト、サービスを用意すると使いまわせる
読む順番を考慮して整理を始める
- 読み手にとって重要な部分だけ整頓してもよい
- 整頓のコミットと振る舞い変更のコミットは分ける
- (コメント: 新しい IF の中で古いメソッドを呼ぶイメージ)
凝集の順番を整理する
- 変更対象の要素を隣接させるように順番を入れ替える
- ファイル・ディレクトリ・リポジトリ単位でも有効
- 「結合を取り除く」ことができれば最善
- 整理するのに最適な条件:
cost(分離) + cost(変更) < cost(結合) + cost(変更) - 分離が難しいケース:
- 知識不足
- 時間や金銭の不足
- チームが変化を受け入れられない
変数宣言と初期化を一緒の場所に移動する
- 宣言と初期化が離れていると読みにくい
- 変数間のデータ依存関係の順序を守らないと、振る舞いが変わることがあるため注意
説明変数と説明定数を使う
- 長い式は変数にする
- リテラル定数はシンボリック定数に置き換える
- 関連定数をまとめたり、意味ごとに分離する
明示的なパラメーターにする
- ルーチンを分割し、パラメーターを明示的にすることで、呼び出しチェーンの上位に持っていく
- パラメーターをまとめてマップで渡すと理解しづらくなったり、後でパラメーターが変更されるため使いにくくなる
ステートメントを小分けにする
- 意味ごとに空行を入れる
- 簡単な整頓でも変更が容易になる
ヘルパーを抽出する
- ヘルパー抽出のケース:
- 大きなルーチンの一部を変更する必要がある場合
- 変更する行をヘルパーにすることで、変更はヘルパーだけに閉じ込められる
- 時間的な結合(a()の前に b()が必要など)を表現する場合
- 大きなルーチンの一部を変更する必要がある場合
部品として使えるようにひとかたまりにする
- まずコードを 1 箇所に集めてから部品に分ける
- 対応すべき兆候:
- 引数リストが長く、繰り返しが多い
- 同じコードや同じ条件式の繰り返しがある
- ヘルパー名が不明確
- 共有している可変データがある
コードから読み取れない情報をコメントで補う
- コードからは読み取れない背景や意図を書く
- 欠陥(一緒に修正すべき箇所など)を見つけた直後がコメントの好機
- 冗長なコメントは削除する
- コードと同じ内容のコメントは消す
- リファクタで不要になったコメントも消す
- (コメント: コメントは少ない方が良い。ただし再利用される、複雑な処理にはあると良さそう)
管理術
- 整頓できるからといって、必ずしも整頓すべきとは限らない
- ポイント:
- いつ整頓を始めるか
- いつやめるか
- 構造変更と振る舞い変更の組み合わせ方
整頓のコミットと振る舞い変更のコミットは分ける
- 整頓専用の PR を作る
- PR はできるだけ小さく
- 整頓と振る舞い変更を切り替えるたびに新しい PR を作る
- 慣れたら整頓 PR のレビュー必須を外してもよい
- (コメント: 責任者はレビューの自動化をしたばかりにシステム障害が発生することを恐れる可能性があると理解する)
- (コメント: 小さく PR を作成して develop にどんどんマージする。機能開発はフィーチャーブランチで対応する)
整理は小さなステップで進める
- 整頓は小さく進められる
- 1 つの整頓が次の整頓へとつながっていく
- バッチが大きいと、統合が遅れ、衝突や意図しない振る舞い変更が増える(相互作用)。また整頓しがちになりコスト増加につながる(先行投資)
- バッチを小さくすると、レビューコストは増加するが、上記の問題も小さくなる
- バッチを小さくした時に、レビューコストをどれだけ減らせるか検討する
- 相互作用のリスクが低ければ、レビューがなくてもソフトが不安定になることはないはず
- そのためにはチームへの信頼と盤石な文化が必要
- (コメント: 整頓は未来への投資)
- 整理をするのは、将来システムの振る舞いを変更するときに簡単にするため
- 振る舞いを変更する整頓は 1 時間以内に区切る
- 長くなる場合、必要最小限の構造変更を見失っているか、振る舞い変更前に何時間も整頓が必要なほどコードがひどい可能性がある
- 振る舞いの変更は一部に集中する傾向があるため、先に整理することで振る舞いの変更も特定の場所に集中するようになる
- 80%の変更は 20%のファイルに集中する
- 先に整頓する利点の 1 つは、整頓も集中すること。そして、振る舞いの変更にまさにふさわしい場所に集中するのだ
先に振る舞いの変更と整頓の絡まりを解きほぐす
- 振る舞い変更前に整理する
- 振る舞いの変更と整頓の絡まりの必要性に早めに気づけば、作業は小さく済む
整頓タイミングを判断する
- 先に整頓
- あとに整頓
- 改めて整頓
- 整頓しない
整理しない
- 判断:
- 今後絶対に変更しないコードである
- 設計改善から学ぶことがない
改めて整理
- 判断:
- 整理してもすぐに見返りがない大きなかたまりの整頓を抱えている
- 整頓を完了することで最終的には見返りがある
- 小出しに整頓できる
- 改めて整頓するものを「お楽しみリスト」として候補を記録しておく
- 改めて整理する価値:
- 整頓することで将来の変更を単純化できる
- 散らかっているコードゆえの追加対応工数を減らす
- 古い API から新しい API に移行する時に、古い API を消せず、新しい API に加えられた変更を古い API に反映しなければいけない時
- 学習ツールとして設計について学ぶ
- あなたが幸せなときというのは、プログラマーとしてより良い状態であるということを過小評価しない
あとに整頓
- 判断:
- 同じ領域の振る舞いを再び変更することがある
- 整頓のコストが振る舞いの変更コストにほぼ比例する時
- 将来の整頓まで待つとかえって高くつく
- あとに整頓しないとやりきった感が得られない
- 後日まで整頓を待つことで整頓のコストが大幅に増えてしまうようなら、今すぐ行うことを考える
先に整頓
- 判断:
- すぐに見返りがある(理解が向上したり、振る舞いを安く変更できる)
- 何をどのように整頓すればよいかわかっている
- 先に整頓することに偏りがちだが、整頓自体が目的にならないよう注意
- 整頓に偏ってもそれほど損はしないだろうし、たいていの場合は報われる
理論
要素を役立つように関係づける
- 例:
box.width() * box.height()をbox.area()に移す - 呼び出し元をシンプルにできるが、Box が関数 1 つ分大きくなるという代償を払う
- 設計上は要素の階層・要素間の関係性・関係性から生まれる利益を意識する。
- ソフトウェア設計者ができること:
- 要素の作成と削除
- 関係性の作成と削除
- 関係性が生み出す利益の増加
- これで、システムの構造と振る舞いをより明確に区別する
構造の変更がオプションを増やす
- 構造の変更はオプションを増やし、将来の変更を容易にする(できることを増やせる状態)
- 構造の変更は振る舞いの変更とは別物で、構造の変更は可逆性であるが振る舞いはそうではない
時間価値とオプショナリティのバランスを大事にする
-
今日の 1 ドル > 明日の 1 ドル(先に稼ぐ)
- 先に整頓より後に整頓がお金の時間価値的には推奨される
- ただし整頓した方が安価な場合は常に先に整頓する
-
オプションを作る(未来の選択肢を増やす)
- 「次に振る舞いが実装できること」自体が価値を持つ
- ポートフォリオ(振る舞いの選択肢)を大きくすることが価値になる
- 不確実性が高いほどオプションの価値は高い
- 変化を受け入れれば、従来のソフトウェア開発だったら派手に失敗するような、まさにそういった状況で生み出す価値は最大になる
- ソフトウェア設計を考えることはオプションを作ることと振る舞いを変更することの配分を考えること
- 考え方:
- 振る舞いの変更の価値が変動する可能性は高ければ高いほどよい
- 開発を長く続けられるほどよい
- 将来もっと安く開発できるに越したことはないが、価値に占める割合は少ない
- オプションを作るために行う設計は少なければ少ないほどよい
- 両者のバランスが重要
-
cost(整頓) + cost(整頓のあとに振る舞いを変更) < cost(整頓せずに振る舞いを変更)- 先に整頓する
-
cost(整頓) + cost(整頓のあとに振る舞いを変更) > cost(整頓せずに振る舞いを変更)-
DCF 視点: 先に稼ぐ → 後で整頓
- ただし、生み出されるオプションの価値がお金を早く確実に使うことによって失われる価値よりも大きいときは先に整頓する
- オプション視点: 先に整頓して未来の選択肢を増やす
- 判断の型:
- ソフトウェア設計のタイミングやスコープに影響するインセンティブ(周りに説明できること)を意識する
- まずは人間関係のスキルを自分自身で練習し、そのあとで直接的な同僚やさらに遠くの同僚へと使っていく
-
DCF 視点: 先に稼ぐ → 後で整頓
可逆的な構造変更は中断可能な状態で進める
- 構造変更は基本的に可逆的
- 誤っても戻せるのでリスクは小さいため間違いを避けるために多くを投資すべきではない
- 中断可能な状態で整頓を進める
結合と分離のバランスを考える
- ソフトウェアのコスト ≒ 結合の大きさ
- 結合が減らせるものは先に整頓すべき
- ただし、トレードオフの関係なので結合のコストを払うか、分離のコストを払うかを選択する
- 例: ある種類の変更の結合を減らせば減らすほど、別の種類の変更に伴う結合が大きくなる
凝縮する
- 結合した要素は同じ親要素の子要素同士であるべき
- 分析しやすく、変更しやすく、思いがけない振る舞い変更への耐性がある
- 堆肥ではない要素(つまり結合していない要素)は別の場所に移すべき
- どれがどれと結合しているかという情報は不完全だし変化するため一度に 1 つの要素を動かす
- ボーイスカウトルール(「来たときよりも美しく」)に従う
結論
- 整頓によって下記の価値があるかどうか
- コスト:コストが小さくなったり、コストが発生する時期が遅くなったり、コストが発生する可能性が低くなったりするか?
- 収益:収益が大きくなったり、収益を手にする時期が早くなったり、収益を生む可能性が高くなったりするか?
- 結合:より少ない要素の変更になるか?
- 凝集:変更が必要な要素が、より小さく、もっと集中した範囲になるか?
- 整頓することでプログラミングが快適になることの効果も侮ってはいけない
- ただし、整頓はやりすぎないこと
- 次の振る舞いの変更ができるように整頓する
- 誰かが待っているような変更の遅れがないときに好きなだけ整頓する
- ソフトウェアの設計には同僚のプログラマー、ステークホルダーとの人間関係を健全にすることも大事
まとめ
整頓は……
- 整頓するタイミングと、どこまで整頓するかを判断する
- 後回しにしても整頓できることも覚えておく
- オプション vs キャッシュフローのバランス
- チームやステークホルダーと認識を合わせておく
- テクニックとして
- 整理は常に小さなステップで進める
- 特に構造の変更は可逆的なので戻せるようにしておく
- 整頓のコミットと振る舞い変更のコミットは分ける
- 結合 vs 分離のバランス
- 整理は常に小さなステップで進める
感想
- 整頓はほとんどの場合「イエス」だが、どのタイミングで整頓するかの判断は適切に行う必要がある
- 「まずは人間関係のスキルを磨く」はその通りだと思った。チームで開発している以上、整頓の説得やメンバーの協力も必要
- 整理をするのは将来システムの振る舞いを変更するときに簡単にするため、という目的を見失わないようにしたい
- もっと早く読みたかったが、経験したからこそ深く理解できたのかもしれない
Discussion