Tag: programação

  • Code Review e a Teoria das Janelas Quebradas

    Code Review e a Teoria das Janelas Quebradas

    Na empresa onde trabalho temos o (bom) hábito de fazer Code Review no código dos projetos que desenvolvemos. A prática não é obrigatória mas todos os desenvolvedores gostam de ter seu código revisado.

    Eu adoro revisar código alheio tanto quanto gosto de ver meu código revisado e, por isso, me esforço para dar pitaco em quase todos os projetos da empresa. Até em projetos de outras equipes.

    Eventualmente eu entro em longas discussões com os outros programadores para que eles renomeiem variáveis ou até mesmo para que coloquem/retirem espaços em branco que violam o Coding Style da empresa. Como usamos Python nos nossos projetos, adotamos a PEP-8 com apenas uma ressalva relativa ao número de colunas por linha que acaba estabelecida apenas pelo bom senso de cada programador.

    E eu sou muito chato com isso. Eu realmente implico com qualquer coisa que não me pareça certa. Não importam se são problemas críticos ou triviais: recebem a mesma atenção.

    Existe uma teoria que afirma que as janelas quebradas de edifícios em uma região da cidade tem relação direta com a criminalidade nesta mesma região.

    Eu acredito nessa teoria e, por isso, sou exigente nas minhas revisões. Faço isso porque acredito que um mero relaxo numa linha em branco dentro do arquivo pode evoluir para um desenho ruim de um módulo inteiro da aplicação.

    Ok, admito, isso pode parecer exagero mas… e se não for? E se a teoria das janelas quebradas se aplica também no contexto do código fonte de uma aplicação?

    Esse tipo de cuidado é ainda mais importante quando trabalhamos com linguagens de programação com tipagens dinâmica ou fraca, pois certas convenções de nomenclatura podem dizer muito sobre os tipos envolvidos em uma operação. Exemplo?

    Uma função chamada get_user() retorna que tipo de objeto? Eu presumo que seja uma instância de um objeto representando um usuário (ex. User). Mas só consigo presumir isso pelo nome da função (ou me dando ao trabalho de ler e entender a sua implementação).

    E a função get_users(), o que retorna? Presumo que seja uma coleção (collection) de objetos representando usuários, certo? Se o desenvolvedor descuidar dessas e de outras convenções o trabalho ficará bem mais complicado para os outros membros da equipe.

    Certa vez eu encontrei um código que fazia algo parecido com isso:

    user = self._get_user_from_credentials(request)

    Conseguem perceber o que está errado? O método diz que retorna um usuário a partir de suas credenciais (ex. username, senha, …) e enviamos para ele um objeto do tipo Request? Pedi para corrigir o problema de uma das duas formas:

    1. passando as credenciais do usuário para o método ou;
    2. renomeando o método.

    Optaram por renomear o método e o código ficou assim:

    user = self._get_user_from_request(request)

    Note que é um método protegido (em Python o prefixo ‘_’ é usado para informar que o método em questão não deve ser chamado externamente) e, por isso, não seria um problema muito grave manter o nome antigo. Mas mantendo como estava deixariamos uma janela quebrada em nosso código.

  • Coisas que você talvez não saiba sobre Linux, Unix e OS X

    Coisas que você talvez não saiba sobre Linux, Unix e OS X

    Já faz bastante tempo que trabalho com Linux e outros unices ao lado de gente muito foda. Ao longo desse período fui aprendendo algumas dicas e macetes que, quando uso na frente de alguns amigos, deixam eles espantados.

    São coisas simples que talvez vocês já conheçam, mas que vou colocar aqui como referência para as “futuras gerações” 😀

    Matando um processo que não quer ser interrompido

    Você executa um comando na linha de comando e ele fica travado. Você dá ^C (CTRL-C) e ele não morre? Tente ^\ (CTRL-\).

    Complementação enviada pelo meu amigo Rudá Moura: ^C envia um SIGINT, ^\ envia um SIGQUIT, o kill (sem argumentos) envia um SIGTERM e o kill -9 (que deve ser usado somente se você já tentou o SIGTERM) envia um SIGKILL que é o único sinal não interceptável pelo processo.

    Sessão SSH travou? Chame o ~

    Você deu ssh para um servidor e por algum motivo a sessão ficou pendurada?

    Tecla “<ENTER>~.” e a sessão será encerrada imediatamente. Lembre-se que se você usa um teclado com acentuação dead-keys você precisará digitar “<ENTER>~<SPACE>.“.

    Esse é o comando-til mais legal mas existem outros:

    $ man ssh  # procure por "ESCAPE CHARACTERS"

    Arquivos com - no início do nome?

    Uma brincadeirinha que a gente fazia com a molecada que chegava pra trabalhar era criar um arquivo chamado “-rf ~” em algum lugar na máquina do cara e desafiar: “apague esse arquivo na linha de comando”.

    Tem algumas respostas certas para esse desafio, mas rm -rf ~ (CUIDADO COM ISSO!) certamente não é uma delas 😀 Tentar fazer escape do “-” ou fazer rm "-rf ~" também não vai funcionar. Os comandos vão interpretar esse sinal como uma opção do próprio comando e não como um argumento do tipo nome de arquivo.

    Solução?

    rm -- "-rf ~"

    Quando colocamos o -- informamos pro comando que todos os parâmetros passados a partir daquele ponto são argumentos para o comando e não opções do comando.

    O meu amigo Elvis (EPx) e o Fedalto conhecem outra forma que também permite remover esse diretório:

    $ rm -rf ./-rf\ ~

    Debugando problemas em software alheio

    Você tem um software que simplesmente não está funcionando direito e não dá nenhuma mensagem que te ajude a resolver o problema? Esse software não é seu; foi feito em C; você só tem o binário; etc?

    Na minha experiência de vida quase sempre esses problemas acontecem por causas “bobas” como um arquivo de configuração que não está no lugar certo ou está com as permissões incorretas, um processo tentando alocar um recurso que já está sendo usado por outro, etc. Mas como saber disso se a única coisa que aparece pra mim é um enigmático “Segmentation Fault”?

    # strace [-p PID|software] [-f]

    O strace mostra todas as syscalls que um processo está executando no console. Como é muita informação é sempre legal redirecionar essa saída para um arquivo e analisar depois.

    Quando o software caiu você abre o arquivo de log e procura (de trás para frente) por mensagens de erro logo antes do programa morrer. Já resolvi dezenas de problemas assim.

    Vale lembrar que essa ferramenta deve ser encarada como um tipo de “último recurso” quando outras técnicas de resolução de problemas já não estiverem funcionando mais.

    No OSX o utilitário que imita o strace se chama dtruss e o jeito de usar é bem similar.

    Pausando o console

    Você executou um tail -f arquivo.log mas os logs chegam numa velocidade que te impedem de analisar as mensagens?

    Faça um Pause no terminal: ^S (CTRL-S). Para liberar o Pause: ^Q (CTRL-Q).

    Alternando entre jobs

    Executou um comando e deu ^Z. Executou outro comando e deu ^Z novamente. Ficou com dois jobs parados, certo? (execute o comando jobs para ver os dois processos parados).

    Para trazer um deles devolta para o primeiro plano use o comando:

    $ %N  # onde N é o número do job

    O Aristeu pediu pra lembrar que esse comando é uma espécie de atalho para o comando fg:

    $ fg N

    E que, fazendo par com o fg tem o comando bg N que “despausa” o processo e coloca ele pra funcionar em segundo plano.

    Se você quer executar um processo direto em segundo plano é só colocar um & no fim da linha:

    $ find / > lista_de_arquivos.txt &

    É o mesmo que fazer ^Z e bg.

    Reexecute comandos

    Você digitou um comando grande e chato na linha de comando, precisa reexecutar ele mas ele tá lááá atrás no histórico? Não precisa ficar com a setinha pra cima até cansar. É só usar a !:

    $ find . -type f | while read i; do mv "$i" "$i.bak"; done  # ok, eu sei que dá pra melhorar isso mas queria uma linha grande, ok? :D
    $ ...
    $ ...
    $ ...
    $ # vamos reexecutar o find|while
    $ !fin

    Buscando no histórico de comandos

    O Thiago Santos, o Aristeu e o toti pediram pra eu acrescentar a busca no histórico. Eu acho que muita gente já sabia desta dica e por isso não coloquei na primeira versão desse artigo. Mas… como tem gente que talvez não conheça aí vai.

    Eu uso o modo VI no console (próximo tópico) e para fazer buscas no histórico de comandos eu uso os comandos de busca do próprio vi: <ESC>/string_de_busca. Para repetir a busca basta ir acionando a tecla N.

    Por padrão a maioria das distribuições Linux (e alguns Unix) usam o modo emacs e para fazer a mesma busca usa-se o comando CTRL+R. Agora é só digitar a string de busca e <ENTER>. Para repetir a busca use o comando CTRL+R novamente.

    Modo VI no console

    Tive um amigo que dizia que você não pode se considerar um usuário de VI se usa o modo emacs (default) no console.

    Para alternar para o modo VI (erroneamente chamado de modo que bipa pelos detratores do VI) é só digitar:

    $ set -o vi

    Ou acrescentar as linhas abaixo ao teu ~/.inputrc:

    set editing-mode vi
    set keymap vi

    Nesse modo você usa os comandos do VI navegar no histórico, fazer busca dos comandos, etc. Exemplo: para buscar o comando find no histórico você digita <ESC>/find<ENTER> e para procurar as próximas ocorrências é só ir apertando N. Encontrando o comando é só dar <ENTER> para executar o comando ou I para entrar no modo de edição e alterar o conteúdo da linha.

    Mas o mais legal desse modo é que você pode chamar a linha de comando no VI a qualquer momento. Vamos supor que você digitou uma mega linha comprida na linha de comando e começou a ficar complicado de edita-la. No modo VI basta você digitar <ESC>vi e o editor VI será carregado com todo o conteúdo da sua linha de comando.

    Se você gravar e sair (:wq) a linha será executada. Se você só sair sem gravar a linha será ignorada.

    Usando o comando history

    O Gustavo Straube também sugeriu usar o comando history em conjunto com o grep:

    $ history | grep 'comando'

    Resetando o terminal

    Você estava lá trabalhando na linha de comando e acidentalmente deu um cat num arquivo binário achando que lá só tinha texto. Teu terminal ficou todo zoado com símbolos irreconhecíveis no lugar dos caracteres? É só resetar o terminal:

    $ reset

    Funciona na imensa maioria das vezes. Mas se não funcionar… aí é só matando esse terminal mesmo 😀

    Agradecimentos para o toti por ter feito me lembrar dessa dica 😀

    Limpando a tela do console

    O comando para limpar a tela do console e posicionar o prompt na primeira linha do terminal é:

    $ clear

    Mas você também pode usar uma combinação de teclas essa função:

    • CTRL-L no Linux
    • CMD-K no Mac OS X

    você pode acionar essa combinação de teclas a qualquer momento. Mesmo depois de já ter digitado alguma coisa no prompt. Exemplo:

    $ ls <CTRL+L>

    Vai limpar a tela e manter o comando ls que já foi digitado.

    Eu já conhecia e usava isso, mas o Adrian C. Miranda me lembro nos comentários.

    Último argumento

    Essa eu não conhecia e foi passada pelo meu colega Vinicius Assef:

    $ git diff path/para/meu/arquivo_que_mudou.py
    $ git add !$

    O !$ pega o último argumento do último comando digitado no shell.

    Para saber mais sobre essa e outras expansões possíveis:

    $ man bash  # procure por ^HISTORY EXPANSION

    Voltando para o diretório anterior

    Essa eu imaginava que todos soubessem, mas descobri que muita gente desconhece. O comando cd que altera o diretório corrente aceita o parâmetro - (cd - que faz você voltar para o diretório onde você estava anteriormente.

    ~/Work/x$ cd ../y
    ~/Work/y$ cd -
    ~/Work/x
    ~/Work/x$ 

    Dica: Você também pode usar “git checkout -” no git para voltar para o branch anterior.

    Usando o make sem um Makefile

    É possível utilizar o utilitário make sem a necessidade de criar um Makefile quando você quer fazer alguma compilação de programas escritos em C/C++.

    Se você tem um programa.c e quer compilar ele basta fazer make programa (make [nome-do-programa-sem-extensão):

    $ ls
    programa.c
    
    $ cat programa.c
    #include <stdio.h>
    
    int main(int argc, char *argv[]) {
    	printf("Hello World!\n");
    	return 0;
    }
    
    $ make programa
    cc programa.c -o programa
    
    $ ./programa
    Hello World!

    Sabe algum macete que não está aqui?

    Eu sei de mais alguns outros vários mas não consegui lembrar pra colocar aqui. Conforme for lembrando vou atualizando esse artigo. E você sabe algum? Mande para blog @ osantana (dot) me.

  • Personal Python Style Guide

    Personal Python Style Guide

    No lugar onde trabalho usamos Github e usamos a funcionalidade de Pull Request para sugerirmos melhoria no código da empresa.

    Eu adoro esse sistema porque ensinamos e aprendemos a programar melhor. Mas algumas sugestões eu evito colocar porque elas são baseadas em minhas preferências pessoais e não em fundamentações técnicas.

    Essas eu vou colocar aqui e apontar esse post para meus colegas de trabalho (e amigos). Quem gostar pode adotar.

    São escolhas e opções de estilo que vão além do que a PEP-8 propõe.

    Múltiplos pontos de retorno

    Vejo muito código assim:

    def f(x):
        if x == "spam":
           do_something()
           do_something_else()

    Não tem nada de errado com esse código. Mas eu fico incomodado com o fato de que todo o código dessa função está dentro do bloco do if. Eu prefiro, quando possível, inverter a lógica do if e inserir um ponto de retorno extra na função:

    def f(x):
        if x != "spam":
           return
    
        do_something()
        do_something_else()

    Tenho amigos programadores que não gostam de inserir pontos de retorno extras na função. Eles tem argumentos bons e fortes para defender o “jeito deles” e eu tenho os meus argumentos para defender o “meu jeito”.

    if/elif tem que ter um else

    É claro que um bom sistema de objetos com interfaces claras e polimorficas eliminaria toneladas de lógica condicional do seu código. Mas, vamos lá, no mundo real os ifs estão por aí.

    Junto com os ifs temos os elifs que podem ser usados (com moderação) para nos ajudar a colocar um pouco de vida ao nosso código.

    Mas quando eu vejo isso:

    def f(x):
        if x == "spam":
           do_something()
        elif x == "eggs":
           do_something_else()

    O meu TOC (Transtorno Obsessivo Compulsivo) “apita” e eu tenho que “consertar” esse código pra algo mais ou menos assim:

    def f(x):
        if x == "spam":
           do_something()
        elif x == "eggs":
           do_something_else()
        else:
           return

    Já teve casos onde fiz else: pass só pra poder dormir de noite. 🙂

    Deixando a brincadeira de lado, colocar um else em todas as construções que tenham elif é uma prática de programação defensiva. É bastante comum encontrar algo parecido com o código abaixo nos sistemas que desenvolvo:

    def f(x):
        if x == "spam":
           do_something()
        elif x == "eggs":
           do_something_else()
        else:
           raise SystemError("Invalid argument")

    Esse tipo de código evita que erros passem desapercebidos pois uma exceção será gerada sempre que um argumento incorreto for passado para essa função. Com isso eu posso corrigir o problema ou acrescentar um elif novo para tratar esse novo argumento.

    Consistência, retornos e erros

    Esse talvez seja um caso mais sério do que apenas uma “questão de estilo” porque afeta a qualidade geral do código se não adotada de forma correta.

    Em linguagens dinâmicas não declaramos os tipos dos identificadores e isso traz uma série de benefícios mas também força o programador a seguir uma série de regras para evitar dores de cabeça. Uma delas é a de que as interfaces (métodos ou funções) precisam retornar valores consistentes.

    def is_odd(n):
        try:
           return n % 2 == 1
        except TypeError:
           return

    O código acima é o de uma função que retorna True se o número passado como parâmetro for ímpar. Se o valor passado como parâmetro não for um número o retorno é None

    Essa função tem um problema de estilo: quando um valor incorreto é passado para uma função ela deveria avisar o programador sobre esse erro. Como fazemos isso? Com o sistema de exceção!

    Então não tem problema receber um TypeError exception quando passamos uma string para uma função que deveria receber apenas números. O código que está chamando essa função claramente tem um bug que precisa ser corrigido.

    O outro problema vai um pouco além da questão estilo. Essa função deveria ter um retorno booleano, ou seja, deveria retornar um valor do conjunto (True, False). Mas isso não está acontecendo. Ela pode retornar None que é um não-valor que não faz parte do conjunto dos booleanos.

    E pior: apesar de não fazer parte do conjunto dos booleanos, o None é interpretado como False pelo Python e isso pode fazer com que erros fiquem ocultos por muito tempo em nosso código.

    Essa função, para mim, deveria ser implementada assim:

    def is_odd(n):
       return n % 2 == 1

    Singular para um, plural para muitos

    O nome que escolho para os identificadores no meu código refletem (parcialmente) o tipo de dado que eles referenciam.

    def is_odd(n):
       return n % 2 == 1

    Se a função tem um nome iniciado com is_* ela retornará um valor booleano.

    def odds(r):
       return [n for n in range(r) if n % 2]

    Se o identificador tem plural no nome (odds) ele vai retornar uma coleção (collection) ou um iterador (iterator).

    def next_odd(n):
       return n + (2 if n % 2 else 1)

    Se o nome do identificador estiver no singular ele vai retornar um valor único.

    class Odds(object):
       def __init__(self):
          self.odds = []
    
       def load_odds(r):
          self.odds = [n for n in range(r) if n % 2]

    Quando o identificador tem um verbo “impositivo” no nome ele não retorna nada (eu raramente uso métodos get_* que violam essa regra). O mesmo, neste caso, quando o método faz mudanças inplace no objeto.

    Essa regra que diz que métodos que fazem mudanças inplace nos objetos não devem retornar valor é adotada pelo próprio Python, por exemplo, nos casos dos métodos list.sort() ou list.reverse().

    To be continued…

    Assim que eu me lembrar de outras coisas vou atualizar esse artigo. Se você tem sugestões de estilo que vocês adotam no dia-a-dia escrevam nos comentários.

  • A Web e o problema das senhas “clear text”

    A Web e o problema das senhas “clear text”

    Nos últimos dias o serviço Trapster avisou que 10 milhões de senhas dos seus usuários poderiam estar comprometidas. No ano passado a rede de sites de notícia Gawker passou pelo mesmo problema por um problema parecido.

    Esse artigo ainda trás conceitos válidos mas as recomendações sobre os melhores e mais recentes algoritmos para hash criptográfico podem estar desatualizados. Pesquise o assunto antes de escolher detalhes de implementação na sua solução.

    E se formos voltar no tempo vamos descobrir que todo ano temos pelo menos 2 ocorrências similares em sites grandes. E isso vem acontecendo ano após ano desde que a Internet se tornou acessível entre “civis”.

    Se todos os usuários usassem senhas diferentes para cada um dos serviços que usa na internet o estrago causado por esse tipo de situação seria bastante limitado. Mas não é isso o que acontece e, quando senhas “vazam” na internet o estrago pode ser gigantesco.

    Problema antigo. Solução conhecida.

    Em 1994 fui fazer estágio na Telesp no interior de São Paulo. Lá eu tive meu primeiro contato “sério” com um Unix. Era um SCO Unix que rodava num 386SX com 7 terminais seriais.

    Enquanto eu estava aprendendo a usar o Unix eu vi que tinha um arquivo chamado /etc/passwd e, pelo nome, imaginei que lá eu encontraria as senhas de usuários do sistema.

    Naquela época eu era “metido a hacker” e fiquei entusiasmado com a idéia de descobrir a senha de todo mundo que usava aquele servidor. Fiquei mais animado ainda quando vi que as permissões do arquivo permitiam que qualquer usuário examinasse seu conteúdo.

    Quando abri o arquivo veio a decepção… no lugar onde deveriam ficar as senhas estava um “x”. Mas não me dei por vencido. Após estudar as manpages (que viam impressas em manuais imensos!) fiquei sabendo que as senhas não estavam lá. Elas estavam no arquivo /etc/shadow.

    Com o ânimo renovado fui atrás desse arquivo. Mas dessa vez as coisas estavam um pouquinho mais difíceis… só o usuário root conseguiria ver esse arquivo.

    Chegou a hora, então, de uma pitada de engenharia social… não vou contar como fiz porque foi muito fácil mas consegui a senha de root do sistema… hora de ver a senha dos outros usuários da Telesp e implantar uma mega-revolução na telefonia brasileira!… erm… menos…

    Quando abri o arquivo tomei uma ducha de água fria definitiva. No lugar onde as senhas deveriam estar eu só um amontoado de caracteres que não se pareciam com senhas. Até poderiam ser as senhas dos usuários mas parecia muito improvável (e de fato não eram).

    Descobri depois que o que estava armazenado ali era o resultado de uma espécie de “criptografia”. Ou seja, em 1992 os sistemas Unix já não armazenavam as senhas em texto puro. É bem provável que eles já não fizessem isso a muito mais tempo.

    Estamos em 2011. Se depois de 19 anos eu armazenasse as senhas dos meus usuários em “texto puro” eu deveria ser chamado de irresponsável e incopetente. Se um invasor tivesse acesso à essas senhas eu deveria ser tratado como criminoso. No mínimo.

    A solução

    A única solução correta e infalível para armazenar senhas de forma segura é: não armazená-las.

    Aí você deve estar perguntando: se eu não armazenar a senha do usuário como eu consigo verificar a senha dele durante sua autenticação?

    Uma resposta “básica” seria: armazene o hash da senha.

    Segundo o HowStuffWorks brasileiro:

    “Hash é resultado da ação de algoritmos que fazem o mapeamento de uma seqüência de bits de tamanho arbitrário para uma seqüência de bits de tamanho fixo menor de forma que seja muito difícil encontrar duas mensagens produzindo o mesmo resultado hash (resistência à colisão ), e que o processo reverso também não seja realizável (dado um hash, não é possível recuperar a mensagem que o gerou).”

    Existem vários algorítmos para cálculos de hash. Cada um deles possui um determinado tipo de aplicação. As funções de hash mais “famosas” são aquelas cuja aplicação está no campo da criptografia: MD2, MD4, MD5, SHA1, SHA256, …

    Vou demonstrar o que acontece com o MD5:

    $ echo "123mudar" | md5sum
    642d8860fc6fe3126803ebdbe9974abd
    $ echo "123mudar" | md5sum
    642d8860fc6fe3126803ebdbe9974abd
    $ echo "123mudor" | md5sum
    fe294bbc902c287efb7acb20c8fdb67a
    

    Note que sempre obtemos o mesmo resultado quando a senha é a mesma mas quando mudamos 1 único caracter o resultado do cálculo de hash muda completamente.

    Tendo isso em mente podemos pensar em armazenar no nosso banco de dados apenas o hash da senha do usuário. Quando for preciso verificar a senha informada pelo usuário aplicamos a função de hash à ela e comparamos com aquela que está armazenada no banco de dados.

    Perfeito não é? Problema resolvido, não? Não! Ainda falta uma pitada de “sal” nessa receita…

    Salt – mais uma dificuldade para o invasor

    Vamos supor que um invasor tenha acesso ao banco de dados da aplicação e ao hash das senhas…

    Com esses hashes o usuário pode disparar um ataque baseado em dicionários ou até mesmo procurar pelos hashes no Google! Veja o que acontece com uma senha “fraca”:

    $ echo "senha" | md5sum
    6fd720fb42d209f576ca23d5e437a7bb
    

    Agora procure por “6fd720fb42d209f576ca23d5e437a7bb” no Google e veja o resultado 😀

    Para resolvermos esse problema devemos usar um “salt” para gerar o hash da senha.

    Salt é uma sequência aleatória de bits que são concatenados à senha do usuário antes de gerar o hash (quanto maior essa sequência mais difícil será o trabalho do invasor).

    Por ser uma sequência aleatória precisamos armazená-la junto com o resultado do hash para ser possível verificar a senha depois. Vamos à um exemplo “pythonico”

    $ python
    >>> import random
    >>> import hashlib
    >>> senha = "senha"
    >>> salt = ''.join(chr(random.randint(65, 122)) for x in range(5))
    >>> salt # Esse é o Salt!
    'vGBAA'
    >>> salt_senha = salt + senha
    >>> salt_senha # salt + senha
    'vGBAAsenha'
    >>> hash = hashlib.md5(salt_senha).hexdigest()
    >>> hash # Esse é o hash do salt+senha
    '3607507cfa3f31b0cf10e83af947df97'
    >>> armazenar = salt + "$" + hash
    >>> armazenar
    'vGBAA$3607507cfa3f31b0cf10e83af947df97'
    

    Tente procurar pelo hash “3607507cfa3f31b0cf10e83af947df97” no Google agora… ou submeter esse hash à um ataque de dicionário… Você verá que aumentamos um pouco a dificuldade para descobrir a senha do usuário.

    Esse é o procedimento usado por grande parte dos frameworks web que implementam alguma forma de armazenamento de senha (ex. django.contrib.auth) (ver atualizações 2 e 3). Ele é bastante seguro e podemos considerar isso satisfatório. Mas as coisas estão mudando…

    A nuvem “do mal”

    Com o advento da “computação na nuvem” chegamos à situação onde podemos comprar “poder de processamento” tal como compramos energia elétrica.

    Antigamente se a gente tivesse um salt+hash em mãos era quase impossível (ou economicamente inviável) conseguir poder de processamento suficiente para submetê-los à um ataque de força bruta.

    Mas as coisas mudaram e com 1 cartão de crédito e uma quantidade “viável” de dinheiro é possível contratar dezenas de “nós” de processamento na Amazon ECS, por exemplo, e colocá-los para “atacar” o nosso salt+hash.

    Esse tipo de prática provavelmente já está sendo usada por alguns invasores pelo mundo e aparentemente não existe uma solução definitiva para esse tipo de situação.

    O que existe são medidas que você pode adotar para dificultar um pouco mais a vida dos vilões 😀

    Uma delas é substituir o algoritmo de hash (MD5/SHA1) por outro algorítmo mais apropriado para o nosso uso.

    O problema em usar os algorítmos MD5 e SHA1 para calcular os hashes de nossas senhas é que eles são muito eficientes e rápidos. As principais aplicações desses algorítmos exigem que eles sejam rápidos (ex. assinatura digital de um arquivo gigantesco).

    Como eles são muito rápidos é possível disparar um ataque de força bruta e testar muitos hashes em um curto espaço de tempo. Como as plataformas na “nuvem” cobram por tempo de uso podemos quebrar uma senha à um custo relativamente baixo (ou viável economicamente).

    Se trocarmos esses algorítmos por um que seja muito mais lento obrigamos o invasor a gastar mais poder de processamento (e consequentemente mais dinheiro) para descobrir nossa senha.

    Um dos métodos mais indicados, hoje, é o bcrypt (blowfish). Existe implementações desse algorítmo para diversas linguagens:

    E como eu sei se um site armazena minhas senhas em texto puro?

    Não é possível saber com 100% de certeza se um site ou serviço armazena as suas senhas em “texto puro”, portanto, o melhor mesmo é criar o hábito de usar senhas diferentes em cada um dos serviços (só tente não anotá-las em papéis! :D).

    Mas apesar de não ser possível ter certeza se o serviço em questão é desenvolvido por um irresponsável é possível buscar indícios dessa irresponsabilidade:

    • Receber um e-mail de confirmação de cadastro onde sua senha está presente – Se ele está te mandando um e-mail com sua senha é grande a possibilidade dela ser armazenada da mesma forma.
    • Use a opção “esqueci minha senha” dos sites para testar – se você usar essa opção e o site te mandar um e-mail (ou mostrar na tela) a sua senha é porque eles tem a sua senha “original” armazenada em algum lugar. O correto é receber um link para *resetar* sua senha.

    Implicações no “mercado”

    Nós que trabalhamos com web e somos entusiastas da idéia “da nuvem” devemos condenar a prática de armazenar dados sensíveis do usuário de forma tão irresponsável. Cada notícia que surge dando conta de vazamentos dessas informações prejudica todos os serviços. Para um leigo é a segurança “da internet” que é falha.

    Se você é um empresário ou desenvolvedor sério e responsável deve cuidar da segurança dos dados dos seus usuários com todo o cuidado e, sempre que ver outra empresa trabalhando de outra maneira você tem a obrigação de condená-la pois ela também está, indiretamente, prejudicando o seu negócio.

    Atualização:

    O meu amigo Guilherme Manika postou um link para um artigo onde a equipe do Gawker relata o problema ocorrido com as senhas de seus usuários.

    Pelo que entendi eles armazenavam o hash das senhas usando a função crypt(3) e um salt com apenas 12 bits que, como disse, é muito pouco para os padrões de ataque atuais.

    Então, em 2008, eles modificaram o sistema para usar o bcrypt() também. Mas, aí a ‘burrada’ deles: eles continuaram gerando o hash com crypt(3) e armazenando no mesmo lugar que os hashes bcrypt() pra manter compatibilidade retroativa!

    Segundo um e-mail que circulou numa lista de segurança, 748.081 usuários tinham as senhas armazenadas com crypt() e 195.178 tinham as senhas armazenadas com crypt() e bcrypt(). Total: 943.259 usuários afetados. Quase um milhão de pessoas.

    Atualização 2:

    O framework Django, ao contrário do que disse, não usa bcrypt() para gerar o hash das senhas armazenadas. No lugar disso ele usa os algoritmos PBKDF2 + SHA256 conforme recomendações do NIST (pdf). Eles também usam outras técnicas complementares para tornar o sistema mais seguro como um comparador de strings em tempo constante.

    Atualização 3:

    O artigo faz muitas referências à bcrypt() que era o algoritmo recomendado pela maioria dos especialistas na época em que esse artigo foi escrito. Acontece que nesse mundo da tecnologia as coisas vão evoluindo e melhorias vão sendo sugeridas. Apesar disso o uso de bcrypt() é “bom o suficiente” e, por isso, manterei o texto original.

    Caso você queira seguir as recomendações mais recentes o melhor é usar PBKDF2+SHA256 (+ algoritmos de comparação de strings em tempo constante) conforme indicado na Atualização 2 acima.

    Se você se interessa pelo assunto, quiser se aperfeiçoar e consegue se virar bem com o inglês eu recomendo o curso (gratuito) de Criptografia de Stanford no Coursera.

  • É mais fácil pedir desculpas do que permissão

    Diferente do que escrevi no post Dicas para um bom programa em Python, onde eu dou dicas de como proceder para ter um programa Python melhor, desta vez vou falar sobre um estilo que prefiro. Não quero dizer que estou certo ou errado, apenas que prefiro assim.

    It’s easier to ask forgiveness than it is to get permission

    Grace Hopper

    Recentemente, dentro do tempo que me sobrava, comecei a desenvolver uma biblioteca pra fazer requisições HTTP para uma API REST. Essa biblioteca seria usada para criar testes automatizados do projeto que iremos começar a desenvolver aqui na empresa.

    Essa biblioteca faria basicamente o mesmo que a httplib e httplib2 do Python mas com algumas conveniências: (de)serialização de JSON/XML, conteúdo calculado no momento da request (ex. assinatura da requisição), e uma classe “TestCase-like” com funções que auxiliassem no desenvolvimento de testes.

    Eu tinha só algumas idéias do que essa biblioteca faria e quase nada de código quando vi o lançamento do Bolacha, desenvolvido pelo Gabriel Falcão, no Planeta Globo.com. Guardei o link pra conferir depois pois poderia ser útil para o que eu queria fazer.

    Ontem eu tive um tempo para analisar e vi que ele não só seria útil como já fazia a parte mais essencial do que eu precisava (requisições GET/POST/PUT/DELETE/…).

    Como o projeto esta hospedado no github.com tratei logo de fazer um fork para criar as outras funcionalidades que eu precisava. Código legal, código simples, código bem feito, mas… quando encontrei os primeiros…

    def request(self, url, method, body=None, headers=None):
      if not isinstance(url, str):
        raise TypeError(f'Bolacha.request, parameter url must be a string. Got {url}')
    
      if not isinstance(method, str):
        raise TypeError(f'Bolacha.request, parameter method must be a string. Got {method}')
    
      if method not in HTTP_METHODS:
        raise ValueError(f'Bolacha.request, parameter method must be a valid HTTP method. Got {method}. {RFC_LOCATION}')
      
      # ...continua

    … notei que o estilo do Gabriel divergia do meu. Nada errado com isso. Tanto que, mesmo assim, continuarei a usar e melhorar o Bolacha mantendo (dentro do possível) o mesmo estilo original do autor para que ele possa aceitar minhas contribuições.

    O que não gosto desse estilo é que, com ele, sempre estamos pedindo permissão, ou seja, verificando de antemão alguma informação no lugar de usá-la imediatamente e, só em caso de erro, considerá-las inválida. Nesse caso estamos adicionando um overhead desnecessário (muito pequeno neste exemplo) até mesmo para casos de uso correto do método.

    Outro problema que temos nesse estilo reside no fato de que estamos usando o mecanismo de herança da linguagem como um sistema de criação de interface para o objeto. Se eu quiser passar um objeto que se comporta exatamente como uma string mas que não seja uma classe derivada de str para o método .request() acima eu não vou poder.

    Eu removeria todas as verificações de isinstance() e deixaria o código assim:

    def request(self, url, method, body=None, headers=None):
      if method not in HTTP_METHODS:
        raise TypeError(f'Bolacha.request, parameter method must be a valid HTTP method. Got {method}. {RFC_LOCATION}')
      # ... continua

    Mais adiante nesse código vemos o uso de uma função chamada is_file() que é implementada da seguinte forma:

    def is_file(obj):
      return hasattr(obj, 'read') and callable(obj.read)

    Mais uma vez, nada de errado. Mas também não é muito o meu estilo. No meu estilo essa função sequer existiria porque, mais adiante, quando fosse necessário usar obj que, no código em questão, pode ser uma string ou um file object, eu faria algo assim:

    try:
      lines.extend(encode_file(obj))
    except AttributeError:
      lines.extend(['...'])

    Mais uma vez eu quero deixar claro que é só uma questão de diferença de estilo e que eu usei o código do Bolacha somente para ilustrar essa diferença. Dentro do estilo do Gabriel o código está perfeito (tá, não existe código perfeito, mas o dele tá muito bom).

    Como leitura complementar sobre essas diferenças eu recomendo o artigo isinstance() considered harmful, Duck Typing, Permission and Forgiveness e PEP-3119 – Introducing Abstract Base Classes (funcionalidade de suporte ao estilo usado no Bolacha).

    Por último, ao meu estilo, gostaria de pedir desculpas ao Gabriel Falcão por ter usado o código do Bolacha para ilustrar esse artigo sem permissão. 🙂

  • Dicas para um bom programa em Python

    Dicas para um bom programa em Python

    Oi pessoal, desta vez eu vou pular as ‘desculpas’ por ter demorado tanto para postar aqui no blog e vamos direto ao assunto.

    Recentemente eu tenho trabalhado bastante com Python (dã!) desenvolvendo projetos de diversos tipos e resolvi escrever aqui sobre algumas coisas que pratico enquanto desenvolvo.

    Esse artigo é uma espécie resumo com boas práticas de programação Python que utilizo no meu dia-a-dia.

    Código mais robusto

    Deu certo ou errado?

    O que você faz quando acontece algo errado na execução do seu método? O que você responde à requisição que lhe foi feita?

    Eu tenho visto em muito código por aí os desenvolvedores retornando valores sentinela (None, null, 0, -1, etc.) para avisar que algo incorreto aconteceu na execução do método.

    def f(arg):
       if not arg:
          return None
       return ["resultado", "de", "um", "processamento"]

    Algumas linguagens de programação não possuem estruturas de tratamento de exceção e, neste caso, o uso de sentinelas é válido. Mas quando a linguagem de programação te disponibiliza essa funcionalidade é bom usá-la.

    def f(arg):
       if not arg:
          raise ValueError("Argumento Invalido")
       return ["resultado", "de", "um", "processamento"]

    Deixem as exceções fluirem.

    Isso mesmo. A menos que você saiba exatamente o que você deve fazer quando uma exceção aparece deixe-a exceção “subir”. Pode ser que “lá em cima” alguém saiba cuidar dela adequadamente.

    Quando não fazemos isso estamos ocultando informação importante para os usuários do nosso código (sejam eles usuários, outros desenvolvedores ou nós mesmos).

    def f():
       try:
          return conecta()
       except ExcecaoQueDeveriaSerErro:
          return None

    Quando eu implemento esse tipo de método/função eu faço assim (na verdade eu não implementaria f() e chamaria conecta()):

    def f():
       return conecta()

    O que seu método/função retorna?

    Código que eu encontrei recentemente:

    def get_fulanos():
       q = Q("select * from patuleia where alcunha like 'fulano%'")
       ret = [str(fulano['nome']) for fulano in q]
       if len(ret) == 1:
          return ret[0]
       return ret

    Perceberam o que está errado? O seu método retorna uma lista de Fulanos ou retorna Fulano?

    Isso está conceitualmente errado e pode fazer você perder horas preciosas do seu dia tentando achar um bug causado por esse tipo de código.

    Aconteceu comigo. Note que str() implementa uma interface de sequence da mesma forma que list(). Então o erro passa silenciosamente no caso abaixo:

    old_fulanos = ["Ze Ruela", "Ze Mane"]
    old_fulanos.extend(get_fulanos())
    print(old_fulanos)

    Rodando esse código você vai obter ['Ze Ruela', 'Ze Mane', 'q', 'u', 'a', 'c', 'k'] sendo que, em mais de 90% dos casos, o que você gostaria de ter seria: ['Ze Ruela', 'Ze Mane', 'quack'].

    “Nada” é diferente de “alguma coisa”.

    Essa dica é só uma complementação da primeira e da segunda dica.

    Quando o seu método/função retorna uma collection (seqüência, conjunto, hash, etc) vazia você deve retorná-la vazia e não um valor sentinela (como None). Isso facilita a vida de quem vai usar o seu método/função:

    def vazio():
       return []
    
    for elemento in vazio():
       pass #... faz algo se o conjunto contiver dados ...

    Se você retorna um valor sentinela:

    def vazio():
       return None
    
    elementos = vazio()
    if elementos:
       for elemento in elementos:
          pass # ...

    Notou que tivemos que criar uma variável com o resultado da operação (para não precisar chamá-la duas vezes) e tratar a sentinela com um “if“? Se eu esqueço de tratar a sentinela meu programa vai quebrar.

    Lembre-se sempre que uma collection vazia tem valor booleano “False“.

    Todo ‘elif‘ tem um irmão ‘else‘.

    Sempre que você precisar usar uma construção if/elif coloque uma cláusula ‘else‘.

    Além de usar a cláusula ‘else‘ eu geralmente faço com que ela gere uma exceção. Desta forma eu sou obrigado a trabalhar todas as possibilidades nos ‘if/elif‘ evitando ocultar uma situação que pode ser inválida.

    class InvalidCommand(Exception):
       pass
    
    def minihelp(comando):
       if comando == "print":
          return ("Imprime dados na tela. "
                  "Deixará de ser comando "
                  "no Python 3.0")
       elif comando == "assert":
          return ("Certifica se uma condição é "
                  "verdadeira e gera uma excessão "
                  "em caso contrário")
       elif comando == "...":
          pass # ...
       else:
          raise InvalidCommand(f"Comando {comando} inválido.")

    Eu gosto dessa prática mas isso não significa que você deva seguí-la sempre. Existem situações onde ter um “valor default” é necessário e nestes casos o uso do else sem levantar exceção se faz necessário.

    if comando == "if":
       print("Vai usar elif?")
    elif comando == "elif":
       print("Muito bem. Agora falta o else")
    else:
       print("Pronto. Agora está bom.")

    “Pythonismos”

    Use mais atributos públicos do que atributos protegidos (“_“).

    Programadores acostumados com Java utilizam muito as cláusulas ‘private‘ e ‘protected‘ para encapsular os atributos de um objeto para logo depois implementarem os getters e setters para acessar esses atributos.

    Essa prática é aconselhada em Java porque em algum momento do futuro você, talvez, precise validar esses dados ou retornar valores calculados. Nestes casos os programadores apenas implementam essa lógica nos métodos que acessam o atributo privado.

    Mas em Python isso não é necessário. Em Python você pode transformar seu atributo público em uma “property” que não muda a forma de se acessar o atributo e permite o acrescimo de lógica ao acesso do mesmo.

    Evite usar “__“.

    Por convenção, em Python, todo método ou atributo iniciado com um “_” é considerado privado (equivalente ao protected em Java) e não deve ser acessado de fora da classe mesmo sendo possível fazê-lo.

    Dito isso parece meio óbvio que não precisamos usar “__” para dificultar ainda mais o acesso à esse atributo/método. Além disso o uso do “__” traz alguns incoveninentes para quem quer derivar a sua classe e acessar este método/atributo já que o Python o renomeia acrescentando o nome da classe ao seu nome (__attr vira _Classe__attr).

    Não sobrescreva builtins.

    Python disponibiliza várias funções e classes builtins que facilitam muito o uso da linguagem e economizam digitação. Mas esses builtins tem nomes muito “comuns” e frequentemente a gente usa os nomes dos builtins como nomes de identificadores. Eu mesmo vivo (vivia) fazendo isso.

    O problema é que em certos momentos alguns problemas podem acontecer quando você tenta chamar um buitin que já não é mais um builtin. Na maioria das vezes o problema “explode” logo e você rapidamente conserta mas em alguns casos você pode perder muitas horas tentando achá-lo.

    Algumas pessoas usam um “_” no fim do nome do identificador (ex. “id” vira “id_”) mas eu acho isso um pouco feio então uso só quando não encontro uma alternativa melhor.

    Vou colocar aqui uma tabela de equivalências que eu costumo usar para substituir o nome dos builtins mais comumente sobrescritos:

    • id – ident, key
    • type – kind, format
    • object – obj
    • list – plural (lista de element vira elements)
    • file – fd, file_handler
    • dict – dic, hashmap
    • str – text, msg

    Análise estática economiza seu tempo.

    Eu uso o pylint, mas conheço algumas pessoas que preferem o pyflakes ou o PyChecker.

    UPDATE (2023-08-08): utilizo o utilitário ruff com muito sucesso nos meus projetos mais novos. O ruff agrega todos os utilitários acima e mais vários outros.

    A dica é essa: usar um programinha de análise estática como esses pode diminuir consideravelmente aqueles errinhos chatos de sintaxe, ou de digitação. Pode limpar os ‘import’ desnecessários do seu software, etc, etc.

    É lógico que esse tipo de ferramenta não substitui uma boa política de testes mas é um bom complemento para ela.

    Challenge yourself

    Máximo de 3 níveis de indentação. (ou 4 se estiver dentro de uma classe)

    Ao se esforçar para que seu código não fique muito aninhado você está trabalhando melhor a implementação dos seus métodos e funções. Nem sempre é possível (ou aconselhável) restringir tanto o nível de identação do seu código mas muitas vezes isso melhora a sua implementação.

    Máximo de 2 indireções.

    Recebeu um objeto como parâmetro? Chame apenas métodos dele e evite ao máximo chamar métodos do retorno desses objetos:

    def f(obj):
        obj.metodo() # legal!
        obj.metodo().outro_metodo() # ruim!

    Quando você chama um método pra um objeto retornado por outro método você está aumentando o acoplamento entre as classes envolvidas impedindo que uma delas seja substituída (ou reimplementada) ‘impunemente’.

    Essa regrinha é uma das regrinhas da Lei de Demeter.

    Máximo de 0 ‘if/elif/else‘s.

    Polimorfismo é isso. No mundo OO ideal, perfeito e utópico praticamente não precisaríamos do comando “if” e usaríamos somente polimorfismo. Mas… como não conseguimos isso tão facilmente* devemos, ao menos, usar o “if” com moderação.

    Conclusão

    Esta é uma lista incompleta de dicas para programadores Python. Se futuramente eu lembrar ou aprender algo novo eu volto aqui para falar sobre o assunto.

    Alguns desenvolvedores podem não concordar com as dicas. Neste caso eles podem enriquecer ainda mais esse texto argumentando sobre o as suas restrições no espaço para comentários.

    Se você tiver alguma dica para compartilhar com a gente coloque nos comentários. Se ela for boa mesmo eu coloco ela no corpo principal do blog.

    * eu mesmo só consegui fazer uma aplicação OO funcional sem usar um único if. Era uma implementação do joguinho de adivinhação de animais (aquele que pergunta “Vive na água? (s/n)”) em Smalltalk.

    Update: pequena correção sugerida pelo Francisco nos comentários: s/Classe/_Classe/.

  • Conhecimentos fundamentais

    Ja faz algum tempo que tenho notado em diversas listas de discussões da área de informática que alguns profissionais de nossa área parecem estar entrando para o mercado de trabalho sem ao menos ter alguns conhecimentos mais fundamentais sobre computação.

    Um “Conhecimento Fundamental” não é um “Conhecimento Essencial” mas é extremamente útil ter esse tipo de conhecimento quando se trabalha com qualquer coisa.

    Na minha simplória definição um “Conhecimento Fundamental” é um tipo de conhecimento que não necessariamente é aplicado diretamente na resolução de um problema mas que certamente enriquece muito a ‘teia’ de informações disponíveis em nossa mente. Esse enriquecimento faz com que a gente consiga apresentar soluções muito mais criativas e eficientes para determinados problemas.

    Para esclarecer um pouco melhor o que eu estou tentando dizer vou citar algumas situações reais que ocorreram com algumas pessoas próximas à mim.

    Operações bit-a-bit

    Quando eu fiz escola técnica no segundo grau eu tive um professor (Victor) que passou um semestre inteiro explicando aritmética binária, operações bit-a-bit e um básico de lógica booleana (que no semestre seguinte foi complementada de maneira adequada por outro professor).

    Nessa época vários colegas de classe ‘matavam’ essa aula porque ela realmente era bastante teórica e pouco prática e esses alunos estavam mais interessados ou em jogar Truco no páteo do colégio ou em ver o último livro de Clipper (que era a ‘sensação’ da época da mesma forma que Java é a ‘sensação’ do momento).

    Eu assisti essas aulas e aprendi sobre deslocamento de bits, soma, subtração, multiplicação binária, aprendi como um número negativo era representado binariamente (complemento de dois e afins), e por aí vai.

    Quase uma década depois eu e mais algumas pessoas em Curitiba fizemos um teste prático em linguagem C para entrar no meu atual emprego no INdT. O desafio proposto pra mim era corrigir uma função de um compressor de arquivos que fazia deslocamentos de bits e umas operações bit-a-bit com máscaras.

    Corrigi a função (estourei um pouco do tempo disponível) e passei no teste. Algum tempo depois um atual-companheiro de trabalho saiu da sala e havia me dito que ele se atrapalhou com o desafio dele (que também envolvia deslocamentos e manipulações de bits) porque ele não lembrava a sintaxe da linguagem C para fazer deslocamentos de bits (>).

    Se eu estivesse na mesma situação que ele eu teria solucionado o problema usando uma ‘alternativa’ à sintaxe da linguagem C e faria sucessivas multiplicações e divisões por 2 que fazem com que os bits se desloquem para à esquerda e direita respectivamente.

    Esse foi um caso real onde um “Conhecimento Fundamental” teria ajudado esse meu amigo. De qualquer forma ele se deu bem no restante do teste e hoje ele trabalha aqui do meu lado.

    Recursividade

    A algum tempo atrás precisei desenvolver um pequeno script pra um provedor de internet que calcularia o valor com pulsos gastos pelos assinantes desse provedor para que posteriormente fossem oferecidos produtos de banda-larga para clientes que fazem uso maior de internet.

    No momento em que estava pensando na forma que eu resolveria esse problema (que eh desnecessariamente complicado) emergiu dos fundos de meus “Conhecimentos Fundamentais” as minhas aulas sobre recursividade. Usei uma prática muito simples de ‘dividir e conquistar’ as ligações que encaixavam em diversas categorias diferentes de tarifação chamando a mesma função de cálculo recursivamente.

    Evidente que esse script não prevê todos os casos e situações diferentes com as quais uma operadora de telefonia precisa se preocupar mas serviu adequadamente para resolver o problema em questão.

    Recentemente um amigo que presta serviços para um provedor de internet que pertence à uma operadora de Telecom me disse que a equipe de desenvolvimento dessa empresa estava ‘batendo cabeça’ a várias semanas tentando resolver o mesmo problema.

    Eram desenvolvedores que não possuem “Conhecimentos Fundamentais” para exercerem suas funções. Passei esse script para que o pessoal usasse.

    Conhecimentos Fundamentais não envelhecem

    Os profissionais de informática hoje perdem muito tempo escolhendo uma faculdade ou outra, um curso ou outro porque uma delas usa Windows e a outra Linux. Uma usa Java e a outra .Net. Algumas ainda usam Pascal e outras começam a usar Python. Os alunos ‘brigam’ com seus professores para que os mesmos ensinem Java ou C# mas nunca brigam com eles pedindo para que explique sobre Autômatos Finitos ou máquinas de Turing.

    Saber Java ou C# hoje é o mesmo que ter sabido Clipper ou Cobol à alguns anos atrás. É um tipo de conhecimento que ‘envelhece’ e perde o sentido. Não é um conhecimento desnecessário, mas a aquisição desse tipo de conhecimento deveria ter uma prioridade menor do que os “Conhecimentos Fundamentais”.

    Fundamentar o conhecimento é algo imprescindível que torna mais fácil, inclusive, adquirir outros tipos de conhecimentos.

  • Nem tudo é perfeito

    Recentemente, durante o trabalho de escrever o meu livro sobre Python (não, não está pronto e ainda falta muito), me deparei com uma característica que achei superlegal em Python. Essa característica provêm da idéia de que uma string também é uma seqüência tal como listas ou tuplas. Essa característica permite que eu faça coisas como:

    >>> a = [1,2,3]
    >>> a += "spam"
    >>> a
    [1, 2, 3, 's', 'p', 'a', 'm']
    

    Quando estava jantando com o pessoal da PyConBrasil (que aliás foi muito massa) fui mostrar pra eles essa característica, mas como não lembrava exatamente o exemplo anterior eu demonstrei conforme abaixo:

    >>> a = [1,2,3]
    >>> a = a + "spam"
    

    Qual não foi meu espanto quando o resultado obtido foi um:

    >>> a = [1,2,3]
    >>> a = a + "spam"
    Traceback (most recent call last):
     File "", line 1, in ?
    TypeError: can only concatenate list (not "str") to list
    

    Quando vi isso entendi que o ‘problema’ ocorre porque o operador “+=” é mapeado para o método __iadd__() enquanto que o operador “+ é mapeado para o método __add__”.

    Até aí tudo certo. O problema da tal ‘inconsistência’ é que no caso específico dos objetos de listas (list) o método __iadd__() nada mais é do que um apelido para o método extend().

    Após algumas discussões com outros pythonistas de alto nível não foi possível chegar a nenhuma conclusão sobre se iss é ou não é um erro de design da linguagem.

  • Faça seu projeto falhar em 5 lições

    No post de hoje veremos em 5 lições como você deve proceder para que seu projeto falhe completamente. Invariavelmente, se você não se empenhar na aplicação das dicas fornecidas aqui é bem provável que você não consiga fazer com que seu projeto falhe mas apenas que tenha um resultado medíocre que é o atraso do cronograma. Por isso, estude todos as lições para que você realmente consiga fazer com que seu projeto falhe.

    Esse artigo é antigo e não reflete integralmente a minha opinião. Entretanto ele será mantido aqui por motivos históricos.

    Osvaldo Santana Neto

    Lição 1 – Analise, analise e depois analise

    Isso mesmo. Seu projeto corre o risco de ser um sucesso se você não trabalhar bastante na análise do problema a ser resolvido. Já imaginou se no lugar de analisar você tivesse implementando algo?

    Implementar algo é a pior coisa que você pode fazer para que seu projeto falhe.

    Se o problema é simples e não demanda muita análise utilize a sua criatividade para aumentar o problema e tentando convencer o usuário (cliente) de que este problema que ele não tem é algo que ele deveria ter.

    Lição 2 – Trate o projeto de software da mesma maneira que um engenheiro trata o projeto de uma ponte

    Se você tratar o seu projeto como um projeto de software ele tem uma mínima chance de dar certo e isso é algo intolerável para a nossa missão. Faça diferente. Trate o seu projeto de software da mesma maneira que um engenheiro trata do projeto de uma ponte.

    Para fazer isso com sucesso auto-denomine-se “Engenheiro de Software”, além de pomposo (já que você não tem um diploma de engenharia, não estudou Cálculo I/II/III/IV e não está registrado no CREA) representa melhor o trabalho que você fará no seu projeto. Isso implica que você terá que abandonar o título de “Desenvolvedor” ou “Programador” porque esses dois caras aí são caras que “desenvolvem” e “programam” e isso é algo que não devemos fazer.

    Software como o nome diz é algo “soft” (maleável, leve, suave) se tratarmos ele dessa forma teremos sucesso no projeto e não é isso que a gente quer. Então trate o software como algo “hard” (estático, pesado, duro) e para nos certificarmos de que tudo dará certo (quer dizer, errado) trate ele como algo “hard” e “heavy” (pesado, grande). Para podermos ilustrar a nossa idéia:

    Trate o desenvolvimento de um projeto de software como se fosse o projeto de uma ponte.

    Nada mais estático e pesado do que uma ponte (o caso da ponte da BR-116 é uma excessão à essa regra).

    Engenheiros adoram usar coisas como PMI, Microsoft Project, Primavera, etc. Essas técnicas geram planilhas muito legais com cronogramas e tabelas cheias de informações facilmente geradas em uma planilha eletrônica convencional. Mas uma planilha eletrônica convencional não é reconhecida por PMIs e etc.

    Adote a metodologia mais pesada que você conseguir adotar. Uma dica: RUP. Sugira a aquisição da caríssima Suite Rose para que você possa passar dias (ou meses) desenhando quadradinhos e setinhas. Assim vocês terão vários diagramas sexys para mostrar para o cliente em substituição à código implementado e funcionando. A metodologia RUP também adora documentações que é o assunto da próxima lição.

    Lição 3 – Documente

    Ao gerar documentação você estará dando uma importante contribuição para o insucesso de nosso projeto. Mas quando eu falo de documentação eu não estou falando apenas de um ou outro documento “levemente útil” como um resumo e/ou esboço de um diagrama de classes ou diagrama de ER. Eu estou falando de páginas e mais páginas de especificações, cartões, diagramas UML em sua plenitude (status, use cases, …), planilhas de todos os tipos, manuais e todo o resto que sua imaginação permitir. Lembre-se:

    Enquanto você documenta você não implementa, logo, fracassa.

    A documentação é uma boa técnica para que nosso projeto falhe porque além de você perder tempo fazendo ela no começo do projeto você vai ter que perder esse tempo todo novamente no término do projeto para “atualizar” a documentação (coloquei “atualizar” entre aspas porque o “atualizar” alí significa jogar tudo fora e fazer novamente).

    Uma dica adicional nessa lição é: formato é muito mais importante que informação, portanto, deixe o designer que existe em você aflorar durante a confecção desta documentação.

    Lição 4 – Fale “buzzwordês”

    “O beans das business class precisam passar por um deploy” é uma frase que contribui muito mais que “As classes de negócio da camada model precisam ser entregues” para o insucesso do projeto, portanto, extenda seu buzzwordês. Alguns bons pontos de partida para isso é ir nos sites da IBM e Sun (principalmente a parte Java) e ler tudo que fizer referência à J2EE, e-business, etc. (dê uma olhada especial no produto Websphere da IBM… aquilo é um clássico do buzzwordês).

    Essa lição aparentemente não apresenta nada prático que contribua para o não-andamento do nosso projeto mas acredite ela é fundamental para que nosso projeto fracasse.

    Lição 5 – Adote o hype

    Eu tenho uma paixão especial por essa lição. É a que eu tenho mais prazer em ensinar. A minha definição de hype é: tudo aquilo que não tem nada de inovador e mesmo assim promete resolver todos os tipos de problema. Os hypes são criados por pessoas de marketing e pessoas de marketing raramente sabem desenvolver software, logo, se você ‘comprar’ tudo o que eles te vendem é grande a chance de que nosso projeto fracasse e nossa tarefa se torne um sucesso.

    Em alguns casos (raros) o hype pode até não ser a solução para todos os problemas, mas soluciona muito bem algum tipo muito específico de problema (mesmo que não tenha nada de inovador em sua idéia), portanto, cuidado com casos do tipo “vou usar XML para gravar uma informação estruturada num formato padrão texto” porque é exatamente pra isso que XML serve e nesse caso ele se torna útil (blerg!). Use XML em coisas para as quais ele não foi pensado: linguagem de query, linguagem de programação, arquivos pequenos de configuração, etc.

    Prefira usar o “Enterprise jXML .NET” para fazer o seu trabalho do que usar algo que seja simples e funcional. Coisas como J2EE, EJB, JMS, etc são ricos em hypes, portanto, fique antenado à essas coisas.

    Conclusão

    É isso aí, com o passar do tempo eu vou passando algumas lições do “Curso de Projetos Fracassados” para que você sempre esteja por dentro das boas práticas para o insucesso de seu projeto.