2P by GN⁺ 2024-02-07 | ★ favorite | 댓글 1개
  • 전통적인 UNIX 철학을 현대적으로 재해석한 CLI 프로그램 설계 원칙과 구체적 가이드라인을 오픈소스 문서로 정리한 레퍼런스로, 커맨드라인 도구를 만드는 개발자를 주요 독자로 삼음
  • CLI는 단순 스크립팅 플랫폼이 아니라 인간 중심의 텍스트 UI로 진화했으며, 이 변화에 맞게 설계 원칙도 갱신되어야 함
  • 컴포저빌리티(composability)와 인간 친화성은 상충하지 않으며, 표준 입출력·파이프·종료 코드 등 UNIX 관행을 지키면 둘을 동시에 달성 가능
  • 헬프 텍스트, 에러 메시지, 출력 형식, 인터랙티비티, 설정 체계 등 실무에서 자주 놓치는 세부 항목까지 구체적 권장 사항으로 제공
  • CLI 도구의 미래 호환성과 사용자 신뢰는 인터페이스 안정성과 분석 데이터 투명성에서 결정되며, 이 가이드는 그 기준선을 제시

철학 (Philosophy)

인간 중심 설계

  • 전통 UNIX 명령어는 주로 다른 프로그램이 사용하는 것을 전제로 설계되었으나, 현재 CLI는 인간이 직접 사용하는 경우가 대부분이므로 인간 우선 설계가 필요
  • 과거 CLI는 "machine-first"였지만 현재는 "human-first" 텍스트 기반 UI로 진화

조합 가능한 작은 부품

  • UNIX 철학의 핵심은 작고 단순한 프로그램을 조합해 더 큰 시스템을 구성하는 것으로, 지금도 유효
  • 표준 stdin/stdout/stderr, 시그널, 종료 코드가 프로그램 간 연결을 보장하며, JSON은 더 구조화된 데이터 교환을 지원
  • 소프트웨어는 반드시 더 큰 시스템의 부품이 되므로, 잘 동작하는 부품이 될지는 설계 단계에서 결정됨

일관성

  • 터미널 사용자는 기존 컨벤션에 손이 익어 있으므로, CLI는 기존 패턴을 따를 것을 권장
  • 단, 일관성이 사용성을 해칠 경우 신중하게 관례를 깰 수 있음

적절한 정보량

  • 명령이 수 분간 아무 출력 없이 대기하면 "너무 적은" 정보, 대량의 디버그 로그를 쏟아내면 "너무 많은" 정보
  • 정보량의 균형은 소프트웨어가 사용자를 지원하는 데 절대적으로 중요

발견 가능성 (Ease of Discovery)

  • GUI는 화면에 기능을 모두 펼쳐 보이지만, CLI는 기억에 의존하는 것으로 오해받음
  • 포괄적인 헬프 텍스트, 풍부한 예시, 다음 명령 제안 등 GUI 기법을 차용해 CLI도 학습하기 쉽게 만들 수 있음

대화로서의 CLI

  • CLI 사용은 반복적 시도-실패를 통한 대화 구조를 가지며, 오류 수정 제안·중간 상태 표시·위험 작업 전 확인 등이 이 특성을 활용하는 설계 기법
  • 최악의 상호작용은 사용자를 무기력하게 만드는 적대적 대화, 최선은 성취감을 주는 유쾌한 교환

견고성 (Robustness)

  • 소프트웨어는 실제로도, 느낌으로도 견고해야 함
  • 예기치 않은 입력의 우아한 처리, 멱등성(idempotence) 유지, 진행 상황 안내, 스택 트레이스 노출 자제가 핵심
  • 복잡한 특수 케이스를 줄이고 단순하게 유지하면 견고성이 높아짐

공감 (Empathy)

  • CLI 도구는 개발자의 창의적 도구이므로 즐겁게 사용할 수 있어야 함
  • 사용자가 자신의 편임을 느낄 수 있도록 문제를 충분히 고민하고 설계할 것

