既存コードの一部や場合によって全部を変更する必要があることは往々にしてあるかと思います。
保守開発をしている方はまさにそのような状況の連続なのではないでしょうか。
または、別の開発会社から引き受けたコードを修正する必要がある場合もそれに当てはまりそうですね。
そのようなタイミングで、変更対象のソースにテストが実装されており、そのテストコードの信頼性が非常に高ければ、テストを実行しながらリファクタリングするのは比較的容易に行えるかと思います。
ただそういった「信頼性の高いテストコードがある状況」は私の経験ではまれです。
ではそのようなラッキーな状況ではない場合はどうすればよいのでしょうか。
以下のようなステップになると思います。
- コードの仕様(動き)を把握する
- 変更箇所を特定する
- 変更を行う
- 変更が想定通りに行えることを確認する
- 変更が他に影響を与えていないことを確認する
この中で、1、5の解決策について記述します。
仕様(動き)を把握し、テストコードを書く
何はともあれ、テストコードを書きましょう。
まずはテストコードを書くためにはどうするか、からです。
まず初めにやってほしいことは、明確な基準を作ることです。
基準とは、仕様であったり修正前の動きだったりを指します。
ここが不明確だと、改修個所の特定も明確に行えず、変更の影響範囲も不明なまま変更を行ってしまいますね。
仕様(動き)を明らかにする
仕様を確認するためには
- ドキュメントから把握する
- 過去の議事メモを探し出し把握する
他にも色々手法はあると思います。
そして把握できた仕様に対して期待するテストコードを書くのが当然の流れですね。
ただこれはあまり良い方法ではないようです。
既存コードに手を入れる場合は
「システムとしてどうあるべきか」よりも「実際にどのように動いているか」の方が重要なのです。
すでに稼働している状態であれば、今稼働している状態が正なのです。
もし設計上意図していない振る舞いでも、ソース上そうなっていればソースが正なのです。
※バグをほっといてもいいというわけではないです。
したがって、ドキュメントなどから把握した仕様をもとにテストを書くだけでは十分とは言えません。
実際に動作している根本であるコードを抑えましょう。
ではコードからどうやって仕様を起こすのか。
仕様「(動き)」のかっこ書きの部分が重要です。
このロジックは今どのように動いているかが重要です。
なのでロジックから「動き」を明らかにします。
コードから動きを明らかにする
ここでテストコードの出番です。
まずは変更対象クラスのインスタンスを作成し、そのクラスメソッドをどれでもいいのでテストするコードを書きましょう。
そしてそのテストが失敗するようなテストを書きましょう。
例えばですね
public class CalcTools {
private final int number1 = 123;
private final int number2 = 154645;
private final int number3 = 4332;
public int calcNumber() {
int calcedNum = 0;
calcedNum = number1 + number2 * number3 / number1 - number2;
return calcedNum;
}
}
このコードの、calcNumberをテストするとします。
なにか計算してるようですが、よくわかりません。
ただ使用してる変数もfinalだし、きっと何か大事な仕様的な役目がこのCalcNumberにはあるはずです(汗)
なので、CalcNumberの呼び出し結果はソースの変更で影響を与えてはいけない箇所です。
ただ、このCalcNumberの計算結果、割り算もあるのにintで宣言してあるし手計算で計算しても正確な数値を把握するのはちょっと難しい。
そこで、とりあえず期待する数値は何でもいいのでテストを書きましょう
public class CalcToolsTest {
@Test
public void test1() {
CalcTools calc = new CalcTools();
assertEquals(0, calc.calcNumber());
}
}
そしてテストは失敗しますがとても大事な結果が出力されます。
java.lang.AssertionError:
Expected :0
Actual :5291999
メソッドを実行して実際に出力される値です。
これが仕様です。コードの動きです。
ここまでの情報から正しい動きのテストコードを書くことができます。
public class CalcToolsTest {
@Test
public void test1() {
CalcTools calc = new CalcTools();
assertEquals(5291999, calc.calcNumber());
}
}
こうすれば、特定のメソッドの動きは読み切りましたね。
これからこのメソッドをリファクタリングしたとしても怖くはありません。
ここで明らかになった動きから、事前にドキュメントから採取した仕様を結びつけることでコードへの理解はぐっと上がりそうですね。
もうお気づきかと思いますが、こういった作業を繰り返して全体の動きをコードから把握して、変更後に影響を与えないような状態を作っていきます。
最終的に出来上がったテストメソッド群を実行した際のカバレッジを取得して100%なことを確認するのも良いですね。テストコードの信頼性がぐっと向上します。
とはいえ現実はどうなのか
上記の作業、とても時間がかかります。
時間に余裕があれば可能ですが、そんな予算も期間も確保できないのがざらです。
「そんな膨大な作業やってられるか!」と思う方もいると思います。私もそうです。
ただ、改修対象が膨大であれば別のアプローチも考えられますが、これが一番堅実です。
仕様ではなく動きを担保するためには、コードから捉えるしかないのです。
ここで今一度テストコードってなにかを考えてみましょう。
私は「以降の改修でバグを生まないようにすること」が目的と考えます。
テストコードは今目の前にあるコードのバグを見つけるのは不向きです。
ただ、「これから未来に改修が入り、その変更が誤ったものであった際に発生するバグ」を捉えるのは得意であると考えます。
今回紹介したような場面がまさにそうです。
改修に付随して発生しうるバグを、改修前に動きをとらえてブレないようにする、そのためにテストコードを書きましょうということです。
これで
- コードの仕様(動き)を把握する
- 変更が他に影響を与えていないことを確認する
方法はなんとなくわかったかと思います。
新しく作るより、既存のシステムを破壊しないように変更することの方が難しいと私は感じます。
私は保開発ばかりで新規開発はあまりやったことはないですが、保守開発の方が実装の難易度は高いのではないかと思います。
上記の意見は
レガシーコード改善ガイド
第13章 変更する必要がありますが、どんなテストを書けばよいのかわかりません
に多分の影響を受けています。
https://www.amazon.co.jp/dp/4798116831