レガシーコードを改善する方法

これは、すべてのプログラマー、プロジェクトマネージャー、またはチームリーダーの人生で少なくとも1回発生します。 たくさんの新鮮な肥料が手に入ります。 運が良ければ、ほんの数百万行です。 原作者はずっと前に温暖な国に飛んでいたので、もしあればドキュメントは絶望的に古くなっています。



あなたの仕事:この混乱から抜け出すこと。



最初の本能的な反応(逃げる)を手放した後、プロジェクトの作業を開始し、会社のリーダーがあなたの進捗を監視していることを十分に理解します。 失敗は選択肢ではありません。 しかし、現時点では、シナリオから判断すると、失敗の可能性が最も高いと思われます。 それではどうしますか?



このような状況に何度かあったことは幸運でした。 少人数の友人グループと私は、適切なスキルがあれば、このような大量の蒸し立てのスクーラーに取り組み、それらを健全な支援プロジェクトに変えることが非常に有益なビジネスであることがわかりました。 使用するいくつかのトリックを次に示します。



バックアップ



アクションを開始する前に、プロジェクトに関連する可能性のあるすべてのバックアップコピーを作成します。 これは、将来便利になる情報が失われないようにするためです。 変更が行われてから1、2日後には答えられない、ある種の愚かな質問が常にあるかもしれません。 これらの種類の問題は、特に構成データで特に頻繁に発生します。それらは通常、バージョン管理スキームには含まれていないため、少なくとも定期的なバックアップから何かを復元することは幸運です。 ですから、後で後悔するよりも保存する方が良いです。 すべてを最も安全な場所にコピーし、読み取り専用モードでない場合は、人生で決して触れないでください。



重要な前提条件:実稼働で機能するものを実際に生成するビルドプロセスがあることを確認する



ほとんどすべての人にとって、その自明性とアセンブリプロセスの存在を前提として、この手順を完全に逃しましたが、HNの多くのコメンテーターがそれを指摘し、完全に正しいです。 最初のステップは、現在実稼働環境で作業していることを確認することです。 つまり、プラットフォームがこのように機能する場合、実稼働環境での現在のビルドとバイトツーバイトが一致するバージョンのソフトウェアをビルドできる必要があります。 これを達成できない場合は、何らかのコミットを本番に送信しようとするときに不快な驚きに備えてください。 テストに全力を尽くし、必要なものがすべて揃っていて、作業能力に自信がある場合は、本番環境にロールアウトします。 すぐに以前のバージョンに戻る準備を整え、必然的に-デブリーフィング中に役立つすべてのものが完全にログに記録されるようにしてください。



データベースに触れないでください



可能であれば、改善の第1段階が完了するまでデータベーススキーマをフリーズします。 コードベースの完全な理解が現れ、レガシーコードが完全に残されたら、変更する準備が整います。 この時点より前にスキームを変更してください-すべてが構築されているデータベースからの信頼できる基盤と古いコードベースと新しいコードベースを並べて実行する機能を失うため、実際の問題が発生する可能性があります。 データベースをそのまま保持した後、新しいビジネスロジックコードの効果を古いものと比較できます。 すべてが説明どおりに機能する場合、違いはないはずです。



テストを書く



変更する前に、できるだけ多くのエンドツーエンドおよび統合テストを作成します。 これらのテストが正しく実行されていることを確認し古いコードがどのように機能するかについて考えられる限り多くのスクリプトをテストします(ここで驚きに備えてください)。 これらのテストには2つの重要な機能があります。初期段階で誤解を排除するのに役立ち、古いコードの代わりに新しいコードを書き始める場合の保護フェンスとして機能します。



すでにCIの経験がある場合は、すべてのテストを自動化し、それを使用して、各コミット後に完全なテストセットを実行するのに十分な速度でテストが動作することを確認します。



メトリックとログの収集



