ระบบนิเวศ JavaScript ต่อสู้กับปัญหา Dependencies ที่บวมพองเมื่อไลบรารีสำหรับกรณีพิเศษครองยอดดาวน์โหลดแพ็กเกจ

ทีมชุมชน BigGo
ระบบนิเวศ JavaScript ต่อสู้กับปัญหา Dependencies ที่บวมพองเมื่อไลบรารีสำหรับกรณีพิเศษครองยอดดาวน์โหลดแพ็กเกจ

ชุมชนนักพัฒนา JavaScript กำลังต่อสู้กับปัญหาที่เพิ่มขึ้นของ dependency bloat ที่เกิดจากไลบรารีที่ให้ความสำคัญกับกรณีพิเศษมากกว่าสถานการณ์การใช้งานทั่วไป ปัญหานี้ได้สร้าง dependency trees ที่ซับซ้อนโดยไม่จำเป็น ซึ่งส่งผลกระทบต่อโปรเจ็กต์หลายล้านโปรเจ็กต์ทั่วโลก

สาเหตุหลัก: การออกแบบที่ซับซ้อนเกินไปสำหรับสถานการณ์ที่หายาก

ปัญหาหลักเกิดจากไลบรารีที่จัดการกับกรณีพิเศษที่นักพัฒนาส่วนใหญ่จะไม่เคยเจอ แทนที่จะมุ่งเน้นไปที่การใช้งานหลัก แพ็กเกจยอดนิยมหลายตัวกลับไปใช้การตรวจสอบและการตรวจสอบประเภทข้อมูลอย่างกว้างขวางสำหรับสถานการณ์ที่เกิดขึ้นน้อยกว่า 1% ในแอปพลิเคชันจริง วิธีการนี้บังคับให้ผู้ใช้ทุกคนต้องแบกรับต้นทุนด้านประสิทธิภาพของฟีเจอร์ที่พวกเขาไม่ต้องการ

การสนทนาในชุมชนเผยให้เห็นว่าปัญหานี้มีอยู่บางส่วนเพราะ JavaScript ในอดีตขาดการกำหนดประเภทข้อมูลที่แข็งแกร่ง ดังที่นักพัฒนาคนหนึ่งกล่าวไว้ว่า JavaScript ต้องการการตรวจสอบ runtime อย่างกว้างขวางเนื่องจากคุณสามารถส่งอะไรก็ได้ให้กับฟังก์ชันและมันเป็นหน้าที่ของการใช้งานที่จะต้องจัดการกับมัน อย่างไรก็ตาม แม้จะมีการนำ TypeScript มาใช้แล้ว ไลบรารีหลายตัวยังคงใช้รูปแบบการเขียนโปรแกรมแบบป้องกันนี้ต่อไป

ไลบรารียอดนิยมที่มียอดดาวน์โหลดหลายล้านครั้งต่อสัปดาห์แสดงให้เห็นขนาดของปัญหา

แพ็กเกจที่ใช้กันอย่างแพร่หลายหลายตัวแสดงให้เห็นปัญหานี้ ไลบรารี is-number ที่ถูกดาวน์โหลด 90 ล้านครั้งต่อสัปดาห์ ไม่ได้แค่ตรวจสอบว่าบางอย่างเป็นตัวเลขหรือไม่ แต่ยังตรวจสอบเฉพาะตัวเลขบวก จำกัด และสตริงที่เหมือนตัวเลข นักพัฒนาส่วนใหญ่แค่ต้องการ typeof n === 'number' ทำให้ความซับซ้อนเพิ่มเติมไม่จำเป็น

ในทำนองเดียวกัน is-arrayish ได้รับดาวน์โหลด 76 ล้านครั้งต่อสัปดาห์ แต่จัดการกับสถานการณ์ที่ซับซ้อนเช่นการตรวจจับ array ข้าม realm ที่แอปพลิเคชันส่วนใหญ่ไม่เคยเจอ เมธอด Array.isArray() มาตรฐานจะเพียงพอสำหรับกรณีการใช้งานส่วนใหญ่

ไลบรารี pascalcase เป็นตัวอย่างของ feature creep โดยรับสตริง ค่า null ค่า undefined อาร์เรย์ ฟังก์ชัน และอ็อบเจ็กต์ใดๆ ที่มีเมธอด toString แต่ผู้ใช้เกือบทุกคนส่งสตริงธรรมดา ทำให้การจัดการ input เพิ่มเติมซ้ำซ้อน

ไลบรารีที่ Over-Engineered ยอดนิยมจากจำนวนดาวน์โหลดรายสัปดาห์:

  • is-number: 90 ล้านดาวน์โหลด/สัปดาห์ - ตรวจสอบตัวเลขเชิงบวก จำนวนจำกัด และสตริงที่เหมือนตัวเลข
  • is-arrayish: 76 ล้านดาวน์โหลด/สัปดาห์ - ตรวจสอบออบเจ็กต์ที่เหมือนอาร์เรย์รวมถึงสถานการณ์ cross-realm
  • pascalcase: 9.7 ล้านดาวน์โหลด/สัปดาห์ - รับค่าสตริง, null, undefined, อาร์เรย์, ฟังก์ชัน และออบเจ็กต์
  • is-regexp: 10 ล้านดาวน์โหลด/สัปดาห์ - รองรับการตรวจจับ RegExp แบบ cross-realm
  • shebang-regex: 86 ล้านดาวน์โหลด/สัปดาห์ - โค้ด 2 บรรทัด เทียบเท่ากับ startsWith('!')

ต้นทุนที่ซ่อนอยู่ของการตรวจสอบที่มองไม่เห็น

วิธีการนี้สร้างภาระที่ซ่อนอยู่สำหรับนักพัฒนาที่โดยไม่รู้ตัวได้รับกฎการตรวจสอบที่เข้มงวดซึ่งฝังลึกอยู่ใน dependency trees ของพวกเขา นักพัฒนาหลายคนไม่รู้ว่าไลบรารีที่พวกเขาใช้โดยอ้อมอาจปฏิเสธตัวเลขลบหรือค่าอนันต์ ทำให้เกิดพฤติกรรมที่ไม่คาดคิดในแอปพลิเคชันที่ใช้งานจริง

ตรรกะการตรวจสอบมักทำงานโดยมองไม่เห็น ทำให้การ debug ยากขึ้นเมื่อการจัดการกรณีพิเศษรบกวนกรณีการใช้งานที่ถูกต้อง สิ่งนี้เปลี่ยนภาระการตรวจสอบจากขอบเขตแอปพลิเคชันซึ่งเป็นที่ที่มันควรจะอยู่ ไปยังส่วนลึกภายใน dependency chain ที่ควบคุมได้ยากกว่า

โซลูชันที่เกิดขึ้นจากชุมชน

ชุมชนนักพัฒนากำลังแก้ไขปัญหานี้อย่างแข็งขันผ่านหลายวิธีการ ชุมชน e18e มีส่วนร่วมในการปรับปรุงประสิทธิภาพทั่วทั้งระบบนิเวศโดยการแทนที่ dependencies ที่บวมพองด้วยทางเลือกที่ทันสมัยและมีประสิทธิภาพ พวกเขาดูแลรายการของการแทนที่ที่แนะนำและให้ปลั๊กอิน ESLint เพื่อช่วยระบุ dependencies ที่มีปัญหา

สำหรับผู้ดูแลไลบรารี โซลูชันเกี่ยวข้องกับการสร้างสมมติฐานที่เข้มงวดขึ้นเกี่ยวกับประเภทข้อมูลที่รับเข้ามาและการลบการตรวจสอบที่ไม่จำเป็น ทางเลือกสมัยใหม่เช่น scule แสดงให้เห็นวิธีการนี้โดยรับเฉพาะประเภทข้อมูลที่พวกเขาออกแบบมาสำหรับในขณะที่รักษา zero dependencies

เครื่องมือเช่น npmgraph และ node-modules.dev ช่วยนักพัฒนาให้เห็นภาพ dependency trees ของพวกเขาและระบุโอกาสในการปรับให้เหมาะสม เครื่องมือเหล่านี้ทำให้ง่ายขึ้นในการจุดแพ็กเกจที่ละเอียดเกินความจำเป็นและหาทางเลือกที่มีประสิทธิภาพมากขึ้น

ทางเลือกที่มีน้ำหนักเบาที่แนะนำ:

  • scule: 1.8M ดาวน์โหลด/สัปดาห์ - การแปลงรูปแบบข้อความที่ไม่มี dependencies
  • dlv: 14.9M ดาวน์โหลด/สัปดาห์ - การเข้าถึง property แบบลึกด้วยการตรวจสอบที่น้อยที่สุด
  • Native Array.isArray() - ทดแทน is-arrayish สำหรับการใช้งานส่วนใหญ่
  • Native typeof n === 'number' - ทดแทน is-number สำหรับการตรวจสอบชนิดข้อมูลพื้นฐาน
  • Inline Math.min(Math.max(value, min), max) - ทดแทนฟังก์ชัน clamp ที่ซับซ้อน

เคลื่อนไปสู่ Dependencies ที่เบากว่า

เส้นทางไปข้างหน้าต้องการการเปลี่ยนแปลงพื้นฐานในปรัชญาการออกแบบไลบรารี แทนที่จะสร้างไลบรารีที่เน้นกรณีพิเศษก่อนซึ่งจัดการทุกสถานการณ์ที่เป็นไปได้ ชุมชนควรมุ่งเน้นไปที่ไลบรารีกรณีการใช้งานทั่วไปพร้อมส่วนขยายเสริมสำหรับความต้องการเฉพาะทาง

วิธีการนี้จะช่วยให้ผู้ใช้ส่วนใหญ่ได้รับประโยชน์จากไลบรารีที่เบาและเร็วกว่า ในขณะที่ยังคงให้โซลูชันสำหรับนักพัฒนาที่ต้องการการจัดการกรณีพิเศษจริงๆ เป้าหมายคือการทำให้แน่ใจว่าเฉพาะผู้ที่ต้องการการตรวจสอบที่ซับซ้อนเท่านั้นที่จะจ่ายต้นทุนด้านประสิทธิภาพของมัน แทนที่จะบังคับใช้กับทั้งระบบนิเวศ

หมายเหตุ: dependency tree หมายถึงเครือข่ายของแพ็กเกจที่โปรเจ็กต์พึ่งพา รวมทั้ง direct dependencies (แพ็กเกจที่คุณติดตั้งโดยตรง) และ indirect dependencies (แพ็กเกจที่ dependencies ของคุณต้องการ)

อ้างอิง: The bloat of edge-case first libraries