Abstraction: La guerre entre la résolution du problème et une solution générale [fermé]

33

En tant que programmeur, je me trouve dans le dilemme où je veux que mon programme soit aussi abstrait et général que possible.

Cela me permettrait généralement de réutiliser mon code et d’avoir une solution plus générale à un problème qui pourrait (ou ne pourrait pas) se reproduire.

Ensuite, cette voix dans ma tête dit, il suffit de résoudre le problème factice, c'est aussi simple que cela! Pourquoi dépenser plus de temps que nécessaire?

Nous avons tous été confrontés à cette question, où l’abstraction est sur votre épaule droite et où Résoudre-le-stupide est assis à gauche.

Lequel à écouter et à quelle fréquence? Quelle est votre stratégie pour cela? Devez-vous tout faire abstraction?

Bryan Harrington
la source
1
"En tant qu'abstrait et général" est généralement appelé l'orgueil du programmeur :) Cependant, en raison de la loi de Murphy, le degré d'adaptation dont vous aurez besoin pour travailler sur la tâche suivante sera celui que vous n'avez pas fourni.
Matthieu M.
1
Une des meilleures questions de tous les temps!
explorest le
1
Vous avez toujours tort de deviner si simple. Lorsque vous avez développé les cas simples "suffisamment de fois", vous commencez à voir un motif. Jusque-là, non.

Réponses:

27

Lequel à écouter et à quelle fréquence?

Ne faites jamais abstraction tant que vous ne le devez pas.

En Java, par exemple, vous devez utiliser des interfaces. Ils sont une abstraction.

En Python, vous n'avez pas d'interface, vous avez Duck Typing, et vous n'avez pas besoin d'abstraction. Donc vous ne le faites pas.

Quelle est votre stratégie pour cela?

Ne résumez pas avant de l'avoir écrit trois fois.

Une fois est - bien - une fois. Il suffit de le résoudre et de passer à autre chose.

Deux fois, cela indique qu'il peut y avoir un motif ici. Ou peut-être pas. Ce peut être juste une coïncidence.

Trois fois est le début d'un motif. Maintenant, cela transcende les coïncidences. Vous pouvez maintenant résumer avec succès.

Devez-vous tout faire abstraction?

Non.

En effet, vous ne devriez jamais rien abstrait tant que vous n’avez pas la preuve absolue que vous faites le bon type d’abstraction. Sans la "règle des trois répétitions", vous écrivez des choses inutilement abstraites d'une manière inutile.

Cela me permettrait généralement de réutiliser mon code

C'est une hypothèse qui est souvent fausse. L'abstraction peut ne pas aider du tout. Cela peut être mal fait. Par conséquent, ne le faites pas avant que vous ne deviez.

S.Lott
la source
1
"N'abstiens jamais jusqu'à ce que tu sois obligé." - Je suppose que vous évitez d'utiliser des classes, des méthodes, des boucles ou des déclarations if? Ces choses sont toutes des abstractions. Si vous y réfléchissez, le code écrit dans des langages de haut niveau est très abstrait, que cela vous plaise ou non. La question n'est pas de savoir s'il faut résumer. Ce sont les abstractions à utiliser.
Jason Baker
9
@ James Baker: "Je suppose que vous évitez d'utiliser des classes, des méthodes, des boucles ou des instructions if". Cela ne semble pas être le sujet de la question. Pourquoi affirmer de manière absurde que toute programmation est impossible si nous évitons de trop abstraire nos conceptions?
S.Lott
1
Je me souviens de la vieille blague dans laquelle un homme demande à une femme si elle va dormir avec lui pour un million de dollars. Quand elle dit oui, il lui demande si elle va dormir avec lui pour cinq dollars. Quand elle dit "Pour quel genre de femme me prends-tu?" la réponse est "Eh bien, nous avons déjà établi que vous allez coucher avec quelqu'un pour du sexe. Maintenant, nous ne faisons que négocier le prix." Mon point est qu'il ne s'agit jamais d'une décision d'abstraire ou non. C'est à peu près combien d'abstraits et quelles abstractions choisir. Vous ne pouvez pas retenir l'abstraction à moins d'écrire en assembleur pur.
Jason Baker
9
@Jason Baker: "Vous ne pouvez pas retenir l'abstraction à moins d'écrire en assembleur pur" C'est trivialement faux. L'assemblage est une abstraction sur le langage machine. Ce qui est une abstraction sur les circuits matériels. S'il vous plaît, arrêtez de lire trop dans la réponse qui n'était pas présente dans la question en premier lieu. La question ne portait pas sur "l'abstraction" en tant que concept ou outil intellectuel. Il s'agissait d'abstraction en tant que technique de conception OO - mal appliquée - trop souvent. Ma réponse n'a pas été que l'abstraction est impossible. Mais cette "surabstraction" est mauvaise.
S.Lott
1
@JasonBaker ce n'est pas une raison inouïe de coucher avec quelqu'un. Vous pensiez peut-être à "l'argent"?
19

