Void-safe augmente la stabilité et la fiabilité du logiciel tout en diminuant le travail pour le développeur ! Une opération totalement gagnante.
Le langage de programmation Eiffel offre une excellente mise en œuvre du Void-safe mais certains des principes présentés ici s'appliquent à tous les langages.
2. Copyright 2012, Group S
Benoît Marchal, Paul-Georges Crismer
@declencheur @pgcrismer
Cette présentation est distribuée sous licence CC-BY
Pour les conditions d’utilisation, consulter
http://creativecommons.org/licenses/by/2.0/be/
5. Heu… sérieusement :-)
durant l’exécution d’une application, une référence est
soit attachée à un objet
create a_domain.make
soit Void
a_domain := Void
6. Banal
synonymes dans tous les langages
SQL, JavaScript, Java, C# : null
Lisp, Pascal : nil
VB : nothing
C/C++ : Ø ou null
8. Mais
l’appel sur une référence non attachée/void
erreur d’exécution
plus fréquent dans un langage orienté-objet
les objets sont accessibles via des références
que dans un langage procédural
où (hormis C) les pointeurs étaient moins utilisés
9. Difficile à trouver
évidemment, on n’écrit pas
Void.do_something
create a_domain.make -- a_domain attachée
a_range := Void -- a_range détachée
a_space.lots_of_complex_stuff
a_space… heu… joker !
10. Similaire à ÷Ø
en calcul sur des entiers
on a besoin du zéro
on a besoin de diviser des entiers
mais il ne faut jamais diviser par zéro
c’est une erreur à l’exécution
mais… plus d’appels sur références que de divisions
11. De même, Void
Void
indispensable
source d’erreur difficile à détecter
références fréquentes dans un système orienté-objet
12. A propos…
appel sur Void déclenche une exception
qu’on pourrait intercepter
mais il est plus simple de tester la référence
d’où les innombrables assertions
a_param /= Void
et le bogue courant c’est d’oublier un test
13. Avoid a void
“That case occurs in system-oriented parts of
programs, typically in libraries that implement
fundamental data structures in the more application-
oriented parts of a program, void references are
generally unnecessary.
[…] confine void references to specific parts of a
system, largely preserving the application-oriented
layers from having to worry about the issues [of void
references]”
14. Concrètement
owner
ACCOUNT: 123 PERSON: Jack
owner
ACCOUNT: 345 PERSON: John
owner
ACCOUNT: 567
Void ici signifie
“je ne le connais pas.”
15. Et pourtant
owner
ACCOUNT: 123 PERSON: Jack
is_nogood: False
owner
ACCOUNT: 345 PERSON: John
is_nogood: False
owner
ACCOUNT: 567 PERSON: Unknown
is_nogood: True
16. Ou encore
owner
ACCOUNT: 123 REAL_PERSON: Jack
owner
ACCOUNT: 345 REAL_PERSON: John
owner
ACCOUNT: 567 UNKNOWN_PERSON
<<deferred>>
PERSON
REAL_PERSON UNKNOWN_PERSON
17. Voire même
owner
ACCOUNT: 123 PERSON: Jack
owner
ACCOUNT: 345 PERSON: John
owner
ACCOUNT: 567 PERSON: Unknown
{NONE}
unknown_person
SHARED_UNKNOWN_PERSON
Shared_unknown: PERSON
is_unknown(person: PERSON): boolean
18. A propos du SQL Null
norme ISO : information manquante ou non applicable
complexe (logique à 3 valeurs, join,…)
mais il ne s’agit pas d’une référence, pas d’appel
souvent traduit par une référence détachable en Eiffel
sans doute pas la meilleure représentation
nous verrons quelques patterns alternatifs
19. Donc, Void
indispensable
source d’erreur difficile à détecter
appel sur une référence à Void
fréquent dans un système orienté-objet
à limiter aux parties techniques de l’application
21. Void safety
renforcer le contrôle des types de référence
attached
detachable
cas particulier, pour les aspects techniques
certified attachment patterns
garantie du compilateur : plus d’appels sur Void
22. CAP
transforme un référence détachable en attachée
CAP de base
x.f(…) est void-safe si
x est un paramètre ou une variable locale
dans la portée d’un test d’attachement de x
n’est pas précédé par une assignation à x
23. Exemple de CAP
local
* name: detachable STRING
do
* -- …
* if name /= Void then
* * name.append ("something else")
* end
end
24. Erreur VEVI
Variable is not properly set.
local
* text: attached STRING
do
* text.append (" or something")
end
26. Erreur VUTA
Target of the Object_call is not attached.
local
* name: detachable STRING
do
* -- …
* name.append (" or something")
end
27. Une solution possible
local
* name: detachable STRING
do
* -- …
* if name /= Void then
* * name.append (" or something")
* end
end
28. Erreur VUAR
Non-compatible actual parameter in feature call.
local
* name: detachable STRING
* text: attached STRING
do
* text := "nothing"
* -- …
* text.append (name)
end
29. Une solution possible
local
* name: detachable STRING
* text: attached STRING
do
* text := "nothing"
* -- …
* if name /= Void then
* * text.append (name)
* end
end
37. créer un projet “exo_vs”, sans le compiler
full-class checking? true
void safety: Complete void-safe
syntax: standard syntax
are types attached by default? true
base library: …base-safe.ecf
precompiled base: …base-safe.ecf
38. Enoncé
par clarté, on indique explicitement attached/detachable
normalement, on n’indique que detachable
complétez deux routines
pour les rendre void-safe et compiler :-)
notez que ce n’est pas la même erreur
bonus : pourquoi ?
42. Proposition de solution
do_detachable (language: detachable STRING)
* do
* * if language /= Void then
* * * language.append (" is void-safe")
* * * print (language)
* * end
* end
46. Un faux problème ?
pré-conditions de type /= Void assure déjà la sécurité
oui, pour du code existant
mais pour du nouveau code, réduit l’effort
moins à écrire
moins de bogues
augmente la lisibilité
48. Contrats logiciels
dans le contrat, on trouve
assertions métier, sémantiquement riches
a_file_in_list : has_file (a_file)
noyées dans… de la technique, un faux void-safe
a_file_not_void : a_file /= Void
49. Redonner de la lisibilité
update_flag (a_document, a_file : STRING ;
a_secretary : SELECT_CLASS)
* require
* * a_file_in_list : has_file (a_file)
* do
* * -- …
* end
50. Bénéfices
délègue au compilateur les considérations techniques
moins de code à écrire, déboguer, etc.
sécurité accrue, par rapport aux préconditions
une vraie documentation lisible et riche
maintenance, reprise du code simplifiée
52. 4 objectifs
statique : vérifiable à la compilation
général : applicable à tous les types
simple : à comprendre et raisonnable à implémenter
compatible : avec le langage et les applications
en pratique, ce n’est que partiel, il faut porter
53. Types attachés
qualifie l’attachement d’un type : attached/detachable
Void n’est permis que pour les références détachables
right: detachable LINKABLE[G]
attached est la valeur par défaut, on ne l’écrit pas
vérifier les paramètres du projet !
attention à l’ancien code, c’est un changement…
54. Un choix logique
local
* name: detachable STRING
* text: attached STRING
do
* text := "nothing" mais inverse
* -- … d ’aujourd’hui
* if name /= Void then
* * text.append (name)
* end
end
55. Assignation
l’assignation préserve le caractère attaché
assigner un type attaché ou un paramètre attaché
uniquement depuis une référence attachée
text := "nothing"
if a_detachable /= Void then
* text := a_detachable
end
56. Initialisation
par défaut, une référence est initialisée à Void
il faut donner une valeur acceptable (erreur VEVI)
une variable doit avoir une valeur acceptable
variable locale, dans le corps de la routine
attribut, via le constructeur
57. CAP
certified attachment pattern
assurer la compatibilité
en intégrant des pratiques courantes
si elles sont sûres
en particulier, le test d’une variable locale /= Void
58. Exemple de CAP
local
* name: detachable STRING
do
* -- …
* if name /= Void then
* * name.append ("something else")
* end
end
59. Autre exemple de CAP
local
* current: detachable LIST_ITEM
* -- …
from
* current := first_element
until
* current = Void or else current.item.is_equal (sought)
loop
* current := l.right
end
60. CAP et pré-condition
do_detachable (name: detachable STRING)
* require
* * name_attached: name /= Void
* do
* * name.append (" is famous")
* * print (text)
* end
61. CAP et attributs
if x /= Void then
* do_something
* x.do_something_else
end
n’est pas void-safe si x est un attribut
multithreading : modification entre test et appel
monothread: do_something modifie l’attribut
62. Stable
impossible d’assigner Void à un attribut stable
donc une fois attaché, il le reste
stable x: detachable TYPE
permet d’utiliser le CAP avec des attributs
puisqu’il ne peut être détaché après le test…
sucre syntaxique pour une conversion
63. Test d’objet
if attached {TYPE} expression as a_local then
* -- …
* a_local.do_something
end
teste le type de l’expression et l’assigne à local
remplace la tentative d’assignation ?=
restreint la portée de la variable locale au test
64. Versions simplifiées
if attached expression as local then
* -- …
* local.do_something
end
if attached local then
* -- …
* local.do_something
end
65. Et si on le “sait” ?
e.g., en combinant diverses pré-conditions
invariant : error implies attached message
report_error
* require
* * error_set: error
* do
* * -- message est attaché… pas pour le compilateur
* end
66. Check… then… plus lisible
if attached message as a_message then
* a_message.do_something
else
* -- que mettre ici ? une erreur ?
end
check attached message as a_message then
* a_message.do_something
end
67. Générique
un paramètre générique est-il attaché ou non ?
si nécessaire, on le précise lors de la déclaration
une déclaration de contrainte habituelle
class GENERIC_CLASS[T -> attached ANY]
68. Tableaux
les éléments d’un tableau sont initialisés à… Void
acceptable pour un type détachable
inacceptable pour un type attaché
make_filled (low, high: INTEGER; value: G)
make_empty et on fait grossir le tableau
si les indices progressent 1 par 1
71. Full class checking
PERSON make_with_names (a_first, a_last: STRING)
* do
make_with_names (a_first, a_last) * * first_name := a_first
first_name: STRING * * last_name := a_last
last_name: STRING * end
make
TAXI_DRIVER * -- first_name & last_name ne sont pas attachés !
* do
make * * create taxi.make
taxi: CAR " end
72. Obligatoire en Void-safe
mais automatique en Eiffel 7.x
re-vérifie les features héritées dans le descendant
dans l’exemple, erreur VEVI
make_with_names (a_first, a_last: STRING)
* do
* * Precursor (a_first, a_last)
* * create taxi.make
* end
73. Void-safety
No void safety
compilation à l’ancienne, pas de contrôle
On demand void safety
vérifie l’initialisation des références attachées
Complete void safety
applique tous les contrôles
74. Void control
Void safety
le compilateur garantit l’absence d’appel sur Void
Void confidence
le programmeur a confiance dans son code
par contrat et/ou “On demand void safe”
choix réaliste et suffisant ?
75. Etat des lieux, Group S
2012
EiffelBase : Void-safe
EWF : Void-safe
ECLI/EPOM : Void-confident
Gobo : pas encore Void-safe (en cours)
EPOSIX : pas Void-safe
76. Donc, en pratique
on peut viser la void confidence
au moins jusqu’à la migration de Gobo
remplacer EPOSIX par EiffelBase, si possible
le projet doit être “On demand Void-safe”
et utiliser les nouveaux ECF
77. Syntax
Obsolete/transitional/standard syntax
note remplace indexing comme mot-clé
Provisional syntax
éléments en cours de normalisation
recommendation : Standard syntax
ISO 25436/ECMA 367
78. Type attachment
attached ➡ detachable
Are types attached by default?
True, pour tout nouveau projet
conseillé pour un nouveau projet
False préserve l’ancien comportement
réservé aux conversions complexes
82. De nouvelles habitudes
le void-safe est un outil
comme le système de type
comme les contrats logiciels
comme l’encapsulation
pour construire des systèmes plus robustes
Void-safe n’est pas un but en soi
83. Dans le code applicatif
minimiser les références détachables
pas un but de les éradiquer mais un moyen
mais il faut… se défaire de mauvaises habitudes
Void veut-il dire quelque chose : quoi ?
peut-on l’exprimer de façon plus claire ?
quelques patterns pour nous aider
84. Pattern du zéro pointé 1
took
CAMERA ARRAY[PHOTO]
count = 54
1
54
PHOTO
took
CAMERA ARRAY[PHOTO]
count = 0
85. Référence détachable
à éviter
took
CAMERA
Void ici signifie
pas de photo donc zéro photo
soyons plus explicite encore
86. si la référence est détachable
if attached took as a_took then
* accross a_took as i loop i.item.something end
end
si la référence est attachée
accross took as i loop i.item.something end
toujours aussi correct mais plus lisible
zéro passage dans la boucle…
87. Quand l’appliquer ?
relation dont la cardinalité Ø-n
structure (tableau, liste,…), vide pour Ø
attention aux “faux null”
par exemple, dans la BD, la foreign key est nulle
88. Pattern de la fourmi 2
inherit ANY redefine default_create end
create default_create
feature {NONE} -- Constructor
* default_create
* * do
* * * Precursor
* * * create text.make_empty
* * end
89. Référence attachée
lazy initialisation
on accepte l’initialisation par défaut (Void)
donc la référence doit être détachable
if attached text as a_text then à éviter
* a_text.append (something)
end
habituellement c’est une optimisation à priori
91. Et, bien entendu…
make (a_text: STRING)
* do
* * default_create
* * text.copy (a_text)
* end
make_as_mirror (a_text: STRING)
* do
* * default_create
* * text.copy (a_text.mirrored)
* end
92. Choisir le défaut
chaîne vide
message informatif
“Nom inconnu”
données de test
“4200 0000 0000 0000”
voir aussi le pattern Iznogoud
93. Et pour les invariants…
confus, on n’est pas tenté de l’écrire ou de le lire
invariant
* valid: card /= Void implies is_valid (card)
facile à écrire, lisible
le défaut est une carte de test, donc valide
invariant
* valid: is_valid (card)
94. Quand l’appliquer ?
dès qu’une valeur par défaut raisonnable existe
attention aux “faux nulls”
par exemple, dans la BD, la colonne est nulle
coût
légère surconsommation mémoire
95. Pattern de la roue libre 3
feature -- Access
* message: STRING
* * attribute
* * * create Result.make_empty
* * end
initialise à une valeur par défaut
96. Attribut
si on l’initialise directement
message := "Hello world!"
le code de l’attribut ne sera pas exécuté
lazy initialisation automatique…
derrière le compilateur doit insérer des tests
donc c’est parfois plus coûteux que la fourmi
98. Quand l’appliquer ?
similaire à la fourmi
dès qu’une valeur par défaut raisonnable existe
meilleur quand le coût de création est plus élevé
par exemple, une requête BD…
attention aux “faux nulls”
par exemple, dans la BD, la colonne est nulle
99. Pattern Iznogoud 4
pourquoi avoir une référence à Void
bogue : erreur d’initialisation ☞ Void-safe
lazy initialisation ☞ zéro pointé, fourmi, roue libre
valeur inconnue : Void a une sémantique métier
la plupart des routines ne font pas de différence
valeur par défaut et valeur inconnue
100. Vive la logique booléenne
owner
ACCOUNT: 123 PERSON: Jack
is_nogood: False
owner
ACCOUNT: 345 PERSON: John
is_nogood: False
owner
ACCOUNT: 567 PERSON: Unknown
is_nogood: True
101. Inversons la charge
soit la valeur est utilisable, soit elle est inconnue
or la plupart des routines appliquent un défaut
if owner = Void then
* print ("Unknown")
else
* print (owner.name)
end
mais on doit répéter le test partout
102. la plupart des routines se contentent du défaut
name: STRING attribute Result := "Unknown" end
print (owner.name)
quelques routines traitent différemment le cas inconnu
if (owner.is_nogood) then
* database.store_name_as_null
else
* database.store_name (owner.name)
end
104. Quand l’appliquer ?
extension des patterns précédents
dès qu’une valeur par défaut raisonnable existe
mais quelques routines ont un traitement spécial
erreur, avertissement, BD, etc.
peu d’algorithmes n’acceptent pas la valeur par défaut
mais il y en a dans la classe
105. Pattern de l’héritier maudit 5
owner
ACCOUNT: 123 REAL_PERSON: Jack
owner
ACCOUNT: 345 REAL_PERSON: John
owner
ACCOUNT: 567 UNKNOWN_PERSON
<<deferred>>
PERSON
REAL_PERSON UNKNOWN_PERSON
106. Quand l’appliquer ?
relation Ø-1
pour l’objet lié
des valeurs/traitements par défaut existent
variante plus intelligente du “Null object pattern”
inconvénient : deux classes supplémentaires
avantage : isole les créations par défaut
108. Quand l’appliquer ?
relation Ø-1
pour l’objet lié
des valeurs par défaut raisonnables existent
ou des traitements par défaut
difficile de modifier le graphe d’héritage
librairie ou lisibilité
109. Pattern de l’objet relation 7
ACCOUNT: 123 PERSON: Jack
OWNING
ACCOUNT: 345 PERSON: John
OWNING
ACCOUNT: 567
110. Quand l’appliquer ?
relation Ø-1
on souhaite enrichir la relation
il n’y a pas de valeurs par défaut raisonnables
inconvénient
la navigation entre les 2 objets est indirecte
111. Pattern detachable 8
quand on ne peut pas travailler avec une valeur
inconnu ou inapplicable
de façon répétitive et très nombreuse
alors Eiffel offre une primitive :Void
le compilateur va garantir qu’on n’oublie pas un test
112. if touch.is_nogood then
* touch.do_special
else
* touch.do_regular
end
if touch /= Void then
* touch.do_special
else
* touch.do_regular
end
chou vert et vert chou, quand c’est fréquent
113. Quand l’appliquer ?
selon la fréquence de ces tests
si > 85% des algorithmes
avantage
le compilateur garantit qu’on oublie pas un test
115. Table des matières
APPLICATION
la racine du système
IDEA
un concept dans une arborescence/Mind Map
nom, description
tableau d’enfants
116. Tout est détachable !
malheureusement
développeur n’a pas perçu le bénéfice du Void-safe
donc le code est difficile à lire, peu maintenable
votre mission
supprimer les detachables, test /= Void, etc.
en utilisant les 8 premiers patterns
121. Pré-conditions
parameter_not_void: parameter /= Void
devenues inutiles pour les paramètres attachés
laissez-les, au moins dans un premier temps
elles faciliteront d’autres aspects de la conversion
elles permettent de compiler en void-safe ou non
122. if … /= Void
le plus gros effort
indiquent que Void a un signification
ils sont toujours vrais…
donc on doit adapter un test
à analyser au cas par cas
123. Ajuster les tests
en utilisant nos patterns
ré-écrire le test
if stuff.is_nogood then
utiliser une valeur par défaut ou un tableau vide
ou le rendre detachable
préconditions /= Void, pas de problème
124. Compromis de contrat
préserver autant que possible le contrat
mais le défaut à changer entre attaché/détachable
à évaluer au cas par cas
pour l’appelant, il est plus facile que vous
retourniez des attachés
acceptiez des détachables, si Void était permis