You are on page 1of 52
Refatoracao Aperfeicoando o design de codigos existentes Segunda Edicao Martin Fowler com contribuigées de Kent Beck yyAddison-Wesley Novatec ‘Authorized ranlation rom theEnglishlangeagecditon, entitled REFACTORING: IMPROVING THEDESIGN OF EXISTING CODE, 2nd Edition by MARTIN FOWLER, published by Penson Education, Ine, publishing 2s Addison-Wesley Professional, Copytight 8 2019 by Parson Edenton, Ine Allright serve, No pat of this book may be reproduced or transmitted in any form or by any means lee ‘ronieor mechanics, nckcing photocopying, recordingor by anyinformation sarge retrieval yer, Wathou permission from Pessson Edvestion, Ine PORTUGUESE language edition published by NOVATEC EDITORA, LTDA,, Copyright © 2018, “Tradupio autorzada da edigio original em inglés, intitulads REFACTORING: IMPROVING THE DESIGN OF EXISTING CODE, 2nd Eciton by MARTIN FOWLER, pblieada pela Person Education, Ine, peblieand ‘como Addison Wesley Professional, Copyright ® 2019 por Pearson Edtaton, Inc Todos os direitos reservados. Nena parte deste lvro pode ser reproduria ou transmitida por qualquer forma ou meo,elettnica ou mecinice, incuindo foroeépia gravagio ou qualquer sistema de armazenamento {Se iformagio, em 1 prmistio da Pearson Eacation, Ine Eeigio em Portugués publicnda pela NOVATEC, EDITORALTDA., Copyright © 2018, Editor: Rubens Prater “raduguo:Licia A. Kinoshita Revisto gramatica Tssia Carvalho Edioragde eletonica: Carolina Kusvabata ISBN: 978-85:7502-728-4 isrico de impresses Abxil/2020 Primers edigdo ovate Editora Lada Rus Luis Anténio doe Ssates 110 (02460-000~Sio Palo, SP Brash, ‘el. 655 112959-6528 Ema: novatee@aovate.com br Site: wwwinovateecombr “Twiter: twitter com/aovarecedtors Facebook: facebook-com/novatec LinkedIn: linkedin. comsin/novatee Fc20200420, 18 capituto 1 Refatoracao: primeiro exemplo Como comesar a falar de refatoragio? O modo tradicional seria apresentar o hist6- rico sobre o assunto, os prineipios basicos ¢ informagoes desse tipo. Quando alguém faz isso em uma conferéncia, fico um pouco sonolento. Minha mente comeca a divagar, ¢ mantenho um processo de baixa prioridade em segundo plano fazendo polling no palestrante até que um exemplo seja apresentado. ‘Os exemplos me acordam, pois posso ver o que est acontecendo. Com os prin- cipios, é muito facil fazer generalizacées amplas ~ ¢ é muito dificil saber como apli- célos. Um exemplo ajuda a deixar tudo claro. Assim, darei inicio a este livro com um exemplo de refatoragio. Explicarei como a refatoragio funciona ¢ darei a vocé uma nogio sobre o processo de refatoracio, Pode rei entio fazer a introdugao costumeita, no estilo dos principios, no préximo capitulo, Com um exemplo introdutério, porém, vejo-me diante de um problema. Se esco- Iho um programa longo € 0 descrevo, mostrando como ele é fatorado, seré com- plicado demais para um leitor mortal me acompanhar. (Tentel fazer isso no livro original —e acabei jogando fora dois exemplos que, apesar de serem bem pequenos, ocupavam umas cem paginas cada um para serem descritos) No entanto, se es¢o- her um programa suficientemente pequeno para que seja compreensivel, eremos a impressio de que a refatoragao nao vale a pena. Desse modo, encontro-me no clissico dilema de qualquer pessoa que queira descrever téenicas titeis para os programas do mundo real. Falando francameate, 0 esforgo para fazer toda a refatorag3o que mostrarei a vooé no pequeno programa que usaremos nao compensa. Contudo, se o cédigo que sera apresentado fizer parte de uum sistema maior, a refatoragdo ser importante. Olhe para meu exemplo e imagi- rne-o no contexto de um sistema muito maior. Ponto de partida Na primeira edigio deste livro, meu programa inicial exibia uma conta de uma videolocadora, e isso, atualmente, poderia levar muitos de vocés a perguntar: "O que Powro pe eaxtipa, € uma videolocadora?”: Em vez de responder a essa pergunta, substitui 0 exemplo por algo mais antigo, porém, ao mesmo tempo, atual Pense em uma companhia de atores de teatro que saia para participar de virios eventos apresentando suas pegas. Em geral, os clientes solicitarao algumas pegas e a companhia cobrard deles com base no ntimero de espectadores € no tipo de peca encenada, Atualmente hé dois tipos de pegas que a companhia apresenta: tragédias © comédias. Além de apresentar uma conta pela apresentagéo, a companhia dé ditos por volume” aos seus clientes, os quais podem ser usados como descontos em futuras apresentages — pense nisso como um mecanismo de fidelizacio do cliente Os atores armazenam dados sobre suas pegas em um arquivo JSON simples semelhante a este: plays,json. { “hamlet: (ane "as-Like": {"nane” ‘othello": ("nane": "Othello", "type y Os dados para as contas também estio em um arquivo JSON. l £ performances! playlO" hence": $5 *playlD": *as-Like", "audience": 35 h *playlD": “othello", "audience": 48 20. Carfruro 1 Reraroragao: PRIMEIRO EXEMPLO © cédigo que exibe a conta est na fungio simples a seguir: Function statenent (invoice, plays) { let totalanount = 8; let volunecredits = 6; let result = *Statenent for ${invoice.custoner}\n"; const fornat = new Intl. unberFormat("en-US", { style: "currency", currency: "USD", inininunFractionbigits: 2 }).formati for (let perf of invoice.perfornances) { const play = plays[perf.playl0]; Tet thistmount = 8; switch (play. type) { case "tragedy": thistnount = 480095; af (perF.audtence > 30) { thistount 1 1898 * (perf.audience - 38); y breaks case “conedy": thisAnount = 300805 Af (perF.audtence > 28) { thistount += 19988 + 589 * (perf.audience - 28); y ‘thisanount +2 389 * perf audience; breab efaut throw new Error(unknown type: ${play.type}");, } I/ sora créditos por volune voluneCredits +: Hath.nax(perf.audience ~ 38, 8); J/ sora un crédito extra para cada dez espectadores de conédia iF (“conedy" === play.type) volunetredits += Math.floor(perF.audtence / 5); I/ exibe @ Links para esta requisicéo result += ° S{play.nane}: ${fornat(thistrount/108)} (${verF.audience} seats)\n"; totalanount 42 thisAnount; ComENTARIOS SOBRE © PROGRAMA INICIAL result result ‘mount oned is ${Fornat(totalanount 186) }\n°s “You earned ${voluneCredits} credits\n"; return results y Acxecugio desse cédigo nos arquivos de dados de teste anteriores resulta na seguinte sada: Statenent for BigCo Hamlet: $658.00 (55 seats) ‘As You Like It: $580.80 (35 seats) OtheLlo: $580.69 (40 seats) Anount owed is $1,730.09 You earned 47 credits Comentarios sobre o programa inicial Quais sao suas ideias acerca do design desse programa? A primeira afirmagéo que eu faria € que ele € tolerdvel como est — um programa tio pequeno que nio exige nenhuma estrutura profunda para ser compreensivel. Lembre-se, porém, do que eu disse antes sobre a nevessidade de manter os exemplos concisos. Pense nesse pro- _grama em uma escala maior ~ talvez com centenas de linhas de extensio, Com esse tamanho, uma Ginica fungao inline seria dificil de entender. Considerando que programa funciona, qualquer afirmagio sobre a sua estru- tura nao seria apenas um julgamento estético, ou uma demonstrago de desgosto, por um cédigo “ieio”? Afinal de contas, o compilador nao se importa se o codigo € feio ou claro, Todavia, se altero o sistema, hé um ser humano envolvido, ¢ os seres humanos se importam. Um sistema com design ruim € dificil de ser alterado ~ porque é dificil identificar 0 que deve ser modificado € como essas modificagbes interagirao com o cédigo existente para termos o comportamento desejado. E se fox dificil identificar © que deve ser alterado, hé uma boa chance de que vou comever cerros e introduzir bugs. Desse modo, se eu tiver de modificar um programa com centenas de linhas de cédigo, preferiria que cle estivesse estruturado na forma de um conjunto de fungdes € de outros elementos de progeama que me permitissem compreender mais facilmente © que o programa faz. Se o programa nao estiver estruturado, em geral ser mais facil para mim conferir-Ihe uma estrutura antes, ¢ ento fazer a alteragao necessétia, sply_ Se voct tiver de acrescentar uma funcionalidade em um programa, mas 0 AQo codigo nao esta estruturado de modo conveniente, refatore-o antes para que seja mais facil acrescentar a funcionalidade, e entdo a acrescente. Nese exemplo, tenho duas modificagdes que os usustios gostariam de fazer: Em primeiro lugar, cles querem que o demonstrativo seja exibido em HTML. Considere au 2 Cariruco 1 # ReeatoRagho: PRIMEIRO EXEMPLO © impacto que essa modificacio teria, Estou diante de uma situagéo que exigiria colocar instrugdes condicionais extras em torno de cada instrugao que acrescente uma string no resultado. Isso adicionaré uma série de complexidades & fungio. Diante disso, a maioria das pessoas preferirs copiar o método e alters-lo para que ere HTML. Fazer uma cépia nao parece uma tarefa muito custosa, mas criaré todo tipo de problemas no futuro. Qualquer mudanga na ligica de cobranga me forgaria, a atualizar os dois métodos ~e garantir que sejam atualizados de forma consistente Se eu estivesse escrevendo um programa que nao mudasse nunca, esse tipo de ope- ragio de copiar e colar nao seria um problema. Porém, se for um programa com vida, ‘itil longa, a duplicagdo sers entao uma ameaga, Isso me remete & segunda modificacio, Os atores querem encenar outros tipos de pegas: eles esperam acrescentar os estilos histérico, pastoral, pastoral-cémico, histé- Hco-pastoral, tsigico-historico, trégico-cémico-hist6rico-pastoral, cenas indivisiveis € poema ilimitado ao seu repertério. Eles ainda nao decidiram exatamente o que que- rem fazer nem quando, Essa mudanga afetard tanto 0 modo como suas pecas serio cobradas quanto a forma de calcular os créditos por volume. Como desenvolvedor cexperiente, posso garansir que, qualquer que seja 0 esquema concebido, eles o modifi- caro novamente no periodo de seis meses. Afinal de contas, quando chegam requisi- es por funcionalidades, elas nao chegam como espides solitarios, mas em batalhdes. Novamente, € naquele método statenent em que as alteragdes devem ser feitas para lidar com mudancas nas regras de classificacio ¢ de cobranca. No entanto, se €u copiasse statenent para htnlStatenent, seria necessério garantir que qualquer modificagio fosse consistente. Alem do mais, 4 medida que as regras se tornarem, mais complexas, ser mais dificil identificar os locais em que as modificagdes deve, ser feitas, e mais dificil efetué-las sem cometer um erro. Deixe-me enfatizar que so essas mudangas que determinam a necessidade de fazer uma refatoragio. Se o cédigo estiver funcionando e nao tiver de ser alterado, deixé-lo como est nao € um problema, Seria bom aperfeigoé-lo, mas, a menos que alguém precise entendé-lo, ele nio estaré causando nenhum dano real. Contudo, assim que alguém precisar entender como esse c6digo funciona ¢ tiver dificuldade ppara saber o que ele faz, sera necessério tomar alguma atitude a respeito. Primeiro passo na refatoracao ‘Toda vez que faco uma refatoracio, o primeiro passo é sempre o mesmo. Devo garan- tir que tenho um conjunto robusto de testes para essa segao de eédigo, Os testes S30, essenciais porque, apesar de fazer uma refatoragdo estruturada a fim de evitar a maior parte das oportunidades para intcodugio de bugs, ainda sou um ser humano ‘© cometo erros. Quanto maior o programa, mais provavel seré que minhas altera- ges, inadvertidamente, fagam algo deixar de funcionar na era digital, o nome para a fragilidade é software. Dzcompoxpo a FUNGAO STATEMENT ‘Como statenent devolve uma string, o que faco é criar algumas faturas, associo a cada uma delas algumas apresentagies de varios tipos de pegas de teatto e gero as strings do demonstrativo, Faso entio uma comparagao de strings entre a nova string fe algumas strings de referéncia verificadas manualmente. Crio todos esses testes usando um framework de testes para que eu possa executé-los somente pressio- nando uma tecla em meu ambiente de desenvolvimento, Os testes demoram apenas alguns segundos para executar e, como voc? ver4, eu os executo com frequéncia. ‘Uma parte importante dos testes ¢ 0 modo como eles apresentam os resultados Sio exibidos com verde, o que significa que todas as strings sao idénticas as strings de referéncia, ou com vermelho, mostrando uma lista de falhas — as linhas cujos resultados foram diferentes. Os testes, portanto, sio conferidos automaticamente. E, essencial criar testes que sejam conferidos automaticamente, Se eu nao fizesse isso, acabaria gastando tempo para verificar manualmente os valores dos testes, com- parando-os com valores anotados em um bloquinho, ¢ isso me causaria atrasos. Frameworks de teste modernos oferecem todas as funcionalidades necessérias para escrever € executar testes conferidos automaticamente 1 Antes de comecar a refatorar, certifique-se de que vacé tenha um conjunto As de testes robusto. Esses testes devem ser conferidos automaticamente. A medida que fizer a refatoracdo, contarei com os testes. Penso neles como um detector de bugs para me proteger contra meus préprios erros. Ao escrever 0 que ‘quero duas vezes, no cédigo ¢ no teste, eu teria de cometer o erro de forma consis- tente nos dois lugares para enganar o detector. Ao conferir meu trabalho duas vezes, reduzo as chances de fazer algo errado, Embora criar testes exija tempo, acabo eco- nomizando esse tempo, com juros consideraveis, ao gastar menos tempo na depu- ragio, Essa é uma parte tio importante da refatoracio que dedicarei um capitulo inteiro a ela (Capitulo 4, Escrevendo testes) Decompondo a fungao statement ‘Ao refacorar uma fungio longa como essa, tento identificar mentalmente os pontos ‘que separam diferentes partes do comportamento geral. A primeira porgdo que me salta aos olhos é a instrugo switch no meio function statenent (invotce, plays) { let totalanount = 6; let volunecredits = 3 let result = *Statenent for §finvoice.custoner}\n"s const fornat = new IntL.AunberFormat( "en-US", { style: "currency", currency: "USD", rnininunFractionDigits: 2 }). format; 2B 4 Cariruco 1 # ReeatoRagho: PRIMEIRO EXEMPLO for (let perf of invoice.perfornances) { const play = plays[perf.playl0]; Let thistrount switch (play.type) { case "tragedy": thistnount = 48688; af (perf.audtence > 30) { thistount 4= 1996 * (perf.eudience - 38); y break case “cones thisAnount = 300605 af (perF.audtence > 20) { thistount += 19988 + 589 * (perf.audtence - 28); y ‘thisAnount +2 389 * perf audience; break efaut throw new Error(unknown ty + $(play. type} ): } I/ sora crédites por volune voluneCredits += Hath.nan(perf audience ~ 38, 6); J sora un crédito extra para cada dez espectadores de conédia Af ("conedy" =5= play.type) voluneCredits += Math.flor(perf.audtence / 5); I/ exibe 2 Linha para esta requisiéo result += ° ${play.nane): ${fornat(thistnount/100)} (S{perF.audtence} seats)\n"; totaldnount +2 thisArount; } result 4 “Anount oved is ${Fornat(totalarount/196)}\n"s result + “You earned S{voluneCredits) credits\n"; return results y Enquanto observo essa parte, concluo que ela calcula o valor cobrado para uma apresentagio, Essa conclusio é um insight sobre o cédigo, Porém, como afirma Ward Cunningham, essa compreensio esti em minha mente — uma forma de armazena- gem reconhecidamente volatil. enho de persist-la, passando-a de minha mente de volta para o cédigo. Desse modo, caso eu retome ao cédigo mais tarde, ele me ira © que esti fazendo~ nio terci de descobrir novamente, Dzcompoxpo a FUNGAO STATEMENT © modo de colocar essa compreensio no cédigo é transformar essa porcio de cédigo em uma fungao prépria, nomeando-a com base no que ela faz algo como ‘anountFor(aPerfornance). Quando quero transformar uma porcéo de cédigo em uma fungao dessa maneira, tenho um procedimento para isso que minimiza as chances de fazer algo errado, Escrevi esse procedimento, ¢, para que fosse mais fécil referen- cié-lo, chamei-o de Extrairfuncdo (Extract Function) (134), Em primeiro lugar, preciso observar o fragmento em busca de qualquer varidvel ‘que nao estar mais no escopo depois que eu tiver extraido o eédigo em sua propria fungio. Nesse caso, tenho tés varidveis: perf, play ¢ thistnount. As duas primeiras sio usadas pelo cddigo extraido, mas nao s40 modificadas, portanto posso passa- -las como parémetros. Varidveis modificadas exigem mais atengao. Nesse caso, ha apenas tuma, portanto posso devolvé-la, Também posso levar sua inicializagio para dentro do cédigo extraida, Tudo isso resulta no cédigo a seguir: function statement. Function anountFor(perf, play) { let thisanount = 9; switch (play.type) { case "tragedy thistnount = 40960; Af (perf.audience > 38) { thisAnount +2 1090 * (perf.audience - 36); } breaks case "comedy": thistrount = 36880; Af (perf.audience > 28) { ‘thisAnount +5 10988 + 580 * (perf audtence - 28); y thistrount += 308 * perf audience; break; default: ‘throw new Ercor(unknown type: ${play.type)"); d return thistnount; } Quando uso um cabegalho como “function algumNome...”” em itélico em algum cédigo, significa que o cédigo que se segue est dentro do escopo da funsio, do arquivo ou da classe cujo nome esta no cabegalho. Em geral, havera mais e&digo nesse , mas ele no sera mostrado, pois nio estar sendo discutido na ocasiio. 25 26 Cariruco 1 # ReeatoRagho: PRIMEIRO EXEMPLO © cédigo original de staterent agora chama essa fung3o para atribuir valor a thistnount nivel mais alto, Function statement (invotce, plays) { let totalanou let volunecredits let result const fornat = new IntL.AunberFormat("en-US", { style: "currency", currency: "USD", nnininunFractionDigits: 2 }).format; for (let perf of invoice.perfornances) { const play = plays{perf.playlD]; let thistmount = arountFor(serf,, play): 8 “statenent for ${invoice.custoner}\n"; I/ sora créditos por volune voluneCredits += Hath.nax(perf.audience - 38, 0); I/ sora un crédito extra para cada dez espectadores de conédia ‘if (*conedy® === play. type) voluneCredits += Math.floor(perf audience / 5); I/ exibe 2 Vinha para esta requisicéo result += ° S{play.nane): ${Fornat(thistmaunt/198)) (S{perF.audtence) seats)\n°s totalénount += thisAnount; ) result += “Anount oved is ${fornat(totalanount/106)}\n"; result += "You earned ${voluneCredits) credits\n"; return result; Depois de fazer essa alteragio, compilo testo imediatamente para ver se causei alguma falha. Testar apés cada refatoragio é um habito importante, embora simples. Erros sio ficeis de cometer — pelo menos, acho que sio. Testar depois de cada altera- io significa que, quando cometo um erro, terei apenas uma pequena modificagio a ser considerada a fim de identificar 0 erro, fazendo com que seja muito mais fil localiza-lo e corrigi-lo, Essa é a esséncia do processo de refatoragio: pequenas modi- ficagdes testes depois de cada modificagao.Se eu tentar fazer muitas delas, cometer um erro me forgara a um episédio complexo de depuracio que poder consumir bastante tempo. Pequenas modificagdes, que permitam um ciclo de feedback répido, sio o segredo para evitar essa confusio. Quando uso compilar, queto dizer fazer 0 que for necessirio para deixar o JavaScript executivel, Como JavaScript é diretamente executavel, isso pode significar nao fazer nada; em outros casos, porém, pode ser mover o cédigo para um diret6rio de saida e/ou usar um processador como o Babel [babel] Dzcompoxpo a FUNGAO STATEMENT 0 altera os programas em passos pequenos, de modo que, se 1 um erro, seré facil localiza o bug. Por ser um cédigo JavaScript, posso extrair anountFor e colocé-lo em uma fungio ani- hada de statenent. E conveniente, pois significa que nao preciso passar dados que estZo no escopo da fungio que a contém para a fungio que acabou de ser extraida. esse exemplo, isso nao lard diferenga, mas € um problema a menos para tratar. Emi nosso caso, os testes passaram, portanto, meu préximo passo é fazer com- mit da alteragdo em meu sistema local de controle de vers6es. Uso um sistema de controle de verses, por exemplo, git ou mercurial, que me permita fazer commits privados. Fago commit depois de cada refatoragao bem-sucedida, de modo que eu possa retornar facilmente a um estado funcional caso me atrapalhe no futuro. Entao, retino as alteragdes em commits mais significativos antes de envid-las para ‘um repositério compartilhado. Extrair fungao (Extract Function) (134) € uma refatoragio comum para ser auto- matizada, Se eu estivesse programando em Java, teria instintivamente usado a sequéncia de teclas em meu IDE para fazer essa refatoragao, Quando eserevi este livro, ndo havia um suporte t2o robusto para essa refatorago nas ferramentas, JavaScript, portanto tive de fazer isso manualmente. Nao ¢ dificil, embora eu precise ter cuidado com as variaveis de escopo local. Depois de ter usado Extrair funcao (Extract Function) (134), observo 0 cédigo que extraf para ver se hi algo répido e facil que eu possa fazer a fim de dar mais clareza a fungio extraida, Minha primeira tarefa é renomear algumas das variéveis para deixi-las mais claras, por exemplo, alterar thisAnount para result, function statement. function anountFor(perf, play) { let result = 0; switch (play.type) { case "tragedy" result = 48009; ‘Af (perf audience > 38) { result + 1000 * (perf.audience - 38); } break; case "comedy" result = 36009; AF (perf.audience > 28) { result + 10008 + 580 * (perf.udience - 26); } result break; 300 * perf. audience; vy 28 Cariruco 1 # ReeatoRagho: PRIMEIRO EXEMPLO default: ‘throw new Ercor(unknawn type: ${play. type"); d return result; y Chamar sempre 0 valor de retorno de uma fungo de “result” faz parte do meu padro de programagio. Desse modo, sempre saberei qual é o seu papel. Nova- mente, compilo, testo e fago commit, Em seguida, passo para o primeiro argumento. function statement function anountFor(aPerformance, play) { let result = 0; switch (play.type) case "tragedy" result = 48009; ‘f (aPerfornance.audience > 38) result 4 1809 * (aPerfornance.audtence - 38); } break; case "comedy" result = 30000 AF (aPerfornance. audience > 28) result + 10080 + 589 * (aPerformance.auiience - 20); } result breaks default: ‘throw new Error("unknown type: S{play.type}"); 300 * aPerfornance. audience; } return results } Mais uma vez, estamos seguindo meu estilo de programacio, Com uma linguagem, dinamicamente tipada como JavaScript, € conveniente manter o controle dos tipos — desse modo, meu nome default para um pardmetro inclui o nome do tipo. Uso um artigo indefinido para ele, a menos que haja alguma informagio especifica sobre 0 seu papel que deva ser incluida no nome. Aprendi essa convencéo com Kent Beck [Beck SBPP] e continuo achando-a itil Qualquer tolo consegue escrever cédigos que um computador possa entender. Bons programadores escrevem cédigos que os eres humanos podem entender. Dzcompoxpo a FUNGAO STATEMENT esforgo de renomear varidveis vale a pena? Com certeza. Um bom cédigo deve comunicar claramente 0 que faz, e nomes de varidveis sao essenciais para a clareza de um cédigo, Nunca tenha medo de mudar nomes para ter mais clareza. Com boas ferramentas para localizar e substituir, em geral isso nao ser4 dificil; testes e ipagem estitica em uma linguagem que a aceita dardo destaque a qualquer ocorréncia que vocé tenha deixado passat. Além disso, com ferramentas automatizadas de refatora- ‘40, € trivial renomear até mesmo funcées amplamente usadas. (© proximo itema ser considerado para renomear ¢ 0 parimetto play, mas reservo um destino diferente para ele. Removendo a variavel play Enquanto considero os parimetros de anountFor, observo de onde cles vém. aPerfornance € proveniente da variavel do lago, portanto mudard naturalmente a cada iteracio. Entretanto, play € obtido da apresentacio, portanto nao € necessario passé-lo como parimetro — posso simplesmente o recalcular em anountFor. Quando divido uma funcio longa, gosto de me livrar de variaveis como play, pois variéveis temporétias criam muitos nomes com escopo local, complicando as extragbes. A refatoragdo que usarei nesse caso é Substitir varidvel tempordvia por consulta (Replace Temp with Query) (207) ‘Comego extzaindo o lado dircito da atribuicio, colocando-o em uma funao. function statement. Function playFor(aPerfornance) { return plays[aPerfornance.playl0}; y nivel mais alto, Function statenent (invoice, plays) { let totalanoun let volunecredits let result = *Statenent for ${invoice.custoner}\n"; const fornat = new IntL.AunberFormat( "en-US" { style: "currency", currency: "USD", nnininunFractionDigits: 2 }). format; for (let perf of inoice.perfornances) { const play = playFor(perf); et thistrount = anountFor(perf,, play); I{ sona créditos por volume voluneCredits += Hath.nax(perf.audience - 38, 6); 29 30 Cariruro 1 * ReeatoRagho: PRIMEIRO EXEMPLO| I/ sone un crédito extra para cada dez espectadores de conédia ‘if (*conedy® === play.type) voluneCredits += Math.floor(perf audience / 5); I/ exibe 2 Vinha para esta requisicéo result += ° ${play.nane): ${fornat(thisArount/188)} (S{perF.2udtence} seats)\n"s totalénount += thisAnount; ) ‘nount oxed is ${fornat(totalarount/196)}\n°; result += "You earned ${voluneCredits) credits\n"; return result; Compilo-testo-fago commit, e entio uso Internalizar varidvel (Inline Variable) (152) nivel mais alto Function st renent (invoice, plays) { let totalarount = 8; 8; let result = *Statenent for §{invotce.custoner}\n"; const fornat = new IntL.unberFormat("en-US", { style: "currency", currency: "USD", rnininunFractionDigits: 2 }).format; let volunecredits for (let perf of invoice.perfornances) { conet_play = playtertperts Tet thistnount = anountFor(perf, playFor(perf)); I/ sora créditos por volune voluneCredits += Hath.max(perf.audtence ~ 38, @); I/ sora un crédito extra para cada dez espectadores de conédia ‘if ("canedy" == playFor(pert).type) voluneCredits += Hath. floor (perf. audience / 5); I/ exibe 2 Linha para esta requisicéo result +=" §{playfer(perf).nane}: S{Format(thistoount/180)} (S{perf.audience) seats)\n"s totalénount += thisAnount; } ‘nount oxed is ${fornat(totalarount/198) }\n°; result 4 "You earned S{voluneCredits) credits\n"; return result; Compilo-testo-fago commit. Com esse cédigo internalizado, posso entio aplicar ‘Mudar declaracao de funcdo (Change Function Declaration) (153) em arountFor para remover o parametto play. Fago isso em dois passos. Em primeito lugar, uso a nova fungdo em anountFor. Dzcompoxpo a fungao stareMenT 31 function statement. Function anountFor(aPerformance, play) { let result = 0; switch (playFor(aPerfornance).type) { case "tragedy" result = 48069; ‘f (aPerfornance.audience > 30) { result += 1988 * (aPerfornance. audience - 38); } breaks case "comedy" result = 36088; ‘if (aPerfornance.audience > 20) { result += 19888 + $08 * (aPerformance.audience - 28); } result += 308 * aPerfornance.audlence; break; default: ‘throw new Error("unknown type: ${playFor(aPer formance) type}"); } return results y Compilo-testo-fago commit, ¢ entio removo o parimetro nivel mais alto, function statenent (invotce, plays) { let totalanount let volunecredits = @; let result = *Statenent for ${invoice.custoner}\n"s const fornat = new IntL.AunberFormat( "en-US", { style: "currency", currency: rnininunFractionDigits: 2 }) for (let perf of invoice.perfornances) { let thistnount = anountFor(perfplayterteees}); "usp", ormat; I/ sora crédites por volune volureCredits += Hath.nax(perf audience ~ 38, 6); J/ sora un crédito extra para cada dez espectadores de conédia AF ("canedy" = playFor(perf).type) volunetredits += Math. Foor (perf. audience / 5); 32 Cariruro 1 * ReeatoRagho: PRIMEIRO EXEMPLO| I[ exibe 2 Uinha para esta requisicéo result += ° §{playfor(perf).nane}: ${Format(thistoount/:60)} (S(perf.audience) seats)\n"; totalimount +2 thisArount y result += “Amount oved is ${Fornat(totalarount/196)}\n°s result + "You earned S{voluneCredits) credits\n"; return results function statement. Function anountFor(aPerformance,-piay) { let result = 0; switch (playFor(aPerfornance).type) { case "tragedy* result = 48089; f (aPerfornance.audience > 30) { result += 1008 * (aPerfornance. audience - 30); } break; case "conedy" result = 36088; “if (aPerfornance.audience > 20) { result += 19888 + 568 * (aPerFormance.audience - 28); } result += 368 * aPerfornance audience; break; default: ‘throw new Error("unknown type: ${playFor(aPer formance). type}"); } return results y E compilo-testo-fago commit novamente. ssa refatoragio deixa alguns programadores alarmados. Anteriormente, 0 ceédigo para procurar a pega era executado uma vez a cada iterago do lago; agora le € executado tés vezes, Falarei sobre a inter-relagao entre refatoragio e desempe- ho mais tarde; por enquanto, porém, direi apenas que € pouco provavel que essa modificagio afete significativamente o desempenho, ¢, mesmo que o fizesse, € muito mais cil melhorar o desempenho de uma base de eédigo bem fatorada. A grande vantagem de remover varidveis locais € que isso facilita muito as extra~ es, pois havers menos escopo local com o quial lidar. Na verdade, eu geralmente removo as varidveis locais antes de fazer qualquer extracio. Decomroxpo a ruNga0 sraTeMeNT 33 Agora que jé cuidei dos argumentos de anauntFor, wolto a observar o local em que essa fungi ¢ chamada, Ela esté sendo usada para definir wma varivel cemporstia que indo é atualizada novamente, portanto aplico Internalizar varavel (line Variable) (152) nivel mais alto, Function statenent (invoice, plays) { let totalarount = 8; let voluneCredits = 6; let result = “Statenent for S(inoice.custoner}\n"; const fornat = new IntL.AunberFornat("en-US", { style: "currency", currency: "USD", rnininunFractionDigits: 2 }).format; for (let perf of invotce.perfornances) { II sora créditos por volune voluneCredits += Hath.max(perf.audtence - 38, 0); I/ sora un crédito extra para cada dez espectadores de conédia ‘f ("camedy" — playfor(per#).type) voluneCradits += Hath. floor (perf. audience / 5); I/ exibe 2 Linha para esta requisicéo result + * S{olyFor(perf).nare}: ${Forat(=runtor(perf)/10)} (Sfperfaucence)seats)\n"s totalénount += anountFor (pe } result += “Anount oved is ${fornat(totalAnount/196)}\n"; result 4 "You earned S{volenecredits) credits\n's return result; Extraindo créditos por volume Eis o estado atual do corpo da fun nivel mais alto. function statenent (invotce, plays) { let totalarount let volurecredits = @; let result = “Statenent for ${invoice.custoner}\n"s const fornat = new IntL.NunberFormat( "en-US", { style: "currency", currency: nnininunFractionDigits: 2 }) "Usp", Format; 34 Cariruto 1 # ReeatoRagho: PRIMEIRO EXEMPLO| for (let perf of invoice.perfornances) { I/ sora créditos por volume voluneCredits += Hath.nax(perf.audience - 38, 0); I/ sora un crédito extra para cada dez espectadores de conédia ‘if (*conedy" === playFor(perf).type) voluneCredits += Hath.flor(perf.audtence / 5); I[ exibe 2 Linha para esta requisicéo result + ° (playfor(per).nane}: ${Forat{ereuntFor(perf)/186)) (Sfperfsuderce} seats)\n"s totalénount += anountFor (pe: ) result += “Anount oved is ${fornat(totalanount/196)}\n°; result += "You earned S{volunecredits) credits\n's return result “Tenho agora a vantagem de ter removido a variavel play, pois facilita extrair 0 céleulo dos eréditos por volume por causa da remogao de uma das variéveis com escopo local ‘Ainda tenho de lidar com as outras duas varidveis. Novamente, perf € fécil de passar, porém voluneCredits & um pouco mais complicado, pois € um acumulador atualizado a cada passagem pelo laco. Assim, minha melhor aposta ¢ inicializar uma sombra dela na fungio extraida e devolvé-la function statement Function volunecreditsFor(perf) { let voluneCredits = 6; volunetredits += Math.nax(perf.audtence - 38, 8); 4F ("conedy" === playFor(perf).type) voluneCreditts += Hath. Foor(perf. audience / 5); return volunecredits; ¥ nivel mais alto. Function statenent (invoice, plays) { let totalarount = 8; let volunecredits = 6; let result = *Statenent for ${invoice.custoner}\n"s const fornat = new IntL.NunberFormat( "en-US", { style: "currency", currency: "US", nnininunFractionDigits: 2 }).format; for (let perf of invoice.perfornances) { voluneCredits += voluneCreditsFor (perf); Decompoxpo a FuNGAO STATEMENT — 35 I[ exibe 2 Linha para esta requisicéo result += * S{olayFor(perf).nane}: {forrat(anountFor(perF)/100)} (${perF.audtence} seats)\n"; ‘totalénount += anountFor(oer#); , result + “Anount oved is ${fornat(totalanount/106)}\n°; result += "You earned ${voluneCredits) credits\n"; return result; Removo o comentirio desnecessario (¢, nesse caso, certamente enganador). ‘Compilo-testo-fago commit desse cédigo, e entio renomeio as variveis na nova funcio. function statement. function voluneCreditsFor(ePerfornance) { let result = 0; result += Math.max(aPerfornance.audtence - 38, 0); if ("conedy" === playFor(aPerfornance).type) result += Nath. floor (aPerfornance. audience / 5); return results y Mostrei tudo cm um s6 passo, mas, como antes, renomeei as varidveis uma de cada ve7, fazendo uma compilagio-teste-commit a cada vez Removendo a variavel format ‘Vamos observar o método principal statenent novamente: nivel mais alto function statenent (invoice, plays) { let totalarount = 85 let volunecredits = 9; let result = *Statenent for §{invoice.custoner}\n"; const fornat = new IntL.AunberFormat("en-US", { style: "currency", currency: "USD", nnininunFractionDigits: 2 }).fornat; for (let perf of invoice.perfornances) { voluneCredits += voluneCreditsFor (perf); I/ exibe 2 Vinha para esta requisicéo result += ° ${playFor(perf) name): 36 Cariruco 1 # ReeatoRagho: PRIMEIRO EXEMPLO ${Format(enountFor (perF)/108)} (S{perF audience} seats)\n°; ‘totalénount += anountFor(oer#); d result += “Anount oved is ${fornat(totalanount/106)}\n°; result 4= "You earned ${voluneCredits) credits\n"s return result; Conforme sugeri antes, variveis temporarias podem ser um problema. Elas sio Aiteis somente em sua propria rotina ¢, desse modo, incentivam rotinas longas € complexas. Meu préximo passo, entio, é substituir algumas delas. A mais ficil € format. Esse € um caso de atribuigio de uma fungio a uma varidvel temporaria, a qual prefiro substituir por uma fungio declarada, function statement. Function fornat(aNunber) { return new Intl.NunberFornat( "en-US", { style: "currency", currency: "USD", nininunfractiondigits: 2 }).format(aNurber); ¥ nivel mais alto, function statenent (invotce, plays) { let totalanount let volunecredits = 9; let result = *Statenent for ${invotce.custoner}\n"s for (let perf of invoice.perfornances) { voluneCredits += voluneCreditsFor(perf); I[ exibe 2 Uinha para esta requisicéo result += * S{olayFor(perf).nane}: S(Format(anountFor (perF)/108)} (S{perF. audience} seats)\n°; ‘totaLénount += anountFor(oer#); d result + “Anount oved is ${fornat(totalanount/106)}\n"; result 4= "You earned ${voluneCredits) credits\n"; return result; Embora alterar uma varivel de fungio para uma fung2o declarada seja uma refato- ragio, no nomeei nem inclui essa refatoragio no catélogo, Ha muitas refatorages que nio achei que fossem suficientemente importantes a esse ponto. Essa é simples de fazer e relativamente rara, portanto nao achei que valesse a pena inclufla Decompoxpo a ruNga0 sraTEMeNT 37 Nao gosto donome—"format” nao comunica realmente o quecla faz. "“formatAsUSD” seria um pouco longo demais, pois € usada em um template de string, particularmente esse escopo pequeno. Acho que o fato de ela estar formatando um valor monetério 0 que deve ser enfatizado nesse caso, portanto, escolhi um nome que sugere isso apliquei Mudar declaragao de funsao (Change Function Declaration) (153) nivel mais alto. Function statenent (invotce, plays) { let totalarount = 8; let volunecredits = 8; let result = “Statenent for ${invotce.custoner}\n"; for (let perf of invoice.perfornances) { voluneCredits += voluneCreditsFor (perf); I[ exibe 2 Linha para esta requisicéo result += * ${playFor(perf) name}: S{usd(anountFor(perf))} (S{perF audience} seats)\n°s totalénaunt += anauntFor (perf); } result + “Anount oved is ${usd{totalarount) }\o°; result += “You earned S{voluneCredits) credits\n"; return result; function statement. Function usd(aNunber) { return new IntL.MunberFornat( "en-US", { style: currency’, currency: "Ust nnininunFractiondigits: 2 }).fornat(aliunber/100); } ‘Nomear € importante, mas é também complicado, Dividir uma fungao grande em fungdes menores sé traré vantagens se os nomes forem bons. Com nomes bons, néo preciso ler o corpo da fungao para ver o que ela faz, Entretanto, dificil eriar nomes apropriados na primeira vez, portanto uso o melhor nome em que puder pensar no momento, € nao hesito em renomear mais tarde. Com frequéncia, uma segunda passagem pelo c6digo ¢ necessaria para perceber qual é realmente o melhor nome. Enquanto modifico o nome, também passo a divisio duplicada por 100 para dentro da fungéo. Armazenar um valor monetdrio como centavos inteiros é uma abordagem comum ~ ela evita os perigos de armazenar valores monetérios fracio- narios como niimeros de ponto flutuante, ao mesmo tempo em que me permite usar operadores aritméticos. Sempre que eu quiser exibir esses mimeros inteiros que representam centavos, porém, preciso de um valor decimal, portanto, minha funcio de formatagio deve cuidar da divisio. 38 Carfruto 1 RevaroragAo: PRIMEIRO EXEMPLO Removendo o total de créditos por volume Minha préxima varidvel visada é voluneCreéits. Esse ¢ um caso mais intrincado, pois, ela € calculada durante as iteragées no lago. Meu primeiro passo, entio, € usar Div dir laco (Split Loop) (257) para separar a acumulagio em voluneCredits. nivel mais alto, Function statenent (tnvotce, plays) { let totalanount let volunecredits = @; let result = *Statenent for §{invoice.custoner}\n"s for (let perf of invoice.perfornances) { I[ exibe 2 Vinha para esta requisicéo result += ° ${playFor(perf) .nane): ${usd(anountFor(perf))} (S{oe" totalAnount += anountFor (pe } for (let perf of invoice.perfornances) { joluneCreditsFor (perf); audience} seats)\0°s voluneCredits } result += “Anount oved is ${usd{totalarount) \o°; result += “You earned S{volunecredits) credits\n"; return result; Depois de fazer isso, posso usar Deslocar instrugdes (Slide Statements) (252) para mover a declaragao da varidvel para perto do lago nivel mais alto. Function statenent (invoice, plays) { let totalanount = 8; let result = *Statenent for ${invoice.custoner}\n"s for (Let perf of invoice.perfornances) { I/ exibe 2 Vinha para esta requisicéo result += ° ${playFor(perf) name}: S{usd(anountror(perf))} (S{perF. audience} seats)\n°; totalénount += anountFor (perf); } Decompoxpo a FUNGAO STATEMENT 39 let voluneCredits = 8; for (Let perf of invoice.perfornances) { voluneCredits += voluneCreditsFor(perf); y result += “Anount oved is ${usd{totalarount)}\o°s result + "You earned S{voluneCredits) credits\n"; return result; Reunir tudo que aualiza a varidvel voluveCredits facilita © uso de Substituir variével temporaria por consulta (Replace Temp with Query) (207). Como antes, o primeiro ppasso é aplicar Extair juncao (Extract Function) (134) a0 céleulo geral da variével function statement Function totalvolurecredits() ( let voluneCredits = 0; for (let perf of invotce.perfornances) { voluneCredits += voluneCreditsFor (perf); d return voluneCredits; } nivel mais alto. Function statenent (invotce, plays) { let totalanount = 6; let result = “Statenent for ${invoice.custoner}\n"s for (Let perf of invoice.perfornances) { I/ exibe 2 Linha para esta requisicéo result += > ${playFor(perf) name}: S{usd(anountFor(perf))} (S{oerF.audtence} seats)\n°; totalanaunt += anountFor (perf); } let voluneCredits = totalvolunetredtts(); result + “Anount oved is ${usd{ totalArount)}o) result += “You earned S{voluneCredits) credits\n's return results Depois que tudo tiver sido extraido, posso aplicar Internalizar variavel (Inline Varia- ble) (132) 40 Cariruco 1 # ReeatoRagho: PRIMEIRO EXEMPLO nivel mais alto. Function statenent (invoice, plays) { let totalanount = 8; let result = “Statenent for ${invoice.custoner}\n"; for (let perf of invoice.perfornances) { I/ exibe a Linha para esta requisicao result += * S{playFor(perf) .nane): S{usd(arountror(perf))} (S{perF.audtence} seats)\n°; totalanount +2 anountFor (perf); ‘nount oved is S{usd{totalarount) \n°; You earned ${totaloluneCredits()} credits\n’s return result; Deixe-me fazer uma pequena pausa para falar sobre © que acabei de fazer nesse caso, Em primeiro lugar, sei que os leitores, mais uma vez, ficardo preocupados com © desempenho em razio dessa alteracéo, pois muitas pessoas se sentem inquietas, quando repetem um lago, Na maioria das vezes, porém, executar um lago como esse outra vez tem um efeito desprezivel no desempenho. Se vocé medisse o tempo de execugio do cédigo antes e depois dessa refatoragao, provavelmente nao perceberia nenhuma mudanga significativa na velocidade ~ e, em geral, € isso que acontece. A maioria dos programadores, até mesmo aqueles que sio experientes, avaliam mal © desempenho real do cédigo. Muitas de nossas intuiges sio desmentidas por compiladores inteligentes, écnicas modernas de caching e afins. © desempenho do software em geral depende somente de algumas partes do cédigo, e mudancas em qualquer outro lugar nao causam nenhuma diferenga significativa ‘Contudo, “em geral” néo é 0 mesmo que ‘sempre’ As vezes, uma refatoracio tera uma implicagio significativa no desempenho, Mesmo nesse caso, eu geralmente sigo fem frente ¢ faco a refatoracio porque é muito mais facil ajustar o desempenho de um cédigo bem fatorado. Se eu introduzir um problema significativo de desempenho durante a refatoracio, gastarei tempo ajustando depois o desempenho. Pode ser que {sso me leve a desfazer alguma refatorag2o que eu tena feito antes ~ porém, na maio- Hla das vezes, por causa da refatoragéo, consigo aplicar uma melhoria mais eficaz para ajuste do desempenho. Acabo ficando com um cédigo com mais clareza e rapidez. Assim, meu conselho geral sobre o desempenho com a refatoragio é este: na maioria das vezes, voce deverd ignori-lo. Se sua refatoragio introduzir redugdes no desempenho, termine antes de refatorar e faga os ajustes de desempenho depois. © segundo aspecto para o qual quero chamar a sua atengao diz respeito aos pequenos passos usados para remover voluneCredits. Eis os quatro passos, cada um, seguido de compilagio, testes ¢ commit em meu repositério local de eédigos-fontes: Dzcompoxpo a rungao sraement 41 = Dividir aco (Split Loop) (257) para isolar a acumulagio; Deslocar instrucdes (Slide Statements) (252) para levar 0 cédigo de inicializagio para perto da acumulagio; = Extrair fungdo (Extract Function) (134) para criar uma fungdo que calcula otal; "= Internalizar varidvel (Inline Variable) (152) para remover totalmente a varivel, Conlesso que nem sempre executo passos tio pequenos como esses — mas, sempre que a situacio se torna dificil, minha primeira reagio é executar pasos menotes. Em particular, caso um teste falhe durante uma refatoragao, se ex nao conseguir ver nem corrigir prontamente o problema, restauro 0 cédigo para o meu tltimo bom commit e relago o que acabei de fazer em passos menores. Iso funciona porque fago commits com muita frequéncia e porque passos pequenos sio o segredo para andar rapidamente, em particular quando trabalhamos com um e6digo dificil. Entéo repito essa sequéncia para remover tatalénount. Comego di (compilo-testo-fago commit), depois desloco a inicializagio da vari -resto-fago commit), € entio extraio a fungao, Ha um pequeno problema nesse caso: o melhor nome para a fungio é “totalAmount’, mas esse é o nome da varidvel,e nao posso ter ambos ao mesmo tempo. Assim, dou um nome aleatério 4 nova fungio ao extratla (e compilo-testo-fago commit). function statement. Function applesavce() ¢ let totalanount = 8; for (let perf of invoice.perfornances) { ‘toteLénount += anountFor(oer#); d return totalanount; } nivel mais alto. Function statenent (invoice, plays) { let result = *Statenent for ${invoice.custoner}\n"; for (let perf of invoice.perfornances) { result + * S{playfor(pert).nane}: S{usd{anountFor(perf))} (${per*.audience} seats)\n"; } let totalanount = applesauce(); result += “Anount oved is ${usd{totalArount) 0°; result += “You earned ${totalvoluneCredits()} credits\n"s return result; 42 Canfruco 1 Reraroragao: PRIMEIRO EXEMRLO Entéo internalizo a varidvel (compilo-testo-fago commit) e renomeio a fungao para algo mais razoével (compilo-testo-fago commit) nivel mais alto, function statement (invotce, plays) { let result = “Statenent for ${invoice.custoner}\n"; for (let perf of invoice.perfornances) { * StplayFor(perf) nare}: Sfusd(arountFor(serf))} (S{perf audience} seats)\n"s result } result + “Anount oved is ${usd{totalvrount())}\n"s result += “You earned ${totalvoluneCredits()} credits\n’; return results function statement. function tetalanount() { let totalirount = 85 for (let perf of invoice.perfornances) { totalAnaunt +2 anountFor (perf); } return totalarount; y Também aproveito a oportunidade para modificar os nomes dentro das fungées extraidas para que estejam de acordo com minha convengio. function statement Function totalarount() { let result = 9; for (let perf of invoice.perfornances) { result += anountFor(perf); } return results y function totalolurecredits() { let result = 0; for (let perf of invotce.perfornances) { result += voluneCreditsFor (perf); } return result; y ‘Stars: MUITAS FUNGOES ANINHADAS 43 Status: muitas funges aninhadas Agora € uma boa hora para fazer uma pausa ¢ observar o estado geral do cédigo function statenent (invotce, plays) { let result = *Statenent for §{invotce.custoner}\n"s for (Let perf of invoice.perfornances) { result += * S{playFor(perf) nave}: S{usd(arountror(perf))} (S{perF.audtence} seats)\n°; } result + “Anount oved is ${usd{totalarount())}\n"; result += "You earned ${totalvoluneCredits()} credits\n"; return result; function totalanount() { Let result = 8; for (let perf of invoice.perfornances) { result + anountfor(perf); } return result; } function totalWoluneCredits() { let result = 8; for (let perf of invotce.performances) { result + voluneCreditsFor (perf); } return result; } function usd(aNurber) { return new Intl.NunberFornat(“en- { style: “currency”, currency: "USD", inininunFractionDigits: 2 }).format(atunber/180); } function voluneCreditsFor(aPerformance) { Tet result result += Math.max(aPerfornance.audtence - 38, 8); ‘if ("conedy® === playFor(aPerfornance) type) result += Math. floor aPerfernance.audtence / §); return result; d 44 Cariruco 1 # ReeatoRagho: PRIMEIRO EXEMPLO function playFor(aPerformance) { return plays[aPerfornance.playl0]; d function anountFor(aPer formance) { Tet result = 8; switch (playFor(aPerfornance).type) { case "tragedy" result = 49900; ‘AF (aPerformance.audience > 38) { result += 1090 ¥ (aPerfornance.audience - 36); result = 30900; Af (aPerformance.audtence > 23) { result += 10900 + 500 ¥ (aPerfornance. audience - 28); y result + 380 * aPerfornance. audience; break; default: throw new Error(“unknown type: ${pLayFor(aPerfornance).type}"); } return result; y y Acestrutura do cédigo esté muito melhor agora. A funcio de mais alto nivel staterent tem apenas sete linhas de cédigo e tudo que ela faz € organizar a exibigio do demons- ‘wativo, Toda a logica de célculo foi transferida para uma porgio de fungdes auxiliares. Isso facilita compreender cada célculo individual bem como o fluxo geral do relatério. Separando as fases de calculo e de formatacao Até agora, minha refatoragao teve como foco o acréscimo de estrutura suficiente & Fangio para que eu pudesse entendé-la e vé-la em termos de suas partes l6gicas. Em gezal, € isso que ocorte no inicio da refatoragao. Separar porgdes complicadas em partes menores é importante, assim como Thes dar bons nomes. Agora posso come- ar a me concentrar mais na mudanga de funcionalidade que quero fazer ~ espe- cificamente, oferecer uma versio HTML desse demonstrative. Em varias aspectos, agora seré muito mais facil fazer isso. Com todo o eédigo de caleulos separado, tudo, que devo fazer € escrever uma versio HTML das sete linhas de cédigo no inicio. O SERARANDO AS FASES DE CALCULO E DE FORMATAGAO problema é que essas fungées separadas estio aninhadas no método do demons- trativo textual, e eu nao gostaria de copiar e colar esse cédigo em uma nova fungio, apesar de ele estar bem organizado, Quero que as mesmas fungoes de célculo sejam, uusadas pelas versdes de texto e de HTML do demonstrative. Hi varias maneiras de fazer isso, mas uma de minhas técnicas favoritas é Separar ‘em fases (Split Phase) (183). Meu objetivo, nesse caso, € separar a logica em duas partes: uma que calcule os dados necessarios para o demonstrativo ¢ outra que os renderize em texto ou em HTML. A primeira fase eria uma estrutura de dados intermedia que € passada para a segunda, Comego um Separar em fases (Split Phase) (183) aplicando Extrair fucdo (Extract Function) (134) ao cédigo que compée a segunda fase. Nesse caso, é o cédigo de apresentagio do demonstrativo, que, na verdade, € todo 0 contetido de statenent Isso, em conjunto com todas as fungio aninhadas, sera colocado em uma funcio de nivel mais alto prépria que chamarei de renderPlatnText. function statenent (invotce, plays) { return renderPlataText(invoice, plays); y function renderPlainText(invoice, plays) ( let result = *Statenent for ${invoice.custoner}\n"; for (let perf of invoice.perfornances) { result + ° S{playfor(pert).nane}: S{usd{anountror(erf))} (${per*.audience} seats)\0"; } result + “Anount oved is ${usd{totalarount())}\n"; result += “You earned S{totalvoluneCredits()} credits\n"s return result; function totalanount() {...} function totalvoluneCredits() {...} Function usd(aNunbder) (...} function volureCreditsFor(aPerformance) {...} function playFor(aPerformance) {...} function anountFor(aPerfornance) Fago meu processo usual de compilar-testar-fazer commit, ¢ ento crio um objeto que atuaré como minha estrutura de dados intermedidria entre as duas fases. Passo esse objeto de dados como argumento para renderPlatnText (compilo-testo-fago commit). Function statenent (invoice, plays) { const statenentData = (0; return renderPlataText(statenentData, tnvotce, plays): y Function renderPlaintext(data, invoice, plays) ( let result = *Statenent for §{invoice.custoner}\n"; 45 46 Canfruco 1 Revaroragao: PRIMEIRO EXEMPLO for (let perf of invoice.perfornances) { result += * S{playfor(perf).nane}: S{usd{anountror(perf))} (${per*.audtence} seats)\n"; d result + “Anount oved is ${usd{totalarount())}\n"; result += “You earned ${totalvolumeCredits()} credits\n"; return result; function totalanount() {...} function totalVolunecredits() {...} function usd(aNunder) (...} Function voluneCreditsFor(aPerformance) {...} function playFor(aPerformance) {...} function anountFor(aPerfornance) Analiso agora os outros argumentos usados por renderPlaiaText. Quero passar os dados provenientes desses argumentos para a estrutura de dados intermedisria, de modo que todo o cédigo de calculos passe para a fungio statenent c renderPlainText, atue exclusivamente nos dados passados para ela por meio do parametro data Mex primeito passo é obter o cliente ¢ adicioné-lo no objeto intermediério (compilo-testo-fago commit) function statenent (invotce, plays) { const statenentdate =}; statenentData.custoner = invoice.custoner; return renderPlainText(statenentData, invoice, plays): y Function renderPlatntext(data, invoice, plays) ( let result = "Statenent for ${éata.custener}\n'; for (let perf of invoice.perfornances) { result + ~ S{playfor(pert).nane}: ${usd{anountFor(erf))} (${per*.audience} seats)\n"; , result + “Anount oved is ${usd{totalarount())}\n"; result += “You earned ${totalvoluneCredits()} credits\n"s return result; De modo semelhante, acrescento as apresentagies, ¢ isso me permite remover 0 pardmetro invoice de renderPlatnText (compilo-testo-fago commit), nivel mais alto, Function statenent (invoice, plays) { const statenentdate =}; statenentData.custoner = invoice.custoner; statenentData.perfornances = invoice. perfornances; SERARANDO AS FASES DE CALCULO E DE FORMATAGAO 47 return renderPlatnText(statenentData, Lavoie plays y Function renderPlatnText(data, plays) { let result = “Statenent for ${data.custoner}\n°; for (let perf of data.perfornances) { result += ° S{playFor(perf).nane}: S{usd{anountFor (per ))} (S{per*. audience} seats)\n"; d result + “Anount oved is ${usd{totalArount())}\n"; result += “You earned ${totalvoluneCredits()} credits\n"s return result; function renderPlain Text function tetalAnount() { let result = 6; for (let perf of data.perfornances) { result += anountFor(perf); } return results y function totaWoluretredits() { let result = 0; for (let perf of data.perfornances) { result += voluneCreditsfor(perf); d return result; } Gostaria agora que 0 nome da pega fosse obtido dos dados intermedistios. Para isso, tenho de enriquecer o registro das apresentagdes com os dados da peca (com- pilo-testo-fago commit). Function statenent (invotce, plays) { oh statenentData-custoner = invoice.custoner; statenentData.perfornances = invoice. performances .nap(enrichPerfornance) ; return renderPlatnText(statenentData, plays); const statenentDate function enrichPerfornance(aPerfornance) { const result = Object.assign({], aPerfornance); return result; } 48 Cariruco 1 # ReeatoRagho: PRIMEIRO EXEMPLO No momento, estou simplesmente criando uma cépia do objeto de apresentacio, mas, em breve, acrescentarei dados nesse novo registro, Uso uma cépia porque nao ‘quero modificar os dados passados para a fungao. Prefiro tratar os dados como imu- Léveis o maximo que puder—um estado mutével rapidamente se torna problematico, © idiom result = Object.asstgn((}, aPerformance) parece muito estranho para pes- soas que nao tenham familiaridade com JavaScript. Ele faz uma c6pia rasa (shallow copy). Eu preferiria ter uma ftungio para isso, mas é um daqueles casos em que 0 idiom esté tao entranhado no uso de JavaScript que escrever minha propria fungio pareceria fora de lugar para programadores JavaScript. Agora que tenho um local para a pega, preciso adicioné-la, Para isso, tenho de aplicar Mover fungao (Move Function) (225) em playFor ¢ em statenent (compilo-tes- to-fago commit), function statement function enrichPerfornance(aPerfornance) { const result = Object.assign({}, aPerformance); resuit.play = playFor(result); return result; } Function playFor(aPerFornance) { return plays[aPerfornance.playI0); y Entio substituo todas as referéncias a playFor em renderPlainText para usar o dado em seu lugar (compilo-testo-fago commit) function renderPlain Text let result = "Statenent for ${data.custener)\n"; for (let perf of data.perfornances) { result += ° S{per*.play.nane}: S{usd(anountFor(perf))} (${perf audience} seats)\n"; } result += “Anount oved is ${usd{totalArount())}\n"; result += “You earned ${totalVoluneCredits()} credits\n"; return result; Function voluneCreditsFor(ePerfornance) let result = 0; result += Math.max(aPerformance.audience - 38, 8); AF (Yconedy" == aPerfornance.play.type) result += Hath. floor(aPerfornance.audience / 5); return result; y SERARANDO AS FASES DE CALCULO E DE FORMATAGAO 49 Function anountfor(aPerformance) { let result = 0; switch (aPerformance.play.type) { case "tragedy" result = 48088; ‘f (aPerfornance.audience > 38) { result 1= 1808 * (aPerfornance.audience - 38); } break; case "comedy": result = 38088; ‘f (aPerfornance. audience > 28) { result + 18988 + 589 * (aPerformance.audience - 28); } result bret default: ‘throw new Error(unknown type: ${aPerfornance.play.type} 308 * aPerfornance. audience; y return results } Em seguida, movo arountFor de modo similar (compilo-testo-fago commit) function statement. function enrichPerfornance(aPerfornance) { const result = Object.assign((}, aPerformance); result.play = playFor(result); result.anount = anountFor (result); return result; y Function anountfor(aPerformance) {...} function renderPlain Text. let result = *Statenent for ${data.custoner}\n°; for (let perf of data.perfornances) { result += ° S(perf.play.nane}: ${usd(perf.anount)} (${perf.audtence) seats)\n"s } result += “Anount oved is S{us¢{totalarount())}\n"s result + “You earned ${totalvoluneCredits()} credits\n"s return result; 50 Capfruto 1 RevaroragAo: PRIMEIRO EXEMRLO Function totalArount() { let result = 0; for (let perf of data.perfornances) { result += perf.anount , return result; } ‘Na sequéncia, movo o céleulo dos exéditos por volume (compilo-testo-fago commit) fanction statement function enrichPerfornance(aPerfornance) { const result = Object.assign({}, aPerformance); result.play = playFor(result); result.anount = anountFor(result); result. voluneCredits = voluneCreditsFor(result); return results y Function volunecredits 3 (aPerFornance) {.. function renderPlain Text function tetalvolurecredits() { let result = 6; for (let perf of data.perfornances) { result += perf.volunecredits; y return results } Por fim, movo os dois célculos de totais. function statement. const statenentData = (03 statenentData.custoner = invoice, custoner; staterentData. performances = invoice.perfornances.nap(enrichPerFornance); serentData. totalanount = totalAnount statenent0: totalvoluneCredits(statenentData); return render? LainText(statenentdata, plays); function totalAnount(data) {...} Function totalvolureCredits(data) (.. staterentData, totaloluneCredits SERARANDO AS FASES DE CALCULO E DE FORMATAGAO SL function renderPlain Text lot result = “Statenent for ${data.custorer}\n"; for (let perf of data.perfornances) { result += ° S{perf.play.nane}: ${usd(perf.anount)} (S{serF.audtence) seats)\o"; } result += “Arount oved is ${usd(¢ata.totalanount) Na"; result += "You earned ${data.totelWolunecredits} credits\n"s return result; Embora eu pudesse ter modificado os corpos dessas fungdes de totais de modo que uusassem a varidvel statenentDsta (pois ela est no escope), prefiro passar 0 pardmetro explicitamente Depois de compilar-testar-fazer commit apés a mudanca, no consegui resistir a alguns usos rapidos de Substituir laco por pipeline (Replace Loop with Pipeline) (261). function renderPlainText. function totalanount(data) { return data, performances reduce((total, p) => total + p.anount, 8); } function totalvolurecredits(data) { return data, performances reduce((total, p) => total + p.volureCredits, 6); y Agora extraio todo o cédigo da primeira fase € o coloco em sua prépria fungio (compilo-testo-fago commit), nivel mais alto. function statenent (invotce, plays) { return renderPlatnText(createStatenentData(invoice, plays)); y Function createstatenentData(invotce, plays) { const statenentData = (}; statenentData.custoner = invoice.custorers statenentData.perfornances = invoice.performances.nap(enrichPer formance); statenentData.totalanount = totalAnount(statenentData); statenentData. totalvoluneCredits = totalVoluneCrecits(statenentData); return statenentData; Como 0 cédigo esta claramente separado agora, passo-o para o seu proprio arquivo (ealtero o nome do resultado devolvido para que esteja de acordo com minha con- vengio usual) 52 Cariruro 1 * RevatoRagho: PRIMEIRO EXEMPLO| statement. inport createStatenentDate fron '. /createStatenentData. js's createStatementData,js export default function createStatenentdataCinvoice, plays) { const result = 0); customer = invoice.custoners .perfornances = invoice.perfornances.nap(enrichPerfarnance); totalarount = totalanount(result); result. totalvoluneCredits = totalVoluneCredits(result); return results Function enrichPerfornance(aPerfornance) {...} function playFor(aPerformance) {...} Function anountFor(aPerfornance) {.. function volureCreditsFor(aPerformance) {...} function totalémount(data) {. function totalvolunecredits(data) {...} tiltima execugao de compilar-testar-fazer commit —e agora sera facil escrever uma versio HTML. statement.js Function htnlstatenent (invoice, plays) { return renderitnl (createStatenentData(invotce, plays)); y function renderitnl (data) { let result = “\n"; result += "etable>\n"; for (Let perf of data.perfornances) { result += > S{perf play nane}; -ctrocthoplaye] thoethoseatse/ thoethocoste/thee/tr>"; result += “\n"; ) result += "\a"; result += “

\n"s result + " credits

\n"; return results } Function usd(adtunber) {...) (Passe usd pata o nivel mais alto para que renderttnl pudesse usi-lo) STATUS: SEPARADO EM DOIS ARQUIVOS (E FASES) 53 Status: separado em dois arquivos (e fases) Este € um bom momento para fazer uma avaliag3o novamente e pensar no local em {que esté 0 cédigo agora. ‘Tenho dois arquivos de cédigo. statement.js ‘import cresteStatenentData fron '/createStatenentbata. js": fanction statenent (invoice, plays) { return renderPlatnText(createStatenentData(invoice, plays)); } function renderPlataText(data, plays) { let result = “Statenent for ${data.customer}\n°; for (let perf of data.perfornances) { result += * ${perf.play.nane}: ${usd(perf.amount)} (${perf.audience} seats)\n"; ) result += “Amount owed is ${usd(data. totalénount)}\n"; result 4= "You earned S{data.totalVoluneredits) credits": return results } function hentStatenent (invoice, plays) { return renderHtml (createStatenentData(invoice, plays)); } function renderittnl (data) { let result = “Statenent for ${data.custoner}\n°; result += “\n"; result += “ctrocthoplayethocthoseatse/thocthocostethtr> for (let perf of data.performances) { result += ° ctroctds{perf.play.nane}eftdoctdS{pert. audience} e/té>j result ‘\n"; > result += “/tables\n's result += “

Anount owed is ${usd(data. totalAnount) }

\n"; result += “

You earned ${data. totalvolumeCredits} credits

\n'; return results } function usd(aNumber) { return new Intl.NumberFormat("en-US", { style: "currency", currency: "Us nininunFractionDigits: 2 }).format(aNunber /100); 54 Capfruco 1 Reratoragao: PRIMEIRO EXEMRLO createStatementData,js export default function createstatenentdata(invoice, plays) { const result = 03 result.custoner = tnoice.custoners result.perforrances = invoice.performances.nap(enrichPerfornance); result.totalarount = totalanount(result); result. totalvoluneCredits = totalVoluneCredits(result); return results function enrichPerfornance(aPerfornance) { const result = Object.assign({}, aPerfornance); result.play = playFor(result); result.arount = anountFor (result); result.voluneCredits = volureCreditsFor(result); return result; d function playFor(aPerformance) { return plays[aPerfornance,play10] } function anountFor(aPerfornance) { Tet result, switch (aPerformance.play.type) case "tragedy": result = 49000; AF (aPerformance.audience > 38) { result += 1980 * (aPerfornance.audience » 38) y break case “conedy" result = 30008; AF (aPerformance.audience > 28) { result += 19908 + 500 * (aPerforrance.auétence - 20); y result 4 300 * aPerformance. audience; breaks default: throw new Error(unknown type: ${aPerformance.play.type}"); } return result; } Reorcanizanvo 03 function voluneCredttsFor(aPerfornance) { let result, result += Math.max(aPerfornance.audtence - 38, 8); ‘if ("conedy’ == aPerfornance.play.type) result 4= Hath. for(aPerfornance. audience / 5); return result; } function totaténount(data) { return data.perfornances -reduce((total, p) => total + p.anount, 8); } function totalvoluneCredits(data) { return data.perfornances -reduce((total, p) => total + p.voluneCredits, 8); } Tenho mais cédigo agora do que cu tinha no inicio: 70 linhas (sem contar henlstaterent), em comparagio com 44, principalmente em raz40 do encapsula- mento extra para deixar o cédigo em fungées. Se tudo mais for o mesmo, mais, cédigo seré ruim ~ raramente, porém, tudo mais continuard igual. O eédigo extra separa a logica em partes identificéveis,isolando os célculos dos demonstrativos de seu layout. Essa modularidade faz com que seja mais facil para mim compreender as partes do cédigo e como clas se encaixam. A concisio é a alma da perspicacia, mas a clateza é a alma de um software capaz de evoluir. Actescentar essa modula- ridade me permite oferccer suporte a0 cédigo para a versio HTML sem qualquer duplicagao nos céleulos. sey Quando programar, siga a regra do acampame QE digo mais saudével do que estava quando vo Hi outras modificagées que eu poderia ter feito para simplificar a légica de exi- bigao, mas, por enquanto, o que fizemos bastard, Tenho sempre de encontrar um equilibrio entre todas as refatoracées que eu poderia fazer ¢ 0 acréscimo de novas funcionalidades. Atualmente, a maioria das pessoas dé menos prioridade & refato- ragio—mas ainda hé um equilibrio, Minha regra é uma variagio da regra do acam- pamento: sempre deixe a base de cédigo mais saudavel do que estava quando voce a encontrou. Ela jamais sera perfeita, mas devers ser melhor. 1: sempre deixe a base de a encontrou. Reorganizando os célculos por tipo Voltarei minha atensdo agora para a préxima mudanga de funcionalidade: aceitar outras categorias de pegas, cada uma com seus proprios edleulos de cobranga e de cxéditos por volume. No momento, para fazer alteragbes, tenho de entrar nas fun- goes de célculo e editar ali as condigdes. A fungio anountFor entatiza o papel central 55 56 Cariruco 1 # ReeatoRagho: PRIMEIRO EXEMPLO {que o tipo da peca desempenha na forma de fazer os célculos ~ porém uma logica condicional como essa tende a se enfraquecer & medida que novas modificagies forem feitas, a menos que ela seja reforcada por elementos mais estruturais da lin- guagem de programagao. Hié varias maneiras de introduzir uma estrutura para deixar isso explicito, mas, nesse caso, uma abordagem natural ¢ 0 polimorfismo de tipo — um recurso de des- taque na orientacio a objetos classica. A orientacdo a objetos cléssica tem sido um aspecto controverso h4 muito tempo no mundo JavaScript, mas a versio ECMAS- cript 2015 oferece uma sintaxe ¢ uma estrutura robustas para ela. Portanto, faz sen- tido usé-la em uma situagio correta ~ como esta De modo geral, meu plano é definir uma hierarquia de heranga com subclas- ses para comédia e tragédia que contenham a légica de eéleulo para esses casos. Quem fizer a chamada usar uma fungio polimérfica para o cAlculo do valor, e a linguagem despachard para os diferentes célculos associados as comédias ¢ as tragé- dias. Criarei uma estrutura semelhante para o célculo dos créditos por volume. Para isso, utilizarei duas refatoragées. A refatoragio principal € Substituir condicional por polimorfismo (Replace Conditional with Polymorphism) (299), que altera uma porcio de cédigo com condicionais por polimorfismo. Contudo, antes de usar Substituir 38) { result += 1090 ¥ (aPerfornance.audience - 36); result = 30900; Af (aPerformance.audtence > 23) { result += 10900 + 500 ¥ (aPerfornance. audience - 28); y result + 380 * aPerfornance. audience; break; default: throw new Error(unknown type: ${aPerformance.play.type}"); } return result; } function voluneCreditsFor(aPerformance) { let result = 8; result += Math.wax(aPerfornance.audience - 38, 8); ‘F ("conedy" = aPerformance.play.type) result += Math.foor(=Perfornance.audtence / 5); return result; , function totalénount data) { return data.perfornances -reduce((total, p) => total + p.anount, 8); d function totalvolunecredits(data) { return data.perfornances reduce((total, p) => total + p.voluneCredits, €); 58 Cariruco 1 # ReeatoRagho: PRIMEIRO EXEMPLO Griando uma calculadora de apresentacao A fungao enrichPerformance é a chave, pois ela preenche a estrutura de dados inter- medidria com os dados de cada apresentacio. No momento, cla chama fungdes com, condicionais para o valor cobrado € os créditos por volume, © que preciso que cla faca € chamar essas fungGes em uma classe que as contenha. Como essa classe contém fungdes para calcular dados sobre as apresentagoes, vou chamé-la de caleu- ladora de apresentagio (performance calculator) function createStatementData, Function enrtchPerFernance(aPerfornance) { const calculator = new PerfornanceCalculator(aPerformance) ; const result = Object.assign({}, aPerfornance); result.play = playFor(result); result.anount = anountFor(cesult); result.volumeCredits = voluneCreditsFor(result); return results y nivel mais alto. class PerfornanceCalculator { (aPerFornance) { this.perfornance = aPerfornance; ) y Até agora, esse novo objeto néo faz nada. Quero passar alguns comportamentos para ela—e gostaria de comegar com o item mais simples de ser transferido, que €0 registo da peca. Estritamente falando, nio preciso fazer isso, pois a pega nao varia, polimorficamente; desse modo, porém, manterei todas as transformagées de dados em um s6 local, ¢ essa consisténcia dard mais clareza ao cédigo. Para que isso funcione, usarei Mudar declaragao de funcao (Change Function Decla- ration) (153) a fim de passar a pega encenada para a calculadora. construct function createStatementData. function enrichPerfornance(aPerfornance) ( const calculator = new PerfornanceCalculator(aPerformance, playFor(aPerformance)); const result = Object.assign((}, aPerformance); result.play = calculator play; result.anount = anountFor(result); result. voluneCredits = voluneCreditsFor(result); return result; Reorcanizanvo 03 class PerformanceCalculator. lass PerfornanceCalculator { constructor(aPerfornance, aPlay) { this.perforrance = aPerfornance; this.play = aPlay } y (Nio estou mais dizendo para compilar-testar-fazer commit o tempo todo, pois sus- peito que voc’ esteja ficando cansado de ler isso, Porém, continuo executando esses ‘passos em todas as oportunidades. As vezes me canso de fazé-los — e dou uma chance aos exros de me morderem, Entao aprendo minha ligfo ¢ volto a entrar no ritmo) Passando fungoes para a calculadora A prdxima porgao de légica que moverei € muito maior e serve para calcular o valor de uma apresentagio. Movi fungées por ai casualmente enquanto reorganizava as fungoes aninhadas—mas esta é uma modificagao mais profunda no contexto da fun- 40, portanto descreverei a refatoragio Mover fungao (Move Function) (225) passo a asso. A primeira parte dessa relatoragao & copiar a l6gica para o seu novo contexto a classe de calculadora. Em seguida, adapto o cédigo para se adequar a sua nova morada, alterando aPerformance pata this.performance e playFor(aPerfornance) para this.play class PerformanceCalculator. get anount() { let result = 6; switch (this.play.type) { case "tragedy" result = 49900; Af (this.perfornance.audtence > 38) { result += 1090 ¥ (this.perfornance.audience - 36); y breaks case “conedy" result = 30900; 4 (ehis.perfornance.audtence > 29) { result += 10900 + 500 ¥ (this.perfornance.audtence - 20); y result += 380 * this.perfornance. audience; break; 9 60 Cariruco 1 # ReeatoRagho: PRIMEIRO EXEMPLO default: ‘throw new Ercor("unknown type: ${this.play.type}"); d return result; y Posso compilar neste ponto para verificar se hé algum erro de compilagio. A “com- pilagao” em meu ambiente de desenvolvimento ocorre quando executo 0 cédigo, entdo o que realmente fago ¢ executar 0 Babel [babel]. Isso ser suficiente para identificar qualquer erro de sintaxe na nova funcio ~ mas nio muito mais que isso. Mesmo assim, esse pode ser um passo tit Depois que a nova fungéo estiver adequada 4 sua nova morada, tomo a fungio original ¢ a translormo em uma fungio de delegagio que chamard a nova fungao, function createStatementData. function anountFor(aPerformance) { return new PerfornanceCalculator(aPerformance, playFor(aPerformance)).anount; } Agora posso compilar-testar-fazer commit para garantir que o cédigo esteja fun- cionando de forma apropriada em sua nova morada. Depois de fazer isso, utilizo Internalizar fungao (Inline Function) (144) para chamar ditetamente a nova fungao (compilo-testo-faco commit) function createStatementData. Function enrichPerFornance(aPerfornance) { const calculator = new PerforranceCalculator(aPerformance, playFor(aPerformance)); const result = Object.assign({}, aPerformance); result.play = calculator play; result.anount = calculator.anount; result. voluneCredits = voluneCreditsFor(result); return results y Repito o mesmo processo para mover 0 céleulo dos exéditos por volume. function createStatementData function enrichPerfornance(aPerfornance) { const calculator = new PerforranceCalculator (aPerformance, playFor(aPerfornance)); const result = Object.assign((}, aPerformance); resuit.play = calculator play; result.anaunt = calculator anount; Reorcanizanvo 03 61 result. voluneCredits = calculator.volurecredits; return result; } class PerformanceCalculator. get voluneCredits() { let result = 6; result += Nath.nax(this.performance.audience - 39, iF ("conedy" === this.play.type) result += Nath.loor(this. performance. audience / §); return results } Deixando a calculadora de apresentacdo polimérfica Agora que tenho a légica em uma classe, ¢ hora de aplicar o polimorfismo, O pri- meiro passo é usar Substituir digo de tipos por subclasses (Replace Type Code with Sub- lasses) (389) para introduzir subclasses no lugar de cédigo de tipos. Para isso, pre- ciso criar subclasses da calculadora de apresentacio e usar a subclasse apropriada em createPerforrancedata, Para ter a subclasse correta, devo substituir a chamada do construtor por uma funcio, pois construtores JavaScript ndo podem devolver sub- lasses. Portanto, uso Substituir construtor por juncao de factory (Replace Constructor with Factory Function) (363). function createStatementData. function enrichPerfornance(aPerfornance) { const calculator = createPerformanceCalculator(aPer formance, playFor(aPerfornance)); const result = Object.assign({}, aPerformance); result.play = calculator plays result.anount = calculator anount; result. voluneCredits = calculator.volureCredits; return result; y nivel mais alto. Function createPerfornanceCalculator(aPerfornance, aPlay) { return new PerfornanceCalculator(aPerformance, aPlay); y Com esse cédigo sendo agora uma fungao, posso criar subclasses da caleuladora de apresentacio e fazer a funcio de criagio selecionar qual delas ser devolvida 62 Canfruco 1" Reraroragao: PRIMEIRO EXEMPLO nivel mais alto. Function createPerfornanceCalculator(aPerfornance, Play) { switch(aPlay.type) { cease "tragedy": return new TragedyCalculator(aPerfornance, aPlay); case “conedy" = return new ConedyCalculator(aPerfornance, aPlay); default: throw new Error(°unknown type: ${aPlay.type}’); } y Class TragedyCalculator extends PerFormanceCalculator { y Class ConedyCalculator extends PerfornanceCatculator ( y Esse cédigo define a estrutura para o polimorfismo, de modo que posso agora ppassar para Substituir condicional por polimorfismo (Replace Conditional with Polymor- phism) (299). ‘Comego pelo céleulo do valor para as tragédias. class TragedyCalculator. get anount() ( let result = 49900; if (this. performance audience > 38) { result += 1098 ¥ (this.perfornance. audience - 36); d return result; 1 Somente o fato de ter esse método na subclasse € suficiente para sobrescrever a con- dicional da superclasse. Todavia, se vocé for téo paranoico quanto eu, poderd fazer o seguinte class PerformanceCaleulator. get anount() { let result = 0; switch (this.play.type) { case “tragedy! ‘throw "bad thing's case “cones result = 30008; AF (this.perfornance audience > 28) { ReoRGANIZANDO O$ CALCULOS POR TIPO 63 result += 10800 + 500 * (this.perfornance.audtence - 20); y result += 398 * this.perfornance audtence; break: ‘throw new Error(“unknown type: ${this.play.type}"); ) return result; } Eu poderia ter removido o caso para tragédia ¢ deixar que a ramificagao default lan- gasse um erro. No entanto, gosto do langamento explicito ~ ¢ ele estara lé somente por mais alguns minutos (motivo pelo qual lancet uma string, e no um objeto de erro melhor) Depois de compilar-testar-fazer commit desse cédigo, passo para baixo tamb © caso da comédia, class ComedyCalculator. get anount() ( let result = 30000; UF (this.perfornance. audience > 28) { result += 10960 + 599 * (this.perforrance.audtence - 28); } result + 308 * this.performance. audience; return results ¥ Posso agora remover o método anaunt da superclasse, pois ele jamais deverd ser cha- mado. Entretanto, seré uma gentileza que fago a mim mesmo no futuro se eu deixar uma lépide, class PerformanceCalculator. get anount() { throw new Error("subclass responsibtltty’); y Apréxima condicional a ser substituida ¢ 0 célculo dos créditos por volume. Obser vando a discussao sobre futuras categorias de pegas teatrais, percebo que a maior das pecas espera verificar se houve mais de 30 pessoas na audiéncia, com apenas algumas categorias introduzindo uma variago. Portanto, faz sentido deixar 0 caso mais comum na superclasse como default e deixar que as variagdes o sobrescrevam caso necessario. Assim, passei para baixo somente o caso das comédias. 64 Cariruco 1 # ReeatoRagho: PRIMEIRO EXEMPLO class PerformanceCalculator. get voluneCredits() { return Math.nax(this.performance.audience - 38, 0); y class ComedyCaleulator, get voluneCredits() { return super.voluneCredits + Hath.flor(this.perfornance.audtence / 5); y Status: criando os dados com a calculadora polimérfica E hora de refletir sobre o que a introdugio da calculadora polimérfica fez com 0 cédigo. createStatementData js export default function createstatenentdata(invoice, plays) { const result = 03 result.custoner = inoice.custoners result.perforrances = invoice.performances.nap(enrichPerfornance); result. totalarount = totalarount(result); result. totalvoluneCredits = totalVoluneCredits(result); return results function enrichperfornance(aPerfornance) { const calculator = createPerfornanceCalculator(aPerfornance, playFor(aPerformance)); const result = Object.assign({), aPerfornance); result.play = calculator.play; result.anount = calculator. anount; result.voluneCredits = calculator. voluneCredits; return result; } function playFor(aPerformance) { return plays[aPerfarnance.playI0] d function totalarount(data) { return data.perfornances -reduce((total, p) => total + p.anount, 8); ‘STATUS: CRIANDO OS DADOS COM A CALCULADORA POLIMGREICA 65 function totalVolunecredits(data) { return data.perfornances sreduce((total, p) => total + p.volunecredits, 6); function createPerfornanceCalculator(aPerfornance, aPlay) { switch(aPlay.type) { cease "tragedy": return new TragedyCalculator(aPerfornance, aPlay); case “conedy" + return new ConedyCalculator(aPerfornance, Play); éefautt: throw new Error(unknown type: ${aPlay.type}’); } Class Perfornancecalculator { constructor(aPerfornance, aPlay) { this.perforrance = aPerfornance; this play = aPlays } get anount() throw new Error("subclass responsibility’); } get voluneCredits() { return Math.nax(this.perfornance.audience - 38, 8); } y Class TragedyCalculator extends PerfornanceCalevlator { get anount() ( Let result = 40060; UF (this.performance.avdtence > 38) result + 1000 * (this.performence.audience - 38); } return result; d ¥ lass ConedyCalculator extends PerfornanceCalculator { get anount() { let result = 30000; Af (this. performance. audience > 28) result 4 10088 + 589 * (this.performance.audience - 28); 66 Cariruco 1 # ReeatoRagho: PRIMEIRO EXEMPLO result += 306 * this.perfornance.audtence; return result; d get voluneCredits() { return super.voluneCredits + Hath.floor( this, performance.audience / 5); } } Novamente, o tamanho do cédigo aumentou, pois introduzi uma estrucura, A van- tagem, nesse caso, € que os célculos para cada tipo de pega estio agrupadas. Sea maior parte das alteragdes forem nesse cédigo, seré conveniente (é-lo claramente separado dessa forma. Actescentar um novo po de pega exige escrever uma nova subelasse ¢ adicioné-la na fungao de criagio. © exemplo proporciona alguns insights sobre quando usar subclasses como essas serd conveniente, Nesse caso, passei a verificagio condicional de duas fungées (anoustFor ¢ voluneCreditsFor) para uma tinica funcio construtora, createPerfornanceCalculator. Quanto mais fungdes houver que dependam do mesmo tipo de polimorfismo, mais conveniente sera essa abordagem. ‘Uma alternativa para o que foi feito nesse exemplo seria fazer createPerformancedata devolver a propria calculadora, em vez de a calculadora preencher a estrutura de dados intermediria, Uma das caracterfsticas interessantes do sistema de classes de JavaScript € que, com ele, usar getters é parecido com um acesso de dados comum. Minha opgio entre devolver a instincia ou calcular dados de safda separados depende de quem vai usar a estrutura de dados posteriormente. Nesse caso, preferi mostrar como usar a estrutura de dados intermedisria para ocultar a decisio de usar uma calculadora polimérfica Consideracées finais Este € um exemplo simples, mas espero que ele dé a voc® uma nogéo de como € uma refatoragao. Use varias refatoragdes, incluindo Extrair fngdo (Extract Func- tion) (134), Internalizar varidvel (Inline Variable) (152), Mover funcao (Move Func- tion) (225) © Substituir condicional por polimorfismo (Replace Conditional with Poly- morphism) (299). Houve tés etapas principais nesse epissdio de refatoragio: decomposigao da funcéo original em um conjunto de fungSes aninhadas, uso de Separar em fases (Spit Phase) (183) para separar 0 cédigo de cileulos do cédigo de exibigio e, por fim, introdugéo de uma calculadora polimérfica para a légica de célculos. Cada uma delas acrescentou estrutura ao cédigo, permitindo que eu comunicasse melhor 0 que o cédigo estava fazendo. Consipenas Ss FINALS Como ocorre com frequéncia na refatoragio, as primeiras etapas foram dite- cionadas principalmente para tentar entender 0 que estava acontecendo. Eis uma sequéncia comum: ler 0 cédigo, obter alguns insights e usar a refatoragao para pas- sar esse insight de sua mente de volta para 0 cédigo. O eédigo mais claro entao faci- lita a sua compreensio, levando a insights mais profundos e a um ciclo de feedback positivo benéfico, Ainda ha algumas melhorias que eu poderia cer feito, mas sinto que fiz 0 suficiente para passar no meu teste de deixar 0 cédigo significativamente melhor do que estava quando o enconttei LO verdadeiro teste para um bom cédigo ¢ a facilidade com que ele pode Qe ser alterado. Escou falando de melhorar 0 cédigo ~ mas os programadores adoram discutir sobre como é a aparéncia de um bom cédigo Sei que algumas pessoas tém objegio 4 minha preferéncia por funcdes pequenas e bem nomeadas. Se considerarmos que isso é uma questio de estética, em que nada € bom nem ruim, mas o pensamento o faz ser assim, nao teremos nenlsuma diretriz, exceto 0 gosto pessoal. Acredito, porém, que podemos ir além do gosto pessoal e dizer que o verdadeiro teste para um bbom cédigo é a facilidade com que podemos modificé-lo. O cédigo deve ser ébvio: quando alguém tiver de fazer uma alteragao, essa pessoa dever4 localizar facilmente © cédigo a ser modificado e fazer a alteracéo rapidamente, sem introduzir erros ‘Uma base de cédigo saudével maximiza nossa produtividade, permitindo desen- volver mais funcionalidades para os nossos usuarios, de modo mais répido ¢ mais barato, Para manter um cédigo saudvel, preste atengio no que esté entre a equipe de programacio e esse ideal, e entio refatore para se aproximar do ideal. Contudo, o mais importante a se aprender com esse exemplo ¢ 0 ritmo da refa- toragio, Sempre que mostto as pessoas como faco uma refatoragio, elas ficam sur- presas com o tamanho pequeno de meus passos, em que cada passo deixa o cédigo ‘em um estado funcional no qual ele compila ¢ os testes passam. Fiquei igualmente surpreso quando Kent Beck me mostrou como fazer isso em um quarto de hotel fem Detroit duas décadas atrés. O segredo para uma refatoragio eficaz € reconhecer que vocé ser mais ripido se der passos mintisculos, pois o cécligo nunca apresenta, falhas ¢ voc pode combinar esses pasos pequenos em alteragées substanciais. Lembre-se disso —e 0 resto é silencio. 7

You might also like

${usd(perf anount)}