Caminho plano para o sucesso
Post de autoria de Felipe Ximenes, publicado originalmente em inglês no blog da Vinta, com revisão técnica de Cuducos, Carriço e Anderson. Tradução livre de Eduardo Cuducos.
Para pythonistas, pode ser também “por que plano é melhor que agrupado”
Se você quer escrever programas de computador que sejam claros e fáceis de entender, garanta que eles tenham um caminho plano para o sucesso.
Um “caminho plano para o sucesso” significa muitas coisas. Primeiro, significa que cada função, método ou procedimento dever ter um único propósito. Uma das formas de identificar se você está fazendo isso corretamente é tentar dar um nome para cada bloco de código. Se você não conseguir chegar em um nome simples, isso já é um cheirinho de que pode ter coisa errada.
A segunda coisa que isso significa é que o caminho de sucesso de uma função deve estar claro nos seus comandos mais planos.
Uma note sobre o que plano quer dizer: Um comando agrupado é um bloco de código que está em uma cláusula que posiciona, visualmente, o início desse código de uma forma que ele fique mais distante da margem esquerda do editor de texto (dado que você siga as boas práticas de identação). if/else e try/catch são exemplos disso. Código plano é o oposto de código agrupado, é código que fica próximo da margem esquerda do editor.
O caminho de sucesso pode significar coisas diferentes em partes diferentes do código: algumas vezes é o comportamento padrão de uma função, em outras, a coisa mais provável que pode acontecer, ou simplesmente o caminho que não diverge do propósito principal do código. Por exemplo, quando você escreve uma função divide(x, y) que recebe argumentos externos, por mais que o propósito do código seja executar x / y, você vai querer assegurar que y não é 0 antes de fazer o cálculo. Verificar o valor de entrada é fundamental para o funcionamento correto da função, mas não é o propósito principal de divide. Por definição, você não vai conseguir ter um caminho plano para o sucesso a não ser que exista apenas um propósito principal para a função. Uma coisa depende da outra.
Vamos ver como isso funciona na prática; aqui temos uma função que transfere dinheiro de uma pessoa para outra, retorna true em caso de sucesso, ou false se ela falhar.
Isso está uma bagunça. Em uma olhada rápida, não é possível entender o que essa função faz. Isso acontece por causa de algumas coisas:
- Blocos if/else e agrupamentos dificultam a identificação do fluxo principal, do objetivo que esse bloco de código tenta alcançar
- Ao menos que você leia tudo e compreenda o que a função faz, não tem como saber quais são os valores retornados para casos de sucesso ou falha
Agora, vamos reescrever:
Repare que, apesar de ter uma aparência mais clara, o código reescrito tem exatamente a mesma complexidade ciclomática da versão anterior. Também vale mencionar que a medida da complexidade ciclomática é um conceito matemático preciso que pode indicar que seu código precisa ser reescrito; por outro lado, um caminho plano está relacionado à semântica do código e é, portanto, um critério subjetivo.
A mudança principal entre a primeira e a segunda versão do código é que se você ler a segunda ignorando todo código agrupado, você fica com o fluxo principal do programa:
Esse é o caminho de sucesso. Quando alguém pega um código novo para ler, é natural que a pessoa primeiro tente entender os blocos de código mais planos para, depois, inspecionar os trechos agrupados — que normalmente esperamos que sejam digressões do fluxo principal dos dados (casos especiais, ou fluxos de detecção de erros). Substituir if/else por cláusulas de proteção é, geralmente, uma das melhores formas de destacar o caminho de sucesso. Mostramos em outro artigo como combinar cláusulas de proteção e decoradores em alguns casos de uso interessantes.
Não conseguir determinar o caminho plano para o sucesso é um sinal que seu código está fazendo muita coisa, e talvez seja uma boa ideia quebrá-lo em múltiplas funções.
Discussion in the ATmosphere