Introdução
O FreeCodeCamp é mais do que uma plataforma de aprendizado; é uma grande base de código aberto com milhares de colaboradores e milhões de linhas de JavaScript e TypeScript. Gerir um projeto tão complexo requer padrões de arquitetura consistentes, convenções de nomenclatura rigorosas e testes minuciosos para evitar regressões e manter a fiabilidade.
Neste artigo, apresentamos as regras de revisão de código mais impactantes extraídas do FreeCodeCamp. Cada regra demonstra como um design cuidadoso, um fluxo de dados consistente e um tratamento de erros robusto podem ajudar grandes projetos a se manterem organizados e reduzir o risco de bugs sutis ao longo do tempo.
Os desafios
Manter a qualidade do código no FreeCodeCamp é um desafio devido à sua grande base de código JavaScript e TypeScript e aos milhares de colaboradores. Garantir padrões consistentes, fluxo de dados previsível e funcionalidade confiável requer regras de revisão estruturadas e verificações automatizadas.
Os principais desafios incluem padrões de codificação inconsistentes, módulos legados fortemente acoplados, cobertura de teste desigual, desvios de documentação e um grande volume de pedidos de retirada.
Normas claras, validação automatizada e revisão cuidadosa do código ajudam a manter a base de código sustentável, estável e escalável à medida que o projeto cresce.
Porque é que estas regras são importantes
As regras de revisão de código consistentes melhoram a manutenção ao impor uma estrutura de módulos uniforme, convenções de nomenclatura e fluxos de dados previsíveis, tornando os testes mais fiáveis e as dependências mais fáceis de seguir.
Também melhoram a segurança através da validação de entradas, do tratamento de erros e de efeitos secundários controlados, ao mesmo tempo que aceleram a integração, ajudando os novos colaboradores a compreender rapidamente as responsabilidades dos módulos e os pontos de integração.
Contexto de ligação a estas regras
Estas regras são extraídas do repositório e dos pull requests do FreeCodeCamp, reflectindo problemas recorrentes como o fluxo de dados pouco claro, a falta de tratamento de erros e testes inconsistentes que afectam a estabilidade e a manutenção.
Cada regra destaca uma armadilha concreta, explica seu impacto no desempenho, na clareza ou na confiabilidade e inclui ❌ exemplos de JavaScript ou TypeScript não compatíveis versus ✅ compatíveis.
1. Evitar a utilização excessiva de qualquer tipo em TypeScript
Evite usar qualquer tipo no TypeScript. Sempre defina tipos precisos e explícitos para variáveis, parâmetros de função e valores de retorno para garantir a segurança de tipo e evitar erros de tempo de execução.
Não conforme:
deixe userData: any = fetchUserData();Conformidade:
interface UserData {
id: string;
name: string;
email: string;
}
let userData: UserData = fetchUserData();Por que isso é importante: A utilização de any desactiva a verificação de tipos do TypeScript, o que pode permitir a ocorrência de erros em tempo de execução e reduzir a capacidade de manutenção e a fiabilidade do código. Os tipos explícitos tornam o código mais seguro e mais fácil de entender para outros desenvolvedores.
2. Preferir nomes descritivos de variáveis em vez de abreviaturas
Utilize sempre nomes de variáveis claros e descritivos. Evite abreviaturas ou nomes crípticos que obscureçam o significado do código.
Não conforme:
const usr = getUser();Conformidade:
const utilizador = getUser();Por que isso é importante: Nomes descritivos de variáveis tornam o código mais fácil de ler, entender e manter. A má nomeação pode confundir os programadores e aumentar o risco de introdução de erros.
3. Evitar loops ou condicionais profundamente aninhados
Refactorize o código para evitar aninhamentos profundos em loops ou condicionais. Utilize retornos antecipados ou funções auxiliares para nivelar a lógica.
Não conforme:
se (utilizador) {
se (user.isActive) {
se (user.hasPermission) {
// Executar ação
}
}
}Conformidade:
se (!user) return;
se (!user.isActive) return;
se (!user.hasPermission) return;
// Executar a ação
processUserAction(user);Por que isso é importante: Uma lógica profundamente aninhada é difícil de seguir, manter e testar. Ela complica a escrita de testes unitários, especialmente para casos negativos e falhas iniciais. O achatamento do fluxo de controle com retornos antecipados torna o código mais fácil de entender, melhora a cobertura de testes e reduz a chance de bugs ocultos em casos extremos.
4. Garantir um tratamento de erros consistente em toda a base de código
Implementar sempre um tratamento de erros consistente. Utilize funções de erro centralizadas ou padrões normalizados para tratar as excepções de forma uniforme.
Não conforme:
try {
// Some code
} catch (e) {
console.error(e);
}Conformidade:
try {
// Some code
} catch (error) {
logError(error);
throw new CustomError('An error occurred', { cause: error });
}Por que isso é importante: O tratamento consistente de erros facilita a depuração, evita comportamentos inesperados e garante a fiabilidade em toda a aplicação.
5. Evitar a codificação rígida de valores de configuração
Não codifique valores específicos do ambiente como URLs, portas ou secrets. Utilize sempre ficheiros de configuração ou variáveis de ambiente.
Não conforme:
const apiUrl = 'https://api.example.com';Conformidade:
const apiUrl = process.env.API_URL;Por que isso é importante: Os valores codificados reduzem a flexibilidade, tornam o código menos seguro e complicam a implantação em diferentes ambientes. O uso de configurações garante a manutenção e a segurança.
6. Manter as funções concentradas numa única responsabilidade
Assegurar que cada função desempenha uma única tarefa bem definida. Evite funções com múltiplas responsabilidades, pois isso pode gerar confusão e dificuldade de manutenção.
Não conforme:
function processUserData(user) {
const validatedUser = validateUser(user);
saveUserToDatabase(validatedUser);
sendWelcomeEmail(validatedUser);
}Conformidade:
function validateUser(user) {
// lógica de validação
}
function saveUserToDatabase(user) {
// lógica de gravação
}
function sendWelcomeEmail(user) {
// lógica de envio de correio eletrónico
}Por que isso é importante: Funções com uma única responsabilidade são mais fáceis de testar, depurar e manter. Elas promovem a reutilização de código e melhoram a legibilidade.
7. Evitar a utilização de números mágicos
Substitua os números mágicos por constantes nomeadas para melhorar a clareza e a manutenção do código.
Não conforme:
const área = comprimento * 3.14159 * raio * raio;Conformidade:
const PI = 3.14159;
const área = comprimento * PI * raio * raio;Porque é que isto é importante: Os números mágicos podem obscurecer o significado do código e tornar as futuras modificações propensas a erros. As constantes nomeadas fornecem contexto e reduzem o risco de introdução de erros.
8. Minimizar a utilização de variáveis globais
Limitar a utilização de variáveis globais para reduzir as dependências e os potenciais conflitos na base de código.
Não conforme:
let user = { name: 'Alice' };
function greetUser() {
console.log(`Hello, ${user.name}`);
}Conformidade:
function greetUser(user) {
console.log(`Hello, ${user.name}`);
}
const user = { name: 'Alice' };
greetUser(user);Por que isso é importante: As variáveis globais podem criar dependências ocultas e efeitos colaterais imprevisíveis. Elas dificultam o rastreamento de onde os dados vêm ou como eles mudam na base de código. A passagem de dados explicitamente através de parâmetros de função mantém o fluxo de dados claro e controlado, o que melhora a modularidade, a depuração e a capacidade de manutenção a longo prazo.
9. Utilizar modelos literais para a concatenação de cadeias de caracteres
Prefira literais de modelo em vez de concatenação de strings para melhor legibilidade e desempenho.
Não conforme:
const mensagem = 'Olá, ' + nome do utilizador + '! Você tem ' + user.notifications + ' novas notificações.';Conformidade:
const message = `Hello, ${user.name}! You have ${user.notifications} new notifications.`;Porque é que isto é importante: Os literais de modelo fornecem uma sintaxe mais limpa e melhoram a legibilidade, especialmente quando se lida com cadeias de caracteres complexas ou conteúdo de várias linhas.
10. Aplicar uma validação correta dos dados introduzidos
Valide sempre as entradas do utilizador para evitar que dados inválidos entrem no sistema e para aumentar a segurança.
Não conforme:
função processUserInput(input) {
// lógica de processamento
}Conformidade:
function validateInput(input) {
if (typeof input !== 'string' || input.trim() === '') {
throw new Error('Invalid input');
}
}
function processUserInput(input) {
validateInput(input);
// processing logic
}Porque é que isto é importante: A validação das entradas é crucial para evitar erros, garantir a integridade dos dados e proteger contra vulnerabilidades de segurança, como ataques de injeção.
11. Manter uma alteração lógica por pedido pull
Certifique-se de que cada pull request (PR) implementa uma única alteração ou funcionalidade lógica; evite combinar correcções, refacções e adições de funcionalidades não relacionadas num PR.
Não conforme:
# "Fix login + update homepage"
--- auth.js
+ if (!user) throw new Error('User not found');
--- HomePage.js
- <button>Start</button>
+ <button>Begin Journey</button>Conformidade: (diff)
# PR 1: Fix login validation
+ if (!user) throw new Error('User not found');
# PR 2: Update homepage button
+ <button>Begin Journey</button>Por que isso é importante: PRs pequenos e focados simplificam a revisão do código, reduzem o risco de efeitos colaterais não intencionais e aceleram os ciclos de mesclagem. As ferramentas de IA podem detetar quando arquivos, módulos ou domínios não relacionados são alterados no mesmo PR - algo que os linters não conseguem determinar.
12. Utilizar nomes alinhados com o domínio para APIs e serviços
Designar APIs, serviços e módulos de acordo com o domínio de atividade (por exemplo, challengeService.createSubmission e não handler1.doIt); os nomes devem refletir claramente a entidade e a ação.
Não conforme:
// backend/services/handler.js
export async function doIt(data) {
return await process(data);
}
// routes/index.js
router.post('/submit', handler.doIt);Conformidade:
// backend/services/challengeService.js
export async function createSubmission({ userId, challengeId, answer }) {
return await challengeModel.create({ userId, challengeId, answer });
}
// routes/challenges.js
router.post('/submissions', challengeService.createSubmission);Por que isso é importante: A nomenclatura alinhada com o domínio torna o código auto-documentado, ajuda a clarificar para novos colaboradores e alinha-se com a lógica empresarial. Apenas uma IA consciente do contexto semântico (nomes de entidades, camadas de serviços) pode detetar nomes desalinhados ou genéricos entre módulos.
13. Assegurar que os testes abrangem as falhas e os casos extremos
Escreva testes não só para o "caminho feliz", mas também para condições de erro, casos extremos e valores-limite; confirme que cada módulo crítico tem testes positivos e negativos.
Não conforme:
describe('login', () => {
it('should succeed with correct credentials', async () => { … });
});Conformidade:
describe('login', () => {
it('should succeed with correct credentials', async () => { … });
it('should fail with incorrect password', async () => { … });
it('should lock account after 5 failed attempts', async () => { … });
});Porque é que isto é importante: A lógica crítica para o negócio muitas vezes quebra quando casos extremos são perdidos, como entradas inválidas, timeouts ou logins com falha. Testar os caminhos de sucesso e de falha garante que a aplicação se comporta de forma fiável em condições reais e evita regressões que são difíceis de detetar mais tarde.
14. Evitar misturar camadas: Os componentes da IU não devem executar a lógica comercial
Mantenha os componentes da interface do utilizador (React, front-end) livres de lógica comercial e de chamadas para bases de dados/serviços; delegue estas tarefas em serviços ou ganchos dedicados.
Não conforme:
// FreeCodeCamp-style
function CurriculumCard({ user, challenge }) {
if (!user.completed.includes(challenge.id)) {
saveCompletion(user.id, challenge.id);
}
return <Card>{challenge.title}</Card>;
}Conformidade:
function CurriculumCard({ user, challenge }) {
return <Card>{challenge.title}</Card>;
}
// In service:
async function markChallengeComplete(userId, challengeId) {
await completionService.create({ userId, challengeId });
}Porque é que isto é importante: Misturar a lógica empresarial nos componentes da IU esbate as fronteiras entre camadas e torna arriscadas as alterações futuras. Também obriga os programadores de front-end a compreender a lógica de back-end e atrasa a colaboração. Manter as responsabilidades separadas garante que cada camada pode evoluir de forma independente e reduz o risco de introduzir erros subtis durante a refacção.
Conclusão
Ao analisar o repositório do FreeCodeCamp, extraímos regras práticas de revisão de código que ajudam a manter a sua grande base de código organizada, legível e sustentável. Estas 20 regras reflectem práticas reais de anos de contribuições, apesar de não serem aplicadas por nenhuma ferramenta automatizada.
A aplicação destas lições nos seus próprios projectos pode melhorar a clareza, reduzir os erros e tornar a colaboração mais fácil. Fornecem uma base sólida para escalar o seu código com segurança, garantindo que, à medida que o projeto cresce, o código permanece fiável e fácil de trabalhar.
A revisão do código é mais do que verificar a sintaxe. Trata-se de manter a qualidade e a integridade do seu código ao longo do tempo. Aprender com um projeto de código aberto maduro como o FreeCodeCamp dá aos programadores orientações concretas para melhorar qualquer base de código.
.avif)
