segunda-feira, 12 de setembro de 2016

Programação de computadores pode ser um exercício de humildade

Apesar do assunto parecer nerd, e ter detalhes que só nerds e programadores entenderão (em parte por se identificarem, por terem passado ou assistido), acho que leigos no assunto também poderão entender e se divertir lendo este texto.

Fazer programas de computador pode ser um exercício de humildade. Pode ser algo que lhe faz aprender que não pode-se ter certeza de nada na vida. Que todas as mais caras e prezadas certezas podem virar fumaça (ou core dump) a qualquer momento. Então a única certeza que se pode ter é que não se pode ter certezas.

Imagina aquele programa que você faz com todo cuidado (ou pensa que teve todo cuidado). E você manda compilar, para converter o seu código fonte em programa executável.

Nesta hora aparecem milhares de erros, que não se sabe de onde vieram. Eles estavam todos escondidos prontos para dar o bote.

Muitos destes erros são resolvidos rapidamente. Tem vezes que muitas mensagens são ocasionados por um simples erro. Mas tem aqueles malditos, cuja mensagem não faz o menor sentido. Você pena por horas, e horas. Tem casos que até mostra para os amigos e colegas de trabalho, para algum descobrir que era uma bobagem.

Existem até histórias que programadores contam entre si, de coisas que se comparam às lendárias histórias de terror contadas em volta das fogueiras de acampamento, tal como aparece em filmes.

Uma vez um me contou que ele e um outro tinham acabado de passar uma destas histórias. Por 3 horas eles viam uma mensagem de erro indicando um erro em uma linha no programa a cada vez que compilavam, e não achavam o erro na linha. Faltou humildade de ler a mensagem de erro por completo. Quando resolveram ler viram "variável 'f' indefinida". Era só defini-la algumas linhas acima.

E existem as mensagens de erro criptográficas, que não fazem o menor sentido.

Outro amigo passou duas horas apanhando com uma mensagem destas. Fui almoçar, e voltei, e o coitado ainda apanhava com isto. Aí começou uma daquelas reuniões que mencionei acima. Eu, ele e mais outro, até que vi o que era. Ele usou uma palavra reservada, que só pode ser usada em um determinado contexto bem específico, como nome de variável, o que deixou o compilador doido mostrando uma mensagem muito louca. Quando mostrei qual era o erro o autor da façanha ficou muito envergonhado. Foi uma destas coisas traumáticas.

Tem erros que são mostrados com uma mensagem criptográfica, nonsense, no lugar errado. Uma daquelas mensagens que não faz menor sentido. Esquecer um ponto e vírgula, ";", num programa em Linguagem C costuma fazer isto. O erro costuma ser na linha acima da indicada, quando não é algumas linhas acima.

Ainda tem coisas muito loucas, de deixar qualquer programador maluco, de fazer ele rever tudo. Quando se esquece de abrir, ou fechar um bloco de programa. Dá medo lembrar. Existem até técnicas de programação para evitar que isto aconteça. Dá erros mais loucos, mais sem sentidos, e ao atacado, em larga escala. Alguém que já passou por isto até percebe que falta fechar ou abrir algum bloco, mas... onde foi mesmo? Qual bloco? Uma "correção" errada pode fazer mais estragos ainda, que só serão vistas adiante.

Esquecer de fechar, ou abrir parênteses, aspas etc, pode fazer loucuras, e criar incertezas parecidas com as citadas no parágrafo acima. O programador precisa saber bem o que está fazendo para corrigir isto direito.

E ainda tem, ou tinham, compiladores que soltavam na sua cara um "Goodbye. Too many errors." ("Adeus. Erros demais." - em livre tradução) quando a lista de erros era muito grande, e nem terminava de olhar o seu programa todo. Dá a sensação de "Seu código é desprezível demais para eu me dar o trabalho de olhar todo.". E tinham compiladores que paravam no primeiro erro que encontravam, te forçando a resolver erro a erro, sem lhe dar a chance de resolver aquele erro depois e os seguintes antes.

