Você está na página 1de 17

[Off-Topic] O Programador

Humilde, por Edsger W.


Dijkstra
12 Abril 2010, 08:49 h

Depois de escrever meu artigo na Info, Fábrica de Software


é uma Besteira, recebi um retweet com um link muito legal
de um texto que eu não conhecia. The Humble
Programmer.

Claro, o autor é super conhecido, o grande Edsger W.


Dijkstra. Ele é mais conhecido pelo paper seminal A Case
against the GO TO Statement. De qualquer forma o The
Humble Programmer foi um discurso que ele deu ao
receber o prêmio Alan Turing de 1972.

O texto é fantástico e deve ser lido na íntegra, mas resolvi


retirar alguns trechos para comentar. O mais interessante é
ler com o contexto do fim dos anos 60 em mente e como
muito do que ele espera para o futuro é uma coisa que nós,
50 anos depois, ainda continuamos esperando. Não
publiquei este texto na Info mesmo por dois motivos:
primeiro porque é mais voltado a programadores, segundo
porque este é um dos meus textos “tamanho Akita” :-)

Duas opiniões sobre programação vem desses dias. Eu os menciono


agora, devo retornar a eles depois. Uma opinião era que um programador
competente deveria ter mente voltada a quebra-cabeças e gostar muito
de truques espertos; a outra opinião é que programação era nada mais
do que otimizar a eficiência do processo computacional, em uma direção
ou outra.

Infelizmente, a imagem do programador mudou, mas para


dois extremos: temos os super-programadores, autores
renomados, mas a profissão me si se tornou uma
commodity, um bem de consumo farto e barato,
justamente pelo que comentei no artigo sobre fábricas de
software. O barateamento artificial de profissão está
levando a um sucateamento e uma demora maior para
pesquisas e evoluções na área, especialmente se contarmos
que a competição para baixar o preço não vem a partir de
aumentar a qualidade técnicas dos processos e tecnologias,
mas principalmente de usar a globalização para levar a
tarefa para áreas onde os recursos humanos são mais
baratos, como Índia e China. Não quer dizer que todos da
profissão sejam operários, os que cresceram sozinhos e
evoluíram tem posições e tarefas respeitáveis, mas isso se
deve mais ao esforço individual.

Essa última opinião era o resultado da circunstância frequente que, de


fato, o equipamento disponível era dolorosamente lento, e nessa época
se encontrava a expectativa ingênua que, assim que máquinas mais
poderosas estivessem disponíveis, programação não seria mais um
problema, e então o esforço de ir até os limites da máquina não seriam
mais necessários e programação era basicamente isso, não era? Mas nas
décadas seguintes uma coisa completamente diferente aconteceu:
máquinas mais poderosas ficaram disponíveis. Mas em vez de nos
encontrarmos num estado de perfeita harmonia com todos os problemas
de programação resolvidos, nos encontramos até o pescoço na crise do
software! Como pode?

Pelo menos hoje ninguém assume que só porque as


máquinas serão melhores a programação será mais simples.
Mas notem uma coisa que sempre repito: as pessoas acham
que os problemas de software são coisas recentes, mas o
termo “crise do software” foi cunhado ainda na década de
70 … e até hoje não foi resolvida.

A visão é que, muito antes dos anos 70 terminarem, devemos ser capazes
de desenhar e implementar os tipos de sistemas que agora estão
forçando nossa habilidades de programação, ao custo de somente uma
pequena porcentagem de homens-anos que nos custam agora, e que
além disso, esses sistemas serão virtualmente livre de bugs. Essas duas
melhorias andam de mãos dadas. Nessa última questão software parece
ser diferente de muitos outros produtos, onde como regra uma alta
qualidade implica em alto preço. Aqueles que quiserem software
confiável descobrirão que devem encontrar meios de evitar a maioria dos
bugs para começo de conversa, e como resultado o processo de
programação se tornará mais barato. Se quiser programadores mais
efetivos, descobrirão que eles não devem desperdiçar tempo debugando,
eles não devem introduzir bugs para começo de conversa. Em outras
palavras: ambas os objetivos apontam para a mesma mudança.

“Programadores mais efetivos … não devem desperdiçar


tempo debugando”, isso é uma conclusão que existe há pelo
menos 5 décadas e ainda assim até hoje os programadores
da atualidade – treinados em faculdades para atender
“fábrica”, e portanto serem operários de ferramentas – tem
ataques de histeria quando sua ferramenta não tem
capacidades específicas para debugar!
“Aqueles que quiserem software confiável descobrirão que
devem encontrar meios de evitar a maioria dos bugs.” Ou
seja, a eficiência não vem de querer achar bugs o mais
rápido possível mas evitar que eles sejam introduzidos em
primeiro lugar. Sei que vai parecer bem pedante, mas nós
temos técnicas para isso e a grande maioria dos
programadores não as usa, sequer são ensinadas na
faculdade. Procure por Extreme Programming. Para evitar
que bugs sejam inseridos por descuido (não significa acabar
com 100% dos bugs mas evitar a maioria) existem técnicas
como Testar Primeiro, Programação em Par, Integração
Contínua, Testes para evitar Bugs de Regressão, Testes de
Aceitação. Enfim, existem diversas técnicas simples e
eficientes que a grande maioria do mercado sequer tem
consciência de sua existência.

Agora para as necessidades econômicas. Atualmente se encontra a


opinião que nos anos 60 programação era uma profissão muito cara, e
que nos anos seguintes salários de programadores devem cair.
Normalmente essa opinião é expressada conectada com a recessão, mas
poderia ser um sintoma de algo diferente e até saudável, de que talvez os
programadores de décadas passadas não fizeram um bom trabalho como
deveriam. A sociedade não está satisfeita com a performance dos
programadores e seus produtos. Mas há outro fator de maior peso. Na
situação presente é normal que para um sistema específico, o preço a ser
pago pelo desenvolvimento de software seja da mesma ordem de
magnitude do preço do hardware necessário, e a sociedade mais ou
menos aceita isso. Mas fabricantes de hardware nos dizem que na
próxima década os preços de hardware devem cair por um fator de 10. Se
o desenvolvimento de software continuar com os mesmos processos
desajeitados e caros como agora, as coisas devem ficar completamente
fora de balanço. Você não pode esperar que a sociedade aceite isso, e
portanto devemos aprender a programar com eficiência uma ordem de
magnitude maior. Para colocar de outra forma: enquanto as máquinas
eram os ítens mais caros no orçamento, a profissão de programação
conseguia livrar sua cara com suas técnicas desajeitadas, mas esse
guarda-chuva vai se fechar rapidamente.
E esse tem sido nosso desafio nas últimas décadas: como
“baratear” a tarefa de programação. Porém isso pode ser
levado para o lado errado. Um dos lados errados é baratear
a mão de obra, basta pegar países mais pobres e que
cobram menos. Outro lado é baratear a tecnologia. Isso
funciona até um ponto de inflecção onde precisaríamos de
um novo degrau de sofisticação mas não a teremos porque
o barateamento cortou as pesquisas e inovações da área. O
argumento todo é baseado em “como não tornar mais caro
o que hoje já é caro” e para isso uma das formas é avançar a
tecnologia para, por exemplo, demandar menos tarefas
manuais que possam ser automatizadas. Até hoje ainda há
“programadores” que perdem seu tempo abrindo as
mesmas janelas e clicando nos mesmos botões “next, next,
next” toda vez que precisa empacotar uma nova versão do
software. Isso é automatizável, mas como o programador
foi treinado para apenas seguir procedimentos, a maioria
não se acha capaz de ser criativo e transformar um
procedimento manual num script automatizado, por
exemplo. É um caso onde o “barateamento” da mão de
obra e de sua formação está impedindo o barateamento
dos processos e o avanço da tecnologia. Isso era um
problema nos anos 70, continua sendo no século XXI.

O argumento 3 é baseado na aproximação construtiva ao problema de


