Livros para programadores

Foto: Yinghai

Os amigos que me conhecem sabem que adoro livros. Para o desespero da minha mulher e das finanças da casa tenho o mal hábito de comprar muito mais livros do que consigo ler.

Recentemente isso ficou pior porque comprei um leitor eletrônico que permite comprar coisas com um toque na tela. Pelo menos o problema com o espaço para os livros foi resolvido.

Programo computadores desde muito cedo mas nunca tive muito estudo formal sobre o assunto (não sou graduado) então sempre dependi dos amigos mais inteligentes e dos livros para aprender as coisas. Alguns foram bem importantes pra mim e esses eu vou listar aqui.

Os meus caminhos de aprendizado sempre foram tortuosos e fragmentados então a lista não tem nenhuma sequência que precise ser seguida. Estão na ordem como fui lembrando deles.

Os livros com links são os que ainda podem ser adquiridos pela internet e que eu ainda recomendo a aquisição por não estarem defasados.

Se você tem sugestões de livros que foram importantes para você, escreva nos comentários.

Revistas de Eletrônica

Ok, não são livros mas foram importantes para mim. Antes de programar eu brincava de montar circuitos eletrônicos. Como eu era muito novo (uns 9 anos) não me lembro de muitos detalhes de todas as revistas mas algumas foram especiais:

  • Experiências e Brincadeiras com Eletrônica Júnior – gostava tanto dela que ganhei uma assinatura do meu pai. Era publicada pela editora Saber e tinha projetos mais simples e menos “sérios”. Adequado para crianças.
  • Be-a-Bá da Eletrônica – Era formatada como mini-cursos e cada edição abordava um tópico específico. Trazia uma placa de circuito impresso para fazer a montagem do circuito principal.

Infelizmente nenhuma das duas é publicada mais. Acho que a revista que chega mais próximo delas é a Eletrônica Total da Editora Saber.

Aprendizado Inicial

Quando eu comecei a aprender a programar as únicas fontes de informação que eu tinha eram os 2 livros que acompanhavam meu computador e a revista Micro Sistemas. Numa segunda fase, também de aprendizado inicial, passei a estudar mais a fundo o universo dos IBM PCs e do MS-DOS.

Livros

manuais-msx

  • Basic MSX – da editora Aleph e editado pelo famoso Prof. Pier (Pierluigi Piazzi). Era uma referência com os comandos e funções da linguagem Basic que acompanhava meu computador.
  • Dominando o Expert – também da editora Aleph e editado pelo Prof. Pier. Era um curso introdutório de informática usando Basic. Foi com esse livro que digitei meus primeiros comandos num computador.
  • Clipper Summer’87 Cartão de Referência (Rubens Prates) – não é bem um livro mas aprendi o básico de Clipper.
  • Clipper Summer’87 vol. 1 e 2 (Antonio Geraldo da Rocha Vidal) – com esses livros aprendi a desenvolver aplicativos comerciais e, a partir desse ponto, passei a trabalhar com programação. Tinha 12 anos.
  • Algoritmos e Estrutura de Dados (Niklaus Wirth) – apesar do livro não usar Pascal nos exemplos de código eu conseguia facilmente adaptar o código para Pascal (do Turbo Pascal 4 até o Turbo Pascal 7). Foi quando aprendi a programar em Pascal. Na minha opinião, hoje, existem livros melhores sobre estruturas de dados e algorítmos.
  • C Completo e Total (Herbert Schildt) – eu aprendi a programar em C “sozinho” mas sonhava em comprar esse livro. Como não tinha dinheiro eu lembro de ir até a livraria para ler ele. Só depois de velho consegui verba pra comprar uma cópia dele pra mim.
  • MS-DOS Avançado (Ray Duncan) – livro que ensinava desenvolvimento de software para DOS em Assembly e C.

Como vocês notaram o meu aprendizado era focado mais em novas linguagens de programação e em programação “baixo nível” (única exceção foi o Clipper que usava para “pagar as contas”).

Revistas

microsistemas

  • Micro Sistemas – “A primeira revista brasileira de microcomputadores” era o subtítulo dessa revista. Ela vinha com reportagens e listagens de programas enviados pelos leitores (publicaram um artigo meu).
  • CPU MSX – Comprava algumas edições dessa revista também. Também tinha artigos interessantes sobre jogos e programação mais avançada (muito tempo digitando as listagens com códigos hexadecimais dos programas em linguagem de máquina).
  • INPUT – Coloquei em Revistas porque ela vinha em fascículos mas, depois de encadernadas, produziam 5 volumes enciclopédicos. Essa coleção, até hoje, é uma referência para questões didáticas. Sempre que preciso explicar alguma coisa para alguém recorro à essa coleção para ver qual a abordagem que eles usaram.
  • Microcomputador Curso Básico – 2 volumes da editora Globo (ou Rio Gráfica). Folheava tanto esses livros que eles acabaram gastos. Gostava da seção Raio X onde eles tiravam fotos de vários computadores abertos e apresentavam a ficha técnica deles. Ficava imaginando o dia que poderia ganhar alguns deles 🙂

Fase Unix/Linux

Eu me divertia horrores programando para essas máquinas com “tela preta”. Vocês não imaginam a minha tristeza quando todo mundo começou a usar interfaces gráficas (Windows).

A coisa legal é que, bem nessa época, eu fui até uma feira em São Paulo (Fenasoft) onde vi um troço chamado Unix. Era uma estação da Silicon Graphics no estande da Globo. Ela tinha interface gráfica mas você ainda tinha que digitar comandos num terminal texto para operá-la. Provavelmente era um Irix.

Como eu tinha um PC/Intel em casa eu não conseguiria rodar esses Unix que vi na feira e comprar uma máquina daquelas… fora de cogitação. Fiquei com aquilo martelando na minha cabeça até começar o meu estágio e operar um 386 com SCO Unix (e um outro com Xenix).

Vi que existiam Unix para PC/Intel mas todos eram caríssimos (não rolava piratear porque não davam acesso aos disquetes originais para um reles estagiário).

Quando reclamei sobre isso com um amigo que estudava computação no Ibilce ele mencionou um tal de Linux. Pedi para um “cara” na cidade que tinha internet rápida e um gravador de CDs gravar um CD com Linux pra mim e assim ele o fez. Era um Slackware.

Decidi aprender a usar aquilo e, para isso, precisava instalar mais um SO no meu computador que já tinha MS-DOS, Novell DOS 7, OS/2 (usado na minha BBS) e Windows 95/98.

Me dediquei com afinco (precisou porque apanhei muito) até aprender bem a usar aquilo.

Mas a coisa ficou séria mesmo quando, por uma dessas pegadinhas do destino, eu comecei a trabalhar na equipe de desenvolvimento do Conectiva Linux em Curitiba.

Quem me ajudou mesmo foram os colegas de trabalho… Mas alguns livros que li na Conectiva e, posteriormente, nos diversos outros lugares em que trabalhei ajudaram muito também.

Livros

  • Aprenda em 24 Horas Unix (Dave Taylor) – sério. Muita gente tem preconceito contra esse tipo de livro mas para um cara que já conhece várias coisas e precisa aprender a “se virar” com uma tecnologia nova eles são ótimos. É um livro para iniciar e te dar referências para aprofundar os estudos.
  • Maximum RPM (Edward C Bailey) – não é um livro sobre programação (fala sobre fazer pacotes RPM para RedHat) mas foi um divisor de águas. Nem é um livro bom mas aprendi a fazer build de vários software (e entender o ./configure; make; make install). Além disso ele é em inglês e, digamos, eu não lia nada em inglês na época. Precisava aprender a fazer pacotes RPM se quisesse garantir meu emprego na Conectiva. E garanti.
  • Expressões Regulares Uma abordagem divertida (Aurélio Jargas) – o Aurélio era meu colega de trabalho e aprendi Expressões Regulares com ele pessoalmente e não com o livro. Mas tudo que ele me ensinou está no livro também, logo, vou colocar ele nessa listagem. É impressionante a didática dele para ensinar um assunto tão complicado.
  • Shell Script Profissional (Aurélio Jargas) – o mesmo caso aqui. Eu não li o livro mas boa parte do que sei sobre programação shell eu aprendi com o Aurélio. O conteúdo que ele me passou está todo aí.
  • Instant Python (Magnus Lie Hetland) – ok, é só um artigo sobre Python e não um livro. Mas esse artigo (traduzido pelo meu chefe) me apresentou a linguagem Python e, desde então, essa tem sido a minha principal ferramenta de trabalho. Mesmo tendo aprendido novas linguagens e trabalhado com outras sempre volto a trabalhar com Python. Dá pra acreditar que aprendi Python com esse artigo?

Fase OOP – Object Oriented  Programming

Depois que saí da Conectiva tive um breve período como sysadmin em uma empresa e desenvolvia mais scripts do que aplicações “de verdade”.

Mas depois desse emprego circulei por diversos outros onde, de fato, voltei a programar computadores mais seriamente.

Como a maioria desses trabalhos usavam linguagens com paradigma orientado a objetos acabei focando em livros que tratem esse tipo de assunto.

  • Fundamentos do Desenho Orientado a Objetos com UML (Meilir Page-Jones) – esse livro foi responsável pela minha “iluminação” com relação ao desenho orientado a objetos. Até então eu só tinha feito código procedural (mesmo em Python!) porque não entendia muito sobre o OOP. O livro usa UML para ilustrar os assuntos mas ele usa UML como deve ser usada: apenas para ilustrar algum conceito. Nada do pedantismo da UML e seus vários tipos de gráficos. Infelizmente a versão traduzida desse livro saiu de circulação mas ainda é possível encontrar o original em inglês.
  • Padrões de Projeto ou Design Patterns (GoF) – a primeira parte desse livro é importante e tem várias idéias importantes para qualquer programador que queira usar OOP. A segunda parte é mais para referência mas tem alguns patterns bacanas.
  •  eXtreme Programming Explained (Kent Beck) – livro que me apresentou o conceito de programação “ágil”, testes automatizados, refatoração, etc. Não tem código mas os conceitos são extremamente poderosos. E o Kent Beck é um excelente autor. Tem uma capacidade invejável de explicar qualquer coisa de forma simples. Esse livro teve uma tradução muito boa mas parece ter saído de circulação.
  • Refactoring (Martin Fowler) – livro elabora melhor a idéia de refatoração citada no livro de XP do Kent Beck. O capítulo sobre “Bad Smells” (Mal cheiros) foi escrito pelo Kent Beck e é uma das melhores partes do livro. Esse livro teve uma tradução muito boa mas parece ter saído de circulação.
  • Pragmatic Programmer (David Thomas & Andrew Hunt) – ganhei esse livro de um amigo e, pra mim, ele é fundamental. Todo programador deveria ler. Fala sobre um monte de coisas. De ética à teste automatizado. De técnicas de depuração à linguagens de programação.
  • Code Complete 2nd ed. (Steve McConnell) – esse livro fala sobre assuntos muito similares aos que você encontrará no Pragmatic Programmer. A diferença é que neste livro ele usa uma abordagem menos “pragmática/ágil” e se aprofunda mais nos assuntos. É um livro bem mais denso. Li a versão original mas já existe tradução.
  • Test-Driven Development by Example (Kent Beck) – eu já tinha lido sobre TDD no livro de XP do Kent Beck mas não tinha a menor idéia do que ele estava falando. Já nesse livro aqui eu consegui compreender a mecânica da coisa. O Kent Beck tem o dom de escrever as coisas de forma simples e objetiva. Vale a pena. Li a versão em inglês mas já existe uma tradução.
  • Advanced Programming in the UNIX Environment (Richard Stevens) – esse livro não era meu e nem cheguei a ler ele inteiro. Para ser honesto eu li uns 2 ou 3 capítulos apenas. Mesmo assim tudo o que eu sei sobre sockets e sobre chamadas como fork/exec do Unix eu aprendi nesse livro. Esse tipo de conhecimento é superimportante para programadores e existem diversos livros que tratam dele. Esse do Stevens é o que mais me ajudou.
  • Introduction to Algorithms (Cormen, Leiserson, Rivest, Stein) – obviamente não li esse livro inteiro. Mas foi o livro que me ensinou o significado da notação big-O para eficiência de algoritmos. Ele ensina a calcular mas confesso que foi além da minha capacidade intelectual. O restante do livro pode ser lido mas me parece mais adequado para consulta e referência. Ele lista diversos tipos de algoritmos. Existe tradução.

