Switch Statement Fallthrough… deveria ser permitido?

Por tanto tempo quanto me lembro, evitei usar o recurso de troca de instrução switch. Na verdade, não consigo me lembrar de alguma vez ter entrado na minha consciência como uma maneira possível de fazer as coisas, já que me ocorreu na minha cabeça que nada mais era do que um bug na declaração de troca. No entanto, hoje eu encontrei um código que o usa por design, o que me fez pensar imediatamente no que todo mundo na comunidade pensa sobre a queda da declaração de switch.

É algo que uma linguagem de programação deve explicitamente não permitir (como o C # faz, apesar de fornecer uma solução alternativa) ou é um recurso de qualquer linguagem que é poderosa o suficiente para deixar nas mãos do programador?

Edit: Eu não era específico o suficiente para o que eu quis dizer com a queda. Eu uso muito esse tipo:

switch(m_loadAnimSubCt){ case 0: case 1: // Do something break; case 2: case 3: case 4: // Do something break; } 

No entanto, estou preocupado com algo assim.

  switch(m_loadAnimSubCt){ case 0: case 1: // Do something but fall through to the other cases // after doing it. case 2: case 3: case 4: // Do something else. break; } 

Desta forma sempre que o caso for 0, 1 fará tudo na instrução switch. Eu vi isso por design e eu só não sei se eu concordo que as declarações de switch devem ser usadas dessa maneira. Eu acho que o primeiro exemplo de código é muito útil e seguro. O segundo parece meio perigoso.

Pode depender do que você considera queda. Eu estou bem com esse tipo de coisa:

 switch (value) { case 0: result = ZERO_DIGIT; break; case 1: case 3: case 5: case 7: case 9: result = ODD_DIGIT; break; case 2: case 4: case 6: case 8: result = EVEN_DIGIT; break; } 

Mas se você tiver um label de caso seguido por um código que caia em outro label de caso, eu consideraria esse mal sempre. Talvez mover o código comum para uma function e chamar de ambos os lugares seja uma ideia melhor.

E, por favor, note que eu uso a definição de FAQ C ++ de “mal”

É uma espada de dois gumes. Às vezes muito útil, muitas vezes perigoso.

Quando isso é bom? Quando você quer 10 casos todos processados ​​da mesma maneira …

 switch (c) { case 1: case 2: ... do some of the work ... /* FALLTHROUGH */ case 17: ... do something ... break; case 5: case 43: ... do something else ... break; } 

A única regra que eu gosto é que se você fizer alguma coisa extravagante onde você exclui o intervalo, você precisa de um comentário claro / * FALLTHROUGH * / para indicar que foi sua intenção.

Fallthrough é realmente uma coisa útil, dependendo do que você está fazendo. Considere esta maneira organizada e compreensível para organizar as opções:

 switch ($someoption) { case 'a': case 'b': case 'c': // do something break; case 'd': case 'e': // do something else break; } 

Imagine fazer isso com if / else. Seria uma bagunça.

Você já ouviu falar do dispositivo de Duff ? Este é um ótimo exemplo de uso de queda de switch.

É um recurso que pode ser usado e pode ser abusado, como quase todos os resources de linguagem.

Pode ser muito útil algumas vezes, mas, em geral, a não-queda é o comportamento desejado. O avanço deve ser permitido, mas não implícito.

Um exemplo, para atualizar versões antigas de alguns dados:

 switch (version) { case 1: // update some stuff case 2: // update more stuff case 3: // update even more stuff case 4: // and so on } 

Eu adoraria uma syntax diferente para fallbacks em switches, algo como, errr ..

 switch(myParam) { case 0 or 1 or 2: // do something; break; case 3 or 4: // do something else; break; } 

Nota: Isso já seria possível com enums, se você declarar todos os casos em seu enum usando flags, certo? Também não parece tão ruim, os casos poderiam (deveria?) Muito bem fazer parte do seu enum já.

Talvez este seria um bom caso (sem trocadilhos) para uma interface fluente usando methods de extensão? Algo como, errr …

 int value = 10; value.Switch() .Case(() => { /* do something; */ }, new {0, 1, 2}) .Case(() => { /* do something else */ } new {3, 4}) .Default(() => { /* do the default case; */ }); 

Embora isso seja ainda menos legível: P

Como com qualquer coisa: se usado com cuidado, pode ser uma ferramenta elegante.

No entanto, eu acho que os inconvenientes mais do que justificam não usá-lo e, finalmente, não permitir mais (C #). Entre os problemas estão:

  • é fácil “esquecer” uma pausa
  • nem sempre é óbvio para os mantenedores de código QUE uma quebra omitida foi intencional

bom uso de um interruptor / caso de queda:

 switch (x) { case 1: case 2: case 3: do something break; } 

BAAAAAD uso de um interruptor / caso de queda:

 switch (x) { case 1: some code case 2: some more code case 3: even more code break; } 

Isso pode ser reescrito usando if / else constrói sem nenhuma perda na minha opinião.

Minha palavra final: fique longe de labels de caso de fall-through como no exemplo BAD, a menos que você esteja mantendo código legado onde esse estilo é usado e bem compreendido.

Poderoso e perigoso. O maior problema com o fall-through é que não é explícito. Por exemplo, se você se deparar com um código freqüentemente editado que tenha um switch com fall-throughs, como você sabe que isso é intencional e não um bug?

Em qualquer lugar que eu use, eu asseguro que esteja devidamente comentado:

 switch($var) { case 'first': // fall-through case 'second': i++; break; } 

Eu não gosto de minhas declarações de switch para cair – é muito propenso a erros e difícil de ler. A única exceção é quando várias instruções de case fazem exatamente a mesma coisa.

Se houver algum código comum que várias ramificações de uma instrução switch desejem usar, extraio isso em uma function comum separada que pode ser chamada em qualquer ramificação.

Usar o fall-through como em seu primeiro exemplo é claramente OK, eu não consideraria isso um verdadeiro avanço.

O segundo exemplo é perigoso e (se não for comentado extensivamente) não é óbvio. Eu ensino meus alunos a não usar tais construções, a menos que considerem valer a pena dedicar um bloco de comentários a ela, que descreve que isso é um avanço intencional e por que essa solução é melhor que as alternativas. Isso desencoraja o uso desleixado, mas ainda o torna permitido nos casos em que é usado para uma vantagem.

Isso é mais ou menos equivalente ao que fizemos em projetos espaciais quando alguém queria violar o padrão de codificação: eles precisavam solicitar a dispensação (e eu fui chamado para aconselhar sobre a decisão).

Queda pensamento deve ser usado somente quando é usado como uma tabela de salto em um bloco de código. Se houver alguma parte do código com uma quebra incondicional antes de mais casos, todos os grupos de casos devem terminar dessa forma. Qualquer outra coisa é “mal”.

Em alguns casos, usar fall-throughs é um ato de preguiça por parte do programador – eles poderiam usar uma série de || declarações, por exemplo, mas em vez disso, use uma série de casos de switch ‘catch-all’.

Dito isto, descobri que eles são especialmente úteis quando sei que, eventualmente, precisarei das opções de qualquer maneira (por exemplo, em uma resposta de menu), mas ainda não implementei todas as opções. Da mesma forma, se você está fazendo um fall-through para ‘a’ e ‘A’, eu acho substancialmente mais limpo usar o switch fall-through do que um compound if statement.

É provavelmente uma questão de estilo e como os programadores pensam, mas eu geralmente não gosto de remover componentes de uma linguagem em nome da ‘segurança’ – é por isso que eu tento mais para C e suas variantes / descendentes do que, digamos, Java. Eu gosto de poder brincar com pointers e afins, mesmo quando não tenho “razão” para isso.