2 คะแนน โดย GN⁺ 2024-02-17 | 2 ความคิดเห็น | แชร์ทาง WhatsApp

เมื่อ 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 ความคิดเห็น

 
GN⁺ 2024-02-17
ความคิดเห็นใน Hacker News
  • ความเห็นเกี่ยวกับ "การกลืนข้อผิดพลาดทิ้ง":

    • การซ่อนข้อผิดพลาดเป็นแนวปฏิบัติที่ไม่ดี
    • มันทำให้การพบบั๊กและการทดสอบยากขึ้น โดยซ่อนข้อบกพร่องของซอฟต์แวร์ไว้แทนที่จะแก้ปัญหา
    • panic ของภาษา Go เป็นวิธีที่ดีในการส่งสัญญาณดัง ๆ ว่าโปรแกรมเมอร์ทำพลาดระหว่างการทดสอบ
    • ถ้าแอกเตอร์ภายในระบบพยายามซ่อนข้อบกพร่องของตัวเอง จะยิ่งทำให้ระบุและแก้ปัญหาได้ยากมากขึ้น
  • ความเห็นเกี่ยวกับความเข้ากันได้แบบย้อนหลัง:

    • ความเข้ากันได้แบบย้อนหลังเป็นงานที่ยุ่งเหยิงเสมอ และเป็นการเลือกระหว่างทำให้สมบูรณ์แบบไม่ได้กับไม่ทำมันเลย
    • นี่จึงเป็นเหตุผลว่าทำไมเมื่อคลิกไฟล์จาก Word '97 หรือเกมสำหรับ MS-DOS มันยังเปิดได้ตามคาดแม้บนคอมพิวเตอร์ยุคปัจจุบัน
  • ข้อไม่พอใจเกี่ยวกับการออกแบบ UI:

    • UI ที่เสนออุปกรณ์ซึ่งอาจมีอยู่ได้เป็นสิ่งที่น่าหงุดหงิดมาก
    • มันทำให้เสียเวลาไปกับการค้นหาอุปกรณ์ที่จริง ๆ แล้วไม่รองรับ
  • การชี้ว่า Microsoft ไม่ได้เรียนรู้:

    • ผ่านไป 30 ปี Microsoft ก็ยังทำผิดพลาดแบบเดิมซ้ำอยู่
    • การที่แอปแสดงรายการว่างเปล่าเมื่อพยายามค้นหาเครื่องพิมพ์ คือการทำปัญหาเดิมซ้ำอีกครั้ง
  • ความเห็นเกี่ยวกับการที่ Xbox ไม่รองรับการพิมพ์:

    • Xbox ไม่รองรับการพิมพ์เพราะ Microsoft กำหนดให้เป็นแบบนั้น
    • ในเชิงฮาร์ดแวร์ มันมีความสามารถในการพิมพ์เทียบเท่ากับอุปกรณ์ Windows อื่น ๆ
    • "ทางแก้" แบบนี้คือปัญหาที่เกิดจากการตัดสินใจที่ไม่มีเหตุผลของ Microsoft เอง
  • คำแนะนำเกี่ยวกับการใช้ API:

    • เป็นเรื่องถูกต้องที่คอมโพเนนต์ควรเจอปัญหาก่อนผู้ใช้ แต่ไม่เห็นด้วยกับวิธีที่ผู้เขียนใช้ถ้อยคำ
    • การที่ฟังก์ชันพิมพ์โยน NotSupported­Exception ไม่ใช่เรื่องผิด
    • สิ่งที่ผู้เขียนอธิบายคือแฮ็กเพื่อรองรับไคลเอนต์ที่ไม่ได้มาตรฐาน
  • ความรู้สึกต่อ "การทำตามอย่างประชดประชัน":

    • ทั้งชอบและไม่ชอบวิธีจัดการปัญหาแบบนี้
    • แต่ถ้าเป้าหมายคือทำให้มีผู้ใช้มากขึ้นบนแพลตฟอร์มและรันซอฟต์แวร์ได้มากขึ้น วิธีนี้ก็ดี
  • ความเห็นเชิงบวกด้านความปลอดภัย:

    • การเพิกเฉยต่อการเรียก API อย่างถูกต้องช่วยป้องกันไม่ให้โปรแกรมทำพฤติกรรมที่เป็นอันตรายมากกว่าเดิม
  • การทบทวนกลยุทธ์ของเบราว์เซอร์:

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

    • มีประเด็นที่นักวิจารณ์มองข้ามไปในสถานการณ์ที่ไม่จำเป็นต้องโยนข้อยกเว้น
    • หากอุปกรณ์ (เครื่องพิมพ์) ไม่ได้เชื่อมต่อ แอปก็ควรจัดการสถานการณ์นั้นอย่างเรียบร้อย และไม่ควรแครช
 
GN⁺ 4 일 전
ความเห็นจาก 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 แน่นอน