読者です 読者をやめる 読者になる 読者になる

テストで保護してリファクタリングする方法

こんにちは。エンジニアの中井です。

コードが複雑化したり規模が大きくなってくると、少しの変更でも、その変更の影響がどこまで及ぶかわからないということが起こると思います。このとき、コードの複雑さを解消するためにリファクタリングを行うのが一つの解決策です。

ただ、リファクタリングによってコードをむやみに変更した場合、依存する処理を壊してしまうリスクもあります。かといってそのまま放置しているとメンテナンスにかかる手間が大きくなる一方なので、どんどんと修正を行いづらい・ビジネスの要求に応えづらいコードベースになっていきます。

依存する処理を壊さずにリファクタリングするには、コードをテストコードで保護しておくと安心です。
このエントリでは、安心して気軽にリファクタリングするために、コードをテストコードで保護してからリファクタリングする流れについて、例を挙げてご紹介します。
記事内のコードはPHPを、テストフレームワークにはPHPUnitを使用します。

1. リファクタリング対象の設定

ここに「日付を渡すと午前か午後かを判定する (午前の場合は true を返す)」仕様のプログラムがあるとします。
日付は "Y-m-d H:i:s" 形式の文字列で渡されるものとし、現在これに対応するテストコードはないとします。

<?php

class Example
{
    public static function isAM($dateString)
    {
        preg_match('/^\d{4}-\d{2}-\d{2} (\d{2}):\d{2}:\d{2}$/', $dateString, $matches);

        if ($matches[1] >= 0) {
            if ($matches[1] < 12) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }
}

このコードを簡単にリファクタリングしてみます。方針として、以下2点を行ってみます。

  • 正規表現でのチェックを DateTime クラスを使うようにする
  • if 文のネストを浅くする

なお、記載をシンプルにするため、日付文字列以外が渡された場合・2016-01-32など不適切な日付が渡された場合などの異常系は考慮しないものとします。

2. 現状のコードにテストコードを書く

まずは上記のコードに対応するテストコードを書きます。

<?php
use PHPUnit\Framework\TestCase;

class ExampleTest extends TestCase
{
    public function testAM()
    {
        $actual = Example::isAM('2016-11-01 00:00:00');
        $this->assertTrue($actual);
    }

    public function testPM()
    {
        $actual = Example::isAM('2016-11-01 13:00:00');
        $this->assertFalse($actual);
    }
}

このテストが通ることを確認します。

$ phpunit ExampleTest.php
..                                             2 / 2 (100%)

Time: 35 ms, Memory: 3.00MB

OK (2 tests, 2 assertions)

3. リファクタリングする

<?php

class Example
{
    public static function isAM($dateString)
    {
        $dateTime = new \DateTime($dateString);
        $hour = $dateTime->format('G');

        return ($hour < 12);
    }
}

1.の方針どおり DateTime クラスを使うようにし、午前か午後かを判定する if 文を単純にしました。

これでテストが落ちないことを確認します。

$ phpunit ExampleTest.php
..                                              2 / 2 (100%)

Time: 35 ms, Memory: 3.00MB

OK (2 tests, 2 assertions)

おわりに

テストコードで保護した上で、コードをリファクタリングする流れについて簡単にご紹介しました。
実際のプロダクションコードではビジネスロジックが複雑でテストが書きづらいケースなどがあり、もう少し複雑なアプローチが必要かもしれません。「レガシーコード改善ガイド」などで様々なテクニックが書かれているので、参考にしてみると良いでしょう。