Archive for the 'tecnologia' Category

Elephant typing

Devido à recente explosão de popularidade das linguagens dinâmicas, você provavelmente já deve ter ouvido falar de duck typing. É a noção que o tipo de um objeto é determinado exclusivamente pelas mensagens às quais ele pode responder. Se dois objetos quaisquer respondem a um mesmo conjunto de mensagens, eles podem ser considerados do mesmo tipo. É uma versão programática do já clássico dito “se algo grasna como um pato e nada como um pato, deve ser um pato.”

Para quem está acostumado com definição estática de tipos, talvez faça sentido pensar que é como se houvesse interfaces definidas dinamicamente de acordo com as necessidades do código em execução. Algoritmos de ordenação, por exemplo, conseguem trabalhar com qualquer conjunto de objetos comparáveis. Ou seja, que forneçam métodos de comparação como maior, menor e igual. Com duck typing, estes objetos não precisam indicar que satisfazem a interface Comparable, precisam apenas responder às mensagens esperadas e o algoritmo de comparação pode tratar todos como comparáveis. Afinal eles respondem se são maiores ou menores que os outros, portanto devem ser comparáveis.

O tempo passa, as linguagens evoluem, surgem novos conceitos e os animais vão ficando maiores. Steve de Korte, criador da linguagem Io, em uma entrevista de julho do ano passado (ouça a primeira parte também) introduziu um conceito que dá para chamar de elephant typing. Ele usa elefantes como analogia para explicar programação com protótipos.

O modo mais comum de criar um objeto em uma linguagem baseada em protótipos é clonar um objeto pré-existente e especializar o clone do modo que for necessário. O clone inicialmente possui as mesmas propriedades que o objeto original e pode ser descrito a partir daí evitando repetição. Para descrever o Dumbo por exemplo, você poderia dizer:

“Dumbo é um elefante como aquele que você viu no zoológico. Só que é um filhote e por isso é um pouco menor. As orelhas dele são bem maiores e ele pode voar. Além disso, ele carrega uma pena na tromba a maior parte do tempo por acreditar que é ela que o faz voar.”

Há objetos, mas não há classes. O programador especifica somente as diferenças do clone para o protótipo. Se mais tarde algo for descoberto sobre o protótipo, também vai valer para o clone. Por exemplo, se depois você descobre que o elefante do zoológico tem medo de ratos, pode presumir que Dumbo também tem. Trocando em miúdos, em Io, se você alterar um objeto adicionando um método novo ou modificando o valor de um atributo, a alteração é propagada para todos os descendentes. Entretanto, este não necessariamente é o comportamento dos protótipos das demais linguagens.

Andando de costas

Aqui vai um pequeno resumo de como adiciono recursos aos meus sistemas:

  1. Eu identifico (ou alguém me chama a atenção para) alguma necessidade
  2. Escrevo alguns testes para a necessidade
  3. Escrevo o código para fazer os testes passarem
  4. Depois que os testes estão devidamente esverdeados, reestruturo o código para que fique um pouco mais elegante
  5. Repito até que a necessidade esteja satisfeita

Este é o modo TDD de fazer as coisas e pode parecer estranho para quem não está acostumado. Mas o que não é estranho para quem não está acostumado?

Por conseqüência este modo esquisito também é o modo XP. Pelo menos para quem segue a cartilha. E, olhando por este lado, parece que XP inverte bastante as coisas. Você faz os testes antes de fazer o código e (re)define a estrutura depois de ter o código pronto. É como programação para caranguejos: você anda e consegue chegar aonde precisa, mas o modo como isso é feito não é muito parecido com o que se vê normalmente. A não ser, é claro, que você esteja vivendo entre caranguejos.

O que eu inicialmente aprendi na escola foi que deveria pensar um pouco em como estruturar a aplicação antes de começar a escrever código. Para registrar (ou documentar, para usar uma palavra um pouco mais corporativa) a estrutura, poderiam ser elaborados diagramas usando uma linguagem gráfica padrão. Depois era hora de implementar o design. Implementar, em corporativês, é escrever o código correspondente ao design (que é a palavra usada para indicar a estrutura pré-projetada). Finalmente, depois que o código estivesse escrito, chegava a hora de testar a coisa.

