Fragmental

5/09/2005

Fantoches

Nota: Devido a utilizar um editor de textos, depois colar no blogger, a formatação ficou uma porcaria. O Renato fez uma versão em PDF (valeu, cara :) ).

A modelagem baseada em objetos é vendida como o meio ideal de se desenvolver um sistema nos dias de hoje. Pelas tecnologias e paradigmas que eu conheço, eu acredito nisso também, mas sem hype.

Eu gosto de Análise e projeto Estruturados. Primeiro porque me fizeram pensar em programas como mais que bits e bytes correndo por uma tela monocromática, me fizeram entender sistemas.

Mas Estruturar Não era Legal?
Antes de continuar, deixa-me fazer uma definição que vai servir para o resto desse blog.

O Paradigma Procedural é como eu chamo a programação onde dados e funções estão separadas. Nesse modelo de desenvolvimento de software as entidades são representadas por estruturas de dados simples que são manipuladas por funções independentes.

Eu não gosto do termo Programação Estruturada (apesar de
usá-lo ocasionalmente). Este termo surgiu para separar a arte de destruir a
seqüência lógica de um sistema com instruções GOTO (veja o que Dijkstra diz sobre
isso tudo no seu paper clássico), comum em BASIC, da separação do fluxo
em sub-rotinas, as funções e procedimentos.

Então, programação orientada a objetos é programação estruturada no sentido que separa as funcionalidades em módulos. Chamaremos neste blog de Programação Procedural o modo de definir um programa como descrito acima.

Modelando o Mundo

A primeira coisa que se aprende num curso básico de Análise de Sistemas (seja qual paradigma/metodologia seja usada) é que um sistema de informação modela um mini-mundo. Este mundo é uma versão abstraída do mundo normal, onde os únicos detalhes que importam são os relacionados com a aplicação, com o que o sistema faz.

Num sistema estruturado, geralmente se pensa em como as coisas acontecem, no fluxo de informação. Pensando procedural, um simples cadastro é assim:

Cliente fornece endereço, endereço é armazenado.

Dados fluindo. Acho que todos percebem que a função, o Processo na nomenclatura de Análise Estruturada (aquelas bolhas do Yourdon) vai receber uma estrutura de dados ENDEREÇO e colocar no repositório (que pode ser um SGBD ou um arquivo ou qualquer outra coisa).

Pensando de uma maneira OO, você tem:

Cliente tem um endereço.

Uhm? Sim. No seu mini-mundo OO, você vai mapear o que te importa (o cliente e seu endereço) para um objeto. É claro que dependendo da sua aplicação o Cliente pode ser jogado fora, mas aí vai te restar o objeto Endereço.

Num modelo OO, você está preocupado com o que existe no seu mundo real, como as coisas se relacionam por elas mesmas.

Implementações Procedurais
Vamos pensar um cenário simples. Um estacionamento.

Nosso sisteminha exemplo tem uma só funcionalidade: descobrir se existe vaga disponível para um carro no estacionamento e colocar esse carro na vaga.

Vamos ver o modo procedural de fazer as coisas. Vou usar Java para os dois casos como é minha linguagem atual, mas como muita gente programa 100% procedural em Java, não devemos ter muitos problemas.

class Carro {

private String placa;

private Date dataEntrada;

private int vaga = -1;

public void setPlaca(String p) {

this.placa = p;

}

public String getPlaca() {

return placa;

}

public void setDataEntrada(Date data) {

this.dataEntrada = data;

}

public Date getDataEntrada() {

return dataEntrada;

}

public void setVaga(int vaga) {

this.vaga = vaga;

}

public int getVaga() {

return vaga;

}

}

class Estacionamento {

public final double VALOR_HORA = 0.5;

private Carro[] vagas = new Carro[5];

public boolean estacionar(Carro c) {

int vagaLivre = vagaLivre();

if (vagaLivre == -1)

return false;

c.setDataEntrada(new Date()); //coloca a data atual do sistema

vagas[vagaLivre] = c;

c.setVaga(vagaLivre);

return true;

}

public double sair(Carro c) {

int vagaOcupada = c.getVaga();

Date dataEntrada = c.getDataEntrada();

double conta = tempoEmHorasAteHoje(dataEntrada) * VALOR_HORA;

c.setVaga(-1);

vagas[vagaOcupada] = null;

c.setDataEntrada(null);

return conta;

}

}

Aposto que muita gente vai estranhar eu rotular isso de procedural. Aqui nós temos a classe Estacionamento que cuida do processo de estacionar e deixar o estacionamento do Carro, representado por um pseudo-JavaBean (um dia eu explico que é um pseudo-JavaBean para mim).

