小さなクラスにリファクタリングする前に大きなクラスを分析する最良の方法は?


8

序文

大規模なスパゲッティコードクラスをリファクタリングする方法を探しているのではありません。そのトピックは他の質問で取り上げられています。

質問

4000行を超え、2000行を超える単一の巨大な更新メソッドを持つ別の同僚が作成したクラスファイルを理解するための手法を探しています。

結局、私はこのクラスのユニットテストを構築し、DRYと単一責任原則に従う多くの小さなクラスにリファクタリングしたいと考えています。

このタスクをどのように管理およびアプローチできますか?最終的には、クラス内で発生するイベントの図を描き、次に抽象化機能に進むことができるようになりたいのですが、クラスの責任と依存関係が何であるかをトップダウンで把握するのに苦労しています。


編集:人々が入力パラメーターに関連するトピックをすでにカバーしている回答では、この情報は初心者向けです。

この場合、クラスのメインメソッドには入力パラメーターがなく、停止するように指示されるまでwhileループが実行されます。コンストラクターもパラメーターを取りません。

これにより、クラス、その要件、および依存関係の混乱が増します。クラスは、静的メソッド、シングルトンを介して他のクラスへの参照を取得し、すでに参照しているクラスを介して到達します。


1
これはほとんどのリファクタリングタスクとどう違うのですか?あなたはインターフェースを壊すつもりはありませんか?
JeffO 2015年

@JeffO私は、彼は30000フィートのビューを望んでいます。「抽出メソッド」、「変数の抽出」などではない
Thomas Junk

1
最終的に単体テストを構築しないでください。ユニットテストの構築を今すぐ開始し、テストでは、クラスについて学習するすべての情報をエンコードします。(残念ながら、テストが全体像に混乱を追加することになりますされ、クラスでバグを発見するかもしれないが、それはまだ良い場所に任意のテストをせずに何が起こっているのか理解しようとするよりもです。)
マイク・Nakis

2
:重複する可能性があり programmers.stackexchange.com/questions/6395/...
JeffO

回答:


10

興味深いことに、このようなコードを理解するには、リファクタリングが最も効率的な方法です。最初はきれいにする必要はありません。迅速かつダーティーな分析リファクタリングパスを実行してから、ユニットテストを使用して元に戻し、より慎重に実行できます。

これが機能する理由は、正しく行われるリファクタリングが一連の小さな、ほとんど機械的な変更であり、コードを実際に理解しなくても実行できるためです。たとえば、あらゆる場所で繰り返されているコードの塊を確認して、それがどのように機能するかを知る必要なく、メソッドに組み込むことができます。最初は、なんとなく不自然な名前を付けるかもしれませんstep1。そして、私がいることに気づくstep1step7、頻繁に一緒に表示されているように見える、と要因出ています。すると、突然煙が晴れて、実際に全体像を見て意味のある名前を付けることができます。

全体的な構造を見つけるために、依存関係グラフを作成すると役立つことがわかりました。手動で作業することで学習できることがわかったので、graphvizを使用して手動でこれを行いますが、おそらく自動化ツールが利用可能です。これは私が取り組んでいる機能の最近の例です。これは、この情報を同僚に伝える効果的な方法でもあることがわかりました。

ディペンデンシーグラフ


2
今日のインタビューの前にこれを読んでいたらいいのに…。クラスをリファクタリングするタスクが与えられたので、これはとても役に立ちました。
2017

8

最初に、質問に答える前に(あなたが求めているものではありませんが)警告の言葉:非常に重要な質問はなぜですか何かをリファクタリングしたいですか?あなたが「同僚」について言及しているという事実は、疑問を投げかけます。変更のビジネス上の理由はありますか?特に企業環境では、満足できないレガシーコードと新しくコミットされたコードの両方が常に存在します。問題の事実は、2人のエンジニアが同じソリューションで特定の問題を解決することはないということです。それが機能し、パフォーマンスに重大な影響がない場合は、そのままにしておいてください。実装は特に好きではないため、機能の作業はリファクタリングよりもはるかに有益です。とはいえ、技術的な負債と保守不可能なコードのクリーンアップについては、言うべきことがいくつかあります。ここでの私の唯一の提案は、あなたはすでに区別をし、変更を加えるために賛同を得ているということです。多大な労力を費やして沈没したは、さらに多くの(そして理解可能な)反発が得られる可能性があります。


