JavaRush /Java Blog /Random-IT /Pausa caffè #85. Tre lezioni di Java che ho imparato nel ...

Pausa caffè #85. Tre lezioni di Java che ho imparato nel modo più duro. Come utilizzare i principi SOLID nel codice

Pubblicato nel gruppo Random-IT

Tre lezioni di Java che ho imparato nel modo più duro

Fonte: Medium Learning Java è difficile. Ho imparato dai miei errori. Ora anche tu puoi imparare dai miei errori e dalle mie amare esperienze, che non necessariamente devi avere. Pausa caffè #85.  Tre lezioni di Java che ho imparato nel modo più duro.  Come utilizzare i principi SOLID nel codice - 1

1. I Lambda possono causare problemi.

I lambda spesso superano le 4 righe di codice e sono più grandi del previsto. Ciò grava sulla memoria di lavoro. Hai bisogno di cambiare una variabile da lambda? Non puoi farlo. Perché? Se lambda può accedere alle variabili del sito di chiamata, potrebbero verificarsi problemi di threading. Pertanto non è possibile modificare le variabili da lambda. Ma il percorso felice in lambda funziona bene. Dopo un errore di runtime, riceverai questa risposta:
at [CLASS].lambda$null$2([CLASS].java:85)
at [CLASS]$$Lambda$64/730559617.accept(Unknown Source)
È difficile seguire l'analisi dello stack lambda. I nomi sono confusi e difficili da rintracciare ed eseguire il debug. Più lambda = più tracce dello stack. Qual è il modo migliore per eseguire il debug di lambda? Utilizza risultati intermedi.
map(elem -> {
 int result = elem.getResult();
 return result;
});
Un altro buon modo è utilizzare tecniche avanzate di debug IntelliJ. Utilizza TAB per selezionare il codice di cui vuoi eseguire il debug e combinarlo con i risultati intermedi. “Quando ci fermiamo su una riga contenente un lambda, se premiamo F7 (entra), IntelliJ evidenzia il frammento di cui deve essere eseguito il debug. Possiamo cambiare il blocco per eseguire il debug utilizzando Tab e, una volta deciso, premere nuovamente F7. " Come accedere alle variabili del sito di chiamata da lambda? È possibile accedere solo alle variabili finali o effettivamente finali. È necessario creare un wrapper attorno alle variabili di posizione della chiamata. O usando AtomicType o usando il tuo tipo. Puoi modificare il wrapper della variabile creato con lambda. Come risolvere i problemi di traccia dello stack? Utilizza funzioni con nome. In questo modo puoi trovare rapidamente il codice responsabile, controllare la logica e risolvere i problemi. Utilizzare funzioni con nome per eliminare la criptica traccia dello stack. Si ripete la stessa lambda? Inseriscilo in una funzione con nome. Avrai un unico punto di riferimento. Ogni lambda riceve una funzione generata, il che rende difficile tenerne traccia.
lambda$yourNamedFunction
lambda$0
Le funzioni con nome risolvono un problema diverso. Grandi lambda. Le funzioni con nome suddividono lambda di grandi dimensioni, creano parti di codice più piccole e creano funzioni collegabili.
.map(this::namedFunc1).filter(this::namedFilter1).map(this::namedFunc2)

2. Problemi con le liste