Ah, YAGNI. Le concept de programmation le plus abusé.

Il y a une différence entre rendre votre code générique et faire un travail supplémentaire. Devez-vous passer plus de temps à faire votre code faiblement couplé et facilement adapté à faire d'autres choses? Absolument. Devriez-vous passer du temps à mettre en œuvre des fonctionnalités inutiles? Non. Devriez-vous passer du temps à faire en sorte que votre code fonctionne avec un autre code qui n'a pas encore été implémenté? Non.

Comme dit le proverbe "Faites la chose la plus simple qui puisse fonctionner". La chose est que les gens confondent toujours simple avec facile . La simplicité prend du travail. Mais cela en vaut la peine.

Pouvez-vous aller à la mer? Bien sûr. Mais, d’après mon expérience, peu d’entreprises ont besoin d’une attitude plus ambitieuse qu’elles ne l’ont déjà fait. La plupart des entreprises ont besoin de plus de personnes qui réfléchissent à l'avance. Sinon, ils finissent toujours par avoir un problème autonome où personne n'a le temps de rien, tout le monde est toujours pressé et personne ne fait rien.

Pensez-y de cette façon: il y a de bonnes chances que votre code soit utilisé à nouveau. Mais vous n'allez pas prédire correctement de cette façon. Alors, faites en sorte que votre code soit propre, abstrait et faiblement couplé. Mais n'essayez pas de faire fonctionner votre code avec des fonctionnalités spécifiques qui n'existent pas encore. Même si vous savez que cela existera dans le futur.

Jason Baker
la source
Belle distinction entre simpleet easy!
Matthieu M.
"Mais selon mon expérience, peu d'entreprises" - il est intéressant de noter que l'inverse peut être vrai dans le monde universitaire.
Steve Bennett
12

Un syllogisme:

  1. La généralité est chère.

  2. Vous dépensez l'argent des autres.

  3. Par conséquent, le coût de la généralité doit être justifié auprès des parties prenantes.

Demandez-vous si vous résolvez réellement le problème plus général afin d'économiser de l'argent aux parties prenantes ultérieurement, ou si vous trouvez simplement un défi intellectuel de trouver une solution inutilement générale.

Si la généralité est jugée souhaitable, elle devrait être conçue et testée comme toute autre fonctionnalité . Si vous ne parvenez pas à écrire des tests démontrant que la généralité que vous avez implémentée résout un problème requis par la spécification, évitez de le faire! Une fonctionnalité qui ne répond à aucun critère de conception et ne peut pas être testée est une fonctionnalité sur laquelle personne ne peut compter.

Et enfin, il n’existe pas de "aussi général que possible". Supposons que vous écriviez un logiciel en C #, juste pour les besoins de l’argumentation. Allez-vous faire en sorte que chaque classe implémente une interface? Chaque classe une classe de base abstraite avec chaque méthode une méthode abstraite? C'est assez général, mais c'est loin d'être "aussi général que possible". Cela permet simplement aux gens de changer la mise en œuvre de n'importe quelle méthode en sous-classant. Et s'ils veulent changer l'implémentation d'une méthode sans sous-classement? Vous pouvez faire de chaque méthode une propriété de type délégué, avec un séparateur afin que les utilisateurs puissent changer chaque méthode. Mais que faire si quelqu'un veut ajouter plus de méthodes? Maintenant, chaque objet doit être extensible.

À ce stade, vous devriez abandonner le langage C # et adopter JavaScript. Mais vous n'avez toujours pas été assez général; Et si quelqu'un veut changer le fonctionnement de la recherche de membre pour cet objet en particulier? Peut-être devriez-vous tout écrire en Python à la place.

Augmenter la généralité signifie souvent abandonner la prévisibilité et augmenter considérablement les coûts de test pour s'assurer que la généralité implémentée réussit réellement à répondre à un réel besoin de l'utilisateur . Ces coûts sont-ils justifiés par leurs avantages pour les parties prenantes qui les paient? Peut-être qu'ils sont; c'est à vous de discuter avec les intervenants. Mes parties prenantes ne veulent pas du tout que j'abandonne le typage statique à la recherche d'un niveau de généralité tout à fait inutile et extrêmement coûteux, mais peut-être le vôtre.

Eric Lippert
la source
2
+1 pour les principes utiles, -1 pour tout savoir sur l'argent.
Mason Wheeler
4
@Mason: Il ne s'agit pas d' argent , mais d' effort . L'argent est une mesure pratique de l'effort . L'efficacité correspond aux avantages cumulés par effort produit; encore une fois, le profit en argent est une mesure pratique des avantages acquis . Il est tout simplement plus facile de discuter de l’abstraction d’argent que de trouver un moyen de mesurer l’effort, les coûts et les avantages. Préféreriez-vous mesurer l'effort, les coûts et les avantages d'une autre manière? N'hésitez pas à faire une proposition d'un meilleur moyen.
Eric Lippert
2
Je suis d'accord avec le point de @Mason Wheeler. Je trouve que la solution ici est de ne pas impliquer les parties prenantes à ce niveau du projet. Cela dépend évidemment beaucoup du secteur, mais les clients ne voient généralement pas le code. En outre, toutes ces réponses semblent être anti-effort, semblent paresseuses.
Orbling le
2
@Orbling: Je suis fermement opposé à tout effort inutile, coûteux et inutile qui enlève des ressources à des projets louables et importants. Si vous suggérez que cela me rend "paresseux", alors je vous soumets que vous utilisez le mot "paresseux" d'une manière inhabituelle.
Eric Lippert
1
D'après mon expérience, les autres projets ont tendance à ne pas disparaître. Il est donc généralement utile d'investir autant de temps que nécessaire dans le projet en cours avant de poursuivre.
Orbling le
5

Soyons clairs: généraliser quelque chose et mettre en œuvre une abstraction sont deux choses complètement différentes.

Par exemple, considérons une fonction qui copie la mémoire.

La fonction est une abstraction qui masque la façon dont les 4 octets sont copiés.

int copy4Bytes (char * pSrc, char * pDest)

Une généralisation ferait que cette fonction copie un nombre quelconque d’octets.

int copyBytes (char * pSrc, char * pDest, int numBytesToCopy)

L'abstraction se prête à la réutilisation, alors que la généralisation la rend utile dans de nombreux cas.

Plus spécifiquement liée à votre question, l'abstraction n'est pas seulement utile du point de vue de la réutilisation du code. Souvent, si cela est fait correctement, votre code sera plus lisible et maintenable. À l'aide de l'exemple ci-dessus, qu'est-ce qui est plus facile à lire et à comprendre si vous parcourez du code copyBytes () ou une boucle for itérant un tableau déplaçant des données, index par index? L'abstraction peut fournir une sorte de documentation personnelle qui, à mon avis, rend le code plus facile à utiliser.

En règle générale, si je parviens à trouver un bon nom de fonction décrivant exactement ce que je souhaite qu'un morceau de code fasse, je lui écris une fonction, que je pense ou non que je l'utiliserai à nouveau.

Pemdas
la source
+1 pour faire la différence entre abstraction et généralisation.
Joris Meys
4

Une bonne règle générale pour ce genre de chose est Zéro, Un, Infini. Autrement dit, si vous avez besoin de ce que vous écrivez plus d'une fois, vous pouvez en déduire que vous en aurez besoin encore plus et que vous généralisez. Cette règle implique que vous ne vous souciez pas de l’abstraction la première fois que vous écrivez quelque chose.

Une autre bonne raison de cette règle est que la première fois que vous écrivez le code, vous ne saurez pas forcément quoi résumer car vous n’avez qu’un seul exemple. La loi de Murphy implique que si vous écrivez un code abstrait la première fois, le deuxième exemple présentera des différences que vous n'aviez pas anticipées.

