Arquivo para a categoria ‘Técnicas de Desenvolvimento de Software’

Não escreva código novo sem antes ter um teste falhando

Quinta-feira, 18 de fevereiro de 2010 por Fernando Hamasaki de Amorim

O título desse post é uma frase de Kent Beck, autor do livro Test Driven Development: By Example. A idéia é que você sempre escreva testes antes de implementar qualquer código. Após o teste escrito falhar, você implementa o suficiente para fazer o teste passar. Com os testes passando, você está livre para refatorar (tanto implementação, quanto teste). A partir daí você cria um novo teste e segue o mesmo fluxo. Esse ciclo se repete até você ter toda a funcionalidade deseja implementada, ou seja, ter testes para todas as possibilidades da sua implementação.

Este é o “bê-a-bá” de TDD, mas na prática isso dificilmente acontece. Não porque não queremos fazer testes (se você não quiser escrever testes, o problema é todo seu), mas porque somos exímios programadores, desenvolvemos orientados a testes por anos, e não precisamos mais seguir os baby steps (passos de bebê), afinal somos programadores maduros.

Sendo assim, pulamos etapas: codificamos primeiro para depois escrever os testes, refatoramos mesmo com testes ainda não passando, escrevemos mais testes mesmo tendo testes anteriores falhando, e por aí vai.

Cuidado! Por mais que você seja um programador “fodão”, ainda sim você pode deixar de testar alguma coisa. Uma lógica de negócio, uma alternativa de fluxo ou uma condição de erro podem passar desapercebidas ao se pular as etapas básicas de TDD. Esse teste faltando, por mais simples que seja, pode causar um erro em ambiente de produção e causar transtornos para o cliente e/ou usuário final da sua aplicação.

Vamos utilizar como exemplo uma simulação de pareamento, onde uma dupla de desenvolvedores irá criar um método chamado positive_balance? para dizer se uma conta bancária, representada pela classe BankAccount, possui saldo positivo.

A linguagem utilizada será Ruby e o framework para testes será RSpec.

Os programadores são Félix (piloto do pareamento) e Péricles. Os dois concordam em iniciar criando a classe BankAccount com a declaração do método positive_balance? sem nenhuma implementação:

class BankAccount
  def positive_balance?

  end
end

- Legal, agora vamos escrever nosso teste. – diz Péricles.
- Para uma conta bancária possuir fundos é nessário que seu saldo seja maior que zero.

describe BankAccount do
  it "should have positive balance" do
    account = BankAccount.new
    account.value = 100.00
    account.positive_balance?.should be_true
  end
end

Eles rodam o teste:

