A Idéia mais Estúpida da Computação

Osvaldo Santana, o pythonista, postou no seu blog algumas “cagadas computacionais” que cometera. Lembrei-me de um causo interessante…

Meu primeiro emprego foi como servidor público, técnico administrativo na Universidade de Brasília. Era um emprego chato para mim, tecnocrata que sou, mas consegui escapar da chatice convencendo o pessoal a me deixar usar um Debian na minha máquina – na época, um Debian Etch, ainda em testes.

Em casa, eu já usava Debian, mas não tinha conexão com a Internet, de modo que fiquei preso ao Debian Woody, GNOME 1.8 etc. etc. No trabalho, porém, eu tinha uma ótima conexão, então atualizava freqüentemente o sistema operacional. Foi minha primeira experiência mais interativa com o APT: antes, só utilizava para instalar pacotes dos sete CDs do Woody que eu tinha gravado.

Depois de um bom tempo usando Debian, enfrento meu primeiro inferno de dependências. Bem feito, quem mandou misturar testing, unstable e até experimental, né? De qualquer forma, entrei em desespero, porque aquela era minha máquina de trabalho e tinha de funcionar. Bato a cabeça, reinstalo pacote, tiro repositório, dou apt-get update pra cá, apt-get dist-upgrade para lá mas nada se resolve…

No desespero daquela sexta feira, tomo uma decisão drástica: vou desinstalar o APT! Lá vamos nós digitar o inacreditável comando:

apt-get remove apt

O Debian não gostou muito… A mensagem que recebi foi algo como:

AVISO: Os pacotes essenciais a seguir serão removidos.
Isso NÃO deveria ser feito a menos que você saiba exatamente o que você está fazendo!
apt
Depois desta operação, 30,1MB de espaço em disco serão liberados.
Você está prestes a fazer algo potencialmente destrutivo.
Para continuar digite a frase 'Sim, faça o que eu digo!'
?]

Bem, é realmente uma mensagem assustadora. Qualquer pessoa perceberia que desinstalar o APT não era só aparentemente uma idéia sem sentido, era um absurdo estúpido! Mas eu não sou qualquer pessoa! Como sou brasileiro e não aprendo nunca, vou lá e digito:

Sim, faça o que eu digo!

Depois de todo o trabalho sujo feito, vamos tentar reinstalar o APT. Bem, vocês podem imaginar que não, não consegui fazer isso. Quando vi que a opção menos absurda seria recompilar o APT, desisti: fiz backup dos documentos e reinstalei o Debian.

Pelo menos saí do inferno de dependências 🙂

Tratamento de erros em C com goto

Esses dias, começou-se a discutir na lista de discussão da Python Brasil razões para se utilizar exceções. Em um certo momento, um participante reconhecidamente competente comentou o quanto é difícil tratar erros através do retorno de funções, como em C.

Quando se tem um algoritmo complexo, cada operação passível de erro implica em uma série de ifs para verificar se a operação ocorreu corretamente. Se a operação tiver falhado, será necessário reverter todas as operações anteriores para sair do algoritmo sem alterar o estado do programa.

Vejamos um exemplo. Suponha que eu tenha a segunte struct para representar arrays:

typedef struct {
        int size;
        int *array;
} array_t;

Agora, eu vou fazer uma função que lê, de um arquivo texto, o número de elementos a ser posto em um desses arrays e, logo em seguida, os elementos. Essa função também vai alocar a struct do array e o array de fato. O problema é que essa função é bastante propensa a erros, pois podemos não conseguir

  • abrir o arquvo dado;
  • alocar a struct;
  • ler o número de elementos do arquvo dado, seja por erro de entrada/saída, seja por fim do arquivo;
  • alocar memória para guardar os elementos a serem lidos;
  • ler um dos elementos, seja por erro de entrada/saída, seja por fim do arquivo.

Complicado, né? Note que, se conseguirmos abrir o arquivo mas não conseguirmos alocar a struct, temos de fechar o arquivo; se conseguirmos abrir o arquivo e alocar a struct mas não conseguirmos ler o número de elementos do arquivo, temos de dealocar a struct e fechar o arquivo; e assim por diante. Assim sendo, se verificarmos todos os erros e adotarmos a tradição de, em caso de erro, retornar NULL, nossa função seria mais ou menos assim:

array_t *readarray(const char *filename) {
        FILE *file;
        array_t *array;
        int i;

        file = fopen(filename, "r");
        if (file == NULL) return NULL;

        array = malloc(sizeof(array_t));
        if (array == NULL) {
		fclose(file);
		return NULL;
	}

        if (fscanf(file, "%d", &(array->size)) == EOF) {
		free(array);
		fclose(file);
		return NULL;
	}

        array->array = malloc(sizeof(int)*array->size);
        if (array->array == NULL)  {
		free(array);
		fclose(file);
		return NULL;
	}

        for (i = 0; i < array->size; i++) {
                if (fscanf(file, "%d", array->array+i) == EOF) {
			free(array->array);
			free(array);
			fclose(file);
			return NULL;
		}
        }
        return array;
}

