เป็นการดีที่จะใช้วิธีการเริ่มต้น Java 8 สองวิธีในแง่ของกันและกัน?


14

ฉันออกแบบอินเทอร์เฟซด้วยสองวิธีที่เกี่ยวข้องคล้ายกับสิ่งนี้:

public interface ThingComputer {
    default Thing computeFirstThing() {
        return computeAllThings().get(0);
    }

    default List<Thing> computeAllThings() {
        return ImmutableList.of(computeFirstThing());
    }
}

การใช้งานประมาณครึ่งหนึ่งจะคำนวณสิ่งหนึ่งเท่านั้นในขณะที่อีกครึ่งหนึ่งอาจคำนวณเพิ่มเติมได้

สิ่งนี้มีแบบอย่างใด ๆ ในโค้ด Java 8 ที่ใช้กันอย่างแพร่หลายหรือไม่? ฉันรู้ว่า Haskell ทำสิ่งที่คล้ายกันในบางประเภทEqของตัวอย่าง( เช่น)

ข้อเสียคือฉันต้องเขียนโค้ดน้อยกว่าอย่างมากถ้าฉันมีบทคัดย่อสองคลาส ( SingleThingComputerและMultipleThingComputer)

ข้อเสียก็คือการคอมไพล์การดำเนินงานที่ว่างเปล่า StackOverflowErrorแต่พัดขึ้นที่รันไทม์กับ เป็นไปได้ที่จะตรวจสอบการเรียกซ้ำซึ่งกันและกันด้วย a ThreadLocalและให้ข้อผิดพลาดที่ดีกว่า แต่นั่นเป็นการเพิ่มโอเวอร์เฮดให้กับโค้ดที่ไม่ใช่บั๊ก


3
ทำไมคุณถึงตั้งใจเขียนโค้ดที่รับประกันว่ามีการเรียกซ้ำแบบไม่สิ้นสุด ไม่มีอะไรที่ดีที่จะเกิดขึ้นได้

1
ก็ไม่ได้ "รับประกัน"; การใช้งานที่เหมาะสมจะแทนที่หนึ่งในวิธีการเหล่านั้น
Tavian Barnes

6
คุณไม่ทราบว่า คลาสหรืออินเทอร์เฟซจำเป็นต้องยืนด้วยตนเองและไม่คิดว่าคลาสย่อยจะทำสิ่งต่าง ๆ เพื่อหลีกเลี่ยงข้อผิดพลาดทางตรรกะ มิฉะนั้นมันจะเปราะและไม่สามารถอยู่ได้ถึงการค้ำประกัน โปรดทราบว่าฉันไม่ได้บอกว่าอินเตอร์เฟซของคุณสามารถหรือควรบังคับใช้คลาสที่ทำthrow new Error();หรือสิ่งที่โง่เพียงว่าอินเตอร์เฟสนั้นไม่ควรมีสัญญาที่เปราะบางผ่านdefaultวิธีการ

ฉันเห็นด้วยกับ @Snowman มันเป็นกับระเบิด ฉันคิดว่ามันเป็นตำราเรียน "รหัสชั่วร้าย" ในแง่ที่ Jon Skeet หมายถึง - โปรดทราบว่าความชั่วร้ายนั้นไม่ได้เลวร้ายเหมือนกันมันชั่วร้าย / ฉลาด / ล่อลวง แต่ก็ยังผิดอยู่ :) ฉันแนะนำyoutu.be/lGbQiguuUGc?t=15m26s ( หรือจริง ๆ แล้วการพูดทั้งหมดสำหรับบริบทที่กว้างขึ้น - มันน่าสนใจมาก)
Konrad Morawski

1
เพียงเพราะแต่ละฟังก์ชั่นสามารถกำหนดในแง่ของอีกไม่ได้หมายความว่าพวกเขาทั้งสองสามารถกำหนดในแง่ของกันและกัน ที่ไม่ได้บินในภาษาใด ๆ คุณต้องการอินเทอร์เฟซที่มีตัวสืบทอดนามธรรม 2 ตัวซึ่งแต่ละอันจะนำไปใช้ในแง่ของสิ่งอื่น แต่สิ่งที่เป็นนามธรรมอื่น ๆ ดังนั้นผู้พัฒนาจึงเลือกด้านที่พวกเขาต้องการนำไปใช้โดยสืบทอดจากคลาสนามธรรมที่ถูกต้อง ด้านหนึ่งต้องถูกกำหนดให้เป็นอิสระจากอีกด้านหนึ่งไม่เช่นนั้นป๊อปก็ไปที่สแต็ก คุณมีค่าเริ่มต้นบ้าสันนิษฐาน implementors จะแก้ไขปัญหาให้คุณabstractมีอยู่เพื่อบังคับให้พวกเขาแก้ไขได้
จิมมี่ฮอฟฟา

คำตอบ:


16

TL; DR: อย่าทำเช่นนี้

สิ่งที่คุณแสดงที่นี่คือรหัสเปราะ

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

การขว้างข้อผิดพลาดหรือคลาสย่อยบ่งชี้ข้อผิดพลาดร้ายแรงที่ไม่ควรจับ:

ข้อผิดพลาดเป็นประเภทย่อยของ Throwable ที่บ่งบอกถึงปัญหาร้ายแรงที่แอปพลิเคชันที่สมเหตุสมผลไม่ควรลองจับ

นอกจากนี้VirtualMachineErrorซึ่งเป็นคลาสพาเรนต์ของStackOverflowErrorกล่าวว่า:

ถูกส่งไปเพื่อระบุว่า Java Virtual Machine เสียหายหรือมีทรัพยากรที่จำเป็นสำหรับการดำเนินการต่อไป

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


คุณอาจรู้จักEric Lippertจากความพยายามเช่นนี้ในฐานะ "สมาชิกของคณะกรรมการออกแบบภาษา C #" เขาพูดเกี่ยวกับคุณสมบัติทางภาษาผลักดันผู้คนสู่ความสำเร็จหรือล้มเหลว: ในขณะที่นี่ไม่ใช่คุณสมบัติภาษาหรือเป็นส่วนหนึ่งของการออกแบบภาษาประเด็นของเขาก็มีความถูกต้องพอ ๆ กันเมื่อต้องใช้อินเทอร์เฟซหรือการใช้วัตถุ

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

ที่มา: C ++ และ Pit Of Despair

มีอินเตอร์เฟซโยนStackOverflowErrorโดยนักพัฒนาเริ่มต้นผลักดันลงไปในหลุมแห่งความสิ้นหวังและเป็นความคิดที่ดี ให้ดันนักพัฒนาซอฟต์แวร์ไปสู่Pit of Successแทน ทำให้ง่ายต่อการใช้ส่วนต่อประสานของคุณอย่างถูกต้องและไม่ขัดข้อง JVM

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

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


คำตอบที่ดี ฉันสงสัยว่าทำไมนี่จึงเป็นเรื่องธรรมดาใน Haskell วัฒนธรรม dev ที่แตกต่างกันฉันเดา
Tavian Barnes

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

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

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