ทำไมตัวเลือก Java 8 จึงไม่ควรใช้ในการโต้แย้ง


392

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

public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {
    // my logic
}

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

public int calculateSomething(String p1, BigDecimal p2) {
    // my logic
}

หรือฉันสามารถแทนที่วิธีของฉันด้วยวิธีสาธารณะสี่วิธีเพื่อให้อินเทอร์เฟซที่ดีกว่าและทำให้ p1 และ p2 ชัดเจนยิ่งขึ้นเป็นตัวเลือก (โซลูชัน 3):

public int calculateSomething() {
    calculateSomething(null, null);
}

public int calculateSomething(String p1) {
    calculateSomething(p1, null);
}

public int calculateSomething(BigDecimal p2) {
    calculateSomething(null, p2);
}

public int calculateSomething(String p1, BigDecimal p2) {
    // my logic
}

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

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1, p2);

หากใช้โซลูชัน 2 รหัสการโทรจะมีลักษณะดังนี้:

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));

หากมีการใช้โซลูชัน 3 ฉันสามารถใช้รหัสด้านบนหรือฉันสามารถใช้รหัสต่อไปนี้ (แต่เป็นรหัสที่มีความหมายมากกว่า):

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result;
if (p1.isPresent()) {
    if (p2.isPresent()) {
        result = myObject.calculateSomething(p1, p2);
    } else {
        result = myObject.calculateSomething(p1);
    }
} else {
    if (p2.isPresent()) {
        result = myObject.calculateSomething(p2);
    } else {
        result = myObject.calculateSomething();
    }
}

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


16
หากคุณใช้ตัวเลือกคุณจะไม่ต้องตรวจสอบว่าตัวเลือกที่ผ่านเป็นพารามิเตอร์ไม่ได้nullหรือไม่
biziclop

17
ใช่ แต่มันจะทำให้คนอื่นเห็นได้ชัดเจนว่าโค้ดในอนาคตจะว่างเปล่า / ว่างดังนั้นจึงอาจหลีกเลี่ยงข้อยกเว้นตัวชี้โมฆะในอนาคต
Neil Stevens

1
ว้าว. อาร์กิวเมนต์ Null ถูกส่งผ่านไปยังเมธอดหรือไม่? นั่นคือ Visual Basic หลักการอะไรที่เป็นการละเมิด? SRP (อาจ) นอกจากนี้ยังละเมิดหลักการอื่นที่มีชื่อปล่อยให้ฉันสูญเสียไปตามสายของการผ่านเฉพาะข้อมูลที่จำเป็นสำหรับวิธีการหรือฟังก์ชั่นในการทำงานของมัน
ส่องสว่าง

20
ทุกอย่างในทางทฤษฎีอาจเป็นโมฆะก็เหมือนทุกวิธีในไลบรารีที่อาจเรียก System.exit (0) คุณไม่สามารถตรวจสอบกับมอก. และคุณไม่ควรตรวจสอบกับเรื่องนี้ ทุกสิ่งที่คุณจะต้องทำตลอดเวลาในความเป็นจริงไม่ควรทำ (เกือบ) ชอบทำให้พารามิเตอร์ทั้งหมดเป็นที่สิ้นสุด ให้เครื่องมือของคุณช่วยคุณป้องกันการเปลี่ยนแปลงค่าพารามิเตอร์หรือลืมเริ่มต้นฟิลด์แทนที่จะทำให้โค้ดของคุณอ่านไม่ได้เป็นพันรอบชิงชนะเลิศและตรวจสอบโมฆะเป็นจำนวนมาก
Yeoman

6
ที่จริงแล้วเพียงใช้คำอธิบายประกอบที่ไม่เป็นโมฆะ / เป็นโมฆะนั่นคือสิ่งที่คุณกำลังมองหาในสถานการณ์นี้ไม่ใช่ตัวเลือก
Bill K

คำตอบ:


202

โอ้รูปแบบการเขียนนั้นต้องใช้เกลือนิดหน่อย

  1. (+) การส่งผลลัพธ์ทางเลือกไปยังวิธีอื่นโดยไม่มีการวิเคราะห์ความหมายใด ๆ ปล่อยให้วิธีการค่อนข้างไม่เป็นไร
  2. (-) การใช้พารามิเตอร์ทางเลือกที่ก่อให้เกิดตรรกะตามเงื่อนไขภายในวิธีการนั้นเป็นไปในทางตรงกันข้าม
  3. (-) จำเป็นต้องแพ็คอาร์กิวเมนต์ในตัวเลือกเป็นสิ่งที่ไม่ดีสำหรับคอมไพเลอร์และทำการห่อที่ไม่จำเป็น
  4. (-) ในการเปรียบเทียบกับพารามิเตอร์ nullable ตัวเลือกมีราคาแพงกว่า

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


38
อันที่จริงมีOptionalพารามิเตอร์เป็นหนึ่งในสามรัฐคือnullตัวเลือกที่ไม่ใช่ null Optionalด้วยisPresent() == falseและไม่ใช่ null Optionalการตัดค่าที่เกิดขึ้นจริง
biziclop

16
@biziclop ใช่แล้วจุดหลีกเลี่ยงไม่ได้ถูกวิพากษ์วิจารณ์แล้ว แต่เจตนาจะมีการแสดงออกที่ไม่เป็นโมฆะเท่านั้น ว่ามันใช้เวลาไม่นานในการฟังคำแนะนำเพื่อหลีกเลี่ยงตัวเลือกในบางกรณีเช่นกันมันเป็นเรื่องที่น่าขันมาก ก@NotNull Optional.
Joop Eggen

80
@biziclop โปรดทราบว่าหากคุณกำลังใช้งานOptionalอยู่ดังนั้นสถานะ 1 ( nullตัวเลือก) มักจะระบุถึงข้อผิดพลาดในการเขียนโปรแกรมดังนั้นคุณอาจไม่สามารถจัดการได้ (ปล่อยให้มันทิ้งNullPointerException)
user253751

50
"suboptimal สำหรับคอมไพเลอร์", "ในการเปรียบเทียบกับพารามิเตอร์ nullable ตัวเลือกมีราคาแพงกว่า" - อาร์กิวเมนต์เหล่านี้อาจใช้ได้สำหรับภาษา C และไม่ใช่ภาษาจาวา โปรแกรมเมอร์ Java ควรเน้นไปที่โค้ดที่สะอาดพกพาได้ทดสอบได้สถาปัตยกรรมที่ดี modularity ฯลฯ และไม่ใช่ใน "ตัวเลือกนั้นมีค่าใช้จ่ายสูงกว่าการอ้างอิงแบบ null" และหากคุณพบว่าคุณต้องมุ่งเน้นไปที่การเพิ่มประสิทธิภาพขนาดเล็กคุณควรข้ามวัตถุรายการทั่วไปและสลับไปยังอาร์เรย์และลำดับต้น (ฉันไม่ต้องการที่จะเป็นที่น่ารังเกียจที่นี่ฉันแค่แบ่งปันความคิดเห็นของฉัน) .
Kacper86

15
"suboptimal สำหรับคอมไพเลอร์": การปรับให้เหมาะสมก่อนกำหนดเป็นรากของความชั่วร้ายทั้งหมด ... หากคุณไม่ได้เขียนโค้ดสำคัญของประสิทธิภาพ (ซึ่งมักเป็นกรณีเมื่อพยายามระบุอินเตอร์เฟสที่สะอาด) นี่ไม่ควรกังวล
Mohan

165

โพสต์ที่ดีที่สุดที่ผมเคยเห็นในหัวข้อที่เขียนขึ้นโดยแดเนียล Olszewski :

แม้ว่ามันอาจเป็นการดึงดูดให้พิจารณาทางเลือกสำหรับพารามิเตอร์วิธีการที่ไม่บังคับ แต่โซลูชันดังกล่าวซีดเมื่อเปรียบเทียบกับทางเลือกอื่น ๆ เมื่อต้องการแสดงปัญหาให้ตรวจสอบการประกาศตัวสร้างต่อไปนี้:

public SystemMessage(String title, String content, Optional<Attachment> attachment) {
    // assigning field values
}

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

SystemMessage withoutAttachment = new SystemMessage("title", "content", Optional.empty());
Attachment attachment = new Attachment();
SystemMessage withAttachment = new SystemMessage("title", "content", Optional.ofNullable(attachment));

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

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

public SystemMessage(String title, String content) {
    this(title, content, null);
}

public SystemMessage(String title, String content, Attachment attachment) {
    // assigning field values
}

การเปลี่ยนแปลงนั้นทำให้รหัสลูกค้าอ่านได้ง่ายขึ้นและง่ายขึ้นมาก

SystemMessage withoutAttachment = new SystemMessage("title", "content");
Attachment attachment = new Attachment();
SystemMessage withAttachment = new SystemMessage("title", "content", attachment);

10
นั่นเป็นจำนวนมากคัดลอก + วางเมื่อเพียงหนึ่งหรือสองย่อหน้ามีความเกี่ยวข้อง
Garret Wilson

47
โชคไม่ดีที่คำอธิบายนี้ไม่ได้ตอบข้อกังวลของผู้โทรเช่นการแยกข้อมูลบางอย่างอาจเป็นทางเลือก ด้วยวิธีการโอเวอร์โหลด (ตามที่แนะนำข้างต้น) รหัสการโทรต้องบอกว่า "มี X อยู่หรือไม่ถ้าเป็นเช่นนั้นฉันจะเรียกวิธีที่ใช้งานมากเกินไป A. มี Y อยู่หรือไม่ฉันจะต้องเรียกใช้วิธีที่มากเกินไป B. ถ้า X และ Y ฉันจะต้องเรียกใช้เมธอดที่โอเวอร์โหลด C " และอื่น ๆ อาจมีคำตอบที่ดีสำหรับสิ่งนี้ แต่คำอธิบายของ "ทำไม" ถึงไม่ครอบคลุม
Garret Wilson

9
นอกจากนี้เมื่อพูดถึงคอลเลกชันมีความแตกต่างที่ชัดเจนระหว่างคอลเลกชันที่ว่างเปล่าและไม่มีคอลเลกชัน ตัวอย่างเช่นแคช มันเป็นแคชพลาดหรือไม่? ว่างหรือไม่ก็ได้ / null มีการเข้าชมแคชที่เกิดขึ้นกับการรวบรวมว่างเปล่าหรือไม่? ตัวเลือก / การรวบรวมเต็มรูปแบบ
Ajax

1
@ Ajax ฉันคิดว่าคุณเข้าใจผิดบทความ พวกเขาจะอ้างจุดสนับสนุนโดยมีผลบังคับใช้ Javaหนังสือซึ่งกล่าวว่าเมื่อชนิดวิธีการกลับมาเป็นคอลเลกชัน (ไม่ใช่วัตถุโดยพลการเป็นแคชจะกลับ) แล้วคุณควรสนับสนุนกลับคอลเลกชันที่ว่างเปล่าแทนหรือnull Optional
Gili