De fato, bastante trabalhoso, e com muito código repetido…

Note, porém, como há duas situações no código acima. Em uma, quando tenho duas operações para reverter, preciso reverter primeiro a última executada, e depois a anterior. Por exemplo, quando vou dealocar tanto a struct quanto o array de inteiros, preciso dealocar primeiro o array de inteiros e depois a struct. Se dealoco a struct primeiro. posso não conseguir dealocar o array posteriormente.

Na outra situação, a ordem não importa. Por exemplo, se vou dealocar a struct e fechar o arquivo, não importa em que ordem eu o faça. Isso implica que eu posso, também, reverter primeiro a última operação executada e depois a primeira operação.

Qual o sentido disso? Bem, na prática, nunca vi uma situação onde eu tenha de reverter primeiro a primeira operação executada, depois a segunda e assim por diante. Isso significa que, quando faço as operações a(), b(), c() etc. a maneira “natural” de revertê-las é chamando os reversores de trás para frente, mais ou menos como:

a();
b();
c();
/* ... */
revert_c();
revert_b();
revert_a();

Agora, vem o pulo do gato. No código acima, após cada operação, vamos colocar um if para verificar se ela falhou ou não. Se falhou, executar-se-á um goto para o reversor da última operação bem sucedida:

a();
if (failed_a()) goto FAILED_A;
b();
if (failed_b()) goto FAILED_B;
c();
if (failed_c()) goto FAILED_C;
/* ... */
revert_c();
FAILED_C:
revert_b();
FAILED_B:
revert_a();
FAILED_A:
return;

Se  a() falhar, o algoritmo retorna; se  b() falhar, o algoritmo vai para FAILED_B:, reverte  a() e retorna; se c() falhar, o algoritmo vai para FAILED_C, reverte b(), reverte  a() e retorna. Consegue ver o padrão?

Pois bem, se aplicarmos esse padrão à nossa função readarray() o resultado será algo como:

array_t *readarray(const char *filename) {
        FILE *file;
        array_t *array;
        int i;

        file = fopen(filename, "r");
        if (file == NULL) goto FILE_ERROR;

        array = malloc(sizeof(array_t));
        if (array == NULL) goto ARRAY_ALLOC_ERROR;

        if (fscanf(file, "%d", &(array->size)) == EOF)
                goto SIZE_READ_ERROR;

        array->array = malloc(sizeof(int)*array->size);
        if (array->array == NULL) goto ARRAY_ARRAY_ALLOC_ERROR;

        for (i = 0; i < array->size; i++) {
                if (fscanf(file, "%d", array->array+i) == EOF)
                        goto ARRAY_CONTENT_READ_ERROR;
        }
        return array;

        ARRAY_CONTENT_READ_ERROR:
        free(array->array);
        ARRAY_ARRAY_ALLOC_ERROR:
        SIZE_READ_ERROR:
        free(array);
        ARRAY_ALLOC_ERROR:
        fclose(file);
        FILE_ERROR:
        return NULL;
}

Quais as vantagens desse padrão? Bem, ele reduz a repetição de código de reversão de operações e separa o código de tratamento de erro da lógica da função. Na verdade, apesar de eu achar exceções o melhor método de tratamento de erros moderno, para tratamento de erros in loco (dentro da própria função) eu acho esse método muito mais prático.

Very funny.vbs

Hoje, recebo o seguinte e-mail de um amigo que trabalha com Visual Basic:

O Belo (e possivelmente bons conselhos, no geral, de como fazer código bom):
http://www.visibleprogress.com/vb_error_handling.htm

e o Horroroso
http://blogs.msdn.com/ericlippert/archive/2004/09/09/227461.aspx

O primeiro link realmente tem sugestões boas e interessantes para quem programa em Visual Basic. O segundo é uma lista de erros que podem ocorrer em Visual Basic e VBScript. O erro que me chamou a atenção foi

48 Error in loading DLL

Erros em carregamento de DLL em Visual Basic não são novidade; o que me surpreendeu é que esse erro também pode ocorrer com VBScript.

O ILOVEYOU, senhores, era mais que inevitável: era praticamente o resultado das leis da Fisíca.

Eu amo a GVT

Ontem, estava redigindo uma primeira versão da minha monografia de conclusão de curso. Lá pelas 14 h, vou fazer uma pesquisa no Google e, pimba! percebo que estou sem conexão com a Internet. Não me preocupei: meu modem é especiamente temperamental e, quando esquenta demais, trava. Basta reiniciá-lo que tudo volta ao normal.

