Como arruinar um projeto com excesso de reflexão, expansão de escopo e diff estrutural
(kevinlynagh.com)- Projetos tendem a seguir dois caminhos: o fluxo de simplesmente construir e terminar logo ou o fluxo em que pesquisa e design crescem até fazer você perder o problema original; e, no avanço real, muitas vezes simplesmente fazer acaba ficando na frente
- A prateleira da cozinha foi concluída em um fim de semana, mas a exploração de workflows de diff estrutural e ideias antigas sobre linguagem e CAD, apesar de muita pesquisa e protótipos, não resultaram em algo que resolvesse diretamente a motivação inicial
- Mesmo ao criar uma busca fuzzy de caminhos para o Emacs, funcionalidades extras de uma boa biblioteca geraram novas exigências e inflaram o design; no fim, ao descartar todo o código da funcionalidade de âncora que não era necessária, o autor voltou a confirmar o YAGNI
- Em diffs de código, a comparação por linha não captura bem estruturas de nível superior como funções ou tipos, e ferramentas baseadas em treesitter também podem ficar difíceis de ler se o pareamento de entidades falhar, mostrando longas sequências de remoções e adições
- A direção necessária é primeiro criar uma ferramenta de escopo mínimo adequada à revisão por turno da saída de LLM, começando com extração de entidades em Rust e pareamento simples para ver rapidamente uma visão geral de mudanças em alto nível
Excesso de reflexão e expansão de escopo
- Projetos tendem a se dividir entre o fluxo de construir e terminar logo e o fluxo em que, ao investigar casos anteriores, o escopo cresce e você acaba não resolvendo o problema original
- A prateleira de cozinha feita no fim de semana foi planejada tomando café, teve o suporte impresso em 3D ajustado algumas vezes e foi concluída no próprio fim de semana com materiais restantes e tinta
- O CAD do suporte para a caixa da Ikea está disponível em OnShape CAD
- Os materiais reaproveitaram sobras da bancada de trabalho, e os cantos foram lixados no olho com uma palm sander
- Nessa prateleira, o principal critério de sucesso não era tanto fabricar algo perfeitamente ajustado à cozinha, mas aproveitar a marcenaria com um amigo; isso reduziu a necessidade de pensar demais em critérios detalhados
- Em contrapartida, ao procurar uma ferramenta de diff estrutural, o autor achou o resultado do difftastic insatisfatório e passou 4 horas pesquisando ferramentas e workflows relacionados, para no fim voltar ao critério original: um workflow de diff melhor para usar no Emacs
- Interesses antigos como interfaces para prototipagem de hardware, uma linguagem misturando Clojure e Rust, e uma linguagem para CAD consumiram centenas de horas em pesquisa e pequenos protótipos, mas ainda não produziram algo que resolvesse diretamente a motivação inicial
- A interface de prototipagem de hardware apareceu em setembro de 2023, o design de linguagem em novembro de 2023, e as ideias ligadas a CAD continuaram em constraints, bidirectional editing e other dubious ideas
- Nos projetos de linguagem e CAD, os critérios de sucesso são vagos: substituir Rust ou Clojure, tratar apenas alguns problemas, servir como playground de aprendizado, trocar um CAD comercial, ou ser útil também para outras pessoas
- Avaliar essas perguntas tem valor, mas o autor considera melhor construir muito na prática do que apenas examinar muitas possibilidades
- Mesmo que, olhando depois, o resultado seja claramente ruim, simplesmente tentar fazer acaba deixando você mais à frente no panorama geral
A lei da conservação da expansão de escopo
- Até o tempo gasto construindo sem pensar demais tem limite e exige equilíbrio, e a experiência de escrever muito código com um agente de LLM para depois jogar tudo fora trouxe de volta a ideia de YAGNI
- O autor queria criar para o Emacs uma busca fuzzy de caminhos por todo o sistema de arquivos, no estilo do Finda, e como já tinha implementado algo parecido manualmente antes, achou que supervisionando um LLM conseguiria terminar em poucas horas
- No começo, em uma conversa de planejamento, recebeu a recomendação de Nucleo, e como ele era bem projetado e documentado, foi adotado para obter recursos de smart case e Unicode normalization
- Por exemplo, a consulta
foocorresponde tanto aFooquanto afoo, masFoonão corresponde afoo - O tratamento de
cafeecafésegue a mesma lógica
- Por exemplo, a consulta
- O problema não era a boa biblioteca em si, mas o fato de o Nucleo também oferecer suporte a âncoras
- Como uma âncora de início de linha parecia inútil em um corpus composto apenas por caminhos de arquivo, surgiu a ideia de interpretá-la como âncora baseada em segmento de caminho
- Por exemplo,
^foodeveria corresponder a/root/foobar/, mas não a/root/barfoo/
- Por exemplo,
- Para tratar isso com eficiência, o índice precisaria armazenar os limites dos segmentos e permitir verificar rapidamente a consulta em cada segmento
- Além disso, também seria preciso lidar com consultas ancoradas com barra, como
^foo/bar, e só a checagem por segmento já não bastaria para casar corretamente caminhos como/root/foo/bar/baz/ - O autor passou mais algumas horas nesse design, trocando ideias com um LLM, criando código que encapsulava os tipos do Nucleo e, no fim, achando tudo excessivamente inflado e desagradável, reescreveu um wrapper menor por conta própria
- Depois de descansar, percebeu que não lembrava de nenhuma vez em que precisara de âncoras no Finda, e que, em um corpus de caminhos, colocar
/no início ou no fim da consulta já substitui a maior parte do papel das âncoras- Só a âncora para o fim do nome do arquivo permanece como exceção
- No fim, todo o código relacionado a âncoras foi descartado, e ficou difícil dizer se ainda assim houve ganho em comparação a ter escrito tudo sozinho desde o início, sem discussões com LLMs ou outras pessoas
- Parece existir uma espécie de lei da conservação pela qual, quanto mais rápido a programação fica, mais aumentam junto funcionalidades desnecessárias, rabbit holes e desvios
Diff estrutural
- Em código, diff normalmente significa um resumo de mudanças por linha entre duas versões de um arquivo, e, na visualização unified, adições e remoções são marcadas com
+e- - O mesmo diff também pode ser renderizado em formato lado a lado, e quanto mais complexa for a mudança, mais fácil esse formato pode ficar de ler
- O problema do diff por linha é que ele não reconhece estruturas de nível superior como funções ou tipos; se as chaves coincidirem de algum jeito, a marcação pode até ser omitida mesmo quando pertencem a funções diferentes
- O difftastic tenta reduzir esse problema usando a concrete syntax tree fornecida pelo treesitter, mas o pareamento de entidades entre versões nem sempre funciona bem
- No diff que motivou diretamente o autor,
struct PendingClicknão foi correspondida entre os dois lados e apareceu como removida à esquerda e adicionada à direita - Ele não investigou a fundo por que o pareamento falhou, mas julgou que seria melhor ver
PendingClickRequestePendingClickcorrespondendo entre os lados, mesmo que isso deixasse o diff inteiro mais longo
Ferramentas e referências sobre diff estrutural
- O autor considera o semanticdiff.com a ferramenta de semantic diff mais madura e cuidadosamente lapidada
- Ela é oferecida por uma pequena empresa alemã, com plugin gratuito para VSCode e um app web que mostra diffs de PRs do GitHub
- Porém, não oferece uma biblioteca de código que possa servir de base para o workflow desejado
- O texto semanticdiff vs. difftastic traz muitos detalhes úteis, incluindo o problema de o difftastic não mostrar nem mesmo mudanças significativas de indentação em Python
- Em um comentário no HN, um dos autores diz que acabou se afastando do uso de treesitter para tratamento semântico, e escreve que, por causa de palavras-chave dependentes de contexto e do comportamento do lexer, o parsing pode falhar e a ferramenta pode até travar quando um nome como
asyncé usado como parâmetro
- O diffsitter é baseado em treesitter e inclui um servidor MCP
- Tem muitas estrelas no GitHub, mas a documentação não parece muito boa, e foi difícil encontrar material explicando como funciona
- A wiki do difftastic diz que ele executa longest-common-subsequence nas folhas da árvore
- O gumtree é uma ferramenta com origem em pesquisa acadêmica de 2014
- Como exige Java, não se encaixa no uso pessoal de uma ferramenta rápida para Emacs
- O mergiraf é um merge-driver baseado em treesitter escrito em Rust
- A visão geral da arquitetura é bem organizada, e internamente usa o algoritmo Gumtree
- Pela documentação e pelos diagramas, passa a impressão de um projeto escrito com cuidado
- Em um comentário no HN, o autor do semanticdiff.com diz que o GumTree devolve resultados rapidamente, mas que, mesmo aplicando várias melhorias sugeridas em artigos posteriores, ainda havia muitos casos em que sempre retornava pareamentos ruins; no fim, ele migrou para uma abordagem baseada em dijkstra que minimiza o custo do mapeamento
- O weave é outro merge-driver baseado em treesitter, escrito em Rust
- A landing page chamativa, o grande número de estrelas no GitHub e o servidor MCP dão uma impressão geral um tanto exagerada
- O autor examinou o crate de extração de entidades sem
- O código principal de diff é razoável, mas um pouco prolixo, e o pareamento de entidades usa um algoritmo guloso
- O modelo de dados não detecta movimentações dentro do arquivo, embora esse tipo de mudança possa ser importante
- Também há muita análise de impacto baseada em heurísticas, que parece exigir integração de linguagem mais forte para ser confiável
- Ao executar
sem diff --verbose HEAD~4, ele também encontrou uma saída bugada que marcava como alteradas linhas que na prática não tinham mudado
- Ao executar
- Havia funcionalidades hipotéticas úteis demais, como se o projeto estivesse 80% pronto, o que não o tornava adequado como base; ainda assim, ele valoriza o fato de terem chegado a esse ponto em apenas 3 meses
- O diffast calcula a tree edit-distance da AST com base em um algoritmo de um artigo acadêmico de 2008
- Suporta Python, Java, Verilog, Fortran e C/C++ por meio de parsers dedicados
- A galeria de exemplos de diferenças em AST é muito bem organizada
- Pode exportar informações em forma de tupla para uso em datalog
- O autochrome é uma ferramenta de diff voltada a Clojure e usa programação dinâmica
- A explicação visual e o walkthrough dos exemplos são muito bons
- O texto de Tristan Hume, Designing a Tree Diff Algorithm Using Dynamic Programming and A*, é uma boa referência sobre projeto de algoritmos de tree diff
O workflow desejado e o plano de escopo mínimo
- O principal caso de uso é a revisão por turno da saída de LLM, e o autor não deixa um agente gerar de uma vez mais de 10 mil linhas de código sem controle
- Ele prefere delegar ao agente tarefas com escopo definido, voltar alguns minutos depois, ver uma visão geral das mudanças, e então editar diretamente no Emacs, jogar tudo fora e tentar de novo, ou até reescrever por conta própria
- O workflow desejado é primeiro ver uma visão geral em alto nível de quais tipos, funções e métodos foram adicionados, removidos ou alterados
- Sobre isso, deve ser possível expandir rapidamente o diff textual de cada entidade, ampliando a visão geral para o diff detalhado de forma natural
- Também deve ser possível corrigir as mudanças ali mesmo, sem saltar para outro lugar, com edição inline sem trocar da tela de diff para a tela do arquivo
- A direção buscada é levar o workflow de revisão e staging de mudanças do Magit do nível de arquivo e linha para o nível de entidade
- Seguindo a lição de escopo mínimo que reapareceu agora, o plano é primeiro criar rapidamente uma framework de extração de entidades baseada em treesitter apenas para Rust
- O pareamento começará por um método guloso simples, e o diff será renderizado na linha de comando
- Se isso já produzir resultado melhor que o difftastic em um commit específico, a ideia é depois conectar a algo mais interativo no Emacs, como o Magit
- Se possível, ele também deixa aberta a chance de reaproveitar o próprio Magit
- O suporte a novas linguagens será adicionado apenas quando necessário
- Mais adiante, em vez do simples algoritmo guloso, também pode explorar pareamento global baseado em pontuação
- Se ficar suficientemente satisfatório, o autor pode até publicar a ferramenta, mas juntar estrelas no GitHub ou karma no HN não é o objetivo; ela também pode continuar como uma ferramenta silenciosa de uso pessoal
- Às vezes, tudo o que se quer é apenas uma prateleira; com essa frase, o texto amarra de novo a ideia de construir apenas o necessário, em vez de expandir demais
1 comentários
Comentários do Hacker News
Acho que isso mostra muito bem a maior dificuldade de uma pesquisa de PhD
Quando você pega um tema interessante e lê o máximo possível da bibliografia relacionada, é fácil perceber quantas coisas parecidas com o que você queria fazer já foram feitas, e aí o scope creep tende a piorar bastante
Depois de gastar toda a energia e empolgação do começo, você precisa empurrar à força os 20~30% finais para levar aquilo até um estado publicável
No dia 400, depois de praticamente explicar a teoria de tudo, você está tentando construir um dispositivo experimental em órbita no ponto de Lagrange para detectar uma partícula universal que media todas as forças do universo conhecido
Fico me perguntando como dá para aliviar isso
Na prática, é um trabalho do tipo aumentar a observabilidade de um sistema de 1% para 1,001%, e acaba sendo mais uma porta de entrada para uma carreira acadêmica
Por isso, quase nunca vejo teses que sejam realmente interessantes, muito novas ou diretamente aplicáveis à ciência
Na prática, quase nunca vi alguém pesquisar assim; normalmente o certo é ler dois ou três artigos e ir construindo a partir dali
Mergulhar fundo na literatura faz mais sentido depois que já houver algum resultado e você começar a organizar isso por escrito
Continuo pensando na ideia de que melhor já basta
Pequenas melhorias se acumulam com o tempo, e como nada nasce perfeitamente novo desde o início, ficar sentado tentando desenhar a solução perfeita tende a ser contraproducente
A ideia de que o obstáculo é o caminho também se encaixa bem aqui
Um colega com quem eu trabalhava, ao criticar mudanças de código, quando sentia que estava pegando detalhe demais, dizia: "está melhor do que antes"
Isso permitia apontar o que podia melhorar, mas ao mesmo tempo dava permissão para seguir em frente mesmo com pequenas imperfeições, e eu apoio muito essa postura
Eu costumava pensar em perfeccionismo só como a busca exagerada por realizações muito altas, mas também pode ser a incapacidade de aceitar algo que não seja perfeito, a ponto de desistir sem avançar
A procrastinação em grandes tarefas muitas vezes tem a mesma raiz
Gostei de algo que o CEO da Rec Room disse
As equipes sempre dizem que gostariam que o projeto tivesse sido mais curto; quase nunca dizem que gostariam de ter adiado mais o lançamento, deixado mais complexo e refinado mais
Isso não se aplica 100% a todas as situações, claro, mas se for para errar, acho melhor fazer menor e lançar cedo do que abrir demais a escala e desperdiçar tempo
Acho que os seres humanos, por natureza, têm facilidade para chegar a ideias parecidas, então se você conclui um projeto sem saber o que já existe, ele tende a acabar sendo uma reinvenção em algum grau
Por outro lado, se você pesquisar antes, pode perceber que aquilo já existia parcialmente e perder o entusiasmo
Mesmo assim, talvez o mais importante seja terminar de construir pelo próprio aprendizado
Claro, isso é mais difícil quando você precisa produzir resultado acadêmico novo ou lucrar com um projeto realmente único, mas até esses campos são surpreendentemente tolerantes a pequenas torções sobre o que já existe
Estou passando exatamente por isso agora em um side project
A área é Information Retrieval, então tenho pouca experiência e naturalmente existe muito prior art que posso aprender ou integrar
Depois de ler este texto, fiquei mais inclinado a primeiro construir a minha própria versão e só olhar referências anteriores quando travar ou precisar de ideias
Por outro lado, vendo o documentário recente sobre Clojure, o Rich Hickey parece ter feito justamente o oposto: passou muito tempo mergulhando em bibliografia, artigos e outras linguagens antes de começar
Mas ele também já tinha feito outras linguagens antes, então, no quadro geral, o aprendizado dele também começou construindo coisas por conta própria
Talvez a questão seja não passar tempo demais só pensando: fazer logo, aprender com a prática, bater em paredes e só então sentir necessidade de investigar mais a fundo
Depois assisti também a "Easy made Simple" e "Hammock Driven Development", e agora fiquei com vontade de aprender Clojure
Clojure documentary on CultRepo channel: https://www.youtube.com/watch?v=Y24vK_QDLFg
Simple Made Easy: https://www.youtube.com/watch?v=SxdOUGdseq4
Hammock Driven Development: https://www.youtube.com/watch?v=f84n5oFoZBc
Definir um prazo resolveu a maior parte dos meus problemas com scope creep
Pela minha experiência, projetos com deadline rígido, como game jams ou competições de programação, são fáceis de terminar, enquanto projetos com final em aberto são muito mais difíceis de concluir
Vejo isso numa linha parecida com o motivo de o padrão C++ sair a cada 3 anos, em vez de esperar até que todos os recursos desejados estejam prontos
https://news.ycombinator.com/item?id=20428703
O texto foi interessante, mas achei que as ideias do autor estavam um pouco espalhadas demais
Para alguém que diz estar sendo esmagado por scope creep, ele parece ser do tipo que faz coisa demais, a ponto de terminar o texto com uma pilha de links sobre assuntos variados
No fundo, parece alguém que realmente gosta de aprender e experimentar várias coisas, e o processo de cair em rabbit holes em si parece estimular a cabeça dele de um jeito prazeroso
Houve uma percepção que me ajudou muito como alguém que constrói coisas sozinho
A maior parte do que parecia uma abstração essencial era, na verdade, scope creep com outro nome
Eu estava adicionando flags a cada nova funcionalidade, até começar a ver um padrão no meu código e definir uma regra
Uma funcionalidade não seria lançada se não houvesse testes para o comportamento com a flag desligada
Isso me fez enxergar a flag não como rota de fuga, mas como parte do produto, e três itens do backlog simplesmente desapareceram quando passei a pensar assim
É verdade que planejamento excessivo e scope creep são problemas, mas, por outro lado, também é preciso evitar balançar demais para o lado do desenvolvimento improvisado
Alguns dos meus projetos mais bem-sucedidos foram casos em que eu modelei os dados e planejei/revisei grande parte das funcionalidades antes de produzir software de fato funcionando
Nessa etapa, muitas vezes é difícil saber o que é exagero, e se eu remover funcionalidades que eu ou o usuário provavelmente vamos querer, mais tarde acabo gastando muito tempo redesenhando o núcleo do código
Se eu errar para o outro lado, o projeto cresce demais e aí passo a chamar isso de scope creep
No fim, esse julgamento depende de quão bem você conhece o domínio
Se você conhece menos do que imagina, vai ter muito retrabalho; se conhece mais do que imagina, talvez pudesse ter ido mais longe e acabou desperdiçando tempo com baby steps
Em qualquer direção sobra arrependimento, então no fim isso parece uma grande questão de julgamento
Não dá para cair na falácia do custo afundado, e só porque você pesquisou um tema em nível de doutorado por algumas horas não significa que precisa colocar isso no projeto
Se não se encaixa exatamente no problema atual, o melhor é descartar sem dó