web-dev-qa-db-ja.com

使い慣れていない、構造的に健全でないコードに機能を追加する最も効果的な方法は何ですか?

これはおそらく、遅かれ早かれ開発中に誰もが直面しなければならないことです。

他の誰かが作成した既存のコードがあり、新しい要件で動作するようにコードを拡張する必要があります。

シンプルな場合もありますが、モジュールの結合度が中〜高、凝集度が中〜低であるため、何かに触れ始めると、すべてが壊れます。また、新しいシナリオと古いシナリオが再び機能するようになっても、正しく修正されているとは感じません。

1つのアプローチはテストを書くことですが、実際には、私が見たすべてのケースで、それはほとんど不可能でした(GUIへの依存、仕様の欠落、スレッド化、複雑な依存関係と階層、期限など)。

したがって、すべてのものが、古き良きカウボーイコーディングアプローチにフォールバックします。しかし、すべてを簡単にする他の体系的な方法はないと信じています。

誰かがより良いアプローチ、またはそのような場合に使用されるべき方法論の名前を知っていますか?

121
Coder

まず、このサイトの誰もが他の誰かが書いたものはすべてゴミだと思っているので、少し身に着けています。

コードを理解することは困難ですが、確かにいくつかの貧弱なプログラミング慣行はそれをより困難にしますが、使用される内部構造とイディオムを理解するのがかなり複雑なシステムの場合、困難になります適切に記述されたコードであっても

システムは20年以上日常的に稼働しています。プログラミングの方法論、ベストプラクティス、デザインの哲学、ファッションは2、3年ごとに変化し、プログラマーはさまざまな速度で改善されたスタイルを取り入れます。したがって、2007年に最先端のコードの優れた例と考えられていたものは、今日では古風で風変わりなものに見えます。演習として、3年前に作成したコードを掘り下げることをお勧めします。

したがって、最初に最初のWTF応答を抑制する必要があります。システムが問題を起こすのに十分な長さで機能しているので、何か良い点があるはずです。

元のコーダースタイルのコツ、使用されているイディオム、コードの奇妙なビットを調べて、それらがパターンに該当するかどうかを確認してください。

必要な変更が小さい場合は、元のコーディングスタイルに従ってください。そうすれば、1つのセットの特異性に慣れるだけで、誰かがコードを取得できます。

必要な変更が大きく、変更が少数の関数またはモジュールに集中している場合は、これらのモジュールをリファクタリングしてコードをクリーンアップする機会を得ます。

とりわけ、即時の変更要求とは何の関係もない作業コードをリファクタリングしないでください。時間がかかりすぎてバグが発生し、完璧になるまでに何年もかかったビジネスルールを誤って押してしまう可能性があります。上司は、小さな変更を提供するのが非常に遅いとあなたを嫌い、ユーザーは何年も問題なく稼働していたシステムをクラッシュさせることを嫌います。

139
James Anderson

基本的に、3つのアプローチがあります。

  1. ゼロから書き込みます。小規模なアプリケーションでは機能する可能性がありますが、大規模なコードベースでは使用できません。

    中小規模のコードベースの場合、すべてのコストで回避することが最悪のシナリオではないことに注意してください。

    ゼロから書き直さないのに十分な理由:古いコードベースは通常、テストされ、特定の状況などでコードを機能させるトリックを含めることが期待されています。すべてを書き直すときは、すでに解決されているバグの束を紹介します。

    これは無効な引数です。これは、コードの品質が非常に低いということです。テストされていない、仕様が欠落している、リファクタリングされていない、などです。ゼロから書き直さないでください。一方、ルールは、高品質でQAテスト済みのコードに限定する必要があります。

  2. カウボーイコーディングアプローチを使用:すべてを変更してからテストします。これは、主にコンテキストに応じて失敗または成功する可能性があります。

    • 例1:ビジネスクリティカルなアプリケーションを変更しています。あなたは既存のコードで何かを壊すと顧客が怒るだろうことを知っています、そしてこの顧客は実際の製品はうまく機能するだけでなく正しく書かれていると確信していますよく書かれたコード)。

      ここでは、新しいバグを導入してはならないため、カウボーイコーディングを使用できません。つまり、他の2つの方法のいずれかを使用する必要があります。

    • 例2:顧客は、以前の開発者との彼の経験は惨事だったと言っています。この製品は半分の時間で動作せず、そのままではまったく使用できません。顧客は、ソースコードを非常に不適切に記述して変更すると、さらに多くのバグが発生する可能性があることを理解しています。

      一方、ここでは、他の2つのソリューションと比較して、全体的なコストを削減できると確信している場合、カウボーイコーディングが良いソリューションになる可能性があります。

  3. 最初のコードをリファクタリングすることから始めます、単体テスト(および特定のケースで必要なその他のテスト)を追加し、それを文書化するなどして、変更を追加します。これは、コードがあまりにも安っぽくなく、ある程度の価値がある場合に適しています。たとえば、コードが時間のプレッシャーと低予算の制約の下でより熟練した同僚によって記述されたことがわかっている場合、私は確かにこのアプローチを使用します。

    • 巧妙に行われるコードの部分があります、

    • リファクタリングしたコードから多くのことを学びます。

    • 時間の制約と低予算の制約がある場合でも、より優れたスキルを持つ開発者によるコードは、特定のレベルのコードです。ある状況でアプリケーションを機能させるトリックがある場合、スキルの高い開発者は、次のコード行が追加された理由を説明するコメントを残します。

プロジェクトやタスクごとに3つすべてを比較し、それぞれのコストを評価し、状況に応じて最適なものを選択するという完璧な解決策はないと思います。

一般に、次の2つのルールに従います。

  • ルール1:コードを記述した開発者のスキルが高いほど、リファクタリングrewrite最初から使用する必要があります。

  • ルール2:プロジェクトが大きいほど、リファクタリングvs.rewriting最初から使用する必要があります。

あなたより熟練した誰かによって書かれた大きなデータベースをリファクタリングしてください。小さなコードや、熟練していないプログラマが作成したコードをゼロから書き直します。

46

それは基本的に私の毎日です。あなたが言及した理由のために、小さなテストを書くことがあまりにも非現実的である場合があります。ただし、常に大きなテストを行う必要がありますが、多くの場合、すべてのベースをカバーすることは不可能です。私が見つけた最良のアプローチは、外向きのスパイラルです。

最初から書き直す代わりに、要素をよく理解してください。長い関数がある場合は、それらを論理的なチャンクに分割してください。小さな要素、最も複雑でない非循環依存関係を分離/もつれさせます。よく理解している並列ライブラリを開始し、小さな関数を汎用ビットで移行し始めます。

これを行うと、システムをゆっくりと解明し、ますますモジュール化されるコードのますます大きなチャンクに向けて取り組みます。

それが薬物であり、あなたがそれにはまっているように文書化します。私はコードに小説を書くつもりはありませんが、コードがすべて文書化されていない可能性が高いので、.NETまたはDoxygenスタイルにしてください。使用または変更しているプロパティ)。

最後のトリック:デフォルトの動作を定義します。プログラム全体にオプションまたはパラメーターがある場合は、デフォルトを定義し、すべてのデフォルトでデプロイする簡単な方法を実装します(単純なINIファイルで実行しました。非常に便利です)その後、他のオプションや機能を、元のシステムに関連する新しいオプションとして追加できます。

40
MPelletier

Big Ball Of Mud を使用する場合、私に効果的なアプローチはテスターを取得して(より多く))徹底的なプロフェッショナル品質を手配することでしたプロジェクトの保証

  • 物事は、より良いコードベースで適用可能なすべてのアプローチはそれをカットしないだけです。
    優れたコードを使用して、ドキュメントを研究して原理を学び、コード(および特にユニットテスト)を読んで設計の詳細を図にし、自分が行った変更をカバーするために独自のユニットテストを記述します。コードが改善されても、ユニットテストはそのまま残りますまたはマイナーな変更を経て、ドキュメントは大幅なやり直しなどを必要としません。

    悪いコードでは、物事は正反対の方法で動作するようです。ドキュメンテーションから得られる情報は、ほとんどが曖昧で時代遅れの廃棄物です。コードを読むだけで、複雑な制御フローと以前のパッチに適用された直感に反するパッチがずっと前に作成された直感に反するパッチで頭を溶かして、設計エラーをすばやく回避できます。

    単体テストに関しては、これらは私が適切なコードでそれらを使用する方法とは逆のことを行います。たとえば、妥当な変更を壊したり、私が犯した実際の間違いを見つけられなかったりします。どちらが苦痛ですが、驚くべきことではありません-最初に設計が悪いunitsをテストすることから他に何を期待できますか?設計を改善するのではなく、ユニットテストは多くの場合、不良コードを保持するために機能します。たとえば、最近のメンテナンスプロジェクトでは、古いセンスのないユニットテストからのみ参照されるコードの大きなチャンクを定期的に削除していました。ところで、私は新しいコードを書くときにその知識を使用します。ユニットテストが複雑すぎたり壊れやすくなる傾向があることがわかった場合、これは自分の設計の問題を修正する必要があることを示しています。

