28 ก.พ. 2568·อ่าน 2 นาที

รูปแบบสัญญาข้อผิดพลาดของ API เพื่อข้อความที่ชัดเจนและเป็นมิตร

ออกแบบสัญญาข้อผิดพลาดของ API โดยใช้รหัสที่เสถียร ข้อความแปลตามภาษา และคำแนะนำที่เหมาะกับ UI เพื่อลดภาระฝ่ายสนับสนุนและช่วยให้ผู้ใช้กู้สถานะได้อย่างรวดเร็ว

รูปแบบสัญญาข้อผิดพลาดของ API เพื่อข้อความที่ชัดเจนและเป็นมิตร

ทำไมข้อผิดพลาด API ที่ไม่ชัดเจนถึงสร้างปัญหาให้ผู้ใช้จริงๆ

ข้อผิดพลาด API ที่กำกวมไม่ใช่แค่ความรำคาญเชิงเทคนิค แต่มันคือช่วงเวลาที่ประสบการณ์ผู้ใช้ขาดตอน ผู้ใช้ติดขัด คาดเดาวิธีแก้ แล้วมักจะยอมแพ้ ข้อความเดียวอย่าง "Something went wrong" กลายเป็นตั๋วซัพพอร์ตเพิ่ม อัตราการยกเลิกบริการสูงขึ้น และบั๊กที่ดูเหมือนไม่เคยแก้ได้จริง

รูปแบบที่พบบ่อยคือ: ผู้ใช้พยายามบันทึกฟอร์ม UI แสดง toast แบบทั่วไป แต่บันทึกแบ็กเอนด์บอกสาเหตุจริง (เช่น "unique constraint violation on email") ผู้ใช้ไม่รู้ว่าควรเปลี่ยนอะไร ฝ่ายซัพพอร์ตช่วยไม่ได้เพราะไม่มีรหัสที่ค้นหาได้ในล็อก ปัญหาเดิมถูกรายงานซ้ำในภาพหน้าจอและข้อความต่างๆ และไม่มีวิธีจัดกลุ่มที่ชัดเจน

รายละเอียดเชิงวิศวกรกับความต้องการของผู้ใช้ไม่เหมือนกัน วิศวกรต้องการบริบทล้มเหลวที่แม่นยำ (ฟิลด์ไหน บริการไหน เวลาออก) ส่วนผู้ใช้ต้องการขั้นตอนถัดไปที่ชัดเจน: "อีเมลนี้ถูกใช้งานแล้ว ลองเข้าสู่ระบบ หรือลองใช้อีเมลอื่น" ผสมสองสิ่งนี้มักจบลงด้วยการเปิดเผยข้อมูลภายในหรือข้อความที่ไร้ประโยชน์

นั่นคือเหตุผลที่ต้องมีสัญญาข้อผิดพลาดของ API เป้าหมายไม่ใช่ "ให้มีข้อผิดพลาดมากขึ้น" แต่เป็นโครงสร้างที่สอดคล้องเพื่อให้:

  • ไคลเอ็นต์ตีความความล้มเหลวได้อย่างน่าเชื่อถือในทุก endpoint
  • ผู้ใช้เห็นข้อความที่ปลอดภัย เป็นภาษาง่าย และช่วยให้กู้สถานะได้
  • ฝ่ายซัพพอร์ตและ QA ระบุปัญหาได้ชัดเจนด้วยรหัสที่เสถียร
  • วิศวกรได้รับข้อมูลวินิจฉัยโดยไม่เปิดเผยข้อมูลสำคัญ

ความสม่ำเสมอคือทั้งหมด หาก endpoint หนึ่งคืนค่า error: "Invalid" และอีก endpoint คืน message: "Bad request" UI จะนำทางผู้ใช้ไม่ได้ และทีมของคุณวัดอะไรไม่ได้ สัญญาที่ชัดเจนทำให้ข้อผิดพลาดคาดเดาได้ ค้นหาได้ และแก้ไขได้ง่ายขึ้น แม้สาเหตุเบื้องหลังจะเปลี่ยน

สัญญาข้อผิดพลาดที่สอดคล้องในทางปฏิบัติหมายถึงอะไร

สัญญาข้อผิดพลาดของ API คือคำสัญญา: เมื่อเกิดความล้มเหลว API ของคุณตอบกลับในรูปแบบที่คุ้นเคยด้วยฟิลด์และรหัสที่คาดเดาได้ ไม่ว่าจะเป็น endpoint ใดก็ตาม

