terça-feira, 6 de maio de 2014

Os maus cheiros do projeto

Os maus cheiros do projeto.

         Percebe-se que o software está com cheiro de podre ou de algo estragado, quando ocorre os seguintes sintomas:

         Entre os sete sintomas existentes a rigidez acontece quando se torna difícil alterar o projeto, mesmo de maneira simples, ou seja, o projeto se torna rígido quando tentamos mudar algo simples e temos que mudar quase todo o projeto.

        Outro sintoma é a fragilidade, ela ocorre quando o projeto fica fácil de estragar, o programa estraga em muitos lugares quando se faz uma única alteração e quanto mais se tenta consertar mais problemas aparecem.

         A imobilidade, também é um sintoma de mau cheiro, o projeto se trona difícil de ser reutilizado, ele contém partes que poderiam ser uteis em outros sistemas, mas é muito arriscado e trabalhoso separar estas partes do sistema operacional.

         A viscosidade é um mau cheiro que faz com que se torne difícil fazer a coisa certa, quando os métodos do projeto são mais difíceis de usar do que as soluções malfeitas. Sendo assim, torna-se mais fácil fazer a coisa errada do que a certa.

         Com a complexidade desnecessária o projeto tem o mau cheiro quando ele tem elementos excessivos, contendo construções que nunca são utilizados, podendo deixar o software pesado, complexo e difícil de entender.

         Outro mau cheiro é a repetição desnecessária, pois é fácil copiar e colar códigos, mas quando ocorre erro no mesmo é mais complicada a correção, pois cada cópia pode torna-se diferente da outra e por causa disto ocorrem diferentes erros.

         A opacidade também é um mau cheiro do código, pois se refere à dificuldade de compreensão de um módulo. Isto ocorre quando o código é escrito de maneira inteligível ou enrolada, se tornando cada vez mais difícil a sua compreensão.


Abaixo, exemplo de um programa em C# pra impressão;

public class Copier
{
 public static void Copy()
 {
  int c;
  while((c = keyboard.Read())!= -1)
   Printer.writer(c));
 }
} 


Abaixo, exemplo de Mau Cheiro do programa pra impressão, pois se teve que incluir mais algumas funções;

public class Copier
{
 //lembre de reiniciar estes flags
 public static bool ptFlag = false; //para impressora
 public static boll punchFlag = false; //para a fita
 public static void Copy()
 {
 int c;
 while((c = (ptFlag?PaperTape.Read():keyboard.Read()))!= -1)
  punchFlag?PaperTape.Punch(c):Printer.writer(c);
 }
}


Abaixo, exemplo Correto de como deveria ficar o programa para impressão;

public interface Reader
{
int Read();
}
public class KeiboardReader:Reader
{
 public int Read(){ return Keyboard.Read();}
} 
public class Copier
{ 
 public static Reader reader = new KeyboardReader();
 public static void Copy()
 {
  int c;
  while((c = (reader.Read()))!= -1)
   Printer.writer(c);
 }
}



           Mas existe solução ou prevenção contra o mau cheiro do software, o correto é sempre manter o projeto do sistema o mais limpo e simples possível, e sempre testar sua flexibilidade e sua fácil alteração caso no futuro seja necessário acrescentar ou alterar algo no programa. Desta forma o software ficará com uma rápida execução e com uma visualização elogiável.




Referência bibliográfica

MARTIN. Robert C., MARTIN Micah, Princípios, padrões e práticas ágeis em C#. Editora BOOKMAN. 2011. (p.121 -133).

Padrão Money

Resolvi escrever sobre este tópico pois trata-se de um assunto de extrema relevância e que muitos, assim como eu tem dúvidas quando se trata de trabalhar com valores monetários, principalmente quando estamos lidado com dinheiro de terceiros. O problema mais sutil é com o arredondamento. Cálculos monetários muitas vezes são arredondados para a menor unidade da moeda com isso é fácil perder alguns centavos devido aos erros de arredondamento.

Enigma de Matt Foemmel

Suponha que eu tenha que alocar 5 centavos entre duas contas: 70% para uma e 30% para outra. O cálculo matemático resulta em 3,5 centavos e 1,5. Não importa como será arredondado os valores, ou tu ganharás 1 centavo ou perderás 1 centavo.

Como funciona

A idéia básica é ter uma classe Dinheiro com campos para o valor e a moeda corrente. Você pode armazenar a quantidade como um tipo inteiro ou tipo decimal fixo. O tipo decimal é mais fácil para algumas manipulações, o integral para outras. Deve-se evitar completamente qualquer tipo de ponto flutuante, pois isso introduzirá o tipo de problemas de arredondamento cuja finalidade de Dinheiro é evitar.

