Este artigo foi traduzido por máquina.

Cutting Edge

Dê a suas classes um contrato de software

Dino Esposito

image: Dino EspositoUma prática antiga mas boa do desenvolvimento de software recomenda que você coloque na parte superior de cada método — antes que qualquer comportamento significativo ocorra — uma barreira de instruções condicionais. Cada instrução condicional verifica uma condição diferente, devem verificar se os valores de entrada. Se a condição não for verificada, o código lança uma exceção. Esse padrão é conhecido como If-Then-Throw.

Mas If-Then-Throw tudo o que precisamos escrever código correto e eficaz? É suficiente para todos os casos?

A noção de que ele pode não ser suficiente em todos os casos, não é um novo. Design por contrato (DbC) é uma metodologia introduziu vários anos atrás, Bertrand Meyer baseiam na idéia de que cada um deles tem um contrato em que ele descreve formalmente ele espera e o que ele oferece. O padrão de If-Then-Throw quase abrange a primeira parte do contrato. ele não tem inteiramente a segunda parte. DbC nativamente não é suportado em qualquer linguagem de programação convencional. No entanto, as estruturas de existirem para permitir que você taste sabores DbC nos idiomas mais usados, como Java, Perl, Ruby, JavaScript e, claro, a Microsoft.Idiomas do NET Framework. No.NET, você fazer DbC por meio da biblioteca de contratos de código adicionada para o.NET Framework 4, localizado no assembly mscorlib. Observe que a biblioteca está disponível para aplicativos do Silverlight 4, mas não para aplicativos de telefonia do Windows.

Eu acredito que quase todos os desenvolvedores concordariam no princípio de que uma abordagem contratar primeiro para o desenvolvimento é ótimo. Mas acho que, para que muitos estão usando ativamente os contratos de código no.NET 4 aplicativos, agora que a Microsoft disponibilizou os contratos de software disponível e integradas no Visual Studio. Este artigo enfoca os benefícios de uma abordagem contratar primeiro para a capacidade de manutenção do código e facilidade de desenvolvimento. Esperamos que você pode usar argumentos neste artigo para vender contratos de código para seu chefe para seu próximo projeto. No futuro prestações desta coluna, vou detalhar aspectos como, por exemplo, configuração, tempo de execução ferramentas e recursos de programação, como herança.

Pensando sobre uma classe de calculadora simples

Os contratos de código são um estado de espírito; Você não deve colocá-los à parte até que são chamados para projetar um aplicativo grande que exija um super arquitetura e o emprego de várias tecnologias de última geração. Tenha em mente que — quando mal gerenciados — até mesmo os mais poderosa tecnologia pode causar problemas. Contratos de código são úteis para qualquer tipo de aplicativo, desde que você tem um bom entendimento deles. Então vamos começar com uma classe simples — uma classe de calculadora clássica, como:

public class Calculadora {Sum(Int32 x, Int32 y) de Int32 pública {return x + y; }

    Divide(Int32 x, Int32 y) de Int32 pública {retorno x / y; } }

Você provavelmente concordará que o código aqui não é realista, pois ele não tem pelo menos uma informação importante: uma verificação para ver se você está tentando dividir por zero. Conforme escrevemos uma versão melhor, vamos supor também que que temos um problema adicional para lidar com: A calculadora não oferece suporte a valores negativos. Figura 1 tem uma versão atualizada do código que adiciona algumas declarações de If-Then-Throw.

Figura 1 A classe de calculadora Implementando o padrão de If-Then-Throw

public class Calculadora {Sum(Int32 x, Int32 y) de Int32 pública { / / retirada de valores de entrada se (x < 0 | | y < 0) lançam novos ArgumentException(); / / Executar a operação de retorno do x + y; }

    Divide(Int32 x, Int32 y) de Int32 pública { / / retirada de valores de entrada se (x < 0 | | y < 0) lançam novos ArgumentException(); Se (y = = 0) lançam novos ArgumentException(); / / Executar a operação de retorno x / y; } }

Até agora, podemos poderá declarar nossa classe tanto começa a processar a entrada de dados ou, no caso de uma entrada inválida, ele simplesmente jogar antes de fazer qualquer coisa. E os resultados gerados pela classe? Que fatos sabemos sobre eles? Observando as especificações, esperamos que ambos os métodos retornam um valor não menos do que zero. Como podemos impor que e falhar se isso não acontece? Precisamos de uma terceira versão do código, como mostrado na a Figura 2.

Figura 2 A classe de calculadora verificando anteriores e posteriores

public class Calculadora {Sum(Int32 x, Int32 y) de Int32 pública {/ / retirada de valores de entrada se (x < 0 | | y < 0) lançam novos ArgumentException();

        / / Executar a operação Int32 resultado = x + y;

        / / Retirada de saída se (resultado < 0) lançam novos ArgumentException();

        retornar o resultado; }

    Divide(Int32 x, Int32 y) de Int32 pública {/ / retirada de valores de entrada se (x < 0 | | y < 0) lançam novos ArgumentException(); Se (y = = 0) lançam novos ArgumentException();

        / / Executar a operação Int32 resultado = x / y;

        / / Retirada de saída se (resultado < 0) lançam novos ArgumentException();

        retornar o resultado; } }

Ambos os métodos agora são articulados em três fases distintas: verificação dos valores de entrada, o desempenho da operação e a seleção da saída. Verificações de entrada e saída têm duas finalidades diferentes. Entrada verifica o sinalizador de bugs no código do chamador. Verificações de saída procuram bugs em seu próprio código. Você realmente precisa verificações de saída? Admito que as condições de seleção podem ser verificadas por meio de asserções em alguns testes de unidade. Nesse caso, você não precisa estritamente tais verificações enterradas no código em tempo de execução. No entanto, tendo verificações no código faz com que a classe autodescritivos e deixa claro o que pode e não é possível — assim como os termos de serviço contratado.

Se você comparar o código fonte do a Figura 2 com a classe simple, começamos, você verá que a origem cresceu em algumas linhas — e essa é uma classe simple com alguns requisitos para atender. Vamos ir um pouco mais.

Na a Figura 2, as três etapas que identificamos (seleção de entrada, operação e seleção de saída) são executados seqüencialmente. E se o desempenho da operação é complexo o suficiente para acomodar os pontos de saída adicionais? E se alguns desses sair pontos consulte situações de erro, onde outros resultados são esperados? Na verdade, as coisas podem ficar complicadas. É para ilustrar esse ponto, no entanto, ele suficiente que adicionamos uma saída de atalho para um dos métodos, conforme mostrado na a Figura 3.

Figura 3 uma saída de atalho duplica o código para posteriores

Sum(Int32 x, Int32 y) de Int32 pública {/ / retirada de valores de entrada se (x < 0 | | y < 0) lançam novos ArgumentException();
            
    / / Atalho sair se (x = = y) {/ / executar temp de var operação = x << 1. / / Otimização de 2 * x

        / / Retirada de saída se (temp < 0) lançam novos ArgumentException();

        retornar temp; }

    / / Executar o resultado da operação var = x + y;

    / / Retirada de saída se (resultado < 0) lançam novos ArgumentException();

    retornar o resultado; }

No código de exemplo (e - é apenas um exemplo), um atalho de tentativas de método de soma se os dois valores são iguais — multiplicando-se em vez de soma. No entanto, o código usado para verificar os valores de saída deve ser replicado para cada caminho de saída antecipada no código.

O resultado final é que ninguém pode razoavelmente pensam adotando uma abordagem de contratar primeiro para o desenvolvimento de software sem algumas ferramentas graves ou pelo menos uma estrutura de auxiliar específico. Verificando condições preliminares é relativamente fácil e barato fazer; lidar manualmente com condições após a execução torna propenso a base de todo código complicado e o erro. Para não mencionar alguns outros aspectos auxiliares de contratos que tornariam o código-fonte das classes de um real mexer para desenvolvedores, como, por exemplo, verificando as condições quando os parâmetros de entrada são coleções e garantindo que a classe está sempre em um estado válido conhecido, sempre que um método ou uma propriedade é chamada.

Insira o contratos de código

No.NET Framework 4, contratos de código é uma estrutura que fornece uma sintaxe muito mais conveniente para expressar um contrato de classe. Em particular, contratos de código de suporte a três tipos de contratos: pré-condições, posteriores e constantes. Pré-condições indicam as condições preliminares que devem ser verificadas para um método a ser executado com segurança. Posteriores expressam as condições que devem ser verificadas depois que o método foi executado corretamente ou por causa de uma exceção gerada. Finalmente, a constante descreve uma condição que é sempre verdadeira durante o ciclo de vida de qualquer instância de classe. Mais precisamente, a constante indica uma condição que deve conter após cada interação possível entre um cliente e a classe — ou seja, depois da execução de membros públicos, incluindo construtores. As condições expressas como constantes não são verificadas e subseqüentemente podem ser temporariamente violadas depois da chamada de um membro particular.

A API de contratos de código consiste em uma lista de métodos estáticos definidos na classe de contrato. Você usa o requer um método para expressar pré-condições e garante para expressar posteriores. Figura 4 mostra como reconfigurar a classe de calculadora usando o código de contratos.

Figura 4 A classe de calculadora escrita usando código contratos

usando a Calculadora de classe de System.Diagnostics.Contracts; public {Sum(Int32 x, Int32 y) de Int32 pública {Contract.Requires <ArgumentOutOfRangeException> (x > = 0 & & y > = 0); Contract.Ensures (Contract.Result <Int32> (de) > = 0);

        Se (x = = y) retornar 2 * x;

        retorno x + y; }

    Divide(Int32 x, Int32 y) de Int32 pública { Contract.Requires <ArgumentOutOfRangeException> (x > = 0 & & y > = 0); Contract.Requires < ArgumentOutOfRangeException >(y > 0); Contract.Ensures (Contract.Result <Int32> (de) > = 0);

        retorno x / y; } }

Uma rápida comparação das a Figura 3 e a Figura 4 mostra o poder de uma API eficiente para implementar o DbC. Código do método é para um formulário altamente legível em que você distinguir somente dois níveis: informações incluindo pré-condições e posteriores e o comportamento real do contrato. Não é necessário combinar condições com o comportamento, como em a Figura 3. Como resultado, melhorou muito legibilidade e manter esse código ficou muito mais fácil para a equipe. Por exemplo, você pode rapidamente e com segurança o adicionar uma pré-condição nova ou editar posteriores à vontade — você intervenha em um só lugar e suas alterações possam ser rastreadas com clareza.

As informações do contrato é expressa por meio de código simples de TRANSLATION FROM VPE FOR CSHARP ou o Visual Basic. Instruções do contrato não são como atributos declarativos clássicos, mas ainda mantêm um forte flavor declarativa. Usando código simples em vez de atributos aumenta a eficiência de programação de desenvolvedores, pois ele torna mais natural para expressar as condições que você tem em mente. Ao mesmo tempo, usar os contratos de código fornece mais orientação quando você refatora o código. Contratos de código, na verdade, indicam o comportamento que você deve esperar do método. Elas ajudam a manter a disciplina de codificação quando você escreve métodos e ajuda a manter seu código legível mesmo quando anteriores e posteriores obtenham diversos. Mesmo que você pode expressar contratos usando uma sintaxe de alto nível, como em a Figura 4, quando o código efetivamente obtém compilado, o fluxo resultante não pode ser muito diferente do código descrito na a Figura 3. Onde está o truque, então?

Uma ferramenta adicional integrado no processo de compilação do Visual Studio — regravador de contratos de código — executa esse truque de remodelagem de código, Noções básicas sobre a finalidade pretendida da expresso anteriores e posteriores e expandindo-os em blocos de código adequada colocado no qual eles pertencem logicamente. Como desenvolvedor, você apenas não se preocupe onde colocar uma pós-condição e onde duplicá-la se, em algum momento, você editar o código para adicionar outro ponto de saída.

Expressando condições

Você pode descobrir a sintaxe exata das anteriores e posteriores de documentação contratos de código; um PDF atualizado pode ser obtido no site em DevLabs bit.LY/f4LxHi. Brevemente daremos-lo. Use o seguinte método para indicar uma condição necessária e caso contrário, lança a exceção especificada:

Contract.Requires <TException> (Condição booleana)

O método tem algumas sobrecargas, que convém considerar. A garante do método expressa uma pós-condição:

Contract.Ensures (condição booleana)

Quando se trata de escrever uma pré-condição, a expressão normalmente conterá somente os parâmetros de entrada e talvez algum outro método ou propriedade na mesma classe. Se for esse o caso, é necessário que decorar esse método com o atributo puro observar que a executar o método não altera o estado do objeto. Observe que as ferramentas de contrato de código assumem getters de propriedade são puros.

