Aprendendo TDD! Mais sobre dublês de teste

No post anterior vimos como os dublês de testes, mais conhecidos por mocks, podem nos ajudar a testar alguns cenários “impossíveis”. Vamos então entender como deixar nossos testes mais confiáveis, isolados e rápidos com outros tipos de dublês!

O artigo The Little Mocker, de Uncle Bob, é leitura recomendadíssima para aprender mais sobre os diferentes tipos de dublês.

Como exemplo vamos imaginar que queremos escrever testes que dependem da seguinte classe que é responsável por autorizar o pagamento de um cartão de crédito:

class ServicoValidacaoPagamento
  def autorizar_pagamento(numero_cartao, mes_expiracao, ano_expiracao)
    # Lógica complexa para autorizar um pagamento
  end
end

Stub

Utilizamos um Stub quando não precisamos nos preocupar com a execução de um método, apenas com o resultado que ele retorna. Para criar um stub para o código anterior, poderíamos fazer algo como:

class ServicoValidacaoPagamentoStub
  def autorizar_pagamento(numero_cartao, mes_expiracao, ano_expiracao)
    true
  end
end

Sempre que utilizarmos ServicoValidacaoPagamentoStub todos os pagamentos serão autorizados, assim para os nossos testes não precisamos nos preocupar com a sua lógica para retornar o que queremos.

Para testar que um pedido é finalizado quando o pagamento é validado poderíamos utilizar o stub assim:

it "marca pedido como finalizado quando pagamento eh aprovado" do
  pedido = Pedido.new(ServicoValidacaoPagamentoStub.new)
  pedido.adicionar_produtos(produtos)
  pedido.forma_de_pagamento(cartao_de_credito)

  pedido.finalizar # vai chamar o serviço para validar o pagamento

  pedido.estado.must_equal EstadoPedidos::FINALIZADO
end

Spy

Um Spy é utilizado quando queremos validar que métodos foram chamados, quantas vezes, com quais argumentos e quaisquer outras informações que nos interesse.

Supondo que agora queremos validar que, ao finalizar o pedido, estamos utilizando os dados corretos do cartão de crédito para validar o pagamento. Esse é um spy que poderíamos utilizar:

class ServicoValidacaoPagamentoSpy
  attr_reader :numero_cartao, :mes_expiracao, :ano_expiracao, :autorizacao_executada
  def autorizar_pagamento(numero_cartao, mes_expiracao, ano_expiracao)
    @autorizaca_executada = true
    @numero_cartao = numero_cartao
    @mes_expiracao = mes_expiracao
    @ano_expiracao = ano_expiracao
    true
  end
end

Com o Spy, além de forçar o valor de retorno também estamos guardando informações se o método foi chamado e quais argumentos foram passados. O teste utilizaria o Spy da seguinte forma:

it "chama servico de validacao ao finalizar pagamento" do
  spy = ServicoValidacaoPagamentoSpy.new
  pedido = Pedido.new(spy)
  pedido.adicionar_produtos(produtos)
  pedido.forma_de_pagamento(cartao_de_credito)

  pedido.finalizar # vai chamar o serviço para validar o pagamento

  spy.autorizaca_executada.must_equal true
spy.numero_cartao.must_equal cartao_de_credito.numero
spy.mes_expiracao.must_equal cartao_de_credito.mes_expiracao
spy.ano_expiracao.must_equal cartao_de_credito.ano_expiracao
end

Utilizando um spy quando queremos validar informações relacionadas a como o método foi executado.

Mock

Assim como o Spy, o Mock também valida métodos que foram chamados. A diferença é bem sútil, um Mock sabe o que deve ser testado, ao contrário do Spy que apenas guarda informação da execução.

class ServicoValidacaoPagamentoMock
  attr_reader :autorizacao_executada
  def autorizar_pagamento(numero_cartao, mes_expiracao, ano_expiracao)
    @autorizaca_executada = true
    true
  end

def verifica_execucao()
autorizacao_executada.true?
end
end 

Um mock possui a lógica de validação dentro dele, assim o teste fica mais simples. No entanto a lógica de verificação pode ser mais complexa do que apenas se o método foi executado, como mostra o exemplo. O teste ficaria assim:

it "chama servico de validacao ao finalizar pagamento" do
  mock = ServicoValidacaoPagamentoMock.new
  pedido = Pedido.new(mock)
  pedido.adicionar_produtos(produtos)
  pedido.forma_de_pagamento(cartao_de_credito)

  pedido.finalizar # vai chamar o serviço para validar o pagamento

  mock.verifica_execucao.must_equal true
end

Stub, Spy ou Mock?

A razão pelo qual Dublês de Testes são conhecidos como mocks é que a diferença entre eles é bem sutil e muitas vezes os frameworks vão empacotar todas essas funcionalidades e chamar de mock.

Na prática utilizamos na maioria das vezes stubs, para apenas substituir o comportamento de um método por seu valor fixo, ou um spy, para avaliar informação relacionadas a chamadas de método. Mas como os autores dos frameworks decidiram chamar tudo de mock ¯_(⌣̯̀⌣́)_/¯

Se quiser ir mais a fundo para entender a diferença entre eles, sugiro o artigo Mocks Aren’t Stubs de Martin Fowler que contém vários exemplos e mais detalhes.

Dummy

Um Dummy é utilizado para substituir um objeto quando você não precisa se importar como ele será utilizado. Geralmente ele é utilizado para melhorar a legibilidade, já que não possuem nenhum comportamento. Esse seria um Dummy para o serviço de autorização de pagamento:

class ServicoValidacaoPagamentoDummy
  def autorizar_pagamento(numero_cartao, mes_expiracao, ano_expiracao)
end
end

O Dummy recebe chamadas, mas não faz nada com elas. O principal objetivo é melhorar a legibilidade, já que sempre que criamos um pedido precisamos passar um serviço podemos utilizar o Dummy ao invés de um nil:

it "adicionar produtos ao pedido" do
  pedido = Pedido.new(ServicoValidacaoPagamentoDummy.new)
  pedido.adicionar_produtos(produtos)

pedido.quantidade_produtos.must_equal 1
end

Fake

Semelhante ao Dummy, o Fake é utilizado para substituir objetos no entanto ele possui uma determinada lógica que é será utilizada em diferentes cenários de teste. Por exemplo:

class ServicoValidacaoPagamentoFake
  def autorizar_pagamento(numero_cartao, mes_expiracao, ano_expiracao)
    numero_cartao == "1234-1234-1234-1234"
  end
end 

Assim, podemos utilizar nos testes e simular a falha/sucesso do pagamento sem precisar mudar o dublê.

it "marca pedido como finalizado quando pagamento eh aprovado" do
  pedido = Pedido.new(ServicoValidacaoPagamentoFake.new)
pedido.adicionar_produtos(produtos)
pedido.forma_de_pagamento(cartao_de_credito_valido)
# cartao_de_credito_valido possui o numero esperado pelo Fake

pedido.finalizar # vai chamar o serviço para validar o pagamento

pedido.estado.must_equal EstadoPedidos::FINALIZADO
end

it "marca pedido como pendente quando pagamento nao eh aprovado" do pedido = Pedido.new(ServicoValidacaoPagamentoFake.new)
pedido.adicionar_produtos(produtos)
pedido.forma_de_pagamento(cartao_de_credito_invalido)
# cartao_de_credito_invalido possui outro numero

pedido.finalizar # vai chamar o serviço para validar o pagamento

pedido.estado.must_equal EstadoPedidos::PENDENTE
end

Dublês de Teste na vida real

Na maioria das vezes que utilizei dublês foi através de uma biblioteca que cria esses objetos. Apesar da facilidade que a biblioteca proporciona é importante entender o motivo da utilização do dublê e também saber que escrever seu próprio não é algo tão difícil assim.

Espero que tenha gostado desse post e que consiga utilizar dublês de teste com mais efetividade! Apesar de ser uma excelente ferramenta eles precisam ser utilizados com cuidado e bastante consciência.

Continue acompanhando a série com posts semanais Aprendendo TDD! Compartilhe com amigos e colegas que querem aprender. Se tem alguma sugestão, dúvida ou comentário use o espaço aqui do blog ou me procure no twitter em @marcosbrizeno. Até a próxima!

[Refatorando tudo!] Não retorne nulo. Nunca.

Considere um exemplo bem simples: precisamos exibir a lista com as últimas compras que um usuário fez.

public HistoricoDeCompras buscaHistoricoDeCompras(String email) { ... }

Para complicar um pouco mais, imagine que existe um serviço cuja responsabilidade é gerenciar compras. Esse serviço expõe uma interface que retorna o histórico de compras de um usuário, dado o seu email.

public HistoricoDeCompras buscaHistoricoDeCompras(String email) {
  RespostaHttp resposta = servicoDeCompras.obterHistorico(email);
  if (resposta.getStatus().sucesso() ) {
    return converterParaHistoricoDeCompras(resposta.getConteudo());
  } else {
    return null;
  }
}

Esse código funciona bem, mas tem um grande problema (que pelo título do post você já deve ter percebido): ele nem sempre retorna o histórico de compras.

Não me obrigue a saber de coisas!

Como Rob Looby explica nesse post fantástico, existem milhares de coisas que nós desenvolvedores precisamos manter na cabeça: a estrutura do programa, o novo design que queremos seguir, boas práticas do time, regras de negócio etc. Os possíveis retornos de uma função/método não deveriam ser uma dessas coisas.

Para quem escreveu o código acima, retornar nulo pode parecer como uma boa decisão. Não tenho nenhum histórico de compras para exibir, então não retorno nada. Como se representa nada? Com um valor nulo.

Se você também acha que null é a melhor maneira de representar nada, sugiro assistir a apresentação da Sandi Metz, Nothing is Something (na verdade assista a palestra independente do que você acha, vale muito a pena :D).

Na grande maioria das vezes, quando retornamos nulo na verdade não queremos retornar um nulo, mas sim dizer algo mais. No exemplo acima queremos dizer que não encontramos nenhum histórico! O que é bem diferente de nulo.

O grande problema é que ao definir uma função que retorna um valor especial, seja ele nulo, 0, -1 ou qualquer outro número mágico, estamos assumindo que quem utilizar esse código vai saber e lembrar disso sempre que usar a nossa função. Por exemplo:

[1,2,3].index(2)
# => 1
[1,2,3].index(5)
# => nil

Em ruby, ao chamar o método index com um valor inexistente, nil é retornado. Dependendo do contexto, pode até fazer sentido já que nil é considerado falso em uma checagem de booleano. Mas de novo, obrigamos a pessoa que utiliza o método a lembrar disso, sempre!

Nada é alguma coisa

Ao invés de retornar um valor especial, podemos ser mais explícitos e diminuir a responsabilidade de quem vai usar nosso código. Considerando o exemplo inicial, vamos imaginar agora dois cenários:

  1. Dado o email de um usuário, quando nenhum histórico for encontrado, então uma mensagem de erro deve ser exibida na tela.
  2. Dado o email de um usuário, quando nenhum histórico for encontrado, então nenhuma informação deve ser exibida.

No primeiro cenário podemos melhorar o nosso código utilizando uma exceção:

public HistoricoDeCompras buscaHistoricoDeCompras(String email) {
  RespostaHttp resposta = servicoDeCompras.obterHistorico(email);
  if (resposta.getStatus().sucesso() ) {
    return converterParaHistoricoDeCompras(resposta.getConteudo());
  } else {
    throw new HistoricoNaoEncontrado("Histórico inexistenete para o email: " + email);
  }
}

Assim, a pessoa que utilizar o código vai saber que precisa tratar a exceção HistoricoNaoEncontrado e, seguindo o cenário, vai exibir a mensagem de error quando ela for lançada.

