Archive for the ‘programando’ Category

Uh-Éme-Éle

Friday, July 25th, 2008

Este tópico no GUJ chamou atenção, especialmente porque o li logo após um interessante texto noblog do Marcelo Araújo sobre uma outra faceta do mesmo tema: modelagem usando especificações não executáveis.

O que eu quero dizer com isso? Dada a tecnologia atual na maioria das empresas (desconsiderando o uso de coisas como DSLs ou mesmo alguma solução MDSD que, ao contrario de MDA, funcione) todos os documentos comumente utilizados para “modelagem” não são verificáveis, não são executáveis e requerem um trabalho manual enorme. Como bem disse o Emerson Macedo no post, você acaba programando duas vezes, uma na sua notação gráfica (UML) e uma na sua notação executável (Java, C#, Ruby… o que for).

Acontece que ao passar do diagrama (e diagramas aceitam qualquer besteira) para o código (onde o compilador e testes unitários são muito exigentes – fora os usuários) o tal do pseudo-modelo criado pelo “analista” no Rational Rose (porque a empresa não percebeu que o Rose foi descontinuado há anos) é completamente diferente do modelo implementado. As classes até têm o mesmo nome mas a mecânica interna é bem diferente. E por quê? Porque UML não vai te oferecer tudo o necessário para modelar. O mínimo que se espera de um modelo de um sistema é que ele seja verificável para saber se cumpre seus requisitos. Como é que eu vou saber isso com UML? Como eu testo UML?

Há algum tempo que eu me pergunto porque que se usa UML para “modelar”. Não me entenda mal, UML é uma ótima noção gráfica para Orientação a Objetos e com ela você consegue passar uma big picture muitas vezes mais rapidamente do que código; mas ela fica por aí: comunicação.

Quando modelamos o comportamento de objetos nós estamos descrevendo como este se comporta em diversas situações. Ao modelar uma determinada atividade você precisa descrever um conjunto grande de detalhes sobre esta, e UML não esta pronta para este tipo de coisa - e nem é a idéia por trás dela. Se modelar em UML fosse eficiente nós estaríamos programando em UML não em C#, Java ou o que for.

O primeiro problema ao tentar introduzir este pensamento na indústria é: mas eu não posso deixar que meus “implementadores” façam o que quiserem. Pergunta: o que raios é um implementador? Um datilografo de luxo?

Supondo que sua empresa seja a típica empresa de três letrinhas, aquelas que contratam qualquer um como “programador júnior” porque ele vai receber instruções do analista. Neste cenário eu pergunto: para que o tal programador? Que tal dar ao seu “analista” uma ferramenta mágica em que ele consiga criar um modelo abstrato e ao mesmo tempo executável? Que tal se ao invés de gastar rios de dinheiro pagando 10-reais-mais-o-busão para seus implementadores você eliminasse completamente a necessidade deles, fazendo com que o “analista” sozinho cuide do serviço?

Não precisa sacar seus milhões do banco nem pensar em como justificar o gasto com esta ferramenta no orçamento, você muito provavelmente já possui o necessário dentro da sua empresa: uma linguagem de programação decente. Faça seu “analista” usar código para expressar a modelagem. No final dá no mesmo para o nível de modelagem desejada e você ainda ganha algo verificável e executável. Economia de rios de dinheiro quase que instantânea.

Mas como assim? O “modelo lógico” é muito diferente do “modelo físico” e o “analista” não pode perder tempo com essas coisinhas de tecnologia, tem que pensar em modelar o negocio. Muito bem, duas coisas:

  1. Analista de Sistemas não é analista de negócios. Se você não sabe o que é um analista de negócios eu recomendo que você mande um e-mail pro Paulo Vasconcellos perguntando.
  2. Há mais de cinco anos que não há quase nenhum motivo para que uma aplicação Java ou C# não seja uma cópia de 1-para-1 de um modelo UML do tipo “diagrama de domínio”. POJO/POCO, Hibernate, IoC, Camadas, OO, AOP, EJB3, MVC… toda essa parafernália de letrinhas permite que seus objetos de negocio não tenham o menor traço de infra-estrutura. Claro que alguém vai ter que fazer a infra-estrutura –ainda que seja mínima. Caso seus “analistas” sejam de qualidade extremamente baixa eu recomendo que você contrate um bom técnico para atuar como arquiteto e líder técnico do time.

Verdade seja dita: esta não é a primeira vez que este blog trata do tema e desde a última vez muita coisa melhorou, mas ainda há muito que melhorar.

Rastreabilidade em User Stories

Friday, July 18th, 2008

Pergunta na XP-Rio:

Me ajudem no seguinte, no processo unificado, espera-se que todos os casos de uso gerem algum tipo de artefato dentro do projeto. Na verdade, com os casos de uso espera-se chegar em classes que juntas formarão o sistema. Dentro da metodologia existe artefatos que quando feitos, permitem mapear todos os artefatos gerados a partir de um caso de uso. A matriz de rastreabilidade.

Gostaria de saber como o XP permite chegar as classes (código) a partir das User Stories. Por exemplo, dado uma User Stories, quais são as classes geradas para atender essa User Stories?

O XP permite isso? Como é feito?

Provavelmente a primeira coisa a se perguntar é: por que eu precisaria deste dados? Alguns modelos de processo como CMMI definem rastreabilidade, que geralmente é definida nesta linha:

Requirements traceability refers to the ability to describe and follow the life of a requirement, in both forwards and backwards direction (i.e. from its origins, through its development and specification, to its subsequent deployment and use, and through all periods of on-going refinement and iteration in any of these phases.)

- Gotel O.C.Z and Finklestein A.C.W (tirado da Wikipedia)

A necessidade parece justa e real, é preciso saber quais partes do software são afetadas por mudanças no negocio e vice-versa. O grande problema desta métrica é que transformações de modelo não são mais tão drásticas há algumas décadas. Ao utilizar qualquer tecnologia relativamente comum (Java, Ruby, .Net, Python, JavaScript…) não existe motivo técnico para que o software não modele o negocio em uma relação próxima de 1-para-1, desta forma saber qual parte de um código realiza qual tarefa torna-se bem simples.

Outro problema é que eu nunca vi um artefato de rastreabilidade, seja qual for, que não ficasse defasado. Se você abraça uma metodologia ágil sabe que refactoring faz parte da vida.

Mesmo com todos os problemas que esta métrica possui eu entendo que existem casos onde seu uso se faz necessário –especialmente por equipes tentando introduzir agilidade em ambientes tradicionais. Supondo que exista uma necessidade justificável para algum controle deste tipo, metodologias ágeis em geral possuem um mecanismo muito mais eficiente que documentos ou mesmos sistemas: testes.

Toda história, todo cartão, deve ter um critério de aceite. O critério de aceite traz o que o cliente considera ser o mínimo para que a história seja considerada concluída. Por exemplo:

História: Como administrador eu quero criar um novo grupo para que possa aplicar permissões à diversos usuários de uma só vez.
Critérios de Aceite:

  • O novo grupo criado deve possuir o usuário que o criou como seu administrador
  • O novo grupo não deve conter outros membros
  • O novo grupo não deve ser visível na listagem de grupos

Cada um destes critérios via um teste unitário e/ou funcional que é executado como parte da integração contínua. Para saber exatamente quais classes e códigos são afetados por cada um deles é simples: use uma ferramenta de test coverage como o Emma e execute apenas os testes de aceitação para aquele cartão. A ferramenta irá gerar relatórios como estes:

[class, %]	[method, %]	[block, %]	[line, %]	[name]
25%  (1/4)!	25%  (3/12)!	40%  (3012/7446)!	25%  (3/12)!	com.sun.tools.javac.v8.resources
94%  (16/17)!	49%  (41/83)!	48%  (1111/2292)!	45%  (201.1/450)!	com.sun.tools.javac.v8
88%  (45/51)!	61%  (242/397)!	54%  (3070/5729)!	52%  (809.6/1563)!	com.sun.tools.javac.v8.tree

Classes que possuem alguma cobertura foram afetadas pelo teste, logo são parte da execução daquela história. Não é difícil escrever um script que –talvez como parte do build- processe estes relatórios e gere o artefato de rastreabilidade que seu processo te obrigar a fazer. Melhor ainda: este artefato nunca estará desatualizado.

Só não se esqueça que melhoria contínua é um conceito importante. Se o artefato de rastreabilidade é algo tão ineficiente a obrigação de um desenvolvedor que perceba isto é mostrar para sua organização que o valor desta pratica é nulo. Não se acomode.

Ruby é JavaScript ao Avesso

Thursday, June 12th, 2008

O titulo é uma brincadeira mas é uma boa forma de lembrar algumas coisinhas sobre programação nestas linguagens. Cada vez mais lidamos no dia-a-dia com conceitos que estão presentes há décadas em linguagens mais esotéricas mas nunca deram as caras no mainstream, um deles é o uso de funções como abstração. Existe um conflito de termos aqui então só para deixar claro eu não estou falando de funções como em programação procedural mas sim de funções como vemos em closures.

Muita gente tem escrito sobre como devemos aprender programação funcional. Eu concordo mas não posso deixar de notar que quando alguém diz programação funcional geralmente ela quer dizer Higher-Order Functions.

E o que é isso? Bom, uma linguagem possui higher-order quando uma função pode receber como parâmetro outra função. O nome deriva do fato de que uma função que recebe outra é considerada de ordem 1, uma função que recebe outra que recebe outra é considerado 3 e assim em diante.

JavaScript possui higher-order programming. Funções são a abstração principal em javaScript e elas podem ser passadas à vontade pelo programa. Por exemplo, vamos supor que queremos comparar dois objetos de acordo com um critério arbitrário. Em JavaScript podemos fazer algo assim:

function melhorEntre(um, outro, criterio){
  if(criterio(um, outro)){
    return um;
  }
  else {
    return outro;
  }
}

sorvete1 = {sabor: 'morango'};
sorvete2 = {sabor: 'chocolate'};

prefiroChocolate = (function (s1, s2){
                                   return (s1.sabor === 'chocolate');
                            });

prefiroMorango = (function (s1, s2){
                                  return (s1.sabor === 'morango');
                            });

alert(melhorEntre(sorvete1, sorvete2, prefiroMorango).sabor);
alert(melhorEntre(sorvete1, sorvete2, prefiroChocolate).sabor);

Em Ruby o código ficaria um pouco diferente:

def melhor_entre(um, outro, criterio)
    if criterio.call(um, outro)
        um
    else
        outro
    end
end

sorvete_1 = { :sabor => 'chocolate' }
sorvete_2 = { :sabor => 'morango' }

prefiro_chocolate = lambda {|s1,s2| s1[:sabor] == 'chocolate'}
prefiro_morango = lambda {|s1,s2| s1[:sabor] == 'morango'}

puts melhor_entre(sorvete_1, sorvete_2, prefiro_morango)[:sabor]
puts melhor_entre(sorvete_1, sorvete_2, prefiro_chocolate)[:sabor]

Agora vamos pensar: as duas linguagens possuem higher-order programming? Não, só JavaScript possui. Em Ruby o que é passado não é uma função e sim um objeto, veja só:

prefiro_chocolate = lambda {|s1,s2| s1[:sabor] == 'chocolate'}
puts prefiro_chocolate
#=> #<Proc:0x00028a64@tmp/compara.rb:19>
puts prefiro_chocolate.class
#=> Proc

O principal divergente da solução em Ruby é que você deve passar a mensagem call para o objeto (ou usar a palavra-chave yield). Na pratica do dia-a-dia não tem tanta diferença e é comum falar em higher-order Ruby.

Em Ruby não precisamos de funções de verdade para termos higher-order programming, podemos usar objetos para modelar as funções. Em JavaScript não temos construções especiais para objetos, mas utilizamos funções:

function Sorveteiro(){
  this.numeroDeVendidos = 0;
  this.vender = function(){this.numeroDeVendidos++;};
};

s = new Sorveteiro();
alert(s.numeroDeVendidos);
s.vender();
alert(s.numeroDeVendidos);
s.vender();
s.vender();
alert(s.numeroDeVendidos);

Alguém me disse essa semana que uma das grandes vantaens em aprender higher-order programming (a pessoa falou em programação funcional mas não é bem isso que ela quis dizer) é que com ela você simula objetos mas o contrario não é verdade. Bom, não é assim. Nada impede de você ter higher-order programming em uma linguagem Orientada a Objetos (JavaScript é Orientada a Objetos!) e com objetos você pode facilmente modelar higher-order programming.

E qual a diferença disso tudo para programação funcional? Bom, programação funcional usa higher-order programming, mas não é isso que define uma linguagem funcional (JavaScript não é funcional).

Em 1984, John Hughes publicou um paper chamado “Why Functional Programming Matters”. Este paper é, até hoje, uma das obras mais importantes para o paradigma. Nele o autor descreve:

The special characteristics and advantages of functional programming are often summed up more or less as follows. Functional programs contain no assignment statements, so variables, once given a value, never change. More generally, functional programs contain no side-effects at all. A function call can have no effect other than to compute its result. This eliminates a ma jor source of bugs, and also makes the order of execution irrelevant - since no side-efect can change the value of an expression, it can be evaluated at any time. This relieves the programmer of the burden of prescribing the flow of control. Since expressions can be evaluated at any time, one can freely replace variables by their values and vice versa - that is, programs are “referentially transparent”. This freedom helps make functional programs more tractable mathematically than their conventional counterparts.

Erik Meijer apresentou uma palestra no JAOO chamada “Why Functional Programming (still) Matters” onde ele afirma que nenhuma linguagem é realmente funcional, provando que se pode simular efeitos colaterais em Erlang, Haskell, F# e várias outras.

A conclusão do Erik -é claro que defendendo suas decisões ao criar o LINQ- é que os conceitos por trás das linguagens ditas funcionais são mais importantes do que ser uma linguagem puramente funcional ou não.

Isso significa que você deve aprender sobre programação funcional e aplicar suas técnicas sempre que necessário mas cuidado com o termo “funcional”. Na maioria das vezes você quis dizer Higher-Order Programming.

Update: Alguns dos comentários msotram uma confusão com funções javaScript e objetos. Não só o texto falou que em JavaScript funções são objetos bem como ele mostrou o exemplo do sorveteiro, mas tentando deixar ainda mais claro:

ds

Singletons…

Wednesday, May 28th, 2008

Singleton Considered Stupid

Nem só de troca de mensagens vivem os objetos

Sunday, May 25th, 2008

Percebi que boa parte das dúvidas quanto ao meu post sobre como objetos não possuem atributos se deve ao fato das pessoas não terem geralmente um conhecimento real sobre o que é troca de mensagens.

Perfeitamente compreensível. Na maior parte dos livros e faculdades as pessoas aprendem que Orientação a Objetos é sobre como utilizar classes e sobre como as funções são chamadas de métodos. Por algum motivo esquecido nas areias do tempo decidiu-se que chamar o método em uma classe era passar uma mensagem e por isso algumas pessoas notoriamente pedantes usam este termo ao invés de dizer apenas “chama a função”.

Bem, os conceitos no parágrafo acima estão errados. Orientação a Objetos não é sobre classes e sim sobre…er… objetos. Você pode ter OO sem ter classes, como JavaScript e Io e pode ter também OO sem mensagens.

Troca de mensagens é um conceito utilizado em diversas áreas, não apenas Orientação a Objetos. Você pode ter um Sistema Operacional baseado neste conceito -como o MINIX por exemplo- ou criar uma arquitetura de computação distribuída como SOAP.

O que distingue a passagem de mensagens é o fato de que o recipiente da mensagem, seja um objeto, um serviço ou um processo, é quem decide o que é feito em decorrência de sua invocação.

Para tentar içar um pouco mais claro eu criei um meta-modelo bem bobinho em Java. Este representa um sistema Orientado a Objetos com classes e passagem de mensagens. O código abaixo mostra como declarar uma Classe calculadora e enviar uma mensagem dizendo para que esta multiplique números.

BlocoDeCodigo bloco = new BlocoDeCodigoImpl<Integer, Integer, Integer>(){
			public Integer executar(Integer a, Integer b){
				return a * b;
			}
		};

		Classe classeCalculadora = novaClasse("Calculadora");
		classeCalculadora.declaraMensagem("multiplique", bloco);

		Instancia calculadora = instanciar("Calculadora");

		assertEquals(8, calculadora.enviaMensagem("multiplique", 2, 4));

Note os passos realizados. Primeiro criamos um bloco de código, uma função. Depois dizemos ao sistema que existe uma classe chamada calculadora. Logo apos registramos o fato de que calculadora responde a uma mensagem executando o bloco que havíamos declarado.

Em termos de semântica, este código é mais ou menos equivalente a este:

public class Calculadora{
 public Integer multiplicar(Integer a, Integer b){
  return a * b;
}
}

Depois nós instanciamos a classe e passamos uma mensagem para ela, o que seria equivalente a:

Calculadora calc = new Calculadora();
calc.multiplicar(2,4);

As classes relevantes:

public class Ambiente {
	static Map<String, Classe> classesDeclaradas = new HashMap<String, Classe>();

	static Classe novaClasse(String nomeDaClasse) {
		ClasseImpl classe = new ClasseImpl(nomeDaClasse);
		classesDeclaradas.put(nomeDaClasse, classe);
		return classe;
	}

	static Instancia instanciar(String nomeDaClasse) {
		return new Instancia(classesDeclaradas.get(nomeDaClasse));
	}
}

public class ClasseImpl implements Classe {

	private final String nome;

	private Map<String, BlocoDeCodigo> mensagens = new HashMap<String, BlocoDeCodigo>();

	public ClasseImpl(String nome) {
		this.nome = nome;
	}

	public void declaraMensagem(String nomeDaMensagem, BlocoDeCodigo blocoASerExecutado) {
		mensagens.put(nomeDaMensagem, blocoASerExecutado);
	}

	public String nome() {
		return nome;
	}

	public boolean respondeA(String nomeDaMensagem) {
		return mensagens.containsKey(nomeDaMensagem);
	}

	public BlocoDeCodigo codigoParaMensagem(String nomeDaMensagem) {
		return mensagens.get(nomeDaMensagem);
	}

}

public class Instancia {
	private final Classe minhaClasse;

	public Instancia(Classe classe) {
		this.minhaClasse = classe;
	}

	public Object enviaMensagem(String mensagem, Object... args) {
		Object primeiro = args.length > 0 ? args[0] : null;
		Object segundo = args.length > 1 ? args[1] : null;

		return minhaClasse.codigoParaMensagem(mensagem).executar(primeiro,
				segundo);
	}

	public Classe classe() {
		return minhaClasse;
	}

}

Esse meta-modelo é baseado em troca de mensagens. A classe Calculadora não recebe código a ser executado ela apenas recebe o nome de uma mensagem e parâmetros. Imagine que eu registre o mesmo bloco de código para várias mensagens, ou que eu use recursos de AOP e intercepte a execução do bloco. Nada disso é relevante para quem invoca a mensagem, ele apenas a envia e o que acontece em decorrência disso é responsabilidade do receptor.

Como quase todas as linguagens atuam desta forma pode ser difícil entender o conceito já que nunca se viu nada diferente. Vamos então implementar outro meta-modelo que não usa troca de mensagens mas sim uma outra forma chamada Data-Directed.

Nesta forma de invocar operações em objetos –que, como a anterior não é específica de OO- quem decide qual função será aplicada é o ambiente de execução, o runtime. Quando você invoca uma operação o ambiente vai procurar dentre os métodos registrados qual é o aplicável para aquele objeto e vai executar o método nele. Common Lisp utiliza este recurso de maneira tão poderosa em suas Generic Functions que praticamente elimina a necessidade de coisas como proxies e AOP.

Nosso meta-modelo para Data-Directed é executado dessa forma:

BlocoDeCodigo bloco = new BlocoDeCodigoImpl<Integer, Integer, Integer>(){
			public Integer executar(Instancia instancia, Integer a, Integer b) {
				return a * b;
			}
		};

		Classe classeCalculadora = novaClasse("Calculadora");
		registrarMetodo("multiplique", classeCalculadora, bloco);

		Instancia calculadora = instanciar("Calculadora");

		assertEquals(8, executarMetodo("multiplique", calculadora, 2, 4));

Repare que agora o bloco de código recebe como seu primeiro argumento uma referência para a instancia a qual se aplica (se você já usou java.lang.Method sabe que isso não é incomum quando se desce ao nível de implementação de linguagem). Caso nosso exemplo fosse minimamente usável seria desta forma que o bloco obteria acesso ao objeto em si.

Logo depois criamos a classe como antes mas ao invés de registrar uma mensagem na classe nós registramos um método no ambiente, dizendo que o método se aplica àquela classe. A invocação em si é bem parecida com a anterior.

Na implementação a única classe mais interessante é o Ambiente, que agora é bem mais esperto:

public class Ambiente {
	static Map<String, Classe> classesDeclaradas = new HashMap<String, Classe>();
	static Map<String, Map<Classe, BlocoDeCodigo>> metodos = new HashMap<String, Map<Classe, BlocoDeCodigo>>();

	static Classe novaClasse(String nomeDaClasse) {
		ClasseImpl classe = new ClasseImpl(nomeDaClasse);
		classesDeclaradas.put(nomeDaClasse, classe);
		return classe;
	}

	static Instancia instanciar(String nomeDaClasse) {
		return new Instancia(classesDeclaradas.get(nomeDaClasse));
	}

	static void registrarMetodo(String nomeDoMetodo, Classe tipoEmQueSeAplica,
			BlocoDeCodigo bloco) {
		metodo(nomeDoMetodo).put(tipoEmQueSeAplica, bloco);
	}

	static Object executarMetodo(String nomeDoMetodo, Instancia instancia,
			Object... args) {
		Map<Classe, BlocoDeCodigo> tiposAceitaveis = metodo(nomeDoMetodo);
		if (!tiposAceitaveis.containsKey(instancia.classe()))
			throw new RuntimeException("Metodo inexistente");

		BlocoDeCodigo bloco = tiposAceitaveis.get(instancia.classe());

		Object primeiro = args.length > 0 ? args[0] : null;
		Object segundo = args.length > 1 ? args[1] : null;

		return bloco.executar(instancia, primeiro, segundo);
	}

	private static Map<Classe, BlocoDeCodigo> metodo(String nomeDoMetodo) {
		if (!metodos.containsKey(nomeDoMetodo))
			metodos.put(nomeDoMetodo, new HashMap<Classe, BlocoDeCodigo>());

		return metodos.get(nomeDoMetodo);
	}

}

Agora não apenas registra as classes mas também os métodos que são aplicáveis à cada classe e faz a invocação dos métodos em si.

Estes exemplos são bem educacionais, sem muita aplicação pratica, mas como já vimos em posts passados o fato de se usar message-passing ou Data-Directed ou properties, ou componentes ou qualquer outra coisa interfere no modo como devemos projetar nosso software. Existe uma vasta literatura sobre este tema mas ainda assim é uma das coisas mais desconhecidas pelo programador profissional.

Apresentação no Australian Architecture Forum 2008

Friday, May 16th, 2008

A apresentaçao em Melbourne acabou de terminar. Foi bem interessante e o evento em si está sendo uma experiência diferente. É algo mais como mesas redondas do que apresentações, mais sobre isso depois.

Aproveitando, agradecendo novemente ao Antônio Carlos pela liberação dos nomes e marcas, a apresentação ia ficar bem sem graça sem os screenshots :)

Sem Respostas Fáceis

Monday, April 7th, 2008

O Guilherme Chapiewski causou uma bela confusão com seu último post. Uma chuva de comentários debate: O Scrum Master deve ser técnico?

Eu já conversei com o Guilherme sobre isso algumas vezes e sei o que ele quis dizer, mas acho que colocou de uma forma meio confusa. Para mim o ponto é: o Scrum Master deve ser um gerente de projetos/facilitador/babá em tempo integral?

Após alguns anos nessa estrada dá para entender porque algumas pessoas se preocupam tanto em afastar o papel de gestor (seja Scrum Master of Universe ou qualquer outro termo que você preferir) do papel de técnico. Muitas vezes técnicos quando postos no papel de gestor não conseguem tirar a mão da graxa e isso atrapalha o andamento das coisas. Eu já tive gerentes de projeto que afundaram todo um release porque pegavam para si tarefas técnicas e não conseguiam exercer suas tarefas oficiais.

Isso acontece bastante mas não quer dizer que vá acontecer sempre, ou que não possa ser resolvido. Dizer que o gestor não pode exercer tarefas técnicas e ponto final é ignorar a característica empírica de um processo ágil.

Quem assume que isso é uma verdade absoluta está buscando respostas fáceis ao invés de tentar resolver o problema. Isso não é diferente em nada do cidadão que usa todos os templates, artefatos e papeis do RUP no seu projeto, ou daquele que acredita que um selo como CMMI ou MPS.BR traz qualidade.

Seguir à risca todos os documentos possíveis ou acreditar piamente que um selo traz qualidade é buscar respostas fáceis.

Desde outubro que eu não trabalho com Scrum. A ThoughtWorks não possui exatamente uma metodologia de trabalho, nós entendemos que agilidade é um processo empírico:

The empirical model of process control provides and exercises control through frequent inspection and adaptation for processes that are imperfectly defined and generate unpredictable and unrepeatable outputs.

Cada projeto é um projeto, cada time é um time e aplicar uma receita de bolo, seja ágil ou não, é ruim. No meu projeto atual temos um time de desenvolvedores do mesmo nível. O papel de gestor de projetos fica com o tech lead e isso basicamente quer dizer que ele é quem perde 1/2 do dia em reuniões e é quem pede cerveja para a retrospectiva de sexta-feira. No resto do tempo está na máquina dele com o Eclipse aberto.

A diferença é que ele atua como tech lead, não como desenvolvedor. Ele não pode se comprometer com uma tarefa grande, ou em programar em par. O que ele faz é (1) ter certeza que possui entendimento sobre o que está sendo feito e dar sua posição sobre isso e (2) servir como palavra final quando algum assunto técnico atinge um impasse.

Eu pedi demissão da Globo.com um pouco antes do projeto acabar. Alem de atuar como Scrum Master e líder técnico da equipe eu ainda tinha que resolver algumas milhares de coisas relacionadas à minha viagem. Mesmo assim eu não abdiquei da minha responsabilidade de líder técnico. Eu desenhei com o Guilherme os WebServices que utilizávamos e não só sabia como eles funcionavam bem como enchi o saco dele para que tal coisa fosse desse jeito e não do outro. Eu trabalhei na primeira versão do framework JavaScript que criamos com o Tiago e depois fizemos algumas sessões de pair programming nas últimas alfinetadas.

Nas primeiras iterações eu ainda consegui pegar tarefas para mim, com o tempo isso foi ficando impossível mas eu não deixei de ser o líder técnico do projeto por isso. Assumir o papel de gestor do projeto me deixou com tempo fragmentado mas quanto mais ágil seu processo (e sua empresa) menos você precisa assumir o papel de gestor. Quando a empresa não requer muita burocracia desnecessária não há muito à facilitar, quando o time se gerencia não há muito há resolver.

Como o Antônio comentou no blog é muito bom chegar ao nível de ter essas discussões, pensar sobre o que é feito, mas é melhor ainda quando percebemos que processos ágeis são sobre pessoas e adaptação. Eu diria que um time não precisa muito mais de um Scrum Master Power Turbo do que de um tech lead, ambos são papeis importantes. Se você coloca seu tech lead como gestor de projeto e o impede de exercer suas funções técnicas você tem que ter uma estratégia para substituir essa perda.

Você pode ter certeza que as práticas que você mais odeia em uma metodologia de desenvolvimento não-ágil não foram criadas por pura maldade dos autores (bem, não sempre…). Estas práticas fizeram (e fazem) sentido nos cenários experimentados. Elas foram úteis e resolveram problemas. A grande questão é que não é porque uma coisa faz sentido em um cenário que ela deve ser aplicado à todos, ou sequer à maioria.

Cuidado para não ficar preso às respostas fáceis. Cuidado para não transformar processos empíricos em processos prescritivos. Cuidado para não tentar programar FORTRAN em qualquer linguagem.

Par de Jarros

Sunday, April 6th, 2008

Metodologias ágeis pensam sempre em times de especialistas generalistas. Isso é algo fantástico.

É óbvio que ter um especialista em banco de dados Oracle no time aumenta bastante a capacidade técnica, é óbvio que ter o James Gosling como programador Java aumenta a produtividade nesta linguagem mas é mais que óbvio que a grande, grande maioria dos projetos de software hoje em dia não precisam de especialistas extremos, precisam de gente boa o suficiente na tecnologia.

Isso nos coloca confortáveis para ao invés de um DBA dedicado termos apenas bons desenvolvedores, que ainda que não saibam diferenciar todos os tipos de tablespace possíveis conseguem fazer o sistema funcionar de forma razoável –e razoável significa dentro dos requisitos funcionais e não-funcionais.

Não consigo lembrar de nenhum projeto que eu tenha participado nos últimos três anos que não envolvesse mais de uma linguagem de programação, geralmente Java com Ruby, C++, JavaScript, C#, Bash e/ou PERL (alem das DSLs: HQL, SQL, etc.). Nos bons times não havia “desenvolvedor C#”, havia desenvolvedor, e isso significava que o cara usava as ferramentas que melhor servissem para o trabalho.

