web-dev-qa-db-ja.com

レガシーコード:メンテナンスと前進に関して何をすべきか?

私は現在、概念実証(POC)を使用して構築されたレガシーコードを使用するアプリケーションに取り組んでいます。これらのPOCは、完成した本番用コードになり、テストは行われず、クラスは時間の経過とともに非常に大きくなりました(ほとんどの神オブジェクトであり、この構造に従う複数のオブジェクトがあります)。これらの質問は、レガシーコードとしてこれらの神オブジェクトに関連しています。

アプリケーション自体については十分に機能しますが、ユーザーの邪魔になるバグがあります。いくつかの回避策がありますが、製品全体に信頼性がないという雰囲気があります(結合のためにバグがアプリケーションに戻ってきます)。開発チームにとって、大きなクラス、密結合、および特定のビジネスルールと1回限りの実装のレガシーコードの継承に関する一般的な問題により、機能を追加/変更する恐れがあります。

レガシーコードでは、コードのメンテナンスまたはリファクタリングのコストを確認する必要があることを理解しています。 POCを本番用コードとして使用および結合すると、リファクタリングがかなり大規模な作業になるため、リファクタリングのほとんどの試みは価値がありません。私は「レガシーコードを使用した効果的な作業」の本の一部を読みましたが、このシナリオが具体的に発生するかどうかはわかりません。また、これらの種類のクラス、または特定の機能の有効なテストを開発することは、リファクタリングと同じコストレビューに該当する可能性があると思います。 (仮定が間違っている場合は訂正してください)

新しい機能については、どうなるかわかりません。新しい機能をチームが決定した優先構造に分離できるという考えはありますが、機能をいくつかの大規模なクラスの残りのメソッドに組み込むことで一貫性を保つという議論があります。一貫性とは、多くのエンティティの多くの用途/ CRUDを含むクラスが新しい機能を受け取る必要があるということです。一貫性については、すべての機能を同じ場所に配置することで、技術的負債の「可能な」見返りを得ることができます。

これらの質問は好みに傾いており、通常、経営陣は方向性を決定する上でより重要です。私にとっての主なポイントは、外部の視点です。チーム(私も含む)が見逃しているディスカッションやその他のアプローチがあるかもしれませんが、長期的には役立つかもしれません。私たちはできることをすべて行うことができ、それがこれらの成長する苦痛を押し通す段階に私たちを残します。

質問

メンテナンスの場合、一貫性が優先されても、大規模なクラスから機能の一部を引き出すことを伴うリファクタリングを回避できない場合がありますか?もしそうなら、それでは良い例は何でしょうか? (正方形の長方形の問題で見られるような、このシナリオに当てはまる「問題」があるかどうかはわかりません)

(私の例:大規模なクラスはフォーカスされているエンティティを処理するためのものであるため、一部の機能が壊れますが、常にフォーカスされているわけではない複数のエンティティに使用されています。エンティティがフォーカスされているという考えを変更することはできません。機能の一部が壊れます。)

前進するために:機能を追加するための望ましい方法は、チーム/管理次第である必要がありますか? (状況に応じて、分離するか、一貫性を保つかを決定できます)

新しい機能は、予想される大規模なリファクタリングの有無にかかわらず、常にどちらか一方(常に一貫しているか、常に分離している)である必要がありますか?

1
eparham7861

レガシーコードで効果的に動作するで説明されている手法が大規模に適用されなかった方法で、約10年半前に同様の背景から来た人として、私は非常に非公式でおそらく珍しい答えを与えますその理由は、チームと経営陣がそれらを受け入れたくないからです。

私は、プロファイラーの使用、テストの作成など、長いプレゼンテーションを準備することを強く主張する、そのような率直な若い男でした。振り返ってみると、私は既存のコードベースを想像できる最悪のものとして、そしてマイケルフェザーズ自身がおそらく予想していなかったであろう最悪の方法で見て以来、かなり独断的でした。彼は数百行のコードにわたるモンスターのメソッドについて話しました。散発的なgotoステートメントを含むコードの20,000行を超える無数のC関数、400以上の変数がすべて関数の上部で宣言され、初期化されていない、およびそれらを初期化すると不快になる同僚について話しましょう単純な古いデータ型を初期化する計算コストを恐れているため、初期化されていない変数のバグを調べながら、ランタイムの動作をより決定論的にするようにしてください(コンパイラを最適化すると冗長な初期化が最適化されるという事実は無視されます)。壊れていないものを修正しないという繰り返し反響のあるポリシーがありましたが、コードベースの性質を調べると、ソフトウェアの新しいバージョンごとに、修正するよりも多くのバグが発生することが多く、テストの欠如により、何が壊れていないかを効果的に伝える方法の問題。

これには、ソフトウェアエンジニアリングだけではありません。あなたが潮と激しく戦おうとすると、あなた自身の正気が危機に瀕します。チームが協力していないと感じて陽気な実用主義者になり、裸の女性やビールなどの最も健全なエンジニアリングの原則を使用して構築されたコードベースよりも多くのことに喜びを感じる場合は、少しリラックスするのに役立ちます。

