- 「新装版」と異なり、JavaではなくJavaScriptで書かれている
- Javaのほうが馴染みがあるので迷ったが、ここ数年の新しい考え方が取り入れられていることを期待して、先に読み始めた
- まず1章を読んだ感想
- 「効果的なリファクタリングへの鍵は、小さいステップを踏むほど速度が上がっていくという事実」
- 最終的にどのような構成にすべきかのゴールが見えていなくても、まず理解しやすくするために小さな関数の集まりに分離していけばよいことに気付かされた。そう思えば安心して少しずつコミットしていける。
- 正直、「リファクタリングなんて、良いコード設計を意識して修正していけば良いんでしょ」、くらいで読み始めたところ、常に少しずつ、動く状態を維持しながら、という進め方が自分の認識と大きく異なっていたことに気づき、非常に驚いた。これは真面目に読んだほうが良さそう、というのが第一印象。
1章
- p13
- playFor 関数のインライン化で3回実行されるようになっている
- 「パフォーマンスに大きな影響を与えないし、たとえそうであっても、コードが整然としていれば後からチューニングが容易にできる」
- ローカル変数を削除することによる大きな利点
- 「扱うべきローカルスコープが減ることにより、メソッドの抽出がずっと楽になること」
- p18
- 「最初から適切な名前を付けるのは難しいため、そのときに浮かんだベストな名前をつけ、後から積極的に修正するようにします。」
- p20
- 「この程度のループの繰り返しでは、パフォーマンスには影響を与えません」
- 「プログラマは、たとえ経験が豊富だったとしても、コードが実際どのように動くかについて、間違った判断をしがちです。賢いコンパイラや、キャッシュ技術などが、私達の直感を上回る動きをするからです。」
- 「ソフトウェアの速度性能に影響する箇所は、実際のところコード全体のごく一部に過ぎません。それ以外をチューニングしても大した効果はないのです。」
- 「リファクタリングによって、パフォーマンスに重大な影響を及ぼしてしまうときもときにはあります。たとえそうなったとしても、私はかまわず作業を継続します。」
- 「よく整理されたコードのほうが、後からパフォーマンスの最適化がしやすいからです」
- 「もう一つ注意してほしいのは、いかに小さなステップを踏んできたか」
- 「リファクタリング途中でテストが失敗したときには、問題を直ちに把握して修正できないなら、まず直近の正常なコミットに戻って、より小さなステップでやり直すようにしています。このやり方は、コミットを頻繁に行っているからこそ有効になるものです。」
- 「特に困難なコードに対処しているときは、小さなステップがすばやさを生み出す鍵になります。」
p24
- 「込み入っている箇所を小さな単位に分割していくことは、名前付けと同じくらい重要」
p43 まとめ
- このリファクタリング例では、大きく分けて三つの段階
- 元の大きな関数を分解して、小さなネストした関数の集まりにする
- 計算とフォーマットのコードを分けてフェーズ分離
- ポリモーフィズムを使って計算ロジック分離
- 「コードがきれいになるにつれ、理解がしやすくなり、より踏み込んだ洞察ができるようになって、前向きなフィードバックが行われていきます。」
- 「好みを超えて、良いコードはどれだけ変更が容易なのかで決まる」
- 「この例を通じて皆さんに最も学んでほしいことは、リファクタリングのリズムです。リファクタリングを実演して見せると、各手順が非常に小さく、コードが常にコンパイルとテストを通過できる状態に保たれているということで驚かれます。」
- 「効果的なリファクタリングへの鍵は、小さいステップを踏むほど速度が上がっていくという事実を認識することです。」
2章
リファクタリングの定義
- リファクタリング(名詞)
- 外部から見たときの振る舞いを保ちつつ、理解や修正が簡単になるように、ソフトウェアの内部構造を変化させること。
- リファクタリングする(動詞)
- 一連のリファクタリングを適用して、外部から見た振る舞いの変更なしに、ソフトウェアを再構築すること。
長年にわたって、業界では「リファクタリング」という用語を、コードをきれいにするあらゆる作業を表すものとしてあいまいに使ってきました。しかし、上記の定義では、コードをきれいにするための特定の手法であることを示しています。
リファクタリングは振る舞いを保ちつつ小さなステップを適用していくことであり、ステップを積み重ねていくことで大きな変化をもたらしていくものなのです。個々のリファクタリングは非常に小さいステップ、またはそれらの組み合わせでできています。その結果、リファクタリングではコードが壊れた状態になっている期間は非常に短く、たとえ未完成であっても、いつでも中断が可能です。
リファクタリングは、コードを「理解や修正が容易になるように」変化させていくものです。
二つの帽子
- 作業を二つの活動に区分すべき
- 機能追加
- リファクタリング
リファクタリングを行う理由
- ソフトウェア設計を改善する
- ソフトウェアを理解しやすくする
- バグの発見を助ける
- プログラミングを速める
- リファクタリングで設計を改善していける
リファクタリングを避けるとき
どのようにして動いているのか理解しなければならない状況になった時に初めてリファクタリングの利点が出てくる
また最初から書き直した方がリファクタリングするより簡単な時もあります。この決定はなかなか難しいものです。アルコードのリファクタリングにどれだけ掛かるかを判断するには、実際にいくつかのリファクタリングを実施して、難しさについての感覚を得なければならないこともよくあります。
リファクタリングの問題点
新機能の実装が遅くなる
- リファクタリングの目的は、速度を上げること
- トレードオフによりやらない、後回しにすることもある
- ほとんどの人は、リファクタリング不足でもっと頻繁に試みるべき
開発速度の名の下に、リファクタリングを軽視する非生産的な傾向の管理者がよく槍玉に上がりますが、開発者自身も同じ行動していることはよくみられます。
チームの技術リーダーなら,自分はコードベースの健康状態に重きを置いているということをメンバーにはっきり示すことが重要です。
最も危険な罠と思うのは、「美しいコード」、「素晴らしいエンジニアリングのプラクティス」といった道徳的理由により、リファクタリングが正当化される状況です。
コードの所有権
ブランチ
- 機能ブランチは乖離するとマージが問題
- master based開発とCIが重要
テスト
- リファクタするにはテストは必須
- ミスなくリファクタができる、IDEの自動リファクタリング機能を活用する手もある
レガシーコード
- リファクタリングするには、テストを追加するしかない
- 通常はテストすることを最初から考慮した設計になっているが、そうでないものはテスト追加は厄介になりやすい。
- 「レガシーコード改善ガイド」が有効
データベース
- かつては、データベースのリファクタリングは問題が多かった
- 今は下記が有効
- Pramod Sadalageの進化的なデータベース設計の手法
- 「データベース・リファクタリング」
リファクタリングとアーキテクチャ、そしてYagni
リファクタリングがアーキテクチャに与えた最も大きな影響は、要求の変化にしなやかに対応できる、優れた設計のコードベースを作り上げる方法を示したことです。コーディングの前にアーキテクチャを完成させてしまう手法の最大の問題点は、ソフトウェアの要求は事前に十分把握できるものという前提に立っていることです。
- Yagni (You’re not going to need it)
- 今必要のないものは作らない
- 現在分かっている要求のみを解決するソフトウェアを構築する
- 必要が生じたときにリファクタをする
リファクタリングとソフトウェア開発プロセス
真にアジャイルなやり方を実践するには、チームメンバーはリファクタリングの技能を持ち、かつ熱心に取り組む人たちでなければなりません。そのために、開発プロセスの多くは、リファクタリングが通常の作業の一部になることに適合する必要があります。
- 強い相乗効果
- 自己テストコード
- 継続的インテグレーション
- リファクタリング
- Yagniという設計手法は、この三つのプラクティスの実践があって初めて可能
参考文献
- 「Refactoring Workbook」Bill Wake
- リファクタリングを実践するための多くの演習
- 「パターン志向リファクタリング入門」Josh Kerievsky
- リファクタリングでgof本のパターンを適用していく手法を示している
- 「データベース・リファクタリング」
- 「Refactoring HTML」
- 「レガシーコード改善ガイド」
- 「リファクタリング:Rubyエディション」
3章 コードの不吉な臭い
- リファクタリングをするための正確な基準はない
- 人間の直感はメトリクスにはかなわない
- リファクタリングの必要性を示す不吉な兆候で判断
- 不可思議な名前
- 重複したコード
- 長い関数
- 長いパラメータリスト
- グローバルなデータ
- 変更可能なデータ
- 変更の偏り
- 変更の分散
- 特性の横恋慕
- データの群れ
- 基本データ型への執着
- 「ミノ駆動本」にも「プリミティブ型執着」として説明されている
- 重複したスイッチ文
- ループ
- 怠け者の要素
- 疑わしき一般化
- 一時的属性
- メッセージの連鎖
- 仲介人
- インサイダー取引
- 巨大なクラス
- クラスのインタフェース不一致
- データクラス
- 相続拒否
- コメント
カタログ
- 変数のインライン化 (Inline Variable)
- ループの分離 (Split Loop)
- 2つの異なる処理を同時におこなっているループは、修正するときに必ず両方を理解する必要が生じるのでループを分離すべき
- ステートメントのスライド (Slide Statements)
- 互いに関係する処理は並んでいる方が良い
- 変数を関数の先頭で宣言するのでなく、利用する直前で、宣言と利用箇所をまとめる
- 問い合わせによる一時変数の置き換え(Replace Temp with Query)
- 変数を関数に置き換えると関数の抽出が容易になる
- 抽出されたロジックと元の関数との間にはっきりとした境界が築かれる
- 厄介な依存性や副作用を特定でき、避けられるようになる
- クラスの中で使うのが最も効果的
- フェーズの分離 (Split Phase)
- 一つのコードが二つの処理を行っている場合、別々のモジュールに分離する
- パイプラインによるループの置き換え (Replace Loop with Pipeline)
- ロジックを追いやすくなる
- ファクトリ関数によるコンストラクタの置き換え (Replace Constructor with Factory Function)
- コンストラクタの制約を避けられる
- 名前を変えられない、newなどの演算子が必要、関数が期待される文脈で使えない
- コンストラクタの制約を避けられる
- ポリモーフィズムによる条件記述の置き換え (Replace Conditional with Polymorphism)
- サブクラスによるタイプコードの置き換え (Replace Type Code with Subclasses)
- コマンドによる関数の置き換え (Replace Function with Command)
- 関数自身をオブジェクトとしてカプセル化することが有用な場合もある
- 「コマンドオブジェクト」 または「コマンド」
- gofの「コマンド」パターンを指す
- 複雑になるので、第一級関数が使えるならそっちを使う
- このリファクタのポイントは煩雑な関数を分解可能にすること