Functions
A função é a menor peça de código que você reaproveita e testa sozinha. Ela vale mais quando faz uma coisa só, tem uma assinatura clara (o nome e os parâmetros que recebe) e cabe na tela. Quando uma mesma função busca, valida, calcula e salva tudo junto, ela vira um nó que ninguém consegue reaproveitar nem testar em separado, porque cada tarefa fica presa às outras.
Conceitos fundamentais
| Conceito | O que é |
|---|---|
| single responsibility (responsabilidade única) | Função tem uma razão para mudar; um motivo para existir |
| arrow function (função flecha) | () => {}: sintaxe curta sem this próprio; ideal para callbacks |
| named function (função nomeada) | function name() {}: aparece com nome no stack trace; ideal para topo de módulo |
| parameter (parâmetro) | Nome declarado na assinatura; recebe o argumento na chamada |
| default parameter (parâmetro padrão) | Valor usado quando o argumento é undefined (function f(x = 0)) |
| rest parameter (parâmetro variádico) | ...args: coleta argumentos restantes em array |
| pure function (função pura) | Mesma entrada → mesma saída; sem efeito colateral observável |
| side effect (efeito colateral) | Leitura ou escrita fora dos argumentos: I/O, mutação externa, log |
| helper (função auxiliar) | Função de apoio que implementa um passo do orquestrador; dá nome ao detalhe |
| SLA (Single Level of Abstraction · Único Nível de Abstração) | Cada função opera em um só nível: orquestra passos ou implementa detalhe, nunca os dois |
Função que faz tudo: várias responsabilidades
❌ Ruim: busca, valida, calcula, persiste e loga na mesma função
realizaVenda(123);
function realizaVenda(x) {
let resultado;
let p = buscaPedido(x);
if (p != null) {
if (p.itens && p.itens.length > 0) {
if (!p.c.inadimplente) {
if (p.total > 100) {
p.desconto = 10;
} else {
p.desconto = 0;
}
apply(p);
function apply(p) {
if (p.desconto) p.total = p.total - p.desconto;
}
let salvo = salvaPedido(p);
resultado = salvo ? salvo : null;
if (Math.random() > 0.5) {
console.log("Log qualquer");
}
} else {
notify(p);
resultado = false;
function notify(p) {
console.log("cliente inadimplente", p?.cliente?.nome);
}
}
} else {
resultado = undefined;
}
} else {
resultado = null;
}
return resultado;
}
✅ Bom: a função principal no topo, responsabilidades separadas
await processOrder(123);
async function processOrder(orderId) {
const order = await getOrder(orderId);
if (isInvalid(order)) {
notifyRejection(order);
return;
}
const invoice = await issueInvoice(order);
return invoice;
function isInvalid(order) {
if (!order || order.items.length === 0) return true;
if (order.customer.defaulted) return true;
return false;
}
function notifyRejection(order) {
console.log("pedido rejeitado", order?.customer?.name);
}
async function issueInvoice(order) {
const discountedOrder = applyDiscount(order);
const invoice = await saveOrder(discountedOrder);
return invoice;
}
}
Funções auxiliares aninhadas: quando separar
Os exemplos deste guia declaram funções auxiliares (as que executam um passo do trabalho) de uso único dentro da função principal, aquela que organiza os passos. Colocar a auxiliar ali dentro mantém a história em um bloco só: o leitor vê quem chama no topo e o detalhe logo abaixo, sem sair do contexto. Esse formato tem um custo, e o custo define até onde ele vale:
- Teste em separado: a função aninhada não pode ser exportada, então só é testada através da função principal que a contém (estratégias em testing).
- Reaproveitamento: uma segunda função que precise da auxiliar não alcança ela, presa dentro de outra.
- Recriação: cada chamada da função principal cria de novo as funções internas. Cada uma delas é uma closure (função que guarda o acesso às variáveis do lugar onde foi escrita). Isso não pesa na maioria dos casos, mas dá para medir em um trecho executado com muita frequência, o chamado hot path (medição em performance).
Regra prática: a função auxiliar nasce aninhada. Ela sobe para o nível do arquivo quando um segundo lugar passa a usá-la, quando precisa de teste próprio ou quando a função principal cresce além de uma tela. Em linguagens com classes (Java, C#), o mesmo padrão vira métodos privados: colocados logo abaixo do método público, na ordem em que são chamados.
❌ Ruim: a função auxiliar, usada em dois lugares, copiada dentro de cada um
function buildInvoiceEmail(invoice) {
const totalLabel = formatCurrency(invoice.total);
const email = `Total: ${totalLabel}`;
return email;
function formatCurrency(amount) {
const formatted = amount.toLocaleString("pt-BR", {
style: "currency",
currency: "BRL",
});
return formatted;
}
}
function buildReceiptPdf(receipt) {
// formatCurrency duplicado: preso em buildInvoiceEmail, este uso não o alcança
const totalLabel = formatCurrency(receipt.total);
const receiptPdf = renderPdf(totalLabel);
return receiptPdf;
function formatCurrency(amount) {
const formatted = amount.toLocaleString("pt-BR", {
style: "currency",
currency: "BRL",
});
return formatted;
}
}
✅ Bom: o segundo uso leva a função auxiliar para o nível do arquivo
function buildInvoiceEmail(invoice) {
const totalLabel = formatCurrency(invoice.total);
const email = `Total: ${totalLabel}`;
return email;
}
function buildReceiptPdf(receipt) {
const totalLabel = formatCurrency(receipt.total);
const receiptPdf = renderPdf(totalLabel);
return receiptPdf;
}
function formatCurrency(amount) {
const formatted = amount.toLocaleString("pt-BR", {
style: "currency",
currency: "BRL",
});
return formatted;
}
Um nível de abstração por função
O princípio que orienta essa divisão é manter um único nível de abstração por função (na sigla em inglês, SLA, de Single Level of Abstraction). Ou a função organiza passos com nome, ou implementa um detalhe. Quando faz as duas coisas ao mesmo tempo, a leitura pula da visão geral para o detalhe miúdo sem aviso, e quem lê se perde.
❌ Ruim: a mesma função organiza e implementa
function buildOrderSummary(order) {
const header = `Order #${order.id}`;
// orquestra E implementa ao mesmo tempo
const lineItems = order.items
.map((item) => ` - ${item.name}: $${item.price.toFixed(2)}`)
.join("\n");
return `${header}\n${lineItems}`;
}
✅ Bom: a função principal chama auxiliares, cada uma faz uma coisa
function buildOrderSummary(order) {
const header = buildHeader(order);
const lineItems = buildLineItems(order);
const summary = [header, lineItems].join("\n");
return summary;
function buildHeader(order) {
const header = `Order #${order.id}`;
return header;
}
function buildLineItems(order) {
const lines = order.items.map((item) => ` - ${item.name}: $${item.price.toFixed(2)}`);
const lineItems = lines.join("\n");
return lineItems;
}
}
Separar cálculo de formatação
❌ Ruim: cálculo e formatação misturados
function getOrderSummary(order) {
const subtotal = order.items.reduce((sum, item) => sum + item.price, 0);
const tax = subtotal * 0.1;
const total = subtotal + tax;
return `Order #${order.id}: $${subtotal.toFixed(2)} + tax $${tax.toFixed(2)} = $${total.toFixed(2)}`;
}
✅ Bom: cálculo separado da formatação
function getOrderSummary(order) {
const totals = calculateTotals(order.items);
const summary = formatSummary(order.id, totals);
return summary;
function calculateTotals(items) {
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
const tax = subtotal * 0.1;
const totals = { subtotal, tax, total: subtotal + tax };
return totals;
}
function formatSummary(orderId, totals) {
const { subtotal, tax, total } = totals;
const summary = `Order #${orderId}: $${subtotal.toFixed(2)} + tax $${tax.toFixed(2)} = $${total.toFixed(2)}`;
return summary;
}
}
Retorno direto
O retorno aparece no topo da função; os detalhes ficam em funções auxiliares logo abaixo.
❌ Ruim: variável auxiliar desnecessária, `else` depois de um `throw`
async function findProductById(id) {
let productFound = null;
const results = await db.query(id);
if (results.rowCount === 0) {
throw new NotFoundError("Product not found.");
} else {
productFound = results.rows[0];
}
return productFound;
}
✅ Bom: intenção clara no topo, detalhe abaixo
async function findProductById(id) {
const product = await fetchProduct(id);
return product;
async function fetchProduct(id) {
const product = await productRepository.findById(id);
if (!product) throw new NotFoundError("Product not found.");
return product;
}
}
Ponto de entrada limpo
A chamada anuncia o que acontece, em uma linha. Toda a montagem do contexto acontece dentro da função.
❌ Ruim: quem chama monta a lógica na mão antes de chamar
await submitOrder({
...order,
total: order.items.reduce((sum, item) => sum + item.price, 0) * (1 - getDiscount(user)),
timestamp: new Date().toISOString(),
});
✅ Bom: entrada de uma linha, detalhes dentro
await submitOrder(orderId);
async function submitOrder(orderId) {
const order = await fetchOrder(orderId);
const pricedOrder = applyPricing(order);
const invoice = await persistOrder(pricedOrder);
return invoice;
}
Sem lógica no retorno
O return devolve o valor que a linha anterior já calculou e nomeou. A variável
carrega um nome expressivo, alinhado com o que a função promete entregar.
A regra vale até para uma função auxiliar de duas linhas, por um motivo prático: a variável com nome te dá onde parar o depurador (o breakpoint, a pausa na execução para inspecionar o valor) já com o valor pronto, aparece limpa no diff quando o cálculo muda e obriga quem escreve a batizar o que a função entrega. O custo é uma linha a mais por função. O guia aceita esse custo de propósito: uma regra mecânica se revisa melhor do que uma exceção decidida caso a caso.
❌ Ruim: lógica ou objeto sem nome direto no `return`
function buildGreeting(user) {
return `Hello, ${user.name}! You have ${user.notifications.length} notifications.`;
}
function getActiveUsers(users) {
return users.filter((user) => user.isActive && !user.isBanned);
}
✅ Bom: variável com nome expressivo antes do `return`
function buildGreeting(user) {
const greeting = `Hello, ${user.name}! You have ${user.notifications.length} notifications.`;
return greeting;
}
function getActiveUsers(users) {
const activeUsers = users.filter((user) => user.isActive && !user.isBanned);
return activeUsers;
}
❌ Ruim: retorno cru, repassa o valor sem nome e não diz o que é
function findPendingOrders(userId) {
return orderRepository.findByStatus(userId, "pending");
}
async function processCheckout(cartId) {
return await checkoutService.process(cartId);
}
✅ Bom: um nome alinhado com a função deixa claro o que sai
function findPendingOrders(userId) {
const pendingOrders = orderRepository.findByStatus(userId, "pending");
return pendingOrders;
}
async function processCheckout(cartId) {
const invoice = await checkoutService.process(cartId);
return invoice;
}
❌ Ruim: texto enorme montado em uma linha, ilegível e sem sentido nas partes
function buildShippingLabel(order) {
return `${order.customer.firstName} ${order.customer.lastName}\n${order.address.street}, ${order.address.number}\n${order.address.city} - ${order.address.state}, ${order.address.zipCode}\nOrder #${order.id}`;
}
✅ Bom: partes com nome antes de montar o resultado
function buildShippingLabel(order) {
const fullName = `${order.customer.firstName} ${order.customer.lastName}`;
const addressLine = `${order.address.street}, ${order.address.number}`;
const cityLine = `${order.address.city} - ${order.address.state}, ${order.address.zipCode}`;
const label = `${fullName}\n${addressLine}\n${cityLine}\nOrder #${order.id}`;
return label;
}
Baixa densidade visual
Linhas que se relacionam ficam juntas. Grupos diferentes se separam com uma linha em branco, e só uma: duas linhas em branco já são espaço demais.
❌ Ruim: parede de código sem respiro entre grupos
async function processOrder(orderId) {
const order = await fetchOrder(orderId);
if (!order) return;
const discountedOrder = applyDiscount(order);
const invoice = buildInvoice(discountedOrder);
await saveInvoice(invoice);
await notifyCustomer(invoice);
return invoice;
}
✅ Bom: cada grupo é um parágrafo de intenção
async function processOrder(orderId) {
const order = await fetchOrder(orderId);
if (!order) return;
const discountedOrder = applyDiscount(order);
const invoice = buildInvoice(discountedOrder);
await saveInvoice(invoice);
await notifyCustomer(invoice);
return invoice;
}
Baixa densidade visual: agrupamento
Uma linha em branco sobrando dentro de um grupo quebra o ritmo. Uma linha em branco faltando entre dois grupos cola o que não tem relação. A regra: nenhuma dentro do grupo, uma entre grupos, nunca duas ou mais.
❌ Ruim: espaço dentro dos grupos, sem separação entre grupos
async function registerUser(input) {
const { name, email } = input;
const exists = await db.users.findByEmail(email);
if (exists) throw new ConflictError('Email taken');
const hash = await hashPassword(input.password);
const user = await db.users.create({ name, email, hash });
const token = generateToken(user.id);
await sendWelcomeEmail(email, token);
return user;
}
✅ Bom: nenhuma linha dentro do grupo, uma entre grupos
async function registerUser(input) {
const { name, email } = input;
const exists = await userRepository.findByEmail(email);
if (exists) throw new ConflictError('Email taken');
const hash = await hashPassword(input.password);
const user = await userRepository.create({ name, email, hash });
const token = generateToken(user.id);
await sendWelcomeEmail(email, token);
return user;
}
Textos longos
Um texto montado com template string (o texto entre crases que aceita valores
no meio com ${...}) ficou gigante? Separe as partes compostas em variáveis com
nome.
❌ Ruim: todos os detalhes montados em uma linha
function buildConfirmationEmail(user, order) {
const message = `Olá ${user.firstName} ${user.lastName}, seu pedido #${order.id} foi confirmado e será entregue no endereço ${order.address.street}, ${order.address.city} - ${order.address.state} em até ${order.deliveryDays} dias úteis.`;
return message;
}
✅ Bom: partes separadas, texto final legível
function buildConfirmationEmail(user, order) {
const fullName = `${user.firstName} ${user.lastName}`;
const address = `${order.address.street}, ${order.address.city} - ${order.address.state}`;
const greeting = `Olá ${fullName}`;
const orderInfo = `seu pedido #${order.id} foi confirmado`;
const deliveryInfo = `e será entregue em ${address} em até ${order.deliveryDays} dias úteis`;
const message = `${greeting}, ${orderInfo} ${deliveryInfo}.`;
return message;
}
Estilo vertical: parâmetros
Até 3 parâmetros na mesma linha. Com 4 ou mais, use um objeto.
❌ Ruim: 4+ parâmetros inline, intenção obscura na chamada
function createInvoice(orderId, customerId, amount, dueDate, currency) { /* ... */ }
createInvoice("ord-1", "cust-99", 149.90, "2026-05-01", "BRL");
✅ Bom: objeto quando 4+ parâmetros
function createInvoice(invoiceData) {
// why: desestruturar no corpo preserva a assinatura como objeto nomeado na chamada
const { orderId, customerId, amount, dueDate, currency } = invoiceData;
/* ... */
}
createInvoice({
orderId: "ord-1",
customerId: "cust-99",
amount: 149.90,
dueDate: "2026-05-01",
currency: "BRL",
});
Arrow function: preservar o this em callbacks
Em JavaScript, o valor de this (a palavra que aponta para o objeto atual) é
decidido por quem chama a função, não por quem a escreveu. É daí que vêm os
bugs difíceis de achar: você escreve um método, passa uma function () {} como
callback (a função que será chamada mais tarde por outro código) e, dentro
dela, o this deixa de ser o objeto que você esperava.
Duas formas de declarar uma função, dois comportamentos diferentes:
function () {}temthispróprio. Em callbacks desetInterval,forEachouaddEventListener, othisque você esperava se perde: viraundefinedno modo estrito (o'use strict', que deixa as regras da linguagem mais rígidas) ou vira o objeto global.() => {}não temthispróprio. A arrow function (função flecha, escrita com=>) usa othisdo lugar onde foi escrita, não o de quem a chama. Por isso ela mantém a referência ao objeto atual dentro do método.
Regra prática: quando a função é um callback (passada para outro código chamar
depois) dentro de um método, use a função flecha. Para declarar o próprio método de um
objeto ou classe, use a forma curta (obj.foo() {}). Aí o this é resolvido no
momento da chamada: quando você escreve obj.foo(), o this é obj.
❌ Ruim: callback `function` dentro do método quebra o `this` do objeto
class Cart {
constructor() {
this.items = [];
this.total = 0;
}
addAll(prices) {
prices.forEach(function (price) {
this.total += price; // this é undefined em strict mode
this.items.push(price);
});
}
}
const cart = new Cart();
cart.addAll([10, 20, 30]); // TypeError: Cannot read properties of undefined (reading 'total')
✅ Bom: a função flecha usa o `this` do método onde foi escrita
class Cart {
constructor() {
this.items = [];
this.total = 0;
}
addAll(prices) {
prices.forEach((price) => {
this.total += price;
this.items.push(price);
});
}
}
const cart = new Cart();
cart.addAll([10, 20, 30]);
❌ Ruim: `setInterval` com `function` perde o acesso aos campos do objeto
class BuildTimer {
constructor(label) {
this.label = label;
this.elapsed = 0;
}
start() {
setInterval(function () {
this.elapsed += 1;
console.log(`${this.label}: ${this.elapsed}s`); // imprime "undefined: NaNs"
}, 1000);
}
}
new BuildTimer("build").start();
✅ Bom: a função flecha mantém o `this`; `label` e `elapsed` continuam acessíveis
class BuildTimer {
constructor(label) {
this.label = label;
this.elapsed = 0;
}
start() {
setInterval(() => {
this.elapsed += 1;
console.log(`${this.label}: ${this.elapsed}s`);
}, 1000);
}
}
new BuildTimer("build").start();
❌ Ruim: função flecha como método do objeto: o `this` não aponta para o objeto
const counter = {
count: 0,
increment: () => {
this.count += 1; // this é o módulo, não counter
},
};
counter.increment();
console.log(counter.count); // 0
✅ Bom: a forma curta mantém o `this` ligado ao objeto na chamada
const counter = {
count: 0,
increment() {
this.count += 1;
},
};
counter.increment();
console.log(counter.count); // 1
Código morto
❌ Ruim: condição impossível, função nunca chamada
function getStatus(value) {
if (false) {
console.log("never runs");
}
return value > 0 ? "active" : "inactive";
}
// migrada para v2, mas continua aqui sem ser chamada
function legacyTransform(items) {
return items.map((item) => item.id);
}
✅ Bom: remove o que não é usado
function getStatus(value) {
const status = value > 0 ? "active" : "inactive";
return status;
}
DoDocs v3.7.0 · Desenvolvido por @thiagocajadev · Baseado no trabalho de pmndrs/docs · Poimandres.