web-dev-qa-db-ja.com

11000行のC ++ソースファイルについてはどうすればよいですか?

私たちのプロジェクトにはこの巨大な(11000行は巨大ですか?)mainmodule.cppソースファイルがあり、それに触れるたびに、私はうんざりしています。

このファイルは非常に中心的で大きいため、ますます多くのコードを蓄積し続け、実際に圧縮を開始する良い方法は考えられません。

このファイルは、製品のいくつかの(> 10)メンテナンスバージョンで使用され、積極的に変更されているため、リファクタリングするのは非常に困難です。たとえば、単純に3つのファイルに分割した場合、メンテナンスバージョンからの変更をマージバックするのは悪夢になります。また、このような長くて豊かな履歴を持つファイルを分割すると、SCC履歴の古い変更の追跡と確認が突然難しくなります。

このファイルには基本的にプログラムの「メインクラス」(メインの内部作業ディスパッチと調整)が含まれているため、機能が追加されるたびに、このファイルに影響し、拡張するたびに影響します。 :-(

この状況で何をしますか? SCCワークフローを台無しにすることなく、新しい機能を別のソースファイルに移動する方法に関するアイデアはありますか?

(ツールに関する注意:C++とVisual Studio; AccuRevとしてSCCを使用しますが、SCCのタイプはここでは重要ではないと思います。を使用しております Araxis Mergeファイルの実際の比較とマージを行うため)

228
Martin Ba
  1. ファイル内で比較的安定しており(高速に変化せず、ブランチ間であまり変化しない)、独立したユニットとして立つことができるコードを見つけます。これを独自のファイルに移動し、さらにすべてのブランチで独自のクラスに移動します。安定しているため、ブランチ間で変更をマージするときに、元のファイルとは異なるファイルに適用する必要のある(厄介な)マージが(多く)発生することはありません。繰り返す。

  2. 基本的に少数のブランチにのみ適用され、スタンドアロンである可能性のあるコードをファイルで見つけます。ブランチの数が少ないため、変化が速いかどうかは関係ありません。これを独自のクラスとファイルに移動します。繰り返す。

そのため、どこでも同じコードと、特定のブランチに固有のコードを削除しました。

これにより、不適切に管理されたコードの中核が残ります-それはどこでも必要ですが、すべてのブランチで異なります(および/またはいくつかのブランチが他のブランチの後ろで実行されるように絶えず変化します)ブランチ間のマージに失敗しました。あんな事はしないで。ファイルを分岐します永続的に。おそらく各分岐でファイル名を変更します。もう「メイン」ではなく、「構成Xのメイン」です。わかりました。したがって、マージによって同じ変更を複数のブランチに適用する機能は失われますが、これはいずれにしても、マージがうまく機能しないコードのコアです。とにかく競合を処理するためにマージを手動で管理する必要がある場合は、各ブランチに個別にマージを手動で適用しても損失はありません。

SCCの種類は重要ではありません。なぜなら、たとえばgitのマージ機能は、おそらく使用しているマージツールよりも優れているからです。 SCCごとに異なるタイミングで「マージが困難」になりますが、SCCを変更できる可能性は低いため、この問題はおそらく無関係です。

85
Steve Jessop

マージは、将来30000 LOCファイルを取得するときほど大きな悪夢ではありません。そう:

  1. そのファイルへのコードの追加を停止します。
  2. それを分割します。

リファクタリングプロセス中にコーディングを停止することができない場合は、少なくとも大きなコードを追加せずに、この大きなファイルを現状のままのままにしておくことができます。それから、いくつかの新しい小さくて適切に設計されたファイルにオーバーロードされた関数を持つ継承されたクラスを保持します。

129

ここで多くのコードの匂いに直面しているように思えます。まず第一に、メインクラスは、 オープン/クローズド原則 に違反しているように見えます。 責任が多すぎる を処理しているようにも聞こえます。このため、コードは必要以上に脆弱であると思います。

リファクタリング後のトレーサビリティに関する懸念を理解することはできますが、このクラスを維持および強化するのはかなり難しく、加えた変更は副作用を引き起こす可能性が高いと思われます。これらのコストがクラスのリファクタリングのコストを上回ると思います。

いずれにしても、コードの匂いは時間とともに悪化するだけなので、少なくともある時点で、これらのコストはリファクタリングのコストを上回ります。あなたの説明から、私はあなたが転換点を過ぎていると思います。

これをリファクタリングするには、小さな手順で行う必要があります。可能であれば、自動テストを追加して現在の動作を確認しますbeforeリファクタリング。次に、分離された機能の小さな領域を選択し、これらをタイプとして抽出して、責任を委任します。

いずれにせよ、それは大きなプロジェクトのように聞こえるので、幸運を祈ります:)

67
Brian Rasmussen

このような問題に対して私が想像した唯一の解決策は次のとおりです。説明した方法による実際のゲインは、進化の進行性です。ここでは革命はありません。さもないと、非常に速くトラブルに陥ります。

元のメインクラスの上に新しいcppクラスを挿入します。今のところ、基本的にはすべての呼び出しを現在のメインクラスにリダイレクトしますが、この新しいクラスのAPIをできるだけ明確で簡潔にすることを目指しています。

これが完了すると、新しいクラスに新しい機能を追加できるようになります。

既存の機能については、十分に安定するため、新しいクラスに徐々に移動する必要があります。 SCCこのコードのヘルプは失われますが、それについてできることはあまりありません。正しいタイミングを選んでください。

私はこれが完璧ではないことを知っていますが、それが助けになることを望みます、そしてプロセスはあなたのニーズに適応しなければなりません!

追加情報

GitはSCCであり、あるファイルから別のファイルにコードの断片をたどることができます。それについて良いことを聞いたことがあります。

Gitは、正しく理解すればコードファイルの断片を表すBLOBの概念を中心に構築されます。これらのピースを別のファイルに移動すると、Gitはそれらを変更しても、それらを見つけます。 Linus Torvaldsのビデオ とは別に、以下のコメントで言及されていますが、これについて明確な何かを見つけることができませんでした。

49
Benoît

孔子は「穴から抜け出すための最初のステップは、穴を掘るのをやめることだ」と言います。

30
fdasfasdfdas

推測してみましょう:多様な機能セットと「カスタマイズ」を促進するセールスマネージャーを持つ10人のクライアントですか?私は以前にそのような製品に取り組んできました。本質的に同じ問題がありました。

あなたは巨大なファイルを持つことはトラブルであると認識していますが、さらにトラブルになるのは「最新」を維持しなければならない10のバージョンです。それは複数のメンテナンスです。 SCCはそれを簡単にすることができますが、正しくすることはできません。

ファイルを部分に分割する前に、すべてのコードを一度に表示および整形できるように、10個のブランチを相互に同期させる必要があります。同じメインコードファイルに対して両方のブランチをテストして、一度に1つのブランチを実行できます。カスタム動作を強制するには、#ifdefとフレンドを使用できますが、定義済みの定数に対して通常のif/elseを使用するのが可能な限り良いです。このようにして、コンパイラはすべての型を検証し、おそらく「デッド」オブジェクトコードを排除します。 (ただし、デッドコードに関する警告をオフにすることもできます。)

すべてのブランチで暗黙的に共有されるファイルのバージョンが1つだけになったら、従来のリファクタリング方法を開始する方が簡単です。

#ifdefsは主に、影響を受けるコードが他のブランチごとのカスタマイズのコンテキストでのみ意味があるセクションに適しています。これらは同じブランチマージスキームの機会を提供すると主張するかもしれませんが、大げさではありません。一度に一つの巨大なプロジェクトをお願いします。

短期的には、ファイルは大きくなるように見えます。これで結構です。あなたがしているのは、一緒にいる必要があるものを一緒にすることです。その後、バージョンに関係なく明らかに同じ領域が表示され始めます。これらはそのままにしておくことも、自由にリファクタリングすることもできます。その他の領域は、バージョンによって明らかに異なります。この場合、いくつかのオプションがあります。 1つの方法は、違いをバージョンごとの戦略オブジェクトに委任することです。別の方法は、共通の抽象クラスからクライアントバージョンを派生させることです。ただし、異なるブランチで開発の「ヒント」を10個持っている限り、これらの変換は不可能です。

25
Ian

これがあなたの問題を解決するかどうかはわかりませんが、あなたがしたいことは、ファイルの内容を互いに独立した小さなファイルに移行することです(要約)。また、約10種類の異なるバージョンのソフトウェアがあり、それらをすべて混乱させることなくサポートする必要があるということです。

まず第一に、これはnoであり、これは簡単で、ブレインストーミングの数分で解決します。ファイルにリンクされている関数はすべてアプリケーションにとって重要であり、単にそれらを切り取って他のファイルに移行しても、問題は解決しません。

次のオプションしかないと思います。

  1. 移行せずに、所有しているもののままにしてください。おそらくあなたの仕事を辞めて、さらに良いデザインで真面目なソフトウェアに取り組み始めてください。極端なプログラミングは、1つか2つのクラッシュを乗り切るのに十分な資金を備えた長期のプロジェクトに取り組んでいる場合、常に最良のソリューションとは限りません。

  2. ファイルが分割されたら、どのように見えるかをレイアウトします。必要なファイルを作成し、アプリケーションに統合します。関数の名前を変更するか、オーバーロードして追加のパラメーターを取得します(単純なブール値かもしれません)。コードで作業する必要がある場合、作業する必要がある関数を新しいファイルに移行し、古い関数の関数呼び出しを新しい関数にマップします。この方法でメインファイルを保持し、アウトソーシングされた正確な時期など、特定の機能に変更が加えられた場合でも、変更を確認できるはずです。

  3. ワークフローが過大評価されており、深刻なビジネスを行うにはアプリケーションの一部を書き直す必要があることを同僚に説得してみてください。

22
Robin

まさにこの問題は、「レガシーコードで効果的に作業する」という本の章の1つで処理されます( http://www.Amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052 =)。

19
Patrick

Mainmodule.cppのAPIポイントにマップする一連のcommandクラスを作成するのが最善だと思います。

それらを配置したら、コマンドクラスを介してこれらのAPIポイントにアクセスするために既存のコードベースをリファクタリングする必要があります。完了したら、各コマンドの実装を新しいクラス構造に自由にリファクタリングします。

もちろん、11 KLOCの単一クラスでは、そこのコードはおそらく非常に結合されて脆弱ですが、個々のコマンドクラスを作成すると、他のプロキシ/ファサード戦略よりもはるかに役立ちます。

私はこのタスクをうらやましくはありませんが、時間が経つにつれて、この問題は、取り組まなければ悪化するだけです。

更新

コマンドパターンはファサードよりも望ましいことをお勧めします。

(比較的)モノリシックなファサードよりも多くの異なるコマンドクラスを維持/整理することが望ましいです。単一のファサードを11 KLOCファイルにマッピングするには、おそらくいくつかの異なるグループ自体に分割する必要があります。

なぜこれらのファサードグループを理解しようとするのですか? Commandパターンを使用すると、これらの小さなクラスを有機的にグループ化および整理できるため、柔軟性が大幅に向上します。

もちろん、両方のオプションは、単一の11 KLOCおよび成長中のファイルよりも優れています。

14
ocodo

1つの重要なアドバイス:リファクタリングとバグ修正を混在させないでください。必要なのは、ソースコードが異なることを除いて、以前のバージョンとidenticalであるプログラムのバージョンです。

1つの方法は、最小の関数/部分をそれ自体のファイルに分割し始め、ヘッダーでインクルードすることです(したがって、main.cppを#includesのリストに変えると、コード自体の匂いがします)ただし、C++の第一人者)、少なくともファイルに分割されています)。

その後、すべてのメンテナンスリリースを「新しい」main.cppまたは構造に合わせて切り替えてみてください。繰り返しますが、他の変更やバグ修正はありません。それらを追跡することは地獄のように混乱します。

もう1つ:すべてを一度にリファクタリングする際に1つの大きなパスを作成することを望む場合、噛むことができる以上のことを食い物にするかもしれません。 1つまたは2つの「パーツ」を選択してすべてのリリースに追加し、顧客にさらに価値を追加します(結局、リファクタリングは直接的な価値を追加しないため、正当化する必要があるコストです)。 1つまたは2つの部分。

明らかに、常に新しいファイルをmain.cppに追加するだけでなく、分割ファイルを実際に使用するためにチーム内で何らかの規律が必要になりますが、繰り返しますが、1つの大規模なリファクタリングを試みることは最善のアクションではないかもしれません。

13
Michael Stum

Rofl、これは私の古い仕事を思い出させます。私が参加する前は、すべてが1つの巨大なファイル(C++)の中にあったようです。次に、(includeを使用して完全にランダムなポイントで)約3(まだ巨大なファイル)に分割しました。このソフトウェアの品質は、ご想像のとおり恐ろしいものでした。プロジェクトの合計は約4万LOCです。 (コメントはほとんど含まれていませんが、重複するコードがたくさんあります)

最後に、プロジェクトを完全に書き直しました。私は、プロジェクトの最悪の部分をゼロからやり直すことから始めました。もちろん、この新しい部分と残りの部分の間の可能な(小さな)インターフェースを念頭に置いていました。次に、この部分を古いプロジェクトに挿入しました。必要なインターフェイスを作成するために古いコードをリファクタリングするのではなく、単に置き換えました。その後、そこから小さなステップを踏んで、古いコードを書き直しました。

これには約半年かかり、その間はバグ修正のほかに古いコードベースの開発はなかったと言わざるを得ません。


編集:

サイズは約40k LOCのままでしたが、新しいアプリケーションには多くの機能が含まれており、初期バージョンでは8年前のソフトウェアよりもバグが少ないと思われます。書き直した理由の1つは、新しい機能が必要であり、古いコード内にそれらを導入することはほぼ不可能だったことです。

ソフトウェアは、組み込みシステム、ラベルプリンター用でした。

私が追加すべきもう1つの点は、理論的にはプロジェクトがC++だったことです。しかし、それはOOではありませんでした。Cであったかもしれません。新しいバージョンはオブジェクト指向でした。

10
ziggystar

私はあなたの痛みを理解しています:)私もそのようなプロジェクトにいくつか参加しており、それは見た目が良くありません。これには簡単な答えはありません。

あなたのために働くかもしれない1つのアプローチは、すべての関数にセーフガードの追加を開始することです。つまり、引数のチェック、メソッドの事前/事後条件、そして最終的にソースの現在の機能をキャプチャするためにユニットテストをすべて追加します。これを取得したら、コードをリファクタリングするための準備が整います。何かを忘れた場合に警告とエラーがポップアップ表示されるためです。

リファクタリングが利益よりも痛みをもたらす場合もありますが。次に、元のプロジェクトを疑似メンテナンス状態のままにして、ゼロから開始し、獣から機能を徐々に追加する方がよい場合があります。

8
Anders

大丈夫ですから、ほとんどの場合、プロダクションコードのAPIを書き直すことは、最初は悪い考えです。 2つのことが必要です。

1つは、このファイルの現在の製品バージョンでコードをフリーズすることをチームに実際に決定させる必要があります。

2つ目は、このプロダクションバージョンを使用して、前処理ディレクティブを使用してビルドを管理するブランチを作成し、大きなファイルを分割する必要があります。 JUSTプリプロセッサディレクティブ(#ifdefs、#includes、#endifs)を使用してコンパイルを分割する方が、APIを再コーディングするよりも簡単です。 SLAと継続的なサポートは間違いなく簡単です。

ここでは、クラス内の特定のサブシステムに関連する関数を単純に切り取り、mainloop_foostuff.cppというファイルに入れて、正しい場所のmainloop.cppに含めることができます。

[〜#〜] or [〜#〜]

より時間がかかりますが堅牢な方法は、物事がどのように含まれるかという二重の間接性を持つ内部依存構造を考案することです。これにより、物事を分割し、依然として相互依存関係を処理できます。このアプローチは位置コーディングを必要とするため、適切なコメントと組み合わせる必要があることに注意してください。

このアプローチには、コンパイルするバリアントに基づいて使用されるコンポーネントが含まれます。

基本的な構造では、mainclass.cppには、次のようなステートメントのブロックの後にMainClassComponents.cppという新しいファイルが含まれます。

#if VARIANT == 1
#  define Uses_Component_1
#  define Uses_Component_2
#Elif VARIANT == 2
#  define Uses_Component_1
#  define Uses_Component_3
#  define Uses_Component_6
...

#endif

#include "MainClassComponents.cpp"

MainClassComponents.cppファイルの主な構造は、次のようなサブコンポーネント内の依存関係を解決するためにあります。

#ifndef _MainClassComponents_cpp
#define _MainClassComponents_cpp

/* dependencies declarations */

#if defined(Activate_Component_1) 
#define _REQUIRES_COMPONENT_1
#define _REQUIRES_COMPONENT_3 /* you also need component 3 for component 1 */
#endif

#if defined(Activate_Component_2)
#define _REQUIRES_COMPONENT_2
#define _REQUIRES_COMPONENT_15 /* you also need component 15 for this component  */
#endif

/* later on in the header */

#ifdef _REQUIRES_COMPONENT_1
#include "component_1.cpp"
#endif

#ifdef _REQUIRES_COMPONENT_2
#include "component_2.cpp"
#endif

#ifdef _REQUIRES_COMPONENT_3
#include "component_3.cpp"
#endif


#endif /* _MainClassComponents_h  */

そして、コンポーネントごとにcomponent_xx.cppファイルを作成します。

もちろん数字を使用していますが、コードに基づいてより論理的なものを使用する必要があります。

プリプロセッサーを使用すると、APIの変更を心配することなく物事を分割することができます。これは、実稼働環境では悪夢です。

生産が完了したら、実際に再設計に取り組むことができます。

8
Elf King

これは大きな問題に対する答えではなく、その特定の部分に対する理論的な解決策です。

  • 大きなファイルをサブファイルに分割する場所を見つけます。これらの各ポイントに特別な形式でコメントを入れます。

  • これらのポイントでファイルをサブファイルに分割するかなり簡単なスクリプトを作成します。 (おそらく、特別なコメントには、スクリプトが分割方法の指示として使用できるファイル名が埋め込まれています。)分割の一部としてコメントを保持する必要があります。

  • スクリプトを実行します。元のファイルを削除します。

  • ブランチからマージする必要がある場合は、最初にピースを連結して大きなファイルを再作成し、マージを実行してから、再度分割します。

また、SCCファイル履歴を保持したい場合は、個々のピースファイルがオリジナルのコピーであることをソース管理システムに伝えることが最善の方法であると考えています。そのファイルに保存されていたセクションの履歴を保存しますが、もちろん大きな部分が「削除」されたことも記録します。

4
Brooks Moses

あまり危険を冒さずに分割する1つの方法は、すべての回線変更を歴史的に見てみることです。他の機能よりも安定している特定の機能はありますか?あなたがそうするなら、変化のホットスポット。

数年以内に行が変更されていない場合は、心配することなく別のファイルに移動できます。特定の行に触れた最後のリビジョンが注釈されたソースを見て、引き出すことができる機能があるかどうかを確認します。

4
Paul Rubel

ファイルサイズを小さくするのではなく、クラスサイズを小さくすることに注意する必要があります。ほぼ同じですが、問題を別の角度から見ることができます(@Brian Rasmussen suggests のように、クラスには多くの責任があるようです)。

4
Björn Pollex

あなたが持っているのは、 blob と呼ばれる既知のデザインアンチパターンの古典的な例です。ここで私が指摘した記事を読むのに時間をかけてください。おそらくあなたは何か役に立つかもしれません。また、このプロジェクトが見た目と同じくらい大きい場合は、制御できないコードへの成長を防ぐための設計を検討する必要があります。

4
David Conde

私の同情-以前の仕事で、対処しなければならないファイルよりも数倍大きいファイルで同様の状況に遭遇しました。解決策は:

  1. 問題のプログラムの関数を徹底的にテストするコードを記述します。まだ手元にないようですね...
  2. ヘルパー/ユーティリティクラスに抽象化できるコードを特定します。大きなものである必要はなく、「メイン」クラスの一部ではないものだけです。
  3. 2.で識別されたコードを別のクラスにリファクタリングします。
  4. テストを再実行して、何も壊れていないことを確認します。
  5. 時間があれば、2に進み、必要に応じて繰り返して、コードを管理しやすくします。

ステップ3で作成するクラスは、新しく明確になった機能に適したコードをより多く吸収するために反復が増える可能性があります。

追加することもできます:

0:購入 Michael Feathers 'book レガシーコードの操作

残念ながら、このタイプの作業はあまりにも一般的ですが、私の経験では、機能しているが、恐ろしいコードを徐々に機能させることができるという大きな価値があります。

3
Steve Townsend

うわー、素晴らしい音。獣をリファクタリングするのに多くの時間が必要だと上司に説明するのは試してみる価値があると思います。彼が同意しない場合、辞めることはオプションです。

とにかく、基本的にすべての実装を破棄して新しいモジュールに再グループ化することをお勧めします。それらを「グローバルサービス」と呼びましょう。 「メインモジュール」はそれらのサービスにのみ転送し、作成する新しいコードは「メインモジュール」の代わりにそれらを使用します。これは妥当な時間内に実行可能でなければなりません(ほとんどがコピーアンドペーストであるため)、既存のコードを壊すことはなく、一度に1つのメンテナンスバージョンを実行できます。まだ時間がある場合は、すべての古い依存モジュールをリファクタリングして、グローバルサービスも使用できます。

3
back2dos

私はこの文があなたの投稿の最も興味深い部分であることがわかりました。

>ファイルは、製品のいくつかの(> 10)メンテナンスバージョンで使用され、積極的に変更されているため、リファクタリングするのは非常に困難です

まず、分岐をサポートするこれらの10以上のメンテナンスバージョンを開発するために、ソース管理システムを使用することをお勧めします。

次に、10個のブランチを作成します(メンテナンスバージョンごとに1つ)。

私はあなたがすでにしびれているのを感じることができます!ただし、機能が不足しているためにソース管理が状況に応じて機能していないか、正しく使用されていません。

次に、作業中のブランチに戻ります。製品の他の9つのブランチを混乱させないことを知って、適切と思われるリファクタリングします。

Main()関数にあなたがそんなに多く持っていることを少し心配します。

私が書くプロジェクトでは、main()を使用して、コアオブジェクト(シミュレーションオブジェクトやアプリケーションオブジェクトなど)の初期化のみを実行します。これらのクラスは、実際の作業を行う場所です。

また、プログラム全体でグローバルに使用するために、メインのアプリケーションロギングオブジェクトを初期化します。

最後に、メインではプリプロセッサブロックにリーク検出コードも追加し、DEBUGビルドでのみ有効にするようにします。これがmain()に追加するすべてです。 Main()は短くする必要があります!

あなたはそれを言う

>ファイルには基本的にプログラムの「メインクラス」(メイン内部作業のディスパッチと調整)が含まれています

これらの2つのタスクは、コーディネーターとワークディスパッチャーという2つの別個のオブジェクトに分割できるように思えます。

これらを分割すると、「SCCワークフロー」が台無しになる場合がありますが、SCCワークフローがソフトウェアメンテナンスの問題を引き起こしていることを厳守しているようです。なぜなら、それを修正するとすぐに眠り始めるからです。

あなたが決定を下すことができない場合、あなたのマネージャーとそれについて歯と爪と戦ってください-あなたのアプリケーションはリファクタリングされる必要があります-そしてひどくそれの音によって!答えにノーを取らないでください!

2
user206705
  1. このファイルとコードに再び触れないでください!
  2. 御Treat走はあなたが行き詰まっているようなものです。そこでエンコードされた機能のアダプターの作成を開始します。
  3. 異なるユニットで新しいコードを記述し、モンスターの機能をカプセル化するアダプターとのみ話します。
  4. ...上記の1つだけが不可能な場合は、ジョブを終了して新しいジョブを取得します。
2
paul_71

あなたが興味深く/役に立つと思うかもしれないもう一つの本は、 リファクタリング です。

2
Derek

あなたがそれを説明したように、主な問題は、分割前と分割後の違い、バグ修正などのマージです。その周りのツール。 Perl、Rubyなどでスクリプトをハードコーディングして、プリスプリットをポストスプリットの連結と比較することによるノイズのほとんどを取り除くのに、それほど時間はかかりません。ノイズの処理に関して最も簡単なことを実行します。

  • 連結前/連結中に特定の行を削除します(ガードを含めるなど)
  • 必要に応じて、diff出力から他のものを削除します

チェックインがあり、連結が実行されるたびに、単一ファイルバージョンと比較するための準備ができているので、作成することさえできます。

2
Tony Delroy

私の0.05ユーロセント:

混乱全体を再設計し、技術的要件とビジネス要件を考慮してサブシステムに分割します(=それぞれのコードベースが異なる可能性のある多数の並行保守トラック、明らかに高い変更可能性などが必要です)。

サブシステムに分割するときは、最も変更された場所を分析し、変更のない部分からそれらを分離します。これにより、トラブルスポットが表示されます。モジュールAPIがそのまま維持され、BCを常に壊す必要がないように、最も変化の激しい部分を独自のモジュール(dllなど)に分離します。これにより、必要に応じて、コアを変更せずに、異なるメンテナンスブランチに異なるバージョンのモジュールをデプロイできます。

再設計はおそらく別のプロジェクトである必要があり、移動するターゲットに対してそれを行おうとしてもうまくいきません。

ソースコードの履歴については、新しいコードでは忘れてください。ただし、必要に応じて履歴を確認できるように、履歴をどこかに保管してください。開始後はそれほど必要ないでしょう。

ほとんどの場合、このプロジェクトの管理者の賛同を得る必要があります。おそらく、開発時間を短縮し、バグを減らし、保守を容易にし、全体的な混乱を少なくすることで議論できます。 「私たちの重要なソフトウェア資産の将来の保証とメンテナンスの実行可能性を積極的に可能にします」のラインに沿った何か:)

これが、少なくとも問題に取り組む方法です。

2
Slinky

コメントを追加することから始めます。関数が呼び出される場所と、物事を移動できるかどうかを参照します。これは物事を動かすことができます。コードベースがどれだけ脆弱かを評価する必要があります。次に、機能の共通部分を一緒に移動します。一度に小さな変化。

2
Jesper Smith

私が行うのに役立つと思う(そして、私はあなたが直面している規模ではないが、今それをしている)、メソッドをクラスとして抽出することです(メソッドオブジェクトリファクタリング)。バージョンによって異なるメソッドは、必要な異なる動作を提供するために共通のベースに注入できる異なるクラスになります。

2
Channing Walton

アプリケーション全体をより適切な方法で書き換える方法を検討してください。あなたのアイデアが実現可能かどうかを確認するために、プロトタイプの一部を書き直してください。

実行可能なソリューションを特定したら、それに応じてアプリケーションをリファクタリングします。

より合理的なアーキテクチャを作成するすべての試みが失敗した場合、少なくともソリューションはおそらくプログラムの機能を再定義することであることを知っています。

2
wallyk

このコードが10人の顧客にサービスを提供し、コードバリアントが含まれている場合、特定の顧客向けのバリアントを含む多くのコードクローンがある可能性があります

11,000行のファイルでクローン検出器を実行したいと思います。 (実際、あなたがそれを私に送ったら、私は私のC++対応クローン検出器を使ってそれをし[bioを参照]答えを送ってくれます)。

それはクローンを表示しますandそれらのクローンがどのように変化したか。その情報があれば、コードをリファクタリングするのはかなり簡単かもしれません。

1
Ira Baxter

ファイルを分割するときにソースの履歴を追跡する最も簡単な方法は次のようなものだと思います:

  1. SCMシステムが提供する履歴保存コピーコマンドを使用して、元のソースコードのコピーを作成します。おそらくこの時点でサブミットする必要がありますが、ビルドシステムに新しいファイルについて伝える必要はまだないので、大丈夫です。
  2. これらのコピーからコードを削除します。これは、保持している行の履歴を壊さないはずです。

これは難しくて興味深いリファクタリングです。

まず、実装をインターフェースから分離します。この巨大なファイルを、呼び出しとパラメーターを転送するだけの空のシェルに変えます。このようにして、呼び出し元に影響を与えることなく、責任を制限したコンポーネントを作成できます(それらは依然として巨大なファイル/モジュール/クラスを呼び出します)。

これを完全に行うには、新しい潜在的なコンポーネントの作成時間を探す必要もあります。コンストラクターの送信方法によっては、すべてのパラメーターを取得するまでパラメーターのスタックが非常に難しい場合があります。

その後、発信者を探して、コンポーネントに電話させることができます。それは簡単な部分です。

1
LBarret

「ファイルには基本的にプログラムの「メインクラス」(メインの内部作業ディスパッチと調整)が含まれているため、機能が追加されるたびに、このファイルに影響を与えたり、成長するたびに影響を受けたりします。」

その大きなSWITCH(あると思います)が主なメンテナンスの問題になる場合、辞書とコマンドパターンを使用するようにリファクタリングし、既存のコードからローダーへのすべてのスイッチロジックを削除します。

    // declaration
    std::map<ID, ICommand*> dispatchTable;
    ...

    // populating using some loader
    dispatchTable[id] = concreteCommand;

    ...
    // using
    dispatchTable[id]->Execute();
1
Grozz

私はこの状況で何をするかは少し弾丸だと思います:

  1. ファイルを分割する方法を理解する(現在の開発バージョンに基づいて)
  2. ファイルに管理ロックを設定します(「金曜日の午後5時以降にmainmodule.cppに触れないでください!!!」
  3. 現在のバージョンまで、10を超えるメンテナンスバージョン(最も古いものから最新のもの)にその変更を適用して、長い週末を過ごします。
  4. サポートされているすべてのバージョンのソフトウェアからmainmodule.cppを削除します。それは新しい時代です-mainmodule.cppはもうありません。
  5. (少なくとも大きな$$$サポート契約なしで)ソフトウェアの複数のメンテナンスバージョンをサポートするべきではないことを管理者に納得させます。顧客のそれぞれが独自のバージョンを持っている場合.... yeeeeeshhhh。 10以上のフォークを維持しようとするのではなく、コンパイラ指令を追加することになります。

ファイルへの古い変更の追跡は、「mainmodule.cppからの分割」などの最初のチェックインコメントによって解決されます。最近の何かに戻る必要がある場合、ほとんどの人はその変更を覚えているでしょう。もしそれが2年後であれば、コメントはどこを見るべきかを教えてくれます。もちろん、コードを変更した人とその理由を調べるために2年以上前に戻ることはどれほど価値がありますか?

1
BIBD

質問を間違って理解した場合は修正してください。

ソースを関数またはクラス(.h/.cppファイルを分離)として分割し、ヘッダーとして含めることができないのはなぜですか?確かにいくつかの機能を再利用する必要があります。

それが始まりです。

0
Sii