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.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À.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
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
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
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.
- Modulo di alto livello, dipendente dall'astrazione
- Modulo di basso livello dipendente dalla stessa astrazione
GO TO FULL VERSION