Não Foi o Algoritmo. Foi a Codificação.
Por alguns dias, eu tinha certeza de que meu sistema tinha um bug profundo e misterioso. Meus logs saíam como lixo. Uma etapa de conversão de dados produzia arquivos que saíam sutilmente errados. Um subprocesso me devolvia bobagem em vez da saída que eu esperava. Saí à caça de uma falha na minha lógica, em algum lugar da parte engenhosa do sistema.
Não havia falha na lógica. O bug era que meu computador e eu discordávamos sobre como soletrar.
O bug que não estava no código
O que tornava isso enlouquecedor era que tudo parecia correto. Eu conseguia ler o código que produzia o lixo e não ver nada de errado nele. As falhas eram estranhas e inconsistentes: parte do texto saía bem, parte saía embaralhada, alguns arquivos abriam perfeitamente e outros estavam sutilmente quebrados. Nada no padrão apontava para uma única linha que eu pudesse ir lá e corrigir.
Essa inconsistência, eu viria a aprender depois, é uma pista comum. Mas, na época, apenas parecia que meu programa estava assombrado.
O que estava realmente errado: dois programas, dois alfabetos
Aqui está o que está por baixo. Computadores não armazenam texto como letras. Eles o armazenam como números, e para transformar esses números de volta em caracteres você precisa de uma tabela combinada — uma codificação — que diz qual número significa qual caractere. Se duas partes de um sistema usam tabelas diferentes, os mesmos bytes significarão coisas diferentes para cada uma delas, e o texto escrito por uma será mal interpretado pela outra.
Esse era todo o meu bug. Meu sistema operacional estava assumindo por padrão uma codificação mais antiga e legada, enquanto as ferramentas e os arquivos com os quais eu trabalhava presumiam a moderna, quase universal. Tudo funcionava enquanto as duas tabelas por acaso concordavam — o que acontece, para os caracteres mais simples — e quebrava no instante em que qualquer coisa saía dessa sobreposição. O texto nunca foi corrompido de propósito. Ele apenas estava sendo lido em um idioma diferente daquele em que tinha sido escrito.
Por que era tão difícil de enxergar
Uma incompatibilidade de codificação é uma classe de bug particularmente desagradável porque muitas vezes não falha de forma limpa. Em vez de lançar uma exceção que aponta para o problema, uma conversão permissiva pode produzir uma saída que está quase certa, até encontrar um caractere sobre o qual as duas codificações discordam — e então ela silenciosamente distorce apenas aquela parte e segue em frente.
Então as falhas são intermitentes e dependem do conteúdo, que é exatamente o perfil que faz você procurar no lugar errado. Bugs intermitentes e dependentes do conteúdo parecem que devem viver na sua lógica, na parte complicada da qual você se orgulha. Este vivia no encanamento, em um padrão que eu nunca tinha sequer notado que dependia dele.
A verdade sem glamour desse tipo de trabalho
Eu tinha imaginado que as partes difíceis de construir um sistema de aprendizado seriam as partes interessantes — os modelos, a matemática, a estratégia. Uma fração genuinamente grande das partes realmente difíceis acabou sendo coisas como esta. Codificações de texto. Quebras de linha. Caminhos de arquivo que significam coisas diferentes em máquinas diferentes. A infraestrutura monótona por baixo de tudo, onde um único padrão errado pode silenciosamente devorar uma semana da sua vida enquanto você encara o código elegante por cima, convencido de que o problema deve estar ali.
É humilhante, de um jeito útil. A matemática raramente foi o que mais me travou com frequência. O encanamento foi.
A correção: pare de confiar nos padrões, force o acordo
O reparo não foi sutil. Parei de depender de qualquer codificação que cada parte do sistema por acaso assumisse por padrão e forcei uma explicitamente — a moderna, universal — em cada fronteira: cada leitura de arquivo, cada escrita de arquivo, cada subprocesso lançado. Uma vez que cada componente foi obrigado a concordar, por escrito, com a mesma tabela, o lixo parou.
Junto com essa correção principal, adicionei um hábito menor e defensivo: manter certos caracteres problemáticos fora dos meus logs já de início — não como substituto para acertar a codificação, mas como rede de segurança. Os logs são o único lugar onde você mais precisa conseguir ler com clareza quando todo o resto está pegando fogo, e eu não queria que a ferramenta da qual dependo para diagnosticar problemas continuasse sendo vítima da mesma classe de problema.
A lição: um padrão é uma decisão que você não tomou
A lição mais profunda sobreviveu ao bug. Todo “padrão” na sua pilha é uma decisão — geralmente tomada por alguém que não conhecia a sua situação específica, muitas vezes anos atrás, por razões que podem já não se aplicar. Na maior parte do tempo, essas decisões herdadas estão bem, e você nunca precisa pensar nelas. Isso é parte do que as torna perigosas. Quando uma delas por acaso está silenciosamente errada para o seu caso, ela é quase invisível, porque você nunca a escolheu conscientemente — e aquilo que você nunca escolheu é o último lugar onde você pensa em procurar.
Perdi a maior parte de uma semana para uma escolha que eu não sabia que tinha feito. Agora, quando algo falha de um jeito que não faz sentido lógico nenhum, uma das minhas primeiras perguntas já não é apenas “onde está o meu bug?”. É também “o que estou presumindo que eu nunca de fato decidi?”.
— Sem sinais, sem retornos, isto não é aconselhamento de investimento.