今、私のために働いたものに戻ります。最初に、管理者にで騙されないようにしてください。あなたは優れた開発者です。あなたはそれを扱うことができますレトリック。自分自身で考え、批判的に考えてください。そうすれば、優れた開発者の煙幕にもかかわらず、-全体が大きく傾いています 品質保証 サイド

  • 自分自身に問いかけることから始めてください-なぜ彼らはこれ泥の大きなボールを捨てなかったのですか?
    なぜ彼らはそれを維持するためにあなたを雇うことに投資したのですか?通常、その理由は次のとおりです。このことはユーザーにとってはうまく機能し、ユーザーにとってもうまく機能し続けることが期待されます。彼らはそれを ブラックボックス と認識し、(くだらない)コードを調べません。

    これで、プロのQAを使用したことがあれば、テスターがソフトウェアを扱う傾向にあることがすぐにわかるでしょう。ユーザーの観点から見たブラックボックス、機能、品質-これらはすべて品質保証のトピックとテーマです。

さて、あなたがこれまで読んだことがあるなら、それが私にとってどれほど正確に機能するのか疑問に思うかもしれません。本当に簡単です。

まず最初に-バグ修正の機能を終了するたびに、 issue tracker の各項目をQA担当者に再割り当てして変更を確認します。このように、面倒な機能テストに時間を費やす必要はありません。候補コードがQAの準備ができていることを簡単に確認してください。

私が本当に運がよければ(頻繁に起こることはありません)、もう心配する必要はありません。最悪のシナリオについても、それほど悪くはありません。私が何か間違ったことをした場合、テスターは1日か2日後に私に戻ってきます。正確に何がうまくいかなかったか、バグを再現する方法の明確な説明、および回帰テストスイートこれらのバグが再び発生した場合に簡単に検出できるように拡張されました。悪くないですよね?

QAに付属する次の優れた点は、正規回帰テストサイクルです。

  • 一部の(不完全な)マネージャーは、これを行うことができる自動的にとあなたに納得させようとするかもしれません-信頼してはいけません。
    良いコードでうまく機能するものは、Big Ball Of Mudでトリックを失敗するだけです。

    テストの実行は面倒であり、テスト結果の分析は労力を消費し、問題と回帰データベースの維持には多くの焦点が必要です(開発にも焦点を当てる場合は、余裕がないよりも多くの焦点が必要です)。すべての取引のジャックになるために時間を浪費する代わりに、プロのテスターがあなたのためにそれをするようにし、設計とコーディングのためにあなたの脳と時間を解放します。古き良き 分業 を活用するだけです。

定期的なテストサイクル(imNShoweeklyが最適ですが、毎月も私にはうまくいきました)で不可能を実現できます効果的なリファクタリング

effectiveつまり、コーディングに1週間、リグレッションバグの修正に1週間が必要な場合、計画どおり2週間を費やすだけです。 -テスターがあなたに代わってカバーするので、脳にダメージを与えるテストの実行と複雑な失敗の分析に費やされる余分な月に沈むことなく。


上記の主な利点に加えて、他にも、小さいながらも楽しいボーナスがあります。

  1. テスターを使用すると、誰かとデザインについて話し合うことができます。私が書いたように、Big Ball of Mudを扱うとき、ドキュメントとコードはここでは役に立ちません-一人で作業する場合、一種の真空を作ります。コードを実行し、それをさまざまな視点から見るテスターは、アイデアをバウンスし、起こり得る変更を調査するための優れたパートナーになります。

  2. テスターを使用すると、設計の問題についての理解をバックアップしてもらうことができます。 code qualityについて不平を言う開発者しかいない場合、これはしばしば 閉じられたドアの後ろからの主観的なWTF のように聞こえます。

    しかし、これがQAの担当者によってcomponent Aのように言ってエコーされた場合、10の回帰バグがあったcomponent Bとは対照的に、10の新機能に対して100の回帰バグがありました。 20の新機能ごとに、通信が突然別のゲームに変わります。

  3. 最後になりましたが、専門的なQAは、設計の改善に投資する価値のある作業量の理解を促進するのに役立ちます。すでに述べたように、管理はコード品質WTFをうまく処理しません。

    しかし、プロのQAがあり、通常はすべてのデータが収集されている場合、マネージャーの頭脳から魔法のスタンプでその秘密のセルに急上昇する傾向があるものを思い付く可能性があります私は承認します =

    昨年、私たちは製品の回帰バグを修正するためだけに約6人月を無駄にしました。では、この無駄を半分にするために何かできることがあるかどうかを分析するために、開発チームに1〜2週間を与えるのはどうでしょう。

