Suponha que o seu programa tenha uma caixa de diálogo pedindo ao usuário entrar um número entre zero e cem. O que você acha que o programa deve fazer se o usuário entrar um número fora da faixa, por exemplo, 250?

1. Mostrar uma mensagem ao usuário solicitando um valor dentro da faixa;

2. Não tomar nenhuma providência e deixar o ambiente de execução mostrar alguma mensagem relacionada a exceptions;

3. Disparar uma exception específica;

Acho que todos concordarão que a opção 1 é melhor, mesmo que isso consuma alguns recursos em tempo de execução.

Mas suponha agora que você desenvolveu uma função para uma biblioteca e que esta função aceita um inteiro cujo valor deve estar entre zero e cem. Você documenta este fato adequadamente para todos que forem usar esta função. Agora suponha que quem chamar esta função passe um valor fora da faixa adequada. Qual das opções acima você acha que a função deve fazer?

Agora a pergunta ficou mais difícil. A alternativa 1 está fora de questão: você não sabe qual é o ambiente de execução (Windows, Web, Serviço, Console) e portanto como mostrar a mensagem. Você nem sabe nem se mostrar uma caixa de mensagem seria adequado, pois pode ser que o erro não seja tão grave do ponto de vista do programa. A alternativa 2 exibirá uma mensagem genérica e talvez termine o programa, algo não muito palatável. A alternativa três parece a mais adequada, mas mesmo assim existe um problema: o custo de verificar os valores pode ser relativamente alto e, afinal de contas, o domínio da função foi bem documentado. Por que a função deve “pagar” este preço por um erro do programador que chama a função – não do usuário final?

Talvez uma melhor alternativa seja ter duas versões da biblioteca: uma com as verificações ligadas, para ser usada durante o desenvolvimento e testes e outra com as verificações desligadas, para ser usada na versão final. Isso é exatamente o que o Windows faz, já que ele é uma grande biblioteca. A versão do Windows que você compra na loja possui grande parte das possíveis verificações desligadas. Para os desenvolvedores existe uma versão chamada “checked build”, na qual são feitas muitas verificações. Esta versão é substancialmente mais lenta e por isso estas verificações não existem no produto final.

A criação de uma versão “debug” e outra “release” é uma prática antiga, usada pelo menos desde a linguagem C através de compilação condicional. Ela está presente hoje tanto no ambiente do Visual Studio como nos programas através de chamadas ao método System.Diagnostics.Debug.Assert.

Este tipo de programação é um assunto estudado há algum tempo. Ele é chamado de “Code Contracts” (“contratos de código”). Algumas linguagens como Eiffel trazem este recurso como um de seus princípios.

A versão 4.0 do .NET Framework, cuja versão final deve sair no início de 2010, virá com um bom suporte a Code Contracts.

A ideia é a seguinte: quando você define uma função, você pode declarar algumas características a respeito de como ela se relaciona com o resto do seu programa. Os principais “contratos” são os seguintes:

· Pré-condições: São usadas, por exemplo, para validar os domínios dos parâmetros da função;

· Pós-condições: Você pode querer garantir que logo antes de retornar, uma função não tenha alterado o estado de forma inválida ou inconsistente;

· Invariantes: Define alguma condição que deva ser sempre verdadeira durante toda a execução da função;

· Assert: Semelhante ao Debug.Assert, discutido anteriormente.

A existência de uma implementação padronizada para contratos permite que ferramentas de verificação estática ou debuggers consigam detectar violações dos contratos mais facilmente.

A implementação, utilizada através de chamadas de funções, não traz mudanças na linguagem. Isso não impede a Microsoft de adicionar este suporte no futuro, especialmente se o recurso for bem recebido.

Mauro Sant’Anna (mas_mauro@hotmail.com) sempre usou contratos em seus programas.

Olho: A criação de uma versão “debug” e outra “release” é uma prática antiga.