Set di funzioni diverse in più app mobili dalla stessa base di codice

3

Ho ereditato un grande codice base (iOS / Objective-C) che viene utilizzato per creare più app mobili. Le app hanno un'interfaccia utente simile, ma non identica.

Le app condividono molte funzionalità comuni, ma ci sono funzionalità che si trovano solo in un'unica app. Alcune funzioni possono essere aggiunte in seguito ad altre app.

Le app hanno un ciclo di vita relativamente indipendente. Le uscite non sono simultanee. È comune avere due o tre versioni di ogni app in fase di sviluppo contemporaneamente: hotfix per la versione pubblicata corrente, prossima versione secondaria, successiva versione principale. Le funzionalità possono spostarsi tra le versioni. Le funzionalità di I.e possono essere posticipate alla versione successiva o spostate a quella precedente.

Per ora c'è uno schema separato per ogni app. Il codice contiene un bel po 'di compilazione condizionale per separare le funzionalità. Le modifiche al codice vengono spesso trasferite tra le diramazioni (sviluppare - > master, sviluppare - > releaseXYZ) utilizzando i comandi git cherry-pick e git revert .

Come posso ridurre il dolore nel mantenere tutto questo? Mi piacerebbe davvero semplificare la gestione dei set di funzionalità tra diverse versioni della stessa app e tra diverse app.

EDIT: Grazie per le vostre risposte finora. Capisco, che ho bisogno di dividere il codice di base in una sorta di moduli. Apprezzerei molto se mi dicessi di più su come gestire questi moduli in modo efficiente / meno doloroso.

    
posta Bobrovsky 16.08.2017 - 20:34
fonte

4 risposte

4

Non so quanto sia modulare il codice, ma per gestire la complessità di tutto questo potrebbe essere utile creare livelli API. La libreria principale ha le funzioni / funzioni in tutte le app, con pacchetti di funzioni che possono essere inclusi in ciascuna delle app finali. Essenzialmente il grafico delle dipendenze è destinato a essere un grafico aciclico diretto (DAG) .

Dopo aver organizzato le tue funzionalità nei moduli, potresti voler includere i punti di estensione in modo che le applicazioni possano iniettare funzionalità per occuparsi delle cose nella compilazione condizionale. In questo modo il tuo codice non si interromperà solo perché apporti una modifica e poi dimentichi la parte nella condizione.

In ogni app specifica, esegui tutte le personalizzazioni e il cablaggio insieme.

    
risposta data 16.08.2017 - 21:04
fonte
2

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
    
risposta data 17.08.2017 - 07:31
fonte
1

Se fossi in te, prenderei in considerazione il codice comune in progetti Xcode separati e li compili come framework o libreria. Quindi puoi separare le diverse interfacce utente nei propri progetti e includere il codice comune in ognuno di essi.

Poiché sembra che tu abbia problemi di complessità con la tua situazione SCM, prenderei in considerazione la possibilità di separare tutte le applicazioni nei loro repository e anche il codice comune. Puoi usare qualcosa come Cocoapods o Carthage per includere il Framework comune in ogni app.

    
risposta data 16.08.2017 - 22:29
fonte
1

Potresti:

  1. Crea un repository GIT contenente funzionalità e magari schermi fittizi che utilizzano tali funzioni;
  2. Ogni app avrà un repository GIT separato, biforcato dal repository del Passaggio 1 (poiché ciascuna app si evolve in modo diverso, condivide funzionalità comuni e per altre funzionalità leggermente modificate);
  3. Ogni volta che vedi appropriato, durante la manutenzione di ogni app, puoi inviare i commit della funzione al repository di base, per essere disponibile alle altre app. Il repository di base è solo per aiutarti a mantenere le caratteristiche comuni.
risposta data 17.08.2017 - 22:36
fonte

Leggi altre domande sui tag