关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » UI前端 »Go语言学习的五个进化阶段(带例子)

    Go语言学习的五个进化阶段(带例子)

    2015-03-07 00:00:00 出处:DestinyXie
    分享

    Francesc (@francesc) 是 Go 核心团队的一员, 是提倡 Google Cloud 平台的开发者. 他是一个编程语言的爱好者, Google的技术指导大师, Go tour的创造者之一. 这个讨论的灵感来自于另一个 Raquel Vélez 在 JSConf. Slides 的讨论,这个讨论已经发到了这里.

    Sourcegraph 是下一代编程协作工具, 用于搜索, 探索, 和审查代码. 我们参加GopherCon India 来分享我们是怎样使用 Go 并学习别人是怎样使用它的, 对配合liveblog的这次讨论我们深感荣幸.

    作为Go团队的开发者之一,Francesc可能比世界上其他人接触到的Go语言程序员都要多。正因为有了这样的有利条件,他把Go语言的学习过程划分为5个阶段。

    这些阶段对于其他语言的学习也是成立的。理解自己处于哪个阶段,可以帮助你找到提高自己的最有效的方法,也可以避免每个阶段学习过程中的常见陷阱。

    编者按:该文对于每一个学习阶段都给出了交互式的代码片段。点击函数名你就可以跳到具体的函数定义,方便进行深入的研究。请看下文。

    这里是GO程序员的五个进化阶段:

    第一个阶段(菜逼): 刚刚学习了这门语言。 已经通过一些教程或者培训班了解基本的语法,可以写短的代码片段。 第二个阶段 (探索者): 可以写一个完整的程序,但不懂一些更高级的语言特征,比如“channels”。还没有使用GO写一个大项目。 第三个阶段(大手): 你能熟练的使用Go, 能够用GO去解决,生产环境中一个具体和完整的问题。已经形成了一套自己的惯用法和常用代码库。在你的编码方案中Go是一个非常好用的工具。 第四阶段 (大神): 绝逼清楚Go语言的设计选择和背后的动机。能理解的简洁和可组合性哲学。 布道师: 积极地与他人分享关于Go语言知识和你对Go语言的理解。在各种合适的场所发出自己的声音, 参与邮件列表、建立QQ群、做专题报告。成为一个布道者不见得是一个完全独立的阶段,这个角色可以在上述的任何一个阶段中。

    第一阶段: 菜逼

    菜鸟在这个阶段使用Go去创建一些小项目或者玩具项目。他们应该会利用到Go tour, Go playground, Go文档, 和邮件列表(golang-nuts).

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

    查看上下文

    func main in golang/example on Sourcegraph

    这是Go语言写的简单例子,这个代码段来自golang/example 代码库里面的 hello.go 。 点击就可以查看完整代码撸。

    一项重要的技能,新人应该试着学习如何正确提问。很多新人在邮件列表里面这样说“嘿,这报错了”,这并没有提供足够的信息,让别人能理解并帮助他们解决问题。别人看到的是一个粘贴了几百行的代码的帖子,并没有花费精力来重点说明所遇到的问题。

    所以, 应该尽量避免直接粘贴代码到论坛。而应该使用可以编辑并且可以在浏览器中直接运行的Go playground的“分享”按钮链接到代码片段。

    探索者已经可以使用Go写一些小的软件,但有时仍然会有些迷茫。他们可能不完全明白怎么使用Go的高级特性,比如通道。虽然他们还有很多东西要学习,但已掌握的足够做一些有用的事情了!他们开始对Go的潜能有感觉了,并对它们能使用Go创建的东西感到兴奋。

    image

    在探索阶段通常会经历两个步骤。第一,膨胀的预期达到顶点,你觉得可以用Go做所有的事情,但还并不能明白或领悟到Go的真谛。你大概会用所熟悉的语言的模式和惯用语来写Go代码,但对于什么是地道的Go,还没有比较强烈的感觉。你开始尝试着手干这样的事情–“迁移架构X,从Y语言到Go语言”。

    到达预期膨胀的顶点之后,你会遇到理想幻灭的低谷。你开始想念语言Y的特性X,此时你还没有完全的掌握地道的Go。你还在用其他编程语言的风格来写Go语言的程序,你甚至开始觉得沮丧。你可能在大量使用reflect和unsafe这两个包,但这不是地道的Go。地道的Go不会使用那些魔法一样的东西。

    这个探索阶段产生的项目的一个很好的例子就是Martini Web框架。Martini是一个Go语言的早期Web框架,它从Ruby的Web框架当中吸收了很多思想(比如依赖注入)。最初,这个框架在社区中引起了强烈的反响,但是它逐渐在性能和可调试性上受到了一些批评。Martini框架的作者Jeremy Saenz积极响应这些来自Go社区的反馈,写了一个更加符合Go语言规范的库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).RunOnAddr in go-martini/martini on Sourcegraph

    来自Martini框架的交互式代码片段,它是不地道的Go的例子。注意用反射包实现的依赖注入

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

    查看上下文

    func TestNegroniServeHTTP in codegangsta/negroni on * Sourcegraph

    来自Negroni库的交互式代码片段,它是地道的Go的例子

    其他语言在提供一些核心功能,比如HTTP处理的时候,往往需要依赖第三方库。但是Go语言在这一点上很不同,它的标准库非常强大。假如你认为Go标准库没有强大到可以做你想做的事情,那么我说你错了。Go语言标准库难以置信的强大,值得你花时间阅读它的代码,学习它实现的模式。

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

    在上下文中查看

    (*Server).ListenAndServe in golang/go on* Sourcegraph

    Go标准库中的ListenAndServe函数片段。假如你写过Go程序,你可能已经调用过这个函数很多次了,但是你曾经花时间看过它的实现么?去点击上面的代码片段吧。

    幻灭的低谷中的幻灭感来自于这样的事实:你还在用其他语言的模式来想问题,而且你还没有完全探索过Go能提供给你什么。下面是一些好玩的事情,你可以做一下来打破困境,进一步探索这门语言中好玩的事。

    go generate

    现在来看看go generate。go generate是一个你可以用来自动自成Go代码的命令。你可以结合例如jsonenums(一个用于为枚举类型自动生成JSON编组样板代码的类库)这样的元编程来使用go generate快速自动实现重复乏味代码的编写。在Go标准类库里面已经有大量可以用于解析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)
            }
        }}

    查看上下文

    Sourcegraph 站点上 campoy/jsonenums 中的 main 函数

    一段互动的片段演示了如何编写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: %sn", err)
            return
        }
        defer destroyScene()
    
        for !window.ShouldClose() {
            drawScene()
            window.SwapBuffers()
            glfw.PollEvents()
        }}

    在文本中查看

    在 Sourcegraph中 go-gl/examples 里面的函数 main

    交互式的片段正说明Go的OpenGL捆绑能制作Gopher cube。点击函数或方法名去探索。

    黑客马拉松和挑战

    你也可以观看挑战和黑客马拉松,类似Gopher Gala和Go Challenge。在过去,来自世界各地的程序员一起挑战一些真实的酷项目,你可以从中获取灵感。

    第三阶段: 老手

    作为一个老手,这意味着你可以解决很多Go语言中你关心的问题。新的需要解决的问题会带来新的疑问,经过试错,你学会了在这门语言中什么是可以做的,什么是不能做的。此时,你已经对这门语言的习惯和模式有了一个坚实的理解。你可以非常高效地工作,写出可读,文档完善,可维护的代码。

    成为老手的一个很好的方法就是在大项目上工作。假如你自己有一个项目的想法,开始动手去做吧(当然你要确定它并不是已经存在了)。大多数人也许并没有一个很大的项目的想法,所以他们可以对已经存在的项目做出贡献。Go语言已经有很多大型项目,而且它们正在被广泛使用,比如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, "%sn", err)
                encounteredError = fmt.Errorf("Error: failed to restart one or more containers")
            } else {
                fmt.Fprintf(cli.out, "%sn", name)
            }
        }
        return encounteredError}

    在上下文中查看

    (*DockerCli).CmdRestart in docker/docker on * Sourcegraph

    Docker项目的交互式代码片段。点击函数名,开始探索之旅吧。

    老手应该对Go生态系统的工具有一个很强的掌握,因为这些工具真的提高生产效率。你应该了解go generate,go vet,go test-race, 和gofmt/goimports/goreturns。你应该使用go fmt,因为它会自动把你的代码按照Go社区的风格标准来格式化。goimports可以做同样的事情,而且还会添加丢失的imports。goretures不光做了前面所说的事情,还可以在返回表达式添加丢失的错误,这是大家都讨厌的地方。

    在老手阶段,你一定要开始做code review。code review的意义并不是要修改或者找到错误(那是测试人员做的事情)。code review可以帮助维持统一的编程风格,提高软件的总体质量,还可以在别人的反馈中提高你自己的编程技术。几乎所有的大型开源项目都对每一个提交做code review。

    下面是一个从人类的反馈当中学习的例子:Google的Go团队以前都在main函数的外面声明命令行标记。在去年的GopherCon会议上,Francesc遇到了SoundCloud公司的Peter Bourgon(@peterbourgon)。Peter Bourgon说在SoundCloud,他们都在main函数内部声明标记,这样他们不会错误地在外部使用标记。Francesc现在认为这是最佳实践。

    第四阶段:专家

    作为一个专家,你很好地了解了语言的哲学思想。对于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}

    查看上下文

    Sourcegraph 站点上 golang/go 中的 (*Client).Go

    来自标准类库的一小块交互代码片段使用了频道。理解标准类库里面的模式背后的决策原因是成为一个专家必经之路。

    但是不要成为只局限于单一语言的专家。跟其他任何语言一样,Go仅仅只是一个工具。你还应该去探索其他语言,并且学习他们的模式和风格。Francesc从他使用Go的经验中找到了编写JavaScript的启发。他还喜欢重点关注于不可变性和致力于避免易变性的Haskell语言,并从中获得了如何编写Go代码的灵感。

    布道者

    作为一个布道者,你分享自己的知识,传授你学会的和你提出的最佳实践。你可以分享自己对Go喜欢或者不喜欢的地方。全世界各地都有Go会议,找到离你最近的。

    你可以在任何一个阶段成为布道者,不要等到你成为这个领域的专家的时候才发出自己的声音。在你学习Go的任何一个阶段,提出问题,结合你的经验给出反馈,不要羞于提出自己不喜欢的地方。你提出的反馈可以帮助社区改善做事情的方法,也可能改变你自己对编程的看法。

    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: %vn", 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))

    在上下文中查看

    func main in golang/tools on Sourcegraph

    流行的present命令的main函数,很多Go的用户使用它来制作幻灯片。许多演讲者修改了这个模块来满足自己的需要。

    Q&A

    问:在GO语言中,我所怀念的一项功能是一个好的调试器。

    答:我们正在做了,不只是调试器,我们还会提供一个更好的总体监视工具可以让你在程序运行时更好地洞察程序在干什么(显示出所有正在运行的goroutine的状态)。在GO 1.5中探索它吧。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    正版 Windows 11产品密钥怎么查找/查看?

    还有3个月,微软将停止 Windows 10 的更新

    Windows 10 终止支持后,企业为何要立即升级?

    Windows 10 将于 2025年10 月终止技术支持,建议迁移到 Windows 11

    Windows 12 发布推迟,微软正全力筹备Windows 11 25H2更新

    Linux 退出 mail的命令是什么

    Linux 提醒 No space left on device,但我的空间看起来还有不少空余呢

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    IT头条

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    液冷服务器概念股走强,博汇、润泽等液冷概念股票大涨

    01:17

    亚太地区的 AI 驱动型医疗保健:2025 年及以后的下一步是什么?

    16:30

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    技术热点

    商业智能成CIO优先关注点 技术落地方显成效(1)

    用linux安装MySQL时产生问题破解

    JAVA中关于Map的九大问题

    windows 7旗舰版无法使用远程登录如何开启telnet服务

    Android View 事件分发机制详解

    MySQL用户变量的用法

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。