
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!
últimos comentários