web-dev-qa-db-ja.com

「変更しない」とマークされているコードをリファクタリングする必要がありますか?

私はかなり大きなコードベースを扱っており、既存のコードをリファクタリングするために数か月が与えられました。すぐに製品に多くの新機能を追加する必要があり、現在のところ、他のものを壊さずに機能を追加することができないため、リファクタリングプロセスが必要です。要するに、私たちの多くが彼らのキャリアで見た、乱雑で巨大なバグのあるコード。

リファクタリング中、時々、次のようなコメントを持つクラス、メソッド、またはコード行に遭遇します

モジュールAに何かする時間を与えるためのタイムアウトセット。このようにタイミングがとれていないと壊れます。

または

これを変更しないでください。私を信じて、あなたは物事を壊すでしょう。

または

SetTimeoutを使用することはお勧めしませんが、この場合は使用する必要がありました。

私の質問は、著者からこのような警告が出されたときにコードをリファクタリングする必要がありますか(いいえ、著者と連絡が取れません)?

150
kukis

新機能の開発が行われると、コードベースの一部が詳細に変更されるwhichを正確に知らなくても、「念のため」リファクタリングしているようです。そうでなければ、もろいモジュールをリファクタリングする必要が本当にあるのか、それともそのままにしておけばよいのかがわかるでしょう。

これを正直に言うと、これは運命のリファクタリング戦略だと思います。あなたは会社の時間とお金を投資して、それが本当に利益をもたらすかどうか誰も分からないようなものに費やしており、あなたは作業コードにバグを導入することによって事態を悪化させることの最中です。

これはより良い戦略です:あなたの時間を使って

  • リスクのあるモジュールに自動テスト(おそらく単体テストではなく統合テスト)を追加します。特に、あなたが言及したこれらのもろいモジュールは、そこで何かを変更する前に完全なテストスイートを必要とします。

  • テストを実行するために必要なビットのみをリファクタリングします。必要な変更を最小限に抑えるようにしてください。唯一の例外は、テストでバグが明らかになった場合です。すぐにバグを修正します(安全に行う必要がある程度にリファクタリングします)。

  • 同僚に「ボーイスカウトの原則」(AKA "opportunistic refactoring" )を教えるため、チームが新しい機能の追加(またはバグの修正)を開始したとき、彼らは彼らが変更する必要があるコードベース。

  • チームのためにFeatherの本「レガシーコードを効果的に使用する」のコピーを入手してください。

つまり、知っているときが来たら確かに脆弱なモジュールを変更してリファクタリングする必要があります(新機能の開発のため、またはテストのため)ステップ1で追加したいくつかのバグを明らかにします)、それであなたとあなたのチームはモジュールをリファクタリングする準備が整い、多かれ少なかれ安全にそれらの警告コメントを無視します。

コメントへの返信として:特に、既存の製品のモジュールが定期的に問題の原因であると疑われる場合、特に「触らないで」とマークされているモジュール、私はあなた全員に同意します。それはレビューされ、デバッグされ、そのプロセスでおそらくリファクタリングされる必要があります(私が言及したテストをサポートしており、必ずしもこの順序ではありません)。バグは変更の正当な理由であり、多くの場合、新機能よりも強力です。ただし、これはケースバイケースの決定です。 「手を触れない」とマークされたモジュールで何かを変更するのが面倒なことに本当に価値があるかどうか、非常に注意深くチェックする必要があります。

224
Doc Brown

はい、他の機能を追加する前にコードをリファクタリングする必要があります。

このようなコメントの問題は、コードベースが実行されている環境の特定の状況に依存することです。特定の時点でプログラムされたタイムアウトは、プログラムされたときに本当に必要だったかもしれません。

