切換語言最容易製造的問題,不是不知道語法,而是把上一個語言的慣用思維直接帶過去。
語法可以查文件,心智模型的切換要靠意識到自己在做什麼。
Anti-pattern 1:用 Python 思維寫 Go(Exception → Panic)
Python 工程師寫 Go 最常見的錯誤:
// ❌ Python 思維的 Go:error 不想處理就 panic
func getUser(id int) User {
user, err := db.QueryUser(id)
if err != nil {
panic(err) // 「反正 recover 會接住」
}
return user
}panic 在 Go 的語意是「程式遇到了不應該發生的情況」——nil pointer、index out of bounds、程式邏輯錯誤。「找不到 user」是預期的業務情境,不是程式錯誤,不應該 panic。
// ✅ Go 的慣用寫法
func getUser(id int) (User, error) {
user, err := db.QueryUser(id)
if err != nil {
return User{}, fmt.Errorf("get user %d: %w", id, err)
}
return user, nil
}Anti-pattern 2:用 Java 思維寫 JavaScript(Class Everywhere)
Java 工程師寫 JavaScript 的典型:
// ❌ Java 思維:到處建 class
class UserService {
constructor(db) {
this.db = db;
}
async getUser(id) {
return await this.db.query(`SELECT * FROM users WHERE id = ?`, [id]);
}
}
class UserController {
constructor(userService) {
this.userService = userService;
}
async handleGet(req, res) {
const user = await this.userService.getUser(req.params.id);
res.json(user);
}
}在 JavaScript,function 是一等公民,大多數情況下不需要 class 的包裝:
// ✅ JavaScript 慣用:function 組合
const createUserRepository = (db) => ({
getUser: (id) => db.query(`SELECT * FROM users WHERE id = ?`, [id]),
});
const createUserHandler = (userRepo) => async (req, res) => {
const user = await userRepo.getUser(req.params.id);
res.json(user);
};Class 在 JavaScript 不是錯的,但如果你的 class 只有方法、沒有狀態、沒有繼承,它只是 function 的無謂包裝。
Anti-pattern 3:在 Go 裡用全局狀態做依賴注入
Python 和 Ruby 的傳統做法:全局 singleton:
# Python 常見模式
db = Database.get_instance()
cache = Redis.get_instance()
def get_user(user_id):
cached = cache.get(f"user:{user_id}")
if cached:
return cached
user = db.query(...)
cache.set(...)
return user帶到 Go 裡,工程師可能寫出:
// ❌ Go 裡的全局 singleton 反模式
var globalDB *sql.DB
var globalRedis *redis.Client
func GetUser(id int) (User, error) {
// 直接用全局變數
cached, _ := globalRedis.Get(ctx, fmt.Sprintf("user:%d", id)).Result()
// ...
}問題:難以測試(你沒辦法換掉全局的 DB),初始化順序難以控制,在並行測試時全局狀態互相污染。
// ✅ Go 的慣用做法:依賴透過參數或 struct 傳入
type UserService struct {
db *sql.DB
cache *redis.Client
}
func (s *UserService) GetUser(id int) (User, error) {
// 使用 s.db 和 s.cache,不是全局變數
}Anti-pattern 4:在 Python asyncio 裡呼叫阻塞函式
# ❌ 在 async 函式裡呼叫同步阻塞操作
async def handle_request(request):
user_id = request.user_id
# 這是同步阻塞的資料庫呼叫!
# 會卡死整個 event loop,所有其他 request 都要等
user = psycopg2.connect(...).cursor().execute("SELECT ...")
return user在 asyncio 的 event loop 裡,任何同步阻塞的操作都會凍結整個 event loop。
# ✅ 使用 async 的資料庫驅動,或 run_in_executor
import asyncpg # async 的 PostgreSQL 驅動
async def handle_request(request):
conn = await asyncpg.connect(...)
user = await conn.fetchrow("SELECT ...", request.user_id)
return user
# 或者,用 run_in_executor 把同步函式移到 thread pool
async def handle_request_with_sync_db(request):
loop = asyncio.get_event_loop()
user = await loop.run_in_executor(None, sync_get_user, request.user_id)
return userAnti-pattern 5:在 Go channel 裡漏掉 goroutine leak
// ❌ goroutine leak:沒有機制結束 goroutine
func processItems(items []Item) {
results := make(chan Result)
for _, item := range items {
go func(item Item) {
results <- process(item)
}(item)
}
// 只取第一個結果就返回
first := <-results
doSomething(first)
// 函式返回了,但其他 goroutine 還在等 results channel 被接收
// channel 沒人讀了,goroutine 永遠卡住 → goroutine leak
}// ✅ 用 context 和 done channel 確保 goroutine 能退出
func processItems(ctx context.Context, items []Item) Result {
results := make(chan Result, len(items)) // buffered channel
for _, item := range items {
go func(item Item) {
select {
case <-ctx.Done():
return // context 取消,goroutine 退出
case results <- process(item):
}
}(item)
}
select {
case <-ctx.Done():
return Result{}
case first := <-results:
return first
}
}Anti-pattern 6:在靜態語言裡濫用 any/interface{}
// ❌ 把 Go 的型別系統當 Python 用
type Config map[string]interface{} // 失去所有型別資訊
func processConfig(cfg Config) {
port := cfg["port"].(int) // runtime panic,如果 port 是 string
host := cfg["host"].(string)
}// ✅ 讓型別系統幫你
type Config struct {
Port int `yaml:"port"`
Host string `yaml:"host"`
}
func processConfig(cfg Config) {
// Port 和 Host 的型別在編譯時就確定了
fmt.Printf("connecting to %s:%d\n", cfg.Host, cfg.Port)
}interface{}(或 Go 1.18 的 any)有它的用途,但在 Go 裡大量使用,等於是在靜態語言裡模擬動態語言,既得不到靜態型別的好處,又多了 type assertion 的 runtime panic 風險。
識別自己在使用哪種 anti-pattern
切換語言時,一個簡單的問題能幫助自我檢查:
「在這個語言的標準庫或主流框架,它自己是怎麼寫的?」
Go 的標準庫到處是 func foo() (T, error),不是 panic。
Node.js 的標準庫到處是 function,不是 class。
Python 的 asyncio 生態系到處是 async def,不是同步的 psycopg2。
主流的程式碼風格,是這個語言的設計者認為「最自然」的用法。偏離它,可能有正當理由,但也可能只是你在用舊語言的思維操作新語言。