Pois bem, reiniciei o modem, mas não consegui recuperar a conexão. Imaginei que o modem estava tão superaquecido que seria necessário esperar um pouco mais para que ele destravasse – embora os LEDs indicassem perfeito funcionamento.

Fui fazer outras atividades que estavam pendentes fora do computador. Duas ou três horas depois, volto para tentar reconectar. Ligo o modem, reinicio, utilizando poff e pon, o daemon pppd e… não funciona. Nesse momento, apelei e fiz aquilo que todo usuário de Linux considera uma vergonha no currículo: reiniciei o computador… Ok, só os usuários mais pedantes consideram isso uma vergonha, mas o que importa é que mesmo isso não funcionou.

Evidentemente, havia algum problema com a comunicação com o servidor. Fui fazer uns testes. Primeiro, olhei os logs do pppd com o comando plog. Noto que o pppd consegue receber um endereço IP externo, e recebe também os endereços dos servidores DNS da GVT. Tento pingar os servidores DNS mas eles não respondem! Isso tinha cara de problema de autenticação. Para verificar, comentei a linha com meu login e senha no arquivo de autenticação do pppd e reiniciei o pppd novamente. Agora, a saída do plog indicava que não consegui um endereço IP nem o endereço dos servidores DNS por falha na autenticação. Imaginei: “É, o problema não é autenticação… ao menos até esse ponto”.

Joguei a toalha e resolvi ligar para a GVT. Depois de percorrer o caminho padrão, fui atendido por um rapaz. Explico para ele a situação, falo que já reiniciei tanto o modem quanto o computador e até comento que enxergo o endereço dos servidores DNS mas não consigo pingá-los. A primeira surpresa agradável foi que o atendente não pediu para eu reiniciar o computador e o modem de novo! Ele foi verificar se havia algo de errado com minha configuração e… pimba! de novo: minha porta estava bloqueada.

Aí que me toquei: eu havia pago a fatura do mês passado na sexta-feira anterior, com seis dias de atraso. Ocorre que eu não havia recebido a fatura por correio, de modo que tive de buscá-la no site da GVT; entretanto, levei um bom tempo para me tocar que a fatura não havia chegado.

Explico-lhe a minha situação. Ele me transfere, então para o departamento de cobranças. Sem precisar esperar mais que meio minuto, sou atendido por uma simpática moça. Explico novamente a situação, ao que ela vai verificar o estado da minha conta. De fato, consta como não paga, e a moça me explica que há o prazo de 72 horas para que o pagamento conste nos servidores. Apesar de ser uma situação chata, concordo que é bem natural…

Aceito, então, a triste sina de esperar até a segunda-feira para ter minha conexão de volta… A atendente, porém, diz que pode solicitar a abertura de minha porta no mesmo momento. Oras, que ótimo! Ela solicita o serviço e diz que, em duas horas, no máximo, eu estaria com minha conexão restaurada. Peço para ela verificar o valor da última fatura, pois eu temia ter pago a fatura errada… mas felizmente paguei a correta.

Por fim, a atendente pede para eu responder uma pesquisa sobre o atendimento. Eu não poderia negar isso a ela, afinal, e lá vou eu, feliz, responder a questão. Naturalmente, disse estar plenamente satisfeito. De qualquer forma, fui lavar umas roupas, para esperar o tempo passar e, na volta, a conexão estava perfeita!

Encontrei alguns bugs no processo. Por exemplo, tive de digitar meu número de telefone no começo, por solicitação do software, pois estava falando pelo celular; entretanto, os dois atendentes me pediram meu número. Entretanto, esses são detalhes insignificantes.

A GVT nunca me causou dores de cabeça. Nunca me trataram como um criminoso por usar Linux – muito pelo contrário, seus manuais já prevêem o uso de Linux. No final de 2007, me ligaram para me fazer uma oferta que pode ser resumida assim:

Notamos que você usa pouco telefone e muita conexão. Nós podemos reduzir sua franquia e quadruplicar sua velocidade de conexão, mas você terá de pagar R$ 5,00 a menos.

Agora, pela primeira vez, precisei de seu atendimento – por um problema que, no fundo, eu mesmo causei – e fui perfeitamente atendido. Por tudo isso, aí vai o meu conselho: se você não usa os serviços da GVT acesse o www.gvt.com.br e contrate-os agora. Talvez você passe pelo único problema sério da GVT, que é a cobertura mais ou menos limitada e as longas filas de espera. Eu garanto: esse problema, vale a pena suportar.

Anúncio

Olá!

Pois é, Suspensão de Descrença é meu novo blog. Esse é um blog menos pessoal, mais técnico, em que falo muito mais de programação, computação e tecnologia da informação.

Ah, sim, você não me conhece? Prazer, meu nome é Adam Victor Nazareth Brandizzi. Seja bem vindo!

Só mais uma dica: dêem uma olhada na página sobre o blog.

Até mais!