Devi lavorare con gli elenchi ( Elenchi ). Hai bisogno di una HashMap per i dati. Per i ruoli avrai bisogno di una TreeMap . L'elenco potrebbe continuare. E non c'è modo di evitare di lavorare con le raccolte. Come fare una lista? Che tipo di elenco ti serve? Dovrebbe essere immutabile o mutevole? Tutte queste risposte influenzano il futuro del tuo codice. Scegli l'elenco giusto in anticipo in modo da non pentirtene in seguito. Arrays::asList crea un elenco "end-to-end". Cosa non puoi fare con questa lista? Non puoi ridimensionarlo. È immutabile. Che cosa si può fare qui? Specifica elementi, ordinamento o altre operazioni che non influiscono sulle dimensioni. Usa Arrays::asList con attenzione perché la sua dimensione è immutabile ma il suo contenuto non lo è. new ArrayList() crea un nuovo elenco "mutabile". Quali operazioni supporta l'elenco creato? Questo è tutto, e questo è un motivo per stare attenti. Crea elenchi mutabili da quelli immutabili utilizzando new ArrayList() . List::of crea una raccolta “immutabile”. Le sue dimensioni e il suo contenuto rimangono invariati a determinate condizioni. Se il contenuto è costituito da dati primitivi, come int , l'elenco è immutabile. Dai un'occhiata al seguente esempio.
@Test
public void testListOfBuilders() {
  System.out.println("### TESTING listOF with mutable content ###");

  StringBuilder one = new StringBuilder();
  one.append("a");

  StringBuilder two = new StringBuilder();
  two.append("a");

  List<StringBuilder> asList = List.of(one, two);

  asList.get(0).append("123");

  System.out.println(asList.get(0).toString());
}
### TEST di listOF con contenuto mutabile ### a123
È necessario creare oggetti immutabili e inserirli in List::of . Tuttavia, List::of non fornisce alcuna garanzia di immutabilità. List::of fornisce immutabilità, affidabilità e leggibilità. Sapere quando utilizzare strutture mutabili e quando utilizzare strutture immutabili. L'elenco degli argomenti che non dovrebbero cambiare dovrebbe essere nell'elenco immutabile. Una lista mutabile può essere una lista mutabile. Comprendi quale raccolta ti serve per creare codice affidabile.

3. Le annotazioni ti rallentano

Usi le annotazioni? Li capisci? Sai cosa fanno? Se pensi che l'annotazione registrata sia adatta a tutti i metodi, allora ti sbagli. Ho usato Logged per registrare gli argomenti del metodo. Con mia sorpresa, non ha funzionato.
@Transaction
@Method("GET")
@PathElement("time")
@PathElement("date")
@Autowired
@Secure("ROLE_ADMIN")
public void manage(@Qualifier('time')int time) {
...
}
Cosa c'è di sbagliato in questo codice? C'è un sacco di digest di configurazione qui. Lo incontrerai molte volte. La configurazione è mista con il codice normale. Non è male di per sé, ma attira la tua attenzione. Le annotazioni sono necessarie per ridurre il codice boilerplate. Non è necessario scrivere la logica di registrazione per ciascun endpoint. Non è necessario impostare transazioni, utilizza @Transactional . Le annotazioni riducono il modello estraendo il codice. Non esiste un vincitore chiaro qui poiché entrambi sono in gioco. Utilizzo ancora XML e annotazioni. Quando trovi uno schema ripetuto, è meglio spostare la logica nell'annotazione. Ad esempio, la registrazione è una buona opzione per le annotazioni. Morale: non abusare delle annotazioni e non dimenticare XML.

Bonus: potresti avere problemi con Opzionale

Utilizzerai orElse from Opzionale . Un comportamento indesiderato si verifica quando non si passa la costante orElse . Dovresti esserne consapevole per evitare problemi in futuro. Diamo un'occhiata ad alcuni esempi. Quando getValue(x) restituisce un valore, viene eseguito getValue(y) . Il metodo in orElse viene eseguito se getValue(x) restituisce un valore opzionale non vuoto .
getValue(x).orElse(getValue(y)
                  .orElseThrow(() -> new NotFoundException("value not present")));

public Optional<Value> getValue(Source s)
{
  System.out.println("Source: " + s.getName());

  // returns value from s source
}

// when getValue(x) is present system will output
Source: x
Source: y
Utilizzare orElseGet . Non eseguirà codice per Options non vuoti .
getValue(x).orElseGet(() -> getValue(y)
                  .orElseThrow(() -> new NotFoundException("value not present")));