リファクタリングに取り組む前に、自分が何を変更しているかをしっかりと理解していることを常に確認したいと思います。この種の問題に取り組むときに私が一般的に使用する方法は2つあります。それは、ロギングとデバッグです。後者は、単体テスト(および意味のある場合はシステムテスト)の存在によって促進されます。何もないので、変更によって予期しない動作の変更が発生しないことを確認するために、完全なブランチカバレッジでテストを作成することを強くお勧めします。ユニットテストとコードのステップ実行は、制御された環境での動作を低レベルで理解するのに役立ちます。ロギングは、実際の動作をよりよく理解するのに役立ちます(これは、マルチスレッドの状況で特に役立ちます)。

私は、デバッグとロギング経由で起こっているすべてのより良い理解を得れば、その後、私は、コードを読み始めます。この時点で、私は一般に何が起こっているのかを理解し、座っているので、2k LoCを読むことはその時点ではるかに意味があるはずです。ここでの私の意図は、エンドツーエンドのビューを取得し、私が記述した単体テストがカバーしていないような奇妙なエッジケースがないことを確認することです。

それから私はデザインを考え、新しいものを作成します。下位互換性が重要な場合は、パブリックインターフェイスが変更されないようにします。

何が起こっているのかをしっかりと理解し、テストを行い、新しいデザインを手に入れたら、レビューのためにそれを置きます。設計が少なくとも2人の担当者によって精査されたら(できればコードに精通しているとしたら)、変更の実装を開始します。

それがあまりにも痛々しいほど明白ではなく、役立つことを願っています。


1
あなたの答えは私にとって本当に役に立ちます。私は実際には最初に現在のクラスからログ結果を生成することを考えていませんでしたが、いくつかの問題があります:このクラスを単体テストできないと思います。その主な機能はすべて、入力パラメーターのない1つのメソッドで発生します。このメソッドは、渡されない他のクラスからほとんどの状態を取得します。コンストラクターも引数を取りません。依存関係はシングルトンを通じてアクセスされます。あなたの提案は、最初にユニットテストを行ってから再設計を始めることですが、ユニットテストのために再設計が必要になる可能性が高いことが判明した場合、どのように適応しますか?
sydan 2015年

注意事項についての補足。私はこれを理解しています。人々がこれらの事柄をそのままにしておくのがどれほど頻繁に最善であるかについて話しているのを見ましたが、クラスは現在まだ開発中であり、新しい開発者がそれを使い始めています。新しい変更と単体テストがないため、変更がクラスにどのように影響するかを理解する方法はありません。私はクラスの1つの機能を抽象化するタスクをすでに受けており、このタスクを完了しましたが、これにより問題の巨大なサイズにほとんど違いがなく、さらに多くの必要があると思います。
sydan 2015年

1
@sydan:他のクラスとの相互作用を模擬できますか?これは一部の言語では他の言語よりも簡単なことです。それがノーゴーの場合、おそらく開始するためのシステムテストですか?
Demian Brecht

興味深い、なぜ反対票か。
Demian Brecht

私も興味があります。
sydan 2015年

4

一般的なレシピはありませんが、いくつかの経験則があります(静的に型付けされた言語を想定していますが、それほど重要ではありません)。

1)メソッドシグネチャを見てください。何が入り、うまくいけば出くるかを教えてくれます。このゴッドクラスの全体的な状態から、これは最初の問題だと思います。複数のパラメータが使用されていると思います。

2)エディター/ EDIの検索機能を使用して、出口点を決定します(通常、return statement_が使用されます)

あなたが知っていることから、関数がどのような必要のためにテストすると、あなたは何を期待して復帰

したがって、簡単な最初のテストでは、必要なパラメーターを指定して関数を呼び出し、結果がnullないことを期待します。それは多くはありませんが、出発点です。

