Pular para o conteúdo

17. Manutenção e Evolução - Parte 2: Refatoração, Modernização e Monitoramento de Desempenho

Continuação da Parte 1: Estratégias de Versionamento e Gerenciamento de Dependências

Refatoração e Modernização de Código

Identificação de Código Legacy

Antes de iniciar qualquer refatoração, identifique áreas problemáticas:

scripts/code-quality-check.js
import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';
import glob from 'glob';
// Configurações
const SRC_DIR = 'src';
const COMPLEXITY_THRESHOLD = 15;
const MAX_FILE_SIZE = 500; // linhas
const MAX_FUNCTION_LENGTH = 50; // linhas
// Encontrar arquivos grandes
function findLargeFiles() {
const files = glob.sync(`${SRC_DIR}/**/*.{js,ts,astro}`, { nodir: true });
return files
.map(file => {
const content = fs.readFileSync(file, 'utf-8');
const lines = content.split('\n').length;
return { file, lines };
})
.filter(({ lines }) => lines > MAX_FILE_SIZE)
.sort((a, b) => b.lines - a.lines);
}
// Encontrar funções complexas (requer ESLint com plugin de complexidade)
function findComplexFunctions() {
try {
const eslintOutput = execSync(
`npx eslint ${SRC_DIR} --no-eslintrc --config .eslintrc.complexity.js --format json`,
{ stdio: ['pipe', 'pipe', 'ignore'] }
).toString();
const results = JSON.parse(eslintOutput);
const complexityIssues = [];
results.forEach(result => {
result.messages
.filter(msg => msg.ruleId === 'complexity')
.forEach(msg => {
complexityIssues.push({
file: result.filePath,
line: msg.line,
column: msg.column,
function: msg.message.match(/^Function '(.+)' has/)?.[1] || 'Anonymous',
complexity: parseInt(msg.message.match(/complexity of (\d+)\./)?.[1] || '0')
});
});
});
return complexityIssues
.filter(issue => issue.complexity > COMPLEXITY_THRESHOLD)
.sort((a, b) => b.complexity - a.complexity);
} catch (error) {
console.error('Erro ao executar ESLint:', error.message);
return [];
}
}
// Encontrar funções longas
function findLongFunctions() {
// Esta é uma implementação simplificada
// Uma análise mais precisa requer um parser de AST como Babel ou TypeScript
const files = glob.sync(`${SRC_DIR}/**/*.{js,ts}`, { nodir: true });
const longFunctions = [];
files.forEach(file => {
const content = fs.readFileSync(file, 'utf-8');
const lines = content.split('\n');
let inFunction = false;
let functionStart = 0;
let bracketCount = 0;
let currentFunction = '';
lines.forEach((line, index) => {
// Detecção simplificada de funções
if (!inFunction && /function\s+(\w+)/.test(line)) {
inFunction = true;
functionStart = index;
currentFunction = line.match(/function\s+(\w+)/)[1];
bracketCount += (line.match(/{/g) || []).length;
bracketCount -= (line.match(/}/g) || []).length;
} else if (!inFunction && /(\w+)\s*=\s*function/.test(line)) {
inFunction = true;
functionStart = index;
currentFunction = line.match(/(\w+)\s*=/)[1];
bracketCount += (line.match(/{/g) || []).length;
bracketCount -= (line.match(/}/g) || []).length;
} else if (!inFunction && /(\w+)\s*[:=]\s*\([^)]*\)\s*=>/.test(line)) {
inFunction = true;
functionStart = index;
currentFunction = line.match(/(\w+)\s*[:=]/)[1];
bracketCount += (line.match(/{/g) || []).length;
bracketCount -= (line.match(/}/g) || []).length;
} else if (inFunction) {
bracketCount += (line.match(/{/g) || []).length;
bracketCount -= (line.match(/}/g) || []).length;
if (bracketCount === 0) {
const length = index - functionStart + 1;
if (length > MAX_FUNCTION_LENGTH) {
longFunctions.push({
file,
function: currentFunction,
startLine: functionStart + 1,
endLine: index + 1,
length
});
}
inFunction = false;
}
}
});
});
return longFunctions.sort((a, b) => b.length - a.length);
}
// Gerar relatório
function generateReport() {
console.log('🔍 Analisando código...\n');
const largeFiles = findLargeFiles();
const complexFunctions = findComplexFunctions();
const longFunctions = findLongFunctions();
console.log(`📊 Relatório de Qualidade de Código (${new Date().toLocaleDateString()})\n`);
if (largeFiles.length > 0) {
console.log(`\n📄 Arquivos Grandes (> ${MAX_FILE_SIZE} linhas): ${largeFiles.length}`);
largeFiles.slice(0, 10).forEach(({ file, lines }) => {
console.log(`- ${file}: ${lines} linhas`);
});
if (largeFiles.length > 10) {
console.log(`... e mais ${largeFiles.length - 10} arquivos`);
}
} else {
console.log('✅ Nenhum arquivo grande encontrado.');
}
if (complexFunctions.length > 0) {
console.log(`\n🧩 Funções Complexas (> ${COMPLEXITY_THRESHOLD} complexidade ciclomática): ${complexFunctions.length}`);
complexFunctions.slice(0, 10).forEach(({ file, function: fn, line, complexity }) => {
console.log(`- ${path.relative('.', file)}:${line} - ${fn}: complexidade ${complexity}`);
});
if (complexFunctions.length > 10) {
console.log(`... e mais ${complexFunctions.length - 10} funções`);
}
} else {
console.log('✅ Nenhuma função complexa encontrada.');
}
if (longFunctions.length > 0) {
console.log(`\n📏 Funções Longas (> ${MAX_FUNCTION_LENGTH} linhas): ${longFunctions.length}`);
longFunctions.slice(0, 10).forEach(({ file, function: fn, startLine, endLine, length }) => {
console.log(`- ${path.relative('.', file)}:${startLine}-${endLine} - ${fn}: ${length} linhas`);
});
if (longFunctions.length > 10) {
console.log(`... e mais ${longFunctions.length - 10} funções`);
}
} else {
console.log('✅ Nenhuma função longa encontrada.');
}
// Salvar relatório detalhado
const report = {
timestamp: new Date().toISOString(),
summary: {
largeFiles: largeFiles.length,
complexFunctions: complexFunctions.length,
longFunctions: longFunctions.length
},
details: {
largeFiles,
complexFunctions,
longFunctions
}
};
fs.writeFileSync(
'code-quality-report.json',
JSON.stringify(report, null, 2)
);
console.log('\nRelatório detalhado salvo em code-quality-report.json');
// Retornar código de erro se houver problemas significativos
if (
largeFiles.length > 10 ||
complexFunctions.length > 20 ||
longFunctions.length > 20
) {
console.log('\n⚠️ Problemas significativos de qualidade de código detectados.');
return 1;
}
return 0;
}
process.exit(generateReport());

Estratégias de Refatoração

Refatoração Incremental

Divida a refatoração em etapas gerenciáveis:

  1. Identificar áreas problemáticas com métricas objetivas
  2. Priorizar baseado em risco, complexidade e valor de negócio
  3. Planejar mudanças incrementais com testes adequados
  4. Implementar em pequenos commits verificáveis
  5. Validar através de testes automatizados e revisão de código

Exemplo: Extrair Componente

---
// Antes: Componente grande e monolítico
---
<div class="product-card">
<div class="product-image">
<img src={product.image} alt={product.name} />
{product.isNew && <span class="badge new">Novo</span>}
{product.discount > 0 && <span class="badge discount">{product.discount}% OFF</span>}
</div>
<div class="product-info">
<h3>{product.name}</h3>
<div class="product-rating">
{Array.from({ length: 5 }).map((_, i) => (
<span class={i < product.rating ? "star filled" : "star"}></span>
))}
<span class="rating-count">({product.reviewCount})</span>
</div>
<div class="product-price">
{product.discount > 0 && (
<span class="original-price">{formatCurrency(product.originalPrice)}</span>
)}
<span class="current-price">{formatCurrency(product.price)}</span>
</div>
<div class="product-actions">
<button class="add-to-cart" onclick={`addToCart(${product.id})`}>
Adicionar ao Carrinho
</button>
<button class="add-to-wishlist" onclick={`addToWishlist(${product.id})`}>
</button>
</div>
</div>
</div>
src/components/product/ProductCard.astro
---
// Depois: Componentes menores e reutilizáveis
import ProductBadges from './ProductBadges.astro';
import ProductRating from './ProductRating.astro';
import ProductPrice from './ProductPrice.astro';
import ProductActions from './ProductActions.astro';
const { product } = Astro.props;
---
<div class="product-card">
<div class="product-image">
<img src={product.image} alt={product.name} />
<ProductBadges isNew={product.isNew} discount={product.discount} />
</div>
<div class="product-info">
<h3>{product.name}</h3>
<ProductRating rating={product.rating} reviewCount={product.reviewCount} />
<ProductPrice price={product.price} originalPrice={product.originalPrice} discount={product.discount} />
<ProductActions productId={product.id} />
</div>
</div>
src/components/product/ProductBadges.astro
---
const { isNew, discount } = Astro.props;
---
{isNew && <span class="badge new">Novo</span>}
{discount > 0 && <span class="badge discount">{discount}% OFF</span>}
src/components/product/ProductRating.astro
---
const { rating, reviewCount } = Astro.props;
---
<div class="product-rating">
{Array.from({ length: 5 }).map((_, i) => (
<span class={i < rating ? "star filled" : "star"}></span>
))}
<span class="rating-count">({reviewCount})</span>
</div>

Modernização Progressiva

Migração para Recursos Modernos

Atualize progressivamente para recursos modernos do JavaScript:

// Antes: Código legado
var products = [];
function getProducts() {
return $.ajax({
url: '/api/products',
method: 'GET',
success: function(data) {
products = data;
renderProducts();
},
error: function(error) {
console.error('Erro ao carregar produtos:', error);
}
});
}
function renderProducts() {
var container = document.getElementById('products-container');
container.innerHTML = '';
for (var i = 0; i < products.length; i++) {
var product = products[i];
var element = document.createElement('div');
element.className = 'product';
element.innerHTML = '<h3>' + product.name + '</h3><p>' + product.price + '</p>';
container.appendChild(element);
}
}
// Depois: Código moderno
let products = [];
async function getProducts() {
try {
const response = await fetch('/api/products');
if (!response.ok) {
throw new Error(`Erro HTTP: ${response.status}`);
}
products = await response.json();
renderProducts();
} catch (error) {
console.error('Erro ao carregar produtos:', error);
}
}
function renderProducts() {
const container = document.getElementById('products-container');
container.innerHTML = '';
products.forEach(product => {
const element = document.createElement('div');
element.className = 'product';
element.innerHTML = `<h3>${product.name}</h3><p>${product.price}</p>`;
container.appendChild(element);
});
}

Migração para TypeScript

Adicione tipagem gradualmente:

  1. Configure o TypeScript no projeto:
Terminal window
npm install --save-dev typescript @types/node
  1. Crie um arquivo tsconfig.json:
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"allowJs": true,
"checkJs": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
  1. Migre arquivos JS para TS incrementalmente:
utils/product.ts
interface Product {
id: number;
name: string;
price: number;
originalPrice?: number;
discount: number;
rating: number;
reviewCount: number;
isNew: boolean;
image: string;
}
export function calculateDiscount(product: Product): number {
if (!product.originalPrice || product.originalPrice <= product.price) {
return 0;
}
return Math.round(
((product.originalPrice - product.price) / product.originalPrice) * 100
);
}
export function formatCurrency(amount: number): string {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL'
}).format(amount);
}

Monitoramento de Desempenho

Métricas Web Vitals

Implemente monitoramento de Web Vitals para acompanhar o desempenho do site:

src/components/WebVitalsTracker.astro
<script>
// Importar a biblioteca web-vitals
import('https://unpkg.com/web-vitals@3/dist/web-vitals.iife.js').then(({ onCLS, onFID, onLCP, onTTFB, onINP }) => {
function sendToAnalytics({ name, delta, value, id }) {
// Enviar para seu sistema de analytics
// Exemplo com Google Analytics 4
if (window.gtag) {
gtag('event', name, {
value: Math.round(name === 'CLS' ? delta * 1000 : delta),
metric_id: id,
metric_value: value,
metric_delta: delta,
});
}
// Registrar no console durante desenvolvimento
if (import.meta.env.DEV) {
console.log(`Web Vital: ${name}`, { value, delta, id });
}
// Opcional: enviar para um endpoint personalizado
if (navigator.sendBeacon) {
const payload = {
name,
value: Math.round(value * 100) / 100,
delta: Math.round(delta * 100) / 100,
id,
page: window.location.pathname,
timestamp: Date.now()
};
navigator.sendBeacon('/api/vitals', JSON.stringify(payload));
}
}
// Monitorar métricas principais
onCLS(sendToAnalytics); // Cumulative Layout Shift
onFID(sendToAnalytics); // First Input Delay
onLCP(sendToAnalytics); // Largest Contentful Paint
onTTFB(sendToAnalytics); // Time to First Byte
onINP(sendToAnalytics); // Interaction to Next Paint
});
</script>

Monitoramento de Erros

Implemente um sistema de rastreamento de erros:

src/components/ErrorTracker.astro
---
const isProd = import.meta.env.PROD;
---
<script define:vars={{ isProd }}>
class ErrorTracker {
constructor() {
this.errors = [];
this.maxErrors = 10;
this.initialize();
}
initialize() {
// Capturar erros não tratados
window.addEventListener('error', (event) => {
this.captureError({
type: 'uncaught',
message: event.message,
stack: event.error?.stack,
source: event.filename,
line: event.lineno,
column: event.colno,
timestamp: Date.now()
});
// Não impedir o comportamento padrão
return false;
});
// Capturar rejeições de promessas não tratadas
window.addEventListener('unhandledrejection', (event) => {
this.captureError({
type: 'unhandledrejection',
message: event.reason?.message || 'Unhandled Promise Rejection',
stack: event.reason?.stack,
timestamp: Date.now()
});
});
// Sobrescrever console.error
const originalConsoleError = console.error;
console.error = (...args) => {
this.captureError({
type: 'console.error',
message: args.map(arg =>
typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
).join(' '),
timestamp: Date.now()
});
originalConsoleError.apply(console, args);
};
}
captureError(error) {
// Adicionar erro à lista
this.errors.push(error);
// Limitar número de erros armazenados
if (this.errors.length > this.maxErrors) {
this.errors.shift();
}
// Em produção, enviar para servidor
if (isProd) {
this.sendToServer(error);
} else {
console.warn('Error tracked (DEV):', error);
}
}
sendToServer(error) {
// Adicionar informações do usuário e contexto
const payload = {
...error,
url: window.location.href,
userAgent: navigator.userAgent,
viewport: {
width: window.innerWidth,
height: window.innerHeight
}
};
// Enviar para o servidor usando Beacon API (não bloqueia navegação)
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/errors', JSON.stringify(payload));
} else {
// Fallback para fetch
fetch('/api/errors', {
method: 'POST',
body: JSON.stringify(payload),
headers: {
'Content-Type': 'application/json'
},
// Usar keepalive para garantir que a requisição seja enviada
keepalive: true
}).catch(e => {
// Silenciar erros do fetch para evitar loops
});
}
}
getErrors() {
return [...this.errors];
}
}
// Inicializar e expor globalmente
window.errorTracker = new ErrorTracker();
</script>

Endpoint para Receber Métricas

src/pages/api/vitals.js
import { createClient } from '@supabase/supabase-js';
export async function post({ request }) {
try {
const payload = await request.json();
// Validar payload
if (!payload || !payload.name) {
return new Response(JSON.stringify({ error: 'Invalid payload' }), {
status: 400,
headers: {
'Content-Type': 'application/json'
}
});
}
// Conectar ao Supabase
const supabaseUrl = import.meta.env.SUPABASE_URL;
const supabaseKey = import.meta.env.SUPABASE_SERVICE_KEY;
const supabase = createClient(supabaseUrl, supabaseKey);
// Salvar métrica
const { error } = await supabase
.from('web_vitals')
.insert([{
metric_name: payload.name,
metric_value: payload.value,
metric_delta: payload.delta,
page_url: payload.page,
timestamp: new Date().toISOString()
}]);
if (error) {
console.error('Error saving web vital:', error);
return new Response(JSON.stringify({ error: 'Database error' }), {
status: 500,
headers: {
'Content-Type': 'application/json'
}
});
}
return new Response(JSON.stringify({ success: true }), {
status: 200,
headers: {
'Content-Type': 'application/json'
}
});
} catch (error) {
console.error('Error processing web vital:', error);
return new Response(JSON.stringify({ error: 'Server error' }), {
status: 500,
headers: {
'Content-Type': 'application/json'
}
});
}
}

Continua na Parte 3: Atualizações de Segurança, Estratégias de Migração e Documentação Técnica