EST. 2026 — บันทึกงานเซิร์ฟเวอร์จริง SYSADMIN · SECURITY · AI · DIRECTADMIN เล่มที่ 01
THE SYSADMIN ALMANAC
หน้าหลัก / AI / ให้ AI คนละค่ายมาตรวจโค้ดที่ AI เขียน — กระบวนการ ไม่ใช่ความศรัทธา
AI

ให้ AI คนละค่ายมาตรวจโค้ดที่ AI เขียน — กระบวนการ ไม่ใช่ความศรัทธา

โดย Ton-Or ·26 ต.ค. 2026·อ่าน 9 นาที
ให้ 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)
8,355เซลล์ที่เป็นสูตร
35ชีตที่มีสูตร
657สูตรหลายตัวดำเนินการ
3 รอบตรวจอิสระ

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

กติกาข้อแรก: ผู้ตรวจอ่านจากต้นฉบับ ไม่ใช่จากข้อสรุปรอบก่อน

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

กติกาที่ผมตั้งแล้วใช้ได้ มีสี่ข้อ

ฝั่งผู้เขียนโค้ด

แก้ได้เฉพาะซอร์ส

  • แก้ไฟล์โค้ดกับไฟล์เทสของตัวเอง
  • ห้ามแก้ไฟล์รายงาน audit ทุกกรณี
  • ตอบข้อค้นพบด้วยการแก้โค้ดหรือด้วยหลักฐานว่าไม่ต้องแก้
  • ห้ามลดระดับข้อค้นพบเอง
ฝั่งผู้ตรวจ

อ่านต้นฉบับเป็นฐาน

  • อ่านสูตรจากไฟล์ต้นฉบับตรง ๆ ไม่ใช่จากรายงานรอบก่อน
  • อ่านโค้ดสถานะปัจจุบัน ไม่ใช่ diff
  • ทุกข้อค้นพบต้องมีคำสั่งที่รันซ้ำได้แนบ
  • เขียนได้เฉพาะไฟล์รายงานของตัวเอง

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

ผู้เขียนโค้ดแก้ซอร์สกับเทสเท่านั้นห้ามแตะรายงานผู้ตรวจคนละค่ายกับผู้เขียนเขียนได้เฉพาะรายงานสเปรดชีตต้นฉบับโค้ดและเทสรายงาน auditอ่านเป็นฐานแก้ได้อ่านโค้ดจริงห้ามแก้เขียนได้ผู้ตรวจไม่อ่านรายงานรอบก่อน เริ่มจากต้นฉบับทุกรอบ
สิทธิ์อ่านและเขียนของสองฝั่ง ผู้ตรวจใช้สเปรดชีตต้นฉบับเป็นฐานเสมอ ผู้เขียนโค้ดแตะรายงานไม่ได้
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มีเคสเทียบ ผ่านจริง→formula alignedสูตรตรงรูป ยังไม่ครบเคส→standard extensionตั้งใจต่างจากต้นฉบับ

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

กฎที่ผูกกับป้าย มีสองข้อ หนึ่งคือหัวข้อที่ยังไม่มีเคสเทียบเก็บไว้เป็นไฟล์ ห้ามติดป้าย 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 เท่านั้น ถ้าไม่ได้รัน ไฟล์ก็เก่าแล้วมีวันที่กำกับให้เห็น

ลูปสามรอบ ทำอะไรบ้าง

  1. รอบที่หนึ่ง ผู้ตรวจอ่านต้นฉบับกับโค้ดปัจจุบัน ออกรายงานจัดชั้น P0/P1/P2 พร้อมหลักฐานต่อข้อ ฝั่งผู้เขียนยังไม่แตะอะไร
  2. ผู้เขียนแก้เฉพาะ P0 แล้วเพิ่มเคสเทียบจากต้นฉบับสำหรับทุกข้อที่แก้ ห้ามแก้รายงาน
  3. รอบที่สอง ผู้ตรวจเริ่มใหม่จากต้นฉบับอีกครั้งโดยไม่อ่านรายงานรอบแรก แล้วค่อยเอาสองรายงานมาเทียบกันตอนท้าย
  4. เก็บกวาดถ้อยคำที่โม้เกินหลักฐาน ติดป้ายสามระดับให้ครบทุกหัวข้อ
  5. รอบที่สาม ตรวจเฉพาะเรื่องรูปสูตรกับการแสดงผล เช่น เครื่องหมายคูณหารและวงเล็บที่แสดงบนหน้าจอ ว่าอ่านแล้วตีความได้ทางเดียว
  6. ปิดรอบด้วยการบันทึกว่าอะไรยังไม่ได้ตรวจ ไม่ใช่บันทึกแค่ว่าอะไรผ่าน

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

ขอบเขตที่ลูปนี้ไม่ครอบ

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

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

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

เคยเจองานแบบนี้ไหม? ติดตรงไหน?

มาแลกวิธีกันต่อในบอร์ด LinuxThai

→ คุยต่อในบอร์ด

ความคิดเห็น 0

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