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.
Réponses:
Je pense que vous avez mal compris l’idée d’une responsabilité unique. La responsabilité d'une classe peut être "enregistrer un fichier". Pour ce faire, il peut alors diviser cette responsabilité en une méthode qui vérifie si un fichier existe, une méthode pour écrire des métadonnées, etc. Chacune de ces méthodes a ensuite une responsabilité unique, qui fait partie de la responsabilité globale de la classe.
Une classe abstraite
DateTime.Now
sonne bien. Mais vous n’avez besoin que d’une seule de celles-ci et elle pourrait être regroupée avec d’autres caractéristiques d’environnement dans une seule classe chargée de l’abstraction des caractéristiques de l’environnement. Encore une fois, une seule responsabilité avec plusieurs sous-responsabilités.Vous n'avez pas besoin d '"interfaces pour chaque fichier contenant une logique", vous avez besoin d'interfaces pour les classes ayant des effets secondaires, par exemple les classes qui lisent / écrivent dans des fichiers ou des bases de données; et même dans ce cas, elles ne sont nécessaires que pour les parties publiques de cette fonctionnalité. Ainsi, par exemple, dans le cas où
AccountRepo
vous n’auriez pas besoin d’interfaces, vous n’auriez peut-être besoin que d’une interface pour l’accès réel à la base de données injectée dans ce référentiel.Cela suggère que vous avez également mal compris les tests unitaires. L'unité d'un test unitaire n'est pas une unité de code. Qu'est-ce même qu'une unité de code? Une classe? Une méthode? Une variable? Une seule instruction de machine? Non, le terme "unité" désigne une unité d'isolement, c'est-à-dire un code pouvant s'exécuter indépendamment des autres parties du code. Un simple test permettant de déterminer si un test automatisé est un test unitaire ou non est de savoir si vous pouvez l'exécuter en parallèle avec tous vos autres tests unitaires sans affecter le résultat. Il existe quelques règles de base concernant les tests unitaires, mais c'est votre mesure clé.
Donc, si des parties de votre code peuvent effectivement être testées dans leur ensemble sans en affecter d’autres, faites-le.
Soyez toujours pragmatique et rappelez-vous que tout est un compromis. Plus vous adhérez à DRY, plus votre code doit devenir étroitement couplé. Plus vous introduisez des abstractions, plus le code est facile à tester, mais plus il est difficile à comprendre. Évitez l'idéologie et trouvez un bon équilibre entre l'idéal et la simplicité. C'est là que réside le compromis d'efficacité maximale tant pour le développement que pour la maintenance.
la source
C'est le contraire du principe de responsabilité unique (SRP). Pour arriver à ce point, vous devez avoir divisé vos fonctionnalités de manière très fine, mais ce n’est pas l’objet du PRS: c’est ignorer l’idée clé de la cohésion .
Selon le SRP, les logiciels devraient être divisés en modules selon des lignes définies par leurs raisons éventuelles de modification, de sorte qu’une seule modification de conception puisse être appliquée à un seul module sans nécessiter de modification ailleurs. Un seul "module" dans ce sens peut correspondre à plus d'une classe, mais si une modification nécessite de toucher des dizaines de fichiers, il s'agit de multiples modifications ou bien vous ne réalisez pas correctement la SRP.
Bob Martin, qui a initialement formulé le PÉR, a écrit un blog il y a quelques années pour tenter de clarifier la situation. Il discute assez longuement de ce qu'est une "raison de changer" aux fins du PÉR. Cela vaut la peine d'être lu dans son intégralité, mais parmi les éléments méritant une attention particulière se trouve cette formulation alternative du PÉR:
(c'est moi qui souligne). Le SRP ne consiste pas à diviser les choses en minuscules morceaux possibles. Ce n'est pas un bon design et votre équipe a raison de résister. Cela rend votre base de code plus difficile à mettre à jour et à maintenir. On dirait que vous essayez peut-être de vendre votre équipe dessus en vous basant sur des considérations relatives aux tests unitaires, mais ce serait mettre la charrue avant les boeufs.
De même, le principe de ségrégation des interfaces ne doit pas être considéré comme un absolu. Ce n'est pas plus une raison pour diviser votre code aussi finement que le SRP, et il s'aligne généralement assez bien avec le SRP. Le fait qu'une interface contienne des méthodes que certains clients n'utilisent pas n'est pas une raison pour la décomposer. Vous recherchez encore la cohésion.
De plus, je vous exhorte à ne pas prendre le principe ouvert-fermé ou le principe de substitution de Liskov comme une raison de favoriser les hiérarchies d'héritage profondes. Il n'y a pas de couplage plus étroit qu'une sous-classe avec ses super-classes, et un couplage étroit est un problème de conception. Privilégiez plutôt la composition que l’héritage, là où il est logique de le faire. Cela réduira votre couplage et, par conséquent, le nombre de fichiers qu'une modification particulière peut avoir besoin de toucher, et cela correspond parfaitement à l'inversion de dépendance.
la source
Ceci est un mensonge. Les tâches n'ont jamais pris que 5 à 10 fichiers.
Vous ne résolvez aucune tâche avec moins de 10 fichiers. Pourquoi? Parce que vous utilisez C #. C # est un langage de haut niveau. Vous utilisez plus de 10 fichiers uniquement pour créer hello world.
Oh, bien sûr, vous ne les remarquez pas parce que vous ne les avez pas écrites. Donc, vous ne regardez pas dedans. Vous leur faites confiance.
Le problème n'est pas le nombre de fichiers. C'est que vous avez maintenant tellement de choses en lesquelles vous ne faites pas confiance.
Essayez donc de faire en sorte que ces tests fonctionnent au point qu’une fois qu’ils ont réussi, vous faites confiance à ces fichiers de la même manière que vous le faites en .NET. Faire cela est le point des tests unitaires. Personne ne se soucie du nombre de fichiers. Ils se soucient du nombre de choses auxquelles ils ne peuvent faire confiance.
Le changement est difficile sur les applications à très grande échelle, peu importe ce que vous faites. La meilleure sagesse à appliquer ici ne vient pas de Oncle Bob. Cela vient de Michael Feathers dans son livre Working Effectiveively with Legacy Code.
Ne commencez pas un festival de réécriture. L'ancien code représente des connaissances durement acquises. S'en débarrasser parce qu'il y a des problèmes et qu'il n'est pas exprimé dans le paradigme nouveau et amélioré X consiste simplement à demander un nouvel ensemble de problèmes et à éviter toute connaissance acquise de manière difficile.
Au lieu de cela, trouvez des moyens de rendre testable votre ancien code non vérifiable (le code hérité dans Feathers parle). Dans cette métaphore, le code est comme une chemise. Les grandes pièces sont jointes à des joints naturels qui peuvent être annulés pour séparer le code de la même manière que vous supprimeriez les joints. Faites ceci pour vous permettre d’attacher des "manches" de test qui vous permettent d’isoler le reste du code. Maintenant, lorsque vous créez les manches de test, vous avez confiance dans les manches car vous l'avez fait avec une chemise de travail. (ow, cette métaphore commence à faire mal).
Cette idée découle de l'hypothèse selon laquelle, comme dans la plupart des magasins, les seules exigences à jour sont dans le code en vigueur. Cela vous permet de verrouiller cela dans des tests qui vous permettent d’apporter des modifications au code de travail éprouvé sans qu'il perde tout son statut de travail prouvé. Maintenant, avec cette première vague de tests en place, vous pouvez commencer à apporter des modifications qui permettent de tester le code "hérité" (indéterminé). Vous pouvez être audacieux parce que les tests de coutures vous soutiennent en disant que c'est ce qu'il a toujours fait et que les nouveaux tests montrent que votre code fait réellement ce que vous pensez qu'il fait.
Qu'est-ce que tout cela a à voir avec:
Abstraction.
Vous pouvez me faire détester toute base de code avec de mauvaises abstractions. Une mauvaise abstraction est quelque chose qui me fait regarder à l'intérieur. Ne me surprends pas quand je regarde à l'intérieur. Soyez à peu près ce que j'attendais.
Donnez-moi un bon nom, des tests lisibles (exemples) qui montrent comment utiliser l'interface et l'organiser afin que je puisse trouver des éléments et que cela ne me dérange pas que nous utilisions 10, 100 ou 1000 fichiers.
Vous m'aidez à trouver des choses avec de bons noms descriptifs. Mettez les choses avec les bons noms dans les choses avec les bons noms.
Si vous faites tout cela correctement, vous résumerez les fichiers à l’endroit où terminer une tâche ne dépend que de 3 à 5 autres fichiers. Les fichiers 70-100 sont toujours là. Mais ils se cachent derrière les 3 contre 5. Cela ne fonctionne que si vous faites confiance aux 3 à 5 pour faire ce qu’il faut.
Donc, ce dont vous avez vraiment besoin, c’est le vocabulaire nécessaire pour trouver les bons noms pour toutes ces choses et des tests en lesquels les gens ont confiance, afin qu’ils cessent de fouiller dans tout. Sans cela, vous me rendriez fou aussi.
@Delioth fait un bon point sur les douleurs de croissance. Lorsque vous êtes habitué à ce que la vaisselle soit dans le placard au-dessus du lave-vaisselle, il faut s’y habituer au-dessus du comptoir à petit-déjeuner. Rend certaines choses plus difficiles. Rend certaines choses plus faciles. Mais cela provoque toutes sortes de cauchemars si les gens ne sont pas d’accord pour savoir où vont les plats. Dans une base de code volumineuse, le problème est que vous ne pouvez déplacer qu'une partie de la vaisselle à la fois. Alors maintenant, vous avez des plats à deux endroits. C'est confu. Il est difficile de croire que les plats sont là où ils sont censés être. Si vous voulez éviter cela, la seule chose à faire est de continuer à faire bouger les assiettes.
Le problème, c’est que vous voudriez vraiment savoir si la vaisselle au bar du petit-déjeuner en vaut la peine avant de passer à travers toutes ces bêtises. Eh bien, je ne peux que recommander de faire du camping.
Lorsque vous essayez un nouveau paradigme pour la première fois, vous devez l’appliquer en dernier lieu dans une grande base de code. Cela vaut pour tous les membres de l'équipe. Personne ne devrait croire que SOLID fonctionne, que OOP fonctionne ou que la programmation fonctionnelle fonctionne. Chaque membre de l'équipe doit pouvoir jouer avec la nouvelle idée, quelle qu'elle soit, dans un projet de jouet. Cela leur permet de voir au moins comment cela fonctionne. Cela leur permet de voir ce qui ne va pas bien. Cela leur permet d'apprendre à bien faire les choses avant de causer un grand désordre.
Donner aux gens un endroit où jouer en toute sécurité les aidera à adopter de nouvelles idées et leur donnera l’assurance que les plats pourraient bien fonctionner dans leur nouveau foyer.
la source
AppSettings
tout pour obtenir un URL ou un chemin de fichier.Il semble que votre code ne soit pas très bien découplé et / ou que la taille de votre tâche soit trop importante.
Les modifications de code devraient porter sur 5 à 10 fichiers, sauf si vous effectuez un codemod ou un refactoring à grande échelle. Si un seul changement touche beaucoup de fichiers, cela signifie probablement que vos changements sont en cascade. Certaines abstractions améliorées (plus de responsabilité unique, de ségrégation d’interface, d’inversion de dépendance) devraient aider. Il est également possible que vous ayez trop de responsabilité et que vous utilisiez un peu plus de pragmatisme - des hiérarchies de types plus courtes et plus minces. Cela devrait également rendre le code plus facile à comprendre puisqu'il n'est pas nécessaire de comprendre des dizaines de fichiers pour savoir ce que le code fait.
Cela pourrait également indiquer que votre travail est trop volumineux. Au lieu de "hé, ajoutez cette fonctionnalité" (qui nécessite des modifications de l'interface utilisateur, des modifications de l'interface API, des modifications de l'accès aux données, des modifications de la sécurité et des modifications de test et ...), décomposez-la en plusieurs parties réparables. Cela devient plus facile à analyser et à comprendre, car vous devez définir des contrats décents entre les bits.
Et bien sûr, les tests unitaires aident tout cela. Ils vous obligent à faire des interfaces décentes. Ils vous obligent à rendre votre code suffisamment souple pour injecter les bits nécessaires au test (si c'est difficile à tester, ce sera difficile à réutiliser). Et ils éloignent les gens de la sur-ingénierie, car plus vous ingéniez, plus vous avez à tester.
la source
Je voudrais expliquer quelques-uns des éléments déjà mentionnés ici, mais plus dans la perspective des limites des objets. Si vous suivez quelque chose qui s'apparente à la conception par domaine, vos objets vont probablement représenter des aspects de votre entreprise.
Customer
etOrder
, par exemple, seraient des objets. Maintenant, si je devais deviner en fonction des noms de classe que vous aviez comme point de départ, votreAccountLogic
classe aurait un code pouvant être exécuté pour n’importe quel compte. Dans OO, cependant, chaque classe est censée avoir un contexte et une identité. Vous ne devez pas obtenir d'Account
objet, puis le transmettre à uneAccountLogic
classe et lui demander de modifier l'Account
objet. C'est ce qu'on appelle un modèle anémique et qui ne représente pas très bien OO. Au lieu de cela, votreAccount
classe devrait avoir un comportement, tel queAccount.Close()
ouAccount.UpdateEmail()
, et ces comportements n’affecteraient que cette instance du compte.Maintenant, la façon dont ces comportements sont gérés peut (et dans de nombreux cas devrait être) être déchargée sur des dépendances représentées par des abstractions (c'est-à-dire des interfaces).
Account.UpdateEmail
Vous pouvez par exemple souhaiter mettre à jour une base de données, un fichier ou envoyer un message à un bus de service, etc. Cela pourrait changer dans le futur. Ainsi, votreAccount
classe peut avoir une dépendance sur, par exemple, uneIEmailUpdate
interface pouvant être l'une des nombreuses interfaces implémentées par unAccountRepository
objet. Vous ne voudriez pas passer toute uneIAccountRepository
interface à l'Account
objet car il en ferait probablement trop, comme rechercher et trouver d'autres comptes (n'importe lesquels), auxquels vous ne souhaitez peut-être pas que l'Account
objet ait accès, mais même si vousAccountRepository
pouvez implémenter les deux.IAccountRepository
etIEmailUpdate
interfaces, leAccount
objet n'aurait accès qu'aux petites portions dont il a besoin. Cela vous aide à maintenir le principe de séparation des interfaces .De manière réaliste, comme d'autres personnes l'ont mentionné, si vous faites face à une explosion de classes, il est probable que vous utilisiez le principe SOLID (et, par extension, OO) dans le mauvais sens. SOLID devrait vous aider à simplifier votre code et non à le compliquer. Mais il faut du temps pour vraiment comprendre ce que signifie le PÉR. Le plus important, cependant, est que le fonctionnement de SOLID dépendra beaucoup de votre domaine et de vos contextes liés (un autre terme DDD). Il n'y a pas de solution miracle ni de solution unique.
Une dernière chose que je tiens à souligner auprès des personnes avec qui je travaille: encore une fois, un objet POO doit avoir un comportement et est en fait défini par son comportement et non par ses données. Si votre objet n'a que des propriétés et des champs, il a toujours un comportement, mais probablement pas le comportement que vous aviez prévu. Une propriété publiquement inscriptible / paramétrable sans autre logique d'ensemble implique que le comportement de sa classe contenante est que n'importe qui, n'importe où, pour quelque raison que ce soit et à tout moment, est autorisé à modifier la valeur de cette propriété sans aucune logique métier ou validation nécessaire entre les deux. Ce n'est généralement pas le comportement souhaité par les gens, mais si vous avez un modèle anémique, c'est généralement le comportement que vos classes annoncent à ceux qui l'utilisent.
la source
C'est fou ... mais ces cours ressemblent à quelque chose que j'écrirais moi-même. Alors regardons-les. Ignorons les interfaces et les tests pour le moment.
BasePathProvider
- IMHO, tout projet non-trivial utilisant des fichiers en a besoin. Donc, je suppose, il existe déjà une telle chose et vous pouvez l'utiliser comme tel.UniqueFilenameProvider
- Bien sûr, vous l'avez déjà, n'est-ce pas?NewGuidProvider
- Même cas, à moins que vous ne commenciez à utiliser le GUID.FileExtensionCombiner
- Le même cas.PatientFileWriter
- Je suppose que c'est la classe principale pour la tâche en cours.Pour moi, ça a l'air bien: vous devez écrire une nouvelle classe qui nécessite quatre classes d'assistance. Les quatre classes d’aide semblent très réutilisables, alors je parierais qu’elles sont déjà quelque part dans votre code. Sinon, c'est soit de la malchance (êtes-vous vraiment la personne de votre équipe pour écrire des fichiers et utiliser des GUID ???) ou un autre problème.
En ce qui concerne les classes de test, bien sûr, lorsque vous créez une nouvelle classe ou la mettez à jour, elle doit être testée. Donc, écrire cinq classes signifie aussi écrire cinq classes de test. Mais cela ne complique pas la conception:
En ce qui concerne les interfaces, elles ne sont nécessaires que lorsque votre infrastructure DI ou votre infrastructure de test ne peut pas gérer les classes. Vous pouvez les voir comme un péage pour des outils imparfaits. Ou vous pouvez les voir comme une abstraction utile vous permettant d'oublier qu'il y a des choses plus compliquées - la lecture du source d'une interface prend beaucoup moins de temps que celle du source de son implémentation.
la source
+++
En ce qui concerne le non-respect des règles: il existe quatre classes, il me faut des endroits, où je ne pouvais les injecter qu’après une refactorisation majeure rendant le code plus moche (du moins pour mes yeux), alors j’ai décidé de les créer à Je trouverais peut-être un meilleur moyen, mais j'en suis heureux (le nombre de ces singletons n'a pas changé depuis des siècles).Func<Guid>
pour cela et injecter une méthode anonyme comme()=>Guid.NewGuid()
dans le constructeur? Et il n’est pas nécessaire de tester cette fonction du framework .Net, c’est quelque chose que Microsoft a fait pour vous. Au total, cela vous fera économiser 4 cours.Selon les abstractions, créer des classes à responsabilité unique et écrire des tests unitaires ne sont pas des sciences exactes. Il est parfaitement normal d'aller trop loin dans une direction lorsqu'on apprend, d'aller à l'extrême, puis de trouver une norme qui ait du sens. On dirait simplement que votre pendule a trop basculé et pourrait même être bloqué.
Voici où je soupçonne que cela a déraillé:
Un des avantages de la plupart des principes SOLID (et certainement pas le seul) est qu’il facilite l’écriture de tests unitaires pour notre code. Si une classe dépend d'abstractions, nous pouvons nous moquer de ces abstractions. Les abstractions qui sont séparées sont plus faciles à simuler. Si une classe fait une chose, elle aura probablement une complexité moindre, ce qui signifie qu'il est plus facile de connaître et de tester tous ses chemins possibles.
Si votre équipe n'écrit pas de tests unitaires, deux événements liés se produisent:
Premièrement, ils font beaucoup de travail supplémentaire pour créer toutes ces interfaces et classes sans en tirer tous les avantages. Il faut un peu de temps et de pratique pour voir comment la rédaction de tests unitaires facilite notre vie. Il y a des raisons pour lesquelles les personnes qui apprennent à écrire des tests unitaires s'y tiennent, mais vous devez persister suffisamment longtemps pour les découvrir par vous-même. Si votre équipe n'essaie pas cela, elle aura l'impression que le reste du travail supplémentaire qu'il fait est inutile.
Par exemple, que se passe-t-il quand ils ont besoin de refactoriser? S'ils ont cent petites classes mais qu'aucun test ne leur indique si leurs modifications fonctionneront ou non, ces classes et interfaces supplémentaires vont sembler être un fardeau, pas une amélioration.
Deuxièmement, l'écriture de tests unitaires peut vous aider à comprendre combien d'abstraction votre code a réellement besoin. Comme je l'ai dit, ce n'est pas une science. Nous commençons mal, nous tournons dans tous les sens et nous nous améliorons. Les tests unitaires ont une manière particulière de compléter SOLID. Comment savoir quand vous devez ajouter une abstraction ou casser quelque chose? En d'autres termes, comment savez-vous quand vous êtes "assez solide"? Souvent, la réponse est lorsque vous ne pouvez pas tester quelque chose.
Peut-être que votre code serait testable sans créer autant d'abstractions et de classes minuscules. Mais si vous n'écrivez pas les tests, comment pouvez-vous le savoir? Jusqu'où allons-nous? Nous pouvons devenir obsédés par la division des choses de plus en plus petites. C'est un trou de lapin. La possibilité d'écrire des tests pour notre code nous permet de voir quand nous avons atteint notre objectif afin que nous puissions cesser d'être obsédés, passer à autre chose et avoir du plaisir à écrire davantage de code.
Les tests unitaires ne sont pas une solution miracle, mais ils constituent une solution vraiment impressionnante qui améliore la vie des développeurs. Nous ne sommes pas parfaits, pas plus que nos tests. Mais les tests nous donnent confiance. Nous nous attendons à ce que notre code soit correct et nous sommes surpris quand il ne va pas, et non l'inverse. Nous ne sommes pas parfaits et nos tests non plus. Mais lorsque notre code est testé, nous avons confiance. Nous sommes moins susceptibles de nous ronger les ongles lorsque notre code est déployé et de nous demander ce qui va se casser cette fois-ci et si ce sera notre faute.
En plus de cela, une fois que nous avons compris, l'écriture de tests unitaires accélère le développement du code, pas le ralentit. Nous passons moins de temps à revoir l'ancien code ou à déboguer pour trouver des problèmes qui ressemblent à des aiguilles dans une botte de foin.
Les bugs diminuent, nous en faisons plus et nous remplaçons l’anxiété par la confiance. Ce n'est pas une huile de mode ou de serpent. C'est vrai. De nombreux développeurs vont en témoigner. Si votre équipe ne l'a pas expérimenté, il lui faut franchir cette courbe d'apprentissage et franchir le cap. Donnez-lui une chance en réalisant qu'ils n'obtiendront pas de résultats instantanément. Mais quand cela se produira, ils seront heureux de le faire et ne regarderont jamais en arrière. (Ou ils deviendront des parias isolés et écriront des articles de blog en colère sur la façon dont les tests unitaires et la plupart des autres connaissances acquises en matière de programmation sont une perte de temps.)
L'examen par les pairs est beaucoup plus facile lorsque tous les tests unitaires sont réussis et qu'une grande partie de cet examen consiste simplement à s'assurer que les tests ont un sens.
la source