You are on page 1of 398
Série de Robert C. Martin = Codigo Limpo Habilidades Praticas do Agile Software Prefacio Um de nossos doces favoritos aqui na Dinamarca é 0 Ga-Jol, cujos fortes vapores de licorice so um complemento perfeito para nosso clima timido e, geralmente, frio. Parte do charme do Ga-lol, para nés dinamarqueses, sao 0s dizeres sébios impressos em cada caixa. Comprei dois pacotes dessa iguaria essa manha e nela veio este antigo ditado dinamarqués: ZErlighed i smé ting er ikke nogen lille ting. “Honestidade em pequenas coisas no é€ uma coisa pequena”. Era um bom pressagio para com o que eu ja desejava dizer aqui. Pequenas coisas sao importantes. Este é um livro sobre preocupagdes modestas cujos valores esto longe de ser pequenos. Deus esta nos detalhes, disse o arquiteto Ludwig Mics van der Rohe. Essa citagdo retoma, argumentos com contemporancos sobre 0 papel da arquitetura no desenvolvimento de software, especialmente no mundo Agile. Bob e eu, as vezes, acabavamos engajados entusiasmadamente debatendo sobre este assunto. E sim, Mies van der Rohe atentava para os utilitarios e as formas imemoriais de construgdo que fundamentam uma étima arquitetura. Por outro lado, ele também selecionava pessoalmente cada maganeta para cada casa que ele projetava, Por qué? Por que pequenas coisas so importantes. Em nosso “debate” sobre Desenvolvimento dirigido a testes (TDD, sigla em inglés), Bob ¢ eu descobrimos que concordamos que a arquitetura do software possuir um lugar importante no desenvolvimento, embora provavelmente tenhamos perspectivas diferentes do significado exato disso. Essas difereneas sao relativamente irrelevantes contudo, pois podemos admitir que profissionais responsdveis dedicam algum tempo para pensar e planejar o inicio de um projeto. As nogdes de desenvolvimento dirigido apenas por testes e por cédigos do final da década de 1990 jAndo existem mais. Mesmo assim, a atengdo aos detalhes ¢ um fundamento de profissionalismo ‘ainda mais critico do que qualquer visio maior. Primeiro, € por meio da pratica em pequenos trabalhos que profissionais adquirem proficiéncia e confianga para se aventurar nos maiores. Segundo, a menor parte de uma construgao desleixada, a porta que nao fecha dircito ou o azulejo levemente torto do cho, ou mesmo uma mesa desarrumada, retiram completamente o charme do todo. E sobre isso que se trata 0 cédigo limpo. Ainda assim, a arquitetura é apenas uma metdfora para o desenvolvimento de software, especialmente para a parte que entrega 0 produto inicial no mesmo sentido que um arquiteto entrega uma construgdo imaculada. Nessa época do Scrum e do Agile, 0 foco esta em colocar 0 produto rapidamente no mercado. Desejamos que a indistria funcione em velocidade maxima na producdo de software. Essas faibricas humanas: programadores que pensam e sentem que trabalham a partir das pendéncias de um produto ou do user story para criar o produto. A metéfoora da fabricago esta mais forte do que nunca no pensamento. Os aspectos da producao da manufatura japonesa de automéveis, de um mundo voltado para a linha de montagem, inspiraram grande parte do Scrum, Ainda assim, mesmo na indiistria automobilistica, a maior parte do trabalho nao esta na fabricagao, mas na manutengdo — ou na prevengdo. Em software, 80% ou mais do que fazemos & chamado de “manutengao”: 0 ato de reparar, Em vez de abracar o upico foco ocidental sobre a produg&o de bons softwares, deverfamos pensar mais como um pedreiro que conserta casas na industria de construg&o, ou um mecdnico de automdveis na area automotiva. O que 0 gerenciamento japonés tem a dizer sobre isso? Por volta de 1951, uma abordagem qualitativa chamada Manutengao Produtiva Total (TPM) surgiu no cenario japonés. Seu foco cra na manutencdo em vez da produ¢ao. Um dos maiores fundamentos da TPM € 0 conjunto dos chamados 5S principios. 5S ¢é uma série de disciplinas— uso aqui o termo “disciplina” para fins educativos. Os 5S principios, na verdade, sao os fundamentos do Lean — outro jargio no cenario ocidental, e cada vez mais conhecida no mundo dos softwares. Esses principios nao so uma opedio. Assim como Uncle Bob diz em suas preliminares, a pratica de um bom software requer tal disciplina: foco, presenga de espirito ¢ pensamento. Nem sempre & sobre fazer, sobre pressionar os equipamentos da fabrica para produzir em velocidade maxima. A filosofia dos 5S inclui os seguintes conceitos: Seiri, ou organizacao (pense em “ordenar”). Saber onde esto as coisas — usar abordagens como nomes adequados — é crucial. Acha que dar nome a identificadores nao é importante? Leia préximos capitulos. Seiton, ou arrumacao (pense em “sistematizar”). Ha um antigo ditado americano que diz: “Um lugar para tudo, ¢ tudo em seu lugar”. Um pedago de cédigo deve estar onde vocé espera encontré-lo — caso nao esteja, refatore e o coloque 1a. Seiso, ou limpeza (pensem em “polir”); manter 0 local de trabalho livre de fios pendurados, gordura, migalhas e lixo. O que os autores falam aqui sobre encher seu cédigo com comentarios ¢ linhas de cédigos como comentarios que informa o passado ou os desejos para o futuro? Livre~ se deles. Seiketsu, ou padronizacao: a equipe concorda em manter o local de trabalho limpo. Vocé acha que este livro fala algo sobre ter um estilo de programacaio consistente e uma série de priticas dentro da equipe? De onde vém tais padrées? Continue a leitura. Shutsuke, ou disciplina (autodisciplina). Isso significa ter disciplina para seguir as priticas refletir frequentememt isso no trabalho e estar disposto a mudar. Se aceitar o desafio — isso, 0 desafio — de ler e aplicar o que ¢ aconselhado neste livro, yocé entendera e apreciara o ultimo item. Aqui estamos finalmente indo em dire¢do as raizes do profissionalismo responsavel numa profissio que deve se preocupar com o ciclo de vida de um produto. Conforme facamos a manutengao de automéveis ¢ outras maquinas na TPM, a manutencao corretiva — esperar que bugs aparegam — é a excegao. Em vez disso, subimos um nivel: inspecionamos as méquinas todos os dias e consertamos as partes desgastadas antes de quebrarem, ou percorremos o equivalente aos famosos 16km antes da primeira troca de dleo para testar o desgaste. No cédigo, a refatoracdo é impiedosa. Vocé ainda pode melhorar um nivel a mais com 0 advento do movimento da TPM ha 50 anos; construa maquinas que sejam passiveis de manutengao. Tornar seu cédigo legivel ¢ tio importante quanto tornd-lo executavel. A ultima pratica, adicionada a TPM em tomo de 1960, ¢ focar na incluso de maquinas inteiramente novas ou substituir as antigas. Como nos adverte Fred Brooks, provavelmente devemos refazer partes principais do software a partir do zero a cada sete anos ou entao se livrar dos entulhos. Talvez devéssemos atualizar a constante de tempo de Fred para semanas, dias ou horas, em vez de anos. E ai onde ficam os detalhes. Ha um grande poder nos detalhes, mesmo assim existe algo simples ¢ profundo sobre essa abordagem para a vida, como talvez esperemos de qualquer abordagem que afirme ter origem japonesa. Mas essa nao € apenas uma visao ocidental de mundo sobre a vida; a sabedoria de ingleses ¢ americanos também est cheia dessas adverténcias. A citagdo de Seiton mais acima veio da ponta da caneta de um ministro cm Ohio que visualizou literalmente a organizag3o “como um remédio para todos os niveis de mal”. E Seiso? Deus ama a limpeza. Por mais bonita que uma casa seja, uma mesa desarrumada retira seu esplendor. E Shutsuke nessas pequenas questées? Quem ¢ fiel no pouco também é no muito. E que tal ficar ansioso para refatorar na hora certa, fortalecendo sua posi¢ao para as “grandes” decisdes subsequentes, em vez de descarta-las? Um homem prevenido vale por dois. Deus ajuda a quem cedo madruga. Nao deixe para amanha o que se pode fazer hoje. (Esse era o sentido original da frase “o ultimo momento de responsabilidade”, em Lean, até cair nas maos dos consultores de software). E que tal aplicar o local de esforgos pequenos e individuais num grande todo? De griio em grao a galinha enche o papo. Ou que tal integrar um trabalho de prevengdo no dia a dia? Antes prevenir do que remediar. O cédigo limpo honra as profundas raizes do conhecimento sob nossa cultura mais ampla, ou como ela fora um dia, ou deve ser, € poderd vir a ser com a atengao correta aos detalhes. Mesmo na grande literatura na area de arquitetura encontramos visdes que remetem a esses supostos detalhes. Pense nas maganetas de Mies van der Rohe. Aquilo é sciri. E ficar atento a cada nome de variavel. Deve-se escolher 0 nome de uma varidvel com cuidado, como se fosse eu primeiro filho, ‘Como todo proprietério de uma casa sabe, tal cuidado ¢ constante refinamento jamais acaba. arquiteto Christopher Alexander — pai dos padrées ¢ das linguagens de padrdes ~ enxerga cada o proprio design como um conserto pequeno ¢ local. E ele enxerga a habilidade de uma boa estrutura como 0 tinico objetivo do arquiteto; pode-se deixar as formas maiores para os padrées e seus aplicativos para os moradores. O design é constante no sé ao adicionarmos um novo cémodo a uma casa, mas ao nos atentarmos a repintura, a substituigdo de carpetes gastos ou a melhoria da pia da cozinha. A maioria das artes refiete relagdes andlogas. Em nossa busca por outras pessoas que dizem que a casa de Deus foi feita nos minimos detalhes, encontramo-nos em boa companhia do autor francés Gustav Flaubert, do século XIX. O poeta francés Paul Valery nos informa que um poema nunca fica pronto e requer trabalho continuo. e parar de uabalhar nele seria abandoni-lo. Tal preocupagao com os detalhes é comum a todos os encargos de exceléncia. Portanto, talvez haja pouca coisa nova aqui, mas ao ler este livro vocé ser desafiado a retomar a disciplina que vocé ha muito largou para a apatia ou um desejo pela espontaneidade e apenas “respondia as mudangas”. Infelizmente, nao costumamos enxergar essas questées como pecas fundamentais da arte de programar. Abandonamos nosso cédigo antecipadamente, nao porque ele ja esteja pronto, mas porque nosso sistema de valores se foca mais na aparéncia externa do que no contetido que entregamos. No final, essa falta de aten¢o nos custa: Dinheiro ruim sempre reaparece. Pesquisas, nem no mercado e nem nas universidades, so humildes o bastante para se rebaixar € manter 0 cédigo limpo. Na época em que trabalhei na empresa Bell Labs Software Production Research (producio, de fato!), ao ficarmos mexendo aqui e ali, nos deparamos com descobertas que sugeriam que 0 estilo consistente de endentacao era um dos indicadores mais significantes estatisticamente da baixa incidéncia de bugs. Queriamos que a qualidade fosse produzida por essa ou aquela estrutura ou linguagem de programaco ou outra nogao de alto nivel; conforme as pessoas cujo suposto profissionalismo se dé ao dominio de ferramentas e métodos de design grandioso, sentimo-nos ofendidos pelo valor que aquelas maquinas de fabricas, os codificadores, recebem devido a simples aplicagao consistente em um estilo de endentagao. Para citar meu proprio livro de 17 anos atras, tal estilo faz a distingao entre exceléncia e mera competéncia. A visio de mundo japonesa entende 0 valor crucial do trabalhador diario e, além do mais, dos sistemas de desenvolvimento voltados para as ages simples ¢ didrias desses trabalhadores. A qualidade é o resultado de um milhdo de atos altruistas de importar-se — nao apenas um grande método qualquer que desga dos céus. Nao ¢ porque esses atos sao simples que eles sejam simplistas, e muito menos que sejam faceis. Eles siio, ndo obstante, a fabrica de magnitude e, também, de beleza em qualquer esforgo humano. Ignori-los é no ser ainda completamente humano. E claro que ainda defendo 0 conceito de um escopo mais amplo, ¢, especialmente, 0 do -valor de abordagens arquiteténicas arraigadas profundamente no conhecimento do dominio ¢ de usabilidade do software. Este livro ndo é sobre isso — ou, pelo menos, nao de modo direto. Mas ele passa uma mensagem mais sutil cuja esséncia nao deve ser subestimada. Ele se encaixa 4 méxima atual das pessoas que realmente se preocupam com o cédigo, como Peter Sommerlad, Kevlin Henney ¢ Giovanni. “O codigo € 0 projeto” e “Cédigo simples” sao seus mantras. Enquanto devamos tentar nos lembrar de que a interface é 0 programa, ¢ que suas estruturas dizem bastante sobre a estrutura de nosso programa, é crucial adotar a humilde postura de que 0 projeto vive no cédigo. E enquanto o retrabalho na metifora da manufatura leva ao custo, o no projeto leva ao valor. Devemos ver nosso cédigo como a bela articulacao dos nobres esforgos do projeto — projeto como um proceso, ¢ nao uma meta estatica. F no cédigo que ocorrem as medidas estruturais de acoplamento ¢ coesao. Se vocé vir a descrigao de Larry Constantine sobre esses dois fatores, ele os conccitua em termos de codigo — e nao em conceitos de alto nivel como se pode encontrar em UML. Richard Gabriel nos informa em seu artigo Abstraction Descant que a abstrago é maligna. O cédigo é antimaligno, e talvez 0 cédigo limpo seja divino. Voltando a minha pequena embalagem de Ga-Jol, acho importante notar que a sabedoria dinamarquesa nos aconselha a no sO prestar atengo a pequenas coisas, mas também a ser honesto em pequenas coisas. Isso significa ser honesto com 0 cédigo ¢ tanto com nossos colegas ¢, acima de tudo, com nés mesmos sobre o estado de nosso cédigo. Fizemos o Melhor para “deixar © local mais limpo do que como o encontramos"”? Refatoramos nosso cédigo antes de verifica- lo? Essas nao sao preocupagées externas, mas preocupagOes que esto no centro dos valores do Agile. Que a refatoragdo seja parte do conceito de “Pronto”, é uma pritica recomendada no Scrum. Nem a arquitetura e nem o cédigo limpo exigem perfeigao, apenas honestidade ¢ que fagamos o melhor de nés. Errar é humano; perdoar € diyino. No Scrum, tornamos as coisas visiveis. Arejamos nossa roupa suja. Somos honestos sobre 0 estado de nosso cédigo porque © cédigo nunca € perfeito. Tornamo-nos mais completamente humanos, mais merecedores do divino e mais préximos da magnitude dos detalhes. Em nossa profissao, precisamos desesperadamente de toda ajuda que conseguirmos. Se 0 piso de uma loja reduz os acidentes ¢ suas ferramentas bem organizadas aumentam a produtividade, entdo sou totalmente a favor. F relagdo a este livro, ele é a melhor aplicag4o pragmatica dos principios de Lean ao software que ja vi. Eu ndo esperava nada mais deste pequeno grupo pratico de individuos que se esforcaram juntos por anos ndo s6 para se aperfeigoarem, mas também para presentear com seus conhecimentos 0 mercado com obras como esta em suas mAos agora. Isso deixa o mundo um pouco melhor do que quando o encontrei antes de Uncle Bob me enviar © manuscrito. Apds ter finalizado estes exercicios com conhecimentos to sublimes, agora vou limpar minha mesa. James O. Coplien Mordrup, Dinamarca Introducao ‘The OMLy VALI meAGugenenn— oF Code Quaciry: WTFs/ninure wrF Good code. Reproduzido com a gentil autorizago de Thom Holwerda. http://www.osnews.com/story/19266/WTFs_m (c) 2008 Focus Shift Que porta representa seu cédigo? Que porta representa sua equipe ou sua companhia? Por que estamos naquela sala? E apenas uma revisdo normal de cédigo ou encontramos uma série de problemas terriveis logo apés iniciarmos a apresentaga0? Estamos depurando em panico, lendo meticulosamente um cédigo que pens4vamos que funcionava? Os clientes estio indo embora aos bandos e estamos com os gerentes em nossos pescogos? Como podemos garantir que cheguemos atrés da porta certa quando 0 caminho fica dificil? A resposta ¢: habilidade profissional Ha duas vertentes para se obter habilidade profissional: conhecimento ¢ trabalho. Vocé deve adquirir o conhecimento dos principios, padrdes, priticas ¢ heuristicas que um profissional habilidoso sabe, ¢ também esmiugar esse conhecimento com seus dedos, olhos e corpo por meio do trabalho arduo e da pratica, Posso Ihe ensinar a mecanica para se andar de bicicleta. Na verdade, a matematica classica é relativamente direta. Gravidade, atrito, momento angular, centro de massa, e assim por diante, podem ser demonstrados com menos de uma pagina cheia de equagdes. Dada essas formulas, eu poderia provar para vocé que é pritico andar de bicicleta ¢ Ihe dar todo 0 conhecimento necessdrio para que yocé consiga. E mesmo assim vocé caira na primeira vez que tentar. Programar nao é diferente. Poderiamos por no papel todos os principios necessirios para um cédigo limpo e, entao, confiar que vocé faré as tarefas (isto é. deixar vocé cair quando subir na bicicleta), mas que tipo de professores ¢ de estudantes isso faria de nds? ‘Nao. Essa nao é a forma que este livro seguira, Aprender a criar cédigos limpos é uma tarefa ardua e requer mais do que o simples conhecimento dos prineipios ¢ padrées. Vocé deve suar a camisa; praticar sozinho e ver que cometeu eros; assistir a outros praticarem ¢ errar: ‘-los tropecar e refazer seus passos; Vé- los agonizar para tomar decisdes e 0 prego que pagarao por as terem tomado da mancira errada. Esteja preparado para trabalhar duro enquanto ler este livro. Esse nao é um livro facil ¢ simples que vocé pode ler num aviao ¢ terminar antes de aterrissar. Este livro Ihe faré trabalhar, e trabalhar duro. Que tipo de trabalho vocé faré? Vové lera cédigos aqui, muitos eddigos. E vocé devera descobrir 0 que esté correto e errado nos cédigos. Vocé terd de seguir 0 raciocinio conforme dividirmos médulos e os unirmos novamente. Isso levara tempo ¢ esforgo, mas achamos que valera a pena. Dividimos este livro em trés partes. Na primeira ha diversos capitulos que descrevem os principios, padrdes e praticas para criar um cédigo limpo. H4 um tanto de cédigos nessa parte, ¢ sera desafiador lé-los. Eles Ihe prepararao para a seco seguinte. Se vocé deixar o livro de lado apés essa primeira parte. Bem, boa sorte! Na segunda parte entra o trabalho pesado que consiste em diversos estudos de caso de complexidade cada vez maior, Cada um € um exercicio para limpar um cédigo — resolver alguns problemas dele. O detalhamento nesta segdo ¢ intenso, Voeé terd de ir e voltar por entre as folhas de textos ¢ cédigos, e analisar e entender 0 cédigo com o qual estamos trabalhando e captar nosso raciocinio para cada alteragdo que fizermos. Reserve um tempo para essa parte, pois deverd levar dias. A terceira parte é a compensagao. E um tnico capitulo com uma lista de heuristicas e “odores” reunidos durante a criag3o dos estudos de caso. Conforme progredirmos ¢ limparmos os cédigos nos estudos de caso, documentaremos cada motivo para nossas agdes como uma heuristica ou um “odor”. Tentaremos entender nossas préprias reagdes em relagdio ao cédigo quando estivermos lendo ou alterando-o, ¢ trabalharemos duro para captar por que nos sentimos de tal forma e por que fizemos isso ou aquilo. O resultado seré um conhecimento base que descreve a forma como pensamos quando criamos, lemos ¢ limpamos um cédigo. Este conhecimento base possui um valor limitado se vocé nao ler com atengao ao longo dos casos de estudo na segunda parte deste livro. Neles, anotamos cuidadosamente cada alteracao que fizemos com referéncias posteriores as heuristicas. Tais referéncias aparecem em colchetes, assim: [H22]. Isso lhe permite ver 0 contexto no qual sao aplicadas ¢ escritas aquelas heuristicas! Essas, em si, no so tao valiosas, mas, sim, a relacdo entre elas e as diferentes decisdes que tomamos ao limpar 0 cédigo nos casos de estudo. A fim de lhe ajudar ainda mais com essas relagdes, colocamos no final do livro uma referéncia cruzada que mostra o nimero da pagina para cada referéncia. Voc pode usé-la com um guia rapido de consulta para saber onde uma determinada heuristica foi aplicada. Mesmo se vocé ler a primeira ¢ a terccira segdes e pular para os casos de estudo, ainda assim tera lido um livro que satisfaz sobre a criacdo de um bom software. Mas se nao tiver pressa ¢ explorar os casos de estudo, seguindo cada pequeno passo e cada minuto de decisio, se colocando em nosso lugar e se forcando a seguir a mesma linha de raciocinio que usamos, entéo vocé adquiriu um entendimento muito mais rico de todos aqueles principios, padrées, praticas @ heuristicas, Todo esse conhecimento ficara em cada fibra de seu corpo. Ele se tornara parte de voce da mesma forma que ao aprender a andar de bicicleta, ela se torna uma parte de sua vontade de pedali-la. Agradecimentos Tlustragées Meus agradecimentos a minhas duas artistas, Jeniffer Kohnke ¢ Angela Brooks, Jennifer ¢ a responsdvel pelas maravilhosas e criativas figuras no inicio de cada capitulo ¢ também pelos retratos de Kent Beck, Ward Cunningham, Bjame Stroustrup, Ron Jeffries, Grady Booch, Dave Thomas, Michael Feathers e de mim mesmo. ‘Angela é a responsavel pelas figuras engenhosas que enfeitam os capitulos.Ao longo dos anos, ela criou bastantes imagens para mim, incluindo muitas das que esto no livro Agile Software Develpment: Principles, Patterns, and Practices. Angela também é minha primogénita e de quem me sinto muito orgulhoso. Sobre a capa ‘A imagem da capa se é a M104 — a Galéxia Sombrero -, que fica na constelagao de Virgem e esta a pouco menos de 30 milhdes de anos-luz de nés. Em seu centro ha um buraco negro supermassivo cujo peso equivale um bilhio de vezes a massa do sol A imagem lhe faz lembrar da exploso da lua Praxis, de Klingon? Eu me recordo vividamente acena de Jornada nas Estrelas VI que mostrava um anel equatorial de destro¢os voando devido a explosio. Desde essa cena, 0 anel equatorial se tomiou um componente comum as explosdes em filmes de fiego cientifica. Ele até foi adicionado a explosao de Alderaan nas (iltimas versdes do filme Jornada nas Estrelas. O que causou a formagao desse anel em torno de M104? Por que cle possui um bojo to amplo ¢ um niicleo to minusculo e brilhoso? Parece-me como se o buraco negro central perdgu sua graga ¢ langou um buraco de 30.000 anos-luz no meio da galaxia, devastando quaisquer civilizacdes que estivessem no caminho daquele distirbio césmico. Buracos negros supermassivos engolem estrelas inteiras no_almogo, convertendo_uma consideravel fragao de sua massa cm energia. MC2 ja é bastante poténcia, mas quando M é uma massa estelar: Cuidado! Quantas estrelas cairam impetuosamente naquelas presas antes de o monstro ficar saciado? O tamanho do vao central poderia ser uma dic: ‘A imagem de M104 da capa € uma combinagao da famosa fotografia de luz visivel do Hubble (foto superior) com a recente imagem infravermelha do observatério espacial Spitzer (foto inferior), E essa segunda imagem que nos mostra claramente a natureza do anel da galaxia. Na luz visivel, vemos apenas a extremidade frontal do anel como uma silhueta. O bojo central ofusca o resto do ancl. Mas no infravermelho, as particulas “quentes” — isto 6, altamente radioativas ~ no anel brilham através do bojo central, A combinag&o de ambas as imagens nos mostra uma visdo que ndo haviamos visto antes e indica que, ha muito tempo atrés, 14 havia um inferno enfurecido de atividades. over image: © Spitzer Space Telescope Cédigo Limpo Ha duas razdes pelas quais voce esta Iendo este livro: voeé € programador e deseja se tornar um ainda melhor. Otimo. Precisamos de programadores melhores. : Capitulo 1: Cédigo Limpo Este livro fala sobre programagao e esta repleto de cédigos que examinaremos a partir de diferentes perspectivas: de baixo para cima, de cima para baixo de dentro para fora. Ao terminarmos, teremos um amplo conhecimento sobre cédigos ¢ seremos capazes de distinguir entre um cédigo bom e um cédigo ruim. Saberemos como escrever um bom cédigo e como tomar um ruim em um bom. O Cédigo Podem dizer que um livro sobre cédigos ¢, de certa forma, algo ultrapassado, que a programacdo deixou de ser uma preocupagao € que devemos nos preocupar com modelos € requisitos. Outros até mesmo alegam que o fim do cédigo, ou seja, da programagao, esta prdximo; que logo todo cédigo serd gerado, ¢ nao mais escrito. E que nao precisarao mais de programadores, pois as pessoas criario programas a partir de especificagdes. Bobagens! Nunca nos livraremos dos eédigos, pois eles representam os detalhes dos requisitos. Em certo nivel, nfo ha como ignorar ou abstrair esses detalhes; eles precisam ser especificados. F especificar requisitos detalhadamente de modo que uma maquina possa executi-los é programar ~ ¢ tal especificagdo 6 0 cédigo. Espero que o nivel de de nossas linguagens continue a aumentar ¢ que o mimero de linguagens especificas a um dominio continue crescendo. Isso sera bom, mas nio acabara com a programaciio. De fato, todas as especificagdes escritas nessas linguagens de niveis mais altos € especificas a um dominio serdo cédigos! Eles precisar‘io ser minuciosos, exatos e bastante formais ¢ detalhados para que uma maquina possa entendé-los ¢ executa-los. As pessoas que pensam que o c6digo um dia desaparecera sto como mateméticos que esperam algum dia descobrir uma matematica que nao precise ser formal. Elas esperam que um dia descubramos uma forma de criar méquinas que possam fazer o que desejamos em vez do que mandamos. Tais maquinas terdo de ser capazes de nos entender tio bem de modo que possam traduzir exigéncias vagamente especificadas em programas executaveis perfeitos para satisfazer nossas necessidades. Isso jamais acontecera. Nem mesmo os seres humanos, com toda sua intui¢Ao ¢ criatividade, 1@m sido capazes de criar sistemas bem-sucedidos a partir das caréncias confusas de seus clientes. Na verdade, se a matéria sobre especificagao de requisitos nZo nos ensinou nada, € porque os requisitos bem especificados so tao formais quanto os cédigos e podem agir como testes executaveis de tais cédigos! Lembre-se de que 0 cédigo é a linguagem na qual expressamos nossos requisitos. Podemos criar linguagens que sejam mais préximas a cles. Podemos criar ferramentas que nos ajudem a analisar a sintaxe e unir tais requisitos em estruturas formais. Mas jamais eliminaremos a preciso necessaria — portanto, sempre havera um cédigo. Cédigo Ruim 2 Codigo ruim Recentemente li o preficio do livro Implementation Patterns! de Kent Beck, no qual ele diz “... este livro bascia-se numa premissa fragil de que um bom cédigo importa...”. Uma premissa frdgi?? Nao concordo! Acho que essa premissa é uma das mais robustas, apoiadas € plenas do que todas as outras em nossa area (e sei que Kent sabe disso). Estamos cientes de que um bom cédigo importa, pois tivemos de lidar com a falta dele por muito tempo. Lembro que no final da década de 1980 uma empresa criou um aplicativo extraordinario que se tornou muito. popular ¢ muitos profissionais 0 compraram c usaram. Mas, entdo, o intervalo entre os langamentos das novas~ distribuigdes comegou a aumentar, Os bugs ndo eram consertados na distribuigéo seguinte. E o tempo de carregamento do aplicativo ¢ 0 niimero de travamentos aumentaram, Lembro-me do dia em que, frustrado, fechei 0 programa e nunca mais o usei. A empresa saiu do mercado logo depois Duas décadas depois encontrei um dos funcionarios de tal empresa na época e 0 perguntei o que havia acontecido, e 0 que eu temia fora confirmado. Eles tiveram de apressar o langamento do produto e, devido a isso, 0 cddigo ficou uma zona. Entao, conforme foram adicionando mais e mais recursos, o cédigo piorava cada vez mais até que simplesmente nao era mais possivel gerencid-lo. Foi 0 cédigo ruim que acabou com a empresa. ‘Alguma vez um cédigo ruim jé lhe atrasou consideravelmente? Se vocé for um programador, independemte de sua experiencia, ento ja se deparou varias vezes com esse obsticulo. Alias, é como se caminhdssemos penosamente por um lamagal de arbustos emaranhados com armadilhas ocultas.Isso ¢ 0 que fazemos num cédigo ruim. Pelejamos para encontrar nosso caminho. esperando avistar alguma dica, alguma indicagao do que esti acontecendo; mas tudo 0 que vemos ¢ um cédigo cada vez mais sem sentido. E claro que um cédigo ruim ja Ihe atrasou. Mas, entiio, por que vocé o escreveu dessa forma” Estava tentando ser rapido? Estava com pressa? Provavelmente. Talyez vocé pensou que no tivesse tempo para fazer um bom trabalho; que seu chefe ficaria com raiva se vocé demorasse um pouco mais para limpar seu c6digo. Talvez vocé estava apenas cansado de trabalhar neste programa e queria termind-lo logo. Ou verificou a lista de coisas que havia prometido fazer € percebeu que precisava finalizar este médulo de uma vez, de modo que pudesse passar para 0 proximo. Todos ja fizemos isso, j4 vimos a bagunca que fizemos e, entao, optamos por arruma- las outro dia. Todos j4 nos sentimos aliviados ao vermos nosso programa confuso funcionar decidimos que uma bagunga que funciona é melhor do que nada. Todos nés ja dissemos que revisariamos e limpariamos 0 cédigo depois. E claro que naquela época nao conheciamos a lei de LeBlanc: Nunca é tarde 1 [Beck07} 4 Capitulo 1: Cédigo Limpo O Custo de Ter um Codigo Confuso Se vocé é programador a mais de dois ou trés anos, provavelmente 0 cédigo confuso de outra pessoa ja fez com que vocé trabalhasse mais lentamente e provavelmente seu proprio cédigo jé Ihe trouxe problemas. © nivel de retardo pode ser significativo. Ao longo de um ou dois anos, as equipes que trabalharam rapidamente no inicio de um projeto podem perceber mais tarde que esto indo a passos de tartaruga. Cada alterac4o feita no cédigo causa uma falha em outras duas ou trés partes do mesmo eédigo, Mudanca alguma é trivial. Cada adigo ou modificagdo ao sistema exige que restauragOes, amarragdes e remendos sejam “entendidas” de modo que outras possam ser incluidas. Com o tempo, a bagunga se torna to grande ¢ profunda que nao da para arrumé-la. ‘Nao hé absolutamente solug&o alguma. Conforme a confusio aumenta, a produtividade da equipe diminui, assintoticamente aproximando-se de zero. Com a redugo da produtividade, a geréncia faz a tinica coisa que cla pode; adiciona mais membros ao projeto na esperanga de aumentar a produtividade. Mas esses novos membros nao conhecem o projeto do sistema, nao sabem a diferenca entre uma mudanga que altera o propésito do projeto e aquela que o atrapalha. Ademais, eles, e todo o resto da equipe, esto sobre tremenda presstio para aumentar a produtividade. Com isso todos criam mais € mais confusdes, levando a produtividade mais perto ainda de zero (veja a Figura 1.1). o8 8888 Time Figura 1.1 Produtividade v, tempo 0 Custo de Ter um Cédigo Confuso 5 O Grande Replanejamento No final, a equipe se rebela. Todos informam 4 geréncia que nao conseguem mais trabalhar neste irritante cédigo-fonte e exigem um replanejamento do projeto. Apesar de a geréncia ndo querer gastar recursos em uma nova remodelago, ela nfo pode negar que a produtividade esta péssima. No final das contas, ela acaba cedendo as exigéncias dos desenvolvedores e autoriza o grande replanejamento desejado. E, entio, formada uma nova equipe especializada. Por ser um projeto inteiramente novo, todos querem fazer parte dessa equipe. Eles desejam comegar do zero e criar algo belo de verdade. Mas apenas os melhores ¢ mais brilhantes sio selecionados e os outros deverao continuar na manutengao do sistema atual. Agora ambos os times esto numa corrida. A nova equipe precisa construir um novo sistema que faga 0 mesmo que o antigo, além de ter de se manter atualizada em relagao as mudangas feitas constantemente no sistema antigo. Este, a geréncia nado substituira até que o novo possa fazer tudo também. Essa corrida pode durar um bom tempo. J4 vi umas levarem 10 anos. E, quando ela termina, os membros originais da nova equipe ja foram embora ha muito tempo, ¢ os atuais exigem 0 replanejamento de um novo sistema, pois esta tudo uma zona novamente. Se vocé ja vivenciou pelo menos um pouco dessa situag’o, entio sabe que dedicar tempo para limpar seu cdigo nao é apenas eficaz em termos de custo, mas uma questiio de sobrevivéncia profissional. Atitude Vocé ja teve de trabalhar penosamente por uma confusdo to grave que levou semanas © que deveria ter levado horas? Vocé ja presenciou 0 que deveria ser uma alteraco tinica e direta, mas que em vez disso foi feita em diversos médulos distintos? Todos esses sintomas sao bastante comuns. Por que isso ocorre em um cédigo? Por que um cédigo bom se decompée tio rapido em um ruim? Temos diversas explicagdes para isso. Reclamamos que os requisitos mudaram de tal forma que estragaram 0 projeto original. Criticamos os prazos por serem curtos demais para fazermos as coisas certas. Resmungamos sobre gerentes tolos ¢ clientes intolerantes ¢ tipos de marketing intiteis ¢ técnicos de telefone, Mas 0 padrio, querido Dilbert, nao esté em nossas estrelas, mas sim em n6és mesmos. Somos profissionais. Isso pode ser algo dificil de engolir. Mas como poderia essa zona ser nossa culpa? E os requisitos? E o prazo? E os tolos gerentes ¢ tipos de marketing intiteis? Eles nfo carregam alguma parcela da culpa? Nao. Os gerentes ¢ marketciros buscam em nés as informagdes que precisam para fazer promessas ¢ firmarem compromissos; € mesmo quando nao nos procuram, nao devemas dar uma de timidos ao dizer-Ihes nossa opinio. Os usuarios esperam que validemos as maneiras pelas quais os requisitos se encaixarao no sistema. Os gerentes esperam que os ajudemos a cumprir 0 prazo. Nossa cumplicidade no planejamento do projeto ¢ tamanha que compartilhamos de uma grande parcela da responsabilidade em caso de falhas: especialmente se estas forem em relagao a um codigo ruim. 6 Capitulo 1: Cédigo Limpo “Mas, espere!™, vocé diz. “E se eu ndo fizer 0 que meu gerente quer, serei demitido”. E provavel que nao. A maioria dos gerentes quer a verdade, mesmo que demonstrem 0 contrario. A maioria deles quer um cédigo bom, mesmo estourando 0 prazo. Eles podem proteger com paixao 0 prazo ¢ 03 requisitos, mas essa é a fungdio deles. A sua é proteger 0 cédigo com essa mesma paixio. Para finalizar essa questo, ¢ se vocé fosse médico ¢ um paciente exigisse que vocé parasse com toda aquela lavagao das maos na preparacao para a cirurgia s6 porque isso leva muito tempo?” E obvio que o chefe neste caso € 0 paciente; mas, mesmo assim, o médico devera totalmente se recusar obedecé-lo. Por qué? Porque 0 médico sabe mais do que o paciente sobre os riscos de doengas e infecgdes. Nao scria profissional (sem mencionar criminoso) que o médico obedecesse ao paciente neste cenario. Da mesma forma que nao é profissional que programadores cedam 4 vontade dos gerentes que nao entendem os riscos de se gerar cédigos confusos. O Principal Dilema Os programadores se deparam com um dilema de valores basicos. Todos os desenvolvedores com alguns anos a mais de experiéncia sabem que bagungas antigas reduzem 0 rendimento. Mesmo assim todos eles se sentem pressionados a cometer essas bagungas para cumprir os prazos. Resumindo, eles nao se esforgam para Os profissionais sérios sabem que a segunda parte do dilema esta errada, Vocé no cumpriré 0 prazo se fizer bagunga no cédigo. De fato, tal desorganizagao reduzira instantaneamente sua velocidade de trabalho, e vocé perder o prazo. A uinica maneira de isso no acontecer — a tinica maneira de ir mais répido— & sempre mamter 0 cédigo limpo. A Arte do Cédigo Limpo? Digamos que vocé acredite que um cédigo confuso seja um obstéculo relevante. Digamos que voce aceite que a tinica forma de trabalhar mais répido é manter seu cédigo limpo. Entio, voc deve se perguntar: “Como escrever um cédigo limpo?” Nao vale de nada tentar escrever um cédigo limpo se vocé nfo souber o que isso significa. ‘As més noticias so que escrever um cédigo limpo é como pintar um quadro. A maioria de nds sabe quando a figura foi bem ou mal pintada. Mas ser capaz de distinguir uma boa arte de uma Tui nio significa que vocé saiba pintar. Assim como saber distinguir um eddigo limpo de um ruim nao quer dizer que saibamos escrever um cédigo limpo. Escrever um cédigo limpo exige 0 uso disciplinado de uma miriade de pequenas técnicas aplicadas por meio de uma sensibilidade meticulosamente adquirida sobre “limpeza”. A “sensibilidade ao cédigo” é 0 segredo. Alguns de nés jé nascemos com cla. Outros precisam se esforgar para adquiri-la. Ela nfo sé nos permite perceber se 0 cddigo é bom ou ruim, como também nos mostra a estratégia ¢ disciplina de como transformar um cédigo ruim em um limpo. Um programador sem “sensibilidade ao cédigo” pode visualizar um médulo confuse reconhecer a bagunga, mas nao sabera o que fazer a respeito dela. J4 um com essa sensibilidade olharé um médulo confuso e verd alternativas. A “sensibilidade ao cédigo” ajudara a esse ‘a Rew 1807. memes Serer Bemrmmedbwose we:neien vole peinolen ‘enc airbag llc, sel Go vejebtadh, Chaicanidnvie is ich de apie wa nidices imma weupades: O Custo de Ter um Codigo Confuso 7 programador a escolher a melhor alternativa e 0 orientara na criago de uma sequéncia de comportamentos para proteger as alteragdes 8 aqui e ali, Em suma, um programador que escreve um cddigo limpo € um artista que pode pegar uma tela em branco e submeté-la a uma séric de transformagées até que se torne um sistema graciosamente programado, O que é um Cédigo Limpo? Provavelmente existem tantas definigdes como existem programadores. Portanto, perguntei a alguns programadores bem conhecidos ¢ com muita cxperiéncia o que achavam. Bjarne Stroustrup, criador do C++ e autor do livro A linguagem de programagao C++ Gosto do meri cédigo elegante e eficiente A légica deve ser direta para difieultar o encobrimento de bugs. cas dependéncias minimas pars facilitar a manuten¢ao. © tratamento de erro completo de acordo com uma estratégia clara € 0 desempenho préximo do mais efielente de modo a nao incitar as pessoas a tornarem 0 cddigo confuso com otimizacdes sorratetras. O cédigo impo faz bem apenas uma coisa. Bjarne usa a palavra “elegante” — uma palavra e tanto! O diciondrio possui as seguintes definigoes: que se caracteriza pela naturalidade de harmonia, leveza, simplicidade; naturalidade no modo se dispor; requintado, fino, estiloso Observe a énfase dada & palavra “naturalidade”. Aparentemente, Bjarne acha que um cédigo limpo proporciona uma leitura natural; ¢ lé-lo deve ser belo como ouvir uma misica num ridio ou visualizar um carro de design magnifico. Bjame também menciona duas vezes “eficiéncia”. Talvez. isso nao devesse nos surpreender vindo do criador do C++, mas acho que ele quis dizer mais do que um simples desejo por agilidade. A repetigao de ciclos nao ¢ elegante, nao € belo. E repare que Bjarne usa a palavra “‘ineitar” para descrever a consequéncia de tal deseleganeia, A verdade aqui é que um cédigo ruim incita o crescimento do caos num cédigo. Quando outras pessoas alteram um cédigo ruim, elas tendem a pioré-lo. Pragmaticos. Dave Thomas e Andy Hunt expressam isso de outra forma, Eles usam a metafora das janelas quebradas.’ Uma construgdo com janelas quebradas parece que ninguém cuida dela. Dessa forma, outras pessoas deixam de se preocupar com ela também, Elas permitem que as outras janelas se quebrem também. No final das contas, as prOprias pessoas as quebram. Elas estragam a fachada com pichagdes ¢ deixam acumular lixo. Uma Gnica janela inicia 0 processo de degradagio, ‘9 Shite haa ath XE ah 8 Capitulo 1: Cédigo Limpo Bjarne também menciona que o tratamento de erro deva ser completo. Isso significa prestar atengilo nos detalhes. Um tratamento de erro reduzido apenas uma das maneiras pela qual 0s programadores deixam de notar os detalhes. Perdas de meméria e condigdes de corrida so outras. Nomenclaturas inconsistentes so ainda outras. A conclusio é que um cédigo limpo requer bastante atengio aos detalhes. Bjarne conclui com a asseveragdo de que um eédigo limpo faz bem apenas uma coisa. Nao é por acaso que ha inumeros principios de desenvolvimento de software que podem ser resumidos a essa simples afirmagio. Varios escritores jé tentaram passar essa ideia. Um eédigo ruim tenta fazer coisas demais, cle esti cheio de propdsitos obscuros ¢ ambiguos. O cédigo limpo ¢ centralizado. Cada fungao, cada classe, cada médulo expée uma tinica tarefa que nunca sofre interferéncia de outros detalhes ou fica rodeada por eles. Grady Booch, autor do livro Object Oriented Analysis and Design with Applications Um cédigo limpo é simples e direto Ele ¢ tao bem legivel quanto uma prosa bem eserita. Ele jamais tora confuso 0 objetivo do desenvolvedor. em vez disso, ele esta repleto de abstracoes claras ¢ linhas de controle objetivas. Grady fala de alguns dos mesmos pontos que Bjarne, yoltando-se mais para questio da legibilidade. Eu, particularmente, gosto desse ponto de vista de que ler um cédigo limpo deve ser como ler uma prosa bem escrita, Pense num livro muito bom que vocé jé leu. Lembre-se de como as palavras eram substituidas por imagens! Fra como assistir a um filme, nfo era? Melhor ainda, voeé via os personagens, ouvia os sons, envolvia-se nas emogdes e no humor. Ler um cédigo limpo jamais sera como ler O senhor dos anéis, Mesmo assim, a analogia com a literatura nao é ruim, Como um bom romance, um cédigo limpo deve expor claramente as questdes do problema a ser solucionado. Ele deve desenvolvé-las até um climax e, entao, dar 20 leitor aquele “Aha! Mas € claro!”, como as questdes ¢ 0s suspenses que so solucionados na revelagao de uma solugio ébvia. ‘Acho que o uso que Grady faz da frase “abstragdes claras” é um paradoxo fascinante! ‘Apesar de tudo, a palavra “clara” é praticamente um sinénimo para “explicito”. Meu diciondrio MacBook tem a seguinte definigao para “claro(a)": direto, decisivo, sem devaneios ou detalhes desnecessdrios. Apesar desta justaposi¢ao de significados, as palavras carregam uma mensagem poderosa. Nosso cédigo deve ser decisivo, sem especulagdes. Ele deve conter apenas 0 necessario Nossos leitores devem assumir que fomos decisivos. © Custo de Ter um Cédigo Confuso 9 O “grande” Dave Thomas, fundador da OTI, o pai da estratégia Eclipse ‘Além de seu criador, um desenvotvedor pode ler ¢ methorar um cédigo limpo. Ele tem testes de unidade e de aceitacao, nomes significativos; ele oferece apenas uma maneira, e nto varias, de se facer uma tarefa; possi poucas dependéncias, ‘as quais so explicitamente declaradas ¢ oferecem um API minimo e claro. O cédigo deve ser imteligivel jé que dependendo da linguagem, nem toda informacdo necessaria pode expressa na cédigo em si. Dave compartilha do mesmo desejo de Grady pela legibilidade, mas com uma diferenga relevante. Dave afirma que um cédigo limpo facilita para que outras pessoas o melhorem, Pode parecer dbvio, mas nao se deve enfatizar muito isso, Ha, afinal de contas, uma diferenga entre um cédigo facil de ler ¢ um facil de alterar. Dave associa limpeza a testes! Dez anos atras, isso levantaria um ar de desconfianga. Mas 0 estudo do Desenvolvimento Dirigido a Testes teve grande impacto em nossa indiistria e se tornou uma de nossos campos de estudo mais essenciais. Dave esta certo. Um codigo, sem testes, nao esta limpo. Nao importa o quo elegante, legivel ou acessivel esteja que, se ele nao possuir testes, ele nao é limpo. Dave usa a palavra minima duas vezes. Aparentemente cle da preferéncia a um cédigo pequeno. De fato, esse tem sido uma citagao comum na literatura computacional. Quando menor. melhor. Dave também diz que 0 cédigo deve ser inteligivel — referéncia esta & programagdo imeligivel (do livro Literate Programming) de Donald Knuth. A conclusao é que 0 codigo deve * ser escrito de uma forma que seja inteligivel aos seres humanos. Michael Feathers, autor de Working Effectively with Legacy Code Eu poderia listar todas as qualidades que vejo em um eédigo impo, mas hd: wma predominaee que leva a todas as outras, Um cédigo limpo sempre parece que foi excrito por alguém que se importava. Nao hi nada de dbvio no que se pode fazer para tornc-to melhor. Tudo foi pensado pete autor do cédigo, se tentar pensar em algumas melhoras, vocé voltardé ao inicio, ou seja, apreciando o cédigo deixado para voes por alguém que se importa bastante com essa tarefa. : poe One word: care. E esse o assunto deste livro. Talvez, i um subtitulo apropriado seria Como se importar HL {+ com 0 cédigo. es que foi cuidado por alguém. Alguém que calmamente 4. [Kauth®2} 10 Capitulo 1: Cédigo Limpo © manteve simples e organi que se importou. .do; alguém que prestou a atengao necesséria aos detalhes; alguém Ron Jeffries, autor de Extreme Programming Installed and Extreme Programming Adventures in C# Ron iniciou sua carreira de programador em Fortran, no Strategic Air Command, e criou cédigos em quase todas as linguagens ¢ maquinas. Vale a pena considerar suas palavras: estes anos recentes, comecei, e quase finalizei, com as regras de Beck sobre cédigo simples. Em ordem de priovidade, sao. + Efete todos os testes + Sem duplicagao de cédigo: + Expressa todas as ideias do projeto que estao no sistema + Minimiza © miimero de ensidades, como classes, métodos. funges e outras do tipo. Dessas quatro, foco-me mais na de duplicagdo. Quando a mesma coisa ¢ feita repetidas vezes, é sinal de que uma idvia em sua cabega nao extd bem representada no codigo. Tento descobrir 0 que ¢ e, entio. expressar ‘aquela ideia com mais clareza Expressividade para mim sao nomes significativos e costume mudar o nome das coisas varias vezes antes de finalizar. Com ferramentas de programacdo modernas, como a Eclipse, renomear & bastante facil, e por isso ‘ndo me incomodo om fazer isso. Entretanto, a expressividade vai além de names. Também verifico se um método ‘ou objeto faz mais de uma tarefa. Se for wm objeto, provavelmente ele precisard ser dividlido em dois ou mais. Se for um méiodo, sempre uso a refatoracda do Métode de Extracao nele, resuliando em wm método que expressa mais claramente sua fungao e em outros métodos que dizem como ela & feita Duplicagdo e expressividade me levam ao que considero wn cédigo limpo, @ methorar um cddigo ruim com ‘apenas esses dois conceitos na mente pode fazer uma grande diferenca. Ha, porém, uma outra corsa da qual estou ciente quando programo, que é um pauco mais dificil de explicar. “Apis anos de rabalho, parece-me que todos os programadores pensar tudo igual. Um exemplo & “encontrar ‘coisas muma coleeao". Tenhamos uma base de dados cam registros de junciondrios ou uma tabela hash de chaves ¢ valores ou um vetor de itens de algum tipo, geralmente procuramos um item especifico naguela cole¢do. Quando percebo isso, costume implementar exsa fungao em um metodo ou classe mals abstrato — 0 (que me proporciona algumas vantagens interessante. Passo implementar a funcionalidade agora com algo mais simples, digamos uma tabela hash, mas como agora todas as referéncias aquela busca estdo na minha classe ou método abstrato, posso alterar a implementacdo sempre que desejar. Posso ainda prosseguir rapidamente enquanto preservo a copacidade de alteragio futur. ‘Além disso, a de colecdes geralmente chama minha atengdo para o que realmente esti acontecendo, e impede (que cu implemenue funcionalidades arbitrarias em colecdes quando tudo que eu preciso so simples maneiras de encontrar o que desejo. Reducdo de duplicacdo de codigo, alta expressividade e criacdo no inicio de absiragoes simples. E isso que torna para mim um cadigo limpo. Aqui, em alguns breves pardgrafos, Ron resumiu o conteiido deste livro. Sem duplica¢ao, uma tarefa, expressividade, pequenas abstragdes. Tudo foi mencionado. O Custo de Ter um Cédigo Confuso H Ward Cunningham, criador do conceito de “WikiWiki”, criador do Fit, co-criador da Programagao Extrema (eXtreme Programming). Incentivador dos Padrées de Projeto. Lider da Smalltalk e da OO. Pai de todos aqueles que se importam com 0 cédigo. Vocé sabe que esté criando um eédigo timpo quando cada rotina que voc® leia se mostra como o que vocé esperava. Vocé pode chamar de cédigo belo quando ele também faz parecer que ay linguagem foi feita para 0 problema. 7 Declaragdes como essa sito caracteristicas de Ward. Vocé a le, coloca na sua cabega e segue para a proxima. Parece to racional, to ébvio que raramente é memorizada como algo profundo. Voeé acha que basicamente ja pensava assim. Mas observemos mais atentamente. “....0 que vocé esperava”. Qual foi a altima vez que vocé viu um médulo como voce 0 esperava que fosse? Nao é mais comum eles serem complexos, complicados, emaranhados? Interpreté-lo erroneamente nao é a regra? Vocé nao esté acostumado a se descontrolar ao tentar entender 0 raciocinio que gerou todo 0 sistema ¢ associé-lo ao médulo que estas lendo? Quando foi a dltima vez. que vocé leu um cédigo para o qual vocé assentiu com a cabega da mesma forma que fez com a declaragao de Ward? Este espera que ao ler um eédigo limpo nada Ihe surpreenda. De fato, ndo seré nem preciso muito esforgo. Vocé ird Ié-lo c sera basicamente © que vocé jd esperava. O cédigo ¢ dbvio,* simples ¢ convincente. Cada médulo prepara o terreno para o seguinte. Cada um Ihe diz como 0 préximo estara escrito. Os programas que sao to limpos ¢ claros assim foram tao bem escritos que voce nem percebera. O programador o faz parecer super simples, como o é todo projeto extraordindrio. E.anogao de beleza do Ward? Todos ja reclamamos do fato de nossas linguagens nao tiverem sido desenvolvidas para os nossos problemas. Mas a declaracao de Ward coloca novamente © peso sobre nds. Ele diz que um cédigo belo faz parecer que a linguagem foi feita para o problema! Portanto, & nossa responsabilidade fazer a linguagem parecer simples! Ha brigas por causa das linguagens em todo lugar, cuidado! Nao é a linguagem que faz os programas parecerem simples, é o programador! 12 Capitulo 1: Cédigo Limpo Escolas de Pensamento E eu (Tio Bob)? O que é um cédigo limpo para mim? isso o que este livro lhe dird em detalhes, 0 que eu e meus compatriotas consideramos um cddigo limpo. Diremo-lhe 0 ‘que consideramos como nomes limpos de variaveis, fungoes limpas, classes limpas etc. Apresentaremos nossos conceitos como verdades absolutas, e no nos desculparemos por nossa austeridade. Para nés, a essa altura de nossa carreira, tais conceitos sdo absolutos. Si nossa escola de pensamento acerca do que seja um cédigo limpo. Nem todos os mestres de artes marciais concordam com qual seria a melhor arte marcial de todas ou a melhor técnica dentro de uma arte marcial espeeffica. Geralmente, cles criam suas proprias escolas de pensamento ¢ recrutam alunos para serem ensinados. Dessa forma, temos 0 Jiu Jitsu dos Gracie, criado ¢ ensinado pela familia Gracie, no Brasil; 0 Jiu Jitsu de Hakkoryu, criado ¢ ensinado por Okuyama Ryuho, em Téquio; e o Jeet Kune Do, criado ¢ ensinado por Bruce Lee, nos EUA. Os estudantes se dedicam a doutrina ensinada por aquela determinada arte, e aprendem o que seus mestres ensinam, geralmente ignorando os ensinamentos de outros mestres. Depois, conforme os estudantes progridem, costuma-se mudar 0 mestre de modo que possam expandir seus conhecimentos e praticas. Alguns, no final, continuam a fim de refinar suas habilidades, descobrem novas técnicas e criam suas proprias escolas. ‘Nenhuma dessas escolas esté 100% certa. Mesmo assim dentro de uma determinada escola agimos como os ensinamentos ¢ téenicas fossem os certos. Apesar de tudo, hé uma forma correta de praticar o Jiu Jitsu de Hakkoryu, ou Jeet Kune Do. Mas essa retidao dentro de uma escola nao invalida as técnicas de outra. Considere este livre como uma descrigao da Escola de Codigo Limpo da Object Mentor. As téenicas ¢ ensinamentos sao a maneira pela qual praticamos nossa arte. Estamos dispostos a alegar que se vocé seguir esses ensinamentos, desfrutard dos beneficios que também aproveitamos e aprender a escrever codigos limpos ¢ profissionais. Mas nao pense vocé que nés estamos 100% “certos”. Provavelmente ha outras escolas e mestres que tém tanto para oferecer quanto nés. O correto seria que vocé aprendesse com elas também. De fato, muitas das recomendagdes neste livro sao contraditorias. Provayelmente vocé nao concordara com todas e podera até mesmo discordar intensivamente com algumas. Tudo bem. Nao podemos querer ter a palavra final. Por outro lado, pensamos bastante € por muito tempo sobre as recomendagdes neste livro. As aprendemos ao longo de décadas de experiéncia ¢ repetidos testes ¢ erros. Portanto, concorde vocé ou nao, seria uma pena se vocé nao conseguisse enxergar nosso ponto de vista. Somos Autores 13 Somos Autores © campo @author de um Javadoc nos diz quem somos. Nés somos autores, e todo autor tem leitores, com os quais uma boa comunicagao é de responsabilidade dos autores. Na préxima vez em que voce escrever uma linha de cédigo, lembre-se de que vocé ¢ um autor, escrevendo para leitores que julgarao seus esfor¢os. Vocé talvez se pergunte: quanto realmente se Ié de um cédigo? A maioria do trabalho nao é escrevé-lo? Vocé j4 reproduziu uma sesso de edigdo? Nas décadas de 1980 e 1990, tinhamos editores, como 0 Emacs, que mantinham um registro de cada tecla pressionada. Vocé podia trabalhar por horas ¢, endo, reproduzir toda a sua sesso de edigo como um filme passando em alta velocidade. Quando fiz isso, os resultados foram fascinantes. A grande maioria da reprodugdo era rolamento de tela ¢ navegagao para outros médulos! Bob entra no modulo, Ele descia até a funcdo que precisava ser alterada. Ele para e pondera suas opcies. Oh, ele sobe para o inicio do médulo a fim de verificar a inicializacao da varidvel. Agora ele desce novamente e comeca a digitar. Opa, ele esté apagando 0 que digitou! Ele digita novameme. Ele apaga novamente. Ele digita a metade de algo e apaga! sy Ele desce até outra funcéo que chama a que ele esté modificando para ver como ela é, chamada. Ele sobe novamente e digita 0 mesmo cédigo que acabara de digitar. Ele para. Ele apaga novamente! Ele abre uma outra janela e analisa uma subclasse. A fingdo é anulada? Bem, vocé entendeu! Na verdade, a taxa de tempo gasto na leitura v. na escrita ¢ de 10x1. Constantemente lemos um cédigo antigo quando estamos criando um novo. Devido & tamanha diferenga, desejamos que a leitura do cédigo seja facil, mesmo se sua criagdo for ardua. E claro que n&o ha como escrever um cédigo sem Ié-lo, portanto tornd-lo de facil leitura realmente facilita a escrita. Nao hé como escapar desta légica. Vocé no pode escrever um cédigo se nao quiser ler as outras partes dele. O cédigo que voce tenta escrever hoje sera de dificil ou facil leitura dependendo da facilidade de leitura da outra parte do eédigo. Se quiser ser rapido, se quiser acabar logo, se quiser que seu cédigo seja de facil eserita, tone-o de facil leitura. 14 Capitulo 1: Cédigo Limpo A Regra de Escoteiro Nao basta escrever um cédigo bom. Ele precisa ser mantido sempre limpo. Todos jé vimos cédigos estragarem e degradarem com o tempo. Portanto, precisamos assumir um papel ativo na preveneao da degradacao. [A Boy Scouts of America (maior organizagao de jovens eseoteiros dos EUA) tem uma regra simples que podemos aplicar 4 nossa profissao. Deixe a drea do acampamento mais limpa do que como vocé a encontrou.s Se todos deixdssemos nosso cédigo mais limpo do que quando o comecamos, ele simplesmente nao degradaria. A limpeza nao precisa ser algo grande, Troque o nome de uma varidvel por um melhor, divida uma funcZo que esteja um pouco grande demais, elimine um pouco de repeti¢ao de cédigo, reduza uma instrugao i £ aninhada, Consegue se imaginar trabalhando num projeto no qual o codigo simplesmente melhorou com o tempo? Voce acredita que qualquer alternativa seja profissional? Na verdade, 0 aperfeigoamento continuo nao ¢ inerente ao profissionalismo? Prequela e Principios Em muitas maneiras este livro é uma “prequela” de outro que escrevi em 2002, chamado Agile Software Development: Principles, Patterns, and Practices (PPP), Ele fala sobre os principios do projeto orientado a objeto ¢ muitas das priticas utilizadas por desenvolvedores profissionais. Se vocé ainda nao Icu o PPP, talvez ache que € a continuagao deste livro. Se ja o leu, entio perceberd que ele é bastante parecido a esse em relacao aos codigos Neste livro ha referéncias esporadicas a diversos principios de projeto, dentre os quais esto: Principio da Responsabilidade Unica (SRP, sigla em ingles), Principio de Aberto-Fechado (OCP, sigla em¥inglés), Principio da Inversdo da Independéncia (DIP, sigla em inglés), dentre outros, Esses prineipios sao descritos detalhadamente no PPP. Conclusao Livros sobre arte nljo prometem Ihe tornar um artista. Tudo 0 que podem fazer ¢ The oferecer algumas das ferramentas, técnicas e linhas de pensamento que outros artistas usaram. Portanto, este livro nao pode prometer Ihe tornar um bom programador. Ou Ihe dar a “sensibilidade ao codigo”, Tudo o que ele pode fazer ¢ Ihe mostrar a linha de pensamento de bons programadores © 0s truques, técnicas ¢ ferramentas que cles usam. ‘Assim como um livro sobre arte, este esta cheio de detalhes. H4 muitos cédigos. Vocé vera cédigos bons ¢ ruins; eédigo ruim sendo transformado em bom; listas de heuristicas, orientagdes e técnicas; e também exemplo apés exemplo. Depois disso, é por sua conta Lembre-se daquela piada sobre o violinista que se perdeu no caminho para a apresentagao em sihaieeretioen diblsss cake euluiale ti Bibliografia 15 um concerto? Ele aborda um senhor na esquina e the pergunta como chegar ao Camegie Hall. O senhor observa 0 violinista com seu violino debaixo dos bragos e diz “Pratique, filho. Pratique!”. Bibliografia [Beck07]: Implementation Patterns, Kent Beck, Addison-Wesley, 2007. [Knuth92]: Literate Programming, Donald E. Knuth, Center for the Study of Language and Information, Leland Stanford Junior University, 1992. 2 Nomes Significativos por Tim Ottinger Introdugao Ha nomes por todos os lados em um software, Nomeamos nossas variaveis, fungdes, parimetros, classes e pacotes, assim como os arquivos-fonte € os diretérios que os possui. Nomeamos também nossos arquivos jar e war e ear. E nomeamos, nomeamos e nomeamos. Como fazemos muito isso, ¢ melhor que o fagamos bem. A seguir esto algumas regras simples para a criagao de bons nomes. 18 Capitulo 2: Nomes Significativos Use Nomes que Revelem seu Propésito Dizer que os nomes devem demonstrar seu propésito € facil. Mas queremos que vocé saiba que estamos falando sério. Escolher bons nomes leva tempo, mas economiza mais. Portanto, cuide de seus nomes e troque-os quando encontrar melhores. Todos que lerem seu c6digo (incluindo ‘vocé mesmo) ficarao agradecidos. © nome de uma variavel, fungdo ou classe deve responder a todas as grandes questdes. Ele deve Ihe dizer porque existe, o que faz e como € usado. Se um nome requer um comentirio, entaio ele nao revela seu propésito. int d; // tempo decorrido em dias O nome d nao revela nada, Ele nfo indica a ideia de tempo decorrido, nem de dias. Devemos escolher um nome que especifique seu uso para mensuragdo ¢ a unidade usada. int elapsedTimeInDays; int daysSinceCreation; int daysSinceModification; int fileageInDays; Escolher nomes que revelem seu propésito pode facilitar bastante o entendimento ea alteragao do cédigo. Qual o propésito deste cédigo? public List getThem() { List listl = new ArrayList(); for (intl] x : theList) if (x[0] == 4) listl.addtx); return list1; } Por que € dificil dizer o que o eédigo faz? Nao ha expressdes complexas. O espacamento ¢ a endentagdo sto cabiveis. S6 ha wés variéveis e duas constantes. Nem mesmo ha classes refinadas ou métodos polimérficos, apenas uma lista de vetores (pelo menos & 0 que parece), O problema nao é a simplicidade do cédigo, mas seu aspecto implicito, isto ¢, 0 contexto ntio estd explicito no cédigo. Devido a isso, é necessério que saibamos as repostas para questdes como: 1. Que tipos de coisas estdo em theList? 2. Qual a importincia de um item na posi¢do zero na theList? 3. Qual a importineia do valor 4? 4, Como eu usaria a lista retornada? |As respostas para tais questées nfo estilo presentes no exemplo do cédigo, mas poderiam. Digamos que estejamos trabalhando num jogo do tipo “campo minado”. Percebemos que o tabulciro é uma lista de células chamada theList. Vamos renomed-la para gameBoard. Evite Informagées Erradas 19 Cada quadrado no tabuleiro é representada por um vetor simples, Mais tarde, descobrimos que a posigao zero é armazena um valor de status ¢ que o valor 4 significa “marcado com uma bandeirinha’”, Ao dar esses nomes explicativos, podemos melhorar consideravelmente 0 codigo: public List getFlaggedCells() ( bist flaggedCells = new ArrayList(); for (int[] cell : gameBoard} if (cell[STATUS_VALUE] flaggedCells.add(cell return flaggedCells; FLAGGED) } Note que a simplicidade do codigo nao mudou. Ele ainda possui o mesmo nitmero de operadores € constantes, com o mesmo numero de itens aninhados. Entretanto, 0 cédigo ficou muito mais explicito. Podemos continuar ¢ criar uma classe simples para as células, em vez de usar um vetor ints. Ela pode ter uma fungao com um nome revele seu propésito (chamada isF lagged, ou seja “esté mareada com uma bandeirinha”) para ocultar os numeros magicos. O resultado é uma nova versio da funcao: public List getFlaggedcells() { List flaggedCells = new ArrayList(); for (Cell cell : gameBoard) if (cell.isFlagged()} flaggedcells.add(cell); return flaggedCells; Com essas simples alteragdes de nomes, nio fica dificil entender 0 que esta acontecendo. Esse € o poder de escolher bons nomes. Os programadores devem evitar passar dicas falsas que confundam o sentido do cédigo. Devemos evitar palavras cujos significados podem se desviar daquele que desejamos. Por exemplo, hp, aix e sco scriam nomes ruins de varidveis, pois sio nomes de plataformas Unix ou variantes. Mesmo se estiver programando uma hipotenusa e hp parecer uma boa abreviagao, 0 nome pode ser mal interpretado. Nao se refira a um grupo de contas como accountList, a menos que realmente seja uma List. A palavra /ist (lista) significa algo especifico para programadores. Se 0 que armazena as contas ndo for uma List de verdade, podera confundir os outros.' Portanto, accountGroup ou bunchOfAccounts ou apenas accounts seria melhor. Cuidado ao usar nomes muito parecidos. Fica dificil perceber a pequena diferenga entre xyZControllerForEfficientHandlingOfstrings em um médulo ¢ XYZControllerForBfficientStorageOfStrings em outro. Ambas as palavras sio muito semelhantes. 20 Capitulo 2: Nomes Significativos Usar conceitos similares para montar palavras é informagao. Usar formatos inconsistentes para palavras leva a uma md interpretacdo. Com os ambientes Java de hoje dispomos do recurso de autocompletar palavras. Digitamos alguns caracteres de um nome ¢ pressionamos uma combinagdo de teclas (se houver uma) ¢, entio, aparece uma lista de possiveis palavras que se iniciam com tais letras. Isso é muito pratico se houver nomes muito parecidos organizados alfabeticamente num mesmo local e se as diferengas forem ébvias, pois é mais provavel que 0 desenvolvedor escolha um objeto pelo nome, sem consultar seus comentarios ou mesmo a lista de métodos fornecidos por aquela classe Um exemplo real de nomes que podem gerar confusfo é 0 uso da letra “I” minuscula ou da vogal “o” maitiscula como nome de variiveis. O problema é que cles se parecem com 0 um ¢ © zero, respectivamente. int a= 1 if (0 ) a= Ol; else 1 = 01; © leitor pode achar que inventamos esse exemplo, mas j4 vimos cédigos nos quais isso acontecia bastante, Uma vez, 0 autor do codigo sugeriu 0 uso de fontes distintas de modo a realgar mais as diferencas, uma solugdo que teria de ser repassada de forma oral ou escrita a todos os futuros desenvolvedores. Com uma simples troca de nome, o problema ¢ resolvido com objetividade ¢ sem precisar criar novas tarefas. Faca Distingées Significativas Os programadores criam problemas para si proprios quando criam um cédigo voltado unicamente para um compilador ou interpretador. Por exemplo, como. no é possivel usar 0 mesmo nome para referir-se 1a duas coisas diferentes num mesmo escopo, vocé pode decidir alterar o nome de maneira arbitraria, As vezes, isso € feito escrevendo um dos nomes errado, produzindo a uma situagao na qual a corre¢do de erros de ortografia impossibilita a compilagao.’ Nao basta adicionar niimeros ou palavras muito comuns, mesmo que o compilador fique satisfeito, Se os nomes precisam ser diferentes, entdo também devem ter significados distintos. Usar mimeros sequenciais em nomes (al, a2,...,aN) € @ oposto da selegao de nomes expressivos. Eles nao geram confusdo, simplesmente nao oferecem informagao alguma ou dica sobre a intengao de seu criador. Considere o seguinte: public static void copyChars(char al[], char a2{l) { for (int i = 0; i < al-length; i++) { a2[i] = allil; ? 5 Use Nomes Pronuncitiveis Fica muito mais ficil ler essa fung&o quando usam-se source ¢ destination como nomes de pardmetros. Palavras muito comuns s4o outra forma de distingdo que nada expressam. Imagine que vocé tenha uma classe Product. Se houver outra chamada Productinfo ou ProductData, vocé usou nomes distintos que nao revelam nada de diferente. Info ¢ Data sao palavras muito comuns € vagas, como “um”, “uma’” ¢ “a”. Observe que nao ha problema em usar prefixes como “um” e “a”, contanto que fagam uma distingdo significativa. Por exemplo, vocé pode usar “um” para varidveis locais ¢ “a” para todos os parimetros de fungdes.’ © problema surge quando vocé decide chamar uma variavel de aZork s6 porque jé existe outra chamada zork. Palayras muito comuns sao redundantes. O nome de uma variavel jamais deve conter a palavra “varidvel”. O nome de uma tabela jamais deve conter a palavra tabela. Entéo como ‘NameString é melhor do que Name? Um Name pode ser um numero do tipo ponto flutuante? Caso possa, estatia violando uma regra sobre passar informagées incorretas, Imagine que vocé encontre uma classe Customer ¢ outra CustomerObject. O que a diferenga nos nomes Ihe diz? Qual seria a melhor para possuir 0 histérico de pagamento de um cliente? Conhecemos um aplicative que é assim. A fim de proteger seus desenvolvedores, trocamos os nomes, mas abaixo estd exatamente 0 tipo de erro: getActiveAccount (); getActiveAccounts|); getActiveAccountInfo(); Como os programadores desse projeto poderiam saber qual das trés fungdes chamar? ‘Na auséncia de convengdes especificas, ndo h como distinguir moneyAmount de money, customerInfo de customer, accountData de accounte theMessage de message. Faga a distingao dos nomes de uma forma que o leitor compreenda as diferengas. Use Nomes Pronunciaveis Os ser humano ¢ bom com as palavras. Uma parte consideravel de seu cérebro é responsivel pelo conceito das palavras. E, por definigdo, as palavras so pronunciaveis. Seria uma lstima no tirar proveito dessa importante parte de nosso cérebro que evoluiu para lidar com a lingua falada.Sendo assim, crie nomes pronuncidveis. Se no puder pronuncié-lo, nao teré como discutir sobre tal nome sem parecer um idiota. “Bem, aqui no bé cé erre trés cé ene t@, temos um pé esse 7é qué int, viram?”. Isso importa porque a programagao é uma atividade social. Conhego uma empresa que possui uma varidvel genymdhms (generation date, year, month, day, hour, minute e second) ¢ seus funcionarios saem falando “gé dabliu eme dé agd eme esse”, Tenho um hébito irritante de pronunciar tudo como esta escrito, portanto comecci a falar “gen-yah-muddahims”. Depois desenvolvedores ¢ analistas estavam falando assim também, e ainda soava estupido. Mas estavamos fazendo uma brincadeira e, por isso, foi divertido. Engragado ou nao, estamos tolerando nomeagdes de baixa qualidade. Novos desenvolvedores tiveram de pedir que Ihes explicassem as varidveis, e, ento, em vez de usar termos existentes na lingua, inventavam palavras bobas ao pronuncié-las. Compare 21 22 Capitulo 2: Nomes Significativos class Dtakerdi02 { private Date genymdhms; private Date modymdhms; private final String pszgint = “1027; Twa * com class Customer { private Date generationTimestamp; private Date modificationTimestamp;; private final string recordId = ~102"; eae y Agora é possivel uma conversa inteligente: “Ei, Mikey, dé uma olhada este registro! A generation timestamp (“criagao de marcagao de horario”) esta marcada para amanha! Como pode?”. Use Nomes Passiveis de Busca Nomes de uma sé letra ou ntimeros possuem um problema em particular por nao ser facil localiz’- los ao longo de um texto. Pode-se usar facilmente o grep para MAX_CLASSES_PER_STUD! mas buscar 0 nimero 7 poderia ser mais complicado. Nomes, definigdes de constantes ¢ varias outras expressdes que possuam tal mimero, usado para outros propésitos podem ser resultados da busca, Pior ainda quando uma constante € um nimero grande ¢ alguém talvez tenha trocado os digitos, criando assim um bug e ao mesmo tempo nfo sendo captada pela busca efetuada. ‘Damesma forma, onome“ =” é uma escolha ruim para qualquer varidvel a qual um programadot talvez precise fazer uma busca. E uma letra muito comum e provavelmente apareceré em todo texto em qualquer programa. Devido a isso, nomes longos se sobressaem aos curtos, ¢ qualquer nome passivel de busca se sobressai a uma constante no cédigo. Particularmente, prefiro que nomes com uma tnica letra SO sejam usados como variaveis locais dentro de métodos pequenos. O samanho de um nome deve ser proporcional ao tamanho do escopo. [NS]. Se uma variavel ou constante pode ser vista ou usada em varios lugares dentro do cédigo. € imperativo atribui-la um nome fécil para busca. Novamente, compare for (int j=0; j<34; i+) ( s += (t[j]*4)/5; com int realDaysPerlIdealDay = 4; const int WORK_DAYS_PER_WEEK = 5; int sum = 0; for (int j=0; j < NUMBER_OF_TASKS; j++) ( int realTaskDays taskEstimatel[j] * realDaysPerIdealDay; int realTaskWeeks (realdays / WORK_DAYS_PER_WEEK); sum += realTaskileeks; Evite Codificagies 23 Note que sum nao é um nome pratico, mas pelo menos é facil de procurar. O nome usado no cédigo serve para uma fungio maior, mas pense como seria muito mais facil encontrar WORK_ DAYS PER WEEK do que buscar em todos os lugares nos quais 0 5 aparece ¢, entio, filtrar os resultados para exibir apenas as instncias que vocé des Evite Codificacées Ja temos de lidar com bastante codificagao e nao precisamos acrescentar mais. Codificar informagées do escopo ou tipos em nomes simplesmente adiciona uma tarefa extra de decifraco. Dificilmente parece légico contratar um novo funcionario para aprender outra “linguagem”™ codificadora além da atual usada no cédigo no qual se est trabalhando. £ uma sobrecarga mental desnecessaria ao tentar resolver um problema. Nomes codificados raramente sao pronuncidveis, além de ser facil escrevé-los incorretamente. A Notagio Hingara Antigamente, quando trabalhavamos com linguagens com limite de tamanho para os nomes, viokivamos essa regra quando necessario, com certo arrependimento. O Fortran forgava codificagdes ao tomar a primeira letra uma indicagao para o tipo. Versdes anteriores do BASIC so permitiam uma letra mais um digito. A Notagéo Hungara (NH) inovou essas limitagdes. Na €poca da API em C do Windows, a NH era considerada muito importante, quando tudo era um inteiro ou um ponteiro para um inteiro long de 32 bits ou um ponteiro do tipo void, ou uma das diversas implementagdes de “strings” (com finalidades e atributos diferentes). O compilador nao verificava os tipos naquele tempo, ento os programadores precisavam de ajuda para lembrar dos tipos. Em linguagens modemas, temos tipos muito melhores, ¢ 08 compiladores os reconhecem e os tomam obrigatérios. Ademais, ha uma certa tendéncia para criagao de classes e fun¢des menores+ de modo que as pessoas possam ver onde cada varidvel que esto usando foi declarada. ‘Os programadores Java no precisam definir o tipo. Os objetos ja so 0 proprio tipo, ¢ a ediglo de ambientes se tornou tao avangada que detectam quando se usa inadequadamente um tipo antes mesmo da compilagao! Portanto, hoje em dia, a NH ¢ outras formas de convengao de tipos sao basicamente obstaculos. Eles dificultam a alteragao do nome ou do tipo de uma variavel, fungao ou classe; dificultam a leitura do cédigo; e criam a possibilidade de que o sistema de codificagao induza 0 leitor ao erro. PhoneNumber phoneString; // 9 nome nao muda na alteracao do tipo! Prefixos de Variaveis Membro Vocé nao precisa mais colocar o prefix m_em variaveis membro. Mas para isso, suas classes € fungdes devem ser pequenas. E vocé deve usar um ambiente de edigio que realce ou colore as varidveis membro de modo a distingui-las. public class Part { private String m_dsc; // Descrigaéo textual void setName(String name) ( 24 Capitulo 2: Nomes Significativos m_dsc = name; } public class Part ( String description; void setbescription(String description) { this.description = description; ) } ‘Além disso, as pessoas rapidamente aprendem a ignorar o prefixo (ou sufixo) para visualizar a parte significativa do nome. Quanto mais lemos 0 cédigo, menos prefixos enxergamos. No final, estes se tornam partes invisiveis e um indicativo de cédigo velho. Interfaces e Implementagées Agvvezes, hd casos especiais para codificagdes. Por exemplo, digamos que vocé esteja construindo uma ABSTRACT FACTORY para criar formas. Essa factory seré uma interface, ¢ implementada por uma classe concreta. Como devemos nomed-la? IshapeFactory e ShapeFactory? Prefiro nao enfeitar as interfaces. O “z” no inicio, tao comum no hoje em dia, é na melhor das hipéteses uma distragdo, e na pior sao informagdes excessivas. Nio quero que meus usuarios saibam que estou thes dando uma interface, e, sim, apenas uma ShapeFactory. Portanto, se eu devo codificar seja a interface ou a implementacao, escolho esta, Para codificar a interface, é preferivel chama-la de ShapeFactoryImp, ou mesmo de CShapeFactory. Evite o Mapeamento Mental Os leitores nao devem ter de traduzir mentalmente os nomes que vocé escolheu por outros que cles conhegam. Essa questéo costuma levar a deciso de n&o usar os termos do dominio do problema e nem os da solugo. Este ¢ um problema com nomes de variaveis de uma sé letra. Certamente um contador de iteragdes pode ser chamado de “i”, 3” ou “k” (mas nunca 1) ~ isso j4 se tornou uma tradig&o — se seu escopo for muito pequeno ¢ ndo houver outros nomes que possam entrar em conflito com ele. Entretanto, na maioria dos contextos, um nome de uma sé letra é uma escolha ruim; ¢ apenas um armazenador que o leitor devera mentalmente mapear de acordo com 0 conceito em uso, Nao ha razao pior do que usar 0 nome “c” s6 porque “a” € “b” jd esto sendo usados. De maneira geral, os programadores so pessoas muito espertas. E esse tipo de pessoas gosta de se exibir mostrando suas habilidades mentais. Apesar de tudo, se vocé puder confiantemente se lembrar de que 0 “r” mindisculo é uma versao da url sem 0 host ¢ 0 contexto, entao obviamente yocé € muito esperto. ‘Uma diferenca entre um programador esperto ¢ um programador profissional ¢ que este entende que clareza é fundamental. Os profissionais usam seus poderes para o bem, ¢ escrevem cédigos que outros possam entender. ‘Nomes de Métodos 25 Nomes de Classes Classes ¢ objetos devem ter nomes com substantivo(s), como Cliente, PaginaWiki, Conta e AnaliseEndereco. Evite palavras como Gerente, Processador. Dados ou Info no nome de uma classe, que também no deve ser um verbo. Nomes de Métodos Os nomes de métodos devem ter verbos, como postarPagamento, excluirPagina ou salvar. Devem-se nomear métodos de acesso, alteragdio e autenticagao segundo seus valores ¢ adicionar 08 prefixos get, set ou is de acordo com o padrio javabean.* string name = employee.getName(); customer.setName(*mike”); if (paycheck.isPosted())... Quando os construtores estiverem sobrecarregados, use métodos factory estiticos com nomes que descrevam os parimetros. Por exemplo, Complex fulcrumPoint = Complex.FromRealNumber (23.0); melhor do que Complex fulcrumPoint = new Complex(23.0); Para forgar seu uso, torne os construtores correspondentes como privados. Nao dé uma de Espertinho Se os nomes forem muito “espertinhos”, apenas as pessoas que compartilhem do mesmo senso de humor que seu dono irdo lembra-los, e sé enquanto se lembrarem da brincadeira. Eles saberio o que deve fazer a fungdo HolyHandGrenade? Claro, € engragado, mas talvez nesse caso Deletertems fique melhor. Opte por clareza no lugar de divertimento. Essas gracinhas em codigos costumam aparecer na forma de coloquialismos e girias. Por exemplo, nao use firmar () para significar terminar(). Nao use piadas de baixo calao, como cairFora para significar abortar |). Diga 0 que vocé quer expressar. Expresse 0 que vocé quer dizer. 26 Capitulo 2: Nomes Significativos Selecione uma Palavra por Conceito Escolha uma palavra por cada conceito abstrato ¢ fique com cla, Por exemplo, € confuso ter pegar, recuperar ¢ obter como métodos equivalentes de classes diferentes. Como lembrar a qual método pertence cada classe? Infelizmente, vocé geralmente precisa se lembrar qual empresa, grupo ou pessoa criou a biblioteca ou a classe de modo a recordar qual termo foi usado, Caso contrario, vocé perde muito tempo vasculhando pelos cabegalhos e exemplos de cédigos antigos. Os ambientes modernos de edigo, como o Eclipse e o IntelliJ, oferecem dicas relacionadas a0 contexto, como a lista de métodos que vocé pode chamar em um determinado objeto. Mas note que a lista geralmente nfo Ihe oferece 03 comentirios que vocé escreveu em tomo dos nomes de suas fungdes. Vocé tem sorte se receber o parimetro nomes (names) das declaragdes das fungdes. Os nomes das fungdes tém de ficar sozinhos, e devem ser consistentes de modo que vocé possa selecionar 0 método correto sem qualquer busca extra Da mesma forma. é confuso ter um controlador, um gerenciador ¢ um driver no mesmo cédigo-fonte. Qual a principal diferenga entre um GerenciadorDeDi spositivo eum controlador-de-prot ccolo? Por que ambos nao sao controladores ougerenciadores? Ambos so realmente drivers? © nome faz com que vocé espere dois objetos com tipos bem distintos, assim como ter classes diferentes. Um lexico consistente é uma grande vantagem aos programadores que precisem usar seu cddigo. Nao Faca Trocadilhos Evite usar a mesma palavra para dois propésitos, Usar o mesmo termo para duas ideias diferentes € basicamente um trocadilho. Se vocé seguir a regra “uma palavra por conceito”, voeé pode acabar ficando com muitas classes que possuam, por exemplo, um método ada. Contanto que as listas de parametros e os valores retornados dos diversos métodos add sejam semanticamente equivalentes, tudo bem. Entretanto, uma pessoa pode decidir usar a palavra add por fins de “consisténcia” quando cla na verdade nao aplica o mesmo sentido a todas. Digamos que tenhamos muitas classes nas quais add criara um novo valor por meio da adigao e concatenagao de dois valores existentes. Agora, digamos que estejamos criando uma nova classe que possua um método que coloque seu Unico pardmetro em uma colegao. Deveriamos chamar este método de ada? Por termos tantos outros métodos add, isso pode parecer consistente. Mas, neste caso, a semantica & diferente. Portanto, deveriamos usar um nome como inserir ou adicionar. Chamar este novo método de add seria um trocadilho. Nosso objetivo, como autores, ¢ tornar a leitura de nosso codigo o mais facil possivel. Desejamos que nosso cédigo seja de rapida leitura, e no um estudo demorado, Queremos usar a linguagem de um livro popular no qual é responsabilidade do autor ser claro, nao uma linguagem académica na qual a tarefa do estudioso é entender minuciosamente o que esta escrito. Use nomes a partir do Dominio da Solucao Lembre-se de que serio programadores que lerdo seu cédigo. Portanto, pode usar termos de Informatica, nomes de algoritmos, nomes de padrdes, termos matematicos etc. Nao é prudente Adicione um Contexto Signifieativo 27 pensar num nome a partir do dominio do problema, pois nao queremos que nossos companheiros de trabalho, tenham de consultar o cliente toda hora para saber 0 significado de um nome o qual eles ja conhecem 0 conceito, 6 que por outro nome. O nome Account Visitor (“conta do visitante”) significa o bastante para um programador familiarizado com 0 padrio VISITOR. Qual programador nao saberia 0 que ¢ uma JobQueue (“fila de tarefas”)? Ha muitas coisas técnicas que os programadores devem fazer, Selecionar nomes técnicos para tais coisas é, geralmente, 0 método mais adequado. Use nomes de Dominios do Problema Quando ndo houver uma soluedo “a la programador”, use © nome do dominio do problema. Pelo menos 0 programador que fizer a manutencao do seu cédigo poder perguntar a um especialista em tal dominio o que o nome significa. Distinguir os conceitos do dominio do problema dos do dominio da solugao é parte da tarefa de um bom programador e designer. O cédigo que tem mais a ver com os conceitos do dominio do problema tem nomes derivados de tal dominio. Adicione um Contexto Significativo Ha poucos nomes que sao significativos por si sé—a maioria nao ¢. Por conta disso, vocé precisa usar nomes que fagam parte do contexto para o leitor. Para isso vocé os coloca em classes, fungdes e namespaces bem nomeados. Se nada disso funcionar, entao talvez. como tiltimo recurso seja necessirio adicionar prefixos ao nome. Imagine que vocé tenha varidveis chamadas firstName, lastName, street, houseNumber, city, state e zipcode. Vistas juntas, fica bem claro que elas formam um enderego. Mas ¢ se vocé sé visse a varidvel state sozinha num método? Automaticamente yocé assumiria ser parte de um enderego? Podem-se usar prefixos para adicionar um contexto: addrFirstName, addrLastName, addr State etc, Pelo menos os leitores entenderdo que essas varidiveis sdo parte de uma estrutura maior, E claro que uma melhor solugao seria criar uma classe chamada Adaress. Entao, até o compilador sabe que as varidveis pertencem a um escopo maior. Veja o método na Listagem 2.1. As variaveis precisam de um contexto mais significative? O nome da fungao oferece apenas parte do contexto; 0 algoritmo apresenta o resto. ‘Apés ter lido a fungao, vocé vé que trés varidveis, number, verb € pluralModifier, fazem parte da mensagem de dedugao (guess statistics message). Infelizmente, 0 contexto deve ser inferido. Ao olhar pela primeira vez o metodo, o significado das varidveis ndo esté claro. 28 Capitulo 2: Nomes Significativos Listagem 2-1 Variaveis com contexto obscuro private void printGuessStatistics/char candidate. int count) ( String number; String verb; String pluralNodifier; if (count == 0) { number = “no*: verb = “Existem*; pluralModifier = “s"; else if (count == 1) { number = "1"; verb = “Existe’; pluralNedifier = **; } else { number = Integer.toString(count) ; verb = “Existem*; pluralNedifier = “s": 1 String guessMessage = String. format( “here ts $s tet", verb, number, candidate, pluralModifier ie print (guessMessage) ; ‘A funcdo é um pouco extensa demais também e as variaveis sao bastante usadas. A fim de dividir a fungao em partes menores, precisamos criar uma classe GuessStatisticsMessage € tomar as trés variaveis como campos desta classe. Isso oferecera um contexto mais claro para as trés variéveis. Elas sao definitivamente parte da GuessStatisticsMessage. A melhora do contexto também permite ao algoritmo ficar muito mais claro ao dividi-lo em fungées menores (veja a Listagem 2.2). Listagem 2-2 Variaveis possuem contexto public class GuessstatisticsMessage ( private string number; private String verb; private String pluralModifier; public String make(char candidate, int count) { creacePluralDependentMessageParts (count) : return string. format ( “There $s 4s tsts’, verb, number, candidate, pluralModiner ); ) private void createPluralDependentMessageParts (int count) { if (count == 0) ¢ thereareNoLetters(); } else if (count == 1) { thereTsOneLetter () ; j else { Nao Adicione Contextos Desnecessarios 29 Listagem 2-2 (continuagio) Variaveis possuem contexto thereAreManyLet ters (count) ; } d private void thereAreManyLetters(int count) { number = Integer. toString(count) ; verb = “Existem” pluralModifier = *s"; ? private void thereTsOneLetter() { number = "1": verb = *Existe* pluralMedifier = *Y; y private void thereareNoLetters() { number = “no": verb = “Existem"; pluralMedifier = “s": t Nao Adicione Contextos Desnecessarios Em um aplicativo ficticio chamado “Gas Station Deluxe” (GSD), seria uma péssima ideia adicionar prefixos a toda classe com GSD. Para ser sincero, vocé estara trabalhando contra suas ferramentas, Vocé digita ¢ e pressiona a tecla de autocompletar e recebe uma lista quilomeétrica de ~ cada classe no sistema. Isso é inteligente? Para que dificultar a ajuda da IDE? Da mesma forma, digamos que vocé inventou uma classe MailingAddress no modulo de contabilidade do GSD e que o chamou de GSDAccountaddress.Mais tarde. vocé precisa armazenar um enderego postal de seu cliente no aplicativo. Vocé usaria GSDAccountAddress? Parece que o nome ¢ adequado? Dez dos 17 caracteres so redundantes ou irrelevantes. Nomes curtos geralmente sio melhores contanto que scjam claros. Nao adicione mais contexto a um nome do que o necessario. Os nomes accountAddress € customerAddress esti bons para instancias da classe ‘Address, mas seriam ruins para nomes de classes. Address esté bom para uma classe. Se precisar diferenciar entre enderecos MAC, enderegos de portas ¢ enderegos da Web, uma ideia seria PostalAddress, MAC ¢ URT. Os nomes resultantes s&io mais precisos, motivo esse da tarefa de se atribuir nomes. Conclusdo O mais dificil sobre escolher bons nomes é a necessidade de se possuir boas habilidades de descri¢ao e um histérico cultural compartilhado. Essa é uma questio de aprender, e no técnica, gerencial ou empresarial. Como consequéncia, muitas pessoas nessa area nao aprendem essa tarefa muito bem. 30 Capitulo 2: Nomes Significativos Elas também tém receio de renomear as coisas por temer que outros desenvolvedores sejam contra, Nao compartilhamos desse medo e achamos que ficamos realmente agradecidos quando os nomes mudam (para melhor). Na maioria das yezes, ndo memorizamos os nomes de classes e métodos. Mas usamos ferramentas modernas para lidar com detalhes de modo que possamos nos focalizar e ver se 0 cédigo € lido como pardgrafos, frases, ou pelo menos como tabelas € estruturas de dados (uma frase nem sempre é a melhor forma de se exibir dados). Provavelmente vocé acabard surpreendendo alguém quando renomear algo, assim como qualquer outra melhoria no codigo. Nao deixe que isso atrapalhe seu progress Siga alguma dessas regras e note se vocé nfo melhorou a legibilidade de seu c6digo. Se estiver fazendo a manutengao do cddigo de outra pessoa, use ferramentas de refatoragao para ajudar a resolver essas questées. Em pouco tempo valeri a pena, e continuard a vale em longo prazo. Nos primérdios da programagio, formavamos nossos sistemas com rotinas ¢ sub-rotinas. Ja na era do Fortran e do PL/I, usavamos programas, subprogramas ¢ fungdes. De tudo isso, apenas funcao prevaleceu. As fungdes so a primeira linha de organizacao em qualquer programa. Escrevé-las bem € 0 assunto deste capitulo. 32 ‘Capitulo 3: Fungdes Veja 0 cédigo na Listagem 3.1. E dificil encontrar uma fungao grande em FitNesse', mas procurando um pouco mais encontramos uma. Além de ser longo, seu cédigo é repetido, ha diversas strings estranhas e muitos tipos de dados e APIS esquisitos e nada dbvios. Veja o quanto vocé consegue compreender nos préximos trés minutos. Listagem 3-1 Htmlvtil.java (FitNesse 20070619) public static String testableltent! ( PageData pageData, boolean includeSuitesetup ) throws exception { Page = pageData.getWikiPaget); + buffer = new FageCrawlerInp .getInheritedPaget SuiteResponder SUITE SE7UP_NAME, wikiPage iteSetup != null) ( WikiPagePath pagePath = suitesetup.aetPageCrawler! String pagePathName = Pat} butter getFullPathisuitesetup); rser.render (pagePath) ; appendi* include -setup jagePathName) atl: ) ) WikiFage setup PageCrawlerInp: eritedPage(*Setlp", wikiPage) ; ” if (setup != mull) { WikiPagePath setupPath wikiPage. getPageCrawler() .getFullpath (setup) + serin setupPathName = FathParser.render{setupPath) ; nelude -setup .") sappend (set upPat hName) pend? \al buffer.append (pageData.getContent (|); if (pageData.hasAttribute("Test"!) { WikiPage teardown PageCrawlerImpl.getInheritedPage(*TearDown", wikiPage) ; if [teardown != null) { WikiPagerath tearDowneath wikiPage.getPageCrawler (i .getPull Path (teardown) ; ‘tring tearDownPathName = FathParser. render |tearDownPath) ; buffer.append(*\n*) -append(*!include -teardewn -append (tearDownPat -append("\n") : 2) 1.75 reels de Gewiilchk clilines Gentes orereelienerane’d Fungies 33 Listagem 3-1 (continuagio) HtmlUtil.java (FitNesse 20070619) if includesuiteSetup! ( Wikibage suiteTeardown FageCrawlerimpl.get InheritedPage( SuiteResponder. SUITE_TEARDOW_NAME, wikipage Lf (suiteTeardown kiBagePath pagePath suiteTeardown.getPageCrawler().getFullfath (suits String pagePathName = PathParser.render !pagePath}; ffer.append("!include -teardown .* .append (pagePathName) lappend(*\n"}; Teardown) ; pagedata.setContent (buffer.tostringi))+ return pagelata.getHtml (|; Conseguiu entender a fungdo depois desses trés minutos estudando-a? Provavelmente nao. Ha muita coisa acontecendo la em muitos niveis diferentes de . Ha strings estranhas e chamadas a fungdes esquisitas misturadas com dois if aninhados controlados por flags. Entretanto, com umas poucas extragdes simples de métodos, algumas renomeagdes € um pouco de reestruturagao,fui capaz de entender 0 propésito da funcaio nas nove linhas da Listagem 3.2. Veja se vocé consegue compreender também em trés minutos. Listagem 3-2 HtmlUtil.java (refatorado) public static String renderPageWithsetupsAndreardowns ( PageData pageData, boolean :sSuite ) throws Exception { boolean isTestPage = pageData.hasAttribute/"Test"); if (isTestPage) [ WikiPage testPage = pageData.getWikiPage!); StringBuffer newFageContent = new StringBuffer ()+ includeSetupPages (testPage, newPageContent, issuite); newPageContent .append (pageData.ge=Content {)); includeTeardownPages (testPage, newPageContent, iss pageData.setContent (nexPageContent .toString|)): ite) : return pageData.getHtml (1; 4 Capitulo 3: Fungoes ‘A menos que jé estivesse estudando o FitNesse, provavelmente vocé nao entendeu todos os detalhes. ‘Ainda assim vocé talvez tenha compreendido que essa fungio efetua a incluso de algumas paginas SetUp e TearDown em uma pagina de teste e, entio, exibir tal pagina em HTML. Se estiver familiarizado com o JUnit’, vocé ja deve ter percebido que essa fungdo pertenca a algum tipo de framework de teste voltado para a Web. E vocé esta certo. Deduzir tal informagao da Listagem 3.2 & muito facil, mas ela esta bastante obscura na Listagem 3.1. Ento, 0 que torna uma funcdo como a da Listagem 3.2 ficil de ler e entender? Como fazer uma fungao transmitir seu propésito? Quais atributos dar as nossas fungdes que permitirao um Jeitor comum deduzir o tipo do programa ali contido? Pequenas! ‘A primeira regra para fungdes ¢ que clas devem ser pequenas. A segunda ¢ que precisam ser mais espertas do que isso. Nao tenho como justificar essa afirmag&o. Nao tenho referéncias de pesquisas que mosirem que fungées muito pequenas s4o melhores. S6 posso dizer que por cerca de quatro décadas tenho criado fungdes de tamanhos variados. Jé escrevi diversos monstros de 3,000 linhas; bastantes fungdes de 100 a 300 linhas; e fungdes que tinham apenas de 20 a 30 linhas. Essa experiéncia me ensinou que, ao longo de muitas tentativas ¢ erros, as fungdes devem ser muito pequenas. Na década de 1980, costumavamos dizer que uma fimedo nao deveria ser maior do que a tela. E claro que na época usivamos as telas VT100, de 24 linhas por 80 colunas, e nossos editores usavam 4 linhas para fins gerenciamento, Hoje em dia, com fontes reduzidas e um belo e grande + monitor, vocé consegue colocar 150 caracteres em uma linha — ndo se deve ultrapassar esse limite—e umas 100 linhas ou mais por tela—as fungdes nao devem chegar a isso tudo, elas devem ter no maximo 20 linhas. © quao pequena deve ser uma fungdo? Em 1999, fui visitar Kent Beck em sua casa, em Oregon, EUA. Sentamo-nos ¢ programamos um pouco juntos. Em certo ponto, cle me mostrou um simpatico programa de nome Java/Swing 0 qual ele chamava de Sparkle. Ele produzia na tela um efeito visual similar a uma varinha magica da fada madrinha do filme da Cinderela. ‘Ao mover 0 mouse, faiscas (sparkles, em inglés) caiam do ponteiro do mouse com um belo cintilar até 0 fim da janela, como se houvesse gravidade na tela, Quando Kent me mostrou 0 cédigo, fiquei surpreso com tamanha pequeneza das fungdes. Eu estava acostumado a fungdes que seguiam por quilémetros em programas do Swing. Cada fungiio neste programa tinha apenas duas, ou trés, ou quatro linhas. Esse deve ser o tamanho das suas fungdes* © quao pequenas devem ser suas fungdes? Geralmente menores do que a da Listagem 3.2! Na verdade, a Listagem 3.2 deveria ser enxuta para a Listagem 3.3. 2. Ferramenta de codigo aberto de teste de unidade para Java, www.junit.org, Faga Apenas uma Coisa 35 Blocos e Endentacaio Aqui quero dizer que blocos dentro de instrugdes if, else, while e outros devem ter apenas uma linha. Possivelmente uma chamada de fungao. Além de manter a fungo pequena, isso adiciona um valor significativo, pois a fungao chamada de dentro do bloco pode receber um nome descritivo. Isso também implica que as fungdes no devem ser grandes ¢ ter estruturas aninhadas. Portanto, o nivel de endentagdo de uma fungdo deve ser de, no maximo, um ou dois. Isso, é claro, facilita a leitura e compreensao das fungdes. Faca Apenas uma Coisa Deve ter ficado claro que a Listagem 3.1 faz muito mais de uma coisa, Ela cria buffers, pega paginas, busca por paginas herdadas, exibe caminhos, ancxa strings estranhas e gera HTML. dentre outras coisas. A Listagem 3.1 vive ocupada fazendo diversas coisas diferentes. Por outro lado, a Listagem 3.3 faz apenas uma coisa simples. Ela inclui SetUp e TearDown em paginas de teste. O conselho a seguir tem aparecide de uma forma ou de outra por 30 anos ou mais AS FUNCOES DEVEM FAZER UMA COISA. DEVEM FAZE-LA BEM. DEVEM FAZER APENAS ELA. © problema dessa declaracao é que é dificil saber 0 que é “uma coisa”, A Listagem 3.3 faz uma coisa? E facil dizer que ela faz trés: 1. Determina se a pagina é de teste. 2. Se for, inclui SetUps e TearDowns. 3. Exibe a pigina em HTML. Entao, uma ou trés coisas? Note que os trés passos da fungao esto em um nivel de abaixe do nome da fungao, Podemos descrever a fungdo com um breve parigrafo TO*: 4 Alinguagem LOGO usave a pelavra “TO” PARA") da mesma forma que Raby ¢ Python usam “det” Portnto, tna fungi comeya com a palavra “TO”, 36 Capitulo 3: Fungies TO RenderPageWithSetupsAndTeardowns, verificamos se a pagina é de teste, se for; incluimos SetUps e TearDowns. Em ambos os casos, exibimos a pagina em HTML Se uma fungdo faz apenas aqueles passos em um nivel abaixo do nome da fungdo, entio ela esté fazendo uma sé coisa, Apesar de tudo, 0 motivo de criarmos fungdo é para decompor um conceito maior (em outras palavras. o nome da fungo) em uma série de passos no proximo nivel de abstracao, Deve estar claro que a Listagem 3.1 contém passos em muitos niveis diferentes de . Portanto, obviamente ela faz mais de uma coisa. Mesmo a Listagem 3.2 possui dois niveis de abstracao , como comprovado pela nossa capacidade de redugdo. Mas ficaria muito dificil reduzir a Listagem 3.3 de modo significativo, Poderiamos colocar a instrug3o if numa fungao chamada includeSetupsandTeardowns! fTest Page, mas isso simplesmente reformula 0 eddigo, sem modificar 0 nivel de. Portanto, outra forma de saber se uma fungdo faz mais de “uma coisa” é se vocé pode extrair outra fungao dela a partir de seu nome que nao seja apenas uma reformulacio de sua implementagao (G34). Secées Dentro de Funcgées Veja a Listagem 4.7 na pagina 71. Note que a fungdo generatePrimes esti dividida em segdes, como declaragées, inicializacdes ¢ selecdo. Esse ¢ um indicio ébvio de estar fazendo mais de uma coisa. Nao da para, de forma significativa, dividir em segdes as fungdes que fazem apenas uma coisa. Um Nivel de Abstrac¢ao por Funcao A fim de confirmar se nossas fungdes fazem s6 “uma coisa”, Precisamos verificar se todas as instrugdes dentro da fungao estdio no mesmo nivel de abstragdo. E facil ver como a Listagem 3.1 viola essa regra. Ha outros conceitos 14 que estao em um nivel de bem alto, como 0 get Html (}: outros que esto em um nivel intermediario, como String pagePathName = PathParser render (pagePath); e outros que esto em um nivel consideravelmente baixo, como id (™\n"). jos niveis de dentro de uma fungio sempre geram confusao. Os leitores podem nao conseguir dizer se uma expresso determinada é um conceito essencial ou um mero detalhe. Pior, como janelas quebradas, uma vez misturados os detalhes aos conceitos, mais ¢ mais detalhes tendem a se agregar dentro da fungao. -ap) Estrutura Switeh Ler o Cédigo de Cima para Baixo: Regra Decrescente Queremos que o cédigo seja lido de cima para baixo, como uma narrativa*. Desejamos que cada fungao seja seguida pelas outras no proximo nivel de de modo que possamos ler 0 programa descendo um nivel de de cada vez conforme percorremos a lista de fungécs. Chamamos isso de Regra Decrescente. Em outras palavras, queremos poder ler 0 programa como se fosse uma série de parigrafos 7O, cada um descrevendo o nivel atual de e fazendo referéncia aos paragrafos TO consecutivos no proximo nivel abaixo. Para incluir SetUps e TearDowns, incluimos os primeiros. depois 0 contetido da pagina de teste e, entdo, adicionamos os segundos. Para incluir Setlips, adicionamos 0 suite setup, se este for uma colecdo, incluimos 0 setup normal. Para incluir o suite setup, buscamos na hierarquia acima a pagina “SuiteSetUp” ¢ adicionamos uma instrugdo de incluséo com 0 caminho aquela pagina. Para procurar na hierarquia acima ‘Acaba sendo muito dificil para os programadores aprenderem a seguir essa regra ¢ criar fungdes que fiquem em apenas um nivel de . Mas aprender esse truque € também muito importante, pois cle ¢ 0 segredo para manter as fungdes curtas € garantir que fagam apenas “uma coisa”. Fazer com que a leitura do cédigo possa ser feita de cima para baixo como uma série de paragrafos TO é uma técnica eficiente para manter o nivel de consistente. ‘Vejaa listagem 3.7 no final deste capitulo. Ela mostra toda a fungo testableHtml refatorada de acordo com os principios descrito aqui, Note como cada fungao indica a seguinte ¢ como cada uma mantém um nivel consistente de Estrutura Switch E dificil criar uma estrutura switch pequena’, pois mesmo uma com apenas dois cases ¢ maior do que eu gostaria que fosse um bloco ou uma fungao. Tambem ¢ dificil construir uma que fala apenas uma coisa. Por padrao, as estruturas switch sempre fazem N coisas. Infelizmente, nem sempre conseguimos evitar 0 uso do switch, mas podemos gos certificar se cada um est em uma classe de baixo nivel e nunca é repetido. Para isso, usamos 0 polimorfismo. Veja a Listagem 3.4. Ela mostra apenas uma das operagées que podem depender do tipo de funcionario (employee, em inglés). 37 38 Capitulo 3: Fungies Listagem 3-4 Payroll.java public Money calculatePay {Employee e} Invalidemployeetype teh (e.type) case CONMISSTONE! ulateConmi'ssionedPay (e) ; ceHourlyPay (¢ return calculateSalariedPay le); ult: row niew TnvalidmployeeType(e. type): Esta fungao tem varios problemas. Primeiro, ela ¢ grande, e quando se adiciona novos tipos de funcionarios ela crescera mais ainda. Segundo, obviamente ela faz mais de uma coisa. Terceiro, ala viola o Principio da Responsabilidade Unica’ (SRP, sigla em inglés) por haver mais de um motivo para altera-la, Quarto, ela viola o Principio de Aberto-Fechado* (OCP, sigla em inglés), pois precisa ser modificada sempre que novos tipos forem adicionados. Mas, provavelmente, 0 pior problema com essa fungao € a quantidade ilimitada de outras fungdes que ter’o a mesma estrutura. Por exemplo, poderiamos ter isPayday(Employee e, Date date) ou deliverPay(Employee e, Money pay) ou um outro grupo. Todas teriam a mesma estrutura deletéria. ‘A solugao (veja a Listagem 3.5) é inserir a estrutura switch no fundo de uma ABSTRACT FACTORY’ e jamais deixar que alguém a veja. A factory usara 0 switch para criar instancias apropriadas derivadas de Employee, e as fungdes, como calculateray, isPayday ¢ deliverPay, serdo enviadas de forma poliférmica através da interface Employee. Minha regra geral para estruturas switch é que sio aceitdveis se aparecerem apenas uma vez, como para a criagio de objetos poliférmicos, ¢ estiverem escondidas atras de uma relagao de heranga de modo que 0 resto do sistema nao possa enxergd-la [G23]. E claro que cada caso é um caso e haverd vezes que nao respeitarei uma ou mais partes dessa regra. Listagem 3-5 * Employee e Factory public abstract class Employee { public abstract boolean isPayday(); public abstract Money calculatePay(); public abstract void deliverPay(Money pay); } "7 a ngpden Wikipsiinconp/Wiki’Singlé ‘reapanaibility principle ‘Use Nomes Descritivos 39 Use Nomes Descritivos Na Listagem 3.7, eu mudei 0 nome do exemplo de nossa fun¢fo testableHtml para SetupTeardownIncluder.render, que ¢ bem melhor, pois descreve 0 que a fungao faz. Também dei a cada método privado nomes igualmente descritivos, como isTestable ou includeSetupandTeardownrages. E dificil superestimar o valor de bons nomes. Lembre-se do principio de Ward: “Voce sabe que esté criando um codigo limpo quando cada rotina que vocé lé é como vocé esperava”’. Metade do esforgo para satisfazer esse principio ¢ escolher bons nomes para fungdes pequenas que fazem apenas uma coisa. Quando menor ¢ mais centralizada for a fungio, mais facil ser pensar em um nome descritivo. Nao tenha medo de criar nomes extensos, pois eles sio melhores do que um pequeno € enigmatico. Um nome longo ¢ descritivo é melhor do que um comentario extenso e descritivo. Use uma convengio de nomenclatura que possibilite uma ficil leitura de nomes de fungdes com varias palavras e, enti, use estas para dar a fungao um nome que explique o que ela faz. Nao se preocupe com o tempo ao escolher um nome. Na verdade, vocé deve tentar varios nomes e, entdo, ler o cédigo com cada um deles. IDEs modernas, como Eclipse ou IntelliJ, facilita a troca de nomes. Utilize uma dessas IDEs ¢ experimente diversos nomes até encontrar um que seja bem descritivo. Sclecionar nomes descritivos esclareceré 0 modelo do médulo em sua mente ¢ Ihe ajudara a melhora-lo. E comum que ao buscar nomes adequados resulte numa boa reestruturagao do cédigo. Seja consistente nos nomes. Use as mesmas frases, substantivos e verbos nosnomes de fungdes de seu médulo. Considere, por exemplo, os nomes includeSetup-AndTeardownvages, includeSetupPages, includeSuitesetupPage e includeSetupPage. A fraseologia nesses nomes permite uma sequéncia de facil dedugdo. Na verdade, se cu lhe mostrasse apenas a série acima, vocé se perguntaria: “O que aconteceu com includeteardownPages, includeSuiteTeardownPage e includeTeardownPage?”, como isso é “... como 0 que vocé esperava?”. ParAmetros de Fungées ‘A quantidade ideal de pardmetros para uma fungao é zero (nulo). Depois ver um (ménade), seguido de dois (diade). Sempre que possivel devem-se evitar trés pardimetros (triade). Para mais de trés deve-se ter um motivo muito especial (poliade) — mesmo assim nao devem ser usados. Parametros sio complicados. Eles requerem bastante conceito. E por isso que me livrei de quase todos no exemplo. Considere, por exemplo, © StringBuffer. Poderiamos té-lo passado como parimetro em vez de instancié-lo como uma variavel, mas entdo nossos leitores teriam de interpreta-lo sempre que o vissem. Ao ler a 40 Capitulo 3: Fungoes est6ria contada por pelo modulo, fica mais facil entender includeSetupPage() do que includeSetupPageInto (newPage-Content). O pardmetro nfo esta no nivel de que o nome fungao, forgando-lhe reconhecer de um detalhe (ou seja, 0 StringBuffer) que nao seja importante particularmente naquele momento. Os parametros so mais dificeis ainda a partir de um ponto de vista de testes. Imagine a dificuldade de escrever todos os casos de teste para se certificar de que todas as varias combinagdes de parimetros funcionem adequadamente. Se nao houver parimetros, essa tarefa € simples. Se houver um, nao € tao dificil assim. Com dois, a situagdo fica um pouco mais desafiadora, Com mais de dois, pode ser desencorajador testar cada combinagao de valores apropriados. Os parémetros de saida sio ainda mais dificeis de entender do que os de entrada. Quando lemos uma fungao, estamos acostumados ideia de informagées entrando na fungao através de parametros e saindo através do valor retornado. Geralmente nao esperamos dados saindo através de parametros. Portanto, parametros de saida costumam nos deixar surpresos ¢ fazer com que leiamos novamente. Um parametro de entrada é a melhor coisa depois de zero parmetro. E facil entender SetupTeardown-Includer.render (pageData). Esta ébvio que renderizemos os dados no objeto pageData. Formas Ménades Comuns ‘Ha duas razées bastante comuns para se passar um Unico pardmetro a uma fungao. Vocé pode estar fazendo uma pergunta sobre aquele parametro, como em boolean fileExists("MyFile”). Ou vocé pode trabalhar naquele parametro, transformando-o em outra coisa ¢ retornando-o. Por exemplo, InputStream fileOpen(*MyFile”) transforma a string do nome de um arquivo em um valor retornado por InputStream. Sdo esses dois usos que os leitores esperam ver em uma fungo. Vocé deve escolher nomes que tomem clara a disting’io, e sempre use duas formas em um contexto consistente. (Veja a seguir Separagdo comando-consulta). Uma forma menos comum mas ainda bastante {itil de um parametro para uma fungdo é um evento. Nesta forma, ha um pardmetro de entrada, mas nenhum de saida. O programa em si serve para interpretar a chamada da fungao como um evento, ¢ usar o parametro para alterar o estado do sistema, por exemplo, void passwordAttemptFailedNtimes(int attempts). Use esse tipo com cautela. Deve ficar claro para 0 leitor que se trata de um evento. Escolha os nomes ¢ os contextos com aten¢ao, Tente evitar funcdes mOénades que nao sigam essas formas, por exemplo, void include SetupPageInto(StringBuffer pageText). Usar um parametro de saida em vez de um valor de retorno para uma modificagiio fica confiiso. Se uma fungdo vai transformar seu parametro de entrada, a alteragao deve aparecer como o valor retornado, De fato, StringBuffer transform(StringBuffer in) ¢ melhor do que void! transform-(stringBuffer out), mesmo que a implementagdo do primeiro simplesmente retorne 0 parimetro de entrada. Pelo menos ele ainda segue 0 formato de uma modificacao. Pardmetros Légicos Esses parmetros so feios. Passar um booleano para uma fungo certamente € uma pratica horrivel, pois ele complica imediatamente a assinatura do método, mostrando explicitamente que a fungio faz mais de uma coisa. Ela faz uma coisa se 0 valor for verdadeiro, e outra se for falso! Use Parametros de Fungées 41 Na Listagem 3.7, nao tinhamos alternativa, pois os chamadores j4 estavam passando aquela flag (valor booleano) como parimetro, e¢ eu queria limitar 0 escopo da refatoragio 4 fungao e para baixo. Mesmo assim, a chamada do método render (true) é muito confusa para um leitor simples. Analisar a chamada e visualizar render (boolean isSuite) ajuda um pouco, mas nem tanto. Deveriamos dividir a fungdo em duas: renderForSuite() e¢ renderForSingleTest (}. Fungées Diades Uma fungao com dois parametros € mais dificil de entender do que uma com um (ménade), Por exemplo, ¢ mais facil compreender writeField(name) do que writeField(output- Stream, name)", Emborao significado de ambas esteja claro, a primeira apresenta seu propésito explicitamente quando a lemos. A segunda requer uma pequena pausa até aprenderios a ignorar 0 primeiro parametro. E isso, é claro, acaba resultando em problemas, pois nunca devemos ignorar qualquer parte do cédigo. O local que ignoramos é justamente aonde sc esconderio os bugs. Hi casos, é claro, em que dois parametros sio necessérios como, por exemplo, em Point p = new Point (0,0). Os pontos de eixos cartesianos naturalmente recebem dois parametros. De fato, ficariamos surpresos se vissemos new Point (0). Entretanto, os dois parémetros neste caso so componentes de um iinico valor! Enquanto que output-Stream e name nfo sio partes de um mesmo valor. Mesmo fungdes diades ébvias, como assertEquals(expected, actual), sao problematicas. Quantas vezes vocé ja colocou actual onde deveria ser expected? Os dois parametros nao possuem uma ordem pré-determinada natural. A ordem expected, actual é uma conven¢%o que requer pratica para assimilé-la Diades no sao ruins, e vocé certamente ter de usd-las, Entretanto, deve-se estar ciente de que havera um prego a pagar e, portanto, deve-se pensar em tirar proveito dos mecanismos disponiveis a vocé para converté-los em ménades. Por exemplo. vocé poderia tornar o método writeField um membro de outputstream de modo que pudesse dizer outputStreamwritePield (name); tornar outputStream uma varidvel membro da classe em uso de modo que nao precisasse passé-lo por pardimetro; ou extrair uma nova classe, como Pieldwri ter, que receba o outputStream em seu construtor e possua um método write. Triades Fungdes que recebem trés parametros so consideravelmente mais dificeis de entender do que as diades. A questo de ordenagdo, pausa e ignoragdo afresentam mais do que 0 dobro de dificuldade. Sugiro que vocé pense bastante antes de criar uma triade. Por exemplo, considere a sobrecarga comum de assertEquals que recebe trés parametros: assertEquals(message, expected, actual). Quantas vezes vocé precisou ler o pardmetro message e deduzir o que ele carrega? Muitas vezes j4 me deparei com essa triade em particular e tive de fazer uma pausa. Na verdade, toda vez que a vejo, tenho de ler novamente e, entiio, a ignoro. Riad lai sic ili ns Tie ni ind Sis Weebl a edn Wl sin uc Use Pardametros de Fungdes a formato imbuimos os nomes dos parametros no nome da fungao. Por exemplo, pode ser melhor escrever assertEquals do que assertExpectedZqualsactual (expected, actual),o que resolveria o problema de ter de lembrar a ordem dos parametros, Evite Efeitos Colaterai: Efeitos colaterais so mentiras. Sua fungao promete fazer apenas uma coisa, mas ela também faz outras coisas escondida. As vezes, cla fara alteragdes inesperadas nas varidveis de sua propria classe. As vezes, ela adicionara as variaveis aos parametros passados 4 fungao ou as globais do sistema. Em ambos os casos elas si “verdades” enganosas € prejudiciais, que geralmente resultam em acoplamentos temporarios estranhos e dependéncias. Considere, por exemplo, a fungao aparentemente inofensiva na Listagem 3.6. Ela usa um algoritmo padrao para comparar um userName (nome de usuario) a um password (senha). Blaretomatrue (verdadeiro) se forem iguais,e false {falso) caso contrario. Mas ha também um efeito colateral. Consegue identificd-lo Listagem 3-6 UserValidator.java ographer cryptographer; public boolean checkPssword(String use: User user = UserGateway. find2yName (user iuser {= User.NULL) ( |. String codedPhrase = user. getPhraseEncodedByPaasword() : String phrase = cryptographer. decrypt (codedehrase, password) : i£ ("Valid Password" equals phrase) | session return ame, String password) { lame} ; Lizei); return false; © efeito colateral ¢ a chamada ao Session.initialize(), é claro. A fungio checkPassword, segundo seu nome, diz que verifica a senka. O nome nao indica que ela inicializa a sesso, Portanto, um chamador que acredita no que diz 0 nome da fungio corre o risco de apagar os dados da sesso existente quando ele decidir autenticar do usuari Esse efeito colateral cria um acoplamento temporirio. Isto é, checkPassword s6 poder ser chamado em determinadas horas (em outras palavras, quando for seguro inicializar a sessiio). Se for chamado fora de ordem, sem querer, os dados da sessiio poderdo ser perdidos. Os acoplamentos tempordrios so confusos, especialmente quando so um efeito colateral. Se for preciso esse tipo 4 Capitulo 3: Fungées de acoplamento, é preciso deixar claro no nome da fung&o. Neste caso, poderiamos renomear a fungdo para checkPasswordAndInitializeSession, embora isso certamente violaria 0 “fazer apenas uma tinica coisa”. ParAmetros de Saida Os pardmetros so comumente interpretados como entradas de uma fungao. Se j4 usa o programa hd alguns anos, estou certo de que vocé j teve de voltar ¢ ler novamente um parametro que era, nna verdade, de saida, e nao de entrada. Por exemplo: appendFooter(s); Essa fungao anexa s como rodapé (Footer, em inglés) em algo? Ou anexa um rodapé as? s ¢ uma entrada ou uma saida? Nao precisa olhar muito a assinatura da fungao para ver: public void appendFooter (StringBuffer report) Isso esclarece a questo, mas a custa da verificagdo da declaragao da fungaio. Qualquer coisa que Ihe force a verificar a assinatura da fun¢ao ¢ equivalente a uma relida. Isso é uma interrupgao do raciocinio e deve ser evitado. Antes do surgimento da programagao orientada a objeto, as vezes era preciso ter parimetros de saida. Entretanto, grande parte dessa necessidade sumiu nas linguagens OO, pois seu proposito éservir como um parametro de saida. Em outras palavras, seria melhor invocar appendFooter como: report.appendFooter |); De modo geral, devem-se evitar parametros de saida. Caso sua fungao precise alterar o estado de algo, faga-a mudar 0 estado do objeto que a pertence. Separacio comando-consulta ‘As fungdes devem fazer ou responder algo, mas nilo ambos. Sua funcdo ou altera o estado de um objeto ou retorna informagdes sobre ele. Efetuar as duas tarefas costuma gerat confusio Considere, por exemplo, a funcdo abaixo: public boolean set(String attribute, String value); Esta fungao define o valor de um dado atributo ¢ retorna tru (verdadeiro) se obtiver éxito e false (£also) se tal atributo nao existir, Isso leva a instrugdes estranhas como: if (set(“username’, “unclebob"))... Imagine isso pelo ponto de vista do leitor. O que isso significa? Esté perguntando se o atributo “username” anteriormente recebeu 0 valor “unclebob”? Ou se “username” obteve éxito ao receber 0 valor “unclebob”? E dificil adivinhar baseando-se na chamada, pois nao est claro se a palavra “set” um verbo ou um adjetivo. Prefira exeegées a retorno de cédigos de erro 45 CO intuito do autor era que set fosse um verbo, mas no contexto da estrutura if, parece um adjetivo. Portanto, a instrugdo lé-se “Se o atributo username anteriormente recebeu o valor unclebob” ¢ niio “atribua o valor unclebob ao atributo username, ¢ se isso funcionar, entao...”. Paderiamos tentar resolver isso renomeando a funeo set para setandcheckifExists, mas nao ajudaria muito para a legibilidade da estrutura i. if (attributeExists(*username”)) [( setAttribute(“username", “unclebob"); Prefira excegdes a retorno de cédigos de erro Fazer fungdes retornarem cédigos de erros é uma leve violagdo da separagdo comando-consulta, pois os comandos sao usados como expressdes de comparagao em estruturas if. E_OK) if (deletePage (page) problema gerado aqui nao € a confusio verbo/adjetivo, mas sim a criagdo de estruturas aninhadas. ‘Ao retornar um cédigo de erro, vocé cria um problema para o chamador, que deverd lidar imediatamente com 0 erro. if (deletePage (page) E_OK) { if (registry.deleteReference(page.name) E_OK) { if (configkeys.deleteKey(page.name.makeKey(}) E_OK){ logger.log(*pagina excluida"); . } else { logger.log(*configkey nao foi excluida”); } } else { logger.logi"deleteReference nao fo1 excluido do registro"); } } else { logger.log("a excluséo falhou"); return B_ERROR; } Por outro lado, se vocé usar excegdes em vez de retornar cédigos de erros, entdo 0 cédigo de tratamento de erro poderd ficar separado do cédigo e ser simplificado: 4 try deletePage(page); registry.deleteReference(page.name); configkeys .deleteKey(page.name.makeKey()}; ) catch (Exception e) { logger. log(e.getMessage()); 3 46 Capitulo 3: Fungées Extraia os blocos try/catch Esses blocos niio tém 0 direito de serem feios. Eles confundem a estrutura do cédigo ¢ misturam © tratamento de erro com o processamento normal do cédigo. Portanto, é melhor colocar as estruturas try ¢ catch em suas proprias fungdes. public void delete(Page page) { try { deletePageAndal1References (page); t catch (Exception e] { logError(ej: y d private void deletePageAndAllReferences(Page page) throws Exception { delet ePage(page); registry.deleteReference (pagename); configkeys .deleteKey (page-name.makeKey()); } private void logError(Exception e) ( logger. log(e.getMessage()); } A fungao delete acima sé faz tratamento de erro. E é facil entendé-la e seguir adiante, A fungdo deletePageAndAllReferences $6 trata de processos que excluem toda uma pagina. Pode-se ignorar o tratamento de erro. Isso oferece uma boa separagao que facilita a compreensio e alteragdo do cédigo. Tratamento de erro é uma coisa s6 As fungées devem fazer uma coisa s6, Tratamento de erro é uma coisa sé. Portanto, uma fungao que trata de erros nao deve fazer mais nada. Isso implica (como no exemplo acima) que a palavra try esta dentro de uma fungdo e deve ser a primeira instrugao e nada mais deve vir apés os blocos catchy finally. Error.java, © chamariz A dependéncia Retomar cédigos de erro costuma implicar que ha classes ou enum nos quais esto definidos todos os cédigos de erro. = public enum Error ( OK, INVALID, NO_SUCH, LOCKED, QUT_OF_RESOURCES, WAITING _FOR_EVEN?; Programacao estruturada 47 Classes como esta so chamarizes @ dependéncia, muitas outras classes devem importa-las ¢ usé-las, Portanto, quando o enum da classe Exror enum, é preciso recompilar todas as outras classes ¢ redistribui-las". Isso coloca uma pressio negativa na classe Error. Os programadores nao querem adicionar novos erros porque senao eles teriam de compilar e distribuir tudo novamente. Por isso, eles reutilizam cddigos de erros antigos em vez de adicionar novos. Quando se usam excegdes em vez de cédigos de erro, as novas excegdes sdo derivadas da classe de excegdes. Podem-se adiciona-las sem ter de recompilar ou redistribuir' Evite repeti¢4o« Leia novamente com ateng4o a Listagem 3.1 ¢ notara que ha um algoritmo que se repete quatro vezes em quatro casos: SetUp, SuiteSetUp, TearDown ¢ SuiteTearDown. Nao€ facil perceber essa duplicagao, pois as quatro instancias esto misturadas com outros eddigos ¢ nao esto uniformemente repetidas. Mesmo assim, a duplicagao é um problema, pois ela amontoa 0 cédigo e serdo necessarias quatro modificagdes se 0 algoritmo mudar. Além de serem quatro oportunidades para a omissdo de um erro. ‘Sanou-se essa duplicagao através do método include na Listagem 3.7. Leiaestecédigonovamente ‘note como a legibilidade do médulo inteiro foi melhorada com a retirada de tais repetigSes. ‘A duplicagdo pode ser a raiz de todo o mal no software. Muitos prineipios e préticas t@m sido eriados com a finalidade de control-la ou elimind-la. Considere, por exemplo, que todas as regras de normalizagao de bando de dados de Ted Codd servem para eliminar duplicagao" de dados. Considere também como a programagao orientada a objeto serve para centralizar 0 cédigo em classes-base que seriam outrora redundantes. Programagao estruturada, Programagao Orientada a Aspecto e Programagao Orientada a Componentes sao todas, em parte, estratégias para climinar duplicagao de cédigo. Parece que desde a invengao da sub-rotina, inovagdes no desenvolvimento de software tém sido uma tentativa continua para eliminar a duplicagio de nossos cédigos-fonte. Programagio estruturada Alguns programadores seguem as regras programagdo estruturada de Edsger Dijkstra’, que disse que cada fungao ¢ bloco dentro de uma fungao deve fer uma entrada e uma saida. Cumprir essas regras significa que deveria haver apenas uma instrugio return na fungdo, nenhum break ou continue num loop e jamais um goto. Enquanto somos soliddrios com os objetives e disciplinas da programagao estruturada, tais regras oferecem pouca vantagem quando as fungdes so muito pequenas. Apenas em fungdes maiores tais regras proporcionam beneficios significativos. Portanto, se vocé mantiver suas fungdes pequenas, entdo as varias instrugdes return, break ou continue casuais no trardo problemas e poderao ser até mesmo mais expressivas do que 71 dlnsantaecqaue eumrmnreon ame eedasiamn oo livenr da eacissidleciis @ dis obieisibisbilih Gavia ennciatnelies, aiwndiia te cilia sircwhlleiel. 48 Capitulo 3: Fungdes a simples regra de uma entrada ¢ uma saida. Por outro lado, 0 goto s6 faz sentido em fungdes grandes, portanto ele deve-se eviti-lo. Como escrever fungées como essa? Criar um software ¢ como qualquer outro tipo de escrita, Ao escrever um artigo, vocé primeiro coloca seus pensamentos no papel e depois os organiza de modo que fiquem ficeis de ler. O primeiro rascunho pode ficar desastroso e desorganizado, entdo vocé o apare, reestrutura ¢ refina até que ele fique como vocé deseja. Quando escrevo fungdes, elas comegam longas e complexas; hé muitas endentagdes € loops aninhados; possuem longas listas de parametros; os nomes s&o arbitrdrios; e hd duplicagao de cédigo. Mas eu também tenho uma colegio de testes de unidade que analisam cada uma dessas linhas desorganizadas do cédigo Sendo assim, eu organizo e refino 0 codigo, divido fungdes, troco os nomes, elimino a duplicagao, reduzo os métodos e os reorganizo, As vezes, desmonto classes inteiras, tudo com ‘08 testes em execugio. No final, minhas fungGes seguem as regras que citei neste capitulo. Nao as aplico desde o inicio, Acho que isso nao seja possivel. Conclusao Cada sistema é construido a partir de uma linguagem especifica a um dominio desenvolvida por programadores para descrever o sistema. As fungdes so os verbos dessa linguagem, e classes os substantivos. Isso no um tipo de retomada da antiga nogo de que substantivos ¢ verbos nos requerimentos de um documento sejam os primeiros palpites das classes ¢ funydes de um sistema, Mas sim uma verdade muito mais antiga. A arte de programar é, ¢ sempre foi, a arte do projeto de linguagem. Programadores experientes veem os sistemas como histérias a serem contadas em vez de programas a serem escritos. Eles usam os recursos da linguagem de programago que escolhem para construir uma linguagem muito mais rica e expressiva do que a usada para contar a estéria, Parte da linguagem especifica a um dominio é a hierarquia de fungdes que descreve todas as agdes que ocorrem dentro daquele sistema, Em um ato engenhoso, escrevem-se essas fungdes para usar a mesma linguagem especifica a um dominio que eles criaram para contar sua propria parte da histéria. Este capitulo falou sobre a mecénica de se escrever bem fungdes. Se seguir as regras aqui descritas, suas fangdes serao curtas, bem nomeadas ¢ bem organizadas. Mas jamais se esqueca de que scu objetivo verdadeiro € contar a historia do sistema, e que as fungdes que vocé escrever precisam estar em perfeita sincronia ¢ formar uma linguagem clara ¢ precisa para Ihe ajudar na narragao. SetupTeardownIncluder SetupTeardownIncluder Listagem 3-7 SetupTeardownIncluder. java vackage fitnesse.htnl; import fitnesse. responders. run.SuiteResponder; import fitnesse.wiki.*; public class SetupTeardownncluder { private PageData paueDate; private boolean isSuite; private Wikifage testPage: private StringBuffer newPageContent; private PageCrawler pageCrawler; public static String render(PageData pageData) throws Exception retum render(pageData, false) ; } pul lic static String throws Exception { retum new Setupfeardownincluder (pageData) .render (issuite) ; } ender (PageData pageData, boolean isSuite) private SetupTeardownIncluder(PageData pageDatei this.pageData = pageDa testPage = pageData.aetnikiPage (i; pageCrawler = testPage.getPageCrawler i); newPageContent = new StringBuffer(); vate String render (boolean issuite) throws Exception ( this.isSuite = issuite; if (isTestPage()) includeSetupandTeardownPages (} return pageData.getitml (i; y private boolean isTestPage() throws Exception { return pagefara-hasAttribute(*Test* private void includesetupandteardownPages() throws includesetupPages (3 includePageContent () ; includeTeardowaPages |); updatePageContent () ; 50 Capitulo 3: Fungdes Listagem 3-7 (continuagao SetupTeardownIncluder. java ows Exception { private void includesecuy if (issuite) ineludesuitesecuppage() ; includeSetupPage(): } Pages (| tI private void includeSuitesetupPage!) throws Exception ¢ include SuiteResponder. SUITE_SETUE NAME, *-setup"); } private void includeSetupPage(} throws Exception { include(*setUp", ‘-setup"|? } private void includePagecontent () throws Exception { newPageContent append [pageData.getContent ()); a private void includeTeardownPages() throws kxception { includeTeardownPage |) : if (isSuite) includeSuiteTeargownPage(!; vate void includeTeardownPage(} throws Exception { include("TearDewn", "-teardown") ; private void includeSuiteTeardownPage() throws Exception { include (SuiteResponder.SUITE_TEARDOWN_NAME, *-teardown"): } private void updatePageContent (} throws Exception [ pageData.setContent (newPageContent .teString(]) ; private void include(Scring pageName, String arg) throws Exception { WikiPage inheritedPage = findInheritedPage (pageNamei ; if (inheritedPage !> null) { String pagePathName = get PathNameForPage (inheritedPage) ; buildincludeDirective(pagePathName, arg); } ) 2 private WikiPage findInheritedPage|String pageNane) throws Exception [ return PageCrawlerInp] get InkeritedPage (pageName, testPage); private String getPathNameForPage(WikiPage page) throws Exception { WikiPagePath pagePath = pageCrawler.getFull Path (page) ; return PathParser. render (pagePath) ; } private void buildincludeDirectiveiString pagePathName, String arg) ( newPageContent append(*\ntinclude *} 4 Comentarios “Nao insira comentarios num eddigo ruim, reescreva-o”. —Brian W. Kernighan e’. J. Plaugher' Nada pode ser t4o util quanto um comentério bem colocado. Nada consegue amontoar um médulo mais do que comentarios dogmaticos e supérfluos. Nada pode ser tao prejudicial quanto um velho comentario mal feito que dissemina mentiras e informagdes incorretas, Comentirios nao so como a Lista de Schindler. Nao sao o “bom puro”. De fato, eles so, no maximo, um mal necessério. Se nossas linguagens de programagao fossem expressivas 0 suficiente ou se tivéssemos 0 talento para manipular com destreza tais linguagens de modo a expressar nossa intengo, nao precisarfamos de muitos comentarios, qui¢é nenhum. 54 Capitulo 4: Comentarios x © uso adequado de comentarios é compensar nosso fracasso em nos expressar no cédigo. Observe que usei a palavra fracasso. E € isso que eu quis dizer. Comentarios siio sempre fracassos. Devemos usi-los porque nem sempre encontramos uma forma de nos expressar sem eles, mas seu uso nao € motivo para comemoracao. Entdo, quando vocé estiver numa situago na qual precise criar um comentirio, pense bem € veja se no hé como se expressar através do cédigo em si. Toda vez que vocé fizer isso, dé em si mesmo um tapinha de aprovacdo nas costas. Toda vez que vocé escrever um comentario, faga uma careta ¢ sinta o fracasso de sua capacidade de expresso. Por que sou n&o gosto de comentirios? Porque eles mentem. Nem sempre, ¢ nao intencionalmente, mas é muito comum. Quanto mais antigo um comentirio for e quanto mais longe estiver do eddigo o qual ele descreve, mais provavel sera que esteja errado. © motivo € simples. Nao € realistico que programadores consigam manté-los atualizados. Cédigos mudame evoluem, Movem-se blocos para ld e para cd, que se bifurcam e se reproduzem ese unem novamente, formando monstros gigantescos. Infelizmente, os comentirios nem sempre os seguem nem sempre ¢ possivel. E, muito frequentemente, os comentarios ficam longe do eédigo © qual descrevem e se tornam dizeres érfios com uma exatidio cada vez menor. Por exemplo. olhe © que aconteceu com o comentario abaixo ¢ a linha que ele procurava descrever MockRequest request; private final String HTTP_DATE_REGEXP = *([SMTWF] [a-z] (2)\\.\\s [0-91{2}\5 [JFMASOND] [a-z] {2)\\s"+ (0-91 (4) \\s [0-9] {2}\\: [0-9] (2}\\: [0-9] (2)\\SGMP"s private Response response; private PitNesseContext context: private FileResponder responder; private Locale saveLocale; i] Exemplo: “Tue, 02 Apr 2003 22:18:49 GMT” Outras instdncias de variéveis que provavelmente foram adicionadas posteriormente ficaram entre a constante H'T'TP_DATE_REGEXP e seu comentario descritivo. F possivel dizer que os programadores deveriam ser disciplinados o bastante para manter 0s comentarios em um elevado estado de atualizagio, relevancia ¢ precisdo. Concordo que deveriam. Mas eu preferiria que essa energia fosse direcionada para tornar 0 cédigo tao claro ¢ descritive que de inicio nem se precisaria de comentarios. Comentarios imprecisos so muito piores do que nenhum, Eles enganam ¢ iludem; deixam expectativas que jamais scrdo cumpridas; citam regras antigas que nao precisariam mais, ou no deveriam, ser seguidas. ‘ S6 se pode encontrar a verdade em um lugar: no cédigo. $6 ele pode realmente the dizer o que ele faz. Ele é a tnica fonte de informagdes verdadeiramente precisas. Entretanto, embora as vezes comentarios sejam necessarios, gastariamos energia consideravel para minimiza-los. Comentarios Compensam um Codigo Ruim Uma das motivacdes mais comuns para criar comentarios é um cédigo ruim. Construimos um modulo ¢ sabemos que esta confuso e desorganizado, Estamos cientes da bagunga. Nos mesmos dizemos “Oh, é melhor inserir um comentdrio!”. Nao! E melhor limpa-lo. Comentarios Bons Cédigos claros e expressivos com poucos comentarios so de longe supcriores aumamontoado € complexo com muitos comentirios. Ao invés de gastar seu tempo criando comentarios para explicar a bagunga que vocé fez, use-o para limpar essa zona. Explique-se no cédigo Certamente hé vezes em que no ¢ possivel se expressar direito no cédigo. Infelizmente, devido a isso, muitos programadores assumiram que o codigo raramente ¢, se ¢ que possa ser, um bom meio para se explicar. Evidentemente isso é falso. O que vocé preferiria ver? Isso: // Verifica se 0 funcionario tem direito a todos os beneficios if ((employee.flags & HOURLY_FLAG) && (employee.age > 65)) Ou isso? if (employee.isEligibleForFullsenefits()) $6 € preciso alguns segundos de pensamento para explicar a maioria de sua intengao no cédigo. Fm muitos casos, é simplesmente uma questao de criar uma fungao cujo nome diga a mesma coisa que vocé deseja colocar no comentiirio. Comentarios Bons Certos comentirios so necessirios ou benéficos. Veremos alguns que considero valerem os bits que consumem. Tenha em mente, contudo, que 0 unico comentario verdadeiramente bom & aquele em que vocé encontrou uma forma para nfo escrevé-lo. Comentarios Legais [As vezes, nossos padrées de programagao corporativa nos forgam a escrever certos comentarios por questdes Iegais. Por exemplo, frases sobre direitos autorais ¢ autoria séo informagdes necessiitias ¢ légicas para se colocar no inicio de um arquivo fonte. Por exemplo, abaixo esté 0 comentario padrao de cabecalho que colocamos no inicio de todo arquivo fonte do FitNesse. Fico feliz em dizer que nossa IDE evita a unido automética desse comentario para que nao fique aglomerado. // Direitos autorais (C) 2003,2004,2005 por Object Mentor,*Inc. Todos os direitos reservados. // Distribuido sob os termos da versdo 2 ou posterior da Licenca Publica Geral da GNU. Comentarios como esse nao devem ser contratos ou tomos legais. Onde for possivel, faga referéncia a uma licenga padrao ou outro documento externo em vez de colocar todos os termos e condicdes no mesmo comentario. 56 y/ Capitulo 4: Comentarios Comentarios Informativos As vezes é pritico fornecer informages basicas em um comentério. Por exemplo, considere 0 comentario abaixo que explica o valor retornado de um método abstrato: // Retorna uma instancia do Responder sendo testado. protected abstract Responder responderinstance(); Um comentirio como este pode ser titi] as vezes, mas, sempre que possivel, é melhor usar 0 nome da fungao para transmitir a informacao. Por exemplo, neste caso, 0 comentario ficaria redundante se trocdssemos 0 nome da fungdo: responderBeingTested. Assim ficaria um pouco melhor: / formato igual a kk:mm:ss EEE, MMM dd, aaaa Pattern timeMatcher = Pattern.compile("\\d*:\\d*\\d* \w, \Wwe \d*, Wd"); Neste caso, 0 comentario nos permite saber que a expressdo regular deve combinar com uma hora e data formatadas com a fungao SimpleDateFormat format usando a string especifica com 0 formato. Mesmo assim, teria ficado melhor e mais claro se esse codigo tivesse sido colocado em uma classe especial para converter os formatos de datas ¢ horas, Ent&o, 0 coment provavelmente seria supérfluo. Explicacdo da intengao As vezes, um comentario vai além de ser apenas informag@es tteis sobre a implementagao e fornece a intengo por trés de uma decistio. No caso a seguir, vemos uma decisio interessante documentada através de um comentitio. Ao comparar dois objetos, 0 autor decidiu que queria classificar como superiores os objetos de sua classe em relacao aos de outras. public int compareTo{Object o) { if(o instanceof WikiPagePath) c WikiPagePath p = (WikiPagePath) o; String compressedName = StringUtiljoin(names, °"); String compressedArgumentName = StringUtil.join(p.names, ™"); return compressedName.compareTo(compressedArgumentName}; } “ return 1; // somos superiores porque scmos tipo certo. Abaixo esta um exemplo melhor ainda. Talvez vocé discorde da solugao do programador, mas pelo menos vocé sabe 0 que cle estava tentando fazer. public void testConcurrentAddWidgets() throws Exception { WidgetBuilder widgetBuilder = new WidgetBuilder(new Class{]{BoldWidget.class}); String text = "bold text!’"*; Comentarios Bons ¢ 57 ParentWidget parent = new BoldWidget(new MockilidgetRoot(), *’’"bold text’’’); AtomicBoolean failFlag = new AtomicBoolean(); failPlag.set(false); /7essa e a nossa melhor tentativa para conseguir uma condicao de corrida. /feaxa isso criamos um grande numero de threads. for (int i = 0; i < 25000; i++) { WidgetBuilderThread widgetBuilderThread = new WidgetBuilderThread(widgetBuilder, text, parent, failFlag); ‘Thread thread = new Thread(widgetBuilderthread); thread.start(); ? assertEquals(false, failFlag.get()); , Esclarecimento As vezes € bom traduzir o significado de alguns parametros ou valores retornados obscuros para algo inteligivel. De modo geral, é melhor encontrar uma forma de esclarecer tal parametro ou valor retornado por si s6, mas quando for parte da biblioteca padraio, ou de um cédigo que nao se possa alterar, ent&io um comentario esclarecedor pode ser ttil public void testCompareTo() throws Exception { WikiPagePath a = PathParser.parse("PageA’); WikiPagePath ab - PathParser.parse("PageA.PageB"); WikiPagePath b = PathParser.parse("PageB"); WikiPagePath aa = PathParser.parse("PageA.PageA"); WikiPagePath bb = PathParser.parse("PageB.PageB"); WikiPagePath ba = PathParser.parse("PageB.PageA”); assert True(a.compareTo(a) 0: “a assertTrue(a.compareto(b) != 0); // a assert True(ab.compareTo(ab} 0); 7 ab == assert True(a.compareTo(b) “Ii ach assertTrue(aa.compareTo(ab) -1); // aa < ab assertTrue(ba.compareTo(bb) -1); # ba < bb assertTrue(b.compareTo(a) == 1); //) b> a assertTruelab.compareTo(aa) == 1); // ab > aa assert True(bb.compareTo(ba) 1); // bb > ba } “ Ha um risco considerdvel, é claro, de que um comentario esclarecedor possa estar incorreto, Leia © exemplo anterior ¢ veja como ¢ dificil verificar se estilo corretos. Isso explica tanto 0 porqué da necessidade do esclarecimento como seu risco. Portanto, antes de eriar comentarios como esses, certifique-se de que ndo ha outra saida melhor e, ento, certifique-se ainda mais se esto precisos. 58 Capitulo 4: Comentarios Alerta Sobre Consequéncias As vezes 6 til alertar outros programadores sobre certas consequéncias. Por exemplo, 0 comentario abaixo explica porque um caso de teste cm particular esta desabilitado: 11 M&ONKo execute a menos que vocé W/ tenha tempo disponivel. public void _testWithReallyBigFile() { writeLinesToFile(10000000); response.setBody(testFile); response.readyToSend(this); String responseString = output. toString(); assertSubString("Content-Length: 1000000000", responseString); assertTrue(bytesSent > 1000000000); 3 Hoje em dia, desabilitariamos 0 teste de caso através do atributo @ignore com uma string explanatéria adequada: @ignore (“Leva muito tempo para executar”). Antes da chegada do JUnit4, uma convengdo comum era colocar um traco inferior (underscore) no inicio do nome do método. O comentario, enquanto divertido, passa sua mensagem muito bem. Outro exemplo mais direto seria: public static SimpleDateFormat makeStandardHttpDateFormat() { //simpleDateFormat n&o é uma thread segura, Wé preciso criar cada instancia independentemente. SimpleDateFormat df = new SimpleDateFormat(*EEE, dd MMM yyyy HH:mm:ss 2"); a£.setTimeZone(TimeZone.getTimeZone(*GMT*)); return df; } Talvez vocé reclame por haver melhores maneiras de resolver esse problema. Talvez eu concorde com vocé, mas © comentario como foi feito € perfeitamente I6gico. Ele evitara que um programador afoito use um inicializador estatico em prol da eficiéncia. Comentario TODO 2 As vezes ¢ cabivel deixar notas “To Do” (‘Fazer’) em comentarios no formato // ToDo. No caso a seguir, 0 comentario TODO explica por que a fung3o tem uma implementagao degradante e o que se deveria fazer com aquela fungao. HTODO-MGM essas néo séo necessérias W/ Bsperamos que isso nfo esteja mais aqui quando verificarmos o modelo protected Versioninfo makeVersion() throws Exception { return null; } Comentérios Ruins 59 ‘TODOS so tarefas que os programadores acham que devem ser efetuadas, mas, por alguna razo, nao podem no momento. Pode ser um lembrete para excluir uma instrugdo desnecessaria uum apelo para que alguém olhe o problema. Ou um pedido para que alguém pense em um nome melhor ou um lembrete para fazer uma alteragaio que é dependente de um evento determinado. Seja qual for o TODO, cle nao justifica deixar um eédigo ruim no sistema. Hoje em dia, a maioria das IDEs oferecem ferramentas recursos para localizar todos 0s comentarios 000; portanto, nao é provavel que fiquem perdidos no cddigo. Mesmo assim, vocé nao deseja que seu cddigo fique amontoado de ToDOs, sendo assim, procure-os regularmente € climine os que puder. Destaque Pode-se usar um comentério para destacar a importincia de algo que talvez parega irrelevante. String listItemContent = match.group(3).trim(); “/ a fungéo trim é muito importante. Ela remove os espacos “/ imiciais que poderiam fazer com que o item fosse #7 xeconhecido como outra lista. new ListItemWidget (this, listItemContent, this.level + 1); return buildbist(text.substring(match.end())); Javadocs em APIs Publicas Nao hé nada de to pritico e satisfatério como uma API piblica em descrita. Os javadoes para a biblioteca Java padrdo sio um exemplo. No méximo, seria dificil escrever programas Java sem eles. Se estiver criando uma API publica, entdo vocé certamente deveria escrever bons javadocs para ela. Mas tenha em mente os outros conselhos neste capitulo. Os javadoes podem ser tio enganadores, naio-locais e desonestos como qualquer outro tipo de comentario. Comentarios Ruins ‘A maioria dos comentarios cai nesta categoria, Geralmente eles sao suportes ou desculpas para um cédigo de baixa qualidade ou justificativas para a falta de decis6es, amontoados como se programador estivesse falando com si mesmo. Murmirio - Usar um comentario s6 porque vocé sente que deve ou porque 0 processo o requer € besteira. Se optar criar um comentario, entio gaste o tempo necessirio para fazé-lo bem. Por exemplo, a seguir esti um caso que encontrei no FitNesse, no qual um comentiirio poderia ter sido util. Entretanto, o autor estava com pressa ou nao prestava muita atengao. Seu murmtrio foi deixado para tras como um enigma: public void loadProperties() { try 60 __ Capitulo 4: Comentarios String propertiesPath = propertiesLocation + "/* + PROPERTIES_FILE; FileInputStream propertiesStream = new FileInputstream(propertiesPath); loadedProperties.load(propertiesStream); } catch(I0Exception e) { #/ Wenhum arquivo de propriedades significa que todos os padrées esto carregados } } O que esse comentario no bloco catch significa? Claramente fazia sentido para o autor, mas o significado n&o foi muito bem transmitido. Aparentemente, se capturarmos (catch) uma ToEXception, significaria que ndo hd arquivo de propriedades; e, neste caso, todos os padrées esto carregados. Mas quem carrega os padrdes? Eles foram carregados antes da chamada 20 loadProperties.1oad? Ou este capturou a excecao, carregou os padrdes e, entéo, passou a excecdo para que ignor4ssemos? Ou loadProperties. Load carregou todos os padrées antes de tentar carregar o arquivo? Sera que o autor estava limpando sua consciéncia por ter deixado ‘0 bloco do catch vazio? Ou — ¢ essa possibilidade ¢ assustadora — ele estava tentando dizer a si mesmo para voltar depois ¢ escrever 0 cédigo que carregaria os padrdes? Nosso tinico recurso é examinar o cédigo em outras partes do sistema para descobrir 0 que esté acontecendo. Qualquer comentario que Ihe obrigue a analisar outro médulo em busca de um significado falhou em transmitir sua mensagem ¢ nao vale os bits que consume. Comentarios Redundantes A Listagem 4.1 uma funcao simples com um comentario no cabegalho que é completamente redundante. Provavelmente leva-se mais tempo para lé-lo do que 0 cédigo em si. tagem 4-1 waitForClose // Utility method that returns when this.closed is true. Throws an exception 1/ if the timeout is reached. public synchronized void waitForClose(final long timeoutMillis) throws Exception if (closed) { weit (timeout) if (closed) throw new Exception (*MockResponseSender could not be closed"); Qual © propésito desse comentario? Certamente nao é mais informativo do que 0 cédigo. Nem mesmo justifica 0 cédigo ou oferece uma intengao ou raciocinio. mais facil ler 0 cédigo apenas. De fato, ele é menos preciso do que o cédigo ¢ induz o leitor a aceitar tal falta de preciso Comentarios Ruins 61 em vez da interpretagdo verdadeira. E mais como um vendedor interesseiro de carros usados lhe garantindo que nao é preciso olhar sob 0 capé. ‘Agora considere o grande numero de javadocs inuteis ¢ redundantes na Listagem 4.2 retirada do Tomcat, Esses comentirios s6 servem para amontoar ¢ encobrir 0 cédigo. Eles nao passam informagao alguma. Para piorar, s6 Ihe mostrei os primeiros, mas hé muito mais neste médulo. Listagem 4-2 ContainerBase. java (Tomcat) public abstract class ContainerBase ements Container, Lifecycle, Pipeline, MBeanRegistration, Serializable { * The processor delay for this component protected int backgroundProcessorDelay = -1: + The lifecyele event support for this componenc protected Lifecyclesuppert lifecycle new LifecycleSupport ithis); * The container event listeners for this Contain protected ArrayList listeners = new Arraybist!); * The Loader implemencation with which chi container is * associated. protected Loader loa jer = null; * The Logger implementation with which this Container is * associated. protected Log logger = null; * Associated logger nane. protected String logName = null; * ‘The Manager implementation wi « associated. protected Manager manager h which this Container is aul; Capitulo 4: Comentirios Listagem 4-2 (continuagio): ContainerBase.java (Tomcat) * The cluster with which this Container is associated. v1 protected Cluster cluster = null; * he human-readable name of this Container. protected String name = null; * The parent Container to which this Contair protected Container parent = null; is a child. * Whe parent class ioader to be configured when we install a * Loader. prot d Classtoader parentClassLoader = nul! + The Pipeline object with wi * associated. ch this Container is protected Pipeline pipeline = new StandardPipeline(this); + he Realm with which this Container is associat: protected Realm realm = null; + The resources DirContext object with which this Container + is associated, protected DirContext resources = aull: Comentérios Ruins 63 Comentarios Enganadores ‘As vezes, com todas as melhores das intengdes, um programador faz uma afirmagao nfo muito clara em seus comentarios. Lembre-se também do redundante ¢ enganador comentario que vimos na Listagem 4.1 ‘Como voce descobriu que o comentario era enganador? O método nao retomava quando this - closed virava true (verdadeiro), mas s6 se this closed ja fosse true (verdadeiro); caso contrario, ele esperava por um tempo limite ¢, ento, langava uma excegdo se this.closed ainda nao fosse true (verdadeiro) sca pequena desinformacao, expressada em um comentirio mais dificil de ler do que o cédigo em si, poderia fazer com que outro programador despreocupadamente chamasse essa fun¢ao esperando-a queretornasse assim que this .closedsetomasse true (verdadeiro) . Esse pobre programador logo se veria efetuando uma depuragiio tentando descobrir o porqué da lentidao do esdigo. Comentarios Imperativos E basicamente tolo ter uma regra dizendo que toda fungao deva ter um Javadoc, ou toda varidvel um comentario. Estes podem se amontoar no cédigo, disseminar mentiras e gerar confusto & desorganizagio. Por exemplo, os javadocs exigidos para cada fun¢ao levariam a abominagdes, como as da Listagem 4.3. Essa zona nfo acrescenta nada e sé serve para ofuscar 0 cédigo ¢ abrir o caminho para mentiras ¢ desinformagdes. Listagem 4-3 title The titl author The au tracks The number of tracke on the CD TnMinutes The duration of the CD in ng String author, tracks, int durationInNinutes) { cd.title = title; cd.author = author cd.tracks = traci cd.duration = duration: cdbist .addicd) ; Comentarios Longos As vezes, as pessoas, toda vez que editam um médulo, sempre adicionam um comentario no inicio, Apés varias alteragSes, a quantidade de comentarios acumulada parece mais uma redagao ou um diario. JA vi médulos com dezenas de paginas assim. 64 Capitulo 4: Comentarios Changes (from 11-¢t-2001) Re-organised the class and moved it to new package com. jrefinery.date (D3) ; Added a getDescription() method, and el s DG; 12-Nov-2001 : IBD requires setDescription!) method, now that NotableDate class ie gone {0G}; Changed getPreviousDayOfWeeki), f nated NotableDate * getFollowingbayOfWeek() and getNearestDayOfWeek{} to correct bugs (D@) ; 05-Dec-2001 : Fixed bux in SpreadsheetDate class (0G): 29-May-2002 : Moved the month constants into a separate interface (MonthCenstants) (DG); 27-Aug-2002 : Fixed bug in addMonths() method, thanks to N???levka Pecr iDG); Fixed errors reported by Checkstyle iD); : Implemented Serializable (D6); Fixed bug in addMonchs method (D6); Implemented Comparable. Updated the iainRange javadocs (06); xed bug in addYears() method (1096282) (DG); 05-Jan-2905 Ha muito tempo, havia um bom motivo para ctiar e preservar essas informagOes no inicio de cada médulo, pois ndo existiam ainda os sistemas de controle de cédigo fonte para fazer isso por nés. Hoje em dia, entretanto, esses comentirios extensos sdo apenas entulhos que confundem o cédigo, devendo assim ser completamente removidos. Comentarios Ruidosos As vezes vocé vé comentirios que nada so além de “chiados”. Eles dizem 0 ébvio ¢ nao fornecem novas informagoes. a * Construtor padrao. si protected AnnualDateRule() { ) Ah, sério? Ou este: J** Dia do mes. */ private int dayOfMonth; E ha também o seguinte tipo de redundAncia: ye - * Retorna do dia do més. * @return o dia do més. ef : public int getDayofMonth() { return dayOfMonth; } Esses comentarios sao to irrelevantes que aprendemos a ignorti-los. Ao lermos 0 cédigo, nossos olhos passam direto por eles. No final, os comentarios passam a “mentir” conforme 0 cédigo muda Comentarios Ruins 65 O primeiro comentario na Listagem 4.4 parece adequado*. Ele explica por que o block catch 6 ignorado. Contudo, o segundo nao passa de um chiado. Aparentemente, o programador estava to frustrado por criar blocos xy /catch na fungaio que ele precisou desabafar. Listagem 4-4 startSending private void startSending|} { uy dosending!) ; ) catch (SocketException €) J/ normal, someone stopped the request. catch(Exception e) response. add (ErrorFesponder .makeExceptionstring{e)} ; response.closeAll(); } catch(Exception el) J/Give me a break! } Em vez de desabafar em comentarios sem sentido ruidosos, ele poderia ter pegado tal frustragdo e usado-a para melhorar a estrutura do cédigo. Ele deveria ter redirecionado sua energia para calocar o tiltimo bloco try/catch em uma fungao separada, como mostra a Listagem 4.5. Listagem 4-5 startSending (refatorado) private void startSending(/ { try doSending!) ; catch |SocketException e} i d cateh (Exception el { / pormal. someone stopped the request. 12 Atendéacia atual das IDEs de verificacio da ortografia er comentirios serk um alivio para nds que lemos bastante cédigos. 66 Capitulo 4: Comentérios - Listagem 4-5 (continuacio) startSending (refatorado) addexceptionandcloseresponse ie}: private void addExceptionandCloseResponse (Exception ¢ { nse .add {ErrorKesponder .nakeExcept ionString(2]); response.closeall(}+ ‘Troque a tentacdo para criar ruidos pela determinacdo para limpar scu cédigo. Vocé pereeberd que isso lhe tornara um programador melhor e mais feliz. Ruidos assustadores Os javadocs também podem ser vistos como ruidos, Qual 0 objetivo dos Javadocs (de uma biblioteca de eédigo livre bem conhecida) abaixo? s** Nome. */ private String name; /** The version. */ private String version; /** Nome da licenca. */ private String licenceName; /** Versio. */ private String info; Releia os comentarios com mais atengao. Notou um erro de recortar-colar? Se os autores nio prestarem atengao na hora de escrever os comentarios (ou colé-los), por que os leitores deveriam esperar algo de importante deles? Evite o comentario se é possivel usar uma fungdo ou uma varidvel Considere 0 pedago de cddigo abaixo: // © médulo da lista global depende do / subsistema do qual fazemos parte? if (smodule.getDependsubsystems().contains(subSysMod.getSubSystem()}) Comentarios Ruins 67 Poderia-se evitar 0 comentario e usar: ArrayList moduleDependees = smodule.getDependSubsystems(); String ourSubSystem = subSysMod.getSubSystem(); if, (moduleDependees.contains(ourSubSystem)) O autor do cédigo original talvez tenha escrito primeiro 0 comentario (pouco provavel) ¢, entao, 0 cédigo de modo a satisfazer o comentario. Entretanto, o autor deveria ter refatorado 0 cédigo. como en fiz, para que pudesse remover 0 comentario. Marcadores de Posi¢ao Algumas vezes os programadores gostam de marcar uma posi¢ao determinada no arquivo fonte. Por exemplo, recentemente encontrei o seguinte num programa: MW ReBES HAILED E raro, mas ha vezes que faz sentido juntar certas fungdes sob um indicador como esses. Mas, de modo geral, eles sio aglomeragées ¢ devem-se exclui-los especialmente as varias barras no final. Pense assim: um indicador é chamativo e ébvio se voce ndo os vé muito frequentemente. Portanto, use-os esporadicamente, e s6 quando gerarem beneficios significatives. Se usar indicadores excessivamente. eles cairdio na categoria de ruidos e sero ignorados. Comentarios ao lado de chaves de fechamento As vezes. os programadores colocam comentarios especiais a0 lado de chaves de fechamento, como na Listagem 4.6. Embora isso possa fazer sentido em fungdes longas com estruturas muito aninhadas, s6 serve para amontoar o tipo de fungdes pequenas ¢ encapsuladas que preferimos. Portanto, se perceber uma vontade de comentar ao lado de chaves de fechamento, tente primeiro reduzir suas fungdes. + Listagem 4-6 we. java class we [ tatic void main(String|] args) { redReader in = new BufferedReader (new Inputstré String line; int Linecount int charCount int wordCount try { snkeader (System. in)) ; while ((line = in.readbune(}) LineCount++; ne. length{) String words[] = line.split( wordCount += words. leng! ) //while System. out .printIn("wordCou System.out.print1n("lineCour System. out .printin(*charCount fy try * + wordCount); * + lineCount) ; * + charCount); 68 Capitulo 4: Comentarios a Listagem 4-6 (continuacio) we.java catch (10Exception e} System.err..print 1n( } /featch } fjmain Tori" + e.getMessage() 3; } Créditos e autoria /* Adicionado por Rick */ Os sistemas de controle de cédigo fonte sio muito bons para lembrar que adicionou o qué e quando. Nao ha necessidade de poluir 0 codigo com comentirios de autoria. Talvez vocé ache que tais comentarios sejam titeis para ajudar outras pessoas a saberem o que falar sobre o cédigo. ‘Mas a verdade € que eles permanecem por anos, ficando cada vez menos precisos e relevantes. Novamente, o sistema de controle de cédigo fonte é um local melhor para este tipo de informacao. Explicagaio do cédigo em comentarios Poucas priticas so tio condenaveis quanto explicar 0 cédigo nos comentirios. Nao faga isso! InputStreamResponse response = new InputStreamResponse(); response. set Body (formatter.getResultStream|), formatter. getByteCount()); // InputStream resultsStream = formatter.getResultstream(); // StreamReader reader = new StreamReader(resultsstream); // response.setContent (reader.read(formatter.getByteCount())); Outros que vissem esse cédigo nao teriam coragem de excluir os comentarios. Eles achariam que estdo 14 por um motivo ¢ sdo importantes demais para serem apagados. Portanto, explicagaio de cédigos em comentarios se acumula como sujeira no fundo de uma garrafa de vinho ruim. Observe 0 cédigo do Commons do Apache: this.bytePos = writeBytes(pngIdBytes, 0); /fhdxPos = bytePos; writeHeader() writeResoluti //dataPos = bytePos; if (writeTmageData()) { writeEnd(); this.pngBytes = resizeByteArray(this.pngBytes, this.maxPos); } else { this.pngBytes = null; 3 return this.pngBytes; Por que aquelas duas linhas de cédigo esto comentadas? Elas sao importantes? Foram deixados como lembretes para alguma alteragao iminente? Ou sio apenas aglomerados que alguém comentara anos atras e simplesmente nao se preocupou em limpar? Comentarios Ruins 69 a Houve uma época, na década de 1960, em que explicar 0 cédigo em comentarios poderia ser pratico. Mas jé faz um tempo que temos 0s sistemas de controle de cédigo fonte, que lembrarao © cdigo para nés. Nao precisamos explicd-lo em comentarios. Simplesmente exclua 0 cédigo. Prometo que nfio o perder Comentarios HTML Cédigos HTML em comentarios de cédigo fonte so uma aberragao, como pode ver no cédigo abaixo. Eles dificultam a leitura dos comentarios onde seriam ficeis de ler — no editor/IDE. Se forem extrair comentirios com alguma ferramenta (como o Javadoc) para exibir numa pagina da Web, ento deveria ser responsabilidade de tal ferramenta, ¢ no do programador, adicionar os comentarios com os eédigos HTML adequados. Tarefa para executar os testes do Fit. Essa tarefa efetua os testes do FitNesse e exibe os resultados


Uso:

glt;taskdef name=squot ;execute-fitnesse-tests":

classname-squot ;fitnesse.ant .ExecuteFitnesseTestsTaskaquot;
classpathref=squot;classpath" />

ou

g<taskdef classpathref+zquot;classpath"
resource="tasks.properties" />

<execute-fitnesse-tests suitepage=" ;FitNesse.SuiteAcceptanceTests" fitnesseport=squot ;8082" resultsdir="$(results.dir}aquot; resultshtmlpage=aquot ;ft-results.htmlequot; . classpathref=squot;classpath" /sgt;

a Cee EEE Ee EEE Informacées nao-locais Se vocé precisar escrever um comentario, entdo, coloque-o perto do cédigo que ele descreve. Nao forneca informagdes gerais do sistema no contexto de um comentario local. Considere, por exemplo, © comentario do Javadoc abaixo. Além de ser terrivelmente redundante, cle também fala sobre a porta padrao. Ainda assim, a funco nem sabe que porta ¢ essa. O comentério nfo esta descrevendo a fungao, mas alguma outra em alguma parte distante do sistema. Certamente nao ha garantia de que esse comentdrio serd atualizado quando 0 c6digo que contém tal padrio for alterado. * Porta na qual o PitNesse deveria rodar. Padréo para 8082. * @param fitnessePort public void setFitnessePort(int fitnessePort) ‘ this.fitnessePort = fitnessePort; 70 Capitulo 4: Comentérios x Informagées excessivas Nao adicione discussdes histéricas interessantes ou descrigdes irrelevantes de detalhes em seus comentarios, Abaixo esté o comentario de um médulo projetado para testar se uma fungo poderia codificar e decodificar base64, Além do numero RFC, a pessoa que ler este cédigo nao precisa das informagdes historicas contidas no comentario, - RFC 2045 - Multipurpose Internet Mail Extensions (MIME) Parte um: se¢io 6.8 do Formato dos Corpos de Mensagens da Internet. Codificacéo e Transferencia de Contetido em Baseé4 © processo de codificag&o representa grupos de entrada de 24 bits como strings de saida de 4 caracteres codificados. Da esquerda para a direita, forma-se um grupo de entrada de 24 bits pela concatenacao de 3 grupos de entrada de 8 bits. Esses 24 bits serdo, ent&o, tratados como 4 grupos concatenados de 6 bits, cada um traduzido para um tinico digito do alfabeto da base64. Ao codificar um fluxo de bits através da codificagao para base64, deve-se presumir que tal fluxo esteja ordenado com o bit mais significante vindo primeiro. Ou seja, o primeiro bit no fuxo sera o mais relevante no primeiro byte de 8 bits, o oitavo ser4 o bit menos relevante no mesmo primeiro byte de 8 bits, e assim por diante. y Conexées nada ébv ‘A conexiio entre um comentario ¢ 0 cédigo que ele descreve deve ser Sbvia. Se for fazer um comentario, ent&o voce deseja, pelo menos, que o Ieitor seja capaz de ler 0 comentario e 0 codigo e, entdo, entender o que foi falado. Considere, por exemplo, 0 comentério abaixo do Commons do Apache: as rn * comega com um array grande o bastante para conter todos os pixels * (mais os bytes de Sltragem) e 200 bytes extras para informacdes no cabecalho y this.pngBytes = new bytel((this.width + 1) * this.height * 3) + 200]; O que é um byte de filtragem? Ele tem a ver com 0 +1? Ou com o *3? Com ambos? Um pixel um byte? Por que 200? O objetivo de um comentario ¢ explicar 0 que 0 cédigo nao consegue por si s6. E uma lastima quando um comentario também precisa ser explicado. Cabecalhos de fungées Fung@es curtas no requerem muita explicag%o, Um nome bem selecionado para uma funcao pequena que faga apenas uma coisa costuma ser melhor do que um comentario no cabegalho. Comentarios Ruins 7 Javadocs em cédigos nao-publicos he Assim como os Javadocs sao priticos para as APIs piiblicas, eles so uma maldig&o para 0 cédigo nao voltado para a distribuigao ao piblico. Gerar paginas Javadoc para classes e fungdes dentro de um sistema geralmente nao ¢ pratico, e a formalidade extra dos comentarios javadocs ‘unem um pouco mais de entulhos ¢ distrago, Exemplo Na Listagem 4.7, crici um médulo para 0 primeiro XP /mmersion para servir de exemplo de ma programagio e estilo de comentario, Entao, Kent Beck refatorou esse cédigo para uma forma muito mais agradavel na presenga de algumas dezenas de estudantes empolgados. Mais tarde, adaptei o exemplo para meu livro Agile Software Development, Principles, Pauerns, and Practices ¢ 0 primeiro de meus artigos da coluna Crafisman publicados na revista Sofware Development. Para mim, o fascinante desse médulo é que havia uma época quando muitos de nos o teriamos considerado “bem documentado™, Agora 0 vemos como uma pequena bagunga, Veja quantos problemas diferentes vocé consegue encontrar. Listagem 4-7 GeneratePrimes.java * This class Generati * maximum. The algorith ‘pp * Eratosthenes of Cy *d.c. 194, Alexandria. The first man to ci * circumference of the Earth. Also known for working on * calendars with leap years and ran the library et Alexandria. ‘p> * The algorithm is quite simple. Given an array of integers ime numbers up to a user specified ve of Eratos! used is the § e, b. c. 276 BC, Cyrene, Libya ~ culate the starting at 2. Cross cut all multiples of 2. Find the next cossed integer, and cross out all of its multiples. eat untilyou have passed the square root of the maximum value. une @author Alphonse @ersion 13 Feb import java.ut public class GeneratePrimes { * @param maxVelue is the generation limit public static int{] generaterrimes(int maxValue) if (maxValue >= // the only valid case /) declarations int ¢ = maxvalue + 1; // size of array boolean{| f = new boolean{s: int i; 2 Capitulo 4: Comentarios = Listagem 4-7 (continuagao) GeneratePrimes.java initialize array to true. for (i = 9; i< s; itei £[i] = true; jet rid of known ] = f{1] = false: non-primes int j; for (1 if (£[i]) // if i is uncrosced, cross its multipl fot j= 2% i; 42 £[j] = false; 7/ mule’ i < Math.cart(s) +1; L++ #7 bump count. int[] primes = new int [count]; ‘/ move che primes into the resule for (4 = 0, j= 0; i< 8; ise) { if (£14) / 48 prine primes [i++] = ir ) return primes; // return the primes else // maxvelue < 2 return new int[0]; // return null array if bad input. Na Listagem 4.8 pode-se ver uma versio refatorada do mesmo médulo. Note que 0 uso de comentirios foi limitado de forma significativa — ha apenas dois no médulo inteiro, e ambos so auto-explicativos. Listagem 4.8: PrimeGenerator java (refatorado) * This class Generates prime mumbers up to a user specified * maximum. The algorithm used is the Sieve of Eratosthenes * Given an array of integers start ;* Find the fir: ng at 2: uncrossed integer, and cross out all its Comentarios Ruins B Listagem 4-8 (continuacao) PrimeGenerator.java (refatorado) » multiples. * in the array. peat until there are no more miltiples public class Pri nerator private static bociean[] crossedout; private static inc[] result public static int|] generatePrinesiint naxvalue) if (maxValue < 2) return new int [0]; else uncrossIntegersUpToimaxValue) ; wiMultiples i); ossedintegersInt oResu! return result; private static void uncrossIntegersUpfoiint maxValue) { crossedOut = new boolean|maxValue + 1); for (ant 1 = 2; i < crossedOut.length; i++} crossedout [i] alse; private static void crossOutMultiples(} int Limit = determinerterationLimit (); for (int i= 2; i <= Limit; i+ if Inottrossed(i)) crossOutMul tiplesOf (i); private static int determinelterat ionbimit () { J Every multiple in the array has a prime #7 4s less than or equal te the root of th #/ 89 we don't have to cross cut multipl #/ larger than that root. cuble iterationLimit = math. sart (crossedout. length) ; recurn lint) iterationbinit; tor that array size, es of numbers private static void crossGutMultiplesOf(int ij { for (int multiple = 2*i; multiple < crossedOut.length; multiple += i) crossedOut [multiple] = crve; Capitulo 4: Comentarios Listagem 4-8 (continuagio) PrimeGenerator java (refatorado) private static boolean notCrossed(int i) return crossedUut [i] == false: private static void pucUncrossedIntegersIntoResult (} result = new int {numberofuncrossedintegers(}]; for (int j = 0, i= 2; i < crossedour.length; i++) if (notcros count-++: return count; ) } Como o primeiro comentario é muito parecido com a fungao generatePrimes, fica facil dizer que ele 6 redundante, Mesmo assim, acho que © comentario serve para facilitar a leitura do algoritmo, portanto prefiro manté-lo, Ja 0 segundo se faz praticamente necessdrio. Ele explica a logica por tras do uso da raiz quadrada como o limite da iteragdo. Nao consegui encontrar um nome simples para a variavel ou qualquer estrutura diferente de programagao que esclarecesse esse ponto. Por outro lado, © uso da raiz quadrada poderia ser um conceito. Realmente estou economizando tanto tempo assim ao limitar a iteracao A raiz quadrada? O cdlculo desta demora mais do que 0 tempo que economize? ‘Vale a pena ponderar. Usar a raiz quadrada como o limite da iteragao satisfaz 0 hacker em mim que usa a antiga linguagem C e Assembly, mas no estou convencido de que compense 0 tempo e 0 esforco que todos gastariam para entendé-la. Bibliografia [KP78]: Kernighan and Plaugher, The Elements of Programming Style, 2d. ed., McGraw- Hill, 1978. 5 Formatacao Quando as pessoas olham 0 cédigo, desejamos que fiquem impressionadas com a polidez, a consisténcia e a atengiio aos detalhes presentes. Queremos que reparem na organizagio. Desejamos que suas sobrancelhas se levantem ao percorrerem os médulos; que percebam que foram profissionais que estiveram ali. Se, em vez disso, virem um emaranhado de cédigo como Capitulo 5: Formatagio > se tivesse sido escrito por um bando de marinheiros bébados, entao provavelmente concluirao que essa mesma falta de atengao foi perpetuada por todo o projeto. ‘Vocé deve tomar conta para que seu cédigo fique bem formatado, escolher uma série de regras simples que governem seu cédigo e, entio, aplicé-la de forma consistente. Se estiver trabalhando em equipe. entdo, todos devem concordar com uma tinica série de regras de formatago. Seria bom ter uma ferramenta automatizada que possa aplicar essas regras para vocé O objetivo da formatacao Primeiro de tudo, sejamos claros. A formatacao do cédigo € importante. Importante demais para se ignorar e importante demais para ser tratada religiosamente. Ela serve como uma comunica¢ao, ¢ essa é a primeira regra nos negécios de um desenvolvedor profissional. Talvez vocé pensasse que “fazer funcionar” fosse a primeira regra. Espero, contudo, que, a esta altura, este livro ja tena tirado esse conceito de sua mente. A funcionalidade que vocé cria hoje tem grandes chances de ser modificada na proxima distribuigdo, mas a legibilidade de seu cédigo terd um grande efeito em todas as mudangas que serio feitas. A formatagao do cédigo ca legibilidade anteriores que continuam a afetar a capacidade de extensfo e de manuteneao tempos apés 0 cédigo original foram alteradas além de reconhecimento. Seu estilo e disciplina sobrevivem, mesmo que seu e6digo niio. Ent&o quais as questes sobre formataglo que nos ajuda a comunicar melhor? Formatacao vertical Comecemos com o tamanho vertical. O seu cédigo-fonte deve ser de que tamanho? Em Java, o tamanho do arquivo esté intimamente relacionado ao da classe. Discutiremos sobre o tamanho das classes quando falarmos sobre classes. Mas, por agora, consideremos apenas 0 tamanho do arquivo. Qual o tamanho da maioria dos cédigos-fonte cm Java? Ha uma grande diversidade de tamanhos ¢ algumas diferengas notaveis em estilo (veja a Figura 5.1), Ha sete projetos diferentes na figura: Junit, FitNesse, testNG, Time and Money, JDepend, Ant e Tomcat. As linhas verticais mostram os comprimentos minimo ¢ maximo em cada projeto. A caixa exibe aproximadamente um tergo (um desvio padrao') dos arquivos. O meio da caixa é a média, Portanto, 0 tamanho médio do cédigo no projeto FitNesse ¢ de cerca de 65 linhas, e cerca de um tergo dos arquivos estdo entre 40 e 100+ linhas. O maior arquivo no FitNesse tem aproximadamente 400 linhas, ¢ 0 menor 6 Note que essa é uma escala logaritmica; portanto, a pequena diferenga na posigdo vertical indica uma diferenga muito grande para o tamanho absoluto. Junit, FitNesse e Time and Money so compostos de arquivos relarivamente pequenos. Nenhum ultrapassa 500 linhas ¢ a maioria dos arquivos tem menos de 200 linhas. Tomeat e Ant, por outro lado, tém alguns arquivos com milhares de linhas ¢ outros, prdximos a metade. ultrapassam 200 linhas. O que isso nos diz? Parece ser possivel construir sistemas significativos (0 FitNesse tem quase 50.000 linhas) a partir de eédigos simples de 200 linhas, com um limite maximo de 500. Embora essa nfo deva ser uma regra fixa, deve-se considera-la bastante, pois arquivos pequenos costumam ser mais faceis de se entender do que os grandes. Formatagio Vertical Ht A metafora do jornal Pense num artigo de jornal bem redigido. Vocé o 1é verticalmente. No topo vocé espera ver uma manchete que Ihe diz do que se trata a estéria e Ihe permite decidir se deseja ou nao ler. O primeiro parigrafo apresenta uma sinopse da estéria toda, omitindo todos os detalhes, falando de uma maneira mais geral. Ao prosseguir a leitura, verticalmente, vao surgindo mais detalhes até que datas, nomes, citagdes, alegagdes e outras minicias sejam apresentadas. Desejamos que um cédigo fonte seja como um artigo de jornal. © nome deve ser simples mas descritivo. O nome em si deve ser 0 suficiente para nos dizer se estamos no médulo certo ou néio. As partes mais superiores do cédigo-fonte devem oferecer os conceitos ¢ algoritmos de alto nivel. Os detalhes devem ir surgindo conforme se move para baixo, até encontrarmos os detalhes eas fungées de baixo nivel no cédigo-fonte, 10000.0 Figura 5.1: Escala logaritmica de distribuigo de tamanho de arquivos (altura da caixa = sigma) Um jomal é composto de muitos artigos; a maioria é bastante pequena. Alguns so um pouco maiores. Muito poucos possuem textos que preencham a pagina toda, Isso torna o jormal aproveitével. Se ele fosse apenas uma estéria extensa com uma aglomeragao desorganizada de fatos, datas e nomes, nds simplesmente nao o leriamos. Espacamento vertical entre conceitos Quase todo eddigo ¢ lido da esquerda para a direita ¢ de cima para baixo. Cada linha representa ‘uma expressdio ou uma estrutura, e cada grupo de linhas representa um pensamento completo. Esses pensamentos devem ficar separados por linhas em branco. Considere, por exemplo, a Listagem 5.1. Hé linhas em branco que separam a declaragaoe a importaco do pacote e cada uma das funcdes. Essa simples c extrema regra tem grande impacto ro layout visual do eédigo. Cada linha em branco indica visualmente a separagao entre conceitos. ‘Ao descer pelo cédigo, seus olhos param na primeira linha apés uma em branco. 8 Capitulo 5: Formatacio Retirar essas linhas em branco, como na Listagem 5.2, gera um efeito notavelmente obscuro na legibilidade do cédigo. Listagem 5-2 BoldWidget . java package fitnesse.wikitext widgets: import java.util regex.*; public Class Boliifidyet extends Barentwidget_( public static final String REGEXP = °'''.2?'''"; private static final Pattern pattern - Fattern.compile(*''*( Pattern.MULTILINE + Pattern.DOTALL); public Boldiidget (ParentWidge: parent, String texti + super (parent) : Matcher match match, find() 7 addchilavidgete (match group (1)) 3 public String render() throws Exception { StringBuffer html = new StringBuffer ("cb>"|; html append ichilditml ()} .append(*") ; return heml.costring() ; ws Exception { tern.matcher (text) ; Esse efeito € ainda mais realgado quando vocé desvia seus olhos do cédigo. No primeiro exemplo, os diferentes agrupamentos de linhas saltam aos olhos, enquanto no segundo tudo fica meio confuso, A diferenga entre essas duas listagens ¢ um pouco de espacamento vertical. Continuidade vertical Se o espagamento separa conceitos, entZo a continuidade vertical indica uma associagao intima Assim, linhas de cédigo que estio intimamente relacionadas devem aparecer verticalmente unidas. Note como os comentirios imiteis na Listagem 5.3 quebram essa intimidade entre a instdncia de duas varidveis. Listagem 5.3 public class ReporterConfig { * The class name of the reporter listener private String m.classNane; + The properties of the reporter listener private ListeProperty> mproperties = new Arraybist(); public void addproperty (Property property) { m_properties.add(property); 1 Formatagiio Vertical 79 A Listagem 5.4 est muito mais facil de se ler. Ela cabe numa iinica visio, pelo menos para mim. Posso olha-la ¢ ver que é uma classe com duas varidveis e um método, sem ter de mover muito minha cabega ou meus olhos. A listagem anterior me faz usar mais o movimento dos olhos e da cabega para obter o mesmo nivel de entendimento, Listagem 5.4 publ: iss ReporterConfig { private String m_classlame; private List m properties = new as st() publ id addProperty (Property property) { operties.add (property) ; Distancia vertical Ja ficou tentando se encontrar numa classe. passando de uma fungdio para a proxima, subindo e descendo pelo cédigo-fonte, tentando adivinhar como as fungdes se relacionam e operam, s6 para se perder nesse labirinto de confusdo? Ja subiu pela estrutura de heranca buscando a defini¢o de uma varidyel ou fungao? Isso ¢ frustrante, pois vocé esta tentando entender 0 que o sistema faz, enquanto gasta tempo e energia mental numa tentativa de localizar e lembra onde esto as pecas. Os conceitos intimamente relacionados devem ficar juntos verticalmente [G10] Obviamente essa regra no funciona para conceitos em arquivos separados. Mas. entéo, ndo se devem separar em arquivos distintos conccitos intimamente relacionados, a menos que tenha uma razio muito boa. Na verdade, esse é um dos motivos por que se devem evitar varidveis protegidas. Para os conceitos que sio tao intimamente relacionados ¢ que estio no mesmo arquivo-fonte, a separagdo vertical deles deve ser uma medida do quao importante eles so para a inteligibilidade um do outro. Queremos evitar que nossos leitores tenham de ficar visualizando varios dos nossos arquivos-fonte ¢ classes. Declaragao de variaveis. Devem-se declarar as varidveis 0 mais proximo possivel de onde serdo usadas. Como nossas fungdes so muito pequenas, as variaveis locais devem ficar no topo de cada fung&o, como mostra a fungo razoavelmente longa abaixo do Junit4.3.1. private static void readPreferences|) { InputStream is= null; try ( is= new FileTnputstream(getPreferencesFile()); setPreferencesinew PropertiesigetPreferences()))+ getPreferences().load(is); } catch (TOBxception e) { try ( if (is != null) is.close(); } catch (TOException e1) ( } 80 Capitulo 5: Formatagio Geralmente, devem-se declarar as variéveis de controle para loops dentro da estrutura de iteragao, como mostra essa pequenina funcao da mesma fonte acima public int countTestCases() ( int count= 0; for (Test each tests) count += each.countTestCases(); return count; + Em raros casos pode-se declarar uma variavel no inicio de um bloco ou logo depois de um loop em uma fung4o razoavelmente longa. Veja um exemplo no pedacinho abaixo de uma fungao muito extensa do TestNG. for (XmlTest test : m_suite.getTests()) ( TestRunner tr = m_runnerFactory.newTestRunner(this, test); tr.addbistener(m_textReporter); m_testRunners.add(tr); invoker = tr.getInvoker(); for (ITestNGMethod m : tr.getBeforeSuiteMethods()) { beforeSuitemethods.put (m.getMethod(), m): i for (ITestNGMethod m : tr.getAfterSuiteMethods()) { afterSuiteMethods.put(m.getMethod(), m); } Instancias de varidveis. Por outro lado, devem-se declarar as instancias de varidveis no inicio da classe. Isso nado deve aumentar a distancia vertical entre tais varidveis, pois, numa classe bem projetada, elas so usadas por muitos, sendo todos, os métodos da classe. Muito ja se discutiu sobre onde devem ficar as instancias de variaveis. Em C++ ¢ comum a regra da tesoura, na qual colocamos todas as instincias das varidveis no final, Em java, contudo, a convengao € colocd-las no inicio da classe. Nao vejo motivo para seguir uma ou outra convengdo. O importante é que as instincias de varidveis sejam declaradas em um local bem conhecido. Todos devem saber onde buscar as declaragées. Considere, por exemplo, o estranho caso da classe Testsuite no JUnit 4.3.1. Resumi bastante essa classe para mostrar a questiio. Se vocé ler até cerca de metade do cédigo, vera duas instancias de variaveis declaradas. Seria dificil oculta-las num lugar melhor, Quem ler este cédigo se depararia por acaso com as declaragdes (como ocorreu comigo). public class TestSuite implements Test { static public Test createTestiClass Formatagio Vertical 81 theClass, String name} ( ? public static Constructor getTestConstructor(Class theClass) throws NoSuchMethodzxception { } public static Test warning(final string message) { } private static String exceptionToString(Throwable t) { 3 private String fName; private Vector fTests= new Vector(10); public TestSuite() { ? public TestSuite(final Class theClass) } public TestSuite(Class theClass, String Fungdes dependentes, Se uma fungao chama outta, elas devem ficar verticalmente proximas, ea que chamar deve ficar acima da que for chamada, se possivel. Isso dé um fluxo natural 20 programa. Se essa convengio for seguida a fim de legibilidade, os leitores poderao confiar que as declaragdes daquelas fung6es virdo logo em seguida apés seu uso. Considere, por exemplo, © fragmento do FitNesse na Listagem 5.5. Note como a fungi mais superior chama as outras abaixo dela ¢ como elas, por sua vez, chama aquelas abaixo delas também. Isso facilita encontrar as fungdes chamadas ¢ aumenta consideravelmente a legibilidade de todo o médulo. 82 Listagem 5-5 WikiPageResponder. java public class WikiPageResponder implenents SecureResponder { protected WikiPag> pact protected PageData pageData; protected String pageTitle; protected Request request; protected PageCrawler crawler; public Response makeResponse (FitNesseContext context, Request request) throws Exception { String pageNane getPageNaneOrDefault (request, *Frontbage"); loadPage!pageName, context); if (page == null) return notFoundResponse (context, request}; else return makePageResponse (context) ; } private String getPageNameOrDefault (Request request, String defaultPageNane) { String pageName = request.getResource(); if (StringUtil.isBlank (pageName) | pageName = defaultPagetame; return pageName; protected void loadPage(String resource, FitNesseContext context) throws Exception { WikipagePath path = PathParser.parse(resource) ; crawler = context .root .getPageCrawler (); crawler. setDeadEndstrategy (new VirtualEnabledPageCrawler ()) 5 page = crawler.getPage(context.root, path) ; if (page != mull} pageDate = page. getDatal); i private Response notFoundResponse(FitNesseContext context, Request request) throws Exception ( return new NotFoundResponder |) makeResponse (context, request); ) private SimpleResponse makePageResponse(FitNesseContext context! throws Exception { pageTitle = PathParser.render(crawler.getFullPath (page) } ; String html = makeHtml (context); cimpleResponse response - new SimpleResponse(); response. setMaxage (0): response.setContent (html) ; return response: Formatacao Horizontal 83 Além disso, esse fragmento apresenta um bom exemplo de como manter as constantes no nivel apropriado [G35]. A constante “FrontPage” poderia ter sido colocada na funcao getPageNameOrDefault, mas isso teria ocultado uma constante bem conhecida ¢ esperada em uma fung’o de baixo nivel. Foi melhor passar tal constante a partir do local no qual ela faz sentido para um onde ela realmente é usada Afinidade conceitual. Determinados bits de cddigo querem ficar perto de outros bits. Eles possuem uma certa afinidade conceitual. Quanto maior essa afinidade, menor deve ser a distancia vertical entre eles, Como vimos, essa afinidade deye basear-se numa dependéncia direta, como uma fungdo chamando outra ou uma fungdo usando uma varidvel. Mas ha outras causas possiveis de afinidade, que pode ser causada por um grupo de fungdes que efetuam uma operagao parecida. Considere 0 pedago de cddigo abaixo do JUnit 4.3.1: public class Assert { static public void assertTrue(string = message, boolean condition) { if (!condition) fail(message); } static public void assertTrue(boolean condition) { assertTrue(null, condition); 1 static public void assertFalse(string message, boolean condition) { assert?rue(message, !condition); } static public void assertFalse(boolean condition) { assertFalse(null, condition); } Essas fungdes possuem uma afinidade conceitual forte, pois compartitham de uma mesma convengao de nomes ¢ efetuam variagdes de uma mesma tarefa basica. O fato de uma chamar a outra ¢ secundario, Mesmo se nao 0 fizessem, ainda iriam querer ficar proximas. Ordenagao vertical De modo geral, desejamos que as chamadas das dependéncias da fungao apontem para baixo. Isto ¢, a fingio chamada deve ficar embaixo da que a cham’. Isso cria um fluxo natural para baixo no médulo do cédigo-fonte, de um nivel maior para um menor. Assim como nos artigos de jornais, esperamos que a maioria dos conceitos venha primeiro, ¢ também que seja expressada com uma quantidade minima de detalhes. Esperamos que os detalhes 84 Capitulo 5: Formatacao de baixo nivel venham por dltimo, Isso nos permite passar os olhos nos arquives-fonte e obter uma idéia de algumas das primeiras fungdes, sem ter de mergulhar nos detalhes. A Listagem 5.5 esta organizada dessa forma, Talvez os exemplos da Listagem 15.5 (p. 263) e 3.7 (p. 50) estejam ainda melhores. Isso é exatamente o oposto de linguagens, como Pascal, Ce C++, que exigem a definigao das fungdes, ou pelo menos a declaracdo, antes de serem usadas. Formatacgio horizontal Qual deve ser o tamanho de uma linha? Para responder isso, vejamos como ocorre em programas comuns. Novamente, examinemos sete projetos diferentes. A Figura 5.2 mostra a distribuigao do comprimento das linhas em todos os sete projetos. A regularidade é impressionante, cada linha fica com cerca de 45 caracteres. De fato, todo comprimento de 20 a 60 representa cerca de | por cento do nimero total de linhas. Isso silo 40%! Talvez outros 30 por cento possuam menos do que 10 caracteres, Lembre-se de que ¢ uma escala logaritmica, portanto a aparéncia linear do declinio gradual acima de 80 caracteres ¢ realmente muito significante. Os programadores claramente preferem linhas curtas, Isso sugere que devemos nos esforgar para manter nossas linhas curtas. O antigo limite de 80 de Hollerith é um pouco arbitrario, ¢ ndo sou contra linhas com 100 ou mesmo 120 caracteres. Mas ultrapassar isso provavelmente é apenas falta de cuidado. 7 60 70 8 90 100 ‘Largura da linha Figura 5.2: Distribuigao da largura da linha em Java Eu costumava seguir a regra na qual jamais se deve ter de rolar a tela para a direita. Mas, hoje em dia, os monitores estzio muito largos para isso, ¢ programadores mais jovens também podem diminuir a fonte de modo que 200 caracteres caibam na tela. Nao faca isso. Eu, pessoalmente, determinei 120 como meu limite. Formatacio Horizontal 85 Espagamento e continuidade horizontal Usamos 0 espago em branco horizontal para associar coisas que esto intimamente relacionadas e para desassociar outras fracamente relacionadas. Considere a fungao seguinte: private void measureLine(string line) { lineCount++; int lineSize = line.length(); totalChars += lineSize; lineWidthHistogram.addLine(linesize, lineCount); recordWidestLine(1ineSize); d Coloquei os operadores de atribuigao entre espagos em branco para destacd-los. As instrugdes de atribuigdo tém dois elementos principais e distintos: os Iados esquerdo e direito. Os espacos tornam essa separagdo ébvia. Por outro lado, ndo coloque espacos entre os nomes das fungdes ¢ os parénteses de abertura. Isso porque a fungo e seus parametros esto intimamente relacionados. Separd-los iria fazer com que parecesse que nao estdo juntos. Eu separo os parimetros entre parénteses na chamada da fungao0, para realgar a virgula ¢ mostrar que eles esto separados. Outro uso do espago em branco é para destacar a prioridade dos operadores, public class Quadratic { public static double rootl(double a, double b, double c) { double determinant = determinantia, b, oc); return (-b + Math.sqrt(determinant)) / (2*a); 4 } public static double root2(int a, int b, int c) { double determinant = determinant(a, b, ); return (-b - Math.sqrt(determinant)) / (2*a): ) private static double determinant(double a, double b, double c) return b*b - 4*a*e; } Note como € facil ler as equagées. Os fatores no possuem espagos em branco entre eles porque eles tm maior prioridade. Os termos sfio separados por espacos em branco porque a adigdo e a subtragdo tém menor prioridade. Infelizmente, a maioria das ferramentas para reformatagao de cédigo nao faz essa distingao entre operadores e usam o mesmo espacamento para todos. Portanto, costuma-se perder espagamentos sutis como os acima na hora da reformatagéio do cédigo. 86 Capitulo 5: Formatagiio Alinhamento horizontal Quando eu era programador em assembly”,eu usava 0 alinhamento horizontal para realgar certas estruturas. Quando comecei a programar em C, C++ e, depois, em Java, continuei a tentar alinhar todos os nomes das variaveis numa série de declaragdes, ou todos os valores numa série de instrugdes de atribuigdo. Meu cédigo ficava assim: public class FitNesseExpediter implements ResponseSender i private Socket socket; private InputStream input; private OutputStream output; private Request request: private Response response; private FitNesseContext context; protected long requestParsingTimeLimit; private long requestProgress; private long requestParsingbeadline; private boolean hasError; public FitNesseExpediter(Socket s, FitNesseContext context) throws Exception { this.context = context; socket = 5; input = s.getInputstream(); output = s.getOutputStream(); requestParsingTimeLimit = 10000; } Entretanto, descobri que esse tipo de alinhamento nao € pratico. Ele parece enfatizar as coisas erradas ¢ afasta meus olhos do propésito real. Por exemplo, na lista de declaragdes acima, vocé fica tentado a ler todos os nomes das varidveis sem se preocupar com seus tipos. Da mesma forma, na lista de atribuigdes, vocé se sente tentado a ler toda a lista de valores, sem se preocupar em ver 0 operador de atribuicao. Para piorar as coisas, as ferramentas de reformatacao automatica geralmente eliminam esse tipo de alinhamento. Portanto, acabei nao fazendo mais esse tipo de coisa. Atualmente, prefiro declaragdes ¢ atribuigdes nao alinhadas, como mostrado abaixo, pois eles destacam uma deficiéncia importante. Se eu tiver listas longas que precisem ser alinhadas, o problema esta no tamanho das listas. ¢ nao na falta de alinhamento, O comprimento da lista de declarag6es na FitNesseExpediter abaixo sugere que essa classe deva ser dividida. public class FitNesseExpediter implements ResponseSender { private Socket socket; private InputStream input; private OutputStream output; private Request request; Formatagio Horizontal 87 Quem estou tentanto enganar? Ainda sou um programador em assembly. Pode-se afastar 0 programador da linguagem, mas nao se pode afastar a linguagem do programador! private Response response; private FitNesseContext context; protected long requestParsingTimeLimit; private long requestProgress: private long requestParsingDeadline; private boolean hasError; public FitNesseExpediter(Socket s, FitNesseContext context) throws Exception this.context = context; socket = s; input = s.getInputStream(); output = s.getOutputstream(); requestParsingTimeLimit = 10000; Endenta¢4o Um arquivo-fonte é mais como uma hierarquia do que algo esquematizado. Ha informacdes pertinentes a0 arquivo como um todo, as classes individuais dentro do arquivo, aos métodos dentro das classes, aos blocos dentro dos métodos e, recursivamente, aos blocos dentro de blocos. Cada nivel dessa hierarquia é um escopo no qual se podem declarar nomes € no qual sto interpretadas declaragdes ¢ instrugdes executaveis. "A fim de tornar visivel a hierarquia desses escopos, endentamos as linhas do cédigo-fonte de acordo com sua posi¢ao na hierarquia. Instrugdes no nivel do arquivo, como a maioria das* declaragées de classes, ndo sio endentadas, Métodos dentro de uma classe sao endentados um nivel a direita dela. Implementagdes do método sao implementadas um nivel 4 direita da declaragao do método. Implementagdes de blocos sfo implementadas um nivel A direita do bloco que as contém, ¢ assim por diante. Osprogramadores dependem bastante desse esquema de endentagao, Elesalinham visualmente na esquerda as linhas para ver em qual escopo elas esto. Isso Ihes permite pular escopos, como de implementagdes de estruturas if ¢ while, que nao sao relevantes no momento. Eles procuram na esquerda por novas declaragdes de métodos, novas varidveis ¢ até novas classes. Sem a endentagdo, os programas scriam praticamente ininteligiveis para humanos. Considere os programas seguintes sintatica e semanticamente idénticos: public class FitNesseServer implements SocketServer { private FitNesseContext context; public FitNesseServer(FitNesseContext context) { this.context = context; } public void serve(Sccket s) ( serve(s, 10000); } public void serve(Socket s, long requestTimeout) { try ( FitNesseExpediter sender new FitNesseExpediter(s, context); sender.setRequest ParsingTimeLimit(requestTimeout); sender.start(); } catch(Exception e) { e.printStackTrace(); } } } 38 Capitulo 5: Formatagio public class FitNesseServer implements SocketServer { private FitNesseContext context; public FitNesseServer(FitNesseContext context) ( this.context = context; 3 public void serve(Socket s) { serve(s, 10060); } public void serve(Socket s, long requestTimeout) { try { FitNesseExpediter sender = new FitNesseExpediter(s, context); sender. setRequest ParsingTimeLimit (requestTimeout); sender.start(); } catch (Exception e) { e.printstackTrace(); ¥ Seus olhos conseguem discernir rapidamente a estrutura do arquivo endentado. Quase instantaneamente vocé localiza as varidveis, os construtores, os métodos acessores (lecitura ¢ escrita, ou setter and getter) e os métodos. Bastam alguns segundos para perceber que sc trata de um tipo simples de interface pra um socket, com um tempo limite. A versdo sem endentagdo, contudo, praticamente incompreensivel sem um estudo mais profundo. Ignorando a endentagdo. As vezes, ficamos tentados a no usar a endentac‘io em estruturas if curtas, loops whi le pequenos ou fungdes pequenas. Sempre que nao resisto a essa tentagdo, quase sempre acabo voltando e endentando tais partes. Portanto, evito alinhar uniformemente escopos como este: public class Commentwidget extends Textilidget { public static final String REGEXP = "*#[*\r\n]*(?:(?:\r\n)|\n|\x)?"; public CommentWidget (ParentWidget parent, String text)(super(parent, text);} public String render() throws Exception {return ™"; } } Prefiro expandir e endentar escopos, como este: public class Commentwidget extends Textilidget { public static final String REGEXP = “*#[*\r\n]*(?:(?:\r\n)|\n|\x)?" public Commentwidget (ParentwWidget parent, String text) { super(parent, text); } Regras de formatag’o do Uncle Bob 89 public string render() throws Exception { return “"; ) } Escopos minisculos De vez em quando, o corpo de uma estrutura while ou for é mimiscula, como mostra abaixo. Como nao gosto disso, procuro evité-las. Quando isso no for possivel, verifico se o corpo da estrutura est endentado adequadamente e entre parénteses, Imimeras vezes ja me enganci com um ponto-e-virgula quietinho Id no final de um loop while na mesma linha. A menos que vocé tome esse ponto-e-virgula visivel endentando-o em sua propria linha, fica dificil visualizd-lo. while (dis.read(buf, 0, readBufferSize) Regra de equipes © titulo desse topico faz um jogo com as palavras. Todo programador tem suas regras de formatacao prediletas, mas se ele for trabalhar em equipe, as regras so dela. Uma equipe de desenvolvedores deve escolher um tinico estilo de formatagao, ¢, entio, todos os membros a l devem usé-lo, Desejamos que 0 software tenha um estilo consistente. Nao queremos que pensem que 0 cédigo foi escrito por um bando de pessoas em desacordo. Quando entrei no projeto FitNesse em 2002, sentei com a equipe para escolher um estilo de programagiio. Isso levou 10 minutos. Decidimos onde colocariamos nossas chaves, o tamanho da endentacio, como nomeariamos as classes, variaveis ¢ métodos, ¢ assim por diante. Entio, codificamos essas regras no formatador de cédigo de nossa IDE e ficamos com ela desde enti, Nao eram as regras que eu preferia, mas as que foram decididas pela equipe. E como membro, tive segui-las na hora de programar no projeto FitNesse. Lembre-se: um bom sistema de software ¢ composto de uma série de documentos de facil leitura. Eles precisam ter um estilo consistente ¢ sutil. O leitor precisa poder confiar que as formatagdes que ele vir em um arquivo-fonte terfio 0 mesmo significado nos outros. A ultima coisa que queremos € adicionar mais complexidade a0 codigo-fonte programando-o com um monte de estilos diferentes. Regras de formatagao do Uncle Bob As regras que uso sao muito simples e esto ilustradas no eédigo da Listagem 5.6. Considere isso um exemplo de como o cédigo ¢ 0 melhor documento padrao em programacio. Capitulo 5: Formatacao Listagem 5-6 CodeAnalyzer.java public class CodeAnalyzer implements JavaFileanalysis { private int lineCount; private int maxLineWidth; private int widestLineNunber; private LineWidthiiscogram lines private int totalchar: idthHistogram: public Codeanalyzer() { LineWidthHistogram } new LineWidthHistogram() + public static List findJavaPiles(File parentDirectory) { t files = new Arraylist(}; \JavaFiles(parentDirectory, files); return files; } privete static void findJavariles(File parentDirectory, List files) { for (File file : parentDirectory,listPiles(]) { if (file.getNane() .endsWith(".java")) files.add(file); else if (file. isDirectory()) findJavaFiles(file, files); } public void analyzeFileiFile javaFile) throws Exception ( BufferedReader br = new BufferedReader (new FileReader (javaFile)): String Line; while {(line = br-readLine(}) measureLine(Line] ; mull) private void measureLine(String line) { lineCount++; int lineSize = line. length( totaiChars += lineSize; LineWidchHistogram.addLine(lineSize, LineCount); recordwidestLine(1inesize); Regras de formatacio do Uncle Bob a1 Listagem 5-6 (continua¢io) CodeAnalyzer.java void recordwidestLine(int lineSize) [ ilineSaze > maxLineWidth) { maxLineWidth = Linesize; widestLineNumber = iineCount; y ) public int gethinecount() { return lineCount; public int getNaxbinewidth() { return mexLinewidch; public int gecWidestLineNumber() { return widestLineNunber: } public Linewidthitistogram getLinewidthiistogram() return Line#idthHistogram; public double getMeanbinel return (double) totalChars/Linecount; public int getMedianbirewidth() { Integer[] sortedwidths = gerSorcediidths|) ; int cumlativelineCount = 0; for (int width : sortecWidths) ( cumulativeLineCount += lineCountForiidth (width); if (cumulativeLineCount > lineCount/2) return wide! throw new Error ("Cannot get here"); private int lineCountForWidth(int width) { return lineWidthiistogram.getLinesforwidthiwidth) .size\) + } private Integer[] getfortedWidths() { Set widths = linoWidthHistogran.getWidths (1+ integer{] sortedWidths - (widths.coarray (new Integer | Arrays. sort (sortedwidths) ; return sortedWidths; 6 Objetos e Estruturas de Dados Ha um motivo para declararmos nossas varidveis como privadas. Nao queremos que ninguém dependa delas. Desejamos ter a liberdade para alterar 0 tipo ou a implementacao, seja por capricho ou impulso. Por que, entdo, tantos programadores adicionam automaticamente métodos de acesso (escrita, ou seviers, ¢ leitura, ou getters) em seus objetos, expondo suas variaveis privadas como se fossem piiblicas? Abstragio de dados Considere a diferenga entre as listagens 6.1 ¢ 6.2. Ambas representam os dados de um ponte no plano cartesiano. Um expde sua implementagdo ¢ 0 outro a esconde completamente. 94 Capitulo Objetos ¢ Estruturas de Dados Listagem 6-1 Caso concreto public class Foint { public double x; public double y Listagem 6-2 Caso abstrato public interface Point { double getx() double get¥ (1; void setCartesian(double x, double y); double getR(); uble getTheta(); void setPolar (doukl x, double theta) ; O belo da Listagem 6.2 ¢ que nao ha como dizer se a implementagao possui coordenadas retangulares ou polares. Pode nao ser nenhuma! E ainda assim a interface representa de modo claro uma estrutura de dados. Mas ela faz mais do que isso. Os métodos exigemumaregra deacesso. Vocé pode leras coordenadas individuais independentemente, mas deve configura-las juntas como uma operacao atémica. A Listagem 6.1, por outro lado, claramente esta implementada em coordenadas retangulares, ¢ nos obriga a manipulé-las independentemente, Isso expde a implementagdo. De fato, ela seria exposta mesmo se as varidveis fossem privadas e estivéssemos usando métodos tinicos de escrita e leitura de varidveis. Ocultar a implementagao nao é s6 uma questo de colocar uma camada de fungdes entre as variaveis. E uma questo de ! Uma classe nfo passa suas varidveis simplesmente por meio de métodos de escrita e leitura, Em vez disso, ela expde interfaces abstratas que permite aos usuarios manipular a esséncia dos dados, sem precisar conhecer a implementacao. Considere as listagens 6.3 ¢ 6.4. A primeira usa termos concretos para comunicar o nivel de combustivel de um veiculo, enquanto a segunda faz o mesmo, s6 que usando . No caso concreto, vocé tem certeza de que ali estao apenas métodos acessores (escrita ¢ leitura, ou getter e setter) de varidveis. No caso abstrato, nao ha como saber 0 tipo dos dados. Listagem 6-3 Veiculo concreto public interface veh: double a double getGal lon: yInGallons(); fcasoline|); } Anti-simetria data/objeto 95 Listagem 6-4 Veiculo abstrato public interface vehicle { double get PercentFuelRenaining |}; Em ambos os casos acima, o segundo ¢ preferivel. Nao queremos expor os detalhes de nossos dados. Queremos expressar nossos dados de forma abstrata. Isso n&o se consegue meramente através de interfaces e/ou métodos de escrita ¢ Icitura. E preciso pensar bastante na melhor maneira de representar os dados que um objeto contenha. A pior opcao € adicionar levianamente métodos de escrita ¢ leitura. Anti-simetria data/objeto Esses dois exemplos mostram a diferenga entre objetos e estruturas de dados. Os objetos usam abstragdes para esconder seus dados, ¢ expdem as fungdes que operam em tais dados.As estruturas de dados expoem seus dados ¢ nao possuem fungées significativas. Leia este paragrafo novamente. Note a natureza complementar das duas definigdes. Elas s4o praticamente opostas. Essa diferenga pode parecer trivial, mas possui grandes implicagOes. Considere, por exemplo, a classe shape procedimental na Listagem 6.5, A classe Geometry opera em trés classes shape que sio simples estruturas de dados sem qualquer atividade. Todas as agées estao na classe Geometry. Listagem 6-5 Classe shape procedimental public class Square public Point topbeft ¢ double side; c class Rectancle { Point topLeft; 2 double height; ouble width; public class Circle { public Point cente public double radius public class Geometry { public final double PI = 3,141592653589793; public double area(Object shape) throws NoSuch: if (shape instanceof Square) ( Square s = (Square) shape; return s.side * s.side; , 96 Capitulo 6: Objetos e Estruturas de Dados Listagem 6-5 (continuago) Classe shape procedimental else if (shape instanceof Rectangle) { Rectangle r = (Rectangle) shape; return r.heicht * r.width: else if (shape instanceof Circle) { Circle ¢ = |Circlejshape; return PI * c.radius * c.radius; } throw new NoSuchshapekxception | Programadores de orientagGo a objeto talvez torgam o nariz ¢ reclamem que isso é procedimental—e estdio certos. Mas nem sempre. Imagine 0 que aconteceria se adiciondssemos uma fungao perimeter () 4 Geometry. As classes shape no seriam afetadas! Assim como quaisquer outras classes que dependessem delas! Por outro lado, se adicionarmos uma nova classe shape, teremos de alterar todas as fungdes em Geometry. Leia essa frase novamente. Note que as duas situagdes so completamente opostas. Agora, considere uma solugao orientada a objeto na Listagem 6.6. O método 4rea() & poliformico. Nao ¢ necessaria a classe Geometry. Portanto, se eu adicionar uma nova forma, nenhuma das fungdes existentes serdo afetadas, mas se eu adicionar uma nova funciio, todas as classes shape deverio ser alteradas', shape poliférmicas Square implements Shape { fe Point topLett; privace double side; public © Gouble ares() { implements shape { nt topleft; private double height; private double width, public double areaj} { return height * widch; H 1D tii clam Rg is “i: li an Gea “Sai. ls ‘isk dha inci i Secale Alei de Demeter “97 Listagem 6-6 (continuacio) Classes shape poliférmicas private double radiu: public final double public doubl en BL Novamente, vemos que a natureza complementar dessas duas definigdes: elas so praticamente opostas! Isso expde a dicotomia fundamental entre objetos ¢ estruturas de dados O cédigo procedimental (usado em estruturas de dados) facilita a adigéio de novas fungées sem precisar alterar as estruturas de dados existentes. O codigo orientado a objeto (OO), por outro lado, facilita a adigiio de novas classes sem precisar alterar as fungdes existentes. CO inverso também ¢ verdade: O cédigo procedimental dificulta a adicdo de novas estruturas de dados, pois todas as funcoes teriam de ser alteradas. O cédigo OO dificulta a adigéo de novas fungdes, pois todas as classes teriam de ser alteradas. Portanto, o que é dificil para a OO é facil para o procedimental, ¢ o que é dificil para 0 procedimental ¢ facil para a OO! Em qualquer sistema complexo haverd vezes nas quais desejaremos adicionar novos tipos de * dados em vez de novas fungdes. Para esses casos, objctos ¢ OO so mais apropriados. Por ouro lado, também haveré vezes nas quais desejaremos adicionar novas fungdes em vez. de tipos de dados. Neste caso, estruturas de dados e cédigo procedimental sao mais adequados. Programadores experientes sabem que a idcia de que tudo é um objeto ¢ um mito. As vezes, vocé realmente deseja estruturas de dados simples com procedimentos operando nelas. A lei de Demeter Hauma nova heuristica muito conhecida chamada Lei de Demeter*: um médulo no deve enxergar 0 interior dos objetos que ele manipula. Como vimos na sego anterior, os objetos escondem seus dados e expdem as operagées. Isso significa que um objeto n&o deve expor sua estrutura interna por meio dos métodos acessores, pois isso seria expor, € no ocultar, sua estrutura interna. Mais precisamente, a Lei de Demeter diz que um método f de uma classe C s6 deve chamar os métodos d “c + Um abjeto criado por f + Um objeto passado como parémetro para f + Um objeto dentro de uma inst&ncia da variével C See Pienveiihiiedbln caaetiiilas e Weaiaee: 8 Capitulo 6: Objetos e Estruturas de Dados O método ndo deve chamar os métodos em objetos retornados por qualquer outra das fungdes permitidas, Em outras palavras, fale apenas com conhecidos, ndo com estranhos. O cédigo’ seguinte parece violar a Lei de Demeter (dentre outras coisas), pois ele chama a fungao getScratchDir() no valor retornado de getOptions() e, ento, chama getAbsolutePath() no valor retornado de getScratchDir(). final String outputDir = ctxt.getopcions().getScratchDir{). getAbsolutePath(); Carrinhos de trem Esse tipo de cédigo costuma ser chamador de carrinho de trem, pois parece com um monte de carrinhos de trem acoplados. Cadeias de chamadas como essa geralmente silo consideradas descuidadas e devem ser evitadas [G36]. Na maioria das vezes é melhor dividi-las assim Options opts = ctxt.getoptions(); Pile scratchDir = opts.getScratchDir(); final String outputDir = scratchDir.getAbsolutePath\(); Esses dois pedagos de cédigo violam a Lei de Demeter? Certamente médulo que os contém sabe que 0 objeto ctxt possui opgdes (options), que contém um diretério de rascunho (scratchDir), que tem um caminho absoluto (AbsolutePath), E muito conhecimento para uma fungdo saber. A fungdo de chamada sabe como navegar por muitos objetos diferentes. Se isso é uma violacdo da Lei de Demeter depende se ctxt, Options e ScracthDir sio ou no objetos ou estruturas de dados. Se forem objetos, ent&o sua estrutura interna deveria estar oculta ao invés de exposta, portanto 0 conhecimento de seu interior é uma violagao clara da lei. Por outro lado, se forem apenas estruturas de dados sem atividades, entdo eles naturalmente ‘expéem suas estruturas internas, portanto aqui nao se aplica a lei O uso de fungées de acesso confunde essas questdes. Se 0 cédigo tiver sido escrito como abaixo, entdo provavelmente nio estariamos perguntando sobre cumprimento ou nio da lei. final String outputDir = ctxt.options.scratchDir.absolutePath; Essa questdo seria bem menos confusa se as estruturas de dados simplesmente tivessem varidveis piblicas e nenhuma fungao, enquanto os objetos tivessem apenas varidveis privadas fangdes piiblicas. Entretanto, ha frameworks e padrdes (¢.g., “beans ”) que exigem que mesmo estruturas de dados simples tenham métodos acessores e de alteragao. i ial a iis isa aa a a A lei de Demeter ee Hibridos De vez em quando, essa confusao leva a estruturas hibridas ruins que so metade objeto e metade estrutura de dados. Elas possuem fungdes que fazem algo significativo, ¢ também variaveis ou meétodos de acesso e de alteracdo publicos que, para todos os efeitos, tomam piblicas as variaveis privadas, incitando outras fungdes externas a usarem tais varidveis da forma como um programa procedimental usaria uma estrutura de dados*. Esses hibridos dificultam tanto a adigao de novas fungées como de novas estruturas de dados Eles sto a pior coisa em ambas as condigdes. Evite crid-los. Eles indicam um modelo confuso cujos autores nao tinham certeza — ou pior, ndo sabiam — se precisavam se proteger de fungdes ou tipos. Estruturas ocultas Ese ctxt, options e scratchDir forem objetos com agdes reais? Entio, como os objetos devem ocultar suas estruturas internas, ndo deveriamos ser capazes de navegar por eles. Entio, como conseguiriamos 0 caminho absoluto de seratehDir (‘diretério de rascunho”)? ctxt.getAbsolutePathofScratchDirectoryOption(); ou ctx.getScratchDirectoryOption().getAbsolutePath() A primeira opgdo poderia levar a uma abundancia de métodos no objeto ctxt. A segunda presume que getScratchDirectoryOption() retorna uma estrutura de dados. ¢ ndo um objeto. Nenhuma das opgées parece boa. “ Se ope for um objeto, devemos dizé-lo para fazer algo: no devemos pergunté-lo sobre sua estrutura interna, Por que queremos 0 caminho absohuto de seratchDir? O que faremos com ele? Considere o c6digo, muitas linhas abaixo, do mesmo médulo: String outFile = cutputDir + “/” + className.replace(’.*, ‘/") + *.class"; FileQutputStream fout = new FileOutputStream(outFile); BufferedOutputStream bos = new BufferedOutputstream(fout); ‘A mistura adicionada de diferentes niveis de detalhes [G34][G6] ¢ um pouco confusa. Pontos, barras, extensdo de arquivos e objetos Fi Le nao devem ser misturados entre si enem com o codigo que os circunda, Ignorando isso, entretanto, vimos que a intengdo de obter o caminho absoluto do diretorio de rascunho era para criar um arquivo de rascunho de um determinado nome. Entio, se disséssemos ao objeto cbt para fazer isso? BufferedoutputStream bos = ctxt.createScratchFileStream(classFileName); ‘That seems like a reasonable thing for an object to do! Isso permite ao ctxt esconder sua estrutura interna ¢ evitar que a fungdo atual viole a Lei de Demeter ao navegar por objetos os qui ela nao deveria enxergar. “he Wt hie seb cen cs eau. 100 Capitulo Objetos e Estruturas de Dados Objetos de transferéncia de dados A forma perfeita de uma estrutura de dados é uma classe com varidveis piiblicas e nenhuma fungao. As vezes, chama-se isso de objeto de transferéncia de dados, ou DTO (sigla em inglés). Os DTOs sao estruturas muitos titeis, especialmente para se comunicar com bancos de dados ou analisar sintaticamente de mensagens provenientes de sockets ¢ assim por diante. Eles costumam se tornar os primeiros numa série de estagios de tradugao que convertem dados brutes num banco de dados em objetos no cédigo do aplicativo. De alguma forma mais comum é 0 formulario “bean” exibido na Listagem 6.7. Os beans tém varidveis privadas manipuladas por métodos de escrita ¢ leitura. O aparente encapsulamento dos beans parece fazer alguns puristas da OO sentirem-se melhores, mas geralmente ndo oferece vantagem alguma. Listagem 6-7 address.java class Address { te String street; String streetixcra; private string city; private String state; private String zip; public Address (string String city, scr: this.street = street; this.streetExtra = streetExtre this.city = s.state street, String streetExtra, state, string zip) crit ng getStreecExtra(| ( reetExtra; public String getcity() { return city; public String getStatel) { return state; i { Bibliografia 101 O Active Record Os Active Records sao formas especiais de DTOs. Eles so estruturas de dados com variaveis piiblicas (ou acessadas por Beans); mas eles tipicamente possuem métodos de navegacdo, como save (salvar) c find (buscar). Esses Active Records so tradugdes diretas das tabelas de bancos de dados ou de outras fontes de dados. Infelizmente, costumamos encontrar desenvolvedores tentando tratar essas estruturas de dados como se fossem objetos, colocando métodos de regras de negdcios neles. Isso é complicado, pois cria um hibrido entre uma estrutura de dados e um objeto. A solugdo, € claro, é tratar o Record Active como uma estrutura de dados e criar objetos separados que contenham as regras de negécio e que ocultem seus dados internos (que provavelmente so apenas instancias do Active Record). Conclusao Os objetos expdem as ages e ocultam os dados. Isso facilita a adigdio de novos tipos de objctos sem precisar modificar as agdes existentes e dificulta a inclusao de novas atividades em objctos existentes. As estruturas de dados expiem os dados e nao possuem agdes significativas. Isso facilita a adigdo de novas agées as estruturas de dados existentes e dificulta a incluso de novas estruturas de dados em fungdes existentes. Em um dado sistema, as vezes, desejaremos flexibilidade para adicionar novos tipos de dados, e, portanto, optaremos por objetos. Em outras ocasides, desejaremos querer flexibilidade para adicionar novas agdes, ¢, portanto, optaremos tipos de dados ¢ procedimentos. Bons desenvolvedores de software entendem essas questées sem preconceito e sclecionam a. abordagem que melhor se aplica no momento. Bibliografia [Refatoracao] Refatoracao - Aperfeigoando o Projeto de Codigo Existente, Martin Fowler et al., Addison-Wesley, 1999. 7 Tratamento de Erro por Michael Feathers Pode parecer estranho ter uma se¢%o sobre tratamento de erro num livro sobre codigo limpo, mas essa tarefa é uma das quais todos temos de fazer quando programamos. A entrada pode estar errada e os dispositives podem falhar. Em suma, as coisas podem dar errado, e quando isso ‘ocorre, nds, como programadores, somos responsaveis por certificar que nosso cédigo faga o que seja preciso fazer. ‘A conexo com um cédigo limpo, entretanto, deve ser clara. Q tratamento de erro domina completamente muitos cddigos-fonte. Quando digo “domina”, nao quero dizer que eles s6 fazem tratamento de erro, mas que é quase impossivel ver o que 0 cédigo faz devido a tantos tratamentos de erros espalhados. Esse recurso ¢ importante, mas se obscurecer a ldgica, esté errado. ‘Neste capitulo ressaltarei uma série de técnicas e considerages que vocé pode usar para criar um cédigo que seja limpo ¢ robusto, que trate de erros com elegancia ¢ estilo. 104 Capitulo 7: Tratamento de Erro Use excegées em vez de retornar cédigos Num passado longinquo havia muitas linguagens que nao suportavam excegdes. Nelas, as técnicas para tratar e informar erros era limitada. Ou vocé criava uma flag de erro ou retornava um cédigo de erro que o chamador pudesse verificar. O cédigo na Listagem 7.1 ilustra essas abordagens. Listagem 7-1 DeviceController.java ic class DeviceController { public void sendshutBown(} { DeviceHiandle handle = getHandle|DEV1); (/ Check the state of the device if (handle != DeviceHandle. INVALID) { J/ Saye the device status to the record field retrieveDeviceRecord (handle) i Tf not suspended, shut down if (record.getStatus(} ! pauseDevice (handle); ClearDeviceWorkQueu eloseDevice (handle! ; J else { logger. log(*Device suspe: Unable to shut down"); a ("Invalid handle for: * + DEVi.toString()); © problema era que essas técnicas entupiam 0 chamador, que devia verificar erros imediatamente apés a chamada. Infelizmente, facilmente se esqueciam de fazer isso. Por esse motivo, ¢ melhor langar uma exce¢ao quando um erro for encontrado. O cédigo de chamada fica mais limpo e sua légica nao fica ofuscada pelo tratamento de erro. A Listagem 7.2 mostra 0 codigo depois de termos optado por langar excegdes em métodos que podem detectar erros. Listagem 7-2 DeviceController. va_(com excec: eController { public void sendShutDown() { ery { yToShut Down (); } catch (DeviceshutDowngrror ¢} logger.logie); Crie primeiro sua estrutura try-catch-finally los Listagem 7-2 (continuacao): DeviceController.java (com exce¢des) private void tryToShutDown() throws DeviceShutDownError DeviceHandle handle = getHandle(DEv1}; BeviceRecord record = retrieveDeviceRecord{i pauseDevice (handle) ; clearDeviceliorkQueu: closeDevice(handiel ; ale}; private DeviceHandle getHandie(DeviceID id) { row new DeviceShutDownError ("Invalid handle for: * + id.tostringi)|; Observe como fica muito mais claro. Isso nao é apenas uma questo estética, O eddigo fica melhor porque duas preocupagdes que estavam intrincadas, 0 algoritmo para o desligamento do dispositivo e 0 tratamento de erro, agora esto separadas. Voce pode pegar cada uma delas e estuda-las independentemente. Crie primeiro sua estrutura try-catch-finally ‘Uma das coisas mais interessantes sobre excegdes € que clas definem um escopo dentro de seu programa, Ao executar o cédigo na parte try da estrutura try...catch..finally, vocé declara que+ aquela execugao pode ser cancelada a qualquer momento c, entao, continuar no catch. De certa forma, os blocos try s4o como transagdes. Seu catch tem de deixar seu programa num estado consistente, néo importa o que acontega no try Por essa razio, uma boa pratica é comegar com uma estrutura try...catch...,finally quando for escrever um cédigo que talvez lance excecdes. Isso Ihe ajuda a definir 0 que o usuario do cédigo deve esperar, independente do que ocorra de errado no cdédigo que ¢ executado no ty. Vejamos um exemplo. Precisamos criar um codigo que acesse um arquivo ¢ consulte alguns objetos em série, Comegamos com um teste de unidade que mostra como capturar uma excegéio se 0 arquivo nao existir: @Test(expected = StorageException.class) public void retrievesectionShouldThrowOnInvalidFileName() { sectionStore.retrieveSection("invalid - file"); ? O teste nos leva a cria esse stub: public List retrieveSection(string sectionName) { // retorno ficticio ate que tenhamos uma implementacao real return new Arraylist(); 106 Capitulo 7: Tratamento de Erro Nosso teste falha porque ele nao langa uma excegdio, Em seguida, mudamos nossa implementaco de modo a tentar acessar um arquivo invilido. Essa operagao langa uma excecao: public List retrieveSection(String sectionName) ( try { FileInputStream stream = new FileInputstream(sectionName) } catch (Exception e) { throw new StorageException("retrieval error’, e): } return new Arraybist(); } ‘Nosso teste funciona agora porque capturamos a excecio. Neste momento, podemos refatorar. Podemos reduzir o tipo de execugao que capturamos para combinar com aquele que realmente € langado pelo construtor FileInputStream: FileNotFoundExcept ion: public List retrieveSection(String sectionName) { try { PileInputStream stream = new FileInputSt ream(sectionName); stream.close(); } catch (FileNotFoundException e) ( throw new StorageException(*retrieval error", e}; > return new ArrayList(); ? ‘Agora que definimos 0 escopo com uma estrutura try...catch, podemos usar 0 TDD para construir o resto da logica que precisamos, que sera adicionada na criagio do Fileinputsteam do close e poderd fingir que nada de errado aconteceu Experimente criar testes que foram excegdes c, entdo, adicione a agao ao seu tratador para cumprir seus testes. Isso fara com que vocé crie primeiro 0 escopo de transagdio do bloco ty € Ihe ajudara a manter essa natureza de transagao daquele escopo. Use excecées nao verificadas ‘A discussao acabou. Por anos, programadores Jaya tém discutido sobre as vantagens ¢ desvantagens de excegdes verificadas. Quando a verificagZo excecdes surgi com a primeira versio do Java, parecia uma Otima ideia, A assinatura de todo método listaria todas as exce¢oes que ele passaria ao seu chamador. Ademais, essas excecdes eram parte do tipo do método. Seu cédigo literalmente no seria compilado se a assinatura nao fase a mesma da que seu cédigo podia fazer. ‘Naquela época, pensamos que excecdes verificadas fosse uma ideia étima; ¢ era, elas tinham algumas vantagens. Entretanto, ficou claro agora que elas ndo s&o necessarias para a producgao de um software robusto. O C# nao verifica excegdes, e, apesar das tentativas, nem o C++. Nem mesmo Python ou Ruby. Ainda assim é possivel criar um software robusto em todas essas linguagens, porque, nese caso, temos de decidir se realmente as excegdes verificadas valem 0 prego que se paga. Que prego? O de verificar excegdes € a violago do Principio de Aberto-Fechado’ Se vocé langar uma excegdo a ser verificada a partir de um método em seu cddigo e 0 fiver ric primeiro sua estrutura try-catch-finally 107 catch estiver trés niveis acima, serd preciso declard-la na assinatura de cada método entre vocé e 0 catch. Isso significa que uma modificag30 em um nivel mais baixo do software pode forcar a alteragao de assinaturas em muitos niveis mais altos. Os médulos alterados podem ser reconstruidos ¢ redistribuidos, mesmo que nada inerente a eles tenha sido mudado. Considere a hierarquia de chamadas de um sistema grande. As fung6es no topo chamam as abaixo delas, que chamam outras abaixo delas e ad infinitum. Agora digamos que uma das fungdes dos niveis mais baixos seja modificada de uma forma que ela deva lancar uma exce¢o. Se essa exceedo for verificada, entéo a assinatura da fungdo deverd adicionar uma instrugdo throws. Mas isso significa que cada fungao que chamar nossa fun¢o modificada também deverd ser alterada para capturar a nova exceg3o ou anexar a instrugo throws apropriada a sua assinatura Ad infinitum. O resultado aninhado é uma cascata de alteragdes que vao desde os niveis mais baixo do software até o mais alto! Quebra-se o encapsulamento, pois todas as fimgdes no caminho de um langamento (throw) devem enxergar os detalhes daquela excegao de nivel mais baixo. Segundo 0 propésito de excegdes de que elas Ihe permitem tratar erros distantes, é uma pena que as excegdes verificadas quebrem dessa forma o encapsulamento. ‘As excegies verificadas podem as vezes scr titeis se vocé estiver criando uma biblioteca critica: é preciso captura-las. Mas no desenvolvimento geral de aplicativo, os custos da dependéncia superam as vantagens. Forneg¢a excegdes com contexto Cada excegao lancada deve fornecer contexto o suficiente para determinar a fonte ea localizagao de um erro, Em Java, vocé pode pegar um stack trace de qualquer exeegao; entretanto, ele no consegue lhe dizer o objetivo da operagao que falhou. Crie mensagens de erro informativas e as passe juntamente com as excegdes. Mencione a operagao que falhou ¢ o tipo da falha. Se estiver registrando as agdes de seu aplicativo, passe informagées suficientes para registrar o erro de seu catch. Defina as classes de excecdes segundo as necessidades do chamador Ha muitas formas de classificar erros. Pode ser pela origem: eles vieram desse componente ou daquele? Pelo tipo: sao falhas de dispositivos, de redes ou erros de programagao? Entretanto, quando definimos as classes de excecao num aplicativo, nossa maior preocupagao deveria ser como elas sao capturadas. Vejamos um exemplo de uma classificagdo ruim de excegao. Aqui, ha uma estrutura try... catch...finally para uma chamada a uma biblioteca de outro fabricante. Ela cobre todas as excegdes que a chamada talvez lance: ACMEPort port = new ACMEPort (12); try { port.open(); } catch (DeviceResponseException e) ( reportPortError(e); logger.log("Device response exception", e); } catch (ATM1212UnlockedException e) { reportPortError(e); 108 Capitulo 7: Tratamento de Erro logger.log("Unlock exception", el; } catch (GMXError e) { report PortError(e); logger.log("Device response exception"); } finally { } ‘Acestrutura possui muita duplicagao, nfio deveriamos estar surpresos. Na maioria dos casos de tratamento de excegdes, 0 que fazemos ¢ relativamente padrao, independente da situa¢Zo no momento. Temos de registrar um erro € nos certificar que podemos prosseguir. Neste caso.como sabemos quea tarefa que estamos fazendo ¢ basicamenteamesma independente da exce¢io, podemos simplificar nosso cédigo consideravelmente. Para isso, pegamos a API que estamos chamando e garantimos que ela retorne um tipo comum de excecio. LocalPort port = new Localport(12); try { port.open(); } catch (PortDeviceFailure e) { reportError(e); logger.log(e.getMessage(), ©); } finally { d Nossa classe Loce1Port é um simples wrapper (“empacotador”) que captura ¢ traduz as excegées lancadas pela classe ACMEPort: public class Localport { private ACMEPort innerPort; public LocalPort(int portNumber) { innerPort = new ACMEPort(portNumber); 3 public void open() { try { innerPort .open(); } catch (DeviceResponseException e) { throw new PortDeviceFailure(e); } catch (ATM1212UnlockedException e) { throw new PortDeviceFailure(e); } catch (GMXError e) { throw new PortDeviceFailure( } } Wrappers como 0 que definimos para a ACMEPort podem ser muito iiteis. Na verdade, empacotar APIs de terceiros é a melhor pratica que existe. Ao fazer iss, vocé minimiza as dependéncias nelas: vocé pode escolher migrar para uma biblioteca diferente no futuro sem Defina 0 fluxo normal 109 muitos problemas. Empacotar também facilita a simulacao de chamadas de terceiros quando for testar seu proprio cédigo. ‘Uma tiltima vantagem de empacotar (wrapping) ¢ que vocé no fica preso as escolhas do modelo de API de um fornecedor em particular. Vocé pode definir a API que preferir. No exemplo anterior, definimos um tnico tipo de exce¢ao para a falha do dispositivo port ¢ descobrimos que poderiamos escrever um cédigo muito mais limpo. Geralmente, uma iinica classe de excegdo esté bom para uma parte especifica do cédigo. As informacées enviadas com a excegao podem distinguir os erros. Use classes diferentes apenas se houver casos cm que vocé qucira capturar uma cxce¢ao ¢ permitir que a outra passe normalmente. Defina o fluxo normal Se vocé seguir os conselhos das segdes anteriores, acabara com uma boa quantidade de divisao entre sua légica do negécio e seu tratamento de erro. ‘A maioria de seu cédigo comecard a parecer um algoritmo limpo ¢ sem apetrechos. Entretanto, esse processo eleva ao maximo a detecgao de erro em seu programa. Vocé empacota suas APIs de modo que vocé possa langar suas proprias excegdes e definir um controlador acima de seu cédigo para que vocé possa lidar com qualquer computagao cancelada. Na maioria das vezes, essa é uma abordagem 6tima, mas ha situagdes nas quais voce. talyez nao queira cancelar, ‘Vejamos um exemplo. Abaixo esta um cédigo confuso que soma as despesas em um aplicativo de finangas: try { Moalgxpenses expenses = expenseReportDAO.getMeals(employee. get ID); m_total += expenses.getTotal(); } catch(MealExpensesNotFound e) ( m_total += getMealPerDiem(); + Neste negécio, se as refeigdes (meals) forem um custo, elas se tornam parte do total. Caso contrario, o funcionario (employee) recebe uma quantia para ajuda de custos (PerDiem) pela refeicao daquele dia. A excecao confunde a légica. ‘Nao seria melhor se nao tivéssemos de lidar com o caso especial? Dessa forma, nosso cédigo seria muito mais simples. Ele ficaria assim: MealExpenses expenses = expenseReportDAO.getMeals(employee.getID()); m_total += expenses.getTotal(); Podemos tornarocédigomais simples? Parece quesim. Podemos alterara ExpenseReportDAO de modo que ela sempre retorne um objeto Meal Expense. Se nao houver gastos com refeigdes, 110 Capitulo 7: Tratamento de Erro ela retorna um objeto Meal Expense que retorna a ajuda de custos como seu total: public class PerDiemMealzxpenses implements MealExpenses ( public int getTotal() ( J/ vetorna a ajuda de custes padrao } ) Isso se chama 0 Padrio Special Case (‘padrao do caso especial’), de Fowler. Vocé cria uma classe ou configure um objeto de modo que ele trate de um caso especial para vocé. Ao fazer isso, 0 cOdigo do cliente ndo precisa lidar com o comportamento diferente. Este fica encapsulado num objeto de caso especial. Nao retorne null ‘Acho que qualquer discussao sobre tratamento de erro deveria incluit as coisas que fazemos que levam a erros. A primeira da lista seria retornar null. Ja perdi a conta dos aplicativos que jé vi que em quase toda linha verificava por null, Abaixo esta um exemplo: public void registerItem(Item item) ( if (item != null) { TtemRegistry registry if (registry != null) Ttem existing = registry.getitem(item.getID()); if (existing.getBillingPeriod().hasRetailowner()) { existing.register(item); = peristentStore.getivemRegistry(}; } } Se vocé trabalhar num cédigo-fonte como esse, ele pode nao parecer te ruim assim para vocé, mas ele é! Quando retornamos nu11, basicamente estamos criando mais trabalho para nos mesmos ¢ jogando problemas em cima de nossos chamadores. 6 basta esquecer uma verificagio mm11 para que o aplicativo fique fora de controle. Percebeu que nao havia uma verificagdo de null na segunda linha do if aninhado? O que teria acontecido em tempo de execug’o se persistentStore fosse null? Teriamos uma Null PointerException em tempo de execugao, e ou alguém esta capturando-a no nivel mais alto ou ndo. Em ambos os casos isso ¢ péssimo. O que vocé faria exatamente em resposta a um langamento de Nul1lPointerExcept ion das profundezas de seu aplicativo? E facil dizer que o problema com o cédigo acima ¢ a falta de uma verificagdo de null. mas, na verdade, o problema ¢ que ele tem muitos. Se vocé ficar tentado a retornar nui1 de um método, em vez disso, considere langar uma exce¢io ou retornar um objeto SPECIAL CASE. Se estiver chamando um método que retorne nul a partir de uma API de terceiros, considere empacoté-lo com um método que lance uma exceg%o ou retorne um objeto de caso especial Em muitos casos, objetos de casos especiais so uma solugao facil. Imagine que seu cédigo fosse assi List employees = getEmployees(); if (employees != null) ( for (Employee e : employees) { Nao passe null ul totalPay += e.getPay(); } Neste momento, getEmployees pode retomnar null, mas ele precisa? Se alteréssemos getEmployee de modo que ele retornasse uma lista vazia, poderiamos limpar o cédigo: List employees = getEmployees(); for(Employee e : employees) { totalPay += e.getPay(}; d Felizmente, Java possui o Collections.emptyList(), ¢ ele retorna uma lista predefinida e imutdvel que podemos usar para esse propésito: public List getEmployees() { if(.. there are no employees .. ) return Collections. emptyList (); } Se programar dessa forma, vocé minimizara a chance de Nul1PointerExceptions ¢ seu cédigo sera mais limpo Nao passe null Retomar null dos métodos é ruim, mas passar nu11 para eles é pior. A menos que esteja trabalhando com uma API que espere receber null, vocé deve evitar passa-lo em seu cédigo sempre que possivel ‘ Vejamos um exemplo do porqué. Abaixo esté um método simples que calcula a disténcia entre dois pontos: public class MetricsCalculator { public double xProjection(Point pl, Point p2) { return (p2.x - pl.x) * 1.57 3 ) O que acontece quando alguém passa nul 1 como parametro? calculator.xProjection(null, new Point (12, 13))+ Receberemos uma Nul1PointerException, é claro. Podemos consertar isso? Poderiamos criar um novo tipo de excegao e langd-lo: public class MetricsCalculator { public double xProjection(Point pl, Point p2) { if (pl null || p2 null) { 112 Capitulo 7: Tratamento de Erro throw InvalidArgumentException( “Invalid argument for Met ricsCalculator-xProjection’! } return (p2.x - pl.x) * 1.5; i Ficou melhor? Pode ser um pouco melhor do que uma excegdo de ponteiro nul, mas lembre-se de que temos de definir um tratador para Tnval idArgument Except ion. O que ele deve fazer? Ha algum procedimento bom? Ha uma alternativa. Poderiamos usar uma série de confirmagdes: public class Metricscalculator ¢ public double xProjection(Point pl, Point p2) { assert pl != null : “pl n&o pode ser nulo"; assert p2 != null : “*p2 ndo pode ser nulo”; return (p2.x - pl.x) * 1.5; } E uma boa informacio, mas nao resolve o problema. Se alguém passar nu11, ainda teremos um erro em tempo de execugao. Na maioria das linguagens de programagao nfo ha uma boa forma de lidar com um valor nulo passado acidentalmente para um chamador. Como aqui este € 0 caso, a abordagem logica seria proibir, por padrao, a passagem de nu 11. Ao fazer isso, vocé pode programar com 0 conhecimento de que um nu11 numa lista de parimetros ¢ sinal de problema, e acabar com mais alguns erros por descuide. Conclusiio Um cédigo limpo é legivel, mas também precisa ser robusto. Esses objetivos nao sdo conflitantes. Podemos criar programas limpos c robustos se enxergarmos 0 tratamento de erro como uma preocupacdo & parte, algo que seja visivel independentemente de nossa légica principal. Na medida em que somos capazes de fazer isso, podemos pensar nisso de forma independente e dar um grande passo na capacidade de manutengao de nosso cédigo. Bibliografia [Martin]: Agile Software Development: Principles, Patterns, and Practices, Robert C. Martin, Prentice Hall. 2002. 8 Limites por James Grenning Raramente controlamos todos os softwares em nossos sistemas. De vez em quando compramos pacotes de outros fabricantes ou usamos cddigos livres, ou dependemos de equipes em nossa propria empresa para construir componentes ou subsistemas para nds. De algum modo, devemos integrar, de forma limpa, esse cddigo externo ao nosso. Neste capitulo veremos as praticas e técnicas para manter limpos os limites de nosso software. 4 Jo 8: Limites O uso de cédigos de terceiros Ha uma tensio natural entre o fornecedor de uma interface e seu usuario. Os fornecedores de pacotes ¢ frameworks de outros fabricantes visam a uma maior aplicabilidade de modo que possam trabalhar com diversos ambientes ¢ atender a um pablico maior, Jd os usuarios desejam uma interface voltada para suas proprias necessidades. Essa tensio pode causar problemas nos limites de nossos sistemas. Tomemos 0 java.util.Map como exemplo. Como pode ver na Figura 8.1, 0s Maps tem uma interface bastante ampla com diversas capacidades, Certamente esse poder e flexibilidade so titeis, mas também pode ser uma desvantagem. Por exemplo, nosso aplicativo pode construir um Map ¢ passi-lo adiante. Nosso objetivo talvez seja que nenhum dos recipientes de nosso Map ndo exclua nada do Map. Mas logo no inicio da lista esté o método clear (). Qualquer usudrio do Map tem o poder de apaga-lo. Ou talvez, segundo a convengaio que adotamos, 0 Map pode armazenar apenas certos tipos de objctos, mas nao € certo que ele restrinja os tipos que so adicionados a ele. Qualquer usuério determinado pode adicionar itens de qualquer tipo 4 qualquer Map Se nosso aplicativo precisa de um Map de Sensors, vocé pode Se deparar com um Sensors assim: clear() void - Map containskey(Object key) boolean - Map containsValue(Object value) boolean - Map entrySet() Set - Map equals(Object 0) boolean - Map get(Object. key) Object - Map getClass() Class - Object hashCode() int - Map isEmpty() boolean - Map keySet() Set - Map notify() void - object notifyAll() void - Object putiobject key, Object value) Object - Map putAll(Map t} void — Map remove(Object key) Object - Map size() int - Map toString() String - Object values() Collection - Map wait() void - Object wait (long timeout) void - Object wait(long timeout, int nanos) void - Object Figura 8.1: Métodos do Map Se nosso aplicativo precisar de um Map de sensors, vocé talvez encontre sensors assim: Map sensors = new HashMap(); E, quando alguma outra parte do cédigo precisa acessar 0 Sensor, voce ve isso: Sensor s = (Sensor)sensors.get(sensorld ); O uso de eédigos de terceiros 115 Nao vemos isso apenas uma vez, mas varias ao longo do cédigo. O cliente deste cédigo fica com a responsabilidade de obter um Object do map e atribui-lo o tipo certo. Apesar de funcionar, nflo € um cédigo limpo. Ademais, esse cédigo no explica muito bem o que ele faz. Pode-se melhorar consideravelmente sua legibilidade com o uso de tipos genéricos, como mostra abaixo: Map sensors = new HashMap(): Sensor s = sensors.get(sensorid ); Entretanto, isso nao resolve o problema de que wap oferece mais capacidade do que precisamos ou queremos. Passar adiante pelo sistema uma insténcia de Map significa que havera varios Ingares para mexer se a interface para o Map mudar, Vocé talvez pense que uma mudanga seja pouco provivel. mas lembre-se de que houve uma quando o suporte a genéricos foi adicionado no Java 5, De fato, ja vimos sistemas que impedem o uso de genéricos devido 4 magnitude das alteragBes necessarias para manter 0 uso abrangente dos Maps. ‘Abaixo esti uma forma limpa de usar o Map. Nenhum usudrio do Sensors se importaria se um pouco de genéricos for usado ou no, Essa escolha se tornou (e sempre deve ser) um detalhe da implementagao. public class Sensors i private Map sensors = new HashMap(); public Sensor getByIdistring id) ( return (Sensor) sensors.get(id): } //eodigo 3 ‘A interface no limite (Map) esta oculta. E possivel alteri-la causando muito pouco impacto no resto do aplicativo. O uso de tipos genéricos nao é mais uma questiio tao problematica assim. pois o gerenciamento de declaragdes e de tipos € feito dentro da classe Sensors. Essa interface também foi personalizada para satisfazer as necessidades do aplicativo. Seu resultado é um cédigo mais facil de se entender e mais dificil de ser utilizado incorretamente. A classe Sensors pode forgar reuras de modelo de negocios ‘Nao estamos sugerindo que cada uso do map seja encapsulado dessa forma. Mas lhe aconselhando para nao passar 0s Maps (ou qualquer outra interface num limite) por todo 0 sistema. Se usar uma interface, como a Map, no limite, a mantenha numa classe ou préxima a uma familia de classes em que ela possa ser usada. Evite retorné-la ou aceiti-la como pardmetro em APIs piblicas Explorando e aprendendo sobre limites Cédigos de terceiros nos ajudam a obter mais funcionalidade em menos tempo. Por onde comegar quando desejamos utilizar pacotes de terceiros? Nao ¢ tarefa nossa testa-los, mas pode ser melhor para nés criar testes para os cédigos externos que formos usar. Suponha que nfo esteja claro como usar uma biblioteca de terceiros. Podemos gastar um dia ou dois (até mais) lendo a documentagao e decidindo como vamos usé-la. Entio, escreveriamos 116 Capitulo 8: Limites nosso cédigo para usar 0 cédigo externo ¢ vemos se ele € ou ndo 0 que achdvamos. Nao ficariamos surpresos de acabar em longas sessdes de depuragao tentando descobrir se os bugs que encontramos so do nosso cddigo ou no deles. Entender codigos de terceiros € dificil. Integra-lo ao seu também é. Fazer ambos ao mesmo tempo dobra a dificuldade. E se adotassemos uma outra abordagem? Em vez de experimentar ¢ tentar o novo cédigo, poderiamos criar testes para explorar nosso conhecimento sobre ele. Jim Newkirk chama isso de testes de aprendizagem'. Nesses testes, chamamos a API do cédigo externo como o fariamos ao usd-la em nosso aplicativo. Basicamente estariamos controlando os experimentos que verificam nosso conhecimento daquela API. O teste se focaliza no que desejamos saber sobre a API. Aprendendo sobre log4j Digamos que queremos usar 0 pacote 1og4j do Apache em vez de nosso proprio gravador de registro interno. Baixariamos o pacote ¢ entao abririamos a pagina de documentagao inicial. Sem ler muito, criamos nosso primeiro caso de teste, esperando que seja impresso “oi” no console. @Test public void testLogcreate() { Logger logger = Logger.getLogger(*MyLogger loager.info("oi"); 1 Quando 0 executamos, o registrador (logger) produz um erro 0 qual nos diz que precisamos de algo chamado Appender. Apés lerum pouco mais, descobrimos que existe um Consol eAppender. Entio, criamos um ConsoleAppender e vemos se desvendamos os segredos de registro no console. @Test public void testLogAddappender() { Logger logger = Logger.getLogger(“MyLogger"); Consoleappender appender = new Consoleappender(); logger. addaAppender (appender); logger.info(*oi"); Desta vez, descobrimos que 0 Appender no possui fluxo de saida, Estranho... Parecia légico ter um. Depois de buscar ajuda no Google, tentamos 0 seguinte: @rest public void testLogAddAppender() { Logger logger = Logger.getLogger(*MyLogger”); logger.removeAllappenders(); logger.addAppender (new ConsoleAppender( new PatternLayout("$p %t &mén"), ConsoleAppender. SYSTEM_OUT)}; logger. info(*oi"); Aprendendo sobre log4) 7 Funcionou. Uma mensagem de registro com “oi” apareceu no console! Parece estranho ter de dizer ao Consoleappender 0 que cle precisa eserever no console. Mais interessante ainda é quando removemos o parimetro ConsoleAppender .SystemOut ¢ ainda é exibido “oi”. Mas quando retiramos 0 PatternLayout, mais uma vez hé mensagem de falta de um fluxo de saida. Esse comportamento é muito estranho. Lendo a documentagio com um pouco mais de atengio, vimos que 0 construtor Consoleappender padrio vem “desconfigurado”, o que nao parece muito Sbvio ou pratico. Parece um bug, ou pelo menos uma inconsisténcia, no 10045, Recorrendo novamente ao Google, lendo e testando, acabamos chegando a Listagem 8.1. Descobrimos bastante coisa sobre como funciona 0 1og4j, e colocamos esse conhecimento numa série de testes simples de unidade. Listagem 8-1 LogTest .java class Logtest { ivate Logger logger; void initialize() { ger = Logger .getLogger (*loager" ogger .removeallappenders() ; Logger .get Root Logger (] .renoveal lappenders () ; @Test public void basiclogger() { BasicConfigurator .configure(); logger. info|"basicLogger*) ; } eres public void addAgpenderWithStream(| ( logger.addappender (new ConsoleAppender ( new PatternLayout ("$p &c én"), Consolearpender .S¢STEM_OUT}); logger. info ("addappenderWitnstream") : atest public void addAppenderWithoutStream() { ‘Joager . addAppender (new Consoleappender ( new PatternLayout ("pt $mbn")))7 logger .1nfo(*addappenderwithoutstream") ; ‘Agora sabemos como obter um console simples e inicializado de registro, ¢ podemos encapsular esse conhecimento em nossas classes de registro de modo que o resto de nosso aplicativo fique isolado da interface limite do !og4j. Os testes de aprendizagem sao melhores que de graga Os testes de aprendizagem acabam no custando nada. Tivemos de aprender sobre a API mesmo, ¢ escrever aqueles testes foi uma forma ficil ¢ separada de obter 0 conhecimento que 118 Capitulo 8: Limites conseguimos. Os testes de aprendizagem sio experimentos precisos que ajudam a aumentar nosso entendimento. Esses testes nao sé saem de graca como geram um retoro positivo de nosso investimento. Quando houver nossas distribuicdes daquele pacote externo, podemos executar os testes para ver se ha diferengas nas atividades. Os testes de aprendizagem verificam se os pacotes de terceiros que estamos usando se comportam como desejamos. Uma vez integrados, nao ha garantias de que 0 cédigo se mantera compativel com as nossas necessidades. Os autores originais sofrerdo pressdio para alterarem seus cédigos para satisfazer suas préprias necessidades. Eles consertario bugs ¢ adicionario novos recursos. Cada distribuigdo vem com um novo risco. Se 0 pacote for alterado de uma forma que fique incompativel com nossos testes, descobriremos de imediato. Vocé precise ou nao do conhecimento proporcionado pelos testes de aprendizagem, deve-se definir um limite claro por meio de uma série de testes externos que experimentem a interface da mesma forma que scu codigo faria. Sem esses testes limite para facilitar a migracao. poderemos ficar tentados a manter por mais tempo do que deveriamos a versio antiga. O uso de cédigo que nao existe ainda Ha outro tipo de limite, um que separa o conhecido do desconhecido. Geralmente ha lugares no cédigo onde nosso conhecimento parece sumir. As vezes, 0 que est do outro lado nos & desconhecido (pelo menos agora). As vezes, optamos nao olhar além do limite. Alguns anos atras fiz parte do time do desenvolvimento de software para um sistema de comunicagao de radios. Havia um subsistema, 0 “Transmissor”, que eu pouco sabia a respeito, @ as pessoas responsiiveis por ele nao tinham ainda definido sua interface. Nao queriamos ficar parados, entao comegamos a trabalhar longe daquela parte desconhecida do eédigo. Sabiamos muito bem onde nosso mundo terminava ¢ onde comegava 0 novo. Conforme trabalhavamos, as vezes chegavamos a esse limite entre os dois mundos. Embora névoas ¢ nuyens de ignorancia ofuscassem nossa visio para além do limite, nosso trabalho nos mostrou 0 que queriamos que fosse a interface limite. Desejévamos dizer ao transmissor algo assim: Configure 0 transmissor na frequéncia fornecida e emita uma representacdo analégica dos dados provenientes desde fluxo. Nao tinhamos ideia de como isso seria feito, pois a API ainda nao havia sido desenvolvida. Portanto, decidimos trabalhar nos detalhes depois. Para ndo ficarmos parados, definimos nossa propria interface. Demos um nome facil de lembrar, como Transmitter. Criamos um método chamado transmit que pegava uma frequéncia ¢ um fluxo de dados. Essa cra a interface que gostariamos de ter. O bom de criar a interface que desejamos é que podemos controlé-la, Isso ajuda a manter 0 cédigo do lado do cliente mais legivel ¢ centralizado na fungao para a qual fora criado. Na Figura 8.2 vocé pode ver que preenchemos as classes do CommunicationsController a partir da API do transmitter, a qual nfo controlavamos e havia sido definida. Ao usar nossa prépria interface para o aplicativo, mantivemos limpo ¢ expressivo nosso cédigo do CommunicationsController. Uma vez definida a API do transmitter, criamos o O uso de eédigo que niio existe ainda 119 transmitterAdapter para fazer a conexio. O ADAPTER? encapsulow a interago com a API € forneceu um tinico local para ser modificado se a API for aperfeigoada. <<> ‘API do Transmitter Figura 8.2: Adivinhando devera ser o transmitter Esse modelo também nos da um seam? bastante conveniente no cédigo para testarmos. ‘Ao usar UM FakeTransmitter (transmissor falso) adequado, podemos testar as classes do CommunicationsController, Além de podermos criar testes limite uma vez que temos a ‘TransmitterAPT para garantir que estamos usando corretamente a API Limites limpos Coisas interessantes ocorrem nos limites. A alieragio ¢ uma delas. Bons projetos de software comodam modificagdes sem muito investimento ou trabalho. Quando usamos cédigos que estiio fora de controle, deve-se dar uma atengao especial ao nosso investimento ¢ garantir que uma mudanca futura nao seja muito custosa. © cédigo nos limites precisa de uma divisio clara ¢ testes que definem o que se deve esperar. Devemos evitar que grande parte de nosso cédigo enxergue as particularidades dos de tercciros. E melhor depender de algo que vocé controle do que pegar algo que acabe controlando vocé Lidamos com 0s limites de cédigos externos através de alguns poucos lugares em nosso cédigo que fazem referéncia a cles. Podemos empacota-los como fizemos com 0 Map, ou talvez usar um ADAPTER para converter nossa interface perfeita na que nos for fornecida. De qualquer forma, nosso cédigo se comunica melhor conosco, prové uso consistente interno pelo limite © possui poucos pontos para serem mexidos quando o cédigo externo sofrer alteracao. Bibliografia [BeckTDD}: Zest Driven Development, Kent Beck, Addison-Wesley, 2003. [GOE}: Padraes de Projeto, Solugdes Reuti et al., Addison-Wesley. 1996. izdveis de Software Orientado a Objetos, Gamma [WELC]: Working Effectively with Legacy Code, Addison-Wesley, 2004. Seimei ecihitas Rdieniees GH. 9 wate de Unidade Nossa profisso evoluiu bastante ao longo dos ultimos dez anos. Em 1997 nao se ouvia falar em Desenvolvimento Dirigido a Testes (TDD, sigla em inglés). Para a grande maioria de nds, os testes de unidade eram um pequeno pedaco de cédigo descartével que escreviamos para nos certificar que nossos programas funcionavam. Meticulosamente cridvamos nossas classes © métodos ¢, entdo, improvisévamos um cédigo para testi-los. Tipicamente, isso envolvia um programa simples de controle que nos permitisse interagir manualmente com o programa que haviamos escrito. Lembro-me de ter criado em meados da década de 1990 um programa em C++ para um sistema integrado em tempo real. Era um simples contador com a seguinte assinatura: 122 Capitulo 9: Testes de Unidade void Timer::ScheduleCommand(Command* theCommand, int milliseconds} A idéia era simples: 0 método execute de Command seria executado numa nova thread apos um nimero especifico de milésimos de segundos, O programa era como testé-lo. Crici um simples programa de controle que esperava alguma ago no teclado. Sempre que um caractere era pressionado, ele agendaria um comando que escreveria 0 mesmo caractere cinco segundos depois, Entio eu digitava uma melodia no teclado e esperava que cla fosse reproduzida na tela cinco segundos depois. “Bu... quero-uma-mulher ... igual ... a-com-quem-me-ea ... sei ... querido ... pa... pai.” Realmente cantei essa melodia enquanto digitava o ponto “.” ¢, entéo, a cantava novamente quando aparecia na tela. Esse era meu teste! Depois que o vi funcionar ¢ 0 mostrei aos meus colegas, joguei 0 codigo do teste fora. Como eu disse, nossa profisso evoluiu. Atualmente cu criaria um teste que garantisse que cada canto do cédigo funcionasse como cu esperava. Eu isolaria meu cédigo do resto do sistema operacional em vez de apenas invocar as fngdes padrao de contagem; simularia aquelas fungdes de modo que eu tivesse controle absoluto sobre o tempo; agendaria comandos que configurassem flags booleanas; ¢, entdo, adiantaria o tempo. observando as flags e verificando se elas mudavam de falsas para verdadeiras quando eu colocasse o valor correto no tempo. Quando pego uma colegao de testes para passar adiante,cu me certifico se cles so adequados para serem executados por qualquer pessoa que precise trabalhar com 0 cddigo, e se eles ¢ 0 cédigo estavam juntos no mesmo pacote de origem. Isso, progredimos bastante, mas ainda podemos ir mais longe. Os movimentos do Agile do TDD tém incentivado muitos programadores a criarem testes de unidade automatizados, ¢ muitos outros esto se unindo a cada dia. Mas nessa correria para adicionar testes a0 nosso oficio, muitos programadores t&m se esquecido de alguns dos pontos mais sutis ¢ importantes de se escrever bons testes. As trés leis do TDD Hoje em dia todos sabem que 0 TDD nos pede para criar primeiro os testes de unidade antes do cédigo de produc&o. Mas essa regra é apenas 0 inicio. Considere as trés leis' abaixo: Primeira Lei Nao se deve escrever 0 cédigo de produso até criar um teste de unidade de falhas. Segunda Lei Nao se deve escrever mais de um teste de unidade do que o necessirio para falhar, e nfo compilar é falhar. Terceira Lei Nao se deve escrever mais cédigos de produg&o do que o necessério para aplicar 0 teste de falha atual. cremate ¢” amuatine tubo tdsereew it diuthees- wailinitanlie iit Hick: SAM Wi Como manter os testes limpos 123 Essas trés leis Ihe colocam numa rotina que talvez dure trinta segundos. Os testes € 0 cédigo de produgao sao escritos juntos, com os testes apenas alguns segundos mais adiantados, Se trabalharmos dessa forma, criariamos dezenas de testes a cada dia, centenas a cada més milhares a cada ano; os testes cobririam praticamente todo 0 nosso cédigo de produgio. O tamanho completo desses testes. que pode competir com 0 tamanho do préprio cédigo de produgio, pode apresentar um problema de gerenciamento intimidador. Como manter os testes limpos Hé alguns anos, pedi para orientar uma equipe que tinha decidido explicitamente que seus codigos de testes ndo deveriam ser preservados segundo os mesmos padrdes de qualidade que seu codigo de produgao. Eles se deram autorizagao para violar as leis em seus testes de unidade. “Rapida e porcamente” era o lema. As variéveis ndo precisavam de nomes bem selecionados, as fungdes de teste nao tinham de ser curtas e descritivas. Os cédigos de testes nao precisavam ser bem desenvolvidos ¢ divididos de modo pensado. Se que o resto dos cédigos de testes funcionasse ¢ que cobrisse 0 codigo de produgao, jé era 0 suficiente, Alguns de vocés lendo isso talvez simpatizem com tal decisdio. Talvez, la no passado, vocé criou testes tipo aquele que fiz para aquela classe Timer. E um grande passo ir da criagiio daquele tipo de teste descartavel para uma colegao de testes de unidade automatizados. Portanto, assim como a equipe que eu orientara, vocé talve7 decida que testes feitos “porcamente” sejam melhores do que nada. que aquela equipe ndo percebera era que ter testes daquele tipo ¢ equivalente, se no pior, anio ter teste algum. © problema é que muitos testes devem ser alterados conforme 0 codigo de produgfo evolui. Quanto pior o teste, mais dificil sera de mudé-lo. Quanto mais confuso for 0 cédigo de teste, so maiorcs as chances de vocé levar mais tempo espremendo novos testes para dentro da colegio do que na criagao do cédigo de produg’io, Conforme voc modifica o eédigo de produgao, os testes antigos comegam a falhar ¢ a bagunca no cédigo de teste dificulta fazé- Jos funcionar novamente. Sendo assim, os testes comegam a ser vistos como um problema em constante crescimento. De distribuigdo em distribuigao, o custo de manutengiio da colegao de testes de minha equipe aumentou. Com o tempo, isso se tornou a maior das reclamagdes entre os desenvolvedores. Quando (08 gerentes perguntaram 0 motivo da estimativa de finalizagdo estava ficando to grande, os desenvolvedores culparam os testes. No final, foram forgados a descartar toda colegao de testes. Mas, sem uma colegao de testes, eles perderam a capacidade de garantir que. apés alteragOes no cédigo-fonte, ele funcionasse como o esperado e que mudangas em uma parte do sistema nfio afetariam as outras partes. Entio, a taxa de defeitos comegou a crescer. Conforme aumentava 0 niimero de bugs indesejaveis, comegaram a temer fazer alteragdes ¢ pararam de limpar 0 codigo de produgo porque temiam que isso poderia fazer mais mal do que bem. O cédigo de producdo comecou a se degradar. No final das contas, ficaram sem testes, com um cédigo de producto confuso ¢ cheio de bugs, com consumidores frustrados ¢ 0 sentimento de que 0 esforco para criagiio de testes nao valeu de nada. De certa forma estavam certos. Tal esforgo tinha sido em yao. Mas fora decisdo deles permitir que 0s testes ficassem uma bagung¢a ¢ se tornasse a origem do fracasso, Se tivessem mantido os testes limpos, 0 esforco para a criago dos testes ndo teria 0s deixado na mao. Posso dizer isso com um pouco de certeza, pois participei de tudo, e ja orientei muitas equipes que obtiveram sucesso com testes de unidade limpos. 124 Capitulo 9: Testes de Unidade A moral da histéria é simples: Os eddigos de testes sdo téo importantes quanto 0 codigo de produgdo, Ele ndo € componente secundario. Ele requer raciocinio, planejamento e cuidado. preciso manté-lo to limpo quanto o eédigo de produgao. Os testes habilitam as “. Se nao mantiver seus testes limpos, ira perdé-los. E, sem eles. vocé perde exatamente o que mantém a flexibilidade cédigo de produgao. Isso mesmo, vocé leu certo. Sdo os testes de unidade que mantém seus cédigos fiexiveis, reutilizéveis ¢ passiveis de manutengdo. A razao é simples. Se vocé tiver testes, nao tera medo de alterar 0 cédigo! Sem os testes, cada modificag&o pode gerar um bug. Nao importa o grau de flexibilidade de sua arquitetura ou de divisio de seu modelo, pois sem os testes voce ficari relutante em fazer mudangas por temer gerar bugs no detectados. Mas com os testes esse medo praticamente some. Quanto maior a cobertura de seus testes, menor o medo. Vocé pode fazer mudangas quase sem penalidade ao cédigo que tenha uma arquitetura emaranhada ¢ um modelo confuso e opaco. De fato, yocé pode improvisar essa arquitetura e esse modelo sem temer! Portanto, ter uma coleeao de testes de unidade automatizados que cubram 0 cédigo de produgao € 0 segredo para manter seu projeto ¢ arquitetura os mais limpos possiveis. Os testes habilitam todas as “-idades”, pois eles possibilitam as alteracdes. Dessa forma, caso seus testes estejam ruins, ento sua capacidade de modificar seu cédigo fica comprometida e vocé comega a perder a capacidade de melhorar a estrutura dele. Quanto piores forem os testes, pior o cédigo se tora. No final, vocé perde os testes € seu codigo se degrada. Testes limpos O que torna um teste limpo? Trés coisas: legibilidade, legibilidade e legibilidade. Talvez isso seja até mais importe nos testes de unidade do que no cédigo de produg4o. O que torna os testes legiveis? O mesmo que torna todos os cédigos legiveis: clareza, simplicidade e consisténcia de expressiio. Num teste vocé quer dizer muito com o minimo de expressdes possiveis Considere 0 cédigo do FitNesse na Listagem 9.1. Esses trés testes s4o dificeis de entender ¢ certamente podem ser melhorados. Primeiro, ha uma quantidade terrivel de cédigo duplicado [G5] nas chamadas repetidas a addPage e assertSubString. Mais importante ainda é que este cédigo est carregado de detalhes que interferem na expressividade do teste. Listagem 9-1 SerializedPageResponderTest java ‘Testes Limpos 125 Listagem 9-1 (continuacao) SerializedPageResponderTest java request .setResource ("root"); request .addinput (*type", *pages"}; Responder responder = new SerializedageResponder!); SimpleResponse response (SimpleResponse) responder makeResponse ( new FicNesseContext (root), request); ring sml = response.getContent (); assertEquals("text /xml", response. getContentTypel}); assertSubString("PageOne", xml); assertSubString("PageTwoc/name>", xm) ; assertSubstring(**, xml}; } public void testGet Pagelierat chyAsxml DoesnzContainsymbolicLinks throws Except ion t Wikipage pageOne = crawler-addPage (root, PathFarser.parse("P. crawler.addPage (root, PathParser parse (*FageOne.Childone")) ; crawler.addPage (root, PathParser.parse|*FageTwo"!) ; PageData data = pagetne.getPatat); WikiPageProperties properties = data.getProperties(}; WikifageProperty symLinks = properties. set (Symbol icPage. PROP: symbinks.set ("SymPage", "PageTWo") + pagedne.conmt (data) ; request .setResource ("root"): request .addinput ("type", *pages"); Responder responder = new Ser1alizedrageResponder () ; SimpleResponse response (SimpleResponse) responder makeResponse ( new FitNesseContext (roor), request); String xml = response.getContent |); assertEquals (*text/aml", response.getContenttype(}) assert SubStringi*PageOne", xl}; assert Substring ("cnane>PageTwo*, xml1; assert Substring (*Child0nec/name>', xml) ; assertNotSubstring("SymPage", xl); public void testGetDataAsHtml|) throws Exception { crawler.addPage (root, PathParser.parse("festPageOne"}, "test request . setResource ( *TestPageOne*) ; request addinput ("type", "data"); Por exemplo, veja as chamadas para PathParser. Elas transformam strings em instancias PagePath pelos crawler. Essa transformacdo é completamente irrelevante na execug’o do teste e serve apenas para ofuscar 0 objetivo. Os detalhes em torno da criagao do responder ¢ a definigdo do tipo de response também so apenas aglomerados. E tem a inepta forma pela qual 126 Capitulo 9: Testes de Unidade é construido URL requisitado a partir de um resource e um pardmetro. (Ajudei a escrever esse cédigo, portanto me sinto completamente no direito de criticé-lo) No final, esse cédigo ndo foi feito para ser lido, O pobre leitor é inundado com um monte de detalhes que devem ser entendidos antes que os testes fagam algum sentido. Agora, considere os testes improvisados na Listagem 9.2. Eles fazem exatamente 0 mesmo, mas foram refatorados de uma forma muito mais clara e descritiva Listagem 9-2 SerializedPageResponderTest .java (refatorado) public void testGetPageliierarchyasxml() throws Exception { makePages("PageOne". "Fagene.Childdne", *Pagelwo"); submitReguest (*root", *t pages") ; assertkes! OL () + assertResponseContaina "cname>PageOne", *Chi1d0ne* public void testSymbolicLinkaAreNot InzmiFagetiier WikiPage page = makePage("PageCne*); makePages ("PageOne.ChildOne", "PageTwo"| ehy() throws Exception { addLinkTo(page, *EageTwo", "SymPage"); submitRequest ("rost", "type:pages"! ; jesponseTSKML() : esponseContains ( cname>PageOne*, *FageTwo", ‘cname>ChildOne" esponseDoesNotContain(* ymPage”) + public void testGetDataAs%ml () throws Exception nakePageliithConcent (*TestPageGne", ‘test pag submicRequest ("Test eOne", "type:data*); assertResponseTsXMLi); ponseContains(*test page", "<1 thy A estrutura desses testes tornou ébvio o padrio CONSTRUIR-OPERAR-VERIFICAR’. Cada um dos testes esta claramente dividido em trés partes. A primeira produz os dados do teste, a segunda opera neles e a terceira verifica se a operagao gerou os resultados esperados. Note que a grande maioria dos detalhes magantes foi climinada. Os testes vio direto a0 ponto ¢ usam apenas os tipos de dados e fungdes que realmente precisam. Quem ler esses testes dev ser capaz de descobrir rapidamente 0 que fazem, sem se deixar enganar ou ficar sobrepujado pelos detalhes. ‘Testes Limpos 127 Linguagem de testes especifica a0 dominio Os testes na Listagem 9.2 mostram a técnica da construgéio de uma linguagem especifica a um dominio para seus testes. Em vez de usar as APIs que os programadores utilizam para manipular 0 sistema, construimos uma série de fung@es e utilitérios que usam tais APIs e que tornam os testes mais convenientes de se escrever e mais faceis de ler. Esses fungdes e utilitarios se tornaram uma ‘API especializada usada pelos testes, Eles testam a linguagem que os programadores usam para auxilia-los a escrever seus testes e para ajudar aqueles que precisem ler esses testes mais tarde. Essa API de teste nao foi desenvolvida de uma so vez; ela evoluiu de uma continua refatoragao de cédigos de testes que ficaram demasiadamente ofuscados por detalhes confusos. Assim como vocé me viu refatorar a Listagem 9.1 para Listagem 9.2, desenvolvedores disciplinacos também refatoram seus cédigos de teste para formas mais sucintas e expressivas. Um padrao duplo De um certo modo, a equipe que mencionei no inicio deste capitulo estava certa. O cédigo dentro da API de teste tem um conjunto diferente de padrées de engenharia em relagdo ao codigo de produg&o, Ele pode ser simples, sucinto expressive, mas ndo precisa ser mais eficiente do que 0 do cédigo de produgao. Apesar de tudo, ele roda num ambiente de teste, ¢ nado de um de produgao, ¢ esses dois ambientes possuem requisitos diferentes. Considere o teste na Listagem 9.3 que escrevi como parte de um sistema de controle de ambiente que eu estava fazendo a prototipagem. Sem entrar em detalhes, posso the dizer que 0 teste verifica se o alarme de temperatura baixa, o aquecedor e 0 ventilador estao todos ligados quando a temperatura estiver “fria demais”, Listagem 9-3 EnvironmentControllerTest. java ig vold turnOnLoTempalarmAtThreashold!) throws Exception { set Temp (WAY_TOO_COLD} ; controller.tici) assert True (hw. heaterSta assert True (hw. blowerstate assert False (iw.coolerState!} rw hiTempAlarm|)); rt True (hw. loTempalarm()) : © Ha, é claro, muitos detalhes aqui. Por exemplo, o que faz a fungdo tic? Na verdade, prefiro que vocé nao se atenha a isso ao ler esse teste, mas que se preocupe apenas se vocé concorda ou ndo se 0 estado final do sistema é consistente com a temperatura sendo “fria demais”. Note que, ao ler o teste, seus olhos precisam ler e reler entre © nome do estado sendo verificado © a medida do estado sendo verificado. Voc8 vé heaterstate e, entio, seus olhos deslizam para a dircita, para assert True. Vocé vé coolerstate e seus olhos devem se voltar para a 128 Capitulo esquerda, para assertFalse. Isso é entediante e falivel, além de dificultar a leitura do teste. Aperfeigoei a legibilidade deste teste consideravelmente na Listagem 9.4. itagem 9-4 EnvironmentControllerfest.java (refatorado) atest public void turnOnLeTempAlarmAtThreshold|) throws Exception { wayTooCcld() + assercEguals(*HEchL", hy.getState() |; E claro que criei uma fung40 wayTooCo1d para ocultar o detalhe da fung4o tic. Mas 0 que se deve notar é a estranha string em assertZquals. As letras maitsculas significam “ligado” ¢ as minisculas “desligado”, ¢ elas sempre seguem a seguinte ordem: {heater, blower, cooler, hi-temp- alarm, lo-temp-alarm} Mesmo que esse cédigo esteja perto de violar a regra do mapeamento mental’, neste caso parece apropriado. Note que, uma vez conhecendo o significado, seus olhos deslizam sobre a string ¢ vocé logo consegue interpretar os resultados. Torna-se quase uma satisfagao ler o teste. Dé uma olhada na Listagem 9.5 e veja como ¢ facil entender esses testes. Listagem 9-5 EnvironmentControllerfest.java (selecio maior) @Test public void turnOnCoolerAndBlowerIfTocHot (j throws Exception ( tootiot () agsertEquals(*hBchl*, hw.getState!)): aTest teocold() ; void turnOnifiTenpalarmat Thr wayTooHot () 141} throxs Exception { assertEquals (*hBCHI", hw.getstate()|; @Test public void turnOnLoTempAlarmatThreshold() throws Exception [ wayToocold() ; assertEqualsi*HBchL*, hw.getstate()!; A fungao getState esti na Listagem 9.6. Note que esse cédigo nao é muito eficiente. Para isso, provavelmente eu deveria ter usado um StringBuffer ‘Testes Limpos 129 Listagem 9-6 MockControlHardware.java ¢ String getState() { String state = °* state += heater ? state += blower state += cooler 7 "C* state += hivempAlarm ? *H" : *h'; state += lo?empAlarm ? "Lb" : "1"; xeturn state; publ As StringBuffers sdio um pouco feias. Mesmo no cédigo de produgdo irei evita-las se 0 custo for pequeno; € vocé poderia argumentar que 0 custo do cédigo na Listagem 9.6 € bastante pequeno Contudo, esse aplicativo esta claramente em um sistema integrado em tempo real, ¢ ¢ mais provivel que os recursos do computador e da meméria sejam muito limitados. 0 ambiente de teste, entretanto, nao costuma ter limitagdes alguma. Essa € a natureza do padrio duplo. Ha coisa que vocé talvez jamais faga num ambiente de produgao que esteja perfeitamente bem em um ambiente de teste, Geralmente, isso envolve questdes de eficiéncia de memoria e da CPU. Mas nunca de clareza. Uma confirmagao por teste Ha uma escola de pensamento‘ que diz que cada fungao de teste em um teste JUnit deve ter um e apenas uma instrugdo de confirmaco (assert). Essa regra pode parecer perversa, mas a vantagem pode ser vista na Listagem 9.5. Aqueles testes chegam a uma tnica conclusio que ¢ facil e rapida de entender. Mas ea Listagem 9.2? Parece ilégico que poderiamos de alguma forma facilmente adicionar a confirmagao se a saida esti em XML e se ela contém certas substrings. Entretanto, podemos dividir 0 teste em dois, cada um com sua prépria confirmagao, como mostra a Listagem 9.7. Listagem 9-7 SerializedPageResponderTest.java (Confirmag&o Unica) public void testGetPayeHierarchyAsxml () throws Exception { givenPages(*PageOne", *Pagefne.Child0ne", "PageTwo"); whenRequestIeissued(“*roct", "type:pages"); thenResponseShouw]dBexMi |) ; oid testGetPageili erarchy#asRigt ages |*PageOne", "PageOne,Childone", *Pa Tags() throws Exception ( TWO") whenRequestIsTssued(*root', "type:page: thenResponseShouldcontain( *cname>PageOne", "PageTwoc/nane>", *Child0ne* 130 Capitulo 9: Testes de Unidade Note que mudei os nomes das fungdes para usar a convengo comum dado-quando-entiio. Isso facilita ainda mais a leitura dos testes, Infelizmente, como pode ver, dividir os testes pode gerar muito cédigo duplicado, Podemos usar o padrao Template Method para eliminar a duplicagdo e colocar as partes dado/quando na classe base ¢ as partes entdo em derivadas diferentes. Ou poderiamos criar uma classe de teste completamente separada e colocar as partes dado e quando na fung&o @Before e as partes quando em cada fung&o @Test. Mas isso parece muito trabalho para uma questo tao pequena. No final, prefiro as confirmagdes miltiplas na Listagem 9.2. Acho que a regra da confirmagao tnica é uma boa orientacao’. Geralmente tento criar uma linguagem de teste para um dominio especifico que a use, como na Listagem 9.5. Mas nao tenho receio de colocar mais de uma confirmagdio em um teste. Acho que a melhor coisa que podemos dizer é que se deve minimizar 0 numero de confirmagdes em um teste. Um itnico conceito por teste Talvez a melhor regra seja que desejamos um iinico conceito em cada fungtio de teste. Nao queremos fungdes longas que saiam testando varias coisas uma ap6s a outra. A Listagem 9.8 um exemplo disso. Esse teste deve ser dividido em trés independentes, pois ele testa trés coisas distintas. Junta-los todos na mesma fungo obriga o leitor compreender o objetivo de cada fungao presente e o que ela testa. Listagem 9-8 + Miscellaneous teste for the addMonths() method. id testaddmentis () ate dl = SerialDate.createInstance!31, 5, 2004); Date a2 = SerialDate.addMonths (1, dl) assertiquals (30, a2.getDayOfMonth'}); assertEgualsi6, d2.getMonch(i) + assert s(2004, d2.get¥¥¥¥(!); 3 = SerialDate.addMonths(2, dij; 3. getDayOtMonth(}): Month (}) ; Seria asser! agser assert jalDate d4 = Serialfate.addMonths(1, SerialDate.addNon ertBquals(30, d4.getDayOfMionth (}) ; erteduals(?, di.getNonth()); ertaquals (2004, d4.got As trés fungdes de teste devem seguir provavelmente assim: + Dado 0 tiltimo dia de um més com 31 dias (como maio): Um dinico conceito por teste 131 1, Quando vocé adicionar um més cujo iiltimo dia seja o 30° (como junho), entao a data deverd ser o dia 30 daquele més, ¢ nao a 31. 2. Quando vocé adicionar dois meses aquela data cujo iiltimo dia seja 0 31°, entiio a data deverd sero dia 31. * Dado a tiltimo dia de um més com 30 dias (como junho): 1. Quando vocé adicionar um més cujo iltimo dia seja o 31°, entao a data deverd ser dia 30 e nao 31. Explicado dessa forma, vocé pode ver que hé uma regra geral ocultanos testes diversos. Ao incrementar més, a data nao pode ser maior do que o altimo dia daquele més. Isso implica que incrementar o més em 28 de fevereiro resultaria em 28 de margo. Esse teste esté faltando ¢ seria util crid-lo. Portanto, nao sfio as confirmagdes multiplas em cada seco da Listagem 9.8 que causa o problema. E 0 fato de que ha mais de um conceito sendo testado. Assim, provavelmente, a melhor regra scja minimizar 0 mimero de confirmagdes por conceito e testar apenas um conceito por fungdo de teste. ELR.S.T. Testes limpos seguem outras cinco regras que formam o acrénimo em inglés acima (Fast, Independent, Repeatable, Self-validating, Timeh Rapidez os testes devem ser rapidos. Devem executar com rapidez. Quando os testes rodam devagar, vocé no desejard execut4-los com frequéncia. E, consequentemente, nfo encontrara problemas cedo o bastante para conserté-los facilmente, E voc€ nao se sentird livre para limpar 0 codigo, que acabara se degradando. Independéncia os testes ndo devem depender uns dos outros. Um teste nao deve configurar as condicées para o préximo. Vocé deve ser capaz de executar cada teste de forma independente & na ordem que desejar. Quando cles dependem uns dos outros, se o primeiro falhar causara um efeito dominé de falhas, dificultando 0 diagnéstico e ocultando os defeitos abaixo dele. Repetitividade deve-se poder repetir os testes em qualquer ambiente. Vocé deve ser capaz de efetuar testes no ambiente de produgao, no de garantia de qualidade e no seu notebook enquanto volta para casa de trem sem uma rede disponivel. Caso seus testes no possam ser repetidos em qualquer ambiente, ento vocé sempre teré uma desculpa para 0 motivo das falhas. E também perceberd que ndo consegue rodar os testes fora o ambiente adequado. Autovalidagao os testes devem ter uma saida booleana. Obtenham ou nao éxito, vocé nio deve ler um arquivo de registro para saber o resultado. Vocé nao deve ter de comparar manualmente dois arquivos texto para ver se os testes foram bem sucedidos. Se os testes nao possuirem: autovalidacdo, entio uma falha pode se tomar subjetiva, e executar os testes pode exigir uma longa validagao manual. 8. Materiais de Treinamento da Object Mentor. 132 Capitulo 9: Testes de Unidade Pontualidade os testes precisam ser escritos em tempo habil. Devem-se criar os testes de unidade imediatamente antes do cédigo de produgSo no qual serao aplicados. Se crid-los depois, 0 teste do cddigo de producao podera ficar mais dificil. Ou talvez vocé ache que um pouco do cddigo de produgao seja complexo demais para testar. Ou talvez vocé nio crie 0 cédigo de produgao de maneira que possa ser testado. Conclusao ‘Aqui abordamos apenas o inicio deste t6pico. De fato, acho que deveria ter um livro inteiro sobre Testes limpos. Os testes sao tao importantes para a satide de um projeto quanto o cédigo de produgao. Talvez. até mais, pois cles preservam ¢ aumentam a ficxibilidade, capacidade de manutengao e reutilizagao do cédigo de produgao. Portanto, mantenha seus testes sempre limpos. Trabalhe para tornd-los sucintos e expressivos. Invente APIS de teste que funcionem como uma linguagem especifica a um dominio que Ihe ajude a criar seus testes. Se deixar os testes de degradarem, seu codigo também ira. Mantenha limpos os seus testes. Bibliografia IRSpec]: RSpec: Behavior Driven Development for Ruby Programmers. Aslak Hellesoy, David Chelimsky, Pragmatic Bookshelf, 2008. IGOF}: Padrées de Projeto, Solucdes Reutilizéveis de Software Orientado a Objetos, Gamma et al., Addison-Wesley, 1996. 10 Classes com Jeff Langr (aaa Até agora, este livro se focou em como escrever bem linhas e blocos de cédigo. Mergulhamos na composigdo apropriada para fungdes e como elas se interrelacionam. Mas apesar de termos falado bastante sobre a expressividade das instrugdes do cédigo ¢ as fungdes que 0 compoem, ainda no teremos um cédigo limpo até discutirmos sobre os niveis mais altos da organizagao do cédigo. Vamos fala agora sobre classes limpas. 134 Capitulo 10; Classes Organizacao das classes Seguindo uma convengao padrao Java, uma classe deve comegar com uma lista de varidvei As publicas (public), estaticas (static) e constantes (constants), se existirem, devem vir primeiro. Depois vém as varidveis estaticas privadas (private static), seguidas pelas instancias privadas. Raramente ha uma boa razao para se ter uma variavel publica (public). ‘As fungdes ptiblicas devem vir apés a lista de varidveis, Gostamos de colocar as tarefas privadas chamadas por uma fungio piiblica logo depois desta. Isso segue a regra de cima para baixo e ajuda o programa a ser lido como um artigo de jornal. Encapsulamento Gostariamos que nossas variaveis ¢ fungdes fossem privadas, mas nao somos radicais. As vezes, precisamos tornar uma varidvel ou fungdo protegida (protected) de modo que Ppossa ser acessada para testes. Para nds, 0 teste tem prioridade. Se um teste no mesmo pacote precisa chamar uma fungdo ou acessar uma variivel, a toraremos protegida ou apenas no escopo do pacote. Entretanto, primeiro buscaremos uma forma de manter a privacidade. Perder © encapsulamento sempre é 0 ultimo recurso. As classes deyem ser pequenas! A primeira regra para classes é que devem ser pequenas. A segunda ¢ que devem ser menores ainda. Nao, nao iremos repetir o mesmo texto do capitulo sobre Funes. Mas assim como com as finedes, ser pequena também ¢ a regra principal quando o assunto for criar classes. Assim como com as fungdes, nossa questo imediata ¢ “O quo pequena?”. Com as fng3es medimos o tamanho contando as linhas fisicas. Com as classes ¢ diferente. Contamos as responsabilidades'. A Listagem 10.1 realga uma classe, a SuperDashboard, que expde cerca de 70 métodos publicos. ‘Atmaioria dos desenvolvedores concordaria que ela € um pouco grande demais em tamanho. Outros diriam que a SuperDashboard é uma “classe de deus”. Listagem 10-1 Responsabilidade: em excesso SuperDashbeard extends String getCustomizerLanguagerathi) enConfigPath (String systerconfigrath) String getSystenConfighecunent |) set Syst enConfighocument (St public As classes devem ser pequenas! 135 Listagem 10-1 (continuagdo) Responsabilidades em excesso public public public public public public publ. publi public publ. public public public public public public public public public public public public public public public public public public public public public public public public public public Metadbject public public public public publi public public public public public public public boolean isMetadatabirty() void setIsMetadatabirty (boolean isMetadatabirty) Component getLast ForusedComponent () void setLast Focused Component lastFecused) void setMouseSelectState (boolean is¥ouseSelecced) boolean isMouseSelected() LanguageManager getLanguageManager |) ject getProject(} Project getPirstProject() Project getLastProject (i String getNewProjectiiane |) void setComponentsizes(Dimension dim) String getCurrentDir (| void setCurrentDir (String newDir) void updateStatus(int dotPos, int markPos) Class[] getDataBaseClasses |) Metadatafeeder getwetadat aFeeder |) void addProject (Project project) boolean secCurrentProject (Project project) boolean removeProject (Project project} MovaPrcjectHeader getPrograsMetadatai) youd resetDashboard!) Project loadProject (String fileName, String projectMane| void settanSaveMetadata (boolean canSave) MetaObject getSelectedOsject |) void deselect Objectsi) ( t project} oraction(String actiontiame, ActionEvent event) void setNodelint mode) FileManager getfileManager() youd setFileNanager (FileManager f. Configwanager getConfigManager (} yoid setConfigManager (ConfigManager configNanager) ClassLoader getClassLoader () void setClessLoader (ClassLoader classloader) Eroperties getProps() String getUserHome |} String getbaseDir () int getMajorversionNunber () int getMinorVersicnNumber () int getBui létumber !) Meta0bject pasting( target, NetaObject pasted, MetaProject project) void processMenuTtons (MetaObject metaObject| void proceseMenuseparators (Metadbject metadbject) void processTabPages (Neta0bject metadbject) void processPlacement (MeraObject object) void pracessCreatelayout (NetaObject object} void updateDisplayLayer (MetaSbject object, int layerindex) void propertyEditedRepaint (MetaObject object) void praceesDeletedbject (Meta0bject object) boolean getAttachedToDesigner () void processProjectChangedState (boolean hasProjectChanged) void processObjectNameChangad (NetaOaject object) void runProject () eManager) 136 Capitulo 10: Classes Listagem 10-1 (continuagio) Responsabilidades em excesso public void setAcowbragging(boolean ailowDraggina) public boolean allowDragging() public boolean isCustomizing(} public void setTitle(string citle) public IdeMenuBar getIdeMenuBar () public void showHelver (Meta0biect metadbiect /... Many non-public methods follow . String propertyName} Mas € se SuperDashboard possuisse apenas os métodos da Listagem 10,2? Listagem 10-2 Pequena o suficiente? 38 SuperDeshboard extends JFrame inglements MetaDa Component getLastFocusedCorponent () void setLastPocused(Component lastFocused) int getMajorVersionNumber | int getMinorVersionNumber i) ant getBuildNunber () Cinco nao ¢ muito, é? Neste caso é, pois apesar da pequena quantidade de métodos, a SuperDashboard possui muitas responsabilidades: ‘O nome de uma classe deve deserever quais responsabilidades cla faz. Na verdade, sclecionar um nome é a primeira forma de ajudar a determinar 0 tamanho da classe. Se nao derivarmos um nome conciso para ela, entdo provavelmente ela ficara grande. Quanto mais ambiguo for o nome da classe, maiores as chances de ela ficar com muitas responsabilidades. Por exemplo, nomes de classes que possuam palavras de varios sentidos, como Pxocessador ou Gerenciador ou Super, geralmente indicam um acimulo lastimavel de responsabilidades. Devemos também poder escrever com cerca de 25 palavras uma breve descrigdo da classe, sem usar as palavras “se”, “e”, “ou” ou “mas”. Como descreveriamos a Superdashboard? “A SuperDashboard oferece acesso ao componente que foi 0 iiltimo utilizado ¢ também nos permite acompanhar os niimeros da versio ¢ da compilagdo”.O primeiro “e” é uma dica de que a classe possui responsabilidades em excesso. O Principio da Responsabilidade Unica O Principio da Responsabilidade Unica (SRP, sigla em inglés) afirma que uma classe ou médulo deve ter um, ¢ apenas um, motivo para mudar. Este principio nos da uma definigao de responsabilidade ¢ uma orientagao para o tamanho da classe. Estas devem ter apenas uma responsabilidad ¢ um motivo para mudar. Uma classe pequena como essa, a SuperDashboard, na Listagem 10.2, possui duas razdes para ser alterada. Primeiro, ela acompanha a informagao sobre a versdo, que precisa ser atualizada sempre que surgir uma nova distribuigao do software. Segundo, ela gerencia os componentes Swing do Java A Wii lini a eg TOP. As classes devem ser pequenas! 137 (6 um derivado do JFrame, a representagao do Swing de uma janela GUI de alto nivel). Sem diivida iremos querer atualizar o numero da verso se alterarmos qualquer cddigo do Swing, mas 0 oposto nao é necessariamente verdadeiro: podemos mudar as informagdes da versdo baseando- nos nas modificacdes em outros cédigos no sistema, Tentar identificar as responsabilidades (motivos para alteragdo) costuma nos ajudar a reconhecer e criar abstrages melhores em nosso eédigo. Podemos facilmente extrair todos os trés métodos SuperDashboard que lidam com as informagdes de verso em uma classe separada chamada Version. (Veja a Listagem 10.3). A classe Version é um construtor que possui um alto potencial para reutilizagdio em outros aplicativos! Listagem 10-3 Classe com responsabilidade unica public class Version { public int getNajorYersionNumber () public int getMinorversiontunber () public int getBuildxumber (| © SRP é um dos conceitos mais importantes no desenvolvimento OO. E também um dos mais simples para se entender e aprender. Mesmo assim, estranhamente, o SRP geralmente © prinefpio mais ignorado na criagao de classes. Frequentemente, encontramos classes que faz muitas coisas. Por qué? Fazer um software funcionar ¢ torné-lo limpo sao duas coisas bem diferentes. “Amaioria de nés tem uma mente limitada, por isso, ns tentamos fazer com que nosso cadigo possua mais do que organizacao e clareza. Isso ¢ totalmente apropriado. Manter uma separagao de questdes é to importante em nossas atividades de programagéo como em nossos programas. O problema é que muitos de nds achamos que ja terminamos se o programa funciona. Esquecemo-nos da outra questio de organizagao e de clareza, Seguimos para o préximo problenta em vez de voltar ¢ dividir as classes muito cheias em outras com responsabilidades tinicas. ‘Ao mesmo tempo, muitos desenvolvedores temem que um grande niimero de classes pequenas e de propésito tinico dificulte mais o entendimento geral do codigo. Eles ficam apreensivos em ter de navegar de classe em classe para descobrir como € realizada uma parte maior da tarefa. Entretanto, um sistema com muitas classes pequenas no possui tantas partes separadas a mais como um com classes grandes. Ha também bastante a se aprender num sistema com poucas classes grandes. Portanto, a questo €: vocé quer suas ferramentas organizadas em caixas de ferramentas com muitas gavetas pequenas. cada um com objetos bem classificados e rotulados? Ou poucas gavetas nas quais vocé coloca tudo? Todo sistema expansivel podera conter uma grande quantidade de légica ¢ complexidade. © objetivo principal no gerenciamento de tal complexidade & organizi-la de modo que © desenvolvedor saiba onde buscar o que ele deseja ¢ que precise entender apenas a complexidade que afeta diretamente um dado momento. Em contrapartida, um sistema com classes maiores de varios propésitos sempre nos atrasa insistindo que percorramos por diversas coisas que nao precisamos saber no momento. Reafirmando 0s pontos anteriores: desejamos que nossos sistemas sejam compostos por muitas classes pequenas, ¢ nfo poucas classes grandes. Cada classe pequena eneapsula uma inica 138 Capitulo 10: Classes responsabilidade, possui um tinico motivo para ser alterada e contribui com poucas outras para obter os comportamentos desejados no sistema. Coesio As classes devem ter um pequeno niimero de instancias de varidveis. Cada metodo de uma classe deve manipular uma ou mais dessas varidveis. De modo geral, quanto mais varidveis um método manipula. mais coeso 0 método é para sua classe. Uma classe na qual cada variavel é utilizada por um método ¢ totalmente coesa. De modo geral, nao € aconselhavel e nem possivel criar tais classes totalmente coesas; por outro lado, gostariamos de obter uma alta coesdio. Quando conseguimos, os métodos e as variaveis da classe so co-dependentes ¢ ficam juntas como um todo légico. Considere a implementagao de uma Stack (pilha) na Listagem 10.4. A classe ¢ bastante coesa. Dos trés métodos, apenas size () ndo usa ambas as variaveis, Listagem 10-4 Stack.java - uma cli elements .add (element); public int pop() throws Poppedhenkapty { if (topOistack == 0) throw new PoppedWhenEnpty |) int elenent = elenents elerents. renove(topofStack) ; return element ; Aestratégia para manter fungdes pequenas ¢ listas de parametros curtas as vezes pode levar proliferacdo de instancias de varidveis que so usadas por uma sequéncia de métodos. Quando isso ocorre, quase sempre significa que ha pelo menos uma outra classe tentando sair da classe maior na qual cla esté. Vocé sempre deve tentar separar as varidveis e os métodos em duas ou mais classes de modo que as novas classes sejam mais coesas. As classes devem ser pequenas! 139 Manutencio de resultados coesos em muitas classes pequenas S6 0 ato de dividir fungdes grandes em menores causa a proliferagao de classes. Imagine uma fingao grande com muitas varidveis declaradas. Digamos que vocé deseje extrair uma pequena parte dela para uma outra fungao, Entretanto, 0 cédigo a ser extraido usa quatro das varidveis declaradas na funcao. Vocé deve passar todas as quatro para a nova fun¢aio como parimetros? Absolutamente nao! Se convertéssemos aquelas quatro variaveis em instancias de variaveis da classe, entao poderiamos extrair 0 cédigo sem passar qualquer varidvel. Seria facil dividir a fungao em partes menores. Infelizmente, isso também significa que nossas classes perderiam coesio, pois acumulariam mais e mais instancias de variaveis que existiriam somente para permitir que as poucas fungdes as compartilhassem. Mas, espere! Se hé poucas fungdes que desejam compartilhar certas varidveis, isso no as tora uma cada uma classe? Claro que sim. Quando as classes perdem coesiio, divida-as! Portanto, separar uma fungdo grande em muitas pequenas geralmente nos permite dividir varias classes também. Isso da ao nosso programa uma melhor organizagaio e uma estrutura mais transparente. Como exemplo do que quero dizer. usemos um exemplo respeitado ha anos, extraido do maravilhoso livro Literate Programming’, de Knuth. A Listagem 10.5 mostra uma tradugfo em Java do programa Print Primes de Knuth. Para ser justo com Knuth, este ndo € 0 programa que ele escreveu, mas 0 que foi produzido pela sua ferramenta WEB. Uso-o por ser um grande ponto de partida para dividir uma fungdo grande em muitas fungdes e classes menores. Listagem 10-5 PrintPrimes.java gi] args) { ORDMAX = 30 amt PI] = new intiM + ll; PAGENUMBER! PAGEOFFSET; ROROFFSET: 3 [Kown9?), 140 Listagem 10-5 (continuacio) PrintPrimes.java int d; int boolean JPRIME; int ORI ant’ SQUAR: int N; int MULT(] = new int(ORDMAX + 1]; SQUARE) ( ORD + 1; P(GRD] * P(ORD]; N= 2; JPRIME = true: while (N < ORD && JPRIME) { while OMLT(N] < J) MULTIN] = MOLTIN] + P(N] + PIN]; if (MOLTIN] == J) JPRIME = false; NeNrt } while (1JPRIME); K=kel PIK PACENUNBER = 1; FAGEOFFSET = 1; while (PAGEOFFSET <= M) { System.out.printini "The First "+ M+ "prime Numbers --- Page " + PAGENUMBER); System. out.printIni""); for (ROWOFFSET = PAGBOFFSET; ROWOFFSET < PAGEOFFSET + PR; ROWOFFSET++) ( for i¢ = 0; C < CC;C+4) if (ROWOPFSET + C * RR <= M) ‘system, out, format ("$103*. aystem.cut .printIn(""); B[RONOFFSET + ¢ * RRI); print 1n("\f"}j PAGENUMBER = PAGENUMBER + 1; PAGHOFFSED = PAGEOFFSET + FR * As classes devem ser pequenas! 141 Este programa, escrito como uma unica fungao, é uma zona; possui uma estrutura com muitas endentacdes, uma abundancia de varidveis estranhas e uma estrutura fortemente acoplada. No minimo essa grande fungao deve ser dividida em algumas menores. Da Listagem 10.6 a 10.8 mostra 0 resultado da divisio do cédigo na Listagem 10.5 em classes ¢ fungdes menores, ¢ seleciona nomes significativos para as classes, fungSes ¢ varidveis. Listagem 10-6 PrimePrinter.java (refatorado) package literatePrimes; public class PrimePrinter { public static void main(String!] argsi ( final ant{) 2: jeGeneracor .generate (NUMBER_OF_PRIMES) ; final int ROWS_PER_PAGE = 54; fical int COLUMNS_?: RowColumPage?rinter tablePrinter = nex RowColumneagePrinter (ROWS_PER_PAC ‘COLUNNS_PER. “The First " + NUMBER OF PRIMES + * Pri Listagem 10-7 RowColumnPagePrinter.java package literatePrines; import java.io. Printstream; public class RowColumnPagePrinter { private int rowsPerPage; vate int columisPerPage; private int numbersPerPage; private String pageHeader; private PrintStream printStream; public RowColumnPege?rinter (int rowsFerPage, int colunnsPerPage, String pageHeader) { this.rowsFerPage = rowsPerPage; this.columsPerPage = colunnsPerPage; this.pageHeader = pageHeader; nunbersPerPage = rowsPerPage * columnsPerPage; printstrean = Systen.out 142 Listagem 10-7 (continuagao) RowColumnPagePrinter. java public void print (int datafl) f int pagetunber = 1; for (int firstIndexOnPage = 0; FirstIndexOnPage < data. length; firstIndexOnPage += numbersPerPagel int lastindexOneage = Math.min|firstIndexOnPage + numbersPerPage - 1, daca-length - 1); print PageHeader (pageHeader, pageNumber} ; printPage(firstIndexonPage, lastIndexonPage, data); printstream.printin(*\f*|; pageNunber++; } private void printpage(int firet IndexoaPage,, it lastIndexOnFage, int(] data) { int first IndexOfLastRowOnPage = first IndexOnPage + rowsPerPage - 1; for (int firstIndexInRow = first IndexOnPage; firstIndexInkow <= firstIndexOfLastRowOnPage; firstIndexInRow-+| ( printRow(firstIndexInRow, lastIndexOnPage, data): printStrean.printIn(**); , } it firstIndexInRow, it lastIndexOnPage, t[] data) { t colum = 0; colum < columnsPorFage; colummr+) { olum * rowsPerPage; for iat index = firstIndexinkow + if (index <= lastindexonPage! printStream. format ("$10d", datalindex|}; } } Header (string pageHeadcr, int pagetumber) { printStream.printin(pageHieader + * --- Page * + pageNumber); printStzeam.printin(*"| ) private void printr. public void setoutput (Printstream printstream) { this.printStream = printStream: t ‘As classes devem ser pequenas! 143 Listagem 10-8 PrimeGenerator. java. package literatePrimes; import java.util Arraylist; public class Primecenerator ( private static int[] primes; private static ArrayList multiplesOfPrimeFactors; protected static int[] generate(int n) { primes = new int (r maitipleeOfprimeFactors = new ArrayLise-|); set2AsFirstPrime( checkOddNumbersForSubsequentPrines(}; return primes; } private static void set2aseirecPrime(} ( pranes(()] mulciples0fPrimeFactors.add (21; } private static void checkoddiumbereForSubsequentPrimes() { int primeIndex for (int candidate = 3; primeTndex < primes length; candidate += 2) { if (isPrime(candidate)) primes (primeindex++] = candidate; private static boolean isPrimelint candidate} ( if (isLeastRelevantMultipleOfNextLargerPrimeFactor (candidate)| { multiplesOfPrimeFactors add (candidate) ; return false; return isMotWult iple0fanyPreviousPrimePactor {candidate} ; i } private static boolean isLeastRelevantMult ipleOfNextLargerPrimeFactor tint candidate) { int nextlargerPrineFector = primes{multiplesOfPrimePactors.size() 1]; int leastRelevantwultiple = nextLargerPrimeFactor * nextLargerPrimeFactor: return candidate == leastRelevantMultiple; } private static boolean isNotMultipleOfAnyPreviousPrimeFactor (int candidate} { for (int n = 1; n < muiltiplesOfPrimeFactors.size(): ast) { if (isMultipleoinchPrimeFactor (candidate, a)) return false; 144 Capitulo 10: Classes Listagem 10-8 (continuagio) PrimeGenerator. java return true; private static boolean isMult ipleOfNthPrimePact: return candidate int candidate, int n) { smal lestodanchMul tip] eNotLessthanCandidate (can private static int sma] lest OddNchMul t ipleNot LessThanCandidate (int multiple = mltiplesOfPrimeFactors.get (n) ; while (mal didate) + primesinl: Factors. satin, multiple; didate, int nj { ‘A primeira coisa que vocé deve perceber é que o programa ficou bem maior, indo de uma pagina para quase trés. Hé diversas razGes para esse aumento. Primeiro, 0 programa refatorado usa nomes de varidveis mais longos ¢ descritivos. Segundo, ele usa declaragdes de fungées ¢ classes como uma forma de adicionar comentarios a0 cédigo. Terceiro, usamos espacos em branco e técnicas de formatagao para manter a legibilidade. Note como o programa foi dividido em trés responsabilidades principais. O programa principal esta sozinho na classe PrimePrinter cuja responsabilidade é lidar com o ambiente de execugdo. Ela seri modificada se 0 método de chamada também for. Por exemplo, se esse programa fosse convertido para um servigo SOAP, esta seria a classe afetada, O RowColumnPagePrinter sabe tudo sobre como formatar uma lista de nimeros em paginas com uma certa quantidade de linhas ¢ colunas. Se for preciso modificar a formatacao da saida, entao essa seria a classe afetada. ‘A classe PrimeGenerator sabe como gerar uma lista de nimeros primos. Note que ela nfo foi feita para ser instanciada como um objeto. A classe ¢ apenas um escopo pritico no qual suas variéveis podem ser declaradas ¢ mantidas ocultas. Se o algoritmo para 0 cdlculo de numeros primos mudar, essa classe também ira. ‘Nao a reescrevemos! Nao comegamos do zero ¢ criamos um programa novamente. De fato, se olhar os dois programas mais de perto, vera que usam 0 mesmo algoritmo e légica para efetuar as tarcfas. ‘Aalteracfo foi feita criando-se uma colegao de testes que verificou 0 comportamento preciso do primeiro programa, Ento, foram feitas umas pequenas mudangas, uma de cada vez. Apos cada alteragdo, executava-se 0 programa para garantir que 0 comportamento nao havia mudado. Um pequeno passo apés 0 outro ¢ o primeiro programa foi limpo ¢ transformado no segundo. ‘Como organizar para alterar 145 Como organizar para alterar Para a maioria dos sistemas, a mudanga é constante. A cada uma, corremos 0 risco de o sistema nao funcionar mais como o esperado. Em um sistema limpo, organizamos nossas classes de modo a reduzir 0s riscos nas alteragdes. ‘A classe Sai na Listagem 10.9 gera strings no formato SQL adequado dado um metadados apropriado, £ um trabalho continuo ¢, como tal, ainda niio suporta funcionalidade SQL, como as instrugdes update. Quando chegar a hora da classe SQL suportar uma instruco update, teremos de “abrir” essa classe ¢ fazer as alteragdes. Qualquer modificagao na classe tem a possibilidade de estragar outro cddigo na classe. E preciso testar tudo novamente. Listagem 10-9 Classe que precisa ser aberta para altera¢&o String creat2i) String insert (Object{] fields) 114} findByKey iString keyColum, String keyValuei select (Column column, String pattern) String select (Criteria criteria) String preparedinsert () String columList(Colum[] columns) ivate String valuesti las, final Colum] columns) e String select ‘A classe Sql deve ser alterada quando adicionamos um novo tipo de instrugao ou quando mudarmos 0s detalhes de um nice tipo de instrugao — por exemplo, se precisarmos modificar a funcionalidade do select para suportar “sub-selects”. Esses dois motivos para alteragio significam que a classe Sail viola 0 SRP. Podemos ver essa quebra da regra num simples ponto horizontal. O método realgado da SQ. mostra que hd métodos privados, como 0 selectWithCriteria, que parecem se relacionar apenas ds instrugdes select. © comportamento do método privado aplicado apenas a um pequeno subconjunto de uma classe por ser uma heuristica util para visualizar possiveis dreas para aperfeigoamento, Entretanto, 0 indicio principal para se tomar uma ago deve ser a alteracao do sistema em si, Se a classe Sqi for considerada logicamente completa, entio nao temos de nos preocupar em separar as responsabilidades. Se nao precisamos da funcionalidade update num futuro préximo, ent devemos deixar a Sql em paz. Mas assim que tivermos de “abrir” uma classe, devemos considerar consertar nosso projeto. E se considerassemos uma solugo como a da Listagem 10.10? Cada método de interface piblica definido na sq anterior na Listagem 10.9 foi refatorado para sua propria classe derivada Sql. Note que os métodos privados, como valuesList, vio diretamente para onde sfio necessarios. O comportamento privado comum foi isolado para um par de classes utilitarias, Where e ColumnList. 146 Capitulo 10: Classes Listagem 10-10 Vérias classes fechadas abstract public class sgl { public sqi(string table, colum[] columns) abstract public String generste(); 1 public class CreateSql extends Sql { public CreatesqliString table, Colum(] colums) override public String generate() ) public class Selectsq] excends Sqi { public SelectSql(String table, Cclumn[{} columns) @0verride public String generate!) } public class InsertSql extends Sql { public Insert$gl(String table, Column{} colums, @override public string generate! private String valuesiist (Object |] fields, final Column(] columns) t{] fields) SelectWithCriceriaSq] extends Sql { ectwithcriteriasal ( table, Colum] columns @override public String generate!) Criteria criteria) public class SelectithMatchSql extends Sql { public SelectWithMatchsql ( String table, Columi{} columns, Celumn colum, String pattern) override public String generate!) 4, public class Findayxey$ql extends Sai public FindByxeysql ( String table, Column[] columns, String keyColumn, String keyValue) Soverride public String generate|) } public claes Preparedinsertsq] extends 3g) ( public PreparedinsertSql (String table, Colum|[] colums) override public String generate() { private String placeholderbist |Colum|[) colums) + public class where { public Where(String criteria) public string generate() blic class Columabist ( public Columnist (Column[] columns} public string generate() Como organizar para alterar 147 CO cédigo em cada classe se tora absurdamente simples. O tempo necessério para entendermos qualquer classe caiu para quase nenhum. O risco de que uma fungéo possa prejudicar outra se torna infima. Do ponto de vista de testes, virou uma tarefa mais facil testar todos os pontos Idgicos nesta solugao, ja que as classes esto isoladas umas das outras. Tao importante quanto é quando chega a hora de adicionarmos as instrugdes update, nenhuma das classes existentes precisam scr alteradas! Programamos a légica para construir instrugdes update numa nova subelasse de Sal chamada UpdateSal. Nenhum outro cédigo no sistema sofrera com essa mudanga. Nossa Idgica reestruturada da Sq representa o melhor possivel. Ela suporta o SRP € outro principio-chave de projeto de classe OO, conhecido como Principio de Aberto-Fechado OCP* (sigia em inglés). As classes devem ser abertas para expanstio, mas fechadas para alteracio. ‘Nossa classe Sql reestruturada esta aberta para permitir novas funcionalidades através da criagio de subclasses, mas podemos fazer essa modificag30 ao mesmo tempo em que mantemos as outras classes fechadas, Simplesmente colocamos nossa classe updatesql em seu devido lugar. Descjamos estruturar nossos sistemas de modo que baguncemos 0 minimo possivel quando os atualizarmos. Num sistema ideal, incorporariamos novos recursos através da expansiio do sistema, ¢ no alterando o cédigo existente. Como isolar das altera¢ées ‘As necessidades mudarao, portanto 0 cédigo também. Aprendemos na introdugae 4 OO que ha classes coneretas, que contém detalhes de implementacao (cédigo), ¢ classes abstratas, que representam apenas conceitos. Uma classe do cliente dependente de detalhes concretos corre perigo quando tais detalhes so modificados. Podemos oferecer interfaces ¢ classes abstratas para ajudar a isolar o impacto desses detalhes. Depender de detalhes concretos gera desafios para nosso sistema de teste. Se estivermos construindo uma classe Port folio e ela depender de uma API TokyoStockExchange externa para derivar 0 valor do portfolio, nossos casos de testes sto afetados pela volatilidade dessa consulta. F dificil criar um teste quando obtemos uma resposta diferente a cada cinco minutos! Em vez de eriar a Portfolio de modo que ela dependa diretamente de TokyoStockExchange, podemos criar uma interface StockExchange que declare um tinico método: Desenvolvemos TokyostockExchange para implementar essa interface. Também nos certificamos se 0 construtor de Port folio recebe uma referéncia a StockExchange como parametro: public Portfolio { private StockExchange exchange; public Portfolio(StockExchange exchange) ( this.exchange = exchange; } } Agora nosso teste pode criar uma implementagao para testes da interface StockExchange que simula a Tokyostockexchange. Essa implementagiio fixaré o valor para qualquer simbolo 148 Capitulo 10: Classes que testarmos. Se nosso teste demonstrar a compra de cinco agdes da Microsoft para nosso port folio, programamos a implementagao do teste para sempre retornar US 100 délares por ago. Nossa implementagao de teste da interface StockExchange se reduz a uma simples tabela de consulta. Podemos, ento, criar um teste que espere US 500 délares como o valor total do portfolio. public class portfoliorest { private FixedStockExchangeStub exchange; private Portfolio portfolio; @Betore protected void setUp() throws Exception { exchange = new FixedStockExchangeStub(); exchange.fix(*MSFT”, 100); portfolio = new Portfoliolexchange); ) @Test public void GivenFiveMSFTTotalshouldBeS00() throws Exception ( portfolio.add(5, “NSFT"); Assert assertEqualsi500, port folio.value()); Se o um sistema estiver desacoplado o bastante para ser testado dessa forma, ele também sera mais flexivel e tera maior capacidade de reutilizacao. A falta de acoplamento significa que 08 clementos de nosso sistema ficam melhores quando isolados uns dos outros ¢ das alteragdes, facilitando o entendimento de cada elemento. ‘Ao minimizar 0 acoplamento dessa forma, nossas classes aderem a outro principio de projeto de classes conhecido como Principio da Inversio da Independéncia ,DIP* (sigla em inglés). Basicamente, 0 DIP diz que nossas classes devem depender de abstragées, nao de detalhes concretos. Em vez de depender da implementagao de detalhes da classe TokyoStockExchange, nossa classe Port folio agora é dependente da interface StockExchange, que representa 0 conceito abstrato de pedir 0 prego atual de um simbolo. Essa isola todos os detalhes especificos da obtengao de tal prego, ineluindo sua origem. Bibliografia IRDD}: Object Design: Roles, Responsibilities, and Collaborations, Rebecca WirfsBrock et al., Addison-Wesley, 2002. [PPP]: Agile Software Development: Principles, Patterns, and Practices, Robert C. Martin, Prentice Hall, 2002. [Knuth92]: Literate Programming, Donald E. Knuth, Center for the Study of language and Information, Leland Stanford Junior University, 1992 11 Sistemas por Dr. Kevin Dean Wampler “Complexidade mata. Ela suga a-vida dos desenvolvedores, dificulta 0 planejamento, a construgao ¢ 0 teste dos produtos”. —Ray Ozzie, CTO, Microsoft Corporation 150 Capitulo 11: Sistemas Como vocé construiria uma cidade? Conseguiria tratar de todos os detalhes sozinho? Provavelmente nao. Até mesmo o gerenciamento de uma cidade é muito para uma pessoa s6. Mesmo assim, as cidades funcionam (na maioria das vezes). Isso porque possuem equipes de pessoas que gerenciam partes especificas da cidade, o sistema de abastecimento de agua, de energia, de transito, a seguranga piiblica, as normas de construgao, ¢ assim por diante. Algumas dessas pessoas sio responsaveis pela visio geral, enquanto outros se focam nos detalhes. As cidades também funcionam porque progrediram em niveis apropriados de e modularidade 0 quais possibilitaram que individuos e os “componentes” pudessem trabalhar de forma eficiente, mesmo sem ter nogao da visio geral. Embora equipes de software geralmente sejam organizadas dessa forma também, os sistemas nos quais trabalham nao costuma ter a mesma divisao de preocupagdes ¢ niveis de . Um cédigo limpo nos ajuda a alcangar esses niveis mais baixos de . Neste capitulo, consideremos como manter 0 cédigo limpo nos niveis de mais altos, o nivel do sistema. Separe a construc4o e 0 uso de um sistema Primeiramente, considere que construcdo & um proceso diferente de utilizacdo. Na época em que escrevo este livro, ha um novo hotel sendo construido que posso ver através da minha janela em Chicago. Hoje sé ha pilares de concreto com uma grua ¢ um elevador preso do lado de fora, Todas as pessoas ocupadas lA usavam capacetes de protec%o e roupas de trabalho. Em mais ou menos um ano, o hotel ficard pronto. A grua ¢ o clevador terao indo embora. O edificio estara limpo, envolto em paredes com janelas de vidro e um tingimento atraente. As pessoas que trabalhardo e ficardo ali também serdo diferentes. Os sistemas de software devem separar o processo de inicializagdo — a criacdo dos objetos do aplicativo ea “conexao” entre as dependéncias — da légica em tempo de execucdo que vem apés a inicializacao. O processo inicial ¢ uma preocupagdo da qual qualquer aplicativo deva tratat. E sera a primeira analisada neste capitulo. A separagdo de preocupagdes é uma das técnicas de projeto mais antigas ¢ importantes em nossa profissao. Infelizmente, a maioria dos aplicativos nao faz essa separagdo. O cédigo do proces de inicializagao ¢ especifico ¢ misturado na légica em tempo de execugdo, Abaixo esta um exemplo tipico: public Service getServicel) { if (service == null) service = new MyServiceImpl(...); // Padraéo bom o suficiente para a maioria dos casos? return service; } Essa é a expresso INICIALIZACAO/AVALIACAO TARDIA, digna de varios méritos. Nao visualizamos a operagao geral da construgao a menos que realmente usemos 0 objeto, ¢, como resultado, nosso tempo de inicializagao pode ser mais rapido. Também garantimos que nunca seja retornado null. Separe a construgao e o uso de um sistema 151 Entretanto, agora temos uma dependéncia codificada permanentemente em MyServicetmpl ¢ tudo 0 que seu construtor exige (0 que omiti). Nao podemos compilar sem resolver essas dependéncias, mesmo se nunca usarmos um objeto desse tipo tempo de execuco. Efetuar testes pode ser um problema. Se MyServiceTmp1 for um objeto grande, precisamos garantir que um TEST DOUBLE’ ou MOCK OBJECT seja atribuido a area de operagao antes deste metodo ser chamado no teste de unidade. Como temos logica da construgo misturada ao processamento normal em tempo de execuedo, devemos testar todos os caminhos da execugao (por exemplo, 0 teste ni:1 1 e seu bloco). Ter essas duas responsabilidades significa que o método faz mais de uma coisa, portanto estamos violando, de certa forma, 0 Principio da Responsabilidade Unica Talvez o pior de tudo € que nao sabemos se MyServicemp] € 0 objeto correto em todas as classes, ¢ foi isso que indiquei no comentario. Por que a classe que possui este método precisa enxergar 0 contexio global? Realmente jamais poderemos saber qual 0 objeto certo usar aqui? E possivel que um tipo seja 0 certo para todos contextos? E claro que uma ocorréncia de INICIALIZAGAO-TARDIA nao é um problema sério. Entretanto, costuma-se ter muitas instancias de pequenas expressdes como essa nos aplicativos. Devido a isso, a estratégia de configuragao global (se houver uma) fica espalhada pelo aplicativo, com pouca modularidade ¢, geralmente, duplicago consideravel. Se formos cuidadosos ao construir sistemas bem estruturados € robustos, jamais devemos deixar que expressdes convenientes prejudiquem a modularidade. O processo de inicializagao da construgio e atribuig4o de um objeto nao sao excegdes, Devemos modularizar esse processo de separadamente da logica normal em tempo de execugao, e nos certificar que tenhamos uma gstratégia global e consistente para resolver nossas dependéncias principais. Separacdo do Main Uma maneira de separar a construgao do uso é simplesmente colocar todos os aspectos dela no main ou em médulos chamados por ele, c modelar o resto do sistema assumindo que todos os objetos foram construidos ¢ atribuidos adequadamente (veja @ Figura 11.1) E facil acompanhar 0 fluxo de controle. A fung3o main constrdi os objetos necessarios para © sistema e, entio, os passa ao aplicativo, que simplesmente os usa. Note a direcao das setas de dependéncia cruzando a barreira entre o main ¢ 0 aplicativo. Todas apontam para a mesma diregdo, para longe do main. Isso significa que o aplicativo nao enxerga 0 main ou o proceso de construgo, mas apenas espera que tudo seja devidamente construido. Factories £ claro que de vez em quando precisamos passar 0 controle para o aplicative quando um objeto for criado. Por exemplo, em um sistema de processamento de pedidos, o aplicativo deve criar instincias do Linertem e adicionar a um Order. Neste caso, podemos usar 0 padrao ABSTRACT FACTORY: para passat 0 controle ao aplicativo quando for preciso criar os objetos LineTtem, mas mantenha os detalhes dessa construg%o separada do cédigo do aplicativo (veja a Figura 11.2). SRE 152 Capitulo 11: Sistemas ineltemFactory + makeLineliom Figura 11.2: Separacao da construg4o com uma factory Novamente, obscrve que todas as dependéncias apontam de main para o aplicativo OrderProcessing. Isso significa que ele esté desacoplado dos detalhes de como criar um LineItem. Essa capacidade ¢ mantida em LineitemFactoryLmplementation, que esta no mesmo lado da linha que o main. E mesmo assim 0 aplicativo possui controle total quando na criagdo de instancias de LineTtem e pode até oferecer parametros do construtor especificos a0 aplicativo, Injegao de dependéncia Um mecanismo poderoso para separar a construgdo do uso é a Injecdo de Dependéncia (DI, sigla em inglés), a aplicagao da Inverso de Controle (IoC, sigla em inglés ao gerenciamento de dependéncia’, A loC move as responsabilidades secundarias de um objeto para outros dedicados 4 Ve; Data, poten ta Desenvolvimento gradual 153 ao propésito que se deseja, dessa forma suportando o Principio da Responsabilidade Unica. Em vez disso, ela deve passar essa responsabilidade para outro mecanismo “dominante”, com isso invertendo o controle. Como essa configuragiio é uma preocupacao global, esse mecanismo dominante geralmente sera ou a rotina “principal” ou um contéiner de tarefa especifica. ‘As consultas a0 JNDI so implementagdes “parciais” da DI, na qual um objeto pede a um servidor de diretérios um “servigo” com um nome especifico. MyService myService = (MyService) (indicontext .lockup(*NameOfMyService’)); © objeto chamador no controla qual tipo seri realmente retornado (contanto que ele implemente a interface apropriada, é claro), mas ele ainda determina ativamente a dependéncia. A Injegao de Dependéncia Verdadeira vai mais além. A classe nao determina diretamente suas dependéncias: cla fica completamente passiva e oferece métodos de escrita (setters) ou pardmetros de construtores (ou ambos) que serao usados para injetar as dependéncias. Durante 0 processo de construco, o contéiner de DI instancia os objetos necessérios (geralmente sob demanda) ¢ usa os pardmetros do construtor ou os métodes de escrita fornecidos para conectar as dependéncias. Quais objetos dependentes so usados realmente sao especificades por um arquivo de configuragao ou diretamente programando-se no modulo de construgao de tarefa especifica. O framework Spring oferece o melhor e mais conhecido contéiner de DI para Java*. Voce define quais objetos conectar um ao outro em um arquivo de configuraga0, ¢ depois solicita objetos especificos pelo nome no eédigo Java. Logo veremos um exemplo. Mas e sobre as virtudes da INICIALIZAGAO-TARDIA? Essa expresso, de vez em quando, ainda é util com a DI. Primeiro, a maioria dos contéineres de DI nao criaré objetos até que sejam necessarios. Segundo, muitos desses contéineres oferecem mecanismos para invocar factories ou construir proxies, que poderiam ser usados para sanar a AVALIACAO- TARDIA e ofimizacdes* semelhantes Desenvolvimento gradual Vilarejos viram pequenas cidades, que viram cidades grandes. No inicio as ruas sao estreitas ¢ quase no existem e, com tempo, elas sio pavimentadas ¢, enti, alargadas. Pequenos edificios e terrenos vazios so substituidos por edificagdes maiores, algumas das quais acabarao virando arranha-céus. No comego, nao ha servigos, como abastecimento de energia, Agua, esgoto ¢ Internet (opal). E sé sero adicionados quando aumentar a densidade da populacdo e de edificagdes. Mas esse desenvolvimento nio esta livre de problemas. Quantas vezes, devido a um projeto de “melhoria” das avenidas vocé dirigiu por engarrafamentos ¢ se perguntou “Por que no construiram as ruas largas o bastante desde o inicio?”. Mas nao podia ter sido de outra forma. Como justificar 0 custo de construgio de uma via expressa de seis faixas passando no meio de uma cidade pequena j antecipando seu desenvolvimento? Quem desejaria tal avenida passando por sua cidade? E mito dizer que podemos conseguir um sistema “correto de primeira”. Em vez disso, devemos implementar apenas os faios de hoje e, entdo, refatorar e expandir o sistema, implementando novos 1s4 Capitulo 11: Sistemas fatos amanhi. Essa é a esséncia das agilidades iterativa ¢ incremental. O desenvolvimento dirigido a testes, a refatorago e 0 cédigo limpo que produzem fazem com que isso tudo funcione em nivel de cédigo. Mas e em nivel de sistema? A estrutura do sistema nao requer um pré-planejamento? Claro que sim, ele nao pode crescer gradualmente do simples para 0 complexo, pode?” Se comparados aos sistemas fisicos. os de software sao tinicos, e suas arquiteturas podem crescer gradualmente se mantivermos uma separagao devida de preocupagaes. Como veremos, ¢ a natureza efémera dos sistemas de software que possibilitam isso. Consideremos primeiro um contra-exemplo de uma arquitetura que no separa as preocupagdes de forma adequada. As arquiteturas EJBI ¢ EB2 originais so um bom exemplo ¢, devide a isso, geram obsticulos desnecessarios para o crescimento orgénico, Considere uma Enrity Bean para uma classe Bank frequente, Uma entity bean é uma representagdio, na meméria, dos dados relacionais, ou seja, a linha de uma tabela. Primeiro, voea define uma interface local (no proceso) ou remota (separada da JVM), que os clientes usariam. A Listagem 11.1 mostra uma possivel interface local: Listagem 11-1 Interface EJB2 local para um EJB da classe Bank package com. exam import java.util.Collections; port javax.ejb.*; public interface BankLocal extends java.ejb.EJBLocal0bject { String getstreetAddrl(} throws BJBEXception; ring getStreetAdér2() throws BJBException: String getCity() throws EJBExcept ion ring getState|) chrows EJBExcept ring getZipCode|) throws BJBExcept ion: a setst’ ing street1) throws EJBException; ring street2) throws EJBException hrows EJBExcept ion; ring state) throws EJBException; ud setCity void set. void eet?ipCode (String 2 Collection getAcccunts() void setAccounts (Colles void addAccou: | throws EIBExcepti. hrows EJBException; ) throws EJBException; m0) throws BIBException; Exibi diversos atributos para o enderego do Bank e uma colegio de contas (account) que hé no banco (bank), cada uma com seus dados manipulados por um EJB de Account separado. A Listagem 11.2 mostra a classe de implementagdo correspondente para o bean de Bank. Desenvolvimento gradual Listagem 11-2 Implementagao da Entity Bean do EJB2 correspondente packs: import je import javax.ei} comexample. banking; lections; Bank implements javax.ejb. ityBean { ring get Streetaddrl(); abstract String getStreetaddr2 (1; abatract abstrac' abstract abstract void setStreetaddrl (String streetli; abstract void setStreetAddr? (String street2): ¢ abstract void setCicy(strin abstract void setState(Stzin abstract zipcode (String zip abstract mM getAccounts() ; public abstract void setAccounts (Collection accounts) + public void adéaccount (AccountDTO accountDTO) TnitialCon' KE = new InitialContext i); AccountWoneLocal eccountHome = context. lookup| "AccountHomeLocal Accountbac: accountHone. create (accounDTO Collection accounts = getRecounts |); accounts .add (account) t / BIB container Log: public abstract void secId(Integer idjz public abstract Integer getid\) Integer ejbCreate(Integer id) { 22. J void ejbeostCreate(integer id) { ... } rest had to be implemented but were usually enpty: void setintityContext (EntityContext ctx) {} void unsetEntityContext () (7 void tet} {) void vate() (} void ejbLoad!) [} void ejbstore() {] public void eybRemove() () Nao mostrei a interface LocalHome correspondente — basicamente uma factory usada para criar objetos — e nem um dos possiveis métodos de localizag%o (consulta, ou queries) que voce pode adicionar. Por fim, vocé teve de criar um ou mais descritores de implementagao que especifiquem os detalhes do mapeamento de objetos relacionais para um armazenamento permanente de dados, para a ado de transaco desejada, para os limites de seguranca, etc. ‘A logica de negécio esta fortemente acoplada ao “container” do aplicativo EJB2. Vocé precisa criar subclasses dos tipos do container e fornecer muitos métodos do tipo lifecycle exigidos pelo container. Esse acoplamento ao container pesado dificulta o teste de unidade isolado. E necessario fazer uma simulag3o do container, o que ¢ dificil, ou gastar muito tempo implementando EJBs e testes em um servidor real. Reutilizar externamente € de modo eficiente a arquitetura EJB2 € impossivel devido ao forte acoplamento. 156 Capitulo 11: Sistemas Por fim, mesmo a programaciio orientada a objeto foi prejudicada. Um bean nao pode herdar de outro. Note a légica para adicionar uma nova conta. E comum nos beans do EJB2 definir os “objetos de transferéncia de dados” (DTOs, sigla em inglés) que sto basicamente “structs” (estruturas) sem atividade alguma. Tsso costuma levar a tipos redundantes que possuem praticamente os mesmos dados, ¢ requer cédigos padronizados para copiar dados de um objeto para outro. Preocupacées transversais A arquitetura EJB2 chega perto de uma divisdo real de preocupagdes em algumas areas. Por exemplo, a seguranga, a comunicagao desejada e alguns dos comportamentos de persisténcia so declarados dos descritores de implementagao, independentemente do codigo-fonte. Note que preocupagdes como persisténcia tendem a atravessar os limites naturais dos objetos de um dominio, Vocé deseja manter todos os seus objetos através da mesma estratégia. por ‘exemplo, usando um SGBD* versus bancos de dados nao-relacionais, seguindo certas convengées de nomenclatura para tabelas e colunas, usando sem4nticas transacionais consistentes, etc, Em principio, vocé pode pensar em sua estratégia de persisténcia de uma forma modular e encapsulada, Mesmo assim, na pritica, é preciso basicamente espalhar por varios objetos © mesmo cédigo que implementa tal estratégia. Usamos 0 termo preocupacées transversais para preocupagdes como essa, Novamente, o framework de persisténcia e a Iégica de dominio (isolada) podem ser modulares. O problema ¢ a minuciosa intersego desses dom{nios. De fato, 0 modo como a arquitetura EJB trata da persisténcia, da seguranga e das transacdes “antecipa” a Programacao Orientada a Aspecto (POAY'— uma abordagem de propésito geral para restaurar a modularidade para preocupagées transversais. Na POA, construgdes modulares chamadas aspectos especificam quais pontos no sistema deyem ter seus comportamentos alterados de uma forma consistente para suportar uma determinada preocupagao. Essa especificacao ¢ feita através de um mecanismo sucinto declarative ‘ou programitico. Usando a persisténcia como exemplo, vocé declararia quais objetos ¢ atributos (ou padrdes do tipo) devem ser mantidos e, entio, delegar as tarefas de persisténcia ao seu framework de persisténcia. O framework da POA efetua as alteragdes de comportamento de mado ndo-invasivo* no cédigo a ser alterado, Vejamos trés aspectos ou mecanismos voltados a aspectos em Java. Proxies para Java Os proxies para Java so adequados para situagdes simples, como empacotar chamadas de métodos em objetos ou classes individuais. Entretanto, os proxies dinamicos oferecidos no JDK sé funcionam com interfaces. Para usar proxies em classe, é preciso usar uma biblioteca de manipulacao de Bytecode, como CGLIB, ASM ou Javassist’. A Listagem 11.3 mostra o esqueleto para um Proxy do JDK que oferega suporte a persisténcia para nosso aplicativo Bank, cobrindo apenas os métodos para obter ¢ alterar a lista de contas. Proxies para Java 157 Listagem 11-3 Exemplo de proxy do JDK i Bank. java. (suppre! amport. java.utils.*; ng package names...) // The abstraction of a bank. public interface Bank { CollectioncAccount> getAccounts () + void setaccounts (CollectiencAccount> accounts) ; 27 Bankinpl jay impore java.utils.*; / The *plain Old Java Object” {POJ0) implementing the abstraction. public class Bankimpl implements Bank { private Last accounts; public CollectioncAccount> getaccounts() | return accounts; public void setAccounts (Collection accou this.accounts = new ArrayList!) for (Account account: accounts} [ this.accounts .add(accounti; // BankProxyHandler. java import java.lang.reflect. import java.util,* / J/ “InvocationHandler* required by the pro: public class BankProxyHandler implements Inv private Bank bank; cationHandler { public BankHandler (Bank bank} { this.bank = bank; } // Method defined in Invocationtandler public Object invoke|Cbject proxy, Method method, 01 throws Throwable ( String methodName = method.getName() ; if (methodName.equals(*getAccounts")) ( bank. setAccounts (getAccountsFronDatabase() return bank.getAccounts (1 } else if (methodNane.equals("setaccounts"}} { bank. setAccounts((Collection) args[0]): setAccountsToDatabase (bank.get Accounts ()) + return null; y else { 158 Capitulo 11: Listagem 11-3 (continuagao) Exemplo de proxy do JDK / Lots of details here: ected Col lect ioncAccount> tected void setAccountsTotal new BankProxyHandler (new BankImpl(})); Definimos uma interface Bank, que serd empacorada pelo proxy, e um Objeto Java Antigo e ‘Simples (POJO, sigla em inglés), chamado BankImp1, que implementa a légica de negécio. (Falaremos depois sobre POJOs). A API do Proxy requer um objeto InvocationHandler ao qual ela chama para implementar quaisquer chamadas ao método Bank feita pelo proxy. Nosso BankProxyHandler usa a API de Reflexdo do Java para mapear as chamadas aos métodos genéricos correspondentes em BankImp1, ¢ assim por diante. Ha bastante cédigo aqui que ¢ relativamente complicado, mesmo para esse caso simples", Usar uma das bibliotecas de manipulagao de bytes é igualmente desafiador. Este “volume” de cédigo ¢ sua complexidade so duas das desvantagens de usar proxies. Eles dificultam a criag&o de um cédigo limpo! Ademais, os proxies ndo oferecem um mecanismo para a especificagao de certas “partes” para execugao através de todo o sistema — necessério para uma solugao de POA real", Frameworks de POA puramente Java Felizmente,hé ferramentas que podem tratarautomaticamente damaioria dos proxiespadronizados. Estes so usados internamente em diversos frameworks como, por exemplo, POA com Spring e com JBoss, para implementar aspectos puramente em Java. No Spring, vocé escreve sua logica de negécio como Objeto Java Antigo e Simples. Os POJOs estiv simplesmente centrados em seus dominios. Eles ndo possuem dependéncias nas estruturas empresariais (ou qualquer outro dominio). Dessa forma, em tese, eles so mais simples e fiiceis de testar. A simplicidade relativa facilita a garantia de que vocé esteja implementado as user stories correspondentes de modo correto ea manutengao e o desenvolvimento do cédigo em user stories futuras. Vocé incorpora a estrutura necessaria do aplicativo, incluindo as preocupagdes transversais, como persisténcia, transagdes, seguranga, fazer cache, transferéncia automitica por falha (failover), etc., usando arquivos de configuragio com declaragécs ou APIs. Em muitos casos, vocé realmente especifica os aspectos da biblioteca do Spring ou do JBoss, onde o framework trata dos mecanismos do uso dos proxies em Java ou de bibliotecas de Bytecode transparentes ao usuario. Essas declaragdes controlam 0 contGiner de injegdlo de dependéncia, que instancia 03 objetos principais e os conecta sob demanda. A Listagem 11.4 mostra um fragmento tipico de um arquivo de configuragao do Spring V2.5, © app.xml": ‘ik ii nec ss aaa ds et ie ania dis saint, ential, coun ontamnlin VekiaS Frameworks de POA puramente Java 159 Listagem 11-4 Arquivo de configuracao do Spring 2.x ‘beans> Cada “bean” é como uma parte de uma “Boneca Russa”, com um objeto domain para um Bank configurado com um proxy (empacotado) por um objeto de acesso a dados (DAO, sigla em ingles), que foi configurado com um proxy pela fonte de dados do driver JDBC. (Veja a Figura 11.3). Figura 11-3 A“Boneca Russa” do decorators O cliente acha que esté chamando getAccounts() em um objeto Bank, mas na verdade esté se comunicando com a parte mais externa de um conjunto de objetos DECORATOR'* aninhados que estendem 0 comportamento basico do POJO Bank. Poderiamos adicionar outros DECORATOR para transages, efetuagao de cache, etc. No aplicativo, sio necessarias algumas linhas para solicitar o contéiner da DI para os objetos no nivel mais superior do sistema, como especificado no arquivo XML. XmlBeanFactory bf = new xmlBeanFactory(new ClassPathResource("app.xml", getClass())); Bank bank = (Bank) bf.getBean("bank"); Devido necessidade de to poucas linhas do cédigo Java especifico para o Spring, o aplicativo esta quase completamente desacoptado do Spring, eliminando todos os problemas de acoplamento de sistemas como o EJB2 rey 160 Capitulo 11: Sistemas Embora o XML possa ser detalhado e dificil de ler, a “politica” especificada nesses arquivos de configuragao € mais simples do que 0 proxy complicado ¢ a légica do aspecto que fica oculta © 6 criada antomaticamente. Esse tipo de arquitetura ¢ tio urgente que levou frameworks como © Spring a efetuarem uma reformulagao completa do padrio EJB para a versio 3. O EJB3 majoritariamente segue o modelo do Spring de suporte a declaragdes a preocupagées transversais usando os arquivos de configuragzio XML e/ou as anotagdes do Java 5. A Listagem 11.5 mostra o EJB3" com nosso objeto Bank reescrito. Listagem 11-8 Um EJB de Bank no EBJ3 package com.exanple. banking.nodel: import javax.persiatence. import j. sutil ArrayList; import java.util.Collection; amnesty @Table(name = *BANKS*) public Class Bank implements java.io.Serialicable ( @ SGeneratedValue(strategy=Generat icn?yne.$UTO) private ink i ined" in Bank's DE row Embeddable // An object * pu lie class Address { protected String streetAddrl; procected String streetAddr?; protected String city; protected String state; protected String zipcode; imbedded te Address address. GOneToMany (cascade = CascadeType.ALL, fetch = FetchType. HAC mapgedBy-"bank") Lect. cour private & accounts = new ArrayList public ant gettd() { return id; jd setIalint id) { id, public void addAcecunt (Account account) { account .setBank (this) ; accounts. add {account} ; public Collection getaccounts|) { return accounts: i Sadie cdicsdiBiner w ecules dhnrihe din enecaniionae acer wiccieewrare: casseseusiiiv wiitonw uis-oumaihamcaiineerseceaunniten nkcwiiideerdicewiicion Aspectos do Aspect 161 Listagem 11-5 (continuacdo) Um EJB do Bank no EBJ3 s(Collection accounts) { © cédigo estd muito mais limpe do que o do EJB? original. Alguns dos detalhes da entidade ainda esto l4, contidos nas anotagdes. Entretanto, como nenhuma daquelas informagoes esta fora das anotagdes, 0 cédigo fica limpo, claro ¢, portanto, facil de testar, fazer a manutengao, ete. Se desejar, voc8 pode mover algumas ou todas as informagées nas anotagdes sobre a persisténeia para os deseritores de implementacdo XML, deixando um POJO puro. Se os detalhes de mapeamento da persisténcia ndo mudarem frequentemente, muitas equipes podem optar por manter as anotagées, entretanto com muito menos efeitos prejudiciais se comparado ao modo invasivo do EJB2. Aspectos do AspectJ Finalmente, a ferramenta mais famosa para a separagdo de preocupagées através de aspectos a linguagem Aspect)”, uma extensto do Java que oferece suporte de “primeira-classe” a aspectos como construtores de modularidade. A abordagem puramente Java oferecida pela POA em Spring e em JBoss sao o suficiente para 80-90% dos casos nos quais os aspectos stio mais iiteis. Entretanto, o Aspect proporciona uma série de ferramentas rica e poderosa para separar preocupacdes. Sua desvantagem ¢ ter de usar varias ferramentas novas € aprender a estrutura ¢ 0 uso das expressdes de uma nova linguagem. Uma “forma de anotagdo” do AspectJ recentemente langada, na qual usam-se anotacdes do Java 5 para definir aspectos usando um eédigo puramente Java, Ameniza parcialmente essa questo do uso de novas ferramentas. Ademais, 0 Framework do Spring possui uma série de recursos que facilita ainda mais, para uma equipe com experiéncia limitada em AspectJ, a incluso de aspectos bascados em anotagdes. Uma discussio completa sobre 0 AspectJ foge do escopo deste livro. Para isso, consulte [AspectJ], [Colyer] e [Spring]. Testes na arquitetura do sistema A capacidade de separar preocupagies através de abordagens voltadas a aspectos nao pode ser exagerada. Se puder escrever a logica de dominio de seu aplicativo usando POJOs, desacoplados de qualquer preocupagées acerca da arquitetura em nivel de codigo, entio é possivel testar sua arquitetura. Voces pode desenvolvé-la do simples ao sofisticado, se for preciso, através da adogao de novas tecnologias sob demanda, Nao € necessario fazer um BDUF (Big Design Up Front). De fato, um BDUF é até mesmo prejudicial por inibir a adaptacaio a mudangas devido & resisténcia psicoldgica de descartar os esforgos anteriores ¢ devido a forma pela qual a escolha da arquitetura influencia as ideias seguintes sobre o design. Arquitetos de construgdes tém de fazer um BDUF porque nio é pratico fazer alteragdes radicais na arquitetura para uma estrutura fisiea grande apds o inicio da construg40". 17, Consalte [Aspect] e [Colyer a i reill, 162 Capitulo 11: Sistemas Embora o software tenha sua propria mecnica, é economicamente pritico fazer alteragdes radicais se a estrutura separa de forma eficiente suas preocupagdes, Isso significa que podemos iniciar um projeto de Software com uma arquitetura simples, porém bem desacoplada, fornecendo rapidamente user sfories que funcionem e, entio, adicionando mais infra-estrutura conforme o desenvolvemos. Alguns dos maiores sites da Web alcancaram um grau de disponibilidade e performance muito altos através do uso de cache sofisticado de dados, seguranga. virtualizagiio, ¢ assim por diante, tudo feito de forma eficiente e flexivel porque os projetos com acoplamento minimo so apropriadamente simples em cada nivel de e de escopo. E claro que isso nao quer dizer que iniciamos um projeto sem algum planejamento. Temos certas expectativas a respeito do escopo, objetivos e programacao gerais para o projeto, assim como para a estrutura geral do sistema final. Todavia, devemos manter a capacidade de alteragdes durante 0 desenvolvimento no caso de aperfeigoamentos. A arquitetura anterior EJB ¢ uma das muitas APIs bem conhecidas que foram desenvolvidas de modo exagerado ¢ que comprometeram a separagao de preocupagdes. Mesmo tais APIs podem ser destrutivas se realmente nao forem necessérias. Uma boa API deve ficar ocu/fa na maioria das vezes, portanto a equipe expande a maioria de seus esforgos criativos centralizados nas user stories sendo implementadas, Caso contririo, os limites da arquitetura inibirdo a entrega eficiente do melhor servigo ao consumidor. Para recapitular: Uma arquitetura de sistema adequada consiste em dominios modularizados de preocupagaes, cada um implementado com POJOs ~ Objetos Java Antigos e Simples, ou outros. Os diferentes dominios sao integrados uns aos outros com ferramentas de Aspectos ou voltadas para eles pouco invasivas. Pode-se testar essa arquitetura da mesma forma que se faz com 0 cédigo. Otimize a tomada de decisées Modularidade separagao de preocupagées descentralizam © gerenciamento ¢ possibilitam a tomada de decisdes. Em um sistema consideravelmente grande, seja uma cidade ou um projeto de software, ninguém pode tomar todas as decisées. Todos sabemos que o melhor é designar responsabilidades as pessoas mais qualificadas. Geralmente nos esquecemos que também é melhor adiar as decisdes até 0 tiltimo momento. Isso no ¢ ser preguicoso ou irresponsivel, mas permitir-nos tomar decisdes quando tivermos as melhores informacdes possiveis. Uma decistio prematura é tomada sem muitas informagdes adequadas. Teremos muito menos retorno do consumidor, reflexo sobre 0 projeto e experiéncia com nossas escolhas de implementagao se decidirmos muito cedo. A agilidade oferecida por um sistema POJO com preocupagées modularizadas nos permite uma otimizacéo — decisdes na hora certa— baseando-se nas informacbes mais recentes, Além de também reduzir complexidade dessas decisdes. 20.0 termo mecénca de softwore foi usado prameramente por [Kolence} Conelusio 163 Use padrées sabiamente quando eles adicionarem um valor demonstrativo E uma coisa maravilhosa assistir 4 construgdo de uma infra-estrutura devido ao ritmo com que ‘as novas estruturas so construidas (mesmo num inverno rigoroso) ¢ avs projetos extraordinarios possiveis com a tecnologia atual. Uma construgao € um mercado maduro com partes altamente otimizadas, métodos e padrdes que evoluiram sob pressdo por séculos. Muitas equipes usavam a arquitetura EJB2 porque ela era padrao, mesmo com 0 advento de planejamentos mais diretos e leves. Jé vi equipes ficarem obcecadas com diversos padrées muito populares e perderem o foco no quesito de implementagao voltado para seus consumidores. Os padroes facilitam a reutilizagdo de ideias e componentes, recrutam pessoas com experiéncia consideravel, encapsulam boas ideias ¢ conectam ox componentes. Entretanto 0 proceso de criagdo de padrdes pode, as vezes, ser muito longo para que 0 mercado fique a espera deles, ¢ alguns padrées acabam se desviando das necessidades reais das pessoas a quem eles pretendem servir. Sistemas precisam de linguagens especificas a um dominio ‘A construgao de infra-estruturas, assim como a maioria dos dominios, desenvolveu uma linguagem rica com vocabulirio, expressdes e padres" que expressam informagdes essenciais de maneira clara e concisa. Houve recentemente na area de softwares uma retomada do interesse pela criago de Linguagens Especificas a um Dominio (DSLs, sigla em inglés)”, que sto linguagens separadas, pequenos scripts ou APIs em linguagens padrio que permitem a escrita do eddigo num formato que possa ser lido como uma prosa redigida por um especialista em dominio. Uma boa DSL minimiza a “distancia de comunicagao” entre 0 conceito de um dominio ¢ o cédigo que o implementa, assim como as priticas flexiveis otimizam @ comunicagao entre os membros de uma equipe ¢ a desta com suas partes interessadas. Se estiver implementando a légica de um dominio na mesma linguagem usada pelo especialista em dominios, havera menos risco de erro na tradugo do dominio para a implementacio. ‘As Linguagens Especificas a um Dominio permitem todos 0s niveis de ¢ todos os dominios no aplicativo a ser expresso como POJOs, desde um nivel mais alto até os detalhes de baixo nivel. Conclusio Os sistemas também devem ser limpos. Uma arquitetura invasiva afeta a agilidade ¢ sobrepuja a légica do dominio que, quando ofuscada, perde-se qualidade porque os bugs se escondem mais facilmente ¢ dificulta a implementacdo. Se a agilidade for comprometida, a produtividade também sera ¢ se perderdo as vantagens do TDD. EM todos os niveis de , 0 objetivo deve estar claro. Isso sé acontecera se vocé criar POJOs e usar mecanismos voltados a aspectos para incorporar de modo nao invasive outras preocupagies de implementagao. (cou mftvencin especialmente ma comune ds 164 Capitulo 11: Sistemas Esteja vocé desenvolvendo sistemas ou médulos individuais, jamais se esquega de usar a coisa mais simples que funcione. Bibliografia [Alexander]: Christopher Alexander, A Timeless Way of Building, Oxford University Press, New York, 1979. [AOSD]: Aspect-Oriented Software Development port, http://aosd.net [ASM]: Pégina do ASM, http://asm.objectweb.org/ [Aspect]: http://eclipse.org/aspectj [CGLIB]: Code Generation Library, http://cglib.sourceforge.net/ [Colyer]: Adrian Colyer, Andy Clement, George Hurley, Mathew Webster, Eclipse AspectJ, Person Education, Inc., Upper Saddle River, NJ. 2005 [DSL]: Domain-specific programming language, http://en.wikipedia.org-wiki/Domainspecific_ programming language [Fowler]: Inversion of Control! Containers and the Dependency Injection pattern, http:// martinfowler.com/articles/injection.huml [Goetz]: Brian Goetz, Java Theory and Practice: Decorating with Dynamic Proxies, http://www. ibm.com/developerworks/java/library/-jtp08305.html [avassist]: Pagina do Javassist, http://www.csg.is.titech.ac,jp/~chiba/javassist/ [JBoss]: Pagina do JBoss, http:/boss.org [JMock}: JMock—A Lightweight Mock Object Library for Java, http:/jmock.org [Kolence}: Kenneth W. Kolence, Software physics and computer performance measurements, Proceedings of the ACM annual conference—Volume 2. Boston, Massachusetts. pp. 1024— 1040, 1972 [Spring]: The Spring Framework, http://www.springframework.org [Mezzaros07]: XUnit Patterns, Gerard Mezzaros, Addison-Wesley, 2007 IGOF]: Padrdes de Projeto, Solugdes Reutilizaveis de Software Orientado a Objetos, Gamma et al,, Addison-Wesley, 1996 12 Emergéncia por Jeff Langr Obtendo clareza através de um processo de emergéncia E se houvesse quatro regras simples que vocé pudesse usar para Ihe ajudar na criagzio de bons projetos enquanto trabalhasse? E se ao seguir cssas regras vocé obtivesse conhecimentos sobre a estrutura ¢ 0 modelo de seu cédigo, facilitando a aplicagao de principio, como 0 SRP € 0 DIP? E se essas quatro regras facilitassem a emergéncia de bons projetos? Muitos de nds achamos que as quatro regras do Projeto Simples! de Kent Beck sejam de ajuda consideravel na criacaio de um software bem projetado. ‘Piso 166 Capitulo 12: Emergéncia De acordo com Kent, um projeto é “simples” se seguir as seguintes regras: + Efetuar todos os testes: + Sem duplicacdo de cédigo; + Expressar 0 propésito do programador * Minimizar o mimero de classes e métodos Essas regras estdio em ordem de relevancia. Regra 1 de Projeto Simples: Efetue todos os testes Primeiro ¢ acima de tudo, um projeto deve gerar um sistema que funcione como o esperado. Um sistema pode ter um projeto perfeito no papel, mas se ndo ha uma maneira simples de verificar se ele realmente funciona como 0 planejado, entiio o que esid escrito ¢ dubitavel. Unm sistema que é testado detalhadamente e que passa em todos 0s testes é um sistema passivel de testes, Isso pode parecer dbyio, mas é importante. Os sistemas que ndo podem ser testados no podem ser verificados. Logo, pode-ser dizer que um sistema que nao pode ser verificado, jamais deveria ser implementado. Felizmente, tomar nossos sistemas passiveis de teste nos direciona a um projeto no qual nossas classes Sejam pequenas ¢ de propésito tinico. Simplesmente é mais ficil testar classes que sigam o SRP. Quanto mais testes criarmos, mais seremos direcionados a coisas mais simples de serem testadas. Portanto, garantir que nosso sistema seja completamente passivel de teste nos ajuda a cria projetos melhores O acoplamento forte dificulta a criagdo de testes. Portanto, similarmente, quanto mais testes criarmos, usaremos mais princ{pios como o DIP ¢ ferramentas como a injegao de dependéncia, interfaces ¢ de modo a minimizar o acoplamento, Assim, nossos projetos se tornam ainda melhores. interessante é que ao seguir uma regra simples e 6bvia a qual afirma que precisamos ter testes e executé-los, afeta constantemente a integragiio de nosso sistema aos objetivos principais da OO de acoplamento fraco e coesao alta. Criar testes leva a projetos melhores. Regras de 2 a 4 de Projeto Simples: Refatoracao ‘Agora que temos testes, podemos manter nosso cédigo € nossas classes limpas. Para isso, refatoramos gradualmente 0 codigo. Para cada linha nova que adicionarmos, paramos ¢ refletimos sobre 0 novo projeto, Acabamos de prejudica-lo? Caso positivo, nds o limpamos e rodamos nossos Testes para nos certificar de que nao danificamos nada. O fato de termos esses testes elimina o receio de que, ao limparmos o eédigo, podemos danifici-lo. Durante a fase de refatoragao, podemos aplicar qualquer conceito sobre um bom projeto de software. Podemos aumentar a cocsdo, diminuir 0 acoplamento, separar preocupacées. modularizar as preocupagdes do sistema, reduzir nossas classes ¢ fungdes, escolher nomes melhores, e por ai vai. Ademais, também podemos aplicar as trés regras finais do projeto simples: climinar a duplicago, garantir a expressividade e minimizar o niimero de classes e métodos. Sem repetigio de cédigo 167 Sem repeticao de codigo ‘A ropetigio de eédigo é 0 inimigo principal para um sistema bem desenvolvido. Ela representa trabalho, risco e complexidade desnecessaria extras. A duplicagao se apresenta de varias formas. Linhas de eédigo que parecem idénticas so, é claro, duplicagdes. Linhas de cddigo semelhantes geralmente podem ser modificadas de modo que fiquem mais parecidas ainda para serem refatoradas mais facilmente. Ha outros tipos de duplicagao também, como a de implementacao. Por exemplo, podemos ter dois métodos na classe de uma colegao: int sizeG 0} boolean isEmpty() {} Poderiamos ter implementagdes separadas para cada método. O isEmpty poderia usar um pooleano, enquanto size poderia usar um contador. Ou poderfamos eliminar essa duplicagio colocando isEmoty na declaragio de size boolean isEmpty() { return 0 == size(); y Criar um sistema limpo requer a eliminagdo de cédigo repetido, mesmo que sejam algumas linhas. Por exemplo, considere 0 cédigo abaixo: public void scaleToOneDimension( float desiredDimension, float imageDimension) if (Math.abs(desiredDimension - imageDimension} < errorThreshold) return; float scalingFactor = desiredDimension / imageDimension; scalingFactor = (floati(Math.floor(scalingFactor * 100) * 0.01£); RenderedOp newlmage = ImageUtilities.getScaledImage( image, scalingFactor, scalingFactor); image.dispose(); system.gc(); image = newImage; public synchronized void rotate(int degrees) { RenderedOp newlmage = ImageUtilicies.getRotatedImage| image, degrees); image.dispose(); system.gc(); image = newImage; } A fim de manter esse cédigo limpo, devemos eliminar a pequena quantidade de duplicagao entre os métodos scaleToOneDimension ¢ rotate: public void scaleToOneDimension ( float desiredDimension, float imageDimension) 168 Capitulo 12: Emergéncia { if (Math.abs(desiredDimension - imageDimension) < errorThreshold) return; float scalingFactor = desiredDimension / imageDimension; scalingFactor = (float) (Math.floor(scalingFactor * 100) * 0.01f); replaceImage (ImageUtilities.getScaledImage( image, scalingFactor, scalingFactor)); } public synchronized void rotate(int degrees) { replaceImage (TmageUtilities.getRotatedImage(image, degrees) ); } private void replaceImage(Renderedop newImage) { image . dispose ( System.gc(); image = newImage; Ao extrairmos a semelhanga neste nivel baixissimo, comegamos a notar as violagdes a0 SRP. Dessa forma, podemos mover um método recém-extraido para outra classe, o que aumentaria sua visibilidade. Outra pessoa na equipe talvez perceba a oportunidade para é abstrair mais a frente © novo método e usi-lo em um contexto diferente, Essa “pequena reutilizagio” pode reduzir consideravelmente a complexidade do sistema. Entender como reutilizar uma pequena parte do cédigo é fundamental para fazer uso da utilizagdo num escopo maior. O padrao TEMPLATE METHOD?® é uma técnica comum para a remogao de duplicagao em alto nivel. Por exemplo public class VacationPolicy { public void accrueUsDivisionvacation() { /f codigo para calcular as ferias baseando-se nas horas trabalhadas ate a data AE ses // codigo para garantir que as ferias cumpram c tempo minimo nos EUA af sy #! codigo para aplicar vaction ao registro de folha de pagamento } public void accruezUDivisionvacation() { /{ codigo para calcular as ferias baseando-se nas horas trabalhadas ate a data ui // codigo para garantir que as ferias cumpram o tempo minimo nos EUA uw // codigo para aplicar vaction ao registre de folha de Pagamento ‘2 IGOFL Expressividade 169 } O cédigo ao longo de accrueusDivisionVacatione accrueEuropeanDivisionVecation é praticamente 0 mesmo, com excego do célculo do tempo minimo legal. Aquele pequeno algoritmo é alterado baseando-se no cargo do funcionério. Podemos eliminar essa duplicacdo dbvia aplicando 0 padro Template Method. abstract public class VacationPolicy { public void accrueVacation() { calculateBaseVacat ionHours(); alterForLegalMinimums(); applyToPayroll(); ) private void calculateBaseVacationHours() { /* ... */ 1; abstract protected void alterForLegalMinimums|); private void applyToPayrell(} { /* w. / i ) public class USVacationPolicy extends vacationPolicy { @override protected void alterForLegalminimums() { // Logica usada nos EUA public class EUVacationPolicy extends VacationPolicy { @override protected void alterForbegalMinimums() ( // Logica usada na Uniao Europeia } } ‘As subclasses preenchem o “buraco” no algoritmo accrueVacation, fornecendo os inicos bits de informagdes que no sio repetidos Expressividade ‘Amaioria de nds jé teve de trabalhar num cédigo intrincado. Muitos de nés mesmos j produzimos alguns cédigos confusos. Escrever cédigos que nés entendamos ¢ fécil, pois quando 0 fazemos, possuimos um conhecimento profundo do problema que desejamos resolver. Mas outras pessoas que pegarem esse mesmo cédigo nao terdo esse mesmo grau de conhecimento, ‘A maioria dos custos de um projeto de software est na manutencdo em longo prazo. A fim de minimizar os possiveis danos conforme fazemos alteragdes, ¢ crucial que sejamos capazes de entender 0 que o sistema faz. Conforme os sistemas sc tornam mais complexos, um desenvolvedor leva cada vez mais tempo para compreendé-lo, ¢ sempre ha uma chance ainda maior de mal entendidos. Portanto, 0 codigo deve expressar claramente o propésito de seu autor. Quando mais claro 0 autor tomar seu cédigo, menos tempo outras pessoas terfio de gastar para compreendé-lo. Isso reduz 0s defeitos e o custo de manutengio. Vocé pode se expressar através da escolha de bons nomes. Queremos ser capazes de ler 0 nome de uma classe ou fungo € ndo ficarmos surpresos 170 Capitulo 12: Emergéncia quando descobrirmos 0 que ela faz. Vocé também pode se expressar mantendo pequenas suas classes ¢ fungdes, que costumam ser faczis de nomear, criar ¢ entender. ‘Vocé também pode se expressar se usar uma nomenclatura padrao. Os Padrdes de Projeto, por exemplo, sio amplamente modos de comunicagao ¢ expressividade. Ao usar os nomes de padrdes, como COMMAND ou VISITOR, no nome de classes que implementem tais padrées, voc’ pode descrever de forma sucinta o seu projeto para outros desenvolvedores. Testes de unidade bem escritos também sao expressivos. Por exemplo, um objetivo principal dos testes € funcionar como um meio de documentagao. Uma pessoa que Ieia nossos testes devera ser capaz de obter um répido entendimento do que se trata uma classe. Mas a forma mais importante de ser expressivo € tentar. Muito freqiientemente, fazemos nosso cédigo funcionar e, entiio, partimos para o problema seguinte, sem considerar 0 bastante em facilitar a leitura daquele cOdigo para outras pessoas. Lembre-se de que ¢ muito mais provavel que essa outra pessoa seja voce. Portanto, tenha um pouco de orgulho em scu trabalho. Gaste um tempo em cada uma das suas fungdes € classes. Escolha nomes melhores, divida fungdes grandes em menores e, de forma geral, cuide do que vocé mesmo criou. Cuidar é um recurso precioso. Poucas classes e métodos Podem-se exagerar mesmo nos conceitos mais fundamentais, como a eliminagao de duplicagao, expressividade do cédigo e o SRP. Numa tentativa de tomar nossas classes ¢ métodos pequenos, podemos vir a criar estruturas miniisculas. Portanto, essa regra sugere que também devamos manter a minima quantidade de fungdes ¢ classes. Muitas classes e métodos, as vezes, sao 0 resultado de um dogmatismo exagerado. Considere, por exemplo, um padrao de programagao que insiste em criar uma interface para cada classe. Ou desenvolvedores que teimam em sempre separar campos ¢ comportamentos em classes de dados ¢ classes de comportamentos. Deve-se evitar tal dogmatismo ¢ adotar uma abordagem mais pragmatica. Nosso objetivo é manter nosso sistema geral pequeno ao mesmo tempo em que também mantemos nossas classes e fungdes pequenas. Lembre-se, contudo, de que essa regra é a de menor prioridade das quatro de Projeto Simples. Portanto, embora seja importante manter baixa a quantidade de classes ¢ fungdes, é mais importante ter testes, eliminar a duplicagao ¢ se expressar. Conclusao Ha uma série de priticas simples que possam substituir a experiéncia? Obviamente que nio. Por outro lado, as préticas descritas neste capitulo e neste livro sto uma forma consolidada de muitas décadas de experiéncia adquiridas pelos autores. Seguir as regras de projeto simples pode e realmente incentiva e possibilita desonvolvedores a aderirem a bons principios ¢ padrdes que, de outra forma, levariam anos para aprender. Bibliografia [XPE]: Extreme Programming Explained: Embrace Change, Kent Beck, Addison-Wesley, 199. [GOF}; Padrdes de Projeto, Solugdes Reutilizaveis de Software Orientado a Objetos, Gamma et al., Addison-Wesley, 1996 13 Concorréncia por Brett L. Schuchert “Objetos sao abstracées de procedimento. Threads sdo abstracdes de agendamento. ” — James ©. Coplien' 4st 12 Capitulo 13: Concorréncia Escrever programas concorrentes limpos ¢ dificil, muito. E muito mais facil criar um cédigo que execute uma tinica thread, assim como um cédigo multithread que pareca bom superficialmente, mas que esteja defeituoso em um nivel mais baixo, Esse cddigo funciona bem até que se use 0 sistema excessivamente. Neste capitulo, discutiremos a necessidade da programagao concorrente ¢ das dificultadas que ela representa, Depois, daremos varias sugestdes para lidar com tais dificuldades e escrever um cédigo concorrente limpo. E, ento, fechamos com as questdes relacionadas aos cédigos concorrentes de teste. Concorréncia limpa é um assunto complexo, valido um livro s6 para ele. Nossa estratégia aqui apresentar uma visao geral ¢ oferecer um tutorial mais detalhado em Concorréncia II na pagina 317. Se vocé estiver apenas curioso sobre concorréncia, este capitulo sera o suficiente por enquanto. Se precisar de um conhecimento mais profundo, ent&o leia também o tutorial. Por que concorréncia? Concorréncia é uma estratégia de desacoplamento. Ela nos ajuda a desacoplar o que € executado de quando é executado, Em aplicativos com apenas uma thread, 0 que e quando ficam 140 fortemente acoplados que geralmente pode-se determinar o estado do aplicativo inteiro apenas ao olhar o rastreamento de retorno na pilha. Desacoplar 0 que de quando pode melhorar consideravelmente tanto as estruturas quanto a taxa de transferéncia dos dados de um aplicativo. De uma perspectiva estruturada, o aplicativo seria mais como muitos minicomputadores trabalhando juntos do que um Gnico e grande main(). Isso pode facilitar a compreensio do sistema ¢ oferecer recursos melhores para separar preocupagdes. Considere, por exemplo, o modelo “Servlet” padrao dos aplicativos da Web. Esses sistemas rodam sob o “guarda-chuva” de um contéiner Web ou EJB que gerencia parcialmente a concorréncia para vocé. Os servlets sao exccutados de forma assincrona sempre que chega um pedido da Web. programador do servlet nao tem de gerenciar todes os pedidos que chegam. Em principio, cada execugio de servlet ocorre em seu proprio mundinho e fica desacoplado de todas as execugbes de outros servlets. E claro que se fosse facil assim, este capitulo nao seria necessario. De fato, o desacoplamento oferecido pelos contéineres Web esti longe de serem perfeitos. Os programadores de servlets tém se estar bem atentos de modo a garantir que seus programas concorrentes estejam corretos. Mesmo assim, as vantagens do modelo de servlet sio significantes. Mas a estrutura ndo é 0 tinico motivo para se adotar a concorréncia. Alguns sistemas possuem limites de tempo de resposta e de taxa de transferéncia de dados que requerem solugdes concorrentes programadas manualmente. Por exemplo, considere um agregador de informagdes com uma tinica thread que obtém os dados de diferentes sites da Web e os agrupa em um resumo diario. Como esse sistema s6 possui uma thread, cle consulta um site de cada vez, sempre terminando em um ¢ seguindo para o préximo. A execugao diaria precisa ser feita em menos de 24 horas. Entretanto, conforme mais websites sdo adicionados. 0 tempo também aumenta, até que sejam necessarias mais do que 24 horas para obter todos os dados. Ter uma Gnica thread implica em muito tempo de espera nos sockets da Web para que a E/S termine, Mitos e conceitos equivocados 1 Poderiamos melhorar 0 desempenho usando um algoritmo multithread que consulte mais de um website por vez. Oupense num sistema que trate de um usuario de cada vez.e exija apenas um segundo de tempo por usuario. Esse sistema é 0 suficiente para poucos usuarios, mas conforme o nimero aumentar, também crescera 0 tempo de resposta. Nenhum usudrio quer entrar numa fila atras de outros 150! Poderiamos melhorar o tempo de resposta tratando de varios usuarios concorrentemente. Ou. entdo, imagine um sistema que interprete extensas series de dados, mas que s6 oferega uma solugao completa apés processi-las todas. Talvez poderiam processar cada série em um computador diferente, de modo que muitas séries de dados fossem processadas paralelamente. Mitos e conceitos equivocados E também ha motivos irrefutiveis para se adotar a concorréncia, Entretanto, como dissemos anteriormente, usar a concorréncia é dificil. Se ndo vocé nao for muito cauteloso, poder criar situagdes muito prejudiciais. Considere os mitos e conceitos equivocados comuns abaixo: + A concorréncia sempre melhora o desempenho. Isso pode ocorrer s vezes, mas s6 quando houver um tempo de espera muito grande que possa ser dividido entre miiltiplas threads ou processadores. Nenhuma situacao ¢ trivial. * O projeto ndo muda ao criar programas concorrentes. De fato, o projeto de um algoritmo concorrente pode ser consideravelmente diferente do projeto de um sistema de apenas uma thread. O desacoplamento entre o que ¢ quando costuma ter grande impacto na estrutura do sistema. + Entender as questoes de concorréncia ndo é importante quando se trabatha com wm contéiner como um da Web ou um EIB. Na verdade, é melhor saber apenas 0 que 0 seu contéiner esta fazendo ¢ como protegé-lo das questdes de atualizagio da concorréncia e do deadlock (impasse) descrito mais adiante. A seguir esto outras frases mais corretas em relagdo & criagdo de softwares concorrentes: * A concorréncia gera um certo aumento, tanto no desempenho como na criacao de cédigo adicional. * Uma concorréncia correta é complexa, mesmo para problemas simples. + Os bugs de concorréncia geralmente ndo se repetem, portanto sao frequentemente ignorados como casos tinicos2 em vez dos defeitos que realmente representam. + A concorréncia geralmente requer uma mudanga essencial na estratégia do projeto. Desafios © que torna a programacdo concorrente to dificil? Considere a simples classe seguinte: public class x ( 174 Capitulo 13: Concorréncia private int lastIdUsed; public int getNextid() ( return ++lastIdUsed; } } Digamos que criemos uma instincia de x, atribuimos 42 ao campo lastldUsed e, entdio a compartilhemos entre duas threads. Agora, suponha que ambas as threads chamem 0 método getNext Td (). Haverd trés resultados possiveis: * Thread um recebe o valor 43, thread um recebe 44 ¢ last idused € 44. * Thread um recebe o valor 44, thread um recebe 43 ¢ LlastIdused € 44, + Thread um recebe o valor 43, thread um recebe 43 ¢ lastTdUsed € 43. O surpreendente tereeiro resultado3 ocorre quando ambas as threads se cruzam. Isso acontece porque ha muitos caminhos que clas podem seguir naquela linha de cédigo Java, e alguns dos caminhos geram resultados incorretos. Hé quantos caminhos diferentes? Para poder responder a essa questo, precisamos entender o que o Just-In-Time Compiler faz com 0 Bytecode gerado ¢ © que o modelo de meméria do Java considera atémico. Uma resposta ripida, usando 0 Bytecode gerado, ¢ que ha 12.870 caminhos possiveis de execugio para aquelas duas threads executadas dentro do método getNext Ta (). Se o tipo de last TdUse¢ mudar de int para long, o niimero de caminhos possiveis cresce para 2.704.156. E claro que a maioria desses caminhos gera resultados validos. O problema é que alguns nao Principios para protecao da concorréncia A seguir cst uma série de principios e técnicas para proteger scus sistemas dos problemas de codigos concorrentes. Principio da Responsabilidade Unica O SRPS declara que um dado método/classe/componente deve ter apenas um motivo para ser alterado. O modelo de concorréncia é complexo o suficiente para ser uma razao € ter seu proprio direito de mudanga e, portanto, merece ser separado do resto do cédigo. Infelizmente, € muito comum que se incluam diretamente os detalhes de implementagiio de concorréncia em outro cédigo de produgao. Abaixo esto alguns pontos a se levar em consideragao: + 0 cédigo voltado para a concorréncia possui seu proprio ciclo de desenvolvimento, alteracao otimizagao. + O cédigo voltado para a concorréncia possui seus préprios desafios, que sao diferentes e mais dificeis do que o cédigo nao voltado para concorréncia. +O mimero de maneiras pelas quais um cédigo voltado para concorréncia pode ser escrito de Solugo: limite 0 escopo dos dados 175 forma errada é desafiador 0 bastante sem 0 peso extra do cédigo de aplicacao que 0 cerca. Recomendagio; Mantenha seu cddigo voliado para a concorréncia separado do resto do codigo’. Solucao: limite 0 escopo dos dados Como vimos, duas threads que modificam o mesmo campo de um objeto compartilhado podem interferir uma na outra, causando um comportamento inesperado. Uma solugdo € usar a palavra reservada synchronized para proteger uma parte critica do cédigo que use aquele objeto compartilhado. E importante restringir a quantidade dessas partes. Fm quantos mais lugares os dados compartilhados podem vir a ser alterados, maiores serao as chances de: * Vocé se esquecer de proteger um ou mais daqueles lugares — danificando todos os cédigos que modifiquem aqueles dados compartilhados + Haver duplicagiio de esforgos para garantir que tudo esteja protegido de forma eficiente (violagao do Principio do Nao Se Repita7, DRY, sigla em inglés); * Dificultar mais a determinagao da origem das falhas, que ja sdo dificeis de encontrar. Recomendagio: Leve a sério o encapsulamento de dados; limite severamente 0 acesso a quaisquer dados que possam ser compartithados. Solucdo: Use cépias dos dados Essa é uma boa maneira de evitar que os dados compartilhados compartilhem seus dados. Em alguns casos podem-se fazer cépias dos objetos traté-los como somente-leitura. Fm outros casos podem-se fazer cépias dos objetos, colocar os resultados de miltiplas threads naquelas cépias c, entlo, unir os resultados numa tinica thread. Se houver uma maneira facil de evitar 0 compartilhamento de objetos, seré muito pouco provavel que o cédigo resultante cause problemas. Talvez. vocé esteja preocupado com 0 custo de toda essa criagao de objetos adicionais. Vale a pena experimentar para descobrir se isso é de fato um problema. Entretanto, se usar c6pias dos objetos permitir ao cédigo evitar a sincronizagao, o que se ganha provavelmente compensara pelas criagdes adicionais e aumento da coleta de lixo. Solucdo: as threads devem ser as mais independentes possiveis Considere escrever seu cédigo com threads de tal modo que cada thread exista em seu proprio mundo, sem compartilhamento de dados com qualquer outra thread. Cada uma processa um pedido do cliente, com todos os seus dados necessarios provenientes de uma fonte nao compartilhada € armazenada como variaveis locais. Isso faz com que cada uma das threads se comporte como se fossem a dnica thread no mundo, sem a necessidade de sincronizagdo. Por exemplo, as classes que criam subclasses a partir de HttpServlet recebem todas as suas informagdes passadas por parametros nos métodos doGet ¢ doPost. Isso faz cada Servlet 176 Capitulo 13: Concorréncia se agir como se tivesse sua propria maquina, Contanto que o cédigo no Servlet use apenas varidveis locais, nio hd como o servlet causar problemas de sincronizagao, E claro que a maioria dos aplicativos usando Servlets acabari adotando recursos compartilhados, como conexdes de bancos de dados Recomendagdo: Tente dividir os dados em subsistemas independentes que possam ser manipulados por threads independentes, possivelmente em processadores diferentes. Conhega sua biblioteca O Java 5 oferece muitas melhorias para o desenvolvimento concorrente em relagao as versdes anteriores, Ha varias coisas a se considerar ao criar cédigo com threads em Java 5: + Use as colegdes seguras para threads fornecidas. + Use o framework Executor para executar tarefas ndo relacionadas. + Use solugdes non-blocking sempre que possivel. * Classes de bibliotecas que no sejam seguras para threads. Colecées seguras para threads Quando o Java estava no inicio, Doug Lea escreveu o livro precursor Concurrent Programming, com o qual juntamente ele desenyolveu varias coledes seguras para threads, que mais tarde se tornou parte do JDK no pacote java.util.concurrent. As colegdes neste pacote so seguras para situagSes multithread ¢ funcionam bem. Na verdade, a implementagdio de ConcurrentHashMap roda melhor do que a HashMap em quase todos os casos. Além de permitir leitura e escrita concorrente simulténea ¢ possuir métodos que suportam operagdes compostas comuns que, caso contrdrio, nao seriam seguras para thread. Se o Java 5 for o ambiente de implementacao, comece com a ConcurrentHashMap Ha diversos outros tipos de classes adicionados para suportar 0 modclo de concorréncia avangado. Aqui esto alguns exemplos: ReentrantLock _| bloqueio que pode ser colocado em um método ¢ liberado em outro. ‘Semaphore | implementacao do seméforo classico, um bloqucio com um contador. CountDownLatch —_| bloqueio que espera por um numero de eventos antes de liberar todas as threads em espera. Isso permite que todas as threads tenham a mesma chance de iniciar quase ao mesmo tempo. Recomendago: Revise as classes disponiveis para vocé. No caso do Java, familiarize-se com as classes java_util.concurrent, java.util.concurrent.atomic, java.util.concurrent. locks. Conhega seus métodos de execugio 77 Conhega seus métodos de execucgao Hi diversas maneiras de dividir 0 comportamento em um aplicativo concorrente, Para falarmos sobre eles, precisamos entender algumas definigdes basicas. recursos de um tamanho ou numero fixo usado em um ambiente concorrente. Recursos limitados (Bound Resources) Conexdes de banco de dados ¢ buffers de leitura/escrita de tamanho fixo so alguns exemplos. Exclusao matua (Mutual Exclusion) ‘apenas uma thread de cada vez pode acessar dados ou recursos compartilhados. Espera indefinida (Starvation) uma thread ou um grupo de threads nao pode prosseguir por um tempo excessivamente longo ou indefinidamente, Por exemplo: sempre deixar que threads de execueao répida rodem primeiro pode fazer com que threads que levem mais tempo tenham de esperar muito caso as de execugao rapida forem infinitas. duas ou mais threads esperam que a outra termine. Cada thread possui um recurso que a outra precisa e¢ nenhuma delas pode terminar até obter 0 tal recurso. Livelock threads num entrave, cada uma tentando fazer seu trabalho, mas se deparando com outras “no caminho". Devido @ repercussio, as threads continuam tentando progredir, mas nao conseguem por um tempo excessivamente longo ou indefinido. Bloqueio infinito (Deadlock) Dadas essas definigdes, agora podemos discutir os modelos de execuco usados na programagao concorrente. Producer-Consumer® Uma ou mais threads producer criam alguma tarefa e a colocam em um buffer ou fila de espera. Uma ou mais threads consumer pegam a tarefa da fila de espera ¢ a finalizam. A fila de espera entre as producers e as consumers € um bound resource (recurso limitado). Isso significa que as producers devem esperar por espaco livre na fila de espera antes de colocar algo nela, e que as consumers devem esperar até que haja algo na fila de espera para ser recuperado. A coordenagdo entre as threads producers e consumers através da fila de espera requer que elas enviem sinais entre si. As producers escrevem na fila de espera e sinalizam que ela nfo esté mais vazia. As consumers Iéem a partir da fila de espera e sinalizam que ela no esta mais cheia. Ambas ficam na espera pela sinalizag&o para poderem continuar. 178 Capitulo 13: Concorréncia Leitores e escritores'’ Quando vocé tem um recurso compartilhado que serve principalmente como uma fonte de informagées para Icitores, mas que de vez em quando é atualizada por escritores, a taxa de transferéncia dos dados é um problema. Isso porque ela pode gerar espera indefinida (starvation) e actimulo de informagGes velhas. Permitir atualizagdes pode afetar a taxa de transferéncia dos dados. Coordenar os leitores de modo que nao leiam algo que um escritor esteja atualizando, vice-versa, ¢ um equilibrio dificil. Os escritores tendem a bloquear muitos leitores por bastante tempo, afetando assim a taxa de transferéncia dos dados. O desafio € equilibrar as necessidades tanto dos Icitores como dos escritores para satisfazer a operag&o correta, oferecer uma taxa de transferéncia de dados razodvel ¢ evitar a espera indefinida. Uma estratégia simples é fazer os escritores esperarem até que no haja mais leitores ©, entio, permitir que facam a atualizagao. Entretanto. se houver leitores constantes, os escritores ficarao numa espera indefinida. Por outro lado, se houver escritores frequentemente ¢ eles tiverem prioridade, o impacto sera na taxa de transferéncia de dados. Encontrar esse equilibrio e evitar as questdes de atualizag’o concorrente € do que trata o problema. Dining Philosophers (Problema dos Filésofos)" Imagine alguns filésofos sentados em uma mesa redonda. Coloca-se um garfo a esquerda de cada um, Ha uma grande tigela de espaguete no meio da mesa. Os filsofos passam o tempo pensando, a menos que estejam com fome. Quando isso acontece, cada um pega seu garfo e come, Um fil6sofo ndo pode comer a menos que esteja segurando dois garfos. Se o fildsofo 4 sua direita ou esquerda ja estiver usando um dos garfos que ele precisa, cle devera esperar até que aquele fildsofo termine de comer ¢ repouse 0 garfo novamente na mesa. Quando um fildsofo acaba de comer, ele devolve seu grafo 4 mesa e espera ficar com fome novamente. Substitua os filésofos por threads e os garfos por recursos. Esse problema ¢ semelhante a muitos aplicativos corporativos nos quais os processos competem pelos recursos. A menos que tenha sido meticulosamente desenvolvido, os sistemas que competem dessa forma podem sofrer deadlock, livelock e queda na taxa de transferéncia de dados ¢ no desempenho. A maioria dos problemas de concorréncia que vocé encontrar sera uma variacdo desses trés. Estude esses algoritmos ¢ crie solugdes usando-os 4 sua maneira, de modo que, ao se deparar com problemas de concorréncia, vocé esteja mais preparado para resolvé-lo Recomendagio: Aprenda esses algorinmos bisicas ¢ entenda suas solucdes. Cuidado com dependéncias entre métodos sincronizados Dependéncias entre métodos sincronizados causam pequenos bugs no codigo concorrente. A linguagem Java possui a palavra reservada synchronized, que protege um tnico método. Entretanto, se houver mais de um método sincronizado na mesma classe compartilhada, entéo seu sistema pode ser sido escrito incorretamente!?. Mantenha pequenas as segdes sincronizadas 179 Recomendagio: evite usar mais de um método em um objeto compartilhado Haver vezes em que vocé deverd usar mais de um método em um objeto compartilhado. Neste caso, ha trés formas de deixar 0 cédigo certo: + Bloqueio voltado para o cliente: faga o cliente bloquear o servidor antes de chamar o primeiro método ¢ certifique-se de que o bloqucio inclua 0 cddigo que chama o iltimo metodo. + Bloqueio voltado para o servidor: dentro do servidor, crie um método que bloqueie 0 servidor, chame todos os métodos e, ent&o, desbloqueie, Faga o cliente chamar 0 novo método. + Servidor extra: crie um servidor intermediério que efetue o bloqueio. Esse é um exemplo de bloqueio voltado para 0 servidor, no qual 0 servidor original nao pode ser alterado. Mantenha pequenas as segées sincronizadas Apalavra reservada synchronized adiciona um bloqueio. Todas as seydes do ¢6digo protegidas pelo mesmo bloqueio garantem que ha apenas uma thread em execugdo para todas elas num dado momento. Os bloqueios sao prejudiciais, pois causam atrasos ¢ adicionam trabalho extra. Portanto, no queremos amontoar nosso eédigo com instrugdes synchronized Por outro lado, devem-se proteger segdes criticas!”. Sendo assim, desejamos criar nosso cédigo com o menor numero possivel de segdes criticas. Alguns programadores ingénuos tentam conseguir isso tornando as segdes criticas muito grandes. Entretanto, estender a sincronizagao além da segao critica minima aumenta os conflitos ¢ prejudica o desempenho"*. Recomendacaio: mantenha suas se¢des sincronizadas as menores possiveis. E dificil criar cédigos de desligamento corretos Criar um sistema que deva ficar para sempre executando é diferente de criar algo que funcione por um tempo e, entdo, desligue de maneira adequada. Obter um desligamento adequado pode ser dificil. Dentre os problemas comuns esto 0 deadlock'*, com threads esperando por um sinal que nunca chega para continuar. Por exemplo, imagine um sistema com uma thread pai que gera diversas threads filhas ¢, entdo, antes de liberar seus recursos ¢ desligar, espera que todas elas finalizem, E sc uma das threads filhas sofrer deadlock? O pai esperard para sempre ¢ o sistema jamais desligara. Ou pense num sistema semelhante que tenha sido instruido a desligar. A thread pai diz a todas 1s suas filhas para abandonar suas tarefas ¢ finalizar. Mas e se duas das filhas estiverem operando como um par producer/consumer? Suponha que a thread producer receba o sinal da thread pai ¢ desligue imediatamente. A consumer talvez estivesse esperando uma mensagem da producer ¢ bloqueada de modo que nao consiga receber o sinal para desligamento. Ela ficaria presa esperando pela producer ¢ nunca finalizar, evitando que a thread pai também finalize. Situagdes como essa nfo s4o to incomuns assim. Portanto, se vocé precisar criar um ebdigo concorrente que exija um desligamento apropriado, prepare-se para passar a maior parte de seu tempo tentando fazer com que 0 desligamento ocorra com sucesso. 180 Capitulo 13: Concorréncia Recomendagio: Pense 0 quanto antes no desligamento e faca com que ele funcione com éxito Vai levar mais tempo do que vocé espera. Revise os algoritmos existentes, pois isso é mais dificil do que voce imagina. Teste de cédigo com threads Considerar que 0 cédigo esta correto, impossivel. Testes ndo garantem que tudo esteja correto. Entretanto, eles podem minimizar os riscos. Isso tudo é valido para uma soluggio com uma tinica thread, Enquanto houver duas ou mais threads usando 0 mesmo cédigo e trabalhando com os mesmos dados compartilhados, as coisas s¢ tornam consideravelmente mais complexas. Recomendacio: Crie testes que consigam expor os problemas e, entao, execute-os frequentemente, com configuracées programdticas e configuragies e carregamentos de sistema. Se o teste falhar, rastreie a falha. Néo ignora uma falha sé porque o teste ndo a detectou no teste seguinte. E muito para se assimilar. Abaixo esto algumas recomendagdes mais detalhadas; + Trate falhas falsas como questées relacionadas as threads. + Primeiro, faga com que scu cédigo sem thread funcione. + Torne seu cédigo com threads portatil. «Tome seu cédigo com threads ajustavel. + Rode com mais threads do que processadores. + Rode em diferentes plataformas + Altere seu cddigo para testar e forcar falhas. Trate falhas falsas como questées relacionadas as threads. cédigo que usa threads causa falhas em coisas que “simplesmente nao falham”. A maioria dos desenvolvedores nio entende como o uso de threads interage com outros cddigos (incluindo seus autores). Os bugs em cédigos com threads podem mostrar seus sintomas uma vez a cada mil ou milhares de execugdes. Tentativas para repetir os eros no sistema podem ser frustrantes. Isso geralmente leva os desenvolvedores a descartarem as falhas como raios césmicos, uma pequena falha no hardware ou outro tipo de “casos isolados”. E melhor assumir que nfo existem casos isolados, os quais quanto mais forem ignorados, mais o cédigo seri construido no topo de uma abordagem possivelmente falha. Recomendagio: Nao ignore fathas de sistema como se fossem casos isolados. Primeiro, faga com que seu cédigo sem thread funcione Isso pode parecer dbvio, mas nao custa nada repetir. Certifique-se de que o cédigo funcione sem threads. Geralmente, isso significa criar POJOs que so chamados pelas suas threads. ‘Teste de cédigo com threads 181 Os POJOs niio enxergam as threads ¢, portanto, podem ser testados fora do ambiente com threads. Quando mais locais no seu sistema vyocé conseguir colocar tais POJOs, melhor. Recomendagio: Nao procure bugs nao relacionados a threads com os relacionados a elas ao mesmo tempo. Certifique-se de que seu cédigo funcione sem threads. Torne seu cédigo com threads portatil Criar © e6digo que suporte concorréncia de modo que possa ser executado em diversas configuragdes: + Uma thread, varias threads, variagdes conforme a execugao. + 0 cédigo com threads interage com algo que possa ser tanto real como artificial. + Execute com objetos artificiais que rodem de forma répida, lenta ¢ varidivel. + Configure testes de modo que possam rodam para um certo niimero de iteracdes Recomendagio: Faga de seu cédigo com threads especialmente portatil de modo que possa execuid-lo em varias configuragées. Torne seu cédigo com threads ajustavel Obter 0 equilibrio certo entre as threads requer testar e errar. O quanto antes, encontre maneiras de cronometrar 0 desempenho de seu sistema sob variadas configuragdes. Possibilite para que 188 threads possam ser facilmente ajustadas. Considere permitir a alteracaio enquanto o sistema estiver em execugao. Considere permitir um auto-ajuste baseando-se na taxa de transferéncia de dados e no uso do sistema. Rode com mais threads do que processadores. Coisas acontecem quando o sistema alterna entre as tarefas. A fim de incentivar a troca (swapping) de tarefas, execute mais threads do que os processadores ou micleos presentes. Quanto mais frequentemente suas tarefas alternarem, mais provavelmente vocé descobriré partes do cédige que precisam de uma segao critica ou que causa um deadlock. Rode em diferentes plataformas Em meados de 2007, elaboramos um curso sobre programagao concorrente. O curso foi desenvolvido essencialmente sob a plataforma OS X. Apresentamos a turma usando 0 Windows XP rodando sob uma maquina virtual (VM, sigla em inglés). Criamos testes para demonstrar que as condigdes para falhas ocorriam mais frequentemente num ambiente OS X do que em um XP. Em todos 0s casos, sabia-se que 0 cédigo testado posstia erros. Isso 86 reforgou 0 fato de que sistemas operacionais diferentes tém diferentes politicas de tratamento de threads, cada uma afetando a execugio do cédigo. O cédigo multithread se comporta de maneira diferente em ambientes diversos'* Vocé deve rodar seus testes em cada possivel ambiente de implementacao. 182 Capitulo 13: Concorréncia Recomendagdo: Execute 0 quanto antes e frequentemente seu cédigo com threads em todas as plataformas finais. Altere seu cédigo para testar e forgar falhas E normal que as falhas se escondam em cédigos concorrentes. Testes simples nado costumam expé-las. Na verdade, elas costumam se ocultar durante o processamento normal ¢ talvez s6 aparegam uma vez em algumas horas, ou dias, ou semanas! O motivo que toma os bugs em threads raros, esporadicos ¢ de rara reincidéneia é que muito poucos caminhos dos milhares possiveis através de uma seco realmente falham. Portanto, a probabilidade de se tomar um caminho falho ¢ extraordinariamente pequena. Isso dificulta muito a detecedo e a depuragao. ‘Como vocé poderia aumentar suas chances de capturar tais raras ocorréneias? Vocé pode alterar seu codigo ¢ forga-lo a rodar em diferentes situagdes através da adigZio de chamadas a métodos como Object .wait(), Object.sleep(), Object.yield() ¢ Object.priority(). Cada um deles pode afetar a ordem da execusao, aumentando assim as chances de detectar uma falha, E melhor que o cédigo falhe o quanto antes. Ha duas opgdes para alteragao: * Manualmente *Automatizada Manualmente Vocé pode inserir manualmente as chamadas a wait(), sleep(), yield()e priority(). E bom fazer isso quando estiver testando uma parte capciosa do codigo. O exemplo abaixo faz exatamente isso: public synchronized String nextUrl0rNull() { if(hasNext()) { String url = urlGenerator.next(); Thread.yield(); // inserido para testar. updateHasNext(); return url; } return null; } ‘A chamada ao yield() inserida mudaré os caminhos de execugaio tomados pelo cédigo € possivelmente fard o cédigo falhar onde nao havia erro antes. Se isso ocorrer, nao foi porque vocé adicionou uma chamada ao yield()"", mas porque seu c6digo j4 possufa a falha e isso simplesmente a tornou evidente. _ Teste de cédigo com threads 183 Ha muitos problemas com essa abordagem: + E preciso encontrar manualmente os locais onde fazer isso. + Como saber onde e qual tipo de chamada colocar? * Deixar tal cédigo em um cédigo de produgao sem necessidade atrasa 0 c6digo. + Essa abordagem ¢ um tiro no escuro. Vocé pode ou nao encontrar falhas. De fato, as probabilidades no esto a seu favor. Precisamos de uma forma de fazer isso durante a fase de testes, e nao na de produgao. Também temos de misturar com facilidade as configuragdes entre as diferentes execugdes, 0 que aumentaré as chances de encontrar erros no todo. Claramente, se dividirmos nosso sistema em POJOs que no saibam nada sobre as threads ¢ as classes que controlam o uso daquelas, sera mais ficil encontrar os locais apropriados para alterar 0 cédigo. Ademais, poderiamos criar muitas variacdes de testes que invoquem os POJOs sob sistemas diferentes de chamadas a sleep. yield, ¢ assim por diante. Automatizada Voeé poderia usar ferramentas como um framework orientado a aspecto, CGLIB ou ASM para alterar seu cédigo de forma automitica. Por exemplo, voc’ poderia usar uma classe com um nico método: public class ThreadJigglePoint ( public static void jiggle() ( , ? Vocé pode adicionar chamadas a ele em varios lugares de seu cédigo: public synchronized string nextUrlorNull() { if(hasNext()) ( ThreadJiglePoint .jiggle(); String url = urlGenerator.next(); ThreadJiglePoint.jiggle(); updateHasNext(}; ThreadJiglePoint jiggle(); return url; } return null; J Agora vocé usa um aspecto simples que selecione aleatoriamente entre fazer nada, dormir ou ficar passivo. Ou imagine quea classe ThreadJigglePoint tem duas implementagdes. A primeira implementa jiggle, no faz nada ¢ é usada na produgdo. A segunda gera um nimero aleatério para selecionar entre dormir, ficar passivo ou apenas prosseguir. Se executar seus testes mil vezes com essa 184 aleatoriedade, talvez vocé revele algumas falhas. Se o teste passar, pelo menos vocé pode dizer que teve a devida diligéncia. Embora um pouco simples, essa podcria ser uma op¢ao razovel em vez de uma ferramenta mais sofisticada. Ha uma ferramenta chamada Contest'*, desenvolvida pela IBM que faz algo semelhante, mas com um pouco mais de sofisticacao. A questo € testar 0 cédigo de modo que as threads executem em ordens diferentes em horas diferentes. A combinagao de testes bem escritos e esse processo podem aumentar consideravelmente as chances de encontrar erros. Recomendagao: Use essas estratégias de testes para desmascarar erros. Conclusdo E dificil conseguir um cédigo concorrente correto. Um cédigo simples de se seguir pode se tornar um pesadelo quando miltiplas threads e dados compartilhados entram em jogo. Se voce estiver criando esse tipo de cédigo, & preciso manté-lo rigorosamente limpo. ou havera falhas sutis e nao frequentes. Primciro ¢ acima de tudo, siga o Principio da Responsabilidade Unica. Divida seu sistema em POJOs que separem o cédigo que enxerga threads daquele que as ignora. Certifique-se de que vocé esta testando apenas seu cédigo que usa threads e nada mais. Isso sugere que ele deva ser pequeno e centralizado. Tenha em mente as possiveis fontes de problemas com concorréncia: miltiplas threads operando em dados compartilhados ou usando uma fonte de recursos em comum, Casos de limites, como desligar adequadamente ou terminar a iteragao de um loop, pode ser um risco a mais. Estude sua biblioteca e conhega os algoritmos essenciais. Entenda como alguns dos recursos oferecidos por ela dao suporte a solugdo de problemas semelhantes aos proporcionados algoritmos essenciais Aprenda como encontrar partes do cédigo que devam ser bloqueadas e as bloqueie — faga isso apenas com as que realmente precisem ser. Evite chamar uma seedo bloqueada a partir de outra, pois isso requer um grande entendimento se algo deve ou nao ser compartilhado. Mantenha a quantidade de objetos compartilhados ¢ escopo do compartilhamento o mais curto possivel. Altere os modelos dos objetos com os dados compartilhados para acomodar os clientes em vez de forcar estes a gerenciar o estado compartilhado, Problemas surgirao, Os que nao aparecem logo geralmente so descartados como casos isolados. Esses famosos “casos isolados” costumam ocorrer apenas na inicializagio ou em momentos aleatérios. Portanto, é preciso ser capaz de rodar repetidamente ¢ constantemente seu cédigo com threads em muitas configuragdes ¢ plataformas. A capacidade de ser testado, que vem naturalmente com as Trés Leis do TDD, implica certo nivel de portabilidade, 0 que oferece © suporte necessério para executar 0 cédigo numa gama maior de configuragdes. Vocé pode melhorar consideravelmente suas chances de encontrar erros se tomar seu tempo para manipular seu cédigo. Isso pode ser feito manualmente ou com alguma ferramenta automatizada, Invista nisso o quanto antes. E melhor ter executado seu cédigo com threads 0 maximo possivel antes de colocé-lo na fase de produgao. 14 Refinamento Sucessivo Caso de estudo de um analisador sintatico de parametro em uma linha de comando Este capitulo é um caso de estudo sobre como obter um refinamento com éxito. Vocé vera um médulo que comegard bem, mas nao progredira mais. Ent&o, vera como ele sera refatorado ¢ limpo. 188 Capitulo 14: Refinamento Sucessivo A maioria de nds, de tempos em tempos, tem de analisar a sintaxe dos pardmetros na linha de comando. Se no tivermos um utilitério adequado, entao simplesmente deixamos passat o array de strings passado 4 fungao main. Ha varios utilitérios bons disponiveis, mas nenhum deles faz exatamente 0 que quero. Portanto, decidi criar 0 meu préprio, que chamarei de Aras. E muito simples us4-lo. Basta criar uma classe Args com os parametros de entrada e uma string de formato, c, entao, consultar a instancia Args pelos valores dos parametros. Por exemplo, veja o exemplo abaixo: Listagem 14-1 Use simples de Args public static void main(stringl) args) ( try { Args arg = new Args(*l,p#.d*", args boolean logging = arg.getBoolean('1 int port = arg.getInt('p'it String directory = arg.getString('d'); executeapplication!logging, port, directory): } catch (ArgsException e} [ Systen.out printf (*Argunent error: $s\n", e.errorMessage|)); Viu a simplicidade? Apenas criamos uma instncia da classe args com dois pardmetros. O Primeiro é a string do formato, ou esquema: “1, p#,4*.” Ela declara trés pardmetros na linha de comando. O primeiro, -1. € booleano; o segundo, -p, € um inteiro; e o terceiro, -d, é uma string. O segundo parametro do construtor Args é um array simples passado como parimetro na linha de comando para main. Seo construtor retornar sem langaruma ArgsExcept.ion, entdoa linha de comando da entrada & analisada e a instincia Args fica pronta para ser consultada. Métodos como get Boolean, getInteger € getString nos permite acessar os valores dos argumentos pelos nomes. Se houver um problema, seja na string de formato ou nos pardmetros da linha de comando, sera lancada uma ArgsException. Para uma descrig&o adequada do que deu errado, consulte o método errorMessage da excegio. Implementacao de Args A Listagem 14.2 é a implementago da classe Args. Leia-a com bastante atengio. Esforcei-me bastante no estilo e na estrutura, e espero que valha a pena. Listagem 14-2 Args. java package com.objectmentor,utilities.args: import static com.objectmentor-utilities.args ArgsException.BrrorCode,*; import java.util.*; public class Args private Map marshalers; Implementagio de Args 189 Listagem 14-2 Args java private Set argsFound; private ListIterator currentArgument; public Args(String schema, String[] args) throws ArgsException { marshalere - new HashMap(i; parseSchema (schema) : parseArgunent Strings iArrays.astist (args) ); ) private void parseSchema(String schema) throws ArgsException { for (String element : schena.split(",")) if (element .iength() > 0) parseSchema2lemenc (element .trim()|; é } private void parseSchemaElement (String element) throws ArgsException ( char element Td = element .charat (9); String elementTail = element .substring(1}; validateSchenaélenent1a element 14) ; if (elementTail.length() == 0) narghalers.put (elerent Id, new BooleanArgunentMarshaler()): else if [elenentTail-equals("*")) narshalers.put (elerentTd, new StringArgunentéarshaler (!): else if (elementTail.equals("#"|) narshalers.put (elementId, new IntegerArgunentMarshaler (}) i else 1f (elementTail.equals("##*)} marshalers.put (elenentId, new DoubleargumentMarshaler |)); else if (elementTail.equals("[*]")) rarshalers.put (elenentId, new Stringarr else ‘throw new ArgsException(INVALID_ARGUMENT_FORMAT, elenentTd, element Tail); rgunentMarshaler(i}7 private void validateSchemaElenentIdichar elenentld} throws ArgsException | if (!Character. isLetter (element 1d) } throw new ArasExcenticn(TSVALID_ARGUMENT NAME, elementId, null); } (bist argsList) throws ArgsException private void parseArgumentStrin. for (currentArgurent = argsList.listIterator(); currentArgument .hasNext ();) String argString = currencargument .next |); if (argString.startswith("-"1) { parseArgument Characters (argString.substring(1i)+ } else { currentArgument .previous(); oreak; } 3 1 190 Capitulo 14: Refinamento Sucessivo Listagem 14-2 Args.java private void parseargunentCharacte ing argChars) throws ArgsException { for (int i= 0; i < argChars.length(|; i++) parseArgumentCharacter (argChars.charAt (i!) private void parsearounentCharacter(char argChar) throws ArgsException { ArgumentMarshaler m = marshalers.get |argChar! ; if (== null) { throw new ArgsExcept ion UNEXPECTED ARGUMENT, argChar, null); }else { sFound.add (argChar! : try { set (currentArgument) ; } catch {ArgsException e} ( €.setErrorArgument Id (argChar) ; E throw e; } 4 public boolean has(char ara) ¢ return argsFound. contains (arg): ] public int nextArgument () { retuim currentArgunent nextIndex (1; public boolean getBoolean|char arg) { return SooleanArgunentMarshaler .getvalue!marshalers.get (arg)); public String getString(char arg) ( return StringArgunentMarshaler.getValue(marshalers get (arg))1 yi public int getInt (char arg| return IntegerArgunentMarshaler,getValue(marshalers.get (arg) ); iy public double getDoubletchar erg! [ return DoubleArgument Marshaler .getValue (marshalers.get (arg)) : public String[] getscringarray {char arg} { return StringArrayArgumentNarshaler.getValue(marshalers.get (ara)); Note que vocé consegue ler este esse cédigo de cima para baixo sem ter de saltar muito para la e para cd. Uma coisa que vocé teve de olhar um pouco mais a a frente é a definigao de ArgumentMarshaler, que deixei de fora propositalmente. Apés ter lido com ateng4o esse codigo, vocé deve ser capaz de entender o que fazem a interface ArgumentMarshaler ¢ scus derivados. Irei Ihe mostrar alguns deles agora (Listagem 14-3 até 14.6). Implementagao de Args 191 Listagem 14-3 ArgumentMarshaler. java public interface ArgunentMarshaler { void set |Iterator currentArgument) throws ArgsExcept i } Listagem 14-4 ArgumentMarshaler. java public class BooleanArgunentYarshaler implements ArgunentMarshaler private boolean bocleanValue = false; public void set (Iterater currentargument) throws ArgsException { pooleanValue = true; 1 public static boolean getValue(ArgumentMarshaler am) if (am != null £& am instanceof BooleanArgunentMarshaler) return | (BooleanArgumentNarshaler) am) .booleanValuc; else return false; Listagem 14-5 StringArgumentMarshaler.java import static com.objectmentor.utilities.args.ArgsException.Errorcode.*; public class StringArgumentMarshaler implements ArgumentMarshaler { ate String stringvalue = " pri public void set (Itera try { stringValue = currentArgument .next (); } catch (NoSuchElementException 2) { throw new ArasExcept ion (MISSING_STRING! ; ‘or currentArgument} throws ArasException { } public static String getValue(ArgumentMarshaler am) { if (am != null && am instanceof StringArgumentMarshaler) return {(StringArgumentMarshaler) am) .stringValue; else return"; 9 Capitulo 14: Refinamento Sucessivo Listagem 14-6 IntegerArgumentMarshaler. java import static con.objectnentor utilities.args.ArgsException.frrorcode.*; | public class IntegerArgumentMarshaler implements Argumentwarshaler { private inc intvalue = 0; public void set (Iterator currentArgument) throws ArgsException { String parameter = null; try ( parameter = currentargument next ()+ intValue iteger.parselnt (parameter); } catch (MoSuchElenentException e) { ew ew ArgsException (MISSING_INTEGER) ; ) catch @amberFormat Exception 2] [ throw nex ArgsException(INVALID_INTEGER, parameter); publi¢ static int cetVelueiArgunentMarshaler am) { if (am != null && am instanceof IntegerArgunenctlarshaler} return ((IntegerArgunentNarshaler} am) .intVah else return O outro ArgumentMarshaler derivado simplesmente replica esse padrao para arrays tipo doubles e String , ¢ serve para encher esse capitulo. Deixarei-os para que vocé pratique neles. Outra coisa pode estar Ihe incomodando: a definigo das constantes dos cédigos de erro. Elas estdo na classe ArgsException (Listagem 14.7). Listagem 14-7 ArgsException. java import static com.objectrentor-utilities.args.ArgsException.frrorCode.* public class Integerargu private int entWarshaler implements ArqumentMarshaler { tvalue = G: public void set (Iterator currentArgument) throws ArgsException { String paranerer = null; parameter = currencargument next (); javvalue = Integer.parseint (parameter) ; } catch (NoSuch#lementaxcepticn €} [ throw new ArgsExcept ion (MISSING_INTECER} ; itch (NumberFormatExcepticn e) { throw new ArgsExcept ion (INVALID_INTEGER, paramecer| } public static int gerValue(ArgumentNarshaler am) { if (am != null && am instenceof IntegerArguneatMarshaler/ return ((IntegerArgunentMarshaler) am) .intValue; else return Implementagao de Args 193 itagem 14-7 (continuagao) ArgsException. java public Argstxcept ion (Errorcode errorcode, char errorArgunentid, string errorvaraneter! { this.errorfode = errorCode; rParameter = errarParameter; rorArgumentid = errorArgument Id; public char getErrorargumentid(} i return errorArgunent 1d; public void seterrorArgumentId(char errorArgumenttd) { this.errorArgunentid = errorArgument1d; } public String getErrorParameter() ( return errorParaneter? ) public void setfrrorParameteriString errorParameter) { this.errorParameter = errorParaneter; ) public ErrorCode getErrorcode|) { return errorCode; d public void setarrorCode(ErrorCode errorCode) { this.errorCode = errorcode: 3 public String errorNessage( switch (errorCode) { case OK return "TIL case UNEXPECTED_ARGUMEN? return String. format ("Argunent -S¢ unexpected.*, errorArgumentd case MISSING_STRING: return String. format ("Could not find string parameter for -%c.", rgument 1d) ; get here."; case INVALID_INTEGER: return String. format ("Argument -S expects an integer but was '$5'.", errorArgumentTd, errorParameter) ; case MISSING_INTEGER: return String. format (*Could not find integer parameter for -%c.*, errorargunent14) ; case INVALID_DOUBLE: return String. format (‘Argument -%c expects a double but was ‘ts".", errorargumentid, errorParameter) ; case MISSING_DOUBLE: return String. format (*Could not find double parameter for -%c.", errorArgument 14); case INVALID_ARGUMENT_NAME: return String.format(**éc' is not a valid argument nane.", errorArgument 1d) ; 194 Capitulo 14: Refinamento Sucessivo agem 14-7 (continuagio) ArgsException. java case INVALID_ARGUMENT_FORMAT: return String. format i"'#s' is not a valid argument format.*, errorParemeter return *"; public enum ErrorCode { CK, TNVALID_ARGUMENT_FORWAT, UNEXPECTRO_ARGUMENT, INVALID_ARGUMENT_NANE, NTEGER, E impressionante a quantidade de cddigo necessaria para esclarecer os detalhes deste simples conceito. Um dos motives disso é que estamos usando uma linguagem particularmente prolixa, Como Java é uma linguagem estatica, ¢ preciso digitar muitas palavras de modo a satisfazer o sistema de tipos. Em linguagens como Ruby, Python ou Smalltalk, esse programa fica muito menor! Leia 0 cédigo novamente. Atente especialmente para os nomes escolhidos, 0 tamanho das fungdes ¢ o formato do cédigo. Se vocé for um programador experiente, talvez faga uma critica aqui ¢ ali ¢ em relagao ao estilo ou a estrutura. De modo geral, entretanto, espero que perceba que este programa esta bem escrito ¢ possui uma estrutura limpa. Por exemplo, deve ficar 6bvio como adicionar um novo tipo de parametro, como uma data ‘ou um mamero complexo, ¢ que essa inserc%o exige pouco esforgo. Em suma, simplesmente bastaria criar um derivado de ArgumentMarshaler, uma nova fun¢ao getxxx e uma nova instrugdo case na fungdo parseSchema=lement. Provelmente haveria também um na nova ArgsException.£rrorCode e uma nova mensagem de erro. Como fiz isso? Permita-me tirar sua diivida. Eu nao criei simplesmente esse programa do inicio ao fim neste formato que ele se encontra agora. E, mais importante, nfo espero que vocé seja capaz de escrever programas limpos ¢ elegantes de primeira. Se aprendemos algo ao longo das tltimas décadas, é que programar é mais uma arte do que ciéncia, Para criar um cédigo limpo, é preciso criar primeiro um “sujo” e, entdo limpé-lo. sso no deveria ser surpresa para vocé. Aprendemos essa verdade na escola quando nossos professores tentavam (em vao) nos fazer escrever rascunhos de nossas redagdes. O processo, eles diziam, era criar um rascunho e, depois, um segundo e, entio, varios outros até que chegassemos a versdo final. Escrever redagées limpas, eles tentavam nos dizer, é uma questo de refinamento constante. A maioria dos programadores iniciantes (assim como muitos alunos do ensino fundamental) nao segue muito bem esse consclho. Eles acreditam que 0 objetivo principal é fazer o programa funcionar e, uma vez conseguido, passam para a tarcfa seguinte, deixando o programa “que funciona” no estado em que estiver. A maioria dos programadores experientes sabe que isso € suicidio profissional. Args: 0 rascunho 195 Args: o rascunho A Listagem 14.8 mostra uma versao anterior da classe Args. Ela “funciona” ¢ esta uma zona. Listagem 14-8 Args.java (primeiro rascunho) import java.text .ParseException; import java.util; public class args { private String schena; private String!] args; private boolean valid = true; private SetcCharacter> unexpectedArgument's private Map booleanargs new HashMap (1 private Map stringArgs = new HashMap(); private Nap intArgs = new HashMap(); private Set @rgsFound = new HashSet ||; private int currentArgunen private char errorArgunentid = "30°; private String errorParameter = *TILT private ErrorCode errorCode = ErrorCode .CK; new TreeSet(); private enum ErrorCode { OK. MISSING_STRING, MISSING_INTEGER, IN VALID_INTEGER. UNEXPECTED_APGUMENT} public Args(String schema, String|] args) throws FarseException ( this.schema = schema; this.args = args; valid = parse(): , private boolean parse(} throws PareeException { if (schema.lengthii == ( && args.length return true; parseSchema(); try { parseArgument } catch (ArgsExcer } return valid; ) i jon el { private boolean parseSchema() throws ParseException ( for (String element : schema.split(',")) { 196 Capitulo 14: Refinamento Sucessivo Listagem 14-8 (continuagao) Args.java (primeiro rascunho) if (element length() > 9) { String trimmed#lement = element. trim(); parseSchemaElement (trimnedElement} ; return true; } private void parseSchonaflement (String element) throws Parsesxception ( char elementid = element chart (0); String élenentTail = elenent .substring il): validateSchemaElenent 14 [element Idj ; if (isBooleanSchenaElenent (elenentTail)) parseBooleanSchemazlenent (element 1d) ; else if (1eStringSchenaElenent (element?ail) ) ‘ parseStringSchenaflenent (element 1a) ; else if [isIntegerScheraflenent (elementTail)) { parseIntegerSchemaflanent [element 1d) ; } else ( throw new Parsekxception( String. format (*Argunent: $c has invalid format elementId, elenentTaili, 0); private void validateschemaBlement 1d(char elementid) throws ParseException { if (Character. ishetter(elementId)) [ row new ParseExcept ion ( "Rad character:" + elementId + ‘in Args format: * + schema, 0); B private vo1d parseBooleanSchenaElement (char elementId) { booleanargs.put (elementid, falsel; private void parseIntegerschemazlement (char élementid) { intaras.put (elementid, 0) private void parseStringSchemaflenent {char elementId} { stringargs.put lelenentid, **) private boolean isScringSchemaFlement |Srring elementTailj ( return elementTail.equals(**"); private boolean isBooleanSchemaklement (String element Tail) { return elementTail.length(} == 0; private boolean isTntegerSchensElement (String elementTail) { return elemenc’tail.equals(*#*): ’ Args: 0 rascunho 197 Listagem 14-8 (continuagao) Args.java (primeiro rascunho) privace boolean parseArgumentsi} throws ArgsException { for (currentargument = 0; currentArgument < args.length; currentArgument++) String arg = args{currentArgunent] parseArgument !argi; ] return crue; 3 private void parseArgutent (String arg) throws ArgsException { if (arg.startswich(*-")} parseElements (ara ; private void parseSlementa(string arg) throws Argsexception-{ for (int i= 1: i < arg.lengthl); i++) parseElenent (arg chart (i); I private void parselement (char argChar} throws argskxception ( if (setArgument (argChar)) argsFound.add(argChar} ; else { unexpectedarguments .add(argChar) ; errorCede = ErrorCode.UNEXPECTED_APGUNENT; valid = false; I private boolean setarqument (char argchar! 2 if (isBooleanArg(argchar) } setBooleanArg(argChar, true): else af (isStringArg(argChar}} setStringarg(araCher): eise if (isIntarg(zrachar)) setIntAra(arochar) ; rows ArgsExceptien { el turn false; return true; private boolean isIntArgichar argChar) {return intargs.containsKey(argCharl :} private void setIntargichar argChar) throws ArgsException { currentargunent++; String parameter = null try ( arameter = args|currentArgunent); tArgs.put (argChar, new Integer (parameter) |: ) catch (ArrayIndexCutofBoundsException e} { id = false; errorArgunent1é = argchar; errorCode = ErrorCode.MISSING_INTEGER: 198 Capitulo 14: Refinamento Sucessivo Listagem 14-8 (continuagio) Args.java (primeiro rascunho) throw new ArgsException(); } catch (NunberForaatExcept ion e) { valid = false; errorArgunentid = argChar; errorvaraneter = parameter; errorCode = ErrorCode. INVALID_INTEG! throw new ArgsExcept ion |) ; ) BR; private void setStringArgichar argChar) throws ArgsException { currentArgument++; try { stringargs.put (argChar, args[currencargument |); } catch (ArrayindexOutOfsoundsException e) { : valid = false; errorArgumentId = argChar: errorCode = FrrorCode.MISSING_STRING; throw new ArgsException() ; ) } private boolean isStringArg(char argChar) { return stringArgs.containsKey |argChar! ; private void secBcoleanarg(char argthar, boolean value) ( booleanArgs.put fargChar, value) ; } private boolean isBooleanArg(char argChar) { return booleanArgs.containsKey (argChar) : ¢ int cardinality!) { return argsFound.size(i; ) public String usage() { if ischema.length() > 0) return *-[" + schema + else recurn "*; } public String errorMessage|) throws Exception { switch jerrorCode) [ case OK: ow new Exception(*TIL?: Should not get here."}; case UNEXPECTED ARGUMENT: return unexpectedargument Message (); case MISSING_STRING: return String. format(*Could not find string parameter for -tc.*, errorArgument Id) Args: 0 rascunho 199 Listagem 14-8 (continuacio) Args.java (primeiro rascunho) case INVALID_INTEGER return String. format ("Argument -8c expects an integer but was ‘#s".*, errorArgumentId, errorParameter) ; case MISSING_INTEGER: return String. format ("Could not find integer parameter for -tc.", errorArgunent id: } return ** private String unexpectedArgumentMessage() ! StringBuffer message = new StringBuffer ("Argument Is) ="); for (char c : unexpectedArguments) { message. append ic) ; } a message.append(* unexpected.*) ; return message. toString(); } private boolean falselfNull(Hoolean b) { return b != null && b; private int zerolinull (Integer 1) { return == null ? 0: i; b ate String blankTiNull (String s) { null 2" 257 pri retum § public String getstring(char arg) { return blankTfNull (stringArgs.get (arg) | ; } public int getintichar arg) 4 return zerolfliull (intargs.get (arg) |; public boolean getBooleanichar arg) [ return falseIfNull (booleanargs.get (arg!) ; } public boolean hasichar arg) { return argsFound.contains (arg) ; public boolean isValia(} return valic } private class Argsixception extends Exception } 200 Capitulo 14: Refinamento Sucessivo Espero que sua reagdo inicial a esse pedago de cédigo scja “Realmente estou feliz por ele nao té-lo deixado assim!”. Se vocé se sentir assim, entao lembre-se de como as outras pessoas ficarao ao ler um cédigo que vocé deixou num formato de “rascunho”. Na verdade, “rascunho” é provavelmente a coisa mais gentil que se possa falar desste cddigo. Estd claro que é um trabalho em progresso. Quantidade de instancias de varidveis ¢ assustadora. Strings estranhas. como “TILT”, “Hashsets” € “Treesete, e os blocos try...catch... catch ficam todas amontoadas. Eu nfo queria criar um cédigo amontoado. De fato, eu estava tentando manter as coisas razoavelmente bem organizadas. Vocé provavelmente pode deduzir isso dos nomes que escolhi para as fungdes ¢ as varidveis, ¢ 0 fato de que mantive uma estrutura bruta. Mas, obviamente, me afastei do problema. A bagunga cresceu gradualmente. As versdes anteriores nao estavam to bagun¢adas assim. Por exemplo, a Listagem 14,9 mostra uma verso antiga na qual s6 funcionavam os parémetros do tipo booleano. Listagem 14-9 Args.java (Booleano apenas) package com.objectmentor .utilities.getopts: import java.util.*; 6 Aras { String schema; String|] args: boolean valid; Set unexpectedargurents Map booleanargs = Map |): int mumberDfArguments = 0; new TreeSet(); (String schema, string{] args) { hema = schema; his.args = args; d= parsei): ce boolean parse!) { af (schema. length/} return true; parseSchema (); parseArgumen Irn unexp 8. length OF edargument. -sizei) ‘seSchena() { : schema.spliti*,")) i for [String elener parseSchenaElomont (element) ; Args: 0 rascunho 201 Listagem 14-9 (continuagio) Args.java (Booleano apenas) return true: } private yoid parseSchenaBlenent (String elexent) { if telement,length(} == 1) { parseBool eanSchemaElement {element} ; , j private void parseBcoleanSchemaElement (String slement) ( char ¢ = element.charAr (0 if {Character.istetter(c)) { ; booleanArgs.put(c, false}; } 2 private boolean parseargurents() { for (String arg : args) parseArgunent (a7) ; return true; y private void parseArgument (string arg) { if (arg.startsWith(*-")) parseflements (arg! ; } private void parseElementsistring arg) { for (int i = 1; 4 < arg.lengthi); i++) parseBlenent (arg.charAt (ii); } private void parseflement (char argchar! { if (isBoolean(argchar)) { numberOfArguments +++ setBooleanarglargChar, true! ; } else unexpectedArguments .add(argChar) ; private void setBooleanArgichar argchar, boolean value) { booleanargs.put (argchar, value) ; } private bociean isteolean(char arachar! ( return booleanArgs.containskey!argChar! + } public int cardinality() [ return nunberOfAraunencs; public String usage|) { if (schem.length|} > 0) return *~["+schema+"] 202 Capitulo 14: Refinamento Sucessivo Listagem 14-9 (continuagio) Args.java (Booleano apenas) else return ) public String errorMessage() if (umexpectedArgunents.sizei) > 0) { return unexpect edArgunentMessage |! ; } else return "*; private String unexpectedAroumentMessaye() { StringBuffer message - new StringBuffer /*Argument is) -*); for (char c : unexpectedArguments) message.appendic) ; message. append(" unexpected. retum message. tostring(); } public boolean getBoolean(char arg) { return booleandrgs.get (arg); Embora vocé possa encontrar muitas formas de criticar esse cédigo, ele nao esta tao ruim assim. Ele esté compacto, simples ¢ facil de entender. Entretanto, dentro dele ¢ facil identificar as “sementes” que baguncario o cédigo. Esti bem claro como ele se tornari uma grande zona Note que a bagunga futura possui apenas mais dois tipos de parimetros do que estes: String ¢ integer, S6 essa adigao tem um impacto negativo enorme no cédigo. Ele o transformou de algo que seria razoavelmente passivel de manuteng&o em algo confuso cheio de bugs. Inseri esses dois tipos de parametro de modo gradual. Primeiro, adicionei o parametro do tipo String, que resultou no seguinte: Listagem 14-10 Args.java (Booleano e String) package con.objectmentor.utilities.geropts; import java.cext.Pa amport Javalucil.; ception; public class args { ate String schema; private String[) args; vate boolean valid = true; vate Set unexpectedAraurents vate Map booleanaras new HasnMap()+ Args: 0 rascunho 203 Listagem 14-10 (continuagao) Args.java (Booleano e String) jat2 Map stringArgs = new HeshMapeCharacter, String>i); private Set argsFound = new HashSet|(}; private int currentargunent private char errorArguitent P enum ErrorCode { OX, MISSING_STRING} private ErrorCode errorCode = BrrorCode.0k; public Args(String schema, String[] args) throws ParseException { this.schena = schema; this.args = arg: valid = parse(|; ) privete boolean parse() throws ParseBxception ( if (schema. lengch(} == 0 && args.length == 9) return true; garseSchema(]; parseArguments return valid; private boolean parseSchema() throws ParseException { for (String element : schema.split(',")) £ if (element length) > 0) { tring trimmedElenent = element .trim(|; parseSchena=lement (trimnedzlenent | ; } return true; 1 private void parseSchenaElement (String element) throws ParseException { char elementid = element .charat(0); String element element substring (1); yalidateSchemaBlement Td (element Td) ; if (isBooleanSchemaPlement (element Tail} ) parseBooleanSchemaElement (element Id) ; else if (isStringSchenaElenent (element Tat. parsescringSchena£lenent (element I4) ; private void validateschemazlementId(char elementid) throws ParseException { if (!Character.isLetter(elementIdi) { throw new Parsesxception/ "Bad character:" + elementid + "in Args format: " + schema, 0}; } private void parseStringSchemaHlenent (char elementId) { stringArgs.put (elementid, **); } 204 Capitulo 14: Refinamento Sucessivo Listagem 14-10 (continuagao) Args.java (Booleano e String) private boolean isStringSchemaélement (String elementTail) ( return elementTail.equals("*"!: private boolean isBooleanSchemaflement (String elenentTail) { return elenentTaii.length() == 0; 5 vate void parseBoolean3chemaElement (char element1d) { booleanargs.put(element?d, false); private boolean parseArguments() { fer icurrentArgunent = 0; currentArgunent < args.length; currentargunenté+) String arg = args |currentArgument] ; parseargunent |arg) } return true; } private void parseArgunent (string arg) { if (arg. startewich(*-")| parseElements larg); } private void parseflements (String arg) { for (int i= 1; i < arg.lengthi); i++) parseBlenent (arg.charAt (i))+ private void parseBlenent (cher argChar! { if (eetArgument (aroChar) ) argsFound.add(arachar) ; else { unexpect ed&rquments .add (araChar} ; valid = false; private boolean secargument ‘char argChar) ( boolean set = true; if (isBoolean{azgChar) } setBocleanArgiargChar, true}; else if (isString(argchar)) secStringArg(argchar, *"); else set = false; return set; } private void setStringArg (ch currentArgunent ++; uy ¢ argChar, String 3) rascunho 205 Listagem 14-10 (continuagio) Args.java (Booleano e String) stringArgs put (argChar, args [currencArgunent }): } catch (ArrayIndexOutofBoundsException ) { valid = false errorargunent errorCode = Er private boolean isStrinaichar argChar) { turn stringArgs.containskey (argChar) ; private vold setfooleanarg(char argChar, boolean value) { booleanArgs.put (argChar, value): } private boolean isRoolean{char argChar! ( turn bocleanArgs..containsKey argChar! ; if (chem: return else return **; public String errorMessage(! throws Exception { if (unexpectedArgutents.size(j > 0) { return unexpectedArqunent Message |) ; } else switch (errorCodel { cage MISSING_STRING: return String. format (*Could not find string paramerer for -&c.", errorArgument) ; case OF: throw new Exception(*rrut; should not get here: } return"; i private String unexpectedArgumentMessage!) { StringBuffer message = new StringBuffer (*Argument(a) -"); for ichar c + unexpectedArguments) { message.append(c) ; nessage-append(" unexpected," ) ; return message. tostring|); ) 206 Capitulo 14: Refinamento Sucessivo Listagem 14-10 (continuagiio) Args.java (Booleano e String) public boolean cetBooleanichar arg) { return falseIrNull‘booleanArgs.get (arg)) + } TéNu11 (Boolean b} false : by private boolean return b == null public String getString(char arg) { return blankIiNull (stringArgs.get (arg) ); } ivate String blankIfNull (String $1 ( return == null? "" : } public boolean has(char arg) { return argsFound.contains(arg) ; } public boolean ii return val. i valid() { } Vocé pode ver que as coisas comegaram a fugir ao controle. Ainda nao esta horrivel, mas a bagunca certamente esta crescendo. EF um amontoado, mas ainda nao esté to grande assim. [sso ocorreu com a adic&o do parametro do tipo integer. Portanto, eu parei Eu tinha pelo menos mais dois tipos de parimetros para adicionar, e eu poderia dizer que eles piorariam as coisas. Se eu forgasse a barra, provavelmente os faria funcionar também, mas deixaria um rastro de bagunca grande demais para consertar. Se a estrutura deste desse codigo algum dia for passivel de manutengo, este € 0 momento para conserti-lo. Portanto, parei de adicionar novos recursos ¢ comecei a refatorar. Como sé adicionei os parimetros do tipo string ¢ integer, eu saiba que cada tipo exigia um bloco de cédigo novo ‘em trés locais principais. Primeiro. cada tipo requer uma forma de analisar a sintaxe de seu elemento de modo a selecionar 0 HashMap para aquele tipo. Depois, seria preciso passar cada tipo nas strings da linha de comando e converté-lo para seu tipo verdadeiro. Por fim, cada tipo precisaria de um método getxxx de modo que pudesse ser retomado ao chamador ja em seu tipo verdadeiro. “Muitos tipos diferentes, todos com métodos similares—isso parece uma classe para mim. E, entdo, surgit! 0 ArgumentMarshaler. Incrementalismo Uma das melhores maneiras de arruinar um programa ¢ fazer modificagdes excessivas em sua estrutura visando uma melhoria. Alguns programas jamais se recuperam de tais “melhorias”. O Args: 0 rascunho 207 problema ¢ que ¢ muito dificil fazer o programa funcionar como antes da “melhoria”. A fim de evitar isso, sigo 0 conceito do Desenvolvimento dirigido a testes (TDD, sigla em inglés). Uma das doutrinas centrais dessa abordagem é sempre manter o sistema operante. Em outras palavras, ao usar o TDD, nio posso fazer alteracdes ao sistema que o danifiquem. Cada uma deve manté-lo funcionando como antes. Para conseguir isso, precisei de uma colecao de testes automatizados que eu pudesse rodar quando desejasse ¢ que verificasse se 0 comportamento do sistema continua inalterado. Para a classe Args, criei uma colegao de testes de unidade e de aceitagao enquanto cu bagungava o cédigo. Os testes de unidade estéio em Java e sto gerenciados pelo JUnit. Os de accitagao so como paginas wiki no FitNesse. Eu poderia rodar esses testes quantas vezes quisesse, e, se passassem, eu ficaria confiante de que o sistema estava funcionando como eu especificara. Sendo assim, continuei ¢ fiz muitas alteragdes mindsculas. Cada uma movia a estrutura do sistema em diregao ao ArgumentMarshaler. E ainda assim, cada mudanga mantinha o sistema funcionando. A primeira que fiz foi adicionar ao esqueleto do ArgumentMarshaller ao final da pilha amontoada de cddigos (Listagem 14.11). Listing 14-11 ArgumentMarshaller appended to Args.java pri ass ArgumentMarshaler { ivate boolean bcoleanValue = false; public void setBoule booleanvalue Olean getRoolean() [return booleanvalue;} private class BocleanArgumentNarshaler extends ArgumentMarshaler { ) private class StringArguentMarshaler extends ArgumentMarshale ) private class InteqerArgumentéarshz } r extends ArgumentMarshaler { Obviamente, isso nfo danificaria nada. Portanto, fiz a modificagao mais simples que pude, uma que danificaria 0 minimo possivel. Troquei o HashMap dos parimetros do tipo booleano para receber um ArgumentMarshaler. private Map booleanArgs new HashMap(); Isso danificou algumas instrugées, que rapidamente consertei- private void parseBooleanSchemaZlement(char elementid) { booleanArgs.put(elementId, new BooleanArgumentMarshaler()); } 208 Capitulo 14: Refinamento Sucessivo private void setBooleanArg(char argChar, boolean value) ( booleanaras.get (argChar).setBoolean(value); public boolean getBoolean(char arg) { return £alseIfNull(booleanargs.get(arg].getBoolean(}); } Note como essas mudangas estiio exatamente nas éreas mencionadas anteriormente: o parse, 0 set ¢ 0 get para o tipo do parimetro. Infelizmente, por menor que tenha sido essa alterago, alguns testes comegaram a falhar. Se olhar com atengdo para 0 getBoolean, vera que se voce ‘0 chamar com “y”, mas no hd houver parametro y, booleanArgs.get (“y‘) retornara mull ea fungdo langara uma Nul1PointerException. Usou-se a fun¢ao falseT fNul1 para evitar que isso ocorresse, mas, com a mudanga que fiz, ela se tornou irrelevante. Oincrementalismo exige que eu conserte isso rapidamente antes de fazer qualquer outra alteracao. De fato, a selucdo nao foi muito dificil. $6 tive de mover a verificagdo por mull. Nao era mais ‘um booleano nu11 que eu deveria verificar, mas 0 ArgumentMarshaller. Primeiro, removi a chamada a falseIfNul1 na fungao get Boolean. Ela nao fazia mais nada agora, portanto a climinei. Os testes ainda falhavam de certa forma, ento eu estava certo de que nao havia gerado novos crros. public boolean getBoolean(char arg) { return booleanArgs.get (arg).getBooleani); } Fm seguida, divide dividi a func&o em duas linhas e coloquei © ArgumentMarshaller em sua prépria varidvel chamade argumentMarshaller. Néo me preocupara com o tamanho do nome; ele era redundante e baguncava a funcdo. Portanto o reduzi para am [N5]. public boolean getBoolean(char arg) { Args.ArgumentMarshaler am = booleanArgs.get(arg); return am.getBoolean(); d E, ent&o, inseri a légica de detecgao de null. public boolean getBoolean(char arg) { Args.ArgumentMarshaler am = booleanArgs.get(arq); return am != null && am.getBoolean(); Pardmetros do tipo string Adicionar parametros do tipo String é¢ muito semelhante a adi¢do de parimetros booleanos. Eu tive de mudar 0 HashMap ¢ fazer as fung6es parse, get ¢ set funcionarem. Nao ha muitas surpresas depois, talvez parega que cu esteja colocando toda a implementagao de disponibilizagao (marshalling) na classe base Argument Marshaller em vez de seus derivados. private Map stringArgs = new HashMap(); ParAmetros do tipo string 209 private void parseStringSchemaBlement(char elementId) ( stringArgs.put(elementId, new StringArgumentMarshaler()); > private void setStringArg(char argChar) throws ArgsException { currentArgument++; try { stringArgs.get (argChar) .setString(args[currentargum ent); } catch (ArrayIndexoutofBoundsException e) ( valid = false; errorArgumentId = argChar; errorCode = ErrorCode.MISSING_STRING; throw new ArgsExcepticn(); } public String getString(char arg) { Args.ArgumentMarshaler am = stringArgs.getiarg); return am == null ? " * : am.getString(); } private class ArgumentMarshaler { private boolean booleanValue = false; private String stringvalue; public void setBcolean(boolean value) { booleanValue = value; public boolean getBoolean() { return booleanvalue; } public void setString(String s) { stringvalue = s; + public String getString() { return stringValue == null 7" " : stringvalue; } } Novamente, essas alteragdes foram feitas uma de cada vez ¢ de tal forma que os testes estavam. sempre funcionando. Quando um falhava, eu me certificava de fazé-lo passar com éxito antes de fazer a proxima mudanga. Mas, a esta altura, vocé ja deve saber o que pretendo. Apés eu colocar todo 0 comportamento doa disponibilizagao dentro da classe base ArgumentMarshaler, comegarei a passd-lo hierarquia abaixo para os derivados, Isso me permitiré manter tudo operante enquanto eu modifico gradualmente a forma deste programa. 210 Capitulo 14: Refinamento Sucessivo © proximo, ¢ dbvio, passo foi mover a funcionalidade do parametro do tipo int para ArgumentMarshaler. Novamente, nao hé nada de novo aqui. private Map intArgs = new HashMap private void parseIntegerSchemaElement(char elementId) ( intArgs.put(elementId, new IntegerArgumentMarshaler()); private void setIntArg(char argChar) throws ArgsException { currentArgument++; String parameter = null; try { parameter = args[currentArgument]; intArgs.get (argChar) .setInteger (Integer. parseInt(parameter)); } catch (ArrayIndexOutOfBoundsException e) ( valid false; errorArgumentTd = argChar; errorCode = ErrorCode.MISSING_INTEGER; throw new ArgsException(): } catch (NumberFormatException e) ( valid = false; errorArgumentId arachar; errorParameter = parameter; errorCode = ErrorCode.INVALID_INTEGER; throw new ArgsException(); } public int getint(char arg) ( aArgs.ArgumentMarshaler am = intArgs.get(arg); return am == null ? 0 : am.getInteger(); J private class ArgumentMarshaler { private boolean booleanvalue = false; private String stringValue; private int integerValue; public void setBoolean(boolean value) { booleanvalue = value; } public boolean getBoolean() ( return beoleanvalue; t public void setString(String s) ( stringvalue = s; + Pardmetros do tipo string public String getString() { return stringValue null ? “ : stringValue; public void setInteger(int i) { integervalue = i; , public int getInteger() { return integerValue; } } 2i1 Apés mover toda a disponibilizago para ArgumentMarshaler, comecei a passar a funcionalidade para os derivados. O primeiro passo foi mover a fung&o setBoolean para BooleanArgumentMarshaller e me certificar de que fosse chamada corretamente. Portanto, criei um método set abstrato. private abstract class ArgumentMarshaler [ protected boolean booleanValue = false; private String stringValue; private int integerValue; public void setBoolean(boolean value) ( booleanvalue = valu public boolean getBoolean() { return booleanValue; public void setString(string s) ( stringvalue = s; public String getString() ( return stringvalue null ? ** : stringvalue; + public void setInteger(int i) { integerValue = } public int getInteger() { return integerValue; } public abstract void set(String s); } Entao, implementei o método set em BooleanArgumentMarshaller. 212 Capitulo 14: Refinamento Sucessive private class BooleanArgumentMarshaler extends ArgumentMarshaler ( public void set(string a) ( booleanValue = true; } } E, finalmente, substitui a chamada a setBoolean pela chamada ao set. private void setBooleanarg(char argChar, boolean value) { booleanaArgs.get(argChar).set ("true"); } Os testes ainda passavam. Como essa mudanga causou a implementagio do set em BooleanArgumentMarshaler, removi 0 miétodo setBoolean da classe base ArgumentMarshaler. Note que a funcao set abstrata recebc um parametro do tipo String, maS a implementagdo em BooleanArgumentMarshaller nfo o usa. Coloquei um parametro 14 porque eu sabia que StringArgumentMarshaller ¢ IntegerArgumentMarsha] ler 0 usariam. Depois, eu queria implementar 0 método get em BooleanArgumentMarshaler. Mas implementar fungdes get sempre fica ruim, pois o tipo retornado tem de ser um Object, ¢ neste caso teria de ser declarado como um booleano. public boolean getHoolean(char arg) { Args.ArgumentMarshaler am = booleanArgs.get (arg); return am != null && (Boolean)am .get(); 3 Sé para que isso compile, adicionei a fung&o get a ArgumentMarshaler. private abstract class ArgumentMarshaler { public Object get() { return null; } d A compilag3o ocorreu ¢, obviamente, os testes falharam. Fazé-los funcionarem novamente era simplesmente uma questo de tornar o get abstrato ¢ implementi-lo em BooleanAgumentMarshaler. private abstract class ArgumentMarshaler { protected boolean booleanValue = false; public abstract Object get(); } private class BooleanArgumentMarshaler extends ArgumentMarshaler { public void set (string s) ( booleanValue = true; + Parametros do tipo string 213 public Object geti) { return booleanvalue; + 3 Mais uma vez, os testes passaram. Portanto, as implementagdes de get e set ficam em BooleanArgumentMarshaler! Isso me permitiu remover a fungdo get Boolean antiga de ArgumentMarshaler, mover a variavel protegida booleanValue abaixo para BooleanArgumentMarshaler e torna-la privada. Fiz as mesmas alteragdes para os tipos String. Implementei set ¢ get ¢ exclui as fungdes nao mais utilizadas ¢ movi as variavcis. private void setStringArg(char argChar) throws ArgsException ( currentArgument+ try { stringArgs.get (argChar) .set (args{currentargument, } catch (ArrayIndexOutOfBoundsException e) { valid = false; errorArgumentid = argChar; errorCode = ErrorCode.MISSING_STRING; throw new ArgsException(); } public String getString(char arg) { Args.ArgumentMarshaler am = stringArgs.getiarg); return am == null ? “" : (String) am.get(); d private abstract class ArgumentMarshaler { private int integerValue; public void setInteger(int i) { integervalue = i + public int getInteger() { return integerValue; , public abstract void set(string s); public abstract Object get(); y private class BooleanArgumentMarshaler extends ArgumentMarshaler { private boolean booleanValue = false; public void set(string s) { booleanValue = true; } public Object get() { xeturn booleanValue; } 214 Capitulo 14: Refinamento Sucessivo } private class StringArgumentMarshaler extends ArgumentMarshaler { private String stringvalue = "”; public void set(String 3) { stringValue = s; } public Object geti) { return stringValue; } } private class IntegerArgumentMarshaler extends ArgumentMarshaler { public void set(string s) { } public Object geti) { return null; } Por fim, repeti 0 processo para os inteiros (integer). $6 um pouco mais complicado, pois 08 inteiros precisam ser analisados sintaticamente, e esse processo pode langar uma excesao. Mas 0 resultado fica melhor, pois toda a ag¢3o de NumberFormatException fica escondido em IntegerArgumentMarshaler. private boolean isIntArg(char argChar) {return intArgs containsKey(argChar);} private void setIntarg(char argChar) throws ArgsException { currentArgument++; String parameter = null; try { parameter = argsicurrentArgument]; intargs.get (argChar).set (parameter); } catch (ArrayIndexoutofBoundsException e) { valid = false; errorArgumentId = argChar; errorCode = ErrorCode.MISSING_INTEGER; throw new ArgsException(); } catch (ArgsException e) ( valid = false; errorArgumentid = argChar; errorParameter = parameter; errorCode = ErrorCode.INVALID_INTEGER; throw e; } private void setBooleanArg(char argChar) { Pardmetros do tipo string 25 3 try ( booleanArgs.get(argChar) } catch (ArgsException e) { } 2 ("true"); } public int getInt(char arg) { Args.ArgumentMarshaler am = intargs.get (arg); return am == null ? 0 : (Integer) am .get(); ) private abstract class ArgumentMarshaler { public abstract void set(String s} throws ArgsException: public abstract Object get{); } private class IntegerArgumentMarshaler extends ArgumentMarshaler { private int intvalue = 0; public void set(String s) throws ArgsException { try € intValue = Integer.parseInt(: } catch (NumberFormatException e) { throw new ArgsException(); } public Object gett) { return intvalue; } ¥ claro que os testes continuavam a passar. Depois, me livre dos trés maps diferentes no inicio do algoritmo, Isso generalizou muito mais o sistema todo. Entretanto, ndo consegui me livrar dele apenas excluindo-os, pois isso danificaria o sistema. Em vez disso, adicionei um novo Map a ArgumentMarshaler e, ento, um a um, mudei os métodos € 0 usei este nove Map no lugar dos trés originais. public class Args { private Map booleanArgs new HashMap(); private Map stringArgs = new HashMap(); private Map intargs new HashMap(); private Map marshalers = new HashMap(); private void parseBooleanSchemazlement(char elementId) { ArgumentMarshaler m = new BooleanArgumentMarshaler(); booleanArgs.put(elementId, m); marshalers.put(elementId, m); 216 Capitulo 14: Refinamento Sucessivo private void parseIntegerSchemaElement(char elementid) ( ArgumentMarshaler m = new IntegerArgumentMarshaler(); intargs.put(elementId, m); marshalers.put(elementId, m); } private void parseStringSchemaHlement(char elementid) [ ArgumentMarshaler m = new StringArgumentMarshaler(); stringArgs.put(elementId, m) marshalers.put(elementI4, m) , E claro que todos os testes ainda passaram. Em seguida, modifiquei o isBooleanArg disso: private boolean isBooleanargichar argChar) { return booleanArgs.containsKey(argChar); y para isso: private boolean isBooleanArg(char argChar) { ArgumentMarshaler m = marshalers.get(argChar); return m instanceof BooleanArgumentMarshaler; } Os testes ainda passaram. Portanto, fiz a mesma mudanga em isTntArg ¢ isStringArg private boolean isIntArg(char argChar) { ArgumentMarshaler m = marshalers.get(argChar), return m instanceof IntegerArgumentMarshaler; y private boolean isStringArgichar argChar) { ArgumentMarshaler m = marshalers.get(argChar); return m instanceof StringArgumentMarshaler; y Os testes ainda passaram. Portanto, eliminei todas as chamadas repetidas a marshalers .get : private boolean setArgument(char argChar) throws argsException { ArgumentMarshaler m = marshalers.get(argChar); if (isBooleanArg(m)) setBooleanArg(argChar); else if (isStringarg(m)) setStringArg(argChar); else if (isIntarg(m)) setIntArg(argChar}: else return false; return true; Pardmetros do tipo string 217 private boolean isintArg(ArgumentMarshaler m) ( return m instanceof IntegerArgumentMarshaler; private boolean isStringArg(ArgumentMarshaler m) ( return m instanceof StringArgumentMarshaler; } private boolean isBooleanArg(ArgumentMarshaler m) { return m instanceof BooleanArgumentMarshaler; Isso nao fez nada de mais para os trés métodos isxxxArg. Portanto, cu os encurtei: private boolean setArgument(char argChar) throws ArgsException { ArgumentMarshaler m = marshalers.get(argChar); if (m instanceof BooleanArgumentMarshaler) setBooleanArg(argChar); else if (m instanceof StringArgumentMarshaler) setStringarg(argChar); else if (m instanceof IntegerArgumentMarshaler) setIntArg(argChar); else return false; return true; 3 Depois, comecei a usar os map do marshalers nas fungdes set, danificando o uso dos outros trés maps. Comecei com os booleanos. private boolean setArgument(char argChar) throws ArgsException ArgumentMarshaler m = marshalers.get(argChar); if (m instanceof BooleanArgumentMarshaler) setBooleanArg(m); else if (m instanceof StringArgumentMarshaler] setStringArg(argChar); else if (m instanceof IntegerArgumentMarshaler) setIntArg(argChar); else return false; return true; } private void setRooleanArgiArgumentMarshaler m) ( try ¢ m.set(*true”); // was: booleanArgs.get(argchar). set(*true"); } catch (ArgsException e) { > 218 Capitulo 14: Refinamento Sucessivo Os testes continuam passando, portanto, fiz 0 mesmo para strings ¢ inteiros. Isso me permitiu integrar parte do cédigo de gerenciamento de excegao 4 fiungo setArgument. private boolean setArgument(char argChar) throws ArgsException { ArgumentMarshaler m = marshalers.get (argChar); try { if (m instanceof BooleanargumentMarshaler) setBooleanArg(m); else if (m instanceof stringArgumentMarshaler) setStringArg(m); else if (m instanceof IntegerArgumentMarshaler) setIntarg(m) else return false; } catch (ArgsException e) { valid = false; errorArgumentId = argCha: throw e; } return true; } private void setIntArg(ArgumentMarshaler m) throws ArgsException { currentargument++ String parameter = null; try { parameter = args[currentargument]; m.set (parameter); } catch (ArrayIndexOutOfRoundsException e) { errorCode = ErrorCode.MISSING_INTEGER; throw new ArgsException(); } catch (ArgsException e) { errorParameter = parameter; errorCode = ErrorCode.INVALID_INTEGER; throw e; y private void setStringArg(ArgumentMarshaler m) throws ArgsException { currentArgument++; try { m.set(args[currentArgument]}; ) catch (ArrayIndexOutOfBoundsException e) { errorCode = ErrorCode.MISSING_STRING; throw new ArgsException(); + Quase consegui remover os trés maps antigos. Primeiro, precisei alterar a fingio gecBoolean disso: public boolean getBoolean(char arg) { Parametros do tipo string 219 Args.ArgumentMarshaler am = booleanArgs.get(arg); return am != null && (Boolean) am.get(); 1 para isso: public boolean getBoolean(char arg) [{ Args.ArgumentMarshaler am = marshalers.get(arg); boolean b = false; try { b = am I= mill && (Boolean) am.get(); } catch (ClassCastException e) { b d return b; : Vocé deve ter se surpreendido com essa ultima alterag3o. Por que decidi usar de repente a ClassCastException? O motivo ¢ que tenho uma série de testes de unidade ¢ uma série separada de testes de accitagao escritos no FitNesse. Acabou que os testes do FitNesse garantiram que, se vocé chamasse a getBoolean em um parametro nao booleano, vocé recebia falso, Mas 0s testes de unidade nao. Até este momento, eu s6 havia rodado os testes de unidade*, Essa tiltima alteragaio me permitiu remover outro uso do map booleano: private void parseBooleanSchemaElement(char elementId) { ArgumentMarshaler m = new BooleanargumentMarshaler(); bookeanargs-putielement id, m); marshalers.put(elementId, m); } E agora podemos excluir 0 map booleano. public class Args { private -Map booleanArgs= new -HashMeap(); private Map stringArgs = new HashMap(); private Map intArgs = new HashMap(); private Map marshalers = new HashMap(); Em seguida, migrei os parametros do tipo String e Integer da mesma maneira ¢ fiz uma pequena limpeza nos booleanos. private void parseBooleanSchemaElement(char elementid) ( marshalers.put(elementId, new BooleanArgumentMarshaler()); y private void parseIntegerSchemaElement (char elementId) [{ veceatsnss’dunaiien un didi anil aiibivn deuooe es: mse id aniihi tum Teen tilewencidins i Waa: 220 Capitulo 14: Refinamento Sucessivo marshalers.put(elementId, new IntegerArgumentMarshaler()); } private void parseStringSchemaBlement(char elementId) ( marshalers.put(elementId, new StringArgumentMarshaler()); public String getString(char arg) ( Args.ArgumentMarshaler am = marshalers.get(arg); try { return am == null ? “” : (String) am.get(); } catch (ClassCastException e) { return ™”, ? public int getint(char arg) { Args.ArgumentMarshaler am try { marshalers.get (arg); return am == null ? 0 : (Integer) am.get(); } catch (Exception e) { return 0; y } public class Args { private Map—stringargs—= new -HashMapt}; private Map—intargs—= new -HashMap{}+ private Map marshalers = new HashMap(); Em seguida, encurtei os métodos parse porque eles nao fazem mais muita coisa. private void parseSchemaElement(string element) throws ParseException { char elementid = element.charAt(0); string elementTail = element.substringi1); validateSchemaElementIdielement Id); if (isBooleanSchemaElement(elementTail)) marshalers.put(elementiId, new BooleanArgumentMarshaler()); else if (isStringSchemaFlement(elementTail)} marshalers.put(elementId, new StringArgumentMarshaler()); ise if (isIntegerSchemaElement(elementTail)) { marshalers.put(elementId, new TntegerArgumentMarshaler()); } else { throw new ParseBxception(String.format( “Parémetro: %¢ possui formato invAlido: %s.", elementId elementTail), 0); } d Tudo bem, agora vejamos o todo novamente. A Listagem 14.12 mostra a forma atual da classe Args Parfimetros do tipo string 221 Listagem 14-12 Args.java (Apés primeira refatoragao) package com.objectmentor.utilities.cetopts; import java.text .Parsefxception; import java.util.*; public class args { private String schema: private Stringl! args; private boolean valid = true, private Set unexpectedArguments = new TreeSet{} private Map marshalers = new HashMap(/; private Set argeFound - new HashSet(!; private int currentArgument; private char errorArgumentId = '\0'; private String errorParameter TILT"; private ErrorCode errorCode = ErrorCode.Ck; peivace enum ErrorCode { OK, MISSING STRING, MISSING_INTEGER, TNVALID_INTEGER, UNEXPECTED_ARGUMENT) public Args (String schema, strin this.schema = schema; vhis.args = args; valid = parsel); [] args) throws ParseException ( private boolean parse) throws ParseException { if (schema. length() == 0 && args. length == 0) veturn tru parseSchema|) ; try { parsearguments (+ } catch ‘ArgsException e) { } recurn valid; te boolean parseschema() throws Parsezxcegtion { ey pri for (String element : schema.split( if [element.lenath|) > 9) { String trimmedElexent = elenent.crim(); parseSchemaElemenc (trimmedElement) ; ) 2 return 1 private void parseSchemaElement |String element) throws ParseExcept char elementid = element.charAt |0|; String elementTail = element.substring(1); validateSchemaElenent 1d(element 1d) ; if (isBooleanSchenaElement (elementTail)} marshalers.put (elementId, new BooleanArgumentéarshaler()): else if (isStringSchemaElerent (element Tail)) marshalers.put (elenent1d, new ScringArgumentMarshaler(}}; 222 Capitulo 14: Refinamento Sucessivo Listagem 14-12 (continuagao) Args.java (Apés primeira refatoracdo) else if [isIntegerSchenaFlement (element Ta21}) ( marshalers.put (elenentId, new IntegerArgumentMarshaler|)}+ } else { throw new Parsesxception (string, format ( "argument: tc has invalid format: %8.", elementIé, elementTail), 0}; } private void validateSchema=lenentId(char elementid) throws ParseException { if ({Character.isLetter(elementId)} [ throw new ParseException( "gad character:" + elementId + "in Args format: * + schema, 0); } } private beolean isStringSchenaflement (String element?ail) { return elementTail.equala("**) ; } private boolean isBooleanSchemaflement (String element’: return elementTail. length!) vate boolean isIntegerSchemaFlement (String element Tail) { return elenentTail.equals("#") BI private boolean parseArguments() throws ArgsException ( for {currentArgument=0; currentArgumentcargs length: currentArgument++) { String ary = args[currentargument] ; parseArgunent (arg) : 3 return true; private void parseargurent (String arg) throws ArgsException { Af (arg.startewith(*-*)) parseslements (arg) ; ) private void parseBlements(String arg) throws ArgsException ( for (int i= 1; i < arg.length(); i++) parseBlement !arg.charAt (i}); y vate vord parseElement {char argChar) throws ArgsException { if [setArgument (argChar) } argsFound.add(argChar) ; else { unexpectedargument: errorCode = Errord valid = fals pr 8.add (argchar) ; ‘ode. UNEXPECTED_ARGUMENT; Pardmetros do tipo string 223, Listagem 14-12 (continuagao) Args.java (Ap6s primeira refatoragao) private boolean setargument (char argChar) throws ArgsException { ArgumentMarshaler m = marshalers.get (argChar) ; vy ( if im instanceof BooleanArgumentMarshaler) setBooleanArg(m) ; else if im instanceof Stringargunentuarshaler! setStringArg (x) : else if (m instanceof IntegerArcumentMarshaler} setIntargim|; else return false; } catch (ArgsException e) { valid = false; errorArgunencTd = argChar; throw e: return true; private void setIntarg|ArgumentNarshaler m) throws ArgsException { currentArgument ++; String parameter = null; uy { Parameter = args|currentargunent]; m.set (parameter) ; } catch (ArrayIndexOutOfBoundsException e) ( errorcode = ErrorCode,MISSING_INTEGER; throw new ArgsEXception() : ArgsException e) ( errorParemeter = parameter; errorCode = Errorfode. INVALID_INTEGER; throx e: private void setStringArg(ArgumentMarshaler m) throws ArgsException ( currentArgument ++; cy i m.set (args[currentArgument]) 1 catch (arrayIndexdutofBoundsaxcentior errorCode = ErrorCode.MISSING_2" throw new ArgsException() ; ) } private void setBooleanirg{ArgunentMarshaler mi { try { mset ("true"); } catch (ArgsException ¢} ( + public int cardinality!) { return argsFound.sizel); } 224 Capitulo 14: Refinamento Sucessivo Listagem 14-12 (continuagio) Args.java (Apés primeira refatoracéo) public String usage() ( if (schema.lenath(| > 9) return *-[* + schema + "1"; else return * } public string errorMessage() throws Exception { itch (errorvode) { case OF: thr-w new Exception(*TILT: Should not get here."); case UN unexpectedArguments = new TreeSet(); private Map marshalers = new HashMap(); private Set argsFound = new HashSet(); private Iterator currentArgument; private char errorArgumentId = ‘\0’; private String errorParameter = “TILT"; private ErrorCode errorCode = ErrorCode.OK: private List argsList; private enum Errorcode { OK, MISSING_STRING, MISSING_INTEGER, INVALID_INTEGER. UNEXPECTED_ARGUMENT} public Args(String schema, String[] args) throws ParseException this.schema = schema; argsList = Arrays.asList (args); ParAmetros do tipo string vy g valid = parse(); y private boolean parse() throws ParseException { if (schema.length() 0 && argsList.size() return true; parseSchema(); try { 0) parseArguments(); } catch (ArgsException e) { d return valid; } private boolean parseArguments() throws ArgsException { for (currentArgument = argsList.iterator(); currentArgument-hasNext();) { String arg = currentArgument.next(); parseAraument (arg); y return true; Bs private void setIntArg(ArgumentMarshaler m) throws ArgsException String parameter = null; try { parameter = currentArgument.next(); m.set (parameter); } catch (NoSuchElementException ©) { errorCode = ErrorCode.MISSING_INTEGER; throw new ArgsException(); } catch (ArgsException e) { errorParameter = parameter; errorCode = ErrorCode.INVALID_INTEGER; throw e; private void setStringArg(ArgumentMarshaler m) throws ArgsException { try { m.set (currentArgument -next()); } catch (NoSuchElementException c) { errorCode = ErrorCode.MISSING_STRING; throw new ArgsException(); } Essas foram simples modificagdes nas quais todos os testes passaram. Agora podemos comegar a mover as fungdes set para os derivados apropriados. Primeiro, preciso fazer a seguinte alteragdo em setArgument: 228 Capitulo 14: Refinamento Sucessivo private boolean setArgument(char argChar) throws ArgsException ( ArgumentMarshaler m = marshalers.get(argChar}; if (m == null) return false; try if (m instanceof BooleanArgumentMarshaler) setBooleanaArg(m; else if (m instanceof StringArgumentMarshaler) setStringAra(m); else if (m instanceof IntegerArgumentMarshaler) setIntArgim); else return false; } catch (ArgsException e) { valid = false: errorArgumentId = argChar; throw e; ) return true; } Essa mudanga ¢ importante porque queremos eliminar completamente a cadeia if-else. Logo, precisamos retirar a condigao de erro la de dentro. Agora podemos comecar a mover as fungdes set. A setBooleanArg é trivial, entdo trabalharemos nela primeiro. Nosso objetivo é alteri-la simplesmente para repassar para BooleanArgumentMarshaler. private boolean setArgument(char argChar) throws ArgsException ( ArgumentMarshaler m = marshalers.get(argChar); if (m == null) return false; try € if (m instanceof BooleanArgumentMarshaler) setBooleanArg(m, currentArgument); else if (m instanceof StringArgumentMarshaler) setStringArg(m); else if (m instanceof IntegerArgumentMarshaler) setIntArg(m); } catch (ArgsException e) { valid = false; errorArgumentId = argChar; throw e; } return true; 3 private void setBooleanArg(ArgumentMarshaler m, Iterator currentArgument) throws ArgsException { try-t m.set("true’); catch—targsException-e}—t + Pardmetros do tipo string 229 Nao acabamos de incluir aquele tratamento de excegao? E muito comum na refatoragao inserir ¢ remover as coisas novamente. A pequeneza dos passos e a necessidade de manter os testes funcionando significa que vocé move bastante as coisas. Refatorar ¢ como resolver 0 cubo de Rubik, Ha muitos passos pequenos necessérios para alcangar um objetivo maior. Cada passo possibilita o proximo. Por que passamos aquele iterator quando setBooleanArg certamente nao precisa dele? Porque setIntarg € setstringArg precisario! E porque queremos implementar todas essas trés fungdes através de um método abstrato em ArgumentMarshaller ¢ passd-lo para setBocleanarg. Dessa forma, setBooleanArg agora nao serve para nada. Se houvesse uma fungao set em ArgumentMarshaler, poderiamos chamé-la diretamente. Portanto, é hora de crid-la! O primeiro passo é adicionar 0 novo método abstrato a Argument Marsha ler. private abstract class ArgumentMarshaler { public abstract void set(Iterator currentArgument) throws ArgsException; public abstract void set(String s) throws ArgsException; public abstract Object get(); } E claro que isso separa todos os derivados. Portanto, vemos implementar 0 novo método em cada um. private class BooleanArgumentMarshaler extends ArgumentMarshaler ( private boolean booleanValue = false; public void set(Iterator currentArgument) throws ArgsException { booleanvalue = true; } public void set(string s) { bocieanvaiue—=truey } public Object geti) [ return bocleanvalue; i } private class StringArqumentMarshaler extends ArgumentMarshaler ( private String stringvalue = **; public void set(Tterator currentArgument) throws ArgsException { & public void set(string s) { stringValue = s: } public Object get() { return stringValue; } 230 Capitulo 14: Refinamento Sucessivo 3 private class IntegerArgumentMarshaler extends ArgumentMarshaler { private int intValue = 0; public void set(Tterator currentArgument) throws ArgsException { + public void set(String s) throws ArgsException { try [ intValue = Integer.parseInt(s); } catch (NumberFormatException e) { throw new ArgsE&xception(); } } public Object geti) { return intValue; } } E, agora, podemos climinar setRooleanArg! private boolean setArgument(char argChar) throws ArgsException { ArgumentMarshaler m = marshalers.get(argChar); if (m == null) return false; try ( if (m instanceof BooleanargumentMarshaler) m.set (currentArgument); else if (m instanceof StringArgumentMarshaler) setStringArg(m); else if m instanceof IntegerArgumentMarshaler) setIntArg(m); } catch (ArgsException e) { valid = false; errorArgumentId = argChar; throw e; } return true; Todos os testes passam e a fungao set ¢ implementada para BooleanArgumentMarshaler! Agora podemos fazer 0 mesmo para Strings ¢ Integers. private boolean setArgument(char argChar) throws ArgsException { ArgumentMarshaler m = marshalers.get(argChar); if (m == null) veturn false; try { if (m instanceof BooleanArgumentMarshaler) Pardmetros do tipo string 231 m.set(currentArgument); else if (m instanceof stringArgumentMarshaler) m.set(currentArgument); else if (m instanceof IntegerArgumentMarshaler) m.set(currentArgument); } catch (ArgsException e) [ valid = false; errorArgumentid = argChar; throw e; } return true; } private class StringArgumentMarshaler extends ArgumentMarshaler { private String stringValue may public void set(Iterator currentArgument) throws ArgsException ( try ¢ stringvalue = currentargument.next(); } catch (NoSuchElementException e) { errorCode = ErrorCode.MISSING_ STRING; throw new Args&xception(); ) public void set(String s) { - public Object get() ( return stringvalue; } private class IntegerArgumentMarshaler extends ArgumentMarshaler { private int intvalue = 0; public void set(Iterator currentArgument) throws ArgsException ( String parameter = null; try { parameter = currentArgument.next(); set (parameter); } catch (NoSuchElementException e) { errorCode = ErrorCode.MISSING_INTEGER; throw new ArgsException(); } catch (argsException e) { errorParameter = parameter; errorcode = ErrorCode.INVALID_INTEGER; throw e7 , public void set(String s) throws ArgsException { y 8 Capitulo 14; Refinamento Sucessivo try ¢ intValue = Integer.parseIntis); } catch (NumberFormatException e) { throw new ArgsException(}; , r public Object geti) { return intvalue; } + E, agora o coup de grace: pode-se remover o caso do tipo! Touché! private boolean setArgument(char argChar) throws ArgsException { ArgumentMarshaler m = marshalers.get(argChar); if (m == nullj return false; try { m.set (currentArgument); return true; ) catch (ArgsException e) { alid = false; errorArgumentId = argChar; throw e; } Agora podemos nos livrar de algumas fungdes iniiteis em IntegerArgumentMarshaler ¢ limp4-la um pouco. private class IntegerArgumentMarshaler extends ArgumentMarshaler { private int intvalue = 0 public void set(Iterator currentargument) throws ArgsException { String parameter = null; try parameter = currentArgument.next(); intValue = Integer.parseInt (parameter); } atch (NoSuchElementException e] { errorCode = ErrorCode.MISSING_INTEGER; throw new ArgsException(); } catch (NumberFormatException ©) { errorParameter - parameter; errorCode = ErrorCode, INVALID_INTEGER; throw new ArgsException(); } public Object gett) { return intvalue; } Pardmetros do tipo string 233 Também podemos transformar ArgumentMarshaler em uma interface. private interface ArgumentMarshaler ( void set(Iterator currentArgument) throws ArgsException; Object get(); Portanto, agora vejamos como fica facil adicionar um novo tipo de paraémetro a 4 nossa estrutura. Sac necess4rias poucas mudancas, e elas devem ser isoladas. Primeiro, adicionamos um novo teste de caso para verificar se 0 parémetro double funciona corretamente. public void testsimpleDoublePresent() throws Exception { Args args = new Args(*x##", new String[] {*-«","42.3"}); assertTrue(args.isValid()}; assertEquals(1, args.cardinality()); assertTrue(args.has(‘x')); assertEquals(42.3, args.getDouble(‘x’), .001); y Agora, limpamos 0 cédigo de andlise da sintaxe ¢ adicionamos o ## para detectar o pardmetro do tipo double. private void parseSchematlement (String element) throws ParseException { char elementId = element.charAt(0); String elementTail = element.substring(1); validateSchemaElementId(elementid); if (elementTail.length() == 0) marshalers.put(elementid, new BooleanArgumentMarshaler()); else if (elementTail.equals("*"}) marshalers.put(elementId, new StringArgumentMarshaler(}); else if (elementTail.equais("#")) marshalers.putielementid, new IntegerArgumentMarshaler()}; else if (elementTail.equals("##”)) marshalers.put(elementId, new DoubleArgumentMarshaler()); else throw new ParseException(String.format( “Parametro: %c possui formato invdlido: $s.*, elementId, elementTail), 0); ) Em seguida, criamos a classe DoubleargumentMarshaler. private class DoubleArgumentMarshaler implements ArgumentMarshaler { private double doublevalue = 0; public void set(Iterator currentArgument) throws ArgaException { string parameter = null; try € parameter = currentArgument.next(); doublevalue = Double.parseDouble(parameter); } catch (NoSuchElementException e) { errorCode = ErrorCode.MISSING_DOUBLE; throw new ArgsException(); } catch (NumberFormatException e) { errorParameter = parameter; 234 Capitulo 14: Refinamento Sucessivo exrorCode = ErrorCode.INVALID DOUBLE; throw new ArgsException(); } public Object get() { return doublevalue; } a, 1ss0 nos forga a adicionar um novo ErrorCode. private enum ErrorCode { OK, MISSING_STRING, MISSING_INTEGER, INVALID INTEGER, UNEXPECTED_ ARGUMENT, MISSING DOUBLE, INVALID DOUBLE} E precisamos de uma fungio getDouble. public double getDouble(char arg) { Args.ArgumentMarshaler am = marshalers.get (arg); try ( return am == null ? 0 : (Double) am.get(); } catch (Exception e) ¢ return 0.0; > } E todos 0s testes passaram! Nao houve problemas. Portanto, agora vamos nos certificar de que todo. © processamento de erros funcione corretamente. No préximo caso de teste, um erro é declarado se uma string cujo valor nao corresponda ao tipo esperado é passada em um pardmetro ##. public void testInvalidDouble() throws Exception { Args args = new Args("x##’, new String[] (*-x*,"Forty two*)); assertFalse(args.isValid()); assertEquals(0, args.cardinality()); assertFalse(args.has(‘x’)); assertEquals(0, args.getInt(*x’)); assertHquals("Parametro -x espera um double mas recebeu ‘Quarenta e Dois’.*, args.errorMessage()); } public String errorMessage() throws Exception { switch (errorCode) { case OK: throw new Exception(*TILT: Nao deve entrar aqui."); case UNEXPECTED_ARGUMENT: return unexpectedArgumentMessage(); case MISSING_STRING: return String.format("N&o foi possivel encontrar um parametro string para -8c.", exrorArgument 1d); Pardmetros do tipo string 235 case INVALID_INTEGER: return String.format("Parametro -%c espera um integer mas recebeu ‘8s’.", errorArgumentId, errorParameter); case MISSING_INTEGER: return String.format("N&o foi possivel encontrar um paraémetro integer para -%c.", errorArgumentid); case INVALID DOUBLE: return String.format("Parametro -%c espera um double mas recebeu ‘%s’.”, errorArgumentId, errorParameter); case MISSING_DOUBLE: return String.format("No foi possivel encontrar um parametro double para -%c.", errorArgumentId); ) return “*; } O teste passa com éxito. O proximo garante que detectemos devidamente um parametro double que est faltando. public void testMissingDouble() throws Exception { Args args = new Args("x##", new String[]{*-x")); assertFalse(args.isValid()); assertEquals(0, args.cardinality()); assertFalse(args-has(‘x’)); assertEquals(0.0, args.getDouble(‘x’), 0.01); assertEquals(“Naéo foi possivel encontrar um paraémetro double para -x.”, args.errorMessage()); } O teste passa normalmente. Fizemos isso apenas por questio de finalizacao. O cédigo de excegao esti horrivel ¢ nao pertence a classe ras. Também estamos langando a ParseException, que nao cabe a nés, Portanto, vamos unir todas as excegdes em uma tinica classe, ArgsException, ¢ colocé-la em seu préprio médulo. public class ArgsException extends Exception ( private char errorArgumentIa = ‘\0'; private String errorParameter = “TILT”; private ErrorCode errorCode = ErrorCode.OK; public ArgsException() (} public ArgsException(String message) {super(message);) public enum ErrorCode { OK, MISSING STRING, MISSING_INTEGER, INVALID_INTEGER, UNEXPECTED ARGUMENT, MISSING_DOUBLE, INVALID_DOUBLE} 236 Capitulo 14: Refinamento Sucessivo public class Args { private char errorArgumentid = ‘\0'; private String errorParameter = TILT"; private ArgsException.FrrorCode errorCode = ArgsException.£rrorCode. OK; private List argsList; public Args(String schema, String[] args) throws ArgsException { this.schema schema; argsList = Arrays.asbist(args); valid = parse(); } private boolean parse() throws ArgsException { if (schema.length() == 0 && argsList.size() return true; parseSchema\(); try { parseArguments(); } catch (ArgsException ¢) [ } return valid; d private boolean parseSchema() throws ArgsException ( } private void parseSchemazlement(string element) throws ArgsException else throw new ArgsException( String.format("Parémetro: %c possui formato invAlido: %3.", element Id,elementTail)); } private void validateSchemazlementid(char elementid) throws ArgsException { if (ICharacter.isLetter(elementId)} { throw new ArgsException( “Caractere errado:” + elementId + “no formato 4 Ara: ) ” + schema); } private void parseElement(char argChar) throws ArgsException { if (setArqument(argChar)) argsFound.add(argChar); else { unexpectedArguments.add(argChar); errercode = ArgsExcept ion.Errorcode.UNEXPECTED_ ARGUMENT; valid = false; Pardmetros do tipo string 237 private class StringArgumentMarshaler implements ArgumentMarshaler private String stringvalue public void set(Iterator currentargument) throws ArgsException { try { stringValue = currentArgument.next(); } catch (NoSuchElementException e) { exrorCode = ArgsException.=rrorCode.MISSING_ ‘STRING; throw new ArgsException(); } public Object gett) { return stringValue; } } private class IntegerArgumentMarshaler implements ArgumentMarshaler private int intvalue public void set(Tterator currentArgument) throws ArgsException ( String parameter = null; try { parameter = currentArgument.next(); intValue = Integer.parseint (parameter); } catch (NoSuchElementException ©} { errorCode = ArgsException.frrorCode.MISSING_ INTEGER; throw new ArgsException(); } catch (NumberFormatException e) { errorParameter = parameter; errorCode = ArgsException.ErrorCode.INVALID_ INTEGER; throw new ArgeException(); } public Object get() { return intvalue; } } private class DoubleArgumentMarshaler implements ArgumentMarshaler private double doubleValue = 0. public void set(Iterator currentArgument) throws ArgsException { String parameter = null; try [ parameter currentArgument.next(1; 238 Capitulo 14: Refinamento Sucessivo doubleValue = Double.parseDouble(parameter); } catch (NoSuchElementException e) { errorCode = ArgsException.ErrorCode.MISSING_ DOUBLE; throw new ArgsException(); } catch (NumberFormatException e) ( errorParameter = parameter; errorCode = Args=xception-FrrorCode.INVALID_ DOUBLE; throw new ArgsException(): } } public Object get() { return doublevalue; } } } Isso ¢ bom. Agora Args langa apenas a excecdo ArgsException que, ao ser movida para seu proprio médulo, agora podemos retirar de Arus os variados cédigos de suporte a erros ¢ colocar naquele médulo. Isso oferece um local ébvio ¢ natural para colocar todo aquele cédigo e ainda nos ajuda a limpar o médulo Args. Portanto, agora separamos completamente os cédigos de exeegdo e de erro do médulo Args. (Veja da Listagem 14.13 4 14.16). Conseguimos isso apenas com pequenos 30 passos, fazendo com que 0s testes rodem entre cada um. Listagem 14-13 ArgsTest.java package com. objectmentor-uti args; import junit. framework. TestCase: public class ArgsTest extends TestCase { public void testCreatewithNoSchemaOrArauments(| throws Exception { Args args = new Args(*", new String(0]1; assertEquals (9, args.cardinality|)); } public void cestWithNoSchemaButWithOneargument (} throws Exception try { new Args(**, new Stringl]{(*-x"}); fail) } catch (ArgsException e) { assertEquals (ArgsExcept ion, ErrorCode.UNEXPECTED_ARGUMENT, -getErrorCode()); } public void testWithNoSchemaButhithMultiplearguments{) throws Excey try { new Args(*", new Stringl](*-x", fail; he Pardmetros do tipo string 239 Listagem 14-13 (continuagaio) ArgsTest .java } catch (Argséxception e) ( assertEquals (Argsbxcept ion. ErrorCode. UNEXPECTED_ARGUMENT, e.getBrrorCode()i; assertEquals|'x', @.getErrorArgumentT4(}): public void testNonbetterSchema() throws Exception { try { thew Args(***, new String[]{}); fail("Args constructor should have thrown exception") ; } catch IArgsixception e) f assertEquals (ArgsExcept ion.BrrorCode. INVALID_APGUMENT_NAME, e.getErrorCode()}; assertEquals ('*', ¢.getErrorArgunentId()); 1 1 public void testInvalidargumentFormat |) throws Exception { try { new Args("f-", new Stringl]{} faill*Args constraccor chould hav } catch (ArgsException e) ( assertEquals (Argsixcept ion .ErrorCode. INVALID_FORMAT, e.getErrorCode(}}; assertEquals('f', ¢.getSrrorArgument Idi)): throws exception"); public void testSimpleBooleanPresent{) throws Exception { Args args = new Argsi"x", new String{](*-x")}; assertEqualsi1, args.cardinality (i); assertEquels{true, args.getBoolean('x')); } public void testSimpleStringPresent |) throws Exception ( Args args = new Args("x*", new String/] "param" }i; assert&quals(1, args.cardinality()): assertTrue(args.has (*x")); assertiquals(*param", arge.getString(‘x")); public void testMissingStringRrgument ij throws Exception ( uy t new Args(*x*", view String[]{*-x"}); fail(); ech (ArgsException e) { assertiqual s (Args=xception.BrrorCode.MISSING_STRT assertRquals('x', e.getirrorArgurentId(}) ; ) e.getErrorcode() } + public void testSpacesInFormat() throws Exception { Args args = new Argsi*x, y*, new Stringl]{"-«y"}); assertEquals(2, args.cardinality(}}; 240 Capitulo 14: Refinamento Sucessivo Listagem 14-13 (continuaga0) ArgsTest.java assert True(args.has('x assert True(args.has | De Ve public void testSimpleIntPresent() throws Bxception ( Acgs args = new Args(*x#", new String[]{*-x", "42"}]; assertEquals (1, args.cardinality(}); assert True(args.has ('x"1); assertEquals(42, args.getInt (*x'1); public void testInvalidinteger |} throws Exception { try { new Args("K#", new St: "Forty two"}); fail; catch (ArgsExcepticn €) 1 assert Equals (ArgsExcept ion .ErrorCode. INVALID_INT! assertiquals('x', eget ErrorArgument T4()) ; assertiquals(*Forty two", e.getirrorParameter ()); 1) e.getErrorcodei)); void testMissingtnteger|) throws Exception { Args(*x#", new String[]{*-x"}); OF } catch (ArgsException ¢} { assert&quals(Args=xcept ion. ErrorCode.MISSING_INTEGER, ¢.getErrorCode!j}; assertEquals('x', e.get@rrorArgumentTd()) ; t } public void testSimpleDoubleFresen ‘Args args = new Argsi"x#é", new String[]{*-x", ascortiquale (1, args.cardinality()); assert True (args.has('x")); assertEquals(42.3, args.getDouble('x'), .001); } public void testInvalidDouble() throws Exception { try { new Args(*x##", new String[]{ fail(); } catch (argsixception e) ( assertEquals |ArgsException.BrrorCode. INVALTD_DOUBLE, e.getErrerCode(}! + assertEquals i'x', e.getErrorArgumentId (|); assertEquals |*Forty two", e.getErrorParaneter ()); , + ) throws Exception { eae -x", ‘Forty two"); public void tescMissingDouble() throws Exception { ty { new Args(*xWE", new String[]{*-x"1); Pardmetros do tipo string 241 Listagem 14-13 (continuagao) ArgsTest -j. new Aros("x#4", new String[]{™ i1 0; } catch (ArasException e) { assertEquals (ArgsException.ErrorCode. INVALID_DOUBLE, e.getErrorCode(!); assertEqualsi'x', €.getkrrorargument 1d(}); assertEquals(‘Forty two", @.getErrorParameter()|; i ", "Forty two"}!; public void testissingbouble() throws Exception { try { new Args(*x##*, new String[]("-2"]); fail); } catch targsExceprion e) { ascertéquals (ArasException. ErrorCode.MISSING_DOUBLE, e.getErrorCode(|}; assertEquals('x', €.getErrorArgument 1d()}; Listagem 14-14 ArgsExceptionTest.java public class ArgsExceptionTest extends TestCase { public void testUnexpectedMessage|) throws Exception [ ArgsException e = new ArcsException(ArgsExcept ion. ErrorCode. UNEXPECTED_ARCUMENT, tx!, null); assertEquals (‘Argument -x unexpected.", e.errorMessage()); } public void testWissingStringMessage() throws Excepti ArgsException = new ArgsException (ArgsExcept ion. ErrorCode,. MISSTNG_STRING, "x', null); ind string parameter for -x.", e.errorMessage()) assertfquals(‘Could not £ } public void testInvalidintegerMessage() throws Exception [ ArgeExcention ¢ = new ArgsException (ArgsException.ErrorCode, INVALID_INTEGER, "x, "Forty two"); assertquals (‘Argument -x expects an integer but was ‘Port e-errorMessage(}) ; g } public void testMissingIntegerMessage() throws Exception { ArgsException e new ArgsExcept ion (ArgsExcept ion.FrrorCode MISSING_INTEGER, ‘x’, nullj; assertiquals(*Could not find integer parameter for -x.", e.errorMessagel)); y public void testInvalidDoubleMessage(} throws Exception { ArgsException e = new ArcsException(ArgsException.ZrrorCode. INVALID_DOUBLE, 242 Capitulo 14: Refinamento Sucessivo Listagem 14-14 (continuacao) ArgsExceptionTest .java ‘x', “Forty two"); assertBquals(*Argument -x expects a double but was ‘Forty two!.", e.errorNessage()); public void testMissingDoubleMessagei) throws Exception { ArgsException e = new ArgsExcention(ArgsExcept ion. ErrorCode.MISSING_DOUBLE, "x, null); agsertEqualsi*Could not find double parameter for -x.", e.errorMessage(}); Listagem 14-15 ArgsException.java public class ArasException extends Exception { private char errorargumentId = “\0' private String errorParameter = *TILT* private ErrorCode errorCode = ErrorCode.OK; public ArgsException() {+ public ArgsException(String message) {super {message) :} public ArgsException(RrrorCode errorCode) { this.errorCode = errorCod=; public ArgsException(ErrorCode errorCode, String errorParaneteri { this.errorCcde = errorCode; this.errorParameter = errorParameter; } public ArgsException(ErrorCode errorCode, char errorArgumentTd, String errorParameter! ( this.errorCode = errorCode; this.errorParameter = errorParaneter; this.errorArgunent Id = errorArgument Id; } public char getErrorargumentId() { return errorArgument Id: public void aetErrorArgumentIdichar errorArgument1d) { this.errorArgumentid = errorArgument Id; Pardmetros do tipo string 243 Listagem 14-15 (continuago) ArgsException. java public void setErrorArgumentTd@ichar errorArgumentld) { this errorArgumentId -gunent Id; public String getErrorvaraneter() return errorParameter; } public void secErrorParameter (String errorParameter) { this.errerParameter = errerParamecer; } public ErrorCode getBrrorCode() { return errartode; public void setErrorCode(ErrorCode errorCode) ( this.errorCode = errorCede; public String errorMessagei) throws Exception { switch jerrorCede! { case OF throw new Excepsion(*TIL’ se UNEXPECTED_ARGUMENT: return String. format ("Argument -%¢ unexpect: case MISSING_STRIN return String. format |"Could not find string parameter for -%c.*, TrorArgument Id) ; Should not get here."); *, ercor&rgument 14}; case INVALID_INTEGE recurn Str eqrorArgumentid, errorParameter); case MISSING_INTEGER: return String.format ("Could not find integer parameter for -%c.", errorArgument Td) ; case INVALID_DOUBLI return String. format ("Argument -%c expects a double but was 'Ss'." errorArgumentId, errorParameter); case MISSING_DOUBLE: return String. format (*Could noc find double parameter for -8c.", errorArgument Id} ; d return "*; , public enum Brrorcode { CK, INVALID_PORMAT, UNEXPECTED ARGUMENT, INVALID_ARCUMENT_NAME, MISSING_STRING, MISSING_INTEGER, TNVALID_INTEGER, MISSING_DOUBLE, INVALID_DOUBLE} ng. format ("Argument -8¢ expects an inceyer but was '%s".". 2a Listagem 14-16 Args.java public class Arge [ private String schema; private Map marshalers = new HashMap(i; private Set arasFound - new HashSet currentargumen private List argsList; public Args (String schema, String[] args} throws ArgsException { this.schena = schema; argsList = arraye.asList (args) ; parse(); private void parse() throws ArgsException { parseSchenai); parseArgunents(); ) private boolean parseSchema() throws ArgsExcepticn { for (String element : schema.split(",")) { if (element.length() > 0) { parseSchenaElenent (elenent.trim()) ; return true; private void parseSchema#lenent (String element) throws ArgsException { char elementId = element .charAt (C1; String eleneatTail = elenent substring! vyalidateSchena ilement Id (element Ia) ; if (elenentTail.length() == 0) narshalere.put (elenentId, new BooleanArguuentMarshaler ()) ; else if (elemenctail.equals(***)) narshalers.put (elementId, new StringArgumentMarshaier |) ); else if [elementTail equals("#"}) marshalers. put (elementTd, new IntegerArgumentMarshaler()) ; else if |elementTail.equals|"##")! marshalers.put (element Id, new DoubleArgumentMarshaler ()); else throw new ArgsException !ArgsException.ErrorCode. INVALID_FORMAT, element?d, elementTail) ; } private void yalidateschemaElenentid(char elenent1d) throws argssxception { if (!haracter.isbetter|elemencid)} { throw new ArgsExcept ion (Argsixception.£rrorCode. INVALID ARGUMENT NAME, elementId, aull); ParAmetros do tipo string 245 Listagem 14-16 (continuagao) Args.java private void parseArounents() throws ArasException ( for (currentargument = argeLiat.iterator(i: currentArgunent .hasMext();) { String arg = currentargurent .next ( parseargument (arg) } void parseargumenc (String ara) throws ArgsExce if (arg.startswith('-"!) parseBlements (arg) ; } privat rion { private void parseElenente (String arg) throws ArgsException { for (int i= 1; i < arg.length(); ist! parseElement (arg.charAt (i); } private void parseElenent (char argChar) throws ArgsException { if (setArgument (arachar)) argsPound.add(argChar) : else ( throw new ArgsExcept ion (ArgsExcept ion. BrrorCode.UNEXPECTED_ARGUMENT arg¢har, mull); } } private boolean setArgument (char argChar) throws ArgsExceptien { RugunentMarshaler m if tm nul) return false: { nn.set (currentArgunent) marshalers.get largChar return } catch (Arg: { e.setErrorArgumentId(arachar) ; throw e7 ‘A maioria das mudangas da classe Args foiforam exclusdes. Muito do cédigo foi apenas removido de Args ¢ colocado em ArgsException. Otimo. Também movemos todos os ArgumentMarshal Ler para seus prdprios arquivos. Melhor ainda. Muitos dos bons projetos de software se resumem ao particionamento—criar locais apropriados para colocar diferentes tipos de cédigo. Essa separagao de preocupagdes toma o cédigo muito mais simples para se manter e compreender. De interesse especial temos 0 método errorMessage de Aros=xception. Obviamente, é uma violag3o ao SRP colocar a formataao da mensagem de erro em Args. Este deve apenas processar os pardmetros, e no o formato de tais mensagens. Entretanto, realmente faz sentido colocar o cédigo da formatagao das mensagens de erro em ArgsExc ‘eption? Francamente, é uma obrigacao. Os usuarios nao gostam de ter que de criar eles mesmos as mensagens de erro fornecidas por ArgsExcepticn. Mas a conveniéncia de ter mensagens de erro prontas ¢ ja preparadas para vocé ndo ¢ insignificante 246 A esta altura ja deve estar claro que estamos perto da solugo final que surgiu no inicio deste capitulo, Deixarei como exercicio para vocé as ultimas modificacées. Conclusio Isso nao basta para que um cédigo funcione. Um cédigo que funcione. geralmente possui bastantes erros. Os programadores que se satisfazem sé de em verem um cédigo funcionando nio esto se comportando de maneira profissional. Talvez temam que nao tenham tempo para melhorar a estrutura eo modelo de seus cédigos, mas eu discordo, Nada tem um efeito mais intenso ¢ degradante em longo termo sobre um projeto de desenvolvimento do que um cédigo ruim, E possivel refazer cronogramas ¢ redefinir requisitos ruins. Podem-se consertar as dindmicas ruins de uma equipe. Mas um eédigo ruim apodrece e degrada, tornando-se um peso que se leva a equipe consigo, Nao me canso de ver equipes cairem num passo lentissimo devido A pressa inicial que os levou a criar uma massa maligna de cédigo que acabou selando seus destinos. E claro que se pode-se limpar um codigo ruim. Mas isso sai caro. Conforme 0 cédigo degrada, os médulos se perpetuam uns para os outros, criando muitas dependéncias ocultas ¢ intrincadas. Encontrar e remover dependéncias antigas é uma tarefa ardua e longa, Por outro lado, manter 0 cédigo limpo é relativamente facil. Se vocé fizer uma bagunga em um médulo pela manha, & mais facil limpé-lo pela tarde, Melhor ainda, se fez a zona a cinco minutos atras, € muito mais facil limp-la agora mesmo. Sendo assim, a solucdo é constantemente manter seu cdigo 0 mais limpo e simples possivel. Jamais deixe que ele comece a se degradar. 1, Recentemente reescrevi este modulo em Ruby e ele ficou com 1/7 do tamanho e com uma estrutura levemente melhor. 2. A fim de evitar surpresas desse tipo mais tarde, adicionei um novo teste de unidade que invoca todos os testes do FitNesse. 15 Caracteristicas Internas do JUnit © JUnit é um dos frameworks Java mais famosos de todos. Em relagao a frameworks, ele € simples, preciso em definigao ¢ elegante na implementagao. Mas como € 0 cédigo? Neste capitulo, analisaremos um exemplo retirado do framework JUnit. 248 Capitulo 15: Testes de Unidade O framework JUnit © JUnit tem tido muitos autores, mas ele comegou com Kent Beck ¢ Eric Gamma dentro de um avido indo Atlanta, Kent queria aprender Java; e Eric, sobre o framework Smalltalk de testes de Kent. “O que poderia ser mais natural para uma dupla de geeks num ambiente apertado do que abrirem seus notcbooks ¢ comegarem a programar?”' Apés trés horas de programagao em alta altitude, eles criaram os fundamentos basicos do JUnit. O médulo que veremos, 0 ComparisonCompactor, é a parte engenhosa do cédigo que ajuda a identificar erros de comparagao entre strings. Dadas duas strings diferentes, como AECDE € ABXDE, co médulo cxibira a diferenga através da produgio de uma string como <. . .B[X]D.. > Eu poderia explicar mais, porém, os casos de teste fazem um trabalho melhor. Sendo assim, veja a Listagem 15.1 e voce entender os requisitos deste médulo em detalhes. Durante 0 processo, analise a estrutura dos testes. Elas poderiam ser mais simples ou ébvias? Listagem 15-1 ComparisonCompactorTest.java package junit.tests. framework; import junit. framework. compari sonCompactor; import junit. framework. Te: public class ComparisonCompactorTest extends TestCase { public void testMessage() { tring failure= new ComparisonCompactor(0, "b', ‘c*).compact ("a") + agsertTrue(*a expected:<[b]> but wae:<[c]>*-equals (failure!) ; public void testStartSare() { String failure srtEuals {" expected: < 1 "ba*, *bo*) compact (null) b[a]> but was:", failure); new Comparisonconpactor public void testEndSame() { String ailure- new ComparisonCompactor{1, "ab", "cb*) compact (null); assertEquals ("expected:<[a}b> but was:<[c]b>", failure) ; , public void testSame() { String failure> new CoxparisonConpactori1, tab", *ab") compact inull) ; assertEgua pectedrcab> but wast*, failure): } Public void testNoContextstartandEndsama() { String failure= new ComparisonCompactor (0, "abc", *adc") assertEquals(*expected:<...[b]...> but was:<...[d]...>", b compact (null) ; ailure} ; 1 JUnle Pocket Guide, Kent Beck, O’Rei O framework JUnit 249 Listagem 15-1 (continuagéio) ComparisonCompactorTest .java public void testStartandmndcontext () { String failure= new CompariconCompactor(1, ‘abe", "ade") « assertEquals(*expected: but was:", failure); ' ompact (null) ; public void testStartAndindContextWithELlipses|) { string failure: new ComparisonCompactor|1, ‘abede*, "abfde*) . compact (null) assertiquals(*expected:<...bic]a...> but was:<..-b[f]d...>*, failurel; ) public void testComparisonErrorStartSameComplete|} { String failure- new ComparisonCompacter (2, "ab", "abe") .compact (null) ; assert Bquals(*expected: but was:", failure) ; } public void testComparisonfrrorEndSameComplete() { String failure= new ComparisonCompactor{0, *be", *abe") .compact (null) ; assertEquals (*expected: but wasi<{a]...>", failure); public vold testComparisonérrorEndSaneCompleteContext |) { String failure= new ComparisonCompactor|2, "be", *abc") .compact (mul) ; assertEquals (*expected:<[]be> but was:", failure) ; y public void testComparisonErrorOverlapingMat ches () { String failure- new ComparisonCompactor (0, "abc", ‘aboe") compact (null); assertequals(*expected:<...[]...> but was:<...[b]...2", failure); } public void testConparisonBrorOverlaping¥atchesContext |) { String failure= new ComparisonConpactor (2, "abc", ‘abbc*) «compact. (null); assertEquals |*expected: but wes:", failure); } public void testComparisonErroroverlapingMatches2() { String failure= new ComparisonCompactor (0, "abcde", *abode") .compacc (null); assertEquals (*expected:<...[@]...> but was:<...[]-..>", failure): } public void testComparisonErrorOverlapingMatches2context () { String failures new ConparisonCompactor(2, *abedde", *abcde") .conpact (nul) aesertEquals (expected:<.,.cd[d]e> but was:<,..cd[Je>", farlurel; public void restComparisonkrrorWithActualNull() String failure= new ComparisonCompactori6, "a", null) .compact (null) + assercEquals("expected: but was:cnull>", failurei; i vonparisonErrorWithActualNullcontext i) { new ComparisonConpactor(2, "a", null) .compact (null); public void te: Scring failurs 250 Capitulo 15: Testes de Unidade Efetuci uma anilise geral do cédigo no ComparisonCompactor usando esses testes, que cobrem 100% do cédigo: cada linha, cada estrutura if e loop for. Isso me dé um alto grau de confianga de que 0 cédigo funciona e de respeito pela habilidade de seus autores. O cédigo do ComparisonCompactor esta na Listagem 15.2. Passe um momento analisando-o. Penso que vocé o achara bem particionado, razoavelmente expressivo e de estrutura simples. Quando vocé terminar, entenderemos juntos os detalhes, Listing 15-2 ComparisonCompactor.java (Original) package junit. framework; public class ComparisonConpactor { private static final string ELLIPSIS private static final String DELTA_END : private static final String DELTA START = *[*; private int fContextLength; private String f®xpected: private String factual private int Prefix; private int Suffix; public ComparisonCompactor (int contextLength, string expected, string actual) { fContextLength = contextLength; fExpected = expected; factual = actual; public String compact (String message) { if (fexpected == null || factual == null || areStringszqual)) return Assert.format (message, fExpected, factual); findConmonPrefix() findCommonsuf fix) ; String expected = conpactString (fExpected) ; String actual - compactString( factual) : return Assert. format (message, expected, actual ivate String compactString (String source) | xing result = DELTA_START + source. substring(fPrefix, source.length!) - Suffix + 1) + DELTA_END; if (fPrefix > 0) result = computeComnonPrefix() + result; if (£Suffix > 0) result = result + computeConmonSuffix()1 return regu: private void findCommonPrefix() { fPref O framework JUnit 251 Listing 15-2 ComparisonCompactor.java (Original) int end .min(fExpected.length(), Actual length ()) + for (3 fPr ferefixe+! if (fExpected.charAt (fPrafix) break; id findcommonsuffix() { int expectedSu fixpected.length() - 1; int actualSuFfix cual.length() ~ 1; for ( ualSuftix > x RE expectedSuffix o= fPrefi: actualguffix--, expectedsuffix--) { if (fExpected.charAt (expectedsuffix) ! break; harAt (actual Suffix} ) iSuffix = fExpected.lengthi) - expectedSutf: rivate String computsCommonPrefix!) { return (fPrefix > fContextLength ? ELLIPSIS : "* fExpected.substring(Math.maxi0, fPrefix - fContextiength), Prefix); private String computeConmonSuftix() ( int end = Math.min|fExpected.length() - £Suffix + 1+ £ContextLength, EExpected.length()); pected. substring (fExpected.length() - fSutfix + 1, end) + ifBxpected.length() - {Suffix + 1< fExpected.length(| - fContextLength ? ELLIPSIS : * return , Talvez vocé tenha algumas criticas em relacdo a este modulo, Ha expressdes muito extensas ¢ uns estranhos +1s e por ai vai. Mas, de modo geral, este médulo esté muito bom. Afinal de contas, ele deveria estar como na Listagem 15.3. Listagem 15-3 ComparisonCompator.java (sem refatoragdo) package junit. tramework: public class ComparisonCompacter { private int ctxt; private String sl; private String 92; private int pfx; private int sfx; public ComparisonCompactor (int ctxt, String s1, String s2) { this. ett; this.sl = 3 this.s2 = s2: 252 Capitulo 15: Testes de Unidade Listagem 15-3 ComparisonCompator.java (sem refatoragio) } public String compact (String msg) { if (sl == null || 32 == oull |i sl-equals(s2)) return Assert, format (1 pix for ength(), s2-length[)|; péxes) ( lengthi) - 17 2 = stelength() - 1; for i; sfx2 >= pfx && sfxl >= pix; sfx2--, sfxl--) { if (sl.charAt(sfxl| != s2.charat(sfx2)) break; ) sfx = sl.length() - sfxl; String cmpl = compactStringisi) : String cmp2 = compactString(s2) ; return Assert. format (msg, empl, cnp2); private String compactstring(string s) { String result = "[" + s.substring(pfx, s.lengthi) - sfx +1) 4°)"; if [pfx > 0) result = (pix > ctxt 7"... sl.substring(Math.nax(0, pfx - ctxt), pix) + result; aun Oe if (sfx > 0) { Math.min(sl.length(} - sfx + 1 + ctxt, sl.lengtht}); result + (sl-substring(s1.lengthi) - sfx +1, end) + agth() - sfx + 1< sl.lengthi) - cext ?*...* 2 **)); return result; ) Mesmo que os autores tenham deixado este médulo num formato muito bom, a Regra de Escoteiro® nos diz que devemos deixé-lo mais limpo do que como vocé o encontrou. Portanto, como podemos melhorar o cédigo original na Listagem 15.2? Primeiro, ndo deve se preocupar com o prefixo £ nas variéveis membro [N6]. Os ambientes de hoje em dia tornam esse tipo de escopo redundante. Sendo assim, eliminemos todos os £. private int contextLength; private String expected; private string actual; private int prefix: private int suffix; O framework JUnit Depois, temos uma estrutura condicional nao encapsulada no inicio da fung&o compact. [G28], public String compact(string message) { if (expected == null || actual == null || areStringsEqual()) return Assert.format(message, expected, actual); findCommonPrefix(); findCommonSuffix(); String expected = compactString(this.expected); String actual = compactString(this.actual); return Assert.format(message, expected, actual); } Devemos encapsular este if para deixar clara nossa inteng&o. Portanto, criemos um método gue a explique public String compact(string message) { if (shouldNotCompact()} return Assert.format(message, expected, actual); findCommonPrefix(); findCommonsuffix(); String expected = compactString(this.expected); String actual = compactStringithis.actual); return Assert.format(message, expected, actual}; d private boolean shouldNotCompact() ( return expected == null || actual == null || « StringsEqual(); ? Nao ligo muito para a notagdo this.expected e this.actual na fungao compact. Isso ocorreu quando alteramos 0 nome de fExpected para expected. Por que ha varidveis nesta fung’o que possuem os mesmos nomes das variaveis membro? Elas nao representam outra coisa [N4]? Devemos desambigiiizar os nomes String compactExpected = compactString (expected); String compactActual = compactString(actual); E um pouco mais dificil entender negativas do que afirmativas [G29]. Portanto, vamos inverter aquela estrutura if, public String compact (String message) ( if (camBeCompacted()) { findCommonPrefix(); findCommonsuffix(); String compactExpected = compactstring(expected); String compactActual = compactString(actual); return Assert.format (message, compactExpected, compactActual); } else { return Assert.format(message, expected, actual); J 254 Capitulo 15: Testes de Unidade } private boolean canBeCompacted() { return expected f= null @& actual t= null &@ lareStringsEqual(); } O nome da funcao esta estranho [N7]. Embora ela realmente compacte as strings, ela talvez nao o faga se canBeCompacted retornar false. Portanto, nomear essa fungaio como compact esconde 0 efeito colateral da verificagao de erro. Note também que. além das strings compactadas, a fungao retorna uma mensagem formatada. Dessa forma, 0 nome da fungao deveria ser formatCompact edComparison. Assim fica muito melhor quando lido juntamente com o parametro da fungio. public String formatCompactedComparisen(String message) { O corpo da estrutura it é onde ocorre a compactagao real das strings. Deveriamos extrair isso para um método chamado compact ExpectedAndActual. Entretanto, queremos que a fun¢ao formatCompactedComparison faga toda a formatagao. A fungdo compact... ndo deve fazer nada além da compactag’o [G30]. Portanto, vamos dividi-la assim: private String compactExpected; private String compactactual; public string formatCompactedComparison(String message) { if (canBeCompacted()) { compactExpectedandactual(); return Assert .format(message, compactExpected, compactactual } else ( xeturn Assert.format(message, expected, actual); t private void compactExpectedAndActual() { findCommonPrefix(); findCommonsuffix(); compactExpected = compactString(expected); compactActual = compactString(actual); , Note que isso nos forga a transformar compact Expected ¢ compactActual em variaveis- membro. Nao gosto da forma de que as duas iltimas linhas da nova fun¢do retorna as variaveis, mas duas primeiras nfo retornam. Elas nfo estao usando conyengdes consistentes [G11]. Portanto, devemos alterar findCommonPrefix ¢ findCommonSuffix para retornar os valores do prefixo e do sufixo. private void compactExpectedAndactual() { prefixIndex = findCommonPrefix(); suffixindex = findCommonsuffix(); compactExpected = compactString(expected); compactActual = compactString(actual); } private int findCommonPrefix() { int prefixIndex = 0; int end = Math.min(expected.length(), actual.length()}; for (; prefixIndex < end; prefixIndex++) { if (expected.charAtiprefixIndex) != actual. charAt (prefixIndex)) break; ? return prefixiIndex; } private int findCommonSuffix() ( int expectedsuffix = expected.length() - 1; int actualSuffix = actual.length() - 1; for (; actualSuffix >= prefixIndex && expectedsuffix >= prefixIndex; { actual. actualsuffix--, expectedsuffix- if (expected.charAt (expectedSuffix) charAt(actualSuffix)| break; } return expected.length() - expectedsuffix; ) Também deveriamos tornar os nomes das varidveis-membro um pouco mais precisos [NI]: apesar de tudo, ambas siio indices, Uma anilise cuidadosa de findCommonSuffix expde um acoplamento tempordrio escondido [G31]; ele depende do fato de prefixtndex ser calculado por findCommonPrefix. Se chamassem cessas duas fungdes fora de ordem, a sesso de depuragao depois ficaria dificil. Portanto, para expor esse acoplamento tempordrio, vamos fazet com que findCommonsuffix receba prefixIndex como um parametro. private void compactExpectedAndactual() { prefixIndex findCommonPrefix(); suffixIndex = findCommonSuffix(prefixIndex’ compactExpected = compactString(expected); compactActual = compactStringlactual); private int findCommonSuffix(int prefixIndex) ( int expectedSuffix = expected-length() - 1; int actualSuffix = actual.length() — for (; actualSuffix >= prefixIndex && expectedSuffix >= prefixIndex; { actual. actualsuffix--, expectedSuffix if (expected.charAt(expectedsuffix) 256 Capitulo 15: Testes de Unidade charAt (actualSuffix)) break; ) return expected. length() - expectedSuffix; } Nao estou satisfeito com isso. A passagem de prefixtndex ficou um pouco arbitraria [G32]. Ela serve para estabelecer a ordenagao, mas no explica essa necessidade. Outro programador talvez destaga o que acabamos de fazer por nao hayer indicagao de que o pardmetro ¢ realmente necessdrio, Portanto, tentemos de outra mancira. private void compactExpectedAndActuall) { findComronPrefixandSuffix(); compactixpected = compactString(expected): compactActual = compactString(actual); j private void findCommonPrefixAndSuffix() ( findCommonPrefix(); int expectedSuffix = expected.length() - 1; int actualsuffix = actual.length() - 1; for (; actualsuffix >= prefixIndex && expectedsuffix >= prefixIndex; actualsuffix--, expectedsuffix-- if if lexpected.charAt{expectedSuffix) charAt(actualsuffix)) break: actual. suffixindex = expected.length() - expectedSuffix; 3 private void findCommonPrefix() { prefixIndex = 0; int end = Math.min(expected.length(), actual.length(]); for (; prefixIndex < end; prefixTndext+) if (expected.charat (prefixindex) charAt (prefixIndex)) break; actual. d Colocamos finéCommonPrefix e findCommensuffix como estavam antes, mudando o nome de findCommonsuffix para findCommonPrefixAndSuffix e fazendo-a chamar findCommonPrefix antes de qualquer outra coisa. Isso estabelece a natureza temporaria das duas fungdes de uma maneira muito mais expressiva do que a solucdo anterior. E também indicar como findCommonPrefixandSuffx é horrivel. Vamos limpé-la agora. private void findCommonPrefixandsuffix() { findCommonPrefix(); int suffixLength = 1; for (; !suffixOverlapsPrefix(suffixLength); suffixLength++) { O framework JUnit 257 if (charFromEnd(expected, suffixLength) != charFromEnd(actual, suffixLength)) break; } suffixindex = suffixbength; } private char charPromEnd(String s, int i) { return s.charAt(s.length()-i);} private boolean suffixOverlapsPrefix(int suffixength) ( return actual.length() - suffixLength < prefixbength || expected.length() - suffixuength < prefixLength; y Assim ficou muito melhor, pois mostra que suffixIndex é 0 comprimento (Length) do sufixo € que nao esté bem nomeado. O mesmo vale para prefixindex, embora neste caso “indice” (index) € “comprimento” (Length) sejam sinénimos. O problema é que a varidvel suffixIndex nao come¢a com zero, mas por I, ¢, portanto, nao é um comprimento real. Esse também ¢ 0 motivo pelo qual existem todos aqueles 1+s em computeCommonSuffix [G33]. Sendo assim, ‘vamos consertar isso. O resultado esta na Listagem 15.4. Listagem 15-4 ComparisonCompactor.java (temporario) { public class ComparisonCompact private int suffixbength; private void findCommonfrefixandsuffix() { findCommonPrefix (1 suffixbength = 0; for (; !suffixOverlapsPrefix/suffixbengch) if (charFromind (expected, suffixiensth| charfroméndiactual, suffixLength| ) break; suffixbengthr+) { } private char charFromnd (String s, int i) { return s.charAt(s.length!) - i - 1)7 private boolean suffixOverlapsPrefix(int suffixLengthi { return actual.length() - suffixbength <= prefixLength || expected. length() - auffixengch <= prefixtength; private String compactString(String source) { String result = DELTA_START + source.substring{prefixLength, source. length(| - suffixbength) + JTALEND; 258 Capitulo 15: Testes de Unidade fagem 15-4 (continuacao) ComparisonCompactor.java (temporério) DELTA_END; if (prefixLength > 0) result = computeCommonPrefix() + 1 if (suffixtength > 0) result = result + computeCommonsuffix(): recurn resul prival int end = Math mi e String computeComonsuffix() { jected, Le: euffixtength + ted.substring (expected. length() - suffixbength, end) + xpected.length() - suffixbength < expected. length() - contextLenath 7 ELLIPSIS: *"1; Substituimos os +1s em computeCommonsuffix por um -1 em charFromsnd, onde faz mais sentido, e dois operadores em suffixOverlapsPrefix, onde também fazem mais sentido. Isso nos permitiu alterar 0 nome de suffixIndex para suffixLength, aumentando consideravelmente a legibilidade do cédiga Porém, ha um problema. Enquanto eu eliminava os +1s, percebi a seguinte linha em compact String: if (suffixLength > 0) Observe-a na Listagem 15.4. Pela légica, como agora suffixLength esta uma unidade menor do que anteriormente, eu deveria alterar o operador > para >=. Mas isso nao faz sentido. Agora faz! Isso significa que antes no fazia sentido e que provavelmente era um bug. Bem, ndo um bug. Apés anilise mais detalhada, vemos que agora a estrutura if evita que um sufixo de comprimento zero scja anexado. Antes de fazermos a alteragdo, a cstrutura if no era funcional, pois suffixtndex jamais poderia ser menor do que um. Isso levanta a questdo sobre ambas as estruturas if em compact string! Parece que poderiam ser eliminadas. Portanto, vamos coloca-las como comentdrios e rodar os testes. Eles passaram! Sendo assim, vamos reestruturar compact String para climinar as estruturas if irrelevantes tomar a fungaio mais simples. private String compactString(string source) ( return computeCommonPrefix() + DELTA_START + source.substring(prefixbength, source.length() - suffixLength) + DELTA_END + computeCommonsuffix(); O framework JUnit 259 Assim esté muito melhor! Agora vemos que a fungdo compact String estd simplesmente unindo os fragmentos. Provavelmente, podemos tornar isso ainda mais claro. De fato, ha varias pequenas limpezas que poderiamos fazer. Mas em vez de the arrastar pelas modificagdes finais, mostrarei logo o resultado na Listagem 15.5. Listagem 15-5 ComparisonCompactor.java (final) package junit. framework: public class ConparisonConpactor ( string ELLIPSIS = * String DELTA_END = *]'; 1 String DELTA_START = *[* Yt contextLength; String expected; String actual; private int prefixLength; private int suffixbength; ri sonCompactor ( ength, String expected, String actual this.contextLength - contextLength; this.expected = expecte this.actual = actual; public String iormatCompactedConparison (String message) | String compactExpected = expected; String compactactual = actual; if ishowldBecompacted()) { FindCommanPrefixandSuf fix() 7 compactExpected = compact (expected) ; compactActual = compact (actual); } return Assert .format (message, , sompactExpected, compac private boolean shouldBeCompacted() { return Ishouldct#eCompacted() : } ‘ivate boolean shouldNorBeCompacted|) { return expected == null || actual = null || expected.equals (actual or: private void findConmonPrefixandsuf fix) ( indConmonPrefix(} 260 Capitulo 15: Testes de Unidade Listagem 15-5 (continuagao) ComparisonCompactor.java (final) suffixbength = 0; for (; !suffixOverlapsPrefix(); suffixle if (charFromend(expected, suffixLength) charFromEnd(actual, suffixLengthi ) break; private char charfronfnd(string s, recurn s.charat (s.length(} - i ~ 1); ) private boolean suffixOverlapsPrefix(} { return actual.lenath|} - suffixLength <= prefixLenath || expected.length() - suffixlength <- prefixtength; private void findComtonPrefix!) { prefixLength int end = Math.min(expected.length(), actual. lengthi for {; prefixLength < end; prefixLength++) if lexpected.charat (prefixLength) != actual.charAt iprefixLength) } break; private String compact (String s) { return ne« StringBuilder () -append (startingEllipsis()) -append(startingContext i}) append (DELTA_START) -append(deltais)) ‘append (DELTA_END) append (endingContext |) } -append (endingEl]lipsis(}) -tostring(); private String starcingEllipeii return prefixtength > contextLength ? ELLIPSIS : ; ) private String startingContext() ( int contextStart = Math.max(¢. prefixength - contextLength) ; int contextEnd = prefixLength; return expected. substring(contextStart, contextEnd) ; private String delta(string s) { int deltaStart = prefixtength; int deltaEnd = s.length() - suffixLength; return s.substring(deltaStart, deltaEnd) ; 1 Conclusio 261 Listagem 15-5 (continuacio) ComparisonCompactor.java (final) private Str. endingContext() { int contextStart = expected. length{) - suffixLengtl int contextina Nath.min (contextStart + revurn expected. subszring|cc expects thy): contextind ring endingEllipsis() return isuffixLength > contextLength ? EU 3 SH, Ficou realmente belo, O médulo esta separado em um grupo de fungdes de andlise e em um de sintese. E estio topologicamente organizadas de modo que a declaragao de cada uma aparece logo apés seu uso. Todas as fungdes de andlise aparecem primeiro, € as de sintese por tiltimo. Se olhar com atengo, veri que reverti diversas das decisdes que eu havia feito anteriormente neste capitulo. Por exemplo, coloquei de volta alguns métodos extraidos em formatCompactedComparison ¢ alterei o sentido da expresso shouldNotBcCompacted. Isso é tipico. Geralmente, uma refatoragao leva 4 outra, que leva ao desfazimento da primeira. Refatorar é um processo iterativo cheio de tentativas e erros, convergindo inevitavelmente em algo que consideramos digno de um profissional. Conclusao E também cumprimos a Regra de Escoteiro. Deixamos este médulo um pouco mais limpo do que como o encontramos. Nao que ja nao estivesse limpo. Seus autores fizeram um trabalho excelente com ele, ‘Mas nenhum médulo esti imune a um aperfeigoamento, ¢ cada um de nos tem o dever de deixar © cédigo um pouco melhor do que como o encontramos. Esta pagina foi deixada intencionalmente em branco 16 Refatorando o SerialDate f Se vocé for ao site http://www. jfree.org/jcommon/ index.php, encontrara a biblioteca JCommon, dentro da qual ha um pacote chamado org.jfrec.date, que possui uma classe chamada SerialDate. lremos explorar essa classe. David Gilbert & 0 criador da SerialDate. Obviamente, cle é um programador competente ¢ experiente. Como veremos, ele exibe um nivel consideravel de profissionalismo e disciplina dentro do cédigo. Para todos os efeitos, esse é um “bom cédigo”, E cu irei desmembra-lo em partes. 264 Capitulo 16: Refatorando o SerialDate Nao possuo mas intengdes aqui. Nem acho que eu seja to melhor do que o David que, de alguma forma, eu teria o direito de julgar scu cédigo. De fato, se visse alguns de meus cédigos, estou certo de que vocé encontraria varias coisas para criticar. Nao, também ndo ¢ uma questo de arrogancia ou maldade. O que pretendo fazer aqui nada mais &do que uma revis%o profissional. Algo com a qual todos nds deveriamos nos sentir confortaveis em fazer. E algo que deveriamos receber bem quando fizessem conosco. E sé através de criticas como essas que aprenderemos. Os médicas fazem isso. Os pilotos também fazem. Os advogados também, E nés programadores precisamos aprender a fazer também. E mais uma coisa sobre David Gilbert: ele é muito mais do que um bom programador. David teve a coragem e a boa vontade de oferecer de graga seu cédigo a comunidade em geral. Ele 0 colocou a disposigao para que todos vissem, usassem ¢ analisassem. Isso foi um bem feito! SerialDate (Listagem B.1, pagina 349) é uma classe que representa uma data em Java. Por que ter uma classe que represente uma data quando 0 Java jd possui a java.util.Date ea java. util.Calendar, dentre outras? O autor criou essa classe em resposta a um incdmodo que eu mesmo costumo sentir. 0 comentirio em seu Javadoc de abertura (linha 67) explica bem isso. Poderiamos deduzir a intenc&o do autor, mas eu certamente ja tive de lidar com essa questao ¢, entZo, sou grato a essa classe que trata de datas ao inyés de horas. Primeiro, faga-a funcionar Ha alguns testes de unidade numa classe chamada SerialDateTests (Listagem B.2, pagina 366). Todos os testes passam. Infelizmente, uma rapida andlise deles mostra que nao testam tudo [TI]. Por cxemplo, efetuar uma busca “Encontrar Tarefas” no método MonthCodeToQuarter (linha 334) indica que ele nao é usado [F4]. Logo, os testes de unidade nao o testam. Ent3o, rodei o Clover para ver 0 que os testes de unidade cobriam € nao cobriam. O Clover informou que os testes de unidades so executavam 91 das 183 instrugdes na Serialpate (~50%) [T2]. O mapa do que era testado parecia uma colcha de retalhos, com grandes buracos de cédigo nao exccutado amontoados ao longo de toda a classe. Meu objetivo era entender completamente c também refatorar essa classe. Eu ndo poderia fazer isso sem um teste de maior cobertura, Portanto, criei minha propria colegio de testes de unidade completamente independente (Listagem B.4, pagina 374) Ao olhar esses testes, vocé vera que muitos foram colocados como comentarios. Esses testes falharam. Eles representam o comportamento que eu acho que a SerialPate deveria ter, Dessa forma, conforme refatoro a SerialDate, farei também com que esses testes passem com éxito. Mesmo com alguns dos testes colocados como comentirio, o Clover informa que os novos testes de unidade executam 170 (92%) das 185 instrugées executiveis. Isso é muito bom, ¢ acho que poderemos aumentar ainda mais esse mimero. Os primeiros poucos testes postos como comentarios (linhas 23-63) sto um pouco de prepoténcia de minha parte. O programa nao foi projetado para passar nesses testes, mas 0 comportamento parece dbvio [G2] para mim. Nao estou certo por que o método testWeekdayCodeTostring foi criado, mas como ele esta 1a, parece dbvio que ele nao deve diferir letras maiusculas de minusculas. Criar esses testes foi simples [T3]. Fazé-los passar foi mais simples ainda: apenas alterei as linhas 259 e 263 para usarem equalsTgnoreCase. Primeiro, faga-a funcionar 265 Coloquei os testes nas linhas 32 ¢ 45 como comentirios porque nio esté claro para mim se as abreviagdes “tues” (Tuesday, terga-feira em inglés) e “thurs” (Thursday, quinta-feira em inglés) devem ser suportadas. Os testes nas linhas 153 e 154 falham. Obviamente, eles deveriam mesmo [G2]. Podemos facilmente conserté-los e, também, os testes das linhas 163 a 213, fazendo as seguintes modificagées a fungio stringToMonthCode. 457 158 459 469 461 462 463 464 465 166 Otestecolocadocomocomentarionalinha3 | 8expoeumbugnométodoget Fol lowingDayOfWeek (linha 672). A data 25 de dezembro de 2004 caiu num sabado. E 0 sabado seguinte em 1° de janeiro de 2005. Entretanto, quando efetuamos o teste, vemos que get Fol lowingDayOfWeek retorna 25 de dezembro como o sdbado seguinte a 25 de dezembro. Isso esta claramente errado [G3], [TI]. Vimos o problema na linha 685, que é um tipico erro de condigdo de limite [TS]. Deveria estar escrito assim: 685 1f (baseDOW >= targetWeekday) { E interessante notar que essa fungao foi o alvo de um conserto anterior. O histérico de alteragdes (linha 43) mostra que os “bugs” em get PreviousDayOfWeek, getFol lowingDayOfweek © getNearest DayOfWeek [16] foram consertados. © teste de unidade testGetNearestDayOfweek (linha 329), que testa o método getNearestDayofiieek (linha 705), nao rodou por muito tempo ¢ nao foi completo como est configurado para ser. Adicionei muitos casos de teste ao método, pois nem todos os meus iniciais passaram [T6]. Vocé pode notar o padrao de falhas ao verificar quais casos de teste esto como comentarios [17]. Isso mostra que o algoritmo falha se o dia mais préximo estiver no futuro. Obviamente, ha um tipo de erro de condigao de limite [TS]. O padrao do que o teste cobre informado pelo Clover também é interessante [T8]. A linha 719 nunca € executada! Isso significa que a estrutura if na linha 718 é sempre falsa. De fato, basta olhar o cédigo para ver que isso ¢ verdade. A variavel adjust € sempre negativa e, portanto, 130 pode ser maior ou igual a 4. Sendo assim, este algoritmo esta errado. Oalgoritmo correto é disponibilizado abaixo: 266 Capitulo 16: Refatorando 0 SerialDate getDayOfWeek () + return SerialDate.addDaysladjust, base) Por fim, podem-se fazer os testes na linha 417 ¢ 429 obterem éxito simplesmente langando uma IllegalArgumentException ao invés de uma string de erro a partir de weekInMonthToString e relativeTostring. Com essas alteragdes, todos os teste de unidade passam, e creio eu que agora a Serialpate funcione. Portanto, esta na hora de torna-la “certa” Ent4o, torne-a certa Iremos abordar a SerialDate de cima para baixo, aperfeigoando-a no caminho. Embora vocé no veja esse proceso, passarei todos os teste de unidade do JCommon, incluindo o meu melhorado para a SerialDate, apés cada alteragdo que eu fizer. Portanto, pode ter certeza de que cada mudanga que vocé vir aqui funciona com todos os testes do Jcommon, Comegando pela linha 1, vemos uma grande quantidade de comentarios sobre licenga, direitos autorais, criadores € historico de alteragées. Reconhego que é preciso tratar de certos assuntos egais, Sendo assim, os direitos autorais e as informagdes sobre a licenga devem permanecer. Por outro lado, o histérico de alteragdes ¢ um resquicio da década de 1960, e deve ser excluido [C1] Poderia-se reduzir a lista de importagées (import) na linha 61 usando java.text.* e java. uti2.*. 1] Nao gostei da formatagiio HTML no Javadoc (linha 67), pois o que me preocupa ¢ ter um arquivo fonte com mais de uma linguagem contida nele. E ele possui quatro: Java, portugnés, Javadoc e html [G1]. Com tantas linguagens assim, fica dificil manter tudo em ordem, Por exemplo, a ‘boa posic&o das linhas 71 ¢ 72 ficam perdidas quando 0 Javadoc ¢ criado, e, mesmo assim, quer deseja ver ¢ <1i> no cédigo-fonte? Uma boa estratégia seria simplesmente envolver todo © comentario com
 de modo que a formatagao visivel no cédigo-fonte scja preservada
dentro do Javadoc’.

Alinha 86 éa declaragao da classe. Por que essa classe se chama SerialDate? Qual o significado
de “serial”? Seria por que a classe € derivada de Serializable? Parece pouco provavel

‘Nao vou deixar vocé tentando adivinhar, Eu sei o porqué (pelo menos acho que sei) da palavra
“serial”. A dica esta nas constantes SERIAL_LOWER_BOUND e SERIAL_UPPER BOUND
nas linhas 98 ¢ 101. Uma dica ainda melhor esti no comentario que comega na linha 830. A
classe se chama SerialDate por ser implementada usando-se um “serial number" (numero de
série, em portugués), que ¢ 0 ntimero de dias desde 30 de dezembro de 1899.

Tenho dois problemas com isso. Primeiro, o termo “serial number” nao esta correto. Isso pode
soar como uma critica, mas a representagao esta mais para uma medida do que um mimero de
série. O termo “serial number” tem mais a ver com a identificagao de produtos do que com datas.
Portanto, n&o acho este nome descritivo [N1]. Um termo mais explicativo seria “ordinal”.

O segundo problema é mais significativo. O nome SerialDate implica uma implementagao.

 

1, end dtp sind metho ceria tizer 0 avisdioe apresemuir todos ux comentirios de form pré-formeseda, de modo que trvessora © mesmo formato mo obdignPrimeiro, faga-a funcionar 267

Essa classe ¢ abstrata, logo no ha necessidade indicar coisa alguma sobre a implementagaio, a qual,
na verdade, ha uma boa razo para ser ocultada. Sendo assim, acho que esse nome esteja no nivel
errado de [N2]. Na minha opiniao, o nome da classe deveria ser simplesmente Date.
Infelizmente, jd cxistem muitas classes na biblioteca Java chamadas de Date. Portanto, essa,
provavelmente, ndo é 0 melhor nome. Como essa classe ¢ sobre dias ao invés de horas, pensei
em chaméa-la de Day (dia em inglés), mas também se usa demasiadamente esse nome em outros
locais, No final, optei por DayDate (DiaData) como a melhor opgao.

A partir de agora, usarei o termo DayDate. Mas espero que se lembre de que nas listagens as
quais vocé vir, DayDate representara SerialDate.

Entendo 0 porqué de DayDate herdar de Comparable ¢ Serializable. Mas cla herda de
MonthConstants? Esta classe (Listagem B.3, pagina 372) ¢ um bando de constantes estéticas
finais que definem os meses. Herdar dessas classes com constantes ¢ um velho truque usado pelos
programadores Java de modo que nao precisassem usar expressdes como MonthConstants.January
—mas isso ¢ uma péssima idéia [2]. A MonthConstants deveria ser realmente um enum.

 

 

public abstract class DavUate implements Comparable,
Serializable (

  

public static enum Month (
TANUARY (1),
FEBRUARY (2) ,
MARCH (3
BPRIL(4),
MAY(5!,
JUNE (S),
TULY (7),
AUGUST (2),
SEPTEMBER (9)
OCTOBER (103,
NOVEMBER (11),
DECEMBER (12) ;

 

 

Month(int index) (
this.index = inde:

}

public static Month make(int mor

 

hindex) {

 

 

recurn mj

 

throw new ILlegalArgumentExceptior month index * + monthIndex) ;
}

public final int index;

 

 

 

 

Alterar a MonthConst ants para este enum exige algumas alteragGes na classe DayDate e para
todos os usuarios. Levei uma hora para fazer todas as modificacdes. Entretanto, qualquer fun¢ao
costumava pegar um int para um més, agora pega um enum Month, Isso significa que podemos
nos livrar do método isValidMonthCode (linha 326) e de toda verificagdo de erro no cédigo
dos Month, como aquela em monthCodeToQuarter (linha 356) [G5].

Em seguida, temos a linha 91, serial VersionUID — variavel usada para controlar o “serializador”.268 Capitulo 16: Refatorando o SerialDate

Se a alterarmos, entéo qualquer DayDate criado com uma versio antiga do software no seré
mais legivel ¢ produzira uma InvalidClassException. Se vocé nio declarar a varidvel
serialVersionUID, o compilador gerara automaticamente uma para vocé, ¢ ela sera diferente toda
vez que vocé alterar 0 médulo. Sei que todos 0s documentos recomendam controlar manualmente
essa varidvel, mas me parece que o controle automatico de serializagao é bem mais seguro [G4].
Apesar de tudo, eu prefiro depurar uma InvalidClassException ao comportamento estranho
que surgiria caso eu me esquecesse de alterar 0 serialversionU1D, Sendo assim, vou excluir
a varidvel — pelo menos por agora’

Acho 0 comentario da linha 93 redundante. Eles s6 servem para passar mentiras e informagdes
erradas [C2]. Portanto, vou me livrar de todo esse tipo de comentarios.

Os comentarios das linhas 97 e 100 falam sobre os ntimeros de série (serial numbers) que
mencionei anteriormente [C1]. As varidveis que eles representam so as datas mais antigas e
atuais possiveis que a DayDate pode descrever. E possivel esclarecer isso um pouco mais [N1].

 

[

 

IEST_DATE_ORDINAL = 2; 7 1/1/1900
j_DATE_ORDINAL = 2958465; / 12/31/9999

 

public static fina
public static final

  

 

 

Nao esté claro para mim potque EARLTEST_DATE_ORDTNAL é 2 em ver de 0. Hé uma dica no
comentirio na linha 829 sugerindo que tem algo a ver com a forma de representagao das datas no
Microsoft Excel. A SpreadsheetDate (Listagem B.5, pagina 382), uma derivada de DayDate,
possui uma explicago muito mais descritiva, O comentario na linha 71 descreve bem a questo.
Meu problema com isso é que a questéo parece estar relacionada 4 implementagto de
spreadsheetDate e nao tem nadaa ver com DayDate. Cheguei a conclusio de que EARLTEST_
DATE_ORDINAL ¢ LATEST DATE_ORDINAL realmente ndo pertencem 4 DayDate e devem ser
movidos para SpreadsheetDate [G6].

Na verdade, uma busca pelo cédigo mostra que essas varidveis sf usadas apenas dentro de
spreadsheetDate. Nada em DayDate ou em qualquer outra classe no framework JCommon as
usa, Sendo assim, moverei-as abaixo para Spreadsheet Date.

As variaveis seguintes, MINIMUM_YEAR_SUPPORTED e MAXIMUM YEAR SUPPORTED (linhas
104 e 107), geram um dilema, Parece claro que, se DayDate é uma classe abstrata que nao dé
nenhuma indicag’o de implementagao, entZo ela nao deveria nos informar sobre um ano minimo
ou maximo. Novamente, fico tentado a mover essas varidveis abaixo para SpreadsheetDate
[G6]. Entretanto, uma busca rapida pelos usuarios que as usam mostra que uma outra classe as
usa: Relat iveDayOfwWeekRule (Listagem B.6, pagina 390). Vemos que,nas linhas 177 e 178
na funcdo getDate, as varidveis sdo usadas para verificar sc o parimetro para getDate é um
ano valido. O dilema é que um usuario de uma classe abstrata necessita de informagdes sobre
sua implementagao.

O que precisamos é fornecer essas informagdes sem poluir a Daybate. Geralmente, pegariamos
as informagoes da implementagao a partir de uma instancia de uma derivada. Entretanto, a fungao
getDate niorecebe uma instancia de uma DayDate, mas retorna tal instancia, o que significa que,
em algum lugar, ela a deve estar criando. Da linha 187 a 205, dao a dica. A instancia de DayDate
€ criada por uma dessas trés fun¢des: get PreviousDayOfWeek, getNearestDayOfweek ou
getFollowingpayofweek.

Olhando novamente a listagem de DayDate, vemos que todas essas fungdes (linhas 638-724)

 

2: Diverand panes ue revienraat movamnenne eves texts Seneomn chpego a cass decisbe, Bias.Primeiro, faga-a funcionar 269

retornam uma data criada por adéDays (linha 571), que chama createinstance (linha 808),
que cria uma SpreadsheetDate! [G7].

Costuma ser uma péssima ideia para classes base enxergar seus derivados. Para consertar isso,
devemos usar 0 padrao ABSTRACT FACTORY? e criar uma DayDateFactory. Essa factory
criara as instancias de DayDate que precisamos e também poderi responder as questdes sobre a
implementagao, como as datas minima e maxima.

 

public abstract class DayDateFactory [
private static DayDatefactory factory = new SpreadshestDateFactory():
public static void setInstance(DeyDatePactory factory) {
DayLatefact ory:

 

  
 

procected abstract wakeDate lint ordinaliy

protected abstract DayDate _makeDatelint day, DayDate.Moath men
protected abstract DayDate _makeDatelint day, int month
DayDate _makeDate | java.util.Date date
int _getMinimumyear |) ;

ant _getMaximumiear |);

   

h, int year);

 

 

int year);

   

   

 

public statac DayDate makeDate(int ordinali
return factory._makedate (ordinal):

public
return

yDate makeDate(int day
eDate(day, month, year);

    

DayDate makeDatelint day, int month, int year) {
ory.makeDateiday, month, year)

    

-Date date) (

 

public
return factory

 

gotMinamum¥eat () {
jetMinimunYear |);

 

 

public stat
return factory,

 

getMaximunYear() {
jet Maximum¥ear () 5

   

 

 

 

Essa classe factory substitui os métodos createInstance pelos makeDate, que melhora
um pouco os nomes [NI]. O padrdo se vira para SpreadshectDateFactory, mas que pode ser
modificado a qualquer hora para usar uma factory diferente. Os métodos estaticos que delegam
responsabilidade aos métodos abstratos para usarem uma combinagao dos padrdes SINGLETON‘,
DECORATOR’ ¢ ABSTRACT FACTORY, que tenho achado iiteis.

A spreadsheet DateFactory se parece com isso:270 Capitulo 16: Refatorando o SerialDate

 

 

public class SpreadsheecDateFactory extends NayDatePactory {
public DayDate _nakeDate(int ordinal) {

return new SpreadsheetDate (ordinal) :

 

 

public DayDate _makeDate(int day, DayDate.Month month, int year) {
return new SpreadsheetDate(day, month, year);

}

public DayDare _makeDatelint day, int month. int year!
turn new SpreadsheetDate(day, month,

   

ic Daybate nakebate(Date date} {
nal GregorianCalendar calendar = new GregorianCalendar |);
lendar. setTime (date! ;

turn new SpreadsheetDate |
calendar.get (Calendar -DATE
baybate.Month.make (calendar .get (Calendar.MONTH) + 1),
calendar.get (Calendar. Y#Ak));

 

 

   

 

)

 

protected inc _getMinimumvear(}
urn Spreadsheet late. MINIMUM YEAR SUPPORTED:

 

}

protected int _getMaximumYear() {

retum SpreadshestDate.MAXIMUM_YEAR_SUPPORTED;
}

  

 

 

 

Como pode ver, j4 movi as variaveis MINIMUM_YEAR_SUPPORTED e MAXTMUM_YEAR_
SUPPORTED para onde elas pertencem [G6], em SpreadsheetDate.

‘A proxima questdio na DayDate sao as constantes day que comegam na linha 109. Isso deveria
ser outro enum [J3]. J4 vimos esse padrao antes, portanto, nao o repetirei aqui. Vocé o vera nas
Ultimas listagens.

Em seguida, precisamos de uma série de tabelas comegando com LAST_DAY_OF_MONTH na linha
140. Meu primeiro problema com essas tabelas ¢ que os comentarios que as descrevem sio
redundantes [C3]. Os nomes esto bons. Sendo assim, irei excluir os comentarios.

Parece nao haver uma boa razdo para que essa tabela nao seja privada [G8], pois existe uma
fungdo LastbayOfMonth estatica que fornece os mesmos dados.

‘A proxima tabela, AGGREGATS DAYS _TO_END_OF_MONTH, € um pouco mais enigmatica, pois
nao é usada em lugar algum no framework 7Common [G9]. Portanto, exclui-a.

O mesmo vale para LRAP_YEAR_AGGREGATF_DAYS_TO_END_OF_MONTH.

Apenas a SpreadsheetDate (linhas 434 e 473) usa a tabela seguinte, AGGREGATE_DAYS_TO_END_
OF_PRECEDING MONTH. Isso leva a questo se ela deve ser movida para spreadsheetDate.
O argumento para ndo fazé-lo ¢ que a tabela nfo é especifica a nenhuma implementag3o em
particular [G6]. Por outro lado, nao ha implementagao além da Spreadsheet Date, e, portanto,
a tabela deve ser colocada proxima do local onde ela ¢ usada.

Para mim, 0 que decide ¢ que para ser consistente [G11], precisamos tomar a tabela privadaPrimeiro, faga-a funcionar 271

expé-la através de uma funcdo, como a julianDateOfLastDay0£Month. Parece que ninguém
precisa de uma fun¢do como essa. Ademais, pode-se facilmente colocar a tabela de volta na
classe DayDate se qualquer implementagdo nova desta precisar daquela.

O mesmo vale para LEAP_YFAR_AGGREGATE_DAYS_T0_END_OF_MONTH.

Agora, veremos as trés séries de constantes que podem ser convertidas em enum (linhas 162-205).

A primeira das wés seleciona uma semana dentro de um més. Transformei-a em um enum
chamado WeekinMonth.

 

publ

   

© enum WeekInMonth {
FIRST(1), SECOND(2), THIRD(3), FOURTH(4), LAST(Q);
public final int index:

 

 

WeekInMonth (int indexi {
this.index = index;

 

 

 

 

A segunda série de constantes (linhas 177-187) é um pouco mais confusa, Usam-se as constantes
INCLUDE_NONE, INCLUDE_FIRST, INCLUDE SECOND e INCLUDE_BOTH para descrever se as
datas nas extremidades de um intervalo devam ser incluidas nele. Matematicamente, usam-se 0s
termos “intervalo aberto”, “intervalo mcio-aberto” ¢ “intervalo fechado”.

Acho que fica mais claro se usarmos a nomenclatura matematica [N3], entéo transformei essa
segunda série de constantes em um enum chamado Datelnterval com enumeradores CLOSED
(fechado), CLOSED_LEF'T (esquerda_fechado), CLOSED_RIGH (direta_fechado) e OPEN (aberto).
A terceira série de constantes (linhas 18-205) descreve se uma busca por um dia particular da
semana deve resultar na ultima, na proxima ou na mais proxima instancia. Decidir 0 nome disso
éno minimo dificil. No final, optei por weekdayRange com enumeradores LAS? (ultimo), NEXT
(préximo) e NEAREST (mais préximo).

Talvez vocé nao concorde com os nomes que escolhi. Para mim cles fazem sentido, pode nao
fazer para vocé. A quest&o ¢ que agora cles estio num formato que facilita sua alteracao [J3].
Eles nao sao mais passados como inteiros, mas como simbolos. Posso usar a fung4o de “nome
alterado” de minha IDE para mudar os nomes, ou os tipos, sem me preocupar se deixei passar
algum -1 ou 2 em algum lugar do cédigo ou se alguma declaragao de um parametro do tipo int
est mal descrito.

Parece que ninguém usa o campo de descrigao da linha 208, Portanto, eu a exclui juntamente
com seu método de acesso ¢ de alteragao [G9].

Também deletei o danoso construtor padrao da linha 213 [G12]. O compilador o eriard para nés.
Podemos pular 0 método isValidWeekdayCode (linhas 216-238), pois o excluimos quando
criamos a enumeracao de Day.

Isso nos leva ao método stringToWeekdayCode (linhas 242-270). Os Javadocs que nao
contribui muito a assinatura do método sdo apenas restos [C3],[G12]. O unico valor que
esse Javadoc adiciona ¢ a descri¢io do valor retornado -1. Entretanto, como mudamos para
a enumeracao de Day, © comentario est de fato errado [C2]. Agora o método langa uma
TllegalargumentException. Sendo assim. deletei o Javadoc.

Também exclui a palavra reservada final das declaragdes de pardmetros e variaveis. Até onde272 Capitulo 16: Refatorando o SerialDate

pude entender, ela nao adicionava nenhum valor real, s6 adiciona mais coisas aos restos initeis
[G12]. Eliminar essas fina contraria alguns conhecimentos convencionais. Por exemplo, Robert
Simons* nos aconselha a “...colocar final em todo o scu eddigo”. Obviamente, cu discordo,
‘Acho que ha alguns poucos usos para o final, como a constante final, mas fora isso, as palavras
reservadas acrescentam pouca coisa e criam muito lixo. Talvez eu me sinta dessa forma porque
0s tipos de erros que o final possa capturar, os testes de unidade que escrevi ja 0 fazem.

Nao me importo com as estruturas if duplicadas [G5] dentro do loop for (linhas 259 e 263),
portanto, eu os uni em um unico i= usando 0 operador | | Também usei a enumeragao de Day
para direcionar 0 loop for e fiz outros retoques.

Ocorreu-me que este método nao pertence 4 DayDate. Ele é a fungao de andlise sintatica de
Day. Ento o movi para dentro da enumeragdo de Day, a qual ficou muito grande. Como Day nao
depende de DayDate, retirei a enumeragao de Day da classe DayDate ¢ coloquei cm seu préprio
arquivo fonte [G13].

Também movi a proxima fungaio, weekdayCodeToString (linhas 272-286) para dentro da
enumeracdo de Day e a chamei de toString.

 

public enum Day {
MONDAY (Calendar MONDAY) ,
TUESDAY (Calendar. TURSDAY) ,
WEDNESDAY (Calendar. WEDNESDAY) .s
‘THURSDAY (Calendar. THURSDAY) ,
PRIDAY (Calendar .FRIDAY) ,
SATURDAY {Calendar SATURDAY) ,
SUNDAY (Calendar. SUNDAY) :

 

public fii
privat

1 int index;
static DateFormatSynbels dateSymbols = new DateFormacsymbols(}+

 

   

Day lint day) {
index = day;

 

index) throws IllegalArgumentExcepticn {

 

throw new Tllegalarmment Except ion(
String. format |"Tllegel day index: 44."

 

public static Day parse(String s) throws
ingl] shortWeekdayNames
dateSymbols. get ShortWeekdays |} ;
string[] weekDayNam
datesymbols.getWeekdays () ;

 
   

   

  

s = s.trin();
ior (Day day : Day.values(j) {
if [s.equalsTgnoreCase/shortWeekdayNames [day.index] | ||
e.equalsTgnoreCase (weekDayNanes [day.index])) {
return day;Primeiro, faga-a funcionar 27

 

 
 
  

ception(
a valid weekday string", si};

String toString) {
mn datesymbols.getWeekdays|) | index! ;

 

Ha duas fungdes getmonths (linhas 288-316). A primeira chama a segunda. Esta sé é chamada
por aquela, Sendo assim, juntei as duas numa inica s6 e as simplifiquei consideravelmente
[G9],{G12].[F4]. Por fim, troquei o nome para ficar um pouco mais descritivo [N1].

 

public static string[] getmonthnanes(} {
return dateFormatSymbole.getMonths (|;

   

 

A fungdo isvalidMonthCode (linhas 326-346) se tomou irrelevante devido ao enum Month.
Portanto, deletei-a [G9]. A fungao monthCodeToQuarter (linhas 356-375) parece a FEATURE,
ENVY? [G14] e, provavelmente, pertence a enum Moth como um método chamado quarter.
Portanto, exclui-a.

 

public int quarter() {
return 1 + {index-1}/3;

 

 

 

Isso deixou 0 enum Month grande o suficiente para ter sua propria classe. Sendo assim, retirei-o
de DayDate para ficar mais consistente com 0 enum Day [G11],{G13].

Os dois métodos seguintes chamam-se monthCodeTostr ing (linhas 377-426). Novamente,
vemos um padrao de um método chamando sua réplica com uma flag. Costuma ser uma péssima
idéia passar uma flag como parametro de uma fung’o, especialmente qual a flag simplesmente
seleciona o formato da saida [G15]. Renomeei, simplifiquei ¢ reestruturei essas fungdes ¢ as
movi para o enum Month [N1},[N3].[C3]{G14]

 

public String toStringi) {
return dateFornatSymbols

i) findex - 1

 

public string toshortscring() {
return dateFormatSynbols .getShortMonths () [index
d

 

 

 

 

O proximo método é 0 stringToMonthCode (linhas 428-472). Renomeei-o, movi-o para enum:
Month € o simplifiquei [N1],[N3],[C3],[G14],[G12].274 Capitulo 16: Refatorando o SerialDate

 

 

   

 

public static Month parse(String s) {
§ = s.trin();
for {Nonth m : Month.values(1!
if (m.matches|s)
return m;

try {
return make (Integer.parseint (s

catch iNunberFortatException e) {}

throw new TllegalArgument Exception ("Invalid month " + s);

 

private boolean matches (String s) {
return s.equalsTgnoreCase(tostring()) ||
s.equalsTgnoreCase(toShortStringi));

   

 

E possivel tornar 0 método isteapYear (linhas 495-517) um pouco mais expressivo [G16].

 

public static boolean isbeapYear(int year) {
boolean fourth = year 3 4 == 0;
boolean hundredth = year 8 100 == 0;
boolean fourttundredth = year & 400 == 0;
return fourth S& (!hundredth || fourHundredth) :

 

 

 

 

 

 

A proxima fungao, LeapYearcount (linhas 19-536), realmente nao pertence a DayDate.
‘Ninguém a chama, exceto pelos dois métodos em SpreadsheetDate. Portanto, a movi para
baixo [G6].

A funco 1astDayofMonth (linhas 538-560) usa 0 array LAST_DAY_OF_MONTH, que pertence
a enum Month [G17]. Portanto, movi-a para Id ¢ também a simplifiquei e a tomei um pouco mais
cexpressiva [G16].

 

public static int lastDayOfMonthiMonth month, int year) {

if (month == Month. FEBRUARY && isLeapYear (year) |
return month.lastDay() + 1;

else
return month. last

   

 

 

Agora as coisas esto ficando mais interessantes. A fungdo seguinte ¢ a addDays (linhas 562—
576). Primeiramente, como ela opera nas varidveis de DayDate, ela ndo pode ser estatica [G18].
Sendo assim, a transformei na insténcia de um método. Segundo, ela chama a fungdo toSerial,
que deve ser renomeada para toordinal [N1]. Por fim, ¢ possivel simplificar 0 método.

 

public DayDate addDays(int deys) (
return DayDateFactory.makeDate(toOrdinal () + days}:Primeiro, faga-a funcionar 275

O mesmo vale para addMonths (linhas 578-602), que deverd ser a instancia de um método.
O algoritmo esta um pouco mais complicado, entio usei VARIAVEIS TEMPORARIAS.
EXPLICATIVAS (EXPLAINING TEMPORARY VARIABLES") para ficar mais tansparente.
Também renomeci o método gecYvy para get year [N1]

 

 

public DayDate addMonths
thisMonthasordin 12 * getYear() + gecttonth() index -
resultMonthAsOrdinal = thisMonthasOrdinal + mont
int resultYear = resulrMonthAsOrdinal / 12;

Month resultWonth = Month.make(resultMonthasOrdinal ® 12 + 1);

 

 

   

         

 

 

LtMonth, resuli
Monch) ;

earl;

 

   

int lastDayOfResultMonth = lastDay0 Month res
int resultDay = Math.minigetDayOfMonthi), lastDayOfResu
return DayDateFactory.makeDatelresultDay, resultMonth, result:

   

 

 

A fungao addvears (linhas 604-626) nfo possui nada de extraordindrio em relagao as outras.

 

public Daybate piusYears(int years) {
int resultYear = getvear() + yeare;
int lastDayOrMonthInResuitYear = lastDayOfMonth(getNonth(), resultYear);
int resultDay = Math.nin(getDayOfMonth|}, lastDayOfMonthTnResultYeari
return DayDateFactory makeDate(resultDay, getNonth(), resultYear};

i

 
   

  

 

   

 

 

 

Estou com uma pulga atras da orelha sobre alterar esses métodos de estaticos para instancias. A
expressio date. addpays(5) deixa claro que o objeto date nao ¢ alterado e que ¢ retornada uma
nova instancia de DayDate?

Ou indica erroneamente que estamos adicionando cinco dias ao objeto date? Vocé pode achar
que isso nao seja um grande problema, mas um pedago de cédigo parecido com o abaixo pode
enganar bastante [G20].

DayDate date = DateFactory.makeDate(5, Month.DECEMBER, 1952);
date.addpays(7); // pula a data em uma semana.

Alguém lendo esse cddigo muito provavelmente entenderia apenas que addDays est alterando
0 objeto date. Portanto, precisamos de um nome que acabe com essa ambiguidade [N4], Sendo
assim, troquei os nomes de plusDays e plusMonths. Parece-me que a expressdo abaixo indica
bem 0 objetivo do método:

DayDate date = oldDate.plusDays(5);

Por outro lado, a instrugo abaixo nao é 0 suficiente para que o leitor deduza que o objeto date
fora modificado:

date.plusDays(5);

Os algoritmos continuam a ficar mais interessantes, getPreviousvay0fWeek (linhas 628-
660) funciona, mas est4 um pouco complicado, Apés pensar um pouco sobre o que realmente
esté acontecendo [G21]. fui capaz de simplificé-lo ¢ usar as VARIAVEIS TEMPORARIAS276 Capitulo 16: Refatorando o SerialDate

EXPLICATIVAS [G19] para esclarecé-lo. Também a passei de um método estético para a
insténcia de um método [G18], e me livrei das instancias duplicadas [G5] (linhas 997-1008).

 

public LayDate getPrevicusDayOfieek (Day targetDayGfieek
Marget = targetDayOtWeek.index - getDey’

     

 

 

Exatamente 0 mesmo ocorreu com get Fol lowingDay0 fWeek (linhas 662-693).

 

public DayDate getFollow:
int offset ToTarget
1f (offsetToTarget <

DayOtWeek (Lay targetDayOfleek) [
argetDayOfWeek. index - getDayOfWeek(! . index;
a)

 

   

offsetTotarget
return plusDays ioffsetToTerget |;
}

 

 

 

 

Consertamos a funcio seguinte, getNearestDayofweek (linhas 695-726), na pagina 270. Mas
as alteragdes que fiz naquela hora nao eram consistentes com o padriio atual nas duas tltimas
fungdes [G11]. Sendo assim, tomnei-a consistente e usei algumas VARIAVEIS TEMPORARIAS
EXPLICATIVAS [G19] para esclarecer 0 algoritmo.

 

public DayDate getNearestDayOfweek|tinal Day targetDay) [
int offset ToThisWeeksTerget = targetDey.index - getDay0fWeek(] . index:
int offserToFutureTarget = ioffsetToThisWeeksTarget + 7) & 7;
int offsetToFreviousTarget = cffsetToFuturerarget -

  

 

if foffset Torun
return plusD:
else
return plusDays [of fsecToFutureTarget} ;

relarget > 3)
 \offset'ToPrevious‘arget] ;

  

 

O método get Endo £Current Month (linhas 728-740) esta um pouco estranho por sera instancia
de um método que inveja [G14] sua propria classe ao receber um parametro DayDate. Tomei-o
a instancia de um método real ¢ esclareci alguns nomes.

 

public DayDate getEndofMonth!) (

Month month = getMonth|);

int year = getYear();

int lascDay = lastDayofMonth(nonth, year) ;

return DayDatePactory.makeDate(lastDay, month,

b

De fato, refatorar weekInMonthToString (lines 742-761) acabou sendo bem interessante.
Usando as ferramentas de refatorago de minha IDE, primeiro movi o método para o enum
WeekInMonth que criei na pagina 275. Entdo, renomeei o método para toString. Em seguida,
alterei-o de um método estitico para a instancia de um método. Todos os testes ainda passavam
ial Seles: UK Sibi 4 ada Seen.

 

 

ar);Primeiro, faga-a funcionar 277

Depois exclui o metodo inteiro! Cinco testes de confirmagio falharam (linhas 411-415, Listagem
B.4, pagina 374). Alterei essas linhas para usarem os nomes dos enumeradores (FIRST, SECOND,
). Todos os testes passaram. Consegue ver por qué? Consegue ver também por que foi preciso
cada um desses passos? A ferramenta de refatoragao garantiu que todos os chamadores de
weekinMonthToString agora invocassem toString no enum weekInMonth, pois todos os
enumeradores implementam toString para simplesmente retornarem seus nomes...
Infelizmente, fui esperto demais. Por mais elegante que estivesse aquela linda sequéncia de
refatoracdes, finalmente percebi que os tinicos usudrios dessa fungaio cram os testes que eu
acabara de modificar, portanto os exclui
Fnganou-me de novo, tenha vergonha na cara! Enganou-me duas vezes, eu ¢ quem preciso
ter vergonha na cara! Entao. depois de determinar que ninguém além dos testes chamava
relat iveToString (linhas 765-781), simplesmente deletei a fungdo e seus testes.
Finalmente chegamos aos métodos abstratos desta classe abstrata. E 0 primeiro nao poderia ser
mais apropriado: toSerial (linhas 838-844). La na pagina 279, troquei o nome para toOrdinel
Analisando isso com o contexto atual, decidi que o nome deve ser getordinalDay.
O proximo método abstrato € 0 toDate (linhas 838-844), Ele converte uma DayDate para
uma java.util.pate. Por que o método € abstrato? Se olharmos sua implementagao na
sgpreadsheetDate (linhas 198-207, Listagem B-5, pagina 382), vemos que ele néo depende
de nada na implementacao daquela classe [G6]. Portanto, o movi para cima.
Os métodos getYYYY, getMonth e getDayOfMonth esto bem como abstratos. Entretanto, 0
getDayOfWeek € outro que deve ser retirado de spreadsheetDate, pois ele nao depende de
nada que esteja em DayDate [G6]. Ou depende?
Se olhar com atengdo (linha 247, Listagem B-5, pagina 382), vera que o algoritmo implicitamente
depende da origem do dia ordinal (ou seja, do dia da semana do dia 0). Portanto, mesmo que essa
fungdo nfo tenha dependéncias fisicas que possam ser movidas para DayDate, ela possui uma
dependéncia ldgica
Dependéncias légicas como essame incomodam [G22]. Se algo logico depende daimplementagio,
entdo algo fisico também depende. Ademais, parece-me que o proprio algoritmo poderia ser
genérico com uma parte muito menor de si dependendo da implementagao [G6].
Sendo assim, criei um método abstrato getDayOfWeekForOrdinalZero em DayDate
e 0 implementei em SpreadsheetDate para retormar Day. SATURDAY. Entéo, subi o
método getDayOfieek para a DayDate e 0 alterei para chamar getOrdinalDay ¢
getDayOivieekFor0rdinal Zero.

 

 

 

public Day getDayOfWeek() {
Day starting getDayOiWeckForOrdin:
inl startingOffset = startingDay.index
return bay nake( (qetOrdinalDay() + startingOffseti ¢ 7 + Tz

    

}

 

 

 

Como uma observagao, olhe atentamente o comentario da linha 895 até a 899. Essa repeticao cra
realmente necessaria? Como de costume, exclui esse comentario juntamente com todos os outros.
O préximo método € 0 compare (linhas 902-913). Novamente, ele nao esta adequadamente
abstrato [G6], de modo que subi sua implementago para DayDate. Ademais, o nome nao diz
muito [N1]. Esse método realmente retorna a diferenga em dias desde o passado por parametro.

You might also like