เหตุใดการใช้สิทธิพิเศษเพิ่มเติมเพื่อตรวจสอบตัวแปร


25

นำตัวอย่างโค้ดสองตัวอย่าง:

if(optional.isPresent()) {
    //do your thing
}

if(variable != null) {
    //do your thing
}

เท่าที่ฉันสามารถบอกความแตกต่างที่ชัดเจนที่สุดคือตัวเลือกต้องสร้างวัตถุเพิ่มเติม

อย่างไรก็ตามหลายคนเริ่มใช้ตัวเลือกอย่างรวดเร็ว ข้อดีของการใช้ตัวเลือกกับการตรวจสอบค่า Null คืออะไร?


4
คุณแทบไม่ต้องการใช้ isPresent คุณควรใช้ ifPresent หรือ map
jk

6
@jk เพราะifงบsoooooทศวรรษที่ผ่านมาและทุกคนโดยใช้แนวคิด monad และ lambdas ในขณะนี้
user253751

2
@immibis ฉันเคยมีการถกเถียงกันมานานเกี่ยวกับหัวข้อนี้กับผู้ใช้รายอื่น ประเด็นก็คือเมื่อif(x.isPresent) fails_on_null(x.get)คุณออกจากระบบประเภทและต้องให้การรับประกันว่ารหัสจะไม่ทำลาย "ในหัวของคุณ" ในระยะ (สั้นยอมรับ) ระหว่างเงื่อนไขและการเรียกใช้ฟังก์ชั่น ในoptional.ifPresent(fails_on_null)ระบบประเภททำให้การรับประกันนี้สำหรับคุณและคุณไม่ต้องกังวล
ziggystar

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

คำตอบ:


19

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

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

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

น่าเสียดายที่ภาษาส่วนใหญ่ที่เสนอในขณะนี้ยังOptionalไม่ได้ยกเลิกnullดังนั้นคุณจะได้รับประโยชน์จากแนวคิดนี้โดยการกำหนดนโยบายที่เข้มงวดว่า "ไม่แน่นอนnull" ดังนั้นOptionalในตัวอย่างเช่น Java ไม่น่าสนใจเท่าที่ควร


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

ภาษาสมัยใหม่ส่วนใหญ่มีประเภทที่ไม่สามารถลบล้างได้ Kotlin, typescript, AFAIK
Zen

5
IMO สิ่งนี้จะจัดการได้ดีขึ้นด้วยคำอธิบายประกอบ @Nullable ไม่มีเหตุผลที่จะใช้Optionalเพียงเพื่อเตือนให้คุณทราบว่าค่าอาจเป็นโมฆะ
Hilikus

18

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

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

return optional.flatMap(x -> x.anotherOptionalStep())
               .flatMap(x -> x.yetAnotherOptionalStep())
               .orElse(defaultValue);

ด้วยnullฉันจะต้องตรวจสอบสามครั้งnullก่อนที่จะดำเนินการต่อซึ่งเพิ่มความซับซ้อนมากและปวดหัวการบำรุงรักษารหัส Optionalsมีการตรวจสอบในตัวที่flatMapและorElseฟังก์ชั่น

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

โปรดทราบว่าฉันไม่ต้องกังวลเกี่ยวกับการห่อหุ้มสิ่งนี้ทั้งหมดไว้ในฟังก์ชั่นเดียวเพื่อปกป้องส่วนอื่น ๆ ของรหัสของฉันจากตัวชี้โมฆะจากผลลัพธ์ระดับกลาง ถ้ามันเหมาะสมกว่าที่จะมี.orElse(defaultValue)ฟังก์ชั่นอื่นของฉันฉันมี qualms น้อยลงเกี่ยวกับการวางไว้ที่นั่นและมันง่ายกว่ามากในการเขียนการดำเนินการระหว่างฟังก์ชั่นต่าง ๆ ตามที่ต้องการ


10

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

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


4

ผมขอยกตัวอย่าง:

class UserRepository {
     User findById(long id);
}

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

ตัวอย่างที่สอง:

class UserRepository {
     Optional<User> findById(long id);
}

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

นอกจากนี้ยังมีประโยชน์เมื่ออ่านรหัสลูกค้า:

User user = userRepository.findById(userId).get();

ฉันฝึกสายตาเพื่อดู. get () เป็นกลิ่นรหัสทันที หมายความว่าลูกค้าเพิกเฉยต่อสัญญาของวิธีการที่เรียกใช้ มันง่ายกว่าที่จะเห็นสิ่งนี้มากกว่าที่ไม่มีตัวเลือกเพราะในกรณีนี้คุณจะไม่ทราบทันทีว่าสัญญาของวิธีการที่เรียกว่า (ไม่ว่าจะอนุญาตให้เป็นโมฆะหรือไม่ได้ผลตอบแทน) ดังนั้นคุณต้องตรวจสอบก่อน

