Principi del Codice Pulito in Java
Principi del codice pulito in Java: metodi piccoli, nomi significativi, responsabilità unica e mutazione minima.
Il codice pulito è codice che la persona successiva — spesso tu stesso, sei mesi dopo — può leggere, fidarsi e modificare senza timore. Il compilatore accetta quasi tutto; il codice pulito riguarda l'altro pubblico, gli esseri umani che lo mantengono. Nessuno dei principi seguenti è un'invenzione specifica di Java, ma Java ti fornisce strumenti concreti — metodi piccoli, campi final, record, eccezioni, tipi significativi — per applicarli. Questo capitolo illustra quelli che danno risultati ogni singolo giorno.
Nomi che rivelano l'intento
Un buon nome risponde al perché il valore esiste, non solo al tipo che è. Se un nome necessita di un commento per spiegarlo, il nome è sbagliato. Evita le singole lettere (al di fuori di cicli brevi), evita abbreviazioni comprensibili solo a te, e preferisci un nome descrittivo più lungo rispetto a uno breve e criptico — il tuo IDE lo autocompleta comunque.
// Unclear: what is d? what unit? what is the magic 86400000?
int d = (t2 - t1) / 86400000;
// Clear: the names and a constant carry the meaning
long MILLIS_PER_DAY = 24L * 60 * 60 * 1000;
long elapsedDays = (endMillis - startMillis) / MILLIS_PER_DAY;I boolean si leggono meglio come domande o stati: isActive, hasNext, shouldRetry. I metodi che fanno qualcosa ottengono nomi verbali (calculateTotal); i metodi che restituiscono qualcosa ottengono nomi sostantivali o getter (total, getTotal). Per le convenzioni a livello di linguaggio alla base di queste scelte, consulta Java naming conventions e Java naming best practices.
Metodi piccoli con una sola responsabilità
Un metodo dovrebbe fare una cosa sola a un solo livello di astrazione. Quando ti ritrovi ad aggiungere un commento come // now validate the input, quel blocco di solito vuole diventare un proprio metodo ben nominato. I metodi brevi sono più facili da nominare, testare e riutilizzare — e il sito di chiamata si legge come una frase.
// Before: one method juggling validation, calculation, and formatting
String receipt(Order order) {
if (order == null || order.items().isEmpty())
throw new IllegalArgumentException("empty order");
int total = 0;
for (var i : order.items()) total += i.price() * i.qty();
return "Total: $" + total / 100 + "." + (total % 100);
}
// After: each step is a named method; receipt() now reads top-down
String receipt(Order order) {
requireNonEmpty(order);
int total = totalCents(order);
return formatCents(total);
}Le clausole di guardia mantengono i metodi piatti. Gestisci i casi limite e ritorna anticipatamente invece di avvolgere il percorso principale in blocchi if sempre più profondi.
Preferire l'immutabilità e la mutazione minima
Lo stato condiviso mutabile è la radice della maggior parte dei bug di concorrenza e di molta confusione ordinaria. Imposta per default campi e variabili locali come final; rilassa il vincolo verso il mutabile solo quando hai un motivo. I record di Java rendono gli oggetti valore immutabili una cosa da una riga, generando un costruttore canonico, equals, hashCode e toString.
// An immutable value object with validation in the compact constructor
record Money(long cents, String currency) {
Money {
if (cents < 0) throw new IllegalArgumentException("cents must be >= 0");
}
Money plus(Money other) { return new Money(cents + other.cents, currency); }
}Nota che plus restituisce un nuovo Money invece di mutare this. Gli oggetti immutabili sono sicuri da condividere tra thread, sicuri da usare come chiavi di mappa e impossibili da corrompere dopo la costruzione. Per approfondire, consulta Java records, immutable classes e immutability best practices; la parola chiave final copre il blocco di campi e variabili.
Fallire velocemente e usare le eccezioni, non i codici di errore
Valida gli argomenti al confine e lancia immediatamente quando qualcosa è sbagliato, così il fallimento emerge vicino alla sua causa anziché tre livelli più in profondità come un confuso NullPointerException. Usa le eccezioni per segnalare errori; non restituire null o valori sentinella magici che ogni chiamante deve ricordarsi di verificare.
| Schema | Da evitare | Da preferire |
|---|---|---|
| Valore mancante | return null; | Optional<T> oppure throw |
| Argomento non valido | return -1; | throw new IllegalArgumentException(...) |
| Stato impossibile | default silenzioso | throw new IllegalStateException(...) |
| Pulizia risorse | finally manuale | try-with-resources |
static User findUser(String id) {
Objects.requireNonNull(id, "id must not be null"); // fail fast
return repository.lookup(id)
.orElseThrow(() -> new NoSuchElementException("no user: " + id));
}Objects.requireNonNull trasforma un NPE vago a valle in un messaggio preciso al punto di ingresso. Per modellare "potrebbe essere assente" senza null, leggi Java Optional; per progettare tipi di errore, consulta exception best practices e custom exceptions.
DRY, ma senza eccedere nell'astrazione
DRY (Don't Repeat Yourself) significa che una singola conoscenza vive in un unico posto. Quando la stessa costante o calcolo appare due volte, estraila. Ma resisti all'errore opposto: due frammenti che oggi sembrano simili potrebbero divergere domani. Il codice duplicato è più economico da correggere rispetto all'astrazione sbagliata. Estrai quando il significato è condiviso, non solo la sintassi.
// Knowledge duplicated: the threshold lives in two places
if (order.total() >= 5000) freeShip = true; // here
if (cart.total() >= 5000) showBadge = true; // and here
// One source of truth
static final int FREE_SHIPPING_THRESHOLD_CENTS = 5000;
boolean qualifiesForFreeShipping(int totalCents) {
return totalCents >= FREE_SHIPPING_THRESHOLD_CENTS;
}Un esempio pratico: un carrello della spesa pulito
Questo programma riunisce i principi in un unico file piccolo ed eseguibile: un record LineItem immutabile che si auto-valida, metodi a scopo singolo con nomi che rivelano l'intento, costanti nominate invece di numeri magici, una clausola di guardia e uguaglianza basata sui valori. Non servono commenti per spiegare cosa fa — sono i nomi a farlo.
Cosa trarre dall'esecuzione:
- L'output si legge esattamente come il dominio che modella —
2 x Notebook @ $12.50 = $25.00— perché ogni metodo e campo è nominato in base al suo scopo. Non hai avuto bisogno di un solo commento per seguire i calcoli del carrello; i nomi che rivelano l'intento hanno fatto la documentazione. - Il subtotale è
$30.97e la spedizione è$5.99, non gratuita: la clausola di guardia dishippingCentsha confrontato il subtotale con la costante nominataFREE_SHIPPING_THRESHOLD_CENTS(5000), e 3097 è al di sotto. Il numero magico vive esattamente in un posto, quindi la regola è impossibile da rendere incoerente. value equality: truedimostra che ilrecordci ha fornitoequalsper valore gratuitamente — due oggettiPencostruiti separatamente sono uguali perché i loro dati sono uguali. Scrivere quella coppiaequals/hashCodea mano è codice boilerplate che non devi più mantenere.rejected bad item: quantity must be >= 0mostra il fail-fast in azione: il costruttore compatto ha validato l'argomento e ha lanciatoIllegalArgumentExceptional momento della costruzione, quindi una quantità-1non può mai entrare nel sistema come dato errato silenzioso.- Ogni helper —
subtotalCents,shippingCents,formatCents— fa una cosa sola, quindimainsi legge dall'alto verso il basso come una storia. I metodi piccoli a responsabilità singola sono ciò che rende l'intero programma scorrevole anziché un muro di logica annidata.