quarta-feira, 14 de abril de 2010

Usando reflection para simplificar nossa vida

Em um projeto que estou trabalhando (o mesmo que citei aqui), existe a necessidade de se comunicar com programas COBOL. A comunicação é feita de uma forma um tanto arcaica. Temos que passar um string contendo os diversos campos, cada um com o tamanho exato.

Digamos que precisamos passar as informações de uma nota fiscal, teríamos essa definição:
  • Data de expedição – 10 caracteres;
  • Nome do Cliente – 15 caracteres;
  • Número da nota – 5 dígitos;
  • Valor – 5 dígitos.
Ao gerar a string contendo o seguintes valores:
  • Data: 01/01/2010;
  • Nome: ALGUM NOME;
  • Número: 123;
  • Valor: 15.
Temos isso como resultado: “2010-01-01ALGUM NOME     00012300015”.

A SOLUÇÃO ANTIGA

A solução que existia para facilitar isso era gerar uma classe de acordo com a definição dos campos, que retorna a string formatada. A classe gerada ficaria assim:
class NotaFiscal 
{ 
  private string dataExpedicao; 
  private string nomeCliente; 
  private string numero; 
  private string valor; 

  public string DataExpedicao 
  { 
    get{return dataExpedicao;} 
    set{dataExpedicao = Utils.FormataCaracteres(value, 10);} 
  } 

  public string NomeCliente 
  { 
    get{return nomeCliente;} 
    set{nomeCliente = Utils.FormataCaracteres(value, 15);} 
  } 

  public string Numero 
  { 
    get{return numero;} 
    set{numero = Utils.FormataDigitos(value, 5);} 
  }

  public string Valor 
  { 
    get{return valor;} 
    set{valor = Utils.FormataDigitos(value, 5);} 
  }
 
  public string ObtemString() 
  { 
    return dataExpedicao + nomeCliente + numero + valor; 
  } 
}
Essa classe tem alguns problemas:
  • todos os dados são strings, não é usada a tipagem da linguagem (int, string, DateTime);
  • a definição de tipo e tamanho dos campos fica espalhada pelo código;
  • a ordem dos campos precisa ser verificada dentro da função ObtemString;
  • caso alguma modificação seja feita nos campos, fica ruim de dar manutenção nesse código.

A Nova solução

A solução que procurei desenvolver tinha alguns pré-requisitos, entre eles:
  • a classe não deveria ter informações relacionadas aos campos que seriam montados na string (definição de tipo, tamanho, etc);
  • poder utilizar os tipos da linguagem (int, DateTime, Enum);
  • a definição de mapeamento da classe para a string a ser gerada deveria ser clara e facilmente editável;
O caminho natural foi desenvolver algo semelhante a um Mapeamento Objeto-Relacional. Eu batizei-o de Mapeamento Objeto-Cobolar. ;)

Com isso temos uma classe de modelo, contendo os dados devidamente tipados e um mapeamento que define quais campos serão transportados para a string, o formato e a posição. Todas essas informações conseguimos obter usando reflection e o mapeamento definido.
class NotaFiscal 
{ 
  public DateTime DataExpedicao { get; set; } 
  public string NomeCliente { get; set; } 
  public int Numero { get; set; } 
  public int Valor { get; set; } 
}

<mapping class=”NotaFiscal”> 
  <field name=”DataExpedicao” Length=”10” /> 
  <field name=”NomeCliente” Length=”15” /> 
  <field name=”Numero” Length=”5” /> 
  <field name=”Valor” Length=”5” /> 
</mapping>

Como você pode ver, não é necessário definir o tipo do campo pois ele é inferido à partir do tipo utilizado no modelo. Ou seja, sabemos que um DateTime deverá ser convertido para o formato yyyy-MM-dd, um inteiro deve ser preenchidos com zeros a esquerda e uma string com espaços a direita.

Para obter a string à partir de um modelo, o que precisa ser feito é chamar um método assim:
conversor.GetString(meuModelo);

Implementando a solução

A implementação não tem muito segredo é apenas uma questão de obter cada valor do nosso modelo seguindo a definição do XML.

O ponto mais importante é a conversão do valor tipado para a string. Como cada tipo tem um formato específico, criei um conversor para cada um. Por exemplo:
class IntConverter : ITypeConverter 
{ 
  public string GetString(Field field, object value) 
  { 
       int valorInteiro = Convert.ToInt32(value); 
       return valorInteiro.ToString().PadLeft(‘0’, field.Length); 
  } 
}

Conclusão

Muitas vezes vale a pena perder um pouco de tempo desenvolvendo uma infra-estrutura que facilite o desenvolvimento. Alguns minutos perdidos no início do projeto podem salvar muitos minutos mais adiante, e, o mais importante, evitar muita dor de cabeça.