Falha na instrução switch ... deve ser permitido? [fechadas]

120

Desde que me lembro, evitei usar a instrução switch fall-through. Na verdade, não consigo me lembrar de alguma vez ter entrado em minha consciência como uma forma possível de fazer as coisas, já que foi perfurado em minha cabeça logo no início que não era nada mais do que um bug na instrução switch. No entanto, hoje encontrei um código que o usa por design, o que me fez imediatamente imaginar o que todos na comunidade pensam sobre o erro de instrução switch.

É algo que uma linguagem de programação não deveria permitir explicitamente (como o C #, embora forneça uma solução alternativa) ou é um recurso de qualquer linguagem poderoso o suficiente para deixar nas mãos do programador?

Edit: Eu não fui específico o suficiente para o que quero dizer com falha. 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;
   }

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

Fostah
fonte
51
Discordo sobre não ser construtivo. 27 votos positivos sobre a questão indicam que outras pessoas também se sentem assim.
Modos Wes de
2
"não construtivo" é um motivo antigo e um tanto vago. Hoje em dia, essas questões podem, e devem, ser sinalizadas para encerramento como sendo puramente baseadas em opiniões. SO é para perguntas respondíveis sobre código ou design, não 'qual é a opinião da "comunidade" [seja lá o que for] sobre minha opinião sobre o recurso X'. Programmers.SE seria uma opção melhor, mas mesmo assim, o valor de uma questão tão ampla e orientada por opinião ainda é altamente duvidoso.
sublinhado_d
1
Há casos de uso muito claros em que o fallthrough é usado, então não tenho certeza de por que seria baseado em opinião. Concordo que essa pergunta não é um pouco clara, mas não deveria ter sido encerrada como não construtiva. Talvez apenas editado para ser um caso mais claro de: "Quando o fallthrough é usado?" Que tem uma resposta clara. Bons textos em C ++ abordam isso, é confuso para novos programadores (ou até programadores mais experientes de outras linguagens), então parece uma boa pergunta para SO. Na verdade, parece uma pergunta SO prototipicamente boa. Mesmo que a forma como foi perguntada não fosse perfeita.
Eric,

Respostas:

89

Pode depender do que você considera um avanço. 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 rótulo de caso seguido por um código que passa por outro rótulo de caso, eu sempre consideraria isso mal. Talvez mover o código comum para uma função e chamar de ambos os lugares seja uma ideia melhor.

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

Fred Larson
fonte
Eu concordo com você: não penso em seu exemplo como um fallthrough e, se houver um caso em que desejo executar vários blocos de código por meio do fallthrough, provavelmente há uma maneira melhor de fazer isso.
Dave DuPlantis,
10
+1 simplesmente por essa definição de "mal". Vou pegar emprestado, obrigado ;-)
Joachim Sauer
Eu entendi e você está certo, mas seu exemplo é muito ruim. Ninguém deve testar dígitos de teste como este. Scnr, sua resposta está certa de qualquer maneira.
Tim Büthe,
Caso em questão, é exatamente isso que o C # não permite. Provavelmente por um motivo :-)
Joey,
Infelizmente, a definição de mal parece ter ficado offline
HopefullyHelpful
57

É uma espada de dois gumes. Às vezes é muito útil, mas frequentemente perigoso.

Quando fica bom? Quando você quiser que 10 casos sejam 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 de que gosto é que, se você fizer algo extravagante e excluir o intervalo, precisará de um comentário claro / * FALLTHROUGH * / para indicar que essa foi sua intenção.

John M
fonte
3
+1! Concordo que, ocasionalmente, é a resposta certa e DUVIDAMENTE concordo que, quando é intencional, deve ser comentada. (Em uma revisão de código, eu FARIA o outro comentário do desenvolvedor uma falha de projeto não vazia. Não que isso aconteça; somos uma loja C # onde isso não é suportado :)
John Rudy,
4
Eu mesmo uso um comentário / * FALL THROUGH * / no meu código quando apropriado. =]
strager
2
Se você usar o PC-Lint, deverá inserir / * - fallthrough * /, caso contrário, ele reclamará.
Steve Melnikoff
1
ainda assim, se você quiser deixar claro que o comportamento é intencional, você também pode if(condition > 1 && condition <10) {condition = 1}; switch(...salvar casos (para uma aparência clara) e todos podem ver que você realmente deseja que esses casos desencadeiem a mesma ação.
Mark
1
Esta codificação ainda é terrível. O primeiro caso deve chamar uma função, o segundo caso deve chamar a mesma função e, em seguida, chamar uma segunda função.
BlueRaja - Danny Pflughoeft
21

Você já ouviu falar do dispositivo de Duff ? Este é um ótimo exemplo de como usar switch fallthrough.

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

tzot
fonte
Aprenda algo novo todos os dias - obrigado! Eu sinto pelos pobres desenvolvedores que passam por tais contorções.
Justin R.,
Uau, isso é algo para envolver seu cérebro.
Fostah,
6
Observe o comentário citado de Duff naquele artigo: "Este código constitui algum tipo de argumento nesse debate, mas não tenho certeza se é a favor ou contra."
John Carter
O dispositivo de Duff é claramente maligno
Marjeta
21

O fall-through é realmente uma coisa útil, dependendo do que você está fazendo. Considere esta maneira simples e compreensível de 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.

Lucas omã
fonte
1
Acho esse tipo de fuga muito útil
Mitchel Sellers,
se 'fazer algo' for mais do que apenas um pouco do que a mudança, será uma bagunça do que se / outra coisa. Com if / else fica claro em todos os lugares qual variável é testada. Mesmo este pequeno exemplo não ficaria tão ruim com if / else em meus olhos
grenix
10

Pode ser muito útil algumas vezes, mas, em geral, nenhum erro é o comportamento desejado. O fall-through 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
}
Peter Mortensen
fonte
6

Adoraria uma sintaxe diferente para substitutos 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 sinalizadores, certo? Também não parece tão ruim; os casos poderiam (deveriam?) muito bem já fazer parte do seu enum.

Talvez este seja um bom caso (sem trocadilhos) para uma interface fluente usando métodos 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 seja provavelmente ainda menos legível: P

Erik van Brakel
fonte
1
Eu gosto muito da primeira sugestão. Isso lê muito bem e economiza algumas linhas de código. O segundo levou um minuto para envolver meu cérebro, mas faz sentido, mas parece destinado a ser uma bagunça.
Fostah
1
Sim, foi o que eu pensei também, foi apenas um peido mental aleatório que eu soltei, em um esforço para encontrar uma maneira diferente de alternar usando construções de linguagem existentes.
Erik van Brakel
Repassei essa pergunta e realmente gostei disso! Tive uma ideia sobre como melhorar a legibilidade disso, mas o SO não me deixa postar comentários de várias linhas :( Alguém deveria implementar isso como POC, parece divertido de implementar e usar (:
Victor Elias
5

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

Porém, acho que as desvantagens 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 pausa omitida foi intencional

Bom uso de um switch / case fall-through:

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

Baaaaad uso de um switch / case fall-through:

switch (x)
{
case 1:
    Some code
case 2:
    Some more code
case 3:
    Even more code
    break;
}

Isso pode ser reescrito usando construções if / else sem nenhuma perda em minha opinião.

Minha palavra final: fique longe de rótulos de caso que falham como no exemplo ruim , a menos que você esteja mantendo um código legado onde esse estilo é usado e bem compreendido.

Steffenj
fonte
3
Você pode reescrever a maioria das construções de linguagem usando if () e goto ... isso não justifica abandonar uma construção explícita.
Shog9,
2
Eu sei, mas as construções switch / case que usam explicitamente o "caso 1: algum caso de código 2: um pouco mais de caso de código 3: quebra de código final;" pode e deve ser reescrito usando if / else if (minha opinião).
steffenj,
1
Observe que a troca pode ser muitas vezes mais rápida do que uma lista de if-elses. O switch usa uma tabela de saltos ou, quando isso não for possível, seus saltos condicionais serão estruturados em uma árvore de decisão em vez de uma lista linear de saltos condicionais. As instruções If-else aninhadas bem estruturadas serão, na melhor das hipóteses, igualmente rápidas.
Jochem Kuijpers
4

É poderoso e perigoso. O maior problema com o fall-through é que não é explícito. Por exemplo, se você encontrar um código editado com frequência que tem um switch com falhas, como saber se isso é intencional e não um bug?

Onde quer que eu o use, asseguro-me de que esteja devidamente comentado:

switch($var) {
    case 'first':
        // Fall-through
    case 'second':
        i++;
        break;
 }
Dan Hulton
fonte
2
A intenção é óbvia se não houver código para o caso 'primeiro'. Se você codificar lá e também quiser executar o código para o caso 'segundo', o comentário é importante. Mas eu evitaria esse cenário de qualquer maneira.
Joel Coehoorn,
1
@Joel - Concordo totalmente. Em minha loja, algumas pessoas colocaram comentários negativos nesses casos, e acho que isso reduz a legibilidade. Se houver código lá, porém, coloque o comentário de fallthrough.
Fred Larson,
O que queremos dizer com "não explícito". É por isso que quebramos; Outras línguas fazem isso. É um problema apenas para pessoas que não aprenderam suas línguas de maneira adequada. C # exige isso para o caso padrão ffs, sem motivo para imho.
mckenzm
3

Usar a falha como no seu primeiro exemplo é claramente OK, e eu não consideraria uma falha real.

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

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

Wouter van Ooijen
fonte
2

Não gosto que minhas switchdeclarações falhem - são muito propensas a erros e difíceis de ler. A única exceção é quando várias caseinstruções fazem exatamente a mesma coisa.

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

Matt Dillard
fonte
1

Em alguns casos, usar falhas é um ato de preguiça por parte do programador - eles poderiam usar uma série de || instruções, por exemplo, mas em vez disso, use uma série de casos de switch 'pega-tudo'.

Dito isso, descobri que eles são especialmente úteis quando sei que eventualmente , vou precisar das opções de qualquer maneira (por exemplo, em uma resposta do menu), mas ainda não implementei todas as opções. Da mesma forma, se você estiver fazendo um fall-through para 'a' e 'A', acho substancialmente mais limpo usar o fall-through do switch do que uma instrução if composta.

Provavelmente é uma questão de estilo e de como os programadores pensam, mas geralmente não gosto de remover componentes de uma linguagem em nome de 'segurança' - é por isso que tento mais para C e suas variantes / descendentes do que, digamos, Java. Gosto de ser capaz de ficar brincando com dicas e coisas do gênero, mesmo quando não tenho "razão" para isso.

Warren
fonte
Estritamente falando. As instruções switch saltam por meio de um valor definido para vários locais de código específicos. Embora o compilador provavelmente detecte essas coisas, há um valor de velocidade e exatidão para uma instrução switch em uma série de instruções ou. Se p é 0 ou p é 1 ou p é 2. Na verdade, tive que avaliar se p era 0 ep era 1, em vez de pular para o local p = 2 no código. Que passou a ser idêntico a p0 e p1. Mas, eu nunca tive que verificá-los.
Tatarize de
1

O fall-through deve ser usado apenas quando é usado como uma tabela de salto para 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".

BCS
fonte