À tort ou à raison, je suis actuellement convaincu que je devrais toujours essayer de rendre mon code aussi robuste que possible, même si cela implique d'ajouter du code / des contrôles redondants qui, je le sais , ne seront d'aucune utilité pour le moment, mais pourrait être x nombre d'années sur la ligne.
Par exemple, je travaille actuellement sur une application mobile qui contient ce morceau de code:
public static CalendarRow AssignAppointmentToRow(Appointment app, List<CalendarRow> rows)
{
//1. Is rows equal to null? - This will be the case if this is the first appointment.
if (rows == null) {
rows = new List<CalendarRow> ();
}
//2. Is rows empty? - This will be the case if this is the first appointment / some other unknown reason.
if(rows.Count == 0)
{
rows.Add (new CalendarRow (0));
rows [0].Appointments.Add (app);
}
//blah...
}
En ce qui concerne plus particulièrement la section deux, je sais que si la section un est vraie, la section deux le sera également. Je ne vois aucune raison pour laquelle la première section serait fausse et la deuxième section évaluerait la vérité, ce qui rend la deuxième if
déclaration superflue.
Toutefois, il se peut qu’à l’avenir cette seconde if
déclaration soit réellement nécessaire et pour une raison connue.
Certaines personnes peuvent commencer par regarder cela et penser que je programme en pensant au futur, ce qui est évidemment une bonne chose. Mais je connais quelques cas où ce type de code m'a "caché" des bogues. Cela signifie qu'il m'a fallu encore plus de temps pour comprendre pourquoi la fonction xyz
agit abc
alors qu'elle devrait l'être def
.
D'autre part, il y a eu de nombreux cas où ce type de code a rendu beaucoup plus facile l'amélioration du code avec de nouveaux comportements, car je n'ai pas à revenir en arrière et à m'assurer que toutes les vérifications pertinentes sont en place.
Existe-t-il des instructions générales concernant ce type de code? (J'aimerais aussi savoir si cela serait considéré comme une bonne ou une mauvaise pratique?)
NB: Ceci pourrait être considéré comme similaire à cette question, mais contrairement à cette question, j'aimerais une réponse en supposant qu'il n'y a pas de délai.
TLDR: Dois-je aller jusqu'à ajouter du code redondant afin de le rendre potentiellement plus robuste à l'avenir?
if(rows.Count == 0)
n'arrivera jamais, vous pouvez alors déclencher une exception - et vérifier ensuite pourquoi votre hypothèse est devenue fausse.rows
jamais nul? Il n'y a jamais de bonne raison, du moins dans .NET, pour qu'une collection soit nulle. Vide , bien sûr, mais pas nul . Je lève une exception sirows
est null, parce que cela signifie que l'appelant manque de logique.Réponses:
À titre d’exercice, vérifions d’abord votre logique. Bien que, comme nous le verrons, vous avez des problèmes plus importants que tout problème logique.
Appelez la première condition A et la deuxième condition B.
Vous dites d'abord:
C'est-à-dire: A implique B ou, en termes plus fondamentaux
(NOT A) OR B
Puis:
C'est:
NOT((NOT A) AND B)
. Appliquer la loi de Demorgan pour obtenir(NOT B) OR A
qui est B implique A.Par conséquent, si vos deux affirmations sont vraies, alors A implique B et B implique A, ce qui signifie qu'elles doivent être égales.
Donc oui, les chèques sont redondants. Vous semblez avoir quatre chemins de code dans le programme, mais en fait vous n'en avez que deux.
Alors maintenant, la question est: comment écrire le code? La vraie question est: quel est le contrat déclaré de la méthode ? La question de savoir si les conditions sont redondantes est un fouillis rouge. La vraie question est "ai-je conçu un contrat raisonnable, et ma méthode met-elle clairement en œuvre ce contrat?"
Regardons la déclaration:
C'est public, il doit donc résister aux mauvaises données des appelants arbitraires.
Il retourne une valeur, il devrait donc être utile pour sa valeur de retour, pas pour son effet secondaire.
Et pourtant, le nom de la méthode est un verbe, suggérant qu’elle est utile pour ses effets secondaires.
Le contrat du paramètre de liste est:
Ce contrat est fou . Imaginez écrire la documentation pour cela! Imaginez écrire des cas de test!
Mon conseil: recommencez. Cette API a une interface de machine à bonbons écrite partout. (L'expression est tirée d'une vieille histoire sur les machines à confiserie de Microsoft, où les prix et les sélections sont des nombres à deux chiffres, et il est très facile de taper "85", qui est le prix du produit 75, et vous obtenez Amusant fait: oui, c'est ce que j'ai fait par erreur lorsque j'essayais de faire sortir de la gomme d'un distributeur automatique chez Microsoft!)
Voici comment concevoir un contrat raisonnable:
Rendez votre méthode utile soit pour son effet secondaire, soit pour sa valeur de retour, pas pour les deux.
N'acceptez pas de types mutables comme des entrées, comme des listes; si vous avez besoin d’une séquence d’informations, prenez un IEnumerable. Seulement lire la séquence; n'écrivez pas dans une collection transmise sauf s'il est très clair qu'il s'agit du contrat de la méthode. En prenant IEnumerable, vous envoyez un message à l'appelant lui indiquant que vous n'allez pas modifier sa collection.
Ne jamais accepter les nulls; une séquence nulle est une abomination. Demander à l'appelant de transmettre une séquence vide si cela a du sens, jamais jamais nul.
Si votre interlocuteur enfreint votre contrat, bloquez-le immédiatement pour lui apprendre que vous êtes une entreprise sérieuse et attraper ses bogues lors des tests et non de la production.
Concevez d’abord le contrat de manière à être aussi raisonnable que possible, puis mettez-le clairement en œuvre. C'est le moyen de pérenniser un design.
Maintenant, je n'ai parlé que de votre cas spécifique et vous avez posé une question générale. Voici donc quelques conseils généraux supplémentaires:
S'il existe un fait que vous pouvez déduire en tant que développeur mais que le compilateur ne peut pas, utilisez une assertion pour documenter ce fait. Si un autre développeur, comme vous ou l'un de vos collègues à l'avenir, enfreint cette hypothèse, l'affirmation vous le dira.
Obtenez un outil de couverture de test. Assurez-vous que vos tests couvrent chaque ligne de code. S'il y a du code non couvert, il vous manque un test ou vous avez un code mort. Le code mort est étonnamment dangereux parce qu’il n’est généralement pas destiné à être mort! On pense immédiatement à l'incroyable horrible faille de sécurité d'Apple "goto fail" il y a quelques années.
Obtenez un outil d'analyse statique. Heck, obtenez plusieurs; chaque outil a sa propre spécialité et aucun n'est un sur-ensemble des autres. Faites attention quand il vous dit qu'il y a du code inaccessible ou redondant. Encore une fois, ce sont probablement des insectes.
Si cela ressemble à ce que je dis: premièrement, concevez bien le code, et deuxièmement, testez-le pour vous assurer qu'il est correct aujourd'hui, eh bien, c'est ce que je dis. En agissant de la sorte, il sera beaucoup plus facile de faire face à l'avenir. La partie la plus difficile de l’avenir concerne tous les codes de machine à confiserie écrits par le passé. Faites-le bien aujourd'hui et les coûts seront moins élevés à l'avenir.
la source
CompareExchange
sans!), Et même dans des scénarios non simultanés, par exemple, "ajoutez un enregistrement s'il n'existe pas, et renvoyez le résultat transmis. record ou celui qui existait "peut être plus pratique que toute approche qui n'utilise ni effet secondaire ni valeur de retour.Ce que vous faites dans le code que vous montrez ci-dessus n’est pas tant une vérification pour le futur que pour un codage défensif .
Les deux
if
déclarations testent des choses différentes. Les deux sont des tests appropriés en fonction de vos besoins.La section 1 teste et corrige un
null
objet. Remarque secondaire: la création de la liste ne crée aucun élément enfant (par exemple,CalendarRow
).La section 2 recherche et corrige les erreurs des utilisateurs et / ou de la mise en œuvre. Ce
List<CalendarRow>
n'est pas parce que vous avez un élément que la liste contient des éléments. Les utilisateurs et les implémenteurs feront des choses que vous ne pouvez pas imaginer simplement parce qu'ils sont autorisés à le faire, que cela soit logique pour vous ou non.la source
Je suppose que cette question est fondamentalement au goût. Oui, c’est une bonne idée d’écrire du code robuste, mais le code de votre exemple est une légère violation du principe KISS (comme le sera un grand nombre de ce code "évolutif").
Personnellement, je n'aurais probablement pas la peine de rendre le code à l'épreuve des balles pour l'avenir. Je ne connais pas l'avenir et, en tant que tel, un tel code "à l'épreuve des balles" est condamné à échouer lamentablement lorsque le futur arrive de toute façon.
Au lieu de cela, je préférerais une approche différente: expliquez les hypothèses que vous expliquez à l'aide de la
assert()
macro ou d'installations similaires. De cette façon, lorsque le futur nous attend, il vous indiquera précisément où vos hypothèses ne sont plus conformes.la source
Un autre principe auquel vous voudrez peut-être réfléchir est l’idée d’ échouer rapidement . L'idée est que lorsque quelque chose ne va pas dans votre programme, vous voulez l'arrêter complètement immédiatement, au moins pendant que vous le développez avant de le publier. En vertu de ce principe, vous souhaitez rédiger de nombreuses vérifications pour vous assurer que vos hypothèses sont vérifiées, mais envisagez sérieusement de faire en sorte que votre programme soit totalement bloqué chaque fois que ces hypothèses sont violées.
Pour être courageux, s'il y a même une petite erreur dans votre programme, vous voulez qu'il se bloque complètement pendant que vous regardez!
Cela peut sembler paradoxal, mais cela vous permet de détecter les bogues le plus rapidement possible lors du développement de routine. Si vous écrivez un morceau de code et que vous pensez que c'est fini, mais que ça se bloque lorsque vous le testez, il ne fait aucun doute que vous n'êtes pas encore fini. En outre, la plupart des langages de programmation vous offrent d’excellents outils de débogage qui sont plus faciles à utiliser lorsque votre programme se bloque complètement au lieu d’essayer de faire de son mieux après une erreur. L’exemple le plus important et le plus courant est que si vous bloquez votre programme en lançant une exception non gérée, le message d’exception vous indiquera une quantité incroyable d’informations sur le bogue, y compris la ligne de code ayant échoué et le chemin emprunté par votre programme. son chemin vers cette ligne de code (la trace de la pile).
Pour plus de réflexion, lisez ce court essai: Ne clouez pas votre programme en position verticale
Ceci est pertinent pour vous car il est possible que les vérifications que vous écrivez existent parfois, car vous souhaitez que votre programme continue à fonctionner même après un dysfonctionnement. Par exemple, considérons cette brève implémentation de la séquence de Fibonacci:
Cela fonctionne, mais que se passe-t-il si quelqu'un passe un nombre négatif à votre fonction? Cela ne fonctionnera pas alors! Vous voudrez donc ajouter une vérification pour vous assurer que la fonction est appelée avec une entrée non négative.
Il pourrait être tentant d'écrire la fonction comme ceci:
Cependant, si vous faites cela, puis plus tard, appelez accidentellement votre fonction Fibonacci avec une entrée négative, vous ne le réaliserez jamais! Pire encore, votre programme continuera probablement à fonctionner, mais commencera à produire des résultats absurdes sans vous donner la moindre idée de ce qui pourrait mal tourner. Ce sont les types de bogues les plus difficiles à résoudre.
Au lieu de cela, il est préférable d'écrire un chèque comme celui-ci:
Maintenant, si vous appelez accidentellement la fonction de Fibonacci avec une entrée négative, votre programme s’arrêtera immédiatement et vous fera savoir que quelque chose ne va pas. De plus, en vous donnant une trace de pile, le programme vous indiquera quelle partie de votre programme a essayé d'exécuter la fonction Fibonacci de manière incorrecte, ce qui vous donnera un excellent point de départ pour déboguer ce qui ne va pas.
la source
Devez-vous ajouter du code redondant? Non.
Mais ce que vous décrivez n'est pas un code redondant .
Ce que vous décrivez est une programmation défensive visant à empêcher le code d’appel de violer les conditions préalables de votre fonction. Que vous le fassiez ou que vous laissiez simplement aux utilisateurs le soin de lire la documentation et d’éviter ces violations, est tout à fait subjectif.
Personnellement, je suis un grand partisan de cette méthodologie, mais comme pour tout, il faut être prudent. Prenons, par exemple, C ++
std::vector::operator[]
. Mettant de côté l'implémentation en mode débogage de VS, cette fonction n'effectue pas de vérification des limites. Si vous demandez un élément qui n'existe pas, le résultat est indéfini. Il appartient à l'utilisateur de fournir un index de vecteur valide. C'est assez délibéré: vous pouvez "accepter" la vérification des limites en l'ajoutant au site d'appels, mais si laoperator[]
mise en œuvre devait l'exécuter, vous ne pourriez pas "désactiver". En tant que fonction de bas niveau, cela a du sens.Mais si vous écriviez une
AddEmployee(string name)
fonction pour une interface de niveau supérieur, je m'attendrais parfaitement à ce que cette fonction lève au moins une exception si vous fournissez une valeur videname
, ainsi que cette condition préalable étant documentée immédiatement au-dessus de la déclaration de fonction. Vous ne fournissez peut-être pas aujourd’hui à cette fonction une entrée non normalisée de l'utilisateur, mais le rendre "sûr" de cette manière signifie que toute violation de condition préalable pouvant survenir dans le futur peut être facilement diagnostiquée, plutôt que de déclencher potentiellement une chaîne de dominos difficiles à gérer. détecter les bugs. Ce n'est pas redondant: c'est de la diligence.Si je devais établir une règle générale (bien que, en règle générale, j'essaie de les éviter), je dirais une fonction qui satisfait l'un des éléments suivants:
… Peut bénéficier d'une programmation défensive. Dans d'autres cas, vous pouvez toujours écrire des
assert
ions qui se déclenchent pendant les tests mais sont désactivés dans les versions validées, pour augmenter encore votre capacité à détecter les bogues.Ce sujet est approfondi sur Wikipedia ( https://en.wikipedia.org/wiki/Defensive_programming ).
la source
Deux des dix commandements de programmation sont pertinents ici:
Tu ne supposeras pas que la saisie est correcte
Tu ne feras pas de code pour une utilisation future
Ici, vérifier la valeur null ne signifie pas "créer du code pour une utilisation future". Créer du code pour une utilisation future s'apparente à l'ajout d'interfaces, car vous pensez qu'elles pourraient être utiles "un jour". En d'autres termes, le commandement est de ne pas ajouter de couches d'abstraction à moins qu'elles ne soient nécessaires maintenant.
La vérification de la valeur null n'a rien à voir avec une utilisation future. Cela concerne le commandement n ° 1: ne présumez pas que la saisie sera correcte. Ne supposez jamais que votre fonction recevra un sous-ensemble d'entrées. Une fonction doit répondre de manière logique, peu importe la façon dont les entrées sont fictives et gâchées.
la source
Thou shall not make code for future use
rencontré des problèmes de maintenabilité plus tôt , malgré la logique intuitive du commandement. J'ai constaté, dans le codage réel, que le commandement n'est efficace que dans le code où vous contrôlez la liste des fonctionnalités et les délais, et assurez-vous que vous n'avez pas besoin d'un code futur pour les atteindre ... c'est-à-dire, jamais.La définition de «code redondant» et de «YAGNI» dépend souvent de la mesure dans laquelle vous regardez.
Si vous rencontrez un problème, vous avez tendance à écrire le code futur de manière à éviter ce problème. Un autre programmeur qui n'a pas rencontré ce problème particulier pourrait considérer que votre code est excessif.
Ma suggestion est de garder le temps que vous passez sur "des choses qui n'ont pas encore erré" si elles se chargent et que vos pairs exploitent des fonctionnalités plus rapidement que vous, puis réduisez-les.
Cependant, si vous êtes comme moi, je suppose que vous aurez simplement tout tapé «par défaut» et que cela ne vous a pas vraiment pris plus longtemps.
la source
Il est judicieux de documenter toute hypothèse concernant les paramètres. Et c'est une bonne idée de vérifier que votre code client ne viole pas ces hypothèses. Je voudrais faire ceci:
[En supposant qu'il s'agisse de C #, Assert n'est peut-être pas le meilleur outil pour le travail, car il n'est pas compilé dans le code publié. Mais c'est un débat pour un autre jour.]
Pourquoi est-ce mieux que ce que vous avez écrit? Votre code a du sens si, dans un futur où votre client a changé, lorsque le client passe dans une liste vide, la bonne chose à faire sera d'ajouter une première ligne et d'ajouter une application à ses rendez-vous. Mais comment savez-vous que ce sera le cas? Mieux vaut faire moins d’hypothèses maintenant sur l’avenir.
la source
Estimez le coût de l'ajout de ce code maintenant . Ce sera relativement bon marché parce que tout est frais dans votre esprit, vous pourrez donc le faire rapidement. Il est nécessaire d’ajouter des tests unitaires - il n’ya rien de pire que d’utiliser une méthode un an plus tard, cela ne fonctionne pas, et vous vous rendez compte qu’elle était cassée depuis le début et n’avait jamais réellement fonctionné.
Estimez le coût de l'ajout de ce code lorsque cela est nécessaire. Cela coûtera plus cher, car il faut revenir au code, tout en mémoire, et c'est beaucoup plus difficile.
Estimez la probabilité que le code supplémentaire soit réellement nécessaire. Ensuite, faites le calcul.
D'autre part, un code complet avec les hypothèses "X n'arrivera jamais" est terrible pour le débogage. Si quelque chose ne fonctionne pas comme prévu, cela signifie soit une erreur stupide, soit une hypothèse erronée. Votre "X n'arrivera jamais" est une hypothèse, et en présence d'un bogue, il est suspect. Ce qui oblige le prochain développeur à perdre du temps. Il est généralement préférable de ne pas s’appuyer sur de telles hypothèses.
la source
La principale question ici est "que se passera-t-il si vous faites / ne faites pas"?
Comme d'autres l'ont souligné, ce type de programmation défensive est bon, mais il est aussi parfois dangereux.
Par exemple, si vous fournissez une valeur par défaut, le programme reste actif. Mais le programme ne fait peut-être pas ce que vous voulez. Par exemple, s'il écrit ce tableau vide dans un fichier, vous pouvez maintenant avoir transformé votre bogue de "crash parce que j'ai fourni null par accident" à "efface les lignes du calendrier parce que j'ai fourni null par accident". (par exemple, si vous commencez à supprimer des éléments n'apparaissant pas dans la liste de la partie "// bla")
La clé pour moi est Jamais des données corrompues . Permettez-moi de répéter cela; JAMAIS. CORROMPU. LES DONNÉES. Si votre programme fait exception, vous obtenez un rapport de bogue que vous pouvez corriger. si elle écrit de mauvaises données dans un fichier et que ce sera plus tard, vous devez semer le sol avec du sel.
Toutes vos décisions "inutiles" doivent être prises avec cette prémisse.
la source
Ce dont vous avez affaire ici est essentiellement une interface. En ajoutant le comportement "quand l'entrée est
null
, initialisez l'entrée", vous avez effectivement étendu l'interface de la méthode. Désormais, au lieu de toujours utiliser une liste valide, vous avez réussi à "corriger" l'entrée. Qu'il s'agisse de la partie officielle ou non officielle de l'interface, vous pouvez parier que quelqu'un (y compris très probablement vous) va utiliser ce comportement.Les interfaces doivent rester simples et relativement stables, notamment avec une
public static
méthode. Vous avez un peu de marge de manœuvre dans les méthodes privées, en particulier les méthodes d'instances privées. En étendant implicitement l'interface, vous avez rendu votre code plus complexe en pratique. Maintenant, imaginez que vous ne souhaitiez pas réellement utiliser ce chemin de code - afin de l'éviter. Maintenant, vous avez un peu de code non testé qui prétend faire partie du comportement de la méthode. Et je peux vous dire tout de suite qu’il ya probablement un bogue: quand vous passez une liste, cette liste est mutée par la méthode. Toutefois, si vous ne le faites pas, vous créez une localeliste, et le jeter plus tard. C’est le genre de comportement incohérent qui vous fera pleurer dans une demi-année, alors que vous essayez de traquer un bogue obscur.En général, la programmation défensive est une chose très utile. Mais les chemins de code pour les contrôles défensifs doivent être testés, comme tout autre code. Dans un cas comme celui-ci, ils compliquent votre code sans raison, et je choisirais plutôt une alternative comme celle-ci:
Vous ne voulez pas d' une entrée où
rows
sont nuls, et vous voulez rendre l'erreur évidente à tous vos appelants dès que possible .Vous devez jongler avec de nombreuses valeurs lorsque vous développez un logiciel. Même la robustesse elle-même est une qualité très complexe - par exemple, je ne considérerais pas votre contrôle défensif plus robuste que de lancer une exception. Les exceptions sont assez pratiques pour vous permettre de réessayer dans un endroit sûr: les problèmes de corruption de données sont généralement beaucoup plus difficiles à suivre que de reconnaître un problème à un stade précoce et de le gérer en toute sécurité. En fin de compte, ils ont tendance à vous donner une illusion de robustesse, puis un mois plus tard, vous remarquez qu'un dixième de vos rendez-vous ont disparu, car vous n'avez jamais remarqué qu'une liste différente avait été mise à jour. Aie.
Assurez-vous de faire la distinction entre les deux. La programmation défensive est une technique utile pour détecter les erreurs à un endroit où elles sont les plus pertinentes, ce qui contribue considérablement à vos efforts de débogage et permet une bonne gestion des exceptions, empêchant ainsi une "corruption sournoise". Échouer tôt, échouer vite. D'autre part, ce que vous faites ressemble plus à "cacher une erreur" - vous jonglez avec les entrées et faites des suppositions sur ce que l'appelant voulait dire. Ceci est très important pour le code destiné à l'utilisateur (par exemple, la vérification orthographique), mais vous devez être prudent lorsque vous le voyez avec du code destiné aux développeurs.
Le problème principal est que quelle que soit votre abstraction, elle va couler ("Je voulais taper ofrerere, pas avant! Stupid correcteur orthographique!"), Et le code pour gérer tous les cas spéciaux et les corrections est toujours le code que vous besoin de maintenir et de comprendre, et le code que vous devez tester. Comparez les efforts pour vous assurer qu'une liste non NULL est passée et pour corriger le bogue que vous avez en production un an plus tard - ce n'est pas un bon compromis. Dans un monde idéal, vous voudriez que chaque méthode fonctionne exclusivement avec sa propre entrée, renvoyant un résultat et ne modifiant aucun état global. Bien sûr, dans le monde réel, vous trouverez de nombreux cas où ce n'est pasla solution la plus simple et la plus claire (par exemple lors de la sauvegarde d’un fichier), mais j’estime que le fait de garder les méthodes "pures" quand elles n’ont aucune raison de lire ou de manipuler un état global rend le code beaucoup plus facile à raisonner. Cela tend également à vous donner des points plus naturels pour séparer vos méthodes :)
Cela ne signifie pas que tout ce qui est inattendu doit faire planter votre application, bien au contraire. Si vous utilisez bien les exceptions, elles constituent naturellement des points de traitement des erreurs sécurisés, dans lesquels vous pouvez restaurer un état d'application stable et permettre à l'utilisateur de continuer ce qu'il est en train de faire (idéalement tout en évitant toute perte de données pour l'utilisateur). À ces points de traitement, vous verrez des possibilités de résoudre les problèmes ("Aucun numéro de commande 2212 trouvé. Voulez-vous dire 2212b?") Ou de donner le contrôle à l'utilisateur ("Erreur de connexion à la base de données. Essayez à nouveau?"). Même si aucune option de ce type n'est disponible, au moins, cela vous donnera une chance que rien ne soit corrompu - j'ai commencé à apprécier le code qui utilise
using
ettry
...finally
beaucoup plus quetry
...catch
, cela vous donne beaucoup d’occasions de maintenir des invariants même dans des conditions exceptionnelles.Les utilisateurs ne doivent pas perdre leurs données et travailler. Cela doit encore être équilibré avec les coûts de développement, etc., mais c'est une très bonne directive générale (si les utilisateurs décident d'acheter ou non votre logiciel, les logiciels internes n'ont généralement pas ce luxe). Même toute l'application qui tombe en panne devient beaucoup moins problématique si l'utilisateur peut simplement redémarrer et revenir à ce qu'il était en train de faire. C’est une véritable robustesse - Word enregistre votre travail tout le temps sans corrompre votre document sur le disque et vous offre une optionrestaurer ces modifications après le redémarrage de Word après un blocage. Est-ce mieux qu'un no bug en premier lieu? Probablement pas - mais n'oubliez pas que le travail passé à attraper un bug rare pourrait être mieux dépensé partout. Mais c'est beaucoup mieux que les alternatives - par exemple, un document corrompu sur le disque, tout le travail depuis la dernière sauvegarde perdue, le document remplacé automatiquement par des modifications avant le crash qui se trouvaient juste être Ctrl + A et Supprimer.
la source
Je vais répondre à cette question en partant de votre hypothèse selon laquelle un code robuste vous sera bénéfique à partir de maintenant. Si votre objectif est d'obtenir des avantages à long terme, je privilégierais la conception et la maintenabilité à la robustesse.
Le compromis entre conception et robustesse, c'est du temps et de la concentration. La plupart des développeurs préfèrent disposer d'un ensemble de code bien conçu, même si cela implique de passer par certains points problématiques et d'effectuer certaines conditions supplémentaires ou de gérer les erreurs. Après quelques années d'utilisation, les utilisateurs ont probablement identifié les lieux qui vous intéressent vraiment.
En supposant que la conception soit à peu près de la même qualité, moins de code est facile à gérer. Cela ne signifie pas que nous ferions mieux de laisser les problèmes connus disparaître pendant quelques années, mais l'ajout d'éléments dont vous savez que vous n'avez pas besoin complique les choses. Nous avons tous examiné le code existant et trouvé des pièces inutiles. Vous devez avoir un code de changement de niveau de confiance élevé qui fonctionne depuis des années.
Donc, si vous pensez que votre application est conçue aussi bien que possible, qu’elle est facile à gérer et qu’elle n’a pas de bugs, trouvez quelque chose de mieux à faire que d’ajouter du code dont vous n’avez pas besoin. C'est le moins que vous puissiez faire par respect pour tous les autres développeurs qui travaillent de longues heures sur des fonctionnalités inutiles.
la source
Non, vous ne devriez pas. Et vous répondez à votre propre question lorsque vous déclarez que cette méthode de codage peut masquer des bogues . Cela ne rendra pas le code plus robuste, mais le rendra plus vulnérable aux bogues et rendra le débogage plus difficile.
Vous indiquez vos attentes actuelles concernant l'
rows
argument: Soit il est nul, soit il contient au moins un élément. La question est donc la suivante: est-ce une bonne idée d'écrire le code pour gérer en plus le troisième cas inattendu dans lequel il n'yrows
a aucun élément?La réponse est non. Vous devriez toujours lever une exception en cas d'entrée inattendue. Considérez ceci: Si certaines autres parties du code annulent l’attente (c’est-à-dire le contrat) de votre méthode, cela signifie qu’il ya un bogue . S'il y a un bogue, vous voulez le savoir le plus tôt possible pour pouvoir le corriger, et une exception vous aidera à le faire.
Ce que le code fait actuellement, c'est de deviner comment récupérer un bogue qui peut ou non exister dans le code. Mais même s'il y a un bogue, vous ne pouvez pas savoir comment le récupérer complètement. Les bogues par définition ont des conséquences inconnues. Certains codes d’initialisation n’ont peut-être pas fonctionné comme prévu. Cela pourrait avoir d’autres conséquences que la seule ligne manquante.
Donc, votre code devrait ressembler à ceci:
Remarque: Il existe des cas spécifiques dans lesquels il est logique de "deviner" comment gérer une entrée non valide plutôt que de simplement lancer une exception. Par exemple, si vous gérez des entrées externes, vous n’avez aucun contrôle. Les navigateurs Web sont un exemple infâme, car ils essaient de gérer avec élégance tout type d’entrée mal formée et non valide. Mais cela n'a de sens qu'avec une entrée externe, pas avec des appels provenant d'autres parties du programme.
Edit: Certaines autres réponses indiquent que vous effectuez une programmation défensive . Je ne suis pas d'accord. La programmation défensive signifie que vous ne faites pas automatiquement confiance à une entrée valide. Ainsi, la validation des paramètres (comme ci-dessus) est une technique de programmation défensive, mais cela ne signifie pas que vous devez modifier les entrées inattendues ou non valides en les devinant. L'approche défensive robuste est de valider l' entrée et ensuite lancer une exception en cas d'entrée incorrecte ou inattendue.
la source
Vous ne devez pas ajouter de code redondant à tout moment.
Vous ne devez pas ajouter de code nécessaire uniquement dans le futur.
Vous devez vous assurer que votre code se comporte bien, peu importe ce qui se passe.
La définition de "bien se comporter" est à la hauteur de vos besoins. Une technique que j'aime bien utiliser est la "paranoïa". Si je suis sûr à 100% qu’un certain cas ne peut jamais se produire, je programme toujours une exception, mais je le fais d’une manière qui: a) indique clairement à tout le monde que je ne prévois jamais que cela se produise, et b) est clairement affiché et consigné et ne conduit donc pas à une corruption rampante plus tard.
Exemple de pseudo-code:
Cela indique clairement que je suis à 100% sûr de pouvoir toujours ouvrir, écrire ou fermer le fichier, c'est-à-dire que je ne vais pas jusqu'à créer un traitement complexe des erreurs. Mais si (non: quand) je ne peux pas ouvrir le fichier, mon programme échouera quand même de manière contrôlée.
Bien entendu, l’interface utilisateur n’affiche pas de tels messages à l’utilisateur, ils sont consignés en interne avec la trace de la pile. Encore une fois, ce sont des exceptions internes "Paranoia" qui s'assurent juste que le code "s'arrête" quand quelque chose d'inattendu se produit. Cet exemple est un peu caché. En pratique, j’impliquerais bien sûr le traitement des erreurs lors de l’ouverture des fichiers, car cela se produit régulièrement (nom de fichier incorrect, clé USB montée en lecture seule, peu importe).
Un terme de recherche connexe très important serait "échec rapide", comme indiqué dans d'autres réponses, et est très utile pour créer un logiciel robuste.
la source
Il y a beaucoup de réponses trop compliquées ici. Vous avez probablement posé cette question parce que vous ne vous sentiez pas bien à propos de ce code, mais vous ne saviez pas pourquoi ni comment le corriger. Donc, ma réponse est que le problème est très probable dans la structure du code (comme toujours).
Tout d'abord, l'en-tête de la méthode:
Attribuer rendez-vous à quelle rangée? Cela devrait être clair immédiatement de la liste des paramètres. Sans aucune connaissance plus loin, je vous attendre à regarder la méthode params comme ceci:
(Appointment app, CalendarRow row)
.Ensuite, les "contrôles d'entrée":
C'est des conneries.
rows
à la méthode est probablement une erreur (voir le commentaire ci-dessus), il ne devrait pas être de la responsabilité de la méthode appeléeAssignAppointmentToRow
de manipuler des lignes autrement que par l'attribution du rendez-vous quelque part.Mais l'idée d'assigner des rendez-vous quelque part est étrange (à moins qu'il s'agisse d'une partie graphique du code). Votre code semble contenir (ou du moins tente de le faire) une structure de données explicite représentant un calendrier (
List<CalendarRows>
<- qui doit être défini commeCalendar
quelque part si vous voulez utiliser cette méthode, vous passez alorsCalendar calendar
à votre méthode). Si vous vous en tenez ainsi, je m'attendrais à ce que lecalendar
soit pré-rempli avec des créneaux où vous placez (affectez) les rendez-vous après (par exemplecalendar[month][day] = appointment
serait le code approprié). Mais vous pouvez également choisir d’abandonner la structure du calendrier de la logique principale et d’avoir justeList<Appointment>
où lesAppointment
objets contiennent des attributs.date
. Et ensuite, si vous devez rendre un calendrier quelque part dans l'interface graphique, vous pouvez créer cette structure de "calendrier explicite" juste avant le rendu.Comme je ne connais pas les détails de votre application, certaines de ces informations ne vous concernent peut-être pas, mais ces deux vérifications (principalement la deuxième) me disent qu'il y a quelque chose qui cloche avec la séparation des problèmes dans votre code.
la source
Pour des raisons de simplicité, supposons que vous aurez éventuellement besoin de ce morceau de code dans N jours (pas plus tard ou plus tôt), soit que vous n’avez pas du tout besoin de le faire.
Pseudocode:
Facteurs pour
C_maint
:C_maint
attenduC_maint
. Ce cas nécessite une formule plus complexe, avec plus de fois des variables telles queN
.Il faut donc laisser de côté une chose tellement importante qui alourdit le code et pourrait ne devenir nécessaire que dans deux ans avec une probabilité faible, mais elle devrait également être mise en œuvre .
la source