14
gnat

1つのアプローチは、テストを作成することです。

あなたの半分はあなた自身の質問に答えたと思います。これを見る方法はいくつかありますが、経験上、標準的なアジャイルプラクティスのいくつかを適用することに常に引き付けられ、アプローチするのと同じ方法で体系的にすべてを実行するように思われます。技術的な問題を解決するのが難しい。

これは私がこの種の状況に個人的に取り組む方法です:

  • 勉強の期間から始めて、メモを取り、図を描き、自分(または近くの「地元の専門家」)にたくさんの質問をします。コードを読んで、問題の全体像を手に入れる必要があります。
  • いくつかの明らかな問題領域を特定し、それらをいじるリスクを評価します。いくつかの変更をスパイクして、障害がどのように発生するかを確認し、スパイクがたどっていたパスをたどる、または他のことを完全に実行するために必要な追加の作業についてのアイデアを得ます。
  • 問題を選び、テストを書いてください。既存のテストが利用可能な場合は使用し、そうでない場合は、ビジネスロジックをできる限り保護する新しいテストを記述します。
    • 失敗するようにテストを記述してから、テストが成功するように修正してから、ダブルチェックとして失敗するようにコードを変更してから、再び成功するようにコードを復元します。最後の「コードエラー」ステップをコードをステップ実行する機会として使用し、他の方法では見逃していた可能性のある明らかなものを特定します。
  • 問題を修正するためにコードを記述する必要があると思われる方法で変更を設計および計画します。ここで、要件と仕様に戻って(ある場合)、ユーザーストーリーや機能、または通常使用する方法と同じようにテストを作成する必要があります。
  • 新しいストーリーに一致する新しいテストを記述します(別名、改訂された仕様)
  • 小さなステップでコードをリファクタリングします。最初にメソッドを抽出し、次にクラスを抽出し、次にメソッドとクラスを移動してから、クラスを結合/削除します。後ですべての難しいリファクタリングが容易になるため、常にすべての簡単なリファクタリングを最初に実行します。あなたの努力を導くための少しの助けやインスピレーションを探しているなら、 RefactoringRefactoring To Patterns の両方を読むことをお勧めします。
    • リファクタリングするときは、元のテストに合格することを目指しており、リファクタリングが完了したら新しいテストに合格することを目指しています。これを行うと、元のテストの一部が破棄されるか、古いテストと新しいテストの両方を通過できるほど幸運になる場合があります。

これは私がうまく機能しているプロセスです。これは時間がかかり、面倒なこともありますが、機能し、常に良い結果をもたらします。時間とリソースを考慮して改善できなかったコードを見つけたことはありません。つまり、最初の調査とスパイクの際に、非常に困難な問題を特定したり、時間と労力に基づいて問題を特定したりする場合があります。このようなことを推定することは非常に困難です。確実に。結局のところ、最初からやり直す方が良い場合もあれば、そのコードが勤務先の会社にとって大きな商業的価値がある場合もあります。作業を完了するための最善の見積もりを与え、問題のあるドメインを完全に理解している場合は、最初からやり直すための最善の見積もりを与えてください。上司にいくつかのケースとシナリオを提示し、管理者にどこにお金を使うかを決定させます。これで熱が取り除かれ、しっかりと元に戻ります。

すでに述べたように、このアプローチは私にとってはうまくいきますが、あなたにとってはまったく同じように動くとは限りません。プロセスを微調整したり、別の方法で問題の解決を検討したりすることができます。ポイントは、これらの困難な問題を解決するには、コードの混乱を単に失われた原因と見なさず、横から考えることによってのみ解決できるということです。代わりに、それを難し​​い問題と挑戦と考えてください。どんな問題があっても、それを解決するための体系的な方法は常にあります。自分に合ったアプローチを見つける必要があるだけです。個人的には、このような挑戦に立ち向かい、数回の勝利を収めると、デザインが不十分なアプリケーションを美しく細工された製品に変えることに大きな誇りを持っています。

