Aikido

Approfondimenti sulla qualità del codice di FreeCodeCamp: regole in grado di migliorare qualsiasi base di codice

Introduzione

FreeCodeCamp è più di una semplice piattaforma di apprendimento: è un vasto codice sorgente open source con migliaia di collaboratori e milioni di righe di JavaScript e TypeScript. La gestione di un progetto così complesso richiede modelli architetturali coerenti, rigide convenzioni di denominazione e test approfonditi per evitare regressioni e garantire l'affidabilità.

In questo articolo presentiamo le regole di revisione del codice più significative tratte da FreeCodeCamp. Ciascuna regola illustra come una progettazione accurata, un flusso di dati coerente e una gestione degli errori solida possano aiutare i progetti di grandi dimensioni a mantenere l'ordine e a ridurre il rischio di bug difficili da individuare nel corso del tempo.

Le sfide

Mantenere la qualità del codice in FreeCodeCamp è una sfida impegnativa, vista l'ampiezza del codice scritto in JavaScript e TypeScript e le migliaia di collaboratori. Garantire modelli coerenti, un flusso di dati prevedibile e funzionalità affidabili richiede regole di revisione strutturate e controlli automatizzati.

Tra le principali sfide figurano modelli di codifica non uniformi, moduli legacy strettamente interconnessi, copertura dei test disomogenea, discrepanze nella documentazione e un elevato volume di pull request.

Standard chiari, una convalida automatizzata e un'attenta revisione del codice contribuiscono a mantenere il codice gestibile, stabile e scalabile man mano che il progetto cresce.

Perché queste regole sono importanti

Regole coerenti per la revisione del codice migliorano la manutenibilità garantendo una struttura uniforme dei moduli, convenzioni di denominazione e flussi di dati prevedibili, rendendo i test più affidabili e facilitando il monitoraggio delle dipendenze.

Inoltre, migliorano la sicurezza grazie alla convalida degli input, alla gestione degli errori e al controllo degli effetti collaterali, accelerando al contempo il processo di inserimento dei nuovi collaboratori, aiutandoli a comprendere rapidamente le responsabilità dei moduli e i punti di integrazione.

Collegare il contesto a queste regole

Queste regole sono state estratte dal repository e dalle pull request di FreeCodeCamp e riflettono problemi ricorrenti quali flussi di dati poco chiari, mancanza di gestione degli errori e test incoerenti che incidono sulla stabilità e sulla manutenibilità.

Ogni regola evidenzia un errore specifico, ne spiega l'impatto sulle prestazioni, sulla chiarezza o sull'affidabilità e include esempi di codice JavaScript o TypeScript ❌ non conforme e ✅ conforme.

1. Evitare l'uso eccessivo di qualsiasi tipo in TypeScript

Evita di utilizzare il tipo "any" in TypeScript. Definisci sempre tipi precisi ed espliciti per le variabili, i parametri delle funzioni e i valori di ritorno, al fine di garantire la sicurezza dei tipi ed evitare errori in fase di esecuzione.

❌ Non conforme:

let userData: any = fetchUserData();

✅ Conforme:

interface UserData {
  id: string;
  name: string;
  email: string;
}

let userData: UserData = fetchUserData();

Perché è importante: L'uso di "any" disabilita il controllo dei tipi di TypeScript, il che può causare errori di runtime e ridurre la manutenibilità e l'affidabilità del codice. I tipi espliciti rendono il codice più sicuro e più facile da comprendere per gli altri sviluppatori.

2. Preferire nomi descrittivi per le variabili piuttosto che abbreviazioni

Utilizza sempre nomi di variabili chiari e descrittivi. Evita le abbreviazioni o i nomi criptici che rendono poco chiaro il significato del codice.

❌ Non conforme:

const usr = getUser();

✅ Conforme:

const user = getUser();

Perché è importante: I nomi descrittivi delle variabili rendono il codice più facile da leggere, comprendere e mantenere. Una denominazione inadeguata può creare confusione tra gli sviluppatori e aumentare il rischio di introdurre bug.

3. Evitare cicli o condizioni profondamente annidati

Riorganizza il codice per evitare un'annidamento eccessivo nei cicli o nelle istruzioni condizionali. Utilizza i ritorni anticipati o le funzioni di supporto per semplificare la logica.

❌ Non conforme:

se (user) {
  se (user.isActive) {
    se (utente.haPermesso) {
      // Esegui l'azione
    }
  }
}

✅ Conforme:

se (!user) restituisci;
se (!user.isActive) return;
se (!user.hasPermission) return;

// Esegui l'azione
elaboraAzioneUtente(utente);

Perché è importante: Una logica profondamente annidata è difficile da seguire, mantenere e testare. Complica la scrittura dei test unitari, specialmente per i casi negativi e gli errori precoci. Appiattire il flusso di controllo con ritorni anticipati rende il codice più facile da comprendere, migliora la copertura dei test e riduce la possibilità di bug nascosti nei casi limite.

4. Garantire una gestione degli errori coerente in tutto il codice

Implementare sempre una gestione degli errori coerente. Utilizzare funzioni di gestione degli errori centralizzate o modelli standardizzati per gestire le eccezioni in modo uniforme.

❌ Non conforme:

try {
  // Some code
} catch (e) {
  console.error(e);
}

✅ Conforme:

try {
  // Some code
} catch (error) {
  logError(error);
  throw new CustomError('An error occurred', { cause: error });
}

Perché è importante: Una gestione coerente degli errori semplifica il debug, previene comportamenti imprevisti e garantisce l'affidabilità dell'intera applicazione.

5. Evitare di inserire i valori di configurazione in modo statico

Non inserire direttamente nel codice valori specifici dell'ambiente, come URL, porte o credenziali riservate. Utilizza sempre file di configurazione o variabili d'ambiente.

❌ Non conforme:

const apiUrl = 'https://api.example.com';

✅ Conforme:

const apiUrl = process.env.API_URL;

Perché è importante: I valori hardcoded riducono la flessibilità, rendono il codice meno sicuro e complicano l'implementazione in ambienti diversi. L'uso delle configurazioni garantisce la manutenibilità e la sicurezza.

6. Assicurarsi che le funzioni si concentrino su un’unica responsabilità

Assicurati che ogni funzione svolga un unico compito ben definito. Evita le funzioni che gestiscono più responsabilità, poiché ciò può causare confusione e difficoltà nella manutenzione.

❌ Non conforme:

function processUserData(user) {
  const validatedUser = validateUser(user);
  saveUserToDatabase(validatedUser);
  sendWelcomeEmail(validatedUser);
}

✅ Conforme:

funzione validateUser(utente) {
  // logica di convalida
}

funzione salvaUtenteNelDatabase(utente) {
  // logica di salvataggio
}

funzione inviaEmailDiBenvenuto(utente) {
  // logica di invio dell'e-mail
}

Perché è importante: Le funzioni con una singola responsabilità sono più facili da testare, da correggere e da mantenere. Favoriscono il riutilizzo del codice e ne migliorano la leggibilità.

7. Evitare di utilizzare numeri magici

Sostituisci i numeri magici con costanti denominate per migliorare la chiarezza e la manutenibilità del codice.

❌ Non conforme:

const area = lunghezza * 3,14159 * raggio * raggio;

✅ Conforme:

const PI = 3,14159;
const area = lunghezza * PI * raggio * raggio;

Perché è importante: I numeri magici possono rendere poco chiaro il significato del codice e rendere le modifiche future soggette a errori. Le costanti con nome forniscono un contesto e riducono il rischio di introdurre bug.

8. Ridurre al minimo l'uso delle variabili globali

Limitare l'uso delle variabili globali per ridurre le dipendenze e i potenziali conflitti nel codice.

❌ Non conforme:

let user = { name: 'Alice' };

function greetUser() {
  console.log(`Hello, ${user.name}`);
}

✅ Conforme:

function greetUser(user) {
  console.log(`Hello, ${user.name}`);
}

const user = { name: 'Alice' };
greetUser(user);

Perché è importante: Le variabili globali possono creare dipendenze nascoste ed effetti collaterali imprevedibili. Rendono difficile tracciare la provenienza dei dati o il modo in cui questi cambiano all'interno del codice. Passare i dati in modo esplicito tramite i parametri delle funzioni mantiene il flusso dei dati chiaro e controllato, migliorando la modularità, il debug e la manutenibilità a lungo termine.

