Filesystem code is where “works on my machine” bugs are born. In Go, most IO is explicit and low-level — which is good, but you must be disciplined.
- Always handle errors.
- Always close resources.
- Design for portability (Windows vs Unix differences).
io/fs enables abstraction over real filesystems.
Benefits:
- can test using
fstest.MapFS - can run logic against embedded filesystems
Common safe pattern:
- write to temp file
- fsync if required
- rename over target
Pitfalls:
- rename semantics differ across platforms
- atomicity is per-filesystem boundary
- Use
path/filepathfor OS paths. - Use
pathfor slash-separated paths (URLs).
Pitfall:
- hardcoding
/breaks on Windows.
Be explicit about file modes and ownership assumptions.
Instead of calling signal.Notify everywhere:
- isolate signal handling in one place
- inject a channel into components
- Forgetting to close files
- Not handling partial writes
- Using
pathinstead offilepath
- Use atomic write patterns for configs/state
- Keep path handling portable
- Use io/fs abstractions for testability
- Avoid leaking file descriptors
These exercises validate:
- atomic write semantics
- portable path behavior
- filesystem abstraction via io/fs
- signal-aware shutdown patterns with injected channels