A solução favorita de Martin Fowler foi essa: ter uma função de alocação na classe Dinheiro. O parâmetro para o alocador é uma lista de números, representando a proporção a ser alocada. O alocador retorna uma lista de objetos dinheiro, garantindo que nenhum centavo seja perdido espalhando-os pelos objetos dinheiro alocados de um modo que, de fora, pareça pseudo-randômico.
Com os números em ponto flutuante descartados, a escolha fica entre inteiros e decimais de ponto fixo, o que em Java se resume a BigDecimal, BigInteger e Long.
class Dinheiro{
   private long quantia;
   private Currency moeda;
}
Usar o long nos permite ter expressões matemáticas mais legíveis. Porém se o número ficar muito grande, ocorrerá um erro de estouro (overflow). A conclusão que se chega é que é necessário a criação de vários construtores para cada tipo numérico:
class Dinheiro{
   public Dinheiro(long quantia, Currency moeda){
      this.moeda = moeda;
      this.quantia = quantia * fatorCentavos( );
   }

   public static final int[ ] centavos = new int[ ] {1,10,100,1000};

   private int fatorCentavos( ){
      return centavos[moeda.getDefaultFractionDigits( )]
   }
}
Diferentes moedas tem diferentes quantias fracionárias. A classe Currency lhe informará o numero de dígitos fracionários em uma classe. O Dinheiro é um Objeto Valor*, então deve ter suas operações de igualdade e código hash sobrescritas para serem baseadas na moeda corrente e na quantia. Se você quiser alocar uma importância em dinheiro para muitos destinatários e não quiser perder centavos, irá precisar de um método de alocação.
class Dinheiro{
   public Dinheiro[ ] alocar (long[ ] razoes){
      long total = 0;
      for(int i = 0; i ;razoes; i++) total += razoes;
      long resto = quantia;
      Dinheiro[ ] resultados = new Dinheiro[razoes.length];
      for(int i = 0; i; resultados.length; i++){
         resultados[ i ] = novoDinheiro(quantia * razoes[ i ] / total);
         resto -= resultados[ i ].quantia;
      }
      for(int i = 0; i; resto; i++)resultados[ i ].quantia++;
      return resultados;
   }
}
Pode-se utilizar isso para resolver o Enigma de Foemmel
class Dinheiro{
   public void testeAlocar( ){
   long[ ] alocacao = {3,7};
   dinheiro[ ]resultado = dinheiro.dolares(0.05).alocar(alocacao);
   equals(Dinheiro.dolares(0.02), resultado[0]);
   equals(Dinheiro.dolares(0.03), resultado[1]);
   }
}
Com isso, uma classe Dinheiro pode ser utilizada para quase todo o cálculo numérico em ambientes OO. A razão principal é encapsular a manipulação do comportamento de arredondamento, o que ajuda a reduzir os problemas de erros de arredondamento. Outra razão para uso de Dinheiro é tornar muito mais fácil o trabalho com diversas moedas. A objeção mais comum a Dinheiro é o desempenho, raramente tenha-se ouvido falar de casos em que ele fez qualquer diferença perceptível, e mesmo nesses casos o encapsulamento, muitas vezes, torna mais fácil a otimização.

*Objeto Valor(Value Object):Um objeto pequeno e simples, como dinheiro ou um intervalo de datas, cuja igualdade não é baseada em identidade.

Referências: Padrões de Arquitetura de Aplicações Corporativas - Martin Fowler - páginas 455 a 457.

Nomes Significativos == Uma Boa Prática



O Código limpo, é como comparar um pintor e sua obra, não basta conhecer cores, texturas e pinceis, para garantir uma pintura magnífica. Pode-se ensinar a mecânica, colocar no papel todos os princípios necessários para um Código limpo e mesmo assim, não teríamos garantias do resultado esperado. Aprender a criar códigos limpos é uma tarefa árdua e requer mais do que o simples conhecimento dos princípios e padrões, requer treino, trabalho, prática e a agonia em pagar o preço de ter tomado decisões erradas.
Neste artigo, vamos apresentar alguns princípios e sugestões, e a intensão é direcionar o raciocínio para um bom projeto e não para um bom programa. Dentro desse contexto, há duas vertentes para se obter habilidade profissional: conhecimento e trabalho.

Devemos adquirir o conhecimento dos princípios, padrões, práticas e heurísticas que um profissional habilidoso sabe, e também esmiuçar esse conhecimento com seus dedos, olhos e corpo por meio de trabalho árduo e da prática. 

O CÓDIGO

Código é a linguagem na qual expressamos nossos requisitos, podemos criar linguagens que possam expressar mais facilmente nossas intensões, podemos criar ferramentas que nos ajudem a analisar a sintaxe e as estruturas formais, mas jamais eliminaremos a precisão necessária, ou seja, sempre teremos um código.

BOAS PRÁTICAS

Nomes Significativos

Há nomes por todos os lados em um software. Nomeamos nossas variáveis, funções, parâmetros, classes e pacotes, assim como o arquivo-fonte e os diretórios que os possui. Nomeamos também nossos arquivos jar e war e ear. Então, nomear é parte importantíssima de nosso código, o que nos obriga a termos muito cuidado nesta prática, seguem abaixo, algumas regras simples para criação de bons nomes: 

Use Nomes que Revelem seu Propósito

Parece fácil, mas escolher bons nomes é coisa séria, leva tempo, mas economiza muito mais, preocupe-se com seus nomes, tenha carinho e cuidado e troque-os quando encontrar melhores, pois aqueles que lerem seu código (inclusive você mesmo) ficarão agradecidos. 

O nome de uma variável, função ou classe deve responder a todas as grandes questões. Ele deve lhe dizer porque existe, o que faz e como é usado. Se um nome requer um comentário, então ele não revela seu propósito.
 int t; // tempo decorrido em dias 
O nome t não revela nada, não dá ideia de tempo decorrido, nem de dias. Devemos escolher um nome que especifique seu uso para medida e a unidade usada.

int tempoDecorridoEmDias; ou elapseTimeInDays;
int diasAposCriacao; ou daysSinceCreation;
Usar nomes que revelem seu proposito facilita bastante o entendimento e a alteração de um código

Evite Informações Erradas

Como comentamos antes, criar nomes não é uma tarefa fácil, então, os programadores tem que tomar cuidado para não induzir a uma interpretação errônea ou diferente daquilo que desejávamos. Por exemplo: Não se refira a um grupo de contas como accountList , a menos que realmente seja uma list . A palavra list (lista) significa algo específico em um código. Se o que armazena as contas não for uma list de verdade, poderá confundir os outros. Portanto, accountGroup ou bunchOfAccounts, ou grupoDeContas, seria melhor. Cuide também para não usar nomes muito parecidos. Fica difícil perceber a pequena diferença entre XYZControllerForEfficientHandlingOfStrings em um módulo e XYZControllerForEfficientStorageOfStrings em outro. Ambas as palavras são muito semelhantes.

Faça Distinções Significativas

Como não é possível usar o mesmo nome para referir-se a duas coisas diferentes num mesmo escopo, você pode decidir alterar o nome de maneira arbitrária. Se os nomes precisam ser diferentes, então também devem ter significados distintos.

Use Nomes Pronunciáveis

O ser humano é bom com as palavras. Uma parte considerável do seu cérebro é responsável pelo conceito das palavras. E, por definição, as palavras são pronunciáveis. Seria um desperdício, não tirar proveito disso. Sendo assim, crie nomes pronunciáveis.

Para ilustrar essa ideia, segue uma exemplo: variável genymdhms (generation date, year, mounth, day, hour, minute e second) não seria melhor escrita assim: generationTimestamp?

Use Nomes Passíveis de Busca

Nomes de uma só letra ou números possuem um problema em particular por não ser fácil localizá-los ao longo de um texto. Pode-se usar facilmente o grep para MAX_CLASSES_PER_STUDENT , mas buscar o numero 7 poderia ser mais complicado. Nomes, definições de constante e várias outras expressões que possuam tal numero, aparecerão na sua pesquisa. Da mesma forma, o nome “e” é uma escolha ruim para qualquer variável que necessite ser buscada.

Nomes de Classes

Classes e objetos devem ter nomes com substantivo(s), como cliente, paginaWiki, Conta e analiseEndereco. Evite palavras como Gerente, Processador, Dados ou Info no nome de uma classe, que também não deve ser um verbo

Nomes de Métodos

Os nomes de métodos devem ter verbos, como postarPagamento, excluirPagina ou salvar. Devem-se nomear métodos de acesso, alteração e autenticação, segundo seus valores e adicionar os prefixos get, set ou is de acordo com o padrão javabean.
string name = employee.getName();
custumer.setName(“mike”);
if (paycheck.isPosted())...
Quando os construtores estiver sobrecarregados, use métodos factory estáticos com nomes que descrevam os parâmetros. Por exemplo:
Complex fulcrumPoint = Complex.FromRealNumber(23,0);
É melhor do que
Complex fulcrumPoint = new Complex (23,0);
Para forçar o uso, torne os construtores correspondentes como privados.

Não de uma de Espertinho

Não use gírias ou códigos que somente pessoas ligadas a você ou de sua região, possam entender, nem piadas ou palavrões.

Selecione uma Palavra por Conceito

Escolha uma palavra por conceito abstrato e fique com ela. Por exemplo, é confuso ter pegar, recuperar e obter como métodos equivalentes de classes diferentes. Da mesma forma, é confuso ter um controlador, e um gerenciador e um driver no mesmo código-fonte.

Um léxico consistente é uma grande vantagem aos programadores que precisem usar seu código.

Não Faça Trocadilhos

Evite usar a mesma palavra para dois propósitos. Usar o mesmo termo para duas ideias diferentes é basicamente um trocadilho.

Se você seguir a regra “uma palavra por conceito”, você pode acabar ficando com muitas classes que possuam, por exemplo, um método add. Contanto que as listas de parâmetros e os valores retornados dos diversos métodos add , sejam semanticamente equivalentes, tudo bem.

Use Nomes a Partir do Domínio da Solução

Lembre-se de que serão programadores que lerão seu código. Portanto, pode usar termos de informática, nomes de algoritmo, nomes de padrões termos matemáticos, etc.

Use Nomes de Domínios do Problema

Quando não houver uma solução para programadores, use o nome do domínio do problema. Pelo menos o programador que fizer a manutenção do seu código poderá perguntar a um especialista em tal domínio o que o nome significa.

Distinguir os conceitos do domínio do problema dos do domínio da solução é parte da tarefa de um bom programador.

Adicione um Contexto Significativo

Há poucos nomes que são significativos por si só, a maioria não é. Por conta disso, você precisa usar nomes que façam parte do contexto para o leitor. Para isso, você os coloca em classes, funções, e namespaces bem nomeados. Se nada disso funcionar, então talvez como ultimo recurso, seja necessário adicionar prefixos ao nome.

Imagine que você tenha varias variáveis chamadas nome, sobreNome, rua, numeroCasa, cidade, estado, cep. Vistas juntas, fica bem claro que elas formam um endereço. Pode-se adicionar um prefixo para um contexto: endNome, endSobreNome, endEstado, etc. Pelo menos, os leitores entenderam que essas variáveis fazem parte de uma estrutura maior

Não Adicione Contextos Desnecessários

Nomes curtos geralmente são melhores contanto que sejam claros. Não adicione mais contexto a um nome do que o necessário.

Os nomes accountAddress e custumerAddress estão bons para instancias de classe Endereço, mas seriam ruins para nomes de classes. Se precisar diferenciar entre endereços de MAC, endereço de portas e endereços de WEB, uma ideia seria usar EnderecoDeCorrespondenciam MAC e URI. Os nomes resultantes são mais precisos, motivo pelo qual existe a tarefa de se atribuir nomes.

CONCLUSÃO

O mais difícil sobre escolher bons nomes é a necessidade de se possuir boas habilidades de descrição, essa é uma habilidade que se aprende e não de aprimoramento técnico, gerencial ou empresarial. Como consequência, muitas pessoas nessa área não aprendem muito bem. Siga algumas dessas regras e note que você melhorou a legibilidade e a clareza de seu código. Se você estiver fazendo a manutenção no código de alguém, use ferramentas de refatoração para ajudar a resolver essas questões. Em pouco tempo valerá a pena, e continuará valendo a longo prazo.

REFERÊNCIAS

Livro - Código Limpo: Habilidades Práticas do Agile Software
Autor - Robert C. Martin

quinta-feira, 1 de maio de 2014

Padrão de Projeto Flyweight

O padrão de projeto Flyweight, ou "peso-mosca", é um padrão estrutural que tem por finalidade usar de compartilhamento de objetos de granularidade fina, isto é, objetos que são iguais exceto por pequenos detalhes. Este padrão usa as informações comuns a diversos objetos em apenas um componente, o resto dos dados únicos são utilizados como parâmetros de métodos, reduzindo o número de objetos em uma aplicação, e assim agilizando o processamento da execução destes muitos objetos repetidos.
Para um bom resultado do uso deste padrão,use-o quando todas as afirmativas seguintes forem verdadeiras:

- A aplicação usa um grande número de objetos;
- Custos de armazenamento são grandes por causa da grande quantidade de objetos;
- A maior parte do estado dos objetos podem ser extrínseco (informações que dependem e variam com o contexto do flyweight, e que por esse motivo nao podem ser compartilhados);
- Muitos grupos de objetos podem ser substituídos por poucos objetos que possam ser compartilhados (estado extrínseco deve ser removido para outro lugar);
- A aplicação não depende da identidade do objeto;

Ainda deve-se pensar sobre o ponto fraco do padrão, que é dependendo da quantidade e da organização dos objetos a serem compartilhados, pode haver um grande custo para procura deles. Então ao utilizar o padrão deve ser analisado qual a prioridade no projeto, espaço ou tempo de execução.
Os participantes deste padrão seriam:

- Cliente : computa ou armazena o estado extrínseco do flyweight, assim como mantém a referência para ele;
- Flyweight: declara uma interface por onde pode receber e atuar sobre estados extrínsecos;
- FlyweightFactory: cria e gerencia objetos flyweights e garante que eles sejam compartilhados corretamente. Quando um cliente solicita um flyweight, um objeto FlyweightFactory fornece a ele, ou cria se caso ele não exista.
- ConcretesFlyweights (no exemplo do processador de texto seria o caractere em si): implementa a interface Flyweight e armazena só os estados intrísecos (compartilháveis).

Um bom exemplo da utilização é no desenvolvimento de jogos, afinal são usadas várias imagens que representam as entidades que compõe o jogo (cenário, jogador, inimigo..).
Isso causa duplicação de informação.Por exemplo, quando aparecem vários inimigos iguais no jogo, o mesmo conjunto de imagens é criado repeditas vezes.
Para exemplificar isso em código, começamos criando uma classe Imagem (informação intríseco):
  public class Imagem {
    protected String NomedaImagem;

    public Imagem(String imagem) {
        NomedaImagem = imagem;
    }

    public void desenharImagem() {
      System.out.println(NomedaImagem + " desenhada!");
    }
    }

Entao criar a classe Ponto (informação extrínseco):
  public class Ponto {
    public int x, y;

    public Ponto(int x, int y) {
        this.x = x;
        this.y = y;
    }
    }
Uma classe Flyweight será feita para armazenar o método de desenho da imagem em um determinado ponto:
 public abstract class SpriteFlyweight {
    public abstract void desenharImagem(Ponto ponto);
    }
Após a criação da classe Flyweight, criamos uma classe Flyweight Concreta, que seria a implementação da operação de fato:
 public class Sprite extends SpriteFlyweight {
    protected Imagem imagem;

    public Sprite(String nomeDaImagem) {
        imagem = new Imagem(nomeDaImagem);
    }

    @Override
    public void desenharImagem(Ponto ponto) {
        imagem.desenharImagem();
        System.out.println("No ponto (" + ponto.x + "," + ponto.y + ")!");
    }
    }
  
A classe fábrica guardará todas as imagens que serão compartilhadas e terá um método para pega-las, tendo assim todo acesso as imagens centralizado nessa classe:
 public class FlyweightFactory {

    protected ArrayList flyweights;

    public enum Sprites {
        JOGADOR, INIMIGO_1, INIMIGO_2, CENARIO_1
    }

    public FlyweightFactory() {
        flyweights = new ArrayList();
        flyweights.add(new Sprite("jogador.png"));
        flyweights.add(new Sprite("inimigo1.png"));
        flyweights.add(new Sprite("inimigo2.png"));
        flyweights.add(new Sprite("cenario1.png"));
    }

    public SpriteFlyweight getFlyweight(Sprites jogador) {
        switch (jogador) {
        case JOGADOR:
            return flyweights.get(0);
        case INIMIGO_1:
            return flyweights.get(1);
        case INIMIGO_2:
            return flyweights.get(2);
        default:
            return flyweights.get(3);
        }
    }
    }
 
E utilizando esse padrão:
 public static void main(String[] args) {
    FlyweightFactory factory = new FlyweightFactory();

    factory.getFlyweight(Sprites.JOGADOR).desenharImagem(new Ponto(10, 10));

    factory.getFlyweight(Sprites.INIMIGO_1).desenharImagem(
            new Ponto(100, 10));
    factory.getFlyweight(Sprites.INIMIGO_1).desenharImagem(
            new Ponto(120, 10));
    factory.getFlyweight(Sprites.INIMIGO_1).desenharImagem(
            new Ponto(140, 10));
    factory.getFlyweight(Sprites.INIMIGO_2).desenharImagem(
            new Ponto(60, 10));
    factory.getFlyweight(Sprites.INIMIGO_2).desenharImagem(
            new Ponto(50, 10));
    }
Pode ser implementado algo para eliminar um objeto nao usado mais, liberando ele da memória e diminuindo espaço.

Referências :
Livro - Padroes de Projetos - Solucoes Reutilizaveis - Gamma Erich
Site - http://brizeno.wordpress.com/category/padroes-de-projeto/flyweight/