Mas, o que tem isso de procedural? São classes, não são?

Meu primeiro exercício é para quem tem background em linguagens procedurais. Tente reescrever isso utilizando pascal clássico (não Delphi), FORTRAN ou C (não C++). O código não vai ficar muito diferente, não é? Para quem não programa nessas linguagens, vai mostrar uma implementaçãozinha mixuruca em pseudocodigo baseado em C:

struct t_carro
{
char * placa ; /* uma string */
int vaga ;
time_t data_entrada ; /* um dos tipos de data */
};

typedef struct t_carro carro ; /*para podermos usar a struct */

carro vazio;

carro vagas[5];

int estacionar(carro c){
int vaga_livre = achar_vaga_livre();

if(vaga_livre==-1) return 0; /* em C, 0 = false e 1 = true */

c.data_entrada = data_do_sistema();

vagas[vaga_livre] = c;
c.vaga = vaga_livre;
return 1;
}

double sair(carro c){

int vaga_ocupada = c.vaga;
time_t data_entrada = c.data_entrada;
double conta = tempo_em_horas_ate_hoje(data_entrada) * VALOR_HORA;

c.vaga=-1;
vagas[vaga_ocupada]=vazio;
c.data_entrada=0;
return conta;
}


Mesmo que você não saiba nada de C, dá pra perceber que os códigos são muito parecidos. A maior diferença e fútil, está nas convenções de nomenclatura. Se você colocar os atributos de Carro na primeira implementação como públicos, além de não perder nada (afinal, o que são get e set além de jeitos mais lentos de fazer um atributo público?) o código vai ficar mais parecido.

Claro que a sintaxe das linguagens é muito parecida, e isso ajuda, mas o que parece não é a sintaxe, é o meio como se programa. Esse é um programa altamente procedural.

Note que não existe um só meio procedural de pensar e implementar ou um só meio OO de fazer isso, uns são melhores outros piores, mas estes são apenas dois exemplos.

Bad Smell: Forças Ocultas
O clássico livro Refactoring, de Martin Fowler introduz o conceito de bad smells, literalmente um fedor no código. Você olha, cheira, vê que tem algo errado, e tem que descobrir a causa.

Note que o sistema funciona! Você pode entregar para o seu cliente exatamente desta maneira, mas o que nos queremos aqui não é entregar algo rápido para gerar uma fatura, estamos estudando a melhor maneira de implementar, e como objetos podem ajudar a tornar o desenvolvimento de software mais natural.

Falando em natural, pense no código que criamos, em qualquer uma das implementações. Esse modelo se parece com o mundo real? Vejamos: temos um procedimento que recebe um carro, o joga no estacionamento... como se na entrada do estacionamento houvesse um guindaste que carregasse os carros até suas vagas. É assim que funciona? Não! O motorista ou o manobrista vai pegar o carro e dirigi-lo ate a vaga certa. O carro "sabe" estacionar, e essa é uma responsabilidade dele: dada uma vaga, estacione.

Claro que você pode efetivamente ter seu carro sendo movido desta forma (é só um sistema, não é o mundo real!), mas eu insisto: modele seu sistema o mais próximo que conseguir do mundo real. Qualquer dia falamos sobre Domain Driven Design, mas por enquanto, isso te ajuda a conversar mais facilmente com seus usuários e manter a mesma linguagem que ele, podendo aproveitar os conceitos que ele aplica no dia a dia.

No caso específico, nosso método estacionar(Carro c) está com responsabilidades demais! Ele tem que coordenar o processo de encontrar uma vaga, modificar o carro, modificar a vaga... e olhe que isso é um exemplo simples, que não envolve acesso a recursos como bancos de dados. Vamos tirar o peso dos ombros do pobre método e colocar nosso carro para ser mais que uma struct de luxo...

class Carro {

private String placa;

private Vaga vaga;

public void estacionar(Vaga v) {

this.vaga = v;

v.ocupar(this);

}

public void sair() {

vaga.setOcupante(null);

this.vaga = null;

}

public Vaga getVaga() {

return vaga;

}

}

class Vaga {

private Date dataEntrada;

private Carro ocupante;

public void ocupar(Carro c) {

this.ocupante = c;

this.dataEntrada = new Date();

}

public double getValor() {

return (tempoEmHorasAteHoje(dataEntrada) * Estacionamento.VALOR_HORA);

}

public long tempoEmHorasAteHoje(Date desde) {

return 1;

}

public void setOcupante(Carro c) {

this.ocupante = c;

}

}

