Salta al contenuto principale

14 - Best Practices e Clean Code

Best Practices e Clean Code in Java Scrivere codice pulito significa creare software leggibile, manutenibile e professionale.

Cosa ne pensi di questo articolo?

14 - Best Practices e Clean Code
JavaFoto:Bibek ghoshPexels

In Breve

Best Practices e Clean Code in Java Scrivere codice pulito significa creare software leggibile, manutenibile e professionale.

Best Practices e Clean Code in Java

Scrivere codice pulito significa creare software leggibile, manutenibile e professionale. I principi SOLID e le best practices guidano verso un design robusto e scalabile.

Cosa Imparerai

  • Principi SOLID
  • Naming conventions
  • Code smell e refactoring
  • Gestione errori corretta
  • Documentazione efficace

Principi SOLID

S - Single Responsibility Principle
// ERRATO: classe con troppe responsabilità
class StudenteManager {
    void salvaStudente(Studente s) { /* salva su DB */ }
    void inviaEmail(Studente s) { /* invia email */ }
    String generaReport(Studente s) { /* genera report */ }
    void validaStudente(Studente s) { /* valida dati */ }
}

// CORRETTO: una responsabilità per classe
class StudenteRepository {
    void salva(Studente s) { /* salva su DB */ }
    Studente trova(Long id) { /* cerca su DB */ }
}

class EmailService {
    void invia(String to, String messaggio) { /* invia */ }
}

class StudenteReportGenerator {
    String genera(Studente s) { /* genera report */ }
}

class StudenteValidator {
    boolean isValido(Studente s) { /* valida */ }
}
O - Open/Closed Principle
// ERRATO: modifica necessaria per nuovi tipi
class CalcolatorePagamento {
    double calcola(String tipo, double importo) {
        if (tipo.equals("CARTA")) {
            return importo * 0.98;  // 2% fee
        } else if (tipo.equals("PAYPAL")) {
            return importo * 0.97;  // 3% fee
        }
        // Aggiungere nuovo else-if per ogni tipo...
        return importo;
    }
}

// CORRETTO: aperto per estensione, chiuso per modifica
interface MetodoPagamento {
    double calcolaImportoFinale(double importo);
}

class PagamentoCarta implements MetodoPagamento {
    public double calcolaImportoFinale(double importo) {
        return importo * 0.98;
    }
}

class PagamentoPayPal implements MetodoPagamento {
    public double calcolaImportoFinale(double importo) {
        return importo * 0.97;
    }
}

// Nuovo metodo: basta aggiungere classe
class PagamentoCrypto implements MetodoPagamento {
    public double calcolaImportoFinale(double importo) {
        return importo * 0.99;
    }
}
L - Liskov Substitution Principle
// ERRATO: sottoclasse che viola il contratto
class Rettangolo {
    protected int larghezza;
    protected int altezza;

    public void setLarghezza(int l) { larghezza = l; }
    public void setAltezza(int a) { altezza = a; }
    public int getArea() { return larghezza * altezza; }
}

class Quadrato extends Rettangolo {
    public void setLarghezza(int l) {
        larghezza = l;
        altezza = l;  // Viola il comportamento atteso!
    }
}

// CORRETTO: composizione invece di ereditarietà problematica
interface Forma {
    int getArea();
}

class RettangoloCorretto implements Forma {
    private final int larghezza;
    private final int altezza;

    RettangoloCorretto(int larghezza, int altezza) {
        this.larghezza = larghezza;
        this.altezza = altezza;
    }

    public int getArea() { return larghezza * altezza; }
}

class QuadratoCorretto implements Forma {
    private final int lato;

    QuadratoCorretto(int lato) {
        this.lato = lato;
    }

    public int getArea() { return lato * lato; }
}
I - Interface Segregation Principle
// ERRATO: interfaccia troppo grande
interface Worker {
    void lavora();
    void mangia();
    void dorme();
}

class Robot implements Worker {
    public void lavora() { /* ok */ }
    public void mangia() { /* non applicabile! */ }
    public void dorme() { /* non applicabile! */ }
}

// CORRETTO: interfacce segregate
interface Lavoratore {
    void lavora();
}

interface EssereVivente {
    void mangia();
    void dorme();
}

class Umano implements Lavoratore, EssereVivente {
    public void lavora() { /* implementa */ }
    public void mangia() { /* implementa */ }
    public void dorme() { /* implementa */ }
}

class RobotCorretto implements Lavoratore {
    public void lavora() { /* implementa solo ciò che serve */ }
}
D - Dependency Inversion Principle
// ERRATO: dipendenza da implementazione concreta
class NotificaService {
    private EmailSender emailSender = new EmailSender();

    void notifica(String messaggio) {
        emailSender.invia(messaggio);  // Accoppiamento stretto!
    }
}

// CORRETTO: dipende da astrazione
interface NotificaSender {
    void invia(String messaggio);
}

class EmailSenderImpl implements NotificaSender {
    public void invia(String messaggio) { /* email */ }
}

class SmsSender implements NotificaSender {
    public void invia(String messaggio) { /* sms */ }
}

class NotificaServiceCorretto {
    private final NotificaSender sender;

    // Dependency Injection
    NotificaServiceCorretto(NotificaSender sender) {
        this.sender = sender;
    }

    void notifica(String messaggio) {
        sender.invia(messaggio);
    }
}

Naming Conventions

Nomi Significativi
// ERRATO: nomi poco chiari
int d;  // cosa rappresenta?
String s;
List<String> list;
void proc(int x);

// CORRETTO: nomi descrittivi
int giorniDallaScadenza;
String nomeStudente;
List<String> emailsAttivi;
void calcolaMediaVoti(int studenteId);

// Convenzioni Java
class NomeClasse {}              // PascalCase
interface Calcolabile {}          // PascalCase
void nomeMetodo() {}              // camelCase
int variabileLocale;              // camelCase
static final int COSTANTE = 10;   // UPPER_SNAKE_CASE

// Booleani: prefisso is/has/can
boolean isAttivo;
boolean hasPermesso;
boolean canModificare;

// Collezioni: plurale
List<Studente> studenti;
Map<String, Integer> votiPerMateria;

// Metodi: verbo + nome
void salvaStudente(Studente s);
Studente trovaStudenteById(Long id);
boolean isStudenteAttivo(Studente s);
List<Studente> getStudentiAttivi();

Code Smell e Refactoring

Esempi di Code Smell
// CODE SMELL: Metodo troppo lungo
void processaOrdine(Ordine o) {
    // 50+ righe di codice...
}

// REFACTORING: Estrai metodi
void processaOrdine(Ordine o) {
    validaOrdine(o);
    calcolaTotale(o);
    applicaSconti(o);
    salvaOrdine(o);
    inviaConferma(o);
}

// CODE SMELL: Magic numbers
if (eta > 18 && punteggio > 75) {
    // ...
}

// REFACTORING: Costanti con nome
private static final int ETA_MINIMA = 18;
private static final int PUNTEGGIO_SUFFICIENTE = 75;

if (eta > ETA_MINIMA && punteggio > PUNTEGGIO_SUFFICIENTE) {
    // ...
}

// CODE SMELL: Parametri booleani
void creaUtente(String nome, boolean admin, boolean attivo);

// REFACTORING: Builder o oggetti
class ConfigurazioneUtente {
    private boolean admin;
    private boolean attivo;
    // builder pattern...
}

// CODE SMELL: Commenti che spiegano codice complesso
// Calcola la media pesata considerando i crediti
double r = 0;
int t = 0;
for (int i = 0; i < v.length; i++) {
    r += v[i] * c[i];
    t += c[i];
}
return r / t;

// REFACTORING: Codice auto-documentante
double calcolaMediaPesata(int[] voti, int[] crediti) {
    double sommaPesata = 0;
    int totaleCrediti = 0;

    for (int i = 0; i < voti.length; i++) {
        sommaPesata += voti[i] * crediti[i];
        totaleCrediti += crediti[i];
    }

    return sommaPesata / totaleCrediti;
}

Gestione Errori

Best Practices Eccezioni
// ERRATO: catch generico
try {
    // operazione
} catch (Exception e) {
    // gestione troppo generica
}

// CORRETTO: catch specifici
try {
    // operazione
} catch (FileNotFoundException e) {
    // gestisci file non trovato
} catch (IOException e) {
    // gestisci altri errori I/O
}

// ERRATO: eccezione ignorata
try {
    riskyOperation();
} catch (Exception e) {
    // silenzioso - MAI fare così!
}

// CORRETTO: almeno logga
try {
    riskyOperation();
} catch (Exception e) {
    logger.error("Errore durante operazione", e);
    throw new ServiceException("Operazione fallita", e);
}

// Eccezioni personalizzate significative
class StudenteNonTrovatoException extends RuntimeException {
    private final Long studenteId;

    StudenteNonTrovatoException(Long id) {
        super("Studente non trovato con ID: " + id);
        this.studenteId = id;
    }

    public Long getStudenteId() {
        return studenteId;
    }
}

// Usa Optional invece di null
Optional<Studente> trovaStudente(Long id);

// Chiamante
Studente s = trovaStudente(id)
    .orElseThrow(() -> new StudenteNonTrovatoException(id));

Best Practices Generali

Regole d'Oro

  • DRY: Don't Repeat Yourself
  • KISS: Keep It Simple, Stupid
  • YAGNI: You Aren't Gonna Need It
  • Fail Fast: Valida input all'inizio
  • Immutabilità: Preferisci oggetti immutabili
  • Composizione > Ereditarietà: Più flessibile
  • Programma su interfacce: Non implementazioni
  • Single Level of Abstraction: Un livello per metodo
Esempio Codice Pulito
// Classe ben strutturata
public class StudenteService {

    private final StudenteRepository repository;
    private final EmailService emailService;
    private final StudenteValidator validator;

    public StudenteService(StudenteRepository repository,
                          EmailService emailService,
                          StudenteValidator validator) {
        this.repository = Objects.requireNonNull(repository);
        this.emailService = Objects.requireNonNull(emailService);
        this.validator = Objects.requireNonNull(validator);
    }

    public Studente registra(RegistrazioneRequest request) {
        validator.valida(request);

        Studente studente = creaStudente(request);
        Studente salvato = repository.salva(studente);

        inviaEmailBenvenuto(salvato);

        return salvato;
    }

    private Studente creaStudente(RegistrazioneRequest request) {
        return Studente.builder()
            .nome(request.getNome())
            .email(request.getEmail())
            .dataRegistrazione(LocalDate.now())
            .build();
    }

    private void inviaEmailBenvenuto(Studente studente) {
        emailService.invia(
            studente.getEmail(),
            "Benvenuto " + studente.getNome()
        );
    }
}

Domande frequenti

Quali sono i principi fondamentali per scrivere codice pulito in Java?

I principi SOLID (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation e Dependency Inversion) e le best practices sono essenziali per creare software leggibile, manutenibile e professionale.

Qual è l'importanza dei Naming Conventions nel codice Java?

I Naming Conventions aiutano a rendere il codice più leggibile e facile da comprendere, riducendo così gli errori di interpretazione e migliorando la collaborazione tra i developer.

Come si identificano e si risolvono i Code Smell nel codice Java?

I Code Smell possono essere identificati attraverso l'analisi del codice e le best practices, mentre vengono risolti attraverso il refactoring, ovvero la ristrutturazione del codice per renderlo più pulito e efficiente.

Qual è il ruolo della Gestione Errori nel codice Java?

La Gestione Errori è fondamentale per garantire che il software sia robusto e scalabile, prevedendo e gestendo eventuali errori o eccezioni durante l'esecuzione del codice.

Fonti e approfondimenti

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti