Bir stream öğesini değiştirmek için peek () kullanmak bir antipattern mı?


36

Diyelim ki bir şeyler akışım var ve onları orta akışta "zenginleştirmek" istiyorum, bunu peek()yapmak için kullanabilirim , örneğin:

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

Şeylerin kodda bu noktada mutasyona tabi tutulmasının doğru bir davranış olduğunu varsayalım - örneğin, thingMutatoryöntem "lastProcessed" alanını şimdiki zamana ayarlayabilir.

Ancak, peek()çoğu bağlamda "bak, ama dokunma" anlamına gelir.

Kullanarak mı peek()hiç mutasyon akışı elemanları bir antipattern veya tedbirsiz?

Düzenle:

Alternatif, daha geleneksel yaklaşım, tüketiciyi dönüştürmek olacaktır:

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

parametre döndüren bir işleve:

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

ve map()bunun yerine kullanın:

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

Ancak bu, perfunctory kodunu (the return) tanıtır ve bunun daha açık olduğuna ikna olmadım, çünkü peek()aynı nesneyi döndürdüğünü biliyorsunuz , ancak map()bir bakışta aynı nesne sınıfı olduğu bile belli değil .

Ayrıca, peek()mutasyona uğrayan bir lambdaya sahip olabilirsiniz, ancak map()sizinle birlikte bir tren kazası yapmak zorundasınız. Karşılaştırmak:

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

Bence peek()sürüm daha net ve lambda açıkça mutasyona uğramış, bu yüzden "gizemli" bir yan etkisi yok. Benzer şekilde, eğer bir yöntem referansı kullanılırsa ve yöntemin adı açıkça mutasyon anlamına gelirse, bu da açık ve açıktır.

Kişisel bir notta, peek()mutasyona uğramaktan çekinmekten çekinmiyorum - çok uygun buluyorum.



1
peekElementlerini dinamik olarak üreten bir akışla kullandınız mı ? Hala çalışıyor mu yoksa değişiklikler mi kayboluyor? Bir akıntının elemanlarını değiştirmek bana güvenilmez geliyor.
Sebastian Redl

@SebastianRedl Ben% 100 güvenilir buldum. İlk önce işleme sırasında toplamak için kullandım: List<Thing> list; things.stream().peek(list::add).forEach(...);çok kullanışlı. Son zamanlarda. Ben yayınladığı için bilgi eklemek kullandım: Map<Thing, Long> timestamps = ...; return things.stream().peek(t -> t.setTimestamp(timestamp.get(t))).collect(toList());. Bu örneği yapmanın başka yolları olduğunu biliyorum, ama burada aşırı derecede basitleştiriyorum. peek()IMHO kullanarak daha kompakt ve daha zarif kodlar üretir. Okunabilirlik bir yana, bu soru gerçekten neyi yetiştirdiğinizle ilgilidir; güvenli mi / güvenilir mi?
Bohemian


@Bohemian. Hala bu yaklaşımı uyguluyor musunuz peek? Stackoverflow'ta da benzer bir sorum var ve ona bakıp geri bildiriminizi verebileceğinizi umuyorum. Teşekkürler. stackoverflow.com/questions/47356992/…
tsolakp

Yanıtlar:


23

Haklısın, ingilizce anlamında "gözetleme" anlamında "bak, ama dokunma" anlamına geliyor.

Ancak javadoc durumları:

dikizlemek

Akış peek (Tüketici eylemi)

Bu akıştaki elementlerden oluşan bir akış döndürür, ayrıca elementler elde edilen akıştan tüketilirken her element üzerinde sağlanan eylemi gerçekleştirir.

Anahtar kelimeler: "gerçekleştirme ... eylemi" ve "tüketilen". JavaDoc, peekakışı değiştirme yeteneğine sahip olmamızı beklediğimiz konusunda çok açık .

Ancak JavaDoc ayrıca şunları da belirtir:

API Notu:

Bu yöntem temel olarak, bir boru hattında belirli bir noktadan akarken öğeleri görmek istediğiniz hata ayıklamayı desteklemek için mevcuttur.

Bu, örneğin akıştaki günlük öğelerinin kaydedilmesi için daha fazla amaçlandığını gösterir.

Ya bu tümünden almak biz eylemleri gerçekleştirmek olmasıdır kullanarak akışında elemanları, ancak kaçınmalıdır mutasyona akışında unsurları. Örneğin, devam edin ve nesnelerdeki yöntemleri çağırın, ancak üzerlerindeki işlemleri değiştirmekten kaçının.

En azından bu satırlar boyunca kodunuza kısa bir yorum eklerdim:

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

Görüşler, bu tür yorumların yararlılığına göre farklılık gösterir, ancak bu durumda böyle bir yorum kullanırdım.


1
Yöntem adı açıkça bir mutasyona, örneğin thingMutatorveya daha fazla somut resetLastProcessedvs.'ye işaret ediyorsa neden bir yoruma ihtiyacınız var? Zorlayıcı bir neden olmadığı sürece, öneriniz gibi ihtiyaç yorumları, genellikle değişken ve / veya yöntem adlarının kötü seçimlerini gösterir. İyi isimler seçildiyse, bu yeterli değil mi? Yoksa iyi bir isme rağmen, içerideki herhangi peek()bir şeyin çoğu programcının "göz ardı edeceği" (görsel olarak atlayacağı) kör bir nokta olduğunu mu söylüyorsunuz ? Ayrıca, "esas olarak hata ayıklama için", "yalnızca hata ayıklama için" aynı şey değildir - "esas olarak" olanlar dışında ne amaçlanmıştır?
Bohemian

@Bohemian Ben esas olarak açıklamak istiyorum çünkü yöntem adı sezgisel değildir. Ve ben Oracle için çalışmıyorum. Java ekibinde değilim. Ne amaçladıkları hakkında hiçbir fikrim yok.

@Bohemian, çünkü elemanları ne değiştirecek bir şeyleri sevmediğimden hoşlanırsam, "gözetleme" bir şeyi değiştirmez çünkü "gözetleme" satırını okumayı bırakacağım. Ancak daha sonra koştuğum tüm diğer kod yerlerinde hatalar bulunmadığında, bununla geri döneceğim, muhtemelen bir hata ayıklayıcı ile donanmış ve belki de "omg, bu yanıltıcı kodu oraya koyan kim ?!"
Frank Hopkins

@Buhemian, burada her şeyin tek bir satırda olduğu, birinin yine de okuması gereken, ancak birinin "peek" in içeriğini atlayabildiği ve genellikle bir stream ifadesinin satır başına bir yayın işlemi ile birden fazla satır üzerinden geçtiği buradaki sınavda
Frank Hopkins

1

Kolayca yanlış yorumlanabilir, bu yüzden böyle kullanmaktan kaçınırdım. Muhtemelen en iyi seçenek, forEach çağrısına ihtiyaç duyduğunuz iki işlemi birleştirmek için bir lambda işlevi kullanmaktır. Ayrıca, mevcut olanı değiştirmek yerine yeni bir nesne döndürmeyi düşünebilirsiniz - biraz daha az verimli olabilir, ancak daha okunabilir bir kodla sonuçlanması muhtemeldir ve yanlışlıkla değiştirilmesi gereken başka bir işlem için değiştirilmiş listeyi kullanma olasılığını azaltır. orijinali aldım.


-2

API notu, yöntemin çoğunlukla hata ayıklama / günlüğe kaydetme / ateşleme istatistikleri gibi eylemler için eklendiğini söyler.

@ apiNote Bu yöntem temel olarak, bir boru hattında belirli bir noktadan akarken öğeleri görmek istediğiniz * ayıklamayı desteklemek için mevcuttur:

       {@code
     * Stream.of ("bir", "iki", "üç", "dört")
     * .filter (e -> e.length ()> 3)
     * .peek (e -> System.out.println ("Süzülmüş değer:" + e))
     * .map (String :: toUpperCase)
     * .peek (e -> System.out.println ("Eşlenmiş değer:" + e))
     * .collect (Collectors.toList ());
     *}


2
Cevabınız zaten Snowman tarafından ele alındı.
Vincent Savard

3
Ve "çoğunlukla" "sadece" anlamına gelmez.
Bohemian

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.