batman-dc-ultimate-online

Batman - DC Universe Online

Olá Pessoal! No meu primeiro post sobre Design By Contract recebi questionamentos interessantes sobre os prós e contras dessa abordagem comparando-a com a forma tradicional de validações com if…throw, logo, o objetivo deste post será evidenciar as diferenças entre essas duas abordagens.

No último post lancei um desafio ao meu amigo Gustavo Badke (@guripunk) após ele ter escrito o seguinte comentário: “Pra min isso é firula, não vejo diferença nenhum do if(…) throw new Exception…“. Alguns códigos que vou apresentar aqui foram escritos por ele, então, considerem-no como uma participação especial nesse post :) . Vamos lá:

Pré-condições

Tradicionalmente, quando precisamos verificar se os parâmetros de um método qualquer estão válidos para utilização, escrevemos algo semelhante ao código abaixo:

1
2
3
4
5
6
7
8
9
10
11
public Cor(int pVermelho, int pVerde, int pAzul)
{
    if (pVermelho < 0 || pVermelho > 255)
        throw new ArgumentOutOfRangeException("O argumento 'pVermelho' deve ser entre 0 e 255");
    if (pVerde < 0 || pVerde > 255)
        throw new ArgumentOutOfRangeException("O argumento 'pVerde' deve ser entre 0 e 255");
    if (pAzul < 0 || pAzul > 255)
        throw new ArgumentOutOfRangeException("O argumento 'pAzul' deve ser entre 0 e 255");
 
    // Implementação...
}

A abordagem acima resolve o nosso requisito de validação dos parâmetros, porém, o código utilizado para isso é extremamente “carregado” para a implementação de um requisito simples. Utilizando DbC a implementação ficaria da seguinte forma:

1
2
3
4
5
6
7
8
public Cor(int pVermelho, int pVerde, int pAzul)
{
    Contract.Requires<ArgumentOutOfRangeException>(pVermelho >= 0 && pVermelho <= 255);
    Contract.Requires<ArgumentOutOfRangeException>(pVerde >= 0 && pVerde <= 255);
    Contract.Requires<ArgumentOutOfRangeException>(pAzul >= 0 && pAzul <= 255);
 
    // Implementação...
}

A abordagem acima não só resolve o nosso requisito de validação dos parâmetros, como também deixa o código mais legível e você ainda conta com a validação dos contratos em tempo de compilacão. O método Requires ainda possibilita a configuração de uma mensagem personalizada caso seja necessário.

Também poderíamos usar AOP para resolver nosso problema, contudo, seria utilizar um recurso muito poderoso e complexo para uma necessidade muito simples. Se você não vai usar AOP para nenhuma outra necessidade do seu projeto, não recomendo utilizar nessa situação.

Não devemos avaliar o poder do DbC analisando somente suas pré-condicões. Precisamos avaliar se todos os aspectos dessa abordagem serão realmente úteis no nosso projeto, isso inclui também as pós-condições e invariantes.

Pós-condições

É comum, após a execução de um método, realizar a verificação de estado da classe nos membros que foram afetados pelo mesmo. Nessa situação, usando a abordagem tradicional, faríamos uma verificação qualquer no final do corpo do método. Algo como mostra o código abaixo:

1
2
3
4
5
6
7
public void AdicionarVermelho(int pValor)
{
    this.Vermelho += pValor;
 
    if (this.Vermelho > 255)
        throw new Exception("A propriedade Vermelho não deve ser maior que 255");
}

Um dos principais problemas nessa abordagem é a falta de legibilidade no método. Veja a solução usando DbC:

1
2
3
4
5
6
public void AdicionarVermelho(int pValor)
{
    Contract.Ensures(this.Vermelho <= 255);
 
    this.Vermelho += pValor;
}

Quando trabalhamos com contratos, sempre inserimos os mesmos no topo dos métodos, o que facilita muito a leitura dos códigos. Mesmo com exemplos simples como esses, podemos perceber que a curva de aprendizagem é muito baixa.

Invariantes

Como você garantiria que um objeto estará válido sempre independente de quais e quantos métodos forem executados? Provavelmente, você precisará construir algum método de validação e chamá-lo ao final de cada método público de sua classe. Nesse ponto, percebemos uma grande diferença entre as abordagens, pois usando DbC, só precisaríamos construir o método Invariant com as validações necessárias e pronto, vejam o exemplo:

1
2
3
4
5
[ContractInvariantMethod]
private void Invariant()
{
    Contract.Invariant(this.Vermelho <= 255);
}

Em resumo, como disse anteriormente, é necessário analisar todos os recursos da abordagem e não somente um. Apensar das pré-condições serem facilmente substituídas por if…throw, isso não acontece da mesma forma com as invariantes.

DbC não é para todos os projetos, DbC não é regra, DbC não é melhor ou pior que ninguém. Use em seus projetos quando precisar de todos os recursos que a abordagem dispõe, e mesmo assim não deixe de analisar alternativas como AOP.

Abraços Pessoal!

Tagged with:
 
"O futuro parece ser extremamente brilhante, com muitas possibilidades pela frente - grandes possibilidades. Como a canção diz: 'Nós precisamos apenas começar'." - Bruce Lee

PodcastProgramação orientada a aspectos é uma ótima saída para solução de requisitos ortogonais em projetos de software. Após algumas experiências e palestras sobre o tema, tive a oportunidade de participar da gravação de um podcast com a galera do .NET Architects: Alexandre Valente e Fábio Gouw.

Nos projetos em que usei AOP, trabalhei com o framework PostSharp, mas no podcast também abordamos Aspect.NET e AspectJ.

Vale muito a pena pesquisar sobre o tema.

Podcast

Post no DNA

http://podcast.dotnetarchitects.net/2010/05/podcast-13programacao-orientada-a-aspecto/

Tagged with: