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:
 

Design By Contract (DbC) em .NET

"O homem, criatura viva e criador individual, é sempre mais importante do que qualquer estabelecido estilo ou sistema." - Bruce Lee

Design By Contract (DbC)Olá Pessoal! Estou escrevendo esse post no intuito de compartilhar com vocês o resultado de uma de minhas pesquisas: Como aplicar o Design By Contract usando os recursos do framework .NET.

O conceito DbC é usado para garantir o estado de seus objetos em tempo de execução. Basicamente, quando construímos nossa classe usando o conceito DbC, definimos acordos formais (o que chamamos de contratos) com quem a utiliza. Esses contratos visam garantir regras de utilização e estado, regras essas que são expressas através de pré-condições, pós-condições e invariantes.

Vamos explorar algumas situações do nosso dia-a-dia para fortalecer o conceito e aprender a utizar a classe Contract que se encontra no namespace System.Diagnostics.Contracts:

Como podem ver no código abaixo, estou criando uma classe chamada Cor que receberá no construtor uma string contendo o código hexadecimal. No teste, desejo garantir que caso a classe Cor seja instanciada com um parâmetro nulo, uma exceção do tipo ArgumentNullException seja lançada.

1
2
3
4
5
6
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Deve_rejeitar_parametros_nulos()
{
    var cor = new Cor(null);
}

Para fazer o nosso teste passar, poderíamos verificar se o parâmetro é nulo usando if e lançar a exceção, porém, quando pensamos em contratos, o que fazemos é criar uma pré-condição de utilização da nossa classe. Para construir pré-condições, utilizamos o método Requires da classe Contract informando qual tipo de exceção será lançada caso o contrato for quebrado. Vejam o código abaixo:

1
2
3
4
5
6
7
8
9
public class Cor
{
    public Cor(string pCodigoHexadecimal)
    {
        Contract.Requires<ArgumentNullException>(pCodigoHexadecimal != null);
 
        // Código que converte o hexadecimal...
    }
}

Como podem ver, o código fica bem mais limpo e expressivo. Sem falar que caso você tenha o plugin do Visual Studio instalado terá essas verificações de constrato em tempo de design, isso mesmo, enquanto você cria suas classes seu código será analisado para verificar possíveis quebras de contrato. Vejam a imagem abaixo:

validacaoContrato

Vamos para outra situação: Agora nossa classe Cor possui um construtor que recebe as taxas de vermelho, verde e azul. Construímos um método para adicionar tonalidades de vermelho na cor, porém, a taxa máxima permitida de vermelho é de 255.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[TestMethod]
public void Deve_garantir_taxa_de_vermelho_menor_ou_igual_255()
{
    var vermelho = 200;
    var verde = 200;
    var azul = 200;
    var cor = new Cor(vermelho, verde, azul);
 
    try
    {
        cor.AdicionarVermelho(56);
    }
    catch
    {
        // O método deve lançar a exceção pois 200 + 56 ultrapassa o limite de 255.
        Assert.AreEqual(vermelho + 56, cor.Vermelho);
    }
}

Para garantir o limite da taxa de vermelho no nosso objeto, precisamos que o nosso método seja executado para então verificar o estado. Em DbC podemos criar uma pós-condição para avaliar o estado do nosso objeto ao final da execução do nosso método/propriedade. No exemplo abaixo usamos o método Ensures para criar a pós-condição que verifica a taxa de vermelho do nosso objeto após a execução do nosso método. Os contratos sempre são definidos no início dos métodos/propriedades.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Cor
{
    public int Vermelho { get; private set; }
 
    public Cor(int pVermelho, int pVerde, int pAzul)
    {
        this.Vermelho = pVermelho;
        // Configurar as outras propriedades...
    }
 
    public void AdicionarVermelho(int pValor)
    {
        // Aqui estamos garantindo que ao final da execução desse método, a propriedade Vermelho deverá respeitar o limite de 255;
        Contract.Ensures(this.Vermelho <= 255);
 
        this.Vermelho += pValor;
    }
}

As pós-condições são tão simples de serem escritas quanto as pré-condições.

Outra forma de garantir o nosso limite de 255 é criar um contrato invariante, ou seja, uma regra que será mantida sempre, independente de quais método forem executados no objeto. Para criamos o contrato invariante no .net usamos o método Invariant da classe Contract. Métodos invariantes são verificados sempre após a execução de qualquer método público da sua classe, eles só devem ter declarações de contratos e não podem retornar valores.

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

Como vimos, DbC é uma obordagem muito interessante e pode nos auxiliar muito no dia-a-dia. O objeto desse post não é apresentar bons padrões para design de testes ou grandes soluções no trabalho com cores, os exemplos aqui mostrados visam apenas apresentar alguns recursos da classe Contract. Façam os devidos testes e contem o que acharam do recurso e dessa abordagem.

Separei alguns links interessantes sobre o assunto no delicious, veja no link: delicious.com/denisferrari/DbC.

Fico no aguardo do feedback de vocês.

Abraços!

Tagged with: