2 นาที

สร้างสเปคฟีเจอร์จากโค้ดด้วย Claude Code: เวิร์กโฟลว์ง่ายๆ

เรียนรู้วิธีสร้างสเปคฟีเจอร์จากโค้ดด้วยการสกัดพฤติกรรมจริงจาก routes และ components แล้วสร้างสเปคแบบ "living" พร้อมรายการช่องว่างที่ต้องตัดสินใจ.

สร้างสเปคฟีเจอร์จากโค้ดด้วย Claude Code: เวิร์กโฟลว์ง่ายๆ

ทำไมคุณต้องการสเปคที่ตรงกับโค้ด

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

เมื่อเวลาผ่านไป โค้ดจะกลายเป็นแหล่งเดียวที่ยังคงอัพเดตอยู่ เอกสารล้าหลัง ตั๋วถูกปิด และการแก้ไขด่วนซ้อนทับกัน เราอาจเพิ่มกฎการตรวจสอบใหม่บน route, toggle UI เปลี่ยนค่าเริ่มต้น, หรือ handler เริ่มคืนข้อผิดพลาดต่างไป คนไม่ค่อยอัพเดตสเปคเพราะรู้สึกว่าเป็นทางเลือก และแต่ละการเปลี่ยนแปลงก็ดูเล็กเกินกว่าจะบันทึก

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

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

เมื่อสเปคตรงกับโค้ด จะปลอดภัยที่จะวางแผนการเปลี่ยนแปลง คุณมองเห็นได้ว่าส่วนไหนมั่นคง ส่วนไหนเป็นผลบังเอิญ และส่วนไหนขาดหายก่อนจะปล่อย

สเปคแบบ “living” และรายการช่องว่างคืออะไร

สเปคแบบ living คือคำอธิบายสั้นๆ ที่แก้ไขได้ของสิ่งที่แอปทำจริงในวันนี้ มันไม่ใช่เอกสารทำครั้งเดียวแล้วทิ้ง มันเปลี่ยนเมื่อพฤติกรรมเปลี่ยน เพื่อให้ทีมเชื่อถือได้

เมื่อคนพูดถึงสเปคฟีเจอร์ที่เขียนจากโค้ด (เช่น ใช้ Claude Code) เป้าหมายคืออ่านพฤติกรรมจริงจาก routes, handlers และ screens แล้วเขียนออกมาเป็นภาษาธรรมดา

สเปคที่เป็นประโยชน์มุ่งที่สิ่งที่ผู้ใช้เห็นและสิ่งที่ระบบสัญญาไว้ ควรครอบคลุม:

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

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

  • ชื่อฟังก์ชันและคลาส โครงต้นไม้ของคอมโพเนนต์
  • การถกเถียงสถาปัตยกรรมและแผน rewrite

รายการช่องว่าง (gaps list) เป็นของแยกต่างหาก มันคือรายการสั้นของความไม่ตรงกันและสิ่งที่ไม่รู้เจอระหว่างเขียนสเปค

  • บั๊กคือ: โค้ดขัดกับพฤติกรรมปัจจุบันหรือกฎที่ตกลงกันไว้
  • คำขอฟีเจอร์คือ: ต้องการพฤติกรรมใหม่
  • ช่องว่างคือ: คุณไม่สามารถบอกได้ว่าพฤติกรรมที่ถูกต้องควรเป็นอย่างไร หรือพฤติกรรมไม่สอดคล้องระหว่างหน้าจอ/บทบาท

ตัวอย่าง: route หนึ่งปฏิเสธไฟล์เกิน 10MB แต่ UI บอก 25MB นั่นคือช่องว่างจนกว่าทีมจะตัดสินว่ากฎไหนจริงและอัพเดตโค้ดหรือสเปคให้ตรงกัน

เลือกขอบเขตและฟอร์แมตสเปคที่เรียบง่าย

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

เลือกจุดเริ่มต้นตามที่ความจริงอยู่:

  • ถ้าต้องการกฎจริง ให้เริ่มจาก routes และ handlers
  • ถ้าต้องการประสบการณ์จริง ให้เริ่มจากจุดเข้า UI
  • ถ้าฟีเจอร์ยุ่งเหยิง ให้เริ่มจากเพจ/คอนโทรลเลอร์ระดับสูงสุดแล้วทำงานออกไป

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

ทำให้ฟอร์แมตสเปคเรียบง่ายและสม่ำเสมอ ทีมจะตรงกันเร็วขึ้นเมื่อสเปคทุกฉบับอ่านในแบบเดียวกัน

เทมเพลตสเปค (ทำซ้ำสำหรับแต่ละฟลว์ที่ผู้ใช้เห็น)

  • Purpose: ผู้ใช้พยายามจะทำอะไร
  • Entry points: จุดที่ฟลว์เริ่ม (URL, เมนู, ปุ่ม)
  • Preconditions: การยืนยันตัวตน บทบาท ข้อมูลที่ต้องมี
  • Main flow: 5–10 ขั้นตอนเป็นภาษาธรรมดา
  • Data and side effects: เรคอร์ดที่สร้าง/อัพเดต, อีเมล, logs
  • Errors and edge cases: เกิดอะไรเมื่อผิดพลาด
  • Open questions: พฤติกรรมไม่ชัดเจนที่ต้องยืนยัน

ใช้โครงสร้างนี้ทำซ้ำ แล้วสเปคฟีเจอร์ของคุณจะอ่านง่าย เปรียบเทียบได้ และอัพเดตง่าย

สกัดพฤติกรรมจาก routes และ handlers

เริ่มจากจุดเข้าเซิร์ฟเวอร์ Routes และ handlers แสดงว่า “แอปทำอะไร” เป็นรูปธรรม: ใครเรียกได้ ต้องส่งอะไร คืนอะไร และระบบเปลี่ยนแปลงอย่างไร

ลิสต์ routes ที่อยู่ในขอบเขตและจับแต่ละอันให้เป็นเจตนาของผู้ใช้ อย่าเขียนว่า “POST /api/orders.” ให้เขียนเป็น “สั่งซื้อสินค้า” หรือ “บันทึกแบบร่าง” ถ้าคุณไม่สามารถตั้งชื่อเจตนาเป็นคำธรรมดา นั่นคือช่องว่างในสเปคแล้ว

เมื่ออ่านแต่ละ handler ให้จับอินพุตและกฎการตรวจสอบเป็นความต้องการที่ผู้ใช้เห็น รวมฟิลด์ที่จำเป็น รูปแบบที่อนุญาต และกฎที่ทำให้เกิดข้อผิดพลาดจริง ตัวอย่าง: “อีเมลต้องถูกต้อง”, “ปริมาณต้องไม่น้อยกว่า 1”, “วันที่เริ่มต้นห้ามอยู่ในอดีต.”

เขียนการตรวจสอบ auth และ role แบบเดียวกัน แทนที่จะเขียน “middleware: requireAdmin” ให้บันทึกว่า: “เฉพาะผู้ดูแลระบบเท่านั้นที่ยกเลิกคำสั่งซื้อได้ทั้งหมด; ผู้ใช้ปกติยกเลิกคำสั่งของตัวเองได้ภายใน 10 นาที.” ถ้าโค้ดตรวจสอบความเป็นเจ้าของ ธงฟีเจอร์ หรือขอบเขต tenant ให้รวมไว้ด้วย

จากนั้นบันทึกผลลัพธ์และผลที่ตามมา สำเร็จแล้วคืนอะไร (ID ที่สร้าง, วัตถุที่อัพเดต)? ข้อผิดพลาดทั่วไปเป็นอย่างไร (401 ยังไม่ได้ล็อกอิน, 403 ไม่ได้รับอนุญาต, 404 ไม่พบ, 409 ขัดกัน, 422 ข้อผิดพลาดการตรวจสอบ)?

สุดท้าย บันทึกผลข้างเคียงเพราะเป็นส่วนหนึ่งของพฤติกรรม: เรคอร์ดที่ถูกสร้าง/อัพเดต, อีเมลหรือการแจ้งเตือนที่ส่ง, อีเวนต์ที่เผยแพร่, งาน background ที่เข้าคิว และสิ่งที่กระตุ้นฟลว์อื่น รายละเอียดเหล่านี้ป้องกันความประหลาดใจเมื่อทีมอื่นอ้างอิงสเปคในภายหลัง

สกัดพฤติกรรมจากคอมโพเนนต์และฟลว์ UI

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

เริ่มโดยหาหน้าจอทางเข้า (entry screens) สำหรับฟีเจอร์ ค้นหา page component, layout wrapper และคอมโพเนนต์ "ตัดสินใจ" ไม่กี่ตัวที่ควบคุมการดึงข้อมูล สิทธิ์ และการนำทาง จุดเหล่านี้มักเป็นที่ที่พฤติกรรมจริงอยู่

เมื่ออ่านคอมโพเนนต์ ให้จับกฎที่ผู้ใช้รู้สึกได้: เมื่อไหร่ที่ action ถูกปิด เงื่อนไขฟิลด์ที่ต้องกรอก สถานะโหลด และวิธีแสดงข้อผิดพลาด (ข้อผิดพลาดใต้ฟิลด์แบบ inline vs toast, ลองใหม่อัตโนมัติ, ปุ่ม “ลองอีกครั้ง”) บันทึกพฤติกรรมสถานะและการแคช เช่น ข้อมูลเก่าปรากฏก่อน การอัพเดตแบบ optimistic หรือ “บันทึกล่าสุด”

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

ตัวอย่างชัดเจน: ในหน้าจอ “เปลี่ยนอีเมล” ให้บันทึกว่า ปุ่มบันทึกจะปิดจนกว่าอีเมลจะถูกต้อง, มี spinner ระหว่างการร้องขอ, ความสำเร็จแสดงแบนเนอร์ยืนยัน, และข้อผิดพลาดการตรวจสอบจาก backend แสดงใต้ช่องใส่อีเมล ถ้าโค้ดมีแฟลกอย่าง newEmailFlow ให้บันทึกทั้งสองรูปแบบและความแตกต่าง

เขียนแต่ละ UI flow เป็นขั้นตอนสั้นๆ (ผู้ใช้ทำอะไร UI ตอบสนองอย่างไร) และเก็บเงื่อนไขกับข้อผิดพลาดไว้ข้างๆ ขั้นตอนที่มีผล วิธีนี้ช่วยให้สเปคอ่านง่ายและช่วยให้เห็นช่องว่างได้เร็วขึ้น

แปลงสิ่งที่ค้นพบเป็นสเปคฟีเจอร์ที่อ่านได้

ค้นหาช่องว่างก่อนปล่อย
เปรียบเทียบพฤติกรรม UI และ API ในบทสนทนาและรายการความไม่ตรงกันพร้อมหลักฐาน.

บันทึกดิบจาก routes และ components มีประโยชน์แต่ยากจะอภิปราย รีเขียนสิ่งที่สังเกตเป็นสเปคที่ PM, ดีไซเนอร์, QA และวิศวกรอ่านและตกลงกันได้

แนวทางปฏิบัติที่เป็นรูปธรรมคือเรื่องผู้ใช้หนึ่งเรื่องต่อ route หรือหน้าจอหนึ่งหน้า เล็กและเฉพาะเจาะจง ตัวอย่าง: “ในฐานะผู้ใช้ที่ล็อกอิน ฉันสามารถรีเซ็ตรหัสผ่านได้เพื่อกลับเข้าถึงบัญชี” ถ้าโค้ดแสดงพฤติกรรมต่างกันตามบทบาท (admin vs user) ให้แยกเป็นเรื่องต่างหากแทนซ่อนในบันทึกย่อย

จากนั้นเขียน acceptance criteria ที่สะท้อนเส้นทางโค้ดจริง ไม่ใช่ผลิตภัณฑ์ในอุดมคติ ถ้า handler คืน 401 เมื่อ token หาย นั่นคือ criterion ถ้า UI ปิดปุ่มจนกว่าฟิลด์ถูกต้อง นั่นก็เป็น criterion

รวมกฎข้อมูลเป็นภาษาธรรมดา โดยเฉพาะอย่างยิ่งกฎที่ทำให้คนประหลาดใจ: ขีดจำกัด การเรียงลำดับ ความเป็นเอกลักษณ์ ฟิลด์ที่จำเป็น “ชื่อผู้ใช้ต้องไม่ซ้ำ (ตรวจสอบเมื่อบันทึก)” ชัดกว่าการเขียนว่า “unique index”

