「ソフトウェア設計の哲学」(17)[既存のコードを修正する]

第16章既存のコードの変更

第16章既存のコードの変更

第1章では、ソフトウェア開発が反復的で段階的に行われる方法について説明しました。大規模なソフトウェアシステムは、一連の進化的な段階を経て開発され、各段階で新しい機能が追加され、既存のモジュールが変更されます。つまり、システムの設計は常に進化しています。最初からシステムの適切な設計を考えることはできません。成熟したシステムの設計は、初期の概念よりも、システムの進化中に行われた変更によって決定されます。前の章では、初期の設計と実装中に複雑さを排除する方法について説明しました。この章では、システムの進化に伴う複雑さの侵入を防ぐ方法について説明します。

第1章では、ソフトウェア開発が反復的で段階的に行われる方法について説明します。大規模なソフトウェアシステムは、一連の進化的な段階を通じて開発され、それぞれが新しい機能を追加し、既存のモジュールを変更します。つまり、システムの設計は常に進化しています。システムの正しい設計を最初から行うことは不可能です。成熟したシステムの設計は、初期の概念ではなく、システムの進化中に行われた変更に依存します。前の章では、初期の設計と実装時に複雑さを軽減する方法について説明しました。この章では、システムの進化に伴う複雑さの増大を防ぐ方法について説明します。

16.1戦略的であり続ける

第3章では、戦術プログラミングと戦略プログラミングの違いを紹介しました。戦術プログラミングの主な目的は、複雑さを増すことになっても、何かをすばやく動作させることです。戦略的プログラミングで最も重要な目標は、優れたシステム設計を生み出すことです。戦術的なアプローチは非常に迅速にシステムの設計を混乱させます。保守と拡張が容易なシステムが必要な場合、「機能」は十分に高い標準ではありません。デザインを優先し、戦略的に考える必要があります。このアイデアは、既存のコードを変更する場合にも適用されます。

第3章では、戦術プログラミングと戦略プログラミングの違いを紹介します。戦術プログラミングの主な目的は、特定の処理を迅速に実行することです。これにより、複雑さが増しても、戦略プログラミングでは、最も重要な目標は、優れたシステム設計。戦術的なアプローチはすぐにシステム設計の混乱につながりました。保守と拡張が容易なシステムが必要な場合、「作業」は十分に高くありません。設計を優先し、戦略的に考える必要があります。このアイデアは、既存のコードを変更するときにも適用されます。

残念ながら、開発者が既存のコードを使用してバグ修正や新機能などの変更を行う場合、通常は戦略的に考えません。典型的な考え方は、「必要なことを行うために、私が実行できる最小限の変更は何ですか?」です。開発者は、コードの変更に慣れていないため、これを正当化することがあります。彼らは、大きな変更が新しいバグを導入する大きなリスクを伴うことを心配しています。ただし、これにより戦術的なプログラミングが行われます。これらの最小限の変更はそれぞれ、いくつかの特殊なケース、依存関係、またはその他の複雑な形態をもたらします。その結果、システム設計は少し悪化し、システムの進化の各ステップで問題が蓄積します。

残念ながら、開発者が既存のコードを入力して変更(バグ修正や新機能など)を行う場合、通常、戦略的に考えていません。典型的な考え方は「私が行う必要のある最小の変更は何か」です。開発者は変更されたコードに満足していないため、これを正当化することがあります。彼らは、大きな変更が新しいエラーを導入する大きなリスクをもたらすことを心配しています。しかし、これは戦術的なプログラミングにつながりました。これらの最小限の変更により、特別な状況、依存関係、またはその他の複雑な形態が生じます。その結果、システム設計が悪化し、システム開発の各段階で問題が蓄積します。

システムのクリーンな設計を維持したい場合は、既存のコードを変更するときに戦略的なアプローチを取る必要があります。理想的には、各変更を終えると、変更を念頭に置いて最初から設計した場合と同じ構造がシステムにあることになります。この目標を達成するには、迅速な解決策を講じるという誘惑に抵抗する必要があります。代わりに、望ましい変更を考慮して、現在のシステム設計が依然として最良のものであるかどうかを検討してください。そうでない場合は、システムをリファクタリングして、最良の設計になるようにします。このアプローチにより、システム設計は変更を加えるたびに改善されます。