Em algum ponto do tempo, devo ter esquecido isso tudo. Pelo que eu descrevi, parece que estou fazendo tudo ao contrário, andando de costas. Afinal, eu começo pelos testes e só no final do ciclo me preocupo com a estrutura. Mas a verdade é que continuo fazendo tudo na mesma ordem. Os testes que faço no início servem para que eu pense um pouco e defina uma estrutura mínima para o código que estou prestes a escrever. Eles definem como o novo pedaço de código vai se ligar ao resto da aplicação, assim como fazem os diagramas. Só que têm uma vantagem bem importante: depois que eu quiser validar tudo, os testes já estão prontos. Do mesmo modo, a atividade final de reestruturação é uma preocupação principalmente com a estrutura da aplicação e, como isso tudo é um ciclo, estar no final é o mesmo que estar no início.

Máquinas carentes

Então você pensava que receber atenção era uma necessidade exclusivamente humana ou no máximo do seu cachorro — ou gato, não quero começar uma guerra cães vs. gatos aqui. A novidade é que isso acontece com máquinas também. Olhe estes velocímetros, por exemplo:

Velocímentro seguro de siVelocímetro carente

O velocímetro da direita é uma dessas máquinas carentes. A graduação é confusa porque os trastes usados para as velocidades numeradas são apenas um pouco mais espessos do que os das intermediárias. O problema fica mais evidente depois que se passa de cem quilômetros por hora. Os números de três dígitos precisam ficar bem próximos uns dos outros e, se você olhar rapidamente, não vai saber se está a 130 ou 140. É preciso parar e se concentrar para diferenciar qual o traste mais espesso. É preciso dispensar mais atenção do que deveria ser necessário para um velocímetro. Sem falar que não dá para fazer isso e olhar para a estrada ao mesmo tempo.

O da esquerda é muito mais seguro de si. Os trastes intermediários são bem mais finos que os principais. Como se não bastasse, eles são mais curtos também e o espaçamento é muito maior. Ele é um velocímetro que não implora para ser olhado, apenas fornece a informação e sai do caminho.

Se você projeta algum tipo de máquina (e todo programador faz isso), tome cuidado para não fazer uma dessas máquinas carentes. As pessoas já estão suficientemente ocupadas dando atenção para outras pessoas. As máquinas não devem competir com isso.

Ano novo, linguagem nova

Não que seja algum tipo de tradição pessoal de ano novo, está mais para coincidência. Mas ano passado, mais ou menos no começo do ano, comecei a estudar Ruby. Minha experiência anterior sempre tinha sido com linguagens compiladas e eu estava me interessando bastante pelo lado dinâmico e interpretado da vida. Aconteceu de Ruby estar bastante comentada na época e acabou sendo a escolha mais óbvia. Depois de um ano, a linguagem está bem mais na moda e o projeto que comecei para estudar está começando a tomar corpo.

A mente é uma fera faminta por novidades e, se você alimentá-la periodicamente, ela pode acabar gerando algumas maravilhas próprias. Uma linguagem nova é um ótima opção para essa dieta. Uma linguagem não é só uma ferramenta, é toda uma escola de pensamento nova e vem acompanhada de um mundo de idéias fresquinhas. Estar em contato com outros mundos é uma boa forma de expandir os horizontes e facilitar o surgimento das boas idéias. Ruby tem feito exatamente isto por mim. Conhecer duck typing, geração de código em tempo de execução e as tão faladas DSLs em Ruby com certeza abre algumas possibilidades para a imaginação, se não para o raciocínio. Mesmo que eu não esteja trabalhando com a linguagem para resolver um determinado problema, o que ela me ensinou já faz bem porque está enraizado na cabeça. Com certeza isso não é exclusividade de Ruby. Se você está trabalhando exclusivamente do lado compilado ou interpretado, aconselho conhecer o outro. E se você não sabe de que lado está, procure saber. O aprendizado é garantido.

Um ano não foi suficiente para que eu possa dizer que conheço profundamente Ruby e acho que vou continuar estudando por muito tempo ainda. Mas acho que já chegou o tempo de apresentar a mim mesmo novos paradigmas. Ultimamente tenho pesquisado sobre linguagens de programação baseadas em protótipos. O que posso dizer com meu limitado conhecimento é que programação baseada em protótipos é um tipo de orientação a objetos.

Só que sem classes!

Numa linguagem orientada a objetos baseada em protótipos o comportamento é inserido diretamente nos objetos. Para criar um método novo, você pega um objeto antigo e adiciona o método diretamente a ele. Não é preciso criar um classe previamente para isso. Se você olhar bem, classes são apenas objetos com instruções especiais sobre como criar outros objetos. Em orientação a objetos puramente baseada em classes, elas são o único meio de criar novos objetos. Com protótipos, objetos podem ser criados a partir de qualquer outro objeto pelo processo de clonagem. O objeto original é chamado de protótipo e daí vem o termo “baseada em protótipos”.

