ในที่สุดการใช้ประโยคในการทำงานหลังจากกลับมาสไตล์ไม่ดี / เป็นอันตรายหรือไม่?


30

ในฐานะส่วนหนึ่งของการเขียน Iterator ฉันพบว่าตัวเองเขียนโค้ดต่อไปนี้ (การจัดการข้อผิดพลาดในการลอก)

public T next() {
  try {
    return next;
  } finally {
    next = fetcher.fetchNext(next);
  }
}

พบว่าอ่านง่ายกว่าเล็กน้อย

public T next() {
  T tmp = next;
  next = fetcher.fetchNext(next);
  return tmp;
}

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

ถ้ามันไม่ดี: ทำไม สไตล์ประสิทธิภาพข้อผิดพลาด ... ?

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


10
โดยส่วนตัวฉันจะสามารถเข้าใจที่สองมากกว่าครั้งแรก แต่ฉันไม่ค่อยใช้finallyบล็อก
Christian Mann

2
ส่งคืน current = fetcher.fetchNext (ปัจจุบัน); // แล้วเรื่องนี้คุณต้องการการดึงข้อมูลล่วงหน้าจริงหรือ
Scarfridge

1
@scarfridge ตัวอย่างนี้เกี่ยวกับการนำไปใช้งานIteratorซึ่งคุณต้องใช้การดึงข้อมูลล่วงหน้าเพื่อhasNext()ทำงาน ลองด้วยตัวคุณเอง
maaartinus

1
@ maaartinus ฉันรู้แล้ว บางครั้ง hasNext () ก็แค่คืนค่าจริงเช่นลำดับ hailstone และบางครั้งการดึงองค์ประกอบต่อไปนั้นมีราคาแพง ในกรณีหลังคุณควรใช้วิธีดึงองค์ประกอบตามความต้องการเช่นเดียวกับการเริ่มต้นขี้เกียจ
scarfridge

1
@scarfridge ปัญหาเกี่ยวกับการใช้งานทั่วไปIteratorคือคุณต้องดึงค่าในhasNext()('ทำให้การดึงมันมักจะเป็นวิธีเดียวที่จะหาว่ามันมีอยู่จริง) และคืนมันกลับมาnext()เหมือนที่ OP ทำ
maaartinus

คำตอบ:


18

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

สิ่งที่ทำให้มันเดือดลงไปคือรหัสที่เข้าใจได้คืออะไรฉันจะพูดและในกรณีนี้คำตอบคือ "meh" คุณกำลัง "พยายาม" คำสั่งการส่งคืนอย่างง่ายจากนั้นดำเนินการผลข้างเคียงที่ซ่อนอยู่ในบล็อกรหัสซึ่งควรจะเป็นการกู้คืนข้อผิดพลาดที่ไม่ปลอดภัย สิ่งที่คุณกำลังทำคือ 'ฉลาด' และรหัส 'ฉลาด' มักทำให้ผู้ดูแลรักษาสับสน "สิ่งที่ ... โอ้ฉันคิดว่าฉันได้รับ" ดีกว่าสำหรับผู้ที่อ่านโค้ดของคุณเพื่อทำตัวคลุมเครือ "อ๋ออ๊ะฮะเข้าท่าใช่แล้ว ... "

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


1
+1 สำหรับความคิดเห็น "ฉลาด" นั่นเป็นตัวอย่างที่ถูกต้องมาก

2
การดำเนินการ 'ส่งคืนและล่วงหน้า' ของถัดไป () ถูกบังคับให้ OP โดยอินเทอร์เฟซ Java iterator ที่ไม่แน่นอน
วินไคลน์

37

บริสุทธิ์จากมุมมองสไตล์ฉันคิดว่าทั้งสามบรรทัด:

T current = next;
next = fetcher.getNext();
return current;

... ทั้งชัดเจนและสั้นกว่าบล็อก try / สุดท้าย เนื่องจากคุณไม่ได้คาดหวังว่าจะมีข้อยกเว้นใด ๆ เกิดขึ้นการใช้tryบล็อกจะทำให้ผู้คนสับสน


2
ลอง / จับ / ในที่สุดบล็อกอาจเพิ่มค่าใช้จ่ายเพิ่มเติมฉันไม่แน่ใจว่า javac หรือคอมไพเลอร์ JIT จะดึงพวกเขาออกแม้ว่าคุณจะไม่ได้โยนข้อยกเว้น
Martijn Verburg

2
@MartijnVerburg ใช่ javac ยังคงเพิ่มรายการในตารางข้อยกเว้นและทำซ้ำรหัสสุดท้ายแม้ว่าบล็อกลองว่างเปล่า
Daniel Lubarov

@Daniel - ขอบคุณฉันขี้เกียจเกินกว่าจะรัน javap :-)
Martijn Verburg

@Martijn Verburg: AFAIK บล็อก try-catch-finallt นั้นฟรีตราบเท่าที่ไม่มีข้อยกเว้นเกิดขึ้นจริง ที่นี่ javac ไม่ได้พยายามและไม่จำเป็นต้องปรับอะไรให้เหมาะสมเพราะ JIT จะดูแลมัน
maaartinus

@maaartinus - ขอบคุณที่เหมาะสม - ต้องอ่านซอร์สโค้ด OpenJDK อีกครั้ง :-)
Martijn Verburg

6

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

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

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

return preProcess(next);

และรหัสนี้ทำให้เกิดข้อยกเว้นfetchNextจะยังคงทำงานได้ OTOH ถ้าคุณเขียนรหัสเช่น:

T ret = preProcess(next);
next = fetcher.fetchNext(next);
return ret;

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

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


คุณไม่ได้ให้เหตุผลที่ดีที่จะหลีกเลี่ยงการใช้finallyวิธีนี้ใช่ไหม ฉันหมายถึงอย่างที่คุณบอกว่ารหัสท้ายที่สุดมีผลกระทบที่ไม่พึงประสงค์; ยิ่งแย่ไปกว่านั้นคุณอาจไม่ได้คิดถึงข้อยกเว้น
Andres F.

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

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

นอกจากนี้ยังมีปัญหาที่เมื่อทั้งการลองและปิดกั้นการโยนข้อยกเว้นข้อยกเว้นในการลองไม่เคยเห็นใครเลย แต่อาจเป็นสาเหตุของปัญหา
Hans-Peter Störr

3

คำสั่งชื่อเรื่อง: "... ในที่สุดประโยคสำหรับการทำงานหลังจากกลับมา ... " เป็นเท็จ บล็อกสุดท้ายจะเกิดขึ้นก่อนที่ฟังก์ชันจะส่งคืน นั่นคือจุดรวมของความเป็นจริงในที่สุด

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

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


+1 ฉันจะเพิ่มว่าแม้ว่าข้อมูลจำเพาะภาษารับประกันว่าค่าจะถูกเก็บไว้ก่อนที่จะถูกส่งกลับมันอาจผิดพลาดไปตามความคาดหวังของผู้อ่านดังนั้นจึงควรหลีกเลี่ยงเมื่อเป็นไปได้อย่างสมเหตุสมผล แก้จุดบกพร่องหนักเป็นสองเท่าเข้ารหัส ...
jmoreno

0

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

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

เนื่องจากสิ่งเหล่านี้บางคนที่อ่านมันจึงต้องคิดถึงปัญหาเหล่านี้ดังนั้นจึงยากที่จะเข้าใจ

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