Programe defensivamente com asserções

O trânsito é um dos ambientes em que o ser humano reconhece que precisa de ajuda pra que funcione direito pra todo mundo. Digo aqui “reconhece” querendo dizer na verdade o contrário: todo mundo acha que é o único bom motorista no mundo, que os outros é que estão querendo ferrá-lo furando sinal fechado, correndo que nem loucos e mudando de pista sem sinalizar, bando egoístas e mal-educados… Enfim, por causa disso a gente aprende a dirigir defensivamente, isto é, se prevenir dos problemas antes que eles aconteçam, evitando situações que coloquem em risco você ou os demais. 1

Em programação não é diferente. Programadores são humanos, cometem muitos erros, e em geral têm dificuldade em reconhecer que seu código não está legal, colocam a culpa no código dos outros, na linguagem, na biblioteca utilizada, no compilador e até no sistema operacional — não obstante o fato de existirem muitos outros programas usando as mesmas coisas e sem apresentar o problema do programa deles.

Assim como a direção defensiva envolve não só seguir as regras de trânsito mas também se preparar para condições adversas, na programação defensiva nosso código precisa mais do que compilar na nossa máquina e funcionar com os testes “caminho feliz” que a gente faz usualmente.

Uma boa prática de programação defensiva é verificar pré-condições do seu código, isto é, coisas que o código precisa que sejam verdadeiras para poder funcionar corretamente. Por exemplo, você pode querer verificar para um certo método se um argumento é não-nulo, ou se não é vazio para o caso de uma lista, ou se representa um objeto de determinado tipo. Em Python, você pode fazer isso criando uma asserção, usando o comando assert:

def buscaPorCpf(cpf):
    assert cpf != None, "argumento cpf nao pode ser None"
    print 'cpf:', cpf

A asserção é uma afirmação sobre o que deve ser verdadeiro em dado momento da execução do seu código. Na prática, ela funciona parecido com uma validação para se defender de erros de um usuário: a diferença é que a asserção é pra se defender de erros do programador, incluindo você mesmo. Quando a condição de uma asserção é verificada sendo falsa, o interpretador Python lança uma AssertionError, opcionalmente com a mensagem que especifique o erro.

É sempre bom usar mensagens que explicitem o tipo de erro, principalmente quando a condição é mais específica, para facilitar a detecção dos problemas, além de facilitar a leitura do código. Por exemplo, você pode querer também verificar se o argumento cpf é do tipo string:

def buscaPorCpf(cpf):
    assert cpf != None, "argumento cpf nao pode ser None"
    assert isinstance(cpf, basestring), "argumento cpf precisa ser uma string"
    print 'cpf:', cpf

Nota: A checagem aqui é feita de tal forma que aceita tanto instâncias de str (ex.: '000.000.000-00') e de unicode (ex.: u'000.000.000-00'), que são subclasses de basestring

Repare que a asserção aqui está se defendendo de coisas básicas, pré-condições que se forem falsas, são obviamente um erro na programação. Nesse exemplo que estamos lidando com um CPF, é importante notar a diferença para uma validação de dados digitados pelo usuário, por exemplo, se ele digita um CPF inválido (que também é uma forma de programação defensiva). Para a validação, o seu programa deve tratar o erro (por exemplo, lançando uma exceção pré-definida ou simplesmente retornando None) de forma consistente com o restante do seu código. Asserções são para se defender de erros de programação e documentar pré-condições ou pós-condições do código, e não para implementar lógica de tratamento de erros.

Outra coisa a ser tomada nota, que pode ser vista como desvantagem de usar asserções para checar pré-condições, é que elas não são propagadas para subclasses do seu código, por exemplo. É muito fácil alguém criar uma classe que estende a sua, e avacalhar a linda defesa que você colocou no __init__. Se você acha interessante esse tipo de recurso, você talvez queira usar ferramentas que implementem programação por contrato, como a pydbc ou a pycontract. 2

Finalizando, se o seu código tiver muitas asserções e você acha que elas podem estar fazendo processamento desnecessário, você pode rodar o interpretador Python com a opção -O que habilita otimizações básicas como pular execução dos comandos assert. Mas… não faça isso! Existe uma lei da natureza que diz que alguns problemas só acontecem quando o cliente usa seu programa, então você provavelmente quer as asserções ajudando lá também. 🙂 3

Notas:

1 Li a metáfora do trânsito a primeira vez no livro O Programador Pragmático, altamente recomendado por sinal!
2 pycontract é a implementação de referência da PEP-0316, uma proposta de embutir tratamento de programação por contrato na linguagem, colocando as pré-condições e pós-condições nas strings de documentação dos métodos. A PEP é antiga e ainda não foi aceita, mas a implementação parece ser estável, e é facilmente instalável no Ubuntu com apt-get install python-contract.
3 Citação quase-literal roubada descaradamente do texto em inglês: Usando Asserções de Forma Efetiva.

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s