Comment puis-je savoir que mon logiciel contient trop d'abstraction et trop de modèles de conception, ou inversement, comment savoir s'il devrait en avoir plus?
Les développeurs avec lesquels je travaille programment différemment sur ces points.
Certains résument chaque petite fonction, utilisent des modèles de conception dans la mesure du possible et évitent la redondance à tout prix.
Les autres, dont moi, essaient d'être plus pragmatiques et écrivent du code qui ne convient pas parfaitement à tous les modèles de conception, mais qui est beaucoup plus rapide à comprendre car moins d'abstraction est appliquée.
Je sais que c'est un compromis. Comment puis-je savoir quand il y a suffisamment d'abstraction dans le projet et comment savoir s'il en a besoin de plus?
Exemple, lorsqu'une couche de mise en cache générique est écrite à l'aide de Memcache. Avons-nous vraiment besoin Memcache
, MemcacheAdapter
, MemcacheInterface
, AbstractCache
, CacheFactory
, CacheConnector
, ... ou est plus facile à maintenir et encore du bon code lorsque vous utilisez seulement la moitié de ces classes?
Trouvé cela sur Twitter:
Réponses:
Combien d'ingrédients sont nécessaires pour un repas? De combien de pièces avez-vous besoin pour construire un véhicule?
Vous savez que vous avez trop peu d'abstraction lorsqu'un petit changement d' implémentation conduit à une cascade de changements partout dans votre code. Des abstractions appropriées aideraient à isoler la partie du code qui doit être modifiée.
Vous savez que vous avez trop d'abstraction lorsqu'un petit changement d' interface conduit à une cascade de changements partout dans votre code, à différents niveaux. Au lieu de changer l'interface entre deux classes, vous vous retrouvez à modifier des dizaines de classes et d'interfaces simplement pour ajouter une propriété ou changer le type d'un argument de méthode.
En dehors de cela, il n'y a vraiment aucun moyen de répondre à la question en donnant un chiffre. Le nombre d'abstractions ne sera pas le même d'un projet à l'autre, d'un langage à un autre, et même d'un développeur à un autre.
la source
interface Doer {void prepare(); void doIt();}
), et il devient douloureux de refactoriser lorsque cette abstraction ne correspond plus. L'élément clé de la réponse est que le test s'applique lorsqu'une abstraction doit changer - si elle ne le fait jamais, elle ne provoque jamais de douleur.Le problème avec les modèles de conception peut se résumer avec le proverbe "lorsque vous tenez un marteau, tout ressemble à un clou." Le fait d' appliquer un modèle de conception n'améliore en rien votre programme. En fait, je dirais que vous créez un programme plus compliqué si vous ajoutez un modèle de conception. La question reste de savoir si vous faites ou non un bon usage du modèle de conception, et c'est le cœur de la question, "Quand avons-nous trop d'abstraction?"
Si vous créez une interface et une super classe abstraite pour une seule implémentation, vous avez ajouté deux composants supplémentaires à votre projet qui sont superflus et inutiles. Le point de fournir une interface est de pouvoir la gérer de manière égale tout au long de votre programme sans savoir comment cela fonctionne. Le but d'une super classe abstraite est de fournir un comportement sous-jacent pour les implémentations. Si vous n'avez qu'une seule implémentation, vous obtenez toutes les interfaces de complication et les classes abstact fournissent et aucun des avantages.
De même, si vous utilisez un modèle Factory et que vous vous retrouvez à convertir une classe afin d'utiliser des fonctionnalités uniquement disponibles dans la super classe, le modèle Factory n'ajoute aucun avantage à votre code. Vous avez seulement ajouté une classe supplémentaire à votre projet qui aurait pu être évitée.
TL; DR Je veux dire que l'objectif d'abstraction n'est pas abstrait en soi. Il sert un objectif très pratique dans votre programme, et avant de décider d'utiliser un modèle de conception ou de créer une interface, vous devriez vous demander si ce faisant, le programme est plus facile à comprendre malgré la complexité supplémentaire ou le programme est plus robuste malgré la complexité supplémentaire (de préférence les deux). Si la réponse est non ou peut-être, alors prenez quelques minutes pour réfléchir à la raison pour laquelle vous vouliez le faire et si cela peut être fait de manière meilleure à la place sans nécessairement la nécessité d'ajouter une abstraction à votre code.
la source
TL: DR;
Je ne pense pas qu'il y ait un nombre "nécessaire" de niveaux d'abstraction au-dessous desquels il y a trop peu ou au-dessus desquels il y a trop. Comme dans la conception graphique, une bonne conception POO doit être invisible et doit être considérée comme acquise. Un mauvais design ressort toujours comme un pouce endolori.
Longue réponse
Très probablement, vous ne saurez jamais sur combien de niveaux d'abstractions vous construisez.
La plupart des niveaux d'abstraction nous sont invisibles et nous les tenons pour acquis.
Ce raisonnement m'amène à cette conclusion:
L'un des principaux objectifs de l'abstraction est de sauver le programmeur de la nécessité de garder à l'esprit tout le fonctionnement du système à tout moment. Si la conception vous oblige à en savoir trop sur le système pour ajouter quelque chose, il y a probablement trop peu d'abstraction. Je pense qu'une mauvaise abstraction (mauvaise conception, conception anémique ou suringénierie) peut également vous forcer à en savoir trop pour ajouter quelque chose. Dans un extrême, nous avons un design basé sur une classe divine ou un groupe de DTO, dans l'autre extrême, nous avons des cadres OR / persistance qui vous font sauter à travers d'innombrables cerceaux pour créer un monde bonjour. Les deux cas vous obligent à trop en savoir trop.
Une mauvaise abstraction adhère à une cloche de Gauss dans le fait qu'une fois que vous avez dépassé un point idéal, cela commence à vous gêner. Une bonne abstraction, en revanche, est invisible et il ne peut pas y en avoir trop parce que vous ne remarquez pas qu'elle est là. Pensez au nombre de couches sur les couches d'API, de protocoles réseau, de bibliothèques, de bibliothèques de systèmes d'exploitation, de systèmes de fichiers, de couches matérielles, etc. sur lesquelles votre application est construite et tient pour acquise.
L'autre objectif principal de l'abstraction est la compartimentation, de sorte que les erreurs ne pénètrent pas au-delà d'une certaine zone, contrairement à la double coque et les réservoirs séparés empêchent un navire d'inonder complètement lorsqu'une partie de la coque a un trou. Si des modifications de code finissent par créer des bogues dans des zones apparemment sans rapport, il y a de fortes chances qu'il y ait trop peu d'abstraction.
la source
Les modèles de conception sont simplement des solutions courantes aux problèmes. Il est important de connaître les modèles de conception, mais ce ne sont que des symptômes d'un code bien conçu (un bon code peut toujours être vide de l'ensemble de quatre modèles de conception), pas la cause.
Les abstractions sont comme des clôtures. Ils aident à séparer les régions de votre programme en morceaux testables et interchangeables (exigences pour créer du code non rigide non rigide). Et un peu comme les clôtures:
Vous voulez des abstractions aux points d'interface naturels pour minimiser leur taille.
Vous ne voulez pas les changer.
Vous voulez qu'ils séparent les choses qui peuvent être indépendantes.
En avoir un au mauvais endroit est pire que de ne pas l'avoir.
Ils ne devraient pas avoir de grosses fuites .
la source
Refactoring
Je n'ai pas vu le mot "refactoring" mentionné une seule fois jusqu'à présent. Alors, c'est parti:
N'hésitez pas à implémenter une nouvelle fonctionnalité aussi directement que possible. Si vous n'avez qu'une seule classe simple, vous n'avez probablement pas besoin d'une interface, d'une superclasse, d'une usine, etc. pour cela.
Si et quand vous remarquez que vous développez la classe de manière à ce qu'elle grossisse trop, c'est le moment de la déchirer. À ce moment-là, il est très logique de réfléchir à la façon dont vous devriez réellement faire cela.
Les motifs sont un outil mental
Les modèles, ou plus précisément le livre "Design Patterns" du gang de quatre, sont excellents, entre autres, car ils créent un langage permettant aux développeurs de penser et de parler. Il est facile de dire "observateur", "usine" ou "façade" et tout le monde sait exactement ce que cela signifie, tout de suite.
Donc, mon avis serait que chaque développeur devrait avoir une connaissance passagère au moins des modèles du livre original, simplement pour pouvoir parler des concepts OO sans avoir à expliquer les bases. Devriez-vous réellement utiliser les modèles chaque fois qu'une possibilité de le faire apparaît? Préférablement pas.
Bibliothèques
Les bibliothèques sont probablement le seul domaine où il peut être afin d'errer du côté de trop de choix basés sur des modèles au lieu de trop peu. Changer quelque chose d'une classe "fat" en quelque chose avec plus de modèles dérivés (généralement cela signifie des classes plus nombreuses et plus petites) changera radicalement l'interface; et c'est la seule chose que vous ne voulez généralement pas changer dans une bibliothèque, car c'est la seule chose qui présente un réel intérêt pour l'utilisateur de votre bibliothèque. Ils ne se soucieraient pas moins de la façon dont vous traitez vos fonctionnalités en interne, mais ils se soucient beaucoup s'ils doivent constamment changer de programme lorsque vous faites une nouvelle version avec une nouvelle API.
la source
Le point de l'abstraction doit être avant tout la valeur apportée au consommateur de l'abstraction, c'est-à-dire le client de l'abstraction, les autres programmeurs et souvent vous-même.
Si, en tant que client qui consomme les abstractions, vous trouvez que vous devez mélanger et faire correspondre de nombreuses abstractions différentes pour faire votre travail de programmation, alors il y a potentiellement trop d'abstractions.
Idéalement, la superposition devrait regrouper un certain nombre d'abstractions inférieures et les remplacer par une abstraction simple et de niveau supérieur que ses consommateurs peuvent utiliser sans avoir à traiter avec l'une de ces abstractions sous-jacentes. S'ils doivent faire face aux abstractions sous-jacentes, alors la couche fuit (par voie d'être incomplète). Si le consommateur doit faire face à trop d'abstractions différentes, la superposition manque peut-être.
Après avoir considéré la valeur des abstractions pour les programmeurs consommateurs, nous pouvons alors nous tourner pour évaluer et considérer l'implémentation, comme celle de DRY-ness.
Oui, il s'agit de faciliter la maintenance, mais nous devons d'abord considérer le sort de la maintenance de nos consommateurs, en fournissant des abstractions et des couches de qualité, puis envisager de faciliter notre propre maintenance en termes d'aspects de mise en œuvre, comme éviter la redondance.
Nous devons examiner le point de vue du client, et si sa vie est facilitée, c'est bien. Si leur vie est plus complexe, c'est mal. Cependant, il se pourrait qu'il y ait une couche manquante qui regroupe ces éléments dans quelque chose de simple à utiliser. En interne, cela peut très bien améliorer la maintenance de l'implémentation. Cependant, comme vous le pensez, il est également possible qu'il soit simplement trop conçu.
la source
L'abstraction est conçue pour rendre le code plus facile à comprendre. Si une couche d'abstraction va rendre les choses plus confuses - ne le faites pas.
L'objectif est d'utiliser le nombre correct d'abstractions et d'interfaces pour:
Résumé seulement si nécessaire
Ne résumez pas lorsque
Quelques exemples
la source
Je pense que cela pourrait être une méta-réponse controversée, et je suis un peu en retard à la fête, mais je pense qu'il est très important de le mentionner ici, car je pense que je sais d'où vous venez.
Le problème avec la façon dont les modèles de conception sont utilisés, c'est que lorsqu'ils sont enseignés, ils présentent un cas comme celui-ci:
Le problème est que lorsque vous commencez à faire de la vraie ingénierie, les choses ne sont pas aussi simples. Le modèle de conception que vous lisez ne correspond pas tout à fait au problème que vous essayez de résoudre. Sans oublier que les bibliothèques que vous utilisez violent totalement tout ce qui est indiqué dans le texte expliquant ces modèles, chacun à sa manière. Et par conséquent, le code que vous écrivez "se sent mal" et vous posez des questions comme celle-ci.
En plus de cela, je voudrais citer Andrei Alexandrescu, en parlant de génie logiciel, qui déclare:
C'est peut-être un peu exagéré, mais je pense que cela explique parfaitement une raison supplémentaire pour laquelle vous pourriez vous sentir moins confiant dans votre code.
C'est dans des moments comme celui-ci que la voix prophétique de Mike Acton, responsable du moteur de jeu chez Insomniac, me crie dans la tête:
Il parle des entrées de votre programme et des sorties souhaitées. Et puis il y a ce joyau de Fred Brooks du mois de l'homme mythique:
Donc, si j'étais vous, je raisonnerais sur mon problème en fonction de mon cas d'entrée typique, et s'il atteint la sortie correcte souhaitée. Et posez des questions comme celle-ci:
Lorsque vous faites cela, la question de "combien de couches d'abstraction ou de motifs de conception sont nécessaires" devient beaucoup plus facile à répondre. De combien de couches d'abstraction avez-vous besoin? Autant que nécessaire pour atteindre ces objectifs, et pas plus. "Qu'en est-il des modèles de conception? Je n'en ai pas utilisé!" Eh bien, si les objectifs ci-dessus ont été atteints sans application directe d'un modèle, c'est bien. Faites-le fonctionner et passez au problème suivant. Commencez par vos données, pas par le code.
la source
L'architecture logicielle invente les langues
Avec chaque couche logicielle, vous créez le langage dans lequel vous (ou vos collègues) souhaitez exprimer leur solution de couche supérieure suivante (donc, je vais ajouter quelques analogues en langage naturel dans mon article). Vos utilisateurs ne veulent pas passer des années à apprendre à lire ou à écrire cette langue.
Cette vue m'aide à décider des problèmes architecturaux.
Lisibilité
Cette langue doit être facilement comprise (rendant le code de la couche suivante lisible). Le code est lu beaucoup plus souvent qu'écrit.
Un concept doit être exprimé avec un mot - une classe ou une interface doit exposer le concept. (Les langues slaves ont généralement deux mots différents pour un verbe anglais, vous devez donc apprendre deux fois le vocabulaire. Toutes les langues naturelles utilisent des mots uniques pour plusieurs concepts).
Les concepts que vous exposez ne doivent pas contenir de surprises. Il s'agit principalement de conventions de dénomination telles que les méthodes get et set, etc. Mais si simplement instancier une classe concrète fait le travail, je préfère cela.
Convivialité
La langue doit être facile à utiliser (ce qui facilite la formulation de «phrases correctes»).
Si toutes ces classes / interfaces MemCache deviennent visibles pour la couche suivante, cela crée une courbe d'apprentissage abrupte pour l'utilisateur jusqu'à ce qu'il comprenne quand et où utiliser lequel de ces mots pour le concept unique de cache.
Exposer uniquement les classes / méthodes nécessaires permet à votre utilisateur de trouver plus facilement ce dont il a besoin (voir la citation DocBrowns d'Antoine de Saint-Exupéry). L'exposition d'une interface au lieu de la classe d'implémentation peut faciliter cela.
Si vous exposez une fonctionnalité où un modèle de conception établi peut s'appliquer, il vaut mieux suivre ce modèle de conception que d'inventer quelque chose de différent. Votre utilisateur comprendra les API suivant un modèle de conception plus facilement qu'un concept complètement différent (si vous connaissez l'italien, l'espagnol vous sera plus facile que le chinois).
Sommaire
Introduisez des abstractions si cela facilite l'utilisation (et vaut la peine de maintenir l'abstraction et l'implémentation).
Si votre code a une sous-tâche (non triviale), résolvez-la "de la manière attendue", c'est-à-dire suivez le modèle de conception approprié au lieu de réinventer un type de roue différent.
la source
La chose importante à considérer est de savoir combien le code consommateur qui gère réellement votre logique métier a besoin de connaître ces classes liées à la mise en cache. Idéalement, votre code ne devrait se soucier que de l'objet cache qu'il souhaite créer et peut-être d'une fabrique pour créer cet objet si une méthode constructeur n'est pas suffisante.
Le nombre de modèles utilisés ou le niveau d'héritage ne sont pas trop importants tant que chaque niveau peut être justifié par d'autres développeurs. Cela crée une limite informelle car chaque niveau supplémentaire est plus difficile à justifier. La partie la plus importante est le nombre de niveaux d'abstraction affectés par les modifications des exigences fonctionnelles ou commerciales. Si vous pouvez modifier un seul niveau pour une seule exigence, vous n'êtes probablement pas trop abstrait ou mal résumé, si vous changez le même niveau pour plusieurs modifications non liées, vous êtes probablement sous-extrait et devez séparer davantage les préoccupations.
la source
Tout d'abord, la citation de Twitter est fausse. Les nouveaux développeurs doivent faire un modèle, les abstractions les aident généralement à "se faire une idée". À condition que les abstractions aient un sens, bien sûr.
Deuxièmement, votre problème n'est pas trop ou trop peu d'abstractions, c'est qu'apparemment personne ne peut décider de ces choses. Personne ne possède le code, aucun plan / conception / philosophie unique n'est mis en œuvre, n'importe quel gars suivant peut faire ce qu'il veut pour ce moment. Quel que soit le style que vous choisissez, il doit en être un.
la source