Archive for the ‘Software Design’ Category

Buscando qualidade de código (parte 1)

Monday, September 17th, 2007

Essa semana durante uma sessão de refatoração, tive a idéia de sempre que me deparar com situações de códigos que precisam ser alterados afim de se adequarem a alguns princípios de boas práticas de desenvolvimento de software, vou compartilhar aqui, explorando os pontos negativos do código e as vantagens de se adequar a um desses princípios, falando também um pouco sobre eles, um caso de cada vez, começando agora com OCP (Open-Closed Principle).

The Open-Closed Principle

Este princípio é a fundação para construção de códigos reutilizáveis e de fácil manutenção. Módulos que se adequam a este princípio são, ao mesmo tempo, abertos para serem extendidos, de maneira que se comporte de maneiras diferentes, a medida que surgem novos requisitos para a aplicação, ou a medida que estes são modificados. E fechados para modificação, o que significa que o código fonte deste módulo não pode ser violado, ninguém pode fazer mudanças neste código fonte.

A descrição deste princípio pode parecer conflitante, já que para extender a funcionalidade de um módulo, é necessário modificá-la, logo deduzimos que uma funcionalidade que não pode ser alterada é dada como fixa. Como este conflito pode ser resolvido? A melhor maneira de demonstrar é através de um exemplo.

No projeto CartolaFC, que estou trabalhando atualmente, existe um gerenciador de tarefas que é responsável pela execução assíncrona de tarefas enfileiradas pela aplicação. Basicamente ele percorre uma lista de pendencias, executando cada uma das tarefas, na ordem em que aparecem. O código abaixo está modificado em relação ao original, para podermos visualizar com mais facilidade.

public class GerenciadorDeTarefas {

    public List<Tarefas> pendentes;
    public void executaTarefasPendentes() {
	for (int i=0; i<pendentes.length; i++) {
	    Tarefa tarefa = pendentes[i];

	    switch (tarefa.tipo()) {
	    case calculoTrofeus:
	    	engine.calculaTrofeus();
	    	break;

	    case calculoEstatisticas:
	    	engine.calculaEstatisticas();
	    	break;
	    }

	    case fechamentoDeMercado:
	    	engine.fechaMercado();
	    	break;
	    }

	    case aberturaDeMercado:
	    	engine.abreMercado();
	    	break;
	    }

	    // .... e assim continua ....
	}
    }
}

O método executaTarefasPendentes() não se adequa ao Open-Closed Principle, pois não está fechado para novos tipos de implementação. Se um dia o cliente decidir extender esta funcionalidade para incluir novas tarefas a serem executadas, que é o que acontece hoje em dia, o método terá que ser modificado para atender este requisito, incluindo mais um bloco “case”, que já não é nada elegante, e assim continuará, para cada novo tipo de tarefa.

O código, na vida real é ainda pior, atualmente são 18 blocos “case”, o que torna a implementação de novas tarefas uma prática intrusiva, suja e acoplada (imagine se todos os métodos a serem executados para as tarefas ficassem espalhados… quantos objetos teríamos que instânciar no gerenciador de tarefas…)

O código a seguir mostra a solução que pretendo dar para este problema, uma solução que se adequa ao Open-Closed Principle. Resumindo, a classe Tarefa se torna abstrata e acrescentamos a ela um método abstrato chamado executar(). Com esse approach, TODOS os tipos de tarefas criados, serão classes concretas que derivam desta classe, implementando seu método abstrato.

public class abstract Tarefa {
    public abstract void executar();
}
public class CalculoDeTrofeus extends Tarefa {
    public void executar() {
	// lógica para cálculo de troféus...
    }
}
public class CalculoDeEstatisticas extends Tarefa {
    public void executar() {
	// lógica para cálculo de estatísticas...
    }
}

E a partir de agora, para o nosso gerenciador de tarefas, o ato de invocar as tarefas pendentes fica totalmente transparente. Tudo que ele precisa fazer é percorrê-las, e para cada uma, invocar seu método executar(), tornando o código não intrusivo, limpo e desacoplado.

public class GerenciadorDeTarefas {

    public List<Tarefas> pendentes;
    public void executaTarefasPendentes() {
	for (int i=0; i<pendentes.length; i++) {
	    Tarefa tarefa = pendentes[i];
	    tarefa.executar();
	}
    }
}

Agora, se quisermos extender a funcionalidade de executarTarefasPendentes() do nosso gerenciador para que passe a executar uma tarefa nova qualquer, tudo o que precisamos fazer é adicionar uma nova implementação para a classe abstrata Tarefa. O nosso método executarTarefasPendentes() não precisa ser alterado, se adequando ao Open-Closed Principle. Seu comportamento pode ser extendido, sem que ele seja alterado.