Hướng dẫn về giao diện dòng lệnh (2021)
(clig.dev)- Hướng dẫn này là bộ chỉ dẫn thiết kế mã nguồn mở nhằm hiện đại hóa nguyên tắc UNIX truyền thống để tạo ra CLI dễ dùng cho con người và vẫn vững chắc cho tự động hóa
- Một CLI tốt phải hoạt động theo hướng ưu tiên con người nhưng vẫn tuân thủ các quy ước như
stdout/stderr, mã thoát, pipe, JSON để có thể kết hợp tự nhiên với các chương trình khác - Trợ giúp, tài liệu, thông báo lỗi và đầu ra phải giúp người dùng biết bước tiếp theo cần làm gì, trong đó
--help, ví dụ, gợi ý, hiển thị tiến trình và mô tả trạng thái đóng vai trò cốt lõi - Đối số, cờ, tương tác, cấu hình và biến môi trường phải tính đến cả kịch bản script lẫn dùng trong terminal, và an toàn hơn nếu không nhận trực tiếp giá trị bí mật qua cờ hay biến môi trường
- Từ tên gọi, phân phối cho đến thu thập phân tích của CLI đều không được làm tổn hại cảm giác kiểm soát của người dùng và khả năng tương thích dài hạn; ngay cả khi phá vỡ quy ước thì mục đích cũng phải rõ ràng
Triết lý cơ bản của thiết kế CLI
- Hướng dẫn này là tài liệu mã nguồn mở, cung cấp các nguyên tắc thiết kế chương trình dòng lệnh hiện đại và chỉ dẫn cụ thể dựa trên các nguyên tắc UNIX truyền thống
- Trước đây dòng lệnh là môi trường ưu tiên máy móc gần với REPL chạy trên nền tảng scripting, nhưng CLI ngày nay mang tính ưu tiên con người mạnh hơn như một UI dựa trên văn bản để truy cập nhiều công cụ, hệ thống và nền tảng
- Dòng lệnh có những ràng buộc cũ và tập quán khá kỳ lạ, nhưng có thể dùng trên gần như mọi laptop, vừa hỗ trợ tương tác vừa hỗ trợ tự động hóa, và thay đổi chậm hơn các thành phần hệ thống khác
- Hướng dẫn này không đề cập tới các chương trình terminal toàn màn hình như emacs hay vim, cũng không phụ thuộc vào ngôn ngữ lập trình hoặc chuỗi công cụ cụ thể nào
CLI vừa ưu tiên con người vừa có thể kết hợp
- Nếu CLI chủ yếu là công cụ do con người sử dụng thì cần ưu tiên con người trước
- Nhiều chương trình CLI nhắm đến người dùng thật nhưng vẫn giữ nguyên thiết kế tương tác cũ vốn tập trung vào máy móc
- Triết lý UNIX về những chương trình nhỏ phối hợp với nhau bằng giao diện gọn gàng vẫn còn rất quan trọng
- Các quy ước như đầu vào chuẩn, đầu ra chuẩn, lỗi chuẩn, signal và mã thoát giúp chương trình có thể kết hợp với nhau
- Văn bản thuần theo từng dòng rất dễ truyền qua pipe, còn JSON hữu ích khi cần dữ liệu có cấu trúc hơn
- Tính nhất quán là chìa khóa để biến chi phí học CLI thành hiệu quả dài hạn
- Khi theo các mẫu quen thuộc, người dùng có thể đoán lệnh và tùy chọn dễ hơn
- Khi quy ước làm giảm tính dễ dùng thì vẫn có thể phá lệ một cách thận trọng
- Một CLI tốt không nên quá im lặng cũng không nên xuất quá nhiều, mà phải giúp người dùng nhận được đúng lượng thông tin họ cần
- CLI thường bị xem là kém khả năng khám phá hơn GUI, nhưng có thể tăng khả năng học bằng trợ giúp đầy đủ, ví dụ, gợi ý lệnh tiếp theo và hướng dẫn xử lý khi xảy ra lỗi
Tương tác hội thoại và độ vững chắc
- Việc dùng dòng lệnh giống một cuộc đối thoại với nhiều lần thử và chỉnh sửa hơn là chỉ một lần chạy
- Với đầu vào sai, có thể đưa ra phương án sửa nếu có thể
- Có thể hiển thị rõ trạng thái trung gian của các tác vụ nhiều bước
- Có thể yêu cầu xác nhận trước các thao tác nguy hiểm
- CLI phải thực sự vững chắc, đồng thời cũng phải khiến người dùng cảm thấy nó đáng tin cậy
- Phải xử lý đầu vào bất ngờ một cách mềm mại
- Khi có thể, thao tác nên có tính idempotent
- Không nên hiển thị stack trace trông đáng sợ trong đầu ra mặc định
- Sự đồng cảm giúp người dùng cảm thấy phần mềm đứng về phía mình là điều quan trọng
- Không phải là dùng emoji hay biến nó thành trò chơi, mà cốt lõi là thiết kế cẩn thận để người dùng thành công
- Hệ sinh thái terminal có nhiều điểm không nhất quán và gây rối, nhưng chính nhờ mức ràng buộc thấp mà các phát minh mới trở nên khả thi
- Hãy theo các mẫu hiện có, nhưng cũng có thể chủ động bỏ những tiêu chuẩn làm hại năng suất hoặc sự hài lòng của người dùng
Những quy tắc nền tảng bắt buộc phải tuân thủ
- Khi có thể, nên dùng thư viện phân tích đối số dòng lệnh
- Khi thành công, mã thoát phải là
0; khi thất bại phải trả về giá trị khác 0- Script xác định thành công hay thất bại của chương trình thông qua mã thoát
- Đầu ra chính của lệnh phải được gửi tới
stdout- Cả đầu ra máy có thể đọc được cũng nên mặc định đi tới
stdoutđể pipe hoạt động đúng
- Cả đầu ra máy có thể đọc được cũng nên mặc định đi tới
- Các thông điệp hiển thị cho người dùng như log hay lỗi phải được gửi tới
stderr- Khi nối pipe, các thông điệp sẽ không bị trộn vào đầu vào của lệnh tiếp theo
Thiết kế trợ giúp
- Khi nhận
-hvà--help, phải hiển thị phần trợ giúp đầy đủ- Các lệnh con cũng có thể có trợ giúp riêng
- Không nên gán
-hthêm nghĩa khác
- Nếu chạy một lệnh cần đối số mà không truyền đối số nào, nên hiển thị phần trợ giúp ngắn gọn
- Mô tả chương trình
- Một hoặc hai ví dụ gọi lệnh
- Giải thích các cờ
- Hướng dẫn dùng
--helpđể xem chi tiết hơn
- Nên đưa đường dẫn hỗ trợ vào phần trợ giúp
- Liên kết website hoặc GitHub là lựa chọn phổ biến
- Nếu có tài liệu web thì phần trợ giúp nên liên kết tới đó
- Nếu lệnh con có trang hoặc anchor riêng thì liên kết trực tiếp sẽ hữu ích hơn
- Vì người dùng có xu hướng xem ví dụ trước khi đọc tài liệu, nên đặt ví dụ ở gần đầu phần trợ giúp là tốt nhất
- Có thể cho thấy trước các trường hợp sử dụng phức tạp nhưng phổ biến
- Nếu có nhiều ví dụ, phù hợp hơn là để trong lệnh cheat sheet riêng hoặc trên một trang web
- Phần trợ giúp có thể được định dạng để dễ quét nhanh
- Tiêu đề in đậm giúp tăng khả năng đọc
- Cần xử lý theo cách độc lập với terminal để ký tự escape không bị hiện nguyên văn
- Nếu người dùng nhập sai mà có thể đoán được ý định thì nên đưa ra gợi ý
- Ví dụ
brew update jqcó thể hướng dẫn sangbrew upgrade jq - Có thể hỏi người dùng có muốn chạy lệnh được gợi ý hay không, nhưng nên tránh tự động ép chạy
- Ví dụ
- Nếu một lệnh cần nhận dữ liệu từ
stdinnhưngstdinlại là terminal tương tác, thì nên lập tức hiển thị trợ giúp rồi thoát, hoặc in thông báo rastderr
Tài liệu hóa
- Trợ giúp cung cấp tóm tắt tức thời và hướng dẫn cho các tác vụ phổ biến, còn tài liệu sẽ trình bày chi tiết mục đích, điều không nhằm tới, cách hoạt động và cách dùng đầy đủ của công cụ
- Nên cung cấp tài liệu dựa trên web
- Có thể tìm kiếm và liên kết đến từng phần cụ thể
- Tài liệu web là hình thức tài liệu toàn diện nhất
- Cũng nên cung cấp tài liệu trong terminal
- Có thể truy cập nhanh
- Đồng bộ với phiên bản công cụ đã cài đặt
- Vẫn hoạt động khi không có Internet
- Có thể cân nhắc cung cấp trang
man- Nhiều người dùng sẽ kiểm tra
man mycmdtrước - Giống như
gitvànpm, có thể truy cập trangmanthông qua lệnh conhelp
- Nhiều người dùng sẽ kiểm tra
Nguyên tắc đầu ra
- Đầu ra dễ đọc cho con người là điều quan trọng nhất
- Tiêu chí đơn giản để xác định một luồng đầu ra có dành cho con người đọc hay không là nó có phải TTY hay không
- Nên cung cấp đầu ra mà máy có thể đọc được trong phạm vi không làm giảm tính tiện dụng
- Luồng các dòng văn bản thuần là giao diện phổ quát của UNIX
- Người dùng kỳ vọng có thể chuyển đầu ra sang các công cụ như
grepvà chúng sẽ hoạt động như mong đợi
- Nếu đầu ra thân thiện với con người làm hỏng đầu ra thân thiện với máy, có thể cung cấp
--plain- Trong đầu ra dạng bảng, việc chia một ô thành nhiều dòng có thể phá vỡ kỳ vọng một bản ghi trên một dòng
--plaincung cấp đầu ra bảng không bị chỉnh sửa để dùng cho script
- Nếu truyền
--jsonthì phải xuất JSON đã được định dạng- JSON dễ xử lý các cấu trúc dữ liệu phức tạp và có thể dùng cùng
jqcùng nhiều công cụ JSON CLI khác - Nó cũng phù hợp để pipe trực tiếp với dịch vụ web qua
curl
- JSON dễ xử lý các cấu trúc dữ liệu phức tạp và có thể dùng cùng
- Khi thành công thì nên có đầu ra, nhưng giữ ngắn gọn
- Lệnh UNIX truyền thống thường không in gì nếu không có vấn đề, nhưng với con người điều đó có thể trông như bị treo
- Nếu cần im lặng cho script thì có thể dùng tùy chọn
-qđể ẩn đầu ra không thiết yếu
- Các lệnh thay đổi trạng thái nên cho người dùng biết điều gì đã thay đổi
git pushlà ví dụ cho việc hiển thị tác vụ đang thực hiện và trạng thái mới của nhánh từ xa
- Cần có cách dễ dàng để xem trạng thái hiện tại của hệ thống
git statushiển thị trạng thái kho lưu trữ cùng gợi ý về các lệnh có thể chạy tiếp theo
- Các thao tác vượt qua ranh giới của thế giới nội bộ chương trình thường nên mang tính tường minh
- Ví dụ đọc hoặc ghi các tệp mà người dùng không truyền qua đối số
- Hoặc giao tiếp với máy chủ từ xa để tải tệp xuống
- Màu sắc nên được dùng có chủ đích
- Dùng quá nhiều sẽ làm mất ý nghĩa và khó đọc hơn
- Nên tắt màu nếu không phải TTY, hoặc
NO_COLORđã được đặt, hoặcTERM=dumb, hoặc có truyền--no-color
- Không nên xuất hoạt ảnh nếu
stdoutkhông phải terminal tương tác- Điều này giúp tránh làm log CI trở nên lộn xộn bởi các chỉ báo tiến trình
- Khi xuất nhiều văn bản, có thể dùng pager như
less- Tốt nhất chỉ dùng khi
stdinhoặcstdoutlà terminal tương tác less -FIRXcó thể là một tập tùy chọn hợp lý
- Tốt nhất chỉ dùng khi
Thông báo lỗi
- Nếu biến lỗi thành tài liệu, có thể giảm thời gian người dùng phải đi tìm tài liệu
- Cần bắt các lỗi có thể dự đoán và viết lại để con người hiểu được
- Ví dụ: hướng dẫn theo kiểu không thể ghi vào
file.txtvà có thể cầnchmod +w file.txt
- Ví dụ: hướng dẫn theo kiểu không thể ghi vào
- Tỷ lệ tín hiệu trên nhiễu rất quan trọng
- Càng nhiều đầu ra không liên quan, người dùng càng khó xác định vấn đề
- Nếu có nhiều lỗi cùng loại, có thể nhóm chúng dưới một tiêu đề giải thích chung
- Thông tin quan trọng nhất nên được đặt ở cuối đầu ra
- Ánh mắt người dùng dễ bị thu hút bởi chữ màu đỏ, nên chỉ dùng có chủ ý và tiết chế
- Nếu lỗi là điều không mong đợi hoặc khó giải thích, nên cung cấp thông tin debug·traceback và cách gửi báo lỗi
- Để tránh làm người dùng bị quá tải, có thể ghi log debug vào tệp thay vì terminal
- Việc gửi báo lỗi nên được làm cho dễ dàng
- Ví dụ như cung cấp URL đã điền sẵn thông tin nếu có thể
Đối số và cờ
- Đối số là tham số theo vị trí, còn cờ là tham số có tên
- Trong
cp foo bar, đường dẫn tệp là đối số -r,--recursive,--file foo.txtlà cờ
- Trong
- Khi có thể, nên ưu tiên cờ hơn đối số
- Phải gõ nhiều hơn nhưng ý nghĩa rõ ràng
- Dễ thay đổi cách nhập trong tương lai hơn
- Mọi cờ đều nên có tên dài
- Ví dụ dùng cả
-hvà--help - Điều này hữu ích để thể hiện rõ ý nghĩa trong script
- Ví dụ dùng cả
- Cờ một ký tự chỉ nên dùng cho các tùy chọn thường dùng
- Nhờ đó có thể giữ lại không gian tên ngắn cho các cờ sẽ thêm sau này
- Nếu áp dụng cùng một thao tác đơn giản lên nhiều tệp, nhiều đối số là phù hợp
- Dạng như
rm file1.txt file2.txt file3.txtrất hợp với globbing
- Dạng như
- Nếu có từ 2 đối số trở lên mang ý nghĩa khác nhau thì có khả năng thiết kế đang có vấn đề
- Ngoại lệ là các thao tác phổ biến và cốt lõi như
cp <source> <destination>; sự ngắn gọn ở đây đáng để ghi nhớ
- Ngoại lệ là các thao tác phổ biến và cốt lõi như
- Nếu đã có tên cờ chuẩn thì nên làm theo
-a, --all-d, --debug-f, --force--json-h, --help-n, --dry-run--no-input-o, --output-p, --port-q, --quiet-u, --user--version
- Giá trị mặc định nên là hành vi đúng cho đa số người dùng
- Nếu giả định người dùng sẽ tìm đúng cờ và nhớ dùng nó mỗi lần, trải nghiệm của phần lớn người dùng sẽ trở nên tệ hơn
- Nếu không có đầu vào thì có thể hỏi bằng prompt, nhưng prompt không được là bắt buộc
- Luôn phải có cách truyền đầu vào bằng cờ hoặc đối số
- Nếu
stdinkhông phải terminal tương tác thì phải bỏ qua prompt và yêu cầu các cờ hoặc đối số cần thiết
- Nên xác nhận trước các thao tác nguy hiểm
- Trong chạy tương tác, có thể yêu cầu nhập
yhoặcyes - Trong chạy không tương tác, có thể yêu cầu
-fhoặc--force - Với thao tác nghiêm trọng, có thể yêu cầu nhập trực tiếp tên đối tượng sẽ bị xóa hoặc cung cấp cờ như
--confirm="name-of-thing"
- Trong chạy tương tác, có thể yêu cầu nhập
- Nếu nhận đầu vào·đầu ra tệp thì nên hỗ trợ
-chostdinhoặcstdout- Nhờ đó có thể nối với đầu ra của lệnh khác mà không cần tệp tạm
- Khi có thể, nên xử lý thứ tự đối số, cờ và lệnh con một cách độc lập
- Vì người dùng thường thêm tùy chọn vào cuối lệnh trước đó rồi chạy lại
- Không nên đọc trực tiếp giá trị bí mật từ cờ
- Giá trị của
--passwordcó thể lộ ra trong đầu rapshoặc lịch sử shell - Nên cân nhắc đầu vào từ tệp như
--password-filehoặc từstdin
- Giá trị của
Tương tác
- Prompt hoặc các yếu tố tương tác chỉ nên dùng khi
stdinlà TTY- Trong script hoặc khi nhận đầu vào qua pipe, prompt sẽ không hoạt động, nên cần báo lỗi cho biết phải truyền cờ nào
- Nếu truyền
--no-inputthì không được thực hiện prompt hay hành vi tương tác- Nếu cần đầu vào thì phải thất bại và hướng dẫn cách truyền bằng cờ
- Khi nhận mật khẩu, không nên hiển thị giá trị mà người dùng đang gõ
- Thường xử lý bằng cách tắt echo của terminal
- Người dùng phải có cách thoát ra
- Ngay cả khi đang bị chặn bởi network I/O v.v., Ctrl-C vẫn phải hoạt động
- Nếu là wrapper như SSH, tmux, telnet, nơi không thể thoát bằng Ctrl-C, thì phải chỉ rõ cách thoát
Lệnh con
- Nếu công cụ đủ phức tạp, có thể giảm độ phức tạp bằng một tập lệnh con
- Gộp nhiều công cụ liên quan vào một lệnh có thể giúp dễ dùng và dễ khám phá hơn
- Cũng thuận tiện để chia sẻ cờ toàn cục, trợ giúp, cấu hình và cơ chế lưu trữ
- Cần có tính nhất quán giữa các lệnh con
- Cùng một ý nghĩa thì nên dùng cùng tên cờ
- Định dạng đầu ra cũng nên tương tự
- Với lệnh con nhiều tầng, nên dùng hệ thống đặt tên nhất quán
- Mẫu hai tầng gồm danh từ và động từ như
docker container createlà khá phổ biến - Cả
noun verblẫnverb nounđều có thể dùng, nhưngnoun verbcó vẻ phổ biến hơn
- Mẫu hai tầng gồm danh từ và động từ như
- Tránh các lệnh có tên mơ hồ hoặc quá giống nhau
- Nếu cùng có
updatevàupgradethì có thể gây nhầm lẫn
- Nếu cùng có
Độ bền
- Cần xác thực mọi dữ liệu do người dùng nhập vào
- Vì có thể có dữ liệu không hợp lệ, nên cần kiểm tra sớm và dừng lại với lỗi dễ hiểu
- Khả năng phản hồi quan trọng hơn tốc độ
- Tốt nhất là xuất ra thứ gì đó cho người dùng trong vòng 100ms
- Ngay cả trước khi gửi yêu cầu mạng cũng nên in thông báo để không trông như bị treo
- Với các tác vụ mất nhiều thời gian, cần hiển thị tiến trình
- Nếu không có đầu ra trong một lúc, chương trình sẽ trông như bị hỏng
- Nếu chỉ báo tiến trình đứng yên quá lâu ở một chỗ, có thể dùng ước lượng thời gian còn lại hoặc hoạt ảnh để cho thấy công việc vẫn đang tiếp tục
- Hãy xử lý song song nếu có thể, nhưng phải thận trọng
- Việc hiển thị tiến độ của các tác vụ song song trong shell là khó, và nếu đầu ra bị trộn lẫn thì sẽ gây rối
- Nhiều thanh tiến trình của
docker pulllà một ví dụ cho thấy điều gì đang diễn ra - Dù có ẩn log phía sau thanh tiến trình khi chạy bình thường, khi xảy ra lỗi vẫn phải in log để có thể gỡ lỗi
- Các tác vụ mạng phải có timeout
- Timeout phải có thể cấu hình được và cần có giá trị mặc định hợp lý
- Phải có khả năng phục hồi sau khi thất bại
- Sau lỗi tạm thời, khi chạy lại bằng
<up>và<enter>, cần có thể tiếp tục từ điểm đã dừng
- Sau lỗi tạm thời, khi chạy lại bằng
- Nếu có thể, cách tiếp cận crash-only là tốt
- Khi lỗi hoặc bị ngắt, có thể thoát ngay lập tức, còn việc dọn dẹp có thể để sang lần chạy sau
- Người dùng có thể dùng công cụ theo những cách ngoài dự đoán
- Họ có thể bọc nó trong script, dùng trên Internet không ổn định, chạy nhiều instance cùng lúc, hoặc chạy trong môi trường chưa được kiểm thử
Tương thích tương lai
- Subcommand, đối số, cờ, tệp cấu hình và biến môi trường đều là giao diện, và phải được duy trì để hoạt động lâu dài
- Nếu có thể, các thay đổi nên mang tính bổ sung
- Thay vì phá vỡ hành vi của cờ hiện có, có thể thêm cờ mới
- Nếu cần thay đổi không mang tính bổ sung, phải cảnh báo trước
- Khi dùng cờ không còn được khuyến nghị, cần thông báo về thay đổi sắp tới và cả cách tương thích tương lai có thể dùng ngay từ bây giờ
- Đầu ra dành cho con người nhìn chung có thể thay đổi mà không sao
- Nên hướng người dùng dùng
--plainhoặc--jsontrong script để có đầu ra ổn định
- Nên hướng người dùng dùng
- Nên tránh subcommand catch-all
- Cách xử lý tự động thành
runnếu không phải subcommand đã biết có thể làm hỏng cách dùng hiện có khi sau này thêm subcommand mới
- Cách xử lý tự động thành
- Không nên cho phép viết tắt tùy ý cho subcommand
- Nếu cho phép
installthànhihoặcins, sau này sẽ khó thêm các lệnh khác bắt đầu bằngi - Alias phải rõ ràng và được duy trì ổn định
- Nếu cho phép
- Cần cân nhắc liệu 20 năm nữa lệnh có còn chạy theo cùng cách hay không
- Không nên tạo ra quả bom hẹn giờ khiến lệnh ngừng hoạt động vì phụ thuộc Internet bên ngoài thay đổi hoặc biến mất
Tín hiệu và ký tự điều khiển
- Khi người dùng gửi Ctrl-C, tức tín hiệu INT, chương trình phải thoát nhanh nhất có thể
- Trước khi bắt đầu dọn dẹp, phải lập tức thông báo điều gì đó
- Mã dọn dẹp phải có timeout
- Nếu Ctrl-C được nhấn lại trong lúc đang dọn dẹp lâu, phải có thể bỏ qua bước dọn dẹp
- Docker Compose thông báo rằng nếu nhấn Ctrl-C thêm một lần nữa trong lúc thoát, có thể lập tức buộc dừng container
- Chương trình phải giả định rằng nó có thể được khởi động khi công việc dọn dẹp trước đó chưa được thực hiện
Cấu hình và biến môi trường
- Cách cấu hình nên thay đổi theo mức độ cụ thể, tính ổn định và độ phức tạp
- Các thiết lập thường xuyên thay đổi theo từng lần chạy thì cờ là phù hợp
- Các thiết lập tương đối ổn định nhưng thay đổi theo người dùng, dự án hoặc máy thì cờ và biến môi trường là phù hợp
- Các thiết lập ổn định cho mọi người dùng trong một dự án thì tệp cấu hình dành riêng cho lệnh, được quản lý bằng phiên bản, là phù hợp
- Nên tuân theo XDG Base Directory Specification
- Mục đích là hỗ trợ vị trí cấu hình dùng chung như
~/.configđể giảm số lượng dotfile trong thư mục home
- Mục đích là hỗ trợ vị trí cấu hình dùng chung như
- Nếu tự động sửa các tệp không phải cấu hình của chính chương trình mình, cần có sự đồng ý của người dùng và cho biết chính xác sẽ làm gì
- Tốt hơn là tạo tệp cấu hình mới thay vì chèn thêm vào tệp cấu hình hệ thống hiện có
- Thứ tự ưu tiên cấu hình phải được áp dụng từ cao xuống thấp
- Cờ
- Biến môi trường của shell đang chạy
- Cấu hình cấp dự án
- Cấu hình cấp người dùng
- Cấu hình toàn hệ thống
- Biến môi trường phù hợp với hành vi thay đổi theo ngữ cảnh mà lệnh được chạy
- Tên biến môi trường nên chỉ dùng chữ hoa, số và dấu gạch dưới để có tính tương thích tối đa, và không nên bắt đầu bằng số
- Giá trị nên là một dòng nếu có thể
- Giá trị nhiều dòng gây vấn đề về tính tiện dụng khi dùng cùng lệnh
env
- Giá trị nhiều dòng gây vấn đề về tính tiện dụng khi dùng cùng lệnh
- Không nên tùy tiện chiếm dụng các tên đã được dùng rộng rãi
- Có thể tham khảo danh sách biến môi trường chuẩn POSIX
- Nếu có thể, nên kiểm tra các biến môi trường phổ biến
NO_COLOR,FORCE_COLORDEBUGEDITORHTTP_PROXY,HTTPS_PROXY,ALL_PROXY,NO_PROXYSHELLTERM,TERMINFO,TERMCAPTMPDIRHOMEPAGERLINES,COLUMNS
- Khi phù hợp, có thể đọc biến môi trường từ
.env- Hữu ích cho các biến hầu như không thay đổi khi làm việc trong một thư mục cụ thể
.envkhông phải là sự thay thế cho tệp cấu hình chính thức- Thường không được lưu trong quản lý mã nguồn
- Chỉ có kiểu chuỗi
- Dễ trở nên lộn xộn
- Dễ phát sinh vấn đề mã hóa
- Dễ chứa thông tin xác thực nhạy cảm và dữ liệu khóa
- Không nên đọc giá trị bí mật từ biến môi trường
- Biến môi trường có thể bị lộ qua tiến trình, log,
docker inspect,systemctl show, v.v. - Bí mật nên được nhận qua tệp thông tin xác thực, pipe, socket
AF_UNIX, dịch vụ quản lý bí mật hoặc các cơ chế IPC khác
- Biến môi trường có thể bị lộ qua tiến trình, log,
Tên, phân phối, thu thập phân tích
- Tên chương trình CLI nên đơn giản và dễ nhớ vì người dùng sẽ gõ thường xuyên
- Nếu quá chung chung, nó có thể xung đột với lệnh khác hoặc gây nhầm lẫn cho người dùng
- Tên nên dùng chữ thường và dấu gạch nối khi cần
curllà tên tốt, cònDownloadURLlà ví dụ không tốt
- Tên nên ngắn, nhưng tên quá ngắn thì phù hợp hơn với các tiện ích rất phổ biến như
cd,ls,ps - Tên phải dễ gõ
- Có trường hợp tên ban đầu của Docker Compose là
plum, nhưng vì khó gõ bằng một tay nên đã đổi thànhfig
- Có trường hợp tên ban đầu của Docker Compose là
- Nếu có thể, nên phân phối dưới dạng một binary duy nhất
- Nếu binary duy nhất là khó, nên dùng trình cài đặt gói mặc định của nền tảng để phân phối theo cách dễ gỡ bỏ
- Các công cụ theo ngôn ngữ, ví dụ như code linter, có thể giả định rằng người dùng có trình thông dịch của ngôn ngữ đó
- Phải dễ gỡ bỏ
- Vì nhiều trường hợp người dùng muốn gỡ ngay sau khi cài, nên tốt hơn là đặt hướng dẫn gỡ bỏ ngay bên dưới hướng dẫn cài đặt
- Chỉ số sử dụng có thể giúp cải thiện công cụ, nhưng người dùng CLI kỳ vọng họ kiểm soát được môi trường của mình
- Không được gửi dữ liệu sử dụng hoặc crash nếu chưa có sự đồng ý
- Phải nêu rõ thu thập gì, vì sao thu thập, được ẩn danh đến mức nào, ẩn danh bằng cách nào, và lưu giữ trong bao lâu
- Lý tưởng nhất là opt-in; nếu chọn opt-out với thu thập mặc định, phải thông báo rõ trên website hoặc ở lần chạy đầu tiên và phải dễ tắt
- Cũng có thể cân nhắc các phương án thay thế cho việc thu thập phân tích
- Gắn đo lường vào tài liệu web
- Gắn đo lường vào lượt tải xuống
- Trao đổi trực tiếp với người dùng và khuyến khích phản hồi cũng như yêu cầu tính năng qua tài liệu và kho mã nguồn
1 bình luận
Ý kiến trên Hacker News
Nói “ngày nay đa số thậm chí không biết dòng lệnh là gì” thì đúng, nhưng ngay cả vào thập niên 1980 mà TFA lấy làm chuẩn cũng vậy
Khác biệt là hiện nay số người biết và có thể dùng dòng lệnh là nhiều nhất trong lịch sử, tăng ít nhất ở mức một chữ số, có khi là hai chữ số, nên có thể gọi đây là thời hoàng kim của CLI
Trạng thái toàn cục và phụ thuộc làm cản trở việc suy luận, và cuối cùng cũng khiến debug lẫn tối ưu hiệu năng trở nên khó khăn
Khi tách các đoạn mã 500 dòng, 1.000 dòng, 5.000 dòng, 10.000 dòng, đôi khi 50.000 dòng ra để chạy riêng, rất nhiều thứ trở nên rõ ràng hơn nhiều
Nếu làm đúng, còn có thể dẫn đến cấu trúc Functional Core, Imperative Shell, vì dòng lệnh phù hợp với triết lý Unix nên có nhiều thao tác không gây tác dụng phụ và ít thao tác có tác dụng phụ
Có thể tạo một lệnh nhỏ sinh và xuất trạng thái hệ thống ngay trước khi một quyết định tệ được đưa ra, và thực tế có thể chạy an toàn cả trong môi trường production
Như vậy cũng dễ trao các công cụ này cho những người cần nằm trong bus factor để họ tham gia hơn
Người trong một làng văn minh hẻo lánh ở Amazon nghĩ gì về terminal thì không liên quan
Khi đánh giá sự thay đổi về chất của một đối tượng đã tăng quy mô, phải nhìn vào tỷ lệ; chỉ nhìn số tuyệt đối thì sẽ có câu đúng nhưng vô nghĩa
Ví dụ, số người nghe jazz ngày nay về tuyệt đối có thể nhiều hơn thời hoàng kim văn hóa của thể loại này, nhưng đó không phải vì jazz phổ biến hơn, mà vì bản thân số người nghe nhạc đã tăng
Vì vậy khó có thể nói jazz ở Mỹ ngày nay quan trọng và có ảnh hưởng hơn thời hoàng kim của nó
Mong là cũng cân nhắc tùy chọn
--dry-runđể cho xem trước thao tác nào sẽ xảy ra mà không thực sự thay đổi gìNó cực kỳ hữu ích khi học một công cụ, hoặc kiểm tra xem mình đã dùng đúng các tùy chọn phức tạp và glob tệp trước khi chạy một thao tác khó hoàn tác hay chưa
--dry-run, còn khi thực thi thật thì yêu cầu cờ--executeKhông biết nếu thiếu nó thì đã xoay xở thế nào, và nó đã cứu tôi khỏi vài lần suýt phá hỏng hoàn toàn mọi thứ
--commitVới các script làm những việc không thể hoàn tác hoặc khó hoàn tác, cách này cho cảm giác an toàn hơn
Tôi đang bối rối không biết nên thiết kế các công cụ mình đang làm như thế nào
Nếu gọi API để lấy dữ liệu, tôi không biết làm sao biến việc đó thành dry run
Có phải đưa ví dụ giả và đầu ra giả không?
do_thing.py | dry-runCó thể nghĩ như việc yêu cầu ở prompt: “hãy giải thích từng bước quá trình thực hiện”
Không chỉ là đừng hiển thị animation nếu đầu ra chuẩn không phải terminal tương tác, mà tuyệt đối không nên hiển thị animation trên stdout
Nhìn chung TFA khá tốt, nhưng tôi thấy tiếc khi tìm phần giải thích khác biệt giữa stderr và stdout mà lại không có
stderr không chỉ dành cho lỗi, mà là nơi dành cho toàn bộ log và đầu ra thông tin; nếu là terminal thì cũng có thể là vùng để đưa animation vào
stdout phải là đầu ra thực sự hữu ích, và phải nhất quán bất kể có phải terminal hay không
Ví dụ trong
echo foo | mysed 's/oo/aa/' | cat, stdout củamysedlàfaa, còn stderr nên chứa log như thông tin phiên bản hoặc nội dung đã tìm thấyTôi không muốn phải vật lộn với công cụ bằng grep chỉ để lấy đầu ra thật, cũng không muốn việc bỏ
| catlàm hành vi thay đổi theo cách khiến debug khó hơnNếu yêu cầu
--helpthì nên đi ra stdout, và nếu yêu cầu log thì log cũng nên đi ra stdoutNếu là thông tin không được yêu cầu thì stderr mới đúng
Nếu có thể quay ngược thời gian, tôi nghĩ nếu đặt tên là stdin, stdout, stdext thì mọi người có khả năng tuân theo quy ước này hơn
Nhưng vì tên là stderr nên lập trình viên sẽ hợp lý mà nghĩ đó là “dùng để báo lỗi”, và nếu không phải lỗi thì đưa vào stdout
Dù vậy, về cấu trúc chương trình thì cách này tốt hơn, và dù ban đầu có thể khó hiểu hơn, nó cho phép làm nhiều việc hữu ích hơn với đầu ra nên vẫn có lợi
Tôi cũng không nghĩ màu sắc thật sự là thông tin
Làm ơn tránh lời khuyên “hãy dùng ký hiệu và emoji ở những chỗ giúp rõ ràng hơn”
yubikey-agent được nêu làm ví dụ thể hiện đúng những điều tôi ghét ở README GitHub và giao diện người dùng kiểu đùa cợt
Về mặt kỹ thuật, ký hiệu và emoji có thể được render khác nhau tùy terminal, khiến thông điệp trở nên khó hiểu
Về mặt thẩm mỹ, mức độ chấp nhận sự nghịch ngợm và vui tươi ở mỗi người rất khác nhau, nên chỉ nên dùng hết sức tiết chế và chỉ khi thật sự biết mình đang làm gì
Tôi cũng thích đầu ra terminal có màu, và emoji cũng có màu nên giúp nắm nhanh bức tranh tổng thể của tình huống
Tôi thích cả tô sáng cú pháp, và nếu không có thì cảm thấy đọc code khó hơn nhiều
Không phải ai cũng như vậy; cũng như tôi không kỳ vọng gu của mình trở thành mặc định, gu ngược lại cũng không nên bị áp làm mặc định
Có người thích, có người ghét, nên mặc định cứ tắt và cho người dùng chọn
Ví dụ chỉ cần một cái cho thành công, một cái cho thất bại là đủ
Và thông tin mà emoji truyền tải bắt buộc cũng phải được lặp lại bằng văn bản
Lần đầu xem CLI Guidelines, tôi cũng dừng đọc ngay ở phần đó
Lần này tôi đã đọc cả phần còn lại, và nhìn chung khá ổn
Tôi hiểu rằng một số CLI rất lớn như
awsnên cần lồng nhau, nhưng việc phải lần theo CLI lồng nhau thật sự rất khó chịuVới đa số ứng dụng, tôi thích chúng in ra mọi tùy chọn trong phần help, rồi để tôi dùng
lesstìm cái mình cần hơnTUI này thuần túy là UI/UX tùy chọn; nếu muốn thì dùng, và nó chuyển các thiết lập đã chọn sang một tệp được tạo riêng hoặc một hàm
Tệp hoặc hàm đó luôn có thể được gọi trực tiếp từ terminal, và cũng cung cấp mọi tùy chọn cùng phần trợ giúp mà TUI dùng
Vì vậy nó cũng có thể dùng trong script, và hữu ích cho người dùng ở nhiều mức độ thành thạo khác nhau
Nếu bạn biết lệnh con mình cần, việc thu hẹp tùy chọn xuống chỉ những thứ cần thiết khá hữu ích, nhưng nếu không biết thì có thể rất khổ
Lục một danh sách tùy chọn dài bằng grep cũng mệt, nên cần có sự cân bằng
mansao?--helpcủa lệnh con vào lệnh cha có thể hữu íchVí dụ
git stash --helphiển thị nguyên phần trợ giúp củagit stash, bao gồm từng mục bên trongCách giải thích rằng “theo truyền thống, các lệnh UNIX được viết chủ yếu với giả định rằng chương trình khác sẽ dùng chúng” là không chính xác
Ban đầu chúng chủ yếu được nhắm tới việc sử dụng tương tác trong login shell
Có những chương trình tạo đầu ra ra stdout (
ls,cat,find,tty,who,date) và những bộ lọc văn bản im lặng (tr,grep,cut,uniq,sort,wc), và ở thời đó người ta có thể làm các tác vụ tính toán cơ bản bằng lệnh một dòngCác chương trình phức tạp được viết bằng C, và sau khi các ngôn ngữ chuyên biệt theo miền như
sedvà AWK xuất hiện, một số chương trình xử lý nhiều chuỗi mới được chuyển sang shellShell không phải là một môi trường lập trình đúng nghĩa, và cũng chưa từng được thiết kế cho mục đích đó
Ngày nay nhiều khi làm cả hai bằng Python hoặc Lua sẽ tốt hơn, nhưng shell và C là những thứ được cài đặt phổ biến nhất
Việc tách nhánh thành
sh,bash,ksh,cshlà một ví dụ hayTôi cho rằng xem bộ công cụ POSIX tiêu chuẩn như thư viện chuẩn của nhiều ngôn ngữ shell là khá hợp lý
Dòng lệnh Unix hiện nay một mặt thì “cực kỳ hữu ích”, mặt khác lại hỏng ngay từ thiết kế
Cứ nghĩ xem nếu viết đoạn dưới đây bằng C hoặc Rust thì mất bao lâu, tính hữu ích là rất rõ:
curl -sS https://go.dev/doc/devel/release | html2text | grep -o -P '\bgo\d+\.\d+\.\d+\b' | sort -V | uniq | tail -1Nhưng vì sao nó hỏng về mặt thiết kế thì có thể xem https://news.ycombinator.com/item?id=29747034
Vấn đề là giao diện dòng lệnh phải vừa dễ đọc với con người, vừa dễ đọc với máy, và không có cách chuẩn nào để giải quyết chuyện này
Vốn dĩ có thể đã có một lời giải chuẩn cho vấn đề vừa để người vừa để máy đọc được
Apple đã tạo Human Interface Guidelines để cụ thể hóa ý nghĩa của các trừu tượng thị giác trong UI desktop
Nhưng dòng lệnh không được tạo ra bởi những người suy nghĩ như các nhà thiết kế Apple, mà bởi những người nghĩ rằng “lười biếng, nôn nóng, kiêu ngạo là đức tính tốt, vậy làm sao diễn đạt thứ mình muốn bằng ít code nhất”
Ở thời điểm đó đây cũng không hẳn là lựa chọn sai, từng byte đều quan trọng, nhưng quyết định thiết kế ấy giờ đã cắm chặt vào một chuỗi công cụ không thể dịch chuyển
Để có thứ tốt hơn hiện nay, có lẽ phải gần như từ bỏ chuỗi công cụ POSIX, và rất khó tưởng tượng việc xây được một UX dễ khám phá và nhất quán về khái niệm trên nền tảng hiện tại
Ngay cả tại cùng một thời điểm cũng có nhiều cách cho phép các đầu ra khác nhau
Chỉ là phải có một chuẩn mà mọi người đều tuân theo, và chính ở điểm đó mọi thứ trở nên phức tạp
Nếu vấn đề là rất ít bản triển khai
lscho phép kết thúc tên tệp bằng ký tự NUL thay vì xuống dòng, thì lời giải trông quá rõ ràng đến mức tôi có cảm giác mình đang bỏ sót điều gì đóCó lý do nào khiến giao diện shell từ chối chuyện này không?
--jsonNói “đừng đọc giá trị bí mật từ biến môi trường” rồi đề xuất tệp thông tin xác thực, pipe, socket AF_UNIX, dịch vụ quản lý bí mật và các cơ chế giao tiếp liên tiến trình khác; tôi thắc mắc trong số đó cái nào tiện nhất và có tính di động tốt nhất
Cũng tò mò liệu dịch vụ quản lý bí mật chỉ được dùng trong công việc hay cả trong dự án cá nhân
Có một bài viết đi sâu hơn một chút về việc xử lý giá trị bí mật trên dòng lệnh tại https://smallstep.com/blog/command-line-secrets/
Tệp thông tin xác thực là một lựa chọn đơn giản và có tính di động tốt
Tệp vốn đã có mô hình phân quyền, và không phụ thuộc vào dịch vụ bên ngoài hay API độc quyền
Nếu chương trình nhận tệp thông tin xác thực thì cũng tương thích với systemd credentials
systemd credentials an toàn hơn so với tệp thông tin xác thực không mã hóa, được mã hóa và cũng có thể ràng buộc với TPM, nhưng phần mềm sử dụng thông tin xác thực không cần tự hỗ trợ TPM
Ví dụ mbsync(https://isync.sourceforge.io/mbsync.html) có khoảng ba cách để cung cấp mật khẩu xác thực IMAP
Nếu không đặt mật khẩu thì khi chạy sẽ hiện prompt, cũng có thể đặt mật khẩu dạng văn bản thuần trong tệp cấu hình nhưng như vậy bất tiện khi chia sẻ
Ngoài ra có thể cấu hình lệnh để lấy mật khẩu, nhờ đó ủy quyền xử lý cho trình quản lý mật khẩu như pass(1)(https://www.passwordstore.org/) hoặc một prompt đồ họa tương tác
Tài liệu tốt nên có thể thiết lập dễ dàng mà không cần tự xây thêm
Các trình quản lý bí mật như Doppler(https://Doppler.com) hoặc AWS Secrets Manager(https://aws.amazon.com/secrets-manager/) có lợi thế là bảo vệ giá trị bí mật ở nơi an toàn và giảm thiểu mức độ lộ lọt của chúng
Thậm chí có thể giảm việc lộ cho cả lập trình viên nội bộ, giúp ngăn các vụ rò rỉ dữ liệu vốn có thể dễ dàng tránh được
Những vụ rò rỉ như vậy có thể đặt cả công ty vào rủi ro và ngày càng phổ biến
Bài liên quan: Command Line Interface Guidelines - https://news.ycombinator.com/item?id=38053692 - tháng 10 năm 2023, 1 bình luận
Command Line Interface Guidelines - https://news.ycombinator.com/item?id=31651161 - tháng 6 năm 2022, 1 bình luận
Command Line Interface Guidelines - https://news.ycombinator.com/item?id=25492119 - tháng 12 năm 2020, 5 bình luận
Thật sự rất khó chịu khi một số terminal tự động chạy lệnh nếu chuỗi dán từ clipboard có ký tự xuống dòng ở cuối
Cá nhân tôi cho rằng giao diện dòng lệnh không nên làm như vậy
bind 'set enable-bracketed-paste on'Nếu thấy phiền thì cũng có thể tắt đi
Nếu cần thì nhiều dòng cũng được, và phải nhấn Enter mới chạy
Fish shell thường có các giá trị mặc định hợp lý, và ngoài prompt ra thì tôi vẫn chưa tìm thấy phần nào đặc biệt muốn tùy biến
Hầu hết những cái tôi từng dùng đều có tùy chọn loại bỏ ký tự xuống dòng khỏi nội dung clipboard
#trước rồi dánNgay cả khi ký tự xuống dòng được diễn giải thì toàn bộ dòng sẽ trở thành chú thích nên an toàn, và trước khi chạy thật chỉ cần xóa
#ở đầu dòng