มันเป็น antipattern ที่จะใช้ peek () เพื่อแก้ไของค์ประกอบสตรีม?


36

สมมติว่าฉันมีกระแสของสิ่งต่าง ๆ และฉันต้องการ "เพิ่มคุณค่า" พวกเขาให้อยู่กลางกระแสฉันสามารถใช้peek()ทำสิ่งนี้ได้เช่น:

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

สมมติว่าการกลายพันธุ์สิ่งต่าง ๆ ณ จุดนี้ในรหัสเป็นการทำงานที่ถูกต้องตัวอย่างเช่นthingMutatorวิธีการอาจตั้งค่าฟิลด์ "lastProcessed" เป็นเวลาปัจจุบัน

อย่างไรก็ตามpeek()ในบริบทส่วนใหญ่หมายถึง "มอง แต่ไม่ได้สัมผัส"

ใช้peek()เพื่อเปลี่ยนแปลงองค์ประกอบของกระแสข้อมูลที่เป็น antipattern หรือไม่เหมาะสมหรือไม่?

แก้ไข:

ทางเลือกที่ธรรมดากว่าคือการเปลี่ยนผู้บริโภค:

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

ไปที่ฟังก์ชันที่คืนค่าพารามิเตอร์:

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

และใช้map()แทน:

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

แต่ที่แนะนำรหัสลวก ๆ (คนreturn) และฉันไม่ได้เชื่อว่ามันเป็นที่ชัดเจนเพราะคุณรู้ว่าpeek()ผลตอบแทนที่วัตถุเดียวกัน แต่map()มันไม่ได้ล้างได้อย่างรวดเร็วว่ามันเป็นเดียวกันระดับของวัตถุ

นอกจากนี้peek()คุณยังสามารถมีแลมบ์ดาที่กลายพันธุ์ได้ แต่map()คุณต้องสร้างซากรถไฟ เปรียบเทียบ:

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

ฉันคิดว่าpeek()เวอร์ชั่นนั้นชัดเจนและแลมบ์ดากำลังกลายพันธุ์อย่างชัดเจนดังนั้นจึงไม่มีผลข้างเคียง "ลึกลับ" ในทำนองเดียวกันหากมีการใช้การอ้างอิงวิธีการและชื่อของวิธีการกลายพันธุ์โดยนัยอย่างชัดเจนก็ชัดเจนและชัดเจน

ในบันทึกส่วนตัวฉันไม่อายที่peek()จะกลายพันธุ์ - ฉันคิดว่ามันสะดวกมาก



1
คุณใช้peekกับสตรีมที่สร้างองค์ประกอบแบบไดนามิกหรือไม่ มันยังคงทำงานหรือการเปลี่ยนแปลงที่หายไป? การดัดแปลงองค์ประกอบของสตรีมนั้นไม่น่าเชื่อถือสำหรับฉัน
Sebastian Redl

@SebastianRedl ฉันพบว่าเชื่อถือได้ 100% ฉันใช้มันครั้งแรกเพื่อรวบรวมระหว่างการประมวลผล: List<Thing> list; things.stream().peek(list::add).forEach(...);มีประโยชน์มาก เมื่อเร็ว ๆ นี้. ฉันใช้มันเพื่อเพิ่มข้อมูลสำหรับการเผยแพร่: Map<Thing, Long> timestamps = ...; return things.stream().peek(t -> t.setTimestamp(timestamp.get(t))).collect(toList());. ฉันรู้ว่ามีวิธีอื่นในการทำตัวอย่างนี้ แต่ฉันทำสิ่งที่ทำให้เข้าใจง่ายเกินไปอย่างมากที่นี่ การใช้peek()จะสร้างรหัสที่กะทัดรัดและสวยงามยิ่งขึ้น IMHO คำถามนี้เป็นเรื่องเกี่ยวกับสิ่งที่คุณยกขึ้นมา ปลอดภัย / เชื่อถือได้หรือไม่
โบฮีเมียน


@Bohemian คุณยังคงฝึกวิธีนี้ด้วยpeekหรือไม่? ฉันมีคำถามที่คล้ายกันใน stackoverflow และหวังว่าคุณจะสามารถดูและให้ข้อเสนอแนะของคุณ ขอบคุณ stackoverflow.com/questions/47356992/…
tsolakp

คำตอบ:


23

คุณถูกต้อง "มอง" ในความหมายภาษาอังกฤษของคำว่า "มอง แต่ไม่ได้สัมผัส"

อย่างไรก็ตามJavaDocฯ :

แอบมอง

ดูสตรีม (การกระทำของผู้บริโภค)

ส่งคืนสตรีมที่ประกอบด้วยองค์ประกอบของสตรีมนี้ดำเนินการเพิ่มเติมตามที่กำหนดในแต่ละองค์ประกอบเนื่องจากองค์ประกอบจะถูกใช้จากสตรีมผลลัพธ์

คำสำคัญ: "กำลังดำเนินการ ... กระทำ" และ "ใช้แล้ว" JavaDoc มีความชัดเจนมากที่เราควรคาดหวังว่าpeekจะมีความสามารถในการปรับเปลี่ยนกระแส

อย่างไรก็ตาม JavaDoc ยังระบุ:

API หมายเหตุ:

วิธีการนี้มีอยู่เป็นส่วนใหญ่เพื่อรองรับการดีบั๊กซึ่งคุณต้องการดูองค์ประกอบขณะที่ไหลผ่านจุดหนึ่งไปป์ไลน์

สิ่งนี้บ่งชี้ว่ามันมีไว้สำหรับการสังเกตเช่นองค์ประกอบการบันทึกในสตรีม