システムのシンプルな設計を維持したい場合は、既存のコードを変更するときに戦略的なアプローチを取る必要があります。理想的には、各変更を完了するときに、最初から変更を念頭に置いてシステムを設計すると、システムは本来あるべき構造を持つことになります。この目標を達成するには、問題をすばやく解決するという誘惑に抵抗する必要があります。代わりに、必要な変更に基づいて現在のシステム設計が依然として最適であるかどうかを検討してください。そうでない場合は、システムをリファクタリングして、最終的に最良の設計を取得してください。このように、すべての変更はシステム設計を改善します。

これは、15ページで紹介した投資マインドセットの例でもあります。システム設計をリファクタリングして改善するために少し余分な時間を費やした場合、システムはよりクリーンになります。これにより開発がスピードアップし、リファクタリングに投資した労力を取り戻すことができます。特定の変更がリファクタリングを必要としない場合でも、コードにいる間に修正できる設計の欠陥がないかどうか注意深く見守る必要があります。コードを変更するときは常に、プロセスの少なくとも少しだけシステム設計を改善する方法を見つけてください。デザインを改善していなければ、おそらく悪化しています。

これは、15ページで説明した投資の考え方の例でもあります。システム設計のリファクタリングと改善に余分な時間を費やすと、システムがよりクリーンになります。これにより、開発がスピードアップし、リファクタリングに費やした労力を取り戻すことができます。特定の変更でリファクタリングが必要ない場合でも、コードで修正できる設計上の欠陥に注意する必要があります。コードを変更する場合は常に、プロセスの少なくとも1つの方法を見つけて、システム設計を改善するようにしてください。デザインを改善しなければ、悪化する可能性があります。

第3章で説明したように、投資の考え方は、商用ソフトウェア開発の現実と衝突することがあります。システムを「適切な方法」でリファクタリングするのに3か月かかるが、迅速でダーティな修正に2時間しかかからない場合、特に厳しい期限に取り組んでいる場合は、迅速でダーティなアプローチを取る必要があるかもしれません。または、システムのリファクタリングが他の多くの人々やチームに影響を与える非互換性を生み出す場合、リファクタリングは実用的ではない可能性があります。

第3章で述べたように、投資の考え方は、商用ソフトウェア開発の現実と対立する場合があります。システムを再構築する「正しい方法」に3か月かかり、迅速で汚れた修理に2時間しかかからない場合、特に厳しい期限内で作業している場合、すばやく汚れたアプローチを取る必要がある場合があります。あるいは、システムのリファクタリングが他の多くの人々やチームに影響を与える非互換性を引き起こす場合、リファクタリングは非現実的かもしれません。

それにもかかわらず、あなたはこれらの妥協にできるだけ抵抗するべきです。「現在の制約を考えると、これがクリーンなシステム設計を作成するために私ができる最善の方法ですか?」と自問してください。おそらく、3か月のリファクタリングとほぼ同じくらいきれいですが、数日で実行できる別のアプローチがありますか?または、大規模なリファクタリングを行う余裕がない場合は、上司に、現在の締め切り後に戻ってくる時間を割り当てるように依頼してください。すべての開発組織は、全体的な作業のごく一部をクリーンアップとリファクタリングに費やすことを計画する必要があります。この作業は長期的には採算が取れます。

ただし、これらの妥協点にできるだけ抵抗する必要があります。「自分の現在の制限を考慮して、これがクリーンなシステム設計を作成するために私が実行できる最善の作業ですか?」3か月程度のリファクタリングと同じくらいクリーンな代替アプローチがあるかもしれませんが、数日で完了しますか?または、今すぐ大きなリファクタリングを行う余裕がない場合は、上司に、現在の締め切り後に元のレベルに戻すための時間を割り当てるよう依頼してください。すべての開発組織は、クリーンアップとリファクタリングのために全体の作業のごく一部を費やすことを計画する必要があります;長期的には、この作業はそれ自体で採算が取れます。

