เหตุใดตัวเลือก Option.get () โดยไม่เรียก isPresent () ไม่ถูกต้อง แต่ไม่ใช่ iterator.next ()


23

เมื่อใช้ Java8 สตรีม api ใหม่เพื่อค้นหาองค์ประกอบเฉพาะในคอลเลกชันฉันเขียนโค้ดดังนี้:

    String theFirstString = myCollection.stream()
            .findFirst()
            .get();

ที่นี่ IntelliJ เตือนว่า get () ถูกเรียกโดยไม่ตรวจสอบ isPresent () ก่อน

อย่างไรก็ตามรหัสนี้:

    String theFirstString = myCollection.iterator().next();

... ไม่ได้เตือนใด ๆ

มีความแตกต่างอย่างลึกซึ้งระหว่างสองเทคนิคนี้หรือไม่ที่ทำให้การสตรีมเข้าหา "อันตราย" มากกว่านี้ในบางด้านนั่นเป็นสิ่งสำคัญที่จะไม่ต้องโทรหารับ () โดยไม่ต้องเรียก isPresent () ก่อน Googling ไปรอบ ๆ ฉันพบบทความที่พูดถึงว่าโปรแกรมเมอร์ไม่ใส่ใจกับตัวเลือก <> และคิดว่าพวกเขาสามารถโทรหา get () ได้ทุกเมื่อ

สิ่งที่ฉันชอบที่จะได้รับข้อยกเว้นโยนฉันที่ใกล้ที่สุดกับสถานที่ที่รหัสของฉันเป็นรถซึ่งในกรณีนี้จะเป็นที่ที่ฉันโทรหารับ () เมื่อไม่มีองค์ประกอบที่จะพบ ฉันไม่ต้องการที่จะเขียนเรียงความไร้ประโยชน์เช่น:

    Optional<String> firstStream = myCollection.stream()
            .findFirst();
    if (!firstStream.isPresent()) {
        throw new IllegalStateException("There is no first string even though I totally expected there to be! <sarcasm>This exception is much more useful than NoSuchElementException</sarcasm>");
    }
    String theFirstString = firstStream.get();

... เว้นแต่จะมีอันตรายในการยั่วยุข้อยกเว้นจากการได้รับ () ที่ฉันไม่รู้?


หลังจากอ่านคำตอบจาก Karl Bielefeldt และความคิดเห็นจาก Hulk ฉันรู้รหัสข้อยกเว้นของฉันข้างต้นค่อนข้างงุ่มง่ามนี่คือสิ่งที่ดีกว่า:

    String errorString = "There is no first string even though I totally expected there to be! <sarcasm>This exception is much more useful than NoSuchElementException</sarcasm>";
    String firstString = myCollection.stream()
            .findFirst()
            .orElseThrow(() -> new IllegalStateException(errorString));

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


7
ข้อยกเว้นในส่วนที่เป็นตัวอย่างของคุณสามารถเขียนได้ง่ายกว่าถ้าคุณใช้orElseThrow - และมันจะชัดเจนสำหรับผู้อ่านทุกคนที่คุณตั้งใจจะโยนข้อยกเว้น
Hulk

คำตอบ:


12

ก่อนอื่นให้พิจารณาว่าการตรวจสอบนั้นซับซ้อนและอาจต้องใช้เวลาค่อนข้างนาน ต้องมีการวิเคราะห์แบบสแตติกและอาจมีรหัสค่อนข้างน้อยระหว่างการโทรสองครั้ง

ประการที่สองการใช้สำนวนของโครงสร้างทั้งสองนั้นแตกต่างกันมาก ฉันเขียนโปรแกรมเต็มเวลาใน Scala ซึ่งใช้Optionsบ่อยกว่า Java ฉันไม่สามารถจำได้เช่นเดียวที่ฉันต้องการที่จะใช้ใน.get() Optionคุณควรคืนค่าOptionalจากฟังก์ชันของคุณหรือใช้orElse()แทน นี่เป็นวิธีที่เหนือกว่ามากในการจัดการกับค่าที่หายไปกว่าการโยนข้อยกเว้น

เนื่องจาก.get()ไม่ควรถูกเรียกใช้ในการใช้งานปกติมันสมเหตุสมผลสำหรับ IDE ที่จะเริ่มการตรวจสอบที่ซับซ้อนเมื่อเห็น a .get()เพื่อดูว่ามีการใช้อย่างถูกต้องหรือไม่ ในทางตรงกันข้ามiterator.next()การใช้ตัววนซ้ำที่เหมาะสมและแพร่หลายคือ

