主頁 > 知識庫 > 五步讓你成為GO 語言高手

五步讓你成為GO 語言高手

熱門標簽:鐵路電話系統(tǒng) 地方門戶網(wǎng)站 AI電銷 Linux服務器 百度競價排名 呼叫中心市場需求 服務外包 網(wǎng)站排名優(yōu)化

Francesc (@francesc) 是 Go 核心團隊的一員, 是提倡 Google Cloud 平臺的開發(fā)者. 他是一個編程語言的愛好者, Google的技術(shù)指導大師, Go tour的創(chuàng)造者之一. 這個討論的靈感來自于另一個 Raquel Vélez 在 JSConf. Slides 討論,這個討論已經(jīng)發(fā)到了這里.

Sourcegraph 是下一代編程協(xié)作工具, 用于搜索, 探索, 和審查代碼. 我們參加GopherCon India 來分享我們是怎樣使用 Go 并學習別人是怎樣使用它的, 對配合liveblog的這次討論我們深感榮幸.

作為Go團隊的開發(fā)者之一,F(xiàn)rancesc可能比世界上其他人接觸到的Go語言程序員都要多。正因為有了這樣的有利條件,他把Go語言的學習過程劃分為5個階段。

這些階段對于其他語言的學習也是成立的。理解自己處于哪個階段,可以幫助你找到提高自己的最有效的方法,也可以避免每個階段學習過程中的常見陷阱。

編者按:這篇文章對于每一個學習階段都給出了交互式的代碼片段。點擊函數(shù)名你就可以跳到具體的函數(shù)定義,方便進行深入的研究。請看下文。

這里是GO程序員的五個進化階段:

第一個階段(菜逼): 剛剛學習了這門語言。 已經(jīng)通過一些教程或者培訓班了解基本的語法,可以寫短的代碼片段。

第二個階段 (探索者): 可以寫一個完整的程序,但不懂一些更高級的語言特征,比如“channels”。還沒有使用GO寫一個大項目。

第三個階段(大手): 你能熟練的使用Go, 能夠用GO去解決,生產(chǎn)環(huán)境中一個具體和完整的問題。已經(jīng)形成了一套自己的慣用法和常用代碼庫。在你的編碼方案中Go是一個非常好用的工具。

第四階段 (大神): 絕逼清楚Go語言的設計選擇和背后的動機。能理解的簡潔和可組合性哲學。

布道師: 積極地與他人分享關于Go語言知識和你對Go語言的理解。在各種合適的場所發(fā)出自己的聲音, 參與郵件列表、建立QQ群、做專題報告。成為一個布道者不見得是一個完全獨立的階段,這個角色可以在上述的任何一個階段中。

第一階段: 菜逼

菜鳥在這個階段使用Go去創(chuàng)建一些小項目或者玩具項目。他們應該會利用到Go tour, Go playground, Go文檔, 和郵件列表(golang-nuts).

func main() {
    fmt.Println(stringutil.Reverse("!selpmaxe oG ,olleH"))}

這是Go語言寫的簡單例子,這個代碼段來自golang/example代碼庫里面的 hello.go 。 點擊就可以查看完整代碼擼。

一項重要的技能,新人應該試著學習如何正確提問。很多新人在郵件列表里面這樣說“嘿,這報錯了”,這并沒有提供足夠的信息,讓別人能理解并幫助他們解決問題。別人看到的是一個粘貼了幾百行的代碼的帖子,并沒有花費精力來重點說明所遇到的問題。

所以, 應該盡量避免直接粘貼代碼到論壇。而應該使用可以編輯并且可以在瀏覽器中直接運行的Go playground的“分享”按鈕鏈接到代碼片段。

Phase 2: the explorer

探索者已經(jīng)可以使用Go寫一些小的軟件,但有時仍然會有些迷茫。他們可能不完全明白怎么使用Go的高級特性,比如通道。雖然他們還有很多東西要學習,但已掌握的足夠做一些有用的事情了!他們開始對Go的潛能有感覺了,并對它們能使用Go創(chuàng)建的東西感到興奮。

在探索階段通常會經(jīng)歷兩個步驟。第一,膨脹的預期達到頂點,你覺得可以用Go做所有的事情,但還并不能明白或領悟到Go的真諦。你大概會用所熟悉的語言的模式和慣用語來寫Go代碼,但對于什么是地道的Go,還沒有比較強烈的感覺。你開始嘗試著手干這樣的事情--“遷移架構(gòu)X,從Y語言到Go語言”。

到達預期膨脹的頂點之后,你會遇到理想幻滅的低谷。你開始想念語言Y的特性X,此時你還沒有完全的掌握地道的Go。你還在用其他編程語言的風格來寫Go語言的程序,你甚至開始覺得沮喪。你可能在大量使用reflect和unsafe這兩個包,但這不是地道的Go。地道的Go不會使用那些魔法一樣的東西。

這個探索階段產(chǎn)生的項目的一個很好的例子就是Martini Web框架。Martini是一個Go語言的早期Web框架,它從Ruby的Web框架當中吸收了很多思想(比如依賴注入)。最初,這個框架在社區(qū)中引起了強烈的反響,但是它逐漸在性能和可調(diào)試性上受到了一些批評。Martini框架的作者Jeremy Saenz積極響應這些來自Go社區(qū)的反饋,寫了一個更加符合Go語言規(guī)范的庫Negroni

func (m *Martini) RunOnAddr(addr string) {
    // TODO: Should probably be implemented using a new instance of http.Server in place of
    // calling http.ListenAndServer directly, so that it could be stored in the martini struct for later use.
    // This would also allow to improve testing when a custom host and port are passed.
 
    logger := m.Injector.Get(reflect.TypeOf(m.logger)).Interface().(*log.Logger)
    logger.Printf("listening on %s (%s)\n", addr, Env)
    logger.Fatalln(http.ListenAndServe(addr, m))}

來自Martini框架的交互式代碼片段,它是不地道的Go的例子。注意用反射包實現(xiàn)的依賴注入

func TestNegroniServeHTTP(t *testing.T) {
    result := ""
    response := httptest.NewRecorder()
 
    n := New()
    n.Use(HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
        result += "foo"
        next(rw, r)
        result += "ban"
    }))
    n.Use(HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
        result += "bar"
        next(rw, r)
        result += "baz"
    }))
    n.Use(HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
        result += "bat"
        rw.WriteHeader(http.StatusBadRequest)
    }))
 
    n.ServeHTTP(response, (*http.Request)(nil))
 
    expect(t, result, "foobarbatbazban")
    expect(t, response.Code, http.StatusBadRequest)}

來自Negroni庫的交互式代碼片段,它是地道的Go的例子

其他語言在提供一些核心功能,比如HTTP處理的時候,往往需要依賴第三方庫。但是Go語言在這一點上很不同,它的標準庫非常強大。如果你認為Go標準庫沒有強大到可以做你想做的事情,那么我說你錯了。Go語言標準庫難以置信的強大,值得你花時間閱讀它的代碼,學習它實現(xiàn)的模式。

func (srv *Server) ListenAndServe() error {
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})}

Go標準庫中的ListenAndServe函數(shù)片段。如果你寫過Go程序,你可能已經(jīng)調(diào)用過這個函數(shù)很多次了,但是你曾經(jīng)花時間看過它的實現(xiàn)么?去點擊上面的代碼片段吧。

幻滅的低谷中的幻滅感來自于這樣的事實:你還在用其他語言的模式來想問題,而且你還沒有完全探索過Go能提供給你什么。下面是一些好玩的事情,你可以做一下來打破困境,進一步探索這門語言中好玩的事。

go generate

現(xiàn)在來看看go generate。go generate是一個你可以用來自動自成Go代碼的命令。你可以結(jié)合例如jsonenums(一個用于為枚舉類型自動生成JSON編組樣板代碼的類庫)這樣的元編程來使用go generate快速自動實現(xiàn)重復乏味代碼的編寫。在Go標準類庫里面已經(jīng)有大量可以用于解析AST的接口,而AST使得編寫元編程工具更簡單,更容易。在會議上,有另外兩次討論(Go語言中的元編程實踐和擁抱標準類庫)談及到了這一點。

func main() {
    flag.Parse()
    if len(*typeNames) == 0 {
        log.Fatalf("the flag -type must be set")
    }
    types := strings.Split(*typeNames, ",")
 
    // Only one directory at a time can be processed, and the default is ".".
    dir := "."
    if args := flag.Args(); len(args) == 1 {
        dir = args[0]
    } else if len(args) > 1 {
        log.Fatalf("only one directory at a time")
    }
 
    pkg, err := parser.ParsePackage(dir, *outputSuffix+".go")
    if err != nil {
        log.Fatalf("parsing package: %v", err)
    }
 
    var analysis = struct {
        Command        string
        PackageName    string
        TypesAndValues map[string][]string
    }{
        Command:        strings.Join(os.Args[1:], " "),
        PackageName:    pkg.Name,
        TypesAndValues: make(map[string][]string),
    }
 
    // Run generate for each type.
    for _, typeName := range types {
        values, err := pkg.ValuesOfType(typeName)
        if err != nil {
            log.Fatalf("finding values for type %v: %v", typeName, err)
        }
        analysis.TypesAndValues[typeName] = values
 
        var buf bytes.Buffer
        if err := generatedTmpl.Execute(buf, analysis); err != nil {
            log.Fatalf("generating code: %v", err)
        }
 
        src, err := format.Source(buf.Bytes())
        if err != nil {
            // Should never happen, but can arise when developing this code.
            // The user can compile the output to see the error.
            log.Printf("warning: internal error: invalid Go generated: %s", err)
            log.Printf("warning: compile the package to analyze the error")
            src = buf.Bytes()
        }
 
        output := strings.ToLower(typeName + *outputSuffix + ".go")
        outputPath := filepath.Join(dir, output)
        if err := ioutil.WriteFile(outputPath, src, 0644); err != nil {
            log.Fatalf("writing output: %s", err)
        }
    }}

一段互動的片段演示了如何編寫jsonenums命令。

OpenGL

許多人使用Go作web服務,但是你知道你也可以用Go寫出很cool的圖形應用嗎?查看Go在OpenGL中的捆綁。

func main() {
    glfw.SetErrorCallback(errorCallback)
 
    if !glfw.Init() {
        panic("Can't init glfw!")
    }
    defer glfw.Terminate()
 
    window, err := glfw.CreateWindow(Width, Height, Title, nil, nil)
    if err != nil {
        panic(err)
    }
 
    window.MakeContextCurrent()
 
    glfw.SwapInterval(1)
 
    gl.Init()
 
    if err := initScene(); err != nil {
        fmt.Fprintf(os.Stderr, "init: %s\n", err)
        return
    }
    defer destroyScene()
 
    for !window.ShouldClose() {
        drawScene()
        window.SwapBuffers()
        glfw.PollEvents()
    }}

交互式的片段正說明Go的OpenGL捆綁能制作Gopher cube。點擊函數(shù)或方法名去探索。

黑客馬拉松和挑戰(zhàn)

你也可以觀看挑戰(zhàn)和黑客馬拉松,類似Gopher Gala和Go Challenge。在過去,來自世界各地的程序員一起挑戰(zhàn)一些真實的酷項目,你可以從中獲取靈感。

第三階段: 老手

作為一個老手,這意味著你可以解決很多Go語言中你關心的問題。新的需要解決的問題會帶來新的疑問,經(jīng)過試錯,你學會了在這門語言中什么是可以做的,什么是不能做的。此時,你已經(jīng)對這門語言的習慣和模式有了一個堅實的理解。你可以非常高效地工作,寫出可讀,文檔完善,可維護的代碼。

成為老手的一個很好的方法就是在大項目上工作。如果你自己有一個項目的想法,開始動手去做吧(當然你要確定它并不是已經(jīng)存在了)。大多數(shù)人也許并沒有一個很大的項目的想法,所以他們可以對已經(jīng)存在的項目做出貢獻。Go語言已經(jīng)有很多大型項目,而且它們正在被廣泛使用,比如Docker, Kubernetes和Go本身??梢钥纯催@個項目列表

func (cli *DockerCli) CmdRestart(args ...string) error {
    cmd := cli.Subcmd("restart", "CONTAINER [CONTAINER...]", "Restart a running container", true)
    nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing the container.")
    cmd.Require(flag.Min, 1)
 
    utils.ParseFlags(cmd, args, true)
 
    v := url.Values{}
    v.Set("t", strconv.Itoa(*nSeconds))
 
    var encounteredError error
    for _, name := range cmd.Args() {
        _, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, false))
        if err != nil {
            fmt.Fprintf(cli.err, "%s\n", err)
            encounteredError = fmt.Errorf("Error: failed to restart one or more containers")
        } else {
            fmt.Fprintf(cli.out, "%s\n", name)
        }
    }
    return encounteredError}

Docker項目的交互式代碼片段。點擊函數(shù)名,開始探索之旅吧。

老手應該對Go生態(tài)系統(tǒng)的工具有一個很強的掌握,因為這些工具真的提高生產(chǎn)效率。你應該了解go generate,go vet,go test-race, 和gofmt/goimports/goreturns。你應該使用go fmt,因為它會自動把你的代碼按照Go社區(qū)的風格標準來格式化。goimports可以做同樣的事情,而且還會添加丟失的imports。goretures不光做了前面所說的事情,還可以在返回表達式添加丟失的錯誤,這是大家都討厭的地方。

在老手階段,你一定要開始做code review。code review的意義并不是要修改或者找到錯誤(那是測試人員做的事情)。code review可以幫助維持統(tǒng)一的編程風格,提高軟件的總體質(zhì)量,還可以在別人的反饋中提高你自己的編程技術(shù)。幾乎所有的大型開源項目都對每一個提交做code review。

下面是一個從人類的反饋當中學習的例子:Google的Go團隊以前都在main函數(shù)的外面聲明命令行標記。在去年的GopherCon會議上,F(xiàn)rancesc遇到了SoundCloud公司的Peter Bourgon(@peterbourgon)。Peter Bourgon說在SoundCloud,他們都在main函數(shù)內(nèi)部聲明標記,這樣他們不會錯誤地在外部使用標記。Francesc現(xiàn)在認為這是最佳實踐。

第四階段:專家

作為一個專家,你很好地了解了語言的哲學思想。對于Go語言的特性,你知道何時應該使用,何時不應該使用。例如,Jeremy Saenz在dotGo風暴討論中談論到了何時不該使用接口。

func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call {
    call := new(Call)
    call.ServiceMethod = serviceMethod
    call.Args = args
    call.Reply = reply
    if done == nil {
        done = make(chan *Call, 10) // buffered.
    } else {
        // If caller passes done != nil, it must arrange that
        // done has enough buffer for the number of simultaneous
        // RPCs that will be using that channel.  If the channel
        // is totally unbuffered, it's best not to run at all.
        if cap(done) == 0 {
            log.Panic("rpc: done channel is unbuffered")
        }
    }
    call.Done = done
    client.send(call)
    return call}

來自標準類庫的一小塊交互代碼片段使用了頻道。理解標準類庫里面的模式背后的決策原因是成為一個專家必經(jīng)之路。

但是不要成為只局限于單一語言的專家。跟其他任何語言一樣,Go僅僅只是一個工具。你還應該去探索其他語言,并且學習他們的模式和風格。Francesc從他使用Go的經(jīng)驗中找到了編寫JavaScript的啟發(fā)。他還喜歡重點關注于不可變性和致力于避免易變性的Haskell語言,并從中獲得了如何編寫Go代碼的靈感。

布道者

作為一個布道者,你分享自己的知識,傳授你學會的和你提出的最佳實踐。你可以分享自己對Go喜歡或者不喜歡的地方。全世界各地都有Go會議,找到離你最近的。

你可以在任何一個階段成為布道者,不要等到你成為這個領域的專家的時候才發(fā)出自己的聲音。在你學習Go的任何一個階段,提出問題,結(jié)合你的經(jīng)驗給出反饋,不要羞于提出自己不喜歡的地方。你提出的反饋可以幫助社區(qū)改善做事情的方法,也可能改變你自己對編程的看法。

func main() {
    httpAddr := flag.String("http", "127.0.0.1:3999", "HTTP service address (e.g., '127.0.0.1:3999')")
    originHost := flag.String("orighost", "", "host component of web origin URL (e.g., 'localhost')")
    flag.StringVar(basePath, "base", "", "base path for slide template and static resources")
    flag.BoolVar(present.PlayEnabled, "play", true, "enable playground (permit execution of arbitrary user code)")
    nativeClient := flag.Bool("nacl", false, "use Native Client environment playground (prevents non-Go code execution)")
    flag.Parse()
 
    if basePath == "" {
        p, err := build.Default.Import(basePkg, "", build.FindOnly)
        if err != nil {
            fmt.Fprintf(os.Stderr, "Couldn't find gopresent files: %v\n", err)
            fmt.Fprintf(os.Stderr, basePathMessage, basePkg)
            os.Exit(1)
        }
        basePath = p.Dir
    }
    err := initTemplates(basePath)
    if err != nil {
        log.Fatalf("Failed to parse templates: %v", err)
    }
 
    ln, err := net.Listen("tcp", *httpAddr)
    if err != nil {
        log.Fatal(err)
    }
    defer ln.Close()
 
    _, port, err := net.SplitHostPort(ln.Addr().String())
    if err != nil {
        log.Fatal(err)
    }
    origin := url.URL{Scheme: "http"}
    if *originHost != "" {
        origin.Host = net.JoinHostPort(*originHost, port)
    } else if ln.Addr().(*net.TCPAddr).IP.IsUnspecified() {
        name, _ := os.Hostname()
        origin.Host = net.JoinHostPort(name, port)
    } else {
        reqHost, reqPort, err := net.SplitHostPort(*httpAddr)
        if err != nil {
            log.Fatal(err)
        }
        if reqPort == "0" {
            origin.Host = net.JoinHostPort(reqHost, port)
        } else {
            origin.Host = *httpAddr
        }
    }
 
    if present.PlayEnabled {
        if *nativeClient {
            socket.RunScripts = false
            socket.Environ = func() []string {
                if runtime.GOARCH == "amd64" {
                    return environ("GOOS=nacl", "GOARCH=amd64p32")
                }
                return environ("GOOS=nacl")
            }
        }
        playScript(basePath, "SocketTransport")
        http.Handle("/socket", socket.NewHandler(origin))
    }
    http.Handle("/static/", http.FileServer(http.Dir(basePath)))
 
    if !ln.Addr().(*net.TCPAddr).IP.IsLoopback()
        present.PlayEnabled !*nativeClient {
        log.Print(localhostWarning)
    }
 
    log.Printf("Open your web browser and visit %s", origin.String())
    log.Fatal(http.Serve(ln, nil))

流行的present命令的main函數(shù),很多Go的用戶使用它來制作幻燈片。許多演講者修改了這個模塊來滿足自己的需要。

QA

問:在GO語言中,我所懷念的一項功能是一個好的調(diào)試器。

答:我們正在做了,不只是調(diào)試器,我們還會提供一個更好的總體監(jiān)視工具可以讓你在程序運行時更好地洞察程序在干什么(顯示出所有正在運行的goroutine的狀態(tài))。在GO 1.5中探索它吧。

以上所述就是本文的全部內(nèi)容了,希望大家能夠喜歡。

您可能感興趣的文章:
  • Go語言的GOPATH與工作目錄詳解
  • Go語言中的Array、Slice、Map和Set使用詳解
  • Go語言interface詳解
  • Go語言命令行操作命令詳細介紹
  • Go語言運行環(huán)境安裝詳細教程
  • GO語言數(shù)組和切片實例詳解
  • Go語言創(chuàng)建、初始化數(shù)組的常見方式匯總
  • Go語言常用字符串處理方法實例匯總
  • GO語言標準錯誤處理機制error用法實例
  • Go程序性能優(yōu)化及pprof使用方法詳解

標簽:黃山 蘭州 衡水 銅川 崇左 湘潭 仙桃 湖南

巨人網(wǎng)絡通訊聲明:本文標題《五步讓你成為GO 語言高手》,本文關鍵詞  ;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P信息告之我們,我們將及時溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡,涉及言論、版權(quán)與本站無關。
  • 相關文章
  • 收縮
    • 微信客服
    • 微信二維碼
    • 電話咨詢

    • 400-1100-266