増え続けるレガシーコード
この記事を読んでいる皆さんの多くは、これまでたくさんのシステム開発に関わってきたことでしょう。仕様変更と闘い、納期に追われ、やっとのことで稼働したシステムも数多いはずです。厳しい状況になればなるほど、実際にコードを動かすことが最優先になり、「コードを保護する」ための単体テストの整備は後回しになってしまいがちです。
ところが、システム開発はシステムが完成して無事に稼働した時点で終わりではありません。ユーザーが実際に使い始めると、保守開発としてさまざまな仕様変更や機能追加が発生するのが常です。それらに対応するためには、厳しいスケジュールの中で、やっつけ仕事で間に合わせたコードに対して改修や機能追加をする必要があります。では、このような仕事にどうやって取り組めば良いのでしょうか?
さて、前回の記事では、『レガシーコード改善ガイド』におけるレガシーコードの定義を紹介しました。繰り返しになりますが、もう一度その定義を確認してみましょう。
レガシーコードとは、単にテストのないコードである
この定義によると、COBOLやJavaやRubyといったプログラミング言語の古さ、新しさに関わらず、テストのないコードはすべてレガシーコードということになります。つまり、皆さんが今まさに書いているコードであっても、テストコードを整備していないとしたら、それは「レガシーコード」ということになります。
幸いなことに、このようなレガシーコードに対処するための技法はいろいろあります。それらの技法は、上記の『レガシーコード改善ガイド』で詳しく紹介されていますが、この連載ではその中のいくつかを紹介していきます。この第2回では「仕様化テスト」を紹介します。
レガシーコードは最初に理解する段階でつまずく
本番システムで稼働しているレガシーコードを変更するときに、まず行うべきことは何でしょうか? そう、まずはレガシーコードの内容を理解することです。しかし残念ながら、多くの場合レガシーコードでは適切なドキュメントがなく、コメントは不適切で、プログラムの構造も理解しづらいのが一般的です。
一般的な作業手順は次のようになるでしょう。
- ステップ1:コードの内容を理解する。
- ステップ2:仕様追加や変更に対応するためにコードを変更する。
- ステップ3:テストコードを書いて、正しく変更されたことを確認する。
レガシーコードに対して上記の作業手順を実際に進めようとすると、ステップ1の段階でしばしば壁に突き当たります。納期に追われてシステムを動かすことを最優先にした結果、コードはとんでもない状態になっているからです。例えば、何百行にも及ぶメソッド、何を表しているのか分からない変数名、スパゲティのように絡み合ったロジックなど、とてもすぐに理解できないような状態です。
しかし、変更にも納期があり、それまでにコードを動かさなければなりません。そうなるとまた、コードを動かすことを最優先にしてしまいがちです。その結果、コードをよく理解しないまま変更し、ほとんどテストもできずにシステムは動き出します。こうして、レガシーコードはさらに泥沼と化していくのです。
既存コードを文書化するためにテストを記述する
この悪循環から抜け出すためには、どうしたらよいのでしょうか?
レガシーコードを変更するときに、まず行うべきことはレガシーコードの内容を理解することでした。しかし、複雑なレガシーコードをいきなり読んだとしても、そうそう簡単に理解できるものではありません。このため全体を理解できないまま、コードの一部分だけを見て仕様追加や変更に対応するためのコードを書いてしまいがちです。
コードを効率よく理解するにはどうしたらよいのでしょうか? コードを理解するためには、コードの振る舞いを教えてくれる「文書」が必要です。しかし、既存のコードを開発したときに作成した仕様書は、役に立たないかもしれません。仕様追加や変更を行ってもそのようなドキュメントはメンテナンスされないことが多く、コードと文書との不整合が起きているからです。
不整合を起こさず、仕様追加や変更に役立つ文書を作る良い方法があります。それが、「仕様化テスト(characterization test)」を作ることです。仕様化テストとは、コードの現在の振る舞いを確認するためのテストのことです。仕様化テストは、コードが実際にどのように振る舞っているかを明らかにします。自動化したテストでコードの振る舞いを確認できれば、システムを変更するときに、少なくとも変更していない部分の振る舞いが変化していないことをすぐに確認できます。まさに、「保護して変更する」ことができるようになります。コードの振る舞いを表すテストなので、コードの振る舞いを理解するにはうってつけの「文書」にもなります。