สิ่งที่ฉันทำจากทั้งหมดนี้คือเราสามารถดำเนินการโดยใช้องค์ประกอบในสตรีม แต่ควรหลีกเลี่ยงการกลายพันธุ์องค์ประกอบในสตรีม ตัวอย่างเช่นไปข้างหน้าและวิธีการโทรบนวัตถุ แต่พยายามหลีกเลี่ยงการกลายพันธุ์การดำเนินการกับพวกเขา

อย่างน้อยที่สุดฉันจะเพิ่มความคิดเห็นสั้น ๆ ในโค้ดของคุณตามบรรทัดเหล่านี้:

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

ความคิดเห็นแตกต่างกันในประโยชน์ของความคิดเห็นดังกล่าว แต่ฉันจะใช้ความคิดเห็นดังกล่าวในกรณีนี้


1
เหตุใดคุณจึงต้องการความคิดเห็นหากชื่อเมธอดส่งสัญญาณการเปลี่ยนแปลงอย่างชัดเจนเช่นthingMutatorหรือเป็นรูปธรรมมากขึ้นresetLastProcessedเป็นต้นหากไม่มีเหตุผลที่น่าสนใจความคิดเห็นที่ต้องการเช่นข้อเสนอแนะของคุณมักจะระบุตัวเลือกตัวแปรและ / หรือชื่อวิธีที่ไม่ดี หากเลือกชื่อที่ดีแล้วนั่นไม่เพียงพอหรือ หรือคุณกำลังพูดว่าแม้จะมีชื่อที่ดีอะไรก็ตามที่peek()เป็นเหมือนจุดบอดที่โปรแกรมเมอร์ส่วนใหญ่จะ "อ่าน" (มองข้าม) นอกจากนี้ "ส่วนใหญ่สำหรับการดีบั๊ก" นั้นไม่เหมือนกับ "สำหรับการดีบั๊กเท่านั้น" เท่านั้น - การใช้งานแบบอื่นที่ไม่ใช่ "ส่วนใหญ่" นั้นมีจุดประสงค์เพื่ออะไร
โบฮีเมียน

@ โบฮีเมียนฉันจะอธิบายเป็นหลักเพราะชื่อวิธีนั้นไม่ง่าย และฉันไม่ได้ทำงานกับ Oracle ฉันไม่ได้อยู่ในทีมจาวาของพวกเขา ฉันไม่รู้ว่าพวกเขาตั้งใจจะทำอะไร

@ โบฮีเมียนเพราะเมื่อมองหาสิ่งที่แก้ไของค์ประกอบในแบบที่ฉันไม่ชอบฉันจะหยุดอ่านบรรทัดที่ "peek" เพราะ "peek" ไม่เปลี่ยนแปลงอะไรเลย หลังจากนั้นเมื่อสถานที่อื่น ๆ ของรหัสไม่ได้มีข้อผิดพลาดที่ฉันกำลังไล่ฉันจะกลับมาที่นี่ด้วยอาจเป็นอาวุธที่มีดีบักเกอร์และจากนั้นอาจจะไป "omg ที่ใส่รหัสนี้ทำให้เข้าใจผิดที่นั่น?!"
Frank Hopkins

@ โบฮีเมียนใน exampe ที่นี่ซึ่งทุกอย่างอยู่ในบรรทัดเดียวเราต้องอ่านต่อไป แต่อาจข้ามเนื้อหาของ "peek" และบ่อยครั้งที่คำสั่ง stream จะข้ามหลายบรรทัดด้วยการดำเนินการสตรีมหนึ่งบรรทัด
Frank Hopkins

1

มันอาจตีความผิดได้ง่ายดังนั้นฉันจะหลีกเลี่ยงการใช้แบบนี้ อาจเป็นตัวเลือกที่ดีที่สุดคือการใช้ฟังก์ชั่นแลมบ์ดาเพื่อรวมสองการกระทำที่คุณต้องการเข้ากับการโทรแต่ละครั้ง คุณอาจต้องการพิจารณาส่งคืนวัตถุใหม่มากกว่าการกลายพันธุ์วัตถุที่มีอยู่ - อาจมีประสิทธิภาพน้อยลงเล็กน้อย แต่มีแนวโน้มที่จะส่งผลให้รหัสอ่านได้มากขึ้นและลดโอกาสที่จะเกิดอุบัติเหตุโดยใช้รายการแก้ไขสำหรับการดำเนินการอื่น ได้รับต้นฉบับ


-2

บันทึกย่อ API บอกเราว่าวิธีการที่ได้รับการเพิ่มส่วนใหญ่สำหรับการกระทำเช่นการแก้จุดบกพร่อง / บันทึก / สถิติการยิง ฯลฯ

@apiNote วิธีนี้มีอยู่เป็นหลักในการรองรับการแก้ไขข้อบกพร่องโดยที่คุณต้องการ * เพื่อดูองค์ประกอบขณะที่ไหลผ่านจุดที่กำหนดในไปป์ไลน์: *

       {@รหัส
     * สตรีมของ ("หนึ่ง", "สอง", "สาม", "สี่")
     * .filter (e -> e.length ()> 3)
     * .peek (e -> System.out.println ("ค่าตัวกรอง:" + e))
     * .map (สตริง :: toUpperCase)
     * .peek (e -> System.out.println ("ค่าที่แมป:" + e))
     * .collect (Collector.toList ());
     *}


2
คำตอบของคุณครอบคลุมโดย Snowman
Vincent Savard

3
และ "ส่วนใหญ่" ไม่ได้หมายถึง "เท่านั้น"
โบฮีเมียน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.