กล่าวอีกนัยหนึ่งมันไม่ใช่เรื่องของการเป็นคนเลวและคนอื่น ๆ ไม่ใช่มันเป็นเรื่องของคนทั่วไปที่เป็นพื้นฐานของการดำเนินงานและมีแนวโน้มที่จะเกิดข้อยกเว้นระหว่างการทดสอบของนักพัฒนาและคนอื่น ๆ ที่ไม่ค่อยได้ใช้ กรณีที่อาจไม่สามารถออกกำลังกายได้มากพอที่จะทำการยกเว้นในระหว่างการทดสอบของนักพัฒนาซอฟต์แวร์ นี่เป็นกรณีที่คุณต้องการให้ IDEs เตือนคุณ


เป็นไปได้ไหมที่ฉันจะต้องเรียนรู้เพิ่มเติมเกี่ยวกับธุรกิจทางเลือกนี้ - ฉันเคยเห็นสิ่งที่ java8 เป็นวิธีที่ดีในการทำแผนที่รายการวัตถุที่เราทำมากมายในเว็บแอพของเรา ดังนั้นคุณควรส่งตัวเลือก <> ทุกที่หรือไม่ นั่นจะทำให้บางคนเริ่มชินแล้ว แต่นั่นก็เป็นอีกเรื่องทาง SE! :)
ทีวีของ Frank

@ ทีวีของแฟรงก์มันน้อยกว่าที่พวกเขาควรจะเป็นทุกที่และอื่น ๆ ที่พวกเขาให้คุณฟังก์ชั่นการเขียนที่แปลงค่าเป็นประเภทที่มีอยู่และคุณโยงพวกเขาด้วยหรือmap ส่วนใหญ่เป็นวิธีที่ดีในการหลีกเลี่ยงการยึดทุกอย่างด้วยเช็ค flatMapOptionalnull
Morgen

15

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

เนื้อหาคำเตือนที่คล้ายกันควรถูกสร้างขึ้นสำหรับตัววนซ้ำ อย่างไรก็ตามไม่เคยมีใครทำมาก่อนและการเพิ่มคำเตือนอาจทำให้คำเตือนในโครงการที่มีอยู่มากเกินไปเป็นความคิดที่ดี

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


6

ฉันยอมรับว่าไม่มีความแตกต่างโดยธรรมชาติในสิ่งเหล่านี้อย่างไรก็ตามฉันต้องการชี้ให้เห็นข้อบกพร่องที่ใหญ่กว่า

ในทั้งสองกรณีคุณมีประเภทซึ่งให้วิธีการที่ไม่รับประกันว่าจะใช้งานได้และคุณควรจะเรียกวิธีอื่นก่อนหน้านั้น ว่า IDE / คอมไพเลอร์ / etc ของคุณเตือนคุณเกี่ยวกับกรณีหนึ่งหรืออื่น ๆ เพียงแค่ขึ้นอยู่กับว่ามันรองรับกรณีนี้และ / หรือคุณมีการกำหนดค่าให้ทำเช่นนั้น

ที่ถูกกล่าวว่าแม้ว่าทั้งสองชิ้นของรหัสเป็นรหัสกลิ่น นิพจน์เหล่านี้บอกใบ้ถึงจุดบกพร่องของการออกแบบและโค้ดที่เปราะบาง

คุณถูกต้องในการที่จะล้มเหลวอย่างรวดเร็วแน่นอน แต่วิธีแก้ปัญหาสำหรับฉันอย่างน้อยก็ไม่สามารถที่จะยักมันออกและโยนมือของคุณไปในอากาศ (หรือข้อยกเว้นลงบนสแต็ค)

คอลเลกชันของคุณอาจจะไม่ว่างเปล่าในตอนนี้ แต่สิ่งที่คุณไว้ใจได้จริงๆคือประเภท: Collection<T>- และนั่นไม่ได้รับประกันว่าคุณจะว่างเปล่า แม้ว่าคุณจะรู้ว่าตอนนี้ไม่ว่างเปล่า แต่ข้อกำหนดที่เปลี่ยนแปลงอาจนำไปสู่การเปลี่ยนแปลงรหัสล่วงหน้าและจู่ ๆ โค้ดของคุณก็ถูกคอลเลกชันว่างเปล่า

ตอนนี้คุณมีอิสระที่จะผลักดันและบอกคนอื่น ๆ ว่าคุณควรจะได้รับรายชื่อที่ไม่ว่างเปล่า คุณสามารถมีความสุขที่คุณพบว่า 'ข้อผิดพลาด' รวดเร็ว อย่างไรก็ตามคุณมีซอฟต์แวร์ที่เสียหายและจำเป็นต้องเปลี่ยนรหัสของคุณ

ตัดความขัดแย้งนี้ด้วยวิธีการที่เป็นประโยชน์มากขึ้น: เนื่องจากคุณได้รับคอลเล็กชันและอาจว่างเปล่าคุณจึงบังคับให้คุณจัดการกับกรณีดังกล่าวโดยใช้ตัวเลือก # map, #orElse หรือวิธีอื่นใดยกเว้น #get

Collection<T>คอมไพเลอร์จะเป็นงานมากที่สุดเท่าที่เป็นไปได้สำหรับคุณเช่นที่คุณสามารถวางใจได้มากที่สุดเท่าที่เป็นไปได้ในการทำสัญญาแบบคงที่ประเภทของการ เมื่อรหัสของคุณไม่เคยละเมิดสัญญานี้ (เช่นผ่านเครือข่ายส่งกลิ่นสองสาย) รหัสของคุณจะเปราะน้อยกว่ามากเมื่อเปลี่ยนสภาพแวดล้อม

ในสถานการณ์ข้างต้นการเปลี่ยนแปลงที่ให้การรวบรวมอินพุตว่างจะไม่มีผลใด ๆ กับโค้ดของคุณ มันเป็นเพียงอินพุตที่ถูกต้องอีกครั้งและยังคงจัดการได้อย่างถูกต้อง

ค่าใช้จ่ายในการนี้คือคุณต้องใช้เวลาในการออกแบบที่ดีขึ้นซึ่งตรงข้ามกับ a) เสี่ยงต่อโค้ดที่เปราะบางหรือ b) สิ่งที่ซับซ้อนเกินความจำเป็นโดยการโยนข้อยกเว้นรอบ ๆ

ในหมายเหตุสุดท้าย: เนื่องจากไม่ใช่ IDE / คอมไพเลอร์ทั้งหมดจึงเตือนเกี่ยวกับการเรียก iterator (). ถัดไป () หรือ optional.get () โดยไม่ต้องมีการตรวจสอบก่อนหน้า สำหรับสิ่งที่คุ้มค่าฉันจะไม่ปล่อยให้รหัสดังกล่าวอนุญาตให้ผ่านการตรวจสอบและเรียกใช้โซลูชันที่สะอาดแทน


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

จับได้เห็นชัดตรงเผง. "เลือกองค์ประกอบที่มีคุณสมบัติ p จากรายการ" -> list.stream (). filter (p) .findFirst () .. และอีกครั้งเราถูกบังคับให้ถามคำถาม "จะเกิดอะไรขึ้นถ้าไม่มี" .. ขนมปังและเนยทุกวัน ถ้ามันเป็น mandatoray และ "แค่นั้น" - ทำไมคุณถึงซ่อนมันไว้ในที่อื่น ๆ
แฟรงก์

4
เพราะอาจเป็นรายการที่โหลดจากฐานข้อมูล อาจเป็นรายการที่รวบรวมทางสถิติที่รวบรวมในฟังก์ชั่นอื่น ๆ : เรารู้ว่าเรามีวัตถุ A, B และ C ฉันต้องการค้นหาจำนวนของสิ่งใด ๆ สำหรับ B. กรณีที่ถูกต้อง (และในใบสมัครของฉันทั่วไป)
ทีวีของ Frank

ฉันไม่รู้เกี่ยวกับฐานข้อมูลของคุณ แต่ฉันไม่รับประกันอย่างน้อยหนึ่งผลลัพธ์ต่อข้อความค้นหา สำหรับคอลเลกชันสถิติใครจะบอกว่าพวกเขาจะไม่ว่างเปล่าเสมอ? ฉันยอมรับว่ามันตรงไปตรงมาเพื่อให้เหตุผลว่าควรมีสิ่งนี้หรือองค์ประกอบนั้น แต่ฉันชอบการพิมพ์แบบคงที่ที่เหมาะสมมากกว่าเอกสารหรือข้อมูลเชิงลึกบางอย่างจากการอภิปรายที่แย่ลง
แฟรงค์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.