ความสำเร็จและความล้มเหลวของ Ninja (2020)
(neugierig.org)- Ninja ที่เปิดตัวในปี 2011 เริ่มต้นจากระบบบิลด์ที่คล้ายกับ Make และกลายเป็นกรณีตัวอย่างความสำเร็จของโอเพนซอร์สที่แพร่หลายไปถึง Chrome, บางส่วนของ Android, Meson และโปรเจกต์ที่ใช้ CMake
- แกนที่ตัดสินความสำเร็จหรือความล้มเหลวไม่ใช่ตัวโค้ดเองเท่านั้น แต่คือ สถาปัตยกรรม และยิ่งไปกว่านั้นคือ ปัญหาทางสังคม เช่นการบำรุงรักษาและความคาดหวังของผู้ใช้
- Ninja อ่านคำสั่ง อินพุต และเอาต์พุตที่ระบุไว้ใน
ninja.buildตรวจสอบเวลาแก้ไขไฟล์ แล้วรันงานที่จำเป็นแบบขนาน โดยมุ่งทำให้ incremental build ของโปรเจกต์ขนาดใหญ่เร็วขึ้น - การตัดสินใจด้านการออกแบบที่ใหญ่ที่สุดคือไม่รวมฟีเจอร์บิลด์ระดับสูงไว้ในตัวเอง แต่คงบทบาทไว้ที่ตัวรัน action graph และแยกให้ตัวสร้าง (generator) รับผิดชอบการตัดสินใจที่ซับซ้อน
- การรวมเข้ากับ CMake, การรองรับ Windows และค่าเริ่มต้นของการรันแบบขนานช่วยให้แพร่หลายขึ้น แต่คำขอและ contribution ที่ขัดกับเป้าหมายการออกแบบก็ยังเป็นภาระใหญ่ของการดูแลโอเพนซอร์ส
บริบทที่ทำให้ Ninja ถูกใช้อย่างแพร่หลาย
- Ninja เป็นระบบบิลด์ที่คล้าย Make ซึ่งเปิดตัวเมื่อราว 9 ปีก่อน ตอนแรกเป็นเพียง side project ที่แชร์ออกไปแบบค่อนข้างเขิน ๆ แต่ต่อมากลับถูกใช้อย่างกว้างขวาง
- กรณีการใช้งานหลักมีดังนี้
- Chrome ลบ การบิลด์ที่ไม่ใช่ Ninja ออกไปในที่สุด
- Android ใช้ Ninja กับบางองค์ประกอบขนาดใหญ่ของระบบ
- โปรเจกต์ Meson ใช้ Ninja และดูเหมือนจะเป็นระบบบิลด์ที่ถูกใช้มากขึ้นเรื่อย ๆ ในโลกซอฟต์แวร์เสรี
- มีหลายโปรเจกต์ที่ใช้ Ninja ร่วมกับ CMake และคู่มือการบิลด์ของ Swift ก็ระบุว่าต้องติดตั้ง Ninja
- Ninja ออกรุ่นในปี 2011, มีการส่งต่อความเป็นเจ้าของโปรเจกต์ในปี 2014 และต่อมาถูกส่งต่อไปยังเมนเทนเนอร์หลักคนที่สาม
- ประสบการณ์สำคัญที่เห็นจากการย้อนมองครั้งนี้คือ สิ่งที่มีผลมากกว่าโค้ดคือ สถาปัตยกรรม และสิ่งที่มีผลมากกว่าสถาปัตยกรรมคือ ประเด็นทางสังคม
การทำงานพื้นฐานเพื่อให้บิลด์เร็ว
- สิ่งที่ Ninja ทำค่อนข้างเรียบง่าย
- ผู้ใช้ระบุคำสั่งที่จะรัน ไฟล์ที่แต่ละคำสั่งใช้ และไฟล์ที่สร้าง ในไฟล์
ninja.build - Ninja อ่านไฟล์นี้และตรวจสอบ เวลาแก้ไข ของไฟล์ต่าง ๆ
- จากนั้นรันคำสั่งที่จำเป็นเพื่อให้ได้สถานะล่าสุดแบบขนาน
- ผู้ใช้ระบุคำสั่งที่จะรัน ไฟล์ที่แต่ละคำสั่งใช้ และไฟล์ที่สร้าง ในไฟล์
- เมื่อเทียบกับ Make ความสามารถของภาษาที่ใช้เขียนอินพุตมีน้อยกว่า และเน้นโครงสร้างที่ทำงานฟีเจอร์จำนวนน้อยเหล่านั้นได้เร็วมาก
- ลำดับการทำงานแบ่งเป็น 3 ขั้น
- parse และตีความไฟล์บิลด์
- ตรวจสอบเวลาแก้ไขของไฟล์อินพุต
- รันคำสั่งที่จำเป็น
- เป้าหมายคือไปถึงขั้นที่สามให้เร็วที่สุดเท่าที่ทำได้ แม้ในโปรเจกต์ขนาดใหญ่ที่มีไฟล์อินพุตมากกว่า 100,000 ไฟล์
- ตัวอย่างการเพิ่มประสิทธิภาพคือ Ninja จะ map พาธของไฟล์อินพุตให้เป็นอ็อบเจ็กต์หน่วยความจำที่ไม่ซ้ำกันให้เร็วที่สุดเท่าที่เป็นไปได้ และหลังจากนั้นใช้การเปรียบเทียบ pointer สำหรับตรวจสอบความเหมือนของพาธ
- รายละเอียดด้านประสิทธิภาพในระดับที่ลึกกว่านี้กล่าวถึงไว้ใน บทของ Ninja ใน The Performance of Open Source Software
- ตลอดหลายปีที่ผ่านมา มีการ reimplement Ninja หลายตัวด้วย
- การทำส่วนที่สนุก 20% แรกนั้นง่าย แต่ 80% ที่เหลือเต็มไปด้วยรายละเอียด และยังไม่มี implementation ที่เร็วกว่าเป็นที่รู้จัก
ทางเลือกด้านการออกแบบที่สำคัญ
-
การแทนกราฟ
- Make จัดการกรณีที่กฎบิลด์หนึ่งข้อสร้างหลายไฟล์ได้ไม่ดีนัก
- Ninja ใช้ กราฟสองฝั่งระหว่างไฟล์กับคำสั่ง แทนที่จะเป็นกราฟระหว่างไฟล์อย่างเดียว
- โหนดไฟล์จะเป็นเส้นเชื่อมเข้าไปยังโหนดคำสั่ง และโหนดคำสั่งจะมีเส้นเชื่อมออกไปยังไฟล์เอาต์พุต
- โครงสร้างนี้จับรูปแบบที่แท้จริงของบิลด์ได้ดีกว่า
- ถ้าอินพุตตัวใดตัวหนึ่งเปลี่ยน คำสั่งนั้นจะล้าสมัย และเมื่อรันคำสั่งแล้ว เอาต์พุตทั้งหมดจะถูกอัปเดต
- ไฟล์หนึ่งไฟล์สามารถมีเส้นเชื่อมขาเข้าได้อย่างมากหนึ่งเส้น
- ตัว command line เองก็อาจมองได้ว่าเป็นอินพุตของโหนดคำสั่ง ดังนั้นเมื่อแฟล็กบน command line เปลี่ยน คำสั่งและเอาต์พุตก็จะกลายเป็นสถานะล้าสมัย
-
deps log และ dependency ของ C header
- หากต้องการจัดการ dependency ของ C header ให้ถูกต้อง จำเป็นต้องใช้ข้อมูล dependency เพิ่มเติมที่ C compiler สร้างขึ้น
- ระหว่างการเลือกว่าใช้ฐานข้อมูลหรือรักษาความเรียบง่าย ผู้เขียนได้สร้าง ฟอร์แมตการแทนค่า deps log ขึ้น
- ฟอร์แมตนี้ค่อนข้างบีบอัดได้ดี แต่ก็ยังผิดอยู่ในบางแง่มุมที่สำคัญ
-
การออกแบบแบบ end-to-end / crash-only
- Ninja ไม่ใช่ daemon process ที่รันค้างไว้ตลอด แต่จะเริ่มทำงานใหม่จากต้นทุกครั้งที่รัน
- ทางเลือกนี้เป็นโครงสร้างที่ได้รับอิทธิพลจาก end-to-end principle และ crash-only software
- ถ้าทำให้เส้นทางการเริ่มใหม่จากต้นเร็วพอ ก็ไม่จำเป็นต้องสร้างเส้นทางโค้ด “ออนไลน์” แยกต่างหาก
- โปรเจกต์ที่สามารถอยู่ค้างในหน่วยความจำได้มักลงเอยด้วยการปล่อยให้ประสิทธิภาพตอนเริ่มต้นถูกละเลย
-
การตรวจสอบสถานะไฟล์
- เหตุผลหนึ่งที่คนคาดหวังว่าเครื่องมือบิลด์ควรอยู่ค้างในหน่วยความจำ คือเพื่อ cache สถานะของไฟล์บนดิสก์
- ในความเป็นจริง kernel cache ข้อมูลนี้ไว้ในหน่วยความจำอยู่แล้ว และการ cache ซ้ำใน userspace ก็ไม่ได้ช่วยประหยัดมากนัก
- บน Linux การดึงสถานะไฟล์นั้นเร็วมาก และ Ninja ทำงานนี้บน single thread
- แม้บนเครื่องที่ถือว่า “เร็ว” ตามมาตรฐานเมื่อ 10 ปีก่อน ก็ยังทำ
statกับไฟล์ 30,000 ไฟล์ได้ภายในเวลาเพียงไม่กี่สิบมิลลิวินาที
-
จุดประนีประนอมระหว่างสเกลกับสเปก
- กฎแบบคร่าว ๆ คือการขยายขนาด 2 เท่ามักแก้ได้ด้วย optimization แต่ถ้าขยาย 10 เท่าจะต้อง re-architect
- Ninja ถูกออกแบบโดยมี Chrome build ที่มีขั้นตอนบิลด์ราว 30,000 ขั้นเป็นศูนย์กลาง
- ตอนนี้มันถูกใช้ในสภาพแวดล้อมที่เล็กกว่านั้นด้วย แต่ในกรณีเช่นนั้นข้อได้เปรียบด้านความเร็วอาจไม่จำเป็น
- ในสภาพแวดล้อมที่ใหญ่กว่า เช่น Android build กำลังเริ่มชนข้อจำกัดด้านสเกล และอาจต้องการแนวทางอื่น
- เพื่อให้รันแบบขนานได้ Ninja ต้องการให้ผู้ใช้ให้ข้อมูลมาเพียงพอ แต่ไม่ได้บังคับว่าต้องรู้ทุกอย่างเกี่ยวกับทั้งบิลด์
- มีจุดประนีประนอมระหว่างความถูกต้อง ความสะดวก และประสิทธิภาพ และในระบบนิเวศโดยรวม เครื่องมือที่ยอมลดความถูกต้องลงบางส่วนเพื่อความสะดวก อาจให้ผลลัพธ์ที่ถูกต้องกว่าเครื่องมือที่ถูกต้องกว่าแต่ใช้งานลำบาก
อุปมาแบบ “assembler” ที่สร้างการแยกชั้น
- ระบบบิลด์มีฟีเจอร์ระดับสูงหลากหลาย และแต่ละเครื่องมือก็อธิบายตัวเองกว้างขวางจนแทบเทียบกันตรง ๆ ได้ยาก
- แกนความคิดสำคัญของ Ninja คือ ไม่ว่าระบบบิลด์จะมีฟีเจอร์ระดับสูงอะไร สุดท้ายแล้วมันต้องสร้าง action graph เพื่อทำให้ไฟล์อยู่ในสถานะล่าสุด
- Ninja ทำหน้าที่รัน action graph นี้เท่านั้น และปล่อยให้ผู้ใช้เลือกเองว่าจะวาง generator แบบใดไว้ด้านบน
- การแยกระหว่างสองโปรแกรมนี้ เดิมทีออกแบบมาให้เหมาะกับโปรเจกต์ Chrome แต่ต่อมากลายเป็น contribution ที่สำคัญที่สุดของ Ninja
- ข้อดีมีสองอย่าง
- ตัว Ninja เองยังคงเรียบง่ายและเร็ว
- งานที่มีต้นทุนสูงอย่างการ glob
*.cถูกผลักไปไว้ฝั่ง generator
- ต่างจากระบบบิลด์อื่นที่จัดการทุกอย่างในคราวเดียว โครงสร้างของ Ninja ทำให้ action graph ที่คำนวณแล้วถูกบันทึกเป็น snapshot ลงดิสก์
- ผลลัพธ์คือมันกลายเป็นการ cache action graph ระหว่างรอบบิลด์
- generator สามารถเป็นระดับสูงได้เท่าที่ต้องการ
- ตัวอย่างเช่น อาจ glob ทั้ง source tree เพื่อหาไฟล์ที่มีคำว่า
testอยู่ในชื่อแล้วถือว่าเป็น test ก็ได้
- ตัวอย่างเช่น อาจ glob ทั้ง source tree เพื่อหาไฟล์ที่มีคำว่า
- นักพัฒนาต้องตัดสินใจเองว่าต้องการจ่ายต้นทุนให้กับอะไร
- ถ้า generator glob ทั้งดิสก์ก็ทำได้ แต่ก็จะเห็นชัดขึ้นด้วยว่าทำไมบิลด์นั้นถึงช้า
- การแยกระหว่าง generator กับ action graph ไม่ได้เรียบง่ายนักในทางปฏิบัติ และใน Ninja เองก็มีรายละเอียดมากมายว่าชั้นไหนควรทำอะไร
- ในทางทฤษฎี ระบบบิลด์ของ Xcode หรือ Visual Studio ก็สามารถคำนวณล่วงหน้าแล้ว snapshot ผลลัพธ์แบบเดียวกันได้ แต่ผู้เขียนมองว่ามักทำไม่สำเร็จเพราะแรงดึงดูดให้เอาชั้นต่าง ๆ มาปนกัน
- Make พยายามรวมฟีเจอร์สำหรับโปรแกรมเมอร์ไว้ทั้งหมด ทั้ง globbing, การขยายตัวแปร, substring และฟังก์ชันต่าง ๆ จึงกลายเป็นภาษาที่อ่อนเกินกว่าจะอธิบายทุกสิ่งที่ต้องการได้ แต่ก็แรงพอจะทำให้คนเขียน Makefile ที่ช้าได้
- Ninja พยายามหลีกเลี่ยงทิศทางนั้น
ค่าเริ่มต้นเป็นตัวกำหนดประสิทธิภาพที่ผู้ใช้รู้สึก
- โดยปกติ Ninja จะ รันคำสั่งแบบขนาน
- Make ก็รันแบบขนานได้ผ่านแฟล็ก
-jแต่ค่าเริ่มต้นคือรันแบบลำดับเดียว - Makefile มักเขียนโดยระบุ dependency ไม่ครบ ทำให้ไม่ปลอดภัยเมื่อรันแบบขนาน
- Ninja รันแบบขนานเสมอแม้บนระบบ single-core จึงทำให้ข้อผิดพลาดแบบนี้ถูกเปิดเผยได้เร็วกว่า
- โปรแกรมที่เหมาะกับ Ninja มักจะปลอดภัยต่อการบิลด์แบบขนานด้วย
- Ninja ไม่ได้มีระบบขั้นสูงสำหรับตรวจจับ dependency ที่ผิดพลาด แต่เป็นโครงสร้างที่ทำให้บิลด์ผิดเกิดบ่อยขึ้นจนปัญหาถูกมองเห็น
- เนื่องจากผู้ใช้มักลืมหรือไม่รู้จักแฟล็กรันแบบขนานของ Make เพียงแค่ค่าเริ่มต้นนี้อย่างเดียวก็อาจทำให้ Ninja “รู้สึกว่าเร็วกว่าเกินสองเท่า” ในการใช้งานจริงเมื่อเทียบกับ Make
- optimization ที่ผู้ใช้ไม่เคยได้สัมผัสจริง แทบไม่มีความหมาย
ตัวชี้วัดที่สำคัญต่อความเร็ว
- ประสิทธิภาพของระบบบิลด์มีความหมายได้หลายแบบ
- บิลด์เต็มตั้งแต่ต้นใช้เวลานานแค่ไหน
- หลังจากบิลด์ไปแล้ว เมื่อแก้ไฟล์หนึ่งไฟล์และบิลด์ใหม่ จะใช้เวลานานแค่ไหน
- Ninja มุ่งเน้นที่การเร่ง edit-compile loop ใน incremental build ของ codebase ขนาดใหญ่
- ตอนเริ่มสร้าง Ninja ผู้เขียนพยายามไล่ตามความเร็วของ blaze หรือ bazel เพราะจำได้ว่ามันเร็วมาก
- แต่เมื่อมองย้อนกลับไป blaze ไม่ได้เร็วเป็นพิเศษในตัวชี้วัดที่ตัวเองสนใจ และเพราะเป็นโปรแกรม Java แม้แต่การแสดงผล help ก็ยังช้า
- เหตุผลที่ยึดติดกับ incremental build เพราะเชื่อว่าเวลารอในแต่ละรอบมีผลมากต่อความพึงพอใจของโปรแกรมเมอร์
- Ninja ถูกใช้ใน edit-compile loop ซึ่งความต่างระหว่าง 1 วินาทีกับ 4 วินาทีนั้นสำคัญ
- ความหมายของคำว่า “เร็ว” สื่อสารกับผู้ใช้ได้ยาก และคู่มือ Ninja ก็เตือนว่าในโปรเจกต์เล็ก ๆ อาจแทบไม่รู้สึกถึงผลด้านความเร็ว
- แต่คำว่า “เร็ว” เป็นคำที่ขายได้ดี จึงทำให้ผู้ใช้แอปขนาดเล็กที่หงุดหงิดกับการขาดฟีเจอร์ก็ยังพยายามใช้ Ninja
- แม้ Ninja จะเน้น incremental rebuild แต่ผู้ใช้บางรายก็รายงานว่าประสิทธิภาพของ full build ดีขึ้นด้วย
- เพราะ Ninja แทบไม่ทำอะไรเลย จึงใช้ CPU ระหว่างบิลด์น้อยและแย่ง CPU จากงานบิลด์จริงน้อยลง
- เอาต์พุตก็สั้นมาก
- บิลด์ที่สำเร็จมักพิมพ์เพียงบรรทัดเดียว
- ระบบบิลด์อื่นอาจพิมพ์หลายขั้นตอนและข้อมูลเวลาเป็นจำนวนมากจนให้ความรู้สึกหนัก
- โครงสร้างที่พูดน้อยนี้ทำให้ Ninja ดู “เหมือนไม่มีตัวตน” มากขึ้น
การรวมกับ CMake และการรองรับ Windows ที่ช่วยขยายการใช้งาน
-
CMake
- เดิมที Ninja ถูกสร้างมาเพื่อใช้กับระบบบิลด์เฉพาะของ Chrome
- Peter Collingbourne เป็นผู้ทำงานเชื่อม Ninja เข้ากับระบบบิลด์ CMake ที่ถูกใช้อย่างแพร่หลายกว่า
- การรวมนี้เริ่มจากงานของ LLVM และต้องเพิ่ม semantics ใหม่ทั้งฝั่ง CMake และฝั่ง Ninja
- Peter ถูกยกให้เป็นคนที่มีผลงานสำคัญที่สุดต่อความสำเร็จของ Ninja ในโลกจริง
- หลังจากนั้นผู้เขียน CMake เข้ามารับช่วงการรวมนี้ต่อ แต่ยังไม่สามารถตอบสนองต่อคำขอและข้อกังวลได้เพียงพอ
- ผู้เขียนไม่เคยใช้ CMake โดยตรงเลยจนถึงตอนนี้
-
Windows
- เพราะ Chrome รองรับ Windows ด้วย Ninja จึงต้องทำงานบน Windows ได้เช่นกัน
- ส่วนใหญ่ของการรองรับ Windows ถูกเขียนโดยผู้ร่วมพัฒนา
- ในเชิงเทคนิค การรองรับ Windows เป็นความยุ่งยากใหญ่พอสมควร
- การรัน process และการจับเอาต์พุตมีความต่างระดับที่ต้องไปเรียนรู้ API ของแต่ละแพลตฟอร์มใหม่
- การออกแบบของ Ninja อาศัยสมมติฐานว่าสามารถดึงเวลาแก้ไขล่าสุดของไฟล์ที่ kernel cache ไว้ได้อย่างรวดเร็ว แต่บน Windows สมมติฐานนี้ไม่เป็นจริง
- ถึงอย่างนั้น Windows ก็เป็นแพลตฟอร์มที่มีฐานนักพัฒนาขนาดใหญ่
- เครื่องมือดี ๆ สำหรับ Linux มักเกิดจากแรงผลักให้แบ่งปัน แต่เครื่องมือสำหรับ Windows มักเกิดจากแรงผลักให้ขาย ทำให้มีเครื่องมือที่เปิดให้อิสระค่อนข้างน้อยกว่า
- การที่ผู้ใช้ช่วงแรกของ Ninja จำนวนมากเป็นผู้ใช้ Windows เคยเป็นเรื่องน่าประหลาดใจ แต่เมื่อคิดถึงจำนวนนักพัฒนาบน Windows ที่มากอยู่แล้ว แค่มีบางส่วนสนใจก็กลายเป็นผู้ใช้จำนวนมากได้
การทำความเข้าใจงานที่เกี่ยวข้อง
- Ninja เริ่มจากการแฮ็กเดโมเล่นช่วงสุดสัปดาห์ จึงน่าเสียดายที่ก่อนสร้างไม่ได้สำรวจงานที่เกี่ยวข้องให้เพียงพอ
- เวลาสร้างอะไรขึ้นมา การเข้าใจ design space อย่างแท้จริงเป็นเรื่องสำคัญ
- คำว่า “action graph” ไม่ใช่คำที่ใช้ตอนสร้าง Ninja แต่ยืมมาจากระบบบิลด์ blaze/bazel ของ Google
- bazel อธิบายว่ากราฟของ target ระดับสูงอย่าง “library” หรือ “binary” จะสร้าง action graph ซึ่งเป็นกราฟของคำสั่ง
- แนวคิดที่มองข้อความ command line เป็นอินพุตเช่นเดียวกับไฟล์ เป็นตัวอย่างหนึ่งของ incremental computation
- incremental computation ไม่ได้เชื่อมโยงกับระบบบิลด์เท่านั้น แต่ยังเกี่ยวข้องกับความเป็น incremental ของ UI ด้วย
- บนบล็อกของ Jane Street มี บทความแนะนำ Incremental ซึ่งเชื่อมโยงกับแนวทางสร้าง UI แบบ React ได้เช่นกัน
- "Build Systems à la Carte" เป็นงานวิจัยที่พูดถึง incremental computation ในบริบทของระบบบิลด์ และถูกยกให้เป็นเอกสารที่น่าเสียดายที่ไม่มีตั้งแต่ก่อนเขียน Ninja
ภาระของการดูแลโอเพนซอร์ส
- ประสบการณ์การเป็นเมนเทนเนอร์โอเพนซอร์สไม่ได้สนุกเป็นพิเศษ
- ความรู้สึกต่อโปรเจกต์นี้ปะปนกันระหว่างความภูมิใจเวลาใครพูดสิ่งดี ๆ กับความผิดหวังที่มากกว่า
- การเปิดให้ใช้ฟรีทำให้บางครั้งมีคนมาขออย่างสุภาพ แต่บ่อยกว่านั้นคือผู้ใช้ที่โกรธและเข้ามาเรียกร้อง และคำขอบคุณนั้นหาได้ยาก
- ยังมีคนที่ขู่ว่าจะ fork ถ้าไม่รับข้อเรียกร้องของพวกเขาอยู่ซ้ำ ๆ
- contribution ที่สมเหตุสมผลแต่ขัดกับเป้าหมายการออกแบบก็เป็นอีกปัญหาหนึ่ง
- เมื่อต้องปฏิเสธ contribution จากคนที่ทั้งฉลาดและมีน้ำใจ ผู้เขียนก็อยากตอบแทนด้วยคำอธิบายที่เพียงพอ แต่การอธิบายนั้นเองก็เป็นเรื่องที่บั่นทอนพลัง
- ผู้เขียนเริ่มต้นเขียนโค้ดเพราะเข้าสู่วงการโปรแกรมมิงผ่านซอฟต์แวร์เสรีและอยากตอบแทนสิ่งที่ได้รับ แต่ซอฟต์แวร์เสรีในปัจจุบันดูจะเปลี่ยนจากการแบ่งปันกันในหมู่คนเท่าเทียม ไปเป็นสภาพที่ผู้ใช้มองตัวเองเหมือนลูกค้าและมองผู้เขียนเป็นคนที่สามารถไปร้องเรียนกับผู้จัดการได้
- แรงจูงใจในตอนนี้จึงใกล้เคียงกับการสร้างความประทับใจให้แฮ็กเกอร์จำนวนน้อยที่ตนเคารพ หรือทำให้สมกับความคาดหวังของพวกเขา มากกว่าการประสบความสำเร็จในวงกว้าง
- ความสำเร็จของ Ninja ทำให้ได้เรียนรู้อะไรมากมาย แต่ผู้เขียนก็มองว่าแม้จะประสบความสำเร็จเล็กกว่านี้ ก็อาจได้บทเรียนคล้ายกัน
เมนเทนเนอร์และผู้ร่วมพัฒนา
- Nico Weber เป็นผู้ร่วมงานที่รอบคอบ และทำหน้าที่เป็นเมนเทนเนอร์ของ Ninja อยู่หลายปี
- Jan Niklas Hasse รับช่วงโปรเจกต์ต่อจาก Nico และดูเหมือนว่าจะทำได้ดี
- นอกจากนี้ยังมี ผู้ร่วมพัฒนา Ninja อีกมากที่เข้าร่วมโปรเจกต์
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
ประโยคที่ว่า “การเขียนโปรแกรมมักถูกพูดถึงเหมือนเป็นการเขียนโค้ด แต่จริง ๆ แล้ว สถาปัตยกรรม สำคัญกว่าโค้ด และ ประเด็นทางสังคม ก็สำคัญกว่าสถาปัตยกรรมขึ้นไปอีก” ถ่ายทอดความคิดที่อยู่ในใจมานานได้อย่างตรงเป๊ะ
บทที่ 1 ของหนังสือเสนอว่า “ปัญหาหลักของงานเราไม่ใช่เรื่องเทคนิคเท่าไร แต่เป็นเรื่องเชิงสังคมวิทยามากกว่า” และพูดถึงประเด็นทางสังคม/การเมือง เช่น เคมีและความผูกพันของทีม, “เวลาโฟกัส”, สภาพแวดล้อมการทำงานที่เงียบ และต้นทุนการลาออก
[1] https://en.wikipedia.org/wiki/Peopleware:_Productive_Project...
ดังนั้นจึงมองว่าวิศวกรรมซอฟต์แวร์ใกล้เคียงกับ สังคมศาสตร์ มากกว่าที่หลายคนยอมรับหรือรู้สึกสบายใจ สังคมศาสตร์มีความไหลลื่นและคาดเดาได้น้อยกว่าวิทยาศาสตร์ธรรมชาติ จึงดูเหมือนมีแนวโน้มที่จะหลีกเลี่ยงส่วนที่เป็นสังคม หรือจัดการกับมันแบบดิบ ๆ มาก ในขณะที่หมกมุ่นกับรายละเอียดเชิงอะตอมของเทคโนโลยี แต่ผลิตภัณฑ์ที่ทีมสร้างออกมากลับแย่มากบ่อยเกินไป
“องค์กรที่ออกแบบระบบย่อมถูกบังคับให้สร้างแบบออกแบบที่จำลองโครงสร้างการสื่อสารขององค์กรนั้น” — Melvin E. Conway, How Do Committees Invent?
ตอนนั้นก็มีการถกเถียงกันแล้ว:
The Success and Failure of Ninja - https://news.ycombinator.com/item?id=23157783 - พฤษภาคม 2020, 38 ความคิดเห็น
ผ่านไปประมาณ 1 ปี การโพสต์ซ้ำก็ถือว่าโอเค และลิงก์เธรดเก่าไว้สำหรับผู้อ่านที่อยากอ่านเพิ่ม
ส่วนที่บอกว่า “Android ใช้ Ninja กับคอมโพเนนต์ใหญ่บางอย่างของระบบ แต่ไม่เคยเข้าใจแน่ชัดว่าเป็นอะไร” นั้นตลกดี
จริง ๆ แล้ว Ninja มีบทบาทใหญ่มากใน AOSP ระบบบิลด์ช่วงแรกใช้ Makefile แต่ซับซ้อนขึ้นอย่างรวดเร็วเพราะ soong ซึ่งเป็นระบบบิลด์แบบประกาศที่ทำเอง และการย้ายไป Bazel ที่ล้มเหลว/หยุดชะงัก Google สร้าง kati(https://github.com/google/kati) เพื่อแปลง Makefile เป็นไฟล์บิลด์ของ Ninja และผลลัพธ์ก็ใหญ่มหึมาจริง ๆ:
λ wc -l out/build-qssi.ninja3035442 out/build-qssi.ninjaกระบวนการเปลี่ยนจาก Makefile/soong ไปเป็น Ninja นั้นเจ็บปวดและใช้เวลาหลายนาทีแม้บนเครื่องรุ่นใหม่ แต่พอ Ninja รับช่วงต่อแล้ว มันก็รันได้เร็วมาก
ยังสงสัยด้วยว่าคุ้มหรือไม่ที่จะเพิ่มเครื่องมืออีกตัวเพื่อแปลจากเครื่องมือหนึ่งไปยังอีกเครื่องมือหนึ่ง โดยเฉพาะถ้าไฟล์ Ninja ใหญ่ขนาดนั้นและมนุษย์อ่านยาก
ในส่วนที่ว่า “ผมเชื่อว่าโปรแกรมเมอร์รับรู้ latency ได้ และแม้จะไม่สังเกตตัวเองก็ได้รับผลต่ออารมณ์ Google เพิ่งทำงานวิจัยด้านนี้และช่วยยืนยันความเชื่อของผมอยู่บ้าง” อยากรู้ว่า งานวิจัยเรื่อง latency นั้นเผยแพร่จริงหรือเปล่า
Ninja ค่อนข้างได้รับความนิยมในหมู่ นักพัฒนาเกม
ประโยคที่ว่า “Windows ยังคงเป็นแพลตฟอร์มขนาดใหญ่ในฝั่งนักพัฒนา และนักพัฒนาเหล่านั้นก็หิวโหยเครื่องมือ” น่าสนใจดี จากมุมมองของคนที่พัฒนาบน Windows เป็นหลัก กลับรู้สึกว่านักพัฒนา Linux ที่ต้องอยู่โดยไม่มี debugger ดี ๆ อย่าง Visual Studio หรือ profiler อย่าง Superluminal น่าจะเป็นฝ่ายที่หิวโหยเครื่องมือมากกว่า ช่วงหลังช่องว่างระหว่างสองแพลตฟอร์มดูเหมือนจะแคบลงเรื่อย ๆ และโดยรวมก็ชอบยูทิลิตี Rust ที่ทำงานข้ามแพลตฟอร์มได้
นึกไม่ออกว่าการโต้ตอบใด ๆ กับ Visual Studio จะเรียกว่า “ดี” ได้บ้าง พอนึกออกอยู่สองสามอย่างที่เรียกได้ว่า “พอใช้ได้แบบเฉียดฉิว” แต่การดีบักไม่อยู่ในนั้น อย่างไรก็ตามในเวอร์ชัน 2022 debugger ไม่ได้เต็มไปด้วยบั๊กอีกต่อไปแล้ว บางทีอาจหมายถึงเรื่องนั้นก็ได้
ผมรู้จัก UNIX ดีพอสมควร เพราะใช้หลายสายพันธุ์มาตั้งแต่ได้สัมผัส Xenix ในปี 1993 แต่แทบไม่ใช้ WSL เลยนอกจากไว้รันคอนเทนเนอร์ Linux Docker
บางอย่างที่ยังใช้ Ninja อยู่ ผมเปลี่ยนไปเป็น samurai แล้ว และมันดีขึ้นในทุกด้านที่เป็นไปได้
ถึงอย่างนั้น ผมก็ยังคิดว่าระบบบิลด์ประเภทนี้เดินผิดทาง สิ่งที่ต้องการจากระบบบิลด์คือการแฮชเนื้อหาของอินพุตแบบถ่ายทอดทั้งหมด แล้วตรวจสอบว่าผลลัพธ์นั้นมีอยู่ในรีจิสทรีหรือไม่
เพียงแต่ว่าทั้งหมดนั้นซับซ้อนมาก ถ้ามองเฉพาะการบิลด์ในเครื่อง ผมเข้าใจว่า SCons กับ Waf ก็ใช้แฮชในการตรวจจับการเปลี่ยนแปลงเช่นกัน
ผมเคยทำสิ่งที่คล้ายกันเป็นไลบรารี Deno ชื่อ “TDAR”[1] และมันทำงานได้ดี แต่ถ้าจะห่อหุ้มเครื่องมือบรรทัดคำสั่งที่ถือว่าตัวเองทำงานบนไฟล์ซิสเต็มที่แก้ไขได้ ให้ดูราวกับเรียกใช้เป็นฟังก์ชันบริสุทธิ์ ก็ต้องลงแรงอยู่พอสมควร
[1] ยังไม่ได้แยกออกมาจากโปรเจกต์แม่[2] แต่เคยพูดถึงไว้ในวิดีโอ YouTube นี้: https://youtu.be/sty29o8sUKI
[2] ถ้าสนใจเรื่องนี้ จะมากระตุ้นให้เปิดซอร์สก็ได้ togos zero zero at gee mail dot comb
แล้ว “ไม่ใช่สิ่งที่ผมต้องการ” ก็ไม่ได้แปลว่า “ผิด” เพราะในโลกนี้มีคนที่มีรสนิยมต่างกันอยู่
ถ้าจะใช้ โมดูล C++20 ใน CMake ต้องมี Ninja ดังนั้น Ninja น่าจะยังอยู่ไปอีกนานทีเดียว
ส่วนที่น่าสนใจที่สุดคือประโยคที่ว่า “มักต้องประนีประนอมระหว่างความถูกต้องกับความสะดวกหรือประสิทธิภาพ และต้องตระหนักว่าตัวเองเลือกอยู่ตรงไหนบนเส้นต่อเนื่องนั้น”
โปรแกรมเมอร์บางคนมองพลวัตนี้อย่างแข็งทื่อเกินไป และคิดว่าคุณค่าฝั่งหนึ่งย่อมต้องมาก่อนโดยปริยาย แต่ในความเป็นจริง ปฏิสัมพันธ์ระหว่างสิ่งเหล่านี้ละเอียดอ่อนกว่ามาก เช่น เครื่องมือที่ยอมเสียความถูกต้องไปบางส่วนเพื่อแลกกับความสะดวก อาจสร้างผลลัพธ์ที่ถูกต้องกว่าในภาพรวมของ ecosystem ได้ เมื่อเทียบกับทางเลือกที่ถูกต้องกว่าแต่สะดวกน้อยกว่า หากโปรแกรมเมอร์หลีกเลี่ยงทางเลือกหลัง
“ขออภัยสำหรับชื่อที่น่าอาย” งั้นเหรอ ชื่อนี้ยอดเยี่ยมออก
ป.ล. ถ้า implement สิ่งนี้ได้ อาจทำให้เร็วขึ้นได้อีก: https://github.com/ninja-build/ninja/issues/2157 แต่ตามที่บทความอธิบายไว้ เครื่องมือนี้ จงใจไม่เก็บสถานะ แม้กระทั่ง hint เล็ก ๆ น้อย ๆ จากการรันครั้งก่อน