Design by Contract 101–parte 2–limpando o código

Publicado em 29/08/2010

4


Olha nóis ai, outra vez? Smiley de boca aberta 

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:

  1. Não pode especificar qual é a Exception que deveria ser disparada;
  2. 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”:

image

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 Smiley piscando

Etiquetado:
Publicado em: Sem categoria