Add three new blog posts

This commit is contained in:
2026-05-13 16:35:54 +08:00
parent d3abc52194
commit 8bfdfefb39
3 changed files with 197 additions and 0 deletions
+75
View File
@@ -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.
+72
View File
@@ -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?
+50
View File
@@ -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.