ไม่ให้ implementation ห่างจาก design — ตารางจัดการความเบี่ยงเบนและการตรวจสามจุดที่ปกป้อง 'ต้นฉบับ'
สิ่งที่คุณจะได้เรียนรู้
- วิธีจัดการ “ความเบี่ยงเบนของ design กับ implementation” โดยไม่ลบ design
- รูปแบบตารางที่ผูก divergence ID (DVG-XXX) กับ ticket ID (TASK-XX)
- วิธีสร้าง ตารางตรวจสามจุด ระหว่าง architectural / detailed / API design เพื่อตรวจหาความขัดแย้ง
กลุ่มเป้าหมาย
- คนที่ทุกข์กับปัญหา “implementation วิ่งนำ design จนไม่ตรงกัน”
- ผู้ที่ติดอยู่ระหว่าง cost ของการเขียน design ใหม่ทุกครั้ง กับ cost ของการปล่อยให้ design เน่า
- ผู้ที่สนใจ การรัน validation command คู่ขนานกับการดำเนินงาน documentation
สภาพแวดล้อม
| รายการ | เนื้อหา |
|---|---|
| รูปแบบ doc | Markdown |
| โครงสร้าง repo | e-scooter-sharing-doc จัดการเป็น repo แยก |
| Validation | ripgrep (rg) |
บทความในชุด (อยู่ระหว่างการพัฒนา) — บทความนี้เป็นส่วนหนึ่งของ สร้าง E-Scooter Sharing ระดับท้องถนนตั้งแต่ศูนย์เพื่อทำความเข้าใจ — ชุดบันทึก Design, Implementation, และ Operations โปรเจกต์ยังดำเนินอยู่และมีการเพิ่มบทความและบันทึก design ใหม่อย่างต่อเนื่อง แรงจูงใจของชุด, technology stack, อภิธานศัพท์ screen ID, และดัชนีบทความถูกรวบรวมไว้ที่ลิงก์ด้านบน
บทนำ
ในโปรเจกต์ e-scooter sharing requirements / architectural / detailed / API design ถูกประกาศเป็น ต้นฉบับ ตั้งแต่เริ่มต้น
เมื่อ implementation คืบหน้า การเปลี่ยนหน้าจอ / endpoint ของ API / ชื่อ status ค่อย ๆ เบี่ยงเบน วิธีตอบสนองปกติคือ 2 ทาง:
- เขียน design ใหม่ทุกครั้ง → เจตนาการ design จางหาย
- ปล่อย design ไว้ → design กับ implementation เบี่ยงและทำให้สมาชิกใหม่สับสน
เราเลือก เส้นทางที่สาม
รักษาเนื้อหา design เป็นต้นฉบับ ตามรอย diff กับ implementation ปัจจุบันใน “ตารางจัดการความเบี่ยงเบน”

หมุดบนแผนที่คือร่างของ “ดึงจาก API ตาม design → แยกสี gray / dark blue ตามสถานะ” เราตามรอย divergence 4 ตัว (DVG-001〜DVG-004) ในตารางก่อนมาถึงตรงนี้
ตารางจัดการความเบี่ยงเบน (DVG-XXX)
สร้าง 30_user-app/002_detailed-design/divergence-register-(S01-S13).md และจัดการความเบี่ยงเบนแต่ละหน้าจอในตาราง
| DVG-ID | หน้าจอ | ค่าที่ design (ต้นฉบับ) | ความจริงของ implementation | ผลกระทบ | ทางแก้ชั่วคราว | TASK-ID | สถานะ |
|---|---|---|---|---|---|---|---|
| DVG-001 | S01 | ดึงข้อมูล master ของพอร์ตเริ่มต้นผ่าน GET /api/v1/ports | implement การดึงข้อมูลเริ่มต้นแล้ว | แก้ปัญหา latency ของการแสดงครั้งแรก | ไม่มี | TASK-A4 | แก้ไขแล้ว |
| DVG-002 | S07 | guard ของ S04 ส่งไป S07 เมื่อยังไม่ผ่านการทดสอบ | implement หน้าจอ / route / ส่ง API ของ S07 แล้ว | ลดความ dependence ของการดำเนินงาน | ไม่มี | TASK-B1 | แก้ไขแล้ว |
กฎ:
- ห้ามลบ divergence อัปเดตได้แค่สถานะ (เปิด / กำลังดำเนินการ / แก้ไขแล้ว / ตกลงเปลี่ยน design)
- ห้ามแตะเนื้อหา design (ต้นฉบับ) มีแค่ตาราง divergence ที่สะท้อนความจริง
- เฉพาะ “ตกลงเปลี่ยน design” เท่านั้นที่เขียนทับฝั่งต้นฉบับได้
ผลตอบแทนของรูปแบบนี้: “ข้อเท็จจริงที่ implementation เบี่ยงจาก design” อยู่ใน doc เขียน design ใหม่ลบประวัติ ตาราง divergence เปลี่ยนเป็นบันทึก
รายการ Task ของ implementation diff (TASK-XX)
งานแก้ไข divergence จัดการใน 30_user-app/002_detailed-design/implementation-diff-tasks-(app-api).md เป็น ticket
### A-1 รวม state guard เข้าด้วยกัน
- เป้าหมาย: app
- เนื้อหา: implement RouteGuardService.determineNextRoute() และรวม logic การเปลี่ยน S05->S08->S09 มาที่จุดเดียว
- เงื่อนไขยอมรับ:
- "ดำเนินการเตรียมขับ" ของ S04 ต้องผ่าน shared guard เสมอ
- ลำดับการตัดสินตรงกับ design
- สถานะ: แก้ไขแล้ว (2026-05-23 / app commit: 34f9009)
เรียงตาม “ความสำคัญ A (แก้ไขเร็ว)” / “ความสำคัญ B (phase ถัดไป)” / “ความสำคัญ C (คุณภาพ)” โดย ticket ID อ้างอิงร่วมกับ divergence ID
การรายงานเสร็จสิ้น log เป็น “implementation” + “test” + “อัปเดต design” — ชุดสามชิ้น
| เนื้อหา | |
|---|---|
| Implementation | Commit ID (เช่น 34f9009) |
| Test | หลักฐานเงื่อนไขยอมรับสำเร็จ (test ผ่าน, ตรวจหน้าจอ ฯลฯ) |
| อัปเดต design | บันทึกการเปลี่ยนสถานะ divergence register เป็น “แก้ไขแล้ว” |
การตรวจสามจุด (Architectural / Detailed / API)
สำหรับแต่ละหน้าจอ S01〜S13 สร้างตารางความสอดคล้องของ artifact 3 design
| หน้าจอ | Architectural | Detailed | API design | คำตัดสิน |
|---|---|---|---|---|
| S05 | OTP ส่ง / verify | sendOtp(phone), verifyOtp(phone, code) | POST /auth/send-otp, /auth/verify-otp | OK |
| S10 | lock-toggle | toggleLock(rentalId, requested) | POST /rentals/{rental_id}/lock-toggle | บางส่วน (อนาคต) |
คำตัดสินใช้ “OK / บางส่วน / ต้องเปลี่ยน design / ต้องเปลี่ยน implementation” ทุกสิ่งที่ต่ำกว่า OK เต็มจะได้ divergence ID และไปอยู่ใน divergence register

ตัวอย่างเช่น “คืนที่นี่” ปิดใช้งานเมื่อพอร์ตเต็ม คือภาพของ architectural (“toggle ปุ่มตามความสามารถคืน”), API (“คืน available_empty_slots”), และ detailed (“logic เปิดปุ่มใน child sheet”) design ทั้งหมดมาถึงคำตัดสินเดียวกัน — OK — ในตารางสามจุดหลัง implementation
สิ่งสำคัญตรงนี้:
- API design เป็นต้นฉบับระดับ string ของ endpoint
- Architectural / detailed design เป็นต้นฉบับระดับ พฤติกรรมทางธุรกิจ
- ตารางสามจุดคือพื้นที่ยืนยันว่า 3 แกนชี้ไปยังข้อเท็จจริงเดียวกัน
ถ้า implementation เปลี่ยนชื่อ /api/v1/foo → /api/v1/bar ก่อนเขียน “ต้นฉบับ” ของ API design ใหม่ ต้องตกลงตารางสามจุดให้ตรงกับฝั่งอื่นด้วย นี่คือหัวใจของกลไกไม่ให้เบี่ยง
Validation Gate และ ripgrep
เมื่อ divergence register โต การเช็คด้วยมือว่า “แก้ไขครบหรือยัง” ไม่จริง validation gate แก้สิ่งนี้
# Gate A: นอก OLD ไม่มี path หลักที่ขาด prefix /api/v1
rg -n "`(GET|POST|PATCH|PUT|DELETE) /(auth|users|ports|scooters|rentals|payments|ekyc)" 00_common 20_API 30_user-app --glob "!**/OLD/**"
# Gate B: ตรวจการมีอยู่ของคำ "ต้องมี diff notation"
rg -n "implementation-diff-note|not-yet|future-impl" 30_user-app
# Gate C: ตรวจความตรงระหว่างตารางสามจุดกับ task list
rg -n "S10|partial|future-impl" 30_user-app/002_detailed-design
Gate A ตรวจหา path ของ API เก่าที่ยังอยู่ใน body — endpoint ที่ไม่มี prefix /api/v1
Gate B คือการตรวจครอบคลุม diff annotation ของรายการที่ยังไม่ implement
Gate C ยืนยันว่า คำตัดสินอย่าง “บางส่วน” หรือ “future-impl” ถูกใช้ความหมายเดียวกันทั้งใน divergence register และ task list
จะเข้า CI หรือรันด้วยมือก็ได้ สิ่งสำคัญคือ มี gate อยู่ และการละเลย design จะถูกตรวจพบ
OLD Directory สำหรับประวัติ
เมื่อจัดระเบียบ design อย่า “ลบ” design เก่า — ย้ายเข้า OLD/ directory
30_user-app/
├─ 001_architectural-design/
├─ 002_detailed-design/
│ ├─ OLD/
│ │ └─ old-payment-flow-design.md
│ ├─ payment-flow-design-(truth-aligned).md
│ └─ divergence-register-(S01-S13).md
--glob "!**/OLD/**" ของ Gate A ยกเว้น OLD ออกจากการค้นหา ตรวจเฉพาะ design ที่ยังมีชีวิต ประวัติยังอยู่ในมือ
บทเรียน
- ความเบี่ยงเบนของ design / implementation จริงกว่าที่จะ “จัดการในรูปแบบที่เห็นได้” มากกว่า “กำจัดทิ้ง”
- ตาราง divergence / task list / ตารางสามจุดเป็น 3 แกนที่มีบทบาทต่างกัน การอ้างอิงร่วมกันปกป้องต้นฉบับ
- validation รันได้ดีในฐานะ gate เบา ๆ ที่ใช้ ripgrep (ไม่ต้องบังคับ CI)
- ห้ามลบ design — ย้ายเข้า OLD เพื่อรักษาประวัติ
design ไม่ใช่ “ตำราที่ไม่มีวันผิด” แต่เป็น “เครื่องมือที่วางไว้กลางการตัดสินใจโดยถือว่าเป็นต้นฉบับ” กลไกเพื่อปกป้องสิ่งนี้ในโปรเจกต์เรา กลายเป็น divergence register และการตรวจสามจุด