มันไม่ใช่การปล่อยข้อมูลสำหรับดีบัก และไม่ใช่แทนที่ล็อก สัญญาคือสิ่งที่แอพไคลเอ็นต์พึ่งพาได้อย่างปลอดภัย ล็อกเก็บสแตกเทรซ รายละเอียด SQL และข้อมูลสำคัญอื่นๆ

ในทางปฏิบัติ สัญญาที่ดีคงสิ่งต่อไปนี้ไว้ให้เสถียร: รูปร่างการตอบกลับข้าม endpoint (สำหรับ 4xx และ 5xx), รหัสเครื่องอ่านที่ไม่เปลี่ยนความหมาย, และข้อความสำหรับผู้ใช้ที่ปลอดภัย มันยังช่วยฝ่ายซัพพอร์ตด้วยการรวม request/trace identifier และอาจมีคำแนะนำ UI ง่ายๆ เช่น ควรลองใหม่หรือแก้ฟิลด์ใด

ความสม่ำเสมอเกิดขึ้นได้เมื่อคุณตัดสินใจว่าจะบังคับใช้ที่ไหน ทีมมักเริ่มจากจุดบังคับใช้หนึ่งจุดแล้วขยาย: API gateway ที่ทำให้ข้อผิดพลาดเป็นมาตรฐาน, middleware ที่ห่อความล้มเหลวที่ไม่ได้จับ, ไลบรารีแชร์ที่สร้างออบเจ็กต์ข้อผิดพลาดเดียวกัน, หรือ exception handler ระดับเฟรมเวิร์กในแต่ละบริการ

ความคาดหวังหลักง่ายๆ: ทุก endpoint คืนหรือรูปแบบสำเร็จหรือสัญญาข้อผิดพลาดสำหรับทุกโหมดความล้มเหลว นั่นรวมการตรวจสอบความถูกต้อง การพิสูจน์ตัวตน ขีดจำกัดอัตรา เวลาออก และการล้มเหลวจาก upstream

รูปร่างการตอบข้อผิดพลาดที่เรียบง่ายแต่ขยายได้

สัญญาข้อผิดพลาดที่ดีต้องกระชับ คาดเดาได้ และเป็นประโยชน์ทั้งกับคนและซอฟต์แวร์ เมื่อไคลเอ็นต์หาฟิลด์เดิมได้เสมอ ฝ่ายซัพพอร์ตจะหยุดเดา และ UI จะให้คำแนะนำที่ชัดเจน

นี่คือรูปแบบ JSON ขั้นต้นที่ใช้ได้กับผลิตภัณฑ์ส่วนใหญ่ (และขยายได้เมื่อเพิ่ม endpoint):

{
  "status": 400,
  "code": "AUTH.INVALID_EMAIL",
  "message": "Enter a valid email address.",
  "details": {
    "fields": {
      "email": "invalid_email"
    },
    "action": "fix_input",
    "retryable": false
  },
  "trace_id": "01HZYX8K9Q2..."
}

เพื่อให้สัญญาเสถียร จัดการแต่ละส่วนเป็นคำสัญญาแยกกัน:

  • status สำหรับพฤติกรรม HTTP และหมวดกว้างๆ
  • code เป็นตัวระบุเครื่องอ่านที่เสถียร (แกนกลางของสัญญาข้อผิดพลาดของคุณ)
  • message คือข้อความปลอดภัยสำหรับ UI (และแปลได้ภายหลัง)
  • details เก็บคำแนะนำเชิงโครงสร้าง: ปัญหาระดับฟิลด์ สิ่งที่ควรทำต่อไป และว่าควรลองใหม่หรือไม่
  • trace_id ให้ฝ่ายซัพพอร์ตค้นหาความล้มเหลวฝั่งเซิร์ฟเวอร์โดยไม่เปิดเผยข้อมูลภายใน

แยกเนื้อหาที่มองเห็นได้สำหรับผู้ใช้จากข้อมูลดีบักภายใน หากต้องการวินิจฉัยเพิ่มเติม ให้บันทึกลงล็อกเชื่อมกับ trace_id (ไม่ใส่ในการตอบกลับ) จะหลีกเลี่ยงการรั่วไหลของข้อมูลที่ละเอียดอ่อน แต่ยังช่วยให้สืบหาปัญหาได้ง่าย

สำหรับข้อผิดพลาดของฟิลด์ details.fields เป็นรูปแบบง่ายๆ: คีย์ตรงกับชื่ออินพุต ค่าเก็บเหตุผลสั้นๆ เช่น invalid_email หรือ too_short เพิ่มคำแนะนำเมื่อตรงส่วนที่จะช่วยได้ สำหรับ timeout ใช้ action: "retry_later" ก็เพียงพอ สำหรับการขัดข้องชั่วคราว retryable: true ช่วยให้ไคลเอ็นต์ตัดสินใจว่าจะโชว์ปุ่มลองใหม่หรือไม่

หมายเหตุก่อนเริ่ม: บางทีมห่อข้อผิดพลาดในวัตถุ error (เช่น { "error": { ... } }) ขณะที่บางทีมเก็บฟิลด์ไว้ระดับบนสุด วิธีใดก็ได้ทำงานได้ สิ่งที่สำคัญคือเลือกซองข้อมูลหนึ่งแบบแล้วใช้ให้สม่ำเสมอทุกที่

รหัสข้อผิดพลาดที่เสถียร: รูปแบบที่ไม่ทำให้ไคลเอ็นต์พัง

รหัสข้อผิดพลาดที่เสถียรคือกระดูกสันหลังของสัญญาข้อผิดพลาด API มันทำให้แอปแดชบอร์ดและทีมซัพพอร์ตรู้จักปัญหาได้ แม้คุณจะปรับข้อความ เพิ่มฟิลด์ หรือปรับ UI

นิยามการตั้งชื่อที่ใช้งานได้จริงคือ:

DOMAIN.ACTION.REASON

ตัวอย่าง: AUTH.LOGIN.INVALID_PASSWORD, BILLING.PAYMENT.CARD_DECLINED, PROFILE.UPDATE.EMAIL_TAKEN รักษาโดเมนให้สั้นและคุ้นเคย (AUTH, BILLING, FILES) ใช้คำกริยาที่อ่านชัด (CREATE, UPDATE, PAY)

ปฏิบัติต่อรหัสเหมือน endpoint: เมื่อเผยแพร่แล้วอย่าเปลี่ยนความหมาย ข้อความที่แสดงแก่ผู้ใช้สามารถปรับให้ดีขึ้นเมื่อเวลาผ่านไป แต่รหัสควรคงที่เพื่อไม่ให้ไคลเอ็นต์พังและให้การวิเคราะห์ยังคงสะอาด

ควรกำหนดด้วยว่ารหัสใดเป็นสาธารณะและรหัสใดเป็นภายใน กฎง่ายๆ: รหัสสาธารณะต้องปลอดภัยสำหรับการแสดง ผลิตภัณฑ์และเอกสาร และใช้โดย UI รหัสภายในเก็บไว้ในล็อกเพื่อดีบัก รหัสสาธารณะหนึ่งรายการอาจแม็พกับสาเหตุภายในหลายแบบ โดยเฉพาะเมื่อผู้ให้บริการภายนอกล้มเหลวด้วยหลายวิธี

การยกเลิกใช้ทำได้ดีที่สุดเมื่อเรียบง่าย หากต้องแทนที่รหัส อย่าใช้ซ้ำแบบเงียบๆ ให้แนะนำรหัสใหม่และประกาศว่ารหัสเก่าถูกเลิกใช้ ให้มีช่วงเวลาที่ทั้งสองอาจปรากฏ หากรวมฟิลด์ deprecated_by ให้ชี้ไปยังรหัสใหม่ (ไม่ใช่ URL)

ตัวอย่าง: รักษา BILLING.PAYMENT.CARD_DECLINED ไว้ แม้ว่าคุณจะปรับข้อความ UI และแยกเป็น "ลองบัตรอื่น" กับ "ติดต่อธนาคาร" ความหมายของรหัสยังคงเดิม ขณะที่คำแนะนำพัฒนาได้

ข้อความแปลโดยไม่เสียความสอดคล้อง

ทดสอบสถานการณ์ล้มเหลวจริง
ต้นแบบการสมัครและการชำระเงินที่มีข้อความผู้ใช้ชัดเจนและการวินิจฉัยที่ปลอดภัย
ลอง AppMaster

การแปลจะยุ่งยากเมื่อ API คืนประโยคเต็มและไคลเอ็นต์ใช้ข้อความนั้นเป็นตรรกะ วิธีที่ดีกว่าคือรักษาสัญญาให้เสถียรแล้วแปลข้อความปลายทาง ไฟล์แปลช่วยให้ความหมายเดิมข้ามภาษา อุปกรณ์ และเวอร์ชันแอปยังคงเหมือนกัน