F
1)
NoMethodError in 'BankAccount should have positive balance'
undefined method `value=' for #
./spec/bank_account_spec.rb:6:
Finished in 0.010015 seconds
1 example, 1 failure

E o resultado com erro diz a eles que não existe um atributo value na classe BankAccount. Félix e Péricles o criam:

class BankAccount
  attr_accessor :value

  def positive_balance?

  end
end

E executam o teste novamente:

F
1)
'BankAccount should have positive balance' FAILED
expected nil to be true
./spec/bank_account_spec.rb:7:
Finished in 0.010605 seconds
1 example, 1 failure

O teste falha. Então chegou a hora de escrever código novo, a implementação da funcionalidade que eles querem. Félix implementa o suficiente para o teste passar.

class BankAccount
  attr_accessor :value

  def positive_balance?
    true
  end
end

Péricles discorda totalmente.
- Cê tá louco, mano?! Vai retornar true para tudo?! O cara vai ter sempre saldo na conta?

Félix argumenta.
- A gente não precisa escrever código suficiente para o teste passar? Isso é suficiente.

E roda o teste:

.
Finished in 0.009987 seconds
1 example, 0 failures

- Viu? Passou. – finaliza Félix.
- Mas isso é muito baby step. – reclama Péricles – Vamos implementar o código real, ou seja:

class BankAccount
  attr_accessor :value

  def positive_balance?
    self.value > 0
  end
end

- Mas por que vamos implementar isso agora? Afinal nossos testes estão passando. – Félix rebate.
- Porque está na cara que esse código retornando true sempre não funciona.

Félix continua forçando a discussão.
- Como não funciona? Funciona sim, os testes estão passando.
- Funciona, mas a implementação está errada. – diz Péricles.
- Errada? Mas atende os requisitos até o momento. Afinal, os testes são para assegurar que a lógica do negócio está sendo cumprida.

Péricles fica pensativo.
- Mas o único teste que fizemos não está cobrindo todos os casos da lógica.
- Concordo com você, Péricles. E o que devemos fazer agora então?
- Devemos escrever um teste em que a conta bancária não irá ter fundos.
- Exatamente! – confirma Félix.

E eles continuam nesse linha de raciocínio até o final do pareamento.

Não estou aqui dizendo que você tem que sempre seguir à risca o Red Green Refactor do TDD, muito menos usar baby steps toda vez que você codificar (afinal, a vida não é um dojo), mas que você tenha atenção e controle do que está fazendo, tento o domínio da funcionalidade que está implementando.

Uma das maneiras de se conseguir isso é com pareamento. Seu par irá lhe ajudar a não deixar escapar nenhum teste. Outra maneira é com inspeção de código. De repente, outro desenvolvedor que não participou da implementação pode enxergar algo que você (e/ou seu par) não viu.

De qualquer forma, seja humilde. Use as etapas de TDD para funcionalidades ou lógica mais complexas. E também fique livre para burlar as regras para implementar coisas simples e funcionalidades básicas, ou quando estiver bastante à vontade e seguro do que está fazendo. Mas nunca, eu disse nunca, deixe de escrever os testes.


Post original:
http://prodis.pro.br/2010/02/16/nao-escreva-codigo-novo-sem-antes-ter-um-teste-falhando

.

Desprenda-se de convenções de nomenclatura em nome de testes

Segunda-feira, 23 de novembro de 2009 por Fernando Hamasaki de Amorim

Eu compartilho da opinião de Jimmy Bogard, que diz que os nomes dos testes precisam descrever o que e o porque, a partir da perspectiva do usuário, onde o desenvolver possa ler o nome do teste e claramente entender o comportamento que é esperado.

Um teste unitário nada mais é que um método em uma classe, e tanto em C# como Java, existem convenções de nomenclatura de métodos.

Em C#, nome de métodos são declarados utilizando Pascal Case:

[TestMethod]
public void ProductShouldHaveAtLeastOneCategory()
{
  //Test implementation.
}

Já em Java, convencionou-se escrever métodos utilizando Camel Case:

@Test
public void productShouldHaveAtLeastOneCategory() {
  //Test implementation.
}

Muitas vezes, o nome desses testes (métodos) ficam um tanto longos, como os exemplos acima. Dessa forma, a legibilidade não é muito boa.

Seguindo um dos conselhos de Neal Ford, em sua apresentação 10 Ways to Improve Your Code, você pode deixar de lado as convenções de nomenclatura da linguagem em favor da legilidade dos nomes dos seus testes. Escreva o nome do teste como se fosse uma frase, nada de letras maiúsculas para cada palavra, e use “_” (underscore) para separar as palavras.

Veja como fica o exemplo acima em C#:

[TestMethod]
public void Product_should_have_at_least_one_category()
{
  //Test implementation.
}

E agora em Java:

@Test
public void product_should_have_at_least_one_category() {
  //Test implementation.
}

Não há nenhum mal em se desprender das convenções de nomenclatura de C# e Java em prol da legibilidade dos nomes dos testes. Afinal, testes são uma documentação executável e nós queremos uma documentação clara para nosso código.


Post original:
http://prodis.pro.br/2009/11/21/desprenda-se-de-convencoes-de-nomenclatura-em-nome-de-testes

.

Coding Dojo

Sexta-feira, 9 de outubro de 2009 por danicuki

O Coding Dojo é uma prática muito interessante e divertida. O principal objetivo é praticar, aprender e ensinar técnicas de desenvolvimento de software.

Há algum tempo realizamos sessões internas de Dojo na Locaweb. Decidimos agora criar uma sessão aberta a qualquer desenvolvedor. Segue abaixo um vídeo explicando os principais conceitos do Coding Dojo.

Mantemos um projeto no github. Lá você poderá baixar os códigos e verificar o calendário das sessões. Para participar, basta enviar o nome completo para daniel.cukier arroba locaweb.com.br ou adolfo.sousa arroba locaweb.com.br dizendo a data que você gostaria de participar.

Cheeseburgers, Ruby e magia negra

Segunda-feira, 21 de setembro de 2009 por Fernando Hamasaki de Amorim

Vamos usar um pouco de magia negra do Ruby para encontrar uma alternativa à implementação clássica do Design Pattern Decorator apresentado pela GoF.

Imagem original de MarketFare Foods, Inc.

.
Este post é a continuação de dois anteriores:

Se você ainda não os leu, recomendo que o faça para entender o contexto do exemplo onde estamos aplicando o Design Pattern Decorator. O ponto onde paramos no último post foi o meu descontentamento em decorar um objeto Cheeseburger de uma forma não muito intuitiva.

(mais…)

Cheeseburgers, Decorators e Ruby

Segunda-feira, 14 de setembro de 2009 por Fernando Hamasaki de Amorim

No post Cheeseburgers, Decorators e Mocks eu mostrei um exemplo prático de utilização do Design Pattern Decorator, que começa com um design usando herança, desaclopa usando composição e finalmente aplica Decorator. Tudo isso foi feito em .NET com C#. Agora vamos fazer o mesmo exemplo de Decorator Pattern utilizando Ruby. Para entender melhor o contexto do exemplo utilizado, sugiro que você leia antes o post anterior.

Imagem original de MarketFare Foods, Inc.

Além de Ruby, utilizarei o RSpec como ferramenta de testes unitários. No final do post há os links para baixar o código completo.

.
Cheeseburgers com Ruby
A principal diferença de implementação do Design Pattern Decorator em Ruby é a não utilização de classes abstratas (ou interfaces). No Ruby não existe classes abstratas ou interfaces, com isso eliminamos um elemento na interação entre os objetos.

Veja como fica o diagrama de classes com a estrutura do padrão:

Decorator em Ruby

Decorator em Ruby

Onde:

  • Component define a interface para objetos que podem ter responsabilidades acrescentadas aos mesmos dinamicante;
  • ConcreteComponent define um objeto para o qual responsabilidades adicionais podem ser atribuídas;
  • Decorator mantém uma referência para um objeto Component e acrescenta responsabilidades ao componente.

Vamos criar as classes do nosso exemplo, mas diferente do post anterior, vou mostrar primeiros os testes e depois as implementações.

Primeiro vamos fazer o teste da classe Cheeseburger, que servirá como ConcreteComponent.

describe Cheeseburger, " when created" do
  it "should have default calories and description" do
    cheeseburger = Cheeseburger.new
    cheeseburger.calories.should == 300
    cheeseburger.description.should == "Bread, Hamburger, Cheese"
  end
end

O teste fala por si só. Agora vamos à implementação.

class Cheeseburger
  attr_reader :description, :calories

  def initialize
    @description = "Bread, Hamburger, Cheese"
    @calories = 300
  end
end

O próximo passo são os testes da classe Sandwich, o nosso Component.

describe Sandwich do
  before(:all) do
    @real_sandwich = mock Cheeseburger
    @sandwich = Sandwich.new @real_sandwich
  end

  it "should call description in real sandwich" do
    @real_sandwich.should_receive(:description).once.and_return("Sandwich description")
    @sandwich.description.should == "Sandwich description"
  end

  it "should call calories in real sandwich" do
    @real_sandwich.should_receive(:calories).once.and_return(150)
    @sandwich.calories.should == 150
  end
end

O bloco before (linhas 2 a 5) é executado somente uma vez antes da execução dos testes. Nele criamos um mock da classe Cheeseburger que é passado como parâmetro na criação da instância da classe Sandwich. Os testes asseguram que os métodos description (linha 8 ) e calories (linha 13) são chamados nos mocks e seus valores são retornados corretamente (linhas 9 e 14).

Então vamos à implementação da classe Sandwich.

class Sandwich
  def initialize(real_sandwich)
    @real_sandwich = real_sandwich
  end

  def description
    @real_sandwich.description
  end

  def calories
    @real_sandwich.calories
  end
end

Agora só faltam nossos Decorators. Seguem os testes da classe Corn:

describe Corn do
  before(:all) do
    @sandwich = mock Sandwich
    @corn = Corn.new @sandwich
  end

  it "should add corn description to sandwich description" do
    @sandwich.should_receive(:description).once.and_return("Sandwich description")
    @corn.description.should == "Sandwich description, Corn"
  end

  it "should add corn calories to sandwich calories" do
    @sandwich.should_receive(:calories).once.and_return(100)
    @corn.calories.should == 170
  end
end

Da mesma forma que fizemos nos testes da classe Sandwich, criamos uma instância de Corn passando um mock da classe Sandwich no bloco before (linhas 2 a 5). Os testes asseguram que a descrição e as calorias do milho será adicionadas ao sanduíche.

Vamos implementar nossa classe Corn.

class Corn < Sandwich
  def initialize(real_sandwich)
    super real_sandwich
  end

  def description
    "#{@real_sandwich.description}, Corn"
  end

  def calories
    @real_sandwich.calories + 70
  end
end

A classe Corn herda a classe Sandwich e o método super chamado na linha 3 vai chamar o método initialize da classe base. O método description (linhas 6 a 8 ) é sobrescrito para retornar a descrição do sanduíche original juntamente com a descrição de milho. O método calories também é sobrescrito (linhas 10 a 12) e soma o retorno do método calories do sanduíche original com as calorias do milho.

O mesmo é feito para as classes OnionRings e PepperSauce, tanto na implementação como nos testes.

class OnionRings < Sandwich
  def initialize(real_sandwich)
    super real_sandwich
  end

  def description
    "#{@real_sandwich.description}, Onion Rings"
  end

  def calories
    @real_sandwich.calories + 140
  end
end
class PepperSauce < Sandwich
  def initialize(real_sandwich)
    super real_sandwich
  end

  def description
    "#{@real_sandwich.description}, Pepper Sauce"
  end

  def calories
    @real_sandwich.calories + 20
  end
end

.
E o nosso diagrama de classes fica assim:

Cheeseburgers com Ruby

Cheeseburgers com Ruby

Note que a classe Cheeseburger não herda de Sandwich. Como no Ruby as coisas são flexíveis, na hora de instanciar um Decorator (Corn, OnionRings ou PepperSauce) você não precisa necessariamente passar como parâmetro um objeto que herde da classe Sandwich. Basta que o objeto passado possua os métodos description e calories. Aqui é que entra em ação o duck typing do Ruby:

If it walks like a duck and quacks like a duck, I would call it a duck.

No nosso caso, se um o objeto se parece com um sanduíche, ou seja, tem uma descrição e quantidade de calorias, então nós o chamamos de saduíche. O duck typing permite a utilização de polimorfismo sem herança.

Agora já podemos montar os cheeseburgers de Itararé e Ilhéus, bem como outras variações. Veja alguns testes:

describe Cheeseburger do
  before(:each) do
    @cheeseburger = Cheeseburger.new
  end

  it "should be an Itarare Cheeseburger" do
    @cheeseburger = Corn.new @cheeseburger
    @cheeseburger.description.should == "Bread, Hamburger, Cheese, Corn"
    @cheeseburger.calories.should == 370
  end

  it "should be an Itarare Cheeseburger with onion rings" do
    @cheeseburger = Corn.new @cheeseburger
    @cheeseburger = OnionRings.new @cheeseburger
    @cheeseburger.description.should == "Bread, Hamburger, Cheese, Corn, Onion Rings"
    @cheeseburger.calories.should == 510
  end

  # Outros testes
end

A decoração de objetos segue quase a mesma linha do fizemos em C# no post anterior. Vamos detalhar o segundo teste para entendermos as diferenças. A variável @cheeseburger é decorada duas vezes, uma vez com a classe Corn (linha 13) e outra com a classe OnionRings (linha 15).

Quando o método calories da variável @cheeseburger é chamado na linha 16, estamos chamando o método calories do último decorador, ou seja, da classe OnionRings. Depois contamos com a delegação para adicionar as calorias dos ingredientes. Veja o acontece:

  1. OnionRings chama o método calories do objeto Corn que foi passado como parâmetro quando a classe OnionRings foi instanciada;
  2. Corn chama o método calories do objeto Cheeseburger que foi passado como parâmetro quando a classe Corn foi instanciada;
  3. Cheeseburger retorna 300 calorias;
  4. Corn adiciona suas 70 calorias ao retorno do objeto Cheeseburger e retorna 370 calorias;
  5. OnionRings adiciona suas 140 calorias ao retorno do objeto Corn e retorna 510 calorias.

Em relação à implementação em C#, o Ruby eliminou 4 passos.

.
Legal, mas nós podemos melhorar?
Sim, nós podemos.

A classe Sandwich não faz nada mais além de delegar as chamadas dos métodos description e calories. Como nós temos somente dois métodos, isso até que não é um problema. Mas e se tivéssemos, por exemplo, uns 10 métodos? Vai ser um saco ficar escrevendo esses métodos que delegam para outros métodos.

Para resolver isso, o Ruby nos fornece um módulo chamado Forwardable, que gera automaticamente todos esses métodos de delegação para nós. Vamos reescrever a classe Sandwich utilizando o módulo Fowardable.

require 'forwardable'

class Sandwich
  extend Forwardable

  def initialize(real_sandwich)
    @real_sandwich = real_sandwich
  end

  def_delegators :@real_sandwich, :description, :calories
end

O módulo Fowardable fornece o método def_delegators, que recebe dois ou mais parâmetros. O primeiro parâmetro é o nome da variável de instância que será usada para delegar as chamadas dos métodos. Os outros parâmetros são os nomes dos métodos que queremos delegar. Na linha 10, o método def_delegators adiciona os métodos description e calories para a classe Sandwich, e cada um deles irá delegar sua chamada para o respectivo método para o objeto armazenado na variável @real_sandwich.

Um detalhe importante é que estamos extendendo o módulo Fowardable (linha 4) ao invés de incluí-lo (utilizando o método include). Isso se deve ao fato que queremos adicionar métodos de classe e não métodos de instância.

Depois dessa alteração na classe Sandwich, rodamos os testes e tudo funciona. Mas algo que ainda me incomoda um pouco é ter que escrever uma linha de código para cada novo ingrediente que acrescentamos no cheeseburger. Vamos pegar como exemplo esse teste:

describe Cheeseburger do
  it "should be an Cheeseburger with pepper sauce, onion rings and corn" do
    cheeseburger = Cheeseburger.new
    cheeseburger = PepperSauce.new
    cheeseburger = OnionRings.new
    cheeseburger = Corn.new
    cheeseburger.description.should == "Bread, Hamburger, Cheese, Pepper Sauce, Onion Rings, Corn"
    cheeseburger.calories.should == 530
  end
end

Na linha 2 criamos um cheeseburger e nas linhas seguintes adicionamos seus ingredientes. Se quisermos fazer a mesma coisa em somente uma linha, vai ficar assim:

describe Cheeseburger do
  it "should be an Cheeseburger with pepper sauce, onion rings and corn" do
    cheeseburger = Corn.new(OnionRings.new(PepperSauce.new(Cheeseburger.new)))
    cheeseburger.description.should == "Bread, Hamburger, Cheese, Pepper Sauce, Onion Rings, Corn"
    cheeseburger.calories.should == 530
  end
end

Hum… A descrição do teste diz que deveria ser um cheeseburger com molho de pimenta, cebola e milho. Mas lendo o código, dá a impressão que estamos criando um milho com cebola, molho de pimenta e cheeseburger. E se a gente tentar deixar mais legível invertendo a ordem da criação dos objetos? Vamos alterar a linha 3 assim:

describe Cheeseburger do
  it "should be an Cheeseburger with pepper sauce, onion rings and corn" do
    cheeseburger = Cheeseburger.new(PepperSauce.new(OnionRings.new(Corn.new)))
    cheeseburger.description.should == "Bread, Hamburger, Cheese, Pepper Sauce, Onion Rings, Corn"
    cheeseburger.calories.should == 530
  end
end

E adivinha o que acontece! O teste não passa! O método initialize da classe Cheeseburger não tem nenhum parâmetro, então é lançado um ArgumentError ao rodar o teste. Além disso, o método initialize da classe Corn precisa receber um parâmetro, o qual não foi passado. Mais um ArgumentError.

Você está conformado com isso? Eu não. Sendo assim, vamos ter que usar um pouco de magia negra para melhorar essa situação. Mas isso fica para o próximo post.

Dúvidas, questionamentos, discórdias, sugestões? Deixe seu comentário.

O código completo com todas as classes e seus testes está disponível aqui no meu Github.

.
Referências:


Post original:
http://prodis.pro.br/2009/09/13/cheeseburgers-decorators-e-ruby

.

Utilizando aspectos em C# com PostSharp

Sábado, 5 de setembro de 2009 por Mozair aka MACSkeptic

Um pouco sobre programação orientada a aspectos (AOP)

Ao longo dos últimos anos, a medida em que o hardware foi se tornando um recurso cada vez mais barato e poderoso, o foco das linguagens de programação tem se voltado para clareza ao invés de performance.

Onde antes era necessário, por exemplo, ponderar com muito cuidado qual o melhor algoritmo para iterar uma lista, hoje esta é uma questão secundária. O que isto propicia a nós, desenvolvedores, é a possibilidade de focar nosso tempo e esforço em questões de clareza e manutenibilidade em nossos códigos.

Com estas novas preocupações em mente, surgiu o paradigma orientado a objetos, que visa traduzir da forma mais natural possível as entidades reais de nosso negócio, para classes e instâncias em nosso código.

Porém, um grande problema que aparece quando se desenvolve um software orientado a objetos é o espalhamento de responsabilidades em comum ao longo de várias classes. Alguns exemplos clássicos deste problema são:

  • Log;
  • Autenticação;
  • Abrir/Fechar conexões;
  • Tratamentos de exceção;

Estas preocupações normalmente encontram-se espalhadas ao longo do código de diversas classes, mesmo que o comportamento desejado seja exatamente igual em todas elas.

Para abordar este problema de espalhamento, surgiu o paradigma de programação orientada a aspectos. Este visa complementar a programação orientada a objetos, eliminando em grande parte o espalhamento de código para preocupações comuns em diversos pontos do código. A seguir, tentarei mostrar, utilizando um exemplo prático, as vantagens que o uso de aspectos podem trazer para a clareza do código desenvolvido.

Aprendendo por meio de exemplos

O problema imediato que o espalhamento de código similar gera é: para cada modificação que seja necessária neste código espalhado, será necessário modificá-lo manualmente em cada ponto – como nós, desenvolvedores, sabemos, tarefas manuais como esta estão fadadas a gerar erros ou inconsistências.

A seguir, implementaremos uma classe de conta corrente, que possibilitará depósitos e saques, começando, naturalmente, pelos testes:

[TestMethod]
public void WithdrawShouldDecreaseBalance()
{
    Account account1 = AccountBuilder.New
        .BelongingTo("MACSkeptic")
        .WithInitialBalance(200.95M)
        .IdentifiedBy(773)
        .Instance;

    Assert.AreEqual(100.95M, account1.Withdraw(100).CurrentBalance);
    Assert.AreEqual(0.0M, account1.Withdraw(100.95M).CurrentBalance);
    Assert.AreEqual(-10.0M, account1.Withdraw(10.00M).CurrentBalance);
}

[TestMethod]
public void DepositShouldIncreaseBalance()
{
    Account account1 = AccountBuilder.New
        .BelongingTo("MACSkeptic")
        .WithInitialBalance(-50.42M)
        .IdentifiedBy(666)
        .Instance;

    Assert.AreEqual(-0.42M, account1.Deposit(50).CurrentBalance);
    Assert.AreEqual(79.58M, account1.Deposit(80).CurrentBalance);
}

Para atender ao comportamento descrito acima, implementaremos a classe “Account”, denotada a seguir:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Codevil.MACSkeptic.PostSharpExample.Entities
{
    public class Account
    {
        public long Number { get; set; }
        public string Owner { get; set; }
        public decimal CurrentBalance { get { return this.currentBalance; } }

        private decimal currentBalance;

        public Account(long number, string owner)
            : this(number, owner, 0)
        {
        }

        public Account(long number, string owner, decimal initialBalance)
        {
            this.Number = number;
            this.Owner = owner;
            this.currentBalance = initialBalance;
        }

        public Account Deposit(decimal howMuch)
        {
            Console.WriteLine(string.Format(
                "New deposit transaction on account {0}.
                                       Balance before operation is ${1}.",
                this.Number,
                this.CurrentBalance));

            Console.WriteLine(string.Format(
                "Depositing ${0} on account {1}.", howMuch, this.Number));

            this.currentBalance += howMuch;

            Console.WriteLine(string.Format(
                "Finished deposit transaction on account {0}.
                                       Balance after the operation is ${1}.",
                this.Number,
                this.CurrentBalance));

            return this;
        }

        public Account Withdraw(decimal howMuch)
        {
            Console.WriteLine(string.Format(
                "New withdraw transaction on account {0}.
                                      Balance before operation is ${1}.",
                this.Number,
                this.CurrentBalance));

            Console.WriteLine(string.Format(
                "Withdrawing ${0} on account {1}.", howMuch, this.Number));

            this.currentBalance -= howMuch;

            Console.WriteLine(string.Format(
                "Finished withdraw transaction on account {0}.
                                   Balance after the operation is ${1}.",
                this.Number,
                this.CurrentBalance));

            return this;
        }
    }
}

Como pode ser visto, nossa conta bancária tem basicamente dois métodos, responsáveis por depositar (Deposit) e sacar (Withdraw) dinheiro. Por enquanto ainda não há tratamento de saldo nas saques, caso se tire mais dinheiro do que o disponível, o resultado será uma conta com saldo negativo.

Logo já conseguimos notar que o log (sendo realizado no próprio console, para manter o foco no que realmente interessa neste artigo) de operaçòes de depósito e saque é bem parecido, gerando um código claramente repetitivo.

Uma possível refatoração nos possibilita eliminar um pouco de repetição, fazendo uso da refatoração “extrair método”, temos:

public Account Deposit(decimal howMuch)
{
    this.LogTransactionStarting("deposit");
    this.LogTransactionDetails("Depositing", howMuch);
    this.currentBalance += howMuch;
    this.LogTransactionFinished("deposit");
    return this;
}
public Account Withdraw(decimal howMuch)
{
    this.LogTransactionStarting("withdraw");
    this.LogTransactionDetails("Withdrawing", howMuch);
    this.currentBalance -= howMuch;
    this.LogTransactionFinished("withdraw");
    return this;
}
private void LogTransactionStarting(string action)
{
    Console.WriteLine(string.Format(
        "New {2} transaction on account {0}.
                                 Balance before operation is ${1}.",
        this.Number,
        this.CurrentBalance,
        action));
}
private void LogTransactionDetails(string action, decimal howMuch)
{
    Console.WriteLine(string.Format(
        "{2} ${0} on account {1}.", howMuch, this.Number, action));
}
private void LogTransactionFinished(string action)
{
    Console.WriteLine(string.Format(
        "Finished {2} transaction on account {0}.
                                 Balance after the operation is ${1}.",
        this.Number,
        this.CurrentBalance,
        action));
}

O código já ficou mais limpo e com menos repetições. Porém, os métodos de “Deposit” e “Withdraw”, e mais especificamente a classe “Account” não deveríam estar preocupados em logar as ações. Usando apenas conceitos de orientação a objetos, o máximo que conseguiríamos fazer para melhorar o código acima seria mover os métodos de log para outra classe. Esta ação melhoraria nosso código, mas ainda assim teríamos as chamadas aos métodos de log na classe “Account”, tirando o foco no negócio, tanto durante a codificação, quanto quando se venha a ler o código no futuro.

Há uma conhecida frase sobre classes em orientação a objetos que diz que se você precisa utilizar “e” ao definir o que sua classe faz, provavelmente ela está aglomerando muitas responsabilidades. No caso, nossa classe de conta faz transações e gera logs de transações.

Neste ponto, podemos notar a natureza do log que precisamos. Temos:

  • Um evento de log no início dos métodos de depósito e saque;
  • Um evento de log com detalhes específicos sobre o depósito e saque sendo realizado no momento;
  • Um evento de log ao térmido dos métodos de depósito e saque.

Uma ferramenta bastante competente em auxiliar no uso de aspectos em .NET é o PostSharp, cujo slogan, “make sense, not code” (faça sentido, não faça código), diz bastante sobre a idéia central  sobre a qual se sedimenta o paradigma de aspectos. Para a sequência deste artigo será necessário o download do PostSharp (utilizaremos a versão 1.5), que pode ser realizado clicando aqui (é necessário registrar-se no site da ferramenta. O registro é gratuito e pode ser feito aqui).

Após fazer a instalação do PostSharp, devemos adicionar as referências às bibliotecas PostSharp.Laos e PostSharp.Public em nosso projeto, conforme imagem a seguir:

image

A documentação detalhada do PostSharp (em inglês) pode ser conferida clicando-se aqui.

Para nossa classe “Account” iremos utilizar dois aspectos:

  • OnMethodBoundaryAspect (ao redor do método): define pontos de entrada ao redor das chamadas de um método. Isto é, imediatamente antes e imediatamente depois de sua chamada. Este aspecto é ideal para o log antes e depois de uma transação em nossa classe “Account”;
  • OnMethodInvocationAspect (na chamada do método): define um ponto de entrada assim que um método é chamado, incluindo detalhes como os parâmetros informados nessa chamada específica. Este aspecto é ideal para o log de detalhes de transação em nossa classe “Account”.

Primeiramente, vamos ver como fica a implementação do aspecto “ao redor” na prática:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PostSharp.Laos;
using Codevil.MACSkeptic.PostSharpExample.Entities;

namespace Codevil.MACSkeptic.PostSharpExample.Aspects
{
    [Serializable]
    public class LogAccountStatusBeforeAndAfterTransaction :
                                            OnMethodBoundaryAspect
    {
        public override void OnEntry(MethodExecutionEventArgs eventArgs)
        {
            Account account = (Account)eventArgs.Instance;
            Console.WriteLine(string.Format(
                "New {2} transaction on account {0}.
                                    Balance before operation is ${1}.",
                account.Number,
                account.CurrentBalance,
                eventArgs.Method.Name));
        }
        public override void OnExit(MethodExecutionEventArgs eventArgs)
        {
            Account account = (Account)eventArgs.Instance;
            Console.WriteLine(string.Format(
               "Finished {2} transaction on account {0}.
                                   Balance after the operation is ${1}.",
               account.Number,
               account.CurrentBalance,
               eventArgs.Method.Name));
        }
    }
}

O aspecto “OnMethodBoundary” possibilita a implementação dos métodos “OnEntry” (antes do método) e “OnExit” (após o método). No código acima pode-se reparar que nosso aspecto precisa de um parâmetro especificando qual a ação está sendo tomada e o código para log foi copiado diretamente do que tínhamos antes na classe “Account”. Um detalhe importante é que todas as classes que implementam algum aspecto do PostSharp devem ser serializáveis.

Vamos ver agora como fica o código de nossa classes “Account” fazendo uso deste aspecto:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Codevil.MACSkeptic.PostSharpExample.Aspects;

namespace Codevil.MACSkeptic.PostSharpExample.Entities
{
    public class Account
    {
        public long Number { get; set; }
        public string Owner { get; set; }
        public decimal CurrentBalance { get { return this.currentBalance; } }
        private decimal currentBalance;

        public Account(long number, string owner)
            : this(number, owner, 0)
        {
        }

        public Account(long number, string owner, decimal initialBalance)
        {
            this.Number = number;
            this.Owner = owner;
            this.currentBalance = initialBalance;
        }
        [LogAccountStatusBeforeAndAfterTransaction]
        public Account Deposit(decimal howMuch)
        {
            this.LogTransactionDetails("Depositing", howMuch);
            this.currentBalance += howMuch;
            return this;
        }
        [LogAccountStatusBeforeAndAfterTransaction]
        public Account Withdraw(decimal howMuch)
        {
            this.LogTransactionDetails("Withdrawing", howMuch);
            this.currentBalance -= howMuch;
            return this;
        }
        private void LogTransactionDetails(string action, decimal howMuch)
        {
            Console.WriteLine(string.Format(
                "{2} ${0} on account {1}.", howMuch, this.Number, action));
        }
    }
}

Podemos perceber os atributos adicionados nas linhas 27 e 34, que apenas informam um aspecto sobre o comportamento deste método, cuja implementação não interessa para a classe “Account”.

Agora, vamos criar um aspecto “OnMethodInvocation”, para o log de detalhes de transação:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PostSharp.Laos;
using Codevil.MACSkeptic.PostSharpExample.Entities;

namespace Codevil.MACSkeptic.PostSharpExample.Aspects
{
    [Serializable]
    public class LogAccountTransactionDetails : OnMethodInvocationAspect
    {
        public override void OnInvocation(MethodInvocationEventArgs eventArgs)
        {
            Account account = (Account)eventArgs.Instance;
            Console.WriteLine(string.Format(
                "{2} ${0} on account {1}.",
                eventArgs.GetArgumentArray().First(),
                account.Number,
                eventArgs.Method.Name));
            eventArgs.Proceed();
        }
    }
}

Novamente, apenas movemos a implementação do método original de log que estava em nossa classe “Account” para dentro de nosso aspecto. O parâmetro “eventArgs” provê acesso, entre outras coisas:

  • À instância corrente da classe na qual o aspecto está sendo aplicado;
  • Ao método sendo chamado;
  • Aos parâmetros passados na chamada do método.

Um ponto importante a se notar no método acima é a chamada “eventArgs.Proceed” (proceder). Este comando diz ao weaver que o método original deve ser executado neste momento, ou seja, o fluxo original do método deve proceder sem alterações. Caso necessário, poderíamos, por exemplo: :

  • Modificar os parâmetros que estão sendo passados para o método original (basta passar o novo parâmetro para o método “proceed”);
  • Sequer chamar o método original neste ponto (basta omitir a chamada ao método “proceed”).

Com isso, conseguimos mover toda nossa preocupação com o log para os aspectos. Nossa classe “Account” atualizada fica assim:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Codevil.MACSkeptic.PostSharpExample.Aspects;

namespace Codevil.MACSkeptic.PostSharpExample.Entities
{
    public class Account
    {
        public long Number { get; set; }
        public string Owner { get; set; }
        public decimal CurrentBalance { get { return this.currentBalance; } }
        private decimal currentBalance;

        public Account(long number, string owner)
            : this(number, owner, 0)
        {
        }

        public Account(long number, string owner, decimal initialBalance)
        {
            this.Number = number;
            this.Owner = owner;
            this.currentBalance = initialBalance;
        }
        [LogAccountStatusBeforeAndAfterTransaction]
        [LogAccountTransactionDetails]
        public Account Deposit(decimal howMuch)
        {
            this.currentBalance += howMuch;
            return this;
        }
        [LogAccountStatusBeforeAndAfterTransaction]
        [LogAccountTransactionDetails]
        public Account Withdraw(decimal howMuch)
        {
            this.currentBalance -= howMuch;
            return this;
        }
    }
}

Antes de mais nada, utilizando uma expressão regular, podemos deixar nosso código ainda mais limpo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Codevil.MACSkeptic.PostSharpExample.Aspects;

namespace Codevil.MACSkeptic.PostSharpExample.Entities
{
    [LogAccountStatusBeforeAndAfterTransaction(
		AttributeTargetMembers = "regex:(Deposit)|(Withdraw)")]
    [LogAccountTransactionDetails(
		AttributeTargetMembers = "regex:(Deposit)|(Withdraw)")]
    public class Account
    {
        public long Number { get; set; }
        public string Owner { get; set; }
        public decimal CurrentBalance { get { return this.currentBalance; } }
        private decimal currentBalance;

        public Account(long number, string owner)
            : this(number, owner, 0)
        {
        }

        public Account(long number, string owner, decimal initialBalance)
        {
            this.Number = number;
            this.Owner = owner;
            this.currentBalance = initialBalance;
        }
        public Account Deposit(decimal howMuch)
        {
            this.currentBalance += howMuch;
            return this;
        }
        public Account Withdraw(decimal howMuch)
        {
            this.currentBalance -= howMuch;
            return this;
        }
    }
}

Repare que agora o código fonte de nossa classe “Account” não está mais poluído com a preocupação de manter um log das transações realizadas.

Quer mais? Ainda podemos fazer uma melhoria final, agora que temos um pouco mais de domínio sobre o funcionamento do PostSharp, podemos eliminar a utilização do aspecto “OnMethodBoundary” utilizado para os logs de início e término da transação. Para isso, basta realizarmos algumas pequenas modificações em nosso aspecto “OnMethodInvocation”, conforme denotado a seguir:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PostSharp.Laos;
using Codevil.MACSkeptic.PostSharpExample.Entities;

namespace Codevil.MACSkeptic.PostSharpExample.Aspects
{
    [Serializable]
    public class LogAccountTransactionDetails : OnMethodInvocationAspect
    {
        public override void OnInvocation(MethodInvocationEventArgs eventArgs)
        {
            Account account = (Account)eventArgs.Instance;

            LogTransactionStarting(eventArgs, account);
            LogTransactionDetails(eventArgs, account);
            eventArgs.Proceed();
            LogTransactionFinished(eventArgs, account);
        }
        private static void LogTransactionDetails(
			MethodInvocationEventArgs eventArgs, Account account)
        {
            Console.WriteLine(string.Format(
                "{2} ${0} on account {1}.",
                eventArgs.GetArgumentArray().First(),
                account.Number,
                eventArgs.Method.Name));
        }
        private static void LogTransactionStarting(
			MethodInvocationEventArgs eventArgs, Account account)
        {
            Console.WriteLine(string.Format(
                "New {2} transaction on account {0}.
                               Balance before operation is ${1}.",
                account.Number,
                account.CurrentBalance,
                eventArgs.Method.Name));
        }
        private static void LogTransactionFinished(
			MethodInvocationEventArgs eventArgs, Account account)
        {
            Console.WriteLine(string.Format(
               "Finished {2} transaction on account {0}.
                                Balance after the operation is ${1}.",
               account.Number,
               account.CurrentBalance,
               eventArgs.Method.Name));
        }
    }
}

Repare que primeiro logamos o início da transação (linha 17), depois os detalhes sobre parâmetros envolvidos na transação (linha 18), só então prosseguimos com a chamada do método original (linha 19), e por último logamos o estado final de nossa conta (linha 20). Com isso obtivemos um comportamento idêntico ao que tínhamos anteriormente com dois aspectos, porém utilizando apenas um. Por fim, basta ajustarmos a classe “Account” para não mais utilizar o aspecto “ao redor”:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Codevil.MACSkeptic.PostSharpExample.Aspects;

namespace Codevil.MACSkeptic.PostSharpExample.Entities
{
    [LogAccountTransactionDetails(
		AttributeTargetMembers = "regex:(Deposit)|(Withdraw)")]
    public class Account
    {
        public long Number { get; set; }
        public string Owner { get; set; }
        public decimal CurrentBalance { get { return this.currentBalance; } }
        private decimal currentBalance;
        private const decimal DefaultInitialBalance = 0;

        public Account(long number, string owner)
            : this(number, owner, DefaultInitialBalance)
        {
        }
        public Account(long number, string owner, decimal initialBalance)
        {
            this.Number = number;
            this.Owner = owner;
            this.currentBalance = initialBalance;
        }

        public Account Deposit(decimal howMuch)
        {
            this.currentBalance += howMuch;
            return this;
        }
        public Account Withdraw(decimal howMuch)
        {
            this.currentBalance -= howMuch;
            return this;
        }
    }
}

Ou seja, mesmo em um pequeno exemplo como este, substituímos pelo menos 6 linhas de código “alien” da classe “Account” original (considerando a melhor implementação possível sem aspectos, onde os métodos de log tivessem sido movidos para outra classe) que estavam relacionadas com a preocupação de manter um log de transações. No lugar delas, adicionamos apenas um atributo declarando um aspecto de comportamento esperado quanto às transações. Ressaltando, para evitar mal entendidos: a vantagem não é necessariamente escrever menos código, mas sim separar claramente as responsabilidades e escrever o código onde ele deve estar.

Fico por aqui, espero que tenham gostado e percebido o quanto aspectos podem facilitar o desenvolvimento e melhorar a manutenibilidade de código. Deixo uma recomendação final para que os interessados em usar aspectos em seus projetos leiam com atenção a documentação do PostSharp (ou de qualquer outra ferramenta que venham a usar) para conferir o que pode ser feito em termos de otimização e detalhes sobre performance (o PostSharp oferece vários recursos de inicialização de aspectos em tempo de compilação, diminuindo o overhead em tempo de execução).

Como sempre, o código base deste artigo está disponível no meu github, e pode ser baixado aqui. Sintam-se à vontade para deixar suas dúvidas, críticas e sugestões nos comentários e até a próxima ;) .

Referências:

Leituras recomendadas:

Cheeseburgers, Decorators e Mocks

Sexta-feira, 31 de julho de 2009 por Fernando Hamasaki de Amorim

Em São Paulo, eu sempre comi cheeseburgers feitos com pão, hamburguer e queijo. Mas quando eu fui para Itararé, cidade do interior do estado de São Paulo, descobri que eles também colocavam milho no sanduíche.

Para exemplificar, vamos imaginar que o cheeseburger de Ilhéus-BA, venha com molho de pimenta. Só para constar, eu nunca fui para Ilhéus, apesar de ser a cidade natal de meu pai. Então na verdade não tenho a mínima idéia de como seja o cheeseburger de lá.

teste

Vamos transportar esses três tipos de cheeseburgers para objetos e fazer alguns testes com eles. Usarei como plataforma .NET, linguagem C#, a ferramenta de testes unitários que vem com o Visual Studio 2008 e o Rhino Mocks como framework de criação de mocks.

.
Cheeseburgers com herança
Primeiramente criamos a classe Cheeseburger, que representa o cheeseburger comum de São Paulo. Nessa classe teremos uma propriedade para descrição e um método que retorna a quantidade de calorias do sanduíche.

public class Cheeseburger
{
    public Cheeseburger()
    {
        this.Description = "Bread, Hamburger, Cheese";
    }

    public string Description { get; protected set; }

    public virtual int Calories()
    {
        return 300;
    }
}

.
Os testes para a classe Cheeseburger são bem simples:

[TestClass]
public class CheeseburgerTest
{
    private Cheeseburger cheeseburger;

    [TestInitialize]
    public void Init()
    {
        this.cheeseburger = new Cheeseburger();
    }

    [TestMethod]
    public void Description_Of_Cheeseburger()
    {
        Assert.AreEqual("Bread, Hamburger, Cheese", this.cheeseburger.Description);
    }

    [TestMethod]
    public void Calories_Of_Cheeseburger()
    {
        Assert.AreEqual(300, this.cheeseburger.Calories());
    }
}

.
Agora vamos criar a classe CheeseburgerItarare que possuirá os mesmos membros de Cheeseburger. Como nós aprendemos na faculdade ou em algum curso que herança é um dos pilares da orientação a objetos e serve para reutilização de código, CheeseburgerItarare irá herdar de Cheeseburger.

public class CheeseburgerItarare : Cheeseburger
{
    public CheeseburgerItarare() : base()
    {
        this.Description += ", Corn";
    }

    public override int Calories()
    {
        return base.Calories() + 70;
    }
}

A diferença no cheeseburger de Itararé é o milho, então acrescentamos um texto para milho na descrição e sobrescrevemos o método Calories para somar 70 calorias ao valor que é retornada do método Calories da classe base Cheeseburger.

Fazemos o mesmo para o cheeseburger de Ilhéus: criamos uma classe CheeseburgerIlheus que herda de Cheeseburger e adicionamos a descrição e calorias equivalentes ao molho de pimenta. Veja o diagrama de classes:

Cheeseburgers com herança

Cheeseburgers com herança

.
Os testes para a classe CheeseburgerItarare ficam assim:

[TestClass]
public class CheeseburgerItarareTest
{
    private CheeseburgerItarare cheeseburgerItarare;

    [TestInitialize]
    public void Init()
    {
        this.cheeseburgerItarare = new CheeseburgerItarare();
    }

    [TestMethod]
    public void Description_Of_Cheeseburger_Itarare()
    {
        Assert.AreEqual("Bread, Hamburger, Cheese, Corn", this.cheeseburgerItarare.Description);
    }

    [TestMethod]
    public void Calories_Of_Cheeseburger_Itarare()
    {
        Assert.AreEqual(370, this.cheeseburgerItarare.Calories());
    }
}

Em princípio muito simples. Os testes unitários asseguram o comportamento de CheeseburgerItarare, ou seja, os valores de retorno da descrição e da quantidade de calorias, mas não testamos implementação. Em outras palavras, não conseguimos assegurar que quando o método Calories de CheeseburgerItarare é executado, uma chamada ao método Calories da classe base Cheeseburger é realizada.

Para testar essa chamada de método da classe base, precisamos usar mocks. Com isso, temos um problema de acoplamento, pois CheeseburgerItarare depende de Cheeseburger e com herança não conseguimos substituir a classe base por um mock. Podemos resolver isso usando composição ao invés de herança.

Antes de passarmos para composição, quero alertar que você precisa avaliar se testar chamadas de outras classes agregam valor aos testes que você está fazendo. Às vezes somente o comportamento da classe já é o suficiente para considerar uma classe testada. Você precisa ponderar o que é importante e necessário testar nos pedaços de código do seu sistema. Nesse pequeno exemplo em específico, estou considerando necessário testar as chamadas de método de Cheeseburger dentro das classes CheeseburgerItarare e CheeseburgerIlheus, por isso vamos modificar o design das mesmas.

.
Cheeseburgers com composição
Os princípios de orientação a objetos, entre outras coisas, nos dizem para:

  • Dar prioridade à composição em à relação à herança;
  • Programar para interfaces, não para para implementações;
  • Depender de abstrações, não de classes concretas.

Na solução de cheeseburgers com herança, violamos todos esses princípios. Então vamos consertar isso.

Primeiro, vamos extrair a interface da classe Cheeseburger:

public interface ICheeseburger
{
    string Description { get; }

    int Calories();
}

.
Nossa classe Cheeseburguer não muda em nada, só que agora implementa a interface ICheeseburguer:

public class Cheeseburger : ICheeseburger
{
    public Cheeseburger()
    {
        this.Description = "Bread, Hamburger, Cheese";
    }

    public string Description { get; protected set; }

    public virtual int Calories()
    {
        return 300;
    }
}

.
Teremos então uma mudança significativa nas classes CheeseburgerItarare e CheeseburgerIlheus, que não herdarão mais de Cheeseburger e implementarão a interface ICheeseburger. Veja o diagrama de classes como fica:

Cheeseburgers com composição

Cheeseburgers com composição

.
E a nova implementação da classe CheeseburgerItarare:

public class CheeseburgerItarare : ICheeseburger
{
    private ICheeseburger cheeseburger;

    public CheeseburgerItarare(ICheeseburger cheeseburger)
    {
        this.cheeseburger = cheeseburger;
        this.Description = this.cheeseburger.Description + ", Corn";
    }

    public string Description { get; protected set; }

    public int Calories()
    {
        return this.cheeseburger.Calories() + 70;
    }
}

A classe CheeseburgerItarare agora tem um campo privado do tipo ICheeseburger (linha 3), que será atribuído valor através de um parâmetro recebido no construtor (linha 7). Dessa forma, estamos compondo a classe Cheeseburger com alguma implementação de ICheeseburger, injetando essa dependência através do seu construtor. O método Calories delega sua chamada ao método Calories do objeto que foi injetado (linha 15) e soma as 70 calorias correspondentes ao milho.

Com esse design nós podemos criar um mock de ICheeseburger, passar esse mock para o construtor de CheeseburgerItarare e testar as chamadas de método e propriedade de ICheeseburger dentro da classe CheeseburgerItarare.

Vamos ver como ficam os testes agora:

[TestClass]
public class CheeseburgerItarareTest
{
    private MockRepository mocks;
    private ICheeseburger cheeseburgerMock;
    private CheeseburgerItarare cheeseburgerItarare;

    [TestInitialize]
    public void Init()
    {
        this.mocks = new MockRepository();
        this.cheeseburgerMock = this.mocks.DynamicMock();
    }

    [TestMethod]
    public void Description_Of_Cheeseburger_Itarare()
    {
        Expect.Call(this.cheeseburgerMock.Description).Return("Cheeseburger description");

        this.mocks.ReplayAll();
        this.cheeseburgerItarare = new CheeseburgerItarare(this.cheeseburgerMock);
        this.mocks.VerifyAll();

        Assert.AreEqual("Cheeseburger description, Corn", this.cheeseburgerItarare.Description);
    }

    [TestMethod]
    public void Calories_Of_Cheeseburger_Itarare()
    {
        Expect.Call(this.cheeseburgerMock.Calories()).Return(100);

        this.mocks.ReplayAll();

        this.cheeseburgerItarare = new CheeseburgerItarare(this.cheeseburgerMock);
        Assert.AreEqual(170, this.cheeseburgerItarare.Calories());

        this.mocks.VerifyAll();
    }
}

Para o teste da quantidade de calorias, na linha 30 gravamos uma chamada esperada do método Calories do mock de ICheeseburger definindo seu valor de retorno para 100. Instanciamos CheeseburgerItarare passando o mock como parâmetro na linha 34 e quando chamamos o método Calories na linha 35, esperamos o valor de 170 calorias: 100 do mock + 70 equivalente às calorias do milho. Finalmente, na linha 37 conseguimos assegurar que o método Calories do mock da interface ICheeseburger foi chamado na implementação do método Calories da classe CheeseburgerItarare.

A implementação e os testes da classe CheeseburgerIlheus seguem o mesmo esquema da classe CheeseburguerItarare.

Com isso, atingimos nosso objetivo de testar comportamento e implementação das classes de cheeseburgers.

Mas e se nós quisermos um novo tipo de cheeseburger, por exemplo, um que venha com cebola? Ah, é fácil, basta criar uma classe nova que implementa ICheeseburger com o mesmo esquema de injeção de dependência de uma implementação de ICheeseburger. Mas e se nós também quisermos criar outras combinações de ingredientes, como por exemplo:

  • Cheeseburger com milho e molho de pimenta;
  • Cheeseburger com milho e cebola;
  • Cheeseburger com milho, cebola e molho de pimenta.

Vamos ter que criar uma classe para cada combinação de ingredientes? E se ao invés de três ingredientes nós tivermos 15 ingredientes? Hum, bastante combinações, não? E se num futuro próximo aparecer novos ingredientes? Vamos ter que criar novas classes para todas as novas combinações de ingredientes?

.
Cheeseburgers com decoradores
Ao invés de criarmos inúmeras classes para inúmeras combinações de ingredientes de cheeseburgers, podemos modificar nosso design para criarmos ingredientes independentes e “decorar” os cheeseburgers com os ingredientes que quisermos e quando quisermos.

Isso segue um outro princípio de orientação a objetos: classes devem estar abertas para extensão, mas fechadas para modificação.

Usaremos um design pattern chamado Decorator, que é definido da seguinte forma:

Atribui responsabilidades adicionais a um objeto dinamicamente. Os Decorators fornecem uma alternativa flexível a subclasses para extensão de funcionalidades.

.
E aqui temos o diagrama de classe com a estrutura do padrão:

Decorator Design Pattern

Decorator Design Pattern

Onde:

  • Component define a interface para objetos que podem ter responsabilidades acrescentadas aos mesmos dinamicante;
  • ConcreteComponent define um objeto para o qual responsabilidades adicionais podem ser atribuídas;
  • Decorator mantém uma referência para um objeto Component e define uma interface que segue a interface de Component;
  • ConcreteDecorator acrescenta responsabilidades ao componente.

Chega de teoria e vamos para a prática nos nossos cheeseburgers.

A primeira coisa a fazer é criar a classe abstrata que servirá como Component. Vamos chamá-la de Sandwich.

public abstract class Sandwich
{
    public virtual string Description { get; protected set; }

    public abstract int Calories();
}

Note que marcamos a propriedade Description como virtual para suas subclasses poderem sobrescrevê-la.

Depois criamos outra classe abstrata que servirá como Decorator e a nomeamos como SandwichDecorator. Essa classe irá herdar de Sandwich.

public abstract class SandwichDecorator : Sandwich
{
    protected Sandwich sandwich;

    public SandwichDecorator(Sandwich sandwich)
    {
        this.sandwich = sandwich;
    }

    public override string Description
    {
        get
        {
            return this.sandwich.Description;
        }
    }

    public override int Calories()
    {
        return this.sandwich.Calories();
    }
}

Da mesma forma que fizemos no exemplo de cheeseburgers com composição, a injeção de dependência é feita pelo construtor (linhas 5 a 8), atribuindo o valor do parâmetro ao campo protegido do tipo Sandwich. A propriedade Description (linhas 10 a 16) é implementada delegando seu retorno para a propriedade Description da variável sandwich, ou seja, o objeto que foi injetado. O método Calories finalmente é implementado (linhas 18 a 21) e também delega sua chamada para a variável sandwich, mas chamando o método Calories da mesma.

Agora vamos modificar a classe Cheeseburger para herdar de Sandwich. Essa é a classe que servirá como ConcreteComponent.

public class Cheeseburger : Sandwich
{
    public Cheeseburger()
    {
        this.Description = "Bread, Hamburger, Cheese";
    }

    public override int Calories()
    {
        return 300;
    }
}

.
O último passo é criar as classes que servirão como ConcreteDecorator: Corn, OnionRings e PepperSauce. Segue o código da classe Corn:

public class Corn : SandwichDecorator
{
    public Corn(Sandwich sandwich) : base(sandwich)
    {

    }

    public override string Description
    {
        get
        {
            return base.Description + ", Corn";
        }
    }

    public override int Calories()
    {
        return base.Calories() + 70;
    }
}

O construtor da classe Corn chama o construtor da classe base SandwichDecorator passando o parâmetro do tipo Sandwich (linhas 3 a 6). A propriedade Description (linhas 8 a 14) é sobrescrita para retornar a descrição da classe base juntamente com a descrição de milho. O método Calories também é sobrescrito (linhas 16 a 19) e soma o retorno do método Calories da classe base com as calorias do milho. Em suma, toda a implementação é feita chamando os membros da classe base e adicionando comportando e/ou estado relacionado ao ingrediente milho quando necessário.

O mesmo esquema para as classes OnionRings e PepperSauce:

public class OnionRings : SandwichDecorator
{
    public OnionRings(Sandwich sandwich) : base(sandwich)
    {

    }

    public override string Description
    {
        get
        {
            return base.Description + ", Onion Rings";
        }
    }

    public override int Calories()
    {
        return base.Calories() + 140;
    }
}
public class PepperSauce : SandwichDecorator
{
    public PepperSauce(Sandwich sandwich) : base(sandwich)
    {

    }

    public override string Description
    {
        get
        {
            return base.Description + ", Pepper Sauce";
        }
    }

    public override int Calories()
    {
        return base.Calories() + 20;
    }
}

.
Aqui está o diagrama de classes dos cheeseburgers com decoradores:

Cheeseburgers com decoradores

Cheeseburgers com decoradores

.
Vamos ver como isso funciona através dos testes.

[TestClass]
public class CheeseburgerVariedTest
{
    private Sandwich cheeseburger;

    [TestInitialize]
    public void Init()
    {
        this.cheeseburger = new Cheeseburger();
    }

    [TestMethod]
    public void Itarare_Cheeseburger()
    {
        this.cheeseburger = new Corn(this.cheeseburger);

        Assert.AreEqual("Bread, Hamburger, Cheese, Corn", this.cheeseburger.Description);
        Assert.AreEqual(370, this.cheeseburger.Calories());
    }

    [TestMethod]
    public void Itarare_Cheeseburger_With_Onion_Rings()
    {
        this.cheeseburger = new Corn(this.cheeseburger);
        this.cheeseburger = new OnionRings(this.cheeseburger);

        Assert.AreEqual("Bread, Hamburger, Cheese, Corn, Onion Rings", this.cheeseburger.Description);
        Assert.AreEqual(510, this.cheeseburger.Calories());
    }

    //Outros testes
}

No primeiro teste decoramos a instância da classe Cheeseburger com milho (linha 15), embrulhando-a (wrapping) com a classe Corn. Já no teste de cheeseburger com milho e cebola, a variável cheeseburger é decorada duas vezes, uma vez com a classe Corn (linha 24) e outra com a classe OnionRings (linha 25). Cada vez que um cheeseburger é decorado, o decorador adiciona seu próprio comportamento antes e/ou depois de delegar para o objeto que ele decora.

Note que, como as classes Corn e OnionRings herdam de SandwichDecorator, que por sua vez herda de Sandwich, assim como a classe Cheeseburger, no final das contas estamos sempre lidando com a classe abstrata Sandwich. Por isso, através do polimorfismo, podemos atribuir uma instância da classe Corn e/ou OnionRings à uma variável do tipo Cheeseburger.

Quando o método Calories da variável cheeseburger é chamado na linha 28, estamos chamando o método Calories do último decorador, ou seja, da classe OnionRings. Depois contamos com a delegação para adicionar as calorias dos ingredientes. Veja o acontece:

  1. OnionRings chama o método Calories da classe base SandwichDecorator;
  2. SandwichDecorator delega sua chamada para o método Calories da classe Corn;
  3. Corn chama o método Calories da classe base SandwichDecorator;
  4. SandwichDecorator delega sua chamada para o método Calories da classe Cheeseburger;
  5. Cheeseburger retorna 300 calorias;
  6. SandwichDecorator retorna o valor das calorias que retornou da classe Cheeseburger;
  7. Corn adiciona suas 70 calorias ao retorno da classe base SandwichDecorator e retorna 370 calorias;
  8. SandwichDecorator retorna o valor das calorias que retornou da classe Corn;
  9. OnionRings adiciona suas 140 calorias ao retorno da classe base SandwichDecorator e retorna 510 calorias.

Complicado para entender? Dê uma olhada novamente no diagrama de classes “Cheesebugers com decoradores” e depois volte para o código. E se você debugar os testes, poderá ver passo-a-passo como as coisas acontecem. No final do post tem o link para você baixar o código completo.

.
Uma coisa interessante é que você pode testar isoladamente as classes ConcreteDecorator (Corn, PepperSauce, OnionRings ou qualquer outro decorador que você queira acrescentar). Por exemplo:

[TestClass]
public class CornTest
{
    private MockRepository mocks;
    private Sandwich sandwichMock;
    private Corn corn;

    [TestInitialize]
    public void Init()
    {
        this.mocks = new MockRepository();
        this.sandwichMock = this.mocks.DynamicMock();
    }

    [TestMethod]
    public void Description_Of_Cheeseburger_With_Corn()
    {
        Expect.Call(this.sandwichMock.Description).Return("Sandwich description");

        this.mocks.ReplayAll();

        this.corn = new Corn(this.sandwichMock);
        Assert.AreEqual("Sandwich description, Corn", this.corn.Description);

        this.mocks.VerifyAll();
    }

    [TestMethod]
    public void Calories_Of_Cheeseburger_With_Corn()
    {
        Expect.Call(this.sandwichMock.Calories()).Return(100);

        this.mocks.ReplayAll();

        this.corn = new Corn(this.sandwichMock);
        Assert.AreEqual(170, this.corn.Calories());

        this.mocks.VerifyAll();
    }
}

Aqui usamos mocks para testar as chamadas de métodos de Sandwich, assim como fizemos nos testes das classes CheeseburgerItarare e CheeseburgerIlheus no exemplo de cheeseburgers com composição.

Com esse design nós podemos ter novos ingredientes sem termos que nos preocupar com combinações, basta criar uma classe nova do ingrediente para decorar os sanduíches. Além disso, nós podemos criar também outros tipos de sanduíche. Por exemplo, podemos criar uma classe HotDog que herda da classe Sandwich. O ponto é: qualquer classe que herde da classe Sandwich pode ser decorada com os nossos ConcreteDecorators.

Design Patterns são soluções para problemas comuns e não necessariamente soluções prontas para o seu problema. Eles lhe ajudam a encontrar a sua solução para o seu problema específico. Fique à vontade para adaptá-los às suas necessidades.

Dúvidas, questionamentos, discórdias, sugestões? Deixe seu comentário.

O código completo com todas as classes e seus testes está disponível aqui.

.
Referências em inglês:

Referências em português:

.
Atualização em 14/09/2009: Fiz um post com a implementação desse exemplo de Decorator Pattern em Ruby. Você pode vê-lo aqui.

Post original:
http://prodis.pro.br/2009/07/29/cheeseburgers-decorators-e-mocks

.

FISL 10 – Aplicações Web escaláveis.

Quinta-feira, 2 de julho de 2009 por Alessandro Santos

Na última edição do Fisl tivemos a oportunidade de conhecer algumas soluções implementadas tanto por grandes empresas quanto por startups para garantir que suas aplicações web obtivessem um alto grau de performance, capacidade e, principalmente, escalabilidade.

Foram expostos alguns cases de infraestrutura de aplicações web com um alto volume de transações como, por exemplo, o boo-box, o Drimio e aplicações desenvolvidas pela globo.com.

Algumas das implementações de infraestrutura usadas por diversas empresas de médio/grande porte já são “antigas” e populares no mundo web, mas com o advento de novas tecnologias para load balance, cached, etc, fazem a tarefa de deixar uma aplicação web escalável, um pouco mais fácil de implementar e manter.

Abaixo seguem os itens mais abordados pelos desenvolvedores presentes no Fisl no tocante a escalabilidade em ambiente web.

Infraestrura em camadas:

A divisão da aplicação web em camadas permite que cada uma delas seja tratada de forma individual.
Essa divisão permite as melhores praticas para garantir que a escalabilidade seja aplicada diretamente na camada que venha a se tornar um gargalo na aplicação.

Arquitetura em camadas.

Load Balanced (Balanceadores de carga):

O uso desse artefato tanto na camada de aplicação quanto na camada de acesso a dados permite escalar a aplicação de forma horizontal.
Escalar na horizontal permite que em uma situação em que aplicação já não tenha um tempo resposta aceitável devido ao alto volume de acessos (performace x capacidade) tenha seu estado normalizado apenas por incluir um novo servidor na infraestrutura da aplicação, ou seja, na camada que foi afetada pelo problema.

fluxo

Banco de dados:

Uma prática comum em sites de médio/grande porte é, além do uso de balanceadores, a divisão de requisições de modificação de dados (insert, update e delete) e leituras em diferentes bases de dados (select).
A sincronização dos dados em todas as bases envolvidas é feita por meio de replicação entre as bases.
O uso de tipo de dados corretos, índices em tabelas e boas práticas na construção de consultas ou store procedures trazem um ganho de performance considerável. Um DBA sempre é bem vindo.

Cached:

Em grande parte da cases no Fisl relacionados ao assunto desse post o termo cached sempre esteve presente nas aplicações apresentadas.
Nesse quesito o memcached, já consolidado na comunidade, foi o sistema de cached mais usado para armazenar conteúdos como resultados de queries SQL, sessões de usuários, css, javascripts, etc.

Monitoração/métricas:

Manter uma aplicação web sob controle requer monitoração constante.
O uso de ferramentas de monitoramento/métricas permite-se alertas sobre possíveis problemas de performance, falhas de hardware, bugs na aplicação e também ajuda com capacity planning.
Apache ab, nagios, cacti, mrtg, snmp, jmeter, firebug foram os mais citados.

Arquivos estáticos:

Deixar arquivos estáticos como css, imagens, javascripts, etc em um servidor a parte e se possível carregar esses dados em memória, reduzindo assim a carga no servidor de aplicação, se tornou um excelente prática.
Deixando o servidor de arquivos estáticos em um subdomínio pode-se tirar vantagem do fato de navegadores modernos serem capazes de carregar arquivos de múltiplos domínios simultaneamente.
Outras melhorias de performance em arquivos estáticos podem ser verificadas aqui.

fluxo11

Algumas dicas deixadas pelos palestrantes:

    - Procurar fazer transações curtas e comandos simples no banco de dados.
    - Regras de negócio na aplicação. Banco de dados apenas para armazenar dados.
    - Use cached sempre que possível.
    - Sessões de usuário devem ser armazenadas em banco ou cached facilitam a escala da aplicação.
    - Eliminar possíveis SPOF (Single point of failure).
    - Coletar estatísticas de performance. Capacity planning deve ser feito com dados reais e não com abstratos.
    - 80% a 90% do tempo de resposta para o usuário final é gasto no frontend da aplicação. Otimize ao máximo essa camada.
    - Linguagem/framework de programação por si só não escala.

Apresentações originais:

Boo-box/Marco Gomes : http://www.slideshare.net/marcogomes/construindo-uma-empresa-de-servio-web-baseada-em-software-livre-e-colaborao-fisl-10

Drimio/Elton Luís Minetto : http://www.slideshare.net/eminetto/app-web-escalaveis-fisl

Globo.com/Camila Dias : http://www.slideshare.net/camilapdias/fisl10-mysql-na-globocom-por-camila-dias-1663778

Cuidado: seu site pode ter sido invadido

Quinta-feira, 25 de junho de 2009 por AkitaOnRails

O Google tem já há algum tempo um serviço onde ele avalia a periculosidade de um website. Da mesma forma como eles já vasculham toda a Internet para indexar suas páginas, agora ele também as avalia em termos de segurança. A maioria dos bons web browsers já implementa integração com isso e, toda vez que você tentar entrar por acidente em um web site com possíveis problemas de segurança, o browser o avisará da seguinte forma:

seguranca
Tela de aviso sobre problema de segurança em um site

Assim você pode escolher se quer seguir ou não. Isso não significa com 100% de certeza que o site está realmente invadido, apenas que, pelo critérios do próprio Google, ele acha que sim.

Agora, o que leva um site a ser invadido? Existem várias maneiras e você como dono de um website, deve ficar muito atento a isso:

  • sua senha na sua hospedagem é fraca. E senha fraca é mais comum do que se pensa. Você usa uma daquelas senhas do tipo “123456″, ou “teste123″ ou mesmo combinações de palavras e datas “joao1970″? Tudo isso é muito fraco e lhes digo que trivialmente quebrável. A senha precisa ser o mais aleatório possível. Quer um exemplo de senha forte? Use este gerador e gere combinações como estas:

    D2F16079AD2267C09BBD3ECD7C546892C4FB3F4AF52BD556ED857

    (Claro, tome todo cuidado para não esquecer depois qual era a senha, é como trancar a porta de casa e esquecer onde deixou a chave ;-)

  • a programação do seu site é falha. E isso é extremamente comum. Na verdade é mais fácil achar um site com defeito do que bem protegido.

    Especialmente se o programador não tem interesse em se aprofundar realmente na área (não, faculdade, cursos, certificações, nada disso é garantia). Lembrem-se, exemplos de revista, livros, foram feitos para serem didáticos. Por exemplo, se um livro mostra um exemplo de como se faz um formulário que recebe um upload de imagens, ou um formulário para cadastrar e-mails, eles estão preocupados em explicar apenas um assunto. Coisas como performance, escalabilidade, usabilidade, segurança, não estão sendo discutidos. Especificamente sobre segurança, esses exemplos quase sempre tem buracos, por exemplo, coisas como “Injeção de SQL”. Ou imagine que em vez de fazer upload de uma imagem como JPG, o malfeitor faça download de um arquivo do tipo “virus.exe.jpg” e seu site aceita e os usuários baixam?

Esses são apenas 2 pequenos exemplos. O mundo da internet tem dezenas de maneiras de se invadir e contaminar um website. Às vezes nem precisa de muito, basta alguém que baixou uma versão de um software como Drupal ou Joomla (e não importa se é open source ou comercial, o risco é o mesmo) e nunca fez atualizações, mesmo depois de já terem sido descobertas falhas de segurança que já foram corrigidas. Sistemas obsoletos vão se tornando mais e mais vulneráveis à medida que novas formas de invasão ou bugs são descobertos.

Além de “Injeção de SQL” (SQL Injection), ainda existem Cross-Site Request Forgery, Cross Site Scripting, Replay Attack, Session Fixation e muito mais. Hoje em dia não basta apenas que o site esteja “rodando”, isso quase sempre não garante muita coisa. Por isso sempre procure pessoas/empresas experientes e de boa reputação para fazer seu site, porque caso contrário é o nome da sua empresa que pode ficar prejudicada quando seus usuários começarem a ser infectados e quando o Google Safe Browsing passar a categorizá-lo como site perigoso.

Faça seu site ser mais rápido

Quinta-feira, 25 de junho de 2009 por Joca

Há dois dias atrás o Levy falou sobre como melhorar a permformance de um site no browser aqui mesmo no TecBlog. Hoje o Google lançou uma nova iniciativa em:

http://code.google.com/speed/

que tem por objetivo ajudar todas as pessoas que trabalham com desenvolvimento de aplicativos web a torná-los mais rápidos. Abaixo um vídeo em inglês (sem legendas) sobre porque cada milisegundo conta:



Esse site tem 3 áreas:

Vale dar uma olhada para rever ou aprender novas técnicas para melhorar a performance do seu site.

Afinal, com um site mais rápido todos ganham.


Switch to our mobile site