Como evitar a maldição da otimização prematura
Publicados: 2022-03-11É quase digna de garantia, na verdade. De novatos a especialistas, de arquitetura a ASM e otimizando qualquer coisa, desde o desempenho da máquina até o desempenho do desenvolvedor, as chances são muito boas de que você e sua equipe estejam causando um curto-circuito em seus próprios objetivos.
Que? Mim? Minha equipe?
Essa é uma acusação muito pesada para nivelar. Deixe-me explicar.
A otimização não é o santo graal, mas também pode ser difícil de obter. Quero compartilhar com você algumas dicas simples (e uma montanha de armadilhas) para ajudar a transformar a experiência de sua equipe de auto-sabotagem para harmonia, realização, equilíbrio e, eventualmente, otimização.
O que é otimização prematura?
A otimização prematura está tentando otimizar o desempenho:
- Ao codificar um algoritmo pela primeira vez
- Antes que os benchmarks confirmem, você precisa
- Antes de criar perfis, identifique onde faz sentido se preocupar em otimizar
- Em um nível mais baixo do que seu projeto atualmente determina
Agora, sou otimista, Optimus.
Pelo menos, vou fingir ser otimista enquanto escrevo este artigo. De sua parte, você pode fingir que seu nome é Optimus, para que isso fale mais diretamente com você.
Como alguém em tecnologia, você provavelmente às vezes se pergunta como poderia ser $year e, apesar de todo o nosso avanço, é de alguma forma um padrão aceitável para $task ser tão irritantemente demorado. Você quer ser magro. Eficiente. Impressionante. Alguém como os programadores da Rockstar, pelos quais esses anúncios de emprego clamam, mas com habilidades de líder. Então, quando sua equipe escreve código, você os encoraja a fazer certo na primeira vez (mesmo que “certo” seja um termo altamente relativo, aqui). Eles sabem que esse é o jeito do Clever Coder, e também o jeito daqueles que não precisam perder tempo refatorando depois.
Eu sinto isso. A força do perfeccionismo às vezes é forte dentro de mim também. Você quer que sua equipe gaste um pouco de tempo agora para economizar muito tempo depois, porque todo mundo se esforçou com sua parte de “Código de merda que outras pessoas escreveram (o que diabos eles estavam pensando?”). Isso é SCOPWWHWTT para abreviar, porque eu sei que você gosta de siglas impronunciáveis.
Eu também sei que você não quer que o código de sua equipe seja isso para si ou para qualquer outra pessoa no futuro.
Então, vamos ver o que pode ser feito para orientar sua equipe na direção certa.
O que otimizar: bem-vindo a isso ser uma arte
Em primeiro lugar, quando pensamos em otimização de programas, geralmente assumimos imediatamente que estamos falando de desempenho. Mesmo isso já é mais vago do que parece (velocidade? uso de memória? etc.), então vamos parar por aí.
Vamos torná-lo ainda mais ambíguo! Apenas no início.
Meu cérebro cheio de teias de aranha gosta de criar ordem sempre que possível, então será preciso cada grama de otimismo para eu considerar o que estou prestes a dizer como uma coisa boa .
Existe uma regra simples de otimização (de desempenho) que diz: Não faça isso . Isso parece muito fácil de seguir rigidamente, mas nem todos concordam com isso. Também não concordo totalmente com isso. Algumas pessoas simplesmente escreverão um código melhor do que outras. Felizmente, para qualquer pessoa, a qualidade do código que eles escreveriam em um novo projeto geralmente melhorará com o tempo. Mas sei que, para muitos programadores, esse não será o caso, porque quanto mais eles souberem, mais maneiras serão tentados a otimizar prematuramente.
Para muitos programadores... quanto mais eles sabem, mais maneiras eles serão tentados a otimizar prematuramente.
Portanto, este Não faça isso não pode ser uma ciência exata, mas destina-se apenas a neutralizar o desejo interno típico do técnico de resolver o quebra-cabeça. Isso, afinal, é o que atrai muitos programadores para o ofício em primeiro lugar. Entendi. Mas peça-lhes para salvá-lo , para resistir à tentação. Se alguém precisa de uma saída para resolver quebra-cabeças agora , sempre pode se interessar pelo Sudoku do jornal de domingo, ou pegar um livro Mensa, ou jogar golfe com algum problema artificial. Mas deixe-o fora do repositório até o momento adequado. Quase sempre esse é um caminho mais sábio do que a pré-otimização.
Lembre-se, essa prática é tão notória que as pessoas perguntam se a otimização prematura é a raiz de todo mal. (Eu não iria tão longe, mas concordo com o sentimento.)
Não estou dizendo que devemos escolher a maneira mais cerebral que podemos pensar em todos os níveis de design. Claro que não. Mas em vez de escolher o mais inteligente, podemos considerar outros valores:
- O mais fácil de explicar ao seu novo contratado
- O mais provável de passar em uma revisão de código por seu desenvolvedor mais experiente
- O mais sustentável
- O mais rápido para escrever
- O mais fácil de testar
- O mais portátil
- etc.
Mas é aqui que o problema se mostra difícil. Não se trata apenas de evitar a otimização de velocidade, tamanho de código, espaço de memória, flexibilidade ou resistência ao futuro. É sobre equilíbrio e sobre se o que você está fazendo está realmente alinhado com seus valores e objetivos. É totalmente contextual e às vezes até impossível de medir objetivamente.
Por que isso é uma coisa boa? Porque a vida é assim. É bagunçado. Nossos cérebros orientados à programação às vezes querem tanto criar ordem no caos que acabamos ironicamente multiplicando o caos. É como o paradoxo de tentar forçar alguém a te amar. Se você acha que conseguiu isso, não é mais amor; enquanto isso, você é acusado de fazer reféns, provavelmente precisa de mais amor do que nunca, e essa metáfora deve ser uma das mais estranhas que eu poderia ter escolhido.
De qualquer forma, se você acha que encontrou o sistema perfeito para alguma coisa, bem... aproveite a ilusão enquanto dura, eu acho. Tudo bem, os fracassos são oportunidades maravilhosas para aprender.
Mantenha o UX em mente
Vamos explorar como a experiência do usuário se encaixa entre essas possíveis prioridades. Afinal, mesmo querer que algo tenha um bom desempenho é, em algum nível, sobre UX.
Se você estiver trabalhando em uma interface do usuário, não importa qual estrutura ou linguagem o código use, haverá uma certa quantidade de clichê e repetição. Definitivamente, pode ser valioso em termos de tempo do programador e clareza de código tentar reduzir isso. Para ajudar na arte de equilibrar as prioridades, quero compartilhar algumas histórias.
Em um emprego, a empresa em que eu trabalhava usava um sistema corporativo de código fechado baseado em uma pilha de tecnologia opinativa. Na verdade, era tão opinativo que o fornecedor que o vendeu para nós se recusou a fazer personalizações de interface do usuário que não se encaixavam nas opiniões da pilha, porque era muito doloroso para seus desenvolvedores. Eu nunca usei a pilha deles, então não os condeno por isso, mas o fato é que esse trade-off “bom para o programador, ruim para o usuário” era tão complicado para meus colegas de trabalho em certos contextos que acabei escrevendo um complemento de terceiros para reimplementar esta parte da interface do usuário do sistema. (Foi um grande aumento de produtividade. Meus colegas de trabalho adoraram! Mais de uma década depois, ainda está economizando tempo e frustração para todos.)
Não estou dizendo que a opinião é um problema em si; só que muito disso se tornou um problema no nosso caso. Como contra-exemplo, um dos grandes atrativos do Ruby on Rails é precisamente que ele é opinativo, em um ecossistema de front-end onde facilmente se tem vertigem por ter muitas opções. (Dê-me algo com opiniões até que eu possa descobrir as minhas!)
Em contraste, você pode ficar tentado a coroar o UX como o Rei de Tudo em seu projeto. Um objetivo digno, mas deixe-me contar minha segunda história.
Alguns anos após o sucesso do projeto acima, um de meus colegas de trabalho veio até mim para me pedir para otimizar o UX automatizando um certo cenário confuso da vida real que às vezes surgia, para que pudesse ser resolvido com um único clique. Comecei a analisar se era possível projetar um algoritmo que não tivesse nenhum falso positivo ou negativo por causa dos muitos e estranhos casos extremos do cenário. Quanto mais eu conversava com meu colega de trabalho sobre isso, mais eu percebia que os requisitos simplesmente não iam valer a pena. O cenário só aparecia de vez em quando — mensalmente, digamos — e atualmente levava alguns minutos para uma pessoa resolver. Mesmo que pudéssemos automatizá-lo com sucesso, sem nenhum bug, levaria séculos para que o tempo necessário de desenvolvimento e manutenção fosse compensado em termos de tempo economizado por meus colegas de trabalho. A pessoa que agradava as pessoas em mim teve um momento difícil para dizer “não”, mas tive que interromper a conversa.
Portanto, deixe o computador fazer o que puder para ajudar o usuário, mas apenas até certo ponto. Como você sabe até que ponto isso é, no entanto?
Uma abordagem que gosto de adotar é traçar o perfil do UX como seus desenvolvedores perfilam seu código. Descubra com seus usuários onde eles passam mais tempo clicando ou digitando a mesma coisa repetidamente e veja se você pode otimizar essas interações. Seu código pode fazer algumas suposições sobre o que eles provavelmente irão inserir e tornar isso um padrão sem entrada? Além de certos contextos proibidos (confirmação do EULA sem cliques?) isso pode realmente fazer a diferença na produtividade e felicidade de seus usuários. Faça alguns testes de usabilidade no corredor, se puder. Às vezes, você pode ter problemas para explicar o que é fácil para os computadores ajudarem e o que não é… mas, no geral, esse valor provavelmente será de grande importância para seus usuários.
Evitando a otimização prematura: quando e como otimizar
Apesar de nossa exploração de outros contextos, vamos agora assumir explicitamente que estamos otimizando algum aspecto do desempenho bruto da máquina para o restante deste artigo. Minha abordagem sugerida também se aplica a outros alvos, como flexibilidade, mas cada alvo terá suas próprias armadilhas; o ponto principal é que a otimização prematura para qualquer coisa provavelmente falhará.

Então, em termos de desempenho, quais métodos de otimização existem para serem seguidos? Vamos cavar.
Esta não é uma iniciativa de base, é Triple-Eh
O TL;DR é: Trabalhe de cima para baixo. As otimizações de nível superior podem ser feitas no início do projeto, e as de nível inferior devem ser deixadas para mais tarde. Isso é tudo que você precisa para entender o significado da frase “otimização prematura”; fazer coisas fora dessa ordem tem uma alta probabilidade de desperdiçar o tempo de sua equipe e ser contra-eficaz. Afinal, você não escreve todo o projeto em código de máquina desde o início, escreve? Portanto, nosso modus operandi AAA é otimizar nesta ordem:
- Arquitetura
- Algoritmos
- conjunto
O senso comum diz que algoritmos e estruturas de dados geralmente são os locais mais eficazes para otimizar, pelo menos no que diz respeito ao desempenho. Tenha em mente, porém, que a arquitetura às vezes determina quais algoritmos e estruturas de dados podem ser usados.
Certa vez, descobri um software fazendo um relatório financeiro consultando um banco de dados SQL várias vezes para cada transação financeira e, em seguida, fazendo um cálculo muito básico no lado do cliente. A pequena empresa usando o software levou apenas alguns meses de uso antes mesmo de sua quantidade relativamente pequena de dados financeiros significar que, com novos desktops e um servidor bastante robusto, o tempo de geração do relatório já era de vários minutos, e isso foi um que eles precisavam usar com bastante frequência. Acabei escrevendo uma instrução SQL direta que continha a lógica de soma - frustrando sua arquitetura movendo o trabalho para o servidor para evitar toda a duplicação e idas e voltas da rede - e mesmo vários anos de dados depois, minha versão poderia gerar o mesmo relatório em meros milissegundos no mesmo hardware antigo.
Às vezes, você não tem influência sobre a arquitetura de um projeto porque é tarde demais para que uma mudança de arquitetura seja viável. Às vezes, seus desenvolvedores podem contornar isso como eu fiz no exemplo acima. Mas se você está no início de um projeto e tem algo a dizer em sua arquitetura, agora é a hora de otimizar isso.
Arquitetura
Em um projeto, a arquitetura é a parte mais cara para mudar após o fato, então este é um lugar onde pode fazer sentido otimizar no início. Se seu aplicativo for entregar dados por meio de avestruzes, por exemplo, você desejará estruturá-lo para pacotes de baixa frequência e alta carga para evitar tornar um gargalo ainda pior. Nesse caso, é melhor você ter uma implementação completa do Tetris para entreter seus usuários, porque um spinner de carregamento simplesmente não vai funcionar. (Brincadeiras à parte: Anos atrás eu estava instalando minha primeira distribuição Linux, Corel Linux 2.0, e fiquei encantado que o processo de instalação de longa duração incluía exatamente isso. Tendo visto as telas de infomercial do instalador do Windows 95 tantas vezes que eu as havia memorizado, este era uma lufada de ar fresco na época.)
Como um exemplo de mudança arquitetônica ser cara, a razão para o relatório SQL acima mencionado ser tão altamente não escalável em primeiro lugar é clara em seu histórico. O aplicativo evoluiu ao longo do tempo, desde suas raízes no MS-DOS e um banco de dados personalizado e criado em casa que nem era originalmente multiusuário. Quando o fornecedor finalmente mudou para SQL, o esquema e o código de relatório parecem ter sido portados um por um. Isso os deixou com mais de 1.000% de melhorias de desempenho impressionantes para espalhar em suas atualizações, sempre que concluíssem a mudança de arquitetura usando as vantagens do SQL para um determinado relatório. Bom para negócios com clientes bloqueados, como meu então empregador, e claramente tentando priorizar a eficiência da codificação durante a transição inicial. Mas atender às necessidades dos clientes, em alguns casos, é tão eficaz quanto um martelo gira um parafuso.
A arquitetura é, em parte, antecipar até que ponto seu projeto precisará ser dimensionado e de que maneira. Como a arquitetura é de alto nível, é difícil ser concreto com nossos “fazer e não fazer” sem restringir nosso foco a tecnologias e domínios específicos.
Eu não chamaria assim, mas todo mundo chama
Felizmente, a Internet está repleta de sabedoria coletada sobre quase todos os tipos de arquitetura já sonhados. Quando você sabe que é hora de otimizar sua arquitetura, pesquisar armadilhas se resume a descobrir a palavra da moda que descreve sua visão brilhante. É provável que alguém tenha pensado na mesma linha que você, tentou, falhou, repetiu e publicou sobre isso em um blog ou livro.
A identificação de palavras-chave pode ser complicada de realizar apenas pesquisando, porque para o que você chama de FLDSMDFR, outra pessoa já cunhou o termo SCOPWWHWTT e descreve o mesmo problema que você está resolvendo, mas usando um vocabulário completamente diferente do que você usaria. Comunidades de desenvolvedores para o resgate! Acesse o StackExchange ou HashNode com uma descrição tão completa quanto possível, além de todas as palavras-chave que sua arquitetura não é , para que eles saibam que você fez uma pesquisa preliminar suficiente. Alguém ficará feliz em esclarecê-lo.
Enquanto isso, alguns conselhos gerais podem ser um bom motivo para reflexão.
Algoritmos e Montagem
Dada uma arquitetura propícia, aqui é onde os codificadores de sua equipe obterão o máximo de T-bling por seu tempo. A prevenção básica da otimização prematura também se aplica aqui, mas seus programadores fariam bem em considerar algumas das especificidades neste nível. Há tanto em que pensar quando se trata de detalhes de implementação que escrevi um artigo separado sobre otimização de código voltado para codificadores de linha de frente e seniores.
Mas uma vez que você e sua equipe tenham implementado algo não otimizado em termos de desempenho, você realmente deixa isso em Não faça isso ? Você nunca otimiza?
Você está certo. A próxima regra é, apenas para especialistas, não faça isso ainda .
Hora de comparar!
Seu código funciona. Talvez seja tão lento que você já saiba que precisará otimizar, porque é um código que será executado com frequência. Talvez você não tenha certeza ou tenha um algoritmo O(n) e imagine que provavelmente está bem. Não importa qual seja o caso, se esse algoritmo valer a pena otimizar, minha recomendação neste momento é a mesma: execute um benchmark simples.
Por quê? Não está claro que meu algoritmo O(n³) não pode ser pior do que qualquer outra coisa? Bem, por dois motivos:
- Você pode adicionar o benchmark ao seu conjunto de testes, como uma medida objetiva de suas metas de desempenho, independentemente de estarem sendo cumpridas no momento.
- Mesmo especialistas podem inadvertidamente tornar as coisas mais lentas. Mesmo quando parece óbvio. Realmente óbvio.
Não acredite em mim nesse segundo ponto?
Como obter melhores resultados com hardware de US$ 1.400 do que com hardware de US$ 7.000
Jeff Atwood, famoso pelo StackOverflow, apontou uma vez que às vezes (geralmente, em sua opinião) pode ser mais econômico comprar apenas um hardware melhor do que gastar tempo valioso do programador em otimização. OK, suponha que você tenha chegado a uma conclusão razoavelmente objetiva de que seu projeto se encaixaria nesse cenário. Vamos supor ainda que o que você está tentando otimizar é o tempo de compilação, porque é um projeto pesado em Swift que você está trabalhando, e isso se tornou um grande gargalo do desenvolvedor. Tempo de compra de hardware!
O que você deve comprar? Bem, obviamente, iene por iene, hardware mais caro tende a ter um desempenho melhor do que hardware mais barato. Então, obviamente, um Mac Pro de US $ 7.000 deve compilar seu software mais rapidamente do que um Mac Mini de médio porte, certo?
Errado!
Acontece que, às vezes , mais núcleos significam uma compilação mais eficiente… e neste caso em particular, o LinkedIn descobriu da maneira mais difícil que o oposto é verdadeiro para sua pilha.
Mas já vi gerenciamento que cometeu um erro ainda maior: eles nem fizeram benchmark antes e depois, e descobriram que uma atualização de hardware não fazia seu software “sentir” mais rápido. Mas não havia como saber com certeza; além disso, eles ainda não tinham ideia de onde estava o gargalo, de modo que permaneceram insatisfeitos em relação ao desempenho, tendo usado o tempo e o dinheiro que estavam dispostos a alocar para o problema.
OK, eu já fiz o benchmarking. Posso realmente otimizar ainda ??
Sim, supondo que você decidiu que precisa. Mas talvez essa decisão espere até que mais/todos os outros algoritmos também sejam implementados, para que você possa ver como as partes móveis se encaixam e quais são mais importantes por meio da criação de perfil. Isso pode ser no nível do aplicativo para um aplicativo pequeno ou pode se aplicar apenas a um subsistema. De qualquer forma, lembre-se, um algoritmo específico pode parecer importante para o aplicativo em geral, mas mesmo especialistas – especialmente especialistas – são propensos a diagnosticar isso erroneamente.
Pense antes de interromper
“Eu não sei vocês, mas…”
Como último ponto de reflexão, considere como você pode aplicar a ideia de falsa otimização a uma visão muito mais ampla: seu projeto ou empresa em si, ou mesmo um setor da economia.
Eu sei, é tentador pensar que a tecnologia vai salvar o dia e que podemos ser os heróis que fazem isso acontecer.
Além disso, se não o fizermos, alguém o fará.
Mas lembre-se que o poder corrompe, apesar das melhores intenções. Não vou vincular a nenhum artigo específico aqui, mas se você não vagou por nenhum, vale a pena procurar alguns sobre o impacto mais amplo de perturbar a economia e a quem isso às vezes serve. Você pode se surpreender com alguns dos efeitos colaterais de tentar salvar o mundo por meio da otimização.
Pós-escrito
Notou algo, Optimus? A única vez que te chamei Optimus foi no início e agora no fim. Você não foi chamado de Optimus ao longo do artigo. Vou ser honesto, esqueci. Eu escrevi o artigo inteiro sem te chamar de Optimus. No final, quando percebi que deveria voltar e espalhar seu nome por todo o texto, uma vozinha dentro de mim disse, não faça isso .