Só de conhecer essa idéia de protótipos, minha cabeça já passou por uma pequena remodelagem. Mas uma coisa é estar a par do conceito, outra é aprender uma linguagem construída sobre ele. Novamente posso estar falando besteira, mas um exemplo de linguagem bastante popular construída sobre a mesma idéia é Javascript. Mas não foi ela que eu escolhi. Se você pesquisar sobre linguagens baseadas em prótipos, vai acabar chegando a Self, que seria minha linguagem escolhida para este ano. Ela já está no pedaço desde os anos 80 e parece ser bastante madura. Uma pequena coisa que impediu de ir em frente foi o fato de não haver um compilador/interpretador disponível. Pelo menos não para quem não possui uma máquina Mac OSX ou SPARC com Solaris. Não por enquanto.

Depois de uma pequena saga em busca de uma linguagem baseada em protótipos que eu pudesse realmente usar, acabei chegando a Io, uma linguagem bem simples nascida em Abril de 2002 pelas mãos de um californiano (não sei se o cara realmente nasceu lá, mas é onde ele diz que mora). Já baixei e compilei meu interpretador e tudo parece estar funcionando corretamente por aqui. Até já fiz meu primeiro programa:

"Hello, Io!" println

Io não tem classes, nem operadores aritméticos e nem atribuição. Tudo isso é implementado a partir de passagem de mensagens. Bem simples e uniforme. Além disso, Io não tem palavras-chave.

Mesmo assim funciona.

Isso com certeza é surpreendente.

Nada de errado com tipagem estática

Tipagem estática quer dizer que todos os tipos estão determinados em tempo de compilação e que não mudam durante a execução do programa. Não deve haver problema nenhum com o fato do compilador verificar todos os tipos do seu programa antes de gerar um executável, certo? Isso pode até ser bom. Não há substituto para uma estrutura bem projetada e boa cobertura de testes, mas ter verificação de tipo durante a compilação pode mesmo evitar erros.

Como muita coisa, tipagem estática é uma faca de dois gumes e este é apenas um lado da moeda. O outro lado são as linguagens que exigem que o programador ajude o compilador a descobrir os tipos. Tudo para que eles possam dizer ao programador ‘Não, você não parece ter errado o tipo de nenhuma expressão’. Na presença de uma dessas linguagens, deveriam ser disparados alguns alarmes e deveria haver uma brigada de forças especiais dedicada especificamente a alertar os mais desavisados. Afinal de contas, as máquinas deveriam ajudar a nós e não nós a elas.

A realidade é que, se isso acontecesse, seria um mundo cheio de alarmes porque esse tipo de linguagem está em toda parte. Mas essa onipresença não faz com que ser forçado a escrever Integer no início de uma declaração como Integer x = 3 deixe de ser quase um insulto à inteligência. Se x é igual a três e três é um inteiro, é óbvio que x também é um inteiro. A declaração de tipo é só repetição. Repetição é uma coisa em que computadores são bons e pessoas são extremamente ruins. Pessoas ficam entediadas e querem desafios, computadores não precisam disso.

O nome que os pesquisadores arrumaram para esse tipo de repetição é inferência de tipos e deveria ser feita por toda linguagem e compilador sempre que possível. Mais: elas deveriam ser projetadas para que sempre fosse possível inferir tipos. Poupar a paciência dos programadores é sempre uma boa idéia e compiladores que precisam de tudo mastigado não fazem isso.

O raciocínio pode até ser estendido para expressões mais complexas. Aqui há uma definição de função em uma linguagem fictícia (que muito provavelmente é sintaxe válida em alguma linguagem real):

fac(n) {
    if (n == 0) return 1;
    else return n * (fac( n - 1));
}

Temos uma função fac que recebe um n, que por sua vez é usado em duas expressões dentro da definição da função. Na primeira, ele é comparado com zero e, na segunda, é subtraído de um. Portanto, n é algo que pode ser comparado e subtraído: um número. Isso faz da nossa função uma função sobre números. Mas, para falar um pouco de matematiquês, qual é o contra-domínio da função? Eu sei que devo passar um número para ela, mas o que vou receber em troca?

