Junior เขียน Code ได้ดีจริง และนั่นคือจุดเริ่มของเรื่องนี้
ลองนึกถึง Pull Request ของ Junior Developer คนหนึ่ง Code อ่านง่าย ชื่อฟังก์ชันชัด ใช้ TypeScript ถูก ใช้ parameterized query ไม่ต่อ SQL เอง มี Unit Test และเจ้าตัวอธิบายได้ว่าทุกบรรทัดทำอะไร ถ้าดูเฉพาะสิ่งที่อยู่ในไฟล์ งานชิ้นนี้ถือว่าทำได้ดี
จากนั้น Reviewer ถามคำถามสั้น ๆ ว่า
Poolตัวนี้ถูกสร้างกี่ครั้ง?
คำถามนี้ทำให้บทสนทนาเปลี่ยนทันที เพราะคำตอบไม่ได้อยู่ใน Syntax ไม่ได้อยู่ใน Type และอาจไม่อยู่ใน Test ด้วย มันอยู่ในความเข้าใจว่าฟังก์ชันนี้ถูกเรียกจากที่ไหน เรียกบ่อยแค่ไหน และ Object ที่สร้างขึ้นมีชีวิตนานเท่าไร
นี่คือ Use Case ที่บทความนี้ต้องการพูดถึง Junior ไม่ได้เขียน Code ไม่เก่ง ตรงกันข้าม เขาอาจเขียนได้ดีและเร็วมาก โดยเฉพาะเมื่อมี AI ช่วย สิ่งที่ยังขาดได้คือประสบการณ์บางชั้นที่ทำให้เห็นผลของ Code หนึ่งบรรทัดต่อ Application ทั้งก้อน
นี่ไม่ใช่ข้อสรุปว่า Junior ทุกคนมีช่องว่างแบบเดียวกัน และไม่ใช่เรื่องอายุ ประเด็นคือคนที่เพิ่งเริ่มทำงานยังมีโอกาสเจอ Production Failure น้อยกว่า จึงอาจยังไม่เคยมีเหตุผลให้ตั้งคำถามบางข้อ แม้ Code ตรงหน้าจะเขียนได้ดีแล้วก็ตาม
Use Case: ฟังก์ชันนี้เขียนดีตรงไหน
ตัวอย่างนี้ใช้ pg ซึ่งเป็น PostgreSQL Driver สำหรับ Node.js สมมติว่า Function ถูกเรียกจาก Request Handler
// users.ts
import { Pool } from 'pg';
export async function getUser(id: string) {
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const { rows } = await pool.query('SELECT * FROM users WHERE id = $1', [id]);
return rows[0];
}
สิ่งที่ Code ชุดนี้ทำถูกมีหลายอย่าง
- Function มี Input และหน้าที่ชัดเจน
- Query ใช้ Parameter
$1แทนการต่อ String จากid - งาน Asynchronous ถูก
awaitก่อนคืนค่า pool.query()ยืมและคืน Client ให้อัตโนมัติสำหรับ Query เดียว- Code สั้นพอให้อ่านและเขียน Unit Test ได้ง่าย
AI สามารถเขียน Code แบบนี้ได้ และ Junior ก็สามารถอ่าน ตรวจ และแก้ต่อได้จริง เพราะฉะนั้นการบอกเพียงว่า “Code จาก AI เชื่อไม่ได้” ไม่ได้อธิบายปัญหาใน Use Case นี้เลย
ถ้า Unit Test Mock pg แล้วเช็กว่า Query กับ Parameters ถูกต้อง Test ก็ผ่านได้อย่างสมเหตุสมผล สิ่งที่ Test ชุดนั้นไม่ได้ตอบคือ Function นี้ถูกเรียกกี่ครั้งในชีวิตจริง และทุกครั้งสร้างอะไรเพิ่มขึ้นมาบ้าง
สิ่งที่ขาดไม่ได้อยู่ในบรรทัดใดบรรทัดหนึ่ง
Prompt อาจขอเพียง “เขียน Function ดึง User จาก Database” AI จึงตอบใน Scope ของ Function: สร้างสิ่งที่ต้องใช้ ยิง Query และคืน Result คำตอบนั้นครบในขอบเขตของคำถาม แต่ปัญหาเกิดในขอบเขตที่ใหญ่กว่า
- ฟังก์ชันPrompt มักถูกถามที่นี่
ตรวจอะไรที่ชั้นนี้คืนค่าถูกไหม type ผ่านไหม
- ไฟล์Prompt มักถูกถามที่นี่
ตรวจอะไรที่ชั้นนี้import ครบ test ของไฟล์นี้ผ่าน
- Module graph
ตรวจอะไรที่ชั้นนี้ใครสร้าง client ตัวนี้อีกบ้าง
- Processปัญหาในบทความนี้เกิดที่นี่
ตรวจอะไรที่ชั้นนี้instance อยู่นานแค่ไหน ใครปิด
- Fleetปัญหาในบทความนี้เกิดที่นี่
ตรวจอะไรที่ชั้นนี้มีกี่ process แต่ละอันมีของตัวเอง
เมื่อ getUser() ถูกเรียกหนึ่งครั้ง new Pool() ก็ทำงานหนึ่งครั้ง ถ้า Function นี้อยู่ใน Request Handler จำนวน Pool จึงโตตามจำนวน Requests ทั้งที่ Pool ถูกออกแบบมาเพื่อเก็บ Database Clients ไว้ใช้ซ้ำ
ตรงนี้ไม่มี Syntax Error ไม่มี Type Error และไม่มีบรรทัดไหนดูน่ากลัว สิ่งที่หายไปคือคำถามเรื่อง Scope กับ Lifetime
- Pool ควรเป็นของ Function, Request, Module หรือ Process?
- Request ถัดไปกลับมาใช้ Pool เดิมได้หรือไม่?
- เมื่อ Requests เข้ามาพร้อมกัน จำนวน Pool และ Connection โตตามอะไร?
- ใครเป็นเจ้าของการ Configure และปิดมัน?
Developer ที่เคยเจอ Connection เต็มหรือ Process ปิดไม่ลงมักถามเรื่องเหล่านี้โดยอัตโนมัติ ส่วนคนที่ยังไม่เคยเจอไม่ได้แปลว่าอ่อนกว่า เขาเพียงยังไม่มีเหตุการณ์นั้นอยู่ใน Mental Model
Shared Instance ทำให้ช่องว่างนี้เห็นชัด
ตาม เอกสาร pg.Pool Pool เริ่มต้นแบบว่างและสร้าง Client เมื่อมี Query เข้ามา pool.query() จะคืน Client ให้ Pool หลังใช้งาน แต่ใน Code ตัวอย่าง Request ถัดไปเข้าถึง Pool เดิมไม่ได้ เพราะ Pool ถูกสร้างอยู่ใน Function
เมื่อ Requests ซ้อนกัน แต่ละ Request จึงอาจเปิด Connection ผ่าน Pool ของตัวเอง แทนที่จะใช้ Connection จำนวนจำกัดผ่าน Pool ร่วม ปัญหานี้ไม่ได้เกิดเพราะ Junior จำ API ผิด แต่เกิดเพราะเขายังไม่เห็นว่า Object ตัวนี้ควรอยู่ตรงไหนของระบบ
- 100requests ซ้อนกัน
- ≈100Pools แยกกัน
- ≈100connection attempts ใน burst
- การใช้ซ้ำ
- client กลับไปยัง Pool ที่ request ถัดไปเข้าถึงไม่ได้
- หลัง Query
- idle client รอ idle timeout ของ Pool ตัวเอง
- เจ้าของ
- ไม่มีจุดกลางคุม config และ shutdown
- 100requests ซ้อนกัน
- 1Pool ที่ใช้ร่วมกัน
- ≤10active clients ที่เหลือรอคิว
- การใช้ซ้ำ
- ทุก request เข้าถึง Pool เดียวกัน
- หลัง Query
- client ว่างพร้อมให้ query ถัดไปใช้จนถึง idle timeout
- เจ้าของ
- db module คุม config และ shutdown path
ตัวอย่างสมมติให้แต่ละ request มีหนึ่ง query ที่ซ้อนเวลากัน และ Shared Pool ตั้ง max: 10 จำนวน connection ที่ฐานข้อมูลยอมรับจริงยังขึ้นกับ budget และ timeout ของระบบ
คำตอบสำหรับ Long-lived Node.js Application นั้นสั้นกว่าคำอธิบายปัญหามาก: สร้าง Pool ที่ Module Scope แล้ว Import Instance เดิมไปใช้
// db.ts
import { Pool } from 'pg';
export const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10,
});
// users.ts
import { pool } from './db';
export async function getUser(id: string) {
const { rows } = await pool.query('SELECT * FROM users WHERE id = $1', [id]);
return rows[0];
}
Node.js จะ Reuse Module ที่ Resolve ไปยังตัวเดียวกัน—CommonJS อ้างอิง Resolved Filename ส่วน ESM อ้างอิง Resolved URL—จึงทำให้ Caller ใน Runtime เดียวกันใช้ Export ชุดเดียวกันได้ แนวคิดเริ่มต้นจึงเป็นหนึ่ง Pool ต่อ Runtime ไม่ใช่หนึ่ง Pool ต่อ Request ส่วนระบบที่มีหลาย Processes, Hot Reload หรือ Serverless มี Boundary ต่างออกไปและต้องดูตาม Runtime จริง
สิ่งที่ควรจำจาก Case นี้ไม่ใช่คำว่า Singleton หรือเลข max: 10 แต่คือวิธีถามว่า Object นี้ควรมีอยู่กี่ตัว และควรมีชีวิตนานเท่าไร คำถามเดียวกันใช้ได้กับ HTTP Client, SDK, Cache, Queue Consumer และ Resource อื่นอีกมาก
AI ช่วยให้เขียนได้ก่อนจะมีโอกาสเห็นผลกระทบบางแบบ
เมื่อก่อน Developer มักใช้เวลานานกว่าจะเขียน API ได้ ระหว่างทางต้องอ่าน Documentation แก้ Syntax Error และลองผิดลองถูกกับ Library ประสบการณ์เหล่านั้นไม่ได้ดีทั้งหมด และเราไม่จำเป็นต้องทำให้คนรุ่นใหม่ลำบากเหมือนเดิม แต่ความสะดุดบางครั้งบังคับให้คนถามว่า Library ทำงานอย่างไร
AI ตัดความสะดุดออกได้มาก Junior จึงไปถึง Code ที่ดูพร้อมใช้งานเร็วขึ้น นี่เป็นข้อดีจริง แต่ความเร็วนี้ทำให้ “ผลงานที่มองเห็น” เดินนำ “Mental Model ที่มองไม่เห็น” ได้ง่ายขึ้น หากทีมใช้ความเรียบร้อยของ Code เป็นหลักฐานว่าความเข้าใจครบแล้ว ช่องว่างจะถูกพบตอนระบบรับ Load หรือเจอ Failure แทนที่จะพบตอน Review
งานทดลองของ Anthropic ในเดือนมกราคม 2026 ให้ผู้เข้าร่วม 52 คน ซึ่งส่วนใหญ่เป็น Software Engineer ระดับต้น เรียนรู้ Python Library ที่ไม่เคยใช้ กลุ่มที่ใช้ AI ได้คะแนนทดสอบหลังงานเฉลี่ย 50% เทียบกับ 67% ในกลุ่มที่เขียนเอง ขณะที่เวลาที่เร็วขึ้นราวสองนาทียังไม่มีนัยสำคัญทางสถิติ งานนี้มี Sample เล็ก วัดผลทันที และใช้ AI แบบ Sidebar จึงสรุปแทนทุกเครื่องมือหรือผลระยะยาวไม่ได้
ส่วนที่น่าสนใจกว่าตัวเลขคือคนที่ใช้ AI แล้วได้คะแนนสูงไม่ได้ใช้มันเพื่อ Generate Code อย่างเดียว พวกเขาถามต่อ ขอคำอธิบาย หรือใช้ AI ตอบคำถามเชิง Concept งานนี้จึงไม่ได้บอกให้เลิกใช้ AI แต่ชี้ว่าวิธีใช้ AI เพื่อ “ทำให้เสร็จ” กับใช้เพื่อ “สร้างความเข้าใจ” ให้ผลไม่เหมือนกัน
Senior ต้องเติมคำถาม ไม่ใช่แค่ส่ง Patch ที่ถูกกลับมา
ถ้า Review Comment มีเพียง “ย้าย Pool ไป db.ts” Junior จะได้ Code ที่ถูก แต่ยังอาจไม่ได้เหตุผล ครั้งหน้า Object เปลี่ยนจาก Pool เป็น HTTP Client ปัญหาเดิมก็กลับมาในชื่อใหม่
Review ที่ถ่ายทอด Mental Model ควรพาไปถึงคำถามเหล่านี้
- Function นี้ถูกเรียกต่อ Request หรือต่อ Process?
- Object ที่สร้างขึ้นถือ Resource อะไรอยู่เบื้องหลัง?
- เราต้องการหนึ่งตัวต่ออะไร: User, Request, Module, Process หรือทั้งระบบ?
- ถ้ามี 100 Calls พร้อมกัน จำนวนอะไรจะโตเป็น 100?
- ใครเป็นเจ้าของการปิด Retry หรือ Recover เมื่อ Dependency มีปัญหา?
- Test แบบไหนจึงจะเห็นปัญหานี้ ไม่ใช่แค่ยืนยันว่าแต่ละบรรทัดทำงาน?
Comment ที่ให้คำตอบ: “ย้าย Pool ไป
db.ts”Comment ที่สร้างความเข้าใจ: “Function นี้รันทุก Request ถ้ามี 100 Requests ตอนนี้จะมี Pool กี่ตัว และ Pool มีไว้เพื่อแก้ปัญหาอะไร?”
เมื่อ Junior ตอบคำถามนี้เอง เขาไม่ได้จำ Patch แต่เพิ่มแนวคิดเรื่อง Scope และ Lifetime เข้าไปในเครื่องมือคิดของตัวเอง AI ก็ช่วยต่อได้ เช่น ให้เปรียบเทียบสองแบบ อธิบาย Resource ที่ซ่อนอยู่ หรือเสนอ Failure Scenario เพื่อให้ทีมตรวจร่วมกัน
Shared Instance เป็นเพียง Use Case แรก
ช่องว่างระหว่าง “เขียน Code ได้ดี” กับ “เห็นระบบครบ” ไม่ได้มีแค่เรื่อง Pool ตัวอย่างอื่นมีโครงสร้างเดียวกัน
- Module-level State ที่เผลอแชร์ข้อมูลข้าม Requests
- Retry ที่แต่ละ Layer ทำถูกของตัวเอง แต่รวมกันแล้ว Request เดียวถูกยิงหลายครั้ง
- Transaction ที่แต่ละ Query ถูกต้อง แต่ไม่ได้อยู่บน Connection เดียวกัน
- Background Job ที่ทำงานสำเร็จ แต่ไม่มี Idempotency เมื่อถูกส่งซ้ำ
- Logging ที่ช่วย Debug ได้ แต่พา Secret หรือข้อมูลผู้ใช้ลง Log ไปด้วย
ทุกข้อเป็น Code ที่อาจดูดีในไฟล์ของมัน ปัญหาอยู่ที่ความสัมพันธ์ระหว่างไฟล์ เวลา ผู้ใช้ และ Infrastructure ซึ่งไม่มีใครเรียนครบได้จาก Syntax อย่างเดียว
Junior Developer ยุค AI จึงไม่ได้จำเป็นต้องเขียน Code แย่ลง เขาอาจเขียนได้ดีเร็วกว่าคนรุ่นก่อนด้วยซ้ำ สิ่งที่ทีมต้องออกแบบเพิ่มคือ Code Review, Pairing และคำถามที่ช่วยให้ความเข้าใจระดับระบบโตทันความเร็วในการผลิต Code
หากโจทย์ของคุณคือทบทวน Architecture และ Development Practice ของระบบ ดูขอบเขต บริการพัฒนาซอฟต์แวร์ของ Nixxel เพื่อประเมินงานที่ต้องทำร่วมกันได้
แหล่งอ้างอิงและอ่านต่อ
- Anthropic: How AI assistance impacts the formation of coding skills
- node-postgres: Pooling
- node-postgres:
pg.PoolAPI - node-postgres: Suggested project structure
- Node.js: CommonJS module caching
- Node.js: ECMAScript modules and URL-based caching
- ฟอร์ม Type-safe ใน Astro: ตรวจ FormData โดยไม่ใช้ as string
- Rust vs. Go ในยุค Agentic AI แบบนี้ เราควรเลือกภาษาไหนดี?