Conclusão

Depois desses livros eu comecei a me interessar por outros assuntos voltados ao empreendedorismo que também foram muito importantes. Mas vou deixar isso para outro artigo.

Gostou desse artigo?

Assine a minha newsletter quinzenal e receba artigos sobre Programação, Python, Django, carreira e empreendedorismo.

[mc4wp_form]

O Melhor da Internet…

… pelo menos na minha opinião 🙂

Desde criança fui uma pessoa curiosa. Na época eu era chamado de “nerd” ou de “CDF” (pra ser nerd hoje basta gostar de Star Wars).

Procurar informações para satisfazer essa curiosidade nos anos 80 significava garimpar informações como um mineiro procura por ouro. A gente usava bancas de revistas, bibliotecas, sebos, livrarias, televisão, rádios, etc. E boa parte disso só estava disponível para quem podia pagar.

Mesmo sendo trabalhoso era bem divertido e gratificante buscar informações desse jeito. A gente ia atrás das informações e não o contrário.

Online: information overload

A primeira vez que consegui uma informação no mundo digital foi via BBS. Fiquei superfeliz por encontrar a lista de interrupções do Ralf Brown. Uma BBS tem toda informação que cabia em alguns CDROMs no computador do dono da BBS e isso já era muito mais do que um ser humano curioso podia lidar.

Quando tive contato com a Internet, com os buscadores (Cadê?, Yahoo!, …) e, posteriormente, com os feeds a coisa saiu do controle. Hoje eu tenho na minha lista de leitura uma quantidade de conteúdo suficiente para consumir durante toda minha vida. E a coisa não para de crescer.

Hoje eu assino vários feeds, sigo várias pessoas importantes, assino muitas newsletters e uma boa fila de livros para ler… o volume de informações que chega é absurdo e, por causa disso, precisei criar algumas regrinhas para melhorar a gestão disso tudo.

Gestão de consumo de informação

Uso três técnicas para me ajudar nessa tarefa: filtragem humana, fila de leitura, deduplicação e priorização por pares.

Filtragem humana

A quantidade de informação para ser consumida cresceu de modo tão intenso que precisei criar mecanismos para filtrar só aquilo que fosse relevante. E é aí que o bicho pega. Se antigamente era difícil garimpar informação na escassez o mesmo se mostrou muito mais difícil na abundância.

Alguns sistemas digitais até te ajudam nessa tarefa (ex. buscadores) mas nada supera os sistemas de “computação humana” (Human Computation).

Quando o Google Reader existia e tinha a funcionalidade de compartilhar artigos entre os seus usuários comecei a implementar um tipo de “filtragem humana”. Descadastrei de vários feeds que meus amigos acompanhavam e passei a ler apenas o que eles compartilhavam. Exemplo: eu assinava o feed do XKCD junto com outros colegas e eles viviam compartilhando as melhores tirinhas. Descadastrando o feed e seguindo esses colegas eu teria disponível só às melhores tirinhas e controlaria o aumento da minha fila de leitura.

Hoje eu faço isso com o Twitter. Dou follow em amigos que seguem sites importantes e sigo alguns sites importantes para compartilhar o conteúdo com eles. O interessante é que ninguém planejou isso. Foi algo criado organicamente.

Outra forma de filtragem humana é a “editorial”. Alguém muito bem informado ou comunidades se reunem para determinar a relevância das informações.

Os sites que melhor trabalham isso em comunidades são os agregadores (ex. Hacker News, Reddit, …) e sites de QA (ex. StackOverflow, Quora, …). Já as pessoas que fazem esse trabalho de forma individualizada, geralmente, usam blogs ou newsletters.

Fila de leitura

Não dá pra parar para ler todas as coisas interessantes que aparecem no momento que elas aparecem. Digo mais: não dá nem para avaliar se essas coisas são importantes ou não. Então eu faço o básico: mando o link direto para minha fila de leitura.

Uso o Instapaper como ferramenta para gerenciar essa fila mas também tem o Pocket. Acho que o Pocket é tecnicamente melhor mas o importador deles é uma porcaria e como o Instapaper me atende satisfatoriamente continuo com eles.

Tenho Instapaper no celular e usava a funcionalidade de exportar para Kindle (parei porque o kindle também já está entupido de coisas pra ler).

A tática para manter a lista de leitura em um tamanho administrável é: algumas vezes por semana tenho como objetivo ler mais artigos do que adicionei desde a última pausa para leitura.

Nem sempre é possível cumprir essa meta e o resultado é que tenho aproximadamente 400 artigos para ler.

Deduplicação

Se percebo que uma fonte de informação está só duplicando informações que já chegaram até mim por outras vias eu reavalio esse canal. Se o nível de duplicação que ele acrescenta é grande eu simplesmente desligo esse canal.

Priorização por pares

Eu priorizo a leitura do conteúdo pelo número de pares (pessoas) que recomendaram o mesmo material. Exemplo: três amigos cuja opinião literária eu respeito muito indicaram o mesmo livro (ebook). Isso e o fato de que Ridley Scott está fazendo um filme com a história desse livro obviamente fizeram eu comprá-lo e começar a ler.

O Melhor da Internet

Com esse método acabo esbarrando em coisas muito interessantes que acabo compartilhando no meu Twitter. Mas para facilitar a vida das pessoas resolvi criar uma newsletter: O Melhor da Internet.

Agora você pode me usar como filtro humano 😀

Quinzenalmente farei um resumo contendo links e uma breve descrição dos melhores artigos e sites que vi desde que comecei a usar a internet. Vou priorizar material recente mas posso incluir coisas antigas que sejam relevantes. Esse resumo será enviado para o email dos assinantes que se cadastrarem no formulário abaixo.

Os tópicos principais serão:

  • Programação – Python, Django, Web, TDD, …
  • Empreendedorismo – side-project, bootstrap, lifestyle business, passive-income-de-verdade, …
  • Carreira – produtividade, boas práticas profissionais, …
  • “Off-topic” – DIY/eletrônica, retrocomputação, jogos, tirinhas, …

[mc4wp_form]

O plano é publicar a primeira edição sai no dia 21 de julho de 2014.

Foto: lecates

“Ondas” tecnológicas

Já é de conhecimento de todos que trabalho com computação já faz muito tempo. Vi muitas ondas passarem.

Quando comecei com BASIC em máquinas de 8bits vi a primeira onda chegar… eram as linguagens estruturadas. Linguagens “procedurais”… código sem GOTO, etc. Quem programava só em BASIC e não conhecia esse “novo paradigma” (aspas propositais) estava fadado ao fracasso e ao ostracismo. Não mereceriam ser chamados de programadores.

Parti para a luta e fui aprender Pascal, C e/ou Clipper.

Ondas tecnológicas

Assim que dominei esse “novo paradigma” avistei outra “nova onda”: Programação Orientada a Objetos.

Essa foi uma das primeiras grandes ondas que contaram com apoio de Marketing de grandes empresas. Lembro-me de ler sobre “orientação a objetos” até em jornais de bairro (hipérbole detectada!). E mais uma vez fui convencido de que se não dominasse esse “novo paradigma” eu estaria fadado ao esquecimento.

Então comecei a estudar o assunto e no meu entendimento inicial (e duradouro) uma “classe” nada mais era do que um “struct” (ou RECORD) com esteróides. Ainda existem registros da minha primeira incursão nesse novo mundo. Eu só fui entender melhor OOP recentemente.

Outra onda que “bombou” mais ou menos na mesma época era a dos bancos de dados relacionais (SQL e afins). Mas eu não tinha muito contato com esse mundo que era restrito às “elite$”.

E com OOP e SQL essas empresas de marketing que, eventualmente, produziam software começaram a ganhar rios de dinheiro vendendo “gelo para esquimó”.

A tecnologia dos computadores surgiu para empresários em mensagens como: “Se sua empresa não usar OOP ou SQL você (perderá dinheiro|será devorado pela concorrência|será feio e bobo).” — (IBM|Gartner|Oracle)

Os empresários eram completamente ignorantes sobre esses assuntos e, num ato de fé, acreditavam em tudo o que as “IBMs” diziam. Afinal elas ficaram grandes e ricas por lidar com tecnologia, certo? Conseguem detectar a falha primordial nesse raciocínio?

Esse tipo de mensagem juntamente com a outra que dizia: “ninguém é demitido por ter escolhido IBM” fez muito bem para o lucro dessas empresas.

Naquela época isso fazia sentido, afinal, os computadores não faziam parte da vida de todo mundo. Tecnologia e mágica eram sinônimos. Mas hoje isso não deveria mais ser assim. A fábrica de “hypes” continua funcionando e os empresários e profissionais da área continuam investindo em tecnologias “da moda” apenas por estarem na moda.

Outras “ondas” movimentaram e ainda movimentam o mercado e com certeza não lembrei de todas: OpenSource, Java (J2EE), XML, NoSQL, Cloud, metodologias de desenvolvimento (ágeis ou não), Big Data, Internet of Things, Frontend/Backend engineering, etc.

Para cada uma delas temos defensores (não é uma onda! é realidade! olha só isso aqui!) e detratores (isso é hype! isso não funciona!). Ambos estão certos e errados.

Meus amigos devem estar pensando: “Mas ele é o cara que mais compra esses hypes! Andava com uma camiseta ‘você ainda usa banco de dados?’!”.

Eu, de fato, acompanho todas essas ondas. Cada uma delas acrescenta algo na minha caixa de ferramentas. Sempre que vejo uma delas chegando já vou logo dando uma chance pra elas se provarem úteis. Mas isso não significa que vou adotá-las em tudo o que farei e que as coisas antigas são lixo.

É o velho clichê: existem ferramentas certas para resolver cada tipo de problema.

Resolvi escrever isso pra que vocês possam refletir sobre a adoção racional de tecnologias. Usar critérios técnicos para a escolha e não ficar pegando jacaré em qualquer onda que aparece.

Outra coisa importante é não parecer bobo falando que você faz “Big Data” pra um cara que já processava toneladas de dados antes mesmo de terem cunhado essa expressão. Ou falar que usa NoSQL pra um cara que já usava Caché (kind of OODBMS), LDAP (hierárquico), ou Isis (schemaless).

Como vivi todas essas ondas eu saco logo que esses caras são mais “gogó” do que outra coisa.

Mantenham o foco em criar coisas boas que resolvam problemas importantes e escolham tecnologia usando critérios técnicos.

Ser proficiente numa linguagem é um critério muito importante mas ele deve ser considerado em conjunto com outros critérios (robustes, disponibilidade de recursos, etc).

Dia desses vi um anúncio procurando programador Clipper e pensei: esse contratante deve ter um excelente software. Deve ser um software tão bom e deve resolver problemas tão importantes que ele resistiu à várias ondas e não virou areia.

Code Review e a Teoria das Janelas Quebradas

Foto: Jeffrey Pott

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

Foto: Zack Smith

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 vc usa um teclado com acentuação deadkeys você precisarar 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 

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.

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

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.

Meu ambiente de trabalho em 7 items

Fui convocado pelo @franciscosouza para listar 7 ítens do meu ambiente de trabalho… então aqui vai…

1. Triveos Tecnologia

A Triveos é a minha empresa e tenho, como sócio técnico, o pythonista Marcos Petry. Não chega a ser uma “equipe” mas já dá pra fazer um bom estrago ;D

Aqui na Triveos a gente não tem preconceito contra nenhuma tecnologia (nosso site e blog rodam em PHP e somos membros do Microsoft Bizspark).

Eu particularmente tenho algumas “birras” com alguns softwares que já me torturaram no passado: Trac, Nagios, Squirrelmail, Bazaar, etc.

2. Git, Github, Codebase

Nós gostamos muito de usar DVCS e adoramos o Github para hospedar nossos (poucos :/) projetos open-source. Gostamos das ferramentas de apoio à criação de grupos de desenvolvedores que eles oferecem.

Usamos o Git por conta do Github. Mas usaríamos Mercurial se o Bitbucket fosse tão bom quanto.

No nosso dia-a-dia a gente lida com projetos de clientes e/ou de código fechado e para esses projetos nós achamos melhor procurar um local mais “tranquilo” pra hospedá-los. Sem o oba-oba, “excessos” do Github e com um suporte mais “rápido” à eventuais problemas.

Então contratamos um plano no Codebase. Lá eles oferecem hospedagem de código com repositórios Git, Mercurial ou SVN. Também disponibilizam um sistema de Tickets/Milestones e Wikis para projetos.

3. Tecla, Webfaction e Linode

Para hospedagem em produção preferimos usar o Linode (e eu indico a todos que querem um serviço simples e de qualidade).

Mas em alguns projetos (leia-se Ludeos) temos algumas exigências relacionadas a comprovação de gastos. Nesses casos usamos o Cloud da Tecla. Eles são melhores que a Locaweb mas ainda são infinitamente inferiores a qualquer hosting similar nos EUA. E nem estou falando de preço (o pior deles, IMHO, é o sistema de cobrança).

Os sites mais simples (e blogs) ficam numa conta compartilhada simples no Webfaction. Para colocar um WordPress “no ar” com poucos cliques é uma baita ferramenta. Mas costumo notar certa lentidão no acesso às páginas de tempos em tempos.

4. Vim, Textmate e nada de IDEs

Uso principalmente o Vim mas me viro bem com o Textmate também. Dependendo da minha “vibe” eu uso um ou outro.

Recentemente passei a usar o MacVim mas ainda não estou me dando muito bem com ele. Vou insistir mais um pouco pra ver se me acostumo.

Nossos funcionários usam Eclipse+PyDev… engraçado isso… 😀

5. Python (… JS, Ruby, C, Shell Script, Java, PHP, …)

Aqui na Triveos é assim: se a bola foi lançada a gente mata ela no peito e chuta pro gol! 😀

É claro que a gente seria mais feliz usando só Python, mas não dá pra fazer isso sempre.

Para desenvolvimento web nós usamos Django mas já namoramos o Flask, o Repoze.BFG (Pyramid), e diversos outros frameworks web feitos em Python.

Já usamos jQuery (apesar de eu não gostar dessa biblioteca) e estou estudando YUI3 seriamente a algum tempo.

6. OS X e Ubuntu Linux

OS X pra criar e Ubuntu pra produzir em massa. 😀

Já usamos CentOS e Debian em ambientes de produção mas eu detesto lidar com software velho. Então adotamos uma alternativa mais “arriscada”? E usamos a última versão de Ubuntu disponível. Procuro sempre atualizar os ambientes de produção.

Instanciamos uma máquina com o Ubuntu mais novo “nas nuvens”, rodamos um comando de setup/deployment e pimba! servidor novo, com software atualizado e rodando…

…mas isso só é possível em projetos onde usamos “Continuous Deployment”… e ainda estamos aprendendo a fazer isso direito. Quando estivermos “fera” nisso pretendo escrever sobre o assunto aqui.

Mas se alguém quiser uma idéia do que planejo pode ver no artigo Python deployment tips do Lorenzo Gil.

7. Ambiente

Uso o Terminal.app (mas tenho planos de experimentar o iTerm) com bash configurado para modo vi no prompt.

Uso:

  • Marinho Brandão (@marinhobrandao)
  • Arthur Furlan (@arthurfurlan)
  • Marcos Petry (@petry)

