SOLID ไม่ใช่พิธีกรรม OOP แต่คือวิธีแบ่งขอบเขตของโค้ดเมื่อให้ AI Agent แก้
SOLID คือหลักออกแบบโค้ด 5 ข้อที่ใช้แยกหน้าที่เมื่อ AI Agent แก้โค้ด เพราะไม่ใช่ทุกข้อที่คุ้มเท่ากัน มาดูกันว่าข้อไหนควรใช้และเมื่อไหร่ควรหยุดแยก

SOLID คือหลักการออกแบบโค้ด 5 ข้อตั้งแต่ช่วงต้นยุค 2000 ที่ Robert C. Martin รวบรวมไว้ ในยุคที่คนเขียนโค้ดทุกบรรทัดยังเป็นมนุษย์ แต่วันนี้เมื่อคนลงมือแก้โค้ดเปลี่ยนมาเป็น AI Agent ที่เปิดไฟล์ในโปรเจกต์แล้วจัดการแทนเราได้ หลักการเดิมจึงกลับมามีบทบาทสำคัญอีกครั้ง
ลองนึกภาพไฟล์ที่ผ่านการแก้มาแล้วเก้ารอบ รอบแรกยังง่าย เพราะไฟล์ยังเล็กพอให้อ่านครบในทีเดียว แต่ไฟล์ที่รับหลายหน้าที่พร้อมกันจะสะสมโค้ดเพิ่มทุกรอบที่มีคนแก้ จนแต่ละส่วนเกี่ยวพันกันไปหมด พอถึงรอบที่สิบ คนที่ลงมือแก้ ไม่ว่าเราหรือ AI Agent ก็ต้องอ่านโค้ดทั้งก้อนก่อน ถึงจะรู้ว่าแตะตรงไหนแล้วไม่กระทบของเก่า
นี่คือเหตุผลที่ SOLID กลับมามีความหมายอีกครั้ง ไม่ใช่แค่เรื่องทฤษฎีหรือพิธีกรรมของการเขียนโปรแกรมเชิงวัตถุอย่าง OOP แต่เป็นวิธีแบ่งขอบเขตหน้าที่ของโค้ดให้ชัดเจน เพื่อให้การแก้ไขหนึ่งเรื่องกินพื้นที่แค่จุดเดียวเท่านั้น
แต่บทเรียน SOLID แบบท่องจำ 5 ข้อไม่ค่อยบอกว่า หลักการทั้งห้านี้ ไม่ได้คุ้มค่าเท่ากันหมด มี 2 ข้อที่คุ้มค่ากับเกือบทุกงาน ส่วนอีก 3 ข้อ ถ้าหยิบมาใช้ผิดจังหวะ จะกลายเป็นภาระที่สร้างปัญหามากกว่าเดิม
SRP: ไฟล์ที่ทำสามเรื่องพร้อมกัน คือไฟล์ที่แก้ยากขึ้นทุกรอบ

ลองดูคลาส Report ตัวอย่างนี้:
class Report {
build(rows) { /* ประกอบตัวเลขเป็นเนื้อรายงาน */ }
toHtml() { /* จัดหน้าตาเป็น HTML */ }
save(path) { /* เขียนลงไฟล์ */ }
}
ในคลาสนี้คลาสเดียว มีเหตุผลที่ต้องกลับมาแก้ถึง 3 เรื่องซ้อนกัน: สูตรคำนวณเปลี่ยน หน้าตารายงานเปลี่ยน หรือตำแหน่งบันทึกไฟล์เปลี่ยน ซึ่งทั้ง 3 เรื่องนี้เปลี่ยนด้วยคนละเหตุผล แต่กลับมารวมอยู่ในไฟล์เดียวกัน
พอเราสั่ง Agent ว่า "เปลี่ยนชื่อไฟล์ที่บันทึกให้มีวันที่ต่อท้าย" สิ่งที่มันต้องอ่านก่อนลงมือแก้จึงไม่ใช่แค่เมธอด save() แต่ต้องอ่านทั้งคลาส เพราะมันไม่มีทางรู้ล่วงหน้าว่า toHtml() แอบดึงชื่อไฟล์ไปแสดงในหัวรายงานด้วยหรือไม่
นี่คือเหตุผลว่าทำไมงานเล็กๆ ถึงกลายเป็นงานใหญ่ การแก้ครั้งแรกๆ ยังเบา เพราะไฟล์ยังเล็กพอให้อ่านครบ แต่ยิ่งไฟล์นี้รับหน้าที่เพิ่ม ก็ยิ่งขยายก้อนโค้ดที่ทั้งเราและ Agent ต้องอ่านให้เข้าใจก่อนลงมือแก้ทุกครั้ง
ทางแก้คือหลักข้อแรกอย่าง SRP ที่ระบุว่า คลาสหนึ่งควรมีเหตุผลเดียวเท่านั้นที่ทำให้ต้องกลับมาแก้ไข
class Report { build(rows) { /* เนื้อรายงานอย่างเดียว */ } }
class ReportHtmlView { render(report) { /* หน้าตาอย่างเดียว */ } }
class ReportFileStore { save(text, path) { /* ที่เก็บอย่างเดียว */ } }
ฟังก์ชันการทำงานทุกอย่างยังอยู่ครบเหมือนเดิม แต่ขนาดของงานเปลี่ยนไปคนละเรื่อง คราวนี้เรื่องชื่อไฟล์อยู่ใน ReportFileStore ที่เดียว ใครลงมือแก้ก็เปิดแค่ไฟล์นั้น ส่วนเราตอนเปิดดู Git diff เพื่อตรวจโค้ดก่อนและหลังแก้ ก็เห็นได้ทันทีว่าแตะเพียงไฟล์เดียว ถ้าดันไปแก้ ReportHtmlView ด้วย เราจะรู้ได้ทันทีว่ามีอะไรผิดปกติ โดยไม่ต้องเสียเวลาไล่อ่านโค้ดข้างในเลย
SRP กับ DIP คือสองข้อที่คุ้มค่าเกือบทุกงาน
ถ้าจะเลือกใช้ SOLID แค่ 2 ข้อ ขอแนะนำคู่นี้ เพราะทั้งสองข้อช่วย ลดปริมาณโค้ดที่ต้องอ่านให้เข้าใจก่อนจะกล้าลงมือแก้
ข้อแรกคือ SRP ที่เราเห็นไปแล้ว ส่วนอีกข้อคือ DIP ซึ่งบอกว่า โค้ดส่วนหลักไม่ควรผูกติดกับรายละเอียดของเครื่องมือที่มันเรียกใช้ ทั้งสองฝั่งควรคุยกันผ่านข้อตกลงกลางอย่าง Interface ที่ระบุแค่ว่ามีคำสั่งอะไรให้เรียกใช้งานได้บ้าง
// ก่อน: Switch รู้จักหลอดไฟตัวนี้ตรงๆ เปลี่ยนอุปกรณ์เมื่อไหร่ ต้องกลับมาแก้ Switch
class Switch {
constructor() { this.bulb = new LightBulb() }
press() { this.bulb.turnOn() }
}
// หลัง: Switch รู้จักแค่ "อุปกรณ์ที่เปิดได้" ใครก็ได้ที่มี turnOn()
class Switch {
constructor(device) { this.device = device }
press() { this.device.turnOn() }
}
ความแตกต่างจะเห็นได้ชัดเมื่อมีโจทย์ใหม่เข้ามา ในโค้ดแบบแรก ถ้าเราสั่ง Agent ว่า "เพิ่มพัดลมให้เปิดผ่านสวิตช์ได้ด้วย" มันต้องเข้าไปแก้ในคลาส Switch ซึ่งเป็นจุดที่อุปกรณ์อื่นในบ้านเรียกใช้งานอยู่ แต่ในโค้ดแบบหลัง เราไม่ต้องแตะคลาส Switch เลย งานจะย้ายไปอยู่ที่การสร้างคลาสอุปกรณ์ตัวใหม่ให้มีเมธอด turnOn() ตามที่ Interface กำหนดไว้แทน
นอกจากนี้ DIP ยังมีผลพลอยได้ที่สำคัญมากเวลาทำงานกับ Agent คือช่วยให้เราเขียน Unit Test เพื่อทดสอบการทำงานเฉพาะจุดได้โดยไม่ต้องต่อกับระบบจริง เราสามารถส่งอุปกรณ์จำลองอย่าง Mock Object ที่แค่บันทึกว่าเรียก turnOn() ไปกี่ครั้งเข้าไปทดสอบแทนได้ แปลว่าเรามีเทสต์ที่รันได้ทันทีไว้เช็กงานที่ Agent ส่งกลับมา แทนที่จะต้องไปไล่หาจุดพังเอาเองทีหลัง
ถ้าปล่อยให้ไฟล์เดียวรับหลายหน้าที่ต่อไป มันจะบวมขึ้นทุกครั้งที่มีฟีเจอร์ใหม่ จนถึงวันที่ไม่มีใครกล้ารีวิว Git diff อย่างจริงจัง และทำได้แค่กดยอมรับเพราะเห็นว่าเทสต์ผ่าน ทั้งที่จริงๆ แล้ว โค้ดที่รันเทสต์ผ่านไม่ได้แปลว่าวิธีที่ Agent ใช้แก้ปัญหาจะถูกต้องเสมอไป
อีกสามข้อ: รอให้เจอปัญหาก่อนค่อยหยิบมาใช้
หลักการอีก 3 ข้อที่เหลือไม่ใช่เรื่องผิด แต่เป็นเครื่องมือที่ควรหยิบมาใช้เมื่อเจอปัญหาที่ช่วยแก้ได้จริงๆ เท่านั้น ไม่ใช่ทำเผื่อไว้ล่วงหน้า
1. OCP
หลักการนี้บอกว่า โค้ดควรรองรับการเพิ่มฟังก์ชันใหม่โดยไม่ต้องกลับไปแก้ไขโค้ดเดิม สัญญาณว่าถึงเวลาต้องใช้ OCP คือบล็อก if-else หรือ switch-case ขนาดยาวที่เราต้องกลับมาเพิ่มเงื่อนไขใหม่ทุกครั้งที่มีประเภทข้อมูลเพิ่มขึ้น
// ก่อน: เพิ่มรูปทรงใหม่ทีไร ต้องกลับมาแก้ตรงกลางทุกที
if (shape.type === "circle") { total += 3.14 * shape.r * shape.r }
else if (shape.type === "square") { total += shape.side * shape.side }
// หลัง: แต่ละรูปทรงถือสูตรของตัวเอง ตรงกลางแค่บวก
total += shape.area()
2. LSP
หลักการที่บอกว่า คลาสลูกต้องนำไปใช้แทนคลาสแม่ได้อย่างสมบูรณ์โดยไม่ทำให้โปรแกรมทำงานผิดพลาด ตัวอย่างคลาสสิกคือ คลาส Bird ที่มีเมธอด fly() แล้วมีคลาส Penguin สืบทอดไป แม้โค้ดจะตรวจผ่านไม่มีข้อผิดพลาด แต่ในความเป็นจริงเพนกวินบินไม่ได้ เมื่อโปรแกรมส่งเพนกวินไปทำงานในจุดที่คาดหวังว่านกจะบินได้ การทำงานจึงผิดพลาด ทางแก้คือย้าย fly() ออกจาก Bird แล้วแยกเป็น Interface เฉพาะสำหรับนกที่บินได้
3. ISP
หลักการที่บอกว่า ไม่ควรบังคับให้คลาสใดคลาสหนึ่งต้องสร้างเมธอดที่ตัวเองไม่ได้ใช้ เช่น ถ้าเราใส่เมธอดหาปริมาตรอย่าง volume() ไว้ใน Interface กลางของรูปทรงทั้งหมด รูปทรง 2 มิติอย่างสี่เหลี่ยมแบนๆ ก็พลอยต้องมีเมธอดหาปริมาตรติดตัวไปด้วยโดยไม่จำเป็น ทางแก้คือแยกออกเป็น 2 Interface รูปทรงไหนเป็น 3 มิติ ค่อยนำ Interface นั้นไปใช้
จะสังเกตได้ว่าทั้ง 3 ข้อนี้มีรูปแบบเดียวกัน คือ เริ่มจากการสังเกตปัญหาที่เกิดขึ้นจริงในโค้ดก่อน แล้วค่อยปรับโครงสร้าง ไม่ใช่แยกเลเยอร์ล่วงหน้าเผื่อไว้ตั้งแต่วันแรก
สัญญาณเตือน: เมื่อแยกโค้ดมากเกินไปจนกลายเป็นของที่ไม่มีใครกล้าแตะ

ในทางกลับกัน ถ้าเรานำ 3 ข้อหลังไปใช้โดยไม่ดูจังหวะ ผลลัพธ์ที่ได้คือชั้นตัวกลางอย่าง Abstraction ที่ซ้อนกันจนซับซ้อนเกินไป โค้ดที่ทำงานจริงมีเพียง 3 บรรทัด แต่อาจกระจายอยู่ถึง 5 ไฟล์ ตั้งแต่ Interface, คลาสที่นำไปใช้จริง, ตัวประกอบชิ้นส่วนอย่าง Factory ไปจนถึงไฟล์ตั้งค่า กว่าจะหาบรรทัดที่ทำงานจริงเจอก็หมดพลังแล้ว
ตรงนี้ส่งผลเสียสองด้าน:
- คนอ่านโค้ด เสียเวลาไล่แกะโครงสร้าง
- AI Agent ก็ไม่ต่างกัน เพราะต้องไล่อ่านให้ครบทั้ง 5 ไฟล์กว่าจะเข้าใจโค้ดไม่กี่บรรทัดนั้น กลายเป็นว่าปริมาณโค้ดที่ต้องอ่านก่อนลงมือแก้กลับมากกว่าตอนที่ยังไม่แยกไฟล์ ทั้งที่เราแยกไฟล์เพื่อลดปริมาณโค้ดที่ต้องอ่าน
เลเยอร์ซับซ้อนที่ไม่มีใครกล้าแตะ มีต้นทุนสูงกว่าไฟล์โค้ดยาวๆ ที่ทุกคนยังอ่านเข้าใจ
สัญญาณว่าเราเริ่มแยกโค้ดในโปรเจกต์มากเกินไปมีดังนี้:
- Interface ที่มีคลาสเรียกใช้จริงเพียงตัวเดียวมาตลอดทั้งปี และไม่มีแผนจะเพิ่มตัวที่สอง
- ชื่อคลาสที่คลุมเครือจนบอกไม่ได้ว่าทำหน้าที่อะไร เช่น
Manager,Handler,Processorที่พอเปิดดูข้างในกลับมีแต่โค้ดส่งต่องานไปที่อื่น - เพิ่มฟีเจอร์เล็กๆ เพียงอย่างเดียว แต่ต้องตามแก้ถึง 4-5 ไฟล์ ที่ทำหน้าที่แค่ส่งผ่านค่าต่อกันเป็นทอดๆ
อาการเหล่านี้เห็นได้ชัดเจนทันทีที่เปิดดูโค้ด ถ้าพบเลเยอร์ส่วนเกินที่ไม่ได้ใช้จริง การยุบชั้นกลางทิ้งตั้งแต่วันนี้ ย่อมง่ายและมีต้นทุนต่ำกว่าการปล่อยให้มีโค้ดส่วนอื่นมาผูกติดเพิ่มขึ้นในอนาคต
เกณฑ์เช็กสุขภาพโค้ดในโปรเจกต์ของคุณ
ลองนำเกณฑ์เหล่านี้ไปตรวจสอบโปรเจกต์ที่กำลังเปิดอยู่ได้ทันที:
- เปิดไฟล์ที่แก้บ่อยที่สุดในรอบเดือน: นับดูว่ามีกี่เหตุผลที่ทำให้ต้องแก้ไฟล์นี้ ถ้ามีมากกว่า 1 เหตุผล นั่นคือจุดแรกที่คุ้มค่าที่สุดในการเริ่มแยกโค้ดตามหลัก SRP
- เลือกโค้ดที่ทำงานจริงมาสัก 1 จุด: นับดูว่าต้องเปิดกี่ไฟล์ถึงจะเข้าใจการทำงานครบถ้วน ถ้าต้องเปิดเกิน 3 ไฟล์ แปลว่าเริ่มมีเลเยอร์ตัวกลางมากเกินไป
- สำรวจ Interface ทั้งหมดในโปรเจกต์: ตัวไหนที่มีคลาสเรียกใช้อยู่เพียงตัวเดียว และไม่มีแผนจะเพิ่ม ให้ยุบเลเยอร์นั้นกลับมาตรงๆ
- ตรวจ Git diff ล่าสุดที่ Agent ส่งมา: ถ้า Agent แตะไฟล์ที่ไม่เกี่ยวข้องกับคำสั่งของเรา ให้กลับไปเช็กขอบเขตหน้าที่ของไฟล์นั้นก่อน แทนที่จะคิดว่าเป็นเพราะ Prompt ไม่ดี
สิ่งสำคัญที่คุณต้องตัดสินใจคือ "ควรแยกโค้ดเมื่อไหร่"
- ถ้ารีบแยกตั้งแต่เริ่มเห็นเค้าลาง ก็จะเสียเวลาไปกับโครงสร้างที่อาจไม่ได้ใช้งานจริง
- แต่ถ้าปล่อยไว้นานจนโครงสร้างเริ่มพันกัน การตามมารื้อทีหลังก็จะมีต้นทุนสูงมาก
เกณฑ์ที่ใช้ตัดสินได้จริงคือ ความถี่ในการแก้ไข: ถ้าไฟล์นั้นแก้แค่นานๆ ครั้ง การปล่อยไว้ก่อนก็ยังไม่เสียหาย แต่ถ้าไฟล์ไหนต้องแก้ทุกสัปดาห์ และแต่ละครั้งแก้ด้วยเหตุผลคนละเรื่องกัน นั่นคือสัญญาณชัดเจนว่าถึงเวลาแยกโค้ดได้แล้ว
สุดท้ายแล้ว ประโยชน์ของ SOLID ในยุค AI Coding ไม่ได้วัดจากจำนวนชั้นของโครงสร้างโค้ด แต่วัดจาก จำนวนไฟล์ที่ทั้ง Agent และเราต้องเปิดอ่าน ก่อนจะมั่นใจและกล้าลงมือแก้โค้ดเพียงหนึ่งบรรทัด
ชอบเรื่องแนวนี้ มีอีบุ๊คฟรีให้อ่านต่อ
NotebookLM ฉบับเข้าใจง่าย โยนเอกสารให้ AI อ่าน แล้วได้สรุป พอดแคสต์ และคลังความรู้ส่วนตัว
กดสมัครแล้วเราจะส่งเทคนิค AI และของแจกใหม่ๆ ให้ทางอีเมล เลิกรับได้ตลอด
สร้าง AI Automation Pipeline ทุกแบบ ด้วย Agents และ Skills

ปูจากพื้นฐาน prompt, context และ cost ไปจนปั้น Skill สั่ง Agent กับ Sub-agent แล้วต่อทุกอย่างเป็น pipeline อัตโนมัติที่ออกแบบเองได้ ดูฟรี 7 บทก่อนตัดสินใจ


