หยุดปัญหา Circular Dependencies ด้วย SDP

Circular dependencies คือเพชฌฆาตเงียบ พวกมันไม่ทำให้เกิดข้อผิดพลาดในการ build และไม่ทำให้เกิด runtime exception ตอน import แต่กลับทำให้เกิดค่า undefined ที่ตรวจจับได้ยาก ซึ่งอาจจะไปปรากฏใน production หลังจากผ่านไปหลายสัปดาห์

การเกิด cycle จะเกิดขึ้นเมื่อ Module A import จาก B และ B ก็ import กลับจาก A

JavaScript จะจัดการการ import เหล่านี้อย่างเงียบๆ หาก Module A ยังโหลดไม่เสร็จในขณะที่ Module B เรียกใช้งาน JavaScript จะส่งคืนค่าเป็น object ว่างๆ ผลที่ตามมาคือโค้ดของคุณจะพังในภายหลัง และ stack trace จะชี้ไปยังตำแหน่งที่ผิด

คุณสามารถแก้ไขปัญหานี้ได้ด้วย dependency-cruiser เครื่องมือนี้จะสแกนไฟล์ของคุณและสร้างแผนผัง dependency graph โดยรองรับทั้ง TypeScript และ monorepos

วิธีการตั้งค่า:

  • เพิ่มเข้าไปใน dev dependencies: yarn add -D dependency-cruiser
  • เพิ่ม script ใน package.json: "depcruise": "depcruise packages --config .dependency-cruiser.js"
  • สร้างไฟล์ .dependency-cruiser.js เพื่อตรวจจับ cycle

แต่การหา cycle เจอเป็นเพียงแค่ครึ่งทางของปัญหา คุณต้องหยุดไม่ให้มันเกิดขึ้นตั้งแต่แรก

ใช้หลักการ Stable Dependencies Principle (SDP) กฎนี้ระบุว่าคุณต้องทำการ depend ไปในทิศทางของความเสถียร (stability)

Stability คือคุณสมบัติทางโครงสร้าง Module จะมีความเสถียร (stable) เมื่อมี module อื่นๆ จำนวนมากมา depend มัน หากคุณแก้ไข module ที่มีความเสถียร คุณจะส่งผลกระทบต่อคนจำนวนมาก ต้นทุน (cost) ในการเปลี่ยนแปลงนี้เองที่ทำให้มันมีความเสถียร

ใช้สูตร Instability: I = Fan-Out / (Fan-In + Fan-Out)

• I = 0 หมายความว่า module นั้นมีความเสถียรสูงสุด ทุกอย่างขึ้นอยู่กับมัน และมันไม่ขึ้นอยู่กับอะไรเลย • I = 1 หมายความว่า module นั้นไม่มีความเสถียรสูงสุด มันขึ้นอยู่กับหลายสิ่งหลายอย่าง แต่ไม่มีอะไรมาขึ้นอยู่กับมันเลย

กฎคือ: ค่า instability score ของ module หนึ่งๆ จะต้องสูงกว่าค่า score ของทุก module ที่มัน import เข้ามา ลูกศรของคุณต้องชี้ไปยังทิศทางที่มีค่า instability ต่ำกว่าเสมอ

low-level utils (I ≈ 0) ของคุณไม่ควร import จาก high-level pages (I ≈ 1) โดยเด็ดขาด

คุณสามารถเปลี่ยนหลักการนี้ให้เป็นกฎอัตโนมัติได้ด้วย dependency-cruiser แทนที่จะตรวจแค่ cycle ให้ตรวจเรื่องทิศทางด้วย:

  • ป้องกันไม่ให้ utils import จาก features
  • ป้องกันไม่ให้ features import จาก pages

วิธีนี้จะช่วยหยุดเงื่อนไขทางโครงสร้างที่ทำให้เกิด cycle คุณจะเปลี่ยนจากการตามแก้ bug มาเป็นการป้องกันไม่ให้มันเกิดขึ้นแทน

Source: https://dev.to/wojciech_kot_b82f5d7cbfc6/stop-circular-dependencies-before-they-stop-you-dependency-cruiser-the-stable-dependencies-34ho