Come organizzi un software altamente personalizzato?

27

Sto lavorando a un progetto software di grandi dimensioni che è altamente personalizzato per vari clienti in tutto il mondo. Ciò significa che abbiamo forse l'80% del codice che è comune tra i vari clienti, ma anche un sacco di codice che deve cambiare da un cliente all'altro. In passato abbiamo fatto il nostro sviluppo in repository separati (SVN) e quando è stato avviato un nuovo progetto (abbiamo pochi ma grandi clienti) abbiamo creato un altro repository basato su qualunque progetto passato abbia la migliore base di codice per le nostre esigenze. Questo ha funzionato in passato, ma abbiamo riscontrato diversi problemi:

  • I Bug che sono corretti in un repository non sono corretti in altri repository. Questo potrebbe essere un problema di organizzazione, ma trovo difficile correggere e correggere un bug in 5 diversi repository, tenendo presente che il team che gestisce questo repository potrebbe trovarsi in un'altra parte del mondo e non abbiamo il loro ambiente di test , né conoscono il loro programma o quali requisiti hanno (un "bug" in un paese potrebbe essere una "funzione" in un altro).
  • Funzionalità e miglioramenti apportati per un progetto, che potrebbero essere utili anche per un altro progetto sono persi o se vengono utilizzati in un altro progetto spesso causano grossi grattacapi che li uniscono da un codice base all'altro (poiché entrambi i rami potrebbero sono stati sviluppati indipendentemente per un anno).
  • Rifiniture e miglioramenti del codice fatti in un ramo di sviluppo sono persi o causano più danni che benefici se devi unire tutte queste modifiche tra i rami.

Stiamo ora discutendo su come risolvere questi problemi e finora abbiamo trovato le seguenti idee su come risolvere questo problema:

  1. Mantieni lo sviluppo in rami separati ma organizzandoti meglio con un repository centrale in cui le correzioni di errori generali vengono unite e tutti i progetti uniscono le modifiche da questo repository centrale a quelle su un normale ( base giornaliera). Ciò richiede una grande disciplina e un grande sforzo per fondersi tra le filiali. Quindi non sono convinto che funzionerà e possiamo mantenere questa disciplina, soprattutto quando la pressione del tempo si fa sentire.

  2. Abbandona i rami di sviluppo separati e disponi di un repository di codice centrale in cui tutto il nostro codice vive e realizza la nostra personalizzazione grazie a moduli e opzioni di configurazione collegabili. Stiamo già utilizzando i contenitori di Dependency Injection per risolvere le dipendenze nel nostro codice e stiamo seguendo lo schema MVVM nella maggior parte del nostro codice per separare in modo pulito la logica di business dall'interfaccia utente.

Il secondo approccio sembra essere più elegante, ma abbiamo molti problemi irrisolti in questo approccio. Ad esempio: come gestire le modifiche / aggiunte nel modello / database. Stiamo usando .NET con Entity Framework per avere entità strongmente tipizzate. Non vedo come possiamo gestire le proprietà che sono richieste per un cliente ma inutili per un altro cliente senza ingombrare il nostro modello di dati. Stiamo pensando di risolverlo nel database utilizzando le tabelle satellite (con una tabella separata in cui le nostre colonne aggiuntive per un'entità specifica vivono con una mappatura 1: 1 all'entità originale), ma questo è solo il database. Come gestisci questo codice? Il nostro modello di dati risiede in una biblioteca centrale che non saremmo in grado di estendere per ogni cliente utilizzando questo approccio.

Sono sicuro che non siamo l'unica squadra alle prese con questo problema e sono scioccato nel trovare così poco materiale sull'argomento.

Quindi le mie domande sono le seguenti:

  1. Che esperienza hai con un software altamente personalizzato, quale approccio hai scelto e come ha funzionato per te?
  2. Quale approccio consigli e perché? C'è un approccio migliore?
  3. Ci sono buoni libri o articoli sull'argomento che puoi raccomandare?
  4. Hai raccomandazioni specifiche per il nostro ambiente tecnico (.NET, Entity Framework, WPF, DI)?

