Skip to content

Latest commit

 

History

History
809 lines (570 loc) · 14.8 KB

File metadata and controls

809 lines (570 loc) · 14.8 KB

CLI Design Anti-Patterns

A comprehensive guide to what NOT to do when building command-line interfaces

These anti-patterns represent common mistakes that frustrate users and make CLIs harder to use. Each pattern includes the problem, why it's harmful, and how to fix it.

The Big Mistakes

1. ❌ Mystery Meat Commands

The Mistake:

$ mkcfg 3 prod ~/config
$ dpl -e p -S

Why It's Bad:

  • Cryptic abbreviations confuse users
  • Positional arguments are hard to remember
  • No clear indication of what will happen

The Fix:

$ myapp config create --type production --output ~/config
$ myapp deploy --environment production --skip-tests

Real Example: Early versions of kubectl used kubectl run for multiple purposes (creating deployments, jobs, pods). They fixed it by separating into clear commands: kubectl create deployment, kubectl create job.


2. ❌ Wall of Text Syndrome

The Mistake:

$ myapp --help
Usage: myapp [--verbose|-v] [--quiet|-q] [--config|-c FILE] [--no-color]
[--format|-f FORMAT] [--output|-o FILE] [--input|-i FILE] [--recursive|-r]
[--force] [--dry-run] [--yes|-y] [--timeout DURATION] [--retries N]
[--parallel|-p N] [--profile PROFILE] [--region REGION] [--api-key KEY]
[--api-secret SECRET] [--endpoint URL] [--insecure] [--cert FILE]
[--key FILE] [--ca FILE] [--help|-h] [--version] COMMAND [ARGS...]

Why It's Bad:

  • Overwhelming and hard to scan
  • No visual hierarchy
  • Users can't find what they need

The Fix:

$ myapp --help
Usage: myapp [options] <command> [args]

Commands:
  init      Initialize a new project
  deploy    Deploy your application
  status    Check deployment status

Common options:
  -h, --help     Show help
  -v, --version  Show version

Run 'myapp <command> --help' for command details

3. ❌ Unhelpful Error Messages

The Mistake:

$ myapp deploy
Error: ENOENT

$ myapp init
Error: Operation failed

$ myapp config set key
Error: Invalid input

Why It's Bad:

  • Doesn't explain what went wrong
  • No guidance on how to fix it
  • Frustrates users

The Fix:

$ myapp deploy
Error: No config file found

Could not find 'myapp.config.yml' in current directory.

To fix this:
  1. Run 'myapp init' to create a config file
  2. Or specify a config with --config <path>

$ myapp config set key
Error: Missing value for 'key'

Usage: myapp config set <key> <value>

Example:
  myapp config set api.endpoint https://api.example.com

4. ❌ Silent Failures

The Mistake:

$ myapp process data.json
$ echo $?
0
$ ls
data.json  # No output file, but no error either!

Why It's Bad:

  • Users think operation succeeded
  • Wastes time debugging
  • Breaks automation

The Fix:

$ myapp process data.json
Error: Failed to process data.json

Input file is empty (0 bytes)

$ echo $?
1

5. ❌ Inconsistent Flag Styles

The Mistake:

$ myapp create --projectName my-project
$ myapp delete -proj my-project
$ myapp list --project_type=web
$ myapp status -p:my-project

Why It's Bad:

  • Users can't remember the right format
  • Breaks muscle memory
  • Looks unprofessional

The Fix:

$ myapp create --project-name my-project
$ myapp delete --project my-project
$ myapp list --project-type web
$ myapp status --project my-project

# Always use:
# - Lowercase with hyphens for long flags
# - Single letters for short flags
# - Consistent naming across commands

6. ❌ No Progress Feedback

The Mistake:

$ myapp process large-dataset.csv
# Nothing happens for 5 minutes...
# Is it working? Frozen? How long will it take?

Why It's Bad:

  • Users think it's broken
  • Can't estimate completion time
  • Leads to Ctrl+C interruptions

The Fix:

$ myapp process large-dataset.csv
Processing dataset (2.3GB)...
[████████████░░░░░░░░] 60% | 1.4GB/2.3GB | ETA: 2m 15s

7. ❌ Buried Important Information

The Mistake:

$ myapp deploy
Initializing...
Loading configuration...
Validating settings...
Checking dependencies...
Preparing artifacts...
Building container...
Optimizing assets...
Compressing files...
WARNING: No backup configured - data loss possible!
Uploading to server...
Configuring services...
Starting application...
Deployment complete!

Why It's Bad:

  • Critical warning hidden in output
  • Users miss important information
  • Can lead to data loss

The Fix:

$ myapp deploy

⚠  WARNING: No backup configured - data loss possible!
   Configure backups with: myapp config set backup.enabled true

Continue deployment? [y/N]: _

8. ❌ Required Config Files

The Mistake:

$ myapp list
Error: Config file not found
Please create .myapp.yml

$ echo 'version: 1' > .myapp.yml
$ myapp list
Error: Invalid config: missing required field 'api_key'

Why It's Bad:

  • Can't try the tool quickly
  • Requires setup before basic operations
  • Frustrating onboarding

The Fix:

$ myapp list
Using default settings (no config file found)

Projects:
- my-project
- another-project

Tip: Run 'myapp init' to create a config file

9. ❌ Non-Standard Conventions

The Mistake:

$ myapp /h          # Instead of --help
$ myapp -version    # Instead of --version
$ myapp quit        # Instead of exit code
$ myapp --yes=true  # Instead of just --yes

Why It's Bad:

  • Breaks user expectations
  • Harder to learn
  • Incompatible with shell conventions

The Fix:

$ myapp --help      # or -h
$ myapp --version   # or -v
$ myapp && echo "Success"
$ myapp --yes       # Boolean flags don't need values

10. ❌ No Examples in Help

The Mistake:

$ myapp transform --help
Usage: myapp transform [options] <input> <output>

Options:
  -f, --format FORMAT    Output format
  -t, --template FILE    Template file
  -v, --variables FILE   Variables file
  -s, --strict          Strict mode

Why It's Bad:

  • Abstract descriptions aren't helpful
  • Users have to guess usage
  • Increases support burden

The Fix:

$ myapp transform --help
Transform files using templates

Usage: myapp transform [options] <input> <output>

Options:
  -f, --format FORMAT    Output format (json, yaml, xml)
  -t, --template FILE    Template file path
  -v, --variables FILE   Variables file for template

Examples:
  # Transform JSON to YAML
  myapp transform data.json data.yaml --format yaml

  # Apply template with variables
  myapp transform input.json output.html \
    --template report.tmpl \
    --variables vars.json

Platform-Specific Mistakes

Windows Mistakes

# Bad: Unix-only paths
$ myapp --config ~/.myapp/config

# Good: Cross-platform paths
$ myapp --config $HOME/.myapp/config

Shell Integration Mistakes

# Bad: Breaks in pipes
$ myapp list | grep active
Error: Cannot use color output in pipe

# Good: Detects pipe automatically
$ myapp list | grep active  # Works, no color

How to Avoid These Mistakes

During Design

  1. Write help text first - If it's hard to explain, it's hard to use
  2. Test with new users - Watch them struggle, fix the pain points
  3. Follow conventions - Don't reinvent the wheel
  4. Start simple - You can always add features later

During Development

  1. Handle all error cases - Every error needs a helpful message
  2. Add progress indicators - Any operation over 1 second
  3. Support scripting - Exit codes, quiet mode, JSON output
  4. Test edge cases - Empty files, network failures, interruptions

Before Release

  1. Review all error messages - Are they helpful?
  2. Check platform compatibility - Windows, Mac, Linux
  3. Verify pipe behavior - Does it work in scripts?
  4. Get user feedback - Beta test with real users

The Golden Rules

If you remember nothing else:

  1. Clear is better than clever
  2. Examples are better than descriptions
  3. Errors should help, not frustrate
  4. Follow existing conventions
  5. Test with real users

Additional Anti-Patterns

11. ❌ Inconsistent Terminology

The Mistake: Using different terms for the same concept across commands.

# Different words for the same action
$ myapp new project
$ myapp create-user john
$ myapp init config
$ myapp generate report

Why It's Bad:

  • Users can't predict command names
  • Harder to discover features
  • Increases cognitive load

The Fix: Pick one verb and stick with it:

$ myapp create project
$ myapp create user john
$ myapp create config
$ myapp create report

12. ❌ Positional Argument Hell

The Mistake:

$ deploy-tool production api-server v2.1.0 true false 3 /tmp/logs
# What does each argument mean?

Why It's Bad:

  • Impossible to remember argument order
  • Easy to swap arguments accidentally
  • No self-documentation

The Fix:

$ deploy-tool \
  --environment production \
  --service api-server \
  --version v2.1.0 \
  --skip-tests \
  --no-backup \
  --replicas 3 \
  --log-dir /tmp/logs

13. ❌ Hidden Dependencies

The Mistake:

$ myapp start
Error: jq not found in PATH

Why It's Bad:

  • Surprises users at runtime
  • No indication of what's needed
  • Breaks in minimal environments

The Fix:

$ myapp doctor
Checking dependencies...
  ✓ git (2.34.0)
  ✗ jq (required for JSON processing)
  ✓ curl (7.81.0)

To install missing dependencies:
  brew install jq    # macOS
  apt install jq     # Ubuntu/Debian

14. ❌ Destructive Defaults

The Mistake:

$ cleanup
Deleting all files in /home/user...
[No confirmation, starts immediately]

Why It's Bad:

  • Data loss without warning
  • No chance to reconsider
  • Violates principle of least surprise

The Fix:

$ cleanup
This will delete 1,234 files (5.6 GB) from /home/user

Files to be deleted:
  • *.tmp (456 files)
  • *.log (678 files)
  • cache/* (100 files)

Continue? [y/N]: _

15. ❌ State Soup

The Mistake:

$ myapp set-env production
$ myapp set-region us-west
$ myapp set-profile admin
$ myapp deploy  # Uses hidden state from above commands

Why It's Bad:

  • Hidden state causes surprises
  • Hard to reproduce commands
  • Context not clear from command alone

The Fix:

# Explicit context
$ myapp deploy --env production --region us-west --profile admin

# Or with context command
$ myapp --context prod-west deploy

16. ❌ Magical Behavior

The Mistake:

$ build
# Detects project type and runs different commands
# Node.js → npm build
# Python → python setup.py build
# Go → go build
# But with different behaviors and options!

Why It's Bad:

  • Unpredictable behavior
  • Hard to debug when wrong
  • Documentation nightmare

The Fix:

$ build --type node
$ build --type python
# Or better: use native tools
$ npm build
$ python setup.py build

17. ❌ Flag Soup

The Mistake:

$ app --verbose --debug --trace --log-level=debug \
      --output-format=json --pretty --color --progress \
      --timeout=30 --retry=3 --retry-delay=5 \
      --config=./app.yml --env-file=.env \
      --profile=dev --context=test \
      action

Why It's Bad:

  • Overwhelming option count
  • Overlapping functionality
  • Hard to remember all flags

The Fix: Group related options and use config files:

$ app action --profile dev  # Profile includes all settings
$ app action -vvv          # Verbosity levels instead of multiple flags

18. ❌ Missing Dry Run

The Mistake:

$ migrate-database --from v1 --to v2
Migrating production database...
[Already running, no way to preview]

Why It's Bad:

  • No way to preview changes
  • Can't verify before execution
  • Risky for production operations

The Fix:

$ migrate-database --from v1 --to v2 --dry-run
Would perform the following migrations:
  1. Add column 'email_verified' to users table
  2. Create index on orders.created_at
  3. Drop deprecated 'legacy_data' table

No changes made. Run without --dry-run to apply.

19. ❌ Ignored Piping

The Mistake:

$ myapp list | grep active
Error: Cannot use interactive mode when piped

Why It's Bad:

  • Breaks Unix philosophy
  • Prevents automation
  • Forces workarounds

The Fix:

$ myapp list | grep active
active-service-1
active-service-2

# Auto-detect pipe and adjust output
if [ -t 1 ]; then
  # Terminal: use colors and formatting
else
  # Pipe: plain text output
fi

20. ❌ Version Hell

The Mistake:

$ myapp version
2.1.0

$ myapp --version
myapp v2.1.0

$ myapp -v
Verbose mode enabled...  # -v is verbose, not version!

Why It's Bad:

  • Inconsistent version flags
  • Conflicts with common conventions
  • Confuses users

The Fix:

$ myapp --version
myapp version 2.1.0

$ myapp version
myapp version 2.1.0
Build: 2023-10-15T10:30:00Z
Commit: abc123def

$ myapp -V  # Capital V for version if -v is verbose
myapp version 2.1.0

Categories of Anti-Patterns

Input Anti-Patterns

  • Mystery meat commands
  • Positional argument hell
  • Flag soup
  • Inconsistent terminology

Output Anti-Patterns

  • Wall of text syndrome
  • Buried important information
  • Ignored piping
  • No progress feedback

Error Anti-Patterns

  • Unhelpful error messages
  • Silent failures
  • Missing error recovery
  • Poor error codes

Design Anti-Patterns

  • Required config files
  • Hidden dependencies
  • State soup
  • Magical behavior

Safety Anti-Patterns

  • Destructive defaults
  • Missing dry run
  • No confirmations
  • No undo/rollback

Convention Anti-Patterns

  • Non-standard conventions
  • Version hell
  • Inconsistent flag styles
  • Platform-specific assumptions

Quick Anti-Pattern Checklist

Before releasing your CLI, ensure you're NOT doing any of these:

Classic Mistakes:

  • Using cryptic abbreviated commands
  • Requiring positional arguments for everything
  • Showing walls of text without structure
  • Giving unhelpful error messages
  • Failing silently without feedback
  • Using inconsistent flag formats
  • Requiring configuration before basic use
  • Performing destructive actions without confirmation
  • Ignoring pipe detection
  • Breaking platform conventions

Common Issues:

  • Hiding important warnings in output
  • Using hidden global state
  • Having unclear command purposes
  • Missing --help on commands
  • Lacking examples in help text

Learn More

Remember: Every anti-pattern here was discovered through real user frustration. Learn from these mistakes to build CLIs that users love rather than tolerate.