Negli ultimi anni, stiamo lentamente passando al codice scritto progressivamente migliore, alcuni piccoli passi alla volta. Stiamo finalmente iniziando a passare a qualcosa che almeno assomiglia a SOLID, ma non siamo ancora del tutto lì. Da quando è stato effettuato il passaggio, una delle maggiori lamentele da parte degli sviluppatori è che non riescono a sopportare la revisione tra pari e attraversare dozzine e dozzine di file in cui in precedenza ogni attività richiedeva allo sviluppatore di toccare solo 5-10 file.
Prima di iniziare a fare il passaggio, la nostra architettura era organizzata in modo simile al seguente (garantito, con uno o due ordini di grandezza in più file):
Solution
- Business
-- AccountLogic
-- DocumentLogic
-- UsersLogic
- Entities (Database entities)
- Models (Domain Models)
- Repositories
-- AccountRepo
-- DocumentRepo
-- UserRepo
- ViewModels
-- AccountViewModel
-- DocumentViewModel
-- UserViewModel
- UI
Per quanto riguarda i file, tutto era incredibilmente lineare e compatto. Ovviamente c'era un sacco di duplicazione del codice, accoppiamento stretto e mal di testa, tuttavia, tutti potevano attraversarlo e capirlo. I principianti completi, persone che non avevano mai aperto Visual Studio, potevano capirlo in poche settimane. La mancanza di complessità complessiva dei file rende relativamente semplice per gli sviluppatori alle prime armi e i nuovi assunti iniziare a contribuire senza troppo tempo di accelerazione. Ma questo è praticamente dove qualsiasi beneficio dello stile di codice esce dalla finestra.
Approvo con tutto il cuore ogni tentativo che facciamo per migliorare la nostra base di codice, ma è molto comune ottenere un respingimento dal resto del team su enormi cambiamenti di paradigma come questo. Un paio dei maggiori punti critici attualmente sono:
- Test unitari
- Conteggio delle classi
- Complessità della Peer Review
I test unitari sono stati una vendita incredibilmente difficile per il team in quanto tutti credono di essere una perdita di tempo e che sono in grado di gestire il loro codice molto più rapidamente nel suo complesso rispetto a ogni singolo pezzo. L'uso dei test unitari come supporto per SOLID è stato per lo più inutile ed è diventato per lo più uno scherzo a questo punto.
Il conteggio delle classi è probabilmente il maggiore ostacolo da superare. Le attività che in precedenza richiedevano 5-10 file ora possono richiedere 70-100! Mentre ciascuno di questi file ha uno scopo distinto, il volume di file puro può essere travolgente. La risposta del team è stata principalmente gemiti e grattarsi la testa. In precedenza un'attività poteva richiedere uno o due repository, un modello o due, un livello logico e un metodo controller.
Ora, per creare una semplice applicazione di salvataggio dei file, hai una classe per verificare se il file esiste già, una classe per scrivere i metadati, una classe da astrarre in DateTime.Now
modo da poter iniettare i tempi per i test delle unità, interfacce per ogni file contenente logica, file per contenere unit test per ogni classe là fuori e uno o più file per aggiungere tutto al contenitore DI.
Per applicazioni di piccole e medie dimensioni, SOLID è una vendita super facile. Tutti vedono il vantaggio e la facilità di manutenibilità. Tuttavia, non stanno vedendo una proposta di buon valore per SOLID su applicazioni su larga scala. Quindi sto cercando di trovare modi per migliorare l'organizzazione e la gestione per farci superare i problemi crescenti.
Ho pensato di dare un esempio un po 'più forte del volume del file basato su un'attività completata di recente. Mi è stato assegnato il compito di implementare alcune funzionalità in uno dei nostri più recenti microservizi per ricevere una richiesta di sincronizzazione dei file. Quando viene ricevuta la richiesta, il servizio esegue una serie di ricerche e controlli e infine salva il documento su un'unità di rete, nonché su 2 tabelle di database separate.
Per salvare il documento sull'unità di rete, avevo bisogno di alcune classi specifiche:
- IBasePathProvider
-- string GetBasePath() // returns the network path to store files
-- string GetPatientFolderName() // gets the name of the folder where patient files are stored
- BasePathProvider // provides an implementation of IBasePathProvider
- BasePathProviderTests // ensures we're getting what we expect
- IUniqueFilenameProvider
-- string GetFilename(string path, string fileType);
- UniqueFilenameProvider // performs some filesystem lookups to get a unique filename
- UniqueFilenameProviderTests
- INewGuidProvider // allows me to inject guids to simulate collisions during unit tests
-- Guid NewGuid()
- NewGuidProvider
- NewGuidProviderTests
- IFileExtensionCombiner // requests may come in a variety of ways, need to ensure extensions are properly appended.
- FileExtensionCombiner
- FileExtensionCombinerTests
- IPatientFileWriter
-- Task SaveFileAsync(string path, byte[] file, string fileType)
-- Task SaveFileAsync(FilePushRequest request)
- PatientFileWriter
- PatientFileWriterTests
Quindi sono in totale 15 classi (esclusi POCO e ponteggi) per eseguire un salvataggio abbastanza semplice. Questo numero è aumentato in modo significativo quando avevo bisogno di creare POCO per rappresentare entità in alcuni sistemi, creato alcuni repository per comunicare con sistemi di terze parti incompatibili con i nostri altri ORM e costruito metodi logici per gestire la complessità di determinate operazioni.