8
S.Robins

私は1年以上にわたって複雑なコードベースに取り組んできました。私の洞察があなたを助けることができるかどうか見てください:

あなたの洞察は正しく、コードの別の部分に到達するまでに、前の部分を忘れています。それは決して終わることのないサイクルになることができます。ここで取り上げるべき重要な教訓は、すべての部品が適切に機能しないと製品は機能しないということです。ひとつの部品が故障しても、製品は動作しません。別の角度から見てください。ある部分を劇的に改善しても、製品の機能が向上しない可能性があります。それが、ここでの主な目標です。

最初は、開発者にならないでください。テスターに​​なってください。

部分的に理解しようとしないでください。すべてのパーツが一緒になっているときの製品全体とその動作を理解します。実稼働環境(つまり、非開発環境-デバッグポイントなし)から、製品をテストします。次に、すべてのテスターと同じように、直面する問題をバグトラッカーに記録します。重大度と優先度を割り当てます。このソフトウェアはかなり前から存在していたので、すでにバグトラッカーが作成されているかどうかを確認します。すでにある場合は、ラッキーです。それらに追加して、時間をかけて既存の各ファイルを確認してください。このサイクルの最後に、ユーザーの視点(間違いなく見逃してはなりません)とQAの視点から製品を理解します。当然のことながら、コード行によってバグが修正されることにも気づくかもしれません。それをコード化した人たちは、当時は本当に必要がなかったので、修正しませんでした。

2番目のステップ:デザイナーケープを着用

製品をいくつかの部分に分割します(文字通り、またはユーザーの便宜に従ってではなく、それらがどのように連携するかに従って)。今までの仕事かもしれませんし、既存の知識が役立つかもしれません。次に、それらが互いに、および10個の依存ライブラリとどのように連携するかを理解してください。次に、追跡されたバグごとに、コードのエンティティを識別するメモを書きます(例:この変更には、クラスX、Y、Zなどの変更が含まれます)。おそらく、このステップの終わりまでに、現在のアーキテクチャの問題点と改善できる点についてのFEWヒントが得られます。

次に、現在のアーキテクチャ/デザインで十分かどうかを判断し、ソフトウェアの改善を続けることができますOR製品がより優れたデザインまたは既存のデザインの変更を必要とする場合。

カードの家

また、複雑な製品には多くのコードが含まれているため、いくつかのことを取り上げて調整または改善する立場にない場合があります。これは、システム全体が絡み合ってクラスの1つに変更を加えることは、カードの家の1枚のカードの位置を変更することと同じであり、どちらの端が壊れるかわからないためです。私の経験では、これは本当です。私は一部を選択し、コードを改善しましたが、コードの他の部分との契約を認識していませんでしたが、コードを放棄して間違いを犯しました。だから、部分を理解しようとするのではなく、それが全体であると理解してみてください。

懸念を優先する

改善しようとしていることを覚えておく必要があります。

製品をより高速にしたいですか?

もちろんそうです。しかし、それは懸念の主なものでしょうか?遅いですか? 「はい」の場合、パフォーマンス基準を作成し、ボトルネックを特定して、それらの部分を改善します。もう一度テストしてください。

使いやすさを向上させたいですか?

それから、それはほとんどAPI/UI側です。

セキュリティを強化しますか?

そして、それはあなたが探究すべき境界です。

私は3つの例しか提供していませんが、他にも探すべきことがたくさんあります。

最新かつ最良のドキュメント

私は、最新かつ最良のドキュメントがコード自体であるという投稿の1つでここを読みました。今日かなりの量のドキュメントを作成しても、それはしばらくしてからの歴史です。したがって、コードは最新のドキュメントです。そのため、コードを閲覧するときはいつでも、コメントに理解を書き込んでください。コードベースを渡すときは、コメントだけに依存しないように注意してください!

2
Sundeep

現在のコードを説明するために必要なだけドキュメントを作成してください。ロジックを理解したら、変更する準備が整います。追加するものはすべて文書化し、すべてのケースをカバーするようにしてください。

回帰テストを作成します。手動でテストするか、テストを自動的に実行するかは関係ありません。元の機能を保持する必要があり、回帰テストはそれを支援します。

仕様が十分でない場合は、人々に詳細を尋ねて書き留めてください。

コードを変更するための計画を作成し、さまざまなフェーズに分けます。

0
Rudolf Olah