2 คะแนน โดย GN⁺ 2024-02-17 | 2 ความคิดเห็น | แชร์ทาง WhatsApp
  • แม้จะไม่สามารถให้ความสามารถจริงได้เพราะข้อจำกัดของแพลตฟอร์มบางอย่างหรือเพราะ 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”

การแก้แบบใช้เส้นทางความล้มเหลวที่มีบันทึกไว้ในเอกสาร

  • ในเอกสารเดิมมีค่าที่คืนกลับ 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 ความคิดเห็น

 
GN⁺ 2024-02-17
ความเห็นจาก Lobste.rs
  • สิ่งที่พูดถึงตรงนี้คือ ฟังก์ชันการพิมพ์ต่าง ๆ ทำงานสอดคล้องกันราวกับว่า รองรับการพิมพ์อย่างสมบูรณ์ แต่ประหลาดตรงที่จะไม่มีเครื่องพิมพ์จริงให้พิมพ์เลย ซึ่งแบบนี้ก็อธิบายอะไรได้หลายอย่าง
    พักเรื่องมุกตลกไว้ก่อน ผมไม่เห็นด้วยกับการเขียนโปรแกรมเชิงป้องกันและประสบการณ์ผู้ใช้ที่ป้องกันเกินเหตุแบบนี้ เพราะมันทำให้ซอฟต์แวร์ไม่ทำหน้าที่ของตัวเองโดยที่ไม่รู้สาเหตุ และก็ไม่มีทางรู้ได้ด้วยว่าทำไม แอปควรจับข้อผิดพลาดแล้วสร้างข้อความที่เป็นมิตรกับผู้ใช้ถ้าทำได้ ไม่อย่างนั้นก็ควรแสดงข้อความผิดพลาดเดิมให้ผู้ใช้เห็น ถ้าเป็นงานเบื้องหลังก็ควรมี error log
    ผมยอมรับว่าบทความนี้เขียนจากมุมมองของนักพัฒนา API ไม่ใช่นักพัฒนาแอป ดังนั้นก็ควรทำเอกสารข้อผิดพลาดของ API และให้ข้อความผิดพลาดที่ฝั่งผู้เรียกสามารถนำไปจัดการต่อได้
    อีกอย่าง ผมก็ไม่ชอบการซ่อนปุ่มใน UI เพราะไม่มีสิทธิ์เข้าถึง ถ้ามีพื้นที่พอ ผมคิดว่าควรแสดงปุ่มไว้แต่ปิดการใช้งาน และเมื่อผู้ใช้เอาเมาส์ไปชี้ก็ควรมีข้อความบอกว่าจะเปิดใช้งานได้อย่างไร
    • ควรคำนึงด้วยว่าบทความนี้ไม่ได้มาจากมุมมองของนักพัฒนา API ทั่วไป แต่เป็นมุมมองของ นักพัฒนา Windows API จุดยืนของ Microsoft มาอย่างยาวนานคือ Windows API ต้องไม่ทำลายความเข้ากันได้แม้เวอร์ชันจะเปลี่ยนไป
    • นี่คือการถกเถียงแบบคลาสสิกเรื่อง กฎของโพสเทล / หลักการความทนทาน ทุกวันนี้เราต่างก็รู้แล้วว่าหลักการความทนทานนั้นล้าสมัย และได้ก่อให้เกิดสัตว์ประหลาดอย่าง HTML หรือโปรโตคอลและรูปแบบไฟล์ขนาดมหึมาที่เขียน parser ให้ถูกต้องได้ยาก
      โดยรวมแล้ว การบังคับให้ถูกต้องแม่นยำน่าจะดีกว่า แต่ถ้าคุณมีผู้ใช้เดิมอยู่ 1 พันล้านคน การพยายามไม่ทำให้มันพังคือเรื่องที่ฉลาดมาก และจากมุมผู้ใช้มันก็มีคุณค่าจริงในระดับระบบ เพราะมันแค่ใช้งานได้ สุดท้ายแล้วท่าทีที่ควรเป็นคือ fail fast แต่อย่าทำให้พังเป็นวงกว้าง
    • ในกรณีนี้ ดูมีแนวโน้มว่า API เดิมตัวหนึ่งไม่มี ข้อผิดพลาดที่มีการจัดทำเอกสารไว้ จึงอาจไม่มีการจัดการข้อผิดพลาดตั้งแต่แรก ถ้าคุณไม่อยากทำให้ซอฟต์แวร์เดิมพังทั้งหมด คุณก็ต้องโกหกแบบหวังดี
      เรื่องนี้ใกล้เคียงกับ เสถียรภาพของ ABI มากกว่า API บน Windows ซอฟต์แวร์ที่ build ไว้เมื่อ 15 ปีก่อนก็ควรยังทำงานต่อบนระบบปฏิบัติการใหม่ได้เท่าที่เป็นไปได้ เมื่อเปลี่ยน function signature ไม่ได้ คุณก็ต้องโกหกแบบหวังดีเพื่อให้ API ที่ไม่มีความหมายอีกต่อไปแล้วยังทำงานต่อได้
      ตัวอย่างเช่น API ยังทำเหมือนว่า Active Desktop มีอยู่จริง เพราะอีกทางเลือกหนึ่งคือทำให้ซอฟต์แวร์เก่า ๆ จำนวนมากพัง
    • เห็นด้วยอย่างยิ่ง ไม่มีอะไรน่าหงุดหงิดไปกว่าการต้องงมหาปุ่มที่ด้วยเหตุผลบางอย่างมองไม่เห็นบนหน้าจอของผม แต่มีคนข้าง ๆ หรือในบทสอนอธิบายว่ามันอยู่ตรงนั้น
      มันทำให้ไม่รู้ว่าฟีเจอร์นั้นหายไปแล้ว หรือแค่ถูกซ่อนไว้ในอีกหน้าหนึ่งระหว่างทาง
    • ถูกต้องที่แอปควรจับข้อผิดพลาดแล้วแสดงให้ผู้ใช้เห็น
      แต่ถ้าแอปไม่ทำ คนที่ใช้แอปนั้นก็จะโทษ Windows ไม่ใช่โทษแอป และแม้แต่ตอนแอปแครชก็ยังเป็นแบบนั้น
      นั่นจึงเป็นเหตุผลที่ Microsoft ต้องทำทางอ้อม ปล่อยให้งานพิมพ์หายไปเฉย ๆ ง่ายกว่ามาก แล้วผู้ใช้ก็จะคิดอยู่ครู่หนึ่งก่อนยอมรับว่า “อ้อ จริงสิ ไม่มีเครื่องพิมพ์นี่นา”
  • ถ้าฟังก์ชันติดตั้งเครื่องพิมพ์สามารถคืนค่าทันทีพร้อม result code ว่า ผู้ใช้ยกเลิกงาน ได้ มองจากฝั่งซัพพอร์ตแอปแล้ว แทบจะแน่นอนว่ามันจะนำไปสู่พฤติกรรมไม่พึงประสงค์ที่รับมือยากกว่ามาก เมื่อเทียบกับกรณีที่ API การพิมพ์โยนข้อยกเว้นตั้งแต่แรก
  • ช่วงนี้ผมใช้ AI-assisted programming เยอะ และเห็นเอเจนต์ชอบใส่การตรวจ if ว่าเป็น null หรือไม่บ่อยมาก ทุกครั้งที่เห็นก็จะนึกถึงบทความนี้
    ผมเลยสั่งเอเจนต์ว่าอย่าตรวจ null ซ้ำ ๆ แต่ให้ใช้ ฟังก์ชันที่ไม่ก่อผลเสีย แทน และให้ยืนยันครั้งเดียวตอนประกาศตัวแปรว่าค่านั้นจะไม่เป็น null แน่นอน
 