それから、解釈学のサークル(HGガダマーによって造られた用語-ドイツの哲学者)に入ることができます。ポイントは次のとおりです。これでクラスの基本的な理解が得られ、この理解を新しい詳細な知識で更新し、クラス全体の新しい理解を得ることができます。

これを科学的方法と組み合わせます。仮定を行い、それらが成立するかどうかを確認します。

3) 1つのパラメーターを取得して、クラス内で何らかの形で変換されている場所を確認します。

たとえば、私と同じようにJavaを使用している場合、通常はゲッターセッターを確認できます。検索パターン$objectname。(または$objectname\.(get|set)Javaを使用している場合)

これで、メソッドが何をするかについて、さらに仮定を行うことができます。

メソッドを介して、入力パラメーターのみ最初に)をトレースします。必要に応じて、いくつかのまたは表を作成し、各変数に対するすべての変更を書き留めます。

それから、メソッドの動作を説明する追加のテストを書くことができます。あなたは、各入力パラメータがどのようにされてラフに理解している場合変換方法に沿ってを、実験開始nullを渡し一つのパラメータまたはのために奇妙な入力。仮定を立て、結果を確認し、入力と仮定を変更します。

これを一度行うと、メソッドの動作を説明する「トン」のテストができます。

4)次のステップでは、私は探します依存関係:そのほかの方法の必要性はないものを入力して正常に動作しますか?それらを削減または再構築する可能性はありますか?依存関係が少ないほど、最初の分割を行うポイントが明確になります。

5)そこから、リファクタリングパターンリファクタリングパターンを使用してリファクタリングの全体を進むことができます。

ここで素晴らしいVid:GoGaRuCo 2014-トラブルシューティングの科学的方法トラブルシューティングについてですが、何かがどのように機能するかを理解する一般的な方法論には役立ちます。

あなたは、呼び出された関数に入力パラメーターないことを述べました:この特別なケースでは、最初に依存関係を特定し、それらをパラメーターにリファクタリングして、好きなようにそれらを入れ替えることができるようにします。


こんにちはトーマス、あなたの答えをありがとう、デミアンのように、それは非常に便利で、優れた方法論的アプローチです。Demianが提案するログと、あなたが提案したさまざまな入力を組み合わせることで、このルートを下に移動し始めると思います。残念ながら、このクラスが実行するメソッドには入力パラメーターがありません。それは永遠のwhileループを実行する時計のタスクであり、依存関係がシングルトン、静的クラスを介して取得され、次にクラスを介して他のオブジェクトを取得することによって取得されているように見えます。
sydan 2015年

クラスが使用する変数が入力、定数、出力、一時変数、またはランダムデバッグ変数であるかを知る方法がないため、メソッド入力を確立する最初の段階は、おそらく私にとって最も難しいでしょう。
sydan 2015年

ああ、楽しそうですね!おそらく、そこに飛び込んで、最初に見つかったオブジェクトをたどります。その後、円は説明されたように始まります;)
Thomas Junk、

それが最善のルートかもしれません...最もよく使われる変数の 'ctrl-f'は強力な出発点のように見えますか?入力パラメーターとメソッド構造の欠如について言及するために、元の質問を編集する必要がありますか?
sydan 2015年

1
私はあなたがあなたの編集で述べたことを好きです。
sydan 2015年

4

あなたは2000行のupdate方法に言及します。

私はこの方法を上から下に読み始めます。互いに属しているステートメントのセットを見つけた場合、つまり1つの責任を共有している場合(たとえば、ユーザーを作成した場合)、これらのステートメントを関数に抽出します。

この方法では、コードの動作は変更しませんが、

  • この巨大な方法を短くする
  • この方法をよりよく、より高いレベルで理解する
  • さらなるリファクタリングのためのメソッドを準備する

他の方法についてもこれらの手順を繰り返します。


1
このプロセスを非破壊的に(単に読んで、通知して、計画して)行う場合は、コードレビューと呼ばれることもあります。Steven C. McConnell氏の著書-コードコンプリート-には、この機会に
役立つ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.