Hanami vs o mundo

Destaque

Coloque-se na situação em que você precisa desenvolver um software corp enterprise em Ruby. Qual a primeira coisa que pipoca em sua mente neste momento?

Pois é:

Domain-Driven Design

A hype (olá pessoal de FP!) ideia do Domain-Driven Design (DDD pros chegados), é trazer o contexto de negócio juntinho do camarada que faz o software funcionar. Por que isso é bom? O principal argumento é: entendimento.

Poderia resumir todo o blue book (Domain-Driven Design: Tackling Complexity in the Heart of Software – Eric Evans) com a frase acima. Obviamente que seus problemas começam juntamente com seu objetivo de deixar o conhecimento alinhado entre todos envolvidos na solução. É exatamente por isso que o livro é extenso e detalhado, mas isso é assunto pra outro papo.

Qual a outra coisa que você pensa quando se fala enterprise ? Aha!

Patterns of Enterprise Application Architecture

O livro vermelhão do (Martin) Fowler é sem dúvidas um ponto de partida para falarmos daquele papo maroto de Arquitetura. Foi lindo! Todo mundo queria ser Arquiteto de Software, lembra disso? Velhos tempos hahaha!

Passada essa fase, começamos todos a pensar: o que é arquitetura de software? Com isso, veio aquela conversa de Arquitetura Evolucionária, ideia que prega que a arquitetura do seu software deveria emergir de acordo com o passar do tempo e das necessidades do software em questão.

De certa forma isso deveria ter sido falado lá atrás, no começo do epicentro do Domain-Driven Design e da importância por uma Arquitetura de Software definida, por mais tosca que parecesse ser.

Motivo simples: muito bolovo teria sido evitado e o nível de frustação e demissão regulados.

Arquitetura de Software

Enfim, a tal da Arquitetura tem sua utilidade, apesar de eu ver poucas pessoas definindo legal o que significa isso.

Podemos definir arquitetura como uma camada do seu software que vista pela outra camada é percebida como um blackbox, tendo pontos de comunicação externos definidos e padronizados.

Outra definição válida é de que arquitetura é todo design de software que agrupado representa ambos requisitos funcionais e não funcionais de uma parte do software. É assim que Clements P, et al define arquitetura de software:

As Clements P, et al. point out, all architectures are designs but not all designs are architectures. Designs that need to be bound in order for the system to meet its functional and nonfunctional requirements and goals are architectural in nature.

@IBM – Developer Works: Documenting Software Architecture Part 1

Dito isso, poderíamos entrar na Deep Web dos tipos de arquitetura de software, como Layered Architecture, Ports & Adapters, Clean Architecture, Hexagonal Architecture, CQRS.. opa, esse não!

Many people have been getting confused over what CQRS is. They look at CQRS as being an architecture; it is not.

O próprio senhor Greg Young disse que esse papo de que CQRS é arquitetura é infudado. E ele fala mais:

CQRS is a very simple pattern that enables many opportunities for architecture that may otherwise not exist. CQRS is not eventual consistency, it is not eventing, it is not messaging, it is not having separated models for reading and writing, nor is it using event sourcing.

Olé!

e o Hanami?

hanami-logo

O que um framework web Ruby tem a ver com tudo isso?

Tudo, obviamente!

A ideia geral do Hanami é ser desacoplado. Independente. Rebeldia? Jamais! O papo é que o framework por definição é feito para colaborar e não definir sua arquitetura no pior estilo One Size fits All Architecture.

Só por não definir uma arquitetura ele já possibilita ao desenvolvedor maluco definir sua própria arquitetura. E mais: evolui-la de acordo com suas próprias necessidades. Epa! Isso traz a tona o que eu falei lá em cima sobre Arquitetura Evolucionária.

weMor1k.gif

Legal, né?

