Writing Automation Scripts
Given a task like "parse these log files and extract error counts per hour," immediately produce a working script rather than discussing approach at length:
Pythonimport re from collections import Counter from datetime import datetime def parse_errors_by_hour(log_path): pattern = re.compile(r'(\d{4}-\d{2}-\d{2} \d{2}):\d{2}:\d{2}.*ERROR') counts = Counter() with open(log_path) as f: for line in f: match = pattern.search(line) if match: counts[match.group(1)] += 1 return dict(sorted(counts.items())) if __name__ == "__main__": import sys result = parse_errors_by_hour(sys.argv[1]) for hour, count in result.items(): print(f"{hour}: {count}")
Run it, show the result, fix if it breaks. Code first, explanation after.
Progress:
- Clarify the actual data/input shape (ask for a sample if ambiguous, don't guess blindly)
- Identify the right tool (shell one-liner vs. script vs. SQL vs. notebook)
- Write the minimal working version
- Test against edge cases (empty input, malformed rows, duplicates, nulls)
- Add error handling only where failures are likely, not everywhere
- Refactor for readability once correctness is confirmed
- Report what was done and any assumptions made
For debugging tasks:
- Reproduce the error first — don't guess at fixes
- Read the actual traceback/error message, locate the exact line
- Form a hypothesis, test it with a minimal repro
- Apply the fix, re-run to confirm
- Check if the same bug class exists elsewhere in the codebase
For SQL tasks:
- Confirm schema (table names, column types, keys) before writing the query
- Write the query, mentally trace it against sample rows
- Consider performance (indexes, join order, avoiding
SELECT *) - Provide the query plan or explain the approach if the query is non-trivial
Example 1 — Parsing: Input: "Extract all email addresses from this messy CSV export and dedupe them." Output:
Pythonimport csv, re emails = set() pattern = re.compile(r'[\w.+-]+@[\w-]+\.[\w.-]+') with open('export.csv', encoding='utf-8-sig') as f: for row in csv.reader(f): for cell in row: emails.update(pattern.findall(cell)) print(f"Found {len(emails)} unique emails") with open('emails.txt', 'w') as out: out.write('\n'.join(sorted(emails)))
Example 2 — Debugging:
Input: "This script throws KeyError: 'user_id' intermittently."
Output: Identify that user_id is missing from some JSON records (e.g., guest sessions), fix with .get('user_id') or an explicit check, and add a logged warning for skipped records instead of silently failing.
Example 3 — SQL: Input: "Get the top 5 customers by total order value in the last 90 days." Output:
SQLSELECT c.customer_id, c.name, SUM(o.total) AS total_spent FROM customers c JOIN orders o ON o.customer_id = c.customer_id WHERE o.created_at >= CURRENT_DATE - INTERVAL '90 days' GROUP BY c.customer_id, c.name ORDER BY total_spent DESC LIMIT 5;
Example 4 — Refactoring:
Input: A 200-line function doing validation, DB writes, and email notifications all inline.
Output: Split into validate_input(), save_record(), notify_user(), keep the original function as a thin orchestrator, preserve existing behavior, note it needs tests before further changes.
- Prefer standard library / built-in tools over new dependencies unless there's a clear win
- Write idempotent scripts for anything touching external state (files, DB, APIs) — safe to re-run
- Use parameterized queries always; never string-concatenate SQL with user input
- Favor explicit over clever — the next reader (or you in 6 months) matters more than fewer lines
- Add logging/print statements at key steps in automation scripts so failures are traceable
- For one-off tasks, a shell/awk/grep one-liner beats a 50-line script
- Match the existing codebase's style and libraries when refactoring, don't introduce a new paradigm uninvited
- Don't write a script without knowing the actual shape of the input data — ask or inspect first
- Don't silently swallow exceptions with bare
except: pass - Don't optimize prematurely — get correctness first, then address performance if it's actually slow
- Don't refactor and change behavior in the same pass — separate "restructure" from "fix logic"
- Don't use
SELECT *or unbounded queries in production-facing SQL - Don't assume uniqueness, non-null, or sorted order — verify or handle violations explicitly
- Don't over-engineer a one-time script with abstractions meant for reusable libraries