Em Janeiro (sim, já faz um tempinho) a MundoJava publicou uma edição comemorativa em homenagem aos 15 anos de uma das literaturas mais importantes da ciência do desenvolvimento de software: “Design Patterns – Elements os Reusable Object-Oriented Software” de Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides (ou simplesmente Gang of Four – GoF).
Nesta edição, meu artigo entitulado “Patterns no Desenvolvimento de Software Moderno” é um apanhado de conceitos sobre alguns patterns revisitados, esquecidos e novos na nossa caixa de ferramentas. Quem viu algumas palestras minhas em 2009 sabe do que estou falando: equipes ágeis estão pecando em algumas práticas OO, mesmo usando bons testes automatizados. Tem muita equipe gerando monstros monolíticos orientados a testes. Como é costume aqui no Débito Técnico, segue o chorinho do artigo:
O Design Moderno visa a Testabilidade
A maior diferença que destacamos sobre a orientação a objetos comparada com o paradigma procedural é o encapsulamento. Ao contrário do que tínhamos no modelo procedural, um design orientado a objetos não é um conjunto de algoritmos encadeados no estilo “entrada-processamento-saída”. Objetos possuem estado e trocam estímulos através de mensagens. As mensagens que o objeto responde fazem parte da sua interface pública. Como disse, uma mensagem pode ser uma simples chamada de método, assim como um estímulo remoto, assíncrono que veio de uma fila que você nem sabe qual é. Objetos possuem encapsulamento forte, e os objetivos do sistema são cumpridos com a colaboração entre eles através da troca de mensagens.
Dado esta característica dos objetos, podemos facilmente testar sua implementação através de testes de unidade. Eu me lembro muito bem quando meu professor de lógica de programação falou que um compilador só consegue detectar erros de sintaxe do meu programa, mas não erros de lógica. “A sintaxe pode ser checada pelo computador, mas não a lógica”, dizia ele. Eu creio que atualmente nós podemos desafiar esse conceito usando testes automatizados e práticas de desenvolvimento orientado a testes (TDD). É possível sim validarmos a lógica de um programa assim como o compilador (ou interpretador) faz com a sintaxe.
É uma premissa fundamental que os objetos tenham somente uma responsabilidade, assim como é importante que eles tenham um encapsulamento que proteja seu estado. Se um objeto tem uma interface pública e esta pode ser utilizada por qualquer outro componente, temos todo ferramental para concentrar testes nessa interface, e assim, conseguimos validar deterministicamente se a lógica do componente funciona.Além disso, se escrevermos os testes antes da implementação teremos uma descrição muito clara da interface necessária. Nosso componente será simples, pois atenderá somente aquilo que nosso teste exige. Desenvolver a aplicação orientada a teste (TDD) é uma das melhores ferramentas para ter um bom design, além de nos dar segurança para evoluirmos o design refatorando sempre que necessário. Ter bons testes tira o nosso medo em mexer naquelas partes obscuras do código. Refatoração só existe de forma segura com testes.
Muitos desenvolvedores questionam como testes podem melhorar o design. Ora, a resposta é bem simples, e está relacionada com os conceitos defendidos neste artigo como coesão, acoplamento e dependências: se um design a priori é difícil de testar isoladamente (com testes unitários) consequentemente é um design acoplado e difícil de manter. A facilidade de testar os componentes é proporcional à qualidade do seu design.
É muito interessante que o Erich Gama na entrevista desta edição mencionou que a Injeção de Dependências (DI) seria um padrão digno de entrar na obra do GOF. As soluções atuais de DI garantem uma melhor testabilidade, pois todas as ligações são feitas externamente à inteligência do próprio componente, permitindo o uso de Mocks e Stubs para simular o comportamento dessas dependências durante um teste de unidade.
Tenho discutido muito sobre design de software com várias pessoas e vejo que a falta de testes é uma das piores faltas em design. É melhor ter um design ruim com testes do que ter um design bom sem testes. O design ruim com testes você pode com segurança refatorar para que ele se torne um bom design. Com um design bom sem testes você não tem nada! Concluindo, um bom design inclui testes.
No ano de 2009 eu tirei “férias” da MundoJava. Minha coluna MundoOO precisava de novas idéias e eu mesmo já estava com assuntos esgotados. Para o ano de 2010 estarei escrevendo uma outra coluna chamada MundoAgile, onde vou compartilhar sobre meu dia-a-dia como Agile Coach, e as dificuldades que tenho nas minhas implantações Scrum e XP. Espero que gostem!
Realmente interessante o artigo. Nunca tinha me interessado por TDD, porém no curso de CSM ficou nítida a sua importância, já estou me aprofundando no assunto ..
Boa, mas não concordo com isso aqui, não: “É melhor ter um design ruim com testes do que ter um design bom sem testes.”
Primeiro porque se o sistema tem design ruim, provavelmente os testes também serão ruins: frágeis (falso negativo), confusos e cegos (passa erro). Argh!
Segundo porque geralmente dá para ir criando novos testes com o tempo.
Isso aconteceu comigo. Peguei um sistema com design ok, mas sem nenhum teste automatizado. Eu seguia mais ou menos o seguinte ciclo para as alterações que fiz nele:
1. Criava alguns testes para capturar o comportamento antigo, mas restritos à parte que eu iria alterar. Pass.
2. Modificava estes testes para capturar o novo comportamento. Fail.
3. Modificava o código principal até Pass.
De vez em quando enforcava o passo 1.
Por outro lado, reconheço que fazer isso é difícil pois o design, mesmo estando ok, não favorecia os testes. Mas nada que um pouco de refactoring criatividade não resolviam.
As explicações relacionadas a design ficaram muito claras. Essa parte do artigo é excelente para quem está começando o estudo de padrões de arquitetura.
Muito útil a citação “CamadasXPartições” e a utilização de Façade para interface entre módulos de um sistema…