16.2コメントの維持:コードの近くにコメントを置く

既存のコードを変更すると、変更によって既存のコメントの一部が無効になる可能性が高くなります。コードを変更するときにコメントを更新するのを忘れがちで、コメントが正確でなくなります。不正確なコメントは読者にイライラさせられ、それらが非常に多い場合、読者はすべてのコメントを信用しなくなります。幸い、少しの規律といくつかの指針となるルールがあれば、多大な労力をかけずにコメントを最新の状態に保つことができます。このセクションと次のセクションでは、いくつかの特定のテクニックを説明します。

既存のコードを変更すると、既存のコメントの一部が無効になる可能性があります。コードを変更すると、コメントの更新を忘れがちになり、コメントが正確でなくなります。不正確なコメントは読者を苛立たせます。コメントが多すぎると、読者はすべてのコメントを信用しなくなります。幸い、少しの規律といくつかのガイドラインがあれば、手間をかけずにノートを最新の状態に保つことができます。このセクションと後続のセクションでは、いくつかの特定の手法を提案します。

コメントが確実に更新されるようにする最善の方法は、コメントを説明するコードの近くに配置して、開発者がコードを変更したときにコメントが表示されるようにすることです。コメントが関連するコードから遠いほど、適切に更新される可能性は低くなります。たとえば、メソッドのインターフェイスコメントの最適な場所は、メソッドの本体のすぐ隣にあるコードファイルです。メソッドへの変更にはこのコードが含まれるため、開発者はインターフェイスのコメントを確認し、必要に応じて更新する可能性があります。

コメントが確実に更新されるようにするための最良の方法は、コメントをコメントの近くに配置して、開発者がコードを変更するときにコメントを確認できるようにすることです。コメントが関連するコードから遠いほど、コメントが正しく更新される可能性は低くなります。たとえば、メソッドインターフェイスコメントの最適な場所は、メソッド本体の横のコードファイルです。メソッドへの変更にはこのコードが含まれるため、開発者はインターフェイスのコメントを確認し、必要に応じて更新する可能性があります。

CやC ++のように、コードとヘッダーファイルが別々になっている言語の代替案は、.hファイル内のメソッドの宣言の横にインターフェイスコメントを配置することです。ただし、これはコードから遠く離れています。開発者はメソッドの本体を変更するときにこれらのコメントを表示せず、別のファイルを開いてインターフェースコメントを見つけて更新するために追加の作業が必要です。インターフェースのコメントはヘッダーファイルに入れて、ユーザーがコードファイルを見なくても抽象化の使い方を学べると主張する人もいます。ただし、コードやヘッダーファイルを読み取る必要はありません。DoxygenやJavadocなどのツールでコンパイルされたドキュメントから情報を取得する必要があります。さらに、多くのIDEはドキュメントを抽出してユーザーに提示します。メソッド名が入力されたときにメソッドのドキュメントを表示するなど。このようなツールがある場合、ドキュメントは、コードを扱う開発者にとって最も便利な場所に配置する必要があります。

CやC ++など、個別のコードとヘッダーファイルを使用する言語の場合、代替方法は、.hファイルのメソッド宣言の横にインターフェイスコメントを配置することです。ただし、これはコードからまだ長い道のりです。開発者はメソッドの本文を変更するときにこれらのコメントを表示しないため、他のファイルを開いてインターフェースのコメントを探して更新する必要があるため、追加の作業が必要です。誰かがインターフェイスファイルのコメントをヘッダーファイルに入れて、ユーザーがコードファイルを見なくても抽象化の使い方を学べると主張する人もいます。ただし、ユーザーはコードやヘッダーファイルを読む必要はなく、DoxygenやJavadocなどのツールでコンパイルされたドキュメントから情報を取得する必要があります。さらに、メソッド名を入力するときにメソッドのドキュメントを表示するなど、多くのIDEがドキュメントを抽出してユーザーに提示します。このようなツールを考えると、ドキュメントは、開発者がコードを開発するのに最も便利な場所にあるはずです。