Passou pela primeira etapa da compilação, sobreviveu à compilação, e gerou o código, então chegou de juntar as bibliotecas de funções ao seu programa. Nesta hora pode tomar mensagens de funções desconhecidas, como "rsnd", quando era para ser "rand". Erros de digitação podem criar chamadas de funções que não existem. E pior, a função pode ser sua, criada pelo programador, chamada em dezenas de lugares, e você errou o nome dela quando a criou.

Terminou o calvário. Tudo certo. Terminou mesmo???? Nãooooooooooo...

Na hora de testar as coisas mais loucas podem acontecer. O programa pode abortar aparentemente do nada, te lançando um "core dump" (ou equivalente, dependendo do sistema operacional usado), e você não ter ideia do que aconteceu, onde errou etc.

A arrogância, a presunção, a prepotência, a sua certeza que está tudo correto etc, nestas horas só servem para aumentar a sua humilhação, para mostrar que o Universo é muito maior do que você. Que você é um nada.

Não é à toa que programadores veneram a Lei de Murphy (o artigo em inglês está bem mais completo) como a lei máxima que rege o Universo, e diversas outras formas de veneração.

Descobrir o que está fazendo o seu programa abortar pode se parecer com um trabalho de Sherlock Holmes. E o seu programa pode não abortar em todos os casos, com todas as suas massas de teste. E pode ter mais de um ponto que faz ele abortar.

E resolvido estes problemas, ou até não tendo passado por eles, ainda tem mais modos de dar errado. Um deles é o temido Loop Infinito. Ele é angustiante. Ele pode criar mais suspense do que os filmes Tubarão e O Exorcista juntos. Você não sabe o que está acontecendo. Se está processado mesmo o dado, ou está preso andando em círculos dentro de alguma rotina, como alguém perdido em um deserto sem nada para se orientar. Você espera por um resultado que nunca chegará. Esperará angustiosamente por ele.

Para alívio do programador existem ferramentas, e técnicas para caçar estes problemas.

Não se esqueçam de uma coisa. A cada problema que é corrigido, você volta ao início deste texto, isto é, tem que compilar de novo, com todas as possibilidades aterrorizantes citadas lá acontecerem de novo, mas tipicamente em menor escala.

Uma vez que o programa não aborta, processa os dados, termina de processar, dá o resultado etc, vem uma questão. O resultado está certo? Acharam que a Via Crúcis terminou, não? Tolinhos.

É necessário ter um modo de conferir o resultado, de saber se está certo ou não. Imagine se o resultado está errado por um erro de sinal em alguma conta? Era para ser um "-", mas colocou um "+", ou pior uma multiplicação. Tem casos que tem que se fazer as contas à mão... e depois ter que descobrir se é o programa ou a sua conta que está errada... ou os dois. E se os dois cometerem o mesmo erro nas contas?

Se ignorou mensagens de advertência da compilação, sem dar uma olhada sequer, pode estar encrencado. Pode ter esquecido de colocar a descrição de chamada de uma função, incluir um arquivo de header, e o compilador assumir que ela é de uma forma, quando é de outra. As coisas mais doidas podem acontecer, desde funcionar normalmente (Por absurdo que pareça, isto pode acontecer.), até dar os mais doidos resultados, incluindo abortar do nada. Cada caso é um caso, e pode ter certeza, a Lei de Murphy pode escolher o mais macabro para acontecer.

Ainda tem os casos especiais que tem que ser pensados, como alguém passar um dado que gera uma divisão por zero. Sim, muitos programas tem que ser à prova de usuários, pois eles são muito competentes em fazer o que não pode fazer, ou o que não foi previsto alguém fazer. Mais Lei de Murphy aqui.

