3 คะแนน โดย GN⁺ 2024-10-01 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Riot Games มองหนี้ทางเทคนิคว่าเป็น โค้ดหรือข้อมูล ที่นักพัฒนาในอนาคตจะต้องจ่ายต้นทุน และได้สรุปภาษากลางสำหรับตัดสินหนี้ผ่านกรณีการพัฒนา League of Legends
  • เกณฑ์ประเมินมีสามอย่างคือ ผลกระทบ, ต้นทุนการแก้ไข และความแพร่กระจาย โดยเฉพาะความแพร่กระจายหมายถึงระดับที่หนี้ลุกลามไปยังระบบ ข้อมูล และแนวปฏิบัติการพัฒนาอื่น ๆ เมื่อเวลาผ่านไป
  • ประเภทของหนี้แบ่งเป็น Local Debt ที่ติดอยู่ในการทำงานภายใน, MacGyver Debt ที่เชื่อมสองระบบแบบชั่วคราว, Foundational Debt ที่สมมติฐานเชิงลึกฝังอยู่ในโครงสร้าง และ Data Debt ที่คอนเทนต์ถูกสร้างทับบนข้อบกพร่อง
  • Cataclysm ของ Jarvan, การใช้ std::string ควบคู่กับ AString, การใช้ Lua ของ BlockBuilder และ block parameter naming bug ถูกใช้เป็นตัวอย่างเฉพาะของแต่ละประเภท
  • หนี้ที่มีความแพร่กระจายต่ำอาจปล่อยไว้นานได้ แต่หนี้ที่มีความแพร่กระจายสูงจะทำให้ต้นทุนการแก้ไขและผลกระทบเพิ่มขึ้นตามเวลา จึงควรตัดเส้นทางการแพร่กระจายตั้งแต่เนิ่น ๆ

สามแกนในการตัดสินหนี้ทางเทคนิค

  • หนี้ทางเทคนิคคือ “โค้ดหรือข้อมูล ที่นักพัฒนาในอนาคตจะต้องจ่ายต้นทุน”
  • หากต้องการตัดสินว่าควรแก้หนี้บางอย่างตอนนี้ แก้ทีหลัง หรือปล่อยไว้ตามความเป็นจริง จำเป็นต้องมีเกณฑ์วัดร่วมกัน
  • Riot ประเมินหนี้ทางเทคนิคด้วยสามแกน
    • impact: ผลกระทบต่อผู้เล่นและนักพัฒนา
    • fix cost: เวลาที่ต้องใช้ในการแก้ไขและความเสี่ยงในการปล่อยใช้งาน
    • contagion: หากปล่อยไว้ ปัญหาจะแพร่กระจายมากแค่ไหน
  • impact: ต้นทุนที่ปรากฏต่อผู้เล่นและนักพัฒนา

    • สำหรับผู้เล่น จะปรากฏเป็นบั๊ก ฟีเจอร์ที่ขาดหาย หรือพฤติกรรมที่ไม่คาดคิด
    • สำหรับนักพัฒนา จะสะสมเป็นความล่าช้าในการ implement การรบกวน workflow และรายละเอียดที่ไม่จำเป็นต้องจำ
    • ในที่นี้ นักพัฒนาไม่ได้หมายถึงแค่วิศวกร แต่รวมถึงสายงานที่เกี่ยวข้องกับการสร้างเกม เช่น ดีไซเนอร์และศิลปิน VFX
    • หนี้บางอย่างขัดขวางการเขียนโค้ดใหม่ของวิศวกร บางอย่างขัดขวางการเขียนสคริปต์ใหม่ของดีไซเนอร์ หรือการสร้าง particle ใหม่ของศิลปิน VFX
  • fix cost: เวลา implement และความเสี่ยงในการปล่อยใช้งาน

    • ต้นทุนการแก้ไขไม่ได้รวมแค่เวลาพัฒนาจริง แต่รวมถึงความเสี่ยงที่เกิดขึ้นเมื่อปล่อยการแก้ไขด้วย
    • ข้อผิดพลาดง่าย ๆ ในฟังก์ชันเดียวอาจแก้ได้ในไม่กี่นาที แต่สมมติฐานเชิงลึกที่ส่งผลต่อ code line ทั้งเกมอาจใช้เวลาหลายสัปดาห์หรือหลายเดือน
    • แม้ระบบที่ “ผิด” ก็อาจถูกใช้เป็นเครื่องมือในการสร้างเกมที่ดีอยู่แล้ว ดังนั้นทันทีที่แก้ คอนเทนต์เดิมอาจพังได้
    • ตัวอย่างเช่น หากเปลี่ยนวิธีจัดการข้อผิดพลาดของ scripting engine หรือวิธีคำนวณเวลาเกิด particle อาจส่งผลต่อสกิลมากกว่า 500 รายการของแชมเปียนมากกว่า 140 ตัว
  • contagion: ระดับการแพร่กระจายเมื่อเวลาผ่านไป

    • contagion หมายถึงเมื่อปล่อยหนี้ทางเทคนิคไว้ มันจะแพร่ไปยังระบบ ข้อมูล และวิธีพัฒนาอื่น ๆ มากแค่ไหน
    • การแพร่กระจายเกิดจากอินเทอร์เฟซกับระบบที่มีปัญหา การคัดลอก/วางข้อมูลที่สร้างทับอยู่ด้านบน และการเปลี่ยนแปลงวิธี implement ฟีเจอร์ใหม่
    • หนี้ที่ถูกแยกกักไว้อย่างดี แม้ค่อยแก้ภายหลังก็มีต้นทุนต่างจากแก้ตอนนี้ไม่มาก
    • หนี้ที่มีความแพร่กระจายสูงจะยิ่งแก้ยากขึ้นเมื่อเวลาผ่านไป และเมื่อระบบจำนวนมากขึ้นติดเชื้อจากการประนีประนอมแกนหลัก ผลกระทบก็จะเพิ่มขึ้นตามไปด้วย

Local Debt: กล่องดำที่สกปรกแค่ภายใน

  • Local Debt คล้ายกับโมเดลการเขียนโปรแกรมแบบกล่องดำคลาสสิก
  • เมื่อมองจากภายนอก ระบบทำงานได้เสถียร แต่ implementation ภายในอาจเลวร้ายหรือสับสนมาก
    • ตัวอย่าง: สกิล, เลเยอร์เครือข่าย, script engine
  • หากในการพัฒนาระบบรอบข้างไม่จำเป็นต้องรับรู้หนี้ภายใน ความแพร่กระจายก็ถือว่าค่อนข้างต่ำ
  • อุปมาในโลกจริง: ดวงตาของมนุษย์

    • ตามโครงสร้างแล้ว ดวงตาของมนุษย์รับภาพกลับหัว และเส้นประสาทจอตาสร้างจุดบอดใกล้บริเวณกึ่งกลางของแต่ละตา
    • ศูนย์การมองเห็นของสมองจะพลิกข้อมูลและเติมจุดบอด ทำให้ส่วนที่เหลือของสมองโต้ตอบกับภาพที่ “ถูกต้อง” ได้
    • ความผิดปกตินี้ถูกจำกัดอยู่เฉพาะระบบตาและเส้นประสาทตา และระบบอื่นหลีกเลี่ยงได้ง่าย จึงเป็นสภาพที่ “ดีพอ”
  • กรณี League: Cataclysm ของ Jarvan

    • Cataclysm ของ Jarvan จนถึงปัจจุบันก็ยังถูกสร้างเป็น minion
    • ดีไซเนอร์สามารถใช้เครื่องมือที่สร้าง “invisible minion” เมื่อต้องผูกเอฟเฟกต์ gameplay เข้ากับตำแหน่งหรือตำแหน่งหลายจุด
    • RiotXypherous อธิบาย “minion” ที่กล่าวถึงตรงนี้ไว้ในคอมเมนต์บน Reddit
    • game object แบบนี้เป็นวิธีที่เสถียรและเข้าใจกันดีสำหรับติดตามและรัน logic ของสคริปต์
    • กำแพงของ Jarvan ต้องใช้ minion จำนวนพอดี 24 ตัว เพื่อไม่ให้ผู้เล่นหลุดออกไปได้
    • เดิมทีเคยมี 12 ตัว แต่ผู้เล่นบางครั้งหลุดออกไประหว่างกำแพงได้ Riot Exgeniar จึงเพิ่มเป็น 24 ตัว
    • ทางเลือกคือโครงสร้าง ring-terrain ซึ่งเป็น logic ชิ้นเดียวที่ควบคุม pathability ของ Cataclysm และสามารถจัดระเบียบ logic กับลดต้นทุนการคำนวณได้เล็กน้อย
  • การประเมิน Cataclysm

    • impact: 1/5
      • ข้อเท็จจริงที่ว่ากำแพงถูกสร้างจาก minion แทบไม่ส่งผลต่อนักพัฒนาคนอื่นที่สร้างคอนเทนต์ใหม่
      • “Jarvan Ult Hitch” เป็นผลจากหนี้นี้ประกอบกับบั๊กการโหลดที่พยายามอ่านนิยาม auto-attack ที่ขาดหายไป
    • fix cost: 2/5
      • ปัจจุบันยังไม่สามารถสร้าง custom geometry ด้วยรูปทรงประกอบได้โดยไม่เขียนโค้ดใหม่
      • หากจะสร้าง “area trigger” รูป ring จำเป็นต้องมีโค้ดคณิตศาสตร์เฉพาะสำหรับคำนวณการชนแบบ ring
      • Riot กำลังสำรวจ Constructive Solid Geometry เพื่อวัตถุประสงค์อื่น และสิ่งนี้อาจลดต้นทุนการแก้ไขลงได้มาก
    • contagion: 1/5
      • ถูกแยกกักไว้อย่างดี เพราะในการพัฒนาฟีเจอร์ไม่จำเป็นต้องคำนึงถึง implementation ของกำแพง Jarvan
      • ความเสี่ยงในการแพร่กระจายคือกรณีที่ดีไซเนอร์คนอื่นคัดลอก/วาง implementation นี้ไปใช้กับแชมเปียนใหม่ ซึ่งในความเป็นจริงก็เคยเกิดขึ้นเป็นครั้งคราว
      • ในฐานะปัญหาด้าน implementation ศักยภาพการแพร่กระจายของ Cataclysm ต่ำและเข้าใจกันดี
  • วิธีจัดการ Local Debt

    • ลักษณะทั่วไปของ Local Debt คือคะแนน contagion ต่ำ
    • หาก impact สูงกว่า fix cost นักพัฒนาที่ทำตัวเป็นพลเมืองดีมักจะแก้มันก่อนจะปล่อยไว้นานเกินไป
    • หากมันไม่มีความแพร่กระจายจริง ๆ ก็ปล่อยไว้ได้นานเท่าที่จำเป็นอย่างปลอดภัย
    • การรีบพุ่งเข้าใส่ Local Debt ที่กระตุ้นความเป็น perfectionist ของวิศวกร แต่ไม่มีผลกระทบกว้างพอ เป็นหนึ่งในความผิดพลาดใหญ่
    • เนื่องจากขอบเขตการเปลี่ยนแปลงเป็นแบบเฉพาะที่ การตรวจยืนยันการแก้ไขและ regression test จึงมักทำได้ง่าย
    • ตัวอย่างที่เพิ่งได้รับการแก้ไข ได้แก่ บั๊กเกี่ยวกับ inhibitor, Janna’s Monsoon และ Tear of the Goddess
      • บั๊กที่ทำให้ inhibitor ในบางสถานการณ์บังคับให้แชมเปียน pathing ไปยังพิกัด 0,0,0
      • ปัญหาที่ Janna’s Monsoon เพิกเฉยต่อ spell shield
      • ปัญหาที่ Tear of the Goddess สะสม stack จากการร่ายที่ไม่ใช้มานา

MacGyver Debt: สภาพที่นำสองระบบมาติดกันด้วยเทปผ้า

  • MacGyver Debt เป็นประเภทหนึ่งที่ตั้งชื่อตามรายการทีวี MacGyver ในช่วงกลางทศวรรษ 1980
  • ในบริบทของหนี้ทางเทคนิค หมายถึงสภาพที่ระบบสองระบบซึ่งขัดแย้งกันถูก “ติดด้วยเทปผ้า” ไว้ตามจุด interface ทั่วทั้ง codebase
  • การเปรียบเทียบในโลกจริง: Seattle

    • ในอดีต Seattle เคยมีชุมชนคู่แข่งสองแห่ง ซึ่งแต่ละแห่งมีกริดของตนเอง
    • เมื่อชุมชนทั้งสองเติบโตขึ้นเป็น Emerald City ในปัจจุบัน กริดที่ต่างกันเล็กน้อยก็ถูกรวมเข้าด้วยกัน ทำให้เกิดบล็อกและอาคารรูปร่างแปลก ๆ รวมถึงการใช้พื้นที่อย่างไม่มีประสิทธิภาพ
  • กรณีของ League: std::string และ AString

    • ใน codebase ของ League มีทั้ง std::string ของ C++ และคลาส AString แบบกำหนดเองของ Riot อยู่ร่วมกัน
    • ทั้งสองอย่างเป็นวิธีสำหรับจัดเก็บ แก้ไข และส่งต่อสตริง
    • Riot มองว่า std::string ทำให้เกิดการจัดสรรหน่วยความจำและต้นทุนด้านประสิทธิภาพจำนวนมากแบบ “ซ่อนอยู่” และทำให้เขียนโค้ดแย่ ๆ ได้ง่าย
    • AString ถูกออกแบบโดยคำนึงถึงการจัดการหน่วยความจำอย่างระมัดระวัง
    • กลยุทธ์การแทนที่คือปล่อยให้สองระบบอยู่ร่วมกัน และให้แปลงไปมาระหว่างกันได้ผ่าน .c_str() และ .Get()
    • มีการเพิ่มการปรับปรุงให้ AString ใช้งานง่ายขึ้น และสนับสนุนให้วิศวกรเปลี่ยน std::string ด้วยตนเองเมื่อแก้ไขโค้ด
    • ด้วยวิธีนี้ std::string จะค่อย ๆ หายไปทีละขั้น และ interface แบบ “เทปผ้า” ระหว่างสองระบบก็ลดลงไปพร้อมกับการทำความสะอาดโค้ด
  • การประเมิน std::string vs AString

    • impact: 2/5
      • การจัดสรรที่มีผลกระทบสูงส่วนใหญ่ซึ่งเคยเกิดจาก std::string ถูกกำจัดออกไปแล้วผ่าน profiling
      • ต้นทุนหลักในปัจจุบันคือภาระทางความคิดเล็กน้อยในการสลับโหมดเมื่อแปลงจากระบบหนึ่งไปยังอีกระบบหนึ่ง
    • fix cost: 3/5
      • การย้ายไป AString ไม่ใช่แค่การ find-and-replace ง่าย ๆ
      • AString มีรูปแบบย่อยตามวัตถุประสงค์ เช่น AStackString ที่จัดสรรครั้งแรกบน stack memory, ARefString สำหรับอ้างอิง static string และ AString ที่อิงการจัดสรรบน heap
      • เพื่อแทนที่ให้ถูกต้อง คนต้องตรวจดูและตัดสินแต่ละจุดด้วยตนเอง และกระบวนการค่อย ๆ เลิกใช้ระบบเดิมจะยาวนานและช้า
    • contagion: -2/5
      • ทำให้ AString ใช้งานง่ายกว่า std::string จึงพลิก contagion ไปในทิศทางที่เป็นประโยชน์
      • ทุกครั้งที่วิศวกรเช็กอินการเปลี่ยนแปลงโค้ดเกม ก็มีโอกาสที่ AString จะแพร่กระจายมากขึ้น
  • วิธีแก้ MacGyver Debt

    • ต้นทุนใหญ่ของ MacGyver Debt มักเป็น ต้นทุนทางปัญญา ที่ต้องสลับโหมดเมื่อข้ามขอบเขต
    • หากติดขัดเพราะบั๊กหรือฟีเจอร์อยู่ในระบบที่ “ผิด” การย้ายจุดเป้าหมายไปยังระบบที่ “ถูก” โดยทั่วไปมักทำได้ตรงไปตรงมา
    • contagion เชิงสัมพัทธ์ระหว่างระบบใหม่กับระบบเดิมเป็นตัวชี้วัดสำคัญ
    • หากพลิกสมดุลให้ฝั่งระบบใหม่แพร่กระจายได้มากกว่า ระบบที่ดีกว่าจะชนะในที่สุด
    • ต้องทำให้ระบบที่ดีกว่าในระดับทั่วทั้งระบบน่าดึงดูดกว่าในระดับเฉพาะที่ด้วย
    • หากวิศวกรที่ถูกกดดันด้วยเวลาเลือกฝั่งสถานะสุดท้ายที่ต้องการได้ แม้กำลังทำ greedy optimization ในงานประจำวัน ก็แปลว่ากำลังไปในทิศทางที่ดี
    • อีกแนวทางหนึ่งคือ brute-force refactor ขนาดใหญ่ และขึ้นอยู่กับว่าระบบต่าง ๆ map กันใกล้เคียงแค่ไหน อาจใช้ clever regex แก้บางส่วนหรือทั้งหมดได้

Foundational Debt: เมื่อสมมติฐานระดับลึกฝังอยู่ในโครงสร้างทั้งหมด

  • Foundational Debt คือสภาพที่สมมติฐานบางอย่างในส่วนลึกของระบบถูกฝังเข้าไปในวิธีการทำงานทั้งหมด
  • ผู้ใช้ที่มีประสบการณ์กับระบบอาจมองว่าสิ่งนี้เป็น “มันก็เป็นแบบนั้นอยู่แล้ว” จึงอาจสังเกตเห็นได้ยาก
  • อุปมาในโลกจริง: United States Customary Units

    • คนที่เติบโตในสหรัฐฯ จะต้องท่องจำการแปลงหน่วย เช่น 1 ไมล์เท่ากับ 5,280 ฟุต, 1 ควอร์ตเท่ากับ 2 ไพนต์, 1 แกลลอนเท่ากับ 4 ควอร์ต
    • รัฐบาลสหรัฐฯ เคยพิจารณาเปลี่ยนไปใช้ metric หลายครั้ง แต่ยังคงเป็นหนึ่งใน 7 ประเทศที่ไม่ได้รับ Système International เป็นระบบการวัดอย่างเป็นทางการ
    • หนี้นี้ฝังอยู่ในป้ายถนน สูตรอาหาร โรงเรียนประถม และในหัวของผู้คน
  • กรณีของ League: BlockBuilder และ Lua

    • ตัวอย่าง Foundational Debt ขนาดใหญ่ที่ Riot เคยจัดการ ได้แก่ Determinism in League of Legends และ Game Data Server
    • การใช้ Lua scripting language ของ League ก็เป็นตัวอย่างของ Foundational Debt เช่นกัน
    • นักออกแบบ League ใช้เครื่องมือชื่อ BlockBuilder เพื่อนำบล็อกฟังก์ชันมาต่อกันเพื่อสร้างพฤติกรรมที่ซับซ้อน
    • บล็อกฟังก์ชันประกอบด้วยการหาค่าระยะห่างระหว่างจุด การสร้าง minion การประมวลผล damage และ script flow control หลากหลายรูปแบบ
    • ชุด operation ที่นักออกแบบเลือกใช้มีความหลากหลายแต่ก็จำกัด และพารามิเตอร์ของแต่ละ operation ก็มีข้อจำกัดด้วย
    • ในช่วงแรกของ League of Legends มีการตัดสินใจไม่เก็บบล็อกและพารามิเตอร์ไว้ในรูปแบบที่เรียบง่ายและมีข้อจำกัดซึ่งเหมาะกับข้อมูล แต่เลือกเก็บด้วย arrays และ tables ของภาษา Lua ซึ่งทรงพลัง แต่ซับซ้อนเกินไปสำหรับวัตถุประสงค์นี้
    • จากนั้นการพัฒนาเกมราว 10 ปีก็ดำเนินต่อมาบนรากฐานนั้น และการจัดการ Lua object กลายเป็นหนึ่งในงานที่พบบ่อยที่สุดในเอนจิน
  • การประเมิน BlockBuilder Lua

    • impact: 4/5
      • ความไม่สอดคล้องกันระหว่าง Lua กับพื้นที่ปัญหานี้ก่อให้เกิดต้นทุนจำนวนมาก
      • ในแต่ละ frame ของ logic ของ BlockBuilder นั้น callstack ถูกปนเปื้อนด้วย marshalling stack frame ประมาณ 6 ชั้น
      • งาน marshalling ไม่ได้มีต้นทุนต่ำในแง่การใช้ CPU ของ server
      • การอ่าน diff ของการเปลี่ยนแปลง script ยากเกินความจำเป็น
      • หากต้อง parsing/searching script file เพื่อทำความเข้าใจฟังก์ชัน จำเป็นต้องมีความเข้าใจภาษา Lua ค่อนข้างลึก
    • fix cost: 4/5
      • Lua ฝังลึกอยู่ในเอนจิน ทำให้ถอดออกได้ยาก
      • หนึ่งในข้อเสนอปัจจุบันคือการสร้าง wrapper class ที่ทำงานเหมือน Lua object แต่ภายในเป็น struct ที่เรียบง่ายกว่ามาก เพื่อค่อย ๆ เปลี่ยนส่วนภายในของ scripting ไปเป็นรูปแบบที่เหมาะสมกว่า
      • ไม่ว่าจะใช้แนวทางใด ก็ต้องดำเนินการอย่างระมัดระวังและรอบคอบ
    • contagion: 4/5
      • ทุกครั้งที่ระบบสัมผัสกับ scripting ระบบนั้นจะถูกหล่อหลอมโดย operation และข้อกำหนดของ Lua backend
      • scripting เป็นหน่วย logic หลักของ LoL
      • Riot เพิ่ม Building Block ใหม่โดยเฉลี่ยประมาณทุก 3–4 วัน และ Building Block แต่ละตัวจะจัดการ Lua object โดยตรง
      • ยิ่งไม่แทนที่ Lua นานเท่าไร การแทนที่ Lua ก็ยิ่งยากขึ้นเท่านั้น
  • วิธีลด Foundational Debt

    • Foundational Debt มีแนวโน้มได้คะแนนสูงในทั้งสามแกน ได้แก่ impact, fix cost และ contagion
    • fix cost ที่สูงทำให้ต้องใช้ระบบที่ไม่สมบูรณ์ต่อไป และบางครั้งนี่ก็เป็นทางเลือกที่ถูกต้อง
    • แต่ด้วย impact และ contagion ที่สูง การแก้ Foundational Debt ที่รุนแรงอาจให้ผลตอบแทนมาก
    • กลยุทธ์การแก้ที่พบได้บ่อยที่สุดที่ Riot สังเกตเห็น คือการสร้างระบบใหม่ขึ้นข้าง ๆ ระบบเดิม
    • หากเป็นไปได้ ให้เปลี่ยน foundational debt เดิมให้เป็น MacGyver Debt แล้วค่อย ๆ พอร์ตผ่าน conversion operation เพื่อสลับไปมาระหว่างระบบใหม่กับระบบเดิม
    • วิธีนี้ช่วยจำกัดความเสี่ยงที่ต้องเผชิญ ขณะเริ่มได้รับประโยชน์ในบางพื้นที่
    • เมื่อการเปลี่ยนผ่านแบบนี้ทำไม่ได้ อาจสร้าง compile time switch หรือถ้าเป็นไปได้ก็ใช้ loading time switch เพื่อสร้างความเชื่อมั่นต่อระบบใหม่
    • แนวทาง compile time switch กำลังถูกใช้ในการเปลี่ยนผ่าน GDS
    • แนวทาง loading time switch ใช้ได้ผลกับ Determinism

Data Debt: สถานะที่มีคอนเทนต์จำนวนมากทับถมอยู่บนข้อบกพร่อง

  • Data Debt เกิดขึ้นเมื่อมีคอนเทนต์จำนวนมากทับถมอยู่บนหนี้ทางเทคนิคประเภทอื่น
  • จุดเริ่มต้นอาจเป็นบั๊กใน scripting system, file format ที่ไม่เหมาะกับ item, หรือสองระบบที่เข้ากันได้ไม่ดี
  • เมื่อมีการสร้างคอนเทนต์จำนวนมาก เช่น art, scripts, sounds บนข้อบกพร่องของโค้ดนั้น การแก้หนี้ทางเทคนิคตั้งต้นจะกลายเป็นเรื่องเสี่ยงมาก
  • เมื่อเวลาผ่านไป การหาว่าอะไรจะพังบ้างจะยากจนแทบทรมาน
  • อุปมาในโลกจริง: DNA

    • genome ของสิ่งมีชีวิตค่อย ๆ สะสมผ่าน mutation, transcription error และ evolutionary pressure ตลอดหลายล้านปี
    • ข้อผิดพลาดในการคัดลอกบางอย่างไร้ประโยชน์แต่ไม่เป็นอันตราย บางอย่างเป็นอันตราย และบางอย่างให้ข้อได้เปรียบอย่างมาก
    • การหาว่าชิ้นส่วน DNA ทำหน้าที่อะไรจริง ๆ นั้นยากมาก
    • เราเข้าใจอย่างสมบูรณ์ว่า base pair หมายถึงอะไร และชุดของ base pair ถูกแปลเป็น amino acid สำหรับ protein construction อย่างไร
    • เราเริ่มเข้าใจมากขึ้นเกี่ยวกับ non-encoding role บางส่วนของ DNA ด้วย
    • แต่ใน base pair มากกว่า 3 พันล้านคู่ของ genome มนุษย์ ยังมีอีกมากที่เราแทบไม่เข้าใจ
    • ตอน CRISPR ของ Radiolab กล่าวถึงปริศนาหนึ่งในลักษณะนั้นที่เพิ่งคลี่คลายได้ไม่นาน
  • กรณีของ League: block parameter naming bug

    • Data Debt ของ League of Legends ส่งผลมากที่สุดเมื่อมันเปลี่ยนสิ่งที่เดิมควรเป็นการแก้เล็ก ๆ ให้กลายเป็นงานหนัก
    • game engineer สั่งสมความรู้เชิงลึกเกี่ยวกับวิธี implement ระบบเกม และชำนาญในการคาดการณ์ว่าการเปลี่ยนโค้ดแบบใดจะทำให้ data แบบใดพัง
    • Data Debt เป็นหนึ่งในข้อพิจารณาที่สำคัญที่สุดเมื่อเปลี่ยนแปลง LoL engine
    • ตัวอย่าง Data Debt ที่แก้ไปเมื่อหลายปีก่อนคือบั๊กเกี่ยวกับ block parameter ใน BlockBuilder scripting language
    • ใน toy example หากพยายามเพิ่ม armor ของ Owner ด้วยตัวแปรและค่าคงที่ ค่าที่คาดหวังคือ bonus armor 25 ซึ่งมาจากตัวแปร Delta 20 บวกค่าคงที่ 5
    • หากชื่อตัวแปรเหมือนกับชื่อ parameter ในอดีตผลลัพธ์จะกลายเป็น 40
    • ผู้เขียนบอกว่าไม่รู้เหมือนกันว่าทำไมจึงไม่เป็น 45
  • กระบวนการแก้จริง

    • เมื่อ NoopMoney วิศวกรจาก Champions team พยายามแก้พฤติกรรมนี้ การแก้โค้ดจริงมีเพียง ลบ 4 บรรทัด เท่านั้น
    • แต่หนี้ที่แพร่กระจายสูงมากต้องอาศัยการวางแผนอย่างละเอียดแม้กับการเปลี่ยนแปลงเล็ก ๆ
    • ใน script 400,000 บรรทัดของ LoL numerical parameter ตรงไหนก็อาจถูกบั๊กนี้ทำให้ค่าเป็นสองเท่าอยู่ได้
    • ปัญหาที่ใหญ่กว่าคือเกมถูก balance และ tuning ให้เข้ากับค่าที่อาจถูกทำให้เป็นสองเท่านั้นอยู่แล้ว ทำให้ script เหล่านั้นทำงาน “correctly” อยู่
    • NoopMoney ต้องทำให้ fix สามารถ toggle ได้บน Live เพื่อเตรียมรับมือบั๊กที่ไม่คาดคิด
    • เพื่อระบุว่า script ใดพึ่งพาบั๊กนี้ เขาทำ regex searching อย่างกว้างขวางและ QA sweep
    • สุดท้ายปัญหาที่เกิดจากการแก้ไขมีขนาดค่อนข้างเล็ก และมีเพียง champion script ไม่กี่ตัวที่ต้องเปลี่ยน
    • Data Debt ทำให้การคาดการณ์ผลลัพธ์เป็นเรื่องยาก
  • การประเมิน Parameter Naming Bug

    • impact: 2/5
      • ผลกระทบเมื่อเกิดขึ้นมีขนาดเล็ก
      • มันอาจทำให้ค่าที่ส่งเข้ามาเพิ่มเป็นสองเท่า และทิ้งค่าคงที่ไป
      • กลายเป็น tribal knowledge ที่ไร้ประโยชน์อีกอย่างหนึ่งที่ designer และ engineer ที่รู้เรื่องนี้ต้องจำไว้
      • developer mindshare เป็นทรัพยากรที่มีค่าเกินกว่าจะถูกใช้สิ้นเปลืองแบบนี้
    • fix cost: 2/5
      • โดยรวมแล้วการแก้ไขนั้นตรงไปตรงมา
      • สามารถสร้าง live feature toggle เพื่อเพิ่มความมั่นใจในความปลอดภัยของ fix ได้
      • ส่วนที่แพงที่สุดคือ screening ช่วงต้นเพื่อประเมินขอบเขตของปัญหา สำหรับกำหนดเป้าหมายการทดสอบ
    • contagion: 4/5
      • สิ่งที่โชคร้ายคือบั๊กนี้เล่นงานพฤติกรรมที่สมเหตุสมผลมาก
      • หากต้องการสร้าง damage ให้ unit การเก็บค่าไว้ในตัวแปรชื่อ “Damage” เป็นเรื่องสมเหตุสมผลอย่างยิ่ง
      • เมื่อ ApplyDamage block รับ amount ผ่าน parameter ที่มีชื่อเดียวกัน บั๊กก็ถูก trigger
      • หากคนอื่น copy/paste block นั้นเพื่อสร้าง spell ที่คล้ายกัน บั๊กก็ยิ่งแพร่กระจาย
  • เหตุผลที่ Data Debt แพร่กระจายสูง

    • โดยทั่วไป Data Debt ถูกประเมินว่ามีต้นทุนการแก้สูง เพราะทำให้ประเมินผลกระทบจากการเปลี่ยนแปลงได้ยาก
    • สิ่งที่น่ากังวลยิ่งกว่าคือ ด้วยธรรมชาติของ data มันแทบจะมีความสามารถในการแพร่กระจายสูงมากเสมอ
    • การ copy/paste data เดิมเพื่อสร้าง data ใหม่เป็นวิธีที่ยอมรับกันโดยทั่วไป
    • เมื่อต้องสร้าง skillshot spell ใหม่ การเริ่มจาก Ezreal’s Mystic Shot ช่วยประหยัดเวลาได้มาก และปัญหาใน data เดิมก็แพร่ต่อไปยัง data ลูกหลานด้วย
    • data แทบไม่ได้รับการตรวจทานเชิงเทคนิคแบบเดียวกับ code review ดังนั้นแม้ bad practice จะเป็นที่รู้กันกว้างขวาง ก็ยังสังเกตเห็นและหยุดการแพร่กระจายได้ยาก
    • การแก้ปัญหาใน data โดยมากต้องให้คนที่มีตาและสมองตรวจสอบเอง compiler กับ formal logic เพียงอย่างเดียวไม่เพียงพอ
  • สองแนวทางในการแก้ Data Debt

    • แนวทางแรกคือ do it right checkbox
      • เป็นวิธีสร้าง toggle ระหว่าง behavior เดิมที่ “broken” กับ behavior ใหม่ที่ “fixed” ให้กับ data creator
      • ตามอุดมคติควรกำหนดให้ fixed version เป็นค่าเริ่มต้น และให้ old content ใช้ broken version
      • หลังจากนั้นสามารถย้ายไปใช้เวอร์ชันใหม่ด้วยการ replacement แบบช้า ๆ และสม่ำเสมอ คล้าย MacGyver Debt
      • ข้อเสียคือเกิดต้นทุนถาวรจากการเพิ่มองค์ประกอบที่ไม่จำเป็นลงใน editing UI มากขึ้นเรื่อย ๆ
    • แนวทางที่สองคือ just fix the damn thing
      • เป็นวิธีที่ NoopMoney ใช้กับ parameter naming bug
      • หลังจากแก้บั๊กแล้ว พยายามซ่อม data ทั้งหมดที่ได้รับผลกระทบอย่างมีนัยสำคัญ
      • เทคนิคที่ช่วยให้น่ากลัวน้อยลง ได้แก่ grep และ regex searching จำนวนมากเพื่อทำความเข้าใจ theoretical impact, targeted testing และการเตรียม toggle เพื่อย้อนกลับไปใช้พฤติกรรมเดิมหากพบสิ่งที่ตกหล่นร้ายแรงกว่าหลัง ship
      • Determinism ช่วยการทดสอบการเปลี่ยนแปลงลักษณะนี้ได้มาก เพราะทำให้ตรวจสอบได้ว่า server สร้างผลลัพธ์เดียวกันก่อนและหลังการเปลี่ยนแปลงหรือไม่

สรุป: ต้องรวม contagion ไว้ในการประเมินต้นทุน

  • ตัวชี้วัดหนี้ทางเทคนิคคือ impact, fix cost, contagion
    • impact คือผลกระทบต่อลูกค้าและนักพัฒนา
    • fix cost คือเวลาและความเสี่ยง
    • contagion คือระดับการแพร่กระจายของปัญหา
  • นักพัฒนาส่วนใหญ่พิจารณา impact และ fix cost เป็นประจำ แต่การพูดคุยเรื่อง contagion ค่อนข้างพบได้น้อยกว่า
  • contagion อาจกลายเป็นศัตรูที่เลวร้ายที่สุดของนักพัฒนา เมื่อปัญหาฝังลึกและกำจัดยากขึ้นเรื่อย ๆ
  • ในทางกลับกัน หากทำให้ fix แพร่กระจายได้มากกว่าปัญหา ก็สามารถเปลี่ยน contagion ให้เป็นอาวุธได้
  • หนี้ทางเทคนิคส่วนใหญ่ที่พบใน League จัดอยู่ในหนึ่งในสี่หมวดหมู่
    • Local Debt: หนี้ที่เหมือน black box ซึ่งข้างในรกเละ
    • MacGyver Debt: หนี้ที่มีระบบตั้งแต่ 2 ระบบขึ้นไปถูกติดเข้าด้วยกันเหมือนใช้เทปกาว ผ่าน conversion function
    • Foundational Debt: หนี้ที่โครงสร้างทั้งหมดถูกสร้างขึ้นบนสมมติฐานที่โชคร้าย
    • Data Debt: หนี้ที่มี data มหาศาลทับถมอยู่บนหนี้ประเภทอื่น ทำให้การแก้ไขเสี่ยงและใช้เวลามาก

1 ความคิดเห็น

 
GN⁺ 2024-10-01
ความคิดเห็นจาก Hacker News
  • ความแพร่กระจายเป็นเหตุผลในตัวเองว่า อินเทอร์เฟซ คือหนึ่งในองค์ประกอบที่สำคัญที่สุดของการออกแบบ และควรคิดไตร่ตรองให้มากพอ
    ถ้ามีอินเทอร์เฟซที่สวยงามแต่มีอิมพลีเมนเทชันที่ยังไม่เหมาะสมพ่วงอยู่ พอมีเวลาก็จัดระเบียบใหม่ได้ง่าย แต่ในทางกลับกันแทบจะไม่เป็นจริง

    • เห็นด้วย แต่การออกแบบให้ดีมักขาดอย่างใดอย่างหนึ่งในสองอย่าง: เวลาในการออกแบบ และความรู้ว่า วันนี้กับอีก 1 ปีข้างหน้าต้องทำอะไรกันแน่
      บางครั้งก็ไม่มีทั้งสองอย่าง
      ในกรณีแบบนี้ การบังคับให้ใช้โมดูลเล็ก ๆ กับความรับผิดชอบเดียวแบบประกอบกันได้ จะช่วยกันไม่ให้การแพร่กระจายรุนแรงเกินไปได้ ไม่ต้องใช้ความรู้เรื่องอนาคตหรือเวลามาก แค่หลีกเลี่ยง อินเทอร์เฟซที่มีพื้นที่ผิวกว้าง แบบตุ๊กตาแม่ลูกดกที่ใช้พารามิเตอร์ควบคุมพฤติกรรมหลายรูปแบบ ควรย้ายการตั้งค่า การพาร์ส และการตัดสินใจด้านพฤติกรรมไปไว้ที่ขอบของลอจิก และไม่ปล่อยให้มันซึมลงไปทั่วทั้งโมเดลชั้นล่าง
    • เห็นด้วย แต่การออกแบบอินเทอร์เฟซที่แข็งแรงและทนต่ออนาคตเป็นหนึ่งในปัญหาที่ยากที่สุดของการพัฒนาซอฟต์แวร์มาโดยตลอด
      ต่อให้เริ่มด้วยความตั้งใจว่าจะหลีกเลี่ยงหนี้ทางเทคนิคให้ได้ไม่ว่าจะด้วยวิธีใด ก็ยังทำให้ถูกต้องได้ยาก และต้องอาศัยมากกว่าความมั่นใจทางเทคนิคหรือวิสัยทัศน์ด้านสถาปัตยกรรมธรรมดา ๆ ในความเป็นจริงมันเข้าไปอยู่ในขอบเขตของ การทำนายอนาคต
    • ถ้าจะเผยอินเทอร์เฟซที่ดีออกมา ต้องเข้าใจว่าการอิมพลีเมนต์ภายในที่ดีควรมีหน้าตาอย่างไร
      ถ้าเผลออบอิมพลีเมนต์โง่ ๆ เข้าไปในอินเทอร์เฟซโดยปริยาย มักจะกลายเป็นว่าสลับแค่อิมพลีเมนต์ก็แก้ไม่ได้ ตัวอย่างที่นึกออกคือพฤติกรรมของ การจัดเรียงและการแบ่งหน้า นักพัฒนารุ่นจูเนียร์ และตอนนี้แม้แต่ซีเนียร์จำนวนมากที่ควรจะรู้ดีกว่านี้ ก็มักเริ่มจากรีเควสต์ที่ใช้พารามิเตอร์จำพวก limit/offset ซึ่งนำไปสู่ปัญหาประสิทธิภาพที่เลวร้ายและพฤติกรรมแปลก ๆ วิธีที่ pagination ทำงานได้อย่างมีประสิทธิภาพ และตัวเลือกการจัดเรียงที่รองรับได้ด้วยประสิทธิภาพดีนั้นผูกติดโดยธรรมชาติกับรูปแบบข้อมูลและการเลือกที่จัดเก็บข้อมูล คนที่ไม่เคยผ่านกระบวนการนี้ในเลเยอร์ล่าง ๆ มาก่อน มีโอกาสน้อยที่จะสร้างอินเทอร์เฟซระดับบนได้ถูกต้อง เว้นแต่จะขุดลงไปในอิมพลีเมนต์ให้มากพอก่อน
    • เพราะแบบนั้นจึงชอบภาษาที่ทำให้ อินเทอร์เฟซชัดเจนมาก อย่าง OCaml หรือ Ada
      ส่วนใหญ่แล้วไม่อยากดูอิมพลีเมนต์ แต่อยากดูแค่อินเทอร์เฟซที่มีเอกสารประกอบดี ๆ ถ้าอธิบายพฤติกรรมของอินเทอร์เฟซเป็นคำง่าย ๆ ไม่ได้ แสดงว่ามีบางอย่างผิดปกติ
    • ในประวัติศาสตร์ดูเหมือนจะมีตัวอย่างโต้แย้งอยู่ไม่น้อย
      QWERTY ขึ้นชื่อว่าไม่ใช่อินเทอร์เฟซทางกายภาพที่เหมาะที่สุด และพวงมาลัยรถยนต์ก็อาจเป็นกรณีคล้ายกัน ในฝั่งคอมพิวเตอร์ x86 เป็นตัวอย่างคลาสสิกของอินเทอร์เฟซที่บนผิวหน้าแล้วไม่เหมาะที่สุด
  • ค่อนข้างน่าประหลาดใจที่บทความนี้เขียนโดย ผู้จัดการฝ่ายวิศวกรรม
    ในบรรดาผู้จัดการที่เคยทำงานด้วย ไม่มีใครคุยเรื่องโค้ดเบสของเราในระดับรายละเอียดทางเทคนิคได้ขนาดนี้ แม้แต่คนที่เคยเป็นวิศวกรมาก่อนก็เช่นกัน
    แต่ถ้าจะพูดให้ยุติธรรม เราไม่เคยมีผู้จัดการที่เลื่อนตำแหน่งจากภายใน และคนข้างในรวมถึงผมเองก็ไม่อยากเลิกทำวิศวกรรม จึงมีนิสัยเสียในการจ้างผู้จัดการจากภายนอก

  • ดูเหมือนจะขาดประเภทหนี้ที่พบได้บ่อยที่สุดที่ผมเคยเห็น: หนี้ผู้ก่อตั้ง
    เป็นหนี้ที่ผู้ก่อตั้งสร้างขึ้นเพื่อปล่อยเทคโนโลยีที่เร็วและมีคุณค่าออกมา สิ่งที่เคยดูเหมือนผลไม้ที่เก็บง่าย กลายเป็นรากฐานของทั้งระบบไปเสียได้
    เอกสารก่อตั้งประเทศของหลายประเทศก็เข้าข่ายนี้ lol (แต่ไม่ใช่ USA! USA! USA!)
    หนี้แบบแม็คไกเวอร์กับหนี้ฐานรากใกล้เคียงที่สุด แต่ทั้งสองแบบก็ยังไม่ชี้ปรากฏการณ์นี้ได้ตรงนัก

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

  • ตอนนั้นก็มีการถกกันแล้ว:
    A Taxonomy of Technical Debt - https://news.ycombinator.com/item?id=16810092 - เมษายน 2018 (113 ความคิดเห็น)
    และยังมีอันนี้ด้วย:
    A Taxonomy of Tech Debt (2018) - https://news.ycombinator.com/item?id=39782923 - มีนาคม 2024 (1 ความคิดเห็น)

  • คำอธิบายที่ว่า “นิยามหนี้ทางเทคนิคว่าเป็นโค้ดหรือข้อมูลที่นักพัฒนาในอนาคตจะต้องจ่ายต้นทุน” ใกล้เคียงกับคำอธิบายที่ดีที่สุดเท่าที่ผมเคยเห็น
    เช่นเดียวกับหนี้ทุกชนิด เมื่อตัดสินใจก่อหนี้ควรใช้ เกณฑ์ตัดสิน ที่ถ่วงดุลความจำเป็นเฉพาะหน้ากับต้นทุนในอนาคต ผมรู้สึกว่าคนส่วนใหญ่ ไม่ใช่แค่นักพัฒนา มักประเมินความจำเป็นเฉพาะหน้าสูงเกินไปและประเมินต้นทุนในอนาคตต่ำเกินไป
    โดยส่วนตัวแล้วผมเกลียดหนี้แทบทุกชนิดอย่างเกือบจะเป็นโรค บางครั้งใช้เวลาเพิ่มอีกวันเพื่อแยกสิ่งที่อาจมีประโยชน์ในอนาคตออกมา เวลาทำถูกก็ราว 50% แต่ทุกครั้งที่ทำแบบนั้น นิสัยนี้จะแข็งแรงขึ้น และเวิร์กโฟลว์พื้นฐานก็เร็วขึ้นด้วย

  • จนถึงตอนนี้เคยทำงานใน “สตาร์ทอัพ” มา 3 แห่ง โดยเข้าร่วมหลังจากที่ทุกแห่งมีรายได้พอจะจ่ายเงินเดือนใกล้เคียงปกติได้แล้ว
    สิ่งที่เห็นบ่อยที่สุดคือผู้ก่อตั้งหลายคนเข้าใจแบบปะปนพร่ามัวระหว่าง ไอเดียที่ตัวเองนึกออก สิ่งที่ถูกสร้างขึ้นจริง และส่วนที่อิมพลีเมนต์แล้วใช้งานได้จริง

  • ตั้งแต่อ่านบทความนี้ครั้งแรก ผมก็ใช้คำว่า ความแพร่กระจาย เวลาอธิบายหนี้ทางเทคนิค และมันเข้ากันได้ค่อนข้างดี

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

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

    • เหมือนหนี้จริงทุกประการ
      ถ้าอยากสร้างอาคารใหม่ตอนนี้เพื่อให้งานเสร็จ โดยไม่รอจนมีทุนในอีก 15 ปีข้างหน้า? ก็กู้เงินได้
      หนี้เป็นเครื่องมือ แต่เป็นเครื่องมือที่ทรงพลังและอันตราย ถ้าไม่ยอมรับและเคารพว่ากำลังใช้มันอยู่ คุณจะเจ็บตัว หรือไม่ก็คนที่รับระเบิดมือจากคุณต่อไปจะเจ็บตัว เหมือนหนี้จริง ๆ
    • เคยได้ยินว่ามีคนเรียกสิ่งนี้ว่า หนี้เชิงยุทธวิธี แทน “หนี้ทางเทคนิค”
    • ปกติแล้วก็เพราะความเร็วนั่นแหละ