Coding Effectively
Required Sub-Skills
ALWAYS REQUIRED:
- •
howto-functional-vs-imperative- Separate pure logic from side effects - •
defense-in-depth- Validate at every layer data passes through
CONDITIONAL: Use these sub-skills when applicable:
- •
howto-develop-with-postgres- PostgreSQL database code - •
writing-good-tests- Writing or reviewing tests - •
property-based-testing- Tests for serialization, validation, normalization, pure functions
Property-Driven Design
When designing features, think about properties upfront. This surfaces design gaps early.
Discovery questions:
| Question | Property Type | Example |
|---|---|---|
| Does it have an inverse operation? | Roundtrip | decode(encode(x)) == x |
| Is applying it twice the same as once? | Idempotence | f(f(x)) == f(x) |
| What quantities are preserved? | Invariants | Length, sum, count unchanged |
| Is order of arguments irrelevant? | Commutativity | f(a, b) == f(b, a) |
| Can operations be regrouped? | Associativity | f(f(a,b), c) == f(a, f(b,c)) |
| Is there a neutral element? | Identity | f(x, 0) == x |
| Is there a reference implementation? | Oracle | new(x) == old(x) |
| Can output be easily verified? | Easy to verify | is_sorted(sort(x)) |
Common design questions these reveal:
- •"What about deleted/deactivated entities?"
- •"Case-sensitive or not?"
- •"Stable sort or not? Tie-breaking rules?"
- •"Which algorithm? Configurable?"
Surface these during design, not during debugging.
Core Engineering Principles
Correctness Over Convenience
Model the full error space. No shortcuts.
- •Handle all edge cases: race conditions, timing issues, partial failures
- •Use the type system to encode correctness constraints
- •Prefer compile-time guarantees over runtime checks where possible
- •When uncertain, explore and iterate rather than assume
Don't:
- •Simplify error handling to save time
- •Ignore edge cases because "they probably won't happen"
- •Use
anyor equivalent to bypass type checking
Error Handling Philosophy
Two-tier model:
- •User-facing errors: Semantic exit codes, rich diagnostics, actionable messages
- •Internal errors: Programming errors that may panic or use internal types
Error message format: Lowercase sentence fragments for "failed to {message}".
Good: failed to connect to database: connection refused Bad: Failed to Connect to Database: Connection Refused Good: invalid configuration: missing required field 'apiKey' Bad: Invalid Configuration: Missing Required Field 'apiKey'
Lowercase fragments compose naturally: "operation failed: " + error.message reads correctly.
Always use proper logging, and errors should always be logged via the language/framework appropriate methods.
Pragmatic Incrementalism
- •Prefer specific, composable logic over abstract frameworks
- •Evolve design incrementally rather than perfect upfront architecture
- •Don't build for hypothetical future requirements
- •Document design decisions and trade-offs when making non-obvious choices
The rule of three applies to abstraction: Don't abstract until you've seen the pattern three times. Three similar lines of code is better than a premature abstraction.
File Organization
Descriptive File Names Over Catch-All Files
Name files by what they contain, not by generic categories.
Don't create:
- •
utils.go- Becomes a dumping ground for unrelated functions - •
helpers.js- Same problem - •
common.rs- What isn't common? - •
misc.go- Actively unhelpful
Do create:
- •
formatter.go- String manipulation utilities - •
calendar/math.go- Date calculations - •
api/src/error_handling.rs- API error utilities - •
user-input.ts- User input validation
Why this matters:
- •Discoverability: Developers find code by scanning file names
- •Cohesion: Related code stays together
- •Prevents bloat: Hard to add unrelated code to
string-formatting.ts - •Import clarity:
import { formatDate } from './date-arithmetic'is self-documenting
When you're tempted to create utils.ts: Stop. Ask what the functions have in common. Name the file after that commonality.
Module Organization
- •Keep module boundaries strict with restricted visibility
- •Platform-specific code in separate files:
unix.go,windows.go,posix.go - •Use conditional compilation or runtime checks for platform branching
- •Test helpers in dedicated modules/files, not mixed with production code
Cross-Platform Principles
Use OS-Native Logic
Don't emulate Unix on Windows or vice versa. Use each platform's native patterns.
Bad: Trying to make Windows paths behave like Unix paths everywhere.
Good: Accept platform differences, handle them explicitly.
// Platform-specific behavior
if (process.platform === "win32") {
// Windows-native approach
} else {
// POSIX approach
}
Platform-Specific Files
When platform differences are significant, use separate files:
process/spawn.go // Shared interface and logic process/unix.go // Unix-specific implementation process/windows.go // Windows-specific implementation
Document Platform Differences
When behavior differs by platform, document it in comments:
// On Windows, this returns CRLF line endings.
// On Unix, this returns LF line endings.
// Callers should normalize if consistent output is needed.
function readTextFile(path: string): string { ... }
Test on All Target Platforms
Don't assume Unix behavior works on Windows. Test explicitly:
- •CI should run on all supported platforms
- •Platform-specific code paths need platform-specific tests
- •Document which platforms are supported
Common Mistakes
| Mistake | Reality | Fix |
|---|---|---|
| "Just put it in utils for now" | utils.ts becomes 2000 lines of unrelated code | Name files by purpose from the start |
| "Edge cases are rare" | Edge cases cause production incidents | Handle them. Model the full error space. |
| "We might need this abstraction later" | Premature abstraction is harder to remove than add | Wait for the third use case |
| "It works on my Mac" | It may not work on Windows or Linux | If deploying to a target, test on it |
| "The type system is too strict" | Strictness catches bugs at compile time | Fix the type error, don't bypass it |
Red Flags
Stop and refactor when you see:
- •A
utils.goorhelpers.rsfile growing beyond 100 lines - •Error handling that swallows errors or uses generic messages
- •Platform-specific code mixed with cross-platform code
- •Abstractions created for single use cases
- •Type assertions (
any) to bypass the type system