… descreverem seus ambientes 😀

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.

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.

Tutorial Linux – Parte 6

Tutorial Linux

Esse tutorial é dividido em várias partes e elas podem ser vistas na página Tutorial Linux.

Capítulo 6. Expressões regulares

Expressões regulares são uma maneira fácil de manipular texto de maneira concisa e rápida, e folgamos em dizer que elas são o recurso mais subutilizado de qualquer sistema que o suporte justamente porque a maioria das pessoas não se preocupa em aprendê-los. Expressões regulares têm um visual pouco convidativo, mas são simples de aprender e economizam muito tempo ao serem usadas. É possível economizar rotinas inteiras de shell script e loops complicados conhecendo apenas alguns poucos metacaracteres e construindo expressões regulares. Expressões regulares são suportadas por praticamente todos os programas Unix que suportam manipulação de texto, como vi, grep e sed. O próprio shell tem seus globs, que nada mais são que expressões regulares simplificadas. Linguagens de programação como awk, Perl e JavaScript trazem expressões regulares como parte da linguagem, outras linguagens como Python colocam a manipulação de expressões regulares na biblioteca padrão e ainda existem bibliotecas de manipulação de ERs para praticamente todas as linguagens de programação sérias, e também para algumas não sérias.

Isso dito, é necessário apenas um certo pensamento programático e conhecer os metacaracteres que fazem uma ER para poder usar esse recurso de forma eficiente. Um programador que não conhece ER pode, por exemplo, fazer um loop que busca várias variantes de uma palavra, ou ainda que analisa uma certa palavra caractere por caractere. O que ele está fazendo quando realiza esse tipo de operação é nada mais que reimplementando algo que já está implementado de forma menos genérica, mais lenta e mais trabalhosa.

A primeira parte do uso de expressões regulares é saber como encontrar texto. A partir disso, é possível realizar substituções simples, e com o uso de agrupamentos de partes da expressão, partir para substituições mais complicadas.

Vamos ver um exemplo simples: temos uma lista de pessoas, e no começo dessa lista queremos acrescentar o tratamento “Sr.”. Eis a lista:

A. Antônio
J. da Silva
V. Corleone
M. Andrade
H. Lekter

Sistematicamente, o que temos que fazer é acrescentar “Sr. ” ao começo de cada linha (com um espaço após o “Sr.”) para ter a lista que desejamos. Nenhuma substituição simples faria isso, é necessário usar metacaracteres das expressões regulares.

O primeiro metacaractere a ser usado é o ^, que significa “começo de linha- qualquer semelhança com o comando do vi para começo de linha não é coinscidência. Nossa expressão fica assim:

s/^/Sr. /

Para executar no vi, digite % e a expressão acima, lembrando que o % significa “em todo o arquivo”.

Isso quer dizer “substitua o começo da linha por ‘Sr. ‘ťť. O resultado é o desejado.

Outros exemplos típicos de ERs seriam “acrescente ‘Sra.’ na frente de nomes que terminam com ‘a'”, “coloque determinada string no fim da linha”, “substituia a ordem nome-sobrenome por sobrenome-nome”, etc. Para fazer esse tipo de tarefa, basta ter pensamento programático e conhecer os metacaracteres apresentados abaixo.

6.1. Metacaracteres

  • . O metacaractere . casa com qualquer um caracter, seja pontuação, alfanumérico, espaço, tab, etc.
  • * Encontra zero ou mais vezes o caractere anterior. Por exemplo, ba* casa com b, ba, baa, baaa, baaaa.
  • [] Um grupo de caracteres. Por exemplo, mal[au] casa com “mala”e “malu”. Também é possível especificar “ranges”, como [a-f], que casa com qualquer letra entre a e f, e [a-fA-F], que casa também com maiúsculas.
  • ^ Começo de linha. Adicionalmente, dentro de colchetes, ganha um significado completamente diferente: negação. Assim, [^a-f] significa “qualquer caractere exceto as letras de a até f.
  • $ Fim de linha
  • Escape. Se quiser casar o texto com um cifrão literal, use $ (caso contrário, você estará procurando um fim de linha.

Esse é o básico de expressões regulares, e isso é suportado em programas como o vi, o sed, o awk, o grep, perl e muitos outros. Alguns programas fazem uso de expressões regulares extendidas, como o egrep e o vi e o perl, que contém alguns metacaracteres úteis mas não essenciais.

6.2. Exemplos

AVISO: Como avisamos anteriormente, esta parte está incompleta!

6.3. sed e expressões regulares

O sed é um editor de texto de linha de comando baseado em expressões regulares muito útil para automatizar tarefas. A maneira básica de usá-lo é assim:

sed 'expressão-regular' arquivo

A saída do comando é enviada para a saída padrão. Como expressões regulares têm muitos caracteres que conflitam com os metacaracteres do shell, nós incluímos a expressão regular, por conservadorismo, sempre entre aspas simples.

Ao contrário do vi, o sed assume que todas as expressões devem ser realizadas em todas as linhas por padrão, então não é necessário especificar o caractere % antes da expressão.

6.4. Grupos

Uma operação muito comum é buscar algum texto desconhecido e depois realizar operações sobre esse texto. Vejamos o exemplo de uma lista de nomes:

Silva, José
Asdrubal, Antônio
Reis, Márcia

Como podemos usar uma expressão regular que inverte a ordem “Sobrenome, Nome”para “Nome Sobrenome”? Nós precisamos, em primeiro lugar, aprender a usar grupos.

Grupos são uma forma de preservar o texto casado para uso posterior em um comando de substituição. Por exemplo, nós precisamos criar uma ER que case com o sobrenome e coloque esse valor em um grupo e também case com o nome e coloque esse valor em outro grupo. Precisamos, em seguida, inverter a representação desses grupos para termos a ordem direta que precisamos.

A marcação desses grupos é feita colocando os trecos das ERs entre (). Porém, em uma ER, () têm seus próprios valores, então precisamos escapar os parênteses com uma barra invertida. Vejamos um exemplo de ER com marcação:

s/([a-z]*)[1-3]*/1/

Essa ER marca a primeira parte da expressão ([a-z]*]) em um grupo, casa com um número de 1 a 3 nenhuma ou mais vezes e depois troca isso tudo pela primeira parte apenas do grupo (na prática, eliminando os números). Note que o primeiro grupo marcado vira1, o segundo vira 2, etc.