Ah, e se eu não quiser definir nada? Quero seguir como uma boa ovelha que sou!? Neste caso o Hanami, mesmo não definindo uma arquitetura, sugere um Hexagonal Architecture out of the box. Sem pensar, você terá camadas bem definidas, isolando seu modelo dos seus adapters de apresentação e integração. Boom!

Talk: Lidando com defeitos em Test-Driven Development

Em Setembro/2014, participei do evento #S7TDD (7 dias com Test-Driven Development). O evento foi muito proveitoso com excelentes reviews dos participantes.

Falei em algumas oportunidades, uma delas abordei como o Test-Driven Development poderia auxiliar no gerenciamento de “guerra”, ou seja, quando as coisas “dão ruim”. Abaixo, o vídeo completo da minha apresentação:

Lidando com defeitos em Test-Driven Development from Hélio Costa e Silva on Vimeo.

Foi uma experiência e tanto participar de um evento online como o #S7TDD. Quem sabe em breve eu não aparece em outro 😉

Planejando no Macro para entender o micro

Finalizei o último post deixando um suspense no ar. Se não viu, recomendo que leia! Pois bem, quando falei sobre planejar no macro e codificar no micro, deixava ali uma ideia útil de fato, muito bem abordada, que quase todo developer já ouviu pelo menos falar o nome e que porém, já foi muito mal utilizada.

A UML, pode ser um aliado forte no seu planejamento macro de uma funcionalidade do software. Calma! Nada de fechar a aba e ir me xingar no twitter. Estou afirmando para você que é desenvolvedor e cria seus próprios testes a esboçar numa visão macro o que planeja fazer e com a UML, você obtém esse rascunho rapidamente.

Porque a UML e não (coloque aqui seu outro método) ?

UML é uma linguagem de modelagem de diagramas e sua simplicidade (no uso simples dela) a torna uma vantagem no esboço de rascunhos macro de seu software. Outro ponto a favor da UML é o shareable que ela permite: mande-me um esboço UML que eu entenderei o que você quer me falar.

Esboço!

Não estou falando para chamar o arquiteto de software para ele criar todo o software em UML e mandar você fazer. Vou repetir: crie você seus esboços para ajudar no seu próprio entendimento do que pretende e como fará os relacionamentos. Geralmente, eu faço micro esboços que resolve uma e somente uma funcionalidade. Após criá-la, consigo ver se fará sentido aquela minha ideia do ponto de vista arquitetônico e daí então, crio meu teste de unidade e sigo adiante.

  • Eu não faço uma funcionalidade e depois ligo com outra que liga com outra;
  • Não guardo estes rascunhos depois que acreditei na solução macro e segui adiante com a micro (Teste de Unidade) ajustando meu design de código, e;
  • Não faço rascunho macro em UML para toda e qualquer nova feature. Apenas naquelas onde as fronteiras da classe alvo do teste estão meio obscuras para mim.

Isto quer dizer que se Order se relaciona com alguns atores (outras entidades/classes puras) e este relacionamento está estranho para mim, eu tenho subir o nível de abstração, saindo do micro e indo para o macro, buscando entender como dar-se-á o futuro relacionamento ou/e se ele faz mesmo sentido naquele contexto.

Tipos de macro-esboço

Geralmente eu apelo para o Diagrama de Objetos por ser algo simples: coloca dentro do retângulo o nome da classe e pronto. Onde ele por ser útil:
a) quando você está procurando quais as fronteiras seu objeto terá; (lembre-se de que todo e qualquer objeto deve ser planejado como se o mesmo fosse uma API provendo serviço através de métodos públicos a outros objetos do mesmo sistema – por isso uso muito o termo fronteira [boundary]).
b) quando você precisa entender se o objeto deverá ser Aggregate Root de algum outro via composição.
c) traçar fluxos possíveis dentro de um conjunto de objetos.

Quando a coisa aperta e eu fico desconfiado das responsabilidades de cada método em objeto(s) em uma feature, eu recorro ao Diagrama de Sequência. Ele torna-se muito útil nos cenários macro-micro (nome horrível), onde estou planejando o macro, mas entrando um pouco no detalhe da responsabilidade (SRP, alguém?) da feature. Ele te ajuda a lembrar sempre da Lei de Demeter com seu Tell Don’t Ask.

Já acabei utilizando raramente o Diagrama de Classe, onde rascunhava um pouco sobre os métodos de uma classe, mas no meu uso ele acabava não agregando na visão macro, por especular a visão micro sem conhecimento de causa.

Porque não usar a UML como documentação de software?

Quer tentar? Vá em frente (e quebre a cara). Eu e alguns amigos já tentamos manter um Diagrama Caso de Uso em um software anos atrás e sinceramente? Totalmente inútil. Precisava de atualização a todo instante; Ficando desatualizado tornava-se um peso a mais e não ajudava no design pois era um punhado de diagrama e não código executável que te prova que sua teoria no código de produção funciona ou não. Imagina você tendo que atualizar todo diagrama de sequência assim que resolve mudar o input ou output de um método. This is madness! – além de ser esforço inútil pelos motivos já citados (não exercita o código de produção).

Concluindo

Diagramas UML, rabiscos, círculos interligados, etc., ajudam você a planejar sua visão macro quando sente-se desconfortável com certa integração entre objetos. São valiosas ferramentas para esboçar sua ideia e reorganizar sua mente antes de seguir para seu próximo Teste de Unidade, que diferente do primeiro, exercita código de produção como ninguém.

Como manter sua suite de testes saudável

taporra

Depois de uma longa pausa por causa da demanda que o #S7TDD me gerou, retorno das sombras para continuar com as polêmicas do Test-first. Hoje, retomo com algo primordial, principal, essencial, o must have em Test-first: nada menos do que ele, a manutenção da suite de testes.

O que é a manutenção da suite?

Partindo do princípio de que sua suite está em constante crescimento, você uma hora ou outra irá se perguntar em como organizar a casa para evitar se perder e não se enrolar com seus testes de unidade.

Para tanto, a manutenção da suite é importante e, quanto mais você praticar Test-first, mais simples e eventual torna-se-á tal manutenção.

E para que preciso saber/fazer isso?

Como nem todo mundo está no nível de Robert Martin (@unclebob), se reorganizar torna-se importante para continuar vendo resultado no Test-First e principalmente, continuar entendendo como o software funciona só de olhar um punhado pequeno de testes de unidade.

Perder o controle da suite é relativamente fácil principalmente quando estamos começando com testes de unidade, pois como muitos questionam: “mas e as junções? você não une as coisas nunca?”. A vantagem do Teste de Unidade mora justamente no cerne apontado nesta frase, mas como tudo tem um viés, você precisa estar atento justamente com a manutenção deste monte de teste de unidade espalhados pelo seu /specs.

Sejamos pragmáticos aqui: sua suite está precisando de uma reorganizada quando você lê um teste e não entende o que aquilo está fazendo dentro de um contexto macro. Deixando a explicação fancy de lado, temos:

  1. Pegue um teste de unidade aleatoriamente;
  2. Leia e veja o que ele implementa (funcionabilidade);
  3. Tente reproduzir aquela situação em um Controller, Command, etc, ou seja, suponha que você viu o teste e deseja criar a funcionalidade numa Action de Controller qualquer;
  4. Ache uma outra funcionalidade que se relacione com essa primeira que você reproduziu;
  5. Leia o teste dela, tente reproduzi-la ligando a primeira funcionalidade na segunda.

Conseguiu entender ambos os testes e relacionar as funcionalidades? Parabéns, sua suite está saudável (por enquanto haha).

Restabelecendo a ordem na casa (dos testes)

A primeira coisa a se fazer caso os steps acima não tenham dado resultado é verificar a anatomia dos testes de unidade. Às vezes, você acumulou duas ou três funcionalidades em um mesmo teste de unidade sem perceber. Ao splitar estes testes em testes específicos para cada situação, a legibilidade irá saltar de um espaguete para algo compreensível. Costumo dizer que esta é a principal dica para quem quer arrumar a legibilidade dos testes.

Supondo que o problema da anatomia está resolvido, podemos melhorar a legibilidade seguindo o Ubiquitous Language descrita por Eric Evans em seu renomado livro Domain-Driven Design cujo já o mencionei aqui algumas vezes no passado. Dar nomes é algo complicado (veja pelas ruas das cidades). O nome é a coisa mais importante do seu código. Isto é o elo entre o código e a vida real, solicitada e falada o tempo todo por alguém de business. Quanto mais você incorporar os nomes que ouve no código, mais natural ficará as reuniões de planejamento/sprint.

Nosso item número três do check-list é o Princípio da Responsabilidade Única (SRP, sigla em inglês). Lê isso: seu método em teste precisa fazer apenas uma coisa. Ele pode entretanto, se relacionar com outros objeto#metódo para complementar sua atividade, pedindo uma espécie de suporte. Pode também delegar atividades para outros objeto#método. Pode inclusive, se passar como parâmetro para outros objeto#método.
Veja a quantidade de recursos em Design Orientado a Objetos que você tem a disposição para não violar a SRP e você aí fazendo 3, 4, 5 steps em um único método sem a menor necessidade. Vai entender, né?

Sério. SRP é TOP 3 não é por acaso. Ela é aqui na minha lista a primeira que trata de código e OOD. As outras duas são mais arquiteturais e convenções do que código na prática.

Acredito que estas três dicas farão o trabalho de reduzir o caos que sua suite possa estar enfrentando enquanto lê isso. Um chute nada estatístico seria 80~90% de problemas resolvidos.

Como evitar on the fly que isso aconteça?

Planeje no macro; Codifique no micro. Não entendeu? Segunda-feira que vem você entenderá o que isso quer dizer.

Destaque

Existe vida após o primeiro teste passar?

listen-to-tests

Conseguiu fazer seu primeiro teste passar. Está orgulhoso de seu novo achievement porém sabe que esse primeiro passo é apenas o início para conseguir mensurar uma evolução no seu processo de desenvolvimento usando Test-first como referência de design de código. Agora, tudo que você precisa é continuar neste ciclo.

O ciclo é importante para evitar se atrapalhar e perder o ritmo neste começo. Fato! Mas, ainda mais importante é ter uma ideia clara do que pretende fazer a seguir.

Saindo um pouco de Test-first, uma coisa legal que você pode fazer é ter uma ToDo list. São aquelas coisas simples onde você organiza suas tarefas. Vai de Remember The Milk até um Trello ou Pivotal Tracker . Obviamente cada um deles tem seus pontos de ataque, mas tangenciei o tema justamente para te alertar que uma mente organizada, ajuda e muito você manter um ritmo. Minha técnica é bem simples: ao final do dia, eu vejo meu progresso no software e estimo um novo ponto de parada para o próximo dia. Quando o dia chega, eu tenho em mente que meu objetivo do dia é “Conseguir enviar um Pedido com sucesso”, por exemplo.

Com a organização em dia, você sabe o que atacar agora. Assim sendo, podemos voltar ao Test-first exclusivamente.

Vida após o primeiro teste com Test-first

Qual a próxima feature importante do seu negócio? Veja, entenda como feature requisitos funcionais e não funcionais, ou seja, pode ser algo não necessariamente explicito pelo chefe/cliente, mas algo que você percebeu fazendo seu teste de unidade com Test-Driven Development. Acredite: isso acontece e muito!

Criar cenários sem final feliz é igualmente importante ao cenário feliz. Se você espera que seu código some dois números, o que acontece se você mandar dois null para ele somar? Ele deveria fazer algo? Se é importante que ele seja resiliente e lide com este tipo de coisa, você deve criar um teste de unidade para este cenário também!

