รีวิวจริงที่ 1: HTTP JSON API
นี่คือรีวิวเต็มบทแรกจากทั้งหมดสี่บท แต่ละบทให้โปรแกรมสไตล์ AI ที่สมบูรณ์ ให้คุณลองหา bug ที่ปลูกไว้ด้วยนิสัยจากสี่บทแรกของโมดูลนี้ แล้วจึงแสดงผลลัพธ์จริงของเครื่องมือจริง พร้อมเวอร์ชันที่แก้แล้วซึ่งรันได้ถูกต้องจริง บทนี้เป็น JSON API เล็ก ๆ สำหรับสร้างและแสดงรายการ “items” — health check, route แสดงรายการ, และ route สร้างรายการ พร้อม bug สี่ตัวจาก AI bug catalog ที่ปลูกไว้ในโค้ดประมาณ 60 บรรทัด
โปรแกรม
หัวข้อที่มีชื่อว่า “โปรแกรม”Output (verified on Node 24)
port looks fine relative to the minimum
health: {"ok":true}
created: {"id":1,"name":"widget"}demo นี้ print สามบรรทัด แล้ว crash ก่อนบรรทัดที่สี่ — ไม่มีบรรทัด malformed response: โผล่มาเลย
ผลลัพธ์จริง (stderr ตามหลังสามบรรทัดข้างบน):
Error [ERR_HTTP_HEADERS_SENT]: Cannot write headers after they are sent to the client at ServerResponse.writeHead (node:_http_server:356:11) at sendJson (...) at IncomingMessage.<anonymous> (...)ลองหา bug ก่อนอ่านต่อ
หัวข้อที่มีชื่อว่า “ลองหา bug ก่อนอ่านต่อ”bug ที่ปลูกไว้สี่ตัว — หนึ่งขอบเขตข้อมูล, หนึ่งอายุของทรัพยากร, หนึ่ง listener ที่หายไป, หนึ่ง type ลองเดาทั้งสี่ตัวก่อนเปิดคำตอบด้านล่าง
Bug 1 — body += chunk; ที่ไม่มีการเช็กขนาด
อาการ: handler ของ POST /items สะสม request body ทั้งหมดลงใน string เดียวโดยไม่มีขีดจำกัดบนเลย
หลักฐาน: ไม่มีอะไรใน demo run นี้ที่ทดสอบ body ขนาดใหญ่จริง ๆ ซึ่งนั่นแหละคืออันตราย — bug นี้มองไม่เห็นในทุก request ขนาดปกติ และจะเสียค่า memory ก็ต่อเมื่อ client (หรือผู้โจมตี) ส่งข้อมูลระดับกิกะไบต์
วิธีแก้: ติดตามจำนวน byte ที่สะสมไว้แล้วปฏิเสธ (413 แล้ว req.destroy()) เมื่อเกินขีดจำกัด MAX_BODY แบบเดียวกับ route ในบท JSON API
Bug 2 — sendJson(res, 500, ...) ต่อจาก sendJson(res, 400, ...) ทันที
อาการ: ใน request ที่มี JSON ผิดรูปแบบ catch block เรียก sendJson สองครั้ง — และ sendJson เรียก res.writeHead() แล้ว res.end() เสมอ
หลักฐาน: crash จริงข้างบน — ERR_HTTP_HEADERS_SENT ถูก throw จากการเรียก sendJson ครั้งที่สองที่ res.writeHead() เพราะ header ถูกส่งไปแล้วโดยการเรียกครั้งแรกก่อนหน้านั้นไม่กี่ขณะ
วิธีแก้: helper สำหรับส่ง response ทุกตัวต้องมี guard ป้องกันการทำงานซ้ำสองครั้ง: if (res.writableEnded) return; ที่ต้นฟังก์ชัน sendJson และ return ทันทีหลังการเรียก sendJson ครั้งแรกใน catch block (ซึ่งไฟล์นี้มีอยู่แล้ว — การเรียกครั้งที่สองเป็นแค่โค้ดที่ตายแล้วซึ่งไม่ควรถูกเขียนขึ้นมาตั้งแต่แรก)
Bug 3 — ไม่มี listener error เลย
อาการ: ทั้ง server และ res แต่ละตัวไม่มี handler .on('error', ...) เลย
หลักฐาน: นี่คือเหตุผลที่แท้จริงว่าทำไม bug 2 ถึง crash ทั้งโปรเซสแทนที่จะแค่ล้มเหลวหนึ่ง request — ERR_HTTP_HEADERS_SENT ถูกส่งเป็น event 'error' บน response stream และเมื่อไม่มี listener เลย Node ก็ถือว่ามันร้ายแรง
วิธีแก้: res.on('error', function (err) { console.error(...); }) ข้างใน request handler และ server.on('error', function (err) { console.error(...); }) ที่ระดับบนสุด เพื่อให้ response ที่พังลดระดับลงเหลือแค่ error ที่ log ไว้ แทนที่จะดึงทุก request ที่กำลังทำงานอยู่ล้มไปด้วย
Bug 4 — PORT > process.env.MIN_ALLOWED_PORT
อาการ: ข้อความที่ print กลับด้าน: '9000' > '10000' ถูกเทียบเป็น string (ทั้งสองฝั่งเป็น string) จึงเทียบแบบ lexicographic — '9' > '1' เป็น true — โค้ดจึง print “port looks fine” สำหรับพอร์ตที่จริง ๆ แล้วต่ำกว่าค่าต่ำสุดที่กำหนดไว้
หลักฐาน: บรรทัดแรกของผลลัพธ์จริงข้างบน — port looks fine relative to the minimum — เป็น branch ที่ผิด; 9000 < 10000 ควร print คำเตือนแทน
วิธีแก้: Number(process.env.PORT) > Number(process.env.MIN_ALLOWED_PORT)
เวอร์ชันที่แก้แล้ว
หัวข้อที่มีชื่อว่า “เวอร์ชันที่แก้แล้ว”Output (verified on Node 24)
warning: port is below the configured minimum
health: {"ok":true}
created: {"id":1,"name":"widget"}
malformed response: {"error":"malformed JSON"}ทั้งสี่ request เสร็จสมบูรณ์แล้ว: คำเตือนเรื่องพอร์ตถูกต้อง health check และการสร้างรายการที่ถูกต้องทั้งคู่ทำงานได้ และ request ที่ผิดรูปแบบได้ 400 ที่สะอาดแทนที่จะทำให้โปรเซส crash
เครื่องมือจับอะไรได้บ้าง
หัวข้อที่มีชื่อว่า “เครื่องมือจับอะไรได้บ้าง”npx eslint (flat config พร้อม eslint-plugin-n + eslint-plugin-promise) บนเวอร์ชันที่มี bug: ไม่มีปัญหาเลย ไม่มี bug ทั้งสี่ตัวที่ตรงกับกฎไหนเลย — ไม่มีการจำกัด body, การส่ง response ซ้ำสองครั้ง, ไม่มี error listener, และการเทียบ string กับ string ล้วนอยู่นอกเหนือสิ่งที่กฎที่ติดตั้งในคอร์สนี้ตรวจสอบ
node --trace-warnings บนเวอร์ชันที่มี bug: ไม่มี warning เลย — ไฟล์นี้ crash ด้วย uncaught exception ไม่ใช่ warning ดังนั้น --trace-warnings จึงไม่มีอะไรเพิ่มเติมให้ที่นี่ (flag นั้นมีไว้สำหรับ bug 12 ในแคตตาล็อก คือ listener leak ไม่ใช่ตัวนี้)
การรันไฟล์คือสิ่งที่จับ bug ได้จริงสามใน สี่ตัว (2, 3, และ 4 ทั้งหมดเห็นได้ในผลลัพธ์จริงข้างบน); bug 1 (ไม่จำกัด body) ไม่มีอาการที่เห็นได้เลยใน run นี้ — ต้องใช้ request ที่ขนาดใหญ่เกิน ไม่ใช่แค่ผิดรูปแบบ ถึงจะสังเกตเห็นมันได้ เหมือนกับ pattern 9 ใน AI bug catalog เป๊ะ