第1页
4/21/2015
gofmt 的文化演变
gofmt 的文化演变
The Cultural Evolution of gofmt
Robert Griesemer Google, Inc.
http://127.0.0.1:3999/gofmt-cn.slide#1
1/34
第2页
4/21/2015
gofmt
gofmt 的文化演变
Go源代码格式化工具
定义了“标准“格式
golang.org代码库中所有提交的Go代码都必须通过gofmt格式化过
除了gofmt之外,相同功能可以通过go/format库获得
不需要设置!
http://127.0.0.1:3999/gofmt-cn.slide#1
2/34
第3页
4/21/2015
初衷
gofmt 的文化演变
代码审查是软件工程的最佳实践
代码审查是基于代码规范和正规格式的
太多时间浪费在审查格式上而不是代码本身了
但是这工作对机器来说是最好不过了的
第一个决定就是要写一个好的格式美化器
http://127.0.0.1:3999/gofmt-cn.slide#1
3/34
第4页
4/21/2015
历史
gofmt 的文化演变
格式美化器和代码美化工具在计算机发展的早期就已出现
对于产生可读的Lisp代码很重要的:
GRINDEF (BillGosper,1967)
其他:
第一个计算行长度
SOAP (R.Scowenetal,1969)
简化了晦涩的算法程序
NEATER2 (KenConrow,R.Smith,1970) PL/1格式器,作为(早期的)纠错工具
cb (UnixVersion7,1979)
C程序美化器
indent (4.2BSD,1983)
缩进和格化化C代码
等等
最近的:
ClangFormat
Uncrustify 等等
C/C++/Objective-C 格式器 C, C++, C#, ObjectiveC, D, Java, Pawn and VALA的美化器
http://127.0.0.1:3999/gofmt-cn.slide#1
4/34
第5页
4/21/2015
事实上
gofmt 的文化演变
在2007年,没人喜欢代码格式器
例外:IDE强制的格式化
但是:很多程序员不用IDE...
问题:如果是格式化太具有毁坏性,那么就没有人会用
被忽视的观点:“刚刚好“的,统一化的格式是好过于各种不同的格式的。
规范的价值在于:整齐划一,而不是完美
http://127.0.0.1:3999/gofmt-cn.slide#1
5/34
第6页
4/21/2015
好的格式美化器的问题
gofmt 的文化演变
当越多人思考他们自己的格式风格的时候,他们就变得更加固执于此了
错误的结论:自动格式器必须要有很多选项!
但是有很多选项的格式器其实违背他们的目的
此外,支持很多选项是难的
尊重用户的想法是最关键的
处理注释是很难的
语言本身也会增加很多额外的复杂度(比如,C的宏)
http://127.0.0.1:3999/gofmt-cn.slide#1
6/34
第7页
4/21/2015
gofmt 的文化演变
格式化Go
http://127.0.0.1:3999/gofmt-cn.slide#1
7/34
第8页
4/21/2015
尽量保证其简单
gofmt 的文化演变
小的语言能让事情变得简单
不要为行长度烦恼
相反的,尊重用户:考虑原有代码中的断行
不要支持任何选项
使其使用傻瓜化
一个格化标准搞定所有!
http://127.0.0.1:3999/gofmt-cn.slide#1
8/34
第9页
4/21/2015
gofmt的基本结构
源代码的处理 基本的格式化 附加:注释的处理 完善:代码和注释的对齐 但是,没有牛X的通用布局算法 相反的:基于节点的精细优化
gofmt 的文化演变
http://127.0.0.1:3999/gofmt-cn.slide#1
9/34
第10页
4/21/2015
处理源代码
gofmt 的文化演变
使用`go/scanner`, `go/parser`及其相关的库 给每一个go文件生成一个抽象语法树 每一个语法结构都有相应的AST节点
// Syntax of an if statement. IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .
// An IfStmt node represents an if statement.
IfStmt struct {
If token.Pos//positionof"if"keyword
Init Stmt
// initialization statement; or nil
Cond Expr
// condition
Body *BlockStmt
Else Stmt // else branch; or nil
}
AST节点有(选择性的)位置信息。
http://127.0.0.1:3999/gofmt-cn.slide#1
10/34
第11页
4/21/2015
基本的格式化
gofmt 的文化演变
遍历AST然后打印每个节点
case *ast.IfStmt: p.print(token.IF) p.controlClause(false, s.Init, s.Cond, nil) p.block(s.Body, 1) if s.Else != nil { p.print(blank, token.ELSE, blank) switch s.Else.(type) { case *ast.BlockStmt, *ast.IfStmt: p.stmt(s.Else, nextIsRBrace) default: p.print(token.LBRACE, indent, formfeed) p.stmt(s.Else, true) p.print(unindent, formfeed, token.RBRACE) } }
打印器(`p.print`)接收包括位置和空格符等的一系列记号
http://127.0.0.1:3999/gofmt-cn.slide#1
11/34
第12页
4/21/2015
细致的调节
基于优先级安排操作数之间的空格.
提高表达式的可读性.
x=a+b x = a + b*c if a+b <= d { if a+b*c <= d {
使用位置信息决定何时换行.
其他一些策略.
gofmt 的文化演变
http://127.0.0.1:3999/gofmt-cn.slide#1
12/34
第13页
4/21/2015
注释的处理
gofmt 的文化演变
注释可以出现在程序的任何两个词汇之间. 通常情况下,不能很明显的知道注释属于哪个 AST 节点. 注释经常是成组出现:
// A CommentGroup represents a sequence of comments // with no other tokens and no empty lines between. // type CommentGroup struct {
List []*Comment // len(List) > 0 }
成组的注释被处理为一个大的注释.
http://127.0.0.1:3999/gofmt-cn.slide#1
13/34
第14页
4/21/2015
注释在 AST 上的表达
gofmt 的文化演变
注释组的连续列表被连接到 AST 的文件节点.
另外,一些被标示为 doc strings 的注释被连接到声明节点.
http://127.0.0.1:3999/gofmt-cn.slide#1
14/34
第15页
4/21/2015
格式化注释
gofmt 的文化演变
基本的办法:基于位置信息合并词汇流和注释流.
http://127.0.0.1:3999/gofmt-cn.slide#1
15/34
第16页
4/21/2015
魔鬼就在细节中
gofmt 的文化演变
在源代码中估计当前的位置.
比较当前的位置和注释的位置去决定下一个是什么.
词汇也包含了空格词汇 - 注释必须被合理的分布!
维持一个未被打印的空格缓冲区,在下一个词汇之前输出,然后分布注释.
多种策略得以正确地处理空格.
很多次的尝试和错误.
http://127.0.0.1:3999/gofmt-cn.slide#1
16/34
第17页
4/21/2015
格式化单独的注释
gofmt 的文化演变
区分代码行和注释. 努力对多行注释进行合理的缩进.
func f() {
/*
* foo
* bar
==>
* bal
*/
if ...
}
func() { /* * foo * bar * bal */ if ...
}
但并不总是能够处理正确. 想达到两个效果:注释能够缩进,注释的内容不进行处理。还没有好的解决办法.
http://127.0.0.1:3999/gofmt-cn.slide#1
17/34
第18页
4/21/2015
对齐
gofmt 的文化演变
仔细选择的对齐可以让代码更容易阅读.
var ( )
x, y int = 2, 3 // foo z float32 // bar s string // bal
var ( ==>
)
x,yint =2,3//foo
z float32
// bar
s string
// bal
很难进行手工维护 (制表符并不能够做到). 但是却非常适合使用格式化工具.
http://127.0.0.1:3999/gofmt-cn.slide#1
18/34
第19页
4/21/2015
灵活的制表符宽度
gofmt 的文化演变
通常的制表符把当前的写位置移动到下一个固定的位置.
基本的办法:让制表符宽度更加灵活.
制表符可以标示一个文本单元的结束位置.
一个列块是一个连续的相邻的单元.
一个列块的宽度可以到达多个单元里最宽文本的宽度.
被 Nick Gravgaard 提出于2006
nickgravgaard.com/elastic-tabstops/ (http://nickgravgaard.com/elastic-tabstops/)
实现在 text/tabwriter包中.
http://127.0.0.1:3999/gofmt-cn.slide#1
19/34
第20页
4/21/2015
灵活制表符宽度的展示
gofmt 的文化演变
http://127.0.0.1:3999/gofmt-cn.slide#1
20/34
第21页
4/21/2015
综合在一起 (1)
gofmt 的文化演变
分析器生成 AST.
打印工具递归地打印AST,使用制表符去灵活的标示制表符的位置.
产生的词汇,位置和空格流会和注释流进行合并.
词汇会扩展为字符串,所有的文本流将会被制表符写入器处理.
制表符写入器会将制表符替换为合适数量的空格.
对于固定宽度的字体,处理的很好.
比例大小的字体也可以被编辑器支持,如果这个编辑器可以支持灵活的制表符宽度.
http://127.0.0.1:3999/gofmt-cn.slide#1
21/34
第22页
4/21/2015
综合在一起 (2)
gofmt 的文化演变
http://127.0.0.1:3999/gofmt-cn.slide#1
22/34
第23页
4/21/2015
从宏观上看
gofmt 的文化演变
http://127.0.0.1:3999/gofmt-cn.slide#1
23/34
第24页
4/21/2015
gofmt 的文化演变
gofmt 的应用
http://127.0.0.1:3999/gofmt-cn.slide#1
24/34
第25页
4/21/2015
gofmt 作为源代码变换工具
gofmt 的文化演变
改写 Go 的代码 (Russ Cox), gofmt -r
gofmt -w -r 'a[i:len(x)] -> a[i:]' *.go
简化 Go 的代码, gofmt -s 更新 API (Russ Cox), go fix 改变语言 (去掉分号,其它)
goimport (Brad Fitzpatrick)
http://127.0.0.1:3999/gofmt-cn.slide#1
25/34
第26页
4/21/2015
大家的反应
gofmt 的文化演变
Go 项目要求所有提交的源代码都用 gofmt 的格式。
一开始,大家都抱怨:`gofmt` 不知道怎样格式成我的风格!
慢慢地,大家不作声了:Go 项目组一定要用 gofmt!
最后,大家看清了:gofmt 不是任何人的风格,但所有人都喜欢 gofmt 的风格。
现在,大家都赞扬: gofmt是大家喜欢 Go 的一个原因。
现在,格式已经不是一个问题。
http://127.0.0.1:3999/gofmt-cn.slide#1
26/34
第27页
4/21/2015
其它语言也在向我们学习
gofmt 的文化演变
Google 的 BUILD 文件现在也有格式器 (Russ Cox).
Java 格式器
Clang 格式器
Dartfmt
www.dartlang.org/tools/dartfmt/ (https://www.dartlang.org/tools/dartfmt/)
等等
现在,任何语言都被要求带有自动的源代码格式器。
http://127.0.0.1:3999/gofmt-cn.slide#1
27/34
第28页
4/21/2015
gofmt 的文化演变
总结
http://127.0.0.1:3999/gofmt-cn.slide#1
28/34
第29页
4/21/2015
编程文化的演变
gofmt 的文化演变
gomft是 Go 语言的一个重要的卖点
大家渐渐达成共识:一致的“足够好“的格式很有好处
这种在 AST-级别上的源代码操作带动了一系列的新的工具。
其它语言也在向我们学习:编程的文化在慢慢演变。
http://127.0.0.1:3999/gofmt-cn.slide#1
29/34
第30页
4/21/2015
至今的收获:应用程序
gofmt 的文化演变
一开始,基本的源代码格式化是一个很好的目标。
但是,真正的用处在于源代码的变换工具。
不要给大家有选择格式的机会。
越简单越好。
我们想要:
Go 分析器:源代码 => 语法树
尽可能让语法树的操作变得容易。
Go 打印器:语法树 => 源代码
http://127.0.0.1:3999/gofmt-cn.slide#1
30/34
第31页
4/21/2015
至今的收获:实现过程
gofmt 的文化演变
最初的版本有很多的尝试和失败。
最大的错误:注释没有连到 AST-节点上.
=> 现在的设计使得操作 AST 和保持注释在正确的地方十分困难。
很混乱:ast.CommentMap
我们想要:
容易操作语法树,连带注释。
http://127.0.0.1:3999/gofmt-cn.slide#1
31/34
第32页
4/21/2015
将来的计划
gofmt 的文化演变
正在设计新的语法树(仍在试验阶段)
语法树操作起来更加简单和容易(例如:声明结点)
更快和更容易地使用分析器和打印器。
让工具用起来可靠并且快。其它一概不理。
http://127.0.0.1:3999/gofmt-cn.slide#1
32/34
第33页
4/21/2015
Thank you
Robert Griesemer Google, Inc. gri@golang.org (mailto:gri@golang.org)
gofmt 的文化演变
http://127.0.0.1:3999/gofmt-cn.slide#1
33/34
第34页
4/21/2015
gofmt 的文化演变
http://127.0.0.1:3999/gofmt-cn.slide#1
34/34