IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

La programmation en C++ est difficile, le génie logiciel en C++ est encore plus difficile,
Un article d'EDUARDO ROCHA, traduit par Escapetiger

Le , par Eduardo Rocha

81PARTAGES

29  1 
La programmation en C++ est difficile, le génie logiciel en C++ est encore plus difficile

EDUARDO ROCHA 26-06-2023 Génie logiciel
Mis à jour le 6 juillet 2023.
Il existe probablement une corrélation entre les compétences en programmation C++ d’une personne donnée et sa capacité à développer des logiciels dans ce langage. Cependant, ces deux aspects sont distincts et n'évoluent pas toujours ensemble. Il est naturel de supposer que la maîtrise de C​++ se traduit directement par la capacité de développer des logiciels en C​++, mais les deux ne sont pas toujours synonymes. Cet article explique comment la complexité du C​++ crée des défis pour le génie logiciel. Il traite également de l'importance de la simplicité pour la maintenabilité et le succès à long terme.

Le génie logiciel et la programmation ne sont pas la même chose.
« Le génie logiciel peut être considéré comme une "programmation intégrée au fil du temps". »
_ Génie logiciel chez Google

C++ est complexe, le génie logiciel n’aime pas ça

C++ est un cas particulier en raison de sa complexité. Il offre de nombreuses façons d'accomplir la même chose et il comporte également de nombreux pièges. C++ est un langage si puissant que les développeurs ont mis au point une infinité de modèles de programmation. Toutefois, le génie logiciel n'aime pas la complexité et ne s'entend pas naturellement avec le C++. Peut-être que cela n'est pas visible dans les petits projets ou les équipes, mais considérez les défis lorsque des dizaines, voire des centaines d'ingénieurs travaillent sur la même base de code comprenant des centaines de milliers de lignes.

Tout comme C, C++ attend du développeur qu'il soit un expert et qu'il l'utilise avec soin. Se tirer une balle dans le pied est assez facile. Pour les projets à grande échelle avec plusieurs développeurs, où seulement quelques-uns seront des experts, le soin et l'attention de toutes les personnes impliquées sont indispensables.
Par exemple, considérez les principes de génie logiciel suivants :

  1. «Vous n'en aurez pas besoin. » “You aren’t gonna need it” (YAGNI) stipule qu'un programmeur ne doit pas ajouter de fonctionnalités avant de les avoir jugées nécessaires. C++ propose de nombreuses manières différentes d’aller à l’encontre de ce principe. Il est tentant de faire d'une fonction ou d'une classe un modèle afin qu'il puisse être réutilisé pour différents types de données, même s'il est actuellement utilisé pour un seul type. Cela rend le code plus difficile à lire, augmente le temps de compilation et dégrade l'utilisation d'outils tels que les analyseurs statiques et les compléteurs de code. Par conséquent, cela ne devrait pas être fait à moins qu'il n'y ait un besoin pour cela.
  2. Évitez l'optimisation prématurée. C'est un principe bien connu qui peut être ignoré dans n'importe quel langage de programmation. Toutefois, si vous utilisez C++ dans un projet, c’est probablement parce que vous avez besoin de bonnes performances. Donc, un certain niveau d'optimisation est nécessaire ; parfois, il faudra même beaucoup d'optimisation. Le problème est qu'il est difficile de délimiter, au sein d'un projet, le code qui doit être optimisé et le code qui n’en aura pas besoin. C++ nous donne les outils pour tout optimiser et nous optimisons souvent plus que nécessaire.


J'adore C++ et c'est mon langage de prédilection par défaut. S'il est utilisé correctement, il fera au moins aussi bien que la plupart des autres langages. Cependant, reconnaître les dangers et les pièges potentiels du langage est la première étape vers un développement sain.

La simplicité est la clé d'une bonne ingénierie logicielle et C++ par défaut n'est pas simple.

Simplifiez lorsque cela est possible !

Le parcours d'apprentissage de C++ a souvent plusieurs pics de confiance. Plus vous apprenez, plus vous réalisez à quel point vous ne savez pas. Et je crois que cela crée un modèle intéressant. Les développeurs expérimentés ont tendance à se limiter à des sous-ensembles du langage et à des sous-ensembles de modèles de programmation suffisants et sûrs. Cette approche est efficace pour simplifier le langage pour un développement facile et maintenable.
Je ne fournirai pas de recette pour le faire. Je ne suis pas sûr d'être qualifié pour le faire. Une chose que je peux dire, c'est qu'un code simple est généralement meilleur que le code optimal et le plus performant. En C++, le code optimal est souvent difficile à lire, difficile à comprendre et, surtout, difficile à maintenir. Je dirais qu'une grande partie de la programmation C++ peut être décrite comme une optimisation précoce à petite échelle.

Un autre problème est que l'écriture de code complexe peut être amusante et, parfois, belle. De nombreux développeurs tombent amoureux de C++ pour cela. Beaucoup d'entre nous trouveront de la joie en utilisant des motifs complexes pour le plaisir. Néanmoins, le problème est que le code devient souvent plus compliqué qu'il ne devrait l'être. Moi-même, j'en suis coupable. Les développeurs qui entrent dans ce groupe devraient au moins être conscients de ce qu'ils font afin qu'ils puissent réfléchir à deux fois avant de trop compliquer les choses pour le plaisir. J'ai travaillé avec des gens qui s'intègrent très bien dans ce groupe, mais qui ne le savent pas. Beaucoup perçoivent l'ajout d'une complexité inutile comme une démonstration de compétence et ne voient pas ses inconvénients (ou ne s'en soucient tout simplement pas).

« Le débogage est deux fois plus difficile que l'écriture du code. Par conséquent, si vous écrivez le code aussi intelligemment que possible, vous n'êtes, par définition, pas assez intelligent pour le déboguer.
— Brian Kernighan

Si la simplification n'est pas possible, encapsulez les bits complexes

Si vous utilisez C++, c’est probablement parce votre projet nécessite de bonnes performances. En outre, des modèles de conception plus complexes peuvent être nécessaires ou peuvent entraîner un code plus simple. Dans ces cas, C++ fournira les bons outils pour le travail, mais le code résultant peut ne pas être facilement lisible ou facile à lire ou à comprendre.

Heureusement, C++ fournit également les outils pour encapsuler correctement et cacher cette complexité. Ici, suivre les principes de l'ingénierie logicielle comme SOLID avec une attention et un soin supplémentaires peut guider le développeur vers le succès.

Le temps supplémentaire nécessaire pour le faire correctement pour les pans les plus complexes (par exemple, une conception plus approfondie et des révisions de code) en vaut la peine à long terme.

Principaux points à retenir

  1. C++ est connu pour sa grande complexité et offre de nombreux modèles de programmation. Cependant, le génie logiciel, qui met l'accent sur la simplicité et la maintenabilité, peut ne pas s'aligner facilement avec les subtilités du C++.
  2. Simplifiez C++ pour un développement maintenable. L'écriture de code facile à comprendre et à maintenir est généralement plus précieuse pour le succès à long terme que l'écriture de code optimal et allégé.
  3. Soyez conscient de la complication excessive et de l'optimisation précoce. Tenez-vous et vos collègues responsables.


La prochaine fois que vous interviewerez quelqu'un pour un poste senior en C++, ne demandez pas au candidat à quel point il est bon en C++, posez-lui des questions sur les pièges du C++ pour le génie logiciel. Il sera très facile d'identifier des ingénieurs ayant une expérience de développement pertinente.

Faire ce qui précède est plus facile à dire qu'à faire. Parfois, des modèles de programmation complexes avec des fonctionnalités de langage complexes se traduiront par un code plus simple et meilleur. Apprendre à le faire nécessite non seulement de l'expérience, mais aussi de la sensibilisation. J'espère que ce billet augmentera votre sensibilisation.

Je tiens à souligner que cet article a été écrit sur la base de mes propres fortes opinions. Donc, si vous êtes d'accord ou si vous avez un point de vue différent, j'aimerais l'entendre ! N'hésitez pas à laisser un commentaire ou à nous contacter.

La version originale de cet article

Et vous ?

Que pensez-vous de la programmation en C++ ?
Pensez-vous que la programmation en C++ est vraiment difficile ? Partagez vos avis.

Une erreur dans cette actualité ? Signalez-nous-la !

Avatar de epsilon68
Membre expérimenté https://www.developpez.com
Le 17/09/2023 à 10:51
J'ai vraiment aimé le c++, j'ai commencé dans ce language. Je me suis même éclaté, j'avais tout fait entièrement.

La conception logiciel est la clef de tout développement, et elle est plus dure en C++ car il faut définir qui est le propriétaire de chaque objet (Cependant, même si les gens ne le font pas en java ou c#, c'est un méga tort, et fait du code peu fiable.) et définir une bonne API est une chose complexe, mais dans tous les languages.

Après je pense que la difficulté ultime est la reprise du code par quelqu'un d'autre. Ma dernière expérience était en C# et était tout bonnement horrible, cependant il est assez aisé de faire du refactoring en C#.

J'avoue que je me suis dit, tellement j'ai souffert, qu'au vu de ce que des personnes peuvent faire en C#, je n'imagine pas ce qu'ils peuvent faire en C++. Aurais-je réussi à modifier et faire évoluer le programme ? je n'en suis pas sûr car le code est 10x plus difficile à lire, mais aussi 10x plus difficile à refactorer en C++.

Je ne sais pas si je reviendrais un jour à programmer en C++, je ferais surement un audit du code avant de me décider 🫣

ps: à tous les managers et DSI : vous êtes responsable de la situation dans laquelle vous avez mis vos équipes, être dans le déni ne sert à rien, la réalité reviendra toujours au plus mauvais moment avec des conséquences toujours plus grandes.
8  1 
Avatar de koala01
Expert éminent sénior https://www.developpez.com
Le 21/09/2023 à 23:19
Salut,

Humm, je ne suis pas aussi catégorique que toi..

Il est vrai que le L de SOLID (LSP, mis pour Liskov Substitution Principle) est -- très clairement orienté objet, vu qu'il parle du seule principe qui soit effectivement spécifique à l'OO : la substituabilité.

Mais pour les autres...

Il n'y a -- par exemple -- absolument aucune difficulté à s'assurer qu'une fonction fasse une chose et une seule, même dans un contexte "non OO", que l'on adopte l'approche AOS ou SOA ne changera sans doute absolument rien : une fonction sera toujours plus facile à tester et à maintenir si elle ne s'occupe effectivement que d'une chose et d'une seule.

Il en va d'ailleurs de même avec l'OCP : Il est "relativement facile" de s'arranger pour "fermer son code" aux modifications (eg : pour faire en sorte qu'un comportement écrit, testé et validé ne doive pas être modifié) mais pour "l'ouvrir" aux évolutions. Et dans ce cas encore, AOS ou SOA n'y changera rien ... à moins bien sur que l'on décide de passer de l'un à l'autre.

Quant aux deux derniers -- ISP et DSP --j'ai d'avantage tendance à les considérer comme les pistes privilégiées à envisager pour améliorer le respect des trois premiers ou, selon la situation, comme le "résultat naturel" d'une tentative (réussie) d'améliorer le respect des trois premiers.

Après, on peut bien sur discuter des heures sur "ce qui fait" réelleement "l'approche OO". M'étant déjà exprimé sur le sujet, je ne le ferai ici que si on m'en fait la demande Mais comme vous le savez, j'ai un avis "plutôt tranché" et sans doute très différents de la plupart des gens

Mais, quoi qu'il en soit, il faut se souvenir que SOLID sont -- d'abord et avant tout -- des principes de conception, ce qui implique que l'on doit (ou du moins, que l'on devrait) s'assurer :
  1. que la manière dont on envisage de faire les choses les respecte les respecte avant d'écrire la moindre ligne de code et
  2. que la manière dont on écrit effectivement le code continue à les respecter "tout au long du processus"
4  0 
Avatar de jo_link_noir
Membre expert https://www.developpez.com
Le 21/09/2023 à 18:56
Les principes SOLID ont une approche très OO dans leur description, mais en fait, ce n'est pas SOLID qui rend moins opti ou plus lent, mais la POO dans certains contextes.

Par exemple, si on possède un tableau d'un type quelconque qui contient 3 états qu'on veut manipuler, on va généralement faire une boucle sur chaque élément qui manipule l'état 1, puis le 2, puis le 3. Si les calculs dépendent du résultat de l'état précédent, on va faire une boucle qui manipule l'état 1, puis une boucle pour l'état 2 et pareil pour le 3ème.

Il s'avère que cette approche est généralement moins optimale que 3 tableaux qui représente chacun un état, car elle engendre plus de perte de cache. C'est la différente entre SoA (Structure of Array) et AoS (Array of Structure).

À ce niveau, on s’intéresse plus aux valeurs que contiennent nos structures plutôt qu'à une abstraction qui permet de les manipuler de manière transparente. Dans ce contexte on se retrouve en opposition avec les principes SOLID. De toute manière, une approche OO ne fonctionne pas bien ici, on va plutôt faire de la programmation orientée data.

Par contre, quand l'OO est adapté, SOLID est utile et je suis du même avis que toi sur la maintenabilité.

Par contre, je ne vois en quoi SOLID permet la disparition totale des fermetures d'une connexion ? Pour moi c'est le RAII qui fait permet cela ou autre selon le langage (le mot clef finally, les contextes managés, etc)
3  0 
Avatar de jo_link_noir
Membre expert https://www.developpez.com
Le 22/09/2023 à 2:53
Il n'y a pas que L qui saute, il y a aussi le D et je ne vois pas vraiment comment appliquer l'ouverture du O sans point de variation disponible. Ça va être compliqué de faire des extensions sans faire de modification.

Pour moi il n'y a que le S (et le I qui va bien avec) qui est un principe plutôt logique, mais qui possède certaines limites: plus on possède de contexte plus il est "facile" de trouver des optimisations. Par conséquent, 2 fonctions peuvent être fusionnées pour faire la même chose de manière plus efficace. Je ne vais pas dire que cela arrive souvent, c'est même plutôt rare, mais c'est quelque chose qui arrive un peu plus souvent quand on se concentre sur nos données.
3  0 
Avatar de koala01
Expert éminent sénior https://www.developpez.com
Le 22/09/2023 à 9:05
Pour le D, on peut encore disucter, bien que, de manière générale, il s'appliquerait en SOA en transmettant de préférence la structure complète (ou, à défaut, l'un des tableau qui la compose) plutôt que la donnée spécifique que l'on trouve dans un tableau spécifique et qui nous intéresse "tout particulièrement".

Car, quoi que l'on en dise, un tableau reste une abstraction, et une structure aussi

Quant au O, ben, en fait, il vient main dans la main avec le S en SOA, dans le sens où, si chaque fonction ne fait qu'une et une seule chose, si tu veux faire évoluer une de tes structures de tableaux, tu vas sans doute rajouter un nouveau tableau à cette structure et ... les fonctions qui "vont bien" pour en gérer les données (dans le respect de SRP).

Et, pour profiter de cette évolution, tu peux "simplement" créer ... une nouvelle fonction qui fait appel aux fonctions "complexes" existantes et qui intègrent les fonctions de base que tu viens d'ajouter pour l'évolution. Ce qui se fait dans le respect de l'OCP.

Bien sur, nous sommes d'accord sur le fait qu'il puisse, peut-être (surement), parfois (souvent), sembler (être réellement) plus intéressant d'aller modifier les fonctions complexes existantes que d'aller créer une fonction séparée qui prenne l'évolution en charge... Mais ca, c'est la grosse tentation que l'on a de manière systématique, et c'est justement la raison pour laquelle l'OCP existe
3  0 
Avatar de jo_link_noir
Membre expert https://www.developpez.com
Le 22/09/2023 à 13:57
Mhouais, ce n'est pas vraiment ce que j’appelle de l'OCP. Ce que tu me décris me fait plus penser à l'équivalent d'une nouvelle classe sans parenté. Pour moi, l'OCP est la capacité à faire des points de variation -> du polymorphisme. La majorité du temps, quand on fait des points de variation sur des données, on se retrouve à modifier du code existant, ce qui ne respecte pas l'OCP. Exemple bête, les visiteurs / std::variant.
3  0 
Avatar de koala01
Expert éminent sénior https://www.developpez.com
Le 22/09/2023 à 23:39
Citation Envoyé par jo_link_noir Voir le message
Pour moi, l'OCP est la capacité à faire des points de variation -> du polymorphisme
L'OCP est un principe (de conception), le polymorphisme est un concept (de programmation) ...

Tu fais simplement l'erreur de placer la relation entre le principe et le concept dans le mauvais sens : l'OCP c'est "l'objectif à atteindre", la règle qu'il faut s'arranger pour respecter, le polymorphisme c'est le moyen (en fait l'un des moyens) qui nous est donné pour y arriver...

Cependant, le polymorphisme n'est pas -- loin s'en faut -- le seul moyen qui nous permettra de respecter l'O.