Mas o que isso quer dizer do ponto de vista do desenvolvedor? Quer dizer que ele vai ser exposto à tecnologias diferentes, o que é ótimo, mas também quer dizer que ele terá que abandonar algumas das suas preferências pessoais em favor do time, o que pode não ser tão bom.

Em um time ágil, todos os membros devem estar comprometidos com o projeto. Não tem essa de “a minha parte está feita, falta Fulano terminar a dele”, todos são responsáveis e todos vão ajudar. Se você foi contratado como programador SQL e precisamos de uma mão para terminar de escrever testes em Selenium é sua obrigação para com o time se voluntariar.

Mas isso, como tudo na vida, tem dois lados. Existem pessoas que não ligam para a tecnologia utilizada, elas gostam do projeto em si, mas no mundo do desenvolvimento existem muitas pessoas –e geralmente pessoas muito boas- que possuem suas preferências. O cara não gosta de programar em Ruby, ele prefere Java. Se o time precisar ele escreve Ruby mas por ele fazia tudo em Java -ele não acredita que Ruby tenha a mesma qualidade, ou algo do tipo.

Lembre-se que o manifesto ágil coloca pessoas acima do processo, se o processo diz que o desenvolvedor deve ser bombril multi-uso mas a pessoa não se sente a vontade nessa área você como gestor tem obrigação de fazer algo a respeito.

O que fazer? Deixar este desenvolvedor apenas nos projetos Java? Se isso for uma opção tudo bem, mas e em times pequenos? Eu vejo essa situação como um contrato entre as partes:

  • O desenvolvedor deve saber que sua preferência de linguagem/tecnologia/plataforma/framework/biblioteca não necessariamente é a melhor para o caso e/ou não é viável na atual situação.
  • O desenvolvedor deve se comprometer a utilizar a tecnologia em questão quando o time necessitar que ele o faça –e ele tem o direito de dizer até 3 palavrões por linha de código escrita
  • O gestor tem que garantir que o desenvolvedor tenha direito à expressar sua opinião quanto às escolhas do grupo
  • O gestor tem que saber que essa pessoa não está confortável nessa posição e fazer o possível para que isso seja amenizado

Da mesma maneira que o gestor do projeto ou do time sabe que não pode arriscar seu sucesso por uma birra do desenvolvedor com a linguagem, ele sabe que é muito difícil encontrar bons desenvolvedores e manter um ambiente de trabalho agradável. Na minha experiência o que eu mais vi foram casos onde o desenvolvedor cumpre a parte dele no contrato acima mas o gestor não. O gestor trata todos os desenvolvedores como iguais, ignorando completamente o caráter pessoal e social de um projeto de desenvolvimento.

O que “amenizar” significa depende do seu contexto. Você pode tentar mudar a pessoa de equipe, pode fazer com que ele tenha a opção de pegar outras tarefas que não seriam imediatamente executadas, oferecer treinamento… O que você não pode fazer é que o processo –ágil ou não- vença à razão e você tenha um desenvolvedor completamente desestimulado e frustrado na sua equipe, que enquanto você não olha está em um site de empregos.

Não deixe que para aquele desenvolvedor agile signifique trabalhar com algo que você não gosta.

Qi4j @ ThoughtWorks Community College

Thursday, April 3rd, 2008

Ontem tivemos mais um Community College na ThoughtWorks Melbourne, desta vez focamos no Qi4j.

É uma idéia interessante. Basicamente o qi4j (”quiforjêi”) usa micro-aspectos para modelar qualquer coisa. O problema é que a sintaxe atrapalha. Eles usam a Linguagem Java, com Language Adaptations e uma Factory mágica, provavelmente uma linguagem própria teria mais efeito.

Testadores Ágeis

Monday, March 24th, 2008

Todo mundo sabe que agilidade é sobre testes. Muitos testes. Bem, mais ou menos. Geralmente quando falamos de testes em metodologias ágeis estamos falando de testes escritos pelo desenvolvedor enquanto escreve código, e estes têm dois objetivos: feedback e bom design.

Feedback se consegue ao executar os testes. Quando você escreve uma classe que quebra um teste você sabe que existe algo errado imediatamente. Bom design se dá porque nada alem do necessário é criado, alem de que as tecnologias atuais de testes exigem que seu código siga alguns bons princípios para que seja testável, como bom gerenciamento de dependências.

Existe, entretanto, outro tipo de teste, geralmente feito por um profissional especializado, que chamamos de QA (Quality Assurance). Este teste valida o software de um ponto de vista diferente. Muita gente se engana achando que testes de desenvolvedor são suficientes para validar um sistema. Obviamente que assim como nem todo sistema exige um web designer profissional especializado nem todo exige um profissional de testes, mas a maioria dos projetos que já participei tinham nisso um benefício.

Estava lendo o artigo do Jeff Paton, ex-colega ThoughtWorker, sobre isso e lembrei das minhas experiências com testadores em time ágeis. Mas primeiro talvez seja melhor contar sobre as experiências em times não-ágeis.

Eu trabalhei em uma empresa muito interessante mas com um processo muito estranho. A empresa tem orgulho de usar waterfall para desenvolver seus produtos, ninguém está autorizado a escrever uma linha de código sem escrever 8 documentos. Obviamente todos os projetos atrasam e obviamente o software tem uma qualidade deprimente, mas eles conseguem fazer dinheiro neste cenário –ainda que pagando um preço muito alto.

Teste é uma coisa séria nesta empresa. Os produtos estão sujeitos à regulamentação de telecomunicações de diversos paises, e cada funcionalidade tinha que ser testada de acordo. O fluxo de desenvolvimento era o típico de waterfall, nós desenvolvedores criávamos nossos sistemas (programas em C++ para plataformas UNIX diversas) e enviávamos uma tag do Subversion para a equipe de testes. O testador teoricamente já lera todas as especificações funcionai do produto e já tinha os casos de testes escritos e, talvez, implementados.

Era aí que a brincadeira começava. Na melhor das hipóteses o testador encontrava um bug, rejeitava o pacote (usando um maravilhoso sistema interno desenvolvido em Lótus Notes) e enviava de volta. O desenvolvedor corrigia e o pacote era retestado. Isso nunca acontecia. O que acontecia era:

  • O pacote era aprovado. O desenvolvedor, com o sistema já em produção, olhava o plano de testes por curiosidade e via que eles não testavam absolutamente nada de relevante, ninguém garantia a qualidade do treco
  • O pacote era rejeitado. O desenvolvedor olha ao motivo da rejeição e via que o que estava sendo testado nem de perto era o que o sistema deveria fazer. O testador Não entendeu o documento, muitas reuniões explicando tudo novamente e o pacote era retestado sem modificações
  • O pacote era recebido pelo testador com total surpresa. Era uma aplicação de linha de comando e o testador esperava uma aplicação com interface web, ou o testador esperava que o sistema usasse banco de dados e ele mantinha tudo em memória, ou…
  • O testador rejeitava o pacote porque não sabia como testa-lo. O desenvolvedor não pensava no testador, o testador não pensava no desenvolvedor.

Claro que todos os membros do escritório de projetos (já falei que tínhamos CMMI?) sabiam a solução para estes problemas e é claro que para eles a solução passava pela criação de mais documentos, de 1 a 5 dependendo de quem você perguntasse.

Nas equipes ágeis que trabalhei a coisa era diferente. O testador senta ao lado do desenvolvedor, e por vezes eles trabalham em pair programming (pair testing, provavelmente). O testador está envolvido em todas as tarefas, validando que o que o analista de negócios pede é entregue, que confirma com as premissas do projeto e etc.

Para fazer isso ele está envolvido na elaboração das user stories e, principalmente, dos critérios de aceite definidos nela. Seu trabalho é verificar que os critérios de aceite são obedecidos pela implementação e que eles fazem sentido em primeiro lugar.

Um profissional come site perfil não pode ser simplesmente um usuário. Ele tem que conhecer sobre metodologia de testes de software (um campo enorme por si só), sobre o domínio do sistema e sobre desenvolvimento. Idealmente o testador é um desenvolvedor e sabe criar suas ferramentas. Um testador deve ser capaz de escrever seu próprio programa usando Selenium RC, ou suas fixtures no Fit.

Infelizmente nem sempre isso acontece. Em muitas empresas o cargo de testador é delegado à pessoas que possuem um conhecimento técnico muito baixo e executam tarefas do tipo “aperte o botão e verifique se quebra o layout”. Neste caso os desenvolvedores podem prover as ferramentas para os testadores mas ainda assim eles devem ter capacidade de escrever os casos de testes usando a ferramenta sozinhos.

O papel do testador não é dizer que a aplicação está homologada, na maioria das vezes isso é apenas uma ilusão que gerentes gostam de cultivar. O papel do testador é garantir a qualidade dele submetendo à testes rígidos. Não é um aceite formal, o testador não é quem aprova o software –o analista de negócios ou seu equivalente aprova o software- ele faz parte do desenvolvimento deste.

Minha experiência diz que assim como trabalhar com testadores apertadores-de-botão não agrega nada ao produto alem de burocracia desnecessária, trabalhar com testadores de verdade no time é uma das melhores coisas que pode acontecer num projeto de software.

No meu projeto anterior os testadores chegaram ao time apenas como apertadores de parafusos. Eles clicavam na tela e verificavam que a informação correta era disponibilizada. Como nossa participação no projeto era facilitar a adoção de metodologias ágeis isso tinha que mudar. A primeira coisa foi a participação dos testadores na definição das histórias, eles trabalham junto com o analista de negócios e o usuário definindo critérios. Eles também participam do sign-off da história –quando ela é apresentada aos desenvolvedores que vão executa-la- e quando conclui a história o desenvolvedor az um walkthrough dela para o testador. Em todas as etapas eles agregam informação, questionam as praticas e, principalmente, garantem que o software final é testável.

Foi adicionado ao time de facilitadores um testador especialista em automação –um desenvolvedor exercendo função de QA, basicamente-, a função dele era disponibilizar ferramentas e disseminar seu uso. Com o tempo desenvolvemos uma Internal DSL em Ruby para controlar nossa ferramenta de teste, o Selenium RC. A ferramenta é utilizada por desenvolvedores nos nossos testes de aceitação que fazem parte do build e pelos testadores na hora de dizer que uma história está concluída.

Os resultados desta adoção são excelentes. Quando nossa parte no projeto –facilitar a adoção das praticas- acabou a pessoas estavam aptas à continuar o bom trabalho por si mesmas. Os desenvolvedores ainda criam as ferramentas mas a gerencia, vendo os benefícios, agendou cursos de Ruby e Selenium para a equipe de QA.