关于快速掌握Go代码库核心功能
写在前面
当你接手一个陌生的Go项目时,是否曾在迷宫般的代码中迷失方向?本文分享一套快速了解Go项目代码阅读秘技,助你快速破译Go代码库的核心设计。
面临的问题
可能大数开发者习惯的 「从上到下线性阅读」 的方式阅读代码,但是这种方式容易带来几个问题:
- 陷入细节沼泽:过早陷入函数实现细节
- 错过整体脉络:只见树木不见森林
- 时间消耗巨大:阅读完整项目动辄数小时
逆向阅读三步法
该方法是从技术大牛get到的,后面经实践确实比之前效率高了很多,整体流程就是:
A[库函数 function] --> B[结构定义 struct] --> C[结构函数 method]
- 库函数: 我们可以直观的看到该项目或者包对外提供了哪些功能
- 结构定义:从结构体定义我们可以直观的看到该包有哪些核心模块,他们大概是负责什么功能
- 结构函数:了解了各个模块负责的功能,我们就可以看某一个模块的具体实现了
针对三类情况:
- 标准库:直接使用包名称
- 第三方库:可以
go get
后使用完整的get地址进行加载 - 自己的库:自己的库可以
go doc your_protect/../pkg_name | grep "^func"
大概是这样your_protect
就是你的go mod项目名称,/../pkg_nam
某一个目录的某一个包
第一步:锁定核心库函数
可以通过以下命令查看库函数:
# 提取包的所有公开函数
go doc <package> | grep "^func"
比如说net/http
包:
> go doc net/http | grep "^func"
func CanonicalHeaderKey(s string) string
func DetectContentType(data []byte) string
func Error(w ResponseWriter, error string, code int)
func Get(url string) (resp *Response, err error)
func Handle(pattern string, handler Handler)
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
func Head(url string) (resp *Response, err error)
func ListenAndServe(addr string, handler Handler) error
func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error
func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser
func NewRequest(method, url string, body io.Reader) (*Request, error)
func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error)
func NotFound(w ResponseWriter, r *Request)
func ParseHTTPVersion(vers string) (major, minor int, ok bool)
func ParseTime(text string) (t time.Time, err error)
func Post(url, contentType string, body io.Reader) (resp *Response, err error)
func PostForm(url string, data url.Values) (resp *Response, err error)
func ProxyFromEnvironment(req *Request) (*url.URL, error)
func ProxyURL(fixedURL *url.URL) func(*Request) (*url.URL, error)
func ReadRequest(b *bufio.Reader) (*Request, error)
func ReadResponse(r *bufio.Reader, req *Request) (*Response, error)
func Redirect(w ResponseWriter, r *Request, url string, code int)
func Serve(l net.Listener, handler Handler) error
func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, ...)
func ServeFile(w ResponseWriter, r *Request, name string)
func ServeFileFS(w ResponseWriter, r *Request, fsys fs.FS, name string)
func ServeTLS(l net.Listener, handler Handler, certFile, keyFile string) error
func SetCookie(w ResponseWriter, cookie *Cookie)
func StatusText(code int) string
很直观的就可以看到对外提供了什么能力。
技巧要点:
- 关注函数名中的动词:Get、Create、Send、Process
- 留意初始化函数:NewXxx、Init、Setup
- 标记工具函数:Parse、Validate、Convert
此步骤解决 「这个库能做什么」 的问题
第二步:解析核心结构体
提取包的核心结构体定义
go doc <package> | grep "^type" | grep struct
例如:
> go doc net/http | grep "^type"|grep struct
type Client struct{ ... }
type Cookie struct{ ... }
type MaxBytesError struct{ ... }
type ProtocolError struct{ ... }
type PushOptions struct{ ... }
type Request struct{ ... }
type Response struct{ ... }
type ResponseController struct{ ... }
type ServeMux struct{ ... }
type Server struct{ ... }
type Transport struct{ ... }
可以直观的看到他们负责的功能:
Client 负责构建 HTTP 客户端;
Server 负责构建 HTTP 服务端;
ServerMux 负责 HTTP 服务端路由;
Transport、Request、Response、Cookie 负责客户端和服务端传输对应的不同模块。
当然我们在具体看某一个结构体的字段时,需要留意字段归属,例如:// 重点关注字段的三大类型: type Server struct { // 1. 配置字段(公开可调参数) Addr string Timeout time.Duration // 2. 组件字段(依赖关系) Handler Handler TLSConfig *tls.Config // 3. 状态字段(内部管理) mu sync.Mutex activeConn map[*conn]struct{} }
此步骤解决 「这个库用什么实现」 的问题
第三步:追踪结构体方法
我们已经知道结构体的功能划分了,可能需要进一步看某一个结构体功能是怎么实现的了,使用以下命令查看某结构体的方法:
# 查看特定结构体的方法集
go doc <package>.<Struct>
例如:
> go doc net/http.Server
package http // import "net/http"
type Server struct {
// Addr optionally specifies the TCP address for the server to listen on,
// in the form "host:port". If empty, ":http" (port 80) is used.
// The service names are defined in RFC 6335 and assigned by IANA.
// See net.Dial for details of the address format.
Addr string
Handler Handler // handler to invoke, http.DefaultServeMux if nil
// DisableGeneralOptionsHandler, if true, passes "OPTIONS *" requests to the Handler,
// otherwise responds with 200 OK and Content-Length: 0.
DisableGeneralOptionsHandler bool
// TLSConfig optionally provides a TLS configuration for use
// by ServeTLS and ListenAndServeTLS. Note that this value is
// cloned by ServeTLS and ListenAndServeTLS, so it's not
// possible to modify the configuration with methods like
// tls.Config.SetSessionTicketKeys. To use
// SetSessionTicketKeys, use Server.Serve with a TLS Listener
// instead.
TLSConfig *tls.Config
// ReadTimeout is the maximum duration for reading the entire
// request, including the body. A zero or negative value means
// there will be no timeout.
//
// Because ReadTimeout does not let Handlers make per-request
// decisions on each request body's acceptable deadline or
// upload rate, most users will prefer to use
// ReadHeaderTimeout. It is valid to use them both.
ReadTimeout time.Duration
// ReadHeaderTimeout is the amount of time allowed to read
// request headers. The connection's read deadline is reset
// after reading the headers and the Handler can decide what
// is considered too slow for the body. If ReadHeaderTimeout
// is zero, the value of ReadTimeout is used. If both are
// zero, there is no timeout.
ReadHeaderTimeout time.Duration
// WriteTimeout is the maximum duration before timing out
// writes of the response. It is reset whenever a new
// request's header is read. Like ReadTimeout, it does not
// let Handlers make decisions on a per-request basis.
// A zero or negative value means there will be no timeout.
WriteTimeout time.Duration
// IdleTimeout is the maximum amount of time to wait for the
// next request when keep-alives are enabled. If IdleTimeout
// is zero, the value of ReadTimeout is used. If both are
// zero, there is no timeout.
IdleTimeout time.Duration
// MaxHeaderBytes controls the maximum number of bytes the
// server will read parsing the request header's keys and
// values, including the request line. It does not limit the
// size of the request body.
// If zero, DefaultMaxHeaderBytes is used.
MaxHeaderBytes int
// TLSNextProto optionally specifies a function to take over
// ownership of the provided TLS connection when an ALPN
// protocol upgrade has occurred. The map key is the protocol
// name negotiated. The Handler argument should be used to
// handle HTTP requests and will initialize the Request's TLS
// and RemoteAddr if not already set. The connection is
// automatically closed when the function returns.
// If TLSNextProto is not nil, HTTP/2 support is not enabled
// automatically.
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
// ConnState specifies an optional callback function that is
// called when a client connection changes state. See the
// ConnState type and associated constants for details.
ConnState func(net.Conn, ConnState)
// ErrorLog specifies an optional logger for errors accepting
// connections, unexpected behavior from handlers, and
// underlying FileSystem errors.
// If nil, logging is done via the log package's standard logger.
ErrorLog *log.Logger
// BaseContext optionally specifies a function that returns
// the base context for incoming requests on this server.
// The provided Listener is the specific Listener that's
// about to start accepting requests.
// If BaseContext is nil, the default is context.Background().
// If non-nil, it must return a non-nil context.
BaseContext func(net.Listener) context.Context
// ConnContext optionally specifies a function that modifies
// the context used for a new connection c. The provided ctx
// is derived from the base context and has a ServerContextKey
// value.
ConnContext func(ctx context.Context, c net.Conn) context.Context
// Has unexported fields.
}
A Server defines parameters for running an HTTP server. The zero value for
Server is a valid configuration.
func (srv *Server) Close() error
func (srv *Server) ListenAndServe() error
func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error
func (srv *Server) RegisterOnShutdown(f func())
func (srv *Server) Serve(l net.Listener) error
func (srv *Server) ServeTLS(l net.Listener, certFile, keyFile string) error
func (srv *Server) SetKeepAlivesEnabled(v bool)
func (srv *Server) Shutdown(ctx context.Context) error
这样是不是很直观的看出了各个方法是干什么的啦
此步骤解决 「这个库如何工作」 的问题
实战
我们随便找一个库,实践一下,使用gin:
> go doc github.com/gin-gonic/gin | grep "^func"
func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine)
func Dir(root string, listDirectory bool) http.FileSystem
func DisableBindValidation()
func DisableConsoleColor()
func EnableJsonDecoderDisallowUnknownFields()
func EnableJsonDecoderUseNumber()
func ForceConsoleColor()
func IsDebugging() bool
func Mode() string
func SetMode(value string)
可以大概看出来他们的功能:
DisableBindValidation() // 禁用绑定验证
DisableConsoleColor() // 禁用控制台颜色
EnableJsonDecoder...() // JSON解码配置
ForceConsoleColor() // 强制控制台颜色
SetMode(value string) // 设置运行模式
Mode() string // 获取当前模式
IsDebugging() bool // 检查调试模式
Dir() http.FileSystem // 文件系统处理
CreateTestContext() // 测试环境创建
库函数对外提供的能力,然后再看看具体有哪些结构体:
> go doc github.com/gin-gonic/gin | grep "^type" | grep struct
type Context struct{ ... }
type Engine struct{ ... }
type Error struct{ ... }
type LogFormatterParams struct{ ... }
type LoggerConfig struct{ ... }
type Negotiate struct{ ... }
type Param struct{ ... }
type RouteInfo struct{ ... }
type RouterGroup struct{ ... }
通过这些结构体看到看出:
type Context struct{ ... } // 请求上下文核心容器
type Engine struct{ ... } // 框架主引擎
type Error struct{ ... } // 错误处理结构
type LogFormatterParams struct{ ... } // 日志格式化参数
type LoggerConfig struct{ ... } // 日志配置
type Negotiate struct{ ... } // 内容协商
type Param struct{ ... } // 路由参数
type RouteInfo struct{ ... } // 路由信息
type RouterGroup struct{ ... } // 路由组管理
接着我们来看Engine
这个模块:
> go doc github.com/gin-gonic/gin.Engine
package gin // import "github.com/gin-gonic/gin"
type Engine struct {
RouterGroup
// RedirectTrailingSlash enables automatic redirection if the current route can't be matched but a
// handler for the path with (without) the trailing slash exists.
// For example if /foo/ is requested but a route only exists for /foo, the
// client is redirected to /foo with http status code 301 for GET requests
// and 307 for all other request methods.
RedirectTrailingSlash bool
// RedirectFixedPath if enabled, the router tries to fix the current request path, if no
// handle is registered for it.
// First superfluous path elements like ../ or // are removed.
// Afterwards the router does a case-insensitive lookup of the cleaned path.
// If a handle can be found for this route, the router makes a redirection
// to the corrected path with status code 301 for GET requests and 307 for
// all other request methods.
// For example /FOO and /..//Foo could be redirected to /foo.
// RedirectTrailingSlash is independent of this option.
RedirectFixedPath bool
// HandleMethodNotAllowed if enabled, the router checks if another method is allowed for the
// current route, if the current request can not be routed.
// If this is the case, the request is answered with 'Method Not Allowed'
// and HTTP status code 405.
// If no other Method is allowed, the request is delegated to the NotFound
// handler.
HandleMethodNotAllowed bool
// ForwardedByClientIP if enabled, client IP will be parsed from the request's headers that
// match those stored at `(*gin.Engine).RemoteIPHeaders`. If no IP was
// fetched, it falls back to the IP obtained from
// `(*gin.Context).Request.RemoteAddr`.
ForwardedByClientIP bool
// AppEngine was deprecated.
// Deprecated: USE `TrustedPlatform` WITH VALUE `gin.PlatformGoogleAppEngine` INSTEAD
// #726 #755 If enabled, it will trust some headers starting with
// 'X-AppEngine...' for better integration with that PaaS.
AppEngine bool
// UseRawPath if enabled, the url.RawPath will be used to find parameters.
UseRawPath bool
// UnescapePathValues if true, the path value will be unescaped.
// If UseRawPath is false (by default), the UnescapePathValues effectively is true,
// as url.Path gonna be used, which is already unescaped.
UnescapePathValues bool
// RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes.
// See the PR #1817 and issue #1644
RemoveExtraSlash bool
// RemoteIPHeaders list of headers used to obtain the client IP when
// `(*gin.Engine).ForwardedByClientIP` is `true` and
// `(*gin.Context).Request.RemoteAddr` is matched by at least one of the
// network origins of list defined by `(*gin.Engine).SetTrustedProxies()`.
RemoteIPHeaders []string
// TrustedPlatform if set to a constant of value gin.Platform*, trusts the headers set by
// that platform, for example to determine the client IP
TrustedPlatform string
// MaxMultipartMemory value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
// method call.
MaxMultipartMemory int64
// UseH2C enable h2c support.
UseH2C bool
// ContextWithFallback enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value() when Context.Request.Context() is not nil.
ContextWithFallback bool
HTMLRender render.HTMLRender
FuncMap template.FuncMap
// Has unexported fields.
}
Engine is the framework's instance, it contains the muxer, middleware and
configuration settings. Create an instance of Engine, by using New() or
Default()
func Default(opts ...OptionFunc) *Engine
func New(opts ...OptionFunc) *Engine
func (engine *Engine) Delims(left, right string) *Engine
func (engine *Engine) HandleContext(c *Context)
func (engine *Engine) Handler() http.Handler
func (engine *Engine) LoadHTMLFiles(files ...string)
func (engine *Engine) LoadHTMLGlob(pattern string)
func (engine *Engine) NoMethod(handlers ...HandlerFunc)
func (engine *Engine) NoRoute(handlers ...HandlerFunc)
func (engine *Engine) Routes() (routes RoutesInfo)
func (engine *Engine) Run(addr ...string) (err error)
func (engine *Engine) RunFd(fd int) (err error)
func (engine *Engine) RunListener(listener net.Listener) (err error)
func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error)
func (engine *Engine) RunUnix(file string) (err error)
func (engine *Engine) SecureJsonPrefix(prefix string) *Engine
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request)
func (engine *Engine) SetFuncMap(funcMap template.FuncMap)
func (engine *Engine) SetHTMLTemplate(templ *template.Template)
func (engine *Engine) SetTrustedProxies(trustedProxies []string) error
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes
func (engine *Engine) With(opts ...OptionFunc) *Engine
看这些方法是不是大概能猜到他们是干嘛的了:
// ========== 构造函数 ==========
func Default(...) *Engine // 创建带默认中间件(使用过gin裤架的肯定知道这两个中间件Logger, Recovery)的引擎
func New(...) *Engine // 创建纯净引擎实例
// ========== 服务启动方法 ==========
func Run(addr ...string) error // 启动HTTP服务器 (默认:8080)
func RunTLS(addr, cert, key string) error // 启动HTTPS服务器
func RunUnix(file string) error // 通过Unix Socket启动
func RunFd(fd int) error // 通过文件描述符启动
func RunListener(listener) error // 通过自定义监听器启动
// ========== 路由与中间件 ==========
func Use(...HandlerFunc) IRoutes // 注册全局中间件
func NoRoute(...HandlerFunc) // 404未找到路由处理器
func NoMethod(...HandlerFunc) // 405方法不允许处理器
func Routes() RoutesInfo // 获取所有注册路由信息
// ========== 模板系统 ==========
func LoadHTMLGlob(pattern string) // 加载HTML模板(通配符)
func LoadHTMLFiles(files ...string) // 加载指定HTML文件
func Delims(left, right string) *Engine // 设置模板定界符
func SetHTMLTemplate(templ *template) // 设置自定义HTML模板
func SetFuncMap(funcMap template.FuncMap) // 设置模板函数映射
// ========== 配置方法 ==========
func SecureJsonPrefix(prefix string) *Engine // 设置JSON安全前缀
func SetTrustedProxies(...string) error // 设置可信代理地址
func With(...OptionFunc) *Engine // 选项模式配置引擎
// ========== 高级功能 ==========
func ServeHTTP(w, req) // 实现http.Handler接口
func Handler() http.Handler // 获取底层HTTP处理器
func HandleContext(c *Context) // 手动处理Context(用于测试/重路由)
这样一看你是不是大概就知道gin框架的一些功能了呢,当然这远远不够的,接下来就是你自己去探索具体实现和各个模块的依赖关系等等
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: