Em geral a experiência de desenvolver um programa para uma plataforma Unix costuma ser bastante positiva, pois apesar da famosa falta de amigabilidade com o usuário, o Unix é muito agradável ao programador. Não é agradável no sentido de ser fácil de programar, isto é, não é "fácil" no mesmo sentido em que o Visual Basic é "fácil". O que ocorre no Unix, que é de grande valia ao programador, é que o sistema é extremamente consistente, funciona de uma maneira muito lógica, é muito robusto e a interface com o sistema operacional (que consiste tão-somente nas system calls) é muito bem documentada e bem definido.
Por isso, do ponto de vista da interação com o Unix, não houve grandes obstáculos no desenvolvimento do Omnitty. Contudo, houve uma série de dificildades e desafios a superar do ponto de vista do planejamento e da implementação do programa, isto é, dos aspectos práticos do desenvolvimento.
O maior desafio no desenvolvimento do Omnitty consistiu no desenvolvimento do seu componente mais complexo, que é a biblioteca ROTE. Após muita pesquisa sobre terminais, seqüências de escape, tentativas de aproveitar código de emuladores de terminais famosos como o xterm e também o driver de console do Linux, acabei decidindo por implementar minha própria biblioteca de emulação e contribuí-la à comunidade. Em vários fóruns por quais passei nessa pesquisa haviam comentários quanto a frustração de haver nenhuma biblioteca que fornecesse a emulação de terminal de maneira modular e reaproveitável.
Foi um desafio desenvolvê-la porque a documentação sobre o funcionamento de terminais, apesar de abundante na Internet, é altamente contraditória e confusa devido à enorme quantidade de tipos de terminais e emuladores de terminais que existem. Uma fonte fundamental de documentação foi a "man page" console_codes do Linux (man console_codes), que dá detalhes sobre as diversas seqüências de escape disponíveis nos terminais modo-texto do Linux.
Outra fonte fundamental foi a experimentação. Para cada seqüência da documentação, escrevia alguns programas de teste para rodar no console Linux e na ROTE para comparar o funcionamento, pois muitos detalhes não estão documentados. Por exemplo, em seqüências cuja documentação indica "apagar o restante da linha", não está claro se o caractere imediatamente sob o cursor deve ou não ser apagado. Este é só um exemplo da ambigüidade da documentação que só pode ser resolvida com testes empíricos.
Uma das características mais notáveis dos ambientes UNIX é a diversidade. Quero dizer com isso que não há, como em outros sistemas operacionais, um programa "certo" para cada tarefa; ao invés disso, em geral há uma grande quantidade de emuladores de terminal diferentes, uma grande quantidade de editores de texto diferentes, e para tudo mais em geral o usuário tem uma ampla gama de escolha.
No UNIX esse conceito é tão natural que torna-se impossível obrigar o usuário a fazer algo de uma certa maneira específica sem que haja uma razão muito forte para isso. Por exemplo, um programa que forçasse um fanático pelo Emacs a usar o vi ou fizesse o contrário ia ser altamente impopular com pelo menos grande parte dos usuários. Comparativamente, no mundo do Microsoft ® Windows ™, vários programas embutem dentro de si editores de texto que são 0% configuráveis, e o usuário não tem escolha alguma. Não pode por exemplo perguntar-se "Mas não posso utilizar o WordPad ao invés disso?"
Ou seja, no UNIX a flexibilidade é um direito fundamental do usuário. Todo programa deve ser flexível se deseja ter sucesso.
Isto é uma filosofia importante, porque muitas vezes durante o desenvolvimento da ROTE e do Omnitty, ocorreu-me a idéia de que o desenvolvimento ficaria extremamente mais fácil se eu restringisse o escopo de utilização, impondo por exemplo que o usuário só rodasse o Omnitty no terminal modo-texto do Linux, ou só no xterm, etc.
Num programa para outro sistema operacional, provavelmente a decisão de restringir o escopo para facilitar o desenvolvimento seria muito razoável, e até poderia ser tomada sem nenhum remorso. Felizmente, no UNIX a pressão da comunidade é forte o suficiente para incentivar o desenvolvedor a optar por trabalhar mais e obter soluções melhores. Daí portanto o considerável trabalho para fazer com que o Omnitty e a ROTE funcionassem corretamente em vários emuladores de terminal.
Um aspecto que ajudou bastante o desenvolvimento foi que durante ele eu fui um dos administradores da Rede Linux do IME-USP, que funcionava portanto como um campo de testes para o Omnitty. Cada novo feature ou correção de bugs era testado num ambiente real de utilização, com várias dezenas de máquinas para administrar.
O ambiente da Administração também proporcionou uma interação muito dinâmica com os usuários do programa (os demais administradores), que tiveram um papel muito importante em sugerir features, apontar bugs e em geral dar um feedback bastante proveitoso.
Essas matérias ensinaram-me a programar de uma maneira organizada. Antes de começar o curso já sabia programar, no sentido de que sabia juntar as instruções e fazer o programa rodar. Mas foi somente com a disciplina MAC122 que comecei a ter uma maior noção sobre a organização dos programas e algoritmos.
Uma das disciplinas mais naturais para o desenvolvimento desse projeto, pois nela aprendi muitos detalhes sobre o modo do funcionamento do UNIX em geral e aspectos da administração de redes Unix. O fato é que a maior parte do programa já estava desenvolvida quando cursei esta matéria, mas ela ajudou bastante a compreender melhor os conceitos utilizados e muitas vezes a refatorar certas partes do programa para que fizessem mais sentido.
Toda a biblioteca ROTE é fundamentalmente um autômato, pois para a interpretação de seqüências de escape e outros caracteres foi necessário definir estados em que o terminal poderia estar, como mudaria de um estado para o outro com base na entrada, em que estado terminaria, etc.
Naturalmente isto poderia ter sido implementado de uma maneira ad-hoc, mas considerando que a complexidade da emulação de terminal já era dificuldade suficiente, implementá-la sem nenhum fundamento mais formal poderia tornar-se bastante problemático.
O problema de implementações ad-hoc para esse tipo de coisa é que geralmente elas levam em conta que toda a entrada já está disponível para consulta a qualquer momento. Mas isto não acontece na ROTE, pois os dados do pseudo-terminal vêm em pedaços, seqüências de bytes, que precisam ser interpretadas sem consultar o restante que ainda não chegou. Por isso, foi necessária alguma disciplina para determinar quais elementos seriam interpretados na hora, quais elementos da entrada fariam o terminal mudar de estado para que posteriormente, quando o restante da entrada chegasse, a interpretação pudesse ser continuada da onde havia parado sem prejuízo do significado.
Esta matéria ensinou-me conceitos importantes de concorrência em programas que utilizam múltiplas threads. O Omnitty não utiliza múltiplas threads, mas faz duas tarefas que são muito semelhantes: (a) utiliza multiplexação de input/output para dar a ilusão de multitarefa, rodando procedimentos no background enquanto ao mesmo tempo interagindo com o usuário; e (b) utiliza tratamento de sinal, que ocorre de forma assíncrona (e portanto, de uma certa forma, concorrente).
Em particular, os conceitos e a prática aprendida na disciplina de programação concorrente ajudou bastante a evitar a introdução de race conditions no código de tratamento de sinal e no código que interage com ele. Como no UNIX o sinal pode ser entregue a qualquer momento e a rotina de tratamento pode ser executada assincronamente, isto quase que equivale a programar utilizando múltiplas threads, e muitos dos mesmos conceitos se aplicam.
Nesta matéria aprendemos em profundidade o funcionamento do UNIX em específico, e sistemas operacionais em geral.
Uma das coisas importantes ensinadas por esta matéria foi o mecanismo de alocação da CPU entre diversos processos, e que processos que bloqueiam liberam a CPU para ser utilizada por outros processos.
Isto foi importante nas decisões de design do Omnitty como por exemplo determinar o tempo de timeout para ler uma tecla do teclado, pois durante a leitura o programa bloqueia e cede CPU para outros programas, tornando-o mais "leve". Outros aspectos importantes relacionados a sistemas operacionais foram as chamadas fork, wait, select, que, juntas, permitem ao programa criar processos filhos, receber notificações de processos filhos e fazer a multiplexação da entrada de forma a conseguir processar dados vindos de múltiplas fontes.
Acredito que uma das principais experiências desse projeto foi interagir com a comunidade do software livre e aprender um pouco mais sobre como ela funciona. Afinal, disponibilizando ambos os projetos, recebi inúmeras opiniões e contribuições.
Foi bastante interessante ver como a colaboração entre desenvolvedores de software livre, coisa que há poucos anos atrás não acreditava ser algo tão dinâmico, consegue efetivamente contribuir para a produção de software de qualidade.