โค้ดที่เขียนไม่ตรงมาตรฐาน ปั่นงานให้เสร็จ ไม่ได้แก้ยากเลย แต่โค้ดที่มีส่วนอื่นๆ ของระบบใช้มันเยอะต่างหากที่แก้ไขยาก และถ้าตรงนั้นยุ่งเหยิงจะยิ่งแก้ไขยากขึ้นอีกหลายเท่า
แล้วโค้ดที่มี Dependency เยอะเกิดได้ยังไง? ก็เกิดขึ้นเพราะมันเป็นฐานรากของ Use case ของคนใช้ เช่น ถ้าระบบคุณต้องการความปลอดภัย Authorize service ก็จะเป็นฐานรากไปอยู่แล้ว ถ้าคุณทำระบบที่เกี่ยวข้องกับการเงิน ตัวตัดบัญชีก็เป็นฐานราก
แล้วคุณจะรู้ได้ยังไงว่าโค้ดส่วนไหนจะ Use case ฐานรากในระบบ ก็มีสองทาง คือ ปล่อยระบบเล็กๆ ออกไปเทสก่อน ดูว่าส่วนไหนคนใช้เยอะ คนอยากให้ต่อเติมเยอะ ตรงนั้นก็จะเป็นฐานราก หรือไม่งั้นก็คือคุณใช้ความเชี่ยวชาญในโดเมนมาตัดสินใจ
ซึ่งความเชี่ยวชาญในโดเมน ก็มีทั้งโดเมนทางเทคนิคและโดเมนทางธุรกิจ เช่น คุณรู้อยู่แล้วในฐานะ Software developer ว่า Input validation ต้องเป็นฐานรากที่ขาดไม่ได้ หรือต้องมี API จากหลังบ้านปล่อยออกไป กับโดเมนทางธุรกิจเช่น ทำระบบ ERP สุดท้ายต้องมาอยู่กับบัญชี
หลายคน พยายามรีบตัดสินใจว่าอะไรเป็นฐานราก ซึ่งอาจจะคิดเอาเอง หรือบังคับให้ User/Stakeholder/Etc. ที่ไม่ได้เป็น Domain expert จริง รีบตัดสินใจให้ ซึ่งก็มักจะผิด วางสิ่งที่ไม่ใช่ฐานไปเป็นฐาน แล้วก็มักจะต้องรื้อ หรือที่แย่กว่าเลยคือคุณกำลังทำงานในโดเมนใหม่ที่ไม่มี Expert แต่เรียกร้องโวยวายให้ต้องมีให้ได้ ก็จะมีตัวปลอมเกิดขึ้นเพื่อสนองนี้ดนั้น แล้วให้ข้อมูลผิดๆ
ตรงข้าม พอทำแบบไปตัดสินใจทีหลัง ก็ลืมมอง Dependency graph ของตัวเองว่าถึงจุดนี้เราต้องตัดสินใจแล้วนะว่านี่คือฐานราก จนกลายเป็นว่าโค้ดพันกัน แล้วต่อให้ทำ Code ได้มาตรฐานขนาดไหน แต่ Track ไม่ได้ว่าแก้ตรงนี้กระทบอะไรบ้าง ก็แก้ไขยากอยู่ดี
เรามี Design ที่ไว้จัดการ Dependency มาก แต่จะเลี่ยง Dependency ได้ยังไงตั้งแต่แรก?
มีโค้ดชุดนึงอารมณ์ประมาณว่า
function X () {
DoA()
DoB()
DoC()
}
function Y () {
DoQ()
DoB()
DoZ()
}
class X () {
public Execute() {
A.Do();
B.Do();
C.Do();
}
}
class Y () {
public Execute() {
Q.Do();
B.Do();
Z.Do();
}
}
ถ้าเราไม่พยายามเลี่ยง Dependency เราเห็นโค้ดชุดนี้เราอาจจะ Abstract ออกมาเป็น
function AdvanceB(preB, postB) {
preB()
DoB()
postB()
}
class BExecutor extends Executor {
public constructor (DoObj preObj, DoObj postObj) {
this.preObj = preObj;
this.postObj = postObj;
}
public Execute() {
preObj.Do();
B.Do();
postObj.Do();
}
}
แล้วจับให้ X กลายเป็น AdvanceB(DoA, DoB)
หรือ BExecutor(A, B)
แต่พอกลายเป็นว่างาน X ต้องเอาผลลัพธ์จาก B มาใช้ยิงเข้า postObj ส่วนงาน Y ไม่ต้อง
ฐานรากที่ชื่อว่า BExecutor ที่เราวางไว้ก็พัง
นี่คือตัวอย่างของการมีฐานรากที่เร็วเกินไป
ซึ่งการสร้าง Class Executor แล้วใช้ Polymorphism ในการวาง General case สำหรับงานทำ B มันเป็นวิธีการจัดการ Dependency ที่ดีนะ มีสอนกันก็เยอะ เป็น Practice ที่ยอมรับกันเป็นมาตรฐาน
แต่ที่เหนือกว่าการจัดการ Dependency ที่ดีคือการเลี่ยง Dependency ที่ไม่จำเป็นตั้งแต่แรกเลย มีให้น้อยที่สุด มีเท่าที่จำเป็นจริงๆ เท่านั้น
สวัสดีครับ
Top comments (0)