BT

Disseminando conhecimento e inovação em desenvolvimento de software corporativo.

Contribuir

Tópicos

Escolha a região

Início Artigos Identificando e Analisando Código Redundante

Identificando e Analisando Código Redundante

Pontos Principais

  • O código pode ser redundante por inúmeras razões que vão desde variáveis não utilizadas, passando por alterações não concluídas e até mesmo código desenvolvido e abandonado.
  • O código redundante tem uma série de consequências, incluindo código fonte inchado, redução de capacidade de manutenção e confiabilidade reduzida. Em alguns casos, código morto também pode afetar o desempenho.
  • Para detectá-lo, o autor desenvolveu uma ferramenta que criou uma árvore de sintaxe abstrata para código fonte em C# usando Roslyn e treinou-a usando uma série de projetos do GitHub incluindo Roslyn e MSBuild.
  • Uma vez que o código redundante foi detectado, ele pode ser removido manualmente, apagando-o ou comentando-o, ou usando uma ferramenta automática. O uso de branches pode ajudar a evitar que o código redundante chegue à branch principal.

Introdução

Algum tempo atrás, desenvolvi uma ferramenta que analisava dependências contidas em código fonte. Criei um Árvore de Sintaxe Abstrata para código C# utilizando Roslyn e uma funcionalidade similar para código C++ utilizando libclang. Para validar se esta ferramenta estava funcionando como previsto, implementei uma funcionalidade que identifica métodos não utilizados. Com isto, percebi que o parser do código C# era muito mais assertivo quando comparado com o código para C++. Assim, decidi dedicar mais tempo no desenvolvimento do parser para C# assim como no código mais sofisticado de outras pessoas também em C#.

No início, a ferramenta era capaz de sinalizar as linhas onde existiam métodos redundantes e a medida que a escala do problema se tornava aparente, uma opção para deletar estas linhas de forma automática foi implementada. Uma análise típica envolveria executar a ferramenta repetidamente para podar o código fonte tão brutalmente quanto possível. Esta ação então foi seguida por vários ciclos de mudanças de reversão para obter compilações bem sucedidas e, com isto, passar nos testes. Os motivos para o fracasso eram que a ferramenta tinha se comportado de forma incorreta ou havia uma limitação conhecida, exemplos do mais recente como reflexo ou a existência de um contrato de código.

A ferramenta foi treinada em inúmeros repositórios do GitHub para projetos em C# que foram escolhidos a medida que os utilizava; desta forma, estaria contribuindo com estes projetos de volta. Em última análise, um pull request foi submetido à comunidade solicitando a discussão das mudanças na minha branch. Como a ferramenta era bruta e estava engajado on-line com as pessoas pela primeira vez, esta situação era onde a diplomacia era necessária e espero não ter ofendido muitas pessoas. Ao contribuir e estar envolvido nas discussões subsequentes, a minha compreensão dos problemas foi aumentando cada vez mais e este artigo é uma tentativa de prover esta experiência para a comunidade de desenvolvimento como um todo.

Os projetos analisados no GitHub

O projeto Mono.Cecil decompila código .NET para C# e foi escrito por JB Evain. Na análise, sugeriu-se que apenas 36 linhas fossem excluídas e JB Evain optou por adicionar, independentemente, algumas das mudanças após a revisão, em vez de fazer um merge.

O projeto Automatic Graph Layout é um projeto oficial da Microsoft desenvolvido por Lev Nachmanson, Sergey Pupyrev, Tim Dwyer, Ted Hart, e Roman Prutkin para desenhar gráficos e gráficos direcionados e é utilizado pelo Visual Studio para apresentar inúmeros diagramas interativos. O pull request sugeriu que 4674 linhas de código fossem deletadas, um número tão alto que é capaz de ser o tamanho do SilverLight (cuja depreciação foi anunciada em 2015). Neste caso, um merge foi realizado com a branch sem nenhuma modificação ou discussão.

O Roslyn é o compilador C# moderno mantido por um time da fundação .NET. Neste caso, o pull request indicou uma necessidade de exclusão para 18364 linhas, o que resultou em uma boa discussão e levou à maior parte da categorização que é discutida logo a seguir. Obviamente, toda esta sugestão era muito grande para ser integrada via merge e, em vez disso, levantou a uma série de questões individuais.

O MSBuild é um outro projeto oficial da Microsoft, com o qual qualquer usuário do Visual Studio precisa estar familiarizado. A análise levou a um pull request indicando a exclusão de 3722 linhas de código. Infelizmente, o time não teve a capacidade neste momento de verificar as sugestões indicadas de mudança.

O último projeto a ser analisado foi o System.XML, montado sob as bibliotecas .NET Core foundational. Estas bibliotecas são mantidas pela fundação .NET e o time possui um chamado aberto para eliminar código morto. O chamado foi abordado por um processo assembly que envolveu cortar o assembly (mais comumente conhecido como eliminação do código morto) e, em seguida, criar uma diferença entre as partes não cortadas e as recortadas para identificar o código compilado que foi removido. Esta diferença, no final, informa o código fonte a ser excluído, um trabalho normalmente realizado pela comunidade voluntária.

Mesmo diante de uma amostra de cinco projetos, ainda não é aconselhável tirar quaisquer conclusões, especialmente devido à falta de estatísticas confiáveis:

 

Mono.Cecil

MSAGL

Roslyn

MSBuild

CoreFX (System.XML)

Deleted

36

4674

18364

3722

427

Initial commit

2010/04/12

2015/02/22

2014/03/18

2015/03/12

2014/11/08

Authors (main)

39 (1)

25 (4)

285 (31)

90 (6)

526 (29)

A data de commit inicial para o MSBuild é notável, dado seu longo histórico. Apenas contar o número de autores sem avaliar sua contribuição é quase certamente sem sentido, então é dada uma estimativa aproximada dos contribuintes significativos. Dito isto, especularia que:

  • Projetos recentemente desenvolvidos têm código mais redundante do que projetos maduros:
    • O código escrito é assumido como necessário;
    • Menos testes correspondem a menos bugs encontrados.
  • Times maiores produzem mais código redundante:
    • A comunicação do time aumenta quatro vezes com relação ao tamanho do time, vide Brooks.

Categorização dos resultados do teste

A ferramenta examina métodos redundantes e, como qualquer teste, pode ser analisado quanto ao seu sucesso e ao seu fracasso.

O verdadeiro-negativo:

  • O código é útil.

Os falsos-negativos:

  • Quando apenas o código foi testado:
    • A funcionalidade testada é utilizada apenas pelo teste. Este pode ser um artefato ao seguir um processo TDD ou BDD, o que neste caso pode ser rastreado de volta para um requisito desnecessário ou que não foi atendido.
  • Código morto:
    • Neste caso, embora o código seja chamado, ele não executa nada de útil e a sua chamada pode ser omitida.
    • Pode ser também um código duplicado.
    • Observe que um código obsoleto pode ser considerado a um passo de ser um código morto.

Os verdadeiros-positivos:

  • Desenvolvimento intencionalmente abandonado:
    • Métodos que são adicionados ao código mas que, devido às mudanças circunstanciais, nunca foram utilizados.
  • Mudanças incompletas:
    • À medida que o código é modificado, alguns métodos passam a ter todas as suas referências excluídas, porém o commit subsequente a esta mudança não levou isto em consideração. Esta situação, algumas vezes, é batizada de oxbow code. Pode estar relacionado a um artefato não intencional de refatoração.
  • Variáveis não utilizadas:
    • Deseja-se que, neste caso, o compilador gere alertas e/ou que os alertas sejam informados pela IDE em uso.

Os falsos-positivos:

  • Deficiências na ferramenta:
  • Regressão:
    • Esta situação ocorreu quando, durante o desenvolvimento, um método foi alterado para não mais chamar o método identificado como morto. Obviamente, não houve teste implementado para recuperar esta regressão.
  • Falta de uma compilação condicional:
    • Normalmente, esta situação se referia a uma falta de prevenção contra #if DEBUG. O Roslyn havia sido utilizado para criar Releases conforme se desejava e esta ação identificou um inchaço no binário correspondente.
  • Desenvolvimento involuntariamente abandonado:
    • Esta situação foi identificada para o caso específico com os dados de teste, mas não com nenhum teste especificamente. No caso geral, espero que este seja um problema temporário e que seja sanado à medida que mais código seja adicionado.
  • Não utilizado no subsistema:
    • Uma quantidade insuficiente de código foi analisada com o objetivo de encontrar qualquer coisa fazendo uso de um método. Este foi o principal problema ao analisar métodos públicos, embora também possa ocorrer devido à reflexão. Um exemplo comum é quando a funcionalidade auxiliar de teste é processada sem o código de teste correspondente, embora isso levante um problema de arquitetura sobre onde o código auxiliar de teste deveria estar localizado.
  • Métodos que pertencem apenas ao depurador:
    • Alguns projetos possuem métodos para imprimir/sinalizar o estado quando o depurador está vinculado (potencialmente para uma compilação de lançamento). Estes métodos devem ser marcados de alguma forma para que não sejam analisados.

Deve ser observado que, quando a ferramenta falha, não é necessariamente devido a uma falta de inferência. Alguns dos casos devem ser cobertos por outras ferramentas. Por exemplo, os avisos do compilador devem sinalizar alguns dos problemas e os detectores de código duplicados também são úteis neste caso.

Os efeitos de um código redundante

Os falsos-negativos e os verdadeiros-positivos devem ser vistos coletivamente como instâncias do YAGNI e com isto, levantar as seguintes considerações:

  • Código fonte inchado:
    • No passado, haviam editores de texto que não conseguiam abrir grandes arquivos.
    • A capacidade cognitiva de um desenvolvedor é limitada pelo número de entidades independentes que podem ser mantidas simultaneamente no cérebro. Geralmente, é necessário ter sete, portanto, remover métodos levará a uma melhor capacidade de raciocínio.
    • Qualquer período de tempo que um desenvolvedor leve para ler e compreender o código fonte provavelmente será um esforço desperdiçado.
    • IDEs modernas na maioria das vezes criam árvores de sintaxe abstratas do código fonte, de modo que o código redundante tende a reduzir.
  • Executáveis inchados:
    • Este é um problema particular para cenários com o uso de smartphones, onde as atualizações levam à remoção de aplicativos, de modo a liberar espaço de armazenamento, normalmente levando a ter que atualizar o dispositivo quando a funcionalidade está muito comprometida.
  • Tempo de execução inchado:
    • Tendem a gerar uma maior superfície de ataques para um hacker, o que levanta preocupações com aspectos relacionados a segurança.
    • Algumas aplicações de dispositivos embarcados alocam toda a memória dinâmica no início, de modo que o código redundante reduzirá o tamanho do heap disponível.

Dada a grande variedade de problemas que podem ocorrer devido ao código redundante, é melhor tratá-lo como um cheiro de código.

  • Performance reduzida:
    • Para casos específicos de código morto, a execução destes códigos desperdiçará ciclos de processamento.
  • Redução de confiabilidade:
    • Para casos específicos de código morto, a execução destes códigos pode levar a interrupções da aplicação.
  • Redução da manutenibilidade:
    • Caso o código inesperadamente deixar de ser redundante, então o comportamento não poderá ser previsto. Por exemplo, veja o caso da Knight Capital que perdeu cerca de $440 milhões de dólares.
    • A porcentagem de cobertura de código A porcentagem tende potencialmente a crescer com falsos-negativos e reduzir com falsos-positivos.
    • Testes desnecessários retardarão a execução do conjunto de teste.
    • As ferramentas de análise estática terão que processar mais e assim trabalhar mais lentamente.
    • O design/arquitetura do software vai parecer mais complicado do que ele realmente é.

Gerenciamento do código redundante existente

Existem basicamente quatro maneiras de se tratar código redundante:

  • Ignorar ele:
    • Pelo menos ele ainda compilará, embora haja um custo de manutenção associado.
    • Não é uma ação recomendada.
  • Remoção automática do executável:
    • Quando uma linguagem compilada faz uso de qualquer opção de eliminação de código morto no vinculador para removê-lo.
    • Quando uma linguagem dinâmica faz uso de alguma técnica de tree shaking para eliminar em tempo de execução.
    • Novamente, não é uma ação recomendada.
  • Bloqueie este código:
    • Comente o código ou faça uso de diretivas pré-compiladas.
    • Faça uso de código/comentários para garantir na IDE que você não está esquecendo nada.
  • Exclua o código:
    • Confie no controle de versão de código a fim de manter uma cópia antiga para referência; se você precisar, poderá encontrá-lo.
    • Alternativamente, você pode nunca precisar dele novamente.

Se o código fonte for alterado, todas as mudanças devem ser revisadas por alguém familiarizado com essa seção do código, com o objetivo de garantir que o código seja realmente redundante.

Gerenciamento do novo desenvolvimento

Supondo que o objetivo não seja introduzir um novo código redundante durante o desenvolvimento, quais estratégias devem ser implantadas? Caso sejam commits parciais, é provável que o código redundante seja temporariamente adicionado à base do código. Isso pode ser mitigado usando os recursos de branches e apenas fazendo merge no branch principal assim que os testes forneçam cobertura e o código fonte passe pela análise estática. Uma outra vantagem ao se utilizar branches é que, quando o desenvolvimento é abandonado, o branch pode permanecer descompactado.

Sobre o autor

Zebedee Mason é um estatístico com 25 anos de experiência na indústria CAD/CAM/CAE e que recentemente migrou para SLAM (Simultaneous Localization and Mapping). Ao longo dos anos, trabalhou com bases de código legado (algumas dos quais desde sua infância), e reescreveu vários pedaços de código acadêmico em componentes de software comercial e pode inclusive até ter escrito alguns algoritmos originais (a confidencialidade comercial impede a confirmação). Esta experiência lhe proveu uma apreciação por ferramentas de software, das quais implementou algumas para aumentar sua produtividade, apesar dos interesses diferentes de desenvolvedores e gerentes de TI com os quais trabalhou.

Avalie esse artigo

Relevância
Estilo/Redação

Conteúdo educacional

BT