3 técnicas de programação poderosas para remover condicionais confusos
Introduz três técnicas de programação poderosas para otimizar e simplificar estruturas condicionais complexas, melhorando a qualidade e a manutenção do código.
No desenvolvimento de software, muitas vezes encontramos lógica de código que precisa lidar com vários cenários. Se não for gerenciada adequadamente, essa lógica pode facilmente evoluir para cadeias longas de if-else ou enormes declarações switch. Este artigo irá apresentar várias técnicas eficazes para otimizar essas estruturas, melhorando a qualidade e a manutenção do código.
1. Programação defensiva: retorno antecipado
Vamos imaginar que estamos a desenvolver um sistema de autenticação de utilizadores que precisa verificar vários estados do utilizador antes de permitir o acesso:
Este código tem problemas estruturais óbvios. Ele usa estruturas if-else aninhadas, tornando o código difícil de ler e manter. À medida que o número de verificações de condições aumenta, o nível de indentação do código aprofunda-se, formando um código em forma de "seta". A lógica de tratamento de erros está dispersa em vários níveis de aninhamento, o que não é propício para a gestão unificada. Mais importante ainda, a lógica central do código—o caso em que o acesso é permitido—está enterrada em camadas múltiplas de julgamentos condicionais, carecendo de intuitividade. Este estilo de codificação não só reduz a legibilidade do código, mas também aumenta o risco de erros e torna a expansão do código difícil.
Podemos otimizar este código usando a abordagem "retorno antecipado":
Ao adotar a estratégia de "retorno antecipado", conseguimos otimizar com sucesso a estrutura original do código.
Este método traz várias melhorias:
- Reduz significativamente a complexidade do aninhamento do código. Cada verificação de condição é tratada de forma independente, tornando a lógica geral mais clara e compreensível. Esta estrutura achatada não só melhora a legibilidade do código, mas também reduz muito a dificuldade de manutenção.
- Este método de otimização alcança uma gestão centralizada da lógica de tratamento de erros. Ao retornar resultados imediatamente após cada verificação de condição, evitamos a execução desnecessária de código enquanto centralizamos o tratamento de vários cenários de erro, tornando o processo de tratamento de erros mais organizado.
- A lógica central do código—as condições para permitir o acesso—torna-se mais proeminente. Esta estrutura torna o objetivo principal do código imediatamente evidente, melhorando muito a expressividade e a compreensibilidade do código.
2. Método da tabela de consulta
Muitas vezes, encontramos cenários onde diferentes resultados precisam ser retornados com base em diferentes entradas. Se não forem adequadamente tratados, esta lógica pode facilmente evoluir para longas cadeias de if-else ou enormes declarações switch. Por exemplo, numa plataforma de e-commerce, precisamos devolver as descrições correspondentes de status com base em diferentes estados de pedido:
Este é um cenário típico de retorno de diferentes resultados com base em diferentes casos. À medida que o número de casos aumenta, a declaração switch ou os julgamentos if-else tornam-se longos. Além disso, neste cenário, se os utilizadores precisarem traduzir esses conteúdos de status para outras línguas, seria necessário modificar o corpo da função ou adicionar novas funções, o que acarretaria custos de manutenção significativos.
Neste caso, podemos usar o método da tabela de consulta para otimizar o código:
Primeiro, ao usar um objeto Map para armazenar a relação de mapeamento entre estados e descrições, o código torna-se mais conciso. Também facilitamos a movimentação das descrições de status para arquivos de configuração, proporcionando conveniência para a internacionalização e atualizações dinâmicas. Quando novos estados são adicionados, não precisamos modificar o código de lógica central; basta adicionar pares chave-valor correspondentes na configuração.
3. Programação orientada a interfaces
Ao desenvolver grandes sistemas de software, muitas vezes precisamos suportar múltiplos fornecedores de serviços ou módulos funcionais. Podemos considerar o uso de programação orientada a interfaces na fase de design do software para facilitar expansões subsequentes, eliminando assim a complexidade de múltiplos julgamentos condicionais trazida pela codificação rígida em sistemas complexos.
Suponha que estamos a desenvolver um sistema de tradução multilíngue que precisa suportar diferentes fornecedores de serviços de tradução. Se não considerarmos a programação orientada a interfaces desde a fase de design, as expansões subsequentes tornar-se-ão muito difíceis:
Esta implementação usa uma estrutura if-else simples e rude para selecionar fornecedores de tradução, tornando o código difícil de manter e expandir. Ao adicionar novos fornecedores de tradução no futuro, é necessário modificar o código existente, e à medida que mais fornecedores de tradução precisam ser suportados, o código ficará inchado e difícil de manter. Ao mesmo tempo, este método complexo também é difícil de testar por unidade porque não é fácil simular diferentes fornecedores de tradução.
Para resolver estes problemas, podemos usar a programação orientada a interfaces para otimizar o código. A programação orientada a interfaces é uma maneira importante de implementar o polimorfismo, permitindo que objetos diferentes respondam de forma diferente à mesma mensagem.
Processo de implementação:
- Definir a interface da estratégia de tradução:
- Implementar esta interface para cada fornecedor de tradução:
- Refatorar a classe TranslationService, passando a estratégia como um parâmetro:
- Usar o código otimizado:
Ao definir a interface TranslationStrategy
e introduzir a programação orientada a interfaces, obtivemos os seguintes benefícios:
TranslationService
pode usar diferentes estratégias de tradução para cada chamada.- Adicionar novos fornecedores de tradução torna-se simples, basta criar uma nova classe de estratégia e implementar a interface.
- O código cliente pode escolher flexivelmente a estratégia a usar para cada tradução sem modificar a lógica central de
TranslationService
. - Cada estratégia de tradução pode ser testada de forma independente, melhorando a testabilidade do código.
- Evitando manter estado em
TranslationService
, torna o serviço mais sem estado e seguro para threads.
Conclusão
Otimizar estruturas de declarações condicionais é um meio importante para melhorar a qualidade do código. Os três métodos introduzidos neste artigo—programação defensiva, método da tabela de consulta e programação orientada a interfaces (combinada com polimorfismo)—cada um tem seus cenários aplicáveis:
- A programação defensiva é adequada para lidar com múltiplas verificações de condições independentes e pode reduzir efetivamente o aninhamento do código.
- O método da tabela de consulta é adequado para lidar com requisitos que reagem de forma diferente a diferentes casos, tornando o código mais conciso e fácil de manter.
- A programação orientada a interfaces combinada com polimorfismo é adequada para construir sistemas complexos mas flexíveis, melhorando a flexibilidade e escalabilidade do código.
No desenvolvimento atual, muitas vezes precisamos escolher métodos apropriados com base em situações específicas, e às vezes até mesmo precisamos aplicar múltiplas técnicas de forma abrangente. O importante é equilibrar a simplicidade, legibilidade e manutenção do código, escolhendo a solução que melhor se adapta ao problema atual.
Lembre-se, uma otimização excessiva pode levar a código excessivamente complexo. Manter o código simples e legível é sempre o princípio principal. Ao aplicar essas técnicas, escolhas sábias devem ser feitas com base nas necessidades específicas do projeto e no nível técnico da equipa.