ขอบเคสมักเป็นสิ่งที่แยกเอกสารธรรมดาจากเอกสารที่ใช้งานได้จริง ออกมาเรียกสถานะว่าง ค่า null การลองใหม่ timeout และสิ่งที่ผู้ใช้เห็นเมื่อการเรียก API ล้มเหลว

เมื่อเจอสิ่งที่ไม่รู้ ให้ทำเครื่องหมายแทนการเดา:

  • Unknown: ควรแสดงข้อความอะไรเมื่อไม่พบอีเมล?
  • Unknown: ควรอนุญาตให้มี 0 รายการไหม หรือบังคับให้มีอย่างน้อย 1?
  • Unknown: ข้อผิดพลาดนี้ควรแสดงต่อผู้ใช้หรือแค่บันทึกเท่านั้น?

เครื่องหมายเหล่านี้จะกลายเป็นคำถามให้ทีมแทนที่จะเป็นสมมติฐานเงียบ ๆ

สร้างรายการช่องว่างโดยไม่กลายเป็น backlog

รายการช่องว่างไม่ใช่ Jira ที่สอง มันคือบันทึกสั้นที่มีหลักฐานของที่ที่โค้ดและพฤติกรรมที่ตั้งใจไว้ไม่ตรงกัน หรือที่ไม่มีใครอธิบายได้ดี ทำได้ดี มันจะเป็นเครื่องมือเพื่อการตกลง ไม่ใช่การต่อสู้วางแผน

เข้มงวดเกี่ยวกับสิ่งที่นับเป็นช่องว่าง:

  • พฤติกรรมไม่ชัดเจน: แอปทำบางอย่างแต่กฎนั้นไม่มีที่เขียนไว้
  • ไม่สอดคล้อง: สองที่ทำต่างกันในเคสเดียวกัน
  • กฎขาดหาย: มีขอบเคสแต่ไม่มีการตัดสินใจในโค้ดหรือเอกสาร

เมื่อบันทึกช่องว่าง ให้ใส่สามส่วนเพื่อให้มีหลักฐาน:

  • Type: bug (โค้ดดูผิด) หรือ missing decision (เจตนาไม่ชัด)
  • Impact: สับสนผู้ใช้, ความเสี่ยงด้านความปลอดภัย, สูญหายข้อมูล, หรือเล็กน้อย
  • Evidence: ที่ที่คุณเห็นและสิ่งที่สังเกต (route/handler/component)

หลักฐานคือสิ่งที่ทำให้รายการไม่กลายเป็นความเห็น ตัวอย่าง: “POST /checkout/apply-coupon ยอมรับคูปองหมดอายุ แต่ CouponBanner.tsx ใน UI บล็อกคูปองเหล่านั้น ผลกระทบ: รายได้และความสับสนของผู้ใช้ ประเภท: บั๊กหรือ missing decision (ยืนยันกฎที่ต้องการ).”

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

หลีกเลี่ยงวันที่และการตารางเวลาในรายการช่องว่าง ถ้าต้องการความเป็นเจ้าของ ให้บอกว่าใครควรตัดสิน (product) หรือใครยืนยันพฤติกรรม (engineering) แล้วย้ายการวางแผนจริงไปที่ backlog ของคุณ

ตัวอย่าง: บันทึกฟีเจอร์จริงจากโค้ด

พาทีมเข้าร่วมแชท
ร่วมงานกันในแชท เขียนสเปคและส่งมอบโค้ดเมื่อพร้อม.

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

เริ่มจาก backend routes ซึ่งมักเป็นที่ที่กฎปรากฏก่อน คุณอาจเจอ route เช่น POST /checkout/apply-promo, GET /checkout/shipping-options, และ POST /checkout/confirm.

จาก handlers เหล่านั้น เขียนพฤติกรรมเป็นคำธรรมดา:

  • คูปองถูกตรวจสอบฝั่งเซิร์ฟเวอร์ (หมดอายุ, ขีดจำกัดการใช้งาน, ความเหมาะสมของลูกค้า).
  • ยอดรวมถูกคำนวณใหม่หลังใช้คูปอง แต่ทำหลังการตรวจสอบสต็อก
  • ตัวเลือกการจัดส่งขึ้นกับปลายทาง น้ำหนัก และว่ามีสินค้าใดถูกมาร์กเป็น “restricted” หรือไม่
  • การยืนยันล้มเหลวถ้าสต็อกของรายการเปลี่ยนตั้งแต่โหลดตะกร้า
  • ภาษีคำนวณหลังเลือกการจัดส่ง (ไม่ใช่ตอนใช้คูปอง)

จากนั้นตรวจสอบ UI components. PromoCodeInput อาจแสดงว่ายอดจะรีเฟรชหลังจากตอบกลับสำเร็จเท่านั้น และข้อผิดพลาดจะแสดงใต้ input. ShippingOptions อาจเลือกตัวถูกที่สุดโดยอัตโนมัติเมื่อโหลดครั้งแรกและกระตุ้นการรีเฟรชแจกแจงราคาเมื่อผู้ใช้เปลี่ยนมัน

ตอนนี้คุณมีสเปคที่อ่านได้และรายการช่องว่างเล็กๆ ตัวอย่างเช่น: ข้อความข้อผิดพลาดแตกต่างกันระหว่าง route ของคูปองและ UI (“Invalid code” vs “Not eligible”) และไม่มีใครชี้ได้ว่ากฎการปัดเศษภาษีคือแบบ per line หรือ order total

ในการวางแผน ทีมตกลงเรื่องความจริงก่อน แล้วตัดสินใจจะแก้ไขอะไร แทนที่จะถกเถียงเป็นความเห็น คุณตรวจสอบพฤติกรรมที่มีหลักฐาน เลือกความไม่สอดคล้องตัวหนึ่งที่จะแก้ แล้วปล่อยให้พฤติกรรมอื่นเป็น “พฤติกรรมปัจจุบันที่รู้” จนกว่าจะถึงเวลาตรวจสอบใหม่

ยืนยันสเปคกับทีมและรักษาให้อัพเดต

สเปคช่วยได้ก็ต่อเมื่อทีมเห็นด้วยว่ามันตรงกับความจริง ทำการอ่านผ่านสั้น ๆ กับวิศวกรหนึ่งคนและคนผลิตภัณฑ์หนึ่งคน จำกัดเวลา: 20–30 นาที มุ่งที่สิ่งที่ผู้ใช้ทำและสิ่งที่ระบบตอบ

ระหว่างการอ่าน ให้เปลี่ยนประโยคเป็นคำถามใช่/ไม่ใช่ “เมื่อผู้ใช้เข้าถึง route นี้ เราคืน 403 เสมอเมื่อไม่มี session ไหม?” “สถานะว่างนี้ตั้งใจหรือไม่?” วิธีนี้แยกความตั้งใจออกจากพฤติกรรมที่หลุดเข้ามาโดยบังเอิญ

ตกลงคำศัพท์ก่อนแก้ไข ใช้คำที่ผู้ใช้เห็นใน UI (ป้ายปุ่ม ชื่อหน้า ข้อความผิดพลาด). เพิ่มชื่อภายในเฉพาะเมื่อช่วยให้วิศวกรหาตำแหน่งโค้ด (ชื่อ route, ชื่อ component). วิธีนี้ป้องกันความไม่ตรงกันเช่น product บอก “Workspace” แต่สเปคเขียนว่า “Org.”

เพื่อให้มันทันสมัย ให้ชัดเจนเรื่องความเป็นเจ้าของและจังหวะ:

  • เจ้าของสเปค: คนเดียวที่ merge การเปลี่ยนแปลง (มักเป็นเจ้าของฟีเจอร์หรือ tech lead)
  • ทริกเกอร์การอัพเดต: เมื่อ merge PR ที่เปลี่ยนพฤติกรรม หรือในแต่ละ release
  • ตรวจสอบด่วน: เพิ่ม checkbox “spec updated?” ใน PR template
  • ที่เก็บ: เก็บใกล้โค้ดเพื่อให้มันเปลี่ยนพร้อมกับโค้ด

ถ้าคุณใช้เครื่องมืออย่าง Koder.ai snapshots และ rollback ช่วยเปรียบเทียบ “ก่อน” และ “หลัง” เมื่ออัพเดตสเปค โดยเฉพาะหลัง refactor ใหญ่

