まえがき
私はnot大規模なスパゲッティコードクラスをリファクタリングする方法を探しています。そのトピックは他の質問でカバーされています。
質問
4000行を超え、2000行を超える単一の巨大な更新メソッドを持つ別の同僚が作成したクラスファイルを理解するための手法を探しています。
最終的には、このクラスの単体テストを作成し、DRYおよび単一責任の原則)に続く多くの小さなクラスにリファクタリングしたいと考えています。
このタスクをどのように管理およびアプローチできますか?最終的には、クラス内で発生するイベントの図を描き、抽象化機能に進むことができるようになりたいのですが、クラスの責任と依存関係が何であるかをトップダウンで把握するのに苦労しています。
編集:入力パラメータに関連するトピックをすでにカバーしている回答の中で、この情報は初心者向けです。
この場合、クラスのメインメソッドには入力パラメーターがなく、停止するように指示されるまでwhileループが実行されます。コンストラクターもパラメーターを取りません。
これにより、クラスの混乱、要件、依存関係が増加します。クラスは、静的メソッド、シングルトンを介して他のクラスへの参照を取得し、すでに参照しているクラスを介して到達します。
興味深いことに、このようなコードを理解するには、リファクタリングが最も効率的な方法です。最初はきれいにする必要はありません。迅速かつダーティーな分析のリファクタリングパスを実行してから、ユニットテストを使用して元に戻し、より慎重に実行できます。
これが機能する理由は、正しく行われるリファクタリングが一連の小さな、ほとんど機械的な変更であるため、コードを実際に理解しなくても実行できるためです。たとえば、どこでも繰り返されているコードの塊を見て、それがどのように機能するかを実際に知る必要なく、メソッドに組み込むことができます。最初はstep1
か何か。次に、step1
およびstep7
は頻繁に一緒に現れるようで、それを除外します。すると、突然煙が晴れて、実際に全体像を見て意味のある名前を付けることができます。
全体的な構造を見つけるために、依存関係グラフを作成すると役立つことがわかりました。私は手動で graphviz を使用してこれを行います。手動の作業がそれを学ぶのに役立つことがわかったので、おそらく自動化ツールが利用可能です。これは私が取り組んでいる機能の最近の例です。これは、この情報を同僚に伝える効果的な方法でもあることがわかりました。
最初に、質問に答える前に(あなたが求めているものではないとしても)警告の言葉:非常に重要な質問はwhyです何かをリファクタリングしたいですか?あなたが「同僚」について言及しているという事実は、疑問を投げかけます:変更のビジネス上の理由はありますか?特に企業環境では、満足できないレガシーコードと新しくコミットされたコードの両方が常に存在します。問題の事実は、2人のエンジニアが同じソリューションで特定の問題を解決することはないということです。それが機能し、パフォーマンスに重大な影響がない場合は、そのままにしておいてください。実装は特に好きではないため、機能の作業はリファクタリングよりもはるかに有益です。とはいえ、技術的な負債と保守不可能なコードのクリーンアップについては、言うべきことがいくつかあります。ここでの私の唯一の提案は、あなたはすでに区別をし、変更を加えるために賛成しているということです。そうしないと、おそらく「なぜこれらの変更を行うのか」と尋ねられます後多大な労力を費やして沈没すると、さらに多くの(そして理解可能な)プッシュバックが得られるでしょう。
リファクタリングに取り組む前に、私は常にfirmが何を変更しているかを理解していることを確認したいと思っています。この種の問題に取り組むときに私が一般的に採用している方法は、ロギングとデバッグの2つです。後者は、単体テスト(およびそれらが理にかなっている場合はシステムテスト)の存在によって促進されます。あなたが持っていないので、私は強く変更が予期しない動作を引き起こさないことを確実にするために完全なブランチカバレッジでテストを書くことを勧めます変更。ユニットテストとコードのステップ実行は、制御された環境での動作を低レベルで理解するのに役立ちます。ロギングは、実際の動作をよりよく理解するのに役立ちます(これは、マルチスレッドの状況で特に役立ちます)。
デバッグとロギングを通じて行われているすべてのことをよりよく理解したら、次にコードの読み取りを開始します。この時点で、私は一般に何が起こっているのかを理解し、座っているので、2k LoCを読むことはその時点ではるかに意味があるはずです。ここでの私の意図は、エンドツーエンドのビューを取得し、私が記述した単体テストがカバーしていないような奇妙なEdgeケースがないことを確認することです。
それから私はデザインを考え、新しいものを作成します。下位互換性が重要な場合は、パブリックインターフェイスが変更されないようにします。
何が起こっているのかをしっかりと理解し、テストを行い、新しいデザインを手に入れたら、レビューのために取り上げます。設計が少なくとも2人(コードに精通していることを願っています)によって吟味されたら、変更の実装を開始します。
それがあまりにも痛々しいほど明白ではなく、役立つことを願っています。
一般的なレシピはありませんが、いくつかの経験則(静的に型付けされた言語を想定しかし、それは実際には重要ではありません):
1)メソッドシグネチャを見てください。何がinになるかを伝え、うまくいけばoutが来る。この神クラスの全体的な状態から、これは最初の苦痛点だと思います。複数のパラメータが使用されていると思います。
2)エディタから検索機能を使用する/ EDIを決定するには出口ポイント(通常はa returnstatement_が使用されます)
あなたが知っていることから、テストのための関数必要なとリターンでの期待.
したがって、簡単な最初のテストは必要なパラメータを使用して関数を呼び出すであり、結果がnon-nullになることを期待します。それは多くはありませんが、出発点です。
それから、 解釈学の円 (H.G.ガダマーによって造られた用語-ドイツの哲学者)を入力できます。重要なのは、これでクラスの基本的な理解が得られ、この理解を新しい詳細な知識で更新し、クラス全体を新しく理解することです。
これを 科学的方法 と組み合わせて、仮定を行い、それらが成立するかどうかを調べます。
3)1つのパラメータを取得して、クラス内で何らかの形で変換されている場所を確認します。
例えば。あなたがやっているJava=私のように、通常getterとsetterがあり、探すことができます。検索パターン$objectname
。(または$objectname\.(get|set)
Javaをやっている)
これで、メソッドが何をするかについて、さらに仮定を行うことができます。
トレースのみ入力パラメーター(最初)それぞれメソッドを介して。必要に応じて、いくつかの図または表を作成します。ここで、各変数に対するすべての変更を書き留めます。
それから、メソッドの動作を説明する追加のテストを書くことができます。各入力パラメーターがどのように大まかに理解している場合は、メソッドに沿って変換、実験を開始:nullで渡す1つのパラメーターまたは 奇妙な入力 について。仮定を立て、結果を確認し、入力と仮定を変更します。
これを一度行うと、メソッドの動作を説明する「トン」のテストができます。
4)次のステップで、私は依存性を探します-:入力から作業以外に、メソッドに必要なもの正しく?それらを削減または再構築する可能性はありますか?依存関係が少ないほど、最初の分割を行うポイントが明確になります。
5)そこから、リファクタリングパターンとリファクタリングパターンとリファクタリングを使ってパターン全体に移動できます。
ここで素敵なVid: GoGaRuCo 2014-トラブルシューティングの科学的方法 それはトラブルシューティングについてですが、それでも理解何か動作の一般的な方法論には役立ちます。
あなたは、呼び出された関数に入力パラメータなしがあることを述べました:この特別な場合私はまず依存関係を特定し、それらをパラメータにリファクタリングして、それらを入れ替えることができるようにします好きなように。
あなたは2000行のupdate
メソッドについて言及しています。
私はこの方法を上から下に読み始めます。互いに属している一連のステートメントを見つけた場合、つまり1つの責任を共有している場合(たとえば、ユーザーを作成している場合)、これらのステートメントを関数に抽出します。
この方法では、コードの動作は変更しませんが、
他の方法についてもこれらの手順を繰り返します。