Essa é fácil, é só ver o que ela retorna. Temos duas expressões que retornam valores, a primeira retorna o valor um (um número). Isso faz de fac uma função de número para número, mas o tipo da segunda expressão de retorno precisa coincidir com o da primeira para que o código esteja consistente. Como n é um número que está sendo multiplicado por outro número (o fatorial do número anterior a ele), os tipos batem.

Estes dois exemplos são bem simples, mas inferir tipos realmente não é muito complicado. Podemos continuar aumentando a complexidade das expressões que ainda será possível inferir tipos. Tudo isto pode ser feito sem que o programador precise prover informação de tipos e em tempo de compilação. Por isso inferência de tipos é diferente de duck typing. Não quer dizer somente que o programador não precisa declarar os tipos, mas que além disso ele não vai precisar esperar até executar o programa para detectar os erros.

BTUF

Não precisa ler o título de novo. Seus olhos não estão enganados: realmente é um T no lugar do D (ou R, dependendo de onde você esteja vindo). BTUF é o acrônimo para Big Testing Up-Front. BTUF é o que acontece quando você tenta desenvolver todos os testes possíveis e imagináveis para seu código antes de começar a escrevê-lo. Com a popularização de Test-Driven Development esta é uma modalidade para cascateamento do desenvolvimento em óbvia ascensão.

Testar também é projetar. Quando se escreve os testes antes do código de produção o que se faz essencialmente é projetar como o código vai parecer olhando do lado de fora. Às vezes é difícil resistir à tentação de projetar tudo antes para depois partir para a implementação. Para quem não consegue evitar, aqui vai uma bela notícia do jornal de ontem: isso não funciona. Apesar de todo mundo minimamente informado já ter lido o jornal, nunca é demais repetir.

Não funciona porque projetar qualquer coisa é infinitamente melhor quando é possível obter retorno. Desenvolver software é uma das atividades que dispõem de maior capacidade de retorno em tempo de projeto. Arquitetos precisam esperar que a equipe de construção termine a obra, roteiristas de cinema podem esperar anos pelo término das filmagens e músicos precisam esperar por um longo processo de mixagem para saber como seus projetos serão apreciados pelo público. Mas programadores podem simplesmente compilar seus programas. Desistir propositalmente deste retorno não é algo muito sábio a se fazer. Escrever todos os testes para um trecho de código antes de escrever é como desenhar vários retângulos e setas descrevendo-o detalhadamente antes de tentar fazê-lo funcionar. O risco de hiper-engenharia é o mesmo porque o código não está lá para gritar de agonia.

Então se você leu o artigo do José Oliveira, considere com muito carinho os conselhos dele. Mas leia o esclarecedor artigo do Ivan Sanchez também. Escreva os testes que conseguir imaginar antes de começar a escrever o código para fazê-los passar, mas por favor não fique muito tempo pensando. Você pode esperar para começar a escrever o código depois que todos os testes estiverem prontos, mas você não precisa e nem quer fazer isso.

Design não se faz somente com diagramas e TDD é só uma técnica de design que usa testes. Pode ser uma abordagem bem mais eficaz que setas e retângulos em alguns casos, já que força o programador a utilizar o código ao mesmo tempo que o projeta. Mas ainda assim é somente uma abordagem para design. BTUF, do mesmo modo, é somente um caso especial de BDUF no qual se usa testes para explicitar completamente o design antes de se escrever qualquer linha de código.

Controle de versão distribuído se distribui

Talvez eu simplesmente tenha pegado o trem tarde demais e estou vendo como novidade o que os veteranos veriam como normalidade, mas tem algo que venho observando há algum tempo. Não vou chamar de tendência para não arriscar queimar a língua no futuro, mas é no mínimo curioso.

Você tem notado como os sistemas de controle de versão distribuídos estão mais comuns?

Há um ano e meio nós no projeto EclipseFP estamos usando um sistema distribuído chamado Darcs. Ele vem se tornando um tipo de padrão de fato na comunidade Haskell porque é escrito em Haskell. Apesar do nosso projeto (ainda) não ser totalmente escrito em Haskell, em geral é uma boa idéia para um ambiente de desenvolvimento se distanciar o mínimo possível da sua comunidade. A escolha então foi óbvia.

Isto não prova muita coisa. Estou falando do meu próprio projeto, afinal de contas. Mas tem mais. A menos que você estivesse em algum tipo de caverna em um planeta distante da Terra durante os últimos dois anos, você já ouviu falar no Ubuntu. Eles também usam um sistema de controle de versão distribuído, o Bazaar-NG (também conhecido por bzr). Outro projeto do qual você talvez já tenha ouvido falar que usa controle de versão distribuído é o Linux. Eles usam o git, que foi originalmente escrito por Linus Torvalds para ser usado no desenvolvimento do seu kernel. Há até um espelho Darcs do repositório deles. Como se não bastasse há ainda os boatos sobre a versão 2.0 do Subversion (conhecido também pela alcunha svn) ser distribuída.

Mas o que me chamou a atenção foi um pequeno detalhe da decisão da Sun de abrir o código de Java semana passada. Passaria totalmente desapercebido se não fosse minha interminável lista de feeds e seu inesgotável estoque de artigos interessantes. Acontece que eles também vão passar a usar mais um sistema de controle de versão distribuído. O nome dele é Mercurial. hg para os íntimos.

A pergunta de um milhão de dólares (ou de um milhão de olhos, como vou tentar mostrar) é: por que eles estão fazendo isso?

Com controle de versão distribuído, cada cópia de trabalho é um repositório completo. Ou seja, cada desenvolvedor ou curioso que queira acompanhar o projeto tem o histórico de modificações ao código-fonte armazenado localmente. Há dez anos não era todo mundo que tinha 40GB de memória de armazenamento disponível e fazia sentido recorrer a um servidor central para armazenar o histórico de um projeto. Hoje as pessoas podem se dar ao luxo de armazenar o histórico do Linux (hoje com mais de 300MB) sem problemas.

Tecnicamente não é necessário haver algo como um repositório central quando se usa controle de versão distribuído. Não há uma hierarquia de repositórios, cada um é independente dos demais. Se eu e você estamos trabalhando em um mesmo projeto, podemos fazer modificações separadamente o tanto quanto quisermos e ambos terão o mesmo poder sobre seu repositório local. Ambos poderão gravar alterações, desfazê-las e consultar o histórico do projeto como quiserem sem precisar saber o que acontece com o repositório do outro. Se quisermos, podemos mesclar os dois repositórios para sincronizá-los e isso faz muito sentido se estivermos realmente trabalhando no mesmo projeto. Como esta operação costuma ser freqüente, as pessoas em geral elegem um dos repositórios como central. Elas passam a enviar suas alterações para lá quando estão prontas para publicação e consultá-lo quando precisam atualizar suas cópias locais. Mas só porque escolheram fazê-lo. Não há nenhuma limitação do sistema que as obrigue a fazer isso.

Isto facilita muito a vida tanto de quem quer contribuir com patches como de quem precisa gerenciá-los. Se você fez uma modificação no código de algum projeto, tudo que você precisa é fazer um mini-fork fazendo uma cópia local do repositório e gravar sua alteração. Mais tarde, quando o corpo de código principal for modificado, o sistema vai se encarregar de juntar tudo sozinho. Com controle centralizado você precisa convencer os mantenedores que o seu patch é importante para que eles o integrem ao código principal ou se contentar em refazer as modificações a cada revisão. Claro que você vai gravar seus patches localmente para facilitar, mas as coisas podem ficar bem cabeludas se você tiver muitas alterações.

Ter um repositório completo local significa também que você pode gravar modificações intermediárias. Todo mundo faz besteira de vez em quando e é bom poder saber que há algo mais que a função de desfazer do editor para te proteger. Quando podem as pessoas tendem a fazer modificações menores e detalhar melhor seus comentários. Ao mesmo tempo que o colaborador externo tem mais controle sobre suas alterações, o mantenedor ganha um histórico de versões mais limpo, livre de revisões gigantescas.

Toda a razão para disponibilizar seu código-fonte livremente é poder contar com a força de uma comunidade. Quanto mais gente vir o código, melhor ele vai poder ficar. Patches são um dos maiores desejos de qualquer mantenedor de um pedaço de código e controle de versão distribuído facilita a vida deles e de quem quer contribuir. Quando se quer atrair contribuições, diminuir as barreiras para quem quiser contribuir não faz mal a ninguém e é justamente isso que a Sun está tentando fazer agora com o OpenJDK.

Uma pequena grande idéia

Qual é o padrão de projeto que você mais usa? Que pequena estrutura insiste em aparecer quase que espontaneamente no seu código?

