3 técnicas poderosas de codificação para remover condicionais bagunçados
Apresenta três técnicas poderosas de codificação para otimizar e simplificar estruturas condicionais complexas, melhorando a qualidade e a mantenibilidade do código.
No desenvolvimento de software, muitas vezes nos deparamos com a lógica de código que precisa lidar com vários cenários. Se não forem gerenciadas adequadamente, essas lógicas podem facilmente evoluir para longas cadeias de if-else ou declarações de switch maciças. Este artigo apresentará várias técnicas eficazes para otimizar essas estruturas, melhorando a qualidade e a mantenibilidade do código.
1. Programação defensiva: retorno antecipado
Digamos que estamos desenvolvendo um sistema de autenticação de usuário que precisa verificar vários status de usuário antes de permitir o acesso:
Este código tem problemas estruturais óbvios. Ele usa estruturas if-else profundamente aninhadas, tornando o código difícil de ler e manter. À medida que o número de verificações de condição aumenta, o nível de indentação do código se aprofunda, formando um chamado código em "forma de flecha". A lógica de tratamento de erros está espalhada por diferentes níveis de aninhamento, o que não é propício à gestão unificada. Mais importante, a lógica central do código - o caso em que o acesso é permitido - está enterrada profundamente em várias camadas de julgamentos condicionais, faltando em 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 de "retorno antecipado":
Adotando a estratégia de "retorno antecipado", conseguimos otimizar a estrutura original do código.
Este método traz várias melhorias:
- Reduz significativamente a complexidade de 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 consideravelmente a dificuldade de manutenção.
- Este método de otimização alcança 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 todo 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 faz com que o principal propósito do código seja imediatamente aparente, melhorando enormemente a expressividade e a compreensibilidade do código.
2. Método de tabela de pesquisa
Freqüentemente encontramos cenários onde diferentes resultados precisam ser retornados com base em diferentes entradas. Se não forem tratados adequadamente, essas lógicas podem facilmente evoluir para longas cadeias de if-else ou declarações de switch maciças. Por exemplo, em uma plataforma de comércio eletrônico, precisamos retornar descrições de status correspondentes com base em diferentes status de pedidos:
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 de switch ou os julgamentos if-else tornam-se longos. Além disso, neste cenário, se os usuários precisarem traduzir esses conteúdos de status para outros idiomas, seria necessário modificar o corpo da função ou adicionar novas funções, o que traria custos de manutenção significativos.
Neste caso, podemos usar o método de tabela de pesquisa para otimizar o código:
Primeiramente, usando um objeto Map para armazenar o relacionamento de mapeamento entre status e descrições, o código se torna mais conciso. Também facilitamos a movimentação das descrições de status para arquivos de configuração, proporcionando conveniência para internacionalização e atualizações dinâmicas. Quando novos status são adicionados, não precisamos modificar o código de lógica central; apenas precisamos 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 provedores de serviços ou módulos funcionais. Podemos considerar usar a programação orientada a interfaces na fase de design de 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 desenvolvendo um sistema de tradução multilíngue que precisa suportar diferentes provedores 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 se tornarão muito difíceis:
Esta implementação usa uma estrutura simples e rudimentar de if-else para selecionar provedores de tradução, tornando o código difícil de manter e expandir. Ao adicionar novos provedores de tradução no futuro, o código existente precisa ser modificado e, à medida que mais provedores de tradução precisam ser suportados, o código se tornará inchado e difícil de manter. Ao mesmo tempo, este método complexo também é difícil de testar unidade, pois não é fácil simular diferentes provedores de tradução.
Para resolver esses problemas, podemos usar a programação orientada a interfaces para otimizar o código. A programação orientada a interfaces é uma forma importante de implementar polimorfismo, permitindo que diferentes objetos respondam de forma diferente à mesma mensagem.
Processo de implementação:
- Definir a interface de estratégia de tradução:
- Implementar esta interface para cada provedor de tradução:
- Refatorar a classe TranslationService, passando a estratégia como um parâmetro:
- Usar o código otimizado:
Definindo a interface TranslationStrategy
e introduzindo 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 provedores de tradução se torna simples, basta criar uma nova classe de estratégia e implementar a interface.
- O código do cliente pode escolher de forma flexível a estratégia a ser usada 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.
- Evitar manter estado em
TranslationService
torna o serviço mais sem estado e seguro para threads.
Conclusão
Otimizar estruturas de declarações condicionais é uma maneira importante de melhorar a qualidade do código. Os três métodos apresentados neste artigo—programação defensiva, método de tabela de pesquisa 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 de código.
- O método de tabela de pesquisa é 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 a escalabilidade do código.
No desenvolvimento real, muitas vezes precisamos escolher métodos apropriados com base em situações específicas, e às vezes até precisamos aplicar de forma abrangente múltiplas técnicas. O importante é equilibrar a simplicidade, legibilidade e mantenibilidade do código, escolhendo a solução que melhor se adapta ao problema atual.
Lembre-se, a super otimização pode levar a um código excessivamente complexo. Manter o código simples e legível é sempre o princípio fundamental. 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 equipe.