Grandes desenvolvedores sabem quando e como refatorar código Rails

Publicados: 2022-03-11

Refatorar em grande escala: Por que você faria algo assim?

Se não está quebrado, não conserte.

É uma frase bem conhecida, mas como sabemos, a maior parte do progresso tecnológico humano foi feito por pessoas que decidiram consertar o que não está quebrado. Especialmente na indústria de software, pode-se argumentar que a maior parte do que fazemos é consertar o que não está quebrado.

Corrigindo funcionalidades, melhorando a interface do usuário, melhorando a velocidade e a eficiência da memória, adicionando recursos: todas essas são atividades para as quais é fácil ver se vale a pena fazer, e então nós, como desenvolvedores Rails experientes, argumentamos a favor ou contra gastar nosso tempo com elas. No entanto, há uma atividade que, na maioria das vezes, cai em uma área cinzenta – refatoração padrão e, especialmente, refatoração de código em grande escala.

O termo refatoração em larga escala merece explicação. Exatamente o que pode ser considerado “grande escala” varia de caso para caso, pois o termo é um pouco vago, mas considero qualquer coisa que afete significativamente mais do que apenas algumas classes, ou mais do que apenas um subsistema, e sua interface como “grande .” Por outro lado, qualquer refatoração Rails que fica escondida atrás da interface de uma única classe definitivamente seria “pequena”. Claro, há muita área cinzenta no meio. Finalmente, confie em seu instinto, se você tem medo de fazer isso, provavelmente é “grande”.

A refatoração, por definição, não produz nenhuma funcionalidade visível, nada que você possa mostrar ao cliente, nenhuma entrega. Na melhor das hipóteses, eles podem produzir pequenas melhorias de velocidade e uso de memória, mas esse não é o objetivo principal. Pode-se dizer que o objetivo principal é o código com o qual você está feliz. Mas porque você está reorganizando o código de tal forma que tem consequências de longo alcance em toda a base de código, há uma chance de que todo o inferno aconteça e haja problemas. É claro que é daí que vem o pavor que mencionamos. Você já apresentou alguém novo à sua base de código e depois que eles perguntaram sobre um pedaço de código peculiarmente organizado, você respondeu com algo como:

Yeeeaahh, este é o código legado que fazia sentido na época, mas as especificações mudaram e agora é muito caro corrigi-lo?

Talvez você até tenha dado a eles um olhar muito sério e dito a eles para deixarem e não tocarem.

Ao planejar como refatorar o código Rails, você pode precisar de um gráfico complexo para começar.

A pergunta: “Por que iríamos querer fazer isso?” é natural e pode ser tão importante quanto fazê-lo...

A pergunta: “Por que iríamos querer fazer isso?” é natural e pode ser tão importante quanto o processo de refatoração porque muitas vezes há outras pessoas que você precisa convencer para permitir que você gaste seu tempo caro na refatoração. Então, vamos considerar os casos em que você gostaria de fazê-lo e os benefícios a serem obtidos:

Melhorias de desempenho

Você está satisfeito com a organização atual do seu código do ponto de manutenção, mas ainda está causando problemas com o desempenho. É muito difícil otimizar a maneira como está configurado atualmente e as alterações seriam muito frágeis.

Há apenas uma coisa a fazer aqui e isso é perfil-lo extensivamente. Execute benchmarks e faça estimativas de quanto você ganhará e, em seguida, tente estimar como isso se traduzirá em ganhos concretos. Às vezes você pode até perceber que a refatoração de código proposta não vale a pena. Outras vezes, você terá dados rígidos frios para apoiar seu caso.

Melhorias arquitetônicas

Talvez a arquitetura seja boa, mas um pouco desatualizada, ou talvez seja tão ruim que você se encolhe toda vez que toca nessa parte da base de código. Funciona bem e rápido, mas é difícil adicionar novos recursos. Nessa dor reside o valor comercial da refatoração. A “dor” também significa que o processo de refatoração levará mais tempo para adicionar novos recursos, talvez muito mais.

E há um benefício a ser obtido. Faça estimativas de custo/benefício para alguns recursos de amostra com e sem sua grande refatoração proposta. Explique que diferenças semelhantes se aplicarão à maioria dos recursos futuros que tocam essa parte do sistema agora e para sempre no futuro, enquanto o sistema estiver sendo desenvolvido. Suas estimativas podem estar erradas, pois geralmente estão no desenvolvimento de software, mas suas proporções provavelmente estarão no estádio.

Atualizá-lo

Às vezes, o código é inicialmente bem escrito. Você está extremamente feliz com isso. É rápido, eficiente em termos de memória, fácil de manter e bem alinhado com as especificações. Inicialmente. Mas então as especificações mudam, as metas de negócios mudam ou você aprende algo novo sobre seus usuários finais que invalida suas suposições iniciais. O código ainda funciona bem e você ainda está muito feliz com isso, mas algo é estranho quando você o analisa no contexto do produto final. As coisas são colocadas no subsistema ligeiramente errado ou as propriedades estão na classe errada, ou talvez alguns nomes não façam mais sentido. Eles agora estão cumprindo um papel que, em termos de negócios, tem um nome completamente diferente. No entanto, ainda é muito difícil justificar qualquer tipo de refatoração Rails em larga escala, já que o trabalho envolvido será em escala com qualquer um dos outros exemplos, mas os benefícios são muito menos tangíveis. Quando você pensa sobre isso, nem é tão difícil mantê-lo. Você apenas tem que lembrar que algumas coisas são na verdade outra coisa. Você só precisa lembrar que A na verdade significa B e a propriedade Y em A na verdade se relaciona com C.

E aqui está o verdadeiro benefício. No campo da neuropsicologia existem muitos experimentos sugerindo que nossa memória de curto prazo ou de trabalho é capaz de conter apenas 7+/-2 elementos, sendo um deles o experimento de Sternberg. Quando estudamos um assunto começamos com elementos básicos e, inicialmente, quando pensamos em conceitos de nível superior temos que pensar em suas definições. Por exemplo, considere um termo simples “senha SHA256 salgada”. Inicialmente, temos que manter em nossas definições de memória de trabalho para “salted” e “SHA256” e talvez até uma definição de “função hash”. Mas uma vez que entendemos completamente o termo, ele ocupa apenas um slot de memória porque o entendemos intuitivamente. Essa é uma das razões pelas quais precisamos entender completamente os conceitos de nível inferior para poder raciocinar sobre os de nível superior. O mesmo vale para termos e definições específicos do nosso projeto. Mas se temos que lembrar a tradução para o significado real toda vez que discutimos nosso código, essa tradução está ocupando mais um daqueles preciosos slots de memória de trabalho. Isso produz carga cognitiva e torna mais difícil raciocinar através da lógica em nosso código. Por sua vez, se for mais difícil de raciocinar, significa que há uma chance maior de ignorarmos um ponto importante e introduzirmos um bug.

E não vamos esquecer os efeitos colaterais mais óbvios. Há uma boa chance de confusão ao discutir mudanças com nosso cliente ou qualquer pessoa familiarizada com os termos comerciais corretos. As novas pessoas que se juntam à equipe precisam se familiarizar tanto com a terminologia de negócios quanto com suas contrapartes no código.

Eu acho que essas razões são muito convincentes e justificam o custo da refatoração em muitos casos. Ainda assim, tenha cuidado, pode haver muitos casos extremos em que você precisa usar seu melhor julgamento para determinar quando e como refatorar.

Em última análise, a refatoração em larga escala é boa pelas mesmas razões que muitos de nós gostamos de iniciar um novo projeto. Você olha para aquele arquivo de origem em branco e um admirável mundo novo começa a girar em sua mente. Desta vez você vai fazer isso direito, o código será elegante, será muito bem definido, rápido, robusto e facilmente extensível e, o mais importante, será uma alegria trabalhar com ele todos os dias. A refatoração, em pequena e grande escala, permite que você recupere esse sentimento, dê nova vida a uma base de código antiga e pague essa dívida técnica.

Por fim, é melhor que a refatoração seja orientada por planos para facilitar a implementação de um determinado novo recurso. Nesse caso, a refatoração será mais focada e muito do tempo gasto na refatoração também será recuperado imediatamente por meio da implementação mais rápida do próprio recurso.

Preparação

Certifique-se de que sua cobertura de teste seja muito boa em todas as áreas da base de código que você provavelmente tocará. Se você vir certas partes que não estão bem cobertas, primeiro passe algum tempo aumentando a cobertura do teste. Se você não tiver nenhum teste, primeiro deve gastar tempo criando esses testes. Se você não puder criar um conjunto de testes adequado, concentre-se nos testes de aceitação e escreva o máximo que puder, e certifique-se de escrever testes de unidade enquanto refatora. Teoricamente, você pode fazer a refatoração de código sem uma boa cobertura de teste, mas isso exigirá que você faça muitos testes manuais e faça isso com frequência. Levará muito mais tempo e será mais propenso a erros. Em última análise, se sua cobertura de teste não for boa o suficiente, o custo de realizar uma refatoração Rails em larga escala pode ser tão alto que você deve, lamentavelmente, considerar não fazer isso. Na minha opinião, esse é um benefício dos testes automatizados que não é enfatizado com frequência suficiente. Testes automatizados permitem que você refatore com frequência e, mais importante, seja mais ousado.

Depois de verificar se a cobertura do teste é boa, é hora de começar a mapear suas alterações. No começo você não deveria estar fazendo nenhuma codificação. Você precisa mapear aproximadamente todas as mudanças envolvidas e rastrear todas as consequências através da base de código, bem como carregar o conhecimento sobre tudo isso em sua mente. Seu objetivo é entender exatamente por que você está alterando algo e o papel que isso desempenha na base de código. Se você tropeçar mudando as coisas apenas porque parece que precisam ser mudadas ou porque algo quebrou e isso parece consertá-lo, você provavelmente acabará em um beco sem saída. O novo código parece funcionar, mas incorretamente, e agora você nem consegue se lembrar de todas as alterações que fez. Neste ponto, você pode precisar abandonar o trabalho que fez na refatoração de código em larga escala e, essencialmente, perdeu seu tempo. Portanto, tome seu tempo e explore o código para entender as ramificações de cada mudança que você está prestes a fazer. Ele vai pagar generosamente no final.

Você precisará de uma ajuda para o processo de refatoração. Você pode preferir outra coisa, mas eu gosto de um simples pedaço de papel em branco e uma caneta. Começo escrevendo a alteração inicial que quero fazer no canto superior esquerdo do papel. Então começo a procurar todos os lugares afetados pela mudança e anoto-os sob a mudança inicial. É importante aqui usar seu julgamento. Em última análise, as notas e diagramas no papel estão lá para você, então escolha um estilo que melhor se adapte à sua memória. Escrevo pequenos trechos de código com marcadores abaixo deles e muitas setas que levam a outras notas indicando coisas que dependem disso diretamente (seta completa) ou indiretamente (setas tracejadas). Eu também anoto as setas com marcas abreviadas como um lembrete para alguma coisa específica que notei na base de código. Lembre-se, você só voltará a essas notas nos próximos dias enquanto realiza as alterações planejadas nelas e é perfeitamente aceitável usar lembretes muito curtos e enigmáticos para que eles usem menos espaço e sejam mais fáceis de colocar no papel . Algumas vezes eu estava limpando minha mesa meses depois de uma refatoração do Rails e encontrei um desses papéis. Era uma bobagem completa, eu não tinha a menor ideia do que significava alguma coisa naquele papel, exceto que poderia ter sido escrito por alguém enlouquecido. Mas eu sei que aquele pedaço de papel era indispensável enquanto eu trabalhava no problema. Além disso, não pense que você precisa escrever todas as alterações. Você pode agrupá-los e acompanhar os detalhes de uma maneira diferente. Por exemplo, em seu artigo principal, você pode observar que precisa “renomear todas as ocorrências de Ab para Cd” e, em seguida, pode rastrear as especificidades de várias maneiras diferentes. Você pode escrevê-los todos em um pedaço de papel separado, você pode planejar realizar uma pesquisa global para todas as ocorrências dele mais uma vez, ou você pode simplesmente deixar todos os arquivos de origem onde a alteração precisa ser feita aberta em seu editor de escolha e faça uma nota mental para repassá-los assim que terminar de mapear as alterações.

