La suite de ma relecture.
Encapsulation et invariants
je m’arroge
Mot compliqué moi pas comprendre.
Ici, j’ai effectuée une opération mathématiquement impossible et mon programme est donc invalide.
Il n’est pas "invalide" dans l’absolu. Au pire, il est ou il n’est pas conforme aux attentes/besoins spécifiés.
En termes de démarche, c’est important, parce qu’un programme n’est pas invalide parce qu’il va manipuler une valeur ou faire un calcul qui n’a mathématiquement aucun sens (et encore, cela va dépendre du niveau de connaissance en math), mais parce qu’il ne va pas passer les tests. La démarche est :
- on définie un comportement attendu (ce que le programme devra faire et ne pas faire)
- on traduit ce comportement en tests (si on fait du TDD) ou des preuves (pour faire plaisir à Peuk)
- le programme sera valide s’il passe les tests et invalide s’il ne les passe pas. C’est ça, le critère de validité.
Il faut (à mon avis) que cette démarche soit progressivement assimilées pendant toute la durée du cours.
D’ailleurs, je reviens sur l’exo :
Et si vous vous entraîniez, en implémentant un nouveau service ? Ce serait une bonne chose que de pouvoir simplifier la fraction, c’est-à-dire avoir le numérateur et le dénominateur les plus petits possibles.
J’en parle plus longuement sur le live, mais donner juste le code, c’est ne pas montrer le bon exemple en termes de ce qu’on (je ?) attend comme travail. Si on veut que l’apprenant ne se lance pas directement dans le code, mais suis une méthodo (1. def des besoins. 2. tests. 3. def de l’API + les tests échouent. 4. Implementation + les tests passent. doc, deploiment, etc), il faut "montrer l’exemple" dans les solutions d’exercice proposées et montrer explicitement cette démarche.
Sinon, on n’apprend que la syntaxe et on perd beaucoup de la programmation "moderne".
Le problème, c’est que j’ai violé l’invariant de ma classe.
Remarque idiote : cet invariant a été défini avant ? A priori, non, donc il ne peut pas être violé !
D’où l’importance de la démarche (définir puis implémenter). On pourrait tout a fait décider que notre classe Fraction supporte Inf
et dans cas, il n’y a pas de violation de l’invariant. Cela n’a pas de sens de dire que l’invariant est violé s’il n’est pas encore défini.
Un invariant c’est quoi ? Un ensemble de garanties sur l’état de toutes les instances de ma classe. Tant que cet invariant tient toujours, mes classes sont supposées rendre les services qu’elles exposent. Par contre, s’il vient à être violé, alors tout peut arriver. C’est typiquement de la programmation par contrat.
Je ne me souviens pas, la prog par contrat a été abordée dans la partie fonction ? (pré et post conditions)
alors tout peut arriver
Cette formulation me gene mais je ne sais pas exactement par quoi la remplacer.
Son invariant est
Idem pour le "est". La formulation me gène un peu. Cf le live, je ne sais pas trop comment expliquer (il est tard…)
Nous ne laisserons plus que les services à la vue des autres, étant donnés que eux savent quels sont les invariants à respecter.
Qui "eux" ?
L’astuce consiste à remplacer le mot-clé struct par class.
Maladroit.
Ils peuvent être publics et accessibles depuis le reste du monde, par tout le monde.
Ou bien ils peuvent être privés et seule la classe les déclarant peut les manipuler.
Ajouter une petite note pour dire qu’il existe une troisième visibilité et que ça sera vu plus tard.
La visibilité a déjà été vu avant ?
Notre code contient quand même un dernier problème, bloquant toujours la compilation.
Donnez l’erreur de compilation. C’est important pour que l’apprenant apprenne aussi les messages d’erreur de compilation.
pour qu’il soit dans un état cohérent et utilisable
C’est quoi un "état cohérent" ?
En effet, nous ne pouvons plus initialiser notre objet.
Pas totalement vrai. On peut l’initialiser par défaut.
C’est ici que seront faites les initialisations de nos différents attributs, entres autres.
"les initialisations avec des valeurs". Les initialisations par defaut des membres devrait (je pense) etre faites lors de la déclaration des membres.
(Et où sont les initialisations par défaut )
prog.cc:23:5: error: use of undeclared identifier 'numerateur'; did you mean 'm_numerateur'?
numerateur /= pgcd;
^~~~~~~~~~
m_numerateur
prog.cc:11:9: note: 'm_numerateur' declared here
int m_numerateur;
^
prog.cc:24:5: error: use of undeclared identifier 'denominateur'; did you mean 'm_denominateur'?
denominateur /= pgcd;
^~~~~~~~~~~~
m_denominateur
prog.cc:12:9: note: 'm_denominateur' declared here
int m_denominateur;
^
Hihihi.
Plus sérieusement, il serait possible de mettre le code dans un github + build automatique + génération automatique du cours avec le code provenant de github ?
On appelle cette syntaxe la liste d’initialisation.
J’aime bien que les termes anglais soit donné (ici "member initializer lists". Peut etre qu’il faudrait dire "liste d’initialisation des membres").
le constructeur d’une classe peut être surchargé
La notion de surcharge a déjà été vu dans le cours ?
on aura ici un peu plus de travail.
Si le cours a déja abordé les chaines et les conversions numériques, ca pourrait etre un code a donner en exercice.
Un dernier invariant pour la route
Le donner en exo
Dans ce genre de cas, il est plus pratique d’écrire l’implémentation de ce service directement dans le fichier d’en-tête.
Pas fan de cette regle.
Le constructeur permet d’initialiser l’objet, tout en vérifiant que les valeurs fournies sont correctes et ne risquent pas de violer les invariants.
Peut etre que ca serait pertinent de parle du constructeur par defaut avant de parler des constructeurs avec paramètres.
Toutes les méthodes qui ne modifient pas l’objet doivent être déclarée const.
Ca laisse sous entendre qu’on écrit la fonction et après qu’on décide si elle est const ou pas. La méthodo devrait etre l’inverse.
La fois prochaine, je relirais le chapitre "Une classe de grande valeur".