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


10

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

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

費用がいくらかかかるので、正式な方法は使用できません。

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

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

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

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


単体テストを作成すると、このプロジェクトの時間が長くなるという理由は何ですか?多くの支持者は反対しますが、それを書く能力も条件となります。
JeffO 2013

プロジェクト全体の時間が増えるとは思いません。私が言いたかったのは、それが短期間の時間(つまり、コードをリファクタリングするときに私が現在費やす即時の時間)を増加させるということです。
Arseni Mourzenko 2013

1
あなたは使用するワンではないでしょうformal methods in software development、述語論理を使用して、プログラムの正しさを証明するために使用され、大規模なコードベースをリファクタリングに適用可能性を持っていないので、とにかく。コードを証明するために通常使用される正式な方法は、医療アプリケーションなどの分野で正しく機能します。あなたが正しいことは、それを行うにはコストがかかるため、それが頻繁に使用されない理由です。
Mushy

ReSharperのリファクタリングオプションなどの優れたツールを使用すると、このような作業がはるかに簡単になります。このような状況では、それだけの価値があります。
billy.bob 2013

1
完全な答えではありませんが、他のすべてのリファクタリング技法が失敗したときに驚くほど効果的であると思う愚かな技法:新しいクラスを作成し、関数を別の関数に分割し、すでにコードが正確に存在するようにし、50行ほど改行するだけで、関数間でメンバー間で共有されるローカル変数を使用すると、個々の関数が私の頭の中によりよくフィットし、どの部分がロジック全体に通されているかをメンバーで見ることができます。これは最終目標ではなく、レガシーの混乱を安全にリファクタリングする準備を整える安全な方法です。
ジミーHoffa

回答:


12

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

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

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

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

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

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


2
「考古学」を単独で使用する場合は+1。それは私がこの活動を説明するために使用するのと同じ用語であり、それを表現するのに最適な方法だと思います(答えも良かったと思いました-私はそれほど浅くない)
Erik Dietrich

10

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

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

問題は、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)、テストを記述するための入力と出力のセットができました。所有しているすべてのパラメーター/結果セットを反復処理し、それらをループで実行する単一のテストを作成できます。特定の組み合わせを実行する追加テストを作成することもできます(特定のI / Oセットの通過をすばやく確認するために使用されます)。

  4. 移動// 500 LOC hereあなたに戻しugly500loc機能(およびログ記録機能を削除)。

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

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


3

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

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

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

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

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

私の知る限り、これに対する防弾技術はありません...あなたは(あなたが快適に感じるどんな方法でも)系統的で、多くの時間と多くの忍耐を必要としています!:)

乾杯と幸運を!

アレックス


ここでは、コードカバレッジツールが不可欠です。検査を介して大規模で複雑な方法ですべての経路をカバーしたことを確認することは困難です。KitchenSinkMethodTest01()... KitchenSinkMethodTest17()が集合的に1〜45、48〜220、245〜399、および488〜500行をカバーするが、その間のコードには触れないことを示すツール。作成する必要がある追加のテストをはるかに簡単に理解できるようになります。
Danは、Firelightによっていじくら
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.