『Go 语言学习专栏』-- 第十三期

发布于 2018-06-04 作者 超级苦工 429次 浏览 版块 分享

go-13.png
13.png

大家好,我叫谢伟,是一名程序员。

这个选题我认真思考了很久,决定把现在的方案分享出来,即:如何从 Github 的开源代码中学习?(中级版本)

下文介绍的方法是我目前的做法,但我希望能不断的进行迭代,达到更佳的效果

如果你跟着这个栏目,进行了学习,私底下也花了些时间,不管是看了更多的书籍,学习了更多的教程,还是写了更多的示例。今天的主题便是带你突破:即如何从入门选手达到中级选手。

假设,你已经大概掌握了Go 语言的基本语法。能独立写一些代码。实现一些基本的需求,即已经入门Go。

那么如何进入到中级水平?

有人说,做项目。

确实,项目确实能够引领你达到中级水平。但问题是,初级阶段,你可能不知道该选择一些什么项目。

  • 哪些项目刚刚好是你需要掂一掂脚刚好够得着?
  • 哪些项目是你还需要更多点的知识才能够的着?
  • 从项目中学什么?
  • 怎么从开源项目中学?梳理流程?抄代码?复现一遍?

这些都是问题。

下面给出我的策略,希望对你有所启发。

1. 评估自己的水平

首先第一点,需要评估自己的水平,知道自己的水平,你才能对一些开源的项目,在一定程度上理解项目的难易复杂度。

比如 Go 里面的明星产品Docker, 是一个非常热门的开源项目,作为初学者的你,你能凭一己之力,就开始学习或者模仿它吗?

答案是可以,但是很难。很容易就打击你的信心,初学者,信心很重要的,不然轻而易举的容易放弃。

那如何评估自己的水平?自己能评估出自己的水平吗?

这个问题,我准备从下面几点来进行评估。首先,确保你对自己真诚。如实的反馈自己的水平。

  • 代码量
  • 写代码的速度
  • 对内置库的使用程度

1.1 代码量

从代码量的角度来看,一定程序上能反映出,你到底写了多少行代码。当然这里的代码量不是粘贴复制,写重复的代码,而是有一定水平的代码,至少完成了某些功能、需求。

你可能没什么概念,一般来说,如果你是在企业内,一天8小时的工作量,连续写代码的时间不会超过 2小时,意味着,一天的核心代码量大概会是 200 行(数据仅供参考),如果你是学生,自己学习,那我也不太相信,你一天能连续写代码超过 2 小时,能一天写出几千行代码。一天的核心代码量大概也就几百行的量级,更何况在学校,你完成的代码,可能都不是一些略带难度,完成一定需求的代码。

我从入门一门编程语言的代码量衡量角度来说,大概需要5000行左右。(个人估算:200 * 5 * 5)

1.2 写代码的速度

这点来说,不是越快越好,一般的写代码,前期的规划、思考、构建等都极其的重要,这些决定了你编码实现的具体分工以及实现的可能性。

那怎么评估写代码的速度?

我认为,第一:不过分依赖现有网上的实现方案。即你上网查阅的比较少,即可实现你的需求;第二:运行出错都能根据IDE 提示解决。而不是频繁的依赖于 搜素引擎。

1.3 内置库的使用

内置库可以说是你编写程序的基石,不一定对内置库的用法了然于胸,但是需要根据文档或者内置的库代码,知道最基础的用法。能快速的知道用哪个库。

比如你需要执行操作系统的一些命令,那么你要清楚知道需要使用的os/exec库。

知道这个命令相关的知识:

  • 指定目录下运行
  • 带什么参数
  • 命令存在环境变量中的目录
  • 是否存在该命令等

即:需要对内置库有一定的使用经验。

2. 找感兴趣的项目

正确评估自己的水平之后,需要寻找到一些自己感兴趣的项目。

我推荐两个平台吧:

  • Github
  • 掘金

Github 几乎是最佳的选择, 掘金这个平台,最近我发现也是挺好的,虽然也是属于技术聚合类的平台,但质量或者颜值还算可以。但是存在一个问题。即自己感兴趣的项目如何能寻找到?

我推荐下面的方法:

  • 热门
  • 趋势:Github Trending

即合理使用 Github Trending :


go-2018-06-03.png

你可以选择你感兴趣的编程语言,查阅到最佳热门的项目都有哪些。继而可以关注感兴趣的作者。通过感兴趣的作者,继而可以发现一些好玩的项目。

另外一个是使用掘金插件,可以尽可能多的机会发现好玩的项目。

掘金-2018-06-03.png

比如,我可能比较关注后端这块,这些侧边栏可以发现很多的分享者,进而可能关注到项目和源代码。

总之,一句话,合理使用平台,尽可能创造机会发现感兴趣的项目。

大概,你现在知道如何发现感兴趣的项目了。

那如何评估,是否适合你来学习呢?

  • 看源代码

第一步我们已经评估了自己的水平,达到了一定的水平,那么看源代码就是检阅你的时候了,第一,你需要看懂。第二,根据自己的水平,尝试是否进行花时间练习。

3. 实现最小代码

如果你根据源代码之后,决定花时间在这个项目上,那么下一步便是学习。学习这个项目的实现方法。不一定任何方面都值得你学习,我认为,比你更好的实现方式都值得学习。借鉴过来,有些是参考具体的实现方式,有些是参考技术的选择。

比如这个:抖音机器人
具体的实现其实不是太复杂,包括这个项目 微信跳一跳小助手

两者本质上选择的技术方案都是一致的:Python + ADB

无外乎使用编程和接口完成对手机的操作。不管是截图还是图像识别。

如果你之前不知道有这样的技术方案能对手机进行操作。那么你就增加了技术广度,知道面对这样的场景,应该选择的方案是 Python + ADB ,当然 Python 不是必须的,编程语言只是工具,你也可以使用 Go + ADB 来实现,都可以。

具体怎么从源代码中学习?我认为下面几点:

  • 确保实现最小的功能,摈弃完美主义
  • 主体功能实现

首先,你需要大概阅读下项目的源代码。不管是你Clone 下来,还是使用Github 源代码阅读插件:比如 Octotree、GayHub

其次,你需要了解项目结构:知道这块是干什么的,那块是干什么的。

然后,你需要抓住核心的东西。从最核心的东西入手,先自己实现这个项目的最小功能。

最后,如果你依然有兴趣。你可以这么操作。第一,换种编程语言重新实现,比如别人方案使用 Python ,你可以使用 Go; 第二,换个场景重新实现,比如别人是完成的是对知乎数据的分析抓取,那么你可以对简书,或者你感兴趣的网站数据分析抓取;第三,优化,有可能你能想出更好的实现方案,或者你仅仅只是优化这个项目的某一个层面而已。

举个例子吧:

我最近发现一个项目:汉子转拼音(https://github.com/mozillazg/go-pinyin)

因为我对 Go 感兴趣,所以,选择了 Go 实现的汉子转拼音的版本。这个工具还有其他的实现方式。

这个项目引起了我的注意,于是我阅读源代码,从帮助文档开始。

帮助文档这么写:

package main

import (
    "fmt"
    "github.com/mozillazg/go-pinyin"
)

func main() {
    hans := "中国人"

    // 默认
    a := pinyin.NewArgs()
    fmt.Println(pinyin.Pinyin(hans, a))
    // [[zhong] [guo] [ren]]

    // 包含声调
    a.Style = pinyin.Tone
    fmt.Println(pinyin.Pinyin(hans, a))
    // [[zhōng] [guó] [rén]]

    // 声调用数字表示
    a.Style = pinyin.Tone2
    fmt.Println(pinyin.Pinyin(hans, a))
    // [[zho1ng] [guo2] [re2n]]

    // 开启多音字模式
    a = pinyin.NewArgs()
    a.Heteronym = true
    fmt.Println(pinyin.Pinyin(hans, a))
    // [[zhong zhong] [guo] [ren]]
    a.Style = pinyin.Tone2
    fmt.Println(pinyin.Pinyin(hans, a))
    // [[zho1ng zho4ng] [guo2] [re2n]]

    fmt.Println(pinyin.LazyPinyin(hans, pinyin.NewArgs()))
    // [zhong guo ren]

    fmt.Println(pinyin.Convert(hans, nil))
    // [[zhong] [guo] [ren]]

    fmt.Println(pinyin.LazyConvert(hans, nil))
    // [zhong guo ren]
}

看上去,是输入一个汉字,区分各种模式,比如默认的形式:不包含声调;比如包含声调;比如声调的一二三四声使用数字来表示;比如多音字模式;等等。

看上去核心来自于:pinyin 这个包。


func SinglePinyin(r rune, a Args) []string {
    if a.Fallback == nil {
        a.Fallback = Fallback
    }
    value, ok := PinyinDict[int(r)]
    pys := []string{}
    if ok {
        pys = strings.Split(value, ",")
    } else {
        pys = a.Fallback(r, a)
    }
    if len(pys) > 0 {
        if !a.Heteronym {
            pys = pys[:1]
        }
        return applyStyle(pys, a)
    }
    return pys
}

// Pinyin 汉字转拼音,支持多音字模式.
func Pinyin(s string, a Args) [][]string {
    pys := [][]string{}
    for _, r := range s {
        py := SinglePinyin(r, a)
        if len(py) > 0 {
            pys = append(pys, py)
        }
    }
    return pys
}

// LazyPinyin 汉字转拼音,与 `Pinyin` 的区别是:
// 返回值类型不同,并且不支持多音字模式,每个汉字只取第一个音.
func LazyPinyin(s string, a Args) []string {
    a.Heteronym = false
    pys := []string{}
    for _, v := range Pinyin(s, a) {
        pys = append(pys, v[0])
    }
    return pys
}

看上去核心来自于这三个函数。遍历字符串。现在你的问题应该是如何将汉字字符串转换为拼音。

继续阅读,发现这个 pinyin_dict.go 文件

var PinyinDict = map[int]string{
    0x3007:  "líng,yuán,xīng",
    0x3400:  "qiū",
    0x3401:  "tiàn",
    0x3404:  "kuà",
    0x3405:  "wǔ",
    0x3406:  "yǐn",
}

看上去一个完整的对照表。即将字符串中字符转换为整型,通过整型能知道拼音是哪个。

好,至此,大概知道了思路。

将字符串中字符转换为十六进制数,通过十六进制数能得到拼音。

那么为了实现最小功能。我可能会这么想:

即:将自己的名字转换为拼音。

所以我这么做。

  • 找到"谢伟"两个拼音,转化为 map
  • 遍历字符串,将字符转换为 十六进制数
package main

import (
    "flag"
    "fmt"
    "strings"
)


func main() {
    var dictMap = map[int32]string{}

    dictMap[0x8c22] = "xiè"
    dictMap[0x4f1f] = "wěi"

    var a = "谢伟"
    pys := [][]string{}
    for _, v := range a {
        py, ok := dictMap[v]
        pyr := []string{}
        if ok {
            pyr = strings.Split(py, ",")
        }
        pys = append(pys, pyr)
    }

    fmt.Println(pys)

}

结果:

[[xiè] [wěi]]

好,上面一步的实现,只是完成了特定的汉子转拼音。

那么如何将任意汉子转换为拼音。

  • 将 pinyin_dict.go 的map 全部复制下来
  • 使用命令行解析参数
package main

import (
    "flag"
    "fmt"
    "go-example-for-live/thirteen/infra"
    "strings"
)

func Pinyin() {

    var name string

    flag.StringVar(&name, "n", "谢伟", "")
    flag.Parse()
    pys := []string{}

    for _, arg := range flag.Args() {
        for _, v := range arg {
            py, ok := infra.PinyinDict[v]
            if ok {
                pyList := strings.Split(py, ",")
                if len(pyList) > 0 {
                    pys = append(pys, pyList[0])
                }
            }
        }

    }

    fmt.Println(pys)

}

func main() {

    Pinyin()

}

结果:(windows 平台 go build main.go, 这么运行 main.exe 参数; Linux 平台 go build main.go , ./main 参数), 下文以 windows 平台为例。

main.exe 谢伟

结果:

[xiè wěi]
main.exe 谢小路

结果:

[xiè xiǎo lù]
main.exe 编程语言

结果:

[biān chéng yǔ yán]
main.exe 鹅鹅鹅,曲项向天歌

结果:

[é é é qū xiàng xiàng tiān gē]
main.exe 莫愁前路无知己,天下谁人不识君

结果:

[mò chóu qián lù wú zhī jǐ tiān xià shéi rén bù shí jūn]

通过这个例子,大概我们已经知道这个项目的核心代码了。差不多就已经解构了这个项目。

4. 扩展

上面的实现最小的基本功能,通过示例演示了如何操作,但不够。你还需要继续思考。

  • 理解其他模块
  • 哪些不足

还是以上文pinyin 项目为例,你或许会想拼音数据来自于哪?

顺藤摸瓜,作者已经告诉你了:pinyin 数据

Unihan Database 数据版本:

Date: 2017-05-14 07:01:48 GMT [JHJ] Unicode version: 10.0.0

  • kHanyuPinyin.txt: Unihan DatabasekHanyuPinyin 部分的拼音数据(来源于《漢語大字典》的拼音数据)
  • kXHC1983.txt: Unihan DatabasekXHC1983 部分的拼音数据(来源于《现代汉语词典》的拼音数据)
  • kHanyuPinlu.txt: Unihan DatabasekHanyuPinlu 部分的拼音数据(来源于《現代漢語頻率詞典》的拼音数据)
  • kMandarin.txt: Unihan DatabasekMandarin 部分的拼音数据(普通话中最常用的一个读音。zh-CN 为主,如果 zh-CN 中没有则使用 zh-TW 中的拼音)
  • kMandarin_overwrite.txt: 手工纠正 kMandarin.txt 中有误的拼音数据(可以修改
  • GBK_PUA.txt: Private Use Area 中有拼音的汉字,参考 GB 18030 - 维基百科,自由的百科全书可以修改
  • nonCJKUI.txt: 不属于 CJK Unified Ideograph 但是却有拼音的字符(可以修改
  • kMandarin_8105.txt: 《通用规范汉字表》(2013 年版)里 8105 个汉字最常用的一个读音 (可以修改)
  • overwrite.txt: 手工纠正的拼音数据(可以修改
  • pinyin.txt: 合并上述文件后的拼音数据
  • zdic.txt: 汉典网 的拼音数据

如果让你自己来做,你可能需要用到爬虫。把数据抓取下来。

你还可能思考,这个项目哪些值得你借鉴?

比如:作者是如何区分各种模式的?比如各种声调模式。为什么要区分各种声调模式啊?

等等,诸如此类。

5. 循坏

等你花了些时间,度过这个阶段,你可能需要练习更高层次的项目,不断的选择,或是工作中的启发、或是工作中的需求等,不断的实现最小的功能,完成阅读、编写代码。不断的思考。


全文完。等我有新的体会,我再分享出来。希望能对你有所启发,再会。

收藏
暂无回复