スプラウトクラスとは
ここに、職員検索を行い、結果を表示するサーブレットがあります。
public class EmployeeSearchServlet extends HttpServlet { protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ・・・ //ここで職員情報をデータベースから検索する Employee employee = ・・・ PrintWriter writer = response.getWriter(); writer.print("<html>"); writer.print("<head><title>検索結果</title><head><body>"); writer.print("職員の名前 :" + employee.getName()); ・・・ } ・・・ }
このサーブレットには、データベースへの接続処理や画面表示処理など、さまざまな処理が書かれています。ここでは説明のために簡略化していますが、J2EE(現Java EE)が登場して間もない頃に作られたWebアプリケーションでは、このようなコードはよくありました。おそらく、現在稼働中のアプリケーションにもこうしたコードがたくさん残っていることでしょう。
このサーブレットには、当然のことながら単体テストは用意されておらず、新たに用意するのも大変です。サーブレットが使われ始めた頃は、サーブレットをインスタンス化することが難しいという理由で、単体テストがないほうが当たり前でした。現在でも、おそらく世に存在する多くのサーブレットプログラムには単体テストが用意されていないことでしょう。
さて、あるときこのサーブレットに、職員が所属する組織の情報も一緒に表示するという機能が必要となり、このサーブレットにコードを追加しなければならなくなりました。さて、どうすればよいでしょうか?
今すぐ仕事を終わらせたいとすれば、既存の職員の検索処理と同様に、所属組織の検索処理をサーブレットに直接書きたくなるところです。しかし、それではレガシーコードのまま、何も進歩がありません。コードをすべて書き終わり結合して実際に動かしてみるまで、追加したコードがちゃんと動作するかは決して確認できません。
「スプラウトクラス」(Sprout Class)は、このような状況に対して使用する手法です。スプラウトクラスとは、既存のクラスには基本的に手を加えず、追加したい機能を実現するために新しいクラスを定義する手法のことです。単体テストが用意されておらず、新たに作ることも難しいクラスに対して機能を追加しなければならない場合に、スプラウトクラスを使用します。
やることはとても簡単です。まず、追加したい機能をメソッドとして持つ新しいクラスを用意します。次のような感じです。この新しいクラスには単体テストを用意します。
public class OrganizationSearcher { private Employee employee; public OrganizationSearchar(Employee employee) { this.employee = employee; public Organization search() { //ここで組織情報をデータベースから検索する Organization organization = ・・・ ・・・ } }
次に、古いクラスで新しいクラスをインスタンス化し、古いクラスから新しいクラスのメソッドを呼び出すようにします。古いクラス、つまりサーブレットは次のような感じになります。
public class EmployeeSearchServlet extends HttpServlet { protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ・・・ //ここで職員情報をデータベースから検索する Employee employee = ・・・ //職員が所属する組織情報を検索する OrganizationSearcher orgSearcher
= new OrganizationSearcher(employee); Organization organization = orgSearcher.search(); PrintWriter writer = response.getWriter(); writer.print("<html>"); writer.print("<head><title>検索結果</title><head><body>"); writer.print("職員の名前 :" + employee.getName()); ・・・ writer.print("組織の名前 :" + organization.getName()); ・・・ } ・・・ }
これで、少なくとも職員が所属する組織情報を検索する処理には単体テストが用意され、テスト済みの状態となりました。
この手法をスプラウトクラスと呼ぶ理由は、レガシーコードの中にテスト済みのコードが少しだけ作り込まれた状態を「発芽」にたとえて表現しているからです。「スプラウト(sprout)」は「発芽させる(動詞)」「新芽(名詞)」を意味します。
レガシーコードでは、「悪化させない」ことだけでも重要なこと
さて、ここまで読んでみて、
「こんな小さな変更のために、わざわざ新しいクラスを 作ってまで単体テストをする必要があるの?」
「このサーブレットが二度と変更されず、 すべて無駄な作業になることはないの?」
「そもそも、古いクラスはそのままだよね?」
という感想を持った読者がいらっしゃると思います。
その感想は正しいと言えるでしょう。今回取り上げたような、1回限りの小さな変更だけに対しては必要ないことかもしれません。しかし、レガシーコードは、その小さな変更の積み重ねで生まれるものです。
単体テストが用意されていない状況では、変更が正しく行えたかどうかを手っ取り早く確認する方法がなく、変更作業に時間がかかります。間違ったコードを書いてしまった場合にも、デバッグするのは容易ではないでしょう。単体テストがあれば、そのような余計な時間を費やすことがありません。単体テストを用意する価値は、テストで節約できた時間も含めて判断するべきです。
また、ソフトウェアの変更箇所は、一般的に集中する傾向にあります。一度変更した箇所を、近い将来また変更しなければならないとしたら、今回新しく用意したクラスを利用できる可能性が高いでしょう。そう考えると、単体テストを用意することが無駄にはなりません。
確かに、古いクラスの既存のコードはそのままです。でも、見方を変えると、古いクラスを悪化させずに機能を追加できたとも言えます。巨大で複雑なレガシーコードは、小さな変更の度に少しずつ悪化してきた結果としてでき上がるものです。今よりも構造を悪化させずに機能を追加できることは、とても重要なことなのです。
『レガシーコード改善ガイド』では、このスプラウトクラスと同様の目的を持つ手法として、スプラウトメソッドやラップクラス、ラップメソッドなども紹介しています。これらの手法は、それ自体が最善の状態にするものではありませんが、現実的な解の1つとして検討する価値のある手法です。今回紹介したような状況に遭遇した際には、採用を考えてみてはいかがでしょうか。