Mantieni centinaia di filiali personalizzate sulla filiale principale


140

Attualmente abbiamo un ramo master per la nostra applicazione PHP in un repository condiviso. Abbiamo più di 500 clienti che sono abbonati del nostro software, la maggior parte dei quali ha una certa personalizzazione per scopi diversi, ognuno in un ramo separato. La personalizzazione potrebbe essere un nome di campo di testo diverso, una funzionalità o un modulo totalmente nuovi o nuove tabelle / colonne nel database.

La sfida che affrontiamo è che man mano che gestiamo queste centinaia di filiali personalizzate e le distribuiamo ai clienti, di volta in volta forniamo nuove funzionalità e aggiorniamo la nostra filiale principale e vorremmo inviare le modifiche delle filiali principali alle filiali personalizzate per aggiornare li all'ultima versione.

Sfortunatamente, ciò comporta spesso molti conflitti nel codice personalizzato e passiamo molte ore a esaminare ogni singolo ramo per risolvere tutti i conflitti. Questo è molto inefficiente e abbiamo scoperto che gli errori non sono rari nel risolvere questi conflitti.

Sto cercando un modo più efficiente per mantenere aggiornati i rami di rilascio dei nostri clienti con il ramo principale che comporteranno meno sforzi durante l'unione.


11
Ci dispiace non dare una risposta "puoi usare lo strumento X", ma non ce n'è una.
Corse di leggerezza in orbita

3
O durante la compilazione (che è probabilmente più comune). Solo .. non interamente basi di codice separatamente.
Corse di leggerezza in orbita

15
@FernandoTan - Il sintomo visibile potrebbe essere il codice, ma la causa principale della malattia è la frammentazione del prodotto, la cura deve provenire dalla messa a fuoco del prodotto / mappatura delle capacità del prodotto, non dalla pulizia del codice - che alla fine accadrà. Ho dettagliato di più nella mia risposta - programmers.stackexchange.com/a/302193/78582
Alex S

8
Questo potrebbe anche essere un problema economico. Guadagni davvero da tutti quei 500 clienti? In caso contrario, è necessario ripensare il modello di prezzi e rifiutare le richieste di modifica se il cliente non paga un costo aggiuntivo.
Christian Strempfer,

13
Questo mi ha spezzato il cuore solo un po '. Fortunatamente altri stanno già urlando le risposte giuste - la mia unica raccomandazione aggiuntiva è di scriverlo e inviarlo a TheDailyWTF.
zxq9,

Risposte:


314

Stai abusando completamente dei rami! Dovresti avere la personalizzazione basata sulla flessibilità nella tua applicazione, non sulla flessibilità nel controllo della tua versione (che, come hai scoperto, non è pensata / progettata per questo tipo di utilizzo).

