Olha nóis ai, outra vez?
Como disse no primeiro post dessa nova série, comecei a aprender DbC por entender que preciso escrever um código mais sólido. Em poucas palavras, vi e entendi que a teoria do DbC faz todo o sentido e pode me ajudar a escrerver um código melhor. Ainda depois, descobri que a Microsoft disponibilizou o Code Contracts, um misto de bibliotecas e extensões para o Visual Studio, que estão alinhados com essa “perspectiva”.
Entretanto, e sempre já um entretanto, ao iniciar uma thread no DotNetArchitects sobre o assunto, percebi que a comunidade pareceu não gostar muito do aspecto que os contratos deram ao código. Confesso que não vi nada tão feio, mas como tanta gente boa viu problemas …
Eu estou tratando aqui apenas de quem utiliza o VS2010. Se você ainda está usando o VS2008, ou quer consultar um bom artigo que trata desse assunto, dê uma lida no post do Felipe Teixeira.
Sobre essa parte
Na primeira parte, mostrei características realmente básicas do Code Contracts. Meu desejo, para essa segunda parte, era continuar nessa linha, mostrando como construir pós-condições. Mas, devido ao feedback recebido, resolvi mudar um pouco a sequência das coisas. Por isso, agora, vou mostrar um pouquinho sobre como estabelecer contratos “sujando menos” o código. Além disso, vou mostrar como ativar a reescrita do run-time.
Compreensão sobre o que é um contrato
Bom, antes de começar a codificar usando contratos, temos que ter uma compreensão melhor sobre o que eles de fato são. Contratos tem os seguintes propósito:
- Deixar claro que características são esperadas para as entradas nos métodos. Repare, na declaração de um método já especificamos seu tipo e também o formato como queremos receber esse dado (por referência, ou por valor). Mas observe que não especificamos nada além disso. Por exemplo, não dizemos se este parâmetro poderá ser uma referência nula (geralmente não pode). Isso é obtido pelas pré-condições;
- Tornar evidente o que devolveremos. Outra vez, no nosso código especificamos o tipo do retorno. Mas nada além disso! Por exemplo, em um retorno numérico, não tornamos evidente, o intervalo válido (sendo assumido o tipo). Isso é obtido pelas pós-condições;
- Tornar evidente o que é um estado valido para os objetos. Que valores são aceitáveis para as propriedades do objeto. Quantas vezes vi código de programadores (nem tão iniciantes), que validavam o set das propriedades com um “diferente de null”, mas inicializavam o tipo com essa propriedade em valor nulo. A garantia de que isso não ocorra são as invariantes.
Veja, se o .net permite que escrevamos contratos para nossos métodos e o VS faz uma verificação do atendimento desses métodos, então, acho que o código “um pouco mais sujo” não seja algo indesejável. Muito pelo contrário… Mas…
Se livrando da “sujeira do código” aproveitando código legado
Eu considero fundamental que métodos públicos (que façam parte da interface pública) de um tipo devem implementar algum código defensivo. Recorrendo ao exemplo do post passado:
public int SayHelloToFirstContact(string[] contacts) { if (contacts == null) throw new ArgumentNullException(); if (contacts.Length < 1 || contacts[0] == null) throw new ArgumentException(); Console.WriteLine("Hello {0}!", contacts[0]); return contacts.Length; }
Repare, a parte defensiva desse código, ao meu ver é indispensável. Você não acha (por favor, escreva um comentário escrevendo o porquê). Sendo assim, não achei tão estranha a recomendação do DataContracts:
1 public int SayHelloToFirstContact(string[] contacts) 2 { 3 Contract.Requires(contacts != null); 4 Contract.Requires(contacts.Length > 0); 5 Contract.Requires(contacts[0] != null); 6 Contract.Requires(contacts[0].Length > 0); 7 8 Console.WriteLine(contacts[0]); 9 return contacts.Length; 10 }
Repare, uma pequena mudança em um código que já era, ao meu ver, indispensável produziu uma série de benefícios diretos para nosso código que, agora, conta com uma verificação extra do VS. Mas …
Mas daí você talvez responda: “E o meu código legado? Vou ter que reescrever tudo?”. Nada! O povo da Microsoft pensa mesmo quando escreve algo novo. Por isso, a alteração abaixo já é suficiente:
1 public int SayHelloToFirstContact(string[] contacts) 2 { 3 if (contacts == null) 4 throw new ArgumentNullException(); 5 if (contacts.Length < 1 || contacts[0] == null) 6 throw new ArgumentException(); 7 8 Contract.EndContractBlock(); 9 10 Console.WriteLine("Hello {0}!", contacts[0]); 11 return contacts.Length; 12 }
Contract.EndContractBlock() reconhece nosso código legado de verificação e nos dá todos os benefícios da verificação estática.
Ativando a reescrita do código para validação em run-time
Se você escreveu algum código usando Contracts substituindo sua validação padrão, talvez tenha ficado desapontado com umas duas coisinhas:
- Não pode especificar qual é a Exception que deveria ser disparada;
- Chamando um método com contratos, mesmo não o respeitando, a execução continuou.
Vamos responder uma coisa de cada vez. Em primeiro lugar, por default, o código relacionado a contratos não é emitido no IL já que se a verificação estática funcionou, não há chances de haver chamadas inválidas (e o código não precisa mais validar). Mas (outra vez, sempre tem um mas), podemos não ter controle sobre todos os acionamentos dos métodos, então, ativemos a reescrita do runtime. Para isso, acesse as propriedades do projeto e ligue a opção “Perform Runtime Contract Checking” e mude o Assembly Mode para “Standard Contract Requires”:
Pronto!
Quanto a questão de selecionar a excessão que deverá ser lançada em caso de falta, basta usar uma sobrecarga do método Requires.Observe o código:
1 public int SayHelloToFirstContact(string[] contacts) 2 { 3 Contract.Requires<ArgumentNullException>(contacts != null); 4 Contract.Requires<ArgumentException>(contacts.Length > 0); 5 Contract.Requires<ArgumentException>(contacts[0] != null); 6 Contract.Requires<ArgumentException>(contacts[0].Length > 0); 7 8 Console.WriteLine("Hello {0}!", contacts[0]); 9 return contacts.Length; 10 }
E, era isso ![]()






Stupied4ever
20/09/2010
Elemar,
Achei mtu bacana seus dois posts sobre DbC, porem eu estou com uma duvida, acredito que de conceito.
Imagine que eu tenho uma classe pessoa, com algumas propriedades (Nome, Idade, Sexo e CPF). Essa classe Sobreescreve o metoto ToString(), e dessa maneira ele tem que dizer ql o nome, CPF e outras propriedades.
Qual a melhor abordagem para o DbC? Devo validar os valores que seram colocados nas properties ou validar no metodo ToString???
elemarjr
20/09/2010
para mim, em ambos.
roberta
04/05/2011
interessante, estou estudando DbC pra uma pesquisa do mestrado e caí nessa sua mini-série por acaso. Uma surpresa muito agradável!
Espero que pretenda retomá-la. Pra mim, a parte mais interessante é a inferência automática de contracts a partir do código – Bertrand Meyer (que criou Eiffel) tem trabalhado nessa parte de inferência de contracts, e fiquei pensando se o CodeContracts de .net é capaz de fazer algumas das inferências que ele documentou.
abraços e até o DNAD!