class Estacionamento {

public static final double VALOR_HORA = 0.5;

private Vaga[] vagas = new Vaga[5];

public boolean estacionar(Carro c) {

Vaga vagaLivre = vagaLivre();

if (vagaLivre == null)

return false;

c.estacionar(vagaLivre);

return true;

}

public double sair(Carro c) {

Vaga vagaOcupada = c.getVaga();

c.sair();

return vagaOcupada.getValor();

}

}



Perceba que nesse modelo nós explicitamos um conceito. O cliente no caso quer saber o preço do uso da vaga, não do carro, e para melhor modelar isso nós criamos um objetinho com este conceito.

Observe os programas. Perceba como as responsabilidades foram divididas entre os objetos participantes, como não existe mais um método (ou um conjunto de métodos) que manipula os dados, geralmente cada objeto cuida de si próprio. Essa é a idéia.

De uma maneira geral: é um bad smell se seus objetos são guiados através de forças ocultas, se seus objetos se comportam como marionetes sendo manipuladas por uma função-mestre, o mestre dos fantoches.

Isso é sintoma de um design altamente procedural, onde a lógica de negócio está implementada em funções e em como estas manipulam estruturas de dados. Você geralmente acaba com uma serie de classes (geralmente implementando o padrão Command, mas por vezes são Façades) coordenadoras e um bando de objetos burros que nada mais são que um agrupamento de dados relacionados.

Neste cenário e comum que simplesmente não se consiga reaproveitar lógica. E o tipo de sistema onde reusabilidade e mantida com copiar-e-colar, já que seus métodos são “gordos” demais e são tão especializados que simplesmente não servem para nada alem do que foram concebidos inicialmente. Outro sintoma deste tipo de design são métodos com muitas linhas de código.

Sugestão: divida a responsabilidade do seu método faz-tudo por objetos que dividam a responsabilidade. Modele melhor seu sistema tentando utilizar conceitos e abstrações do mundo real.

Anatomia de um Sistema OO

Um sistema baseado em objetos e formado por componentes (os próprios objetos) que colaboram entre si. Isso não e tão diferente de um sistema procedural, onde as funções e procedimentos devem trabalhar juntos para um baixo acoplamento e alta coesão.

Uma maneira pratica de avaliar seu modelo OO contra fantoches e mestres e o diagrama de seqüência. Observe o diagrama de seqüência de nossa primeira implementação:

O diagrama de interação e exatamente para mostrar como os objetos interagem no sistema. Veja que a interação neste caso e sempre iniciada pelo nosso mestre dos fantoches. Vamos examinar o diagrama de nossa implementação com responsabilidades distribuídas:

Perceberam a diferença? Os objetos agora colaboram entre si, não existe uma única unidade de controle. Se você precisar estender a funcionalidade do seu sistema, agora pode reutilizar os procedimentos que foram separados do método inicial, o método estacionar() agora e apenas um estimulador dos outros objetos.

Entre Flexibilidade e Produtividade

Como sempre, o programador deve lutar entre ser flexível ou ser produtivo. Na verdade, essa e uma luta desnecessária, quando você produz software de qualidade você acaba sendo produtivo em longo prazo.

A grande dificuldade e como saber quando sua arquitetura esta flexível e quando esta flexível demais. Você pidões perder dias esculpindo um framework em cima da sua aplicação, onde qualquer mudança será muito simples, mas você precisa pensar que pode ser que uma mudança brusca nunca seja necessária, e não há na maioria das vezes como saber as necessidades futuras.

Um bom modelo de objetos vai permitir que você consiga separar bem as cosias. Objetos mapeando significados do domínio, como o exemplo da Vaga, te dão flexibilidade próxima da flexibilidade que seu cliente tem, então se ele resolver mudar seus conceitos você pode seguir a mesma linha de raciocínio ao mudar os seus.

Refatorar seu código constantemente ajudar a manter algo simples e flexível. Siga esta pratica.

Conclusão

Todo mundo aprende a construir algoritmos, e algoritmos são dados fluindo entre funções. A migração de paradigma e complexa, e a literatura especializada em Java e (principalmente) J2EE não ajudam.

Existe um motivo para a criação do paradigma de orientação a Objetos. Este paradigma prove abstrações melhores e mais naturais, mais próximas do mundo. Antes de definir seu sistema, pense em como as coisas interagem no mundo, pense que objetos não são dados+funções, eles são entidades por si só, e devem colaborar. Não crie “donos da razão” no seu sistema, produza classes coesas e colaborativas.

Update: Mudei a cor dos códigos. Eu escrevi num editor de textos normal e a formatação aqui ficou uma bagunça, vou tentar definir uns CSS para o blog.


 
f