跪拜 Guibai
← All articles
Frontend · Backend · Go

A Line-by-Line Code Review Fixes Variable Flow and Init Order in a Go CAPTCHA Module

By 妙码生花 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Go's init() ordering across packages is a recurring footgun when initialization depends on config or database readiness. The pattern shown here—a bootstrap function with sync.Once for one-shot work plus a separate repeated cleanup call—is a reusable recipe for any Go service that needs phased startup without init-order races.

Summary

The review targets a click-CAPTCHA component built in Go. It flags a struct where Width and Height lack the Image prefix that ImageBase64 carries, then moves a readDir utility into a shared filesystem package with recursive support. The bigger refactor pulls icon metadata into a YAML config array so iconPaths no longer threads through four function calls; the CAPTCHA response Elements field switches to Chinese names wrapped in angle brackets.

Initialization order is the central structural fix. The original code ran icon loading and expired-CAPTCHA cleanup inside Create(), while a separate init() set up a Chinese character pool. Because config and database aren't ready when init() fires, the AI proposed a single bootstrap function gated by sync.Once for one-shot setup, with the cleanup call kept outside the Once block. All shared config-derived variables get hoisted to package-level globals.

The review closes by decoding an unfamiliar image-loading function—png.Decode, image.NewRGBA, draw.Draw—and swapping the hardcoded PNG decoder for the general image.Decode to make the loader format-agnostic.

Takeaways
Struct field naming should be consistent: if ImageBase64 carries an Image prefix, Width and Height should become ImageWidth and ImageHeight.
A readDir helper that only lists top-level files belongs in a shared filesystem package and should support recursive directory traversal.
Passing iconPaths through four function signatures is a smell; store icon metadata (file name, Chinese name, path) in a config array and reference it globally.
CAPTCHA response Elements should return Chinese names wrapped in angle brackets (e.g., <飞机>) instead of raw identifiers.
YAML icon config uses en_name and cn_name keys rather than a single name field, leaving room for future language columns.
Go's init() cannot depend on config.Init() or database.Init() because those haven't run yet; a bootstrap function called at the top of Create() solves this.
One-shot initialization (icon loading, character pool) goes inside sync.Once; repeated work like cleaning expired CAPTCHAs stays outside the Once block.
Extract repeated config.Get().Captcha calls into a package-level global variable so every function doesn't fetch it independently.
Replace png.Decode with image.Decode to make the image loader format-agnostic, then cache img.Bounds() in a local variable before calling image.NewRGBA and draw.Draw.
Conclusions

AI-generated Go code often scatters initialization across init() and business-logic functions because the model defaults to init() for setup but then discovers dependency ordering problems and patches them ad hoc. A human reviewer consolidating this into a single bootstrap function is a necessary cleanup step that AI alone rarely performs unprompted.

The iconPaths threading problem is a classic sign of AI generating working but unrefactored code: the model solves the immediate data-flow requirement without recognizing that a config-backed global removes four parameters and simplifies the call graph.

Switching from png.Decode to image.Decode is a small change with an outsized lesson—AI tools tend to reach for format-specific decoders because they match the known file type, but a general decoder future-proofs the code and is exactly the kind of judgment a developer adds during review.

Concepts & terms
Go init() function
A special function in Go that runs automatically when a package is imported, before main(). Multiple init() functions in a package execute in source order, but init() cannot safely depend on initialization performed by other packages' init() functions.
sync.Once
A Go synchronization primitive that guarantees a function executes exactly one time, even across multiple goroutines. Commonly used for lazy one-shot initialization inside a bootstrap or factory function.
image.Decode vs png.Decode
image.Decode is a general-purpose Go standard library function that detects the image format from the byte stream and delegates to the appropriate format-specific decoder (PNG, JPEG, GIF, etc.). png.Decode only handles PNG input and will fail on other formats.
From the discussion
Featured comments
birjemin 1 likes

handler -> service -> repository -> model, same style as when I first switched to golang, but after writing more, there's no need to force this anymore

妙码生花

Yeah, this four-layer application architecture was settled on after first asking many AIs for links to highly-starred Go web projects on GitHub, then manually reviewing them. As for the directory structure, alongside these four layers at the same level, we've also added router, response, infra, middleware, and in the future possibly dto or httpx (moving response into it) and the like

See top comments, translated →
Source: juejin.cn ↗ Google Translate ↗ Backup ↗