Lors de l'une de mes premières revues de code (il y a quelque temps), on m'a dit qu'il est recommandé d'inclure une clause par défaut dans toutes les instructions switch. Je me suis récemment souvenu de ce conseil, mais je ne me souviens plus de la justification. Cela me semble assez étrange maintenant.
Existe-t-il une raison raisonnable de toujours inclure une instruction par défaut?
Cette langue est-elle dépendante? Je ne me souviens pas de la langue que j'utilisais à l'époque - cela s'applique peut-être à certaines langues et pas à d'autres?
Les cas de commutation doivent presque toujours avoir un cas default
.
Raisons d'utiliser une default
1.Pour 'attraper' une valeur inattendue
switch(type)
{
case 1:
//something
case 2:
//something else
default:
// unknown type! based on the language,
// there should probably be some error-handling
// here, maybe an exception
}
2. Pour gérer les actions 'par défaut', où les cas sont ceux d'un comportement spécial.
Vous voyez cela beaucoup dans les programmes basés sur les menus et les scripts bash shell. Vous pouvez également voir cela lorsqu'une variable est déclarée en dehors du casse du commutateur mais non initialisée, et chaque cas l'initialise à quelque chose de différent. Ici, la valeur par défaut doit également l'initialiser pour que le code de ligne qui accède à la variable ne génère pas d'erreur.
3. Pour montrer à quelqu'un qui lit votre code que vous avez couvert ce cas.
variable = (variable == "value") ? 1 : 2;
switch(variable)
{
case 1:
// something
case 2:
// something else
default:
// will NOT execute because of the line preceding the switch.
}
C'était un exemple simplifié à l'extrême, mais le fait est que quelqu'un qui lit le code ne devrait pas se demander pourquoi variable
ne peut pas être autre chose que 1 ou 2.
Le seul cas auquel je peux penser de NE PAS utiliser default
est lorsque le commutateur vérifie quelque chose pour lequel il est évident que toutes les autres solutions peuvent être ignorées
switch(keystroke)
{
case 'w':
// move up
case 'a':
// move left
case 's':
// move down
case 'd':
// move right
// no default really required here
}
Non.
Et s'il n'y a pas d'action par défaut, le contexte est important. Que faire si vous ne voulez agir que sur quelques valeurs?
Prenons l'exemple de la lecture des touches du clavier pour un jeu.
switch(a)
{
case 'w':
// Move Up
break;
case 's':
// Move Down
break;
case 'a':
// Move Left
break;
case 'd':
// Move Right
break;
}
Ajouter:
default: // Do nothing
N’est qu’une perte de temps et augmente la complexité du code sans raison.
J'utiliserais toujours une clause par défaut, quelle que soit la langue dans laquelle vous travaillez.
Les choses peuvent et vont mal. Les valeurs ne seront pas ce que vous attendez, etc.
Ne pas vouloir inclure une clause par défaut implique que vous êtes sûr de connaître l'ensemble des valeurs possibles. Si vous pensez connaître l'ensemble des valeurs possibles, alors, si la valeur est en dehors de cet ensemble de valeurs possibles, vous voudriez en être informé - il s'agit certainement d'une erreur.
C'est pourquoi vous devriez toujours utiliser une clause default et générer une erreur, par exemple en Java:
switch (myVar) {
case 1: ......; break;
case 2: ......; break;
default: throw new RuntimeException("unreachable");
}
Il n'y a aucune raison d'inclure plus d'informations que la chaîne "inaccessible"; Si cela se produit réellement, vous devrez quand même regarder la source et les valeurs des variables, etc., et la pile d'exceptions inclura ce numéro de ligne. Inutile de perdre votre temps à écrire plus de texte dans le message d'exception.
NE PAS avoir le cas par défaut peut être réellement bénéfique dans certaines situations.
Si vos cas de commutation sont des valeurs énumères, en ne disposant pas de cas par défaut, vous pouvez obtenir un avertissement du compilateur s'il vous manque des cas. Ainsi, si de nouvelles valeurs enum sont ajoutées à l'avenir et que vous oubliez d'ajouter des cas pour ces valeurs dans le commutateur, vous pouvez connaître le problème au moment de la compilation. Vous devez néanmoins vous assurer que le code prend les mesures appropriées pour les valeurs non gérées, au cas où une valeur non valide aurait été transtypée en type enum. Donc, cela peut fonctionner mieux pour les cas simples où vous pouvez revenir dans le cas enum plutôt que de faire une pause.
enum SomeEnum
{
ENUM_1,
ENUM_2,
// More ENUM values may be added in future
};
int foo(SomeEnum value)
{
switch (value)
{
case ENUM_1:
return 1;
case ENUM_2:
return 2;
}
// handle invalid values here
return 0;
}
Dans mon entreprise, nous écrivons des logiciels pour le marché de l'avionique et de la défense, et nous incluons toujours une instruction par défaut, car TOUS les cas d'une instruction switch doivent être explicitement gérés (même s'il ne s'agit que d'un commentaire disant «Ne rien faire»). Nous ne pouvons pas nous permettre le logiciel simplement pour se comporter mal ou simplement planter sur des valeurs inattendues (ou même ce que nous pensons impossible).
On peut discuter qu'un cas par défaut n'est pas toujours nécessaire, mais en l'exigeant toujours, il est facilement vérifié par nos analyseurs de code.
Une instruction "switch" always devrait-elle inclure une clause par défaut? Non, il devrait généralement inclure un défaut.
L'inclusion d'une clause par défaut n'a de sens que si elle a quelque chose à faire, comme affirmer une condition d'erreur ou fournir un comportement par défaut. Y compris un "juste parce que" est une programmation culte de la cargaison et ne fournit aucune valeur. C'est l'équivalent "switch" de dire que toutes les déclarations "if" doivent inclure un "else".
Voici un exemple trivial où cela n'a aucun sens:
void PrintSign(int i)
{
switch (Math.Sign(i))
{
case 1:
Console.Write("positive ");
break;
case -1:
Console.Write("negative ");
break;
default: // useless
}
Console.Write("integer");
}
C'est l'équivalent de:
void PrintSign(int i)
{
int sgn = Math.Sign(i);
if (sgn == 1)
Console.Write("positive ");
else if (sgn == -1)
Console.Write("negative ");
else // also useless
{
}
Console.Write("integer");
}
Autant que je sache, la réponse est 'default' est optionnel, dire qu'un commutateur doit toujours contenir un défaut est comme dire que chaque 'if-elseif' doit contenir un 'else'. S'il y a une logique à faire par défaut, alors l'instruction 'default' devrait être là, mais sinon le code pourrait continuer à s'exécuter sans rien faire.
Avoir une clause par défaut quand ce n'est pas vraiment nécessaire est Programmation défensive Cela conduit généralement à un code trop complexe à cause de trop code de traitement d'erreur. Cette gestion des erreurs et ce code de détection nuisent à la lisibilité du code, rendent la maintenance plus difficile et conduisent finalement à plus de bogues qu’ils ne résolvent.
Donc, je crois que si le défaut ne devrait pas être atteint - vous n’aurez pas à l’ajouter.
Notez que "ne devrait pas être atteint" signifie que s'il atteint un bogue dans le logiciel, vous devez tester des valeurs pouvant contenir des valeurs non souhaitées à cause des entrées de l'utilisateur, etc.
Je dirais que cela dépend de la langue, mais en C, si vous basculez sur un type enum et que vous gérez toutes les valeurs possibles, il vaut probablement mieux ne PAS inclure un cas par défaut. Ainsi, si vous ajoutez une balise enum supplémentaire ultérieurement et oubliez de l'ajouter au commutateur, un compilateur compétent vous avertira du cas manquant.
Si vous savez que l'instruction switch aura uniquement un ensemble défini d'étiquettes ou de valeurs, faites ceci pour couvrir les bases, ainsi vous obtiendrez toujours un résultat valide. Placez simplement la valeur par défaut sur l'étiquette qui serait programmée/logique être le meilleur gestionnaire pour les autres valeurs.
switch(ResponseValue)
{
default:
case No:
return false;
case Yes;
return true;
}
Au moins, ce n'est pas obligatoire en Java. Selon JLS, il est indiqué qu’au moins un cas par défaut peut être présent. Ce qui signifie qu'aucun cas par défaut n'est acceptable. Cela dépend parfois aussi du contexte dans lequel vous utilisez l'instruction switch. Par exemple, en Java, le bloc de commutateur suivant ne nécessite pas de cas par défaut.
private static void switch1(String name) {
switch (name) {
case "Monday":
System.out.println("Monday");
break;
case "Tuesday":
System.out.println("Tuesday");
break;
}
}
Mais dans la méthode suivante qui s'attend à renvoyer une chaîne, la casse par défaut est pratique pour éviter les erreurs de compilation.
private static String switch2(String name) {
switch (name) {
case "Monday":
System.out.println("Monday");
return name;
case "Tuesday":
System.out.println("Tuesday");
return name;
default:
return name;
}
}
bien que vous puissiez éviter les erreurs de compilation pour la méthode ci-dessus sans avoir la casse par défaut, il suffit simplement d'avoir une instruction return à la fin, mais fournir la casse par défaut la rend plus lisible.
Parce que MISRA C le dit bien:
L'exigence d'une clause finale par défaut est la programmation défensive. Cette clause doit soit prendre les mesures appropriées, soit contenir un commentaire approprié expliquant pourquoi aucune mesure n’est prise.
Cela dit, je déconseille de suivre MISRA C à ce sujet, pour la plupart des logiciels:
Si la valeur de commutateur ( switch (variable )) ne peut pas atteindre le cas par défaut, le cas par défaut n'est pas du tout nécessaire. Même si nous conservons le cas par défaut, il n'est pas exécuté du tout. C'est du code mort.
C'est une "convention" de codage facultatif. Selon l'utilisation est de savoir si cela est nécessaire ou non. Je crois personnellement que si vous n'en avez pas besoin, cela ne devrait pas être là. Pourquoi inclure quelque chose qui ne sera pas utilisé ou atteint par l'utilisateur?
Si les possibilités de cas sont limitées (c'est-à-dire un booléen), la clause par défaut estredundant!
Vous devriez avoir un défaut pour attraper les valeurs inattendues qui arrivent.
Cependant, je ne suis pas d’accord avec Adrian Smith pour dire que votre message d’erreur devrait être quelque chose de totalement insignifiant. Il y a peut-être un cas non traité que vous n'aviez pas prévu (ce qui est un peu le point) que votre utilisateur finira par voir et un message du type "inaccessible" est totalement inutile et n'aide personne dans cette situation.
Exemple, combien de fois avez-vous eu un BSOD totalement dépourvu de sens? Ou une exception fatale @ 0x352FBB3C32342?
S'il n'y a pas de cas par défaut dans une instruction switch
, le comportement peut être imprévisible si ce cas survient à un moment donné, ce qui n'était pas prévisible au stade du développement. Il est recommandé d’inclure un cas default
.
switch ( x ){
case 0 : { - - - -}
case 1 : { - - - -}
}
/* What happens if case 2 arises and there is a pointer
* initialization to be made in the cases . In such a case ,
* we can end up with a NULL dereference */
Une telle pratique peut donner lieu à un bogue tel que déréférence NULL, fuite de mémoire ainsi que d’autres types de bogues graves.
Par exemple, nous supposons que chaque condition initialise un pointeur. Mais si default
est le cas Censé survenir et si nous n’initialisons pas dans ce cas, il est alors possible d’atteindre Avec une exception pointeur nulle. Il est donc suggéré d’utiliser une instruction default
, même si celle-ci peut être triviale.
Dépend de la manière dont le commutateur fonctionne dans une langue particulière. Toutefois, dans la plupart des langues, si aucun cas ne correspond, l'exécution passe par l'instruction switch sans préavis. Imaginez que vous attendiez un ensemble de valeurs et que vous les traitiez dans un commutateur, mais vous obtenez une autre valeur dans l'entrée. Rien ne se passe et vous ne savez pas que rien ne s'est passé. Si vous attrapiez le cas par défaut, vous sauriez qu'il y a quelque chose qui cloche.
Le cas par défaut peut ne pas être nécessaire dans le commutateur utilisé par enum. lorsque switch contient toute la valeur, le cas par défaut ne sera jamais exécuté. Donc dans ce cas, ce n'est pas nécessaire.
Je crois que cela est assez spécifique à la langue et pour le cas C++, un point mineur pour le type enum class . Ce qui semble plus sûr que le C enum traditionnel. MAIS
Si vous regardez l'implémentation de std :: byte c'est quelque chose comme:
enum class byte : unsigned char {} ;
Source: https://en.cppreference.com/w/cpp/language/enum
Et considérez aussi ceci:
Sinon, si T est un type d’énumération dont le type est sous-jacent fixe ou non, et si la liste braced-init-list n’a qu’un initialiseur, et si la conversion de l’initialiseur en type sous-jacent n’est pas étroite, et si l'initialisation est directe-liste-initialisation, puis l'énumération est initialisée avec le résultat de la conversion de l'initialiseur en son type sous-jacent.
(depuis C++ 17)
Source: https://en.cppreference.com/w/cpp/language/list_initialization
Ceci est un exemple de classe enum représentant des valeurs qui ne sont pas des énumérateurs. Pour cette raison, vous ne pouvez pas vous fier totalement aux enums. Selon l'application, cela peut être important.
Cependant, j'aime beaucoup ce que @Harlan Kassler a déclaré dans son message et je commencerai à utiliser cette stratégie dans certaines situations.
Juste un exemple de classe enum dangereux:
enum class Numbers : unsigned
{
One = 1u,
Two = 2u
};
int main()
{
Numbers zero{ 0u };
return 0;
}
Les instructions de commutateur doivent-elles toujours contenir une clause par défaut? Aucun cas de commutateur ne peut exister sans le cas, le cas par défaut déclenchera la valeur de commutateur switch(x)
dans ce cas x s'il ne correspond à aucune autre valeur de cas.