Eu particularmente nunca cheguei a contar quantas vezes uso cada padrão, mas tem um que com certeza estaria bem no topo da lista dos mais usados se eu contasse. Dica: não é o Singleton. Também não é nenhum dos padrões do clássico GoF. É um padrão bem pequeno e localizado que às vezes é chamado de Null Object. Muito provavelmente você já deve conhece-lo, seja por este ou outro nome. Isso de nomes diferentes para os mesmo padrões acontece com freqüência, já que muitos deles brotam em vários locais, várias cabeças, diferentes de forma independente.

Suponha que você esteja construindo uma wiki como parte de uma ferramenta de acompanhamento de projetos. Somente usuários autenticados podem editar páginas nesta wiki. Isso pode ser resolvido com algo parecido com isso (Ruby):

1 def edit
    @page = find_page(params[:page_name])

    if current_user.nil? ||
5      !page.editors.include?(current_user.login)
      redirect_to :action => 'show',
                  :page_name = params[:page_name]
    end
  end

Mas aquela linha cinco está particularmente feia. Podemos esconder essa verificação num método bem nomeado na classe User.

1 class User
    def can_edit?(page)
      page.editors.include? self.login
    end
5 end

Ainda há bastante espaço para melhoria aqui. Esse trem de chamadas de método da linha três está bem grande e pode ser diminuído, mas vamos primeiro ver como ficou a nova definição de edit:

1 def edit
    @page = find_page(params[:page_name])

    if current_user.nil? ||
5      !current_user.can_edit?(@page)
      redirect_to :action => 'show',
                  :page_name = params[:page_name]
    end
  end

Já melhorou bastante, mas aquela verificação de nulidade que ficou na linha quatro ainda está me incomodando. Há tantos detalhes embutidos nesta pequena chamada current_user.nil? que podem fazer a cabeça de alguém explodir. Para entender isso é preciso saber que current_user é igual a nil quando o usuário não está autenticado. Além disso, a regra que diz que os usuários devem estar autenticados e autorizados a editar a página está toda codificada nesta pequena linha de código. É responsabilidade demais para uma única linha.

Eu não quero ser responsável pela explosão de nenhuma cabeça, portanto vou procurar algum modo de melhorar isto. Seria melhor se pudéssemos dizer somente current_user.can_edit?(@page), mas não podemos fazer isso porque o objeto nil (sim, nil, assim como qualquer coisa em Ruby, também é um objeto) que representa o usuário não autenticado não responde ao método can_edit?. Por isso precisamos de todo esse tratamento desajeitado para o caso especial.

Mas o usuário não autenticado não precisa ser representado por nil. Ao invés disso, podemos definir uma nova classe chamada UnidentifiedUser que é um Null Object. Ela vai ser uma classe bem pequena, usada simplesmente para tratar o caso especial do usuário não autenticado. Os detalhes sobre tratamento de usuários sem autenticação vão ficar confinados a ela e o restante do sistema poderá tratar todos os usuários uniformemente. Segue o código:

1 class UnidentifiedUser
    def can_edit?(page)
      false
    end
5 end

E acabou. O usuário não identificado só precisa responder que não pode editar nenhuma página, sem exceção. Como isto aqui é Ruby, não precisamos dizer que esta classe herda da User original porque usamos duck typing. Se estivéssemos usando uma linguagem estaticamente tipada, precisaríamos disso (como vamos ver no próximo exemplo que usa Java).

Isso é tudo que precisamos para esta classe, mas ainda precisamos alterar o método current_user, para que retorne um objeto UnidentifiedUser ao invés de nil no caso do usuário não estar autenticado.

1 def current_user
    session[:user] || UnidentifiedUser.new
3 end

Outra forma de alcançar o mesmo efeito é injetar o método can_edit? diretamente no objeto nil, já que Ruby não diferencia tempo de compilação, linkagem e execução. Desse modo não precisamos nem modificar este último método.

Este pequeno padrão se mostra muito útil. Suponha agora que você esteja escrevendo um IDE para alguma linguagem extremamente bela que quase ninguém usa por achar diferente e esquisita. Este seu IDE usa um compilador externo por baixo dos panos e o programador pode escolher se quer ver a saída do compilador. Vamos começar pelo código feio e passar lentamente para uma versão mais elegante. Claro que isso é só minha opinião. Mas este é o meu blog, tudo aqui é só minha opinião. (Java)

