Dependency
Chakrit Likitkhajorn
Posted on December 10, 2019
โค้ดที่เขียนไม่ตรงมาตรฐาน ปั่นงานให้เสร็จ ไม่ได้แก้ยากเลย แต่โค้ดที่มีส่วนอื่นๆ ของระบบใช้มันเยอะต่างหากที่แก้ไขยาก และถ้าตรงนั้นยุ่งเหยิงจะยิ่งแก้ไขยากขึ้นอีกหลายเท่า
แล้วโค้ดที่มี 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 ที่ไม่จำเป็นตั้งแต่แรกเลย มีให้น้อยที่สุด มีเท่าที่จำเป็นจริงๆ เท่านั้น
สวัสดีครับ
Posted on December 10, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 30, 2024