Ainda tem aqueles problemas que acontecem em situações muito especiais (Lei de Murphy de novo), que são muito difíceis de prever, e pior ainda de se reproduzir. Um deles é vazamento de arrays, nos quais um array tem um tamanho, e você extrapola o tamanho dele, atropelando outras variáveis, sujando-as ou pegando dados de forma inválida, dando os resultados mais loucos possíveis. Nisto tudo pode parecer certinho, mas só percebe depois que está tudo errado.

E quando vazam informações de um dado para o outro? Aí você reduz a massa de teste para procurar o problema e... não acontece. Aumenta a massa de dados, e acontece. Qual é a massa de dados que faz isto acontecer? Qual é o dado, ou a combinação de dados, que faz isto acontecer? Passei meses para resolver um destes, para conseguir isolar em um canto pequeno o suficiente para acertá-lo em cheio. Ele era muito evasivo, muito esquivo. E eram simplesmente duas variáveis que não eram zeradas entre um item e outro no processamento. Pior, estava em produção a mais de 3 anos, e só eu vi.

Quando tem que processar dados que acontecem em tempo real tem que tomar uma nova série de cuidados. Pode acontecer uma coisa que não pode acontecer junto com uma outra (Lei de Murphy de novo), ou não pode começar a tratar uma coisa antes de terminar de tratar a outra. Um erro nisto pode fazer todos os sinais ficarem verde em um cruzamento, um avião pousar enquanto outro decola na mesma pista, uma usina nuclear explodir, o radar de um navio de guerra informar que um avião de passageiros é um caça etc.

Espaço em disco é outra ameaça. Um teste meu que durava um final de semana foi abortado por causa disto. Isto pode acontecer a qualquer momento, desde quando vai salvar o programa, e perder os fontes do programa que estava fazendo, compilando, ou até quando está fazendo testes. E pode ter sido causado por um colega de trabalho, que um programa que ele testava entrou em loop infinito (tal como falado acima), escrevendo no disco até o disco lotar e sabotar o trabalho de todos. Vi isto acontecer meses atrás.

Depois de resolver, melhor falando, achar que resolveu tudo que poderia dar errado, tratar todos os casos especiais etc, chega a hora de mostrar para o cliente, de colocar em produção etc. Agora surge outro medo... e se não funcionar na demonstração para o cliente, ou quando entrar em produção?

Uma vez vi um caso de não funcionar em uma situação especial que só aconteceu em uma demonstração, mesmo tendo feito outras muitas demonstrações antes.

Existem historias de que um engenheiro se suicidou no dia anterior da inauguração de uma ponte que ele construiu, tendo certeza que ela cairia na inauguração. Ela está lá até hoje. Como tem gente que achava que a Ponte de Tacoma Narrows era segura, que não cairia com a oscilação. Nem a certeza de dar errado, ou dar certo, são certas.

Sim, mas nunca se deve ter certeza de nada, eu garanto que a lista das coisas que podem dar errado que citei acima está curta. Por isto eu acho que a melhor sessão de bugs de manual de programa que vi até hoje, no qual não tinha nada a relatar, estava escrito "Devem existir alguns.".

A profissão de programador não é tão fácil como alguns pensam.

Então, Feliz Dia do Programador.


Versão 1.0.1: Correção de bugs de Português.

2 comentários:

  1. Imagina um ";" no final de um "#define" em C?
    Um "define" que é para uma constante cadeia de caracteres usada numa mensagem de um "puts".

    ResponderExcluir
    Respostas
    1. Lembro da demonstração do Windows 98, Bill Gates esteve ao vivo mostrando a mais revolucionária tecnologia criada até hoje, a tela azul da morte.

      Um das minhas maiores resistência a adotar paradigmas orientados a objetos é exatamente por haver abstração demais entre o que se escreve e como se executa, e ainda apanho muito quando estou a codar usando as bibliotecas de processamento simetrico da nvidia ( CUDA ) simplesmente por muito dos conceitos dela serem diretamente dependentes desse paradigma.

      Lembro de ter lido em algum lugar, se quer ser um bom programador, também tem que ser sadomazoquistas, por que tem que sentir prazer na frustação.

      Excluir