Manutenção de Software
1 Introdução 🔗
Este capítulo começa com uma apresentação sobre a importância do tema do livro, aproveitando para caracterizar os principais tipos de Manutenção de Software (Seção 1.1). Em seguida, enfatizamos a relevância da área de Compreensão de Código, já que todo programa precisa ser devidamente entendido antes de ser mantido (Seção 1.2). Na sequência, apresentamos um breve histórico da área, abordando tópicos como as Leis de Evolução de Software ou Leis de Lehman (Seção 1.3). Depois, definimos o conceito de manutenibilidade e destacamos sua importância (Seção 1.4). Ainda neste primeiro capítulo, discutimos o papel de Modelos de Linguagem (LLMs) em tarefas de Compreensão, Manutenção e Evolução de Software (Seção 1.5). Por fim, apresentamos o conteúdo dos capítulos seguintes (Seção 1.6), para que o leitor tenha uma visão geral do que será estudado.
1.1 Tipos de Manutenção de Software 🔗
A Computação possui diversas áreas, como Engenharia de Software, Bancos de Dados, Inteligência Artificial, Computação Gráfica, entre outras. Por sua vez, essas áreas contam com subáreas. No caso específico de Engenharia de Software, existe um documento chamado Software Engineering Body of Knowledge (SWEBOK, link) que define suas principais subáreas. Esse documento é elaborado por cientistas e profissionais convidados por uma organização científica chamada IEEE Computer Society. A versão mais recente do SWEBOK foi lançada em 2024 e, sem surpresa, Manutenção de Software aparece como uma das subáreas da Engenharia de Software, ao lado de Processos, Requisitos, Design, Arquitetura, Testes, entre outras.
Na verdade, a importância de Manutenção de Software não é uma surpresa, pois uma parte cada vez maior do mundo moderno é digital — ou seja, movida por software. Isso inclui serviços bancários, comércio, entretenimento, mídia, notícias, jogos, agendamento de transportes e hotéis, pedidos de comida, educação, livros, mapas, entre outros. Por exemplo, em seu celular, você certamente possui aplicativos que oferecem esses serviços. No entanto, o que queremos destacar neste momento é o seguinte: há uma quantidade crescente de software impulsionando empresas e organizações no mundo inteiro, e esse software precisa ser desenvolvido, mas também compreendido, mantido e evoluído.
Assim, se você for um desenvolvedor de software, é provável que passe a maior parte do seu dia entendendo, modificando, adaptando e evoluindo sistemas que já estão em funcionamento e que foram implementados por outras pessoas.
Existem, especificamente, cinco tipos de manutenção de software:
Manutenção Corretiva: ocorre quando é necessário corrigir um bug em um sistema reportado por seus usuários. Esse é o tipo de manutenção mais comum, e mesmo alunos de cursos de Introdução a Programação já tiveram esse tipo de experiência.
Manutenção Preventiva: este tipo de manutenção é realizado quando se busca por bugs latentes, ou seja, que ainda não foram reportados por usuários e, portanto, não causaram falhas ou danos. Trata-se de uma manutenção análoga àquela feita por alguém que leva o carro à oficina mesmo que ele não tenha ainda apresentado problemas.
Manutenção Adaptativa: ocorre quando é necessário adaptar um sistema a uma nova regra de negócio ou tecnologia. Por exemplo, se você trabalha em um banco, pode ser responsável por adaptar um sistema a uma nova norma do Banco Central que alterou as regras de cobrança de um imposto financeiro. Como outro exemplo de manutenção adaptativa, você pode integrar um time encarregado de migrar a linguagem de programação usada no aplicativo do banco. Por exemplo, o aplicativo é implementado em Java, mas o banco pretende migrar para Kotlin.
Refatoração: ocorre quando você percebe que determinada parte do código não está implementada da melhor forma. Mais especificamente, trata-se de um código com algum problema que vai dificultar manutenções futuras. Por exemplo, uma função pode ser muito complexa, com centenas de linhas. Então, dizemos que ela deve ser refatorada, o que implica dividi-la em funções menores e mais fáceis de compreender.
Manutenção Evolutiva: este tipo de manutenção é tão importante que decidimos destacá-lo no título de nosso livro. Ela ocorre quando implementamos novas funções e recursos em um sistema existente, ou seja, quando evoluímos seu conjunto de funcionalidades. Por exemplo, um banco está sempre procurando oferecer novos serviços para seus clientes, como PIX, investimentos em criptomoedas, “caixinhas” para guardar dinheiro, etc. O sistema desse banco não nasceu com tais serviços. Em vez disso, ele passou por manutenções evolutivas, realizadas para implementar funcionalidades que vão manter o banco competitivo no mercado.
Nota: A classificação e os tipos de manutenção podem variar conforme o autor. Por exemplo, uma classificação amplamente utilizada no passado foi proposta por Lientz & Swanson, em 1978 (link). No entanto, por ser mais antiga, ela não inclui refatorações — um tipo de manutenção que hoje é essencial para garantir a longevidade de sistemas de software.
Uma diferença fundamental entre produtos físicos e produtos digitais — como software — é que, nestes últimos, é mais fácil realizar manutenções evolutivas. Imagine, por exemplo, um computador. É sempre possível realizar alguma manutenção evolutiva nele, como acrescentar mais memória ou trocar a CPU por uma mais potente. Porém, existe um limite para isso e, na prática, somos forçados a trocar de computador após alguns anos. Por outro lado, sistemas de software costumam durar muito mais tempo do que produtos de hardware. Para dar alguns exemplos, o sistema operacional Linux foi criado em 1991, a primeira versão do Excel para Windows foi lançada em 1987 e até hoje usamos sistemas bancários cujo desenvolvimento teve início na década de 1980. Esses sistemas, no entanto, não ficaram congelados no tempo; ao contrário, foram continuamente evoluídos e passaram a incorporar diversas novas funcionalidades.
1.2 Compreensão de Código 🔗
Para realizar qualquer manutenção com sucesso, você precisa antes compreender o código que será modificado. Costuma-se dizer que o código é escrito uma vez, mas lido centenas de vezes pelos diferentes desenvolvedores que irão mantê-lo no futuro, realizando os tipos de manutenção mencionados anteriormente. Por isso, é fundamental escrever código que possa ser facilmente lido e entendido por outros desenvolvedores. Compreensão de Código é o nome dado ao conjunto de práticas e convenções usadas para escrever código que possa ser facilmente lido, entendido e modificado. Dada a importância desse conceito, decidimos incluí-lo no título do nosso livro.
Literatura Científica: Em 2017, pesquisadores de universidades de quatro países publicaram um estudo que mediu o percentual de tempo que desenvolvedores dedicam a atividades de compreensão de código (link). Para isso, utilizaram uma ferramenta que coleta todas as interações de um desenvolvedor com IDEs e outras aplicações durante seu dia de trabalho. A ferramenta foi instalada nas máquinas de 78 desenvolvedores que atuavam em sete projetos de duas empresas chinesas. Os pesquisadores concluíram que, em média, os desenvolvedores passaram 58% do tempo efetivo de trabalho em atividades de compreensão de código. Portanto, esses resultados reforçam nosso argumento principal nesta seção: para que atividades de manutenção sejam realizadas de forma produtiva, o código precisa primeiro ser lido e entendido.
1.3 Um Pouco de História 🔗
Manutenção, especialmente a corretiva, sempre foi uma preocupação dos desenvolvedores de software. Por exemplo, uma das primeiras pessoas a usar o termo bug como sinônimo de defeito em software foi a cientista da computação Grace Hopper. Em 1947, enquanto trabalhava na Universidade de Harvard, ela observou que os programas executados por um dos grandes computadores da época não estavam funcionando corretamente. Ao investigar, ela descobriu que o motivo era uma mariposa que havia entrado em um dos relés eletromecânicos do computador. Conforme pode ser conferido na próxima figura, Hopper fez então a seguinte anotação bem-humorada no diário de sua equipe: primeiro caso real de um bug encontrado em um dos nossos computadores.
Desde então, diversos bugs ficaram famosos por causarem grandes prejuízos e, por isso mesmo, foram estudados em detalhes. Em 1996, um foguete francês, chamado Ariane 5, explodiu devido a um overflow na conversão de um valor do tipo float para um valor inteiro. Esse bug resultou na explosão do foguete e em um prejuízo de centenas de milhões de dólares. Em meados da década de 1980, uma máquina de radioterapia chamada Therac-25 foi lançada com bugs de concorrência, que fizeram com que pacientes recebessem doses excessivas de radiação. Infelizmente, esses bugs levaram à morte de seis pacientes.
Mais recentemente, em julho de 2024, um bug em um sistema de segurança da empresa CrowdStrike causou uma pane em oito milhões de máquinas Windows, paralisando aeroportos, serviços bancários, empresas de mídia, etc. O bug ocorreu na função do programa que faz a leitura dos arquivos com as especificações das regras de segurança monitoradas pelo sistema. Curiosamente, o código do sistema era distribuído de forma gradual para as máquinas clientes, permitindo uma rápida reversão para a versão anterior em caso de bugs críticos. Porém, o arquivo de configuração — que não faz parte do código executável — era disponibilizado de uma só vez para todas as máquinas clientes da CrowdStrike. Por esse motivo, o bug rapidamente causou uma “tela azul” em milhões de máquinas.
Um exemplo conhecido de manutenção preventiva ocorreu na virada do último milênio, de 1999 para 2000. Na época, diversos sistemas armazenavam os valores de datas com dois dígitos, ou seja, no formato DD-MM-AA. Muitas empresas ficaram receosas de que, na virada do milênio, certas operações com datas pudessem retornar valores incorretos. Por exemplo, uma subtração 00 - 99 poderia gerar um resultado inesperado. Diante disso, formaram grupos de trabalho para realizar manutenções preventivas e converter todas as datas para o formato DD-MM-AAAA. Assim, os possíveis bugs foram prevenidos, e pouquíssimos sistemas apresentaram problemas na virada do milênio.
1.3.1 Leis de Lehman 🔗
Meir Lehman foi um pesquisador da IBM e também professor do Imperial College na Inglaterra que nas décadas de 1970 e 1980 investigou diversos desafios inerentes à evolução de sistemas de software. Nesse período, ele publicou vários artigos científicos nos quais propôs, discutiu e deu exemplos do que ficou conhecido como Leis da Evolução de Software ou, simplesmente, Leis de Lehman.
Em essência, as Leis de Lehman enunciam propriedades que, embora hoje sejam amplamente conhecidas, eram menos óbvias na época. No seu conjunto, elas recomendam que sistemas de software devem sempre passar por manutenções adaptativas e evolutivas, de forma que continuem satisfazendo seus usuários. Por outro lado, essas manutenções têm um custo, pois aumentam a complexidade dos sistemas, que crescem de forma contínua e, paralelamente, sofrem um declínio na sua qualidade interna. Ou seja, com o passar dos anos, a compreensão do código e a realização de novas manutenções tornam-se mais difíceis.
De forma interessante, na década de 90, a prática de refatoração foi proposta exatamente para mitigar o declínio na qualidade do código de um sistema, que ocorre quando ele é modificado ao longo dos anos por desenvolvedores diferentes. O conceito surgiu em uma tese de doutorado, defendida por William Opdyke em 1992, na Universidade de Illinois, EUA (link). Em seguida, em 1999, refatoração foi incluída entre as práticas de programação preconizadas por Extreme Programming (XP). Em 2000, Martin Fowler publicou seu livro sobre o assunto, o que ajudou também a popularizar a adoção da prática. Como afirmamos, a ideia de refatoração é simples: periodicamente, precisamos reorganizar o código de um sistema, tornando-o mais legível e também melhorando sua estrutura. Porém, após uma refatoração, o comportamento do sistema deve ser preservado; ou seja, não devem ocorrer implementações de novas funcionalidades e nem mesmo a correção de bugs
1.4 Manutenibilidade 🔗
Sistemas de software podem levar um ou dois anos para serem construídos, mas depois podem ser mantidos por décadas. Por esse motivo, os custos de manutenção frequentemente representam até 90% do custo total de um sistema ao longo de sua vida útil. Consequentemente, toda empresa que desenvolve ou contrata o desenvolvimento de um software deve se preocupar com sua manutenibilidade, isto é, com a facilidade de compreender e manter o código de um sistema.
Manutenibilidade é um requisito não-funcional, assim como desempenho, escalabilidade, usabilidade, segurança, privacidade, confiabilidade, portabilidade, entre outros. Existe uma norma ISO, chamada ISO/IEC 25010, que define atributos de qualidade para produtos de software. Você pode pensar nessa norma como uma espécie de ISO 9001 voltada para a qualidade de software. Sem surpresas, manutenibilidade é uma das características que devem ser consideradas para avaliar a qualidade de um produto de software, segundo a ISO 25010. A norma também define algumas subcaracterísticas que tornam a manutenção de um sistema mais fácil, incluindo:
- Modularidade: O sistema possui módulos bem definidos que se
comunicam apenas por meio de interfaces ou APIs?
- Reusabilidade: É fácil reutilizar as funções e classes do
sistema?
- Analisabilidade: É fácil compreender as funções e classes do sistema?
- Facilidade de mudanças: É fácil modificar as funções e classes do
sistema?
- Testabilidade: É fácil testar as funções e classes do sistema?
Essas características deixam claro que manutenibilidade abrange
aspectos variados, incluindo não apenas a compreensão e modificação do
código, mas também elementos de design, arquitetura e testes. Por
exemplo, se o design e a arquitetura de um sistema forem uma big ball
of mud
, sua manutenção será difícil, mesmo quando as mudanças
requeridas são simples. Já a ausência de testes aumenta os riscos de
regressões e, portanto, torna as manutenções menos seguras.
1.5 Papel dos Modelos de Linguagens 🔗
As pesquisas sobre o uso de Inteligência Artificial (IA) em tarefas de Compreensão, Manutenção e Evolução de Software não são uma novidade. Desde a década de 1990 (ou até mesmo antes) são realizadas pesquisas sobre recomendação de refatorações, remodularização automática de sistemas, detecção de code smells e correção de bugs, entre outras.
Porém, a partir de 2020, surgiu uma inovação relevante na área de IA, representada por Modelos de Linguagem de Grande Escala (LLM). Esses modelos surpreenderam a todos por apresentar uma capacidade de resolução de problemas muito superior às ferramentas de IA que existiam até então. Para tanto, beneficiaram-se de avanços em algoritmos de IA e de uma fase de treinamento utilizando uma enorme quantidade de documentos disponíveis na Web, incluindo código.
Uma das principais aplicações de modelos de linguagem está na área de Engenharia de Software. Por exemplo, esses modelos apresentam resultados muito bons quando usados para gerar código e também para gerar testes automatizados. Logo, não é surpresa que tenham surgido versões customizadas de LLMs para a implementação de programas.
Além disso, modelos de linguagem são também úteis em atividades de Compreensão, Manutenção e Evolução de Software. Por exemplo, podem ser usados para correção de bugs, recomendação de refatorações e revisão de código.
No entanto, quaisquer resultados produzidos por modelos de linguagem precisam ser revisados por humanos, pois são sujeitos a erros e também a alucinações, isto é, muitas vezes esses modelos tomam caminhos errados e geram respostas sem nexo.
Logo, nossa visão sobre esse tema é a seguinte: pelo menos no estágio atual, o conhecimento humano é vital para garantir que modelos de linguagem não produzam ou modifiquem código de forma parcialmente ou totalmente errada. Em outras palavras, esses modelos, de fato, melhoram a produtividade dos desenvolvedores durante as atividades que apresentaremos neste livro, principalmente se elas tiverem como alvo programas mais simples. Porém, a última palavra continua sendo dos humanos. Daí a importância de formar a nova geração de Engenheiros de Software, para que sejam capazes de realizar tarefas de Compreensão, Manutenção e Evolução de Software, tanto em código escrito manualmente quanto em código gerado automaticamente por LLMs.
1.6 O que Vamos Estudar? 🔗
O restante deste livro possui nove capítulos:
No Capítulo 2, vamos estudar algumas práticas e recomendações para escrita de código legível. Por exemplo, vamos comentar sobre guias de estilo e de nomes, sobre o uso de linters e formatadores e sobre a importância de investir tempo na escolha de nomes legíveis e consistentes ao longo de todo o código. Em seguida, vamos abordar a importância de escrever funções de fácil entendimento, as quais devem ser coesas e possuir baixo acoplamento. Terminamos o capítulo recomendando boas práticas para tratamento de exceções.
No entanto, como código é uma notação operacional e de baixo nível, nem todo código é auto-explicativo, principalmente para aqueles que não participaram da sua escrita. Por isso, no Capítulo 3, vamos estudar boas práticas para a escrita de comentários de código. Vamos começar tratando de ferramentas para geração automática de documentação a partir de comentários, como a ferramenta Javadoc. Em seguida, apresentaremos dois tipos de comentários: comentários públicos e comentários privados. Comentários públicos fazem parte da documentação de referência de uma biblioteca ou subsistema e, portanto, documentam elementos de código que serão usados por terceiros. Já comentários privados explicam partes internas de um módulo e, logo, serão lidos por desenvolvedores que vão manter esse módulo no futuro. Ainda no Capítulo 3, tratamos de anti-padrões de comentários, isto é, tipos de comentários que devem ser evitados. E terminamos o capítulo com uma seção sobre comentários de APIs Web, especificamente de APIs REST.
[demais capítulos em breve]
Bibliografia 🔗
Marco Tulio Valente. Engenharia de Software Moderna: Princípios e Práticas para Desenvolvimento de Software com Produtividade, 2020.
Meir M. Lehman, Juan F. Ramil, Paul Wernick, Dewayne E. Perry,
Wladyslaw M. Turski:
Metrics and Laws of Software Evolution — The Nineties View. 4th IEEE
International Software Metrics Symposium (METRICS), 1997.
International Organization for Standardization, ISO/IEC 25002:2024 — Systems and software engineering — Systems and Software Quality Requirements and Evaluation (SQuaRE) — Quality model overview and usage, 2024.
Exercícios 🔗
1. Pensando em um prédio, o que seria uma manutenção: (a) evolutiva; (b) adaptativa; (c) corretiva; (d) preventiva; (e) e uma refatoração.
2. Como você classificaria uma manutenção para melhorar o desempenho em tempo de execução de uma certa funcionalidade de um sistema? E para eliminar uma falha de segurança?
3. Identifique e descreva dois bugs que existem na seguinte função Python que retorna uma tupla com as raízes de uma equação de segundo grau, com coeficientes a, b e c.
def calcular_raizes_segundo_grau(a, b, c):
delta = b**2 - 4*a*c
raiz1 = (-b + math.sqrt(delta)) / (2 * a)
raiz2 = (-b - math.sqrt(delta)) / (2 * a)
return (raiz1, raiz2)
4. Seja a seguinte classe de um sistema Python, de um banco hipotético.
class ContaBancaria:
def __init__(self, saldo):
self.saldo = saldo
def sacar(self, valor):
self.saldo = self.saldo - valor
def depositar(self, valor):
self.saldo = self.saldo + valor
Classifique as seguintes mudanças segundo os tipos de manutenção estudados:
- Verificar se a conta possui saldo antes de um saque.
- Implementar um novo método para realizar transferências entre
contas.
- Migrar o sistema para uma outra linguagem de programação.
5. Suponha que você trabalhe mantendo uma Calculadora para Android. Descreva uma possível manutenção adaptativa neste aplicativo. E também uma manutenção evolutiva.
6. Existem sistemas de software importantes e grandes que sofrem pouquíssima manutenção e que continuam sendo usados? Se sim, dê exemplos.
7. Uma das Leis de Lehman afirma que “à medida que um sistema sofre manutenções, sua complexidade interna aumenta e a qualidade de sua estrutura interna deteriora-se, a não ser que um trabalho seja realizado para estabilizar ou evitar tal fenômeno.” Qual o nome que se dá atualmente para esse trabalho realizado para evitar a degradação da qualidade interna de um sistema?
8. Suponha um artigo científico com o seguinte título: Um Catálogo
de Refatorações para Garantir Conformidade com a Lei Geral de Proteção
de Dados Pessoais (LGPD)
. Por que o uso de refatoração nesse
contexto não está correto?