Rodrigo Rosenfeld Rosas

Produtividade e Quantidade de Código

28/03/2010 10:45 (Atualizado em 28/03/2010 11:23)

Produtividade é basicamente a relação entre resultado útil e o tempo necessário para realizá-lo.

Frequentemente, eu leio artigos que clamam que a linguagem X torna o trabalho mais produtivo que a linguagem Y por n motivos, normalmente ilustrados com trechos de código para validar sua tese. A maior parte destes artigos foca no argumento que é possível escrever um mesmo resultado com menos linhas de código em sua linguagem preferida. Eu comentarei mais sobre os outros argumentos utilizados em outros artigos, mas focarei na relevância da quantidade de código escrito quando se fala em produtividade.

A maior parte dos textos que li sobre comparações entre linguagens/frameworks mostram como é possível escrever menos código em uma linguagem ou framework e concluem que a linguagem/framework X é mais produtiva que Y sem explicar a relação entre quantidade de código escrito e produtividade.

Frequentemente eu invisto muito mais tempo para escrever um código menor do que eu levaria para obter o mesmo resultado com mais código. Os motivos são gosto pessoal (gosto de ler soluções elegantes e código sintético) e, principalmente, preocupação com produtividade a longo prazo. Obviamente, a curto prazo, nesses casos, a produtividade é maior se eu escrever mais código, o que mostra que a produtividade é variável ao longo do tempo de vida de um projeto.

Quanto mais um projeto cresce, mais o tamanho e a consistência de seu código tornam-se mais relevante. Existem fatores mais relevantes para a produtividade de um sistema do que o tamanho do código em si: documentação, cobertura por testes automatizados, modularidade, comunicação eficiente entre desenvolvedores e clientes, para citar alguns. Isto não quer dizer que reduzir a quantidade de código não aumente a produtividade. De fato pode aumentar consideravelmente, mas é necessário explicar por quê.

Aqueles que já desenvolveram com a IDE Delphi lembrar-se-ão que muito código era adicionado e removido automaticamente pelo IDE e que estes códigos não influenciavam absolutamente na medição de produtividade. De fato, o IDE era o ponto forte do Delphi, não a linguagem. Era raro encontrar material sobre diversos tópicos nos tutoriais e livros disponíveis sobre o Delphi, incluindo: documentação, internacionalização, modularização, distribuição binária e integração com sistemas de controle de versão. Certamente havia material disponível sobre estes assuntos, mas pouquíssimos desenvolvedores em Delphi estudaram esses tópicos.

A maior parte sentia-se produtivo pela facilidade de criar um formulário, adicionar controles e botões e tratar rapidamente os diversos eventos disponíveis para cada controle. De fato, era raro achar uma alternativa que permitia iniciar um projeto Desktop com a mesma velocidade obtida com o Delphi. No entanto, mesmo antes do desenvolvimento para a Web ter-se tornado tão popular, o Delphi já não era muito utilizado em nível mundial. O Delphi foi um fenômeno no Brasil e ainda existem diversos sistemas em uso desenvolvidos em Delphi no país, mas o Brasil foi exceção neste caso.

Aqueles que já tiveram a oportunidade de trabalhar com este IDE lembrar-se-ão como era rápido começar um projeto mas muito complicado de controlar seu crescimento ou dar continuidade em um sistema desenvolvido por outros. Alguns arguirão que isto acontece em qualquer linguagem/IDE/framework, mas isto não é verdade. Eu contribuo com frequência para alguns projetos sem dificuldade em entender o código, mesmo sem a ajuda dos desenvolvedores principais.

Toda linguagem, framework ou IDE trazem consigo toda uma cultura comunitária. Cada comunidade costuma concordar com uma série de premissas. Em Delphi, a cultura geral da comunidade de usuários era entregar mais rápido em vez de com mais qualidade. Isto significava que vários códigos eram copiados de um tratamento de evento para outro em vez de escreverem um código mais modular. Não digo que todos os desenvolvedores pensavam da mesma forma, mas essa era a cultura geral entre os "Delphistas".

