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