2 คะแนน โดย GN⁺ 2024-02-07 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • คู่มือนี้คือแนวทางการออกแบบโอเพนซอร์สสำหรับสร้าง 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 ที่ดูน่ากลัวเป็นเอาต์พุตปริยาย
  • ความเข้าอกเข้าใจผู้ใช้ เป็นสิ่งสำคัญที่จะทำให้ผู้ใช้รู้สึกว่าซอฟต์แวร์อยู่ข้างตน
    • ไม่ใช่การใส่อีโมจิหรือทำให้เหมือนเกม แต่คือการออกแบบอย่างใส่ใจเพื่อให้ผู้ใช้ประสบความสำเร็จ
  • ระบบนิเวศของเทอร์มินัลมีทั้งความไม่สอดคล้องและความสับสนมากมาย แต่ข้อจำกัดที่ต่ำก็เปิดทางให้เกิดนวัตกรรมใหม่ได้
    • ควรยึดตามแพตเทิร์นเดิม แต่ก็สามารถละทิ้งมาตรฐานที่บั่นทอนประสิทธิภาพหรือความพึงพอใจของผู้ใช้ได้อย่างตั้งใจ

กฎพื้นฐานที่ต้องปฏิบัติตาม

  • หากเป็นไปได้ ควรใช้ ไลบรารีสำหรับพาร์สอาร์กิวเมนต์บรรทัดคำสั่ง
    • ช่วยจัดการอาร์กิวเมนต์ แฟลก คำสั่งช่วยเหลือ และคำแนะนำการสะกดอย่างสม่ำเสมอ
    • ตัวอย่างได้แก่ docopt, Cobra, Click, Typer, clap, swift-argument-parser
  • เมื่อสำเร็จควรคืน 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 ด้วย
  • เมื่อสำเร็จควรมีผลลัพธ์ แต่ให้สั้นเข้าไว้
    • คำสั่ง 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_COLOR
    • DEBUG
    • EDITOR
    • HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY
    • SHELL
    • TERM, TERMINFO, TERMCAP
    • TMPDIR
    • HOME
    • PAGER
    • LINES, COLUMNS
  • ในกรณีที่เหมาะสม อาจอ่านตัวแปรสภาพแวดล้อมจาก .env ได้
    • มีประโยชน์สำหรับตัวแปรที่แทบไม่เปลี่ยนระหว่างการทำงานในไดเรกทอรีหนึ่ง ๆ
  • .env ไม่ใช่สิ่งทดแทนไฟล์ตั้งค่าอย่างเป็นทางการ
    • มักไม่ถูกเก็บไว้ในระบบจัดการซอร์สโค้ด
    • มีเพียงชนิดสตริงเท่านั้น
    • มีแนวโน้มจะจัดระเบียบได้ไม่ดี
    • มีโอกาสเกิดปัญหาเรื่อง encoding ได้ง่าย
    • มีแนวโน้มจะใส่ข้อมูลรับรองและข้อมูลคีย์ที่อ่อนไหวไว้
  • ไม่ควรอ่านค่าความลับจากตัวแปรสภาพแวดล้อม
    • ตัวแปรสภาพแวดล้อมอาจถูกเปิดเผยผ่านโปรเซส ล็อก docker inspect, systemctl show เป็นต้น
    • ควรรับค่าความลับผ่านไฟล์ข้อมูลรับรอง, pipe, ซ็อกเก็ต AF_UNIX, บริการจัดการความลับ หรือวิธี IPC อื่น

ชื่อ การแจกจ่าย และการเก็บข้อมูลวิเคราะห์

  • ชื่อโปรแกรม CLI ควรเรียบง่ายและจำง่าย เพราะผู้ใช้ต้องพิมพ์บ่อย
    • หากทั่วไปเกินไป อาจชนกับคำสั่งอื่นหรือทำให้ผู้ใช้สับสนได้
  • ชื่อควรใช้ตัวพิมพ์เล็กและขีดกลางเท่าที่จำเป็น
    • curl เป็นชื่อที่ดี และ DownloadURL เป็นตัวอย่างที่ไม่ดี
  • ชื่อควรสั้น แต่ชื่อที่สั้นเกินไปเหมาะกับยูทิลิตีที่ใช้กันแพร่หลายมากอย่าง cd, ls, ps
  • ควรเป็นชื่อที่พิมพ์ง่าย
    • มีกรณีที่ชื่อเริ่มต้นของ Docker Compose คือ plum แต่เปลี่ยนเป็น fig เพราะพิมพ์ด้วยมือเดียวได้ไม่ถนัด
  • หากเป็นไปได้ ควรแจกจ่ายเป็นไบนารีเดียว
    • หากทำไบนารีเดียวยาก ก็ควรใช้ตัวติดตั้งแพ็กเกจมาตรฐานของแพลตฟอร์มเพื่อให้ถอนการติดตั้งได้ง่าย
    • เครื่องมือเฉพาะภาษา เช่น code linter อาจสมมติได้ว่าผู้ใช้มีอินเทอร์พรีเตอร์ของภาษานั้นอยู่แล้ว
  • ควรถอนการติดตั้งได้ง่าย
    • เพราะมักมีกรณีที่ผู้ใช้ต้องการถอนการติดตั้งทันทีหลังติดตั้ง จึงควรวางคำแนะนำการถอนการติดตั้งไว้ใต้คำแนะนำการติดตั้ง
  • ตัวชี้วัดการใช้งานอาจช่วยปรับปรุงเครื่องมือได้ แต่ผู้ใช้ CLI คาดหวังว่าจะควบคุมสภาพแวดล้อมของตนเองได้
  • ไม่ควรส่งข้อมูลการใช้งานหรือข้อมูลแครชโดยไม่ได้รับความยินยอม
    • ควรระบุให้ชัดว่ากำลังเก็บอะไร เก็บไปทำไม ทำให้ไม่ระบุตัวตนมากน้อยเพียงใด ทำอย่างไรจึงไม่ระบุตัวตน และเก็บไว้นานเท่าใด
    • โดยอุดมคติควรเป็นแบบ opt-in และหากเลือกเก็บแบบค่าเริ่มต้นแล้วให้ปิดเองภายหลังได้แบบ opt-out ก็ควรแจ้งให้ชัดเจนบนเว็บไซต์หรือตอนรันครั้งแรก และต้องปิดได้ง่าย
  • อาจพิจารณาทางเลือกอื่นแทนการเก็บข้อมูลวิเคราะห์
    • วัดผลจากเอกสารบนเว็บ
    • วัดผลจากการดาวน์โหลด
    • พูดคุยกับผู้ใช้โดยตรง และส่งเสริมให้มีฟีดแบ็กกับคำขอฟีเจอร์ผ่านเอกสารและรีโพซิทอรี

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

 
GN⁺ 2024-02-07
ความคิดเห็นจาก Hacker News
  • คำพูดที่ว่า “ทุกวันนี้คนส่วนใหญ่ไม่รู้ด้วยซ้ำว่าคำสั่งบรรทัดคำสั่งคืออะไร” ก็จริง แต่แม้แต่ในยุค ทศวรรษ 1980 ที่บทความต้นฉบับใช้อ้างอิงก็เป็นแบบนั้นเหมือนกัน
    ความต่างคือ ตอนนี้คนที่รู้จักและใช้งานบรรทัดคำสั่งได้มีจำนวนมากที่สุดในประวัติศาสตร์ และเพิ่มขึ้นอย่างน้อยระดับหลักเดียวของเท่า ๆ กัน หรืออาจถึงหลักสองเท่า จึงพอจะเรียกได้ว่าเป็น ยุคทองของ CLI

    • ตอนนี้กำลังแยกบางส่วนของแอปออกมาเป็น เครื่องมือบรรทัดคำสั่ง เพื่อสลายโมโนลิท
      สถานะส่วนกลางและ dependency ทำให้การอนุมานยากขึ้น และสุดท้ายก็ทำให้การดีบักกับการปรับจูนประสิทธิภาพยากขึ้นด้วย
      ถ้าแยกโค้ด 500 บรรทัด, 1,000 บรรทัด, 5,000 บรรทัด, 10,000 บรรทัด, บางครั้งถึง 50,000 บรรทัด ให้ออกมารันแยกได้ หลายอย่างจะชัดเจนขึ้นมาก
      ถ้าทำดี ๆ ยังช่วยพาไปสู่โครงสร้างแบบ Functional Core, Imperative Shell ได้ด้วย เพราะบรรทัดคำสั่งที่สอดคล้องกับปรัชญา Unix ควรมีการทำงานแบบไม่มีผลข้างเคียงเป็นส่วนใหญ่ และมีการทำงานที่มีผลข้างเคียงให้น้อย
      คุณสามารถสร้างคำสั่งเล็ก ๆ ที่สร้างและแสดงสถานะของระบบก่อนหน้าที่จะมีการตัดสินใจผิดพลาดออกมาได้ และยังรันใน production ได้อย่างแทบจะปลอดภัย
      แบบนี้ก็ทำให้ส่งต่อเครื่องมือเหล่านี้ให้คนที่ควรถูกนับอยู่ใน bus factor เข้ามามีส่วนร่วมได้ง่ายขึ้น
    • ถ้าเปลี่ยนคำว่า “คน” เป็น “ผู้ใช้คอมพิวเตอร์” ใจความของผู้เขียนก็ถูกต้อง
      คนในหมู่บ้านอารยธรรมกลางป่าอเมซอนจะคิดอย่างไรกับเทอร์มินัลนั้นไม่เกี่ยวข้อง
    • ต้องแยก จำนวนสัมบูรณ์กับสัดส่วน ออกจากกัน
      ถ้าจะตัดสินการเปลี่ยนแปลงเชิงคุณภาพของสิ่งที่มีจำนวนเพิ่มขึ้น ต้องดูสัดส่วน ไม่ใช่ดูแค่จำนวนสัมบูรณ์ ไม่อย่างนั้นจะได้ประโยคที่จริงแต่ไม่มีความหมาย
      เช่น ทุกวันนี้จำนวนผู้ฟังแจ๊สแบบสัมบูรณ์อาจมากกว่าช่วงพีกทางวัฒนธรรมของแนวเพลงนี้ แต่ไม่ได้แปลว่าแจ๊สได้รับความนิยมมากกว่า แค่จำนวนคนที่ฟังเพลงโดยรวมเพิ่มขึ้น
      ถึงอย่างนั้นก็ยากจะบอกว่าแจ๊สในสหรัฐสำคัญและทรงอิทธิพลกว่ายุครุ่งเรืองของมัน
    • ประเด็นสำคัญคือ ในบรรดาผู้ใช้คอมพิวเตอร์ยุคทศวรรษ 1980 ถ้ามองเป็นสัดส่วนแล้วเป็นแบบนั้นด้วยหรือไม่
  • อยากให้พิจารณาใส่ ออปชัน --dry-run ที่แสดงล่วงหน้าว่าจะเกิดอะไรขึ้นโดยไม่เปลี่ยนแปลงจริง
    มันมีประโยชน์มากสำหรับตอนเรียนรู้เครื่องมือ หรือก่อนรันงานที่ย้อนกลับยาก เพื่อเช็กว่าใส่ออปชันซับซ้อนและ file glob ถูกต้องหรือไม่

    • ถ้าคำสั่งนั้นย้อนกลับไม่ได้และมีผลข้างเคียงมาก ควรตั้งค่าเริ่มต้นเป็น --dry-run และต้องใส่แฟลก --execute เมื่อต้องการรันจริง
    • WhatIf ของ PowerShell เป็นหนึ่งในฟีเจอร์ที่ชอบที่สุด
      ไม่รู้เหมือนกันว่าถ้าไม่มีมันจะทำยังไง และมันช่วยไว้หลายครั้งจากสถานการณ์ที่เกือบพังหมดจริง ๆ
    • ในทางกลับกัน ผมชอบให้พฤติกรรมเริ่มต้นไม่ทำการเปลี่ยนแปลงจริง และถ้าจะรันจริงค่อยส่ง --commit เข้าไป
      สำหรับสคริปต์ที่ทำงานซึ่งย้อนกลับไม่ได้หรือย้อนกลับยาก วิธีนี้รู้สึกปลอดภัยกว่า
    • อยากเห็นตัวอย่างเครื่องมือบรรทัดคำสั่งที่มี dry run flag
      ผมสับสนว่าจะออกแบบมันอย่างไรในเครื่องมือที่กำลังทำอยู่
      ถ้าต้องเรียก API เพื่อดึงข้อมูล จะทำให้เป็น dry run ได้อย่างไร
      ต้องให้ตัวอย่างปลอมกับผลลัพธ์ปลอมหรือ?
    • ผมคิดว่า dry-run ควรเป็นฟังก์ชันที่ต่อ pipe ได้กับทุกคำสั่ง: 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 ออกทำให้พฤติกรรมเปลี่ยนจนดีบักยาก

    • ถ้าจะปรับกฎ “stdout คือผลลัพธ์ที่มีประโยชน์” ให้แม่นขึ้นอีกหน่อย ก็คือ stdout ควรเป็นสิ่งที่ผู้ใช้ร้องขอ
      ถ้าผู้ใช้ขอ --help ก็ควรไปที่ stdout และถ้าผู้ใช้ขอ log log ก็ต้องไปที่ stdout
      ถ้าเป็นข้อมูลที่ไม่ได้ร้องขอ stderr คือที่ที่เหมาะสม
    • เป็นกฎที่ดี แต่ชื่อมันชวนให้เข้าใจผิด
      ถ้าย้อนเวลาได้ น่าจะตั้งชื่อเป็น stdin, stdout, stdext แบบนี้ คนอาจทำตามธรรมเนียมนี้มากกว่า
      แต่เพราะมันชื่อ stderr นักพัฒนาจึงคิดอย่างสมเหตุสมผลว่า “เอาไว้รายงานข้อผิดพลาด” และถ้าไม่ใช่ข้อผิดพลาดก็โยนไป stdout
      ถึงอย่างนั้นในเชิงโครงสร้างโปรแกรม วิธีนี้ดีกว่า และแม้ตอนแรกจะสับสนกว่า แต่ก็ได้ประโยชน์เพราะเอา output ไปทำสิ่งที่มีประโยชน์มากขึ้น
    • ถ้าอย่างนั้นก็น่าสงสัยว่าควรแสดงแอนิเมชันที่ไหน
      ผมก็ไม่ค่อยคิดว่าสีจะนับเป็นข้อมูลเหมือนกัน
  • อยากให้เลี่ยงคำแนะนำแบบ “ใช้สัญลักษณ์และอีโมจิในที่ที่ช่วยให้ชัดเจนขึ้น” จริง ๆ
    yubikey-agent ที่ยกมาเป็นตัวอย่างแสดงให้เห็นตรง ๆ ถึงสิ่งที่ผมไม่ชอบใน GitHub README และส่วนติดต่อผู้ใช้แบบขี้เล่น
    ในเชิงเทคนิค สัญลักษณ์และอีโมจิอาจถูกเรนเดอร์ต่างกันไปในแต่ละเทอร์มินัล จนทำให้ข้อความชวนสับสน
    ในเชิงสุนทรียะ ระดับความยอมรับต่อความขี้เล่นและความสดใสของแต่ละคนก็ต่างกันมาก จึงควรใช้อย่างยับยั้งมาก ๆ และใช้เฉพาะเมื่อรู้จริง ๆ ว่ากำลังทำอะไร

    • ผมชอบ output แบบนั้น
      ชอบเอาต์พุตเทอร์มินัลที่มีสี และอีโมจิก็มีสีด้วย เลยช่วยให้เห็นภาพรวมของสถานการณ์ได้เร็ว
      ผมก็ชอบ syntax highlighting และถ้าไม่มีจะรู้สึกว่าอ่านโค้ดยากขึ้นมาก
      ไม่ใช่ทุกคนจะเป็นแบบนั้น และในเมื่อผมก็ไม่ได้คาดหวังให้รสนิยมของตัวเองเป็นค่าเริ่มต้น ก็ไม่ควรบังคับรสนิยมตรงข้ามให้เป็นค่าเริ่มต้นเหมือนกัน
    • ถ้าเป็น ฟีเจอร์แบบเลือกเปิดได้ ก็ดี
      บางคนชอบ บางคนไม่ชอบ ดังนั้นปิดไว้เป็นค่าเริ่มต้นแต่เปิดให้ผู้ใช้เลือกเองก็พอ
    • ในเชิงแนวทาง ควรจำกัด คลังคำอีโมจิ ที่ใช้ใน output ไว้ไม่เกินสองตัว
      เช่น มีตัวหนึ่งแทนความสำเร็จ อีกตัวหนึ่งแทนความล้มเหลวก็พอ
      และข้อมูลที่อีโมจิสื่อจะต้องมีข้อความกำกับซ้ำด้วยเสมอ
    • เห็นด้วยอย่างมาก
      ตอนที่เห็น CLI Guidelines ครั้งแรก ผมก็หยุดอ่านตรงส่วนนั้นเลย
      รอบนี้กลับมาอ่านส่วนที่เหลือด้วย และโดยรวมก็ถือว่าค่อนข้างดี
  • เข้าใจได้ว่า CLI บางตัวอย่าง aws มีขนาดใหญ่มากจนต้องมีการซ้อนระดับ แต่การต้องค่อย ๆ เจาะเข้าไปตาม nested CLI นั้นน่าหงุดหงิดจริง ๆ
    สำหรับแอปส่วนใหญ่ การแสดงตัวเลือกทั้งหมดใน help แล้วให้ฉันใช้ less หาเฉพาะสิ่งที่ต้องการยังดีกว่า

    • ฉันทำแอปแบบ TUI อยู่เยอะ และจะใส่ TUI frontend ที่ดีให้ทุกแอป เพื่อให้คนที่ไม่ค่อยสายเทคนิค เช่น QA ใช้งานได้
      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 เกิดขึ้น โปรแกรมบางส่วนที่เน้นประมวลผลสตริงมาก ๆ ก็ย้ายมาอยู่บนเชลล์
    เชลล์ไม่ใช่สภาพแวดล้อมการเขียนโปรแกรมที่ปกติ และก็ไม่เคยถูกตั้งใจให้เป็นแบบนั้น

    • บางครั้งโปรแกรม C ยาว 10,000 บรรทัดอาจทำได้ด้วยเชลล์ 10 บรรทัด และบางครั้งโปรแกรมเชลล์ 1,000 บรรทัดอาจควรเป็น C แค่ 100 บรรทัด
      ทุกวันนี้หลายกรณีอาจเหมาะกับ Python หรือ Lua มากกว่า แต่เชลล์กับ C ยังคงเป็นสิ่งที่มีติดตั้งอยู่แพร่หลายที่สุด
    • จะเรียกว่าปกติหรือไม่ก็ตาม เชลล์ก็เป็น ภาษาโปรแกรม และใน Unix ยุคแรกจุดนี้ยิ่งเด่นชัดมาก
      การแยกออกเป็น 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, บริการจัดการความลับ และการสื่อสารระหว่างโปรเซสแบบอื่น ๆ เลยสงสัยว่าในบรรดานี้อะไรสะดวกและพกพาได้ดีที่สุด
    และก็สงสัยด้วยว่าบริการจัดการความลับนั้นใช้กันเฉพาะในงานหรือใช้กับโปรเจกต์ส่วนตัวด้วยหรือไม่

    • ฉันเป็นหนึ่งในผู้เขียน CLI Guidelines
      มีบทความที่เขียนลงลึกขึ้นอีกหน่อยเรื่องการจัดการค่าความลับบนบรรทัดคำสั่งอยู่ที่ 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 ไม่ควรทำแบบนั้น

    • ใน Bash ใช้ bind 'set enable-bracketed-paste on' ได้
    • iTerm บน macOS จะขอให้ยืนยันถ้าคุณพยายามวางสตริงที่มีอักขระขึ้นบรรทัดใหม่
      ถ้ารำคาญก็ปิดได้
    • Fish shell โดยปกติจะทำงานตามที่คาดไว้ คือแค่แทรกข้อความเข้าไป และให้แก้ไขคำสั่งได้ก่อนรัน
      ถ้าจำเป็นจะวางหลายบรรทัดก็ได้ และจะรันก็ต่อเมื่อกด Enter
      Fish shell มักมีค่าเริ่มต้นที่สมเหตุสมผล และนอกจากพรอมป์ต์แล้วก็ยังไม่ค่อยเจอจุดที่อยากปรับแต่งเป็นพิเศษ
    • ลองใช้ตัวจัดการคลิปบอร์ดดูก็ได้
      ส่วนใหญ่ที่ฉันเคยใช้มีตัวเลือกให้ลบอักขระขึ้นบรรทัดใหม่ออกจากเนื้อหาในคลิปบอร์ด
    • ถ้ารู้ว่าจะมีขึ้นบรรทัดใหม่ไม่เกินหนึ่งตัว ฉันจะพิมพ์ # ก่อนแล้วค่อยวาง
      ต่อให้ขึ้นบรรทัดใหม่ถูกตีความ ทั้งบรรทัดก็จะกลายเป็นคอมเมนต์จึงปลอดภัย และก่อนรันจริงค่อยลบ # ที่ต้นบรรทัดออก