Larry Coleman
la source
1
Cela ressemble beaucoup à ma version de la règle de refactoring: frappez deux! refactor!
Frank Shearar
@Frank: C'est probablement votre règle de refactoring, mais avec le second paragraphe ajouté par expérience personnelle.
Larry Coleman
+1: Je pense que cela a également été noté assez tôt dans The Pragmatic Programmer, presque textuellement IIRC.
Steven Evers
oh vraiment. Il n'y a rien d'abstrait à résumer avec un seul exemple: dès que vous avez un deuxième exemple, vous pouvez voir ce qui est commun et ce qui ne l'est pas, et vous pouvez faire abstraction!
Frank Shearar
À mon avis, un échantillon de deux est trop petit pour pouvoir généraliser à l'infini. En d'autres termes, beaucoup trop tôt.
2

Eric Lippert souligne trois choses qui, à mon avis, s'appliquent dans son article sur la vérification future d'un dessin . Je pense que si vous le suivez, vous serez en forme.

Premièrement: la généralité prématurée coûte cher.

Deuxièmement: dans votre modèle, ne représentez que les éléments qui se trouvent toujours dans le domaine du problème et dont les relations de classe ne changent pas.

Troisièmement: Gardez vos politiques loin de vos mécanismes.

Conrad Frix
la source
1

Cela dépend de la raison pour laquelle vous codez, du but de votre projet. Si la valeur de votre code est qu'elle résout un problème concret, vous souhaitez que cela soit fait et que vous passiez au problème suivant. S'il existe des solutions simples et rapides pour simplifier la tâche des futurs utilisateurs du code (y compris vous-même), apportez-nous à tout prix des adaptations raisonnables.

D'autre part, il existe des cas où le code que vous écrivez a un but plus générique. Par exemple, lorsque vous écrivez une bibliothèque pour d’autres programmeurs, elle l’utilisera dans une grande variété d’applications. Les utilisateurs potentiels sont inconnus et vous ne pouvez pas leur demander exactement ce qu'ils veulent. Mais vous voulez rendre votre bibliothèque largement utile. Dans de tels cas, je passerais plus de temps à essayer de soutenir la solution générale.

Rob Weir
la source
0

Je suis un grand fan du principe KISS.

Je me concentre sur ce que l’on me demande de faire et non sur la meilleure solution. J'ai dû abandonner le perfectionnisme (et le TOC), car cela me rendait malheureux.

Pablo
la source
Pourquoi l'aversion du perfectionnisme? (Je ne t'ai pas voté au passage)
Orbling le
Ne pas viser la perfection me convient. Pourquoi? Parce que je sais que mes programmes ne seront jamais parfaits. Il n’existe pas de définition standard de la perfection, aussi j’ai simplement choisi de fournir quelque chose qui fonctionne bien.
Pablo
-1

Abstraction est sur votre épaule droite et Solve-it-stupid est assis à gauche.

Je ne suis pas d'accord avec "résoudre le stupide" , je pense qu'il peut être plus "résoudre le malin" .

Quoi de plus intelligent:

  • Ecrire une solution généralisée complexe qui pourrait supporter plusieurs cas
  • L' écriture de code court et efficace qui permet de résoudre le problème à portée de main et est facile à entretenir, et qui peut être étendue à l'avenir S'il est nécessaire.

Le choix par défaut devrait être le deuxième. Sauf si vous pouvez démontrer un besoin de génération.

La solution généralisée ne devrait être que lorsque vous savez que c'est quelque chose qui sera utilisé dans plusieurs projets / cas différents.

Habituellement, je trouve que les solutions généralisées sont mieux utilisées en tant que "code de bibliothèque principal".

Nuit noire
la source
Si vous pouviez faire toutes les hypothèses ici, vous n'auriez pas de problème. Court et facile à entretenir s’excluent mutuellement. Si vous savez que quelque chose sera réutilisé, la solution générale y remédierait.
JeffO
@ Jeff Je ne suis pas tout à fait sûr de comprendre votre commentaire.
Darknight, le
Vous avez sorti le "Résoudre ça stupide" hors contexte.
Bryan Harrington