古いプラットフォームがまだ開発および機器に利用できる場合。 これを完全に新しいデータベーステーブルで行い、想像できる各イベントに単純なカウンターを追加し、イベントの名前に基づいてこれらのカウンターをインクリメントする単純な関数を追加します。 このようにして、コードを数行追加するだけでタイムスタンプ付きのイベントログを実装し、あるタイプのイベントが別のタイプのイベントにつながる数を把握できます。 一例:ユーザーがアプリケーションを開き、ユーザーがアプリケーションを閉じます。 2つのイベントが何らかのバックエンドの課題につながる場合、長期的にはこれら2つのカウンター間に一定の差があるはずです。この差は、現在開いているアプリケーションの数を表します。 アプリケーションのクロージャーよりも多くのオープニングが表示される場合は、アプリケーションが終了する方法(少なくともクラッシュ)が存在する必要があることを知っています。 見つかったイベントごとに、他のイベントとのつながりがあります。 通常は、システム内のどこかで明らかな間違いを除いて、永続的な関係に向かって作業します。 エラーに対応するカウンターを減らし、チェーンに沿ってカウンターを最初に指定した数字まで最大化するよう努めます。 (たとえば、購入代金を支払おうとするユーザーは、最終的に支払いを受け取る必要があります)。



この非常に単純なトリックは、バックエンドアプリケーションをアカウンティングシステムに変換し、実際のアカウンティングシステムと同様に、番号が一致する必要があるため、どこでも問題が発生しません。



時間が経つにつれて、このアプローチはプログラムの健全性を向上させる上で非常に貴重になり、ソースコード管理システムの変更ログによって完全に補完されます。このログでは、バグが発生した時点とさまざまなカウンターにどのような影響があったかを判断できます。



通常、これらのカウンターを5分間の解像度でインストールします(したがって、1時間あたり12バケットになります)が、アプリケーションが生成するイベントの数が少なくなる場合は、バケットが作成される間隔を変更できます。 すべてのカウンタは1つのデータベーステーブルを共有するため、各カウンタはこのテーブルの列にすぎません。



一度に1つだけ変更する



コードまたはそれが動作するプラットフォームの動作の信頼性を向上させると同時に、新しい機能を追加したりバグを修正したりするというtrapに陥らないでください。 これは大きな頭痛の種になります。これは、各ステップでアクションの望ましい結果を尋ねる必要があるためです。これにより、以前に行われたテストの一部が無効になります。



プラットフォームの変更



アプリケーションを別のプラットフォームに転送することを決定した場合、それを実行しますが、 他のすべてを変更しないままにします。 必要に応じて、ドキュメントまたはテストを追加できますが、ビジネスロジックと相互依存関係はすべて同じままにする必要があります。



アーキテクチャの変更



次の段階は、アプリケーションアーキテクチャの変更です(必要な場合)。 この時点で、コードの高レベルの構造を自由に変更できます。通常、モジュール間の水平接続の数を減らし、エンドユーザーとの各対話中にアクティブになるコードの量を減らします。 古いコードが本質的にモノリシックであった場合、今度はよりモジュール化して、大きな関数を小さな関数に分割しますが、変数とデータ構造の名前はそのままにします。



HN mannykannotのユーザーは、これが常に可能であるとは限らないことに気づきました。特に不運な場合は、アーキテクチャを変更するためにさらに深く掘り下げる必要があります。 私はこれに同意し、言及するべきだったので、ここで少し追加します。 また、高レベルと低レベルの両方の変更を行う場合は、少なくとも変更を1つのファイルに制限するか、最悪の場合は1つのサブシステムに制限して、変更の量を可能な限り制限するようにしてください。 そうしないと、行った変更をデバッグする必要があるときに非常に困難になります。



低レベルのリファクタリング



この時点で、各モジュールの機能を十分に理解する必要があります。実際の仕事、つまり、コードをリファクタリングして操作の信頼性を向上させ、新しい機能のためにコードを準備する準備ができています。 これはたまたま最も時間のかかる作業の一部であり、外出先でドキュメントを書き、完全にドキュメント化し、その機能を完全に理解するまでモジュールを変更しないでください。 変数や関数、データ構造の名前を自由に変更して、明確さと一貫性を高め、テストを追加します(状況によって必要な場合は単体テストも)。



バグ修正



