Aikido

Perché dovresti usare una classe per file: migliorare l'organizzazione e la navigabilità del codice

Leggibilità

Regola

Regola classe per file.
Più classi in un singolo file make codice
organizzazione poco chiaro e più difficile da navigazione.

Lingue supportate: 45+

Introduzione

Inserire più classi in un unico file rende difficile individuare classi specifiche durante la navigazione nel codice. Gli sviluppatori che cercano Repository utenti non lo troverai subito se è nascosto in un file chiamato database.js insieme ad altre cinque classi. Ciò viola il principio della “minima sorpresa” e rallenta lo sviluppo, poiché i membri del team perdono tempo a cercare le definizioni delle classi.

Perché è importante

Manutenibilità del codice: la presenza di più classi in un unico file rende poco chiari i confini tra le responsabilità. Quando è necessario modificare una classe, gli sviluppatori devono aprire un file contenente classi non correlate, aumentando così il carico cognitivo e il rischio di modificare accidentalmente il codice sbagliato.

Navigazione e reperibilità: gli IDE e gli editor di testo faticano a fornire una funzione "vai alla definizione" accurata quando più classi condividono lo stesso file. Gli sviluppatori perdono tempo a cercare all’interno dei file invece di passare direttamente alla classe di cui hanno bisogno. Questo problema si aggrava nei codici di grandi dimensioni con centinaia di classi.

Conflitti nel controllo di versione: quando più classi condividono un unico file, le modifiche apportate a classi diverse da sviluppatori diversi generano conflitti di unione. L'utilizzo di file separati consente lo sviluppo parallelo senza oneri di coordinamento, poiché ogni sviluppatore lavora sul proprio file.

Esempi di codice

❌ Non conforme:

// database.js
class UserRepository {
    async findById(id) {
        return db.users.findOne({ id });
    }
}

class OrderRepository {
    async findByUser(userId) {
        return db.orders.find({ userId });
    }
}

class ProductRepository {
    async findInStock() {
        return db.products.find({ stock: { $gt: 0 } });
    }
}

module.exports = { UserRepository, OrderRepository, ProductRepository };

Perché è sbagliato: Tre classi di repository non correlate in un unico file denominato database.js. Ricerca di OrderRepository richiede di sapere che si trova in database.js piuttosto che OrderRepository.js. Le modifiche al file interessano più classi, creando conflitti di unione non necessari.

✅ Conforme:

// UserRepository.js
class UserRepository {
    async findById(id) {
        return db.users.findOne({ id });
    }
}
module.exports = UserRepository;

// OrderRepository.js
class OrderRepository {
    async findByUser(userId) {
        return db.orders.find({ userId });
    }
}
module.exports = OrderRepository;

// ProductRepository.js
class ProductRepository {
    async findInStock() {
        return db.products.find({ stock: { $gt: 0 } });
    }
}
module.exports = ProductRepository;

Perché è importante: Il fatto che ogni classe sia contenuta in un proprio file rende la navigazione più intuitiva. Gli IDE possono passare direttamente a OrderRepository.js durante la ricerca della classe. Le modifiche apportate a un repository non influiscono sugli altri, eliminando così i conflitti di unione superflui.

Conclusione

Assegnate ai file il nome della classe che contengono, per garantire una navigazione intuitiva. Questa convenzione risulta particolarmente utile nei codici di grandi dimensioni, dove è fondamentale poter individuare rapidamente classi specifiche. I file aggiuntivi valgono la pena per la chiarezza organizzativa che garantiscono.

Domande frequenti

Hai delle domande?

E le piccole classi di supporto o le classi private?

Le piccole classi di supporto utilizzate esclusivamente da una classe padre possono rimanere nello stesso file se si tratta effettivamente di dettagli di implementazione. Tuttavia, se tali classi vengono riutilizzate o superano le 20-30 righe, è opportuno estrarle. Le classi private che esistono solo per supportare una classe pubblica costituiscono eccezioni ragionevoli.

Questo vale anche per le interfacce e i tipi di TypeScript?

I tipi e le interfacce utilizzati da una classe possono trovarsi nello stesso file. Tuttavia, i tipi condivisi da più file devono essere inseriti nei propri file di definizione dei tipi. Il fattore determinante è se la definizione viene utilizzata solo da quel file o se è necessaria anche altrove.

E che dire di linguaggi come Python, in cui l'uso di più classi è una pratica comune?

Le convenzioni di Python possono variare, ma il principio rimane lo stesso: raggruppare le classi correlate se formano un modulo coeso, evitando però di mescolare classi non correlate. Un file `models.py` contenente `User` e `UserProfile` ha senso. Un file `utils.py` con venti classi non correlate, invece, no.

Come posso organizzare classi correlate, come le classi "genitore" e "figlio"?

Salvateli in file separati all’interno della stessa directory. Per Animal e Dog, utilizzate animals/Animal.js e animals/Dog.js. La struttura delle directory evidenzia le relazioni, mantenendo al contempo una classe per file. Questo approccio garantisce una migliore scalabilità rispetto al raggruppamento di classi correlate in un unico file.

E se l'estrazione delle classi generasse molti file di piccole dimensioni?

È preferibile avere molti file piccoli piuttosto che pochi file grandi. Gli IDE moderni gestiscono in modo efficiente migliaia di file. La navigazione nel file system è più veloce rispetto alla ricerca all’interno di file di grandi dimensioni. La chiarezza derivante dal sapere esattamente dove si trova ogni classe supera la percezione di avere “troppi file”.

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.