複数のモバイルアプリのビルドに使用される大きなコードベース(iOS/Objective-C)を継承しました。アプリのUIは似ていますが、同じではありません。
アプリは多くの共通機能を共有しますが、1つのアプリでのみ見られる機能があります。一部の機能は後で他のアプリに追加できます。
アプリには比較的独立したライフサイクルがあります。リリースは同時ではありません。各アプリの2つまたは3つのバージョンを同時に開発することは一般的です:現在公開されているバージョンのホットフィックス、次のマイナーバージョン、次のメジャーバージョン。機能はバージョン間を移動できます。つまり、機能を新しいバージョンに延期したり、以前のバージョンに移動したりできます。
現時点では、アプリごとに個別のスキームがあります。コードには、機能を分離するための条件付きコンパイルがかなり含まれています。 git cherry-pick
およびgit revert
コマンドを使用して、ブランチ間で頻繁に転送されるコード変更(開発->マスター、開発-> releaseXYZ)。
このすべてを維持する苦痛をどのように軽減できますか?同じアプリの異なるバージョン間および異なるアプリ間の機能セット管理を本当に簡略化したいと思います。
編集:これまでの答えをありがとう。コードベースをある種のモジュールに分割する必要があることを理解しています。これらのモジュールを効率的に管理する方法/苦痛を軽減する方法をもっと教えていただければ幸いです。
コードがどのようにモジュール化されているかはわかりませんが、これらすべての複雑さを処理するには、APIレイヤーを作成すると便利な場合があります。コアライブラリには、すべてのアプリに機能/機能があり、最終的な各アプリに機能パックを含めることができます。基本的に、依存関係グラフは 有向非循環グラフ(DAG) になるように意図されています。
機能をモジュールに編成した後、アプリケーションが機能を注入して条件付きコンパイルの処理を行えるように、拡張ポイントを含めることができます。そうすれば、変更を加えてから条件の一部を忘れただけでコードが壊れることはありません。
特定のアプリごとに、すべてのカスタマイズと配線を一緒に行います。
他の答えは、アーキテクチャとプロジェクトのレイアウトについての良い提案を与えます。私はこの特定のポイントに対処したいと思いました:
コードには、機能を分離するための条件付きコンパイルがかなり含まれています。
私が最近取り組んでいるプロジェクトでこれに遭遇し、次のテクニックを使用してそれを改善しました:
私たちのコードの一部はディレクティブですばやく変更され、次のようになります。
_#if <SOMETHING>
callSomeFunction(aConst);
#else
callSomeFunction(bConst);
#endif
_
これは、_#if
_のパスごとに異なる定義の単一の定数を作成し、次のように関数を1回だけ呼び出すことで改善できます。
_// At top of file
#if <SOMETHING>
const int kImportantVal = aConst;
#else
const int kImportantVal = bConst;
#endif
…
// In the actual code
callSomeFunction(kImportantVal);
_
多くの場合、急いで、コード全体に_#if
_ディレクティブを振りかけるだけで、関数は次のようになります。
_#if SOMETHING
callA();
#else
callB();
#endif
callC();
#if SOMETHING
callD();
#else
callE();
#endif
_
callD()
とcallE()
がcallC()
の結果に依存しない場合、これは次のように書き換えられます。
_#if SOMETHING
callA();
callD();
#else
callB();
callE();
#endif
callC();
_
機能は、単一の_#if
_または他のディレクティブ内でグループ化された関数に移動できます。
上記の例を見ると、別の方法は次のようになります。
_#if SOMETHING
void callA()
{
// ...whatever callA() above does;
}
void callD()
{
// ...whatever callD() above does;
}
#else
void callA()
{
// ...whatever callB() above does;
}
void callD()
{
// ...whatever callE() above does;
}
#endif
// ...
callA();
callC();
callD();
_
C++やObjective-Cなどのオブジェクト指向言語では、さまざまなビルドのさまざまなオブジェクトによって実装されるインターフェースを作成するのはかなり簡単です。たとえば、次のコードがある場合:
_@interface SomeInterface
{
}
- (void)methodA;
@end
@implementation SomeInterface
-(void)methodA
{
#if SOMETHING
doX();
#else
doY();
#endif
}
@end
_
代わりに、_#if SOMETHING
_の各ブランチに1つずつ、2つのクラスを作成します。それらは次のようになります。
_@interface SomeInterfaceX : SomeInterface
{
}
@end
@implementation SomeInterfaceX
- (void)methodA
{
doX();
}
@end
_
そして
_@interface SomeInterfaceY : SomeInterface
{
}
@end
@implementation SomeInterfaceY
- (void)methodA
{
doY();
}
@end
_
C++では、派生クラスを使用して同様のことができます。
Objective-Cでは、必要なターゲットのみにcategory
を追加するオプションがあります。たとえば、SomeClass
では、2つのターゲットに1つの機能セットが必要ですが、1つのターゲットにのみ存在する必要がある追加の機能があります。そのため、1つのターゲットでのみ実装されているメソッドを取得し、それらのメソッドのみを実装するカテゴリを含む別のファイルにそれらを配置します。そのファイルは1つのターゲットでのみコンパイルされ、他のターゲットではコンパイルされません。
だからあなたは次のようなものを持っているでしょう:
_@interface SomeClass {
// ... class definition that is common to both targets
}
@end
@implementation SomeClass
// ... class implementation that is common to both targets
@end
_
次に、別のファイルで
_@interface SomeClass (Additions)
// ... additional methods for target that needs them
@end
@implementation SomeClass (Additions)
// ... implementation of addition methods for target that needs them
@end
_
あなたは出来る:
私があなただったら、共通のコードを個別のXcodeプロジェクトに分解し、それらをフレームワークまたはライブラリとしてコンパイルします。次に、さまざまなUIを独自のプロジェクトに分離し、それぞれに共通のコードを含めることができます。
SCMの状況にも複雑さの問題があるようですので、すべてのアプリケーションを独自のリポジトリに分離し、共通のコードも独自のリポジトリに分離することを検討します。 CocoapodsやCarthageなどを使用して、各アプリに共通のフレームワークを含めることができます。