cd /news/developer-tools/the-quiet-design-choices-behind-a-50… · home topics developer-tools article
[ARTICLE · art-90928] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

The Quiet Design Choices Behind a 50-Line CLI Tool

A developer building a 50-line CLI tool that sorts files by type shared three design decisions: using pathlib over os.path for robust extension handling, implementing a dry-run flag before destructive operations, and using a dict instead of an if/elif chain for maintainability. The choices prioritize explicit behavior, safety, and separation of data from logic.

read3 min views1 publishedAug 10, 2026

I'm building a CLI tool that scans a directory and sorts files into folders by type. Small in scope, but every small tool still forces real design decisions. Here are three from this week, and the reasoning behind each.

pathlib over os.path

I chose pathlib

over os.path

for one core reason: paths aren't strings, and treating them as strings invites bugs a purpose-built API already solves. os.path

returns plain strings, so any extension logic has to be hand-rolled, and compound extensions like archive.tar.gz

are where that breaks down.

name, ext = os.path.splitext("archive.tar.gz")

Path("archive.tar.gz").suffix     # '.gz'   — last suffix only
Path("archive.tar.gz").suffixes   # ['.tar', '.gz']  — all of them, if you ask for them

pathlib

doesn't make compound extensions disappear as a problem — .suffix

still only gives you the last one. What it gives you is a typed, documented way to choose which behaviour you want (.suffix

vs .suffixes

) instead of guessing at string-split indices and finding out you guessed wrong when a .tar.gz

file quietly gets misclassified.

Why dry run exist before any destructive operation?

I added a dry-run flag because moving files isn't trivially reversible, and previewing an operation is free. On a personal machine with well-understood files, a wrong move is a minor inconvenience. But the same operation gets genuinely risky once files are shared, moved across filesystems (where the operation isn't atomic), or simply irreplaceable. The flag doesn't assume which situation you're in; it just makes the cost of finding out in advance effectively zero.

$ python3 main.py --source ~/downloads --dry-run
[DRY RUN] invoice.pdf -> Documents/
[DRY RUN] photo.jpg -> Images/
[DRY RUN] script.py -> Code/

A dry run is only trustworthy if it can't lie to you. The branch that decides what file goes where runs identically in both modes; classification happens before the dry-run check, not inside it. The only thing the flag changes is the last step: print the destination instead of calling the move. Same logic, same output shape, one line different.

A dict, not an if/elif chain

An if/elif chain fuses data and logic — adding a new file type means editing the function's control flow directly, which risks breaking existing, already-tested branches.

if ext == ".pdf":
    return "Documents"
elif ext == ".jpg":
    return "Images"
elif ext == ".py":
    return "Code"

EXTENSION_MAP = {".pdf": "Documents", ".jpg": "Images", ".py": "Code"}
return EXTENSION_MAP.get(ext, "Other")

The practical payoff isn't just tidiness. Because the mapping is plain data, it doesn't have to stay Python code at all; it's one small step away from living in a config.json

a non-programmer could edit, instead of being buried inside a function only I can safely touch.

None of these decisions is novel on their own. What made them worth writing down is the habit of asking why before writing the obvious version, the one that works today but quietly costs more later.

── more in #developer-tools 4 stories · sorted by recency
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/the-quiet-design-cho…] indexed:0 read:3min 2026-08-10 ·