これで、実際のエンドユーザーに表示される変更を加える準備ができました。 戦闘の最初の順序は、チケットキューに長年にわたって蓄積されたバグの長いリストです。 いつものように、最初に問題がまだ存在することを確認し、この目的のためにテストを書いてから、バグを修正します。CIと書かれたエンドツーエンドのテストは、理解不足やいくつかの無関係な問題。



データベース更新



必要なすべてが完了し、しっかりしたサポートされたコードベースが再びある場合、データベーススキーマを変更するオプションが表示されます。これを行う予定がある場合は、データベースを別のモデルに置き換えることもできます。 この時点で行ったことはすべて、驚くことなく責任ある方法でこの変更を行うのに役立ちます。新しいコードで新しいデータベースを徹底的にテストすることができ、すべてのテストが利用可能であり、移行が滞りなく成功することを保証します。



ロードマップの実装



おめでとうございます、あなたは危険から脱し、新しい機能を導入する準備ができています。



大規模な手直しについても考えない



大規模な手直しは、ほぼ間違いなく失敗するタイプのプロジェクトです。 まず、完全に未知の領域で開始するため、どこから始めればよいのかがわかりません。次に、最後の日、つまり新しいシステムをリリースする直前にすべての問題を先送りします。 そして、ひどく失敗します。 ビジネスロジックの仮定は間違っていることが判明し、突然、古いシステムが特定のことを行った理由を突然理解し、一般に、古いシステムを組み立てた人は結局そのようなばかではないという結論に達します。 あらゆる意味で会社(およびあなた自身の評判)を本当に台無しにしたい場合は、大規模なやり直しを開始しますが、賢い場合は、これも考慮されません。



そのため、代替として、ステップバイステップで作業します



これらのもつれの1つを最も迅速な方法で解くには、理解しているコードから任意の要素を取り出して(コードの小さな部分である場合もありますが、重要なモジュールである場合もあります)、古いコンテキストのままで段階的に改善してください。 古いビルドツールが使用できなくなった場合は、いくつかのトリックを使用する必要があります(以下を参照)が、少なくとも変更を開始するときに機能することが証明されているコードを可能な限り維持するようにしてください。 したがって、コードベースが改善されると、それが実際に何をするかについての理解も深まります。 典型的なコミットは、2、3行で構成する必要があります。



リリース!



行われた各変更は本番環境にリリースされますが、変更がエンドユーザーに表示されない場合でも、システムの理解が不足しているため、可能な限り最小限の手順を踏むことが重要です。作業環境でのみ問題を認識している可能性が高くなります。 小さな変更の直後にこの問題が発生した場合、いくつかの利点が得られます。





プロキシを有利に使用する



Web開発者の場合は、神を称賛し、エンドユーザーと古いシステムの間にプロキシを配置します。 これで、古いシステムに送信する要求と新しいシステムにリダイレクトする要求を行ごとに制御できるようになりました。これにより、起動する対象と表示する対象をより簡単かつ正確に制御できます。 十分に賢いプロキシを使用している場合、すべてが期待どおりに機能することに満足するまで、個々のURLの新しいシステムにトラフィックを送信するためにおそらくそれを使用できます。 統合テストがこのインターフェイスに接続する場合は、さらに優れています。



はい、しかしこれには時間がかかりすぎます!



さて、どのように見えるか。 実際、これらの手順を実行する場合は、再作業が必要です。 しかし、それは実際に機能し、このプロセスの最適化は、おそらく実際にあなたが知っているよりも多くのことをシステムについて知っていることを前提としています。 私は評判があり、このように働いている間、否定的な驚きを本当に望んでいません。 運が悪い場合、これは会社を失敗に導くか、顧客の仕事を台無しにする本当の脅威があるかもしれません。 このような状況では、数日または数週間を節約しようとするよりも、完全な制御と鉄筋コンクリートプロセスを好むため、成功する結果が危険にさらされます。 あなたがよりカウボーイであり、そしてあなたの上司が同意するなら、より多くのリスクを取ることは容認できるかもしれませんが、ほとんどの企業はわずかにより遅いが、勝利へのずっと真の道を選びます。



All Articles