Já no segundo cenário, quando basta não exibir nenhum informação na tela, podemos utilizar o Padrão de Projeto Null Object. Um Null Object é simplesmente uma representação de um objeto que não possui nenhuma informação, mas que também não retorna nulo ou lança exceções.

Por exemplo, supondo que a classe HistoricoDeCompras possua uma lista de Compras, podemos criar um Null Object para ela que possua o mesmo tipo, através de herança, e que tenha uma lista de Compras válida, mas vazia:

public class HistoricoDeCompras {
  private List<Compras> compras;
  ...
}

public class HistoricoDeComprasNulo extends HistoricoDeCompras {
  private List<Compras> compras = new ArrayList();
  ...
}

Assim, ao retornar o HistoricoDeComprasNulo, não precisamos obrigar a pessoa a lembrar nenhum detalhe de implementação, o código continua funcional.

public HistoricoDeCompras buscaHistoricoDeCompras(String email) {
  RespostaHttp resposta = servicoDeCompras.obterHistorico(email);
  if (resposta.getStatus().sucesso() ) {
    return converterParaHistoricoDeCompras(resposta.getConteudo());
  } else {
    return new HistoricoDeComprasNulo();
  }
}

Em Scala, Java 8 e várias outras linguagens temos a nossa disposição valores opcionais. Com eles deixamos a cargo de quem utiliza o método lidar com a falta de informação, mas sem precisar retornar um valor mágico.

var helloWorld: Option[String] = Some("Hello World!")
println(helloWorld.getOrElse("Hello who?"))
// Hello World!
var helloWorld: Option[String] = None
println(helloWorld.getOrElse("Hello who?"))
// Hello who?

Valores opcionais permitem definir o que fazer quando o dado não está presente. No exemplo acima, em Scala, utilizamos Some para definir um valor válido e None para um valor inexistente. Ao utilizar o método getOrElse podemos definir o que será retornado caso nada seja encontrado.

Esse post do Rodrigo Turini explica com mais detalhes como utilizar valores opcionais no Java 8: http://blog.caelum.com.br/chega-de-nullpointerexception-trabalhe-com-o-java-util-optional/.

Pode parecer besteira ter essa trabalheira a mais só para não retornar null, mas acredite, o seu “eu do futuro” vai te agradecer se você parar de fazer isso!

Não se esqueça de conferir o links do post para se aprofundar mais no assunto.

Compartilhe o texto com seus colegas para evitar que o “eu do futuro” deles também sofra com esses problemas e continue acompanhando os posts da série [Refatorando tudo!] para melhorar ainda mais seu código!

Como você organiza seu código?

Muitas práticas de programação tem como objetivo principal facilitar a leitura do programa, uma delas é a organização de código. Toda linguagem tem alguma forma de empacotar e dividir a lógica de negócio, mas que ao final não fazem tanta diferença assim na execução do programa.

Apesar de parecer ser uma atividade simples, organizar o código é algo que eu nunca havia pensado com muitos detalhes antes, até que um amigo me apontou para um artigo que apresenta quatro estratégias para organizar seu código.

Na grande maioria dos códigos em que trabalhei, a divisão mais comum é a por tipo, que organiza os pacotes baseado na função que cada classe desempenha. Aplicações Rails por exemplo são automaticamente geradas seguindo esse paradigma.

Captura de Tela 2016-05-21 às 6.56.39 AM

Todos os modelos ficam em uma pasta dedicada, todos os arquivos de configuração ficam em outra. No entanto o que isso te diz sobre o domínio da sua aplicação? Será que a organização das pastas deveria te dizer algo sobre o domínio? Recomendo assistir a apresentação Architecture the Lost Years, de Bob Martin, para ver mais detalhes sobre esse ponto.

Não é que essa organização esteja errada, o programa continuará funcionando normalmente e desenvolvedores conseguem entender a arquitetura e navegar pelo o código. O problema é pensar que essa é a única maneira de organização!

Na maioria dos projetos com frameworks Java que já vi é comum encontrar divisão por pacotes que não agrega nenhuma informação lógica – mesmo não sendo frameworks com convenções tão forte como Rails.