ただし、この方程式を変える可能性のあるものはいくつもあります。ハードウェアの変更、OSの変更、別のシステムコンポーネントの無関係な変更、一般的なデータボリュームの変更などです。現在でもそれが必要である、またはそれで十分であるという保証はありません(保護することになっていたものの整合性が長期間壊れていた可能性があります-適切な回帰テストがなければ、気付かない可能性があります)。これが、別のコンポーネントの終了を可能にするための固定遅延のプログラミングがほとんど常に正しくなく、偶発的にしか機能しない理由です。

あなたの場合、あなたは元の状況を知りませんし、元の作者に尋ねることもできません。 (おそらく、適切な回帰/統合テストも行われていないか、単純に先に進んで、何かを壊したかどうかをテストに通知させることができます。)

これは、注意して何も変更しないという議論のように見えるかもしれません。とにかく、大きな変更が必要になると言うので、このスポットで達成されていた微妙なバランスを混乱させる危険はすでにそこにあります。 Apple cartnow)を混乱させることは、あなたがしている唯一のことがリファクタリングであるとき、はるかに良いです、物事が壊れた場合、それが原因となったのはリファクタリングであったことを確認してください。同時に追加の変更が行われるまで待たず、確信が持てません。

140
Kilian Foth

私の質問は、著者からのそのような警告が発生したときにコードをリファクタリングする必要がありますか

いいえ、または少なくともまだです。自動テストのレベルが非常に低いことを意味します。自信を持ってリファクタリングするには、テストが必要です。

今のところ、何かを壊さずに機能を追加することはできません

現時点では、リファクタリングではなく、安定性の向上に集中する必要があります。安定性の向上の一環としてリファクタリングを実行する場合がありますが、これは実際の目標、つまり安定したコードベースを達成するためのツールです。

これはレガシーコードベースになっているようですので、少し違う扱いをする必要があります。

まず、特性テストを追加します。仕様について心配する必要はありません。現在の動作をアサートするテストを追加するだけです。これは、新しい作業が既存の機能を破壊するのを防ぐのに役立ちます。

バグを修正するたびに、バグが修正されたことを証明するテストケースを追加します。これは直接の回帰を防ぎます。

新しい機能を追加するときは、少なくともいくつかの基本的なテストを追加して、新しい機能が意図したとおりに機能することを確認します。

おそらく、「レガシーコードを効果的に使用する」のコピーを入手できますか?

既存のコードをリファクタリングするために数か月が与えられました。

まず、テストカバレッジを取得します。最も壊れやすい領域から始めます。最も変化する領域から始めます。次に、不良デザインを特定したら、それらを1つずつ交換します。

大きなリファクタリングを1回行うことはほとんどありませんが、代わりに毎週小さなリファクタリングが絶え間なく流れています。 1つの大きなリファクタリングには、物事を壊す大きなリスクがあり、うまくテストするのは困難です。

60
Daenyth

覚えてください G。K.チェスタートンのフェンス :道路が建設された理由を理解するまで、道路を遮っているフェンスを降ろさないでください。

コードの作成者と問題のコメントを見つけ、それらを理解して理解することができます。コミットメッセージ、メールスレッド、ドキュメントが存在する場合は、それらを確認できます。次に、マークされたフラグメントをリファクタリングするか、コメントに知識を書き留めて、このコードを保守する次の人がより情報に基づいた決定を行えるようにします。

あなたの目的は、コードが何をするのか、なぜそれがなぜその時警告でマークされたのか、そして警告が無視された場合どうなるのかを理解することです。

その前に、「触れない」とマークされているコードには触れません。

23
9000

あなたが示したようなコメント付きのコードは、リファクタリングするものの私のリストの一番上にありますもし、私がそうする理由がある場合のみ。重要なのは、コードがひどく悪臭を放つため、コメントでさえ臭いがするということです。このコードに新しい機能を追加するのは無意味です。このようなコードは、変更が必要になるとすぐに終了する必要があります。

また、コメントは少なくとも役に立たないことに注意してください。コメントは警告のみで理由はありません。はい、それらはサメのタンクの周りの警告サインです。しかし、近くで何かをしたいのなら、サメと一緒に泳ぐことを試みるポイントはほとんどありません。イムホ、まずサメを取り除くべきだ。

そうは言っても、あえてそのようなコードに取り組む前に、良いテストケースが絶対に必要です。これらのテストケースを取得したら、変更するごく小さなすべてを理解し、実際に動作を変更していないことを確認してください。影響がないことを証明できるまで、コードのすべての動作特性を保持することを最優先事項とします。あなたはサメを扱っていることを忘れないでください-あなたは非常に注意しなければなりません。

したがって、タイムアウトが発生した場合は、コードが待機している内容を正確に理解するまでそのままにし、その後、タイムアウトが導入された理由を修正してから、削除を続行してください。

また、上司が着手している取り組みとは何か、なぜそれが必要なのかを上司に理解してもらうようにしてください。そして、彼らがノーと言ったら、それをしないでください。これには絶対にサポートが必要です。

私は先に進んでそれを変更すると言います。信じられないかもしれませんが、すべてのコーダーが天才ではなく、このような警告は、改善の余地があることを意味します。著者が正しいとわかった場合は、警告の理由を文書化または説明することができます。

5
JES

現状のままで問題なく動作する場合は、このような警告が表示されたコードをリファクタリングすることはおそらくお勧めできません。

しかし、あなたがmustリファクタリングなら...

最初に、いくつかの単体テストおよび統合テストを記述して、警告が警告している条件をテストします。本番のような条件を真似てみてください可能な限り。つまり、本番データベースをテストサーバーにミラーリングしたり、マシン上で同じ他のサービスを実行したりするなど、できることは何でもできます。次に、リファクタリングを試みます(もちろん、分離されたブランチで)。次に、新しいコードでテストを実行して、元のコードと同じ結果が得られるかどうかを確認します。可能であれば、おそらくリファクタリングを本番環境に実装しても問題ありません。ただし、問題が発生した場合は、変更をロールバックする準備をしてください。

特定の問題のいくつかの側面が見落とされているか、誤った結論を導き出すために使用されているため、コメントを回答に拡大しています。

この時点で、リファクタリングするかどうかの問題は時期尚早です(おそらく「はい」の特定の形式で答えられるでしょうが)。

ここでの中心的な問題は、(一部の回答に記載されているように)引用するコメントが、コードに競合状態またはその他の同時実行性/同期の問題があることを強く示していることです ここで説明しています 。これらは、いくつかの理由で、特に難しい問題です。最初に、発見したように、一見無関係な変更が問題を引き起こす可能性があります(他のバグもこの影響をもたらす可能性がありますが、同時実行エラーはほとんど常に発生します)。次に、診断が非常に困難です。原因から時間またはコードが離れている場合、それを診断するために行うことはそれがなくなる可能性があります( Heisenbugs )。第3に、同時実行バグはテストで見つけるのが非常に困難です。部分的には、これは組み合わせの爆発によるものです。シーケンシャルコードには十分悪いですが、同時実行の可能なインターリーブを追加すると、シーケンシャルの問題が比較で重要でなくなるところまで爆破します。さらに、優れたテストケースでも問題が発生することがあるのはまれです-ナンシーレブソンは、 Therac 25 の致命的なバグの1つが、約350回実行されますが、バグが何であるかわからない場合、またはバグがある場合でも、効果的なテストを行うために何回繰り返すかはわかりません。さらに、このスケールでは自動テストのみが可能であり、テストドライバーが微妙なタイミング制約を課して、実際にはバグを引き起こさないようにすることができます(ハイゼンバグ)。

POSIX pthreadを使用するコードの Helgrind など、一部の環境での同時実行性テスト用のツールはいくつかありますが、ここでは詳細を知りません。ご使用の環境に適したツールがある場合、テストは静的分析で補足する必要があります(またはその逆ですか?)。

難しさを増すために、コンパイラー(および実行時のプロセッサーでさえ)は、多くの場合、スレッドの安全性を非常に直感的に理解できない方法でコードを再編成できます(おそらく、最もよく知られているケースは double-checked lock イディオム、ただし一部の環境(Java、C++ ...)はそれを改善するように変更されています。)

このコードには、すべての症状を引き起こす1つの単純な問題がある可能性がありますが、新しい機能を追加する計画が停止する可能性のあるシステム上の問題がある可能性が高くなります。私はあなたがあなたの手に深刻な問題を抱えているかもしれないと確信していると思います、おそらくあなたの製品に対する実存的な脅威さえあります、そして最初にすべきことは何が起こっているかを見つけることです。これにより同時実行性の問題が明らかになった場合は、より一般的なリファクタリングを実行する必要があるかどうかを尋ねる前に、さらに機能を追加する前に、まずそれらを修正することを強くお勧めします。

4
sdenham

私はかなり大きなコードベースを扱っており、既存のコードをリファクタリングするために数か月が与えられました。製品に多くの新機能を追加する必要があるため、リファクタリングプロセスが必要です[...]

リファクタリング中、時々、「これに触れないでください!」のようなコメントを持つクラス、メソッド、またはコード行に遭遇します。

はい、それらの部分をリファクタリングする必要があります特に。これらの警告は、以前の作者が「このものを無差別に改ざんしないでください。非常に複雑で、予期しない相互作用がたくさんあります」という意味でそこに出されました。あなたの会社がソフトウェアをさらに開発し、多くの機能を追加することを計画しているので、彼らは特にこのようなものをクリーンアップするようにあなたに命じました。つまり、あなたはidlyを改ざんしているのではなく、故意にそれをクリーンアップするタスクを担当しています。

それらのトリッキーなモジュールが何をしているかを調べて、それを小さな問題(元の作者がすべきこと)に分解します。保守可能なコードを混乱させないようにするには、良い部分をリファクタリングし、悪い部分をrewriteにする必要があります。

3
DepressedDaniel

この質問は、「コードを継承する方法」のダッシュを含む、いつ/どのようにコードをリファクタリングおよび/またはクリーンアップするかの議論の別のバリエーションです。私たちにはそれぞれ異なる経験があり、チームや文化が異なるさまざまな組織で働いているため、「必要なことを実行し、解雇されない方法で実行する」以外に正解や誤解はありません。 。

サポートするアプリケーションでコードのクリーンアップまたはリファクタリングが必要だったため、ビジネスプロセスを脇に倒すことを喜んで受け入れる組織は多くないと思います。

この特定のケースでは、コードのコメントにより、コードのこれらのセクションを変更してはならないというフラグが立てられています。したがって、先に進み、ビジネスがその側に落ちた場合、アクションをサポートするために示すものがないだけでなく、実際には決定に反する成果物があります。

そのため、いつものように、変更しようとしていることのすべての側面を理解してから、慎重に作業を進めて変更を加え、容量とパフォーマンス、およびタイミングに細心の注意を払い、全体をテストする方法を見つける必要があります。コード内のコメント。

しかし、それでも、あなたの経営陣はあなたがしていることに固有のリスクを理解し、あなたがしていることがリスクを上回るビジネス価値を持ち、そのリスクを軽減するためにできることを実行したことに同意する必要があります。

さて、皆さんが私たち自身のTODOに戻りましょう。私たちが知っていることは、時間があれば、私たち自身のコード作成で改善することができます。

2
Thomas Carlisle

そのとおり。これらは、このコードを書いた人がコードに不満を持っていて、たまたま機能するようになるまでそれを突いた可能性が高いことを示しています。彼らが本当の問題が何であるかを理解していなかった、またはさらに悪いことに、それらを理解していて、それらを修正するのが面倒だった可能性があります。

ただし、それらを修正するには多大な労力が必要であり、そのような修正にはリスクが伴うことを警告しています。