ข้อผิดพลาดและกับดักที่พบบ่อย

วิธีที่เร็วที่สุดในการทำให้สเปคเสียความเชื่อถือคือบรรยายผลิตภัณฑ์ที่คุณต้องการ ไม่ใช่ผลิตภัณฑ์ที่คุณมี ยึดกฎเข้มงวด: ทุกข้อความต้องมีหลักฐานที่ชี้ได้ในโค้ดหรือหน้าจอจริง

กับดักอีกอย่างคือการคัดลอกโครงรูปของโค้ดลงเอกสาร สเปคที่อ่านว่า “Controller -> Service -> Repository” ไม่ใช่สเปค แต่มันเป็นแผนที่โฟลเดอร์ เขียนเป็นเงื่อนไขที่ผู้ใช้เห็น: อะไรกระตุ้นการกระทำ ผู้ใช้เห็นอะไร บันทึกอะไร และข้อผิดพลาดเป็นอย่างไร

สิทธิ์และบทบาทมักถูกมองข้ามจนสุดท้ายทุกอย่างพัง เพิ่มกฎการเข้าถึงแต่แรก แม้มันจะยุ่ง ให้ระบุว่าใครบ้างดู สร้าง แก้ ลบ ส่งออก หรืออนุมัติ และกฎถูกบังคับที่ไหน (UI เท่านั้น, API เท่านั้น, หรือทั้งสอง)

อย่าข้ามเส้นทางที่ไม่สมบูรณ์แบบ พฤติกรรมจริงซ่อนอยู่ใน retries ความล้มเหลวบางส่วน และกฎตามเวลาเช่นการหมดอายุ, คูลดาวน์, งานตามตาราง, หรือข้อจำกัด “ครั้งเดียวต่อวัน” ให้ถือสิ่งเหล่านี้เป็นพฤติกรรมระดับหนึ่ง

วิธีด่วนในการพบช่องว่างคือเช็ค:

  • ข้อผิดพลาดการตรวจสอบและข้อความข้อผิดพลาดที่ผู้ใช้เห็นจริง
  • การจัดการการส่งซ้ำซ้อน (idempotency)
  • งาน background (queues, cron) และเกิดอะไรถ้ามันล้มเหลว
  • ปัญหาการแข่งขัน (สองคนแก้เรคอร์ดเดียวกัน)
  • พฤติกรรมตามเวลา (timeout, หมดอายุ, rate limits)

สุดท้าย ให้รายการช่องว่างเคลื่อนไหว แต่ละช่องว่างควรถูกติดป้ายว่า: “unknown, needs decision,” “bug, fix,” หรือ “missing feature, plan.” ถ้าไม่มีการติดป้าย รายการจะหยุดนิ่งและสเปคจะหยุดเป็น “living”

เช็คลิสต์ด่วนก่อนแชร์สเปค

โปรโตไทป์มือถือด้วย Flutter
โปรโตไทป์โฟลว์มือถือจากสเปคและปรับหน้าจอทีละขั้นตอน.

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

ความชัดและความเข้าใจร่วมกัน

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

ตรวจสอบ:

  • ทดสอบหน้าเดียว: ตอนเปิดบอกเป้าหมายผู้ใช้ จุดเริ่มต้น และจุดสิ้นสุด
  • บทบาทและการเข้าถึง: บทบาทสำคัญและแต่ละบทบาททำหรือไม่ทำอะไร
  • ผลลัพธ์: ความสำเร็จเป็นอย่างไร และผู้ใช้เห็นอะไรเมื่อล้มเหลว (ข้อความ การเปลี่ยนเส้นทาง การลองใหม่)
  • ขอบและข้อจำกัด: ขีดจำกัดขนาด, rate limits, timeout, กฎการตรวจสอบ และเกิดอะไรเมื่อข้อมูลขาดหาย
  • ภาษา: ใช้คำที่ผู้ใช้เห็นก่อน; กำหนดศัพท์เฉพาะเท่าที่จำเป็น

ช่องว่างที่ช่วยได้ ไม่ใช่เสียงรบกวน

