Google 的 Code Review 实践经验

Google 的 Code Review 目标是不断提供 codebase 的质量,同时要求审阅者在代码的高质量和业务的推进之间做好权衡,两边的极端都不要走,并给出了一些 实战经验,下面我总结了下:

1. 设计

新增的几块代码是否有意义?代码的结构是否合理,应该放在 codebase 里还是抽离成组件?对系统的持续集成是否会造成印象?等等

2. 功能

站在用户的角度和开发者的角度,从功能和代码块上去审阅功能的合理性,除此之外,还要考虑单从代码上看不到的问题,比如并发问题、UI 交互问题、死锁问题等等。

3. 复杂度

审查是否存在工程上的过度设计,鼓励解决当下的问题,不要对未来做过多的设计,因为未来(业务)充满了不确定性。

4. 测试

除非是紧急上线,一般情况下,都要求提交的代码有单元测试、集成测试和端对端测试,要确保测试用例正确、有意义、有效果。

5. 命名

开发者是否给所有的变量都准确命名?一个好的命名不应太长也不可过短,刚好让人看懂最好了。

6. 评论

开发者写的评论除了他自己别人看得懂么?所有的评论是否是有意义的?是否描述清楚了代码是干啥的?有一点需要强调:对于正则以及复杂的逻辑是必须加注释的。

7. 格式

这一块我觉得他讲的并不好,格式主要在工具层面通过 lint 来纠正,只不过格式之外会有一些团队的约定,需要人为去判断或者不好工具检测的部分,可以在 Code Review 的时候提出来。

8. 文档

相关的 README 或者文档自动生成工具是否补全,废弃的代码、文档是否删除等。

9. 每一行

确保每一行都仔细审阅,包括数据文件、自动生成的文件、大的数据结构描述等等,如果代码很难阅读,你应该提前告诉开发者注意这方面的问题,难阅读的代码,对任何人都是一样难阅读的,这种代码应该尽量避免出现。

10. 上下文

Code Review 工具一般只会展示 diff 的内容,有的时候,你需要阅读整个文件,以确保开发者的逻辑是有用的,站在系统层面去考虑,这段代码是否会带来复杂度,是否是健康的。

11. 好的内容

如果你看到开发者写的内容不错,不要吝啬的赞美和鼓励。

本文同步自 小胡子哥的个人网站,原文地址: http://www.barretlee.com/blog/2019/10/30/google-code-review-practice/

Google 的 Code Review 实践经验

换一种视角理解 awk 命令

awk 是使用频度非常高的一个超级有用的命令,如果你做过应用的线上运维,想必已经是十分熟悉了,但是对大多数人来说,它仍然是个陌生的东西,即便看过很多次文档,依然记不住它的模样,还是得翻文档、查 Google。下面我就带着你,换一种视角重新理解 awk。

AWK

它是什么?我觉得它就是一个表单筛选工具,往下看。

一个例子

下面是我截取 cat /etc/passwd 的部分内容,

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin

保存为 1.txt,然后执行如下命令:

cat 1.txt | awk -F : ' BEGIN { print "No. R1 R3 R4"; count = 0 } $3 > 5 && $3 < 50 && $1 !~ /root/ { count += 1; print NR, $1, $3, $4} END { print "total " count " lines" } '

输入的内容是:

No. R1 R3 R4
7 man 6 12
8 lp 7 7
9 mail 8 8
10 news 9 9
total 4 lines

看起来好像很复杂的样子,下面我们进行分解动作,一步一步带你认识 awk 的世界。

获取表单

例子里的数据是结构化的,每一行有 7 条数据,使用 : 连接符合并成一行,总共 14 行,所以你看到的就是一个无表头的 14 行 7 列数据,我们可以通过如下命令对表单进行整理:

cat 1.txt | awk -F : ' { print $1, $2, $3, $4, $5, $6, $7 } '

-F 是分隔符标识,默认 awk 命令以空格作为分隔符,在这个例子中,我们需要告诉程序 : 为列分隔符。这条命令执行的结果是:

root x 0 0 root /root /bin/bash
daemon x 1 1 daemon /usr/sbin /usr/sbin/nologin
bin x 2 2 bin /bin /usr/sbin/nologin
sys x 3 3 sys /dev /usr/sbin/nologin
sync x 4 65534 sync /bin /bin/sync
games x 5 60 games /usr/games /usr/sbin/nologin
man x 6 12 man /var/cache/man /usr/sbin/nologin
lp x 7 7 lp /var/spool/lpd /usr/sbin/nologin
mail x 8 8 mail /var/mail /usr/sbin/nologin
news x 9 9 news /var/spool/news /usr/sbin/nologin

$0 会打印整行数据,$1 表示第一列数据,同理,我们将七列数据都打印了出来,$1$7 都是 print 函数的入参,事实上,它与这种写法是一个意思:

cat 1.txt | awk -F : ' { print($1, $2, $3, $4, $5, $6, $7) } '

添加表头

由于我们提供的数据没有表头,我们给它加上:

cat 1.txt | awk -F : ' BEGIN { print("No.", "R1", "R2", "R3", "R4", "R5", "R6", "R7") } { print(NR, $1, $2, $3, $4, $5, $6, $7) } '

在原来的基础上,我们增加了 BEGIN { } 段落,为此我们就给内容提供了表头:

No. R1 R2 R3 R4 R5 R6 R7
1 root x 0 0 root /root /bin/bash
2 daemon x 1 1 daemon /usr/sbin /usr/sbin/nologin
3 bin x 2 2 bin /bin /usr/sbin/nologin
4 sys x 3 3 sys /dev /usr/sbin/nologin
5 sync x 4 65534 sync /bin /bin/sync
6 games x 5 60 games /usr/games /usr/sbin/nologin
7 man x 6 12 man /var/cache/man /usr/sbin/nologin
8 lp x 7 7 lp /var/spool/lpd /usr/sbin/nologin
9 mail x 8 8 mail /var/mail /usr/sbin/nologin
10 news x 9 9 news /var/spool/news /usr/sbin/nologin

NR 是行号的意思,为了让它好看一点,我们使用 printf 函数进行格式化打印:

cat 1.txt | awk -F : ' BEGIN { printf("%8s %5s %5s %5s %5s %5s %15s %20s\n", "No.", "R1", "R2", "R3", "R4", "R5", "R6", "R7") } { printf("%5s %8s %5s %5s %5s %5s %15s %20s\n", NR, $1, $2, $3, $4, $5, $6, $7) } '

打印的结果:

  No.    R1    R2    R3    R4    R5              R6                   R7
1 root x 0 0 root /root /bin/bash
2 daemon x 1 1 daemon /usr/sbin /usr/sbin/nologin
3 bin x 2 2 bin /bin /usr/sbin/nologin
4 sys x 3 3 sys /dev /usr/sbin/nologin
5 sync x 4 65534 sync /bin /bin/sync
6 games x 5 60 games /usr/games /usr/sbin/nologin
7 man x 6 12 man /var/cache/man /usr/sbin/nologin
8 lp x 7 7 lp /var/spool/lpd /usr/sbin/nologin
9 mail x 8 8 mail /var/mail /usr/sbin/nologin
10 news x 9 9 news /var/spool/news /usr/sbin/nologin

怎么样,这个表单看起来就很自然了吧!

表单筛选

想象一下,拿到手里的是一个 Excel 表格,我们要对表格进行分析,比如,我要隐藏 R2、R3、R4 这几栏,十分简单,打印的时候不写出来就行了,

cat 1.txt | awk -F : ' BEGIN { printf("%8s %5s %5s %15s %20s\n", "No.", "R1", "R5", "R6", "R7") } { printf("%5s %5s %5s %15s %20s\n", NR, $1, $5, $6, $7) } '

结果是:

  No.    R1    R5              R6                   R7
1 root root /root /bin/bash
2 daemon daemon /usr/sbin /usr/sbin/nologin
3 bin bin /bin /usr/sbin/nologin
4 sys sys /dev /usr/sbin/nologin
5 sync sync /bin /bin/sync
6 games games /usr/games /usr/sbin/nologin
7 man man /var/cache/man /usr/sbin/nologin
8 lp lp /var/spool/lpd /usr/sbin/nologin
9 mail mail /var/mail /usr/sbin/nologin
10 news news /var/spool/news /usr/sbin/nologin

再比如,我们想拿到第六列包含 var 的内容,正则过滤下就行了

cat 1.txt | awk -F : ' $6 ~ /var/ { printf("%5s %5s %5s %15s %20s\n", NR, $1, $5, $6, $7) } '

执行结果是:

 7   man   man  /var/cache/man    /usr/sbin/nologin
8 lp lp /var/spool/lpd /usr/sbin/nologin
9 mail mail /var/mail /usr/sbin/nologin
10 news news /var/spool/news /usr/sbin/nologin

再来个复杂点的,我们想拿到 R4 范围在 0~5,并且不包含 Root 的数据:

cat 1.txt | awk -F : ' $4 >= 0 && $4 <= 5 && $1 !~ /root/ { printf("%5s %5s %5s %15s %20s\n", NR, $1, $5, $6, $7) } '

筛选结果出来了:

2 daemon daemon       /usr/sbin    /usr/sbin/nologin
3 bin bin /bin /usr/sbin/nologin
4 sys sys /dev /usr/sbin/nologin

相关的筛选功能还有很多,这里就不一一枚举了,上述几个筛选其实也已经可以满足大部分的条件了。

增加表尾

我们想统计 R7 包含 nologin 信息的条目数量,展示在表尾,稍微复杂一点,这里会用到一点编程知识:

cat 1.txt | awk -F : ' BEGIN { count = 0 } { if ($7 ~ /nologin/){ count = count + 1; } printf("%5s %8s %5s %5s %5s %5s %15s %20s\n", NR, $1, $2, $3, $4, $5, $6, $7) } END { printf("total %s items with nologin", count)}'

看到的结果是:

    1     root     x     0     0  root           /root            /bin/bash
2 daemon x 1 1 daemon /usr/sbin /usr/sbin/nologin
3 bin x 2 2 bin /bin /usr/sbin/nologin
4 sys x 3 3 sys /dev /usr/sbin/nologin
5 sync x 4 65534 sync /bin /bin/sync
6 games x 5 60 games /usr/games /usr/sbin/nologin
7 man x 6 12 man /var/cache/man /usr/sbin/nologin
8 lp x 7 7 lp /var/spool/lpd /usr/sbin/nologin
9 mail x 8 8 mail /var/mail /usr/sbin/nologin
10 news x 9 9 news /var/spool/news /usr/sbin/nologin
total 8 items with nologin

结尾多了一行,我们来分析下这句命令,总共分为三段:

  • BEGIN { count = 0 },声明计数器
  • { if ($7 ~ /nologin/){ count = count + 1; } printf(...) },判断并计数
  • END { printf("total %s items with nologin", count)},打印结果

小结

以上,揭开了 awk 的面纱以后,你会发现它并没有那么复杂。当然,awk 也远不是上面我们看到的这么简单,它的使用可以变得非常复杂只不过在日常的运维过程中我们用不上而已。

把 awk 当做表单处理工具来理解,我相信你一定可以轻松记住它的所有命令,就算记不住,以后看到一长串命令也能够很快地理解它。更多资料可以看这个手册:The GNU Awk User’s Guide

本文同步自 小胡子哥的个人网站,原文地址: http://www.barretlee.com/blog/2019/10/29/awk/

换一种视角理解 awk 命令

Git 约定式提交规范实践

约定式提交规范 提供了一个轻量级的提交历史编写规则,它的内容十分简单:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

举个简单的例子:

feat(config): 允许 config 对象直接从其他 config 继承

BREAKING CHANGE: 在 config 对象中增加 `extends` 字段,用于从其他继承 config

close issue #23

在 git commit 时,如果你想进行多行 commit 编辑,可以通过 git commit -a 进入编辑界面;如果是单行,可以直接 git commit -a -m 'COMMIT MESSAGE' 完成提交。

更多的约定

约定式规范与 SemVer 的设计是相吻合的,

PATCH -> type(fix)
MINOR -> type(feat)
MAJOR -> BREAKING CHNAGE

