Steve Lindstrom
Novatec
Authorized Portuguese translation of the English edition of CSS Refactoring, ISBN 9781491906422 © 2015 Steve Lindstrom. This translation is published and sold by permission of O'Reilly Media, Inc., the owner of all rights to publish and sell the same. Tradução em português autorizada da edição em inglês da obra CSS Refactoring, ISBN 9781491906422 © 2015 Steve Lindstrom. Esta tradução é publicada e vendida com a permissão da O'Reilly Media, Inc., detentora de todos os direitos para publicação e venda desta obra. © Novatec Editora Ltda. 2017. Todos os direitos reservados e protegidos pela Lei 9.610 de 19/02/1998. É proibida a reprodução desta obra, mesmo parcial, por qualquer processo, sem prévia autorização, por escrito, do autor e da Editora. Editor: Rubens Prates Tradução: Lúcia A. Kinoshita Revisão gramatical: Priscila A. Yoshimatsu Editoração eletrônica: Carolina Kuwabata ISBN: 978-85-7522-537-0 Histórico de impressões: Janeiro/2017
Primeira edição
Novatec Editora Ltda. Rua Luís Antônio dos Santos 110 02460-000 – São Paulo, SP – Brasil Tel.: +55 11 2959-6529 Email: novatec@novatec.com.br Site: www.novatec.com.br Twitter: twitter.com/novateceditora Facebook: facebook.com/novatec LinkedIn: linkedin.com/in/novatec
capítulo 1
Refatoração e arquitetura
Este é o ponto de partida de nossa jornada pela refatoração de CSS. Neste capítulo, aprenderemos o que é refatoração e como ela se relaciona com a arquitetura de software. Discutiremos também a importância da refatoração e alguns dos motivos pelos quais o seu código poderá precisar dela e trabalharemos com dois exemplos de refatoração para compreender melhor esses conceitos.
O que é refatoração? A refatoração é o processo de reescrever código a fim de deixá-lo mais simples e reutilizável sem alterar o seu comportamento. É uma habilidade fundamental se você escreve código, pois será necessário fazer refatorações em algum momento, querendo ou não; talvez você até já tenha refatorado código sem se dar conta disso! Como a refatoração não altera o comportamento do código, é razoável se perguntar inicialmente por que valeria a pena fazê-la. Antes que essa pergunta possa ser respondida, porém, é importante entender o que é arquitetura de software.
O que é arquitetura de software? Assim como um ser vivo, um sistema de software geralmente é composto de muitas partes menores, especializadas em fazer algo específico. Quando combinadas, essas partes menores funcionam em conjunto a fim de criar um sistema de software maior. Arquitetura de software é o termo usado para descrever como todas as partes de um projeto de software se encaixam. 20
Capítulo 1 ■ Refatoração e arquitetura
21
Cada parte do software, desde um site simples até o sistema de controle de um veículo espacial, tem uma arquitetura, seja intencional ou não. No entanto, as melhores arquiteturas geralmente são planejadas muito antes de qualquer programação ser feita. A seguir, apresentaremos algumas das características mais importantes de uma boa arquitetura.
Arquiteturas boas são previsíveis Ser previsível significa que suposições precisas podem ser feitas sobre o funcionamento e a estrutura do software. A previsibilidade é indicador de um planejamento antecipado apropriado e ajudará a economizar tempo de desenvolvimento, pois não haverá dúvidas sobre: • Quais são as responsabilidades de um componente • Onde um código em particular poderá ser encontrado • Onde um novo código deve ser colocado Pelo fato de ser possível fazer suposições com exatidão em uma arquitetura previsível, os desenvolvedores que não tenham familiaridade com o código deverão ser capazes de compreendê-lo mais rapidamente.
Arquiteturas boas possibilitam reutilização de código A reutilização de código é a capacidade de o código ser usado em vários lugares sem que esteja duplicado. A reutilização de código é vantajosa porque agiliza o tempo de desenvolvimento, pois você não precisará reescrever códigos já existentes. De modo semelhante, quanto menos código você tiver para resolver um problema em particular, menos tempo terá de gastar na manutenção de todas essas implementações. Por exemplo, se você descobrir um bug em um código que seja reutilizado em um projeto, saberá que esse bug estará presente em todos os lugares em que o código foi utilizado. Porém, ao corrigi-lo em um só local, você o estará corrigindo em todos os lugares nos quais esse código foi utilizado.
22
Refatoração de CSS
Arquiteturas boas são extensíveis A capacidade de extensão é um princípio da boa arquitetura porque permite que o sistema tenha facilmente novas funcionalidades desenvolvidas a partir dessa base. A maioria dos softwares não é desenvolvida do início ao fim em um só dia, portanto é muito importante que o software possa ser desenvolvido de forma incremental, sem demandar grandes mudanças estruturais. Se o seu projeto exigir mudanças significativas frequentes na arquitetura, será muito mais difícil fazer o seu lançamento.
Arquiteturas boas permitem manutenção De modo muito semelhante à capacidade de extensão, a manutenibilidade é muito importante para uma arquitetura, pois permite que você modifique facilmente as funcionalidades existentes. Com o tempo, os requisitos podem mudar e você será forçado a modificar o seu código. Ter um software que possa ser mantido significa que você poderá modificar uma parte de seu código sem necessariamente ter de alterar drasticamente todas as outras partes.
Arquitetura de software e refatoração Em poucas palavras, a refatoração existe para ajudar a manter e a promover uma boa arquitetura de software. Ela não é nada mais que um conjunto de técnicas que podem ser usadas para reorganizar o código, transformando-o em uma estrutura mais significativa, com o intuito de deixá-lo mais previsível, reutilizável, extensível e fácil de manter. Quando a arquitetura de seu software exibir as características mencionadas anteriormente, o software será muito mais confiável para os usuários visados, além de ser muito mais agradável trabalhar com ele.
Deficiências que levam à refatoração Por que o código não é escrito corretamente, antes de tudo, de modo que não haja necessidade de refatorá-lo depois? Apesar de nossos melhores esforços para projetar e escrever um código com a melhor qualidade
Capítulo 1 ■ Refatoração e arquitetura
23
possível, com o tempo algo mudará, exigindo refatoração. Vamos analisar algumas das causas.
Mudança de requisitos Com o passar do tempo, os sistemas de software evoluem como resultado da mudança de requisitos. Quando um software é escrito a fim de satisfazer a um conjunto de requisitos, é provável que ele não leve em consideração certos aspectos para satisfazer outro conjunto de requisitos que ainda não tenha sido escrito (e nem deveria). Desse modo, quando os requisitos mudam, o mesmo deve ocorrer com o código; se houver restrições quanto a prazos, a qualidade do código poderá sofrer como resultado da adoção de atalhos.
Arquitetura com design precário Mesmo que você saiba de que uma boa arquitetura é composta, nem sempre é viável gastar um tempo significativo planejando tudo. Se você não tiver uma visão clara de como tudo deverá funcionar em conjunto desde o início, talvez seja necessário fazer algumas refatorações posteriormente no caminho. Também é muito comum desenvolver uma nova funcionalidade de forma realmente rápida (o que pode resultar na adoção de atalhos) para ver se ela ganha impulso junto aos usuários e, então, organizar o código mais tarde se isso ocorrer, ou removê-lo caso contrário.
Subestimar as dificuldades Estimar quanto tempo o desenvolvimento de software exigirá é difícil e, infelizmente, essas estimativas muitas vezes são usadas para definir cronogramas. Quando o prazo para um projeto é subestimado, os desenvolvedores são pressionados a “simplesmente terminar o trabalho”, o que resulta em escrever códigos rapidamente, sem planejá-los muito bem. Se isso ocorrer com frequência, até mesmo o melhor dos códigos poderá se transformar em um enorme prato de “código espaguete”, difícil de compreender e de administrar.
24
Refatoração de CSS
Ignorar as melhores práticas Pode ser difícil se manter em dia com todas as melhores práticas, especialmente se o seu trabalho envolver muitas tecnologias e/ou gerenciamento de pessoas. Se você estiver trabalhando em uma equipe e menosprezar uma boa prática, há uma boa chance de um colega avisá-lo. Se você perdeu a oportunidade de usar uma boa prática, em algum momento no futuro talvez vá precisar rever o seu código e fazer uma refatoração.
Dificuldade em se manter em dia com as melhores práticas A tecnologia muda em um ritmo muito rápido e, como resultado, uma técnica que já tenha sido considerada uma melhor prática pode não sê-la mais. Por exemplo, antes de 2011, se você quisesse exibir um contêiner que parecesse ter cantos arredondados em um site, era necessário ter uma imagem para cada um dos cantos, incluí-las em seu HTML e então posicioná-las usando CSS para garantir que tudo estivesse alinhado corretamente. Atualmente, essa técnica está obsoleta, pois os navegadores modernos são capazes de exibir cantos arredondados com a propriedade de CSS border-radius. Se você não atualizar continuamente o seu código de modo a usar as melhores práticas modernas, com o passar do tempo seu débito técnico aumentará e o seu código estará em um estado muito pior do que estaria caso contrário.
Quando o código deve ser refatorado? Refatorar o código é muito mais fácil se for feito com um contexto. Desse modo, geralmente é melhor refatorar quando você estiver corrigindo um bug ou desenvolvendo uma nova funcionalidade que faça uso do código existente. Refatorar o código de modo consistente enquanto trabalha em tarefas menores reduz as chances de causar falhas em algo, e aqueles que forem modificar o mesmo código depois que este tiver sido refatorado também se beneficiarão com o seu trabalho. Com o tempo, uma refatoração
Capítulo 1 ■ Refatoração e arquitetura
25
consistente resultará em um código melhor, desde que suas alterações estejam de acordo com as propriedades de uma boa arquitetura. Porém, às vezes você poderá se deparar com um código que tenha muitas dependências e terá de decidir se deve refatorar ou não. Refatorar um código com muitas dependências pode ser como puxar uma linha solta em uma camisa: quanto mais você puxa a linha, mais a camisa desfiará. De modo semelhante, quanto mais você modificar um código que tenha muitas dependências, mais dependências deverão ser atualizadas. Em situações como essa, se você estiver diante de um prazo final apertado, talvez seja vantajoso terminar primeiro o seu trabalho no prazo e então alocar tempo para retornar e refatorar. Contudo, ao longo do caminho, se você perceber que há pequenas partes que podem ser refatoradas sem afetar negativamente o seu cronograma, poderá considerar refatorá-las agora.
Quando o código NÃO deve ser refatorado? Saber quando não refatorar o código provavelmente é mais importante que saber quando ele deve ser refatorado. A refatoração talvez tenha uma reputação ruim porque, com frequência, os desenvolvedores de software parecem reescrever códigos somente por reescrever. Talvez outra pessoa tenha escrito o código e a pessoa fazendo a refatoração desnecessária sofra de um caso de Síndrome do Não Foi Escrito Aqui, em que ela acha que o código é inferior pelo fato de não ter sido escrito por ela. Ou quem sabe, certo dia, uma pessoa decida que simplesmente não gosta do modo como escreveu o código anteriormente (talvez ela tenha usado underscores em vez de hifens no nome das classes e quer fazer o inverso agora) e, desse modo, resolva mudar tudo a fim de satisfazer a essa compulsão. Em muitos casos, isso pode ser considerado um “trabalho falso”, que faz as pessoas se sentirem produtivas quando não o são. No Capítulo 5, discutiremos o estabelecimento de um plano sobre como o seu código deverá ser escrito, esboçando um conjunto de padrões de codificação. Nessa ocasião, ficará muito mais claro que você deverá refatorar somente se essa tarefa resultar em melhorias em sua arquitetura ou se estiver de acordo com os seus padrões de codificação.
26
Refatoração de CSS
Tenho permissão para refatorar o meu próprio código? Se você estiver trabalhando em um projeto pessoal, a resposta é um sonoro “sim”, mas, se estiver trabalhando em uma empresa na qual você não seja necessariamente o responsável, a resposta talvez não seja tão clara. Em um mundo perfeito, toda empresa compreenderia a importância da refatoração, mas muitas vezes a realidade não é assim. Se os seus colegas na empresa não tiverem o conhecimento técnico acerca da refatoração, você poderá tentar instruí-los – ouvi dizer que livros sobre Refatoração de CSS constituem ótimos presentes! Pessoas sensatas, responsáveis por garantir que os softwares sejam lançados com um código de alta qualidade, provavelmente entenderão isso, mas aqueles que não compreendem poderão argumentar que: • Gastar tempo reescrevendo código sem ver mudanças é um desperdício de tempo e de dinheiro. • Se não estiver quebrado, não é necessário consertar. • Você deveria ter escrito o código corretamente na primeira vez. Se você se deparar com algum desses argumentos e se sentir confiante o suficiente para fazer uma refatoração, o meu conselho é fazê-lo de qualquer modo, desde que você consiga respeitar o cronograma e tenha cuidado para não causar falhas em nada. Se você ouviu afirmações como essas, estou inclinado a apostar que a pessoa que as fez jamais participou de uma revisão de código, portanto suas alterações provavelmente não serão notadas de qualquer maneira. No entanto, se você estiver refatorando código simplesmente por refatorar, poderá considerar esperar até que se torne mais evidente que as alterações são necessárias; uma otimização prematura muitas vezes pode ser tão ruim quanto um débito técnico.
Exemplos de refatoração Agora que você tem uma ideia geral das vantagens da refatoração e quando é (ou não) uma boa ideia fazê-la, podemos começar a discutir o modo de refatorar o seu código.
Capítulo 1 ■ Refatoração e arquitetura
27
Embora este livro seja sobre refatoração de CSS, é muito mais fácil analisar inicialmente o conceito com um código que calcule um valor discreto, em oposição a um código que altere a aparência de elementos HTML. Assim, o nosso primeiro exemplo mostrará a refatoração de um código JavaScript básico que calcula o preço total de um pedido de compra pela internet. No segundo exemplo, faremos a refatoração de CSS. Exemplos de código Pelo fato de poder ser difícil entender o que acontece em trechos longos de código que ocupem várias páginas e arquivos, partes menores de código serão usadas nos exemplos deste livro. Todo o código JavaScript de nosso primeiro exemplo pode ser incluído em um arquivo HTML para facilitar a execução. Nos exemplos mais complicados, o CSS usado para definir a aparência geral dos elementos nos exemplos será incluído por meio de um arquivo CSS separado. Neste livro, os estilos incluídos inline entre tags <style> e </style> serão diretamente relevantes para o exemplo em questão e serão usados para ilustrar um conceito específico. Todos os exemplos de código estão disponíveis online no site associado ao livro (https://www.cssrefactoringbook.com).
Exemplo de refatoração 1: calculando o preço total em um pedido de compra pela internet O Exemplo 1.1 contém um pouco de JavaScript para calcular o preço total de um pedido de compra pela internet se os seguintes dados forem fornecidos: • O preço de cada item comprado • A quantidade de cada item comprado • O custo de frete de cada item comprado • Informações do cliente para o envio das mercadorias • Um código de desconto opcional que pode reduzir o preço do pedido
28
Refatoração de CSS
Exemplo 1.1 – Calculando o total de um pedido de compra pela internet /** * Calcula o preço total do pedido após aplicar os custos de frete, os * descontos e os impostos. * * @param {Object} customer – uma coleção de informações sobre * a pessoa que fez o pedido. * * @param {Array.<Object>} lineItems – uma coleção de produtos * e quantidades compradas, assim como o custo de frete de uma unidade. * * @param {string} discountCode – um código de desconto opcional que * pode acionar um desconto a ser deduzido antes de o frete e o imposto * serem adicionados. */ var getOrderTotal = function (customer, lineItems, discountCode) { var discountTotal = 0; var lineItemTotal = 0; var shippingTotal = 0; var taxTotal = 0; for (var i = 0; i < lineItems.length; i++) { var lineItem = lineItems[i]; lineItemTotal += lineItem.price * lineItem.quantity; shippingTotal += lineItem.shippingPrice * lineItem.quantity; } if (discountCode === '20PERCENT') { discountTotal = lineItemTotal * 0.2; } if (customer.shiptoState === 'CA') { taxTotal = (lineItemTotal - discountTotal) * 0.08; } var total = ( lineItemTotal discountTotal + shippingTotal + taxTotal
Capítulo 1 ■ Refatoração e arquitetura
29
); return total; };
Chamar getOrderTotal com os dados do Exemplo 1.2 resulta na exibição de Total: $266. O Exemplo 1.3 explica por que esse resultado é mostrado.
Exemplo 1.2 – Executando getOrderTotal com uma entrada para teste var lineItem1 = { price: 50, quantity: 1, shippingPrice: 10 }; var lineItem2 = { price: 100, quantity: 2, shippingPrice: 20 }; var lineItems = [lineItem1, lineItem2]; var customer = { shiptoState: 'CA' }; var discountCode = '20PERCENT'; var total = getOrderTotal(customer, lineItems, discountCode); document.writeln('Total: $' + total);
Exemplo 1.3 – Explicando por que getOrderTotal exibe “Total: $266” discountTotal = 0 lineItemTotal = 0 shippingTotal = 0 taxTotal = 0 # Primeira iteração do LAÇO FOR: lineItemTotal = 0 + (50 * 1) = 50 shippingTotal = 0 + (10 * 1) = 10
30
Refatoração de CSS
# Segunda iteração do LAÇO FOR: lineItemTotal = 50 + (100 * 2) = 250 shippingTotal = 10 + (20 * 2) = 50 # discountTotal é calculado porque discountCode é igual a “20PERCENT": discountTotal = 250 * 0.2 = 50 # taxTotal é definido porque customer.shiptoState é igual a “CA": taxTotal = (250 - 50) * 0.08 = 16 total = 250 - 50 + 50 + 16 = 266
Testes de unidade Após analisar os cálculos, vemos que a matemática está correta e tudo parece estar funcionando conforme esperado. Para garantir que tudo continue funcionando com o passar do tempo, podemos escrever agora um teste de unidade (unit test). Falando de modo simples, um teste de unidade é um código que executa outro código a fim de garantir que tudo esteja funcionando conforme esperado. Os testes de unidade devem ser escritos para testar partes únicas da funcionalidade de modo a restringir a causa-raiz de qualquer problema que possa surgir. Mais tarde, uma suíte de testes de unidade escrita para o seu projeto como um todo deve ser executada anteriormente ao lançamento de novos códigos para que bugs introduzidos no sistema possam ser descobertos e corrigidos antes que seja tarde demais. Os dados de entrada do Exemplo 1.2 podem ser usados para escrever um teste de unidade, mostrado no Exemplo 1.4, que confirma se a função devolve o valor esperado (266). Após a execução do teste, um contador de quantos testes com e sem sucesso foram executados, além de uma lista com os testes sem sucesso, serão exibidos.
Exemplo 1.4 – Um teste de unidade para getOrderTotal var successfulTestCount = 0; var unsuccessfulTestCount = 0; var unsuccessfulTestSummaries = []; /** * Confirma se os cálculos em `getOrdertotal()` estão corretos. */
Capítulo 1 ■ Refatoração e arquitetura var testGetOrderTotal = function () { // define as expectativas var expectedTotal = 266; // configura os dados para o teste var lineItem1 = { price: 50, quantity: 1, shippingPrice: 10 }; var lineItem2 = { price: 100, quantity: 2, shippingPrice: 20 }; var lineItems = [lineItem1, lineItem2]; var customer = { shiptoState: 'CA' }; var discountCode = '20PERCENT'; var total = getOrderTotal(customer, lineItems, discountCode); // testa os resultados em relação às expectativas if (total === expectedTotal) { successfulTestCount++; } else { unsuccessfulTestCount++; unsuccessfulTestSummaries.push( 'testGetOrderTotal: expected ' + expectedTotal + '; actual ' + total ); } };
31
32
Refatoração de CSS
// executa os testes testGetOrderTotal(); document.writeln(successfulTestCount + ' successful test(s)<br/>'); document.writeln(unsuccessfulTestCount + ' unsuccessful test(s)<br/>'); if (unsuccessfulTestCount) { document.writeln('<ul>'); for(var i = 0; i < unsuccessfulTestSummaries.length; i++) { document.writeln('<li>' + unsuccessfulTestSummaries[i] + '</li>'); } document.writeln('</ul>'); }
A execução de testGetOrderTotal resulta no teste passando com sucesso pela asserção, conforme podemos ver na Figura 1.1.
Figura 1.1 – Resultado do teste de unidade mostrando que houve sucesso.
No futuro, porém, se por algum motivo um bug for introduzido e o multiplicador usado no cálculo de discountTotal mudar de 0.2 para –0.2, o teste não seria mais bem-sucedido e veríamos o resultado exibido na Figura 1.2. Os testes de unidade são uma maneira eficaz de garantir que seu sistema continue funcionando conforme esperado com o passar do tempo. Eles podem ser especialmente úteis quando reescrevemos código porque uma asserção já estará documentada, e essa asserção permitirá ter mais certeza de que o comportamento do código não sofreu alterações.
Capítulo 1 ■ Refatoração e arquitetura
33
Figura 1.2 – Resultado do teste de unidade mostrando que não houve sucesso.
Agora que compreendemos o código usado para calcular o preço total de um pedido de compra pela internet e temos um teste de unidade associado a ele, vamos ver como a refatoração pode melhorar a situação.
Refatorando getOrderTotal Uma observação mais atenta de getOrderTotal revela que há uma série de cálculos sendo efetuados nessa função: • O desconto total a ser subtraído do preço final • O preço total de todos os itens em cada linha • Os custos totais de frete • Os custos totais de impostos • O preço total do pedido Se um bug for acidentalmente introduzido em um desses cinco cálculos, o teste de unidade (testGetOrderTotal) informará que algo deu errado, mas não será óbvio o que especificamente deu errado. Esse é o principal motivo pelo qual os testes de unidade devem ser escritos para testar partes únicas da funcionalidade.
34
Refatoração de CSS
Para deixar o código mais granular, cada um dos cálculos mencionados anteriormente deve ser extraído em uma função separada com um nome que descreva o que ela faz, como no Exemplo 1.5.
Exemplo 1.5 – Extraindo fragmentos de código para compor novas funções /** * Calcula o preço total de todos os itens pedidos. * * @param {Array.<Object>} lineItems – uma coleção de produtos * e quantidades compradas e o custo de frete de uma unidade. * * @returns {number} O preço total de todos os itens pedidos. */ var getLineItemTotal = function (lineItems) { var lineItemTotal = 0; for (var i = 0; i < lineItems.length; i++) { var lineItem = lineItems[i]; lineItemTotal += lineItem.price * lineItem.quantity; } return lineItemTotal; }; /** * Calcula o custo total de frete para todos os itens pedidos. * * @param {Array.<Object>} lineItems – uma coleção de produtos * e quantidades compradas e o custo de frete de uma unidade. * * @returns {number} O preço total de frete para todos os itens pedidos. */ var getShippingTotal = function (lineItems) { var shippingTotal = 0; for (var i = 0; i < lineItems.length; i++) { var lineItem = lineItems[i]; shippingTotal += lineItem.shippingPrice * lineItem.quantity; } return shippingTotal;
Capítulo 1 ■ Refatoração e arquitetura
35
}; /** * Calcula o desconto total a ser subtraído do total de um pedido. * * @param {number} lineItemTotal – O preço total de todos os itens pedidos. * * @param {string} discountCode – Um código de desconto opcional que pode * acionar um desconto a ser deduzido antes de o frete e o imposto * serem adicionados. * * @returns {number} O total de desconto a ser subtraído do total de um pedido. */ var getDiscountTotal = function (lineItemTotal, discountCode) { var discountTotal = 0; if (discountCode === '20PERCENT') { discountTotal = lineItemTotal * 0.2; } return discountTotal; }; /** * Calcula o total de impostos a ser aplicado a um pedido. * * @param {number} lineItemTotal – O preço total de todos os itens pedidos. * * @param {Object} customer – Uma coleção de informações sobre a pessoa que * fez um pedido. * * @returns {number} O total de impostos a ser aplicado a um pedido. */ var getTaxTotal = function (lineItemTotal, customer){ var taxTotal = 0; if (customer.shiptoState === 'CA') { taxTotal = lineItemTotal * 0.08; } return taxTotal; };
36
Refatoração de CSS
Cada nova função também deve ter um teste de unidade que a acompanha, como o teste mostrado no Exemplo 1.6.
Exemplo 1.6 – Testes de unidade para as funções extraídas escritas em JavaScript /** * Confirma se getLineItemTotal funciona conforme esperado. */ var testGetLineItemTotal = function () { var lineItem1 = { price: 50, quantity: 1 }; var lineItem2 = { price: 100, quantity: 2 }; var lineItemTotal = getLineItemTotal([lineItem1, lineItem2]); var expectedTotal = 250; if (lineItemTotal === expectedTotal) { successfulTestCount++; } else { unsuccessfulTestCount++; unsuccessfulTestSummaries.push( 'testGetLineItemTotal: expected ' + expectedTotal + '; actual ' + lineItemTotal ); } }; /** * Confirma se getShippingTotal funciona conforme esperado. */ var testGetShippingTotal = function () { var lineItem1 = { quantity: 1, shippingPrice: 10 };
Capítulo 1 ■ Refatoração e arquitetura
37
var lineItem2 = { quantity: 2, shippingPrice: 20 }; var shippingTotal = getShippingTotal([lineItem1, lineItem2]); var expectedTotal = 250; if (shippingTotal === expectedTotal) { successfulTestCount++; } else { unsuccessfulTestCount++; unsuccessfulTestSummaries.push( 'testGetShippingTotal: expected ' + expectedTotal + '; actual ' + shippingTotal ); } }; /** * Garante que GetDiscountTotal funcione conforme esperado quando um código * de desconto válido for usado. */ var testGetDiscountTotalWithValidDiscountCode = function () { var discountTotal = getDiscountTotal(100, '20PERCENT'); var expectedTotal = 20; if (discountTotal === expectedTotal) { successfulTestCount++; } else { unsuccessfulTestCount++; unsuccessfulTestSummaries.push( 'testGetDiscountTotalWithValidDiscountCode: expected ' + expectedTotal + '; actual ' + discountTotal ); } }; /** * Garante que GetDiscountTotal funcione conforme esperado quando um código * de desconto inválido for usado.
38
Refatoração de CSS
*/ var testGetDiscountTotalWithInvalidDiscountCode = function () { var discountTotal = GetDiscountTotal (100, '90PERCENT'); var expectedTotal = 0; if (discountTotal === expectedTotal) { successfulTestCount++; } else { unsuccessfulTestCount++; unsuccessfulTestSummaries.push( 'testGetDiscountTotalWithInvalidDiscountCode: expected ' + expectedTotal + '; actual ' + discountTotal ); } }; /** * Garante que GetTaxTotal funcione conforme esperado quando o cliente * mora na Califórnia. */ var testGetTaxTotalForCaliforniaResident = function () { var customer = { shiptoState: 'CA' }; var taxTotal = getTaxTotal(100, customer); var expectedTotal = 8; if (taxTotal === expectedTotal) { successfulTestCount++; } else { unsuccessfulTestCount++; unsuccessfulTestSummaries.push( 'testGetTaxTotalForCaliforniaResident: expected ' + expectedTotal + '; actual ' + taxTotal ); } }; /** * Garante que GetTaxTotal funcione conforme esperado quando o cliente não
Capítulo 1 ■ Refatoração e arquitetura
39
* mora na Califórnia. */ var testGetTaxTotalForNonCaliforniaResident = function () { var customer = { shiptoState: 'MA' }; var taxTotal = getTaxTotal(100, customer); var expectedTotal = 0; if (taxTotal === expectedTotal) { successfulTestCount++; } else { unsuccessfulTestCount++; unsuccessfulTestSummaries.push( 'testGetTaxTotalForNonCaliforniaResident: expected ' + expectedTotal + '; actual ' + taxTotal ); } };
Por fim, getOrderTotal deve ser modificada para usar as novas funções, como vemos no Exemplo 1.7.
Exemplo 1.7 – Modificando getOrderTotal para que use as funções extraídas /** * Calcula o preço total do pedido após aplicar os custos de frete, os * descontos e os impostos. * * @param {Object} customer - uma coleção de informações sobre * a pessoa que fez o pedido. * * @param {Array.<Object>} lineItems - uma coleção de produtos * e as quantidades compradas e o custo de frete de uma unidade. * * @param {string} discountCode - um código de desconto opcional que pode * acionar um desconto a ser deduzido antes de o frete e o imposto serem * adicionados. */
40
Refatoração de CSS
var getOrderTotal = function (customer, lineItems, discountCode) { var lineItemTotal = getLineItemTotal(lineItems); var shippingTotal = getShippingTotal(lineItems); var discountTotal = getDiscountTotal(lineItemTotal, discountCode); var taxTotal = getTaxTotal(lineTtemTotal, customer); return lineItemTotal - discountTotal + shippingTotal + taxTotal; };
Após analisar o código anterior, as seguintes observações podem ser feitas: • Há mais funções do que antes. • Há mais testes de unidade do que antes. • Cada função executa uma tarefa em particular. • Cada função tem um teste de unidade que a acompanha. • As funções podem ser usadas em conjunto para fazer cálculos mais complexos. De modo geral, esse código tem um formato muito melhor agora. Os cálculos individuais usados em getOrderTotal foram extraídos e cada cálculo tem um teste de unidade que o acompanha. Isso significa que será muito mais simples apontar com exatidão qual parte da funcionalidade está com problemas caso um bug seja introduzido no código. Além do mais, se os totais para imposto ou frete tiverem de ser calculados em outra parte do código, a funcionalidade existente, que já tem testes de unidade, poderá ser usada.
Exemplo de refatoração 2: um exemplo simples de refatoração de CSS O Exemplo 1.8 apresenta um código que exibe o título de um site.
Exemplo 1.8 – HTML para o título de um site <!doctype html> <html> <head> <title>Ferguson's Cat Shelter</title> <link rel="stylesheet" type="text/css" href="css/style.css" />
Capítulo 1 ■ Refatoração e arquitetura
41
</head> <body> <main> <h1 style="font-family: Helvetica, Arial, sans-serif;font-size: 36px; font-weight: 400;text-align: center;"> San Francisco's Premiere Cat Shelter </h1> </main> </body> </html>
Se abrirmos um navegador e carregarmos index.html, veremos o que está sendo exibido na Figura 1.3.
Figura 1.3 – Imagem de tela do título do site.
Em nosso primeiro exemplo de refatoração, escrevemos um teste de unidade para o código antes de refatorá-lo a fim de garantir que o seu comportamento não mudaria. Ao refatorar o CSS, continua sendo importante garantir que suas modificações não alterem nada; infelizmente, porém, não é tão simples fazer isso porque algo visual está sendo testado, não uma funcionalidade que gera valores discretos. O Capítulo 5 discute técnicas úteis para manter a igualdade do ponto de vista visual. Por enquanto, porém, simplesmente obter uma imagem de tela para fornecer uma referência visual antes da refatoração será suficiente.
42
Refatoração de CSS
Refatorando o título do site Observando o código no Exemplo 1.8, está claro que há espaço para melhorias, pois o título, representado por uma tag <h1>, tem os estilos incluídos no atributo style. Quando os estilos estão embutidos no HTML por meio do atributo style de um elemento ou entre tags <style></style>, eles são conhecidos como estilos inline. De modo muito semelhante à função original no Exemplo 1.1 que fazia vários cálculos, os estilos inline não são muito reutilizáveis. Quando os estilos são definidos com o atributo style, eles só podem ser aplicados a esse elemento em particular. Se os estilos estiverem incluídos entre tags <style></style>, eles só poderão ser aplicados a essa página específica. Como a maioria dos sites tem várias páginas, e cada página poderia ter um título, esses estilos devem ser extraídos do HTML e colocados em um arquivo CSS separado (nesse caso, em style.css) que possa ser incluído em várias páginas e carregado no cache pelo navegador. O Exemplo 1.9 exibe o conteúdo de style.css e o Exemplo 1.10 mostra o HTML com o CSS inline extraído.
Exemplo 1.9 – CSS do título, extraído e inserido em style.css h1 }
{ font-family: Helvetica, Arial, sans-serif; font-size: 36px; font-weight: 400; text-align: center;
Exemplo 1.10 – HTML com o CSS inline extraído <!doctype html> <html> <head> <title>Ferguson's Cat Shelter</title> <link rel="stylesheet" type="text/css" href="css/style.css" /> </head> <body> <main>
Capítulo 1 ■ Refatoração e arquitetura
43
<h1>San Francisco's Premiere Cat Shelter</h1> </main> </body> </html>
Uma rápida atualização no navegador mostra que nada mudou e, novamente, algumas observações podem ser feitas: • Extrair CSS inline permite reusabilidade. • Separar funcionalidades (estilos e estrutura) deixa o código mais legível. • Testes de regressão podem ser feitos manualmente em um navegador web ou comparando uma interface refatorada em relação a uma imagem de tela. Extrair estilos e colocá-los em um arquivo separado permite reutilizar códigos, pois esses estilos poderão ser usados em várias páginas. Quando o CSS está em um arquivo separado do HTML, tanto o HTML quanto o CSS se tornam mais legíveis, pois o HTML não conterá linhas extremamente longas com definições de estilo e o CSS estará agrupado em porções lógicas. Por fim, testes de mudanças podem ser feitos recarregando manualmente a página no navegador para que as mudanças possam ser comparadas em relação a uma imagem de tela obtida antes da refatoração. Embora esse exemplo tenha sido bem simples, muitas alterações pequenas como essa podem resultar em vantagens significativas com o passar do tempo.
Resumo do capítulo Concluímos o primeiro capítulo e sabemos o que é refatoração e como ela se relaciona com a arquitetura de software. Também aprendemos por que a refatoração é importante e quando ela deve ser feita. Por fim, analisamos dois exemplos de refatoração e conhecemos os testes de unidade. A seguir, veremos o que é a cascata, que, sem dúvida, é o conceito mais importante a ser compreendido quando se trata de escrever CSS.