กำหนดข้อผิดพลาดออกจากการมีอยู่

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

10.1 ทำไม exception จึงเพิ่มความซับซ้อน

ผมใช้คำว่า exception เพื่อหมายถึงเงื่อนไขที่ไม่ธรรมดาใดๆ ที่เปลี่ยนแปลงการไหลควบคุมปกติในโปรแกรม ภาษาโปรแกรมหลายภาษารวมถึง formal exception mechanism ที่อนุญาตให้ exception ถูก throw โดย lower-level code และ catch โดย enclosing code อย่างไรก็ตาม exception สามารถเกิดขึ้นได้แม้ไม่ใช้ formal exception reporting mechanism เช่น เมื่อ method return special value ที่ระบุว่ามันไม่ได้สำเร็จการทำงานปกติของมัน Form ทั้งหมดนี้ของ exception ช่วยเพิ่มความซับซ้อน

Code บางส่วนอาจพบ exception ได้หลายวิธีต่างๆ:

  • Caller อาจให้ argument หรือข้อมูลการกำหนดค่าที่ไม่ถูกต้อง
  • Method ที่ถูกเรียกใช้อาจไม่สามารถสำเร็จการดำเนินการที่ขอได้ ตัวอย่างเช่น I/O operation อาจล้มเหลว หรือ resource ที่จำเป็นอาจไม่พร้อมใช้งาน
  • ในระบบแบบกระจาย packet เครือข่ายอาจหลุดหรือล่าช้า server อาจไม่ตอบสนองในเวลาที่เหมาะสม หรือ peer อาจสื่อสารในลักษณะที่ไม่คาดคิด
  • Code อาจตรวจพบ bug การไม่สอดคล้องภายใน หรือสถานการณ์ที่มันไม่พร้อมจะจัดการ

ระบบขนาดใหญ่ต้องรับมือกับเงื่อนไขพิเศษจำนวนมาก โดยเฉพาะถ้าระบบเป็นแบบกระจายหรือจำเป็นต้องป้องกันความผิดพลาด การจัดการ exception สามารถบัญชีสำหรับส่วนที่มีนัยสำคัญของ code ทั้งหมดในระบบ

Code สำหรับการจัดการ exception นั้นยากกว่า code สำหรับกรณีปกติโดยเนื้อแท้ Exception ก่อให้เกิดการรบกวนต่อการไหลปกติของ code มักหมายความว่าบางสิ่งไม่ทำงานตามที่คาดไว้ เมื่อ exception เกิดขึ้น programmer สามารถจัดการได้สองวิธี ซึ่งแต่ละวิธีอาจซับซ้อนได้ Approach แรกคือการสู้ต่อและให้เสร็จสิ้นงานที่อยู่ระหว่างดำเนินการแม้จะมี exception ตัวอย่างเช่น ถ้า network packet หลุด มันสามารถส่งซ้ำได้ ถ้า data เสียหาย บางทีมันสามารถกู้คืนจาก redundant copy ได้ Approach ที่สองคือการยกเลิกการดำเนินการที่อยู่ระหว่างดำเนินการและรายงาน exception ขึ้นไป อย่างไรก็ตาม การยกเลิกสามารถซับซ้อนได้เนื่องจาก exception อาจเกิดขึ้นที่จุดที่ system state ไม่สอดคล้องกัน (data structure อาจได้รับการสร้างเพียงบางส่วน) code สำหรับการจัดการ exception ต้องกู้คืน consistency เช่น โดยการคลายการเปลี่ยนแปลงใดๆ ที่ทำก่อน exception เกิดขึ้น

นอกจากนี้ exception handling code ยังสร้างโอกาสสำหรับ exception เพิ่มเติม พิจารณากรณีของการส่งซ้ำ network packet ที่หลุด บางทีอาจ packet ไม่ได้หลุม แต่เพียงแต่ล่าช้า ในกรณีนี้ การส่งซ้ำ packet จะทำให้เกิด duplicate packet มาถึง peer นี่คือ exceptional condition ใหม่ที่ peer ต้องจัดการ หรือ พิจารณากรณีของการกู้คืนข้อมูลที่สูญหายจาก redundant copy: จะเกิดอะไรถ้า redundant copy ก็สูญหายไปด้วย Secondary exception ที่เกิดขึ้นระหว่างการกู้คืนมักจะละเอียดอ่อนและซับซ้อนกว่า primary exception ถ้า exception ถูกจัดการโดยการยกเลิกการดำเนินการที่อยู่ระหว่างดำเนินการ มันต้องถูกรายงานให้ caller ทราบว่าเป็น exception อื่น เพื่อป้องกันการไหลแบบ cascade ที่ไม่มีสิ้นสุดของ exception developer ต้องหาวิธีสุดท้ายในการจัดการ exception โดยไม่นำเสนอ exception เพิ่มเติม

Language support สำหรับ exception มักจะละเอียดถี่ถ้วนและยุ่งยาก ซึ่งทำให้ exception handling code อ่านยากลำบาก ตัวอย่างเช่น พิจารณา code ต่อไปนี้ ซึ่ง read collection ของ tweet จาก file โดยใช้ Java's support สำหรับ object serialization และ deserialization:

try (
      FileInputStream fileStream =
                   new FileInputStream(fileName);
      BufferedInputStream bufferedStream =
                   new BufferedInputStream(fileStream);
      ObjectInputStream objectStream =
                   new ObjectInputStream(bufferedStream);
) {
      for (int i = 0; i < tweetsPerFile; i++) {
            tweets.add((Tweet) objectStream.readObject());
      }
}
catch (FileNotFoundException e) {
      ...
}
catch (ClassNotFoundException e) {
      ...
}
catch (EOFException e) {
      // Not a problem: not all tweet files have full
      // set of tweets.
}
catch (IOException e) {
      ...
}
catch (ClassCastException e) {
      ...
}

แค่ basic try-catch boilerplate บัญชีสำหรับ line ของ code มากกว่า code สำหรับการดำเนินการกรณีปกติ โดยไม่สนใจ code ที่จริง ๆ แล้วจัดการ exception มันยากที่จะเชื่อมโยง exception handling code กับ code สำหรับกรณีปกติ: ตัวอย่างเช่น มันไม่ชัดเจนว่า exception แต่ละรายการถูกสร้างที่ใด Alternative approach คือการแบ่ง code ออกเป็นหลาย ๆ ที่แตกต่างกัน try block ในกรณีที่สุด ๆ จะมี try สำหรับแต่ละ line ของ code ที่สามารถสร้าง exception ได้ นี่จะทำให้ชัดเจนว่า exception เกิดขึ้นที่ไหน แต่ try block เองหักการไหลของ code และทำให้อ่านยากขึ้น นอกจากนี้ exception handling code บางส่วนอาจสิ้นสุดลงในการทำซ้ำในหลายๆ try block

เป็นเรื่องยากที่จะมั่นใจว่า exception handling code ทำงานจริงๆ Exception บางรายการ เช่น I/O error ไม่สามารถสร้างได้ง่าย ๆ ในสภาพแวดล้อมการทดสอบ ดังนั้นจึงเป็นการยากที่จะทดสอบ code ที่จัดการพวกมัน Exception ไม่ค่อยเกิดขึ้นในระบบการทำงาน ดังนั้น exception handling code ไม่ค่อยสำเร็จการทำงาน Bug สามารถไม่ถูกตรวจพบมานานหลายวัน และเมื่อ exception handling code จำเป็นต้องใช้ในที่สุด มีโอกาสดีว่ามันจะไม่ทำงาน (พูดคำกล่าวที่เป็นที่ชื่นชอบของผม: "code ที่ยังไม่ได้สำเร็จการทำงานไม่ทำงาน") Recent study พบว่า มากกว่า 90% ของ catastrophic failure ในแบบกระจาย data-intensive system ถูกเกิดจาก incorrect error handling1 เมื่อ exception handling code ล้มเหลว มันเป็นเรื่องยากที่จะ debug ปัญหา เนื่องจากมันเกิดขึ้นน้อยครั้ง

10.2 Exception มากเกินไป

Programmer บวกเพิ่มปัญหาที่เกี่ยวข้องกับการจัดการ exception โดยการกำหนด exception ที่ไม่จำเป็น นักพัฒนาส่วนใหญ่สอนมาว่าเป็นเรื่องสำคัญที่จะตรวจพบและรายงาน error พวกเขามักตีความสิ่งนี้ว่า "ยิ่งตรวจพบ error มากเท่าไหร่ก็ยิ่งดี" สิ่งนี้นำไปสู่ style แบบป้องกันตัวเกินไปโดยสิ่งใด ๆ ที่ดูน่าสงสัยแม้เพียงเล็กน้อยจะถูกปฏิเสธด้วย exception ซึ่งส่งผลให้ unnecessary exception ไหลบ่าและเพิ่มความซับซ้อนของระบบ

ผมทำความผิดนี้เองในการออกแบบ Tcl scripting language Tcl มี unset command ที่สามารถใช้ได้เพื่อลบ variable ผมกำหนด unset เพื่อให้ throw error ถ้า variable ไม่มี ในเวลานั้น ผมคิดว่า ต้องเป็น bug ถ้าใครพยายามลบ variable ที่ไม่มี ดังนั้น Tcl ควรรายงานมัน อย่างไรก็ตาม use ที่ธรรมดาที่สุดข้อหนึ่งของ unset คือการทำความสะอาด temporary state ที่สร้างโดย previous operation มักจะเป็นเรื่องยากที่จะทำนายว่า state อะไรบ้างที่ถูกสร้าง โดยเฉพาะถ้า operation ยุติ partway through ดังนั้น สิ่งที่ง่ายที่สุดคือการลบ variable ทั้งหมดที่อาจถูกสร้าง การกำหนด unset ทำให้เรื่องนี้ยุ่งยาก: developer จบลงด้วยการห่อหุ้ม call ไป unset ในคำสั่ง catch เพื่อจับและละเว้น error ที่ throw โดย unset ในความพิจารณาของผลต่อมา การกำหนด unset command นั้น ความผิด error ที่สำคัญที่สุดข้อหนึ่งที่ผมทำในการออกแบบ Tcl

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

Exception ที่ throw โดย class นั้นเป็นส่วนหนึ่งของ interface ของมัน class ที่มี exception จำนวนมากมี interface ที่ซับซ้อน และมันจะ shallower กว่า class ที่มี exception น้อยกว่า Exception นั้นเป็น element ของ interface ที่ซับซ้อนโดยเฉพาะ มันสามารถ propagate ขึ้นไปผ่าน stack level หลายระดับก่อนที่จะถูก catch ดังนั้นมันส่งผลกระทบไม่เพียง method's caller เท่านั้น แต่มีศักยภาพว่า higher-level caller (และ interface ของพวกเขา)ด้วย

Throw exception นั้นง่าย การจัดการพวกมันนั้นยาก ดังนั้น ความซับซ้อนของ exception มาจาก exception handling code ที่ดีที่สุดวิธี reduce ความเสียหายจากความซับซ้อนที่เกิดจาก exception handling คือ reduce จำนวนสถานที่ที่มี exception ต้องจัดการ ส่วนที่เหลือของบทนี้จะพูดถึง four technique สำหรับ reduce จำนวนของ exception handler

10.3 กำหนด error ออกจากการมีอยู่

วิธีที่ดีที่สุดในการกำจัด exception handling complexity คือการกำหนด API ของคุณเพื่อไม่มี exception ให้จัดการ: define error ออกจากการมีอยู่ อาจดูเหมือน sacrilegious แต่มันมีประสิทธิภาพมากในทางปฏิบัติ พิจารณา Tcl unset command ที่พูดคุยด้านบน มากกว่า throw error เมื่อ unset ถูกขอให้ลบ unknown variable มันควรส่งคืนโดยไม่มีการทำอะไร ผมควรจะเปลี่ยนการกำหนด unset เล็กน้อย: มากกว่าการลบ variable unset ควรมั่นใจว่า variable ไม่มีการมีอยู่อีกต่อไป ด้วย definition แรก unset ไม่สามารถทำงาน ถ้า variable ไม่มี ดังนั้นการสร้าง exception ทำให้เหมาะสม ด้วย definition ที่สอง มันเป็นเรื่องธรรมชาติสำหรับ unset ที่จะถูกเรียกใช้ด้วยชื่อของ variable ที่ไม่มี ในกรณีนี้ งานของมันเสร็จสิ้นแล้ว ดังนั้นมันสามารถส่งคืนได้ ไม่มี error case ที่จะรายงาน

10.4 ตัวอย่าง: file deletion ใน Windows

File deletion ให้อีกตัวอย่างหนึ่งของวิธี error สามารถกำหนดออกไป Windows operating system ไม่อนุญาตให้ file ถูกลบถ้าเป็นการเปิดใน process ซึ่งนี่คือแหล่งที่มาของความหงุดหงิดอย่างต่อเนื่องสำหรับ developer และ user เพื่อลบ file ที่ใช้ user ต้องค้นหา system เพื่อหา process ที่มี file เปิด จากนั้นจึงฆ่า process นั้น บางครั้ง user ยอมแพ้และ reboot system พวกเขา เพียงแค่ดังนั้นพวกเขาสามารถลบ file ได้

Unix operating system กำหนด file deletion ได้อย่างหรูหรากว่า ใน Unix ถ้า file เปิดอยู่เมื่อมันถูกลบ Unix ไม่ลบ file ทันที แทนที่จะทำเครื่องหมาย file สำหรับการลบ จากนั้นการลบ operation จะส่งคืนได้สำเร็จ File name ถูกลบออกจาก directory ของมัน ดังนั้น process อื่น ๆ ไม่สามารถเปิด file เก่าและ new file ด้วยชื่อเดียวกัน สามารถสร้าง ได้ แต่ existing file data ยังคงอยู่ Process ที่มี file เปิดอยู่แล้วสามารถอ่านและเขียนมันได้ตามปกติ เมื่อ file ถูกปิดโดย all accessing process จาก data ของมันจะถูกปล่อยออกมา

Unix approach กำหนด error สองประเภทต่างกันออกไป ก่อนอื่น delete operation ไม่ส่งคืน error ถ้า file ใช้งานอยู่ currently delete สำเร็จ และ file จะถูกลบในที่สุด ประการที่สอง ลบ file ที่ใช้งานอยู่ไม่สร้าง exception สำหรับ process ที่ใช้ file Approach ที่เป็นไปได้อย่างหนึ่งสำหรับปัญหานี้จะมี delete file ทันที และทำเครื่องหมาย all open ของ file เพื่อปิดใช้งาน ความพยายาม any โดย process อื่น ๆ เพื่อ read หรือ write deleted file จะล้มเหลว อย่างไรก็ตาม approach นี้จะสร้าง new error สำหรับ process เหล่านั้นเพื่อจัดการ แทนที่จะ Unix อนุญาตพวกเขาให้ access file ที่ได้รับการประณามต่อ delay file deletion กำหนด error ออกจากการมีอยู่

อาจดูแปลกว่า Unix อนุญาต process เพื่อ continue เพื่อ read และ write file ที่ได้รับการประณาม แต่ผมไม่เคยพบสถานการณ์ที่สิ่งนี้เกิดปัญหาอย่างมีนัยสำคัญ Unix definition ของ file deletion นั้นง่ายกว่ามากในการทำงาน สำหรับ developer และ user ทั้งหมด กว่า Windows definition

10.5 ตัวอย่าง: Java substring method

ตัวอย่างสุดท้ายของข้อควรพิจารณา Java String class และ substring method ของมัน กำหนด index สองดัชนี string substring return substring เริ่มต้นที่ character ที่กำหนดโดย first index และสิ้นสุดด้วย character ก่อน second index อย่างไรก็ตาม ถ้า index ใด ๆ ออกนอก range ของ string จากนั้น substring throw IndexOutOfBoundsException Exception นี้ไม่จำเป็นและ complicate การใช้ method นี้ ผมมักพบตัวเองในสถานการณ์ที่ one หรือ both ของ index อาจออก range ของ string และผมต้องการ extract all character ใน string ที่ overlap specified range แต่น่าเสียดาย สิ่งนี้ต้องให้ผมเช็ค each ของ index และ round พวกเขา up ถึง zero หรือ down ไป end ของ string one-line method call ตอนนี้กลาย 5–10 line ของ code

Java substring method จะ easier เพื่อ use ถ้ามัน perform adjustment นี้ auto ดังนั้นว่า implement API ดังต่อไปนี้: "return character ของ string (ถ้ามี) ด้วย index มากกว่า equal หรือ beginIndex และ น้อยกว่า endIndex" นี่ simple และ natural API และ define IndexOutOfBoundsException exception ออก existence method behavior ตอนนี้ well-defined แม้ว่า one หรือ both ของ index negative หรือถ้า beginIndex greater ว่า endIndex Approach นี้ simplify API สำหรับ method ในขณะที่ increase functionality ของมัน ดังนั้น make method deeper หลาย language อื่น ได้ take error-free approach ตัวอย่างเช่น Python return empty result สำหรับ out-of-range list slice

เมื่อผมให้เหตุผลสำหรับ define error ออก existence people บางครั้งโต้แย้งว่า throw error จะ catch bug ถ้า error define ออก existence จะ result ในน่า buggier software บางทีสิ่งนี้คือ reason ว่าทำไม Java developer ตัดสินใจว่า substring ควร throw exception error-ful approach อาจ catch error บางตัว แต่มัน increase complexity ซึ่ง result ใน other bug ใน error-ful approach developer ต้องเขียน additional code เพื่อ avoid หรือ ignore error และ increase likelihood ของ bug หรือ พวกเขาอาจลืม write additional code ซึ่ง case unexpected error อาจ throw ที่ runtime ในขณะเดียวกัน define error ออก existence simplify API และ reduce amount ของ code ต้อง write

โดยรวม วิธีดีที่สุด reduce bug นั้น make software simpler

10.6 Mask exception

Second technique สำหรับ reduce จำนวนของ place exception ต้องจัดการนั้น exception masking ด้วย approach นี้ exceptional condition ถูกตรวจพบและจัดการ low level ใน system ดังนั้น high level ของ software ไม่จำเป็นต้อง aware ของ condition Exception masking เป็นเรื่องธรรมดา especially ใน distributed system สำหรับ instance ใน network transport protocol เช่น TCP packet สามารถ drop สำหรับ various reason เช่น corruption และ congestion TCP mask packet loss โดยการ resend packet ที่หลุด ภายใน implementation ของมัน ดังนั้น all data eventually ได้รับการส่ง และ client ไม่รู้เรื่อง drop packet

Example ที่ controversial เพิ่มเติม ของ masking เกิดใน NFS network file system ถ้า NFS file server crash หรือล้มเหลว respond สำหรับ any reason client reissue request ของพวกเขา server over และ over again จนกว่า problem eventually resolve low-level file system code บน client ไม่ report exception ใด ๆ ไป invoking application operation ใน progress (และ hence application) เพียง hang จนกว่า operation สามารถ complete successfully ถ้า hang last longer ว่า short time NFS client print message บน user's console ของ form "NFS server xyzzy not responding still trying"

NFS user มักจะ complain เกี่ยวกับ fact ว่า application ของพวกเขา hang ในขณะ wait สำหรับ NFS server เพื่อ resume normal operation หลายคน suggest ว่า NFS ควร abort operation ด้วย exception มากกว่า hang อย่างไรก็ตาม report exception จะ make thing worse ไม่ดีกว่า there's ไม่มาก application สามารถ do ถ้า lose access ไป file ของมัน Possibility หนึ่ง ต้องมี application เพื่อ retry file operation แต่ นี่จะ still hang application และ it's easier เพื่อ perform retry ใน one place ใน NFS layer มากกว่า at every file system call ใน every application (compiler ไม่ควร worry เกี่ยวกับ สิ่งนี้) alternative อื่น ๆ ต้องมี application เพื่อ abort และ return error ให้ caller ของพวกเขา it's unlikely ว่า caller ต่างๆ ที่รู้ว่าจะทำอะไร ด้วย ดังนั้นพวกเขาจะ abort เช่นกัน ซึ่ง result ใน collapse ของ user's working environment user ยังคง wouldn't สามารถ get work ใด ๆ ทำ ขณะ file server ลง และพวกเขา would ต้อง restart ทั้งหมด application ของพวกเขา once file server back ไป life

ดังนั้น the best alternative ตรวจสอบ NFS เพื่อ mask error และ hang application ด้วย approach นี้ application ไม่จำเป็นต้อง code ใด ๆ เพื่อ deal server problem และพวกเขาสามารถ resume seamlessly once server back ไป life ถ้า user get tired ของ wait พวกเขา always สามารถ abort application manually

Exception masking ไม่ทำงานใน all situation แต่มันเป็น powerful tool ใน situation ที่ทำงาน มัน result ใน deeper class เนื่องจาก reduce class's interface (fewer exception สำหรับ users ที่ aware ของ) และ add functionality ใน form ของ code mask exception Exception masking นั้น example ของ pull complexity downward

10.7 Exception aggregation

Third technique สำหรับ reduce complexity related ไป exception นั้น exception aggregation Idea ด้านหลัง exception aggregation คือการ handle many exception ด้วย single piece ของ code มากกว่า write distinct handler สำหรับ many individual exception handle พวกเขาทั้งหมด ใน one place ด้วย single handler

Consider ว่าจะ handle missing parameter ใน Web server Web server implement collection ของ URL เมื่อ server receive incoming URL มัน dispatch ไป URL-specific service method เพื่อ process URL นั้น และ generate response URL contains various parameter ที่ use เพื่อ generate response each service method จะ call lower-level method (let's call มัน getParameter) เพื่อ extract parameter ที่มันต้องการจาก URL ถ้า URL ไม่ contain desire parameter getParameter throw exception

เมื่อ student ใน software design class implement เช่น server หลายคนของพวกเขา wrap each distinct call เพื่อ getParameter ใน separate exception handler เพื่อ catch NoSuchParameter exception อย่างเช่น Figure 10.1 นี้ result ใน large number ของ handler ทั้งหมด ของ ทำการทำ essentially same thing (generate error response)

img

Figure 10.1: code ที่ top dispatch ไป one ของ several method ใน Web server each ของ ที่ handle particular URL each ของ those method (bottom) use parameter จาก incoming HTTP request ใน figure นี้ there's separate exception handler สำหรับ each call เพื่อ getParameter นี่ result ใน duplicate code

Better approach คือการ aggregate exception Instead ของ catch exception ใน individual service method let พวกเขา propagate up ไป top-level dispatch method สำหรับ Web server อย่าง Figure 10.2 single handler ใน method นี้ สามารถ catch all exception และ generate appropriate error response สำหรับ missing parameter

img

Figure 10.2: code นี้เป็น functionally equivalent ไป Figure 10.1 แต่ exception handling ได้รับการ aggregated single exception handler ใน dispatcher catch ทั้งหมด NoSuchParameter exception จาก all ของ URL-specific method

aggregation ที่อธิบายไว้ใน preceding paragraph มี good property จาก standpoint ของ encapsulation และ information hiding top-level exception handler encapsulate knowledge เกี่ยวกับ how เพื่อ generate error response แต่ know nothing เกี่ยวกับ specific error มันเพียง use error message provide ใน exception getParameter method encapsulate knowledge เกี่ยวกับ how เพื่อ extract parameter จาก URL และมัน also know ว่าจะ describe extraction error ใน human-readable form piece นี้ของ information closely related ดังนั้นมันทำให้เหมาะสม สำหรับพวกเขาที่อยู่ใน same place อย่างไรก็ตาม getParameter know nothing เกี่ยวกับ syntax ของ HTTP error response นอก functionality ได้รับการ add ไป Web server new method เช่น getParameter อาจ create ด้วย own error ถ้า new method throw exception ใน same way เช่น getParameter (โดยการ generate exception ที่ inherit จาก same superclass และ include error message ใน each exception) พวกเขาสามารถ plug ไป existing system ด้วย no other change top-level handler จะ auto generate error response สำหรับพวกเขา

Example นี้ illustrate generally-useful design pattern สำหรับ exception handling ถ้า system process series ของ request มันเป็นประโยชน์เพื่อ define exception ว่า abort current request clean system's state และ continue ด้วย next request exception ถูก catch ใน single place near top ของ system's request-handling loop exception นี้สามารถ throw ที่ any point ใน process ของ request เพื่อ abort request different subclass ของ exception สามารถ define สำหรับ different condition exception ของ type นี้ควร clearly distinguish จาก exception ว่า fatal ไป entire system

Exception aggregation work best ถ้า exception propagate several level ขึ้น stack ก่อน handle นี่ allow more exception จาก more method เพื่อ handle ใน same place นี่ opposite ของ exception masking masking usually work best ถ้า exception handle ใน low-level method สำหรับ masking low-level method typically library method ใช้โดย many other method ดังนั้น allow exception เพื่อ propagate จะ increase จำนวน ของ place ว่า handle masking และ aggregation similar ใน both approach position exception handler ที่ catch most exception eliminate many handler ว่า อื่น ๆ ที่ต้องการ create

Another example ของ exception aggregation เกิดใน RAMCloud storage system สำหรับ crash recovery RAMCloud system consist ของ collection ของ storage server ที่ keep multiple copy ของ each object ดังนั้น system สามารถ recover จาก variety ของ failure ตัวอย่างเช่น ถ้า server crash และ lose all ของ data ของมัน RAMCloud reconstruct ลด data ใช้ copy store บน other server Error สามารถ happen บน smaller scale เช่น server อาจ discover ว่า individual object corrupt

RAMCloud ไม่มี separate recovery mechanism สำหรับ each different kind ของ error แทน RAMCloud "promote" many smaller error ไป larger one RAMCloud สามารถ ใน principle handle corrupted object โดย restore ว่า object เดียว จาก backup copy อย่างไรก็ตาม not do นี้ แทน ถ้า discover corrupted object มัน crash server contain object RAMCloud use approach นี้ because crash recovery quite complex และ approach นี้ minimize จำนวน ของ different recovery mechanism ว่า ต้อง create create mechanism สำหรับ crash server ไม่อาจหลีกเลี่ยง ดังนั้น RAMCloud use same mechanism สำหรับ other kind ของ recovery เช่นกัน นี้ reduce amount ของ code ต้อง write และ also meant ว่า server crash recovery get invoke more often ผลเนื่องจาก bug ใน recovery มี likely เพื่อ discover และ fix

One disadvantage ของ promote corrupted object ไป server crash นั่นคือ increase cost ของ recovery considerably นี้ not problem ใน RAMCloud เนื่องจาก object corruption quite rare อย่างไรก็ตาม error promotion อาจ make sense สำหรับ error ว่า happen frequently ตัวอย่างเช่น would not practical เพื่อ crash server anytime one ของ network packet ของมัน lost

One way ของ think เกี่ยวกับ exception aggregation นั่นคือว่า replace several special-purpose mechanism each tailor สำหรับ particular situation ด้วย single general-purpose mechanism ที่สามารถ handle multiple situation นี่ provide another illustration ของ benefit ของ general-purpose mechanism

10.8 เพียงแค่ crash?

Fourth technique สำหรับ reduce complexity related ไป exception handling นั่นคือการ crash application ใน most application there จะ certain error ว่า it's ไม่มูลค่า try เพื่อ handle typically error นี้ difficult หรือ impossible เพื่อ handle และ don't occur very often simplest thing เพื่อ do ใน response ไป error นี้ คือ print diagnostic information และ then abort application

One example นั่นคือ "out ของ memory" error ว่า occur during storage allocation consider malloc function ใน C ว่า return NULL ถ้า not สามารถ allocate desire block ของ memory นี่ unfortunate behavior เนื่องจาก assume ว่า every single caller ของ malloc จะ check return value และ take appropriate action ถ้า there's no memory application contain numerous call ไป malloc ดังนั้น check result หลัง each call จะ add significant complexity ถ้า programmer forget check (ซึ่ง fairly likely) then application จะ dereference null pointer ถ้า memory run out result ใน crash ว่า camouflage real problem

เพิ่มเติม there's not much application สามารถ do เมื่อ discover ว่า memory exhausted ใน principle application สามารถ look สำหรับ unneeded memory เพื่อ free แต่ถ้า application มี unneeded memory มัน could already freed มัน ซึ่ง จะ have prevent out-of-memory error ใน first place today's system มี so much memory ว่า memory almost never run out ถ้า do usually indicate bug ใน application ดังนั้น rarely make sense เพื่อ try เพื่อ handle out-of-memory error นี้ create too much complexity สำหรับ too little benefit

Better approach คือการ define new method ckalloc ว่า call malloc check result และ abort application ด้วย error message ถ้า memory exhausted application never invoke malloc directly มันเสมอ invoke ckalloc

ใน newer language เช่น C++ และ Java new operator throw exception ถ้า memory exhausted there's ไม่ much point ใน catch exception นี้ เนื่องจาก มี good chance ว่า exception handler จะ also try เพื่อ allocate memory ซึ่ง จะ also fail dynamically allocate memory นั่นคือ such fundamental element ของ any modern application ว่า not make sense สำหรับ application เพื่อ continue ถ้า memory exhausted better เพื่อ crash as soon as error detect

there's many other example ของ error ที่ crash application make sense สำหรับ most program ถ้า I/O error occur ในขณะ read หรือ write open file (เช่น disk hard error) หรือถ้า network socket not สามารถ open there's ไม่ much application สามารถ do เพื่อ recover ดังนั้น aborting ด้วย clear error message นั่นคือ sensible approach error นี้ infrequent ดังนั้นพวกเขา unlikely เพื่อ affect overall usability ของ application aborting ด้วย error message นั่นคือ also appropriate ถ้า application encounter internal error เช่น inconsistent data structure condition เช่นนี้ probably indicate bug ใน program

whether หรือ not นั่นคือ acceptable เพื่อ crash บน particular error depend บน application สำหรับ replicated storage system not appropriate เพื่อ abort บน I/O error แทน system ต้อง use replicate data เพื่อ recover any information ว่า lost recovery mechanism จะ add considerable complexity ไป program แต่ recover lost data นั่นคือ essential part ของ value system provide ไป user ของมัน

10.9 design special case ออก existence

สำหรับ same reason ว่า make sense เพื่อ define error ออก existence มันเสมอ make sense เพื่อ define other special case ออก existence special case สามารถ result ใน code ว่า riddled ด้วย if statement ซึ่ง make code hard เพื่อ understand และ lead ไป bug ดังนั้น special case ควร eliminate wherever possible best way เพื่อ do นี้ by design normal case ใน way ว่า auto handle special case โดยไม่มี extra code

ใน text editor project describe ใน บท 6 student ได้รับ implement mechanism สำหรับ select text และ copy หรือ delete selection most student introduce state variable ใน select implementation เพื่อ indicate whether หรือ not selection exist พวกเขา probably choose approach นี้ เนื่องจาก there's time เมื่อ no selection visible บน screen ดังนั้นมันดูเหมือน natural เพื่อ represent notion นี้ใน implementation อย่างไรก็ตาม approach นี้ result ใน numerous check เพื่อ detect "no selection" condition และ handle มัน specially

selection handling code สามารถ simplify โดย eliminate "no selection" special case ดังนั้น selection เสมอ exist เมื่อ there's no selection visible บน screen มัน represent internally ด้วย empty selection ซึ่ง start และ end position เป็น same ด้วย approach นี้ selection management code สามารถ write โดยไม่มี any check สำหรับ "no selection" เมื่อ copy selection ถ้า selection empty then 0 byte จะ insert ที่ new location (ถ้า implement correctly there's จะ no need เพื่อ check สำหรับ 0 byte เป็น special case) similarly มันควร possible เพื่อ design code สำหรับ delete selection ดังนั้น empty case handle โดยไม่มี any special-case check พิจารณา selection ทั้งหมด บน single line เพื่อ delete selection extract portion ของ line before selection และ concatenate มัน ด้วย portion ของ line follow selection เพื่อ form new line ถ้า selection empty approach นี้ จะ regenerate original line

Example นี้ also illustrate "different layer different abstraction" idea จาก บท 7 notion ของ "no selection" make sense ใน term ของ how user think เกี่ยวกับ application's interface แต่ว่า not mean มันต้องถูก represent explicitly ภายในใน application have selection ว่า เสมอ exist แต่ sometimes empty และ ดังนั้น invisible result ใน simpler implementation

10.10 Take มันไกลเกินไป

define away exception หรือ mask พวกเขา ภายใน module only make sense ถ้า exception information not need outside module นี้ true สำหรับ example ใน chapter นี้ เช่น Tcl unset command และ Java substring method ใน rare situation ที่ caller care เกี่ยวกับ special case detect โดย exception there's other way สำหรับ มัน เพื่อ get information นี้

อย่างไรก็ตาม it's possible เพื่อ take idea นี้ far too ใน module สำหรับ network communication student team mask all network exception ถ้า network error occur module catch มัน discard มัน และ continue ว่า there's ไม่มี problem นี้ meant ว่า application use module มี no way เพื่อ find out ถ้า message lost หรือ peer server fail โดยไม่มี information นี้ it's impossible เพื่อ build robust application ใน case นี้ essential สำหรับ module เพื่อ expose exception แม้ว่า add complexity ไป module's interface

ด้วย exception เช่น ด้วย many other area ใน software design you must determine ว่า important และ ว่า not important thing ว่า not important ควร hide และ more ของ พวกเขา better แต่ when something important มันต้อง expose

10.11 Conclusion

special case ของ any form make code harder เพื่อ understand และ increase likelihood ของ bug chapter นี้ focus บน exception ซึ่ง one ของ most significant source ของ special-case code และ discuss ว่าจะ reduce จำนวน ของ place ว่า exception ต้องจัดการ best way เพื่อ do นี้ by redefine semantic เพื่อ eliminate error condition สำหรับ exception ว่า can't define away you ควร look สำหรับ opportunity เพื่อ mask พวกเขา at low level ดังนั้น impact limited หรือ aggregate several special-case handler ไป single more generic handler together technique นี้ สามารถ have significant impact บน overall system complexity

1Ding Yuan et. al., "Simple Testing Can Prevent Most Critical Failures: An Analysis of Production Failures in Distributed Data-Intensive Systems," 2014 USENIX Conference on Operating System Design and Implementation.