programar corretamente. Hoje uma técnica comum é fazer um programa
e depois testá-lo. Mas: teste de programa pode ser uma maneira muito
efetiva de mostrar a presença de bugs, mas é totalmente inadequado
para mostrar sua ausência. A única forma efetiva de aumentar o nível de
confiança de um programa significativamente é dar uma prova
convincente de sua correção. Mas então não se deve fazer o programa
primeiro e depois provar sua correção, porque senão o requerimento de
fornecer a prova somente aumentará a carga do pobre programador. Do
contrário: o programador deve fazer a prova de correção e o programa
crescerem de mãos dadas. O argumento 3 é essencialmente baseada na
seguinte observação. Se o primeiro perguntar qual é a estrutura que uma
prova convincente deve ter, tendo encontrado isso, então construir um
programa que satisfaz os requerimentos da prova, então essa
preocupação de correção se torna um guia heurístico muito efetivo.

Preciso admitir que o Dijkstra se refere a provas formais


matemáticas, neste caso. Mas eu gostaria de expandir o
conceito. O que ele diz é que o correto seria primeiro criar
uma “prova” e depois implementar o código que supre os
requerimentos dessa “prova”. Eu gostaria de dizer que
Dijkstra praticamente foi pioneiro no conceito de “Test First”
de Extreme Programming, também conhecido
como TDD ou “Test Driven Development” onde o conceito
é: 1) primeiro escreva um teste que descreve o
requerimento; 2) agora implementamos o código que faz o
teste falhar; 3) em seguida implementamos o restante que
faz o teste passar; 4) vamos para o próximo requerimento.
Dijkstra sabia há 50 anos que esta ordem de
desenvolvimento é que minimiza o volume de bugs a
posteriori – além de dezenas de outros benefícios que já
descobrimos hoje.

O argumento 4 tem a vez com a maneira com que a quantidade de


esforço intelectual necessário para desenhar um programa depende do
tamanho do programa. Foi sugerido que existe algum tipo de lei da
natureza nos dizendo que a quantidade de esforço intelectual necessário
cresce com o quadrado do tamanho do programa. Mas, graças aos céus,
ninguém conseguiu provar essa lei. E isso porque ela não precisa ser
verdade. Todos sabemos que a única ferramenta mental que exige uma
peça finita de raciocínio e pode cobrir diversos casos é chamada de
“abstração”; como resultado a exploração efetiva de seus poderes de
abstração devem ser consideras como uma das atividades mais vitais de
um programador competente. Nesse sentido deve valer a pena apontar
que o propósito de abstração não é ser vago, mas criar um novo nível
semântico onde se pode ser absolutamente preciso. (…) Um resultado foi
a identificação de vários padrões de abstração que tem um papel vital em
todo o processo de composição dos programas. O suficiente já é
conhecido sobre esses padrões de abstração para devotar uma aula sobre
cada um deles.

Isso é mais complexo mas tem a ver com a capacidade de


abstração do programador. Em si esse conceito é “abstrato”
e difícil de definir. A primeira parte tem a ver com talento:
uma pessoa que não tem talento para programação não
será um programador e ponto final. Partindo dessa
premissa, que existe a centelha do talento, agora são
necessárias milhares de horas de prática, e eu me refiro a
prática de código mesmo e não apenas repetindo
procedimentos, mas experimentando as situações mais
diferentes possíveis.

Só assim a intuição vai emergir da experiência


possibilitando identificar padrões no código, oportunidades
de refatoramentos e otimizações, criação de construções de
mais alto nível para simplificar o programa e assim por
diante. Esse caminho começa com o programador seguindo
procedimentos, mas deve evoluir rapidamente para e
experimentação. Isso é fundamental.

Como curiosidade, parte do que ele descreve é o que hoje


conhecemos também como “Design Patterns”.

Agora para o quinto argumento. Tem a ver com a influência da


