Method Wrapper in Pharo/Smalltalk III

rotate-zoom-out-withdraw

Assertions

Require, ensure and invariant

Bertrand Meyer introduziu Design By Contract em seu livro Object-Oriented Software Construction, sobre Engenharia de Software, exemplificado na sua linguagem e ambiente de programação Eiffel. As assertivas que implementaremos serão require, ensure e invariant. A assertiva em require deverá ser satisfeita antes da execução do método ao qual ela se aplica e representa uma pré-condição. A assertiva em ensure deverá ser satisfeita após a execução do método, é uma pós-condição. A assertiva em invariant deve ser verdadeira antes e depois da execução do método. Vamos exemplificar com um exemplo clássico usando uma classe Account que representa uma conta bancária. O saldo da conta (balance) não pode ser negativo em nenhum momento o que pode ser representado por uma assertiva invariant 'self balance >= 0'. Outras assertivas serão exemplificadas nos exemplos de código abaixo:

myassertions-example

O código acima usa o estilo de API conhecido como DSL. A técnica é inspirada no tópico Using Functions  e Context Variables do livro Domain Specific Languages de Martin Fowler. A identação nada significa e é usada apenas para mostrar o relacionamento estrutural entre as várias partes.

É muito fácil inferir pela inspeção do código acima que os invariantes estabelecem que o saldo da conta (balance) não deve ser negativo antes e depois da execução de cada método. As assertivas devem usar a interface pública da classe para acessar o estado de cada objeto. Por isso o código do invariante é 'self balance >= 0' e não 'balance >= 0'. Embora as assertivas possam usar as variáveis de instância não o devem fazer. Isso acarretaria uma dependência espúria da representação interna do objeto.

Podemos ver também que o método #withDraw: tem como pré-condições que o argumento (amount) não deve ser maior que o saldo (balance). Além disso não deve ser nulo e não deve ser negativo. As pré-condições podem usar os argumentos diretamente mas devem usar a interface pública para obter o saldo (self balance). Acima foi dada uma razão mas outro motivo vem do fato que as pré-condições devem ser asseguradas pelo código cliente que não pode ter acesso ao estado interno do objeto usado (isso violaria o encapsulamento).

As assertivas não fazem parte do código em produção (de forma similar aos unit tests) e só são inseridas antes de sessões de debugging. Outro cuidado que se deve ter é de não colocar assertivas nos métodos que representam queries tal como o método #balance se o mesmo for usado na construção de assertivas. No caso específico de #balance nem faria sentido pois seríamos obrigados a usar o acesso direto às variáveis de instância e violaríamos a diretriz de usar a interface pública e não o estado interno do objeto.

O método #withDraw: também possui uma pós-condição que visa garantir a correção da implementação (ou a consistência entre a implementação e a especificação que é representada pela pós-condição, conforme prefere colocar Bertrand Meyer). Note a variável old_ usada para representar o estado do objeto antes da execução do método.

As assertivas para o método #deposit: são similares.

Após a instalação das assertivas o código dos métodos de Account ficam instrumentados como abaixo (mostramos apenas o método #withDraw:):
withdraw-with-assertions

Os invariantes envolvem as pré-condições e pós-condições que, por sua vez, envolvem a invocação da versão inalterada do método que teve o nome modificado pela adição do prefixo ‘__OLD__’.

Após a instalação das assertivas é executado um código que viola as especificações representadas nas assertivas.

precondition-violation

O saldo de valor 100 não permite que uma retirada de valor 150 seja feita sem violar a pré-condição 'amount <= self balance'. Em produção as assertivas não devem estar presentes. O bug então seria observado em algum ponto da execução através de unit tests ou via interface do usuário. Antes da sessão de debugging as assertivas são inseridas e se repete o cenário que evidenciou o bug (execução do sistema pelo usuário ou de unit tests). Verifica-se então que quem violou o contrato foi o código cliente e é nele que deve ser efetivada a correção que impeça a violação da pré-condição.

precondition-fixing

Agora as assertivas podem ser removidas que o código cliente garante que não será feita uma retirada excessiva em relação ao saldo. Ao contrário do que prega a programação defensiva o uso de assertivas evita que o código dos métodos fique sobrecarregado com testes prejudicando a clareza. Uma camada de filtros deve ser interposta entre as entradas do usuário, por exemplo, e as invocações dos métodos de mais baixo nível. Esta camada impede que os contratos representados pelas pré-condições sejam violados.

Um outro benefício do uso de assertivas durante a sessão de debugging é que o bug pode ser detectado numa bifurcação da execução que não estaria na pilha de execução durante uma sessão de debugging sem assertions. Isto é demonstrado no trecho abaixo do artigo Design and Implementation of a Backward-In-Time Debugger.

lost-method-stack-trace

Time bounded assertions

Além das assertivas também é possível estabelecer requisitos de performance. Para isso vamos colocar um delay no método Account>>#balance:

balance-delayed

O código para instrumentar o método Account>>#balance é o seguinte:

ensureTimeBoundedTo

O método Account>>#balance após a instrumentação fica como abaixo:

balance-timebounded-instrumented

A execução do código abaixo causa o acionamento da assertiva.

balance-call-with-time-violation

A tela do debugger resultante segue abaixo:

balance-time-bound-failure-debugger

Conforme já foi dito antes não se deve colocar assertivas nos métodos que representam queries e que podem ser usadas em assertivas, o que causaria um loop infinito. No entanto assertivas de performance não se enquadram neste caso pois não causariam loops infinitos.

Invariantes de performance também podem ser aplicados conforme os códigos abaixo exemplificam:

invariant-time-bound

O código acima aplica o invariante de performance aos métodos #balance e #deposit:. No caso do método #balance o invariante é combinado com a assertiva ensure time bounded. Veja abaixo:

deposit-time-bounded

balance-invariant-time-bounded

O script mais completo a seguir combina várias assertivas nos vários métodos de Account:

myassertions-withdraw-all

Os resultados nos vários métodos são mostrados abaixo:

withdraw-instrumented deposit-instrumented balance-instrumented

O método #setBalance: não é instrumentado pois é usado para inicialização em #initialBalance:.
setbalance-not-instrumented
initialBalance-classside

Copy and postcopy

No script anterior vamos incluir a class Bank que apenas representa uma coleção de contas (Account):

script-with-bank
using-account-and-bank

A variável old_, que é uma cópia do objeto Bank antes da invocação do método original não representa uma cópia profunda (deep copy) a menos que implementemos #postcopy em Bank:

bank-postcopy

#postcopy é um override do mesmo método na classe Object e que é usado para complementar a cópia rasa (shalow copy) feita por #copy. Caso não fizéssemos uso de #postcopy para também fazer uma cópia da coleção de contas os dois objetos Bank compartilhariam as contas, o que não queremos. Abaixo mostramos as implementações de Object>>#copy e  Object>>#postCopy:

object-copy object-postcopy

Os métodos gerados com assertivas para Bank seguem abaixo:

bank-addaccount bank-removeaccount

No próximo post usaremos pragmas (Ver links no primeiro post) para especificar as assertivas.

Anúncios

2 Respostas para “Method Wrapper in Pharo/Smalltalk III

  1. Pingback: Method Wrapper in Pharo/Smalltalk II | Crab Log

  2. Pingback: SmallFBP: a Smalltalk framework for Flow-Based Programming – Part 5 | Crab Log

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s