22 de julho de 2010, por Denis Ferrari em Design, Tecnologias

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.

Abraços!


Comentários:
19 Comentários postados em "Design By Contract (DbC) em .NET"
Jhonatan Fernando on julho 22nd, 2010 at 17:04 #

Denis, parabéns pelo artigo, ficou muito bom!!


Edmilson on julho 22nd, 2010 at 18:08 #

Denis, muito bom artigo, seu blog esta show de Bola!!
[]´s

Edmilson


Denis Ferrari on julho 22nd, 2010 at 18:36 #

Obrigado pelo feedback Edmilson! :)


Gustavo on julho 22nd, 2010 at 18:55 #

Pra min isso é firula, não vejo diferença nenhum do if(…) throw new Exception…


Denis Ferrari on julho 22nd, 2010 at 23:47 #

Oi Gustavo,

Por que não fazemos o seguinte: Faça sua implementação da mesma classe garantindo as regras a sua maneira, depois comparamos os resultados ok? O que acha?


Cesar Rabelo on julho 23rd, 2010 at 11:25 #

Esse namespace contracts está disponível em qual versão do .net framework? Ou ela é externa a .net framework? Tentei simular esse exemplo no vs2008 framework 3.5 e não achei esse namespace. System.Diagnostics.Contracts: só achei essas System.Diagnostics.CodeAnalysis;
System.Diagnostics.Eventing;
System.Diagnostics.PerformanceData;
System.Diagnostics.SymbolStore;


Denis Ferrari on julho 23rd, 2010 at 11:44 #

Olá Cesar,

O exemplo está na versão 4.0. Para trabalhar com contratos na versão 3.5 do framework você pode utilizar o Spec#. Veja maiores informações no link:
http://research.microsoft.com/en-us/projects/specsharp/

Abraços!


Gustavo on julho 23rd, 2010 at 11:46 #

Blz,

Me envie essa solution q vc usou nesse demo q eu altero do meu jeito sem essas firulas de Contracts… hehe

KISS


Denis Ferrari on julho 23rd, 2010 at 11:52 #

Já está no seu e-mail.

Não precisa desenvolver o construtor do hexadecimal, só garantir as pré-condições, pós-condições e invariantes sem usar os contratos. Se preocupe com o valor mínimo e máximo das taxas (0 -> 255).
;)


Renan on julho 23rd, 2010 at 12:13 #

Parabens, gostei.Aprendizado novo.


Guilherme on julho 23rd, 2010 at 16:37 #

Podemos usar DbC para fazer validações nas entidades? Por exemplo garantir que uma variavel nunca seja nula ou vazia, esse tipo de coisa? É uma boa pratica ou recomendado?


Denis Ferrari on julho 23rd, 2010 at 17:21 #

Oi Guilherme,

O DbC é justamente para isso, garantir o estado do seu objeto durante todo o tempo de vida dele. Você protege seus métodos contra parâmetros inválidos e suas propriedades contra configurações errôneas.

É uma das formas de garantir o estado do seu objeto, algumas pessoas utilizam AOP para isso, se quiser saber mais veja o link:
http://podcast.dotnetarchitects.net/2010/05/podcast-13programacao-orientada-a-aspecto/

Abraços!


Dilter Porto on julho 26th, 2010 at 15:33 #

Denis,
acho que a única dúvida que vem é com relação ao custo desta implementação. A maior clareza no código e a utilização de uma técnica de propósito específico para um problema (manutenção correta de estado dos objetos) traz um custo maior se comparado às tradicionais implementações com if .. throw exception ?


Denis Ferrari on julho 26th, 2010 at 18:44 #

Olá Dilter,

A curva de aprendizado dessa técnica é muito pequena, o que torna o seu custo irrisório.

Vou escrever um novo post comparando as duas soluções.

Abraços!


Fabrício Vargas Matos on julho 27th, 2010 at 13:58 #

Fala Denis,

Legal saber do recurso no .NET.

O uso de pré/pós-condições e invariantes (não estou falando de classes .NET, mas da técnica) é algo muito interessante para especificação de comportamentos dos algoritmos e sua prova de correção. Aplicamos isso muito bem em casos mais específicos, como algoritmos de busca e ordenação por exemplo e outros comportamentos de infra. Mas na indústria de sistemas de informação, na camada de domínio, não vemos um uso adequado. Custo? Talvez. Embora a aderência dessas técnicas (básicas em Métodos Formais) ao desenvolvimento de sistemas de informação seja questionável, a verdade é que muitos poucos desenvolvedores realmente sabem utilizar coisas como Invariantes, indução matemática, etc..

Nessa seara tem pano pra manga!

Abraços!


Cadu on julho 28th, 2010 at 0:58 #

Fala meu camarada parabéns gostei do post abração


Dilter Porto on julho 30th, 2010 at 14:55 #

Olá Denis.
Desculpa, não especifiquei bem o custo que estava me referindo.

Estava falando a respeito do custo de performance pelo fato da necessidade da instanciação de mais objetos e processamentos adicionais. Isso tudo comparado ao custo da implementação tradicional, com o “if”.

Valeu.


Denis Ferrari on agosto 5th, 2010 at 1:52 #

Dilter,

Realizei vários testes na minha máquina comparando as implementações, não tive uma queda de performance expressiva usando DbC. Como isso não é uma comprovação científica, sugiro que você faça alguns testes de utilização na arquitetura que deseja implementar, e é claro, compartilhe os resultados :) .

Abraços!


[...] ter escrito o seguinte comentário: “Pra min isso é firula, não vejo diferença ne&#1…“. Alguns códigos que vou [...]


Deixe um comentário

Nome: 
Email: 
URL: 
Comentário: