<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          The Go Blog: 關(guān)于 context 的一點最佳實踐

          共 3914字,需瀏覽 8分鐘

           ·

          2021-02-28 05:26

          關(guān)于 context 的一點最佳實踐

          3bd7bba88ed2d619b803d63ac34dbf6e.webp

          2021 年 2月 24 日,官方 blog 中詳細(xì)講了關(guān)于 context 使用的一些最佳實踐,提供了代碼示例,告訴你為何 context不應(yīng)存儲在 struct 內(nèi)部,最好的方式是作為函數(shù)的第一個參數(shù)傳遞,以及如何在非常必要的情況下(保持向后兼容)以一種最安全的方式將 context 存儲到 struct 中。下面是原文的主要內(nèi)容。

          Introduction

          在許多 Go API,尤其是現(xiàn)代 API 中,函數(shù)和方法的第一個參數(shù)通常是 context.Contextcontext 提供了很多方法例如 WithCancel、WithDeadline、WithValue、以實現(xiàn)跨 API 的流程控制。很多 lib 在與遠(yuǎn)程服務(wù)器(如數(shù)據(jù)庫、api等)交互時,經(jīng)常使用 context 做控制。

          context 的文檔中講到:

          ?context?不應(yīng)存儲在?struct?內(nèi)部,最好的方式是作為函數(shù)的第一個參數(shù)傳遞

          本文在此建議的基礎(chǔ)上講解了原因和示例,說明了為什么要使用函數(shù)傳遞 Context 而不是將其存儲在 struct 中的重要性。還以 net/http 的代碼為例,解釋了應(yīng)該在何種情況下可以在 struct 中 存儲 Context 。

          推薦 context 作為函數(shù)參數(shù)傳遞

          讓我們先看下在函數(shù)中傳遞 context

          type?Worker?struct?{?/*?…?*/?}

          type?Work?struct?{?/*?…?*/?}

          func?New()?*Worker?{
          ??return?&Worker{}
          }

          func?(w?*Worker)?Fetch(ctx?context.Context)?(*Work,?error)?{
          ??_?=?ctx?//?A?per-call?ctx?is?used?for?cancellation,?deadlines,?and?metadata.
          }

          func?(w?*Worker)?Process(ctx?context.Context,?w?*Work)?error?{
          ??_?=?ctx?//?A?per-call?ctx?is?used?for?cancellation,?deadlines,?and?metadata.
          }

          這里 (*Worker).Fetch(*Worker).Process 都直接將 context 作為函數(shù)第一個參數(shù)。這樣從 context 的生成到結(jié)束,調(diào)用方可以很清晰地知道 context 的傳遞路線。

          將 context 存儲到 strcut 所帶來的一些困惑

          在結(jié)構(gòu)體中嵌套 context 來實現(xiàn)上面的 Worker 示例,調(diào)用者對所使用的 context 生命周期產(chǎn)生迷惑:

          type?Worker?struct?{
          ??ctx?context.Context
          }

          func?New(ctx?context.Context)?*Worker?{
          ??return?&Worker{ctx:?ctx}
          }

          func?(w?*Worker)?Fetch()?(*Work,?error)?{
          ??_?=?w.ctx?//?A?shared?w.ctx?is?used?for?cancellation,?deadlines,?and?metadata.
          }

          func?(w?*Worker)?Process(w?*Work)?error?{
          ??_?=?w.ctx?//?A?shared?w.ctx?is?used?for?cancellation,?deadlines,?and?metadata.
          }

          (*Worker).Fetch(*Worker).Process 方法同時使用了 Worker 結(jié)構(gòu)體中的 context ,這種情況下使得調(diào)用方無法定義不同的 context ,比如有調(diào)用方想用 WitchCancel,有的想用 WithDeadline,也很難理解上面?zhèn)鱽淼?context 的作用是 cancel?還是 deadline?調(diào)用者所使用 context 的生命周期被綁定到了一個共享的 context 上面。

          特殊情況:保留向后兼容性

          當(dāng) go 1.7 版本發(fā)布時,大量的的 API 需要以向后兼容的方式支持 context.Context,例如,net/httpClient 方法(例如Get和Do)是使用 context 的典范。使用這些方法發(fā)送的 http 請求都將受益于 context.Context 附帶的 WithDeadline,WithCancelWithValue 等方法支持。

          一般有兩種方式能夠在支持 context.Context 的同時保持代碼的向后兼容:

          1. 在 struct 中添加 context (稍后我們將看到);
          2. 復(fù)制原有函數(shù),在函數(shù)第一個參數(shù)中使用 context,舉個栗子,database/sql 這個 package 的 Query 方法的簽名一直是:
          func?(db?*DB)?Query(query?string,?args?...interface{})?(*Rows,?error)

          當(dāng) context package 引入的時候,Go team 新增了這樣一個函數(shù):

          func?(db?*DB)?QueryContext(ctx?context.Context,?query?string,?args?...interface{})?(*Rows,?error)

          并且只修改了一處代碼:

          func?(db?*DB)?Query(query?string,?args?...interface{})?(*Rows,?error)?{
          ????return?db.QueryContext(context.Background(),?query,?args...)
          }

          通過這種方式,Go team 能夠在平滑地升級一個 package 的同時不對代碼的可讀性、兼容性造成影響。類似的代碼在 golang 源碼中隨處可見。更多的保持代碼兼容性的討論可見 [Go team 關(guān)于如何保持 Go Modules 兼容性的一些實踐]。

          然而在某些情況下,比如 API 公開了大量 function,重寫所有函數(shù)是不切實際的。

          package net/http 選擇在 struct 中添加 context.Context,這是一個結(jié)構(gòu)體嵌套 context 比較恰當(dāng)?shù)姆独?。先讓我們看?net/httpDo 函數(shù)。在引入 context 之前,Do 的定義如下:

          func?(c?*Client)?Do(req?*Request)?(*Response,?error)

          在 1.7 引入 context 后,為了遵循 net/http 這種標(biāo)準(zhǔn)庫的向后兼容原則,考慮到該核心庫所包含的函數(shù)過于多,maintainers 選擇在結(jié)構(gòu)體 http.Request 中添加 context.Context。

          type?Request?struct?{
          ??ctx?context.Context

          ??//?...
          }

          func?NewRequestWithContext(ctx?context.Context,?method,?url?string,?body?io.Reader)?(*Request,?error)?{
          ??//?Simplified?for?brevity?of?this?article.
          ??return?&Request{
          ????ctx:?ctx,
          ????//?...
          ??}
          }

          func?(c?*Client)?Do(req?*Request)?(*Response,?error)

          在修改大量 API 以支持 context 時,在結(jié)構(gòu)體中添加 context.Context 是有意義的, 如上所述。但是,記住首先要考慮復(fù)制函數(shù)對 context 進(jìn)行支持,在不犧牲實用性和理解性的前提下向后兼容上下文:

          func?(c?*Client)?Call()?error?{
          ??return?c.CallContext(context.Background())
          }

          func?(c?*Client)?CallContext(ctx?context.Context)?error?{
          ??//?...
          }

          Conclusion

          通過 context,可以輕松地在調(diào)用堆棧中傳播重要的跨 lib 和跨 API 信息。但是,必須一致、清晰地使用它,以使其易于理解,易于調(diào)試且有效。

          當(dāng) context 作為方法中的第一個參數(shù)傳遞而不是存儲在 struct 中時,用戶可以充分利用其可擴展性,可以通過調(diào)用堆棧構(gòu)建 WithCancel,WithDeadlineWithValue 的傳遞樹。而且最重要的是,當(dāng)將其作為參數(shù)傳入時,可以清楚地了解其傳播范圍,從而可以輕松地理解和調(diào)試。

          最后一句話總結(jié)本文,When designing an API with context, remember the advice: pass context.Context in as an argument; don't store it in structs.


          原文地址:https://blog.golang.org/context-and-structs

          官方資訊*最新技術(shù)*獨家解讀

          瀏覽 82
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲最大视频免费网站 | 日韩无码电影一区 | 男人的天堂黄片 | 激情影院tiantang | 亚洲无码在线中文字幕 |