ก่อนอื่น ตัดสินใจว่าการแปลเก็บที่ไหน หากต้องการแหล่งเดียวสำหรับเว็บ มือถือ และเครื่องมือซัพพอร์ต ข้อความจากเซิร์ฟเวอร์ช่วยได้ หาก UI ต้องการควบคุมโทนและเลย์เอาต์ ไคลเอ็นต์เก็บการแปลมักง่ายกว่า หลายทีมใช้วิธีผสม: API คืนรหัสคงที่บวก message key และพารามิเตอร์ ไคลเอ็นต์เลือกข้อความแสดงผลที่เหมาะสม

สำหรับสัญญาข้อผิดพลาด คีย์ข้อความมักปลอดภัยกว่าประโยคฝัง API API อาจคืน message_key: "auth.too_many_attempts" พร้อม params: {"retry_after_seconds": 300} UI แปลและฟอร์แมตรายละเอียดโดยไม่เปลี่ยนความหมาย

การจัดรูปพจน์และ fallback สำคัญกว่าที่คิด ใช้ระบบ i18n ที่รองรับกฎพจน์ตามโลคัล ไม่ใช่แค่แบบอังกฤษ "1 vs many" กำหนดลำดับ fallback (เช่น: fr-CA -> fr -> en) เพื่อให้สตริงที่ขาดไม่แสดงหน้าจอว่าง

ข้อควรระวังคือให้ถือว่าข้อความที่แปลเป็นเนื้อหาสำหรับผู้ใช้โดยเคร่งครัด อย่าใส่สแตกเทรซ ไอดีภายใน หรือรายละเอียด "ทำไมถึงล้มเหลว" ลงในสตริงแปล เก็บรายละเอียดสำคัญไว้ในฟิลด์ที่ไม่แสดง (หรือในล็อก) และให้ข้อความผู้ใช้ที่ปลอดภัยและปฏิบัติได้

เปลี่ยนความล้มเหลวของแบ็กเอนด์เป็นคำแนะนำที่ผู้ใช้ทำตามได้

ส่ง API ของคุณไปได้ทุกที่
ปรับใช้ไปยัง AppMaster Cloud หรือ AWS Azure หรือ Google Cloud ของคุณเอง
สร้างแอป

ข้อผิดพลาดส่วนใหญ่มีประโยชน์กับวิศวกร แต่บ่อยครั้งมันไปปรากฏบนหน้าจอเป็น "Something went wrong" สัญญาข้อผิดพลาดที่ดีเปลี่ยนความล้มเหลวให้เป็นขั้นตอนถัดไปที่ชัดเจนโดยไม่รั่วไหลข้อมูลสำคัญ

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

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

คำแนะนำฟิลด์สำคัญกว่าข้อความยาว เมื่อแบ็กเอนด์รู้ว่าฟิลด์ไหนล้มเหลว ให้คืน pointer ที่อ่านด้วยเครื่อง (เช่น ชื่อฟิลด์ email หรือ card_number) และเหตุผลสั้นๆ ที่ UI จะแสดงแบบอินไลน์ หากมีหลายฟิลด์ผิด ให้คืนทั้งหมดเพื่อให้ผู้ใช้แก้ได้ในครั้งเดียว

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

รวมบริบทการแก้ปัญหาอย่างปลอดภัยอย่างสม่ำเสมอ: trace_id, timestamp ถ้ามี และขั้นตอนแนะนำเช่นเวลาที่ควรลองใหม่ ด้วยวิธีนี้ timeout ของผู้ให้บริการการชำระเงินจะแสดง "บริการการชำระเงินช้า กรุณาลองอีกครั้ง" พร้อมปุ่มลองใหม่ ขณะที่ฝ่ายซัพพอร์ตใช้ trace_id เดียวกันค้นหาความล้มเหลวฝั่งเซิร์ฟเวอร์ได้

ขั้นตอนทีละขั้น: นำสัญญาไปใช้ทั่วทั้งระบบ

การนำสัญญาข้อผิดพลาดของ API ไปใช้ดีที่สุดเมื่อถือเป็นการเปลี่ยนแปลงผลิตภัณฑ์ขนาดเล็ก ไม่ใช่แค่รีแฟกเตอร์ ทำแบบค่อยเป็นค่อยไป และให้ทีมซัพพอร์ตกับ UI มีส่วนร่วมตั้งแต่ต้น

ลำดับการนำไปใช้ที่ช่วยปรับปรุงข้อความผู้ใช้ได้เร็วโดยไม่ทำให้ไคลเอ็นต์พัง:

  1. สำรวจสิ่งที่มีอยู่ (จัดกลุ่มตามโดเมน) ส่งออกการตอบข้อผิดพลาดจริงจากล็อกและจัดกลุ่มตามโดเมน เช่น auth, signup, billing, file upload, permissions มองหาการซ้ำ ข้อความไม่ชัด และสถานที่ที่ความล้มเหลวเหมือนกันมีหลายรูปแบบ
  2. กำหนดสคีมาและแชร์ตัวอย่าง เอกสารรูปร่างการตอบกลับ ฟิลด์ที่ต้องมี และตัวอย่างต่อโดเมน รวมรหัสคงที่ คีย์ข้อความสำหรับแปล และส่วนคำแนะนำสำหรับ UI (ถ้ามี)
  3. ลงมือทำตัวแม็พข้อผิดพลาดศูนย์กลาง ใส่การฟอร์แมตไว้ที่เดียวเพื่อให้ทุก endpoint คืนโครงสร้างเดียวกัน ในแบ็กเอนด์ที่สร้างโดยอัตโนมัติ (หรือแบ็กเอนด์แบบ no-code อย่าง AppMaster) มักหมายถึงขั้นตอนเดียวที่แชร์ว่า "แม็พข้อผิดพลาดเป็นการตอบกลับ" ที่ทุก endpoint หรือกระบวนการเรียก
  4. อัปเดต UI ให้ตีความรหัสและแสดงคำแนะนำ ให้ UI พึ่งพารหัส ไม่ใช่ข้อความ ใช้รหัสตัดสินใจว่าจะเน้นฟิลด์ แสดงการกระทำลองใหม่ หรืแนะนำการติดต่อซัพพอร์ต
  5. เพิ่มการล็อกพร้อม trace_id ที่ซัพพอร์ตขอได้ สร้าง trace_id สำหรับทุกคำขอ บันทึกลงล็อกฝั่งเซิร์ฟเวอร์พร้อมรายละเอียดความล้มเหลวดิบ และคืนให้ในการตอบข้อผิดพลาดเพื่อให้ผู้ใช้คัดลอกได้

หลังการรอบแรก รักษาสัญญาให้เสถียรด้วยเอกสารน้ำหนักเบา: พจนานุกรมรหัสข้อผิดพลาดต่อโดเมน ไฟล์แปลสำหรับข้อความที่แสดงให้ผู้ใช้ ตารางแม็พจากรหัส -> คำแนะนำ UI/การกระทำถัดไป และ playbook ฝ่ายซัพพอร์ตที่เริ่มจาก "ส่ง trace_id มาให้เรา"

หากมีไคลเอ็นต์เก่ารักษาฟิลด์เก่าไว้ชั่วระยะเวลาการเลิกใช้ แต่หยุดสร้างรูปแบบหนึ่ง-off ใหม่ทันที

ความผิดพลาดทั่วไปที่ทำให้การซัพพอร์ตยากขึ้น

หลีกเลี่ยงการเขียนทับแบ็กเอนด์ที่ยุ่งเหยิง
สร้างซอร์สโค้ดที่พร้อมใช้งานในโปรดักชันและสร้างใหม่อย่างสะอาดเมื่อต้องการเปลี่ยนแปลง
เริ่มสร้าง

ปัญหาส่วนใหญ่ของฝ่ายซัพพอร์ตไม่ได้มาจาก "ผู้ใช้แย่" แต่มาจากความกำกวม เมื่อสัญญาข้อผิดพลาดไม่สอดคล้อง ทุกทีมก็คิดเองทำเอง และผู้ใช้เจอข้อความที่ทำอะไรไม่ได้

กับดักที่พบบ่อยคือการถือว่า HTTP status code คือเรื่องทั้งหมด "400" หรือ "500" บอกอะไรผู้ใช้ได้น้อยมาก รหัสสถานะช่วยเรื่องการขนส่งและการจำแนกกว้าง แต่คุณยังต้องการรหัสระดับแอปที่มีความหมายคงที่ข้ามเวอร์ชัน

