DEV Community

Chakrit Likitkhajorn
Chakrit Likitkhajorn

Posted on

Dependency

โค้ดที่เขียนไม่ตรงมาตรฐาน ปั่นงานให้เสร็จ ไม่ได้แก้ยากเลย แต่โค้ดที่มีส่วนอื่นๆ ของระบบใช้มันเยอะต่างหากที่แก้ไขยาก และถ้าตรงนั้นยุ่งเหยิงจะยิ่งแก้ไขยากขึ้นอีกหลายเท่า

แล้วโค้ดที่มี 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)