เป็นตัวเลือกใช้กับการประชุมที่วิธีการที่มีประเภทผลตอบแทนไม่เคยกลับเป็นโมฆะและมักจะมีการประชุม (น้อยที่แข็งแกร่ง) วิธีการที่ไม่มีประเภทผลตอบแทนที่เป็นตัวเลือกไม่กลับมาเป็นโมฆะ (แต่จะดีกว่า .


3

Optional<T>ช่วยให้คุณมี "ความล้มเหลว" หรือ "ไม่มีผล" เป็นค่าการตอบสนอง / ผลตอบแทนที่ถูกต้องสำหรับวิธีการของคุณ (คิดเช่นของการค้นหาฐานข้อมูล) การใช้Optionalแทนการใช้nullเพื่อระบุความล้มเหลว / ไม่มีผลลัพธ์มีข้อดีบางประการ:

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

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


4
นี่ไม่ใช่สิ่งที่เป็นตัวเลือกสำหรับ ในการรายงานข้อผิดพลาดให้ใช้ข้อยกเว้น ถ้าคุณไม่สามารถทำเช่นนั้นใช้ชนิดซึ่งมีทั้งผลหรือรหัสข้อผิดพลาด
James Youngman

ฉันไม่เห็นด้วยอย่างยิ่ง ไม่บังคับใช้ () เป็นวิธีที่ดีในการแสดงความล้มเหลวหรือไม่มีอยู่ในความคิดของฉัน
LegendLength

@ LegendLength ฉันต้องไม่เห็นด้วยอย่างยิ่งในกรณีที่เกิดข้อผิดพลาด หากฟังก์ชั่นล้มเหลวมันจะดีกว่าที่จะให้คำอธิบายแก่ฉันไม่ใช่แค่ว่างเปล่า "ฉันล้มเหลว"
Winston Ewert

ขออภัยฉันเห็นด้วยฉันหมายถึง 'ความล้มเหลวที่คาดหวัง' เนื่องจากไม่พบพนักงานที่ชื่อ 'x' ในระหว่างการค้นหา
LegendLength

3

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

opTr.ifPresent(tr -> transactions.put(tr.getTxid(), tr));

2

พิจารณาวิธีการต่อไปนี้:

void dance(Person partner)

ตอนนี้เรามาดูรหัสการโทร:

dance(null);

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

dance(optionalPerson)

สิ่งนี้ทำให้เกิดข้อผิดพลาดในการคอมไพล์เนื่องจากการเต้นคาดหวังว่าจะPersonไม่เป็นOptional<Person>

dance(optionalPerson.get())

หากฉันไม่มีบุคคลนี้ทำให้เกิดข้อยกเว้นในบรรทัดนี้ ณ จุดที่ฉันพยายามแปลงOptional<Person>ให้เป็นบุคคลโดยมิชอบด้วยกฎหมาย กุญแจสำคัญคือไม่เหมือนกับการส่งผ่านค่า null ยกเว้นการติดตามไปที่นี่ไม่ใช่ตำแหน่งอื่นในโปรแกรม

เมื่อต้องการรวมทุกอย่างเข้าด้วยกัน: การใช้ตัวเลือกในทางที่ผิดจะทำให้ง่ายต่อการติดตามปัญหาการใช้โมฆะในทางที่ผิดนั้นเป็นเรื่องยากที่จะติดตามปัญหา


1
หากคุณกำลังโยนข้อยกเว้นเมื่อเรียกOptional.get()รหัสของคุณเขียนไม่ดี - คุณไม่ควรโทรหา Optional.get โดยไม่ตรวจสอบOptional.isPresent()ก่อน (ตามที่ระบุใน OP) หรือคุณสามารถเขียนoptionalPersion.ifPresent(myObj::dance)ได้
Chris Cooper

@QmunkE, ใช่คุณควรโทรหา Option.get () เท่านั้นหากคุณรู้อยู่แล้วว่าตัวเลือกไม่ว่างเปล่า (เพราะคุณเรียกว่า isPresent หรือเพราะอัลกอริทึมของคุณรับประกัน) อย่างไรก็ตามฉันกังวลเกี่ยวกับผู้คนที่ตรวจสอบ isPresent อย่างไม่รู้ตัวแล้วมองข้ามค่าที่หายไปซึ่งสร้างโฮสต์ของความล้มเหลวเงียบซึ่งจะเป็นความเจ็บปวดในการติดตาม
Winston Ewert

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

@LegendLength ประสบการณ์ของฉันคือ 95% ของคดีสามารถตรวจสอบย้อนกลับได้อย่างง่ายดายเพื่อระบุว่า NULL มาจากไหน อย่างไรก็ตามส่วนที่เหลืออีก 5% นั้นยากมากที่จะติดตาม
Winston Ewert

-1

ใน Objective-C การอ้างอิงวัตถุทั้งหมดเป็นทางเลือก ด้วยเหตุนี้รหัสเช่นคุณโพสต์เป็นโง่

if (variable != nil) {
    [variable doThing];
}

โค้ดอย่างที่กล่าวมาข้างต้นนั้นไม่เคยได้ยินมาก่อนใน Objective-C โปรแกรมเมอร์จะ:

[variable doThing];

หากvariableมีค่าdoThingจะถูกเรียกใช้ ถ้าไม่ไม่เป็นอันตราย โปรแกรมจะถือว่าเป็นไม่มีการเรียกใช้และทำงานต่อไป

นี่คือประโยชน์ที่แท้จริงของตัวเลือก if (obj != nil)คุณจะได้ไม่ต้องครอกรหัสของคุณด้วย


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

-3

ถ้า (optional.isPresent ()) {

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

คุณอาจต้องการเขียนวิธีคงที่ระดับผู้ช่วยที่กำหนด null-ness ของประเภทวัตถุที่กำหนดใด ๆ เช่นนี้:

function isNull( object o ) 
{
   return ( null == o ); 
}

if ( isNull( optional ) ) ... 

หรืออาจเป็นประโยชน์มากขึ้น:

function isNotNull( object o ) 
{
   return ( null != o ); 
}

if ( isNotNull( optional ) ) ... 

แต่อย่างใดอย่างหนึ่งเหล่านี้จริงๆอ่านเพิ่มเติม / เข้าใจ / บำรุงรักษาได้ดีกว่าเดิมหรือไม่

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