ข้อผิดพลาดอีกอย่างคือการเปลี่ยนความหมายของรหัสตามเวลา หาก PAYMENT_FAILED เคยหมายถึง "บัตรถูกปฏิเสธ" แล้วต่อมาหมายถึง "Stripe ล่ม" UI และเอกสารจะผิดโดยไม่มีใครสังเกต ฝ่ายซัพพอร์ตจึงได้ตั๋วเช่น "ลองบัตรสามใบแล้วยังล้ม" ในขณะที่ปัญหาจริงคือการล่มของผู้ให้บริการ

การคืนข้อความ exception ดิบ (หรือยิ่งกว่านั้น สแตกเทรซ) ก็ล่อตาล่อใจเพราะทำเร็ว แต่มักไม่เป็นประโยชน์กับผู้ใช้และอาจรั่วไหลข้อมูลภายใน เก็บดีบักดิบในล็อก อย่าใส่ในการตอบกลับที่แสดงต่อผู้ใช้

รูปแบบที่สร้างเสียงรบกวนได้บ่อย:

  • ใช้รหัสจับหมดเช่น UNKNOWN_ERROR บ่อยไป จะทำให้ไม่สามารถแนะนำผู้ใช้ได้
  • สร้างรหัสมากเกินไปโดยไม่มีระบบจัดหมวดหมู่ ทำให้แดชบอร์ดและ playbook ยากดูแล
  • ผสมข้อความสำหรับผู้ใช้กับข้อมูลดีบักในฟิลด์เดียวกัน ทำให้การแปลและคำแนะนำ UI เปราะบาง

กฎง่ายๆ: รหัสหนึ่งรหัสต่อการตัดสินใจของผู้ใช้ หากผู้ใช้แก้ได้โดยเปลี่ยนอินพุต ให้ใช้รหัสเฉพาะพร้อมคำแนะนำชัดเจน ถ้าแก้ไม่ได้ (เช่น การล่มของผู้ให้บริการ) ให้รักษารหัสเสถียรและคืนข้อความปลอดภัยพร้อมการกระทำเช่น "ลองอีกครั้งทีหลัง" และ correlation ID สำหรับซัพพอร์ต

เช็คลิสต์ก่อนปล่อย

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

  • รูปร่างเดียวกันทุกที่: ทุก endpoint (รวม auth, webhooks, file uploads) คืนซองข้อผิดพลาดเดียวกัน
  • รหัสที่เสถียรและมีเจ้าของ: แต่ละรหัสมีเจ้าของชัดเจน (Payments, Auth, Billing) อย่าใช้รหัสซ้ำกับความหมายอื่น
  • ข้อความปลอดภัยและแปลได้: ข้อความที่แสดงสั้นและไม่รวมความลับ (โทเคน ข้อมูลบัตรเต็ม ข้อความ SQL ดิบ สแตกเทรซ)
  • คำแนะนำ UI ที่ชัดเจน: สำหรับประเภทความล้มเหลวยอดนิยม UI แสดงการกระทำถัดไปหนึ่งอย่างที่ชัดเจน (ลองอีกครั้ง อัปเดตฟิลด์ ใช้วิธีการชำระเงินอื่น ติดต่อซัพพอร์ต)
  • ติดตามได้สำหรับซัพพอร์ต: ทุกการตอบข้อผิดพลาดมี trace_id (หรือเทียบเท่า) ที่ซัพพอร์ตขอได้ และระบบล็อก/มอนิเตอร์หาข้อมูลเต็มได้อย่างรวดเร็ว

ทดสอบฟลว์สมจริงจบหนึ่งรอบ: ฟอร์มที่มีอินพุตไม่ถูกต้อง เซสชันหมดอายุ ขีดจำกัดอัตรา และการล้มของ third-party หากคุณอธิบายความล้มเหลวได้ในหนึ่งประโยคและชี้ไปยัง trace_id ในล็อก คุณก็พร้อมปล่อย

ตัวอย่าง: กรณีสมัครและการชำระเงินที่ผู้ใช้กู้คืนได้

สร้าง API ที่มีข้อผิดพลาดชัดเจน
ออกแบบการตอบข้อผิดพลาดแบบเดียวกันและใช้ซ้ำได้ในทุก endpoint
ลอง AppMaster

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

สมัคร: ข้อผิดพลาดการตรวจสอบที่ผู้ใช้แก้ได้

ผู้ใช้ป้อนอีเมลอย่าง sam@ แล้วกดสมัคร API คืนรหัสเสถียรและคำแนะนำระดับฟิลด์ ดังนั้นไคลเอ็นต์ทุกตัวจะไฮไลต์อินพุตเดียวกัน

