Vous avez raison de distinguer les deux types d'échec, ils sont en effet subtilement différents.
Échec dans le Constructeur : Le fichier peut ne pas exister, ne pas avoir les autorisations de lecture, contenir des données non valides/non analysables, etc.
Juste lancez une exception. Les objets à moitié construits sont le chemin le plus court vers des programmes truffés de bogues.
Une classe doit avoir un invariant que le constructeur établit et que toutes les méthodes maintenir . Si le constructeur ne peut pas établir l'invariant, alors l'objet est inutilisable et la meilleure façon de le signaler, en C++, est de lancer une exception afin que le langage garantisse que l'objet à moitié construit ne sera pas utilisé.
Si les gens suggèrent que vous pourriez avoir besoin d'un état invalide rappelez-leur les Principe de responsabilité unique : votre classe a déjà une responsabilité spécifique (son invariant), ceux qui souhaitent plus peuvent encapsuler dans une classe dédiée à fournir optionalité . De ma tête, boost::optional
y std::unique_ptr
sont tous deux d'excellents choix.
Échec de la méthode habituelle : Le fichier peut déjà exister, il se peut qu'il n'y ait pas d'accès en écriture, trop peu de données de vente disponibles pour créer un graphique circulaire, etc.
Malheureusement, vous n'avez pas réussi à faire la distinction entre deux cas :
- des méthodes qui ne sont jamais lire l'instance
- qui modifient également l'instance
Pour toutes les méthodes, vous devez choisir votre stratégie de signalement des erreurs. Mon conseil est le suivant exceptions son exceptionnel . Si l'échec est considéré comme exceptionnel (lien réseau en panne alors qu'il est en place 99,99% du temps), alors une exception est acceptable. D'un autre côté, si l'échec est attendu, généralement en fonction de l'entrée telle qu'une find
ou, dans votre cas, une méthode write
à un fichier spécifié, vous voulez alors donner à l'utilisateur la possibilité de réagir de manière appropriée.
Il reste au moins deux possibilités, après avoir écarté les exceptions :
- renvoyant un code (
bool
, enum
) indiquant si l'opération s'est bien déroulée ou non
- demandant à l'utilisateur de fournir un politique d'erreur qui sera invoqué en cas de problème
La politique d'erreur peut être aussi simple qu'une enum
(skip, retry-once, throw) ou aussi compliquée qu'un véritable Strategy
avec différentes méthodes.
De plus, personne ne dit que votre méthode ne peut avoir que un mécanisme de signalement des erreurs. Par exemple, vous pouvez choisir :
- pour invoquer une politique d'erreur si le fichier existe déjà (fourni par l'utilisateur après tout, il pourrait vouloir changer de nom)
- à lancer si le disque n'est pas accessible (on s'attend généralement à ce que le matériel fonctionne !)
Enfin, en plus de cela, les méthodes qui modifient également l'instance doivent se préoccuper des éléments suivants maintenir l'invariant que le constructeur a établi. Si une méthode peut bousiller l'invariant, alors ce n'est pas un invariant du tout, et vos utilisateurs devraient trembler de peur à chaque fois que la classe est utilisée... La sagesse générale est d'effectuer toutes les opérations qui pourraient lancer antes de commencer à modifier l'objet.
Une mise en œuvre simpliste (mais tellement facile) est la fonction copier et échanger idiome : copier l'objet en interne, effectuer les opérations sur la copie, et à la fin de la méthode échanger l'état de la copie avec l'état de l'objet courant. Si quelque chose ne va pas, la copie est corrompue et immédiatement éliminée pendant le déroulement de la pile, laissant l'objet intact.
Pour en savoir plus sur ce sujet, vous pouvez lire les articles suivants Exceptions Garanties . Ce que j'ai décrit est une mise en œuvre typique de la Garantie d'exception forte similaire aux transactions des bases de données (tout ou rien).