เหตุใดจึงต้องลองใช้ ... ในที่สุดโดยไม่มีข้อห้าม?


79

try ... catchวิธีที่คลาสสิกในการเขียนโปรแกรมอยู่กับ เมื่อไรที่มันจะเหมาะสมที่จะใช้tryโดยไม่catch?

ใน Python ข้อมูลต่อไปนี้ถูกกฎหมายและสมเหตุสมผล:

try:
  #do work
finally:
  #do something unconditional

อย่างไรก็ตามรหัสไม่ได้catchอะไรเลย ในทำนองเดียวกันหนึ่งสามารถคิดใน Java มันจะเป็นดังนี้:

try {
    //for example try to get a database connection
}
finally {
  //closeConnection(connection)
}

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

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


4
ใน Java ทำไมไม่ใส่คำสั่ง return ที่ท้ายบล็อกลอง?
วินไคลน์

2
ฉันเสียใจที่ลอง .. ในที่สุดและลอง .. จับทั้งคู่ใช้คำหลักลองนอกเหนือจากทั้งสองเริ่มต้นด้วยลองพวกเขากำลังสร้าง 2 ที่แตกต่างกันโดยสิ้นเชิง
Pieter B

3
try/catchไม่ใช่ "วิธีการเขียนโปรแกรมแบบดั้งเดิม" เป็นวิธีการเขียนโปรแกรม C ++ แบบคลาสสิกเนื่องจาก C ++ ขาดการลอง / สร้างที่เหมาะสมซึ่งหมายความว่าคุณต้องดำเนินการเปลี่ยนแปลงสถานะที่รับประกันได้โดยใช้ hacks ที่น่าเกลียดที่เกี่ยวข้องกับ RAII แต่ภาษา OO ที่ดีไม่มีปัญหานั้นเพราะพวกเขาให้ลอง / ในที่สุด มันใช้เพื่อจุดประสงค์ที่แตกต่างจากการลอง / จับ
Mason Wheeler

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

4
@ MasonWheeler "น่าเกลียดแฮ็ค"โปรดอธิบายสิ่งที่ไม่ดีเกี่ยวกับการมีวัตถุจัดการมันเป็นของตัวเองล้าง?
Baldrickk

คำตอบ:


146

ขึ้นอยู่กับว่าคุณสามารถจัดการกับข้อยกเว้นที่สามารถเลี้ยงดูได้ ณ จุดนี้หรือไม่

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

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

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


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

13
@ จะ - นั่นเป็นเหตุผลที่ฉันใช้วลี "ที่สุด"
ChrisF

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

เพราะบางครั้งวิธีที่อยู่ด้านบนใกล้เคียงที่สุดเท่าที่ทำได้
Newtopian

"เพียงแค่มีการลอง / ในที่สุดบล็อกมีเหตุผลอย่างสมบูรณ์" ก็มองหาคำตอบนี้ ขอบคุณ @ChrisF
neal aise

36

finallyบล็อกจะใช้สำหรับรหัสที่ต้องเรียกใช้เสมอไม่ว่าจะเป็นเงื่อนไขข้อผิดพลาด (ยกเว้น) ที่เกิดขึ้นหรือไม่

รหัสในfinallyบล็อกจะทำงานหลังจากtryบล็อกเสร็จสมบูรณ์และหากมีข้อผิดพลาดเกิดขึ้นหลังจากcatchบล็อกที่เกี่ยวข้องนั้นเสร็จสมบูรณ์ มันทำงานอยู่เสมอแม้ว่าจะมีข้อยกเว้นที่ไม่ได้ตรวจสอบที่เกิดขึ้นในtryหรือcatchบล็อก

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

ควรใช้ความระมัดระวังในfinallyบล็อกเพื่อให้แน่ใจว่ามันไม่ได้เป็นข้อยกเว้น ตัวอย่างเช่นให้แน่ใจว่าได้ตรวจสอบตัวแปรทั้งหมดnullเป็นต้น


8
+1: มันเป็นสำนวนที่ "ต้องทำความสะอาด" การใช้งานส่วนใหญ่try-finallyสามารถถูกแทนที่ด้วยwithคำสั่ง
S.Lott

12
ภาษาต่าง ๆ มีการปรับปรุงเฉพาะภาษาที่มีประโยชน์อย่างมากในการtry/finallyสร้าง C # มีusing, Python มีwithฯลฯ
yfeldblum

5
@yfeldblum - มีความแตกต่างเล็กน้อยระหว่างusingและtry-finallyเนื่องจากDisposeวิธีการจะไม่ถูกเรียกโดยusingบล็อกถ้ามีข้อยกเว้นเกิดขึ้นในตัวIDisposableสร้างของวัตถุ try-finallyช่วยให้คุณสามารถเรียกใช้รหัสได้แม้ว่าตัวสร้างของวัตถุจะเกิดข้อยกเว้น
Scott Whitlock

5
@ScottWhitlock: นั่นเป็นสิ่งที่ดีเหรอ? คุณกำลังพยายามทำอะไรเรียกวิธีการบนวัตถุที่ไม่มีโครงสร้าง นั่นเป็นสิ่งเลวร้ายหลายพันล้านอย่าง
DeadMG

1
@DeadMG: โปรดดูstackoverflow.com/q/14830534/18192
Brian

17

ตัวอย่างที่ลอง ... ในที่สุดโดยไม่มี catch clauseนั้นเหมาะสม (และยิ่งกว่านั้นเป็นสำนวน ) ใน Java คือการใช้งานของLockในแพ็คเกจล็อคยูทิลิตี้พร้อมกัน

  • นี่คือวิธีการอธิบายและจัดทำเอกสาร API (ตัวอักษรตัวหนาในเครื่องหมายคำพูดเป็นของฉัน):

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

     Lock l = ...;
     l.lock();
     try {
         // access the resource protected by this lock
     } finally {
         l.unlock();
     }
    

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


ฉันสามารถl.lock()ลองใช้ดูได้ไหม? try{ l.lock(); }finally{l.unlock();}
RMachnik

1
ในทางเทคนิคคุณสามารถ ฉันไม่ได้ใส่มันเพราะความหมายมันไม่สมเหตุสมผล tryในตัวอย่างนี้มีจุดประสงค์เพื่อห่อการเข้าถึงทรัพยากรเหตุใดจึงสร้างมลพิษกับสิ่งที่ไม่เกี่ยวข้องกับสิ่งนั้น
gnat

4
คุณสามารถ แต่ถ้าl.lock()ล้มเหลวfinallyบล็อกจะยังคงทำงานหากl.lock()อยู่ในtryบล็อก หากคุณทำเช่นเดียวกับตัวริ้นแนะนำfinallyบล็อกจะทำงานเฉพาะเมื่อเรารู้ว่าได้รับการล็อค
Wtrmute

12

ในระดับพื้นฐานcatchและfinallyแก้ปัญหาที่เกี่ยวข้อง แต่แตกต่างกันสองปัญหา:

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

ดังนั้นทั้งสองเกี่ยวข้องกับปัญหา (ข้อยกเว้น) แต่นั่นก็คือทั้งหมดที่พวกเขามีเหมือนกัน

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

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


2
nitpick: "... บล็อกในที่สุดก็จะต้องอยู่ในวิธีการเดียวกันที่เป็นทรัพยากรที่ได้สร้างขึ้น ..." เป็นความคิดที่ดีที่จะทำเช่นนั้นเพราะจะเห็นได้ง่ายขึ้นว่าไม่มีทรัพยากรรั่วไหล อย่างไรก็ตามมันไม่ได้เป็นเงื่อนไขที่จำเป็น เช่นคุณไม่ต้องทำอย่างนั้น คุณสามารถปล่อยทรัพยากรในส่วนfinallyของคำสั่ง try (แบบสแตติกหรือแบบไดนามิก) ... และยังสามารถป้องกันการรั่วได้ 100%
สตีเฟ่นซี

6

@yfeldblum มีคำตอบที่ถูกต้อง: ลองโดยไม่ต้องมีคำสั่ง catch โดยปกติแล้วควรแทนที่ด้วยโครงสร้างภาษาที่เหมาะสม

ใน C ++ มันใช้ RAII และ constructors / destructors ใน Python มันเป็นwithคำสั่ง; และใน C # มันเป็นusingคำสั่ง

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


2
และใน Java เป็นบล็อก ARM
MarkJ

2
สมมติว่าคุณมีวัตถุที่ออกแบบมาไม่ดี (ตัวอย่างเช่นวัตถุที่ไม่ได้ติดตั้ง IDisposable ใน C # อย่างเหมาะสม) ซึ่งไม่ใช่ตัวเลือกที่ทำงานได้
mootinator

1
@mootinator: คุณไม่สามารถสืบทอดจากวัตถุที่ออกแบบมาไม่ดีและแก้ไขได้หรือไม่
Neil G

2
หรือการห่อหุ้ม? Bah ฉันหมายถึงใช่แน่นอน
mootinator

4

ในหลายภาษาfinallyคำสั่งจะทำงานหลังจากคำสั่ง return หมายความว่าคุณสามารถทำสิ่งต่อไปนี้

try {
  // Do processing
  return result;
} finally {
  // Release resources
}

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

ไม่ว่าจะดีหรือไม่ดีขึ้นอยู่กับการถกเถียง แต่try {} finally {}ก็ไม่ได้ จำกัด อยู่แค่การจัดการข้อยกเว้นเสมอไป


0

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

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

ป้อนคำอธิบายรูปภาพที่นี่

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

จุดล้มเหลวและการกู้คืน

ตอนนี้มันก็ไม่เคยยากที่จะเขียนประเภทของฟังก์ชั่นที่ผมเรียกว่า "จุดที่เป็นไปได้ของความล้มเหลว" (ที่คนที่throw, IE) และ "การกู้คืนข้อผิดพลาดและรายงาน" ฟังก์ชั่น (คนที่catchเช่น)

ฟังก์ชั่นเหล่านั้นมักจะน่ารำคาญที่จะเขียนอย่างถูกต้องก่อนการจัดการข้อยกเว้นที่สามารถใช้ได้ตั้งแต่ฟังก์ชั่นที่สามารถใช้เป็นความล้มเหลวภายนอกเช่นความล้มเหลวในการจัดสรรหน่วยความจำก็สามารถกลับมาNULLหรือ0หรือ-1หรือตั้งค่ารหัสข้อผิดพลาดในระดับโลกหรือสิ่งดังต่อไปนี้ และการกู้คืนข้อผิดพลาด / การรายงานเป็นเรื่องง่ายเสมอตั้งแต่เมื่อคุณทำงานลง call stack ไปยังจุดที่เหมาะสมในการกู้คืนและรายงานความล้มเหลวคุณเพียงแค่นำรหัสข้อผิดพลาดและ / หรือข้อความและรายงานไปยังผู้ใช้ และโดยธรรมชาติแล้วฟังก์ชั่นที่อยู่ในลำดับชั้นของสิ่งนี้ซึ่งไม่เคยล้มเหลวไม่ว่าจะมีการเปลี่ยนแปลงอย่างไรในอนาคต ( Convert Pixel) จะตายง่ายต่อการเขียนอย่างถูกต้อง (อย่างน้อยก็เกี่ยวกับการจัดการข้อผิดพลาด)

การแพร่กระจายข้อผิดพลาด

อย่างไรก็ตามฟังก์ชั่นที่น่าเบื่อนั้นมักจะเกิดจากความผิดพลาดของมนุษย์คือตัวกระจายข้อผิดพลาดซึ่งไม่ได้ทำงานโดยตรงกับความล้มเหลว แต่เรียกว่าฟังก์ชั่นที่อาจล้มเหลวได้ลึกลงไปในลำดับชั้น ณ จุดนั้นAllocate Scanlineอาจต้องจัดการกับความล้มเหลวจากmallocนั้นส่งคืนข้อผิดพลาดลงไปConvert Scanlinesจากนั้นConvert Scanlinesจะต้องตรวจสอบข้อผิดพลาดนั้นและส่งมันลงไปDecompress Imageแล้วจากนั้นDecompress Image->Parse ImageและParse Image->Load ImageและLoad Imageไปยังคำสั่งผู้ใช้ปลายทางที่มีการรายงานข้อผิดพลาด .

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

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

การลดข้อผิดพลาดของมนุษย์: รหัสข้อผิดพลาดทั่วโลก

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

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

การลดข้อผิดพลาดของมนุษย์: การจัดการข้อยกเว้น

อย่างไรก็ตามวิธีการแก้ปัญหาข้างต้นยังคงต้องใช้ฟังก์ชั่นมากมายในการจัดการด้านการควบคุมการแพร่กระจายข้อผิดพลาดด้วยตนเองแม้ว่ามันอาจจะลดจำนวนบรรทัดif error happened, return errorของรหัสประเภทคู่มือ มันจะไม่กำจัดมันอย่างสมบูรณ์เพราะมักจะต้องมีการตรวจสอบข้อผิดพลาดอย่างน้อยหนึ่งที่และส่งคืนเกือบทุกฟังก์ชันการเผยแพร่ข้อผิดพลาดเดียว ดังนั้นนี่คือเมื่อการจัดการข้อยกเว้นเข้ามาในรูปภาพเพื่อบันทึกวัน (sorta)

แต่คุณค่าของการจัดการข้อยกเว้นที่นี่คือการเพิ่มความจำเป็นในการจัดการกับลักษณะการไหลของการควบคุมของการเผยแพร่ข้อผิดพลาดด้วยตนเอง นั่นหมายความว่าคุณค่าของมันนั้นเชื่อมโยงกับความสามารถในการหลีกเลี่ยงการเขียนจำนวนcatchบล็อกในฐานโค้ดของคุณ ในแผนภาพด้านบนสถานที่เดียวที่ควรมีcatchบล็อกคือLoad Image User Commandที่ซึ่งรายงานข้อผิดพลาด ไม่มีอะไรที่ควรจะต้องทำcatchอย่างอื่นเพราะอย่างนั้นมันเริ่มที่จะได้รับเป็นที่น่าเบื่อและเป็นข้อผิดพลาดได้ง่ายเช่นเดียวกับการจัดการรหัสข้อผิดพลาด

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

การล้างทรัพยากร

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

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

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

ย้อนกลับผลข้างเคียงภายนอก

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

นี่finallyคือวิธีการแก้ปัญหาที่หรูหราที่สุดในภาษาที่หมุนรอบความแปรปรวนและผลข้างเคียงเพราะบ่อยครั้งที่ตรรกะประเภทนี้มีความเฉพาะเจาะจงกับฟังก์ชั่นหนึ่ง ๆ และไม่ได้แมปกับแนวคิดของ "การล้างทรัพยากร" " และฉันขอแนะนำให้ใช้finallyอย่างเสรีในกรณีเหล่านี้เพื่อให้แน่ใจว่าฟังก์ชั่นของคุณย้อนกลับผลข้างเคียงในภาษาที่สนับสนุนไม่ว่าคุณจะต้องการcatchบล็อกหรือไม่(และอีกครั้งหากคุณถามฉันรหัสที่เขียนดีควรมีจำนวนขั้นต่ำcatchบล็อกและcatchบล็อกทั้งหมดควรอยู่ในสถานที่ที่เหมาะสมที่สุดตามแผนภาพด้านบนLoad Image User Command)

ภาษาดรีม

อย่างไรก็ตาม IMO finallyนั้นใกล้เคียงกับอุดมคติสำหรับการกลับรายการผลข้างเคียง แต่ไม่มากนัก เราจำเป็นต้องแนะนำbooleanตัวแปรหนึ่งตัวเพื่อย้อนกลับผลข้างเคียงอย่างมีประสิทธิภาพในกรณีของการออกก่อนกำหนด (จากข้อยกเว้นที่ส่งออกไปหรืออย่างอื่น) เช่น:

bool finished = false;
try
{
    // Cause external side effects.
    ...

    // Indicate that all the external side effects were
    // made successfully.
    finished = true; 
}
finally
{
    // If the function prematurely exited before finishing
    // causing all of its side effects, whether as a result of
    // an early 'return' statement or an exception, undo the
    // side effects.
    if (!finished)
    {
        // Undo side effects.
        ...
    }
}

ถ้าฉันสามารถออกแบบภาษาได้วิธีการในฝันของฉันในการแก้ปัญหานี้จะเป็นเช่นนี้เพื่อทำให้รหัสข้างบนเป็นไปโดยอัตโนมัติ:

transaction
{
    // Cause external side effects.
    ...
}
rollback
{
    // This block is only executed if the above 'transaction'
    // block didn't reach its end, either as a result of a premature
    // 'return' or an exception.

    // Undo side effects.
    ...
}

... กับ destructors เพื่อทำให้การทำความสะอาดของทรัพยากรในท้องถิ่นทำให้มันดังนั้นเราจะต้องtransaction, rollbackและcatch(แม้ว่าฉันอาจยังคงต้องการที่จะเพิ่มfinallyสำหรับการพูด, การทำงานร่วมกับทรัพยากร C ที่ไม่ได้ทำความสะอาดตัวเองขึ้นไป) อย่างไรก็ตามfinallyด้วยbooleanตัวแปรเป็นสิ่งที่ใกล้เคียงที่สุดที่จะทำให้สิ่งนี้ตรงไปตรงมาที่ฉันพบว่ายังขาดภาษาในฝันของฉัน วิธีแก้ปัญหาที่ตรงไปตรงมามากที่สุดอันดับที่สองที่ฉันพบคือนี้เป็นขอบเขตของภาษาเช่น C ++ และ D แต่ฉันมักจะพบว่าขอบเขตของตัวป้องกันนั้นค่อนข้างน่าอึดอัดใจในทางแนวคิดเนื่องจากมันทำให้แนวคิดของ ในความคิดของฉันเหล่านั้นเป็นแนวคิดที่แตกต่างกันมากที่จะจัดการในวิธีที่แตกต่าง

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

ข้อสรุป

ฉันคิดว่าtry/finallyรหัสของคุณสำหรับการปิดซ็อกเก็ตนั้นดีและดีมากเพราะ Python ไม่มีตัวทำลาย C ++ เทียบเท่าและโดยส่วนตัวฉันคิดว่าคุณควรใช้สิ่งนี้อย่างอิสระสำหรับสถานที่ที่ต้องการผลข้างเคียง และลดจำนวนสถานที่ที่คุณต้องไปcatchยังสถานที่ที่เหมาะสมที่สุด


-66

การจับข้อผิดพลาด / ข้อยกเว้นและการจัดการพวกเขาในลักษณะที่เรียบร้อยขอแนะนำอย่างยิ่งแม้ว่าจะไม่ได้รับคำสั่ง

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

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

กฎทอง: รับข้อยกเว้นเสมอเนื่องจากการเดาต้องใช้เวลา


22
-1: ใน Java อาจจำเป็นต้องใช้คำสั่งย่อยในที่สุดเพื่อปล่อยทรัพยากร (เช่นปิดไฟล์หรือปล่อยการเชื่อมต่อฐานข้อมูล) นั่นเป็นอิสระจากความสามารถในการจัดการข้อยกเว้น
วินไคลน์

@ kevincline เขาไม่ได้ถามว่าจะใช้ในที่สุดหรือไม่ ... ทั้งหมดที่เขาถามคือการจับข้อยกเว้นจำเป็นหรือไม่ .... เขารู้ว่าสิ่งที่พยายามจับและในที่สุดก็ทำ ..... ในที่สุดก็เป็น ส่วนที่สำคัญที่สุดเราทุกคนรู้ดีและทำไมมันถึงถูกใช้ ....
Pankaj Upadhyay

63
@Pankaj: คำตอบของคุณแสดงให้เห็นว่าข้อควรเป็นปัจจุบันเมื่อมีการcatch tryผู้มีประสบการณ์มากขึ้นรวมถึงฉันเชื่อว่านี่เป็นคำแนะนำที่ไม่ดี เหตุผลของคุณมีข้อบกพร่อง เมธอดที่มีเครื่องหมายtryไม่ใช่สถานที่ที่เป็นไปได้เท่านั้นที่สามารถดักจับข้อยกเว้นได้ บ่อยครั้งที่ง่ายที่สุดและดีที่สุดในการอนุญาตการจับและรายงานข้อยกเว้นจากระดับบนสุดแทนที่จะทำซ้ำประโยคการจับตลอดทั้งรหัส
kevin cline

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

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