Você está na página 1de 717

Princpios, Padres

e Prticas

geis

em

Robert C. Martin
Micah Martin
Prefcio de Chris Sells

Os autOres
Robert C. Martin (Tio Bob) fundador e presidente da Object Mentor, Inc., em Gurnee,
Illinois, EUA, uma empresa internacional que oferece consultoria em aprimoramento de
processos, em projeto de software orientado a objetos, treinamento e servios de desenvolvimento de habilidades para grandes empresas do mundo todo. Tambm autor dos livros
Designing Object Oriented C++ Applications Using the Booch Method e Agile Software
Development Principles, Patterns, and Practices (ambos da Prentice Hall), do livro UML
for Java Programming (Addison-Wesley) e foi o editor-chefe do C++ Journal, de 1996 a
1999. destacado palestrante em conferncias internacionais e em feiras profissionais.
Micah Martin trabalha na Object Mentor como desenvolvedor, consultor e conselheiro
sobre assuntos que variam dos princpios e padres orientados a objetos s prticas de
desenvolvimento de software gil. Micah cocriador e desenvolvedor-chefe do projeto de
cdigo-fonte aberto FitNesse. Tambm autor e palestrante.

M379p

Martin, Robert C.
Princpios, padres e prticas geis em C# [recurso
eletrnico] / Robert C. Martin, Micah Martin ; traduo: Joo
Eduardo Nbrega Tortello ; reviso tcnica: Daniel Antonio
Callegari. Dados eletrnicos. Porto Alegre : Bookman,
2011.
Editado tambm como livro impresso em 2011.
ISBN 978-85-7780-842-7
1. Computao. 2. Desenvolvimento de programas C#.
I. Martin, Micah. II. Ttulo.
CDU 004.413

Catalogao na publicao: Ana Paula M. Magnus CRB 10/2052

Robert C. Martin
Micah Martin

Princpios, Padres
e Prticas

geis

em

Traduo:

Joo Eduardo Nbrega Tortello


Reviso tcnica:

Daniel Antonio Callegari

Doutor em Cincia da Computao


Professor da PUC-RS e profissional certificado Microsoft
Verso impressa
desta obra: 2011

2011

Obra originalmente publicada sob o ttulo


Agile Principles, Patterns, and Practices in C#, 1st Edition
ISBN 0131857258 / 9780131857254
Authorized translation from the English language edition, entitled AGILE PRINCIPLES,
PATTERNS, AND PRACTICES IN C#, 1st Edition, by MARTIN, ROBERT C.; MARTIN, MICAH,
published by Pearson Education,Inc., publishing as Prentice Hall, Copyright 2007. All
rights reserved. No part of this book may be reproduced or transmitted in any form or by any
means, electronic or mechanical, including photocopying, recording or by any information
storage retrieval system, without permission from Pearson Education,Inc.
Portuguese language edition published by Bookman Companhia Editora Ltda, a Division of
Artmed Editora SA, Copyright 2011
Traduo autorizada a partir do original em lngua inglesa da obra intitulada AGILE
PRINCIPLES, PATTERNS, AND PRACTICES IN C#, 1 Edio, autoria de MARTIN, ROBERT
C.; MARTIN, MICAH, publicado por Pearson Education, Inc., sob o selo Prentice Hall,
Copyright 2007. Todos os direitos reservados. Este livro no poder ser reproduzido
nem em parte nem na ntegra, nem ter partes ou sua ntegra armazenado em qualquer
meio, seja mecnico ou eletrnico, inclusive fotoreprografao, sem permisso da Pearson
Education,Inc.
A edio em lngua portuguesa desta obra publicada por Bookman Companhia Editora Ltda,
uma Diviso de Artmed Editora SA, Copyright 2011
Capa: Rogrio Grilho, arte sobre capa original
Preparao de original: Sandro Waldez Andretta
Editora Snior Bookman: Arysinha Jacques Affonso
Editora responsvel por esta obra: Elisa Etzberger Viali
Projeto e editorao: Techbooks

Reservados todos os direitos de publicao, em lngua portuguesa,


ARTMED EDITORA S.A.
(BOOKMAN COMPANHIA EDITORA uma diviso da ARTMED EDITORA S.A.)
Av. Jernimo de Ornelas, 670 - Santana
90040-340 Porto Alegre RS
Fone (51) 3027-7000 Fax (51) 3027-7070
proibida a duplicao ou reproduo deste volume, no todo ou em parte, sob
quaisquer formas ou por quaisquer meios (eletrnico, mecnico, gravao,
fotocpia, distribuio na Web e outros), sem permisso expressa da Editora.
SO PAuLO
Av. Embaixador Macedo Soares, 10.735 - Pavilho 5 - Cond. Espace Center
Vila Anastcio 05095-035 So Paulo SP
Fone (11) 3665-1100 Fax (11) 3667-1333
SAC 0800 703-3444
IMPRESSO NO BRASIL
PRINTED IN BRAZIL

AGRADECIMENTOS

Lowell Lindstrom, Brian Button, Erik Meade, Mike Hill, Michael Feathers, Jim Newkirk, Micah Martin, Angelique Martin, Susan Rosso, Talisha Jefferson, Ron Jeffries, Kent
Beck, Jeff Langr, David Farber, Bob Koss, James Grenning, Lance Welter, Pascal Roy,
Martin Fowler, John Goodsen, Alan Apt, Paul Hodgetts, Phil Markgraf, Pete McBreen, H.
S. Lahman, Dave Harris, James Kanze, Mark Webster, Chris Biegay, Alan Francis, Jessica
DAmico, Chris Guzikowski, Paul Petralia, Michelle Housley, David Chelimsky, Paul Pagel,
Tim Ottinger, Christoffer Hedgate e Neil Roodyn.
Agradecimentos muito especiais a Grady Booch e Paul Becker por me permitirem
incluir os captulos originalmente escritos para a terceira edio do livro Object-Oriented
Analysis and Design with Applications, de Grady. Um agradecimento especial a Jack
Reeves por gentilmente me permitir reproduzir seu artigo O que projeto de software?.
As maravilhosas e, s vezes, impressionantes ilustraes foram desenhadas por Jennifer Kohnke e por minha filha, Angela Brooks.

APRESENTAO

Em meu primeiro emprego temporrio como programador profissional, fui contratado para adicionar funcionalidades (features) a um banco de dados de insetos. Isso
aconteceu no Departamento de Fitopatologia do campus agrcola da Universidade de
Minnesota e os insetos eram afdeos, gafanhotos e lagartas. O cdigo tinha sido escrito
por um entomologista que havia aprendido dBase apenas o suficiente para criar seu
primeiro formulrio e que depois o duplicou no restante do aplicativo. medida que
eu adicionava funcionalidades, consolidava o mximo de funcionalidade possvel para
que as correes dos erros (no cdigo) pudessem ser aplicadas em um nico lugar, bem
como as melhorias e assim por diante. Isso me custou todo o vero, mas no final eu
havia dobrado a funcionalidade, ao passo que tinha diminudo o tamanho do cdigo
pela metade.
Muitos anos depois, eu e um amigo decidimos programar algo em conjunto (era
uma implementao de IDispatch ou IMoniker, ambos populares entre ns na poca). Ora
eu digitava enquanto ele me observava e dizia onde havia algo errado, ora ele assumia o
teclado e eu me intrometia at retomar o controle. Isso durou vrias horas e foi uma das
experincias de codificao mais agradveis que j tive.
Pouco tempo depois, meu amigo me contratou como principal arquiteto da recm-formada diviso de software de sua empresa. Em muitas ocasies, como parte de
meu trabalho de arquitetura, escrevia o cdigo-cliente de objetos que eu gostaria que
existissem e o passava aos engenheiros, que o implementavam at que o cliente funcionasse.
Assim como muitas crianas que aprenderam tcnicas prticas no banco traseiro
de um Chevy 57, antes que a educao sexual se tornasse parte do currculo padro das
escolas, acredito que minhas aventuras com vrios aspectos das metodologias de desenvolvimento gil no so nicas. Em geral, minhas experincias com mtodos geis, como
refatorao, programao em pares e desenvolvimento guiado por testes, foram bem-sucedidas, mesmo que eu no soubesse exatamente o que estava fazendo. Evidentemente, havia referncias sobre metodologia gil disponveis, mas assim como me recusei a aprender
como convidar Suzy para a reunio danante em edies antigas da National Geographic,
queria que minhas tecnologias geis fossem adequadas para meu grupo; isto , para o
.NET. Usando .NET (mesmo afirmando claramente que .NET no melhor do que Java
em muitos casos), Robert est falando minha lngua, como nossos professores da escola
que se davam ao trabalho de aprender a nossa gria, sabendo que a mensagem era mais
importante do que o meio.
No entanto, no se tratava apenas de .NET; eu queria que minha primeira vez fosse
suave, que comeasse lentamente sem me assustar, mas que tambm me apresentasse a
todas as coisas boas. E foi exatamente isso que Robert Tio Bob Martin fez neste livro.
Seus captulos introdutrios expem os fundamentos do movimento gil sem perder para
o Scrum ou para a Programao Extrema ou qualquer das outras metodologias geis, permitindo que o leitor combine os tomos para formar as molculas que o agradem. Ainda
melhor (e sem dvida minha parte favorita do estilo de Robert) quando ele mostra essas
tcnicas em ao, abordando um problema conforme seria apresentado em um ambiente

viii APRESENTAO
real e o examinando passo a passo, mostrando os erros e deslizes, e como o fato de aplicar
as tcnicas que ele defende o leva de volta para um terreno seguro.
No sei se o mundo que Robert descreve neste livro existe de verdade; vi apenas
relances dele em minha vida. Entretanto, fica claro que todas as crianas bacanas esto
fazendo isso. Pense no Tio Bob como a sua Dra. Ruth do mundo gil, cujo nico objetivo
: se for fazer algo, que seja bem feito e que todos se divirtam.
Chris Sells

PREFCIO

Mas, Bob, voc disse que terminaria o livro no ano passado.


Claudia Frers, UML World, 1999

Introduo de Bob
Passaram-se sete anos desde a legtima reclamao de Claudia, mas acho que compensei o
atraso. Publicar trs livros um a cada dois anos, enquanto tocava uma empresa de consultoria e produzia muito cdigo, dava treinamento, aconselhamento, palestras e escrevia
artigos, colunas e blogs , sem falar em construir uma famlia e usufruir da parentela,
pode ser um grande desafio. Mas eu amo isso.
Desenvolvimento gil a capacidade de desenvolver software rapidamente, diante de
requisitos que mudam com rapidez. Para alcanar essa agilidade, precisamos usar prticas que forneam a disciplina e o retorno necessrios. Precisamos empregar princpios de
projeto que mantenham nosso software flexvel e passvel de manuteno, e precisamos
conhecer os padres de projeto que tm se mostrado capazes de equilibrar esses princpios para problemas especficos. Este livro uma tentativa de reunir todos esses trs
conceitos em um todo que funcione.
Esta obra descreve esses princpios, padres e prticas, e demonstra como eles so
aplicados por meio do exame de dezenas de estudos de caso diferentes. O mais importante
que os estudos de caso no so apresentados como trabalhos completos. Em vez disso,
so projetos em andamento. Voc ver os projetistas cometerem enganos e observar
como eles os identificam e os corrigem. Voc ver os projetistas decifrarem enigmas e se
preocuparem com ambiguidades e compromissos. Voc ver o ato do projeto.

Introduo de Micah
No incio de 2005, eu fazia parte de uma pequena equipe de desenvolvimento que comeou
a trabalhar em um aplicativo .NET para ser escrito em C#. Usar prticas de desenvolvimento gil era obrigatrio, o que foi um dos motivos de eu participar. Embora eu j tivesse
usado C#, a maior parte de minha experincia com programao era em Java e C++. Eu
no achava que trabalhar em .NET faria muita diferena; no final das contas, no fez.
Fizemos nossa primeira entrega dois meses depois do incio do projeto. Era uma verso parcial, contendo apenas uma parte de todas as funcionalidades pretendidas, mas era
suficiente para ser utilizvel. E, de fato, foi utilizada. Aps apenas dois meses, a empresa
j colhia os benefcios de nosso desenvolvimento. A administrao estava to entusiasmada que pediu para contratar mais pessoas a fim de iniciar mais projetos.
Como eu havia participado da comunidade gil durante muitos anos, conhecia vrios bons desenvolvedores geis que poderiam nos ajudar. Liguei para eles e pedi para
se juntarem a ns. Nenhum de meus colegas geis ingressou em nossa equipe. Por qu?
Acredito que principalmente porque estvamos desenvolvendo em .NET.

x PREFCIO
Quase todos os desenvolvedores geis tm formao em Java, C++ ou Smalltalk.
Mas programadores .NET geis praticamente no existem. Talvez meus amigos no
tenham me levado a srio quando eu disse que estvamos desenvolvendo software gil
com .NET ou talvez no quisessem se associar a .NET. Esse era um problema significativo.
Tampouco essa foi a primeira evidncia que vi desse problema.
Dar cursos de uma semana sobre vrios tpicos de software me permitiu conhecer
uma amostra bastante representativa de desenvolvedores do mundo todo. Muitos dos
alunos que instru eram programadores .NET e outros tantos eram programadores Java
ou C++. No h uma maneira gentil de dizer isto: de acordo com minha experincia,
programadores .NET em geral so mais fracos do que programadores Java e C++. H
excees, claro. Contudo, com base na observao em minhas aulas, no posso chegar
a outra concluso: os programadores .NET tendem a ser mais fracos nas prticas de
software geis, nos padres de projeto, nos princpios de projeto e assim por diante. Frequentemente, em meus cursos, os programadores .NET nunca tinham ouvido falar desses
conceitos fundamentais. Isso precisa mudar.
A primeira edio deste livro, Agile Software Development: Principles, Patterns,
and Practices, de Robert C. Martin, meu pai, foi publicada no final de 2002 e ganhou o
Jolt Award de 2003. uma obra excelente, celebrado por muitos desenvolvedores. Infelizmente, teve pouco impacto na comunidade .NET. Apesar de o contedo do livro ser
relevante para .NET, poucos programadores .NET o leram.
Espero que esta edio em .NET atue como uma ponte entre .NET e o resto da
comunidade de desenvolvedores. Espero que os programadores a leiam e vejam que
existem maneiras melhores de escrever software. Espero que eles comecem a usar
prticas de software melhores, criem projetos melhores e elevem a qualidade nos aplicativos .NET. Espero que os programadores .NET no sejam mais fracos do que os
outros programadores. Espero que eles alcancem um novo status na comunidade de
software, de tal modo que os desenvolvedores Java fiquem orgulhosos de ingressar em
uma equipe .NET.
Ao longo do processo de escrita deste livro, questionei-me muitas vezes sobre a ideia
de meu nome estar na capa de um texto sobre .NET. Eu no sabia se queria meu nome associado a .NET e todas as conotaes negativas que pareciam advir disso. Mas no posso
mais negar o fato. Sou um programador .NET. No! Um programador .NET gil. E tenho
orgulho disso.

Sobre este livro


Uma breve histria
No incio dos anos 1990, eu (Bob) escrevi Designing Object-Oriented C++ Applications
Using the Booch Method. Esse livro foi uma espcie de obra-prima para mim, e fiquei
muito contente com o resultado e com as vendas.
O texto que voc est lendo comeou como uma segunda edio de Designing, mas
se transformou ao longo do caminho. Nestas pginas permanece pouco do livro original.
Pouco mais do que trs captulos foram mantidos, e foram significativamente alterados. O
objetivo, o esprito e muitas lies do livro so os mesmos. Na dcada seguinte publicao de Designing, aprendi muito sobre projeto e desenvolvimento de software. Esta obra
reflete esse aprendizado.

PREFCIO

xi

Que dcada! Designing foi publicado imediatamente antes de a Internet colidir com
o planeta. Desde ento, o nmero de acrnimos com que temos de lidar duplicou. Temos
EJB, RMI, J2EE, XML, XSLT, HTML, ASP, JSP, ZOPE, SOAP, C# e .NET, assim como padres de projeto, Java, Servlets e servidores de aplicativos. Confesso que foi difcil manter
os captulos deste livro atualizados.
A conexo Booch
Em 1997, eu me aproximei de Grady Booch para ajudar a escrever
a terceira edio de seu bem-sucedido livro, Object-Oriented Analysis and Design with
Applications. Eu j havia trabalhado com Grady em alguns projetos e tinha sido um vido
leitor e colaborador de seus vrios trabalhos, incluindo a UML. Assim, aceitei com alegria
e pedi para meu bom amigo Jim Newkirk ajudar no projeto.
Nos dois anos seguintes, Jim e eu escrevemos vrios captulos do livro de Booch.
Evidentemente, esse esforo me inpediu de trabalhar o quanto gostaria nesta obra, mas
achei que valia a pena colaborar com Booch. Alm disso, na poca, este livro era apenas
uma segunda edio de Designing e meu corao no estava nele. Se fosse para dizer algo,
eu queria que fosse algo novo e diferente.
Infelizmente, o livro de Booch no era para acontecer. difcil encontrar tempo para
escrever um livro durante os horrios normais. Nos entusiasmantes dias da onda pontocom, isso era quase impossvel. Grady ficou ainda mais ocupado com a Rational e com
novas iniciativas, como a Catapulse. Assim, o projeto parou. Por fim, perguntei a Grady
e Addison-Wesley se eu poderia incluir neste livro os captulos que Jim e eu tnhamos
escrito. Eles concordaram gentilmente. Portanto, vrios captulos sobre estudo de caso e
UML vieram dessa fonte.
O impacto da Programao Extrema
No final de 1998, a XP surgiu e desafiou nossas
estimadas crenas sobre desenvolvimento de software. Devemos criar muitos diagramas
UML antes de escrever qualquer cdigo? Ou devemos evitar qualquer tipo de diagrama e
simplesmente escrever muito cdigo? Devemos descrever detalhadamente nosso projeto
em diversos documentos? Ou devemos tentar tornar o cdigo narrativo e expressivo, de
modo que os documentos auxiliares no sejam necessrios? Devemos programar em pares? Devemos escrever testes antes de escrever cdigo de produo? O que devemos fazer?
Essa revoluo veio em um momento oportuno. Da metade para o final dos anos
1990, a Object Mentor estava ajudando diversas empresas nos problemas de projeto e
gerenciamento de projetos de OO. Estvamos ajudando as empresas a fazer seus projetos. Como parte desse apoio, introduzimos gradativamente nas equipes nossas prprias
atitudes e prticas. Infelizmente, essas atitudes e prticas no foram registradas. Em vez
disso, elas eram uma tradio oral que passvamos para nossos clientes.
Em 1998, percebi que precisvamos registrar nosso processo e nossas prticas
para que pudssemos melhor articul-las para nossos clientes. Ento, escrevi muitos
artigos sobre processo no C++ Report.1 Esses artigos erraram o alvo. Eles eram informativos e, em alguns casos, divertidos, mas em vez de codificar as prticas e atitudes
que usvamos em nossos projetos, eram um compromisso inconsciente com valores
que haviam sido adquiridos ao longo de dcadas. Foi necessrio que Kent Beck me
mostrasse isso.

Esses artigos esto disponveis na seo de publicaes do site www.objectmentor.com. Existem quatro
artigos. Os trs primeiros so intitulados Iterative and Incremental Development (I, II, III). O ltimo
intitulado C.O.D.E Culled Object Development process.

xii PREFCIO
A conexo Beck
No final de 1998, ao mesmo tempo em que eu estava preocupado com
a codificao do processo da Object Mentor, me deparei com o trabalho de Kent sobre
Programao Extrema (XP). O trabalho estava espalhado no wiki2 de Ward Cunningham e
misturado com publicaes de muitas outras pessoas. Apesar disso, com algum esforo e
boa vontade, consegui entender o ponto principal sobre o que Kent estava falando. Fiquei
intrigado, mas ctico. Algumas das coisas sobre as quais a XP falava estavam exatamente
de acordo com meu conceito de processo de desenvolvimento. Outras, no entanto, como a
falta de uma etapa de projeto articulada, me deixaram intrigado.
Kent e eu no tnhamos experincias com software muito distintas. Ele era um conhecido consultor de Smalltalk e eu era um conhecido consultor de C++. A comunicao
entre esses dois mundos era difcil. Havia quase um golfo de paradigma kuhniano3 entre
eles.
Sob outras circunstncias, eu nunca teria pedido a Kent para escrever um artigo
para o C++ Report. Mas a congruncia de nosso pensamento sobre processo era capaz
de superar o problema da linguagem. Em fevereiro de 1999, encontrei Kent em Munique,
na conferncia sobre programao orientada a objetos. Ele estava dando uma palestra
sobre XP na sala em frente qual eu estava dando uma palestra sobre princpios de projeto orientado a objetos. Sendo impossvel ouvir essa palestra, procurei Kent na hora do
almoo. Falamos sobre XP e pedi para que ele redigisse um artigo para o C++ Report.
Kent escreveu um artigo excelente, sobre um incidente no qual ele e um colega de trabalho
conseguiram fazer uma alterao de projeto radical em um sistema em funcionamento em
aproximadamente uma hora.
Nos meses seguintes, passei pelo lento processo de destrinchar meus prprios receios sobre a XP. Meu maior medo era adotar um processo no qual no havia etapa explcita de projeto antecipada. A princpio, rejeitei a ideia. No tinha eu a obrigao de ensinar
meus clientes e o setor como um todo que o projeto suficientemente importante para se
perder tempo com ele?
Finalmente, percebi que eu mesmo no tinha praticado essa etapa. Mesmo em todos
os artigos e livros que tinha escrito sobre projeto, diagramas de Booch e diagramas UML,
eu tinha usado cdigo como uma maneira de verificar se os diagramas eram significativos. Em todas as consultorias que fiz para clientes, eu passava uma ou duas horas ajudando-os a desenhar diagramas e os instruindo a explorar esses diagramas com cdigo.
Entendi que, embora as palavras da XP sobre projeto fossem estranhas, em sentido kuhniano4 as prticas que estavam por trs delas me eram familiares.
Meus outros receios sobre a XP eram mais fceis de lidar. Eu sempre tive vontade
secreta de programar com um parceiro. A XP me proporcionou uma maneira de acabar
com o segredo e revelar meu desejo. Refatorao, integrao contnua, cliente in situ: para
mim, tudo isso era muito fcil de aceitar. Era muito parecido com a maneira como eu j
aconselhava meus clientes a trabalhar.

O site http://c2.com/cgi/wiki contm uma grande quantidade de artigos sobre uma variedade imensa de
assuntos. Seus autores chegam s centenas ou milhares. Diz-se que Ward Cunningham poderia provocar
uma revoluo social usando somente algumas linhas de cdigo em Perl.
3
Qualquer trabalho intelectual digno de crdito escrito entre 1995 e 2001 deve usar o termo kuhniano. Ele
se refere ao livro The Structure of Scientific Revolutions, de Thomas S. Kuhn (University of Chicago Press,
1962).
4
Se voc menciona Kuhn duas vezes no artigo, ganha estrelinhas.

PREFCIO

xiii

Uma prtica da XP foi reveladora para mim. O desenvolvimento guiado por testes
(TDD5) parece inofensivo quando voc o ouve pela primeira vez: escreva casos de teste
antes de escrever cdigo de produo. Todo cdigo de produo escrito para fazer os
casos de testes falhos passarem. Eu no estava preparado para as profundas ramificaes
que escrever cdigo dessa maneira teria. Essa prtica transformou completamente minha
maneira de escrever software para melhor.
Assim, no outono de 1999, eu estava convencido de que a Object Mentor deveria adotar a XP como processo de escolha e de que eu deveria abandonar meu desejo de escrever
meu prprio processo. Kent tinha feito um trabalho excelente de enunciar as prticas e o
processo da XP; minhas tentativas eram ineficazes.
.NET
As grandes corporaes esto em guerra para ganhar a sua lealdade. Elas acreditam que, se tiverem a linguagem, tero os programadores e as empresas que empregam
esses programadores.
A primeira batalha dessa guerra foi Java. Java foi a primeira linguagem criada
por uma grande corporao com o objetivo de obter o conhecimento da marca pelo
programador. Foi um enorme sucesso. A linguagem Java se inseriu profundamente na
comunidade do software e , em grande medida, o padro dos modernos aplicativos
multicamada de TI.
A IBM no ficou para trs com o ambiente Eclipse, est capturando um grande segmento do mercado de Java. A consagrada Microsoft tambm reagiu, fornecendo .NET em
geral e C# em particular.
Surpreendentemente, muito difcil diferenciar entre Java e C#. As linguagens so
semanticamente equivalentes e sintaticamente to parecidas que muitos trechos de cdigo so indistinguveis. O que a Microsoft carece em inovao tcnica, ela mais do que
compensa em sua incrvel capacidade de informar-se sobre as novidades e vencer.
A primeira edio deste livro foi escrita usando Java e C++ como linguagem de
codificao. Esta obra foi escrita usando C# e a plataforma .NET. Isso no deve ser
considerado um endosso. No estamos tomando partido nessa guerra. Alis, acho que
a guerra terminar assim que uma linguagem melhor surgir e conquistar a preferncia
dos programadores, ativo to valioso para as corporaes concorrentes.
O motivo para uma verso em .NET deste livro atingir o pblico .NET. Embora os
princpios, padres e prticas deste livro sejam agnsticos quanto linguagem, os estudos de caso no o so. Assim como os programadores .NET ficam mais vontade lendo
estudos de caso em .NET, os programadores, ativo to valioso para as corporaes concorrentes

O diabo est nos detalhes


Este livro contm muito cdigo .NET. Esperamos que voc leia o cdigo cuidadosamente,
pois, em grande medida, ele o assunto do livro. O cdigo a realizao do que este livro
explica.
Esta obra apresenta um padro: uma srie de estudos de caso de tamanhos variados. Alguns so muito pequenos e outros exigem vrios captulos para descrever. Cada
estudo de caso precedido por um material destinado a preparar voc adequadamente,
descrevendo os princpios de projeto orientado a objetos e os padres utilizados nesse
estudo de caso.
5

Kent Beck, TDD, Desenvolvimento Guiado por Testes, (Bookman Editora, 2010).

xiv PREFCIO
O livro comea com uma discusso sobre prticas e processos de desenvolvimento.
Essa discusso pontuada por diversos pequenos estudos de caso e exemplos. Depois,
a obra aborda o assunto do projeto e dos princpios de projeto e, para alguns padres de
projeto, mais princpios de projeto que governam os pacotes e mais padres. Todos esses
assuntos so acompanhados por estudos de caso.
Portanto, prepare-se para ler bastante cdigo e para meditar sobre alguns diagramas
UML. O livro que voc tem em mos muito tcnico e suas lies, assim como o diabo,
esto nos detalhes*.

Organizao
Este livro est organizado em quatro sees e dois apndices.
A Seo I, Desenvolvimento gil, descreve o conceito de desenvolvimento gil. Ela comea
com o Manifesto da Aliana gil, fornece um panorama da Programao Extrema (XP)
e, em seguida, mostra muitos pequenos estudos de caso que esclarecem algumas das
prticas da XP, especialmente aquelas que tm impacto sobre o modo como projetamos e
escrevemos cdigo.
A Seo II, Projeto gil, fala sobre projeto de software orientado a objetos: o que , o
problema e as tcnicas de gerenciamento de complexidade e os princpios do projeto de
classes orientado a objetos. A seo termina com vrios captulos que descrevem um subconjunto prtico da UML.
A Seo III, O estudo de caso da folha de pagamentos, descreve o projeto orientado a objetos e a implementao em C# de um sistema simples de folha de pagamentos em lote.
Os primeiros captulos dessa seo descrevem os padres de projeto que o estudo de caso
encontra. O ltimo captulo o estudo de caso inteiro, o maior e mais completo do livro.
A Seo IV, Empacotando o sistema de folha de pagamentos, comea descrevendo os princpios do projeto de pacotes orientado a objetos e, ento, ilustra esses princpios empacotando as classes da seo anterior de forma incremental. A seo termina com captulos
que descrevem o projeto do banco de dados e da interface do usurio do aplicativo de
folha de pagamentos.
Dois apndices finalizam a obra: o Apndice A, Uma stira de duas empresas, e o Apndice B, o artigo de Jack Reeves, O que software?.

* N. de R.T.: Preferimos manter os diagramas em ingls para facilitar a associao dos conceitos com os elementos presentes nos cdigos-fonte como os nomes das classes, por exemplo. Somente em casos muito
especficos os textos foram traduzidos para melhorar a compreenso.

PREFCIO

xv

Como usar este livro


Se voc desenvolvedor, leia o livro do incio ao fim. Esta obra foi escrita principalmente
para desenvolvedores e contm as informaes necessrias para desenvolver software de
maneira gil. Ler o livro inteiro introduz as prticas, em seguida os princpios, depois os
padres e, por fim, fornece estudos de caso que abordam todo o contedo. Integrar esse
conhecimento o ajudar a fazer seus projetos.
Se voc gerente ou analista de negcios, leia a Seo I. Os Captulos 1 a 6 apresentam uma discusso aprofundada dos princpios e das prticas geis, levando-o dos
requisitos ao planejamento, testes, refatorao e programao. A Seo I fornece orientaes sobre como montar equipes e gerenciar projetos. Ela o ajudar a fazer seus projetos.
Se voc quer aprender UML, leia primeiro os Captulos 13 a 19. Depois, leia todos
os captulos da Seo III. Essa sequncia de leitura lhe dar uma boa base tanto sobre
sintaxe quanto sobre o uso de UML, e tambm o ajudar na converso entre UML e C#.
Se voc quer aprender sobre padres de projeto, leia a Seo II e aprenda primeiro
sobre os princpios de projeto. Depois, leia a Seo III e a Seo IV. Essas sees definem
todos os padres e mostram como us-los em situaes tpicas.
Se voc quer aprender sobre princpios de projeto orientado a objetos, leia a Seo
II, a Seo III e a Seo IV. Os captulos dessas sees descrevem os princpios do projeto
orientado a objetos e mostram como utiliz-los.
Se voc quer aprender sobre mtodos de desenvolvimento gil, leia a Seo I. Essa
seo descreve o desenvolvimento gil, dos requisitos ao planejamento, testes, refatorao
e programao.
Se voc quiser rir um pouco, leia o Apndice A.

SUMRIO

Seo I: Desenvolvimento gil

29

Captulo 1

31

Prticas geis

A Aliana gil
Indivduos e interaes mais que processos e ferramentas
Software em funcionamento mais que documentao abrangente
Colaborao com o cliente mais que negociao de contratos
Resposta a mudanas mais que seguir um plano

Princpios
Concluso
Bibliografia

Captulo 2 Viso geral da Programao Extrema


As prticas da Programao Extrema
Equipe coesa
Histrias de usurio
Ciclos curtos
Testes de aceitao
Programao em pares
Desenvolvimento guiado por testes (TDD Test-Driven Development)
Posse coletiva
Integrao contnua

32
32
33
34
35

35
37
38

39
39
39
40
40
41
41
42
42
43

18

SUMRIO

Ritmo sustentvel
rea de trabalho aberta
Jogo de planejamento
Projeto simples
Refatorao
Metfora

Concluso
Bibliografia

Captulo 3

47
47

Planejamento

Explorao inicial
Melhoramento, diviso e velocidade

Planejamento da entrega
Planejamento da iterao
Definio de pronto
Planejamento de tarefas
Iterao
Monitoramento
Concluso
Bibliografia

Captulo 4 Teste
Desenvolvimento guiado por testes
Exemplo de projeto com testes a priori
Isolamento do teste
Desacoplamento casual

Testes de aceitao
Arquitetura casual
Concluso
Bibliografia

Captulo 5

Refatorao

Um exemplo de refatorao simples: gerao de nmeros primos


Teste de unidade
Refatorao
A releitura final

Concluso
Bibliografia

43
44
44
44
45
46

49
49
50

50
51
51
52
53
53
54
54

55
55
56
57
59

60
62
62
62

63
64
65
66
72

76
76

SUMRIO

Captulo 6

Um episdio de programao

O jogo de boliche
Concluso
Viso geral das regras do boliche

19

77
77
119
120

Seo II: Projeto gil

121

Captulo 7

123

O que projeto gil?

Maus cheiros do projeto


Maus cheiros do projeto os odores do software em putrefao
Rigidez
Fragilidade
Imobilidade
Viscosidade
Complexidade desnecessria
Repetio desnecessria
Opacidade

Por que o software apodrece


O programa Copy
Um cenrio familiar
Projeto gil do programa Copy

Concluso
Bibliografia

Captulo 8

Princpio da Responsabilidade nica (SRP)

Definindo uma responsabilidade


Separando responsabilidades acopladas
Persistncia
Concluso
Bibliografia

Captulo 9

Princpio do Aberto/Fechado (OCP)

Descrio do OCP
O aplicativo Shape
Violando o OCP
Obedecendo ao OCP
Antecipao e estrutura natural
Inserindo os ganchos

123
124
124
124
125
125
125
125
126

126
127
127

131
133
133

135
137
138
138
139
139

141
142
144
144
146
147
148

20

SUMRIO

Usando abstrao para obter fechamento explcito


Usando uma estratgia orientada a dados para obter fechamento

Concluso
Bibliografia

Captulo 10

149
151

152
152

Princpio da Substituio de Liskov (LSP)

Violaes do LSP

153
155

Um exemplo simples
Uma violao mais sutil
Um exemplo real

155
155
161

Fatorar em vez de derivar


Heursticas e convenes
Concluso
Bibliografia

165
168
169
169

Captulo 11

Princpio da Inverso de Dependncia (DIP)

Disposio em camadas

171
172

Inverso de posse
Dependncia de abstraes

173
174

Um exemplo simples de DIP

174

Encontrando a abstrao subjacente

O exemplo Furnace
Concluso
Bibliografia

Captulo 12

Princpio da Segregao de Interface (ISP)

Poluio de interface
Separar clientes significa separar interfaces
Interfaces de classe versus interfaces de objeto
Separao por meio de delegao
Separao por meio de herana mltipla

O exemplo de interface de usurio de caixa eletrnico


Concluso
Bibliografia

Captulo 13 Viso geral da UML para programadores C#


Diagramas de classes
Diagramas de objetos

176
177

179
179

181
181
183
184
185
186

187
193
193

195
198
200

Diagramas de sequncia

200

Diagramas de colaborao

200

SUMRIO

Diagramas de estados
Concluso
Bibliografia

Captulo 14 Trabalhando com diagramas


Por que modelar?
Por que construir modelos de software?
Devemos produzir projetos completos antes de codificar?

21
201
202
202

203
203
204
204

Usando UML de modo eficiente

205

Comunicando-se com os outros


Roteiros
Documentao final
O que manter e o que jogar fora

205
207
207
208

Refinamento iterativo
Comportamento primeiro
Verifique a estrutura
Visualizando o cdigo
Evoluo de diagramas

Quando e como desenhar diagramas


Quando desenhar diagramas e quando parar
Ferramentas CASE
E a documentao?

Concluso

Captulo 15

Diagramas de estados

Eventos especiais
Superestados
Pseudoestados iniciais e finais

Usando diagramas de mquina de estados finitos


Concluso

Diagramas de objetos

Um instantneo no tempo
Objetos ativos
Concluso

Captulo 17

209
211
214
214

215
215
216
217

217

Os fundamentos

Captulo 16

209

Casos de uso

Escrevendo casos de uso


Cursos alternativos
O que mais?

Diagramando casos de uso

219
219
221
222
223

224
225

227
227
229
232

233
233
235
235

235

22

SUMRIO

Concluso
Bibliografia

Captulo 18

236
236

Diagramas de sequncia

Os fundamentos
Objetos, linhas de vida, mensagens e outras mincias
Criao e destruio
Loops simples
Casos e cenrios

Conceitos avanados
Loops e condies
Mensagens que demoram
Mensagens assncronas
Mltiplas threads
Objetos ativos
Enviando mensagens para interfaces

Concluso

Captulo 19

237
238
238
240
240

244
244
245
247
252
252
253

254

Diagramas de classes

Os fundamentos
Classes
Associao
Herana

255
255
255
256
257

Um exemplo de diagrama de classes


Os detalhes
Esteretipos de classe
Classes abstratas
Propriedades
Agregao
Composio
Multiplicidade
Esteretipos de associao
Classes aninhadas
Classes associativas
Qualificadores de associao

Concluso
Bibliografia

Captulo 20

237

258
260
260
261
262
263
264
266
266
267
268
268

269
269

Heursticas e caf

271

A cafeteira eltrica Mark IV Special

271

Especificao
Uma soluo comum, mas horrvel
Abstrao imaginria
Uma soluo melhorada

272
274
277
278

SUMRIO

Implementando o modelo abstrato


As vantagens desse projeto

ExagerOO
Bibliografia

23
282
290

291
304

Seo III: Estudo de caso da folha de pagamentos


Especificao bsica do sistema de folha de pagamentos
Exerccio
Caso de uso 1: Adicionar novo funcionrio
Caso de uso 2: Excluir um funcionrio
Caso de uso 3: Lanar um carto de ponto (Time Card)
Caso de uso 4: Lanar um recibo de venda (Sales Receipt)
Caso de uso 5: Lanar uma taxa de servio do sindicato
Caso de uso 6: Alterar detalhes do funcionrio
Caso de uso 7: Executar a folha de pagamentos de hoje

Captulo 21 COMMAND e ACTIVE OBJECT: versatilidade e multitarefa


Comandos simples
Transaes
Desacoplamento fsico e temporal
Desacoplamento temporal

Mtodo Undo
Objeto ativo
Concluso
Bibliografia

305
306
306
307
307
307
307
308
308
308

309
310
312
313
313

314
314
320
320

Captulo 22 TEMPLATE METHOD e STRATEGY:


herana versus delegao

321

TEMPLATE METHOD

322

Abuso de padro
Bubble Sort

325
325

STRATEGY
Concluso
Bibliografia

Captulo 23
FAADE
MEDIATOR
Concluso
Bibliografia

329
334
334

FAADE e MEDIATOR

335
335
336
339
339

24

SUMRIO

Captulo 24

SINGLETON e MONOSTATE

SINGLETON

342

Benefcios
Custos
SINGLETON em ao

MONOSTATE

Concluso
Bibliografia

347
348
348

353
353

NULL OBJECT

Descrio
Concluso
Bibliografia

Captulo 26

343
344
344

346

Benefcios
Custos
MONOSTATE em ao

Captulo 25

341

355
355
358
358

Estudo de caso da folha de pagamentos: iterao 1

Especificao bsica
Anlise pelos casos de uso
Adicionando funcionrios
Excluindo funcionrios
Lanando cartes de ponto
Lanando recibos de venda
Lanando uma taxa de servio de sindicato
Alterando detalhes do funcionrio
Dia do pagamento

Reflexo: encontrando as abstraes subjacentes


Pagamento de funcionrios
Cronograma de pagamentos
Mtodos de pagamento
Afiliaes

Concluso
Bibliografia

Captulo 27 Estudo de caso da folha de pagamentos:


implementao
Transaes
Adicionando funcionrios
Excluindo funcionrios
Cartes de ponto, recibos de venda e taxas de servio
Alterando funcionrios
O que eu estava fumando?

359
359
360
361
363
363
364
364
365
367

369
369
369
371
371

372
372

373
373
374
379
381
390
400

SUMRIO

Pagando os funcionrios
Pagando funcionrios assalariados
Pagando funcionrios que recebem por hora

Programa principal
O banco de dados
Concluso
Sobre este captulo
Bibliografia

25
404
406
409

421
422
423
423
424

Seo IV: Empacotando o sistema de folha de pagamentos

425

Captulo 28

427

Princpios de projeto de pacotes e componentes

Pacotes e componentes
Princpios da coeso de componentes: granularidade

427
428

Princpio da Equivalncia Reutilizao/Entrega (REP)


Princpio da Reutilizao Comum (CRP)
Princpio do Fechamento Comum (CCP)
Resumo da coeso de componentes

428
430
430
431

Princpios do acoplamento de componentes: estabilidade


Princpio das Dependncias Acclicas (ADP)
Princpio das Dependncias Estveis (SDP)
Princpio das Abstraes Estveis (SAP)

Concluso

Captulo 29

431
432
437
442

446

FACTORY

447

Um problema de dependncia
Tipagem esttica versus dinmica
Fbricas substituveis
Usando fbricas para dispositivos de teste
Importncia das fbricas
Concluso
Bibliografia

449
451
451
452
453
454
454

Captulo 30 Estudo de caso da folha de pagamentos:


anlise do pacote

455

Estrutura de componentes e notao


Aplicando o Princpio do Fechamento Comum (CCP)
Aplicando o Princpio da Equivalncia Reutilizao/Entrega (REP)
Acoplamento e encapsulamento
Mtricas

456
457
459
460
462

26

SUMRIO

Aplicando as mtricas no aplicativo de folha de pagamentos

464

Fbricas de objetos
Reconsiderando os limites da coeso

467
468

A estrutura de empacotamento final


Concluso
Bibliografia

468
472
472

Captulo 31

COMPOSITE

Comandos compostos
Multiplicidade ou nenhuma multiplicidade
Concluso

Captulo 32

OBSERVER: evoluindo para um padro

O relgio digital
O padro OBSERVER
Modelos
Gerenciamento dos princpios do projeto orientado a objetos

Concluso
Bibliografia

474
475
476

477
477
498
499
500

500
501

Captulo 33 ABSTRACT SERVER, ADAPTER e BRIDGE


ABSTRACT SERVER
ADAPTER
A forma de classe de ADAPTER
O problema do modem, ADAPTERs e LSP

Bridge
Concluso
Bibliografia

Captulo 34

473

503
504
505
506
506

510
512
512

PROXY E GATEWAY: gerenciando APIS de terceiros

Proxy
Implementando PROXY
Resumo

Bancos de dados, middleware e outras interfaces de terceiros


TABLE DATA GATEWAY
Teste e TDGs na memria

Usando outros padres com bancos de dados


Concluso
Bibliografia

513
513
518
532

533
535
543

548
549
549

SUMRIO

Captulo 35 VISITOR

27

551

VISITOR
ACYCLIC VISITOR

552
556

Usos de VISITOR

561

DECORATOR
EXTENSION OBJECT
Concluso
Bibliografia

569
575
587
587

Captulo 36

Estado

589

Instrues switch/case aninhadas

590

A varivel de estado de escopo interno


Testando as aes
Custos e benefcios

593
593
593

Tabelas de transio
Usando interpretao de tabela
Custos e benefcios

O padro STATE
STATE versus STRATEGY
Custos e benefcios
O compilador de mquinas de estados (SMC)
Turnstile.cs gerado pelo SMC e outros arquivos de suporte
Custos e benefcios

Classes de aplicativos de mquinas de estados


Diretivas de aplicativo de alto nvel para interfaces grficas com o usurio
Controladores de interao de interface grfica do usurio
Processamento distribudo

Concluso
Bibliografia

Captulo 37 Estudo de caso da folha de pagamentos:


o banco de dados
Construindo o banco de dados
Uma falha no projeto do cdigo
Adicionando um funcionrio
Transaes
Carregando um funcionrio
O que falta?

594
595
596

597
600
600
601
604
609

609
609
611
612

613
613

615
615
616
619
631
637
652

28

SUMRIO

Captulo 38 A interface do usurio do sistema de folha de


pagamentos: MODEL VIEW PRESENTER
A interface
Implementao
Construindo uma janela
A janela Payroll
A inaugurao
Concluso
Bibliografia

Apndice A

Uma stira de duas empresas

Rufus Inc.: Projeto Kickoff


Rupert Industries: Projeto Alpha

Apndice B
ndice

O que software?

653
655
656
666
673
686
687
687

689
689
689

703
715

Seo I

DESENVOLVIMENTO GIL
As interaes humanas so complicadas e nunca so muito claras
e precisas em seus efeitos, mas elas importam mais do que
qualquer outro aspecto do trabalho.
Tom DeMarco e Timothy Lister, Peopleware

rincpios, padres e prticas so importantes, mas so as pessoas que os fazem


funcionar. Como afirma Alistair Cockburn: Processo e tecnologia so um efeito de
segunda ordem no resultado de um projeto. O efeito de primeira ordem so as pessoas.1
No podemos gerenciar equipes de programadores como se eles fossem sistemas
constitudos de componentes conduzidos por um processo. Citando Alistair Cockburn
novamente, as pessoas no so unidades de programao substituveis por meio de um
plugue. Se nossos projetos devem ser bem-sucedidos, precisamos montar equipes colaborativas e auto-organizadas.
As empresas que estimulam a formao de tais equipes tero uma vantagem competitiva enorme sobre aquelas que conservam a viso de que uma organizao de desenvolvimento de software nada mais do que um monte de pessoinhas retorcidas e parecidas.
Uma equipe que tem um bom relacionamento a fora de desenvolvimento de software
mais poderosa que existe.

Comunicao particular.

Captulo 1

PRTICAS GEIS
O galo cata-vento na torre da igreja, embora feito de ferro,
logo seria quebrado pelo vento da tempestade
se no entendesse a nobre arte de girar a cada sopro.
Heinrich Heine

uitos de ns j sobrevivemos ao pesadelo de participar de um projeto sem prticas


para servirem de guias. A falta de prticas eficazes leva imprevisibilidade, a erros
repetidos e a esforo em vo. Os clientes ficam desapontados com cronogramas atrasados,
aumento de oramento e qualidade insatisfatria. Os desenvolvedores ficam desanimados
por trabalharem durante horas para produzir software deficiente.
Depois de vivenciar tal fiasco, ficamos com receio de repetir a experincia. Nossos
medos nos motivam a criar um processo que restrinja nossas atividades e exija certos
resultados e artefatos. Tiramos essas restries e resultados da experincia passada, escolhendo o que pareceu funcionar em projetos anteriores. Nossa esperana que funcione
novamente e acabe com nossos temores.
No entanto, os projetos no so to simples a ponto de algumas restries e artefatos conseguirem evitar erros de forma confivel. medida que os erros continuam a
ser cometidos, os diagnosticamos e colocamos em vigor ainda mais restries e artefatos
para evit-los no futuro. Depois de muitas experincias assim, acabamos sobrecarregados com um processo enorme e desajeitado, que nos impede de fazer os projetos.
Um processo grande e desajeitado pode criar exatamente os mesmos problemas que
deveria evitar. Ele pode diminuir a velocidade da equipe a ponto de atrasar os cronogramas e estourar os oramentos. Tambm pode reduzir a rapidez de resposta da equipe
a ponto de ela sempre criar o produto errado. Infelizmente, isso leva muitas equipes a
acreditar que no tm processo suficiente. Assim, em uma espcie de inflao de processo
descontrolada, elas tornam seus processos ainda maiores.

32

DESENVOLVIMENTO GIL

Inflao de processo descontrolada uma boa descrio para o que acontecia em


muitas empresas de software nos anos 2000. Embora muitas equipes ainda estivessem
funcionando sem nenhum processo, a adoo de enormes processos pesos-pesados aumentava rapidamente, sobretudo nas grandes empresas.

A Aliana gil
Motivado pela observao de que as equipes de software de muitas empresas estavam
presas no atoleiro do processo sempre crescente, um grupo de especialistas do setor,
autodenominados Aliana gil, se reuniu no incio de 2001 para esboar os valores e
princpios que permitiriam s equipes de software desenvolver rapidamente e responder
s mudanas. Nos meses seguintes, esse grupo trabalhou para criar uma declarao dos
valores. O resultado foi o Manifesto da Aliana gil.
Manifesto para o Desenvolvimento gil de Software
Estamos descobrindo maneiras melhores de desenvolver software,
fazendo-o ns mesmos e ajudando outros a faz-lo.
Com esse trabalho, passamos a valorizar:
Indivduos e interaes mais que processos e ferramentas
Software em funcionamento mais que documentao abrangente
Colaborao com o cliente mais que negociao de contratos
Resposta a mudanas mais que seguir um plano
Ou seja, mesmo havendo valor nos itens direita,
valorizamos mais os itens esquerda.
Kent Beck

Mike Beedle

Arie van Bennekum

Alistair Cockburn

Ward Cunningham

Martin Fowler

James Grenning

Jim Highsmith

Andrew Hunt

Ron Jeffries

Jon Kern

Brian Marick

Robert C. Martin

Steve Mellor

Ken Schwaber

Jeff Sutherland

Dave Thomas

Indivduos e interaes mais que processos e ferramentas


As pessoas so o ingrediente mais importante do sucesso. Um bom processo no salvar
um projeto do fracasso se a equipe no tiver participantes competentes, mas um processo
ruim pode tornar ineficiente at o mais competente dos participantes. Mesmo um grupo
de participantes competentes pode fracassar, se no trabalhar como uma equipe.
Um participante competente no necessariamente um programador excepcional.
Um participante competente pode ser um programador comum, mas algum que trabalhe
bem com outras pessoas. Trabalhar bem com outras pessoas comunicando-se e interagindo mais importante do que o talento natural para programao. Uma equipe de programadores comuns que se comunicam bem tem mais probabilidade de ser bem-sucedida
do que um grupo de celebridades que no consegue interagir como uma equipe.

PRTICAS GEIS

33

As ferramentas corretas podem ser muito importantes para o sucesso. Compiladores, ambientes de desenvolvimento interativos (IDEs), sistemas de controle de cdigo-fonte etc. todos so fundamentais para o funcionamento correto de uma equipe de
desenvolvedores. Contudo, as ferramentas podem ser supervalorizadas. O excesso de
ferramentas enormes e de difcil manuseio to ruim quanto a falta de ferramentas.
Nosso conselho comear pequeno. No presuma que voc deve abandonar uma ferramenta at a ter experimentado e verificado que no pode utiliz-la. Em vez de comprar
o sistema de controle de cdigo-fonte topo de linha e supercaro, encontre um gratuito e
utilize-o at ter certeza de que deve abandon-lo. Antes de adquirir licenas para a melhor
de todas as ferramentas de engenharia de software auxiliada por computador (CASE)
para a equipe, use quadros de avisos e papel quadriculado at que voc possa mostrar
inequivocamente que precisa de mais. Antes de comprometer-se com o melhor e maior
sistema de banco de dados, experimente os arquivos planos. No presuma que as maiores
e melhores ferramentas o ajudaro automaticamente a fazer o melhor. Frequentemente,
elas mais atrapalham do que ajudam.
Lembre-se de que montar a equipe mais importante do que construir o ambiente.
Muitas equipes e gerentes cometem o erro de construir o ambiente primeiro e esperar que
a equipe se entenda bem automaticamente. Em vez disso, trabalhe para criar a equipe e
depois a deixe configurar o ambiente de acordo com a necessidade.

Software em funcionamento mais que documentao abrangente


Software sem documentao um desastre. O cdigo no o meio ideal para comunicar
o fundamento lgico e a estrutura de um sistema. Em vez disso, a equipe precisa produzir
documentos de fcil leitura por seres humanos, que descrevam o sistema e o fundamento
lgico das decises de projeto.
No entanto, documentao demais pior do que pouca documentao. Documentos
de software grandes demais demoram muito tempo para ser produzidos e ainda mais
tempo para se manter sincronizados com o cdigo. Se eles no se mantiverem sincronizados, se transformaro em mentiras enormes e complicadas e se tornaro uma fonte de
orientao errada.
sempre recomendvel que a equipe escreva e mantenha um documento breve do
fundamento lgico e da estrutura. Mas esse documento precisa ser curto e notrio. Com
curto quero dizer umas dez ou vinte pginas no mximo. Com notrio quero dizer que
ele deve discutir o fundamento lgico global do projeto e somente as estruturas de nvel
mais alto do sistema.
Se tudo que temos um breve documento do fundamento lgico e da estrutura,
como ensinamos os novos membros da equipe sobre o sistema? Trabalhamos com eles
de perto. Transferimos nosso conhecimento sentando ao lado deles e os ajudando. Eles se
tornam parte da equipe por meio de treinamento prximo e interao.
Os dois melhores documentos para a transferncia de informaes para novos membros so o cdigo e a equipe. O cdigo no mente sobre o que faz. Pode ser difcil extrair
o fundamento lgico e o objetivo do cdigo, mas ele a nica fonte de informao clara. A
equipe tem na cabea de seus membros o mapa sempre mutante do sistema. O modo mais
rpido e eficiente de registrar esse mapa no papel e transferi-lo para outros por meio da
interao humana.

34

DESENVOLVIMENTO GIL

Muitas equipes tm se preocupado em buscar a documentao, em vez do software.


Com frequncia, essa uma falha fatal. Existe uma regra simples que evita isso:

Primeira Lei da Documentao de Martin


No produza qualquer documento, a no ser que sua necessidade seja imediata
e significativa.

Colaborao com o cliente mais que negociao de contratos


Software no pode ser pedido como uma mercadoria. Voc no pode escrever uma descrio do software que deseja e ento fazer algum desenvolv-lo em um cronograma fixo,
por um preo fixo. Tentativas de tratar projetos de software dessa maneira tm fracassado
repetidamente. E s vezes os fracassos so espetaculares.
Para os gerentes da empresa, tentador dizer ao seu pessoal de desenvolvimento
quais so suas necessidades e esperar que eles retornem rapidamente com um sistema
que atenda essas necessidades. Mas esse modo de operao leva a uma qualidade baixa e
ao fracasso.
Os projetos bem-sucedidos envolvem o feedback regular e frequente do cliente.
Em vez de depender de um contrato ou de um detalhamento de servio, o cliente do
software trabalha intimamente com a equipe de desenvolvimento, dando retorno frequente sobre seus esforos.
Um contrato que especifique os requisitos, o cronograma e o custo de um projeto
deficiente. Na maioria dos casos, os termos que ele especifica perdem o significado muito
antes que o projeto termine, s vezes at muito antes de o contrato ser assinado! Os melhores contratos so aqueles que governam o modo como a equipe de desenvolvimento e
o cliente trabalharo juntos.
Um exemplo de contrato bem-sucedido o que negociei, em 1994, para um grande
projeto de meio milho que se estenderia por vrios anos. Ns, a equipe de desenvolvimento, recebamos um valor mensal relativamente baixo. Recebamos pagamentos altos
quando entregvamos certos blocos grandes de funcionalidade. Esses blocos no eram
especificados em detalhes pelo contrato. Em vez disso, o contrato dizia que o pagamento
de um bloco seria feito quando este passasse no teste de aceitao do cliente. Os detalhes
desses testes de aceitao tambm no estavam especificados no contrato.
Durante o desenrolar desse projeto, trabalhamos muito intimamente com o cliente. Libervamos o software para ele quase toda sexta-feira. Na segunda ou tera-feira da
semana seguinte, ele tinha uma lista de alteraes para colocar no software. Priorizvamos
essas alteraes em conjunto e, depois, as agendvamos para as semanas subsequentes.
O cliente trabalhava to prximo a ns que os testes de aceitao nunca deram problema.
Ele sabia quando um bloco de funcionalidade atendia suas necessidades, pois o observava
evoluir de uma semana para outra.
Os requisitos desse projeto estavam em constante mudana. Grandes alteraes no
eram incomuns. Blocos de funcionalidade inteiros eram removidos e outros inseridos. Ainda assim, o contrato e o projeto sobreviveram e foram bem-sucedidos. O segredo para esse
sucesso foi a intensa colaborao com o cliente e um contrato que governava essa colaborao, em vez de tentar especificar os detalhes do escopo e da agenda por um custo fixo.

PRTICAS GEIS

35

Resposta a mudanas mais que seguir um plano


A capacidade de responder mudana frequentemente determina o sucesso ou o fracasso
de um projeto de software. Quando criamos planos, precisamos garantir que eles sejam
flexveis e prontos para se adaptar s mudanas do negcio e da tecnologia.
O desenrolar de um projeto de software no pode ser planejado com muita antecedncia. Primeiro, provvel que o ambiente comercial mude, fazendo os requisitos mudarem. Segundo, quando os clientes virem o sistema comear a funcionar, provvel que
tambm alterem os requisitos. Por fim, mesmo que saibamos quais so os requisitos e
tenhamos certeza de que eles no mudaro, no somos muito bons em estimar quanto
tempo levar para desenvolv-los.
Para os gerentes iniciantes tentador criar e grudar na parede um belo diagrama
PERT ou de Gantt do projeto inteiro. Eles podem achar que esse diagrama lhes d controle
sobre o projeto. Podem tambm acompanhar as tarefas individuais e risc-las no diagrama
quando so concludas. Podem ainda comparar as datas reais com as planejadas no diagrama e reagir a qualquer discrepncia.
Mas o que realmente acontece que a estrutura do diagrama degrada. medida que
a equipe obtm conhecimento sobre o sistema e o cliente obtm conhecimento sobre as
necessidades da equipe, certas tarefas que esto no diagrama se tornam desnecessrias.
Outras tarefas sero descobertas e precisaro ser acrescentadas. Em resumo, o plano
passar por mudanas na forma e no apenas nas datas.
Uma estratgia de planejamento melhor fazer planos detalhados para a semana seguinte, planos aproximados para os prximos trs meses e planos extremamente
rudimentares para alm disso. Devemos saber em quais tarefas individuais estaremos trabalhando na prxima semana. Devemos conhecer em linhas gerais os requisitos em que
estaremos trabalhando nos prximos trs meses. E devemos ter apenas uma vaga ideia do
que o sistema far depois de um ano.
Essa determinao decrescente do plano significa que estamos investindo em um
plano detalhado apenas para as tarefas imediatas. Uma vez feito o plano detalhado,
difcil que ele mude, pois a equipe ter muito pique e comprometimento. Mas, como
esse plano governa o trabalho de uma semana apenas, o restante dele permanece flexvel.

Princpios
Os valores anteriores inspiraram os 12 princpios a seguir. Esses princpios so as caractersticas que diferenciam um conjunto de prticas geis de um processo peso-pesado.
1. Nossa maior prioridade satisfazer o cliente com a entrega antecipada e contnua
de software de valor. O MIT Sloan Management Review publicou uma anlise das
prticas de desenvolvimento de software que ajudam as empresas a fazer produtos
de alta qualidade.1 O artigo revelou vrias prticas que tinham um impacto significativo na qualidade do sistema final. Uma delas era uma forte correlao entre
qualidade e a entrega antecipada de um sistema em funcionamento parcial. O artigo
relatou que quanto menos funcional a entrega inicial, maior a qualidade na
1

Product-Development Practices That Work: How Internet Companies Build Software, MIT Sloan Management Review, Winter 2001, nmero de reimpresso 4226.

36

DESENVOLVIMENTO GIL

entrega final. O artigo tambm revelou uma forte correlao entre a qualidade final
e entregas frequentes de funcionalidade crescente. Quanto mais frequentes so as
entregas, maior a qualidade final.
Um conjunto de prticas geis faz entregas antecipada e frequentemente. Esforamo-nos para entregar um sistema rudimentar dentro das primeiras semanas do
incio do projeto. Da em diante, nos esforamos para continuar a entregar sistemas
com funcionalidade crescente a cada poucas semanas. Os clientes podem optar por
colocar esses sistemas em produo, se acharem que so funcionais o bastante. Ou
podem optar simplesmente por examinar a funcionalidade existente e informar as
alteraes que desejam fazer.
2. Mudanas nos requisitos so bem-vindas, mesmo com o desenvolvimento j adiantado. Os processos geis canalizam a mudana para a vantagem competitiva do
cliente. Essa uma declarao de atitude. Os participantes de um processo gil no
tm medo de mudanas. Eles consideram as mudanas nos requisitos como coisas
boas, pois elas significam que a equipe aprendeu mais sobre o que ser necessrio
para satisfazer o cliente.
Uma equipe gil trabalha arduamente para manter a estrutura de seu software
flexvel, a fim de que, quando os requisitos mudarem, o impacto no sistema seja mnimo. Mais adiante neste livro, discutiremos os princpios, os padres e as prticas
de projetos orientados a objetos que nos ajudam a manter esse tipo de flexibilidade.
3. Entregar com frequncia software funcionando, de poucas semanas a poucos meses, dando preferncia escala de tempo mais curta. Entregamos software que
funciona e o entregamos antecipada e frequentemente. No ficamos contentes de
entregar um punhado de documentos ou planos. No consideramos isso como verdadeiras entregas. Nossa ateno est no objetivo de entregar software que atenda as
necessidades do cliente.
4. Executivos e desenvolvedores devem trabalhar juntos diariamente ao longo de
todo o projeto. Para que um projeto seja gil, clientes, desenvolvedores e interessados devem ter uma interao significativa e frequente. Um projeto de software no
como uma arma do tipo atire e esquea. Um projeto de software deve ser continuamente orientado.
5. Construir projetos com indivduos motivados. Fornecer a eles o ambiente e o apoio
necessrios e ter confiana de que eles faro o trabalho. As pessoas so o fator de
sucesso mais importante. Todos os outros fatores processo, ambiente, administrao etc. so de segunda ordem e esto sujeitos mudana, caso estejam prejudicando as pessoas.
6. O mtodo mais eficiente e eficaz de transmitir informaes para e dentro de uma
equipe de desenvolvimento a conversa face a face. Em um projeto gil, as pessoas falam umas com as outras. O principal modo de comunicao a interao
humana. Os documentos escritos so criados e atualizados de forma incremental no
mesmo cronograma que o software e somente quando necessrio.
7. Software funcionando a principal medida do progresso. Os projetos geis medem
seu progresso pela quantidade de software que est satisfazendo a necessidade do
cliente atualmente. Eles no medem o progresso em termos da fase em que esto
nem pelo volume de documentao que foi produzida ou pela quantidade de cdigo
de infraestrutura criada. Eles esto 30% prontos quando 30% da funcionalidade necessria est funcionando.

PRTICAS GEIS

37

8. Processos geis promovem o desenvolvimento sustentvel. Os patrocinadores,


desenvolvedores e usurios devem manter um ritmo constante indefinidamente. Um projeto gil no como uma corrida de 50 metros; como uma maratona.
A equipe no parte a toda velocidade e tenta manter essa velocidade durante o
percurso. Em vez disso, ela corre em um ritmo rpido, porm sustentvel. Correr
demais leva ao esgotamento, a atalhos e ao fracasso. As equipes geis tm seu ritmo prprio. Elas no se permitem ficar cansadas demais. No utilizam a energia
de amanh para fazer um pouco mais hoje. Elas trabalham em uma velocidade
que lhes permite manter os padres de qualidade mais alta durante todo o projeto.
9. Ateno contnua excelncia tcnica e ao bom projeto aumenta a agilidade. Alta
qualidade o segredo da alta velocidade. O modo de ir rpido manter o software o
mais limpo e robusto possvel. Assim, todos os membros da equipe gil esto comprometidos a produzir somente o cdigo da mais alta qualidade que puderem. Eles
no fazem baguna e depois dizem para si mesmos que arrumaro tudo quando
tiverem mais tempo. Eles arrumam toda a baguna quando ela feita.
10. Simplicidade a arte de maximizar o volume de trabalho que no precisou ser
feito essencial. As equipes geis no tentam construir o sistema grandioso no
cu. Em vez disso, elas sempre tomam o caminho mais simples que seja coerente
com seus objetivos. Elas no do muita importncia antecipao dos problemas de
amanh e tambm no tentam se defender de todos os de hoje. Em vez disso, fazem
o trabalho mais simples e de qualidade mais alta hoje, confiantes de que ele ser
mais fcil de alterar, se e quando os problemas de amanh surgirem.
11. As melhores arquiteturas, requisitos e projetos emergem de equipes auto-organizadas. Uma equipe gil auto-organizada. As responsabilidades no so dadas de
fora aos membros individuais da equipe, mas, em vez disso, so comunicadas para
a equipe como um todo. A equipe determina a melhor maneira de cumprir essas
responsabilidades.
Os membros da equipe gil trabalham juntos em todos os aspectos do projeto. Cada membro pode contribuir com o todo. Nenhum membro da equipe sozinho exclusivamente responsvel pela arquitetura, pelos requisitos ou pelos testes. A equipe compartilha essas responsabilidades e cada membro tem influncia
sobre elas.
12. Em intervalos regulares, a equipe reflete sobre como se tornar mais eficaz e, ento, adapta e ajusta seu comportamento de forma correspondente. Uma equipe
gil ajusta continuamente sua organizao, suas regras, suas convenes, seus relacionamentos etc. Uma equipe gil sabe que seu ambiente est mudando continuamente e que deve mudar com ele para permanecer gil.

Concluso
O objetivo profissional de todo desenvolvedor de software e de toda equipe de desenvolvimento proporcionar o maior valor possvel para empregadores e clientes. Apesar disso,
nossos projetos muitaz vezes fracassam ou no proporcionam valor. A espiral ascendente
da inflao do processo, embora bem intencionada, culpada por pelo menos parte desse
fracasso. Os princpios e valores do desenvolvimento gil de software foram concebidos
como um modo de ajudar as equipes a romper o ciclo da inflao do processo e para se
concentrar em tcnicas simples a fim de atingir seus objetivos.

38

DESENVOLVIMENTO GIL

Quando escrevamos este livro, havia muitos processos geis para se escolher:
Scrum,2 Crystal,3 desenvolvimento baseado em recursos (FDD),4 desenvolvimento adaptativo de software (ADP)5 e Programao Extrema (XP).6 Contudo, a ampla maioria das
equipes geis bem-sucedidas tem recorrido a todos esses processos para ajustar sua forma de agilidade. Essas adaptaes parecem se aglutinar em torno de uma combinao de
Scrum e XP, na qual as prticas de Scrum so utilizadas para gerenciar vrias equipes que
usam XP.

Bibliografia
[Beck99] Kent Beck, Extreme Programming Explained: Embrace Change, Addison-Wesley,
1999.
[Highsmith2000] James A. Highsmith, Adaptive Software Development: A Collaborative
Approach to Managing Complex Systems, Dorset House, 2000.
[Newkirk2001] James Newkirk and Robert C. Martin, Extreme Programming in Practice,
Addison-Wesley, 2001.

www.controlchaos.com
crystalmethodologies.org
4
Peter Coad, Eric Lefebvre and Jeff De Luca, Java Modeling in Color with UML: Enterprise Components
and Process, Prentice Hall, 1999.
5
[Highsmith2000]
6
[Beck99], [Newkirk2001]
3

Captulo 2

VISO GERAL DA
PROGRAMAO EXTREMA
Como desenvolvedores, precisamos lembrar que
a XP no a nica possibilidade.
Pete McBreen

Captulo 1 esboou o que o desenvolvimento de software gil, mas no nos ensinou exatamente o que fazer. Aprendemos algumas superficialidades e objetivos,
porm no tivemos uma orientao de verdade. Este captulo supre essa lacuna.

As prticas da Programao Extrema


Equipe coesa
Queremos que clientes, gerentes e desenvolvedores trabalhem juntos para que todos conheam os problemas uns dos outros e colaborem para resolv-los. Quem o cliente?
O cliente de uma equipe de XP a pessoa ou o grupo que define e prioriza recursos. s
vezes, o cliente um grupo de analistas de negcio, especialistas em garantia da qualidade
e/ou especialistas em marketing trabalhando na mesma empresa que os desenvolvedores.
s vezes, o cliente um representante do usurio comissionado pelo grupo de usurios.
Outras vezes, quem de fato est pagando. Mas, em um projeto de XP, o cliente, seja l
como for definido, um membro da equipe e est disponvel para ela.
O melhor caso quando o cliente trabalha na mesma sala que os desenvolvedores. O
segundo melhor caso quando o cliente trabalha a 100 minutos dos desenvolvedores. Quanto
maior a distncia, mais difcil para o cliente ser um verdadeiro membro da equipe. muito
difcil integrar na equipe um cliente localizado em outro prdio ou em outro estado.
O que voc faz se o cliente simplesmente no pode estar prximo? Meu conselho
encontrar algum que possa estar perto e esteja disposto e seja capaz de substituir o
verdadeiro cliente.

40

DESENVOLVIMENTO GIL

Histrias de usurio
Para planejar um projeto, devemos saber algo sobre os requisitos, mas no precisamos
saber muito. Para propsitos de planejamento, precisamos saber sobre um requisito apenas o suficiente para estim-lo. Talvez voc ache que para estimar um requisito precisa
conhecer todos os seus detalhes. Mas isso no totalmente verdade. Voc precisa saber
que existem detalhes e os tipos de detalhes em termos gerais, mas no precisa conhecer os
pormenores.
Os detalhes especficos de um requisito provavelmente mudam com o tempo, especialmente quando o cliente comea a ver o sistema a ser montado. Nada foca melhor os requisitos do que ver o sistema ganhar vida. Portanto, capturar os detalhes especficos sobre
um requisito muito antes de ele ser implementado provavelmente resultar em esforo em
vo e em enfoque prematuro.
Na XP, temos uma noo dos detalhes dos requisitos discutindo-os com o cliente.
Mas no capturamos esses detalhes. Em vez disso, o cliente escreve algumas palavras
em uma ficha que concordamos que v nos lembrar da conversa. Os desenvolvedores
escrevem uma estimativa na ficha quase ao mesmo tempo em que o cliente a escreve. Eles
baseiam essa estimativa na noo do detalhe que obtiveram durante suas conversas com
o cliente.
Uma histria de usurio uma indicao mnemnica de uma conversa em andamento a respeito de um requisito. uma ferramenta de planejamento que o cliente usa
para agendar a implementao de um requisito, com base em sua prioridade e em seu
custo estimado.

Ciclos curtos
Um projeto de XP entrega software funcionando a cada duas semanas. Cada uma dessas iteraes de duas semanas produz software que funciona e que trata de algumas das
necessidades dos interessados. Ao final de cada iterao, o sistema demonstrado aos
interessados para obter seu retorno.
O plano de iterao Normalmente, uma iterao dura duas semanas e representa uma
entrega secundria que pode ou no ser colocada em produo. O plano de iterao um
conjunto de histrias de usurio selecionadas pelo cliente de acordo com um oramento
estabelecido pelos desenvolvedores.
Os desenvolvedores definem o oramento de uma iterao medindo quanto fizeram
na iterao anterior. O cliente pode selecionar qualquer nmero de histrias para a iterao, desde que o total da estimativa no exceda esse oramento.
Uma vez iniciada uma iterao, a empresa concorda em no mudar a definio nem
a prioridade das histrias nessa iterao. Durante esse tempo, os desenvolvedores esto
livres para dividir as histrias em tarefas e para desenvolver as tarefas na ordem que fizer
mais sentido tcnico e comercial.
O plano de entrega As equipes de XP frequentemente criam um plano de entrega que
estabelece as prximas seis iteraes, aproximadamente. Esse plano conhecido como
plano de entrega, plano de lanamento ou plano de produo (release plan). Geralmente,
uma entrega se d aps trs meses de trabalho. Ela representa uma entrega importante
que normalmente pode ser colocada em produo. Um plano de entrega consiste em co-

VISO GERAL DA PROGRAMAO EXTREMA

41

lees de histrias de usurio priorizadas, que foram selecionadas pelo cliente de acordo
com um oramento apresentado pelos desenvolvedores.
Os desenvolvedores definem o oramento da entrega medindo quanto fizeram na
entrega anterior. O cliente pode selecionar qualquer nmero de histrias para a entrega,
desde que o total da estimativa no exceda esse oramento. A empresa tambm determina
a ordem na qual as histrias sero implementadas na entrega. Se a equipe assim desejar,
pode planejar as primeiras iteraes da entrega mostrando quais histrias sero concludas em quais iteraes.
As entregas no so rgidas. A empresa pode mudar o contedo da entrega a qualquer momento. Ela pode cancelar histrias, escrever novas histrias ou mudar a prioridade de uma histria. Contudo, a empresa deve se esforar para no alterar uma iterao.

Testes de aceitao
Os detalhes sobre as histrias de usurio so capturados na forma de testes de aceitao
especificados pelo cliente. Os testes de aceitao de uma histria so escritos imediatamente antes ou mesmo concomitantemente com a implementao dessa histria. Eles so
redigidos em uma linguagem de script que os permita ser executados automtica e repetidamente.1 Juntos, eles atuam para verificar se o sistema est se comportando conforme
os clientes especificaram.
Os testes de aceitao so escritos por analistas de negcio, especialistas em garantia
da qualidade e examinadores durante a iterao. A linguagem em que so escritos fcil
para programadores, clientes e executivos lerem e entenderem. a partir desses testes
que os programadores conhecem a verdadeira operao detalhada das histrias que esto
implementando. Esses testes se tornam o verdadeiro documento de requisitos do projeto.
Cada detalhe sobre cada recurso descrito nos testes de aceitao e esses testes so a
autoridade final com relao a esses recursos estarem prontos e corretos.
Uma vez que um teste de aceitao seja aprovado, ele adicionado no corpo de testes
de aceitao aprovados e nunca mais pode falhar. Esse corpo de testes de aceitao crescente executado vrias vezes por dia, sempre que o sistema construdo. Se um teste de
aceitao falha, a construo declarada um fracasso. Assim, uma vez implementado um
requisito, ele nunca violado. O sistema migrado de um estado de funcionamento para
outro e nunca pode ficar inativo por mais do que algumas horas.

Programao em pares
O cdigo escrito por pares de programadores trabalhando juntos na mesma estao de trabalho. Um membro de cada par comanda o teclado e digita o cdigo. O outro membro do par
observa o cdigo que est sendo digitado, procurando erros e melhorias.2 Os dois interagem
intensamente. Ambos esto completamente empenhados no ato de escrever software.
Os papis mudam com frequncia. Se o operador ficar cansado, o parceiro assume
o teclado e comea a operar. O teclado mudar de mos entre eles vrias vezes em uma
hora. O cdigo resultante projetado e escrito pelos dois membros. Nenhum deles pode
receber mais de metade do crdito.

1
2

Consulte o site www.fitnesse.org.


Tenho visto pares nos quais um membro controla o teclado e o outro controla o mouse.

42

DESENVOLVIMENTO GIL

A associao de pares tambm muda com frequncia. Uma meta razovel mudar os
parceiros pelo menos uma vez por dia, de modo que cada programador trabalhe em dois
pares diferentes a cada dia. Durante o curso de uma iterao, todos os membros da equipe
devem ter trabalhado entre si, e em todos os aspectos da iterao.
A programao em pares aumenta significativamente a difuso de conhecimento por
toda a equipe. Embora as especialidades permaneam e tarefas que exigem certas especialidades normalmente dependam de especialistas apropriados, esses especialistas formaro pares com quase todos na equipe. Isso difundir a especialidade, de modo que os
outros membros da equipe podem substituir os especialistas rapidamente. Estudos feitos
por Williams3 e Nosek4 tm sugerido que a formao de pares no reduz a eficincia do
pessoal de programao, mas diminui muito a taxa de falhas.

Desenvolvimento guiado por testes (TDD Test-Driven Development)


O Captulo 4 discute esse assunto com mais detalhes. O que se segue uma rpida viso
geral.
Todo cdigo de produo escrito para fazer um teste de unidade* falho passar.
Primeiro, escrevemos um teste de unidade que falha, pois a funcionalidade que ele est
testando no existe. Depois, escrevemos o cdigo que faz o teste passar.
Essa iterao entre escrever casos de teste e cdigo muito rpida, cerca de um minuto,
mais ou menos. Os casos de teste e o cdigo evoluem juntos, com os casos de teste liderando
o cdigo por uma frao muito pequena. (Consulte o Captulo 6 para ver um exemplo.)
Como resultado, um corpo de casos de teste muito completo desenvolve-se com o
cdigo. Esses testes permitem aos programadores verificar se o programa funciona. A
programao de um par que faa uma pequena alterao pode executar os testes para garantir que nada foi danificado. Isso facilita muito a refatorao (discutida posteriormente
neste captulo).
Quando voc escreve um cdigo para fazer os casos de teste passar, por definio
esse cdigo passvel de teste. Alm disso, h uma forte motivao para desacoplar os
mdulos, a fim de que eles possam ser testados de modo independente. Assim, o projeto
de um cdigo escrito dessa maneira tende a ser muito menos acoplado. Os princpios do
projeto orientado a objetos (OOD Object-Oriented Design) desempenham um papel poderoso em ajud-lo nesse desacoplamento (consulte a Seo II).

Posse coletiva
Um par tem o direito de retirar qualquer mdulo e melhor-lo. Nenhum programador
responsvel individualmente por qualquer mdulo ou tecnologia especfica. Todos trabalham na interface grfica do usurio (GUI Graphical User Interface).5 Todos trabalham
no middleware. Todos trabalham no banco de dados. Ningum tem mais autoridade do
que ningum sobre um mdulo ou sobre uma tecnologia.

[Williams2000], [Cockburn2001]
[Nosek98]
* N. de R.T.: Do original, unit test. Tambm frequentemente chamado de teste unitrio.
5
No estou defendendo uma arquitetura de trs camadas aqui. Simplesmente escolhi trs divises comuns
da tecnologia de software.
4

VISO GERAL DA PROGRAMAO EXTREMA

43

Isso no significa que a XP nega as especialidades. Se a sua especialidade a interface grfica, mais provvel que trabalhe em tarefas de interface grfica. Mas voc
tambm ser solicitado a formar pares em tarefas de middleware e de banco de dados.
Se voc resolver aprender uma segunda especialidade, pode se inscrever para tarefas
e trabalhar com especialistas que as ensinaro para voc. Voc no est restrito sua
especialidade.

Integrao contnua
Os programadores registram seu cdigo e o integram vrias vezes por dia. A regra simples. O primeiro a registrar vence; todos os outros mesclam.
As equipes de XP utilizam controle de cdigo-fonte sem bloqueio. Isso significa
que os programadores podem retirar qualquer mdulo a qualquer momento, independentemente de quem mais o tenha retirado. Ao registrar o mdulo novamente, aps
modific-lo, o programador deve estar preparado para mescl-lo com qualquer alterao feita por qualquer um que tenha registrado o mdulo anteriormente. Para evitar
longas sesses de mesclagem, os membros da equipe registram seus mdulos com
muita frequncia.
Um par trabalhar por uma ou duas horas em uma tarefa. Eles criam casos de teste e cdigo de produo. Em algum ponto de quebra conveniente, provavelmente muito
antes que a tarefa esteja concluda, eles decidem registrar o cdigo novamente. Primeiro
eles se certificam de que todos os testes sejam executados. Depois, integram seu novo
cdigo na base de cdigo existente. Se houver alguma mesclagem a fazer, eles a fazem. Se
necessrio, consultam os programadores que registraram antes deles. Uma vez que suas
alteraes estejam integradas, eles constroem o novo sistema. Eles executam cada teste
no sistema, inclusive todos os testes de aceitao correntemente em andamento. Se estragarem algo que j funcionava, eles corrigem isso. Uma vez executados todos os testes,
finalizam o registro.
Portanto, as equipes de XP construiro o sistema muitas vezes por dia. Elas constroem o sistema inteiro, de ponta a ponta.6 Se o resultado final de um sistema um
CD-ROM, elas gravam o CD-ROM. Se o resultado final do sistema um site ativo, elas
instalam esse site, provavelmente em um servidor de teste.

Ritmo sustentvel
Um projeto de software no uma corrida de 100 metros rasos; uma maratona. Uma
equipe que saltar da linha de partida e comear a correr o mais rpido que puder ficar
esgotada muito antes da chegada. Para terminar rapidamente, a equipe deve correr em um
ritmo sustentvel; ela deve economizar sua energia e ficar de prontido. Ela deve correr
intencionalmente em um ritmo constante e moderado.
A regra da XP que uma equipe no pode fazer horas extras. A nica exceo
a essa regra que, na ltima semana de uma entrega, uma equipe que esteja a uma
grande distncia de seu objetivo de entrega pode correr a toda velocidade at o final e
fazer horas extras.

Ron Jeffries diz: De ponta a ponta mais longe do que voc pensa.

44

DESENVOLVIMENTO GIL

rea de trabalho aberta


A equipe trabalha junto em uma sala aberta. As mesas possuem estaes de trabalho.
Cada mesa tem duas ou trs estaes de trabalho. Duas cadeiras so colocadas em frente
a cada estao de trabalho. As paredes so cobertas com grficos de andamento, decomposies de tarefas, diagramas UML (Unified Modeling Language) etc.
O som nessa sala um zumbido de conversa. Cada par fica ao alcance da audio de
outro par. Todos conseguem ouvir quando outro est com problemas. Cada um conhece o
estado do outro. Os programadores esto em uma posio que permite se comunicarem
intensamente.
Algum poderia pensar que esse seria um ambiente que distrai. Seria fcil ficar
com receio de que voc nunca fizesse nada, por causa do rudo e da distrao constantes. Na verdade, isso no acontece. Alm disso, em vez de interferir na produtividade,
conforme sugeriu um estudo da Universidade de Michigan, trabalhar em um ambiente
de centro de comando de guerra pode aumentar a produtividade por um fator de 2.7

Jogo de planejamento
O Captulo 3 entra em mais detalhes sobre o jogo de planejamento da XP. Vamos descrev-lo brevemente aqui.
A essncia do jogo de planejamento a diviso de responsabilidade entre negcio e
desenvolvimento. Os executivos clientes julgam o quanto um recurso importante e os
desenvolvedores estimam quanto custar para implementar esse recurso.
No incio de cada entrega e de cada iterao, os desenvolvedores fornecem um oramento para os clientes. Os clientes escolhem as histrias cujos custos totalizem esse
oramento e no podem ultrapassar o oramento. Os desenvolvedores determinam
o oramento com base no quanto foram capazes de realizar na iterao ou na entega
anterior.
Com essas regras simples em vigor e com iteraes curtas e entregas frequentes,
no demorar muito para que os clientes e desenvolvedores se acostumem com o ritmo
do projeto. Os clientes tero uma noo da velocidade dos desenvolvedores. Com base
nessa noo, os clientes podero determinar quanto tempo seu projeto levar e quanto
custar.

Projeto simples
Uma equipe de XP torna seus projetos o mais simples e expressivos que puder. Alm disso, a equipe restringe seu foco para considerar apenas as histrias que esto planejadas
para a iterao atual, no se preocupando com as histrias futuras. Em vez disso, ela migra o projeto do sistema de uma iterao para outra, para que seja o melhor projeto para
as histrias que o sistema implementa atualmente.
Isso significa que uma equipe de XP provavelmente no comear com a infraestrutura, provavelmente no selecionar primeiro o banco de dados e provavelmente no
selecionar primeiro o middleware. Em vez disso, o primeiro ato da equipe ser fazer o

www.sciencedaily.com/releases/2000/12/001206144705.htm

VISO GERAL DA PROGRAMAO EXTREMA

45

primeiro lote de histrias funcionar da maneira mais simples possvel. A equipe adicionar a infraestrutura somente quando aparecer uma histria que obrigue a isso.
Trs mantras da XP orientam o desenvolvedor.
1. Pense na coisa mais simples que possa funcionar. As equipes de XP sempre tentam
encontrar a opo de projeto mais simples possvel para o lote de histrias atual.
Se pudermos fazer as histrias atuais funcionarem com arquivos planos, talvez no
seja necessrio o uso de um banco de dados. Se pudermos fazer as histrias atuais
funcionarem com uma conexo via sockets simples, possivelmente no usaremos
um ORB ou um Web Service. Se pudermos fazer as histrias atuais funcionarem
sem multiencadeamento, h a possibilidade de no incluirmos multiencadeamento.
Tentamos considerar a maneira mais simples de implementar as histrias atuais.
Depois, escolhemos uma soluo prtica que seja a mais prxima possvel da simplicidade que podemos obter do ponto de vista prtico.
2. Voc no vai precisar disso. Sim, mas sabemos que um dia vamos precisar desse
banco de dados. Sabemos que um dia precisaremos ter um ORB. Sabemos que um
dia precisaremos dar suporte para mltiplos usurios. Portanto, precisamos colocar
os ganchos para essas coisas agora, certo?
Uma equipe de XP considera seriamente o que acontecer se resistir tentao de adicionar a infraestrutura antes que ela seja estritamente necessria. A
equipe comea a partir da suposio de que no precisar dessa infraestrutura. A
equipe s coloca a infraestrutura se tiver uma prova ou pelo menos uma evidncia
muito convincente de que colocar a infraestrutura agora ser mais econmico do
que esperar.
3. Uma vez e somente uma vez. Os adeptos da XP no toleram duplicao de cdigo.
Onde quer que a encontrem, eles a eliminam.
Existem muitas fontes de duplicao de cdigo. As mais evidentes so aquelas extenses de cdigo que foram capturadas com um mouse e soltas em vrios lugares. Quando
as encontramos, as eliminamos criando uma funo ou uma classe base. s vezes, dois
ou mais algoritmos podem ser muito semelhantes e, apesar disso, diferirem de maneiras
sutis. Ns os transformamos em funes ou utilizamos o padro TEMPLATE METHOD
(consulte o Captulo 22). Uma vez descoberta, no toleraremos duplicao, qualquer que
seja sua origem.
A melhor maneira de eliminar redundncia criando abstraes. Afinal, se duas coisas so semelhantes, alguma abstrao deve unific-las. Assim, o ato de eliminar redundncia obriga a equipe a criar muitas abstraes e a reduzir ainda mais o acoplamento.

Refatorao
O Captulo 5 aborda a refatorao com mais detalhes.8 O que se segue uma breve viso
geral.
O cdigo tende a se deteriorar. medida que adicionamos recursos e lidamos com
erros, a estrutura do cdigo degrada. Se no for controlada, essa degradao levar a uma
baguna complicada e impossvel de organizar.
8

[Fowler99]

46

DESENVOLVIMENTO GIL

As equipes de XP revertem essa degradao por meio da refatorao frequente. Refatorao a prtica de fazer uma srie de pequenas transformaes que melhoram a
estrutura do sistema, sem afetar seu comportamento. Uma transformao sozinha insignificante, mas, juntas, elas se combinam em transformaes significativas do projeto e da
arquitetura do sistema.
Aps cada minscula transformao, executamos os testes de unidade para garantir
que no tenhamos estragado nada. Depois, fazemos a transformao seguinte, a seguinte
e a seguinte, executando os testes aps cada uma delas. Dessa maneira, mantemos o sistema funcionando enquanto transformamos seu projeto.
A refatorao feita continuamente e no no final do projeto, da entrega ou da iterao, ou mesmo no final do dia. Refatorao algo que fazemos a cada hora ou a cada meia
hora. Com a refatorao, mantemos o cdigo sempre o mais limpo, simples e expressivo
possvel.

Metfora
A metfora a nica prtica de XP que no concreta e direta, e, portanto, a menos
compreendida de todas as prticas de XP. Ns, adeptos da XP, somos profundamente
pragmticos e essa falta de definio nos deixa incomodados. Alis, os proponentes da XP
tm discutido com frequncia a eliminao da metfora como prtica. No entanto, de certa
forma ela uma das prticas mais importantes da programao externa.
Pense em um quebra-cabea. Como voc sabe que as peas se encaixam? Claramente,
cada pea faz fronteira com outras e seu formato deve complementar perfeitamente as peas que ela toca. Se voc fosse cego e tivesse tato excelente, poderia montar o quebra-cabea separando cada pea diligentemente e experimentando-a posio aps posio.
Mas algo mais poderoso do que o formato das peas une o quebra-cabea: uma imagem. A imagem o verdadeiro guia. Ela to poderosa que, se duas peas adjacentes no tm
formatos complementares, voc sabe que o fabricante do quebra-cabea cometeu um erro.
Isso metfora. Trata-se da viso global que interliga o sistema inteiro. a viso do sistema que torna bvia a localizao e o formato de todos os mdulos individuais. Se o formato
de um mdulo incompatvel com a metfora, voc sabe que o mdulo que est errado.
Frequentemente, uma metfora se reduz a um sistema de nomes. Os nomes fornecem um vocabulrio para os elementos do sistema e ajudam a definir suas relaes.
Por exemplo, uma vez trabalhei em um sistema que transmitia texto para uma tela a 60
caracteres por segundo. Nessa velocidade, o preenchimento de uma tela poderia levar algum
tempo. Assim, deixamos que o programa que estava gerando o texto preenchesse um buffer.
Quando o buffer estava cheio, armazenvamos o programa no disco. Quando o buffer estava
quase vazio, recarregvamos o programa e deixvamos executar mais um pouco.
Falvamos sobre esse sistema em termos de caminhes basculantes para transporte de lixo. Os buffers eram pequenos caminhes. A tela era o depsito. O programa era
o produtor de lixo. Todos os nomes se encaixavam e nos ajudavam a pensar no sistema
como um todo.

VISO GERAL DA PROGRAMAO EXTREMA

47

Como outro exemplo, uma vez trabalhei em um sistema que analisava trfego de rede.
A cada 30 minutos, ele sondava dezenas de adaptadores de rede e ativava o monitoramento
dos dados neles contidos. Cada adaptador de rede nos fornecia um pequeno bloco de dados, composto de diversas variveis individuais. Chamvamos esses blocos de fatias. As
fatias eram dados brutos que precisavam ser analisados. O programa de anlise cozinhava as fatias; portanto, era chamado de a torradeira. Chamvamos as variveis individuais
dentro das fatias de migalhas. Em suma, essa era uma metfora til e divertida.
Evidentemente, uma metfora mais do que um sistema de nomes. Uma metfora
uma viso do sistema. Ela orienta todos os desenvolvedores a escolher nomes adequados,
a selecionar locais adequados para as funes, a criar novas classes e mtodos apropriados etc.

Concluso
A Programao Extrema um conjunto de prticas simples e concretas que se combinam
em um processo de desenvolvimento gil. A XP um bom mtodo de uso geral para desenvolver software. Muitas equipes de projeto podero adot-la como ela . Outras podero
adapt-la, adicionando ou modificando prticas.

Bibliografia
[ARC97] Alistair Cockburn, The Methodology Space, Humans and Technology, relatrio
tcnico HaT TR.97.03 (datado de 97.10.03), http://members.aol.com/acockburn/papers/
methyspace/methyspace.htm.
[Beck99] Kent Beck, Extreme Programming Explained: Embrace Change, Addison-Wesley, 1999.
[Beck2003] Kent Beck, Test-Driven Development by Example, Addison-Wesley, 2003.
[Cockburn2001] Alistair Cockburn and Laurie Williams, The Costs and Benefits of Pair
Programming, XP2000 Conference in Sardinia, reproduzido em Giancarlo Succi and Michele Marchesi, Extreme Programming Examined, Addison-Wesley, 2001.
[DRC98] Daryl R. Conner, Leading at the Edge of Chaos, Wiley, 1998.
[EWD72] D.J. Dahl, E.W. Dijkstra and C.A.R. Hoare, Structured Programming, Academic
Press, 1972.
[Fowler99] Martin Fowler, Refactoring: Improving the Design of Existing Code, Addison-Wesley, 1999.
[Newkirk2001] James Newkirk and Robert C. Martin, Extreme Programming in Practice,
Addison-Wesley, 2001.
[Nosek98] J.T. Nosek, The Case for Collaborative Programming, Communications of the
ACM, 1998, p. 105-108.
[Williams2000] Laurie Williams, Robert R. Kessler, Ward Cunningham, Ron Jeffries,
Strengthening the Case for Pair Programming, IEEE Software, JulyAug de 2000.

Captulo 3

PLANEJAMENTO
Quando voc consegue mensurar aquilo que defende express-lo
em nmeros, sabe algo sobre o assunto; mas quando
voc no consegue med-lo e express-lo em nmeros,
seu conhecimento exguo e insatisfatrio.
Lord Kelvin, 1883

que se segue uma descrio do Jogo do Planejamento da Programao Extrema.1


semelhante maneira como o planejamento feito em vrios outros mtodos
geis2: Scrum,3 Crystal,4 desenvolvimento baseado em funcionalidades5 (FDD) e desenvolvimento adaptativo de software (ADP).6 No entanto, nenhum desses processos explica com
tanto detalhe e rigor.

Explorao inicial
No incio do projeto, os desenvolvedores e os clientes conversam sobre o novo sistema
para identificar todas as funcionalidades (features) significativas que conseguirem. Contudo, eles no tentam esgotar todas as possibilidades. medida que o projeto prosseguir,
os clientes continuaro a descobrir mais funcionalidades, fluxo que no parar at o projeto terminar.
Sempre que uma funcionalidade identificada, ela decomposta em uma ou mais histrias de usurio, as quais so escritas em fichas ou equivalente. Pouco recurso escrito na
ficha, alm do nome da histria (por exemplo, Login, Adicionar Usurio, Excluir Usurio ou
Alterar Senha). Nesse estgio no estamos tentando capturar detalhes. Queremos simplesmente algo para nos lembrar das conversas que tivemos sobre as funcionalidades.
1

[Beck99], [Newkirk2001]
www.AgileAlliance.org
3
www.controlchaos.com
4
[Cockburn2005]
5
Peter Coad, Eric Lefebvre and Jeff De Luca, Java Modeling in Color with UML: Enterprise Components
and Process, Prentice Hall, 1999.
6
[Highsmith2000]
2

50

DESENVOLVIMENTO GIL

Os desenvolvedores trabalham juntos para fazer uma estimativa das histrias. As


estimativas so relativas e no absolutas. Escrevemos vrios pontos em uma ficha de
histria para representar o custo relativo da histria. Podemos no ter certeza absoluta
sobre quanto tempo um ponto de histria representa, mas sabemos que uma histria com
8 pontos demorar duas vezes mais do que uma histria com 4 pontos.

Melhoramento, diviso e velocidade


Histrias grandes ou pequenas demais so difceis de estimar. Os desenvolvedores tendem
a subestimar as histrias grandes e a superestimar as pequenas. Qualquer histria que
seja grande demais deve ser dividida em partes menores. Qualquer histria que seja pequena demais deve ser combinada com outras histrias pequenas.
Por exemplo, considere a histria Os usurios podem transferir dinheiro com segurana para, de e entre suas contas. Essa uma histria grande. Estim-la ser difcil
e provavelmente impreciso. No entanto, podemos dividi-la em muitas histrias que sejam
mais fceis de estimar:
Os usurios podem fazer login.
Os usurios podem fazer logout.
Os usurios podem depositar dinheiro em suas contas.
Os usurios podem retirar dinheiro de suas contas.
Os usurios podem transferir dinheiro de uma de suas contas para outra conta.
Quando uma histria dividida ou combinada, ela precisa ser reavaliada. No
sensato simplesmente adicionar ou subtrair a estimativa. O motivo de dividir ou combinar
uma histria para que ela tenha um tamanho no qual a estimativa seja precisa. No
surpresa descobrir que uma histria estimada em 25 pontos seja decomposta em histrias que resultem em 30! Trinta a estimativa mais precisa.
Toda semana conclumos determinado nmero de histrias. A soma das estimativas
das histrias concludas uma mtrica conhecida como velocidade. Se conclumos histrias de 42 pontos durante a semana anterior, nossa velocidade foi de 42.
Aps trs ou quatro semanas, teremos uma boa ideia de nossa velocidade mdia.
Podemos usar isso para prever o volume de trabalho que faremos nas semanas subsequentes. O monitoramento da velocidade uma das ferramentas de gerenciamento mais
importantes em um projeto de XP.
No incio de um projeto, os desenvolvedores no tero uma noo muito boa de sua
velocidade. Eles devem fazer suposies iniciais por qualquer meio que acharem que dar os
melhores resultados. A necessidade de preciso nesse ponto no particularmente importante; portanto, eles no precisam gastar uma quantidade de tempo exagerada nisso. Alis, o
bom e velho chute abalizado normalmente suficiente.

Planejamento da entrega
Dada uma velocidade, os clientes podem ter uma ideia do custo de cada uma das histrias, assim como de seu valor comercial e sua prioridade. Isso permite que os clientes
escolham as histrias que querem executadas primeiro. Essa escolha no apenas uma
questo de prioridade. Algo importante, mas tambm dispendioso, pode ser adiado em fa-

PLANEJAMENTO

51

vor de algo que tem menos importncia, mas muito menos dispendioso. Escolhas como
essa so decises comerciais. Os executivos decidem quais histrias lhes proporcionam
mais valor pelo dinheiro gasto.
Os desenvolvedores e os clientes concordam com uma data para a primeira verso
do projeto. Normalmente isso se dar dali a dois a quatro meses. Os clientes escolhem as
histrias que desejam implementadas dentro dessa verso e a ordem aproximada em que
devem ser implementadas. Os clientes no podem escolher mais histrias do que estejam
de acordo com a velocidade atual. Como inicialmente a velocidade imprecisa, essa escolha rudimentar. Mas a preciso no muito importante nesse ponto. O plano de entrega
poder ser ajustado quando a velocidade se tornar mais precisa.

Planejamento da iterao
Em seguida, os desenvolvedores e os clientes escolhem a dimenso da iterao: normalmente, uma ou duas semanas. Mais uma vez, os clientes escolhem as histrias que desejam implementadas na primeira iterao, mas no podem escolher mais histrias do que
estejam de acordo com a velocidade atual.
A ordem das histrias dentro da iterao uma deciso tcnica. Os desenvolvedores
implementam as histrias na ordem que fizer mais sentido tcnico. Eles podem trabalhar
nas histrias sucessivamente, finalizando uma aps a outra, ou podem repartir as histrias e trabalhar em todas elas concomitantemente. Isso fica inteiramente por conta dos
desenvolvedores.
Os clientes no podem mudar as histrias da iterao uma vez que ela tenha comeado. Eles esto livres para alterar ou reordenar qualquer outra histria do projeto, mas
no aquelas em que os desenvolvedores estiverem trabalhando.
A iterao termina na data especificada, mesmo que nem todas as histrias tenham
terminado. As estimativas de todas as histrias concludas so totalizadas e a velocidade
dessa iterao calculada. Ento, essa medida de velocidade utilizada para planejar a
prxima iterao. A regra muito simples: a velocidade planejada para cada iterao a
velocidade medida da iterao anterior. Se a equipe conseguiu fazer 31 pontos de histria
na ltima iterao, deve planejar a obteno de 31 pontos de histria na prxima. A velocidade da equipe de 31 pontos por iterao.
A informao da velocidade ajuda a manter o planejamento sincronizado com a equipe.
Se a equipe obtiver qualificao e habilidades, a velocidade aumentar de forma proporcional. Se a equipe perder algum, a velocidade cair. Se for desenvolvida uma arquitetura que
facilite o desenvolvimento, a velocidade aumentar.

Definio de pronto
Uma histria no est pronta at que todos os seus testes de aceitao passem. Esses
testes de aceitao so automatizados. Eles so escritos pelo cliente, por analistas de
negcio, por especialistas em garantia da qualidade, por testadores e at por programadores, logo no incio de cada iterao. Esses testes definem os detalhes das histrias e so a
autoridade final a respeito de como as histrias se comportam. Veremos mais sobre testes
de aceitao no prximo captulo.

52

DESENVOLVIMENTO GIL

Planejamento de tarefas
No incio de uma nova iterao, os desenvolvedores e clientes se renem para planejar.
Os desenvolvedores decompem as histrias em tarefas de desenvolvimento. Uma tarefa algo que um desenvolvedor pode implementar em um perodo de 4 a 16 horas. As
histrias so analisadas com a ajuda dos clientes e as tarefas so enumeradas o mais
completamente possvel.
Uma lista das tarefas criada em um flip chart, em um quadro de avisos ou em
algum outro meio conveniente. Ento, um por um, os desenvolvedores se inscrevem
para as tarefas que desejam implementar, estimando cada tarefa em pontos de tarefa
arbitrrios.7
Os desenvolvedores podem se inscrever para qualquer tipo de tarefa. Os especialistas em banco de dados no precisam se inscrever apenas em tarefas de banco de dados.
O pessoal de interface do usurio pode se inscrever em tarefas de banco de dados, se
desejarem. Embora possa parecer ineficiente, existe um mecanismo para gerenciar isso.
A vantagem bvia: quanto mais os desenvolvedores sabem a respeito do projeto inteiro,
mais saudvel e informada a equipe de projeto. Queremos que o conhecimento do projeto seja difundido por toda a equipe, independentemente da especialidade.
Cada desenvolvedor sabe quantos pontos de tarefa conseguiu implementar na iterao anterior; esse nmero o oramento do desenvolvedor. Ningum se inscreve para
mais pontos do que esto no oramento.
A seleo de tarefas continua at que todas as tarefas estejam atribudas ou at
que todos os desenvolvedores tenham utilizado seus oramentos. Se restarem tarefas,
os desenvolvedores negociam uns com os outros, trocando tarefas com base em suas
diversas habilidades. Se isso no criar condies suficientes para que todas as tarefas
sejam atribudas, os desenvolvedores pedem aos clientes para que retirem tarefas ou
histrias da iterao. Se todas as tarefas forem atribudas e os desenvolvedores ainda
tiverem capacidade para mais trabalho em seus oramentos, eles pedem mais histrias
aos clientes.
Na metade da iterao, a equipe realiza uma reunio. Nesse ponto, metade das
histrias agendadas para a iterao deve estar concluda. Se metade das histrias no
estiver concluda, a equipe tenta repartir tarefas e responsabilidades para garantir
que todas as histrias estejam concludas ao final da iterao. Se os desenvolvedores
no conseguirem resolver essa redistribuio, os clientes precisam ser avisados. Os
clientes podem retirar uma tarefa ou histria da iterao. No mnimo, eles indicaro
as tarefas e histrias de prioridade mais baixa para que os desenvolvedores evitem
trabalhar nelas.
Suponha, por exemplo, que os clientes selecionaram oito histrias, totalizando 24
pontos de histria para a iterao. Suponha ainda que elas foram decompostas em 42
tarefas. Na metade da iterao, esperaramos ter 21 tarefas e 12 pontos de histria concludos. Esses 12 pontos de histria devem representar histrias totalmente concludas.
Nosso objetivo concluir histrias e no simplesmente tarefas. O cenrio de pesadelo
chegar ao fim da iterao com 90% das tarefas concludos, mas nenhuma histria terminada. Na metade do caminho, queremos ver histrias concludas que representem a
metade dos pontos de histria da iterao.

Muitos desenvolvedores acham interessante usar horas de programao cheias como pontos de tarefa.

PLANEJAMENTO

53

Iterao
A cada duas semanas, a iterao atual termina e a seguinte comea. Ao final de cada
iterao, o executvel em andamento demonstrado para os clientes. Os clientes so solicitados a avaliar a aparncia, o comportamento e o desempenho do projeto. Eles daro
retorno em termos de novas histrias de usurio.
Os clientes verificam o progresso frequentemente. Eles podem medir a velocidade,
prever a velocidade da equipe e agendar histrias de alta prioridade antecipadamente. Em
resumo, os clientes tm todos os dados e o controle necessrio para gerenciar o projeto
como quiserem.

Monitoramento
Monitorar e gerenciar um projeto de XP envolve registrar os resultados de cada iterao e
usar esses resultados para prever o que acontecer nas iteraes seguintes. Considere, por
exemplo, a Figura 3-1. Esse grfico chamado de grfico de velocidade. Normalmente, o
encontraramos nas paredes do centro de comando de guerra do projeto.
Esse grfico mostra quantos pontos de histria foram concludos passaram em
seus testes de aceitao automatizados ao final de cada semana. Embora exista alguma
variao entre as semanas, os dados mostram claramente que essa equipe est concluindo
cerca de 42 pontos de histria por semana.
Considere tambm o grfico da Figura 3-2. Esse grfico de burndown, como conhecido, mostra, semana por semana, quantos pontos permanecem a ser concludos para
o prximo marco ou entrega importante. A inclinao desse grfico um indicador razovel da data final.
Note que a diferena entre as barras no grfico de burndown no igual altura das
barras no grfico de velocidade. O motivo que novas histrias esto sendo acrescentadas
no projeto. Isso tambm pode indicar que os desenvolvedores reavaliaram as histrias.
Quando esses dois grficos so deixados nas paredes da sala de projeto, qualquer um
pode inspecion-los e saber, em questo de segundos, qual a situao do projeto. possvel
saber quando o prximo marco importante ser cumprido e at que ponto o escopo e as estimativas esto aumentando. Esses dois grficos so a verdadeira essncia da XP e de todos
os mtodos geis. No fim, tudo consiste em gerar informaes de gerenciamento confiveis.

60
50
40
30
20
10

Figura 3-1
Grfico de velocidade.

00
3
/2

00
3
/2
3/
3

10
/3

00
3
/2

00
3
24
/2

/2

00
3
/2
10
/2

17
/2

00
3
/2

00
3
3/
2

/2
27
/1

/2

00
3

20
/1

Pontos de histria

Velocidade

54

DESENVOLVIMENTO GIL

Pontos de histria restantes

Pontos de histria

600
500
400
300
200
100

3
00

03

/2
/3
10

3/

3/

20

3
/2
/2
24

/2
/2
17

/2
/2
10

00

3
00

3
00

03
3/

2/

20

3
00
/2
/1
27

20

/1

/2

00

Figura 3-2
Grfico de burndown.

Concluso
De iterao em iterao e de entrega em entrega, o projeto entra em um ritmo previsvel e
confortvel. Todos sabem o que esperar e quando. Os interessados verificam o progresso frequentemente e de forma substancial. Em vez de verem agendas cheias de diagramas e planos,
os interessados veem software em funcionamento que podem tocar, sentir e dar opinies.
Os desenvolvedores veem um plano razovel, baseado em suas prprias estimativas
e controlado por suas prprias velocidades medidas. Eles escolhem as tarefas em que se
sentem vontade para trabalhar e mantm alta a qualidade de sua mo de obra.
Os gerentes recebem dados de cada iterao e usam esses dados para controlar e
gerenciar o projeto. Eles no precisam recorrer a presso e ameaas ou apelar lealdade
para cumprir uma data arbitrria e no realista.
Isso pode parecer lindo e maravilhoso, mas no . Os interessados nem sempre ficaro contentes com os dados produzidos pelo processo, especialmente no comeo. Usar um
mtodo gil no significa que os interessados obtero o que desejam. Significa simplesmente
que eles podero controlar a equipe para obter o mximo valor comercial pelo menor custo.

Bibliografia
[Beck99] Kent Beck, Extreme Programming Explained: Embrace Change, Addison-Wesley, 1999.
[Cockburn2005] Alistair Cockburn, Crystal Clear: A Human-Powered Methodology for
Small Teams, Addison-Wesley, 2005.
[Highsmith2000] James A. Highsmith, Adaptive Software Development: A Collaborative
Approach to Managing Complex Systems, Dorset House, 2000.
[Newkirk2001] James Newkirk e Robert C. Martin, Extreme Programming in Practice,
Addison-Wesley, 2001.

Captulo 4

TESTE
O fogo o teste do ouro; a adversidade, o dos homens fortes.
Sneca (c. 3 A.C.-65 D.C.)

screver um teste de unidade* mais um ato de projeto do que de verificao. Trata-se


mais de documentar do que de verificar. O ato de escrever um teste de unidade fecha
vrios ciclos de informao, o menor dos quais o relativo verificao de funes.

Desenvolvimento guiado por testes


E se voc seguisse estas trs regras simples?
1. No escrever cdigo de produo at que voc tenha escrito um teste de unidade
que falhe.
2. No escrever mais de um teste de unidade do que o suficiente para falhar ou que
no compile.
3. No escrever mais qualquer cdigo de produo do que o suficiente para passar no
teste falho.
Se segussemos esses passos, trabalharamos em ciclos muito curtos. Escreveramos apenas o suficiente de um teste de unidade para faz-lo falhar e, depois, apenas o suficiente
de cdigo de produo para faz-lo passar. Alternaramos entre essas etapas a cada um
ou dois minutos.
A primeira e mais evidente consequncia que toda funo do programa tem
testes que verificam seu funcionamento. Esse conjunto de testes funciona como um

* N. de R.T.: Tambm chamado de teste unitrio.

56

DESENVOLVIMENTO GIL

impedimento para mais desenvolvimento. Ele nos diz quando danificamos inadvertidamente alguma funcionalidade j existente. Podemos adicionar funes no programa ou
alterar sua estrutura sem receio de que, no processo, estraguemos algo importante. Os
testes nos informam que o programa ainda est se comportando corretamente. Assim,
estamos muito mais livres para fazer alteraes e melhorias em nosso programa.
Um efeito mais importante, porm menos evidente, que o ato de escrever primeiro o teste nos obriga a um ponto de vista diferente. Devemos ver o programa que estamos para escrever a partir da perspectiva de um chamador desse programa. Assim,
ficamos imediatamente preocupados com a interface do programa e tambm com sua
funo. Escrevendo primeiro o teste, projetamos o software de modo a poder ser chamado convenientemente.
Alm disso, escrevendo o teste primeiro, nos obrigamos a projetar o programa de
modo que ele possa ser testado. Projetar o programa de modo que ele possa ser chamado e testado muito importante. Para que isso seja possvel, o software precisa estar
desacoplado de seu ambiente. Assim, o ato de escrever os testes primeiro nos obriga a
desacoplar o software!
Outro efeito importante de escrever testes antes que eles funcionam como uma
forma de documentao valiosa. Se voc quer saber como chamar uma funo ou como
criar um objeto, existe um teste que mostra isso. Os testes atuam como um conjunto de
exemplos que ajudam os outros programadores a saber como trabalhar com o cdigo.
Essa documentao pode ser compilada e executada. Ela permanecer atualizada. E no
pode mentir.

Exemplo de projeto com testes a priori


Apenas por diverso, escrevi recentemente uma verso de Hunt the Wumpus (Caa ao
Monstro). Esse programa um jogo de ao simples no qual o jogador anda em uma caverna, tentando matar o monstro antes de ser comido por ele. A caverna um conjunto de
cmaras interligadas por corredores. Cada cmara pode ter passagens para o norte, sul,
leste ou oeste. O jogador se movimenta dizendo ao computador em que direo deve ir.
Um dos primeiros testes que escrevi para esse programa foi testMove (Listagem
4-1). Essa funo criava um novo WumpusGame, ligava a cmara 4 cmara 5 por meio
de uma passagem para leste, colocava o jogador na cmara 4, executava o comando para
mover para leste e, ento, declarava que o jogador devia estar na cmara 5.

Listagem 4-1
[Test]
public void TestMove()
{
WumpusGame g = new WumpusGame();
g.Connect(4,5,"E");
g.GetPlayerRoom(4);
g.East();
Assert.AreEqual(5, g.GetPlayerRoom());
}

TESTE

57

Todo esse cdigo foi escrito antes de qualquer parte de WumpusGame. Aceitei o conselho de Ward Cunningham e escrevi o teste da maneira que eu queria fosse lido. Acreditei
que poderia fazer o teste passar escrevendo o cdigo que se adaptasse estrutura decorrente do teste. Isso chamado de programao intencional. Voc expressa sua inteno
em um teste antes de implement-lo, mostrando-a da maneira mais simples e clara possvel. Voc confia em que essa simplicidade e clareza apontem para uma boa estrutura para
o programa.
A programao pela inteno me levou imediatamente a uma deciso de projeto
interessante. O teste nunca utiliza uma classe Room. A ao de interligar uma cmara
outra comunica minha inteno. Parece que no preciso de uma classe Room para
facilitar essa comunicao. Em vez disso, posso simplesmente usar inteiros para representar as cmaras.
Isso pode parecer absurdo. Afinal, pode dar a impresso de que esse programa consiste exatamente em cmaras, mover-se entre cmaras, descobrir o que elas contm etc. O
projeto decorrente de minha inteno imperfeito porque no tem uma classe Room?
Eu poderia argumentar que o conceito de interligaes bem mais importante para o
jogo Wumpus do que o conceito de cmara. Poderia afirmar que esse teste inicial apontaria
para uma boa maneira de resolver o problema. Alis, acho que esse o caso, mas no a
ponto de eu tentar demonstrar. A questo que o teste esclareceu um problema de projeto
fundamental em uma etapa bastante antecipada. O ato de escrever testes primeiro um
ato de discernir entre decises de projeto.
Note que o teste o informa como o programa funciona. A maioria de ns poderia
escrever facilmente os quatro mtodos nomeados em WumpusGame a partir dessa especificao simples. Tambm poderamos nomear e escrever os outros trs comandos de
direo sem muitos problemas. Se quisssemos, posteriormente, saber como interligar
duas cmaras ou mover em uma direo especfica, esse teste nos mostraria claramente
como fazer isso. Esse teste funciona como um documento que descreve o programa e que
pode ser compilado e executado.

Isolamento do teste
O ato de escrever testes antes do cdigo de produo frequentemente expe reas no software que precisam ser desacopladas. Por exemplo, a Figura 4-1 mostra um diagrama UML
simples de um aplicativo de folha de pagamentos. A classe Payroll usa a classe EmployeeEmployee

CheckWriter
Payroll
+ writeCheck()

+ calculatePay()
+ postPayment()

Employee
Database
+ getEmployee
+ putEmployee

Figura 4-1
Modelo de folha de pagamentos acoplado.

58

DESENVOLVIMENTO GIL

Database para buscar um objeto Employee, pede para que Employee calcule seu pagamento, passa esse pagamento para o objeto CheckWriter para emitir um cheque e, por
fim, lana o pagamento no objeto Employee e grava o objeto novamente no banco de dados.
Suponha que ainda no escrevemos nada desse cdigo. At aqui, esse diagrama
est simplesmente em um quadro de avisos, aps uma rpida sesso de projeto.1 Agora, precisamos escrever os testes que especificam o comportamento do objeto Payroll.
Vrios problemas esto associados escrita desse teste. Primeiro, que banco de dados
usaremos? Payroll precisa ler algum tipo de banco de dados. Devemos escrever um
banco de dados totalmente funcional antes que possamos testar a classe Payroll? Que
dados carregamos nele? Segundo, como verificamos se foi impresso o cheque correto?
No podemos escrever um teste automatizado que procure um cheque na impressora e
verifique o valor que est nele!
A soluo para esses problemas usar o padro MOCK OBJECT.2 Podemos inserir
interfaces entre todos os colaboradores de Payroll e criar stubs de teste que implementem essas interfaces.
A Figura 4-2 mostra a estrutura. Agora a classe Payroll usa interfaces para se comunicar com EmployeeDatabase, CheckWriter e Employee. Foram criados trs MOCK
OBJECTs que implementam essas interfaces. Esses MOCK OBJECTs so consultados
pelo objeto PayrollTest para ver se o objeto Payroll os gerenciou corretamente.

Mock
CheckWriter

PayrollTest

Mock
Employee

interface

interface

Employee

CheckWriter
Payroll

+ calculatePay()
+ postPayment()

+ writeCheck()

interface

Employee
Database
+ getEmployee
+ putEmployee

Mock
Employee
Database

Figura 4-2

Payroll desacoplada usando MOCK OBJECT para teste.

1
2

[Jeffries2001]
[Mackinnon2000]

TESTE

59

Listagem 4-2
TestPayroll
[Test]
public void TestPayroll()
{
MockEmployeeDatabase db = new MockEmployeeDatabase();
MockCheckWriter w = new MockCheckWriter();
Payroll p = new Payroll(db, w);
p.PayEmployees();
Assert.IsTrue(w.ChecksWereWrittenCorrectly());
Assert.IsTrue(db.PaymentsWerePostedCorrectly());
}

A Listagem 4-2 mostra a inteno do teste. Ela cria os MOCK OBJECTs adequados, passa-os para o objeto Payroll, diz para que o objeto Payroll pague todos os
funcionrios (employees) e, ento, pede para que os MOCK OBJECTs verifiquem se todos os cheques foram escritos corretamente e se todos os pagamentos foram lanados
corretamente.
claro que esse teste est simplesmente verificando se Payroll chamou todas as
funes corretas, com todos os dados corretos. O teste no est verificando se os cheques foram emitidos ou se um banco de dados verdadeiro foi utilizado corretamente.
Em vez disso, est verificando se a classe Payroll est se comportando isoladamente
como deve.
Voc poderia se perguntar para que serve MockEmployee. Parece vivel que a classe
Employee real pudesse ser utilizada, em vez de uma simulada (mock). Se assim fosse, eu
no teria escrpulos em us-la. Porm, nesse caso, presumi que a classe Employee era
mais complexa do que o necessrio para verificar a funo de Payroll.

Desacoplamento casual
O desacoplamento de Payroll bom. Ele nos permite alternar entre diferentes bancos
de dados e emissores de cheque para testar e ampliar o aplicativo. Acho curioso o fato de
esse desacoplamento ter ocorrido pela necessidade de testar. Aparentemente, a necessidade de isolar o mdulo que est sendo testado nos obriga a desacoplar de uma forma que
favorece a estrutura global do programa. Escrever testes antes do cdigo melhora nossos
projetos.
Grande parte deste livro consiste em princpios de projeto para gerenciar dependncias. Esses princpios fornecem algumas diretrizes e tcnicas para desacoplar classes e
pacotes. Voc achar esses princpios mais vantajosos se pratic-los como parte de sua
estratgia de teste de unidade. So os testes de unidade que proporcionaro grande parte
do incentivo e da orientao para o desacoplamento.

60

DESENVOLVIMENTO GIL

Testes de aceitao
Os testes de unidade so necessrios, mas insuficientes como ferramentas de verificao.
Os testes de unidade verificam se os pequenos elementos do sistema funcionam como deveriam, mas no verificam se o sistema como um todo funciona corretamente. Os testes de
unidade so testes de caixa branca3, que verificam os mecanismos individuais do sistema.
Os testes de aceitao so testes de caixa preta4, que verificam se os requisitos do cliente
esto sendo cumpridos.
Os testes de aceitao so escritos por pessoas que no conhecem os mecanismos
internos do sistema. Esses testes podem ser escritos diretamente pelo cliente ou por
analistas de negcio, testadores ou especialistas em garantia da qualidade. Os testes de
aceitao so automatizados. Normalmente eles so redigidos em uma linguagem de especificao especial que pode ser lida e escrita por pessoas sem muitos conhecimentos
tcnicos.
Os testes de aceitao so a documentao definitiva de uma funcionalidade. Uma vez
que o cliente tenha escrito os testes de aceitao que verificam se uma funcionalidade est
correta, os programadores podem ler esses testes para realmente entender a funcionalidade. Portanto, assim como os testes de unidade servem como uma documentao que pode
ser compilada e executada dos detalhes internos do sistema, os testes de aceitao servem
como uma documentao das funcionalidades do sistema, que tambm pode ser compilada
e executada. Em resumo, os testes de aceitao se tornam o verdadeiro documento de
requisitos.
Alm disso, o ato de escrever testes de aceitao primeiro tem um impacto profundo
na arquitetura do sistema. Para que o sistema possa ser testado, ele precisa ser desacoplado no nvel alto da arquitetura. Por exemplo, a interface do usurio precisa ser desacoplada das regras de negcio de tal maneira que os testes de aceitao possam acessar essas
regras de negcio sem passar pela IU.
Nas primeiras iteraes de um projeto, a tentao fazer testes de aceitao manualmente. Isso desaconselhvel, pois tira dessas primeiras iteraes a presso pelo
desacoplamento exercida pela necessidade de automatizar os testes de aceitao. Quando voc comea a primeira iterao sabendo perfeitamente que deve automatizar os testes de aceitao, assume compromissos arquitetnicos muito diferentes. Assim como
os testes de unidade o motivam a tomar decises de projeto melhores em pequena
escala, os testes de aceitao o motivam a tomar decises arquitetnicas melhores em
grande escala.
Considere novamente o aplicativo de folha de pagamentos. Em nossa primeira
iterao, devemos ter a capacidade de adicionar e excluir funcionrios do banco de dados. Tambm devemos ter a capacidade de criar cheques-salrio para os funcionrios
que esto no banco de dados. Felizmente, precisamos lidar apenas com funcionrios
mensalistas. Os outros tipos de funcionrios foram deixados para uma iterao posterior.
Ainda no escrevemos nenhum cdigo e no investimos em projeto algum. Este
o melhor momento para comear a pensar nos testes de aceitao. Mais uma vez, a programao intencional uma ferramenta til. Devemos escrever os testes de aceitao da

3
4

Um teste que conhece e depende da estrutura interna do mdulo que est sendo testado.
Um teste que no conhece nem depende da estrutura interna do mdulo que est sendo testado.

TESTE

61

maneira como achamos que eles devem aparecer e, ento, podemos projetar o sistema de
folha de pagamentos de forma correspondente.
Quero que os testes de aceitao sejam cmodos de escrever e fceis de alterar. Quero
que eles sejam colocados em uma ferramenta de colaborao e que estejam disponveis na
rede interna para que eu possa execut-los sempre que desejar. Portanto, usarei a ferramenta de cdigo-fonte aberto FitNesse.5A ferramenta FitNesse permite que cada teste de
aceitao seja escrito como uma pgina Web simples, acessada e executada a partir de um
navegador Web.
A Figura 4-3 mostra um exemplo de teste de aceitao escrito com a ferramenta
FitNesse. O primeiro passo do teste adicionar dois funcionrios no sistema de folha
de pagamentos. O segundo pag-los. O terceiro garantir que os contracheques sejam
emitidos corretamente. Nesse exemplo, estamos supondo que o desconto uma deduo
fixa de 20%.
Evidentemente, esse tipo de teste muito fcil para os clientes lerem e escreverem.
Mas pense no que isso significa para a estrutura do sistema. As duas primeiras tabelas do
teste so funes do aplicativo de folha de pagamentos. Se voc fosse escrever o sistema
de folha de pagamentos como uma estrutura reutilizvel, elas corresponderiam s funes
de API (interface de programao de aplicativos). Alis, para que a ferramenta FitNesse
chame essas funes, as APIs devem ser escritas.6
Primeiro, adicionamos dois funcionrios.

Adicionar funcionrios
id

nome

Jeff Languid

salrio
1000.00

Kelp Holland

2000.00

Em seguida, os pagamos.
Criar contracheque
data de
pagamento

nmero do
contracheque

31/1/2001

1000

Garantir que o desconto fixo de 20% seja feito.

Inspecionar contracheques
id

salrio bruto

salrio lquido

1000

800

2000

1600

Figura 4-3
Exemplo de teste de aceitao.

5
6

www.fitnesse.org.
A maneira pela qual a ferramenta FitNesse chama essas funes de API est fora do escopo deste livro. Para
obter mais informaes, consulte a documentao da ferramenta FitNesse. Consulte tambm [Mugridge2005].

62

DESENVOLVIMENTO GIL

Arquitetura casual
Observe a presso que os testes de aceitao fizeram em relao arquitetura do sistema
de folha de pagamentos. O prprio fato de termos pensado primeiro nos testes nos levou
ideia de uma API para as funes desse sistema. Claramente, a IU utilizar essa API
para conseguir alcanar os seus objetivos. Observe tambm que a impresso dos contracheques deve ser desacoplada da funo Create Paychecks. Essas so boas decises
arquitetnicas.

Concluso
Quanto mais simples for a execuo de um conjunto de testes, mais frequentemente esses
testes sero executados. Quanto mais testes forem executados, mais cedo qualquer desvio
deles ser encontrado. Se pudermos executar os testes vrias vezes por dia, ento o sistema nunca estar danificado por mais do que alguns minutos. Essa uma meta razovel.
Simplesmente no permitimos que o sistema se deteriore. Uma vez que ele funcione em
certo nvel, nunca se deteriorar para um nvel inferior.
Apesar disso, a verificao apenas uma das vantagens de se escrever testes. Tanto
os testes de unidade como os testes de aceitao so uma forma de documentao. Essa
documentao pode ser compilada e executada e, portanto, precisa e confivel. Alm
disso, esses testes so escritos em linguagens claras, fceis para seu pblico ler. Os programadores podem ler os testes de unidade porque eles so escritos na linguagem de
programao que eles utilizam. Os clientes podem ler os testes de aceitao porque so
escritos em uma linguagem tabular simples.
Possivelmente, a vantagem mais importante de todos esses testes o impacto que
eles tm na arquitetura e no projeto. Para que um mdulo ou um aplicativo se torne passvel de teste, o mdulo ou aplicativo tambm deve ser desacoplado. Quanto mais passvel
de teste ele for, mais desacoplado ser. O ato de considerar testes de aceitao e de unidade amplos tem um efeito profundamente positivo na estrutura do software.

Bibliografia
[Jeffries2001] Ron Jeffries, Extreme Programming Installed, Addison-Wesley, 2001.
[Mackinnon2000] Tim Mackinnon, Steve Freeman, and Philip Craig, Endo-Testing: Unit
Testing with Mock Objects, in Giancarlo Succi and Michele Marchesi, Extreme Programming Examined, Addison-Wesley, 2001.
[Mugridge2005] Rick Mugridge and Ward Cunningham, Fit for Developing Software: Framework for Integrated Tests, Addison-Wesley, 2005.

Captulo 5

REFATORAO
O nico elemento que est se tornando escasso em
um mundo de fartura a ateno humana.
Kevin Kelly, na Wired

ste captulo fala sobre prestar ateno ao que voc est fazendo e garantir que esteja
dando o seu melhor. Discutimos sobre a diferena entre fazer algo funcionar e fazer
algo direito. O valor que damos estrutura de nosso cdigo tambm abordado.
Em sua obra clssica, Refactoring, Martin Fowler define a refatorao como o
processo de alterar um sistema de software de tal maneira que no mude o comportamento externo do cdigo, embora melhore sua estrutura interna.1 Por que desejaramos melhorar a estrutura de um cdigo que j funciona? Em time que est ganhando,
no se mexe, correto?
Todo mdulo de software tem trs funes. A primeira a que ele realiza enquanto est sendo executado. Essa funo a razo da existncia do mdulo. A segunda
funo de um mdulo permitir alterao. Quase todos os mdulos mudaro durante
sua vida e os desenvolvedores so responsveis por garantir que tais mudanas sejam
as mais simples de fazer. Um mdulo difcil de alterar precisa ser consertado, mesmo
que funcione. A terceira funo de um mdulo se comunicar com seus leitores. Desenvolvedores no familiarizados com o mdulo devem ser capazes de l-lo e entend-lo sem muito exerccio mental. Um mdulo que no se comunica est estragado e
precisa ser consertado.
O que necessrio para tornar um mdulo fcil de ler e de alterar? Grande parte
deste livro dedicada aos princpios e padres cujo principal objetivo ajud-lo a criar
mdulos flexveis e adaptveis. Mas necessrio algo mais do que apenas princpios e
padres para se produzir um mdulo fcil de ler e alterar. Isso requer ateno. Requer
disciplina. Requer paixo para criar a perfeio.
1

[Fowler99], p. xvi

64

DESENVOLVIMENTO GIL

Um exemplo de refatorao simples: gerao de nmeros primos


Considere o cdigo da Listagem 5-1. Esse programa gera nmeros primos. Trata-se de uma
funo enorme, com muitas variveis de uma s letra e comentrios para nos ajudar a l-lo.

Listagem 5-1
GeneratePrimes.cs, verso 1
/// <remark>
/// Esta classe gera nmeros primos at um mximo especificado
/// pelo usurio. O algoritmo usado o Crivo de Eratstenes.
///
/// Eratstenes de Cirene, b. c. 276 AC, Cirene, Lbia -/// d. c. 194, Alexandria. O primeiro homem a calcular a
/// circunferncia da Terra. Tambm conhecido por trabalhar em
/// calendrios com anos bissextos e administrar a biblioteca
/// de Alexandria.
///
/// O algoritmo muito simples. Dado um array de inteiros,
/// comeando em 2, exclui todos os mltiplos de 2. Encontra
/// o prximo inteiro no excludo e exclui todos os seus
/// mltiplos. Repete at que voc tenha passado a raiz
/// quadrada do valor mximo.
///
/// Escrito em Java por Robert C. Martin em 9 de Dez de 1999
/// Transformado em C# por Micah Martin em 12 de Jan de 2005.
///</remark>
using System;
/// <summary>
/// autor: Robert C. Martin
/// </summary>
public class GeneratePrimes
{
///<summary>
/// Gera um array de nmeros primos.
///</summary>
///
/// <param name="maxValue">O limite da gerao.</param>
public static int[] GeneratePrimeNumbers(int maxValue)
{
if (maxValue >= 2) // nico caso vlido
{
// declaraes
int s = maxValue + 1; // tamanho do array
bool[] f = new bool[s];
int i;
// inicializa o array como true
for (i = 0; i < s; i++)

REFATORAO

65

f[i] = true;
// descarta os no primos conhecidos
f[0] = f[1] = false;
// crivo
int j;
for (i = 2; i < Math.Sqrt(s) + 1; i++)
{
if(f[i]) // se i no excludo, exclui seus mltiplos
{
for (j = 2 * i; j < s; j += i)
f[j] = false; // o mltiplo no primo
}
}
// quantos nmeros primos existem?
int count = 0;
for (i = 0; i < s; i++)
{
if (f[i])
count++; // incrementa a contagem
}
int[] primes = new int[count];
// move os nmeros primos para o resultado
for (i = 0, j = 0; i < s; i++)
{
if (f[i]) // se for primo
primes[j++] = i;
}
return primes; // retorna os nmeros primos
}
else // maxValue < 2
return new int[0]; // retorna um array nulo se a entrada for invlida.
}
}

Teste de unidade
O teste de unidade de GeneratePrimes aparece na Listagem 5-2. Ele adota uma estratgia estatstica, verificando se o gerador pode gerar nmeros primos at 0, 2, 3 e 100.
No primeiro caso, no deve haver nmeros primos. No segundo, deve haver um nmero primo e deve ser o nmero 2. No terceiro, deve haver dois nmeros primos e devem
ser os nmeros 2 e 3. No ltimo caso, deve haver 25 nmeros primos, o ltimo dos
quais 97. Se todos os testes passarem, presumimos que o gerador est funcionando.
Duvido que isso seja infalvel, mas no consigo imaginar um cenrio razovel no qual
esses testes passariam e a funo falharia.

66

DESENVOLVIMENTO GIL

Listagem 5-2
GeneratePrimesTest.cs
using NUnit.Framework;
[TestFixture]
public class GeneratePrimesTest
{
[Test]
public void TestPrimes()
{
int[] nullArray = GeneratePrimes.GeneratePrimeNumbers(0);
Assert.AreEqual(nullArray.Length, 0);
int[] minArray = GeneratePrimes.GeneratePrimeNumbers(2);
Assert.AreEqual(minArray.Length, 1);
Assert.AreEqual(minArray[0], 2);
int[] threeArray = GeneratePrimes.GeneratePrimeNumbers(3);
Assert.AreEqual(threeArray.Length, 2);
Assert.AreEqual(threeArray[0], 2);
Assert.AreEqual(threeArray[1], 3);
int[] centArray = GeneratePrimes.GeneratePrimeNumbers(100);
Assert.AreEqual(centArray.Length, 25);
Assert.AreEqual(centArray[24], 97);
}
}

Refatorao
Para ajudar a refatorar esse programa, estou usando Visual Studio com o complemento
de refatorao ReSharper da JetBrains. Essa ferramenta torna trivial extrair mtodos e
renomear variveis e classes.
Parece bastante claro que a funo principal quer ser trs funes distintas. A primeira inicializa todas as variveis e monta o filtro (crivo). A segunda executa a filtragem,
e a terceira carrega os resultados filtrados em um array de inteiros. Para mostrar essa
estrutura mais claramente, transcrevi essas funes em trs mtodos distintos (Listagem
5-3). Removi tambm alguns comentrios desnecessrios e mudei o nome da classe para
PrimeGenerator. Todos os testes ainda funcionavam.
Transcrever as trs funes me obrigou a promover algumas das variveis da funo
para campos estticos da classe. Isso torna muito mais claro quais variveis so locais e
quais tm influncia mais ampla.

REFATORAO

Listagem 5-3
PrimeGenerator.cs, verso 2
///<remark>
/// Esta classe gera nmeros primos at um mximo especificado
/// pelo usurio. O algoritmo usado o Crivo de Eratstenes.
/// Dado um array de inteiros, comeando em 2,
/// encontra o primeiro inteiro no excludo e exclui todos
/// os seus mltiplos. Repete at que no existam mais
/// mltiplos no array.
///</remark>
using System;
public class PrimeGenerator
{
private static int s;
private static bool[] f;
private static int[] primes;
public static int[] GeneratePrimeNumbers(int maxValue)
{
if (maxValue < 2)
return new int[0];
else
{
InitializeSieve(maxValue);
Sieve();
LoadPrimes();
return primes; // retorna os nmeros primos
}
}
private static void LoadPrimes()
{
int i;
int j;
// quantos nmeros primos existem?
int count = 0;
for (i = 0; i < s; i++)
{
if (f[i])
count++; // incrementa a contagem
}
primes = new int[count];
// move os nmeros primos para o resultado
for (i = 0, j = 0; i < s; i++)
{
if (f[i]) // se for primo
primes[j++] = i;
}

67

68

DESENVOLVIMENTO GIL

}
private static void Sieve()
{
int i;
int j;
for (i = 2; i < Math.Sqrt(s) + 1; i++)
{
if(f[i]) // se i no excludo, exclui seus mltiplos.
{
for (j = 2 * i; j < s; j += i)
f[j] = false; // o mltiplo no primo
}
}
}
private static void InitializeSieve(int maxValue)
{
// declaraes
s = maxValue + 1; // tamanho do array
f = new bool[s];
int i;
// inicializa o array como true.
for (i = 0; i < s; i++)
f[i] = true;
// descarta os no primos conhecidos
f[0] = f[1] = false;
}
}

Listagem 5-4
PrimeGenerator.cs, verso 3 (parcial)
public class PrimeGenerator
{
private static bool[] f;
private static int[] result;
public static int[] GeneratePrimeNumbers(int maxValue)
{
if (maxValue < 2)
return new int[0];
else

REFATORAO

69

{
InitializeArrayOfIntegers(maxValue);
CrossOutMultiples();
PutUncrossedIntegersIntoResult();
return result;
}
}
private static void InitializeArrayOfIntegers(int maxValue)
{
// declaraes
f = new bool[maxValue + 1];
f[0] = f[1] = false; //nem primos nem mltiplos.
for (int i = 2; i < f.Length; i++)
f[i] = true;
}
}

A funo InitializeSieve um pouco complicada, de modo que a limpei bastante


(Listagem 5-4). Em primeiro lugar, substitu todos os usos da varivel s por f.Length. Depois, mudei os nomes das trs funes para algo um pouco mais expressivo. Por fim, reorganizei a parte interna de InitializeArrayOfIntegers (nascida com o nome InitializeSieve) para ser um pouco mais legvel. Todos os testes ainda funcionavam.
Em seguida, considerei CrossOutMultiples. Nessa funo e em outras havia vrias
declaraes da forma if(f[i] == true). O objetivo era verificar se i no tinha sido excluda; portanto, mudei o nome de f para unCrossed. Mas isso levou a declaraes horrveis, como unCrossed[i] = false. Achei a dupla negativa confusa. Portanto, mudei o
nome do array para isCrossed e alterei o sentido de todos os booleanos. Todos os testes
ainda funcionavam.
Descartei a inicializao que configurava isCrossed[0] e isCrossed[1] como
true e simplesmente garanti que nenhuma parte da funo usasse o array isCrossed
para ndices menores do que 2. Retirei o loop interno da funo CrossOutMultiples e o
chamei de CrossOutMultiplesOf. Tambm achei que if (isCrossed[i] == false) era
confuso, de modo que criei uma funo chamada NotCrossed e alterei a declarao de if
para if (NotCrossed(i)). Todos os testes ainda funcionavam.
Dediquei um bom tempo para escrever um comentrio que tentasse explicar por
que voc precisa iterar apenas at a raiz quadrada do tamanho do array. Isso me levou a
colocar o clculo em uma funo onde eu pudesse escrever o comentrio explicativo. Ao
escrever o comentrio, percebi que a raiz quadrada o mximo fator primo de qualquer
um dos inteiros no array. Portanto, escolhi esse nome (maxPrimeFactor) para as variveis
e funes que tratavam disso. O resultado de todas essas refatoraes aparece na Listagem 5-5. Todos os testes ainda funcionavam.

70

DESENVOLVIMENTO GIL

Listagem 5-5
PrimeGenerator.cs, verso 4 (parcial)
public class PrimeGenerator
{
private static bool[] isCrossed;
private static int[] result;
public static int[] GeneratePrimeNumbers(int maxValue)
{
if (maxValue < 2)
return new int[0];
else
{
InitializeArrayOfIntegers(maxValue);
CrossOutMultiples();
PutUncrossedIntegersIntoResult();
return result;
}
}
private static void InitializeArrayOfIntegers(int maxValue)
{
isCrossed = new bool[maxValue + 1];
for (int i = 2; i < isCrossed.Length; i++)
isCrossed[i] = false;
}
private static void CrossOutMultiples()
{
int maxPrimeFactor = CalcMaxPrimeFactor();
for (int i = 2; i < maxPrimeFactor + 1; i++)
{
if(NotCrossed(i))
CrossOutputMultiplesOf(i);
}
}
private static int CalcMaxPrimeFactor()
{
// Exclumos todos os mltiplos de p, onde p primo.
// Assim, todos os mltiplos excludos tm p e q como
// fatores. Se p > sqrt do tamanho do array, ento
// q nunca ser maior do que 1. Assim, p o
// maior fator primo no array e tambm
// o limite da iterao.
double maxPrimeFactor = Math.Sqrt(isCrossed.Length) + 1;
return (int) maxPrimeFactor;
}

REFATORAO

71

private static void CrossOutputMultiplesOf(int i)


{
for (int multiple = 2*i;
multiple < isCrossed.Length;
multiple += i)
isCrossed[multiple] = true;
}
private static bool NotCrossed(int i)
{
return isCrossed[i] == false;
}
}

A ltima funo a refatorar PutUncrossedIntegersIntoResult. Esse mtodo


tem duas partes. A primeira conta o nmero de inteiros no excludos no array e cria o
array de resultados com esse tamanho. A segunda move os inteiros no excludos para o
array de resultados. Coloquei a primeira parte em sua prpria funo e fiz algumas limpezas (Listagem 5-6). Todos os testes ainda funcionavam.

Listagem 5-6
PrimerGenerator.cs, verso 5 (parcial)
private static void PutUncrossedIntegersIntoResult()
{
result = new int[NumberOfUncrossedIntegers()];
for (int j = 0, i = 2; i < isCrossed.Length; i++)
{
if (NotCrossed(i))
result[j++] = i;
}
}
private static int NumberOfUncrossedIntegers()
{
int count = 0;
for (int i = 2; i < isCrossed.Length; i++)
{
if (NotCrossed(i))
count++; // incrementa a contagem.
}
return count;
}

72

DESENVOLVIMENTO GIL

A releitura final
Em seguida, fiz uma ltima varredura pelo programa inteiro, lendo-o do comeo ao fim,
assim como algum leria uma prova geomtrica. Essa uma etapa importante. At aqui,
estive refatorando fragmentos. Agora, quero ver se o programa inteiro permanece unido
como um todo legvel.
Logo percebo que no gosto do nome InitializeArrayOfIntegers. O que de fato
est sendo inicializado no um array de inteiros, mas um array de valores booleanos.
Mas InitializeArrayOfBooleans no uma melhoria. O que estamos realmente fazendo nesse mtodo no excluir todos os inteiros relevantes para que, ento, possamos
excluir os mltiplos. Assim, mudei o nome para UncrossIntegersUpTo. Percebi tambm
que no gostei do nome isCrossed para o array de booleanos. Portanto, mudei para
crossedOut. Todos os testes ainda funcionam.
Algum poderia pensar que estou sendo frvolo com essas mudanas de nome, mas
com um navegador de refatorao voc pode se permitir fazer esses tipos de ajustes; eles
custam praticamente nada. Mesmo sem um navegador de refatorao, uma operao simples de localizar e substituir muito barata. E os testes reduzem significativamente as
chances de estragarmos algo involuntariamente.
No sei o que eu estava fumando quando escrevi toda aquela porcaria de maxPrimeFactor. Oh, Deus! A raiz quadrada do tamanho do array no necessariamente um nmero primo. Esse mtodo no calculava o mximo fator primo. O comentrio explicativo
simplesmente estava errado. Portanto, reescrevi o comentrio para explicar melhor o fundamento lgico por trs da raiz quadrada e renomeei todas as variveis adequadamente.2
Todos os testes ainda funcionam.
Que diabos esse +1 est fazendo ali? Deve ter sido paranoia. Eu estava com receio
de que uma raiz quadrada fracionria fosse convertida em um inteiro pequeno demais
para servir como limite de iterao. Mas isso tolice. O verdadeiro limite de iterao
o maior nmero primo menor ou igual raiz quadrada do tamanho do array. Vou me
livrar do +1.
Todos os testes funcionam, mas essa ltima alterao me deixa bastante nervoso.
Eu entendo o fundamento lgico por trs da raiz quadrada, mas tive a sensao horrvel
de que poderia haver alguns casos ocultos que no estavam sendo abordados. Portanto,
vou escrever outro teste que verifique se no existem mltiplos em qualquer das listas de
nmeros primos entre 2 e 500. (Consulte a funo TestExhaustive na Listagem 5-8.) O
novo teste passa e meus receios foram atenuados.
O restante do cdigo me parece perfeito. Portanto, acho que terminamos. A verso
final aparece nas listagens 5-7 e 5-8.

Certa vez, observei Kent Beck refatorar esse mesmo programa. Ele aboliu completamente a raiz quadrada.
Seu fundamento lgico era que a raiz quadrada era difcil de entender e que nenhum teste falharia se voc
iterasse at o tamanho do array. No sou capaz de abrir mo da eficincia. Acho que isso mostra minhas
razes na linguagem assembly.

REFATORAO

Listagem 5-7
PrimeGenerator.cs (final)
///<remark>
/// Esta classe gera nmeros primos at um mximo especificado
/// pelo usurio. O algoritmo usado o Crivo de Eratstenes.
/// Dado um array de inteiros, comeando em 2,
/// encontra o primeiro inteiro no excludo e exclui todos
/// os seus mltiplos. Repete at que no existam
/// mais mltiplos no array.
///</remark>
using System;
public class PrimeGenerator
{
private static bool[] crossedOut;
private static int[] result;
public static int[] GeneratePrimeNumbers(int maxValue)
{
if (maxValue < 2)
return new int[0];
else
{
UncrossIntegersUpTo(maxValue);
CrossOutMultiples();
PutUncrossedIntegersIntoResult();
return result;
}
}
private static void UncrossIntegersUpTo(int maxValue)
{
crossedOut = new bool[maxValue + 1];
for (int i = 2; i < crossedOut.Length; i++)
crossedOut[i] = false;
}
private static void PutUncrossedIntegersIntoResult()
{
result = new int[NumberOfUncrossedIntegers()];
for (int j = 0, i = 2; i < crossedOut.Length; i++)
{
if (NotCrossed(i))
result[j++] = i;
}
}

73

74

DESENVOLVIMENTO GIL

private static int NumberOfUncrossedIntegers()


{
int count = 0;
for (int i = 2; i < crossedOut.Length; i++)
{
if (NotCrossed(i))
count++; // incrementa a contagem.
}
return count;
}
private static void CrossOutMultiples()
{
int limit = DetermineIterationLimit();
for (int i = 2; i <= limit; i++)
{
if(NotCrossed(i))
CrossOutputMultiplesOf(i);
}
}
private static int DetermineIterationLimit()
{
// Todo mltiplo no array tem um fator primo que
// menor ou igual raiz do tamanho do array;
// portanto, no precisamos excluir mltiplos de nmeros
// maiores do que essa raiz.
double iterationLimit = Math.Sqrt(crossedOut.Length);
return (int) iterationLimit;
}
private static void CrossOutputMultiplesOf(int i)
{
for (int multiple = 2*i;
multiple < crossedOut.Length;
multiple += i)
crossedOut[multiple] = true;
}
private static bool NotCrossed(int i)
{
return crossedOut[i] == false;
}
}

REFATORAO

Listagem 5-8
GeneratePrimesTest.cs (final)
using NUnit.Framework;
[TestFixture]
public class GeneratePrimesTest
{
[Test]
public void TestPrimes()
{
int[] nullArray = PrimeGenerator.GeneratePrimeNumbers(0);
Assert.AreEqual(nullArray.Length, 0);
int[] minArray = PrimeGenerator.GeneratePrimeNumbers(2);
Assert.AreEqual(minArray.Length, 1);
Assert.AreEqual(minArray[0], 2);
int[] threeArray = PrimeGenerator.GeneratePrimeNumbers(3);
Assert.AreEqual(threeArray.Length, 2);
Assert.AreEqual(threeArray[0], 2);
Assert.AreEqual(threeArray[1], 3);
int[] centArray = PrimeGenerator.GeneratePrimeNumbers(100);
Assert.AreEqual(centArray.Length, 25);
Assert.AreEqual(centArray[24], 97);
}
[Test]
public void TestExhaustive()
{
for (int i = 2; i<500; i++)
VerifyPrimeList(PrimeGenerator.GeneratePrimeNumbers(i));
}
private void VerifyPrimeList(int[] list)
{
for (int i=0; i<list.Length; i++)
VerifyPrime(list[i]);
}
private void VerifyPrime(int n)
{
for (int factor=2; factor<n; factor++)
Assert.IsTrue(n%factor != 0);
}
}

75

76

DESENVOLVIMENTO GIL

Concluso
O resultado final desse programa lido de forma muito melhor do que no incio. Ele
tambm funciona um pouco melhor. Estou muito satisfeito com o resultado. O programa muito mais fcil de entender e, portanto, muito mais fcil de alterar. Alm disso, a
estrutura do programa isolou suas partes. Isso tambm torna o programa mais fcil de
alterar.
Voc pode pensar remover funes que so chamadas apenas uma vez pode afetar o
desempenho adversamente. Eu acho que, na maioria dos casos, a maior clareza compensa
alguns nanossegundos a mais. Contudo, pode haver loops internos profundos onde esses
poucos nanossegundos sero dispendiosos. Meu conselho presumir que o custo ser
desprezvel e esperar que se prove o contrrio.
Esse tempo investido valeu a pena? Afinal, a funo j estava correta quando comeamos. Recomendo com veemncia que voc sempre pratique tal refatorao em todo
mdulo que escrever e em todo mdulo que mantiver. O investimento de tempo muito
pequeno comparado com o trabalho que estar evitando para voc e para outros no futuro
prximo.
Refatorar como limpar a cozinha depois do jantar. Na primeira vez que voc ignora
a limpeza, termina o jantar mais cedo. Mas a falta de pratos limpos e de espao de trabalho torna o jantar mais demorado de preparar no dia seguinte. Isso o leva a querer fugir da
limpeza novamente. Alis, voc sempre pode terminar o jantar mais rapidamente hoje, se
ignorar a limpeza. Mas a baguna s aumenta. Ento, voc acabar perdendo muito tempo
procurando os utenslios de cozinha corretos, removendo a comida ressecada incrustada
nas vasilhas, esfregando-as para que estejam devidamente limpas etc. O jantar demora
uma infinidade. Ignorar a limpeza no o torna mais rpido.
O objetivo da refatorao, conforme descrito neste captulo, limpar seu cdigo todos os dias, a toda hora e a todo minuto. No queremos que a baguna aumente. No
queremos ter de esfregar e arear os trechos incrustados que se acumulam com o tempo.
Queremos ser capazes de ampliar e modificar nossos sistemas com um mnimo de trabalho. O mais importante capacitador dessa habilidade a limpeza do cdigo.
No consigo enfatizar isso o suficiente. Todos os princpios e padres deste livro
nada adiantam se o cdigo dentro dos quais eles so utilizados for uma baguna. Antes de
investir em princpios e padres, invista em cdigo limpo.

Bibliografia
[Fowler99] Martin Fowler, Refactoring: Improving the Design of Existing Code, Addison-Wesley, 1999.

Captulo 6

UM EPISDIO DE
PROGRAMAO
Projetar e programar so atividades humanas;
esquea isso e tudo estar perdido.
Bjarne Stroustrup, 1991

ara demonstrar as prticas de programao geis, Bob Koss (RSK) e Bob Martin (RCM)
programaro em dupla um aplicativo simples, enquanto voc fica observa como uma
mosca na parede. Para criar nosso aplicativo, usaremos desenvolvimento guiado por testes e
muita refatorao. O que se segue uma reproduo bastante fiel de um episdio de programao que os dois Bobs fizeram em um quarto de hotel, no final de 2000.

Cometemos muitos erros enquanto programvamos. Alguns deles foram de cdigo,


outros de lgica, alguns de projeto e outros nos requisitos. medida que voc ler, nos ver
debatendo em todas essas reas, identificando e, finalmente, lidando com nossos erros e
concepes erradas. O processo desordenado, como todos os processos humanos. O resultado: bem, a ordem que resultou de um processo to desordenado espantosa.
O programa calcula o placar de um jogo de boliche; portanto, saber as regras ser importante. Se voc no conhece as regras do boliche, consulte o quadro da pgina 120.

O jogo de boliche
RCM:

Voc me ajuda a escrever um pequeno aplicativo que calcule os pontos do boliche?

RSK:

(Pensa consigo mesmo: a prtica XP da programao em pares afirma que no


posso negar um pedido de ajuda. Quando seu chefe quem est pedindo, ento)
Claro, Bob, ficarei contente em ajudar.

RCM:

Certo, excelente. Quero escrever um aplicativo que leve em conta as regras da


federao de boliche. Ele precisa registrar todos os jogos, determinar as classificaes das equipes, definir os vencedores e perdedores de cada competio
semanal e mostrar a pontuao de cada jogo com preciso.

78

DESENVOLVIMENTO GIL

RSK:

Legal. Eu j fui um bom jogador de boliche. Ser divertido. Voc citou vrias histrias de usurio; com qual delas gostaria de comear?

RCM:

Vamos comear com a contagem de um jogo s.

RSK:

Certo. O que isso significa? Quais so as entradas e sadas dessa histria?

RCM:

A mim me parece que as entradas so simplesmente uma sequncia de arremessos. Um arremesso um inteiro que informa quantos pinos foram derrubados
pela bola. A sada a contagem de cada quadro.

RSK:

Estou supondo que voc est atuando como cliente neste exerccio; portanto, em
que forma voc deseja que as entradas e sadas estejam?

RCM:

Sim, eu sou o cliente. Precisaremos de uma funo que seja chamada para somar
os arremessos e de outra que obtenha o placar. Algo do tipo:
ThrowBall(6);
ThrowBall(3);
Assert.AreEqual(9, GetScore());

RSK:

Certo, vamos precisar de alguns dados de teste. Deixe-me esboar o desenho de


um carto de marcao. [Consulte a Figura 6-1.]

1 4

4 5

14

29

49

0 1 7
60

61

77

6
97

2
117

133

Figura 6-1
Carto de marcao tpico de jogo de boliche.
RCM:

Esse cara muito irregular.

RSK:

Ou est bbado, mas isso servir como um teste de aceitao decente.

RCM:

Precisaremos de outros, mas vamos tratar disso depois. Como devemos comear? Devemos propor um projeto para o sistema?

RSK:

Queria um diagrama UML que mostrasse a ideia geral do domnio do problema


que podemos ver a partir do carto de pontuao. Isso fornecer alguns candidatos a objetos que poderemos explorar melhor no cdigo.

UM EPISDIO DE PROGRAMAO

79

RCM:

(Colocando seu chapu de projetista de objetos poderoso.) Certo, evidentemente,


um objeto jogo (Game) consiste em uma sequncia de dez quadros (Frame). Cada
objeto quadro contm um, dois ou trs arremessos (Throw).

RSK:

Genial. Era exatamente isso que eu estava pensando. Deixe-me desenhar isso
rapidamente. [Consulte a Figura 6-2.]
10
Game

1..3
Frame

Throw

Figura 6-2
Diagrama UML do carto de marcao para boliche.
RSK:

Bem, escolha uma classe, qualquer classe. Devemos comear no final do encadeamento de dependncia e trabalhar de trs para diante? Isso tornar o teste
mais fcil.

RCM:

Claro, por que no? Vamos criar um caso de teste para a classe Throw.

RSK:

(Comea a digitar.)
//ThrowTest.cs-------------------------------using NUnit.Framework;
[TestFixture]
public class ThrowTest
{
[Test]
public void Test???
}

RSK:

Voc tem ideia de qual deve ser o comportamento de um objeto Throw?

RCM:

Ele contm o nmero de pinos derrubados pelo jogador.

RSK:

Certo, voc acabou de dizer em poucas palavras que, na verdade, ele no faz
nada. Talvez devamos voltar a ele e nos concentrarmos em um objeto que tenha
de fato um comportamento, em vez de ser simplesmente um armazm de dados.

RCM:

Humm. Voc quer dizer que a classe Throw poderia no existir?

RSK:

Bem, se ela no tem um comportamento, que importncia teria? Ainda no sei se


ela existe ou no. Eu acharia mais produtivo se trabalhssemos com um objeto
que tivesse mais do que mtodos set e get. Mas se voc quer pilotar (empurrando o teclado para RCM).

RCM:

Vamos mover o encadeamento de dependncia para Frame e ver se podemos escrever casos de teste que nos obriguem a terminar Throw (empurrando o teclado
de volta para RSK).

RSK:

(Refletindo se RCM o estava levando a um beco sem sada para ensin-lo ou se


estava realmente concordando.) Certo, arquivo novo, novo caso de teste.

80

DESENVOLVIMENTO GIL

//FrameTest.cs-----------------------------------using NUnit.Framework;
[TestFixture]
public class FrameTest
{
[Test]
public void Test???
}

RCM:

Tudo bem, essa a segunda vez que digitamos isso. Agora, voc consegue imaginar algum caso de teste interessante para Frame?

RSK:

Um Frame poderia fornecer sua pontuao, o nmero de pinos em cada arremesso,


se foi um strike ou um spare

RCM:

Certo, mostre-me o cdigo.

RSK:

(digita)
//FrameTest.cs----------------------------using NUnit.Framework;
[TestFixture]
public class FrameTest
{
[Test]
public void TestScoreNoThrows()
{
Frame f = new Frame();
Assert.AreEqual(0, f.Score);
}
}
//Frame.cs--------------------------------------public class Frame
{
public int Score
{
get { return 0; }
}
}

RCM:

Est bem, o caso de teste passa. Mas Score uma propriedade realmente estpida. Ela falhar se adicionarmos um arremesso em Frame. Ento, vamos escrever
o caso de teste que adicione alguns arremessos e depois verifique a pontuao.
//FrameTest.cs--------------------------------[Test]
public void TestAddOneThrow()
{
Frame f = new Frame();
f.Add(5);
Assert.AreEqual(5, f.Score);
}

UM EPISDIO DE PROGRAMAO

RCM:

Isso no compila. No existe um mtodo Add em Frame.

RSK:

Aposto que, se voc definir o mtodo, ele compilar ;-)

RCM:

(Digita.)

81

//Frame.cs--------------------------------------public class Frame


{
public int Score
{
get { return 0 };
}
public void Add(Throw t)
{
}
}

RCM:

(Pensando em voz alta.) Isso no compila, pois no escrevemos a classe Throw.

RSK:

Diga-me, Bob. O teste est passando um inteiro e o mtodo espera um objeto


Throw. Voc no pode ter as duas coisas. Antes de tomarmos novamente o caminho de Throw, voc pode descrever seu comportamento?

RCM:

Uau! Eu no tinha reparado que tinha escrito f.Add(5). Eu deveria ter escrito f.Add(new Throw(5)), mas isso horrvel. O que quero mesmo escrever
f.Add(5).

RSK:

Feio ou no, vamos deixar a esttica de lado por enquanto. Voc pode descrever
algum comportamento de um objeto Throw? Resposta binria, Bob.

RCM:

101101011010100101. No sei se existe algum comportamento em Throw;


estou comeando a achar que Throw apenas um int. Mas, no precisamos
considerar isso ainda, pois podemos escrever Frame.Add de forma a aceitar
um int.

RSK:

Ento eu acho que devemos fazer isso justamente porque simples. Quando nos
sentirmos mal, faremos algo mais sofisticado.

RCM:

Combinado.
//Frame.cs--------------------------------------public class Frame
{
public int Score
{
get { return 0};
}
public void Add(int pins)
{
}
}

RCM:

Certo, isso compila e o teste falha. Agora, vamos fazer o teste passar.

82

DESENVOLVIMENTO GIL

//Frame.cs--------------------------------------public class Frame


{
private int score;
public int Score
{
get { return score; }
}
public void Add(int pins)
{
score += pins;
}
}

RCM:

Isso compila e os testes passam. Mas claramente simplista. Qual o prximo


caso de teste?

RSK:

Podemos fazer uma pausa primeiro?


------------------------------Break----------------------------

RCM:

Isso est melhor. Frame.Add uma funo frgil. E se voc cham-la com um 11?

RSK:

Ela pode lanar uma exceo se isso acontecer. Mas quem a est chamando? Isso
vai ser uma estrutura de aplicativo que milhares de pessoas usaro, e precisaremos
proteg-la contra tais coisas, ou vai ser usada somente por voc? Se for o segundo
caso, simplesmente no a chame com um 11 (risos).

RCM:

Bem pensado. Os testes do resto do sistema obtero um argumento invlido. Se


tivermos problemas, podemos colocar a verificao mais tarde.
Ento, a funo Add no trata ainda de strikes ou spares. Vamos escrever
um caso de teste que expresse isso.

RSK:

Hummmm. Se chamamos Add(10) para representar um strike, o que GetScore()


deve retornar? No sei como escrever a declarao; portanto, talvez estejamos fazendo a pergunta errada. Ou ento estamos fazendo a pergunta certa com o objeto
errado.

RCM:

Quando voc chama Add(10) ou Add(3), seguido de Add(7), no tem sentido


chamar Score em Frame. Frame teria de examinar suas instncias posteriores
para calcular a pontuao. Se instncias posteriores de Frame no existirem, ele
teria de retornar algo horrvel, como -1. No quero retornar -1.

RSK:

Sim, tambm detesto a ideia do -1. Voc introduziu a ideia de Frames saber sobre
outros Frames. Quem est guardando esses diferentes objetos Frame?

RCM:

O objeto Game.

RSK:

Portanto, Game depende de Frame e Frame, por sua vez, depende de Game. Odeio
isso.

RCM:

Frames no precisam depender de Game; eles poderiam ser organizados em uma


lista encadeada. Cada Frame poderia conter ponteiros para seus prximos Frames e para os anteriores. Para obter a pontuao de um Frame, o Frame olharia

UM EPISDIO DE PROGRAMAO

83

para trs, para obter a pontuao do Frame anterior, e olharia para frente para as
bolas que conseguissem um spare ou um strike.
RSK:

Estou me sentindo meio burro, pois no consigo visualizar isso. Mostre-me algum cdigo.

RCM:

Certo. Mas, precisamos de um caso de teste primeiro.

RSK:

Para Game ou outro teste para Frame?

RCM:

Acho que precisamos de um para Game, pois ser Game que construir os Frames
e os interligar.

RSK:

Quer parar o que estamos fazendo em Frame e pular mentalmente para Game ou
quer apenas ter um objeto MockGame que faa exatamente o que precisamos para
que Frame funcione?

RCM:

No, vamos parar de trabalhar em Frame e comear a trabalhar em Game. Os casos


de teste em Game devem provar que precisamos da lista encadeada de Frames.

RSK:

No tenho certeza sobre como eles mostraro a necessidade da lista. Preciso


codificar.

RCM:

(digita)
//GameTest.cs-----------------------------------------using NUnit.Framework;
[TestFixture]
public class GameTest
{
[Test]
public void TestOneThrow()
{
Game game = new Game();
game.Add(5);
Assert.AreEqual(5, game.Score);
}
}

RCM:

Isso parece razovel?

RSK:

Claro, mas ainda estou procurando a evidncia dessa lista de Frames.

RCM:

Eu tambm. Vamos continuar seguindo esses casos de teste e ver onde eles levam.
//Game.cs---------------------------------public class Game
{
public int Score
{
get { return 0; }
}
public void Add(int pins)
{
}
}

84

DESENVOLVIMENTO GIL

RCM:

Tudo bem; isso compila e o teste falha. Agora, vamos faz-lo passar.
//Game.cs---------------------------------public class Game
{
private int score;
public int Score
{
get { return score; }
}
public void Add(int pins)
{
score += pins;
}
}

RCM:

Isso passa. Bom.

RSK:

No posso discordar. Mas ainda estou procurando aquela grande evidncia da


necessidade de uma lista encadeada de objetos Frame. Foi exatamente isso que
nos levou a Game.

RCM:

Sim, isso que estou procurando tambm. Espero realmente que, quando comearmos a introduzir casos de teste de spare e strike, precisemos construir Frames e interlig-los em uma lista encadeada. Mas no quero construir isso at que
o cdigo nos obrigue.

RSK:

Bem pensado. Vamos continuar em pequenas etapas em Game. Que tal outro teste
que verifique dois arremessos sem nenhum spare?

RCM:

Tudo bem; isso deve passar agora. Vamos tentar.


//GameTest.cs-------------------------------[Test]
public void TestTwoThrowsNoMark()
{
Game game = new Game();
game.Add(5);
game.Add(4);
Assert.AreEqual(9, game.Score);
}

RCM:

Sim, esse passa. Agora, vamos tentar quatro bolas, sem nenhuma pontuao.

RSK:

Esse tambm passar. Eu no esperava por isso. Podemos continuar a adicionar


arremessos e no precisaremos de um Frame. Mas, quando fizermos um spare ou
um strike, talvez precisamos de um.

RCM:

com isso que estou contando. Mas veja este caso de teste:
//TestGame.cs--------------------------------------[Test]
public void TestFourThrowsNoMark()

UM EPISDIO DE PROGRAMAO

85

{
Game game = new Game();
game.Add(5);
game.Add(4);
game.Add(7);
game.Add(2);
Assert.AreEqual(18, game.Score);
Assert.AreEqual(9, game.ScoreForFrame(1));
Assert.AreEqual(18, game.ScoreForFrame(2));
}

RCM:

Isso parece razovel?

RSK:

Com certeza. Esqueci que temos de mostrar a pontuao em cada quadro. Ah,
nosso esboo de carto de marcao estava servindo de encosto para minha Coca-Cola Diet. Sim, foi por isso que esqueci.

RCM:

(Suspiro.) Tudo bem; primeiro, vamos fazer esse caso de teste falhar, adicionando
o mtodo ScoreForFrame em Game.
//Game.cs---------------------------------public int ScoreForFrame(int frame)
{
return 0;
}

RCM:

Excelente; isso compila e falha. Agora, como o fazemos passar?

RSK:

Podemos comear a fazer objetos Frame. Mas essa a coisa mais simples que
far o teste passar?

RCM:

No, na verdade, poderamos simplesmente criar um array de inteiros em Game.


Cada chamada de Add adicionaria um novo inteiro no array. Cada chamada de
ScoreForFrame simplesmente percorrer o array e calcular o placar.
//Game.cs---------------------------------public class Game
{
private int score;
private int[] throws = new int[21];
private int currentThrow;

86

DESENVOLVIMENTO GIL

public int Score


{
get { return score; }
}
public void Add(int pins)
{
throws[currentThrow++] = pins;
score += pins;
}
public int ScoreForFrame(int frame)
{
int score = 0;
for(int ball = 0;
frame > 0 && ball < currentThrow;
ball+=2, frame--)
{
score += throws[ball] + throws[ball + 1];
}
return score;
}
}

RCM:

(muito satisfeito consigo mesmo) A est! Isso funciona.

RSK:

Por que o nmero mgico 21?

RCM:

Esse o nmero mximo de arremessos possveis em um jogo.

RSK:

Eca! Deixe-me adivinhar; quando jovem, voc era viciado em UNIX e se orgulhava
de escrever um aplicativo inteiro em uma nica instruo que mais ningum podia decifrar.
ScoreForFrame() precisa ser refatorado para ser mais comunicativo. Mas antes de considerarmos a refatorao, deixe-me fazer outra pergunta. Game o melhor
lugar para esse mtodo? Eu acho que Game est violando o Princpio da Responsabilidade nica (SRP). [Consulte o Captulo 8.] Ele est aceitando arremessos e sabe
como pontuar para cada quadro. O que voc acharia de um objeto Scorer?

RCM:

(Fazendo um gesto brusco com a mo.) No sei onde as funes esto agora; no
momento, estou interessado em fazer a pontuao funcionar. Quando isso estiver
funcionando, a poderemos discutir os valores do SRP. No entanto, entendi o que
voc quis dizer com viciado em UNIX; vamos tentar simplificar esse loop.
public int ScoreForFrame(int theFrame)
{
int ball = 0;
int score = 0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{

UM EPISDIO DE PROGRAMAO

87

score += throws[ball++] + throws[ball++];


}
return score;
}

RCM:

Agora est um pouco melhor, mas existem efeitos colaterais na expresso score+=.
Eles no importam aqui, pois no interessa em que ordem as duas expresses de
adendo so avaliadas. (Ou interessa? possvel que os dois incrementos sejam
feitos antes de uma das operaes do array?)

RSK:

Suponho que poderamos fazer uma experincia para verificar se existem efeitos
colaterais, mas essa funo no vai funcionar com spares e strikes. Devemos continuar tentando torn-la mais legvel ou devemos aumentar sua funcionalidade?

RCM:

A experincia poderia fazer sentido apenas em certos compiladores. Outros poderiam usar ordens de avaliao diferentes. No sei se isso um problema, mas
vamos nos livrar da possvel dependncia da ordem e prosseguir com mais casos
de teste.
public int ScoreForFrame(int theFrame)
{
int ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
int firstThrow = throws[ball++];
int secondThrow = throws[ball++];
score += firstThrow + secondThrow;
}
return score;
}

RCM: Certo, prximo caso de teste. Vamos tentar um spare.


[Test]
public void TestSimpleSpare()
{
Game game = new Game();
}

RCM:

Estou cansado de escrever isso. Vamos refatorar o teste e colocar a criao do


jogo em uma funo SetUp.
//GameTest.cs-------------------------------using NUnit.Framework;
[TestFixture]
public class GameTest
{

88

DESENVOLVIMENTO GIL

private Game game;


[SetUp]
public void SetUp()
{
game = new Game();
}
[Test]
public void TestOneThrow()
{
game.Add(5);
Assert.AreEqual(5, game.Score);
}
[Test]
public void TestTwoThrowsNoMark()
{
game.Add(5);
game.Add(4);
Assert.AreEqual(9, game.Score);
}
[Test]
public void TestFourThrowsNoMark()
{
game.Add(5);
game.Add(4);
game.Add(7);
game.Add(2);
Assert.AreEqual(18, game.Score);
Assert.AreEqual(9, game.ScoreForFrame(1));
Assert.AreEqual(18, game.ScoreForFrame(2));
}
[Test]
public void TestSimpleSpare()
{
}
}

RCM:

Est melhor; agora, vamos escrever o caso de teste do spare.


[Test]
public void TestSimpleSpare()
{
game.Add(3);
game.Add(7);
game.Add(3);
Assert.AreEqual(13, game.ScoreForFrame(1));
}

RCM:

Tudo bem, esse caso de teste falha. Agora precisamos faz-lo passar.

UM EPISDIO DE PROGRAMAO

RSK:

89

Eu digito.
public int ScoreForFrame(int theFrame)
{
int ball = 0;
int score = 0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
int firstThrow = throws[ball++];
int secondThrow = throws[ball++];
int frameScore = firstThrow + secondThrow;
// o spare precisa do primeiro arremesso do prximo quadro
if (frameScore == 10)
score += frameScore + throws[ball++];
else
score += frameScore;
}
return score;
}

RSK:

Irr! Funciona!

RCM:

(Pegando o teclado.) Certo, mas acho que o incremento de ball no caso frameScore==10 no devia estar ali. Aqui est um caso de teste que prova meu
argumento.
[Test]
public void TestSimpleFrameAfterSpare()
{
game.Add(3);
game.Add(7);
game.Add(3);
game.Add(2);
Assert.AreEqual(13, game.ScoreForFrame(1));
Assert.AreEqual(18, game.Score);
}

RCM:

Ah! Veja! Isso falha. Agora, se apenas removermos esse incmodo incremento...
if (frameScore == 10)
score += frameScore + throws[ball];

RCM:

Uh, ainda falha. O mtodo Score pode estar errado? Vou testar isso mudando o
caso de teste para usar ScoreForFrame(2).
[Test]
public void TestSimpleFrameAfterSpare()
{

90

DESENVOLVIMENTO GIL

game.Add(3);
game.Add(7);
game.Add(3);
game.Add(2);
Assert.AreEqual(13, game.ScoreForFrame(1));
Assert.AreEqual(18, game.ScoreForFrame(2));
}

RCM:

Hummmm. Isso passa. A propriedade Score deve estar bagunada. Vamos


examin-la.
public int Score
{
get { return score; }
}
public void Add(int pins)
{
throws[currentThrow++] = pins;
score += pins;
}

RCM:

Sim, isso est errado. A propriedade Score est simplesmente retornando a


soma dos pinos e no o placar correto. Precisamos que Score chame ScoreForFrame() com o quadro atual.

RSK:

No sabemos qual o quadro atual. Vamos acrescentar uma mensagem em cada


um de nossos testes atuais, um por vez, claro.

RCM:

Certo.
//GameTest.cs-------------------------------[Test]
public void TestOneThrow()
{
game.Add(5);
Assert.AreEqual(5, game.Score);
Assert.AreEqual(1, game.CurrentFrame);
}
//Game.cs---------------------------------public int CurrentFrame
{
get { return 1; }
}

RCM:

Isso funciona, mas estpido. Vamos fazer o prximo caso de teste.


[Test]
public void TestTwoThrowsNoMark()
{
game.Add(5);
game.Add(4);

UM EPISDIO DE PROGRAMAO

91

Assert.AreEqual(9, game.Score);
Assert.AreEqual(1, game.CurrentFrame);
}

RCM:

Esse no interessante; vamos tentar o prximo.


[Test]
public void TestFourThrowsNoMark()
{
game.Add(5);
game.Add(4);
game.Add(7);
game.Add(2);
Assert.AreEqual(18, game.Score);
Assert.AreEqual(9, game.ScoreForFrame(1));
Assert.AreEqual(18, game.ScoreForFrame(2));
Assert.AreEqual(2, game.CurrentFrame);
}

RCM:

Esse falha. Agora vamos faz-lo passar.

RSK:

Acho que o algoritmo trivial. Basta dividir o nmero de arremessos por 2, pois
so dois arremessos por quadro. A menos que tenhamos um strike. Mas ainda no
temos strikes; portanto, vamos ignor-los aqui tambm.

RCM:

(agita-se, somando e subtraindo 1 at que funcione)1


public int CurrentFrame
{
get { return 1 + (currentThrow 1) / 2; }
}

RCM:

Isso no est muito bom.

RSK:

E se no calcularmos isso a cada vez? E se ajustarmos uma varivel membro


currentFrame aps cada arremesso?

RCM:

Certo, vamos tentar isso.


//Game.cs---------------------------------private int currentFrame;
private bool isFirstThrow = true;
public int CurrentFrame
{
get { return currentFrame; }
}
public void Add(int pins)
{
throws[currentThrow++] = pins;
score += pins;

Dave Thomas e Andy Hunt chamam isso de programao por coincidncia.

92

DESENVOLVIMENTO GIL

if (isFirstThrow)
{
isFirstThrow = false;
currentFrame++;
}
else
{
isFirstThrow = true;;
}
}

RCM:

Tudo bem, isso funciona. Mas tambm significa que o quadro atual (currentFrame) o do arremesso da ltima bola e no o quadro no qual a prxima bola
ser arremessada. Se nos lembrarmos disso, tudo bem.

RSK:

Eu no tenho memria to boa; portanto, vamos tornar isso mais legvel. Mas antes
de perdermos tempo com mais alguma coisa, vamos tirar esse cdigo de Add() e
coloc-lo em uma funo membro privada chamada AdjustCurrentFrame() ou
algo parecido.

RCM:

Certo, isso parece bom.


public void Add(int pins)
{
throws[currentThrow++] = pins;
score += pins;
AdjustCurrentFrame();
}
private void AdjustCurrentFrame()
{
if (isFirstThrow)
{
isFirstThrow = false;
currentFrame++;
}
else
{
isFirstThrow = true;
}
}

RCM:

Agora, vamos mudar os nomes de variveis e funes para que sejam mais claros.
Como devemos chamar currentFrame?

RSK:

Gosto desse nome, mas acho que no a estamos incrementando no lugar certo.
Para mim, o quadro atual o nmero do quadro em que estou arremessando.
Portanto, ela deve ser incrementada imediatamente aps o ltimo arremesso de
um quadro.

RCM:

Concordo. Vamos alterar os casos de teste para refletir isso; depois, corrigiremos
AdjustCurrentFrame.

UM EPISDIO DE PROGRAMAO

93

//GameTest.cs-------------------------------[Test]
public void TestTwoThrowsNoMark()
{
game.Add(5);
game.Add(4);
Assert.AreEqual(9, game.Score);
Assert.AreEqual(2, game.CurrentFrame);
}
[Test]
public void TestFourThrowsNoMark()
{
game.Add(5);
game.Add(4);
game.Add(7);
game.Add(2);
Assert.AreEqual(18, game.Score);
Assert.AreEqual(9, game.ScoreForFrame(1));
Assert.AreEqual(18, game.ScoreForFrame(2));
Assert.AreEqual(3, game.CurrentFrame);
}
//Game.cs---------------------------------private int currentFrame = 1;
private void AdjustCurrentFrame()
{
if (isFirstThrow)
{
isFirstThrow = false;
}
else
{
isFirstThrow = true;
currentFrame++;
}
}

RCM:

Est funcionando. Agora, vamos testar CurrentFrame nos dois casos de spare.
[Test]
public void TestSimpleSpare()
{
game.Add(3);
game.Add(7);
game.Add(3);
Assert.AreEqual(13, game.ScoreForFrame(1));
Assert.AreEqual(2, game.CurrentFrame);
}
[Test]
public void TestSimpleFrameAfterSpare()
{

94

DESENVOLVIMENTO GIL

game.Add(3);
game.Add(7);
game.Add(3);
game.Add(2);
Assert.AreEqual(13, game.ScoreForFrame(1));
Assert.AreEqual(18, game.ScoreForFrame(2));
Assert.AreEqual(3, game.CurrentFrame);
}

RCM:

Funciona. Agora, vamos voltar ao problema original. Precisamos que Score funcione.
Podemos escrever Score agora, para chamar ScoreForFrame(CurrentFrame-1).
[Test]
public void TestSimpleFrameAfterSpare()
{
game.Add(3);
game.Add(7);
game.Add(3);
game.Add(2);
Assert.AreEqual(13, game.ScoreForFrame(1));
Assert.AreEqual(18, game.ScoreForFrame(2));
Assert.AreEqual(18, game.Score);
Assert.AreEqual(3, game.CurrentFrame);
}
//Game.cs---------------------------------public int Score()
{
return ScoreForFrame(CurrentFrame 1);
}

RCM:

Isso faz o caso de teste TestOneThrow falhar. Vamos examin-lo.


[Test]
public void TestOneThrow()
{
game.Add(5);
Assert.AreEqual(5, game.Score);
Assert.AreEqual(1, game.CurrentFrame);
}

RCM:

Com apenas um arremesso, o primeiro quadro est incompleto. O mtodo score


est chamando ScoreForFrame(0). Isso nojento.

RSK:

Talvez sim, talvez no. Para quem estamos escrevendo esse programa e quem
vai chamar Score? razovel supor que ele no ser chamado em um quadro
incompleto?

RCM:

Sim, mas isso me incomoda. Para contornar esse problema, precisamos retirar
score do caso de teste TestOneThrow. isso que queremos fazer?

RSK:

Talvez. Poderamos eliminar o caso de teste TestOneThrow inteiro. Ele foi usado
para nos levar aos casos de teste de interesse. Ser que tem alguma utilidade
agora? Ainda temos cobertura em todos os outros casos de teste.

UM EPISDIO DE PROGRAMAO

RCM:

95

Sim, entendo o que voc quer dizer. Certo, vamos acabar com ele (edita o cdigo,
executa o teste, obtm a barra verde). Ahhh, assim est melhor.
Agora melhor trabalharmos no caso de teste do strike. Afinal, queremos ver
todos esses objetos Frame construdos em uma lista encadeada, no ? (abafando o riso).
[Test]
public void TestSimpleStrike()
{
game.Add(10);
game.Add(3);
game.Add(6);
Assert.AreEqual(19, game.ScoreForFrame(1));
Assert.AreEqual(28, game.Score);
Assert.AreEqual(3, game.CurrentFrame);
}

RCM:

Certo, isso compila e falha, conforme o previsto. Agora, precisamos faz-lo


passar.
//Game.cs---------------------------------public class Game
{
private int score;
private int[] throws = new int[21];
private int currentThrow;
private int currentFrame = 1;
private bool isFirstThrow = true;
public int Score
{
get { return ScoreForFrame(GetCurrentFrame() 1); }
}
public int CurrentFrame
{
get { return currentFrame; }
}
public void Add(int pins)
{

96

DESENVOLVIMENTO GIL

throws[currentThrow++] = pins;
score += pins;
AdjustCurrentFrame(pins);
}
private void AdjustCurrentFrame(int pins)
{
if (isFirstThrow)
{
if(pins == 10) //Strike
currentFrame++;
else
isFirstThrow = false;
}
else
{
isFirstThrow=true;
currentFrame++;
}
}
public int ScoreForFrame(int theFrame)
{
int ball = 0;
int score = 0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
int firstThrow = throws[ball++];
if(firstThrow == 10) //Strike
{
score += 10 + throws[ball] + throws[ball+1];
}
else
{
int secondThrow = throws[ball++];
int frameScore = firstThrow + secondThrow;
// o spare precisa do primeiro arremesso dos
prximos quadros
if (frameScore == 10)
score += frameScore + throws[ball];
else
score += frameScore;
}
}
return score;
}
}

UM EPISDIO DE PROGRAMAO

RCM:

Certo, no foi muito difcil. Vamos ver se ele consegue mostrar o placar de um
jogo perfeito.
[Test]
public void TestPerfectGame()
{
for (int i=0; i<12; i++)
{
game.Add(10);
}
Assert.AreEqual(300, game.Score);
Assert.AreEqual(10, game.CurrentFrame);
}

RCM:

Argh, isso est dizendo que o placar 330. Por qu?

RSK:

Porque o quadro atual est sendo incrementado at 12.

RCM:

Ah! Precisamos limit-lo em 10.


private void AdjustCurrentFrame(int pins)
{
if (isFirstThrow)
{
if(pins == 10) //Strike
currentFrame++;
else
isFirstThrow = false;
}
else
{
isFirstThrow=true;
currentFrame++;
}
if(currentFrame > 10)
currentFrame = 10;
}

RCM:

97

Droga, agora est dizendo que o placar 270. O que est acontecendo?

98

DESENVOLVIMENTO GIL

RSK:

Bob, a propriedade Score est subtraindo 1 de SetCurrentFrame; portanto,


est fornecendo a pontuao do quadro 9 e no do 10.

RCM:

O qu? Voc quer dizer que devo limitar o quadro atual em 11 e no 10? Vou tentar isso.
if(currentFrame > 11)
currentFrame = 11;

RCM:

Certo, ento agora ele obtm a pontuao correta, mas falha porque o quadro
atual 11 e no 10. Eca! Essa coisa de quadro atual um p no saco. Queremos
que o quadro atual seja aquele em que o jogador est fazendo o arremesso, mas
o que isso significa no final do jogo?

RSK:

Talvez devamos voltar ideia de que o quadro atual seja o da ltima bola arremessada.

RCM:

Ou talvez precisemos pensar na ideia do ltimo quadro completado? Afinal, o


placar do jogo a qualquer momento a pontuao do ltimo quadro completado.

RSK:

Um quadro completado aquele no qual voc pode escrever o placar, certo?

RCM:

Sim, um quadro contendo um spare completado depois da prxima bola. Um


quadro contendo um strike completado depois das duas prximas bolas. Um
quadro sem pontuao completado aps a segunda bola do quadro.
Espere um pouco. Estamos tentando fazer a propriedade Score funcionar,
certo? Basta forar Score a chamar ScoreForFrame(10), caso o jogo tenha terminado.

RSK:

Como sabemos se o jogo terminou?

RCM:

Se AdjustCurrentFrame tentar incrementar currentFrame aps o dcimo quadro, o jogo estar terminado.

RSK:

Espere. O que voc est dizendo que, se CurrentFrame retornar 11, o jogo estar terminado; esse o modo como o cdigo funciona agora!

RCM:

Humm. Voc quer dizer que devemos alterar o caso de teste para corresponder ao
cdigo?
[Test]
public void TestPerfectGame()
{
for (int i=0; i<12; i++)
{
game.Add(10);
}
Assert.AreEqual(300, game.Score);
Assert.AreEqual(11, game.CurrentFrame);
}

RCM:

Bem, funciona. Mas ainda estou preocupado com isso.

UM EPISDIO DE PROGRAMAO

RSK:

99

Talvez algo acontea conosco depois. No momento, acho que estou vendo um
erro. Posso? (pegando o teclado)
[Test]
public void TestEndOfArray()
{
for (int i=0; i<9; i++)
{
game.Add(0);
game.Add(0);
}
game.Add(2);
game.Add(8); // Spare do 10 quadro
game.Add(10); // Strike na ltima posio do array.
Assert.AreEqual(20, game.Score);
}

RSK:

Humm. Isso no falha. Pensei que, como a vigsima primeira posio do array era
um strike, o marcador tentaria adicionar a vigsima segunda e a vigsima terceira
posies no placar. Mas acho que no.

RCM:

Humm, voc ainda est pensando naquele objeto scorer, no ? Seja como for,
vejo que voc estava chegando a isso, mas como score nunca chama ScoreForFrame com um nmero maior do que 10, o ltimo strike no contado realmente
como strike. Ele contou apenas um 10 para completar o ltimo spare. Ns nunca
ultrapassamos o final do array.

RSK:

Certo, vamos colocar nosso carto de marcao original no programa.


[Test]
public void TestSampleGame()
{
game.Add(1);
game.Add(4);
game.Add(4);
game.Add(5);
game.Add(6);
game.Add(4);
game.Add(5);
game.Add(5);
game.Add(10);
game.Add(0);
game.Add(1);
game.Add(7);
game.Add(3);
game.Add(6);
game.Add(4);
game.Add(10);
game.Add(2);
game.Add(8);
game.Add(6);
Assert.AreEqual(133, game.Score);
}

100

DESENVOLVIMENTO GIL

RSK:

Isso funciona. Voc consegue pensar em outros casos de teste?

RCM:

Sim, vamos testar mais algumas condies limite; o que voc acha do panaca que
consegue 11 strikes e depois um 9 final?
[Test]
public void TestHeartBreak()
{
for (int i=0; i<11; i++)
game.Add(10);
game.Add(9);
Assert.AreEqual(299, game.Score);
}

RCM:

Funciona. T, o que voc acha de um spare no dcimo quadro?


[Test]
public void TestTenthFrameSpare()
{
for (int i=0; i<9; i++)
game.Add(10);
game.Add(9);
game.Add(1);
game.Add(1);
Assert.AreEqual(270, game.Score);
}

RCM:

(Olhando todo feliz para a barra verde.) Isso tambm funciona. No consigo pensar em mais nada, e voc?

RSK:

No, acho que abrangemos tudo. Alm disso, quero muito refatorar essa baguna.
Ainda vejo o objeto scorer em alguns lugares.

RCM:

Certo, bem, a funo ScoreForFrame est uma baguna. Vamos consider-la.


public int ScoreForFrame(int theFrame)
{
int ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;

UM EPISDIO DE PROGRAMAO

101

currentFrame++)
{
int firstThrow = throws[ball++];
if(firstThrow == 10) //Strike
{
score += 10 + throws[ball] + throws[ball+1];
}
else
{
int secondThrow = throws[ball++];
int frameScore = firstThrow + secondThrow;
// o spare precisa do primeiro arremesso do
prximo quadro
if (frameScore == 10)
score += frameScore + throws[ball];
else
score += frameScore;
}
}
return score;
}

RCM:

Eu quero retirar o corpo dessa clusula else e colocar em um mtodo separado,


chamado HandleSecondThrow, mas no posso, pois ela usa as variveis locais
ball, firstThrow e secondThrow.

RSK:

Poderamos transformar essas variveis locais em variveis membros.

RCM:

Sim, de certo modo isso refora sua ideia de que poderemos colocar a pontuao
em seu prprio objeto scorer. T, vamos tentar isso.

RSK:

(pega o teclado)
private int ball;
private int firstThrow;
private int secondThrow;
public int ScoreForFrame(int theFrame)
{
ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
firstThrow = throws[ball++];
if(firstThrow == 10) //Strike
{
score += 10 + throws[ball] + throws[ball+1];
}
else
{

102

DESENVOLVIMENTO GIL

secondThrow = throws[ball++];
int frameScore = firstThrow + secondThrow;
// o spare precisa do primeiro arremesso do prximo quadro
if (frameScore == 10)
score += frameScore + throws[ball];
else
score += frameScore;
}
}
return score;
}

RSK:

Isso funciona; agora podemos colocar a clusula else em sua prpria funo.
public int ScoreForFrame(int theFrame)
{
ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
firstThrow = throws[ball++];
if(firstThrow == 10) //Strike
{
score += 10 + throws[ball] + throws[ball+1];
}
else
{
score += HandleSecondThrow();
}
}
return score;
}
private int HandleSecondThrow()
{
int score = 0;
secondThrow = throws[ball++];
int frameScore = firstThrow + secondThrow;
// o spare precisa do primeiro arremesso do prximo quadro
if (frameScore == 10)
score += frameScore + throws[ball];
else
score += frameScore;
return score;
}

RCM:

Veja a estrutura de ScoreForFrame! Em pseudocdigo, parecido com isto:

UM EPISDIO DE PROGRAMAO

103

if strike
score += 10 + NextTwoBalls;
else
HandleSecondThrow.

RCM:

E se mudssemos para:
if strike
score += 10 + NextTwoBalls;
else if spare
score += 10 + NextBall;
else
score += TwoBallsInFrame

RSK:

Cus! Essas so praticamente as regras da pontuao do boliche, no ? Tudo


bem, vamos ver se conseguimos colocar essa estrutura na funo real. Primeiro,
vamos mudar a maneira como a varivel ball est sendo incrementada, para que
os trs casos a manipulem de modo independente.
public int ScoreForFrame(int theFrame)
{
ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
firstThrow = throws[ball];
if(firstThrow == 10) //Strike
{
ball++;
score += 10 + throws[ball] + throws[ball+1];
}
else
{
score += HandleSecondThrow();
}
}
return score;
}
private int HandleSecondThrow()
{
int score = 0;
secondThrow = throws[ball + 1];
int frameScore = firstThrow + secondThrow;
// o spare precisa do primeiro arremesso do prximo quadro
if (frameScore == 10)
{
ball += 2;
score += frameScore + throws[ball];
}

104

DESENVOLVIMENTO GIL

else
{
ball += 2;
score += frameScore;
}
return score;
}

RCM:

(Pega o teclado.) Agora vamos nos livrar das variveis firstThrow e secondThrow
e substitu-las pelas funes apropriadas.
public int ScoreForFrame(int theFrame)
{
ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
firstThrow = throws[ball];
if(Strike())
{
ball++;
score += 10 + NextTwoBalls;
}
else
{
score += HandleSecondThrow();
}
}
return score;
}
private bool Strike()
{
return throws[ball] == 10;
}
private int NextTwoBalls
{
get { return (throws[ball] + throws[ball+1]); }
}

RCM:

Essa etapa funciona; vamos continuar.


private int HandleSecondThrow()
{
int score = 0;
secondThrow = throws[ball + 1];
int frameScore = firstThrow + secondThrow;
// o spare precisa do primeiro arremesso do prximo quadro
if (Spare())

UM EPISDIO DE PROGRAMAO

105

{
ball += 2;
score += 10 + NextBall;
}
else
{
ball += 2;
score += frameScore;
}
return score;
}
private bool Spare()
{
return throws[ball] + throws[ball+1] == 10;
}
private int NextBall
{
get { return throws[ball]; }
}

RCM:

Isso tambm funciona. Agora, vamos cuidar de frameScore.


private int HandleSecondThrow()
{
int score = 0;
secondThrow = throws[ball + 1];
int frameScore = firstThrow + secondThrow;
// o spare precisa do primeiro arremesso do prximo quadro
if (IsSpare())
{
ball += 2;
score += 10 + NextBall;
}
else
{
score += TwoBallsInFrame;
ball += 2;
}
return score;
}
private int TwoBallsInFrame
{
get { return throws[ball] + throws[ball+1]; }
}

RSK:

Bob, voc no est incrementando ball de maneira uniforme. No caso do spare


e do strike, voc incrementa antes de calcular o placar. No caso de TwoBallsInFrame, voc incrementa depois de calcular o placar. E o cdigo depende dessa
ordem! O que est acontecendo?

106

DESENVOLVIMENTO GIL

RCM:

Desculpe, eu devia ter explicado. Estou pretendendo mover os incrementos para


Strike, Spare e TwoBallsInFrame. Assim, eles desaparecero do mtodo ScoreForFrame e o mtodo ser como nosso pseudocdigo.

RSK:

Tudo bem, vou confiar em voc por mais algumas etapas; mas, lembre-se, estou
de olho.

RCM:

Agora, como ningum mais usa firstThrow, secondThrow e frameScore,


podemos nos livrar deles.
public int ScoreForFrame(int theFrame)
{
ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
if(Strike())
{
ball++;
score += 10 + NextTwoBalls;
}
else
{
score += HandleSecondThrow();
}
}
return score;
}
private int HandleSecondThrow()
{
int score = 0;
// o spare precisa do primeiro arremesso do prximo quadro
if (Spare())
{
ball += 2;
score += 10 + NextBall;
}
else
{
score += TwoBallsInFrame;
ball += 2;
}
return score;
}

RCM:

(O brilho em seus olhos um reflexo da barra verde.) Agora, como a nica varivel que acopla os trs casos ball, e ball tratada de modo independente em
cada caso, podemos mesclar os trs casos.
public int ScoreForFrame(int theFrame)
{

UM EPISDIO DE PROGRAMAO

107

ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
if(Strike())
{
ball++;
score += 10 + NextTwoBalls;
}
else if (Spare())
{
ball += 2;
score += 10 + NextBall;
}
else
{
score += TwoBallsInFrame;
ball += 2;
}
}
return score;
}

RSK:

Certo, agora podemos tornar os incrementos uniformes e renomear as funes


para que sejam mais explcitas. (pega o teclado)
public int ScoreForFrame(int theFrame)
{
ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
if(Strike())
{
score += 10 + NextTwoBallsForStrike;
ball++;
}
else if (Spare())
{
score += 10 + NextBallForSpare;
ball += 2;
}
else
{
score += TwoBallsInFrame;
ball += 2;
}
}

108

DESENVOLVIMENTO GIL

return score;
}
private int NextTwoBallsForStrike
{
get { return (throws[ball+1] + throws[ball+2]); }
}
private int NextBallForSpare
{
get { return throws[ball+2]; }
}

RCM:

Veja este mtodo ScoreForFrame! So as regras do boliche expressas da maneira


mais sucinta possvel.

RSK:

Mas, Bob, o que aconteceu com a lista encadeada de objetos Frame? (fala abafando o riso)

RCM:

(Suspira.) Fomos enfeitiados pelos duendes do projeto esquemtico exagerado.


Meu Deus, trs caixinhas desenhadas nas costas de um guardanapo Game, Frame e Throw e ainda assim complicado demais e completamente errado.

RSK:

Cometemos um erro, comeando com a classe Throw. Devamos ter comeado


com a classe Game!

RCM:

verdade! Na prxima vez, vamos tentar comear no nvel mais alto e depois
descer.

RSK:

(Respira fundo.) Projeto top-down!??!?!?

RCM:

Correo: projeto top-down com testes a priori. Francamente, eu no sei se essa


regra boa. apenas o que teria nos ajudado neste caso. Portanto, na prxima
vez, vou experimentar isso e ver o que acontece.

RSK:

Sim, tudo bem. De qualquer forma, ainda temos alguma refatorao a fazer. A
varivel ball simplesmente um iterador privado de ScoreForFrame e seus
subordinados. Todos eles devem ser movidos para um objeto diferente.

RCM:

Oh, sim, seu objeto Scorer. Voc estava certo, afinal. Vamos fazer isso.

RSK:

(Pega o teclado e d vrios passos pequenos, pontuados por testes a serem criados.)
//Game.cs---------------------------------public class Game
{
private int score;
private int currentFrame = 1;
private bool isFirstThrow = true;
private Scorer scorer = new Scorer();
public int Score
{
get { return ScoreForFrame(GetCurrentFrame() 1); }
}
public int CurrentFrame
{
get { return currentFrame; }

UM EPISDIO DE PROGRAMAO

}
public void Add(int pins)
{
scorer.AddThrow(pins);
score += pins;
AdjustCurrentFrame(pins);
}
private void AdjustCurrentFrame(int pins)
{
if (isFirstThrow)
{
if(pins == 10) //Strike
currentFrame++;
else
isFirstThrow = false;
}
else
{
isFirstThrow = true;
currentFrame++;
}
if(currentFrame > 11)
currentFrame = 11;
}
public int ScoreForFrame(int theFrame)
{
return scorer.ScoreForFrame(theFrame);
}
}
//Scorer.cs---------------------------------public class Scorer
{
private int ball;
private int[] throws = new int[21];
private int currentThrow;
public void AddThrow(int pins)
{
throws[currentThrow++] = pins;
}
public int ScoreForFrame(int theFrame)
{
ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
if(Strike())
{
score += 10 + NextTwoBallsForStrike;

109

110

DESENVOLVIMENTO GIL

ball++;
}
else if (Spare())
{
score += 10 + NextBallForSpare;
ball += 2;
}
else
{
score += TwoBallsInFrame;
ball += 2;
}
}
return score;
}
private int NextTwoBallsForStrike
{
get { return (throws[ball+1] + throws[ball+2]); }
}
private int NextBallForSpare
{
get { return throws[ball+2]; }
}
private bool Strike()
{
return throws[ball] == 10;
}
private int TwoBallsInFrame
{
get { return throws[ball] + throws[ball+1]; }
}
private bool Spare()
{
return throws[ball] + throws[ball+1] == 10;
}
}

RSK:

Isso est muito melhor. Agora, Game simplesmente controla os quadros e Scorer
calcula o placar. O Princpio da Responsabilidade nica impera!

RCM:

Seja o que for, est melhor. Voc notou que a varivel score no est mais sendo
usada?

RSK:

Ah! Voc est certo. Vamos elimin-la. (Comea a apagar as coisas alegremente.)
public void Add(int pins)
{
scorer.AddThrow(pins);
AdjustCurrentFrame(pins);
}

UM EPISDIO DE PROGRAMAO

RSK:

Nada mal. Agora, devemos limpar aquele AdjustCurrentFrame?

RCM:

Certo, vamos ver isso.

111

private void AdjustCurrentFrame(int pins)


{
if (isFirstThrow)
{
if(pins == 10) //Strike
currentFrame++;
else
isFirstThrow = false;
}
else
{
isFirstThrow = true;
currentFrame++;
}
if(currentFrame > 11)
currentFrame = 11;
}

RCM:

Tudo bem. Primeiro, vamos colocar os incrementos em uma nica funo que
tambm restrinja o quadro em 11. (Brrrr. Ainda no gosto desse 11.)

RSK:

Bob, 11 significa fim do jogo.

RCM:

Sim. Brrrr. (Pega o teclado, faz algumas alteraes, pontuadas por testes.)
private void AdjustCurrentFrame(int pins)
{
if (isFirstThrow)
{
if(pins == 10) //Strike
AdvanceFrame();
else
isFirstThrow = false;
}
else
{
isFirstThrow = true;
AdvanceFrame();
}
}
private void AdvanceFrame()
{
currentFrame++;
if(currentFrame > 11)
currentFrame = 11;
}

RCM:

Certo, est um pouco melhor. Agora, vamos colocar o caso do strike em sua prpria
funo. (D mais alguns passos pequenos e executa testes entre cada um deles.)

112

DESENVOLVIMENTO GIL

private void AdjustCurrentFrame(int pins)


{
if (isFirstThrow)
{
if(AdjustFrameForStrike(pins) == false)
isFirstThrow = false;
}
else
{
isFirstThrow = true;
AdvanceFrame();
}
}
private bool AdjustFrameForStrike(int pins)
{
if(pins == 10)
{
AdvanceFrame();
return true;
}
return false;
}

RCM:

Est muito bom. Agora, e 11?

RSK:

Voc realmente detesta isso, no ?

RCM:

Sim, veja a propriedade Score:


public int Score
{
get { return ScoreForFrame(GetCurrentFrame() 1); }
}

RCM:

Esse -1 estranho. o nico lugar onde realmente usamos CurrentFrame e,


apesar disso, precisamos ajustar o que ele retorna.

RSK:

Droga, voc est certo. Quantas vezes ns invertemos nisso?

RCM:

Muitas. Mas isso. O cdigo quer currentFrame para representar o quadro da


bola do ltimo arremesso e no o quadro no qual estamos para arremessar.

RSK:

Xiii, isso vai estragar muitos casos de teste.

RCM:

Na verdade, acho que devemos retirar CurrentFrame de todos os casos de teste


e retirar a prpria funo CurrentFrame. Ningum a utiliza.

RSK:

Certo, entendi o que voc quer dizer. Farei isso. Ser como acabar com o sofrimento de um cavalo manco. (Pega o teclado.)
//Game.cs---------------------------------public int Score
{
get { return ScoreForFrame(currentFrame); }
}

UM EPISDIO DE PROGRAMAO

113

private void AdvanceFrame()


{
currentFrame++;
if(currentFrame > 10)
currentFrame = 10;
}

RCM:

Oh, meu Deus! Voc est querendo dizer que estvamos nos preocupando com
isso? S mudamos o limite de 11 para 10 e removemos o -1. Puxa!

RSK:

Sim, tio Bob, no valeu toda a preocupao que tivemos.

RCM:

Detesto o efeito colateral em AdjustFrameForStrike(). Quero me livrar dele. O


que voc acha disto?
private void AdjustCurrentFrame(int pins)
{
if ((isFirstThrow && pins == 10) || (!isFirstThrow))
AdvanceFrame();
else
isFirstThrow = false;
}

RSK:

Eu gosto da ideia e isso passa nos testes, mas odeio a longa instruo if. E quanto a isto?
private void AdjustCurrentFrame(int pins)
{
if (Strike(pins) || (!isFirstThrow))
AdvanceFrame();
else
isFirstThrow = false;
}
private bool Strike(int pins)
{
return (isFirstThrow && pins == 10);
}

RCM:

Sim, est timo. Poderamos at ir um passo adiante:


private void AdjustCurrentFrame(int pins)
{
if (LastBallInFrame(pins))
AdvanceFrame();
else
isFirstThrow = false;
}
private bool LastBallInFrame(int pins)
{
return Strike(pins) || (!isFirstThrow);
}

114

DESENVOLVIMENTO GIL

RSK:

Perfeito!

RCM:

Certo, parece que terminamos. Vamos apenas ler o programa inteiro e ver se o
mais simples e comunicativo possvel.
//Game.cs---------------------------------public class Game
{
private int currentFrame = 0;
private bool isFirstThrow = true;
private Scorer scorer = new Scorer();
public int Score
{
get { return ScoreForFrame(currentFrame); }
}
public void Add(int pins)
{
scorer.AddThrow(pins);
AdjustCurrentFrame(pins);
}
private void AdjustCurrentFrame(int pins)
{
if (LastBallInFrame(pins))
AdvanceFrame();
else
isFirstThrow = false;
}
private bool LastBallInFrame(int pins)
{
return Strike(pins) || (!isFirstThrow);
}
private bool Strike(int pins)
{
return (isFirstThrow && pins == 10);
}
private void AdvanceFrame()
{
currentFrame++;
if(currentFrame > 10)
currentFrame = 10;
}
public int ScoreForFrame(int theFrame)
{
return scorer.ScoreForFrame(theFrame);
}
}
//Scorer.cs---------------------------------public class Scorer
{

UM EPISDIO DE PROGRAMAO

private int ball;


private int[] throws = new int[21];
private int currentThrow;
public void AddThrow(int pins)
{
throws[currentThrow++] = pins;
}
public int ScoreForFrame(int theFrame)
{
ball = 0;
int score=0;
for (int currentFrame = 0;
currentFrame < theFrame;
currentFrame++)
{
if(Strike())
{
score += 10 + NextTwoBallsForStrike;
ball++;
}
else if (Spare())
{
score += 10 + NextBallForSpare;
ball += 2;
}
else
{
score += TwoBallsInFrame;
ball += 2;
}
}
return score;
}
private int NextTwoBallsForStrike
{
get { return (throws[ball+1] + throws[ball+2]); }
}
private int NextBallForSpare
{
get { return throws[ball+2]; }
}
private bool Strike()
{
return throws[ball] == 10;
}
private int TwoBallsInFrame
{
get { return throws[ball] + throws[ball+1]; }
}

115

116

DESENVOLVIMENTO GIL

private bool Spare()


{
return throws[ball] + throws[ball+1] == 10;
}
}

RCM:

Parece muito bom. No consigo pensar em mais nada para fazer.

RSK:

Sim, est legal. Vamos examinar os testes s por precauo.


//GameTest.cs-------------------------------using NUnit.Framework;
[TestFixture]
public class GameTest
{
private Game game;
[SetUp]
public void SetUp()
{
game = new Game();
}
[Test]
public void TestTwoThrowsNoMark()
{
game.Add(5);
game.Add(4);
Assert.AreEqual(9, game.Score);
}
[Test]
public void TestFourThrowsNoMark()
{
game.Add(5);
game.Add(4);
game.Add(7);
game.Add(2);
Assert.AreEqual(18, game.Score);
Assert.AreEqual(9, game.ScoreForFrame(1));
Assert.AreEqual(18, game.ScoreForFrame(2));
}
[Test]
public void TestSimpleSpare()
{
game.Add(3);
game.Add(7);
game.Add(3);
Assert.AreEqual(13, game.ScoreForFrame(1));
}
[Test]
public void TestSimpleFrameAfterSpare()
{

UM EPISDIO DE PROGRAMAO

game.Add(3);
game.Add(7);
game.Add(3);
game.Add(2);
Assert.AreEqual(13, game.ScoreForFrame(1));
Assert.AreEqual(18, game.ScoreForFrame(2));
Assert.AreEqual(18, game.Score);
}
[Test]
public void TestSimpleStrike()
{
game.Add(10);
game.Add(3);
game.Add(6);
Assert.AreEqual(19, game.ScoreForFrame(1));
Assert.AreEqual(28, game.Score);
}
[Test]
public void TestPerfectGame()
{
for (int i=0; i<12; i++)
{
game.Add(10);
}
Assert.AreEqual(300, game.Score);
}
[Test]
public void TestEndOfArray()
{
for (int i=0; i<9; i++)
{
game.Add(0);
game.Add(0);
}
game.Add(2);
game.Add(8); // Spare do 10 quadro
game.Add(10); // Strike na ltima posio do array.
Assert.AreEqual(20, game.Score);
}
[Test]
public void TestSampleGame()
{
game.Add(1);
game.Add(4);
game.Add(4);
game.Add(5);
game.Add(6);
game.Add(4);
game.Add(5);
game.Add(5);
game.Add(10);

117

118

DESENVOLVIMENTO GIL

game.Add(0);
game.Add(1);
game.Add(7);
game.Add(3);
game.Add(6);
game.Add(4);
game.Add(10);
game.Add(2);
game.Add(8);
game.Add(6);
Assert.AreEqual(133, game.Score);
}
[Test]
public void TestHeartBreak()
{
for (int i=0; i<11; i++)
game.Add(10);
game.Add(9);
Assert.AreEqual(299, game.Score);
}
[Test]
public void TestTenthFrameSpare()
{
for (int i=0; i<9; i++)
game.Add(10);
game.Add(9);
game.Add(1);
game.Add(1);
Assert.AreEqual(270, game.Score);
}
}

RSK:

Agora sim. Voc consegue pensar em mais algum caso de teste significativo?

RCM:

No, acho que deu. No vejo mais nada para retirar agora.

RSK:

Ento, terminamos.

RCM:

Acho que sim. Muito obrigado por sua ajuda.

RSK:

Qua nada, foi divertido.

UM EPISDIO DE PROGRAMAO

119

Concluso
Depois de escrever este captulo, eu o publiquei no site da Object Mentor.2 Muitas pessoas leram e fizeram comentrios. Algumas ficaram incomodadas com o fato de quase no
usarmos projeto orientado a objetos. Acho essa reao interessante. Devemos ter projeto
orientado a objetos em todo aplicativo e em todo programa? No caso em questo, o programa simplesmente no precisou dele. A classe Scorer foi a nica concesso ao OO e, ainda
assim, foi mais uma diviso simples do que um verdadeiro projeto orientado a objetos.
Outras pessoas acharam que devia haver uma classe Frame. Uma delas inclusive
criou uma verso do programa com essa classe, muito maior e mais complexa do que a
que voc v aqui.
Alguns acharam que no fomos leais UML. Afinal, no fizemos um projeto completo
antes de comear. O ridculo diagrama UML nas costas do guardanapo (Figura 6-2) no era
um projeto completo; ele no inclua diagramas de sequncia. Eu acho esse argumento muito
estranho. Para mim, no parece provvel que adicionar diagramas de sequncia na Figura
6-2 nos faria abandonar as classes Throw e Frame. Alis, acho que isso nos levaria a crer que
essas classes eram necessrias.
Isso significa que os diagramas so inadequados? Claro que no. Bem, na verdade, sim, de certa maneira. Para esse programa, os diagramas no ajudaram em nada.
Alis, atrapalharam. Se os tivssemos seguido, teramos terminado com um programa
muito mais complexo do que o necessrio. Voc poderia afirmar que tambm teramos
acabado com um programa mais fcil de manter, mas discordo. O programa que voc
v aqui fcil de entender e, portanto, fcil de manter. No existem dentro dele dependncias mal gerenciadas que o tornariam rgido ou frgil.
Portanto, sim, s vezes os diagramas so inadequados. Mas quando? Quando voc os
cria sem cdigo para valid-los e, depois, pretende segui-los. Nada h de errado em desenhar um diagrama para explorar uma ideia. No entanto, tendo produzido um diagrama,
voc no deve supor que ele o melhor projeto para a tarefa. Voc pode descobrir que o
melhor projeto evoluir quando der passos muito pequenos, escrevendo testes primeiro.
Corroborando essa concluso, vou deix-lo com as palavras do general Dwight David
Eisenhower: Na preparao para a batalha, tenho sempre verificado que os planos so
inteis, mas o planejamento indispensvel.

www.objectmentor.com

120

DESENVOLVIMENTO GIL

Viso geral das regras do boliche


O boliche um jogo no qual se lana uma bola do tamanho de um melo em uma pista estreita,
em direo a dez pinos de madeira. O objetivo derrubar o nmero mximo possvel de pinos por
arremesso.
O jogo composto de dez quadros. No incio de cada quadro, os dez pinos esto levantados. O
jogador tem duas chances para derrubar todos eles.
Se o jogador derruba todos os pinos na primeira tentativa, isso chamado de strike e o quadro
termina. Se o jogador no derruba todos os pinos com a primeira bola, mas consegue com a segunda, isso chamado de spare. Aps a segunda bola, o quadro termina, mesmo que ainda existam
pinos em p.
Um quadro de strike tem a pontuao calculada somando-se dez contagem do quadro anterior, mais o nmero de pinos derrubados pelas duas prximas bolas. Um quadro de spare
tem a pontuao calculada somando-se dez contagem do quadro anterior, mais o nmero de
pinos derrubados pela prxima bola. Caso contrrio, um quadro tem sua pontuao calculada
somando-se o nmero de pinos derrubados pelas duas bolas pontuao do quadro anterior.
Se no dcimo quadro for conseguido um strike, o jogador pode arremessar mais duas bolas para
completar a pontuao do strike. Do mesmo modo, se no dcimo quadro for conseguido um spare,
o jogador pode arremessar mais uma bola para completar a pontuao do spare. Assim, o dcimo
quadro pode ter trs bolas, em vez de duas.

1 4

4 5

14

29

49

60

0 1 7

61

97

77

2
117

133

O carto de marcao acima mostra um jogo tpico, mas um tanto sofrvel. No primeiro quadro, o
jogador derrubou um pino com a primeira bola e quatro com a segunda. Assim, a pontuao do jogador para o quadro 5. No segundo quadro, o jogador derrubou quatro pinos com a primeira bola
e cinco com a segunda. Isso significa nove pinos no total, que somados ao quadro anterior do 14
pontos.
No terceiro quadro, o jogador derrubou seis pinos com a primeira bola e derrubou o resto com a
segunda, conseguindo um spare. Nenhuma pontuao pode ser calculada para esse quadro, at
que a prxima bola seja lanada.
No quarto quadro, o jogador derruba cinco pinos com a primeira bola. Isso nos permite completar a
pontuao do spare do quadro 3. Pontuao do quadro 3: 10, mais a pontuao do quadro 2 (14),
mais a primeira bola do quadro 4 (5), ou seja, 29 pontos. A ltima bola do quadro 4 um spare.
O quadro 5 um strike. Isso nos permite concluir a pontuao do quadro 4, que : 29 + 10 + 10 = 49.
O quadro 6 deplorvel. A primeira bola caiu na canaleta e no derrubou nenhum pino. A segunda
bola derrubou apenas um pino. A pontuao para o strike do quadro 5 : 49 + 10 + 0 + 1 = 60.
O resto voc provavelmente descobrir sozinho.

Seo II

PROJETO GIL

e agilidade consiste em criar software em incrementos minsculos, como voc pode


projetar o software? Como garantir que o software tenha uma boa estrutura, que seja
flexvel, passvel de manuteno e reutilizvel? Se voc o cria em incrementos minsculos,
no est realmente preparando o terreno para muito descarte e reformulao em nome da
refatorao? Voc no vai perder a viso global?
Em uma equipe gil, a viso global evolui com o software. A cada iterao, a equipe melhora o projeto do sistema de modo que ele seja o melhor possvel para o sistema
conforme ele est agora. A equipe no perde muito tempo antecipando os requisitos e as
necessidades futuras. Nem tenta construir hoje a infraestrutura para suportar as funcionalidades que talvez sejam necessrias amanh. Em vez disso, a equipe se concentra na
estrutura do sistema atual, tornando-a to boa quanto possvel.
Isso no um abandono da arquitetura e do projeto. Em vez disso, uma maneira
de evoluir progressivamente a arquitetura e o projeto mais adequados para o sistema.
Tambm uma maneira de manter esse projeto e essa arquitetura adequados, medida
que o sistema cresce e evolui com o tempo. O desenvolvimento gil torna o processo de
projeto e arquitetura contnuo.
Como sabemos se o projeto de um sistema de software bom? O Captulo 7 enumera
e descreve os sintomas de um projeto ruim. Tais sintomas (ou maus cheiros de projeto)
frequentemente permeiam a estrutura global do software. O captulo demonstra como esses sintomas se acumulam em um projeto de software e explica como evit-los.

122

PROJETO GIL

Os sintomas so:
Rigidez. difcil alterar o projeto.
Fragilidade. O projeto (design) fcil de estragar.
Imobilidade. difcil reutilizar o projeto.
Viscosidade. difcil fazer a coisa certa.
Complexidade desnecessria. Projeto excessivo.
Repetio desnecessria. Abuso do mouse.
Opacidade. Expresso desorganizada.
Esses sintomas tm natureza semelhante aos maus cheiros do cdigo, mas esto em um
nvel mais alto. Eles so os maus cheiros que permeiam a estrutura global do software, em
vez de uma pequena seo de cdigo.
Como sintoma, um mau cheiro de projeto algo que pode ser medido subjetivamente ou mesmo objetivamente. Com frequncia, o mau cheiro causado pela violao de um
ou mais princpios de projeto. Os Captulos 8 a 12 descrevem os princpios de projetos
orientados a objetos que ajudam os desenvolvedores a eliminar os sintomas do projeto
ruim os maus cheiros de projeto e a criar os melhores projetos para o conjunto de
funcionalidades atual.
Os princpios so:
Captulo 8: O Princpio da Responsabilidade nica (SRP)
Captulo 9: O Princpio do Aberto/Fechado (OCP)
Captulo 10: O Princpio da Substituio de Liskov (LSP)
Captulo 11: O Princpio da Inverso de Dependncia (DIP)
Captulo 12: O Princpio da Segregao de Interface (ISP)
Esses princpios resultam de dcadas de experincia em engenharia de software. Eles no
foram produzidos por uma s pessoa, mas representam a integrao das ideias e publicaes de uma grande quantidade de desenvolvedores de software e pesquisadores. Embora
sejam apresentados aqui como princpios de projeto orientado a objetos, na verdade eles
so casos especiais de princpios consagrados da engenharia de software.
As equipes geis aplicam os princpios apenas para resolver maus cheiros; elas no
aplicam os princpios quando os maus cheiros no existem. Seria um erro obedecer a um
princpio incondicionalmente apenas porque se trata de um princpio. Os princpios existem para nos ajudar a eliminar os maus cheiros. Eles no so um perfume que deve ser
borrifado por todo o sistema. A submisso excessiva aos princpios leva ao mau cheiro de
projeto da complexidade desnecessria.

Captulo 7

O QUE PROJETO GIL?


Depois de examinar o ciclo de vida do desenvolvimento
de software do meu ponto de vista, conclu que a nica
documentao de software que parece realmente
satisfazer os critrios de um projeto de engenharia
so as listagens de cdigo-fonte.
Jack Reeves

m 1992, Jack Reeves escreveu um artigo influente O que projeto de software?


no C++ Journal.1 Nesse texto, Reeves argumentava que o projeto de um sistema de
software documentado principalmente pelo seu cdigo-fonte, e que os diagramas representando o cdigo-fonte so auxiliares do projeto e no o projeto em si. O artigo de Jack
considerado precursor do desenvolvimento gil.

Nas pginas a seguir, falaremos frequentemente sobre o projeto. Voc no deve


tomar isso como um conjunto de diagramas UML separados do cdigo. Um conjunto de
diagramas UML pode representar partes de um projeto, mas eles no so o projeto. Um
projeto de software um conceito abstrato. Ele est relacionado forma e estrutura globais do programa, assim como forma e estrutura detalhadas de cada mdulo, classe e
mtodo. O projeto pode ser representado por muitos meios diferentes, mas sua materializao final o cdigo-fonte. No final, o cdigo-fonte o projeto.

Maus cheiros do projeto


Se tiver sorte, voc comea um projeto com uma ideia clara de como deseja que o sistema
seja. O projeto do sistema uma imagem vital em sua mente. Se tiver mais sorte ainda, a
primeira verso reflete a imagem do projeto.
Mas a algo d errado. O software comea a apodrecer como um pedao de carne estragada. medida que o tempo passa, o apodrecimento continua. Feridas horrveis, cheias de
pus e furnculos, se acumulam no cdigo, tornando-o cada vez mais difcil de manter. Por

[Reeves92] Trata-se de um artigo excelente. Recomendo que voc o leia. Ele foi includo neste livro, no
Apndice B, pgina 703.

124

PROJETO GIL

fim, o mero esforo exigido para fazer mesmo a mais simples das alteraes se torna to oneroso que os desenvolvedores e os gerentes de linha de frente clamam por uma reviso.
Tais revises raramente so bem-sucedidas. Embora os projetistas comecem com
boas intenes, eles descobrem que esto atirando em um alvo mvel. O sistema antigo
continua a evoluir e mudar, e o novo projeto precisa continuar. As verrugas e lceras se
acumulam no novo projeto, antes mesmo que ele chegue sua primeira entrega (release).

Maus cheiros do projeto os odores do software em putrefao


Voc sabe que o software est apodrecendo quando ele comea a exalar um dos seguintes
odores:
Rigidez
Fragilidade
Imobilidade
Viscosidade
Complexidade desnecessria
Repetio desnecessria
Opacidade

Rigidez
Rigidez a tendncia do software de ser difcil de alterar, mesmo de maneira simples. Um
projeto rgido quando uma nica modificao provoca uma sucesso de alteraes subsequentes em mdulos dependentes. Quanto mais mdulos precisam ser alterados, mais
rgido o projeto.
A maioria dos desenvolvedores se depara com essa situao. Eles so solicitados
a fazer o que parece ser uma alterao simples. Examinam a modificao e fazem uma
estimativa razovel do trabalho necessrio. Depois, no entanto, medida que trabalham
na alterao, descobrem que existem consequncias imprevistas. Os desenvolvedores se
descobrem perseguindo a alterao em partes enormes do cdigo, modificando bem mais
mdulos do que tinham estimado inicialmente e descobrindo diversas outras alteraes
que precisam fazer. No final, as mudanas demoram muito mais tempo do que a estimativa inicial. Quando perguntados por que sua estimativa foi to equivocada, eles repetem o
tradicional lamento dos desenvolvedores de software: Foi muito mais complicado do que
eu pensava!.

Fragilidade
Fragilidade a tendncia de um programa estragar em muitos lugares quando uma nica
alterao feita. Frequentemente, os novos problemas esto em reas que no tm uma
relao conceitual com a rea alterada. Corrigir esses problemas leva a ainda mais problemas e a equipe de desenvolvimento comea a parecer um cachorro correndo atrs do
rabo.
medida que a fragilidade de um mdulo aumenta, a probabilidade de que uma
alterao introduza problemas inesperados aproxima-se da certeza. Isso parece absurdo, mas tais mdulos no so incomuns. Tratam-se dos mdulos que constantemente

O QUE PROJETO GIL?

125

precisam de reparos, aqueles que nunca ficam fora da lista de bugs. So aqueles que
todos sabem que precisam ser revisados, mas que ningum quer encarar. So os mdulos que, quanto mais voc os corrige, pior ficam.

Imobilidade
Um projeto imvel quando contm partes que poderiam ser teis em outros sistemas,
mas o trabalho e o risco envolvidos na separao dessas partes do sistema original so
grandes demais. Essa uma ocorrncia infeliz, porm muito comum.

Viscosidade
A viscosidade aparece em duas formas: viscosidade do software e viscosidade do ambiente.
Quando se deparam com uma alterao, os desenvolvedores normalmente encontram
mais de uma maneira de faz-la. Alguns deles preservam o projeto; outros, no (isto ,
produzem solues malfeitas). Quando os mtodos que preservam o projeto so mais
difceis de usar do que as solues malfeitas, a viscosidade do projeto alta. fcil fazer
a coisa errada, mas difcil fazer a coisa certa. Queremos projetar nosso software de modo
que as alteraes que preservam o projeto sejam fceis de fazer.
A viscosidade do ambiente ocorre quando o ambiente de desenvolvimento lento
e ineficiente. Por exemplo, se os tempos de compilao forem muito longos, os desenvolvedores ficaro tentados a fazer alteraes que no obriguem grandes recompilaes, mesmo que essas alteraes no preservem o projeto. Se o sistema de controle
de cdigo-fonte exigir horas para verificar apenas alguns arquivos, os desenvolvedores
ficaro tentados a fazer alteraes que exijam o mnimo de check-ins possvel, independentemente de o projeto ser preservado.
Nos dois casos, um projeto viscoso aquele no qual o projeto do software difcil de
preservar. Queremos criar sistemas e ambientes de projeto que tornem fcil preservar e
aprimorar o projeto.

Complexidade desnecessria
Um projeto tem o mau cheiro da complexidade desnecessria quando contm elementos
que no so teis no momento. Isso acontece com muita frequncia, quando os desenvolvedores antecipam mudanas nos requisitos e colocam recursos no software para lidar
com essas mudanas em potencial. Isso pode parecer bom em um primeiro momento.
Afinal, a preparao para futuras mudanas deve manter nosso cdigo flexvel e evitar
alteraes apavorantes mais adiante.
Infelizmente, muitas vezes o efeito justamente o oposto. Preparando-se para muitas
possibilidades, o projeto se torna sujo, contendo construes que nunca so utilizadas. Algumas dessas preparaes podem compensar, mas outras tantas no. Nesse meio-tempo,
o projeto carrega o peso desses elementos no utilizados. Isso torna o software complexo
e difcil de entender.

Repetio desnecessria
Recortar e colar podem ser operaes de edio de texto teis, mas podem ser desastrosas na edio de cdigo. Com muita frequncia, os sistemas de software so baseados em

126

PROJETO GIL

dezenas ou centenas de elementos de cdigo repetidos. Isso acontece da seguinte forma:


Ralph precisa escrever algum cdigo que frave o arvadent.2 Ele olha em outras partes do
cdigo onde suspeita que ocorreu outro caso de fravar o arvadent e encontra um trecho
de cdigo conveniente. Ele recorta e cola esse cdigo em seu mdulo e faz as modificaes
adequadas.
Sem o conhecimento de Ralph, o cdigo que ele pegou com o mouse foi colocado l
por Todd, que o pegou de um mdulo escrito por Lilly. Lilly foi a primeira a fravar um
arvadent, mas percebeu que fravar um arvadent era muito parecido com fravar um garnatosh. Ela encontrou em algum lugar um cdigo que fravava um garnatosh, recortou e colou
em seu mdulo e o modificou conforme foi necessrio.
Quando o mesmo cdigo aparece inmeras vezes, de formas ligeiramente diferentes, est faltando uma abstrao para os desenvolvedores. Encontrar todas as repeties
e elimin-las com uma abstrao adequada pode no estar na lista de prioridades, mas
tornaria o sistema mais fcil de entender e manter.
Quando existe cdigo redundante no sistema, o trabalho de alterar o sistema pode
se tornar rduo. Os erros encontrados em tal unidade repetida precisam ser corrigidos
em cada repetio. Contudo, como cada repetio ligeiramente diferente das outras, a
correo nem sempre a mesma.

Opacidade
Opacidade refere-se dificuldade de compreenso de um mdulo. O cdigo pode ser escrito
de maneira clara e intelegvel ou de maneira opaca e enrolada. Ainda, o cdigo tende a se tornar cada vez mais opaco com o tempo. necessrio um esforo constante a fim de manter o
cdigo claro e a opacidade diminuir.
Quando os desenvolvedores escrevem um mdulo pela primeira vez, o cdigo parece
claro para eles. Afinal, eles trabalharam muito nele e o compreendem nos mnimos detalhes. Depois de um tempo, provvel que se perguntem como foram capazes de escrever
algo to horrvel. Para evitar isso, os desenvolvedores precisam se colocar no lugar de seus
leitores e fazer um esforo para refatorar seu cdigo a fim de que todos possam compreend-lo. Seu cdigo tambm precisa ser revisado por outros.

Por que o software apodrece


Em ambientes no geis, os projetos degradam porque os requisitos mudam de maneiras
no previstas no projeto inicial. Frequentemente, essas mudanas precisam ser feitas o
mais rpido possvel e podem ser realizadas por desenvolvedores que no estejam familiarizados com a filosofia original do projeto. Assim, embora a alterao no projeto funcione,
de algum modo ela viola o projeto original. medida que as mudanas continuam, essas
violaes se acumulam at que a podrido surja.
No entanto, no podemos culpar a mudana dos requisitos pela degradao do projeto. Ns, como desenvolvedores de software, sabemos muito bem que os requisitos mudam. Alis, a maioria de ns entende que os requisitos so os elementos mais volteis do
projeto. Se nossos projetos esto fracassando devido constante mudana de requisitos,
2

Para os leitores que no tm o ingls como lngua materna, o termo fravle the arvadent (aqui levemente adaptado) composto de palavras sem sentido cujo propsito indicar alguma atividade de programao indefinvel.

O QUE PROJETO GIL?

127

so nossos projetos e prticas que esto errados. Devemos encontrar uma maneira de
tornar nossos projetos resistentes a tais mudanas e usar prticas que os protejam do
apodrecimento.
Uma equipe gil lida bem com mudanas. Ela investe pouco no incio e, assim, no
fica comprometida com um projeto inicial obsoleto. Em vez disso, a equipe mantm o projeto do sistema o mais limpo e simples possvel e o respalda com muitos testes de unidade
e de aceitao. Isso mantm o projeto flexvel e fcil de alterar. A equipe tira proveito dessa
flexibilidade para aprimorar o projeto continuamente; assim, cada iterao termina com
um sistema cujo projeto o mais adequado possvel para os requisitos dessa iterao.

O programa Copy
Um cenrio familiar
Observar um projeto apodrecer ilustra o que acabamos de relatar. Digamos que, na segunda-feira bem cedo, seu chefe pea para que voc escreva um programa que copie caracteres do teclado para a impressora. Fazendo alguns exerccios mentais rpidos, voc conclui
que isso exigir menos de dez linhas de cdigo. O tempo de projeto e codificao dever
ocupar bem menos de uma hora. Com as reunies de grupo multifuncional, as reunies
de qualidade, as reunies de progresso de grupo dirias e as trs crises atuais no campo,
esse programa dever levar cerca de uma semana para terminar se voc fizer horas extras. Entretanto, voc sempre multiplica suas estimativas por 3.
Trs semanas, voc avisa ao seu chefe. Ele d um riso de desdm e sai, deixando-o
com sua tarefa.
O projeto inicial Sobrou um tempinho antes de a reunio de reviso de processo comear e voc decide traar um projeto para o programa. Usando projeto estruturado, voc
produz o diagrama estrutural da Figura 7-1.
Existem trs mdulos (ou subprogramas) no aplicativo. O mdulo Copy chama os
outros dois. O programa Copy busca caracteres do mdulo de leitura do teclado Read
Keyboard e os encaminha para o mdulo de escrita na impressora Write Printer.
Voc examina seu projeto e constata que ele bom. Com um sorriso no rosto, sai de
seu escritrio para ir quela reunio. Pelo menos voc poder tirar uma soneca l.

Copy

char

char

Read Keyboard

Write Printer

Figura 7-1
Diagrama estrutural do programa Copy.

128

PROJETO GIL

Listagem 7-1
O programa Copy
public class Copier
{
public static void Copy()
{
int c;
while((c=Keyboard.Read())!= 1)
Printer.Write(c);
}
}

Na tera-feira, voc chega um pouco mais cedo para finalizar o programa Copy. Infelizmente, uma das crises no campo esquentou durante a noite e voc tem de ir ao laboratrio para ajudar a depurar um problema. No intervalo para o almoo, que finalmente
acontece s 15h, voc consegue digitar o cdigo do programa Copy. O resultado a Listagem 7-1.
Voc acabou de salvar a edio, quando voc termina percebe que j est atrasado para a reunio sobre qualidade. Voc no pode faltar; o tema ser a importncia
dos defeitos zero. Assim, voc engole suas batatas-fritas e sua Coca-Cola e vai para a
reunio.
Na quarta-feira, voc chega mais cedo novamente e, desta vez, nada parece estar fora de
ordem. Voc comea a compilar o cdigo-fonte do programa Copy. Ora, vejam! Ele compila
na primeira vez, sem erros! Isso bom, pois seu chefe o chama para uma reunio inesperada
sobre a necessidade de conservar toner de impressora a laser.
Na quinta-feira, depois de passar quatro horas ao telefone, acompanhando um tcnico
em Rocky Mount, Carolina do Norte, atravs de comandos de depurao e log de erros remotos em um dos componentes mais obscuros do sistema, voc pega um Hoho e testa seu
programa Copy. Ele funciona na primeira vez! timo, pois seu novo estagirio acabou de
apagar o diretrio mestre de cdigo-fonte do servidor e voc precisa encontrar as fitas de
backup mais recentes e restaur-lo. Evidentemente, o ltimo backup completo foi tirado h
trs meses e, alm dele, voc tem 94 backups incrementais para restaurar.
A sexta-feira est completamente vaga. Que bom, pois demora o dia inteiro para carregar o programa Copy em seu sistema de controle de cdigo-fonte.
Obviamente, o programa um grande sucesso e implantado em toda a empresa. Sua
reputao de excelente programador confirmada mais uma vez e voc se deleita com a glria
de suas realizaes. Com sorte, voc poder produzir 30 linhas de cdigo este ano!
Os requisitos so mutantes Alguns meses depois, seu chefe lhe informa que o programa Copy tambm deve ser capaz de ler a leitora de fita de papel. Voc range os dentes,
revira os olhos e se pergunta, por que as pessoas esto sempre mudando os requisitos.
Seu programa no foi projetado para uma leitora de fita de papel! Voc avisa seu chefe que
essa alterao vai acabar com a elegncia de seu projeto. No entanto, seu chefe inflexvel
e afirma que os usurios realmente precisam ler caracteres da leitora de fita de papel de
vez em quando.

O QUE PROJETO GIL?

129

Voc suspira e planeja as modificaes. Voc gostaria de adicionar um argumento


booleano na funo Copy. Se fosse true, voc leria a leitora de fita de papel; se fosse false, leria o teclado, como antes. Infelizmente, voc no pode alterar a interface
porque agora muitos programas usam Copy. Alterar a interface acarretaria semanas
e semanas de recompilao e novos testes. Os engenheiros de teste de sistema iriam
linch-lo, sem mencionar as sete pessoas do grupo de controle de configurao. E o inspetor de processo teria de obrigar todos os tipos de revises de cdigo para cada mdulo
que chamasse Copy!
No, alterar a interface est fora de cogitao. Mas como o programa Copy saber
que deve ler a leitora de fita de papel? Voc usar uma global! bvio! Voc tambm usar
o melhor e mais til recurso da famlia C de linguagens, o operador?:! A Listagem 7-2
mostra o resultado.
Os chamadores de Copy que queiram ler a leitora de fita de papel devem primeiro
configurar ptFlag como true. Depois, eles podem chamar Copy e o programa ler a leitora de fita de papel sem problemas. Quando Copy retornar, o chamador deve reinicializar
ptFlag; caso contrrio, o prximo chamador poder ler a leitora de fita de papel por
engano, em vez do teclado. Para lembrar os programadores de sua tarefa de reinicializar
esse flag, voc adicionou um comentrio.
Mais uma vez, voc libera seu software para a aprovao. Ele ainda mais bem-sucedido que antes e hordas de programadores aguardam ansiosamente uma oportunidade
para us-lo. A vida boa.
D a eles uma ninharia
Algumas semanas depois, seu chefe que ainda seu chefe,
apesar das trs reorganizaes de toda a empresa no mesmo intervalo de meses diz que
s vezes os clientes querem que o programa Copy gere sada na perfuradora de fita de papel. Clientes! Eles esto sempre arruinando seus projetos. Escrever software seria muito
mais fcil se no fossem os clientes. Voc comenta com seu chefe que essas alteraes
incessantes esto tendo um impacto extremamente negativo na elegncia de seu projeto,
e avisa que, se as mudanas continuarem nesse ritmo horrvel, ser impossvel manter o
software antes que o ano chegue ao fim. Seu chefe assente com a cabea e pede que voc
faa a mudana assim mesmo.

Listagem 7-2
Primeira modificao do programa Copy
public class Copier
{
//lembrar de reinicializar este flag
public static bool ptFlag = false;
public static void Copy()
{
int c;
while((c=(ptFlag ? PaperTape.Read()
: Keyboard.Read())) != 1)
Printer.Write(c);
}
}

130

PROJETO GIL

Listagem 7-3
Segunda modificao do programa Copy
public class Copier
{
//lembre de reinicializar estes flags
public static bool ptFlag = false;
// para a impressora
public static bool punchFlag = false;
// para a fita
public static void Copy()
{
int c;
while((c=(ptFlag ? PaperTape.Read()
: Keyboard.Read())) != 1)
punchFlag ? PaperTape.Punch(c): Printer.Write(c);
}
}

Essa mudana de projeto semelhante anterior. Precisamos apenas de outra global


e outro operador ?:! A Listagem 7-3 mostra o resultado de seus esforos.
Voc se orgulha de ter se lembrado de alterar o comentrio. Apesar disso, voc receia que a estrutura de seu programa esteja comeando a ruir. Qualquer alterao a mais
no dispositivo de entrada certamente o obrigar a reestruturar completamente a condicional do loop while. Talvez seja hora de tirar a poeira de seu currculo.
Espere mudanas Voc tem a liberdade de determinar quanto dessa histria foi um exagero satrico. O objetivo foi mostrar como o projeto de um programa pode degradar rapidamente na presena de mudanas. O projeto original do programa Copy era simples e
elegante. Depois de apenas duas alteraes, ele comeou a mostrar sinais de rigidez, fragilidade, imobilidade, complexidade, redundncia e opacidade. Essa tendncia certamente vai
continuar e o programa se tornar uma baguna.
Poderamos cruzar os braos e culpar as alteraes. Poderamos nos queixar que
o programa foi bem projetado para a especificao original e que as mudanas na especificao fizeram o projeto degradar. Contudo, isso desconsidera um dos fatos mais
importantes no desenvolvimento de software: os requisitos sempre mudam!
Lembre que as coisas mais volteis na maioria dos projetos de software so os
requisitos. Os requisitos mudam constantemente. Esse um fato que ns, desenvolvedores, precisamos aceitar! Vivemos em um mundo de requisitos que mudam e nosso
trabalho garantir que o software sobreviva a essas mudanas. Se o projeto de nosso software degradar porque os requisitos mudaram, no estamos sendo geis.

O QUE PROJETO GIL?

131

Listagem 7-4
Verso gil 2 de Copy
public interface Reader
{
int Read();
}
public class KeyboardReader: Reader
{
public int Read() {return Keyboard.Read();}
}
public class Copier
{
public static Reader reader = new KeyboardReader();
public static void Copy()
{
int c;
while((c=(reader.Read())) != 1)
Printer.Write(c);
}
}

Projeto gil do programa Copy


Uma equipe de desenvolvimento gil comea exatamente da mesma maneira, com o cdigo
da Listagem 7-1.3 Quando o chefe pede que o programa leia a leitora de fita de papel, os
desenvolvedores alteram o projeto de modo que seja flexvel a esse tipo de mudana. O resultado parecido com a Listagem 7-4.
Em vez de tentar reparar o projeto para fazer o novo requisito funcionar, a equipe
aproveita a oportunidade para melhor-lo, de modo que ele seja mais flexvel a esse tipo
de alterao no futuro. Assim, quando o chefe solicitar um novo tipo de dispositivo de
entrada, a equipe responder de modo a no causar degradao no programa Copy.
A equipe seguiu o Princpio do Aberto/Fechado (OCP), que descrevemos no Captulo 9. Esse princpio nos orienta a projetar nossos mdulos de modo que eles possam ser
estendidos sem modificao. Exatamente o que a equipe fez. Todo novo dispositivo de
entrada que o chefe pedir ser providenciado sem modificar o programa Copy.
No entanto, observe que na primeira vez que projetou o mdulo, a equipe no tentou
prever como o programa iria mudar. Em vez disso, escreveu o mdulo da maneira mais
simples possvel. A equipe modificou o projeto do mdulo para ser flexvel a alteraes
somente quando os requisitos mudaram.
Voc poderia pensar que a equipe fez apenas metade do trabalho. Os desenvolvedores se precaveram contra diferentes dispositivos de entrada, mas eles tambm poderiam
3

Na verdade, provavelmente a prtica do desenvolvimento guiado por testes obrigaria o projeto a ser flexvel
o suficiente para suportar o chefe sem alterao. Contudo, neste exemplo, vamos ignorar isso.

132

PROJETO GIL

ter se precavido contra diferentes dispositivos de sada. Contudo, na verdade a equipe no


tinha como prever que os dispositivos de sada mudariam. Acrescentar proteo extra
antes no teria utilidade. claro que, se tal proteo fosse necessria, seria fcil acrescent-la posteriormente. Portanto, no haveria motivo para acrescent-la antes.
Seguindo prticas geis Os desenvolvedores geis de nosso exemplo construram uma
classe abstrata para se precaver contra mudanas no dispositivo de entrada. Como eles
souberam fazer isso? A resposta est em um dos dogmas fundamentais do projeto orientado a objetos.
O projeto inicial do programa Copy rgido por causa da direo de suas dependncias. Veja a Figura 7-1 novamente. Note que o mdulo Copy depende diretamente de
KeyboardReader e de PrinterWriter. Copy um mdulo de alto nvel nesse aplicativo.
Ele define a poltica do aplicativo. Ele sabe como copiar caracteres. Infelizmente, ele tambm se tornou dependente dos detalhes de baixo nvel do teclado e da impressora. Assim,
quando os detalhes de baixo nvel mudam, a poltica de alto nvel afetada.
Quando a rigidez foi exposta, os desenvolvedores geis sabiam que a dependncia
do mdulo Copy em relao ao dispositivo de entrada precisava ser invertida, usando o
Princpio da Inverso de Dependncia (DIP) do Captulo 11, de modo que Copy no dependesse mais desse dispositivo. Assim, eles usaram o padro STRATEGY, discutido no
Captulo 22, para produzir a inverso desejada.
Portanto, em poucas palavras, os desenvolvedores geis sabiam o que fazer porque
seguiram estes passos:
1

Eles detectaram o problema seguindo as prticas geis.

2. Eles diagnosticaram o problema aplicando princpios de projeto.


3. Eles resolveram o problema aplicando um padro de projeto apropriado.
A interao entre esses trs aspectos do desenvolvimento de software o ato de projetar.
Mantendo o projeto to bom quanto possvel
Os desenvolvedores geis se dedicam a
manter o projeto o mais adequado e limpo possvel. Esse no um comprometimento
casual ou experimental. Os desenvolvedores geis no limpam o projeto a cada poucas
semanas. Em vez disso, eles mantm o software o mais limpo, simples e expressivo que
puderem todos os dias, a toda hora e a todo minuto. Eles nunca dizem: Vamos corrigir
isso depois. Eles no deixam a podrido comear.
A atitude que os desenvolvedores geis tm em relao ao projeto do software
a mesma que os cirurgies tm em relao ao procedimento de esterilizao. O procedimento de esterilizao o que torna a cirurgia possvel. Sem ele, o risco de infeco
seria intolervel. Os desenvolvedores geis se sentem da mesma maneira em relao
aos seus projetos. O risco de permitir que mesmo a menor deteriorao comece alto
demais.
O projeto deve permanecer limpo. E como o cdigo-fonte a expresso mais importante do projeto, ele tambm deve permanecer limpo. O profissionalismo prescreve que
ns, desenvolvedores de software, no podemos tolerar a podrido de cdigo.

O QUE PROJETO GIL?

133

Concluso
O que projeto gil, afinal? Projeto gil um processo e no uma circunstncia. a aplicao contnua de princpios, padres e prticas para melhorar a estrutura e a legibilidade
do software. a dedicao em manter o projeto do sistema o mais simples, limpo e expressivo possvel, o tempo todo.
Nos captulos a seguir, vamos investigar os princpios e padres do projeto de software. medida que voc ler, lembre que um desenvolvedor gil no aplica esses princpios
e padres de antemo em um projeto grande. Em vez disso, eles so aplicados de iterao
para iterao, em uma tentativa de manter limpos o cdigo e o projeto que o incorpora.

Bibliografia
[Reeves92] Jack Reeves, What Is Software Design?, C++ Journal, (2), 1992. Tambm
disponvel no endereo www.bleading-edge.com/Publications/C++Journal/Cpjour2.htm.

Captulo 8

PRINCPIO DA
RESPONSABILIDADE
NICA (SRP)
Ningum, a no ser o Buda, deve assumir a
responsabilidade de revelar segredos ocultos
E. Cobham Brewer 1810-1897, Dictionary of Phrase and Fable (1898)

Princpio da Responsabilidade nica (SRP Single Responsibility Principle) foi


descrito no trabalho de Tom DeMarco1 e Meilir Page-Jones.2 Eles o chamavam de
coeso, o que definiam como a afinidade funcional dos elementos de um mdulo. Neste
captulo, alteramos um pouco esse significado e relacionamos a coeso com as foras que
fazem um mdulo (ou uma classe) mudar.

Princpio da Responsabilidade nica


Uma classe deve ter apenas um motivo para mudar.

Considere o jogo de boliche do Captulo 6. Na maior parte de seu desenvolvimento,


a classe Game estava lidando com duas responsabilidades: controlar o quadro atual e calcular o placar. No fim, RCM e RSK separaram essas duas responsabilidades em duas classes. A classe Game manteve a responsabilidade de controlar os quadros e a classe Scorer
assumiu a responsabilidade de calcular o placar.
Por que era importante separar essas duas responsabilidades em classes distintas?
Porque cada responsabilidade um eixo de mudana. Quando os requisitos mudarem, a
alterao se manifestar por meio de uma mudana na responsabilidade entre as classes.
Se uma classe assumir mais de uma responsabilidade, ela ter mais de um motivo para
mudar.

[DeMarco79], p. 310
[PageJones88], p. 82

136

PROJETO GIL

Computational
Geometry
Application

Rectangle
+ draw()
+ area() : double

Graphical
Application

GUI

Figura 8-1
Mais de uma responsabilidade.
Se uma classe tem mais de uma responsabilidade, as responsabilidades se tornam
acopladas. Mudanas em uma responsabilidade podem prejudicar ou inibir a capacidade
da classe de cumprir as outras. Esse tipo de acoplamento leva a projetos frgeis que estragam de maneiras inesperadas quando alterados.
Considere, por exemplo, o projeto da Figura 8-1. A classe Rectangle mostra dois
mtodos. Um deles desenha o retngulo na tela e o outro calcula a rea do retngulo.
Dois aplicativos diferentes usam a classe Rectangle. Um deles faz geometria
computacional. Usa Rectangle para ajudar na matemtica das formas geomtricas,
mas nunca desenha o retngulo na tela (GUI Graphical User Interface, Interface Grfica com o Usurio). O outro aplicativo grfico por natureza e tambm pode efetuar
alguma geometria computacional, mas claramente desenha o retngulo na tela.
Esse projeto viola o SRP. A classe Rectangle tem duas responsabilidades. A primeira fornecer um modelo matemtico da geometria de um retngulo. A segunda representar o retngulo em uma interface grfica.
A violao do SRP causa vrios problemas desagradveis. Primeiro, precisamos
incluir GUI no aplicativo de geometria computacional. Em .NET, o assembly GUI precisaria ser construdo e entregue com o aplicativo de geometria computacional.
Segundo, se uma alterao em GraphicalApplication fizer Rectangle mudar por
algum motivo, essa mudana poder nos obrigar a reconstruir, testar novamente e entregar ComputationalGeometryApplication outra vez. Se esquecermos de fazer isso, esse
aplicativo poder estragar de maneiras imprevisveis.
Um projeto melhor separar as duas responsabilidades em duas classes completamente diferentes, como mostrado na Figura 8-2. Esse projeto move as partes computacionais de Rectangle para a classe GeometricRectangle. Agora, as mudanas
feitas no modo como os retngulos so desenhados no podem afetar ComputationalGeometryApplication.

PRINCPIO DA RESPONSABILIDADE NICA (SRP)

Computational
Geometry
Application

Geometric
Rectangle

137

Graphical
Application

Rectangle
+ draw()

GUI

+ area() : double

Figura 8-2
Responsabilidades separadas.

Definindo uma responsabilidade


No contexto do SRP, definimos uma responsabilidade como um motivo para mudar.
Se voc consegue pensar em mais de um motivo para mudar uma classe, essa classe
tem mais de uma responsabilidade. s vezes difcil ver isso. Estamos acostumados
a considerar a responsabilidade em grupos. Pense, por exemplo, na interface Modem
da Listagem 8-1. A maioria de ns concordar que essa interface parece perfeitamente
razovel. As quatro funes que ela declara certamente so funes pertencentes a um
modem.
No entanto, duas responsabilidades esto sendo mostradas aqui. A primeira
o gerenciamento da conexo. A segunda, a comunicao de dados. As funes dial e
hangup gerenciam a conexo do modem; as funes send e recv transmitem dados.
Essas duas responsabilidades devem ser separadas? Isso depende de como o
aplicativo est mudando. Se o aplicativo mudar de uma maneira que afete a assinatura
das funes de conexo, o projeto ter o mau cheiro da rigidez, pois as classes que
chamam send e read precisaro ser recompiladas e reentregues com mais frequncia do que gostaramos. Nesse caso, as duas responsabilidades devem ser separadas,
como mostrado na Figura 8-3. Isso impede os aplicativos clientes de acoplar as duas
responsabilidades.

Listagem 8-1
Modem.cs -- violao do SRP
public interface Modem
{
public void Dial(string pno);
// Discar
public void Hangup();
// Desligar
public void Send(char c); // Enviar
public char Recv();
// Receber
}

138

PROJETO GIL

interface

interface

Data
Channel

Connection

+ send(:char)
+ recv() : char

+ dial(pno : String)
+ hangup()

Modem
Implementation

Figura 8-3
Interface de modem separada.
Por outro lado, se o aplicativo no estiver mudando de uma maneira que faa as duas responsabilidades mudarem em momentos diferentes, no haver necessidade de separ-las.
Alis, separ-las teria o mau cheiro da complexidade desnecessria.
H um corolrio aqui. Um eixo de mudana s ser um eixo de mudana se as
mudanas ocorrerem. No sensato aplicar SRP alis, qualquer outro princpio se
no houver um sintoma.

Separando responsabilidades acopladas


Note que, na Figura 8-3, mantivemos as duas responsabilidades acopladas na classe
ModemImplementation. Isso no desejvel, mas pode ser necessrio. Frequentemente existem motivos, relacionados aos detalhes do hardware ou do sistema operacional,
que nos obrigam a acoplar o que normalmente no acoplaramos. Contudo, separando
suas interfaces, desacoplamos os conceitos no que diz respeito ao restante do aplicativo.
Podemos ver a classe ModemImplementation como uma soluo deselegante ou
uma verruga; no entanto, note que todas as dependncias desapareceram dela. Ningum
precisa depender dessa classe. Ningum, exceto main, precisa saber que ela existe. Assim,
colocamos o trecho feio atrs de uma cerca. Sua feiura no precisa vir a pblico e poluir
o resto do aplicativo.

Persistncia
A Figura 8-4 mostra uma violao comum do SRP. A classe Employee (que representa um
funcionrio) contm regras de negcio (como calcular o pagamento) e controle de persistncia (armazenar). Essas duas responsabilidades quase nunca devem ser misturadas.
As regras de negcio tendem a mudar frequentemente e, embora a persistncia possa no
mudar com tanta frequncia, ela muda por motivos completamente diferentes. Vincular
regras de negcio ao subsistema de persistncia procurar problemas.

PRINCPIO DA RESPONSABILIDADE NICA (SRP)

139

Employee
Subsistema de
Persistncia

+ CalculatePay
+ Store

Figura 8-4
Persistncia acoplada.
Felizmente, conforme vimos no Captulo 4, a prtica do desenvolvimento guiado por testes
normalmente obrigar essas duas responsabilidades a serem separadas muito antes que
o projeto comece a cheirar mal. Contudo, se os testes no impuserem a separao e se os
maus cheiros da rigidez e da fragilidade se tornarem fortes, o projeto dever ser refatorado, usando-se os padres FAADE (FACHADA), DAO (Data Access Object Objeto de
Acesso a Dados) ou PROXY para separar as duas responsabilidades.

Concluso
O Princpio da Responsabilidade nica simples, porm difcil de acertar. Temos a tendncia de unir responsabilidades. Encontrar e separar essas responsabilidades corresponde em grande parte ao projeto de software em si. De fato, os demais princpios que
discutiremos de certo modo sempre voltam a esse problema.

Bibliografia
[DeMarco79] Tom DeMarco, Structured Analysis and System Specification, Yourdon
Press Computing Series, 1979.
[PageJones88] Meilir Page-Jones, The Practical Guide to Structured Systems Design, 2d
ed., Yourdon Press Computing Series, 1988.

Captulo 9

PRINCPIO DO ABERTO/
FECHADO (OCP)
Porta holandesa: Substantivo. Porta dividida horizontalmente de modo que as
partes inferior e superior podem ser abertas de maneira independente.
The American Heritage Dictionary of
the English Language, quarta edio, 2000

omo disse Ivar Jacobson, Todos os sistemas mudam durante seus ciclos de vida.
Isso deve ser lembrado ao se desenvolver sistemas com expectativa de durar mais
do que a primeira verso.1 Como podemos criar projetos que sejam estveis em face
mudana e que durem mais do que a primeira verso? Bertrand Meyer2 nos orientou h
muito tempo, em 1988, quando inventou o agora famoso princpio do aberto/fechado.
Para parafrase-lo:

Princpio do Aberto/Fechado (OCP)


As entidades de software (classes, mdulos, funes etc.) devem ser abertas para
ampliao, mas fechadas para modificao.

Quando uma nica mudana em um programa resulta em uma sucesso de mudanas nos mdulos dependentes, o projeto tem o mau cheiro da rigidez. O OCP nos aconselha a refatorar o sistema para que alteraes desse tipo no causem mais modificaes.
Se o OCP for bem aplicado, mudanas desse tipo so obtidas pela adio de novo cdigo
e no pela alterao de cdigo antigo que j funciona. Isso pode parecer bom demais um
ideal inatingvel , mas, na verdade, existem algumas estratgias relativamente simples e
eficazes para se aproximar desse ideal.

1
2

[Jacobson92], pg. 21
[Meyer97]

142

PROJETO GIL

Descrio do OCP
Os mdulos que obedecem ao OCP tm duas caractersticas principais.
1. Eles so abertos para ampliao. Isso significa que o comportamento do mdulo
pode ser ampliado. medida que os requisitos do aplicativo mudam, podemos ampliar o mdulo com novos comportamentos que satisfaam essas alteraes. Em
outras palavras, podemos mudar o que o mdulo faz.
2. Eles so fechados para modificao. Ampliar o comportamento de um mdulo no
resulta em mudanas no cdigo-fonte (ou binrio) do mdulo. A verso em binrio
executvel do mdulo seja em uma biblioteca que pode ser vinculada, uma DLL ou
um arquivo .EXE permanece intacta.
Essas duas caractersticas parecem estar em conflito. O modo normal de ampliar o
comportamento de um mdulo alterando seu cdigo-fonte. Um mdulo que no pode ser
alterado normalmente considerado como tendo um comportamento fixo.
Como possvel modificar os comportamentos de um mdulo sem alterar seu cdigo-fonte? Como podemos mudar o que o mdulo faz sem alter-lo?
A resposta : com abstrao. Em C# ou em qualquer outra linguagem de programao orientada a objetos (OOPL Object-Oriented Programming Language), possvel criar
abstraes fixas e que ainda assim representem um grupo ilimitado de comportamentos
possveis. As abstraes so classes base abstratas e o grupo ilimitado de comportamentos possveis representado por todas as classes derivadas possveis.
Um mdulo pode manipular uma abstrao. Tal mdulo pode ser fechado para modificao, pois ele depende de uma abstrao fixa. Apesar disso, o comportamento desse
mdulo pode ser ampliado pela criao de novas derivadas da abstrao.
A Figura 9-1 mostra um projeto simples que no obedece ao OCP. As classes Client
e Server so concretas. A classe Client usa a classe Server. Se quisermos que um
objeto Client use um objeto de servidor diferente, a classe Client deve ser alterada para
citar a nova classe de servidor.
A Figura 9-2 mostra o projeto correspondente que obedece ao OCP, usando o padro STRATEGY (consulte o Captulo 22). Nesse caso, a classe ClientInterface
abstrata, com funes membro abstratas. A classe Client usa essa abstrao. Contudo,
os objetos da classe Client usaro objetos da classe derivada Server. Se quisermos
que objetos Client usem uma classe de servidor diferente, uma nova derivada da classe
ClientInterface pode ser criada. A classe Client pode permanecer inalterada.

Client

Server

Figura 9-1
Client no aberta e fechada.

PRINCPIO DO ABERTO/FECHADO (OCP)

143

interface
Client Interface

Client

Server

Figura 9-2
Padro STRATEGY: Client aberta e fechada.
A classe Client tem algum trabalho que precisa fazer e pode descrev-lo em termos
da interface abstrata apresentada por ClientInterface. Os subtipos de ClientInterface podem implementar essa interface da maneira que escolherem. Assim, o comportamento especificado em Client pode ser ampliado e modificado pela criao de novos
subtipos de ClientInterface.
Voc pode estar se perguntando por que chamei ClientInterface dessa maneira.
Por que, em vez disso, no chamei de AbstractServer? O motivo, conforme veremos
posteriormente, que as classes abstratas so mais intimamente associadas aos seus
clientes do que s classes que as implementam.
A Figura 9-3 mostra uma estrutura alternativa usando o padro TEMPLATE
METHOD (consulte o Captulo 22). A classe Policy tem um conjunto de funes pblicas
concretas que implementam uma diretriz (policy), semelhantes s funes de Client na
Figura 9-2. Como antes, essas funes de diretriz descrevem o trabalho que precisa ser
feito em termos de algumas interfaces abstratas. Neste caso, no entanto, as interfaces
abstratas fazem parte da prpria classe Policy. Em C#, elas seriam mtodos abstratos.
Essas funes so implementadas nos subtipos de Policy. Assim, os comportamentos
especificados dentro de Policy podem ser ampliados ou modificados pela criao de novas derivadas da classe Policy.
Esses dois padres so as maneiras mais comuns de satisfazer o OCP. Eles representam uma clara separao entre a funcionalidade genrica e a implementao detalhada
dessa funcionalidade.

Policy
+ PolicyFunction()
# ServiceFunction()

Implementao
# ServiceFunction()

Figura 9-3
Padro TEMPLATE METHOD: a classe base aberta e fechada.

144

PROJETO GIL

O aplicativo Shape
O exemplo Shape tem aparecido em muitos livros sobre projeto orientado a objetos. Esse
exemplo abominvel muito usado para mostrar o funcionamento do polimorfismo. Desta vez, no entanto, ele ser usado para explicar o OCP.
Temos um aplicativo que deve ser capaz de desenhar crculos e quadrados em uma
interface grfica de usurio padro. Os crculos e quadrados devem ser desenhados em
uma ordem especfica. Uma lista de crculos e quadrados ser criada na ordem apropriada
e o programa deve percorrer a lista nessa ordem e desenhar cada crculo ou quadrado.

Violando o OCP
Em C, usando tcnicas procedurais que no obedecem ao OCP, poderamos resolver esse
problema como mostrado na Listagem 9-1. Aqui, vemos um conjunto de estruturas de
dados que tm o mesmo primeiro elemento, mas que, fora isso, so diferentes. O primeiro
elemento de cada uma delas um cdigo de tipo que identifica a estrutura de dados como
Circle ou como Square. A funo DrawAllShapes percorre um array de ponteiros para
essas estruturas de dados, examinando o cdigo do tipo e, ento, chamando a funo
apropriada, DrawCircle ou DrawSquare.

Listagem 9-1
Soluo procedural para o problema do quadrado/
crculo
--shape.h--------------------------------------enum ShapeType {circle, square};
struct Shape
{
ShapeType itsType;
};
--circle.h--------------------------------------struct Circle
{
ShapeType itsType;
double itsRadius;
Point itsCenter;
};
void DrawCircle(struct Circle*);
--square.h--------------------------------------struct Square
{
ShapeType itsType;
double itsSide;
Point itsTopLeft;

PRINCPIO DO ABERTO/FECHADO (OCP)

145

};
void DrawSquare(struct Square*);
--drawAllShapes.cc------------------------------typedef struct Shape *ShapePointer;
void DrawAllShapes(ShapePointer list[], int n)
{
int i;
for (i=0; i<n; i++)
{
struct Shape* s = list[i];
switch (s->itsType)
{
case square:
DrawSquare((struct Square*)s);
break;
case circle:
DrawCircle((struct Circle*)s);
break;
}
}
}

Como no pode ser fechada para novos tipos de figuras, a funo DrawAllShapes no
obedece ao OCP. Se eu quisesse ampliar essa funo para desenhar uma lista de figuras
que inclusse tringulos, teria de modific-la. Na verdade, eu teria de modificar a funo para qualquer novo tipo de figura que precisasse desenhar.
Evidentemente, esse programa apenas um exemplo simples. Na vida real, a instruo switch da funo DrawAllShapes seria repetida muitas vezes em vrias funes ao longo de todo o aplicativo, cada uma fazendo algo um pouco diferente. Poderia
haver uma de cada para arrastar figuras, alongar figuras, mover figuras, excluir figuras
etc. Adicionar uma nova figura a esse aplicativo significa procurar cada lugar em que
existam tais instrues switch ou encadeamentos de if/else e adicionar a nova
figura a cada uma.
Alm disso, bastante improvvel que todas as instrues switch e encadeamentos de if/else sejam to perfeitamente estruturados como o que est em DrawAllShapes. muito mais provvel que os predicados das instrues if sejam combinados
com operadores lgicos ou que as clusulas case das instrues switch sejam combinadas para simplificar a tomada de deciso local. Em algumas situaes patolgicas,
as funes podem fazer com objetos Square exatamente as mesmas coisas que fazem
com objetos Circle. Tais funes nem mesmo teriam as instrues switch/case ou
os encadeamentos de if/else. Assim, o problema de encontrar e entender todos os
lugares onde a nova figura precisa ser adicionada pode no ser simples.
Alm disso, considere os tipos de alteraes que teriam de ser feitas. Teramos de
adicionar um novo membro enumerao ShapeType. Como todas as diferentes figu-

146

PROJETO GIL

ras dependem da declarao dessa enumerao, teramos de recompilar todas elas.3


Alm disso, tambm precisaramos recompilar todos os mdulos que dependem de
Shape.
Portanto, precisaramos no apenas modificar o cdigo-fonte de todas as instrues
switch/case ou encadeamentos de if/else, mas tambm alterar os arquivos binrios,
por meio de recompilao, de todos os mdulos que utilizam qualquer uma das estruturas de dados Shape. Alterar os arquivos binrios significa que os assemblies, as DLLs ou
qualquer outro tipo de componente binrio precisar ser entregue novamente. O simples
ato de adicionar uma nova figura no aplicativo acarreta uma sucesso de mudanas subsequentes em muitos mdulos do cdigo-fonte e mais ainda em mdulos binrios e componentes binrios. Claramente, o impacto de adicionar uma nova figura muito grande.
Vamos repassar isso rapidamente. A soluo da Listagem 9-1 rgida porque a
adio de Triangle faz Shape, Square, Circle e DrawAllShapes serem recompiladas e novamente entregues. A soluo frgil porque haver muitas outras instrues
switch/case ou if/else que sero difceis de encontrar e decifrar. A soluo imvel porque qualquer um que tente reutilizar DrawAllShapes em outro programa ser
obrigado a carregar Square e Circle, mesmo que esse novo programa no precise
delas. Em resumo, a Listagem 9-1 manifesta muitos maus cheiros de um projeto ruim.

Obedecendo ao OCP
A Listagem 9-2 mostra o cdigo de uma soluo para o problema square/circle que
obedece ao OCP. Nesse caso, escrevemos uma classe abstrata chamada Shape. Essa classe abstrata tem um mtodo abstrato chamado Draw. Circle e Square so derivadas da
classe Shape.
Note que, para ampliar o comportamento da funo DrawAllShapes na Listagem 9-2
para desenhar um novo tipo de figura, basta adicionar uma nova classe derivada da classe
Shape. A funo DrawAllShapes no precisa mudar. Assim, DrawAllShapes obedece ao
OCP. Seu comportamento pode ser estendido sem modific-la. Alis, adicionar uma classe
Triangle no efeito algum sobre quaisquer mdulo mostrado aqui. Claramente, alguma
parte do sistema deve mudar para lidar com a classe Triangle, mas todo o cdigo mostrado
aqui imune mudana.
Em um aplicativo real, a classe Shape teria muito mais mtodos. Apesar disso,
adicionar uma nova figura ao aplicativo ainda muito simples, pois basta criar a nova
derivada e implementar todas as suas funes. No h necessidade de percorrer todo o
aplicativo procurando lugares que exigem alteraes. Esta soluo no frgil.
Tambm no uma soluo rgida. Nenhum dos mdulos de cdigo-fonte existentes
precisa ser modificado e nenhum dos mdulos binrios existentes precisa ser recompilado com uma exceo. O mdulo que cria instncias da nova classe derivada de Shape
precisa ser modificado. Normalmente, isso feito por main, em alguma funo chamada
por main ou no mtodo de algum objeto criado por main.4
3

Em C/C++, as alteraes em enumeraes podem causar uma mudana no tamanho da varivel usada
para conter a enumerao. Assim, voc deve tomar muito cuidado se decidir que no precisa recompilar
as outras declaraes de figura.
4
Tais objetos, conhecidos como fbricas, sero abordados em mais detalhes no Captulo 29.

PRINCPIO DO ABERTO/FECHADO (OCP)

147

Listagem 9-2
Soluo de projeto orientado a objetos para o
problema Square/Circle
public interface Shape
{
void Draw();
}
public class Square: Shape
{
public void Draw()
{
//desenha um quadrado
}
}
public class Circle: Shape
{
public void Draw()
{
//desenha um crculo
}
}
public void DrawAllShapes(IList shapes)
{
foreach(Shape shape in shapes)
shape.Draw();
}

Por fim, a soluo no imvel. DrawAllShapes pode ser reutilizada por qualquer
aplicativo, sem a necessidade de usar Square ou Circle junto. Assim, a soluo no manifesta as caractersticas de mau projeto mencionadas.
Este programa obedece ao OCP. Ele alterado pela adio de novo cdigo, em vez
de o ser pela alterao de cdigo j existente. Portanto, o programa no experimenta a
sucesso de mudanas exibida por programas que no obedecem ao princpio. As nicas
alteraes exigidas so a adio do novo mdulo e a alterao de main relacionada que
permita a instanciao dos novos objetos.
Mas considere o que aconteceria com a funo DrawAllShapes da Listagem 9-2, se
decidssemos que todos os objetos Circle devem ser desenhados antes de qualquer objeto
Square. A funo DrawAllShapes no fechada em relao a uma alterao como essa. Para
implementar essa mudana, precisaramos entrar em DrawAllShapes e percorrer a lista,
primeiro em busca de objetos Circle, depois em busca de objetos Square.

Antecipao e estrutura natural


Se tivssemos antecipado esse tipo de mudana, poderamos ter inventado uma abstrao que nos protegesse dela. As abstraes que escolhemos na Listagem 9-2 prejudicam

148

PROJETO GIL

mais do que ajudam esse tipo de alterao. Isso parece surpreendente; afinal, que cdigo
poderia ser mais natural do que uma classe base Shape com derivadas Square e Circle? Por que esse modelo natural do mundo real no a melhor opo? Claramente, a
resposta que esse modelo no natural em um sistema em que a ordem est acoplada
ao tipo de figura.
Isso nos leva a uma concluso perturbadora. Em geral, independentemente de quanto
um mdulo seja fechado, sempre haver algum tipo de mudana com relao qual ele no
ser fechado. No existe um modelo que seja natural para todos os contextos!
Como o fechamento no pode ser completo, ele deve ser estratgico. Isto , o projetista deve escolher os tipos de alteraes em relao s quais vai fechar o projeto, deve
imaginar os tipos de mudanas que sero mais provveis e, ento, construir abstraes
para se precaver contra essas mudanas.
Isso exige certa anteviso, derivada da experincia. Os projetistas experientes esperam conhecer os usurios e o setor bem o suficiente para avaliar a probabilidade de
diversos tipos de mudanas. Ento, esses projetistas invocam o OCP perante as mudanas
mais provveis.
Isso no fcil, pois significa fazer conjecturas fundamentadas sobre os provveis tipos de mudanas que o aplicativo sofrer ao longo do tempo. Quando os projetistas fazem
suposies corretas, eles vencem. Quando fazem suposies erradas, perdem. E algumas
vezes eles certamente faro suposies erradas.
Alm disso, obedecer ao OCP dispendioso. necessrio tempo de desenvolvimento e esforo para criar as abstraes adequadas. Essas abstraes tambm aumentam a
complexidade do projeto de software. Existe um limite para a quantidade de abstrao
que os desenvolvedores podem se permitir. Claramente, queremos limitar a aplicao do
OCP s mudanas provveis.
Como sabemos quais mudanas so provveis? Fazemos a pesquisa apropriada, fazemos as perguntas adequadas e utilizamos nossa experincia e bom senso. Depois de
tudo isso, esperamos at que as mudanas aconteam!

Inserindo os ganchos
Como nos precavemos contra as mudanas? No sculo passado, dizamos que teramos
de inserir os ganchos para as mudanas que pensvamos que poderiam ocorrer. Achvamos que isso tornaria nosso software flexvel.
Contudo, os ganchos que inseramos frequentemente eram incorretos. Pior ainda,
eles tinham o mau cheiro da complexidade desnecessria que precisava ser suportada e
mantida, mesmo que os ganchos no fossem usados. Isso no bom. No queremos carregar o projeto com muitas abstraes desnecessrias. Em vez disso, queremos inserir a
abstrao apenas quando precisamos dela.
Engane-me uma vez
Engane-me uma vez e voc deveria se envergonhar. Engane-me
duas vezes e eu deveria me envergonhar. Essa uma atitude poderosa no projeto de software. Para evitar que nosso software fique cheio de complexidade desnecessria, podemos
nos permitir ser enganados uma vez. Isso significa que, inicialmente, escrevemos nosso
cdigo esperando que ele no mude. Quando ocorre uma mudana, implementamos as

PRINCPIO DO ABERTO/FECHADO (OCP)

149

abstraes que nos protegem de mudanas futuras desse tipo. Em resumo, levamos o primeiro tiro e, depois, nos certificamos de estarmos protegidos de mais tiros provenientes
dessa arma especfica.
Estimulando a mudana Se decidimos levar o primeiro tiro, ser vantajoso receber as
balas desde o incio e com frequncia. Queremos saber quais tipos de mudanas so provveis, antes de termos ido muito longe no caminho do desenvolvimento. Quanto mais
tempo esperarmos para descobrir quais tipos de mudanas sero provveis, mais difcil
ser criar as abstraes adequadas.
Portanto, precisamos estimular as mudanas. Fazemos isso por meio das diversas
maneiras discutidas no Captulo 2.
Escrevemos os testes primeiro. O teste um tipo de utilizao do sistema. Escrevendo os testes primeiro, obrigamos o sistema a ser passvel de teste. Portanto, mudanas na capacidade de testar no nos surpreendero posteriormente. Teremos construdo as abstraes que tornam o sistema passvel de teste. Provavelmente, muitas
dessas abstraes nos precavero contra outros tipos de mudanas no futuro.
Utilizamos ciclos de desenvolvimento muito curtos: dias, em vez de semanas.
Desenvolvemos as funcionalidades antes da infraestrutura e mostramos essas funcionalidades frequentemente para os interessados.
Desenvolvemos primeiro as funcionalidades mais importantes.
Entregamos o software desde cedo e com frequncia. Fazemos isso na frente de nossos clientes e usurios o mais rpida e frequentemente possvel.

Usando abstrao para obter fechamento explcito


Certo, levamos o primeiro tiro. O usurio quer que desenhemos todos os objetos Circle
antes de qualquer objeto Square. Agora queremos nos precaver contra qualquer mudana
futura desse tipo.
Como podemos fechar a funo DrawAllShapes em relao a mudanas na ordem
de desenho? Lembre que o fechamento baseado na abstrao. Assim, para fechar DrawAllShapes em relao ordem, precisamos de algum tipo de abstrao de ordem.
Essa abstrao forneceria uma interface abstrata, por meio da qual qualquer diretiva de
ordenao possvel poderia ser expressa.
Uma diretiva de ordenao significa que, dados quaisquer dois objetos, possvel
descobrir qual deve ser desenhado primeiro. A linguagem C# fornece tal abstrao. IComparable uma interface com um s mtodo, CompareTo. Esse mtodo recebe um objeto
como parmetro e retorna 1 se o objeto receptor for menor do que o parmetro, 0 se eles
forem iguais e 1 se o objeto receptor for maior do que o parmetro.
A Figura 9-3 mostra como a classe Shape poderia ficar ao estender a interface IComparable.

150

PROJETO GIL

Listagem 9-3
Shape estendendo IComparable
public interface Shape: IComparable
{
void Draw();
}

Listagem 9-4
DrawAllShapes com ordenao
public void DrawAllShapes(ArrayList shapes)
{
shapes.Sort();
foreach(Shape shape in shapes)
shape.Draw();
}

Agora que temos uma maneira de determinar a ordem relativa de dois objetos Shape,
podemos orden-los e, ento, desenh-los em ordem. A Listagem 9-4 mostra o cdigo C#
que faz isso.
Isso nos proporciona uma maneira de ordenar objetos Shape e desenh-los na ordem apropriada. Mas ainda no temos uma abstrao de ordem decente. Como se v, os
objetos Shape individuais tero de sobrescrever o mtodo CompareTo para especificar a
ordenao. Como isso funcionaria? Que tipo de cdigo escreveramos em Circle.CompareTo para garantir que os objetos Circle fossem desenhados antes dos objetos Square?
Considere a Listagem 9-5.

Listagem 9-5
Ordenando um objeto Circle
public class Circle: Shape
{
public int CompareTo(object o)
{
if(o is Square)
return 1;
else
return 0;
}
}

PRINCPIO DO ABERTO/FECHADO (OCP)

151

Claramente, essa funo e todas as suas irms nas outras classes derivadas de Shape no obedecem ao OCP. No h como fech-las em relao s novas derivadas de Shape.
Sempre que uma nova classe derivada de Shape for criada, todas as funes CompareTo() precisaro ser alteradas.5
Isso no ter importncia se nenhuma nova classe derivada de Shape for criada. Por
outro lado, se elas fossem criadas com frequncia, esse projeto causaria uma quantidade significativa de alteraes e descarte de cdigo. Novamente, levaramos o primeiro tiro.

Usando uma estratgia orientada a dados para obter fechamento


Se devemos fechar as classes derivadas de Shape quanto a uma saber da outra, podemos
usar uma estratgia baseada em tabela. A Listagem 9-6 mostra uma possibilidade.
Adotando essa estratgia, fechamos com xito a funo DrawAllShapes em relao
aos problemas de ordenao em geral e cada uma das classes derivadas de Shape em relao criao de novas derivadas de Shape ou de uma mudana na diretiva que reordena
os objetos Shape pelo tipo (por exemplo, mudando a ordem para que os objetos Square
sejam desenhados primeiro).
O nico item que no fechado em relao ordem dos vrios objetos Shape a tabela
em si. E essa tabela pode ser colocada em seu prprio mdulo, separado de todos os outros
mdulos, de forma que as alteraes feitas nele no afetem os outros mdulos.

Listagem 9-6
Mecanismo de ordenao de tipos baseado em tabela
/// <summary>
/// Este comparador pesquisar o tipo de uma figura
/// na tabela de hashing de prioridades. A tabela de
/// prioridades define a ordem das figuras. As figuras
/// que no so encontradas precedem as que so.
/// </summary>
public class ShapeComparer: IComparer
{
private static Hashtable priorities = new Hashtable();
static ShapeComparer()
{
priorities.Add(typeof(Circle), 1);
priorities.Add(typeof(Square), 2);
}
private int PriorityFor(Type type)
{
if(priorities.Contains(type))
return (int)priorities[type];
else

possvel resolver esse problema usando o padro ACYCLIC VISITOR, descrito no Captulo 35. Mostrar essa
soluo agora seria precipitado; mais adiante, vou lembr-lo de voltar para c.

152

PROJETO GIL

return 0;
}
public int Compare(object o1, object o2)
{
int priority1 = PriorityFor(o1.GetType());
int priority2 = PriorityFor(o2.GetType());
return priority1.CompareTo(priority2);
}
}
public void DrawAllShapes(ArrayList shapes)
{
shapes.Sort(new ShapeComparer());
foreach(Shape shape in shapes)
shape.Draw();
}

Concluso
De muitas maneiras, o Princpio do Aberto/Fechado est no centro do projeto orientado
a objetos. A obedincia a esse princpio responsvel pelos maiores benefcios oriundos
da tecnologia orientada a objetos: flexibilidade, capacidade de reutilizao e facilidade
de manuteno. Apesar disso, a obedincia a esse princpio no obtida simplesmente
usando-se uma linguagem de programao orientada a objetos. Tambm no recomendvel aplicar abstrao desenfreada em todas as partes do aplicativo. necessrio dedicao dos desenvolvedores para aplicar abstrao somente nas partes do programa que
exibem mudanas frequentes. Resistir abstrao precipitada to importante quanto
a abstrao em si.

Bibliografia
[Jacobson92] Ivar Jacobson, Patrick Johnsson, Magnus Christerson e Gunnar vergaard, Object-Oriented Software Engineering: A Use Case Driven Approach, Addison-Wesley, 1992.
[Meyer97] Bertrand Meyer, Object Oriented Software Construction, 2d ed., Prentice Hall,
1997.

Captulo 10

PRINCPIO DA SUBSTITUIO
DE LISKOV (LSP)

s principais mecanismos por trs do Princpio do Aberto/Fechado so a abstrao e


o polimorfismo. Em linguagens estaticamente tipadas, como C#, um dos principais
mecanismos que suportam abstrao e polimorfismo a herana. Com o uso da herana,
criamos classes derivadas que implementam mtodos abstratos de classes base.
Quais so as regras de projeto que governam esse uso especfico da herana? Quais
so as caractersticas das melhores hierarquias de herana? Quais so as armadilhas que
nos faro criar hierarquias que no obedecem ao OCP? Essas perguntas so respondidas
pelo Princpio da Substituio de Liskov (LSP Liskov Substitution Principle).

Princpio da Substituio de Liskov


Os subtipos devem ser substituveis pelos seus tipos de base.

Barbara Liskov escreveu este princpio em 19881:


O que se deseja aqui algo como a seguinte propriedade de substituio: se para cada
objeto o1 do tipo S existe um objeto o2 do tipo T, tal que, para todos os programas P definidos em termos de T, o comportamento de P fica inalterado quando o1 substitudo por
o2, ento S um subtipo de T.

[Liskov88]

154

PROJETO GIL

A importncia desse princpio se torna evidente quando voc considera as consequncias de sua violao. Suponha que tenhamos uma funo f que recebe como argumento uma referncia para alguma classe base B. Suponha tambm que, quando passada
para f disfarada de B, alguma classe derivada D de B faz f se comportar incorretamente.
Ento, D viola o LSP. Claramente, D frgil na presena de f.
Os autores de f ficaro tentados a submeter D a algum tipo de teste, de modo que f
possa se comportar corretamente quando um D passado a ela. Esse teste viola o OCP
porque, agora, f no fechada para todas as diversas derivadas de B. Tais testes so um
mau cheiro de cdigo provocado por desenvolvedores inexperientes ou, o que pior, por
desenvolvedores apressados para reagir s violaes do LSP.

Listagem 10-1
Uma violao do LSP causando uma violao do OCP
struct Point {double x, y;}
public enum ShapeType {square, circle};
public class Shape
{
private ShapeType type;
public Shape(ShapeType t){type = t;}
public static void DrawShape(Shape s)
{
if(s.type == ShapeType.square)
(s as Square).Draw();
else if(s.type == ShapeType.circle)
(s as Circle).Draw();
}
}
public class Circle: Shape
{
private Point center;
private double radius;
public Circle(): base(ShapeType.circle) {}
public void Draw() {/* desenha o crculo */}
}
public class Square: Shape
{
private Point topLeft;
private double side;
public Square(): base(ShapeType.square) {}
public void Draw() {/* desenha o quadrado */}
}

PRINCPIO DA SUBSTITUIO DE LISKOV (LSP)

155

Violaes do LSP
Um exemplo simples
Violar o LSP frequentemente resulta no uso de verificao de tipo em tempo de execuo de uma maneira que viola inteiramente o OCP. Muitas vezes, uma instruo if
ou um encadeamento if/else explcito usado para determinar o tipo de um objeto,
para que o comportamento apropriado a esse tipo possa ser selecionado. Considere a
Listagem 10-1.
Claramente, a funo DrawShape da Listagem 10-1 viola o OCP. Ela precisa conhecer
cada derivada possvel da classe Shape e deve ser alterada sempre que novas classes derivadas de Shape forem criadas. Alis, muitos consideram, justificadamente, a estrutura
dessa funo como um antema do bom projeto. O que levaria um programador a escrever uma funo como essa?
Considere Joe, o engenheiro. Joe estudou tecnologia orientada a objetos e concluiu
que a sobrecarga de trabalho resultante do polimorfismo alta demais para valer a pena.2
Portanto, ele definiu a classe Shape sem qualquer funo abstrata. As classes Square e
Circle derivam de Shape e tm funes Draw(), mas no sobrescrevem uma funo de
Shape. Como Circle e Square no podem ser substitudas por Shape, DrawShape deve
inspecionar o objeto Shape recebido, determinar seu tipo e, depois, chamar a funo Draw
adequada.
O fato de Square e Circle no poderem ser substitudas por Shape uma violao
do LSP. Essa violao forou a violao do OCP por parte de DrawShape. Assim, uma violao do LSP uma violao latente do OCP.

Uma violao mais sutil


claro que existem outras maneiras bem mais sutis de violar o LSP. Considere um aplicativo que usa a classe Rectangle, como descrito na Listagem 10-2.
Imagine que esse aplicativo funcione bem e esteja instalado em muitos locais. Assim
como acontece com todo software bem-sucedido, seus usurios exigem mudanas de tempos em tempos. Um dia, os usurios pedem a capacidade de manipular quadrados, alm
de retngulos.
dito frequentemente que herana o relacionamento -Um. Em outras palavras, diz-se que, se um novo tipo de objeto pode satisfazer o relacionamento -Um com
um tipo de objeto antigo, a classe do novo objeto deve ser derivada da classe do objeto
antigo.
Para todos os fins prticos normais, um quadrado um retngulo. Assim, lgico
considerar a classe Square (quadrado) como derivada da classe Rectangle (retngulo).
(Consulte a Figura 10-1.)

Em uma mquina razoavelmente rpida, essa sobrecarga da ordem de 1ns por chamada de mtodo;
portanto, difcil entender o argumento de Joe.

156

PROJETO GIL

Listagem 10-2
Classe Rectangle
public class Rectangle
{
private Point topLeft;
private double width;
private double height;
public double Width
{
get { return width; }
set { width = value; }
}
public double Height
{
get { return height; }
set { height = value; }
}
}

s vezes, esse uso do relacionamento -Um considerado uma das tcnicas fundamentais da anlise orientada a objetos, um termo muito utilizado, mas raramente
definido. Um quadrado um retngulo e, assim, a classe Square deve ser derivada da
classe Rectangle. Esse tipo de pensamento pode levar a alguns problemas sutis, porm
significativos. Geralmente, esses problemas no so antecipados, at que apaream no
cdigo.
Nossa primeira pista de que algo deu errado pode ser o fato de que um objeto Square
no precisa das variveis membro height e width (altura e largura). Ainda assim, ele as
herdar de Rectangle. Claramente, isso um desperdcio. Em muitos casos, tal desperdcio insignificante. Mas, se precisarmos criar centenas de milhares de objetos Square
como em um programa de CAD/CAE, no qual cada pino de cada componente de um circuito
complexo desenhado como um quadrado , esse desperdcio poder ser significativo.
Vamos supor, por enquanto, que no estamos muito preocupados com eficincia de
memria. Outros problemas resultam de derivar Square de Rectangle. Square herdar

Rectangle

Square

Figura 10-1
Square herda de Rectangle.

PRINCPIO DA SUBSTITUIO DE LISKOV (LSP)

157

as propriedades Width e Height. Essas propriedades so inadequadas para um objeto


Square, pois a largura e a altura de um quadrado so idnticas. Esse um forte indcio
de que h um problema. No entanto, existe uma maneira de esquivar-se dele. Poderamos
sobrescrever Width e Height, como segue:
public new double Width
{
set
{
base.Width = value;
base.Height = value;
}
}
public new double Height
{
set
{
base.Height = value;
base.Width = value;
}
}

Agora, quando algum definir a largura de um objeto Square, sua altura mudar
de modo correspondente. E quando algum definir a altura, sua largura mudar com
ela. Assim, as invariantes aquelas propriedades que sempre devem ser verdadeiras,
independentemente do estado do objeto Square permanecem intactas. O objeto Square
continuar sendo um quadrado matematicamente correto:
Square s = new Square();
s.SetWidth(1); // Felizmente tambm define a altura como 1.
s.SetHeight(2); // Define a largura e a altura como 2.
// Coisa boa.

Mas considere a seguinte funo:


void f(Rectangle r)
{
r.SetWidth(32); // chama Rectangle.SetWidth
}

Se passarmos uma referncia para um objeto Square nessa funo, o objeto


Square ser corrompido, pois a altura no mudar. Essa uma clara violao do
LSP. A funo f no serve para derivadas de seus argumentos. O motivo da falha que
Width e Height no foram declaradas como virtual em Rectangle e, portanto, no
so polimrficas.
Podemos corrigir isso facilmente, declarando as propriedades do mtodo set
como virtual. Contudo, quando a criao de uma classe derivada nos obriga a fazer
mudanas na classe base, isso frequentemente significa que o projeto imperfeito.
Certamente, ele viola o OCP. Poderamos reagir a isso dizendo que esquecer de tornar
Width e Height virtuais foi a falha de projeto real e que agora estamos simplesmente
corrigindo esse erro. Contudo, isso difcil de justificar, pois definir a altura e a largu-

158

PROJETO GIL

ra de um retngulo uma operao bastante elementar. Por meio de qual raciocnio as


tornaramos virtuais, se no antecipamos a existncia de Square?
Entretanto, vamos supor que aceitemos o argumento e corrijamos as classes. Teramos o cdigo da Listagem 10-3.

Listagem 10-3
Rectangle e Square coerentes
public class Rectangle
{
private Point topLeft;
private double width;
private double height;
public virtual double Width
{
get { return width; }
set { width = value; }
}
public virtual double Height
{
get { return height; }
set { height = value; }
}
}
public class Square: Rectangle
{
public override double Width
{
set
{
base.Width = value;
base.Height = value;
}
}
public override double Height
{
set
{
base.Height = value;
base.Width = value;
}
}
}

PRINCPIO DA SUBSTITUIO DE LISKOV (LSP)

159

O problema real Agora, Square e Rectangle parecem funcionar. Independentemente


do que voc faa com um objeto Square, ele permanecer coerente com um quadrado
matemtico. E independentemente do que voc faa com um objeto Rectangle, ele continuar sendo um retngulo matemtico. Alm disso, voc pode passar um objeto Square
para uma funo que aceite um objeto Rectangle e o objeto Square ainda atuar como
um quadrado e permanecer coerente.
Assim, poderamos concluir que o projeto agora coerente e correto. Contudo, essa
concluso estaria errada. Um projeto coerente no o necessariamente para todos os seus
usurios! Considere a funo g:
void g(Rectangle r)
{
r.Width = 5;
r.Height = 4;
if(r.Area()!= 20)
throw new Exception("rea incorreta!");
}

Essa funo chama os membros Width e Height do que acredita ser um objeto
Rectangle. Ela funciona muito bem para um objeto Rectangle, mas lana uma exceo
(Exception) se receber um objeto Square. Portanto, aqui est o verdadeiro problema: o
autor de g sups que alterar a largura de um objeto Rectangle deixa sua altura inalterada.
Claramente, razovel supor que mudar a largura de um retngulo no afeta sua
altura! Contudo, nem todos os objetos que podem ser passados como Rectangle satisfazem essa suposio. Se voc passar uma instncia de um objeto Square para uma funo
como g, cujo autor fez essa suposio, ela no funcionar. A funo g frgil com relao
hierarquia Square/Rectangle.
A funo g mostra que existem funes que recebem objetos Rectangle, mas que
no podem operar corretamente com objetos Square. Como para essas funes Square
no substituvel por Rectangle, o relacionamento entre Square e Rectangle viola o
LSP.
Algum poderia afirmar que o problema reside na funo g, que o autor no tinha
o direito de fazer a suposio de que a largura e a altura eram independentes. O autor
de g discordaria. A funo g recebe um objeto Rectagle como argumento. Existem invariantes, declaraes de verdade, que obviamente se aplicam a uma classe chamada
Rectangle, e uma dessas invariantes que altura e largura so independentes. O autor
de g tinha todo o direito de declarar essa invariante. Foi o autor de Square que violou a
invariante.
Curiosamente, o autor de Square no violou uma invariante de Square. Derivando
Square de Rectangle, o autor de Square violou uma invariante de Rectangle!
A validade no intrnseca
O Princpio da Substituio de Liskov nos leva a uma concluso muito importante: um modelo visto isoladamente no pode ser validado de forma
significativa. A validade de um modelo s pode ser expressa em termos de seus clientes.
Por exemplo, quando examinamos a verso final das classes Square e Rectangle isoladamente, descobrimos que elas eram coerentes e vlidas. Apesar disso, quando as examinamos do ponto de vista de um programador que fez suposies razoveis a respeito da
classe base, o modelo falhou.

160

PROJETO GIL

Ao considerar se um projeto especfico adequado, voc no pode simplesmente ver


a soluo de maneira isolada. preciso consider-la em termos das suposies razoveis
feitas pelos usurios desse projeto.3
Quem sabe quais suposies razoveis os usurios de um projeto vo fazer? A maioria dessas suposies no fcil prever. Alis, se tentssemos prever todas elas, provavelmente acabaramos impregnando nosso sistema com o mau cheiro da complexidade
desnecessria. Portanto, assim como acontece com todos os outros princpios, frequentemente melhor adiar todas as violaes do LSP (menos as mais evidentes) at que se
tenha sentido o mau cheiro da fragilidade relacionada.
-Um est relacionado ao comportamento
Ento, o que aconteceu? Por que o modelo
aparentemente razovel de Square e Rectangle deu errado? Afinal, um objeto Square
no um objeto Rectangle? O relacionamento -Um no vale?
No, no que diz respeito ao autor de g! Um quadrado poderia ser um retngulo, mas
do ponto de vista de g, um objeto Square definitivamente no um objeto Rectangle.
Por qu? Porque o comportamento de um objeto Square no compatvel com a expectativa de g quanto ao comportamento de um objeto Rectangle. Quanto ao comportamento,
um objeto Square no um objeto Rectangle e, na realidade, o software est relacionado
ao comportamento. O LSP torna claro que, no projeto orientado a objetos, o relacionamento -Um pertence ao comportamento que pode ser razoavelmente suposto e do qual
os clientes dependem.
Projeto por contrato
Muitos desenvolvedores podem se sentir pouco vontade com a
ideia de um comportamento razoavelmente suposto. Como voc sabe o que seus clientes
realmente esperaro? Existe uma tcnica para tornar essas suposies razoveis explcitas e, com isso, forar o LSP. Tal tcnica denominada projeto por contrato (DBC Design
by Contract) e explicada por Bertrand Meyer.4
Usando DBC, o autor de uma classe declara o contrato dessa classe explicitamente.
O contrato informa ao autor de qualquer cdigo cliente sobre os comportamentos com os
quais pode contar. O contrato especificado pela declarao de pr-condies e ps-condies para cada mtodo. As pr-condies devem ser verdadeiras para que o mtodo
execute. Ao ser concludo, o mtodo garante que as ps-condies so verdadeiras.
Podemos ver a ps-condio do mtodo set Rectangle.Width como segue:
assert((width == w) && (height == old.height));

onde old o valor de Rectangle antes que Width seja chamado. Agora a regra das pr-condies e ps-condies das classes derivadas, conforme declarada por Meyer, : A nova declarao de uma rotina [em uma classe derivada] s pode substituir a pr-condio original
por outra igual ou mais fraca, e a ps-condio original, por uma igual ou mais forte.5
Em outras palavras, ao utilizar um objeto por meio da interface de sua classe base,
o usurio s conhece as pr-condies e ps-condies da classe base. Assim, os objetos
derivados no devem esperar que esses usurios obedeam pr-condies mais fortes do
que as exigidas pela classe base. Ou seja, os usurios devem aceitar tudo que a classe base

Frequentemente, voc descobrir que essas suposies razoveis so declaradas nos testes de unidade
escritos para a classe base. Esse mais um bom motivo para praticar o desenvolvimento guiado por testes.
4
[Meyer97], p. 331
5
[Meyer97], p. 573

PRINCPIO DA SUBSTITUIO DE LISKOV (LSP)

161

possa aceitar. Alm disso, as classes derivadas devem obedecer a todas as ps-condies
da classe base. Isto , seus comportamentos e sadas no devem violar as restries estabelecidas para a classe base. Os usurios da classe base no devem ser confundidos pela
sada da classe derivada.
Claramente, a ps-condio do mtodo set Square.Width mais fraca6 do que a
ps-condio do mtodo set Rectangle.Width, pois no impe a restrio (height ==
old.height). Assim, a propriedade Width de Square viola o contrato da classe base.
Certas linguagens, como a Eiffel, tm suporte direto para pr-condies e ps-condies. Voc pode declar-las e fazer o sistema de runtime as verificar. A linguagem C# no tem
esse recurso. Em C#, devemos considerar manualmente as pr-condies e ps-condies de
cada mtodo e garantir que a regra de Meyer no seja violada. Alm disso, pode ser muito til
documentar essas pr-condies e ps-condies nos comentrios de cada mtodo.
Especificando contratos em testes de unidade Os contratos tambm podem ser especificados escrevendo-se testes de unidade. Testando completamente o comportamento de
uma classe, os testes de unidade tornam claro o comportamento da classe. Os autores de
cdigo cliente desejaro examinar os testes de unidade para saber o que supor razoavelmente em relao s classes que esto usando.

Um exemplo real
Chega de quadrados e retngulos! O LSP diz respeito a software real? Vamos ver um estudo
de caso proveniente de um projeto em que trabalhei h alguns anos.
Motivao No incio dos anos 1990, adquiri uma biblioteca de classes que continha algumas classes contineres.7 As classes contineres eram aproximadamente relacionadas
s classes Bag e Set de Smalltalk. Havia duas variedades de Set e duas variedades semelhantes de Bag. A primeira variedade se chamava bounded e era baseada em um array. A
segunda se chamava unbounded e era baseada em uma lista encadeada.
O construtor de BoundedSet especificava o nmero mximo de elementos que o conjunto poderia conter. O espao para esses elementos era alocado previamente como um
array dentro de BoundedSet. Assim, se a criao de BoundedSet fosse bem-sucedida, podamos garantir que teria memria suficiente. Como ela tinha por base um array, era muito
rpida. No havia alocaes de memria durante a operao normal. E, como a memria
era alocada previamente, podamos ter certeza de que a operao de BoundedSet no
esgotaria o heap. Por outro lado, isso era um desperdcio de memria, pois poucas vezes
utilizava todo o espao previamente alocado.
Diferentemente, UnboundedSet no tinha um limite declarado para o nmero de
elementos que podia conter. Desde que houvesse memria para o heap, UnboundedSet
continuaria a aceitar elementos. Portanto, era muito flexvel. Alm disso, era econmica,
pois utilizava apenas a memria necessria para conter os elementos que continha no momento. Tambm era lenta, pois precisava alocar e desalocar memria como parte de sua
operao normal. Por fim, um perigo era que sua operao normal podia esgotar o heap.
Eu no estava satisfeito com as interfaces dessas classes. No queria que o cdigo
de meu aplicativo dependesse delas, pois achava que iria querer substitu-las por classes
6

O termo mais fraca pode confundir. X mais fraca do que Y se X no impe todas as restries de Y. No
importa quantas restries novas X imponha.
7
A linguagem era C++, muito antes da biblioteca de contineres padro estar disponvel.

162

PROJETO GIL

interface
Set

Unbounded Set

Bounded
Set

Unbounded Set
de terceiros

Bounded Set
de terceiros

Figura 10-2
Camada adaptadora da classe continer.
melhores posteriormente. Assim, envolvi essas classes contineres em minha prpria
interface abstrata, como mostrado na Figura 10-2.
Criei uma interface, chamada Set, que apresentava as funes abstratas Add, Delete e IsMember, como mostrado na Listagem 10-4.8 Essa estrutura unificou as variedades
unbounded e bounded dos dois conjuntos e permitiu que elas fossem acessadas com
uma interface comum. Assim, algum cliente poderia aceitar um argumento de tipo Set
e no se preocupar se o tipo Set real em que funcionava fosse da variedade bounded ou
unbounded. (Veja a funo PrintSet na Listagem 10-5.)
uma grande vantagem no ter de saber ou se preocupar com o tipo de Set que voc
est usando. Isso significa que o programador pode decidir qual tipo de Set necessrio
em cada instncia especfica e que nenhuma das funes clientes ser afetada por essa
deciso. O programador pode escolher um tipo UnboundedSet quando houver pouca memria e a velocidade no for fundamental ou pode escolher um tipo BoundedSet quando
houver bastante memria e a velocidade for importante. As funes clientes manipularo
esses objetos por meio da interface da classe base Set e, portanto, no sabero nem se
preocuparo com o tipo de Set que esto usando.

Listagem 10-4
Classe Set abstrata
public interface Set
{
public void Add(object o);
public void Delete(object o);
public bool IsMember(object o);
}

O cdigo original foi transformado em C# aqui, para tornar mais fcil para os programadores .NET
entenderem.

PRINCPIO DA SUBSTITUIO DE LISKOV (LSP)

163

Listagem 10-5
PrintSet
void PrintSet(Set s)
{
foreach(object o in s)
Console.WriteLine(o.ToString());
}

Problema
Eu queria adicionar um objeto PersistentSet nessa hierarquia. Um conjunto persistente (PersistentSet) pode ser armazenado em um fluxo (stream) e lido de
volta posteriormente, possivelmente por outro aplicativo. No entanto, o nico continer
a que eu tinha acesso e que tambm oferecia persistncia no era aceitvel. Ele aceitava
objetos derivados da classe base abstrata PersistentObject. Eu criei a hierarquia mostrada na Figura 10-3.
Note que PersistentSet contm uma instncia do conjunto persistente para a qual
delega todos os seus mtodos. Assim, se voc chama Add no objeto PersistentSet, ele
simplesmente delega isso ao mtodo apropriado do conjunto persistente contido.
Superficialmente, tudo parecia correto. Contudo, existe uma implicao bastante
ameaadora. Os elementos adicionados ao conjunto persistente devem ser derivados de
PersistentObject. Como PersistentSet simplesmente delega para o conjunto persistente, qualquer elemento adicionado a PersistentSet deve derivar de PersistentObject. Apesar disso, a interface de Set no tem tal restrio.
Quando um cliente est adicionando membros classe base Set, esse cliente no
pode ter certeza se Set poder ser um objeto PersistentSet. Assim, o cliente no tem
como saber se os elementos que adiciona devem ser derivados de PersistentObject.
Considere o cdigo de PersistentSet.Add() na Listagem 10-6. Esse cdigo torna
claro que, se qualquer cliente tentar adicionar a meu PersistentSet um objeto que no
seja derivado da classe PersistentObject, resultar em um erro de tempo de execuo.
A coero (cast) lanar uma exceo. Nenhum dos clientes existentes da classe base abstrata Set espera que excees sejam lanadas em Add. Como essas funes sero confundidas por uma classe derivada de Set, essa mudana na hierarquia viola o LSP.

interface
Set

PersistentSet

Persistent
Object

Persistent Set
de terceiros

Figura 10-3
Hierarquia PersistentSet.

164

PROJETO GIL

Listagem 10-6
Mtodo Add em PersistentSet
void Add(object o)
{
PersistentObject p = (PersistentObject)o;
thirdPartyPersistentSet.Add(p); // PersistentSet de terceiros
}

Isso um problema? Certamente. Funes que nunca falharam antes quando passadas a uma classe derivada de Set, agora podem causar erros de tempo de execuo quando passadas para um PersistentSet. Depurar esse tipo de problema relativamente
difcil, pois o erro de tempo de execuo ocorre muito longe do erro de lgica. O erro de
lgica a deciso de passar um PersistentSet para uma funo ou adicionar um objeto
em PersistentSet que no derivado de PersistentObject. Em um ou outro caso,
a deciso poderia estar a milhes de instrues da chamada do mtodo Add. Encontr-la
seria difcil. Corrigi-la seria pior.
Uma soluo que no obedece ao LSP
Como resolvemos esse problema? H vrios
anos, eu o resolvi por conveno, o que significa que no o resolvi no cdigo-fonte. Em vez
disso, estabeleci uma conveno segundo a qual PersistentSet e PersistentObject
ficavam ocultos do aplicativo. Eles eram conhecidos apenas por um mdulo especfico.
Esse mdulo era responsvel por ler e escrever todos os contineres no repositrio persistente. Quando um continer precisava ser escrito, seu contedo era copiado
nas classes derivadas adequadas de PersistentObject e depois adicionado aos objetos
PersistentSet, os quais eram ento salvos em um fluxo (stream). Quando um continer
precisava ser lido de um fluxo, o processo era invertido. Um objeto PersistentSet era
lido do fluxo e depois os objetos PersistentObject eram removidos do PersistentSet
e copiados nos objetos no persistentes normais, os quais eram ento adicionados a um
conjunto Set normal.
Essa soluo pode parecer muito restritiva, mas foi a nica maneira que encontrei
de impedir que objetos PersistentSet aparecessem na interface de funes que queriam
adicionar objetos no persistentes a elas. Alm do mais, isso eliminou a dependncia do
restante do aplicativo em relao ideia de persistncia.
Essa soluo funcionou? Na verdade, no. A conveno era violada em vrias partes do aplicativo, por desenvolvedores que no entendiam sua necessidade. Esse o
problema das convenes: elas precisam ser continuamente revendidas para cada desenvolvedor. Se o desenvolvedor no tiver sido informado da conveno ou se no concordar com ela, a conveno ser violada. Alm disso, uma violao pode comprometer
a estrutura inteira.
Uma soluo compatvel com o LSP
Como eu resolveria isso agora? Eu reconheceria
que uma PersistentSet no tem um relacionamento -Um com Set, que no uma derivada correta de Set. Assim, eu separaria as hierarquias, mas no completamente. Set e
PersistentSet tm caractersticas em comum. Na verdade, apenas o mtodo Add causa
a dificuldade com o LSP. Dessa forma, eu criaria uma hierarquia na qual tanto Set como
PersistentSet fossem irms, sob uma interface que permitisse teste de participao,
iterao etc. (Consulte a Figura 10-4.) Isso permitiria que objetos PersistentSet fossem

PRINCPIO DA SUBSTITUIO DE LISKOV (LSP)

165

interface
Member
Container
Persistent
Object

Remove(T)
IsIn(T)

interface
Set

PersistentSet

Add(T)

Persistent Set
de terceiros

Add(T)

Figura 10-4
Uma soluo compatvel com o LSP.
iterados e testados quanto participao etc., mas no proporcionaria a capacidade de
adicionar em a PersistentSet objetos que no fossem derivados de PersistentObject.

Fatorar em vez de derivar


Outro caso de herana interessante e enigmtico o de Line e LineSegment.9 Considere
as listagens 10-7 e 10-8. Inicialmente, essas duas classes parecem ser candidatas naturais
para herana. LineSegment precisa de cada varivel membro e de cada funo membro declarada em Line. Alm disso, LineSegment adiciona sua prpria funo membro,
Length, e sobrescreve o significado da funo IsOn. Apesar disso, essas duas classes
violam o LSP de maneira sutil.

Listagem 10-7
Line.cs
public class Line
{
private Point p1;
private Point p2;
public Line(Point p1, Point p2){this.p1=p1; this.p2=p2;}
public Point P1 { get { return p1; } }
public Point P2 { get { return p2; } }
public double Slope { get {/*cdigo*/} }
public double YIntercept { get {/*cdigo*/} }
public virtual bool IsOn(Point p) {/*cdigo*/}
}

Apesar da semelhana deste exemplo com o de Square/Rectangle, ele procede de um aplicativo real e
estava sujeito aos problemas reais discutidos.

166

PROJETO GIL

Listagem 10-8
LineSegment.cs
public class LineSegment: Line
{
public LineSegment(Point p1, Point p2): base(p1, p2) {}
public double Length() { get {/*cdigo*/} }
public override bool IsOn(Point p) {/*cdigo*/}
}

Um usurio de Line tem o direito de esperar que todos os pontos (points) colineares
estejam nela. Por exemplo, o ponto retornado pela propriedade YIntercept aquele no
qual a linha cruza o eixo Y. Como esse ponto colinear em relao linha, os usurios
de Line tm o direito de esperar que IsOn(YIntercept) == true. Contudo, em muitas
instncias de LineSegment essa declarao falhar.
Por que esse problema importante? Por que no derivar LineSegment de Line e
conviver com os problemas sutis? Essa uma boa oportunidade para usar o discernimento. Existem raras ocasies em que melhor aceitar uma falha sutil em um comportamento polimrfico do que tentar manipular o projeto para obter completa compatibilidade
com o LSP. Aceitar o compromisso, em vez de buscar a perfeio, uma deciso de engenharia. Um bom engenheiro sabe quando o compromisso mais lucrativo do que a perfeio. Contudo, a obedincia ao LSP no deve ser renunciada levianamente. A garantia de
que uma subclasse sempre funcionar onde suas classes base so usadas uma maneira
poderosa de gerenciar a complexidade. Uma vez que ela seja abandonada, devemos considerar cada subclasse individualmente.
No caso de Line e LineSegment, uma soluo simples ilustra uma ferramenta importante do projeto orientado a objetos. Se temos acesso s classes Line e LineSegment, podemos fatorar os elementos comuns de ambas em uma classe base abstrata. As listagens 10-9,
10-10 e 10-11 mostram a fatorao de Line e LineSegment na classe base LinearObject.
Representando tanto Line como LineSegment, LinearObject fornece a maior parte da funcionalidade e dos membros de dados de ambas as subclasses, com exceo do
mtodo IsOn, que abstrato. Os usurios de LinearObject no podem supor que compreendem a extenso do objeto que esto utilizando. Assim, eles podem aceitar um objeto
Line ou LineSegment sem problemas. Alm disso, os usurios de Line nunca precisaro
lidar com um objeto LineSegment.
A fatorao uma ferramenta poderosa. Se qualidades de duas subclasses podem
ser decompostas, existe a clara possibilidade de que posteriormente apaream outras
classes que tambm precisem dessas qualidades. Sobre a fatorao, Rebecca Wirfs-Brock,
Brian Wilkerson e Lauren Wiener comentam:
Podemos afirmar que, se todas as classes em um conjunto de classes aceitam uma responsabilidade comum, elas devem herdar essa responsabilidade de uma superclasse
comum.
Se ainda no existir uma superclasse comum, crie uma e mova as responsabilidades
comuns para ela. Afinal, tal classe comprovadamente til voc j mostrou que as
responsabilidades sero herdadas por algumas classes. No concebvel que uma

PRINCPIO DA SUBSTITUIO DE LISKOV (LSP)

167

ampliao posterior de seu sistema possa adicionar uma nova subclasse que aceite
essas mesmas responsabilidades de uma nova maneira? Essa nova superclasse provavelmente ser uma classe abstrata.10

Listagem 10-9
LinearObject.cs
public abstract class LinearObject
{
private Point p1;
private Point p2;
public LinearObject(Point p1, Point p2)
{this.p1=p1; this.p2=p2;}
public Point P1 { get { return p1; } }
public Point P2 { get { return p2; } }
public double Slope { get {/*cdigo*/} }
public double YIntercept { get {/*cdigo*/} }
public virtual bool IsOn(Point p) {/*cdigo*/}
}

Listagem 10-10
Line.cs
public class Line: LinearObject
{
public Line(Point p1, Point p2): base(p1, p2) {}
public override bool IsOn(Point p) {/*cdigo*/}
}

Listagem 10-11
LineSegment.cs
public class LineSegment: LinearObject
{
public LineSegment(Point p1, Point p2): base(p1, p2) {}
public double GetLength() {/*cdigo*/}
public override bool IsOn(Point p) {/*cdigo*/}
}

10

[Wirfs-Brock90], p. 113

168

PROJETO GIL

Listagem 10-12
Ray.cs
public class Ray: LinearObject
{
public Ray(Point p1, Point p2): base(p1, p2) {/*cdigo*/}
public override bool IsOn(Point p) {/*cdigo*/}
}

Listagem 10-13
Uma funo degenerada em uma derivada
public class Base
{
public virtual void f() {/*algum cdigo*/}
}
public class Derived: Base
{
public override void f() {}
}

A Listagem 10-12 mostra como os atributos de LinearObject podem ser usados por
uma classe no prevista: Ray. Um objeto Ray pode ser substitudo por um objeto LinearObject e nenhum usurio de LinearObject teria qualquer problema para lidar com ele.

Heursticas e convenes
Algumas heursticas simples podem fornecer indcios sobre violaes do LSP. Todas essas
heursticas so relacionadas s classes derivadas que de algum modo removem funcionalidade de duas classes base. Uma derivada que faz menos do que sua base normalmente
no substituvel por essa base e, portanto, viola o LSP.
Considere a Figura 10-13. A funo f em Base implementada, mas em Derived
degenerada. Aparentemente, o autor de Derived descobriu que a funo f no tinha utilidade em um objeto Derived. Infelizmente, os usurios de Base no sabem que no devem
chamar f e, assim, existe uma violao de substituio.
A presena de funes degeneradas em classes derivadas nem sempre indica uma
violao do LSP, mas interessante examin-las quando ocorrerem.

PRINCPIO DA SUBSTITUIO DE LISKOV (LSP)

169

Concluso
O Princpio do Aberto/Fechado um elemento crucial do projeto orientado a objetos.
Quando esse princpio usado, os aplicativos so mais passveis de manuteno, reutilizveis e robustos. O Princpio da Substituio de Liskov um dos principais capacitadores do OCP. A possibilidade de substituio de subtipos permite que um mdulo, expresso em termos de um tipo base, seja extensvel sem modificao. Essa possibilidade de
substituio deve ser algo de que os desenvolvedores dependam implicitamente. Assim,
o contrato do tipo base precisa ser bem compreendido, se no explicitamente imposto,
pelo cdigo.
O termo -Um amplo demais para ser usado como definio de um subtipo. A verdadeira definio de subtipo substituvel, onde a possibilidade de substituio definida
por um contrato explcito ou implcito.

Bibliografia
[Liskov88] Data Abstraction and Hierarchy, Barbara Liskov, SIGPLAN Notices, 23(5)
(May 1988).
[Meyer97] Bertrand Meyer, Object-Oriented Software Construction, 2d ed., Prentice Hall,
1997.
[Wirfs-Brock90] Rebecca Wirfs-Brock et al., Designing Object-Oriented Software, Prentice Hall, 1990.

Captulo 11

PRINCPIO DA INVERSO DE
DEPENDNCIA (DIP)
Nunca mais
Deixe os principais interesses do estado dependerem
Dos milhares de acasos que podem influenciar
Um elemento da fraqueza humana
Sir Thomas Noon Talfourd (1795-1854)

Princpio da Inverso de Dependncia (DIP Dependency-Inversion Principle)


A.

Mdulos de alto nvel no devem depender de mdulos de baixo nvel. Ambos


devem depender de abstraes.

B.

As abstraes no devem depender de detalhes. Os detalhes devem depender


das abstraes.

om o passar dos anos, muitos tm me questionado por que utilizo a palavra inverso no nome desse princpio. O motivo que os mtodos de desenvolvimento de
software mais tradicionais, como o projeto e anlise estruturados, tendem a criar estruturas de software nas quais os mdulos de alto nvel dependem de mdulos de baixo nvel e
nas quais a diretiva (policy) depende do detalhe. Alis, um dos objetivos desses mtodos
definir a hierarquia de subprogramas que descreve como os mdulos de alto nvel chamam os mdulos de baixo nvel. O projeto inicial do programa Copy da Figura 7-1 um
bom exemplo de tal hierarquia. A estrutura de dependncia de um programa orientado a
objetos bem projetado invertida com relao estrutura de dependncia que normalmente resulta dos mtodos procedurais tradicionais.
Considere as implicaes dos mdulos de alto nvel que dependem de mdulos de
baixo nvel. So os mdulos de alto nvel que contm as decises de diretiva importantes
e os modelos de negcio de um aplicativo. Esses mdulos contm a identidade do aplicativo. Apesar disso, quando esses mdulos dependem de mdulos de nvel mais baixo, alteraes nestes ltimos podem ter efeitos diretos nos mdulos de nvel mais alto e podem
obrig-los a mudar tambm.

172

PROJETO GIL

Essa situao absurda! So os mdulos que definem diretivas de alto nvel que devem influenciar os mdulos detalhados de baixo nvel. Os mdulos que contm as regras
de negcio de alto nvel devem ter precedncia e ser independentes dos mdulos que
contm os detalhes da implementao. Os mdulos de alto nvel simplesmente no devem
depender dos mdulos de baixo nvel.
Alm disso, so os mdulos que definem diretivas de alto nvel que queremos
reutilizar. J fazemos um bom trabalho na reutilizao de mdulos de baixo nvel na
forma de bibliotecas de sub-rotinas. Quando mdulos de alto nvel dependem de mdulos de baixo nvel, torna-se muito difcil reutiliz-los em diferentes contextos. Contudo, quando os mdulos de alto nvel so independentes dos mdulos de baixo nvel,
eles podem ser reutilizados com muita simplicidade. Esse princpio est no centro do
projeto de frameworks.

Disposio em camadas
De acordo com Booch, todas as arquiteturas orientadas a objetos bem estruturadas
tm camadas claramente definidas, com cada camada fornecendo algum conjunto coerente de servios por meio de uma interface bem definida e controlada.1 Uma interpretao simplista dessa declarao poderia levar um projetista a produzir uma estrutura
semelhante Figura 11-1. Nesse diagrama, a camada Policy de alto nvel utiliza uma
camada Mechanism de nvel inferior, a qual por sua vez utiliza uma camada Utility
de nvel detalhado. Embora isso possa parecer adequado, tem a caracterstica traioeira de que a camada Policy sensvel s alteraes feitas na camada inferior Utility.
A dependncia transitiva. A camada Policy depende de algo que depende da camada Utility; assim, a camada Policy depende transitivamente da camada Utility.
Isso muito inadequado.
A Figura 11-2 mostra um modelo mais adequado. Cada camada de nvel superior declara uma interface abstrata para os servios de que precisa. Ento, as camadas de nvel inferior
so concretizadas a partir dessas interfaces abstratas. Cada classe de nvel superior utiliza
a camada de nvel mais baixo seguinte, por meio da interface abstrata. Assim, as camadas
superiores no dependem das inferiores. Em vez disso, as camadas inferiores dependem de
interfaces de servio abstratas declaradas nas camadas superiores. No apenas a dependncia transitiva da camada Policy em relao camada Utility eliminada, como tambm a
dependncia direta da camada Policy em relao camada Mechanism.

Camada Policy

Camada
Mechanism

Camada Utility

Figura 11-1
Esquema de disposio em camadas simplista.
1

[Booch96], p. 54

PRINCPIO DA INVERSO DE DEPENDNCIA (DIP)

173

Inverso de posse
Note que a inverso aqui no apenas de dependncias, mas tambm de posse da interface. Frequentemente, pensamos nas bibliotecas de utilidades (utility) como tendo suas
prprias interfaces. Mas quando o DIP empregado, descobrimos que os clientes tendem
a possuir as interfaces abstratas e que seus servidores derivam delas.
s vezes isso conhecido como o princpio de Hollywood: No nos procure; ns o
procuraremos.2 Os mdulos de nvel inferior fornecem a implementao das interfaces
declaradas dentro dos mdulos de nvel superior e chamadas por eles.
Usando essa inverso de posse, a camada Policy no afetada pelas alteraes
feitas na camada Mechanism ou na camada Utility. Alm disso, a camada Policy pode
ser reutilizada em qualquer contexto que defina mdulos de nvel inferior que obedeam
interface PolicyServiceInterface. Assim, invertendo as dependncias, criamos uma
estrutura que simultaneamente mais flexvel, durvel e mvel.
Nesse contexto, posse significa simplesmente que as interfaces so distribudas com
os clientes que as possuem e no com os servidores que as implementam. A interface est
no mesmo pacote ou biblioteca que o cliente. Isso obriga a biblioteca ou pacote do servidor
a depender da biblioteca ou pacote do cliente.

Policy
interface
Camada Policy

Policy Service
Interface

Mechanism

Camada
Mechanism

interface
Mechanism
Service
Interface

Utilidades

Camada
Utility

Figura 11-2
Camadas invertidas.
2

[Sweet85]

174

PROJETO GIL

Evidentemente, existem ocasies em que no queremos que o servidor dependa do


cliente. Por exemplo, quando existem muitos clientes, mas apenas um servidor. Nesse
caso, os clientes devem concordar com a interface de servio e public-la em um pacote
separado.

Dependncia de abstraes
Uma interpretao do DIP um tanto mais simplista, apesar de ainda muito poderosa, a
heurstica depender de abstraes. Dito de forma simples, essa heurstica recomenda
que voc no deve depender de uma classe concreta, mas que todos os relacionamentos
em um programa devem terminar em uma classe ou interface abstrata.
Nenhuma varivel deve conter uma referncia para uma classe concreta.
Nenhuma classe deve derivar de uma classe concreta.
Nenhum mtodo deve sobrescrever um mtodo implementado de qualquer uma de
suas classes base.
Certamente, essa heurstica violada pelo menos uma vez em todo programa.
Algum precisa criar as instncias das classes concretas e qualquer mdulo que faa
isso depender delas.3 Alm disso, parece no haver motivo para seguir essa heurstica para classes que so concretas mas no volteis. Se uma classe concreta no vai
mudar muito e no sero criadas outras classes derivadas semelhantes, depender dela
causar pouco dano.
Por exemplo, na maioria dos sistemas, a classe que descreve uma string concreta.
Em C#, por exemplo, a classe concreta string. Essa classe no voltil. Isto , ela no
muda com muita frequncia. Portanto, no causa dano depender diretamente dela.
Contudo, a maioria das classes concretas que ns escrevemos como parte de um programa aplicativo voltil. So dessas classes concretas que no queremos depender diretamente. Sua volatilidade pode ser isolada mantendo-as atrs de uma interface abstrata.
Essa no uma soluo completa. Existem ocasies em que a interface de uma classe
voltil deve mudar, e essa mudana deve ser propagada para a interface abstrata que representa a classe. Tais alteraes foram o isolamento da interface abstrata.
Esse o motivo pelo qual a heurstica um pouco simplista. Se, por outro lado,
adotarmos a viso mais ampla de que os mdulos ou camadas clientes declaram as interfaces de servio de que necessitam, as interfaces mudaro somente quando o cliente
precisar da mudana. As alteraes feitas nas classes que implementam a interface
abstrata no afetaro o cliente.

Um exemplo simples de DIP


A inverso de dependncia pode ser aplicada sempre que uma classe envia uma mensagem para outra. Por exemplo, considere o caso do objeto Button e do objeto Lamp.

Na verdade, existem maneiras de contornar esse fato, se voc puder usar strings para criar classes. A linguagem C# e outras tantas permitem isso. Nessas linguagens, os nomes das classes concretas podem ser
passados para o programa como dados de configurao.

PRINCPIO DA INVERSO DE DEPENDNCIA (DIP)

Button
+ Poll()

175

Lamp
+ TurnOn()
+ TurnOff()

Figura 11-3
Modelo simplista de objetos Button e Lamp.
O objeto Button (boto) sente o ambiente externo. Ao receber a mensagem Poll,
o objeto Button determina se um usurio o pressionou. Seja qual for o mecanismo de
percepo um cone de boto em uma interface grfica do usurio, um boto fsico sendo
pressionado por um dedo humano ou mesmo um detector de movimento em um sistema
de segurana domiciliar , o objeto Button detecta que um usurio o ativou ou desativou.
O objeto Lamp (lmpada) afeta o ambiente externo. Ao receber uma mensagem TurnOn,
o objeto Lamp acende uma luz de algum tipo. Ao receber uma mensagem TurnOff, ele apaga
essa luz. O mecanismo fsico no tem importncia. Poderia ser um LED no console de um
computador, uma lmpada de vapor de mercrio em um estacionamento ou mesmo o laser
em uma impressora a laser.
Como podemos projetar um sistema de modo que o objeto Button controle o objeto
Lamp? A Figura 11-3 mostra um modelo simplista. O objeto Button recebe mensagens
Poll, determina se o boto foi pressionado e, depois, simplesmente envia a mensagem
TurnOn ou TurnOff para o objeto Lamp.
Por que isso simplista? Considere o cdigo em C# decorrente desse modelo (Listagem 11-1). Note que a classe Button depende diretamente da classe Lamp. Essa dependncia significa que Button ser afetada por alteraes em Lamp. Alm disso, no ser
possvel reutilizar Button para controlar um objeto Motor. Nesse modelo, os objetos
Button controlam objetos Lamp e somente objetos Lamp.
Essa soluo viola o DIP. A diretiva de alto nvel do aplicativo no foi separada da
implementao de baixo nvel. As abstraes no foram separadas dos detalhes. Sem tal
separao, a diretiva de alto nvel depende automaticamente dos mdulos de baixo nvel e
as abstraes dependem automaticamente dos detalhes.

Listagem 11-1
Button.cs
public class Button
{
private Lamp lamp;
public void Poll()
{
if (/*alguma condio*/)
lamp.TurnOn();
}
}

176

PROJETO GIL

Encontrando a abstrao subjacente


Qual a diretiva de alto nvel? a abstrao subjacente ao aplicativo, as verdades que no
variam quando os detalhes so alterados. o sistema dentro do sistema a metfora.
No exemplo Button/Lamp, a abstrao subjacente detectar um gesto de ligar/desligar de
um usurio e transmitir esse gesto para um objeto de destino. Que mecanismo usado
para detectar o gesto do usurio? No importa! Qual o objeto de destino? irrelevante!
Esses so detalhes que no afetam a abstrao.
O modelo da Figura 11-3 pode ser melhorado invertendo-se a dependncia do objeto
Lamp. Na Figura 11-4, vemos que agora o objeto Button contm uma associao com algo
chamado ButtonServer, que fornece as interfaces que Button pode usar para ligar ou
desligar algo. Lamp implementa a interface ButtonServer. Assim, Lamp est agora causando a dependncia, em vez de ser dependente.

Button
+ poll()

interface
ButtonServer
+turnOff()
+turnOn()

Lamp

Figura 11-4
Inverso de dependncia aplicada a Lamp.

O projeto da Figura 11-4 permite que um objeto Button controle qualquer dispositivo que queira implementar a interface ButtonServer. Isso nos proporciona muita flexibilidade e tambm significa que os objetos Button podero controlar objetos que ainda
no foram inventados.
Contudo, essa soluo tambm impe uma restrio para qualquer objeto que precise
ser controlado por um objeto Button. Tal objeto deve implementar a interface ButtonServer. Isso inadequado, pois esses objetos tambm podem querer ser controlados por
um objeto Switch (comutador ou chave) ou algum tipo de objeto que no seja Button.
Invertendo a direo da dependncia e fazendo o objeto Lamp impor a dependncia em vez de ser dependente, tornamos Lamp dependente de um detalhe diferente:
Button. Ou no?
Lamp certamente depende de ButtonServer, mas ButtonServer no depende
de Button. Qualquer tipo de objeto que saiba como manipular a interface ButtonServer poder controlar um objeto Lamp. Assim, a dependncia apenas em relao ao
nome. Podemos corrigir isso mudando o nome de ButtonServer para algo um pouco
mais genrico, como SwitchableDevice (dispositivo comutvel). Tambm podemos
garantir que Button e SwitchableDevice sejam mantidos em bibliotecas separadas
para que o uso de SwitchableDevice no implique o uso de Button.

PRINCPIO DA INVERSO DE DEPENDNCIA (DIP)

177

Nesse caso, ningum possui a interface. Temos a interessante situao na qual


a interface pode ser usada por muitos clientes diferentes e implementada por muitos servidores diferentes. Assim, a interface precisa operar independentemente, sem
pertencer a um ou outro grupo. Em C#, a colocaramos em um namespace e em uma
biblioteca separados.4

O exemplo Furnace
Vamos ver um exemplo mais interessante. Considere o software que poderia controlar o
regulador de um forno (furnace). O software pode ler a temperatura atual em um canal
de entrada/sada (E/S) e instruir o forno para ligar ou desligar, enviando comandos para
outro canal de E/S. A estrutura do algoritmo poderia ser como a da Listagem 11-2.

Listagem 11-2
Algoritmo simples para um termostato
const byte TERMOMETER = 0x86;
const byte FURNACE = 0x87;
const byte ENGAGE = 1;
const byte DISENGAGE = 0;
void Regulate(double minTemp, double maxTemp)
{
for(;;)
{
while (in(THERMOMETER) > minTemp)
wait(1);
out(FURNACE,ENGAGE);
while (in(THERMOMETER) < maxTemp)
wait(1);
out(FURNACE,DISENGAGE);
}
}

A inteno de alto nvel do algoritmo clara, mas o cdigo est repleto de detalhes
de baixo nvel. Esse cdigo nunca poderia ser reutilizado com um hardware de controle
diferente.
Talvez isso no seja uma grande perda, pois o cdigo muito pequeno. Mas, mesmo
assim, uma vergonha no poder reutilizar o algoritmo. Em vez disso, inverteramos as
dependncias e veramos algo como a Figura 11-5.

Em linguagens dinmicas, como Smalltalk, Python ou Ruby, a interface simplesmente no existiria como
uma entidade de cdigo-fonte explcita.

178

PROJETO GIL

Isso mostra que a funo Regulate recebe dois argumentos, os quais so ambos
interfaces. A interface Thermometer pode ser lida (read) e a interface Heater (para o
aquecedor) pode ser ativada (engage) e desativada (disengage). Isso tudo que o algoritmo Regulate precisa. Agora ele pode ser escrito como mostrado na Listagem 11-3
Isso inverteu as dependncias de modo que a diretiva de regulagem de alto nvel
no depende de quaisquer detalhes especficos do termmetro ou do forno. O algoritmo
perfeitamente reutilizvel.

interface
Regulate
parameter

parameter

interface
Thermometer
+ read()

interface
Heater
+ engage()
+ disengage()

Canal de E/S
Thermometer

Canal de E/S
Heater

Figura 11-5
Regulador genrico.

Listagem 11-3
Regulador genrico
void Regulate(Thermometer t, Heater h,
double minTemp, double maxTemp)
{
for(;;)
{
while (t.Read() > minTemp)
wait(1);
h.Engage();
while (t.Read() < maxTemp)
wait(1);
h.Disengage();
}
}

PRINCPIO DA INVERSO DE DEPENDNCIA (DIP)

179

Concluso
A programao procedural tradicional cria uma estrutura de dependncia na qual a diretiva depende dos detalhes. Isso inadequado, pois as diretivas tornam-se vulnerveis
s mudanas nos detalhes. A programao orientada a objetos inverte essa estrutura de
dependncia de modo que tanto os detalhes como as diretivas dependam da abstrao, e
as interfaces de servio so frequentemente de posse de seus clientes.
Alis, essa inverso de dependncias a caracterstica marcante do bom projeto
orientado a objetos. No importa em qual linguagem um programa seja escrito. Se suas
dependncias so invertidas, tem-se um projeto de OO. Se suas dependncias no so
invertidas, tem-se um projeto procedural.
O princpio da inverso de dependncia o mecanismo de baixo nvel fundamental
por trs de muitos dos benefcios reivindicados pela tecnologia orientada a objetos. Sua
aplicao correta necessria para a criao de frameworks reutilizveis. Ele tambm
fundamentalmente importante para a construo de cdigo malevel em presena de
mudana. Como as abstraes e os detalhes so isolados uns dos outros, o cdigo muito
mais fcil de manter.

Bibliografia
[Booch96] Grady Booch, Object Solutions: Managing the Object-Oriented Project, Addison-Wesley, 1996.
[GOF95] Eric Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995.
[Sweet85] Richard E. Sweet, The Mesa Programming Environment, SIGPLAN Notices,
20(7) July 1985: 216-229.

Captulo 12

PRINCPIO DA SEGREGAO
DE INTERFACE (ISP)

princpio da Segregao de Interface (ISP Interface Segregation Principle) lida com


as desvantagens das interfaces gordas. As classes cujas interfaces no so coesas
tm interfaces gordas. Ou seja, as interfaces da classe podem ser divididas em grupos de
mtodos. Cada grupo atende a um conjunto diferente de clientes. Assim, alguns clientes
usam um grupo de mtodos e outros clientes usam outros grupos.
O ISP reconhece que existem objetos que exigem interfaces no coesas; contudo, ele
sugere que os clientes no devem reconhec-las como uma nica classe. Em vez disso, os
clientes devem reconhecer classes base abstratas que tm interfaces coesas.

Poluio de interface
Considere um sistema de segurana no qual objetos Door (porta) podem ser travados (lock) e
destravados (unlock) e saber se esto abertos ou fechados. (Consulte a Listagem 12-1.) Door

Listagem 12-1
Porta de segurana
public interface Door
{
void Lock();
void Unlock();
bool IsDoorOpen();
}

182

PROJETO GIL

codificado como uma interface para que os clientes possam utilizar objetos compatveis com
a interface Door sem depender de implementaes especficas de Door.
Agora, considere que uma implementao, TimedDoor, precisa fazer soar um alarme
quando a porta for deixada aberta por muito tempo. Para tanto, o objeto TimedDoor se
comunica com outro objeto, chamado Timer. (Consulte a Listagem 12-2.)
Quando um objeto deseja ser informado sobre um limite de tempo (timeout), ele
chama a funo Register de Timer. Os argumentos dessa funo so o limite de tempo e
uma referncia para um objeto TimerClient cuja funo TimeOut ser chamada quando
esse tempo expirar.
Como podemos fazer a classe TimerClient se comunicar com a classe TimedDoor
para que o cdigo de TimedDoor possa ser notificado do limite de tempo? Existem vrias
alternativas. A Figura 12-1 mostra uma soluo comum. Obrigamos Door (e, portanto,
TimedDoor) a herdar de TimerClient. Isso garante que TimerClient possa se registrar
em Timer e receber a mensagem de TimeOut.
O problema dessa soluo que a classe Door agora depende de TimerClient.
Nem todas as variedades de portas precisam de temporizao. Alis, a abstrao de
interface
TimerClient
Timer
0..*

+ Timeout

Door

TimedDoor

Figura 12-1
TimerClient no topo da hierarquia.

Listagem 12-2
public class Timer
{
public void Register(int timeout, TimerClient client)
{/*cdigo*/}
}
public interface TimerClient
{
void TimeOut();
}

PRINCPIO DA SEGREGAO DE INTERFACE (ISP)

183

Door original nada tinha a ver com temporizao. Se forem criadas derivadas de Door
isentas de temporizao, elas tero de fornecer s implementaes degeneradas do mtodo TimeOut uma violao em potencial do LSP. Alm disso, os aplicativos que usam
essas classes derivadas tero de importar a definio da classe TimerClient, mesmo
que no seja utilizada. Isso tem o mau cheiro da complexidade e da redundncia desnecessrias.
Esse um exemplo de poluio de interface, uma sndrome comum em linguagens
estaticamente tipadas, como C#, C++ e Java. A interface de Door foi poluda com um
mtodo de que ela no necessita. Ela foi obrigada a incorporar esse mtodo exclusivamente para beneficiar uma de suas subclasses. Se essa prtica for seguida, sempre que uma
derivada precisar de um novo mtodo, esse mtodo ser adicionado classe base. Isso
poluir ainda mais a interface da classe base, tornando-a gorda.
Alm disso, sempre que um novo mtodo adicionado classe base, esse mtodo deve ser implementado ou aparecer por padro nas classes derivadas. Alis, uma
prtica associada adicionar esses mtodos classe base, fornecendo-lhes implementaes degeneradas (ou padro), especificamente para que as classes derivadas no
sejam sobrecarregadas com a necessidade de implement-las. Conforme aprendemos
anteriormente, tal prtica pode violar o LSP, levando a problemas de manuteno e
reutilizao.

Separar clientes significa separar interfaces


Door e TimerClient representam interfaces utilizadas por clientes completamente diferentes. Timer usa TimerClient e as classes que manipulam portas usam Door. Como
os clientes so separados, as interfaces tambm devem permanecer separadas. Por qu?
Porque os clientes exercem foras sobre suas interfaces servidoras.
Quando pensamos nas foras que causam mudanas no software, normalmente pensamos em como as alteraes nas interfaces afetaro seus usurios. Por exemplo, estaramos preocupados com as mudanas para todos os usurios de TimerClient se sua interface mudasse. Contudo, existe uma fora que atua na outra direo. s vezes, o usurio
fora uma mudana na interface.
Por exemplo, alguns usurios de Timer registraro mais de um pedido de limite
de tempo. Considere TimedDoor. Quando detecta que a porta (Door) foi aberta, ele
envia a mensagem Register para Timer, solicitando um limite de tempo. Contudo,
antes que o limite de tempo expire, a porta fecha, permanece fechada durante algum
tempo e, depois, abre novamente. Isso nos faz registrar um novo pedido de limite de
tempo antes que o antigo tenha expirado. Por fim, o primeiro pedido de limite de tempo
expira e a funo TimeOut de TimedDoor chamada. Door faz soar um alarme falso.
Podemos corrigir essa situao usando a conveno mostrada na Listagem 12-3.
Inclumos um nico cdigo timeOutId em cada registro de limite de tempo e repetimos
esse cdigo na chamada de TimeOut para TimerClient. Isso permite que cada derivada
de TimerClient saiba qual pedido de limite de tempo est sendo respondido.
Claramente, essa mudana afetar todos os usurios de TimerClient. Aceitamos
isso, pois a falta de timeOutId uma omisso que precisa ser corrigida. Contudo, o
projeto da Figura 12-1 tambm far Door e todos os seus clientes serem afetados por
essa correo! Isso tem o mau cheiro da rigidez e da viscosidade. Por que um erro em
TimerClient deve ter algum efeito sobre os clientes das derivadas de Door que no

184

PROJETO GIL

Listagem 12-3
Timer with ID
public class Timer
{
public void Register(int timeout,
int timeOutId,
TimerClient client)
{/*cdigo*/}
}
public interface TimerClient
{
void TimeOut(int timeOutID);
}

exigem temporizao? Esse tipo de interdependncia estranha arrepia os clientes e


gerentes at os ossos. Quando uma mudana em uma parte do programa afeta outras
partes totalmente no relacionadas, o custo e as repercusses das mudanas se tornam imprevisveis e o risco de consequncias negativas da mudana aumenta substancialmente.

Princpio da Segregao de Interface


Os clientes no devem ser obrigados a depender de mtodos que no utilizam.

Quando os clientes so obrigados a depender de mtodos que no utilizam, esses clientes esto sujeitos s mudanas feitas nesses mtodos. Isso resulta em um acoplamento
involuntrio entre todos os clientes. Dito de outra maneira, quando um cliente depende
de uma classe que contm mtodos no utilizados por ele, mas que outros clientes usam,
aquele cliente ser afetado pelas mudanas impostas classe por esses outros clientes.
Devemos evitar tais acoplamentos sempre que possvel e, portanto, precisamos separar
as interfaces.

Interfaces de classe versus interfaces de objeto


Considere TimedDoor novamente. Aqui est um objeto que tem duas interfaces separadas, utilizadas por dois clientes distintos: Timer e os usurios de Door. Essas duas
interfaces devem ser implementadas no mesmo objeto, pois a implementao de ambas
manipula os mesmos dados. Como podemos obedecer ao ISP? Como podemos separar as
interfaces quando elas devem permanecer juntas?
A resposta reside no fato de que os clientes de um objeto no precisam acess-lo pela
interface do objeto. Em vez disso, eles podem acess-lo por meio de delegao ou por uma
classe base do objeto.

PRINCPIO DA SEGREGAO DE INTERFACE (ISP)

185

Separao por meio de delegao


Uma soluo criar um objeto que derive de TimerClient e delegue para TimedDoor.
A Figura 12-2 mostra essa soluo. Quando quer registrar um pedido de limite de tempo
em Timer, TimedDoor cria um DoorTimerAdapter e o registra em Timer. Quando Timer
envia a mensagem TimeOut para DoorTimerAdapter, DoorTimerAdapter delega a mensagem para TimedDoor.
Essa soluo obedece ao ISP e impede o acoplamento de clientes de Door com Timer.
Mesmo que a mudana em Timer mostrada na Listagem 12-3 fosse feita, nenhum dos
interface
Timer Client
Door
Timer
0..*

+ Timeout

Door Timer
Adapter

Timed Door

+ Timeout()

+ DoorTimeOut
creates

Figura 12-2
Adaptador para o temporizador da porta.

Listagem 12-4
TimedDoor.cs
public interface TimedDoor: Door
{
void DoorTimeOut(int timeOutId);
}
public class DoorTimerAdapter: TimerClient
{
private TimedDoor timedDoor;
public DoorTimerAdapter(TimedDoor theDoor)
{
timedDoor = theDoor;
}
public virtual void TimeOut(int timeOutId)
{
timedDoor.DoorTimeOut(timeOutId);
}
}

186

PROJETO GIL

usurios de Door seria afetado. Alm disso, TimedDoor no precisa ter exatamente a mesma interface que TimerClient. DoorTimerAdapter pode transformar a interface TimerClient na interface TimedDoor. Assim, essa uma soluo para muitos casos. (Consulte
a Listagem 12-4.)
No entanto, essa soluo tambm um tanto deselegante. Ela envolve a criao de
um novo objeto sempre que queremos registrar um limite de tempo. Alm disso, a delegao exige uma quantidade muito pequena (mas ainda diferente de zero) de tempo de
execuo e memria. Em alguns domnios de aplicao, como nos sistemas de controle de
tempo real embarcados, tempo de execuo e memria so suficientemente escassos para
tornar isso uma preocupao.

Separao por meio de herana mltipla


A Figura 12-3 e a Listagem 12-5 mostram como a herana mltipla pode ser usada
para alcanar o ISP. Nesse modelo, TimedDoor herda tanto de Door como de TimerClient. Embora os clientes das duas classes base possam usar TimedDoor, nenhum
deles depende da classe TimedDoor. Assim, eles usam o mesmo objeto por meio de
interfaces distintas.
Essa soluo minha preferncia normal. A nica vez em que eu escolheria a soluo da Figura 12-2 em detrimento da que aparece na Figura 12-3 seria se a transformao
realizada pelo objeto DoorTimerAdapter fosse necessria ou se diferentes transformaes fossem necessrias em diferentes momentos.
interface
Timer Client
Timer

Door
0..*

+ Timeout

Timed Door
+ Timeout

Figura 12-3
TimedDoor com herana mltipla.

Listagem 12-5
TimedDoor.cpp
public interface TimedDoor: Door, TimerClient
{
}

PRINCPIO DA SEGREGAO DE INTERFACE (ISP)

187

O exemplo de interface de usurio de caixa eletrnico


Agora, vamos considerar um exemplo mais significativo: o tradicional problema do caixa
eletrnico (ATM Automated Tester Machine). A interface do usurio (UI User Interface) de
um caixa eletrnico precisa ser muito flexvel. Talvez a sada precise ser traduzida para vrios idiomas e ser apresentada em uma tela (Screen UI), em um painel em braille (Braille
UI) ou falada em um sintetizador de voz (Speech UI) (Figura 12-4). Claramente, essa flexibilidade pode ser obtida criando-se uma classe base abstrata que tenha mtodos abstratos
para todas as diferentes mensagens que precisam ser apresentadas pela interface.
Considere tambm que cada transao realizada pelo caixa eletrnico encapsulada
como uma derivada de uma classe Transaction. Assim, poderamos ter classes para operaes de depsito, saque e transferncia, como DepositTransaction, WithdrawalTransaction, TransferTransaction etc. Cada uma dessas classes chama mtodos UI. Por
exemplo, para pedir ao usurio para que digite o valor a ser depositado, o objeto DepositTransaction chama o mtodo RequestDepositAmount da classe UI. Do mesmo modo,
para perguntar ao usurio quanto em dinheiro vai transferir entre contas, o objeto TransferTransaction chama o mtodo RequestTransferAmount de UI. Isso corresponde ao
diagrama da Figura 12-5.
Note que precisamente essa a situao que o ISP nos diz para evitar. Cada uma das
transaes est usando mtodos UI que nenhuma outra classe utiliza. Isso gera a possibilidade de que mudanas em uma das classes derivadas de Transaction obriguem uma
mudana correspondente em UI, afetando com isso todas as outras derivadas de Transaction e toda e qualquer outra classe que dependa da interface UI. Algo est exalando
os maus cheiros da rigidez e da fragilidade por aqui.
Por exemplo, se fssemos adicionar uma transao PayGasBillTransaction, teramos que adicionar novos mtodos em UI para tratar das mensagens especficas que essa
transao desejaria exibir. Infelizmente, como DepositTransaction, WithdrawalTransaction e TransferTransaction dependem da interface UI, todas provavelmente sero
recompiladas. Pior ainda, se todas as transaes fossem entregues como componentes em
assemblies separados, muito provavelmente esses assemblies precisariam ser novamente
entregues, mesmo que nada de sua lgica fosse alterado. Voc consegue sentir o mau cheiro
da viscosidade?

interface
ATM UI

Screen UI

Braille UI

Speech UI

Figura 12-4
Interface de usurio de caixa eletrnico.

188

PROJETO GIL

Transaction
{abstract}

+ Execute()

Deposit
Transaction

Withdrawal
Transaction

Transfer
Transaction

interface
UI

+ RequestDepositAmt
+ RequestWithdrawalAmt
+ RequestTransferAmt
+ InformInsufficientFunds

Figura 12-5
Hierarquia de transaes do caixa eletrnico.
Esse infeliz acoplamento pode ser evitado segregando-se a interface UI em interfaces
individuais, como DepositUI, WithdrawUI e TransferUI. Essas interfaces separadas
podem ento ser herdadas por herana mltipla na interface UI final. A Figura 12-6 e a
Listagem 12-6 mostram esse modelo.
Quando uma nova derivada da classe Transaction for criada, ser necessria uma
classe base correspondente para a interface UI abstrata e, assim, a interface UI e todas as
suas derivadas devem mudar. Contudo, essas classes no so amplamente usadas. Alis,
elas provavelmente s so utilizadas por main ou por qualquer processo que inicialize o
sistema e crie a instncia concreta de UI. Portanto, o impacto da adio de novas classes
base de UI minimizado.
Um exame cuidadoso da Figura 12-6 mostra um dos problemas da compatibilidade
com o ISP que no era evidente a partir do exemplo TimedDoor. Note que cada transao
precisa de alguma forma saber sobre sua verso particular da UI. DepositTransaction
deve saber sobre DepositUI, WithdrawTransaction deve saber sobre WithdrawalUI
e assim por diante. Na Listagem 12-6, tratei desse problema obrigando cada transao a
ser construda com uma referncia sua UI especfica. Note que isso me permite usar o
idioma da Listagem 12-7.
Isso til, mas tambm obriga cada transao a conter um membro de referncia
para sua UI. Em C#, algum poderia ficar tentado a colocar todos os componentes de UI
em uma nica classe. A Listagem 12-8 mostra essa estratgia. Entretanto, isso tem um
efeito desastroso. A classe UIGlobals depende de DepositUI, WithdrawalUI e TransferUI. Isso significa que um mdulo que queira usar qualquer uma das interfaces UI
depender transitivamente de todas elas, exatamente a situao que o ISP nos avisa para
evitar. Se for feita uma alterao em qualquer uma das interfaces UI, todos os mdulos
que usam UIGlobals podero ser obrigados a recompilar. A classe UIGlobals uniu novamente as interfaces que tivemos tanto trabalho para segregar!

PRINCPIO DA SEGREGAO DE INTERFACE (ISP)

Transaction
{abstract}

+ Execute()

Withdrawal
Transaction

Deposit
Transaction

interface
Deposit UI

Transfer
Transaction

interface
Withdrawal UI

+ RequestDepositAmt

+ RequestWithdrawalAmt
+ InformInsufficientFunds

interface
Transfer UI
+ RequestTransferAmt

interface
UI

+ RequestDepositAmt
+ RequestWithdrawlAmt
+ RequestTransferAmt
+ InformInsufficientFunds

Figura 12-6
Interface UI segregada para caixa eletrnico.

Listagem 12-6
Interface UI segregada para caixa eletrnico
public interface Transaction
{
void Execute();
}
public interface DepositUI
{
void RequestDepositAmount();
}
public class DepositTransaction: Transaction
{
privateDepositUI depositUI;
public DepositTransaction(DepositUI ui)
{
depositUI = ui;
}

189

190

PROJETO GIL

public virtual void Execute()


{
/*cdigo*/
depositUI.RequestDepositAmount();
/*cdigo*/
}
}
public interface WithdrawalUI
{
void RequestWithdrawalAmount();
}
public class WithdrawalTransaction: Transaction
{
private WithdrawalUI withdrawalUI;
public WithdrawalTransaction(WithdrawalUI ui)
{
withdrawalUI = ui;
}
public virtual void Execute()
{
/*cdigo*/
withdrawalUI.RequestWithdrawalAmount();
/*cdigo*/
}
}
public interface TransferUI
{
void RequestTransferAmount();
}
public class TransferTransaction: Transaction
{
private TransferUI transferUI;
public TransferTransaction(TransferUI ui)
{
transferUI = ui;
}
public virtual void Execute()
{
/*cdigo*/
transferUI.RequestTransferAmount();
/*cdigo*/
}
}
public interface UI: DepositUI, WithdrawalUI, TransferUI
{
}

PRINCPIO DA SEGREGAO DE INTERFACE (ISP)

191

Listagem 12-7
Idioma de inicializao de interface
UI Gui; // objeto global;
void f()
{
DepositTransaction dt = new DepositTransaction(Gui);
}

Listagem 12-8
Empacotando as globais em uma classe
public class UIGlobals
{
public static WithdrawalUI withdrawal;
public static DepositUI deposit;
public static TransferUI transfer;
static UIGlobals()
{
UI Lui = new AtmUI(); // Alguma implementao de UI
UIGlobals.deposit = Lui;
UIGlobals.withdrawal = Lui;
UIGlobals.transfer = Lui;
}
}

Considere agora uma funo g que precisa acessar DepositUI e TransferUI. Considere
tambm que desejamos passar as interfaces do usurio para essa funo. Devemos escrever a declarao da funo como segue:
void g(DepositUI depositUI, TransferUI transferUI)

Ou assim:
void g(UI ui)

A tentao de escrever esta ltima forma (monadria) forte. Afinal, sabemos que
na primeira forma (polidria) os dois argumentos vo se referir ao mesmo objeto. Alm
disso, se fssemos usar a forma polidria, sua chamada poderia ser como segue:
g(ui, ui);

De algum modo isso parece perverso.

192

PROJETO GIL

Perversa ou no, a forma polidria em geral prefervel forma monadria. A forma


monadria obriga g a depender de cada interface includa em UI. Assim, quando WithdrawalUI mudasse, g e todos os clientes de g poderiam ser afetados. Isso pior do que
g(ui,ui)! Alm disso, no podemos garantir que os dois argumentos de g iro sempre
se referir ao mesmo objeto! No futuro, pode ser que os objetos da interface sejam separados por algum motivo. O fato de que todas as interfaces esto combinadas em um nico
objeto uma informao que g no precisa saber. Assim, prefiro a forma polidria para
tais funes.
Muitas vezes os clientes podem ser agrupados pelos mtodos de servio que chamam. Tais agrupamentos permitem criar interfaces segregadas para cada grupo, em vez
de para cada cliente. Isso reduz muito o nmero de interfaces que o servio precisa entender e evita que ele dependa de cada tipo de cliente.
s vezes, os mtodos chamados por diferentes grupos de clientes coincidiro. Se a
sobreposio for pequena, as interfaces dos grupos devero permanecer separadas. As
funes comuns devem ser declaradas em todas as interfaces que se sobrepem. A classe
servidora herdar as funes comuns de cada uma dessas interfaces, mas as implementar apenas uma vez.
Quando aplicativos orientados a objetos so mantidos, as interfaces para classes e
componentes existentes frequentemente mudam. s vezes, essas mudanas tm um impacto
enorme e obrigam a recompilao e a redistribuio de uma parte muito grande do sistema.
Esse impacto pode ser reduzido pela adio de novas interfaces nos objetos existentes, em
vez de alterar a interface existente. Se os clientes da interface antiga quiserem acessar mtodos da nova interface, eles podero consultar o objeto dessa interface, como mostrado na
Listagem 12-9.

Listagem 12-9
void Client(Service s)
{
if(s is NewService)
{
NewService ns = (NewService)s;
// usa a nova interface de servio
}
}

PRINCPIO DA SEGREGAO DE INTERFACE (ISP)

193

Assim como com os demais princpios, tome cuidado para no exagerar. O fantasma
de uma classe com centenas de interfaces diferentes, algumas segregadas pelo cliente e
outras segregadas pela verso, assustador.

Concluso
Classes gordas causam acoplamentos bizarros e prejudiciais entre seus clientes. Quando
um cliente obriga uma mudana na classe gorda, todos os outros clientes so afetados.
Assim, os clientes devem depender apenas dos mtodos que chamam. Isso pode ser alcanado dividindo-se a interface da classe gorda em muitas interfaces especficas do cliente.
Cada interface especfica declara apenas as funes chamadas por seu cliente ou grupo
de clientes especfico. A classe gorda pode ento herdar todas as interfaces especficas do
cliente e implement-las. Isso acaba com a dependncia dos clientes em relao a mtodos
que no chamam e permite que os clientes sejam independentes uns dos outros.

Bibliografia
[GOF95] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995.

Captulo 13

VISO GERAL DA UML PARA


PROGRAMADORES C#

UML (Unified Modeling Language) uma notao grfica para desenhar diagramas
de conceitos de software. Voc pode us-la para desenhar diagramas de um domnio
de problema, de um projeto de software proposto ou de uma implementao de software
j concluda. Fowler descreve esses trs nveis como conceitual, especificao e implementao.1 Este livro trata dos dois ltimos.
Os diagramas em nvel de especificao e de implementao tm uma forte ligao
com o cdigo-fonte. Alis, o objetivo de um diagrama em nvel de especificao ser transformado em cdigo-fonte. Do mesmo modo, o objetivo de um diagrama em nvel de implementao descrever um cdigo-fonte j existente. Assim, os diagramas nesses nveis
devem seguir certas regras e certa semntica. Tais diagramas tm pouca ambiguidade e
bastante formalidade.
Por outro lado, os diagramas no nvel conceitual no so fortemente relacionados
com o cdigo-fonte. Em vez disso, se relacionam com a linguagem humana. Eles so uma
forma abreviada, utilizada para descrever conceitos e abstraes que existem no domnio
do problema humano. Como no seguem regras semnticas rgidas, seu significado pode
ser ambguo e estar sujeito interpretao.
Considere, por exemplo, a seguinte frase: Um cachorro um animal. Podemos criar
um diagrama UML conceitual que represente essa frase, como mostrado na Figura 13-1.
Esse diagrama representa duas entidades Animal e Dog (cachorro) ligadas pela
relao de generalizao. Animal uma generalizao de Dog. Dog um caso especial de
Animal. Isso tudo que o diagrama significa. Nada mais pode ser deduzido a partir dele.
Poderamos estar afirmando que nosso cachorro de estimao, Sparky, um animal; ou
1

[Fowler1999]

196

PROJETO GIL

Animal

Dog

Figura 13-1
Diagrama UML conceitual.
ento, poderamos estar afirmando que os cachorros, como uma espcie biolgica, pertencem ao reino animal. Assim, o diagrama est sujeito interpretao.
Contudo, o mesmo diagrama no nvel da especificao ou da implementao tem um
significado muito mais preciso:
public class Animal {}
public class Dog: Animal {}

Esse cdigo-fonte define Animal e Dog como classes ligadas por uma relao de
herana. Enquanto o modelo conceitual no diz absolutamente nada sobre computadores,
processamento de dados ou programas, o modelo da especificao descreve parte de um
programa.
Infelizmente, os diagramas em si no comunicam em que nvel foram desenhados.
O no reconhecimento do nvel de um diagrama uma fonte de falha de comunicao significativa entre programadores e analistas. Um diagrama em nvel conceitual no define
cdigo-fonte nem deveria definir. Um diagrama em nvel de especificao que descreve a
soluo para um problema no precisa ser parecido com o diagrama em nvel conceitual
que descreve esse problema.
Todos os diagramas restantes deste livro esto nos nveis de especificao/implementao e, onde possvel, so acompanhados pelo cdigo-fonte correspondente. Acabamos
de ver nosso ltimo diagrama em nvel conceitual.
A seguir faremos um giro muito breve pelos principais diagramas utilizados na UML.
Ento, voc ser capaz de ler e escrever a maioria dos diagramas UML de que normalmente precisar. Restaro apenas (e captulos subsequentes trataro disso) os detalhes e
formalismos com os quais voc precisar se tornar proficiente em UML.
A UML tem trs tipos de diagramas principais. Os diagramas estticos descrevem
a estrutura lgica imutvel de elementos de software, representando classes, objetos e
estruturas de dados, e os relacionamentos existentes entre eles. Os diagramas dinmicos mostram como as entidades de software mudam durante a execuo, representando
o fluxo da execuo ou a maneira como as entidades mudam de estado. Os diagramas
fsicos mostram a estrutura fsica imutvel das entidades de software, representando entidades fsicas, como arquivos de cdigo-fonte, bibliotecas, arquivos binrios, arquivos de
dados e seus semelhantes, e os relacionamentos existentes entre elas.

VISO GERAL DA UML PARA PROGRAMADORES C#

Listagem 13-1
TreeMap.cs
using System;
namespace TreeMap
{
public class TreeMap
{
private TreeMapNode topNode = null;
public void Add(IComparable key, object value)
{
if (topNode == null)
topNode = new TreeMapNode(key, value);
else
topNode.Add(key, value);
}
public object Get(IComparable key)
{
return topNode == null ? null : topNode.Find(key);
}
}
internal class TreeMapNode
{
private static readonly int LESS = 0; // menor
private static readonly int GREATER = 1;
// maior
private IComparable key;
private object value;
private TreeMapNode[] nodes = new TreeMapNode[2];
public TreeMapNode(IComparable key, object value)
{
this.key = key;
this.value = value;
}
public object Find(IComparable key)
{
if (key.CompareTo(this.key) == 0) return value;
return FindSubNodeForKey(SelectSubNode(key), key);
}
private int SelectSubNode(IComparable key)
{
return (key.CompareTo(this.key) < 0)? LESS : GREATER;
}
private object FindSubNodeForKey(int node, IComparable key)
{
return nodes[node] == null ? null : nodes[node].Find(key);
}

197

198

PROJETO GIL

public void Add(IComparable key, object value)


{
if (key.CompareTo(this.key) == 0)
this.value = value;
else
AddSubNode(SelectSubNode(key), key, value);
}
private void AddSubNode(int node, IComparable key,
object value)
{
if (nodes[node] == null)
nodes[node] = new TreeMapNode(key, value);
else
nodes[node].Add(key, value);
}
}
}

Considere o cdigo da Listagem 13-1. Esse programa implementa um mapa baseado


em um algoritmo de rvore binria simples. Familiarize-se com o cdigo antes de considerar os diagramas a seguir.

Diagramas de classes
O diagrama de classes da Figura 13-2 mostra as principais classes e relacionamentos do
programa. Uma classe TreeMap tem mtodos pblicos chamados Add e Get e uma referncia para um n TreeMapNode em uma varivel chamada topNode. Cada n TreeMapNode contm uma referncia para duas outras instncias de TreeMapNode em algum tipo
de continer chamado nodes. Cada instncia de TreeMapNode contm referncias para
duas outras instncias, em variveis chamadas key e value. A varivel key contm uma
referncia para alguma instncia que implementa a interface IComparable. A varivel
value contm simplesmente uma referncia para algum objeto.
Vamos examinar as nuanas dos diagramas de classe no Captulo 19. Por enquanto,
voc precisa saber apenas que:
Retngulos representam classes e setas representam relacionamentos.
Nesse diagrama, todos os relacionamentos so associaes. As associaes so
relacionamentos de dados simples nos quais um objeto contm uma referncia
para outro e chama mtodos do outro.
O nome em uma associao mapeia o nome da varivel que contm a referncia.
Um nmero ao lado de uma seta normalmente mostra a quantidade de instncias
mantidas pelo relacionamento. Se esse nmero maior do que 1, est implcito
algum tipo de continer, normalmente um array.

VISO GERAL DA UML PARA PROGRAMADORES C#

2
TreeMap

nodes

TreeMapNode

topNode

+ Add(key, value)
+ Get(value)

199

+ Add(key, value)
+Find(key)

key

<<interface>>
IComparable

value
objeto

Figura 13-2
Diagrama de classes de TreeMap.
Os cones de classe podem ter mais de um compartimento. O compartimento superior sempre contm o nome da classe. Os outros compartimentos descrevem funes
e variveis.
A notao interface significa que IComparable uma interface.
A maioria das notaes mostradas opcional.
Examine atentamente esse diagrama e relacione-o com o cdigo da Listagem 13-1.
Observe como os relacionamentos de associao correspondem s variveis de instncia.
Por exemplo, a associao de TreeMap com TreeMapNode denominada topNode e corresponde varivel topNode dentro de TreeMap.

:TreeMap
:TreeMap

topNode
topNode
:TreeMapNode
:TreeMapNode
- key
key == "Martin"
"Martin"

nodes[LESS]
nodes[LESS]
:TreeMapNode
:TreeMapNode
- key
key == "Bob"
"Bob"

nodes[LESS]
nodes[LESS]
:TreeMapNode
:TreeMapNode
- key
- key
==
"Alan"
"Alan"

nodes[GREATER]
nodes[GREATER]
:TreeMapNode
:TreeMapNode
- key
key == "Robin"
"Robin"

nodes[GREATER]
nodes[GREATER]
:TreeMapNode
:TreeMapNode
- key
- key
= "Don"
= "Don"

nodes[LESS]
nodes[LESS]
:TreeMapNode
:TreeMapNode
- key
- key
==
"Paul"
"Paul"

Figura 13-3
Diagrama de objetos de TreeMap.

nodes[GREATER]
nodes[GREATER]
:TreeMapNode
:TreeMapNode
- key
- key
= "Sam"
= "Sam"

200

PROJETO GIL

Diagramas de objetos
A Figura 13-3 um diagrama de objetos. Ele mostra um conjunto de objetos e relacionamentos em um momento especfico da execuo do sistema. Voc pode consider-lo como
um instantneo da memria.
Nesse diagrama, os cones de retngulo representam objetos. Voc pode saber que
so objetos porque seus nomes esto sublinhados. O que aparece aps os dois-pontos o
nome da classe a que o objeto pertence. Note que o compartimento inferior de cada objeto
mostra o valor da varivel key desse objeto.
Os relacionamentos entre os objetos so denominados links e derivam das associaes da Figura 13-2. Note que os links recebem nomes para as duas clulas no array nodes.

Diagramas de sequncia
A Figura 13-4 um diagrama de sequncia. Ele descreve como o mtodo TreeMap.Add
implementado.
O bonequinho representa um chamador desconhecido. Esse chamador chama o mtodo
Add em um objeto TreeMap. Se a varivel topNode null, TreeMap responde criando um
novo n TreeMapNode e atribuindo-o a topNode. Caso contrrio, TreeMap envia a mensagem
Add para topNode.

:TreeMap
add(key, value)
value

key

[topNode == null]

topNode:
TreeMapNode

add(key, value)
[topNode != null]

Figura 13-4
TreeMap.add.
As expresses booleanas entre colchetes so chamadas guardas. Elas mostram o
caminho tomado. A seta de mensagem que termina no cone TreeMapNode representa
construo. As setas pequenas com crculos so chamadas tokens de dados. Nesse caso,
elas representam os argumentos da construo. O retngulo fino abaixo de TreeMap
chamado ativao. Ele representa quanto tempo o mtodo add executa.

Diagramas de colaborao
A Figura 13-5 um diagrama de colaborao, representando o caso de TreeMap.Add
no qual topNode no null. Os diagramas de colaborao contm as mesmas informaes dos diagramas de sequncia. Contudo, enquanto os diagramas de sequncia
esclarecem a ordem das mensagens, os diagramas de colaborao esclarecem os relacionamentos entre os objetos.

VISO GERAL DA UML PARA PROGRAMADORES C#

201

1: add(key, value)
:TreeMap

[topNode != null]
1.1:add(key, value)
topNode
:TreeMapNode

Figura 13-5
Diagrama de colaborao de um caso de TreeMap.Add.
Os objetos so ligados por relacionamentos chamados links. Um link existe sempre
que um objeto pode enviar uma mensagem para outro. O que trafega por esses links so
as mensagens em si. Elas so representadas pelas setas menores. As mensagens so rotuladas com seu nome, seu nmero de sequncia e por quaisquer guardas que se apliquem.
A estrutura de pontos do nmero de sequncia mostra a hierarquia chamadora. A
funo TreeMap.Add (mensagem 1) chama a funo TreeMapNode.Add (mensagem 1.1).
Assim, a mensagem 1.1 a primeira enviada pela funo chamada pela mensagem 1.

Diagramas de estados
A UML tem uma notao ampla para mquinas de estados finitos. A Figura 13-6 mostra
apenas o subconjunto mais simples dessa notao.
A Figura 13-6 mostra a mquina de estado de uma roleta de metr. Existem dois
estados: Locked e Unlocked. Dois eventos podem ser enviados para a mquina. O evento
coin significa que o usurio colocou uma moeda na roleta. O evento pass significa que o
usurio passou pela roleta.
As setas so chamadas transies. Elas so rotuladas com o evento que dispara a
transio e pela ao que a transio realiza. Quando uma transio disparada, ela faz
o estado do sistema mudar.
Podemos traduzir a Figura 13-6 para o portugus, como segue:
Se estivermos no estado Locked e recebermos um evento coin, fazemos a transio
para o estado Unlocked e chamamos a funo Unlock.
Se estivermos no estado Unlocked e recebermos um evento pass, fazemos a transio para o estado Locked e chamamos a funo Lock.
coin / Unlock
pass / Alarm

Locked

Unlocked
pass / Lock

Figura 13-6
Mquina de estados de uma roleta de metr.

coin / Thankyou

202

PROJETO GIL

Se estivermos no estado Unlocked e recebermos um evento coin, permanecemos no


estado Unlocked e chamamos a funo Thankyou.
Se estivermos no estado Locked e recebermos um evento pass, permanecemos no
estado Locked e chamamos a funo Alarm.
Os diagramas de estados so extremamente teis para se descobrir como um sistema se comporta. Eles nos do a oportunidade de explorar o que o sistema deve fazer
em casos inesperados, como quando um usurio deposita uma moeda e depois deposita
outra moeda sem um motivo especfico.

Concluso
Os diagramas mostrados neste captulo so suficientes para a maioria dos objetivos. A
maioria dos programadores sobrevive sem problemas apenas com o conhecimento de
UML mostrado aqui.

Bibliografia
[Fowler1999] Martin Fowler with Kendall Scott, UML Distilled: A Brief Guide to the Standard Object Modeling Language, 2d ed., Addison-Wesley, 1999.

Captulo 14

TRABALHANDO COM
DIAGRAMAS

ntes de explorarmos os detalhes da UML, devemos falar sobre quando e por que a
utilizamos. Muito dano tem sido causado nos projetos de software devido ao uso
errado e ao abuso da UML.

Por que modelar?


Por que os engenheiros constroem modelos? Por que os engenheiros aeroespaciais constroem modelos de avio? Por que os engenheiros estruturais constroem modelos de pontes? Para que servem esses modelos?
Esses engenheiros constroem modelos para saber se seus projetos funcionaro. Os
engenheiros aeroespaciais constroem modelos de avies e depois os colocam em tneis
de vento para saber se os avies voaro. Os engenheiros estruturais constroem modelos
de pontes para saber se elas se mantero em p. Os arquitetos constroem modelos de
prdios para saber se seus clientes gostaro de como ficaro. Os modelos so construdos
para descobrir se algo funcionar.
Isso significa que os modelos devem ser passveis de teste. De nada adianta construir
um modelo se voc no puder aplicar critrios para test-lo. Se voc no pode avaliar o
modelo, ele no tem valor.
Por que os engenheiros aeroespaciais simplesmente no constroem o avio e tentam
faz-lo voar? Por que os engenheiros estruturais simplesmente no constroem a ponte e
depois verificam se ela para em p? Porque avies e pontes so muito mais caros do que
os modelos. Investigamos os projetos com modelos quando estes so mais baratos do
que a coisa real que estamos construindo.

204

PROJETO GIL

Por que construir modelos de software?


Um diagrama UML pode ser testado? muito mais barato cri-lo e test-lo do que o
software que representa? Em ambos os casos, a resposta no to bvia quanto para
engenheiros aeroespaciais e engenheiros estruturais. No existem critrios slidos
para testar um diagrama UML. Podemos examin-lo, avali-lo e aplicar nele princpios
e padres, mas no final a avaliao ainda subjetiva. mais barato desenhar diagramas UML do que escrever software, mas a diferena no muito grande. Alis, existem
casos em que mais fcil alterar cdigo-fonte do que um diagrama. Ento, quando faz
sentido utilizar UML?
Eu no escreveria alguns captulos deste livro se no fizesse sentido utilizar UML.
No entanto, tambm fcil usar UML de forma errada. Usamos UML quando precisamos testar algo definitivo e quando seu uso mais barato do que utilizar cdigo.
Por exemplo, digamos que eu tenha uma ideia para certo projeto. Preciso saber se os
outros desenvolvedores de minha equipe acham que se trata de uma ideia boa. Assim,
eu desenho um diagrama UML no quadro branco e peo a opinio de meus colegas.

Devemos produzir projetos completos antes de codificar?


Por que os arquitetos, engenheiros aeroespaciais e engenheiros estruturais desenham
plantas? O motivo que uma nica pessoa pode desenhar a planta de uma casa que, para
ser construda, exigir cinco ou mais pessoas. Uma dezena de engenheiros aeroespaciais
pode desenhar plantas de um avio que, para ser construdo, exigir milhares de pessoas.
As plantas podem ser desenhadas sem se escavar as fundaes, despejar concreto ou instalar janelas. Em resumo, muito mais barato planejar uma construo antecipadamente
do que constru-la sem planejamento. No custa muito jogar fora uma planta errada, mas
custa muito demolir um prdio defeituoso.
Mais uma vez, as coisas no so to claros no software. Nem sempre desenhar diagramas UML muito mais barato do que escrever cdigo. Alis, muitas equipes de projeto
tm gasto mais em seus diagramas do que no cdigo em si. Tambm no bvio que
jogar fora um diagrama muito mais barato do que jogar cdigo fora. Portanto, no to
evidente que criar um projeto amplo em UML antes de escrever cdigo seja uma opo
econmica.

TRABALHANDO COM DIAGRAMAS

205

Usando UML de modo eficiente


Aparentemente, arquitetura, engenharia aeroespacial e engenharia estrutural no fornecem uma metfora clara para desenvolvimento de software. No podemos usar UML despreocupadamente, da maneira que essas outras disciplinas utilizam plantas e modelos
(consulte o Apndice B). Portanto, quando e por que devemos usar UML?
Os diagramas so mais teis para comunicao com outras pessoas e para ajudar
a resolver problemas de projeto. importante utilizar somente a quantidade de detalhes necessria para atingir seu objetivo. Um diagrama com muitos adornos contraproducente. Crie diagramas simples e limpos. Diagramas UML no so cdigo-fonte e
no devem ser tratados como um lugar para declarar cada mtodo, varivel e relacionamento.

Comunicando-se com os outros


A UML extremamente til para comunicar conceitos de projeto entre desenvolvedores
de software. Muita coisa pode ser feita em um quadro branco, com um pequeno grupo de
desenvolvedores. Se voc tem algumas ideias que precisa comunicar, a UML pode ser uma
rima opo.
A UML muito boa para comunicar ideias de projeto especficas. Por exemplo, o
diagrama da Figura 14-1 muito claro. Vemos LoginPage derivando da classe Page e
usando UserDatabase. Aparentemente, as classes HttpRequest e HttpResponse so
necessrias para LoginPage. Algum poderia facilmente imaginar um grupo de desenvolvedores diante de um quadro branco e debatendo a respeito de um diagrama como esse.
Alis, o diagrama torna muito claro como seria a estrutura do cdigo.
Por outro lado, a UML no to boa para comunicar detalhes algortmicos. Considere o cdigo simples de ordenao pelo mtodo da bolha da Listagem 14-1. Expressar esse
mdulo simples em UML no muito satisfatrio.
A Figura 14-2 nos fornece uma estrutura aproximada, mas desajeitada e no reflete
os detalhes interessantes. A Figura 14-3 no mais fcil de ler do que o cdigo e significativamente mais difcil de criar. Para esses propsitos, a UML deixa muito a desejar.

Page

HttpRequest

HttpResponse
UserDatabase
+ GetUser()

LoginPage
+Load_Page()

Figura 14-1
LoginPage.

206

PROJETO GIL

Listagem 14-1
BubbleSorter.cs
public class BubbleSorter
{
private static int operations;
public static int Sort(int [] array)
{
operations = 0;
if (array.Length <= 1)
return operations;
for (int nextToLast = array.Length-2;
nextToLast >= 0; nextToLast--)
for (int index = 0; index <= nextToLast; index++)
CompareAndSwap(array, index);
return operations;
}
private static void Swap(int[] array, int index)
{
int temp = array[index];
array[index] = array[index+1];
array[index+1] = temp;
}
private static void CompareAndSwap(int[] array, int index)
{
if (array[index] > array[index+1])
Swap(array, index);
operations++;
}
}

BubbleSorter

+ Sort(array : int[]) : int


+ Swap(array : int[], index : int)
+ CompareAndSwap(array : int[], index : int)

Figura 14-2
BubbleSorter.

TRABALHANDO COM DIAGRAMAS

207

:BubbleSorter

Sort

CompareAndSwap(array, index)
for(int index = 0; index <= nextToLast; index++)
for(int nextToLast = array.length-2; nextToLast >= 0; nextToLast--)

Figura 14-3
Diagrama de sequncia de BubbleSorter.

Roteiros
A UML pode ser til para criar roteiros de grandes estruturas de software. Tais roteiros
oferecem aos desenvolvedores uma maneira rpida de descobrir quais classes dependem de quais outras e fornecem uma referncia para a estrutura do sistema inteiro.
Por exemplo, na Figura 14-4, fcil ver que os objetos Space tm um objeto
PolyLine constitudo de muitos objetos Line derivados de LinearObject, os quais
contm dois objetos Point. Descobrir essa estrutura no cdigo seria maante. Descobri-la em um diagrama seria trivial.
Tais roteiros podem ser ferramentas de ensino teis. Contudo, qualquer membro da
equipe deve ser capaz de desenhar tal diagrama no quadro branco rapidamente. Alis, o
da Figura 14-4 eu tinha em minha memria, de um sistema em que trabalhei h dez anos.
Tais diagramas capturam o conhecimento que todos os desenvolvedores devem ter em
mente para trabalhar de maneira eficiente no sistema. Portanto, de modo geral, no faz
muito sentido tanta preocupao em criar e arquivar tais documentos. Sua maior utilidade , mais uma vez, no quadro branco.

Documentao final
O melhor momento para criar um documento de projeto que voc pretende guardar
no final do projeto. Esse documento refletir precisamente o ltimo estado do projeto
e certamente poder ser til para uma prxima equipe.

208

PROJETO GIL

Objeto
Geomtrico

itsOutline
Point

PolyLine

LinearObject

Space
2

*
Ray

Authored
Space

InfiniteLine

Portal

Line

Human
Portal

Door

Window

WallOpening

Figura 14-4
Diagrama de roteiro.
Existem algumas armadilhas, no entanto. Os diagramas UML precisam ser considerados cuidadosamente. No queremos milhares de pginas de diagramas de sequncia!
Em vez disso, queremos alguns diagramas importantes que descrevam as principais questes do sistema. No ter um diagrama UML pior do que ter um confuso, repleto de linhas
e caixas, como o da Figura 14-5.

O que manter e o que jogar fora


Aprenda a jogar diagramas UML fora. Melhor ainda, crie-os em um meio efmero. Escreva-os em um quadro branco ou em folhas de papel.
Apague o quadro branco frequentemente e jogue fora as folhas de papel. No use uma ferramenta CASE nem um programa de desenho como regra. Existe tempo e lugar para tais
ferramentas, mas a maioria de seus diagramas UML deve ter vida curta.
Contudo, alguns diagramas devem ser guardados: aqueles que expressam uma soluo de projeto comum em seu sistema. Guarde os diagramas que registram protocolos
complexos, difceis de ver no cdigo. Esses so os diagramas que fornecem roteiros para
reas do sistema que no so tocadas com muita frequncia, que registram o objetivo do
projetista de uma maneira melhor do que o cdigo pode express-lo.
Voc no precisa procurar esses diagramas; quando os vir, saber. Tambm no
necessrio cri-los antecipadamente. Voc estar pressupondo e vai supor errado.
Os diagramas teis aparecero muitas vezes. Eles aparecero em quadros brancos ou
em folhas de papel, em diferentes sesses de projeto. Em algum momento, algum far
uma cpia do diagrama, apenas para que ele no precise ser desenhado novamente.
Esse o momento de guardar o diagrama em alguma rea comum a que todos tenham
acesso.

TRABALHANDO COM DIAGRAMAS

fravitz

fradle

209

gazatch

fivvle

parnangle

gabaduchi

goassach

turnatls

tolanomatine
gilmaso

coriadon

Kobe
teliadora
dorasifus

bisotias

Sobe

castigamator
quidatle

Kaastor

zelsofus
sagatock
freedle

meek

denar

garmatos
Gorsage
Gossage

Figura 14-5
Um exemplo ruim, mas muito comum.
importante manter as reas comuns acessveis e organizadas. recomendvel
colocar diagramas teis em um servidor Web ou em uma base de conhecimento em rede.
Contudo, no permita que centenas ou milhares de diagramas se acumulem ali. Seja criterioso com relao a quais diagramas so verdadeiramente teis e quais poderiam ser recriados rapidamente por algum da equipe. Mantenha apenas aqueles cuja sobrevivncia
a longo prazo seria muito valiosa.

Refinamento iterativo
Como criamos diagramas UML? Em um momento de inspirao? Desenhamos os diagramas de classe primeiro e depois os diagramas de sequncia? Devemos erigir a estrutura do sistema inteira antes de dar corpo aos detalhes?
A resposta para todas essas perguntas um retumbante no. Tudo que os seres
humanos fazem bem, eles o fazem dando passos minsculos e depois avaliando o
que fizeram. Erramos quando damos saltos enormes. Queremos criar diagramas UML
teis. Portanto, os criaremos dando passos minsculos.

Comportamento primeiro
Gosto de comear com o comportamento. Se eu acho que a UML vai me ajudar a refletir
sobre um problema, comeo desenhando um diagrama de sequncia ou um diagrama de

210

PROJETO GIL

colaborao simples do problema. Considere, por exemplo, o software que controla um


telefone celular. Como esse software faz a ligao telefnica?
Poderamos imaginar que o software detecta cada boto pressionado e envia uma
mensagem para algum objeto que controla a discagem. Portanto, desenharemos um objeto
boto (Button) e um objeto discador (Dialer) e mostraremos o objeto Button enviando
muitas mensagens digit (dgito) para Dialer (Figura 14-6). (O asterisco significa muitos.)
1*:digit(n)
: Button

: Dialer

Figura 14-6
Um diagrama de sequncia simples.
O que Dialer far quando receber uma mensagem digit? Bem, ele precisa exibir
o dgito na tela. Portanto, talvez envie displayDigit para o objeto Screen (Figura 14-7).
1*:Digit(n)
: Button

: Dialer

1.1 : DisplayDigit(n)

: Screen

Figura 14-7
Continuao da Figura 14-6.
Em seguida, o objeto Dialer precisa que um tom seja emitido pelo alto-falante. Portanto, o faremos enviar a mensagem tone para o objeto Speaker (Figura 14-8).
1*:digit(n)
: Button

: Dialer
1.2: tone(n)
1.1 : displayDigit(n)

: Speaker

: Screen

Figura 14-8
Continuao da Figura 14-7.
Em algum ponto, o usurio vai clicar no boto Send (Enviar), indicando que a ligao deve ser feita. Nesse ponto, precisaremos dizer ao transmissor do celular para que se
conecte com a rede celular e transmita o nmero de telefone que foi discado (Figura 14-9).

TRABALHANDO COM DIAGRAMAS

211

2:Send
send : Button
1*:digit(n)

2.1 : connect(pno)
: Dialer

: Button

: Radio

1.2: tone(n)
1.1 : displayDigit(n)

: Speaker

: Screen

Figura 14-9
Diagrama de colaborao.
Uma vez estabelecida a conexo, o objeto Radio pode dizer ao objeto Screen para
que acenda a luz do indicador de em uso. quase certo que essa mensagem ser enviada
em uma thread de controle diferente, o que denotado pela letra em frente ao nmero de
sequncia. O diagrama de colaborao final aparece na Figura 14-10.
2:Send
send : Button
2.1 : connect(pno)

1*:digit(n)

: Radio

: Dialer

: Button
1.2: tone(n)

1.1 : displayDigit(n)

: Speaker

: Screen
A1 : inUse

Figura 14-10
Diagrama de colaborao do telefone celular.

Verifique a estrutura
Esse pequeno exerccio mostrou como construmos uma colaborao a partir do nada.
Observe como inventamos objetos ao longo do caminho. No sabamos que esses objetos
estariam l; sabamos apenas que precisvamos que certas coisas acontecessem; portanto, inventamos objetos para faz-las.
Antes de continuarmos, precisamos examinar o que essa colaborao significa para
a estrutura do cdigo. Portanto, criaremos um diagrama de classes (Figura 14-11) que
suporte a colaborao. Esse diagrama ter uma classe para cada objeto da colaborao e
uma associao para cada link da colaborao.

212

PROJETO GIL

Button

Dialer

Speaker

Screen

Radio

Figura 14-11
Diagrama de classes do telefone celular.
Aqueles que conhecem a UML notaro que ignoramos a agregao e a composio.
Isso foi intencional. Haver bastante tempo para considerar se qualquer um desses relacionamentos se aplica.
O importante agora a anlise das dependncias. Por que Button deve depender de
Dialer? Se voc pensar a respeito, isso terrvel. Considere o cdigo decorrente:
public class Button
{
private Dialer itsDialer;
public Button(Dialer dialer)
{itsDialer = dialer;}
...
}

// Referncia ao seu Dialer

No quero que o cdigo-fonte de Button mencione o cdigo-fonte de Dialer. Button uma classe que posso utilizar em muitos contextos. Por exemplo, eu gostaria de
usar a classe Button para controlar a tecla liga/desliga ou o boto de menu ou os outros
botes de controle do telefone. Se eu vincular Button a Dialer, no poderei reutilizar o
cdigo de Button para outros propsitos.
Posso corrigir isso inserindo uma interface entre Button e Dialer, como mostrado
na Figura 14-12. Aqui, vemos que cada objeto Button recebe um token que o identifica.
Ao detectar que o boto foi pressionado, a classe Button chama o mtodo buttonPressed da interface ButtonListener, passando o token. Isso elimina a dependncia de Button em relao a Dialer e permite que Button seja usado em praticamente qualquer
lugar que precise receber pressionamentos de boto.

TRABALHANDO COM DIAGRAMAS

Button

213

interface
ButtonListener

- token
+ buttonPressed(token)

Dialer

Speaker

Radio

Screen

Figura 14-12
Isolando Button de Dialer.
Note que essa mudana no teve efeito sobre o diagrama dinmico da Figura 14-10.
Todos os objetos so os mesmos; apenas as classes mudaram.
Infelizmente, agora fizemos Dialer saber algo sobre Button. Por que Dialer deve
obter sua entrada de ButtonListener? Por que deve conter um mtodo chamado buttonPressed? O que Dialer tem a ver com Button?
Podemos resolver esse problema e nos desfazer de toda essa bobagem de token usando um grupo de adaptadores (Figura 14-13). O adaptador ButtonDialerAdapter implementa a interface ButtonListener, recebendo o mtodo buttonPressed e enviando uma
mensagem digit(n) para Dialer. O dgito (digit) passado para Dialer mantido no
adaptador.
interface
ButtonListener
Button
+ buttonPressed()

ButtonDialer
Adapter
- digit

Dialer
Radio
+ digit(n)

Speaker

Screen

Figura 14-13
Adaptando objetos Button a objetos Dialer.

214

PROJETO GIL

Listagem 14-2
ButtonDialerAdapter.cs
public class ButtonDialerAdapter: ButtonListener
{
private int digit;
private Dialer dialer;
public ButtonDialerAdapter(int digit, Dialer dialer)
{
this.digit = digit;
this.dialer = dialer;
}
public void ButtonPressed()
{
dialer.Digit(digit);
}
}

Visualizando o cdigo
Podemos visualizar facilmente o cdigo de ButtonDialerAdapter. Ele aparece na Listagem 14-2. A capacidade de visualizar o cdigo extremamente importante ao trabalhar
com diagramas. Usamos os diagramas como um atalho para o cdigo e no como substitutos dele. Se voc estiver desenhando diagramas e no puder visualizar o cdigo que
eles representam, estar construindo castelos no ar. Pare o que est fazendo e descubra
como transformar isso em cdigo. Nunca deixe os diagramas se tornarem um fim em si
mesmos. Voc sempre deve certificar-se de conhecer o cdigo que est representando.

Evoluo de diagramas
Note que a ltima alterao que fizemos na Figura 14-13 invalidou o modelo dinmico
da Figura 14-10. O modelo dinmico nada sabe sobre os adaptadores. Vamos mudar
isso agora.
A Figura 14-14 mostra como os diagramas evoluem juntos, de maneira iterativa. Voc
comea com um pouco de dinmica. Em seguida, explora o que essa dinmica significa
para os relacionamentos estticos. Voc altera os relacionamentos estticos de acordo com
os princpios do bom projeto. Ento, volta e aprimora os diagramas dinmicos.
Cada um desses passos minsculo. No queremos investir mais do que cinco minutos
em um diagrama dinmico, antes de explorarmos a estrutura esttica decorrente. No queremos passar mais do que cinco minutos refinando essa estrutura esttica, antes de considerarmos o impacto sobre o comportamento dinmico. Em vez disso, queremos evoluir os dois
diagramas juntos, usando ciclos muito curtos.
Lembre-se de que provavelmente estaremos fazendo isso em um quadro branco, e
provavelmente no estaremos registrando para a posteridade o que estamos fazendo. No
estamos tentando ser muito formais ou muito precisos. Alis, os diagramas que inclu nas
figuras anteriores so pouco mais precisos e formais do que voc normalmente precisaria

TRABALHANDO COM DIAGRAMAS

215

2:buttonPressed
ButtonListener
2.1:Send
: SendButton
DialerAdapter
: Button

1.1:digit(n)
: ButtonDialer
Adapter

2.1.1 : connect(pno)
: Dialer

1.1.2: tone(n)
ButtonListener

1.1.1 :
displayDigit(n)

: Radio

1*:buttonPressed
: Speaker

: Screen
A1 : inUse

Figura 14-14
Adicionando adaptadores ao modelo dinmico.
ser. O objetivo no quadro branco no ter todos os pontos corretos em seus nmeros de
sequncia. O objetivo que todos os que esto diante do quadro entendam a discusso. O
objetivo parar de trabalhar no quadro e comear a escrever cdigo.

Quando e como desenhar diagramas


Desenhar diagramas UML pode ser uma atividade muito til. Tambm pode ser um terrvel desperdcio de tempo. A deciso de usar UML pode ser muito boa ou muito ruim. Tudo
depende de como e quanto voc opta por utiliz-la.

Quando desenhar diagramas e quando parar


No estabelea a regra de que tudo deve ser diagramado. Tais regras so completamente
inteis. Quantidades enormes de tempo de projeto e energia podem ser desperdiadas na
busca de diagramas que ningum jamais ler.
Desenhe diagramas quando:
Vrias pessoas precisam compreender a estrutura de uma parte especfica do projeto, pois todas elas vo trabalhar nele simultaneamente. Pare quando todos concordarem que compreendem.
Voc quer o consenso da equipe, mas duas ou mais pessoas discordam sobre como
um elemento especfico deve ser projetado. Estabelea um limite de tempo para a
discusso e, em seguida, escolha uma maneira de decidir, como o voto ou um juiz
imparcial. Pare no final do tempo ou quando a deciso puder ser tomada. Em seguida, apague o diagrama.
Voc quer trabalhar em uma ideia de projeto e os diagramas podem ajud-lo a refletir
sobre ela. Pare quando puder concluir seu raciocnio no cdigo. Descarte os diagramas.

216

PROJETO GIL

Voc precisa explicar a estrutura de alguma parte do cdigo para outra pessoa ou
para si mesmo. Pare quando a explicao for melhor examinando o cdigo.
O fim do projeto est prximo e seu cliente solicitou os diagramas como parte de um
fluxo de documentao para outros.
No desenhe diagramas:
Porque o processo pede que voc faa isso.
Porque voc se sente culpado por no desenh-los ou porque acha que isso que
os bons projetistas fazem. Os bons projetistas escrevem cdigo. Eles s desenham
diagramas quando necessrio.
Para criar documentao abrangente da fase de projeto, antes de codificar. Tais
documentos quase nunca tm valor e consomem quantidades de tempo imensas.
Para outras pessoas codificarem. Os verdadeiros arquitetos de software participam
da codificao de seus projetos.

Ferramentas CASE
As ferramentas CASE para UML podem ser vantajosas, mas tambm dispendiosas. Tenha
muito cuidado ao tomar a deciso de adquirir e implantar uma ferramenta CASE para
UML.
As ferramentas CASE para UML no tornam mais fcil desenhar diagramas? No,
elas tornam muito mais difcil. H uma longa curva de aprendizagem para se tornar
proficiente e, mesmo ento, as ferramentas so mais desajeitadas do que os quadros
brancos, que so muito fceis de usar. Normalmente os desenvolvedores j esto
familiarizados com eles. Se no for o caso, praticamente no h curva de aprendizagem.
As ferramentas CASE para UML no facilitam a colaborao de equipes grandes
nos diagramas? Em alguns casos. Contudo, a grande maioria dos desenvolvedores
e dos projetos de desenvolvimento no precisa produzir diagramas em quantidades
e complexidades tais que exijam um sistema cooperativo automatizado para coordenar suas atividades de diagramao. Em todo caso, o melhor momento para adquirir
um sistema para coordenar a preparao de diagramas UML quando um sistema
manual j tiver sido colocado em vigor, estiver comeando a apresentar deficincias
e a nica escolha for automatizar.
As ferramentas CASE para UML no tornam mais fcil gerar cdigo? improvvel que o esforo global envolvido na criao de diagramas, gerao do cdigo e uso
do cdigo gerado seja menor do que o custo de simplesmente escrever o cdigo. Se
houver um ganho, ele no ser muito grande ou nem mesmo de um fator de 2. Os
desenvolvedores sabem editar arquivos de texto e usar IDEs. Gerar cdigo a partir
de diagramas pode parecer uma boa ideia, mas insisto veementemente que voc
mea o ganho de produtividade antes de gastar muito dinheiro.
E quanto a essas ferramentas CASE que tambm so IDEs e mostram o cdigo e os
diagramas juntos? Essas ferramentas so definitivamente interessantes. Contudo, a
presena constante de UML no importante. O fato de que o diagrama muda quando
modifico o cdigo ou que o cdigo muda quando modifico o diagrama no me ajuda
muito, na verdade. Francamente, prefiro comprar um IDE que tenha se esmerado em
descobrir como me ajudar a manipular meus programas, em vez de meus diagramas.

TRABALHANDO COM DIAGRAMAS

217

Novamente, mea o ganho de produtividade antes de assumir um enorme compromisso financeiro.


Em resumo, observe antes de dar o salto e observe muito bem. Pode ser uma vantagem prover sua equipe com uma ferramenta CASE dispendiosa, mas confirme essa vantagem com suas prprias experincias, antes de comprar algo que poder ficar na prateleira.

E a documentao?
Uma boa documentao fundamental para qualquer projeto. Sem ela, a equipe ficar
perdida em um mar de cdigo. Por outro lado, documentao demais do tipo errado
pior, pois voc tem toda essa papelada que distrai e engana, e ainda tem o mar de cdigo.
A documentao precisa ser criada, mas deve ser criada prudentemente. A escolha
do que no documentar to importante quanto a escolha do que documentar. Um
protocolo de comunicao complexo precisa ser documentado. Um esquema relacional
complexo precisa ser documentado. Uma estrutura reutilizvel complexa precisa ser documentada. Contudo, nenhuma dessas coisas precisa de 100 pginas de UML. A documentao do software deve ser curta e objetiva. O valor de um documento de software
inversamente proporcional ao seu tamanho.
Para uma equipe de projeto de 12 pessoas, trabalhando em um projeto de um milho
de linhas de cdigo, eu teria um total de 25 a 200 pginas de documentao persistente, sendo minha preferncia a menor. Esses documentos incluiriam diagramas UML da estrutura
de alto nvel dos mdulos importantes, diagramas ER (Entidade-Relacionamento) do esquema relacional, uma ou duas pginas sobre como compilar o sistema, instrues de teste, instrues de controle do cdigo-fonte e assim por diante. Eu colocaria essa documentao em
um wiki1 ou em alguma ferramenta de composio colaborativa, para que todos da equipe
pudessem acess-la na tela, pesquis-la e alter-la conforme fosse necessrio.
D muito trabalho tornar um documento pequeno, mas vale a pena. As pessoas leem
documentos pequenos. Elas no leriam tomos de mil pginas.

Concluso
Algumas pessoas diante de um quadro branco podem usar UML para ajud-las a refletir
sobre um problema de projeto. Tais diagramas devem ser criados iterativamente, em ciclos
muito curtos. melhor explorar cenrios dinmicos primeiro e, ento, determinar suas
implicaes na estrutura esttica. importante evoluir os diagramas dinmicos e estticos
em conjunto, usando ciclos iterativos muito curtos, da ordem de cinco minutos ou menos.
As ferramentas CASE para UML podem ser vantajosas em certos casos. Mas para uma
equipe de desenvolvimento normal, elas provavelmente mais atrapalham do que ajudam. Se
voc acha que precisa de uma ferramenta CASE para UML, mesmo integrada com um IDE,
faa primeiro algumas experincias quanto produtividade. Observe antes de dar o salto.
A UML uma ferramenta e no um fim em si mesmo. Como ferramenta, ela pode
ajud-lo a refletir sobre seus projetos e a comunic-los para outros. Utilize-a moderadamente e ela proporcionar a voc o maior benefcio. Abuse dela e ela desperdiar grande
parte de seu tempo. Quando usar UML, pense pequeno.
1

Uma ferramenta de composio de documentos colaborativa baseada na Web. Consulte os sites http://
c2.com e http://fitnesse.org.

Captulo 15

DIAGRAMAS DE ESTADOS

UML tem um rico conjunto de notaes para descrever mquinas de estados finitos (FSMs Finite State Machines). Neste captulo, examinaremos as partes mais
teis dessa notao. As mquinas de estados finitos so ferramentas extremamente
teis para se escrever todos os tipos de software. Eu as utilizo para interfaces grficas
do usurio, protocolos de comunicao e para qualquer outro tipo de sistema baseado
em eventos. Infelizmente, acho que muitos desenvolvedores no conhecem os conceitos
das mquinas de estados finitos e, portanto, esto perdendo muitas oportunidades
para simplificar. Neste captulo, darei minha pequena contribuio para corrigir isso.

Os fundamentos
A Figura 15-1 mostra um diagrama de transio de estados (STD State Transition
Diagram) simples que descreve uma FSM que controla o modo de um usurio fazer
login em um sistema. Os retngulos arredondados representam estados. O nome de
cada estado aparece em seu compartimento superior. No compartimento inferior esto
aes especiais que nos dizem o que fazer quando entramos (entry) ou samos (exit)
do estado. Por exemplo, quando entramos no estado Prompting for Login (Solicitando login), ativamos a ao showLoginScreen para exibir a tela de login. Quando
samos desse estado, ativamos a ao hideLoginScreen para escond-la.

220

PROJETO GIL

Sending Password

Prompting for Login


forgotPassword
entry/showLoginScreen
exit/hideLoginScreen

entry/sendPassword

failed
sent

login / validateUser
retry

Validating
User

failed

forgotPassword

Login Failed
entry/showLoginFailureScreen
exit/hideLoginFailureScreen

valid
Sending Password Failed
logout

ValidUser
entry/showSendFailureScreen
exit/hideSendFailureScreen
OK
Sending Password Succeeded
entry/showSendSuccessScreen
exit/hideSendSuccessScreen

Figura 15-1
Mquina de estados para login simples.
As setas entre os estados so denominadas transies. Cada uma delas rotulada
com o nome do evento que dispara a transio. Algumas tambm so rotuladas com uma
ao a ser executada quando a transio for disparada. Por exemplo, se estamos no estado
Prompting for Login e recebemos um evento login, fazemos a transio para o estado
Validating User e ativamos a ao validateUser.
O crculo preto no canto superior esquerdo do diagrama denominado pseudoestado inicial. Uma FSM comea sua vida seguindo a transio desse pseudoestado.
Assim, nossa mquina de estados comea fazendo a transio para o estado Prompting for Login.
Desenhei um superestado em torno dos estados Sending Password Failed e Sending Password Succeeded porque ambos reagem ao evento OK fazendo a transio para
o estado Prompting for Login. No quis desenhar duas setas idnticas, de modo que
usei a convenincia de um superestado.

DIAGRAMAS DE ESTADOS

221

Essa FSM torna claro o funcionamento do processo de login e decompe o processo em


pequenas funes. Se implementarmos todas as funes de ao, como showLoginScreen,
validateUser e sendPassword, e as conectarmos com a lgica mostrada no diagrama, poderemos ter certeza de que o processo de login funcionar.

Eventos especiais
O compartimento inferior de um estado contm pares evento/ao. Os eventos entry e exit
so padro, mas, como se v na Figura 15-2, voc pode fornecer seus prprios eventos, se
quiser. Se um desses eventos especiais ocorre enquanto a FSM est nesse estado, ento a
ao correspondente ativada.

Estado
entry/AoDeEntrada
exit/AoDeSada
meuEvento1/minhaAo1
meuEvento2/minhaAo2

Figura 15-2
Estados e eventos especiais na UML.
Antes da UML, eu costumava representar um evento especial como uma seta de transio que fazia um loop de volta para o mesmo estado, como na Figura 15-3. Contudo, na
UML isso tem um significado ligeiramente diferente. Qualquer transio que saia de um
estado ativar a ao exit, se houver. Do mesmo modo, qualquer transio que entre em
um estado ativar a ao entry, se houver. Assim, na UML, uma transio reflexiva, como
a da Figura 15-3, ativa no apenas a minhaAo, mas tambm as aes exit e entry.

meuEvento/minhaAo
Estado

Figura 15-3
Transio reflexiva.

222

PROJETO GIL

Superestados
Como voc viu na FSM de login da Figura 15-1, os superestados so convenientes quando voc tem muitos estados que respondem a alguns dos mesmos eventos da mesma
maneira. Voc pode desenhar um superestado em torno desses estados semelhantes e
simplesmente desenhar as setas de transio saindo do superestado, em vez de sarem
dos estados individuais. Assim, os dois diagramas da Figura 15-4 so equivalentes.
As transies de superestado podem ser sobrescritas desenhando-se uma transio explcita a partir dos subestados. Assim, na Figura 15-5, a transio pause de
S3 sobrescreve a transio pause padro do superestado Cancelable. Nesse sentido,
um superestado parecido com uma classe base. Os subestados podem sobrescrever
suas transies de superestado da mesma maneira que as classes derivadas podem
sobrescrever seus mtodos de classe base. Contudo, desaconselhvel levar essa metfora longe demais. O relacionamento entre superestados e subestados no exatamente
equivalente herana.
Os superestados podem ter aes entry, exit e eventos especiais, da mesma
maneira que os estados normais. A Figura 15-6 mostra uma FSM na qual tanto os
superestados como os subestados tm aes exit e entry. Quando faz a transio
de Algum Estado para Sub, a FSM primeiramente ativa a ao entrarSuper, seguida
da ao entrarSub. Do mesmo modo, se faz a transio de Sub2 de volta para Algum
Estado, a FSM primeiramente ativa sairSub2 e depois sairSuper. Contudo, como
no sai do superestado, a transio e2 de Sub para Sub2 simplesmente ativa sairSub
e entrarSub2.

E1
E1
cancelar/reiniciar

E2

cancelar/reiniciar

Incio

cancelar/reiniciar

E2
cancelar/reiniciar

E3
E3

Figura 15-4
Transio: mltiplos estados e superestado.

Incio

DIAGRAMAS DE ESTADOS

223

Cancelvel
pausar/segurar

E1

Pausado

cancelar/reiniciar
E2

Incio
OK/segura
pausar/verificarX

E3

Pausa
especial

falha/avisar

Figura 15-5
Sobrescrevendo transies de superestado.

e3
Super
Sub
e1

Sub2
e2

entry/entrarSub
exit/sairSub

Algum Estado

entry/entrarSub2
exit/sairSub2

entry/entrarSuper
exit/sairSuper

Figura 15-6
Ativao hierrquica de aes entry e exit.

Pseudoestados iniciais e finais


A Figura 15-7 mostra dois pseudoestados muito usados na UML. As FSMs comeam a
existir no processo de transio do pseudoestado inicial. A transio que sai do pseudoestado inicial no pode ter um evento, pois o evento a criao da mquina de estados.
Contudo, a transio pode ter uma ao. Essa ao ser a primeira ativada aps a criao
da FSM.
Analogamente, uma FSM acaba no processo de transio para o pseudoestado final.
O pseudoestado final nunca realmente alcanado. Qualquer ao na transio para o
pseudoestado final ser a ltima ativada pela FSM.

224

PROJETO GIL

entrada/processarEntrada

Processamento
exit/limpar

/inicializar

Figura 15-7
Pseudoestados inicial e final.

Usando diagramas de mquina de estados finitos


Considero diagramas como esses extremamente teis para descobrir mquinas de estado
para subsistemas cujo comportamento conhecido. Por outro lado, a maioria dos sistemas receptivos s FSMs no tem comportamentos que so conhecidos antecipadamente.
Em vez disso, os comportamentos da maioria dos sistemas crescem e evoluem com o
passar do tempo. Os diagramas no so um meio propcio para sistemas que precisam
mudar frequentemente. Problemas de layout e espao interferem no contedo dos diagramas. Essa interferncia s vezes pode impedir que os projetistas faam as mudanas
necessrias em um projeto. O fantasma da reforma do diagrama os impede de adicionar
uma classe ou um estado necessrio e os faz usar uma soluo precria que no afete o
layout do diagrama.
Por outro lado, texto um meio muito flexvel para se lidar com mudana. Os problemas de layout so mnimos e sempre h espao para adicionar linhas de texto. Portanto,
para sistemas que evoluem, eu crio tabelas de transio de estado (STTs State Transition Tables) em arquivos de texto, em vez de usar diagramas de transio de estado.
Considere o STD da roleta de metr da Figura 15-8. Isso pode ser facilmente representado
como uma STT, como mostrado na Tabela 15-1.

coin/Unlock
pass/Alarm

Locked

Unlocked

coin/Refund

pass/Lock

Figura 15-8
STD de roleta de metr.
Tabela 15-1

STT de roleta de metr

Estado atual

Evento

Novo estado

Ao

Locked
Locked
Unlocked
Unlocked

coin
pass
coin
pass

Unlocked
Locked
Unlocked
Locked

Unlock
Alarm
Refund
Lock

DIAGRAMAS DE ESTADOS

225

A STT uma tabela simples com quatro colunas. Cada linha da tabela representa
uma transio. Observe cada seta de transio no diagrama. Voc ver que as linhas da
tabela contm os dois pontos finais de cada seta, assim como o evento e a ao da seta. A
STT lida usando-se o seguinte modelo de frase: Se estamos no estado Locked e recebemos um evento coin, vamos para o estado Unlocked e chamamos a funo Unlock.
Essa tabela pode ser convertida em um arquivo de texto, de forma muito simples:
Locked
Locked
Unlocked
Unlocked

coin
pass
coin
pass

Unlocked
Locked
Unlocked
Locked

Unlock
Alarm
Refund
Lock

Essas 16 palavras contm toda a lgica da FSM.


O compilador de mquina de estados (SMC State Machine Compiler) um compilador simples que escrevi em 1989 para ler STTs e gerar cdigo em C++ para implementar a lgica. Desde ento, o SMC cresceu e mudou para gerar cdigo de vrias linguagens.
Examinaremos o SMC com muito mais detalhes no Captulo 36, quando discutirmos o
padro STATE. O SMC est disponvel gratuitamente na seo de recursos do site www.
objectmentor.com.
Criar e manter mquinas de estados finitos dessa forma muito mais fcil do que
tentar manter diagramas e a gerao do cdigo economiza muito tempo. Assim, embora os
diagramas possam ser muito teis para ajud-lo a refletir sobre uma FSM ou apresent-la
para outros, a forma de texto muito mais conveniente para desenvolvimento.

Concluso
As mquinas de estados finitos representam um conceito poderoso para estruturar software.
A UML fornece uma notao muito poderosa para visualizar FSMs. Contudo, frequentemente mais fcil desenvolver e manter uma FSM usando uma linguagem textual em vez de
diagramas.
A notao de diagrama de estados da UML muito mais rica do que a descrita
aqui. Existem vrios outros pseudoestados, cones e elementos que voc pode aplicar.
Contudo, raramente os considero teis. A notao que descrevi neste captulo tudo
que utilizo.

Captulo 16

DIAGRAMAS DE OBJETOS

s vezes pode ser til mostrar o estado do sistema em um momento especfico.


Como um instantneo de um sistema em execuo, um diagrama de objetos UML
mostra objetos, relacionamentos e valores de atributos que mantm em determinado
momento.

Um instantneo no tempo
H algum tempo, eu estava envolvido com um aplicativo que permitia aos usurios
desenharem a planta baixa de um prdio em uma interface grfica. O programa capturava na estrutura de dados os aposentos, portas, janelas e vos na parede, como mostrado na Figura 16-1. Embora esse diagrama mostre os tipos de estruturas de dados
possveis, no informa exatamente quais objetos e relacionamentos so instanciados
em dado momento.
Vamos supor que um usurio de nosso programa desenhe dois aposentos, uma
cozinha e um refeitrio, conectados por um vo na parede. Tanto a cozinha como o
refeitrio tm uma janela para o lado de fora. O refeitrio tambm tem uma porta que
se abre para fora. Esse cenrio representado pelo diagrama de objetos da Figura 16-2.
Esse diagrama mostra os objetos que esto no sistema e a quais outros objetos esto
conectados. Ele mostra kitchen (cozinha) e lunchRoom (refeitrio) como instncias
distintas de Space. Mostra tambm como esses dois aposentos so conectados por um
vo na parede (WallOpening). Mostra ainda que o exterior representado por outra
instncia de Space. Alm disso, mostra todos os outros objetos e relacionamentos que
devem existir.

228

PROJETO GIL

2
*
Floor

*
Space

Portal

opensInto
HumanPortal

Door

Window

WallOpening

Figura 16-1
Planta baixa.
Diagramas de objetos como esse so teis quando voc precisa mostrar como a estrutura interna de um sistema em dado momento, ou quando o sistema est em um estado especfico. Um diagrama de objetos mostra a inteno do projetista. Mostra a maneira
como certas classes e relacionamentos sero utilizados. Ele pode ajudar a mostrar como
o sistema mudar medida que vrias entradas forem dadas.

: window
kitchen : Space

firstFloor : floor

: WallOpening

outside : Space

lunchRoom :
Space
: window

: Door
opensInto

Figura 16-2
Refeitrio e cozinha.

DIAGRAMAS DE OBJETOS

229

Mas, tenha cuidado; fcil deixar-se levar. Na dcada passada, eu provavelmente


teria desenhado menos de uma dezena de diagramas de objetos desse tipo. A necessidade
deles simplesmente no surgia com muita frequncia. Quando necessrios, eles so indispensveis, e por isso que os estou incluindo neste livro. Contudo, voc no vai precisar
deles muitas vezes e definitivamente no deve supor que precisa desenh-los para cada
cenrio do sistema ou mesmo para cada sistema.

Objetos ativos
Os diagramas de objetos tambm so teis em sistemas multitarefas. Considere, por
exemplo, o cdigo de SocketServer da Listagem 16-1. Esse programa implementa um
framework simples que o permite escrever servidores de sockets sem ter de lidar com
todos os problemas desagradveis relacionados a threads e sincronismo que acompanham os sockets.

Listagem 16-1
SocketServer.cs
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace SocketServer
{
public interface SocketService
{
void Serve(Socket s);
}
public class SocketServer
{
private TcpListener serverSocket = null;
private Thread serverThread = null;
private bool running = false;
private SocketService itsService = null;
private ArrayList threads = new ArrayList();
public SocketServer(int port, SocketService service)
{
itsService = service;
IPAddress addr = IPAddress.Parse("127.0.0.1");
serverSocket = new TcpListener(addr, port);
serverSocket.Start();
serverThread = new Thread(new ThreadStart(Server));

230

PROJETO GIL

serverThread.Start();
}
public void Close()
{
running = false;
serverThread.Interrupt();
serverSocket.Stop();
serverThread.Join();
WaitForServiceThreads();
}
private void Server()
{
running = true;
while (running)
{
Socket s = serverSocket.AcceptSocket();
StartServiceThread(s);
}
}
private void StartServiceThread(Socket s)
{
Thread serviceThread =
new Thread(new ServiceRunner(s, this).ThreadStart());
lock (threads)
{
threads.Add(serviceThread);
}
serviceThread.Start();
}
private void WaitForServiceThreads()
{
while (threads.Count > 0)
{
Thread t;
lock (threads)
{
t = (Thread) threads[0];
}
t.Join();
}
}
internal class ServiceRunner
{
private Socket itsSocket;
private SocketServer itsServer;
public ServiceRunner(Socket s, SocketServer server)
{

DIAGRAMAS DE OBJETOS

231

itsSocket = s;
itsServer = server;
}
public void Run()
{
itsServer.itsService.Serve(itsSocket);
lock (itsServer.threads)
{
itsServer.threads.Remove(Thread.CurrentThread);
}
itsSocket.Close();
}
public ThreadStart ThreadStart()
{
return new ThreadStart(Run);
}
}
}
}

O diagrama de classes desse cdigo aparece na Figura 16-3. Ele no muito inspirador e, a partir do diagrama de classes, difcil ver qual a inteno desse cdigo.
A figura mostra todas as classes e relacionamentos, mas de algum modo a viso global
no cumpre sua obrigao.

ServiceRunner

SocketServer

interface
SocketService

Thread
threads

+ serve(port, service)

+ serve(Socket s)

delegate
Server()

creates

serverThread

Thread

Figura 16-3
Diagrama de classes de SocketServer.

232

PROJETO GIL

threads :
LinkedList

: SocketServer

delegate
Server()

serverThread :
Thread

creates
creates
*

serviceThread :
Thread

: ServiceRunner

: SocketService

Figura 16-4
Diagrama de objetos de SocketServer.
No entanto, veja o diagrama de objetos da Figura 16-4. Isso mostra a estrutura
muito melhor do que o diagrama de classes. A Figura 16-4 mostra que SocketServer
est ligado a serverThread e que serverThread executado em um delegate chamado
Server(). Isso mostra que serverThread responsvel por criar todas as instncias
de ServiceRunner.
Observe as linhas grossas em negrito em torno das instncias de Thread. Os objetos
com bordas grossas em negrito representam objetos ativos, os quais atuam como cabea
de uma thread de controle. Eles contm os mtodos, como Start, Abort, Sleep etc., que
controlam a thread. Nesse diagrama, todos os objetos ativos so instncias de Thread,
pois todo o processamento realizado em delegates para os quais as instncias de Thread
contm referncias.
O diagrama de objetos mais expressivo do que o diagrama de classes porque a estrutura desse aplicativo em particular construda em tempo de execuo. Nesse caso, a
estrutura consiste mais em objetos do que em classes.

Concluso
Os diagramas de objetos fornecem um instantneo do estado do sistema em um momento
especfico. Essa pode ser uma maneira interessante de representar um sistema, especialmente quando a estrutura do sistema construda dinamicamente, em vez de ser imposta
pela estrutura esttica de suas classes. No entanto, deve-se ter prudncia ao desenhar
muitos diagramas de objetos. Na maioria das vezes, eles podem ser inferidos diretamente
dos diagramas de classes correspondentes e, portanto, ter pouca utilidade.

Captulo 17

CASOS DE USO

s casos de uso so uma ideia maravilhosa, mas sua aplicao no tem sido bem-sucedida. Muitas equipes acabam complicando demais o ato de escrever casos de
uso. Normalmente, tais equipes discutem problemas de forma, em vez de contedo. Elas
argumentam e debatem sobre pr-condies, ps-condies, atores, atores secundrios e
muitos outros fatores que simplesmente no importam.
O segredo para um bom caso de uso a simplicidade. No se preocupe com a forma;
simplesmente escreva-o em um papel em branco, e em um processador de textos simples
ou em fichas em branco. No se preocupe em preencher todos os detalhes. Os detalhes s
sero importantes mais adiante. Tambm no se preocupe em capturar todos os casos de
uso; essa uma tarefa impossvel.
Lembre sempre: amanh os casos de uso vo mudar. No importa a quantidade de
detalhes, o tempo dedicado escrita ou o esforo de explorao e anlise dos requisitos:
amanh eles vo mudar.
Se algo vai mudar amanh, voc no precisa anotar seus detalhes hoje. Alis, voc
deve adiar a captura dos detalhes at o ltimo momento possvel. Considere os casos de
uso como requisitos daquele momento.

Escrevendo casos de uso


Preste ateno ao ttulo desta seo. Ns escrevemos casos de uso, no os desenhamos.
Os casos de uso no so diagramas, mas sim descries textuais de requisitos comportamentais, escritos a partir de determinado ponto de vista.

234

PROJETO GIL

Pera! voc diria. Eu sei que a UML tem diagramas de caso de uso, eu os vi.
Sim, a UML tem diagramas de caso de uso. No entanto, esses diagramas nada informam sobre o contedo dos casos de uso. Eles no apresentam dados sobre os requisitos
comportamentais que os casos de uso capturam. Os diagramas de caso de uso da UML
mostram algo totalmente diferente.
Um caso de uso uma descrio do comportamento de um sistema. Essa descrio
escrita do ponto de vista de um usurio que acabou de pedir para o sistema fazer algo
especfico. Um caso de uso captura a sequncia visvel de eventos pela qual um sistema
passa em resposta ao estmulo de um nico usurio.
Um evento visvel aquele que o usurio pode ver. Os casos de uso no descrevem
comportamentos no visveis. Eles no discutem os mecanismos ocultos do sistema. Eles
descrevem apenas o que o usurio pode ver.
Normalmente, um caso de uso dividido em duas sees. A primeira o curso principal. Aqui, descrevemos como o sistema responde ao estmulo do usurio e presumimos
que nada d errado.
Por exemplo, este um caso de uso tpico para um sistema de ponto de venda.
Pagamento do item:
1.

O caixa passa o produto pelo scanner; o scanner l o cdigo UPC (cdigo de


produto universal).

2. O preo e a descrio do item, assim como o subtotal atual, aparecem na tela


diante do cliente. O preo e a descrio tambm aparecem na tela do caixa.
3.

O preo e a descrio so impressos no recibo.

4.

O sistema emite um tom de reconhecimento audvel para informar o caixa


de que o cdigo UPC foi lido corretamente.

Esse o curso principal de um caso de uso. Nada mais complexo necessrio. Talvez at mesmo essa minscula sequncia seja detalhada demais, se fosse demorar um
pouco para ser o caso de uso implementado. No deveramos registrar esse tipo de detalhe
at que o caso de uso estivesse h alguns dias ou semanas de ser implementado.
Como voc pode avaliar um caso de uso se no registra seu detalhe? Voc fala com os
interessados sobre o detalhe, sem necessariamente registr-lo. Isso fornecer as informaes que precisa para fazer uma estimativa aproximada. Por que no registrar os detalhes,
se voc vai falar com os interessados a respeito deles? Porque amanh os detalhes vo
mudar. Essa mudana no afetaria a estimativa? Sim, mas no decorrer de muitos casos
de uso, esses efeitos so integrados. Registrar os detalhes cedo demais no eficaz em
termos de custo.
Se ainda no vamos registrar os detalhes do caso de uso, o que devemos registrar?
Como sabemos que o caso de uso existe, se no anotamos algo? Escreva o nome do caso
de uso. Mantenha uma lista deles em uma planilha eletrnica ou em um documento de
processador de textos. Melhor ainda, escreva o nome do caso de uso em uma ficha e mantenha uma pilha de fichas de caso de uso. Preencha os detalhes quando eles estiverem
prximos da implementao.

CASOS DE USO

235

Cursos alternativos
Alguns desses detalhes estaro relacionados a fatores que podem dar errado. Durante
as conversas com os interessados, discuta sobre cenrios de falha. medida que o
momento em que o caso de uso ser implementado se aproximar, reflita cada vez mais
sobre esses cursos alternativos. Eles se tornam adendos do curso principal do caso de
uso. Eles podem ser escritos como segue.
O cdigo UPC no lido:
Se o scanner no conseguir capturar o cdigo UPC, o sistema deve emitir o tom de
passar de novo, dizendo ao caixa para que tente outra vez. Se aps trs tentativas o
scanner ainda no capturar o cdigo UPC, o caixa dever digit-lo manualmente.

No existe cdigo UPC:


Se o item no tiver um cdigo UPC, o caixa dever digitar o preo manualmente.
Esses cursos alternativos so interessantes, pois sugerem outros casos de uso que os
interessados podem no ter identificado inicialmente. Neste caso, parece necessrio
ter a capacidade de digitar o cdigo UPC ou o preo manualmente.

O que mais?
E quanto aos atores, atores secundrios, pr-condies, ps-condies e o resto? No
se preocupe com essas coisas. Para a ampla maioria dos sistemas em que voc trabalhar, no precisar saber de tudo isso. Se precisar saber mais sobre casos de uso,
voc pode ler o trabalho definitivo de Alistair Cockburn sobre o assunto.1 Por enquanto, aprenda a andar, antes de correr. Acostume-se a escrever casos de uso simples.
Quando voc domin-los ou seja, quando eles tiverem sido usados com sucesso em
um projeto , poder adotar cuidadosa e parcimoniosamente algumas das tcnicas
mais sofisticadas. Mas, lembre-se: no enrole.

Diagramando casos de uso


De todos os diagramas da UML, os diagramas de caso de uso so os mais confusos e
menos teis. Recomendo que voc os evite sempre, com exceo do diagrama de limite
do sistema.
A Figura 17-1 mostra um diagrama de limite do sistema. O retngulo grande o
limite do sistema. Tudo que est dentro do retngulo faz parte do sistema sob desenvolvimento. Fora do retngulo esto os atores que influem no sistema. Os atores so
entidades de fora do sistema e fornecem os estmulos para o sistema. Normalmente,
os atores so usurios humanos. Tambm poderiam ser outros sistemas ou mesmo
dispositivos, como relgios de tempo real.

[Cockburn2001]

236

PROJETO GIL

Pagar item

Cliente
Fechar venda

Caixa
Log in
Devoluo

Supervisor

Figura 17-1
Diagrama de limite do sistema.
Dentro do retngulo delimitador esto os casos de uso: as elipses com nomes dentro.
As linhas conectam os atores aos casos de uso que eles estimulam. Evite usar setas; ningum sabe realmente o que significa a direo das setas.
Esse diagrama quase intil, mas no totalmente. Ele contm pouca informao de
utilidade para o programador, mas constitui uma boa capa para uma apresentao para
os interessados.
Sugiro que voc ignore totalmente os relacionamentos de caso de uso. Eles no agregaro valor algum aos seus casos de uso nem ao seu entendimento do sistema, e iro gerar
debates improdutivos sobre a escolha entre usar extends ou generalization.

Concluso
Este foi um captulo curto, o que adequado, pois o assunto simples. Essa simplicidade
deve ser sua atitude em relao aos casos de uso. Se alguma vez voc seguir o caminho
sombrio da complexidade, ele governar seu destino para sempre. Esteja com a fora e
mantenha seus casos de uso simples.

Bibliografia
[Cockburn2001] Alistair Cockburn, Writing Effective Use Cases, Addison-Wesley, 2001.

Captulo 18

DIAGRAMAS DE SEQUNCIA

s diagramas de sequncia so os modelos dinmicos mais comuns desenhados


pelos usurios de UML. Evidentemente, a UML oferece muitos recursos para
ajud-lo a desenhar diagramas incompreensveis. Neste captulo, descreveremos esses
recursos e tentaremos convenc-lo a utiliz-los com muita moderao.
Certa vez, prestei consultoria para uma equipe que tinha decidido criar diagramas de sequncia para cada mtodo de cada classe. Por favor, no faa isso; puro
desperdcio de tempo. Use diagramas de sequncia quando voc tiver uma necessidade
imediata de explicar a algum como um grupo de objetos colabora ou quando quiser
visualizar essa colaborao. Utilize-os como uma ferramenta de que voc se vale de vez
em quando para aprimorar suas habilidades analticas e no como uma documentao
necessria.

Os fundamentos
Aprendi a desenhar diagramas de sequncia pela primeira vez em 1978. James Grenning, um amigo e colega de longa data, os mostrou para mim quando trabalhvamos
em um projeto que envolvia protocolos de comunicao complexos entre computadores interligados por modems. O que vou mostrar aqui muito parecido com a notao
simples que ele me ensinou naquela poca e deve ser suficiente para a ampla maioria
dos diagramas de sequncia que voc precisar desenhar.

238

PROJETO GIL

:Page
Um pedido
HTTPRequest
gerado por um
formulrio
HTML

e:Employee

EmployeeDB

Login
e := GetEmployee(empid)
empid

senha
Validade(senha)

Uma de duas
pginas Web
informando ao
usurio se o login
foi bem-sucedido

result: bool
:HTTPResponse

Figura 18-1
Diagrama de sequncia tpico.

Objetos, linhas de vida, mensagens e outras mincias


A Figura 18-1 mostra um diagrama de sequncia tpico. Os objetos e classes envolvidos na
colaborao aparecem na parte superior. Os objetos tm os nomes sublinhados; as classes, no. O bonequinho (ator) esquerda representa um objeto annimo. Ele a nascente
e o sumidouro de todas as mensagens que entram e saem da colaborao. Nem todos os
diagramas de sequncia tm esse ator annimo, mas muitos tm.
As linhas tracejadas abaixo dos objetos e do ator so denominadas linhas de vida.
Uma mensagem sendo enviada de um objeto para outro mostrada como uma seta entre
as duas linhas de vida. Cada mensagem rotulada com seu nome. Os argumentos aparecem entre os parnteses que vm aps o nome ou ao lado dos tokens de dados (as pequenas setas com crculos na extremidade). O tempo est na dimenso vertical; portanto,
quanto mais abaixo uma mensagem aparece, mais tarde ela enviada.
O pequeno retngulo fino na linha de vida do objeto Page chamado ativao. As ativaes so opcionais; a maioria dos diagramas no precisa delas. As ativaes representam
o tempo em que uma funo executa. Nesse caso, ela mostra por quanto tempo a funo
Login executada. As duas mensagens que saem direita da ativao foram enviadas pelo
mtodo Login. A seta tracejada no rotulada mostra a funo Login retornando para o
ator e passando de volta um valor de retorno.
Observe o uso da varivel e na mensagem GetEmployee. Isso significa o valor retornado por GetEmployee. Observe tambm que o objeto Employee (Funcionrio) chamado e. Voc adivinhou: eles so a mesma coisa. O valor retornado por GetEmployee uma
referncia para o objeto Employee.
Por fim, note que, como EmployeeDB uma classe, seu nome no est sublinhado.
Isso s pode significar que GetEmployee um mtodo esttico. Assim, esperaramos que
EmployeeDB fosse codificada como na Listagem 18-1.

Criao e destruio
Podemos mostrar a criao de um objeto em um diagrama de sequncia usando a conveno ilustrada na Figura 18-2. Uma mensagem no rotulada termina no objeto a ser criado
e no em sua linha de vida. Esperaramos que ShapeFactory fosse implementada como
mostrado na Listagem 18-2.

DIAGRAMAS DE SEQUNCIA

Listagem 18-1
EmployeeDB.cs
public class EmployeeDB
{
public static Employee GetEmployee(string empid)
{
...
}
...
}

Listagem 18-2
ShapeFactory.cs
public class ShapeFactory
{
public Shape MakeSquare()
{
return new Square();
}
}

ShapeFactory
MakeSquare
s : Shape

s:Square

Figura 18-2
Criando um objeto.

:TreeMap

topNode:
TreeNode

Clear

Figura 18-3
Liberando um objeto para o coletor de lixo.

239

240

PROJETO GIL

Listagem 18-3
TreeMap.cs
public class TreeMap
{
private TreeNode topNode;
public void Clear()
{
topNode = null;
}
}

Em C#, no destrumos objetos explicitamente. O coletor de lixo realiza toda a


destruio explcita para ns. No entanto, existem ocasies em que queremos explicitar
que acabamos de usar um objeto e que, no que nos diz respeito, o coletor de lixo pode
peg-lo.
A Figura 18-3 mostra como denotamos isso na UML. A linha de vida do objeto a ser
liberado chega a um fim prematuro em um grande X. A seta de mensagem que termina no
X representa o ato de liberar o objeto para o coletor de lixo.
A Listagem 18-3 mostra a implementao que poderamos esperar desse diagrama.
Note que o mtodo Clear configura a varivel topNode como null. Como o nico objeto
que contm uma referncia para essa instncia de TreeNode, TreeMap ser liberado para
o coletor de lixo.

Loops simples
Voc pode representar um loop simples em um diagrama UML desenhando uma caixa em
torno das mensagens que se repetem. A condio do loop apresentada entre colchetes
e colocada em algum lugar na caixa, normalmente no canto inferior direito. Consulte a
Figura 18-4.
Essa uma conveno de marcao til. Contudo, no correto tentar capturar
algoritmos em diagramas de sequncia. Os diagramas de sequncia devem ser usados
para revelar as conexes entre objetos e no os pequenos detalhes de um algoritmo.

Casos e cenrios
No desenhe diagramas de sequncia como o da Figura 18-5, com tantos objetos e uma
enorme quantidade de mensagens. Ningum consegue l-los. Ningum os ler. Eles so
um enorme desperdcio de tempo. Em vez disso, aprenda a desenhar alguns diagramas
de sequncia menores que capturem a essncia do que voc est tentando fazer. Cada
diagrama de sequncia deve caber em uma nica pgina, com bastante espao para texto
explicativo. No deve ser necessrio reduzir os cones para tamanhos diminutos para que
caibam na pgina.

DIAGRAMAS DE SEQUNCIA

: Payroll

: PayrollDB

e : Employee

GetEmployeeList
idList : List
GetEmployee(id)
e : Employee

Pay
[for each id in idList]

Figura 18-4
Um loop simples.

Payroll

PayrollDB

HourlyPay
Classification

e : Employee

TimeCard

GetEmployeeList
e:=GetEmployee(id)

WeeklyPayment
Schedule
IsPayDay
IsPayDay
true

CalculatePay

true
CalculatePay
Date
DateInPayPeriod
Hours

CalcOvertime

pay
pay

Union
Affiliation

CalculateDeductions
CalculateTaxes

deductions

Payment
Disposition

Dues
ServiceCharges

SendPayment
payment

Figura 18-5
Um diagrama de sequncia excessivamente complexo.

241

242

PROJETO GIL

Alm disso, no desenhe dezenas ou centenas de diagramas de sequncia. Se houver diagramas demais, eles no sero lidos. Descubra o que h em comum a todos os
cenrios e concentre-se nisso. No mundo dos diagramas UML, os pontos em comum
so muito mais importantes do que as diferenas. Use seus diagramas para mostrar
os temas e as prticas comuns. No os utilize para documentar cada pequeno detalhe.
Se voc precisar realmente desenhar um diagrama de sequncia para descrever como
as mensagens fluem, faa isso sucinta e moderadamente. Desenhe o mnimo possvel
deles.
Primeiro, pergunte-se se o diagrama de sequncia mesmo necessrio. Em geral, o
cdigo mais comunicativo e econmico. A Listagem 18-4, por exemplo, mostra como poderia ser o cdigo de uma classe Payroll. Esse cdigo muito expressivo e autossuficiente. No precisamos do diagrama de sequncia para entend-lo; portanto, no h necessidade de desenh-lo. Quando o cdigo basta-se a si mesmo, os diagramas so redundantes
e representam um desperdcio.
O cdigo realmente pode ser usado para descrever parte de um sistema? Na verdade, esse deve ser um objetivo dos desenvolvedores e projetistas. A equipe deve criar
cdigo que seja expressivo e legvel. Quanto mais o cdigo puder descrever a si mesmo,
menos diagramas sero necessrios e melhor ser o projeto como um todo.
Segundo, se voc achar que um diagrama de sequncia necessrio, pergunte a si
mesmo se existe uma maneira de dividi-lo em um pequeno grupo de cenrios. Por exemplo, poderamos decompor o diagrama de sequncia grande da Figura 18-5 em vrios
diagramas de sequncia muito menores, que seriam muito mais fceis de ler. Considere
como o pequeno cenrio da Figura 18-6 muito mais fcil de entender.

Listagem 18-4
Payroll.cs
public class Payroll
{
private PayrollDB itsPayrollDB;
private PaymentDisposition itsDisposition;
public void DoPayroll()
{
ArrayList employeeList = itsPayrollDB.GetEmployeeList();
foreach (Employee e in employeeList)
{
if (e.IsPayDay())
{
double pay = e.CalculatePay();
double deductions = e.CalculateDeductions();
itsDisposition.SendPayment(pay deductions);
}
}
}
}

DIAGRAMAS DE SEQUNCIA

243

Terceiro, pense no que voc quer retratar. Voc est tentando mostrar os detalhes de uma operao de baixo nvel, como na Figura 18-6, que ilustra como calcular
o pagamento por hora (hourly pay)? Ou est tentando mostrar uma viso de alto nvel
do fluxo global do sistema, como na Figura 18-7? Em geral, os diagramas de alto nvel
so mais teis do que os de baixo nvel. Os diagramas de alto nvel ajudam o leitor a
compor o sistema mentalmente. Eles expem mais os pontos em comum do que as
diferenas.

HourlyPay
Classification

TimeCard

CalculatePay
Date
DateInPayPeriod
Hours

CalcOvertime

pay

Figura 18-6
Um pequeno cenrio.

Payroll

PayrollDB

e : Employee

GetEmployeeList

e:=GetEmployee(id)
IsPayDay
true

CalculatePay
pay

CalculateDeductions
deductions
SendPayment
payment

Figura 18-7
Uma viso de alto nvel.

Payment
Disposition

244

PROJETO GIL

Conceitos avanados
Loops e condies
possvel desenhar um diagrama de sequncia que especifique um algoritmo completamente. A Figura 18-8 mostra o algoritmo da folha de pagamentos (payroll) completo, com
loops e instrues if bem especificados.
A mensagem payEmployee prefixada com a seguinte expresso de recorrncia:
*[foreach id in idList]

O asterisco nos informa que se trata de uma iterao; a mensagem ser enviada repetidamente at que a expresso de guarda entre colchetes seja falsa. Embora a UML tenha
uma sintaxe especfica para expresses de guarda, acho mais til usar um pseudocdigo
do tipo C# que sugira o uso de um iterador ou de uma instruo foreach.
A mensagem payEmployee termina em um retngulo de ativao que est tocando o
primeiro, mas deslocado dele. Isso denota que agora existem duas funes em execuo
no mesmo objeto. Como a mensagem payEmployee recorrente, a segunda ativao tambm ser e, assim, todas as mensagens que dependem dela faro parte do loop.
Observe a ativao que est prxima da guarda relacionada ao dia do pagamento
[payday]. Isso denota uma instruo if. A segunda ativao s obter o controle se a
condio de guarda for verdadeira. Assim, se isPayDay retornar true, calculatePay,
calculateDeductions e sendPayment sero executadas; caso contrrio, no sero.

: Payroll

: PayrollDB

e : Employee

GetEmployeeList
idList : Vector
*[foreach id in idList]:payEmployee(id)
GetEmployee(id)
e : Employee

[payday]

payday := IsPayDay
CalculatePay
CalculateDeductions
SendPayment

Figura 18-8
Diagrama de sequncia com loops e condies.

: Payment
Disposition

DIAGRAMAS DE SEQUNCIA

245

O fato de ser possvel capturar todos os detalhes de um algoritmo em um diagrama


de sequncia no deve ser interpretado como uma licena para capturar todos os seus
algoritmos dessa maneira. A representao de algoritmos na UML deselegante, na melhor das hipteses. Um cdigo como o da Listagem 18-4 uma maneira muito melhor de
expressar um algoritmo.

Mensagens que demoram


Normalmente, no consideramos o tempo que leva para enviar uma mensagem de um
objeto para outro. Na maioria das linguagens de OO, esse tempo praticamente zero.
por isso que desenhamos as linhas de mensagem horizontalmente: elas so imediatas. Em
alguns casos, no entanto, as mensagens demoram a serem enviadas. Poderamos tentar
enviar uma mensagem por uma rede ou em um sistema em que a thread de controle possa
ser dividida entre a chamada e a execuo de um mtodo. Quando isso possvel, podemos denot-lo usando linhas inclinadas, como mostrado na Figura 18-9.
Essa figura mostra uma ligao telefnica. Esse diagrama de sequncia tem trs objetos. O objeto caller a pessoa que est fazendo a ligao. O objeto callee a pessoa
que est recebendo a ligao. O objeto telco a companhia telefnica.
Tirar o telefone do gancho envia a mensagem de em uso (off-hook) para a companhia telefnica, a qual responde com um tom de discagem (dial tone). Tendo recebido o tom de discagem, o chamador (caller) disca o nmero do telefone do receptor
(callee). A companhia telefnica responde fazendo tocar o telefone do receptor e fazendo soar um tom de toque (ringback) para o chamador. O receptor atende ao telefone em
resposta ao toque. A companhia telefnica estabelece a conexo. O receptor diz al
(hello) e a ligao telefnica bem-sucedida.

caller

telco

callee

off hook

dial tone
dial

ring
ringback

off hook

connect

connect

"hello"

Figura 18-9
Ligao telefnica normal.

246

PROJETO GIL

caller

telco

callee

off hook

dial tone

Condio de
corrida

dial
Tenta fazer
uma ligao.

ringback

connect
O telefone
fica mudo. O
chamador ouve
uma respirao?

off hook

ring

connect
O tempo passa.

on hook
Desiste de
esperar por um tom
de discagem.

dial tone

Figura 18-10
Ligao telefnica falha.
Contudo, existe outra possibilidade, a qual demonstra a utilidade desses tipos de
diagramas. Observe atentamente a Figura 18-10. Note que o diagrama comea exatamente igual. No entanto, imediatamente antes que o telefone toque, o receptor tira o fone do
gancho para fazer uma ligao. Agora o chamador est conectado com o receptor, mas
nenhum dos dois sabe disso. O chamador est esperando por um al e o receptor est
esperando por um tom de discagem. Finalmente, o receptor desliga, frustrado, e o chamador ouve um tom de discagem.
O cruzamento das duas setas na Figura 18-10 chamado condio de corrida*. As
condies de corrida ocorrem quando duas entidades assncronas podem executar simultaneamente operaes incompatveis. Em nosso caso, a companhia telefnica executou a
operao ring e o receptor estava ocupado. Nesse ponto, todos os participantes tinham
uma ideia diferente do estado do sistema. O chamador estava esperando por um al, a
companhia telefnica achava que seu trabalho tinha terminado e o receptor estava esperando por um tom de discagem.
Nos sistemas de software, as condies de corrida podem ser muito difceis de
descobrir e depurar. Esses diagramas podem ser teis para encontr-las e diagnostic-las. Principalmente, eles so teis para explic-las para outras pessoas, uma vez
descobertas.

* N. de R.T.: Do original, race condition.

DIAGRAMAS DE SEQUNCIA

247

Mensagens assncronas
Quando voc envia uma mensagem para um objeto, normalmente no espera receber o
controle de volta at que o objeto receptor tenha acabado de executar. As mensagens que
se comportam dessa maneira so denominadas mensagens sncronas. Contudo, em sistemas distribudos ou multitarefas possvel que o objeto que est enviando obtenha o controle de volta imediatamente e que o objeto receptor execute em outra thread de controle.
Tais mensagens so denominadas mensagens assncronas.
A Figura 18-11 mostra uma mensagem assncrona. Note que a ponta da seta aberta,
em vez de ser preenchida. Veja de novo todos os outros diagramas de sequncia deste captulo. Todos eles foram desenhados com mensagens sncronas (pontas de seta preenchidas).
a elegncia ou perversidade; faa sua escolha da UML o fato de que tal diferena sutil
na ponta da seta possa ter uma profunda diferena no comportamento representado.

logger:Log

LogMessage(msg)

Figura 18-11
Mensagem assncrona.
As verses anteriores da UML usavam metade de pontas de seta para denotar mensagens assncronas, como se v na Figura 18-12. Visualmente, isso muito mais distintivo. Os olhos do leitor so imediatamente atrados para a assimetria da ponta da seta.
Portanto, eu continuo a usar essa conveno, mesmo tendo sido trocada na UML 2.0.

logger:Log

LogMessage(msg)

Figura 18-12
Maneira melhor e antiga de representar mensagens assncronas.
As listagens 18-5 e 18-6 mostram cdigo que poderia corresponder Figura 18-11.
A Listagem 18-5 mostra um teste de unidade para a classe AsynchronousLogger da Listagem 18-6. Note que a funo LogMessage retorna imediatamente aps colocar a mensagem na fila. Note tambm que a mensagem processada em uma thread completamente
diferente, iniciada pelo construtor. A classe TestLog garante que o mtodo logMessage
se comporte de forma assncrona por primeiramente verificar se a mensagem foi colocada
na fila, mas no processada, ento deixando o processador para outras threads e, finalmente, verificando se a mensagem foi processada e retirada da fila.

248

PROJETO GIL

Essa apenas uma implementao para uma mensagem assncrona. Outras implementaes so possveis. Em geral, denotamos uma mensagem como assncrona
se o chamador pode esperar que ela retorne antes que as operaes desejadas sejam
executadas.

Listagem 18-5
TestLog.cs
using System;
using System.Threading;
using NUnit.Framework;
namespace AsynchronousLogger
{
[TestFixture]
public class TestLog
{
private AsynchronousLogger logger;
private int messagesLogged;
[SetUp]
protected void SetUp()
{
messagesLogged = 0;
logger = new AsynchronousLogger(Console.Out);
Pause();
}
[TearDown]
protected void TearDown()
{
logger.Stop();
}
[Test]
public void OneMessage()
{
logger.LogMessage("one message");
CheckMessagesFlowToLog(1);
}
[Test]

DIAGRAMAS DE SEQUNCIA

public void TwoConsecutiveMessages()


{
logger.LogMessage("another");
logger.LogMessage("and another");
CheckMessagesFlowToLog(2);
}
[Test]
public void ManyMessages()
{
for (int i = 0; i < 10; i++)
{
logger.LogMessage(string.Format("message:{0}", i));
CheckMessagesFlowToLog(1);
}
}
private void CheckMessagesFlowToLog(int queued)
{
CheckQueuedAndLogged(queued, messagesLogged);
Pause();
messagesLogged += queued;
CheckQueuedAndLogged(0, messagesLogged);
}
private void CheckQueuedAndLogged(int queued, int logged)
{
Assert.AreEqual(queued,
logger.MessagesInQueue(), "queued");
Assert.AreEqual(logged,
logger.MessagesLogged(), "logged");
}
private void Pause()
{
Thread.Sleep(50);
}
}
}

249

250

PROJETO GIL

Listagem 18-6
AsynchronousLogger.cs
using System;
using System.Collections;
using System.IO;
using System.Threading;
namespace AsynchronousLogger
{
public class AsynchronousLogger
{
private ArrayList messages =
ArrayList.Synchronized(new ArrayList());
private Thread t;
private bool running;
private int logged;
private TextWriter logStream;
public AsynchronousLogger(TextWriter stream)
{
logStream = stream;
running = true;
t = new Thread(new ThreadStart(MainLoggerLoop));
t.Priority = ThreadPriority.Lowest;
t.Start();
}
private void MainLoggerLoop()
{
while (running)
{
LogQueuedMessages();
SleepTillMoreMessagesQueued();
Thread.Sleep(10); // Lembrar-me de explicar isto.
}
}
private void LogQueuedMessages()
{
while (MessagesInQueue() > 0)
LogOneMessage();
}
private void LogOneMessage()
{
string msg = (string) messages[0];

DIAGRAMAS DE SEQUNCIA

messages.RemoveAt(0);
logStream.WriteLine(msg);
logged++;
}
private void SleepTillMoreMessagesQueued()
{
lock (messages)
{
Monitor.Wait(messages);
}
}
public void LogMessage(String msg)
{
messages.Add(msg);
WakeLoggerThread();
}
public int MessagesInQueue()
{
return messages.Count;
}
public int MessagesLogged()
{
return logged;
}
public void Stop()
{
running = false;
WakeLoggerThread();
t.Join();
}
private void WakeLoggerThread()
{
lock (messages)
{
Monitor.PulseAll(messages);
}
}
}
}

251

252

PROJETO GIL

Mltiplas threads
Mensagens assncronas significam mltiplas threads de controle. Podemos mostrar vrias
threads de controle diferentes em um diagrama UML rotulando o nome da mensagem com
um identificador de thread, como mostrado na Figura 18-13.
Note que o nome da mensagem prefixado com um identificador, como T1, seguido
de dois-pontos. Esse identificador refere-se thread a partir da qual a mensagem foi enviada. No diagrama, o objeto AsynchronousLogger foi criado e manipulado pela thread
T1. A thread que registra a mensagem, executando dentro do objeto Log, chamada T2.
Como voc pode ver, os identificadores de thread no correspondem necessariamente aos nomes que aparecem no cdigo. A Listagem 18-6 no identifica a thread de log T2.
Em vez disso, os identificadores de thread servem para o diagrama.

Objetos ativos
s vezes, queremos denotar que um objeto tem uma thread interna separada. Tais objetos
so conhecidos como objetos ativos. Eles so mostrados com um contorno em negrito,
como na Figura 18-14.
Os objetos ativos instanciam e controlam suas prprias threads. No existem restries em relao aos seus mtodos. Seus mtodos podem ser executados na thread do
objeto ou na thread do chamador.

out : TextWriter
T1:

: Asynchronous
Logger

T1:Start
T1:LogMessage(msg)
T2:WriteLine

running == true
T1:Stop

Figura 18-13
Mltiplas threads de controle.

DIAGRAMAS DE SEQUNCIA

253

logger:
Asynchronous
Logger

LogMessage(msg)

Figura 18-14
Objeto ativo.

Enviando mensagens para interfaces


Nossa classe AsynchronousLogger uma maneira de registrar mensagens atravs de um
log. E se quisssemos que nosso aplicativo fosse capaz de usar muitos tipos diferentes de
registradores de log? Provavelmente criaramos uma interface Logger (registrador de log)
que declarasse o mtodo LogMessage e derivasse nossa classe AsynchronousLogger e
todas as outras implementaes dessa interface. Consulte a Figura 18-15.
O aplicativo vai enviar mensagens para a interface Logger. O aplicativo no saber que o objeto um AsychronousLogger. Como podemos representar isso em um
diagrama de sequncia?
A Figura 18-16 mostra a estratgia bvia. Voc simplesmente nomeia o objeto
como a interface e pronto. Talvez isso parea violar as regras, pois impossvel ter
uma instncia de uma interface. No entanto, estamos afirmando apenas que o objeto
logger corresponde ao tipo Logger. No estamos dizendo que de algum modo conseguimos instanciar uma interface nua.

interface
Logger
+ LogMessage

interface Logger {
void LogMessage(string msg);
}
public class AsynchronousLogger : Logger {
...
}

Asynchronous
Logger

Figura 18-15
Projeto de registrador de log simples.

254

PROJETO GIL

logger : Logger

LogMessage(msg)

Figura 18-16
Enviando para uma interface.
s vezes, contudo, sabemos o tipo do objeto e ainda assim queremos mostrar a mensagem sendo enviada para uma interface. Por exemplo, poderamos saber que criamos um
objeto AsynchronousLogger, mas ainda queremos mostrar o aplicativo usando apenas a
interface Logger. A Figura 18-17 mostra como isso representado. Usamos o pirulito de
interface na linha de vida do objeto.
logger :
Asynchronous
Logger
LogMessage(msg)
Logger

Figura 18-17
Enviando para um tipo derivado por meio de uma interface.

Concluso
Vimos que os diagramas de sequncia so uma maneira eficaz de comunicar o fluxo de
mensagens em um aplicativo orientado a objetos. No entanto, tambm alertamos que
fcil abusar e ir longe demais com eles.
Vez ou outra, um diagrama de sequncia no quadro branco pode ser valioso. Um documento muito curto, com cinco ou seis diagramas de sequncia denotando as interaes
mais comuns em um subsistema, pode valer ouro. Por outro lado, um documento com mil
diagramas de sequncia provavelmente no valer o papel em que est impresso.
Uma das grandes falcias do desenvolvimento de software nos anos 1990 era a ideia
de que os desenvolvedores deveriam desenhar diagramas de sequncia para todos os mtodos antes de escreverem o cdigo. No faa isso: sempre um desperdcio de tempo
muito dispendioso.
Em vez disso, use diagramas de sequncia como a ferramenta que foram destinados
a ser. Utilize-os em um quadro branco para se comunicar com outras pessoas em tempo
real. Use em um documento conciso para capturar as colaboraes importantes e centrais
do sistema.
No que diz respeito aos diagramas de sequncia, pouco melhor do que muito. Voc
sempre pode desenhar um deles posteriormente, se achar que precisa.

Captulo 19

DIAGRAMAS DE CLASSES

s diagramas de classes da UML nos permitem denotar o contedo esttico e os


relacionamentos de classes. Em um diagrama de classes podemos mostrar as
variveis membro e as funes membro de uma classe. Tambm podemos mostrar se
uma classe herda de outra ou se contm uma referncia para outra. Em resumo, podemos
retratar todas as dependncias de cdigo-fonte entre as classes.
Isso pode ser valioso. Pode ser muito mais fcil avaliar a estrutura de dependncia de
um sistema a partir de um diagrama do que a partir do cdigo-fonte. Os diagramas tornam
certas estruturas de dependncia visveis. Podemos ver ciclos de dependncia e determinar
a melhor forma de romp-los. Podemos ver quando classes abstratas dependem de classes
concretas e podemos determinar uma estratgia para redirecionar tais dependncias.

Os fundamentos
Classes
A Figura 19-1 mostra a forma mais simples de diagrama de classes. A classe chamada
Dialer est representada como um retngulo simples. Esse diagrama representa nada o
cdigo mostrado sua direita.

Dialer

public class
{
}

Figura 19-1
Smbolo de classe.

256

PROJETO GIL

Essa a maneira mais comum de representar uma classe. Na maioria dos diagramas, as
classes podem ser representadas apenas pelos seus nomes.
Um smbolo de classe pode ser subdividido em compartimentos. O compartimento
superior contm o nome da classe, o segundo contm as variveis da classe e o terceiro
contm os seus mtodos. A Figura 19-2 mostra esses compartimentos e como eles se traduzem em cdigo.
public class Dialer
{
private ArrayList digits;
private int nDigits;
public void Digit(int n);
protected bool RecordDigit(int n);
}

Dialer
- digits : ArrayList
- nDigits : int
+ Digit(n : int)
# RecordDigit(n : int) : bool

Figura 19-2
Compartimentos do smbolo de classe com o cdigo correspondente.

Observe o caractere na frente das variveis e funes no smbolo de classe. Um


trao () denota private; o jogo da velha (#), protected; e um sinal de adio (+),
public.
O tipo de uma varivel ou o argumento de uma funo aparece aps os dois-pontos
que seguem o nome da varivel ou do argumento. Da mesma forma, o valor de retorno de
uma funo aparece aps os dois-pontos que seguem a funo.
Esse tipo de detalhe s vezes til, mas no deve ser usado com muita frequncia.
Os diagramas UML no so um lugar para se declarar variveis e funes. Tais declaraes so mais bem realizadas no cdigo-fonte. Use esses adornos somente quando eles
forem essenciais para o propsito do diagrama.

Associao
As associaes entre classes na maioria das vezes representam variveis de instncia que
contm referncias para outros objetos. Por exemplo, a Figura 19-3 mostra uma associao
entre Phone e Button. A direo da seta indica que Phone contm uma referncia para
Button. O nome prximo ponta da seta o nome da varivel de instncia. O nmero
prximo ponta da seta indica quantas referncias so mantidas.

15
Phone

Button
itsButtons

public class Phone


{
private Button itsButtons[15];
}
Figura 19-3
Associao.

DIAGRAMAS DE CLASSES

Phonebook

*
itsPnos

Phone
Number

257

public class Phonebook


{
private ArrayList itsPnos;
}

Figura 19-4
Associao de um para muitos.

Na Figura 19-3, 15 objetos Button esto conectados ao objeto Phone. A Figura 19-4 mostra o que acontece quando no h limite. Um objeto Phonebook est conectado a muitos
objetos PhoneNumber. (O asterisco significa muitos.) Em C#, isso mais comumente
implementado com ArrayList ou alguma outra coleo.
Eu poderia ter dito, um Phonebook tem muitos PhoneNumbers. Em vez disso, evitei o uso da palavra tem. Isso foi intencional. Os verbos comuns da OO TEM-UM e -UM
tm levado a vrios mal-entendidos lamentveis. Por enquanto, no espere que eu utilize
os termos comuns. Em vez disso, usarei termos que descrevem o que acontece no software, como est conectado a.

Herana
Na UML, voc deve tomar muito cuidado com as setas. A Figura 19-5 mostra o porqu.
A seta que aponta para Employee denota herana.1 Se voc desenhar suas setas de
qualquer jeito, poder ser difcil saber se elas significam herana ou associao. Para
evitar problemas, eu frequentemente desenho os relacionamentos de herana verticais
e as associaes horizontais.
Na UML, todas as setas apontam na direo da dependncia do cdigo-fonte. Como
a classe SalariedEmployee que menciona o nome de Employee, a seta aponta para
Employee. Portanto, na UML as setas de herana apontam para a classe base.

Employee

public class Employee


{
...
}

Salaried
Employee

public class SalariedEmployee : Employee


{
...
}
Figura 19-5
Herana.

Na verdade, ela denota generalizao, mas no que diz respeito ao programador de C#, a diferena discutvel.

258

PROJETO GIL

interface
ButtonListener

ButtonDialer
Adapter

interface ButtonListener
{
...
}
public class ButtonDialerAdapter
: ButtonListener
{
...
}
Figura 19-6
Relacionamento realiza.

A UML tem uma notao especial para o tipo de herana usado entre uma classe e uma
interface da linguagem C#. Como se v na Figura 19-6, trata-se de uma seta de herana tracejada.2 Nos prximos diagramas, voc ver que me esqueci de tracejar as setas que apontam
para interfaces. Sugiro que voc tambm se esquea de tracejar as setas que desenhar em
quadros brancos. A vida curta demais para se ficar tracejando setas.
A Figura 19-7 mostra outra maneira de transmitir a mesma informao. As interfaces podem ser desenhadas como pirulitos nas classes que as implementam. Vemos frequentemente esse tipo de notao em projetos de COM*.
ButtonListener

ButtonDialer
Adapter

Figura 19-7
Indicador de interface tipo pirulito.

Um exemplo de diagrama de classes


A Figura 19-8 mostra um diagrama de classes simples de parte de um sistema de caixa
eletrnico. Esse diagrama interessante tanto pelo que mostra quanto pelo que no mostra. Note que me esforcei para indicar todas as interfaces. Considero fundamental garantir
que meus leitores saibam quais classes pretendo que sejam interfaces e quais pretendo
que sejam implementadas. Por exemplo, o diagrama mostra imediatamente que WithdrawalTransaction (transao de saque) se comunica com uma interface CashDispenser
(dispositivo que entrega as cdulas). Claramente, alguma classe no sistema ter de implementar CashDispenser, mas nesse diagrama no nos importamos com qual ela .
2

Isso chamado de relacionamento realiza. H mais nisso do que apenas herana de interface, mas
a diferena est fora dos objetivos deste livro e provavelmente fora dos objetivos de qualquer um que
escreva cdigo para sobreviver.
* N. de R.T.: Component Object Model.

DIAGRAMAS DE CLASSES

259

Perceba que no fui muito meticuloso na documentao dos mtodos das diversas
interfaces UI. Certamente, WithdrawalUI (interface para saques) precisar de mais do
que os dois mtodos mostrados ali. E quanto a PromptForAccount (solicitar nmero de
conta) ou InformCashDispenserEmpty (informar que no h cdulas suficientes)? Inserir esses mtodos no diagrama o tornaria congestionado. Fornecer um grupo representativo dos mtodos o suficiente para que o leitor entenda o recado.
Novamente, observe que a conveno da associao horizontal e da herana vertical
ajuda a diferenciar esses tipos de relacionamentos completamente distintos. Sem uma
conveno como essa, pode ser difcil entender o significado do emaranhado.
Observe como separei o diagrama em trs zonas distintas. As transaes e suas aes
esto esquerda, as diversas interfaces UI esto todas direita e a implementao de UI est
na parte inferior. Note tambm que as conexes entre os agrupamentos so mnimas e regulares. Em um caso, so trs associaes, todas apontando da mesma maneira. No outro caso,
so trs relacionamentos de herana, todos unidos em uma nica linha. Os agrupamentos
e a maneira como esto conectados ajudam o leitor a ver o diagrama em trechos coerentes.

interface
Transaction
+ Execute()

interface
CashDispenser

interface
WithdrawalUI
Withdrawal
Transaction
+ PromptForWithdrawalAmount
+ InformInsufficientFunds

interface
DepositAccepter

interface
DepositUI
Deposit
Transaction

+ PromptForDepositAmount
+ PromptForEnvelope
interface
TransferUI

Transfer
Transaction

interface
Screen

+ PromptForTransferAmount
+ PromptForFromAccount
+ PromptForToAccount

UI

MessageLog

+ DisplayMessage
+ DisplayMessage

SpanishUI

EnglishUI

Figura 19-8
Diagrama de classes de caixa eletrnico.

+ LogMessage

260

PROJETO GIL

Listagem 19-1
UI.cs
public abstract class UI:
WithdrawalUI, DepositUI, TransferUI
{
private Screen itsScreen;
private MessageLog itsMessageLog;
public abstract void PromptForDepositAmount();
public abstract void PromptForWithdrawalAmount();
public abstract void InformInsufficientFunds();
public abstract void PromptForEnvelope();
public abstract void PromptForTransferAmount();
public abstract void PromptForFromAccount();
public abstract void PromptForToAccount();
public void DisplayMessage(string message)
{
itsMessageLog.LogMessage(message);
itsScreen.DisplayMessage(message);
}
}

Voc deve ser capaz de ver o cdigo ao olhar para o diagrama. A Listagem 19-1 parecida com o que voc esperaria para a implementao da interface do usurio?

Os detalhes
Inmeros detalhes e adornos podem ser adicionados aos diagramas de classes da UML.
Na maioria das vezes, esses detalhes e adornos no devem ser inseridos. No entanto, existem ocasies em que eles podem ser teis.

Esteretipos de classe
Os esteretipos de classe aparecem entre os caracteres e 3, normalmente acima do nome da
classe. J os vimos antes. A denotao interface na Figura 19-8 um esteretipo de classe.
Os programadores C# podem usar dois esteretipos padro: interface e utility.
interface Todos os mtodos de classes marcados com esse esteretipo so abstratos.
Nenhum dos mtodos pode ser implementado. Alm disso, as classes interface no podem ter variveis de instncia. As nicas variveis que elas podem ter so variveis estticas. Isso corresponde exatamente s interfaces da linguagem C#. Consulte a Figura 19-9.

Esses caracteres no so dois sinais de menor e dois de maior. Se voc usar operadores de desigualdade
duplicados, em vez dos caracteres e adequados e corretos, a polcia da UML o encontrar.

DIAGRAMAS DE CLASSES

261

Eu desenho interfaces com tanta frequncia que explicitar o esteretipo inteiro no


quadro branco pode ser bastante inconveniente. Assim, muitas vezes uso a forma abreviada que aparece na parte inferior da Figura 19-9 para tornar o desenho mais fcil. Isso no
UML padro, mas muito mais conveniente.

interface
Transaction
+ Execute()
I
Transaction

interface Transaction
{
public void Execute();
}

+ Execute()

Figura 19-9
Esteretipo de classe interface.
utility Todos os mtodos e variveis de uma classe utility so estticos. Booch costumava chamar essas classes de utilitrias.4 Consulte a Figura 19-10.

utility
Math

public class Math


{
public static readonly double PI =
3.14159265358979323;

+ PI : double
+ Sin()
+ Cos()

public static double Sin(double theta){...}


public static double Cos(double theta){...}
}
Figura 19-10
Esteretipo de classe utility.

Se quiser, voc pode fazer seus prprios esteretipos. Eu uso com frequencia os esteretipos persistent, C-API, struct ou function. Basta garantir que quem ir ler seus
diagramas saber o que seus esteretipos significam.

Classes abstratas
Na UML, existem duas maneiras de denotar que uma classe ou um mtodo abstrato.
Voc pode escrever o nome em itlico ou usar a propriedade {abstract}. As duas opes
so mostradas na Figura 19-11.
difcil escrever em itlico em um quadro branco e a propriedade {abstract} pouco
concisa. Assim, no quadro branco eu uso a conveno mostrada na Figura 19-12, caso preci-

[Booch94], p. 186.

262

PROJETO GIL

se denotar uma classe ou mtodo como abstrato. Novamente, isso no UML padro, mas no
quadro branco muito mais conveniente.5

Shape
- itsAnchorPoint
+ Draw()

Shape
{abstract}

public abstract class Shape


{
private Point itsAnchorPoint;
public abstract void Draw();
}

- itsAnchorPoint
+ Draw() {abstract}

Figura 19-11
Classes abstratas.

{A}
Shape
+ Draw() {A}

Figura 19-12
Indicao extraoficial de classes abstratas.

Propriedades
Propriedades como {abstract} podem ser adicionadas em qualquer classe. Elas representam informaes extras que normalmente no fazem parte de uma classe. Voc pode
criar suas prprias propriedades a qualquer momento.
As propriedades so escritas em uma lista separada por vrgulas de pares nome/valor,
como segue:
{author=Martin, date=20020429, file=shape.cs, private}

As propriedades do exemplo anterior no fazem parte da UML. Alm disso, as propriedades no precisam ser especficas do cdigo, mas podem conter qualquer metadado
que voc imaginar. A propriedade {abstract} a nica definida pela UML que os programadores normalmente acham til.

Talvez voc se lembre da notao de Booch. Uma qualidade dessa notao era sua convenincia; Ela era
tima para o quadro branco.

DIAGRAMAS DE CLASSES

263

Shape
{author=Martin,
date=20020429,
file=shape.cs,
private}

Figura 19-13
Propriedades.

Presume-se que uma propriedade que no tenha um valor assuma o valor booleano
true. Assim, {abstract} e {abstract = true} so sinnimos. As propriedades so escritas abaixo e direita do nome da classe, como mostrado na Figura 19-13.
A no ser na propriedade {abstract}, no sei se isso ser muito til. Em minha experincia de muitos anos desenhando diagramas UML, nunca precisei usar propriedades
de classe.

Agregao
Agregao uma forma especial de associao que significa um relacionamento todo/parte
(whole/part). A Figura 19-14 mostra como ela desenhada e implementada. Note que a
implementao mostrada na Figura 19-14 indistinguvel da associao. Isso um indcio.

Whole

Part

public class Whole


{
private Part itsPart;
}

Figura 19-14
Agregao.

Infelizmente, a UML no fornece uma definio segura para esse relacionamento. Isso
pode ser confuso, pois vrios programadores e analistas adotam suas definies favoritas
para o relacionamento. Por esse motivo, eu no uso o relacionamento e recomendo que voc
tambm o evite. De fato, esse relacionamento quase foi eliminado da UML 2.0.
A nica regra concreta que a UML nos fornece com relao s agregaes esta: um
todo no pode ser sua prpria parte. Portanto, instncias no podem formar ciclos de
agregaes. Um nico objeto no pode ser um agregado de si mesmo, dois objetos no podem ser agregados um do outro, trs objetos no podem formar um crculo de agregao
e assim por diante. Consulte a Figura 19-15.
No acho que essa seja uma definio til. Com que frequncia fico preocupado em
garantir que instncias formem um grafo acclico dirigido? Poucas vezes. Portanto, acho
esse relacionamento intil nos tipos de diagramas que desenho.

264

PROJETO GIL

X
Q

Figura 19-15
Ciclos invlidos de agregao entre instncias.

Composio
Composio uma forma especial de agregao, como mostrado na Figura 19-16. Novamente, note que a implementao indistinguvel da associao. Desta vez, entretanto, o
motivo que o relacionamento no tem muita utilidade em um programa C#. Por outro
lado, os programadores C++ encontram muitos usos para ele.

Owner

public class Owner


{
private Ward itsWard;
}

Ward

Figura 19-16
Composio.

A mesma regra que se aplica agregao se aplica tambm composio. No pode


haver ciclos de instncias. Um proprietrio (owner) no pode ser seu prprio guardio
(ward). Contudo, a UML fornece muito mais definio para a composio.
Uma instncia de guardio no pode pertencer simultaneamente a dois proprietrios. O diagrama de objeto da Figura 19-17 invlido. Contudo, note que o diagrama
de classes correspondente no invlido. Um proprietrio pode transferir a posse de
um guardio para outro proprietrio.

O1

O2

Figura 19-17
Composio invlida.

DIAGRAMAS DE CLASSES

265

O proprietrio responsvel pela durao do guardio. Se o proprietrio destrudo,


o guardio deve ser destrudo com ele. Se o proprietrio copiado, o guardio deve
ser copiado com ele.
Em C#, a destruio acontece nos bastidores por causa do coletor de lixo; portanto,
raramente h necessidade de gerenciar a durao de um objeto. Cpias profundas no
so desconhecidas, mas a necessidade de mostrar a semntica da cpia profunda em um
diagrama rara. Assim, embora eu tenha usado relacionamentos de composio para
descrever alguns programas em C#, tal uso raro.
A Figura 19-18 mostra como a composio utilizada para denotar cpia profunda. Temos uma classe chamada Address (endereo) que contm muitos objetos string.
Cada string contm uma linha do endereo. Claramente, quando voc faz uma cpia de
Address, quer que a cpia mude independentemente do original. Assim, precisamos fazer
uma cpia profunda. O relacionamento de composio entre Address e os objetos String
indica que as cpias precisam ser profundas.6

interface
ICloneable

Address

*
string

+SetLine(n,line)

itsLines

public class Address: ICloneable


{
private ArrayList itsLines = new ArrayList();
public void SetLine(int n, string line)
{
itsLines[n] = line;
}
public object Clone()
{
Address clone = (Address) this.MemberwiseClone();
clone.itsLines = (ArrayList) itsLines.Clone();
return clone;
}
}

Figura 19-18
Cpia profunda decorrente da composio.

Exerccio: Por que foi suficiente clonar a coleo itsLines? Por que no precisei clonar as instncias reais
de string?

266

PROJETO GIL

Multiplicidade
Os objetos podem conter arrays ou colees de outros objetos, ou podem conter muitos objetos do mesmo tipo em variveis de instncia separadas. Na UML, isso pode ser mostrado
colocando-se uma expresso de multiplicidade na extremidade da associao. As expresses
de multiplicidade podem ser nmeros simples, intervalos ou uma combinao de ambos. Por
exemplo, a Figura 19-19 mostra um BinaryTreeNode usando uma multiplicidade igual a 2.
public class BinaryTreeNode
{
private BinaryTreeNode leftNode;
private BinaryTreeNode rightNode;
}

2
BinaryTreeNode

Figura 19-19
Multiplicidade simples.
Aqui esto as formas de multiplicidade permitidas:
Dgito.

O nmero exato de elementos

* ou 0..*

De zero para muitos

0..1

Zero ou um, em Java, frequentemente implementado com uma


referncia que pode ser null

1..*

De um para muitos

3..5

De trs a cinco

0, 2..5, 9..*

Tolice, mas vlido

Esteretipos de associao
As associaes podem ser rotuladas com esteretipos que alteram seus significados. A
Figura 19-20 mostra os que eu utilizo com mais frequncia.
O esteretipo create indica que o destino da associao criado pela origem. A
implicao que a origem cria o destino e ento o passa para outras partes do sistema. No
exemplo, mostrei uma fbrica tpica*.
O esteretipo local usado quando a classe de origem cria uma instncia do destino e a mantm em uma varivel local. A implicao que a instncia criada no sobrevive
aps a funo membro que a cria ter terminado. Assim, ela no mantida por nenhuma
varivel de instncia nem passada para o sistema de algum modo.
O esteretipo parameter mostra que a classe de origem obtm acesso instncia
de destino por meio do parmetro de uma de suas funes membro. Novamente, a implicao que a origem se esquece completamente desse objeto quando a funo membro
retorna. O destino no salvo em uma varivel de instncia.

* N. de R.T.: O autor refere-se ao padro de projeto denominado Factory Method, que ser visto mais adiante.

DIAGRAMAS DE CLASSES

public class A {
public B MakeB() {
return new B();
}
}
public class A {
public void F() {
B b = new B();
// usa b
}
}

B
create

B
local

public class A {
public void F(B b) {
// usa b;
}
}

B
parameter

delegate

267

public class A {
private B itsB;
public void F() {
itsB.F();
}
}

Figura 19-20
Esteretipos de associao.
Usar setas de dependncia tracejadas, como mostra o diagrama, um idioma comum e conveniente para denotar parmetros. Eu normalmente prefiro isso a usar o esteretipo parameter.
O esteretipo delegate usado quando a classe de origem encaminha a chamada de
uma funo membro para o destino. Vrios padres de projeto aplicam essa tcnica: PROXY,
DECORATOR e COMPOSITE.7 Como utilizo muito esses padres, acho a notao til.

Classes aninhadas
As classes aninhadas so representadas na UML com uma associao adornada com um
crculo cruzado, como mostrado na Figura 19-21.

Figura 19-21
Classe aninhada.
7

[GOF95], p. 163, 175, 207.

public class A {
private class B {
...
}
}

268

PROJETO GIL

Classes associativas
As associaes com multiplicidade nos informam que a origem est ligada a muitas instncias do destino, mas o diagrama no nos informa que tipo de classe continer usado. Isso
pode ser representado pelo uso de uma classe associativa, como mostrado na Figura 19-22.
As classes associativas mostram como uma associao especfica implementada.
No diagrama, elas aparecem como uma classe normal ligada associao por uma linha
tracejada. Para ns, programadores C#, isso significa que a classe de origem contm uma
referncia para a classe de associao, a qual por sua vez contm referncias para o destino.
As classes associativas tambm podem ser escritas para conter instncias de algum outro objeto. s vezes, essas classes impem regras de negcio. Por exemplo, na
Figura 19-23, uma classe Company contm muitas instncias de Employee atravs de
EmployeeContracts. Nunca achei essa notao muito til, para dizer a verdade.

*
Address

string
itsLines

public class Address {


private ArrayList itsLines;
};
Array
List

Figura 19-22
Classe associativa.

itsEmployees
Company

Employee
*

public class Company {


private EmploymentContract[]
itsEmployees;
};

Employment
Contract

Figura 19-23
Contrato de trabalho (employment contract).

Qualificadores de associao
Os qualificadores de associao so usados quando a associao implementada por
meio de algum tipo de chave ou token, em vez de uma referncia normal da linguagem C#.
O exemplo da Figura 19-24 mostra um objeto LoginTransaction associado a um objeto
Employee. A associao intermediada por uma varivel membro chamada empid, a qual
contm a chave do banco de dados para Employee.

DIAGRAMAS DE CLASSES

Login
Transaction

empid

Employee

269

public class LoginTransaction {


private string empid;
public string Name() {
get {
Employee e=DB.GetEmp(empid);
return e.GetName();
}
}
}

Figura 19-24
Qualificador de associao.

Essa notao til em pouqussimas situaes. s vezes, conveniente mostrar


que um objeto est associado a outro por um banco de dados ou uma chave de dicionrio. No entanto, quem ler o diagrama deve saber como o qualificador usado para
acessar o objeto, o que no imediatamente evidente a partir da notao.

Concluso
A UML tem muitos elementos, adornos e artefatos. So tantos que voc pode dedicar parte
de sua vida a se tornar um advogado especialista em UML, fazendo o que de melhor os
advogados fazem: escrever documentos que ningum consegue entender.
Neste captulo, evitei a maioria dos recursos misteriosos e complexos da UML. Em
vez disso, mostrei as partes da UML que eu utilizo. Espero que, alm desse conhecimento,
voc tambm tenha aprendido o valor do minimalismo. Em UML, quase sempre menos
mais.

Bibliografia
[Booch94] Grady Booch, Object-Oriented Analysis and Design with Applications, 2d ed.
Addison-Wesley, 1994.
[GOF95] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995.

Captulo 20

HEURSTICAS E CAF

enho ensinado projeto orientado a objetos para desenvolvedores de software profissionais h 12 anos. Meus cursos so divididos em aulas de manh e exerccios
tarde. Para os exerccios, eu divido a classe em equipes e peo para que resolvam um
problema de projeto usando UML. Na manh seguinte, escolhemos uma ou duas equipes para apresentar suas solues em um quadro branco e criticamos seus projetos.

Em meus cursos, tenho notado que os alunos cometem sempre um mesmo conjunto
de erros de projetos. Este captulo apresenta alguns dos equvocos mais comuns, mostra
por que so erros e discute como podem ser corrigidos. Em seguida, resolve o problema
de modo a solucionar todas as necessidades de projeto habilmente.

A cafeteira eltrica Mark IV Special


Na primeira manh de uma aula de projeto orientado a objetos, eu apresento as definies
bsicas de classes, objetos, relacionamentos, mtodos, polimorfismo etc. Ao mesmo tempo, apresento tambm os fundamentos da UML. Assim, os alunos aprendem os conceitos
fundamentais, o vocabulrio e as ferramentas do projeto orientado a objetos.
Durante a tarde, dou turma o seguinte exerccio para trabalhar: projete o software que controla uma cafeteira eltrica simples. Aqui est a especificao que forneo
a eles.1

Esse problema est em meu primeiro livro: [Martin1995], p. 60.

272

PROJETO GIL

Especificao
A cafeteira Mark IV Special produz at 12 xcaras de caf por vez. O usurio coloca um
filtro no porta-filtro, enche o filtro com gros de caf e desliza o porta-filtro para seu receptculo. Ento, o usurio despeja at 12 xcaras de gua no respectivo receptculo e
pressiona o boto Brew (Preparar). A gua aquecida at ferver. A presso do vapor fora
a gua a borrifar nos gros de caf e o caf goteja na jarra, passando pelo filtro. A jarra
mantida aquecida por perodos prolongados por meio de uma chapa de aquecimento,
a qual s ligada se houver caf na jarra. Se a jarra retirada da chapa de aquecimento
enquanto a gua est sendo borrifada nos gros, o fluxo de gua interrompido para que
o caf filtrado no seja derramado na chapa. Os seguintes itens de hardware precisam ser
monitorados ou controlados:
A resistncia do boiler. Ela pode ser ligada ou desligada.
A resistncia da chapa de aquecimento. Ela pode ser ligada ou desligada.
O sensor da chapa de aquecimento. Ele tem trs estados: warmerEmpty, potEmpty,
potNotEmpty (aquecedor vazio, jarra vazia e jarra no vazia, respectivamente).
Um sensor para o boiler, o qual determina se ele contm gua. Ele tem dois estados:
boilerEmpty ou boilerNotEmpty (boiler vazio, boiler no vazio).
O boto Brew. Esse boto momentneo inicia o ciclo de preparao. Ele tem um indicador que acende quando o ciclo de preparao termina e o caf est pronto.
Uma vlvula de alvio de presso que se abre para reduzir a presso no boiler. A
queda de presso interrompe o fluxo de gua no filtro. A vlvula pode ser aberta ou
fechada.
O hardware da Mark IV foi projetado e atualmente est em desenvolvimento. Os
engenheiros de hardware forneceram uma API de baixo nvel para utilizarmos, de modo
que no precisamos escrever nenhum cdigo de driver de E/S trabalhoso. O cdigo dessas
funes de interface est mostrado na Listagem 20-1. Se esse cdigo parece estranho para
voc, lembre-se de que foi escrito por engenheiros de hardware.

Listagem 20-1
CoffeeMakerAPI.cs
namespace CoffeeMaker
{
public enum WarmerPlateStatus
{
WARMER_EMPTY,
POT_EMPTY,
POT_NOT_EMPTY
};
public enum BoilerStatus
{
EMPTY,NOT_EMPTY
};
public enum BrewButtonStatus

HEURSTICAS E CAF

{
PUSHED,NOT_PUSHED
};
public enum BoilerState
{
ON,OFF
};
public enum WarmerState
{
ON,OFF
};
public enum IndicatorState
{
ON,OFF
};
public enum ReliefValveState
{
OPEN, CLOSED
};
public interface CoffeeMakerAPI
{
/*
* Esta funo retorna o status do sensor da chapa de aquecimento.
* Esse sensor detecta a presena da jarra
* e se ela contm caf.
*/
WarmerPlateStatus GetWarmerPlateStatus();
/*
* Esta funo retorna o status do interruptor do boiler.
* Trata-se de um sensor por flutuao que detecta se
* h mais de 1/2 xcara de gua no boiler.
*/
BoilerStatus GetBoilerStatus();
/*
* Esta funo retorna o status do boto de preparo.
* O boto de preparo um interruptor momentneo que recorda
* seu estado. Cada chamada para essa funo retorna o
* estado recordado e, ento, redefine esse estado como
* NOT_PUSHED (no pressionado).
*
* Assim, mesmo que essa funo seja sondada em uma velocidade
* muito lenta, ela ainda detectar quando o boto de preparo
* est pressionado.
*/
BrewButtonStatus GetBrewButtonStatus();

273

274

PROJETO GIL

/*
* Esta funo liga ou desliga a resistncia de aquecimento no
* boiler.
*/
void SetBoilerState(BoilerState s);
/*
* Esta funo liga ou desliga a resistncia na
* chapa de aquecimento.
*/
void SetWarmerState(WarmerState s);
/*
* Esta funo liga ou desliga a luz do indicador.
* A luz do indicador deve ser ligada no final
* do ciclo de preparao. Ela deve ser desligada quando
* o usurio pressionar o boto de preparo.
*/
void SetIndicatorState(IndicatorState s);
/*
* Esta funo abre e fecha a vlvula de alvio de presso.
* Quando essa vlvula for fechada, a presso do vapor no
* boiler forar a gua quente a borrifar sobre
* o filtro de caf. Quando a vlvula for aberta, o vapor
* no boiler escapar para o ambiente e a gua
* do boiler no ser borrifada no filtro.
*/
void SetReliefValveState(ReliefValveState s);
}
}

Se quiser um desafio, pare de ler aqui e tente projetar esse software. Lembre-se de que
voc est projetando o software de um sistema embarcado de tempo real simples. O que
espero de meus alunos um conjunto de diagramas de classe, diagramas de sequncia e
mquinas de estado.

Uma soluo comum, mas horrvel


A soluo mais comum apresentada por meus alunos a da Figura 20-1. Nesse diagrama, a classe central CoffeeMaker circundada por subordinadas que controlam
os diversos dispositivos. A classe CoffeeMaker contm um objeto Boiler, um WarmerPlate, um Button e um Light (respectivamente, boiler, chapa de aquecimento,

HEURSTICAS E CAF

275

boto e luz). Boiler contm um objeto BoilerSensor e um objeto BoilerHeater


(respectivamente, sensor do boiler e aquecedor do boiler). WarmerPlate contm um
objeto PlateSensor e um objeto PlateHeater (respectivamente, sensor da chapa e
aquecedor da chapa). Por fim, duas classes base, Sensor e Heater, atuam como pais
dos elementos Boiler e WarmerPlate, respectivamente.
Para os iniciantes difcil avaliar o quanto essa estrutura horrvel. Muitos erros
graves esto ocultos nesse diagrama. Muitos deles no seriam notados at que voc tentasse codificar esse projeto e descobrisse que o cdigo absurdo.
Antes de entrarmos nos problemas do projeto em si, vamos ver os problemas da
maneira como a UML criada.
Mtodos ausentes O maior problema que a Figura 20-1 apresenta a completa ausncia de mtodos. Estamos escrevendo um programa, e os programas consistem em comportamento! Onde est o comportamento nesse diagrama?
Quando criam diagramas sem mtodos, os projetistas podem estar dividindo o software em algo que no comportamento. As divises que no so baseadas em comportamento quase sempre so erros significativos. O comportamento de um sistema o primeiro indcio de como o software deve ser dividido.
Classes vapor Se considerarmos os mtodos que poderamos colocar na classe Light,
poderemos ver quanto esse projeto est mal dividido. Claramente, o objeto Light quer
ser ligado ou desligado. Assim, poderamos colocar um mtodo On() e um mtodo Off()
na classe Light. Como seria a implementao dessa funo? Consulte a Listagem 20-2.

Listagem 20-2
Light.cs
public class Light {
public void On() {
CoffeeMaker.api.SetIndicatorState(IndicatorState.ON);
}
public void Off() {
CoffeeMaker.api.SetIndicatorState(IndicatorState.OFF);
}
}

276

PROJETO GIL

Light

Button

CoffeeMaker

Boiler

BoilerSensor

CoffeeMaker

PlateHeater

BoilerHeater

PlateSensor

Heater

Sensor

Figura 20-1
Cafeteira eltrica hiperconcreta.
A classe Light tem algumas peculiaridades. Primeiro, ela no tem variveis. Isso
estranho, pois um objeto normalmente manipula algum tipo de estado. Alm disso, os
mtodos On() e Off() simplesmente delegam para o mtodo SetIndicatorState de
CoffeeMakerAPI. Aparentemente, a classe Light nada mais do que um transformador
de chamadas e nada est fazendo de til.
Esse mesmo raciocnio pode ser aplicado s classes Button, Boiler e WarmerPlate. Elas nada mais so do que adaptadoras que transformam uma chamada de
funo de uma forma para outra. Alis, elas poderiam ser completamente retiradas
do projeto, sem alterar a lgica da classe CoffeeMaker. Essa classe teria apenas que
chamar CoffeeMakerAPI diretamente, em vez de chamar por meio das adaptadoras.
Considerando os mtodos e, ento, o cdigo, rebaixamos essas classes da posio
eminente de continer da Figura 20-1, para simples espaos reservados sem muita razo
para existir. Por isso, eu as chamo de classes vapor.

HEURSTICAS E CAF

277

Abstrao imaginria
Observe as classes base Sensor e Heater da Figura 20-1. A seo anterior deve t-lo
convencido de que suas derivadas eram puro vapor, mas e quanto s classes base em
si? Superficialmente, elas parecem fazer sentido, mas parece no haver lugar para suas
derivadas.
Abstraes so complicadas. Ns, seres humanos, as vemos por toda parte, mas
muitas no so adequadas para serem transformadas em classes base. Essas, em particular, no tm lugar nesse projeto. Para garantir, pergunte-se: quem as utiliza?
Nenhuma classe no sistema utiliza a classe Sensor ou a classe Heater. Se ningum
as utiliza, que motivo elas tm de existir? s vezes, podemos tolerar uma classe base que
ningum usa, caso fornea algum cdigo comum para suas derivadas, mas essas classes
base no contm qualquer cdigo. Na melhor das hipteses, seus mtodos so abstratos.
Considere, por exemplo, a interface Heater da Listagem 20-3. Uma classe sem nada a no
ser funes abstratas e que nenhuma outra classe utiliza oficialmente intil.

Listagem 20-3
Heater.cs
public interface Heater {
void TurnOn();
void TurnOff();
}

A classe Sensor (Listagem 20-4) pior! Assim como Heater, ela tem mtodos abstratos e nenhum usurio. O pior que o valor de retorno de seu nico mtodo ambguo.
O que o mtodo Sense() retorna? Em BoilerSensor, ele retorna dois valores possveis,
mas em WarmerPlateSensor retorna trs valores possveis. Em resumo, no podemos
especificar o contrato de Sensor na interface. O melhor que podemos fazer dizer que
esses sensores podem retornar valores int. Isso muito pouco.
O que aconteceu aqui foi que lemos a especificao, encontramos um monte de substantivos provveis, fizemos algumas inferncias a respeito de seus relacionamentos e, ento, criamos um diagrama UML com base nesse raciocnio. Se aceitssemos essas decises como uma arquitetura e as implementssemos da maneira como esto, acabaramos
com uma classe todo-poderosa CoffeeMaker circundada por subordinadas vaporosas.
Poderamos igualmente program-la em C!

Listagem 20-4
Sensor.cs
public interface Sensor {
int Sense();
}

278

PROJETO GIL

Classes deusas
Todo mundo sabe que classes deusas so uma m ideia. Voc no
deve concentrar toda a inteligncia de um sistema em um nico objeto ou em uma nica
funo. Dois dos objetivos do projeto orientado a objetos so a diviso e a distribuio
de comportamento em muitas classes e muitas funes. No entanto, muitos modelos de
objeto que parecem ser distribudos so na verdade redutos de classes deusas. A Figura
20-1 um bom exemplo. primeira vista, muitas classes parecem ter um comportamento interessante. Mas, medida que nos aprofundamos no cdigo que implementaria
essas classes, descobrimos que apenas uma delas, CoffeeMaker, tem comportamento
interessante; as restantes so todas abstraes imaginrias ou classes vapor.

Uma soluo melhorada


A soluo do problema da cafeteira eltrica um interessante exerccio de abstrao. A
maioria dos desenvolvedores iniciantes na OO fica bastante surpresa com o resultado.
O truque para resolver esse (ou qualquer outro) problema retroceder e separar os
detalhes essenciais. Esquea os boilers, vlvulas, aquecedores, sensores e todos os pequenos detalhes; concentre-se no problema subjacente. Qual esse problema? O problema :
como voc faz caf?
Como voc faz caf? A soluo mais comum e mais simples para esse problema
gotejar gua quente sobre gros de caf e coletar a infuso resultante em algum tipo de
recipiente. De onde obtemos a gua quente? Vamos chamar isso de HotWaterSource (fonte de gua quente). Onde coletamos o caf? Vamos chamar isso de ContainmentVessel
(recipiente de conteno).2
So essas duas classes de abstraes? Um objeto HotWaterSource tem um comportamento que poderia ser capturado no software? Um objeto ContainmentVessel faz algo
que o software possa controlar? Se pensarmos sobre a unidade Mark IV, podemos imaginar o boiler, a vlvula e o sensor do boiler desempenhando o papel de HotWaterSource.
O objeto HotWaterSource seria responsvel por aquecer a gua e distribu-la pelos gros
de caf para gotejar no objeto ContainmentVessel. Tambm poderamos imaginar a chapa de aquecimento e seu sensor desempenhando o papel do objeto ContainmentVessel.
Ele seria responsvel por manter o caf aquecido e por permitir que saibamos se restou
caf no recipiente.
Como voc capturaria a discusso anterior em um diagrama UML? A Figura 20-2
mostra um possvel esquema. HotWaterSource e ContainmentVessel so representados como classes e so associados pelo fluxo de caf (coffee flow).

Esse nome particularmente adequado para o tipo de caf que eu gosto de tomar.

HEURSTICAS E CAF

279

A associao mostra um erro comumente cometido pelos iniciantes em OO. A associao feita com algo fsico em relao ao problema, em vez do controle do comportamento do software. O fato de o caf fluir de HotWaterSource para ContainmentVessel
completamente irrelevante para a associao entre essas duas classes.
Por exemplo, e se o software de ContainmentVessel informasse a HotWaterSource
quando deveria iniciar e interromper o fluxo de gua quente para o recipiente? Isso poderia
ser representado como mostrado na Figura 20-3. Note que ContainmentVessel est enviando a mensagem Start (iniciar) para HotWaterSource. Isso significa que a associao
da Figura 20-2 est invertida. HotWaterSource no depende de ContainmentVessel. Em
vez disso, ContainmentVessel depende de HotWaterSource.

Hot Water
Source

Coffee Flow

Containment
Vessel

Figura 20-2
Linhas cruzadas.

start
HotWater
Source

Containment
Vessel

Figura 20-3
Iniciando o fluxo de gua quente.
A lio aqui simplesmente esta: as associaes so os caminhos pelos quais as
mensagens so enviadas entre os objetos. Elas nada tm a ver com o fluxo de objetos fsicos. O fato de a gua quente fluir do boiler para a jarra no significa que deve haver uma
associao de HotWaterSource para ContainmentVessel.
Chamo esse erro especfico de linhas cruzadas, porque as linhas entre as classes se
cruzaram entre os domnios lgico e fsico.
A interface do usurio da cafeteira eltrica
Deve ser evidente que est faltando algo
em nosso modelo de cafeteira eltrica. Temos um objeto HotWaterSource e um objeto
ContainmentVessel, mas nenhuma maneira para um ser humano interagir com o sistema. Em algum lugar, nosso sistema precisa detectar os comandos de um ser humano.
Do mesmo modo, o sistema deve ser capaz de relatar seu status para seus proprietrios
humanos. Certamente, a Mark IV tinha hardware dedicado a esse propsito. O boto e a
luz serviam como interface do usurio.
Assim, adicionaremos uma classe UserInterface em nosso modelo de cafeteira
eltrica. Isso nos dar uma trade de classes interagindo, para fazer caf sob a orientao
de um usurio.
Caso de uso 1: O usurio pressiona o boto de preparo Certo, dadas essas trs classes,
como suas instncias se comunicam? Vamos examinar vrios casos de uso para ver se
podemos descobrir qual o comportamento dessas classes.
Qual de nossos objetos detecta o fato de que o usurio pressionou o boto Brew?
Claramente, deve ser o objeto UserInterface. O que esse objeto deve fazer quando o
boto Brew pressionado?

280

PROJETO GIL

IsReady
HotWater
Source

User
Interface

Containment
Vessel
IsReady

Figura 20-4
Boto Brew pressionado, verificando se est pronto.
Nosso objetivo iniciar o fluxo de gua quente. Contudo, antes de podermos fazer
isso, melhor garantirmos que o objeto ContainmentVessel esteja pronto para aceitar
caf. Tambm seria melhor garantirmos que o objeto HotWaterSource esteja pronto. Se
pensarmos sobre a Mark IV, estamos garantindo que o boiler esteja cheio e que a jarra
esteja vazia e posicionada no aquecedor.
Portanto, primeiro o objeto UserInterface envia uma mensagem para HotWaterSource e para ContainmentVessel, a fim de saber se eles esto prontos. Isso est mostrado na Figura 20-4.
Se uma dessas consultas retornar false, nos recusamos a comear a preparar o
caf. O objeto UserInterface pode alertar o usurio de que seu pedido foi negado. No
caso da Mark IV, poderamos fazer a luz piscar algumas vezes.
Se as duas consultas retornarem true, precisamos iniciar o fluxo de gua quente.
O objeto UserInterface provavelmente deve enviar uma mensagem Start para HotWaterSource. Ento, o objeto HotWaterSource comear a fazer o que for necessrio para
que a gua quente flua. No caso da Mark IV, ele fechar a vlvula e ligar o boiler. A Figura
20-5 mostra o cenrio completo.
Caso de uso 2: O recipiente de conteno no est pronto Na Mark IV, sabemos que o
usurio pode tirar a jarra do aquecedor enquanto o caf est sendo preparado. Qual de
nossos objetos detectaria o fato de que a jarra foi retirada? Certamente, seria o objeto
1:IsReady
User
Interface

HotWater
Source
3:Start

Containment
Vessel
2:IsReady

Figura 20-5
Boto Brew pressionado, completo.

HEURSTICAS E CAF

281

ContainmentVessel. Os requisitos da Mark IV nos informam que, quando isso acontece,


precisamos interromper o fluxo de caf. Assim, o objeto ContainmentVessel deve ser
capaz de dizer a HotWaterSource para que pare de enviar gua quente. Do mesmo modo,
precisa ser capaz de dizer a ele para que comece novamente quando a jarra for recolocada. A Figura 20-6 acrescenta os novos mtodos.
Caso de uso 3: Preparo concludo
Em algum ponto, terminaremos o preparo do caf e
teremos de desligar o fluxo de gua quente. Qual de nossos objetos sabe quando o preparo
est concludo? No caso da Mark IV, o sensor do boiler nos informa que este est vazio, de
modo que nosso objeto HotWaterSource detectaria isso. Contudo, no difcil imaginar
uma cafeteira eltrica na qual o objeto ContainmentVessel detectaria que o preparo
estaria terminado. Por exemplo, e se nossa cafeteira eltrica fosse ligada ao encanamento
e, portanto, tivesse um fornecimento de gua infinito? E se um gerador de micro-ondas
aquecesse a gua medida que ela flusse pelo encanamento em um recipiente com isolamento trmico?3 E se esse recipiente tivesse uma torneira por meio da qual os usurios
pegassem o caf? Nesse caso, um sensor no recipiente saberia se estaria cheio e se a gua
quente deveria ser desligada.
No domnio abstrato de HotWaterSource e ContainmentVessel nenhum deles
um candidato convincente para detectar a concluso do preparo. Minha soluo para isso
ignorar o problema. Vou supor que um ou outro objeto pode informar aos outros que o
preparo est concludo.
Quais objetos em nosso modelo precisam saber que o preparo est concludo? O
objeto UserInterface com certeza, pois na Mark IV ele precisa acender a luz. Tambm
deve estar claro que o objeto HotWaterSource precisa saber que o preparo terminou, pois
ele precisar interromper o fluxo de gua quente. Na Mark IV, ele desligar o boiler e abrir a vlvula. O objeto ContainmentVessel precisa saber que o preparo est concludo?
ContainmentVessel precisa fazer ou monitorar algo especial quando o preparo tiver terminado? Na Mark IV, ele vai detectar uma jarra vazia sendo colocada de volta na chapa,
sinalizando que o usurio serviu o ltimo caf. Isso faz a Mark IV desligar a luz. Portanto,
sim, o objeto ContainmentVessel precisa saber que o preparo est terminado. Alis, o
mesmo argumento pode ser usado para dizer que UserInterface deve enviar a mensagem Start para ContainmentVessel quando o preparo comear. A Figura 20-7 mostra as
novas mensagens. Note que mostrei que HotWaterSource ou ContainmentVessel podem
enviar a mensagem Done (pronto).
1a:IsReady
User
Interface

HotWater
Source
3a:Start
1b: Pause
2b: Resume
Containment
Vessel

2a:IsReady

Figura 20-6
Fazendo uma pausa e retomando o fluxo de gua quente.

Certo, estou me divertindo um pouco. Mas, e se?

282

PROJETO GIL

2c: Done
1a:IsReady
HotWater
Source

User
Interface
3a:Start
2d: Done

1c: Done

1b: Pause
2b: Resume

Containment
Vessel
2a:IsReady

1d: Done

Figura 20-7
Detectando quando o preparo est concludo.
Caso de uso 4: Todo o caf consumido A Mark IV apaga a luz quando o preparo est terminado e uma jarra vazia colocada na chapa. Claramente, em nosso modelo de objetos
ContainmentVessel que deve detectar isso. Ele ter que enviar uma mensagem Complete
para UserInterface. A Figura 20-8 mostra o diagrama de colaborao completo.
A partir desse diagrama, podemos desenhar um diagrama de classes com todas as
associaes intactas. Esse diagrama no contm surpresas. Voc pode v-lo na Figura 20-9.

Implementando o modelo abstrato


Nosso modelo de objetos est razoavelmente bem dividido. Temos trs reas de responsabilidade distintas e cada uma parece estar enviando e recebendo mensagens de modo
equilibrado. Parece no haver um objeto deus em nenhum lugar. Tambm no parece
haver quaisquer classes vapor.
At aqui, tudo bem, mas como implementamos a Mark IV nessa estrutura? Simplesmente implementamos os mtodos dessas trs classes para chamar CoffeeMakerAPI?
Isso seria uma vergonha! Capturamos a essncia do que necessrio para fazer caf. O
projeto seria deficiente se agora vinculssemos essa essncia Mark IV.
2c: Done
1a:IsReady
User
Interface

HotWater
Source
3a:Start

2d: Done

1e: Complete

1c: Done

Containment
Vessel
2a:IsReady

1d: Done

4a:Start

Figura 20-8
Todo o caf consumido.

1b: Pause
2b: Resume

HEURSTICAS E CAF

283

Hot Water
Source

User Interface

Containment
Vessel

Figura 20-9
Diagrama de classes.
Na verdade, vou estabelecer uma regra agora mesmo. Nenhuma das trs classes que
criamos deve saber qualquer coisa sobre a Mark IV. Esse o Princpio da Inverso de
Dependncia (DIP). No vamos permitir que a diretiva de alto nvel para fazer caf desse
sistema dependa da implementao de baixo nvel.
Certo, mas como criaremos a implementao da Mark IV? Vamos ver todos os casos
de uso novamente. Desta vez, vamos examin-los do ponto de vista da Mark IV.
Caso de uso 1: O usurio pressiona o boto Brew Como a interface do usurio (UserInterface) sabe que o boto Brew foi pressionado? Claramente, ele deve chamar a funo
CoffeeMakerAPI.GetBrewButtonStatus(). Onde deve chamar essa funo? J decretamos que a classe UserInterface em si no pode saber nada sobre CoffeeMakerAPI.
Ento, onde fica essa chamada?
Aplicaremos o DIP e colocaremos a chamada em uma derivada de UserInterface.
Consulte a Figura 20-10 para ver os detalhes.
Derivamos M4UserInterface de UserInterface e colocamos um mtodo CheckButton() em M4UserInterface. Quando essa funo for chamada, ela chamar a funo CoffeeMakerAPI.GetBrewButtonStatus(). Se o boto tiver sido pressionado (PUSHED), a funo chamar o mtodo protegido StartBrewing() de UserInterface. As
listagens 20-5 e 20-6 mostram como isso seria codificado.
User Interface

Hot Water
Source

# StartBrewing

M4UserInterface
+ CheckButton

Containment
Vessel

Figura 20-10
Detectando o boto Brew.

284

PROJETO GIL

Listagem 20-5
M4UserInterface.cs
public class M4UserInterface: UserInterface
{
private void CheckButton()
{
BrewButtonStatus status =
CoffeeMaker.api.GetBrewButtonStatus();
if (status == BrewButtonStatus.PUSHED)
{
StartBrewing();
}
}
}

Listagem 20-6
UserInterface.cs
public class UserInterface
{
private HotWaterSource hws;
private ContainmentVessel cv;
public void Done() {}
public void Complete() {}
protected void StartBrewing()
{
if (hws.IsReady() && cv.IsReady())
{
hws.Start();
cv.Start();
}
}
}

Talvez voc esteja se perguntando por que eu criei o mtodo protegido StartBrewing(). Por que no chamei simplesmente as funes Start() de M4UserInterface? O motivo simples, mas significativo. Os testes IsReady() e as consequentes
chamadas dos mtodos Start() de HotWaterSource e de ContainmentVessel so
diretivas de alto nvel que a classe UserInterface deve possuir. Esse cdigo vlido

HEURSTICAS E CAF

285

independentemente de estarmos implementando uma Mark IV e, portanto, no deve


ser acoplado derivada de Mark IV. Esse outro exemplo do Princpio da Responsabilidade nica (SRP). Eu fao essa mesma distino vrias vezes neste exemplo. Eu mantenho o mximo de cdigo possvel nas classes de alto nvel. O nico cdigo que coloco
nas derivadas aquele direta e inextricavelmente associado Mark IV.
Implementando as funes IsReady( )
Como os mtodos IsReady() de HotWaterSource e de ContainmentVessel so implementados? Deve estar claro que esses so
os nicos mtodos abstratos e que, portanto, essas classes so classes abstratas. As
derivadas correspondentes M4HotWaterSource e M4ContainmentVessel as implementaro chamando as funes CoffeeMakerAPI apropriadas. A Figura 20-11 mostra a nova
estrutura e as listagens 20-7 e 20-8 mostram a implementao das duas derivadas.
{A}

Hot Water
Source

User Interface
# StartBrewing

+ {A} IsReady()

{A}
Containment
Vessel
M4UserInterface
+ {A} IsReady()
+ CheckButton

M4Containment
Vessel

Figura 20-11
Implementando os mtodos isReady.

Listagem 20-7
M4HotWaterSource.cs
public class M4HotWaterSource: HotWaterSource
{
public override bool IsReady()
{
BoilerStatus status =
CoffeeMaker.api.GetBoilerStatus();
return status == BoilerStatus.NOT_EMPTY;
}
}

M4HotWater
Source

286

PROJETO GIL

Listagem 20-8
M4ContainmentVessel.cs
public class M4ContainmentVessel: ContainmentVessel
{
public override bool IsReady()
{
WarmerPlateStatus status =
CoffeeMaker.api.GetWarmerPlateStatus();
return status == WarmerPlateStatus.POT_EMPTY;
}
}

Implementando as funes Start()


O mtodo Start() de HotWaterSource simplesmente um mtodo abstrato implementado por M4HotWaterSource para chamar
as funes de CoffeeMakerAPI que fecham a vlvula e ligam o boiler. Quando escrevi
essas funes, comecei a ficar cansado de todas as estruturas CoffeeMaker.api.XXX
que estava escrevendo; portanto, fiz uma pequena refatorao ao mesmo tempo. O resultado est na Listagem 20-9.

Listagem 20-9
M4HotWaterSource.cs
public class M4HotWaterSource: HotWaterSource
{
private CoffeeMakerAPI api;
public M4HotWaterSource(CoffeeMakerAPI api)
{
this.api = api;
}
public override bool IsReady()
{
BoilerStatus status = api.GetBoilerStatus();
return status == BoilerStatus.NOT_EMPTY;
}
public override void Start()
{
api.SetReliefValveState(ReliefValveState.CLOSED);
api.SetBoilerState(BoilerState.ON);
}
}

HEURSTICAS E CAF

287

Listagem 20-10
M4ContainmentVessel.cs
public class M4ContainmentVessel: ContainmentVessel
{
private CoffeeMakerAPI api;
private bool isBrewing = false;
public M4ContainmentVessel(CoffeeMakerAPI api)
{
this.api = api;
}
public override bool IsReady()
{
WarmerPlateStatus status = api.GetWarmerPlateStatus();
return status == WarmerPlateStatus.POT_EMPTY;
}
public override void Start()
{
isBrewing = true;
}
}

O mtodo Start() de ContainmentVessel um pouco mais interessante. A


nica ao que M4ContainmentVessel precisa executar lembrar-se do estado do
preparo do sistema. Conforme veremos mais adiante, isso permitir que ele responda
corretamente quando jarras forem colocadas ou retiradas da chapa. A Listagem 20-10
mostra o cdigo.
Chamando M4UserInterface.CheckButton
Como o fluxo de controle chega em um
lugar no qual a funo CoffeeMakerAPI.GetBrewButtonStatus() possa ser chamada? A propsito, como o fluxo de controle chega onde qualquer um dos sensores possa
ser detectado?
Muitas equipes que tentam resolver esse problema ficam completamente travadas
nesse ponto. Algumas no querem supor que existe um sistema operacional multitarefa na cafeteira eltrica e, assim, utilizam uma estratgia de sondagem (polling) para
os sensores. Outras querem usar multitarefas para que no tenham que se preocupar
com sondagem. Tenho visto esse argumento em particular ir e voltar por uma hora ou
mais em algumas equipes.
O erro dessas equipes que eu finalmente aponto, depois de deix-las suar um pouco que a escolha entre multitarefa e sondagem totalmente irrelevante. Essa deciso
pode ser tomada no ltimo minuto, sem prejudicar o projeto. Portanto, sempre melhor supor que as mensagens podem ser enviadas de forma assncrona, como se fossem
threads independentes, e depois colocar a sondagem ou a multitarefa no ltimo minuto.

288

PROJETO GIL

At aqui, o projeto sups que, de algum modo, o fluxo de controle entrar de forma
assncrona no objeto M4UserInterface para que ele possa chamar CoffeeMakerAPI.
GetBrewButtonStatus(). Agora, vamos supor que estamos trabalhando em uma plataforma mnima que no suporta mltiplas threads. Isso significa que vamos ter de sondar.
Como fazemos isso funcionar?
Considere a interface Pollable da Listagem 20-11. Essa interface tem apenas
um mtodo Poll(). E se M4UserInterface implementasse essa interface? E se o
programa Main() travasse em um loop incondicional, chamando esse mtodo repetidamente? O fluxo de controle entraria repetidas vezes em M4UserInterface e poderamos detectar o boto Brew.
Alis, podemos repetir esse padro para todas as trs derivadas M4. Cada uma
tem seus prprios sensores que precisa verificar. Portanto, como mostrado na Figura
20-12, podemos extrair todas as derivadas M4 de Pollable e chamar todas elas a
partir de Main().

Listagem 20-11
Pollable.cs
public interface Pollable
{
void Poll();
}

{A}

User Interface

Hot Water
Source

# StartBrewing
+{A}IsReady( )
{A}

M4UserInterface
+ CheckButton

Containment
Vessel
+{A}IsReady( )

M4Containment
Vessel

<<interface>>
Pollable
+ poll( )

Figura 20-12
Cafeteira eltrica com sondagem.

M4HotWater
Source

HEURSTICAS E CAF

289

A Listagem 20-12 mostra como poderia ser a funo Main. Ela colocada em uma
classe chamada M4CoffeeMaker. A funo Main() cria a verso implementada da api e,
ento, cria os trs componentes M4. Ela chama funes Init() para interligar os componentes. Por fim, ela permanece em um loop infinito, chamando Poll() em cada um dos
componentes por sua vez.

Listagem 20-12
M4CoffeeMaker.cs
public static void Main(string[] args)
{
CoffeeMakerAPI api = new M4CoffeeMakerAPI();
M4UserInterface ui = new M4UserInterface(api);
M4HotWaterSource hws = new M4HotWaterSource(api);
M4ContainmentVessel cv = new M4ContainmentVessel(api);
ui.Init(hws,cv);
hws.Init(ui, cv);
cv.Init(hws,ui);
while (true)
{
ui.Poll();
hws.Poll();
cv.Poll();
}
}

Listagem 20-13
M4UserInterface.cs
public class M4UserInterface: UserInterface, Pollable
{
private CoffeeMakerAPI api;
public M4UserInterface(CoffeeMakerAPI api)
{
this.api = api;
}
public void Poll()
{
BrewButtonStatus status = api.GetBrewButtonStatus();
if (status == BrewButtonStatus.PUSHED)
{
StartBrewing();
}
}
}

290

PROJETO GIL

Agora deve estar claro como a funo M4UserInterface.CheckButton() chamada. Alis, deve estar claro que essa funo no chamada CheckButton(). Ela chamada Poll(). A Listagem 20-13 mostra como M4UserInterface fica agora.
Completando a cafeteira eltrica O raciocnio usado nas sees anteriores pode ser repetido para cada um dos outros componentes da cafeteira eltrica. O resultado aparece
nas listagens 20-14 a 20-21.

As vantagens desse projeto


Apesar da natureza simples do problema, esse projeto exibe algumas caractersticas muito
interessantes. A Figura 20-13 mostra a estrutura. Tracei uma linha em torno das trs classes abstratas. Essas classes contm a diretiva de alto nvel da cafeteira eltrica. Note que
todas as dependncias que cruzam a linha apontam para dentro. Nada dentro da linha
depende de algo de fora. Assim, as abstraes so completamente separadas dos detalhes.
As classes abstratas nada sabem sobre botes, luzes, vlvulas, sensores ou quaisquer outros elementos detalhados da cafeteira eltrica. De maneira similar, as derivadas
so dominadas por esses detalhes.
Note que as trs classes abstratas poderiam ser reutilizadas para se fazer muitos
tipos diferentes de mquinas de caf. Poderamos utiliz-las facilmente em uma mquina
de caf que estivesse ligada ao encanamento e usasse um tanque e uma torneira. Parece
provvel que tambm poderamos utiliz-las para uma mquina de venda automtica de
caf. Alis, acho que poderamos utiliz-las em um fervedor de ch automtico ou mesmo
em uma fazedora de canja de galinha. Essa segregao entre diretiva de alto nvel e detalhe
a essncia do projeto orientado a objetos.

Hot Water
Source {A}

User Interface
{A}

Containment
Vessel

{A}

M4UserInterface

M4HotWater
Source

+ CheckButton

M4Containment
Vessel

<<interface>>
Pollable

Figura 20-13
Componentes da cafeteira eltrica.

HEURSTICAS E CAF

291

As origens desse projeto Eu no me sentei um belo dia e simplesmente desenvolvi esse


projeto de maneira simples e direta. Alis, em 1993, meu primeiro projeto para a cafeteira
eltrica era muito mais parecido com a Figura 20-1. Contudo, escrevi muitas vezes sobre
esse problema e o utilizei como exerccio em inmeras aulas. Assim, esse projeto foi refinado com o passar do tempo.
O cdigo foi criado, com teste primeiro, usando os testes de unidade da Listagem
20-22. Criei o cdigo baseado na estrutura da Figura 20-13, mas o montei gradativamente,
um caso de teste falho por vez.4
No estou convencido de que os casos de teste esto completos. Se esse fosse mais do
que um exemplo de programa, eu teria feito uma anlise mais exaustiva dos casos de teste.
Contudo, achei que tal anlise teria sido exagero para este livro.

ExagerOO
Este exemplo tem certas vantagens pedaggicas. Ele pequeno, fcil de entender e mostra como os princpios do projeto orientado a objetos podem ser usados para gerenciar
dependncias e separar preocupaes. Por outro lado, sua pequeneza significa que as
vantagens dessa separao provavelmente no compensam os custos.
Se fssemos escrever a cafeteira eltrica Mark IV como uma FSM, veramos que
ela teria sete estados e 18 transies.5 Poderamos codificar isso em 18 linhas de cdigo
SMC. Um loop principal simples que sondasse os sensores teria outras dez linhas mais ou
menos, e as funes de ao que a FSM chamaria teriam mais uma dezena. Em resumo,
poderamos escrever o programa inteiro em menos de uma pgina de cdigo.
Se no contarmos os testes, a soluo OO da cafeteira eltrica tem cinco pginas de
cdigo. No h como justificarmos essa disparidade. Em aplicativos maiores, as vantagens
do gerenciamento de dependncias e da separao de preocupaes claramente compensam os custos do projeto orientado a objetos. Nesse exemplo, no entanto, mais provvel
que o inverso seja verdadeiro.

Listagem 20-14
UserInterface.cs
using System;
namespace CoffeeMaker
{
public abstract class UserInterface
{
private HotWaterSource hws;
private ContainmentVessel cv;
protected bool isComplete;

4
5

[Beck2002]
[Martin1995], p. 65.

292

PROJETO GIL

public UserInterface()
{
isComplete = true;
}
public void Init(HotWaterSource hws, ContainmentVessel cv)
{
this.hws = hws;
this.cv = cv;
}
public void Complete()
{
isComplete = true;
CompleteCycle();
}
protected void StartBrewing()
{
if (hws.IsReady() && cv.IsReady())
{
isComplete = false;
hws.Start();
cv.Start();
}
}
public abstract void Done();
public abstract void CompleteCycle();
}
}

Listagem 20-15
M4UserInterface.cs
using CoffeeMaker;
namespace M4CoffeeMaker
{
public class M4UserInterface: UserInterface, Pollable
{
private CoffeeMakerAPI api;
public M4UserInterface(CoffeeMakerAPI api)
{
this.api = api;
}
public void Poll()

HEURSTICAS E CAF

{
BrewButtonStatus buttonStatus = api.GetBrewButtonStatus();
if (buttonStatus == BrewButtonStatus.PUSHED)
{
StartBrewing();
}
}
public override void Done()
{
api.SetIndicatorState(IndicatorState.ON);
}
public override void CompleteCycle()
{
api.SetIndicatorState(IndicatorState.OFF);
}
}
}

Listagem 20-16
HotWaterSource.cs
namespace CoffeeMaker
{
public abstract class HotWaterSource
{
private UserInterface ui;
private ContainmentVessel cv;
protected bool isBrewing;
public HotWaterSource()
{
isBrewing = false;
}
public void Init(UserInterface ui, ContainmentVessel cv)
{
this.ui = ui;
this.cv = cv;
}
public void Start()
{
isBrewing = true;
StartBrewing();
}
public void Done()
{

293

294

PROJETO GIL

isBrewing = false;
}
protected void DeclareDone()
{
ui.Done();
cv.Done();
isBrewing = false;
}
public abstract bool IsReady();
public abstract void StartBrewing();
public abstract void Pause();
public abstract void Resume();
}
}

Listagem 20-17
M4HotWaterSource.cs
using System;
using CoffeeMaker;
namespace M4CoffeeMaker
{
public class M4HotWaterSource: HotWaterSource, Pollable
{
private CoffeeMakerAPI api;
public M4HotWaterSource(CoffeeMakerAPI api)
{
this.api = api;
}
public override bool IsReady()
{
BoilerStatus boilerStatus = api.GetBoilerStatus();
return boilerStatus == BoilerStatus.NOT_EMPTY;
}
public override void StartBrewing()
{
api.SetReliefValveState(ReliefValveState.CLOSED);
api.SetBoilerState(BoilerState.ON);
}
public void Poll()
{
BoilerStatus boilerStatus = api.GetBoilerStatus();

HEURSTICAS E CAF

if (isBrewing)
{
if (boilerStatus == BoilerStatus.EMPTY)
{
api.SetBoilerState(BoilerState.OFF);
api.SetReliefValveState(ReliefValveState.CLOSED);
DeclareDone();
}
}
}
public override void Pause()
{
api.SetBoilerState(BoilerState.OFF);
api.SetReliefValveState(ReliefValveState.OPEN);
}
public override void Resume()
{
api.SetBoilerState(BoilerState.ON);
api.SetReliefValveState(ReliefValveState.CLOSED);
}
}
}

Listagem 20-18
ContainmentVessel.cs
using System;
namespace CoffeeMaker
{
public abstract class ContainmentVessel
{
private UserInterface ui;
private HotWaterSource hws;
protected bool isBrewing;
protected bool isComplete;
public ContainmentVessel()
{
isBrewing = false;
isComplete = true;
}
public void Init(UserInterface ui, HotWaterSource hws)
{
this.ui = ui;

295

296

PROJETO GIL

this.hws = hws;
}
public void Start()
{
isBrewing = true;
isComplete = false;
}
public void Done()
{
isBrewing = false;
}
protected void DeclareComplete()
{
isComplete = true;
ui.Complete();
}
protected void ContainerAvailable()
{
hws.Resume();
}
protected void ContainerUnavailable()
{
hws.Pause();
}
public abstract bool IsReady();
}
}

Listagem 20-19
M4ContainmentVessel.cs
using CoffeeMaker;
namespace M4CoffeeMaker
{
public class M4ContainmentVessel: ContainmentVessel, Pollable
{
private CoffeeMakerAPI api;
private WarmerPlateStatus lastPotStatus;
public M4ContainmentVessel(CoffeeMakerAPI api)
{
this.api = api;
lastPotStatus = WarmerPlateStatus.POT_EMPTY;

HEURSTICAS E CAF

}
public override bool IsReady()
{
WarmerPlateStatus plateStatus =
api.GetWarmerPlateStatus();
return plateStatus == WarmerPlateStatus.POT_EMPTY;
}
public void Poll()
{
WarmerPlateStatus potStatus = api.GetWarmerPlateStatus();
if (potStatus!= lastPotStatus)
{
if (isBrewing)
{
HandleBrewingEvent(potStatus);
}
else if (isComplete == false)
{
HandleIncompleteEvent(potStatus);
}
lastPotStatus = potStatus;
}
}
private void
HandleBrewingEvent(WarmerPlateStatus potStatus)
{
if (potStatus == WarmerPlateStatus.POT_NOT_EMPTY)
{
ContainerAvailable();
api.SetWarmerState(WarmerState.ON);
}
else if (potStatus == WarmerPlateStatus.WARMER_EMPTY)
{
ContainerUnavailable();
api.SetWarmerState(WarmerState.OFF);
}
else
{ // potStatus == POT_EMPTY
ContainerAvailable();
api.SetWarmerState(WarmerState.OFF);
}
}
private void
HandleIncompleteEvent(WarmerPlateStatus potStatus)
{
if (potStatus == WarmerPlateStatus.POT_NOT_EMPTY)
{
api.SetWarmerState(WarmerState.ON);
}

297

298

PROJETO GIL

else if (potStatus == WarmerPlateStatus.WARMER_EMPTY)


{
api.SetWarmerState(WarmerState.OFF);
}
else
{ // potStatus == POT_EMPTY
api.SetWarmerState(WarmerState.OFF);
DeclareComplete();
}
}
}
}

Listagem 20-20
Pollable.cs
using System;
namespace M4CoffeeMaker
{
public interface Pollable
{
void Poll();
}
}

Listagem 20-21
CoffeeMaker.cs
using CoffeeMaker;
namespace M4CoffeeMaker
{
public class M4CoffeeMaker
{
public static void Main(string[] args)
{
CoffeeMakerAPI api = new M4CoffeeMakerAPI();
M4UserInterface ui = new M4UserInterface(api);
M4HotWaterSource hws = new M4HotWaterSource(api);
M4ContainmentVessel cv = new M4ContainmentVessel(api);
ui.Init(hws, cv);

HEURSTICAS E CAF

hws.Init(ui, cv);
cv.Init(ui, hws);
while (true)
{
ui.Poll();
hws.Poll();
cv.Poll();
}
}
}
}

Listagem 20-22
TestCoffeeMaker.cs
using M4CoffeeMaker;
using NUnit.Framework;
namespace CoffeeMaker.Test
{
internal class CoffeeMakerStub: CoffeeMakerAPI
{
public bool buttonPressed;
public bool lightOn;
public bool boilerOn;
public bool valveClosed;
public bool plateOn;
public bool boilerEmpty;
public bool potPresent;
public bool potNotEmpty;
public CoffeeMakerStub()
{
buttonPressed = false;
lightOn = false;
boilerOn = false;
valveClosed = true;
plateOn = false;
boilerEmpty = true;
potPresent = true;
potNotEmpty = false;
}
public WarmerPlateStatus GetWarmerPlateStatus()
{
if (!potPresent)
return WarmerPlateStatus.WARMER_EMPTY;
else if (potNotEmpty)
return WarmerPlateStatus.POT_NOT_EMPTY;

299

300

PROJETO GIL

else
return WarmerPlateStatus.POT_EMPTY;
}
public BoilerStatus GetBoilerStatus()
{
return boilerEmpty ?
BoilerStatus.EMPTY : BoilerStatus.NOT_EMPTY;
}
public BrewButtonStatus GetBrewButtonStatus()
{
if (buttonPressed)
{
buttonPressed = false;
return BrewButtonStatus.PUSHED;
}
else
{
return BrewButtonStatus.NOT_PUSHED;
}
}
public void SetBoilerState(BoilerState boilerState)
{
boilerOn = boilerState == BoilerState.ON;
}
public void SetWarmerState(WarmerState warmerState)
{
plateOn = warmerState == WarmerState.ON;
}
public void
SetIndicatorState(IndicatorState indicatorState)
{
lightOn = indicatorState == IndicatorState.ON;
}
public void
SetReliefValveState(ReliefValveState reliefValveState)
{
valveClosed = reliefValveState == ReliefValveState.CLOSED;
}
}
[TestFixture]
public class TestCoffeeMaker
{
private M4UserInterface ui;
private M4HotWaterSource hws;
private M4ContainmentVessel cv;
private CoffeeMakerStub api;
[SetUp]

HEURSTICAS E CAF

public void SetUp()


{
api = new CoffeeMakerStub();
ui = new M4UserInterface(api);
hws = new M4HotWaterSource(api);
cv = new M4ContainmentVessel(api);
ui.Init(hws, cv);
hws.Init(ui, cv);
cv.Init(ui, hws);
}
private void Poll()
{
ui.Poll();
hws.Poll();
cv.Poll();
}
[Test]
public void InitialConditions()
{
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[Test]
public void StartNoPot()
{
Poll();
api.buttonPressed = true;
api.potPresent = false;
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[Test]
public void StartNoWater()
{
Poll();
api.buttonPressed = true;
api.boilerEmpty = true;
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}

301

302

PROJETO GIL

[Test]
public void GoodStart()
{
NormalStart();
Assert.IsTrue(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
private void NormalStart()
{
Poll();
api.boilerEmpty = false;
api.buttonPressed = true;
Poll();
}
[Test]
public void StartedPotNotEmpty()
{
NormalStart();
api.potNotEmpty = true;
Poll();
Assert.IsTrue(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsTrue(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[Test]
public void PotRemovedAndReplacedWhileEmpty()
{
NormalStart();
api.potPresent = false;
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsFalse(api.valveClosed);
api.potPresent = true;
Poll();
Assert.IsTrue(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[Test]
public void PotRemovedWhileNotEmptyAndReplacedEmpty()
{
NormalFill();
api.potPresent = false;

HEURSTICAS E CAF

Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsFalse(api.valveClosed);
api.potPresent = true;
api.potNotEmpty = false;
Poll();
Assert.IsTrue(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
private void NormalFill()
{
NormalStart();
api.potNotEmpty = true;
Poll();
}
[Test]
public void PotRemovedWhileNotEmptyAndReplacedNotEmpty()
{
NormalFill();
api.potPresent = false;
Poll();
api.potPresent = true;
Poll();
Assert.IsTrue(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsTrue(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[Test]
public void BoilerEmptyPotNotEmpty()
{
NormalBrew();
Assert.IsFalse(api.boilerOn);
Assert.IsTrue(api.lightOn);
Assert.IsTrue(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
private void NormalBrew()
{
NormalFill();
api.boilerEmpty = true;
Poll();
}
[Test]

303

304

PROJETO GIL

public void BoilerEmptiesWhilePotRemoved()


{
NormalFill();
api.potPresent = false;
Poll();
api.boilerEmpty = true;
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsTrue(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
api.potPresent = true;
Poll();
Assert.IsFalse(api.boilerOn);
Assert.IsTrue(api.lightOn);
Assert.IsTrue(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
[Test]
public void EmptyPotReturnedAfter()
{
NormalBrew ();
api.potNotEmpty = false;
Poll ();
Assert.IsFalse(api.boilerOn);
Assert.IsFalse(api.lightOn);
Assert.IsFalse(api.plateOn);
Assert.IsTrue(api.valveClosed);
}
}
}

Bibliografia
[Beck2002] Kent Beck, Test-Driven Development, Addison-Wesley, 2002.
[Martin1995] Robert C. Martin, Designing Object-Oriented C++ Applications Using the
Booch Method, Prentice Hall, 1995.

Seo III

ESTUDO DE CASO DA FOLHA


DE PAGAMENTOS

hegou a hora de nosso primeiro estudo de caso grande. J estudamos as prticas e


os princpios. Discutimos a essncia do projeto. Falamos sobre teste e planejamento. Agora, ao trabalho!
Nos prximos captulos, exploraremos o projeto e a implementao de um sistema
de folha de pagamentos em lote, cuja especificao bsica aparece a seguir. Como parte
desse projeto e implementao, faremos uso de vrios padres de projeto: COMMAND,
TEMPLATE METHOD, STRATEGY, SINGLETON, NULL OBJECT, FACTORY e FAADE.
Esses padres so o assunto dos prximos captulos. No Captulo 26, trabalharemos no
projeto e implementao do problema da folha de pagamentos.
Voc pode ler esse estudo de caso de diversas maneiras.
Leia tudo, aprendendo primeiro os padres de projeto e depois vendo como eles so
aplicados no problema da folha de pagamentos.
Se voc conhece os padres e no est interessado em uma reviso, v direto para o
Captulo 26.
Leia primeiro o Captulo 26 e depois volte e leia os captulos que descrevem os
padres que foram utilizados.
Leia o Captulo 26 por partes. Quando ele mencionar um padro que voc no
conhece, leia o captulo que descreve esse padro e depois volte para o Captulo 26.

No existem regras. Escolha (ou invente) a estratgia que funcione melhor para voc.

306

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Especificao bsica do sistema de folha de pagamentos


A seguir esto algumas anotaes que fizemos enquanto conversvamos com nosso cliente.
(Essas anotaes tambm so dadas no Captulo 26.)
Esse sistema consiste em um banco de dados dos funcionrios da empresa e suas
informaes relacionadas, como os cartes de ponto. O sistema deve fazer o pagamento de
todos os funcionrios no valor correto, pontualmente, pelo mtodo que eles especificarem.
Alm disso, vrios descontos devem ser feitos no pagamento.
Alguns funcionrios trabalham por hora. Eles recebem um salrio por hora, que
um dos campos em seus registros de empregado. Eles apresentam cartes de ponto
dirios que registram a data e o nmero de horas trabalhadas. Se trabalham mais de
8 horas por dia, eles recebem 1,5 vezes seu salrio normal pelas horas extras. Eles
recebem todas as sextas-feiras.
Alguns funcionrios recebem um salrio fixo. Eles so pagos no ltimo dia til do
ms. Seu salrio mensal um dos campos em seus registros de empregado.
Alguns dos funcionrios assalariados tambm recebem uma comisso baseada em
suas vendas. Eles apresentam recibos de venda que registram a data e o valor da
venda. A taxa da comisso um campo em seus registros de empregado. Eles recebem a cada duas sextas-feiras.
Os funcionrios podem escolher o mtodo de pagamento. Eles podem ter seus cheques-salrio enviados para o endereo postal de sua escolha, podem peg-los com o
pagador ou solicitar que os cheques sejam depositados diretamente na conta bancria de sua escolha.
Alguns funcionrios pertencem ao sindicato. Seus registros de empregado tm um
campo para desconto das taxas semanais. As taxas devem ser descontadas de seus
pagamentos. Alm disso, de tempos em tempos o sindicato pode cobrar despesas de
servio individualmente de seus membros. Essas despesas de servio so apresentadas pelo sindicato semanalmente e devem ser descontadas do valor do prximo
pagamento do funcionrio apropriado.
O aplicativo de folha de pagamentos ser executado uma vez a cada dia til e far o
pagamento dos funcionrios apropriados nesse dia. O sistema ser informado sobre
a data em que os funcionrios devem ser pagos; portanto, gerar os pagamentos dos
registros desde a ltima vez que o funcionrio foi pago at a data especificada.

Exerccio
Antes de continuar, recomendo que voc projete o sistema de folha de pagamentos por
conta prpria, agora. Talvez voc queira esboar alguns diagramas UML iniciais. Melhor
ainda, talvez queira escrever os primeiros casos de uso com testes a priori. Aplique os
princpios e as prticas que aprendemos at aqui e tente criar um projeto equilibrado e
saudvel. Lembre-se da cafeteira eltrica!
Consulte os casos de uso a seguir, se for projetar a sua folha de pagamentos. Caso
contrrio, pule-os; eles sero apresentados novamente no Captulo 26.

O ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

307

Caso de uso 1: Adicionar novo funcionrio


Um novo funcionrio adicionado pelo recebimento de uma transao AddEmp. Essa
transao contm o nome, endereo e nmero atribudo ao funcionrio. A transao tem
trs formas:
1. AddEmp

<EmpID>

"<name>"

"<address>"

<hrly-rate>

2. AddEmp

<EmpID>

"<name>"

"<address>"

<mtly-slry>

3. AddEmp

<EmpID>

"<name>"

"<address>"

<mtly-slry>

<comm-rate>

O registro de empregado criado com seus campos designados apropriadamente.


Alternativas: Um erro na estrutura da transao Se a estrutura da transao inadequada, ela impressa em uma mensagem de erro e nenhuma ao executada.

Caso de uso 2: Excluir um funcionrio


Os funcionrios so excludos quando uma transao DelEmp recebida. A forma dessa
transao a seguinte:
DelEmp <EmpID>

Quando essa transao recebida, o registro de empregado apropriado excludo.


Alternativa: EmpID invlido ou desconhecido Se o campo <EmpID> no est estruturado
corretamente ou no se refere a um registro de empregado vlido, a transao impressa
com uma mensagem de erro e nenhuma outra ao executada.

Caso de uso 3: Lanar um carto de ponto (Time Card)


Ao receber uma transao TimeCard, o sistema criar um registro de carto de ponto e o
associar ao registro de empregado apropriado:
TimeCard <empid> <date> <hours>

Alternativa 1: O funcionrio selecionado no recebe por hora O sistema imprimir uma


mensagem de erro apropriada e no executar mais nenhuma ao.
Alternativa 2: Um erro na estrutura da transao O sistema imprimir uma mensagem
de erro apropriada e no executar mais nenhuma ao.

Caso de uso 4: Lanar um recibo de venda (Sales Receipt)


Ao receber a transao SalesReceipt, o sistema criar um novo registro de recibo de
venda e o associar ao funcionrio comissionado apropriado.
SalesReceipt <EmpID> <date> <amount>

308

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Alternativa 1: O funcionrio selecionado no comissionado


mensagem de erro apropriada e no executar mais aes.
Alternativa 2: Um erro na estrutura da transao
de erro apropriada e no executar mais aes.

O sistema imprimir uma

O sistema imprimir uma mensagem

Caso de uso 5: Lanar uma taxa de servio do sindicato


Ao receber essa transao, o sistema criar um registro de taxa de servio e o associar ao
membro do sindicato apropriado:
ServiceCharge <memberID> <amount>

Alternativa: Transao malformada Se a transao no estiver bem formada ou se <memberID> no se referir a um membro do sindicato existente, a transao ser impressa
com uma mensagem de erro apropriada.

Caso de uso 6: Alterar detalhes do funcionrio


Ao receber essa transao, o sistema alterar um dos detalhes do registro de empregado
apropriado. Essa transao tem diversas variaes possveis:
ChgEmp <EmpID> Name <name>
ChgEmp <EmpID> Address <address>
ChgEmp <EmpID> Hourly <hourlyRate>
ChgEmp <EmpID> Salaried <salary>
ChgEmp <EmpID> Commissioned <salary> <rate>
ChgEmp <EmpID> Hold
ChgEmp <EmpID> Direct <bank> <account>
ChgEmp <EmpID> Mail <address>
ChgEmp <EmpID> Member <memberID> Dues <rate>
ChgEmp <EmpID> NoMember

Altera o nome do funcionrio


Altera o endereo do
funcionrio
Muda para por hora
Muda para assalariado
Muda para comissionado
Mantm o cheque-salrio
Depsito direto
Envia cheque-salrio pelo
correio
Coloca o funcionrio no
sindicato
Desliga o funcionrio do
sindicato

Alternativa: Erros de transao Se a estrutura da transao est incorreta, <EmpID> no


se refere a um funcionrio real ou <memberID> j se refere a um membro, imprime um
erro conveniente e no executa mais qualquer ao.

Caso de uso 7: Executar a folha de pagamentos de hoje


Ao receber a transao de dia de pagamento, o sistema descobre todos os funcionrios
que devem ser pagos na data especificada. Depois, o sistema determina quanto eles devem
receber e os paga de acordo com seus mtodos de pagamento selecionados. impresso
um relatrio de trilha de auditoria mostrando a ao executada por cada funcionrio:
Payday <date>

Captulo 21

COMMAND E ACTIVE
OBJECT: VERSATILIDADE E
MULTITAREFA
Nenhum homem recebeu da natureza o direito de
comandar seus semelhantes.
Denis Diderot (1713 -1784)

e todos os padres de projeto que tm sido descritos ao longo dos anos, considero
COMMAND um dos mais simples e elegantes. Mas a simplicidade enganosa. A variedade de usos que podem ser feitos de COMMAND provavelmente no tem limite.
A simplicidade de COMMAND, como se v na Figura 21-1, quase risvel. A Listagem
21-1 no ajuda a reduzir a leveza. Parece absurdo que possamos ter um padro consistindo em nada mais do que uma interface com um mtodo.
<<interface>>
Command
+ Execute()

Figura 21-1
Padro COMMAND.

Listagem 21-1
Command.cs
public interface Command
{
void Execute();
}

310

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

De fato, esse padro rompe um limite que revela uma complexidade interessante. A
maioria das classes associa um conjunto de mtodos a um conjunto de variveis correspondente. O padro COMMAND no faz isso. Ele encapsula uma nica funo isenta de
qualquer varivel.
Esse padro pode ser considerado um antema que beira a decomposio funcional.
Ele eleva o papel de uma funo ao nvel de uma classe, blasfmia! Apesar disso, nessa fronteira onde dois paradigmas colidem, coisas interessantes comeam a acontecer.

Comandos simples
Anos atrs, fiz uma consultoria para uma grande empresa que produzia fotocopiadoras.
Eu estava ajudando uma de suas equipes de desenvolvimento no projeto e implementao
do software embarcado de tempo real que determinava o funcionamento interno de uma
nova copiadora. Tivemos a ideia de usar o padro COMMAND para controlar os dispositivos de hardware. Criamos uma hierarquia semelhante Figura 21-2.
A funo dessas classes deve ser bvia. Chamar Execute() em um RelayOnCommand
ativa um rel. Chamar Execute() em um MotorOffCommand desliga um motor. O endereo do motor ou do rel passado para o objeto como um argumento para seu construtor.
Com essa estrutura em vigor, pudemos passar objetos Command no sistema e execut-los (com Execute()) sem saber precisamente que tipo de comando (Command) eles
representavam. Isso levou a algumas simplificaes interessantes.
O sistema era dirigido por eventos. Os rels (relays) abriam ou fechavam, os motores
(motors) davam a partida ou paravam e os engates (clutches) engatavam ou desengatavam,
de acordo com certos eventos que ocorriam no sistema. Muitos desses eventos eram detectados por sensores. Por exemplo, quando um sensor tico determinava que uma folha
de papel tinha atingido certo ponto na trajetria do papel, precisvamos acionar certo
engate. Pudemos implementar isso simplesmente vinculando o ClutchOnCommand apropriado ao objeto que controlava esse sensor tico em particular. Consulte a Figura 21-3.
<<interface>>
Command
+ Execute()

RelayOn
Command

MotorOn
Command

RelayOff
Command

ClutchOn
Command

MotorOff
Command

ClutchOff
Command

Figura 21-2
Alguns comandos simples para o software da copiadora.

COMMAND E ACTIVE OBJECT: VERSATILIDADE E MULTITAREFA

Sensor

311

Command

Figura 21-3
Um comando acionado por um sensor.
Essa estrutura simples tem uma grande vantagem. O objeto Sensor no tem ideia
do que est fazendo. Ao detectar um evento, ele simplesmente chama Execute() no
objeto Command a que est vinculado. Isso significa que os objetos Sensor no precisam
saber a respeito de engates ou rels individuais. Eles no precisam conhecer a estrutura
mecnica do trajeto do papel. Sua funo se torna consideravelmente simples.
A complexidade de determinar quais rels se fecham quando certos sensores declaram eventos foi movida para uma funo de inicializao. Em algum ponto durante
a inicializao do sistema, cada objeto Sensor vinculado a um objeto Command apropriado. Isso coloca todas as interligaes lgicas entre os sensores e comandos a fiao em um nico lugar e as retira do corpo principal do sistema. Alis, seria possvel
criar um arquivo de texto simples que descrevesse quais objetos Sensor estavam vinculados a quais objetos Command. O programa de inicializao poderia ler esse arquivo
e construir o sistema adequadamente. Assim, a fiao do sistema poderia ser determinada completamente fora do programa e poderia ser ajustada sem recompilao.
Encapsulando a noo de comando, esse padro nos permitiu desacoplar as interligaes lgicas do sistema dos dispositivos que estavam sendo conectados. Essa foi uma
vantagem e tanto.

Onde foi parar o I ?


Na comunidade .NET, comum preceder o nome de uma interface com um I maisculo. No exemplo
anterior, a interface Command provavelmente seria chamada ICommand. Embora muitas convenes
.NET sejam boas, e em geral este livro as siga, essa conveno especfica no adotada pelos humildes autores deste livro.
No recomendvel poluir o nome de algo com um conceito irrelevante, especialmente se esse conceito pode mudar. E se, por exemplo, decidirmos que ICommand deve ser uma classe abstrata em
vez de uma interface? Devemos ento encontrar todas as referncias a ICommand e alter-las para
Command? Devemos tambm recompilar e redistribuir todos os assemblies afetados?
Estamos no sculo XXI. Temos IDEs inteligentes que nos informam, com uma passada de mouse,
se uma classe uma interface. hora de os ltimos vestgios da notao hngara serem finalmente
aposentados.

312

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Transaes
O padro COMMAND tem outro uso comum que acharemos til no problema da folha de
pagamentos: a criao e execuo de transaes. Imagine, por exemplo, que estejamos escrevendo o software que mantm um banco de dados de funcionrios (consulte a Figura 21-4).
Os usurios podem executar diversas operaes nesse banco de dados, como adicionar novos
funcionrios, excluir funcionrios antigos ou alterar os atributos de funcionrios existentes.
Um usurio que decida adicionar um novo funcionrio deve especificar todas as
informaes necessrias para criar o registro de empregado com sucesso. Antes de atuar
sobre essas informaes, o sistema precisa verificar se elas esto sinttica e semanticamente corretas. O padro COMMAND pode ajudar nesse trabalho. O objeto comando atua
como um repositrio dos dados no validados, implementa os mtodos de validao e
implementa os mtodos que finalmente executam a transao.
Considere, por exemplo, a Figura 21-5. AddEmployeeTransaction contm os mesmos campos de dados de Employee, assim como um ponteiro para um objeto PayClassification (classificao do pagamento). Esses campos e esse objeto so criados a partir dos dados especificados pelo usurio ao instruir o sistema para que adicione um novo
funcionrio.
O mtodo Validate inspeciona todos os dados e garante que eles faam sentido. Ele
os verifica quanto correo sinttica e semntica. Ele pode at fazer uma verificao para
garantir se os dados da transao so coerentes com o estado atual do banco de dados.
Por exemplo, ele poderia certificar-se de que esse funcionrio ainda no existe.
Employee
- name
- address

interface
Pay
Classification
+ CalculatePay()

Commissioned
Classification
-basePay
-commissionRate

Salaried
Classification
-monthlyPay

Hourly
Classification
-hourlyRate

0..*
Sales
Receipt
-date
-amount

0..*
TimeCard
-date
-hours

Figura 21-4
Banco de dados de funcionrios.

COMMAND E ACTIVE OBJECT: VERSATILIDADE E MULTITAREFA

313

interface
Transaction
+ validate()
+ execute()

AddEmployee
Transaction
- name
- address
+ validate()
+ execute()

interface
Pay
Classification

+ CalculatePay()

Figura 21-5
Transao AddEmployee.
O mtodo Execute usa os dados validados para atualizar o banco de dados. Em
nosso exemplo simples, um novo objeto Employee seria criado e carregado com os campos do objeto AddEmployeeTransaction. O objeto PayClassification seria movido
ou copiado no objeto Employee.

Desacoplamento fsico e temporal


A vatagem que isso nos proporciona o excepcional desacoplamento do cdigo que obtm
os dados do usurio, do cdigo que valida e opera nesses dados e dos prprios objetos
de negcio. Por exemplo, poderia se esperar que os dados para adicionar um novo funcionrio fossem obtidos de uma caixa de dilogo em uma interface grfica do usurio. Seria
uma vergonha se o cdigo da interface grfica do usurio contivesse os algoritmos de validao e execuo da transao. Tal acoplamento impediria que o cdigo de validao e
execuo fosse utilizado em outras interfaces. Separando o cdigo de validao e execuo
na classe AddEmployeeTransaction, desacoplamos fisicamente esse cdigo da interface
de obteno. Alm disso, separamos o cdigo que sabe manipular a logstica do banco de
dados das prprias entidades de negcio.

Desacoplamento temporal
Tambm desacoplamos o cdigo de validao e execuo de uma maneira diferente. Uma
vez obtidos os dados, no h motivo para que os mtodos de validao e execuo devam
ser chamados imediatamente. Os objetos da transao podem ser mantidos em uma lista
e validados e executados muito mais tarde.
Suponha que tenhamos um banco de dados que deve permanecer inalterado durante o dia. As mudanas s podem ser aplicadas entre meia-noite e 1h. Seria uma
vergonha ter de esperar at a meia-noite e, ento, correr para digitar todos os comandos antes da 1h. Seria muito mais conveniente digitar todos os comandos, valid-los
imediatamente e, ento, execut-los mais tarde, meia-noite. O padro COMMAND nos
proporciona essa capacidade.

314

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

<<interface>>
Command
+ Execute()
+ Undo()

Figura 21-6
Variao Undo do padro COMMAND.

Mtodo Undo
A Figura 21-6 acrescenta o mtodo Undo() ao padro COMMAND. lgico que, se o mtodo
Execute() de uma derivada de Command pode ser implementado para lembrar dos detalhes
da operao que executa, o mtodo Undo() pode ser implementado para desfazer essa operao e retornar o sistema ao seu estado original.
Imagine, por exemplo, um aplicativo que permita ao usurio desenhar formas geomtricas na tela. Uma barra de ferramentas tem botes que permitem ao usurio desenhar crculos, quadrados, retngulos etc. Digamos que o usurio clique no boto Draw Circle (Desenhar Crculo). O sistema cria um objeto DrawCircleCommand e, ento, chama Execute()
nesse comando. O objeto DrawCircleCommand monitora o mouse do usurio, esperando
por um clique na janela de desenho. Ao receber esse clique, ele define o ponto do clique no
centro do crculo e comea a desenhar um crculo animado nesse centro, com um raio que
acompanha a posio atual do mouse. Quando o usurio clica novamente, o objeto DrawCircleCommand interrompe a animao do crculo e adiciona o objeto crculo apropriado
na lista de formas atualmente exibidas na tela de desenho. Ele tambm armazena a ID do
novo crculo em sua prpria varivel privada. Ento, ele retorna do mtodo Execute(). Em
seguida, o sistema coloca o objeto DrawCircleCommand expandido na pilha de comandos
concludos.
Algum tempo depois, o usurio clica no boto Undo (Desfazer) na barra de ferramentas. O sistema l a pilha de comandos concludos e chama Undo() no objeto Command
resultante. Ao receber a mensagem Undo(), o objeto DrawCircleCommand exclui o crculo
correspondente ID salva da lista de objetos correntemente exibidos na tela de desenho.
Com essa tcnica, voc pode implementar Undo de maneira fcil em praticamente
qualquer aplicativo. O cdigo que sabe como desfazer um comando est sempre prximo
ao cdigo que sabe como executar o comando.

Objeto ativo
Uma de minhas utilizaes prediletas do padro COMMAND o padro ACTIVE OBJECT.1 Essa antiga tcnica de implementao de mltiplas threads de controle tem sido
usada, de uma forma ou de outra, para fornecer um ncleo multitarefa simples para milhares de sistemas industriais.

[Lavender96].

COMMAND E ACTIVE OBJECT: VERSATILIDADE E MULTITAREFA

315

A ideia muito simples. Considere as listagens 21-2 e 21-3. Um objeto ActiveObjectEngine mantm uma lista encadeada de objetos Command. Os usurios podem
adicionar novos comandos ao mecanismo ou podem chamar Run(). A funo Run() simplesmente percorre a lista encadeada, executando e removendo cada comando.
Talvez isso no parea muito grandioso. Mas imagine o que aconteceria se um dos
objetos Command da lista encadeada se colocasse de volta na lista. A lista nunca ficaria
vazia e a funo Run() nunca retornaria.
Considere o caso de teste da Listagem 21-4. Esse caso de teste cria um objeto
SleepCommand, o qual, dentre outras coisas, passa um atraso de 1.000 ms para o
construtor de SleepCommand. Ento, o caso de teste coloca o objeto SleepCommand em
ActiveObjectEngine. Aps chamar Run(), o caso de teste espera que determinado
nmero de milissegundos tenha decorrido.

Listagem 21-2
ActiveObjectEngine.cs
using System.Collections;
public class ActiveObjectEngine
{
ArrayList itsCommands = new ArrayList();
public void AddCommand(Command c)
{
itsCommands.Add(c);
}
public void Run()
{
while (itsCommands.Count > 0)
{
Command c = (Command) itsCommands[0];
itsCommands.RemoveAt(0);
c.Execute();
}
}
}

Listagem 21-3
Command.cs
public interface Command
{
void Execute();
}

316

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Listagem 21-4
TestSleepCommand.cs
using System;
using NUnit.Framework;
[TestFixture]
public class TestSleepCommand
{
private class WakeUpCommand : Command
{
public bool executed = false;
public void Execute()
{
executed = true;
}
}
[Test]
public void TestSleep()
{
WakeUpCommand wakeup = new WakeUpCommand();
ActiveObjectEngine e = new ActiveObjectEngine();
SleepCommand c = new SleepCommand(1000, e, wakeup);
e.AddCommand(c);
DateTime start = DateTime.Now;
e.Run();
DateTime stop = DateTime.Now;
double sleepTime = (stop-start).TotalMilliseconds;
Assert.IsTrue(sleepTime >= 1000,
"SleepTime " + sleepTime + " expected > 1000");
Assert.IsTrue(sleepTime <= 1100,
"SleepTime " + sleepTime + " expected < 1100");
Assert.IsTrue(wakeup.executed, "Command Executed");
}
}

Vamos examinar melhor esse caso de teste. O construtor de SleepCommand contm trs
argumentos. O primeiro o tempo de atraso, em milissegundos. O segundo o objeto ActiveObjectEngine em que o comando ser executado. Por fim, existe outro objeto comando
chamado wakeup. O objetivo que o objeto SleepCommand espere pelo nmero especificado
de milissegundos e, ento, execute o comando wakeup.
A Listagem 21-5 mostra a implementao de SleepCommand. Ao ser executado, SleepCommand verifica se j foi executado antes. Se no foi, ele registra o tempo de incio. Se o
tempo de espera no tiver decorrido, ele se coloca de volta em ActiveObjectEngine. Se o
tempo de espera tiver decorrido, ele coloca o comando wakeup em ActiveObjectEngine.
Podemos fazer uma analogia entre esse programa e um programa multitarefa que
esteja esperando um evento. Em um programa multitarefa, quando uma thread espera
por um evento, normalmente faz uma chamada do sistema operacional que bloqueia a
thread at que o evento tenha ocorrido. O programa da Listagem 21-5 no faz o bloqueio.

COMMAND E ACTIVE OBJECT: VERSATILIDADE E MULTITAREFA

317

Listagem 21-5
SleepCommand.cs
using System;
public class SleepCommand : Command
{
private Command wakeupCommand = null;
private ActiveObjectEngine engine = null;
private long sleepTime = 0;
private DateTime startTime;
private bool started = false;
public SleepCommand(long milliseconds, ActiveObjectEngine e, Command wakeupCommand)
{
sleepTime = milliseconds;
engine = e;
this.wakeupCommand = wakeupCommand;
}
public void Execute()
{
DateTime currentTime = DateTime.Now;
if (!started)
{
started = true;
startTime = currentTime;
engine.AddCommand(this);
}
else
{
TimeSpan elapsedTime = currentTime startTime;
if (elapsedTime.TotalMilliseconds < sleepTime)
{
engine.AddCommand(this);
}
else
{
engine.AddCommand(wakeupCommand);
}
}
}
}

Em vez disso, se o evento que est esperando por (elapsedTime.TotalMilliseconds


< sleepTime) no tiver ocorrido, a thread simplesmente se coloca de volta em ActiveObjectEngine.
Construir sistemas multitarefa usando variaes dessa tcnica tem sido (e continuar a ser) uma prtica muito comum. Threads desse tipo so conhecidas como tarefas
de execuo at a concluso (RTC Run-To-Completion); cada instncia de Command

318

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

executada at a concluso, antes que a prxima possa ser executada. O nome RTC significa que as instncias de Command no so bloqueadas.
O fato de as instncias de Command serem todas executadas at a concluso proporciona s threads RTC a interessante vantagem de compartilharem a mesma pilha de
tempo de execuo. Ao contrrio das threads de um sistema multitarefa tradicional, no
necessrio definir nem alocar uma pilha de tempo de execuo separada para cada thread
RTC. Isso pode ser uma vantagem poderosa em um sistema com memria restrita e com
muitas threads.
Continuando nosso exemplo, a Listagem 21-6 mostra um programa simples que
utiliza SleepCommand e exibe comportamento multitarefa. Esse programa chamado DelayedTyper.
Note que DelayedTyper implementa Command. O mtodo Execute simplesmente
imprime um caractere que foi passado no construtor, verifica o flag stop e, se este no foi
ativado, chama DelayAndRepeat. DelayAndRepeat constri um SleepCommand usando
o atraso que foi passado no construtor e, ento, insere o objeto SleepCommand em ActiveObjectEngine.
fcil prever o comportamento desse objeto Command. Na verdade, ele fica preso em
um loop, digitando repetidamente um caractere especificado e esperando por um atraso
especificado. Ele sai do loop quando o flag stop ativado.
O programa Main de DelayedTyper inicia vrias instncias de DelayedTyper que
vo para ActiveObjectEngine, cada uma com seu prprio caractere e atraso, e depois
chama um SleepCommand que ativar o flag stop aps algum tempo. A execuo desse
programa produz uma string simples de valores 1, 3, 5 e 7. Execut-lo novamente produz
uma string semelhante, porm diferente. Aqui esto duas execues tpicas:
135711311511371113151131715131113151731111351113711531111357...
135711131513171131511311713511131151731113151131711351113117...

Essas strings so diferentes porque o clock da CPU e o relgio de tempo real no


esto em perfeito sincronismo. Esse tipo de comportamento no determinstico a caracterstica distintiva dos sistemas multitarefas.
O comportamento no determinstico tambm a fonte de muita angstia, aflio e
dor. Como qualquer um que j tenha trabalhado em sistemas embarcados de tempo real
sabe, difcil depurar comportamento no determinstico.

COMMAND E ACTIVE OBJECT: VERSATILIDADE E MULTITAREFA

Listagem 21-6
DelayedTyper.cs
using System;
public class DelayedTyper : Command
{
private long itsDelay;
private char itsChar;
private static bool stop = false;
private static ActiveObjectEngine engine =
new ActiveObjectEngine();
private class StopCommand :Command
{
public void Execute()
{
DelayedTyper.stop = true;
}
}
public static void Main(string[] args)
{
engine.AddCommand(new DelayedTyper(100, 1));
engine.AddCommand(new DelayedTyper(300, 3));
engine.AddCommand(new DelayedTyper(500, 5));
engine.AddCommand(new DelayedTyper(700, 7));
Command stopCommand = new StopCommand();
engine.AddCommand(
new SleepCommand(20000, engine, stopCommand));
engine.Run();
}
public DelayedTyper(long delay, char c)
{
itsDelay = delay;
itsChar = c;
}
public void Execute()
{
Console.Write(itsChar);
if (!stop)
DelayAndRepeat();
}
private void DelayAndRepeat()
{
engine.AddCommand(
new SleepCommand(itsDelay, engine, this));
}
}

319

320

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Concluso
A simplicidade do padro COMMAND esconde sua versatilidade. O padro COMMAND
pode ser usado para uma formidvel variedade de objetivos, de transaes de banco de
dados a controle de dispositivos, ncleos multitarefa e administrao de operaes fazer/
desfazer de interface do usurio.
Tem-se sugerido que o padro COMMAND viola o paradigma da OO, enfatizando as
funes em detrimento das classes. Talvez seja verdade, mas no mundo real do desenvolvedor de software, a utilidade se sobrepe teoria. O padro COMMAND pode ser muito til.

Bibliografia
[GOF95] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995
[Lavender96] R. G. Lavender and D. C. Schmidt, Active Object: An Object Behavioral
Pattern for Concurrent Programming, in J. O. Coplien, J. Vlissides, and N. Kerth, eds.
Pattern Languages of Program Design, Addison-Wesley, 1996.

Captulo 22

TEMPLATE METHOD E
STRATEGY: HERANA VERSUS
DELEGAO
A melhor estratgia na vida a diligncia.
Provrbio chins

o incio dos anos 1990 nos primrdios da OO , estvamos todos empolgados


com a ideia de herana. As implicaes do relacionamento eram profundas. Com a
herana podamos programar pela diferena! Ou seja, dada uma classe que tivesse algo
quase til para ns, podamos criar uma subclasse e alterar apenas os trechos que quisssemos. Podamos reutilizar cdigo simplesmente herdando-o! Podamos estabelecer
taxonomias de estruturas de software inteiras, cada nvel das quais reutilizava cdigo dos
nveis superiores. Era o admirvel mundo novo.

Obviamente, estvamos deslumbrados. Em 1995, ficou claro que era muito fcil e
dispendioso abusar da herana. Gamma, Helm, Johnson e Vlissides chegaram a enfatizar:
Favorea a composio de objetos em detrimento da herana de classe.1 Assim, reduzimos nosso uso de herana, frequentemente substituindo-a por composio ou delegao.
Este captulo a histria de dois padres que simbolizam a diferena entre herana
e delegao. TEMPLATE METHOD e STRATEGY resolvem problemas semelhantes e podem ser usados indistintamente em muitas ocasies. Contudo, TEMPLATE METHOD usa
herana para resolver o problema, enquanto STRATEGY usa delegao.
Tanto TEMPLATE METHOD quanto STRATEGY resolvem o problema de separar
um algoritmo genrico de um contexto detalhado. Muitas vezes vemos necessidade disso
no projeto de software. Temos um algoritmo genericamente aplicvel. Para obedecermos
ao Princpio da Inverso de Dependncia (DIP), queremos garantir que o algoritmo genrico no dependa da implementao detalhada. Em vez disso, queremos que o algoritmo
genrico e a implementao detalhada dependam de abstraes.

[GOF95], p. 20.

322

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

TEMPLATE METHOD
Pense em todos os programas que voc j escreveu. Muitos provavelmente tm esta estrutura de loop principal fundamental:
Initialize();
while (!Done()) // loop principal
{
Idle(); // faz algo til.
}
Cleanup();

Primeiro, inicializamos o aplicativo. Em seguida, entramos no loop principal,


onde fazemos o que o programa precisa fazer. Poderamos processar eventos de interface
grfica do usurio ou talvez registros de banco de dados. Por fim, ao terminarmos, samos
do loop principal e, antes disso, fazemos a limpeza de sada.
Essa estrutura to comum que podemos captur-la em uma classe chamada
Application. Depois, podemos reutilizar essa classe em todo novo programa que quisermos escrever. Pense nisso! Nunca mais teremos de escrever esse loop!2

Listagem 22-1
FtoCRaw.cs
using System;
using System.IO;
public class FtoCRaw
{
public static void Main(string[] args)
{
bool done = false;
while (!done)
{
string fahrString = Console.In.ReadLine();
if (fahrString == null || fahrString.Length == 0)
done = true;
else
{
double fahr = Double.Parse(fahrString);
double celcius = 5.0/9.0*(fahr 32);
Console.Out.WriteLine("F={0}, C={1}",fahr,celcius);
}
}
Console.Out.WriteLine("ftoc exit");
}
}

Quem dera!.

TEMPLATE METHOD E STRATEGY: HERANA VERSUS DELEGAO

323

Considere, por exemplo, a Listagem 22-1. Aqui, vemos todos os elementos do programa padro. TextReader e TextWriter so inicializados. Um loop principal faz
leituras em Fahrenheit de Console.In e imprime as converses em Celsius. No final
impressa uma mensagem de sada.
Esse programa tem todos os elementos da estrutura de loop principal anterior.
Ele faz alguma inicializao, realiza seu trabalho em um loop principal, em seguida faz
a limpeza e sai.
Podemos separar essa estrutura fundamental do programa ftoc usando o padro
TEMPLATE METHOD. Esse padro coloca todo o cdigo genrico em um mtodo implementado de uma classe base abstrata. O mtodo implementado captura o algoritmo genrico, mas transfere todos os detalhes para mtodos abstratos da classe base.
Assim, por exemplo, podemos capturar a estrutura do loop principal em uma
classe base abstrata chamada Application. Consulte a Listagem 22-2.
Essa classe descreve uma aplicao de loop principal genrico. Podemos ver o loop
principal na funo implementada Run. Tambm podemos ver que todo o trabalho est
sendo transferido para os mtodos abstratos Init, Idle e Cleanup. O mtodo Init
cuida de toda inicializao que precisamos fazer. O mtodo Idle faz o trabalho principal
do programa e ser chamado repetidamente at que SetDone seja chamado. O mtodo
Cleanup faz o que precisa ser feito antes de sairmos.

Listagem 22-2
Application.cs
public abstract class Application
{
private bool isDone = false;
protected abstract void Init();
protected abstract void Idle();
protected abstract void Cleanup();
protected void SetDone()
{
isDone = true;
}
protected bool Done()
{
return isDone;
}
public void Run()
{
Init();
while (!Done())
Idle();
Cleanup();
}
}

324

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Podemos reescrever a classe ftoc herdando de Application e simplesmente completando os mtodos abstratos. A Listagem 22-3 mostra como fica isso.
fcil ver como o antigo aplicativo ftoc foi enquadrado no padro TEMPLATE METHOD.

Listagem 22-3
FtoCTemplateMethod.cs
using System;
using System.IO;
public class FtoCTemplateMethod: Application
{
private TextReader input;
private TextWriter output;
public static void Main(string[] args)
{
new FtoCTemplateMethod().Run();
}
protected override void Init()
{
input = Console.In;
output = Console.Out;
}
protected override void Idle()
{
string fahrString = input.ReadLine();
if (fahrString == null || fahrString.Length == 0)
SetDone();
else
{
double fahr = Double.Parse(fahrString);
double celcius = 5.0/9.0*(fahr 32);
output.WriteLine("F={0}, C={1}", fahr, celcius);
}
}
protected override void Cleanup()
{
output.WriteLine("ftoc exit");
}
}

TEMPLATE METHOD E STRATEGY: HERANA VERSUS DELEGAO

325

Abuso de padro
Voc deve estar pensando: Ele est falando srio? Ele realmente espera que eu utilize
essa classe Application em todos os novos aplicativos? Ela no ajudou em nada e ainda
complicou demais o problema.
Er..., verdade...:^(
Escolhi esse exemplo porque era simples e oferecia uma boa plataforma para mostrar a mecnica de TEMPLATE METHOD. Por outro lado, na verdade no recomendo
construir ftoc desse modo.
Esse um bom exemplo de abuso de padro. Usar TEMPLATE METHOD para esse
aplicativo especfico ridculo. Ele complica e aumenta o programa. Encapsular o loop
principal de todos os aplicativos do universo parecia maravilhoso quando comeamos,
mas a aplicao prtica no d resultados nesse caso.
Os padres de projeto so maravilhosos. Eles podem ajud-lo em muitos problemas
de projeto. Mas o fato de existirem no significa que sempre devem ser utilizados. Nesse
caso, TEMPLATE METHOD era aplicvel ao problema, mas seu uso no era aconselhvel.
O custo do padro era maior do que o benefcio que ele proporcionava.

Bubble Sort
Vamos ver um exemplo mais til. Consulte a Listagem 22-4. Note que, assim como
Application, Bubble Sort fcil de entender e, portanto, constitui uma ferramenta
de ensino til. Contudo, ningum em seu legtimo direito jamais usaria Bubble Sort
se tivesse um volume significativo de ordenao a fazer. Existem algoritmos muito melhores.

Listagem 22-4
BubbleSorter.cs
public class BubbleSorter
{
static int operations = 0;
public static int Sort(int [] array)
{
operations = 0;
if (array.Length <= 1)

326

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

return operations;
for (int nextToLast = array.Length-2;
nextToLast >= 0; nextToLast--)
for (int index = 0; index <= nextToLast; index++)
CompareAndSwap(array, index);
return operations;
}
private static void Swap(int[] array, int index)
{
int temp = array[index];
array[index] = array[index+1];
array[index+1] = temp;
}
private static void CompareAndSwap(int[] array, int index)
{
if (array[index] > array[index+1])
Swap(array, index);
operations++;
}
}

A classe BubbleSorter sabe como ordenar um array de inteiros usando o algoritmo


de ordenao por bolhas. O mtodo Sort de BubbleSorter contm o algoritmo que sabe
como fazer uma ordenao por bolhas. Os dois mtodos auxiliares Swap e CompareAndSwap tratam dos detalhes dos inteiros e arrays e manipulam os mecanismos exigidos
pelo algoritmo Sort.
Usando o padro TEMPLATE METHOD, podemos separar o algoritmo de ordenao
por bolhas em uma classe base abstrata chamada BubbleSorter. BubbleSorter contm uma implementao de funo Sort que chama um mtodo abstrato denominado
OutOfOrder e outro denominado Swap. O mtodo OutOfOrder compara dois elementos
adjacentes no array e retorna true se eles esto fora de ordem. O mtodo Swap troca duas
clulas adjacentes no array.
O mtodo Sort no sabe a respeito do array e tambm no se preocupa com os tipos
de objetos armazenados no array. Ele simplesmente chama OutOfOrder para vrios ndices no array e determina se esses ndices devem ser trocados. Consulte a Listagem 22-5.
Dado BubbleSorter, podemos agora criar derivadas simples que consigam ordenar
qualquer tipo diferente de objeto. Por exemplo, poderamos criar IntBubbleSorter, que
ordenaria arrays de inteiros, e DoubleBubbleSorter, que ordenaria arrays de doubles.
Consulte a Figura 22-1 e as listagens 22-6 e 22-7.
O padro TEMPLATE METHOD mostra uma das formas clssicas de reutilizao
na programao orientada a objetos. Algoritmos genricos so colocados na classe base
e herdados em diferentes contextos detalhados. Mas essa tcnica tem seus custos. A herana um relacionamento muito forte. As derivadas so inextricavelmente vinculadas s
suas classes base.

TEMPLATE METHOD E STRATEGY: HERANA VERSUS DELEGAO

Listagem 22-5
BubbleSorter.cs
public abstract class BubbleSorter
{
private int operations = 0;
protected int length = 0;
protected int DoSort()
{
operations = 0;
if (length <= 1)
return operations;
for (int nextToLast = length-2;
nextToLast >= 0; nextToLast--)
for (int index = 0; index <= nextToLast; index++)
{
if (OutOfOrder(index))
Swap(index);
operations++;
}
return operations;
}
protected abstract void Swap(int index);
protected abstract bool OutOfOrder(int index);
}

BubbleSorter
{abstract}
#outOfOrder
#swap

IntBubble
Sorter

DoubleBubble
Sorter

Figura 22-1
Estrutura do ordenador por bolhas.

327

328

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Listagem 22-6
IntBubbleSorter.cs
public class IntBubbleSorter : BubbleSorter
{
private int[] array = null;
public int Sort(int[] theArray)
{
array = theArray;
length = array.Length;
return DoSort();
}
protected override void Swap(int index)
{
int temp = array[index];
array[index] = array[index + 1];
array[index + 1] = temp;
}
protected override bool OutOfOrder(int index)
{
return (array[index] > array[index + 1]);
}
}

Listagem 22-7
DoubleBubbleSorter.cs
public class DoubleBubbleSorter : BubbleSorter
{
private double[] array = null;
public int Sort(double[] theArray)
{
array = theArray;
length = array.Length;
return DoSort();
}
protected override void Swap(int index)
{
double temp = array[index];
array[index] = array[index + 1];
array[index + 1] = temp;
}
protected override bool OutOfOrder(int index)
{
return (array[index] > array[index + 1]);
}
}

TEMPLATE METHOD E STRATEGY: HERANA VERSUS DELEGAO

329

Por exemplo, as funes OutOfOrder e Swap de IntBubbleSorter so exatamente


o que necessrio para outros tipos de algoritmos de ordenao. Mas no h como reutilizar OutOfOrder e Swap nesses outros algoritmos. Herdando BubbleSorter, condenamos IntBubbleSorter a ser eternamente vinculado a BubbleSorter. O padro STRATEGY oferece outra opo.

STRATEGY
O padro STRATEGY resolve o problema da inverso das dependncias do algoritmo
genrico e da implementao detalhada de uma maneira muito diferente. Considere mais
uma vez o problema Application que faz abuso de padro.
Em vez de colocar o algoritmo genrico do aplicativo em uma classe base abstrata, o colocamos em uma classe concreta chamada ApplicationRunner. Definimos os
mtodos abstratos que o algoritmo genrico precisa chamar dentro de uma interface denominada Application. Derivamos FtoCStrategy dessa interface e o passamos para
ApplicationRunner. Ento, ApplicationRunner delega para essa interface. Consulte a
Figura 22-2 e as listagens 22-8 a 22-10.
Deve estar claro que essa estrutura tem vantagens e custos em relao estrutura
TEMPLATE METHOD. O padro STRATEGY envolve mais classes totais e mais indireo
do que o padro TEMPLATE METHOD. O ponteiro de delegao dentro de ApplicationRunner acarreta um custo ligeiramente maior em termos de tempo de execuo e espao
de dados do que a herana acarretaria. Por outro lado, se tivssemos muitos aplicativos
diferentes para executar, poderamos reutilizar a instncia de ApplicationRunner e passar muitas implementaes diferentes de Application, reduzindo com isso a sobrecarga
de espao de cdigo.
Nenhum desses custos e vantagens predominante. Na maioria dos casos, nenhum
deles possui grande significncia. No caso tpico, o mais preocupante a classe extra, necessria para o padro STRATEGY. Contudo, h mais coisas a considerar.
interface
Application
Application
Runner
+ run

+ init
+ idle
+ cleanup
+ done : boolean

ftocStrategy

Figura 22-2
Estrutura do padro STRATEGY para o algoritmo Application.

330

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Listagem 22-8
ApplicationRunner.cs
public class ApplicationRunner
{
private Application itsApplication = null;
public ApplicationRunner(Application app)
{
itsApplication = app;
}
public void run()
{
itsApplication.Init();
while (!itsApplication.Done())
itsApplication.Idle();
itsApplication.Cleanup();
}
}

Listagem 22-9
Application.cs
public interface Application
{
void Init();
void Idle();
void Cleanup();
bool Done();
}

Listagem 22-10
FtoCStrategy.cs
using System;
using System.IO;
public class FtoCStrategy : Application
{
private TextReader input;
private TextWriter output;
private bool isDone = false;
public static void Main(string[] args)
{

TEMPLATE METHOD E STRATEGY: HERANA VERSUS DELEGAO

331

(new ApplicationRunner(new FtoCStrategy())).run();


}
public void Init()
{
input = Console.In;
output = Console.Out;
}
public void Idle()
{
string fahrString = input.ReadLine();
if (fahrString == null || fahrString.Length == 0)
isDone = true;
else
{
double fahr = Double.Parse(fahrString);
double celcius = 5.0/9.0*(fahr 32);
output.WriteLine("F={0}, C={1}", fahr, celcius);
}
}
public void Cleanup()
{
output.WriteLine("ftoc exit");
}
public bool Done()
{
return isDone;
}
}

Vejamos uma implementao da ordenao por bolhas que utiliza o padro STRATEGY. Consulte as listagens 22-11 a 22-13.

Listagem 22-11
BubbleSorter.cs
public class BubbleSorter
{
private int operations = 0;
private int length = 0;
private SortHandler itsSortHandler = null;
public BubbleSorter(SortHandler handler)
{
itsSortHandler = handler;

332

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

}
public int Sort(object array)
{
itsSortHandler.SetArray(array);
length = itsSortHandler.Length();
operations = 0;
if (length <= 1)
return operations;
for (int nextToLast = length 2;
nextToLast >= 0; nextToLast--)
for (int index = 0; index <= nextToLast; index++)
{
if (itsSortHandler.OutOfOrder(index))
itsSortHandler.Swap(index);
operations++;
}
return operations;
}

Listagem 22-12
SortHandler.cs
public interface SortHandler
{
void Swap(int index);
bool OutOfOrder(int index);
int Length();
void SetArray(object array);
}

Listagem 22-13
IntSortHandler.cs
public class IntSortHandler : SortHandler
{
private int[] array = null;
public void Swap(int index)
{
int temp = array[index];
array[index] = array[index + 1];
array[index + 1] = temp;
}

TEMPLATE METHOD E STRATEGY: HERANA VERSUS DELEGAO

333

public void SetArray(object array)


{
this.array = (int[]) array;
}
public int Length()
{
return array.Length;
}
public bool OutOfOrder(int index)
{
return (array[index] > array[index + 1]);
}
}

Note que a classe IntSortHandler nada sabe a respeito de BubbleSorter, e no


depende de qualquer maneira da implementao da ordenao por bolhas. Isso no acontece com o padro TEMPLATE METHOD. Examine novamente a Listagem 22-6 e veja
como IntBubbleSorter depende diretamente de BubbleSorter, a classe que contm o
algoritmo de ordenao por bolhas.
A estratgia do padro TEMPLATE METHOD viola o DIP parcialmente. A implementao dos mtodos Swap e OutOfOrder depende diretamente do algoritmo de ordenao por
bolhas. A estratgia do padro STRATEGY no contm tal dependncia. Assim, podemos
usar IntSortHandler com implementaes de Sorter que no sejam BubbleSorter.
Por exemplo, podemos criar uma variao da ordenao por bolhas que termina
mais cedo, caso uma passagem pelo array o encontre em ordem. (Veja a Figura 22-14.)
QuickBubbleSorter tambm pode usar IntSortHandler ou qualquer outra classe derivada de SortHandler.

Listagem 22-14
QuickBubbleSorter.cs
public class QuickBubbleSorter
{
private int operations = 0;
private int length = 0;
private SortHandler itsSortHandler = null;
public QuickBubbleSorter(SortHandler handler)
{
itsSortHandler = handler;
}
public int Sort(object array)
{
itsSortHandler.SetArray(array);
length = itsSortHandler.Length();

334

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

operations = 0;
if (length <= 1)
return operations;
bool thisPassInOrder = false;
for (int nextToLast = length-2;
nextToLast >= 0 && !thisPassInOrder; nextToLast--)
{
thisPassInOrder = true; //potencialmente.
for (int index = 0; index <= nextToLast; index++)
{
if (itsSortHandler.OutOfOrder(index))
{
itsSortHandler.Swap(index);
thisPassInOrder = false;
}
operations++;
}
}
return operations;
}
}

Assim, o padro STRATEGY oferece uma vantagem em relao ao padro TEMPLATE METHOD. Enquanto o padro TEMPLATE METHOD permite que um algoritmo genrico manipule muitas implementaes detalhadas possveis, o padro STRATEGY, obedecendo totalmente ao DIP, permite, alm disso, que cada implementao detalhada seja
manipulada por muitos algoritmos genricos diferentes.

Concluso
O padro TEMPLATE METHOD simples de escrever e utilizar, mas tambm rgido. O
padro STRATEGY flexvel, mas voc precisa criar uma classe extra, instanciar um objeto extra e ligar o objeto extra ao sistema. Portanto, a escolha entre TEMPLATE METHOD e
STRATEGY depende de voc precisar da flexibilidade de STRATEGY ou poder conviver com
a simplicidade de TEMPLATE METHOD. Muitas vezes, tenho optado pelo padro TEMPLATE
METHOD simplesmente porque ele mais fcil de implementar e usar. Por exemplo, eu usaria a soluo do padro TEMPLATE METHOD para o problema da ordenao por bolhas, a
no ser que tivesse certeza absoluta de que precisaria de algoritmos de ordenao diferentes.

Bibliografia
[GOF95] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995.
[PLOPD3] Robert C. Martin, Dirk Riehle, and Frank Buschmann, eds. Pattern Languages
of Program Design 3, Addison-Wesley, 1998.

Captulo 23

FAADE E MEDIATOR
O simbolismo erige uma fachada de respeitabilidade
para ocultar a indecncia dos sonhos.
Mason Cooley

s dois padres discutidos neste captulo tm um objetivo comum: impor um tipo de


diretiva (policy) para outro grupo de objetos. O padro FAADE impe a diretiva a
partir de cima; o padro MEDIATOR, a partir de baixo. O uso de FAADE visvel e restritivo; o de MEDIATOR invisvel e permissivo.

FAADE
O padro FAADE1 usado quando voc quer fornecer uma interface simples e especfica
para um grupo de objetos que tm uma interface complexa e geral. Considere, por exemplo, a classe DB.cs da Listagem 34-9. Essa classe impe uma interface muito simples,
especfica para ProductData, nas interfaces complexas e gerais das classes dentro do
namespace System.Data. A Figura 23-1 mostra a estrutura.
Note que a classe DB evita que Application precise saber da intimidade do namespace System.Data. A classe oculta toda a generalidade e complexidade de System.Data
atrs de uma interface muito simples e especfica.
Uma fachada como DB impe muitas diretivas em relao ao uso de System.Data,
sabendo como inicializar e fechar a conexo com o banco de dados, transformar os membros de ProductData em campos de banco de dados e desfazer essa transformao, e
construir as consultas e comandos apropriados para manipular o banco de dados. Toda
essa complexidade fica oculta dos usurios. Do ponto de vista de Application, System.
Data no existe; fica oculta atrs da fachada.

N. de R.T.: O nome correto FAADE (com ); vem do francs (pronuncia-se fassad, aproximadamente)
e significa, literalmente, fachada.

336

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Db
ProductData

+Store(ProductData)
+GetProduct(sku)
+DeleteProductData(Sku)

Application

System.Data

SqlConnection

SqlCommand

IDataReader

Figura 23-1
A fachada (FAADE) DB.
O uso do padro FAADE indica que os desenvolvedores adotaram a conveno de que
todas as chamadas do banco de dados devem passar por DB. Se alguma parte do cdigo de
Application passar diretamente para System.Data, em vez de passar pela fachada, essa
conveno violada. Desse modo, o padro FAADE impe suas diretivas no aplicativo. Por
conveno, DB se torna o nico intermedirio das instalaes de System.Data.
O padro FAADE pode ser usado para ocultar qualquer aspecto de um programa.
Contudo, usar FAADE para ocultar o banco de dados se tornou to comum que o padro
tambm conhecido como TABLE DATA GATEWAY (gateway de dados de tabela).

MEDIATOR
O padro MEDIATOR tambm impe diretivas. Contudo, enquanto o padro FAADE impe suas diretivas de maneira visvel e restritiva, o padro MEDIATOR impe as suas de
maneira oculta e no restritiva. Por exemplo, a classe QuickEntryMediator da Listagem
23-1 fica em silncio nos bastidores e vincula um campo de entrada de texto a uma lista.
Quando voc digita no campo de entrada de texto, o primeiro elemento da lista que corresponda ao que foi digitado realado. Isso permite que voc digite abreviaes e selecione
um item da lista rapidamente.

FAADE E MEDIATOR

Listagem 23-1
QuickEntryMediator.cs
using System;
using System.Windows.Forms;
/// <summary>
/// QuickEntryMediator. Esta classe recebe um componente TextBox
/// e um componente ListBox. Ela presume que o usurio digitar
/// no componente TextBox caracteres que so prefixos de
/// entradas no componente ListBox. Ela seleciona
/// automaticamente o primeiro item do componente ListBox que
/// corresponde ao prefixo corrente no componente TextBox.
///
/// Se o componente TextField for nulo ou se o prefixo no
/// corresponder a nenhum elemento no componente ListBox, ento
/// a seleo do ListBox ser apagada.
///
/// No existem mtodos a chamar para esse objeto. Voc
/// simplesmente o cria e se esquece dele. (Mas no o deixe
/// ser pego pelo coletor de lixo...)
///
/// Exemplo:
///
/// TextBox t = new TextBox();
/// ListBox l = new ListBox();
///
/// QuickEntryMediator qem = new QuickEntryMediator(t,l);
/// // isso tudo, pessoal.
///
/// Escrito originalmente em Java
/// por Robert C. Martin, Robert S. Koss
/// em 30 de junho de 1999 2113 (SLAC)
/// Transformado em C# por Micah Martin
/// em 23 de maio de 2005 (no trem)
/// </summary>
public class QuickEntryMediator
{
private TextBox itsTextBox;
private ListBox itsList;
public QuickEntryMediator(TextBox t, ListBox l)
{
itsTextBox = t;
itsList = l;
itsTextBox.TextChanged += new
EventHandler(TextFieldChanged);
}
private void
TextFieldChanged(object source, EventArgs args)
{

337

338

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

string prefix = itsTextBox.Text;


if (prefix.Length == 0)
{
itsList.ClearSelected();
return;
}
ListBox.ObjectCollection listItems = itsList.Items;
bool found = false;
for (int i = 0; found == false &&
i < listItems.Count; i++)
{
Object o = listItems[i];
String s = o.ToString();
if (s.StartsWith(prefix))
{
itsList.SetSelected(i, true);
found = true;
}
}
if (!found)
{
itsList.ClearSelected();
}
}
}

TextBox

ListBox

QuickEntryMediator

Figura 23-2
QuickEntryMediator.

FAADE E MEDIATOR

339

A estrutura de QuickEntryMediator aparece na Figura 23-2. Uma instncia de QuickEntryMediator construda com um componente ListBox e um componente TextBox. QuickEntryMediator registra um EventHandler no componente TextBox. Esse EventHandler chama o mtodo TextFieldChanged quando h uma mudana no texto. Ento, esse
mtodo encontra um elemento de ListBox que seja prefixado pelo texto e o seleciona.
Os usurios de ListBox e TextField no tm ideia de que esse MEDIATOR existe. Ele fica l, em silncio, impondo sua diretiva nesses objetos sem sua permisso ou
conhecimento.

Concluso
A imposio de diretivas pode ser feita a partir de cima usando o padro FAADE, caso
essa diretiva precise ser grande e visvel. Por outro lado, se so necessrias sutileza e discrio, o padro MEDIATOR pode ser a escolha mais adequada. As fachadas (FAADE)
normalmente so o ponto focal de uma conveno. Todos concordam em usar a fachada
(FAADE), em vez dos objetos que esto debaixo dela. O padro MEDIATOR, por outro
lado, fica oculto dos usurios. Sua diretiva um fato consumado, em vez de uma questo
de conveno.

Bibliografia
[Fowler03] Martin Fowler, Patterns of Enterprise Application Architecture, Addison-Wesley, 2003.
[GOF95] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995.

Captulo 24

SINGLETON E MONOSTATE
Bno infinita da existncia! Ela ;
e nada h alm dela.
Edwin A. Abbott, Flatland (1884)

ormalmente, existe um relacionamento de um para muitos entre classes e instncias. Voc pode criar muitas instncias da maioria das classes. As instncias so
criadas quando necessrias e so descartadas quando sua utilidade termina. Elas vm e
vo em um fluxo de alocaes e desalocaes de memria.
Porm, algumas classes devem ter apenas uma instncia. Essa instncia deve aparentar ter surgido quando o programa comeou e ser descartada somente quando o programa termina. Tais objetos s vezes so as razes do aplicativo. A partir das razes, voc
pode chegar a muitos outros objetos do sistema. s vezes, esses objetos so fbricas, as
quais voc pode usar para criar os outros objetos do sistema. s vezes esses objetos so
gerenciadores, responsveis por monitorar determinados outros objetos e orient-los em
seus passos.
Quaisquer que sejam esses objetos, ser uma falha lgica grave se mais de um deles
for criado. Se mais de uma raiz criada, o acesso aos objetos no aplicativo pode depender de uma raiz escolhida. Os programadores, no sabendo que existe mais de uma raiz,
podem se encontrar examinando um subconjunto dos objetos do aplicativo sem saber. Se
existe mais de uma fbrica, o controle administrativo sobre os objetos criados pode ficar
comprometido. Se existe mais de um gerenciador, atividades destinadas a ser seriais podem se tornar concomitantes.

Pode parecer que os mecanismos que impem a singularidade desses objetos sejam excessivos. Afinal, quando voc inicializa o aplicativo, pode simplesmente criar um
de cada e pronto.1 Na verdade, essa normalmente a melhor estratgia. Tal mecanismo
deve ser evitado quando no existe necessidade imediata e significativa. Contudo, tambm
1

Chamo isso de padro JUST CREATE ONE (basta criar um).

342

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

queremos que nosso cdigo comunique nosso objetivo. Se o mecanismo para impor a
singularidade trivial, a vantagem da comunicao pode superar o custo do mecanismo.
Este captulo ocupa-se de dois padres que impem a singularidade. Esses padres
tm compromissos de custo/benefcio muito diferentes. Na maioria dos contextos, seu
custo baixo o suficiente para mais do que equilibrar a vantagem de sua expressividade.

SINGLETON
O padro SINGLETON muito simples.2 O caso de teste da Listagem 24-1 mostra como
ele deve funcionar. A primeira funo de teste mostra que a instncia de Singleton
acessada com o mtodo public static Instance e que se Instance chamado vrias
vezes, uma referncia para exatamente a mesma instncia retornada a cada vez. O segundo caso de teste mostra que a classe Singleton no tem construtores public; portanto, no h como criar uma instncia sem usar o mtodo Instance.

Listagem 24-1
Caso de teste de Singleton
using System;
using System.Reflection;
using NUnit.Framework;
[TestFixture]
public class TestSimpleSingleton
{
[Test]
public void TestCreateSingleton()
{
Singleton s = Singleton.Instance;
Singleton s2 = Singleton.Instance;
Assert.AreSame(s, s2);
}
[Test]
public void TestNoPublicConstructors()
{
Type singleton = typeof(Singleton);
ConstructorInfo[] ctrs = singleton.GetConstructors();
bool hasPublicConstructor = false;
foreach(ConstructorInfo c in ctrs)
{
if(c.IsPublic)

[GOF95], p. 127.

SINGLETON E MONOSTATE

343

{
hasPublicConstructor = true;
break;
}
}
Assert.IsFalse(hasPublicConstructor);
}
}

Listagem 24-2
Implementao de Singleton
public class Singleton
{
private static Singleton theInstance = null;
private Singleton() {}
public static Singleton Instance
{
get
{
if (theInstance == null)
theInstance = new Singleton();
return theInstance;
}
}
}

Esse caso de teste uma especificao do padro SINGLETON e leva diretamente ao cdigo mostrado na Listagem 24-2. Inspecionando-se esse cdigo, deve ficar claro que nunca
pode haver mais de uma instncia da classe Singleton dentro do escopo da varivel esttica Singleton.theInstance.

Benefcios
Independncia de plataforma: Usando-se middleware adequado (por exemplo, Remoting), o padro SINGLETON pode ser estendido para trabalhar em muitos CLRs
(Common Language Runtime) e muitos computadores.
Aplicvel a qualquer classe: Voc pode mudar qualquer classe para SINGLETON
simplesmente tornando seus construtores private e adicionando as funes e variveis static apropriadas.
Pode ser criado por derivao: Dada uma classe, voc pode criar uma subclasse
SINGLETON.
Avaliao lenta: Se o SINGLETON nunca usado, ele nunca criado.

344

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Custos
Destruio indefinida: No existe uma boa maneira de destruir ou desativar um
SINGLETON. Se voc adiciona um mtodo decommission que anula theInstance,
outros mdulos do sistema ainda podem conter uma referncia para o SINGLETON.
As chamadas subsequentes para Instance criaro outra instncia, fazendo existir
duas instncias concomitantes. Esse problema particularmente grave em C++,
onde a instncia pode ser destruda, levando possvel retirada da referncia de um
objeto destrudo.
No herdado: Uma classe derivada de um SINGLETON no um SINGLETON. Se
ela precisa ser SINGLETON, a funo e a varivel static precisam ser adicionadas.
Eficincia: Cada chamada de Instance chama a instruo if. Para a maioria dessas chamadas, a instruo if intil.
No transparente: Os usurios de um SINGLETON sabem que o esto usando, pois
precisam chamar o mtodo Instance.

SINGLETON em ao
Suponha que tenhamos um sistema baseado na Web que permite aos usurios fazer login
em reas seguras de um servidor Web. Tal sistema ter um banco de dados contendo
nomes de usurio, senhas e outros atributos de usurio. Suponha ainda que o banco de
dados seja acessado por uma API externa. Poderamos acessar o banco de dados diretamente em cada mdulo que precisasse ler e gravar um usurio. Contudo, isso espalharia
a utilizao da API externa pelo cdigo e no nos deixaria nenhum lugar para impor convenes de acesso ou estrutura.
Uma soluo melhor usar o padro FAADE e criar uma classe UserDatabase que
fornea mtodos para ler e gravar objetos User.3 Esses mtodos acessam a API externa do
banco de dados, fazendo a transformao entre objetos User e as tabelas e linhas do banco
de dados. Dentro de UserDatabase, podemos impor convenes de estrutura e acesso. Por
exemplo, podemos garantir que nenhum registro de User seja gravado a menos que tenha
um username que no esteja vazio. Ou ento, podemos serializar o acesso a um registro de
User, garantindo que dois mdulos no possam ler e gravar nele simultaneamente.
O cdigo das listagens 24-3 e 24-4 mostra uma soluo SINGLETON. A classe SINGLETON chamada UserDatabaseSource e implementa a interface UserDatabase. Note
que o mtodo esttico Instance() no tem a instruo if tradicional para proteger contra
criaes mltiplas. Em vez disso, ele tira proveito do recurso de inicializao do .NET.
Esse um uso extremamente comum do padro SINGLETON. Ele garante que todo
o acesso ao banco de dados seja feito com uma nica instncia de UserDatabaseSource.
Isso torna fcil colocar verificaes, contadores e bloqueios em UserDatabaseSource
para impor as convenes de acesso e estrutura mencionadas anteriormente.

Essa forma especial do padro FAADE conhecida como GATEWAY. Para uma discusso detalhada sobre
GATEWAY, consulte [Fowler03].

SINGLETON E MONOSTATE

Listagem 24-3
Interface UserDatabase
public interface UserDatabase
{
User ReadUser(string userName);
void WriteUser(User user);
}

Listagem 24-4
Singleton UserDatabase
public class UserDatabaseSource : UserDatabase
{
private static UserDatabase theInstance =
new UserDatabaseSource();
public static UserDatabase Instance
{
get
{
return theInstance;
}
}
private UserDatabaseSource()
{
}
public User ReadUser(string userName)
{
// Alguma implementao
}
public void WriteUser(User user)
{
// Alguma implementao
}
}

345

346

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

MONOSTATE
O padro MONOSTATE outra maneira de se obter singularidade. Ele funciona por meio
de um mecanismo completamente diferente. Podemos ver como esse mecanismo funciona
estudando o caso de teste Monostate da Listagem 24-5.
A primeira funo de teste simplesmente descreve um objeto cuja varivel x pode
ser configurada e recuperada. Mas o segundo caso de teste mostra que duas instncias da
mesma classe se comportam como se fossem uma s. Se voc configura a varivel x de
uma instncia com um valor em particular, pode recuperar esse valor obtendo a varivel
x de uma instncia diferente. como se as duas instncias fossem simplesmente nomes
diferentes para o mesmo objeto.
Se colocssemos a classe Singleton nesse caso de teste e substitussemos todas
as instrues new Monostate por chamadas para Singleton.Instance, o caso de teste
ainda passaria. Portanto, esse caso de teste descreve o comportamento de Singleton
sem impor a restrio de uma nica instncia!

Listagem 24-5
Dispositivo de teste de Monostate
using NUnit.Framework;
[TestFixture]
public class TestMonostate
{
[Test]
public void TestInstance()
{
Monostate m = new Monostate();
for (int x = 0; x < 10; x++)
{
m.X = x;
Assert.AreEqual(x, m.X);
}
}
[Test]
public void TestInstancesBehaveAsOne()
{
Monostate m1 = new Monostate();
Monostate m2 = new Monostate();
for (int x = 0; x < 10; x++)
{
m1.X = x;
Assert.AreEqual(x, m2.X);
}
}
}

SINGLETON E MONOSTATE

347

Listagem 24-6
Implementao de Monostate
public class Monostate
{
private static int itsX;
public int X
{
get { return itsX; }
set { itsX = value; }
}
}

Como duas instncias podem se comportar como se fossem um nico objeto? Em


termos gerais, os dois objetos precisam compartilhar as mesmas variveis. Isso obtido
facilmente, tornando-se todas as variveis static. A Listagem 24-6 mostra a implementao de Monostate que passa no caso de teste anterior. Note que a varivel itsX static,
mas que nenhum dos mtodos . Isso importante, conforme veremos posteriormente.
Acho esse padro deliciosamente idiossincrtico. No importa quantas instncias de
Monostate voc crie, todas elas se comportaro como se fossem um nico objeto. Voc
pode at destruir ou desativar todas as instncias atuais sem perder os dados.
Note que a diferena entre os dois padres o comportamento versus estrutura. O
padro SINGLETON impe a estrutura da singularidade, impedindo que mais de uma
instncia seja criada. Em contraste, o padro MONOSTATE impe o comportamento da
singularidade, sem impor restries estruturais. Para ressaltar essa diferena, considere
que o caso de teste de MONOSTATE vlido para a classe Singleton, mas que o caso de
teste de SINGLETON no est nem perto de ser vlido para a classe Monostate.

Benefcios
Transparncia: Os usurios no se comportam de forma diferente dos usurios de
um objeto normal. Eles no precisam saber que o objeto monostate.
Capacidade de derivao: As derivadas de um monostate so monostate. Alis,
todas as derivadas de um monostate fazem parte do mesmo monostate. Todas elas
compartilham as mesmas variveis estticas.
Polimorfismo: Como os mtodos de um monostate no so estticos, eles podem ser
sobrescritos em uma derivada. Assim, diferentes derivadas podem oferecer diferentes comportamentos para o mesmo conjunto de variveis estticas.
Criao e destruio bem definidas: As variveis de um monostate, sendo estticas,
tm tempos de criao e destruio bem definidos.

348

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Custos
Nenhuma converso: Uma classe que no monostate no pode ser convertida em
uma classe monostate por derivao.
Eficincia: Como se trata de um objeto real, um monostate pode passar por muitas
criaes e destruies. Frequentemente, essas operaes so dispendiosas.
Presena: As variveis de um monostate ocupam espao, mesmo que o monostate
nunca seja utilizado.
Local por plataforma: Voc no pode fazer um monostate funcionar em vrias instncias de CLR ou em vrias plataformas juntas.

MONOSTATE em ao
Considere a implementao da mquina de estados finitos (FSM) simples da roleta de
metr mostrada na Figura 24-1. A roleta comea no estado Locked. Se uma moeda depositada, a roleta muda para o estado Unlocked e destrava a catraca, reinicia qualquer
estado de alarme que possa estar presente e deposita a moeda em sua caixa coletora. Se
um usurio passa pela catraca nesse ponto, a roleta volta para o estado Locked e bloqueia
a catraca.
Existem duas condies anormais. Se o usurio depositar duas ou mais moedas
antes de passar pela catraca, ser reembolsado e a catraca permanecer destravada. Se o
usurio passar sem pagar, um alarme soar e a catraca permanecer bloqueada.
O programa de teste que descreve essa operao aparece na Listagem 24-7. Note que
os mtodos de teste presumem que Turnstile um monostate e espera poder enviar
eventos e reunir consultas de diferentes instncias. Isso faz sentido, se nunca houver mais
de uma instncia de Turnstile.

Coin/Unlock, AlarmOff, Deposit


Locked

Pass/Alarm

H
Pass/Lock
Unlocked

Figura 24-1
Mquina de estados finitos da roleta de metr.

Coin/Refund

SINGLETON E MONOSTATE

Listagem 24-7
TurnstileTest
using NUnit.Framework;
[TestFixture]
public class TurnstileTest
{
[SetUp]
public void SetUp()
{
Turnstile t = new Turnstile();
t.reset();
}
[Test]
public void TestInit()
{
Turnstile t = new Turnstile();
Assert.IsTrue(t.Locked());
Assert.IsFalse(t.Alarm());
}
[Test]
public void TestCoin()
{
Turnstile t = new Turnstile();
t.Coin();
Turnstile t1 = new Turnstile();
Assert.IsFalse(t1.Locked());
Assert.IsFalse(t1.Alarm());
Assert.AreEqual(1, t1.Coins);
}
[Test]
public void TestCoinAndPass()
{
Turnstile t = new Turnstile();
t.Coin();
t.Pass();
Turnstile t1 = new Turnstile();
Assert.IsTrue(t1.Locked());
Assert.IsFalse(t1.Alarm());
Assert.AreEqual(1, t1.Coins, "coins");
}

349

350

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

[Test]
public void TestTwoCoins()
{
Turnstile t = new Turnstile();
t.Coin();
t.Coin();
Turnstile t1 = new Turnstile();
Assert.IsFalse(t1.Locked(), "unlocked");
Assert.AreEqual(1, t1.Coins, "coins");
Assert.AreEqual(1, t1.Refunds, "refunds");
Assert.IsFalse(t1.Alarm());
}
[Test]
public void TestPass()
{
Turnstile t = new Turnstile();
t.Pass();
Turnstile t1 = new Turnstile();
Assert.IsTrue(t1.Alarm(), "alarm");
Assert.IsTrue(t1.Locked(), "locked");
}
[Test]
public void TestCancelAlarm()
{
Turnstile t = new Turnstile();
t.Pass();
t.Coin();
Turnstile t1 = new Turnstile();
Assert.IsFalse(t1.Alarm(), "alarm");
Assert.IsFalse(t1.Locked(), "locked");
Assert.AreEqual(1, t1.Coins, "coin");
Assert.AreEqual(0, t1.Refunds, "refund");
}
[Test]
public void TestTwoOperations()
{
Turnstile t = new Turnstile();
t.Coin();
t.Pass();
t.Coin();
Assert.IsFalse(t.Locked(), "unlocked");
Assert.AreEqual(2, t.Coins, "coins");
t.Pass();
Assert.IsTrue(t.Locked(), "locked");
}
}

SINGLETON E MONOSTATE

Listagem 24-8
Turnstile
public class Turnstile
{
private static bool isLocked = true;
private static bool isAlarming = false;
private static int itsCoins = 0;
private static int itsRefunds = 0;
protected static readonly
Turnstile LOCKED = new Locked();
protected static readonly
Turnstile UNLOCKED = new Unlocked();
protected static Turnstile itsState = LOCKED;
public void reset()
{
Lock(true);
Alarm(false);
itsCoins = 0;
itsRefunds = 0;
itsState = LOCKED;
}
public bool Locked()
{
return isLocked;
}
public bool Alarm()
{
return isAlarming;
}
public virtual void Coin()
{
itsState.Coin();
}
public virtual void Pass()
{
itsState.Pass();
}
protected void Lock(bool shouldLock)
{
isLocked = shouldLock;
}
protected void Alarm(bool shouldAlarm)
{
isAlarming = shouldAlarm;
}

351

352

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

public int Coins


{
get { return itsCoins; }
}
public int Refunds
{
get { return itsRefunds; }
}
public void Deposit()
{
itsCoins++;
}
public void Refund()
{
itsRefunds++;
}
}
internal class Locked: Turnstile
{
public override void Coin()
{
itsState = UNLOCKED;
Lock(false);
Alarm(false);
Deposit();
}
public override void Pass()
{
Alarm(true);
}
}
internal class Unlocked: Turnstile
{
public override void Coin()
{
Refund();
}
public override void Pass()
{
Lock(true);
itsState = LOCKED;
}
}

SINGLETON E MONOSTATE

353

A implementao do monostate Turnstile est na Listagem 24-8. A classe base


Turnstile delega as duas funes de evento, coin e pass, para duas derivadas de
Turnstile, Locked e Unlocked, que representam os estados da FSM.
O exemplo da pgina anterior mostra alguns dos recursos teis do padro MONOSTATE. Ele tira proveito da capacidade das derivadas de monostate serem polimrficas e
do fato de elas prprias serem monostate. Esse exemplo tambm mostra como s vezes
pode ser difcil transformar um monostate em um no monostate. A estrutura dessa soluo depende fortemente da natureza monostate de Turnstile. Se precisssemos controlar mais de uma roleta com essa FSM, o cdigo exigiria alguma refatorao significativa.
Talvez voc esteja preocupado com o uso no convencional da herana nesse exemplo. Ter derivado Unlocked e Locked de Turnstile parece uma violao dos princpios
normais da OO. Contudo, como Turnstile um monostate, no existem instncias distintas dele. Assim, Unlocked e Locked no so realmente objetos distintos, mas em vez
disso fazem parte da abstrao de Turnstile. Unlocked e Locked tm acesso s mesmas variveis e mtodos de Turnstile.

Concluso
Frequentemente, necessrio impor uma nica instanciao de um objeto especfico. Este
captulo mostrou duas tcnicas muito diferentes. O padro SINGLETON faz uso de construtores privados, de uma varivel esttica e de uma funo esttica para controlar e
limitar a instanciao. O padro MONOSTATE simplesmente torna todas as variveis do
objeto estticas.
O melhor uso do padro SINGLETON se d quando voc tem uma classe que deseja
restringir por meio de derivao e no se importa de que todo mundo tenha que chamar
o mtodo Instance() para obter acesso. O melhor uso do padro MONOSTATE se d
quando voc quer que a natureza singular da classe seja transparente para os usurios ou
quando quer usar derivadas polimrficas do nico objeto.

Bibliografia
[Fowler03] Martin Fowler, Patterns of Enterprise Application Architecture, Addison-Wesley, 2003.
[GOF95] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995.
[PLOPD3] Robert C. Martin, Dirk Riehle, and Frank Buschmann, eds. Pattern Languages
of Program Design 3, Addison-Wesley, 1998.

Captulo 25

NULL OBJECT
Imperfeitamente sem falhas, friamente regular,
esplendidamente nula, Perfeio morta, nunca mais.
Lord Alfred Tennyson (1809-1892)

Descrio
Considere o cdigo a seguir:
Employee e = DB.GetEmployee("Bob");
if (e != null && e.IsTimeToPay(today))
e.Pay();

Procuramos no banco de dados um objeto Employee chamado "Bob". O objeto DB retornar


null se tal objeto no existir. Caso contrrio, ele retornar a instncia de Employee solicitada. Se o funcionrio (employee) existe e tem um pagamento, chamamos o mtodo pay.
Todos ns j escrevemos cdigo como o acima descrito. O idioma comum porque,
nas linguagens baseadas em C, a primeira expresso de && avaliada primeiro e a segunda s ser avaliada se a primeira for true. A maioria de ns tambm j se roeu por dentro
por ter se esquecido de testar o valor null. Embora o idioma possa ser comum, ele
horrvel e propenso a erros.
Podemos atenuar a tendncia ao erro fazendo DB.GetEmployee lanar uma exceo,
em vez de retornar null. Contudo, blocos try/catch podem ser ainda mais feios do que
uma verificao de null.
Podemos resolver esses problemas usando o padro NULL OBJECT.1 Esse padro
frequentemente elimina a necessidade de verificar o valor null e pode ajudar a simplificar
o cdigo.
1

[PLOPD3], p. 5. Esse agradvel artigo est repleto de humor, ironia e muitas recomendaes prticas.

356

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

A Figura 25-1 mostra a estrutura. Employee se torna uma interface que tem duas
implementaes. EmployeeImplementation, a implementao normal, contm todos os
mtodos e variveis que voc esperaria de um objeto Employee. Quando encontra um
funcionrio no banco de dados, DB.GetEmployee retorna uma instncia de EmployeeImplementation. NullEmployee s retornado se DB.GetEmployee no consegue encontrar o funcionrio.
NullEmployee implementa todos os mtodos de Employee para fazer nada. O que
nada depende do mtodo. Por exemplo, poderia se esperar que IsTimeToPay fosse
implementado para retornar false, pois um NullEmployee (funcionrio nulo) nunca
ser pago.
Assim, usando esse padro, podemos alterar o cdigo original para:
Employee e = DB.GetEmployee("Bob");
if (e.IsTimeToPay(today))
e.Pay();

Isso no nem propenso a erros nem feio. H uma coerncia perfeita.


DB.GetEmployee sempre retorna uma instncia de Employee. Essa instncia ir se comportar de modo correto, independentemente de o funcionrio ser encontrado.

interface
Employee

DB

creates
NullEmployee

creates

Figura 25-1
Padro NULL OBJECT.

Employee
Implementation

NULL OBJECT

357

Listagem 25-1
EmployeeTest.cs (parcial)
[Test]
public void TestNull()
{
Employee e = DB.GetEmployee("Bob");
if (e.IsTimeToPay(new DateTime()))
Assert.Fail();
Assert.AreSame(Employee.NULL, e);
}

Evidentemente, em muitos casos ainda desejaremos saber se DB.GetEmployee no


conseguiu encontrar um funcionrio. Isso pode ser conseguido criando-se em Employee
uma varivel static readonly que contenha a nica instncia de NullEmployee.
A Listagem 25-1 mostra o caso de teste de NullEmployee. Nesse caso, "Bob" no
existe no banco de dados. Note que o caso de teste espera que IsTimeToPay retorne false. Note tambm que ele espera que o funcionrio retornado por DB.GetEmployee seja
Employee.NULL.
A classe DB est mostrada na Listagem 25-2. Note que, para os propsitos de nosso
teste, o mtodo GetEmployee retorna simplesmente Employee.NULL.

Listagem 25-2
DB.cs
public class DB
{
public static Employee GetEmployee(string s)
{
return Employee.NULL;
}
}

358

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Listagem 25-3
Employee.cs
using System;
public abstract class Employee
{
public abstract bool IsTimeToPay(DateTime time);
public abstract void Pay();
public static readonly Employee NULL =
new NullEmployee();
private class NullEmployee : Employee
{
public override bool IsTimeToPay(DateTime time)
{
return false;
}
public override void Pay()
{
}
}
}

A classe Employee est mostrada na Listagem 25-3. Note que essa classe tem uma varivel static, NULL, que contm a nica instncia da implementao aninhada de Employee.
NullEmployee implementa IsTimeToPay para retornar false e Pay para no fazer nada.
Tornar NullEmployee uma classe aninhada private uma maneira de garantir
que exista apenas uma instncia dela. Ningum mais pode criar outras instncias de
NullEmployee. Isso bom, pois queremos escrever coisas como:
if (e == Employee.NULL)

Isso no seria confivel se fosse possvel criar muitas instncias do funcionrio nulo.

Concluso
Aqueles que tm usado linguagens baseadas em C h muito tempo se acostumaram com
funes que retornam null ou 0 em caso de algum tipo de falha. Presumimos que o valor de retorno de tais funes precisa ser testado. O padro NULL OBJECT muda isso.
Usando esse padro, podemos garantir que as funes sempre retornem objetos vlidos,
mesmo quando falham. Esses objetos que representam falha nada fazem.

Bibliografia
[PLOPD3] Robert C. Martin, Dirk Riehle, and Frank Buschmann, eds. Pattern Languages
of Program Design 3, Addison-Wesley, 1998.

Captulo 26

ESTUDO DE CASO DA
FOLHA DE PAGAMENTOS:
ITERAO 1
Tudo que de algum modo belo, belo em si mesmo e termina em
si mesmo, no tendo enaltecimento como parte de si mesmo.
Marcos Aurlio, cerca de 170 D.C.

estudo de caso a seguir descreve a primeira iterao do desenvolvimento de um


sistema de folha de pagamentos em lote simples. Voc ver que as histrias de usurio desse estudo de caso so simplistas. Por exemplo, os impostos simplesmente no so
mencionados. Isso tpico de uma iterao inicial. Ela fornecer apenas uma parte muito
pequena do valor comercial que os clientes precisam.
Neste captulo, faremos o tipo de anlise e sesso de projeto rpidos que frequentemente ocorrem no incio de uma iterao normal. O cliente selecionou as histrias para a
iterao e agora precisamos descobrir como vamos implement-las. Tais sesses de projeto so breves e superficiais, assim como este captulo. Os diagramas UML que voc ver
aqui nada mais so do que rpidos esboos feitos em um quadro branco. O trabalho de
projeto real ocorrer no prximo captulo, quando trabalharemos nos testes de unidade e
nas implementaes.

Especificao bsica
A seguir esto algumas anotaes que fizemos enquanto conversvamos com nosso cliente
sobre as histrias que foram selecionadas para a primeira iterao.
Alguns funcionrios trabalham por hora. Eles recebem um salrio por hora, que
um dos campos em seus registros de empregado. Eles apresentam cartes de ponto
dirios que registram a data e o nmero de horas trabalhadas. Se trabalham mais de
8 horas por dia, eles recebem 1,5 vezes seu salrio normal pelas horas extras. Eles
recebem todas as sextas-feiras.
Alguns funcionrios recebem um salrio fixo. Eles so pagos no ltimo dia til do
ms. Seu salrio mensal um dos campos em seus registros de empregado.

360

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Alguns dos funcionrios assalariados tambm recebem uma comisso de vendas.


Eles apresentam recibos de venda que registram a data e o valor da venda. A taxa da
comisso um campo em seus registros de empregado. Eles recebem a cada duas
sextas-feiras.
Os funcionrios podem escolher o mtodo de pagamento. Eles podem ter seus cheques-salrio enviados para o endereo postal de sua escolha, podem peg-los com o
pagador ou solicitar que os cheques sejam depositados diretamente na conta bancria de sua escolha.
Alguns funcionrios pertencem ao sindicato. Seus registros de empregado tm um
campo para desconto das taxas semanais. As taxas devem ser descontadas de seus
pagamentos. Alm disso, de tempos em tempos o sindicato pode cobrar despesas de
servio individualmente de seus membros. Essas despesas de servio so apresentadas pelo sindicato semanalmente e devem ser descontadas do valor do prximo
pagamento do funcionrio apropriado.
O aplicativo de folha de pagamentos ser executado uma vez a cada dia til e far o
pagamento dos funcionrios apropriados nesse dia. O sistema ser informado sobre
a data em que os funcionrios devem ser pagos; portanto, gerar os pagamentos dos
registros desde a ltima vez que o funcionrio foi pago at a data especificada.
Poderamos comear gerando o esquema de banco de dados. Claramente, esse problema exige algum tipo de banco de dados relacional e os requisitos nos do uma ideia
muito boa de como poderiam ser as tabelas e campos. Seria fcil projetar um esquema
vivel e, ento, construir algumas consultas. Contudo, essa estratgia gerar um aplicativo
para o qual o banco de dados a preocupao central.
Bancos de dados so detalhes de implementao! A considerao do banco de
dados deve ser adiada tanto quanto possvel. Aplicativos demais j foram projetados com
o banco de dados em mente desde o incio e, assim, so inextricavelmente vinculados a
esses bancos de dados. Lembre-se da definio de abstrao: a ampliao do essencial
e a eliminao do irrelevante. Neste estgio do projeto, o banco de dados irrelevante;
apenas uma tcnica usada para armazenar e acessar dados nada mais.

Anlise pelos casos de uso


Em vez de comearmos com os dados do sistema, vamos comear pelo seu comportamento. Afinal, comportamento do sistema que estamos sendo pagos para criar.
Uma maneira de capturar e analisar o comportamento de um sistema criar casos de
uso. Conforme originalmente descrito por Jacobson, os casos de uso so muito parecidos
com a noo de histrias de usurio na XP.1 Um caso de uso como uma histria de usurio
que foi elaborada com um pouco mais de detalhe. Tal elaborao adequada, uma vez que a
histria de usurio tenha sido selecionada para implementao na iterao corrente.
Quando fazemos anlise de caso de uso, examinamos as histrias de usurio e os testes de aceitao para descobrir os tipos de estmulos fornecidos pelos usurios desse sistema. Em seguida, tentamos descobrir como o sistema responde a esses estmulos. Por exemplo, aqui esto as histrias de usurio que nosso cliente escolheu para a prxima iterao:

[Jacobson92].

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: ITERAO 1

361

1. Adicionar um novo funcionrio


2. Excluir um funcionrio
3. Lanar um carto de ponto
4. Lanar um recibo de venda
5. Lanar uma taxa de servio de sindicato
6. Alterar detalhes do funcionrio (por exemplo, salrio por hora, salrio mensal etc.)
7. Executar a folha de pagamentos de hoje
Vamos converter cada uma dessas histrias de usurio em um caso de uso elaborado. No precisamos entrar em muitos detalhes: apenas o suficiente para nos ajudar a
refletir sobre o projeto do cdigo que satisfaa cada histria.

Adicionando funcionrios
Caso de uso 1: Adicionar novo funcionrio
Um novo funcionrio adicionado pelo recebimento de uma transao AddEmp. Essa transao contm o nome, endereo e nmero atribudo ao funcionrio. A transao tem trs
formas:
1.

AddEmp <EmpID> "<name>" "<address>" H <hrly-rate>

2.

AddEmp <EmpID> "<name>" "<address>" S <mtly-slry>

3.

AddEmp <EmpID> "<name>" "<address>" C <mtly-slry> <com-rate>

O registro de empregado criado com seus campos designados apropriadamente.


Alternativa 1: Um erro na estrutura da transao
Se a estrutura da transao inadequada, ela impressa em uma mensagem de erro e nenhuma ao executada.

O caso de uso 1 sugere uma abstrao. A transao AddEmp tem trs formas, todas as
quais compartilham os campos <EmpID>, <name> e <address>. Podemos usar o padro
COMMAND para criar uma classe base abstrata AddEmployeeTransaction com trs
derivadas: AddHourlyEmployeeTransaction, para os funcionrios que trabalham por
hora; AddSalariedEmployeeTransaction, para os assalariados e AddCommissionedEmployeeTransaction, para os que recebem comisso (consulte a Figura 26-1).

362

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

AddEmployee
Transaction
- Name
- EmployeeId
- Address

AddHourly
Employee
Transaction

AddCommissioned
Employee
Transaction

AddSalaried
Employee
Transaction

Figura 26-1
Hierarquia de classes AddEmployeeTransaction.
Essa estrutura obedece perfeitamente ao Princpio da Responsabilidade nica
(SRP), separando cada tarefa em sua prpria classe. A alternativa seria colocar todas
essas tarefas em um nico mdulo. Embora isso possa reduzir o nmero de classes
no sistema e, portanto, torn-lo mais simples, tambm concentraria todo o cdigo de
processamento de transaes em um nico lugar, criando um mdulo grande e potencialmente propenso a erros.
O caso de uso 1 fala especificamente sobre um registro de empregado, o que implica algum tipo de banco de dados. Novamente, nossa predisposio aos bancos de dados
pode nos fazer pensar sobre layouts de registro ou na estrutura de campos de uma tabela de banco de dados relacional, mas devemos resistir a esses impulsos. O que o caso
de uso est realmente pedindo que criemos um funcionrio. Qual o modelo de objeto
de um funcionrio? Uma pergunta melhor poderia ser: o que as trs transaes criam?
A meu ver, elas criam trs tipos de objetos funcionrio, imitando os trs tipos de transaes AddEmp. A Figura 26-2 mostra uma possvel estrutura.

Employee

Hourly
Employee

Commissioned
Employee

Salaried
Employee

Figura 26-2
Possvel hierarquia de classes Employee.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: ITERAO 1

363

Excluindo funcionrios
Caso de uso 2: Excluir um funcionrio
Os funcionrios so excludos quando uma transao DelEmp recebida. A forma dessa transao
a seguinte:

DelEmp <EmpID>
Quando essa transao recebida, o registro de empregado apropriado excludo.

Alternativa 1: EmpID invlido ou desconhecido


Se o campo <EmpID> no est estruturado corretamente ou no se refere a um registro de empregado vlido, a transao impressa com uma mensagem de erro e nenhuma outra ao executada.

Alm da classe DeleteEmployeeTransaction bvia, no estou tendo nenhuma ideia em


particular a partir do caso de uso 2. Vamos prosseguir.

Lanando cartes de ponto

Caso de uso 3: Lanar um carto de ponto (Time Card)


Ao receber uma transao TimeCard, o sistema criar um registro de carto de ponto e o associar
ao registro de empregado apropriado.

TimeCard <empid> <date> <hours>

Alternativa 1: O funcionrio selecionado no recebe por hora


O sistema imprimir uma mensagem de erro apropriada e no executar mais nenhuma ao.

Alternativa 2: Um erro na estrutura da transao


O sistema imprimir uma mensagem de erro apropriada e no executar mais nenhuma ao.

Esse caso de uso indica que algumas transaes s se aplicam a certos tipos de
funcionrios, reforando a ideia de que cada tipo deve ser representado por uma classe
diferente. Nesse caso, tambm existe uma associao implcita entre cartes de ponto e
funcionrios que recebem por hora. A Figura 26-3 mostra um possvel modelo esttico
para essa associao.

364

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Hourly
Employee

0..*
TimeCard

Figura 26-3
Associao entre HourlyEmployee e TimeCard.

Lanando recibos de venda


Caso de uso 4: Lanar um recibo de venda Sales Receipt
Ao receber a transao SalesReceipt, o sistema criar um novo registro de recibo de venda e o
associar ao funcionrio comissionado apropriado.

SalesReceipt <EmpID> <date> <amount>

Alternativa 1: O funcionrio selecionado no comissionado


O sistema imprimir uma mensagem de erro apropriada e no executar mais aes.

Alternativa 2: Um erro na estrutura da transao


O sistema imprimir uma mensagem de erro apropriada e no executar mais aes.

Este caso de uso muito parecido com o caso de uso 3 e implica na estrutura mostrada
na Figura 26-4.

Commissioned
Employee

0..*
SalesReceipt

Figura 26-4
Funcionrios comissionados e recibos de venda.

Lanando uma taxa de servio de sindicato


Este caso de uso mostra que os membros do sindicato no so acessados por IDs de funcionrio. O sindicato mantm seu prprio esquema de numerao para identificao de
seus membros. Assim, o sistema deve ser capaz de associar membros do sindicato a funcionrios. Existem muitas maneiras de prover esse tipo de associao; portanto, para no
sermos arbitrrios, vamos deixar essa deciso para depois. Talvez restries de outras
partes do sistema nos obriguem a agir de uma maneira ou de outra.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: ITERAO 1

365

Caso de uso 5: Lanar uma taxa de servio do sindicato


Ao receber essa transao, o sistema criar um registro de taxa de servio e o associar ao membro
do sindicato apropriado.

ServiceCharge <memberID> <amount>

Alternativa 1: Transao malformada


Se a transao no estiver bem formada ou se <memberID> no se referir a um membro do sindicato
existente, a transao ser impressa com uma mensagem de erro adequada.

Uma coisa certa. Existe uma associao direta entre os membros do sindicato
e suas taxas de servio. A Figura 26-5 mostra um possvel modelo esttico para essa
associao.
0..*
UnionMember

ServiceCharge

Figura 26-5
Membros do sindicato e taxas de servio.

Alterando detalhes do funcionrio


Caso de uso 6: Alterar detalhes do funcionrio
Ao receber essa transao, o sistema alterar um dos detalhes do registro de empregado apropriado.
Existem diversas variaes possveis para essa transao.
ChgEmp <EmpID> Name <name>
ChgEmp <EmpID> Address <address>
ChgEmp <EmpID> Hourly <hourlyRate>
ChgEmp <EmpID> Salaried <salary>
ChgEmp <EmpID> Commissioned <salary> <rate>
ChgEmp <EmpID> Hold
ChgEmp <EmpID> Direct <bank> <account>
ChgEmp <EmpID> Mail <address>
ChgEmp <EmpID> Member <memberID> Dues<rate>
ChgEmp <EmpID> NoMember

Altera o nome do funcionrio


Altera o endereo do funcionrio
Muda para por hora
Muda para assalariado
Muda para comissionado
Mantm o cheque-salrio
Depsito direto
Envia cheque-salrio pelo correio
Coloca o funcionrio no sindicato
Desliga o funcionrio do sindicato

Alternativa 1: Erros de transao


Se a estrutura da transao est incorreta, <EmpID> no se refere a um funcionrio real ou <memberID> j se refere a um membro, o sistema imprimir um erro conveniente e no executar mais aes.

366

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Este caso de uso revelador. Ele nos informou a respeito de todos os aspectos do
funcionrio que devem estar sujeitos mudana. O fato de podermos mudar um funcionrio de salrio por hora para salrio mensal significa que o diagrama da Figura 26-2
certamente invlido. Em vez disso, provavelmente seria mais apropriado usar o padro
STRATEGY para calcular o pagamento. A classe Employee poderia conter uma classe
de estratgia chamada PaymentClassification, como na Figura 26-6. Isso uma vantagem, pois podemos alterar o objeto PaymentClassification sem mudar nenhuma
outra parte do objeto Employee. Quando um funcionrio que recebe por hora alterado
para um funcionrio de salrio mensal, HourlyClassification do objeto Employee
correspondente substitudo por um objeto SalariedClassification.
Existem trs variedades de objetos PaymentClassification. Os objetos HourlyClassification mantm o salrio por hora e uma lista de objetos TimeCard. Os
objetos SalariedClassification mantm o valor do salrio mensal. Os objetos CommissionedClassification mantm um salrio mensal, uma taxa de comisso e uma
lista de objetos SalesReceipt.
O mtodo de pagamento tambm deve ser altervel. A Figura 26-6 implementa essa
ideia usando o padro STRATEGY e derivando trs tipos de classes PaymentMethod. Se
o objeto Employee contm um objeto MailMethod, o funcionrio correspondente ter os
cheques-salrio enviados para o endereo registrado no objeto MailMethod. Se o objeto
Employee contm um objeto DirectMethod, o pagamento do funcionrio correspondente
ser depositado diretamente na conta bancria registrada no objeto DirectMethod. Se o
objeto Employee contm um objeto HoldMethod, os cheques-salrio do funcionrio correspondente sero enviados para o pagador mant-los at que sejam pegos.

interface
Payment
Method

interface
Affiliation

Employee

NoAffiliation

HoldMethod

Payment
Classification
UnionAffiliation

DirectMethod

- Dues

- Bank - Account

Salaried
Classification

- Address

0..*

- Salary

MailMethod

ServiceCharge
Hourly
Classification
- HourlyRate

0..*
TimeCard

Commissioned
Classification
- CommissionRate
- Salary

0..*
SalesReceipt

Figura 26-6
Diagrama de classes revisado de Payroll: o modelo central.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: ITERAO 1

367

Por fim, a Figura 26-6 aplica o padro NULL OBJECT para afiliao no sindicato.
Cada objeto Employee contm um objeto Affiliation, o qual tem duas formas. Se
o objeto Employee contm um objeto NoAffiliation, o pagamento do funcionrio
correspondente no descontado por nenhuma organizao a no ser o empregador.
Contudo, se o objeto Employee contm um objeto UnionAffiliation, esse funcionrio precisa pagar as quotas e taxas de servio que esto registradas nesse objeto
UnionAffiliation.
Esse uso dos padres faz o sistema se adaptar bem ao Princpio do Aberto/Fechado
(OCP). A classe Employee fechada em relao s alteraes no mtodo de pagamento,
na classificao do pagamento e na afiliao ao sindicato. Novos mtodos, classificaes e
afiliaes podem ser adicionados ao sistema sem afetar Employee.
A Figura 26-6 est se tornando nosso modelo central* (ou arquitetura). Ele est
no centro de tudo que o sistema de folha de pagamentos faz. Existiro muitas outras
classes e projetos no aplicativo de folha de pagamentos, mas todos sero resultantes
dessa estrutura fundamental. Evidentemente, essa estrutura no rgida. Ns a modificaremos com o resto.

Dia do pagamento
Caso de uso 7: Executar a folha de pagamentos de hoje
Ao receber a transao de dia de pagamento, o sistema descobre todos os funcionrios que devem
ser pagos na data especificada. Ento o sistema determina quanto eles devem receber e os paga de
acordo com seus mtodos de pagamento selecionados. impresso um relatrio de trilha de auditoria
mostrando a ao executada por cada funcionrio.

Payday <date>

Embora seja fcil entender o objetivo desse caso de uso, no to simples determinar seu impacto sobre a estrutura esttica da Figura 26-6. Precisamos responder
vrias perguntas.
Primeiro, como o objeto Employee sabe fazer o clculo de seu pagamento? Certamente, o sistema precisa totalizar os cartes de ponto de um funcionrio que recebe
por hora e multiplicar pelo valor da hora. Analogamente, o sistema precisa totalizar os
recibos de venda de um funcionrio comissionado, multiplicar pela taxa de comisso
e somar ao salrio bsico. Mas onde isso feito? O lugar ideal parece ser nas derivadas de PaymentClassification. Esses objetos mantm os registros necessrios para
calcular o pagamento; portanto, eles provavelmente devem ter os mtodos para determinar o pagamento. A Figura 26-7 mostra um diagrama de colaborao que descreve
como isso poderia funcionar.

* N. de R.T.: Do original, core model.

368

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Date
1:Pay
Payday
Transaction

Employee

Date
1.1:CalculatePay
Pay

Payment
Classification

Figura 26-7
Calculando o pagamento de um funcionrio.
Quando solicitado a calcular o pagamento, o objeto Employee remete esse pedido
para seu objeto PaymentClassification. O algoritmo usado depende do tipo de PaymentClassification que o objeto Employee contm. As Figuras 26-8 a 26-10 mostram
os trs cenrios possveis.

Hourly
Classification

TimeCard

CalculatePay
hours
GetHours
Date
date
GetDate
para cada
carto de ponto

Figura 26-8
Calculando o pagamento de um funcionrio que recebe por hora.

Commissioned
Classification

SalesReceipt

CalculatePay
amount
GetAmount
Date
date
GetDate

para cada
recibo de venda

Figura 26-9
Calculando o pagamento de um funcionrio comissionado.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: ITERAO 1

Date

369

Pay

1:CalculatePay
Salaried
Classification

Figura 26-10
Calculando o pagamento de um funcionrio assalariado.

Reflexo: encontrando as abstraes subjacentes


At aqui, aprendemos que a anlise de um caso de uso simples pode fornecer muitas informaes e ideias para o projeto de um sistema. As Figuras 26-6 a 26-10 resultaram do
raciocnio a respeito dos casos de uso; ou seja, do raciocnio a respeito do comportamento.
Para usar o OCP de maneira eficiente, devemos procurar abstraes e descobrir
aquelas que formam a base do aplicativo. Frequentemente, essas abstraes no so declaradas ou nem mesmo insinuadas pelos requisitos do aplicativo ou pelos casos de uso.
Os requisitos e os casos de uso podem estar impregnados demais de detalhes para expressar as generalidades das abstraes subjacentes.

Pagamento de funcionrios
Vamos examinar os requisitos novamente. Vemos declaraes como estas: alguns funcionrios trabalham por hora, alguns funcionrios recebem um salrio fixo e alguns
funcionrios recebem uma comisso. Isso sugere a seguinte generalizao: todos os funcionrios so pagos, mas so pagos por esquemas diferentes. A abstrao aqui que todos
os funcionrios so pagos. Nosso modelo de PaymentClassification nas Figuras 26-7
a 26-10 expressa perfeitamente essa abstrao. Assim, essa abstrao j foi descoberta em
nossas histrias de usurio, fazendo-se uma anlise de caso de uso muito simples.

Cronograma de pagamentos
Procurando outras abstraes, encontramos: eles recebem todas as sextas-feiras, eles so
pagos no ltimo dia til do ms e eles recebem a cada duas sextas-feiras. Isso nos leva a
outra generalidade: todos os funcionrios so pagos de acordo com um cronograma. A abstrao aqui a noo de cronograma. Deve ser possvel perguntar a um objeto Employee se
determinada data seu dia de pagamento. Os casos de uso mal mencionam isso. Os requisitos associam o cronograma de um funcionrio e uma classificao de pagamento. Especificamente, os funcionrios que recebem por hora so pagos semanalmente, os assalariados so
pagos mensalmente e os que recebem comisses so pagos quinzenalmente; contudo, essa
associao essencial? A diretiva no poderia mudar um dia, de modo que os funcionrios
pudessem escolher um cronograma especfico ou os funcionrios pertencentes a diferentes
departamentos ou a diferentes divises pudessem ter cronograma diferentes? A diretiva do
cronograma no poderia mudar independentemente da diretiva de pagamento? Certamente,
isso parece provvel.

370

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Se, conforme os requisitos indicam, delegssemos o problema do cronograma para


a classe PaymentClassification, nossa classe no poderia ser fechada em relao aos
problemas de mudana no cronograma. Quando mudssemos a diretiva de pagamento, tambm teramos que testar o cronograma; quando mudssemos os cronogramas,
tambm teramos que testar a diretiva de pagamento. Tanto o OCP como o SRP seriam
violados.
Uma associao entre diretiva de cronograma e de pagamento poderia levar a erros
nos quais uma mudana em uma diretiva de pagamento especfica causaria o agendamento incorreto de certos funcionrios. Erros como esse podem fazer sentido para os programadores, mas amedrontam gerentes e usurios. Eles temem, justificadamente, que,
se os cronogramas podem ser infringidos por uma mudana na diretiva de pagamento,
qualquer mudana feita em qualquer lugar poderia causar problemas em qualquer outra
parte no relacionada do sistema. Eles receiam que no possam prever os efeitos de uma
mudana. Quando os efeitos no podem ser previstos, a confiana perdida e o programa
assume o status de perigoso e instvel na mente de seus gerentes e usurios.
Apesar da natureza bsica da abstrao da agenda, nossa anlise de caso de uso
no nos forneceu qualquer indcio direto sobre sua existncia. Identificar isso exigiu uma
considerao cuidadosa dos requisitos e uma observao dos truques da comunidade de
usurios. A dependncia excessiva de ferramentas e procedimentos e a falta de confiana
na inteligncia e na experincia so receitas para um desastre.
As Figuras 26-11 e 26-12 mostram os modelos estticos e dinmicos da abstrao do cronograma. Como voc pode ver, usamos o padro STRATEGY mais uma vez.
A classe Employee contm a classe abstrata PaymentSchedule. As trs variedades de
PaymentSchedule correspondem aos trs cronogramas conhecidos, por meio dos quais
os funcionrios so pagos.
itsSchedule
Employee

Weekly
Schedule

interface
Payment
Schedule

Monthly
Schedule

Biweekly
Schedule

Figura 26-11
Modelo esttico da abstrao Schedule.

Payment
Schedule

Employee

isPayDay(date)
IsPayday(date)

Figura 26-12
Modelo dinmico da abstrao Schedule.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: ITERAO 1

371

Mtodos de pagamento
Outra generalizao que podemos fazer a partir dos requisitos que todos os funcionrios
recebem seu pagamento por meio de algum mtodo. A abstrao a classe PaymentMethod. Curiosamente, essa abstrao j est expressa na Figura 26-6.

Afiliaes
Os requisitos indicam que os funcionrios podem ser afiliados a um sindicato; no entanto,
o sindicato pode no ser a nica organizao que reivindique uma parte do pagamento
de um funcionrio. Talvez os funcionrios queiram fazer contribuies automticas para
certas instituies beneficentes ou paguem mensalidades para associaes profissionais,
automaticamente. Portanto, a generalizao se torna o funcionrio pode ser afiliado a
muitas organizaes que devem ser pagas automaticamente com seu cheque-salrio.
A abstrao correspondente a classe Affiliation, que est mostrada na Figura 26-6.
Essa figura, entretanto, no mostra o objeto Employee contendo mais de um objeto Affiliation e mostra a presena de uma classe NoAffiliation. Esse projeto no se enquadra
muito bem na abstrao que agora achamos que precisamos. As figuras 26-13 e 26-14 mostram os modelos estticos e dinmicos que representam a abstrao Affiliation.
A lista de objetos Affiliation tornou evidente a necessidade de usar o padro
NULL OBJECT para funcionrios no afiliados. Agora, a lista de afiliaes de um funcionrio que no tem nenhuma afiliao simplesmente estar vazia.

itsAffiliations

Affiliation

Employee
0..*

UnionAffiliation
-Dues

0..*

ServiceCharge
-Date -Amount

Figura 26-13
Estrutura esttica da abstrao Affiliation.

Employee

Payment
Classification

pay = CalculatePay(date)
pay = CalculatePay(date)

[foreach affiliation] fee = GetFee(date)

Figura 26-14
Estrutura dinmica da abstrao Affiliation.

Affiliation

372

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Concluso
Esse um bom comeo para um projeto. Ao transformar histrias de usurio em casos de
uso e procurar abstraes nesses casos de uso, criamos um molde para o sistema. Uma
arquitetura est se desenvolvendo. Note, entretanto, que essa arquitetura foi criada examinando-se apenas as primeiras histrias de usurio. No fizemos uma anlise abrangente
de cada requisito do sistema. Tambm no exigimos que cada histria de usurio e cada
caso de uso fosse perfeito. Alm disso, no fizemos um projeto exaustivo do sistema, com
diagramas de classe e de sequncia para cada detalhe e para cada ttulo que poderamos
imaginar.
Pensar o projeto importante. Pensar o projeto em etapas pequenas e incrementais
fundamental. Fazer muito pior do que fazer pouco. Neste captulo, conclumos uma
quantidade de trabalho quase ideal. Ele parece inacabado, mas suficiente para entendermos e evoluirmos.

Bibliografia
[Jacobson92] Ivar Jacobson, Object-Oriented Software Engineering: A Use Case Driven
Approach, Addison-Wesley, 1992.

Captulo 27

ESTUDO DE CASO DA
FOLHA DE PAGAMENTOS:
IMPLEMENTAO

est mais do que na hora de comearmos a escrever o cdigo que suporta e verifica
os projetos que estamos fabricando. Vou criar esse cdigo em etapas muito pequenas
e incrementais, mas no texto mostrarei a voc apenas os pontos importantes. O fato de
voc ver apenas instantneos de cdigos completos no significa que os escrevi dessa forma. Cada lote de cdigo apresentado passou por dezenas de edies, compilaes e casos
de teste, que fizeram mudanas minsculas e evolutivas no cdigo.
Voc tambm vai ver muita UML. Pense nessa UML como um diagrama rpido que
esboo em um quadro branco para mostrar a voc, meu colega de dupla, o que tenho em
mente. A UML um meio de nos comunicarmos.

Transaes
Comearemos pensando a respeito das transaes que representam os casos de uso. A
Figura 27-1 mostra que representamos transaes como uma interface chamada Transaction, a qual tem um mtodo denominado Execute(). Esse , evidentemente, o padro
COMMAND. A implementao da classe Transaction est mostrada na Listagem 27-1.
interface
Transaction
Transaction
Parser

creates

+ Execute()

Figura 27-1
Interface Transaction.

374

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Listagem 27-1
Transaction.cs
namespace Payroll
{
public interface Transaction
{
void Execute();
}
}

Adicionando funcionrios
A Figura 27-2 mostra uma possvel estrutura para as transaes que adicionam funcionrios. Note que dentro dessas transaes que o cronograma de pagamento dos funcionrios associado s suas classificaes de pagamento. Isso adequado, pois as transaes
so artifcios, em vez de serem parte do modelo central. Assim, por exemplo, o modelo
central no sabe que os funcionrios que recebem por hora so pagos semanalmente. A
associao entre classificao de pagamento e cronograma de pagamento apenas parte
de um dos artifcios perifricos e pode ser alterada a qualquer momento. Por exemplo,
poderamos facilmente adicionar uma transao que nos permitisse mudar cronogramas
de funcionrio.

Listagem 27-2
PayrollTest.TestAddSalariedEmployee
[Test]
public void TestAddSalariedEmployee()
{
int empId = 1;
AddSalariedEmployee t =
new AddSalariedEmployee(empId, "Bob", "Home", 1000.00);
t.Execute();
Employee e = PayrollDatabase.GetEmployee(empId);
Assert.AreEqual("Bob", e.Name);
PaymentClassification pc = e.Classification;
Assert.IsTrue(pc is SalariedClassification);
SalariedClassification sc = pc as SalariedClassification;
Assert.AreEqual(1000.00, sc.Salary, .001);
PaymentSchedule ps = e.Schedule;
Assert.IsTrue(ps is MonthlySchedule);
PaymentMethod pm = e.Method;
Assert.IsTrue(pm is HoldMethod);
}

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

375

interface
Transaction
creates

Employee
+setSchedule
+setClassification
+setMethod

AddEmployee
Transaction
Payroll
Database

global
- empid
- itsAddress
- itsName

HoldMethod
creates

AddHourly
Employee

Add
Commissioned
Employee

AddSalaried
Employee

- hourlyRate

- salary

creates

-salary
-commissionRate

creates

creates

Weekly
Schedule

Monthly
Schedule

Biweekly
Schedule

Hourly
Classification

Salaried
Classification

Commissioned
Classification

Figura 27-2
Modelo esttico de AddEmployeeTransaction.
Essa deciso obedece perfeitamente ao OCP e ao SRP. responsabilidade das transaes (e no do modelo central) especificar a associao entre tipo de pagamento e cronograma
de pagamento. Alm disso, essa associao pode ser mudada sem se alterar o modelo central.
Note tambm que o mtodo de pagamento padro manter o cheque-salrio com o
pagador. Se um funcionrio quer um mtodo de pagamento diferente, ele deve ser alterado
com a transao ChgEmp apropriada.
Como sempre, comeamos a produzir cdigo escrevendo primeiro os testes. O caso
de teste da Listagem 27-2 mostra que AddSalariedTransaction est funcionando corretamente. O cdigo a seguir faz esse caso de teste passar.
O banco de dados da folha de pagamentos
A classe AddEmployeeTransaction usa
outra classe, chamada PayrollDatabase. Por enquanto, essa classe mantm todos os objetos Employee existentes em uma tabela de hashing (Hashtable) que tem chaves fornecidas por empID. A classe tambm mantm uma Hashtable que mapeia objetos memberID
do sindicato em objetos empID. Descobriremos como tornar esse contedo persistente
posteriormente. A estrutura dessa classe aparece na Figura 27-3. PayrollDatabase um
exemplo do padro FAADE.

376

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

EmpID
Employee
Hashtable

Payroll
Database

MemberID
EmpID
Hashtable

Figura 27-3
Estrutura esttica de PayrollDatabase.
A Listagem 27-3 mostra uma implementao rudimentar de PayrollDatabase.
Essa implementao se destina a nos ajudar em nossos casos de teste iniciais. Ela ainda
no contm a tabela de hashing que mapeia IDs de membro em instncias de Employee.

Listagem 27-3
PayrollDatabase.cs
using System.Collections;
namespace Payroll
{
public class PayrollDatabase
{
private static Hashtable employees = new Hashtable();
public static void AddEmployee(int id, Employee employee)
{
employees[id] = employee;
}
public static Employee GetEmployee(int id)
{
return employees[id] as Employee;
}
}
}

Em geral, eu considero implementaes de banco de dados como detalhes. As decises


sobre esses detalhes devem ser adiadas tanto quanto possvel. Se esse banco de dados especficos vai ser implementado com um sistema de gerenciamento de banco de dados relacional
(SGBDR), com arquivos planos ou com um sistema de gerenciamento de banco de dados
orientado a objetos (SGBDOO), irrelevante neste ponto. No momento, estou interessado
apenas na criao da API que fornecer servios de banco de dados para o restante do aplicativo. Encontrarei as implementaes apropriadas para o banco de dados posteriormente.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

377

Adiar os detalhes sobre o banco de dados uma prtica incomum, mas vantajosa.
As decises sobre o banco de dados normalmente podem esperar at termos muito
mais conhecimento sobre o software e suas necessidades. Esperando, evitamos o problema de colocar infraestrutura demais no banco de dados. Em vez disso, implementamos apenas recursos de banco de dados suficientes para as necessidades atuais do
aplicativo.
Usando TEMPLATE METHOD para adicionar funcionrios
A Figura 27-4 mostra o modelo dinmico para a adio de um funcionrio. Note que o objeto AddEmployeeTransaction envia mensagens para si mesmo a fim de obter os objetos PaymentClassification e PaymentSchedule apropriados. Essas mensagens so implementadas nas
derivadas da classe AddEmployeeTransaction. Essa uma aplicao do padro TEMPLATE METHOD.
A Listagem 27-4 mostra a implementao do padro TEMPLATE METHOD na classe
AddEmployeeTransaction. Essa classe implementa o mtodo Execute() para chamar
duas funes virtuais puras que sero implementadas por derivadas. Essas funes,
MakeSchedule() e MakeClassification(), retornam os objetos PaymentSchedule e
PaymentClassification de que o objeto Employee recentemente criado precisa. Em
seguida, o mtodo Execute() vincula esses objetos a Employee e salva Employee em
PayrollDatabase.
Dois aspectos merecem destaque aqui. Primeiro, quando o padro TEMPLATE METHOD aplicado, como aqui, com o nico propsito de criar objetos, ele conhecido pelo
AddEmployee
Transaction
Execute

Payroll
Database

name, address
Employee

GetClassification
SetClassification

GetSchedule
SetClassification

SetMethod

Hold
Method

AddEmployee(employee)

Figura 27-4
Modelo dinmico para adicionar um funcionrio.

378

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Listagem 27-4
AddEmployeeTransaction.cs
namespace Payroll
{
public abstract class AddEmployeeTransaction : Transaction
{
private readonly int empid;
private readonly string name;
private readonly string address;
public AddEmployeeTransaction(int empid,
string name, string address)
{
this.empid = empid;
this.name = name;
this.address = address;
}
protected abstract
PaymentClassification MakeClassification();
protected abstract
PaymentSchedule MakeSchedule();
public void Execute()
{
PaymentClassification pc = MakeClassification();
PaymentSchedule ps = MakeSchedule();
PaymentMethod pm = new HoldMethod();
Employee e = new Employee(empid, name, address);
e.Classification = pc;
e.Schedule = ps;
e.Method = pm;
PayrollDatabase.AddEmployee(empid, e);
}
}
}

nome FACTORY METHOD. Segundo, comum os mtodos de criao no padro FACTORY METHOD serem chamados MakeXXX(). Percebi esses dois problemas enquanto
estava escrevendo o cdigo e por isso que os nomes de mtodo diferem entre o cdigo e
o diagrama.
Eu deveria ter voltado e mudado o diagrama? Nesse caso, no vi necessidade disso.
No pretendo que esse diagrama seja usado como referncia por mais ningum. Alis, se
fosse um projeto real, esse diagrama teria sido desenhado em um quadro branco e provavelmente j teria sido apagado.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

379

Listagem 27-5
AddSalariedEmployee.cs
namespace Payroll
{
public class AddSalariedEmployee : AddEmployeeTransaction
{
private readonly double salary;
public AddSalariedEmployee(int id, string name,
string address, double salary)
: base(id, name, address)
{
this.salary = salary;
}
protected override
PaymentClassification MakeClassification()
{
return new SalariedClassification(salary);
}
protected override PaymentSchedule MakeSchedule()
{
return new MonthlySchedule();
}
}
}

A Listagem 27-5 mostra a implementao da classe AddSalariedEmployee. Essa


classe deriva de AddEmployeeTransaction e implementa os mtodos MakeSchedule()
e MakeClassification() para passar de volta os objetos apropriados para AddEmployeeTransaction.Execute().
Faa AddHourlyEmployee e AddCommissionedEmployee como exerccio. Lembre-se de escrever seus casos de teste primeiro.

Excluindo funcionrios
As figuras 27-5 e 27-6 apresentam os modelos esttico e dinmico das transaes que
excluem funcionrios. A Listagem 27-6 mostra o caso de teste para excluir um funcionrio. A Listagem 27-7 mostra a implementao de DeleteEmployeeTransaction. Essa
uma implementao tpica do padro COMMAND. O construtor armazena os dados nos
quais o mtodo Execute() opera.

380

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

interface
Transaction

Delete
Employee

Payroll
Database

- empid

Figura 27-5
Modelo esttico da transao DeleteEmployee.

Delete
Employee
Transaction

Payroll
Database

Execute
DeleteEmployee(empid)

Figura 27-6
Modelo dinmico da transao DeleteEmployee.

Listagem 27-6
PayrollTest.DeleteEmployee
[Test]
public void DeleteEmployee()
{
int empId = 4;
AddCommissionedEmployee t =
new AddCommissionedEmployee(
empId, "Bill", "Home", 2500, 3.2);
t.Execute();
Employee e = PayrollDatabase.GetEmployee(empId);
Assert.IsNotNull(e);
DeleteEmployeeTransaction dt =
new DeleteEmployeeTransaction(empId);
dt.Execute();
e = PayrollDatabase.GetEmployee(empId);
Assert.IsNull(e);
}

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

381

Listagem 27-7
DeleteEmployeeTransaction.cs
namespace Payroll
{
public class DeleteEmployeeTransaction : Transaction
{
private readonly int id;
public DeleteEmployeeTransaction(int id)
{
this.id = id;
}
public void Execute()
{
PayrollDatabase.DeleteEmployee(id);
}
}
}

A esta altura, voc j deve ter observado que PayrollDatabase fornece acesso esttico a
seus campos. Na verdade, PayrollDatabase.employees uma varivel global. Durante
dcadas, livros-texto e professores desencorajaram o uso de variveis globais, com bons
motivos. Apesar disso, as variveis globais no so intrinsecamente ms ou prejudiciais.
Esta situao em particular uma escolha ideal para uma varivel global. Sempre haver
apenas uma instncia dos mtodos e variveis de PayrollDatabase e ela precisa ser
conhecida por um pblico amplo.
Talvez voc preferisse usar os padres SINGLETON ou MONOSTATE para tanto.
Eles atingiriam o objetivo, verdade. Contudo, eles fariam isso usando variveis globais
eles mesmos. Um SINGLETON ou um MONOSTATE , por definio, uma entidade global. Nesse caso, achei que um SINGLETON ou um MONOSTATE teriam o mau cheiro
da complexidade desnecessria. mais fcil simplesmente manter o banco de dados
global.

Cartes de ponto, recibos de venda e taxas de servio


A Figura 27-7 mostra a estrutura esttica da transao que lana cartes de ponto de
funcionrios. A Figura 27-8 mostra o modelo dinmico. A ideia bsica que a transao recebe o objeto Employee de PayrollDatabase, solicita o objeto PaymentClassification de Employee e, ento, cria e adiciona um objeto TimeCard a esse objeto
PaymentClassification.

382

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

interface
Transaction

TimeCard
Transaction
Payroll
Database

- Date
- Hours
- empid
creates

TimeCard
Employee
- Date
- Hours

Hourly
Classification

Payment
Classification

Figura 27-7
Estrutura esttica de TimeCardTransaction.

TimeCard
Transaction

Payroll
Database

pc:Hourly
Classification

Employee

GetEmployee
Execute
empid

pc faz downcast de
PaymentClassification

Employee
pc = GetPaymentClassification()

tc:TimeCard
Hours, Date
AddTimeCard(tc)

Figura 27-8
Modelo dinmico para lanar um objeto TimeCard.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

383

Listagem 27-8
PayrollTest.TestTimeCardTransaction
[Test]
public void TestTimeCardTransaction()
{
int empId = 5;
AddHourlyEmployee t =
new AddHourlyEmployee(empId, "Bill", "Home", 15.25);
t.Execute();
TimeCardTransaction tct =
new TimeCardTransaction(
new DateTime(2005, 7, 31), 8.0, empId);
tct.Execute();
Employee e = PayrollDatabase.GetEmployee(empId);
Assert.IsNotNull(e);
PaymentClassification pc = e.Classification;
Assert.IsTrue(pc is HourlyClassification);
HourlyClassification hc = pc as HourlyClassification;
TimeCard tc = hc.GetTimeCard(new DateTime(2005, 7, 31));
Assert.IsNotNull(tc);
Assert.AreEqual(8.0, tc.Hours);
}

Note que no podemos adicionar objetos TimeCard a objetos PaymentClassification gerais; s podemos adicion-los a objetos HourlyClassification. Isso significa
que devemos fazer um downcast* no objeto PaymentClassification recebido do objeto
Employee para um objeto HourlyClassification. Esse um bom uso para o operador
as em C# (consulte a Listagem 27-10).
A Listagem 27-8 mostra um dos casos de teste que verifica se cartes de ponto
podem ser adicionados para funcionrios que recebem por hora. Esse cdigo de teste
simplesmente cria um funcionrio pago por hora e o adiciona ao banco de dados. Ento,
ele cria um objeto TimeCardTransaction, chama Execute() e verifica se o objeto HourlyClassification do funcionrio contm o objeto TimeCard apropriado.
A Listagem 27-9 mostra a implementao da classe TimeCard. No momento, essa
classe no tem quase nada. Trata-se simplesmente de uma classe de dados.
A Listagem 27-10 mostra a implementao da classe TimeCardTransaction. Observe
o uso de objetos InvalidOperationException. Essa no uma prtica de longo prazo
particularmente boa, mas basta isso no incio do desenvolvimento. Aps termos alguma ideia
de como devem ser as excees, poderemos voltar e criar classes de exceo significativas.
As Figuras 27-9 e 27-10 mostram um projeto semelhante para a transao que lana recibos de venda para um funcionrio comissionado. Deixei a implementao dessas
classes como exerccio.

* N. de R.T.: Converso de um tipo mais genrico para um mais especfico.

384

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Listagem 27-9
TimeCard.cs
using System;
namespace Payroll
{
public class TimeCard
{
private readonly DateTime date;
private readonly double hours;
public TimeCard(DateTime date, double hours)
{
this.date = date;
this.hours = hours;
}
public double Hours
{
get { return hours; }
}
public DateTime Date
{
get { return date; }
}
}
}

Listagem 27-10
TimeCardTransaction.cs
using System;
namespace Payroll
{
public class TimeCardTransaction : Transaction
{
private readonly DateTime date;
private readonly double hours;
private readonly int empId;
public TimeCardTransaction(
DateTime date, double hours, int empId)
{
this.date = date;
this.hours = hours;
this.empId = empId;
}

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

public void Execute()


{
Employee e = PayrollDatabase.GetEmployee(empId);
if (e != null)
{
HourlyClassification hc =
e.Classification as HourlyClassification;
if (hc != null)
hc.AddTimeCard(new TimeCard(date, hours));
else
throw new InvalidOperationException(
"Tried to add timecard to " +
"non-hourly employee");
}
else
throw new InvalidOperationException(
"No such employee.");
}
}
}

interface
Transaction

Payroll
Database
interface
SalesReceipt
Transaction
Employee

Payment
Classification

- Date
- Amount
- empid

SalesReceipt
- Date - Amount

Commissioned
Classification

Figura 27-9
Modelo esttico de SalesReceiptTransaction.

385

386

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

SalesReceipt
Transaction

Payroll
Database

pc:Commissioned
Classification

Employee

GetEmployee
Execute
empid

pc faz downcast de
PaymentClassification.

Employee
pc = GetPaymentClassification()

sr:SalesReceipt
Amount, Date
AddSalesReceipt(sr)

Figura 27-10
Modelo dinmico de SalesReceiptTransaction.

As Figuras 27-11 e 27-12 mostram o projeto da transao que lana taxas de


servio para membros do sindicato. Esses projetos indicam um descasamento entre o
modelo de transao e o modelo central que criamos. Nosso objeto Employee bsico
pode ser afiliado a muitas organizaes diferentes, mas o modelo de transao presume que qualquer afiliao deve ser uma afiliao ao sindicato. Assim, o modelo de
transao no fornece nenhuma maneira de identificar um tipo de afiliao em particular. Em vez disso, ele simplesmente presume que, se estamos lanando uma taxa de
servio, o funcionrio afiliado ao sindicato.
O modelo dinmico resolve esse dilema, procurando UnionAffiliation no conjunto de objetos Affiliation contidos no objeto Employee. Ento, o modelo adiciona o
objeto ServiceCharge a esse objeto UnionAffiliation.
A Listagem 27-11 mostra o caso de teste para ServiceChargeTransaction. Ele
simplesmente cria um funcionrio pago por hora, adiciona a ele um objeto UnionAffiliation, garante que a ID de membro apropriada seja registrada em PayrollDatabase, cria e executa uma transao ServiceChargeTransaction e, por fim,
garante que o objeto ServiceCharge apropriado seja realmente adicionado ao objeto
UnionAffiliation de Employee.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

interface
Transaction

Payroll
Database
ServiceCharge
Transaction
Employee

interface
Affiliation

- Date
- Amount
- memberID

ServiceCharge
Union
Affiliation

- Date - Amount

Figura 27-11
Modelo esttico de ServiceChargeTransaction.

ServiceCharge
Transaction

Payroll
Database

affiliation:
UnionAffiliation

Employee

GetUnionMember
Affiliation sofre downcast
para UnionAffiliation
de Affiliation.

Execute
memberID

Employee

affiliations = getAffiliations()
Pesquisa as afiliaes para encontrar
uma que possa sofrer downcast para
um objeto UnionAffiliation.

sc:ServiceCharge
Amount, Date
AddServiceCharge(sc)

Figura 27-12
Modelo dinmico de ServiceChargeTransaction.

387

388

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Listagem 27-11
PayrollTest.AddServiceCharge
[Test]
public void AddServiceCharge()
{
int empId = 2;
AddHourlyEmployee t = new AddHourlyEmployee(
empId, "Bill", "Home", 15.25);
t.Execute();
Employee e = PayrollDatabase.GetEmployee(empId);
Assert.IsNotNull(e);
UnionAffiliation af = new UnionAffiliation();
e.Affiliation = af;
int memberId = 86; // Maxwell Smart
PayrollDatabase.AddUnionMember(memberId, e);
ServiceChargeTransaction sct =
new ServiceChargeTransaction(
memberId, new DateTime(2005, 8, 8), 12.95);
sct.Execute();
ServiceCharge sc =
af.GetServiceCharge(new DateTime(2005, 8, 8));
Assert.IsNotNull(sc);
Assert.AreEqual(12.95, sc.Amount, .001);
}

Listagem 27-12
ServiceChargeTransaction.cs
using System;
namespace Payroll
{
public class ServiceChargeTransaction : Transaction
{
private readonly int memberId;
private readonly DateTime time;
private readonly double charge;
public ServiceChargeTransaction(
int id, DateTime time, double charge)
{
this.memberId = id;
this.time = time;
this.charge = charge;
}

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

389

public void Execute()


{
Employee e = PayrollDatabase.GetUnionMember(memberId);
if (e != null)
{
UnionAffiliation ua = null;
if(e.Affiliation is UnionAffiliation)
ua = e.Affiliation as UnionAffiliation;
if (ua != null)
ua.AddServiceCharge(
new ServiceCharge(time, charge));
else
throw new InvalidOperationException(
"Tries to add service charge to union"
+ "member without a union affiliation");
}
else
throw new InvalidOperationException(
"No such union member.");
}
}
}

Quando desenhei a UML da Figura 27-12, pensei que substituir NoAffiliation


por uma lista de afiliaes era um projeto melhor. Achei que seria mais flexvel e menos
complexo. Afinal, eu poderia adicionar novas afiliaes sempre que quisesse e no
precisaria criar a classe NoAffiliation. Contudo, ao escrever o caso de teste da Listagem 27-11, percebi que configurar a propriedade Affiliation em Employee era
melhor do que chamar AddAffiliation. Afinal, os requisitos no pedem para que um
funcionrio tenha mais de um objeto Affiliation; portanto, no h necessidade de
usar uma converso para fazer uma escolha entre muitos tipos em potencial. Fazer isso
seria mais complexo do que o necessrio.
Esse um exemplo do motivo pelo qual pode ser perigoso produzir UML demais sem
verific-la no cdigo. O cdigo pode revelar muitas coisas sobre seu projeto que a UML
no revela. Aqui, eu estava colocando estruturas na UML que no eram necessrias. Talvez
um dia elas pudessem ser teis, mas no precisam ser mantidas desde j. O custo dessa
manuteno pode no compensar o benefcio.
Nesse caso, mesmo que o custo de manter o downcast seja relativamente baixo, no vou utilizar isso; muito mais simples implementar sem uma lista de objetos Affiliation. Assim, manterei o padro NULL OBJECT funcionando na classe
NoAffiliation.
A Listagem 27-12 mostra a implementao de ServiceChargeTransaction. Ela
realmente muito mais simples sem o loop que procura objetos UnionAffiliation.
Ela simplesmente recebe o objeto Employee do banco de dados, faz um downcast do
seu objeto Affiliation para um objeto UnionAffiliation e adiciona a ele o objeto
ServiceCharge.

390

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Alterando funcionrios
A Figura 27-13 mostra a estrutura esttica das transaes que alteram os atributos de
um funcionrio. Essa estrutura extrada facilmente do Caso de Uso 6. Todas as transaes recebem um argumento EmpID, de modo que podemos criar uma classe base de
nvel superior chamada ChangeEmployeeTransaction. Abaixo dessa classe base esto
as classes que alteram atributos simples, como ChangeNameTransaction e ChangeAddressTransaction. As transaes que mudam classificaes tm uma semelhana de
propsito no sentido de que todas elas modificam o mesmo campo do objeto Employee.
Assim, elas podem ser agrupadas sob uma classe base abstrata, ChangeClassificationTransaction. O mesmo vale para as transaes que alteram o pagamento e as afiliaes. Isso pode ser visto na estrutura de ChangeMethodTransaction e de ChangeAffiliationTransaction.

interface
Transaction
Payroll
Database

Change
Employee
Transaction

Employee

- empid

ChangeName
Transaction

interface
Payment
Classification

ChangeAddress
Transaction

- name

- address

ChangeHourly
Transaction
- hourlyRate

Change
Classification
Transaction

interface
Payment
Schedule

ChangeSalaried
Transaction

Change
Commissioned
Transaction

- salary
-salary
-commissionRate

creates

creates

creates

Hourly
Classification

Salaried
Classification

Commissioned
Classification

Weekly
Schedule

Monthly
Schedule

Biweekly
Schedule

Figura 27-13
Modelo esttico de ChangeEmployeeTransaction.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

Change
Employee
Transaction

interface
Payment
Method

391

Payroll
Database

-empid
Employee
Employee

Change
Method
Transaction

ChangeDirect
Transaction

ChangeHold
Transaction

-bank -account
creates

Change
Affiliation
Transaction

ChangeMail
Transaction

Change
Member
Transaction

-address
creates

DirectMethod

creates

creates

MailMethod

Union
Affiliation

HoldMethod
-bank -account

-address

interface
Affiliation

Change
Unaffiliated
Transaction

-dues

Figura 27-13
(Continuao).
A Figura 27-14 mostra o modelo dinmico de todas as transaes de alterao. Novamente, vemos o padro TEMPLATE METHOD em uso. Em cada caso, o objeto Employee
correspondente ao EmpID deve ser recuperado de PayrollDatabase. Assim, a funo
Execute de ChangeEmployeeTransaction implementa esse comportamento e, ento,
envia a mensagem Change para si mesma. Esse mtodo ser declarado como virtual e
implementado nas derivadas, como mostrado nas Figuras 27-15 e 27-16.
A Listagem 27-13 mostra o caso de teste de ChangeNameTransaction. Esse caso de
teste simples usa a transao AddHourlyEmployee para criar um funcionrio pago por
hora, chamado Bill. Ento, ele cria e executa uma transao ChangeNameTransaction

empid
1:Execute

Employee

1.1:GetEmployee
Change
Employee
Transaction

global

Payroll
Database

1.2:Change
Employee

Figura 27-14
Modelo dinmico de ChangeEmployeeTransaction.

392

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

que deve alterar o nome do funcionrio para Bob. Por fim, ele busca a instncia Employee
de PayrollDatabase e verifica se o nome foi alterado.
name

Employee
1:Change

1.1:SetName
ChangeName
Transaction

Employee
global

Figura 27-15
Modelo dinmico de ChangeNameTransaction.

Employee
1:Change

address
1.1:SetAddress
ChangeAddress
Transaction

Employee
parameter

Figura 27-16
Modelo dinmico de ChangeAddressTransaction.

Listagem 27-13
PayrollTest.TestChangeNameTransaction()
[Test]
public void TestChangeNameTransaction()
{
int empId = 2;
AddHourlyEmployee t =
new AddHourlyEmployee(empId, "Bill", "Home", 15.25);
t.Execute();
ChangeNameTransaction cnt =
new ChangeNameTransaction(empId, "Bob");
cnt.Execute();
Employee e = PayrollDatabase.GetEmployee(empId);
Assert.IsNotNull(e);
Assert.AreEqual("Bob", e.Name);
}

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

393

A Listagem 27-14 mostra a implementao da classe base abstrata ChangeEmployeeTransaction. A estrutura do padro TEMPLATE METHOD est claramente em evidncia.
O mtodo Execute() simplesmente l a instncia de Employee apropriada de PayrollDatabase e, se tiver xito, chama o mtodo abstrato Change().

Listagem 27-14
ChangeEmployeeTransaction.cs
using System;
namespace Payroll
{
public abstract class ChangeEmployeeTransaction : Transaction
{
private readonly int empId;
public ChangeEmployeeTransaction(int empId)
{
this.empId = empId;
}
public void Execute()
{
Employee e = PayrollDatabase.GetEmployee(empId);
if(e != null)
Change(e);
else
throw new InvalidOperationException(
"No such employee.");
}
protected abstract void Change(Employee e);
}
}

A Listagem 27-15 mostra a implementao de ChangeNameTransaction. A segunda


metade do padro TEMPLATE METHOD pode ser vista facilmente. O mtodo Change()
implementado para mudar o nome do argumento Employee. A estrutura de ChangeAddressTransaction muito semelhante e foi deixada como exerccio.

394

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Listagem 27-15
ChangeNameTransaction.cs
namespace Payroll
{
public class ChangeNameTransaction :
ChangeEmployeeTransaction
{
private readonly string newName;
public ChangeNameTransaction(int id, string newName)
: base(id)
{
this.newName = newName;
}
protected override void Change(Employee e)
{
e.Name = newName;
}
}
}

Mudando a classificao
A Figura 27-17 mostra como a hierarquia sob ChangeClassificationTransaction imaginada. O padro TEMPLATE METHOD usado
mais uma vez. Todas essas transaes devem criar um novo objeto PaymentClassification e, ento, envi-lo ao objeto Employee. Isso conseguido enviando-se a mensagem GetClassification para ele mesmo. Esse mtodo abstrato implementado
em cada uma das classes derivadas de ChangeClassificationTransaction, como
mostrado nas Figuras 27-18 a 27-20.

Change
Classification
Transaction

emp:Employee

Change(emp)
paymentClassification :=
GetClassification()
SetClassification(paymentClassification)

paymentSchedule :=
GetSchedule()
SetSchedule(paymentSchedule)

Figura 27-17
Modelo dinmico de ChangeClassificationTransaction.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

ChangeHourly
Transaction
GetClassification
hourlyRate
hc:
Hourly
Classification

hc
GetSchedule

ws::
WeeklySchedule

ws

Figura 27-18
Modelo dinmico de ChangeHourlyTransaction.

ChangeSalaried
Transaction
GetClassification
salary
sc:
Salaried
Classification

sc
GetSchedule

ms::
MonthlySchedule

ms

Figura 27-19
Modelo dinmico de ChangeSalariedTransaction.

Change
Commissioned
Transaction
GetClassification
cc

commissionRate,
salary
cc:
Commissioned
Classification

GetSchedule
bws

bws::
Biweekly
Schedule

Figura 27-20
Modelo dinmico de ChangeCommissionedTransaction.

395

396

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

A Listagem 27-16 mostra o caso de teste para ChangeHourlyTransaction. O caso


de teste usa uma transao AddCommissionedEmployee para criar um funcionrio
comissionado e, ento, cria uma transao ChangeHourlyTransaction e a executa. A
transao busca o funcionrio alterado e verifica se seu objeto PaymentClassification
HourlyClassification com o salrio por hora adequado e se seu objeto PaymentSchedule WeeklySchedule.
A Listagem 27-17 mostra a implementao da classe base abstrata ChangeClassificationTransaction. Mais uma vez, fcil distinguir o padro TEMPLATE METHOD.
O mtodo Change() chama os dois mtodos get abstratos para as propriedades Classification e Schedule e usa os valores dessas propriedades para definir a classificao e
a agenda (schedule) do objeto Employee.
A deciso de usar propriedades em vez de funes get foi tomada medida que o
cdigo estava sendo escrito. Novamente, vemos a tenso entre os diagramas e o cdigo.
A Listagem 27-18 mostra a implementao da classe ChangeHourlyTransaction.
Essa classe completa o padro TEMPLATE METHOD, implementando os mtodos get das
propriedades Classification e Schedule que herdou de ChangeClassificationTransaction. A classe implementa o mtodo get Classification para retornar um
objeto HourlyClassification recentemente criado e implementa o mtodo get Schedule para retornar um objeto WeeklySchedule recentemente criado.

Listagem 27-16
PayrollTest.TestChangeHourlyTransaction()
[Test]
public void TestChangeHourlyTransaction()
{
int empId = 3;
AddCommissionedEmployee t =
new AddCommissionedEmployee(
empId, "Lance", "Home", 2500, 3.2);
t.Execute();
ChangeHourlyTransaction cht =
new ChangeHourlyTransaction(empId, 27.52);
cht.Execute();
Employee e = PayrollDatabase.GetEmployee(empId);
Assert.IsNotNull(e);
PaymentClassification pc = e.Classification;
Assert.IsNotNull(pc);
Assert.IsTrue(pc is HourlyClassification);
HourlyClassification hc = pc as HourlyClassification;
Assert.AreEqual(27.52, hc.HourlyRate, .001);
PaymentSchedule ps = e.Schedule;
Assert.IsTrue(ps is WeeklySchedule);
}

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

Listagem 27-17
ChangeClassificationTransaction.cs
namespace Payroll
{
public abstract class ChangeClassificationTransaction
: ChangeEmployeeTransaction
{
public ChangeClassificationTransaction(int id)
: base (id)
{}
protected override void Change(Employee e)
{
e.Classification = Classification;
e.Schedule = Schedule;
}
protected abstract
PaymentClassification Classification { get; }
protected abstract PaymentSchedule Schedule { get; }
}
}

Listagem 27-18
ChangeHourlyTransaction.cs
namespace Payroll
{
public class ChangeHourlyTransaction
: ChangeClassificationTransaction
{
private readonly double hourlyRate;
public ChangeHourlyTransaction(int id, double hourlyRate)
: base(id)
{
this.hourlyRate = hourlyRate;
}
protected override PaymentClassification Classification
{
get { return new HourlyClassification(hourlyRate); }
}
protected override PaymentSchedule Schedule
{
get { return new WeeklySchedule(); }
}
}
}

397

398

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Como sempre, ChangeSalariedTransaction e ChangeCommissionedTransaction so deixados como exerccio.


Um mecanismo semelhante usado para a implementao de ChangeMethodTransaction. A propriedade abstrata Method usada para selecionar a derivada correta de
PaymentMethod, a qual ento enviada para o objeto Employee (consulte as Figuras
27-21 a 27-24).

Change
Method
Transaction

emp:Employee

Change(emp)
paymentMethod := GetMethod()
SetMethod(paymentMethod)

Figura 27-21
Modelo dinmico de ChangeMethodTransaction.

Change
Direct
Transaction
GetMethod
bank, account
dm:
DirectMethod

dm

Figura 27-22
Modelo dinmico de ChangeDirectTransaction.

Change
Mail
Transaction
GetMethod
address
mm

mm:
MailMethod

Figura 27-23
Modelo dinmico de ChangeMailTransaction.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

399

Change
Hold
Transaction
GetMethod
hm:
HoldMethod

hm

Figura 27-24
Modelo dinmico de ChangeHoldTransaction.

A implementao dessas classes se mostrou simples e previsvel. Elas tambm so


deixadas como exerccio.
A Figura 27-25 mostra a implementao de ChangeAffiliationTransaction. Mais
uma vez, usamos o padro TEMPLATE METHOD para selecionar a derivada de Affiliation que deve ser enviada para o objeto Employee. (Consulte as Figuras 27-26 a 27-28.)

Change
Affiliation
Transaction

emp:Employee

Change(emp)
aff := GetAffiliation()

SetAffiliation(aff)

Figura 27-25
Modelo dinmico de ChangeAffiliationTransaction.

Change
Member
Transaction
GetAffiliation
dues
ua

ua:
UnionAffiliation

Figura 27-26
Modelo dinmico de ChangeMemberTransaction.

400

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Change
Unaffiliated
Transaction
GetAffiliation
na

na:
NoAffiliation

Figura 27-27
Modelo dinmico de ChangeUnaffiliatedTransaction.

interface
Transaction

Payday
Transaction

Payroll
Database

-date
Employee

Figura 27-28
Modelo esttico de PaydayTransaction.

O que eu estava fumando?


Fiquei bastante surpreso quando implementei esse projeto. Examine atentamente os
diagramas dinmicos das transaes de afiliao. Voc consegue identificar o problema?
Como sempre, comecei a implementao escrevendo o caso de teste de ChangeMemberTransaction. Voc pode ver esse caso de teste na Listagem 27-19. O caso de teste
comea muito simples. Ele cria um funcionrio pago por hora chamado Bill e, depois,
cria e executa uma transao ChangeMemberTransaction para colocar Bill no sindicato.
Em seguida, ele verifica se Bill tem um objeto UnionAffiliation vinculado e se UnionAffiliation tem o valor das taxas correto.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

401

Listagem 27-19
PayrollTest.ChangeUnionMember()
[Test]
public void ChangeUnionMember()
{
int empId = 8;
AddHourlyEmployee t =
new AddHourlyEmployee(empId, "Bill", "Home", 15.25);
t.Execute();
int memberId = 7743;
ChangeMemberTransaction cmt =
new ChangeMemberTransaction(empId, memberId, 99.42);
cmt.Execute();
Employee e = PayrollDatabase.GetEmployee(empId);
Assert.IsNotNull(e);
Affiliation affiliation = e.Affiliation;
Assert.IsNotNull(affiliation);
Assert.IsTrue(affiliation is UnionAffiliation);
UnionAffiliation uf = affiliation as UnionAffiliation;
Assert.AreEqual(99.42, uf.Dues, .001);
Employee member =PayrollDatabase.GetUnionMember(memberId);
Assert.IsNotNull(member);
Assert.AreEqual(e, member);
}

A surpresa est escondida nas ltimas linhas do caso de teste. Essas linhas garantem
que PayrollDatabase registrou a afiliao de Bill no sindicato. Nos diagramas UML existentes nada garante que isso acontea. A UML est preocupada apenas com a derivada de
Affiliation apropriada ser vinculada a Employee. Eu no havia reparado no que estava
faltando, e voc?
Codifiquei alegremente as transaes de acordo com os diagramas, mas o teste de
unidade falhou. Uma vez ocorrida a falha, era bvio o que eu tinha esquecido. A soluo
para o problema, no entanto, no era bvia. Como fao a afiliao ser registrada por
ChangeMemberTransaction, mas apagada por ChangeUnaffiliatedTransaction?
A resposta foi adicionar a ChangeAffiliationTransaction outro mtodo abstrato,
chamado RecordMembership(Employee). Essa funo implementada em ChangeMemberTransaction para vincular o objeto memberId instncia de Employee. Em ChangeUnaffiliatedTransaction, ela implementada para apagar o registro de afiliao.
A Listagem 27-20 mostra a implementao resultante da classe base abstrata
ChangeAffiliationTransaction. Novamente, o uso do padro TEMPLATE METHOD
bvio.
A Listagem 27-21 mostra a implementao de ChangeMemberTransaction. Ela no
muito complicada nem interessante. Por outro lado, a implementao de ChangeUnaffiliatedTransaction, na Listagem 27-22, um pouco mais substancial. A funo RecordMembership precisa decidir se o funcionrio atual membro do sindicato. Se for, ela obtm o
objeto memberId de UnionAffiliation e apaga o registro de afiliao.

402

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Listagem 27-20
ChangeAffiliationTransaction.cs
namespace Payroll
{
public abstract class ChangeAffiliationTransaction :
ChangeEmployeeTransaction
{
public ChangeAffiliationTransaction(int empId)
: base(empId)
{}
protected override void Change(Employee e)
{
RecordMembership(e);
Affiliation affiliation = Affiliation;
e.Affiliation = affiliation;
}
protected abstract Affiliation Affiliation { get; }
protected abstract void RecordMembership(Employee e);
}
}

Listagem 27-21
ChangeMemberTransaction.cs
namespace Payroll
{
public class ChangeMemberTransaction :
ChangeAffiliationTransaction
{
private readonly int memberId;
private readonly double dues;
public ChangeMemberTransaction(
int empId, int memberId, double dues)
: base(empId)
{

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

this.memberId = memberId;
this.dues = dues;
}
protected override Affiliation Affiliation
{
get { return new UnionAffiliation(memberId, dues); }
}
protected override void RecordMembership(Employee e)
{
PayrollDatabase.AddUnionMember(memberId, e);
}
}
}

Listagem 27-22
ChangeUnaffiliatedTransaction.cs
namespace Payroll
{
public class ChangeUnaffiliatedTransaction
: ChangeAffiliationTransaction
{}
public ChangeUnaffiliatedTransaction(int empId)
: base(empId)
{}
protected override Affiliation Affiliation
{
get { return new NoAffiliation(); }
}
protected override void RecordMembership(Employee e)
{
Affiliation affiliation = e.Affiliation;
if(affiliation is UnionAffiliation)
{
UnionAffiliation unionAffiliation =
affiliation as UnionAffiliation;
int memberId = unionAffiliation.MemberId;
PayrollDatabase.RemoveUnionMember(memberId);
}
}
}
}

403

404

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

No posso dizer que estou contente com esse projeto. Incomoda-me o fato de ChangeUnaffiliatedTransaction precisar saber sobre UnionAffiliation. Eu poderia
resolver isso colocando os mtodos abstratos RecordMembership e EraseMembership
na classe Affiliation. Contudo, isso foraria UnionAffiliation e NoAffiliation a
saber sobre PayrollDatabase. E no estou muito contente com isso tambm.1
Apesar disso, do modo como est, a implementao bastante simples e viola o OCP
apenas ligeiramente. O mais interessante que poucos mdulos do sistema sabem sobre
ChangeUnaffiliatedTransaction, de modo que suas dependncias extras no vo causar muitos danos.

Pagando os funcionrios
Finalmente, hora de considerar a transao que est na raiz desse aplicativo: a que
instrui o sistema a pagar os funcionrios apropriados. A Figura 27-28 mostra a estrutura
esttica da classe PaydayTransaction. As figuras 27-29 e 27-30 descrevem o comportamento dinmico.
Os modelos dinmicos expressam bastante comportamento polimrfico. O algoritmo utilizado pela mensagem CalculatePay depende do tipo de PaymentClassification que o objeto Employee contm. O algoritmo utilizado para determinar se uma
data um dia de pagamento depende do tipo de PaymentSchedule que Employee contm. O algoritmo utilizado para enviar o pagamento para Employee depende do tipo do
objeto PaymentMethod. Esse alto grau de abstrao permite que os algoritmos sejam
fechados em relao adio de novos tipos de classificaes de pagamento, agendas,
afiliaes ou mtodos de pagamento.

Payday
Transaction

Payroll
Database

Employee

Execute
GetEmployees
list<Employee*>

Payday(date)

Para cada employee

Figura 27-29
Modelo dinmico de PaydayTransaction.

Eu poderia usar o padro VISITOR para resolver esse problema, mas isso provavelmente seria uma engenharia exagerada.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

405

Payment
Schedule

Employee

Payday(date)
IsPayDay(date)
NO

Figura 27-30
Cenrio do modelo dinmico: o pagamento no hoje.
Os algoritmos retratados na Figura 27-31 e na Figura 27-32 introduzem a noo de
lanamento. Aps o valor correto do pagamento ser calculado e enviado para Employee, o
pagamento lanado; isto , os registros envolvidos no pagamento so atualizados. Assim,
podemos definir o mtodo CalculatePay como o que calcula o pagamento desde o ltimo
lanamento at a data especificada.
Desenvolvedores e decises comerciais
De onde veio essa noo de lanamento? Ela
certamente no foi mencionada nas histrias de usurio nem nos casos de uso. Na verdade, eu a criei como um modo de resolver um problema que percebi. Eu estava preocupado
com o fato de que o mtodo Payday poderia ser chamado vrias vezes com a mesma data
ou com uma data no mesmo perodo de pagamento; portanto, eu queria garantir que o
funcionrio no fosse pago mais de uma vez. Fiz isso de iniciativa prpria, sem perguntar
para meu cliente. Simplesmente parecia a coisa certa a fazer.

Payment
Schedule

Employee

Payment
Classification

Payday(date)
IsPayDay(date)
YES
amount := CalculatePay(date)
Pay(amount)
Post(date)

Figura 27-31
Cenrio do modelo dinmico: o pagamento hoje.

Payment
Method

406

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Payment
Classification

Employee

Affiliation

Post(date)
Post(date)
Post(date)
Para cada afiliao em Employee

Figura 27-32
Cenrio do modelo dinmico: lanando o pagamento.
Na verdade, eu tomei uma deciso comercial, resolvendo que vrias execues do programa de folha de pagamentos deviam produzir resultados diferentes. Eu devia ter
consultado meu cliente ou gerente de projeto, pois eles poderiam discordar.
Ao verificar com o cliente,2 descobri que a ideia do lanamento era contrria ao
seu objetivo. O cliente quer executar o sistema de folha de pagamentos e, ento, examinar os cheques-salrio. Se algum deles estiver errado, o cliente quer corrigir as informaes da folha de pagamentos e executar o programa novamente. O cliente me diz
que eu nunca devo considerar cartes de ponto ou recibos de venda de datas fora do
perodo de pagamento atual.
Ento, temos de nos livrar do esquema de lanamento. Pareceu uma boa ideia na
ocasio, mas no era o que o cliente queria.

Pagando funcionrios assalariados


Os dois casos de teste da Listagem 27-23 verificam se um funcionrio assalariado est
sendo pago adequadamente*. O primeiro caso de teste garante que o funcionrio seja pago
no ltimo dia do ms. O segundo caso de teste garante que o funcionrio no seja pago se
no for o ltimo dia do ms.

Listagem 27-23
PayrollTest.PaySingleSalariedEmployee et al.
[Test]
public void PaySingleSalariedEmployee()
{
int empId = 1;
AddSalariedEmployee t = new AddSalariedEmployee(
empId, "Bob", "Home", 1000.00);

Certo, o cliente sou eu.


* N. de R.T.: Nas listagens, GrossPay, Deductions e NetPay significam, respectivamente, salrio bruto, dedues e salrio lquido.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

407

t.Execute();
DateTime payDate = new DateTime(2001, 11, 30);
PaydayTransaction pt = new PaydayTransaction(payDate);
pt.Execute();
Paycheck pc = pt.GetPaycheck(empId);
Assert.IsNotNull(pc);
Assert.AreEqual(payDate, pc.PayDate);
Assert.AreEqual(1000.00, pc.GrossPay, .001);
Assert.AreEqual("Hold", pc.GetField("Disposition"));
Assert.AreEqual(0.0, pc.Deductions, .001);
Assert.AreEqual(1000.00, pc.NetPay, .001);
}
[Test]
public void PaySingleSalariedEmployeeOnWrongDate()
{
int empId = 1;
AddSalariedEmployee t = new AddSalariedEmployee(
empId, "Bob", "Home", 1000.00);
t.Execute();
DateTime payDate = new DateTime(2001, 11, 29);
PaydayTransaction pt = new PaydayTransaction(payDate);
pt.Execute();
Paycheck pc = pt.GetPaycheck(empId);
Assert.IsNull(pc);
}

A Listagem 27-24 mostra a funo Execute() de PaydayTransaction. Ela faz uma iterao em todos os objetos Employee do banco de dados, perguntando a cada funcionrio se o dia que est nessa transao sua data de pagamento. Se for, ela cria um novo
cheque-salrio para o funcionrio e diz para que o funcionrio preencha seus campos.

Listagem 27-24
PaydayTransaction.Execute()
public void Execute()
{
ArrayList empIds = PayrollDatabase.GetAllEmployee

Ids();

foreach(int empId in empIds)


{
Employee employee = PayrollDatabase.GetEmployee(empId);
if (employee.IsPayDate(payDate)) {
Paycheck pc = new Paycheck(payDate);
paychecks[empId] = pc;
employee.Payday(pc);
}
}
}

408

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

A Listagem 27-25 mostra MonthlySchedule.cs. Note que ela implementa IsPayDate


para retornar true somente se o argumento date for o ltimo dia do ms.
A Listagem 27-26 mostra a implementao de Employee.PayDay(). Essa funo o
algoritmo genrico para calcular e enviar o pagamento de todos os funcionrios. Observe
o uso excessivo do padro STRATEGY. Todos os clculos detalhados so deixados para as
classes de estratgia contidas: classification, affiliation e method.

Listagem 27-25
MonthlySchedule.cs
using System;
namespace Payroll
{
public class MonthlySchedule : PaymentSchedule
{
private bool IsLastDayOfMonth(DateTime date)
{
int m1 = date.Month;
int m2 = date.AddDays(1).Month;
return (m1 != m2);
}
public bool IsPayDate(DateTime payDate)
{
return IsLastDayOfMonth(payDate);
}
}
}

Listagem 27-26
Employee.Payday()
public void Payday(Paycheck paycheck)
{
double grossPay = classification.CalculatePay(paycheck);
double deductions =
affiliation.CalculateDeductions(paycheck);
double netPay = grossPay deductions;
paycheck.GrossPay = grossPay;
paycheck.Deductions = deductions;
paycheck.NetPay = netPay;
method.Pay(paycheck);
}

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

409

Pagando funcionrios que recebem por hora


O pagamento dos funcionrios que recebem por hora um bom exemplo do incrementalismo do projeto com testes a priori. Comecei com casos de teste muito simples e evolu
para casos cada vez mais complexos. Mostrarei os casos de teste primeiro e depois o cdigo de produo resultante.
A Listagem 27-27 mostra o caso mais simples. Adicionamos no banco de dados um
funcionrio pago por hora e, ento, pagamos esse funcionrio. Como no existem cartes
de ponto, esperamos que o cheque-salrio tenha o valor zero. A funo utilitria ValidateHourlyPaycheck representa uma refatorao que aconteceu posteriormente. Inicialmente, esse cdigo estava oculto dentro da funo de teste. Esse caso de teste passou aps
retornar true de WeeklySchedule.IsPayDate().
A Listagem 27-28 mostra dois casos de teste. O primeiro testa se podemos pagar um
funcionrio aps adicionar um nico carto de ponto. O segundo testa se podemos pagar
horas extras para um carto que contenha mais de 8 horas. Evidentemente, no escrevi
esses dois casos de teste ao mesmo tempo. Em vez disso, escrevi o primeiro, o fiz funcionar e depois escrevi o segundo.

Listagem 27-27
PayrollTest.TestPaySingleHourlyEmployeeNoTimeCards()
[Test]
public void PayingSingleHourlyEmployeeNoTimeCards()
{
int empId = 2;
AddHourlyEmployee t = new AddHourlyEmployee(
empId, "Bill", "Home", 15.25);
t.Execute();
DateTime payDate = new DateTime(2001, 11, 9);
PaydayTransaction pt = new PaydayTransaction(payDate);
pt.Execute();
ValidateHourlyPaycheck(pt, empId, payDate, 0.0);
}
private void ValidateHourlyPaycheck(PaydayTransaction pt,
int empid, DateTime payDate, double pay)
{
Paycheck pc = pt.GetPaycheck(empid);
Assert.IsNotNull(pc);
Assert.AreEqual(payDate, pc.PayDate);
Assert.AreEqual(pay, pc.GrossPay, .001);
Assert.AreEqual("Hold", pc.GetField("Disposition"));
Assert.AreEqual(0.0, pc.Deductions, .001);
Assert.AreEqual(pay, pc.NetPay, .001);
}

410

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Listagem 27-28
PayrollTest.PaySingleHourlyEmployee...()
[Test]
public void PaySingleHourlyEmployeeOneTimeCard()
{
int empId = 2;
AddHourlyEmployee t = new AddHourlyEmployee(
empId, "Bill", "Home", 15.25);
t.Execute();
DateTime payDate = new DateTime(2001, 11, 9); // Sexta-feira
TimeCardTransaction tc =
new TimeCardTransaction(payDate, 2.0, empId);
tc.Execute();
PaydayTransaction pt = new PaydayTransaction(payDate);
pt.Execute();
ValidateHourlyPaycheck(pt, empId, payDate, 30.5);
}
[Test]
public void PaySingleHourlyEmployeeOvertimeOneTimeCard()
{
int empId = 2;
AddHourlyEmployee t = new AddHourlyEmployee(
empId, "Bill", "Home", 15.25);
t.Execute();
DateTime payDate = new DateTime(2001, 11, 9); // Sexta-feira
TimeCardTransaction tc =
new TimeCardTransaction(payDate, 9.0, empId);
tc.Execute();
PaydayTransaction pt = new PaydayTransaction(payDate);
pt.Execute();
ValidateHourlyPaycheck(pt, empId, payDate,
(8 + 1.5)*15.25);
}

Fazer o primeiro caso de teste funcionar foi uma questo de alterar HourlyClassification.CalculatePay para fazer um loop pelos cartes de ponto do funcionrio,
somar as horas e multiplicar pela remunerao. Fazer o segundo teste funcionar me obrigou a alterar a funo para calcular o salrio fixo e as horas extras.
O caso de teste da Listagem 27-29 garante que no possamos pagar funcionrios que
recebem por hora a menos que PaydayTransaction seja construda com uma sexta-feira.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

411

Listagem 27-29
PayrollTest.PaySingleHourlyEmployeeOnWrongDate()
[Test]
public void PaySingleHourlyEmployeeOnWrongDate()
{
int empId = 2;
AddHourlyEmployee t = new AddHourlyEmployee(
empId, "Bill", "Home", 15.25);
t.Execute();
DateTime payDate = new DateTime(2001, 11, 8); // Quinta-feira
TimeCardTransaction tc =
new TimeCardTransaction(payDate, 9.0, empId);
tc.Execute();
PaydayTransaction pt = new PaydayTransaction(payDate);
pt.Execute();
Paycheck pc = pt.GetPaycheck(empId);
Assert.IsNull(pc);
}

A Listagem 27-30 um caso de teste que garante que possamos calcular o pagamento de um funcionrio que tem mais de um carto de ponto.

Listagem 27-30
PayrollTest.PaySingleHourlyEmployeeTwoTimeCards()
[Test]
public void PaySingleHourlyEmployeeTwoTimeCards()
{
int empId = 2;
AddHourlyEmployee t = new AddHourlyEmployee(
empId, "Bill", "Home", 15.25);
t.Execute();
DateTime payDate = new DateTime(2001, 11, 9); // Sexta-feira
TimeCardTransaction tc =
new TimeCardTransaction(payDate, 2.0, empId);
tc.Execute();
TimeCardTransaction tc2 =
new TimeCardTransaction(payDate.AddDays(-1), 5.0,empId);
tc2.Execute();
PaydayTransaction pt = new PaydayTransaction(payDate);
pt.Execute();
ValidateHourlyPaycheck(pt, empId, payDate, 7*15.25);
}

412

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Por fim, o caso de teste da Listagem 27-31 mostra que s pagaremos um funcionrio pelos cartes de ponto do perodo de pagamento atual. Os cartes de ponto de outros
perodos de pagamento so ignorados.

Listagem 27-31
PayrollTest.Test...WithTimeCardsSpanningTwoPayPeriods()
[Test]
public void
TestPaySingleHourlyEmployeeWithTimeCardsSpanningTwoPayPeriods()
{
int empId = 2;
AddHourlyEmployee t = new AddHourlyEmployee(
empId, "Bill", "Home", 15.25);
t.Execute();
DateTime payDate = new DateTime(2001, 11, 9); // Sexta-feira
DateTime dateInPreviousPayPeriod =
new DateTime(2001, 11, 2);
TimeCardTransaction tc =
new TimeCardTransaction(payDate, 2.0, empId);
tc.Execute();
TimeCardTransaction tc2 = new TimeCardTransaction(
dateInPreviousPayPeriod, 5.0, empId);
tc2.Execute();
PaydayTransaction pt = new PaydayTransaction(payDate);
pt.Execute();
ValidateHourlyPaycheck(pt, empId, payDate, 2*15.25);
}

O cdigo que faz tudo isso funcionar desenvolveu-se progressivamente, um caso de


teste por vez. A estrutura que voc v no cdigo a seguir evoluiu de um caso de teste para
outro. A Listagem 27-32 mostra os fragmentos apropriados de HourlyClassification.
cs. Simplesmente fazemos um loop pelos cartes de ponto. Para cada carto, verificamos
se ele est no perodo de pagamento. Se estiver, calculamos o pagamento que ele representa.

Listagem 27-32
HourlyClassification.cs (fragmento)
public double CalculatePay(Paycheck paycheck)
{
double totalPay = 0.0;
foreach(TimeCard timeCard in timeCards.Values)
{

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

413

if(IsInPayPeriod(timeCard, paycheck.PayDate))
totalPay += CalculatePayForTimeCard(timeCard);
}
return totalPay;
}
private bool IsInPayPeriod(TimeCard card,
DateTime payPeriod)
{
DateTime payPeriodEndDate = payPeriod;
DateTime payPeriodStartDate = payPeriod.AddDays(-5);
return card.Date <= payPeriodEndDate &&
card.Date >= payPeriodStartDate;
}
private double CalculatePayForTimeCard(TimeCard card)
{
double overtimeHours = Math.Max(0.0, card.Hours 8);
double normalHours = card.Hours overtimeHours;
return hourlyRate * normalHours +
hourlyRate * 1.5 * overtimeHours;
}

A Listagem 27-33 mostra que WeeklySchedule s paga nas sextas-feiras.

Listagem 27-33
WeeklySchedule.IsPayDate()
public bool IsPayDate(DateTime payDate)
{
return payDate.DayOfWeek == DayOfWeek.Friday;
}

O clculo do pagamento de funcionrios comissionados deixado como exerccio.


No dever haver grandes surpresas.
Perodos de pagamento: um problema de projeto Agora hora de implementarmos as
quotas e as taxas de servio do sindicato. Estou contemplando um caso de teste que adicionar um funcionrio assalariado, o converter em membro do sindicato e, ento, pagar o funcionrio e garantir que as quotas sejam descontadas do pagamento. A codificao
aparece na Listagem 27-34.

414

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Listagem 27-34
PayrollTest.SalariedUnionMemberDues()
[Test]
public void SalariedUnionMemberDues()
{
int empId = 1;
AddSalariedEmployee t = new AddSalariedEmployee(
empId, "Bob", "Home", 1000.00);
t.Execute();
int memberId = 7734;
ChangeMemberTransaction cmt =
new ChangeMemberTransaction(empId, memberId, 9.42);
cmt.Execute();
DateTime payDate = new DateTime(2001, 11, 30);
PaydayTransaction pt = new PaydayTransaction(payDate);
pt.Execute();
Paycheck pc = pt.GetPaycheck(empId);
Assert.IsNotNull(pc);
Assert.AreEqual(payDate, pc.PayDate);
Assert.AreEqual(1000.0, pc.GrossPay, .001);
Assert.AreEqual("Hold", pc.GetField("Disposition"));
Assert.AreEqual(???, pc.Deductions, .001);
Assert.AreEqual(1000.0 -???, pc.NetPay, .001);
}

Observe o ??? nas duas ltimas linhas do caso de teste. O que devo colocar ali? As
histrias de usurio me informam que as quotas do sindicato so semanais, mas os funcionrios assalariados so pagos mensalmente. Quantas semanas existem em cada ms?
Eu devo simplesmente multiplicar as quotas por 4? Isso no muito preciso. Vou perguntar ao cliente o que ele quer.3
O cliente me diz que as quotas do sindicato vencem toda sexta-feira. Ento, o que
preciso fazer contar o nmero de sextas-feiras no perodo de pagamento e multiplicar
pelas quotas semanais. Existem cinco sextas-feiras em novembro de 2001, o ms para o
qual o caso de teste foi escrito. Assim, posso modificar o caso de teste adequadamente.
Contar as sextas-feiras de um perodo de pagamento significa que preciso saber quais
so as datas de incio e fim desse perodo. Fiz esse clculo antes, na funo IsInPayPeriod da Listagem 27-32. (Voc provavelmente escreveu um clculo semelhante para CommissionedClassification.) Essa funo usada pela funo CalculatePay do objeto
HourlyClassification para garantir que apenas os cartes de ponto do perodo de pagamento sejam totalizados. Agora parece que o objeto UnionAffiliation tambm deve
chamar essa funo.

E ento Bob fala com ele mesmo novamente. Visite o site www.google.com/groups e procure Schizophrenic
Robert Martin (Robert Martin esquizofrnico).

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

415

Mas, espere! O que essa funo est fazendo na classe HourlyClassification?


J determinamos que a associao entre a agenda de pagamento e a classificao de
pagamento casual. A funo que determina o perodo de pagamento deve estar na
classe PaymentSchedule e no na classe PaymentClassification!
interessante o fato de nossos diagramas UML no nos terem ajudado a vislumbrar
esse problema. O problema s surgiu quando comecei a pensar sobre os casos de teste de
UnionAffiliation. Esse mais um exemplo do quanto necessrio, em qualquer projeto, obter um retorno (feedback) atravs do cdigo. Os diagramas podem ser teis, mas
confiar neles sem um retorno a partir do cdigo um negcio arriscado.
Ento, como obtemos o perodo de pagamento da hierarquia PaymentSchedule e
nas hierarquias PaymentClassification e Affiliation? Essas hierarquias no sabem
nada umas das outras. Tenho uma ideia a respeito disso. Poderamos colocar as datas do
perodo de pagamento no objeto Paycheck. No momento, Paycheck tem apenas a data
final do perodo de pagamento. Devemos colocar ali a data de incio tambm.
A Listagem 27-35 mostra a mudana feita em PaydayTransaction.Execute().
Note que, quando o objeto Paycheck criado, so passadas as datas de incio e fim do
perodo de pagamento. Note tambm que PaymentSchedule que calcula ambas. As mudanas em Paycheck devem ser bvias.
As duas funes em HourlyClassification e CommissionedClassification
que determinavam se objetos TimeCard e SalesReceipt estavam dentro do perodo de
pagamento foram mescladas e movidas para a classe base PaymentClassification.
Consulte a Listagem 27-36.

Listagem 27-35
PaydayTransaction.Execute()
public void Execute()
{
ArrayList empIds = PayrollDatabase.GetAllEmployeeIds();
foreach(int empId in empIds)
{
Employee employee = PayrollDatabase.GetEmployee(empId);
if (employee.IsPayDate(payDate))
{
DateTime startDate =
employee.GetPayPeriodStartDate(payDate);
Paycheck pc = new Paycheck(startDate, payDate);
paychecks[empId] = pc;
employee.Payday(pc);
}
}
}

416

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Listagem 27-36
PaymentClassification.IsInPayPeriod(...)
public bool IsInPayPeriod(DateTime theDate, Paycheck paycheck)
{
DateTime payPeriodEndDate = paycheck.PayPeriodEndDate;
DateTime payPeriodStartDate = paycheck.PayPeriodStartDate;
return (theDate >= payPeriodStartDate)
&& (theDate <= payPeriodEndDate);
}

Agora estamos prontos para calcular as quotas para o sindicato do funcionrio, em


UnionAffiliation. CalculateDeductions. O cdigo da Listagem 27-37 mostra como
isso feito. As duas datas que definem o perodo de pagamento so extradas do cheque-salrio e passadas para uma funo utilitria que conta o nmero de sextas-feiras
(fridays) entre elas. Ento, esse nmero multiplicado pelas alquotas semanais (dues)
para se calcular as quotas do perodo de pagamento.

Listagem 27-37
UnionAffiliation.CalculateDeductions(...)
public double CalculateDeductions(Paycheck paycheck)
{
double totalDues = 0;
int fridays = NumberOfFridaysInPayPeriod(
paycheck.PayPeriodStartDate, paycheck.PayPeriodEndDate);
totalDues = dues * fridays;
return totalDues;
}
private int NumberOfFridaysInPayPeriod(
DateTime payPeriodStart, DateTime payPeriodEnd)
{
int fridays = 0;
for (DateTime day = payPeriodStart;
day <= payPeriodEnd; day.AddDays(1))
{
if (day.DayOfWeek == DayOfWeek.Friday)
fridays++;
}
return fridays;
}

Os dois ltimos casos de teste esto relacionados com taxas de servio do sindicato.
O primeiro caso de teste, mostrado na Listagem 27-38, certifica-se de que deduzimos as
taxas de servio adequadamente.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

417

Listagem 27-38
PayrollTest.HourlyUnionMemberServiceCharge()
[Test]
public void HourlyUnionMemberServiceCharge()
{
int empId = 1;
AddHourlyEmployee t = new AddHourlyEmployee(
empId, "Bill", "Home", 15.24);
t.Execute();
int memberId = 7734;
ChangeMemberTransaction cmt =
new ChangeMemberTransaction(empId, memberId, 9.42);
cmt.Execute();
DateTime payDate = new DateTime(2001, 11, 9);
ServiceChargeTransaction sct =
new ServiceChargeTransaction(memberId, payDate, 19.42);
sct.Execute();
TimeCardTransaction tct =
new TimeCardTransaction(payDate, 8.0, empId);
tct.Execute();
PaydayTransaction pt = new PaydayTransaction(payDate);
pt.Execute();
Paycheck pc = pt.GetPaycheck(empId);
Assert.IsNotNull(pc);
Assert.AreEqual(payDate, pc.PayPeriodEndDate);
Assert.AreEqual(8*15.24, pc.GrossPay, .001);
Assert.AreEqual("Hold", pc.GetField("Disposition"));
Assert.AreEqual(9.42 + 19.42, pc.Deductions, .001);
Assert.AreEqual((8*15.24)-(9.42 + 19.42),pc.NetPay, .001);
}

O segundo caso de teste, que se revelou um problema para mim, aparece na Listagem 27-39. Esse caso de teste garante que as taxas de servio com datas fora do perodo
de pagamento atual no sejam deduzidas.

Listagem 27-39
PayrollTest.ServiceChargesSpanningMultiplePayPeriods()
[Test]
public void ServiceChargesSpanningMultiplePayPeriods()
{
int empId = 1;
AddHourlyEmployee t = new AddHourlyEmployee(
empId, "Bill", "Home", 15.24);

418

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

t.Execute();
int memberId = 7734;
ChangeMemberTransaction cmt =
new ChangeMemberTransaction(empId, memberId, 9.42);
cmt.Execute();
DateTime payDate = new DateTime(2001, 11, 9);
DateTime earlyDate =
new DateTime(2001, 11, 2); // sexta-feira anterior
DateTime lateDate =
new DateTime(2001, 11, 16); // prxima sexta-feira
ServiceChargeTransaction sct =
new ServiceChargeTransaction(memberId, payDate, 19.42);
sct.Execute();
ServiceChargeTransaction sctEarly =
new ServiceChargeTransaction(memberId,earlyDate,100.00);
sctEarly.Execute();
ServiceChargeTransaction sctLate =
new ServiceChargeTransaction(memberId,lateDate,200.00);
sctLate.Execute();
TimeCardTransaction tct =
new TimeCardTransaction(payDate, 8.0, empId);
tct.Execute();
PaydayTransaction pt = new PaydayTransaction(payDate);
pt.Execute();
Paycheck pc = pt.GetPaycheck(empId);
Assert.IsNotNull(pc);
Assert.AreEqual(payDate, pc.PayPeriodEndDate);
Assert.AreEqual(8*15.24, pc.GrossPay, .001);
Assert.AreEqual("Hold", pc.GetField("Disposition"));
Assert.AreEqual(9.42 + 19.42, pc.Deductions, .001);
Assert.AreEqual((8*15.24) (9.42 + 19.42),
pc.NetPay, .001);
}

Para implementar isso, eu queria que UnionAffiliation::CalculateDeductions


chamasse IsInPayPeriod. Infelizmente, acabamos de colocar IsInPayPeriod na classe
PaymentClassification. (Consulte a Listagem 27-36.) Foi conveniente coloc-la ali, embora as derivadas de PaymentClassification que precisassem cham-la. Mas agora
outras classes tambm precisam dela. Portanto, movi a funo para uma classe DateUtil. Afinal, a funo est apenas determinando se uma data especfica est entre duas
outras datas dadas. (Consulte a Listagem 27-40.)

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

419

Listagem 27-40
DateUtil.cs
using System;
namespace Payroll
{
public class DateUtil
{
public static bool IsInPayPeriod(
DateTime theDate, DateTime startDate, DateTime endDate)
{
return (theDate >= startDate) && (theDate <= endDate);
}
}
}

Agora finalmente podemos concluir a funo UnionAffiliation::Calculate


Deductions. Faa isso como um exerccio.
A Listagem 27-41 mostra a implementao da classe Employee.

Listagem 27-41
Employee.cs
using System;
namespace Payroll
{
public class Employee
{
private readonly int empid;
private string name;
private readonly string address;
private PaymentClassification classification;
private PaymentSchedule schedule;
private PaymentMethod method;
private Affiliation affiliation = new NoAffiliation();
public Employee(int empid, string name, string address)
{
this.empid = empid;
this.name = name;
this.address = address;
}
public string Name
{
get { return name; }

420

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

set { name = value; }


}
public string Address
{
get { return address; }
}
public PaymentClassification Classification
{
get { return classification; }
set { classification = value; }
}
public PaymentSchedule Schedule
{
get { return schedule; }
set { schedule = value; }
}
public PaymentMethod Method
{
get { return method; }
set { method = value; }
}
public Affiliation Affiliation
{
get { return affiliation; }
set { affiliation = value; }
}
public bool IsPayDate(DateTime date)
{
return schedule.IsPayDate(date);
}
public void Payday(Paycheck paycheck)
{
double grossPay = classification.CalculatePay(paycheck);
double deductions =
affiliation.CalculateDeductions(paycheck);
double netPay = grossPay deductions;
paycheck.GrossPay = grossPay;
paycheck.Deductions = deductions;
paycheck.NetPay = netPay;
method.Pay(paycheck);
}
public DateTime GetPayPeriodStartDate(DateTime date)
{
return schedule.GetPayPeriodStartDate(date);
}
}
}

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

421

Programa principal
O programa principal da folha de pagamentos pode agora ser expresso como um loop
que analisa transaes de uma fonte de entrada e ento as executa. As Figuras 27-33
e 27-34 descrevem a esttica e a dinmica do programa principal. O conceito simples: PayrollApplication fica em um loop, solicitando alternadamente transaes
de TransactionSource e, ento, pedindo a esses objetos Transaction que executem
(Execute). Note que isso diferente do diagrama da Figura 27-1 e representa uma mudana em nosso pensamento em direo a um mecanismo mais abstrato.

interface
Transaction
Source

Payroll
Application

interface
Transaction

+GetTransaction

TextParser
Transaction
Source

Figura 27-33
Modelo esttico do programa principal.

Payroll
Application

Transaction
Source

t := GetTransaction()
t:Transaction

Execute()

Figura 27-34
Modelo dinmico do programa principal.
TransactionSource uma interface que podemos implementar de vrias maneiras. O diagrama esttico mostra a derivada chamada TextParserTransactionSource,
a qual l um fluxo de texto recebido e analisa as transaes, conforme descrito nos casos de uso. Ento, esse objeto cria os objetos Transaction apropriados e os envia para
PayrollApplication.
A separao entre interface e implementao em TransactionSource permite que
a origem das transaes varie. Por exemplo, poderamos facilmente fazer a interface de
PayrollApplication para uma GUITransactionSource ou para uma RemoteTransactionSource.

422

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

O banco de dados
Agora que a maior parte do aplicativo foi analisada, projetada e implementada, podemos
considerar o papel do banco de dados. Claramente, a classe PayrollDatabase encapsula
algo que envolve persistncia. Os objetos contidos em PayrollDatabase devem viver mais
tempo do que qualquer execuo em particular do aplicativo. Como isso deve ser implementado? Evidentemente, o mecanismo transitrio utilizado pelos casos de teste no
suficiente para o sistema real. Mas temos vrias opes.
Poderamos implementar PayrollDatabase usando um sistema de gerenciamento
de banco de dados orientado a objetos (SGBDOO). Isso permitiria que os objetos ficassem
dentro do armazenamento permanente do banco de dados. Como projetistas, teramos
um pouco mais de trabalho a fazer, pois o SGBDOO no acrescentaria muita novidade
em nosso projeto. Uma das maiores vantagens dos produtos de SGBDOO que eles tm
pouco ou nenhum impacto sobre o modelo de objetos dos aplicativos. No que diz respeito
ao projeto, o banco de dados praticamente no existe.4
Outra opo seria usar arquivos de texto planos simples para gravar os dados. Na
inicializao, o objeto PayrollDatabase poderia ler esse arquivo e construir os objetos
necessrios na memria. No final do programa, o objeto PayrollDatabase poderia gravar
uma nova verso do arquivo de texto. Certamente, essa opo no bastaria para uma empresa com centenas de milhares de funcionrios ou para uma que quisesse acesso concorrente
em tempo real para seu banco de dados de folha de pagamentos. Contudo, ela poderia ser
suficiente para uma empresa menor e certamente poderia ser usada como um mecanismo
para testar o restante das classes do aplicativo, sem se investir em um enorme sistema de
banco de dados.
Ainda outra opo seria incorporar um sistema de gerenciamento de banco de dados
relacional (SGBDR) ao objeto PayrollDatabase. A implementao do objeto PayrollDatabase faria ento as consultas apropriadas no SGBDR, para criar temporariamente
os objetos necessrios na memria.
O ponto que qualquer um desses mecanismos funcionaria. Nosso aplicativo foi
projetado de tal maneira que no sabe nem se preocupa com qual a implementao
subjacente do banco de dados. No que diz respeito ao aplicativo, o banco de dados simplesmente um mecanismo para gerenciar o armazenamento.
Normalmente, os bancos de dados no devem ser considerados como um fator importante do projeto e da implementao. Conforme mostramos aqui, eles podem ser deixados por ltimo e tratados como um detalhe.5 Fazendo isso, deixamos abertas diversas
opes interessantes para implementar a necessria persistncia e para criar mecanismos para testar o restante do aplicativo. Tambm no nos vinculamos a uma tecnologia
ou produto de banco de dados especfico. Temos a liberdade de escolher o banco de da-

Isso otimismo. Em uma aplicao simples, como a folha de pagamentos, o uso de um SGBDOO teria
pouco impacto no projeto do programa. medida que as aplicaes se tornam cada vez mais complicadas,
o impacto que o SGBDOO tem sobre a aplicao aumenta. Apesar disso, o impacto bem menor do que o
de um SGBDR (banco de dados relacional).
5
s vezes, a natureza do banco de dados um dos requisitos do aplicativo. Os SGBDRs oferecem poderosos sistemas de consulta e produo de relatrios que podem ser listados como requisitos do aplicativo.
Contudo, mesmo quando tais requisitos so explcitos, os projetistas ainda devem desacoplar o projeto do
aplicativo do projeto do banco de dados. O projeto do aplicativo no deve depender de qualquer tipo de
banco de dados especfico.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: IMPLEMENTAO

423

dos que precisarmos, com base no restante do projeto, e mantemos a liberdade de mudar
ou substituir esse produto de banco de dados no futuro, conforme for necessrio.

Concluso
Com aproximadamente 32 diagramas nos captulos 26 e 27, documentamos o projeto e a
implementao do aplicativo de folha de pagamentos. O projeto utiliza uma grande quantidade de abstrao e polimorfismo. O resultado que grandes partes do projeto so fechadas em relao s mudanas de diretiva de folha de pagamentos. Por exemplo, o aplicativo
poderia ser alterado para lidar com funcionrios que recebessem trimestralmente, com
base em um salrio normal e um cronograma de bnus. Essa mudana exigiria adio ao
projeto, mas pouco mudaria no projeto e no cdigo existentes.
Durante este processo de projeto, raramente refletimos se estvamos fazendo anlise,
projeto ou implementao. Em vez disso, nos concentramos nos problemas de clareza e gerenciamento de dependncias. Tentamos encontrar as abstraes subjacentes sempre que
possvel. O resultado que temos um bom projeto para um aplicativo de folha de pagamentos
e temos uma base de classes que so pertinentes ao domnio do problema como um todo.

Sobre este captulo


Os diagramas deste captulo so derivados dos diagramas de Booch do captulo correspondente de meu livro de 1995.6 Esses diagramas foram criados em 1994. medida que
os criei, tambm escrevi parte do cdigo que os implementava para garantir que os diagramas fizessem sentido. Contudo, no escrevi nada que esteja prximo do tamanho do
cdigo apresentado aqui. Portanto, os diagramas no tiraram proveito das informaes
significativas do cdigo e dos testes (feedback). Essa falta de feedback visvel.
Este captulo aparece em meu livro de 2002.7 Escrevi o cdigo daquele captulo em
C++, na ordem apresentada aqui. Em todas as ocasies, os casos de teste foram escritos
antes do cdigo de produo. Em muitos casos, esses testes foram criados progressivamente, evoluindo medida que o cdigo de produo tambm evolua. O cdigo de produo foi escrito de acordo com os diagramas, contanto que fizesse sentido. Em vrios casos
no fazia sentido; portanto, mudei o projeto do cdigo.
Um dos primeiros lugares em que isso aconteceu foi quando decidi contra as mltiplas instncias de Affiliation no objeto Employee. Outra foi quando descobri que no
tinha considerado o registro da afiliao do funcionrio no sindicato, em ChangeMemberTransaction.
Isso normal. Quando voc projetar sem feedback, sempre cometer erros. Foi o feedback imposto pelos casos de teste e a execuo do cdigo que revelaram esses erros para ns.
Este captulo foi transformado de C++ para C# por meu coautor, Micah Martin. Foi
dada ateno especial s convenes e estilos da linguagem C#, para que o cdigo no se
tornasse C#++. (A verso final deste cdigo pode ser encontrada em www.objectmentor.
com/PPP/payroll.net.zip.) Os diagramas no foram alterados, exceto pela substituio de
relacionamentos de composio por associaes.

6
7

[Martin1995].
[Martin2002].

424

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS

Bibliografia
[Jacobson92] Ivar Jacobson, Object-Oriented Software Engineering: A Use Case Driven
Approach, Addison-Wesley, 1992.
[Martin1995] Designing Object-Oriented C++ Aplications Using the Booch Method,
Prentice Hall, 1995.
[Martin2002] Agile Software Development: Principles, Patterns, and Practices, Prentice
Hall, 2002.

Seo IV

EMPACOTANDO O SISTEMA DE
FOLHA DE PAGAMENTOS

esta seo, exploraremos os princpios de projeto que nos ajudam a dividir um sistema de software grande em pacotes. O Captulo 28 discute esses princpios. O Captulo 29 descreve um padro que usaremos para melhorar a estrutura de empacotamento.
O Captulo 30 mostra como os princpios e o padro podem ser aplicados no sistema de
folha de pagamentos.

Captulo 28

PRINCPIOS DE PROJETO DE
PACOTES E COMPONENTES
Lindo pacote.
Anthony

medida que o tamanho e a complexidade dos aplicativos de software aumentam,


necessrio algum tipo de organizao de alto nvel. As classes so timas unidades
para organizar pequenos aplicativos, mas tm uma granulao fina demais para serem
usadas como unidade organizacional nica para aplicativos grandes. necessrio algo
maior do que uma classe para organizar aplicativos grandes. Esse algo chamado pacote ou componente.

Pacotes e componentes
O termo pacote tem inmeros significados em software. Para nossos propsitos, focamos
um tipo especfico de pacote, frequentemente chamado de componente. Um componente
uma unidade binria que pode ser entregue de modo independente. Em .NET, os componentes costumam ser chamados de assemblies e so mantidos dentro de DLLs.
Como elementos fundamentalmente importantes de sistemas de software grandes,
os componentes permitem que tais sistemas sejam decompostos em entregveis binrios
menores. Se as dependncias entre os componentes so bem gerenciadas, possvel corrigir erros e adicionar funcionalidades implantando novamente apenas os componentes
que foram alterados. Mais importante, o projeto de sistemas grandes depende essencialmente do bom projeto de componentes, de modo que as equipes individuais podem se
concentrar em componentes isolados, em vez de se preocuparem com o sistema inteiro.
Na UML, os pacotes podem ser usados como contineres para grupos de classes.
Esses pacotes podem representar subsistemas, bibliotecas ou componentes. Ao agrupar
classes em pacotes, podemos raciocinar sobre o projeto em um nvel de abstrao mais
alto. Se esses pacotes so componentes, podemos us-los para gerenciar o desenvolvimen-

428

EMPACOTANDO O SISTEMA DE FOLHA DE PAGAMENTOS

to e a distribuio do software. Nosso objetivo neste captulo aprender a dividir as classes de um aplicativo de acordo com alguns critrios e, ento, distribuir as classes dessas
divises em componentes que podem ser implantados independentemente.
Porm, as classes frequentemente tm dependncias em relao a outras classes e
essas dependncias muitas vezes ultrapassam os limites do componente. Assim, os componentes tero relacionamentos de dependncia entre si. Os relacionamentos entre os componentes expressam a organizao de alto nvel do aplicativo e precisam ser gerenciados.
Isso levanta algumas questes:
1. Quais so os princpios para alocar classes em componentes?
2. Quais princpios de projeto governam os relacionamentos entre os componentes?
3. Os componentes devem ser projetados antes das classes (de cima para baixo)? Ou as
classes devem ser projetadas antes dos componentes (de baixo para cima)?
4. Como os componentes so representados fisicamente? Em C#? No ambiente de
desenvolvimento?
5. Uma vez criados, com que objetivo colocaremos esses componentes?
Este captulo descreve seis princpios para gerenciar o contedo e os relacionamentos entre componentes. Os trs primeiros, princpios de coeso de pacotes, nos ajudam a
alocar classes em pacotes. Os trs ltimos princpios governam o acoplamento de pacotes
e nos ajudam a determinar como os pacotes devem estar relacionados. Os dois ltimos
princpios tambm descrevem um conjunto de mtricas de gerenciamento de dependncias que permitem aos desenvolvedores avaliar e caracterizar a estrutura de dependncia
de seus projetos.

Princpios da coeso de componentes: granularidade


Os princpios da coeso de componentes ajudam os desenvolvedores a decidir como vo
dividir as classes nos componentes. Esses princpios dependem do fato de pelo menos
algumas das classes e suas inter-relaes terem sido descobertas. Assim, esses princpios
adotam uma viso de baixo para cima da diviso.

Princpio da Equivalncia Reutilizao/Entrega (REP)


O grnulo da reutilizao o grnulo da entrega.

O que esperar do autor de uma biblioteca de classes que voc est pretendendo reutilizar?
Certamente, boa documentao, cdigo que funcione, interfaces bem especificadas etc.
Mas voc quer mais.
Primeiro, para fazer valer o tempo de reutilizar o cdigo, voc quer que o autor garanta que vai mant-lo. Afinal, se voc tiver que manter, vai precisar investir nisso uma
grande quantidade de tempo, tempo que poderia ser mais bem gasto projetando do zero
um componente menor e melhor.

PRINCPIOS DE PROJETO DE PACOTES E COMPONENTES

429

Segundo, voc vai querer que o autor o avise sobre qualquer mudana planejada na
interface e na funcionalidade do cdigo. Mas avisar no suficiente. Voc deve poder se
recusar a utilizar novas verses. Afinal, o autor poderia introduzir uma nova verso exatamente em um momento de entrega no cronograma ou poderia fazer alteraes no cdigo
incompatveis com seu sistema.
Seja qual for o caso, se voc decidir rejeitar essa verso, o autor deve garantir o suporte para uso da verso antiga durante algum tempo. Esse tempo pode ser de apenas trs
meses ou de at um ano; isso vocs dois podem negociar. Mas o autor no pode simplesmente abandon-lo e se recusar a dar suporte. Se ele no concordar em dar suporte para
o uso de verses mais antigas, talvez voc tenha que pensar duas vezes se quer utilizar
esse cdigo e ficar sujeito s alteraes imprevisveis do autor.
Essa uma questo prioritariamente poltica. Ela tem a ver com o trabalho administrativo e de suporte que precisa ser fornecido se outras pessoas vo reutilizar cdigo. Mas
essas questes polticas e administrativas tm um efeito profundo sobre a estrutura de
empacotamento do software. Para dar as garantias que os usurios precisam, os autores
organizam seu software em componentes reutilizveis e, ento, controlam esses componentes com nmeros de entrega (release numbers).
Assim, o REP declara que o grnulo da reutilizao, um componente, no pode ser
menor do que o grnulo da entrega. Tudo que reutilizamos tambm deve ser entregue e
controlado. No realista um desenvolvedor simplesmente escrever uma classe e ento
dizer que ela reutilizvel. A reutilizao s vem depois que um sistema de controle est
em vigor e oferece as garantias de notificao, segurana e suporte de que todos os usurios em potencial precisaro.
O REP nos d a primeira indicao sobre como dividir nosso projeto em componentes. Como a capacidade de reutilizao deve ser baseada em componentes, os componentes reutilizveis devem conter classes reutilizveis. Portanto, pelo menos alguns componentes devem conter conjuntos de classes reutilizveis.
Parece preocupante uma fora poltica afetar a diviso de nosso software, mas software no uma entidade matematicamente pura que pode ser estruturada de acordo com
regras matematicamente puras. Software um produto humano que d suporte a esforos
humanos. O software criado e usado por seres humanos. E se o software vai ser reutilizado, ele deve ser dividido de uma maneira que os seres humanos achem conveniente
para esse propsito.
O que isso nos diz a respeito da estrutura interna de um componente? Deve-se considerar o contedo interno do ponto de vista dos usurios em potencial. Se um componente
contm software que vai ser reutilizado, ele no deve conter tambm software que no foi
projetado para reutilizao. Ou todas as classes de um componente so reutilizveis ou
nenhuma delas .
Alm disso, o critrio no simplesmente a capacidade de reutilizao; tambm devemos considerar quem a pessoa que vai reutilizar. Certamente, uma biblioteca de classes
continer reutilizvel, assim como um framework financeiro. Mas no desejaramos que
eles fizessem parte do mesmo componente, pois muitas pessoas que gostariam de reutilizar
uma biblioteca de classes continer no teriam nenhum interesse em um framework financeiro. Assim, queremos que todas as classes de um componente sejam reutilizadas pelo
mesmo pblico. No queremos que um grupo de pessoas ache que um componente consiste em algumas classes necessrias e outro grupo ache que elas so totalmente inadequadas.

430

EMPACOTANDO O SISTEMA DE FOLHA DE PAGAMENTOS

Princpio da Reutilizao Comum (CRP)


As classes de um componente so reutilizadas juntas. Se voc reutiliza uma das
classes de um componente, reutiliza todas elas.

Esse princpio nos ajuda a decidir quais classes devem ser colocadas em um componente.
O CRP declara que as classes que tendem a ser reutilizadas juntas pertencem ao mesmo
componente.
As classes raramente so reutilizadas de maneira isolada. Em geral, as classes reutilizveis colaboram com outras classes que fazem parte da abstrao reutilizvel. O CRP
diz que essas classes pertencem ao mesmo componente. Em tal componente, esperaramos ver classes que tivessem muitas dependncias entre si. Um exemplo simples poderia ser uma classe continer e suas iteradoras associadas. Essas classes so reutilizadas
juntas porque so fortemente acopladas. Assim, elas devem estar no mesmo componente.
Porm, o CRP nos diz mais do que simplesmente quais classes devem ser colocadas
juntas em um componente. Ele tambm nos diz quais classes no devem ser colocadas
no componente. Quando um componente usa outro, criada uma dependncia entre eles.
Pode ser que o componente que est usando outro utilize apenas uma classe dentro do
componente usado. Contudo, isso no enfraquece a dependncia. O componente que est
usando outro ainda depende do componente usado. Sempre que o componente usado
entregue, o componente que o est usando deve ser revalidado e reentregue. Isso verdade
mesmo que o componente usado esteja sendo entregue devido a alteraes em uma classe
pela qual o componente que o est utilizando no se interessa.
Alm disso, comum os componentes existirem em DLLs. Se o componente usado
entregue como uma DLL, o cdigo que o est utilizando depende da DLL inteira. Qualquer modificao nessa DLL, mesmo que seja em uma classe pela qual o cdigo que a est
utilizando no se interesse, ainda far com que uma nova verso da DLL seja entregue. A
nova DLL ainda ter que ser implantada novamente e o cdigo que a est usando ainda
ter que ser revalidado.
Assim, quero garantir que, quando dependo de um componente, dependo de todas
as classes que esto nesse componente. Para dizer isso de outra maneira, quero garantir
que as classes que coloco em um componente sejam inseparveis, que seja impossvel
depender de algumas e no das outras. Caso contrrio, estarei revalidando e implantando
novamente mais do que necessrio e estarei desperdiando esforo significativo.
Portanto, o CRP nos diz mais sobre quais classes no devem estar juntas do que
sobre quais devem estar. O CRP diz que as classes que no esto fortemente vinculadas
umas s outras com relacionamentos de classe no devem estar no mesmo componente.

Princpio do Fechamento Comum (CCP)


As classes de um componente devem ser fechadas em relao aos mesmos tipos
de mudanas. Uma mudana que afete um componente afeta todas as classes
desse componente e de nenhum outro.

PRINCPIOS DE PROJETO DE PACOTES E COMPONENTES

431

Esse o Princpio da Responsabilidade nica (SRP) reformulado para componentes. Assim como o SRP diz que uma classe no deve conter vrios motivos para mudar, o CCP diz
que um componente no deve ter vrios motivos para mudar.
Na maioria dos aplicativos, a capacidade de manuteno mais importante do
que a capacidade de reutilizao. Se o cdigo de um aplicativo vai mudar, melhor que
as alteraes ocorram todas em apenas um componente, em vez de serem distribudas
por muitos componentes. Se as alteraes se concentram em um nico componente,
precisamos entregar novamente apenas o componente modificado. Outros componentes
que no dependem do componente alterado no precisam ser revalidados ou entregues
novamente.
O CCP nos avisa para reunirmos em um s lugar todas as classes que provavelmente
mudaro pelos mesmos motivos. Se duas classes so to fortemente vinculadas (fsica ou
conceitualmente) que sempre mudam juntas, elas pertencem ao mesmo componente. Isso
minimiza a carga de trabalho relacionada entrega, revalidao e redistribuio do
software.
Esse princpio est intimamente associado ao Princpio do Aberto/Fechado (OCP).
Isso por causa do fechamento, no sentido OCP da palavra, com que esse princpio est
lidando. O OCP declara que as classes devem ser fechadas para modificao, mas abertas
para ampliao. Porm, conforme aprendemos, 100% de fechamento no atingvel. O
fechamento deve ser estratgico. Projetamos nossos sistemas de modo que sejam fechados
para os tipos de alteraes mais comuns que temos praticado.
O CCP amplia isso, agrupando nos mesmos componentes classes que so abertas
para certos tipos de alteraes. Assim, quando aparece uma mudana nos requisitos, essa
mudana tem uma boa chance de ficar restrita a um nmero mnimo de componentes.

Resumo da coeso de componentes


Antigamente, nossa viso de coeso era muito mais simples. Costumvamos pensar que
coeso era simplesmente a caracterstica de um mdulo executar uma e apenas uma funo. Contudo, os trs princpios de coeso de componentes descrevem um tipo de coeso
muito mais complexo. Ao escolhermos as classes que sero agrupadas em um componente, devemos considerar as foras opostas envolvidas na capacidade de reutilizao e de
desenvolvimento.
Equilibrar essas foras com as necessidades do aplicativo no fcil. Alm disso, o
equilbrio quase sempre dinmico. Isto , a diviso que adequada hoje pode no ser no
prximo ano. Assim, a composio do componente provavelmente ser instvel e evoluir
com o tempo, medida que o enfoque do projeto mudar da capacidade de desenvolvimento para a capacidade de reutilizao.

Princpios do acoplamento de componentes: estabilidade


Os prximos trs princpios tratam dos relacionamentos entre componentes. Aqui, novamente, vamos nos deparar com a tenso entre a capacidade de desenvolvimento e o
projeto lgico. As foras que afetam a arquitetura de uma estrutura de componentes so
tcnicas, polticas e volteis.

432

EMPACOTANDO O SISTEMA DE FOLHA DE PAGAMENTOS

Princpio das Dependncias Acclicas (ADP)


No permita ciclos no grafo de dependncia de componentes.

Voc j passou pela experincia de ter trabalhado um dia inteiro e, na manh seguinte,
descobrir que tudo o que fez no funciona mais? Por que no funciona? Porque algum
ficou at mais tarde e alterou alguma coisa de que voc dependia! Chamo isso de a sndrome da manh seguinte.
Essa sndrome ocorre em ambientes de desenvolvimento nos quais muitos desenvolvedores esto modificando os mesmos arquivos-fonte. Em projetos relativamente pequenos,
com apenas alguns desenvolvedores, esse problema no muito srio. Mas medida que o
tamanho do projeto e da equipe de desenvolvimento aumenta, as manhs seguintes podem
se tornar um pesadelo. No incomum passarem-se semanas sem que se seja capaz de compilar uma verso estvel do projeto. Em vez disso, todos mudam o cdigo o tempo inteiro,
tentando faz-lo funcionar com as ltimas alteraes que algum fez.
Nas ltimas dcadas surgiram duas solues para esse problema: a compilao
semanal e o ADP. As duas solues so provenientes do setor das telecomunicaes.
A compilao semanal A compilao (build) semanal comum em projetos de tamanho mdio. Ela funciona assim: nos quatro primeiros dias da semana, os desenvolvedores
ignoram-se uns aos outros. Eles trabalham em cpias pessoais do cdigo sem se preocupar
com a integrao com o cdigo dos outros. No quinto dia, sexta-feira, eles integram todas as
suas alteraes e compilam o sistema. Isso tem a formidvel vantagem de permitir que os
desenvolvedores vivam em um mundo isolado durante quatro dos cinco dias de trabalho. A
desvantagem, evidentemente, o grande esforo de integrao na sexta-feira.
Infelizmente, medida que o projeto cresce, torna-se quase impossvel concluir a integrao na sexta-feira. O trabalho de integrao aumenta e se estende at o sbado. Poucos sbados so suficientes para convencer os desenvolvedores de que a integrao deve
comear na quinta-feira. E assim o incio da integrao passa para o meio da semana.
medida que o ciclo de trabalho de desenvolvimento versus integrao diminui, a eficincia da equipe tambm diminui. A frustrao diante desse problema leva os desenvolvedores ou os gerentes de projeto a alterar o cronograma para uma compilao quinzenal.
O tempo de integrao, no entanto, continua a crescer com o tamanho do projeto.
Finalmente, instaura-se uma crise. Para manter a eficincia, o cronograma de compilao precisa ser constantemente alongado, o que aumenta os riscos do projeto. A integrao e os testes se tornam cada vez mais difceis e a equipe perde a vantagem do retorno
rpido.
Eliminando ciclos de dependncia
A soluo para esse problema dividir o ambiente
de desenvolvimento em componentes que possam ser liberados. Os componentes se tornam unidades de trabalho que podem estar sob a responsabilidade de um desenvolvedor
ou de uma equipe de desenvolvedores. Quando os desenvolvedores conseguem fazer um
componente funcionar, eles o liberam para uso por outros desenvolvedores. Eles do ao
componente um nmero de verso, o colocam em um diretrio para outras equipes usarem e continuam a modificar seu componente em suas prprias reas. Todos os demais
utilizam a verso liberada.

PRINCPIOS DE PROJETO DE PACOTES E COMPONENTES

433

medida que novas verses de um componente so feitas, outras equipes podem decidir se vo adotar a nova verso imediatamente. Se optarem por no adotar, elas simplesmente continuam a usar a verso antiga. Quando decidirem que esto prontas, passam a
usar a nova verso.
Assim, nenhuma das equipes fica merc das outras. As mudanas feitas em
um componente no precisam ter um efeito imediato nas outras equipes. Cada equipe
pode decidir por si mesma quando vai adaptar seu componente s novas verses dos
componentes que utiliza. Alm disso, a integrao acontece em pequenos incrementos.
No existe um ponto nico no qual todos os desenvolvedores devem reunir-se e integrar tudo o que esto fazendo.
Esse um processo simples e racional, e, portanto, muito utilizado. Contudo, para
faz-lo funcionar, voc precisa gerenciar a estrutura de dependncia dos componentes.
No pode haver ciclos. Se houver ciclos na estrutura de dependncia, a sndrome da manh seguinte no poder ser evitada.
Considere o diagrama de componentes da Figura 28-1. Vemos aqui uma estrutura
de componentes bastante comum, montada em um aplicativo. A funo desse aplicativo
no importante para os objetivos deste exemplo. O que importante a estrutura de
dependncia dos componentes. Note que essa estrutura um grafo direcionado. Os
componentes so os ns e os relacionamentos de dependncia so as linhas de conexo
com direo.
Observe tambm que, independentemente de qual componente voc comece, impossvel seguir os relacionamentos de dependncia e retornar a esse componente. Essa
estrutura no tem ciclos. Ela um grafo acclico direcionado (DAG Directed Acyclic
Graph).
Agora, veja o que acontece quando a equipe responsvel por MyDialogs faz uma
nova verso de seu componente. fcil descobrir quem afetado por essa verso; basta
seguir as setas de dependncia de trs para diante. Assim, MyTasks e MyApplication sero ambos afetados. Os desenvolvedores que esto trabalhando nesses componentes tero
que decidir quando devero integrar a nova verso de MyDialogs.

MyApplication

Message
Window

Task Window

My Tasks
Database

Tasks

My Dialogs

Windows

Figura 28-1
Estruturas de componentes representam um grafo acclico direcionado.

434

EMPACOTANDO O SISTEMA DE FOLHA DE PAGAMENTOS

Quando liberado, MyDialogs no tem efeito em muitos dos outros componentes do


sistema. Eles nada sabem sobre MyDialogs e no reparam quando ele muda. Isso significa
que o impacto da liberao de MyDialogs relativamente pequeno.
Quando os desenvolvedores que esto trabalhando no componente MyDialogs quiserem fazer um teste com ele, precisaro apenas compilar sua verso de MyDialogs com
a verso do componente Windows que estiverem utilizando no momento. Nenhum dos outros componentes do sistema precisa ser envolvido. Isso timo. Significa que os desenvolvedores que esto trabalhando em MyDialogs tero relativamente pouco a fazer para
configurar um teste e tero relativamente poucas variveis a considerar.
A entrega do sistema inteiro feita de baixo para cima. Primeiro, o componente
Windows compilado, testado e liberado, seguido de MessageWindow e MyDialogs, ento Task e, em seguida, TaskWindow e Database. MyTasks o prximo e, finalmente,
MyApplication. Esse processo muito claro e fcil de lidar. Sabemos como construir o
sistema porque entendemos as dependncias entre suas partes.
O efeito de um ciclo no grafo de dependncia de componentes
Digamos que um novo
requisito nos obrigue a alterar uma das classes em MyDialogs, de modo que ela utilize
uma classe de MyApplication. Isso cria um ciclo de dependncia, como mostrado no
diagrama de componentes da Figura 28-2.
Esse ciclo gera alguns problemas imediatos. Por exemplo, os desenvolvedores que
esto trabalhando no componente MyTasks sabem que, para liberar, precisam ser compatveis com Tasks, MyDialogs, Database e Windows. Contudo, com o ciclo em vigor,
agora eles tambm precisam ser compatveis com MyApplication, TaskWindow e MessageWindow. Ou seja, MyTasks depende agora de todos os outros componentes do sistema. Isso torna muito difcil liberar MyTasks. MyDialogs sofre do mesmo problema.
Na verdade, o ciclo obriga MyApplication, MyTasks e MyDialogs a sempre serem entregues ao mesmo tempo; eles se tornaram um nico componente grande. Todos os desenvolvedores que esto trabalhando em qualquer um desses componentes experimentaro a sndrome da manh seguinte mais uma vez. Uns passaro por cima dos outros,
pois todos eles precisam usar exatamente a mesma verso dos componentes dos outros.

MyApplication

Message
Window

Task Window

My Tasks
Database

Tasks

My Dialogs

Windows

Figura 28-2
Um diagrama de componentes com um ciclo.

PRINCPIOS DE PROJETO DE PACOTES E COMPONENTES

435

E isso apenas parte do problema. Considere o que acontece quando queremos


testar o componente Mydialogs. Descobrimos que precisamos referenciar todos os outros componentes do sistema, incluindo o componente Database. Ou seja, precisamos
fazer uma compilao total apenas para testar MyDialogs, o que intolervel.
Voc j se perguntou por que precisa referenciar tantas bibliotecas diferentes e tantas
coisas de outras pessoas apenas para executar um simples teste de unidade de uma de suas
classes? Provavelmente porque existem ciclos no grafo de dependncia, fato que torna muito difcil isolar mdulos. O teste de unidade e a entrega tornam-se complexos e propensos
a erro. Os tempos de compilao tambm aumentam geometricamente com o nmero de
mdulos. Alm disso, quando existem ciclos no grafo de dependncia, pode ser uma tarefa
rdua descobrir em que ordem os componentes devem ser compilados. Alis, talvez no
haja uma ordem correta, o que pode levar a alguns problemas desagradveis.
Quebrando o ciclo sempre possvel quebrar um ciclo de componentes e restabelecer o
grafo de dependncia como um DAG. Existem dois mecanismos principais:
1. Aplicar o Princpio da Inverso de Dependncia (DIP). No caso da Figura 28-2, poderamos criar uma classe base abstrata que tivesse a interface necessria para MyDialogs. Poderamos ento colocar essa classe base abstrata em MyDialogs e herd-la
para MyApplication. Isso inverteria a dependncia entre MyDialogs e MyApplication, quebrando o ciclo. Consulte a Figura 28-3.
Note que, mais uma vez, nomeamos a interface de acordo com o cliente e no de
acordo com o servidor. Essa mais uma aplicao da regra que diz que as interfaces
pertencem aos clientes.
2. Criar um novo componente de que dependam tanto MyDialogs como MyApplication. Mover a classe (ou classes) de que ambos dependem para esse novo componente. Consulte a Figura 28-4.
A segunda soluo significa que a estrutura de componentes voltil na presena de
requisitos mutantes. Alis, medida que o aplicativo cresce, a estrutura de dependncia
de componentes se instabiliza e aumenta. Assim, a estrutura de dependncia sempre deve
ser monitorada quanto existncia de ciclos. Quando ciclos ocorrem, eles devem ser queMyDialogs

MyApplication

MyDialogs

MyApplication
interface

X Server

Figura 28-3
Quebrando o ciclo com inverso de dependncia.

436

EMPACOTANDO O SISTEMA DE FOLHA DE PAGAMENTOS

MyApplication

Message
Window

Task Window

My Tasks
Database

Tasks

My Dialogs

New
Component

Windows

Figura 28-4
Quebrando o ciclo com um novo componente.
brados de algum modo. s vezes isso significar criar um novo componente, fazendo a
estrutura de dependncia crescer.
Projeto de cima para baixo versus de baixo para cima
Os problemas que discutimos at
aqui levam a uma concluso inevitvel. A estrutura de componentes no pode ser projetada
de cima para baixo na ausncia de cdigo. Em vez disso, essa estrutura evolui medida que
o sistema cresce e muda.

Algumas pessoas podem achar isso absurdo. Seria de se esperar que decomposies
de grnulos grandes, como os componentes, tambm so decomposies funcionais de
alto nvel. Quando vemos um agrupamento de grnulos grandes, como uma estrutura de
dependncia de componentes, achamos que os componentes devem representar de algum
modo as funes do sistema.
Embora seja verdade que os componentes oferecem servios e funes uns para os
outros, isso no tudo.
A estrutura de dependncia de componentes um mapa da capacidade de compilao do aplicativo. por isso que as estruturas de componentes no podem ser completamente formadas no incio do projeto. Tambm por isso que elas no so estritamente
baseadas na decomposio funcional. medida que mais classes se acumulam nos primeiros estgios de implementao e projeto, h uma necessidade crescente de gerenciar
as dependncias para que o projeto possa ser desenvolvido sem a sndrome da manh
seguinte. Alm disso, queremos manter as alteraes to localizadas quanto possvel; portanto, comeamos a prestar ateno ao SRP e ao CCP e a reunir as classes que provavelmente mudaro juntas.
medida que o aplicativo continua a crescer, ficamos preocupados com a criao
de elementos reutilizveis. Assim, o CRP comea a determinar a composio dos componentes. Por fim, quando aparecem ciclos, o ADP aplicado e o grafo de dependncia
de componentes se instabiliza e cresce por razes que esto mais ligadas estrutura de
dependncia do que funo.
Se tentssemos projetar a estrutura de dependncia de componentes antes de termos
projetado quaisquer classes, provavelmente erraramos muito. No saberamos quase nada

PRINCPIOS DE PROJETO DE PACOTES E COMPONENTES

437

sobre fechamento comum, no notaramos qualquer elemento reutilizvel e quase certamente criaramos componentes que produziriam ciclos de dependncia. Assim, a estrutura
de dependncia de componentes cresce e evolui com o projeto lgico do sistema.
No entanto, logo a estrutura de componentes torna-se estvel o suficiente para suportar o desenvolvimento por vrias equipes. Quando isso acontece, as equipes podem se
concentrar em seus prprios componentes. A comunicao entre as equipes pode ficar
restrita aos limites dos componentes. Isso permite que muitas equipes trabalhem concomitantemente no mesmo projeto, com sobrecarga mnima.
Lembre-se, no entanto, de que a estrutura dos componentes continuar a se instabilizar e mudar, medida que o desenvolvimento prosseguir. Isso impede o perfeito
isolamento entre as equipes de componentes. Essas equipes tero que trabalhar juntas,
medida que as formas dos componentes se moldarem umas nas outras.

Princpio das Dependncias Estveis (SDP)


Depender na direo da estabilidade.

Os projetos no podem ser completamente estticos. Se o projeto deve ser mantido, alguma volatilidade necessria. A soluo obedecer ao CCP. Usando esse princpio, criamos
componentes sensveis a certos tipos de mudanas. Esses componentes so projetados
para serem volteis; esperamos que eles mudem.
Qualquer componente que esperemos ser voltil no deve depender de um componente difcil de mudar! Caso contrrio, o componente voltil tambm ser difcil de mudar.
Um mdulo que voc projetou para ser fcil de alterar, no entanto, pode se tornar
difcil de mudar simplesmente porque algum colocou uma dependncia nele. Repentinamente, sem que qualquer linha de cdigo-fonte no mdulo tenha mudado, seu mdulo fica
difcil de alterar. Obedecendo ao SDP, garantimos que os mdulos mais difceis de alterar
no sejam dependentes de mdulos destinados a serem fceis de mudar.
Estabilidade O que significa estabilidade? Coloque uma moeda em p. Ela estvel nessa posio? Provavelmente voc diria que no. Contudo, ela permanecer nessa posio
por um longo tempo. Assim, a estabilidade no est diretamente relacionada frequncia
da mudana. A moeda no est mudando, mas difcil consider-la estvel.
O dicionrio Webster diz que algo estvel se no muda facilmente.1 A estabilidade
est relacionada com a quantidade de esforo exigida para fazer uma mudana. A moeda
no estvel, pois exige pouco trabalho para derrub-la. Por outro lado, uma mesa muito estvel, pois necessria um grande esforo para vir-la.
Como isso se relaciona com software? Muitos fatores tornam um componente
de software difcil de mudar: tamanho, complexidade, clareza etc. Mas vamos ignorar
todos esses fatores e nos concentrar em algo diferente. Uma maneira segura de tornar
um componente de software difcil de mudar fazer muitos outros componentes de
software dependerem dele. Um componente com muitas dependncias extremamente

Websters Third New International Dictionary.

438

EMPACOTANDO O SISTEMA DE FOLHA DE PAGAMENTOS

estvel, pois uma grande quantidade necessria para harmonizar todas as alteraes
com todos os componentes dependentes.
A Figura 28-5 mostra X, um componente estvel. Esse componente tem trs outros que dependem dele e, portanto, tem trs bons motivos para no mudar. Dizemos
que ele responsvel por esses trs componentes. Por outro lado, X no depende de
nada, de modo que no tem nenhuma influncia externa para faz-lo mudar. Dizemos
que ele independente.
A Figura 28-6, por outro lado, mostra um componente muito instvel. Y no tem outros componentes dependendo dele; dizemos que ele irresponsvel. Y tambm tem trs
componentes de que depende, de modo que mudanas podem vir de trs fontes externas.
Dizemos que Y dependente.

Figura 28-5
X: Um componente estvel.

Figura 28-6
Y: Um componente instvel.

PRINCPIOS DE PROJETO DE PACOTES E COMPONENTES

439

Mtricas de estabilidade Como podemos medir a estabilidade de um componente? Uma


maneira contar o nmero de dependncias que entram e saem desse componente. Essas
contagens nos permitem calcular a estabilidade posicional do componente:
Aa (acoplamentos aferentes): o nmero de classes fora desse componente que dependem de classes dentro desse componente
Ae (acoplamentos eferentes): o nmero de classes dentro desse componente que dependem de classes fora desse componente
I (instabilidade):
Essa mtrica tem o intervalo [0,1]. I = 0 indica um componente estvel ao mximo.
I = 1 indica um componente instvel ao mximo.
As mtricas Aa e Ae so calculadas contando-se o nmero de classes fora do componente em questo que tm dependncias nas classes dentro do componente em
questo. Considere o exemplo da Figura 28-7:
As setas tracejadas entre os componentes representam dependncias dos componentes. Os relacionamentos entre as classes desses componentes mostram como essas
dependncias so implementadas. Existem relacionamentos de herana e associao.
Agora, digamos que queremos calcular a estabilidade do componente Pc. Verificamos que trs classes fora de Pc dependem de classes que esto em Pc. Assim, Aa = 3.
Alm disso, h uma classe fora de Pc de que classes que esto em Pc dependem. Assim,
Ae = 1 e I = 1/4.
Em C#, essas dependncias normalmente so representadas por instrues using.
Alis, a mtrica I ser mais fcil de calcular quando voc tiver organizado seu cdigo-fonte
de modo que exista uma classe em cada arquivo-fonte. Em C#, a mtrica I pode ser calculada pela contagem das instrues using e por nomes totalmente qualificados.
Quando a mtrica I igual a 1, isso significa que nenhum outro componente depende desse componente (Aa = 0) e que esse componente depende de outros componentes
(Ae > 0). o mximo de instabilidade que um componente pode apresentar; ele irresponsvel e dependente. Sua falta de dependentes no lhe d qualquer motivo para no
mudar e os componentes de que ele depende podem lhe dar amplos motivos para mudar.
Pa
q

Pb
r

Pc
t

Pd
u

Figura 28-7
Tabulando Aa, Ae e I.

440

EMPACOTANDO O SISTEMA DE FOLHA DE PAGAMENTOS

Por outro lado, quando a mtrica I igual a 0, outros componentes dependem do


componente (Aa > 0), mas ele mesmo no depende de quaisquer outros (Ae = 0). Ele
responsvel e independente. Tal componente o mais estvel possvel. Seus dependentes
tornam difcil alter-lo e ele no tem dependncias que possam obrig-lo a mudar.
De acordo com o SDP, a mtrica I de um componente deve ser maior do que as
mtricas I dos componentes de que ele depende. Ou seja, as mtricas I devem diminuir
na direo da dependncia.
Estabilidade de componente varivel
Se todos os componentes de um sistema fossem
estveis ao mximo, o sistema seria inaltervel. Essa no uma situao desejvel. Alis,
queremos projetar nossa estrutura de componentes de modo que alguns deles sejam instveis e outros estveis. A Figura 28-8 mostra uma configurao ideal para um sistema
com trs componentes.
Os componentes sujeitos mudana esto na parte superior e dependem do componente estvel na parte inferior. Colocar os componentes instveis na parte superior
do diagrama uma conveno til, pois qualquer seta que aponte para cima est violando o SDP.
A Figura 28-9 mostra como o SDP pode ser violado. Flexible um componente que
pretendemos que seja fcil de alterar. Queremos que Flexible seja instvel, com uma
mtrica I prxima a 0. Contudo, algum desenvolvedor, trabalhando no componente chamado Stable, colocou uma dependncia em Flexible. Isso viola o SDP, pois a mtrica
I de Stable muito menor do que a mtrica I de Flexible. Como resultado, Flexible
no mais ser fcil de mudar. Uma alterao em Flexible nos obrigar a lidar com Stable e com todas as suas dependentes.
Para corrigir isso, temos que interromper de algum modo a dependncia de Stable
em relao a Flexible. Por que essa dependncia existe? Vamos supor que dentro de
Flexible exista uma classe C que outra classe U dentro de Stable precisa usar. Consulte
a Figura 28-10.

I=1

Instvel

Instvel

Estvel

I=0

Figura 28-8
Configurao de componentes ideal.

I=1

PRINCPIOS DE PROJETO DE PACOTES E COMPONENTES

441

I=0,25
Stable

Flexible
I=0,75

Figura 28-9
Violao do SDP.

Stable

Flexible

Figura 28-10
A causa da dependncia ruim.
Podemos corrigir esse problema usando o DIP. Criamos uma interface chamada IU
e a colocamos em um componente chamado UInterface. Garantimos que essa interface
declare todos os mtodos que U precisa usar. Ento, fazemos C herdar dessa interface.
Consulte a Figura 28-11. Isso interrompe a dependncia de Stable em relao a Flexible e obriga os dois componentes a serem dependentes de UInterface. UInterface
muito estvel (I = 0) e Flexible mantm sua instabilidade necessria (I = 1). Agora,
todas as dependncias fluem na direo decrescente de I.

442

EMPACOTANDO O SISTEMA DE FOLHA DE PAGAMENTOS

Stable

UInterface

Flexible

interface
IU

Figura 28-11
Corrigindo a violao da estabilidade com o DIP.
Localizao de projeto de alto nvel
Alguma parte do software no sistema no deve
mudar com muita frequncia. Essa parte do software representa a arquitetura de alto
nvel e decises de projeto. No queremos que essas decises arquitetnicas sejam volteis. Assim, a parte do software que encapsula o projeto de alto nvel do sistema deve
ser colocada em componentes estveis (I = 0). Os componentes instveis (I = 1) devem
conter apenas software que provavelmente mudar.
Contudo, se o projeto de alto nvel for colocado em componentes estveis, ser difcil
alterar o cdigo-fonte que representa esse projeto, o que poderia tornar o projeto rgido.
Como um componente que estvel ao mximo (I = 0) pode ser flexvel o suficiente para suportar alteraes? A resposta ser encontrada no OCP. Esse princpio nos diz que possvel
e desejvel criar classes que sejam flexveis o suficiente para serem estendidas sem exigir
modificao. Quais tipos de classes obedecem a esse princpio? A resposta : as classes
abstratas.

Princpio das Abstraes Estveis (SAP)


Um componente deve ser to abstrato quanto estvel.

Esse princpio estabelece uma relao entre estabilidade e abstrao. Ele diz que um
componente estvel tambm deve ser abstrato para que sua estabilidade no impea que
ele seja estendido. Por outro lado, ele diz que um componente instvel deve ser concreto,
pois sua instabilidade permite que o cdigo concreto dentro dele seja facilmente alterado.
Assim, se um componente precisa ser estvel, ele tambm deve consistir em classes
abstratas para que possa ser estendido. Os componentes estveis e extensveis so flexveis e no restringem demasiadamente o projeto.

PRINCPIOS DE PROJETO DE PACOTES E COMPONENTES

443

Combinados, SAP e SDP equivalem ao DIP para componentes. Isso verdade,


porque o SDP diz que as dependncias devem fluir na direo da estabilidade e o SAP
diz que estabilidade implica abstrao. Assim, as dependncias fluem na direo da
abstrao.
Contudo, o DIP lida com classes. Com classes no existe meio-termo. Uma classe ou
abstrata ou no . A combinao de SDP e SAP lida com componentes e permite que um
componente possa ser parcialmente abstrato e parcialmente estvel.
Medindo a abstrao A mtrica A uma medida da abstrao de um componente. Seu
valor simplesmente a relao entre as classes abstratas de um componente e o nmero
total de classes no componente, onde:
Nc o nmero de classes no componente.
Na o nmero de classes abstratas no componente. Lembre-se de que uma classe
abstrata uma classe com pelo menos um mtodo abstrato e no pode ser instanciada:
A (abstrao).
A mtrica A varia de 0 a 1. Zero significa que o componente no tem nenhuma classe abstrata. O valor 1 significa que o componente contm somente classes abstratas.
A sequncia principal Estamos agora em condies de definir a relao entre estabilidade (I) e abstrao (A). Podemos criar um grfico com A no eixo vertical e I no eixo horizontal. Se representarmos os dois tipos bons de componentes nesse grfico, encontraremos
os componentes que so estveis e abstratos ao mximo no canto superior esquerdo, em
(0,1). Os componentes que so instveis e concretos ao mximo esto no canto inferior
direito, em (1,0). Consulte a Figura 28-12.
Nem todos os componentes podem cair em uma dessas duas posies. Os componentes tm graus de abstrao e estabilidade. Por exemplo, muito comum uma classe
abstrata derivar de outra classe abstrata. A derivada uma abstrao que tem uma
dependncia. Assim, embora seja abstrata ao mximo, ela no ser estvel ao mximo.
Sua dependncia diminuir sua estabilidade.
Como no podemos impor que todos os componentes fiquem em (0,1) ou em (1,0),
devemos supor que um lugar geomtrico de pontos no grfico A/I define posies razoveis

(0,1)

(1,0)

I
Figura 28-12
O grfico A/I.

444

EMPACOTANDO O SISTEMA DE FOLHA DE PAGAMENTOS

(1,1)

(0,1)

na
Zo d e d e
a
id
til

u
In

A
q
se
u

ia
nc
pr
in
p
ci
al

na
Zo de nto
e
m
fri
so
(0,0)

(1,0)

Figura 28-13
Zonas de excluso.
para os componentes. Podemos deduzir qual esse lugar geomtrico encontrando as reas
onde os componentes no devem estar; isto , as zonas de excluso. Consulte a Figura 28-13.
Considere um componente na rea prxima a (0,0). Esse componente altamente
estvel e concreto. Tal componente no desejvel, pois rgido. Ele no pode ser estendido, pois no abstrato. E ele muito difcil de mudar, devido a sua estabilidade. Assim,
normalmente no esperamos ver componentes bem projetados situados prximos a (0,0).
A rea perto de (0,0) uma zona de excluso, a zona de sofrimento.
Deve-se notar que, em alguns casos, os componentes caem dentro da zona de sofrimento. Um exemplo seria um componente representando um esquema de banco de dados.
Os esquemas de banco de dados so notoriamente volteis, extremamente concretos e
depende-se muito deles. Esse um dos motivos pelos quais a interface entre aplicativos
de OO e bancos de dados to difcil e as atualizaes de esquema geralmente so trabalhosas.
Outro exemplo de componente que fica prximo a (0,0) aquele que contm uma biblioteca concreta de utilidades. Embora tal componente tenha uma mtrica I igual a 1, na
verdade ele pode ser no-voltil. Considere um componente string, por exemplo. Mesmo
que todas as classes dentro dele sejam concretas, ele no-voltil. Tais componentes so
inofensivos na zona (0,0), pois no provvel que sejam alterados. Alis, podemos considerar um terceiro eixo no grfico como o da volatilidade. Sendo assim, o grfico da Figura
28-13 mostra o plano na volatilidade = 1.
Considere um componente prximo a (1,1). Esse local indesejvel, pois o componente abstrato ao mximo e, apesar disso, no tem dependentes. Tais componentes so
inteis. Assim, essa regio chamada de zona de inutilidade.
Parece evidente que gostaramos que nossos componentes volteis estivessem o
mais longe possvel das duas zonas de excluso. O lugar geomtrico dos pontos distantes ao mximo de cada zona a linha que liga (1,0) e (0,1). Essa linha conhecida
como sequncia principal.2
Um componente situado na sequncia principal no abstrato demais por sua
estabilidade nem instvel demais por sua abstrao. Ele no intil nem particular2

O nome sequncia principal foi adotado devido ao meu interesse por astronomia e por diagramas HR
(Hertzsprung-Russell).

PRINCPIOS DE PROJETO DE PACOTES E COMPONENTES

445

mente doloroso. Depende-se dele medida que abstrato e ele depende de outros
medida que concreto.
Claramente, as posies mais desejveis para um componente ocupar esto em um
dos dois pontos extremos da sequncia principal. Contudo, de acordo com minha experincia, menos da metade dos componentes de um projeto podem ter tais caractersticas
ideais. Os outros componentes tm as melhores caractersticas se estiverem sobre a
sequncia principal ou prximos a ela.
Distncia da sequncia principal Isso nos leva nossa ltima mtrica. Se desejvel que
os componentes estejam na sequncia principal ou prximos dela, podemos criar uma
mtrica que avalie quanto um componente est distante desse ideal.
D (distncia).

. Isso varia de [0,~0,707].

D (distncia normalizada). D = |A + I 1|. Esta mtrica muito mais conveniente


do que D, pois varia de [0,1]. Zero indica que o componente est exatamente na sequncia
principal. Um indica que o componente est o mais longe possvel da sequncia principal.
Dada essa mtrica, um projeto pode ser analisado quanto a sua conformao global
sequncia principal. A mtrica D de cada componente pode ser calculada. Qualquer
componente que tenha um valor D que no esteja prximo a 0 pode ser reexaminado e
reestruturado. Na verdade, esse tipo de anlise tem me ajudado muito a definir componentes que so mais passveis de manuteno e menos sensveis mudana.
Tambm possvel fazer uma anlise estatstica de um projeto. Pode-se calcular a
mdia e a varincia de todas as mtricas D dos componentes dentro de um projeto. Pode-se
esperar que um projeto em conformidade tenha mdia e varincia prximas a 0. A varincia
pode ser utilizada para estabelecer limites de controle, os quais podem identificar componentes excepcionais em comparao a todos os outros. Consulte a Figura 28-14.
Nesse diagrama de disperso no baseado em dados reais vemos que a maioria
dos componentes se situa ao longo da sequncia principal, mas que alguns deles esto a
mais de um desvio padro (Z = 1) distantes da mdia. Vale a pena observar esses componentes anmalos. Por algum motivo, eles so muito abstratos, com poucos dependentes,
ou muito concretos, com muitos dependentes.

qu

Se

Z=2

n
a

ci

pa

ci

in
pr
l

Z=1

Z=2

Z=1
1

Figura 28-14
Diagrama de disperso dos valores de D do componente.

446

EMPACOTANDO O SISTEMA DE FOLHA DE PAGAMENTOS

Pacote: Payroll

0,2

D' 0,1

R1.0

R1.1

R1.2

R2.0

R2.1

Entrega (Release)

Figura 28-15
Grfico do tempo dos valores de D de um nico componente.
Outra maneira de usar as mtricas representar o valor de D de cada componente ao longo do tempo. A Figura 28-15 mostra um modelo simulado de tal representao.
Voc pode ver que algumas dependncias estranhas entraram no componente Payroll
ao longo das ltimas entregas (releases, R). O grfico mostra um limite de controle em
D = 0,1. O ponto R2.1 ultrapassou esse limite de controle; portanto, seria interessante
perdermos algum tempo para descobrir por que esse componente est to longe da
sequncia principal.

Concluso
As mtricas de gerenciamento de dependncias descritas neste captulo avaliam a conformidade de um projeto a um padro de dependncia e abstrao que considero bom. A
experincia tem mostrado que certas dependncias so boas e outras ruins. Esse padro
reflete essa experincia. Contudo, uma mtrica no irrevogvel; apenas uma medida
em relao a um padro arbitrrio. possvel que o padro escolhido neste captulo seja
adequado para certos aplicativos, mas no para outros. Talvez mtricas bem melhores
sejam utilizadas para avaliar a qualidade de um projeto.

Captulo 29

FACTORY
O homem que constri uma fbrica constri um templo.
Calvin Coolidge (1872-1933)

Princpio da Inverso de Dependncia (DIP), apresentado no Captulo 11, nos informa que devemos dar preferncia s dependncias em classes abstratas e evitar
dependncias em classes concretas, especialmente quando essas classes so volteis. Portanto, o trecho de cdigo a seguir viola esse princpio:
Circle c = new Circle(origin, 1);
Circle uma classe concreta. Portanto, os mdulos que criam instncias de Circle
devem violar o DIP. Alis, qualquer linha de cdigo que utilize a palavra-chave new viola
o DIP.
Existem ocasies em que violar o DIP inofensivo, o que uma boa proteo. Quanto
maior a probabilidade de uma classe concreta mudar, maior a probabilidade de ela causar
problemas. Mas se a classe concreta no voltil, depender dela no preocupante. Por
exemplo, criar e depender instncias de string muito seguro, pois improvvel que
string mude em breve.
Por outro lado, quando estamos desenvolvendo ativamente um aplicativo, muitas classes concretas so bastante volteis, de modo que depender delas problemtico. melhor
dependermos de uma interface abstrata para nos proteger da maioria das alteraes.
O padro FACTORY nos permite criar instncias de objetos concretos, embora
dependamos apenas de interfaces abstratas. Portanto, ele pode ser de grande ajuda durante o desenvolvimento ativo, quando essas classes concretas so altamente volteis.
A Figura 29-1 mostra o cenrio problemtico. Temos uma classe chamada SomeApp
que depende da interface Shape. SomeApp usa instncias de Shape exclusivamente por
meio da interface Shape e no utiliza mtodos especficos de Square ou de Circle. Infe-

448

EMPACOTANDO O SISTEMA DE FOLHA DE PAGAMENTOS

lizmente, SomeApp tambm cria instncias de Square e Circle e, assim, precisa depender
de classes concretas.

Some App
creates

interface
Shape

Square

Circle

Figura 29-1
Um aplicativo que viola o DIP para criar classes concretas.

Podemos corrigir isso aplicando o padro FACTORY em SomeApp, como na Figura 29-2.
Aqui, vemos a interface ShapeFactory, a qual tem dois mtodos: MakeSquare e MakeCircle. O mtodo MakeSquare retorna uma instncia de Square e o mtodo MakeCircle retorna uma instncia de Circle. Contudo, o tipo de retorno das duas funes Shape.
A Listagem 29-1 mostra como o cdigo de ShapeFactory. A Listagem 29-2 mostra
ShapeFactoryImplementation.

Some App

interface
ShapeFactory
interface
Shape
+ MakeSquare()
+ MakeCircle()

ShapeFactory
Implementation

Square

Circle

creates

Figura 29-2
Aplicao de FACTORY em SomeApp.

FACTORY

449

Listagem 29-1
ShapeFactory.cs
public interface ShapeFactory
{
Shape MakeCircle();
Shape MakeSquare();
}

Listagem 29-2
ShapeFactoryImplementation.cs
public class ShapeFactoryImplementation : ShapeFactory
{
public Shape MakeCircle()
{
return new Circle();
}
public Shape MakeSquare()
{
return new Square();
}
}

Note que isso resolve completamente o problema da dependncia de classes concretas. O cdigo do aplicativo no depende mais de Circle nem de Square, mas ainda consegue criar instncias deles. Ele manipula essas instncias por meio da interface Shape e
nunca chama mtodos especficos de Square ou Circle.
O problema da dependncia de uma classe concreta foi removido. Algum precisa
criar ShapeFactoryImplementation, mas ningum mais precisa criar Square ou Circle. Mais provavelmente, ShapeFactoryImplementation ser criado por Main ou por
uma funo de inicializao ligada a Main.

Um problema de dependncia
Os leitores perspicazes reconhecero um problema nessa forma do padro FACTORY. A
classe ShapeFactory tem um mtodo para cada uma das derivadas de Shape. Isso resulta em uma dependncia exclusiva do nome que torna difcil adicionar novas derivadas
em Shape. Sempre que adicionamos uma nova derivada de Shape, precisamos adicionar
um novo mtodo na interface ShapeFactory. Na maioria dos casos, isso significa que precisaremos compilar e entregar novamente todos os usurios de ShapeFactory.1

1
Outra vez, nem sempre isso vlido em C#. Voc poderia correr o risco de deixar de compilar e entregar de
novo os clientes de uma interface alterada.

450

EMPACOTANDO O SISTEMA DE FOLHA DE PAGAMENTOS

Podemos nos livrar desse problema de dependncia sacrificando um pouco a segurana de tipo. Em vez de fornecer a ShapeFactory um mtodo para cada derivada de
Shape, podemos fornecer apenas uma funo make que receba um objeto string. Por
exemplo, veja a Listagem 29-3. Essa tcnica exige que ShapeFactoryImplementation
utilize um encadeamento if/else no argumento recebido para selecionar a derivada de
Shape a ser instanciada. Isso est mostrado nas listagens 29-4 e 29-5.

Listagem 29-3
Um trecho que cria um crculo
[Test]
public void TestCreateCircle()
{
Shape s = factory.Make("Circle");
Assert.IsTrue(s is Circle);
}

Listagem 29-4
ShapeFactory.cs
public interface ShapeFactory
{
Shape Make(string name);
}

Listagem 29-5
ShapeFactoryImplementation.cs
public class ShapeFactoryImplementation : ShapeFactory
{
public Shape Make(string name)
{
if(name.Equals("Circle"))
return new Circle();
else if(name.Equals("Square"))
return new Square();
else
throw new Exception(
"ShapeFactory cannot create: {0}", name);
}
}

FACTORY

451

Algum poderia argumentar que isso perigoso, porque os chamadores que escreverem incorretamente o nome de uma figura recebero um erro de tempo de execuo, em
vez de um erro em tempo de compilao. verdade. Contudo, se voc escrever os testes
de unidade adequados e aplicar desenvolvimento guiado por testes, capturar esses erros
de tempo de execuo muito antes de eles se tornarem problemas.

Tipagem esttica versus dinmica


O compromisso que acabamos de testemunhar entre segurana de tipo e flexibilidade tipifica
o debate crnico em relao aos estilos de linguagem. De um lado esto as linguagens estaticamente tipadas, como C#, C++ e Java, que verificam os tipos em tempo de compilao
e geram erros de compilao caso os tipos declarados no sejam coerentes. De outro lado
esto as linguagens tipadas dinamicamente, como Python, Ruby, Groovy e Smalltalk, que
fazem sua verificao de tipo em tempo de execuo. O compilador no insiste na coerncia
de tipos; na verdade, a sintaxe dessas linguagens tambm no permite tal verificao.
Como vimos no exemplo de FACTORY, a tipagem esttica pode levar a laos de
dependncia que impem modificaes nos arquivos-fonte com o nico objetivo de manter a coerncia de tipos. Em nosso caso, precisamos alterar a interface ShapeFactory
sempre que uma nova derivada de Shape adicionada. Essas alteraes podem impor
recompilaes e novas entregas que de outro modo seriam desnecessrias. Resolvemos
esse problema quando afrouxamos a segurana de tipo e dependemos de nossos testes
de unidade para capturar erros de tipo; ganhamos a flexibilidade de adicionar novas
derivadas de Shape sem alterar ShapeFactory.
Os defensores das linguagens estaticamente tipadas sustentam que a segurana em
tempo de compilao compensa os pequenos problemas de dependncia, o maior ritmo
de modificaes no cdigo-fonte e a maior frequncia de recompilaes e novas entregas.
O outro lado argumenta que os testes de unidade encontraro a maioria dos problemas
que a tipagem esttica encontraria e que, portanto, o trabalho de modificao do cdigo-fonte, de recompilao e de nova entrega desnecessrio.
Acho interessante o fato de que o aumento da popularidade das linguagens tipadas
dinamicamente esteja, at aqui, controlando o aumento da adoo do desenvolvimento
guiado por testes (TTD). Talvez os programadores que adotam o TDD estejam verificando
que isso muda a equao segurana versus flexibilidade. Talvez esses programadores estejam se convencendo gradualmente de que a flexibilidade das linguagens tipadas dinamicamente supera as vantagens da verificao de tipo esttica.
Talvez estejamos no auge da popularidade das linguagens estaticamente tipadas. Se
a tendncia atual continuar, poderemos verificar que as principais linguagens industriais
estaro mais relacionadas com Smalltalk do que com C++.

Fbricas substituveis
Uma das maiores vantagens de usar fbricas a capacidade de substituir uma implementao de uma fbrica por outra. Desse modo, voc pode substituir famlias de objetos
dentro de um aplicativo.
Por exemplo, imagine um aplicativo que precisasse se adaptar a muitas implementaes de banco de dados diferentes. Em nosso exemplo, vamos supor que os usurios
possam utilizar arquivos planos ou adquirir um adaptador Oracle. Poderamos usar o

452

EMPACOTANDO O SISTEMA DE FOLHA DE PAGAMENTOS

padro PROXY para isolar o aplicativo da implementao do banco de dados.2 Tambm


poderamos usar fbricas para instanciar os proxys. A Figura 29-3 mostra a estrutura.
interface
Employee
Factory

Application

+ makeEmp
+ makeTimecard
interface
Employee

interface
TimeCard

Oracle
TimeCard
Proxy

Oracle
Employee
Proxy

Oracle
Employee
Factory

creates
FlatFile
Employee
Proxy

FlatFile
TimeCard
Proxy
FlatFile
Employee
Factory

creates

Figura 29-3
Fbrica substituvel.
Observe as duas implementaes de EmployeeFactory. Uma cria proxys que trabalham com arquivos planos (flat files) e a outra cria proxys que trabalham com Oracle.
Note tambm que o aplicativo no sabe ou no se preocupa com a implementao que est
sendo utilizada.

Usando fbricas para dispositivos de teste


Ao escrevermos testes de unidade, muitas vezes queremos testar o comportamento de um
mdulo separado dos mdulos que ele utiliza. Por exemplo, poderamos ter um aplicativo
Payroll que usasse um banco de dados (consulte a Figura 29-4). Talvez quisssemos
testar a funo do mdulo Payroll sem usar o banco de dados.

Payroll

Database

Figura 29-4
Payroll usa o banco de dados.
2

Estudaremos o padro PROXY no Captulo 34. No momento, voc precisa saber apenas que Proxy uma
classe que sabe ler objetos especficos de tipos de bancos de dados em particular.

FACTORY

453

Podemos fazer isso usando uma interface abstrata para o banco de dados. Uma
implementao dessa interface abstrata utiliza o banco de dados real. Outra implementao um cdigo de teste escrito para simular o comportamento do banco de dados
e para verificar se as chamadas do banco de dados esto sendo feitas corretamente. A
Figura 29-5 mostra a estrutura. O mdulo PayrollTest testa PayrollModule fazendo chamadas para ele e tambm implementa a interface Database de modo que ela
possa capturar as chamadas que Payroll faz para o banco de dados. Isso permite que
PayrollTest certifique-se de que Payroll est se comportando corretamente. Isso
tambm permite que PayrollTest simule muitos tipos de falhas e problemas de banco
de dados que de outro modo so difceis de gerar. Esse um padro de teste conhecido
como SELF-SHUNT, tambm s vezes conhecido como imitao (mocking ou spoofing,
em ingls).
Como Payroll obtm a instncia de PayrollTest que utiliza como Database? Certamente, Payroll no vai criar PayrollTest. Da mesma forma, de algum modo Payroll
deve obter uma referncia para a implementao de Database que vai usar.
Em alguns casos, perfeitamente natural PayrollTest passar a referncia de Database para Payroll. Em outros, pode ser que PayrollTest precise configurar uma varivel
global para se referir a Database. Em ainda outros casos, Payroll pode estar esperando
para criar a instncia de Database. Neste ltimo caso, podemos usar um objeto Factory
para fazer Payroll pensar que est criando a verso de teste de Database, passando uma
fbrica alternativa para Payroll.
A Figura 29-6 mostra uma possvel estrutura. O mdulo Payroll adquire a fbrica
por meio de uma varivel global ou de uma varivel esttica em uma classe global
chamada GdatabaseFactory. O mdulo PayrollTest implementa DatabaseFactory
e configura uma referncia para si mesmo nessa varivel GdatabaseFactory. Quando
Payroll usa a fbrica para criar um objeto Database, o mdulo PayrollTest captura a
chamada e retorna uma referncia para si mesmo. Desse modo, Payroll est convencido
de que criou PayrollDatabase e, ainda assim, o mdulo PayrollTest pode falsificar o
mdulo Payroll e capturar todas as chamadas de banco de dados.

Importncia das fbricas


Uma interpretao rigorosa do DIP insistiria no uso de fbricas para toda classe voltil do
sistema. Alm disso, o poder do padro FACTORY sedutor. s vezes, esses dois fatores
podem nos levar a usar fbricas por padro, o que no uma prtica recomendada.
interface
Database

Payroll

PayrollTest

Database
Implementation

Figura 29-5
Banco de dados de SELF-SHUNTs de PayrollTest.

454

EMPACOTANDO O SISTEMA DE FOLHA DE PAGAMENTOS

GdatabaseFactory
global

interface
Database
Factory

interface
Payroll

Database

Database
Factory
Implementation

creates

PayrollTest
Database
Implementation

Figura 29-6
Falsificando a fbrica.
Eu insiro as fbricas no sistema somente quando elas se tornam imprescindveis.
Por exemplo, se o uso do padro PROXY se torna necessrio, provavelmente ser preciso
usar uma fbrica para criar os objetos persistentes. Ou, se por meio de testes de unidade,
eu encontrar situaes nas quais preciso falsificar o criador de um objeto, provavelmente
usarei uma fbrica. Mas, pressuponho desde o incio que as fbricas sero necessrias.
As fbricas representam uma complexidade que frequentemente pode ser evitada,
sobretudo nas fases iniciais de um projeto em desenvolvimento. Quando so usadas por
padro, as fbricas aumentam a dificuldade de ampliar o projeto. Para criar uma nova
classe, talvez seja necessrio criar at quatro novas classes: as duas classes de interface
que representam a nova classe e sua fbrica e as duas classes concretas que implementam
essas interfaces.

Concluso
As fbricas so ferramentas poderosase e podem representar uma grande vantagem na
obedincia ao DIP. Elas permitem que mdulos de diretiva de alto nvel criem instncias
de objetos sem depender das implementaes concretas desses objetos. As fbricas tambm possibilitam trocar famlias de implementaes completamente diferentes por um
grupo de classes. Contudo, as fbricas so uma complexidade que muitas vezes pode ser
evitada. Utiliz-las por padro raramente a melhor estratgia.

Bibliografia
[GOF95] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995.

Captulo 30

ESTUDO DE CASO DA FOLHA


DE PAGAMENTOS: ANLISE
DO PACOTE
Regra prtica: se voc acha que algo engenhoso e sofisticado,
cuidado provavelmente uma extravagncia.
Donald A. Norman, The Design of Everyday Things, 1990

izemos bastante anlise, projeto e implementao do problema da folha de pagamentos. No entanto, ainda temos muitas decises a tomar. Em primeiro lugar, apenas
dois programadores Bob e Micah trabalharam no problema. A estrutura atual do ambiente de desenvolvimento est de acordo com essa situao. Todos os arquivos de programa esto localizados em um nico diretrio. No existe uma estrutura de ordem mais alta.
No existem pacotes, subsistemas, nem componentes que possam ser entregues, a no ser
o aplicativo inteiro. Isso no ir evoluir.
Devemos supor que, medida que esse programa crescer, o nmero de pessoas trabalhando nele tambm crescer. Para torn-lo adequado para vrios desenvolvedores,
precisamos dividir o cdigo-fonte em componentes assemblies, DLLs que possam ser
facilmente obtidos por check-out*, modificados e testados.
Atualmente o aplicativo de folha de pagamentos consiste em 4.382 linhas de cdigo,
divididas em aproximadamente 63 classes e 80 arquivos-fonte. Embora esse no seja um
nmero muito grande, representa uma carga organizacional. Como devemos gerenciar esses
arquivos-fonte e dividi-los em componentes que possam ser entregues independentemente?
De forma anloga, como devemos dividir o trabalho de implementao de modo que
o desenvolvimento possa ocorrer harmoniosamente, sem que os programadores atrapalhem uns aos outros? Deveramos dividir as classes em grupos que fossem convenientes
para indivduos ou equipes fazerem check-out e darem suporte.

* N. de R.T.: O autor refere-se operao de obter uma cpia de trabalho de um ou mais arquivos a partir de
um sistema de verses concorrentes (CVS Concurrent Version System).

456

EMPACOTANDO O SISTEMA DE FOLHA DE PAGAMENTOS

Estrutura de componentes e notao


A Figura 30-1 mostra uma possvel estrutura de componentes para o aplicativo de folha de
pagamentos. Trataremos da convenincia dessa estrutura posteriormente. Por enquanto,
nos limitaremos ao modo como ela documentada e usada.
Por conveno, os diagramas de componentes so desenhados com as dependncias
apontando para baixo. Os componentes da parte superior so dependentes. Os da parte
inferior so os dos quais se depende.
A Figura 30-1 dividiu o aplicativo de folha de pagamentos em oito componentes. O
componente PayrollApplication contm a classe PayrollApplication e as classes
TransactionSource e TextParserTransactionSource. O componente Transactions
contm a hierarquia de classes Transaction completa. Os constituintes dos outros componentes devem ficar evidentes examinando-se cuidadosamente o diagrama.
As dependncias tambm devem estar claras. O componente PayrollApplication
depende do componente Transactions porque a classe PayrollApplication chama o
mtodo Transaction::Execute. O componente Transactions depende do componente
PayrollDatabase porque cada uma das muitas derivadas de Transaction se comunica
diretamente com a classe PayrollDatabase. As outras dependncias so justificveis da
mesma forma.
Quais critrios utilizamos para agrupar essas classes em componentes? Simplesmente inserimos nos mesmos componentes as classes que parecia necessrio ficarem juntas.
Contudo, conforme aprendemos no Captulo 28, isso provavelmente no uma boa ideia.
Payroll
Application
+ PayrollApplication
+ TransactionSource
+ TextParserTransactionSource
Application
Transactions

+ Application

+ Transaction
+ (Todas as
derivadas)

Payroll
Database
+ PayrollDatabase
+ Employee

Methods
+ PaymentMethod
+ (Todas as
derivadas)

Schedules
+ PaymentSchedule
+ (Todas as
derivadas)

Classifications

Affiliations

+ PaymentClassification
+ (Todas as
derivadas)
+ TimeCard
+ SalesReceipt

+ Affiliation
+ UnionAffiliation
+ ServiceCharge

Figura 30-1
Possvel diagrama de componentes do aplicativo de folha de pagamentos.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: ANLISE DO PACOTE

457

Considere o que acontecer se fizermos uma mudana no componente Classifications. Essa mudana forar uma nova compilao e um novo teste do componente
EmployeeDatabase e deveria forar mesmo. Mas tambm forar uma nova compilao
e um novo teste do componente Transactions. Certamente, ChangeClassificationTransaction e suas trs derivadas da Figura 27-13 devem ser compiladas e testadas
novamente, mas por que as outras devem ser?
Tecnicamente, essas outras transaes no precisam de nova compilao e novo teste.
Contudo, se elas fazem parte do componente Transactions e se esse componente vai ser
entregue novamente para lidar com as alteraes feitas no componente Classifications,
no compilar e testar novamente o componente como um todo poderia ser visto como uma
irresponsabilidade. Mesmo que todas as transaes no sejam compiladas e testadas, o pacote em si deve ser entregue novamente e implantado outra vez. Ento, todos os seus clientes
exigiro no mnimo revalidao e provavelmente tambm uma nova compilao.
As classes do componente Transactions no compartilham o mesmo fechamento.
Cada uma sensvel s suas prprias alteraes particulares. ServiceChargeTransaction aberta s mudanas na classe ServiceCharge, enquanto TimeCardTransaction
aberta s mudanas na classe TimeCard. Na verdade, conforme a Figura 30-1 indica,
uma parte do componente Transactions dependente de quase todas as outras partes
do software. Assim, esse componente tem um alto ritmo de entregas (releases). Sempre
que algo for alterado em qualquer lugar para baixo, o componente Transactions ter de
ser revalidado e entregue novamente.
O pacote PayrollApplication ainda mais sensvel: qualquer mudana em
qualquer parte do sistema afetar esse pacote, de modo que seu ritmo de entregas deve
ser enorme. Voc poderia pensar que isso inevitvel que medida que se sobe na
hierarquia de dependncia de pacotes, o ritmo de entregas deve aumentar. Felizmente,
no entanto, isso no verdade, e evitar esse sintoma um dos principais objetivos do
projeto orientado a objetos.

Aplicando o Princpio do Fechamento Comum (CCP)


Analise a Figura 30-2, que agrupa as classes do aplicativo de folha de pagamentos de acordo com seu fechamento. Por exemplo, o componente PayrollApplication contm as
classes PayrollApplication e TransactionSource. Essas duas classes dependem da
classe abstrata Transaction, que est no componente PayrollDomain. Note que a classe
TextParserTransactionSource est em outro componente que depende da classe abstrata PayrollApplication. Isso cria uma estrutura invertida na qual os detalhes dependem das generalidades e as generalidades so independentes, o que obedece ao DIP.
O caso mais extraordinrio de generalidade e independncia o componente
PayrollDomain. Esse componente contm a essncia do sistema inteiro, apesar de no
depender de nada! Examine esse componente atentamente. Ele contm Employee, PaymentClassification, PaymentMethod, PaymentSchedule, Affiliation e Transaction. Esse componente contm todas as principais abstraes de nosso modelo, apesar
de no ter dependncias. Por qu? Porque todas as classes que ele contm so abstratas.
Considere o componente Classifications, que contm as trs derivadas de PaymentClassification, junto com a classe ChangeClassificationTransaction e suas
trs derivadas, assim como TimeCard e SalesReceipt. Note que qualquer alterao feita
nessas nove classes isolada; a no ser TextParser, nenhum outro componente afetado! Tal isolamento tambm vale para o componente Methods, para o componente Schedules e para o componente Affiliations. Isso muito isolamento.

458

EMPACOTANDO O SISTEMA DE FOLHA DE PAGAMENTOS

Text Parser

TextParserTransactionSource

Payroll Application

PayrollApplication
TransactionSource

Application

Application

Classifications
HourlyClassification
CommissionedClassification
SalariedClassification
ChangeClassificationTransaction
e derivadas
TimeCard
SalesReceipt

Methods
MailMethod
HoldMethod
DirectMethod
ChangeMethodTransaction
e derivadas

Schedules
WeeklySchedule
MonthlySchedule
BiweeklySchedule
ChangeScheduleTransaction
e derivadas

Affiliations

Payroll Domain
Employee
Affiliation
PayrollClassification
PayrollSchedule
Affiliations
PayrollMethod
Transaction

Payroll Database
Implementation

UnionAffiliation
ServiceCharge
ChangeAffiliationTransaction
e derivadas

Payroll Database

PayrollDatabase

Figura 30-2
Uma hierarquia fechada quanto aos componentes para o aplicativo de folha de pagamentos.
Note que a maior parte do cdigo detalhado que ser escrito est em componentes
com poucos ou nenhum dependente. Como quase nada depende deles, os chamamos de
irresponsveis. O cdigo dentro desses componentes altamente flexvel; ele pode ser
alterado sem afetar muitas outras partes do projeto. Note tambm que os pacotes mais
gerais do sistema contm a mnima quantidade de cdigo. Muitas coisas dependem desses
componentes, mas eles no dependem de nada. Como muitos componentes dependem
deles, os chamamos de responsveis; e como no dependem de nada, os chamamos de
independentes. Assim, o volume de cdigo responsvel (isto , cdigo no qual alteraes
afetariam muitos outros cdigos) muito pequeno. Alm disso, essa pequena quantidade
de cdigo responsvel tambm independente; assim, nenhum outro mdulo o induzir a
mudar. Essa estrutura invertida, com generalidades altamente independentes e responsveis na parte inferior e detalhes altamente irresponsveis e dependentes na parte superior,
a caracterstica distintiva do projeto orientado a objetos.

ESTUDO DE CASO DA FOLHA DE PAGAMENTOS: ANLISE DO PACOTE

459

Compare a Figura 30-1 com a Figura 30-2. Note que os detalhes da parte inferior
da Figura 30-1 so independentes e altamente responsveis. Esse o lugar errado para
detalhes! Os detalhes devem depender das principais decises arquitetnicas do sistema e nada deve depender deles. Note tambm que as generalidades os componentes
que definem a arquitetura do sistema so irresponsveis e altamente dependentes.
Assim, os componentes que definem as decises arquitetnicas dependem (e, portanto,
so restringidas por eles) dos componentes que contm os detalhes da implementao.
Isso uma violao do SAP. Seria melhor se a arquitetura restringisse os detalhes!

Aplicando o Princpio da Equivalncia Reutilizao/Entrega (REP)


Quais partes do aplicativo de folha de pagamentos podemos reutilizar? Outro setor de
nossa empresa, querendo reutilizar nosso sistema de folha de pagamentos, mas tendo
um conjunto de diretivas diferente, no poderia reutilizar Classifications, Methods,
Schedules nem Affiliations, mas poderia reutilizar PayrollDomain, PayrollApplication, Application, PayrollDatabase e, possivelmente, PDImplementation.
Por outro lado, outro departamento que quisesse escrever software que analisasse o banco de dados de funcionrios atual poderia reutilizar PayrollDomain, Classifications,
Methods, Schedules, Affiliations, PayrollDatabase e PDImplementation. Em
cada caso, o grnulo da reutilizao um componente.
Raramente, uma nica classe de um componente seria reutilizada. O motivo simples. As classes dentro de um componente devem ser coesas. Ou seja, elas dependem
umas das outras e no podem ser fcil ou sensatamente separadas. No faria sentido,
por exemplo, utilizar a classe Employee sem usar a classe PaymentMethod. Na verdade,
para fazer isso, voc teria que modificar a classe Employee para que ela no contivesse
uma instncia de PaymentMethod. Certamente, no queremos dar suporte para o tipo de
reutilizao que nos obriga a modificar os componentes reutilizados. Portanto, o grnulo
da reutilizao o componente. Isso nos fornece outro critrio de coeso para utilizar ao
tentarmos agrupar classes em componentes: as classes no devem ser apenas fechadas
juntas, mas tambm reutilizadas juntas, de acordo com o REP.
Considere novamente nosso diagrama de componentes original da Figura 30-1. Os
componentes que poderamos querer reutilizar, como Transactions ou PayrollDatabase, no so facilmente reutilizveis, pois arrastam consigo muita bagagem extra. O
componente PayrollApplication depende de tudo. Se quisssemos criar um novo aplicativo de folha de pagamentos que usasse um conjunto diferente de diretivas de agenda,
mtodo, afiliao e classificao, no poderamos usar esse pacote como um todo. Em vez
disso, teramos de pegar classes individuais de PayrollApplication, Transactions,
Methods, Schedules, Classifications e Affiliations. Desmontando os componentes dessa maneira, destrumos sua estrutura de entregas. No podemos dizer que a verso
3.2 de PayrollApplication reutilizvel.
Como a Figura 30-1 viola o CRP, o usurio, tendo aceitado os fragmentos reutilizveis de nossos vrios componentes, no poder depender de nossa estrutura de entregas. Reutilizando a classe PaymentMethod, o usurio afetado por uma nova entrega de
Methods. Na maioria das vezes, as alteraes sero feitas nas classes que no esto sendo
reutilizadas; apesar disso, o usurio ainda dever controlar nosso novo nmero de verso
e provavelmente compilar o cdigo e test-lo novamente.
Isso ser to difcil de gerenciar que a estratgia mais provvel do usurio ser fazer uma cpia dos componentes reutilizveis e desenvolver essa cpia separadamente da

460

EMPACOTANDO O SISTEMA DE FOLHA DE PAGAMENTOS

nossa. Isso no reutilizao. Os dois cdigos se tornaro diferentes e exigiro suporte


independente, efetivamente duplicando o trabalho de suporte.
Esses problemas no so mostrados pela estrutura da Figura 30-2. Os componentes
dessa estrutura so mais fceis de reutilizar. PayrollDomain no arrasta muita bagagem
consigo. Ele reutilizvel, independentemente de qualquer uma das derivadas de PaymentMethod, PaymentClassification, PaymentSchedule etc.
O leitor perspicaz notar que o diagrama de componentes da Figura 30-2 no obedece ao CRP completamente. Especificamente, as classes dentro de PayrollDomain no
formam a menor unidade reutilizvel. A classe Transaction no precisa ser reutilizada
com o restante do componente. Poderamos projetar muitos aplicativos que acessam Employee e seus campos, mas nunca utilizar Transaction.
Isso sugere uma mudana no diagrama de componentes, como mostr