9. Utilizzare i letterali template per la concatenazione delle stringhe

Preferisci i letterali di template alla concatenazione di stringhe per una migliore leggibilità e prestazioni.

❌ Non conforme:

const messaggio = 'Ciao, ' + user.name + '! Hai ' + user.notifications + ' nuove notifiche.';

✅ Conforme:

const message = `Hello, ${user.name}! You have ${user.notifications} new notifications.`;

Perché è importante: I letterali template offrono una sintassi più chiara e migliorano la leggibilità, soprattutto quando si ha a che fare con stringhe complesse o contenuti su più righe.

10. Implementare un'adeguata convalida dei dati in ingresso

Verificare sempre i dati inseriti dall'utente per impedire che dati non validi entrino nel sistema e per migliorare la sicurezza.

❌ Non conforme:

funzione elaboraInputUtente(input) {
  // logica di elaborazione
}

✅ Conforme:

function validateInput(input) {
  if (typeof input !== 'string' || input.trim() === '') {
    throw new Error('Invalid input');
  }
}

function processUserInput(input) {
  validateInput(input);
  // processing logic
}

Perché è importante: La convalida degli input è fondamentale per prevenire errori, garantire l'integrità dei dati e proteggersi da vulnerabilità di sicurezza quali gli attacchi di tipo injection.

11. Inserire una sola modifica logica per ogni pull request

Assicurati che ogni pull request (PR) implementi una singola modifica logica o funzionalità; evita di combinare in un unico PR correzioni, rifattorizzazioni e aggiunte di funzionalità non correlate tra loro.

❌ Non conforme:

# "Fix login + update homepage"
--- auth.js
+ if (!user) throw new Error('User not found');

--- HomePage.js
- <button>Start</button>
+ <button>Begin Journey</button>

✅ Conforme: (diff)

# PR 1: Fix login validation
+ if (!user) throw new Error('User not found');

# PR 2: Update homepage button
+ <button>Begin Journey</button>

Perché è importante: Le PR piccole e mirate semplificano la revisione del codice, riducono il rischio di effetti collaterali indesiderati e accelerano i cicli di merge. Gli strumenti di intelligenza artificiale sono in grado di rilevare quando file, moduli o domini non correlati subiscono modifiche nella stessa PR — cosa che i linter non sono in grado di determinare.

12. Utilizzare denominazioni in linea con il dominio per le API e i servizi

Assegnare nomi alle API, ai servizi e ai moduli in base al dominio aziendale (ad esempio, challengeService.createSubmission anziché handler1.doIt); i nomi devono riflettere chiaramente l'entità e l'azione.

❌ Non conforme:

// backend/services/handler.js
export async function doIt(data) {
  return await process(data);
}

// routes/index.js
router.post('/submit', handler.doIt);

✅ Conforme:

// 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);

Perché è importante: Una denominazione allineata al dominio rende il codice autodocumentante, favorisce la chiarezza per i nuovi collaboratori e si allinea alla logica di business. Solo un'intelligenza artificiale consapevole del contesto semantico (nomi delle entità, livelli di servizio) è in grado di rilevare denominazioni non allineate o generiche tra i moduli.

13. Assicurarsi che i test coprano i casi di errore e i casi limite

Scrivete test non solo per lo “scenario ideale”, ma anche per le condizioni di errore, i casi limite e i valori al limite; assicuratevi che ogni modulo critico sia sottoposto a test sia positivi che negativi.

❌ Non conforme:

describe('login', () => {
  it('should succeed with correct credentials', async () => { … });
});

✅ Conforme:

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 () => { … });
});

Perché è importante: La logica critica per il business spesso fallisce quando vengono trascurati casi limite, come input non validi, timeout o accessi non riusciti. Testare sia i percorsi di successo che quelli di errore garantisce che l'applicazione si comporti in modo affidabile in condizioni reali e previene regressioni difficili da individuare in un secondo momento.

14. Evitare di mescolare i livelli: i componenti dell'interfaccia utente non dovrebbero eseguire la logica di business