実装コメントを書くとき、メソッド全体のすべてのコメントをメソッドの先頭に置かないでください。それらを広げて、各コメントを、そのコメントが参照するすべてのコードを含む最も狭い範囲に押し下げます。たとえば、メソッドに3つの主要なフェーズがある場合、メソッドの上部に、すべてのフェーズを詳細に説明するコメントを1つ記述しないでください。代わりに、フェーズごとに個別のコメントを記述し、そのコメントをそのフェーズのコードの最初の行のすぐ上に配置します。一方、メソッドの実装の上部に、次のように全体的な戦略を説明するコメントを付けることも役立ちます。

実装コメントを書くときは、メソッド全体のすべてのコメントをメソッドの先頭に置かないでください。それらを展開して、各コメントを最も狭い範囲にプッシュします。これには、そのコメントによって参照されるすべてのコードが含まれます。たとえば、メソッドに3つの主要なステージがある場合、メソッドの上部に、すべてのステージを詳細に説明するメモを記述しないでください。代わりに、ステージごとに個別のコメントを記述し、そのステージのコードの最初の行の真上にコメントを配置します。一方、メソッドの実装の上部に全体的な戦略を説明するコメントを追加すると役立つ場合もあります。次に例を示します。

//  We proceed in three phases:
//  Phase 1: Find feasible candidates
//  Phase 2: Assign each candidate a score
//  Phase 3: Choose the best, and remove it

追加の詳細は、各フェーズのコードのすぐ上に文書化できます。

その他の詳細情報は、各ステージのコードの上に記録できます。

一般に、コメントがそれが記述するコードから遠いほど、それはより抽象的である必要があります(これにより、コメントがコードの変更によって無効になる可能性が低くなります)。

一般的に、記述されたコードからコメントが離れているほど、コメントはより抽象的になります(これにより、コードの変更によりコメントが無効になる可能性が低くなります)。

16.3コメントはコミットログではなくコードに属しているコメントはコミットログではなくコードに属している

コードを変更する際のよくある間違いは、変更に関する詳細情報をソースコードリポジトリのコミットメッセージに入れることですが、それをコードに文書化しないことです。将来、リポジトリのログをスキャンすることでコミットメッセージを参照できますが、情報を必要とする開発者は、リポジトリのログをスキャンすることを考えないでしょう。ログをスキャンしても、正しいログメッセージを見つけるのは面倒です。

コードを変更するときのよくある間違いは、変更に関する詳細情報をコードに記録するのではなく、ソースコードリポジトリのコミットメッセージに含めることです。将来、リポジトリのログをスキャンしてコミットメッセージを参照することは可能ですが、この情報を必要とする開発者は、リポジトリのログをスキャンすることを検討することはほとんどありません。ログをスキャンしても、正しいログメッセージを見つけるのは困難です。

コミットメッセージを書くときは、開発者が将来その情報を使用する必要があるかどうか自問してください。その場合は、この情報をコードに記載してください。例は、コード変更の動機となった微妙な問題を説明するコミットメッセージです。これがコードに記載されていない場合、開発者は後でバグを再現したことに気付かずに後で変更を取り消す可能性があります。この情報のコピーをコミットメッセージにも含めたい場合は問題ありませんが、最も重要なことは、コードに含めることです。これは、開発者がドキュメントを見る可能性が最も高い場所にドキュメントを配置するという原則を示しています。コミットログはめったにその場所にはありません。

コミットメッセージを書くとき、開発者が将来その情報を使用する必要があるかどうか自問してください。その場合は、この情報をコードに記録します。例は、コードの変更を引き起こした微妙な問題を説明するコミットメッセージです。これがコードに記載されていない場合、開発者はエラーを再現したことに気付かずに、後で変更を提案して元に戻すことができます。コミットメッセージにこの情報のコピーも含めたい場合、それはすばらしいことですが、最も重要なことは、コードでそれを取得することです。これは、開発者がドキュメントを見る可能性が最も高い場所にドキュメントを配置するという原則を示しています。コミットログがその場所に配置されることはほとんどありません。

16.4コメントの維持:重複を避ける

コメントを最新に保つための2番目の手法は、重複を避けることです。ドキュメンテーションが重複している場合、開発者がすべての関連コピーを見つけて更新することはより困難になります。代わりに、各設計の決定を1度だけ文書化してください。特定の決定の影響を受けるコードの複数の場所がある場合、これらの各ポイントでドキュメントを繰り返さないでください。代わりに、ドキュメントを配置する最も明確な単一の場所を見つけます。たとえば、変数に関連するトリッキーな動作があり、その変数が使用されるいくつかの異なる場所に影響を与えるとします。変数の宣言の横のコメントにその動作を文書化できます。これは、開発者が変数を使用するコードを理解するのに問題があるかどうかをチェックするのにふさわしい場所です。

コメントを最新に保つための2番目の手法は、重複を避けることです。ドキュメントが複製されている場合、開発者が関連するすべてのコピーを見つけて更新することは困難です。代わりに、各設計決定を1回だけ記録するようにしてください。特定の決定の影響を受けるコードの複数の場所がある場合、これらのすべての場所でドキュメントを繰り返さないでください。代わりに、ドキュメントを配置する最もわかりやすい場所を見つけます。たとえば、変数に関連するトリッキーな動作があり、変数が使用されるいくつかの異なる場所に影響を与えるとします。この動作は、変数宣言の横のコメントに記録できます。これは当たり前のことであり、開発者は変数を使用するコードを理解するのに問題があるかどうかを確認できます。

開発者が見つけられる特定のドキュメントを置く「明白な」単一の場所がない場合は、セクション13.7で説明されているように、designNotesファイルを作成します。または、利用可能な最適な場所を選び、そこにドキュメントを配置します。さらに、中央の場所を参照する他の場所に短いコメントを追加します。「以下のコードの説明については、xyzのコメントを参照してください。」マスターコメントが移動または削除されたために参照が古くなった場合、開発者が指定された場所でコメントを見つけられないため、この矛盾は自明です。リビジョン管理履歴を使用して、コメントに何が起こったかを調べ、参照を更新できます。対照的に、ドキュメントが複製され、一部のコピーが更新されない場合、

開発者が見つけられるように特定のドキュメントを置く「明白な」場所がない場合は、セクション13.7で説明されているように、designNotesファイルを作成します。または、最適な場所を選択し、そこにドキュメントを配置します。さらに、参照の中央の別の場所に短いコメントを追加します。「以下のコードの説明を理解するには、xyzのコメントを確認してください。」メインコメントが移動または削除されたために参照が古くなった場合、この矛盾は自明です。比喩的に言えば、開発者は指定された場所でコメントを見つけることができないため、リビジョン管理履歴を使用してコメントに何が起こったかを見つけ、参照を更新できます。逆に、ドキュメントが複製されていて、一部のコピーが更新されていない場合、開発者は古い情報を使用していることを知りません。

あるモジュールの設計上の決定を別のモジュールで再文書化しないでください。たとえば、呼び出されたメソッドで何が起こるかを説明するコメントをメソッド呼び出しの前に置かないでください。読者が知りたい場合は、メソッドのインターフェースのコメントを確認する必要があります。優れた開発ツールは通常、たとえば、メソッドの名前を選択するか、その上にマウスを置くと、メソッドのインターフェイスコメントを表示することにより、この情報を自動的に提供します。開発者が適切なドキュメントを簡単に見つけられるようにしてください。ただし、ドキュメントを繰り返すことは避けてください。

あるモジュールの設計決定を別のモジュールに記録しないでください。たとえば、呼び出されたメソッドで何が起こるかを説明するために、メソッド呼び出しの前にコメントを追加しないでください。読者が知りたい場合は、メソッドのインターフェースノートを確認する必要があります。優れた開発ツールは通常、この情報を自動的に提供します。たとえば、メソッドの名前を選択するか、メソッドの名前の上にマウスを置くと、メソッドのインターフェースコメントが表示されます。開発者が適切なドキュメントを簡単に見つけられるようにしてください。ただし、ドキュメントを繰り返さないでください。

