Perché Java Opzionale 8 non dovrebbe essere usato negli argomenti

Ho letto su molti siti Web. Opzionale dovrebbe essere usato solo come tipo di ritorno e non utilizzato negli argomenti del metodo. Sto lottando per trovare una ragione logica per cui. Ad esempio, ho un pezzo di logica che ha 2 parametri opzionali. Quindi penso che avrebbe senso scrivere la mia firma del metodo in questo modo (soluzione 1):

public int calculateSomething(Optional p1, Optional p2 { // my logic } 

Molte pagine Web specificano Opzionale non dovrebbe essere usato come argomenti del metodo. Con questo in mente, potrei usare la seguente firma del metodo e aggiungere un commento Javadoc chiaro per specificare che gli argomenti potrebbero essere nulli, sperando che i futuri manutentori leggeranno il Javadoc e quindi eseguiranno sempre controlli nulli prima di usare gli argomenti (soluzione 2) :

 public int calculateSomething(String p1, BigDecimal p2) { // my logic } 

In alternativa, potrei sostituire il mio metodo con quattro metodi pubblici per fornire un’interfaccia più gradevole e renderlo più ovvio. P1 e p2 sono opzionali (soluzione 3):

 public int calculateSomething() { calculateSomething(null, null); } public int calculateSomething(String p1) { calculateSomething(p1, null); } public int calculateSomething(BigDecimal p2) { calculateSomething(null, p2); } public int calculateSomething(String p1, BigDecimal p2) { // my logic } 

Ora provo a scrivere il codice della class che invoca questa logica per ogni approccio. Per prima cosa richiamo i due parametri di input da un altro object che restituisce Optional s e quindi invoco calculateSomething . Pertanto, se viene utilizzata la soluzione 1, il codice chiamante sarà simile al seguente:

 Optional p1 = otherObject.getP1(); Optional p2 = otherObject.getP2(); int result = myObject.calculateSomething(p1, p2); 

se viene utilizzata la soluzione 2, il codice chiamante sarà simile al seguente:

 Optional p1 = otherObject.getP1(); Optional p2 = otherObject.getP2(); int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null)); 

se si applica la soluzione 3, potrei usare il codice sopra o potrei usare quanto segue (ma è molto più codice):

 Optional p1 = otherObject.getP1(); Optional p2 = otherObject.getP2(); int result; if (p1.isPresent()) { if (p2.isPresent()) { result = myObject.calculateSomething(p1, p2); } else { result = myObject.calculateSomething(p1); } } else { if (p2.isPresent()) { result = myObject.calculateSomething(p2); } else { result = myObject.calculateSomething(); } } 

Quindi la mia domanda è: perché è considerata una ctriggers pratica usare gli s Optional come argomenti del metodo (vedi soluzione 1)? Sembra la soluzione più leggibile per me e rende più ovvio che i parametri potrebbero essere vuoti / nulli per i manutentori futuri. (Sono consapevole del fatto che i progettisti di Optional intendevano usarlo solo come un tipo di ritorno, ma non riesco a trovare alcun motivo logico per non usarlo in questo scenario).

Oh, quelli stili di codifica devono essere presi con un po ‘di sale.

  1. (+) Passare un risultato Opzionale ad un altro metodo, senza alcuna analisi semantica; lasciando questo al metodo, va tutto bene.
  2. (-) L’utilizzo di parametri opzionali che causano la logica condizionale all’interno dei metodi è letteralmente controproducente.
  3. (-) Necessario compattare un argomento in un Opzionale, non è ottimale per il compilatore e esegue un wrapping non necessario.
  4. (-) Rispetto ai parametri nullable Opzionale è più costoso.

In generale: opzionale unifica due stati, che devono essere svelati. Quindi meglio adatto per il risultato di input, per la complessità del stream di dati.

Il miglior post che ho visto sull’argomento è stato scritto da Daniel Olszewski :

Sebbene potrebbe essere allettante considerare Opzionale per i parametri del metodo non obbligatori, una soluzione di questo tipo impallidisce rispetto ad altre possibili alternative. Per illustrare il problema, esaminare la seguente dichiarazione del costruttore:

 public SystemMessage(String title, String content, Optional attachment) { // assigning field values } 

A prima vista potrebbe sembrare una giusta decisione di progettazione. Dopotutto, abbiamo contrassegnato in modo esplicito il parametro allegato come facoltativo. Tuttavia, come per chiamare il costruttore, il codice client può diventare un po ‘goffo.

 SystemMessage withoutAttachment = new SystemMessage("title", "content", Optional.empty()); Attachment attachment = new Attachment(); SystemMessage withAttachment = new SystemMessage("title", "content", Optional.ofNullable(attachment)); 

Invece di fornire chiarezza, i metodi factory della class Optional distraggono solo il lettore. Nota c’è solo un parametro opzionale, ma immagina di avere due o tre. Zio Bob sicuramente non sarebbe fiero di tale codice 😉

Quando un metodo può accettare parametri opzionali, è preferibile adottare l’approccio ben collaudato e progettare tale caso utilizzando l’overloading del metodo. Nell’esempio della class SystemMessage, la dichiarazione di due distinti costruttori è superiore all’utilizzo facoltativo.

 public SystemMessage(String title, String content) { this(title, content, null); } public SystemMessage(String title, String content, Attachment attachment) { // assigning field values } 

Questo cambiamento rende il codice client molto più semplice e facile da leggere.

 SystemMessage withoutAttachment = new SystemMessage("title", "content"); Attachment attachment = new Attachment(); SystemMessage withAttachment = new SystemMessage("title", "content", attachment); 

Non ci sono quasi buoni motivi per non usare Optional come parametri. Gli argomenti contro questo si basano su argomenti di autorità (vedi Brian Goetz – il suo argomento è che non possiamo imporre opzioni non nulle) o che gli argomenti Optional possono essere nulli (essenzialmente lo stesso argomento). Ovviamente, qualsiasi riferimento in Java può essere nullo, dobbiamo incoraggiare le regole che vengono applicate dal compilatore, non dalla memoria dei programmatori (che è problematica e non scala).

I linguaggi di programmazione funzionale incoraggiano i parametri Optional . Uno dei modi migliori per usarlo è avere più parametri opzionali e usare liftM2 per usare una funzione assumendo che i parametri non siano vuoti e restituendo un optional (vedi http://www.functionaljava.org/javadoc/4.4/functionaljava/fj /data/Option.html#liftM2-fj.F- ). Sfortunatamente, Java 8 ha implementato una libreria molto limitata a supporto opzionale.

Come programmatori Java dovremmo usare null solo per interagire con le librerie legacy.

Questo consiglio è una variante della regola del “essere il più preciso ansible per quanto riguarda gli input e il più specifico ansible per quanto riguarda gli output”.

Di solito, se si dispone di un metodo che accetta un valore normale non nullo, è ansible mapparlo su Optional , quindi la versione semplice è strettamente più aspecifica per quanto riguarda gli input. Tuttavia ci sono un sacco di possibili motivi per cui si vorrebbe richiedere un argomento Optional , tuttavia:

  • vuoi che la tua funzione venga utilizzata insieme a un’altra API che restituisce un Optional
  • La tua funzione dovrebbe restituire qualcosa di diverso da un Optional vuoto se il valore dato è vuoto
  • Pensi che Optional sia così fantastico che a chiunque usi la tua API dovrebbe essere richiesto di saperne di più 😉

Il pattern con Optional è per uno per evitare di restituire null . È ancora perfettamente ansible passare in null a un metodo.

Anche se questi non sono ancora ufficiali, è ansible utilizzare annotazioni in stile JSR-308 per indicare se si accettano o meno valori null nella funzione. Tieni presente che dovresti avere gli strumenti giusti per identificarlo effettivamente e fornirebbe un controllo statico maggiore rispetto a un criterio runtime applicabile, ma sarebbe d’aiuto.

 public int calculateSomething(@NotNull final String p1, @NotNull final String p2) {} 

Mi sembra un po ‘sciocco, ma l’unica ragione per cui posso pensare è che gli argomenti object nei parametri del metodo sono già opzionali in un modo – possono essere nulli. Quindi forzare qualcuno a prendere un object esistente e avvolgerlo in un optional è un po ‘inutile.

Detto questo, concatenare metodi che prendono / restituiscono optionals è una cosa ragionevole da fare, ad esempio, forse Monad.

La mia opinione è che Opzionale dovrebbe essere una Monade e questi non sono concepibili in Java.

Nella programmazione funzionale si gestiscono funzioni di ordine puro e superiore che prendono e compongono i propri argomenti solo in base al loro “tipo di dominio aziendale”. La composizione di funzioni che si nutrono, o di cui il calcolo deve essere riferito, il mondo reale (i cosiddetti effetti collaterali) richiede l’applicazione di funzioni che si occupano di decomprimere automaticamente i valori dalle monadi che rappresentano il mondo esterno (Stato, Configurazione, Futures, Forse, O, Writer, ecc …); questo è chiamato sollevamento. Puoi considerarlo come una sorta di separazione delle preoccupazioni.

Mescolare questi due livelli di astrazione non facilita la leggibilità, quindi è meglio evitarlo.

Un altro motivo per essere attenti quando si passa un parametro Optional come è che un metodo dovrebbe fare una cosa … Se si passa un parametro Optional si potrebbe favorire fare più di una cosa, potrebbe essere simile a passare un parametro booleano.

 public void method(Optional param) { if(param.isPresent()) { //do something } else { //do some other } } 

Penso che sia perché di solito scrivi le tue funzioni per manipolare i dati, e poi solleva su Optional usando la map e funzioni simili. Ciò aggiunge il comportamento Optional predefinito ad esso. Naturalmente, potrebbero esserci dei casi, quando è necessario scrivere la propria funzione ausiliaria che funziona su Optional .

Credo che il risone dell’essere sia che devi prima controllare se Opzionale è nullo e quindi cercare di valutare il valore che esso racchiude. Troppe convalide inutili.

Gli optionals non sono progettati per questo scopo, come spiegato da Brian Goetz .

Puoi sempre usare @Nullable per indicare che un argomento del metodo può essere nullo. L’utilizzo di un opzionale non ti consente di scrivere la logica del tuo metodo in modo più ordinato.

L’accettazione facoltativa come parametri causa involontari involontari a livello di chiamante.

Ad esempio nel caso di:

 public int calculateSomething(Optional p1, Optional p2 {} 

Supponiamo di avere due stringhe non nulle (cioè restituite da qualche altro metodo):

 String p1 = "p1"; String p2 = "p2"; 

Sei obbligato ad avvolgerli in Opzionale anche se sai che non sono vuoti.

Questo peggiora ulteriormente quando devi comporre altre strutture “mappabili”, ad es. Eithers :

 Either value = compute().right().map((s) -> calculateSomething( < here you have to wrap the parameter in a Optional even if you know it's a string >)); 

ref:

i metodi non dovrebbero aspettarsi Opzione come parametri, questo è quasi sempre un odore di codice che indica una perdita di stream di controllo dal chiamante al chiamato, dovrebbe essere responsabilità del chiamante controllare il contenuto di un’opzione

rif. https://github.com/teamdigitale/digital-citizenship-functions/pull/148#discussion_r170862749

L’utilizzo Optional come argomenti del metodo richiede anche il controllo null su se stesso. Logica duplicata non necessaria. Per esempio:-

 void doSomething(Optional isoOpt, Optional prodOpt){ if(isoOpt != null){ isoOpt.orElse(ISOCode.UNKNOWN); } if(prodOpt != null){ prodOpt.orElseThrow(NullPointerException::new); } //more instructions } 

Immagina se il numero di parametri Optional aumenta. Se si dimentica il controllo nullo, probabilmente NPE verrà lanciato in fase di runtime.

Inoltre, se il tuo metodo è pubblico, il cliente sarà costretto a ricoprire i suoi parametri in Optionals, il che sarà un fardello soprattutto se il numero di argomenti crescerà.

Un altro approccio, quello che puoi fare è

 // get your optionals first Optional p1 = otherObject.getP1(); Optional p2 = otherObject.getP2(); // bind values to a function Supplier calculatedValueSupplier = () -> { // your logic here using both optional as state} 

Una volta che hai creato una funzione (fornitore in questo caso) sarai in grado di passarla come qualsiasi altra variabile e sarebbe in grado di chiamarla usando

 calculatedValueSupplier.apply(); 

L’idea qui è che tu abbia o meno un valore opzionale, sarà il dettaglio interno della tua funzione e non sarà in parametro. Il pensiero funziona quando si pensa al parametro opzionale come è in realtà una tecnica molto utile che ho trovato.

Per quanto riguarda la tua domanda, dovresti farlo o meno basandoti sulle tue preferenze, ma come altri hanno detto che rende la tua API brutta a dir poco.

Inizialmente, ho anche preferito passare Optionals come parametro, ma se si passa da una prospettiva API-Designer a una prospettiva API-User, si vedono gli svantaggi.

Per il tuo esempio, in cui ogni parametro è facoltativo, suggerirei di cambiare il metodo di calcolo in una propria class come segue:

 Optional p1 = otherObject.getP1(); Optional p2 = otherObject.getP2(); MyCalculator mc = new MyCalculator(); p1.map(mc::setP1); p2.map(mc::setP2); int result = mc.calculate(); 

So che questa domanda riguarda più l’opinione che i fatti concreti. Ma di recente mi sono trasferito da uno sviluppatore .net a uno java, quindi di recente mi sono iscritto alla parte opzionale. Inoltre, preferirei dichiararlo come un commento, ma dal momento che il mio livello non mi consente di commentare, sono costretto a mettere questa come risposta.

Quello che ho fatto, che mi è servito bene come regola generale. Usa Optionals per i tipi restituiti e usa Optionals solo come parametri, se richiedo sia il valore dell’Optional, sia che Weather ha un valore all’interno del metodo.

Se mi interessa solo il valore, controllo isPresent prima di chiamare il metodo, se ho qualche tipo di registrazione o logica diversa all’interno del metodo che dipende dal fatto che il valore esista, allora passerò felicemente in Optional.