1 class Compiler {

     // ...

5    void compile(IFile target) {
         if (compilerOutputEnabled())
             output.write(
                 "Starting compilation from file "
               + target.getName());
10
         String cmdLine = "ghc --make " +
             target.getFullPath().toOSString();

         if (compilerOutputEnabled())
15           output.write(cmdLine);

         String output = processRunner.execute(
             sourceFolderFor(target), cmdLine);
20
         if (compilerOutputEnabled())
             output.write(output);
     }

25}

Eu avisei que iríamos começar pelo código feio… Você também está sentindo o mau cheiro? Essas linhas repetidas perguntando se alguma coisa deve ser relatada não estão te dando coceira? Vamos tratar isso antes que comecem a estourar bolhas.

O objeto output apareceu magicamente para este nosso método. Estamos assumindo que ele foi inicializado pelo nosso Compiler em algum ponto omitido do código. Mas não precisa ser assim. Podemos injetar qualquer Writer se o output for um parâmetro do método.

Vamos escrever então um Writer especial que sabe quando deve escrever de verdade ou não e injeta-lo no método compile por passagem de parâmetro.

1 public CompilerOutputWriter extends Writer {

      // ....

5     @Override
      public void write(char[] cbuf,
          int off, int len)
      {
          if (compilerOutputEnabled()) {
              underlyingWriter.write(
                  cbuf, off, len);
          }
10    }
  }

A responsabilidade de checar se a saída deve ser mostrada agora passou para o objeto CompilerOutputWriter. Podemos tirar as checagens do método compile:

1 class Compiler {

     // ...

5    void compile(IFile target, Writer output) {
         output.write(
                 "Starting compilation from file "
               + target.getName());

10       String cmdLine = "ghc --make " +
             target.getFullPath().toOSString();

         output.write(cmdLine);

15       String output = processRunner.execute(
             sourceFolderFor(target), cmdLine);

         output.write(output);
20   }

  }

O código está ficando melhor, mas ainda dá pra deixá-lo mais apresentável. Podemos apagar a classe CompilerOutputWriter por inteiro (você também tem uma ótima sensação quando apaga código?) e introduzir um NullObject que será selecionado quando a saída do compilador for desabilitada. Por exemplo:

1 public class NullWriter extends Writer {

      // ....

5     @Override
      public void write(char[] cbuf,
                        int off, int len)
      { /* ignore */ }

  }

Um NullWriter simplesmente ignora a entrada. O pobre compilador vai achar que está escrevendo alguma coisa, mas qualquer coisa que ele pedir será ignorada. Agora a responsabilidade de saber se o usuário quer ou não ver a saída do compilador é de quem está chamando o Compiler, e ele tem todo o direito de delegar a responsabilidade.

O padrão NullObject é uma forma de tratar casos especiais que substitui a lógica condicional por uma solução mais limpa. Por falar nisso, parece que eliminar lógica condicional é uma Boa Idéia™.

A linguagem independente de linguagem

A palavra de hoje é modelagem.

Quando os programadores ouvem esta palavra, não pensam em porcelana, argila ou massa de modelar. Eles imaginam diagramas, esquemas, retângulos, setas e coisas do tipo. Bem estranho, mas é verdade.

O que os programadores chamam de modelagem não passa de uma forma de expressão que usa uma linguagem visual ao invés de uma escrita. Se os esquemas fossem quadros, o código-fonte seria um poema. São artes diferentes e têm públicos diferentes. Do mesmo modo que há gente que consegue apreciar melhor um poema do que um quadro, tem gente que entende melhor código-fonte do que esquemas.

Diferentemente do que acontece nas artes, os esquemas e diagramas dos programadores podem ser usados para gerar código-fonte sem nenhuma intervenção humana. Dependendo da tecnologia em uso, este código precisa ser lapidado manualmente para que possa ser finalmente executado por um computador. Pessoas diferentes conhecem linguagens de programação diferentes, por isso alguém teve a idéia de usar os diagramas para permitir liberdade na escolha das linguagens textuais.

Usar uma linguagem visual pode ser uma boa idéia, mas não para obter independência de linguagem. Esse objetivo de escrever (ou deveria dizer desenhar?) um programa de forma independente de linguagem é simplesmente inviável. Nunca chegaremos lá, simplesmente porque não dá para projetar nada sem uma linguagem, uma representação, específica. Se você vai escrever alguma coisa, precisa escolher uma linguagem qualquer e, por definição, não existe isso de linguagem independente de linguagem.

O que podemos fazer é desenvolver linguagens cada vez de mais alto nível que poderão ser traduzidas para diversas outras de baixo nível. Quando finalmente conseguirmos ficar livres das linguagens de base, vamos descobrir que estamos presos à mais abstrata. Ela pode ser mais produtiva, intuitiva e todo o resto que você queira imaginar, mas ainda é uma linguagem. Isto não é ruim.

O propósito de usar linguagens visuais não é poder trocar a linguagem de suporte. De preferência, não devia nem haver uma representação textual intermediária que precise de ajustes manuais. Na Terra dos Modeladores Felizes, diagramas são executáveis do mesmo modo que um conjunto de instruções de máquina. A verdadeira razão de haver linguagens visuais é que algumas pessoas acreditam que elas são mais abstratas e produtivas.

Abstração é causa direta da produtividade. A segunda não vem sem a primeira. Não adianta usar uma linguagem visual se ela possui exatamente as mesmas construções de alguma outra textual. Se ela puder ser traduzida para a forma textual com uma correspondência direta, não há ganho de produtividade. Na verdade, é bem provável que haja perda. O ponto é que programadores passam muito pouco tempo realmente digitando (ou desenhando). A parte mais difícil e trabalhosa é feita dentro da cabeça. Acelerar a edição é se concentrar no problema errado. O que queremos mesmo é maior expressividade. Isto pode muito bem vir de uma linguagem visual.

Ou não.

Afinal, não é qualquer imagem que vale mais do que mil palavras.

Erros de programação não existem…

…isso é coisa da sua imaginação.

Às vezes — e aqui sempre não seria uma aproximação tão grosseira — quem desenvolve um programa não sabe exatamente em que sistema ele será usado. É fácil ter uma ligeira idéia, mas há usuários demais para que seja possível ter certeza. Você já deve ter ouvido slogans do tipo “Escreva uma vez, rode em qualquer lugar”, mas isso é só propaganda. Claro que uma plataforma comum dá alguma segurança e aumenta a produtividade. Mas se você quer saber exatamente como seu programa se comporta nas mais variadas configurações, você precisa testá-lo. Não tem jeito. Você precisa soltar o coitado neste lugar inóspito que é o mundo real e ver como ele se sai. Pode parecer injusto, mas é para o bem dele.

Os usuários esperam que as coisas Simplesmente Funcionem ™ e saber o que eles esperam é um trabalho de requisitos e testes, não de programação. Isto não quer dizer que programadores não devam fazer isso, mas que as habilidades que eles devem ter vão além de escrever código. Isto envolve muito condicionamento, bastante experiência e um bocado de imaginação. Mas se um programa não funciona do modo esperado em alguma situação, ele não está errado. Se eu não programar o EclipseFP para saber que alguns módulos poderão ser importados de outra parte do mundo fora do meu controle, se seu formulário não estiver preparado para o espertinho que escreve os números por extenso ou se Joel Spolsky não previr que na Polônia o Alt-Direito é usado para digitar caracteres especiais, não quer dizer que nossos programas estejam incorretos. Eles estão corretos para algumas das possíveis situações.

Todo programa é escrito e testado com algum uso em mente. Mesmo que os testes não sejam escritos em linguagem de computador, todo mundo testa o que escreve pelo menos uma vez de forma manual. Obviamente estou simplificando um pouco as coisas aqui. Afinal existe gente que escreve programas e não precisa (ou não quer) se dar ao luxo (ou à sanidade) de executá-los antes da entrega. Mas, por favor, vamos desconsiderar esses prodígios (ou azarados) pelo bem do argumento. Se o código for executado e validado várias vezes de forma automática à medida que evolui, a chance de que deixe de funcionar diminui. Mas o fato é que, mesmo se você estiver escrevendo testes automáticos como deveria, eles não cobrem todas as situações. Eles não conseguem fazer isso. É simplesmente impossível porque o espaço de busca é potencialmente infinito.

Testes não podem garantir que um programa nunca vai se comportar de forma inesperada. Nada pode. Se seu programa for usado por uma variedade razoável de pessoas, alguém em algum lugar do mundo vai ter a chance de dizer “como diabos eles não pensaram nisso?”. Tudo que você pode fazer, se você tiver cuidado e reservar bastante tempo para pesquisa e testes, é garantir que quem vá dizer isso esteja em um lugar bastante remoto. Mas vai haver alguém. Sempre.

Erros de programação — diferentemente de fantasmas, lobisomens e sacis — não existem. O que existe mesmo são situações não previstas e testes que ainda não foram escritos.