Add three new blog posts
This commit is contained in:
@@ -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.
|
||||
@@ -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?
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user