Functions
Função é a unidade mínima de reuso e teste. Seu valor está em single responsibility (responsabilidade única, fazer uma coisa só), assinatura clara e tamanho que cabe na tela. Quando uma função busca, valida, calcula e persiste tudo junto, ela vira um ponto de acoplamento que ninguém consegue reaproveitar nem testar isoladamente.
Uma função faz uma coisa. Seu nome diz o quê. Seu tamanho cabe na tela.
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 |
God function: múltiplas 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: orquestrador no topo, responsabilidades separadas
await processOrder(123);
async function processOrder(orderId) {
const order = await getOrder(orderId);
if (isInvalid(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 notifyDefault(order);
return false;
}
async function issueInvoice(order) {
const discountedOrder = applyDiscount(order);
const invoice = await saveOrder(discountedOrder);
return invoice;
}
}
SLA: orquestrador ou implementação, nunca os dois
❌ Ruim: mesma função orquestra 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: orquestrador chama helpers, cada um 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;
}
}
Direct return
O retorno fica no topo da função, com os detalhes encapsulados em auxiliares abaixo dela.
❌ Ruim: variável auxiliar desnecessária, else após 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
O caller expressa o quê, não o como. Toda construção de contexto fica dentro da função.
❌ Ruim: caller monta lógica inline 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 retorno nomeia o resultado, não o computa. A variável é expressiva e simétrica com a intenção da função.
❌ Ruim: lógica ou objeto anônimo 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 expressiva 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: bare return: pass-through sem nome, o retorno não diz o que é
function findPendingOrders(userId) {
return orderRepository.findByStatus(userId, "pending");
}
async function processCheckout(cartId) {
return await checkoutService.process(cartId);
}
✅ Bom: nome simétrico 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: string imensa montada inline: ilegível e sem semântica
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 nomeadas 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 relacionadas ficam juntas. Grupos distintos se separam com exatamente uma linha em branco. Nunca duas.
❌ 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: parágrafos 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
Blank lines em excesso dentro de um grupo quebram o ritmo. Blank lines ausentes entre grupos colam o que não se relaciona. A regra: 0 linhas dentro, 1 entre, nunca 2+.
❌ 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: 0 linhas dentro do grupo, 1 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;
}
Strings longas
Template literal gigante? Extraia as partes compostas em variáveis nomeadas.
❌ Ruim: todos os detalhes interpolados inline
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: compostos extraídos, string 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 this em callbacks
Em JavaScript, o this é decidido por quem chama a função, não por quem a escreve. É daí que vêm os bugs sutis: você escreve um método, passa function () {} como callback (função de retorno), e dentro dele 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, othisesperado se perde: viraundefinedem strict mode (modo estrito) ou o objeto global.() => {}não temthispróprio. A arrow function (função flecha) herda othislexical (léxico), ou seja, othisdo escopo onde ela foi escrita. Por isso preserva a referência da instância dentro do método.
Regra prática: em callbacks dentro de métodos, use arrow. Em métodos de objeto e classe, use method shorthand (método curto, obj.foo() {}). O this é resolvido no call site (ponto de chamada): obj.foo() → this === obj.
❌ Ruim: callback `function` dentro do método quebra o `this` da instância
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: arrow function captura o `this` léxico do método
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 acesso aos campos da instância
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: arrow herda `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: arrow como método de objeto: o `this` léxico não é 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: method shorthand mantém `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;
}
Desenvolvido por @thiagocajadev · Fork baseado no repositório pmndrs/docs · Poimandres.