แต่ละช่องว่างควรเฉพาะและทดสอบได้ แทนที่จะเขียนว่า “การจัดการข้อผิดพลาดไม่ชัด” ให้เขียน: “ถ้า payment provider คืน 402 UI แสดง toast ทั่วไป; ยืนยันข้อความที่ต้องการและพฤติกรรมการลองใหม่.” เพิ่มการกระทำต่อไปเพียงอย่างเดียว (ถาม product, เพิ่มเทสต์, ตรวจ logs) และระบุผู้ที่ควรตอบ

ขั้นตอนถัดไปที่เริ่มได้ในสัปดาห์นี้

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

รันเวิร์กโฟลว์หนึ่งรอบ: อ่าน routes/handlers สำคัญ ๆ อย่างคร่าว ๆ ติดตามฟลว์ UI หลัก และจดพฤติกรรมที่สังเกตได้ (อินพุต, เอาต์พุต, การตรวจสอบ, สถานะข้อผิดพลาด) ถ้าติดขัด ให้บันทึกคำถามเป็น gap แล้วไปต่อ

เมื่อเสร็จ แชร์สเปคให้ทีมคอมเมนต์ และตั้งกฎหนึ่งข้อ: พฤติกรรมที่ปล่อยต้องอัพเดตสเปคในหน้าต่างการส่งมอบเดียวกัน แม้จะแค่ห้าบรรทัด

เก็บ gaps แยกจาก backlog รวมเป็นกลุ่มว่า “พฤติกรรมไม่รู้,” “พฤติกรรมไม่สอดคล้อง,” และ “ขาดการทดสอบ,” แล้วทบทวนสั้น ๆ ทุกสัปดาห์เพื่อตัดสินใจว่าสิ่งไหนสำคัญตอนนี้

ถ้าการร่างและทำซ้ำช้า เครื่องมือแบบ chat-based อย่าง Koder.ai สามารถช่วยให้ได้เวอร์ชันแรกเร็ว: อธิบายฟีเจอร์ วาง snippets หรือชื่อ route ที่สำคัญ ปรับข้อความในบทสนทนา แล้วส่งออกแหล่งที่มาถ้าต้องการ จุดประสงค์คืิอความเร็วและความชัดเจนที่ทีมแชร์ ไม่ใช่กระบวนการใหญ่

คำถามที่พบบ่อย

Where do I start if I want to write a feature spec from existing code?

เริ่มจากส่วนเล็กที่มองเห็นได้โดยผู้ใช้ (เช่น “รีเซ็ตรหัสผ่าน” หรือ “เชิญสมาชิกทีม”) อ่าน routes/handlers เพื่อจับกฎและผลลัพธ์ แล้วอ่าน UI flow เพื่อจับสิ่งที่ผู้ใช้เห็นจริง (สถานะปิดปุ่ม ข้อผิดพลาด การเปลี่ยนเส้นทาง) เขียนตามเทมเพลตที่สม่ำเสมอและบันทึกความไม่แน่ใจเป็นรายการ gaps แยกต่างหาก.

Should the spec describe what the product should do, or what the code does today?

ค่าเริ่มต้น: ถือว่า พฤติกรรมโค้ดปัจจุบัน เป็นแหล่งความจริงและจดมันไว้

ถ้าพฤติกรรมดูเหมือนเกิดโดยบังเอิญหรือไม่สอดคล้อง อย่า “แก้” มันในสเปค—ให้ทำเครื่องหมายเป็น gap พร้อมหลักฐาน (เห็นที่ไหนและมันทำอย่างไร) แล้วขอการตัดสินใจเพื่ออัพเดตโค้ดหรือสเปคแทน.

What’s a simple spec format that stays readable as the app grows?

ทำให้มันเรียบและทำซ้ำได้. เทมเพลตที่ใช้งานได้จริงคือ:

  • Purpose
  • Entry points
  • Preconditions (auth/role/data)
  • Main flow (5–10 ขั้นตอน)
  • Data and side effects
  • Errors and edge cases
  • Open questions

โครงสร้างนี้ช่วยให้สเปคอ่านง่ายและช่วยให้เห็นความไม่ตรงกันได้เร็วขึ้น.

How do I turn handler validation and auth checks into plain-language requirements?

เขียนกฎเป็นความต้องการที่ผู้ใช้เข้าใจ ไม่ใช่บันทึกโค้ด

ตัวอย่าง:

  • “อีเมลต้องถูกต้อง”
  • “ปริมาณต้องไม่น้อยกว่า 1”
  • “เฉพาะผู้ดูแลระบบเท่านั้นที่ยกเลิกคำสั่งซื้อได้ทั้งหมด; ผู้ใช้ปกติยกเลิกคำสั่งของตัวเองได้ภายใน 10 นาที”

จับความผิดพลาดที่เกิดขึ้นและสิ่งที่ผู้ใช้เห็นเมื่อเกิดเหตุด้วย.

What outputs and side effects should a spec include?

มุ่งที่สิ่งที่สังเกตได้:

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

ผลข้างเคียงสำคัญเพราะส่งผลต่อฟีเจอร์อื่นและความคาดหวังของฝ่ายซัพพอร์ต/ops.

What if the UI and backend disagree (like different file size limits)?

ถ้า UI บล็อกสิ่งที่ API อนุญาต (หรือกลับกัน) ให้บันทึกเป็น gap จนกว่าจะมีการตัดสินใจ

บันทึกสิ่งต่อไปนี้:

  • UI บอก/ทำอะไร
  • Backend บังคับอะไร
  • ผลกระทบ (ความสับสน ความปลอดภัย ปัญหาข้อมูล)

แล้วตกลงกฎเดียวกันและอัพเดตทั้งโค้ดและสเปคให้ตรงกัน.

How do I write a gaps list without turning it into a planning fight?

เก็บรายการ gaps ให้สั้นและมีหลักฐาน. แต่ละรายการควรมี:

  • Type: bug vs missing decision
  • Impact: เล็กน้อย vs ร้ายแรง (สับสน, ความปลอดภัย, สูญหายข้อมูล)
  • Evidence: ที่ที่คุณเห็น (route/handler/component) และพฤติกรรมที่สังเกตได้

หลีกเลี่ยงการกำหนดกำหนดการหรือเปลี่ยนให้เป็น backlog ที่สอง.

Which edge cases are most important to capture in a living spec?

บันทึกพวกนี้อย่างชัดเจนแทนที่จะซ่อนไว้

รวมถึง:

  • สถานะว่าง (ไม่มีผลลัพธ์, ไม่มีสิทธิ์)
  • การลองใหม่/timeout และผู้ใช้ควรทำอย่างไรต่อ
  • การส่งซ้ำซ้อน (ดับเบิลคลิก, รีเฟรช)
  • การแข่งขันกันแก้ไข (สองคนแก้เรคอร์ดเดียวกัน)
  • กฎตามเวลา (หมดอายุ, คูลดาวน์)

ส่วนเหล่านี้มักเป็นต้นเหตุของบั๊กและความประหลาดใจ.

How do I validate the spec with the team so people trust it?

สั้น ๆ: 20–30 นาที อ่านกับวิศวกร 1 คนและคนผลิตภัณฑ์ 1 คน

เปลี่ยนคำกล่าวให้เป็นคำถามใช่/ไม่ใช่ (เช่น “เมื่อเรียก route นี้ เราจะคืน 403 เสมอเมื่อไม่มี session ไหม?”). จัดคำศัพท์ให้ตรงกับที่ UI ใช้ (ปุ่ม ข้อความ) เพื่อให้ทุกคนหมายถึงสิ่งเดียวกัน.

How do I keep the spec “living” instead of letting it drift again?

เก็บสเปคใกล้โค้ดและปรับปรุงเป็นส่วนหนึ่งของการปล่อย

ค่าที่ใช้ง่าย:

  • เจ้าของสเปคชัดเจนคนเดียวที่ merge การเปลี่ยนแปลง
  • ทริกเกอร์การอัพเดต: ทุกครั้งที่มีการเปลี่ยนพฤติกรรมใน PR หรือทุก release
  • เพิ่ม checkbox ใน PR template: “Spec updated?”
  • แยก gaps และทบทวนเป็นรอบ

เป้าหมายคือการแก้ไขเล็กๆ บ่อยๆ ไม่ใช่ rewrite ใหญ่ ๆ.

Related posts