Car l'OCP nous dit simplement que
le code doit être fermé aux modification, mais ouvert aux évolutions"
Autrement dit, tu ne dois pas (ou, du moins, tu ne devrais pas) avoir à modifier un code existant -- qui a été testé et validé -- (c'est l'aspect "fermé aux modifications" afin d'être en mesure d'intégrer une évolution (c'est l'aspect "ouvert aux évolutions"

Ou, si tu préfères, tout ce que te demande en réalité l'OCP, c'est de faire en sorte (ou du moins d'essayer) que le code que tu as écrit avant-hier et validé hier ne devra pas modifié demain pour te permettre d'intégrer l'évolution suivante.

Peu importe comment tu y arrives !!!

Au mieux, il est possible de déconseiller certaines pratiques, comme le RAII (if (dynamic_cast< ... *>(ptr)!= nullptr ) ou un équivalent à peine caché sous forme d'un switch ... case (ou n'importe quel switch case dont on aurait de bonnes raisons de croire que le nombre de valeurs envisagées risque d'augmenter à chaque évolution)

Et, de ce point de vue là, le SRP te donne "une autre clé" nous permettant d'assurer le respect de l'OCP; même si c'est de manière un peu plus indirecte

Car, mieux le SRP sera respecté, plus tu pourras partir du principe que les fonctions qui le respectent le mieux seront celles qui... auront le moins besoin dêtre modifiées pour permettre "l'évolution suivante".

Tu n'as même pas besoin d'être dans une approche orientée objets ... Tu as juste besoin de te rendre compte que ta fonction "super complexe" (ou peut être pas tant que cela) prend en réalité en charge "un certain nombre" de responsabilités qui auraient parfaitement pu être "ventilées" vers des fonctions "beaucoup plus simples"

D'ailleurs, je vais même aller (beaucoup) plus loin : à part LSP, qui est vraiment très spécifique, et peut être l'ISP qui, à un certain niveau, ne pourra que t'inciter à ne pas *** forcément *** exposer toutes les fonctions que tu as créées, les trois autres principes (SRP, OCP et DIP) se tiennent pour ainsi dire par la main! Laisses moi m'expliquer avant de hurler, s'il te plait

Le SRP t'incite à créer des fonctions qui n'ont qu'une et une seule responsabilité. Soit ...

On pourrait donc croire qu'une fonction de tri ne prend qu'une seule et unique responsabilité : celle de ... trier les éléments qui lui sont donnés. On est d'accord

Ben, en fait ... non, je ne suis pas "tout à fait d'accord" car -- je l'admets, je coupes les cheveux en quatre -- pour pouvoir décider de déplacer les éléments, il faut -- d'abord et avant tout -- qu'elle les compare deux à deux. Et cette comparaison est une responsabilité qui mériterait, selon les termes du SRP, d'être prise en charge par une fonciton spécifique.

Le mieux de l'histoire, c'est que si tu "extrait" effectivement cette fonction de comparaison de ton algorithme de tri, ta fonction de tri n'a ... plus aucune raison d'être modifiée une fois qu'elle a été validée (à moins, bien sur, que tu décide de changer d'algorithme de tri ) Voilà donc l'OCP qui se pointe "tout naturellement" car, que tu veuilles un tri "croissant" ou "décroissant", il n'y a finalement plus que ... la manière dont la comparaison sera effectuée qui devra être modifiée si tu veux passer de l'un à l'autre.

Au pire, tout ce qu'il te reste à faire, c'est de permettre à la personne qui veut utiliser la méthode de tri que tu as développée de choisir la fonction de comparaison qui lui convient (et, pourquoi pas, d'en sélectionner une "par défaut", pour la facilité).

Et si, au lieu de parler de "fonction de comparaison", nous venions parler de "comparateur" Il s'agirait toujours de la même "fonction de comparaison", cependant, le simple fait de lui avoir choisi un autre terme qui "puisse sembler plus adapté" pour la désigner, et ... Bardaf : nous voilà avec un "zolie abstraction".

Et devine quoi Si je décide de faire dépendre ma fonction de tri de l'abstraction (désormais) connue sous le nom de "comparateur", voilà le respect du DIP qui arrive en courant !

Car, à partir du moment où tu est -- effectivement -- en mesure de fournir deux éléments à ton "comparateur" et qu'il te permet de déterminer la relation A <comparaison> B est valide, il peut parfaitement te servir dans ta fonction de tri. Et ce, quelle que soit la forme effective de ton "comparateur", pour autant que nous nous soyons mis d'accord sur la manière de l'utiliser

Il est aussi vrai qu'il serait sans doute particulièrement intéressant d'aborder une approche générique dans le cadre de cet exemple. Et pourtant, la logique de SRP appelant OCP et DIP dans la foulée est susceptible de s'appliquer ... quel que soit le paradigme envisagé

La majorité du temps, quand on fait des points de variation sur des données, on se retrouve à modifier du code existant, ce qui ne respecte pas l'OCP. Exemple bête, les visiteurs / std::variant.
Peut-être est-ce l'occasion, ici, au calme du forum, d'essayer d'analyser les raison qui rendent cette situation possible, et, si possible, de dresser des pistes à envisager afin de l'éviter.

Bien sur, il sera surement beaucoup plus facile de suivre ces pistes sur un code de 100 fichiers que sur un code de 100 000 ... A moins que tu n'aies du temps à perdre dans une refactorisation complète
3  0 
Avatar de koala01
Expert éminent sénior https://www.developpez.com
Le 23/09/2023 à 22:44
Citation Envoyé par jo_link_noir Voir le message
Tu t'es vachement centrés sur la programmation générique.
Même pas...

Il faut juste garder en tête qu'une fonction n'est ... que le moyen de faire comprendre à quelque chose d'aussi bête qu'un oridanteur le comportement que l'on attend de lui à un moment précis.

Et il faut donc garder en tête le fait que ce comportement est destiné à nous fournir un résultat "clairement défini", "prédictible" et "reproductible":
  • clairement défini : C'est ce que l'on a le plus facile à obtenir, pour autant que l'on choisisse correctement le nom de la fonction... Il vaut mieux choisir un nom qui évoque clairement ce que la fonction est sensée faire
  • prédictible : Même si cela doit te demander "un certain temps" de le faire "à la main", tu es (ou devrais être) en mesure de déterminer à l'avance le résultat que tu obtiendra en appelant ce comportement (cette fonction) avec un jeu de donné spécifique (et dans une situation donnée)
  • reproductible : si tu appelles deux fois le même comportement avec les même données (et dans les même circonstances) tu obtiendra deux fois le même résultat.


Et, à partir de là, on peut partir sur le SRP: une fonction ne devrait faire qu'une et une seule chose pour s'assurer de la faire bien

C'est à dire que, si une fonction doit faire deux chose (comme "comparer" et "réarranger" pour reprendre mon exemple précédent), c'est -- peut-être -- qu'elle en fait une de trop. Tu devrais (pourrais) donc envisager "d'extraire" le comportement qui consiste à comparer deux éléments de celui qui consiste à les réarranger.

Alors, bien sur, ce qui nous intéressant dans le comportement de comparaison, c'est le résultat que l'on obtient, et il faut donc ... un moyen de l'obtenir; mais c'est le propre de tout comportement, de toutes fonctions, non

Et il semble "logique" de se dire que, ayant extrait le comportement "plus simple" qui consite à effectuer la comparaison de la fonction de "réarrangement", il faudra trouver un moyen -- n'importe lequel (fusse un pointeur de fonction) -- de faire en sorte que le comportement de réarrangement "sache" quel comportement de comparaison appeler et, surtout, comment l'utiliser : quelles données lui transmettre, dans quel ordre, et comment interpréter le résultat.

L'abstraction n'arrivant que ... parce que je décide d'appeler ce comportement de comparaison "compare", "comparator" ou encore "compareFunction" car je trouve cela plus clair que de l'appeler simplement "ptr" (ou "funcptr".
Dans ce que tu décris, le Comparator revient à une interface avec post et pré-conditions,
Mais toute fonction testable a interface (comment l'utiliser) , précondition (sur les données à transmettre) et postconditions (résultat escompté) ... Ou à tout le moins l'un ou plusieurs de ces éléments
  • Le simple fait de typer un paramètre va -- forcément -- imposer une précondtion car, si ce n'est pas le type adéquat (ou, au pire, si on ne sait pas convertir la donnée fournie dans le type adéquat), tu es dans la merde...
  • La moindre vérification de validité du résultat à l'intérieur de la fonction n'est jamais qu'une manière de t'assurer du respect d'une post condition
  • Et l'interface ne fait jamais qu'exposer la manière dont tu vas l'utiliser : créer un pointeur de fonction qui renvoie une donnée d'un type donné à partir de deux données de type potentiellement identiques, c'est fournir une interface

LSP a toute sa place.
AAAhhh, non ... Du moins, pas dans le cadre de cette discussion ...

Le LSP ne s'intéresse qu'aux notions de "sous-typage" (faisons simple : d'héritage / implémentation des interface) et de substituabilité. C'est d'ailleurs la raison de la présence du S que l'on trouve entre principe et Liskov

La substituabilité étant la possibilité de manipuler un objet de type B -- qui serait "sous-type de A" comme s'il s'agissait en réalité d'un objet du type A.

Pré et post conditions (ainsi qu'invariant) étant des notions intervenant dans la programmation par contrat. Si tu me donnes des données valides et cohérentes (préconditions), je dois pouvoir te fournir un résultat valide, cohérent, prédictible et reproductible (post conditions) ...

Ce qui se passe, c'est que tu confonds encore une fois le "quoi" et le "comment" : le "quoi" étant la substituabilité, et le comment étant -- entre autres -- composé de la programmation par contrat, celle-ci ne faisant que te donner "des pistes à suivre" pour obtenir une substituabilité cohérente
Ce dont je parle est un système où chaque type à son/ses propres algos (exit la généricité)
Je viens de t'expliquer que la généricité n'est pas une fin en soi, mais, à partir du moment où elle peut arriver "naturellement" ... simplement parce que tu as respecté le SRP, une fois qu'elle est possible (ou facilitée), il serait peut-être dommage de ne pas en profité en cas de besoins
voir des interdépendances.

Ahh, les interdépendances ...

Quelles qu'elles soient, c'est toujours un gros problème. Et le truc, c'est qu'il s'agit d'une boulle à facette, façon "soirée disco"...

Car certaines viennent carrément d'une mauvaise conception au départ. Et d'autres viennet aussi parfois de la manière dont le code est écrit. Le pire étant qu'il y a tellement moyen d'en obtenir pour si peu de moyens de les éviter que cela peut devenir une gagure que d'essayer de les éviter

Ceci dit, je suis sur que tu penses spécifiquement à un cas bien particulier auquel tu auras été confronté "récemment".

Ceci dit, interdépendances et OCP ne sont que les deux face d'une même pièce, dans le sens où, plus tu as d'interdépendances, moins il t'es aisé de respecter l'OCP et, à l'inverse, plus tu respecte l'OCP, moins tu as de risque de souffrir d'interdépendance "fortes"
Du coup je reste sur ma position, l'OCP n'est pas le bienvenu.
C'est ton choix

Et je vais laisser les autres faire le leur ...

Pour ma part, j'aime simplement trop écrire sur un forum, et je me laisse beaucoup trop facilement emporter par ma plume
2  0 
Avatar de jo_link_noir
Membre expert https://www.developpez.com
Le 24/09/2023 à 2:45
Citation Envoyé par koala01 Voir le message

AAAhhh, non ... Du moins, pas dans le cadre de cette discussion ...

Le LSP ne s'intéresse qu'aux notions de "sous-typage" (faisons simple : d'héritage / implémentation des interfaces) et de substituabilité. C'est d'ailleurs la raison de la présence du S que l'on trouve entre principe et Liskov

La substituabilité étant la possibilité de manipuler un objet de type B -- qui serait "sous-type de A" comme s'il s'agissait en réalité d'un objet du type A.

Pré et post conditions (ainsi qu'invariant) étant des notions intervenant dans la programmation par contrat. Si tu me donnes des données valides et cohérentes (préconditions), je dois pouvoir te fournir un résultat valide, cohérent, prédictible et reproductible (post conditions) ...
Je vais la faire à l'envers. Comporator décrit un type pouvant être décrit par une interface ou un concept. Tout type correspondant au concept de Comparator étant d'office un sous-type de Comparator. Sous-type ayant comme pré-condition une fonction équivalent à operator()(T const&, T const& qui a comme post-condition un type convertible en bool. C'est de ce contrat que je parle. Tu remarqueras que je traite les types de la même manière que les valeurs, par conséquent, contravariance et covariance ne sont que pré et post-conditions. Mais comme de toute manière la notions de sous-typage peut être implicite quand basé sur le comportement (comprendre truc et bidule sont définit, hop, ça en fait un sous type de X: duck-typing) et que la substituabilité implique le respect d'un contrat, je ne me prive pas de réduire le concept à des pré et post-conditions.
2  0 
Avatar de jo_link_noir
Membre expert https://www.developpez.com
Le 23/09/2023 à 18:25
Tu t'es vachement centrés sur la programmation générique. Dans ce que tu décris, le Comparator revient à une interface avec post et pré-conditions, LSP a toute sa place. Ce dont je parle est un système où chaque type à son/ses propres algos (exit la généricité), voir des interdépendances. Du coup je reste sur ma position, l'OCP n'est pas le bienvenu.
1  0