情報がすでにプログラムの外部のどこかに文書化されている場合は、プログラム内の文書化を繰り返さないでください。外部ドキュメントを参照してください。たとえば、HTTPプロトコルを実装するクラスを作成する場合、コード内でHTTPプロトコルを記述する必要はありません。このドキュメントのソースは、すでにWeb上に多数あります。これらのソースの1つのURLを含む短いコメントをコードに追加するだけです。別の例は、ユーザーマニュアルに既に記載されている機能です。コマンドのコレクションを実装するプログラムを作成していて、各コマンドの実装を担当するメソッドが1つあるとします。これらのコマンドを説明するユーザーマニュアルがあれば、コード内でこの情報を複製する必要はありません。代わりに、

情報がすでにプログラムの外のどこかに記録されている場合は、プログラム内で繰り返し記録せず、外部ドキュメントを参照してください。たとえば、HTTPプロトコルを実装するクラスを作成する場合、HTTPプロトコルをコードで記述する必要はありません。このドキュメントのソースは既にインターネット上に多数あります。コードに短いコメントを追加し、ソースの1つにURLを追加してください。別の例は、ユーザーマニュアルに記載されている特性です。一連のコマンドを実装するプログラムを作成していて、各コマンドを実装するメソッドがあるとします。これらのコマンドを説明するユーザーマニュアルがある場合は、コードでこの情報を繰り返す必要はありません。代わりに、各コマンドメソッドのインターフェイスコメントに次の簡単な説明を含めます。

// Implements the Foo command; see the user manual for details.

読者がコードを理解するために必要なすべてのドキュメントを簡単に見つけられることが重要ですが、それはすべてのドキュメントを書かなければならないという意味ではありません。

読者はコードを理解するために必要なすべてのドキュメントを簡単に見つけることができますが、これは重要ですが、これらのドキュメントをすべて記述する必要があるという意味ではありません。

16.5コメントの維持:差分を確認する

ドキュメントを最新の状態に保つための良い方法の1つは、リビジョン管理システムに変更をコミットする前に、そのコミットのすべての変更をスキャンするまでに数分かかることです。各変更がドキュメントに適切に反映されていることを確認してください。これらのコミット前のスキャンでは、誤ってデバッグコードをシステムに残したり、TODOアイテムの修正に失敗したりするなど、他のいくつかの問題も検出されます。

ドキュメントを最新の状態に保つための良い方法は、変更管理システムに変更を送信する前に、その送信に対するすべての変更をスキャンするのに数分かかることです。各変更がドキュメントに正しく反映されていることを確認します。これらの事前コミットされたスキャンは、システムに誤ってデバッグコードを残したり、TODOアイテムの修復に失敗したりするなど、他の問題も検出します。

16.6高レベルのコメントは維持が簡単です

ドキュメントの保守に関する最後の1つの考え:コードよりも高レベルで抽象的なコメントは、保守が簡単です。これらのコメントはコードの詳細を反映していないため、コードの小さな変更による影響はありません。全体的な動作の変更のみがこれらのコメントに影響します。もちろん、第13章で説明したように、一部のコメントは詳細かつ正確である必要があります。ただし、一般的に、最も有用なコメント(コードを繰り返すだけではない)も維持が最も簡単です。

ドキュメントの保守に関する最後の1つの考え:コメントがコードよりも高度で抽象的なものである場合、コメントの保守は簡単です。これらのコメントはコードの詳細を反映していないため、コードの変更による影響はなく、全体的な動作の変更のみがこれらのコメントに影響します。もちろん、第13章で説明したように、特定の注記は詳細かつ正確である必要があります。ただし、一般的に、最も有用なコメント(繰り返しの多いコードだけではない)も保守が最も簡単です。

おすすめ

転載: blog.csdn.net/WuLex/article/details/108617956