web-dev-qa-db-fra.com

Qu'est-ce qui définit un code robuste?

Mon professeur continue de se référer à cet exemple Java quand il parle de code "robuste":

if (var == true) {
    ...
} else if (var == false) {
    ...
} else {
    ...
}

Il prétend que "code robuste" signifie que votre programme prend en compte toutes les possibilités, et qu'il n'y a pas d'erreur: toutes les situations sont gérées par le code et aboutissent à un état valide, d'où le "else".

Je doute cependant. Si la variable est un booléen, quel est l'intérêt de vérifier un troisième état lorsqu'un troisième état est logiquement impossible?

"Ne pas avoir d'erreur" semble également ridicule; même les applications Google affichent des erreurs directement à l'utilisateur au lieu de les engloutir silencieusement ou de les considérer comme un état valide. Et c'est bien - j'aime savoir quand quelque chose ne va pas. Et il semble tout à fait prétendre qu'une application n'aurait jamais d'erreurs.

Alors, quelle est la définition réelle de "code robuste"?

42
Lotus Notes

quel est l'intérêt de vérifier un troisième état lorsqu'un troisième état est logiquement impossible?

Qu'en est-il d'un Boolean? qui permet un état NULL qui n'est ni vrai ni faux. Maintenant, que devrait faire le logiciel? Certains logiciels doivent être hautement résistants aux chocs comme les stimulateurs cardiaques. Avez-vous déjà vu quelqu'un ajouter une colonne à une base de données qui était un Boolean et initialiser les données actuelles à NULL au départ? Je sais que je l'ai vu.

Voici quelques liens qui expliquent ce que signifie être robuste en termes de logiciels:

Si vous pensez qu'il existe une définition universellement acceptée de "robuste" ici, bonne chance. Il peut y avoir des synonymes comme anti-bombe ou anti-idiot. The Duct Tape Programmer serait un exemple de quelqu'un qui écrit habituellement du code robuste au moins dans ma compréhension des termes.

33
JB King

Pour les besoins de ma discussion, un Bool peut avoir 2 états, True ou False. Tout le reste est non conforme à la spécification des langages de programmation. Si votre chaîne d'outils n'est pas conforme à ses spécifications, vous êtes arrosé quoi que vous fassiez. Si un développeur créait un type de Bool qui avait plus de 2 états, c'est la dernière chose qu'il ferait sur ma base de code.

Option A.

if (var == true) {
    ...
} else if (var == false) {
    ...
} else {
    ...
}

Option B

if (var == true) {
    ...
} else {
    ...
}

J'affirme que l'option B est plus robuste .....

Tout twit peut vous dire de gérer les erreurs inattendues. Ils sont généralement faciles à détecter une fois que vous y pensez. L'exemple que votre professeur a donné n'est pas quelque chose qui pourrait arriver, c'est donc un très mauvais exemple.

A est impossible à tester sans harnais de test alambiqués. Si vous ne pouvez pas le créer, comment allez-vous le tester? Si vous n'avez pas testé le code, comment savez-vous qu'il fonctionne? Si vous ne savez pas que cela fonctionne, vous n'écrivez pas de logiciel robuste. Je pense qu'ils appellent toujours ça un Catch22 (grand film, regardez-le parfois).

L'option B est triviale à tester.

Prochain problème, posez-vous cette question au professeur: "Que voulez-vous que je fasse à ce sujet si un booléen n'est ni vrai ni faux?" Cela devrait conduire à une discussion très intéressante .....

Dans la plupart des cas, un vidage de mémoire est approprié, au pire, il ennuie l'utilisateur ou coûte beaucoup d'argent. Et si, disons, le module était le système de calcul de rentrée en temps réel de la navette spatiale? N'importe quelle réponse, aussi inexacte soit-elle, ne peut pas être pire que l'avortement, ce qui tuera les utilisateurs. Alors, que faire, si vous savez que la réponse peut être fausse, optez pour le 50/50, ou abandonnez et allez à l'échec à 100%. Si j'étais membre d'équipage, je prendrais le 50/50.

L'option A me tue L'option B me donne une chance égale de survie.

Mais attendez - c'est une simulation de la rentrée de la navette spatiale - alors quoi? Abandonnez pour que vous le sachiez. Cela vous semble une bonne idée? - NON - car vous devez tester avec le code que vous prévoyez d'expédier.

L'option A est meilleure pour la simulation, mais ne peut pas être déployée. Il est inutile que l'option B soit le code déployé, la simulation fonctionne de la même manière que les systèmes en direct.

Disons que c'était une préoccupation valable. La meilleure solution serait d'isoler la gestion des erreurs de la logique d'application.

if (var != true || var != false) {
    errorReport("Hell just froze over, var must be true or false")
}
......
if (var == true){
 .... 
} else {
 .... 
}

Lecture plus approfondie - Machine à rayons X Therac-25, panne de fusée Ariane 5 et autres (le lien contient de nombreux liens rompus mais suffisamment d'informations pour que Google puisse vous aider)

11
mattnz

En fait, votre code n'est pas plus robuste mais MOINS robuste. Le else final est tout simplement du code mort que vous ne pouvez pas tester.

Dans les logiciels critiques comme les vaisseaux spatiaux, le code mort et plus généralement le code non testé sont interdits: si un rayon cosmique produit un seul événement bouleversé qui à son tour rend votre code mort activé, tout est possible. Si la SEU active une partie du code robuste, le comportement (inattendu) reste sous contrôle.

9
mouviciel

Je pense que le professeur peut confondre "erreur" et "bug". Un code robuste devrait certainement avoir peu ou pas de bogues. Un code robuste peut, et dans un environnement hostile, doit avoir une bonne gestion des erreurs (que ce soit la gestion des exceptions ou des tests d'état de retour rigoureux).

Je suis d'accord que l'exemple de code du professeur est idiot, mais pas aussi idiot que le mien.

// Assign 3 to x
var x = 3;
x = 3;   // again, just for sure
while (x < 3 or x > 3) { x = 3; }  // being robust
if (x != 3) { ... }  // this got to be an error!
7
David Andersson

Il n'y a pas de définition convenue de Code robuste , car pour beaucoup de choses en programmation c'est plus ou moins subjectif ...

L'exemple que donne votre professeur dépend de la langue:

  • Dans Haskell, un Boolean peut être True ou False, il n'y a pas de troisième option
  • En C++, un bool peut être true, false, ou (malheureusement) provenir d'une distribution douteuse qui le place dans un cas inconnu ... Ceci ne devrait pas se produire, mais peut, à la suite d'une erreur précédente.

Cependant, ce que votre professeur conseille obscurcit le code en introduisant une logique étrangère pour les événements à ne pas se produire au milieu du programme de base, donc je vais pointez-vous plutôt vers Programmation défensive .

Dans le cas universitaire, vous pouvez même l'augmenter en adoptant une stratégie Design By Contract:

  • Établir des invariants pour les classes (par exemple, size est le nombre d'éléments dans la liste data)
  • Établir des pré-conditions et des post-conditions pour chaque fonction (par exemple, cette fonction ne peut être invoquée qu'avec a étant inférieur à 10)
  • Testez chacun de ceux aux points d'entrée et de sortie de chacune de vos fonctions

Exemple:

class List:
  def __init__(self, items):
    self.__size = len(items)
    self.__data = items

  def __invariant(self):
    assert self.__size == len(self.__data)

  def size(self):
    self.__invariant()

    return self.__size

  def at(self, index):
    """index should be in [0,size)"""
    self.__invariant()
    assert index >= 0 and index < self.__size

    return self.__data[index]

  def pushback(self, item):
    """the subsequent list is one item longer
       the item can be retrieved by self.at(self.size()-1)"""
    self.__invariant()

    self.__data.append(item)
    self.__size += 1

    self.__invariant()
    assert self.at(self.size()-1) == item
6
Matthieu M.

L'approche de votre professeur est totalement erronée.

Une fonction, ou juste un peu de code, devrait avoir une spécification qui dit ce qu'elle fait, qui devrait couvrir toutes les entrées possibles. Et le code doit être écrit de sorte que son comportement soit garanti pour correspondre à la spécification. Dans l'exemple, j'écrirais la spécification assez simple comme ceci:

Spec: If var is false then the function does "this", otherwise it does "that". 

Ensuite, vous écrivez la fonction:

if (var == false) dothis; else dothat; 

et le code répond aux spécifications. Donc, votre professeur dit: Et si var == 42? Regardez la spécification: il dit que la fonction devrait faire "ça". Regardez le code: la fonction fait "ça". La fonction répond aux spécifications.

Lorsque le code de votre professeur rend les choses totalement non fiables, c'est le fait qu'avec son approche, lorsque var n'est ni vrai ni faux, il exécutera du code qui n'a jamais été appelé auparavant et qui est complètement non testé, avec des résultats tout à fait imprévisibles.

2
gnasher729

Je suis d'accord avec la déclaration de @ gnasher729: L'approche de votre professeur est totalement erronée.

Robuste signifie qu'il est résistant à la casse/à la défaillance car il fait peu d'hypothèses et est découplé: il est autonome, auto-défini et portable. Cela implique également d'être adaptable à l'évolution des besoins. Dans un mot, votre code est durable.

Cela se traduit généralement par des fonctions courtes qui obtiennent leurs données à partir de paramètres transmis par l'appelant et l'utilisation d'interfaces publiques pour les consommateurs - méthodes abstraites, wrappers, indirection, interfaces de style COM, etc. - plutôt que des fonctions contenant du code d'implémentation concret.

1
Vector

Un code robuste est simplement un code qui gère bien les échecs. Ni plus ni moins.

Parmi les échecs, il existe de nombreux types: code incorrect, code incomplet, valeurs inattendues, états inattendus, exceptions, épuisement des ressources, ... Un code robuste les gère bien.

1
Sparky

Je considérerais le code que vous avez donné comme un exemple de programmation défensive (au moins comme j'utilise le terme). Une partie de la programmation défensive consiste à faire des choix qui minimisent les hypothèses faites sur le comportement du reste du système. Par exemple, laquelle de ces options est la meilleure:

for (int i = 0; i != sequence.length(); ++i) {
    // do something with sequence[i]
}

Ou:

for (int i = 0; i < sequence.length(); ++i) {
    // do something with sequence[i]
}

(Si vous rencontrez des difficultés pour voir la différence, vérifiez le test de boucle: le premier utilise !=, Le second utilise <).

Maintenant, dans la plupart des circonstances, les deux boucles se comporteront exactement de la même manière. Cependant, le premier (en comparant avec !=) Fait l'hypothèse que i ne sera incrémenté qu'une seule fois par itération. S'il ignore la valeur sequence.length(), la boucle pourrait continuer au-delà des limites de la séquence et provoquer une erreur.

Vous pouvez donc faire valoir que la deuxième implémentation est plus robuste: elle ne dépend pas des hypothèses quant à savoir si le corps de la boucle change i (remarque: en fait, elle fait toujours l'hypothèse que i n'est jamais négatif ).

Pour expliquer pourquoi vous ne voudrez peut-être pas faire cette supposition, imaginez que la boucle scanne une chaîne et effectue un traitement de texte. Vous écrivez la boucle et tout va bien. Maintenant, vos exigences changent et vous décidez que vous devez prendre en charge les caractères d'échappement dans la chaîne de texte, de sorte que vous modifiez le corps de la boucle de sorte que s'il détecte un caractère d'échappement (par exemple, une barre oblique inverse), il incrémente i pour ignorer le caractère immédiatement après l'évasion. Maintenant, la première boucle a un bogue car si le dernier caractère du texte est une barre oblique inverse, le corps de la boucle incrémentera i et la boucle continuera au-delà de la fin de la séquence.

0
John Bartholomew