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.
def transfere_dinheiro(origem, destino, quantia):
if quantia > 0:
if origem.saldo >= quantia:
origem.saldo = origem.saldo - quantia
destino.saldo = destino.saldo + quantia
notificação_de_sucesso(origem, quantia)
return True
else:
notificação_de_saldo_insuficiente(origem)
return False
else:
return False
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:
def transfere_dinheiro(origem, destino, quantia):
if quantia <= 0:
return False
if origem.saldo < quantia:
notificação_de_saldo_insuficiente(origem)
return False
origem.saldo = origem.saldo - quantia
destino.saldo = destino.saldo + quantia
notify_de_sucesso(origem, quantia)
return True
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:
def transfere_dinheiro(origem, destino, quantia):
origem.saldo = origem.saldo - quantia
destino.saldo = destino.saldo + quantia
notify_de_sucesso(origem, quantia)
return True
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.