Assicurati che i componenti dell'interfaccia utente (React, front-end) non contengano logica di business né chiamate al database o ai servizi; delega queste attività a servizi dedicati o agli hook.

❌ Non conforme:

// FreeCodeCamp-style
function CurriculumCard({ user, challenge }) {
  if (!user.completed.includes(challenge.id)) {
    saveCompletion(user.id, challenge.id);
  }
  return <Card>{challenge.title}</Card>;
}

✅ Conforme:

function CurriculumCard({ user, challenge }) {
  return <Card>{challenge.title}</Card>;
}

// In service:
async function markChallengeComplete(userId, challengeId) {
  await completionService.create({ userId, challengeId });
}

Perché è importante: Incorporare la logica di business nei componenti dell’interfaccia utente offusca i confini tra i livelli e rende rischiose le modifiche future. Inoltre, costringe gli sviluppatori front-end a comprendere la logica del back-end e rallenta la collaborazione. Mantenere separate le responsabilità garantisce che ogni livello possa evolversi in modo indipendente e riduce il rischio di introdurre bug impercettibili durante il refactoring.

Conclusione

Analizzando il repository di FreeCodeCamp, abbiamo individuato alcune regole pratiche di revisione del codice che contribuiscono a mantenere il suo ampio codice sorgente organizzato, leggibile e gestibile. Queste 20 regole riflettono le pratiche effettivamente adottate nel corso di anni di contributi, sebbene non siano applicate da alcuno strumento automatizzato.

Applicare questi insegnamenti ai propri progetti può migliorare la chiarezza, ridurre i bug e rendere più agevole la collaborazione. Essi costituiscono una solida base per scalare il codice in modo sicuro, garantendo che, man mano che il progetto cresce, il codice rimanga affidabile e facile da utilizzare.

La revisione del codice non si limita alla verifica della sintassi. Si tratta piuttosto di garantire la qualità e l’integrità del codice nel tempo. Imparare da un progetto open source consolidato come FreeCodeCamp offre agli sviluppatori indicazioni concrete per migliorare qualsiasi base di codice.

Domande frequenti

Hai delle domande?

In che modo queste regole regolano il flusso dei dati e i confini dei moduli in FreeCodeCamp?

Garantiscono una gestione coerente degli input e degli output, un comportamento prevedibile delle funzioni e una chiara separazione dei livelli di responsabilità, riducendo l'accoppiamento e rendendo i moduli più facili da rifattorizzare e testare.

In che modo queste regole migliorano la copertura e l'affidabilità dei test?

Richiedendo test unitari e di integrazione per i casi limite, la gestione degli errori e la logica di business critica, garantiscono che le regressioni vengano individuate tempestivamente e che i test automatizzati riflettano accuratamente il comportamento del sistema.

In che modo queste regole aiutano a gestire il codice legacy?

Forniscono modelli per il refactoring di moduli strettamente interconnessi o obsoleti, mantenendo al contempo la compatibilità con le versioni precedenti e riducendo al minimo il rischio di regressioni.

Queste regole possono prevenire problemi di sicurezza su FreeCodeCamp?

Sì. Garantiscono la convalida degli input, la gestione sicura dei dati e una gestione coerente degli errori, il che riduce rischi quali attacchi di tipo injection, eccezioni non gestite e fughe di dati.

In che modo i nuovi collaboratori possono trarre vantaggio da queste regole?

Responsabilità dei moduli ben definite, una nomenclatura coerente e una gestione degli errori standardizzata aiutano i nuovi sviluppatori a comprendere rapidamente l'architettura e a integrarsi in modo sicuro senza introdurre regressioni.

In che modo queste regole sono state ricavate da FreeCodeCamp?

Sono stati ricavati dall'analisi delle richieste di pull, delle modifiche al codice, dei thread di discussione e dei modelli ricorrenti che hanno influito sulla manutenibilità, sulla leggibilità e sulla stabilità dell'intero codice.

Metti in sicurezza ora

Metti in sicurezza il tuo codice, il cloud e il runtime in un unico sistema centralizzato.
Trova e risolvi le vulnerabilità rapidamente e automaticamente.

Nessuna carta di credito richiesta | Risultati della scansione in 32 secondi.