Então como fazemos para inverter a ordem “Sobrenome, Nome”? Temos que casar o texto alfabética até a vírgula, sem incluir a vírgula, e botar em um grupo. Logo em seguida, devemos casar com qualquer texto alfabético e botar em um outro grupo. A ER fica assim:

s/([A-Za-z]*), ([A-Za-z]*)/2 1/

6.5. Classes de caracteres POSIX

Se você rodar a maioria dos exemplos acima com uma massa de dados maior, vai notar que as ERs falham quando encontram caracteres acentuadas. Isso acontece porque [a-z] [A-Z] casam apenas com caracteres não acentuados.

Para facilitar o gerenciamento de caracteres especiais, foram criadas classes de caracteres que contém uma série de caracteres de um tipo. Note, porém. que algumas características de caracteres são dependentes de fatores culturais. “á” é um caractere acentuado para um brasileiro, mas nem sequer existe para um inglês, então o funcionamento das classes de caracteres depende da configuração de localização do sistema operacional.

As classes de caracteres POSIX mais comuns são:

  • [:alnum:] – Caracteres alfanuméricos
  • [:alpha:] – Letras
  • [:upper:] – Letras maiúsculas
  • [:lower:] – Letras minúsculas
  • [:digit:] – Números decimais
  • [:space:] – Caracteres de espaçamento
  • [:punct:] – Pontuação

O funcionamento de uma classe de caractere POSIX é o mesmo de um grupo como “a-z”. Logo, uma expressão regular que casa com uma letra maiúscula qualquer poderia ser:

[[:upper:]]

O classe POSIX foi incluída entre colchetes pelo mesmo motivo que uma sequência como “a-z”seria: os colchetes marcam um grupo. Note que a classe POSIX já inclui colchetes, mas isso não elimina a necessidade de usar colchetes para marcar as classes!

Note, porém, que o funcionamento de classes POSIX está sujeito às configurações de seu sistema operacional. Esse é um ponto que o administrador de sistemas deve ajudar você. Note que nem todo sistema Unix suporta as características de internacionalização do português, então o uso de classes POSIX deve ser feito com muito cuidado.

6.6. Palavras finais

Expressões regulares são uma maneira muito prática de validar e manipular texto, mas por seu visual estranho acabam por ser pouco aprendidas e pouco usadas. Mas a “linguagem” expressões regulares é prática e e fácil de aprender, mesmo que aparente ser algo estranho. A manipulação de texto usando expressões regulares economiza tempo e linhas de código ineficientes, e quando bem usadas, facilitam a legibilidade do programa.

Tutorial Linux – Parte 5

Tutorial Linux

Esse tutorial é dividido em várias partes e elas podem ser vistas na página Tutorial Linux.

Capítulo 5. Assuntos a considerar

5.1. Segurança

O desenvolvimento de shell scripts requer cuidados extremados com segurança. Em qualquer ambiente profissional, segurança é um assunto importante e que deve ser levado em conta. É comum ouvir argumentos como “não precisamos nos preocupar com isso, temos um firewallťť ou coisa parecida, mas esses são os argumentos mais perigosos. Um firewall dá sempre uma falsa sensação de segurança. Um firewall não protege, por exemplo, a rede interna de uma empresa de seus próprios funcionários – a principal origem de ataques contra a estrutura de TI de qualquer empresa. Além disso, mesmo que ataques não ocorram, boa parte dos furos de segurança são bugs que podem causar interrupção de serviço. Manter uma disciplina de escrever scripts seguros, além de ser uma boa política de segurança, é uma boa disciplina que certamente evitará horas de manutenção desnecessária no futuro.

Vamos ver alguns dos problemas mais comuns de segurança com relação a shell scripts.

5.2. Permissões excessivamente permissivas

Ao criar arquivos, verifique se eles realmente devem ser lidos por todo o sistema. Use o umask para setar restrições maiores. Não deixe informação visível a não ser que seja estritamente necessário.

Por outro lado, não tente proteger seu script sendo obscuro e pedante. Boa segurança é aquela que, mesmo exposta para o todo o mundo, ainda é robusta. Não baseie sua estratégia de segurança no desconhecimento da maneira como ela funciona. E mantenha o usuário em perspectiva. Manter arquivos sem permissões de leitura para o sistema todo é uma boa política, mas se isso for necessário para o funcionamento do ambiente de produção, não exite em relaxar a permissão (mas analise as alternativas, como criar grupos de pessoas que podem acessar tais logs).

5.3. Race conditions

Uma maneira muito comum de ataque é se aproveitar da criação de arquivos temporários. Com uma pequena manipulação do ambiente, é possível que um usuário não autorizado crie um link simbólico no lugar de algum arquivo temporário usado por um script (ou por qualquer programa). Como as ações praticadas em um link se refletem no arquivo para o qual ele aponta, é possível sobrescrever e mudar permissões de arquivos importantes do sistema.

A maneira mais simples de evitar isso é não usar um diretório temporário compartilhado como o /tmp. Crie um diretório apenas para o usuário, como $HOME/tmp. Sete as variáveis $TMP e $TMPDIR para esse diretório. Se for necessário usar um diretório compartilhado, use o mktemp, que cria nomes de arquivos não predizíveis de maneira segura. Evite construções que usam $$, já que a variável de ambiente $$ (sinônimo do PID do shell) é facilmente predizível na maioria dos sistemas UNIX.

5.4. Dados importantes aparecendo como parâmetros

Cuidado com o conteúdo de dados importantes. Em primeiro lugar, dados como senhas devem estar protegidos no sistema de arquivos por permissões apropriadas (o ideal seria não armazenar senhas em disco). Ao manipular senhas, lembre-se que elas ficam em memória e podem ser bisbilhotadas por outros programas. Nunca passe senhas por parâmetros – eles aparecem na listagem do ps. Use pipes.

5.5. Verifique o $PATH e o $IFS

As variáveis de ambiente $PATH e $IFS são muito importantes. Modificar seus conteúdos pode quebrar o funcionamento do script de muitas maneiras. Sete manualmente e sempre o valor dessas variáveis no topo do script. Uma pessoa que mude um PATH, por exemplo, pode fazer com que um dado importante (como uma senha, ou dados confidenciais) sejam enviados para os programas errados, possivelmente programas que essa pessoa mesmo manipula. Adicionalmente, nunca ponha o diretório atual (.) no PATH.

5.6. Evite scripts setuid

A melhor recomendação com relação a scripts setuid é “não use”. Há inúmeros problemas que podem ser usados para conseguir acesso ao usuário dono do script. Se necessário, use um wrapper compilado ou utilize o sudo. Cada sistema UNIX requer cuidado específico quanto à segurança de shell scripts setuid. Consulte o manual de seu sistema operacional caso precise utilizar esse recurso. Nem todo sistema UNIX suporta shell scripts setuid, então esse é o tipo de solução não portável.

5.7. Favoreça portabilidade

A plataforma pode mudar de um dia para o outro. De maneira geral, escrever scripts já é uma solução portável, mas requer cuidados para não usar recursos específicos de uma determinada plataforma. Mesmo que a mudança de plataforma não pareça algo que possa acontecer, lembre-se que o problema do ano 2000 também parecia não ser algo que pudesse acontecer.

5.8. scripts frágeis

Scripts frágeis podem, em cascata, quebrar o ambiente de produção. Verifique sempre o código de retorno dos comandos para ter certeza de que eles funcionaram como esperado. Ao fazer comparações usando [, sempre inclua as variáveis sendo comparadas entre aspas. Valide dados. Se não é necessário expandir uma variável dentro de uma string, use aspas simples ao invés de duplas. Espere que a entrada de seus programas seja algo extremamente variável (seja bastante permissivo com o que pode entrar) e gere uma saída rigidamente definida.

5.9. Escreva código reutilizável

Sempre que possível, evite fazer código específico para um problema. Gastar um pouquinho de tempo a mais com código reutilizável pode salvar horas de esforço posterior. Além disso, centralizando as principais funções de seu script em torno de código reutilizado significa que há apenas um lugar para consertar ao invés de dezenas de lugares. É possível evoluir e tornar mais robusta uma função reutilizável e ter impactos positivos em todo o sistema.

Por exemplo, se você está escrevendo uma função que lida com logs, possivelmente vai precisar de fazer um processamento de datas. Essas funções de processamento de datas podem ser utilizadas por analisadores de um tipo de log diferente, ou ainda por programas que não são analisadores de log. Criar uma biblioteca de funções de datas e incluí-las nos demais scripts é bem mais inteligente que reescrever as funções diversas vezes.

5.10. KISS

Keep It Simple, Stupid. Não tente escrever aplicações inteiras com shell scripts. Escreva pequenos scripts que realizam operações específicas e que você pode garantir que funcionam bem (essa é uma forma bastante prática de reutilizar código!) e conecte esses utilitários.

Além disso, dentro de scripts, não use estratégias mirabolantes. Use expressões regulares com cuidado, favoreça legibilidade.

5.11. Comente seus scripts

Escreva comentários. Diga para que servem as funções, mas não explique o óbvio. Comentários devem ser escritos para pessoas que conhecem a linguagem, então não é necessário explicar que FOO=bar seta a variável FOO com valor bar. Se o código é muito longo ou complicado, um comentário explicando o que ele faz (não como ele faz) é bem vindo.

5.12. Marque áreas que devem ser consertadas

Algumas vezes, é necessário usar uma solução frágil para resolver rapidamente um problema. Porém, é necessário marcar essa solução para que depois possa ser revisada com mais calma. Ponha um comentário com o texto FIXME e uma explicação do que deve ser consertado. Alguns editores de texto destacam com cores diferentes o texto FIXME, e usar uma string padronizada torna fácil encontrar os pontos em que o script deve ser consertado.

5.13. Indente o código

“Indentar” o código é fundamental para facilitar a compreensão. Separe os blocos de comandos e indente-os de acordo.

5.14. Use variáveis descritivas (mas não muito)

Variáveis de ambiente devem ter um nome minimamente descritivo de sua função. Uma boa variável para conter um nome, por exemplo, é $nome. Não chame essa variável de $n, que seria uma maneira de tornar o programa mais críptico, mas também não chegue no extremo de chamar de $nomedapessoaretiradodatabelatal. Variáveis com nomes muito descritivos são normalmente muletas para um programa excessivamente complicado.

Continua…