大部分的提交中,我们都会使用 fix 和 feat 来描述本次修改的类型,当然也包含其他类型,如 chore/docs/reflector/improvement/perf/test/style,值得注意的是:

  • 一般不用写 body 部分的内容,除非存在 BREAKING CHANGE
  • description 的内容要相当简明扼要,用简单的语句把修改点直接说出来
  • 一般不建议将多次修改放在一次提交中,尤其是一次半(第二个修改只完成了一部分)的情况
  • scope 可以是一个文件的地址,如 /lib/utils;也可以是某个功能点 parser,不建议超过两个单词

一些技巧

合并多次提交

如果你上次修改的内容存在 bug 或未完成,本次提交的内容与上次几乎一样,建议使用 git rebase -i 进行提交的合并,如

git rebase -i HEAD~3 # 展示最近 3 次修改

输出如下:

pick 0291959 chore(blog): 清理无关项
pick 1ef8f31 chore(blog): 清理无关项
pick 36a91db fix(post): 格式化 post meta 数据格式,增加 --- 开始符

可以将第二行的 pick 修改为 squash,表示保留 commit 但将本次修改合并到上次,相关的操作可以看 这篇文章

关闭 ISSUE

在 github/gitlab 中,如果 commit message 中带有 Fix #23 诸如此类的信息,当 commit 被 push 到 repo 后,会自动关闭编号为 23 的 issue。

自动生成 CHANGELOG

在写日报或者周报,或者在项目发版时,我们可以很轻松地从提交日志中看到自己或者团队干了些什么事情:

alias git-changelog='git log --oneline --decorate';

当然也可以使用开源的工程自动生成结构化更强的 CHANGELOG 日志,如 auto-changelog,它提供了可自定义的 CHANGELOG 模板。

项目配置

约定如果没有工具来辅助和约束,大概率就成了一纸空文,毫无意义。在项目实战中,我们可以做如下配置让项目成员强制进行约定式提交。

1. 安装工具

推荐使用 @commitlint/cli 进行检测,安装方式:

npm install @commitlint/cli --save-dev

2. 配置约定

@commitlint 工具包中有一个规则比较强的检测规范:@commitlint/config-conventional,也安装到项目中:

npm install @commitlint/config-conventional --save-dev

安装完成后,需要显式地配置,在项目中增加 commitlint.config.js

module.exports = { 
extends: [
'@commitlint/config-conventional'
]
};

config-conventional 中允许类型有 build/chore/ci/docs/feat/fix/perf/refactor/revert/style/test

3. 提交时执行检查

推荐使用 husky 这个工具,它会帮助我们自动配置 commit hooks,只需在项目中添加 .huskyrc.json 文件:

{
"hooks": {
"pre-commit": "node ./node_modules/@commitlint/cli/lib/cli.js -E HUSKY_GIT_PARAMS"
}
}

当然也可以直接在 package.json 中配置 husky 字段,具体可以查看 文档

小结

整洁的提交记录并不仅仅意味着开发者自动生成 CHANGELOG,遵守约定可以给项目沉淀一个结构化的提交历史,再加上一些 emoji,生成出来的文档简直就是一篇生动的项目发展史,它有主意我们像工种传达变化的性质,同时对继续集成也会带来一定的好处,比如我们可以根据 type 触发不同的构建和部署流程。

本文同步自 小胡子哥的个人网站,原文地址: http://www.barretlee.com/blog/2019/10/28/commit-convention/

Git 约定式提交规范实践

分享下我个人博客的配置和发布

分享下我个人博客(https://www.barretlee.com)的配置和发布:

1. 域名

在 DNSPod 上配置的域名,默认解析到 coding pages,国外解析到 github pages,DNSPod 支持 D 监控,当域名不可用时会邮件警报。

coding 支持绑定多个域名,也支持给所有绑定的域名自动配置证书,github 只能绑定一个域名,这就会导致 www.barretlee.com 和 barretlee.com 只能有一个是 https,比较坑,貌似 Google 的 DNS 解析服务能够解决这个问题,国内的似乎都不行。

2. 开发

使用 hexo 构建,由于文章比较多,超过 300 篇,构建时长约 6min,很慢;hexo 支持多 git 仓库部署,我配置了 coding 和 github 两个。

使用了不少 hexo 的插件,但是很多都不满足需求,不满足需求的插件都重新改写了。平时使用改写的 hexo-admin 在本地编辑内容。

3. 部署

以前每次都是本地编辑文章,然后构建发布,发现错别字,又回到本地编辑、构建、发布,体验十分差,所以近两年都懒得写文章了,宁愿发长微博。

本周末折腾了一番,接入了 travis-ci,发现还挺好用,只是配置的时候需要注意点 git 仓库权限问题,可以参考 https://github.com/barretlee/blog/blob/master/.travis.yml 解决问题,后续写文章应该会直接走 github 网页新建文件。

4. 评论

从多说到畅言到 github issue,估计不会再继续折腾了,看了两个开源的 github issue 评论组件,gitment 和 gitalk ,这两个工具都有点像半成品,感觉还有很大的优化空间,由于 gitalk UI 稍微看得舒服点,将就着用了,效果如图四。历史评论就懒得迁移了。

周末把博客 UI 的部分细节做了调整,估计后续三五年都不会再折腾博客设计了。

本文同步自 小胡子哥的个人网站,原文地址: http://www.barretlee.com/blog/2019/10/27/blog-config/

分享下我个人博客的配置和发布

让 VSCode 在本地 Run 起来

Visual Studio Code 是微软推出的一款轻量级编辑器,与它一起在市场争锋的相似软件还有 Atom 和 Sublime Text,面世第二年的它只占据 7% 左右的市场,后来在短短三年时间雄踞了半壁江山,不可谓不哇塞。

vscode-snapshot

发育如此强势的软件,背后到底是如何设计的,未来一段时间,我将带着你一点一点拨开她的面纱,再撩开她的裙摆。

下载源码

Visual Studio Code 简称 VSCode,需要注意的是,平时我们使用的 VSCode 那是产品,而下面我们要介绍的是源码,产品是源码的构建结果;源码使用的 MIT License,而产品使用的是这个 MICROSOFT SOFTWARE LICENSE TERMS,如果你想把 VSCode 用于商用,建议从源码构建出新的产品,而不是直接使用人家官网上提供下载链接的 VSCode Product。

我们先把源码 down 下来:

git clone --depth 1 https://github.com/microsoft/vscode.git

由于 VSCode 项目过于活跃,提交量非常庞大,到目前为止,已经有 56,092 次提交了,建议在下载源码的时候加了一句 --depth 1,意思就是只现在最近一次 commit 的代码。

30s 后……71M,不慢。

安装依赖

在安装依赖之前,我们不妨稍微分析下 VSCode 的项目结构,

➜  vscode (master✔) tree -L 1
.
├── CONTRIBUTING.md
├── LICENSE.txt
├── README.md
├── ThirdPartyNotices.txt
├── azure-pipelines.yml
├── build/
├── cglicenses.json
├── cgmanifest.json
├── extensions/
├── gulpfile.js
├── package.json
├── product.json
├── remote/
├── resources/
├── scripts/
├── src/
├── test/
├── tsfmt.json
├── tslint.json
└── yarn.lock

未来我们需要重点关注的是 src/extensions/ 两个目录,前者放的是 VSCode 的核心源码,后者放的是 VSCode 的内置插件。

眼神再晃动一下,应该还会看到几个熟悉的关键词,build/,gulpfile.js,package.json,tslint.jsonyarn.lock,由此,我们基本可以断定,这个仓库是一个用 TypeScript 开发,用 yarn 管理依赖,用 gulp 进行打包的 Node.js 项目,事实上她也是一个 Electron 项目。

好了,目录就看到这里,接着开始安装漫长的依赖安装:

➜  vscode (master✔) yarn

执行 yarn 后,VSCode 会干三件事情:

  • preinstall 脚本中对 yarn 的版本做判断
    • 要求必须 >=1.10.1
    • 并且只允许使用 yarn 来安装依赖,npm 安装会弹个错误
  • 安装 package.json 中描述的各个依赖
    • 很多依赖都需要重新编译,而编译过程经常会失败
    • 失败了怎么办?看错误提示,如果流程没中断,就让它一直跑下去
    • 一直卡着,好像不跑了怎么办?ctrl-c 终止进程后重新执行 yarn
  • postinstall 挨个安装 build/remote/test/extensions 等目录中的依赖
    • extension 的安装比较特殊,安装的过程中又会执行 updateGrammar 脚本

整个安装过程十分的慢,可以考虑泡杯咖啡打开电视剧……

执行了 yarn 整个安装并没有结束,剩下几步 VSCode 会在你执行 gulp 相关脚本的时候做检测,倘若资源不存在便会安装,由于很多资源都在墙外,我们还是分解下动作,分步手动下载:

1. 把 Electron 安装下来:

➜  vscode (master✔) yarn electron

如果下载太慢,建议在命令行开下代理:

➜  vscode (master✔) proxychains4 yarn electron

这里附加一个小插曲,安装到半途时更换了下代理,应该是 gulp-vinyl-zip 这个包处理 buffer 异常,导致下次下载断点续传 buffer 位置对不上,然后每次执行 yarn electron 就直接退出进程,应该是个 bug;解决办法是,在这个包的 open() 方法里打个 log,把 path 打印出来,然后把打印出来的资源删掉就行了。

一小时后……

我已经不能忍了,电视剧都看了一集了,还是没下载完,其实 electron-v6.0.12-darwin-x64 这个文件只有 66.2M。

为了完成 electron 的安装,不得不附加第二个插曲,还是得翻源码解决问题:之前可以通过全局配置 ELECTRON_MIRROR 的地址来选择 Electron 下载源,而最新版 VSCode 的 Electron 是直接从 github 上下载的,从 gulp-atom-electron 这个包的源码里断点找到了 asset 和 assetPath,手动将 asset 下载下来后放到 assetPath,解决了问题。

2. 把内置的几个依赖插件安装下来:

➜  vscode (master✔) yarn download-builtin-extensions

历时差不多一个小时,终于把依赖下载完成了,这是我安装依赖花的时间最长的一次,家里的网络还是比不上厂里自带翻墙功能的网络,衰……

构建程序

由于启动一次构建花费的时间太长,1~5min 不等(看机器性能和人品),所以我建议你使用 yarn watch 来构建,它会完成一次构建并监听文件的变化,后续不用重新构建。

构建完成以后,就可以执行命令打开 VSCode 的界面了,不过在打开之前,我意外地在 package.json 的 scripts 中发现,VSCode 竟然已经有 Web 版本了!!!

VSCode Web

这比我之前的预期要早了很多,很早就听说他们内部团队在搞 Web 版本了,没想到这么快就要面世了。社区上有一个基于 VSCode 搞的 Web 版,叫 Code-Server,Star 量有好几万,估计官方的 Web 版出来以后,code-server 就要凉凉了。

哦,把 web 版本跑起来的方式是:

# gulp watch 完成后执行
➜ vscode (master✔) yarn web

会自动弹开一个地址:http://localhost:8080/,目前 Web 版的功能还不完备,比如插件部分就没有适配,应该还在研发状态,连 inside 版本都没进。这也算是我写这个教程的第一个意外惊喜吧,看来我得重新研究下 VSCode 的源码了。

执行如下脚本,可以打开 VSCode 的客户端:

➜  vscode (master✔) ./scripts/code.sh

然后你就可以看到这样的界面了:

VSCode Client

如果你是 windows 系统,执行的脚本应该是 ./script/code.bat

小结

好了,本文的扫盲就到这里。

本文主要通过傻瓜式地教学,给大家演示了下,如何将源码变成我们熟悉的 VSCode 客户端,相信同学们在动手的过程中还会遇到各种依赖安装问题,不要灰心,实在不行就 rm -rf node_modules,然后重试。

下回再给大家讲述如何开发和调试 VSCode 的源码。

本文同步自 小胡子哥的个人网站,原文地址: http://www.barretlee.com/blog/2019/10/23/vscode-study-01-start/

让 VSCode 在本地 Run 起来