3 coisas que aprendi ao trabalhar com Cloud Native Applications

A internet é uma novidade que veio pra ficar, mesmo. Trabalhar com uma arquitetura nativa na nuvem é bem diferente, mesmo já trabalhando com microserviços.

Nos últimos 6 meses eu trabalhei em um projeto bem legal de construção de uma plataforma digital para criação de APIs e aplicações com arquitetura nativa na nuvem e nesse post vou compartilhar 3 coisas que aprendi e que podem ser úteis pra quem estar indo por esse mesmo caminho.

Se você gostar desse post, avisa lá no Twitter ou deixa um comentário que eu escrevo outros entrando em mais detalhes em cada um desses conceitos.

A sua pipeline não será mais a mesma

Aquela ideia de clonar o repositório e rodar os testes muda um pouco já que provavelmente as aplicações serão encapsuladas dentro de um container. Ao invés de clonar o repo você deve baixar a imagem e rodar os testes dentro de um container, o que ajuda a reduzir as diferenças entre ambientes.

Mas já passei por algumas dores de cabeça quando os testes passavam localmente, mas falhavam na pipeline (ambos rodando dentro de um container). Ao lidar com Docker é preciso estar atento as tags das imagens, volumes e imagens antigas que possam “poluir” e criar diferenças entre a pipeline e seu ambiente.

Ah, provavelmente o feedback será um pouco mais lento, pois clonar o repositório é bem mais rápido que baixar uma imagem e rodar um container. Mas no final das contas, tomando cuidado com tags pra reutilizar imagens em cada passo, a diferença não será algo que vá causar dor de cabeça.

Você não *precisa* saber onde as coisas estão

O Rails te proporciona maneiras legais de acessar os diferentes ambientes a partir da sua própria máquina, basta definir uma variável de ambiente e as configurações apropriadas serão carregadas. Mas num projeto com arquitetura nativa na nuvem não é tão simples encontrar onde o servidor está rodando e, mesmo que encontre, não dá pra confiar que vai ficar lá por muito tempo.

O mundo de ops convergiu para a ideia de servidores efêmeros que são fáceis de subir, ao invés de servidores resilientes que não caem. Numa arquitetura de nuvem isso é levado ao extremo, os vendors (Amazon, Google etc) cuidam da distribuição de carga pra você e conseguem prover um nível mais alto de segurança, mas isso quer dizer que você fica com um pouco menos de controle (o que na verdade é bom!).

Lógico que é possível acessar um servidor, mas você precisa fazer isso utilizando as CLIs dos vendors (o que te dá mais segurança) e não pelo Rails. Pra consumir uma API por exemplo, utilizamos recursos de balanceamento de carga do Google Cloud Platform e ele iria levar a requisição para o servidor. E eu não precisava saber qual o IP do servidor em si, apenas do balanceador de carga.

O mesmo vale para o banco de dados, junto a imagem do servidor nós subíamos um proxy, do próprio Google Cloud, para conectar ao banco (que também é um recurso do Google Cloud). Na prática a aplicação Rails funcionava como se o banco estivesse rodando junto com ela, no localhost (mesmo em produção), e de novo eu não precisava saber aonde o banco estava.

Essa “falta de controle” na verdade quer dizer menos dor de cabeça pra você pois esses problemas já estarão resolvidos. Além disso os vendors provavelmente conseguem prover um nível de segurança bem mais alto, já que tem um time dedicado a isso.

Kubernetes é uma excelente ferramenta

Nesse projeto utilizamos o Google Kubernetes Engine do Google Cloud para gerenciar um cluster Kubernetes onde os containers Docker estariam rodando. O que eu mais curti do Kubernetes é que ele provê um balanço muito bom entre a quantidade de conhecimento de infra que eu preciso ter como um desenvolvedor.

Ter que fazer configurações usando yaml é bem ruim, mas não é um grande problema (e existem outras alternativas também) e no geral aprender Kubernetes foi uma boa experiência, existe muita documentação e a comunidade é bem ativa. Dá até pra rodar tudo localmente com o minikube.

Entender os conceitos básicos de Node, Pod etc do Kubernetes não é tão complexo e, uma vez que entra na sua cabeça, é bem fácil criar e configurar o cluster. Além disso as facilidades que o Kubernetes provê, desde rolling deploys até escalabilidade, tiram bastante preocupações da cabeça de quem desenvolve (e de quem mantém também).

Não é a toa que no último Radar de Tecnologia da ThoughtWorks apareceram tantos blips relacionados a Kubernetes e foi um dos temas do radar.

Bônus: Além dos microserviços

Utilizar o Kubernetes pra orquestrar containers facilitou bastante a nossa vida, mas ainda tinham problemas que precisavam ser resolvidos, por exemplo: todas as APIs estão expostas na internet, então temos que evitar uma série de problemas ao receber requisições.

Utilizamos duas soluções pra reduzir duplicações na plataforma como um todo: a primeira foi utilizar o Kong API Gateway pra facilitar a vida de quem quer consumir essas APIs e a segunda foi criar sidecars para lidar essas questões de segurança e facilitar a vida de quem desenvolve.

Além disso, a ideia de service mesh pra facilitar a comunicação entre os serviços da plataforma (sem contato com o mundo externo) também parece interessante, além de já existirem vários produtos no ecossistema Kubernetes.

Anúncios

Refatoração não é só mudar o código!

Você provavelmente já ouviu alguém falando em refatoração, mas será que a pessoa realmente estava falando de refatoração? Qual a diferença entre refatorar e apenas modificar o código? Se quiser ir mais afundo no tema e entender como Padrões de Projeto podem ajudar, leia o livro Refatorando com Padrões de Projeto.

É importante entender a diferença, apesar de parecer algo mais conceitual, pois a mentalidade ao refatorar o código é diferente de quando implementamos uma funcionalidade nova ou estamos corrigindo um problema.

Ao refatorar buscamos maneiras de (1) melhorar o design existente, (2) aplicando mudanças em pequenos passos e (3) evitando deixar o sistema quebrado. Vamos falar sobre essas três partes em mais detalhes.

Ao buscar maneiras de melhorar o design existente devemos pensar em novas soluções que podem melhorar o código (um pensamento bem diferente de quando estamos corrigindo um bug).

A segunda parte é muito importante pois ao aplicar mudanças em pequenos passos garantimos a consistência e ritmo de desenvolvimento. Quanto menos mudanças fizermos mais fácil é evitar problemas.

E a terceira parte é bem ligada a segunda pois, ao fazer mudanças pequenas, podemos executar todos os testes e validar o que está sendo feito. Se o tempo entre ter testes verdes for muito grande, é um sinal de que talvez as mudanças devam ser menores.

As técnicas de refatoração descritas por Martin Fowler seguem esse modelo pois descrevem um passo-a-passo onde aplicamos uma mudança pequena e executamos os testes até alcançar o objetivo final da refatoração.

Se você quer refatorar o código o primeiro passo deve ser identificar o code smell e qual ação quer tomar.

Digamos que você encontrou um método que tem muitos argumentos e quer pensar numa maneira de melhorá-lo. Você pode criar um novo objeto para armazenar esses dados e passar apenas esse objeto, outra opção seria quebrar o método e dividir a lógica e parâmetros, ou ainda mover alguns desses parâmetros para um construtor.

Existem várias opções ao refatorar um código então é muito importante entender o contexto do código e decidir o caminho antes de começar as modificações.

Em seguida vamos aplicar a refatoração, sempre lembrando de fazer mudanças pequenas e garantir que os testes passem. Se no meio de uma refatoração você encontrar uma oportunidade de corrigir um problema, anote em algum lugar e continue. Lembre-se que queremos diminuir a quantidade de mudanças.

Uma vez que a refatoração foi aplicada podemos criar um commit pois deixamos o sistema com todos os testes passando. Agora podemos voltar ao modo de corrigir bugs ou implementar funcionalidades.

Nos próximos posts vamos ver como Padrões de Projeto podem nos ajudar na hora de decidir o caminho das refatorações. Até lá.

Como você organiza seu código?

Muitas práticas de programação tem como objetivo principal facilitar a leitura do programa, uma delas é a organização de código. Toda linguagem tem alguma forma de empacotar e dividir a lógica de negócio, mas que ao final não fazem tanta diferença assim na execução do programa.

Apesar de parecer ser uma atividade simples, organizar o código é algo que eu nunca havia pensado com muitos detalhes antes, até que um amigo me apontou para um artigo que apresenta quatro estratégias para organizar seu código.

Na grande maioria dos códigos em que trabalhei, a divisão mais comum é a por tipo, que organiza os pacotes baseado na função que cada classe desempenha. Aplicações Rails por exemplo são automaticamente geradas seguindo esse paradigma.

Captura de Tela 2016-05-21 às 6.56.39 AM

Todos os modelos ficam em uma pasta dedicada, todos os arquivos de configuração ficam em outra. No entanto o que isso te diz sobre o domínio da sua aplicação? Será que a organização das pastas deveria te dizer algo sobre o domínio? Recomendo assistir a apresentação Architecture the Lost Years, de Bob Martin, para ver mais detalhes sobre esse ponto.

Não é que essa organização esteja errada, o programa continuará funcionando normalmente e desenvolvedores conseguem entender a arquitetura e navegar pelo o código. O problema é pensar que essa é a única maneira de organização!

Na maioria dos projetos com frameworks Java que já vi é comum encontrar divisão por pacotes que não agrega nenhuma informação lógica – mesmo não sendo frameworks com convenções tão forte como Rails.

Por exemplo, todas as classes ficam dentro do pacote br.com.empresa.nomeDoProjeto, o único conteúdo do pacote br é o pacote com, que também tem como único conteúdo o pacote empresa… até que, depois de vários pacotes “vazios” chegamos ao código real.

O problema da má organização de pacotes é que isso pode forçar a complicar mais os nomes das classes. Tanto que existe um jogo Java: Real or Not? onde precisamos escolher qual classe é real do framework Spring entre outras duas que são inventadas.

Mesmo em projetos que usam frameworks com forte convenção, como Rails, é possível adaptar o código para que ele seja organizado de uma maneira diferente, como mostrado no post Baking a Cake with Rails.

Se você prefere organizar pacotes por componentes, por camadas ou qualquer outra forma, é possível! O objetivo é tornar a arquitetura e design mais explícitos, não seguir cegamente um framework, como descrito no post Screaming Architecture.

Como falei no começo, organização de código é algo que só comecei a me preocupar de verdade recentemente, mas já pude ver que, ao utilizar uma abordagem de divisão por componente o código fica bem mais conciso e fácil de navegar.

E você, já tinha parado pra pensar em como organiza seu código antes? Qual sua maneira preferida?

Porque nomear um atributo como “type” não é uma boa ideia em Rails.

Ou: como utilizar o padrão Herança de Tabela Única (Single Table Inheritance) em Rails.

Se alguma vez você já tentou criar um atributo chamado “type” no seu modelo Active Record, provavelmente você teve algum problema, ou já sabia exatamente o que estava fazendo. Se você se encaixa no primeiro grupo, então esse post talvez possa lhe ajudar um pouco.

Um pequeno exemplo

Vamos criar um scaffold usando Rails da seguinte maneira:

rails generate scaffold Dealer name type company

Criamos o Dealer (Negociante) que no nosso dominio tem os atributos nome (nome da pessoa) e empresa (empregador). Adicionamos também um campo type, esse campo deve ter os valores “Seller” caso seja um vendedor ou “Buyer” caso seja um comprador.

Agora vamos no rails console para criar um novo Dealer da seguinte maneira:

Dealer.create name: "Zeca", company: "Uburu Inc", type: "Seller"

A seguinte exceção será lançada: ActiveRecord::SubclassNotFound: Invalid single-table inheritance type: Seller is not a subclass of Dealer.

O começo da mensagem diz que o tipo da herança de tabela única é invalido pois a classe Seller não é uma subclasse de Dealer. Vamos ver um pouco mais sobre o padrão Single Table Inheritance (STI) [1]

Single Table Inheritance

O padrão STI está catalogado como parte dos Padrões de Arquitetura de Aplicações Empresariais (Patterns of Enterprise Application Architecture). O cenário de aplicação do padrão é quando você precisa representar uma herança em uma base de dados relacional.

A ideia para resolver o problema é bem simples, vamos adicionar um campo que vai dizer qual subclasse deve ser instanciada com aqueles dados. Voltando ao exemplo do post, podemos então ter uma superclasse chamada Dealer e duas subclasses Seller/Buyer que vão ter os mesmos atributos, mas lógicas diferentes.

O Rails já vem pronto para utilizarmos o STI. Basta utilizar que chamemos o campo que diferencia a classe de ‘type’. Por isso, no exemplo anterior, aquele exceção foi lançada. Criamos o modelo Dealer, mas ainda não temos o modelo Seller ou Buyer.

Para resolver o problema vamos criar o Seller e Buyer e fazer com que elas herdem de Dealer. Note que as classes Seller e Buyer não precisam herdar da classe ActiveRecord pois elas vão herdar de Dealer. O código seria algo do tipo:

class Seller < Dealer
end

Agora, se tentarmos criar o mesmo objeto de antes, veremos a seguinte saida:

Dealer.create name: "Zeca", company: "Uburu Inc", type: "Seller"
=> #<Seller id: 1, name: "Zeca", type: "Seller", company: "Uburu Inc", created_at: "2013-11-14 00:51:55", updated_at: "2013-11-14 00:51:55">

Ou seja, mesmo criando um Dealer, o Rails sabe que se trata de um Seller, graças ao STI. Se tentarmos criar um Seller também conseguimos o mesmo resultado:

Seller.create name: "Zeca", company: "Uburu Inc"
=> #<Seller id: 2, name: "Zeca", type: "Seller", company: "Uburu Inc", created_at: "2013-11-14 00:57:36", updated_at: "2013-11-14 00:57:36">

Veja também que o Rails já colocou o valor do atributo ‘type’ para “Seller” automaticamente. Assim você não precisa se preocupar em sempre criar um “Dealer” com o ‘type’ sendo “Seller” ou “Buyer”, basta utilizar a classes que você realmente precisa.

Se o nosso objetivo fosse só ter um campo chamado ‘type’ mas sem nenhuma herança podemos simplesmente renomear o campo para ‘alguma_coisa_type’ ou, se isso não for possível, podemos adicionar a seguinte linha na implementação da classe:

class Seller < ActiveRecord::Base
  self.inheritance_column = nil
end

Assim, o Rails não vai mais se importar com o ‘type’ para herança. Ou caso você precise configurar o atributo que define a herança como outro nome, também é possível passar o nome desejado para que o Rails trate o modelo como uma implementação do STI.

Referências

1. Martin Fowler, Single Table Inheritance. http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html

Cuidado ao cortar sua arquitetura

Quando estamos modelando o nosso sistema, é bem comum pensarmos em camadas. Talvez um dos modelos de maior sucesso no mundo do desenvolvimento (web ou não) é o MVC. A separação em camadas é bem interessante e facilita bastante a separação de responsabilidades e definição de limites.

No entanto, por mais lógico que pareça separar o desenvolvimento por camadas, dificilmente ajuda. Imagine a seguinte situação: uma nova funcionalidade precisa ser desenvolvida, pra simplificar pense num CRUD qualquer. Como você dividiria essa tarefa entre as pessoas da sua equipe?

Cortes horizontais

Dividir a tarefa em camadas, onde um par vai trabalhar no modelo (provavelmente mudando o banco de dados) e outro par fazendo a parte de views (fazendo algo na interface), pode parecer a decisão correta, afinal você pode colocar o especialista em banco de dados pra fazer a parte de modelos e outra pessoa que detona em CSS pra construir uma boa interface.

O problema dessa divisão é que ela exige uma sincronia muito grande entre a equipe. E (aqui vem um dos piores males do desenvolvimento de software) abre um espaço gigantesco para suposições.

O par que está desenvolvendo o back-end precisa construir tudo o que o par que vai trabalhar na interface vai precisar. E se a comunicação não acontecer, muita coisa pode ser feita a mais ou a menos. Esse é o maior perigo quando são feitos cortes horizontais na arquitetura.

Cortes Verticais

Agora imagine que, ao implementar a mesma funcionalidade de CRUD, vamos dividir as tarefas em ações. Então um par faz a parte de criação, por exemplo, e outro faz a visualização.

Obviamente a comunicação precisa acontecer para garantir que tudo seja feito de maneira homogênea, os nomes das tabelas são os mesmos, as views seguem os mesmos estilos, etc. Mas a comunicação sempre precisa acontecer, esse é o real segredo de projetos bem sucedidos!

No entanto, o par que desenvolve a visualização das entidades sabe exatamente o que vai ser necessário nos modelos para que as views funcionem. Assim, minimizamos o espaço para suposições, pois cada par pode implementar exatamente o necessário, nem mais nem menos.

Se eu não escrevi, então é legado.

Muita se fala sobre código legado, sobre práticas pra gerenciar código legado, como evoluir uma base de código legado, mas você já parou pra pensar o que é código legado?

Alguns dias atrás tivemos uma discussão com os membros da equipe utilizando como base o famoso livro “Working Effectively With Legacy Code” de Michael Feathers [1], e antes de entrar no livro eu propus que discutíssemos o que é código legado e chegamos a um conjunto de pontos interessantes sobre código legado.

Subjetividade

Michael Feathers define código legado como código que não possui testes [2], no entanto esta definição é um pouco antiga, pois o livro é de 2004, quando as práticas de testes automatizados ainda estavam sendo difundidas pela comunidade e as ferramentas começaram a aparecer.

O ponto de vista extremo do autor ressalta o fato de que código legado é um código que é difícil de ser mantido. Um código que não possui uma boa suíte de testes dificulta a análise do impacto de suas mudanças, e isso contribui para a inércia da equipe, que evita ao máximo realizar alterações profundas.

No entanto, hoje em dia com a grande abrangência e utilização de ferramentas de testes, não podemos confiar apenas nesse fato, pois um outro conjunto de fatores pode contribuir, talvez não com a mesma intensidade, para que a equipe sofra com mudanças.

É difícil escrever uma frase que defina totalmente o que é código legado, vamos então analisar um conjunto de pontos que podem facilitar essa análise e reflexão sobre o que um projeto legado.

Tecnologia

A tecnologia utilizada em um projeto modifica bastante o desempenho da equipe e sempre discutimos quais as melhores de acordo com cada situação. Com relação a código legado, será que a tecnologia faz um código ser legado ou não?

Os sistemas de pagamento que são escritos em cobol, podem ser considerado sistemas legados? Nesse caso a tecnologia não é mais presente no cenário atual de desenvolvimento, a dificuldade para manter esse tipo de sistema é muito grande, principalmente para encontrar desenvolvedores com conhecimento sobre a tecnologia.

Considere então um sistema que utiliza uma linguagem atual, mas que faz uso de bibliotecas, gems ou frameworks que não são compatíveis com novas versões. Nesse caso a tecnologia impede o aplicativo de continuar a evoluir e contribui para deixar a base de código legado, pois com a renovação da equipe fica difícil manter o conhecimento sobre versões anteriores.

No entanto a tecnologia não pode ser utilizado como único fator para indicar que o projeto é legado. Como um exemplo podemos analisar os aplicativos móveis que precisam manter compatibilidade com versões de sistemas anteriores. Apesar da tecnologia ser antiga o desenvolvimento continua ativo, apesar dos problemas com rotatividade de membros.

Manutenção

Isso leva a outro ponto interessante, o livro fala que código legado é código sem testes, mas qual é o papel da bateria de testes de um projeto? Talvez o mais importante seja facilitar a manutenção a longo prazo, ou seja, um código sem testes é muito difícil de ser mantido, caindo facilmente no limbo do código legado.

Um exemplo são sistemas antigos que ninguém mais altera com medo de fazer parar de funcionar. A solução na maioria dos casos é confiar que aquele monstrengo funciona e cercar ele de serviços que podem ser testados.

E quando a empresa não acredita em testes, então a linha que acaba de ser escrita já está legada? Não necessariamente, pois caso seja um sistema pequeno, onde é fácil entender e alterar (um CRUD simples, por exemplo), talvez os testes não sejam tão importantes.

Mas um sistema médio/grande que não possui nenhuma garantia de funcionamento está amaldiçoado a ser legado. Conforme o time for sendo renovado, os novatos precisarão de muita coragem e tempo de treinamento para realizar alterações, mesmo que sejam superficiais.

Abandonado

Passado algum tempo onde os desenvolvedores não tocam mais em uma determinada parte do sistema, ele começa a ficar abandonado. Um sistema, ou parte dele, que não é tocado por um longo tempo pode ser considerado legado?

Essa discussão leva a dois pontos, o primeiro é de que um desenvolvedor novo pode considerar o sistema legado e um mais experiente não. Isso geralmente acontece quando o conhecimento não está distribuído entre a equipe, então apenas uma pessoa do grupo fica “responsável” por atualizar aquela parte que realiza pagamentos.

O segundo ponto é de que o conceito de legado pode ser aplicado apenas em uma parte do sistema. No caso de sistemas realmente grandes pode ser que a utilização de determinada ferramenta que apenas uma pessoa da equipe conhece crie dificuldades para manter o sistema.

Idade

Um sistema velho pode ser considerado legado? A resposta depende do que é velho, que também é um conceito subjetivo. Um sistema de cinco anos que utiliza versões não tão atuais das tecnologias e que a equipe já foi completamente renovada, pode ser considerado legado?

É fácil perceber que não dá pra escrever um parágrafo que descreva o que é um sistema legado. A conclusão que nós chegamos foi de que, a partir de uma lista de conceitos relacionados a código legado podemos analisar se o nosso projeto é legado ou não. E essa discussão foi muito positiva, pois tratou principalmente sobre a confiança dos membros da equipe em alterar o sistema.

E o que é código legado para você? O quão confiável está você e sua equipe com o sistema que vocês desenvolvem?

Referências:

[1] FEATHERS, Michael C. Working effectively with legacy code.
[2] http://en.wikipedia.org/wiki/Legacy_code

[Livro] The Productive Programmer

The Productive Programmer

The Productive Programmer

O livro parte de uma premissa, no mínimo interessante: com o passar do tempo, o autor percebeu que os programadores ficaram MENOS eficientes, e não o contrário. A ideia é que, por passar a utilizar cada vez mais interfaces gráficas ao ponto de se interessar apenas por interface, o que seria um aumento de produtividade, do ponto de vista do usuário final de computadores, acabou prejudicando os desenvolvedores que perderam um pouco eficiência máxima do computador. No entanto, desenvolvedores são um tipo bem diferente de usuários de computador e muitas vezes precisam abrir mão da “facilidade de uso” para ter um ganho com eficiência.

A partir daí o autor parte para quatro princípios para se tornar um programador produtivo: Aceleração, Foco, Automação e “Canonicalidade”.

Aceleração fala sobre executar tarefas que fazemos a todo momento de maneira mais rápidas. Foco se refere a como alcançar e se manter em um estado de super produtividade, utilizando programas para reduzir distrações, procurar informações de forma mais eficiente, etc. Automação é auto-explicativo, se refere a deixar o computador trabalhar por você. “Canonicalidade” diz respeito a evitar duplicações de informações, para que você evite desperdício com redundância.

Esta primeira parte do livro realmente se refere a ferramentas que você pode utilizar para se tornar um programador mais produtivo. Coisas simples como utilizar um GnomeDo ou QuickSilver até a utilização de repositórios centralizados para compartilhar informações. É uma etapa meio maçante, mas que lhe apresenta informações interessantes.

Já a segunda parte do livro fala sobre a experiência do autor como consultor da ThoughtWorks em vários diferentes projetos e práticas que ajudam qualquer time de desenvolvimento a se tornar mais produtivo.

Um dos primeiros tópicos abordados é o TDD, falando sobre os benefícios, como evoluir códigos de teste e a importância da cobertura de código. Outro tópico fala sobre análise estática, em linguagens estáticas lógico, citando algumas ferramentas para análise de qualidade de código. Também são mencionados ferramentas para linguagens dinâmicas.

Outro tópico interessante discutido no livro é o principio “Good Citizenship “, que já foi falado aqui no blog (https://brizeno.wordpress.com/2012/11/26/o-objeto-bom-cidadao-good-citizenship/). Também são feitas discussões filosóficas, utilizando assuntos não relacionados com desenvolvimento de software mas que servem como exercício para uma discussão sobre princípios de design de software.

As metáforas utilizada no livro são bem interessantes, por exemplo quando o autor compara projetos de software onde sempre se busca adicionar informações ao navio Vasa, que foi um navio “inovador” com um orçamento infinito, mas que não conseguiu navegar mais que 120 metros.

Outro exemplo interessante é quando o autor fala sobre desenvolvedores que tem medo de tentar novidades porque todos os outros dizem que algo “sempre foi feito assim”. A comparação é feita com o experimento dos macacos raivosos, onde um grupo de macacos era colocado em uma sala com uma escada e um cacho de banas no final da escada. Sempre que um dos macacos subia, os outros levavam uma rajada de água gelada. Com o tempo, os macacos começaram a bater em qualquer outro macaco que tentava subir. Com o tempo, nenhum macaco subia mais, então um dos macacos foi substituído, e logo o macaco novo tentou subir a escada e apanhou dos outros, até desistir de subir. Os macacos foram então substituidos por completo e chegou-se ao ponto que nenhum macaco tentava subir sem nunca ter sido molhado.

No fim, o livro vale muito a pena. Os capítulos são curtos e bem concisos, ou seja, nada de redundância ou conversa fiada para deixar o livro maior. Então se você quiser se tornar melhor no que faz todo dia, recomendo a leitura do livro. Não sei sobre detalhes da versão traduzida, mas a versão original utiliza uma linguagem bem simples e fácil de ler.

The Productive Programmer
By Neal Ford
Publisher: O’Reilly Media
Released: July 2008
Pages: 226