リファクタについて


まえがき

最近、リファクタリングの重要性を再認識したので、自分用のメモとして残しておく。

なぜリファクタが重要なのか

そもそも、最初の設計段階で完璧な設計ができていれば、リファクタは必要ない。
が、完璧な設計なんてそもそも不可能。
そのアプリケーションが将来メンテすることがないと保証されているなら、最初から完璧な設計ができるかもしれないが、
今の時代に一度作られたアプリケーションが進化しないなんてことは普通ない。
かつ、その分野の業務知識について完璧に知っている状態で実装を始められるなんてことはほぼありえないので、
ある程度理解した状態で設計を始める以上、完璧なものを求めること自体現実的じゃない。
まとめると、
- アプリケーションは進化していくもの
- 業務知識は徐々に理解できていくもの
なので、設計は絶えず改善していく必要がある。
そしてそのためには、リファクタリングが絶対に必要。

リファクタリングをいつするか

よほどのレガシーコードでみんなが悪戦苦闘している場合を除いて、リファクタのために特別な工数を割けるなんてことはないと思われる。
(レガシーコードでもまとまった工数がもらえるとは限らないし
なので、改修の際にリファクタできる工数も確保するのが正しいと思う。
「立つ鳥跡を濁さず」で、改修する前よりもした後の方が綺麗なコードになることを目指す。
順番としては、先に必要なリファクタをしてしまって、後から改修するのがいい。
(もちろん、改修中も絶えずリファクタを繰り返していく。)
リファクタをせずに、立て付けの悪いところに更に改修を加えて、手に負えなくなったコードを見たことはある。

リファクタリングとテストコード

テストコードがなくても平気な人とは上手くやっていく自信がない。
「レガシーコードとは、テストコードがないコードのことである」みたいな文言を、「リファクタリング」で目にした記憶がある。(違ってたらごめんなさい)
テストコードの意義は、
- 素早く、かつ安全に、リファクタリングを行うことができる
- (クラス単位の)仕様書の代わり
だと思っていて、リファクタとテストコードは切っても切り離せない関係だと思う。
もし(十分にケースをカバーした)テストコードがなければ、自前でテストデータを用意して実際にテストしなければならない。
しかも、毎回、修正のたびにテストしないといけない。かつ、修正の影響範囲を調べ、
影響のある全てのテストケースについてテストしないといけない。
結果として、リファクタを気軽にできなくなってしまう。
もちろん、リファクタによってテストが壊れることもある。
これについてはだいぶ長い間頭を悩ませていて、以前はテストが壊れない実装ができてからテストコードを書いたほうがいいのかと
思っていたが、最近は、もっといい設計があると最初からわかっている場合でも、既存のコードに対してテストコードを実装するのを
優先したほうがいいという考えに変わってきた。
2番目の意義も重要だと思うが、本題から外れるので別の機会に回したい。

リファクタリングの方向性

闇雲にコードをいじるだけでは、かえって悪い実装になる、というのは自分の失敗談。
当たり前だが、リファクタによってコードは改善されなければならない。
その時に、「良い実装とはなにか」についての知識(と、それを実現するためのリファクタのテクニック)が必要。
良い実装とは:
- 各クラスが必要最小限のインターフェースしか持っていないこと
- 各クラスのサイズが小さいこと
- 各クラスのインスタンス変数の数が少ないこと
- 各クラスのメソッドが細かく分割されていること
- 各クラスにsetterが存在しないこと
- 各クラスが必要最小限のgetterしか持っていないこと
結局、単一責任かつ粗結合なクラスということになるが、上記を意識すれば自然にそのようなクラスを実装できるのではと思う。
(単一責任とか、一つって言ってもどの粒度で区切るかによってどでかい業務単位でも一つと言えば一つだし)
あとは、ドメイン駆動的な知識、特にみんな軽視しがちな値オブジェクトを積極的に実装していくことが大事な気がする。

リファクタリングの流れ

「リファクタリング」にあるように、細かいステップで修正・テスト・コミットを繰り返していく。
インターフェースが変わらないのであれば、内部の構造をどんなふうに変えようが外部には全く影響がない。
また、そのようにテストコードも実装しておくべき。
インターフェースが変わる場合、既存のインターフェースを残したまま、少しずつ新しいインターフェースに切り替えていく、というのも重要。
例えば、B#hogeの代わりに新しいメソッド(new_hoge)をインターフェースにしたい場合、
1. B#new_hogeを定義し、B#hoge内の処理をコピー。引数や内部のロジックを変更し、動作が変わらないようにB#hogeからB#new_hogeを呼ぶ形にする。
2. テストが全て(B#hogeのテスト、及びB#hogeを呼んでいる箇所に対するテスト)通ることを確認。
3. B#new_hogeのテストを実装。
4. B#hogeを呼んでいる箇所を一つずつB#new_hogeを呼ぶ形に切り替え、そのたびにテストが通ることを確認。
5. B#hogeを呼んでいる箇所がなくなった時点で削除。
同じような流れで、クラスの位置づけを安全に切り替えることも可能。

参考文献