Modifica

Grazie per tutti i suggerimenti. La maggior parte delle idee corrisponde a quelle che avevamo già nel nostro team, ma è davvero utile vedere l'esperienza che hai avuto con loro e suggerimenti per migliorarle.

Non sono ancora sicuro in che direzione andremo e non prenderò la decisione (da solo), ma passerò questo nella mia squadra e sono sicuro che sarà utile.

Al momento il tenore sembra essere un unico repository utilizzando vari moduli specifici del cliente. Non sono sicuro che la nostra architettura sia all'altezza di questo o di quanto dobbiamo investire per adattarlo, quindi alcune cose potrebbero vivere in repository separati per un po ', ma penso che sia l'unica soluzione a lungo termine che funzionerà.

Quindi, grazie ancora per tutte le risposte!

    
posta aKzenT 10.08.2012 - 13:57
fonte

9 risposte

9

Sembra che il problema fondamentale non sia solo la manutenzione del repository di codice, ma una mancanza di un'architettura adatta .

  1. Qual è il nucleo / essenza del sistema, che sarà sempre condiviso da tutti i sistemi?
  2. Quali miglioramenti / deviazioni sono richiesti da ciascun cliente?

Un framework o una libreria standard racchiude il primo, mentre il secondo sarebbe implementato come add-on (plugin, sottoclassi, DI, qualsiasi cosa abbia senso per la struttura del codice).

Probabilmente anche un sistema di controllo del codice sorgente che gestisce i rami e lo sviluppo distribuito; Sono un fan di Mercurial, altri preferiscono Git. Il framework sarebbe il ramo principale, ogni sistema personalizzato sarebbe sub-branch, per esempio.

Le tecnologie specifiche utilizzate per implementare il sistema (.NET, WPF, qualunque cosa) sono in gran parte poco importanti.

Ottenere questo è non facile , ma è fondamentale per la sostenibilità a lungo termine. E naturalmente più a lungo aspetti, maggiore sarà il debito tecnico con cui dovrai confrontarti.

Puoi trovare utile il libro Architettura software: principi e modelli organizzativi .

Buona fortuna!

    
risposta data 10.08.2012 - 21:37
fonte
11

Una società per cui ho lavorato ha avuto lo stesso problema e l'approccio per affrontare il problema è stato questo: è stato creato un quadro comune per tutti i nuovi progetti; questo include tutte le cose che devono essere le stesse in ogni progetto. Per esempio. modulo di generazione di strumenti, esportazione in Excel, registrazione. È stato fatto uno sforzo per assicurarsi che questo quadro comune sia migliorato solo (quando un nuovo progetto ha bisogno di nuove funzionalità), ma non è mai stato biforcato.

Sulla base di tale framework, il codice specifico del cliente è stato mantenuto in repository separati. Quando è utile o necessario, correzioni di bug e miglioramenti sono copiati tra i progetti (con tutti gli avvertimenti descritti nella domanda). Tuttavia, miglioramenti utili a livello globale entrano nel quadro comune.

Avere tutto in una base di codice comune per tutti i clienti ha alcuni vantaggi, ma d'altra parte, leggere il codice diventa difficile quando ci sono innumerevoli if s per far sì che il programma si comporti in modo diverso per ogni cliente.

EDIT: un aneddoto per rendere questo più comprensibile:

The domain of that company is warehouse management, and one task of a warehouse management system is to find a free storage location for incoming goods. Sounds easy, but in practice, a lot of constraints and strategies have to be observed.

At one point in time, management asked a programmer to make a flexible, parameterisable module to find storage locations, which implemented several different strategies and should have been used in all subsequent projects. The noble effort resulted in a complex module, which was very difficult to understand and maintain. In the next project, the project lead couldn't figure out how to make it work in that warehouse, and the developer of said module was gone, so he eventually ignored it and wrote a custom algorithm for that task.

