Às vezes, você se depara com uma situação em que precisa estender / melhorar algum código existente. Você vê que o código antigo é muito enxuto, mas também é difícil de estender e leva tempo para ler.
É uma boa idéia substituí-lo por código moderno?
Há algum tempo, gostei da abordagem lean, mas agora parece-me que é melhor sacrificar muitas otimizações em favor de abstrações mais altas, interfaces melhores e código mais legível e extensível.
Os compiladores parecem estar melhorando também, então coisas como struct abc = {}
são silenciosamente transformadas em memset
s, shared_ptr
s estão praticamente produzindo o mesmo código que o ponteiro bruto, os modelos estão funcionando super bem porque produzem código super enxuto e assim por diante.
Mas, ainda assim, às vezes você vê matrizes baseadas em pilha e funções C antigas com alguma lógica obscura, e geralmente elas não estão no caminho crítico.
É uma boa ideia alterar esse código se você precisar tocar em um pequeno pedaço dele de qualquer maneira?
Respostas:
Onde?
Em uma página inicial de um site em escala do Google, não é aceitável. Mantenha as coisas o mais rápido possível.
Em uma parte de um aplicativo usado por uma pessoa uma vez por ano, é perfeitamente aceitável sacrificar o desempenho para obter a legibilidade do código.
Em geral, quais são os requisitos não funcionais para a parte do código em que você está trabalhando? Se uma ação deve executar abaixo de 900 ms. em um determinado contexto (máquina, carga, etc.) 80% do tempo e, na verdade, ele executa menos de 200 ms. Em 100% do tempo, com certeza, torne o código mais legível, mesmo que possa impactar levemente o desempenho. Se, por outro lado, a mesma ação nunca foi executada em menos de dez segundos, bem, você deve tentar ver o que há de errado com o desempenho (ou o requisito em primeiro lugar).
Além disso, como a melhoria da legibilidade diminuirá o desempenho? Muitas vezes, os desenvolvedores estão adaptando o comportamento próximo à otimização prematura: eles têm medo de aumentar a legibilidade, acreditando que isso destruirá drasticamente o desempenho, enquanto o código mais legível passará alguns microssegundos mais fazendo a mesma ação.
fonte
goto
mais rápido do que para loops. Ironicamente, o otimizador se saiu melhor com loops, tornando o código mais lento e mais difícil de ler.Geralmente não .
Alterar o código pode causar problemas imprevisíveis em outras partes do sistema (que às vezes podem passar despercebidos até muito mais tarde em um projeto, se você não tiver testes sólidos de unidade e fumaça no local). Eu costumo pensar "se não está quebrado, não conserte" a mentalidade.
A exceção a essa regra é se você estiver implementando um novo recurso que toque nesse código. Se, nesse ponto, não fizer sentido e a refatoração realmente precisar ocorrer, aguarde enquanto o tempo de refatoração (e testes e buffer suficientes para lidar com problemas indiretos) forem contabilizados em estimativas.
Obviamente, perfil, perfil, perfil , especialmente se for uma área de caminho crítico.
fonte
Em resumo: depende
Você realmente vai precisar ou usar sua versão refatorada / aprimorada?
Realmente precisa ser otimizado?
Em detalhes
Você vai precisar das coisas limpas e brilhantes?
Há coisas a serem cautelosas aqui, e você precisa identificar o limite entre o que é ganho real e mensurável e o que é apenas sua preferência pessoal e o mau hábito potencial de tocar código que não deveria ser.
Mais especificamente, saiba disso:
Existe o excesso de engenharia
É um anti-padrão e vem com problemas embutidos:
Alguns também podem mencionar o princípio do KISS como referência, mas aqui é contra-intuitivo: a maneira otimizada é a maneira simples ou a maneira limpa da arquitetura? A resposta não é necessariamente absoluta, como explicado no restante abaixo.
Você não vai precisar
O princípio YAGNI não é completamente ortogonal com a outra questão, mas ajuda a se perguntar: você vai precisar?
A arquitetura mais complexa realmente apresenta um benefício para você, além de parecer mais sustentável?
Se não está quebrado, não conserte
Escreva isso em um cartaz grande e pendure-o próximo à tela, na área da cozinha no trabalho ou na sala de reuniões do desenvolvedor. É claro que existem muitos outros mantras que valem a pena se repetir, mas esse em particular é importante sempre que você tenta fazer um "trabalho de manutenção" e sente vontade de "melhorá-lo".
É natural que desejemos "melhorar" o código ou até mesmo tocá-lo, mesmo inconscientemente, enquanto lemos através dele para tentar entendê-lo. É uma coisa boa, pois significa que somos opinativos e tentamos entender melhor os internos, mas também está ligado ao nosso nível de habilidade, nosso conhecimento (como você decide o que é melhor ou não? Bem, veja as seções abaixo ...) e todas as suposições que fazemos sobre o que achamos que conhecemos o software ...:
Realmente precisa ser otimizado?
Tudo isso dito, por que foi "otimizado" em primeiro lugar? Eles dizem que a otimização prematura é a raiz de todos os males, e se você vir um código não documentado e aparentemente otimizado, normalmente você poderia assumir que provavelmente não seguiu as Regras de Otimização , não precisava muito do esforço de otimização e que era apenas o a arrogância do desenvolvedor habitual está entrando em ação. Mais uma vez, talvez seja apenas a sua falando agora.
Em caso afirmativo, dentro de quais limites se torna aceitável? Se for necessário, esse limite existe e oferece espaço para melhorar as coisas, ou uma linha dura para decidir deixá-lo ir.
Além disso, cuidado com as características invisíveis. Provavelmente, sua versão "extensível" desse código também aumentará a memória em tempo de execução e apresentará ainda mais espaço na memória estática para o executável. Os recursos brilhantes de OO vêm com custos não intuitivos como esses e podem ser importantes para o seu programa e o ambiente em que ele deve rodar.
Medida, Medida, Medida
Como o pessoal do Google agora, é tudo sobre dados! Se você pode fazer backup com dados, é necessário.
Existe uma história não tão antiga que, para cada US $ 1 gasto em desenvolvimento, será seguido por pelo menos US $ 1 em testes e pelo menos US $ 1 em suporte (mas, na verdade, é muito mais).
A mudança afeta muitas coisas:
Portanto, não é apenas o consumo de recursos de hardware (velocidade de execução ou consumo de memória) que você precisa medir aqui, mas também o consumo de recursos da equipe . Ambos precisam ser previstos para definir um objetivo-alvo, para serem medidos, contabilizados e adaptados com base no desenvolvimento.
E para o seu gerente, isso significa ajustá-lo ao atual plano de desenvolvimento, por isso, comunique-o e não entre na codificação furiosa de cowboys / submarinos / black-ops.
Em geral...
Sim mas...
Não me interpretem mal, em geral, eu seria a favor de fazer o que você sugere, e eu frequentemente defendo isso. Mas você precisa estar ciente do custo a longo prazo.
Em um mundo perfeito, é a solução certa:
Na prática:
você pode piorar
Você precisa de mais olhos para vê-lo, e quanto mais o complexificar, mais você precisará.
você não pode prever o futuro
Você não pode saber com absoluta certeza se precisará disso, e nem mesmo se as "extensões" necessárias serão mais fáceis e rápidas de implementar na forma antiga, e se elas precisarão ser super otimizadas .
representa, do ponto de vista da administração, um enorme custo sem ganho direto.
Faça parte do processo
Você mencionou aqui que é uma mudança bastante pequena e tem alguns problemas específicos em mente. Eu diria que geralmente está bom neste caso, mas a maioria de nós também tem histórias pessoais de pequenas mudanças, edições quase cirúrgicas, que acabaram se transformando em pesadelo de manutenção e em prazos quase perdidos ou explodidos, porque Joe Programmer não viu um das razões por trás do código e tocou em algo que não deveria ter sido.
Se você tiver um processo para lidar com essas decisões, tire vantagem delas:
Cobertura de teste, criação de perfil e coleta de dados são complicados
Mas, é claro, seu código e métricas de teste podem sofrer os mesmos problemas que você está tentando evitar no seu código real: você testa as coisas certas, e elas são a coisa certa para o futuro, e você mede as coisas certas coisas?
Ainda assim, em geral, quanto mais você testar (até um certo limite) e medir, mais dados você coletará e mais seguro estará. Mau tempo de analogia: pense nisso como dirigir (ou a vida em geral): você pode ser o melhor motorista do mundo, se o carro avariar ou se alguém decidir se matar dirigindo seu carro hoje, o seu habilidades podem não ser suficientes. Existem duas coisas ambientais que podem afetar você, e os erros humanos também são importantes.
As revisões de código são os testes de corredor da equipe de desenvolvimento
E acho que a última parte é fundamental aqui: faça revisões de código. Você não saberá o valor de suas melhorias se as fizer sozinho. As revisões de código são o nosso "teste de corredor": siga a versão de Raymond da Lei de Linus para detectar bugs e detectar excesso de engenharia e outros antipadrões, e para garantir que o código esteja alinhado com as habilidades de sua equipe. Não faz sentido ter o "melhor" código se ninguém mais puder entender e mantê-lo, e isso vale tanto para otimizações enigmáticas quanto para projetos arquitetônicos profundos de 6 camadas.
Como palavras finais, lembre-se:
fonte
Em geral, você deve se concentrar na legibilidade primeiro e no desempenho muito mais tarde. Na maioria das vezes, essas otimizações de desempenho são insignificantes, mas o custo de manutenção pode ser enorme.
Certamente todas as "pequenas" coisas devem ser alteradas em favor da clareza, pois, como você apontou, a maioria delas será otimizada pelo compilador de qualquer maneira.
Quanto às otimizações maiores, pode haver uma chance de que as otimizações sejam realmente críticas para alcançar um desempenho razoável (embora esse não seja o caso surpreendentemente frequente). Eu faria suas alterações e depois traçaria o perfil do código antes e depois das alterações. Se o novo código tiver problemas significativos de desempenho, você sempre poderá reverter para a versão otimizada e, caso contrário, poderá continuar com a versão do código mais limpo.
Altere apenas uma parte do código de cada vez e veja como isso afeta o desempenho após cada rodada de refatoração.
fonte
Depende de por que o código foi otimizado e qual seria o efeito de alterá-lo e qual poderia ser o impacto do código no desempenho geral. Também deve depender se você tem uma boa maneira de carregar as alterações de teste.
Você não deve fazer essa alteração sem criar um perfil antes e depois e de forma perferível sob uma carga semelhante à que seria vista na produção. Isso significa não usar um pequeno subconjunto de dados em uma máquina do desenvolvedor ou testar quando apenas um usuário está usando o sistema.
Se a otimização foi recente, você poderá conversar com o desenvolvedor e descobrir exatamente qual era o problema e quão lento o aplicativo era antes da otimização. Isso pode lhe dizer muito se vale a pena fazer a otimização e para quais condições a otimização era necessária (um relatório que cobre um ano inteiro, por exemplo, pode não ter ficado lento até setembro ou outubro, se você estiver testando sua alteração em fevereiro, a lentidão talvez ainda não seja aparente e o teste é inválido).
Se a otimização for bastante antiga, os métodos mais novos podem ser mais rápidos e mais legíveis.
Em última análise, esta é uma pergunta para o seu chefe. É demorado refatorar algo que foi otimizado e garantir que a alteração não afete o resultado final e que ela tenha um desempenho tão bom ou pelo menos aceitável em comparação com a maneira antiga. Ele pode querer que você gaste seu tempo em outras áreas, em vez de assumir uma tarefa de alto risco para economizar alguns minutos de tempo de codificação. Ou ele pode concordar que o código é difícil de entender e precisa de intervenção frequente e que melhores métodos estão agora disponíveis.
fonte
se a criação de perfil mostrar que a otimização não é necessária (não está em uma seção crítica) ou ainda tem um tempo de execução pior (como resultado de uma otimização prematura ruim), substitua-o pelo código legível que é mais fácil de manter
verifique também se o código se comporta da mesma maneira com os testes apropriados
fonte
Pense nisso da perspectiva dos negócios. Quais são os custos da mudança? Quanto tempo você precisa para fazer a alteração e quanto economizará a longo prazo, tornando o código mais fácil de estender ou manter? Agora anexe um preço a esse tempo e compare-o ao dinheiro perdido, reduzindo o desempenho. Talvez você precise adicionar ou atualizar um servidor para compensar o desempenho perdido. Talvez o produto não atenda mais aos requisitos e não possa mais ser vendido. Talvez não haja perda. Talvez a mudança aumente a robustez e economize tempo em outros lugares. Agora tome sua decisão.
Em uma nota lateral, em alguns casos, pode ser possível manter as duas versões de um fragmento. Você pode escrever um teste gerando valores de entrada aleatórios e verificar os resultados com a outra versão. Use a solução "inteligente" para verificar o resultado de uma solução perfeitamente compreensível e obviamente correta e, assim, obtenha alguma garantia (mas nenhuma prova) de que a nova solução é equivalente à antiga. Ou faça o contrário e verifique o resultado do código complicado com o código detalhado e, assim, documentando a intenção por trás do hack de maneira inequívoca.
fonte
Basicamente, você está perguntando se a refatoração é um empreendimento que vale a pena. A resposta para isso é certamente sim.
Mas...
... você precisa fazer isso com cuidado. Você precisa de testes sólidos de unidade, integração, funcionalidade e desempenho para qualquer código que esteja refatorando. Você precisa ter certeza de que eles realmente testam todas as funcionalidades necessárias. Você precisa da capacidade de executá-los fácil e repetidamente. Depois disso, você poderá substituir componentes por novos componentes que contenham a funcionalidade equivalente.
Martin Fowler escreveu o livro sobre isso.
fonte
Você não deve alterar o código de produção e de trabalho sem uma boa razão. A "refatoração" não é uma razão suficientemente boa, a menos que você não possa fazer seu trabalho sem essa refatoração. Mesmo que você esteja corrigindo erros no próprio código difícil, reserve um tempo para entendê-lo e fazer a menor alteração possível. Se o código é difícil de entender, você não será capaz de entendê-lo completamente e, portanto, quaisquer alterações feitas terão efeitos colaterais imprevisíveis - erros, em outras palavras. Quanto maior a alteração, maior a probabilidade de causar problemas.
Haveria uma exceção a isso: se o código incompreensível tivesse um conjunto completo de testes de unidade, você poderia refatorá-lo. Como nunca vi ou ouvi falar de código incompreensível com testes de unidade completos, você primeiro escreve os testes de unidade, obtém o acordo das pessoas necessárias para que esses testes de unidade representem de fato o que o código deveria estar fazendo e, então, faz as alterações no código . Eu fiz isso uma ou duas vezes; é uma dor no pescoço e muito caro, mas produz bons resultados no final.
fonte
Se é apenas um pequeno pedaço de código que faz algo relativamente simples de uma maneira difícil de entender, eu mudaria o "entendimento rápido" em um comentário estendido e / ou em uma implementação alternativa não utilizada, como
fonte
A resposta é, sem perda de generalidade, sim. Sempre adicione código moderno quando achar difícil ler o código e exclua o código incorreto na maioria dos casos. Eu uso o seguinte processo:
<function>_clean()
. Em seguida, "concorra" ao seu código contra o código incorreto. Se o seu código for melhor, remova o código antigo.QED.
fonte
Se eu pudesse ensinar uma coisa ao mundo (sobre Software) antes de morrer, ensinaria que "Desempenho versus X" é um falso dilema.
A refatoração é tipicamente conhecida como um benefício pela legibilidade e confiabilidade, mas também pode ser facilmente suportada pela otimização. Ao lidar com a melhoria de desempenho como uma série de refatorações, você pode respeitar a regra do parque de campismo e também acelerar o aplicativo. Na verdade, é, pelo menos na minha opinião, incumbido eticamente de você fazê-lo.
Por exemplo, o autor desta pergunta encontrou um pedaço de código maluco. Se essa pessoa estivesse lendo meu código, descobriria que a parte maluca tem 3-4 linhas. Está em um método por si só, e o nome e a descrição do método indicam O QUE o método está fazendo. O método conteria de 2 a 6 linhas de comentários embutidos, descrevendo COMO o código maluco obtém a resposta certa, apesar de sua aparência questionável.
Compartimentado dessa maneira, você é livre para trocar as implementações desse método como desejar. De fato, foi provavelmente assim que escrevi a versão maluca para começar. Você pode tentar, ou pelo menos perguntar sobre alternativas. Na maioria das vezes, você descobrirá que a implementação ingênua é notavelmente pior (geralmente eu apenas me preocupo com uma melhoria de 2-10x), mas compiladores e bibliotecas estão sempre mudando e quem sabe o que você pode encontrar hoje que não estava disponível quando a função foi escrita?
fonte
Provavelmente não é uma boa ideia tocá-lo - se o código foi escrito dessa maneira por razões de desempenho, significa que alterá-lo pode trazer de volta problemas de desempenho que foram resolvidos anteriormente.
Se você não decidir mudar as coisas para ser mais legível e extensível: Antes de fazer uma mudança, de referência o código antigo sob pesada carga. Melhor ainda, se você puder encontrar um documento antigo ou um registro de problema descrevendo o problema de desempenho que esse código de aparência estranha deve corrigir. Depois de fazer as alterações, execute os testes de desempenho novamente. Se não for muito diferente, ou ainda dentro de parâmetros aceitáveis, provavelmente está OK.
Às vezes, pode acontecer que, quando outras partes de um sistema mudam, esse código com desempenho otimizado não precisa mais de otimizações tão pesadas, mas não há como saber disso sem testes rigorosos.
fonte
O problema aqui é distinguir "otimizado" de legível e extensível, o que nós, como usuários, vemos como código otimizado e o que o compilador vê como otimizado, são duas coisas diferentes. O código que você está olhando para alterar pode não ser um gargalo e, portanto, mesmo que o código seja "enxuto", ele nem precisa ser "otimizado". Ou, se o código for antigo o suficiente, pode haver otimizações feitas pelo compilador para os integrados, o que torna o uso de uma estrutura interna simples mais nova igualmente ou mais eficiente que o código antigo.
E o código ilegível "enxuto" nem sempre é otimizado.
Eu costumava pensar que o código inteligente / lean era um código bom, mas às vezes aproveitando as regras obscuras da linguagem doía, em vez de ajudar na criação do código, fui mordido com mais frequência do que nunca em qualquer trabalho incorporado ao tentar seja inteligente porque o compilador transforma seu código inteligente em algo totalmente inutilizável pelo hardware incorporado.
fonte
Nunca substituirei o código otimizado pelo código legível, porque não posso comprometer o desempenho e optarei por usar os comentários adequados em cada seção, para que qualquer pessoa possa entender a lógica implementada nessa seção otimizada, que resolverá os dois problemas.
Portanto, o código será otimizado + o comentário apropriado também o tornará legível.
NOTA: Você pode tornar um Código Otimizado legível com a ajuda de comentários adequados, mas não pode tornar o Código Legível um Otimizado.
fonte
Aqui está um exemplo para ver a diferença entre código simples e código otimizado: https://stackoverflow.com/a/11227902/1396264
no final da resposta, ele apenas substitui:
com:
Para ser justo, não tenho idéia do que a declaração if foi substituída, mas, como o respondente diz que são algumas operações bit a bit, dando o mesmo resultado (só vou acreditar na palavra dele) .
Isso é executado em menos de um quarto do tempo original (11,54 s vs 2,5 s)
fonte
A principal questão aqui é: a otimização é necessária?
Se for, não será possível substituí-lo por um código mais lento e legível. Você precisará adicionar comentários etc. para torná-lo mais legível.
Se o código não precisar ser otimizado, ele não deverá ser (a ponto de afetar a legibilidade) e você poderá fatorá-lo novamente para torná-lo mais legível.
NO ENTANTO - verifique se você sabe exatamente o que o código faz e como testá-lo antes de começar a mudar as coisas. Isso inclui o pico de uso, etc. Se não for necessário compor um conjunto de casos de teste e executá-los antes e depois, você não terá tempo para refatorar.
fonte
É assim que eu faço as coisas: primeiro eu faço com que ele funcione em código legível, depois eu o otimizo. Eu mantenho a fonte original e documento minhas etapas de otimização.
Então, quando preciso adicionar um recurso, volto ao meu código legível, adiciono o recurso e sigo as etapas de otimização que documentei. Como você documentou, é muito rápido e fácil otimizar seu código com o novo recurso.
fonte
A legibilidade do IMHO é mais importante que o código otimizado, porque na maioria dos casos a micro-otimização não vale a pena.
Artigo sobre micro-otimizações sem sentido :
fonte
A otimização é relativa. Por exemplo:
Esta suposição:
leva a:
Referências
A análise de custo-benefício de campos de bits para uma coleção de booleanos - The Old New Thing
Badness de campo de bits - Hardwarebug
Bitfields legíveis e de manutenção em C | pagetable.com
fonte