Novo Blog

Novo endereço

https://blog.nilo.pro.br

domingo, 11 de julho de 2010

Como não desenvolver software

Eu nunca vi dois programadores desenvolverem o mesmo programa da mesma forma. Dizer o que é certo em desenvolvimento de software é muito difícil, tudo depende de quais correntes de pensamento você segue. Orientação a objetos, script-maníacos, programação funcional? Tem espaço para todo mundo e ninguém escapa em maior ou menor grau de vários sintomas aqui descritos.

Algumas coisas são tão ruins que mesmo em um assunto tão "quente" como desenvolvimento de software, pode-se achar um consenso. Eu vou relatar algumas das más práticas que conheci, pois acredito que elas são bastante comuns, especialmente com as novas tecnologias, Internet e código open source. Quanto maior o time de desenvolvimento, mais frequentes ficam esse pequenos problemas. O intuito deste post é relatar algumas experiências com o mesmo rigor científico de uma conversa de bar. Todas as afirmações são relativas e dependem do humor de quem as lê. Tudo deve ser encarado com várias pitadas de sal e bom humor. Eu avisei :-D
  • Over-engineering: quem nunca viu um pequeno exemplo com 1000 linhas de código não sabe do que estou falando... era para ser um "Hello World"... 1000 linhas depois ninguém sabia o que era. Um programa de computador precisa ser razoavelmente robusto a falhas, mas existe uma grande diferença entre uma aplicação crítica e uma aplicação simples, que pode simplesmente parar de funcionar e dar uma mensagem de erro! O sintoma se apresenta quando a confiabilidade do Sol começa a ser posta em jogo. E a pergunta cruel surge: quando o Sol explodir, o que faremos? Variações desta realidade são exigências como confiabilidade e disponibilidade de sistemas web comparáveis às do Facebook ou do Google, exceto que você ainda não tem o tráfego nem o dinheiro destes sites. Variações incluem o desejo incotrolável de utilizar o maior número de patterns no maior número de classes possível. Nada pode ser feito em apenas uma classe. Classes ficam melhor em dúzias. Porque deixar para um futuro refactoring o que já pode ser feito hoje?
  • Paralisia por análise: quando os programadores se lançam na quest máxima: a busca DO framework. Perdem-se às vezes meses com experimentos para simplesmente escolher um novo framework, ignorando toda a experiência anterior do time, ou que o tempo perdido não volta mais. Em informática é muito difícil escapar desta situação, principalmente com software open source, constantemente atualizado e melhorado. Parar de trocar pode simplesmente significar a morte do programador, ou sua condenação ao castigo máximo, como programar para sempre em Clipper ou Cobol :-D A questão é que uma decisão deve ser tomada e normalmente nenhum framework oferece solução para tudo, ou torna tudo simples, fácil e claro. Algumas pessoas buscam soluções que "não dão trabalho", mas esquecem que o fruto desta automatização é normalmente seguir padrões e conceitos do novo framework, conceitos que demandam tempo para ser aprendidos. Uma mistura de paralisia por análise com over-engineering é quando o time resolve encontrar uma solução perfeitamente escalável, adaptável a qualquer cliente e que possa rodar por uns 10 anos com pouca manutenção... ou seja, buscam alcançar o nirvana da programação. Isso não é desculpa para não analisar as melhores alternativas para resolver o problema, mas esta análise não deve durar mais tempo que o desenvolvimeno da solução em si.
  • Bala de prata: é quando o time decide resolver tudo com a mesma tecnologia sempre. Normalmente com C ou Java, mas começam a aparecer mais e mais casos com linguagens scripts. A verdade é que não existe bala de prata (no sentido de solução universal), nem lobizomem. Toda solução deve ser cuidadosamente analisada, dentro de um prazo bem definido, para não cair no problema da paralisia. Já se dizia há muito tempo: "Se a única ferramenta que você conhece é um martelo, tudo vira prego". 
  • Time Bom Bril: é o sonho de todo gerente de software. Um time multiplataforma, multilinguagem, com profissionais altamente inteligentes, simpáticos e dóceis. Entre outras características deste tipo de alucinação é usar "O time" em todo e qualquer tipo projeto, ignorando o conhecimento de domínio. Hospital, eleições, lançamento de foguetes, hardware? Não tem problema, "O time" resolve. Além deste tipo de patia causar danos ao pobres membros do time, a perda de produtividade é enorme, causando a aparição do folclórico Jack-of-all-trades-master-of-none. Ninguém consegue dominar tudo e alcançar resultados excelentes em tudo, isso é ilusão. Profissionais altamente capazes podem aprender muito rápido e superar o resultado de times ordinários, mas isso tem um preço. A pulverização de esforços e a constante pressão podem queimar um time de desenvolvimento muito rápidamente.
  • Cada um por si e ninguém por todos: muitos chefes atrapalham, mas quando não há algum chefe, todos são chefes! Essa é fácil de se cair quando se tem um time super star ou um dream team em determinada tecnologia. Como todos são profissionais de alto valor e experientes, independência total é dada a cada membro do time. No final, ninguém se entende, pois cada membro do tal time resolveu utilizar as práticas e conceitos da corrente de desenvolvimento que mais lhe agrada. No fim, a empresa fica com uma salada de códigos-fonte, provavelmente escritos em várias linguagens de programação, frameworks diferentes, etc. Um pouco de ordem ajuda a manter a sanidade do time.
  • Commits preciosos: alguns times são intruídos a só "comitarem" suas alterações após finalizarem sua tarefa de implementação. Isso funciona bem e evita que código inacabado perturbe o desenvolvimento de outros desenvolvedores devido a anomalias no build. O problema é que algumas pessoas não dividem as tarefas em espaços razoáveis de tempo. O sintoma são commits quinzenais ou mesmo mensais! Indagados sobre a prática, alguns podem responder que fazem isso para não poluir o repositório com commits pequenos. A questão toda é que o próprio desenvolvedor precisa de um sistema de controle de versão para seu desenvolvimento inacabado. Se um sistema de controle de versão distribuído, como o GIT ou Mercurial é usado, pode-se resolver este tipo de problema com branches locais. Com SVN não tem muito jeito... você tem que comitar após testar e garantir que o que foi commitado não quebra o build. Se algo estiver errado, o sistema de controle de versão tem ferramentas para corrigir estas falhas. Além disso, fazer o merge após um mês de desenvolvimento pode ser muito trabalhoso, pois a probabilidade de se ter alterado o mesmo arquivo que outras pessoas é alta, gerando conflitos.
  • Repo killer: quando um membro decide comitar e atualizar apenas partes do repositório, privando os outros membros do time de trabalharem com a mesma versão do código. Essa é inexplicável. Uma variação é o desenvolvedor que escolhe a dedo o que atualizar, esquecendo de incluir novos arquivos e mudanças que nem ele mesmo lembra que fez. Esse problema é uma bomba relógio, pois as diferenças entre os repositórios será evidente cedo ou tarde.
  • Builds locais: poupar o dinheiro de uma máquina de build pode ser a pior economia a fazer em um time de software. Se apenas uma pessoa desenvolve o código, você só saberá que tudo foi comitado ou não no dia que ela sair da empresa. Aí começa a caça aos arquivos perdidos. Outro grande problema é a não documentação do ambiente de desenvolvimento, mas esse problema aparece mais rápido, normalmente quando o HD do desenvolvedor pifa e ele passa uma semana para lembrar tudo que usava (IDE's, bibliotecas, paths, etc). Esse problema também expõe um mais grave: a falta de testes.
  • Ausência de testes: uma das causas de se perder um usuário de seu sistema é transformá-lo em testador. Para isso, você não desenvolve testes automatizados e normalmente não realiza qualquer controle de releases. Isso gera inúmeros problemas durante todo ciclo de desenvolvimento e o único teste acaba sendo feito pelo compilador. Um código que compila está longe de estar livre de erros. Não controlar os erros já reportados pelo usuário é grave. Não corrigi-los é pior e ressuscitar bugs antigos por falta de testes é imperdoável.
  • Zipmania: desenvolvedores param de usar o sistema de controle de versão e começam a trocar Zips com nomes poderosos por IM: sistema20100610_XPO.zip. O vetor de transmissão é um desenvolvedor suicida que resolve fazer a integração na máquina de outro desenvolvedor, antes de fazer os devidos commits, para "evitar conflitos". Fica melhor ainda quando eles começam a criar zips para "congelar" um release e fazer "backup".
  • Variáveis com apenas dois ou três caracteres: essa é muito boa. Um dia você será castigado, tendo que rastrear código em C que usa strtok e variáveis como z, zz e zzz! Fica mais interessante quando esse código é responsável por crashes imprevisíveis no código, do tipo null pointer/segfaults :-D. O pior é que o carrasco pode ter sido você mesmo! É sempre bom comentar o código, principalmente quando o método implementa algo mais complexo que 10 linhas. Mesmo que você não comente, o minimo que pode ser feito é utilizar nomes de variáveis que ainda signifiquem algo dentro de 6 meses ou 2 anos.
  • Gotos: código em C com goto ninguém merece. Tirando raríssimas exceções, usar goto's em C é imperdoável. Fica ainda pior quando os "labels" tem sempre o mesmo nome, em várias funções e o tal goto é escondido por macros. Eu aprendi programar nos anos 80, gotos eram a única saída em Basic, mas já eram abominados em C e Pascal há bastante tempo e mesmo antes (Dijkstra, 1968). Para programadores modernos, um goto é simplesmente um atentado a programação estruturada.
  • A praga do log: aqui os sintomas são diferentes dependendo da linguagem. Em C/C++ você pode usar o pré-processador para esconder seu log de DEBUG ou fazer a coisa certa e usar um framework descente. Em Java, você tem os loggers da vida. O pior que você pode fazer é usar múltiplos System.outs ou printfs para fazer o log na mão. A praga do log é que mesmo usando um framework, você acaba espalhando linhas de log para tudo e usando apenas dois ou três níveis de log. No final, você tem arquivos de log gigantescos que chegam mesmo a retardar a execução do programa. Eu já procurei causas de lentidão em sistemas Java para descobrir que tínhamos deixado o log das bibliotecas em C ligado! Simplesmente, cada linha do log abria, escrevia, dava um flush e fechava o arquivo na mesma linha, claro, escondendo tudo isso numa macro! C hoje em dia é tão rápido que você pode fazer barbaridades, mas quando você loga deste jeito dentro de um XML parser... a coisa explode.
  • Ignorar mensagens de erro do compilador: essa aqui é um prelúdio para o sofrimento. Se no gcc seu código não compila com -Wall ou se você é o rei dos pragmas do Visual C++, lembre que se estas mensagens fossem realmente pouco importantes, elas não seriam geradas pelo compilador! No Java, usa-se o -Xlint para ter warnings mais precisos. O importante é que seu build seja limpo! Isso faz com que qualquer erro seja rapidamente notado. O mesmo para os logs. Se tudo é mostrado na tela, você simplesmente pára de olhar, pois se acostuma a ver lixo ou mensagens que não são interessantes.
  • Localização tabajara: ao trabalhar com código para múltiplas línguas, melhor não improvisar. Java resolve este tipo de problema muito bem e o Python na sua versão 3 melhorou bastante. Misturar strings em páginas de código diferentes é fatal! Fazer localização com ifs no código é pior ainda. Até a Microsoft tenta forçar o uso de UNICODE no Visual C++. O pior é ignorar diferenças de codificação e mesmo de tratamento de mensagens. Além da ordem das palavras, línguas diferentes tem critérios também diferentes para ordenação de caracteres, plural e letras maiúsculas, por exemplo. 
  • Build scripts escritos em Perl e batches: quem ainda trabalha com Perl merece todo castigo :-D O problema são pessoas que não utilizam Perl e que tentam fazer scripts de build em Perl. O mais legal é quando o build é multiplataforma é os tais scripts geram código em C ou C#, sem nada dizer. Para melhorar a festa, pode-se utilizar múltiplos scripts (multiplicados pelo padrão: copy and paste, rename). Agora a cereja do sorvete é fazer isso tudo usando Windows ou Cygwin.
  • Babel: por que utilizar apenas uma linguagem de programação? Seu projeto pode ter muitas, você pode até deixar seus programadores escolherem a linguagem de programação mais adaptada a cada tarefa e depois reunir isso tudo via SOAP. Usar Perl com qualquer outra coisa é trabalhar contra a empresa. C/C++ e Java, tudo bem, um pouco de Python ok. Mas se seu código é realmente escrito partes em Basic, partes em C/C++/C# e você resolveu colocar a parte concorrente em F# é porque você já vive o sonho do .Net. O mesmo acontece em Java: Jython ou JRuby, Groovy e Scala. Você pode misturar como quiser, mas a manutenção do código vai ser um pesadelo. Quanto mais linguagens você utilizar, mais difícil será encontrar alguém capaz de trabalhar com todas estas linguagens. Lembre-se que toda linguagem evolui, forçando um esforço arqueológico para entender o que a versão X fazia com a versão Z. Melhor evitar ou pedir aprovação do alto conselho Klingon antes de começar a construir sua torre. E por que apenas uma torre? Você pode desenvolver seu código para múltiplas plataformas, para adicionar um pouco de tempero. Mesmo com Java, faz-se milagre: J2SE, Java ME CDC e Java ME CLDC... a festa! Com C# você pode brincar com o compact framework e por que não com Mono?
  • Demos: demo é coisa do cão :-D Quem trabalha comigo ouve essa todo dia. O problema de "demos" que elas são normalmente escritas para rodar apenas uma vez e num curto espaço de tempo. O que leva ao próximo problema. Uma demo é diferente de um protótipo. Um protótipo normalmente é criado para resolver um problema específico ou para demonstrar como os analistas entenderam o problema, um instrumento para refinar e melhorar programas. Uma demo é normalmente criada para demonstrar o domínio de uma tecnologia, é um PPT executável e com código fonte! Normalmente elas resistem a aposentadoria e tem a tendência a evoluir. O ambiente propício para proliferação é a véspera de exposições e eventos.
  • Códigos escritos para rodarem apenas uma vez: não acredite nisso. Uma vez que um programa é escrito, o universo conspira para que ele seja mantido por muito tempo. Quase nunca você vai escrever código para rodar apenas uma vez. Se for o caso, tenha certeza de apagar o bicho logo depois da primeira execução, só para garantir.
  • XML Doom: alguém resolve que tudo deve ser escrito em XML. Eu já via até métodos que recebem os seus parâmetros no formato mágico que começa com X. O mais interessante do XML é que ele foi criado para facilitar o intercâmbio de informações e também ser legível tanto por computadores quanto por seres humanos. A ideia era poder ter um formato simples, baseado em UTF, ou pelo menos ciente dos inúmeros problemas de internacionalização e de representação de dados binários em formato texto. A coisa começou a desandar quando começamos a escrever arquivos XML na mão, para facilitar o parsing de arquivos de configuração. O XML que deveria ser gerado pelo computador, acabou sendo gerado manualmente. Alguns conseguem ter a sorte de ter editores de textos adaptados e mesmo ferramentas que ajudam na árdua tarefa de criação destes monstros. Uma ideia muito boa que acabou sendo abusada. XML não deve ser usado pra tudo.
  • Copy and Paste hell: essa é a pior de todas. Se você copia e cola o mesmo código várias vezes, algo está errado. Ou você não usa os recursos de template de seu IDE (aqueles que transformam syso em System.out.println()) ou você está realmente perdido. Grave mesmo é se você copia grandes partes de código, criando métodos gigantes. Programas criados com alta participação de copy and paste demonstram um problema grave de modularização e normalmente não isolam conceitos adequadamente. Lembre-se que cada código copiado e colado terá os mesmos problemas do código de origem, se você tiver sorte, fazendo com que alterações e modificações tenham que ser propagadas para todas as cópias. É realmente um desafio a sua memória. Sempre se esquece um ou dois... e lá estarão os problemas. Duro mesmo é corrigir o mesmo problema várias vezes. Além disso o tamanho do programa cresce assustadoramente rápido. Depois de ver código em javascript com 10000 linhas por arquivo você fica com medo desta síndrome.

Parece comédia, mas tudo isso é real. Profissão alguma é perfeita ou sem dificuldades, a diferença em informática é que nós criamos e retiramos os obstáculos no nosso caminho :-). Normalmente misturando novas soluções com antigos problemas numa velocidade fantástica.