Case — an evening's script
the most dangerous genre
What it is
The last case is the smallest and the most dangerous. A twenty-line task: sort out a folder of photos by renaming the files after the date they were taken.
The danger with scripts like this isn't their complexity but that nobody takes them seriously. No mini-spec is written, no tests, no review — "it's only a rename". And it is run straight against the real folder.
Step 1. What arrived
Write a python script that renames all the files in the
photos folder by modification date, format YYYY-MM-DD.import os
from datetime import datetime
from pathlib import Path
folder = Path("photos")
for f in folder.iterdir():
date = datetime.fromtimestamp(f.stat().st_mtime)
new_name = date.strftime("%Y-%m-%d") + f.suffix
f.rename(folder / new_name)Nine lines, readable in one go, doing exactly what was asked. It is hard to imagine what could break here.
Step 2. What it did
The folder held four files:
IMG_1001.jpg "first photo" taken 14 March
IMG_1002.jpg "second photo" taken 14 March
IMG_1003.jpg "third photo" taken 15 March
notes.txt "not a photo"After running it:
2026-03-14.jpg "second photo"
2026-03-15.jpg "third photo"
2026-03-15.txt "not a photo"There are three files now. first photo is nowhere.
Two photos from the same day got the same name, and the second quietly
wrote over the first: rename in Python doesn't ask permission and doesn't
warn. notes.txt, which nobody asked it to touch, was renamed along the
way.
No error, no warning, no exit code. The script ran successfully.
Step 3. How it should have been asked
A Python 3.12 script: rename the photos in a folder by the
file's modification date into the format YYYY-MM-DD.
Required:
- by default change NOTHING, only show the plan
"old name -> new"; carry it out on an --apply flag
- if several files share a date: -2, -3 and so on
- if a target name is already taken by a file not in this
list — stop and do nothing
- touch only .jpg/.jpeg/.png/.heic, skip the rest and list
what was skipped
- take the folder as an argument, don't hard-wire itSix lines of requirements for nine lines of code. It looks excessive right up until the first lost photograph.
The first is the important one. A preview mode by default turns a dangerous script into a safe one: first you see what will happen, then you allow it.
Step 4. What came out
$ python3 rename.py photos
IMG_1001.jpg -> 2026-03-14.jpg
IMG_1002.jpg -> 2026-03-14-2.jpg
IMG_1003.jpg -> 2026-03-15.jpg
skipped (not a photo): notes.txt
nothing changed. files to rename: 3
repeat with --apply to carry it outThe whole plan is visible, and so is the main thing: the two files from 14 March get different names. Now it can be allowed:
$ python3 rename.py photos --apply
...
renamed: 3The result:
2026-03-14.jpg "first photo"
2026-03-14-2.jpg "second photo"
2026-03-15.jpg "third photo"
notes.txt "not a photo"Four files went in, four came out. Nothing was lost, notes.txt wasn't
touched.
Step 5. A rule for the whole genre
Scripts that change something outside themselves — files, a database, emails, messages — differ from other code in one way: a mistake can't be rolled back. So there is a short list for them worth applying every time:
- A preview mode by default. Execution on an explicit flag.
- On a copy first.
cp -r photos photos-trialtakes a second. - Never overwrite silently. Name clash — stop.
- Filter by type. "All the files in the folder" is almost never what was meant.
- A report at the end. How many processed, how many skipped and why.
- The folder is an argument, not a constant. One day someone forgets to change the constant.
Not one item takes any effort. All six together are the difference between a tool and a game of roulette.
Cheat sheet
The most dangerous code is the shortest: nobody takes it seriously.
renameoverwrites silently: two files with one date means one file fewer.A script that changes files has no undo. No history, no recycle bin.
A preview mode by default is the main protection, and it costs one line of requirements.
"All the files in the folder" almost never means what you meant.
The first run goes against a copy of the folder, not the real one.
Six lines of requirements for nine lines of code is a normal proportion.