「他社から譲り受けたコードに機能を追加しようとして、眠れない夜を過ごしています。 9377>
“私はレガシーコードの最も純粋な形を扱っています” “私は仕事をしなければならないのに少しも理解できない、もつれた、構造化されていないコードを扱うのに本当に苦労しています。 レガシー コードとは、誰かから取得したコード、誰かによって書かれたコード、理解しにくいコード、時代遅れのテクノロジで書かれたコードなど、おそらく多くの異なる定義がある言葉です。
Question> あなたはレガシー コードをどのように定義しますか
Defining Legacy Code
Michael Feathers は彼の著書 “Working Effectively with Legacy Code” でレガシー コードとはテストのないコードと定義しています。 テストがなければ、コードが良くなっているか悪くなっているかを判断することはできません。
この定義を少し修正したものが、「ユニットテストのないコードはレガシーコードと呼ばれる」です。 テストは常に、できるだけコードに近いところにあるほうがよいのです (ユニット テスト > 統合テスト > UI テスト)。 ですから、ユニットテストのないコードをレガシーコードと呼ぶのは不公平ではありません。
Working with Legacy Code
Question> もしあなたがレガシーコードに変更を加えるなら、どんなアプローチを取りますか
ほとんどの人は、「変更を加えてその日を終えることにして、コードの改善について悩む必要はありません」と言うかもしれません。 この思考プロセスの背後にある論理的根拠は次のとおりです。 変更を加えてストーリーを完成させるほうがよい
Michael Feathers はこの変更を行うスタイルを Edit and Pray と呼びました。 このスタイルでは、レガシー コードを増やすことにしか貢献できません。
The Boy Scout Rule
Boy Scout Rule の考え方は非常にシンプルで、おじさんのボブによれば、次のように述べられています。 コードを見つけたときよりもきれいにしておくこと! 古いコードに触れるときはいつでも、それを適切にクリーニングする必要があります。 コードをより理解しにくくするような近道の解決策を適用するだけでなく、丁寧に扱うようにしましょう。 9377>
ボーイスカウトのルールをレガシー コードに適用すると、非常に強いメッセージが得られます。 9377>
近道をすることができないことを理解した今、私たちに残された唯一の選択肢は、いくつかのテストを書き、コードをリファクタリングして、開発を進めることです。 質問>
- どのテストを書くべきか
- どのくらいリファクタリングすべきか
Which Tests To Write
ほとんどすべてのレガシー システムで、システムが何をするかは何をすることになっているかというより重要である。
Characterization Tests、動作を保持したいときに必要なテストはcharacterization testと呼ばれます。 特性化テストは、コードの部分の実際の動作を特性化するテストです。 まあ、こうなるはずだ」とか「こうなると思う」というのはない。 テストは、システムの実際の現在の動作を文書化します。
Writing Characterization Test
特性化テストは、定義上、本番環境で実行しているのとまったく同じ方法で、システムの実際の現在の動作を文書化します。
顧客によってレンタルされたいくつかの映画のテキスト ステートメントを生成する顧客オブジェクトの特性テストを書いてみましょう。
import static com.code.legacy.movie.MovieType.CHILDREN;
import static org.junit.Assert.assertEquals;public void shouldGenerateTextStatement(){ Customer john = new Customer("John");
Movie childrenMovie = new Movie("Toy Story", CHILDREN);
int daysRented = 3;
Rental rental = new Rental(childrenMovie, daysRented); john.addRental(rental); String statement = john.generateTextStatement();
assertEquals("", statement);
}このテストでは、3 日間レンタルした子供向け映画を与えられた顧客の「テキスト ステートメント」生成について理解しようと(または特性化)します。 システムを理解していないため (少なくとも現時点では)、ステートメントが空白または任意のダミー値を含むことを期待します。
テストを実行して失敗させてみましょう。
java.lang.AssertionError:
Expected :""
Actual :Rental Record for John, Total amount owed = 12.5. You earned 4 frequent renter points.さて、コードの動作がわかったので、先に進んでテストを変更できます。
import static com.code.legacy.movie.MovieType.CHILDREN;
import static org.junit.Assert.assertEquals;public void shouldGenerateTextStatement(){
String expectedStatement = "Rental Record for John, Total amount owed = 12.5. You earned 4 frequent renter points"; Customer john = new Customer("John");
Movie childrenMovie = new Movie("Toy Story", CHILDREN);
int daysRented = 3;
Rental rental = new Rental(childrenMovie, daysRented);
john.addRental(rental); Sting statement = john.generateTextStatement();
assertEquals(expectedStatement, statement);
}ちょっと待ってください。 はい、まさにそのとおりです。
私たちは今、バグを見つけようとしているのではありません。 私たちは、システムの現在の動作との違いとして現れるバグを、後で見つけるためのメカニズムを設置しようとしているのです。 この視点を採用すると、テストに対する見方が変わります。テストは道徳的な権威を持たず、システムが実際に何をするかを文書化してそこに座っているだけです。
Question> システムを特徴付けるために書くテストの総数はどれくらいでしょうか。 レガシー コード内の任意のクラスについて、次々とケースを書くことに人生のかなりの部分を捧げることができます。
Question> では、いつやめるのでしょうか。
Answer> 私たちが特徴づけをしているコードを見てください。 コード自体はそれが何をするかについてのアイデアを与えることができ、私たちが疑問を持っている場合、テストはそれらを尋ねるための理想的な方法です。
Question> それはコードのすべてをカバーしていますか?
Answer> そうでないかもしれません。 しかし、それから次のステップを行います。 コードで行いたい変更について考え、私たちが持っているテストが、私たちが引き起こす可能性のある問題を感知するかどうかを把握しようとします。 9377>
How Much To Refactor?
レガシー コードにはリファクタリングすべきことがたくさんあり、すべてをリファクタリングすることはできません。 これに答えるには、レガシー コードをリファクタリングする目的を理解することに戻る必要があります。
私たちは、レガシー コードをリファクタリングして、それが私たちに来たときよりもきれいにして、他の人が理解できるようにしたいと考えています。 数日間でシステム全体を書き直そうとするリファクタリングに狂奔するつもりはありません。 私たちがやりたいのは、「新しい変更を実装する際に邪魔になるコードをリファクタリングすること」です。 次回の記事では、例を挙げてこのことをよりよく理解することにします。
Conclusion