public Optional<Value> getValue(Source s)
{
  System.out.println("Source: " + s.getName());

  // returns value from s source
}

// when getValue(x) is present system will output
Source: x

Conclusione

Imparare Java è difficile. Non puoi imparare Java in 24 ore. Affina le tue abilità. Prenditi tempo, impara ed eccelle nel tuo lavoro.

Come utilizzare i principi SOLID nel codice

Fonte: Cleanthecode Scrivere codice affidabile richiede principi SOLIDI. Ad un certo punto tutti abbiamo dovuto imparare a programmare. E siamo onesti. Eravamo STUPIDI. E il nostro codice era lo stesso. Grazie a Dio abbiamo SOLIDITÀ. Pausa caffè #85.  Tre lezioni di Java che ho imparato nel modo più duro.  Come utilizzare i principi SOLID nel codice - 2

Principi SOLIDI

Allora come si scrive il codice SOLID? In realtà è semplice. Devi solo seguire queste cinque regole:
  • Principio di responsabilità unica
  • Principio aperto-chiuso
  • Principio di sostituzione di Liskov
  • Principio di separazione delle interfacce
  • Principio di inversione delle dipendenze
Non preoccuparti! Questi principi sono molto più semplici di quanto sembri!

Principio di responsabilità unica

Nel suo libro, Robert C. Martin descrive questo principio come segue: “Una classe dovrebbe avere una sola ragione per cambiare”. Vediamo insieme due esempi.

1. Cosa non fare

Abbiamo una classe chiamata User che consente all'utente di fare le seguenti cose:
  • Registra un account
  • Login
  • Ricevi una notifica al primo accesso
Questa classe ora ha diverse responsabilità. Se cambia il processo di registrazione, cambierà la classe Utente . Lo stesso accadrà se il processo di accesso o il processo di notifica cambiano. Ciò significa che la classe è sovraccarica. Ha troppe responsabilità. Il modo più semplice per risolvere questo problema è spostare la responsabilità sulle classi in modo che la classe Utente sia responsabile solo della combinazione delle classi. Se il processo poi cambia, hai una classe chiara e separata che deve cambiare.

2. Cosa fare

Immagina una classe che dovrebbe mostrare una notifica a un nuovo utente, FirstUseNotification . Consisterà di tre funzioni:
  • Controlla se è già stata visualizzata una notifica
  • Mostra notifica
  • Contrassegna la notifica come già mostrata
Questa classe ha più ragioni per cambiare? NO. Questa classe ha una funzione chiara: visualizzare una notifica per un nuovo utente. Ciò significa che la classe ha una ragione per cambiare. Vale a dire, se questo obiettivo cambia. Quindi, questa classe non viola il principio di responsabilità unica. Naturalmente, ci sono alcune cose che potrebbero cambiare: il modo in cui le notifiche vengono contrassegnate come lette potrebbe cambiare o il modo in cui viene visualizzata la notifica. Tuttavia, poiché lo scopo della lezione è chiaro e basilare, va bene.

Principio aperto-chiuso

Il principio aperto-chiuso è stato coniato da Bertrand Meyer: “Gli oggetti software (classi, moduli, funzioni, ecc.) dovrebbero essere aperti all’estensione, ma chiusi alla modifica”. Questo principio è in realtà molto semplice. È necessario scrivere il codice in modo che sia possibile aggiungervi nuove funzionalità senza modificare il codice sorgente. Ciò aiuta a prevenire una situazione in cui è necessario modificare le classi che dipendono dalla classe modificata. Tuttavia, questo principio è molto più difficile da implementare. Meyer ha suggerito di utilizzare l'ereditarietà. Ma porta a una forte connessione. Ne discuteremo nei Principi di separazione delle interfacce e nei Principi di inversione delle dipendenze. Quindi Martin ha trovato un approccio migliore: usare il polimorfismo. Invece dell'ereditarietà convenzionale, questo approccio utilizza classi base astratte. In questo modo, le specifiche di ereditarietà possono essere riutilizzate mentre non è richiesta l'implementazione. L'interfaccia può essere scritta una volta e poi chiusa per apportare modifiche. Le nuove funzioni devono quindi implementare questa interfaccia ed estenderla.

Principio di sostituzione di Liskov

Questo principio è stato inventato da Barbara Liskov, vincitrice del Premio Turing per i suoi contributi ai linguaggi di programmazione e alla metodologia del software. Nel suo articolo definisce il suo principio come segue: “Gli oggetti in un programma dovrebbero essere sostituibili con istanze dei loro sottotipi senza compromettere la corretta esecuzione del programma”. Diamo un'occhiata a questo principio come programmatore. Immaginiamo di avere un quadrato. Potrebbe essere un rettangolo, il che sembra logico poiché un quadrato è una forma speciale di un rettangolo. È qui che viene in soccorso il principio di sostituzione di Liskov. Ovunque ti aspetteresti di vedere un rettangolo nel tuo codice, è anche possibile che appaia un quadrato. Ora immagina che il tuo rettangolo abbia i metodi SetWidth e SetHeight . Ciò significa che anche la piazza ha bisogno di questi metodi. Sfortunatamente, questo non ha alcun senso. Ciò significa che qui viene violato il principio di sostituzione di Liskov.

Principio di separazione delle interfacce

Come tutti i principi, quello della separazione delle interfacce è molto più semplice di quanto sembri: “Molte interfacce specifiche del cliente sono migliori di un’interfaccia generica”. Come nel caso del principio di responsabilità unica, l’obiettivo è ridurre gli effetti collaterali e il numero di modifiche necessarie. Ovviamente nessuno scrive un codice del genere di proposito. Ma è facile incontrarlo. Ricordi il quadrato del principio precedente? Ora immagina di decidere di attuare il nostro piano: produciamo un quadrato da un rettangolo. Ora stiamo forzando il quadrato a implementare setWidth e setHeight , che probabilmente non fanno nulla. Se lo facessero, probabilmente romperemmo qualcosa perché la larghezza e l'altezza non sarebbero quelle che ci aspettavamo. Fortunatamente per noi, questo significa che non stiamo più violando il principio di sostituzione di Liskov, poiché ora consentiamo l’uso di un quadrato ovunque utilizziamo un rettangolo. Ciò crea però un nuovo problema: stiamo violando il principio della separazione delle interfacce. Forziamo la classe derivata a implementare la funzionalità che sceglie di non utilizzare.

Principio di inversione delle dipendenze

L'ultimo principio è semplice: i moduli di alto livello dovrebbero essere riutilizzabili e non dovrebbero essere influenzati dalle modifiche ai moduli di basso livello.
  • R. I moduli di alto livello non dovrebbero dipendere dai moduli di basso livello. Entrambi devono dipendere da astrazioni (come le interfacce).
  • B. Le astrazioni non dovrebbero dipendere dai dettagli. I dettagli (implementazioni concrete) devono dipendere dalle astrazioni.
Ciò può essere ottenuto implementando un'astrazione che separa i moduli di alto e basso livello. Il nome del principio suggerisce che la direzione della dipendenza cambia, ma non è così. Separa solo la dipendenza introducendo un'astrazione tra di loro. Di conseguenza, otterrai due dipendenze:
  • Modulo di alto livello, dipendente dall'astrazione
  • Modulo di basso livello dipendente dalla stessa astrazione
Questo può sembrare difficile, ma in realtà avviene automaticamente se si applica correttamente il principio aperto/chiuso e il principio di sostituzione di Liskov. È tutto! Ora hai imparato i cinque principi fondamentali su cui si basa SOLID. Con questi cinque principi puoi rendere il tuo codice straordinario!
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION