Por que eu devo ler este artigo:O artigo aborda a Metáfora da dívida técnica, que diz respeito à degradação de software ocasionada pela dívida, que são desvios de boas práticas de engenharia de software deixadas para trás intencionalmente ou não, mas que acumulam “juros” ao longo do tempo.

Este tema será útil a arquitetos de software, gestores de projetos e a desenvolvedores que seguem o paradigma orientado a Testes, quando buscam métricas a respeito de conformidade entre design e implementação de software em relação à dívida técnica.

Cada falha de design encontrada pela verificação é computada em horas de trabalho para se corrigir a violação arquitetural. Este artigo apresenta uma abordagem para realizar o cálculo da Dívida Técnica Arquitetural em aplicações Java.

Os métodos utilizados para esta finalidade envolvem a mineração de dados de repositórios de código-fonte aberto, bancos de dados de rastreamento de defeitos, estimativa de tempo de correção de violações de regras de design e verificação de conformidade arquitetural.
Autores: Andrey Estevão da Silva e Cássio Leonardo Rodrigues

A pressão para se cumprir prazos de entrega e/ou descuidos quanto a boas práticas de engenharia de software como código ilegível, desvio de padrões arquiteturais definidos intencionais ou não e ausência de processos de desenvolvimento ocasionam aumento no tempo de manutenção e engessamento na criação de novas funcionalidades da aplicação.

Define-se este montante de tempo e custos para se trazer o tempo de correção a um limiar aceitável como “A Metáfora de Dívida Técnica”.

Seu primeiro relato ocorreu em 1992 por Ward Cunninghan em “The WyCash Portfolio”. Cerca de 20 anos se passaram até então e ainda é preciso evoluir o nível de maturidade quanto à dívida técnica a nível gerencial e operacional.

A gerência da dívida pode ser comparada a uma dívida financeira: o devedor deve decidir se é interessante pagá-la no momento em que ela ocorre e liquidar os juros, ou pagá-la mais adiante, assumindo a dívida e acumulando-se o montante.

Lidar com os custos gerados pode ser difícil, pois suas ocorrências não são uniformes.

Segue o questionamento de um participante sobre como tomar decisões a respeito da dívida técnica em uma pesquisa de campo entre 35 profissionais de diversos níveis de experiência na indústria de software: “Em algum momento, se isso (a implementação/correção) funciona, satisfaz as necessidades, há relativamente pouco ou nenhum impacto para os clientes, seja de produtividade ou de manutenção, então, vale a pena pagar a dívida?”

Pode-se observar que é necessário ponderar diversos fatores para tomada de decisão sobre uma medida quanto à dívida. Na entrevista, 75% dos entrevistados não eram familiarizados com o termo, o que justifica a continuidade de estudos na área, pois se trata de uma característica que deteriora o software com a qual se convive a todo o tempo, devendo ser gerenciada adequadamente.

A dívida ocorre por si só em um projeto. Fatores naturais favorecem o seu acontecimento, como por exemplo, tecnologias obsoletas ou muito recente adotadas e mudanças de regra de negócio. Na fase de arquitetura, esta dívida é quase que invisível (a nível prático), o que requer maior atenção em sua identificação e ao mensurá-lo.

A dívida pode ser estimada através de estudos orientados a falhas específicas de design: classes-deusas (classes que possuem uma carga de responsabilidades exagerada), por exemplo, são mais propensas a defeitos do que classes bem divididas.

A afirmação foi comprovada através da detecção de Code Smells: uma vez que uma função é utilizada em larga escala, para diversas finalidades em uma aplicação, torna-se difícil de ser modularizada, gerando-se dívida técnica.

Existem também outras maneiras de se calcular a dívida técnica que não envolvem apenas análise de código. GUO e Seaman propõem uma estimativa com base na análise de dados históricos de defeitos em aplicações.

A abordagem é denominada Gerenciamento de Portfólios: usada na gestão financeira, consiste na analogia a uma carteira de investimentos (montante da dívida), onde o gestor decide onde aplicará fundos (correções). O investidor faz a aplicação analisando-se a variância (risco) em relação ao montante.

Existem ferramentas no mercado para automatização do cálculo da dívida técnica, uma delas é o Sonar, que é uma plataforma para gerência de testes e qualidade de software.

Em sua versão mais nova, o plugin Technical Debt, que gerenciava a dívida, foi depreciado e substituído por uma implementação paga do framework SQALE. Além disso, para o cálculo da dívida técnica arquitetural, os valores de cada violação específica devem ser configurados manualmente segundo experiência da organização que o usar.

Outro ponto a ser considerado é que dada a experiência atual, não há como codificar um plugin que mude esses valores ou que os utilize de uma outra base histórica. Propõe-se, então, no presente artigo uma automação desta fase por meio da análise de dados coletados de outras bases.

A respeito de análise estática, podem ser considerados usos, por exemplo, de Design por contrato. Propõe-se o uso de checagem de pré-condições e pós-condições através de asserções, podendo-se assim verificar violações de contrato, que são comportamentos que objetos e propriedades devem assumir.

Esta solução é o pilar de diversas técnicas como a checagem de condições através de orientação a aspectos e testes unitários.

A dívida técnica apresentada e estimada neste artigo é em relação ao desvio de regras de especificação arquitetural, aqui denominada como Regras de Conformidade Arquitetural. Dentre essas regras, podemos avaliar: obediência à hierarquia de pacotes, extensão de classes, conformidade de nomenclatura e modificadores de classes, obrigatoriedade de implementação de interfaces, e até mesmo se atributos e métodos correspondentes a um ou vários padrões de projeto se encontram codificados como tal, ou se realmente se encontram nas classes em que deveriam estar. Este problema pode ser caracterizado como Gap entre Design e Implementação.

Solução Proposta

Pretende-se usar a ferramenta Design Wizard para verificação de regras de conformidade. Justifica-se o seu uso por ser uma abstração da API de testes unitários JUnit, porém voltada para testes de design.

Outro fator que foi considerado é que já existem implementações utilizando-se a ferramenta, o que torna possível uma prova de conceito da mesma: verificou-se o design de arquiteturas tendo-se como entrada de dados uma abordagem em MDA (Model Driven Architecture), sendo seus componentes diagramas de classes UML.

O analisador de código, então, verificava por meio de asserções se a arquitetura foi desenvolvida conforme o especificado pelos diagramas.

A Figura 1 representa o exemplo que será utilizado no cálculo da dívida técnica. A figura representa como foram implementadas as classes de domínio de um portal acadêmico.

Nela, podemos encontrar elementos que representam o tipo de conteúdo apresentado pelo portal e aspectos estruturais peculiares ao projeto de baixo nível de sistema, como relacionamentos horizontais e heranças.

Pode-se perceber que uma grande gama de classes está diretamente ligada a uma superclasse denominada GenericEntity. Isto é uma regra de design (arquitetura/projeto).

Cenário constituído pelas classes de domínio de um portal acadêmico

abrir imagem em nova janela

Figura 1. Cenário constituído pelas classes de domínio de um portal acadêmico

A Figura 2 apresenta o uso do Sonar para se estimar a dívida técnica do projeto implementado segundo o modelo representado na Figura 1. O procedimento consistiu em, primeiramente, gerar um arquivo de código fonte compilado em Java (JAR) do módulo que contém as classes de domínio do portal acadêmico.

Em seguida, foi feita a importação deste arquivo na aplicação Sonar. Para isto, é necessário que o módulo seja gerenciável pela ferramenta de automação de compilação Maven, que gera um descritor em XML (arquivo pom.xml) contendo informações como, por exemplo, as dependências de bibliotecas necessárias para que o projeto seja compilado.

Com o servidor do Sonar em execução e aplicação Maven instalada no ambiente de testes, foi utilizado o comando maven sonar:sonar em um terminal na raiz onde se encontra o descritor da aplicação a ser analisada. Com este comando, o projeto Maven então é importado para o Sonar, que calcula diversas métricas, dentre elas a dívida técnica do projeto.

Na Figura 2 são mostrados na interface diversos dados sobre o código, tais como o número de linhas de código (Lines of Code), nível de documentação de API´s envolvidas no desenvolvimento (Documentation), nível de comentários de código (Comments), nível de duplicação de código (Duplication), possíveis problemas encontrados (Issues), dentre outros. Considerando-se estes fatores, o resultado da dívida estimado (Technical Debt) é de 26,6 dias para sua correção.

Dívida técnica antes da verificação de Design

abrir imagem em nova janela

Figura 2. Dívida técnica antes da verificação de Design

Em seguida, modificou-se o código da aplicação gerando-se uma nova versão, onde se removeu propositadamente as restrições de herança da classe “Empregado”, que anteriormente estendia da classe GenericEntity.

Por se tratar da violação de uma regra de design, ou seja, uma inconformidade em relação ao projeto, espera-se que este impacto seja quantificado sob a forma de dívida.

Verifica-se então o resultado apresentado na Figura 3, obtido através do mesmo procedimento para obtenção da versão anterior do projeto, onde se queria estimar a dívida técnica antes da verificação de design.

Pode-se perceber que a dívida técnica entre as análises de 15:45 e 15:57 do mesmo dia permanece inalterada após a remoção das restrições, o que significa que as divergências entre regras do design e implementação, que podem gerar complicações mais tarde, não estão sendo quantificada como dívida técnica.

Dívida técnica após a verificação de Design

abrir imagem em nova janela

Figura 3. Dívida técnica após a verificação de Design

Para capturar esta exceção, será realizada então uma análise de Design com auxílio da ferramenta DesignWizard, apresentada na Listagem 1. Neste trecho está sendo feita uma verificação de código estática, onde foi importado o arquivo JAR da aplicação para o DesignWizard.

Em seguida, estamos fazendo uma asserção, verificando se a classe “Empregado” existe e se a mesma estende da classe GenericEntity. A intenção é que a ausência desta condição seja quantificada como dívida técnica.

Listagem 1. Asserção de design, método “checkHeritage()”


  public static void checkHeritage(){
    1  try {
    2  DesignWizard dw = new DesignWizard("stg-infra_teste.jar");
    3  //Arquivo a ser comparado
    4  ClassNode classNode = dw.getClass("br.ufg.cercomp.stg.entity.Empregado");
    5  //Extensão do Arquivo a ser comparado
    6  ClassNode extension = new ClassNode("br.ufg.cercomp.entity.GenericEntity");
    7
    8  //Realiza um teste unitário
    9  assertTrue(classNode.extendsClass(extension));
    10
    11  } catch (IOException e) {
    12   //Dispara uma exceção caso o arquivo jar nao seja encontrado
    13   e.printStackTrace();
    14  } catch (InexistentEntityException e) {
    15   //Dispara uma exceção caso a entidade comparada não exista
    16   e.printStackTrace();
    17  }
    }

Ao executar o código, obtém-se uma exceção, conforme pode se observar na Listagem 2.

É possível notar que a inserção de uma classe sem as restrições impostas não produz inicialmente efeito algum na dívida técnica, pois a mesma não interpreta tal fato com uma violação.

Porém, por exemplo, ao se dizer que a classe GenericEntity é uma classe abstrata que satisfaz as condições de um Framework de Persistência X e que sem ela não será possível fazer a injeção de dependência, haverá um retrabalho por parte do desenvolvedor para satisfazer o design, o que deveria ser computado como dívida técnica.

Listagem 2. Falha detectada na Asserção de design, método “checkHeritage()”

junit.framework.AssertionFailedError
    at junit.framework.Assert.fail(Assert.java:55)
    at junit.framework.Assert.assertTrue(Assert.java:22)
    at junit.framework.Assert.assertTrue(Assert.java:31)
    at junit.framework.TestCase.assertTrue(TestCase.java:201)
    at DesignTest.checkHeritage(DesignTest.java:9)

Primeiramente, tentou-se o desenvolvimento de um plugin para o Sonar que utilizasse os seus serviços REST disponíveis para criação de regras.

Porém, há uma restrição quanto ao cálculo da dívida técnica decorrentes da violação de regras manuais. Neste caso, o valor não é calculado, mesmo se populadas o valor da dívida técnica em horas, disponível pela própria API.

Em contato com o fórum da empresa, foi aconselhado primeiramente que se criassem regras estendidas de uma violação pré-existente, denominada “Architectural Constraint”.

Porém, ainda assim a dívida técnica não é calculada. Em nova comunicação com os analistas, os mesmos disseram ser possível fazê-lo configurando-se os custos de remediação do plugin SQALE, mas isto foge a intenção deste artigo que é a automatização do cálculo destes custos.

Mineração de Dados

A fim de se encontrar uma estimativa de tempo adequada para a correção de violações de Design, foram minerados dois tipos de bases de dados abertas em busca de falhas de design e seu tempo de resolução: O Bug Tracker Jira e o Repositório de projetos de código aberto GitHub, ambos selecionados por terem web services e API ´s em java que facilitam o seu manuseio.

Foram analisadas as bases dos dois sistemas de 19 empresas de produtos de código-fonte aberto, num total de 1877 projetos, conforme Tabela 1.

Empresa

Quantidade de Repositórios

Apache Software foundation

363

Jasig

39

JBoss

38

Jenkins

1101

Spring

46

Hibernate

12

Atlassian

24

Sonatype

152

Pentaho

46

JRuby

9

Groovy

4

Grails

6

MongoDB

4

ZKoss

11

Glassfish

3

SonarSource

18

Clojure

1

Total

1877

Tabela 1. Quantitativo de Rastreadores de Defeitos Jira e GitHub analisados

Foi selecionado um conjunto de expressões que representam (em linguagem natural) critérios de violação para se realizar a extração de dados no sistema Jira em cada empresa.

A consulta busca apenas registros de tarefas cuja resolução seja “Consertada” e seu status seja “Fechado ou Resolvido”. Neste caso, as consultas são facilitadas graças a uma linguagem própria de buscas disponível, a JQL (Jira Query Language).

Foram selecionados alguns critérios de Design que podem ser violados durante a fase de codificação, dentre eles:

· Deveria estender uma classe, mas não estendeu;

· Deveria implementar uma interface, mas não implementou;

· Deveria implementar o Padrão Singleton

· Deveria ser anotada, mas não foi;

· Deveria implementar o padrão Builder;

· Deveria implementar inversão de Controle e Injeção de dependência;

· Foi colocada uma classe no pacote errado;

· Deveria implementar o Padrão Factory;

· Deveria implementar o Padrão Strategy

Cada registro retornado pela extração de dados contém um identificador no formato KEY-#####, em que KEY são caracteres, geralmente iniciais de cada projeto e # números de 0 a 9, que representam uma organização sequencial de cada problema reportado pelos usuários.

Foi realizado um mapeamento relacional entre cada KEY e repositórios GitHub, que não necessariamente devem seguir o mapeamento 1:1.

Pôde-se observar que a comunidade de código aberto tem a boa prática de relacionar grande parte dessas KEY ao seu respectivo commit. Esta verificação é necessária por dois motivos:

· Verificar se realmente a correção foi realizada e não somente marcada como resolvida no Bug Tracker;

· Verificar qual a real data final de término da correção com base na data de commit, pois a mesma tem mais chances de ser a real data de finalização do problema reportado, visto que seu release será cobrado por parte do usuário solicitante.

Esta medida também apura a estimativa de tempo para correção de uma determinada violação de design.

O sucesso e desempenho da consulta por commits é fortemente influenciado pelos termos de pesquisa no Bug Tracker, devendo estes ser bem elaborados, contendo, se possível, “chavões” da área. Faz-se a pesquisa pelos identificadores no Github através de um web service. O mesmo possui limitações quanto ao número de requisições possíveis (60 requisições por hora para usuário não autenticado e 5000 para usuário autenticado).

Arquitetura da aplicação

De posse das informações apresentadas, foi desenvolvida uma aplicação em Java para automação do processo de extração de dados das bases de bugs e código-fonte de cada empresa. No caso da extração na base de dados Jira, foi utilizada a sua API em Java, desenvolvida pela própria Atlassian.

É feita a abstração de diversos web services disponíveis por meio de REST: consulta por projetos, autenticação e consulta por problemas, dentre outros.

Na comunicação com o GitHub, foi utilizada uma API desenvolvida pela empresa de integração contínua Jenkins. Denominada Kohsuke Github, a API faz também a abstração dos serviços REST para commits, busca de usuários, busca de repositórios, busca de arquivos, patches (trechos que contém apenas o código modificado em um commit em relação a sua versão anterior).

Além dessas facilidades implementadas, o autor também desenvolveu uma implementação do Conceito OKHttp, que implementa um cache para consultas condicionais, o que evita o número elevado de requisições e, por consequência, erros 302.

A aplicação aqui proposta para o cálculo da dívida foi desenvolvida utilizando-se o servidor JBoss, pois os autores consideram que os serviços para criação de batches disponível pela sua API são de fácil manuseio: basta apenas anotar o método que fara a extração com seu tempo de início e a mesma inicia após o seu deploy, realizando a carga dos dados em uma base de conhecimento local. Todos os serviços foram injetados por meio de CDI (Context Dependency Injection).

A Camada de Persistência foi desenvolvida com base na JPA, o que ajudou no mapeamento objeto relacional dos distintos domínios de aplicação (repositório de código e rastreio de bugs). Utilizou-se como container o framework Hibernate. O banco de dados selecionado para persistência dos registros foi o PostgreSQL

A arquitetura da aplicação é subdividida em quatro camadas, conforme Figura 4:

· Fachada – Responsável pela comunicação com a camada de serviços da aplicação. Seu projeto foi configurado para que outras aplicações clientes possam utilizá-la, como por exemplo, um analisador estático que queira usar dados da dívida;

· Autenticação – Provê controle de acesso entre os serviços GitHub e Jira por meio de tokens de acesso;

· Serviços – Fazem a comunicação com as camadas DAO e outras camadas de serviço externas ao contexto local, neste caso os web services Jira e GitHub;

· DTO – É composta por objetos de transferência de dados. Nesta camada, utilizou-se o padrão Factory, para a partir de uma classe não gerenciada pela JPA (Entidades das API´s Git e JIRA, por exemplo, não pertencentes ao domínio local) pudesse ser feito o mapeamento para uma classe gerenciada e, assim, pudesse ser persistida pela DAO;

· DAO – Responsável pelas operações de CRUD dos objetos de domínio da aplicação;

· Entidades – São as classes que contém as classes de domínio da aplicação, utilizadas na comunicação objeto-relacional.

Diagrama de Pacotes Representando a Arquitetura da Aplicação

abrir imagem em nova janela

Figura 4. Diagrama de Pacotes Representando a Arquitetura da Aplicação

Resultados

Os dados extraídos compõem uma base de conhecimentos sobre cada critério violado, provendo informações necessárias para se configurar um modelo de estimativa de tempo de correção destes problemas.

É feita a junção de dados de commit (dentre eles o código fonte associado) e dados de bugs, associando-se o seu identificador em comum a um critério de violação. Assim, pode-se fazer associação de outras variáveis com o cálculo de dívida técnica, por exemplo, a complexidade ciclomática, obtida da análise do código fonte associado.

Deficiências da solução

Um dos problemas que devem ser corrigidos com a abordagem é de se criar uma maneira mais automatizada de se eliminar falsos positivos encontrados na fase de extração.

Até o momento, os problemas que não fazem realmente parte do critério de violação são manualmente eliminados da base de dados, o que pode gerar atrasos na entrega final e utilização da estimativa, caso contenha muitos falsos positivos.

Outro problema que deve ser avaliado é de se ainda verificar a melhor forma de apurar o tempo real de trabalho sobre o bug reportado. A desconsideração do tempo finalização ao se extrair dados do JIRA já elimina certos problemas de quando o desenvolvedor se esquece de finalizar a tarefa, conforme já foi constatado, onde a tarefa neste caso apresenta atrasos inexplicáveis em sua finalização.

Ferramentas de automação e processos têm sido desenvolvidas com a finalidade de apresentar métricas sobre o custo de correção de violações e quantificação de esforços de retrabalho. Este artigo apresentou uma maneira automatizada de cálculo da dívida.

Aliando-se a menção em Schmidt, a respeito das capacidades da dívida técnica em relação à arquitetura ainda a serem exploradas, este artigo se referiu a checagem de conformidade arquitetural, buscando garantir correto alinhamento ao design de aplicações proposto.

A ferramenta elaborada permite dinamicidade no que diz respeito à apuração da estimativa da dívida em relação às diversas falhas arquiteturais possíveis de se ocorrer. Aumentando-se o seu uso, aumenta-se a amostra de elementos violados e seu tempo de correção, permitindo uma melhor estimativa da dívida.

Referências

LIM, Erin; TAKSANDE, Nitin; SEAMAN, Carolyn. A balancing act: what software practitioners have to say about technical debt. Software, IEEE, v. 29, n. 6, p. 22-27, 2012.

KRUCHTEN, Philippe; NORD, Robert L.; OZKAYA, Ipek. Technical debt: from metaphor to theory and practice. IEEE Software, v. 29, n. 6, p. 18-21, 2012.

ZAZWORKA, Nico et al. Investigating the impact of design debt on software quality. In: Proceedings of the 2nd Workshop on Managing Technical Debt. ACM, 2011. p. 17-23.

SONARQUBE. Technical Debt Evaluation (SQALE).
http://www.sonarsource.com/products/plugins/governance/sqale

SEAMAN, Carolyn; GUO, Yuepu. Measuring and monitoring technical debt. Advances in Computers, v. 82, p. 25-46, 2011.