ส่วน 'ในที่สุด' ของ 'ลอง ... จับได้ ... ในที่สุด' สร้างความจำเป็นหรือไม่


25

บางภาษา (เช่น C ++ และ PHP เวอร์ชันก่อนหน้า) ไม่รองรับfinallyส่วนของtry ... catch ... finallyโครงสร้าง คือfinallyเคยจำเป็น? เพราะรหัสในมันทำงานอยู่เสมอทำไมฉันจะไม่ / ไม่ควรวางรหัสนั้นหลังจากtry ... catchบล็อกโดยไม่มีส่วนfinallyคำสั่ง? ทำไมต้องใช้ (ฉันกำลังมองหาเหตุผล / แรงบันดาลใจสำหรับการใช้ / ไม่ใช้finallyไม่ใช่เหตุผลที่ต้องทำ 'จับ' หรือทำไมการทำเช่นนั้นถูกกฎหมาย)


ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
maple_shaft

คำตอบ:


36

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

try { 
    throw new SomeException();
} catch {
    DoSomethingWhichUnexpectedlyThrows();
}
Cleanup();

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


4
ขอบคุณสำหรับคำตอบที่กระชับและตรงไปตรงมาซึ่งไม่ได้พูดนอกเรื่องในเชิงทฤษฎีและ 'ภาษา X ดีกว่าอาณาเขตของ Y'
Agi Hammerthief

56

ดังที่คนอื่น ๆ ได้กล่าวไว้ไม่มีการรับประกันว่ารหัสหลังจากtryคำสั่งจะทำงานจนกว่าคุณจะตรวจพบข้อยกเว้นที่เป็นไปได้ทุกข้อ ที่กล่าวมานี้:

try {
   mightThrowSpecificException();
} catch (SpecificException e) {
   handleError();
} finally {
   cleanUp();
}

สามารถเขียนใหม่1เป็น:

try {
   mightThrowSpecificException();
} catch (SpecificException e) {
   try {
       handleError();
   } catch (Throwable e2) {
       cleanUp();
       throw e2;
   }
} catch (Throwable e) {
   cleanUp();
   throw e;
}
cleanUp();

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

C ++ ไม่มีfinallyเนื่องจากBjarne Stroustrup เชื่อว่า RAII ดีกว่าหรืออย่างน้อยก็พอเพียงสำหรับกรณีส่วนใหญ่:

เหตุใด C ++ จึงไม่สร้างโครงสร้าง "สุดท้าย"

เนื่องจาก C ++ สนับสนุนทางเลือกที่เกือบจะดีกว่าเสมอ: เทคนิค "การได้มาซึ่งทรัพยากรเป็นการเริ่มต้น" (TC ++ PL3 ส่วน 14.4) แนวคิดพื้นฐานคือการแสดงทรัพยากรโดยวัตถุในท้องถิ่นเพื่อให้ตัวทำลายวัตถุในท้องถิ่นจะปล่อยทรัพยากร ด้วยวิธีนี้โปรแกรมเมอร์ไม่สามารถลืมที่จะปล่อยทรัพยากร


1รหัสที่เฉพาะเจาะจงเพื่อตรวจจับข้อยกเว้นและทำการสร้างใหม่โดยไม่สูญเสียข้อมูลการติดตามสแต็กแตกต่างกันไปตามภาษา ฉันใช้จาวาซึ่งการติดตามสแต็กถูกจับเมื่อสร้างข้อยกเว้น ใน C # throw;คุณควรที่จะใช้เพียงแค่


8
คุณต้องจับข้อยกเว้นในhandleError()กรณีที่สองใช่ไหม?
Juri Robl

1
คุณอาจกำลังโยนข้อผิดพลาด ฉันจะใช้ถ้อยคำที่catch (Throwable t) {}มีลอง .. จับบล็อกรอบบล็อกเริ่มต้นทั้งหมด (เพื่อจับ throwables จากhandleErrorเช่นกัน)
njzk2

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

1
คำตอบนี้ไม่ได้ตอบคำถามที่ว่า C ++ ไม่มีfinallyซึ่งเป็นสิ่งที่เหมาะสมยิ่งกว่า
DeadMG

1
@AgiHammerthief ซ้อนtryอยู่ภายในcatchสำหรับข้อยกเว้นเฉพาะ ประการที่สองเป็นไปได้ที่คุณไม่ทราบว่าคุณสามารถจัดการข้อผิดพลาดได้สำเร็จหรือไม่จนกว่าคุณจะตรวจสอบข้อยกเว้นหรือสาเหตุของข้อยกเว้นยังป้องกันไม่ให้คุณจัดการข้อผิดพลาด (อย่างน้อยก็ในระดับนั้น) เป็นเรื่องปกติเมื่อใช้ I / O rethrow อยู่ที่นั่นเพราะวิธีเดียวที่จะรับประกันการcleanUpทำงานคือการจับทุกอย่างแต่รหัสดั้งเดิมจะอนุญาตให้มีข้อยกเว้นที่เกิดขึ้นในcatch (SpecificException e)บล็อกเพื่อเผยแพร่ขึ้นไปด้านบน
Doval

22

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

int DoSomething() {
    try {
        open_connection();
        return get_result();
    }
    catch {
        return 2;
    }
    finally {
        close_connection();
    }
}

VS

int DoSomething() {
    int result;
    try {
        open_connection();
        result = get_result();
    }
    catch {
        result = 2;
    }
    close_connection();
    return result;
}

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

3
บางทีสิ่งที่พบได้ทั่วไปก็คือการกลับเข้าไปในบล็อกลองมากกว่าภายในบล็อกจับ
Michael Anderson

finallyในใจของฉันรหัสไม่เพียงพออธิบายการใช้งานของ (ฉันจะใช้รหัสเช่นเดียวกับในบล็อกที่สองเนื่องจากมีคำสั่งการส่งคืนหลายรายการที่ทำให้ฉันหมดกำลังใจ)
Agi Hammerthief

15

ตามที่คุณได้คาดการณ์ไว้อย่างชัดเจนแล้ว C ++ ให้ความสามารถแบบเดียวกันโดยไม่มีกลไกนั้น ดังนั้นการพูดอย่างเคร่งครัดจึงไม่จำเป็นต้องใช้กลไกtry/finally

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

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

ฉันคิดว่าผู้ที่อ้างว่าไวยากรณ์ C ++ สำหรับความสามารถนี้เป็น "ไม่เป็นมิตร" มากกว่า Java ค่อนข้างขาดจุด ยิ่งไปกว่านั้นพวกเขายังขาดจุดสำคัญมากเกี่ยวกับการแบ่งความรับผิดชอบซึ่งเกินกว่าไวยากรณ์และมีจำนวนมากเกี่ยวกับวิธีการออกแบบรหัส

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

Java (และที่คล้ายกัน) แตกต่างกันบ้าง ในขณะที่พวกเขาทำ (เรียงลำดับ) สนับสนุน a finalizeที่สามารถให้ความสามารถในทางทฤษฎีในทำนองเดียวกันการสนับสนุนนั้นอ่อนแอมากจนไม่สามารถใช้งานได้จริง

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

class Foo {
    // ...
public:
    void do_whatever() { if (xyz) throw something; }
    ~Foo() { /* handle cleanup */ }
};

... และรหัสลูกค้ามีลักษณะดังนี้:

void f() { 
    Foo f;
    f.do_whatever();
    // possibly more code that might throw here
}

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

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

แม้ว่าฉันจะเรียกมันว่า "วิธีการของ Java" ด้านบนtry/ finallyและกลไกที่คล้ายกันภายใต้ชื่ออื่น ๆ นั้นไม่ได้ จำกัด อยู่แค่ Java เท่านั้น สำหรับตัวอย่างที่โดดเด่นหนึ่งตัวอย่างส่วนใหญ่ (ทั้งหมด?) ของภาษา. NET (เช่น C #) จะให้ข้อมูลเดียวกัน

การทำซ้ำล่าสุดของทั้ง Java และ C # ยังให้บางสิ่งบางอย่างของจุดกึ่งกลางระหว่าง Java "คลาสสิก" และ C ++ ในเรื่องนี้ ใน C # วัตถุที่ต้องการให้การล้างข้อมูลอัตโนมัติสามารถใช้IDisposableอินเตอร์เฟสซึ่งมีDisposeวิธีการ (อย่างน้อยราง) คล้ายกับ C + + destructor ในขณะที่สิ่งนี้สามารถใช้งานได้ผ่านทางtry/ finallylike ใน Java, C # ทำงานโดยอัตโนมัติอีกเล็กน้อยด้วยusingคำสั่งที่ช่วยให้คุณกำหนดทรัพยากรที่จะถูกสร้างขึ้นเมื่อมีการป้อนขอบเขตและทำลายเมื่อออกจากขอบเขต แม้ว่าจะยังอยู่ในระดับที่ไม่เพียงพอของระบบอัตโนมัติและความแน่นอนที่ได้รับจาก C ++ แต่นี่ก็ยังเป็นการพัฒนาที่เหนือกว่า Java อย่างมาก โดยเฉพาะอย่างยิ่งนักออกแบบชั้นสามารถรวบรวมรายละเอียดของวิธีการIDisposableการกำจัดของชั้นเรียนในการดำเนินงานของ สิ่งที่เหลือสำหรับโปรแกรมเมอร์ลูกค้าคือภาระที่น้อยกว่าในการเขียนusingคำสั่งเพื่อให้มั่นใจว่าIDisposableอินเทอร์เฟซจะถูกใช้เมื่อมันควรจะเป็น ใน Java 7 และใหม่กว่าชื่อถูกเปลี่ยนเพื่อปกป้องผู้กระทำผิด แต่แนวคิดพื้นฐานนั้นเหมือนกันโดยทั่วไป


1
คำตอบที่สมบูรณ์แบบ destructors มีต้องมีคุณลักษณะใน C ++
โทมัส Eding

13

ไม่สามารถเชื่อได้ว่าไม่มีใครได้ยกเรื่องนี้ (ไม่มีการเล่นสำนวนเจตนา) - คุณไม่จำเป็นต้องมีประโยคจับ !

นี่คือเหตุผลที่สมบูรณ์แบบ:

try 
{
   AcquireManyResources(); 
   DoSomethingThatMightFail(); 
}
finally 
{
   CleanUpThoseResources(); 
}

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

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


9

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

ในที่สุดบล็อกคือการช่วยเหลือและให้แน่ใจว่าไม่ว่าข้อยกเว้นการล้างจะเกิดขึ้น


4
นั่นไม่ใช่เหตุผลที่เพียงพอสำหรับ a finallyเนื่องจากคุณสามารถป้องกันข้อยกเว้น "ไม่คาดคิด" ด้วยcatch(Object)หรือcatch(...)catch-alls
MSalters

1
ซึ่งฟังดูเหมือนเป็นเรื่องหลีกเลี่ยง ในที่สุดแนวคิดก็สะอาดกว่า แม้ว่าฉันจะต้องยอมรับว่าไม่ค่อยจะใช้มัน
quick_now

7

บางภาษามีทั้งตัวสร้างและตัวทำลายสำหรับวัตถุ (เช่น C ++ ฉันเชื่อ) ด้วยภาษาเหล่านี้คุณสามารถทำมากที่สุด (arguably ทั้งหมด) ของสิ่งที่มักจะทำในfinallydestructor เช่นนี้ - ในภาษาเหล่านั้น - finallyประโยคอาจไม่จำเป็น

ในภาษาที่ไม่มีตัวทำลาย (เช่น Java) มันเป็นเรื่องยาก (อาจเป็นไปไม่ได้) ในการทำความสะอาดที่ถูกต้องโดยไม่มีfinallyข้อ NB - ใน Java มีfinaliseวิธีการ แต่ไม่มีการรับประกันว่ามันจะถูกเรียกว่า


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

@ Morwenn - จุดดี ฉันพูดเป็นนัยด้วยการอ้างอิงของฉันไปยัง Java finaliseแต่ฉันไม่ต้องการที่จะได้รับการขัดแย้งทางการเมืองรอบ destructors / finalies ในเวลานี้
OldCurmudgeon

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

@RobK - และนี่คือฟังก์ชั่นที่แน่นอนของ a finaliseแต่มีทั้งรสชาติที่เพิ่มขึ้นและกลไกที่คล้ายกัน oop - แสดงออกอย่างมากและเปรียบได้กับfinaliseกลไกของภาษาอื่น ๆ
OldCurmudgeon

1

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

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


3
ใน. NET พวกเขาจะดำเนินการโดยใช้กลไกแยกต่างหาก ใน Java อย่างไรก็ตามสิ่งก่อสร้างเพียงอย่างเดียวที่ได้รับการยอมรับโดย JVM นั้นมีความหมายเทียบเท่ากับ "on error goto" ซึ่งเป็นรูปแบบที่รองรับโดยตรงtry catchแต่ไม่ใช่try finally; รหัสที่ใช้หลังถูกแปลงเป็นรหัสโดยใช้แบบเก่าเท่านั้นโดยคัดลอกเนื้อหาของfinallyบล็อกในทุกจุดของรหัสที่อาจจำเป็นต้องดำเนินการ
supercat

@supercat ดีขอบคุณสำหรับข้อมูลเพิ่มเติมเกี่ยวกับ Java
ปีเตอร์ B

1

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

เหตุผลที่คุณอาจใช้บล็อกในที่สุดแทนที่จะเป็นรหัสหลังจากบล็อก try-catch

  • คุณกลับมาก่อนจากบล็อกลอง: พิจารณาสิ่งนี้

    Database db = null;
    try {
     db = open_database();
     if(db.isSomething()) {
       return 7;
     }
     return db.someThingElse();
    } finally {
      if(db!=null)
        db.close();
    }
    

    เมื่อเทียบกับ:

    Database db = null;
    int returnValue = 0;
    try {
     db = open_database();
     if(db.isSomething()) {
       returnValue = 7;
     } else {
       returnValue = db.someThingElse();
     }
    } catch(Exception e) {
      if(db!=null)
        db.close();
    }
    return returnValue;
    
  • คุณกลับก่อนจากบล็อก catch: เปรียบเทียบ

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      return 7;
    } catch (DBIsADonkeyException e ) {
      return 11;
    } finally {
      if(db!=null)
        db.close();
    }
    

    VS:

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      if(db!=null) 
        db.close();
      return 7;
    } catch (DBIsADonkeyException e ) {
      if(db!=null)
        db.close();
      return 11;
    }           
    db.close();
    
  • คุณสร้างข้อยกเว้นขึ้นใหม่ เปรียบเทียบ:

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      throw convertToRuntimeException(e,"DB was wonkey");
    } finally {
      if(db!=null)
        db.close();
    }
    

    VS:

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      if(db!=null)
        db.close();
      throw convertToRuntimeException(e,"DB was wonkey");
    } 
    if(db!=null)
      db.close();
    

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

ตอนนี้ใน C ++ สิ่งเหล่านี้สามารถจัดการกับวัตถุที่มีขอบเขต แต่ IMO มีข้อเสียสองประการสำหรับวิธีนี้ 1. ไวยากรณ์ไม่เป็นมิตร 2. ลำดับการก่อสร้างเป็นสิ่งที่ตรงกันข้ามกับการทำลายล้างสามารถทำให้สิ่งต่าง ๆ ชัดเจนน้อยลง

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


1

ทั้งหมดที่เป็นตรรกะ "จำเป็น" ในภาษาการเขียนโปรแกรมเป็นคำแนะนำ:

assignment a = b
subtract a from b
goto label
test a = 0
if true goto label

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

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


1
คำตอบของคุณเป็นเรื่องจริง แต่ฉันไม่ได้รหัสในการชุมนุม มันเจ็บปวดเกินไป ฉันถามว่าเหตุใดจึงใช้ฟีเจอร์ที่ฉันไม่เห็นจุดที่เป็นภาษาที่สนับสนุนมันไม่ใช่ชุดคำสั่งภาษาขั้นต่ำที่เปลือยเปล่า
Agi Hammerthief

1
ประเด็นก็คือการใช้ภาษาใด ๆ เพียงแค่ 5 การดำเนินการนี้สามารถใช้อัลกอริทึมใดก็ได้ - แม้ว่าค่อนข้างคดเคี้ยว รุ่น / ผู้ประกอบการส่วนใหญ่ในภาษาระดับสูงไม่จำเป็นต้อง "" ถ้าเป้าหมายเพียงเพื่อใช้อัลกอริทึม หากเป้าหมายคือการมีการพัฒนาอย่างรวดเร็วของโค้ดที่สามารถบำรุงรักษาได้ที่อ่านได้แล้วส่วนใหญ่จำเป็น แต่ "สามารถอ่านได้" และ "บำรุงรักษา" ไม่สามารถวัดได้และเป็นอัตนัยอย่างยิ่ง นักพัฒนาภาษาที่ดีมีฟีเจอร์มากมาย: หากคุณไม่ได้ใช้งานบางอย่างแล้วอย่าใช้มัน
James Anderson

0

ที่จริงแล้วช่องว่างที่ใหญ่กว่าสำหรับฉันมักจะเป็นภาษาที่รองรับfinallyแต่ไม่มี destructors เนื่องจากคุณสามารถจำลองตรรกะทั้งหมดที่เกี่ยวข้องกับ "การล้างข้อมูล" (ซึ่งฉันจะแยกออกเป็นสองหมวดหมู่) ผ่านทาง destructors ในระดับกลางโดยไม่ต้องจัดการกับการล้างข้อมูลด้วยตนเอง ตรรกะในทุกฟังก์ชั่นที่เกี่ยวข้อง เมื่อฉันเห็นรหัส C # หรือ Java ทำสิ่งต่าง ๆ เช่นการปลดล็อก mutexes และปิดแฟ้มด้วยตนเองในfinallyบล็อกซึ่งรู้สึกล้าสมัยแล้วก็เหมือนกับ kinda รหัส C เมื่อสิ่งทั้งหมดนั้นเป็นแบบอัตโนมัติใน C ++ ผ่านทาง destructors ในรูปแบบที่ปลดปล่อยมนุษย์ในความรับผิดชอบนั้น

อย่างไรก็ตามฉันยังคงพบความสะดวกสบายเล็กน้อยหากรวม C ++ finallyและเป็นเพราะมีการทำความสะอาดสองประเภท:

  1. การทำลาย / การปลดปล่อย / ปลดล็อค / ปิด / ทรัพยากรอื่น ๆ ในท้องถิ่น (destructors นั้นสมบูรณ์แบบสำหรับสิ่งนี้)
  2. การเลิก / ย้อนกลับผลข้างเคียงภายนอก (ตัวทำลายมีความเพียงพอสำหรับสิ่งนี้)

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

อย่างไรก็ตามกลไกที่ตรงไปตรงมามากขึ้นจะเป็นrollbackบล็อกที่ฉันไม่เคยเห็นในภาษาใด ๆ มาก่อน มันเป็นความฝันที่ไพเราะของฉันถ้าฉันเคยออกแบบภาษาที่เกี่ยวข้องกับการจัดการข้อยกเว้น มันจะคล้ายกับสิ่งนี้:

try
{
    // Cause external side effects. These side effects should
    // be undone if we don't finish successfully.
}
rollback
{
    // Reverse external side effects. This block is *only* executed 
    // if the 'try' block above faced a premature return out 
    // of the function. It is different from 'finally' which 
    // gets executed regardless of whether or not the function 
    // exited prematurely. This block *only* gets executed if we 
    // exited prematurely from  the try block so that we can undo 
    // whatever side effects it failed to finish making. If the try 
    // block succeeded and didn't face a premature exit, then we 
    // don't want this block to execute.
}

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

นอกจากนี้ยังมีบางกรณีที่คลุมเครือที่คุณเพียงต้องการทำสิ่งต่าง ๆ ไม่ว่าจะออกจากฟังก์ชั่นไม่ว่าจะออกจากระบบอย่างไรเช่นอาจบันทึกการประทับเวลา มีfinallyวิธีการแก้ปัญหาที่ตรงไปตรงมาและสมบูรณ์แบบที่สุดสำหรับงานเนื่องจากพยายามยกตัวอย่างวัตถุเพียงเพื่อใช้ destructor เพื่อจุดประสงค์เดียวในการบันทึก timestamp เพียงแค่รู้สึกแปลก ๆ จริง ๆ (แม้ว่าคุณจะทำได้ดีและค่อนข้างสะดวกกับ lambdas )


-9

เช่นเดียวกับสิ่งผิดปกติอื่น ๆ มากมายเกี่ยวกับภาษา C ++ การขาดtry/finallyโครงสร้างคือข้อบกพร่องในการออกแบบหากคุณสามารถเรียกมันได้ว่าในภาษาที่ดูเหมือนว่าบ่อยครั้งจะไม่มีงานออกแบบจริงใด ๆเลย

RAII (การใช้การร้องขอ destructic destructic ที่อิงตามขอบเขตบนวัตถุสแต็กเพื่อทำความสะอาด) มีข้อบกพร่องสองประการ ประการแรกคือมันต้องใช้วัตถุที่มีกองซ้อนซึ่งเป็นสิ่งที่น่ารังเกียจที่ละเมิดหลักการทดแทน Liskov มีเหตุผลที่ดีมากมายว่าทำไมไม่มีภาษา OO อื่นมาก่อนหรือตั้งแต่ C ++ ใช้ภาษาเหล่านี้ - ภายใน epsilon; D ไม่ได้นับว่ามันใช้อย่างหนักใน C ++ และไม่มีส่วนแบ่งตลาดอยู่แล้ว - และอธิบายปัญหาที่พวกเขาก่อให้เกิดนั้นอยู่นอกเหนือขอบเขตของคำตอบนี้

ประการที่สองสิ่งที่finallyสามารถทำได้คือการทำลายล้างวัตถุ สิ่งที่ทำกับ RAII ใน C ++ ส่วนใหญ่จะอธิบายในภาษา Delphi ซึ่งไม่มีการรวบรวมขยะด้วยรูปแบบต่อไปนี้:

myObject := MyClass.Create(arguments);
try
   doSomething(myObject);
finally
   myObject.Free();
end;

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

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

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

dataset.DisableControls();
try
   LoadData(dataset);
finally
   dataset.EnableControls();
end;

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

สิ่งนี้จะเกิดขึ้นใน C ++ ได้อย่างไร ดีแรกที่คุณจะต้องรหัสขึ้นทั้งชั้น มันอาจจะถูกเรียกว่าDatasetEnablerหรือสิ่งใดสิ่งหนึ่ง การดำรงอยู่ทั้งหมดของมันจะเป็นตัวช่วย RAII จากนั้นคุณจะต้องทำสิ่งนี้:

dataset.DisableControls();
{
   raiiGuard = DatasetEnabler(dataset);
   LoadData(dataset);
}

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

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


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