24 คะแนน โดย xguru 2020-12-28 | 3 ความคิดเห็น | แชร์ทาง WhatsApp

แนวทางโอเพนซอร์สที่อัปเดตให้ทันสมัย โดยยังคงยึดตามหลักการ Unix แบบดั้งเดิม

  • ปรัชญาการออกแบบ CLI

→ ให้ความสำคัญกับคนเป็นอันดับแรก

→ องค์ประกอบย่อยที่เรียบง่ายและทำงานร่วมกันได้

→ รักษาความสม่ำเสมอระหว่างโปรแกรม

→ พูดเท่าที่จำเป็น (เอาต์พุตไม่น้อยหรือมากเกินไป)

→ ทำให้ค้นพบได้ง่าย (มี help ที่ครอบคลุม, ตัวอย่าง, แนะนำคำสั่งถัดไปที่ควรรัน, แนะนำสิ่งที่ควรทำเมื่อเกิดข้อผิดพลาด)

→ เหมือนการสนทนาทั่วไป

→ แข็งแรงทนทาน

→ เห็นอกเห็นใจผู้ใช้

→ ความสับสน: หากต้องฝ่าฝืนกฎ ให้ระบุเจตนาและจุดประสงค์ให้ชัดเจน

  • แนวทาง CLI

→ พื้นฐาน

✓ ควรใช้ไลบรารีสำหรับ parsing บรรทัดคำสั่ง: Go(Cobra,cli), Node(oclif), Python (Click,Typer), Ruby(TTY)

✓ เมื่อสำเร็จให้คืนค่า 0, เมื่อผิดพลาดให้คืนค่าอื่นที่ไม่ใช่ 0

✓ เอาต์พุตให้ส่งไปที่ stdout

✓ log, error และข้อความอื่น ๆ ให้ส่งไปที่ stderr

→ help

✓ หากรันโดยไม่ระบุ option ให้แสดง help (-h, --help)

✓ โดยปกติให้แสดง help แบบกระชับ

· โปรแกรมนี้ทำอะไร

· ตัวอย่างการเรียกใช้หนึ่งหรือสองแบบ

· คำอธิบายของ flag (ถ้ามีไม่มาก)

· --help สำหรับคำอธิบายเพิ่มเติม

✓ เมื่อใช้ option -h, --help ให้แสดง help แบบเต็ม

✓ ระบุช่องทางสำหรับรับ feedback/issue

✓ ใน help ควรมีลิงก์ไปยังเอกสารเวอร์ชันเว็บ

✓ อธิบายด้วยตัวอย่าง

✓ ถ้ามีตัวอย่างมาก ควรนำไปไว้ที่อื่น (cheatsheet หรือเว็บเพจ)

✓ ไม่ต้องใส่ใจกับ man page มากนัก (คนใช้ไม่มาก และยังใช้บน Windows ไม่ได้)

✓ ถ้า help ยาว ให้ pipe ผ่าน pager

✓ แสดง flag และคำสั่งที่ใช้บ่อยที่สุดไว้ตอนต้นของ help

✓ ใช้ formatting ใน help (เช่น ตัวหนา)

✓ ถ้าผู้ใช้ทำอะไรผิด และคุณพอเดาได้ว่าเขาหมายถึงอะไร ให้แนะนำสิ่งนั้น

✓ หากคำสั่งของคุณคาดหวังว่าจะรับบางอย่างผ่าน pipe แต่ stdin เป็น interactive terminal ให้แสดง help แล้วจบทันที

→ เอาต์พุต

✓ เอาต์พุตแบบ Human-readable (อ่านได้โดยมนุษย์) สำคัญที่สุด

✓ หากไม่กระทบต่อการใช้งาน ควรมีเอาต์พุตแบบ machine-readable ด้วย

✓ หากความเป็น human-readable ทำให้ machine-readable เป็นไปไม่ได้ ควรมี option --plain เพื่อให้ใช้งานร่วมกับ grep / awk เป็นต้นได้

✓ เมื่อรับ --json ให้แสดงผลเป็นรูปแบบ JSON

✓ เมื่อสำเร็จ เอาต์พุตควรไม่มีจะดีที่สุด แต่ถ้าจำเป็นต้องมี ให้กระชับ และรองรับ option -q เพื่อตัดเอาต์พุตที่ไม่จำเป็น

✓ หากมีการเปลี่ยนสถานะ ให้บอกผู้ใช้ (ดูเอาต์พุตของ git push)

✓ ทำให้ดูสถานะปัจจุบันของระบบได้ง่าย

✓ แนะนำคำสั่งที่ผู้ใช้สามารถรันต่อได้ (เหมือนที่ git status แสดง git add / restore)

✓ การกระทำที่ออกไปนอกขอบเขตภายในของโปรแกรมต้องชัดเจน เช่น อ่านหรือเขียนไฟล์ที่ผู้ใช้ไม่ได้สั่ง (cache) หรือเชื่อมต่อกับเซิร์ฟเวอร์ระยะไกล (ดาวน์โหลดไฟล์)

✓ ใช้ ASCII art เพื่อเพิ่มความหนาแน่นของข้อมูล

✓ ใช้สีอย่างมีเจตนา อย่าใช้พร่ำเพรื่อ

✓ หากไม่ใช่ terminal หรือผู้ใช้ร้องขอ ให้ปิดสี

✓ หาก stdout ไม่ใช่ interactive terminal อย่าแสดง animation

✓ ใช้สัญลักษณ์/อีโมจิเฉพาะเมื่อช่วยให้ชัดเจนขึ้น

✓ โดยปกติอย่าแสดงข้อมูลที่มีแต่ผู้สร้างเท่านั้นที่เข้าใจ

✓ อย่าใช้ stderr เป็นเหมือนไฟล์ log (อย่างน้อยอย่าตั้งเป็นค่าเริ่มต้น ควรแสดงระดับ log เช่น ERR, WARN เฉพาะในโหมดละเอียด)

✓ หากต้องแสดงข้อความจำนวนมาก ให้ใช้เครื่องมือแบ่งหน้าอย่าง less

→ Error

✓ จับ error แล้วเขียนข้อความใหม่ให้อ่านเข้าใจได้สำหรับคน

✓ Signal-to-noise ratio สำคัญ หาก error เดียวกันเกิดหลายครั้ง ให้รวมแสดงพร้อมหัวข้ออธิบาย

✓ ควรคิดว่าผู้ใช้จะเห็นจุดไหนก่อนเป็นอย่างแรก

✓ หากเกิดข้อผิดพลาดที่ไม่คาดคิด/อธิบายไม่ได้ ให้แสดงข้อมูล debug/trace และอธิบายวิธีส่งบั๊กนี้ให้ผู้พัฒนา

✓ ทำให้ส่ง bug report ได้โดยแทบไม่ต้องออกแรงเพิ่ม (เช่น สร้าง URL ที่รวมข้อมูลครบแล้ว และเพียงเปิดในเบราว์เซอร์ก็รายงานได้เลย)

→ Argument & Flags : อาร์กิวเมนต์และแฟลก

✓ อาร์กิวเมนต์: พารามิเตอร์ตามตำแหน่ง ลำดับสำคัญ cp bar foo กับ cp foo bar ต่างกัน

✓ แฟลก: พารามิเตอร์ที่มีชื่อ เช่น -r แบบอักษรเดียว หรือ --recursive แบบหลายอักษร โดยทั่วไปลำดับไม่สำคัญ

อาจรวมค่าของผู้ใช้ไว้ด้วย เช่น --file foo.txt หรือ --file=foo.txt

✓ ควรเลือกใช้แฟลกมากกว่าอาร์กิวเมนต์ แม้จะต้องพิมพ์มากกว่า แต่ชัดเจนกว่า หากมีอาร์กิวเมนต์มากจะขยายความสามารถภายหลังได้ยาก

✓ ควรมีทั้งเวอร์ชันสั้นและเวอร์ชันเต็มของแฟลก หากใช้เวอร์ชันเต็มในสคริปต์ก็ไม่ต้องมีคำอธิบายเพิ่ม

✓ ใช้แฟลกแบบอักษรเดียวเฉพาะกับแฟลกที่ใช้บ่อยเป็นหลัก

✓ สำหรับการทำงานง่าย ๆ ก็อาจรับหลายอาร์กิวเมนต์ได้

✓ หากต้องมีอาร์กิวเมนต์ที่ต่างกันตั้งแต่สองตัวขึ้นไป อาจเป็นสัญญาณว่าคุณกำลังทำอะไรผิด

✓ แฟลกควรใช้ชื่อมาตรฐาน (ถ้ามีอยู่แล้ว)

-a --all , -d --debug , -f --force , -h --help , -o --output , -p --port , -q --quiet , -u --user

✓ ค่าเริ่มต้นควรเป็นสิ่งที่เหมาะกับผู้ใช้ส่วนใหญ่

✓ หากผู้ใช้ระบุอาร์กิวเมนต์/แฟลกที่ต้องรับค่า แต่ไม่ได้ส่งค่ามา ให้ถามผู้ใช้เพื่อรับค่า

✓ ควรมีวิธีส่งค่าผ่านอาร์กิวเมนต์/แฟลกเสมอ และไม่ควรบังคับให้ต้องกรอกผ่าน prompt เท่านั้น

✓ ก่อนทำสิ่งที่อันตราย ควรขอการยืนยันเสมอ

✓ หากอินพุตหรือเอาต์พุตเป็นไฟล์ ควรรองรับ - เพื่อรับจาก stdin หรือส่งออกไปยัง stdout

$ curl https://example.com/something.tar.gz | tar xvf -

✓ หากแฟลกรับค่าเพิ่มเติมได้ ควรยอมรับคำพิเศษอย่าง none ด้วย เช่น ssh -F none

✓ หากเป็นไปได้ ให้ออกแบบอาร์กิวเมนต์ แฟลก และ subcommand โดยไม่ขึ้นกับลำดับ

✓ ค่าอาร์กิวเมนต์ที่มีความอ่อนไหว (เช่น รหัสผ่าน) ควรเปิดให้ป้อนผ่านไฟล์ได้

→ Interactivity

✓ ใช้ prompt หรือฟีเจอร์ interactive เฉพาะเมื่อ stdin เป็น interactive terminal เท่านั้น

✓ หากมีการส่ง --no-input มา อย่าใช้ prompt หรือฟีเจอร์ interactive ใด ๆ

✓ เมื่อต้องรับรหัสผ่าน อย่าแสดงค่าที่ผู้ใช้พิมพ์

✓ ทำให้ผู้ใช้ออกได้ง่าย (อย่าทำแบบ vim) ให้ Ctrl-C ใช้งานได้ หากเป็นกรณีรันโปรแกรมอย่าง ssh, tmux จนใช้ Ctrl-C ไม่ได้ ให้แสดงอย่างชัดเจนว่ามี escape sequence ที่ขึ้นต้นด้วย ~ แบบเดียวกับ SSH

→ Subcommands

✓ เครื่องมือที่ซับซ้อนสามารถลดความซับซ้อนได้ด้วย subcommand

✓ นอกจากนี้ หากมีหลายเครื่องมือที่เกี่ยวข้องกันอย่างใกล้ชิด ก็สามารถรวมไว้ในคำสั่งเดียวเพื่อให้ใช้งานสะดวกขึ้นได้

✓ ควรสม่ำเสมอระหว่าง subcommand ต่าง ๆ แฟลกเดียวกันควรมีความหมายเดียวกัน และมีรูปแบบเอาต์พุตคล้ายกัน

✓ ใช้ชื่อที่สม่ำเสมอระหว่าง subcommand หลายชั้น

✓ อย่าใส่คำสั่งที่ชื่อคล้ายกันหรือทำให้สับสน เช่น update กับ upgrade

→ Robustness

✓ ตรวจสอบอินพุตของผู้ใช้ทั้งหมด เช็กให้เร็ว และแสดงข้อผิดพลาดที่เข้าใจได้

✓ ความตอบสนองสำคัญกว่าความเร็ว

✓ หากใช้เวลานาน ให้แสดงความคืบหน้า

✓ ถ้าเป็นไปได้ ให้ทำงานแบบขนาน แต่ต้องคิดให้รอบคอบ

✓ ควรมี timeout

✓ ทำให้เป็น idempotent (รันซ้ำแล้วผลลัพธ์ไม่เปลี่ยน) เพื่อให้เมื่อเกิดข้อผิดพลาด ผู้ใช้สามารถกดลูกศรขึ้นใน shell แล้วรันต่อได้จากเดิม

✓ ทำให้เป็น crash-only ซึ่งเป็นขั้นต่อไปของ idempotence หากไม่จำเป็นต้อง cleanup หลังงานเสร็จ หรือเลื่อน cleanup ไปครั้งถัดไปได้ โปรแกรมก็ควรจบได้ทันทีเมื่อเกิดความล้มเหลวหรือถูกหยุด

✓ ผู้คนจะใช้โปรแกรมของคุณผิดวิธีแน่นอน

→ Future-proofing

✓ หากทำได้ การเปลี่ยนแปลงควรเป็นแบบ additive อย่าทำลายความเข้ากันได้ด้วยการเปลี่ยนพฤติกรรมเดิม แต่ให้เพิ่มแฟลกใหม่แทน

✓ หากไม่ใช่การเปลี่ยนแบบ additive ควรเตือนก่อน

✓ การเปลี่ยนเอาต์พุตสำหรับมนุษย์ส่วนใหญ่ถือว่าใช้ได้

✓ แม้จะมี subcommand ที่คนใช้บ่อย ก็อย่าสร้าง catch-all subcommand ที่รันสิ่งนั้นให้โดยไม่ต้องระบุชัดเจน

✓ อย่าอนุญาตตัวย่อของคำสั่ง subcommand แบบตามอำเภอใจ

✓ อย่าสร้าง “ระเบิดเวลา” ที่วันหนึ่งจะหยุดทำงาน

→ Signals and control Characters

✓ หากผู้ใช้กด Ctrl-C (INT signal) ให้หยุดโดยเร็วที่สุด

✓ หากผู้ใช้กด Ctrl-C ระหว่าง cleanup ที่ใช้เวลานาน ให้เพิกเฉยก่อน และหากกดอีกครั้งจึงค่อยบังคับปิด

^CGracefully stopping... (press Ctrl+C again to force)

→ Configuration

✓ ควรทำตามสเปก XDG (X Desktop Group)

✓ หากจะแก้ไขการตั้งค่าที่ไม่ใช่ของโปรแกรมคุณเอง ให้ขอการยืนยันจากผู้ใช้ และบอกให้ชัดว่าจะทำอะไร

✓ พารามิเตอร์การตั้งค่าควรถูกใช้ตามลำดับความสำคัญ

แฟลก > ตัวแปรสภาพแวดล้อมของ shell > การตั้งค่าระดับโปรเจกต์ (.env) > การตั้งค่าผู้ใช้ > การตั้งค่าระบบ

→ Environment Variables

✓ ตัวแปรสภาพแวดล้อมมีไว้สำหรับพฤติกรรมที่เปลี่ยนไปตาม context ที่คำสั่งถูกรัน

✓ เพื่อให้พกพาได้สูงสุด ชื่อตัวแปรสภาพแวดล้อมควรมีเฉพาะตัวพิมพ์ใหญ่ ตัวเลข และขีดล่าง และต้องไม่ขึ้นต้นด้วยตัวเลข

✓ หากเป็นไปได้ ควรใช้ค่าแบบบรรทัดเดียว (single-line) สำหรับตัวแปรสภาพแวดล้อม

✓ อย่าใช้ชื่อที่เป็นที่ใช้กันอย่างกว้างขวางอยู่แล้ว

✓ หากเป็นไปได้ ควรตรวจสอบและใช้ตัวแปรสภาพแวดล้อมทั่วไป

NO_COLOR, DEBUG, EDITOR, HTTP_PROXY, SHELL, TERM, TERMINFO, HOME, TMPDIR, PAGER, LINES ..

✓ หากจำเป็น ให้โหลดตัวแปรสภาพแวดล้อมจาก .env

✓ อย่าใช้นามสกุล .env กับไฟล์ตั้งค่า

→ Naming

✓ ชื่อควรเป็นคำที่เรียบง่ายและจำง่าย

✓ ใช้ตัวพิมพ์เล็กเท่านั้น และใช้ - (dash) เท่าที่จำเป็นจริง ๆ

✓ ให้สั้นเท่าที่ทำได้

✓ พิมพ์บนคีย์บอร์ดได้ง่าย

→ Distribution

✓ หากเป็นไปได้ ให้แจกจ่ายเป็น single binary

✓ ถอนการติดตั้งได้ง่าย

→ Analytics

✓ อย่าส่งข้อมูลการใช้งานและข้อมูล crash ของเครื่องมือกลับมาหาคุณโดยไม่ได้รับความยินยอมจากผู้ใช้

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

 
jonnung 2021-01-09

ขอบคุณสำหรับเนื้อหาดี ๆ ครับ

 
xguru 2020-12-28

ดูเหมือนว่าด้วย Rust และ Go ที่ช่วยให้สร้างเป็น single binary ได้อย่างดี ทำให้มีเครื่องมือ command line ดี ๆ เพิ่มขึ้นเรื่อย ๆ

การพัฒนาก็ยิ่งสะดวกขึ้นและทรงพลังมากขึ้นเรื่อย ๆ

 
xguru 2020-12-28

ผมแปลแบบคร่าว ๆ ไปพร้อมกับได้เรียนรู้อะไรเยอะเลยเหมือนกัน พอทำเสร็จแล้วก็คิดขึ้นมาว่า.. หรือจริง ๆ น่าจะแปลทั้ง repo ไปเลยจะดีกว่า ^^;;