複数のクライアントに対して同じソフトウェアの異なるカスタマイズされたバージョンを維持する方法


46

ニーズの異なる複数のクライアントがあります。ソフトウェアはある程度モジュール化されていますが、すべてのモジュールのビジネスロジックをあちこちで調整する必要があることはほぼ確実です。モジュールごとにクライアントを個別の(物理)モジュールに分割することを正当化するには、おそらく変更が小さすぎるため、ビルドの問題、リンクの混乱を恐れます。しかし、これらの変更は大きすぎて、構成ファイルのスイッチで構成するには多すぎます。これは、展開中に問題を引き起こし、特にティンカータイプの管理者では多くのサポートトラブルを引き起こす可能性があるためです。

ビルドシステムに、クライアントごとに1つずつ、複数のビルドを作成させて、問題の単一の物理モジュールの特殊バージョンに変更が含まれるようにします。だから私はいくつか質問があります:

ビルドシステムに複数のビルドを作成させることをお勧めしますか?ソース管理、特にsvnにさまざまなカスタマイズを保存するにはどうすればよいですか?


4
あなたの#ifdefために働きますか?
オホ

6
プリプロセッサディレクティブはすぐに非常に複雑になり、コードの読み取りとデバッグが難しくなります。
ファルコン

1
より良い回答を得るには、実際のプラットフォームとアプリケーションの種類(デスクトップ/ Web)に関する詳細を含める必要があります。デスクトップC ++アプリでのカスタマイズは、PHP Webアプリとはまったく異なります。
GrandmasterB

2
@Falcon:2011年にあなたが答えを選んだので、私は多くの疑問を持っていますが、提案された方法でSVNを使用した経験がありますか?私の異議は根拠がありませんか?
Doc Brown

2
@Doc Brown:分岐とマージは退屈で複雑でした。当時、動作や構成を変更するクライアント固有のプラグインまたは「パッチ」を備えたプラグインシステムを使用していました。いくらかオーバーヘッドがありますが、Dependendy Injectionで管理できます。
ファルコン

回答:


7

必要なのは、機能の分岐のようなコード編成です。特定のシナリオでは、これはクライアント固有のトランク分岐と呼ばれるべきです。なぜなら、新しいものを開発する(またはバグを解決する)間、機能分岐も使用する可能性が高いからです。

http://svnbook.red-bean.com/en/1.5/svn.branchmerge.commonpatterns.html

アイデアは、新しい機能のブランチがマージされたコードトランクを持つことです。すべての非クライアント固有機能を意味します。

また、クライアント固有のブランチもあり、必要に応じて同じ機能のブランチもマージします(必要に応じてチェリーピッキング)。

これらのクライアントブランチは機能ブランチと似ていますが、一時的でも短命でもありません。それらは長期間にわたって維持され、主に統合されるだけです。クライアント固有の機能ブランチの開発はできるだけ少なくする必要があります。

クライアント固有のブランチは、トランクへの並列ブランチであり、トランク自体でアクティブであり、どこでも全体としてマージされません。

            feature1
            ———————————.
                        \
trunk                    \
================================================== · · ·
      \ client1            \
       `========================================== · · ·
        \ client2            \
         `======================================== · · ·
              \ client2-specific feature   /
               `——————————————————————————´

7
機能の分岐の場合は+1。クライアントごとにブランチを使用することもできます。これを実現するために、SVN / CVSの代わりに分散型VCS(hg、git、bzr)を使用してください;)
Herberth Amaral

43
-1。それは「機能ブランチ」の目的ではありません。「機能の開発が終了したときにマージする一時的なブランチ」としての定義に矛盾しています。
P

14
それでも、ソース管理でこれらのすべてのデルタを維持し、それらを永久に並べる必要があります。短期的にはこれでうまくいくかもしれません。長期的にはひどいかもしれません。
すぐに

4
-1、これは命名の問題ではありません-異なるクライアントに異なるブランチを使用することは、コード複製の別の形式です。これは私見のアンチパターンであり、どのようにそれをしないかの例です。
Doc Brown

7
ロバート、編集の前でさえ、あなたが提案したことをかなりよく理解したと思うが、これは恐ろしいアプローチだと思う。N個のクライアントがあるとします。トランクに新しいコア機能を追加するたびに、SCMによって新しい機能をN個のブランチに簡単に伝達できるようです。ただし、この方法でブランチを使用すると、クライアント固有の変更が明確に分離されるのを簡単に回避できます。その結果、トランクの変更ごとにマージの競合が発生する可能性がNになりました。さらに、1つではなくN個の統合テストを実行する必要があります。
Doc Brown

38

これをSCMブランチで実行しないでください。共通コードを、ライブラリまたはプロジェクトスケルトンアーティファクトを生成する別のプロジェクトにします。各顧客プロジェクトは、依存関係として共通のプロジェクトに依存する別個のプロジェクトです。

これは、共通の基本アプリがSpringなどの依存性注入フレームワークを使用している場合に最も簡単です。そのため、カスタム機能が必要な場合、各顧客プロジェクトにオブジェクトの異なる置換バリアントを簡単に注入できます。まだDIフレームワークを持っていない場合でも、DIフレームワークを追加してこの方法で実行するのが最も簡単な選択です。


物事をシンプルにするもう1つのアプローチは、依存関係注入を使用する代わりに、継承(および単一のプロジェクト)を使用することです。共通プロジェクトとカスタマイズの両方を同じプロジェクトに保持します(継承、部分クラス、またはイベントを使用)。その設計では、クライアントブランチは(選択した回答で説明されているように)正常に機能し、共通コードを更新することでクライアントブランチを常に更新できます。
drizin

私が何かを見逃していないなら、あなたが100人の顧客を持っているなら、あなたは(100 x NumberOfChangedProjects)を作成し、DIを使用してそれらを管理することを提案しますか?それはそのためになら、私はdefinetelyひどいだろう保守以来、この種のソリューションから離れて滞在する...
sotn

13

すべてのクライアントのソフトウェアを単一のブランチに保存します。異なるクライアントに対して行われた変更を分岐させる必要はありません。ほとんどの場合、ソフトウェアはすべてのクライアントにとって最高の品質であり、コアインフラストラクチャのバグ修正は不必要なマージオーバーヘッドなしで全員に影響を与え、より多くのバグが発生する可能性があります。

共通コードをモジュール化し、異なるファイルで異なるコードを実装するか、異なる定義で保護します。ビルドシステムに各クライアントに特定のターゲットを持たせ、各ターゲットは1つのクライアントに関連するコードのみでバージョンをコンパイルします。たとえば、コードがCの場合、「#ifdef」またはビルド構成管理用の言語のメカニズムを使用して、さまざまなクライアントの機能を保護し、クライアントが支払った機能の量に対応する一連の定義を準備します。

ifdefs が気に入らない場合は、「インターフェースと実装」、「ファンクター」、「オブジェクトファイル」、または1か所にさまざまなものを保存するために言語が提供するツールを使用します。

ソースをクライアントに配布する場合は、ビルドスクリプトに特別な「ソース配布ターゲット」を作成することをお勧めします。このようなターゲットを呼び出すと、ソフトウェアのソースの特別なバージョンが作成され、出荷できるように別のフォルダーにコピーされますが、コンパイルはされません。


8

多くの人が述べているように、コードを適切に分解し、共通のコードに基づいてカスタマイズします。これにより、保守性が大幅に向上します。オブジェクト指向の言語/システムを使用しているかどうかに関係なく、これは可能です(ただし、実際にオブジェクト指向であるものよりもCで行うのはかなり難しいです)。これはまさに、継承とカプセル化が解決するのに役立つタイプの問題です!


5

非常に慎重に

機能分岐はオプションですが、やや重いと思います。また、深い変更を簡単に行うことができるため、制御されていないと、アプリケーションの分岐が発生する可能性があります。理想的には、コアコードベースをできるだけ一般的かつ汎用的に保つために、可能な限りカスタマイズを押し上げます。

大幅な変更やリファクタリングを行わずにコードベースに適用できるかどうかはわかりませんが、これを行う方法を次に示します。基本的な機能は同じでしたが、各顧客が非常に特定の機能セットを必要とする同様のプロジェクトがありました。一連のモジュールとコンテナーを作成し、構成(IoC)で組み立てます。

次に、各顧客に対して、基本的に構成とビルドスクリプトを含むプロジェクトを作成し、サイト用に完全に構​​成されたインストールを作成しました。時々、このクライアント用にカスタムメイドされたコンポーネントもいくつか配置します。しかし、これはまれであり、可能な限り他のプロジェクトで使用できるように、より一般的な形式で作成してプッシュダウンしようとします。

最終結果は、必要なカスタマイズレベルを取得し、インストールスクリプトをカスタマイズして、顧客サイトにアクセスしたときに、システムを常に微調整しているように見えず、追加の非常に大きなボーナスが得られることですビルドに直接フックされた回帰テストを作成できるようにします。このようにして、顧客固有のバグが発生したときはいつでも、システムがデプロイされたときにシステムをアサートするテストを記述できるため、そのレベルでもTDDを実行できます。

要するに:

  1. フラットなプロジェクト構造を持つ、大幅にモジュール化されたシステム。
  2. 構成プロファイルごとにプロジェクトを作成します(複数のプロファイルを共有できますが、顧客)
  3. 必要な機能セットを別の製品として組み立て、そのように扱います。

適切に行われた場合、製品アセンブリにはいくつかの設定ファイルを除くすべてが含まれているはずです。

これをしばらく使用した後、私は最終的に、主に使用されるシステムまたは重要なシステムをコアユニットとして組み立て、このアセンブリを顧客のアセンブリに使用するメタパッケージを作成しました。数年後、私は非常に迅速に組み立てて顧客ソリューションを作成できる大きなツールボックスを手に入れました。私は現在、Spring Rooを調査しており、ある日、最初のインタビューで顧客と一緒にシステムの最初のドラフトを作成できることを期待して、もう少しアイデアを推し進めることができないかどうかを確認しています...開発;-)。

これが役に立てば幸いです


3

モジュールごとにクライアントを個別の(物理)モジュールに分割することを正当化するには、おそらく変更が小さすぎるため、ビルドの問題、リンクの混乱を恐れます。

IMO、彼らは小さすぎることはできません。可能であれば、ほとんどすべての場所で戦略パターンを使用して、クライアント固有のコードを除外します。これにより、分岐する必要があるコードの量が減り、すべてのクライアントで一般的なコードの同期を保つために必要なマージが減ります。また、テストを簡素化します...デフォルトの戦略を使用して一般的なコードをテストし、クライアント固有のクラスを個別にテストできます。

モジュールAでポリシーX1を使用するにはモジュールBでポリシーX2を使用する必要があるようにモジュールがコーディングされている場合、X1とX2を単一のポリシークラスに結合できるようにリファクタリングを検討してください。


1

SCMを使用してブランチを維持できます。マスターブランチをクライアントのカスタムコードからそのまま/クリーンに保ちます。このブランチでメイン開発を行います。アプリケーションのカスタマイズされたバージョンごとに、個別のブランチを維持します。優れたSCMツールは、ブランチのマージで非常にうまく機能します(Gitが思い浮かびます)。マスターブランチの更新はすべてカスタマイズされたブランチにマージする必要がありますが、クライアント固有のコードは独自のブランチにとどまることができます。


ただし、可能な場合は、モジュール式で構成可能な方法でシステムを設計してください。これらのカスタムブランチの欠点は、コア/マスターから離れすぎていることです。


1

プレーンなCで記述している場合、これはややugい方法です。

  • 共通コード(例:ユニット "frangulator.c")

  • クライアント固有のコード、小さく、各クライアントにのみ使用される断片。

  • メインユニットコードで、#ifdefと#includeを使用して次のようなことを行います。

#ifdef CLIENT = CLIENTA
#include "frangulator_client_a.c"
#endif

これを、クライアント固有のカスタマイズを必要とするすべてのコードユニットで繰り返しパターンとして使用します。

これは非常に見苦しく、他のトラブルにもつながりますが、単純でもあり、クライアント固有のファイルを簡単に相互比較できます。

また、クライアント固有のすべての部分が常に明確に(それぞれ独自のファイルに)表示され、メインコードファイルとファイルのクライアント固有の部分との間に明確な関係があることも意味します。

本当に賢い場合は、makefilesをセットアップして正しいクライアント定義を作成することができます。

クライアントを作る

client_a用にビルドされ、「make clientb」はclient_b用に作成されます。

(およびターゲットが指定されていない「make」は、警告または使用法の説明を発行できます。)

以前にも同様のアイデアを使用しましたが、セットアップには時間がかかりますが、非常に効果的です。私の場合、1つのソースツリーで約120の異なる製品が構築されました。


0

gitでは、すべての共通コードを含むマスターブランチと、各クライアントのブランチを作成します。コアコードに変更が加えられるたびに、すべてのクライアント固有のブランチをマスターの上にリベースするだけで、現在のベースラインの上に適用されるクライアント用の一連の移動パッチを作成できます。

クライアントに変更を加え、他のブランチに含める必要があるバグに気づいたときはいつでも、それをマスターまたは修正が必要な他のブランチにチェリーピックできます(ただし、異なるクライアントブランチはコードを共有していますが) 、おそらく両方をマスターから共通のブランチから分岐させる必要があります)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.