Ad esempio, fai in modo che le etichette dei campi di testo provengano da un file di testo, non siano codificate nella tua applicazione (è così che funziona l'internazionalizzazione). Se alcuni clienti hanno funzionalità diverse, rendere l'applicazione modulare , con limiti interni rigorosi governati da API rigorose e stabili, in modo che le funzionalità possano essere collegate secondo necessità.

L'infrastruttura di base e tutte le funzionalità condivise devono quindi essere archiviate, gestite e testate una sola volta .

Avresti dovuto farlo dall'inizio. Se hai già cinquecento varianti di prodotto (!), Risolverlo sarà un lavoro enorme ... ma non più di una manutenzione continua.


142
+1 per "Avresti dovuto farlo dall'inizio". Questo livello di debito tecnico può distruggere un'azienda.
Daenyth,

31
@Daenyth: Francamente con cinquecento rami personalizzati sono stupito che non lo abbia già fatto. Chi lascia che le cose peggiorino così tanto? lol
Razze di leggerezza in orbita

73
@FernandoTan Sono così, quindi, mi dispiace per te ...
Enderland

20
@FernandoTan: anche a me. :( Forse avresti dovuto fare più domande al colloquio?;) Per essere chiari, il "tu" nella mia risposta è l'organizzazione. È un'astrazione. Non sto cercando di assegnare la colpa alle persone.
Corse di leggerezza in orbita

58
Per prima cosa ottieni maggiori informazioni: lascia che gli sviluppatori facciano una differenza tra la versione corrente e il ramo personalizzato. Quindi almeno sai quali differenze ci sono. Tale elenco ti consente di vedere dove puoi vincere la riduzione più rapida dei rami. Se 50 hanno nomi di campo personalizzati, concentrati su questo e ti farà risparmiare 50 rami. Quindi cerca il prossimo. Potresti anche avere alcuni che non sono ripristinabili, ma almeno l'importo sarà inferiore e non crescerà ulteriormente quando avrai più clienti.
Luc Franken,

93

Avere 500 clienti è un bel problema, se avessi trascorso il tempo in anticipo per evitare questo problema con le filiali, potresti non essere mai stato in grado di rimanere nel trading abbastanza a lungo da ottenere clienti.

In primo luogo, spero che addebiti i tuoi clienti abbastanza da coprire TUTTI i costi di mantenimento delle loro versioni personalizzate. Suppongo che i clienti si aspettino di ottenere nuove versioni senza dover pagare nuovamente le personalizzazioni. Vorrei iniziare trovando tutti i file uguali nel 95% dei tuoi rami. Quel 95% è la parte stabile della tua applicazione.

Quindi, trova tutti i file che hanno solo poche righe diverse tra i rami: prova a introdurre un sistema di configurazione in modo che queste differenze possano essere rimosse. Quindi, ad esempio, anziché avere centinaia di file con etichette di campi di testo diversi, hai 1 file di configurazione che può sovrascrivere qualsiasi etichetta di testo. (Questo non deve essere fatto in una sola volta, basta rendere un'etichetta del campo di testo configurabile la prima volta che un client vuole cambiarla.)

Quindi passa ai problemi più difficili utilizzando il modello di strategia, l'iniezione delle dipendenze, ecc.

Prendi in considerazione l'archiviazione di json nel database piuttosto che l'aggiunta di colonne per i campi propri del client: questo potrebbe funzionare per te se non hai bisogno di cercare questi campi con SQL.

Ogni volta che controlli un file in un ramo, DEVI diffonderlo con main e giustificare ogni singola modifica, incluso lo spazio bianco. Molte modifiche non saranno necessarie e possono essere rimosse prima del check-in. Questo potrebbe dipendere da uno sviluppatore con impostazioni diverse nel proprio editor per la formattazione del codice.

Stai cercando di passare da 500 filiali con molti file diversi, alla maggior parte delle filiali che hanno solo pochi file diversi. Pur facendo abbastanza soldi per vivere.

Potresti avere ancora 500 filiali tra molti anni, ma se sono molto più facili da gestire, allora hai vinto.


Sulla base del commento di br3w5:

  • È possibile prendere ogni classe diversa tra i client
  • Crea una "xxx_baseclass" che definisce tutti i metodi chiamati nella classe dall'esterno
  • Rinominare la classe in modo che xxx sia chiamato xxx_clientName (come sottoclasse di xxx_baseclass)
  • Utilizzare l'iniezione delle dipendenze in modo che venga utilizzata la versione corretta della classe per ciascun client
  • E ora per l'intuizione intelligente br3w5 ha inventato! Utilizzare uno strumento di analisi del codice statico per trovare il codice ora duplicato e spostarlo nella classe base ecc

Fai quanto sopra solo dopo aver ottenuto il grano facile e seguilo prima con alcune classi.


28
+1 per il tentativo di fornire un approccio al problema reale
Ian

35
Ero davvero preoccupato che ti stessi congratulando con te stesso per la tua risposta, fino a quando ho capito che non eri lo stesso @Ian che ha scritto la risposta.
Theron Luhn,

2
Forse dovrebbero usare uno strumento di analisi del codice statico per restringere le parti del codice duplicate (dopo aver identificato tutti i file uguali)
br3w5,

1
Anche la creazione di pacchetti con versione per aiutare il team a rintracciare quale client ha quale versione del codice
br3w5

1
Sembra un modo lungo e tortuoso di dire "solo riformattare il tuo codice"
Roland Tepp,

40

In futuro, fai le domande del test Joel nella tua intervista. Avresti più probabilità di non entrare in un naufragio.


Questo è, ah, come dovremmo dire ... un problema davvero, davvero brutto da avere. Il "tasso di interesse" su questo debito tecnico sarà molto, molto alto. Potrebbe non essere recuperabile ...

Quanto sono integrati con il "core" questi cambiamenti personalizzati? Puoi renderli la loro propria libreria e avere un unico "core" e ogni cliente specifico ha il suo "componente aggiuntivo"?

O sono tutte configurazioni molto minori?

Penso che la soluzione sia una combinazione di:

  • Modifica di tutte le modifiche codificate in elementi basati sulla configurazione. In questo caso ognuno ha la stessa applicazione principale, ma gli utenti (o tu) attivano / disattivano la funzionalità, impostano i nomi, ecc. Secondo necessità
  • Spostando funzionalità / moduli "specifici del cliente" in progetti separati, quindi invece di avere un "progetto" hai un "progetto principale" con i moduli che puoi aggiungere / rimuovere facilmente. In alternativa puoi anche fare queste opzioni di configurazione.

Né sarà banale come se fossi finito qui con oltre 500 clienti, probabilmente non hai fatto alcuna distinzione in questo. Mi aspetto che i tuoi cambiamenti nel separare questo siano un compito che richiede molto tempo.

Ho anche il sospetto che avrai problemi significativi nel separare e classificare facilmente tutto il tuo codice specifico per il cliente.

Se la maggior parte delle modifiche sono specificamente differenze di formulazione, ti suggerisco di leggere domande come questa sulla localizzazione della lingua. Sia che tu stia eseguendo più lingue interamente o solo un sottoinsieme, la soluzione è la stessa. Questo è in particolare PHP e localizzazione.


1
Inoltre, dal momento che questo sarà un compito enorme (per non dire altro), sarà una sfida significativa persino convincere i tuoi manager a dedicare grandi quantità di tempo e denaro a questo problema. @FernandoTan Ci possono essere domande + risposte su questo sito che possono aiutare con questo specifico problema.
Radu Murzea,

10
Quale domanda del test Joel ti avrebbe detto che la società sta abusando delle filiali?
SpaceTrucker,

2
@SpaceTrucker: Beh, "Realizzi build giornaliere?" avrebbe potuto aiutare. Con 500 filiali, probabilmente non le avevano, o avrebbero potuto dire che lo fanno solo per alcune filiali.
sleske,

17

Questo è uno dei peggiori anti-pattern che puoi colpire con qualsiasi VCS.

L'approccio corretto qui è quello di trasformare il codice personalizzato in qualcosa guidato dalla configurazione, e quindi ogni cliente può avere la propria configurazione, sia codificata in un file di configurazione, sia in un database o in qualche altra posizione. È possibile abilitare o disabilitare intere funzionalità, personalizzare l'aspetto delle risposte e così via.

Ciò consente di mantenere un ramo master con il proprio codice di produzione.


3
Se lo fai, fai un favore a te stesso e prova a utilizzare il modello di strategia il più possibile. Ciò renderà molto più facile mantenere il tuo codice che se ti limitassi a farlo if(getFeature(FEATURE_X).isEnabled()).
TMN,

13

Lo scopo dei rami è quello di esplorare una possibile via di sviluppo senza rischiare di interrompere la stabilità del ramo principale. Dovrebbero infine essere ricongiunti in un momento opportuno o essere scartati se portano a un vicolo cieco. Quello che hai non sono così tanti rami, ma piuttosto 500 forcelle dello stesso progetto e cercare di applicare i cambiamenti vitali a tutti loro è un compito sisifo.

Quello che dovresti fare invece è avere il tuo codice core attivo nel suo repository, con i punti di ingresso necessari per modificare il comportamento attraverso la configurazione e per iniettare il comportamento come consentito dalle dipendenze invertite .

Le diverse configurazioni che hai per i client possono quindi semplicemente distinguersi a vicenda da uno stato configurato esternamente (ad esempio un database) o, se necessario, vivere come repository separati, che aggiungono il core come sottomodulo.


6
Hai dimenticato i rami di manutenzione, che sono sostanzialmente l'opposto dei rami che hai descritto nella tua risposta. :)
Corse di leggerezza in orbita

7

Tutte le cose importanti sono state proposte da buone risposte qui. Vorrei aggiungere i miei cinque penny come suggerimento per il processo.

Vorrei suggerirti di risolvere questo problema a lungo o medio termine e di adottare la tua politica, come sviluppare il codice. Cerca di diventare un team di apprendimento flessibile. Se qualcuno ha permesso di avere 500 repository invece di rendere il software configurabile, allora è il momento di chiederti come hai lavorato finora e lo farai da ora in poi.

Che significa:

  1. Chiarire le responsabilità di gestione del cambiamento: se un cliente ha bisogno di alcuni adattamenti, chi lo sta vendendo, chi lo sta permettendo e chi decide come verrà modificato il codice? Dove devono girare le viti se alcune cose devono essere cambiate?
  2. Chiarire il ruolo, chi nella tua squadra è autorizzato a fare nuovi repository e chi no.
  3. Cerca di assicurarti che tutti nel tuo team vedano la necessità di schemi che consentano flessibilità al software.
  4. Chiarisci il tuo strumento di gestione: come fai a sapere rapidamente quale cliente ha quali adozioni di codice. Lo so, una "lista di 500" sembra fastidiosa, ma qui c'è un po 'di "economia emotiva", se vuoi. Se non riesci a capire le modifiche del cliente in breve tempo, ti senti ancora più perso e attratto come se dovessi iniziare un elenco. Quindi, utilizza tale elenco per raggruppare le caratteristiche nel modo in cui le risposte delle altre persone qui ti hanno mostrato:
    • raggruppare i clienti con modifiche minori / importanti
    • raggruppa per modifiche relative al soggetto
    • raggruppa per modifiche facili da unire e modifiche difficili da unire
    • trova gruppi di modifiche uguali apportate a diversi repository (oh sì, ce ne saranno alcuni).
    • forse il più importante per parlare con il tuo manager / investitore: raggruppa per cambiamenti costosi e cambiamenti economici .

Questo non intende in alcun modo creare una cattiva atmosfera di pressione nella tua squadra. Suggerisco piuttosto di chiarire prima questi punti per te stesso e, ovunque tu ne senta il supporto, organizzalo insieme al tuo team. Invita persone amichevoli al tavolo per migliorare tutta la tua esperienza.

Quindi, prova a stabilire una finestra temporale a lungo termine, dove cucini questa cosa su una piccola fiamma. Suggerimento: prova a unire almeno due repository ogni settimana, quindi rimuovine almeno uno . Potresti imparare che spesso, puoi unire più di due rami, man mano che diventi routine e supervisione. In questo modo, in un anno puoi gestire le filiali peggiori (più costose?) E in due anni puoi ridurre questo problema per avere un software chiaramente migliore. Ma non aspettarti di più, poiché alla fine nessuno "avrà tempo" per questo, ma tu sei quello che non lo consentirà più come sei l'architetto del software.

Ecco come proverei a gestirlo se fossi nella tua posizione. Tuttavia, non so come il tuo team accetterà queste cose, come il software lo consente davvero, come sei supportato e anche cosa devi ancora imparare. Sei l'architetto del software - provaci e basta :-)


2
Aspetti positivi su come affrontare le questioni sociali / organizzative in agguato dietro i problemi tecnici. Questo è troppo spesso trascurato.
sleske,

5

In contrapposizione a tutti i no-sayers, supponiamo che il vero bisogno di affari.

(ad esempio, deliverable è il codice sorgente, i clienti provengono dalla stessa linea di business e quindi concorrenti l'uno dall'altro e il tuo modello di business promette di mantenere segreti i loro segreti)

Inoltre, supponiamo che la tua azienda abbia gli strumenti per mantenere tutte le filiali, ovvero manodopera (diciamo 100 sviluppatori dedicati alla fusione, ipotizzando un ritardo di rilascio di 5 giorni; oppure 10 sviluppatori supponendo che il ritardo di rilascio di 50 giorni sia OK), oppure tali prove impressionante automatizzato che unioni automatici sono veramente testati sia per spec nucleo ed estensione spec in ogni ramo, e quindi solo le modifiche che non fondono "pulito" richiedono l'intervento umano. Se i tuoi clienti pagano non solo per le personalizzazioni ma per la loro manutenzione, questo può essere un modello di business valido.

La mia domanda (e no-sayers) è: hai una persona dedicata responsabile della consegna a ciascun cliente? Se siete, diciamo, un'azienda da 10.000 persone, potrebbe essere il caso.

Questo potrebbe essere gestito dall'architettura del plugin in alcuni casi, diciamo che il tuo core è trunk, i plugin potrebbero essere conservati in trunk o rami e la configurazione per ogni cliente è un file con un nome univoco o è conservata nella filiale del cliente.

I plugin possono essere caricati in fase di esecuzione o integrati in fase di compilazione.

Veramente molti progetti sono fatti in questo modo, fondamentalmente si applica lo stesso problema: semplici modifiche di base sono banali da integrare, le modifiche ai conflitti devono essere ripristinate o sono necessarie modifiche a molti plugin.

Ci sono casi in cui i plugin non sono abbastanza buoni, è allora che è necessario modificare così tanti interni del core che il conteggio dell'interfaccia del plugin diventa troppo grande da gestire.

Idealmente, questo dovrebbe essere gestito da una programmazione orientata all'aspetto , dove trunk è un codice core e rami sono aspetti (ovvero un codice extra e istruzioni su come collegare gli extra al core)

Un semplice esempio, è possibile specificare che la personalizzazione fooviene eseguita prima o dopo il core klass.fooo che la sostituisce o che la avvolge e può modificare l'input o l'output.

Ci sono moltissime biblioteche per questo, tuttavia il problema dei conflitti di unione non scompare: le fusioni pulite sono gestite dall'AOP e i conflitti necessitano ancora dell'intervento umano.

Infine, tali attività devono veramente occuparsi della manutenzione delle filiali , vale a dire, la caratteristica X specifica del cliente è così comune che è più economico spostarla nel core, anche se non tutti i clienti la stanno pagando?


3

Non stai risolvendo la causa principale della malattia osservando il sintomo. L'uso di un approccio di "gestione del codice" è sintomatico, ma non risolverà le cose a lungo termine. La causa principale è la mancanza di capacità, funzionalità, estensioni e varianti del prodotto "ben gestite".

Il tuo codice "personalizzato" non rappresenta altro che estensioni delle funzionalità e delle capacità del prodotto e cambiamenti del campo dati in altri.

Quanto sono estese le funzionalità personalizzate, quanto diverse, quanto contestualmente simili o meno giocheranno molto nella "sanificazione" della base di codice del prodotto.

Più che come codice e versione, questo è un luogo in cui la gestione del prodotto, l'architettura del prodotto e l'architettura dei dati entrano in gioco. Sul serio.

Perché, alla fine della giornata, il codice non è altro che la tua offerta di funzioni / servizi aziendali e di prodotto ai tuoi clienti. Questo è ciò per cui la tua azienda viene pagata.

Ottenere una migliore comprensione di ciò deve venire dal punto di vista delle "capacità" e non dal punto di vista del codice.

Tu, la tua azienda e il prodotto non potete essere tutto per tutti. Ora che hai una discreta base di entrate di 500 clienti, è tempo di realizzare ciò che intendi essere.

E se stai offrendo diverse cose, avrebbe senso modulare le capacità dei tuoi prodotti in modo organizzato.

Quanto saranno larghi e profondi i tuoi prodotti? Altrimenti ciò porterà a problemi di "qualità del servizio" e "diluizione e frammentazione del prodotto" man mano che si scende.

Sarai un CRM o ERP o elaborazione / spedizione ordini o Microsoft Excel?

Le estensioni esistenti devono eseguire il rollup e l'armonizzazione, in modo che un grande software di grandi dimensioni inserisca e unisca i prodotti acquisiti da una startup.

Sarà necessario disporre di una solida gestione del prodotto e dell'architettura dei dati per mappare quanto segue:

  • Filiale principale, funzionalità del prodotto e base delle funzionalità
  • Funzionalità, tipi e varianti di estensioni personalizzate
  • Significato e variazione dei "campi personalizzati"

..per creare una road map di assimilazione e armonizzazione di tutti questi thread / rami di prodotti sciolti nel grande contesto della tua applicazione principale.

PS: connettiti con me, conosco una persona che può aiutarti a risolvere questo problema :)


-5

Posso riferirmi a questo. Ho preso molti progetti. In effetti, il 90% del nostro lavoro di sviluppo sta sistemando queste cose. Non tutti sono perfetti, quindi ti suggerisco di utilizzare il controllo versione nel modo corretto e dove ti trovi, se possibile puoi fare quanto segue.

  • Da ora in poi, quando un cliente richiede un aggiornamento, spostalo nel nuovo repository biforcato.
  • Se vuoi unirli al master, fallo come prima cosa e risolvi i conflitti.
  • Quindi gestisci i loro problemi e gli sprint con il loro repository e mantieni quelli in master che vuoi avviare in master. Ciò potrebbe mettere a dura prova i cicli di rilascio, ma ciò ti farà risparmiare nel tempo.
  • Mantenere un ramo principale del repository principale per i nuovi clienti e il repository principale dovrebbe avere solo quei rami su cui si sta lavorando per cose future. I rami legacy possono quindi essere eliminati una volta migrati nei repository dei clienti.

Ho importato personalmente un repository da GitHub con 40 filiali in Bitbucket e creato 40 repository. Ci sono volute solo quattro ore. Queste sono state le variazioni del tema di WordPress, quindi push and pull è stato rapido.

Ci sono molte ragioni per "non farlo bene la prima volta", e penso che quelli che li accettano rapidamente e passano a "farlo bene questa volta" avrebbero sempre successo.


16
In che modo più repository renderebbero più semplice la manutenzione?
Mathletics,

In alcuni casi come il nostro, i clienti devono avere accesso a ciascun repository e gestire i propri problemi quando diventa una soluzione personalizzata, quindi hanno un repository che semplifica la gestione e, come ho già detto, si tratta di variazioni di temi wordpress che ha funzionato bene. Potrebbe non funzionare in molti casi.
Farrukh Subhani,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.