理想的には、問題が何であるかを理解し、適切に修正できるようになります。例えば:

モジュールAに何かする時間を与えるためのタイムアウトセット。このようにタイミングがとれていないと壊れます。

これは、モジュールAがいつ使用できる状態にあるか、いつ処理が終了したかを適切に示していないことを強く示唆しています。おそらく、これを書いた人はモジュールAの修正を気にしたくなかったか、何らかの理由でできなかったでしょう。これは、適切な順序付けではなく運によってタイミングの依存関係が処理されていることを示唆しているため、発生を待っている災害のように見えます。これを見たら是非修正したいです。

これを変更しないでください。私を信じて、あなたは物事を壊すでしょう。

これではあまりわかりません。それはコードが何をしていたかに依存します。それは、何らかの理由で実際にコードを破壊する明らかな最適化のように見えるものがあることを意味する可能性があります。たとえば、ループが発生して、別のコードが依存する特定の値に変数が残る場合があります。または、変数が別のスレッドでテストされ、変数の更新の順序を変更すると、他のコードが壊れる可能性があります。

SetTimeoutを使用することはお勧めしませんが、この場合は使用する必要がありました。

これは簡単なように見えます。あなたはsetTimeoutが何をしているかを見ることができ、おそらくそれを行うためのより良い方法を見つけることができるはずです。

とはいえ、これらの種類の修正がリファクタリングの範囲外である場合、これらはこのコード内でリファクタリングしようとすると、作業範囲が大幅に増える可能性があることを示しています。

少なくとも、影響を受けるコードを注意深く調べ、問題が何であるかをより明確に説明できる程度までコメントを改善できるかどうかを確認してください。それはあなたが直面しているのと同じ謎に直面することから次の人を救うかもしれません。

1
David Schwartz

コメントの作成者である可能性が最も高いコード自体を完全に理解していなかった。彼らが実際に何をしているのかを知っていれば、彼らは実際に役立つコメントを書いていただろう(あるいはそもそもレースの状況を紹介していなかった)。 「信頼してください、あなたは物事を壊します。」のようなコメントは、著者が何かを変更しようとしたために、彼らが完全には理解していない予期しないエラーを引き起こしたことを示しています。

コードはおそらく、実際に何が起こっているのかを完全に理解せずに、推測と試行錯誤によって開発されています。

これの意味は:

  1. コードを変更することは危険です。明らかに理解するには時間がかかります。また、おそらく良い設計原則に従っていないため、効果と依存関係が不明瞭になる可能性があります。テストが不十分である可能性が高く、コードの機能を完全に理解していない場合、エラーや動作の変更が発生しないことを確認するテストを作成することは困難です。レーシングコンディション(ほのめかしたコメントのような)は特に厄介です-これはユニットテストがあなたを救わない1つの場所です。

  2. コードを変更するのは危険notです。コードには、不明瞭なバグやレース条件が含まれている可能性が高いです。コードで重大なバグが発見された場合、または優先度の高いビジネス要件の変更によりこのコードをすぐに変更しなければならない場合は、deepになります。トラブル。これで、1で説明したすべての問題が発生しましたが、時間のプレッシャーがかかっています!さらに、コード内のこのような「暗い領域」は、コードが接触するコードの他の部分に広がり感染する傾向があります。

さらに面倒なこと:単体テストでは保存されません。通常、このようなレガシーコードを修正するための推奨アプローチは、最初に単体テストまたは統合テストを追加してから、分離してリファクタリングすることです。ただし、レースの状況は自動テストでは取得できません。唯一の解決策は、座ってコードを理解するまで熟考し、その後、書き直して競合状態を回避することです。

これは、タスクが通常のリファクタリングよりもはるかに厳しいことを意味します。実際の開発タスクとしてスケジュールする必要があります。

影響を受けるコードを定期的なリファクタリングの一部としてカプセル化できる可能性があるため、少なくとも危険なコードは隔離されます。

1
JacquesB