<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>

          『每周譯Go』Google:12 條 Golang 最佳實踐

          共 9012字,需瀏覽 19分鐘

           ·

          2021-11-04 16:57

          這是直接總結(jié)好的 12 條,詳細的再繼續(xù)往下看:

          1. 先處理錯誤避免嵌套

          2. 盡量避免重復(fù)

          3. 先寫最重要的代碼

          4. 給代碼寫文檔注釋

          5. 命名盡可能簡潔

          6. 使用多文件包

          7. 使用?go get?可獲取你的包

          8. 了解自己的需求

          9. 保持包的獨立性

          10. 避免在內(nèi)部使用并發(fā)

          11. 使用 Goroutine 管理狀態(tài)

          12. 避免 Goroutine 泄露

          # 最佳實踐?#

          這是一篇翻譯文章,為了使讀者更好的理解,會在原文翻譯的基礎(chǔ)增加一些講解或描述。

          來在維基百科:

          "A best practice is a method or technique that has consistently shown results superior
          to those achieved with other means"

          最佳實踐是一種方法或技術(shù),其結(jié)果始終優(yōu)于其他方式。

          寫 Go 代碼時的技術(shù)要求:

          • 簡單性

          • 可讀性

          • 可維護性

          #?樣例代碼?#

          需要優(yōu)化的代碼。

          type Gopher struct {
          Name string
          AgeYears int
          }

          func (g *Gopher) WriteTo(w io.Writer) (size int64, err error) {
          err = binary.Write(w, binary.LittleEndian, int32(len(g.Name)))
          if err == nil {
          size += 4
          var n int
          n, err = w.Write([]byte(g.Name))
          size += int64(n)
          if err == nil {
          err = binary.Write(w, binary.LittleEndian, int64(g.AgeYears))
          if err == nil {
          size += 4
          }
          return
          }
          return
          }
          return
          }

          看看上面的代碼,自己先思索在代碼編寫方式上怎么更好,我先簡單說下代碼意思是啥:

          • 將?Name?和?AgeYears?字段數(shù)據(jù)存入?io.Writer?類型中。

          • 如果存入的數(shù)據(jù)是?string?或?[]byte?類型,再追加其長度數(shù)據(jù)。

          如果對?binary?這個標準包不知道怎么使用,就看看我的另一篇文章《快速了解 “小字端” 和 “大字端” 及 Go 語言中的使用》。

          #?先處理錯誤避免嵌套?#

          func (g *Gopher) WriteTo(w io.Writer) (size int64, err error) {
          err = binary.Write(w, binary.LittleEndian, int32(len(g.Name)))
          if err != nil {
          return
          }
          size += 4
          n, err := w.Write([]byte(g.Name))
          size += int64(n)
          if err != nil {
          return
          }
          err = binary.Write(w, binary.LittleEndian, int64(g.AgeYears))
          if err == nil {
          size += 4
          }
          return
          }

          減少判斷錯誤的嵌套,會使讀者看起來更輕松。

          #?盡量避免重復(fù)?#

          上面代碼中?WriteTo?方法中的?Write?出現(xiàn)了 3 次,比較重復(fù),精簡后如下:

          type binWriter struct {
          w io.Writer
          size int64
          err error
          }

          // Write writes a value to the provided writer in little endian form.
          func (w *binWriter) Write(v interface{}) {
          if w.err != nil {
          return
          }
          if w.err = binary.Write(w.w, binary.LittleEndian, v); w.err == nil {
          w.size += int64(binary.Size(v))
          }
          }

          使用?binWriter?結(jié)構(gòu)體。

          func (g *Gopher) WriteTo(w io.Writer) (int64, error) {
          bw := &binWriter{w: w}
          bw.Write(int32(len(g.Name)))
          bw.Write([]byte(g.Name))
          bw.Write(int64(g.AgeYears))
          return bw.size, bw.err
          }

          #?type-switch 處理不同類型?#

          func (w *binWriter) Write(v interface{}) {
          if w.err != nil {
          return
          }
          switch v.(type) {
          case string:
          s := v.(string)
          w.Write(int32(len(s)))
          w.Write([]byte(s))
          case int:
          i := v.(int)
          w.Write(int64(i))
          default:
          if w.err = binary.Write(w.w, binary.LittleEndian, v); w.err == nil {
          w.size += int64(binary.Size(v))
          }
          }
          }

          func (g *Gopher) WriteTo(w io.Writer) (int64, error) {
          bw := &binWriter{w: w}
          bw.Write(g.Name)
          bw.Write(g.AgeYears)
          return bw.size, bw.err
          }

          #?type-switch 精簡?#

          摒棄了上面代碼的?v.(string)?、v.(int)?類型反射使用。

          func (w *binWriter) Write(v interface{}) {
          if w.err != nil {
          return
          }
          switch x := v.(type) {
          case string:
          w.Write(int32(len(x)))
          w.Write([]byte(x))
          case int:
          w.Write(int64(x))
          default:
          if w.err = binary.Write(w.w, binary.LittleEndian, v); w.err == nil {
          w.size += int64(binary.Size(v))
          }
          }
          }

          進入不同分支,x?變量對應(yīng)的就是該分支的類型。

          #?自行決定是否寫入?#

          type binWriter struct {
          w io.Writer
          buf bytes.Buffer
          err error
          }

          // Write writes a value to the provided writer in little endian form.
          func (w *binWriter) Write(v interface{}) {
          if w.err != nil {
          return
          }
          switch x := v.(type) {
          case string:
          w.Write(int32(len(x)))
          w.Write([]byte(x))
          case int:
          w.Write(int64(x))
          default:
          w.err = binary.Write(&w.buf, binary.LittleEndian, v)
          }
          }

          // Flush writes any pending values into the writer if no error has occurred.
          // If an error has occurred, earlier or with a write by Flush, the error is
          // returned.
          func (w *binWriter) Flush() (int64, error) {
          if w.err != nil {
          return 0, w.err
          }
          return w.buf.WriteTo(w.w)
          }

          func (g *Gopher) WriteTo(w io.Writer) (int64, error) {
          bw := &binWriter{w: w}
          bw.Write(g.Name)
          bw.Write(g.AgeYears)
          return bw.Flush()
          }

          WriteTo?方法中,分了兩大部分,增加了靈活性:

          • 組裝信息

          • 調(diào)用?Flush?方法來決定是否寫入?w。

          #?函數(shù)適配器?#

          func init() {
          http.HandleFunc("/", handler)
          }

          func handler(w http.ResponseWriter, r *http.Request) {
          err := doThis()
          if err != nil {
          http.Error(w, err.Error(), http.StatusInternalServerError)
          log.Printf("handling %q: %v", r.RequestURI, err)
          return
          }

          err = doThat()
          if err != nil {
          http.Error(w, err.Error(), http.StatusInternalServerError)
          log.Printf("handling %q: %v", r.RequestURI, err)
          return
          }
          }

          函數(shù)?handler?包含了業(yè)務(wù)的邏輯和錯誤處理,下來將錯誤處理單獨寫一個函數(shù)處理,代碼修改如下:

          func init() {
          http.HandleFunc("/", errorHandler(betterHandler))
          }

          func errorHandler(f func(http.ResponseWriter, *http.Request) error) http.HandlerFunc {
          return func(w http.ResponseWriter, r *http.Request) {
          err := f(w, r)
          if err != nil {
          http.Error(w, err.Error(), http.StatusInternalServerError)
          log.Printf("handling %q: %v", r.RequestURI, err)
          }
          }
          }

          func betterHandler(w http.ResponseWriter, r *http.Request) error {
          if err := doThis(); err != nil {
          return fmt.Errorf("doing this: %v", err)
          }

          if err := doThat(); err != nil {
          return fmt.Errorf("doing that: %v", err)
          }
          return nil
          }

          #?組織你的代碼?#


          1. 先寫最重要的

          許可信息、構(gòu)建信息、包文檔。

          import?語句:相關(guān)聯(lián)組使用空行分隔。

          import (
          "fmt"
          "io"
          "log"

          "golang.org/x/net/websocket"
          )

          其余代碼,以最重要的類型開始,以輔助函數(shù)和類型結(jié)尾。

          2. 文檔注釋

          包名前的相關(guān)文檔。

          // Package playground registers an HTTP handler at "/compile" that
          // proxies requests to the golang.org playground service.
          package playground

          Go 語言中的標示符(變量、結(jié)構(gòu)體等等)在 godoc 導(dǎo)出的文章中應(yīng)該被正確的記錄下來。

          // Author represents the person who wrote and/or is presenting the document.
          type Author struct {
          Elem []Elem
          }

          // TextElem returns the first text elements of the author details.
          // This is used to display the author' name, job title, and company
          // without the contact details.
          func (p *Author) TextElem() (elems []Elem) {

          擴展

          使用 godoc 工具在網(wǎng)頁上查看 go 項目文檔。

          # 安裝
          go get golang.org/x/tools/cmd/godoc

          # 啟動服務(wù)
          godoc -http=:6060

          直接在本地訪問 localhost:6060?查看文檔。

          3. 命名盡可能簡潔

          或者說,長命名不一定好。

          盡可能找到一個可以清晰表達的簡短命名,例如:

          • MarshalIndent?比?MarshalWithIndentation?好。

          不要忘了,在調(diào)用包內(nèi)容時,會先寫包名。

          • 在?encoding/json?包內(nèi),有一個結(jié)構(gòu)體?Encoder,不要寫成?JSONEncoder。

          • 這樣被使用?json.Encoder?。

          4. 多文件包

          是否應(yīng)該將一個包拆分到多個文件?

          • 應(yīng)避免代碼太長

          標準包?net/http?總共 15734 行代碼,被拆分到 47 個文件中。

          • 拆分代碼和測試。

          net/http/cookie.go 和 net/http/cookie_test.go 文件都放置在 http 包下。

          測試代碼只有在測試時才被編譯。

          • 拆分包文檔

          當在一個包內(nèi)有多個文件時,按照慣例,創(chuàng)建一個 doc.go 文件編寫包的文檔描述。

          個人思考:當一個包的說明信息比較多時,可以考慮創(chuàng)建 doc.go 文件。

          5. 使用 go get 可獲取你的包

          當你的包被提供使用時,應(yīng)該清晰的讓使用者知道哪些可復(fù)用,哪些不可復(fù)用。

          所以,當一些包可能會被復(fù)用,有些則不會的情況下怎么做?

          例如:定義一些網(wǎng)絡(luò)協(xié)議的包可能會復(fù)用,而定義一些可執(zhí)行命令的包則不會。



          • cmd?可執(zhí)行命令的包,不提供復(fù)用

          • pkg?可復(fù)用的包

          個人思考:如果一個項目中的可執(zhí)行入口比較多,建議放置在 cmd 目錄中,而對于 pkg 目錄目前是不太建議,所以不用借鑒。

          #?API?#

          1. 了解自己的需求

          我們繼續(xù)使用之前的 Gopher 類型。

          type Gopher struct {
          Name string
          AgeYears int
          }

          我們可以定義這個方法。

          func (g *Gopher) WriteToFile(f *os.File) (int64, error) {

          但方法的參數(shù)使用具體的類型時會變得難以測試,因此我們使用接口。

          func (g *Gopher) WriteToReadWriter(rw io.ReadWriter) (int64, error) {

          并且,當使用了接口后,我們應(yīng)該只需定義我們所需要的方法。

          func (g *Gopher) WriteToWriter(f io.Writer) (int64, error) {

          2. 保持包的獨立性

          import (
          "golang.org/x/talks/content/2013/bestpractices/funcdraw/drawer"
          "golang.org/x/talks/content/2013/bestpractices/funcdraw/parser"
          )
          // Parse the text into an executable function.
          f, err := parser.Parse(text)
          if err != nil {
          log.Fatalf("parse %q: %v", text, err)
          }

          // Create an image plotting the function.
          m := drawer.Draw(f, *width, *height, *xmin, *xmax)

          // Encode the image into the standard output.
          err = png.Encode(os.Stdout, m)
          if err != nil {
          log.Fatalf("encode image: %v", err)
          }

          代碼中?Draw?方法接受了?Parse?函數(shù)返回的?f?變量,從邏輯上看?drawer?包依賴?parser?包,下來看看如何取消這種依賴性。

          parser?包:

          type ParsedFunc struct {
          text string
          eval func(float64) float64
          }

          func Parse(text string) (*ParsedFunc, error) {
          f, err := parse(text)
          if err != nil {
          return nil, err
          }
          return &ParsedFunc{text: text, eval: f}, nil
          }

          func (f *ParsedFunc) Eval(x float64) float64 { return f.eval(x) }
          func (f *ParsedFunc) String() string { return f.text }

          drawer?包:

          import (
          "image"

          "golang.org/x/talks/content/2013/bestpractices/funcdraw/parser"
          )

          // Draw draws an image showing a rendering of the passed ParsedFunc.
          func DrawParsedFunc(f parser.ParsedFunc) image.Image {

          使用接口類型,避免依賴。

          import "image"

          // Function represent a drawable mathematical function.
          type Function interface {
          Eval(float64) float64
          }

          // Draw draws an image showing a rendering of the passed Function.
          func Draw(f Function) image.Image {

          測試:接口類型比具體類型更容易測試。

          package drawer

          import (
          "math"
          "testing"
          )

          type TestFunc func(float64) float64

          func (f TestFunc) Eval(x float64) float64 { return f(x) }

          var (
          ident = TestFunc(func(x float64) float64 { return x })
          sin = TestFunc(math.Sin)
          )

          func TestDraw_Ident(t *testing.T) {
          m := Draw(ident)
          // Verify obtained image.

          4. 避免在內(nèi)部使用并發(fā)

          func doConcurrently(job string, err chan error) {
          go func() {
          fmt.Println("doing job", job)
          time.Sleep(1 * time.Second)
          err <- errors.New("something went wrong!")
          }()
          }

          func main() {
          jobs := []string{"one", "two", "three"}

          errc := make(chan error)
          for _, job := range jobs {
          doConcurrently(job, errc)
          }
          for _ = range jobs {
          if err := <-errc; err != nil {
          fmt.Println(err)
          }
          }
          }

          如果這樣做,那如果我們想同步調(diào)用?doConcurrently?該如何做?

          func do(job string) error {
          fmt.Println("doing job", job)
          time.Sleep(1 * time.Second)
          return errors.New("something went wrong!")
          }

          func main() {
          jobs := []string{"one", "two", "three"}

          errc := make(chan error)
          for _, job := range jobs {
          go func(job string) {
          errc <- do(job)
          }(job)
          }
          for _ = range jobs {
          if err := <-errc; err != nil {
          fmt.Println(err)
          }
          }
          }

          對外暴露同步的函數(shù),這樣并發(fā)調(diào)用時也是容易的,同樣也滿足同步調(diào)用。

          #?最佳的并發(fā)實踐?#

          1. 使用 Goroutine 管理狀態(tài)

          Goroutine 之間使用一個 “通道” 或帶有通道字段的 “結(jié)構(gòu)體” 來通信。

          type Server struct{ quit chan bool }

          func NewServer() *Server {
          s := &Server{make(chan bool)}
          go s.run()
          return s
          }

          func (s *Server) run() {
          for {
          select {
          case <-s.quit:
          fmt.Println("finishing task")
          time.Sleep(time.Second)
          fmt.Println("task done")
          s.quit <- true
          return
          case <-time.After(time.Second):
          fmt.Println("running task")
          }
          }
          }

          func (s *Server) Stop() {
          fmt.Println("server stopping")
          s.quit <- true
          <-s.quit
          fmt.Println("server stopped")
          }

          func main() {
          s := NewServer()
          time.Sleep(2 * time.Second)
          s.Stop()
          }

          2. 使用帶緩沖的通道避免 Goroutine 泄露

          func sendMsg(msg, addr string) error {
          conn, err := net.Dial("tcp", addr)
          if err != nil {
          return err
          }
          defer conn.Close()
          _, err = fmt.Fprint(conn, msg)
          return err
          }

          func main() {
          addr := []string{"localhost:8080", "http://google.com"}
          err := broadcastMsg("hi", addr)

          time.Sleep(time.Second)

          if err != nil {
          fmt.Println(err)
          return
          }
          fmt.Println("everything went fine")
          }

          func broadcastMsg(msg string, addrs []string) error {
          errc := make(chan error)
          for _, addr := range addrs {
          go func(addr string) {
          errc <- sendMsg(msg, addr)
          fmt.Println("done")
          }(addr)
          }

          for _ = range addrs {
          if err := <-errc; err != nil {
          return err
          }
          }
          return nil
          }

          這段代碼有個問題,如果提前返回了?err?變量,errc?通道將不會被讀取,因此 Goroutine 將會阻塞。

          總結(jié)

          • 在寫入通道時 Goroutine 被阻塞。

          • Goroutine 持有對通道的引用。

          • 通道不會被 gc 回收。

          使用緩沖通道解決 Goroutine 阻塞問題。

          func broadcastMsg(msg string, addrs []string) error {
          errc := make(chan error, len(addrs))
          for _, addr := range addrs {
          go func(addr string) {
          errc <- sendMsg(msg, addr)
          fmt.Println("done")
          }(addr)
          }

          for _ = range addrs {
          if err := <-errc; err != nil {
          return err
          }
          }
          return nil
          }

          如果我們不能預(yù)知通道的緩沖大小,也稱容量,那該怎么辦?

          創(chuàng)建一個傳遞退出狀態(tài)的通道來避免 Goroutine 的泄露。

          func broadcastMsg(msg string, addrs []string) error {
          errc := make(chan error)
          quit := make(chan struct{})

          defer close(quit)

          for _, addr := range addrs {
          go func(addr string) {
          select {
          case errc <- sendMsg(msg, addr):
          fmt.Println("done")
          case <-quit:
          fmt.Println("quit")
          }
          }(addr)
          }

          for _ = range addrs {
          if err := <-errc; err != nil {
          return err
          }
          }
          return nil
          }

          #?參考?#

          原文鏈接:https://talks.golang.org/2013/bestpractices.slide#1

          視頻鏈接:https://www.youtube.com/watch?v=8D3Vmm1BGoY


          想要了解關(guān)于 Go 的更多資訊,還可以通過掃描的方式,進群一起探討哦~



          瀏覽 28
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产亚洲 久一区二区草榴AV | www.黄片视频 | 视频一区二区三区四区久久 | 欧美精品成人网站在线 | 亚洲看片网 |