Fase 4 - Executar a avaliação
Essa avaliação será divida da seguinte forma:
- Avaliado cada métrica de forma unitária de acordo com os critérios
- Depois será avaliado cada questão do GQM
- Por fim, será avaliado o Objetivo da avaliação
Info
Dê zoom com o scroll do mouse no diagrama para ver melhor. Caso prefira, abra em tela cheia.
Você também mode mover o diagrama com o mouse.
graph TD
BussinessObjective[Objetivo de Negócio: Melhorar Manutenibilidade do AGROMART] --> Objective1[Objetivo de Medição 1: Avaliar Qualidade do Código];
Objective1 --> Question1[Q1: Modularidade?]
Question1 --> AvgQtdRowsByFile[M1: Linhas por arquivo]
Question1 --> QtdRowsByFunction[M2: Linhas por função]
Question1 --> Density[M7: Linhas duplicadas]
Question1 --> DuplCode[M8: Blocos de código duplicados]
Question1 --> DuplFiles[M9: Arquivos duplicados]
Question1 --> Complexity[M3: Complexidade Ciclomática por função]
Objective1 --> Question2[Q2: Legibilidade?]
Question2 --> Complexity
Question2 --> documentedModules[M4: Porcentagem de módulos documentados]
Question2 --> CognitiveC[M10: Complexidade cognitiva]
Objective1 --> Question3[Q3: Testabilidade?]
Question3 --> Metric5[M5: Cobertura de testes]
Question3 --> Metric6[M6: Tempo médio de execução dos testes por arquivo]
As métricas foram obtidas da seguinte forma:
- Linhas por arquivo: ESLint
- Linhas por função: ESLint
- Complexidade Ciclomática por função: ESLint e Sonar
- Porcentagem de módulos documentados: Analisamos cada endpoint com a documentação
- Cobertura de testes: Jest
- Tempo médio de execução dos testes por arquivo: Jest
- Porcentagem de linhas de código duplicadas no projeto: Sonar
- Blocos de código duplicados: Sonar
- Arquivos duplicados: Sonar
- Complexidade cognitiva: Sonar
Comparação com critérios
Linhas por arquivo:
Todos os arquivos estavam no estado ✅ Bom
Com essa métrica percebemos que as regras negócios provavelmente estão bem separadas pois não existem arquivos muito grandes, que dificultassem a refatoração.
Essa métrica foi importante para entendermos que o projeto tem uma boa separação pelas regras de negócio.
Talvez essa métrica não seja a melhor para isso, mas pode indicar pontos críticos de baixa coesão do produto.
Linhas por função:
Das 97 funções não vazias, temos:
✅ Bom: 79
⚠️ Aceitável: 13
❌ Crítico: 5
Com isso, é possível reforçar que o projeto tem uma boa modularidade, separando o projeto em pequenos blocos de código que juntos irão fazer sentido.
Apesar de o projeto estar bem modularizado, essa métrica mostra que no geral o produto tem uma boa coesão e que por estar modularizado, é mais fácil testar pequenos módulos.
Porém, ainda existem pontos críticos a serem resolvidos, e funções que podem ser melhoradas.
Complexidade Ciclomática por função:
Das 97 funções não vazias, temos:
✅ Bom: 87
⚠️ Aceitável: 4
❌ Crítico: 6
Essa métrica foi MUITO importante pois mostrou a facilidade de entender e testar cada módulo. Pois quando se tem uma complexidade ciclomática maior, é necessário testar vários e vários casos que dificulta a testabilidade também.
Porcentagem de módulos documentados:
Todos os endpoints tem seus casos de usos documentados. Então o critério está ✅ Bom.
Cobertura de testes:
88.88% das linhas estão cobertas por testes automatizados, fazendo com que esteja no nível
✅ Bom.
Porcentagem de linhas de código duplicadas no projeto:
7,6% de todas as linhas de código do projeto são duplicadas o que indica que este critério é
❌ Crítico.
Blocos de código duplicados:
O número de blocos de código repetidos que foi encontrado no projeto é 19, o que indica que este critério esta
❌ Crítico.
Arquivos duplicados:
O número de arquivos em que foram encontradas duplicações dentro do projeto é 10, o que indica que este critério esta
❌ Crítico.
Complexidade cognitiva:
O valor da complexidade cognitiva é 185, esta é uma medida de quanto esforço mental é necessário para entender o fluxo de controle do código. Este critério está em uma situação
❌ Crítico.
👨⚖️ Julgamento
Q1: Modularidade
A partir das métricas, o grupo concluiu que apesar de existir poucas linhas por função na maioria das funções, o projeto se encontra atualmente com várias repetições de códigos e blocos de código duplicados, tendendo a um provável baixa modularidade.
Além disso, a complexidade cognitiva do código fonte é considerada muito alta para a quantidade de linhas do projeto. Sendo um ponto extremamente crítico.
As métricas de quantidade de linhas, portanto, não foram muito eficazes para essa análise. Mas as métricas de complexidade e duplicação de código demostraram facilmente em o que deveríamos focar.
❌ Não está modular
Q2: Legibilidade
Entendemos que por mais que a complexidade estivesse alta, o projeto estava muito bem documentado. Todos os módulos tem seus casos de uso com diagramas e possuem os critérios de aceitação bem definidos. Portanto, o projeto é facilmente legível com a documentação, porém ao melhorar a complexidade, o projeto seria muito mais fácil de entender para novos integrantes.
⚠️ A legibilidade está aceitável
Q3: Testabilidade?
As métricas mostram uma cobertura de código muito boa, que mostra que o projeto está bem testável e que é fácil testar novos módulos. Além de que os testes são rápidos, demorando cerca de 200ms para cada arquivo, fazendo com que seja viável testar várias vezes o projeto ao longo de sua evolução. O grupo concluiu que o projeto tem ótima testabilidade.
✅ A testabilidade está boa
Melhorias Propostas:
1.1 Redução de complexidade da função isDefaultRoute no arquivo src/middlewares/helpers/defaultRoutes.js
Antes com complexidade 16:
function isDefaultRoute(url) {
if (!url.startsWith('/admin')
&& !url.startsWith('/i18n')
&& !url.startsWith('/content')
&& !url.startsWith('/upload')
&& !url.startsWith('/expo-notifications')
&& !url.startsWith('/plugins')
&& !url.startsWith('/pagamento')
&& !url.startsWith('/users-permissions')
&& !url.startsWith('/auth/google')
&& !url.startsWith('/auth/facebook')
&& !url.startsWith('/auth/github')
&& !url.startsWith('/email')
&& !url.startsWith('/_health')
&& url != '/'
&& url != '' ){
return false;
} else{
return true;
}
}
Para complexidade 3:
const defaultRoutePrefixes = [
'/admin',
'/i18n',
'/content',
'/upload',
'/expo-notifications',
'/plugins',
'/pagamento',
'/users-permissions',
'/auth/google',
'/auth/facebook',
'/auth/github',
'/email',
'/_health',
];
function isDefaultRoute(url) {
return defaultRoutePrefixes.some((prefix) => url.startsWith(prefix))
|| url === '/'
|| url === '';
}
1.2 Redução de complexidade da função subistituirValores no arquivo src/plugins/pagamento/server/utils/gateways/padrao.js
Antes, com complexido 11 e 53 linhas:
function substituirValores(inputJSON, dado) {
let outputJSON = {};
// entra em um loop para cada chave do json
for (const key in inputJSON) {
if (inputJSON.hasOwnProperty(key)) {
const value = inputJSON[key];
if (typeof value === 'string') {
if (extractValue(value)) {
// valor dentro de um extract value
if (value === '${valorTotal}') {
outputJSON[key] = getTotal(dado.extrato);
} else if (value === '${dataHoje}') {
outputJSON[key] = getDueDate();
} else {
outputJSON[key] = getValueFromObjectByPath(
extractValue(value),
dado
);
}
} else {
// valor fixo
outputJSON[key] = value;
}
} else if (
(typeof value === 'number' && Number.isFinite(value)) ||
typeof value === 'boolean'
) {
outputJSON[key] = value;
} else if (Array.isArray(value)) {
outputJSON[key] = [];
// se for um array vai ser um array de items
dado.extrato.itens.forEach((item, index) => {
let newItem = {};
for (const kv in value[0]) {
const valueWithIndex = value[0][kv].replace(/index/g, index);
newItem[kv] = getValueFromObjectByPath(
extractValue(valueWithIndex),
dado
);
if (newItem[kv] == null) {
newItem[kv] = index;
}
}
outputJSON[key].push(newItem);
});
} else {
// se não for nem string nem array vai chamar de novo
outputJSON[key] = substituirValores(value, dado);
}
}
}
return outputJSON;
}
Agora com complexidade 3 utilizando Strategy:
const subistituirValoresStrategy = {
'string': ({value, outputJSON, key, dado}) => {
if (extractValue(value)) {
// valor dentro de um extract value
if (value === '${valorTotal}') {
outputJSON[key] = getTotal(dado.extrato);
} else if (value === '${dataHoje}') {
outputJSON[key] = getDueDate();
} else {
outputJSON[key] = getValueFromObjectByPath(
extractValue(value),
dado
);
}
} else {
// valor fixo
outputJSON[key] = value;
}
},
'number': ({value, outputJSON, key}) => {
if(Number.isFinite(value)) {
outputJSON[key] = value;
}
},
'boolean': ({value, outputJSON, key}) => {
outputJSON[key] = value;
},
'object': ({value, outputJSON, key, dado}) => {
if (Array.isArray(value)) {
outputJSON[key] = [];
// se for um array vai ser um array de items
dado.extrato.itens.forEach((item, index) => {
let newItem = {};
for (const kv in value[0]) {
const valueWithIndex = value[0][kv].replace(/index/g, index);
newItem[kv] = getValueFromObjectByPath(
extractValue(valueWithIndex),
dado
);
if (newItem[kv] === null) {
newItem[kv] = index;
}
}
outputJSON[key].push(newItem);
});
} else {
// se não for nem string nem array vai chamar de novo
outputJSON[key] = substituirValores(value, dado);
}
},
};
function substituirValores(inputJSON, dado) {
let outputJSON = {};
// entra em um loop para cada chave do json
for (const key in inputJSON) {
if (inputJSON.hasOwnProperty(key)) {
const value = inputJSON[key];
const strategy = typeof value;
subistituirValoresStrategy[strategy]?.({value, outputJSON, key, dado});
}
}
return outputJSON;
}
1.3 Redução de complexidade da função parseJSONRecursively no arquivo src/plugins/pagamento/server/utils/gateways/padrao.js
Antes, com complexidade 5:
function parseJSONRecursively(jsonString) {
const jsonObject = JSON.parse(jsonString);
const parseNestedJSON = (obj) => {
for (const key in obj) {
if (typeof obj[key] === 'string') {
try {
obj[key] = parseJSONRecursively(obj[key]);
} catch (error) {
// Ignorar a string que não é um JSON válido
}
} else if (typeof obj[key] === 'object') {
parseNestedJSON(obj[key]);
}
}
}
parseNestedJSON(jsonObject);
return jsonObject;
}
Para complexidade 2 com uso de Strategy:
const jsonParserStrategy = {
'string': (obj, key) => {
try {
obj[key] = parseJSONRecursively(obj[key]);
} catch {
// Ignorar a string que não é um JSON válido
}
},
'object': (obj, key) => {
parseNestedJSON(obj[key]);
}
};
function parseNestedJSON(obj) {
Object.keys(obj).forEach((key) => {
const strategy = typeof obj[key];
jsonParserStrategy[strategy]?.(obj, key);
});
}
function parseJSONRecursively(jsonString) {
const jsonObject = JSON.parse(jsonString);
parseNestedJSON(jsonObject);
return jsonObject;
}
1.4 Redução de complexidade da função getValueFromObjectByPath no arquivo src/plugins/pagamento/server/utils/gateways/padrao.js
Antes, com complexidade 4:
function getValueFromObjectByPath(path, obj) {
const paths = path.split('||');
let value = obj;
let find = false;
for (const p of paths) {
const keys = p.split('.');
let value = obj;
if (find == false) {
for (const key of keys) {
// Verifica se a chave contém um índice
if (/\d+/.test(key)) {
const index = parseInt(key, 10);
value = Array.isArray(value) ? value[index] : undefined;
} else {
value = value[key];
}
if (value === undefined) {
break;
}
}
}
if (value === undefined) {
find = false;
} else {
return value;
}
}
}
Para complexidade 2:
function getValueFromObjectByPath(path, obj) {
const paths = path.split('||');
let value = undefined;
paths.some((path) => {
return path.split('.').reduce((acc, key) => {
if (/\d+/.test(key)) {
const index = parseInt(key, 10);
value = Array.isArray(acc) ? acc[index] : undefined;
return value;
}
value = acc[key];
return value;
}, obj);
});
return value;
}
1.5 A configuração do ESLint foi consertada e foram adicionadas regras para impedir que a complexidade do projeto suba de forma descontrolada. Essas regras reforçam o controle das métricas antes mesmo desse código subir ao repositório, pois será indicado em tempo de desenvolvimento.
Foram adicionadas as seguintes regras:
- complexity: 3
- max-depth: 3
- max-lines: 200
- max-lines-per-function: 30
- max-params: 2
Também foi adicionado o plugin do sonar, para que apontamentos do sonar e indicações sejam identificadas o quanto antes possível.