A few years later, the layout of the warehouse where this module was originally used changed, and the module with all its flexibility didn't match the new requirements; so I replaced it with a custom algorithm there, too.

I know LOC is not a good measurement, but anyway: the size of "flexible" module was ~3000 LOC (PL/SQL), while a custom module for the same task takes ~100..250 LOC. Therefore, trying to be flexible extremely increased the size of the code base, without gaining the reusability we had hoped for.

    
risposta data 10.08.2012 - 14:43
fonte
4

Ho lavorato per molti anni in una domanda di amministrazione delle pensioni che aveva problemi simili. I piani pensionistici sono molto diversi tra le aziende e richiedono conoscenze altamente specializzate per l'implementazione di logiche e report di calcolo e anche una progettazione dei dati molto diversa. Posso solo dare una breve descrizione di una parte dell'architettura, ma forse darà un'idea sufficiente.

Abbiamo avuto due team separati: un team di sviluppo di base, che era responsabile del codice di sistema principale (che sarebbe il tuo codice condiviso dell'80% sopra) e un'implementazione team, che aveva esperienza nel settore dei sistemi pensionistici, ed era responsabile dell'apprendimento dei requisiti del cliente e degli script e rapporti di codifica per il cliente.

Abbiamo avuto tutte le nostre tabelle definite da Xml (questo prima del tempo in cui i framework di entità erano testati nel tempo e comuni). Il team di implementazione progetterebbe tutte le tabelle in Xml e all'applicazione principale potrebbe essere richiesto di generare tutte le tabelle in Xml. Sono stati inoltre associati file di script VB, Crystal Reports, documenti Word ecc. Per ciascun client. (C'era anche un modello di ereditarietà incorporato nell'Xml per abilitare il riutilizzo di altre implementazioni).

L'applicazione principale (un'applicazione per tutti i client), memorizzava nella cache tutte le cose specifiche del client quando veniva una richiesta per quel client e generava un oggetto dati comune (un po 'come un set di record ADO remoto), che poteva essere serializzato e passato in giro.

Questo modello di dati è meno fluido degli oggetti entità / dominio, ma è altamente flessibile, universale e può essere elaborato da un set di codice principale. Forse nel tuo caso potresti definire i tuoi oggetti di entità di base con solo i campi comuni e disporre di un dizionario aggiuntivo per i campi personalizzati (aggiungi qualche tipo di set di descrittori di dati all'oggetto entità in modo che contenga metadati per i campi personalizzati. )

Disponevamo di repository separati per il codice di sistema principale e per il codice di implementazione.

Il nostro sistema principale in realtà aveva una logica aziendale molto piccola, a parte alcuni moduli di calcolo comuni molto standard. Il sistema principale funzionava come: generatore di schermo, runner di script, generatore di report, accesso ai dati e livello di trasporto.

Segmentare la logica di base e la logica personalizzata è una sfida difficile. Tuttavia, abbiamo sempre ritenuto che fosse meglio avere un sistema principale che eseguisse più client, anziché più copie del sistema in esecuzione per ciascun client.

    
risposta data 10.08.2012 - 16:42
fonte
4

Uno dei progetti su cui ho lavorato ha supportato più piattaforme (più di 5) su un gran numero di versioni di prodotti. Molte delle sfide che stai descrivendo sono state le cose che abbiamo affrontato, anche se in un modo leggermente diverso. Avevamo un DB proprietario, quindi non avevamo gli stessi tipi di problemi in quell'arena.

La nostra struttura era simile alla tua, ma avevamo un unico repository per il nostro codice. Il codice specifico della piattaforma è stato inserito nelle proprie cartelle di progetto all'interno dell'albero del codice. Codice comune vissuto all'interno dell'albero in base al livello a cui apparteneva.

Avevamo una compilazione condizionale, basata sulla piattaforma in costruzione. Mantenerlo era una specie di sofferenza, ma doveva essere fatto solo quando nuovi moduli sono stati aggiunti al livello specifico della piattaforma.

Avere tutto il codice in un unico repository ha reso facile per noi fare correzioni di bug su più piattaforme e rilasci contemporaneamente. Avevamo un ambiente di compilazione automatizzato per tutte le piattaforme che serviva da blocco nel caso in cui il nuovo codice rompesse una presunta piattaforma non correlata.

Abbiamo cercato di scoraggiarlo, ma ci sarebbero stati casi in cui una piattaforma necessitava di una correzione basata su un bug specifico della piattaforma che era in codice altrimenti comune. Se potessimo scavalcare condizionatamente la compilazione senza far sembrare il modulo fugace, lo faremo prima. In caso contrario, sposteremmo il modulo fuori dal territorio comune e lo inseriremo in una piattaforma specifica.

Per il database, avevamo alcune tabelle con colonne / modifiche specifiche della piattaforma. Ci assicureremmo che ogni versione della piattaforma della piattaforma soddisfacesse un livello base di funzionalità in modo che il codice comune potesse farvi riferimento senza preoccuparsi delle dipendenze della piattaforma. Le query / manipolazioni specifiche della piattaforma sono state inserite nei livelli del progetto della piattaforma.

Quindi, per rispondere alle tue domande:

  1. Un sacco, e quella era una delle migliori squadre con cui ho lavorato. Codebase in quel momento era intorno a 1M loc. Non sono riuscito a scegliere l'approccio, ma ha funzionato abbastanza bene. Anche con il senno di poi, non ho visto un modo migliore di gestire le cose.
  2. Raccomando il secondo approccio suggerito con le sfumature che menziono nella mia risposta.
  3. Non ci sono libri a cui possa pensare, ma vorrei iniziare la ricerca sullo sviluppo multipiattaforma.
  4. Istituire una governance strong. È la chiave per assicurarsi che i tuoi standard di codifica siano seguiti. Seguire quegli standard è l'unico modo per mantenere le cose gestibili e mantenibili. Abbiamo avuto la nostra parte di richieste appassionate per rompere il modello che stavamo seguendo, ma nessuno di questi appelli ha mai influenzato l'intero team di sviluppo senior.
risposta data 10.08.2012 - 16:43
fonte
2

Ho lavorato su un sistema più piccolo (20 kloc), e ho trovato che DI e la configurazione sono entrambi ottimi modi per gestire le differenze tra i client, ma non abbastanza da evitare di forgiare il sistema. Il database è suddiviso tra una parte specifica dell'applicazione, che ha uno schema fisso e la parte dipendente dal client, che è definita tramite un documento di configurazione XML personalizzato.

Abbiamo mantenuto una singola filiale in mercurial configurata come se fosse consegnabile, ma brandizzata e configurata per un client fittizio. Le correzioni dei bug sono state integrate in quel progetto e il nuovo sviluppo delle funzionalità di base si verifica solo lì. I rilasci ai clienti effettivi sono ramificazioni, archiviati nei propri repository. Teniamo traccia delle grandi modifiche apportate al codice tramite numeri di versione scritti manualmente e tracciamo correzioni di errori utilizzando i numeri di commit.

    
risposta data 10.08.2012 - 16:57
fonte
2

Ho paura di non avere esperienza diretta del problema che descrivi, ma ho alcuni commenti.

La seconda opzione, quella di riunire il codice in un repository centrale (il più possibile praticabile) e l'architettura per la personalizzazione (sempre, per quanto possibile) è quasi sicuramente la strada da percorrere a lungo termine.

Il problema è come prevedi di arrivarci e quanto tempo ci vorrà.

In questa situazione, probabilmente è OK (temporaneamente) avere più di una copia dell'applicazione nel repository alla volta.

Questo ti consentirà di passare gradualmente a un'architettura che supporta direttamente la personalizzazione senza doverlo fare in un colpo solo.

    
risposta data 11.08.2012 - 01:31
fonte
2

The second approach seems to be more elegant, but we have many unsolved problems in this approach.

Sono sicuro che qualcuno di questi problemi possa essere risolto, uno dopo l'altro. Se rimani bloccato, chiedi qui o su SO del problema specifico.

Come altri hanno sottolineato, avere una base di codice centrale / un repository è l'opzione che si dovrebbe preferire. Provo a rispondere alla tua domanda di esempio.

For example: how do handle changes/additions in your model/database. We are using .NET with Entity Framework to have strongly typed entities. I don't see how we can handle properties which are required for one customer but useless for another customer without cluttering our data model.

Ci sono alcune possibilità, tutte che ho visto nei sistemi del mondo reale. Quale scegliere dipende dalla tua situazione:

  • vivere con il disordine in una certa misura
  • introducono tabelle "CustomAttributes" (che descrivono nomi e tipi) e "CustomAttributeValues" (per i valori, ad esempio memorizzati come rappresentazione di stringa, anche se sono numeri). Ciò consentirà di aggiungere tali attributi al momento dell'installazione o del tempo di esecuzione, con valori individuali per ciascun cliente. Non insistere sul fatto che ogni attributo personalizzato sia modellato "visibilmente" nel tuo modello dati.

  • ora dovrebbe essere chiaro come usarlo nel codice: avere solo un codice generale per accedere a quelle tabelle e un codice individuale (magari in una DLL plug-in separata, che dipende da te) per interpretare quegli attributi correttamente

  • un'altra alternativa consiste nel fornire a ciascuna tabella delle entità un grande campo stringa in cui è possibile aggiungere una stringa XML individuale.
  • cerca di generalizzare alcuni concetti, in modo che possano essere riutilizzati più facilmente tra diversi clienti. Raccomando il libro di Martin Fowler " Modelli di analisi ". Sebbene questo libro non riguardi la personalizzazione del software pre, potrebbe essere utile anche per te.

E per codice specifico: puoi anche provare a introdurre un linguaggio di scripting nel tuo prodotto, specialmente per aggiungere script specifici del cliente. In questo modo non solo crei una linea chiara tra il tuo codice e il codice specifico del cliente, puoi anche consentire ai tuoi clienti di personalizzare il sistema in qualche misura da solo.

    
risposta data 12.08.2012 - 08:54
fonte
0

Ho creato solo una di queste applicazioni. Direi che il 90% delle unità vendute sono state vendute così come sono, senza modifiche. Ogni cliente aveva la propria pelle personalizzata e abbiamo fornito il sistema all'interno di quella skin. Quando è arrivata una mod che ha interessato le sezioni principali, abbiamo provato a utilizzare IF branching . Quando è arrivata la mod # 2 per la stessa sezione, passiamo alla logica CASE consentita per l'espansione futura. Questo sembrava gestire la maggior parte delle richieste minori.

Altre eventuali richieste personalizzate minori sono state gestite implementando la logica Case.

Se le mod erano due radicali, abbiamo costruito un clone (include separato) e avvolto un CASE attorno ad esso per includere il diverso modulo.

Correzioni di bug e modifiche sul core hanno riguardato tutti gli utenti. Abbiamo testato attentamente lo sviluppo prima di andare in produzione. Abbiamo sempre inviato notifiche via email che hanno accompagnato qualsiasi modifica e MAI, MAI, MAI inviato modifiche di produzione il venerdì ... MAI.

Il nostro ambiente era classico ASP e SQL Server. NON eravamo un negozio di spaghetti code ... Tutto era modulare usando Include, Subroutine e funzioni.

    
risposta data 11.08.2012 - 02:09
fonte
-1

Quando mi viene chiesto di avviare lo sviluppo di B che condivide l'80% di funzionalità con A, lo farò:

  1. Clona A e modifica.
  2. Estrai la funzionalità condivisa sia da A che da B in C che useranno.
  3. Rendi sufficientemente configurabile per soddisfare i bisogni sia di B che di se stesso (quindi B è incorporato in A).

Hai scelto 1, e non sembra adattarsi bene alla tua situazione. La tua missione è di prevedere quale tra 2 e 3 è più adatto.

    
risposta data 10.08.2012 - 16:25
fonte