เมื่อ API ไม่ทำอะไรเลย ก็ต้องไม่ทำอะไรให้ถูกวิธี
- เมื่อ API ไม่ควรทำอะไรเลย สิ่งสำคัญคือมันต้อง "ไม่ทำอะไร" อย่างถูกต้อง
- ตัวอย่างเช่น Windows มีโครงสร้างพื้นฐานด้านการพิมพ์ขนาดใหญ่ แต่ Xbox ไม่มีโครงสร้างพื้นฐานแบบนั้น
- หากแอปพยายามสั่งพิมพ์บน Xbox แล้วโยน
NotSupportedException ถือว่าเป็นวิธีที่ผิด
- เพราะแอปอาจถูกทดสอบบนพีซีเป็นหลัก พอไปรันบน Xbox จึงอาจไม่มีการจัดการข้อยกเว้นและทำให้แอปล่มได้
- การออกแบบที่ดีกว่าสำหรับการ "รองรับ" ฟังก์ชันการพิมพ์บน Xbox คือให้ฟังก์ชันพิมพ์ทำงานสำเร็จ แต่รายงานว่าไม่มีเครื่องพิมพ์ที่ติดตั้งอยู่
- เมื่อผู้ใช้พยายามสั่งพิมพ์ ระบบจะขอให้เลือกเครื่องพิมพ์ แต่รายการว่างเปล่า ทำให้ผู้ใช้เข้าใจว่า "ไม่มีเครื่องพิมพ์" แล้วจึงยกเลิกคำสั่งพิมพ์
- สำหรับแอปที่พยายามติดตั้งเครื่องพิมพ์ ฟังก์ชันติดตั้งเครื่องพิมพ์สามารถคืนค่าทันทีพร้อมรหัสผลลัพธ์ว่า "ผู้ใช้ยกเลิกการทำงาน"
- เป้าหมายคือทำตัวราวกับว่าฟังก์ชันการพิมพ์ได้รับการรองรับอย่างสมบูรณ์ แต่ในความเป็นจริงแค่ทำตัวเหมือนว่าไม่มีเครื่องพิมพ์
- ในระบบที่การพิมพ์ใช้งานไม่ได้เลย อาจเพิ่มฟังก์ชันตรวจสอบว่าสามารถพิมพ์ได้หรือไม่ เพื่อซ่อนปุ่มพิมพ์ออกจาก UI
- พฤติกรรมแบบนี้เรียกว่า "inert"
- พื้นผิวของ API ยังคงมีอยู่และยังทำงานตามสเปก แต่ในความเป็นจริงมันไม่ได้ทำอะไรเลย
- สิ่งสำคัญคือมันต้อง "ไม่ทำอะไร" อย่างสอดคล้องกับวิธีที่มีการจัดทำเอกสารไว้ เพื่อให้เกิดปัญหากับโค้ดเดิมน้อยที่สุด
ตัวอย่างการปิดการทำงานของ API
- มีตัวอย่างของการปิดการทำงาน API ที่ประกอบด้วยฟังก์ชันหลายแบบ ได้แก่ ฟังก์ชันสร้าง widget handle, ฟังก์ชันที่รับ widget handle และฟังก์ชันที่ปิด widget handle
- ตอนแรกทีมเสนอให้ปิด API โดยทำให้
CreateWidget สำเร็จแต่คืนค่า null pointer
- แต่วิธีนี้อาจทำให้แอปสับสนได้: "เรียกสำเร็จแล้วแต่ไม่ได้ handle ที่ใช้ได้งั้นเหรอ?"
- การที่
EnableWidget คืนค่าเป็น "handle ไม่ถูกต้อง" ก็อาจทำให้เกิดความสับสนเช่นกัน
- ในเอกสารเดิมพบว่ามีค่าที่ส่งกลับชื่อ
ERROR_CANCELLED ซึ่งหมายถึงการสร้าง widget ถูกผู้ใช้ยกเลิก
- ดังนั้นทุกครั้งที่แอปพยายามสร้าง widget ก็สามารถตอบได้ว่า "ไม่ ผู้ใช้ยกเลิกแล้ว"
ความเห็นของ GN⁺
- ประเด็นสำคัญที่สุดของบทความนี้คือ เมื่อ API ไม่ทำอะไรเลย มันก็ควรไม่ทำอะไรในแบบที่ไม่ทำลายประสบการณ์ผู้ใช้และยังคงความเข้ากันได้กับโค้ดเดิม
- แนวทางนี้ช่วยตอกย้ำให้เห็นความสำคัญของการออกแบบ API สำหรับนักพัฒนา และมีส่วนช่วยสร้างประสบการณ์ซอฟต์แวร์ที่เป็นมิตรกับผู้ใช้
- บทความนี้แสดงให้เห็นแง่มุมที่ละเอียดอ่อนของวิศวกรรมซอฟต์แวร์ และนำเสนอกรณีศึกษาที่น่าสนใจเกี่ยวกับวิธีทำให้แอปล้มเหลวได้อย่างสง่างามแม้อยู่ในสภาพแวดล้อมที่ไม่คาดคิด
2 ความคิดเห็น
ความคิดเห็นใน Hacker News
ความเห็นเกี่ยวกับ "การกลืนข้อผิดพลาดทิ้ง":
panicของภาษา Go เป็นวิธีที่ดีในการส่งสัญญาณดัง ๆ ว่าโปรแกรมเมอร์ทำพลาดระหว่างการทดสอบความเห็นเกี่ยวกับความเข้ากันได้แบบย้อนหลัง:
ข้อไม่พอใจเกี่ยวกับการออกแบบ UI:
การชี้ว่า Microsoft ไม่ได้เรียนรู้:
ความเห็นเกี่ยวกับการที่ Xbox ไม่รองรับการพิมพ์:
คำแนะนำเกี่ยวกับการใช้ API:
NotSupportedExceptionไม่ใช่เรื่องผิดความรู้สึกต่อ "การทำตามอย่างประชดประชัน":
ความเห็นเชิงบวกด้านความปลอดภัย:
การทบทวนกลยุทธ์ของเบราว์เซอร์:
สิ่งที่นักวิจารณ์การจัดการข้อยกเว้นเข้าใจพลาด:
ความเห็นจาก Lobste.rs
พักเรื่องมุกตลกไว้ก่อน ผมไม่เห็นด้วยกับการเขียนโปรแกรมเชิงป้องกันและประสบการณ์ผู้ใช้ที่ป้องกันเกินเหตุแบบนี้ เพราะมันทำให้ซอฟต์แวร์ไม่ทำหน้าที่ของตัวเองโดยที่ไม่รู้สาเหตุ และก็ไม่มีทางรู้ได้ด้วยว่าทำไม แอปควรจับข้อผิดพลาดแล้วสร้างข้อความที่เป็นมิตรกับผู้ใช้ถ้าทำได้ ไม่อย่างนั้นก็ควรแสดงข้อความผิดพลาดเดิมให้ผู้ใช้เห็น ถ้าเป็นงานเบื้องหลังก็ควรมี error log
ผมยอมรับว่าบทความนี้เขียนจากมุมมองของนักพัฒนา API ไม่ใช่นักพัฒนาแอป ดังนั้นก็ควรทำเอกสารข้อผิดพลาดของ API และให้ข้อความผิดพลาดที่ฝั่งผู้เรียกสามารถนำไปจัดการต่อได้
อีกอย่าง ผมก็ไม่ชอบการซ่อนปุ่มใน UI เพราะไม่มีสิทธิ์เข้าถึง ถ้ามีพื้นที่พอ ผมคิดว่าควรแสดงปุ่มไว้แต่ปิดการใช้งาน และเมื่อผู้ใช้เอาเมาส์ไปชี้ก็ควรมีข้อความบอกว่าจะเปิดใช้งานได้อย่างไร
โดยรวมแล้ว การบังคับให้ถูกต้องแม่นยำน่าจะดีกว่า แต่ถ้าคุณมีผู้ใช้เดิมอยู่ 1 พันล้านคน การพยายามไม่ทำให้มันพังคือเรื่องที่ฉลาดมาก และจากมุมผู้ใช้มันก็มีคุณค่าจริงในระดับระบบ เพราะมันแค่ใช้งานได้ สุดท้ายแล้วท่าทีที่ควรเป็นคือ fail fast แต่อย่าทำให้พังเป็นวงกว้าง
เรื่องนี้ใกล้เคียงกับ เสถียรภาพของ ABI มากกว่า API บน Windows ซอฟต์แวร์ที่ build ไว้เมื่อ 15 ปีก่อนก็ควรยังทำงานต่อบนระบบปฏิบัติการใหม่ได้เท่าที่เป็นไปได้ เมื่อเปลี่ยน function signature ไม่ได้ คุณก็ต้องโกหกแบบหวังดีเพื่อให้ API ที่ไม่มีความหมายอีกต่อไปแล้วยังทำงานต่อได้
ตัวอย่างเช่น API ยังทำเหมือนว่า Active Desktop มีอยู่จริง เพราะอีกทางเลือกหนึ่งคือทำให้ซอฟต์แวร์เก่า ๆ จำนวนมากพัง
มันทำให้ไม่รู้ว่าฟีเจอร์นั้นหายไปแล้ว หรือแค่ถูกซ่อนไว้ในอีกหน้าหนึ่งระหว่างทาง
แต่ถ้าแอปไม่ทำ คนที่ใช้แอปนั้นก็จะโทษ Windows ไม่ใช่โทษแอป และแม้แต่ตอนแอปแครชก็ยังเป็นแบบนั้น
นั่นจึงเป็นเหตุผลที่ Microsoft ต้องทำทางอ้อม ปล่อยให้งานพิมพ์หายไปเฉย ๆ ง่ายกว่ามาก แล้วผู้ใช้ก็จะคิดอยู่ครู่หนึ่งก่อนยอมรับว่า “อ้อ จริงสิ ไม่มีเครื่องพิมพ์นี่นา”
ifว่าเป็น null หรือไม่บ่อยมาก ทุกครั้งที่เห็นก็จะนึกถึงบทความนี้ผมเลยสั่งเอเจนต์ว่าอย่าตรวจ null ซ้ำ ๆ แต่ให้ใช้ ฟังก์ชันที่ไม่ก่อผลเสีย แทน และให้ยืนยันครั้งเดียวตอนประกาศตัวแปรว่าค่านั้นจะไม่เป็น null แน่นอน