Ou seja, se o objetivo fosse desenvolver um projeto de vida curta, Delphi certamente seria uma excelente escolha caso fosse economicamente viável. Para projetos de vida longa, eu realmente não recomendaria esta abordagem para sistemas Desktop. Outra conclusão é que não se trata apenas do fato de uma tecnologia lhe permitir fazer isso ou aquilo (Delphi permitia modularização e diversas outras boas práticas) mas de como a comunidade que está por traz daquela tecnologia pensa. Na comunidade Ruby, por exemplo, o desenvolvimento orientado a testes automatizados é altamente recomendado e utilizado, com foco tanto em produtividade quanto em qualidade (de código e funcionamento), enquanto a preocupação com desempenho é adiada ao máximo.

Muito já se discutiu no passado sobre as vantagens ou desvantagens do Delphi em relação a outras tecnologias para desenvolvimento em Desktop. Atualmente, o foco está na comparação entre linguagens e frameworks para o desenvolvimento para web, com artigos comparando diversas linguagens (Java, C#, VBScript, Ruby, Python, Perl, C++), frameworks para web (.NET, Struts, JSF, Rails, Django, TurboGears) e frameworks Javascript (jQuery, Prototype, MooTools) para não citar outras tecnologias como Flex, Flash, Java applets, etc.

A maior parte das discussões gira em torno da polêmica Produtividade versus Escalabilidade. Como vocês podem perceber, o assunto é muito amplo e, por isso, resolvi focar apenas na quantidade de código envolvido, deixando os outros fatores para futuros artigos.

Escrever menos código aumenta a produtividade?

Depende, normalmente sim. As perguntas mais interessantes são "em que situações?" e "por que?". Alguns desenvolvedores só se consideram produtivos com o uso de IDEs. Neste caso, as IDEs são capazes de gerar bastante código, de modo que o programador escreva de fato pouco código. Uma linguagem em que esta situação se aplica é Java. Praticamente todo desenvolvedor Java utiliza um IDE, seja o Netbeans, Eclipse, IntelliJ ou qualquer outro. Eu nunca conheci um desenvolvedor Java que não use um IDE.

Deste exemplo é que vem minha primeira observação: a produtividade em um projeto está minimamente relacionada ao tempo gasto puramente escrevendo código. Isto porque boa parte de um código pode ser gerada por um IDE em menos de 1 segundo e porque o tempo que um desenvolvedor leva para escrever um código é desprezível quando comparado ao tempo em que ele leva pensando na implementação de funcionalidades ou correção de bugs.

Além disso, existem IDEs que não apenas geram códigos clichê (boilerplate), como construtores, Getters e Setters no Java ou C++, mas também esqueletos completos de aplicações funcionais baseados em diagramas UML, por exemplo. Neste caso, em um minuto é possível mostrar uma aplicação completa sendo desenvolvida com um determinado IDE. Certamente é possível dizer que tal IDE é super produtivo se o resultado final desejado for exatamente aquele gerado pelo IDE!

No entanto, na quase totalidade dos sistemas não é isso que acontece. Em sistemas grandes, relevantes para nós (caso contrário você não perderia tempo lendo este artigo imenso), o tempo útil de um projeto é de anos ou décadas. Isto significa que ao longo do projeto, a maior parte do tempo é utilizada com manutenção e evolução do código, além da integração de novos membros à equipe de desenvolvedores, enquanto os membros que iniciaram o projeto abandonam a empresa ou projeto em algum momento.

A maior parte dos programas geradores de código funcionam bem para criar uma aplicação do zero, mas não permitem ou dificultam o caminho inverso, que acontecerá na maior parte do tempo, durante a manutenção do sistema. Geralmente as apresentações dessas ferramentas focam na construção de um aplicativo a partir do nada, pois é justamente a área em que são produtivas, e assim conquistam a maior parte do público leigo ou de não programadores que se espantam de quão rápido uma ferramenta foi capaz de construir uma aplicação inteira.

E normalmente esse é o início de todos os males. Os primeiros desenvolvedores são taxados de produtivos por conseguirem desenvolver protótipos tão rapidamente e ganham prestígio entre os cargos gerenciais e possivelmente alguma promoção onde deixam de desenvolver para coordenar uma equipe de desenvolvedores. A partir desse momento, a ferramenta já não é mais útil pois a aplicação já existe e a maior parte do trabalho está relacionada à manutenção e continuação do programa, o que costuma ficar cada vez mais complicado visto que o código gerado nem sempre é modular ou fácil de entender e manter.

Ou seja, no estado inicial desses projetos, a produtividade será muito elevada, não importando a quantidade de código gerado. Por outro lado, após um ano de desenvolvimento, provavelmente o projeto estará em um nível em que é muito complicado de evoluir ou dar manutenção. Neste estágio, muitas vezes são disponibilizadas atualizações das ferramentas ou novas ferramentas que prometem mais funcionalidade. É tão complicado dar manutenção na base de código existente que diversas equipes reescrevem sistemas completos quando chegam neste ponto.

Assim, vários sistemas foram reescritos de Microsoft ASP para ASP.NET ou C#, vários foram migrados de Java (Struts, JSP, JSF, etc) para Rails e de Microsfot MFC para .NET, sem contar algumas aplicações Desktop que foram reescritas com um foco para a web. Em vários destes casos, a decisão de reescrever todo o sistema melhorou a produtividade geral do projeto, mas em grande parte houve apenas um reinício deste ciclo. Muitos gerentes de projeto justificam que este é um ciclo normal e calculam o tempo de vida de um projeto em 4 anos, quando muito.

Já ouvi algumas pessoas insinuando que Ruby é uma linguagem mais moderna e dinâmica que Java por ser mais nova. Que elas não podem ser comparadas porque Java é muito mais antiga e os recursos disponíveis na época em que foram criadas não permitem uma comparação justa. O fato é que não poderia ser mais justo visto que ambas as linguagens surgiram na mesma época, na verdade. Ou seja, uma linguagem, assim como um projeto, quando bem projetado e evoluído pode durar facilmente uma década, sem afetar sua manutenabilidade. Testes automatizados contribuem enormemente para o sucesso destes projetos.

Existem vários projetos de código aberto que continuam evoluindo após vários anos de existência. A comunidade de software livre costuma ter um padrão superior para a qualidade de código exigida, quando comparada às empresas em geral, especialmente porque a base de desenvolvedores envolvidos também costuma ser maior e por isso é muito importante que o código tenha qualidade, documentação, testes, suporte a internacionalização, entre outras qualidades. Vários deles passaram por refatoração de boa parte do código, ao longo do tempo, e alguns foram até mesmo completamente re-escritos, como a migração do KDE 3 para o KDE 4. Mas de um modo geral, a duração de um software livre costuma ser muito superior a de softwares particulares.

Quanto mais tempo dura um projeto, maior costuma ser a base de código e é justamente nesse momento em que a quantidade de código mais importa. A quantidade de código consome muito mais tempo de entendimento e mudança do que de criação do código propriamente. Quando o código é pequeno, modular, auto-contido e conciso, o tempo necessário para seu entendimento e evolução é consideravelmente menor.

Exemplificação

Para exemplificar, imagine que seja necessário implementar um algoritmo que dado n vetores de inteiros, pretenda-se filtrar os elementos da interseção destes vetores, gravando em um arquivo estes elementos separados por vírgula.

Seguem duas implementações, uma em Java e outra em Ruby:

Java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  /* suprimindo as declarações de package e imports gerados pelas IDEs e que não costumam ser
     lidas por desenvolvedores Java, bem como interfaces e extends normalmente utilizados em
     projetos Java típicos e incluindo as bibliotecas Apache Commons para suprir as deficiências
     da API padrão do Java. Se você acredita que é possível tornar este código menor, por favor
     deixe um comentário ou envie-me um e-mail */
  public class App {
    static void grava_intersecao(String nome_arquivo, List... vetores) {
      List intersecao = vetores[0];
      for (int i=1; i < vetores.length; i++) intersecao.retainAll(vetores[i]);
      try { FileUtils.writeStringToFile(new File(nome_arquivo), StringUtils.join(intersecao, ','));} catch (IOException e) {}
    }
    public static void main(String args[]) {
      List imparesDe1A20 = new ArrayList<Integer>();
      for (int i=1; i<=19; i+=2) imparesDe1A20.add(i);
      grava_intersecao("arquivo.txt", Arrays.asList(1,6,9), imparesDe1A20, Arrays.asList(9,87,56));
    }
  }

Ruby:

1
2
3
4
5
  def grava_intersecao nome_arquivo, *vetores
    File.open(nome_arquivo, 'w'){|f| f.write vetores.inject{|a,b| a & b}.join(',')}
  end

  grava_intersecao 'arquivo.txt', [1,6,9], (1..20).step(2).to_a, [9, 87, 56]

O tempo que eu levo para escrever ambas as soluções é muito pequeno, independentemente da quantidade de linhas de código (que seriam muito maiores normalmente, pois os desenvolvedores costumam usar a IDE para identar o código). No entanto, eu levei muito mais tempo para desenvolver a solução em Java pois tive que pesquisar diversas APIs... Comparando o número de caracteres úteis entre as duas implementações, o código em Ruby equivale a cerca de 40% do código em Java.

Qual destes códigos seria mais rapidamente compreendidos e seria mais fácil de manter por um novo desenvolvedor que estivesse sendo integrado a um projeto?

Tenha em mente que em um projeto grande este trecho seria apenas uma agulha em um palheiro. Portanto multiplique diversas vezes o tempo de entendimento destes trechos para ter uma idéia do tempo necessário para integrar um novo desenvolvedor a um projeto em uma linguagem mais expressiva quando comparado a um projeto desenvolvido em uma linguagem menos expressiva (leia-se: quantidade maior de código).

Curiosidade: embora não seja o foco deste artigo, deixe-me aproveitar para comentar um pouco sobre um tópico que merece um artigo à parte: inconsistência. O código em Java acima não funcionará por um detalhe sutil. Seria muito complicado para um desenvolvedor Java, mesmo um experiente, saber prontamente por que este código não funcionará. Eu mesmo só percebi quanto tentei executá-lo e precisei ler a documentação para entender. Mesmo com a documentação, ela não é clara o suficiente para tirar conclusões definitivas. Seria preciso testar de qualquer forma. Uma alteração simples faria com que o código acima funcionasse:

1
2
  /* a ordem dos parâmetros foi ligeiramente alterada */
  grava_intersecao("arquivo.txt", imparesDe1A20, Arrays.asList(1,6,9), Arrays.asList(9,87,56));

A documentação de retainAll avisa que é possível que seja gerada uma exceção UnsupportedOperationException se o método não for suportado pela lista. No entanto, a documentação de Arrays.asList não diz se a lista retornada suporta ou não esta operação. Também não achei menção ao suporte desta operação na documentação de ArrayList. Foi necessário testar em ambos os casos e, mesmo assim, como eu poderia ter certeza se funcionaria com outra implementação da linguagem Java do que a que estou usando?

Aproveito para comentar sobre outro pensamento comum que é incorreto: "em uma linguagem compilada tem-se mais garantia que um código irá funcionar quando o programa é compilado, comparado a um programa interpretado sem fase de compilação" ou "uma linguagem de tipagem estática tem menos chance de gerar uma exceção do que uma linguagem com tipagem dinâmica". Este exemplo mostra que mesmo que o código em Java compile, haverá um erro em tempo de execução dependendo do tipo de lista que será passada como primeiro parâmetro. Por isso é importante escrever testes automatizados, independentemente do tipo de linguagem. A melhor forma de corrigir o código Java acima provavelmente seria:

1
2
  //List intersecao = vetores[0];
  List intersecao = new ArrayList(vetores[0]);

Voltando ao tópico principal, quando se diz que uma linguagem permite escrever um mesmo sistema com menos código, significa que a linguagem é mais expressiva, e que é mais fácil entender e evoluir um código em uma linguagem mais expressiva. Concluindo: a quantidade de código escrito pouco afeta a produtividade com relação ao tempo gasto para escrever o código quando comparado ao enorme ganho no tempo necessário para entendê-lo, mantê-lo e evoluí-lo.

comentários gerenciados por Disqus