ฟังก์ชั่นกลับจริง / เท็จเทียบกับเป็นโมฆะเมื่อประสบความสำเร็จและโยนข้อยกเว้นเมื่อล้มเหลว


22

ฉันกำลังสร้าง API ซึ่งเป็นฟังก์ชันที่อัปโหลดไฟล์ ฟังก์ชั่นนี้จะไม่ส่งคืน / เป็นโมฆะหากไฟล์ถูกอัปโหลดอย่างถูกต้องและส่งข้อยกเว้นเมื่อเกิดปัญหา

ทำไมมีข้อยกเว้นและไม่ใช่แค่ของเท็จ? เพราะภายในข้อยกเว้นฉันสามารถระบุสาเหตุของความล้มเหลว (ไม่มีการเชื่อมต่อชื่อไฟล์หายไปรหัสผ่านผิดรายละเอียดไฟล์หายไป ฯลฯ ) ฉันต้องการสร้างข้อยกเว้นที่กำหนดเอง (ด้วย enum บางอย่างเพื่อช่วยผู้ใช้ API ในการจัดการข้อผิดพลาดทั้งหมด)

นี่เป็นวิธีปฏิบัติที่ดีหรือเป็นการส่งคืนวัตถุที่ดีกว่า (ภายในบูลีนข้อความแสดงข้อผิดพลาดเพิ่มเติมและ enum สำหรับข้อผิดพลาด) หรือไม่?


5
มีความเป็นไปได้ที่ซ้ำกันของReturn Magic value, โยน exception หรือ return false เมื่อล้มเหลว?
ริ้น

59
จริงและเท็จไปด้วยกันได้ดี ความว่างเปล่าและข้อยกเว้นเข้ากันได้ดี จริงและมีข้อยกเว้นร่วมกันเช่นแมวและภาษี สักวันฉันอาจจะอ่านรหัสของคุณ โปรดอย่าทำอย่างนี้กับฉัน
candied_orange

4
ฉันชอบรูปแบบที่วัตถุถูกส่งคืนที่ห่อหุ้มความสำเร็จหรือความล้มเหลว ตัวอย่างเช่น Rust มีResult<T, E>ตำแหน่งTเป็นผลลัพธ์หากสำเร็จและEเป็นข้อผิดพลาดหากไม่สำเร็จ การโยนข้อยกเว้น (อาจเป็น - ไม่แน่ใจใน Java) อาจมีราคาแพงเนื่องจากเกี่ยวข้องกับการคลายการเรียกสแต็ก แต่การสร้างวัตถุข้อยกเว้นนั้นราคาถูก เห็นได้ชัดว่าใช้รูปแบบที่เป็นที่ยอมรับของภาษาที่คุณกำลังใช้ แต่ไม่รวมบูลีนและข้อยกเว้นเช่นนี้
Dan Pantry

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

4
@DanPantry ใน Java call call จะถูกตรวจสอบเมื่อสร้างข้อยกเว้นไม่ใช่เมื่อทำการขว้าง fillInStackTrace();ถูกเรียกใน super constructor ในชั้นเรียนThrowable
Simon Forsberg

คำตอบ:


35

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

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

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


7
หากเป็นผลลัพธ์ที่คาดไว้สำหรับคำขออัปโหลดไฟล์ที่ล้มเหลวฉันจะประหลาดใจโดยมีข้อยกเว้นเกิดขึ้นในหน้าของฉัน (โดยเฉพาะอย่างยิ่งหากมีค่าส่งคืนในลายเซ็นวิธีการ)
hoffmale

1
โปรดทราบว่าเหตุผลในการทำให้ข้อยกเว้นมาตรฐานเป็นไปได้ว่าผู้โทร (อาจเป็นไปได้มากที่สุด) หลายคนจะไม่เขียนโค้ดเพื่อพยายามแก้ไขปัญหา เป็นผลให้ผู้โทรส่วนใหญ่ต้องการความเรียบง่าย "จับข้อยกเว้นกลุ่มใหญ่นี้" โดยไม่ต้องแสดงรายการทั้งหมดอย่างชัดเจน
jpmc26

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

21
Kilian พูดในสโลแกนที่เรียกซ้ำว่า "ข้อยกเว้นสำหรับสถานการณ์พิเศษ" สถานการณ์ที่ไม่ธรรมดาคือเมื่อฟังก์ชันไม่สามารถบรรลุวัตถุประสงค์ได้ ถ้าฟังก์ชันควรจะอ่านไฟล์และไฟล์ไม่สามารถอ่านได้ว่าเป็นพิเศษ หากฟังก์ชั่นนี้ควรบอกคุณว่ามีไฟล์อยู่หรือไม่ (ไม่ต้องสนใจว่า TOCTOU ที่มีศักยภาพ) แสดงว่าไฟล์ที่ไม่มีอยู่นั้นไม่พิเศษเพราะฟังก์ชั่นยังสามารถทำสิ่งที่ควรทำ ถ้าฟังก์ชั่นนั้นควรจะยืนยันว่าไฟล์นั้นมีอยู่แสดงว่ามันไม่มีอยู่นั้นยอดเยี่ยมเพราะนั่นคือสิ่งที่ "ยืนยัน" หมายถึง
Steve Jessop

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

31

ไม่มีเหตุผลที่จะกลับมาtrueประสบความสำเร็จหากคุณไม่กลับมาfalseล้มเหลวอีก รหัสลูกค้าควรมีลักษณะอย่างไร

if (result = tryMyAPICall()) {
    // business logic
}
else {
    // this will *never* happen anyways
}

ในกรณีนี้ผู้โทรต้องการบล็อกแบบลองต่อไป แต่ก็สามารถเขียนได้ดีกว่า:

try {
    result = tryMyAPICall();
    // business logic
    // will only reach this line when no exception
    // no reason to use an if-condition
} catch (SomeException se) { }

ดังนั้นtrueค่าที่ส่งคืนจะไม่เกี่ยวข้องอย่างสมบูรณ์สำหรับผู้โทร voidดังนั้นเพียงแค่ให้วิธีการ


โดยทั่วไปมีสามวิธีในการออกแบบโหมดความล้มเหลว

  1. ส่งคืนจริง / เท็จ
  2. ใช้voidยกเว้น (ทำเครื่องหมาย) แล้วโยน
  3. ส่งคืนวัตถุผลลัพธ์ระดับกลาง

ส่งคืนtrue/false

สิ่งนี้ใช้ใน API แบบเก่ากว่าซึ่งส่วนใหญ่เป็น c-style ข้อเสียคือ obviuos คุณไม่รู้ว่าเกิดอะไรขึ้น PHP ทำสิ่งนี้บ่อยครั้งนำไปสู่การใช้รหัสเช่นนี้:

if (xyz_parse($data) === FALSE)
   $error = xyz_last_error();

ในบริบทแบบมัลติเธรดนี่แย่ยิ่งกว่าเดิม

โยนข้อยกเว้น (ตรวจสอบ)

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

ส่งคืนวัตถุผลลัพธ์

นี่เป็นอีกวิธีที่ดีในการจัดการกับสิ่งนี้ มันมักจะใช้สำหรับการแยกหรือสิ่งที่ต้องได้รับการตรวจสอบ

ValidationResult result = parser.validate(data);
if (result.isValid())
    // business logic
else
    error = result.getvalidationError();

นิสัยดีสะอาดสำหรับผู้โทรเช่นกัน

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

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


5

นี้จริงๆลงมาไม่ว่าจะเป็นความล้มเหลวเป็นพิเศษหรือคาดว่า

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

หากข้อผิดพลาดทั่วไปเป็นเรื่องธรรมดาคุณควรปรับให้เหมาะสมสำหรับนักพัฒนาที่กำลังตรวจสอบข้อผิดพลาดเหล่านั้นและส่วนtry-catchคำสั่งค่อนข้างยุ่งยากกว่าif/elifหรือswitchชุดข้อมูลเล็กน้อย

ออกแบบ API เสมอในความคิดของคนที่ใช้งานอยู่


1
ในกรณีของฉันเหมือนใครบางคนชี้ให้เห็นควรเป็นความล้มเหลวพิเศษที่ผู้ใช้ API ไม่สามารถอัปโหลดไฟล์
Accollativo

4

falseอย่ากลับมาบูลถ้าคุณไม่เคยตั้งใจที่จะกลับมา เพียงทำวิธีการvoidและเอกสารที่จะทำให้เกิดข้อยกเว้น ( IOExceptionเหมาะสม) กับความล้มเหลว

เหตุผลนี้คือถ้าคุณส่งคืนบูลีนผู้ใช้ API ของคุณอาจสรุปได้ว่าเขา / เธอสามารถทำสิ่งนี้ได้:

if (fileUpload(file) == false) {
    // Handle failure.
}

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

try {
    fileUpload(file);
} catch (IOException e) {
    // Handle failure.
}

3

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

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

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


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