ferramenta que estamos tentando usar contra nossos hábitos de
pensamento. Eu observo uma tradição cultural, que em todas as
probabilidades tem suas raízes na Renascença, de ignorar essa influência,
de tomar a mente humana como um mestre supremo e autônomo de
seus artefatos. Mas se eu começar a analisar os hábitos de pensamentos
meus e de meus colegas humanos, eu chego, quer eu queira ou não, a
uma conclusão completamente diferente, que as ferramentas que
estamos tentando usar e a linguagem ou notação que usamos para
expressar ou registrar nossos pensamentos, são os maiores fatores que
determinam o que podemos pensar ou expressar! A análise dessa
influência que linguagens de programação tem nos hábitos de
pensamento de seus usuários, e o reconhecimento que, agora, poder
cerebral é de longe o recurso mais escasso, juntos nos dão uma nova
coleção de parâmetros para comparar méritos relativos de várias
linguagens de programação. O programador competente é totalmente
consciente do tamanho estritamente limitado de seu crânio; então ele
aproxima da tarefa de programação com total humildade, e entre outras
coisas ele evita truques espertos como se fosse a praga. (…) Outra lição
que devemos aprender do passado recente é que o desenvolvimento de
linguagens de programação “mais ricas” ou “mais poderosas” foi um erro
no sentido de que essas mostruosidades barrocas, essas conglomerações
de idiossincrasias, são realmente ingerenciáveis, ambas mecanicamente e
mentalmente. Eu vejo um grande futuro para linguagens de programação
muito sistemáticas e muito modestas.

Um bom programador reconhece suas limitações e busca


ferramentas que melhor se adequem aos problemas. A
maioria das guerras de linguagens começa com
argumentos assim, mas o problema é que a insistência em
se ter “apenas uma linguagem” e acumular em cima dela
tudo que todos querem, a torna um monstro “barroco”,
como Dijkstra coloca. Programadores – pessoas – tem
cérebros limitados, e nós precisamos conseguir nos
expressar em forma de código. Quanto mais complicada a
ferramenta, mais da mente é gasta para manter as peças na
cabeça e menos em criar código elegante.

Veja o próprio Dijkstra, até os anos 70, ele literalmente viu a


criação dos computadores, programou em praticamente
tudo, desde linguagem de máquina, até Fortran, Lisp, Algol.
Se naquela época ele poderia conhecer a fundo múltiplas
linguagens, eu não vejo desculpa para hoje, com todos os
recursos extras que temos, não sabermos uma ordem de
magnitude mais linguagens e tecnologias e técnicas.
Ele menciona linguagens mais “modestas” e eu diria que
isso são nossas atuais linguagens dinâmicas de alto nível
como Ruby ou Python. Abstrações que permitem sistemas
ainda maiores com menos complexidade.

Para complementar quero colocar um aviso a aqueles que identificam a


dificuldade da tarefa de programação com a briga contra as
inadequações de nossas ferramentas atuais, porque eles podem concluir
que, uma vez que nossas ferramentas se tornem mais adequadas,
programação não será um problema. Programação continuará sendo
muito difícil, porque uma vez que nos livrarmos dos desajeitos
circunstanciais, nos encontraremos livres para lidar com problemas que
agora estão muito além da nossa capacidade de programação.

E desde a década de 70 sabemos que não existe bala de


prata, não é uma nova ferramenta que vai simplesmente
tornar a tarefa de programar magicamente ordens de
magnitude mais eficientes. Não existe almoço de graça.

Isso já nos ensinou algumas lições, e a que eu escolhi estressar nesta


palestra é o seguinte. Nós devemos fazer um trabalho de programação
melhor, dado que nos aproximemos da tarefa com total apreciação por
sua tremenda dificuldade, dado que nos seguremos a linguagens de
programação modestas e elegantes, dado que nós respeitemos as
limitações intrínsecas da mente humana e aproximemos da tarefa como
Programadores Muito Humildes.

Programadores precisam ser Humildes no sentido correto


da palavra: em assumir suas próprias limitações e criar
novas técnicas, tecnologias e maneiras de executar o
mesmo trabalho com mais qualidade, mais eficiência,
quebrando as regras e tradições e criando novos padrões.
Programadores que seguem apenas o que foi ensinado,
apenas os procedimentos, são arrogantes porque acham
que tudo que já poderia ter sido descoberto já foi.
Eu sempre repito que um bom programador é burro e
preguiçoso. Burro porque se ele se achar inteligente vai
achar também que já sabe tudo, e se já sabe tudo para que
pesquisar mais? E preguiçoso porque um programador
esforçado demais vai seguir o mesmo procedimento todos
os dias, com muito afinco, já um preguiçoso vai se cansar
de fazer isso e vai criar uma forma automatizada para fazer
o mesmo trabalho e sobrar mais tempo para ele descansar.

Acho que o Dijkstra concordaria com essa colocação ;-)

[Tradução] Metaprogramação
em Ruby: é tudo sobre Self
16 Novembro 2009, 14:56 h

Hoje o Yehuda Katz publicou um artigo muito didático


sobre metaprogramação em Ruby que achei legal traduzir.
Aí vai:

Depois de escrever meu último post sobre idiomas de


plugins Rails, eu percebi que metaprogramação Ruby, no
fundo, é na realidade bem simples.

Tem a ver com o fato de que todo código Ruby é executado


– não há separação entre fases de compilação e runtime,
cada linha de código é executado contra um self particular.
Considere os próximos 5 trechos de código:

1 class Person
2 def self.species
3 "Homo Sapien"
4 end
5 end
6
class Person
7
class << self
8
def species
9
"Homo Sapien"
10
end
11
end
12
end
13
14
class << Person
15
def species
16
"Homo Sapien"
17
end
18
end
19
20
Person.instance_eval
21
do
22
def species
23
"Homo Sapien"
24
end
25
end
26
27
def Person.species
28
"Homo Sapien"
29
end

Todos os 5 trechos definem um Person.species que


retornam Homo Sapiens. Agora considere outro conjunto de
trechos:

1
2 class Person
3 def name
4 "Matz"
5 end
6 end
7
8 Person.class_eval do
9 def name
1 "Matz"
0 end
1 end
1
Todos esses trechos definem um método chamado name na
classe Person. Então Person.new retornará “Matz”. Para
aqueles familiarizados com Ruby, isso não é novidade.
Quando se aprende sobre metaprogramação, cada um
desses trechos é apresentado de forma isolada: outro
mecanismo para colocar métodos onde eles “pertencem”.
Na verdade, entretanto, existe uma única explicação
unificada de porque todos esses trechos de cóigo
funcionam da forma como funcionam.

Primeiro, é importante entender como a metaclasse de


Ruby funciona. Quando você aprende Ruby, você aprende
sobre o conceito de classe, e que cada objeto de Ruby tem
um:

class Person
1
end
2
3
Person.class #=> Class
4
5
class Class
6
def loud_name
7
"#{name.upcase}!"
8
end
9
end
10
11
Person.loud_name #=>
12
"PERSON!"

Person é uma instância de Class, então qualquer método


adicionado a Class está disponível em Person também. O
que não lhes é dito, entretanto, é que cada objeto em Ruby
também tem seu próprio metaclass, uma Class que pode
ter métodos, mas está anexado apenas ao objeto.

1 matz = Object.new
2 def matz.speak
"Place your burden to machine's
3
shoulders"
4
end

O que está acontecendo é que estamos adicionando o


método speak à metaclass de matz, e o objeto matz herda de
sua metaclass e depois de Object. A razão de porque isso
não é tão claro é porque o metaclass é invisível em Ruby:

matz = Object.new
1
def matz.speak
2
"Place your burden to machine's
3
shoulders"
4
end
5
6
matz.class #=> Object

Na verdade, a “classe” de matz é sua metaclass invisível.


Podemos ter acesso a essa metaclass assim:

metaclass = class << matz; self; end


1
metaclass.instance_methods.grep(/speak/) #=>
2
["speak"]

Até este ponto, você provavelmente está tendo que se


esforçar para ter tantos detalhes na cabeça; parece que
existem regras demais. E que diabos é isso de class <<
matz?

Acontece que todas essas regras esquisitas se resumem em


um conceito simples: controle sobre o self em uma
determinada parte do código. Vamos retornar à um dos
trechos que já vimos antes:

1 class Person
2 def name
3 "Matz"
end
4
5
self.name #=>
6
"Person"
7
end

