Go初学者应四步构建可维护项目:用go mod init初始化模块、net/http启动服务、chi管理路由、按handlers/services/models分层。需避免GOPATH、相对导入和单文件堆砌,通过依赖注入保障可测性。

Go 初学者直接上手做项目,最现实的路径不是“从零开始造轮子”,而是用 go mod init 搭骨架、用 net/http 跑通第一个请求、用 gorilla/mux 或 chi 管路由、把逻辑拆进 handlers 和 services 目录——这四步走完,你就已经站在可维护项目的起点上了。
怎么初始化一个可提交的 Go 项目结构
别碰 GOPATH,也别手动建 src 目录。直接在空文件夹里执行:
go mod init example.com/myapp
这会生成 go.mod 文件,并声明模块路径。模块路径不一定要是真实域名,但必须唯一(本地开发可用 myapp.local)。之后所有依赖都会记录在 go.mod 中,go run 和 go build 都能正确解析。
推荐初始目录结构(非强制,但能避开后期重构痛):
-
cmd/myapp/main.go—— 唯一入口,只做初始化和启动 -
internal/handlers/—— HTTP 处理函数,不暴露给其他模块 -
internal/services/—— 业务逻辑,比如用户注册、订单创建 -
internal/models/—— 数据结构定义(struct)、数据库映射(如用gorm) -
pkg/(可选)—— 若有想复用到其他项目的工具函数,放这里
如何写一个带路由和 JSON 响应的最小可用服务
用 net/http 自带的 http.ServeMux 很快,但路由能力弱(不支持路径参数、不支持中间件)。初学建议直接上 github.com/go-chi/chi/v5,轻量且文档清晰。
安装:
go get -u github.com/go-chi/chi/v5
示例 cmd/myapp/main.go:
package main
import (
"log"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func main() {
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"ok"}`))
})
log.Println("Server starting on :8080")
http.ListenAndServe(":8080", r)
}
注意:chi 的路由匹配是前缀式,r.Get("/users", ...) 不会拦截 /users/123;要支持路径参数,得写成 r.Get("/users/{id}", ...),再用 chi.URLParam(r, "id") 取值。
为什么 handler 不该直接操作数据库或调外部 API
因为测试会崩。如果 handler 里直接 new 一个 sql.DB 或调 http.Post,那单元测试时你只能 mock 全局函数(难)或跑真实 DB(慢且不稳定)。
正确做法:把依赖作为接口传入。例如定义:
type UserService interface {
CreateUser(name string) error
}
func NewUserHandler(us UserService) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// ...
us.CreateUser("alice")
}
}
这样测试时可以传个假实现:
type mockUserService struct{}
func (m mockUserService) CreateUser(name string) error { return nil }
实际运行时再传真实实现(比如基于 gorm 的)。这种写法叫“依赖注入”,不是设计模式炫技,是让代码能测、能换、能活过三个月。
常见踩坑点:go run 和 go build 的行为差异
go run main.go 只编译并运行当前文件,不会自动识别 cmd/myapp/ 下其他包;而 go run ./cmd/myapp 才会按模块加载全部依赖。
另一个坑是:如果你在 main.go 里写了 import "./internal/handlers" 这种相对路径导入,go mod tidy 会报错——Go 要求所有 import 必须是模块路径(如 "example.com/myapp/internal/handlers"),不能是文件系统路径。
还有:Windows 用户用 go run 启动服务后 Ctrl+C 经常不退出进程,得用 taskkill /f /im go.exe 清理残留,这不是 bug,是 Go 在 Windows 上信号处理的限制。
真正卡住初学者的,往往不是语法,而是模块路径怎么设、包怎么导、测试怎么写、错误怎么传——这些细节没对齐,项目就卡在“能跑”和“能改”之间,反复重写三次还是单文件 main.go。










