E 'un antipattern usare peek () per modificare un elemento stream?

31

Supponiamo di avere un flusso di cose e voglio "arricchirle" a metà stream, posso usare peek() per fare ciò, ad esempio:

streamOfThings.peek(this::thingMutator).forEach(this::someConsumer);

Supponiamo che la mutazione delle cose a questo punto nel codice sia un comportamento corretto; ad esempio, il metodo thingMutator può impostare il campo "lastProcessed" sull'ora corrente.

Tuttavia, peek() nella maggior parte dei contesti significa "guarda, ma non toccare".

L'utilizzo di peek() per muta elementi di flusso è antipattern o sconsiderato?

Modifica:

L'approccio alternativo, più convenzionale, sarebbe quello di convertire il consumatore:

private void thingMutator(Thing thing) {
    thing.setLastProcessed(System.currentTimeMillis());
}

a una funzione che restituisce il parametro:

private Thing thingMutator(Thing thing) {
    thing.setLastProcessed(currentTimeMillis());
    return thing;
}

e usa map() invece:

stream.map(this::thingMutator)...

Ma questo introduce il codice superficiale (il return ) e non sono convinto che sia più chiaro, perché sai che peek() restituisce lo stesso oggetto, ma con map() non è nemmeno chiaro a colpo d'occhio che è lo stesso classe di oggetto.

Inoltre, con peek() puoi avere un lambda che muta, ma con map() devi costruire un naufragio del treno. Confronto:

stream.peek(t -> t.setLastProcessed(currentTimeMillis())).forEach(...)
stream.map(t -> {t.setLastProcessed(currentTimeMillis()); return t;}).forEach(...)

Penso che la versione peek() sia più chiara e che la lambda si stia chiaramente mutando, quindi non ci sono effetti collaterali "misteriosi". Allo stesso modo, se viene utilizzato un riferimento al metodo e il nome del metodo implica chiaramente la mutazione, anche questo è chiaro ed evidente.

Su una nota personale, non evito di usare peek() per mutare - lo trovo molto comodo.

    
posta Bohemian 01.02.2016 - 18:39
fonte

3 risposte

22

Hai ragione, "sbirciare" nel senso inglese della parola significa "guarda, ma non toccare".

Tuttavia il JavaDoc afferma:

peek

Stream peek(Consumer action)

Returns a stream consisting of the elements of this stream, additionally performing the provided action on each element as elements are consumed from the resulting stream.

Parole chiave: "esecuzione ... azione" e "consumato". JavaDoc è molto chiaro che dovremmo aspettarci che peek abbia la possibilità di modificare il flusso.

Tuttavia il JavaDoc indica anche:

API Note:

This method exists mainly to support debugging, where you want to see the elements as they flow past a certain point in a pipeline

Questo indica che è inteso più per l'osservazione, ad es. elementi di registrazione nello stream.

Ciò che ricavo da tutto questo è che possiamo eseguire azioni usando gli elementi nello stream, ma dovremmo evitare gli elementi mutanti nello stream. Ad esempio, vai avanti e chiama i metodi sugli oggetti, ma cerca di evitare operazioni di modifica su di essi.

Per lo meno, aggiungerei un breve commento al tuo codice seguendo queste linee:

// Note: this peek() call will modify the Things in the stream.
streamOfThings.peek(this::thingMutator).forEach(this::someConsumer);

Le opinioni divergono sull'utilità di tali commenti, ma vorrei usare un commento del genere in questo caso.

    
risposta data 01.02.2016 - 19:09
fonte
0

Potrebbe essere facilmente interpretato male, quindi eviterei di usarlo in questo modo. Probabilmente l'opzione migliore è usare una funzione lambda per unire le due azioni necessarie nella chiamata forEach. Puoi anche prendere in considerazione la possibilità di restituire un nuovo oggetto piuttosto che mutare quello esistente - potrebbe essere leggermente meno efficiente, ma è probabile che porti a un codice più leggibile e riduca il potenziale di utilizzare accidentalmente l'elenco modificato per un'altra operazione che dovrebbe ho ricevuto l'originale.

    
risposta data 01.02.2016 - 19:44
fonte
-2

La nota dell'API ci dice che il metodo è stato aggiunto principalmente per azioni come debug / logging / firing stats ecc.

@apiNote Questo metodo esiste principalmente per supportare il debug, dove vuoi      * per vedere gli elementi mentre passano oltre un certo punto in una pipeline:      *

       {@code
     *     Stream.of("one", "two", "three", "four")
     *         .filter(e -> e.length() > 3)
     *         .peek(e -> System.out.println("Filtered value: " + e))
     *         .map(String::toUpperCase)
     *         .peek(e -> System.out.println("Mapped value: " + e))
     *         .collect(Collectors.toList());
     * }
    
risposta data 12.05.2017 - 18:53
fonte

Leggi altre domande sui tag