Aikido

Como manter funções concisas: escrevendo código de fácil manutenção

Legibilidade

Regra

Manter funções concisa.
Funções funções longas são difíceis de compreender, testar, e manter.

Idiomas suportados: 45+

Introdução

Funções que se estendem por centenas de linhas misturam múltiplas responsabilidades, tornando difícil entender o que a função faz sem ler cada linha. Funções longas geralmente lidam com múltiplas preocupações, como validação, lógica de negócios, transformação de dados e tratamento de erros, tudo em um só lugar. Isso viola o princípio da responsabilidade única e cria um código difícil de testar, depurar e modificar sem quebrar o comportamento existente.

Por que isso importa

Manutenibilidade do código: Funções longas exigem que os desenvolvedores mantenham mais contexto em suas mentes para entender o comportamento. Modificar uma parte corre o risco de quebrar outra porque toda a lógica está interligada. Correções de bugs se tornam arriscadas, pois efeitos colaterais não intencionais são difíceis de prever.

Complexidade de teste: Testar uma função de 200 linhas significa cobrir todos os possíveis caminhos de código em um único teste, exigindo uma configuração complexa e inúmeros casos de teste. Funções menores podem ser testadas independentemente com testes unitários focados, tornando as suítes de teste mais rápidas e confiáveis.

Exemplos de código

❌ Não-conforme:

async function processOrder(orderData) {
    if (!orderData.items?.length) throw new Error('Items required');
    if (!orderData.customer?.email) throw new Error('Email required');
    const subtotal = orderData.items.reduce((sum, item) => 
        sum + (item.price * item.quantity), 0);
    const tax = subtotal * 0.08;
    const total = subtotal + tax + (subtotal > 50 ? 0 : 9.99);
    const order = await db.orders.create({
        customerId: orderData.customer.id,
        total: total
    });
    await emailService.send(orderData.customer.email, `Order #${order.id}`);
    await inventory.reserve(orderData.items);
    return order;
}

Por que está errado: Esta função lida com validação, cálculo, operações de banco de dados, e-mail e inventário. Testar exige a simulação de todas as dependências. Qualquer alteração na lógica de impostos ou validação requer a modificação de toda esta função.

✅ Compatível:

function validateOrder(orderData) {
    if (!orderData.items?.length) throw new Error('Items required');
    if (!orderData.customer?.email) throw new Error('Email required');
}

function calculateTotal(items) {
    const subtotal = items.reduce((sum, item) => 
        sum + (item.price * item.quantity), 0);
    return subtotal + (subtotal * 0.08) + (subtotal > 50 ? 0 : 9.99);
}

async function createOrder(customerId, total) {
    return await db.orders.create({ customerId, total });
}

async function processOrder(orderData) {
    validateOrder(orderData);
    const total = calculateTotal(orderData.items);
    const order = await createOrder(orderData.customer.id, total);
    
    // Non-critical operations in background
    emailService.send(orderData.customer.email, `Order #${order.id}`).catch(console.error);
    
    return order;
}

Por que isso importa: Cada função tem uma responsabilidade clara. validateOrder() e calculateTotal() pode ser testado independentemente sem mocks. createOrder() isola a lógica do banco de dados. Operações de e-mail e inventário não bloqueiam a criação de pedidos, e as falhas são tratadas separadamente.

Conclusão

Evolua APIs através de mudanças aditivas: adicione novos campos, adicione novos endpoints, adicione parâmetros opcionais. Quando mudanças disruptivas forem inevitáveis, use o versionamento de API para executar versões antigas e novas simultaneamente. Deprecie campos antigos com cronogramas claros e guias de migração antes de removê-los.

FAQs

Dúvidas?

Como dividir funções longas?

Identifique responsabilidades distintas dentro da função. Extraia a validação para funções separadas. Mova cálculos para funções puras. Mova operações de I/O (banco de dados, chamadas de API) para suas próprias funções. Cada função extraída deve ter um propósito único e claro com um nome descritivo.

Funções pequenas não adicionam overhead e prejudicam o desempenho?

Compiladores e interpretadores modernos fazem o inlining de funções pequenas, eliminando a sobrecarga de chamadas. O impacto no desempenho é insignificante em comparação com os benefícios de manutenibilidade. Faça o profiling antes de otimizar. Código legível é mais fácil de otimizar posteriormente, quando você identifica gargalos reais.

E quanto às funções com muitos passos sequenciais?

Etapas sequenciais sugerem um fluxo de trabalho que pode ser dividido em funções menores. Crie funções auxiliares para cada etapa e chame-as em sequência a partir de uma função coordenadora. Isso torna o fluxo de trabalho legível e cada etapa testável de forma independente.

Como lidar com funções que exigem muitos parâmetros após a extração?

Passe objetos contendo parâmetros relacionados em vez de longas listas de parâmetros. Ou considere se as funções extraídas deveriam ser métodos de uma classe que mantém um estado compartilhado. Se uma função precisa de mais de 6 parâmetros, isso pode indicar uma abstração deficiente ou estruturas de dados ausentes.

Devo extrair funções mesmo que sejam chamadas apenas uma vez?

Sim, se a extração melhora a legibilidade. Uma função extraída bem nomeada documenta o que um bloco de código faz melhor do que comentários. A extração pontual é valiosa quando esclarece lógica complexa ou reduz níveis de aninhamento na função pai.

Fique seguro agora

Proteja seu código, Cloud e runtime em um único sistema centralizado.
Encontre e corrija vulnerabilidades rapidamente de forma automática.

Não é necessário cartão de crédito | Resultados da varredura em 32 segundos.