{
  "error": {
    "code": "AUTH.EMAIL_INVALID",
    "message": "Enter a valid email address.",
    "i18n_key": "auth.email_invalid",
    "params": { "field": "email" },
    "ui": { "field": "email", "action": "focus" },
    "trace_id": "4f2c1d..."
  }
}

บนเว็บ แสดงข้อความใต้ช่องอีเมล บนมือถือ โฟกัสฟิลด์อีเมลและโชว์แบนเนอร์เล็กๆ ในอีเมลคุณสามารถบอกว่า: "เราไม่สามารถสร้างบัญชีได้เพราะที่อยู่อีเมลไม่สมบูรณ์" รหัสเดียว ความหมายเดียวกัน

การชำระเงิน: ล้มเหลวพร้อมคำอธิบายที่ปลอดภัย

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

{
  "error": {
    "code": "PAYMENT.DECLINED",
    "message": "Your payment was declined. Try another card or contact your bank.",
    "i18n_key": "payment.declined",
    "params": { "retry_after_sec": 0 },
    "ui": { "action": "show_payment_methods" },
    "trace_id": "b9a0e3..."
  }
}

ฝ่ายซัพพอร์ตขอ trace_id แล้วตรวจสอบว่าถูกคืนรหัสใด เป็น decline แบบถาวรหรือลองใหม่ได้ เกี่ยวกับบัญชีและจำนวนเงินที่พยายาม และว่า UI ได้รับคำแนะนำหรือไม่

นี่คือจุดที่สัญญาข้อผิดพลาดของ API ให้ผลตอบแทน: เว็บ iOS/Android และอีเมลของคุณคงความสอดคล้อง แม้ผู้ให้บริการภายนอกหรือรายละเอียดภายในจะเปลี่ยน

การทดสอบและมอนิเตอร์สัญญาตลอดเวลา

ทำให้ปัญหาติดตามได้ง่าย
เพิ่ม trace ID ในการตอบกลับเพื่อให้ฝ่ายสนับสนุนค้นหาเหตุการณ์ฝั่งเซิร์ฟเวอร์ได้
เริ่มใช้งาน

สัญญาข้อผิดพลาดของ API ไม่ได้ "เสร็จ" เมื่อปล่อย มันเสร็จเมื่อรหัสข้อผิดพลาดเดียวกันยังคงนำไปสู่การกระทำผู้ใช้เหมือนเดิมแม้จะผ่านการรีแฟกเตอร์และฟีเจอร์ใหม่หลายเดือน

เริ่มจากทดสอบจากภายนอก เหมือนไคลเอ็นต์จริง สำหรับรหัสข้อผิดพลาดแต่ละรายการ เขียนคำขอที่กระตุ้นมันอย่างน้อยหนึ่งรายการและยืนยันพฤติกรรมที่คุณพึ่งพาจริงๆ: สถานะ HTTP, รหัส, คีย์แปล, และฟิลด์คำแนะนำ UI (เช่น ฟิลด์ไหนจะไฮไลต์)

ชุดทดสอบเล็กๆ ครอบคลุมความเสี่ยงส่วนใหญ่:

  • คำขอแนวทางสำเร็จหนึ่งรายการถัดจากแต่ละเคสข้อผิดพลาด (จับการตรวจสอบที่มากเกินโดยไม่ได้ตั้งใจ)
  • เทสต์หนึ่งรายการต่อรหัสเสถียรเพื่อตรวจฟิลด์คำแนะนำหรือการแม็พฟิลด์
  • เทสต์หนึ่งรายการเพื่อให้แน่ใจว่าความล้มเหลวไม่ทราบขึ้นมาจะคืนรหัสทั่วไปที่ปลอดภัย
  • เทสต์หนึ่งรายการเพื่อให้คีย์แปลมีอยู่สำหรับแต่ละภาษาที่รองรับ
  • เทสต์หนึ่งรายการเพื่อให้แน่ใจว่ารายละเอียดลับไม่ปรากฏในการตอบกลับของไคลเอ็นต์

มอนิเตอร์ช่วยจับการถดถอยที่เทสต์พลาด ติดตามจำนวนของรหัสข้อผิดพลาดตามเวลาและเตือนเมื่อมีพุ่งขึ้นอย่างฉับพลัน (เช่น รหัสการชำระเงินเพิ่มเป็นสองเท่าหลังรีลีส) และจับตาดูรหัสใหม่ในโปรดักชัน ถ้ามีรหัสปรากฏที่ไม่อยู่ในรายการเอกสาร แสดงว่าใครบางคนอาจข้ามสัญญา