4
แน่นอนว่าคุณควรให้ความช่วยเหลือแต่คุณไม่สามารถควบคุมรหัสได้เสมอและในความเป็นจริงคุณอาจต้องการแยกความแตกต่างระหว่าง "มีค่าและเป็นคอลเลกชันที่ว่างเปล่า" กับ "ไม่มี ค่าที่กำหนดไว้ (ยัง) " เนื่องจาก "magic null" เป็นวิธีการที่ท้อใจดังนั้นจึงขึ้นอยู่กับคุณผู้พัฒนาจึงเลือกตัวเลือกที่แย่ที่สุด ฉันชอบที่ว่างเปล่าเป็นตัวเลือกให้เป็นตัวแทนของแคชพลาดแทนที่จะเป็นคอลเลกชันที่ว่างเปล่าตั้งแต่ค่าที่เก็บไว้เกิดขึ้นจริงอาจจะเป็นคอลเลกชันที่ว่างเปล่า
Ajax

86

เกือบจะไม่มีเหตุผลที่ดีที่ไม่ได้ใช้Optionalเป็นพารามิเตอร์ ข้อโต้แย้งต่อสิ่งนี้ขึ้นอยู่กับข้อโต้แย้งจากผู้มีอำนาจ (ดู Brian Goetz - ข้อโต้แย้งของเขาคือเราไม่สามารถบังคับใช้ตัวเลือกที่ไม่เป็นโมฆะได้) หรือการOptionalโต้แย้งอาจเป็นโมฆะ แน่นอนการอ้างอิงใด ๆ ใน Java อาจเป็นโมฆะเราจำเป็นต้องสนับสนุนกฎที่บังคับใช้โดยคอมไพเลอร์ไม่ใช่หน่วยความจำโปรแกรมเมอร์ (ซึ่งเป็นปัญหาและไม่ขยาย)

ภาษาโปรแกรมเชิงหน้าที่สนับสนุนOptionalพารามิเตอร์ หนึ่งในวิธีที่ดีที่สุดในการใช้สิ่งนี้คือการมีพารามิเตอร์ที่เป็นตัวเลือกหลายตัวและliftM2การใช้ฟังก์ชั่นที่สมมติว่าพารามิเตอร์นั้นไม่ว่างเปล่าและส่งคืนทางเลือก (ดูที่http://www.functionaljava.org/javadoc/4.4/functionaljava/fj/) data / Option.html # liftM2-fj.F- ) Java 8 มีการใช้งานไลบรารีที่ จำกัด มาก

ในฐานะที่เป็นโปรแกรมเมอร์ Java เราควรใช้ null เพื่อโต้ตอบกับไลบรารีดั้งเดิมเท่านั้น


3
วิธีการเกี่ยวกับการใช้@Nonnullและ@Nullableจาก javax.annotation แทน? ฉันใช้มันอย่างกว้างขวางในห้องสมุดที่ฉันพัฒนาและการสนับสนุนจาก IDE (IntelliJ) นั้นดีมาก
Rogério

1
ไม่เป็นไร แต่คุณต้องใช้คำอธิบายประกอบนี้ทุกที่และคุณจำเป็นต้องมีห้องสมุดเพื่อสนับสนุนวิธีการเช่นแผนที่, flatMap / bind, liftM2, ลำดับ ฯลฯ
Mark Perry

14
ฟังก์ชั่นการเขียนโปรแกรมภาษาสนับสนุนพารามิเตอร์เสริม ต้องการการอ้างอิง ฟังก์ชั่นส่วนใหญ่ไม่ควรเลือกใช้ แทนความรับผิดชอบของการจัดการ (ใช้ฟังก์ชั่นลำดับสูงกว่าที่เหมาะสม) ด้วยการมีหรือไม่มีค่าอยู่ในผู้โทรของฟังก์ชั่นที่เป็นปัญหา
jub0bs

5
นี้. อันที่จริงไม่มีคำตอบที่มีการชักจูงให้เพียงพอและไม่มีของพวกเขาตอบคำถามนี้โดยเฉพาะ "ทำไมการใช้ optionals เป็นพารามิเตอร์วิธีถือว่าปฏิบัติไม่ดีในขณะที่ annotating พารามิเตอร์ด้วยวิธี@NonNullมีทั้งหมดดีถ้าพวกเขาทำหน้าที่วัตถุประสงค์เดียวกันในรูปแบบที่แตกต่างกันอย่างไร" สำหรับฉันมีข้อโต้แย้งเพียงข้อเดียวที่อย่างน้อยก็มีความหมายบางอย่าง: "ควรใช้ตัวเลือกเพื่อให้ API ที่ดีกว่าซึ่งมีประเภทการส่งคืนที่ชัดเจนอย่างไรก็ตามสำหรับอาร์กิวเมนต์ของเมธอด - ยังฉันไม่คิดว่านี่เป็นข้อโต้แย้งที่แข็งแกร่งพอ
Utku Özdemir

1
แน่นอนพอใจกว่าเป็นโมฆะ แต่สิ่งที่เป็นที่นิยมมากกว่าคือไม่จำเป็นต้องมีค่าเพิ่มเติมมากมายตั้งแต่แรกเพราะการออกแบบที่ดี: D
yeoman

12

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

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

  • คุณต้องการให้ฟังก์ชันของคุณใช้ร่วมกับ API อื่นที่ส่งกลับ Optional
  • ฟังก์ชั่นของคุณควรส่งคืนสิ่งอื่นที่ไม่ใช่ค่าว่างOptionalหากค่าที่กำหนดเป็นค่าว่าง
  • คุณคิดว่าOptionalมันยอดเยี่ยมมากที่ใครก็ตามที่ใช้ API ของคุณควรจะต้องเรียนรู้เกี่ยวกับมัน ;-)

10

รูปแบบที่มีความOptionalเป็นหนึ่งเพื่อหลีกเลี่ยงการกลับมา nullมันยังคงเป็นไปได้อย่างสมบูรณ์แบบที่จะผ่านnullไปยังวิธีการ

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

public int calculateSomething(@NotNull final String p1, @NotNull final String p2) {}

ปัญหาที่นี่เป็นที่ค่อนข้างที่ @NotNull ไม่ใช่กรณีเริ่มต้นและต้องมีคำอธิบายประกอบอย่างชัดเจน - ดังนั้นแผ่นเหล็กและสิ่งที่ผู้คนจะไม่ทำเนื่องจากความเกียจคร้าน
dwegener

1
@dwegener ใช่ แต่Optionalไม่ได้แก้ไขปัญหาอย่างใดอย่างหนึ่งดังนั้นคำแนะนำของฉันสำหรับคำอธิบายประกอบ
Makoto

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

โปรดทราบว่า @Nullable อาจไม่ได้หมายความว่าคุณ "ยอมรับ" null เป็นค่าที่ถูกต้องกล่าวคือไม่ส่งข้อยกเว้นใด ๆ อาจหมายความว่าคุณป้องกันกรณีนี้ แต่จะยังคงมีข้อยกเว้น (นี่คือความหมาย Findbugs) ดังนั้นคุณสามารถแนะนำความกำกวมด้วยคำอธิบายประกอบดังกล่าวแทนความชัดเจน
tkruse

ฉันไม่แน่ใจว่ามีความคลุมเครืออะไรบ้างที่ @ user2986984 "การไม่จัดการค่าว่าง" อาจหมายถึงข้อยกเว้นที่เกิดขึ้นจริงเท่านั้น
Makoto

7

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

ที่ถูกกล่าวว่าวิธีการผูกมัดร่วมกันที่เลือก / คืนตัวเลือกเป็นสิ่งที่สมเหตุสมผลที่จะทำเช่นบางที Monad


7
ไม่สามารถคืนค่าและฟิลด์เป็นค่าว่างได้หรือไม่ ดังนั้นOptionalจะไม่มีจุดหมายอย่างสิ้นเชิง?
ซามูเอลเอ็ดวินวอร์ด

6

ตรวจสอบ JavaDoc ใน JDK10, https://docs.oracle.com/javase/10/docs/api/java/util/Optional.htmlบันทึกย่อของ API จะถูกเพิ่ม:

API หมายเหตุ: ตัวเลือกมีไว้เพื่อใช้เป็นหลักในการส่งคืนเมธอดที่มีความต้องการชัดเจนในการแสดง "ไม่มีผลลัพธ์" และตำแหน่งที่ใช้ null อาจทำให้เกิดข้อผิดพลาด


4

สิ่งที่ฉันใช้คือตัวเลือกควรเป็น Monad และสิ่งเหล่านี้ไม่สามารถคาดเดาได้ใน Java

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

การผสมผสานสองระดับของสิ่งที่เป็นนามธรรมไม่ช่วยให้อ่านง่ายดังนั้นคุณควรหลีกเลี่ยง


3

อีกเหตุผลที่ควรระมัดระวังเมื่อผ่านOptionalพารามิเตอร์ as คือวิธีการควรทำสิ่งหนึ่ง ... ถ้าคุณผ่านOptionalparam คุณสามารถทำมากกว่าหนึ่งสิ่งได้มันอาจคล้ายกับการส่งพารามิเตอร์แบบบูล

public void method(Optional<MyClass> param) {
     if(param.isPresent()) {
         //do something
     } else {
         //do some other
     }
 }

ฉันคิดว่านั่นไม่ใช่เหตุผลที่ดี ตรรกะเดียวกันสามารถนำมาใช้สำหรับการตรวจสอบว่าพารามิเตอร์เป็นโมฆะหรือไม่หากไม่ได้ใช้ตัวเลือก จริงๆแล้ววิธีif(param.isPresent())นี้ไม่ใช่วิธีที่ดีที่สุดในการใช้ตัวเลือก แต่คุณสามารถใช้:param.forEach(() -> {...})
hdkrus

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

2

การยอมรับตัวเลือกเป็นพารามิเตอร์ทำให้การตัดที่ไม่จำเป็นในระดับผู้โทร

ตัวอย่างเช่นในกรณีของ:

public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {}

สมมติว่าคุณมีสตริงที่ไม่ใช่ค่า null สองสตริง (เช่นส่งคืนจากวิธีอื่น):

String p1 = "p1"; 
String p2 = "p2";

คุณถูกบังคับให้ต้องห่อในตัวเลือกแม้ว่าคุณจะรู้ว่าพวกเขาไม่ว่างเปล่า

สิ่งนี้แย่ลงไปอีกเมื่อคุณต้องเขียนกับโครงสร้าง "แม็พ" อื่น ๆ เช่น Eithers :

Either<Error, String> value = compute().right().map((s) -> calculateSomething(
< here you have to wrap the parameter in a Optional even if you know it's a 
  string >));

Ref:

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

อ้าง https://github.com/teamdigitale/digital-citizenship-functions/pull/148#discussion_r170862749


1

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


1

ฉันเชื่อว่า reson of being คือคุณต้องตรวจสอบก่อนว่าตัวเลือกนั้นเป็นโมฆะหรือไม่และลองประเมินค่าที่มันห่อหุ้ม มีการตรวจสอบที่ไม่จำเป็นมากเกินไป


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

1

optionals ไม่ได้ออกแบบมาเพื่อการนี้ตามที่อธิบายไว้อย่างชัดเจนโดยไบรอันเก๊

คุณสามารถใช้@Nullableเพื่อแสดงว่าอาร์กิวเมนต์ของเมธอดสามารถเป็นโมฆะได้ การใช้ตัวเลือกไม่ได้ช่วยให้คุณสามารถเขียนตรรกะวิธีการของคุณได้อย่างเป็นระเบียบมากขึ้น


