Sommaire
- Introduction
- Fonctions partielles et fonction totales
- Méthode par contrat
- Méthode par valeur sentinelle
- Méthode par flag de réussite
- Méthode par exception
- Bilan
- C++17 et
std::optional
-
Une nouvelle API pour
std::optional
- Conclusion
Introduction
Ce journal discute de la gestion d'erreur lors des appels de fonction. Dans la première partie, nous discuterons plusieurs solutions de gestion d'erreur classiques rencontrée dans de nombreux languages comme C, C++, Python, etc. Dans la seconde partie, nous présenterons std::optional<T>
, une nouvelle classe de la librairie standard C++17 et nous discuterons comment celle-ci change la gestion d'erreur. Pour finir je donnerai un avis personnel sur l'API de cette classe que je n'aime pas, et je proposerai une solution alternative inspirée de ce qui se fait dans d'autres langages, comme swift, rust, ocaml, haskell, etc.
Ce journal s'adresse au développeur débutant dans sa première partie. Le développeur débrouillé pourra être intéressé par la présentation de std::optional
de la seconde partie. Enfin la partie finale sera sûrement le point de départ de nombreux débats (constructifs) et pourra intéresser tout le monde, mais surtout le développeur confirmé.
Ce journal est clairement MON avis sur la question, et de nombreux points sont largement subjectifs, ils sont issus de mon expérience personnelle et je vous invite à m'incendier dans les commentaires si vous n'êtes pas d'accord ;)
Note : je m'intéresse uniquement au traitement des optionals, donc j'ignore certains détails de bonnes pratiques de programmation ou d'optimisation ou de cas particuliers, ainsi inutile de troller sur le fait que certains de mes exemples n'exploitent pas la move sémantique et copient inutilement des chaînes, je sais ;)
Fonctions partielles et fonction totales
Il existe de nombreuses fonctions qui peuvent échouer, on appelle cela des fonctions partielles. Quelques exemples que nous utiliserons au cours de ce document.
- Une fonction
maximum
qui donne la valeur maximum d'une liste. Que se passe-t-il si la liste est vide ? - Une fonction
recherche
qui cherche un élément dans un tableau et retourne l'indice où celui-ci se trouve. Que se passe-t-il si l'élément n'est pas dans le tableau ? - Une fonction
lireFichier
qui retourne le contenu lue dans un fichier. Que se passe-t-il si le fichier n'est pas lisible ou n'existe pas ?
Pour toutes ces fonctions, il convient de mettre en place une politique de gestion d'erreur adaptée. Nous en discuterons plusieurs :
- La politique du contrat.
- La méthode des valeurs sentinelles
- La méthode du flag de réussite.
- La méthode par exception
- La méthode par optional.
Méthode par contrat
Cette méthode est de loin la plus simple, il suffit de placer un contrat avec l'utilisateur de la fonction disant qu'il ne doit appeler la fonction que dans un contexte où elle doit réussir.
Cette approche est utilisé par exemple en C++ pour l'opérateur operator[]
d'accès aux cases d'un tableau sur un std::vector
. Dans le cas où on essaye d'accéder à une mauvaise case, le comportement du programme est indéfini.
Cette méthode est très simple, mais force le développeur à s'assurer que le contrat est respecté avant l'appel de fonction. C'est souvent contre productif voir impossible. Dans le cas de la fonction recherche
il faudrait parcourir une première fois la structure pour vérifier que l'élément est dedans avant d'appeler la fonction recherche
pour savoir où il est. Dans le cas de la fonction lireFichier
, c'est carrément impossible puisque pour savoir si on peut lire un fichier, il faut le lire, et qu'il n'y a aucune garantie qu'un fichier lue à un instant t sera lisible à l'instant t+1.
Je n'apprécie pas cette méthode car elle est source de nombreux bugs difficiles à trouver.
Méthode par valeur sentinelle
L'idée ici étant d'utiliser une valeur de retour particulière pour matérialiser l'erreur.
C'est une approche très souvent utilisée dans de nombreuses librairies. Par exemple, en C, nous avons la fonction fopen
FILE *fopen(const char *path, const char *mode);
chargée d'ouvrir un fichier. En cas de réussite, la fonction retourne un pointeur vers un objet utilisé par la suite pour traiter le fichier. En cas d'échec, elle retourne un pointeur NULL
.
Dans le cas de la méthode recherche
qui renvoie l'indice dans un tableau d'un élément recherché, on pourrait renvoyer une valeur négative, puisque les indices dans un tableau sont toujours positifs. L'usage se ferait ainsi de la manière suivante :
// prototype de la fonction
int rechercher(const Collection &c, const Item item);
//...
int offset = rechercher(maCollection, monItem);
if(offset >= 0)
{
std::cout << "Item trouvé à la position " << offset << std::endl;
std::cout << "La valeur de l'item est " << maCollection[offset] << std::endl;
}
else
{
std::cout << "Item non trouvé".
}
Cette méthode ne peut cependant pas s'appliquer à tous les cas de figure. Quelle valeur sentinelle pourrait renvoyer la fonction maximum
? Celle-ci devra être garantie de ne pas pouvoir être confondue avec une valeur qui serait le vrai résultat de la fonction.
Cette méthode rend l'erreur facile, en effet, il est aisé d'oublier de tester la réussite, ainsi le code suivant, qui semble anodin, est faux :
int offset = rechercher(maCollection, monItem);
std::cout << "Item trouvé à la position " << offset << std::endl;
std::cout << "La valeur de l'item est " << maCollection[offset] << std::endl;
En effet, si l'élément n'est pas trouvé, offset vaut -1
et maCollection[offset]
n'a pas de sens.
Méthode par flag de réussite
Ici la fonction vas renvoyer un flag, souvent un booléen pour matérialiser la réussite. La valeur de retour étant en fait passée par référence et modifiée.
// prototype de la fonction
bool rechercher(const Collection &c, const Item item, int &offset);
//...
int offset;
bool res = rechercher(maCollection, monItem, offset);
if(res)
{
std::cout << "Item trouvé à la position " << offset << std::endl;
std::cout << "La valeur de l'item est " << maCollection[offset] << std::endl;
}
else
{
std::cout << "Item non trouvé".
}
Cette approche corrige une des limitations de la méthode par valeur sentinelle, elle peut fonctionner pour n'importe quelle fonction, puisque il n'est pas nécessaire de trouver une valeur sentinelle adaptée, la réussite étant matérialisée par le booléan. Cependant on peut toujours oublier de tester le booléan de résultat et ainsi utiliser la valeur de offset
qui serait non initialisée (ou initialisée par défaut avec une valeur fausse).
Méthode par exception
Cette méthode est plus souvent utilisée dans des langages comme Python. Par exemple, la fonction de recherche d'un élément dans une liste vas soit renvoyer l'indice de l'élément, soit va lever une exception qui va remonter la pile d'appel jusqu'à être interceptée ou jusqu'à terminer le programme.
>>> # cas qui fonctionne
>>> [1,2,3].index(2)
1
>>> # cas qui renvoie une exception
>>> [1,2,3].index(4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: 4 is not in list
La méthode par exception a de nombreux avantages :
- Si on ne traite pas l'exception, elle finira le programme, souvent avec un message d'erreur explicite, ce qui évite les bugs dissimulés où la valeur sentinelle ou non initialisée est utilisée.
- Le code n'est pas complexifié par une gestion d'erreur avec de nombreux if
- La gestion d'erreur peut être repoussée à plus tard, dans une fonction appelante.
La gestion de l'exception se fait, en Python, avec un bloc try / except
:
try:
offset = maCollection.index(monItem)
print("l'element a été trouvé, il est en position", offset, "et c'est", maCollection[offset]
except: # Normalement il ne faut pas attraper TOUTES les exceptions, mais c'est un tutoriel simplifié
print("Élement non trouvé")
Le C++ gère aussi les exceptions, mais la librairie standard ne s'en sert pas. Pour différentes raisons que je n'aborderais pas, la communauté du C++ n'aime pas les exceptions et peu de librairie les utilisent correctement.
Cependant, rien ne force le développeur à gérer les exceptions et rien n'indique à celui-ci qu'une fonction peut lancer une exception, si ce n'est une lecture scrupuleuse de la documentation. Certains langages, comme Java, proposent un système d'exception qui force le développeur à gérer celles-ci ou à les propager explicitement à la fonction appelante.
C'est donc à mon sens une solution intéressante, mais il est trop facile d'oublier de traiter un cas exceptionnel. Heureusement, celui-ci se traduira par une exception qui finalisera le programme, mais en tant que développeur j'aurais aimé que mon compilateur m'assure lors du développement que je n'avais pas raté ce cas.
Bilan
Dans l'état actuel des choses, nous avons des méthodes qui laissent beaucoup de place à l'erreur potentielle et qui forcent à bien lire la documentation de chaque fonction pour distinguer les valeurs sentinelles, comprendre le fonctionnement du flag de réussite, ou connaître la liste des exceptions pouvant apparaître.
La méthode par exception est sans doute la plus propre de toutes, mais il est difficile dans les langages qui ne gèrent pas les exceptions vérifiées (Comme C++, Python, etc.) d'être certain d'avoir traité toutes les exceptions possibles.
En dernier point, touts ces méthodes sont très implicites et il n'est pas évidant de repérer les oublis de gestion d'erreur lors d'une lecture de code.
C++17 et std::optional
std::optional vas apparaître avec C++17 et une implementation était disponible dans Boost.Optional. Il s'agit d'une classe qui peut contenir ou pas une valeur. L'objet peut être testé avec une méthode has_value
et la valeur peut être récupérée le cas échéant avec la méthode value
. Vous pouvez dès à présent tester std::optional
dans le namespace std::experimental
dans de nombreux compilateurs. Reprenons notre exemple de la fonction rechercher :
// prototype de la fonction
std::optional<int> rechercher(const Collection &, const Item &);
// ...
std::optional<int> offsetO = rechercher(maCollection, monItem);
if(offsetO.has_value()) // ou tout simplement if(offset)
{
int offset = offsetO.value();
std::cout << "Item trouvé à la position " << offset << std::endl;
std::cout << "La valeur de l'item est " << maCollection[offset] << std::endl;
}
else
{
std::cout << "Item non trouvé".
}
Cette approche est très proche de l'approche par flag de réussite, sauf que le flag de réussite est contenu dans l'optional
en même temps que la valeur de retour. Je trouve personnellement que cela rend l'API de la fonction plus lisible car on distingue bien en argument les entrées et en valeur de retour les sorties pouvant ne pas réussir. Comparons nos anciens prototypes :
// Méthode par valeur sentinelle, le prototype ne donne aucune
// information sur les erreurs possibles, il faudra lire la documentation
int rechercher(const Collection &, const Item &);
// Méthode par flag de réussite, le prototype nous laisse penser qu'il
// y a quelque chose à faire, d'autant que la valeur passée par référence
// n'est pas const, ce qui devrait nous alarmer, mais c'est tout de même
// flou.
bool rechercher(const Collection &, const Item &, int &);
// Méthode par exception, le prototype ne liste pas les exceptions.
int rechercher(const Collection &, const Item &);
// Méthode par optional, le prototype met en évidence la présence d'un
// résultat qui peut ne pas être là.
std::optional<int> rechercher(const Collection &, const Item &);
De plus, on ne peut pas utiliser directement l'optional<T>
en lieu
et place de la valeur contenu, il faut explicitement aller le chercher
avec la méthode value
.
Cette méthode est donc attrayante à mes yeux, mais on peut encore se tromper en allant directement chercher la valeur avec value
sans tester sa présence :
int offset = rechercher(maCollection, monItem).value();
Heureusement, la fonction value
lève une exception dans ce cas là, donc tout n'est pas perdu.
Une autre limitation concerne la soupe de code qui apparaît lorsque on travaille avec de nombreuses méthodes retournant des optional. Imaginons que nous avons les méthodes f
, g
et h
tel que :
std::optional<B> f(Arg);
std::optional<C> g(B);
D h(C);
et que nous voulions construire la méthode std::optional<D> foo(Arg)
. Notre code risque de ressembler à ceci :
std::optional<D> foo(Arg arg)
{
auto fResO = f(arg);
if(!fResO)
{
return std::optional<D>{}; // un optional vide
}
auto fRes = fResO.value();
auto gResO = g(fRes);
if(!gResO)
{
return std::optional<D>{}; // un optional vide
}
auto gRes = gResO.value();
return std::optional<D>{h(gRes));
}
Et encore, je vous ai épargné la cascade de if imbriqué. Ce code est lourd et fastidieux et potentiellement source d'erreur. Pourrait-on faire mieux ?
Une nouvelle API pour std::optional
Je reproche deux choses à std::optional
dans son état actuel :
a) La libraire est source d'erreur car on peut appeler la fonction value
alors que l'optional ne contient rien. Il faut en théorie s'assurer qu'il contient bien une valeur avant, et rien ne force cette vérification.
b) Il est difficilement composable, en effet, l'utilisation de plusieurs std::optional
est souvent source d'une cascade de if imbriqués qui font mal aux crane et qui sont sources d'erreurs.
Sécurité
Nous allons nous attaquer au premier point. Ce que je veux c'est une fonction qui, connaissant un optional, fasse un traitement si celui-ci contient une valeur et pas de traitement sinon.
Dit autrement, je veux cette fonction, que j'appelle arbitrairement optionalApply
:
template<typename T1, typename F>
void optionalApply(std::optional<T1> o, const F &f)
{
if(o)
{
f(o.value());
}
}
Simple non ? Elle effectue l'action f
si l'optional est bon, et pas d'action sinon. F
est un type templaté qui peut contenir au choix des fonctions, des lambdas, ou des objets fonction. On pourra donc écrire le code de recherche d'un élément dans un tableau de la façon suivante :
optionalApply(rechercher(maCollection, monItem), [&](int offset)
{
std::cout << "Item trouvé à la position " << offset << std::endl;
std::cout << "La valeur de l'item est " << maCollection[offset] << std::endl;
});
En se servant des fonctions lambda de C++11. Je l'avoue, la syntaxe est particulière du fait des lambdas, mais ici on obtient un code ou il est impossible de se tromper. Par contre je n'ai pas géré le cas o ùon veuille faire quelque chose si l'optional ne contient rien. Autant faire une nouvelle fonction, optionalCase
template<typename T1, typename FOk, typename FNotOk>
void optionalCase(std::optional<T1> o, const FOk &fOk, const FNotOk &fNotOk)
{
if(o)
{
fOk(o.value());
}
else
{
fNotOk();
}
}
Ce qui nous donne maintenant :
optionalCase(rechercher(maCollection, monItem),
[&] (int offset)
{
std::cout << maCollection[offset] << std::endl;
},
[] ()
{
std::cout << "Item non trouvé" << std::endl;
}
);
On pourra débattre de la lisibilité du truc, mais maintenant, en tout cas, on ne peut plus se tromper.
Composabilité avec map
Maintenant la question c'est comment chaîner plusieurs fonctions qui peuvent renvoyer des optionals et ce facilement, en limitant la présence de variables intermédiaires (les optionals et leur valeur) et en limitant l'erreur au maximum.
Pour cela on va écrire la fonction map
, variante de optionalApply
qui contient un type de retour :
template<typename T1, typename T2, typename F>
std::optional<T2> map(std::optional<T1> o, const F &f)
{
if(o)
{
return std::optional<T2>(f(o.value()));
}
return std::optional<T2>(); // un optional vide
}
Cette fonction nous permet donc d'appliquer n'importe quelle fonction de transformation de A
vers B
sur un std::optional<A>
vers un std::optional<B>
. Exemple d'utilisation, on veut calculer la longueur du contenu d'un fichier, sachant que on possède une fonction std::optional<std::string> readFileContent(const std::string &path)
.
// Solution sans map
std::optional<size_t> getFileLength(const std::string &path)
{
auto contentO = readFileContent(path);
if(contentO)
{
auto content = contentO.value();
return std::optional<size_t>(content.size());
}
else
{
return std::optional<size_t>(); // optional vide
}
}
// Solution avec map
std::optional<size_t> getFileLength(const std::string &path)
{
return map(readFileContent(path), [] (std::string content)
{
return content.size();
});
}
La solution avec map est plus simple. Notons que on pourrait faire encore plus simple et plus lisible en exploitant C++14 et C++17 pour la détection automatique des type des lambdas et si map
était une méthode de optional, on pourrait avoir quelque chose du genre :
std::optional<size_t> getFileLength(const std::string &path)
{
return readFileContent(path).map([] (auto content)
{
return content.size();
});
}
Notons aussi que le lambda n'est pas obligatoire si la fonction à chaîner existe déjà.
Composabilité avec bind
Nous ne sommes toujours pas capables de traiter le cas présenté initialement avec les fonctions f
, g
et h
car celles-ci ne sont pas des fonctions de A
dans B
, mais de A
dans std::optional<B>
, ce qui complexifie un peu les choses, nous allons introduire une nouvelle fonction, bind
qui gère ce cas.
template<typename T1, typename T2, typename F>
std::optional<T2> bind(std::optional<T1> o, const F &f)
{
if(o)
{
return f(o.value());
}
return std::optional<T2>(); // un optional vide
}
On peut donc maintenant traiter notre cas initial, je vous rappelle :
// sans map ni bind
std::optional<D> foo(Arg arg)
{
auto fResO = f(arg);
if(!fResO)
{
return std::optional<D>{}; // un optional vide
}
auto fRes = fResO.value();
auto gResO = g(fRes);
if(!gResO)
{
return std::optional<D>{}; // un optional vide
}
auto gRes = gResO.value();
return std::optional<D>{h(gRes));
}
// avec map et bind
std::optional<D> foo(Arg arg)
{
return map(bind(f(arg), g), h)
}
// avec map et bind en fonction membre des optionals
std::optional<D> foo(Arg arg)
{
return f(arg).bind(g).map(h);
}
Voila, c'est plus simple et moins source d'erreur.
Pour finir, que faire si en bout de chaîne on veux une valeur par défaut si il n'y a pas de valeur dans l'optional, et bien il existe value_or
qui est déjà dans le standard c++17 qui permet de récupérer la valeur ou une valeur par défaut, ce qui permet d'écrire :
std::optional<D> fooWithDefault(Arg arg, D def)
{
return map(bind(f(arg), g), h).value_or(def);
}
Conclusion
Je vous ai présenté différentes méthodes de gestion des fonctions partielles. Pour ma part, mon coeur balance entre les optionals (avec map
et bind
) et les exceptions. Les optionals assurent une sécurité importante du code, une grande composabilité et assurent que l'erreur est traitée. Les exceptions ont moins de garantie, mais il existe des cas où on ne veut pas traiter l'erreur et où il est préférable d'avoir une exception.
Ces deux approches sont utilisables en C++, en Python (mais avec moins de garantie à la compilation) et dans de nombreux langages comme Haskell (mon préféré), Rust, Swift, Ocaml, j'en oublie des milliers. L'approche par optional a une place de premier ordre dans certains langages. Le C++ propose depuis son standard C++17 une API d'optional, mais je la trouve limitée et je vous ai proposé des extensions.
Les fonctions map
, bind
, optionalCase
et optionalApply
que je vous ai proposées sont utilisables comme cela, mais pour en faire une vraie librairie il faudrait prendre en compte de nombreux cas particuliers, notamment pour traiter le cas où les objets contenus dans l'optional ne peuvent pas être copiés.
# Ouai bof
Posté par ckyl . Évalué à 5.
Je ne fais pas de C++ mais vu ce que tu écris je suis d'accord avec ton constat. Un optional avec une méthode de test et de déréférencement n'apporte pas grand chose à retourner possiblement null. Les deux sont vérifiables avec une analyseur statique de code et les deux permettent de faire des erreurs… Et au passage si tu es dans un langage ou le compilo/runti,e ne peut pas virer l'optional et bien tu viens de prendre un accès mémoire en plus…
D'ailleurs bien que l'
Optional
de Java soit loin d'être puissant ou génial, c'est un des premier truc que j'explique aux gens. Si tu utilisesisPresent()
suivi d'unget()
alors tu fais certainement une erreur car tu n'as rien gagné du tout. Les API fonctionnelles elles empêchent toute erreur.Sur le sujet des différents types de gestions d'erreur et les approches des différents langages, je me souviens de cet excellent article: The error model
[^] # Re: Ouai bof
Posté par ribwund . Évalué à 4.
L'interet de optional en c++ c'est justement d'eviter de renvoyer un pointeur (mais sinon ca apporte pas plus).
Avec optional l'ownership est explicite, et ca fonctionne avec les move et RVO.
Perso je suis assez fan, et j'utilise pas mal (avec StatusOr pour la même chose en plus "lourd" https://github.com/google/certificate-transparency/blob/master/cpp/util/statusor.h).
[^] # Re: Ouai bof
Posté par groumly . Évalué à 9.
Yep. Et en java en plus, ton optional peut techniquement être null, ce qui doit être assez cocasse de se prendre un npe sur un optional.
Les,optional, c'est utile quand c'est intégré au cœur du langage (cf Swift, kotlin, et j'imagine d'autres aussi), et que t'as des constructions idiomatiques pour les gérer (genre le guard let de Swift).
Si c'est pour remplacer if foo != null par sa version object if foo.isPresent(), se taper des generics de partout et alourdir tout le reste du code avec des foo.get(), ca améliore les choses que marginalement.
Linuxfr, le portail francais du logiciel libre et du neo nazisme.
[^] # Re: Ouai bof
Posté par Guillaum (site web personnel) . Évalué à 2.
Tout à fait d'accord sur l'histoire du pointeur null, en java cela aidera pas. En C++ tu peux éviter de mettre des pointeurs dans des optionnels et dans ce cas là tu auras moins de problème.
Personnellement mon optional maison lève une exception si on lui met un pointeur null dedans.
[^] # Re: Ouai bof
Posté par rvlander . Évalué à 1.
Beaucoup des langages (tous?) qui gère les union types à paramètre gèrent celaen natif. On pense en premier lieu aux langages fonctionnels (Haskell, OCaml …), mais on peut aussi penser à Rust qui intègre le type Option dans sa librairie standard. Rust reprend beaucoup des principe du fonctionnel en son cœur même si il ne l'est pas.
Dans ces langages, lorsque l'on fait appel à une fonction qui retourne un tel type, on est obligé de gérer tous les cas (le compilateur nous y force). Par ailleurs un sucre syntaxique (pattern matching), nous permet de faire cela de manière concise. Ces langages fournissent aussi en natif des fonctions map, bind pour ces types.
En gros ces types ont été transposés dans Java et C++, mais les compilateurs de ces derniers ne présentant pas les mêmes garanties, l'effet de l'utilisation de Optional est moins prégnant surtout en terme de possibilité de bug. Cependant, ça me parait tout de même utile ne serait-ce que pour la concision que ça apporte.
La mode étant au pensons fonctionnel, beaucoup de ses principes sont transposés dans les langages populaires. Cependant, cela s'effectue toujours avec un perte de garantie et de sécurité car ces derniers n'ont pas été pensés pour faire du fonctionnel initialement.
Quant à savoir pourquoi tout le monde s'obstine avec ces langages bâtards pour leur nouveaux projets n’ayant pas besoin de performances extrêmes, c'est un autre histoire…
# C++ et exceptions
Posté par François (site web personnel) . Évalué à 4.
Je ne fais pas du tout de C++ et du coup ce passage m'intrigue profondément. Je ne veux pas lancer de troll hein, mais ça m'aurait vraiment intéressé de connaître ces raisons.
Sinon super article, très intéressant ;)
[^] # Re: C++ et exceptions
Posté par Guillaum (site web personnel) . Évalué à 5.
Alors, en premier lieu, je dois corriger un point faux (imprécis) de mon article. La librairie standard c++ utilise les exceptions, mais dans certains cas limités, comme une allocation ratée. Mais l'ouverture d'un fichier, la recherche d'un élément qui n'existe pas, …, sont gérées avec des fonctions de test
is_open
, ou des valeurs sentinellesconteneur.end()
.Concernant l'usage, certains se plaignent des performances ou de l'augmentation de la taille du code, mais c'est très controversé et discutable en fonction du contexte de travail.
Un autre problème, soulevé dans mon journal, est que les exceptions ne sont pas du tout listées dans le prototypes des fonctions, et donc qu'il est difficile de savoir, sans lire la documentation, qu'une fonction peut lancer une exception, et les choses peuvent évoluer sans prévenir.
Un autre point important est la difficulté d'écrire du code qui résiste bien aux exceptions, l'idée étant que si une exception est lancée, tu dois pouvoir te retrouver dans un état stable et prévisible après récupération de l'exception, sinon il ne sert à rien de survire à l'erreur si c'est pour se retrouver dans un état indéterminé.
[^] # Re: C++ et exceptions
Posté par rewind (Mastodon) . Évalué à 5.
Elle l'utilise aussi dans
vector::at
(et d'autres du même genre commestring::at
) qui est l'équivalent de l'accès indicé (celui avec les accolades) mais avec vérification du domaine. Et il y a les fonctions de conversion genrestd::stoi
qui peuvent renvoyer des exceptions. Il y a aussi des fonctions liées au système commestd::thead::detach
. Et puis, il y a quasiment toutes les fonctions de std::filesystem qui ont deux versions : une avec exception et l'autre avec un code d'erreur.Bref, affirmer que la bibliothèque standard utilise les exceptions dans des cas limités est erroné. Elle utilise les exceptions pour les cas exceptionnels, ce qui est la raison d'être des exceptions.
[^] # Re: C++ et exceptions
Posté par Guillaum (site web personnel) . Évalué à 5.
Merci pour la précision des librairies qui utilisent des exceptions.
La notion de cas exceptionnels est vraiment dépendante du point de vue. C'est toujours un grand moment de cassage de tête de savoir ce que on fait passer en exception et ce que on fait passer en
std::optional
ou autre méthode.[^] # Re: C++ et exceptions
Posté par rewind (Mastodon) . Évalué à 4.
Que penser alors de
std::optional
qui peut renvoyer une exception ? :P[^] # Re: C++ et exceptions
Posté par Guillaum (site web personnel) . Évalué à 3.
Pas avec les améliorations proposées dans ce journal ;)
[^] # Re: C++ et exceptions
Posté par fearan . Évalué à 3. Dernière modification le 05 septembre 2016 à 10:04.
tu peux aussi ajouter les dynamic_cast T& qui te jettent une exception std::bad_cast, alors que les dynamic_cast T* vont te renvoyer un pointeur null dans le cas où ce n'est pas possible.
En c++ on a le choix et c'est bien. Je déteste devoir parsemer mon code de try/catch pour une gestion qui peut s'agrémenter de if ou ?;
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
# contrats
Posté par Guillaume Knispel . Évalué à 2.
Les contrats n'impliquent pas intrinsèquement un comportement non-défini (au sens du C avec apparition de démons naseaux) lorsqu'ils sont violés.
Si l'on prend le cas d'une fonction mathématique (cad "pure" en info) on pourrait dire que le contrat est que les paramètres sont dans le domaine de définition. Ça n'implique pas en soit l'apparition de démons naseaux si l'appelant est en dehors, et il est tout à fait convenable (et même préférable) de réagir par exemple en levant une exception dans une implémentation.
[^] # Re: contrats
Posté par Guillaum (site web personnel) . Évalué à 2.
Belle traduction, j’apprécie ;)
Quand une fonction n'est pas définie pour tout son domaine, il y a plusieurs solutions :
division
avec le prototype suivantint division(int a, nonZeroInt b)
, oùnonZeroInt
est un type int qui ne peut pas contenir zéro. Mais cela force l'utilisateur à convertir et finalement cela repousse le problème plus haut dans le code.std::optional
en valeur de retour, ainsidivision
deviendraitstd::optional<int> division(int a, int b)
. Mais on repousse le problème plus bas dans le code.C'est un vrai problème et à chaque cas de figure il y a une solution qui est plus ou moins adaptée.
Une solution que j’apprécie vraiment, mais qui est peu utilisé dans la vraie vie c'est l'utilisation de type rafinés. Tu peux ajouter des contraintes sur les types de ta fonction et laisser le compilateur prouver que ces contraintes ne seront jamais violée. Sinon il peut te monter dans quel cas ton code est faux ou simplement te laisser dans le doute en t’annonçant qu'il n'a pas réussis à faire la preuve. En Haskell il y a Liquid Haskell qui est très amusant à utiliser, mais je n'ai pas de retour sur l'impact que cela peut avoir sur un gros projet.
[^] # Re: contrats
Posté par Nicolas Boulay (site web personnel) . Évalué à 2.
Des langages comme Smalltack utilisent des contrats qui sont simplement des tests en début et fin de fonctions. Ces tests s’héritent avec l'interface objet.
L’intérêt est de faire confiance à la lib de base, ce qui évite de programmer de façon défensive, ce qui a un coût en perf énorme. Les contrats permettent ici de récupérer les erreurs.
Le problème des types complexes, c'est qu'un humain a du mal à s'en sortir avec. Comprendre simplement les type GADT de Ocaml, n'est pas simple du tout. Et pourtant les possibilités sont limités.
"La première sécurité est la liberté"
# Liberté de l'utilisateur
Posté par arnaudus . Évalué à 2.
Merci pour cette présentation claire de l'état de l'art sur la gestion des erreurs. En fait, j'ai quand même l'impression que ce domaine est peut-être l'un de ceux où les langages de programmation ont le plus de marge de progression. En particulier, il s'agit toujours d'un compromis entre la souplesse et la robustesse, entre une approche quasiment mathématique (obligation de gérer toutes les causes d'échec) et une approche pragmatique.
Par exemple, ce qui me gène pas mal avec std::optional, c'est qu'on impose à l'utilisateur une syntaxe particulière (en gros, on reporte sur l'utilisateur le traitement des erreurs). L'autre problème, c'est qu'il ne semble pas facile de distinguer les "warnings" (du style "attention, il y a quelque chose qui ne se passe pas comme prévu") et les erreurs ("la valeur de retour n'a pas de sens").
Honnêtement, le meilleur système que j'ai trouvé pour les projets de taille moyenne (code scientifique principalement destiné à tourner en "interne", ne doit pas faire n'importe quoi mais pas d'aspect critique—en particulier, mieux vaut planter que de rester dans un état douteux), c'est la logique de certains langages interprétés, où les fonctions peuvent retourner quelque chose comme "NA" et/ou émettre des warnings, ce qui fait que le code n'est jamais dans un état réelement indéterminé (au pire, f(NA) renvoie NA). Du coup, on peut développer sans se prendre la tête avec le traitement des erreurs, et on peut ratrapper le coup à n'importe quel niveau. Le côté négatif, évidemment, c'est que l'erreur n'est pas identifiée clairement par le code de retour, et que les détails sont dans stderr, donc plus ou moins inaccessibles au programme.
[^] # Re: Liberté de l'utilisateur
Posté par fearan . Évalué à 2.
Crée ton optional avec levée d'exception si tu y accède alors qu'il n'existe pas, ou utilise value_or pour avoir une valeur avec du sens; je ne vois pas vraiment ce que l'on peut faire de mieux.
Il vaut mieux lire l'interface exposée ici pour se faire une idée.
http://en.cppreference.com/w/cpp/experimental/optional
pour son exemple
Je me contenterai de
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
[^] # Re: Liberté de l'utilisateur
Posté par arnaudus . Évalué à 3.
Ça rajoute une couche de complexité qui n'est pas forcément nécessaire. Je sais bien que le C++ est un langage verbeux et qu'on peut changer de langage si on aime les trucs concis, mais personnellement ça me gêne de devoir appeler ".value()" pour accéder à la valeur cherchée. Je comprendrais le mécanisme des optional s'il y avait une conversion implicite de optional vers T, ce qui permettrait, au choix, d'utiliser la fonction de manière transparente, ou de gérer les erreurs si le contexte le demande.
C++ est devenu un truc tellement gros que j'ai l'impression que la plupart des projets en utilisent un sous-ensemble, du style sans STL, sans exceptions, sans templates, sans héritage multiple, sans pointeurs, sans lambdas, etc. Si tu publies une bibliothèque dont la gestion d'erreur repose sur des optional, tu forces les utilisateurs à adopter cette méthode, même si ça ne correspond pas du tout à la manière dont, en interne, ils gèrent les erreurs. Je ne sais pas si le jeu en vaut la chandelle.
[^] # Re: Liberté de l'utilisateur
Posté par Guillaum (site web personnel) . Évalué à 3.
Personnellement je déteste les conversions implicites, c'est subjectif, mais je sais que je vais me faire avoir à un moment donné.
En raisonnant de cette manière on ne fait aucune évolution et on reste à faire du C avec classes comme l'était les premières versions de C++.
[^] # Re: Liberté de l'utilisateur
Posté par arnaudus . Évalué à 2.
Ça serait vrai si la gestion des erreurs par les optional était une solution universelle et consensuelle. Personnellement, j'ai l'impression que ça ressemble à une fausse bonne idée, qui complexifie l'interface et qui impose à l'utilisateur un paradigme de gestion des erreurs qu'il ne maîtrise peut-être pas, qu'il n'apprécie peut-être pas, ou qui n'est peut-être pas pertinent dans le cadre de son projet.
Je ne suis pas Norstradamus et j'ai peut-être tort, mais ma prédiction, c'est que si ce type de gestion d'erreur se généralise, 99% du C++ développé en 2020 sera quelque chose comme
Du coup, j'ai du mal à voir l'avantage…
[^] # Re: Liberté de l'utilisateur
Posté par Guillaum (site web personnel) . Évalué à 4.
Je ne vois pas en quoi cela complexifie l'interface ? D'un coté nous avons une interface implicite où les erreurs sont gérées par des valeurs sentinelles ou des flags, où il faut lire la documentation d'une fonction en profondeur pour comprendre son comportement et avoir de la rigueur lors de l'utilisation pour ne pas se tromper et pour laquelle tout le monde réinvente de la gestion d'erreur tous les jours. Un jour le flag de retour est un
bool
qui vauttrue
pour ok,false
sinon, l'autre jour c'est l'inverse, le troisième jour c'est un int. De l'autre nous avons une fonction dont le prototype te renseigne beaucoup sur son comportement et pour laquelle tu ne peut pas te tromper en l'utilisant. Et la seule complexification de l'interface c'est l'utilisation d'une classeoptional
contenant deux méthodes,map
etbind
(non fournies en standard je te l'accorde). On peut aussi utiliser l'approche par exception sur le.value()
comme proposée par d'autres.Pour le coté consensus, c'est tout de même une approche prise dans de nombreux langages : Rust, Swift, OCaml, Haskell, entre autre… Avec une approche plus ou moins poussée.
Pourquoi n'as tu pas poussé le vice de ton exemple jusqu'à mettre un optional sur le
+
pour montrer à quel point les optionals sont complexes ?La gestion d'optional n’apparaît que dans les fonctions qui peuvent générer des erreurs, et dans ces fonctions, il faut traiter les erreurs et soit c'est fait avec des optionals, soit avec autre chose, quoi qu'il en soit, cela apparaitra. Soit il n'y a pas d'erreur à traiter, et dans ce cas là pas de problème. Ainsi ta fonction
distance
qui est toujours définie, sauf en cas de nombre infinis, peut être écrite sans optional. Après si lesNotANumber
t’embêtes, alors tu pourras mettre des optionals pour protéger.[^] # Re: Liberté de l'utilisateur
Posté par Anthony Jaguenaud . Évalué à 3.
Pour moi, ce qui est intéressante dans ce genre de classe, c’est de surcharger tous les opérateurs, comme ça tu écris ta/tes formule/s et à la fin, quand tu as besoin de savoir le résultat, tu vérifies que ton résultat est correct avant de l’utiliser. J’avais écris un truc équivalent pour des données arrivant sur un BUS.
[^] # Re: Liberté de l'utilisateur
Posté par foobarbazz . Évalué à 3. Dernière modification le 07 septembre 2016 à 15:50.
En Haskell, on fait comme ça :
liftA4 n'existe pas en pratique, mais c'est l'idée, pour toute fonction de type a -> b -> c -> d… on calcule une fonction de type f a -> f b -> f c -> f d. Ça s'appelle un foncteur applicatif, ça permet de faire de la gestion d'erreur, mais pas que. On peut simuler du calcul non déterministe, faire des traces, du code pure qui travaille sur des IO, etc.
C'est beau.
[^] # Re: Liberté de l'utilisateur
Posté par Guillaum (site web personnel) . Évalué à 3.
Moué, en pratique cela me choque la fonction
distance
qui prend desMaybe
en paramètre… Je ne sais pas si j'ai quelque part dans mon code des fonctions qui prennent desMaybe
en paramètre (si ce n'est des combinateurs).Alors, si on suppose que
square :: Float -> Maybe Float
et quesqrt :: Float -> Maybe Float
, mais bon, parce que on est pervers, je ferais :Où
Mais là on nous prendrais pour des malades ;)
Si cela intéresse quelqu'un, je peux expliquer ce qu'est ce bordel. Cela fait peur, mais les trois opérateurs
=<<
,<$>
et<*>
sont tellement utilisés tous le temps en haskell qu'il sont un peu comme le=
, les[]
et le->
du C.[^] # Re: Liberté de l'utilisateur
Posté par Guillaum (site web personnel) . Évalué à 3.
C'est pas mal en effet. J'aime moins car je trouve que c'est plus verbeux / complexe que l'approche à base de
map
etbind
et que on peut facilement oublier de faire le traitement. Cependant j'admet que c'est une solution intéressante avec ce qui est fournit par défaut.[^] # Re: Liberté de l'utilisateur
Posté par fearan . Évalué à 3.
à noter que l'exemple n'est vraiment pas top,
si on fait if( machinOptional ), on peut ensuite utiliser *machinOptional, le value() fait une verif supplémentaire qui n'est pas nécessaire.
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
[^] # Re: Liberté de l'utilisateur
Posté par Guillaum (site web personnel) . Évalué à 2.
Oui. C'est un exemple pédagogique pour montrer que cette approche est, à mon sens, lourde. On est pas à un test dans un exemple pédagogique, mais promis, la prochaine fois, je le ferais mieux ;)
# Mauvaise connaissance du c++
Posté par fearan . Évalué à 5.
Pour la stl, les exceptions sont rare uniquement si tu le réclame, optional à un option de construction qui lui permet de jeter une exception lorsque tu utilisas values() si tu lui réclame.
Tu as aussi le value_or() qui permet d'avoir une valeur par défaut si on a rien. C'est pareil avec les ios, les dynamic_cast, les conteurs (at() au lieu de operator[]… )
Le fait d'avoir le choix est une bonne chose, je ne compte pas le nombre de
que je croise en java pour devoir gérer le fait que l'on sait qu'on a pas d'exception dans ce cas précis, mais qu'on doit faire comme si. Avec en prime le fait que le compilo pense que ma peut être null, ce qui peut faire chier au niveau de certaines annotations, donc faut encore en rajouter…
Heureusement depuis java 7 on peut cumuler les catch (catch ExecptionMachin|ExceptionTruc|ExceptionBidule|ExcetionCasseCouille|ExcetptionJeDepasseLaLigneDe120Caractere|ExceptionJenAiEncore e)
Le code java est pollué par de la gestion de truc qui n'arrivent jamais, et le jour où ça arrive, c'est tellement ignoré que ça passe à la trappe; j'ai une préférence pour faire un printstacktrace, au cas où.
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
[^] # Re: Mauvaise connaissance du c++
Posté par ckyl . Évalué à 1.
Hum ignoré ou
printStracktrace()
, dans les deux cas tu as merdé ton design quelque part. À priori même à deux endroits: définition de l'API et gestion des erreurs à l'utilisation.Normalement au pire tu utilise une API qui utilise des checked exceptions pour signaler des cas que tu considères être des faults plutôt que des contingencies (ie. tu ne peux rien faire localement sinon remonter le problème à un fault handler de plus haut niveau qui lui décidera de l'action adéquat)
Ça reste verbeux mais il n'y a pas de miracle. Les gens qui utilisent des unchecked se plaignent du côté loterie, de la gestion de compatibilité impossible etc. mais ne sont pas embêtés pour écrire du code de porc. Et les gens qui utilisent des checked se plaignent d'avoir à gérer ce qui est a gérer. Comme la flemmardise et le vite fait mal fait sont la règle dans le métier, on a tendance à plus entendre les gens qui ne veulent gérer aucun cas d'erreur.
Et comme les faults des uns sont souvent les contingencies des autres, on arrivera rarement a faire une séparation propre des deux niveaux API.
Rien de nouveau sous le soleil: http://www.oracle.com/technetwork/java/effective-exceptions-092345.html
Et depuis la nuit des temps, ce genre de code bases sont des tas de boue.
Même quand le langage est pas coopératif, c'est pas bien difficile de mettre en place des principes simples qui empêche d'ignorer ce genre d'erreurs. Au minimum tu imposes que les trucs "impossibles" soient remontés au fault handler le plus proche ou racine, plutôt que de continuer son petit bonhomme de chemin.
A choisir, pouvoir raisonner sur une base de code est beaucoup plus important que sauver deux lignes de code.
[^] # Re: Mauvaise connaissance du c++
Posté par xcomcmdr . Évalué à 2. Dernière modification le 05 septembre 2016 à 14:33.
Le problème des checked exceptions, c'est que un changement dans une méthode des types d’exceptions retournés, on doit changer tous les appelants (sinon ça compile pas). Alors que les appelants en ont rien à faire, la plupart du temps.
ça pollue le code appelant, et j'ose pas imaginer le bordel si tu mets ça dans une bibliothèque partagée publiquement, pour tous ses utilisateurs…
Source : Kent Beck. ;-)
Je crois que la gestion des cas d'exceptions et des erreurs, c'est plus compliqué que deux lignes de code. Sinon, on en parlerait pas encore aujourd'hui.
"Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)
[^] # Re: Mauvaise connaissance du c++
Posté par ckyl . Évalué à 2.
Donc tu n'as aucun problème dans le fait que n'importe quelle API que tu utilises change sa signature sans t'en informer et sans aucune information à la compilation ? Par ce que c'est exactement ce dont il s'agit. On parle d'un bon vieux
goto
non local qui peut apparaitre ou disparaitre n'importe où n'importe quand sans aucun moyen de contrôle.En fait ce qui dérange les gens c'est d'un côté de gérer correctement une API, son évolution et de l'autre la robustesse et exploitabilité d'une application.
Vu qu'on n'est déjà pas capable de le faire correctement quand un système de type est la pour nous aider, on va tout faire à runtime histoire au moins on est plus embêté par ce foutu système de type qui nous dit qu'on fait n'importe quoi en changeant la signature / fonctionnement d'une API publiée ! Et puis c'est vrai enfin quoi puisque la plupart des utilisateurs font n'importe quoi, pourquoi s'embêter ?
Tu as une analyse du problème bien plus intéressante que la mienne dans l'article que j'ai déjà cité: The error model.
Puisque tout le monde dit des conneries, Kent Beck et la maitresse inclus, l'auteur est en général bien moins intéressant que l'argumentaire.
[^] # Re: Mauvaise connaissance du c++
Posté par xcomcmdr . Évalué à 2. Dernière modification le 05 septembre 2016 à 15:52.
En quoi je devrais gérer les exceptions de tous ceux que j'appelle ? Quand pourrais-je m'occuper de mes exceptions, plutôt que de faire du support pour le code d'autrui ? A quel moment ça s'arrête ?
Critiquer le bordel que les Checked Exceptions mettent dans les responsabilités entre appelé et appelant, ce n'est pas critiquer le système de types… Faut se calmer, là…
"Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)
[^] # Re: Mauvaise connaissance du c++
Posté par ckyl . Évalué à 1.
Tu peux parfaitement critiquer.
Maintenant ce que tu critiques ce n'est pas uniquement les checked exceptions c'est le principe que les erreurs fassent partie de la signature d'une méthode.
[^] # Re: Mauvaise connaissance du c++
Posté par xcomcmdr . Évalué à 2. Dernière modification le 05 septembre 2016 à 21:22.
Ben non. Avec les exceptions normales, le changement du type/nombre d'exceptions retournables par une méthode :
1. Ne t'empêchent pas de compiler
2. Ne te forcent pas à changer (à minima) tes signatures et ton code (= t'occuper du boulot des autres).
3. Et ce, pour tous les appelants
Je ne dis pas d'ignorer les exceptions, mais au moins avec les exceptions normales on peut garder l'indépendance entre les couches du code.
Parce que la entre les checked Exceptions et (par exemple) la classe de base qui touchent les classes filles => ça donne quelque chose de très monolithique très vite.
Et surtout, les exceptions normales ne te forcent pas à violer SRP. ;-)
"Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)
[^] # Re: Mauvaise connaissance du c++
Posté par Guillaum (site web personnel) . Évalué à 3.
C'est dingue comme on peut avoir des avis si divergent, c'est interessant.
Dans mon cas de figure je veux être au courant que l'API a changée, et pour moi la possibilité d'une exception en plus ou en moins cela fait partie de l'API, c'est tout aussi important que si un argument est ajouté à une méthode et je veux que cela m’empêche de compiler.
Après si tu laisses les exceptions remonter une pile d'appel de 12 fonctions, c'est qu'il y a aussi sans doute un problème de conception quelque part et il serait intéressant de revoir l'encapsulation.
[^] # Re: Mauvaise connaissance du c++
Posté par barmic . Évalué à 3.
C'est de la gestion d'erreur, si tu considère que c'est de la pollution c'est dommage. De plus si elles n'en ont rien c'est de loin ce qu'il y a de plus concis.
Mais surtout, là où tu traite ton exception tu as forcément un couplage avec l'exception. Il doit la comprendre et savoir quoi en faire. Faire remonter aussi haut que tu semble le dire ton exception, pousserait à penser que tu expose du fonctionnement un peu trop interne à trop haut. Ensuite pour toutes les méthodes passe plat, c'est plutôt sympa pour avoir une meilleure visibilité des chemins que ton exception peut prendre (et les refacto peuvent facilement être automatisés).
Le type de tes exceptions est exactement comme le type de tes paramètres ou de ton retour. Si tu casse ton API c'est dommage pour toi.
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: Mauvaise connaissance du c++
Posté par xcomcmdr . Évalué à 1. Dernière modification le 06 septembre 2016 à 15:21.
La gestion des erreurs : OK;
Les checked exceptions qui impactent absolument tout le monde au moindre changement/ajout : non merci.
Après, si ça te gêne pas de violer SRP, c'est dommage.
"Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)
[^] # Re: Mauvaise connaissance du c++
Posté par barmic . Évalué à 3.
Ce n'est pas possible. Vraiment. Si tu as ce genre de situation c'est probablement que le design est à revoir. Les exceptions ce n'est pas fait pour traverser toutes ta pile d’exécution. Si tu as vraiment ce genre d'impact (ou que tu en as l'impression) c'est que tu ne maîtrise pas le cheminement de tes erreurs. De plus ça peut vouloir dire que tu expose trop de choses aux appelants.
Ce n'est pas simple, mais la gestion d'erreur n'est pas simple. Devoir traiter l'erreur à tous les étages via des retours de méthodes n'est pas particulièrement plus simple, ni agréable.
Probablement parce que ça n'a rien avoir. Si tu change une exception, même si tu fais passe plat de cette exception, tu change ton contrat (à moins que ton contrat soit déjà large et qu'il annoncé une exception mère). Donc c'est ça responsabilité de l'annoncer.
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: Mauvaise connaissance du c++
Posté par xcomcmdr . Évalué à 0.
Les Checked Exceptions != Exceptions habituelles… Dans le cas des exceptions normales, je suis tout à fait d'accord.
"Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)
[^] # Re: Mauvaise connaissance du c++
Posté par barmic . Évalué à 2.
Je ne comprends pas. Les checkeds exceptions sont là pour t'empêcher de lancer des erreurs de manière implicite (comme je le dis ça fait parti du contrat de ta méthode) et sont donc de la responsabilité de toute la pile d'appel traversante. Les exceptions non vérifiées sont pratiques pour justement traverser 80% (ou 100% qui sait ?) de ta pile sans avoir à te demander ce qui se passe.
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: Mauvaise connaissance du c++
Posté par xcomcmdr . Évalué à 1.
Et c'est bien ce qui coince. De part leur nature, tu changes le type de Checked Exceptions retourné ou tu en rajoutes une : ça impact tout la pile.
"Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)
[^] # Re: Mauvaise connaissance du c++
Posté par Guillaum (site web personnel) . Évalué à 2.
C'est là que le design est mal fichu. Cela impacte toute la pile le temps que tu règles le problème en gérant l'exception à l'endroit qui te semble pertinent dans la pile, si tu veux le gérer localement, et bien fait le localement (et le reste de la pile ne sera pas impactée). Si tu veux le faire 10 étapes plus haut, alors il est normal que cela impact les 10 couches intermédiaires.
[^] # Re: Mauvaise connaissance du c++
Posté par barmic . Évalué à 3.
Non, ça n'impacte que ceux qui décident de ne pas se charger de ce problème.
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: Mauvaise connaissance du c++
Posté par xcomcmdr . Évalué à -1.
Bon, va te documenter sur la différence entre les Checked Exceptions et les Exceptions normales, et pourquoi seul Java les implémente, parce que là tu parles dans le vide…
"Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)
[^] # Re: Mauvaise connaissance du c++
Posté par barmic . Évalué à 4.
Je suis aller voir si je ratais quelque chose et ils semble que non, c'est assez simple. Les exceptions non vérifiées peuvent être ignorée silencieusement alors que les autres doivent être géré (soit par un
try
/catch
soit par unthrows
) et c'est vérifié statiquement.Pas trouvé grand chose là dessus. Le seul truc qui parle d'une controverse que j'ai trouvé c'est ça : https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html
En cherchant un peu différemment j'ai trouvé ça d'intéressant : http://stackoverflow.com/a/18472120. Globalement il y a quelques arguments :
Il semble en effet. C'est dommage, mais j'aurais au moins essayé de communiqué.
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: Mauvaise connaissance du c++
Posté par xcomcmdr . Évalué à 0.
Ah mais l'intention derrière les Checked Exceptions est noble. Mais en pratique, c'est du caca.
Supeeeer.
"Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)
[^] # Re: Mauvaise connaissance du c++
Posté par barmic . Évalué à 3.
Tu peux répéter la même chose à l'envi ça ne rendra pas les choses plus vraies pour autant.
Si tu change le comportement extérieur de
UserRepository.getContent(Path)
ça signature change que ce soit explicite ou implicite les cas d'erreurs changent (c'est pas un problème de java ou autre). C'est factuel. Le fait que tu ne veux pas le voir apparaître dans ton code sans autre raison que « ça fait des choses à changer » (alors que ça ne fait que mettre en évidence ce qui a changé), n'est pas plus intéressant que ça. C'est comme si tu me disais que le typage statique est contraignant parce que si tu change le type d'une variable, tu dois le reporter partout où tu t'en sert.Surtout que c'est dans des cas rare que tu fais ça. Selon ton niveau de design tu va présenter un type d'exception et tu va encapsuler toutes exceptions spécifiques comme un sous type de se dernier. Dans l'exemple de l'article, tu expose ton implémentation donc oui chaque modification va tout bazarder. Mais le design de l'exemple est à revoir AMHA.
D'ailleurs (c'est assez marrant) l'auteur met le doigt sur le problème :
C'est bien que lever une IOException dans une méthode
AddUserAction.execute(Object)
n'est probablement pas une bonne idée.Pour moi l'erreur consiste à avoir une approche bottom-up plutôt que top-down. Ici la question qu'il faut se poser c'est est-ce que
ContextMenu.menuClicked()
a quelque chose à faire d'uneIOException
? Ce qui l'intéresse c'est peut être plutôt de savoir que l'Action
ai échouée. Que ça vienne d'une erreur d'IO, d'une connexion à la base raté ou autre, ne change probablement pas vraiment son comportement, non ? Si son job c'est d'afficher l'erreur qui va bien à l'utilisateur alors ça peut carrément être traité dans une exception générique à tous les cas.Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: Mauvaise connaissance du c++
Posté par xcomcmdr . Évalué à 1. Dernière modification le 08 septembre 2016 à 14:30.
Tu peux répéter à l'envie que les Checked Exceptions n'apportent aucun problème, ce ne sera pas vrai pour autant. Surtout que de mon côté j'ai des exemples et articles qui démontrent leurs problèmes par A + B.
Mais il n'y a pas pire aveugle que celui qui ne veut pas voir.
"L'UI qui s'occupe des SQLExceptions du Data Layer ? Je vois aucun problème avec ça. SRP n'est aucunement violé !"
- barmic , 2016
Mettez au moins un chapeau en alu sur la tête, comme ça on pourra vous éviter dans la rue. Parce que là, un tel niveau d'incompétence de votre part c'est criminel.
"Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)
[^] # Re: Mauvaise connaissance du c++
Posté par barmic . Évalué à 3.
C'est justement ce que je dis. Tu ne devrais avoir ni SQLException, ni IOException au niveau de l'UI (que ce soit dans les signatures utilisées ou dans le code). À la place tu devrait avoir une abstraction. C'est bien le fait de propager très loin ton exception qui viole la SRP.
Si dans l'exemple de l'article que tu présente, l'
Action
ou leRepository
(la responsabilité de chacun n'est pas très clair avec juste le nom) aurait encapsuler l'exception dans un type commeActionException
(nom peut être mal choisi). Tu aurais :Ça fait combien de fois que je dis qu'il ne faut pas propager comme ça les exceptions ? Tiens je le dis juste dans le commentaire où tu répond bizarrement…
Non je dis que ton argument est mauvais pas que les checked exceptions sont exempte de problème. Les API fluent (très en vogues en ce moment) ne marchent pas très bien avec. Tu as des cas où il est plus agréable de faire autrement (code de retour,
Optional
, pour les API fluent ou de promesse avec desonException()
).Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: Mauvaise connaissance du c++
Posté par fearan . Évalué à 3.
Moui enfin catcher une exception pour la renommer en cours de route pour en faire une qui dit exactement la même chose qui est relancé ensuite… Comment dire… Beurk!
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
[^] # Re: Mauvaise connaissance du c++
Posté par xcomcmdr . Évalué à 1.
Et ça sert absolument à rien, surtout.
"Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)
[^] # Re: Mauvaise connaissance du c++
Posté par barmic . Évalué à 4.
Je vois pas comment m'expliquer plus clairement. Les choses sont liées entre elles. Si tu as une abstraction, elle doit bien avoir un sens, non ? Tu est sensé faire le même boulot d'abstraction avec tes exceptions qu'avec le reste de ton code. Ça donne un comportement cohérent à ton API.
Il ne s'agit en aucun cas de faire un rethrow de l'exception tout juste encapsulée dans une nouvelle exception, mais de faire un travail de normalisation à fin de permettre à l’appelant d'utiliser ton abstraction de manière facilité et de ne pas avoir à se préoccuper de tous les cas, mais uniquement à faire un travail de présentation (pour l'exemple du dessus).
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: Mauvaise connaissance du c++
Posté par barmic . Évalué à 3.
Ben non. Elle fait l'abstraction. Une exception comme IOException est inutilisable pour une interface graphique. Elle n'a qu'un message (voir : https://docs.oracle.com/javase/8/docs/api/java/io/IOException.html). Si tu veux avoir un message un minimum propre dans ton interface il va falloir avoir une description de l'erreur plus intelligente que ça. Par exemple un code d'erreur et une série de paramètres (le chemin ici par exemple). Le code peut te permettre d'avoir un message d'erreur localisé qui sera formaté avec les paramètres contenu dans la nouvelle exception. Tu peux aussi catégoriser l'erreur pour pour pouvoir faire des renvoies aux documentation de ton logiciel qui concernent cette partie.
Exactement la même chose dis-tu ? ^^
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: Mauvaise connaissance du c++
Posté par fearan . Évalué à 4.
Typiquement la localisation de l'erreur se fait au niveau de l'interface (dernier item de la chaine avant l'utilisateur), et c'est à ce moment là qu'elle est catché; devoir rajouter tout le long des couches le throws IOException, ou une encapsulation de l'erreur dans une autre n'apporte rien.
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
[^] # Re: Mauvaise connaissance du c++
Posté par barmic . Évalué à 3.
Je ne suis pas d'accord. L'UI n'a pas à savoir qu'il peut y avoir une IOException, une SQLException ou je ne sais quoi d'autres, c'est de la logique interne de l'implémentation de ton Action que tu leak.
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: Mauvaise connaissance du c++
Posté par fearan . Évalué à 3.
L'ui doit remonter une information à l'utilisateur pour lui indiquer que quelque chose s'est mal passée. Tu peux te contenter d'un catch all laissant un message laconique, ou essayer d'être plus fin et donner des information utile.
Enfin on parle de IOException, ça peut surtout être un FileNotFoundException.
Et ça typiquement, c'est le truc que tu peux filer à l’utilisateur qui vient de sélectionner le fichier, avec une localisation qui va bien; à la rigueur avec un petit message explicatif que le fichier à été déplacé ou supprimé depuis qu'il a cliqué.
Pareil si c'est un soucis d'encodage du fichier ou du format qui n'est pas le bon, c'est l'utilisateur qui à la main sur ce qu'il donne. Si y a un problème de verrou sur le fichier c'est encore lui qui sait quel fichier il peut fermer.
Alors on peut lui filer un ApplicationException, avec le code du message qui va bien pour le traduire au moment de l'afficher, mais si tu avais prévu de faire des actions différente en fonction de l'erreur tu dois recréer une exception par type que tu veux traiter différemment.
Et puis la fois où tu veux réutiliser des fonctions qui ont encapsulé les erreurs, mais que tu te rends compte qu'il faut en fait en spécialiser une tu te retrouves à toucher ton ancien bout de code qui n'a rien à voir avec ton évol.
Alors évidemment tu peux dire que toutes tes fonctions te renvoient une GeneralException, mais au final ça ne donne aucune information.
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
[^] # Re: Mauvaise connaissance du c++
Posté par Michaël (site web personnel) . Évalué à 5.
Je pense que ce qu'essaie de dire barmic, c'est que certaines exceptions n'ont pas le droit d'être propagées en dehors de leur domaine et qu'on doit mettre en place des “superviseurs” qui montent la garde à la frontière d'un domaine et qui traduisent les erreurs d'un domaine en erreurs génériques, qui peuvent mentionner tous les détails utiles à l'utilisateur (sous forme d'un texte) ou bien proposer un option de récupération. Pour le cas particulier de l'UI d'une application habituelle, tout ce qui compte c'est de savoir qu'il y a eu une erreur, quel contexte afficher et si on peut récupérer l'erreur. Et c'est uniquement des erreurs de ce type que doit voir le code de l'UI.
[^] # Re: Mauvaise connaissance du c++
Posté par barmic . Évalué à 2.
Merci :)
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: Mauvaise connaissance du c++
Posté par barmic . Évalué à 2.
Donc l'UI doit savoir faire tout ça ? Si par configuration tu as le choix entre une action qui va accéder au disque, à une base de données ou à un webservice, ton ui va devoir gérer chacun de ses cas dans un enchaînement de if/else/if ? Les exceptions te permettent de gérer chacun de ses cas dans des classes différentes et soit d'être génériques soit par polymorphisme effectuer le bon traitement.
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: Mauvaise connaissance du c++
Posté par fearan . Évalué à 3.
Plutôt la couche en dessous avec des catch que des if/else if
Certaines doivent être transformée avant car n'intéresse pas le niveau plus haut, mais si tu choppes ton exception pour juste la renommer "parce que", je m'insurge.
Typiquement si ton parseur de nombre dans le xml te pête à la gueule te disant juste NumberFormatException, ça à du sens de la chopper au dessus disant que c'est le parsing xsd qui vient de foirer en ajoutant ligne+colonne de l'erreur. Par contre chopper toutes les Exception lors de la lecture d'un fichier pour les transformer en JYarrivePasException, non. Les actions à entreprendre peuvent dépendre du type de l’exception; donc pas au niveau de L'UI qui ne devrait faire que de l'affichage, mais juste en dessous. (par exemple ouvrir une fenêtre avec un gros point rouge sur la ligne qui a planté le parsing).
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
[^] # Re: Mauvaise connaissance du c++
Posté par barmic . Évalué à 4.
Donc dans l'
Action
?Il n'y a pas de différence, tu as un bloque de code de traitement dans ton UI par possibilité d'erreur dans l'
Action
et potentiellement pour la combinatoire des différentesAction
s. Mieux tu peux avoir des exception identiques pour des Actions différentes qui demandent donc des réactions différentes en terme d'affichage utilisateur. Tu peux avoir uneIOException
pour pleins de raisons différentes et dans des cas d'accès disques ou réseau.On va pas boucler là dessus ?
Rien empêche d'avoir ça dans ton exception plutôt que dans le code de l'UI. Je l'ai déjà dis plus haut, mais bon. Imagine tu ta gestion d'erreur soit relativement simple et consiste à afficher un message à l'utilisateur. Si tu sors une exception qui contient :
Ton UI va devoir :
et ce bout de code sera identique à pas mal de cas. xcomcmdr sera content car la SRP sera vraiment respectée l'UI ne s'intéresse pas à savoir comment est-ce que l'Action se débrouille pour rendre son service !
Bien sûr ça n'est pas aussi simple, par exemple les parseurs XML te fournissent généralement une liste d'erreur, c'est au développeur de voir comment il veut gérer ces cas pour l'utilisateur.
Les checkeds exceptions devraient être utilisées comme ça. Elles ne sont pas parfaites, il y a pleins de cas où elles sont sous-optimales, voir carrément embêtante, mais affirmer qu'elles sont mauvaises parce que quand on les utilise mal elles sont chiantes à gérer ne me semble pas plus intéressant que ça. Il faut savoir comment elles devraient être utiliser pour pouvoir ensuite savoir quand les utiliser ou pas.
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: Mauvaise connaissance du c++
Posté par Anthony Jaguenaud . Évalué à 3.
Sur une API donnée, j’utilise une classe d’exception dont les erreurs particulières héritent. Et je catch toujours la classe parent là où je souhaite récupérer les erreurs inattendues.
Ex :
[^] # Re: Mauvaise connaissance du c++
Posté par barmic . Évalué à 3.
Et c'est bien. Tu peux même te servir de cet arborescence pour jouer sur la granularité des erreurs et ne pas exposer cette précision dans ton API.
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: Mauvaise connaissance du c++
Posté par fearan . Évalué à 3.
Je sais pas, je récupère un XML, généré par l'appli, que je parse. Les chance que ce xml ne valide pas le schéma, qu'il soit mal formé ou n'ait pas la balise attendu est nulle, je dois vraiment polluer le code? Et je met quoi ? serr ("Oups si on est la c'est qu'on a vraiment merdé" );
Là j'ai pris un exemple tangent, mais imagine que c'est une regex en dure dans le code, faut aussi se farcir de la gestion d'exception pour dire qu'elle est mal rédigée?
Bref lorsque tu codes, tu peux faire des assertions sur ce que tu as en entrée, et tu peux être certains que là où tu passes tu n'auras jamais l'exception; devoir alourdir le code et provoquer des changement de scope (et donc de visibilité) pour des cas qui n'arrivent jamais n'est vraiment pas un gain.
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
[^] # Re: Mauvaise connaissance du c++
Posté par ckyl . Évalué à 2.
Des idées:
[^] # Re: Mauvaise connaissance du c++
Posté par fearan . Évalué à 3.
C'est pour cela que j'ai tendance à préférer le stacktrace qu'on retrouvera dans les logs; ça évite de planter toute l'appli et de déconnecter sauvagement tous les utilisateurs du serveur métier.
Merci, mais je ne vais pas recoder la roue; je fais avec les briques que j'ai; je ne vais pas coder une api par état non plus (telle api à utiliser dans ce cas là, telle api dans celui ci…
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
[^] # Re: Mauvaise connaissance du c++
Posté par ckyl . Évalué à 2.
Le stacktrace local n'est pas une solution. Ce que tu veux c'est que la fault remonte vers le fault handler adéquat qui fera alors la reprise sur erreur attendu pour l'unité en cours d'exécution (fonctionnel et non fonctionnel):
Souvent quand tu as une base de code ou 90% des catch sont
printstacktrace()
/ serr / log, tu vas aussi trouver des choses comme ça:[^] # Re: Mauvaise connaissance du c++
Posté par fearan . Évalué à 2.
Alors là c'est qu'on ne passe pas sur les mêmes bases de codes; si on a un e.printStackTrace(), c'est que c'est normalement un cas qui ne doit JAMAIS arriver; si cela arrive, les unités de parse de log te remontent le machin directe chez le dev pour qu'il regarde le cas.
Par contre comme les procédures pour installer un patch sont assez compliqué à mettre en oeuvre, on préfère garder le système en état de fonctionnement.
J'ajouterai que ton code, ça risque de ne pas compiler (value peut être non initialisé), il faudrait au moins le mettre à 111, 421 ou 666 ou encore, ajouter un throw :)
tiens un truc amusant pour chopper un constructor que l'on sait exister avec ensuite utilisation de ce constructor j'ai ça dans mon catch : InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException
ça en fait pas mal pour rien…
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
[^] # Re: Mauvaise connaissance du c++
Posté par ckyl . Évalué à 3.
Donc le cas qui ne doit JAMAIS arriver, tu mets quand même un système pour vérifier qu'il n'arrive jamais en fait.
Et quand ce qui n'arrive jamais arrive, le système va tout de même avoir un comportement et puisque que tu ne veux pas que ton système plante, offre 10M$ au client ou tue un petit chaton tu vas forcément avoir un chemin d'exécution qui prend le truc en compte tu ne peux pas juste continuer après ton serr comme si de rien n'était.
Effectivement =>
catch (ReflectiveOperationException e)
;)Après non, on ne va pas défendre la plupart des API du JDK non plus faut pas pousser.
# moche ?
Posté par Nicolas Boulay (site web personnel) . Évalué à 3.
C'est assez moche comme code. En Ocaml, le type optionnel est un type somme, ce qui oblige à le décomposer, donc, c'est impossible de l'oublier ou de tomber sur un pointeur nul. Il manque ce filtrage à C++ pour avoir toutes la puissance du truc.
Ensuite, niveau exception je déteste ça. Ne pas trouver quelques choses dans une recherche, peut être un bug ou un cas attendu, j'ai déjà eu des gros problèmes à démêler les 2 cas. Dans le cas de Ocaml, il faudra que les fonctions inclus leur exceptions dans leur signature, cela permettra au moins d'éviter des oublies.
"La première sécurité est la liberté"
[^] # Re: moche ?
Posté par Guillaum (site web personnel) . Évalué à 2.
C'est exactement ce que je propose à la fin de l'article non ? Sauf que comme on ne peut pas "pattern matché" en C++, je propose deux fonctions utilitaires,
map
etbind
qui font les tests et qui appellent les lambdas associés.[^] # Re: moche ?
Posté par Nicolas Boulay (site web personnel) . Évalué à 2.
non, bind ne permet pas de vérifier que le cas "vide" est géré.
"La première sécurité est la liberté"
[^] # Re: moche ?
Posté par Guillaum (site web personnel) . Évalué à 4.
Ba si…
Si tu as un
std::optional<A>
nommévalue
et une fonction deA
dansstd::optional<B>
nomméef
. Alosbind(value, f)
te renvoie unstd::optional<B>
. Soit il y avait quelque chose dansvalue
et il a appliquéf
sur ce quelque chose, soit il n'y avait rien, et il renvoie unstd::optional<B>
.Le cas vide est donc géré et repoussé à la prochaine étape. Un jour, en fin de chaîne, une décision sera prise sur quoi faire du optional vide, une valeur par défaut, avec
value_or
, ou un comportement different, avecoptionalCase
.# Remarques diverses et ... tardives
Posté par lmg HS (site web personnel) . Évalué à 2.
(Je doute que ce message ait beaucoup de visibilité vu mon temps de découverte de l'article original, mais voici tout de même quelques remarques).
De la programmation par contrat
Tout d'abord, la Programmation par Contrat est totalement méprise—ou très mal présentée. Son objectif n'est pas de gérer des problèmes plausibles et liés à l'environnement tel qu'un fichier illisible, corrompu, ou encore des sockets plantées.
Son objectif est de traiter les erreurs de programmation. C'est pour cela que l'on dit que c'est à l'utilisateur de vérifier le contrat d'appel (pré-conditions avant d'appeler la fonction). Appeler
pop()
sur une pile vide est idiot. De même que d'accéder à un élément hors bornes, ou d'exécutersqrt(sin(x)-1)
sur tout x. Ce sont autant d'erreurs dont la prévention est de la responsabilité de l'appelant.A ce sujet, je préfère 100 fois une assertion là pour détecter une précondition non remplie qu'une exception pour analyser ce qu'il se passe. En terme d'investigation, c'est un vrai bonheur à contrario de toutes les alternatives dynamiques (pour repérer et tordre le coup aux erreurs de programmation en C&C++, donc). Je suis d'accord accessoirement sur le fait que les types opaques c'est encore plus mieux. Mais cela complexifie les choses car un
strictlypositive<> * strictlynegative<>
donne unstrictlynegative<>
, mais quid de l'addition ? (Ou alors, il faut faire comme avec boost.unit et avoir une arithmétique sur desrange<min, max>
. Hum… C'est tordu, mais ça peu m'amuser à investiguer.)Bref, je me suis déjà longuement étendu sur le sujet par ici: http://luchermitte.github.io/blog/2014/05/24/programmation-par-contrat-un-peu-de-theorie/ (série de 3 billets)
Des exceptions dans la SL
Il y a effectivement peu d'endroits où la SL choisit de lancer des
std::logic_error
, qui sont des exceptions qui signifient "erreur de programmation". En général, une approche pure contrat, mais pas toujours instrumentée est employée. Cf les "STL checkées" sur le sujet. Et j'espère qu'après le C++17 si les évolutions sur les contrats sont validées, toute la SL sera annotée pour spécifier les contrats de toutes les fonctions qui en ont.De mon avis,
std::vector<>::at()
est une hérésie qui n'aurait jamais du exister.Ailleurs, s'il y a des choses qui peuvent vraiment échouer, des exceptions de runtime (dans la terminologie C++, je sais que runtime error veut dire le contraire dans d'autres langages) seront lancées—d'autres exemples ont été donnés. J'ai envie de dire que quelque part, c'est plus nos métiers qui vont vraiment détecter des situations exceptionnelles (et plausibles) et à ne pas confondre avec des erreurs de programmation.
A propos d'
optional
et des erreursSi dans le contexte d'une fonction de recherche, un retour optionnel (qui ne signifie pas forcément "erreur") a du sens, dans le contexte de retour d'erreurs de runtime (au sens C++ donc),
optional
n'est pas un bon outil car il ne porte en lui aucun contexte. Et tu nous montres ici à quel point les enchainements ne sont pas propres/simples.Je vous invite plutôt à vous tourner vers des types comme
expected
. Il y a eu un article sur le sujet en 2012 et de l'encre électronique a coulé (pour encore plus de monadification de la bête) après ça :- la vidéo de la conf: http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C
- les slides: https://onedrive.live.com/?cid=F1B8FF18A2AEC5C5&id=F1B8FF18A2AEC5C5%211158&parId=root&o=OneUp
Pour le coup, c'est fait pour. Et c'est plus proche du genre de type que cet article recherche.
[^] # Re: Remarques diverses et ... tardives
Posté par lmg HS (site web personnel) . Évalué à 1.
Il me semblait bien que j'avais vu passer des choses en plus de la conf d'Andrei Alexandrescu: une proposition d'évolution pour disposer d'un type
std::expected<>
est en cours: http://open-std.org/Jtc1/sc22/wg21/docs/papers/2016/p0323r1.pdfIl est présenté comme une généralisation de
std::optional
.[^] # Re: Remarques diverses et ... tardives
Posté par kantien . Évalué à 1.
Effectivement c'est une meilleure approche si on veut plus d'information sur la raison de l'échec.
Dans le même genre, à la place des
Maybe a
, en Haskell on peut utiliser lesdata Either a b = Left a | Right b
.Et en Ocaml, il y a soit le type
'a option = None | Some of 'a
soit le type('a, 'b) result = Ok of 'a | Error of 'b
.Ce qui correspond à la classe
std::expected<T,E>
proposée pour le C++ :Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
# Faire des monades en C++
Posté par kantien . Évalué à 3.
Comme le commentaire du dessus, je suis en retard par rapport à la date de publication du journal : je l'ai découvert via la dépêche sur les contenus primés de septembre.
Je voudrais juste signaler un article sur les monades en C++ étant donné que c'est ce qui est présenté en fin d'article pour gérer les
std::optional
via uneoption monad
.L'article compare les deux approches entre Haskell et C++, ce qui pourrait intéresser l'auteur du journal qui, je le sais, apprécie particulièrement le langage Haskell.
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.