[Rodolfo Carvalho] Escreva menos, use Python

Ontem vivenciei uma situação em que tivemos que manter um código legado para inserir mais uma pequena funcionalidade.
Uma “regra” aqui é não se desviar do que você tem que fazer (que possui mais valor de negócio) para cuidar de outras coisas. Outra “regra” é não deixar débito técnico.
A grande dificuldade é encontrar o balanço adequado entre esses dois objetivos, e ainda assim implementar sua nova funcionalidade.

Bem, livre de qualquer “regra”, vou aproveitar esse post para introduzir 3 recursos do Python que nem sempre são lembrados, e que nos permitem economizar linhas de código, escrevendo de forma clara e concisa o que queremos fazer.

  1. Método get dos dicionários;
  2. Método split das strings;
  3. Função builtin/global setattr.

Vamos partir do seguinte código legado:

#...
from django.shortcuts import render_to_response
from model import Cadastro
#...

def tratar_cadastro(request):
   post = request.POST
   #...
   cadastro = Cadastro()
   #...

   if post.has_key('nome'):
       cadastro.nome = post['nome']

   if post.has_key('endereco'):
       cadastro.endereco = post['endereco']

   if post.has_key('telefone'):
       cadastro.telefone = post['telefone']

   if post.has_key('email'):
       cadastro.email = post['email']

   return render_to_response('cadastro/index.html', {"cadastro": cadastro})

É um esboço de uma View do Django. Como podem ver, espera-se que a view seja requisitada via HTTP POST, com a passagem de diversos parâmetros, que ficam armazenados no dicionário Python request.POST. Esse papo de cadastro, nome, telefone, etc é só para ilustrar.
Então o código que temos é uma sequência de if’s que setam atributos de uma instância de Cadastro que é posteriormente passada para o template.

Podemos imaginar que temos um formulário numa página que, ao ser submetido, exibe as informações preenchidas para confirmação.

Agora temos que adicionar mais um campo ao cadastro. Digamos que nosso cliente pediu para guardar também o campo “idade”. Por simetria, acabaria acontecendo o seguinte:

#...
from django.shortcuts import render_to_response
from model import Cadastro
#...

def tratar_cadastro(request):
    post = request.POST
    #...
    cadastro = Cadastro()
    #...

    if post.has_key('nome'):
        cadastro.nome = post['nome']

    if post.has_key('idade'):
        cadastro.idade = post['idade']

    if post.has_key('endereco'):
        cadastro.endereco = post['endereco']

    if post.has_key('telefone'):
        cadastro.telefone = post['telefone']

    if post.has_key('email'):
        cadastro.email = post['email']

    return render_to_response('cadastro/index.html', {"cadastro": cadastro})

Pra que mudar o que já funciona? Bem, vamos então ao primeiro dos tópicos.
Usar o dict.has_key é ruim e desnecessário. Esse método é deprecated no Python 2.6 e já foi removido da linguagem no Python 3.0, em favor da forma chave in dicionario. No lugar dele e do if deveríamos usar o dict.get.
Mas o que esse método faz? Ele retorna o valor de dict['key'] se o dicionário tem a chave ‘key’, senão retorna None ou outro valor padrão que o programador especificar.
Ficaria assim:

#...
from django.shortcuts import render_to_response
from model import Cadastro
#...

def tratar_cadastro(request):
    post = request.POST
    #...
    cadastro = Cadastro()
    #...

    if post.has_key('nome'):
        cadastro.nome = post['nome']

    cadastro.idade = post.get('idade', u'Campo não preenchido')

    if post.has_key('endereco'):
        cadastro.endereco = post['endereco']

    if post.has_key('telefone'):
        cadastro.telefone = post['telefone']

    if post.has_key('email'):
        cadastro.email = post['email']

    return render_to_response('cadastro/index.html', {"cadastro": cadastro})

Além de economizar linhas, temos um código menos redundante e seguindo o princípio de “It’s better to beg forgiveness than to ask permission”.
Vamos refatorar o resto do código:

#...
from django.shortcuts import render_to_response
from model import Cadastro
#...

def tratar_cadastro(request):
    post = request.POST
    #...
    cadastro = Cadastro()
    #...

    cadastro.nome = post.get('nome')
    cadastro.idade = post.get('idade')
    cadastro.endereco = post.get('endereco')
    cadastro.telefone = post.get('telefone')
    cadastro.email = post.get('email')

    return render_to_response('cadastro/index.html', {"cadastro": cadastro})

Assim, caso um dos campos não venha no POST, será atribuído o valor None ao atributo do objeto cadastro.
Notem que, assim como antes, existe bastante duplicação de código (por razões práticas, fiz um exemplo com poucas chaves do dicionário, mas o caso que me deparei eram mais que dez…).
Agora então é a hora de introduzir os outros dois conceitos, o split e o setattr.
O primeiro transforma uma string em lista através de um separador. Exemplo:

>>>  print "um, dois, tres".split(", ")
['um', 'dois', 'tres']

Isso pode ser usado para declararmos uma lista sem precisar de muitas aspas e vírgulas… basta colocar todos os itens da lista em uma string e usar o split.
O outro faz o mesmo que objeto.atributo = valor, porém atributo pode ser algo dinâmico, definido em tempo de execução.
Com essas mudanças, nosso código ganha uma tremenda compressão, sem perder legibilidade:

#...
from django.shortcuts import render_to_response
from model import Cadastro
#...

def tratar_cadastro(request):
    post = request.POST
    #...
    cadastro = Cadastro()
    #...

    for attr in 'nome idade endereco telefone email'.split():
        setattr(cadastro, attr, post.get(attr))

    return render_to_response('cadastro/index.html', {"cadastro": cadastro})