แฟรงเกนสไตน์ ในการเขียนโปรแกรมคือโค้ดที่ประกอบขึ้นจากชิ้นส่วนที่เข้ากันไม่ได้ของเทคโนโลยี รูปแบบ และสถาปัตยกรรมที่แตกต่างกัน ตามการวิจัยของ ThoughtWorks Technology Radar (2024) พบว่า 28% ของโปรเจ็กต์ขนาดใหญ่มีอาการของกลุ่มอาการแฟรงเกนสไตน์ — ความหลากหลายทางสถาปัตยกรรมที่เกิดขึ้นเมื่อไม่มีวิสัยทัศน์ทางเทคนิคที่เป็นหนึ่งเดียว โดยเปรียบเทียบกับนวนิยายของแมรี เชลลีย์ โค้ดแบบนี้ทำงานได้ แต่การบำรุงรักษากลายเป็นฝันร้าย
หัวข้อสำคัญ
แฟรงเกนสไตน์ (Frankenstein code, Frankenstein pattern) เป็นต่อต้านรูปแบบที่ระบบซอฟต์แวร์ถูกประกอบขึ้นจากชิ้นส่วนที่ไม่ได้ออกแบบมาให้ทำงานร่วมกัน เช่นเดียวกับสัตว์ประหลาดของแฟรงเกนสไตน์ โค้ดแบบนี้อาจทำงานได้ แต่มันน่าเกลียด คาดเดาไม่ได้ และเป็นอันตรายเมื่อมีการเปลี่ยนแปลงเล็กน้อย
คำศัพท์นี้มาจากวรรณกรรม: ในนวนิยาย แมรี เชลลีย์ เรื่อง “แฟรงเกนสไตน์ หรือ โพรมีเทียสยุคใหม่” (1818) นักวิทยาศาสตร์สร้างสิ่งมีชีวิตจากเศษชิ้นส่วนของศพคนตายหลายคน ในการเขียนโปรแกรม การเปรียบเทียบนี้ตรงกัน — นักพัฒนานำชิ้นส่วนจากเฟรมเวิร์ก ไลบรารี และภาษาต่างๆ มาประกอบเข้าด้วยกันอย่างหยาบๆ ได้ผลลัพธ์ที่ทำงานได้แต่น่ากลัว
ความแตกต่างระหว่างแฟรงเกนสไตน์กับ สปาเกตตีโค้ด อยู่ที่ขนาดและธรรมชาติของปัญหา สปาเกตตีโค้ดคือโครงสร้างที่พันกันภายในเทคโนโลยีสแต็คเดียวกัน แฟรงเกนสไตน์คือความหลากหลายในระดับสถาปัตยกรรม: เทคโนโลยีที่แตกต่างกัน กระบวนทัศน์ที่เข้ากันไม่ได้ แนวทางที่ขัดแย้งกันภายในระบบเดียว
สถาปัตยกรรมไมโครเซอร์วิส อนุญาตให้ใช้เทคโนโลยีที่แตกต่างกันสำหรับเซอร์วิสที่แตกต่างกัน แต่มีเงื่อนไขว่าต้องมีขอบเขตที่ชัดเจนและโปรโตคอลการโต้ตอบที่ได้มาตรฐาน แฟรงเกนสไตน์คือการผสมผสานที่ไร้ระเบียบโดยไม่มีขอบเขต: REST และ GraphQL ในคอนโทรลเลอร์เดียวกัน ORM สองตัวในโมดูลเดียวกัน SQL และ NoSQL สำหรับเอนทิตีเดียวกัน
การไม่มี ผู้นำทางเทคนิคหรือสถาปนิกเป็นสาเหตุหลัก เมื่อไม่มีผู้รับผิดชอบความสมบูรณ์ของสถาปัตยกรรมในโปรเจ็กต์ นักพัฒนาแต่ละคนเลือกเครื่องมือตามความชอบ คนหนึ่งชอบ Spring อีกคนชอบ Guice คนที่สามชอบ DI ที่เขียนเอง ผลลัพธ์คือสลัดสถาปัตยกรรม
การรวม โปรเจ็กต์เป็นสาเหตุที่พบบ่อยเป็นอันดับสอง ทีมสองทีมพัฒนาโมดูลของตนแยกกันโดยใช้สแต็คที่แตกต่างกัน เมื่อต้องรวมโมดูลเข้าด้วยกันเป็นแอปพลิเคชันเดียว พวกเขาก็แค่ “เชื่อม” ด้วยอะแดปเตอร์และชั้นกลาง กลายเป็นแฟรงเกนสไตน์
การเข้าซื้อกิจการ ขององค์กรเป็นสถานการณ์ที่สาม บริษัท A ซื้อบริษัท B และต้องการรวมผลิตภัณฑ์เข้ากับของตน แทนที่จะเขียนใหม่ — เชื่อมผ่าน API ฐานข้อมูลร่วม และไม้ค้ำยัน ภายในหนึ่งปีระบบกลายเป็นสัตว์ประหลาดที่ไม่มีใครเข้าใจ
| สาเหตุ | คำอธิบาย | ผลลัพธ์ทั่วไป |
|---|---|---|
| ไม่มีสถาปนิก | นักพัฒนาแต่ละคนเลือกสแต็คของตนเอง | HTTP ไคลเอ็นต์ 3 ตัวในโมดูลเดียว |
| รวมโปรเจ็กต์ | ผลิตภัณฑ์สองอย่างถูกเชื่อมเป็นหนึ่ง | ORM สองตัว วิธีการบันทึก log สองแบบ |
| M&A | การเข้าซื้อบริษัทพร้อมผลิตภัณฑ์ | ลูกผสมของสถาปัตยกรรมและรูปแบบต่างๆ |
| การทดลอง | นำเทคโนโลยีใหม่มาใช้โดยไม่มีกลยุทธ์ | Java 8 + Java 21 ฟีเจอร์ในไฟล์เดียว |
| การตัดสินใจทางการเมือง | การบังคับใช้เทคโนโลยีจากเบื้องบนโดยไม่คำนึงถึงบริบท | Enterprise-เฟรมเวิร์กสำหรับสคริปต์ธรรมดา |
นักพัฒนาที่มีประสบการณ์ ที่ต้องการลองเทคโนโลยีใหม่ในโปรดักชันมักเป็นต้นเหตุของแฟรงเกนสไตน์ แทนที่จะจำกัดการทดลองไว้ในโมดูลที่แยกออกมา พวกเขานำโค้ดทดลองไปใส่ในส่วนที่สำคัญของระบบ
ตัวอย่างคลาสสิก คือการใช้ ORM หลายตัวในแอปพลิเคชันเดียวกัน บางโมดูลใช้ Hibernate บางโมดูลใช้ MyBatis และบางโมดูลใช้ JDBC โดยตรง ธุรกรรม失控 แคชไม่สอดคล้องกัน และนักพัฒนาใหม่ไม่รู้ว่าจะเลือกแนวทางใดสำหรับฟีเจอร์ใหม่
ตัวอย่างที่สอง คือการผสมผสานรูปแบบสถาปัตยกรรม ในคอนโทรลเลอร์ REST API มีการเรียกใช้ SOAP เซอร์วิส คำสั่ง SQL โดยตรง การเข้าถึงระบบไฟล์ และการสร้าง HTML แอปพลิเคชันแบบนี้ไม่สามารถทดสอบ ขยาย หรือจัดทำเอกสารได้
ตัวอย่างที่สาม คือเทคโนโลยีสแต็คที่ใช้ Python สำหรับแบ็กเอนด์ Node.js สำหรับไมโครเซอร์วิส C# สำหรับไคลเอ็นต์เดสก์ท็อป และ Java สำหรับแอปพลิเคชัน Android โดยที่ตรรกะทางธุรกิจทั้งหมดกระจายอยู่ระหว่างนั้นโดยไม่มีการแบ่งแยกความรับผิดชอบที่ชัดเจน
// แฟรงเกนสไตน์ — รูปแบบและเทคโนโลยีที่ผสมผสาน
// callbacks, Promises และ async/await รวมกัน
// callbacks
db.query("SELECT * FROM users", function(err, rows) {
if (err) handleError(err);
// Promise ภายใน callback
fetch("/api/data").then(function(data) {
// async/await ภายใน then
(async () => {
const result = await processData(data);
sendResponse(result);
})();
});
});
// โค้ดที่สะอาด — รูปแบบ async/await ที่เป็นหนึ่งเดียว
async function getUserData(userId) {
const user = await db.query("SELECT * FROM users WHERE id = ?", [userId]);
const data = await fetch("/api/data/" + userId);
return await processData(user, data);
}
ฐานข้อมูลเดียว ถูกใช้พร้อมกันทั้งแบบ SQL เชิงสัมพันธ์ (ที่มีการ normalized) และ NoSQL เชิงเอกสาร (ที่มีคอลัมน์ JSON) คำสั่งบางส่วนผ่าน ORM บางส่วนผ่าน stored procedures บางส่วนผ่าน SQL โดยตรงจากโค้ด โครงสร้างฐานข้อมูลไม่มีเอกสาร การย้ายข้อมูลขัดแย้งกัน
ความยากในการเริ่มงาน เป็นผลกระทบแรก นักพัฒนาใหม่ต้องรู้ 5 ภาษา 3 เฟรมเวิร์ก 2 รูปแบบสถาปัตยกรรม เพื่อเข้าใจว่าระบบทำงานอย่างไร การเริ่มงานยืดเยื้อจากสัปดาห์เป็นเดือน ตามข้อมูลของ LinkedIn (2023) โปรเจ็กต์ที่มีความหลากหลายทางเทคโนโลยีสูญเสียพนักงานใหม่บ่อยเป็น 2 เท่า
ความไม่แน่นอน ของพฤติกรรมเป็นผลกระทบที่สอง การเปลี่ยนแปลงใน Python ไมโครเซอร์วิสอาจทำให้ Java โมดูลเสียหายอย่างไม่คาดคิด เพราะใช้ฐานข้อมูลร่วมกันโดยไม่มีสัญญาที่ชัดเจน การดีบักปัญหาเหล่านี้ต้องอาศัยความรู้ในทุกเทคโนโลยีในสแต็คพร้อมกัน
ความปลอดภัย เป็นผลกระทบที่สาม แต่ละเทคโนโลยีในสแต็คต้องการการกำหนดค่าความปลอดภัยของตนเอง แพตช์ของตนเอง การตรวจสอบของตนเอง การรักษาความปลอดภัยในระดับที่ยอมรับได้สำหรับเทคโนโลยีที่แตกต่างกัน 5–6 ชนิดนั้นแทบเป็นไปไม่ได้ หนึ่งในนั้นจะกลายเป็นช่องโหว่อย่างหลีกเลี่ยงไม่ได้
SonarQube สามารถวัดหนี้ทางเทคนิคได้ แต่ไม่สามารถวัด “หนี้ทางสถาปัตยกรรม” — ความเข้ากันไม่ได้ของส่วนประกอบ หนี้นี้ไม่ปรากฏในคำเตือนของ linter แต่อยู่ในความเป็นไปไม่ได้ที่จะเพิ่มฟังก์ชันใหม่โดยไม่ต้องเปลี่ยนแปลงโมดูลสามโมดูลที่เขียนด้วยเทคโนโลยีต่างกัน
ขั้นตอนแรกและสำคัญที่สุด คือการแต่งตั้งสถาปนิกหรือ tech lead ที่รับผิดชอบความสมบูรณ์ของเทคโนโลยีสแต็ค บุคคลนี้มีสิทธิ์ยับยั้งการนำเทคโนโลยีใหม่มาใช้โดยไม่ได้รับการตรวจสอบทางสถาปัตยกรรม ไม่ใช่ประชาธิปไตย แต่เป็นการตัดสินใจเพียงผู้เดียวที่รับผิดชอบในเทคโนโลยีสำคัญ
ขั้นตอนที่สอง — นำกระบวนการ Architecture Decision Record (ADR) มาใช้ การตัดสินใจทางสถาปัตยกรรมที่สำคัญใดๆ (การเลือกฐานข้อมูล เฟรมเวิร์ก โปรโตคอล) จะถูกบันทึกเป็นข้อความสั้น: บริบท ทางเลือกที่พิจารณา การตัดสินใจที่เลือก ผลกระทบ ADR ถูกเก็บไว้ใน repository และพร้อมให้ทั้งทีมเข้าถึง
ขั้นตอนที่สาม — กำหนดหลักการ “หนึ่งงาน — หนึ่งเครื่องมือ” สำหรับ HTTP คำขอ — หนึ่งไคลเอ็นต์ สำหรับ ORM — หนึ่งไลบรารี สำหรับการบันทึก log — หนึ่งเฟรมเวิร์ก ข้อยกเว้นทำได้ผ่าน ADR พร้อมเหตุผลเท่านั้น ถ้าในโปรเจ็กต์มี Axios อยู่แล้ว — อย่าเพิ่ม fetch ถ้ามี SLF4J — อย่าเขียนผ่าน System.out
การทดลอง ทำได้ แต่ต้องอยู่ในสภาพแวดล้อมที่แยกออกมา จัดสรรโมดูลหรือเซอร์วิสที่สามารถเขียนใหม่ด้วยเทคโนโลยีใหม่โดยไม่ส่งผลกระทบต่อระบบที่เหลือ ถ้าการทดลองสำเร็จ — ทำให้เป็นมาตรฐานผ่าน ADR ถ้าไม่ — ลบทิ้งโดยไม่มีผลกระทบ
การสำรวจ — ขั้นตอนแรก จัดทำแผนที่完整ของเทคโนโลยีสแต็ค: เฟรมเวิร์ก ไลบรารี ภาษา โปรโตคอลใดบ้างที่ใช้ ในโมดูลใด และสำหรับงานใด คุณจะเห็นขนาดของปัญหา: เครื่องมือที่ซ้ำกัน เทคโนโลยีที่ขัดแย้ง dependencies ที่ไม่ได้ใช้
การทำให้เป็นมาตรฐาน — ขั้นตอนที่สอง เลือกหนึ่งเครื่องมือสำหรับแต่ละงาน ตัวอย่างเช่น: Hibernate เท่านั้นสำหรับ ORM, SLF4J + Logback เท่านั้นสำหรับการบันทึก log, REST เท่านั้นสำหรับ API บันทึกมาตรฐานใน ADR เริ่มเปลี่ยนจากโมดูลที่มีความหลากหลายมากที่สุด
กลยุทธ์ Parallel Run — ขั้นตอนที่สาม เครื่องมือเก่าและใหม่ทำงานพร้อมกันจนกว่าเครื่องมือใหม่จะพิสูจน์ความน่าเชื่อถือ ตัวอย่างเช่น HTTP ไคลเอ็นต์เก่าและใหม่ทำงานพร้อมกัน แต่ไคลเอ็นต์ใหม่ใช้สำหรับบางคำขอเท่านั้น หลังจากระยะการเสถียรภาพ เครื่องมือเก่าจะถูกลบ
// แฟรงเกนสไตน์ — แนวทาง HTTP สามแบบในโปรเจ็กต์เดียว
// โมดูล A: OkHttp
OkHttpClient client = new OkHttpClient().newCall(request);
// โมดูล B: RestTemplate (Spring)
restTemplate.getForObject(url, String.class);
// โมดูล C: java.net.HttpURLConnection
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
// แนวทางที่เป็นหนึ่งเดียว: RestTemplate สำหรับซิงค์, WebClient สำหรับรีแอกทีฟ
@Autowired
private RestTemplate restTemplate;
public String callApi(String url) {
return restTemplate.getForObject(url, String.class);
}
ผู้นำทางเทคนิค เป็นเครื่องมือหลักในการต่อสู้กับแฟรงเกนสไตน์ ไม่ใช่ผู้จัดการ ไม่ใช่สถาปนิกในหอคอยงาช้าง แต่เป็นนักพัฒนาที่ปฏิบัติจริง ซึ่งเขียนโค้ด ตรวจสอบ PR และตัดสินใจทางสถาปัตยกรรม หากไม่มีบุคคลดังกล่าว โปรเจ็กต์จะเลื่อนไปสู่ความหลากหลายทางเทคโนโลยีอย่างหลีกเลี่ยงไม่ได้
RFC (Request for Comments) เป็นกระบวนการที่ยืมมาจากชุมชนโอเพนซอร์ส ก่อนนำเทคโนโลยีสำคัญใดๆ มาใช้ ผู้เขียนเขียน RFC: ปัญหา วิธีแก้ไขที่เสนอ ทางเลือก แผนการนำไปใช้ ทีมอภิปราย ลงคะแนน อนุมัติหรือปฏิเสธ RFC สร้างความโปร่งใสและป้องกันการตัดสินใจทางสถาปัตยกรรม “เงียบ”
เรดาร์เทคโนโลยี (Technology Radar โดย ThoughtWorks) เป็นเครื่องมือในการจัดหมวดหมู่เทคโนโลยี: Adopt, Trial, Assess, Hold ทีมตรวจสอบเรดาร์เป็นประจำและอัปเดตสถานะ ซึ่งช่วยแยกแยะ “สิ่งที่กำลังเป็นที่นิยม” จาก “สิ่งที่มีประโยชน์” และหลีกเลี่ยงการนำเทคโนโลยีที่ไม่ผ่านการตรวจสอบมาใช้ในโค้ดที่สำคัญ
คุณสมบัติที่สำคัญที่สุด ของสถาปัตยกรรมคือความสอดคล้อง (consistency) แม้แต่เครื่องมือที่ไม่ดีที่สุด ที่ใช้ทั่วทั้งโปรเจ็กต์ ก็ดีกว่าเครื่องมือที่ดีที่สุดที่ใช้ในโมดูลเดียว ความสอดคล้องลดภาระทางปัญญา ทำให้การเริ่มงานง่ายขึ้น และทำให้โค้ดคาดเดาได้
คำถามที่พบบ่อย
Polyglot persistence คือการใช้ฐานข้อมูลที่แตกต่างกันอย่างมีสติสำหรับงานที่แตกต่างกัน (PostgreSQL สำหรับธุรกรรม, Redis สำหรับแคช, Elasticsearch สำหรับการค้นหา) แฟรงเกนสไตน์คือการผสมผสานที่ไร้ระเบียบโดยไม่มีกลยุทธ์ ความแตกต่างอยู่ที่การมีคำตัดสินทางสถาปัตยกรรม: polyglot คือแผน แฟรงเกนสไตน์คือการไม่มีแผน
ได้ และนี่เป็นปัญหาที่พบบ่อย เมื่อไมโครเซอร์วิสแต่ละตัวใช้ภาษา ฐานข้อมูล โปรโตคอล และแนวทางการ deploy ของตนเองโดยไม่มีมาตรฐานกลาง — จะเกิดแฟรงเกนสไตน์แบบกระจาย สำหรับไมโครเซอร์วิส มาตรฐานร่วมมีความสำคัญ: โปรโตคอลเดียว (REST/gRPC), รูปแบบ log เดียวกัน, observability แบบรวมศูนย์
อย่า ห้าม — จงแนะนำ เสนอให้ผู้เขียน RFC อธิบายว่าทำไมโซลูชันที่มีอยู่ไม่เหมาะสม ทางเลือกใดที่พิจารณาแล้ว และจะโยกย้ายอย่างไร บ่อยครั้งในระหว่างการเขียน RFC นักพัฒนาเองก็เข้าใจว่าไม่จำเป็นต้องใช้เทคโนโลยีใหม่ ถ้า RFC น่าเชื่อถือ — ก็นำมาใช้ แต่ต้องมีแผนและข้อจำกัด
เริ่มต้น ด้วยการสำรวจ จากนั้นทำให้เป็นมาตรฐาน อย่าพยายามเขียนใหม่ทั้งหมดในครั้งเดียว เลือกหนึ่งชั้น (เช่น HTTP ไคลเอ็นต์หรือการบันทึก log), เลือกเครื่องมือเดียว, เขียน ADR และโยกย้ายทีละส่วน วิธี Strangler Fig — แทนที่ส่วนประกอบเก่าด้วยใหม่ทีละชิ้น โดยไม่หยุดการทำงานของแอปพลิเคชัน
ยิ่งน้อย ยิ่งดี เหมาะสมที่สุด — หนึ่งภาษา หนึ่งเฟรมเวิร์ก หนึ่งฐานข้อมูล หนึ่งวิธีบันทึก log ตามความเป็นจริง — 2–3 ภาษา (เมื่อแบ่งแยกอย่างชัดเจน), 1–2 ฐานข้อมูล, 1–2 เฟรมเวิร์ก เทคโนโลยีเพิ่มเติมแต่ละอย่างเพิ่มภาระทางปัญญาของทีมและค่าใช้จ่ายในการบำรุงรักษา
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม