What makes a good script?
Four properties separate scripts you trust from ones that cause surprises — idempotency, dry-run mode, clear output, and graceful errors.
- List the four properties of a trustworthy automation script
- Explain what idempotency means and why it matters
- Describe what dry-run mode does and when to add it
A script that runs once without crashing is not the same as a script you trust. The gap between the two is a handful of design choices. Get them right from the start and your scripts remain usable months later, by other people, in contexts you didn't anticipate.
1. Idempotent
An idempotent script produces the same result whether you run it once or ten times. Running it twice does not double the work, corrupt the output, or leave the system in a broken state.
Why it matters: automation pipelines fail partway through. If a script is idempotent, you can safely re-run it after a failure and it will finish without redoing completed work or making things worse.
The simplest way to get there: before doing something, check whether it is already done.
output = Path("processed/report.csv")
if output.exists():
print(f"Already done: {output}")
else:
# do the work2. Dry-run mode
A dry-run flag lets you see what the script would do without actually doing it. This is invaluable when you are about to run a bulk operation on real data and want to verify the logic first.
# With --dry-run, print the action but skip the file operation
if dry_run:
print(f"[dry run] would copy {src} -> {dst}")
else:
shutil.copy(src, dst)
print(f"copied {src} -> {dst}")The discipline: every destructive operation in your script should have a dry-run path. If there are no destructive operations, dry-run still useful as a preview.
3. Clear output
A silent script is an anxious script. When automation runs unattended, you need to know what happened. Print what the script did, and print it in a way that is easy to scan:
print(f"Processing {len(files)} files...")
for f in files:
# ... do work ...
print(f" done: {f.name}")
print("All done.")Write to standard output for normal progress, standard error for warnings and errors. Keep each line short. Avoid printing binary data or enormous structures — summarise them instead.
4. Graceful errors
Scripts that silently swallow errors are the hardest to debug. When something unexpected happens, your script should fail loudly and early — with a message that tells you what went wrong and where.
try:
with open(input_file) as f:
data = f.read()
except FileNotFoundError:
print(f"Error: input file not found: {input_file}", file=sys.stderr)
sys.exit(1)sys.exit(1) signals failure to whatever called the script (a shell, a CI runner, a
cron job). Exit code 0 means success; anything else means failure. If you let exceptions
propagate uncaught, Python will print a traceback — which is better than silence, but a
user-facing error message is better still.
You do not need all four properties on every throwaway one-liner. But for any script that will run on shared infrastructure, run on a schedule, or touch data you care about, all four are worth the ten minutes they take to add.
Check your understanding
Knowledge check
- 1.What does it mean for a script to be idempotent?
- 2.A dry-run flag should perform all the same file operations as a normal run, just more slowly.
- 3.Which of these are properties of a trustworthy automation script?
Where to go next
Next: building a CLI script — putting these principles into practice by structuring
a real script with argparse, a main() function, and a --dry-run flag.