リファクタリング-すべてのテストに合格する限り、単純にコードを書き直すことは適切ですか?


9

最近、RailsConf 2014の「All the Little Things」を視聴しました。この講演中に、Sandi Metzは、ネストされた大きなifステートメントを含む関数をリファクタリングします。

def tick
    if @name != 'Aged Brie' && @name != 'Backstage passes to a TAFKAL80ETC concert'
        if @quality > 0
            if @name != 'Sulfuras, Hand of Ragnaros'
                @quality -= 1
            end
        end
    else
        ...
    end
    ...
end

最初のステップは、関数をいくつかの小さなものに分割することです:

def tick
    case name
    when 'Aged Brie'
        return brie_tick
    ...
    end
end

def brie_tick
    @days_remaining -= 1
    return if quality >= 50

    @quality += 1
    @quality += 1 if @days_remaining <= 0
end

私が興味深いと思ったのは、これらの小さな関数の書き方でした。brie_tickたとえば、は元のtick関数の関連部分を抽出して作成されたのではなく、test_brie_*単体テストを参照して最初から作成されました。これらの単体テストのすべてに合格すると、brie_tick完了したと見なされました。小さな機能がすべて完了すると、元のモノリシックtick機能は削除されました。

残念ながら、プレゼンターは、このアプローチが4つの*_tick機能のうち3つが間違っていた(そしてもう1つは空だった)ことに気づいていないように見えました。*_tick関数の動作が元の関数の動作と異なる場合がありtickます。たとえば、@days_remaining <= 0in brie_tick< 0- であるべきです- およびでbrie_tick呼び出された場合、正しく動作しません 。days_remaining == 1quality < 50

ここで何が問題になっていますか?これはテストの失敗ですか?これらの特定のエッジケースのテストがなかったためですか?または、リファクタリングの失敗-コードを最初から書き直すのではなく、段階的に変換する必要があったためですか?


2
質問を受けるかどうかはわかりません。もちろんコードを書き直しても大丈夫です。「コードを単純に書き直しても大丈夫ですか」という具体的な意味がわかりません。「多くのことを考えずにコードを書き直しても大丈夫ですか」と尋ねるなら、答えはノーです。その方法でコードを書くのは良くないのと同じです。
John Wu

これは主に、主に成功のユースケースのテストに焦点を当てたテスト計画と、エラーのユースケースまたはサブユースケースのカバーがほとんど(またはまったくない)ために発生します。したがって、それは主にカバレッジの漏洩です。テストのリーク。
Laiv

@JohnWu-私はリファクタリングが一般的にソースコードへの一連の小さな変換( "extract-method"など)として単にコードを書き換えるのではなく(つまり、一から書き直すことなく)リンクされたプレゼンテーションで行われたように、既存のコードを調べます)。
user200783 2018年

@JohnWu-ゼロからの書き換えは許容可能なリファクタリング手法ですか?そうでなければ、リファクタリングに関するそのような評判の良いプレゼンテーションがそのアプローチを取るのを見るのは残念です。OTOHが許容できる場合、意図しない動作の変化は欠落しているテストのせいにすることができますが、テストがすべての可能なエッジケースをカバーしていることを確信する方法はありますか?
user200783 2018年

@ User200783それはもっと大きな質問ですね(実際にテストが包括的であることを確認するにはどうすればよいですか?)演習を行い、開発チームがロジックを書き換えるときに、それらを意識するようにします。
John Wu

回答:


11

これはテストの失敗ですか?これらの特定のエッジケースのテストがなかったためですか?または、リファクタリングの失敗-コードを最初から書き直すのではなく、段階的に変換する必要があったためですか?

どちらも。Fowlersのオリジナルブックの標準的なステップのみを使用してリファクタリングすることは、書き換えを行うよりも間違いなくエラーが発生しにくいため、多くの場合、これらの種類のベビーステップのみを使用することが推奨されます。すべてのエッジケースの単体テストがなく、環境が自動リファクタリングを提供していない場合でも、「説明変数の導入」や「関数の抽出」などの単一のコード変更では、動作の詳細を変更する機会がはるかに少なくなります。関数の完全な書き直しよりも既存のコード。

ただし、場合によっては、コードのセクションを書き換えることが必要またはやりたいことです。その場合は、より良いテストが必要です。

リファクタリングツールを使用する場合でも、小さいステップまたは大きいステップを適用するかどうかに関係なく、コードを変更するとエラーが発生するリスクが常にあることに注意してください。そのため、リファクタリングには常にテストが必要です。テストはバグの可能性を減らすだけで、バグがないことを証明できないことにも注意してください。それでも、コードやブランチカバレッジを調べるなどの手法を使用すると、高い信頼レベルが得られます。コードセクションを書き換えた場合、多くの場合、このような手法を適用する価値があります。


1
ありがとう、それは理にかなっています。それで、望ましくない動作の変化に対する最終的な解決策が包括的なテストを持つことである場合、テストがすべての可能なエッジケースをカバーすることを確信する方法はありますか?例えば、100%のカバレッジ有することが可能であろうbrie_tick依然として決して問題のテストない状態@days_remaining == 1でテスト、例えば、ケース@days_remainingの組10とを-10
user200783 2018年

2
すべての可能な入力でテストするのは現実的ではないため、テストがすべての可能なエッジケースをカバーしていることを絶対に確信することはできません。しかし、テストの信頼性を高める方法はたくさんあります。テストの有効性をテストする方法であるミューテーションテストを調べることができます。
bdsl 2018年

1
この場合、見逃されたブランチは、テストの開発中にコードカバレッジツールで検出された可能性があります。
cbojar 2018年

2

ここで何が問題になっていますか?これはテストの失敗ですか?これらの特定のエッジケースのテストがなかったためですか?または、リファクタリングの失敗-コードを最初から書き直すのではなく、段階的に変換する必要があったためですか?

レガシーコードの操作で非常に困難なことの1つは、現在の動作を完全に理解することです。

すべての動作を制約するテストのないレガシーコードは、一般的なパターンです。どちらが当てはまるかというと、それは制約のない動作が自由変数であることを意味しますか?または十分に指定されていない要件?

話から

これは、リファクタリングの定義による実際のリファクタリングです。このコードをリファクタリングします。動作を変更せずに配置を変更します。

これはより保守的なアプローチです。要件が十分に指定されていない可能性がある場合、テストで既存のロジックのすべてがキャプチャされない場合は、続行する方法について非常に注意する必要があります。

確かに、テストがシステムの動作を不適切に記述している場合、「テストの失敗」があると断言できます。そして、それは公平だと思いますが、実際には有用ではありません。これは一般的な問題です。

または、リファクタリングの失敗-コードを最初から書き直すのではなく、段階的に変換する必要があったためですか?

問題はないが、かなりの変換は、ステップ・バイ・ステップされている必要があること。エラー率が高いため、リファクタリングツール(ガイド付きオートメーションではなく、人間のキーボードオペレーター?)の選択がテストカバレッジとうまく一致していなかったのです。

これには、信頼性の高いリファクタリングツールを使用する、システムの制約を改善するためのより幅広いテストバッテリーを導入することで対処できます。

だから私はあなたの接続詞がよく選ばれていないと思います。ANDないOR


2

リファクタリングは、コードの外部から見える動作を変更するべきではありません。それが目標です。

単体テストが失敗した場合は、動作が変更されたことを示しています。しかし、単体テストに合格することは決して目標ではありません。それは多かれ少なかれあなたの目標を達成するのに役立ちます。リファクタリングによって外部から見える動作が変更され、すべての単体テストに合格した場合、リファクタリングは失敗しました。

この場合の単体テストは、間違った成功感を与えるだけです。しかし、何が問題でしたか?2つのこと:リファクタリングは不注意であり、単体テストはあまり良くありませんでした。


1

「正しい」を「テストに合格」と定義した場合、定義上、テストされていない動作を変更することは間違いありません。

特定のエッジ動作定義する必要がある場合は、そのテストを追加します。定義されていない場合は、何が発生してもかまいません。あなたが本当に知識を持っているtrue場合は、そのエッジケースでいつ動作が気にならないかを文書化するためにチェックするテストを書くことができます

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.