5
ฉันขอโทษ แต่ฉันไม่สามารถเห็นด้วยกับข้อโต้แย้ง "ไม่ได้รับการออกแบบ" หรือ "ใครบางคนแนะนำให้ต่อต้านมัน" วิศวกรเราควรเจาะจง การใช้ @Nullable นั้นยิ่งแย่กว่าการใช้ตัวเลือกเนื่องจาก Optionals นั้นละเอียดมากขึ้นจากมุมมอง API ฉันไม่เห็นเหตุผลที่ดีในการใช้ตัวเลือก Optionals เป็นข้อโต้แย้ง (หากคุณไม่ต้องการใช้วิธีการมากไป)
Kacper86

0

อีกวิธีหนึ่งที่คุณสามารถทำได้คือ

// get your optionals first
Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();

// bind values to a function
Supplier<Integer> calculatedValueSupplier = () -> { // your logic here using both optional as state}

เมื่อคุณสร้างฟังก์ชั่น (ซัพพลายเออร์ในกรณีนี้) คุณจะสามารถผ่านสิ่งนี้เป็นตัวแปรอื่น ๆ และจะสามารถเรียกมันได้โดยใช้

calculatedValueSupplier.apply();

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

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


0

ตอนแรกฉันยังต้องการส่งพารามิเตอร์ Optionals แต่ถ้าคุณเปลี่ยนจากเปอร์สเปคทีฟ API ไปเป็นเปอร์สเปคทีฟผู้ใช้ API คุณจะเห็นข้อเสีย

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

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();

MyCalculator mc = new MyCalculator();
p1.map(mc::setP1);
p2.map(mc::setP2);
int result = mc.calculate();

0

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

สิ่งที่ฉันได้ทำซึ่งได้ทำหน้าที่ฉันเป็นกฎง่ายๆ คือการใช้ Optionals สำหรับประเภทส่งคืนและใช้ Optionals เป็นพารามิเตอร์เท่านั้นหากฉันต้องการทั้งค่าของตัวเลือกและสภาพอากาศหรือไม่หรือไม่ก็ได้ตัวเลือกมีค่าภายในวิธีการ

ถ้าฉันสนใจเฉพาะค่าฉันตรวจสอบ isPresent ก่อนเรียกวิธีถ้าฉันมีการบันทึกบางชนิดหรือตรรกะต่าง ๆ ภายในวิธีที่ขึ้นอยู่กับว่ามีค่าอยู่หรือไม่ฉันจะผ่านตัวเลือกอย่างมีความสุข


0

นี่เป็นเพราะเรามีข้อกำหนดที่แตกต่างกันสำหรับผู้ใช้ API และนักพัฒนา API

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

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


0

ก่อนอื่นถ้าคุณใช้วิธีที่ 3 คุณสามารถแทนที่โค้ด 14 บรรทัดสุดท้ายด้วย:

int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));

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

public int calculateSomething(String p1OrNull, BigDecimal p2OrNull)

วิธีนี้เป็นที่ชัดเจนว่าอนุญาตให้มีค่า Null

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

public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper)

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

public static <T, U> Function<? super T, Optional<U>> optFun(Function<T, U> function) {
    return t -> Optional.ofNullable(function.apply(t));
}

สมมติว่าคุณมีทะเยอทะยานเช่นนี้: String getName()

คุณไม่สามารถส่งไปที่ flatMap ดังนี้:

opt.flatMap(Widget::getName) // Won't work!

แต่คุณสามารถผ่านมันได้ดังนี้:

opt.flatMap(optFun(Widget::getName)) // Works great!

นอกเหนือจากการเขียนโปรแกรมที่ใช้งานได้แล้วควรหลีกเลี่ยง Optionals

Brian Goetz พูดได้ดีที่สุดเมื่อเขาพูดสิ่งนี้:

เหตุผลเพิ่มเติมถูกเพิ่มลงใน Java เนื่องจาก:

return Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .findFirst()
    .getOrThrow(() -> new InternalError(...));

สะอาดกว่านี้:

Method matching =
    Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .getFirst();
if (matching == null)
  throw new InternalError("Enclosing method not found");
return matching;

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