รีวิวโค้ดที่ AI เขียน: จุดที่ AI พลาดบ่อย และ checklist ก่อน merge
AI เขียนโค้ดเร็ว แต่ชอบแต่ง package ที่ไม่มีจริง กลืน error เงียบ และเขียนเทสต์ที่ไม่ได้เทสต์อะไร checklist รีวิวทีละข้อ diff เล็ก รันก่อนอ่าน และคนเซ็นรับ
- AI แต่งชื่อ package ที่ไม่มีอยู่จริงได้ และชื่อที่แต่งมักซ้ำเดิม คนร้ายจึงจดชื่อนั้นรอไว้ได้ (slopsquatting)
- ตรวจ dependency ใหม่ทุกตัวกับ registry ก่อนติดตั้ง ใช้คำสั่งเดียวจบ
- ให้ AI ส่งงานเป็น diff เล็ก รันเทสต์และ linter ก่อนเริ่มอ่าน แล้วค่อยอ่านเฉพาะจุดเสี่ยง
- คนที่กด merge คือคนรับผิดชอบโค้ด ไม่ใช่ AI
AI เขียนโค้ดได้เร็วกว่าคนหลายเท่า แต่ความเร็วนั้นย้ายภาระไปอยู่ที่คนรีวิว โค้ดที่ดูเรียบร้อยและรันผ่านอาจเรียก package ที่ไม่มีอยู่จริง กลืน error ทิ้ง หรือเปิดช่องโหว่ด้วยค่าเริ่มต้นที่ไม่ปลอดภัย บทความนี้รวมจุดที่ AI พลาดบ่อย พร้อม checklist ที่ใช้ได้ทันทีก่อนกด merge
ข้อเท็จจริงตรวจกับงานวิจัย USENIX Security 2025, ACM CCS 2023, รายงาน Veracode 2025 และแนวทางรีวิวโค้ดของ Google ส่วนคำสั่งทดสอบบน npm 10.9, pip 25.0, Python 3.12
ทำไมโค้ดจาก AI ต้องรีวิวต่างจากโค้ดคน
คนเขียนผิดมักผิดแบบมีร่องรอย ตั้งชื่อตัวแปรแปลก ๆ หรือเขียนไม่จบ แต่ AI ผิดแบบ ดูถูกต้อง โค้ดสวย มี comment ครบ ชื่อฟังก์ชันสมเหตุสมผล ทำให้คนรีวิวเผลอเชื่อ
ตัวเลขจากงานวิจัยที่ตรวจได้
- Veracode 2025 ทดสอบโมเดลมากกว่า 100 ตัว กับโจทย์ 80 ข้อ พบว่า 45% ของกรณีทดสอบ มีช่องโหว่ Java แย่สุดเกิน 70% ส่วน Python, C#, JavaScript อยู่ที่ 38-45% และโมเดลใหญ่ไม่ได้ดีกว่าโมเดลเล็กอย่างมีนัยสำคัญ
- Perry และคณะ (CCS 2023) คนที่ใช้ผู้ช่วย AI เขียนโค้ดปลอดภัยน้อยกว่ากลุ่มที่ไม่ใช้ และ มั่นใจว่าโค้ดตัวเองปลอดภัยมากกว่า ส่วนคนที่ไว้ใจ AI น้อยกว่าและแก้ prompt หลายรอบ ได้โค้ดที่มีช่องโหว่น้อยกว่า
ข้อสรุปคือ ปัญหาไม่ได้อยู่ที่โมเดลตัวไหน แต่อยู่ที่ขั้นตอนหลัง AI ส่งงาน
จุดที่ AI พลาดบ่อย
1. แต่ง API หรือ package ที่ไม่มีอยู่จริง
งานวิจัย “We Have a Package for You!” (USENIX Security 2025) ให้โมเดล 16 ตัวเขียนโค้ด Python และ JavaScript 576,000 ตัวอย่าง จาก package ที่แนะนำ 2.23 ล้านครั้ง มี 440,445 ครั้ง (19.7%) เป็นชื่อที่ไม่มีอยู่จริง รวม 205,474 ชื่อไม่ซ้ำ โมเดลเชิงพาณิชย์เฉลี่ยอย่างน้อย 5.2% โมเดล open-source 21.7%
ส่วนที่อันตรายคือ ชื่อที่แต่งมักซ้ำเดิม ถามซ้ำ 10 รอบ 43% ของชื่อปลอมโผล่ทุกรอบ และ 58% โผล่มากกว่าหนึ่งครั้ง คนร้ายจึงจดชื่อนั้นบน npm หรือ PyPI รอไว้ ใส่ malware แล้วรอให้มีคน npm install ตาม AI เรียกว่า slopsquatting (Seth Larson จาก Python Software Foundation ตั้งชื่อนี้ ล้อกับ typosquatting)
API ที่ไม่มีจริงก็เช่นกัน เช่น เรียก method ที่เวอร์ชันนั้นไม่มี หรือ option ที่ไม่เคยมีใน library จะพังตอนรัน ไม่ใช่ตอนอ่าน
2. ไม่คิดถึง edge case
AI เขียน happy path เก่ง แต่ลืม list ว่าง ค่า 0 ค่าติดลบ เวลาข้าม timezone ตัวอักษรไทยใน input หรือ request ที่ซ้ำสองครั้ง ถามตัวเองทุกครั้งว่า “ถ้า input ว่าง ใหญ่มาก หรือผิดรูปแบบ จะเกิดอะไร”
3. กลืน error เงียบ
try:
save_order(order)
except Exception:
pass # บันทึกไม่สำเร็จ แต่ไม่มีใครรู้
โค้ดแบบนี้ทำให้เทสต์ผ่านและหน้าจอไม่ขึ้น error แต่ข้อมูลหาย ทุก except ต้องทำอย่างใดอย่างหนึ่ง: จัดการจริง, log แล้วโยนต่อ หรือคืนค่าที่ผู้เรียกรู้ว่าล้มเหลว
4. เทสต์ที่ไม่ได้เทสต์อะไร
def apply_discount(price, pct):
return price * (1 - pct / 100)
def test_apply_discount():
result = apply_discount(100, 10)
assert result is not None # ผ่านแม้ผลจะผิด
เทสต์นี้ผ่านแม้สูตรผิด ที่ต้องการคือ assert ค่าที่ถูกจริง และครอบ edge case
import pytest
def test_apply_discount():
assert apply_discount(100, 10) == 90
assert apply_discount(100, 0) == 100
assert apply_discount(100, 100) == 0
def test_discount_over_100_is_rejected():
with pytest.raises(ValueError):
apply_discount(100, 150) # จะ fail จนกว่าโค้ดจะเช็ก pct
อีกแบบที่เจอบ่อยคือ mock ตัวฟังก์ชันที่กำลังทดสอบเสียเอง หรือแก้ค่า expected ให้ตรงกับผลที่โค้ดผิดคืนมา ถ้า AI แก้เทสต์ให้ผ่าน ให้ดูว่ามันแก้โค้ดหรือแก้เทสต์
5. ค่าเริ่มต้นที่ไม่ปลอดภัย
Veracode พบว่าโมเดลป้องกัน cross-site scripting (CWE-80) ไม่ได้ใน 86% ของกรณี และ log injection (CWE-117) ใน 88% ของกรณี สิ่งที่เจอบ่อยในงานจริง
verify=Falseตอนเรียก HTTPS เพื่อให้ “รันผ่าน”- CORS
*หรือdebug=Trueติดไปถึง production - ต่อ string เป็น SQL แทน parameterized query
- เอา input ผู้ใช้ใส่ HTML หรือ log ตรง ๆ
- key หรือ password ฝังในโค้ดเป็นตัวอย่าง แล้วลืมเอาออก
ตัวอย่างคำนวณ: โอกาสเจอ package ปลอมสักตัว
สมมติให้ AI เสนอ dependency ทั้งโปรเจกต์ 10 ตัว และแต่ละตัวมีโอกาสเป็นชื่อปลอม 5.2% (ค่าเฉลี่ยขั้นต่ำของโมเดลเชิงพาณิชย์ในงานวิจัย) และสมมติว่าแต่ละตัวเป็นอิสระต่อกัน ซึ่งเป็นการลดรูปเพื่อให้เห็นภาพ
- โอกาสที่ตัวหนึ่งเป็นของจริง = 1 − 0.052 = 0.948
- โอกาสที่ทั้ง 10 ตัวจริงหมด = 0.948¹⁰ ≈ 0.586
- โอกาสที่มีปลอมอย่างน้อยหนึ่งตัว = 1 − 0.586 ≈ 41%
| dependency ที่ AI เสนอ | อัตรา 5.2% | อัตรา 21.7% |
|---|---|---|
| 10 ตัว | ≈ 41% | ≈ 91% |
| 20 ตัว | ≈ 66% | ≈ 99% |
ตัวเลขจริงขึ้นกับโมเดลและโจทย์ แต่บทเรียนชัด ตรวจทุกตัว ไม่ใช่สุ่มตรวจ
ตรวจ package ก่อนติดตั้ง
ชื่อที่ไม่มีอยู่จะได้ 404 ทันที ชื่อที่มีอยู่ให้ดูวันที่สร้าง ถ้าเพิ่งสร้างไม่กี่วันและไม่มีใครใช้ ให้สงสัยไว้ก่อน
# npm: มีไหม สร้างเมื่อไร
npm view <ชื่อ-package> time.created
# PyPI: 404 = ไม่มีชื่อนี้
curl -s -o /dev/null -w "%{http_code}\n" https://pypi.org/pypi/<ชื่อ-package>/json
แล้วเปิดหน้า package ดู repo ต้นทาง ผู้ดูแล และจำนวนดาวน์โหลด รายละเอียดการล็อกเวอร์ชันและ lockfile อยู่ในบทความความปลอดภัยของ npm และ pip
Checklist ก่อน merge
| # | ตรวจอะไร | วิธีเร็วที่สุด |
|---|---|---|
| 1 | diff เล็กพอจะอ่านจบ | git diff --stat ถ้าเกินหลายร้อยบรรทัด ให้ AI แบ่ง |
| 2 | dependency ใหม่มีจริงและน่าเชื่อถือ | npm view / PyPI JSON ทุกตัว |
| 3 | รันผ่านจริง | เทสต์ + linter + type check ก่อนอ่าน |
| 4 | เทสต์ assert ค่าที่ถูก | อ่านเทสต์ก่อนโค้ด หา is not None, mock ตัวเอง |
| 5 | error ไม่ถูกกลืน | ค้น except: pass, catch {} ว่าง |
| 6 | edge case | input ว่าง / 0 / ใหญ่มาก / ซ้ำ |
| 7 | ค่าความปลอดภัย | ค้น verify=False, * ใน CORS, debug, SQL ต่อ string, secret ในโค้ด |
| 8 | API ที่เรียกมีจริงในเวอร์ชันที่ใช้ | เปิดเอกสารของเวอร์ชันที่ล็อกไว้ |
| 9 | คนเข้าใจทุกบรรทัดที่ merge | อธิบายให้ตัวเองฟังได้ ถ้าไม่ได้ ถาม AI หรือเขียนใหม่ |
diff เล็ก แล้วรันก่อนอ่าน
แนวทางรีวิวโค้ดของ Google บอกว่าราว 100 บรรทัดมักพอดี 1,000 บรรทัดมักใหญ่เกินไป การเปลี่ยนเล็กรีวิวได้ละเอียดกว่า หาบั๊กง่ายกว่า และย้อนกลับง่ายกว่า กับ AI ยิ่งสำคัญ เพราะมันพร้อมจะส่ง 1,000 บรรทัดในนาทีเดียว สั่งให้ทำทีละขั้น หนึ่งขั้นหนึ่ง commit
ลำดับที่ใช้จริง
- รันก่อน เทสต์, linter, type check, build ถ้าพังไม่ต้องอ่าน ส่งกลับให้ AI แก้
- อ่านเทสต์ ดูว่าเทสต์พิสูจน์สิ่งที่ต้องการจริง
- อ่านโค้ดเฉพาะจุดเสี่ยง dependency ใหม่, จุดรับ input, auth, เงิน, การลบข้อมูล
- ลองบนเครื่องจริง เปิดหน้าจอ ลองกรณีผิด ไม่ใช่แค่กรณีถูก
ถ้าโค้ดที่ AI เขียนรันได้บนเครื่องเราแต่พังบน server บทความรันได้บนเครื่องผมมีวิธีกันไว้
AI เขียนโค้ดได้ แต่เซ็นรับโค้ดไม่ได้ คนที่กด merge คือคนรับผิดชอบ
ข้อผิดพลาดที่เจอบ่อย
- อ่านแค่ว่าโค้ดดูดี โค้ด AI ดูดีเสมอ ต้องรันและตรวจ assert
- ติดตั้ง package ตามที่ AI บอกทันที ตรวจกับ registry ก่อนทุกครั้ง
- ให้ AI แก้เทสต์จนผ่าน แล้วไม่ดูว่ามันแก้โค้ดหรือแก้ค่า expected
- รับ diff ก้อนใหญ่เพราะรีบ ก้อนใหญ่ = รีวิวผ่าน ๆ = บั๊กหลุด
- ไว้ใจ AI รีวิว AI ใช้เป็นด่านเสริมได้ แต่คนต้องเป็นด่านสุดท้าย
- ลืมว่า AI อ่านเนื้อหาภายนอกได้ ถ้า agent อ่านเว็บหรือ issue แล้วเขียนโค้ด เนื้อหานั้นอาจสั่งมันได้ ดูการป้องกัน prompt injection
ด่านที่ถูกที่สุดคือ diff เล็ก ด่านที่ถูกรองลงมาคือรันก่อนอ่าน
แหล่งอ้างอิง
- We Have a Package for You! A Comprehensive Analysis of Package Hallucinations by Code Generating LLMs — arXiv (USENIX Security 2025)
- Do Users Write More Insecure Code with AI Assistants? — arXiv (ACM CCS 2023)
- AI-Generated Code Poses Major Security Risks in Nearly Half of All Development Tasks — Veracode
- Small CLs — Google Engineering Practices
- AI hallucinations lead to a new cyber threat: Slopsquatting — CSO Online
คำถามที่พบบ่อย
ให้ AI อีกตัวรีวิวแทนคนได้ไหม
ใช้เป็นด่านแรกได้ ช่วยจับเรื่องที่มองข้ามง่าย แต่ไม่ควรเป็นด่านสุดท้าย งานวิจัย CCS 2023 พบว่าคนที่ใช้ผู้ช่วย AI เขียนโค้ดที่ปลอดภัยน้อยกว่า และมั่นใจว่าโค้ดปลอดภัยมากกว่า ความมั่นใจเกินจริงนี้คือสิ่งที่คนรีวิวต้องกัน
diff ขนาดเท่าไรถึงเรียกว่าเล็ก
แนวทางรีวิวโค้ดของ Google บอกว่าราว 100 บรรทัดมักพอดี 1,000 บรรทัดมักใหญ่เกินไป และจำนวนไฟล์ก็มีผล 200 บรรทัดในไฟล์เดียวอาจโอเค แต่กระจาย 50 ไฟล์มักใหญ่ไป
เทสต์ผ่านหมดแล้ว ยังต้องอ่านโค้ดอีกไหม
ต้อง เพราะ AI เขียนทั้งโค้ดและเทสต์ ถ้าเทสต์แค่เช็กว่าผลไม่เป็น None มันจะผ่านแม้ตัวเลขผิด อ่านเทสต์ก่อนโค้ด ดูว่า assert ค่าที่ถูกจริงหรือเปล่า
คำสั่งและค่าตั้งในบทความทดสอบบนระบบตัวอย่าง ลองบนเครื่องทดสอบก่อนใช้กับระบบจริงเสมอ