J’ai continué un peu ma lecture.
Les exercices sur les behaviors (3.3.1) sont assez ennuyeux et je n’ai pas eu le courage de tous les faire. Surtout qu’on reprend ceux d’avant… J’ai trouvé qu’ils sont assez artificiels, surtout celui où on dit d’enlever des trucs et d’en rajouter pour faire réussir/échouer la preuve de complétude ou disjonction. Autant quand on passe dessus la première fois, on se dit « c’est pour apprendre », autant la seconde fois, c’est un peu la corvée.
Celui qui semble le moins gavant est celui avec les catégories de caractères (3.4.1.2), mais il reste un peu artificiel.
De ce que je comprends, un des intérêts majeurs des behaviors par rapport à un ensemble d’implications, c’est la possibilité de vérifier qu’ils sont complets ou disjoints. Tu en parles bien sûr, mais j’ai l’impression que tu pourrais mettre plus en valeur ça. Tu parles de déterminisme à un endroit, ce n’est pas vraiment illustré dans les exercices. La complétude est illustrée, mais pas forcément sur des cas non-triviaux.
Je pense qu’un exercice un peu plus élaboré aurait sa place, mais demande de créer un peu de contexte. Quelque chose du genre ci-dessous (que j’ai inventé à l’arrache avec un bout de code).
Dans un premier temps, on imagine une machine avec trois modes (off, idle, operating) et un interrupteur (on, off). Le besoin est le suivant :
Dans un second temps, on rajoute un watchdog pour assurer la sûreté de fonctionnement :
- si le watchdog détecte une erreur, alors la machine ne doit pas être operating.
enum MS {
MS_OFF, MS_IDLE, MS_OPERATING,
};
enum SS {
SS_OFF, SS_ON,
};
enum WS {
WS_OK, WS_ERROR,
};
int transition(int ms, int ss, int ws) {
if (ms == MS_OFF) {
if (ss == SS_OFF)
return MS_OFF;
else
return MS_IDLE;
}
else if(ms == MS_IDLE) {
if (ss == SS_OFF || ws == WS_ERROR)
return MS_OFF;
else
return MS_OPERATING;
}
else {
if (ss == SS_OFF || ws == WS_ERROR)
return MS_OFF;
else
return MS_OPERATING;
}
}
int main() {
int ms = MS_OPERATING;
int ss = SS_ON;
int ws = WS_OK;
ms = transition(ms, ws, ws);
ws = WS_ERROR;
ms = transition(ms, ws, ws);
return 0;
}
Quand j’ai essayé d’inventer/résoudre l’exercice, je me suis rendu compte des trucs suivants.
- Ce n’est pas tout à fait évident d’écrire la spécifications en terme de behaviors, parce qu’il y a plusieurs variables d’entrées qui peuvent prendre différentes valeurs et donc plusieurs choix de
behaviors cohérents possibles.
- Les behaviors permettent de s’assurer d’avoir pris en compte toutes les combinaisons d’entrées, tout en gardant une formulation plutôt naturelle avec la clause qui va bien (y compris avec la clause de sûreté !).
- Garder la propriété de sûreté indépendante ouvre un choix de spécification dans les behaviors (soit on laisse l’état suivant fixé formellement, soit on arrête un choix donné).
- L’implémentation n’est pas forcément un calque des spec (par exemple, on pourrait faire le if sur le watchdog au plus haut niveau, et pas comme j’ai fait) et pourrait dépendre de détails concrets de la machine sans changer la spec (par exemple s’il y a une séquence d’actions à suivre pour s’éteindre).
Bref, c’était improvisé, mais assez formateur finalement. Je suis parti un peu loin.
Finally, we can ask WP to verify that behaviors are disjoint (to guarantee determinism)
Du coup, j’ai une question. Pourquoi tu parles de déterminisme dans ce cas-là ? Parce qu’au pire, on a des post-conditions contradictoires, mais je vois pas en quoi c’est non déterminste. Et tu as des cas d’usages réalistes ou on a vraiment besoin de cas non-disjoints ? Dans mon exemple, j’ai fait ça de manière un peu tordue pour tester la complétude…