Quando você mapear as consequências de sua mudança inicial, pela natureza de ser em grande escala, você provavelmente identificará mudanças adicionais que têm consequências adicionais. Repita a análise para eles também, observando todas as alterações dependentes. Dependendo do tamanho das alterações, você pode escrevê-las no mesmo pedaço de papel ou escolher um novo em branco. Uma coisa muito importante para tentar fazer ao mapear as mudanças é tentar identificar os limites onde você pode realmente interromper as mudanças de ramificação. Você deseja limitar a refatoração ao menor conjunto de alterações sensatas e arredondadas. Se você vir um ponto em que pode simplesmente parar e deixar o resto como está, faça-o mesmo que veja que deve ser refatorado, mesmo que esteja relacionado em conceito às suas outras alterações. Termine esta rodada de refatoração de código, teste minuciosamente, implante e volte para mais. Você deve procurar ativamente esses pontos para manter o tamanho das alterações gerenciáveis. Claro, como sempre, faça um julgamento. Muitas vezes cheguei a um ponto em que eu poderia cortar o processo de refatoração adicionando algumas classes proxy para fazer um pouco de tradução de interface. Eu até comecei a implementá-los quando percebi que eles dariam tanto trabalho quanto empurrar a refatoração um pouco mais até o ponto em que haverá um ponto de “parada natural” (ou seja, quase nenhum código proxy necessário). Eu então voltei atrás, revertendo minhas últimas mudanças e refatorei. Se tudo isso soa um pouco como mapear um território inexplorado é porque eu sinto que é, exceto que os mapas de território são apenas bidimensionais.

Execução

Depois de fazer a preparação da refatoração, é hora de executar o plano. Certifique-se de que sua concentração está alta e garanta um ambiente livre de distrações. Às vezes, chego a desligar completamente a conexão com a Internet neste momento. O problema é que, se você se preparou bem, tenha um bom conjunto de anotações no papel ao seu lado e sua concentração estará em alta! Muitas vezes você pode se mover muito rápido através das mudanças dessa maneira. Em teoria, a maior parte do trabalho foi feito de antemão, durante a preparação.

Quando você estiver realmente refatorando o código, preste atenção em bits estranhos de código que fazem algo muito específico e podem parecer um código ruim. Talvez eles sejam códigos ruins, mas muitas vezes eles estão lidando com um caso estranho que foi descoberto enquanto investigava um bug na produção. Com o tempo, a maioria dos códigos Rails cresce “cabelos” ou “verrugas” que estão lidando com bugs estranhos, por exemplo, um código de resposta estranho aqui que talvez seja necessário para o IE6 ou uma condição lá que lida com um bug de tempo estranho. Eles não são importantes para o quadro geral, mas ainda são detalhes significativos. Idealmente, eles são explicitamente cobertos com testes de unidade, se não, tente primeiro cobri-los. Certa vez, fui encarregado de portar um aplicativo de médio porte do Rails 2 para o Rails 3. Eu estava muito familiarizado com o código, mas era um pouco confuso e havia muitas mudanças a serem consideradas, então optei pela reimplementação. Na verdade, não foi uma reimplementação real, já que dificilmente é uma jogada inteligente, mas comecei com um aplicativo Rails 3 em branco e refatorei fatias verticais do aplicativo antigo no novo, usando aproximadamente o processo descrito. Cada vez que eu terminava uma fatia vertical, eu percorria o código Rails antigo, examinando cada linha e verificando se havia sua contraparte no novo código. Eu estava essencialmente, pegando todos os “cabelos” de código antigo e replicando-os na nova base de código. No final, a nova base de código teve todos os casos de canto resolvidos.

Certifique-se de realizar testes manuais com frequência suficiente. Isso irá forçá-lo a procurar por “interrupções” naturais no processo de refatoração que permitirão que você teste uma parte do sistema, bem como lhe dará a confiança de que você não quebrou nada que não esperava quebrar no processo .

Embrulhe isso

Assim que você terminar de refatorar seu código Rails, certifique-se de revisar todas as suas alterações uma última vez. Olhe para o diff inteiro e passe por cima dele. Muitas vezes, você notará coisas sutis que perdeu no início da refatoração porque não tinha o conhecimento que tem agora. É um bom benefício da refatoração em larga escala: você obtém uma imagem mental mais clara da organização do código, especialmente se você não a escreveu originalmente.

