Pontos Principais
-
O processo de desenvolvimento de software é de difícil compreensão devido à complexidade;
-
A complexidade leva a muitas crenças e mitos infundados de origem duvidosa;
-
Pesquisas recentes sobre o processo de desenvolvimento desafiam muitas das crenças ditas como verdades;
-
Alguns resultados da pesquisa não são intuitivos, revelando forças inesperadas no processo;
- No desenvolvimento de software, os fatores não relacionados a tecnicidade geralmente superam a parte técnica.
Recentemente, encontrei uma pesquisa que relaciona a linguagem de programação usada em um projeto e a qualidade do código produzida. Fiquei intrigado porque os resultados foram contrários ao que esperava. Por um lado, o estudo pode apresentar falhas, por outro lado, muitas práticas e mitos estabelecidos no desenvolvimento de software são duvidosos. Nós as adaptamos porque "todo mundo" usa ou são consideradas as melhores práticas, ou são usadas por pessoas influentes (o próprio termo já deveria ser um sinal de alerta). Elas realmente funcionam ou são lendas urbanas? E se olharmos para os dados concretos? Verifiquei alguns outros artigos, e em todos os casos os resultados foram surpreendentes.
Levando em consideração a importância dos sistemas de software em nossa economia, é surpreendente como são escassas as pesquisas científicas sobre o processo de desenvolvimento. Um dos motivos pode ser o fato do processo de desenvolvimento de software ser muito caro, impedindo que os pesquisadores entrem nas empresas, o que inviabiliza os experimentos em projetos reais. Recentemente, os repositórios de código públicos, como GitHub ou GitLab, mudaram essa realidade, fornecendo dados acessíveis, fazendo com que mais pesquisadores se aprofundem nestes dados.
Um dos primeiros estudos baseados em dados de repositórios públicos, intitulado A large ecosystem study to understand the effect of programming languages on code quality (Um grande estudo do ecossistema para entender o efeito das linguagens de programação na qualidade do código, tradução literal para o português), foi publicado em 2016. A pesquisa tentou validar a crença quase onipresente e dada como certa, de que algumas linguagens de programação produzem um código de maior qualidade em comparação a outras. Os pesquisadores estavam procurando uma correlação entre uma linguagem de programação e a quantidade e os tipos de defeitos. A análise de commits relacionados a bugs em 729 projetos do GitHub, desenvolvidos em 17 linguagens diferentes, mostrou uma correlação esperada. Era notável que linguagens como TypeScript, Clojure, Haskell, Ruby e Scala estavam menos sujeitas a terem erros, quando comparadas com C, C++, Objective-C, JavaScript, PHP e Python.
No geral, as linguagens funcionais e estaticamente tipadas eram menos sujeitas a erros do que as linguagens mais dinâmicas, de script ou procedurais. Curiosamente, os tipos de defeitos têm maior relação com a linguagem do que o número absoluto deles. De maneira geral, os resultados não foram surpreendentes, confirmando o que a maioria da comunidade acreditava ser verdade. O estudo ganhou popularidade e foi amplamente citado. Porém, existe um ponto a ser analisado: Os resultados foram estatísticos e, ao interpretá-los, devemos tomar cuidado. A significância estatística nem sempre acarreta significância prática e, como os autores corretamente alertam, correlação não é causalidade. Os resultados do estudo não implicam (embora muitos leitores tenham interpretado dessa forma) que se alterarmos um código C para Haskell, teremos menos bugs no código. De qualquer forma, o artigo ao menos forneceu argumentos baseados em dados.
Mas isso não é o fim da história. Como um dos pilares do método científico é a replicação, uma equipe de pesquisadores tentou refazer o estudo de 2016. O resultado, após corrigir algumas lacunas metodológicas encontradas no artigo original, foi publicado em 2019 no artigo On the Impact of Programming Languages on Code Quality A Reproduction Study (O impacto das linguagens de programação na qualidade do código; Um estudo de reprodução. Tradução literal para o português).
A replicação esteve longe de ser bem-sucedida, a maioria das reclamações do artigo original não foi reproduzida. Embora algumas correlações ainda fossem estatisticamente significativas, não eram significativas do ponto de vista prático. Em outras palavras, se olharmos para os dados, parece que a linguagem de programação que escolhemos, não diz respeito nada a quantidade de bugs. Não está convencido? Vamos ver outro artigo.
Um artigo publicado em 2019, intitulado Understanding Real-World Concurrency Bugs in Go (Compreendendo bugs de simultaneidade do mundo real usando o Go, tradução para o português), dá ênfase aos bugs de concorrência em projetos desenvolvidos em Go, uma linguagem de programação moderna desenvolvida pelo Google. Ela foi especialmente projetada para tornar a programação simultânea mais fácil e menos sujeita a erros. Embora o Go defenda o uso da simultaneidade de passagem de mensagem como uma abordagem que reduz a possibilidade de erros, ela fornece mecanismos para ambas implementações: Passagem de mensagem e sincronização por memória compartilhada, sendo portanto, uma escolha natural a utilização das duas abordagens. Os pesquisadores analisaram bugs de simultaneidade encontrados em seis projetos open source populares escritos em Go, incluindo Docker, Kubernetes e gRPC. Os resultados confundiram até os autores:
"Surpreendentemente, o estudo mostra que é tão fácil criar bugs de simultaneidade com passagem de mensagens quanto com memória compartilhada, às vezes até mais".
Embora os estudos que vimos até o momento indicam que os avanços na linguagem de programação têm pouca relação com os defeitos do código, pode haver outra explicação.
Vamos dar uma olhada em outra pesquisa, o clássico experimento do Táxi de Munique conduzido no início dos anos 1980. Embora a pesquisa não esteja relacionada à TI, mas à segurança no trânsito, os pesquisadores encontraram resultados não intuitivos semelhantes. Na década de 1980, os fabricantes de automóveis alemães começaram a instalar o primeiro sistema de freio ABS nos automóveis. Como o ABS torna o carro mais estável durante a frenagem, é natural que melhore a segurança na estrada. Os pesquisadores queriam saber o quanto era essa melhora. Operacionalizando em conjunto com uma empresa de táxi que planejava instalar o ABS em parte da frota, 3000 carros foram selecionados sendo metade com ABS e a outra metade sem, escolhidos aleatoriamente. Os pesquisadores observaram os carros por 3 anos. Em seguida, compararam as taxas de acidentes no grupo de carros com e sem ABS. O resultado foi no mínimo surpreendente, praticamente não houve diferença, inclusive os carros com ABS tinham um pouco mais de probabilidade de se envolverem em um acidente.
Como no caso da pesquisa sobre taxa de bugs e bugs de simultaneidade no Go, em tese deveria haver uma diferença, mas os dados mostram o contrário. No experimento do ABS, os investigadores coletaram alguns dados adicionais. Em primeiro lugar, os carros eram equipados com uma espécie de caixa preta coletando informações como velocidade e aceleração. Em segundo lugar, foram designados observadores para ficarem juntamente com os motoristas, com o objetivo de anotarem o comportamento na estrada. A imagem dos dados era clara. Com o ABS instalado nos carros, os motoristas mudaram o comportamento na estrada. Percebendo que agora teriam mais controle do carro e seria necessário uma distância menor para frenagem, os motoristas começaram a dirigir mais rápido e de maneira mais perigosa, fazendo curvas mais fechadas, com o carro por muitas vezes perdendo a aderência na parte traseira.
A explicação desse fenômeno é baseada no conceito de risk-taking (ou, risco alvo, no português) da psicologia, as pessoas se comportam de forma que o risco geral, denominado risco-alvo, esteja em um nível constante. Quando as circunstâncias mudam, as pessoas adaptam o comportamento para que o nível de risco seja constante. Instalar o ABS nos carros diminui o risco de dirigir, porém, faz com que os motoristas compensem essa mudança, passando a dirigir de forma mais agressiva. A compensação de risco semelhante foi encontrada em outras áreas também. As crianças correm mais riscos físicos ao praticar esportes com mecanismos de proteção, frascos de remédios com tampas à prova de crianças tornam os pais mais descuidados, cordas melhores nos pára-quedas são puxadas mais tarde.
Vamos voltar aos estudos sobre a qualidade de código. O que os pesquisadores estavam analisando? Commits no repositório. Quando o desenvolvedor faz o commit no código? Quando tem a certeza de que a qualidade é aceitável. Em outras palavras, o risco do código ter erros no commit está em um nível razoável. O que acontece quando o desenvolvedor muda para uma linguagem que está menos sujeita a erros? Rapidamente perceberá que agora pode escrever menos testes, gastar menos tempo revisando o código, pulando algumas verificações de qualidade, mantendo o mesmo risco de comprometer código com um commit de baixa qualidade. Como no caso dos motoristas de carros com ABS, eles adaptaram o comportamento à nova situação, para que o risco alvo seja o mesmo de antes. Cada desenvolvedor tem um padrão interno de qualidade de código e de risco alvo que possa comprometer o código abaixo desse padrão. Observe que o risco alvo e o padrão variam entre os desenvolvedores, mas os estudos sugerem que, na média, eles são os mesmos entre desenvolvedores de linguagens diferentes.
A questão é: Existem outras técnicas estabelecidas para melhorar a qualidade do código? Procurei artigos sobre duas técnicas: Programação em pares e, revisão de código. Ambas funcionam como é as pessoas dizem? Bem, sim e não, parece que a situação é um pouco mais complicada. Em ambos os casos, existem vários estudos examinando a eficácia da abordagem.
Vejamos a meta-análise de experimentos de programação em pares descrita no artigo The effectiveness of pair programming: A meta-analysis (A eficácia da programação em pares: uma meta-análise. Tradução para o português). Existe melhora na qualidade do código? "A análise mostra um pequeno efeito positivo na qualidade da programação em pares". Um pequeno efeito positivo talvez seja um pouco decepcionante, mas isso não é o fim da história.
"Uma análise mais detalhada das evidências sugere que a programação em pares é mais rápida do que a programação solo quando a complexidade da tarefa de programação é baixa e produz soluções de código de qualidade superior quando a complexidade da tarefa é alta. A qualidade superior para tarefas complexas tem um custo de esforço consideravelmente maior, enquanto o tempo de conclusão reduzido para as tarefas mais simples tem um preço de qualidade notoriamente inferior."
No caso da revisão de código, os resultados das pesquisas foram, de maneira geral, mais consistentes, mas os principais benefícios não são como era esperado, na área de detecção precoce de defeitos. Como autores do estudo sobre práticas de revisão de código na Microsoft Expectations, Outcomes, and Challenges of Modern Code Review (Expectativas, Resultados e Desafios da Revisão do Código Moderno, no português) concluíram:
"Nosso estudo revela que, embora a descoberta de defeitos continue sendo a principal motivação para a revisão, elas produziram menos resultados sobre os defeitos do que o esperado, porém, forneceram benefícios adicionais, como transferência de conhecimento, maior conscientização da equipe e criação de soluções alternativas para os problemas."
O questionamento natural é: Qual é o motivo da discrepância entre os resultados da pesquisa científica e as crenças comuns na comunidade? Um dos motivos pode ser a divisão entre a academia e os profissionais, de modo que os resultados dos estudos encontram um caminho difícil para os desenvolvedores, mas isso é apenas parte da história.
Em meados dos anos 1980, Fred Brooks publicou o famoso artigo "No Silver Bullet - Essence and Accident in Software Engineering" (Sem Bala de Prata - A essência e o acidente na engenharia de software, em português). Na introdução, o escritor compara o projeto de software a um lobisomem.
"O projeto de software tem familiaridade com algo do tipo (pelo menos sob o ponto de vista do gerente sem background técnico), geralmente inocente e franco, mas capaz de se tornar um monstro de cronogramas perdidos, orçamentos estourados e produtos defeituosos. Então, ouvimos gritos desesperados por uma bala de prata, algo que possa fazer os custos de software caírem tão rapidamente quanto os custos de hardware caem".
Ele argumenta que não existem saída fácil (que ele remonta como sendo as balas de prata) no desenvolvimento de software devido à sua própria natureza. É um esforço inerentemente complexo. Na década de 1980, a maioria dos softwares rodava em uma única máquina com um único processador de um núcleo, a internet estava no começo, os smartphones estavam em um futuro distante e ninguém ouvia falar em virtualização ou cloud. Brooks estava focando na complexidade técnica e agora estamos mais cientes dela nos processos sociais, psicológicos e de negócios envolvidos no desenvolvimento de software.
Essa complexidade também aumentou substancialmente desde a publicação de seu livro. As equipes de desenvolvimento são maiores, geralmente distribuídas e multiculturais, os sistemas de software estão muito mais emaranhados com o tecido empresarial e social. Apesar de todo o progresso, o desenvolvimento de software ainda é extremamente complexo, porém, em vários momentos beira do caos. Devemos enfrentar requisitos em constante mudança, complexidade técnica crescente e circuitos confusos de feedback não-lineares criados por forças técnicas, comerciais e sociais todas emboladas entre si. A fiação natural de nossos cérebros é muito pobre em descobrir o que está acontecendo em tal ambiente. Não é de surpreender que a comunidade de TI esteja infestada de exageros, mitos e guerras religiosas. Queremos desesperadamente orientar toda a equipe, para que nossos cérebros façam aquilo que foram feitos para fazer: Encontrar padrões.
Às vezes, são tão bons que vemos canais na superfície de Marte, faces em pontos aleatórios e técnicas infalíveis na roleta do cassino. Ao começarmos a acreditar em algo, ficamos literalmente viciados nisso, cada confirmação de nossa crença nos dá uma injeção de dopamina. Começamos a proteger as crenças, e como resultado, nos fechamos em nosso mundo, escolhendo conferências, livros e mídia que afirmam aquilo que já sabemos. Com o tempo, as crenças se solidificam em um dogma que quase ninguém ousa desafiar.
Mesmo com o método científico que nos permite lidar com a complexidade e com os preconceitos de uma forma mais racional, pode ser muito difícil prever o resultado de uma ação em processos complexos como o desenvolvimento de software. Alteramos a linguagem de programação para melhor e a qualidade do código não muda, introduzimos a programação em pares ou a revisão do código para melhorar a qualidade do código e experimentamos qualidade inferior ou obtemos benefícios em áreas inesperadas. Mas também há um lado bom em toda essa complexidade. Podemos encontrar pontos de alavancagem inesperados. Se queremos melhorar a qualidade do código, ao invés de buscar soluções técnicas, como uma nova linguagem de programação ou melhores ferramentas, podemos nos concentrar em melhorar a cultura de desenvolvimento, elevando os padrões de qualidade ou fazendo os commits com bugs serem mais arriscados.
Olhar dessa perspectiva pode dar luz sobre algumas oportunidades que não sao tão óbvias. Por exemplo, se uma equipe apresenta revisões de código, isso torna-o produzido por um desenvolvedor mais visível para outros membros da equipe e, portanto, aumenta o risco que podemos tomar na produção de código de baixa qualidade. Consequentemente, a revisão de código deve ter o efeito de aumentar a qualidade do código que sofreu commit, não apenas encontrando bugs ou violações no padrão criado pelos revisores (o que as pesquisas citadas acima estavam procurando), mas também evitando que os desenvolvedores produzam bugs. Em outras palavras, para elevar a qualidade do código bastaria convencer os desenvolvedores de que o código está sendo revisado, mesmo que ninguém esteja fazendo isso.
A moral dos estudos mora também no fato de que os fatores tecnológicos não podem ser separados dos psicológicos e culturais. Como em muitas outras áreas, pesquisas baseadas em dados mostram que o mundo não funciona da maneira como acreditamos. Para verificar o quanto a crença corresponde à realidade, não temos que esperar que os pesquisadores desenvolvam estudos de longo prazo. Algum tempo atrás, tivemos uma disputa emocional sobre algum assunto com muitos argumentos de ambos os lados. Depois de cerca de meia hora, alguém disse: "Vamos verificar na internet e resolvemos o impasse em 30 segundos". O pensamento científico e alguma dose de ceticismo não são reservados aos cientistas, às vezes basta uma verificação rápida na internet, ou algumas vezes precisamos coletar e analisar dados, mas em muitos casos não estamos falando de ciência de foguetes, mas sim em como introduzir mais racionalidade nas práticas de desenvolvimento de software, que é um tópico amplo que talvez valha outro artigo.
Sobre o Autor
Jacek Sokulski tem mais de 20 anos de experiência em desenvolvimento de software. Atualmente trabalha na DOT Systems como arquiteto de software. Seu interesse abrange desde sistemas distribuídos e arquitetura de software até sistemas complexos, IA, psicologia e filosofia. Tem PhD em Matemática e é pós-graduado em Psicologia.