Le altre risposte danno buoni suggerimenti per l'architettura e il layout del progetto. Volevo affrontare questo punto specifico:
Code contains quite a lot of conditional compilation to separate features.
Recentemente mi sono imbattuto in questo su un progetto su cui sto lavorando e l'ho migliorato usando le seguenti tecniche:
Usa costanti
Alcuni dei nostri codici sono stati modificati rapidamente con le direttive e ora assomigliano a questo:
#if <SOMETHING>
callSomeFunction(aConst);
#else
callSomeFunction(bConst);
#endif
Questo può essere migliorato creando una singola costante che è definita in modo diverso per ogni percorso di #if
e chiamando la funzione una sola volta, in questo modo:
// At top of file
#if <SOMETHING>
const int kImportantVal = aConst;
#else
const int kImportantVal = bConst;
#endif
…
// In the actual code
callSomeFunction(kImportantVal);
Combinazione di sezioni
Spesso nella nostra fretta ci limitiamo a cospargere #if
di direttive in tutto il codice, lasciando funzioni come questa:
#if SOMETHING
callA();
#else
callB();
#endif
callC();
#if SOMETHING
callD();
#else
callE();
#endif
se callD()
e callE()
non dipendono dal risultato di callC()
, questo può essere riscritto come:
#if SOMETHING
callA();
callD();
#else
callB();
callE();
#endif
callC();
Funzioni refactored
Le funzionalità possono essere spostate in funzioni raggruppate in una singola #if
o altra direttiva.
Se guardiamo all'esempio sopra, un altro modo per farlo sarebbe il seguente:
#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();
Usa polimorfismo
Nei linguaggi orientati agli oggetti come C ++ e Objective-C, è abbastanza semplice creare un'interfaccia che sia implementata da diversi oggetti in diverse build. Ad esempio, se hai questo codice:
@interface SomeInterface
{
}
- (void)methodA;
@end
@implementation SomeInterface
-(void)methodA
{
#if SOMETHING
doX();
#else
doY();
#endif
}
@end
Dovresti invece creare 2 classi, una per ogni ramo di #if SOMETHING
. Potrebbero assomigliare a questo:
@interface SomeInterfaceX : SomeInterface
{
}
@end
@implementation SomeInterfaceX
- (void)methodA
{
doX();
}
@end
e
@interface SomeInterfaceY : SomeInterface
{
}
@end
@implementation SomeInterfaceY
- (void)methodA
{
doY();
}
@end
Puoi fare qualcosa di simile in C ++ con le classi derivate.
Categorie
In Objective-C abbiamo la possibilità di aggiungere una category
solo nelle destinazioni che ne hanno bisogno. Ad esempio, in SomeClass
vogliamo un set di funzionalità in 2 target, ma esistono alcune funzionalità aggiuntive che dovrebbero esistere solo in 1 dei target. Pertanto, adottiamo i metodi implementati solo nell'obiettivo 1 e li inseriamo in un file separato contenente una categoria che implementa solo quei metodi. Quel file è compilato solo nell'obiettivo 1 e non nell'altro.
Quindi avresti qualcosa di simile:
@interface SomeClass {
// ... class definition that is common to both targets
}
@end
@implementation SomeClass
// ... class implementation that is common to both targets
@end
Quindi in un file separato
@interface SomeClass (Additions)
// ... additional methods for target that needs them
@end
@implementation SomeClass (Additions)
// ... implementation of addition methods for target that needs them
@end