Por exemplo, todas as classes ficam dentro do pacote br.com.empresa.nomeDoProjeto, o único conteúdo do pacote br é o pacote com, que também tem como único conteúdo o pacote empresa… até que, depois de vários pacotes “vazios” chegamos ao código real.

O problema da má organização de pacotes é que isso pode forçar a complicar mais os nomes das classes. Tanto que existe um jogo Java: Real or Not? onde precisamos escolher qual classe é real do framework Spring entre outras duas que são inventadas.

Mesmo em projetos que usam frameworks com forte convenção, como Rails, é possível adaptar o código para que ele seja organizado de uma maneira diferente, como mostrado no post Baking a Cake with Rails.

Se você prefere organizar pacotes por componentes, por camadas ou qualquer outra forma, é possível! O objetivo é tornar a arquitetura e design mais explícitos, não seguir cegamente um framework, como descrito no post Screaming Architecture.

Como falei no começo, organização de código é algo que só comecei a me preocupar de verdade recentemente, mas já pude ver que, ao utilizar uma abordagem de divisão por componente o código fica bem mais conciso e fácil de navegar.

E você, já tinha parado pra pensar em como organiza seu código antes? Qual sua maneira preferida?

Ebook grátis: Primeiros passos com Padrões de Projeto

Como desenvolvedores, nós enfrentamos vários problemas para lidar com a informação nos sistemas: estruturar e armazenar as informações, transformar dados para que possam ser lidos pelos usuário, agrupar dados de diferentes sistemas etc. Assim, desenhar um sistema e sua arquitetura nem sempre é uma tarefa simples e direta. Além disso, conforme aprendemos mais sobre o domínio e a maneira como o sistema é utilizado, percebemos que o desenho inicial não será suficiente e sentimos a necessidade de modificá-lo.

No entanto, a maioria dos desafios que encontramos não é único. Certamente o domínio do sistema pode ser diferente, mas a essência dos problemas continua a mesma: como lidar com informação. Assim surgiu a ideia dos Padrões de Projeto, como uma base coletiva de conhecimento com soluções para esses “problemas comuns” que encontramos todos os dias.

Ao longo do livro vamos ver como se deu o surgimento dos Padrões de Projeto e como eles foram adaptados para o desenvolvimento de software. Em seguida vamos analisar características do paradigma Orientado a Objetos e como eles buscam simplificar o desenvolvimento de aplicações. Por fim, serão explorados dois exemplos de padrões voltados para o desenvolvimento Orientado a Objetos, para exemplificar como eles podem ajudar na manutenção de sistemas.

O ebook pode ser baixado de graça em leanpub.com. Compartilhe com suas amigas e amigos!

Cuidado ao cortar sua arquitetura

Quando estamos modelando o nosso sistema, é bem comum pensarmos em camadas. Talvez um dos modelos de maior sucesso no mundo do desenvolvimento (web ou não) é o MVC. A separação em camadas é bem interessante e facilita bastante a separação de responsabilidades e definição de limites.

No entanto, por mais lógico que pareça separar o desenvolvimento por camadas, dificilmente ajuda. Imagine a seguinte situação: uma nova funcionalidade precisa ser desenvolvida, pra simplificar pense num CRUD qualquer. Como você dividiria essa tarefa entre as pessoas da sua equipe?

Cortes horizontais

Dividir a tarefa em camadas, onde um par vai trabalhar no modelo (provavelmente mudando o banco de dados) e outro par fazendo a parte de views (fazendo algo na interface), pode parecer a decisão correta, afinal você pode colocar o especialista em banco de dados pra fazer a parte de modelos e outra pessoa que detona em CSS pra construir uma boa interface.

O problema dessa divisão é que ela exige uma sincronia muito grande entre a equipe. E (aqui vem um dos piores males do desenvolvimento de software) abre um espaço gigantesco para suposições.

O par que está desenvolvendo o back-end precisa construir tudo o que o par que vai trabalhar na interface vai precisar. E se a comunicação não acontecer, muita coisa pode ser feita a mais ou a menos. Esse é o maior perigo quando são feitos cortes horizontais na arquitetura.

Cortes Verticais

Agora imagine que, ao implementar a mesma funcionalidade de CRUD, vamos dividir as tarefas em ações. Então um par faz a parte de criação, por exemplo, e outro faz a visualização.

Obviamente a comunicação precisa acontecer para garantir que tudo seja feito de maneira homogênea, os nomes das tabelas são os mesmos, as views seguem os mesmos estilos, etc. Mas a comunicação sempre precisa acontecer, esse é o real segredo de projetos bem sucedidos!

No entanto, o par que desenvolve a visualização das entidades sabe exatamente o que vai ser necessário nos modelos para que as views funcionem. Assim, minimizamos o espaço para suposições, pois cada par pode implementar exatamente o necessário, nem mais nem menos.

O “objeto” bom cidadão (Good Citizenship)

Um tópico bem falado quando o assunto é design de software é sobre quebra de encapsulamento e como projetar bem as classes para que elas sejam altamente coesas (tenha um objetivo bem definido) e pouco acopladas (dependam de poucos objetos para funcionar).

Quando se fala sobre relacionamentos entre objetos, um bom tópico para ser estudado é a “boa cidadania” ou Good Citizenship.

Good Citizenship

Um livro que aborda bem o assunto é “The Productive Programmer” [1], de Neal Ford. Nele o autor aborda alguns pontos chaves de maneira bem simples e com exemplos para mostrar o porquê de tamanha preocupação com esses pontos.

Segundo Ford [1], a boa cidadania se refere a objetos que são conscientes do seu estado e atentos aos estados dos outros objetos. Quando se fala em estado de um objeto, significa, de maneira bem simples, as suas variáveis internas.

As variáveis internas de um objetos definem como ele se comporta, geralmente as computações feitas em métodos dos objetos dependem das suas variáveis. Elas também representam o relacionamento do objeto com os outros, indicando as suas dependências. Daí a preocupação com o estado do objeto.

Quebrando a encapsulação

Um dos exemplos mais simples de quebra do princípio da boa cidadania é a quebra de encapsulação. Um post bem conhecido do Paulo Silveira no blog da Caelum [2] fala justamente sobre isso, tanto que o título é “Como não aprender Java e Orientação a Objetos: getters e setters”. Ao invés de copiar e colar o texto aqui, o link está no final do post, leitura obrigatória.

Esse ponto já foi exaustivamente discutido e todo mundo sabe o quão ruim pode ser oferecer getters e setters para todos os atributos dos objetos. Quando vários objetos tem acesso a uma determinada informação fica mais difícil mudar essa informação. E como nós todos sabemos, quase nunca é necessário mudar uma informação em um projeto de software.

Construtores

Esse é um tópico interessante que, eu particularmente, ainda não tinha ouvido falar antes de ler o livro [1]. Como o construtor pode quebrar o princípio da boa cidadania? Como o construtor pode mudar o estado do objeto? É justamente ai que Ford ataca a maneira como os construtores são vistos.

Segundo Ford [1], os construtores são geralmente vistos apenas como um mecansmos para criar objetos, no entanto os construtores dizem para você o que é necessário para que um objeto seja criado com um estado válido. Então qual é o ponto de utilizar um construtor vazio? Se o estado inicial do seu objeto não precisa de nenhuma informação, então tudo bem, mas dificilmente esse é o caso.

Remover os construtores padrões é bem difícil dependendo da linguagem e dos frameworks utilizados. Em C++ existe sempre um construtor padrão que não necessita de argumentos, pode ser por não passar nenhum argumento, ou por utilizar valores padrões para todos eles. Ford [1] também fala sobre a especificação do JavaBeans, que obriga a utilização de um construtor padrão vazio.

Se a linguagem/framework necessitar de construtores padrões é bem difícil que você consiga ir contra, a menos que possa ser substituido por algo diferente. A recomendação é que os construtores padrões sejam tratados evitados pela equipe.

Métodos estáticos

Os métodos estáticos tem como função funcionar como se fossem funções (???), ou seja, eles são utilizados de maneira procedural, sem utilizar nenhum estado ou guardar nenhuma informação dos objetos e de si mesmo. Um bom exemplo são as classes que são responsáveis por cálculos matemáticos, você chama o método sem precisar instanciar ou se preocupar com o que vai acontecer depois.

O grande problema quando um método estático guarda informações é que, diferente de um objeto que é instanciado dentro de um método, o escopo de um método estático é, muitas vezes, todo o programa. Então não existem garantias de controle do estado do objeto, uma vez que qualquer outro lugar do código pode alterá-lo caso necessário.

Esse é o mesmo problema com variáveis globais e com a versão “orientada a objetos” de variáveis globais, também conhecida como Singleton [3]. Um dos problemas com o singleton é que facilmente ele se carrega com várias responsabilidades, pois como você não tem como controlar o estado do objeto, a classe acaba se carregando de responsabilidades para garantir que as computações sejam feitas por completo. E então acaba se relacionando cada vez mais com outros objetos e aumenta a complexidade e no final você fica com outra classe Main e ainda tem a coragem de dizer que usa padrões de projeto orientado a objetos.

No entanto o padrão Singleton não é sinônimo de gambiarra (tá, é sim), o ponto é só lembrar que ele não deve se carregar com responsabilidades. Tente extrair as responsabilidades do objeto utilizando outros padrões de projeto. No entanto esteja atento que é dificil garantir controle sobre o objeto, então pense duas, três ou quantas vezes forem necessárias para se convencer a não utilizar um Singleton.

Utilizando um objeto que não é um bom cidadão

Um último ponto abordado em The Productive Programmer [1] sobre boa cidadania é sobre utilizar um objeto que não é um bom cidadão. O autor cita como exemplo a classe mais odiada do universo: java.util.Calendar.

Além de ferir o senso comum de qualquer pessoa (que os meses NÃO começam em zero), o autor cita como um dos principais problemas o fato de que o Calendar não é capaz de manter controle sobre seu próprio estado. Como por exemplo quando você utiliza os horríveis setters para colocar a data como 31 de Fevereiro. O que deveria acontecer? Um exceção? Não atualizar a informação? Não, é claro que quando você seta a data para 31 de Fevereiro, na verdade você quis dizer 2 de Março =]

A opinião de Ford é: remova o objeto “criminoso”. No caso da Calendar é possível utilizar a biblioteca Joda Time [4] como foi muito bem exemplificado no post do Rafael Lacerda no blog da Caelum [5] falando sobre a dificuldade de cálculos de datas, leitura obrigatória também.

No entanto podemos tomar outras atitudes, caso o objeto não possa ser removido. Talvez a melhor seja encapsular esse objeto dentro de uma outra classe que oferece um maior controle. Um padrão de projeto para isso é o Adapter [6], também conhecido como Wraper. Com ele você pode ter uma instância do objeto criminoso dentro de uma classe que vai oferecer métodos mais amigáveis e garantir que ele se comporte.

Apesar de simples, e até algumas vezes óbvio, é difícil seguir este princípio. No entanto vale a pena “perder” um pouco de tempo pensando sobre o design das suas classes e como elas funcionam atualmente. Como diria Maurício Aniche [7], também da Caelum, você não vai arder no inferno ágil se pensar um pouco antes de escrever testes para esperar o “design emergir”.

Referências:

[1] Neal Ford, The Productive Programmer. O’Reilly, 2008.
[2] Paulo Silveira, Como não aprender Java e Orientação a Objetos: getters e setters. (http://blog.caelum.com.br/nao-aprender-oo-getters-e-setters/)
[3] Marcos Brizeno, Mão na massa: Singleton (https://brizeno.wordpress.com/category/padroes-de-projeto/singleton/)
[4] Joda.org (http://joda-time.sourceforge.net/)
[5] Raphael Lacerda, O eterno problema de calcular a diferença de dias entre duas datas em Java. (http://blog.caelum.com.br/o-eterno-problema-de-calcular-a-diferenca-de-dias-entre-duas-datas-em-java/)
[6] Marcos Brizeno, Mão na massa: Adapter. (https://brizeno.wordpress.com/category/padroes-de-projeto/adapter/)
[7] Maurício Aniche, Métodos ágeis: o que é folclore e o que é real? (http://www.infoq.com/br/presentations/agile-realidade-folclore)

O que é Arquitetura Ágil?

De uma maneira simples e rápida: é a aplicação do pensamento enxuto nas práticas arquiteturais.

O pensamento enxuto é a busca pela maximização do valor através da contínua eliminação de desperdícios. O desenvolvimento ágil é fortemente baseado no pensamento enxuto. Apesar de que as metodologias definem várias práticas, valores, princípios, etc, etc, etc… No fim, tudo o que elas buscam é: maximizar o valor eliminando desperdícios.

Por isso você dificilmente conseguirá achar um time ágil que escreve um Documento de Visão [2], dada a sua grandiosíssima utilidade para o projeto. No entanto, se a equipe trabalha, por exemplo, para órgão públicos, os documentos são extremamente necessário, então, neste caso, os documentos trazem sim valor ao projeto, não devendo ser eliminados.

A Arquitetura Ágil então busca aplicar este pensamento, de cortar desperdícios, e agregar mais valor ao cliente. As definições mais clássicas de arquitetura definem como “a estrutura organizacional dos seus componentes e o relacionamento entre estes” [3]. Com base nisso surgiram várias práticas arquiteturais para realizar esta análise, como modelos, diagramas, entre outros. Mas a verdade é que “a estrutura organizacional dos seus componentes e o relacionamento entre estes”, por si só, não traz nada para o cliente, ou projeto.

Entra então a Arquitetura Ágil. Nos trabalhos mais atuais, quando se fala de Arquitetura, é muito comum falar sobre atributos de qualidade [4], ou seja, agora a Arquitetura não é só uma visão da implementação, ela também traz valor ao projeto, ao buscar alcançar determinados atributos de qualidade, e isso sim traz um real valor.

Um bom exemplo de atributo de qualidade é o tempo de resposta para criação de um relatório. Como poderíamos destacar que o tempo de resposta para gerar um certo conjunto de relatórios, em determinada situação, seja igual a 10 segundos, por exemplo? A equipe não consegue descrever uma estória sobre isso, pois essa é uma preocupação constante no projeto e não algo que vai ser testado e implementado para ser feito o deploy.

A Arquitetura Ágil vai então propor atividades que tragam a atenção do time para esta tarefa. Para este exemplo, poderia ser utilizado a prática de Landing Zones [5], onde seria definido valores mínimo, ideal e máximo para o tempo de criação do relatório. Assim a equipe poderia escrever um teste que, ao quebrar estes limites alertaria um problema.

Existem ainda vários outros pontos de discussão, como onde encaixar a arquitetura, se é preciso realmente uma preocupação com a arquitetura, quem é o arquiteto, ou “Quem  precisa de um arquiteto?” [6]

Se você quer saber mais sobre Arquitetura, compareça a JavaCE Comunity Conference (http://conference.javace.org/) e vote no meu LT (http://call4paperz.com/events/javace-conference-lightning-talk/proposals/388) para ser um dos palestrantes no evento.

[1] Lean Thinking. http://www.lean.org.br/o_que_e.aspx

[2] Documento de visão. http://pt.wikipedia.org/wiki/Documento_de_visão

[3] VAROTO, A. C. Visões em arquitetura de software. Dissertação de Mestrado, Universidade de São Paulo, São Paulo, SP, 2002.

[4] SHARIFLOO, A. A.; et al. Embedding Architectural Practices into Extreme Programming . 19th Australian Conference on Software Engineering , 2008.

[5] GUERRA, E. Práticas para lidar com Arquitetura em ambientes ágeis. MundoJ, Nov/Dez, 2011

[6] FOWLER, M. Who Needs an Architect? IEEE Software, Jul/Ago, 2003