É perfeitamente aceitável criar um teste de unidade que dado dois null ele deverá retornar uma exceção (Exception). O anormal, seria você criar um teste de unidade que não espera que uma exceção seja lançada. Neste caso, você deve esperar um valor de retorno ou que um mock seja chamado.

Muito interessante criar testes que exercitam diferentes cenários e configurações de seus inputs. Por exemplo, numa inscrição, você pode simular o comportamento do método que inscreve pessoas, utilizando pessoas cadastradas, pessoas elegíveis à inscrição e pessoas não elegíveis a inscrição. Desta forma, você teria testes assim:

it “fará a inscrição dado um usuário elegível”

it “não fará a inscrição ao receber um usuário expirado”

it “não fará a inscrição ao receber um usuário inelegível”

Na maioria dos casos, você pode perfeitamente possuir mais de um caso de teste para um mesmo requisito funcional que você pegou para fazer.

O código mudará

Acostume-se. Ele mudará! No seu primeiro teste você não tem nada no seu domínio. Após o primeiro teste, novas coisas surgirão. No segundo teste, você acabará mudando código de produção que afeta o primeiro teste. Isto é ruim? Muito pelo contrário: isto é excelente. Significa que você está literalmente Guided by Tests, pois está criando o código necessário para seu teste passar. Está com isso deixando o Test-first guiar seu design de código.

Problemas irão ocorrer. Em problema, o primeiro passo é não seguir adiante. Mantenha-se aonde está, exatamente no teste que você está com dificuldades. Tente entender o que o teste está lhe dizendo. No mês de agosto inteiro, dediquei os posts a tratar os problemas mais comuns ao tentar seguir adiante com TDD. Leia o Prelúdio da série “Rua sem saída” e veja se eu abordei sua dúvida. Isto te ajudará a voltar aos trilhos (Rails ;P) com Test-first.

Concluindo

Organização mental; Seguir passos palpáveis que dão retorno rápido em modificações; Lembrar-se do Red, Green, Refactor; Efetivamente fazer o teste antes do código de produção, pois o teste te auxilia a guiar o design do código; saber “ouvir” o teste; e, o mais importante: não ter medo de mudar seu código de produção devido a um novo teste de unidade são a chaves necessárias para abrir qualquer caminho travado.

Rua sem saída: não consigo criar meu próximo teste – Parte IV

Seguindo para o último post da série, vou abordar o décimo item da lista inicial. Antes de continuar, veja os primeiros posts da série:

Pro tip: Lembrando que o prelúdio contém tópicos e dicas gerais que servirão para praticamente todos os itens abordados 😉

Demorei tanto pensando numa solução que acabou desistindo e criando o código sem teste. O código de produção funciona (você o testou manualmente), porém ainda não sabe como escreverá (e se) o teste que o validará

WOW! Poderia me estender e enunciar os problemas que isto te causará – isso seria um sermão chato, então sem delongas vou citar apenas um bom motivo pelo qual isto é ruim:

Você precisará testar essa rotina manualmente TODA vez em que mudar o código de QUALQUER PARTE do software

Holy moly! Isso realmente é um bom motivo, concorda? No passado, a cada novo deploy o time precisava testar manualmente todo o site – veja bem: todo o site. A cada deploy então, ficava mais complicado, pois tinha mais coisas a testar. Os bugs não chegaram a ser recorrentes, mas sempre acontecia de uma feature dada como ok quebrar devido a um side-effect maluco.

Aconteceu uma vez d’eu ver um software em produção não Test-Driven rodando. O cenário era muito crítico. Vários bugs recorrentes, horas para achar e resolver um bug sem quebrar o outro lado, etc. Eu simplesmente peguei do zero e criei uma versão test-driven. Eu não sabia das implementações (pois não participei da implementação original), logo não fui infectado pelas más decisões (de design). Comecei o software seguindo Clean Architecture citado pelo Unclebob. Ou seja: nada de persistência; nada de web-tier; nada de nada. Literalmente fui no meu ~/coding/ e rodei um mkdir: mkdir ~/coding/new-project-api. Depois criei um esqueleto de diretórios ultra simples: mkdir ~/coding/new-project-api/tests e mkdir ~/coding/new-project-api/src/$DOMAIN_MODULE.