Quando você escreve uma pós-condição, você precisa ter acesso a outras informações, como, por exemplo, o valor que está sendo retornado ou o valor inicial de uma variável local. Fazer isso por meio de métodos ad hoc, como, por exemplo, Contract.Result <T> Para obter o valor (do tipo T) que está sendo retornado do método e Contract.OldValue <T> Para obter o valor armazenado na variável local especificada no início da execução do método. Finalmente, você também terá a oportunidade para verificar se uma condição quando uma exceção é lançada durante a execução do método. Nesse caso, você pode usar o método Contract.EnsuresOnThrow <TException>.

Abbreviators

A sintaxe de contrato é certamente mais compacta do que o uso de código simples, mas ele pode ficar grande também. Quando isso acontece, legibilidade está em risco novamente. Um remédio natural é agrupar várias instruções de contrato uma sub-rotina, conforme mostrado na a Figura 5.

Figura 5 usando ContractAbbreviators

public class Calculadora {Sum(Int32 x, Int32 y) de Int32 pública {/ / retirada de valores de entrada ValidateOperands (x, y); ValidateResult();

        / / Executar a operação se (x = = y) retorno x << 1; retorno x + y; }

    Divide(Int32 x, Int32 y) de Int32 pública {/ / seleção de entrada de valores ValidateOperandsForDivision (x, y); ValidateResult();

        / / Executar a operação de retorno x / y; }

    [ContractAbbreviator] private void ValidateOperands (Int32 x, y Int32) {Contract.Requires <ArgumentOutOfRangeException> (x > = 0 & & y > = 0); } [ContractAbbreviator] private void ValidateOperandsForDivision (Int32 x, y Int32) {Contract.Requires <ArgumentOutOfRangeException> (x > = 0 & & y > = 0); Contract.Requires < ArgumentOutOfRangeException >(y > 0); }

    ValidateResult() de void privada [ContractAbbreviator] {Contract.Ensures (Contract.Result <Int32> (de) > = 0); } }

O atributo ContractAbbreviator instrui o regravador sobre como interpretar corretamente os métodos decorados. Sem o atributo para qualificá-lo como uma espécie de macro para expandir, na verdade, o método ValidateResult (e outros métodos de ValidateXxx em a Figura 5) conteria o código em vez disso inextricable. O que seria, por exemplo, Contract.Result <T> Consulte, como ele é usado em um método void? Atualmente, o atributo ContractAbbreviator deve ser explicitamente definido pelo desenvolvedor no projeto, pois ele não está incluído no assembly mscorlib. A classe é bastante simple:

o namespace System.Diagnostics.Contracts {[AttributeUsage (AttributeTargets.Method, AllowMultiple = false)] classe sealed interno [Conditional("CONTRACTS_FULL")] ContractAbbreviatorAttribute: System. Attribute {}}

Código limpo e aprimorado

Resumindo, a API de contratos de código — basicamente a classe do contrato — é uma parte nativa dos.NET Framework 4, como ele pertence ao assembly mscorlib. Visual Studio 2010 vem com uma página de configuração nas propriedades do projeto específicas para a configuração de contratos de código. Para cada projeto, você deve ir para lá e explicitamente Habilitar verificação de contratos de tempo de execução. Você também precisará fazer o download de ferramentas de tempo de execução do site DevLabs. Uma vez no site, pega o instalador adequado para a versão do Visual Studio, você tem. Ferramentas de tempo de execução incluem regravador de contratos de código e gerador de interface, além do verificador de estático.

Contratos de código ajudam a escrever código limpo, forçando a indicar o comportamento esperado e resultados para cada método. No mínimo, isso oferece orientações ao ir para refatorar e melhorar seu código. Há muito mais a ser discutido sobre contratos de código. Em particular, neste artigo, eu apenas rapidamente mencionado invariáveis e não mencionou a herança de contrato de recurso eficiente em todos os. Em futuros artigos, pretendo abordar tudo isso e muito mais. Fique sintonizado!

Dino Esposito é o autor de "Programming Microsoft ASP.NET MVC"(Microsoft Press, 2010) e co-autor do"Microsoft.NET: arquitetar aplicativospara a empresa "(Microsoft Press, 2008). Residente na Itália, Esposito é um palestrante sempre presente em eventos do setor no mundo inteiro. Siga-lhe Twitter no Twitter.com/despos.

Graças ao especialista técnico seguir pela revisão deste artigo: Brian grunkemeyer