Se possível, peça a um colega para revisá-lo também. Ele nem precisa estar particularmente familiarizado com essa parte exata da base de código, mas deve ter uma familiaridade geral com o projeto e seu código. Ter um novo conjunto de olhos sobre as mudanças pode ajudar muito. Se você absolutamente não conseguir que outro desenvolvedor os veja, você terá que fingir ser um. Tenha uma boa noite de sono e revise-o com uma mente fresca.

Se você não tiver controle de qualidade, terá que usar esse chapéu também. Mais uma vez, faça uma pausa e se afaste do código e volte para realizar o teste manual. Você acabou de passar pelo equivalente a entrar em um armário de fiação elétrica desordenado com um monte de ferramentas e resolver tudo, possivelmente cortando e reconectando coisas, então é preciso ter um pouco mais de cuidado do que o normal.

Por fim, aproveite os frutos do seu trabalho considerando todas as mudanças planejadas que agora serão muito mais limpas e fáceis de implementar.

Quando você não faria isso?

Embora haja muitos benefícios em realizar refatoração em larga escala regularmente para manter o código do projeto atualizado e de alta qualidade, ainda é uma operação muito cara. Há também casos em que não seria aconselhável:

Sua cobertura de teste é ruim

Como foi mencionado: uma cobertura de teste muito fraca pode ser um grande problema. Use seu próprio julgamento, mas pode ser melhor a curto prazo se concentrar em aumentar a cobertura enquanto trabalha em novos recursos e executa o maior número possível de refatorações localizadas em pequena escala. Isso o ajudará muito quando você decidir mergulhar e classificar partes maiores da base de código.

A refatoração não é impulsionada por um novo recurso e a base de código não muda há muito tempo

Eu usei o tempo passado em vez de dizer “a base de código não mudará” de propósito. A julgar pela experiência (e por experiência quero dizer estar errado muitas vezes), você quase nunca pode confiar em suas previsões sobre quando uma certa parte da base de código precisará ser alterada. Então, faça a próxima melhor coisa: olhe para o passado e assuma que o passado se repetirá. Se algo não foi alterado por um longo tempo, você provavelmente não precisa alterá-lo agora. Espere essa mudança acontecer e trabalhe em outra coisa.

Você está pressionado pelo tempo

A manutenção é a parte mais cara do ciclo de vida do projeto e a refatoração a torna menos cara. É absolutamente necessário que qualquer empresa utilize a refatoração para reduzir a dívida técnica e tornar a manutenção futura mais barata. Caso contrário, corre-se o risco de entrar em um ciclo vicioso em que se torna cada vez mais caro adicionar novos recursos. Espero que seja evidente por que isso é ruim.

Dito isso, a refatoração em larga escala é muito, muito imprevisível quando se trata de quanto tempo levará, e você não deve fazer isso pela metade. Se, por qualquer motivo interno ou externo, você estiver sendo pressionado pelo tempo e não tiver certeza de que será capaz de terminar dentro desse prazo, talvez seja necessário abandonar a refatoração. Pressão e estresse, especialmente do tipo induzido pelo tempo, levam a um nível mais baixo de concentração, o que é absolutamente necessário para refatoração em grande escala. Trabalhe para obter mais “compras” de sua equipe para reservar tempo para isso e olhe em seu calendário por um período em que você terá tempo. Não é necessário que seja um período contínuo de tempo. É claro que você terá outros problemas para resolver, mas esses intervalos não devem ser maiores que um ou dois dias. Nesse caso, você terá que se lembrar de seu próprio plano, porque começará a esquecer o que aprendeu sobre a base de código e exatamente onde parou.

Conclusão

Espero ter dado algumas orientações úteis e convencido você dos benefícios, e ouso dizer da necessidade, de realizar refatoração em grande escala em certas ocasiões. O tópico é muito vago e, claro, nada dito aqui é uma verdade definitiva e os detalhes variam de projeto para projeto. Tentei dar conselhos, que na minha opinião são geralmente aplicáveis, mas como sempre, considere o seu caso particular e use a sua própria experiência para se adaptar aos seus desafios específicos. Boa sorte na refatoração!