GN⁺ 2024-02-17
ความเห็นจาก 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 ว่าข้อผิดพลาดนั้นถูกโยนออกมาหรือถูกกลบทิ้ง
    • นี่ไม่ใช่ การกลบข้อผิดพลาด แต่ถ้าใช้สำนวนแบบ Linux จะใกล้กับการไม่ทำให้ user space พังมากกว่า
      การจัดการที่เป็นไปได้มีสองแบบ: แจ้งข้อผิดพลาดเพราะเครื่องนี้ไม่มีวันมีเครื่องพิมพ์ หรือคืนค่ารายการเครื่องพิมพ์ว่างเปล่าเพราะเครื่องนี้ไม่มีวันมีเครื่องพิมพ์
      ทั้งสองแบบถือว่าใช้ได้ แต่มีเพียงแบบเดียวที่ไม่ทำให้ user space พัง
    • ถ้าอ่านบทความจะเห็นว่าพูดถึง ประสบการณ์ผู้ใช้ มากกว่าประสบการณ์นักพัฒนา
      “การส่งเสียงไซเรนดัง ๆ” ดีสำหรับผู้ทดสอบหรือนักพัฒนา แต่ไม่ค่อยดีสำหรับผู้ใช้ปลายทาง
      จากมุมมองผู้ใช้ปลายทาง การกลบข้อผิดพลาดยังดีกว่าแอปล่ม
    • เวลาการเชื่อมต่ออินเทอร์เน็ตล้มเหลว แล้วหลายสิบปีก็ยังขึ้นแค่ข้อความว่าล้มเหลว น่าหงุดหงิดมาก
      ไม่มีทางรู้ว่าอะไรกันแน่ที่ล้มเหลว: ซอฟต์แวร์ในคอมของฉัน, ฮาร์ดแวร์, สายอีเทอร์เน็ตหรือ Wi‑Fi, สวิตช์, เราเตอร์, เคเบิลโมเด็ม, สายอินเทอร์เน็ตที่เข้าบ้าน, ISP, หรือระบบปลายทางที่พยายามเชื่อมต่อ เรื่องนี้แทบไม่ดีขึ้นเลย
  • แม้จะคาดได้ว่าจะมีคนตอบรับในเชิงลบ แต่งานสกปรกแบบนี้แหละคือเหตุผลที่เมื่อคลิกเอกสาร Word ’97 หรือเกมที่คอมไพล์ไว้สำหรับ MS-DOS เมื่อ 30 ปีก่อน มันยังเปิดขึ้นมาได้ตามที่คาดหวัง
    ความเข้ากันได้ย้อนหลังเป็นเรื่อง messy เสมอ จะทำแบบไม่สมบูรณ์หรือไม่ทำเลยก็อย่างใดอย่างหนึ่ง

    • ได้ยินท่อนนี้เกี่ยวกับ Microsoft บ่อย แต่ในเรื่องเกมกลับโชคไม่ค่อยดี
      สุดท้ายก็ต้องไปใช้ GOG และ GOG เองก็ลงเอยด้วยการทำ virtualization ของสภาพแวดล้อม
      เช่น ฉันไม่คิดว่าจะมีใครติดตั้ง Sim City ต้นฉบับบน Windows 11 แล้วสำเร็จ
    • ไฟล์ที่เขียนด้วย Word ’97 ต่อให้เมื่อ 25 ปีก่อนก็ยังไม่ได้เปิดออกมาตรงตามคาดเป๊ะบนคอมพิวเตอร์คนละเครื่อง 2 เครื่อง
      การจัดรูปแบบขึ้นอยู่กับ ไดรเวอร์เครื่องพิมพ์ ที่ติดตั้งไว้
      ในทางกลับกัน ไฟล์ LaTeX จาก 35 ปีก่อนยังใช้งานได้ดีจนถึงตอนนี้
    • ต่อให้เป็นไฟล์จากปี 1997 ก็ยังต้องอาศัยดวง และมีโอกาสสูงที่จะแสดงผิดไม่ว่าจะเปิดด้วยแอปออฟฟิศตัวไหน
      Word ไม่สามารถรับประกันได้ด้วยซ้ำว่าเมื่อเปิดซ้ำบนคอมเครื่องเดิมและเวอร์ชันเดิมแล้วรูปแบบจะไม่เปลี่ยน และนั่นคือเหตุผลที่ฉันไปเรียน TeX
      เรื่องเกมก็เหมือนกัน Microsoft ทำเกมจำนวนมากพังเมื่อปิด GFWL
      Dark Souls ภาคต้นฉบับเล่นไม่ได้จนกว่าจะถูกนำกลับมาวางขายบน Steam ใหม่ เลยต้องใช้เวอร์ชันเถื่อน
    • บน Windows 64 บิตรุ่นใหม่ ซอฟต์แวร์ MS-DOS ใช้งานไม่ได้
      จะมีหน้าต่างแจ้งว่า “ไม่รองรับ” ขึ้นมา และถึงจะไปดาวน์โหลด dosbox ได้ แต่นั่นก็ไม่ใช่การทำงาน “ตรงตามที่คาดหวังเป๊ะ”
    • อยากให้คำพูดนี้จริงมาก แต่ฉันลำบากมากในการพยายามรันเกมที่เก็บไว้ตั้งแต่สมัยเด็ก
      เกือบทุกครั้งสุดท้ายก็ต้องยอมแพ้หรือไม่ก็ซื้อใหม่ใน GOG ซึ่งจริง ๆ แล้วก็คือจ่ายเงินให้กับงานที่ช่วยจัดการปัญหาความเข้ากันได้เหล่านั้น
  • ไม่มีอะไรน่าหงุดหงิดไปกว่าหน้า UI ของอุปกรณ์ที่บอกเป็นนัยว่ามันน่าจะมีอยู่จริง แต่สุดท้ายกลับพบว่าตอนนี้ไม่มี
    สุดท้ายก็ต้องเสียเวลาไปถึงจะรู้ว่าอุปกรณ์เหล่านั้นไม่ได้รับการรองรับ และหน้าจอนั้นเป็นแค่ mockup ที่ใครสักคนทำไว้

    • ถึงอย่างนั้นก็คงยังน่าหงุดหงิดน้อยกว่าการที่แอปตายไปเฉยๆ
      ถ้าแอปไม่ได้เตรียมรับสถานการณ์ที่ไม่รองรับการพิมพ์ไว้ แล้วการพิมพ์โยนข้อยกเว้นออกมา แอปก็น่าจะล้มไปตรงนั้นเลย
      ถ้าเป็นแอปที่เตรียมรับกรณีไม่รองรับการพิมพ์ไว้ ก็ควรตรวจสอบก่อนผ่าน API แบบชัดเจนว่ารองรับการพิมพ์หรือไม่ และถ้าไม่รองรับก็ไม่ควรแสดง UI การพิมพ์เลย
      บทความนี้พูดถึงวิธีจัดการกับแอปที่ไม่ได้ตรวจสอบก่อน ไม่ใช่แอปแบบนั้น
      บั๊กอยู่ที่ตัวแอป แต่แพลตฟอร์มที่ดีควรพยายามทำให้แม้แต่แอปที่แย่ก็ยังทำงานต่อได้ แทนที่จะปล่อยให้มันตายไปเฉยๆ
    • ในกรณีนี้ ความรับผิดชอบอาจอยู่ที่ผู้เขียนซอฟต์แวร์มากกว่า Microsoft ที่ไม่ได้จัดการสถานการณ์เรื่องเครื่องพิมพ์
      หน้าที่ของ 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 และอื่นๆ
    • บางครั้งคุณก็ต้องทำ API ที่ไคลเอนต์เดิมใช้อยู่แล้วให้ใช้งานได้
      คุณย้อนเวลากลับไปคอมไพล์หรือเขียนไคลเอนต์ใหม่ไม่ได้
      ถ้าไคลเอนต์มีการตรวจ NotSupportedException อยู่แล้ว คุณก็ควรคืนค่านั้น แต่ถ้าไม่มี ก็ต้องใช้แนวทางอื่น
    • ถ้าอยู่ในทีม Windows แล้วยังมีทัศนคติแบบนั้น ถ้าผมเป็นผู้จัดการอาจไล่ออกไปแล้ว
      นี่คือ 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 ได้โดยแทบไม่ต้องออกแรงเลย ซึ่งเป็นผลดีทั้งต่อแพลตฟอร์มและนักพัฒนา
    สำหรับผู้ใช้ รายการเครื่องพิมพ์ที่ว่างเปล่าก็เป็นสัญญาณชัดเจนว่าไม่สามารถพิมพ์ได้
    ที่สำคัญที่สุดคือแอปไม่ล้ม และไม่แสดงข้อความผิดพลาดแย่ๆ อย่าง “เกิดปัญหาขึ้น โปรดลองอีกครั้งภายหลัง”
    ข้อความแบบนั้นไม่ได้เป็นประสบการณ์ผู้ใช้ที่ดีกว่าเลย
    นักพัฒนาสามารถคอมไพล์แอปใหม่เพื่อใส่ข้อความที่อธิบายได้ชัดเจนกว่า แต่ถ้าทำแบบนั้นก็กลับไปที่จุดเริ่มต้นอีก

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

    • ถ้าต้องการตัวอย่างสมัยใหม่ของผลิตภัณฑ์ที่ไม่ใช่ Microsoft และถูกใช้อย่างแพร่หลายซึ่งจัดการความพร้อมใช้งานของเครื่องพิมพ์ได้ไม่ดี ก็มี gnome-control-center
      หลังเพิ่มเครื่องพิมพ์ใน Settings แล้ว หากเปลี่ยนไปที่แท็บ “Color Profiles” ระหว่างที่กำลังติดตั้ง ถ้าจังหวะพอดี gnome-control-center จะล่ม
      ดูให้ละเอียดจะพบว่ามันพยายามไล่รายการเครื่องพิมพ์ที่ใช้งานได้ โดยรู้อยู่ว่าต้องมีเครื่องพิมพ์ แต่ข้อมูลยังไม่มีอยู่ จึงล่มไปตรง ๆ
      โชคดีที่แค่รอไม่กี่วินาทีให้ GNOME ติดตั้งเครื่องพิมพ์เสร็จ แล้วเปิด Settings ใหม่ก็พอ
      ถึงอย่างนั้น หากเป็นแอปหลักที่ทำให้ผู้ใช้ปลายทางจากหลากหลายพื้นเพสามารถใช้งานเดสก์ท็อปได้อย่างเหมาะสม ในโลกอุดมคติมันก็ควรจะไม่ล่มเด็ดขาด
      การนึกถึงกรณีขอบเขตเป็นเรื่องยาก และการจินตนาการว่าการจัดการกรณีขอบเขตเองก็ยังมีกรณีขอบเขตอีกนั้นยิ่งยากกว่า
      ไม่ว่าจะถูกหรือผิด อย่างน้อยผู้เขียนก็คิดลึกไปไกลกว่าแค่ “เป็นแอปแย่ ๆ เลยควรเขียนใหม่ให้รองรับ Wayland”
  • ครั้งหนึ่งเคยมีมุมมองว่าการที่เบราว์เซอร์พยายามแสดงหน้าเว็บให้ดีที่สุดแม้ในโค้ด HTML จะมีข้อผิดพลาด เป็นกลยุทธ์ที่ยอดเยี่ยม
    แนวคิดคือเดาเจตนาของผู้เขียนให้ได้มากที่สุดแล้วเดินหน้าต่อไป โดยมองว่าข้อผิดพลาดเป็นสิ่งไม่ดีและผู้ใช้ไม่ต้องการข้อผิดพลาด
    นึกว่าเราได้เรียนรู้จากประสบการณ์นั้นแล้ว แต่ดูเหมือนจะไม่ใช่

    • เบราว์เซอร์ก็ยังคงใช้กลยุทธ์นั้นอยู่
      ความพยายามใช้ XHTML มาแทนด้วยการตรวจสอบแบบเข้มงวดล้มเหลว และ HTML5 ที่ประสบความสำเร็จกลับเลือกถอยกลับไปกำหนดไว้อย่างชัดเจนว่าควรตีความลำดับที่ผิดทุกรูปแบบอย่างไร เพื่อให้เบราว์เซอร์อย่างน้อยทำงานได้สอดคล้องกัน
    • การที่เบราว์เซอร์ผ่อนปรนต่ออินพุตนี่เองที่ทำให้ เว็บสมัยใหม่ เป็นไปได้
      หากเบราว์เซอร์ยุคแรกแสดงข้อผิดพลาดทันทีที่เจอแท็กที่ไม่เคยเห็นมาก่อน ฟีเจอร์ใหม่ของเบราว์เซอร์ก็คงถูกทำให้ตายตั้งแต่เกิด
    • ลองป้อน Hello และ World! ดู แล้วเราจะรู้ว่าจากภูมิปัญญาที่ได้มานั้น ตอนนี้มันยังแสดงข้อผิดพลาดอยู่หรือไม่
      แต่ถ้ามันยังเรนเดอร์ได้ดีอยู่ นั่นอาจหมายความว่าเรายังต้องเรียนรู้อีกมาก หรืออาจหมายความว่าการจัดการข้อผิดพลาดบางประเภทอย่างสง่างาม และล่มในสถานการณ์ที่เป็นข้อยกเว้นจริง ๆ ก็มีเหตุผลของมัน
    • ตลอด 20 ปีที่ผ่านมา ความก้าวหน้าของซอฟต์แวร์ก็ไม่ได้ยอดเยี่ยมสักเท่าไร ยกเว้น LLM และแม้แต่ LLM ก็ถูกทำให้พังด้วยการเซ็นเซอร์
      ต่อให้ย้อนเวลากลับไปปี 1999 ก็คงไม่ได้รู้สึกเสียดายอะไรนัก
    • ชวนสงสัยว่าแท้จริงแล้วเราคิดว่าเราได้เรียนรู้อะไรกันแน่