diff --git a/content/blog/go-concurrency.md b/content/blog/go-concurrency.md new file mode 100644 index 0000000..d35f43d --- /dev/null +++ b/content/blog/go-concurrency.md @@ -0,0 +1,75 @@ +--- +title: "Go Concurrency Patterns in Practice" +date: 2026-05-15T10:00:00+08:00 +draft: false +tags: + - Go + - Concurrency + - Programming +--- + +I've been writing Go for a few years now, and concurrency remains one of those topics where textbook knowledge doesn't quite prepare you for production. Here are a few patterns I've found genuinely useful. + +## The Worker Pool + +The classic. Useful when you need bounded parallelism: + +```go +func workerPool(jobs <-chan Job, results chan<- Result, count int) { + var wg sync.WaitGroup + for i := 0; i < count; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for job := range jobs { + results <- process(job) + } + }() + } + wg.Wait() + close(results) +} +``` + +## Context Propagation + +Probably the single most important practice I've adopted: **every long-running goroutine should accept a context**. It makes cancellation and timeout handling explicit rather than an afterthought. + +```go +func fetchWithTimeout(ctx context.Context, url string) ([]byte, error) { + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, err + } + // ... +} +``` + +## The Select Loop + +When you need to coordinate multiple channels, `select` is your friend. A common pattern: + +```go +for { + select { + case msg := <-messages: + handle(msg) + case <-ctx.Done(): + return ctx.Err() + case <-ticker.C: + flush() + } +} +``` + +## Things I Wish I Knew Earlier + +1. **Channel ownership** — the goroutine that writes should also close the channel +2. **Buffered channels aren't a fix for deadlocks** — they just push the problem further out +3. **`sync.WaitGroup` copies by value** — pass it by pointer, always +4. **The race detector is free** — `go test -race` should be part of your CI + +Concurrency in Go is *easy to write* but *hard to get right*. The compiler won't save you from logical races. That's on us. diff --git a/content/blog/rest-api-design.md b/content/blog/rest-api-design.md new file mode 100644 index 0000000..d925ba1 --- /dev/null +++ b/content/blog/rest-api-design.md @@ -0,0 +1,72 @@ +--- +title: "REST API Design: Mistakes I've Made So You Don't Have To" +date: 2026-05-13T09:00:00+08:00 +draft: false +tags: + - API + - REST + - Backend +--- + +I've designed, built, and maintained enough REST APIs to have made most of the classic mistakes. Here's what I wish someone had told me upfront. + +## 1. Versioning Early + +Don't wait until you need it. Version your API from day one, either in the URL (`/v1/users`) or via the `Accept` header. Retrofitting versioning onto an unversioned API is painful for everyone involved. + +My preference: **URL-based versioning**. It's visible, impossible to forget, and trivially cacheable. + +## 2. Pagination by Default + +Every list endpoint should be paginated. Every. Single. One. + +```json +{ + "data": [...], + "pagination": { + "cursor": "eyJpZCI6MTIzfQ==", + "has_more": true, + "total": 847 + } +} +``` + +I've moved from offset-based to cursor-based pagination almost everywhere. Cursors handle real-time data changes gracefully and perform better on large datasets. + +## 3. Error Responses Should Be Consistent + +A predictable error shape means less client-side code: + +```json +{ + "error": { + "code": "INSUFFICIENT_BALANCE", + "message": "Account balance too low for this transaction", + "details": { + "required": 29.99, + "available": 12.50 + } + } +} +``` + +Never expose stack traces in production. Never leak internal IDs without intent. + +## 4. Idempotency Keys + +For any mutating endpoint, support an `Idempotency-Key` header. Your payment team will thank you, and so will your users when a retry doesn't double-charge them. + +## 5. Think in Terms of Deprecation + +Add a `Deprecation` and `Sunset` header to old endpoints: + +``` +Deprecation: true +Sunset: Sat, 01 Aug 2026 00:00:00 GMT +``` + +Give consumers time to migrate, then actually follow through on removal. Keeping deprecated endpoints around "just in case" is technical debt with interest. + +--- + +Good API design is mostly about **empathy for the consumer**. Ask yourself: would *you* enjoy integrating with this? diff --git a/content/blog/vim-motions.md b/content/blog/vim-motions.md new file mode 100644 index 0000000..5707af3 --- /dev/null +++ b/content/blog/vim-motions.md @@ -0,0 +1,50 @@ +--- +title: "Vim Motions That Actually Save Time" +date: 2026-05-14T14:00:00+08:00 +draft: false +tags: + - Vim + - Productivity + - Editor +--- + +Everyone tells you to learn Vim, but nobody tells you *which* motions are worth the muscle memory investment. After three years of daily use, here's my honest ranking. + +## Tier 1: Non-Negotiable + +These pay for themselves within a week: + +| Motion | What it does | Why it matters | +|--------|-------------|----------------| +| `ci"` | Change inside quotes | Editing strings, attributes | +| `ci(` / `ci{` | Change inside brackets | Refactoring arguments | +| `%` | Jump to matching bracket | Navigating code blocks | +| `*` / `#` | Search word under cursor | Quick variable tracing | +| `.` | Repeat last change | The single biggest time-saver | + +## Tier 2: Worth Practicing + +These take a few weeks to internalize but compound: + +- `f{char}` / `F{char}` — find character inline. Faster than `h`/`l` spam +- `t{char}` / `T{char}` — find *until* character. Perfect for deleting `until` a comma +- `ctrl+d` / `ctrl+u` — half-page scroll. Keeps your eyes on the same spot +- `zz` — center current line. Eliminates mental reorientation + +## Tier 3: Nice to Have + +Useful but not essential for daily work: + +- `gt` / `gT` — switch tabs +- `ctrl+o` / `ctrl+i` — jump back/forward in the jumplist +- Macros (`q{register}`) — only worth it for repetitive tasks of 10+ iterations + +## What I Don't Use + +- **Line numbers as targets** (`:42`) — too slow mentally +- **Visual block mode** — I use multi-cursor in VS Code instead +- **`/` search with regex** — I just use `*` and `f` 90% of the time + +--- + +The real productivity gain isn't in any single motion — it's in **not thinking about navigation**. When your fingers just move the cursor without conscious effort, that's when the payoff arrives.