A Line-by-Line Code Review Fixes Variable Flow and Init Order in a Go CAPTCHA Module
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.
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.
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.
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