ให้ AI คนละค่ายมาตรวจโค้ดที่ AI เขียน — กระบวนการ ไม่ใช่ความศรัทธา
ให้ AI ตรวจงานตัวเองมันจะบอกว่าผ่านเสมอ ทางที่ใช้ได้จริงคือตั้งกติกาให้ผู้ตรวจอ่านจากต้นฉบับ ไม่ใช่อ่านจากข้อสรุปรอบก่อน แล้วบังคับให้ทุกข้อค้นพบมีหลักฐานที่รันซ้ำได้แนบมาด้วย
พอดีผมมีสเปรดชีตงานคำนวณโครงสร้างอยู่ชุดหนึ่ง เป็นไฟล์ที่คนใช้กันมานาน มีสูตรกระจายอยู่ 35 ชีต นับรวมแล้วแปดพันกว่าเซลล์ ผมอยากได้เป็นเว็บที่เปิดใช้บนมือถือหน้างานได้ เลยให้เอเจนต์ AI พอร์ตสูตรออกมาเป็นโค้ดฝั่งเว็บ งานเดินเร็วมาก แต่พอทำเสร็จผมกลับไม่กล้าใช้ เพราะไม่มีทางรู้เลยว่าสูตรที่มันพอร์ตออกมาตรงกับต้นฉบับจริงหรือแค่ "หน้าตาเหมือน" ผมเลยลองวิธีที่ตอนแรกฟังดูงี่เง่า คือให้เอเจนต์อีกค่ายที่ไม่ได้เขียนโค้ดชุดนี้มาเป็นคนตรวจ แล้ววนแบบนั้นสามรอบ
กระบวนการในบทความนี้เป็นการตรวจสอบซอฟต์แวร์ว่าคำนวณตรงกับไฟล์ต้นฉบับหรือไม่เท่านั้น ไม่ได้ทำให้ผลลัพธ์กลายเป็นสิ่งที่ใช้อ้างอิงในงานที่ต้องมีผู้รับผิดชอบตามกฎหมาย งานที่ต้องมีคนเซ็นรับรอง ยังต้องมีคนเซ็นรับรองเหมือนเดิมทุกกรณี
ปัญหาจริงคือเราไม่มีตัวเทียบ
เวลาพอร์ตโค้ดจากภาษาหนึ่งไปอีกภาษา เรามีเทสเดิมเป็นตัวจับ แต่พอร์ตจากสเปรดชีตไม่มีอะไรแบบนั้นเลย สิ่งที่มีคือเซลล์ที่เก็บทั้งสูตรและค่าที่คำนวณไว้ล่าสุด นั่นแหละคือจุดเริ่ม
สิ่งแรกที่ผมสั่งให้ทำก่อนแตะโค้ดสักบรรทัด คือ ทำบัญชีสูตรทั้งเล่มออกมาก่อน ให้รู้ว่ากำลังเจอกับอะไรอยู่ ไม่ใช่ไล่พอร์ตไปเรื่อย ๆ แล้วค่อยมาเดาว่าครบหรือยัง
import json, openpyxl
wb = openpyxl.load_workbook('workbook.xlsm', data_only=False)
inv = {}
for ws in wb.worksheets:
cells = [(c.coordinate, c.value) for row in ws.iter_rows() for c in row
if isinstance(c.value, str) and c.value.startswith('=')]
if cells:
inv[ws.title] = cells
print('sheets with formulas:', len(inv))
print('formula cells :', sum(len(v) for v in inv.values()))
json.dump(inv, open('formula_inventory.json', 'w'), ensure_ascii=False)
พอเห็นตัวเลขนี้ผมเลิกคิดเรื่อง "พอร์ตให้ครบทุกเซลล์" ทันที เพราะเซลล์ส่วนใหญ่เป็นตัวช่วย เป็นตัวควบคุมฟอร์ม หรือเป็นสูตรซ่อนที่ไม่ได้ออกหน้ารายงาน สิ่งที่ต้องตรงคือชุดสูตรที่ให้ผลลัพธ์ออกหน้าจอ ซึ่งน้อยกว่ามาก แต่ต้องระบุให้ชัดว่าอันไหนบ้าง
กติกาข้อแรก: ผู้ตรวจอ่านจากต้นฉบับ ไม่ใช่จากข้อสรุปรอบก่อน
ผมเจอตั้งแต่รอบแรกว่า ถ้าเปิดให้ผู้ตรวจอ่านรายงาน audit รอบก่อนได้ มันจะเริ่มจาก "รอบที่แล้วสรุปว่าผ่าน" แล้วไล่หาเหตุผลมาสนับสนุน แทนที่จะตรวจใหม่จริง ๆ ผลคือได้รายงานที่ยาวขึ้นแต่ไม่ได้เจออะไรเพิ่ม
กติกาที่ผมตั้งแล้วใช้ได้ มีสี่ข้อ
แก้ได้เฉพาะซอร์ส
- แก้ไฟล์โค้ดกับไฟล์เทสของตัวเอง
- ห้ามแก้ไฟล์รายงาน audit ทุกกรณี
- ตอบข้อค้นพบด้วยการแก้โค้ดหรือด้วยหลักฐานว่าไม่ต้องแก้
- ห้ามลดระดับข้อค้นพบเอง
อ่านต้นฉบับเป็นฐาน
- อ่านสูตรจากไฟล์ต้นฉบับตรง ๆ ไม่ใช่จากรายงานรอบก่อน
- อ่านโค้ดสถานะปัจจุบัน ไม่ใช่ diff
- ทุกข้อค้นพบต้องมีคำสั่งที่รันซ้ำได้แนบ
- เขียนได้เฉพาะไฟล์รายงานของตัวเอง
กติกาชุดนี้ผมเขียนเป็นไฟล์เดียววางไว้ในโปรเจกต์ แล้วให้เอเจนต์ทั้งสองฝั่งอ่านไฟล์นี้เป็นบรรทัดแรกของทุกรอบ ไม่ได้พิมพ์สั่งใหม่ทุกครั้ง เพราะพิมพ์ใหม่ทุกครั้งแล้วกติกาจะค่อย ๆ เพี้ยนไปเอง
AUDIT-RULES.md (ทั้งสองฝั่งต้องอ่านก่อนเริ่มทุกรอบ)
1. ต้นฉบับคือไฟล์สเปรดชีตเท่านั้น รายงาน audit รอบก่อนไม่ใช่ต้นฉบับ
2. อ่านโค้ดจากสถานะปัจจุบันของไฟล์ ห้ามตัดสินจาก diff อย่างเดียว
3. ทุกข้อค้นพบต้องมี: อ้างอิงเซลล์ต้นฉบับ + รูปสูตรสองฝั่ง + คำสั่งรันซ้ำ
4. ผู้เขียนโค้ดแก้ได้เฉพาะซอร์สและเทส ห้ามแก้ไฟล์รายงาน
5. ผู้ตรวจเขียนได้เฉพาะรายงานของตัวเอง ห้ามแก้ซอร์ส
6. จบรอบต้องมีหัวข้อ "สิ่งที่ยังไม่ได้ตรวจ" เสมอ
ข้อที่ผมว่าคนมองข้ามมากสุดคือข้อ "อ่านโค้ดสถานะปัจจุบัน ไม่ใช่ diff" เพราะถ้าให้ดู diff ผู้ตรวจจะเห็นแค่สิ่งที่เปลี่ยน แล้วมองไม่เห็นบั๊กที่นั่งอยู่ในโค้ดเดิมมาตั้งแต่ต้น ซึ่งรอบผมเจอแบบนั้นสองข้อ
จัดชั้นข้อค้นพบ แล้วบังคับให้มีหลักฐาน
รายงานที่ใช้ได้จริงต้องอ่านแล้วรู้ว่าต้องทำอะไรก่อน ผมเลยบังคับให้ทุกข้อค้นพบมีโครงเดียวกันหมด แล้วจัดชั้นเป็นสามระดับ P0 คือผิดเลขต้องแก้ก่อนใช้ P1 คือวิธีต่างจากต้นฉบับแต่ผลยังยอมรับได้ P2 คือเรื่องการแสดงผลหรือถ้อยคำ
{
"id": "P0-03",
"severity": "P0",
"area": "แรงเฉือนของบันได",
"source_ref": "ชีต Stair1 เซลล์ AC7",
"source_shape": "(w * L / 2) - (w * d)",
"code_ref": "calc-rc.js :: rcStairCalc",
"code_shape": "w * L / 2",
"evidence_cmd": "node validate_excel.js --case stair1",
"observed": "1359.607",
"expected": "1271.026",
"fix": "หักระยะ effective depth ออกจากแรงเฉือนที่ฐานรองรับ"
}
ตรง source_shape กับ code_shape คือหัวใจ เพราะมันบังคับให้ผู้ตรวจเขียนรูปสูตรทั้งสองฝั่งออกมาวางเทียบกัน ไม่ใช่เขียนว่า "ตรวจแล้วไม่ตรง" ลอย ๆ แล้วหลายครั้งแค่เขียนสองบรรทัดนี้ออกมาก็เห็นเองเลยว่าใครผิด
ฝั่งหลักฐาน ผมไม่รับข้อค้นพบที่ไม่มีคำสั่งรันซ้ำได้แนบมาด้วย
node validate.js # PASS 108/108 | worst diff = 0.000%
node validate_excel.js # PASS 55/55 fixtures
node --check calc-rc.js calc-foundation.js templates.js
คลาสความผิดพลาดที่ AI พลาดซ้ำทุกรอบ
อันนี้คือสิ่งที่ผมได้จากการวนสามรอบแล้วมานั่งดูว่าที่เจอทั้งหมดจัดกลุ่มได้เป็นอะไรบ้าง ปรากฏว่ามันซ้ำกันอยู่แค่สามแบบ และไม่เกี่ยวกับว่าใช้เอเจนต์ค่ายไหน
หนึ่ง พอร์ตสูตรออกมาเป็นรูป algebra ที่เท่ากันแต่คนละรูป ตัวเลขออกมาตรงเป๊ะทุกเคสที่เทส แต่พอเปลี่ยนสภาพโหลดไปอีกแบบแล้วไม่ตรง เพราะต้นฉบับมีวงเล็บครอบตัวหารไว้ทั้งก้อน ส่วนโค้ดหารทีละตัว ในเคสปกติมันเท่ากัน พอมีเทอมที่เป็นศูนย์หรือติดลบเข้ามาถึงแตก
// รูปที่ต้นฉบับเขียน: (w / (8 * L^2)) * (L + a)^2 * (L - a)^2
const m1 = (w / (8 * L ** 2)) * (L + a) ** 2 * (L - a) ** 2;
// รูปที่เอเจนต์พอร์ตมารอบแรก — เท่ากันเฉพาะบางช่วงค่า
const wrong = w / 8 / L ** 2 * ((L + a) * (L - a)) ** 2;
สอง ใช้ตารางมาตรฐานผิดฉบับ อันนี้อันตรายกว่าข้อแรกมาก เพราะโค้ดถูกทุกบรรทัด แต่ค่าคงที่ที่เอามาใส่มาจากมาตรฐานคนละปี ตัวเลขที่ได้เลยผิดทั้งแผง โดยที่เทสทุกตัวผ่านหมดเพราะเทสเองก็ใช้ค่าจากตารางเดียวกันนั้น ทางแก้ที่ผมใช้คือ ห้ามฝังค่าจากตารางมาตรฐานลงในโค้ดโดยไม่ระบุที่มา ทุกค่าต้องมาจากไฟล์ข้อมูลที่มีฟิลด์บอกฉบับและปีกำกับ
สาม คัดลอกค่าจากตารางเหลื่อมคอลัมน์ อันนี้เป็นความผิดพลาดแบบเดียวกับที่คนทำ คือตารางในต้นฉบับมีคอลัมน์ที่หัวตารางรวมเซลล์อยู่ พอไล่อ่านทีละแถวเลยหยิบค่าจากคอลัมน์ถัดไปหนึ่งช่อง ตรวจด้วยตาไม่มีทางเห็น เจอได้ทางเดียวคือเขียนสคริปต์อ่านค่าจากต้นฉบับมาเทียบกับที่ฝังในโค้ดทีละตัว
# เทียบค่าคงที่ในโค้ดกับค่าที่อ่านจากต้นฉบับ ทีละตัว ไม่เชื่อสายตา
import json
src = json.load(open('table_from_source.json'))
code = json.load(open('table_in_code.json'))
for k in sorted(src):
if abs(src[k] - code.get(k, float('nan'))) > 1e-9:
print(f'MISMATCH {k}: source={src[k]} code={code.get(k)}')
ทั้งสามคลาสนี้รอดเทสได้หมดถ้าเทสถูกเขียนขึ้นจากความเข้าใจชุดเดียวกับที่เขียนโค้ด ค่าที่ใช้เทียบต้องมาจากต้นฉบับเสมอ ไม่ใช่จากการรันโค้ดตัวเองแล้วบันทึกผลไว้เป็นค่าคาดหวัง
ชั้นความเชื่อมั่น กันการโม้เกินหลักฐาน
ปัญหาที่โผล่รอบสองคือ หน้าเว็บกับเอกสารเริ่มมีข้อความประมาณว่า "ตรงกับต้นฉบับทุกกรณี ส่วนต่างศูนย์เปอร์เซ็นต์" ซึ่งเกินจริง เพราะที่ผ่านคือชุดที่ทดสอบแล้วเท่านั้น ไม่ใช่ทั้งแปดพันเซลล์ ผมเลยบังคับให้ทุกคำกล่าวอ้างต้องติดป้ายหนึ่งในสามระดับนี้เสมอ
ป้ายที่สามสำคัญไม่แพ้สองอันแรก เพราะบางจุดผมตั้งใจให้ต่างจากต้นฉบับจริง ๆ เช่นเลือกวิธีที่อนุรักษ์กว่า ถ้าไม่มีป้ายนี้ ผู้ตรวจรอบถัดไปจะรายงานเป็นความผิดพลาดซ้ำทุกรอบ เสียเวลาทั้งสองฝั่ง
กฎที่ผูกกับป้าย มีสองข้อ หนึ่งคือหัวข้อที่ยังไม่มีเคสเทียบเก็บไว้เป็นไฟล์ ห้ามติดป้าย validated exact เด็ดขาด สองคือถ้อยคำบนหน้าเว็บต้องดึงจากตัวเลขชุดเดียวกับที่ validator พิมพ์ออกมา ห้ามพิมพ์มือ ไม่งั้นวันหนึ่งเทสพังแล้วหน้าเว็บยังโชว์ว่าผ่านอยู่
{
"generated_by": "validate_excel.js",
"generated_at": "2026-11-20T09:14:22+07:00",
"claims": [
{ "area": "แผ่นพื้นสองทาง", "level": "validated exact",
"fixtures": 12, "worst_diff": "0.000%" },
{ "area": "จันทันตะเข้", "level": "formula aligned",
"fixtures": 0, "note": "สูตรตรงรูป แต่ยังไม่มีเคสเทียบเก็บไว้" },
{ "area": "แรงเฉือนฐานราก", "level": "standard extension",
"fixtures": 4, "note": "ตั้งใจใช้วิธีอนุรักษ์กว่าต้นฉบับ" }
]
}
หน้าเว็บอ่านไฟล์นี้ไปเรนเดอร์ตรง ๆ เลยไม่มีทางที่ข้อความจะเกินหลักฐาน เพราะไฟล์นี้เกิดจากการรัน validator เท่านั้น ถ้าไม่ได้รัน ไฟล์ก็เก่าแล้วมีวันที่กำกับให้เห็น
ลูปสามรอบ ทำอะไรบ้าง
- รอบที่หนึ่ง ผู้ตรวจอ่านต้นฉบับกับโค้ดปัจจุบัน ออกรายงานจัดชั้น P0/P1/P2 พร้อมหลักฐานต่อข้อ ฝั่งผู้เขียนยังไม่แตะอะไร
- ผู้เขียนแก้เฉพาะ P0 แล้วเพิ่มเคสเทียบจากต้นฉบับสำหรับทุกข้อที่แก้ ห้ามแก้รายงาน
- รอบที่สอง ผู้ตรวจเริ่มใหม่จากต้นฉบับอีกครั้งโดยไม่อ่านรายงานรอบแรก แล้วค่อยเอาสองรายงานมาเทียบกันตอนท้าย
- เก็บกวาดถ้อยคำที่โม้เกินหลักฐาน ติดป้ายสามระดับให้ครบทุกหัวข้อ
- รอบที่สาม ตรวจเฉพาะเรื่องรูปสูตรกับการแสดงผล เช่น เครื่องหมายคูณหารและวงเล็บที่แสดงบนหน้าจอ ว่าอ่านแล้วตีความได้ทางเดียว
- ปิดรอบด้วยการบันทึกว่าอะไรยังไม่ได้ตรวจ ไม่ใช่บันทึกแค่ว่าอะไรผ่าน
ข้อสุดท้ายคือสิ่งที่ผมคิดว่าได้ประโยชน์สูงสุดจากทั้งกระบวนการ รายงานที่จบด้วยรายการ "สิ่งที่ยังไม่ได้ตรวจ" ทำให้รอบถัดไปเริ่มจากจุดที่ยังไม่มีใครมอง แทนที่จะวนตรวจของเดิมซ้ำ
ขอบเขตที่ลูปนี้ไม่ครอบ
พูดให้ตรง กระบวนการนี้ตอบได้เรื่องเดียวคือ โค้ดคำนวณตรงกับไฟล์ต้นฉบับในชุดที่ทดสอบแล้วหรือไม่ มันไม่ตอบว่าไฟล์ต้นฉบับถูกต้องตามหลักวิชาหรือเปล่า ไม่ตอบว่าเหมาะกับงานที่จะเอาไปใช้หรือเปล่า และไม่แทนการตรวจสอบโดยผู้มีคุณวุฒิ
ส่วนที่ยังเป็นช่องว่างจริง ๆ ในงานผมคือความครอบคลุม ไม่ใช่ความถูกต้องที่รู้ตัว ชีตหลายตัวมีสูตรอยู่ในบัญชีแล้วแต่ยังไม่ได้ทำเคสเทียบ ตราบใดที่ยังไม่ได้ทำ ผมก็ติดป้ายไว้ตามจริงว่ายังไม่ได้ตรวจ ไม่ไปเขียนว่าผ่าน
อีกเรื่องที่ผมไม่ทำคือเปรียบเทียบว่าเอเจนต์ค่ายไหนเก่งกว่ากัน เพราะที่เจอคือมันพลาดคนละจุดกัน ไม่ใช่ตัวหนึ่งดีกว่าอีกตัวทุกด้าน ประโยชน์ของการใช้คนละค่ายมาตรวจ อยู่ที่มันไม่ได้ พลาดแบบเดียวกัน ต่างหาก ซึ่งเป็นเหตุผลเดียวกับที่เราให้คนอื่นอ่านโค้ดเราอยู่แล้ว ใครจะเอาไปปรับใช้กี่รอบก็ตามเวลาที่มีครับ ตรงไหนผมอธิบายตกหล่นหรือมีกติกาที่ควรเพิ่ม เดี๋ยวกลับมาแก้เพิ่มให้ภายหลังครับ
เคยเจองานแบบนี้ไหม? ติดตรงไหน?
มาแลกวิธีกันต่อในบอร์ด LinuxThai



ความคิดเห็น 0
ยังไม่มีความคิดเห็น — เป็นคนแรกเลย