- คู่มือนี้คือแนวทางการออกแบบโอเพนซอร์สสำหรับสร้าง CLI ที่อัปเดต หลักการ UNIX แบบดั้งเดิมให้ทันสมัย เพื่อให้ใช้งานง่ายสำหรับมนุษย์และมีความทนทานต่อการทำงานอัตโนมัติ
- CLI ที่ดีควรทำงานแบบ ให้มนุษย์มาก่อน แต่ก็ต้องรักษาธรรมเนียมอย่าง
stdout/stderr, exit code, pipe และ JSON เพื่อให้ทำงานร่วมกับโปรแกรมอื่นได้อย่างเป็นธรรมชาติ - คำสั่งช่วยเหลือ เอกสาร ข้อความผิดพลาด และเอาต์พุต ควรทำให้ผู้ใช้รู้ว่าต้องทำอะไรต่อไป โดย
--help, ตัวอย่าง, คำแนะนำ, การแสดงความคืบหน้า และ คำอธิบายสถานะ มีบทบาทสำคัญ - อาร์กิวเมนต์ แฟลก การโต้ตอบ การตั้งค่า และตัวแปรสภาพแวดล้อม ต้องคำนึงถึงทั้งการใช้งานผ่านสคริปต์และเทอร์มินัล และเพื่อความปลอดภัย ไม่ควรรับค่าลับโดยตรงผ่านแฟลกหรือ ตัวแปรสภาพแวดล้อม
- ชื่อของ CLI การแจกจ่าย ตลอดจนการเก็บข้อมูลวิเคราะห์ ต้องไม่บั่นทอนความรู้สึกว่าผู้ใช้ยังควบคุมได้ และต้องไม่ทำลายความเข้ากันได้ในระยะยาว แม้ในกรณีที่จำเป็นต้องแหกธรรมเนียมก็ต้องมีจุดประสงค์ที่ชัดเจน
ปรัชญาพื้นฐานของการออกแบบ CLI
- คู่มือนี้เป็นเอกสารโอเพนซอร์ส ที่ให้ทั้งหลักการออกแบบโปรแกรมบรรทัดคำสั่งสมัยใหม่และแนวทางเชิงปฏิบัติโดยอิงจากหลักการ UNIX แบบดั้งเดิม
- ในอดีต บรรทัดคำสั่งเป็นสภาพแวดล้อมแบบ ให้เครื่องมาก่อน ที่ใกล้เคียงกับ REPL บนแพลตฟอร์มสคริปต์ แต่ปัจจุบัน CLI มีลักษณะ ให้มนุษย์มาก่อน มากขึ้นในฐานะ UI แบบข้อความสำหรับเข้าถึงเครื่องมือ ระบบ และแพลตฟอร์มต่าง ๆ
- แม้บรรทัดคำสั่งจะมีข้อจำกัดเก่าแก่และธรรมเนียมที่ค่อนข้างแปลก แต่ก็ใช้งานได้บนแล็ปท็อปแทบทุกเครื่อง รองรับทั้งการใช้งานแบบโต้ตอบและการทำงานอัตโนมัติ และเปลี่ยนแปลงช้ากว่าส่วนประกอบอื่นของระบบ
- คู่มือนี้ไม่ได้ครอบคลุมโปรแกรมเทอร์มินัลแบบเต็มจออย่าง emacs หรือ vim และไม่ได้ผูกกับภาษาโปรแกรมหรือ toolchain ใดเป็นพิเศษ
CLI ที่ให้มนุษย์มาก่อนและประกอบใช้งานร่วมกันได้
- หาก CLI เป็นเครื่องมือที่มนุษย์ใช้เป็นหลัก ก็ควรออกแบบโดย คำนึงถึงมนุษย์ก่อน
- โปรแกรม CLI จำนวนมากมุ่งให้มนุษย์ใช้ แต่ยังคงยึดรูปแบบการโต้ตอบแบบเก่าที่ออกแบบมาเพื่อเครื่องเป็นหลัก
- ปรัชญา UNIX ที่ให้โปรแกรมเล็ก ๆ ทำงานร่วมกันผ่านอินเทอร์เฟซที่สะอาดยังคงสำคัญ
- ธรรมเนียมอย่าง standard input/output/error, signal และ exit code ทำให้โปรแกรมต่าง ๆ นำมาประกอบกันได้
- ข้อความธรรมดาแบบแยกบรรทัดส่งผ่าน pipe ได้ง่าย ส่วน JSON มีประโยชน์เมื่อจำเป็นต้องใช้ข้อมูลที่มีโครงสร้างมากขึ้น
- ความสม่ำเสมอคือหัวใจสำคัญที่เปลี่ยนต้นทุนการเรียนรู้ CLI ให้กลายเป็นประสิทธิภาพระยะยาว
- หากทำตามแพตเทิร์นที่มีอยู่ ผู้ใช้จะเดาคำสั่งและออปชันได้ง่ายขึ้น
- เมื่อธรรมเนียมทำลายการใช้งาน ก็อาจแหกได้อย่างระมัดระวัง
- CLI ที่ดีไม่ควรเงียบเกินไปหรือส่งเอาต์พุตมากเกินไป แต่ควรให้ข้อมูลแก่ผู้ใช้เท่าที่จำเป็น
- แม้ CLI มักถูกมองว่าค้นพบความสามารถได้ยากกว่า GUI แต่ก็เพิ่มความเรียนรู้ได้ด้วยคำสั่งช่วยเหลือที่ครอบคลุม ตัวอย่าง คำแนะนำคำสั่งถัดไป และแนวทางรับมือเมื่อเกิดข้อผิดพลาด
การโต้ตอบและความทนทาน
- การใช้งานบรรทัดคำสั่งใกล้เคียงกับ บทสนทนา ที่ต้องลองและแก้หลายครั้ง มากกว่าการรันเพียงครั้งเดียว
- หากอินพุตผิด ก็อาจเสนอวิธีแก้ไขที่เป็นไปได้
- สามารถแสดงสถานะกลางของงานหลายขั้นตอนได้อย่างชัดเจน
- สามารถขอการยืนยันก่อนทำงานที่มีความเสี่ยงได้
- CLI ต้องมีความทนทานจริง และในขณะเดียวกันก็ต้องทำให้ผู้ใช้รู้สึกได้ว่ามันทนทาน
- ต้องจัดการอินพุตที่ไม่คาดคิดได้อย่างนุ่มนวล
- หากเป็นไปได้ งานต่าง ๆ ควรมีคุณสมบัติ idempotent
- ไม่ควรแสดง stack trace ที่ดูน่ากลัวเป็นเอาต์พุตปริยาย
- ความเข้าอกเข้าใจผู้ใช้ เป็นสิ่งสำคัญที่จะทำให้ผู้ใช้รู้สึกว่าซอฟต์แวร์อยู่ข้างตน
- ไม่ใช่การใส่อีโมจิหรือทำให้เหมือนเกม แต่คือการออกแบบอย่างใส่ใจเพื่อให้ผู้ใช้ประสบความสำเร็จ
- ระบบนิเวศของเทอร์มินัลมีทั้งความไม่สอดคล้องและความสับสนมากมาย แต่ข้อจำกัดที่ต่ำก็เปิดทางให้เกิดนวัตกรรมใหม่ได้
- ควรยึดตามแพตเทิร์นเดิม แต่ก็สามารถละทิ้งมาตรฐานที่บั่นทอนประสิทธิภาพหรือความพึงพอใจของผู้ใช้ได้อย่างตั้งใจ
กฎพื้นฐานที่ต้องปฏิบัติตาม
- หากเป็นไปได้ ควรใช้ ไลบรารีสำหรับพาร์สอาร์กิวเมนต์บรรทัดคำสั่ง
- เมื่อสำเร็จควรคืน exit code เป็น
0และเมื่อผิดพลาดควรคืนค่าที่ไม่ใช่ 0- สคริปต์ใช้ exit code เพื่อตัดสินว่าโปรแกรมทำงานสำเร็จหรือล้มเหลว
- เอาต์พุตหลักของคำสั่งควรถูกส่งไปที่
stdout- เอาต์พุตที่เครื่องอ่านได้ก็ควรไปที่
stdoutเป็นค่าเริ่มต้นด้วย เพื่อให้ pipe ทำงานได้ตามปกติ
- เอาต์พุตที่เครื่องอ่านได้ก็ควรไปที่
- ข้อความสำหรับผู้ใช้ เช่น log message และข้อผิดพลาด ควรถูกส่งไปที่
stderr- เมื่อต่อผ่าน pipe ข้อความเหล่านี้จะได้ไม่ปนเป็นอินพุตของคำสั่งถัดไป
การออกแบบคำสั่งช่วยเหลือ
- เมื่อส่ง
-hหรือ--helpเข้ามา ต้องแสดงคำสั่งช่วยเหลือที่เพียงพอ- คำสั่งย่อยก็อาจมีคำสั่งช่วยเหลือของตัวเองได้
- ไม่ควรนำ
-hไปใช้ทับความหมายอื่น
- หากรันคำสั่งที่ต้องมีอาร์กิวเมนต์โดยไม่ส่งอาร์กิวเมนต์ใดมาเลย ควรแสดงคำสั่งช่วยเหลือแบบกระชับ
- คำอธิบายโปรแกรม
- ตัวอย่างการเรียกใช้งานหนึ่งหรือสองแบบ
- คำอธิบายแฟลก
- คำแนะนำให้ใช้
--helpเพื่อดูรายละเอียดเพิ่มเติม
- ควรใส่ช่องทางการขอความช่วยเหลือไว้ในคำสั่งช่วยเหลือด้วย
- ลิงก์เว็บไซต์หรือ GitHub เป็นรูปแบบที่พบได้ทั่วไป
- หากมีเอกสารบนเว็บ ก็ควรลิงก์ไปจากคำสั่งช่วยเหลือ
- หากคำสั่งย่อยมีหน้าหรือ anchor เฉพาะ การลิงก์ตรงจะมีประโยชน์
- ผู้ใช้มักหยิบตัวอย่างมาใช้ก่อนอ่านเอกสาร ดังนั้นจึงควรวาง ตัวอย่าง ไว้ช่วงต้นของคำสั่งช่วยเหลือ
- อาจแสดงกรณีใช้งานที่ซับซ้อนแต่พบบ่อยก่อนก็ได้
- หากมีตัวอย่างจำนวนมาก อาจเหมาะกว่าหากแยกไปไว้ในคำสั่ง cheat sheet หรือหน้าเว็บต่างหาก
- คำสั่งช่วยเหลือสามารถจัดรูปแบบให้อ่านกวาดตาได้ง่าย
- หัวข้อแบบตัวหนาช่วยเพิ่มความอ่านง่าย
- ควรจัดการด้วยวิธีที่ไม่ขึ้นกับเทอร์มินัล เพื่อไม่ให้อักขระ escape แสดงออกมาตรง ๆ
- หากผู้ใช้พิมพ์ผิด และเราคาดเดาเจตนาได้ ก็ควรเสนอคำแนะนำ
- เช่น
brew update jqอาจแนะนำให้ใช้brew upgrade jq - อาจถามว่าจะให้รันคำสั่งที่แนะนำหรือไม่ แต่ควรหลีกเลี่ยงการรันให้โดยอัตโนมัติ
- เช่น
- หากเป็นคำสั่งที่ต้องรับอินพุตจาก
stdinแต่stdinเป็นเทอร์มินัลแบบโต้ตอบ ก็ควรแสดงคำสั่งช่วยเหลือทันทีแล้วจบการทำงาน หรือพิมพ์ข้อความไปที่stderr
เอกสารประกอบ
- คำสั่งช่วยเหลือมีหน้าที่ให้สรุปแบบฉับไวและแนะนำงานที่พบบ่อย ส่วนเอกสารจะอธิบายวัตถุประสงค์ สิ่งที่ไม่ครอบคลุม วิธีทำงาน และวิธีใช้งานทั้งหมดของเครื่องมืออย่างละเอียด
- ควรมีเอกสารแบบเว็บ
- ค้นหาได้และลิงก์ไปยังส่วนที่เฉพาะเจาะจงได้
- เอกสารบนเว็บคือรูปแบบเอกสารที่ครอบคลุมที่สุด
- ควรมีเอกสารที่เข้าถึงได้จากเทอร์มินัลด้วย
- เข้าถึงได้รวดเร็ว
- ซิงก์กับเวอร์ชันของเครื่องมือที่ติดตั้งอยู่
- ใช้งานได้แม้ไม่มีอินเทอร์เน็ต
- อาจพิจารณาจัดทำหน้า
man- ผู้ใช้จำนวนมากมักตรวจ
man mycmdก่อน - เช่นเดียวกับ
gitและnpmที่เข้าถึงหน้าmanได้ผ่านคำสั่งย่อยhelp
- ผู้ใช้จำนวนมากมักตรวจ
หลักการด้านผลลัพธ์
- ผลลัพธ์ที่มนุษย์อ่านง่าย สำคัญที่สุด
- เกณฑ์ง่าย ๆ ในการตัดสินว่าผลลัพธ์สตรีมนั้นมีไว้ให้มนุษย์อ่านหรือไม่ คือดูว่าเป็น TTY หรือไม่
- ควรมีผลลัพธ์ที่เครื่องอ่านได้ด้วย ตราบเท่าที่ไม่ทำให้การใช้งานแย่ลง
- สตรีมบรรทัดข้อความธรรมดาเป็นอินเทอร์เฟซสากลของ UNIX
- ผู้ใช้คาดหวังว่าจะส่งผลลัพธ์ไปยังเครื่องมืออย่าง
grepแล้วทำงานได้ตามคาด
- หากผลลัพธ์ที่เป็นมิตรกับมนุษย์ทำให้ผลลัพธ์ที่เป็นมิตรกับเครื่องเสียหาย สามารถมี
--plainได้- ในผลลัพธ์แบบตาราง การแบ่งเซลล์ออกเป็นหลายบรรทัดอาจทำลายความคาดหวังว่า 1 เรคอร์ดต้องอยู่ 1 บรรทัด
--plainให้ผลลัพธ์ตารางแบบไม่ปรุงแต่งสำหรับใช้ในสคริปต์
- เมื่อส่ง
--jsonมา ควรแสดง JSON แบบจัดรูปแบบแล้ว- JSON จัดการโครงสร้างข้อมูลที่ซับซ้อนได้ง่าย และใช้ร่วมกับ
jqและเครื่องมือ JSON CLI ต่าง ๆ ได้ - เหมาะกับการ pipe ตรงไปยังเว็บเซอร์วิสผ่าน
curlด้วย
- JSON จัดการโครงสร้างข้อมูลที่ซับซ้อนได้ง่าย และใช้ร่วมกับ
- เมื่อสำเร็จควรมีผลลัพธ์ แต่ให้สั้นเข้าไว้
- คำสั่ง UNIX แบบดั้งเดิมมักไม่แสดงอะไรเมื่อไม่มีปัญหา แต่สำหรับมนุษย์อาจดูเหมือนค้างอยู่
- หากต้องการไม่ให้มีผลลัพธ์สำหรับสคริปต์ สามารถใช้ตัวเลือก
-qเพื่อซ่อนผลลัพธ์ที่ไม่จำเป็นได้
- คำสั่งที่เปลี่ยนสถานะควรบอกผู้ใช้ว่ามีอะไรเปลี่ยนไป
git pushเป็นตัวอย่างที่แสดงทั้งสิ่งที่กำลังทำและสถานะใหม่ของรีโมตบรานช์
- ควรดูสถานะปัจจุบันของระบบได้ง่าย
git statusแสดงทั้งสถานะของรีโพซิทอรีและคำใบ้ว่าคำสั่งถัดไปที่อาจรันได้คืออะไร
- งานที่ข้ามขอบเขตของโลกภายในโปรแกรม โดยทั่วไปควรต้องระบุอย่างชัดเจน
- กรณีอ่านหรือเขียนไฟล์ที่ผู้ใช้ไม่ได้ส่งมาเป็นอาร์กิวเมนต์
- กรณีสื่อสารกับเซิร์ฟเวอร์ระยะไกลเพื่อดาวน์โหลดไฟล์ลงมา
- ควรใช้สีอย่างมีจุดมุ่งหมาย
- ถ้าใช้มากเกินไป ความหมายจะหายไปและอ่านยากขึ้น
- ควรปิดสีเมื่อไม่ใช่ TTY, หรือมีการตั้งค่า
NO_COLOR, หรือTERM=dumb, หรือส่ง--no-colorมา
- หาก
stdoutไม่ใช่เทอร์มินัลแบบโต้ตอบ ไม่ควรแสดงแอนิเมชัน- ช่วยป้องกันไม่ให้ progress indicator ทำให้ log ของ CI รก
- เมื่อต้องแสดงข้อความจำนวนมาก สามารถใช้ pager อย่าง
lessได้- ควรใช้เฉพาะเมื่อ
stdinหรือstdoutเป็นเทอร์มินัลแบบโต้ตอบ less -FIRXอาจใช้เป็นชุดตัวเลือกที่สมเหตุสมผลได้
- ควรใช้เฉพาะเมื่อ
ข้อความแสดงข้อผิดพลาด
- หากทำให้ข้อผิดพลาดอ่านได้เหมือนเอกสาร จะช่วยลดเวลาที่ผู้ใช้ต้องไปค้นเอกสาร
- ควรดักจับข้อผิดพลาดที่คาดการณ์ได้ แล้วเขียนใหม่ให้อยู่ในรูปแบบที่มนุษย์เข้าใจได้
- ตัวอย่าง: อธิบายว่าเขียนลง
file.txtไม่ได้ และอาจต้องใช้chmod +w file.txt
- ตัวอย่าง: อธิบายว่าเขียนลง
- อัตราส่วนสัญญาณต่อสัญญาณรบกวนเป็นเรื่องสำคัญ
- ยิ่งมีผลลัพธ์ที่ไม่เกี่ยวข้องมาก ผู้ใช้ก็ยิ่งหาปัญหาได้ยาก
- หากมีข้อผิดพลาดประเภทเดียวกันหลายรายการ สามารถจัดกลุ่มไว้ใต้หัวข้ออธิบายเดียวได้
- ควรวางข้อมูลที่สำคัญที่สุดไว้ท้ายผลลัพธ์
- สายตาของผู้ใช้มักถูกดึงไปที่ข้อความสีแดง จึงควรใช้ให้น้อยอย่างตั้งใจ
- หากเป็นข้อผิดพลาดที่ไม่คาดคิดหรืออธิบายได้ยาก ควรให้ข้อมูล debug/traceback และวิธีส่งบั๊ก
- เพื่อไม่ให้ผู้ใช้รู้สึกถาโถม สามารถเขียน debug log ลงไฟล์แทนเทอร์มินัลได้
- ควรทำให้การส่งบั๊กทำได้ง่าย
- ตัวอย่างเช่น ให้ URL ที่กรอกข้อมูลที่เป็นไปได้ไว้ล่วงหน้าแล้ว
อาร์กิวเมนต์และแฟล็ก
- อาร์กิวเมนต์คือพารามิเตอร์แบบอิงตำแหน่ง ส่วนแฟล็กคือพารามิเตอร์แบบมีชื่อ
- ใน
cp foo barพาธไฟล์คืออาร์กิวเมนต์ -r,--recursive,--file foo.txtคือแฟล็ก
- ใน
- หากเป็นไปได้ควรเลือกใช้ แฟล็ก มากกว่าอาร์กิวเมนต์
- แม้จะต้องพิมพ์มากกว่า แต่ความหมายชัดเจนกว่า
- และเปลี่ยนรูปแบบอินพุตในอนาคตได้ง่ายกว่า
- ทุกแฟล็กควรมีชื่อแบบยาว
- เช่นมีทั้ง
-hและ--help - มีประโยชน์ต่อการแสดงความหมายให้ชัดในสคริปต์
- เช่นมีทั้ง
- แฟล็กตัวอักษรเดียวควรใช้เฉพาะตัวเลือกที่ใช้บ่อย
- เพื่อสงวนพื้นที่ชื่อแบบสั้นไว้ให้แฟล็กที่จะเพิ่มในอนาคต
- หากเป็นงานง่าย ๆ แบบเดียวกันที่ใช้กับหลายไฟล์ การมีหลายอาร์กิวเมนต์ถือว่าเหมาะสม
- รูปแบบอย่าง
rm file1.txt file2.txt file3.txtเข้ากันได้ดีกับ globbing
- รูปแบบอย่าง
- หากมีอาร์กิวเมนต์ตั้งแต่ 2 ตัวขึ้นไปที่มีความหมายต่างกัน มีโอกาสสูงว่าการออกแบบไม่ดี
- ข้อยกเว้นคือกรณีอย่าง
cp <source> <destination>ที่พบได้บ่อยและเป็นการทำงานหลัก จนความสั้นคุ้มค่าที่จะจดจำ
- ข้อยกเว้นคือกรณีอย่าง
- หากมีชื่อแฟล็กมาตรฐานอยู่แล้ว ควรทำตามนั้น
-a, --all-d, --debug-f, --force--json-h, --help-n, --dry-run--no-input-o, --output-p, --port-q, --quiet-u, --user--version
- ค่าเริ่มต้นควรเป็นพฤติกรรมที่ถูกต้องสำหรับผู้ใช้ส่วนใหญ่
- ถ้าสมมติให้ผู้ใช้ต้องคอยหาแฟล็กที่เหมาะแล้วจำมาใช้ทุกครั้ง ประสบการณ์ของผู้ใช้ส่วนใหญ่จะแย่ลง
- หากไม่มีอินพุต สามารถถามผ่านพรอมป์ต์ได้ แต่พรอมป์ต์ต้องไม่เป็นสิ่งจำเป็น
- ต้องมีวิธีส่งอินพุตผ่านแฟล็กหรืออาร์กิวเมนต์ได้เสมอ
- หาก
stdinไม่ใช่เทอร์มินัลแบบโต้ตอบ ควรข้ามพรอมป์ต์แล้วบังคับให้ส่งแฟล็กหรืออาร์กิวเมนต์ที่จำเป็น
- ควรมีการยืนยันก่อนทำงานที่อันตราย
- ในการรันแบบโต้ตอบ อาจให้พิมพ์
yหรือyes - ในการรันแบบไม่โต้ตอบ อาจบังคับให้ใช้
-fหรือ--force - สำหรับงานที่ร้ายแรง อาจให้พิมพ์ชื่อสิ่งที่จะลบเอง หรือมีแฟล็กอย่าง
--confirm="name-of-thing"
- ในการรันแบบโต้ตอบ อาจให้พิมพ์
- หากรับอินพุต/เอาต์พุตเป็นไฟล์ ควรรองรับ
-เพื่อใช้stdinหรือstdout- จะได้เชื่อมต่อกับผลลัพธ์ของคำสั่งอื่นได้โดยไม่ต้องมีไฟล์ชั่วคราว
- หากเป็นไปได้ ควรจัดการลำดับของอาร์กิวเมนต์ แฟล็ก และซับคอมมานด์อย่างเป็นอิสระจากกัน
- เพราะผู้ใช้มักเพิ่มตัวเลือกไว้ท้ายคำสั่งเดิมแล้วรันใหม่
- ไม่ควรอ่านค่าลับจากแฟล็กโดยตรง
- ค่า
--passwordอาจโผล่ในผลลัพธ์psหรือใน shell history - ควรพิจารณาอินพุตจากไฟล์อย่าง
--password-fileหรือstdin
- ค่า
การโต้ตอบ
- ควรใช้พรอมป์ต์หรือองค์ประกอบแบบโต้ตอบเฉพาะเมื่อ
stdinเป็น TTY เท่านั้น- ระหว่างการทำงานในสคริปต์หรือรับอินพุตจาก pipe พรอมป์ต์จะใช้งานไม่ได้ จึงควรแจ้งเป็นข้อผิดพลาดว่าต้องส่งแฟล็กใดมา
- หากส่ง
--no-inputมา ต้องไม่แสดงพรอมป์ต์หรือทำงานแบบโต้ตอบ- หากต้องมีอินพุต ให้ล้มเหลวและบอกวิธีส่งผ่านแฟล็กแทน
- เมื่อต้องรับรหัสผ่าน ต้องไม่แสดงค่าที่ผู้ใช้พิมพ์
- จัดการโดยปิด echo ของเทอร์มินัล
- ผู้ใช้ต้องสามารถออกจากโปรแกรมได้
- แม้จะค้างอยู่กับ network I/O เป็นต้น Ctrl-C ก็ควรใช้งานได้
- หากเป็น wrapper อย่าง SSH, tmux, telnet ที่ออกด้วย Ctrl-C ไม่ได้ ควรบอกวิธีออกให้ชัดเจน
ซับคอมมานด์
- หากเครื่องมือซับซ้อนมากพอ สามารถลดความซับซ้อนด้วยชุดของซับคอมมานด์ได้
- การรวมเครื่องมือที่เกี่ยวข้องหลายตัวไว้ในคำสั่งเดียว อาจช่วยให้ใช้งานและค้นพบได้ง่ายขึ้น
- ยังเหมาะกับการแชร์ global flag, help, config และกลไกการจัดเก็บร่วมกัน
- ระหว่างซับคอมมานด์ต้องมีความสอดคล้องกัน
- ความหมายเดียวกันควรใช้ชื่อแฟล็กเดียวกัน
- รูปแบบผลลัพธ์ก็ควรคล้ายกันด้วย
- หากมีซับคอมมานด์หลายชั้น ควรใช้ระบบการตั้งชื่อที่สอดคล้องกัน
- รูปแบบสองชั้นแบบคำนามและกริยา เช่น
docker container createเป็นแพตเทิร์นที่พบได้บ่อย - ทั้ง
noun verbและverb nounใช้ได้ แต่noun verbดูจะพบบ่อยกว่า
- รูปแบบสองชั้นแบบคำนามและกริยา เช่น
- ควรหลีกเลี่ยงคำสั่งที่กำกวมหรือชื่อคล้ายกัน
- หากมี
updateและupgradeอยู่ด้วยกัน อาจทำให้สับสนได้
- หากมี
ความทนทาน
- ต้องตรวจสอบข้อมูลทั้งหมดที่ผู้ใช้ป้อน
- เนื่องจากอาจมีข้อมูลที่ไม่ถูกต้องเข้ามาได้ จึงควรตรวจสอบตั้งแต่เนิ่น ๆ และหยุดการทำงานพร้อมข้อความผิดพลาดที่เข้าใจได้
- การตอบสนอง สำคัญกว่าความเร็ว
- ควรแสดงผลบางอย่างให้ผู้ใช้เห็นภายใน 100ms
- ควรแสดงข้อความก่อนส่งคำขอเครือข่ายด้วย เพื่อไม่ให้ดูเหมือนว่าโปรแกรมค้าง
- สำหรับงานที่ใช้เวลานาน ควรแสดงความคืบหน้า
- หากไม่มีเอาต์พุตอยู่ช่วงหนึ่ง โปรแกรมจะดูเหมือนเสีย
- หากตัวบ่งชี้ความคืบหน้าหยุดค้างอยู่นาน สามารถแสดงการประมาณเวลาที่เหลือหรือแอนิเมชันเพื่อบอกว่ายังทำงานอยู่ได้
- ควรประมวลผลแบบขนานเมื่อทำได้ แต่ต้องระมัดระวัง
- การแสดงความคืบหน้าของงานแบบขนานในเชลล์ทำได้ยาก และหากเอาต์พุตปะปนกันก็จะทำให้สับสน
- แถบความคืบหน้าหลายอันของ
docker pullเป็นตัวอย่างที่แสดงว่าเกิดอะไรขึ้น - แม้จะซ่อนล็อกไว้หลังแถบความคืบหน้าในกรณีที่ทำงานปกติ แต่เมื่อเกิดข้อผิดพลาดก็ควรแสดงล็อกเพื่อให้ดีบักได้
- งานเครือข่ายควรมี timeout
- timeout ควรตั้งค่าได้ และควรมีค่าเริ่มต้นที่สมเหตุสมผล
- ควรกู้คืนได้หลังความล้มเหลว
- หลังจากความล้มเหลวชั่วคราว เมื่อรันใหม่ด้วย
<up>และ<enter>ก็ควรทำต่อจากจุดที่หยุดไว้ได้
- หลังจากความล้มเหลวชั่วคราว เมื่อรันใหม่ด้วย
- ถ้าเป็นไปได้ แนวทางแบบ crash-only จะดีกว่า
- เมื่อเกิดความล้มเหลวหรือการขัดจังหวะ สามารถออกได้ทันที และเลื่อนการเก็บกวาดไปทำในการรันครั้งถัดไปได้
- ผู้ใช้อาจใช้เครื่องมือในรูปแบบที่คาดไม่ถึง
- อาจห่อด้วยสคริปต์ ใช้บนอินเทอร์เน็ตที่ไม่เสถียร รันหลายอินสแตนซ์พร้อมกัน หรือรันในสภาพแวดล้อมที่ไม่เคยทดสอบ
ความเข้ากันได้ในอนาคต
- คำสั่งย่อย อาร์กิวเมนต์ แฟล็ก ไฟล์ตั้งค่า และตัวแปรสภาพแวดล้อม ล้วนเป็นอินเทอร์เฟซ และควรรักษาให้ใช้งานได้ยาวนาน
- การเปลี่ยนแปลงที่เป็นไปได้ควรเป็นแบบเพิ่มเติม
- แทนที่จะทำให้พฤติกรรมของแฟล็กเดิมพัง สามารถเพิ่มแฟล็กใหม่ได้
- หากจำเป็นต้องมีการเปลี่ยนแปลงที่ไม่ใช่แบบเพิ่มเติม ควรเตือนล่วงหน้า
- เมื่อมีการใช้แฟล็กที่ไม่แนะนำให้ใช้แล้ว ควรแจ้งการเปลี่ยนแปลงในอนาคต และบอกวิธีที่เข้ากันได้กับอนาคตซึ่งสามารถใช้ได้ตั้งแต่ตอนนี้
- เอาต์พุตสำหรับมนุษย์อ่าน แม้เปลี่ยนไปก็โดยทั่วไปไม่เป็นไร
- ควรชี้นำให้ผู้ใช้ใช้
--plainหรือ--jsonในสคริปต์เพื่อให้ได้เอาต์พุตที่เสถียร
- ควรชี้นำให้ผู้ใช้ใช้
- ควรหลีกเลี่ยงคำสั่งย่อยแบบ catch-all
- วิธีที่ถ้าไม่ใช่คำสั่งย่อยที่รู้จักก็ถือเป็น
runโดยอัตโนมัติ อาจทำให้การใช้งานเดิมพังได้เมื่อต้องเพิ่มคำสั่งย่อยใหม่ในภายหลัง
- วิธีที่ถ้าไม่ใช่คำสั่งย่อยที่รู้จักก็ถือเป็น
- ไม่ควรอนุญาตตัวย่อแบบตามอำเภอใจของคำสั่งย่อย
- หากอนุญาตให้
installใช้เป็นiหรือinsได้ ในอนาคตก็จะเพิ่มคำสั่งอื่นที่ขึ้นต้นด้วยiได้ยาก - alias ควรชัดเจนและคงเส้นคงวา
- หากอนุญาตให้
- ควรพิจารณาว่าอีก 20 ปีข้างหน้า คำสั่งจะยังทำงานแบบเดิมหรือไม่
- ไม่ควรสร้าง ระเบิดเวลา ที่ทำให้คำสั่งหยุดทำงานเพราะการพึ่งพาอินเทอร์เน็ตภายนอกเปลี่ยนไปหรือหายไป
สัญญาณและอักขระควบคุม
- เมื่อผู้ใช้ส่ง Ctrl-C หรือสัญญาณ INT โปรแกรมควรออกให้เร็วที่สุดเท่าที่ทำได้
- ก่อนเริ่มงานเก็บกวาด ควรบอกอะไรบางอย่างทันที
- โค้ดเก็บกวาดควรมี timeout
- หากมีการกด Ctrl-C ซ้ำระหว่างการเก็บกวาดที่ใช้เวลานาน ก็ควรสามารถข้ามการเก็บกวาดได้
- Docker Compose จะแจ้งว่า หากกด Ctrl-C อีกครั้งระหว่างการปิดระบบ จะสามารถบังคับหยุดคอนเทนเนอร์ได้ทันที
- โปรแกรมควรถือไว้ก่อนว่าอาจเริ่มทำงานในสภาพที่งานเก็บกวาดครั้งก่อนยังไม่ได้ถูกรัน
การตั้งค่าและตัวแปรสภาพแวดล้อม
- วิธีการตั้งค่าควรแตกต่างกันไปตาม ความเฉพาะเจาะจง, ความเสถียร, และ ความซับซ้อน
- การตั้งค่าที่เปลี่ยนบ่อยในแต่ละครั้งที่รัน เหมาะกับแฟล็ก
- การตั้งค่าที่ค่อนข้างคงที่แต่ต่างกันตามผู้ใช้ โปรเจ็กต์ หรือเครื่อง เหมาะกับแฟล็กและตัวแปรสภาพแวดล้อม
- การตั้งค่าที่คงที่สำหรับผู้ใช้ทุกคนภายในโปรเจ็กต์ เหมาะกับไฟล์ตั้งค่าเฉพาะคำสั่งที่อยู่ภายใต้การควบคุมเวอร์ชัน
- ควรปฏิบัติตาม XDG Base Directory Specification
- มีจุดประสงค์เพื่อรองรับตำแหน่งตั้งค่าทั่วไปอย่าง
~/.configและลดการเพิ่มขึ้นของ dotfile ในโฮมไดเรกทอรี
- มีจุดประสงค์เพื่อรองรับตำแหน่งตั้งค่าทั่วไปอย่าง
- หากจะแก้ไขไฟล์ที่ไม่ใช่การตั้งค่าของโปรแกรมตนเองโดยอัตโนมัติ ควรขอความยินยอมจากผู้ใช้และบอกให้ชัดเจนว่าจะทำอะไรบ้าง
- การสร้างไฟล์ตั้งค่าใหม่มักดีกว่าการต่อท้ายไฟล์ตั้งค่าระบบที่มีอยู่แล้ว
- ควรใช้ลำดับความสำคัญของการตั้งค่าจากสูงไปต่ำ
- แฟล็ก
- ตัวแปรสภาพแวดล้อมของเชลล์ที่กำลังรันอยู่
- การตั้งค่าระดับโปรเจ็กต์
- การตั้งค่าระดับผู้ใช้
- การตั้งค่าทั้งระบบ
- ตัวแปรสภาพแวดล้อมเหมาะกับพฤติกรรมที่เปลี่ยนไปตาม บริบท ที่คำสั่งถูกรัน
- ชื่อตัวแปรสภาพแวดล้อมควรใช้เฉพาะตัวพิมพ์ใหญ่ ตัวเลข และขีดล่าง เพื่อให้พกพาได้สูงสุด และไม่ควรขึ้นต้นด้วยตัวเลข
- ค่าควรเป็นบรรทัดเดียวถ้าเป็นไปได้
- ค่าหลายบรรทัดทำให้เกิดปัญหาด้านการใช้งานเมื่อใช้ร่วมกับคำสั่ง
env
- ค่าหลายบรรทัดทำให้เกิดปัญหาด้านการใช้งานเมื่อใช้ร่วมกับคำสั่ง
- ไม่ควรจับจองชื่อที่ใช้กันแพร่หลายอย่างไม่ระวัง
- สามารถอ้างอิงรายการตัวแปรสภาพแวดล้อมมาตรฐานของ POSIX ได้
- หากเป็นไปได้ ควรตรวจสอบตัวแปรสภาพแวดล้อมสากล
NO_COLOR,FORCE_COLORDEBUGEDITORHTTP_PROXY,HTTPS_PROXY,ALL_PROXY,NO_PROXYSHELLTERM,TERMINFO,TERMCAPTMPDIRHOMEPAGERLINES,COLUMNS
- ในกรณีที่เหมาะสม อาจอ่านตัวแปรสภาพแวดล้อมจาก
.envได้- มีประโยชน์สำหรับตัวแปรที่แทบไม่เปลี่ยนระหว่างการทำงานในไดเรกทอรีหนึ่ง ๆ
.envไม่ใช่สิ่งทดแทนไฟล์ตั้งค่าอย่างเป็นทางการ- มักไม่ถูกเก็บไว้ในระบบจัดการซอร์สโค้ด
- มีเพียงชนิดสตริงเท่านั้น
- มีแนวโน้มจะจัดระเบียบได้ไม่ดี
- มีโอกาสเกิดปัญหาเรื่อง encoding ได้ง่าย
- มีแนวโน้มจะใส่ข้อมูลรับรองและข้อมูลคีย์ที่อ่อนไหวไว้
- ไม่ควรอ่านค่าความลับจากตัวแปรสภาพแวดล้อม
- ตัวแปรสภาพแวดล้อมอาจถูกเปิดเผยผ่านโปรเซส ล็อก
docker inspect,systemctl showเป็นต้น - ควรรับค่าความลับผ่านไฟล์ข้อมูลรับรอง, pipe, ซ็อกเก็ต
AF_UNIX, บริการจัดการความลับ หรือวิธี IPC อื่น
- ตัวแปรสภาพแวดล้อมอาจถูกเปิดเผยผ่านโปรเซส ล็อก
ชื่อ การแจกจ่าย และการเก็บข้อมูลวิเคราะห์
- ชื่อโปรแกรม CLI ควรเรียบง่ายและจำง่าย เพราะผู้ใช้ต้องพิมพ์บ่อย
- หากทั่วไปเกินไป อาจชนกับคำสั่งอื่นหรือทำให้ผู้ใช้สับสนได้
- ชื่อควรใช้ตัวพิมพ์เล็กและขีดกลางเท่าที่จำเป็น
curlเป็นชื่อที่ดี และDownloadURLเป็นตัวอย่างที่ไม่ดี
- ชื่อควรสั้น แต่ชื่อที่สั้นเกินไปเหมาะกับยูทิลิตีที่ใช้กันแพร่หลายมากอย่าง
cd,ls,ps - ควรเป็นชื่อที่พิมพ์ง่าย
- มีกรณีที่ชื่อเริ่มต้นของ Docker Compose คือ
plumแต่เปลี่ยนเป็นfigเพราะพิมพ์ด้วยมือเดียวได้ไม่ถนัด
- มีกรณีที่ชื่อเริ่มต้นของ Docker Compose คือ
- หากเป็นไปได้ ควรแจกจ่ายเป็นไบนารีเดียว
- หากทำไบนารีเดียวยาก ก็ควรใช้ตัวติดตั้งแพ็กเกจมาตรฐานของแพลตฟอร์มเพื่อให้ถอนการติดตั้งได้ง่าย
- เครื่องมือเฉพาะภาษา เช่น code linter อาจสมมติได้ว่าผู้ใช้มีอินเทอร์พรีเตอร์ของภาษานั้นอยู่แล้ว
- ควรถอนการติดตั้งได้ง่าย
- เพราะมักมีกรณีที่ผู้ใช้ต้องการถอนการติดตั้งทันทีหลังติดตั้ง จึงควรวางคำแนะนำการถอนการติดตั้งไว้ใต้คำแนะนำการติดตั้ง
- ตัวชี้วัดการใช้งานอาจช่วยปรับปรุงเครื่องมือได้ แต่ผู้ใช้ CLI คาดหวังว่าจะควบคุมสภาพแวดล้อมของตนเองได้
- ไม่ควรส่งข้อมูลการใช้งานหรือข้อมูลแครชโดยไม่ได้รับความยินยอม
- ควรระบุให้ชัดว่ากำลังเก็บอะไร เก็บไปทำไม ทำให้ไม่ระบุตัวตนมากน้อยเพียงใด ทำอย่างไรจึงไม่ระบุตัวตน และเก็บไว้นานเท่าใด
- โดยอุดมคติควรเป็นแบบ opt-in และหากเลือกเก็บแบบค่าเริ่มต้นแล้วให้ปิดเองภายหลังได้แบบ opt-out ก็ควรแจ้งให้ชัดเจนบนเว็บไซต์หรือตอนรันครั้งแรก และต้องปิดได้ง่าย
- อาจพิจารณาทางเลือกอื่นแทนการเก็บข้อมูลวิเคราะห์
- วัดผลจากเอกสารบนเว็บ
- วัดผลจากการดาวน์โหลด
- พูดคุยกับผู้ใช้โดยตรง และส่งเสริมให้มีฟีดแบ็กกับคำขอฟีเจอร์ผ่านเอกสารและรีโพซิทอรี
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
คำพูดที่ว่า “ทุกวันนี้คนส่วนใหญ่ไม่รู้ด้วยซ้ำว่าคำสั่งบรรทัดคำสั่งคืออะไร” ก็จริง แต่แม้แต่ในยุค ทศวรรษ 1980 ที่บทความต้นฉบับใช้อ้างอิงก็เป็นแบบนั้นเหมือนกัน
ความต่างคือ ตอนนี้คนที่รู้จักและใช้งานบรรทัดคำสั่งได้มีจำนวนมากที่สุดในประวัติศาสตร์ และเพิ่มขึ้นอย่างน้อยระดับหลักเดียวของเท่า ๆ กัน หรืออาจถึงหลักสองเท่า จึงพอจะเรียกได้ว่าเป็น ยุคทองของ CLI
สถานะส่วนกลางและ dependency ทำให้การอนุมานยากขึ้น และสุดท้ายก็ทำให้การดีบักกับการปรับจูนประสิทธิภาพยากขึ้นด้วย
ถ้าแยกโค้ด 500 บรรทัด, 1,000 บรรทัด, 5,000 บรรทัด, 10,000 บรรทัด, บางครั้งถึง 50,000 บรรทัด ให้ออกมารันแยกได้ หลายอย่างจะชัดเจนขึ้นมาก
ถ้าทำดี ๆ ยังช่วยพาไปสู่โครงสร้างแบบ Functional Core, Imperative Shell ได้ด้วย เพราะบรรทัดคำสั่งที่สอดคล้องกับปรัชญา Unix ควรมีการทำงานแบบไม่มีผลข้างเคียงเป็นส่วนใหญ่ และมีการทำงานที่มีผลข้างเคียงให้น้อย
คุณสามารถสร้างคำสั่งเล็ก ๆ ที่สร้างและแสดงสถานะของระบบก่อนหน้าที่จะมีการตัดสินใจผิดพลาดออกมาได้ และยังรันใน production ได้อย่างแทบจะปลอดภัย
แบบนี้ก็ทำให้ส่งต่อเครื่องมือเหล่านี้ให้คนที่ควรถูกนับอยู่ใน bus factor เข้ามามีส่วนร่วมได้ง่ายขึ้น
คนในหมู่บ้านอารยธรรมกลางป่าอเมซอนจะคิดอย่างไรกับเทอร์มินัลนั้นไม่เกี่ยวข้อง
ถ้าจะตัดสินการเปลี่ยนแปลงเชิงคุณภาพของสิ่งที่มีจำนวนเพิ่มขึ้น ต้องดูสัดส่วน ไม่ใช่ดูแค่จำนวนสัมบูรณ์ ไม่อย่างนั้นจะได้ประโยคที่จริงแต่ไม่มีความหมาย
เช่น ทุกวันนี้จำนวนผู้ฟังแจ๊สแบบสัมบูรณ์อาจมากกว่าช่วงพีกทางวัฒนธรรมของแนวเพลงนี้ แต่ไม่ได้แปลว่าแจ๊สได้รับความนิยมมากกว่า แค่จำนวนคนที่ฟังเพลงโดยรวมเพิ่มขึ้น
ถึงอย่างนั้นก็ยากจะบอกว่าแจ๊สในสหรัฐสำคัญและทรงอิทธิพลกว่ายุครุ่งเรืองของมัน
อยากให้พิจารณาใส่ ออปชัน
--dry-runที่แสดงล่วงหน้าว่าจะเกิดอะไรขึ้นโดยไม่เปลี่ยนแปลงจริงมันมีประโยชน์มากสำหรับตอนเรียนรู้เครื่องมือ หรือก่อนรันงานที่ย้อนกลับยาก เพื่อเช็กว่าใส่ออปชันซับซ้อนและ file glob ถูกต้องหรือไม่
--dry-runและต้องใส่แฟลก--executeเมื่อต้องการรันจริงไม่รู้เหมือนกันว่าถ้าไม่มีมันจะทำยังไง และมันช่วยไว้หลายครั้งจากสถานการณ์ที่เกือบพังหมดจริง ๆ
--commitเข้าไปสำหรับสคริปต์ที่ทำงานซึ่งย้อนกลับไม่ได้หรือย้อนกลับยาก วิธีนี้รู้สึกปลอดภัยกว่า
ผมสับสนว่าจะออกแบบมันอย่างไรในเครื่องมือที่กำลังทำอยู่
ถ้าต้องเรียก API เพื่อดึงข้อมูล จะทำให้เป็น dry run ได้อย่างไร
ต้องให้ตัวอย่างปลอมกับผลลัพธ์ปลอมหรือ?
do_thing.py | dry-runคิดซะว่าเหมือนขอในพรอมป์ต์ว่า “ช่วยอธิบายขั้นตอนการทำงานทีละขั้นให้หน่อย”
ไม่ใช่แค่ว่าเมื่อ standard output ไม่ได้เป็นเทอร์มินัลแบบโต้ตอบแล้วไม่ควรแสดงแอนิเมชัน แต่คือ ไม่ควรแสดงแอนิเมชันบน stdout เลย
โดยรวมแล้วบทความต้นฉบับดีมาก แต่พอหาส่วนที่อธิบายความต่างระหว่าง stderr กับ stdout แล้วไม่เจอ ก็รู้สึกเสียดาย
stderr ไม่ได้มีไว้แค่ข้อผิดพลาด แต่เป็นที่สำหรับ log และข้อความเชิงข้อมูลทั้งหมด และถ้าเป็นเทอร์มินัลก็เป็นพื้นที่ที่อาจใส่แอนิเมชันได้
stdout ควรเป็นผลลัพธ์จริงที่มีประโยชน์ และควรสม่ำเสมอไม่ว่าจะเป็นเทอร์มินัลหรือไม่ก็ตาม
เช่นใน
echo foo | mysed 's/oo/aa/' | catค่า stdout ของmysedควรเป็นfaaและที่ stderr ควรเป็น log เช่นข้อมูลเวอร์ชันหรือสิ่งที่พบผมไม่อยากต้องสู้กับเครื่องมือด้วย grep เพียงเพื่อจะได้ผลลัพธ์จริง และก็ไม่อยากให้การเอา
| catออกทำให้พฤติกรรมเปลี่ยนจนดีบักยากถ้าผู้ใช้ขอ
--helpก็ควรไปที่ stdout และถ้าผู้ใช้ขอ log log ก็ต้องไปที่ stdoutถ้าเป็นข้อมูลที่ไม่ได้ร้องขอ stderr คือที่ที่เหมาะสม
ถ้าย้อนเวลาได้ น่าจะตั้งชื่อเป็น stdin, stdout, stdext แบบนี้ คนอาจทำตามธรรมเนียมนี้มากกว่า
แต่เพราะมันชื่อ stderr นักพัฒนาจึงคิดอย่างสมเหตุสมผลว่า “เอาไว้รายงานข้อผิดพลาด” และถ้าไม่ใช่ข้อผิดพลาดก็โยนไป stdout
ถึงอย่างนั้นในเชิงโครงสร้างโปรแกรม วิธีนี้ดีกว่า และแม้ตอนแรกจะสับสนกว่า แต่ก็ได้ประโยชน์เพราะเอา output ไปทำสิ่งที่มีประโยชน์มากขึ้น
ผมก็ไม่ค่อยคิดว่าสีจะนับเป็นข้อมูลเหมือนกัน
อยากให้เลี่ยงคำแนะนำแบบ “ใช้สัญลักษณ์และอีโมจิในที่ที่ช่วยให้ชัดเจนขึ้น” จริง ๆ
yubikey-agent ที่ยกมาเป็นตัวอย่างแสดงให้เห็นตรง ๆ ถึงสิ่งที่ผมไม่ชอบใน GitHub README และส่วนติดต่อผู้ใช้แบบขี้เล่น
ในเชิงเทคนิค สัญลักษณ์และอีโมจิอาจถูกเรนเดอร์ต่างกันไปในแต่ละเทอร์มินัล จนทำให้ข้อความชวนสับสน
ในเชิงสุนทรียะ ระดับความยอมรับต่อความขี้เล่นและความสดใสของแต่ละคนก็ต่างกันมาก จึงควรใช้อย่างยับยั้งมาก ๆ และใช้เฉพาะเมื่อรู้จริง ๆ ว่ากำลังทำอะไร
ชอบเอาต์พุตเทอร์มินัลที่มีสี และอีโมจิก็มีสีด้วย เลยช่วยให้เห็นภาพรวมของสถานการณ์ได้เร็ว
ผมก็ชอบ syntax highlighting และถ้าไม่มีจะรู้สึกว่าอ่านโค้ดยากขึ้นมาก
ไม่ใช่ทุกคนจะเป็นแบบนั้น และในเมื่อผมก็ไม่ได้คาดหวังให้รสนิยมของตัวเองเป็นค่าเริ่มต้น ก็ไม่ควรบังคับรสนิยมตรงข้ามให้เป็นค่าเริ่มต้นเหมือนกัน
บางคนชอบ บางคนไม่ชอบ ดังนั้นปิดไว้เป็นค่าเริ่มต้นแต่เปิดให้ผู้ใช้เลือกเองก็พอ
เช่น มีตัวหนึ่งแทนความสำเร็จ อีกตัวหนึ่งแทนความล้มเหลวก็พอ
และข้อมูลที่อีโมจิสื่อจะต้องมีข้อความกำกับซ้ำด้วยเสมอ
ตอนที่เห็น CLI Guidelines ครั้งแรก ผมก็หยุดอ่านตรงส่วนนั้นเลย
รอบนี้กลับมาอ่านส่วนที่เหลือด้วย และโดยรวมก็ถือว่าค่อนข้างดี
เข้าใจได้ว่า CLI บางตัวอย่าง
awsมีขนาดใหญ่มากจนต้องมีการซ้อนระดับ แต่การต้องค่อย ๆ เจาะเข้าไปตาม nested CLI นั้นน่าหงุดหงิดจริง ๆสำหรับแอปส่วนใหญ่ การแสดงตัวเลือกทั้งหมดใน help แล้วให้ฉันใช้
lessหาเฉพาะสิ่งที่ต้องการยังดีกว่าTUI นี้เป็น UI/UX ล้วน ๆ ที่จะใช้หรือไม่ใช้ก็ได้ และจะส่งค่าตั้งค่าที่เลือกไปยังไฟล์สร้างแยกต่างหากหรือฟังก์ชันหนึ่งตัว
ไฟล์หรือฟังก์ชันนั้นต้องเรียกใช้จากเทอร์มินัลได้โดยตรงเสมอ และมีตัวเลือกกับคำอธิบายช่วยเหลือทั้งหมดที่ TUI ใช้อยู่ด้วย
ดังนั้นจึงใช้ทำสคริปต์ได้ด้วย และมีประโยชน์กับผู้ใช้หลายระดับความชำนาญ
ถ้ารู้อยู่แล้วว่าต้องใช้คำสั่งย่อยไหน การลดตัวเลือกให้เหลือเฉพาะที่เกี่ยวข้องก็มีประโยชน์มาก แต่ถ้าไม่รู้ก็อาจทรมานได้
การไล่
grepหาในรายการตัวเลือกยาว ๆ ก็ลำบากเหมือนกัน จึงต้องมีความสมดุลmanเหรอ?--helpของคำสั่งย่อยเข้าไปไว้ในคำสั่งหลัก อาจช่วยได้เช่น
git stash --helpแสดงคู่มือgit stashเต็ม ๆ พร้อมแต่ละรายการที่มีอยู่คำอธิบายที่ว่า “ตามธรรมเนียมแล้ว คำสั่ง UNIX ถูกเขียนขึ้นโดยสมมติว่าจะให้โปรแกรมอื่นนำไปใช้เป็นหลัก” นั้นไม่แม่นยำ
แต่เดิมมันตั้งใจให้ใช้แบบ โต้ตอบ ภายใน login shell เป็นหลัก
มีทั้งโปรแกรมที่สร้างผลลัพธ์ออกทาง stdout (
ls,cat,find,tty,who,date) และ text filter แบบเงียบ ๆ (tr,grep,cut,uniq,sort,wc) และในยุคนั้นงานคอมพิวเตอร์พื้นฐานก็ทำได้ด้วยคำสั่งบรรทัดเดียวโปรแกรมที่ซับซ้อนจะเขียนด้วย C และหลังจากมีภาษาเฉพาะทางอย่าง
sedกับ AWK เกิดขึ้น โปรแกรมบางส่วนที่เน้นประมวลผลสตริงมาก ๆ ก็ย้ายมาอยู่บนเชลล์เชลล์ไม่ใช่สภาพแวดล้อมการเขียนโปรแกรมที่ปกติ และก็ไม่เคยถูกตั้งใจให้เป็นแบบนั้น
ทุกวันนี้หลายกรณีอาจเหมาะกับ Python หรือ Lua มากกว่า แต่เชลล์กับ C ยังคงเป็นสิ่งที่มีติดตั้งอยู่แพร่หลายที่สุด
การแยกออกเป็น
sh,bash,ksh,cshก็เป็นตัวอย่างที่ดีการมองชุดเครื่องมือมาตรฐาน POSIX ว่าเป็น standard library ของภาษาเชลล์หลายตัวก็ถือว่าสมเหตุสมผลพอสมควร
ปัจจุบัน command line ของ Unix นั้นด้านหนึ่ง “มีประโยชน์มหาศาล” แต่อีกด้านก็ พังในเชิงการออกแบบ
แค่ลองคิดว่าถ้าจะเขียนสิ่งนี้ด้วย C หรือ Rust จะใช้เวลานานแค่ไหน ก็เห็นชัดแล้วว่ามันมีประโยชน์:
curl -sS https://go.dev/doc/devel/release | html2text | grep -o -P '\bgo\d+\.\d+\.\d+\b' | sort -V | uniq | tail -1แต่ถ้าสงสัยว่าทำไมถึงพังในเชิงการออกแบบ ให้ดู https://news.ycombinator.com/item?id=29747034
ปัญหาคือ command line interface ต้อง อ่านง่ายสำหรับคน และในเวลาเดียวกันก็ต้องอ่านง่ายสำหรับเครื่องด้วย ซึ่งไม่มีวิธีมาตรฐานในการแก้ปัญหานี้
สำหรับปัญหาที่ต้องให้ทั้งคนและเครื่องอ่านได้พร้อมกัน เดิมทีอาจมีทางออกแบบมาตรฐานอยู่ก็ได้
Apple สร้าง Human Interface Guidelines เพื่อทำให้ความหมายของนามธรรมเชิงภาพในเดสก์ท็อป UI ชัดเจนขึ้น
แต่ command line ไม่ได้ถูกสร้างโดยคนที่คิดแบบนักออกแบบของ Apple มันถูกสร้างโดยคนที่คิดว่า “ความขี้เกียจ ความใจร้อน และความหยิ่งผยองเป็นคุณธรรม แล้วฉันจะเขียนสิ่งที่ต้องการด้วยโค้ดให้น้อยที่สุดได้อย่างไร”
ในยุคนั้นมันก็ไม่ใช่ทางเลือกที่ผิด และทุกไบต์มีความสำคัญ แต่การตัดสินใจด้านการออกแบบนั้นตอนนี้ถูกฝังแน่นอยู่ใน toolchain ที่ขยับไม่ได้แล้ว
ถ้าจะให้ได้อะไรที่ดีกว่าปัจจุบัน ก็ดูเหมือนต้องยอมทิ้ง POSIX toolchain ไปแทบทั้งหมด และยากจะจินตนาการถึงการสร้าง UX ที่ค้นพบได้ง่ายและสอดคล้องกันทางแนวคิดบนฐานปัจจุบัน
แม้ในเวลาเดียวกันก็ยังมีหลายวิธีที่จะทำให้มีเอาต์พุตต่างกันสำหรับแต่ละฝ่าย
เพียงแต่ต้องมีมาตรฐานที่ทุกคนทำตาม และตรงนั้นเองที่เรื่องเริ่มซับซ้อน
ถ้าปัญหาอยู่ที่แทบไม่มี
lsตัวไหนเปิดให้ปิดท้ายชื่อไฟล์ด้วยอักขระ NUL แทนการขึ้นบรรทัดใหม่ได้ วิธีแก้ก็ดูชัดเจนเสียจนเหมือนกำลังพลาดอะไรบางอย่างอินเทอร์เฟซของเชลล์ปฏิเสธเรื่องนี้ด้วยเหตุผลอะไรหรือ?
--jsonมีคนบอกว่า “อย่าอ่านค่าความลับจากตัวแปรสภาพแวดล้อม” พร้อมเสนอให้ใช้ไฟล์ข้อมูลรับรอง, pipe, AF_UNIX socket, บริการจัดการความลับ และการสื่อสารระหว่างโปรเซสแบบอื่น ๆ เลยสงสัยว่าในบรรดานี้อะไรสะดวกและพกพาได้ดีที่สุด
และก็สงสัยด้วยว่าบริการจัดการความลับนั้นใช้กันเฉพาะในงานหรือใช้กับโปรเจกต์ส่วนตัวด้วยหรือไม่
มีบทความที่เขียนลงลึกขึ้นอีกหน่อยเรื่องการจัดการค่าความลับบนบรรทัดคำสั่งอยู่ที่ https://smallstep.com/blog/command-line-secrets/
ไฟล์ข้อมูลรับรองเป็นตัวเลือกที่เรียบง่ายและพกพาได้ดี
ไฟล์มีโมเดลสิทธิ์อยู่แล้ว และไม่ต้องพึ่งบริการภายนอกหรือ API แบบปิด
ถ้าโปรแกรมรับไฟล์ข้อมูลรับรอง ก็จะเข้ากันได้กับ systemd credentials ด้วย
systemd credentials ปลอดภัยกว่าไฟล์ข้อมูลรับรองที่ไม่ได้เข้ารหัส, สามารถเข้ารหัสได้ และยังผูกกับ TPM ได้ด้วย แต่ซอฟต์แวร์ที่ใช้ข้อมูลรับรองไม่จำเป็นต้องรองรับ TPM เอง
ตัวอย่างเช่น mbsync(https://isync.sourceforge.io/mbsync.html) มีวิธีให้รหัสผ่านสำหรับยืนยันตัวตน IMAP อยู่ราว ๆ สามแบบ
ถ้าไม่ตั้งรหัสผ่านไว้ ก็จะมีพรอมป์ต์ถามตอนรัน, จะใส่รหัสผ่านแบบข้อความล้วนไว้ในไฟล์ตั้งค่าก็ได้ แต่ไม่สะดวกต่อการแชร์
อีกทางคือสามารถตั้งคำสั่งสำหรับดึงรหัสผ่านได้ ทำให้มอบหมายงานนี้ให้ตัวจัดการรหัสผ่านอย่าง pass(1)(https://www.passwordstore.org/) หรือพรอมป์ต์กราฟิกแบบโต้ตอบจัดการแทนได้
เอกสารทำไว้ดี จึงตั้งค่าได้ง่ายโดยแทบไม่ต้องต่อเติมอะไรเอง
ตัวจัดการความลับอย่าง Doppler(https://Doppler.com) หรือ AWS Secrets Manager(https://aws.amazon.com/secrets-manager/) มีข้อดีตรงที่เก็บปกป้องค่าความลับไว้ในที่ปลอดภัยและลดการเปิดเผยให้น้อยที่สุด
แม้แต่กับนักพัฒนาภายในเองก็ยังช่วยลดการเข้าถึงได้ จึงช่วยป้องกันข้อมูลรั่วไหลที่หลีกเลี่ยงได้ง่าย
การรั่วไหลลักษณะนี้อาจทำให้ทั้งบริษัทตกอยู่ในความเสี่ยง และกำลังเกิดบ่อยขึ้นเรื่อย ๆ
บทความที่เกี่ยวข้อง: Command Line Interface Guidelines - https://news.ycombinator.com/item?id=38053692 - ตุลาคม 2023, ความคิดเห็น 1 รายการ
Command Line Interface Guidelines - https://news.ycombinator.com/item?id=31651161 - มิถุนายน 2022, ความคิดเห็น 1 รายการ
Command Line Interface Guidelines - https://news.ycombinator.com/item?id=25492119 - ธันวาคม 2020, ความคิดเห็น 5 รายการ
น่าหงุดหงิดมากที่บางเทอร์มินัลรันคำสั่งให้อัตโนมัติถ้าสตริงที่วางจากคลิปบอร์ดลงท้ายด้วยอักขระขึ้นบรรทัดใหม่
โดยส่วนตัวคิดว่า command-line interface ไม่ควรทำแบบนั้น
bind 'set enable-bracketed-paste on'ได้ถ้ารำคาญก็ปิดได้
ถ้าจำเป็นจะวางหลายบรรทัดก็ได้ และจะรันก็ต่อเมื่อกด Enter
Fish shell มักมีค่าเริ่มต้นที่สมเหตุสมผล และนอกจากพรอมป์ต์แล้วก็ยังไม่ค่อยเจอจุดที่อยากปรับแต่งเป็นพิเศษ
ส่วนใหญ่ที่ฉันเคยใช้มีตัวเลือกให้ลบอักขระขึ้นบรรทัดใหม่ออกจากเนื้อหาในคลิปบอร์ด
#ก่อนแล้วค่อยวางต่อให้ขึ้นบรรทัดใหม่ถูกตีความ ทั้งบรรทัดก็จะกลายเป็นคอมเมนต์จึงปลอดภัย และก่อนรันจริงค่อยลบ
#ที่ต้นบรรทัดออก