Supondo que eu tenho esse pseudo-código:
bool conditionA = executeStepA();
if (conditionA){
bool conditionB = executeStepB();
if (conditionB){
bool conditionC = executeStepC();
if (conditionC){
...
}
}
}
executeThisFunctionInAnyCase();
As funções executeStepX
devem ser executadas se e somente se as anteriores tiverem êxito. De qualquer forma, a executeThisFunctionInAnyCase
função deve ser chamada no final. Sou iniciante em programação, desculpe-me pela pergunta muito básica: existe uma maneira (em C / C ++, por exemplo) de evitar tanto tempo?if
cadeia produzindo esse tipo de "pirâmide de código", às custas da legibilidade do código ?
Eu sei que se pudéssemos pular a executeThisFunctionInAnyCase
chamada de função, o código poderia ser simplificado como:
bool conditionA = executeStepA();
if (!conditionA) return;
bool conditionB = executeStepB();
if (!conditionB) return;
bool conditionC = executeStepC();
if (!conditionC) return;
Mas a restrição é a executeThisFunctionInAnyCase
chamada de função. A break
declaração poderia ser usada de alguma maneira?
c++
c
if-statement
control-flow
ABCplus
fonte
fonte
false
ser considerada semelhante a uma situação excepcional?false
retorno pode ser bastante normal.Respostas:
Você pode usar um
&&
(AND lógico):isso atenderá a ambos os seus requisitos:
executeStep<X>()
deve avaliar apenas se o anterior foi bem-sucedido (isso é chamado de avaliação de curto-circuito )executeThisFunctionInAnyCase()
será executado em qualquer casofonte
&&
e,||
portanto, não há como você querer uni-las em uma únicaif
instrução sem estragar a legibilidade. E nem sempre é fácil mover essas condições para funções externas, porque elas podem depender de muitas variáveis locais previamente calculadas, o que criaria uma terrível bagunça se você tentar passar cada uma delas como argumento individual.Basta usar uma função adicional para que sua segunda versão funcione:
Usar ifs profundamente aninhados (sua primeira variante) ou o desejo de interromper "parte de uma função" geralmente significa que você precisa de uma função extra.
fonte
foo
funciona através de uma sequência de condições e ações relacionadas. A funçãobar
é claramente separada das decisões. Se vimos os detalhes das condições e ações, pode ser quefoo
ainda esteja fazendo muito, mas por enquanto essa é uma boa solução.bar
você precisarem passá-las manualmentefoo
como parâmetros. Se for esse o caso e sefoo
for chamado apenas uma vez, eu iria errar no uso da versão goto para evitar definir duas funções fortemente acopladas que acabariam não sendo muito reutilizáveis.if (!executeStepA() || !executeStepB() || !executeStepC()) return
Os programadores da velha escola C usam
goto
neste caso. É o único usogoto
incentivado pelo guia de estilo do Linux, chamado de saída de função centralizada:Algumas pessoas trabalham em torno do uso
goto
envolvendo o corpo em um loop e quebrando-o, mas efetivamente as duas abordagens fazem a mesma coisa. Agoto
abordagem é melhor se você precisar de alguma outra limpeza somente seexecuteStepA()
for bem-sucedida:Com a abordagem de loop, você acabaria com dois níveis de loops nesse caso.
fonte
goto
e imediatamente pensam "Este é um código terrível", mas ele tem seus usos válidos.goto
e nem preciso pensar para ver que esse é um código terrível. Uma vez eu tive que manter isso, é muito desagradável. O OP sugere uma alternativa razoável da linguagem C no final da pergunta, e eu a incluí na minha resposta.throw
é, de muitas maneiras, pior do quegoto
porque, comthrow
isso, nem está claro no contexto local em que você vai acabar! Use as mesmas considerações de design para o fluxo de controle no estilo goto que usaria para exceções.Esta é uma situação comum e há muitas maneiras comuns de lidar com isso. Aqui está minha tentativa de resposta canônica. Por favor, comente se eu perdi alguma coisa e vou manter esta postagem atualizada.
Esta é uma flecha
O que você está discutindo é conhecido como anti-padrão de seta . É chamada de seta porque a cadeia de ifs aninhados forma blocos de código que se expandem cada vez mais para a direita e depois para a esquerda, formando uma seta visual que "aponta" para o lado direito do painel do editor de código.
Achate a flecha com a guarda
Algumas maneiras comuns de evitar o Arrow são discutidas aqui . O método mais comum é usar um padrão de guarda , no qual o código lida primeiro com os fluxos de exceção e depois lida com o fluxo básico, por exemplo, em vez de
... você usaria ....
Quando houver uma longa série de guardas, isso achatará o código consideravelmente, pois todos os guardas aparecem totalmente à esquerda e seus ifs não estão aninhados. Além disso, você está visualizando o emparelhamento visual da condição lógica com o erro associado, o que torna muito mais fácil saber o que está acontecendo:
Seta:
Guarda:
É objetiva e quantificável mais fácil de ler, porque
Como adicionar código comum no final
O problema com o padrão de guarda é que ele se baseia no que é chamado de "retorno oportunista" ou "saída oportunista". Em outras palavras, ele quebra o padrão de que toda e qualquer função deve ter exatamente um ponto de saída. Este é um problema por dois motivos:
Abaixo, forneci algumas opções para contornar essa limitação, usando os recursos de idioma ou evitando o problema completamente.
Opção 1. Você não pode fazer isso: use
finally
Infelizmente, como desenvolvedor de c ++, você não pode fazer isso. Mas esta é a resposta número um para idiomas que contêm finalmente uma palavra-chave, pois é exatamente para isso que serve.
Opção 2. Evite o problema: reestruture suas funções
Você pode evitar o problema dividindo o código em duas funções. Esta solução tem o benefício de trabalhar para qualquer idioma e, além disso, pode reduzir a complexidade ciclomática , que é uma maneira comprovada de reduzir a taxa de defeitos e melhora a especificidade de qualquer teste de unidade automatizado.
Aqui está um exemplo:
Opção 3. Truque de idiomas: use um loop falso
Outro truque comum que vejo é usar while (true) e break, como mostrado nas outras respostas.
Embora isso seja menos "honesto" do que usado
goto
, é menos propenso a ser confuso ao refatorar, pois marca claramente os limites do escopo da lógica. Um codificador ingênuo que corta e cola seus rótulos ou suasgoto
declarações pode causar grandes problemas! (E, francamente, o padrão é tão comum agora, acho que comunica claramente a intenção e, portanto, não é "desonesto").Existem outras variantes dessas opções. Por exemplo, um poderia usar em
switch
vez dewhile
. Qualquer construção de idioma com umabreak
palavra - chave provavelmente funcionaria.Opção 4. Aproveite o ciclo de vida do objeto
Uma outra abordagem aproveita o ciclo de vida do objeto. Use um objeto de contexto para carregar seus parâmetros (algo que nosso exemplo ingênuo desconfia) e descartá-lo quando terminar.
Nota: Certifique-se de entender o ciclo de vida do objeto de seu idioma preferido. Você precisa de algum tipo de coleta de lixo determinística para que isso funcione, ou seja, você precisa saber quando o destruidor será chamado. Em alguns idiomas, você precisará usar em
Dispose
vez de um destruidor.Opção 4.1. Aproveite o ciclo de vida do objeto (padrão de wrapper)
Se você usar uma abordagem orientada a objetos, pode fazê-lo corretamente. Esta opção usa uma classe para "agrupar" os recursos que requerem limpeza, bem como suas outras operações.
Novamente, certifique-se de entender o seu ciclo de vida do objeto.
Opção 5. Truque de idiomas: use avaliação de curto-circuito
Outra técnica é tirar proveito da avaliação de curto-circuito .
Esta solução tira proveito da maneira como o operador && funciona. Quando o lado esquerdo de && é avaliado como falso, o lado direito nunca é avaliado.
Esse truque é mais útil quando o código compacto é necessário e quando o código provavelmente não recebe muita manutenção, por exemplo, você está implementando um algoritmo conhecido. Para uma codificação mais geral, a estrutura desse código é muito frágil; mesmo uma pequena alteração na lógica pode desencadear uma reescrita total.
fonte
Apenas faça
É simples assim.
Devido a três edições em que cada uma mudou fundamentalmente a pergunta (quatro se uma conta a revisão de volta à versão 1), incluo o exemplo de código ao qual estou respondendo:
fonte
&&
lista muito mais clara. Geralmente eu quebro as condições para separar linhas e adiciono um final// explanation
a cada .... No final, é muito menos código para se olhar, e depois que você entende como&&
funciona, não há esforço mental em andamento. Minha impressão é que a maioria dos programadores profissionais de C ++ estaria familiarizada com isso, mas como você diz em diferentes setores / projetos, o foco e a experiência são diferentes.Na verdade, existe uma maneira de adiar ações no C ++: fazendo uso do destruidor de um objeto.
Supondo que você tenha acesso ao C ++ 11:
E então usando esse utilitário:
fonte
executeThisFunctionInAnyCase();
será executado mesmo sefoo();
gerar uma exceção. Ao escrever código com exceção de segurança, é uma boa prática colocar todas essas funções de limpeza em um destruidor.foo()
. E se você fizer isso, pegue. Problema resolvido. Corrija os erros, corrigindo-os, não escrevendo soluções alternativas.Defer
classe é um pequeno pedaço de código reutilizável que permite fazer qualquer limpeza de final de bloco, de uma maneira segura e excepcional. é mais conhecido como um protetor de escopo . sim, qualquer uso de uma proteção de escopo pode ser expresso de outras maneiras mais manuais, assim como qualquerfor
loop pode ser expresso como um bloco e umwhile
loop, que por sua vez podem ser expressos comif
egoto
, que podem ser expressos em linguagem assembly, se você desejar, ou para aqueles que são verdadeiros mestres, alterando os bits da memória através de raios cósmicos dirigidos pelo efeito borboleta de grunhidos e cantos curtos especiais. mas por que fazer isso.Existe uma boa técnica que não precisa de uma função adicional do wrapper com as instruções de retorno (o método prescrito pelo Itjax). Utiliza um
while(0)
pseudo-loop do. Oswhile (0)
garante que ele não é realmente um laço, mas executados apenas uma vez. No entanto, a sintaxe do loop permite o uso da instrução break.fonte
inline
. De qualquer forma, é uma boa técnica para saber, pois ajuda mais do que esse problema.Você também pode fazer isso:
Dessa forma, você tem um tamanho de crescimento linear mínimo, uma linha por chamada e é facilmente sustentável.
EDIT : (Obrigado @Unda) Não é um grande fã porque você perde visibilidade IMO:
fonte
Isso funcionaria? Eu acho que isso é equivalente ao seu código.
fonte
ok
ao usar a mesma variável assim.Supondo que o código desejado seja o que eu vejo atualmente:
Eu diria que a abordagem correta, na medida em que é mais simples de ler e mais fácil de manter, teria menos níveis de indentação, que é (atualmente) o objetivo declarado da questão.
Isso evita qualquer necessidade de
goto
s, exceções,while
loops fictícios ou outras construções difíceis e simplesmente continua o trabalho simples em mãos.fonte
return
ebreak
pular para fora do loop sem a necessidade de introduzir variáveis extras "flag". Nesse caso, o uso de um goto seria igualmente inocente - lembre-se de que você está trocando uma complexidade extra de goto por uma complexidade variável mutável extra.newbie
, fornece uma solução mais limpa, sem inconvenientes. Observo que também não depende desteps
ter a mesma assinatura ou mesmo de ser funções, em vez de blocos. Eu posso ver isso sendo usado como um refator de primeira passagem, mesmo onde uma abordagem mais sofisticada é válida.Talvez não seja a melhor solução, mas você pode colocar suas instruções em um
do .. while (0)
loop e usarbreak
instruções em vez dereturn
.fonte
do .. while (0)
para definições de macro também está abusando de loops, mas é considerado OK.Você pode colocar todas as
if
condições, formatadas como você deseja, em uma função própria, para o retorno executar aexecuteThisFunctionInAnyCase()
função.No exemplo base do OP, o teste e a execução da condição podem ser divididos como tal;
E então chamado como tal;
Se lambdas C ++ 11 estiverem disponíveis (não havia tag C ++ 11 no OP, mas elas ainda podem ser uma opção), podemos renunciar à função separada e agrupá-la em uma lambda.
fonte
Se você não gosta
goto
e não gosta dedo { } while (0);
loops e gosta de usar C ++, também pode usar um lambda temporário para ter o mesmo efeito.fonte
if
você não gosta Goto&&
você não gosta do {} while (0)&&
Você gosta C ++ ... Desculpe, não pude resistir, mas essa última condição falha porque a questão é marcado c , bem como c ++As cadeias de IF / ELSE no seu código não são o problema de idioma, mas o design do seu programa. Se você é capaz de re-fatorar ou reescrever seu programa, gostaria de sugerir que você procure em Design Patterns ( http://sourcemaking.com/design_patterns ) para encontrar uma solução melhor.
Normalmente, quando você vê muitos FIs e outros itens no seu código, é uma oportunidade de implementar o Padrão de Design da Estratégia ( http://sourcemaking.com/design_patterns/strategy/c-sharp-dot-net ) ou talvez uma combinação de outros padrões.
Tenho certeza de que existem alternativas para escrever uma longa lista de if / else, mas duvido que elas mudem tudo, exceto que a cadeia ficará bonita para você (no entanto, a beleza está nos olhos de quem vê ainda se aplica ao código também:-) ) . Você deve se preocupar com coisas como (em 6 meses quando eu tiver uma nova condição e não me lembro de nada sobre esse código, poderei adicioná-lo facilmente? Ou, se a cadeia mudar, com que rapidez e sem erros serei implementado)
fonte
Você acabou de fazer isso ..
99 vezes de 100, esta é a única maneira de fazê-lo.
Nunca, jamais, tente fazer algo "complicado" no código do computador.
By the way, eu tenho certeza que o seguinte é a solução real que você tinha em mente ...
A instrução continue é crítica na programação algorítmica. (Tanto quanto, a instrução goto é crítica na programação algorítmica.)
Em muitas linguagens de programação, você pode fazer isso:
(Observe antes de tudo: blocos simples como esse exemplo são uma parte crítica e importante da escrita de códigos bonitos, principalmente se você estiver lidando com programação "algorítmica".)
Novamente, é exatamente isso que você tinha em sua cabeça, certo? E essa é a maneira bonita de escrever, para que você tenha bons instintos.
No entanto, tragicamente, na versão atual do objetivo-c (além disso - não conheço o Swift, desculpe), há um recurso que pode ser subido onde ele verifica se o bloco envolvente é um loop.
Aqui está como você contorna isso ...
Então não esqueça disso ..
faça {} enquanto (falso);
significa apenas "faça este bloco uma vez".
ou seja, não há absolutamente nenhuma diferença entre escrever
do{}while(false);
e simplesmente escrever{}
.Isso agora funciona perfeitamente como você queria ... aqui está a saída ...
Então, é possível que seja assim que você vê o algoritmo em sua cabeça. Você deve sempre tentar escrever o que está na sua cabeça. (Particularmente se você não estiver sóbrio, porque é aí que a bonita sai! :))
Em projetos "algorítmicos", onde isso acontece muito, no objetivo-c, sempre temos uma macro como ...
... então você pode fazer isso ...
Existem dois pontos:
a, mesmo que seja estúpido que o objetivo-c verifique o tipo de bloco em que uma instrução continue está, é preocupante "lutar contra isso". Portanto, é uma decisão difícil.
b, existe a pergunta que você deve recuar, no exemplo, nesse bloco? Eu perco o sono com perguntas assim, então não posso aconselhar.
Espero que ajude.
fonte
if
, você também pode usar nomes de função mais descritivos e colocar os comentários nas funções.Faça com que suas funções de execução gerem uma exceção se falharem em vez de retornar falso. Em seguida, seu código de chamada pode ficar assim:
É claro que estou assumindo que, no seu exemplo original, a etapa de execução retornaria falsa apenas no caso de um erro ocorrer dentro da etapa?
fonte
Já existem muitas boas respostas, mas a maioria delas parece compensar parte (reconhecidamente muito pouco) da flexibilidade. Uma abordagem comum que não exige essa troca é a adição de uma variável de status / manutenção . O preço é, obviamente, um valor extra para acompanhar:
fonte
ok &= conditionX;
e não simplesmenteok = conditionX;
?No C ++ (a pergunta está marcada como C e C ++), se você não pode alterar as funções para usar exceções, ainda pode usar o mecanismo de exceção se escrever uma pequena função auxiliar como
Em seguida, seu código pode ler da seguinte maneira:
Se você gosta de sintaxe sofisticada, pode fazê-lo funcionar por meio de conversão explícita:
Então você pode escrever seu código como
fonte
Se seu código é tão simples quanto o seu exemplo e seu idioma suporta avaliações de curto-circuito, você pode tentar o seguinte:
Se você estiver passando argumentos para suas funções e recuperando outros resultados para que seu código não possa ser gravado da maneira anterior, muitas das outras respostas seriam mais adequadas ao problema.
fonte
Para C ++ 11 e além, uma boa abordagem pode ser a implementação de um sistema de saída de escopo semelhante ao mecanismo de escopo (saída) de D.
Uma maneira possível de implementá-lo é usar C ++ 11 lambdas e algumas macros auxiliares:
Isso permitirá que você retorne mais cedo da função e garanta que qualquer código de limpeza definido seja sempre executado na saída do escopo:
As macros são realmente apenas decoração.
MakeScopeExit()
pode ser usado diretamente.fonte
[=]
geralmente está errado para um lambda com escopo definido.[&]
: é seguro e minimamente surpreendente. Captura por valor somente quando o lambda (ou cópias) poderia sobreviver mais tempo do que o escopo no momento da declaração ...Por que ninguém está dando a solução mais simples? : D
Se todas as suas funções tiverem a mesma assinatura, você poderá fazê-lo desta maneira (no idioma C):
Para uma solução C ++ limpa, você deve criar uma classe de interface que contenha um método execute e agrupe suas etapas em objetos.
Em seguida, a solução acima terá a seguinte aparência:
fonte
Supondo que você não precise de variáveis de condição individuais, inverter os testes e usar o else-falthrough como o caminho "ok" permitiria obter um conjunto mais vertical de instruções if / else:
Omitir a variável com falha torna o código um pouco IMO obscuro.
Declarar as variáveis internas é bom, não se preocupe com = vs ==.
Isso é obscuro, mas compacto:
fonte
Parece uma máquina de estado, o que é útil porque você pode implementá-la facilmente com um padrão de estado .
Em Java, seria algo parecido com isto:
Uma implementação funcionaria da seguinte maneira:
E assim por diante. Em seguida, você pode substituir a condição big if por:
fonte
Como Rommik mencionou, você pode aplicar um padrão de design para isso, mas eu usaria o padrão Decorator em vez de Strategy, pois você deseja encadear chamadas. Se o código for simples, eu usaria uma das respostas bem estruturadas para evitar o aninhamento. No entanto, se for complexo ou exigir encadeamento dinâmico, o padrão Decorator é uma boa opção. Aqui está um diagrama de classes yUML :
Aqui está um exemplo de programa LinqPad C #:
O melhor livro para ler sobre padrões de design, IMHO, é Head First Design Patterns .
fonte
Várias respostas sugeriram um padrão que eu vi e usei muitas vezes, especialmente em programação de rede. Nas pilhas de rede, geralmente há uma longa sequência de solicitações, qualquer uma das quais pode falhar e interromper o processo.
O padrão comum era usar
do { } while (false);
Eu usei uma macro para
while(false)
fazê-lodo { } once;
O padrão comum era:Esse padrão era relativamente fácil de ler e permitia o uso de objetos que destruíam adequadamente e também evitavam vários retornos, facilitando um pouco a etapa e a depuração.
fonte
Para melhorar a resposta C ++ 11 de Mathieu e evitar o custo de tempo de execução incorrido com o uso de
std::function
, sugiro usar o seguinteEssa classe de modelo simples aceita qualquer functor que possa ser chamado sem nenhum parâmetro e o faz sem alocações dinâmicas de memória e, portanto, está melhor em conformidade com o objetivo de abstração do C ++ sem sobrecarga desnecessária. O modelo de função adicional existe para simplificar o uso pela dedução de parâmetro do modelo (que não está disponível para os parâmetros do modelo de classe)
Exemplo de uso:
Assim como a resposta de Mathieu, esta solução é totalmente segura contra exceções e
executeThisFunctionInAnyCase
será chamada em todos os casos. DeveriaexecuteThisFunctionInAnyCase
lançar, os destruidores são implicitamente marcadosnoexcept
e, portanto, uma chamada parastd::terminate
seria emitida em vez de causar uma exceção a ser lançada durante o desenrolamento da pilha.fonte
functor
nodeferred
construtor, sem precisar forçar amove
.Parece que você deseja fazer todas as suas ligações em um único bloco. Como outros propuseram, você deve usar um
while
loop e deixar de usarbreak
ou uma nova função com a qual possa sairreturn
(pode ser mais limpo).Eu pessoalmente banir
goto
, mesmo para a saída da função. Eles são mais difíceis de detectar ao depurar.Uma alternativa elegante que deve funcionar para o seu fluxo de trabalho é criar uma matriz de funções e iterar nessa.
fonte
Como você também tem um bloco de [...] código entre execuções, acho que você tem alocação de memória ou inicialização de objeto. Dessa forma, você precisa se preocupar em limpar tudo o que você já inicializou na saída e também limpá-lo se encontrar algum problema e qualquer uma das funções retornar falso.
Nesse caso, o melhor que eu tive na minha experiência (quando trabalhei com o CryptoAPI) foi criar pequenas classes, no construtor você inicializa seus dados, no destruidor você o inicializa. Cada próxima classe de função deve ser filha da classe de função anterior. Se algo der errado - lance uma exceção.
E então, no seu código, você só precisa chamar:
Eu acho que é a melhor solução se cada chamada do ConditionX inicializar algo, alocar memória e etc. Melhor garantir que tudo será limpo.
fonte
uma maneira interessante é trabalhar com exceções.
Se você escrever esse código, está indo de alguma forma na direção errada. Não considero "o problema" ter esse código, mas ter uma "arquitetura" tão confusa.
Dica: discuta esses casos com um desenvolvedor experiente no qual você confia ;-)
fonte
Outra abordagem -
do - while
loop, apesar de ter sido mencionado antes, não havia nenhum exemplo disso que mostrasse como ele se parece:(Bem, já existe uma resposta com
while
loop, mas odo - while
loop não verifica redundantemente se é verdade (no início), mas no final xD (isso pode ser ignorado).fonte