Esta é uma coleção de histórias da Internet sobre como os bugs às vezes têm manifestações completamente incríveis. Talvez você também tenha uma história para contar.
Alergia a carro a sorvete de baunilha
Uma história para engenheiros que entendem que o óbvio nem sempre é a solução e que, por mais implausíveis que sejam, os fatos são fatos. A Divisão Pontiac da General Motors Corporation recebeu uma reclamação:
, , , . : . , , , , . Pontiac, . , , , . , . , , : « Pontiac, - , , , ?».
Como você pode imaginar, o presidente da divisão ficou cético em relação à carta. Porém, por precaução, enviou um engenheiro para verificar. Ele ficou surpreso ao ser recebido por um homem rico e bem-educado que vivia em uma bela área. Eles concordaram em se encontrar logo após o jantar para irem juntos à sorveteria. Estava baunilha naquela noite e, quando voltaram para o carro, ele não pegava.
O engenheiro veio mais três noites. Na primeira vez, o sorvete foi de chocolate. O carro deu partida. Na segunda vez, foi sorvete de morango. O carro deu partida. Na terceira noite, ele pediu baunilha. O carro não deu partida.
Tendo raciocinado com sabedoria, o engenheiro se recusou a acreditar na alergia do carro a sorvete de baunilha. Portanto, concordei com o dono do carro que ele continuará suas visitas até encontrar uma solução para o problema. E, ao longo do caminho, começou a tomar notas: anotava todas as informações, a hora do dia, o tipo de gasolina, a hora de chegada e volta da loja, etc.
Logo, o engenheiro percebeu que o dono do carro estava gastando menos tempo comprando sorvete de baunilha. O motivo foi o layout do produto na loja. O sorvete de baunilha era o mais popular e ficava em um freezer separado na frente da loja para ser mais fácil de encontrar. E todas as outras variedades estavam no fundo da loja, e demorou muito mais tempo para encontrar a variedade certa e pagar.
Agora a pergunta era para o engenheiro: por que o carro não deu partida, se passou menos tempo do momento em que o motor foi desligado? Como o problema era o tempo, não o sorvete de baunilha, o engenheiro rapidamente encontrou a resposta: era uma eclusa de gás. Acontecia todas as noites, mas quando o dono do carro passava mais tempo procurando sorvete, o motor tinha tempo de esfriar o suficiente e ligava silenciosamente. E quando o homem comprou sorvete de baunilha, o motor ainda estava muito quente e o gás não teve tempo de se dissolver.
Moral: mesmo problemas completamente insanos às vezes podem ser reais.
Crash Bandicoot
É doloroso experimentar isso. Como um programador, você se acostuma a culpar seu código primeiro, segundo, terceiro ... e em algum lugar na décima milésima posição você culpa o compilador. E ainda mais abaixo na lista, você já culpa o equipamento.
Aqui está minha história sobre um bug de hardware.
Para o jogo Crash Bandicoot, escrevi um código para carregar e salvar em um cartão de memória. Para um desenvolvedor de jogos tão farisaico, era como andar em um parque: achei que o trabalho levaria vários dias. No entanto, como resultado, depurei o código por seis semanas. Resolvi outros problemas ao longo do caminho, mas a cada poucos dias eu voltava a esse código por várias horas. Foi uma agonia.
O sintoma era o seguinte: quando você salva o playthrough atual do jogo e acessa o cartão de memória, quase sempre tudo dá certo ... Mas às vezes a operação de leitura ou gravação é concluída por timeout sem nenhum motivo óbvio. Gravações curtas freqüentemente danificam o cartão de memória. Quando um jogador tenta salvar, ele não apenas falha em salvar, mas também destrói o mapa. Panqueca.
Depois de um tempo, nossa produtora da Sony, Connie Bus, começou a entrar em pânico. Não podíamos lançar o jogo com esse bug e, após seis semanas, não entendi qual era a causa do problema. Por meio de Connie, entramos em contato com outros desenvolvedores do PS1: alguém encontrou um problema semelhante? Não. Ninguém teve problemas com o cartão de memória.
Quando você não tem nenhuma ideia para depurar, quase a única abordagem é "dividir e conquistar": você remove mais e mais código do programa incorreto até que haja um fragmento relativamente pequeno, o que ainda causa um problema. Ou seja, você corta o programa pedaço por pedaço até que a parte que contém o bug permaneça.
Mas a questão é que é muito difícil cortar pedaços de um videogame. Como executá-lo se você removeu o código de emulação de gravidade? Ou desenhando personagens?
Portanto, você deve substituir módulos inteiros por stubs que pretendem fazer algo útil, mas na verdade fazem algo muito simples que não pode conter erros. Temos que escrever essas muletas para fazer o jogo funcionar. É um processo lento e doloroso.
Resumindo, eu consegui. Eu removi mais e mais pedaços de código até que houvesse um código inicial que configura o sistema para iniciar o jogo, inicializa o equipamento para renderização, etc. Claro, nesta fase eu não poderia fazer um menu de salvar e carregar, porque eu teria que fazer um esboço para todo o código gráfico. Mas eu poderia fingir ser um usuário que usa a tela de salvar e carregar (invisível) e pede para salvar e depois gravar no cartão de memória.
Como resultado, fiquei com um pequeno trecho de código que ainda apresentava o problema mencionado - mas até agora estava acontecendo aleatoriamente! Na maioria das vezes, tudo funcionava bem, mas ocasionalmente travava. Removi quase todo o código do jogo, mas o bug ainda existia. Isso era intrigante: o código restante realmente não fazia nada.
Em algum momento, provavelmente às três da manhã, um pensamento me ocorreu. As operações de leitura e gravação (E / S) assumem o tempo exato. Ao trabalhar com um disco rígido, cartão de memória ou módulo Bluetooth, o código de baixo nível responsável pela leitura e escrita o faz de acordo com os pulsos do relógio.
Com a ajuda de um relógio, um dispositivo que não está diretamente conectado ao processador é sincronizado com o código executável no processador. O relógio determina a taxa de baud - a taxa de baud. Se houver confusão com as temporizações, o hardware ou o software, ou ambos, também serão confundidos. E isso é muito ruim, porque os dados podem ser corrompidos.
E se algo em nosso código confundir os tempos? Verifiquei tudo relacionado a isso no código do programa de teste e percebi que ajustamos o temporizador programável no PS1 para uma frequência de 1 kHz (1000 ciclos por segundo). Isso é bastante, por padrão, quando o set-top box é iniciado, ele funciona a 100 Hz. E a maioria dos jogos usa essa frequência.
Andy, o desenvolvedor do jogo, ajustou o cronômetro para 1 kHz para que os movimentos fossem calculados com mais precisão. Andy é propenso a excessos e, se imitarmos a gravidade, faremos isso com a maior precisão possível!
Mas e se a aceleração do cronômetro de alguma forma afetasse o tempo geral do programa e, portanto, o relógio que ajusta a taxa de transmissão do cartão de memória?
Comentei o código do cronômetro. O erro nunca mais aconteceu. Mas isso não significa que o corrigimos, porque a falha ocorreu aleatoriamente. E se eu apenas tiver sorte?
Alguns dias depois, experimentei o programa de teste novamente. O bug não se repetiu. Voltei para a base de código completa do jogo e alterei o código de salvar e carregar para que o cronômetro programável fosse redefinido para seu valor original (100 Hz) antes de acessar o cartão de memória e, em seguida, de volta para 1 kHz novamente. Não houve mais travamentos.
Mas por que isso aconteceu?
Voltei para o programa de teste novamente. Tentei encontrar alguma regularidade na ocorrência de um erro com um temporizador de 1 kHz. Acabei percebendo que o erro ocorre quando alguém joga com o controlador PS1. Já que eu raramente faria isso - por que precisaria de um controlador ao testar o código de salvamento e carregamento? - então não percebi essa dependência. Mas um dia um de nossos artistas estava esperando que eu terminasse o teste - eu provavelmente estava xingando naquele momento - e nervosamente torceu o controlador em suas mãos. Ocorreu um erro. "Espere o que ?! Bem, faça de novo! "
Quando percebi que esses dois eventos estão interligados, consegui reproduzir facilmente o erro: comecei a escrever no cartão de memória, movi o controlador, danifiquei o cartão de memória. Para mim, parecia um bug de hardware.
Procurei Connie e contei minha descoberta. Ela repassou a informação a um dos engenheiros que projetou o PS1. "Impossível", respondeu ele, "não pode ser um problema de hardware." Pedi a Connie que falasse conosco.
O engenheiro me ligou e discutimos com ele em seu inglês ruim e meu japonês (extremamente) quebrado. Finalmente, eu disse: "Deixe-me enviar meu programa de teste de 30 linhas em que o movimento do controlador causa um bug." Ele concordou. Disse que era uma perda de tempo e que estava terrivelmente ocupado trabalhando em um novo projeto, mas que desistiria porque somos um desenvolvedor muito importante para a Sony. Limpei meu programa de teste e enviei para ele.
Na noite seguinte (estávamos em Los Angeles e ele em Tóquio), ele me ligou e se desculpou constrangido. Foi um problema de hardware.
Não sei exatamente o que era o bug, mas pelo que ouvi na sede da Sony, definir o temporizador para um valor alto o suficiente iria interferir nos componentes da placa-mãe próximos ao cristal do temporizador. Um deles era o controlador de taxa de baud do cartão de memória, que também definia a taxa de baud para os controladores. Não sou engenheiro, então posso ter confundido algo.
Mas a questão é que houve interferência entre os componentes da placa-mãe. E ao transmitir dados simultaneamente através da porta do controlador e da porta do cartão de memória com um temporizador operando na frequência de 1 kHz, os bits foram perdidos, os dados foram perdidos e o cartão foi danificado.
Vacas más
Na década de 1980, meu mentor Sergei escreveu um software para o CM-1800, um clone soviético do PDP-11. Este microcomputador acaba de ser instalado em uma estação ferroviária perto de Sverdlovsk, um importante centro de transporte na URSS. O novo sistema foi projetado para o roteamento de vagões e fluxos de carga. Mas acabou sendo um bug irritante que causava travamentos e travamentos aleatórios. As quedas sempre ocorriam quando alguém ia para casa à noite. Mas apesar da investigação cuidadosa no dia seguinte, o computador funcionou corretamente com todos os testes manuais e automatizados. Isso geralmente indica uma condição de corrida ou algum outro bug de simultaneidade que se manifesta sob certas condições. Cansado de ligações noturnas, Sergei decidiu ir ao fundo da questão e, em primeiro lugar, entender quais condições no pátio de manobra levaram ao colapso do computador.
Primeiro, ele coletou estatísticas sobre todas as quedas inexplicáveis e construiu um gráfico baseado em datas e horas. O padrão era óbvio. Depois de assistir por mais alguns dias, Sergey percebeu que poderia prever facilmente o tempo de futuras falhas do sistema.
Ele logo soube que as interrupções só ocorriam quando a estação separava vagões de gado do norte da Ucrânia e oeste da Rússia para um matadouro próximo. Isso por si só era estranho, porque o matadouro era abastecido por fazendas muito mais próximas no Cazaquistão.
A usina nuclear de Chernobyl explodiu em 1986 e a precipitação radioativa tornou a área circundante inabitável. Grandes áreas no norte da Ucrânia, Bielo-Rússia e oeste da Rússia foram contaminadas. Suspeitando de um alto nível de radiação nos carros que chegam, Sergei desenvolveu um método para testar essa teoria. A população foi proibida de ter dosímetros, então Sergei colocou vários militares na estação ferroviária. Depois de vários goles de vodca, ele conseguiu convencer o soldado a medir o nível de radiação em um dos carros suspeitos. Descobriu-se que o nível é várias vezes superior aos valores normais.
Além de o gado emitir forte radiação, seu nível era tão alto que ocasionou a perda acidental de bits na memória do CM-1800, que estava no prédio ao lado da estação.
Havia escassez de alimentos na URSS e as autoridades decidiram misturar a carne de "Chernobyl" com carne de outras regiões do país. Isso tornou possível reduzir o nível geral de radioatividade sem perder recursos valiosos. Ao saber disso, Sergei imediatamente preencheu os documentos de emigração. E as quedas do computador pararam por si mesmas quando o nível de radiação diminuiu com o tempo.
Através dos canos
A Movietech Solutions já criou um software para salas de cinema de bilheteria, bilhetagem e gerenciamento geral. A versão DOS do aplicativo carro-chefe tem sido bastante popular entre as cadeias de teatro de pequeno e médio porte na América do Norte. Portanto, não é surpreendente que, quando a versão do Windows 95 foi anunciada, integrada com as telas sensíveis ao toque mais recentes e quiosques de autoatendimento e equipada com todos os tipos de ferramentas de relatório, ela também se tornou popular rapidamente. Na maioria das vezes, a atualização ocorreu sem problemas. Os profissionais de TI no local instalaram novo hardware, migraram dados e os negócios continuaram. Exceto quando não continuou. Quando isso aconteceu, a empresa enviou James, apelidado de "The Cleaner".
Embora esse apelido faça alusão ao tipo nefasto, o limpador é apenas uma combinação de instrutor, instalador e pau para toda obra. James poderia passar alguns dias na casa do cliente juntando todos os componentes e, então, por alguns dias, ele ensinou a equipe como usar o novo sistema, consertando quaisquer problemas de hardware que pudessem surgir e realmente ajudando o software a passar por seu período de formação.
Portanto, não é de se estranhar que, nesse momento agitado, James chegasse ao escritório pela manhã, e não tivesse tempo de se aproximar de sua mesa, quando foi saudado pelo patrão, cheio de cafeína acima do normal.
“Temo que você precise viajar para Annapolis, na Nova Escócia, o mais rápido possível. Todo o sistema deles caiu e, após uma noite trabalhando com seus engenheiros, não conseguimos descobrir o que aconteceu. Parece que o servidor tem uma falha de rede. Mas só depois de o sistema funcionar por alguns minutos.
- Eles não voltaram para o sistema antigo? - James respondeu bastante sério, embora em sua mente arregalasse os olhos de surpresa.
- Exatamente: seu especialista em TI "mudou de prioridade" e decidiu sair com seu antigo servidor. James, eles instalaram o sistema em seis locais e acabaram de pagar pelo suporte premium, e seus negócios agora estão na década de 1950.
James se endireitou ligeiramente.
- Isso é outro assunto. Ok, vamos começar.
Quando chegou a Annapolis, a primeira coisa que fez foi encontrar o primeiro cinema do cliente que apresentava problemas. No mapa feito no aeroporto, tudo parecia decente, mas os arredores do endereço desejado pareciam suspeitos. Não é um gueto, mas lembra o filme noir. Quando James estacionou no meio-fio no centro, uma prostituta se aproximou dele. Dado o tamanho de Annapolis, era provavelmente o único em toda a cidade. Sua aparência imediatamente lembrou a famosa personagem que ofereceu sexo por dinheiro na tela grande. Não, não sobre Julia Roberts, mas sobre Jon Voight [uma dica do filme "Midnight Cowboy" - aprox. por. ]
Depois de mandar a prostituta para casa, James foi ao cinema. O ambiente ficou melhor, mas ainda dá a impressão de decadente. Não que James estivesse muito preocupado. Ele já tinha estado em lugares miseráveis. E este era o Canadá, onde até os ladrões são educados o suficiente para agradecer depois de roubar sua carteira.
A entrada lateral do cinema ficava em um beco úmido. James foi até a porta e bateu. Logo ela rangeu e abriu um pouco.
- Você é um limpador? Uma voz rouca veio de dentro.
“Sim, sou eu ... vim consertar tudo.
James entrou no saguão do cinema. Provavelmente sem outra escolha, os funcionários começaram a emitir bilhetes de papel para os visitantes. Isso tornava os relatórios financeiros difíceis, quanto mais detalhes mais interessantes. Mas a equipe cumprimentou James com alívio e imediatamente o levou para a sala do servidor.
À primeira vista, tudo estava em ordem. James logou no servidor e verificou os lugares suspeitos de sempre. Sem problemas. No entanto, como precaução, James desligou o servidor, substituiu a NIC e fez o rollback do sistema. Ela imediatamente começou a trabalhar por completo. A equipe começou a vender ingressos novamente.
James ligou para Mark e relatou a situação. Não é difícil supor que James queira ficar aqui e ver se algo inesperado acontece. Ele desceu as escadas e começou a perguntar aos funcionários sobre o que havia acontecido. Obviamente, o sistema parou de funcionar. Eles desligaram e ligaram, funcionou. Mas depois de 10 minutos, o sistema caiu.
Nesse exato momento, algo semelhante aconteceu. De repente, o sistema de bilhetagem começou a dar erros. A equipe suspirou e pegou os tíquetes de papel, e James correu para a sala do servidor. Tudo parecia bem com o servidor.
Então um dos funcionários entrou.
- O sistema está funcionando novamente.
James estava confuso porque ele não tinha feito nada. Mais precisamente, nada que fizesse o sistema funcionar. Ele se desconectou, pegou o telefone e ligou para a equipe de suporte da empresa. Logo o mesmo funcionário entrou na sala do servidor.
- O sistema está mentindo.
James olhou para o servidor. Um padrão interessante e familiar de formas multicoloridas dançou na tela - tubos caoticamente torcidos e entrelaçados. Todos nós já vimos esse protetor de tela uma vez. Foi lindamente interpretado e literalmente hipnotizado.
James apertou o botão e o padrão desapareceu. Ele correu para a bilheteria e no caminho encontrou o funcionário que voltava para ele.
- O sistema está funcionando novamente.
Se você conseguir fazer uma palmada no rosto mentalmente, foi exatamente o que James fez. Protetor de tela. Ele usa OpenGL. E, portanto, durante a operação, ele consome todos os recursos do processador do servidor. Como resultado, cada solicitação ao servidor atinge o tempo limite.
James voltou para a sala do servidor, conectou-se e substituiu o belo protetor de tela do pipe por uma tela em branco. Ou seja, em vez de um protetor de tela que consome 100% dos recursos do processador, instalei outro que não consome recursos. Então esperei 10 minutos para verificar meu palpite.
Quando James chegou ao próximo cinema, ele se perguntou como explicar ao seu supervisor que ele tinha acabado de voar 800 km para desligar o protetor de tela.
Bater em uma fase lunar específica
História real. Era uma vez um bug de software que dependia da fase da lua. Havia uma pequena sub-rotina que era comumente usada em vários programas do MIT para calcular a aproximação da fase verdadeira da lua. GLS construiu esta sub-rotina em um programa LISP que produz uma string com carimbo de data / hora de quase 80 caracteres ao escrever um arquivo. Era muito raro a primeira linha de uma mensagem ser muito longa e continuar na próxima linha. E quando o programa então leu este arquivo, ele amaldiçoou. O comprimento da primeira linha dependia da data e hora exatas, bem como do comprimento da especificação da fase no momento em que o carimbo de hora foi impresso. Ou seja, o bug dependia literalmente da fase da lua!
Primeira edição em papel do Jargon File(Steele-1983) continha uma amostra de tal string levando ao bug descrito, mas o compositor o "consertou". Desde então, foi descrito como um "bug da fase lunar".
No entanto, tenha cuidado com as suposições. Há alguns anos, engenheiros do CERN (Centro Europeu de Pesquisa Nuclear) cometeram erros em experimentos realizados no Grande Colisor Eletron-Pósitron. Como os computadores estão processando ativamente a enorme quantidade de dados gerados por este dispositivo antes de mostrar o resultado aos cientistas, muitos presumiram que o software é de alguma forma sensível à fase da lua. Vários engenheiros desesperados descobriram a verdade. O erro ocorreu devido a uma ligeira mudança na geometria do anel de 27 km devido à deformação da Terra durante a passagem da Lua! Essa história entrou no folclore dos físicos como "a vingança de Newton sobre a física de partículas" e um exemplo da conexão entre as leis físicas mais simples e mais antigas com os conceitos científicos mais avançados.
A descarga do vaso sanitário para o trem
O melhor bug de hardware de que já ouvi falar foi em um trem de alta velocidade na França. O bug levou à frenagem de emergência do trem, mas apenas se houvesse passageiros a bordo. Em cada caso, o trem foi retirado de serviço, verificado, mas nada foi encontrado. Em seguida, ele foi enviado de volta para a linha e ele imediatamente parou de emergência.
Durante uma das verificações, um engenheiro que viajava no trem foi ao banheiro. Logo ele foi levado embora, BOOM! Parada de emergência.
O maquinista entrou em contato com o motorista e perguntou:
- O que você fez antes de frear?
- Bom, abrandei na descida ...
Foi estranho, porque durante a marcha normal o comboio desacelera nas encostas dezenas de vezes. O trem seguiu em frente e, na descida seguinte, o maquinista avisou:
- Vou desacelerar.
Nada aconteceu.
- O que você fez na última travagem? - perguntou o motorista.
- Bem ... eu estava no banheiro ...
- Bem, então vá ao banheiro e faça o que você fez quando descemos de novo!
O engenheiro foi ao banheiro e quando o motorista avisou: "Estou freando", deu a descarga. Claro, o trem parou imediatamente.
Agora eles podiam reproduzir o problema e precisavam encontrar a causa.
Dois minutos depois, eles notaram que o cabo do controle remoto para frenagem do motor (o trem tinha um motor nas duas pontas) foi desconectado da parede do quadro elétrico e colocado no relé que controlava o solenóide do plugue do vaso sanitário ... Quando o relé ligou, interferiu no cabo do freio e no sistema a proteção contra colisões simplesmente incluía frenagem de emergência.
O portal que odiava FORTRAN
Há alguns meses, percebemos que as conexões de rede no continente [isso foi no Havaí] estavam ficando muito, muito lentas. Pode durar 10-15 minutos e reaparecer de repente. Depois de um tempo, um colega meu reclamou para mim que as conexões de rede no continente não estavam funcionando. Ele tinha algum código FORTRAN que precisava ser copiado para uma máquina no continente, mas não funcionou porque "a rede não durou o suficiente para que o upload do FTP fosse concluído".
Sim, descobriu-se que falhas de rede ocorreram quando um colega tentou enviar por FTP o arquivo de origem do FORTRAN para uma máquina no continente. Tentamos arquivar o arquivo: então ele copiou silenciosamente (mas não havia descompactador na máquina de destino, então o problema não foi resolvido). Por fim, "dividimos" o código FORTRAN em blocos muito pequenos e os enviamos um de cada vez. A maioria dos fragmentos foi copiada sem problemas, mas alguns deles não funcionaram, ou funcionaram após várias tentativas.
Depois de examinar os fragmentos do problema, descobrimos que eles têm algo em comum: todos eles contêm blocos de comentários que começam e terminam com linhas que consistem em letras C maiúsculas (como um colega preferiu comentar no FORTRAN). Enviamos emails para o continente aos especialistas em redes e pedimos ajuda. Claro, eles queriam ver amostras de nossos arquivos que não puderam ser enviados por FTP ... mas nossas cartas não os alcançaram. Finalmente, criamos uma descrição simples de como são os arquivos não encaminhados. Funcionou :) [Atrevo-me a adicionar aqui um exemplo de um dos comentários problemáticos no FORTRAN? Provavelmente não vale a pena!]
No final, conseguimos descobrir. Um novo gateway foi instalado recentemente entre nossa parte do campus e a rede do continente. Ele tinha ENORME dificuldade para transmitir pacotes que continham Cs maiúsculos duplicados! Apenas alguns desses pacotes podem consumir todos os recursos do gateway e evitar que a maioria dos outros pacotes seja interrompida. Reclamamos com o fabricante do gateway ... e eles nos disseram: “Ah, sim, você encontrou um bug C duplicado! Já sabemos sobre ele. " No final, resolvemos o problema comprando um novo gateway de outro fabricante (em defesa do primeiro, direi que a impossibilidade de transferir programas para FORTRAN para alguém pode ser uma vantagem!).
Tempos difíceis
Vários anos atrás, enquanto trabalhava em um sistema Perl ETL projetado para reduzir o custo dos testes clínicos de Fase 3, precisei processar cerca de 40.000 datas. Dois deles não passaram no teste. Isso não me incomodou muito, pois essas datas foram retiradas dos dados fornecidos pelo cliente, o que muitas vezes era, digamos, surpreendente. Mas quando verifiquei os dados iniciais, descobri que essas datas eram 1º de janeiro de 2011 e 1º de janeiro de 2007. Achei que o bug estava no programa que acabei de escrever, mas descobri que já tinha 30 anos. Isso pode parecer misterioso para aqueles que não estão familiarizados com o ecossistema de software. Por causa de uma decisão de longa data de outra empresa de ganhar dinheiro, meu cliente me pagou para corrigir um bug que uma empresa introduziu acidentalmente e outra intencionalmente. Para que você entenda do que se trata,Preciso contar a vocês sobre a empresa que adicionou o recurso que se tornou um bug como resultado, bem como alguns outros eventos curiosos que contribuíram para o bug misterioso que consertei.
Nos velhos tempos, os computadores Apple às vezes redefiniam espontaneamente sua data para 1º de janeiro de 1904. O motivo era simples: um "relógio do sistema" alimentado por bateria era usado para registrar a data e a hora. O que aconteceu quando a bateria acabou? Os computadores começaram a rastrear a data pelo número de segundos desde o início da era. A época significava a data original de referência e, para o Macintosh, era 1º de janeiro de 1904. E depois que a bateria acabou, a data atual foi redefinida para a especificada. Mas por que isso aconteceu?
Anteriormente, a Apple usava 32 bits para armazenar o número de segundos a partir da data original. Um bit pode armazenar um de dois valores - 1 ou 0. Dois bits podem armazenar um de quatro valores: 00, 01, 10, 11. Três bits - um valor de oito: 000, 001, 010, 011, 100, 101, 110, 111, etc. E 32 pode armazenar um de 2 32 valores, ou seja, 4 294 967 296 segundos. Para datas da Apple, isso tinha aproximadamente 136 anos, então Macs mais antigos não podem lidar com datas após 2040. E se a bateria do sistema acabar, a data é redefinida para 0 segundos a partir do início da época e você deve definir a data manualmente toda vez que ligar o computador (ou até comprar uma bateria nova).
No entanto, a decisão da Apple de armazenar datas como segundos desde o início da época significava que não podíamos lidar com datas antes do início da época, o que tinha implicações de longo alcance, como veremos. A Apple introduziu um recurso, não um bug. Entre outras coisas, isso significava que o sistema operacional Macintosh era imune ao "bug do milênio" (o que não pode ser dito sobre muitos aplicativos Mac que tinham seus próprios sistemas de data para contornar as restrições).
Ir em frente. Usamos o Lotus 1-2-3, o "aplicativo matador" desenvolvido pela IBM que ajudou a lançar a revolução do PC, embora os computadores Apple tivessem VisiCalc, o que tornou os computadores pessoais um sucesso. Para ser justo, se o 1-2-3 não tivesse aparecido, os PCs dificilmente teriam decolado, e a história dos computadores pessoais poderia ter se desenvolvido de forma muito diferente. O Lotus 1-2-3 tratou incorretamente 1900 como um ano bissexto. Quando a Microsoft lançou sua primeira planilha Multiplan, ela tinha uma pequena participação de mercado. E quando lançamos o projeto Excel, decidimos não apenas copiar o esquema de nomenclatura para linhas e colunas do Lotus 1-2-3, mas também para garantir a compatibilidade para bugs, tratando deliberadamente 1900 como um ano bissexto. Este problema persiste até hoje. Ou seja, no 1-2-3 era um bug, e no Excel foi uma decisão deliberada que garantiuque todos os usuários do 1-2-3 podem importar suas planilhas para o Excel sem alterar os dados, mesmo que estejam errados.
Mas havia outro problema. A Microsoft lançou pela primeira vez o Excel para Macintosh, que não reconhecia datas até 1º de janeiro de 1904. E no Excel, o início de uma era era 1º de janeiro de 1900. Portanto, os desenvolvedores fizeram uma alteração para que seu programa reconheça o tipo de época e armazene os dados dentro de si de acordo com a época desejada. A Microsoft até escreveu um artigo explicativo sobre isso. E essa decisão levou ao meu bug.
Meu sistema ETL recebeu planilhas Excel de clientes que foram criadas no Windows, mas também podiam ser criadas em um Mac. Portanto, o início de uma era na tabela pode ser 1º de janeiro de 1900 ou 1º de janeiro de 1904. Como descobrir? O formato de arquivo do Excel mostra as informações necessárias, mas o analisador que usei não apareceu (agora mostra) e presumiu que você conhece a época de uma determinada tabela. Provavelmente, eu poderia gastar mais tempo entendendo o binário do Excel e enviando o patch para o autor do analisador, mas tinha muito a fazer para o cliente, então rapidamente escrevi uma heurística para determinar a época. Foi simples.
No Excel, a data 5 de julho de 1998 pode ser representada no formato "07-05-98" (sistema americano inútil), "5 de julho de 98", "5 de julho de 1998", "5 de julho de 98" ou em algum outro formato inútil (ironicamente, um dos formatos que minha versão do Excel não oferecia era o padrão ISO 8601). No entanto, na tabela, a data não formatada foi armazenada como "35981" para a época de 1900 ou "34519" para a época de 1904 (os números representam o número de dias desde o início da época). Eu estava usando um analisador simples para extrair o ano da data formatada e, em seguida, usando o analisador Excel para extrair o ano da data não formatada. Se os dois valores diferissem em 4 anos, então entendi que estava usando o sistema com o era-1904.
Por que simplesmente não usei datas formatadas? Porque 5 de julho de 1998 pode ser formatado como "Julho de 98", faltando o dia do mês. Recebemos tabelas de tantas empresas que as criaram de maneiras tão diferentes que nós (neste caso, eu) tivemos que lidar com as datas. Além disso, se o Excel acerta, nós também devemos!
Então encontrei 39082. Deixe-me lembrar que o Lotus 1-2-3 considerou 1900 um ano bissexto, e isso foi fielmente repetido no Excel. E como isso adicionou um dia a 1900, muitas funções de data podem estar erradas naquele mesmo dia. Ou seja, 39082 poderia ter sido 1º de janeiro de 2011 (em Macs) ou 31 de dezembro de 2006 (no Windows). Se meu "analisador de anos" estava extraindo 2011 do valor formatado, está tudo bem. Mas como o analisador do Excel não sabe qual época está sendo usada, o padrão é epoch-1900, retornando 2006. Meu aplicativo viu que havia uma diferença de 5 anos, considerou um erro, registrou e retornou um valor não formatado.
Para contornar isso, escrevi este (pseudocódigo):
diff = formatted_year - parsed_year
if 0 == diff
assume 1900 date system
if 4 == diff
assume 1904 date system
if 5 == diff and month is December and day is 31
assume 1904 date system
E então todas as 40.000 datas foram analisadas corretamente.
No meio de grandes trabalhos de impressão
No início dos anos 80, meu pai trabalhava na Storage Technology, uma unidade de negócios extinta que construía unidades de fita e sistemas pneumáticos para alimentação de fita em alta velocidade.
Eles redesenharam as unidades para que pudessem ter uma unidade central "A" conectada a sete unidades "B", e o pequeno SO na RAM que controlava a unidade "A" poderia delegar operações de leitura e gravação a todas as unidades "B".
Cada vez que a unidade "A" era iniciada, um disquete tinha que ser inserido na unidade periférica conectada a "A" para carregar o sistema operacional em sua memória. Era extremamente primitivo: o poder de processamento era fornecido por um microcontrolador de 8 bits.
O público-alvo desses equipamentos eram empresas com grandes armazenamentos de dados - bancos, redes de varejo etc. - que precisavam imprimir muitas etiquetas de endereço ou extratos bancários.
Um cliente teve um problema. No meio de um trabalho de impressão, uma determinada unidade "A" pode parar de funcionar, fazendo com que todo o trabalho seja ativado. Para fazer a unidade voltar a funcionar, a equipe teve que reiniciar tudo. E se isso acontecesse no meio de uma tarefa de seis horas, uma enorme quantidade de tempo caro do computador seria desperdiçada e a programação de toda a operação seria interrompida.
Tecnologias de armazenamento enviaram técnicos. Apesar de seus melhores esforços, eles não conseguiram reproduzir o bug nas condições de teste: parece ter ocorrido no meio de grandes trabalhos de impressão. O problema não era com o hardware, eles substituíram tudo o que podiam: a RAM, o microcontrolador, a unidade de disquete, todas as partes imagináveis de uma unidade de fita - o problema persistia.
Aí os técnicos ligaram para a sede e chamaram o Perito.
O examinador pegou uma cadeira e uma xícara de café, sentou-se na sala de informática - naquela época havia salas dedicadas aos computadores - e observou a equipe enfileirar um grande trabalho de impressão. O especialista esperou que uma falha ocorresse - e isso aconteceu. Todos olharam para o especialista - e ele não tinha ideia do por que isso aconteceu. Portanto, ele ordenou que a tarefa ficasse na fila novamente, e todos os funcionários com técnicos voltaram ao trabalho.
O especialista voltou a sentar-se na cadeira e esperou pela falha. Demorou cerca de seis horas e ocorreu a falha. O especialista novamente não tinha ideias, exceto que tudo aconteceu em uma sala cheia de pessoas. Ele mandou reiniciar a missão, sentou-se novamente e esperou.
Na terceira falha, o especialista percebeu algo. A falha ocorreu quando o pessoal trocou as correias em uma unidade externa. Além disso, o acidente ocorreu assim que um dos funcionários passou por um determinado ladrilho do chão.
O piso elevado era feito de ladrilhos de alumínio colocados de 15 a 20 cm de altura. Vários fios de computador corriam sob o piso elevado para que alguém não pisasse acidentalmente em um cabo importante. Os ladrilhos foram colocados bem firmes para que nenhum detrito pudesse entrar sob o piso elevado.
O especialista percebeu que um dos ladrilhos estava deformado. Quando um funcionário pisou em seu canto, o ladrilho esfregou suas bordas nos ladrilhos adjacentes. Eles também esfregaram as partes de plástico que conectavam as telhas, o que criou micro-descargas estáticas que criaram interferência de radiofrequência.
Hoje em dia, a RAM está muito melhor protegida de interferências de radiofrequência. Mas naquela época não era assim. O especialista percebeu que essas interferências atrapalhavam a memória e com ela o funcionamento do sistema operacional. Ele ligou para o serviço de acompanhantes, encomendou um novo ladrilho, instalou ele mesmo e o problema foi embora.
É a maré!
A história aconteceu em uma sala de servidores, no quarto ou quinto andar de um escritório em Portsmouth (eu acho), na área do cais.
Um dia, um servidor Unix com o banco de dados principal travou. Ele foi reiniciado, mas continuou a cair com alegria continuamente. Decidimos ligar para alguém do serviço de suporte.
Suporte cara ... acho que o nome dele era Mark, mas não importa ... acho que não o conheço. Não importa, realmente. Vamos ficar no Mark, ok? Excelente.
Então, algumas horas depois o Mark chegou (de Leeds a Portsmouth o caminho não é fechado, sabe), ligou o servidor e tudo funcionou sem problemas. Suporte típico de merda, o cliente fica muito chateado com isso. Mark examina os arquivos de log e não encontra nada questionável. Então Mark volta para o trem (ou qualquer meio de transporte que ele pegou, poderia ser uma vaca manca, pelo que eu sei ... bem, não importa, ok?) E volta para Leeds, perdendo o dia.
O servidor travou novamente naquela noite. A história é a mesma ... o servidor não sobe. Mark tenta ajudar remotamente, mas o cliente não consegue iniciar o servidor.
Outro trem, ônibus, merengue de limão ou alguma outra merda, e Mark está de volta a Portsmouth. Veja, o servidor inicializa sem problemas! Milagre. Mark verifica por várias horas se tudo está em ordem com o sistema operacional ou software e vai para Leeds.
Por volta do meio do dia, o servidor trava (vá com calma!). Desta vez, parece sensato trazer pessoal de suporte de hardware para substituir o servidor. Mas não, depois de cerca de 10 horas também cai.
A situação se repetiu por vários dias. O servidor está ativo, trava após cerca de 10 horas e não inicia nas próximas 2 horas. Eles verificaram o resfriamento, vazamentos de memória, verificaram tudo, mas não encontraram nada. Então os acidentes pararam.
A semana passou despreocupada ... todos estavam felizes. Feliz até que tudo comece de novo. A imagem é a mesma. 10 horas de trabalho, 2-3 horas de inatividade ...
E então alguém (acho que me disseram que essa pessoa não tinha nada a ver com TI) disse:
"Esta é a maré!"
A exclamação foi recebida com olhares vazios e, provavelmente, a mão de alguém vacilou no botão para chamar o guarda.
"Ele para de trabalhar com a maré."
Este pareceria um conceito completamente estranho para a equipe de suporte de TI que dificilmente lê o Anuário da Maré enquanto toma um café. Eles explicaram que não tinha nada a ver com a maré porque o servidor estava funcionando há uma semana sem problemas.
"A maré estava baixa na semana passada e alta nesta semana."
Um pouco de terminologia para quem não tem licença para operar um iate. As marés dependem do ciclo lunar. E conforme a Terra gira, a cada 12,5 horas a atração gravitacional do Sol e da Lua cria uma onda gigantesca. No início de um ciclo de 12,5 horas, ocorre a preia-mar, no meio do ciclo a vazante e novamente no final da preia-mar. Mas à medida que a órbita da lua muda, também muda a diferença entre vazante e fluxo. Quando a Lua está entre o Sol e a Terra ou no lado oposto da Terra (lua cheia ou sem lua), temos as marés de Syzygy - as marés mais altas e as marés vazantes mais baixas. Na meia lua, temos as marés em quadratura - as marés mais baixas. A diferença entre os dois extremos é bastante reduzida. O ciclo lunar dura 28 dias: sizígia - quadratura - sizígia - quadratura.
Quando as forças das marés foram explicadas aos técnicos, eles imediatamente pensaram em chamar a polícia. E é bastante lógico. Mas descobriu-se que o cara estava certo. Duas semanas antes, um contratorpedeiro havia atracado perto do escritório. Cada vez que a maré o subia a uma certa altura, o posto de radar do navio ficava no nível do piso da sala do servidor. E o radar (ou equipamento de guerra eletrônico, ou algum outro brinquedo militar) criou o caos nos computadores.
Missão de vôo para um foguete
Fui instruído a portar um grande (cerca de 400 mil linhas) sistema de controle e monitoramento de lançamento de mísseis para novas versões do sistema operacional, compilador e linguagem. Mais precisamente, do Solaris 2.5.1 no Solaris 7 e do Verdix Ada Development System (VADS) escrito em Ada 83 para o sistema Rational Apex Ada escrito em Ada 95. VADS foi adquirido pela Rational e seu produto é obsoleto, embora Rational tentou implementar versões compatíveis de pacotes específicos de VADS para facilitar a transição para o compilador Apex.
Três pessoas me ajudaram a obter um código compilado de forma limpa. Demorou duas semanas. E então trabalhei sozinho para fazer o sistema funcionar. Resumindo, foi a pior arquitetura e implementação de um sistema de software que já encontrei, então demorei mais dois meses para concluir a portabilidade. Em seguida, o sistema foi entregue para testes, o que levou vários meses. Corrigi imediatamente os bugs que encontrei durante o teste, mas seu número diminuiu rapidamente (o código-fonte era um sistema de produção, então sua funcionalidade funcionou de forma bastante confiável, eu só tive que remover os bugs que surgiram ao me adaptar ao novo compilador). No final, quando tudo deu certo, fui transferido para outro projeto.
E na sexta-feira antes do Dia de Ação de Graças, o telefone tocou.
Cerca de três semanas depois, um lançamento de foguete deveria ser testado e, em testes de laboratório de contagem regressiva, a sequência de comandos foi bloqueada. Na vida real, isso levaria à interrupção dos testes, e se um bloqueio ocorresse alguns segundos após a partida do motor, várias ações irreversíveis ocorreriam nos sistemas auxiliares, o que levaria muito - e caro - para preparar o foguete. Não iria começar, mas muitas pessoas ficariam muito chateadas com a perda de tempo e muito, muito dinheiro. Não deixe ninguém dizer que o Departamento de Defesa está gastando dinheiro de improviso - ainda não conheci um gerente de contrato que não tenha um orçamento primeiro ou segundo, seguido de um cronograma.
Nos meses anteriores, esse teste de contagem regressiva foi executado centenas de vezes em muitas variações, com apenas alguns pequenos contratempos. Portanto, a probabilidade de isso acontecer era muito baixa, mas suas consequências foram muito significativas. Multiplique esses dois fatores e você entenderá que as notícias previam uma semana de feriados arruinada para mim e dezenas de engenheiros e gerentes.
E a atenção foi atraída para mim como a pessoa que portou o sistema.
Como acontece com a maioria dos sistemas de segurança crítica, muitos parâmetros foram registrados aqui, então foi bastante fácil identificar algumas linhas de código que foram executadas antes do sistema travar. E, claro, não havia absolutamente nada de extraordinário neles, as mesmas expressões foram executadas com sucesso literalmente milhares de vezes durante a mesma execução.
Chamamos o pessoal do Apex para o Rational porque eles desenvolveram o compilador e algumas das rotinas que desenvolveram foram chamadas no código suspeito. Eles (e todos os demais) ficaram impressionados com a necessidade de descobrir a causa do problema do significado nacional literal.
Como não havia nada de interessante nas toras, decidimos tentar reproduzir o problema em um laboratório local. Esta não foi uma tarefa fácil, pois o evento ocorria aproximadamente uma vez a cada 1000 execuções. Um dos supostos motivos foi que a chamada para uma função mutex desenvolvida pelo fornecedor (parte do lote de migração VADS)
Unlocknão levou ao desbloqueio. O encadeamento de chamada processou mensagens de pulsação, que nominalmente chegavam a cada segundo. Elevamos a frequência para 10 Hz, ou seja, 10 vezes por segundo, e começamos a correr. Após cerca de uma hora, o sistema foi bloqueado. No log, vimos que a sequência de mensagens gravadas era a mesma do teste que falhou. Fizemos mais algumas corridas, o sistema bloqueou de forma estável 45-90 minutos após a partida e cada vez que o registro continha a mesma pista. Apesar de estarmos tecnicamente executando código diferente agora - a taxa de mensagens era diferente - o comportamento do sistema se repetiu, então nos certificamos de que esse cenário de carregamento levasse ao mesmo problema.
Agora era necessário descobrir exatamente onde na sequência de expressões ocorreu o bloqueio.
Esta implementação usou o sistema de tarefas Ada e foi incrivelmente mal usada. As tarefas são uma construção de alto nível executável simultaneamente em Ada, uma espécie de threads de execução, apenas incorporados na própria linguagem. Quando duas tarefas precisam interagir, elas "se encontram", trocam os dados necessários e, em seguida, interrompem o encontro e retornam às suas performances independentes. No entanto, o sistema foi implementado de forma diferente. Depois que um alvo tinha um encontro, esse alvo se encontrava com outro, que então se encontrava com um terceiro, e assim por diante, até que algum processamento fosse concluído. Depois disso, todos esses encontros terminaram e cada tarefa teve que retornar à sua execução. Ou seja, estávamos lidando com o sistema de chamada de função mais caro do mundo,que interrompeu todo o processo de "multitarefa" enquanto processava alguns dos dados de entrada. E antes, isso não causava problemas apenas porque o rendimento era muito baixo.
Descrevi esse mecanismo de tarefa porque quando um encontro foi solicitado ou esperado para ser concluído, uma "troca de tarefa" poderia ocorrer. Ou seja, o processador pode começar a processar outra tarefa pronta para ser executada. Acontece que quando uma tarefa está pronta para o encontro com outra tarefa, a execução de uma tarefa completamente diferente pode começar e, eventualmente, o controle retorna ao primeiro encontro. E outros eventos podem ocorrer que levam a uma troca de tarefa; um desses eventos é uma chamada de função do sistema, como imprimir ou executar um mutex.
Para entender qual linha de código estava causando o problema, eu precisava encontrar uma maneira de registrar o progresso da sequência de expressões sem acionar uma troca de tarefa, o que poderia evitar que o travamento ocorresse. Então eu não pude aproveitar
Put_Line()para não realizar operações de E / S. Você poderia definir uma variável de contador ou algo parecido, mas como posso ver seu valor se não posso exibi-lo na tela?
Além disso, ao examinar o log, verificou-se que, apesar do congelamento do processamento das mensagens de heartbeat, que bloqueava todas as operações de I / O do processo e não permitia a realização de outro processamento, outras tarefas independentes continuavam a ser executadas. Ou seja, o trabalho não foi bloqueado inteiramente, apenas a cadeia (crítica) de tarefas.
Este foi o gancho necessário para avaliar a expressão de bloqueio.
Fiz um pacote Ada que continha uma tarefa, um tipo enumerado e uma variável global desse tipo. Literais enumerados foram amarrados para expressões específicas sequências problemáticos (por exemplo
Incrementing_Buffer_Index, Locking_Mutex,Mutex_Unlocked) e, em seguida, inseriu expressões de atribuição nele, que atribuíram a enumeração correspondente a uma variável global. Como o código-objeto de tudo isso simplesmente mantinha uma constante na memória, a alternância de tarefas como resultado de sua execução era extremamente improvável. Em primeiro lugar, suspeitamos de expressões que poderiam alternar a tarefa, uma vez que o bloqueio ocorreu durante a execução, e não retornam ao alternar a tarefa de volta (por vários motivos).
A tarefa de rastreamento simplesmente era executada em um loop e periodicamente verificada para ver se o valor da variável global havia mudado. A cada alteração, o valor era salvo em um arquivo. Depois, uma breve espera e um novo cheque. Eu escrevi a variável em um arquivo porque a tarefa foi executada apenas quando o sistema a selecionou para execução ao alternar a tarefa na área do problema. O que quer que aconteça nesta tarefa não afetará outras tarefas bloqueadas não relacionadas.
Esperava-se que quando o sistema atingir a execução do código problemático, a variável global fosse zerada a cada expressão seguinte. Então algo acontecerá, levando a uma troca da tarefa, e como a frequência de sua execução (10 Hz) é menor que a da tarefa de monitoramento, o monitor pode fixar o valor da variável global e escrevê-lo. Em uma situação normal, eu poderia obter uma sequência repetitiva de um subconjunto de enumerações: os últimos valores da variável no momento da troca de tarefa. Ao travar, a variável global não deve mais ser alterada, e o último valor escrito mostrará qual expressão não concluiu a execução.
Lançado o código de rastreamento. Ele está congelado. E o monitoramento funcionou como um relógio.
O log acabou com a sequência esperada, que foi interrompida por um valor que indicava que o mutex foi chamado
Unlocke a tarefa estava pendente - como foi o caso de milhares de chamadas anteriores.
Nessa época, os engenheiros da Apex analisaram freneticamente seu código e encontraram um lugar no mutex onde, em teoria, um bloqueio poderia ocorrer. Mas sua probabilidade era muito pequena, uma vez que apenas uma certa sequência de eventos ocorrendo em um determinado momento poderia levar ao bloqueio. Lei de Murphy, rapazes, essa é a lei de Murphy.
Para proteger esse trecho de código, substituí as chamadas para as funções mutex (criadas com base na funcionalidade mutex do sistema operacional) por um pequeno pacote mutex nativo Ada para controlar o acesso mutex a esse trecho.
Colou no código e executei o teste. Sete horas depois, o código continuou a funcionar.
Meu código foi entregue ao Rational, onde foi compilado, desmontado e verificado que não usa a mesma abordagem que foi usada nas funções mutex problemáticas.
Foi a revisão de código mais lotada da minha carreira :) Havia cerca de dez engenheiros e gerentes na sala comigo, mais uma dúzia de pessoas conectadas em uma teleconferência - e todos eles examinaram cerca de 20 linhas de código.
O código foi revisado, novos arquivos executáveis foram construídos e enviados para testes de regressão formal. Algumas semanas depois, os testes de contagem regressiva foram bem-sucedidos e o foguete decolou.
Ok, tudo isso é bom e lindo, mas qual é o objetivo dessa história?
Era um problema totalmente nojento. Centenas de milhares de linhas de código, execução paralela, mais de uma dúzia de processos de interação, arquitetura e implementação inadequadas, interfaces para sistemas embarcados e milhões de dólares gastos. Sem pressão, certo.
Eu não fui o único a trabalhar neste problema, embora tenha sido o centro das atenções enquanto fazia a transferência. Mas mesmo que eu tenha feito isso, isso não significa que entendi todas as centenas de milhares de linhas de código, ou pelo menos as folheei. O código e os logs foram analisados por engenheiros de todo o país, mas quando me contaram suas hipóteses sobre as causas da falha, demorei meio minuto para refutá-las. E quando me pediram para analisar teorias, passei para outra pessoa, porque era óbvio para mim que esses engenheiros estavam indo para o lado errado. Parece presunçoso? Sim, é, mas rejeitei hipóteses e pedidos por um motivo diferente.
Eu entendi a natureza do problema. Eu não sabia exatamente onde estava ou por que, mas sabia exatamente o que estava acontecendo.
Ao longo dos anos, acumulei muito conhecimento e experiência. Fui um dos pioneiros no uso do Ada, entendi suas vantagens e desvantagens. Eu sei como as bibliotecas de tempo de execução Ada lidam com tarefas e lidam com a execução paralela. E sou bom em programação de baixo nível ao nível de memória, registros e montador. Em outras palavras, tenho um conhecimento profundo na minha área. E eu os usei para encontrar a causa do problema. Eu não apenas contornei o bug, mas descobri como encontrá-lo em um ambiente de execução muito sensível.
Essas histórias de luta com o código não são muito interessantes para aqueles que não estão familiarizados com as peculiaridades e condições de tal luta. Mas essas histórias ajudam a entender o que é necessário para resolver problemas realmente difíceis.
Você precisa ser mais do que apenas um programador para resolver problemas realmente difíceis. Você precisa entender o “destino” do código, como ele interage com seu ambiente e como o próprio ambiente funciona.
E então você tem sua semana de férias estragada.
Continua.