关于快速掌握Go代码库核心功能

写在前面

当你接手一个陌生的Go项目时,是否曾在迷宫般的代码中迷失方向?本文分享一套快速了解Go项目代码阅读秘技,助你快速破译Go代码库的核心设计。

面临的问题

可能大数开发者习惯的 「从上到下线性阅读」 的方式阅读代码,但是这种方式容易带来几个问题:

  1. 陷入细节沼泽:过早陷入函数实现细节
  2. 错过整体脉络:只见树木不见森林
  3. 时间消耗巨大:阅读完整项目动辄数小时

逆向阅读三步法

该方法是从技术大牛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

很直观的就可以看到对外提供了什么能力。
技巧要点​​:

  1. 关注函数名中的​​动词​​:Get、Create、Send、Process
  2. 留意​​初始化函数​​:NewXxx、Init、Setup
  3. 标记​​工具函数​​: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 协议》,转载必须注明作者和本文链接
刻意学习
讨论数量: 2

哈喽,我这边基于 dify-plus 二开一些功能,后端是用的 go ,有兴趣吗?

4天前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
129
粉丝
107
喜欢
197
收藏
286
排名:328
访问:2.9 万
私信
所有博文
社区赞助商