혼돈 (Chaos)

  • 터미널 세계는 불일치로 가득하지만, 그 혼돈이 자유로운 창조의 원천이기도 함
  • "표준이 생산성이나 사용자 만족도에 명백히 해롭다면 그 표준을 버려라" — Jef Raskin

가이드라인 — 기본 (The Basics)

  • 인수 파싱 라이브러리를 사용할 것: 언어별 권장 라이브러리는 Go(Cobra, cli), Python(Click, Typer, Argparse), Rust(clap), Node(oclif) 등 다수
  • 성공 시 종료 코드 0, 실패 시 0이 아닌 코드 반환 — 스크립트가 성공·실패를 판별하는 기준
  • 기본 출력은 stdout, 로그·에러 등 메시지는 stderr로 전송

가이드라인 — 헬프 (Help)

  • -h 또는 --help 플래그에 상세 헬프 텍스트 표시, 서브커맨드에도 동일 적용
  • 인수 없이 실행 시 간결한 헬프 표시 (설명, 1~2개 예시, 플래그 설명, --help 안내 포함)
    • jq가 이를 잘 구현한 예시로 언급됨
  • --help / -h / help subcommand다양한 형태의 헬프 요청 모두 지원
  • 헬프 텍스트 상단에 웹 문서 링크 및 피드백 경로 제공
  • 예시를 먼저 보여줄 것 — 복잡한 활용 사례로 점진적으로 이어지는 스토리 구성 권장
  • 자주 쓰이는 플래그와 커맨드를 헬프 텍스트 상단에 배치 (git의 구성 방식 참조)
  • 굵은 제목 등 서식을 활용해 스캔하기 쉽게 구성하되, 터미널 독립적 방식 사용
  • 사용자가 잘못 입력했을 때 의도를 추측해 수정 제안 가능 — 단, 자동 실행은 신중하게 결정할 것
    • 잘못된 입력이 단순 오타가 아닌 논리적 실수일 수 있으며, 자동 수정 시 해당 구문을 영구 지원해야 하는 부담 발생

가이드라인 — 문서 (Documentation)

  • 웹 기반 문서 제공 — 검색 가능성과 링크 공유를 위해 필수
  • 터미널 기반 문서 제공 — 설치된 버전과 동기화되며 오프라인에서도 접근 가능
  • man 페이지 제공 고려 — ronn 같은 도구로 생성 가능하며, npm help ls처럼 서브커맨드로 접근하도록 지원 권장

가이드라인 — 출력 (Output)

  • 인간 가독성 최우선 — TTY 여부로 사람이 읽는지 판별
  • 텍스트 스트림은 UNIX의 보편 인터페이스로, 머신 가독 출력도 지원할 것
  • 인간 친화적 출력이 파이프 호환성을 해칠 경우 --plain 플래그로 일반 텍스트 출력 제공
  • --json 플래그 전달 시 JSON 형식 출력 지원
  • 성공 시 출력은 간결하게, 필요 없으면 출력 없이 — 스크립트용으로 -q 옵션으로 출력 억제 지원
  • 상태 변경 시 사용자에게 알림git push가 원격 브랜치 상태를 출력하는 것이 좋은 예
  • git status처럼 현재 시스템 상태를 쉽게 확인하고 다음 작업도 안내하는 출력 구성
  • 색상은 의도적으로 사용하고, 파이프 상태·NO_COLOR·TERM=dumb·--no-color 등 조건에서 색상 비활성화 필수
  • TTY가 아닌 환경에서는 애니메이션·스피너 표시 금지 (CI 로그 오염 방지)
  • 이모지·심볼은 명확성을 높이는 경우에만 사용 (yubikey-agent가 예시로 제시됨)
  • 개발자만 이해할 수 있는 정보는 기본 출력에서 제외, verbose 모드에서만 표시
  • stderr를 로그 파일처럼 사용하지 말 것 — 기본적으로 로그 레벨 레이블(ERR, WARN) 출력 자제
  • 대량 출력 시 less페이저 사용 고려 — TTY 환경에서만 활성화하고 less -FIRX 옵션 권장

가이드라인 — 에러 (Errors)

  • 예상 가능한 에러는 인간이 이해할 수 있는 메시지로 재작성 (예: "chmod +w file.txt 실행 필요")
  • 신호 대 잡음비 유지 — 같은 유형의 에러는 단일 헤더로 묶어 출력
  • 중요한 정보는 출력 끝부분에 배치 — 붉은 텍스트는 의도적으로, 드물게 사용
  • 예상치 못한 에러 발생 시 디버그 정보와 버그 리포트 제출 방법 안내 포함
  • 버그 리포트 URL에 정보를 자동 채워 제출하기 쉽게 구성

가이드라인 — 인수와 플래그 (Arguments and Flags)

  • 인수(args)는 위치 기반, 플래그(flags)는 이름 기반 — 플래그를 인수보다 선호할 것
  • 모든 플래그에 전체 이름 버전 제공 (예: -h--help 동시 지원)
  • 단일 문자 플래그는 자주 쓰이는 플래그에만 한정
  • 표준이 있는 경우 표준 플래그 이름 사용 (-f/--force, -q/--quiet, -v, --json 등)
  • 기본값은 대부분의 사용자에게 적합한 값으로 설정
  • 인수나 플래그 미전달 시 프롬프트로 입력 요청, 단 비대화형 환경에서는 프롬프트 강제 금지
  • 위험한 작업 전 확인 요청 — 위험 수준에 따라 y/n 확인, dry-run 제공, 또는 직접 텍스트 입력 요구
    • mild(파일 삭제), moderate(디렉토리 삭제, 원격 리소스 변경), severe(전체 서버 삭제)로 위험도 구분
  • 파일 입출력 시 -stdin/stdout 읽기/쓰기 지원 (예: curl ... | tar xvf -)
  • 플래그에서 시크릿 직접 수신 금지--password-file 플래그나 stdin 사용 권장 (ps 출력·셸 히스토리 노출 위험)

가이드라인 — 인터랙티비티 (Interactivity)

  • 프롬프트·인터랙티브 요소는 stdin이 TTY일 때만 표시
  • --no-input 전달 시 모든 프롬프트 비활성화
  • 비밀번호 입력 시 에코 비활성화 (입력 내용 화면 미표시)
  • 사용자가 언제든 탈출 가능하도록 명확히 안내 — Ctrl-C는 항상 동작하게 유지

가이드라인 — 서브커맨드 (Subcommands)

  • 서브커맨드 간 플래그 이름·출력 형식 일관성 유지
  • 복잡한 도구는 noun verb 또는 verb noun 형태의 2단계 서브커맨드 구조 사용 (예: docker container create)
  • 모호하거나 유사한 이름의 서브커맨드 회피 (예: update와 upgrade 동시 사용 지양)

가이드라인 — 견고성 (Robustness Guidelines)

  • 입력 검증을 초기에 수행하고, 잘못된 데이터는 이해하기 쉬운 에러와 함께 조기 종료
  • 응답성이 속도보다 중요 — 100ms 이내에 무언가 출력할 것
  • 오래 걸리는 작업에는 진행 표시줄(progress bar) 제공 — Python(tqdm), Go(schollz/progressbar), Node(node-progress) 라이브러리 활용 가능
  • 병렬 처리 시 출력이 뒤섞이지 않도록 주의
  • 네트워크 타임아웃 설정 — 기본값 포함, 영구 대기 방지
  • 일시적 오류 발생 후 재시도 시 이전 상태에서 재개 가능하도록 설계
  • crash-only 설계 — 정리 작업 없이 즉시 종료 가능한 구조로 멱등성 확보

가이드라인 — 미래 호환성 (Future-proofing)

  • 변경은 하위 호환 추가(additive) 방식으로 유지
  • 호환성 깨는 변경 전 프로그램 내에서 사전 경고 표시
  • 인간용 출력 변경은 일반적으로 허용 — 스크립트용은 --plain·--json 사용 유도
  • catch-all 서브커맨드 금지 — 나중에 해당 이름의 서브커맨드 추가 불가 문제 발생
  • 서브커맨드 약어 자동 허용 금지 — 명시적 alias만 허용, 안정적으로 유지
  • "타임 폭탄" 금지 — 20년 후에도 동작할 수 있도록 외부 의존성 최소화

가이드라인 — 시그널과 제어 문자 (Signals)

  • Ctrl-C(INT 시그널) 수신 시 즉시 종료, 정리 작업에 타임아웃 설정
  • 정리 중 Ctrl-C 재입력 시 강제 종료 가능하도록 안내 (Docker Compose 예시 참조)
  • 프로그램은 정리 작업이 완료되지 않은 상태에서 시작될 수 있음을 가정하고 설계

가이드라인 — 설정 (Configuration)

설정 적용 우선순위 (높음 → 낮음):

  • 플래그 → 현재 셸 환경변수 → 프로젝트 수준 설정(.env) → 사용자 수준 설정 → 시스템 전체 설정

설정 유형별 권장:

  • 호출마다 달라지는 설정 (디버그 수준, dry-run): 플래그 사용

  • 프로젝트·머신별로 다른 설정 (경로, 색상, HTTP 프록시): 플래그 + 환경변수 조합

  • 프로젝트 전체 공유 설정 (Makefile, package.json 유형): 버전 관리 파일 사용

  • XDG Base Directory 스펙 준수 — ~/.config 기반 설정 경로 권장 (yarn, fish, neovim, tmux 등 지원)

  • 다른 프로그램의 설정 파일 자동 수정 시 사용자 동의 획득 필수


가이드라인 — 환경변수 (Environment Variables)

  • 환경변수는 실행 컨텍스트에 따라 달라지는 동작에 적합
  • 이름은 대문자·숫자·언더스코어만 사용, 숫자로 시작 금지
  • 한 줄 값 권장 — 멀티라인은 env 명령과 호환 문제 발생
  • NO_COLOR, DEBUG, EDITOR, HTTP_PROXY, SHELL, TMPDIR, HOME, PAGER범용 환경변수 우선 확인
  • 프로젝트별 .env 파일 읽기 지원 권장 — 단, .env는 정식 설정 파일의 대체재가 아님
    • .env의 한계: 버전 관리 미포함, 이력 없음, 문자열 단일 타입, 인코딩 문제 취약
  • 환경변수에서 시크릿 읽기 금지 — 모든 프로세스로 전파, 로그 유출, Docker inspect·systemctl show로 노출 위험
    • 시크릿은 크리덴셜 파일, 파이프, AF_UNIX 소켓, 시크릿 관리 서비스를 통해서만 수신

가이드라인 — 네이밍 (Naming)

  • 단순하고 기억하기 쉬운 단어 사용 — 너무 일반적이면 다른 명령과 충돌 위험
  • 소문자와 필요 시 대시만 사용 (curl은 좋은 예, DownloadURL은 나쁜 예)
  • 짧게 유지하되, cd·ls·ps처럼 극도로 짧은 이름은 범용 유틸리티용으로 예약
  • Docker Compose의 전신인 plumfigdocker compose 개명 사례 — 타이핑 편의성이 네이밍의 중요 기준임을 보여주는 실제 사례

가이드라인 — 배포 (Distribution)

  • 가능하면 단일 바이너리로 배포 — PyInstaller 등 활용
  • 단일 바이너리 불가 시 플랫폼 네이티브 패키지 인스톨러 사용
  • 제거 방법을 설치 안내 하단에 명시

가이드라인 — 분석 데이터 (Analytics)

  • 사용자 동의 없이 사용 데이터·크래시 데이터 전송 금지
  • 수집 시 수집 항목, 이유, 익명화 방법, 보존 기간을 명확히 공개
  • 기본 opt-in 권장 — opt-out 방식이라면 첫 실행 또는 웹사이트에서 명확히 고지
    • Angular.js(명시적 opt-in), Homebrew(Google Analytics, FAQ 공개), Next.js(기본 활성화 익명 통계) 세 가지 사례 소개
  • 분석 대안으로 웹 문서 계측, 다운로드 수 계측, 사용자 직접 인터뷰 활용 가능
Hacker News 의견
  • 현재 많은 사람들이 커맨드 라인이 무엇인지 모르며, 왜 그것을 사용해야 하는지에 대해서도 관심이 없음.

    • 1980년대에도 이러한 상황은 마찬가지였지만, 현재는 그 어느 때보다 커맨드 라인을 아는 사람들이 많아졌음. CLI(커맨드 라인 인터페이스)의 황금기라고 할 수 있음.
  • 스크립트에서는 서브커맨드의 임의 축약을 허용하지 말 것. 예를 들어, mycmd install 대신 mycmd ins 또는 mycmd i를 허용하면, i로 시작하는 새로운 커맨드를 추가할 수 없게 됨.

    • 스크립트에서 짧은 인자 사용을 피해야 함. 짧은 인자는 사람이 사용할 때 타이핑을 줄이기 위한 편의성을 제공하지만, 스크립트에서는 명시적으로 작성하는 것이 비용이 적고, 읽기와 쓰기 비율을 고려할 때 더 바람직함.
  • --dry-run 옵션을 고려해볼 것. 실제 변경을 하지 않고 어떤 작업이 수행될지 미리 보여주는 기능은 도구를 배우고 복잡한 옵션을 올바르게 설정했는지 확인하는 데 매우 유용함.

  • stdout이 대화형 터미널이 아닌 경우, 애니메이션을 표시하지 말 것. 이는 CI 로그 출력에서 진행 상황 바가 크리스마스트리처럼 변하는 것을 방지함.

    • stdout에서는 절대 애니메이션을 표시하지 말 것. stderr는 로깅, 정보 제공 등을 위한 것이며, stdout은 tty 여부와 관계없이 유용한 출력을 제공해야 함.
  • 심볼과 이모지는 명확성을 높일 때만 사용할 것.

    • 심볼과 이모지는 터미널 간에 렌더링이 일관되지 않을 수 있으며, 사용자의 취향에 따라 호불호가 갈릴 수 있으므로 매우 신중하게 사용해야 함.
  • 현재 Unix 커맨드 라인은 한편으로는 "놀라울 정도로 유용"하고 다른 한편으로는 "설계상의 결함"이 있음.

    • Unix 커맨드 라인이 유용한 이유는 C나 Rust로 같은 작업을 수행하는 데 걸리는 시간을 생각해보면 알 수 있음.
    • 설계상의 결함은 커맨드 라인 인터페이스가 동시에 인간과 기계가 읽을 수 있어야 한다는 점에서 비롯됨. 이 문제를 해결하는 정석적인 방법은 없음.
  • CLI가 매우 크고 중첩이 필요한 경우(예: aws)를 제외하고, 대부분의 앱은 모든 옵션을 도움말에 출력하고 사용자가 less를 사용하여 필요한 내용을 찾게 하는 것을 선호함.

  • 전통적으로 UNIX 명령어는 다른 프로그램에 의해 주로 사용될 것이라고 가정하에 작성됨.

    • 실제로는 대화형 로그인 쉘 내에서 상호작용적으로 사용되기 위해 의도되었음. 출력을 생성하는 프로그램과 "조용한" 텍스트 필터로 나뉘며, 복잡한 프로그램은 C로 작성됨.
  • 환경 변수에서 비밀번호를 읽지 말 것.

    • 비밀번호는 크리덴셜 파일, 파이프, AF_UNIX 소켓, 비밀 관리 서비스 또는 다른 IPC 메커니즘을 통해서만 받아야 함.
  • CLI 가이드라인에 대한 가장 포괄적인 책은 Eric Raymond의 저서임.

    • 오랜 시간이 지났지만, clig.dev를 훑어보니 시간이 지남에 따라 의견이 많이 변화했음을 알 수 있음.