ถ้าคุณจะไม่ทำอะไรเลย อย่างน้อยก็ควรไม่ทำอะไรให้ถูกวิธี
(devblogs.microsoft.com)- แม้จะไม่สามารถให้ความสามารถจริงได้เพราะข้อจำกัดของแพลตฟอร์มบางอย่างหรือเพราะ API ถูกยกเลิกใช้งานไปแล้ว หากต้องการไม่ให้แอปเดิมพัง ก็จำเป็นต้องออกแบบพฤติกรรมแบบ “ไม่ทำอะไรเลย” ให้อยู่ภายใต้ สัญญา API ที่มีการจัดทำเอกสารไว้
- ในสภาพแวดล้อมอย่าง Xbox ที่ไม่มี โครงสร้างพื้นฐานด้านการพิมพ์ การตอบกลับสำเร็จพร้อม จำนวนเครื่องพิมพ์เป็น 0 ปลอดภัยกว่าสำหรับแอปที่เขียนโดยอิงพฤติกรรมบนพีซี มากกว่าการโยน
NotSupportedException - แนวทางนี้เรียกว่า inert API คือการคงพื้นผิว API และสเปกเอาไว้ แต่ทำให้มันไม่ทำงานที่มีประโยชน์จริง
- หากใน Widget API ที่กำลังเลิกใช้
CreateWidgetสำเร็จแต่คืนค่าnullptrผู้เรียกจะเจอสถานะที่ขัดแย้งกัน ดังนั้นเส้นทางความล้มเหลวแบบERROR_CANCELLEDที่มีการระบุไว้ในเอกสารจึงสอดคล้องกว่า - inert API ที่ถูกต้องจะทำให้มีแค่ความสามารถที่หายไป และใช้เส้นทางความล้มเหลวที่โค้ดเดิมเตรียมรับไว้อยู่แล้วเพื่อลด exception, handle ที่ไม่ถูกต้อง และ crash
แม้ไม่มีความสามารถแล้ว แต่สัญญา API ยังอยู่
- มีบางกรณีที่ต้องทำให้ API ไม่ทำอะไรเลย แต่ความไม่ทำงานนั้นก็ยังต้องสอดคล้องกับโค้ดเดิมและพฤติกรรมที่ระบุไว้ในเอกสาร
- การออกแบบการไม่ทำงานที่ผิดพลาดอาจสร้าง exception, ค่าที่คืนมาแบบขัดแย้งกัน และสถานะที่ไม่ถูกต้อง ซึ่งนำไปสู่แอปล่มหรือข้อความผิดพลาดที่ไม่จำเป็นต่อผู้ใช้
วิธีปิดความสามารถของ API การพิมพ์บน Xbox
- Windows มี โครงสร้างพื้นฐานด้านการพิมพ์ ที่ครอบคลุม แต่ Xbox ไม่มีโครงสร้างดังกล่าว
- หากแอปพยายามพิมพ์บน Xbox แล้วฟังก์ชันการพิมพ์โยน
NotSupportedExceptionปัญหาอาจใหญ่ขึ้น- แอปที่ติดตั้งบน Xbox มีโอกาสสูงว่าจะถูกทดสอบมาหลัก ๆ บนพีซี
- บนพีซี การพิมพ์ใช้งานได้เสมอ ดังนั้น exception ที่ไม่ถูกจัดการอาจทำให้แอปล่ม
- แม้จะจับ exception ได้ ผู้ใช้อาจเห็นข้อความผิดพลาดอย่าง “ให้ส่ง incident code ไปยังฝ่ายสนับสนุน”
- การออกแบบที่ดีกว่าคือให้ฟังก์ชันการพิมพ์สำเร็จ แต่รายงานว่า ไม่มีเครื่องพิมพ์ที่ติดตั้งอยู่
- แอปจะเปิด UI เลือกเครื่องพิมพ์และแสดงรายการว่าง
- ผู้ใช้จะเห็นว่าไม่มีเครื่องพิมพ์และยกเลิกคำขอพิมพ์ได้
- สำหรับแอปที่พยายามช่วยติดตั้งเครื่องพิมพ์ ฟังก์ชันติดตั้งอาจคืนค่าทันทีพร้อมผลลัพธ์ที่หมายถึง ผู้ใช้ยกเลิกการทำงาน
เงื่อนไขของ inert API
- พฤติกรรมแบบ “ไม่ทำอะไรเลย” นี้เรียกว่า inert
- inert API คือ API ที่ยังคงมีพื้นผิวและทำงานตามสเปก แต่ในทางปฏิบัติแล้วไม่ได้ทำงานที่มีประโยชน์จริง
- แก่นสำคัญคือการไม่ทำอะไรในแบบที่ยังสอดคล้องกับเอกสาร และมีโอกาสน้อยที่สุดที่จะสร้างปัญหาให้โค้ดเดิม
- ใน API การพิมพ์ อาจเพิ่มฟังก์ชันสำหรับตรวจสอบได้ว่าระบบสามารถพิมพ์ได้จริงหรือไม่
- แอปสามารถใช้ฟังก์ชันนี้เพื่อซ่อนปุ่ม Print บนระบบที่ไม่รองรับการพิมพ์เลย
- แอปที่แค่สมมติว่าพิมพ์ได้ ก็จะทำงานราวกับอยู่บนระบบที่ “ไม่มีเครื่องพิมพ์และการพยายามติดตั้งก็ไม่เกิดผล”
การออกแบบที่ควรหลีกเลี่ยงใน Widget API ที่กำลังยกเลิกใช้งาน
- API ที่กำลังจะเลิกใช้มีฟังก์ชันสำหรับสร้าง widget handle, ฟังก์ชันที่รับ widget handle, และฟังก์ชันสำหรับปิด widget handle
- ข้อเสนอแรกคือให้
CreateWidgetคืนค่าS_OKพร้อมตั้งค่า*widget = nullptr- การเรียกดูเหมือนสำเร็จ แต่กลับไม่ได้ handle ที่ใช้งานได้จริง
- ในโค้ดทดสอบจริงบางส่วน มีการตัดสินความสำเร็จจากการที่ handle เป็น
nullหรือไม่ แทนที่จะดูค่าที่คืนมา
- การออกแบบให้
EnableWidgetคืนค่า “handle ไม่ถูกต้อง” ก็ทำให้ผู้เรียกสับสนเช่นกัน- แอปส่ง handle ที่ได้รับหลัง
CreateWidgetสำเร็จไปยังEnableWidget - API กลับตอบว่า handle นั้นไม่ถูกต้อง
- ผู้เรียกจึงต้องเจอสถานะที่ขัดแย้งกันแบบ “ขอ widget แล้วได้มา แต่พอส่งกลับไปกลับบอกว่าไม่ใช่ widget”
- แอปส่ง handle ที่ได้รับหลัง
การแก้แบบใช้เส้นทางความล้มเหลวที่มีบันทึกไว้ในเอกสาร
- ในเอกสารเดิมมีค่าที่คืนกลับ
ERROR_CANCELLEDซึ่งหมายถึงผู้ใช้ยกเลิกการสร้าง widget - แอปต้องรองรับความเป็นไปได้ที่ widget จะไม่ถูกสร้างจากเงื่อนไขภายนอกอยู่แล้ว จึงสามารถทำให้การสร้าง widget ล้มเหลวแบบ ผู้ใช้ยกเลิก ได้ทุกครั้ง
- ลำดับการทำงานของการออกแบบที่แก้แล้วนั้นเรียบง่าย
CreateWidgetจะคง*widget = nullptrไว้และคืนค่าHRESULT_FROM_WIN32(ERROR_CANCELLED)- เมื่อการสร้าง widget ล้มเหลวเสมอ ก็จะไม่มี widget handle ที่ถูกต้องอยู่เลย
GetWidgetAliases,EnableWidget,Closeจะคืนค่าE_HANDLEว่า handle ไม่ถูกต้อง
- ด้วยวิธีนี้ เมื่อไม่มี widget ก็ไม่จำเป็นต้องสร้าง alias หลอกขึ้นมา
- เพราะไม่มี widget ที่ถูกต้องอยู่แล้ว จึงไม่มีกรณีที่แอปจะร้องขอ alias ได้อย่างเป็นปกติ
เกณฑ์เมื่อนำ Desktop API ไปยังสภาพแวดล้อมอื่น
- บนเดสก์ท็อป API การพิมพ์มีอยู่เสมอ และฟังก์ชัน “ดึงรายการเครื่องพิมพ์” ก็มีการระบุไว้ในเอกสารว่าจะไม่โยน exception
- เมื่อนำ API นี้ไปใช้บน Xbox หากต้องการให้แอปเดสก์ท็อปเดิมยังทำงานต่อได้ พฤติกรรมแบบ inert ที่ถูกต้องคือการคืนความจริงอย่างตรงไปตรงมาว่า “Xbox ไม่มีเครื่องพิมพ์”
- การโยน exception เพื่อตอบคำถามว่า “มีเครื่องพิมพ์กี่เครื่อง?” ไม่สอดคล้องกับสัญญา API ที่บันทึกไว้ในเอกสาร
- หลักการเดียวกันนี้ใช้ได้ตอนยกเลิก API เดิมเช่นกัน
- ต่อให้ไม่ทำงานที่มีประโยชน์จริง พฤติกรรมของ API ก็ยังต้องสอดคล้องกับสัญญา
- สิ่งสำคัญคือใช้ค่าที่คืนกลับและสถานะที่โค้ดเดิมสามารถรองรับได้อยู่แล้ว
2 ความคิดเห็น
ความเห็นจาก 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 แน่นอน
ความเห็นจาก Hacker News
เรียนรู้มาแบบ กลบข้อผิดพลาดทิ้ง และมองว่าเป็นแนวปฏิบัติที่ไม่ดี
ไม่เพียงแต่ไม่ช่วยแก้ปัญหาจริงที่ว่าไม่สามารถพิมพ์ออกจาก Xbox ได้ แต่ยังซ่อนด้วยว่าซอฟต์แวร์พังไปมากแค่ไหน ทำให้การหาบั๊กและการทดสอบยากขึ้นมาก
ชอบที่
panicของ Go ไม่ได้ถูกสร้างมาให้รันไทม์ใช้พร่ำเพรื่อหรือไว้ recovery แต่เป็นเครื่องมือที่ส่งเสียงไซเรนดัง ๆ ว่าตอนทดสอบโปรแกรมเมอร์ทำพลาดถ้าองค์ประกอบต่าง ๆ ในระบบพยายามซ่อนความบกพร่องหรือข้อผิดพลาดของตัวเองอย่างแข็งขัน การหาสาเหตุและแก้ไขจะยากขึ้นแบบทวีคูณ
ในระบบ B2B, ไมโครเซอร์วิส, และระบบที่ขับเคลื่อนด้วย API ที่ฉันดูแลอยู่ คำขอที่อยู่นอกสเปกควรถูกทำให้ล้มเหลวอย่างรวดเร็ว
แต่ Windows โอนอ่อนอย่างมากเพื่อความเข้ากันได้ย้อนหลัง และยังมีกรณีที่ช่วยชีวิตซอฟต์แวร์ดังที่พังอย่าง SimCity ด้วยการแพตช์หน่วยความจำด้วยซ้ำ: https://arstechnica.com/gadgets/2022/10/windows-95-went-the-...
ในสภาพแวดล้อมเดสก์ท็อปที่ผู้ใช้แก้โค้ดระบบเองไม่ได้ หลายครั้งประสบการณ์ผู้ใช้ปลายทางสำคัญกว่าความถูกต้อง
สิ่งสำคัญไม่ใช่ว่า BSOD เกิดจากอะไร แต่คือ Microsoft ได้เรียนรู้ว่าซอฟต์แวร์หรือฮาร์ดแวร์ที่แย่ทำให้ชื่อเสียงของ Microsoft เสียหาย
โดยส่วนตัวฉันชอบการออกแบบที่ให้ล้มเหลวหรือหยุดทันทีเมื่อมีอินพุตหรือเงื่อนไขที่ผิด แต่ก็เข้าใจคุณค่าของแนวทางนี้ในบริบทดังกล่าว
สิ่งสำคัญคือบริบท ไม่ใช่การยึดติดกับด้านใดด้านหนึ่งแบบเคร่งครัด
ตัวอย่างเช่น ไม่ควรเอาแนวทางของ Chen ไปใช้กับการออกแบบเตาปฏิกรณ์นิวเคลียร์หรือการเทรดความถี่สูงมาก แต่กับเกมเอนจินอาจเป็นแนวทางที่ยอดเยี่ยม
การตัดสินใจว่าจะให้ส่วนประกอบของรันไทม์อยู่ในสถานะ “ปิดใช้งานอย่างไม่เป็นอันตราย” หรือไม่ เป็นการตัดสินใจด้านการออกแบบที่มาก่อนเรื่องว่า implementation จะ
panicหรือไม่ในกรณีนี้ การนิยามความหมายของ “พิมพ์จาก Xbox” เป็นหน้าที่ของ Microsoft
จะนิยามให้เป็นข้อผิดพลาดก็ได้ หรือในทางทฤษฎีจะนิยามให้เป็นฟีเจอร์ที่อาจรองรับได้ในภายหลัง แต่ตอนนี้ยังไม่มีวิธีทำจริงก็ได้
ถ้าเสียบเครื่องพิมพ์ USB และมี “เกม” ที่รู้วิธีพิมพ์แบบเดียวกับพอร์ตขยายของเกม Gameboy ที่เคยรองรับ GB Printer ในทางทฤษฎีก็พิมพ์จาก Xbox ได้
การที่เกม Xbox พยายามพิมพ์ไม่ได้เป็นข้อผิดพลาดโดยเนื้อแท้เสมอไป และการนิยามให้เป็นข้อผิดพลาดก็เป็นเพียงทางเลือกที่มี trade-off เช่นความสามารถในการพอร์ตแอปพลิเคชัน
คุณอาจแสดงหน้าจอเลือกที่ไม่มีเครื่องพิมพ์ให้เลือกเหมือนใน Xbox, ทำให้ API โกหกว่าพิมพ์แล้ว, หรือรองรับการพิมพ์จริงไปเลยก็ได้
ต้องนิยาม “การพิมพ์จาก Xbox” ให้เป็นข้อผิดพลาดก่อน จึงค่อยเป็นประเด็นด้าน implementation หรือ debugging ว่าข้อผิดพลาดนั้นถูกโยนออกมาหรือถูกกลบทิ้ง
การจัดการที่เป็นไปได้มีสองแบบ: แจ้งข้อผิดพลาดเพราะเครื่องนี้ไม่มีวันมีเครื่องพิมพ์ หรือคืนค่ารายการเครื่องพิมพ์ว่างเปล่าเพราะเครื่องนี้ไม่มีวันมีเครื่องพิมพ์
ทั้งสองแบบถือว่าใช้ได้ แต่มีเพียงแบบเดียวที่ไม่ทำให้ user space พัง
“การส่งเสียงไซเรนดัง ๆ” ดีสำหรับผู้ทดสอบหรือนักพัฒนา แต่ไม่ค่อยดีสำหรับผู้ใช้ปลายทาง
จากมุมมองผู้ใช้ปลายทาง การกลบข้อผิดพลาดยังดีกว่าแอปล่ม
ไม่มีทางรู้ว่าอะไรกันแน่ที่ล้มเหลว: ซอฟต์แวร์ในคอมของฉัน, ฮาร์ดแวร์, สายอีเทอร์เน็ตหรือ Wi‑Fi, สวิตช์, เราเตอร์, เคเบิลโมเด็ม, สายอินเทอร์เน็ตที่เข้าบ้าน, ISP, หรือระบบปลายทางที่พยายามเชื่อมต่อ เรื่องนี้แทบไม่ดีขึ้นเลย
แม้จะคาดได้ว่าจะมีคนตอบรับในเชิงลบ แต่งานสกปรกแบบนี้แหละคือเหตุผลที่เมื่อคลิกเอกสาร Word ’97 หรือเกมที่คอมไพล์ไว้สำหรับ MS-DOS เมื่อ 30 ปีก่อน มันยังเปิดขึ้นมาได้ตามที่คาดหวัง
ความเข้ากันได้ย้อนหลังเป็นเรื่อง messy เสมอ จะทำแบบไม่สมบูรณ์หรือไม่ทำเลยก็อย่างใดอย่างหนึ่ง
สุดท้ายก็ต้องไปใช้ GOG และ GOG เองก็ลงเอยด้วยการทำ virtualization ของสภาพแวดล้อม
เช่น ฉันไม่คิดว่าจะมีใครติดตั้ง Sim City ต้นฉบับบน Windows 11 แล้วสำเร็จ
การจัดรูปแบบขึ้นอยู่กับ ไดรเวอร์เครื่องพิมพ์ ที่ติดตั้งไว้
ในทางกลับกัน ไฟล์ LaTeX จาก 35 ปีก่อนยังใช้งานได้ดีจนถึงตอนนี้
Word ไม่สามารถรับประกันได้ด้วยซ้ำว่าเมื่อเปิดซ้ำบนคอมเครื่องเดิมและเวอร์ชันเดิมแล้วรูปแบบจะไม่เปลี่ยน และนั่นคือเหตุผลที่ฉันไปเรียน TeX
เรื่องเกมก็เหมือนกัน Microsoft ทำเกมจำนวนมากพังเมื่อปิด GFWL
Dark Souls ภาคต้นฉบับเล่นไม่ได้จนกว่าจะถูกนำกลับมาวางขายบน Steam ใหม่ เลยต้องใช้เวอร์ชันเถื่อน
จะมีหน้าต่างแจ้งว่า “ไม่รองรับ” ขึ้นมา และถึงจะไปดาวน์โหลด dosbox ได้ แต่นั่นก็ไม่ใช่การทำงาน “ตรงตามที่คาดหวังเป๊ะ”
เกือบทุกครั้งสุดท้ายก็ต้องยอมแพ้หรือไม่ก็ซื้อใหม่ใน GOG ซึ่งจริง ๆ แล้วก็คือจ่ายเงินให้กับงานที่ช่วยจัดการปัญหาความเข้ากันได้เหล่านั้น
ไม่มีอะไรน่าหงุดหงิดไปกว่าหน้า UI ของอุปกรณ์ที่บอกเป็นนัยว่ามันน่าจะมีอยู่จริง แต่สุดท้ายกลับพบว่าตอนนี้ไม่มี
สุดท้ายก็ต้องเสียเวลาไปถึงจะรู้ว่าอุปกรณ์เหล่านั้นไม่ได้รับการรองรับ และหน้าจอนั้นเป็นแค่ mockup ที่ใครสักคนทำไว้
ถ้าแอปไม่ได้เตรียมรับสถานการณ์ที่ไม่รองรับการพิมพ์ไว้ แล้วการพิมพ์โยนข้อยกเว้นออกมา แอปก็น่าจะล้มไปตรงนั้นเลย
ถ้าเป็นแอปที่เตรียมรับกรณีไม่รองรับการพิมพ์ไว้ ก็ควรตรวจสอบก่อนผ่าน API แบบชัดเจนว่ารองรับการพิมพ์หรือไม่ และถ้าไม่รองรับก็ไม่ควรแสดง UI การพิมพ์เลย
บทความนี้พูดถึงวิธีจัดการกับแอปที่ไม่ได้ตรวจสอบก่อน ไม่ใช่แอปแบบนั้น
บั๊กอยู่ที่ตัวแอป แต่แพลตฟอร์มที่ดีควรพยายามทำให้แม้แต่แอปที่แย่ก็ยังทำงานต่อได้ แทนที่จะปล่อยให้มันตายไปเฉยๆ
หน้าที่ของ Microsoft คือทำให้แอปไม่ล้มเพียงเพราะผู้ให้บริการซอฟต์แวร์ไม่ได้คำนึงถึงเครื่องพิมพ์
พอแอปพยายามพิมพ์แล้วให้ผู้ใช้เลือกเครื่องพิมพ์ แต่กลับแสดงรายการว่างเปล่าแบบนี้ ดูเหมือน Microsoft จะไม่เรียนรู้อะไรมาเลยตลอด 30 ปี: https://www.reddit.com/r/hacking/comments/djvzd/windows_nt_l...
ทิศทางที่บทความเสนออย่างเป็นรูปธรรม โดยเฉพาะแนวคิดที่ว่า องค์ประกอบควรเจ็บก่อนที่ผู้ใช้จะต้องเจ็บ นั้นถูกต้อง แต่กรอบการเล่ามันน่ารำคาญมาก
คำพูดทำนองว่า “มีบางครั้งที่ต้องทำให้ API ไม่ทำอะไรเลย”, “การที่ฟังก์ชันพิมพ์โยน
NotSupportedExceptionออกมานั้นผิด” แบบนี้ไม่ใช่คำแนะนำทั่วไปอย่างแน่นอนนี่คือการแฮ็กเพื่อรองรับไคลเอนต์ที่ห่วย ซึ่งบางครั้งก็จำเป็น แต่ไม่ใช่คำแนะนำที่ปกติหรือเอาไปเหมารวมได้
ถ้อยคำแบบนี้เผยให้เห็นร่องรอยของการซึมซับความทุกข์ในฐานะนักพัฒนา Microsoft เข้าไปแล้ว
ถ้าไคลเอนต์ไม่ได้เปลี่ยน แต่ API เปลี่ยนจนทำให้มันพัง แบบนั้นสิ่งที่ห่วยไม่ใช่ไคลเอนต์ แต่เป็น API
สำหรับทุกแพลตฟอร์มที่ถูกใช้อย่างแพร่หลายและให้ความสำคัญกับ ความเข้ากันได้ย้อนหลัง อย่างจริงจัง นี่ถือเป็นเรื่องปกติเต็มที่และเหมารวมเป็นหลักการได้
Windows เป็นตัวอย่างชัดเจน แต่เรื่องเดียวกันก็เกิดขึ้นกับ Linux kernel ABI, glibc, เว็บแพลตฟอร์ม, Java และอื่นๆ
คุณย้อนเวลากลับไปคอมไพล์หรือเขียนไคลเอนต์ใหม่ไม่ได้
ถ้าไคลเอนต์มีการตรวจ
NotSupportedExceptionอยู่แล้ว คุณก็ควรคืนค่านั้น แต่ถ้าไม่มี ก็ต้องใช้แนวทางอื่นนี่คือ Windows
คุณห้ามทำให้แอปพลิเคชันที่ผู้ใช้พึ่งพาอยู่พัง
ถ้ามีแอปไคลเอนต์ที่ “ห่วย” หรือทำงานผิด คุณก็ต้องใช้ทางอ้อม, shim, และ compatibility hack ทุกอย่างที่จำเป็นเพื่อทำให้มันทำงานได้ตามที่คาดหวัง
ต่อให้ต้องใส่โค้ดจัดการหน่วยความจำแบบพิเศษเพื่อให้ SimCity ทำงานได้ ก็ต้องทำเหมือนกัน
ลูกค้าของคุณพึ่งพาไคลเอนต์นั้นอยู่ และถ้ามันหยุดทำงานเพราะสิ่งที่คุณเปลี่ยน พวกเขาก็จะโทษคุณ
ที่นี่ทุกคนมัวแต่ยึดติดกับเรื่อง “ไม่ทำอะไรเลย” และ “การจัดการข้อผิดพลาด” แต่ดูเหมือนบทความจะอธิบายได้ไม่ดีว่ามันไม่ได้หมายถึงการกลืนทุกปัญหาแล้วร้อง kumbaya ไปเรื่อยๆ
บริบทจริงคือ การจำลองและความเข้ากันได้ สำหรับซอฟต์แวร์ที่ไม่มีวันถูกเปลี่ยน และมันต่างจากสถานการณ์ส่วนใหญ่มาก
ตัว Microsoft เองก็ทำให้เส้นแบ่งระหว่างสองอย่างนี้พร่ามัวอยู่บ่อยๆ เลยยิ่งชวนสับสน แต่สิ่งที่พูดถึงตรงนี้ไม่ใช่การจัดการข้อผิดพลาดทั่วไป
โดยเนื้อแท้แล้ว การจำลองคือการโกหก
Xbox ไม่มีทางต่อเครื่องพิมพ์จริงได้, บน Linux ก็กำลังรันเกม Windows, และ iPhone ปลอมๆ อาจจริงๆ แล้วเป็นเซิร์ฟเวอร์ ARM บน AWS
ถ้าจะโกหก ก็ต้องโกหกให้แนบเนียนพอ เพื่อให้แอปพลิเคชันเดิมยังทำงานต่อได้
เมื่อ Microsoft บอกว่าสามารถพอร์ตแอป Windows เดิมไปยัง Xbox ได้ แอป Windows นั้นมีคำมั่นอยู่แล้วว่าสามารถพิมพ์ได้ ดังนั้น Xbox ก็ต้องโกหกให้มันดูเหมือนว่าสิ่งนั้นเป็นไปได้
ในทางกลับกัน ถ้าโปรแกรมมี use-after-free อยู่ แต่คุณแอบอ้อมไปเงียบๆ ราวกับไม่มีบั๊ก แบบนั้นไม่ใช่การจัดการที่ถูกต้อง
เพราะไม่มีใครเคยสัญญาว่าแม้แต่ use-after-free เดิมๆ ก็จะต้องเข้ากันได้แบบระดับบั๊กบนแพลตฟอร์มใหม่
การตีความแบบนั้นเป็นเพียงการสมมติขึ้นมาเพราะมันสนับสนุนจุดยืนที่ต้องการ
ในความเป็นจริง เนื้อหาของบทความนี้ก็ใช้ได้เหมือนกันแม้ว่าผมจะเขียนโปรแกรมสำหรับ Xbox พรุ่งนี้แล้วพยายามสั่งพิมพ์
ไม่มีตรงไหนที่แบ่งแยกว่า behavior นี้ใช้เฉพาะกับซอฟต์แวร์เก่าและไม่มีคนดูแลเท่านั้น
ผมทั้งชอบและไม่ชอบแนวทางนี้
ตามสัญชาตญาณแล้ว ผมไม่ชอบการจัดการปัญหาด้วย malicious compliance
แต่อีกด้านหนึ่ง ถ้าเป้าหมายคือทำให้ผู้ใช้มากขึ้นสามารถรันซอฟต์แวร์ได้มากขึ้นบนแพลตฟอร์ม แม้ระบบพิมพ์จะพังอยู่ก็ตาม ผมก็เห็นด้วยเต็มที่ว่านี่เป็นการตัดสินใจที่ดี
สายคอมเมนต์นี้แปลกมาก
ชัดเจนว่ามีคนที่เกลียดทุกอย่างที่ Microsoft ทำ
แก่นของบล็อกคือทำให้กระบวนการนำแอป (UWP) ไปยัง XBOX เป็นไปอย่างไร้แรงเสียดทาน
ให้แอป “ใช้งานได้เลย” โดยไม่ต้องคอมไพล์ใหม่ ไม่ต้องมี
ifdefสกปรกๆ และไม่ต้องมีงานเพิ่ม จากนั้นนักพัฒนาค่อยปรับแอปให้เข้ากับแพลตฟอร์ม XBOX ก็ได้ประเด็นสำคัญคือนักพัฒนาสามารถนำแอปไปลง XBOX ได้โดยแทบไม่ต้องออกแรงเลย ซึ่งเป็นผลดีทั้งต่อแพลตฟอร์มและนักพัฒนา
สำหรับผู้ใช้ รายการเครื่องพิมพ์ที่ว่างเปล่าก็เป็นสัญญาณชัดเจนว่าไม่สามารถพิมพ์ได้
ที่สำคัญที่สุดคือแอปไม่ล้ม และไม่แสดงข้อความผิดพลาดแย่ๆ อย่าง “เกิดปัญหาขึ้น โปรดลองอีกครั้งภายหลัง”
ข้อความแบบนั้นไม่ได้เป็นประสบการณ์ผู้ใช้ที่ดีกว่าเลย
นักพัฒนาสามารถคอมไพล์แอปใหม่เพื่อใส่ข้อความที่อธิบายได้ชัดเจนกว่า แต่ถ้าทำแบบนั้นก็กลับไปที่จุดเริ่มต้นอีก
ตัวอย่างเช่น การกล่าวหาว่ากลืนข้อผิดพลาดนั้นไม่ใช่ประเด็นหลักเลย
ประเด็นสำคัญที่ฝ่ายวิจารณ์บทความนี้มองข้ามไปคือ ที่นี่ไม่มี สถานการณ์ยกเว้น ที่ควรต้องโยน exception
อุปกรณ์นี้ไม่ได้เชื่อมต่อกับเครื่องพิมพ์ และในกรณีนี้ก็เป็นเช่นนั้นตามนิยาม ดังนั้นแอปควรจัดการสถานการณ์นี้อย่างเรียบร้อย ไม่ใช่ล่ม
คงไม่มีใครโยน exception เพียงเพราะแล็ปท็อปเข้าถึงเครื่องพิมพ์ไม่ได้
หลังเพิ่มเครื่องพิมพ์ใน Settings แล้ว หากเปลี่ยนไปที่แท็บ “Color Profiles” ระหว่างที่กำลังติดตั้ง ถ้าจังหวะพอดี gnome-control-center จะล่ม
ดูให้ละเอียดจะพบว่ามันพยายามไล่รายการเครื่องพิมพ์ที่ใช้งานได้ โดยรู้อยู่ว่าต้องมีเครื่องพิมพ์ แต่ข้อมูลยังไม่มีอยู่ จึงล่มไปตรง ๆ
โชคดีที่แค่รอไม่กี่วินาทีให้ GNOME ติดตั้งเครื่องพิมพ์เสร็จ แล้วเปิด Settings ใหม่ก็พอ
ถึงอย่างนั้น หากเป็นแอปหลักที่ทำให้ผู้ใช้ปลายทางจากหลากหลายพื้นเพสามารถใช้งานเดสก์ท็อปได้อย่างเหมาะสม ในโลกอุดมคติมันก็ควรจะไม่ล่มเด็ดขาด
การนึกถึงกรณีขอบเขตเป็นเรื่องยาก และการจินตนาการว่าการจัดการกรณีขอบเขตเองก็ยังมีกรณีขอบเขตอีกนั้นยิ่งยากกว่า
ไม่ว่าจะถูกหรือผิด อย่างน้อยผู้เขียนก็คิดลึกไปไกลกว่าแค่ “เป็นแอปแย่ ๆ เลยควรเขียนใหม่ให้รองรับ Wayland”
ครั้งหนึ่งเคยมีมุมมองว่าการที่เบราว์เซอร์พยายามแสดงหน้าเว็บให้ดีที่สุดแม้ในโค้ด HTML จะมีข้อผิดพลาด เป็นกลยุทธ์ที่ยอดเยี่ยม
แนวคิดคือเดาเจตนาของผู้เขียนให้ได้มากที่สุดแล้วเดินหน้าต่อไป โดยมองว่าข้อผิดพลาดเป็นสิ่งไม่ดีและผู้ใช้ไม่ต้องการข้อผิดพลาด
นึกว่าเราได้เรียนรู้จากประสบการณ์นั้นแล้ว แต่ดูเหมือนจะไม่ใช่
ความพยายามใช้ XHTML มาแทนด้วยการตรวจสอบแบบเข้มงวดล้มเหลว และ HTML5 ที่ประสบความสำเร็จกลับเลือกถอยกลับไปกำหนดไว้อย่างชัดเจนว่าควรตีความลำดับที่ผิดทุกรูปแบบอย่างไร เพื่อให้เบราว์เซอร์อย่างน้อยทำงานได้สอดคล้องกัน
หากเบราว์เซอร์ยุคแรกแสดงข้อผิดพลาดทันทีที่เจอแท็กที่ไม่เคยเห็นมาก่อน ฟีเจอร์ใหม่ของเบราว์เซอร์ก็คงถูกทำให้ตายตั้งแต่เกิด
HelloและWorld!ดู แล้วเราจะรู้ว่าจากภูมิปัญญาที่ได้มานั้น ตอนนี้มันยังแสดงข้อผิดพลาดอยู่หรือไม่แต่ถ้ามันยังเรนเดอร์ได้ดีอยู่ นั่นอาจหมายความว่าเรายังต้องเรียนรู้อีกมาก หรืออาจหมายความว่าการจัดการข้อผิดพลาดบางประเภทอย่างสง่างาม และล่มในสถานการณ์ที่เป็นข้อยกเว้นจริง ๆ ก็มีเหตุผลของมัน
ต่อให้ย้อนเวลากลับไปปี 1999 ก็คงไม่ได้รู้สึกเสียดายอะไรนัก