とにかく、まず、モノリスまたはGodオブジェクトは、一連のグローバル変数を持つのと同じように、結合と凝集を弱体化し始めます。プログラムが状態管理につまずき、不変条件を効果的に維持できないことが判明した場合、それらの問題を修正する難しさは、関係する状態の範囲/可視性に比例します。何百もの関数がグローバル変数にアクセスするのと同じように、この永続的な状態にアクセスできる何百ものメソッドがクラスにある場合、クラスメンバーもそうである可能性があります。状態が概念的な不変条件に違反してバグを引き起こす場合、容疑者のリストは、状態、クラスメソッドを変更できるかどうかに関係なく、関数の数に比例します。このように説明すると、同僚がGodオブジェクトを簡単に操作して、妥協案としてクラスの内部にアクセスする必要のない関数をクラスから引き上げ始めるように説得される可能性があります。極端な例として、コードベースの機能全体が単一のGodクラスによって提供された場合、すべてのグローバル変数のみを使用して最悪のCコードベースよりもプライベート状態で不変量を効果的に維持することができなくなります。実用的なデバッグの観点から、物事を正気にするための最優先事項は、通常、状態管理を簡素化し、不変条件を自信を持って維持できるようにすることです。 SOLID)のような原則にも先行し、無効な状態に繰り返し遭遇して最初に状態管理を制御している恐ろしいレガシーコードベースがある場合。

テスト主導型の考え方を採用することに関しては、私の場合、コードベースはファウルであり、チームは非協力的すぎてその考え方を適用できませんでした。散発的な関数とそれ自体が80,000を超えるメインエントリポイントによって7,000個のグローバル変数を適切に初期化する必要があるコードベースで効果的なユニットテストと統合テストの作成を開始する場合は、管理を含むチーム全体が実際に参加する必要があります。コードベースの小さなセクションをテストするためのコード行。

テストに必要な最小限のコードとテストするための合理的なインターフェイス(前者)を考え出そうとしたスタンドアロンプ​​ロジェクトで、ソフトウェアのライセンスシステムの単体テストを構築するために、ある週末に一度だけ試してみました(前者ライセンスインターフェイスはモノリシックで、散発的なGUI機能も含まれていました)。何百もの試行とエラーを必要とする初期化プロセスでライセンスコードを検証するために、ほぼすべてが他のすべてと組み合わされているため、約800,000行のコードを取り込む必要があり、グローバル状態を効果的に理解するために何が必要かを理解しましたそうするように初期化されました。その場合、単体テストの構築には、以前のライセンスコードと同じアルゴリズムを適用してライセンスコードを検証する新しいライセンスライブラリを最初から作成する場合よりもはるかに多くの時間、思考、およびデバッグが必要でした(密結合された80万行のコードではなく、数百行のコードで実行されます)。以前の経験から、厄介なコードベースについてのホラーストーリーを一日中交換することができます。

もしあなたがそのようなシナリオにいるなら、多分最良のオプションは去ることです。それは私が最終的にやったことです。そうは言っても、古いコードを参照目的でのみ使用して、ソフトウェアの主要なセクションをほぼ一から再作成しようとすることは、健全性の目的で有用であることがわかりました。目標は、ソフトウェアに必要な重要な機能を再現する、テスト可能な完全に独立したライブラリを作成することです。それはそれについて取り組む最も生産的な方法ではないかもしれません、コードのソフトウェアの外で完全に働くことはすぐにはメリットになりませんが、最後にあなたは使用できるこの独立したライブラリを手に入れ、自信を持ってチームにデモンストレーションして示しますテストを通じて概念的に機能し、おそらくコードの大きなセクションを、効果的で信頼性の高いことが示されているライブラリに置き換えるのに十分な数の人々を説得することができます。絶望的な複雑さの悪質な海で泳いでいる場合は、自信を高める優れたエクササイズです。

4
user204677

私は一貫性の議論に同意しません、あなたがそれを言うところ

一貫性とは、多くのエンティティの多くの用途/ CRUDを含むクラスが新しい機能を受け取る必要があるということです。

努力すべきことの1つは単一責任の原則であるため、多くのエンティティの多くの関数を含むクラスは避ける必要があります。また、このアプローチを打ち破り、新しい機能または更新された機能を現在のクラスから移動することで、どのコードがまだ「古い」かを簡単に追跡できます。それはあなたに多くの利益を与えるでしょう。時が来れば、何をテストする必要があり、何をリファクタリングする必要があるのか​​をよりよく理解できます。

コードを新しい場所に移動する正確な方法は、配置されているアーキテクチャ、アプリケーションのタイプ、および新しいアーキテクチャがどのようになるかによって異なります。

まず、何に向かっているのかを定義します。これが何であるかについて明確なビジョンがない限り、あなたは付加価値をつけません。例:

  • 従来のASPからMVCにアップグレードします。
  • no-sqlデータベースに切り替えます。
  • これらの長時間実行プロセスをワーカーキューに移動します。

「これらのオブジェクトが大きすぎる」場合、「新しい方法」を定義するのに苦労することになると思います

理想的には、古い方法へのanyコードの追加を停止し、すべての新しいコードを新しい方法で配置します。明らかに、これが常に可能であるとは限りません。いくつかのバグ修正を行うように誘惑/強制されます。

最終的にはすべてが「新しい方法」であり、それまでに「古い方法」の最後の部分を削除できますが、「新しい新しい方法」ができるかもしれません

1
Ewan