ตัดสินใจแต่เนิ่นๆว่าสิ่งใดเก็บภายในกับสิ่งใดส่งให้ไคลเอ็นต์ แยกปฏิบัติได้ง่ายคือ: ไคลเอ็นต์ได้รหัสเสถียร คีย์แปล และคำแนะนำการกระทำ; ล็อกได้สแตกเทรซ ไอดีคำขอ และความล้มเหลวของ dependency

ครั้งละเดือน รีวิวข้อผิดพลาดโดยใช้การสนทนาจากซัพพอร์ตจริง เลือกรหัสห้าอันดับแรกตามปริมาณและอ่านตั๋วหรือแชทไม่กี่รายการสำหรับแต่ละรหัส หากผู้ใช้ยังถามคำถามเดิมซ้ำ แสดงว่าคำแนะนำ UI ขาดขั้นตอน หรือข้อความยังคลุมเครือ

ขั้นตอนถัดไป: นำรูปแบบไปใช้ในผลิตภัณฑ์และเวิร์กโฟลว์ของคุณ

เริ่มจากจุดที่ความสับสนมีต้นทุนสูงที่สุด: ขั้นตอนที่มีการทิ้งบ่อยที่สุด (มักเป็นการสมัคร ชำระเงิน หรืออัปโหลดไฟล์) และข้อผิดพลาดที่สร้างตั๋วมากที่สุด มาตรฐานเหล่านี้ก่อนเพื่อเห็นผลภายในสปรินท์

วิธีปฏิบัติที่ช่วยโฟกัสการนำไปใช้:

  • เลือกรหัสข้อผิดพลาด 10 อันดับแรกที่ขับเคลื่อนซัพพอร์ตและกำหนดรหัสเสถียรและค่าดีฟอลต์
  • นิยามการแม็พรหัส -> คำแนะนำ UI -> การกระทำถัดไป ต่อแต่ละ surface (เว็บ มือถือ แอดมิน)
  • ทำให้สัญญาเป็นค่าพื้นฐานสำหรับ endpoint ใหม่และมองว่าการขาดฟิลด์เป็นความล้มเหลวการตรวจทาน
  • เก็บ playbook ภายในเล็กๆ: แต่ละรหัสหมายถึงอะไร ซัพพอร์ตขออะไร และใครเป็นเจ้าของการแก้ไข
  • ติดตามเมตริกบางรายการ: อัตราข้อผิดพลาดตามรหัส, จำนวน "unknown error", และปริมาณตั๋วที่ผูกกับแต่ละรหัส

ถ้าคุณสร้างด้วย AppMaster (appmaster.io) การฝังสิ่งนี้ตั้งแต่ต้นคุ้มค่า: กำหนดรูปร่างข้อผิดพลาดที่สอดคล้องสำหรับ endpoint ของคุณ แล้วแม็พรหัสเสถียรไปยังข้อความ UI ในหน้าจอเว็บและมือถือของคุณเพื่อให้ผู้ใช้ได้รับความหมายเดิมทุกที่

ตัวอย่างง่ายๆ: ถ้าซัพพอร์ตได้รับเรื่องร้องเรียน "Payment failed" ซ้ำ ๆ มาตรฐานจะทำให้ UI แสดง "Card declined" พร้อมคำแนะนำให้ลองบัตรอื่นสำหรับรหัสหนึ่ง และ "Payment system temporarily unavailable" พร้อมการกระทำลองใหม่สำหรับอีกรหัส ฝ่ายซัพพอร์ตขอ trace_id แทนที่จะเดา

ตั้งการทำความสะอาดซ้ำๆ ในปฏิทิน เลิกใช้รหัสที่ไม่ได้ใช้ ปรับข้อความคลุมเครือ และเพิ่มข้อความแปลเมื่อมีปริมาณการใช้งานจริง สัญญายังคงเสถียรในขณะที่ผลิตภัณฑ์เปลี่ยนแปลงต่อไป

ง่ายต่อการเริ่มต้น
สร้างบางสิ่งที่ น่าทึ่ง

ทดลองกับ AppMaster ด้วยแผนฟรี
เมื่อคุณพร้อม คุณสามารถเลือกการสมัครที่เหมาะสมได้

เริ่ม