ฉันคิดว่าฉันอ่านบทสัมภาษณ์บรูซเอคเคลแบบเดียวกับที่คุณทำและมันก็บั่นทอนฉันอยู่เสมอ ในความเป็นจริงการโต้แย้งเกิดขึ้นโดยผู้ให้สัมภาษณ์ (ถ้านี่เป็นเรื่องจริงที่คุณพูดถึง) Anders Hejlsberg อัจฉริยะ MS ด้านหลัง. NET และ C #
http://www.artima.com/intv/handcuffs.html
Fan แม้ว่าฉันจะเป็น Hejlsberg และผลงานของเขาการโต้เถียงนี้ทำให้ฉันเป็นคนหลอกลวง โดยทั่วไปแล้วจะเดือดลงไปที่:
"การตรวจสอบข้อยกเว้นนั้นไม่ดีนักเพราะโปรแกรมเมอร์มักทำผิดกฎพวกเขาโดยการจับพวกเขาและไล่ออกสิ่งเหล่านี้ซึ่งนำไปสู่ปัญหาที่ถูกซ่อนอยู่และถูกเพิกเฉย
โดย"นำเสนอเป็นอย่างอื่นแก่ผู้ใช้"ฉันหมายความว่าถ้าคุณใช้ข้อยกเว้นรันไทม์โปรแกรมเมอร์แบบเกียจคร้านก็จะไม่สนใจมัน (เมื่อเทียบกับการจับบล็อกด้วยบล็อก catch ว่างเปล่า) และผู้ใช้จะเห็นมัน
สรุปบทสรุปของการโต้แย้งคือการที่"โปรแกรมเมอร์จะไม่ใช้พวกเขาอย่างถูกต้องและไม่ได้ใช้อย่างถูกต้องจะเลวร้ายยิ่งกว่าไม่ได้มีพวกเขา"
มีความจริงบางประการเกี่ยวกับเรื่องนี้และในความเป็นจริงฉันสงสัยว่าแรงจูงใจ Goslings ที่ไม่ให้แทนที่ตัวดำเนินการใน Java มาจากอาร์กิวเมนต์ที่คล้ายกัน - พวกเขาสับสนโปรแกรมเมอร์เพราะพวกเขามักจะถูกทำร้าย
แต่ในท้ายที่สุดฉันพบว่ามันเป็นการโต้เถียงที่ผิด ๆ ของ Hejlsberg และอาจเป็นโพสต์เฉพาะกิจที่สร้างขึ้นเพื่ออธิบายการขาดมากกว่าการตัดสินใจที่ดี
ฉันจะยืนยันว่าในขณะที่การใช้งานเกินของข้อยกเว้นตรวจสอบเป็นสิ่งที่ไม่ดีและมีแนวโน้มที่จะนำไปสู่การจัดการเลอะเทอะโดยผู้ใช้ แต่การใช้งานที่เหมาะสมของพวกเขาช่วยให้โปรแกรมเมอร์ API ให้ประโยชน์ที่ดีแก่โปรแกรมเมอร์ลูกค้า API
ตอนนี้โปรแกรมเมอร์ API ต้องระวังอย่าโยนข้อยกเว้นที่ถูกตรวจสอบไปทั่วสถานที่หรือพวกเขาจะรบกวนโปรแกรมเมอร์ของไคลเอ็นต์ โปรแกรมเมอร์ไคลเอนต์ที่ขี้เกียจจะหันมาจับ(Exception) {}
เหมือน Hejlsberg เตือนและผลประโยชน์ทั้งหมดจะหายไปและนรกจะตามมา แต่ในบางกรณีไม่มีข้อยกเว้นสำหรับการตรวจสอบที่ดี
สำหรับฉันตัวอย่างคลาสสิกคือ API แบบเปิดไฟล์ ทุกภาษาโปรแกรมในประวัติศาสตร์ของภาษา (ในระบบไฟล์อย่างน้อย) มี API ที่ช่วยให้คุณเปิดไฟล์ และโปรแกรมเมอร์ลูกค้าทุกคนที่ใช้ API นี้รู้ว่าพวกเขาต้องจัดการกับกรณีที่ไฟล์ที่พวกเขาพยายามเปิดไม่มีอยู่ ให้ฉันใช้ถ้อยคำใหม่ที่: โปรแกรมเมอร์ลูกค้าทุกคนที่ใช้ API นี้ควรรู้ว่าพวกเขาต้องจัดการกับกรณีนี้ และมี rub: โปรแกรมเมอร์ API สามารถช่วยให้พวกเขารู้ว่าพวกเขาควรจัดการกับมันผ่านการแสดงความคิดเห็นเพียงอย่างเดียวหรือพวกเขาสามารถยืนยันลูกค้าจัดการกับมันได้
ใน C สำนวนเป็นไปอย่างนั้น
if (f = fopen("goodluckfindingthisfile")) { ... }
else { // file not found ...
โดยที่fopen
บ่งบอกถึงความล้มเหลวโดยการคืนค่า 0 และ C (โง่เขลา) ให้คุณปฏิบัติกับ 0 เป็นบูลีนและ ... โดยทั่วไปคุณเรียนรู้สำนวนนี้แล้วและคุณก็โอเค แต่ถ้าคุณเป็น noob และคุณไม่ได้เรียนรู้สำนวน จากนั้นแน่นอนคุณเริ่มต้นด้วย
f = fopen("goodluckfindingthisfile");
f.read(); // BANG!
และเรียนรู้วิธีที่ยากลำบาก
โปรดทราบว่าเรากำลังพูดถึงภาษาที่พิมพ์อย่างยิ่งเท่านั้นที่นี่: มีความคิดที่ชัดเจนว่า API นั้นเป็นภาษาที่มีการพิมพ์อย่างรุนแรง: มันเป็นฟังก์ชั่น Smorgasbord (วิธีการ) เพื่อให้คุณใช้กับโปรโตคอลที่กำหนดไว้อย่างชัดเจนสำหรับแต่ละคน
โปรโตคอลที่กำหนดไว้อย่างชัดเจนนั้นโดยทั่วไปแล้วจะถูกกำหนดโดยลายเซ็นวิธีการ ที่นี่ fopen ต้องการให้คุณส่งสตริง (หรืออักขระ * ในกรณีของ C) หากคุณให้อย่างอื่นคุณจะได้รับข้อผิดพลาดในการคอมไพล์ คุณไม่ได้ปฏิบัติตามโปรโตคอล - คุณใช้ API ไม่ถูกต้อง
ในบางภาษา (คลุมเครือ) ชนิดส่งคืนจะเป็นส่วนหนึ่งของโปรโตคอลด้วยเช่นกัน หากคุณพยายามโทรเทียบเท่าfopen()
ในบางภาษาโดยไม่กำหนดให้ตัวแปรคุณจะได้รับข้อผิดพลาดในการคอมไพล์เวลา (คุณสามารถทำได้ด้วยฟังก์ชั่นโมฆะ)
จุดที่ฉันพยายามทำคือ: ในภาษาที่พิมพ์แบบคงที่โปรแกรมเมอร์ API สนับสนุนให้ลูกค้าใช้ API อย่างถูกต้องโดยป้องกันรหัสลูกค้าของพวกเขาจากการรวบรวมถ้ามันทำผิดพลาดใด ๆ ที่เห็นได้ชัด
(ในภาษาที่พิมพ์แบบไดนามิกเช่น Ruby คุณสามารถส่งสิ่งใดพูดลอยเป็นชื่อไฟล์ - และมันจะรวบรวมทำไมรบกวนผู้ใช้ด้วยข้อยกเว้นการตรวจสอบถ้าคุณไม่ได้จะควบคุมข้อโต้แย้งวิธี อาร์กิวเมนต์ที่สร้างที่นี่ใช้กับภาษาที่พิมพ์แบบคงที่เท่านั้น)
ดังนั้นข้อยกเว้นที่ตรวจสอบแล้วมีอะไรบ้าง
นี่คือหนึ่งใน API ของ Java ที่คุณสามารถใช้เปิดไฟล์ได้
try {
f = new FileInputStream("goodluckfindingthisfile");
}
catch (FileNotFoundException e) {
// deal with it. No really, deal with it!
... // this is me dealing with it
}
เห็นไหม นี่คือลายเซ็นต์สำหรับวิธี API นั้น:
public FileInputStream(String name)
throws FileNotFoundException
โปรดทราบว่าFileNotFoundException
เป็นข้อยกเว้นที่ตรวจสอบ
โปรแกรมเมอร์ API กำลังบอกสิ่งนี้กับคุณ: "คุณสามารถใช้ตัวสร้างนี้เพื่อสร้าง FileInputStream ใหม่ แต่คุณ
a) ต้องส่งชื่อไฟล์เป็นสตริง
b) ต้องยอมรับความเป็นไปได้ที่ไฟล์อาจไม่พบในขณะใช้งาน "
และนั่นคือประเด็นทั้งหมดที่ฉันกังวล
ที่สำคัญคือสิ่งที่คำถามระบุว่า "สิ่งที่อยู่นอกเหนือการควบคุมของโปรแกรมเมอร์" ความคิดแรกของฉันคือเขา / เธอหมายถึงสิ่งที่อยู่นอกเหนือการควบคุมโปรแกรมเมอร์API แต่ในความเป็นจริงการตรวจสอบข้อยกเว้นเมื่อใช้อย่างถูกต้องควรเป็นสิ่งที่อยู่นอกการควบคุมของโปรแกรมเมอร์ของไคลเอ็นต์และการเขียนโปรแกรมของ API ฉันคิดว่านี่เป็นกุญแจสำคัญที่จะไม่ละเมิดข้อยกเว้นที่ตรวจสอบ
ฉันคิดว่าไฟล์ที่เปิดอยู่แสดงให้เห็นถึงจุดอย่างดี โปรแกรมเมอร์ API รู้ว่าคุณอาจให้ชื่อไฟล์แก่พวกเขาว่าไม่มีอยู่จริงในเวลาที่มีการเรียกใช้ API และพวกเขาจะไม่สามารถส่งคืนสิ่งที่คุณต้องการ แต่จะต้องมีข้อยกเว้น พวกเขายังรู้ว่าสิ่งนี้จะเกิดขึ้นอย่างสม่ำเสมอและโปรแกรมเมอร์ลูกค้าอาจคาดหวังว่าชื่อไฟล์จะถูกต้องในเวลาที่พวกเขาเขียนการโทร แต่มันอาจจะผิดที่รันไทม์ด้วยเหตุผลเกินกว่าการควบคุมของพวกเขา
ดังนั้น API ทำให้ชัดเจน: จะมีบางกรณีที่ไฟล์นี้ไม่มีอยู่ในเวลาที่คุณโทรหาฉันและคุณก็จัดการกับมันได้ดีกว่า
นี่จะชัดเจนยิ่งขึ้นเมื่อมีกรณีพิพาท ลองนึกภาพฉันเขียนตาราง API ฉันมีรูปแบบตารางที่ไหนสักแห่งที่มี API รวมถึงวิธีนี้:
public RowData getRowData(int row)
ตอนนี้เป็นโปรแกรมเมอร์ API ฉันรู้ว่าจะมีกรณีที่ลูกค้าบางรายผ่านค่าลบสำหรับแถวหรือค่าแถวนอกตาราง ดังนั้นฉันอาจถูกล่อลวงให้โยนข้อยกเว้นที่เลือกและบังคับให้ลูกค้าจัดการกับมัน:
public RowData getRowData(int row) throws CheckedInvalidRowNumberException
(ฉันจะไม่เรียกมันว่า "ตรวจสอบ" แน่นอน)
นี่คือการใช้งานข้อยกเว้นที่ตรวจสอบไม่ถูกต้อง รหัสลูกค้าจะเต็มไปด้วยการโทรเพื่อดึงข้อมูลแถวทุกอันที่จะต้องใช้ลอง / จับและอะไร? พวกเขาจะรายงานกับผู้ใช้ว่าหาแถวผิดหรือไม่ อาจไม่ - เพราะสิ่งที่ UI ล้อมรอบมุมมองตารางของฉันคือมันไม่ควรให้ผู้ใช้เข้าสู่สถานะที่มีการร้องขอแถวที่ผิดกฎหมาย ดังนั้นจึงเป็นข้อบกพร่องในส่วนของโปรแกรมเมอร์ลูกค้า
โปรแกรมเมอร์ API IllegalArgumentException
ยังคงสามารถคาดการณ์ได้ว่าลูกค้าจะได้รหัสข้อผิดพลาดดังกล่าวและควรจะจัดการกับมันด้วยข้อยกเว้นรันไทม์เช่น
มีข้อยกเว้นในการตรวจสอบgetRowData
นี้เป็นกรณีที่ชัดเจนที่จะนำไปสู่โปรแกรมเมอร์ขี้เกียจของ Hejlsberg เพียงแค่เพิ่มการจับว่างเปล่า เมื่อสิ่งนั้นเกิดขึ้นค่าแถวที่ผิดกฎหมายจะไม่ปรากฏชัดเจนแม้แต่กับผู้ทดสอบหรือการดีบักของนักพัฒนาไคลเอ็นต์ แต่จะนำไปสู่ข้อผิดพลาดแบบเคาะที่ยากที่จะระบุแหล่งที่มาของ จรวด Arianne จะระเบิดขึ้นหลังจากเปิดตัว
ตกลงนี่คือปัญหา: ฉันกำลังบอกว่าข้อยกเว้นที่ตรวจสอบFileNotFoundException
ไม่ได้เป็นเพียงสิ่งที่ดี แต่เป็นเครื่องมือสำคัญในกล่องเครื่องมือโปรแกรมเมอร์ API สำหรับการกำหนด API ในวิธีที่มีประโยชน์ที่สุดสำหรับลูกค้าโปรแกรมเมอร์ แต่CheckedInvalidRowNumberException
สิ่งที่ไม่สะดวกคือการเขียนโปรแกรมที่ไม่ดีและควรหลีกเลี่ยง แต่จะบอกความแตกต่างได้อย่างไร
ฉันเดาว่ามันไม่ได้เป็นวิทยาศาสตร์ที่แน่นอนและฉันเดาว่าสิ่งนั้นเป็นพื้นฐานและอาจพิสูจน์ให้เห็นถึงข้อโต้แย้งของ Hejlsberg แต่ฉันไม่มีความสุขที่ได้โยนลูกออกมาพร้อมกับอ่างอาบน้ำที่นี่ดังนั้นให้ฉันดึงกฎบางอย่างที่นี่เพื่อแยกข้อยกเว้นที่ได้รับการตรวจสอบอย่างดีจากความไม่ดี:
อยู่นอกการควบคุมของลูกค้าหรือปิด vs เปิด:
ควรใช้ข้อยกเว้นที่ตรวจสอบแล้วเฉพาะกรณีที่เกิดข้อผิดพลาดที่ไม่สามารถควบคุมได้ทั้ง API และโปรแกรมเมอร์ของไคลเอ็นต์ สิ่งนี้เกี่ยวข้องกับการเปิดหรือปิดระบบ ในUI ที่มีข้อ จำกัดซึ่งโปรแกรมเมอร์ของไคลเอ็นต์มีการควบคุมพูดถึงปุ่มคำสั่งแป้นพิมพ์ ฯลฯ ทั้งหมดที่เพิ่มและลบแถวออกจากมุมมองตาราง (ระบบปิด) มันเป็นข้อผิดพลาดในการเขียนโปรแกรมไคลเอนต์หากพยายามดึงข้อมูลจาก แถวที่ไม่มีอยู่ ในระบบปฏิบัติการแบบใช้ไฟล์ที่มีจำนวนผู้ใช้ / แอปพลิเคชันใด ๆ สามารถเพิ่มและลบไฟล์ (ระบบเปิด) มันเป็นไปได้ว่าไฟล์ที่ลูกค้าร้องขอได้ถูกลบไปแล้วโดยไม่ต้องมีความรู้ดังนั้นพวกเขาควรคาดหวังว่าจะจัดการกับมัน .
แพร่หลาย:
การตรวจสอบข้อยกเว้นไม่ควรใช้กับการเรียก API ที่ไคลเอนต์ใช้บ่อย บ่อยครั้งที่ฉันหมายถึงสถานที่จำนวนมากในรหัสลูกค้า - ไม่บ่อยครั้ง ดังนั้นรหัสไคลเอนต์จึงไม่น่าจะพยายามเปิดไฟล์เดียวกันมากนัก แต่มุมมองตารางของฉันได้รับRowData
ทุกที่จากวิธีการที่แตกต่างกัน โดยเฉพาะฉันจะเขียนโค้ดเป็นจำนวนมากเช่น
if (model.getRowData().getCell(0).isEmpty())
และมันจะเจ็บปวดที่ต้องห่อ / ลองทุกครั้ง
แจ้งผู้ใช้:
ควรใช้ข้อยกเว้นที่ผ่านการตรวจสอบในกรณีที่คุณสามารถจินตนาการถึงข้อความแสดงข้อผิดพลาดที่เป็นประโยชน์ซึ่งจะปรากฏต่อผู้ใช้ปลายทาง นี่คือ"และคุณจะทำอย่างไรเมื่อมันเกิดขึ้น?" คำถามที่ฉันยกขึ้นข้างบน นอกจากนี้ยังเกี่ยวข้องกับรายการ 1 เนื่องจากคุณสามารถคาดการณ์ได้ว่ามีบางสิ่งที่อยู่นอกระบบไคลเอนต์ API ของคุณอาจทำให้ไฟล์ไม่อยู่ที่นั่นคุณสามารถบอกผู้ใช้ได้อย่างสมเหตุสมผล:
"Error: could not find the file 'goodluckfindingthisfile'"
เนื่องจากหมายเลขแถวที่ผิดกฎหมายของคุณเกิดจากข้อผิดพลาดภายในและไม่มีข้อผิดพลาดของผู้ใช้จึงไม่มีข้อมูลที่เป็นประโยชน์ที่คุณสามารถให้ได้ หากแอปของคุณไม่ปล่อยให้ข้อยกเว้นรันไทม์ไปที่คอนโซลมันอาจจะทำให้พวกเขามีข้อความที่น่าเกลียดเช่น:
"Internal error occured: IllegalArgumentException in ...."
กล่าวโดยย่อถ้าคุณไม่คิดว่าโปรแกรมเมอร์ของลูกค้าสามารถอธิบายข้อยกเว้นของคุณในลักษณะที่ช่วยผู้ใช้คุณอาจไม่ควรใช้ข้อยกเว้นที่ตรวจสอบ
นั่นคือกฎของฉัน ค่อนข้างจะถูกวางแผนและจะมีข้อยกเว้นอย่างไม่ต้องสงสัย (โปรดช่วยฉันปรับแต่งถ้าคุณต้องการ) แต่เหตุผลหลักของฉันคือมีบางกรณีFileNotFoundException
ที่ข้อยกเว้นที่ตรวจสอบนั้นมีความสำคัญและมีประโยชน์ส่วนหนึ่งของสัญญา API เป็นประเภทพารามิเตอร์ ดังนั้นเราไม่ควรแจกจ่ายมันเพียงเพราะมันถูกใช้ในทางที่ผิด
ขออภัยไม่ได้ตั้งใจที่จะทำสิ่งนี้ให้นานและใจร้อน ให้ฉันทำตามคำแนะนำสองข้อ:
ตอบ: โปรแกรมเมอร์ API: ใช้ข้อยกเว้นที่ตรวจสอบอย่าง จำกัด เพื่อรักษาประโยชน์ของมันไว้ เมื่อมีข้อสงสัยให้ใช้ข้อยกเว้นที่ไม่ได้ตรวจสอบ
B: โปรแกรมเมอร์ของไคลเอ็นต์: ติดนิสัยในการสร้างข้อยกเว้น (google it) ในการพัฒนาของคุณ JDK 1.4 และใหม่กว่านั้นให้ตัวสร้างในRuntimeException
สิ่งนี้ แต่คุณสามารถสร้างของคุณเองได้อย่างง่ายดาย นี่คือตัวสร้าง:
public RuntimeException(Throwable cause)
จากนั้นเริ่มติดนิสัยเมื่อใดก็ตามที่คุณต้องจัดการกับข้อยกเว้นที่ตรวจสอบแล้วและคุณรู้สึกเกียจคร้าน (หรือคุณคิดว่าโปรแกรมเมอร์ API ขยันหมั่นเพียรในการใช้ข้อยกเว้นที่ตรวจสอบในตอนแรก) อย่าเพิ่งกลืนข้อยกเว้นห่อมัน และทำใหม่อีกครั้ง
try {
overzealousAPI(thisArgumentWontWork);
}
catch (OverzealousCheckedException exception) {
throw new RuntimeException(exception);
}
ใส่สิ่งนี้ลงในเทมเพลตโค้ดขนาดเล็กหนึ่งตัวของ IDE และใช้งานเมื่อคุณรู้สึกขี้เกียจ วิธีนี้หากคุณจำเป็นต้องจัดการกับข้อยกเว้นที่ถูกตรวจสอบอย่างแท้จริงคุณจะถูกบังคับให้กลับมาและจัดการกับมันหลังจากที่พบปัญหาที่รันไทม์ เพราะเชื่อฉัน (และ Anders Hejlsberg) คุณจะไม่กลับมาที่สิ่งที่ต้องทำในของคุณ
catch (Exception e) { /* TODO deal with this at some point (yeah right) */}