web-dev-qa-db-ja.com

大きなメソッドをリファクタリングして何も壊さないようにする場合、何が役立ちますか?

私は現在、大規模なコードベースの一部をリファクタリングしており、ユニットテストはまったくありません。私は力ずくでコードをリファクタリングしようとしました。つまり、コードが何をしていてどのような変更が意味を変えないかを推測しようとしましたが、成功しませんでした:コードベースのすべての機能をランダムに破壊しました。

リファクタリングには、レガシーC#コードをより機能的なスタイルに移行すること(レガシーコードはLINQを含む.NET Framework 3以降の機能を使用しません)、コードのメリットがあるジェネリックを追加することなどが含まれます。

彼らがいくらかかるかを考えると、私は formal methods を使用できません。

一方で、少なくとも "リファクタリングされたレガシーコードにはユニットテストが付属する" ルールは、いくらコストがかかっても、厳密に従う必要があると思います。問題は、500 LOCプライベートメソッドのごく一部をリファクタリングするときに、単体テストを追加することが難しいように見えることです。

特定のコードに関連する単体テストを知るのに役立つものは何ですか?コードの静的分析が何らかの形で役立つと思いますが、次の目的で使用できるツールと手法は何ですか。

  • どのユニットテストを作成すればよいかを正確に把握し、

  • そして/または私が行った変更が元のコードに影響を及ぼし、現在とは異なる方法で実行されているかどうかを知っていますか?

10

私も同様の課題を抱えています。 Working with Legacy Codeの本はすばらしいリソースですが、単体テストで作業を進めて作業をサポートできるという前提があります。時にはそれが不可能な場合もあります。

私の考古学の仕事(このようなレガシーコードのメンテナンスの私の用語)では、あなたが概説したのと同様のアプローチに従います。

  • ルーチンが現在何をしているかをしっかりと理解することから始めます。
  • 同時に、ルーチンが何であったかを特定します想定してください。この弾丸と前の弾丸は同じだと多くの人が考えていますが、微妙な違いがあります。多くの場合、ルーチンが想定されていることを実行していた場合、メンテナンスの変更を適用しません。
  • ルーチンを介していくつかのサンプルを実行し、境界線のケース、関連するエラーパス、およびメインラインパスにヒットすることを確認します。私の経験では、付随的な損傷(機能の破損)は、境界条件がまったく同じ方法で実装されていないことに起因しています。
  • これらのサンプルケースの後、永続化する必要のないものを特定します。繰り返しますが、このような副作用が他の場所で付随的な損傷につながることがわかりました。

この時点で、そのルーチンによって公開および/または操作されたものの候補リストが必要です。それらの操作の一部は、不注意である可能性があります。ここで、findstrとIDE=を使用して、候補リストの項目を参照する可能性のある他の領域を理解します。これらの参照がどのように機能するか、およびそれらの機能が何であるかを理解するのに少し時間を費やします自然です。

最後に、元のルーチンの影響を理解していると思い込んだら、一度に1つずつ変更を加え、上で概説した分析手順を再実行して、変更が期待どおりに機能していることを確認します。それが動作するように。影響を確認しようとすると、これが私に吹き荒れることがわかったので、一度に複数のものを変更しないように特に努めています。場合によっては、複数の変更を回避できますが、一度に1つずつルートをたどることができれば、それが私の好みです。

要するに、私のアプローチはあなたが示したものに似ています。それは多くの準備作業です。次に慎重に、個別に変更します。そして、検証、検証、検証します。

12
user53019

大きなメソッドをリファクタリングして何も壊さないようにするにはどうすればよいですか?

短い答え:小さなステップ。

問題は、500 LOCプライベートメソッドのごく一部をリファクタリングするときに、単体テストを追加することが難しいように見えることです。

次の手順を検討してください。

  1. 実装を別の(プライベート)関数に移動し、呼び出しを委任します。

    _// old:
    private int ugly500loc(int parameters) {
        // 500 LOC here
    }
    
    // new:    
    private int ugly500loc_old(int parameters) {
        // 500 LOC here
    }
    
    private void ugly500loc(int parameters) {
        return ugly500loc_old(parameters);
    }
    _
  2. すべての入力と出力に対して、元の関数にログコードを追加します(ログが失敗しないことを確認してください)。

    _private void ugly500loc(int parameters) {
        static int call_count = 0;
        int current = ++call_count;
        save_to_file(current, parameters);
        int result = ugly500loc_old(parameters);
        save_to_file(current, result); // result, any exceptions, etc.
        return result;
    }
    _

    アプリケーションを実行し、それを使用してできることをすべて実行します(有効な使用、無効な使用、通常の使用、通常でない使用など).

  3. これで、テストを記述するためのmax(call_count)入力および出力のセットができました。 singleテストを記述して、すべてのパラメーター/結果セットを反復処理し、ループで実行することができます。特定の組み合わせを実行する追加テストを作成することもできます(特定のI/Oセットの通過をすばやく確認するために使用されます)。

  4. _// 500 LOC here_を_ugly500loc_関数に戻します(ロギング機能を削除します)。

  5. 大きな関数から関数の抽出を開始し(他に何もせず、関数を抽出するだけ)、テストを実行します。この後、500LOC関数の代わりに、リファクタリングする関数が少し増えるはずです。

  6. 末永く幸せに過ごす。

10
utnapistim

通常、単体テストが適しています。

電流が期待どおりに機能することを証明する必要なテストを行います。時間をかけて、最後のテストで出力に自信を持たせる必要があります。

特定のコードに関連する単体テストを知るのに役立つものは何ですか?

あなたはコードの一部をリファクタリングしている最中です、あなたはそれが何をしそしてそれが何に影響を与えるかを正確に知る必要があります。したがって、基本的には、影響を受けるすべてのゾーンをテストする必要があります。これには多くの時間がかかります...しかし、これはリファクタリングプロセスの予想される結果です。

その後、問題なくすべてを引き裂くことができます。

私の知る限り、これには弾丸を証明するテクニックはありません...(あなたが快適に感じるどんな方法でも)系統立てる必要があり、多くの時間と多くの忍耐が必要です! :)

乾杯と幸運を!

アレックス

3
AlexCode