Aqui, estamos adicionando o método nome à classe Person.


Quando dizemos class Person, o self até o fim do bloco é a
própria classe Person.

Person.class_eval do
1
def name
2
"Matz"
3
end
4
5
self.name #=>
6
"Person"
7
end

Aqui, estamos fazendo exatamente a mesma coisa:


adicionando o método name a instâncias da classe Person.
Neste caso, class_eval deixa o self ser o Person até o fim
do bloco. Isso é perfeitamento direto quando se lida com
classes, e igualmente direto quando se lida com
metaclasses:

def Person.species
1
"Homo Sapien"
2
end
3
4
Person.name #=>
5
"Person"

Como no exemplo do matz anteriormente, estamos


definindo o método species à metaclass de Person. Nós não
manipulamos self, mas você pode ver o uso de def num
objeto anexa o método à metaclass desse objeto.
class Person
1
def self.species
2
"Homo Sapien"
3
end
4
5
self.name #=>
6
"Person"
7
end

Aqui, abrimos a classe Person, fazendo


o self ser Person pela duração do bloco, como no exemplo
acima. Entretanto, estamos definindo um método à
metaclasse de Person aqui, já que estamos definindo o
método em um objeto (self). Você também pode ver
que self.name enquanto dentro da classe Person é idêntico
a Person.name enquanto fora dela.

1 class << Person


2 def species
3 "Homo Sapien"
4 end
5
6 self.name #=> ""
7 end

Ruby dá uma sintaxe para acessar a metaclass de um objeto


diretamente. Fazendo class << Person, estamos fazendo
o self ser a metaclass de Person pela duração do bloco.
Como resultado, o método species é adicionado à
metaclass de Person, em vez da classe propriamente dita.

1 class Person
2 class << self
3 def species
4 "Homo Sapien"
5 end
6
7 self.name #=> ""
8 end
9 end

Aqui, combinamos diversas técnicas. Primeiro,


abrimos Person, tornando self igual à classe Person. Em
seguida, fazemos class << self, tornando self igual à
metaclass de Person. Quando definimos o método species,
ela é definida na metaclass de Person.

Person.instance_eval do
1
def species
2
"Homo Sapien"
3
end
4
5
self.name #=>
6
"Person"
7
end

O último caso, instance_eval na realidade faz algo


interessante. Ela quebra o self no self que é usado para
executar métodos e o self que é usado quando novos
métodos são definidos. Quando instance_eval é usado,
novos métodos são definidos na metaclass, mas o self é o
próprio objeto.

Em alguns desses casos, as múltiplas formas de atingir a


mesma coisa sai naturalmente da semântica de Ruby.
Depois desta explicação, deve ficar claro que def
Person.species, class << Person; def species, e class
Person; class << self; def species não são três maneiras
de fazer a mesma coisa que nasceram juntas, mas elas
acabam saindo da própria flexibilidade do Ruby em relação
a o que o self é em determinado ponto do seu programa.

Por outro lado, class_eval é um pouco diferente. Porque ele


recebe um bloco, em vez de agir como uma palavra-
reservada, ela captura as variáveis locais ao redor dela. Isso
fornece a possibilidade poderosas capacidades de DSLs, em
adição a controlar o self usado em um bloco de código.
Mas além disso, ele é idêntico às outras construções aqui.

Finalmente, instance_eval quebra o self em duas partes, ao


mesmo tempo que dá acesso a variáveis definidas fora dela.
Na tabela a seguir, define um novo escopo significa que
código dentro do bloco não tem acesso a variáveis locais
fora do bloco.

mecanismo método de resoluçãodefinição de métodonovo escopo?

class Person Person mesmo sim

class << Person metaclass do Person mesmo sim

Person.class_eval Person mesmo não

Person.instance_evalPerson metaclass do Person não

Também note que class_eval está apenas disponível


a Modules (note que Class herda de Module) e é um
sinônimo para module_eval. Além disso, instance_exec, que
foi adicionado ao Ruby 1.8.7, funciona exatamente
como instance_eval, exceto que ele também lhe permite
enviar variáveis a um bloco.

Você também pode gostar