Disso, abri um arquivo de teste novo dentro de /tests/$DOMAIN_MODULE/ e criei minha primeira especificação com Teste de Unidade. Para os demais, fui perguntando para os outros devs que tinham trabalhado no projeto inicial, perguntando sobre as features do projeto. Fiz minha versão do software, focando totalmente na arquitetura e design. Depois que tinha várias features prontas e funcionando, adicionei um ODM (Object Document Mapper) com MongoDB e simplesmente nada mudou no meu código e aquelas regras de negócio começaram a salvar árvores no Mongo de forma natural e desacoplada. Somente no final eu pensei em adicionar um web-tier para responder as requisições do HTTP Server. AH mas e a integração com o aplicativo mobile? Eles precisam sabers dos endpoints e o que virá; AH, mas e o frontend; AH, mas e meu chefe; Ah, mas e o cliente. Cada caso é um caso diferente, mas posso afirmar que no meu caso tive que lidar com tudo isso e foi bem tranquilo (após resistências de todos os lados). Acho que o pessoal tem medo de mudança, não sei. As dicas sobre isso ficarão para posts futuros – mas se você estiver em encrenca por causa disto, get in touch!

Ok, mas como escrever o teste que validará o que eu fiz? Há pessoas que fazem teste depois. Eu não sou fã disso, me acustomei com o classical TDD como chamam – mas em todo caso se você tiver experiência com Test-first e souber o que está fazendo, não terá grandes problemas em implementar seu teste. Em qualquer outro caso:

  • Como você testa manualmente?

Ignore as interações com interface e outros datasources. Feito isso, você tem praticamente seu teste de unidade mental. Basta passar para teste de unidade não se esquecendo de que mock/stub é seu amigo!

  • Pensei no item 1. mas testar está complicado.

Talvez você tenha acoplado demais sua implementação e aí o teste está te avisando disto. Recomendo ler o Prelúdio da série. Lá tem ideias gerais para quaisquer uma das 10 situações descritas aqui.

  • Meu software é um algoritmo.

Realmente. TDD não vai te ajudar muito. Se você está criando um algoritmo para resolver um problema, TDD não te ajudará em nada. Agora, se este algoritmo está inserido dentro de um Domain Model, Test-first + Test Unit te ajudará a deixar este algoritmo isolado e respondendo apenas por si só.

  • Estava testando uma integração com Paypal, Google APIs, etc.

A depender do que estava fazendo, Test-first para conseguir conectar numa API e resgatar dados simplesmente com o objetivo de sentir a API, não faz qualquer sentido. Novamente TDD é ferramenta de design de software e não de validação/verificação.

Em resumo: fazer um teste de uma parte do software depois é basicamente engenharia reversa sem os relacionamentos entre objetos/métodos. Lembre-se que isto pode custar caro uma vez que seu design sem o teste pode ter ficado horrível e o teste (de|in)testável.

Concluindo

Por fim, esta série chega assim ao seu término. Espero que tenha ajudado alguém a sair de uma rua sem saída ou no bom português, uma encrenca com test-first. Mudança de paradigma ou modo de codificar não é trivial, mas você estando disposto, é possível obter excelentes resultados a curto prazo.

Lembre-se: a curva de aprendizado e prática com Test-first é de pelo menos seis rápidos meses. Isto me lembra quando comecei a usar o Vim para codificar: enquanto eu não larguei mão dos outros editores, não consegui aprender direito a ferramenta.

Faltou alguma Rua sem Saída que gostaria que eu comentasse? Avisa aí 😉

Dúvidas, desesperos, e bênçãos via comentários nos tópicos ou como sempre, via e-mail.