Branches are the wrong solution here. Branches are for when the code
(temporarily) needs to change from the common base.
Questo è fuorviante. I rami funzione (rami temporanei) sono solo uno dei tanti usi per i rami. I rami del cliente potrebbero essere rami di lunga vita, che hanno solo un antenato comune e che potrebbero beneficiare periodicamente degli aggiornamenti degli antenati.
The problem with branches is that changes made to one branch stay on
that one branch until you explicitly merge them onto another branch.
This effectively means that if you have a change in the common code,
you need to apply that change to each 'client-branch' individually,
which is exactly what you don't want.
Il suggerimento di utilizzare un sottomodulo (con codice comune) richiede anche l'aggiornamento esplicito del sottomodulo di ciascun cliente. Con lo svantaggio che quando si usano i sottomoduli non si può scegliere la ciliegina (scegliere quali modifiche si vogliono incorporare), a meno che non si decida di suddividere il sottomodulo - che sarebbe il peggiore di entrambi i mondi. Inoltre, l'uso di sottomoduli ti costringerà a mantenere tutti i codici comuni all'interno di una singola sottocartella, il che potrebbe essere un problema se devi conservare alcuni file (come HTML, ASPX, JSP, ecc.) Nella cartella principale o in una cartella specifica. Se si utilizzano i rami client, è possibile avere codice comune e codice client specifico nelle stesse cartelle (e fintanto che li si mantiene in file separati, l'unione sarà sempre banale).
Si potrebbe facilmente avere un ramo per ogni cliente, e unire i miglioramenti del prodotto in ogni ramo secondo necessità (o anche ribilanciare i rami del cliente, il che porterebbe a una cronologia più pulita, anche se ciò potrebbe portare ad alcuni stati incoerenti nella storia) . Questa risposta spiega i rami specifici del cliente. La principale sfida qui è mantenere la funzionalità principale isolata dalle personalizzazioni. Ciò potrebbe essere ottenuto usando ereditarietà, classi parziali o eventi.
Un semplice esempio: il ramo master potrebbe avere classi base, che ospiteranno la maggior parte del codice, e classi derivate quasi vuote che ospiteranno fondamentalmente le personalizzazioni. Ogni cliente avrebbe il proprio ramo e cambierebbe solo le classi derivate. Quando vengono aggiunte nuove funzionalità al ramo principale (linea di base del prodotto), è possibile unirle nelle filiali del cliente. Le estensioni del cliente potrebbero rompersi (se si utilizza un linguaggio strongmente tipizzato) a causa di modifiche del contratto sulla classe base - e ciò è positivo, è un segno che la personalizzazione dovrebbe essere rivista.
È importante isolare il codice comune dal codice specifico del cliente, in modo che le unioni siano semplici. È una buona idea tenerli in file separati, sia che si tratti di classi diverse (personalizzazione che eredita dal codice comune) sia che si tratti di classi parziali. La funzionalità di base e le personalizzazioni potrebbero anche essere classi completamente estranee, a patto che le si inserisca in qualche codice di avvio (come sottoscrizione di eventi o qualcosa del genere).
Progettando correttamente i metodi (e / o gli eventi di estensione), puoi ottenere un prodotto estensibile abbastanza solido ed evitare l'inferno di manutenzione.