Au cours des dernières années, nous avons lentement commencé à adopter un code de mieux en mieux écrit, petit à petit. Nous commençons enfin à passer à quelque chose qui ressemble au moins à SOLID, mais nous n'en sommes pas encore là. Depuis le passage à l'acte, l'un des plus gros griefs des développeurs est qu'ils ne supportent pas l'examen par des pairs et la traversée de dizaines et de dizaines de fichiers, alors que chaque tâche ne nécessitait auparavant que le développeur qui manipule 5 à 10 fichiers.
Avant de commencer à effectuer le changement, notre architecture était organisée de la manière suivante (accordée, avec un ou deux ordres de grandeur en plus):
Solution
- Business
-- AccountLogic
-- DocumentLogic
-- UsersLogic
- Entities (Database entities)
- Models (Domain Models)
- Repositories
-- AccountRepo
-- DocumentRepo
-- UserRepo
- ViewModels
-- AccountViewModel
-- DocumentViewModel
-- UserViewModel
- UI
En ce qui concerne les fichiers, tout était incroyablement linéaire et compact. Il y avait évidemment beaucoup de duplication de code, de couplage étroit et de maux de tête, cependant, tout le monde pouvait le parcourir et le résoudre. Les novices complets, ceux qui n’avaient jamais autant ouvert Visual Studio, pourraient le comprendre en quelques semaines seulement. L'absence de complexité générale des fichiers rend relativement simple la tâche des développeurs novices et des nouvelles recrues de commencer à contribuer sans trop de temps de mise en œuvre. Mais c’est à peu près à ce niveau que les avantages du style de code s’échappent.
J'approuve sans réserve toutes les tentatives que nous faisons pour améliorer notre base de code, mais il est très courant que le reste de l'équipe répugne à réagir à des changements de paradigme aussi importants que celui-ci. Quelques-uns des plus gros points d'achoppement sont actuellement:
- Tests unitaires
- Nombre de classe
- Complexité de l'examen par les pairs
Les tests unitaires ont été incroyablement difficiles à vendre à l’équipe car ils pensent tous qu’ils perdent du temps et qu’ils sont capables de tester leur code beaucoup plus rapidement dans son ensemble que pour chaque élément individuellement. L'utilisation de tests unitaires comme une approbation de SOLID a généralement été vaine et est devenue une blague à ce stade-ci.
Le nombre de classes est probablement le plus gros obstacle à surmonter. Les tâches qui prenaient auparavant 5 à 10 fichiers peuvent maintenant en prendre 70 à 100! Bien que chacun de ces fichiers remplisse un objectif spécifique, leur volume peut être accablant. La réponse de l'équipe a été principalement des gémissements et des maux de tête. Auparavant, une tâche pouvait nécessiter un ou deux référentiels, un modèle ou deux, une couche logique et une méthode de contrôleur.
Maintenant, pour construire une simple application de sauvegarde de fichier, vous avez une classe pour vérifier si le fichier existe déjà, une classe pour écrire les métadonnées, une classe pour l’abstraction DateTime.Now
afin que vous puissiez injecter du temps pour les tests unitaires, des interfaces pour chaque fichier contenant de la logique, des fichiers. contenir des tests unitaires pour chaque classe et un ou plusieurs fichiers pour tout ajouter à votre conteneur DI.
SOLID est très facile à vendre pour les applications de petite à moyenne taille. Tout le monde voit les avantages et la facilité de maintenance. Cependant, ils ne voient tout simplement pas une bonne proposition de valeur pour SOLID dans les applications à très grande échelle. J'essaie donc de trouver des moyens d'améliorer l'organisation et la gestion pour nous permettre de surmonter les difficultés de croissance.
Je me suis dit que je donnerais un peu plus fort un exemple du volume de fichier basé sur une tâche récemment terminée. On m'a confié la tâche d'implémenter certaines fonctionnalités de l'un de nos nouveaux microservices afin de recevoir une demande de synchronisation de fichiers. Lorsque la demande est reçue, le service effectue une série de recherches et de contrôles, puis enregistre le document sur un lecteur réseau, ainsi que dans 2 tables de base de données distinctes.
Pour enregistrer le document sur le lecteur réseau, j'avais besoin de quelques classes spécifiques:
- 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
Cela fait donc 15 classes au total (à l’exclusion des POCO et des échafaudages) pour effectuer une sauvegarde relativement simple. Ce nombre a considérablement augmenté lorsque j'ai eu besoin de créer des POCO pour représenter des entités dans quelques systèmes, de construire quelques référents pour communiquer avec des systèmes tiers incompatibles avec nos autres ORM et de mettre au point des méthodes logiques pour gérer les subtilités de certaines opérations.