|
|
|
|
|
|
c****x 发帖数: 6601 | 1 http://www.yinwang.org/blog-cn/2017/05/17/practical-idealism
曾经有人看了我的文章,以为我是一个“理想主义者”,来找我聊天。他说:“你知道
吗,我跟你一样喜欢干净优雅的代码。上次我在某公司工作,看到他们的代码乱得不成
样子,二话没说就给他们推翻重写了,结果有几个小地方跟原来的代码不大一样,后来
系统因此当掉了。老板对我说,明天你不用再来上班了!你说我是不是好心没好报啊?”
虽然我同情他丢了工作,然而我并不认同这种不经同意就把别人的代码推翻重写的作法
。我对他说:“哈哈,你不可以这样改别人的代码的!如果我是你老板,虽然可能不会
开掉你,却也会给你一个严重警告的。”
从我们的对话你也许已经发现了,我并不是一个通常人所谓的“理想主义者”。虽然我
有很多新颖而美好的想法,然而它们每一个都深深地植根在现实中。我反对一切不以现
实为基础的“理想”,我往往在很早的时候就发现和避免那些最终会失败的“理想主义
作法”。
如何对待别人的代码
那么我是如何对待别人的“垃圾代码”的呢?你也许会很惊讶我的做法:我尽量不动它
们!
虽然我喜欢干净优雅的代码,然而对于别人写的代码,就算它再丑陋再乱,我也不会乱
动它。我就像一个外科专家,多次的对已有的代码进行“换心手术”。这种手术成功的
要诀,是制造尽量小的“切口”,刚好可以换掉心脏,改善它的功能,而不动其他部位
。就算那些地方血管乱绕,堆满各种垃圾,也不要去动它们。
这是为什呢?因为代码首要的目标,应该是“解决问题”(包括“没有 bug”),其次
的目标才是“简单优雅”。如果不能解决问题,代码再优雅又有什么用呢。对于已经可
以解决问题的代码,就算它们再乱再复杂,我对它们也是高度尊重的,绝对不敢像这个
朋友一样,不假思索就删掉重写。这就像你给别人做换心手术,看到大腿上有些血管是
乱的,又把大腿切开倒腾,你的病人不死才怪呢。
我自己写代码的时候,“解决问题”和“简单优雅”往往是紧密结合,交织在一起的。
如果我写不出简单优雅的代码,我就不能又快又正确的解决问题。所以我的代码往往从
一开头就是简单优雅,模块化的。我从很小的函数开始写起,每个小的函数只解决很小
的问题,最终我把它们组合在一起,解决掉整个问题。
对于别人的代码,情况就很不一样了。很多人写的代码很乱,很复杂,不易理解,看得
我头痛,但由于他们在上面花了很多的时间,而且这些代码经过了很长时间的使用,大
量现实情况的考验,所以它们已经算是解决了问题。对于这样的代码,我的经验是这样
:如果把它删掉完全重写,是很难不犯原作者已经犯过的错误的。就算你自认为水平世
界一流,写的代码极其简单和优雅,也不能避免犯错。
这不是一个智力的问题,而是一个智慧的问题。喜欢删掉别人代码重写的人,也许有很
高的智力,却缺乏智慧。代码是用来解决现实问题的,而现实有许许多多的细节,代码
需要覆盖现实世界各种不完美的地方。这些不完美也许来自库代码,也许来自操作系统
,也许来自网络协议,也许来自用户习惯,也许来自自然界。我们必须承认,很多这些
东西我们是没有能力,没有时间,也没有必要去改变的。
别人已经写好,用了几年的代码,很有可能已经遇到各种现实问题,各种边角情况,原
来的作者虽然不像你一样思路清晰,却也为此付出了时间和精力。这些复杂混乱的代码
逻辑里面,已经针对现实世界的不完美,做出了基本可行的解决方案。一个有智慧的人
,必须能够利用这些前人留下来的混乱代码,因为它包含了时间积累下来的财富。
那么我一般是如何利用别人遗留下来的混乱代码的呢?我的策略包含好几个要点。
首先,我尽量保持别人的代码原封不动。因为别人的代码解决的问题,很可能不是我当
前需要解决的问题。因为看不顺眼而去改别人的代码,不但分散自己的精力,而且有可
能制造新的 bug,导致新老代码中同时多处出现 bug,难以追踪和修复。为了保持别人
的代码原封不动,却又让自己写的新代码简单优雅,我必须深入的理解原有代码的接口
(interface),以及它原有的各种特征,我力求保持它们原封不动。这就像外科大夫
做换心手术,他必须保证已有的血管都连接到正确的地方。
我喜欢把自己的代码做成一个可替换的,模块化的元件,可以随时在系统里插入或者移
除。一旦发现出了问题,我可以随时把我的代码从逻辑里面去掉,重新测试,这样我就
可以知道问题出在原来的代码,还是出在我的新代码里面。另外,我还会注意避免对已
有函数进行换名,这样我可以把自己的修改局限在一个或者少数几个文件里面,避免
Git 的历史里面出现不必要的,让人分心的修改。就算要换名也应该单独作为 commit
,而不应该跟逻辑的修改混在一起。
如果经过多次试验,我发现别人的代码的确需要修改,不然我没法继续写新的代码,那
么我只好对它进行修改。由于已有的代码复杂而混乱,我一般会极其小心的对待它。我
绝对不会删掉大片的代码,从头开始写,那几乎注定是要失败的。通常,我会先“隔离
”出很小的一部分代码,对它们进行重写,随之立即进行大量的测试和试验,找原来代
码的作者来进行 review,如此反复……
那么这块改掉的代码需要小到什么程度呢?我也许就只改写一个 for 循环,把几行代
码提出去做成帮助函数,简化一个表达式,把一个类成员变成一个局部变量,改几个局
部变量的名字之类的。每一个这样的小改动都有可能出错,所以在此之后必须进行严格
的验证,确保修改后的代码和原来的代码语义相同。这样反反复复很多次之后,你才能
正确的替换掉原来的代码。
从我对待别人代码的方式,你也许已经发现了,我不是一个通常意义上的理想主义者。
我不会为了自己“简单优雅”的理想,而完全的推翻别人的代码重写,因为我知道现实
世界的复杂性,我知道这样做注定是要失败的。我对待别人代码的态度,是深深地植根
于现实的。通过极其严密的措施,我确保改进后的代码跟原来的代码语义完全相同,尽
最大可能避免重复前人的错误,避免制造新的 bug。
由于我的理想植根于现实,我把自己称为“现实理想主义者”(practical idealist)
,而不是“理想主义者”(idealist)。我亲眼见过纯粹的理想主义者,不经同意就大
幅度的删除重写大量代码,给团队的开发带来灾难性的后果。
通过这个例子,你可能已经发现为什么“现实理想主义”是优于“理想主义”的。下面
我来讲一下,为什么“现实理想主义”也优于完全的“现实主义”。
超越现实主义
既然我不是一个完全的理想主义者,那么是不是说,我就是一个完全的“现实主义者”
呢?在我的职业生涯中,我已经多次证明了,我不是一个完全的现实主义者,我能做到
现实主义者做不到的事情。我心中的“理想”成分,让我能够看到现实主义者看不到的
可能性,而我的“现实”成分,又帮助我为这种可能性找到切实可行的路线。理想和现
实的结合,指引我达到现实主义者认为是不可能的目标。
说到这一点,第一个跳进我脑海里的例子,是我当年在 Google 完成的项目。Google
需要一个可以像 IDE 一样索引 Python 代码的工具,可以支持准确的“跳转到定义”
功能。作为现实主义者的团队领导(Steve)对我说,你去拿一个开源的 Python 工具
,比如 PyDev,修改之后插入到我们的构架里就可以了。
当我调研了十多个开源 Python 工具和 IDE 之后,发现它们都不能准确地实现“跳转
到定义”,大部分的实现方式都是字符串搜索而已,完全不着边际。这时候,我的理想
成分告诉我,这应该是有可能的,只不过现有的工具都不知道怎么实现它而已。为了实
现 Python 这样的动态语言的精确索引,就必须实现类型推导,而这是我很在行的事情
。于是我决定做一个新的 Python 类型推导器,这样就可以利用它实现精确的跳转功能。
我把这个想法告诉了 Steve 和其它团队成员,结果作为现实主义者的他们,非常的担
心这个项目无法在三个月的实习期内完成。Steve 说:“你知道吗,光是写一个
Python 的 parser 就够写三个月了。我很担心你不能完成任务!” 这时候,我的现实
成分开始起作用。我告诉他:“你知道吗,我并不觉得写 Python 的 parser 是一件很
难的事情,但我也不觉得它是一件很有意义的事情,所以我会拿一个开源的 parser 来
,把它生成的语法树转换成我自己简化的数据结构,然后在上面完成我们需要的功能。”
结果,我拿了 Jython 里面的 Python parser,写了一个转换器,把它输出的语法树转
换成我自己设计的更加简单的数据结构,然后在上面实现了 PySonar。整个对付
parser 的过程只花了我两天时间,剩下的时间我都在研究和实现最关键,最有趣的部
分。
总是就是说,我拿了别人已经做好的,自己不想做的东西来,然后加上自己的核心思想
,达到了最终的目的。最后,我不但在三个月的时间里完成了 PySonar,而且把它完完
全全的集成到了 Grok 项目里面。今天 PySonar 仍然在为 Google 的 Python 程序员
提供高质量的索引服务,它生成的数据被提供给 CodeSearch 等一系列内部代码搜索服
务。
个人兴趣与企业兴趣
所以你已经看到了,我是理想与现实的合体,这种组合超越了理想,也超越了现实。“
理想”为我提供了很多人看不见的可能性,而“现实”为这种可能性指出切实可行的路
线。
最近有一位成功的企业家给我来信,很关心我的发展。说很理解我的理想主义,说我只
喜欢做自己感兴趣的事情,这往往跟企业的兴趣是一对矛盾。虽然他是一片好心,但我
觉得这是对我很常见的一种误解。在工作过的每一个公司,我都把企业和团队的兴趣利
益放在首要的位置,但同时又想法把自己的一部分兴趣结合进去,试图达到两全其美的
效果。可以说,我对之前公司利益的关心和贡献程度,很多时候都高于 VP 级别的领导
。为什么这样说呢?让我来给你一个例子。
我曾经在职的某公司,邀请了某位“JavaScript 大牛”来做 VP。可是久而久之,我发
现这个 VP 其实并不懂很多事情,尽在瞎指挥。为什么他瞎指挥呢?因为他并没有把公
司的利益放在心上。他从 JavaScript 的社区招来很多喜欢吹牛扯淡,光说不做的人。
这些人成天就喜欢发布各种开源代码,对公司产品一点用处都没有的代码。表面上是为
了撑起公司的名声,然而明眼人都看得出来,那都是为了他们自己的利益。拿着公司的
高工资,却不是给公司写代码,反而花着公司的钱去世界各地开会,会议的内容也跟公
司的利益毫不相干。
后来,这个 VP 提出了一个“新想法”。他说,我们团队的代码应该实现“模块化管理
”。如何实现模块化管理呢?我们把代码按照目录结构切分开,分成 30 个“模块”。
把每个模块做成一个 Git 代码库,代码库之间通过 Maven 里面的版本号依赖关系进行
连接。每个人负责一两个模块,使用“语义版本号”(semver)标注模块的版本。如果
修改了代码,就更新对应的版本号,这样依赖于这个模块的代码库就必须做出相应的修
改,才能连接到新的模块代码,不然它们就可以继续使用旧的模块代码……
这个新想法没有经过团队的集体讨论研究,就被 VP 的一个亲信动手实现了。一夜醒来
,我们发现代码库被他切分成了 30 多个,制定了一系列规章条款,要我们遵守。接下
来的事情,我发现自己没法工作了。一天当中有超过半天的时间,我发现自己在为那些
semver 伤脑经。你刚刚更新了所有的代码,才工作了几个小时,正要提交自己的改动
的时候,却发现另外几个模块的版本号更新了!你得手动去看是哪些代码库发生了改变
,更新自己 maven 文件里的依赖关系,然后才能进行测试,提交自己的代码。有时候
当你提交之前,忽然又有其它的模块版本号发生了改变,所以你前功尽弃,又得去查到
底是谁改了他的模块版本号。有很多次,有人没有把版本号完全搞对就提交代码,结果
导致项目 build 失败。
后来我发现,这种所谓的“模块化”,根本就不是真正的模块化,而 semvar 版本号,
也并不比 Git 的 hash 更好。模块不应该是按目录结构划分的,而应该是按代码的逻
辑结构,而且模块之间不应该有“循环依赖关系”,否则这些模块就不应该被分成模块
,而应该合并在一起。另外一个精髓的思想:每一个 Git commit 的 hash,其实本身
就是一个“全宇宙唯一”的版本号,它包含了代码所处的,独一无二状态。所以 Git
的 commit 的时间顺序,其实自然而然的解决了这种模块间版本依赖的问题。所以把代
码拆分成 30 多个 Git 代码库,使用 semvar 连接它们,完全是多此一举,而且造成
了开发效率的极其低下。
观察到这个问题之后,我向团队群发了邮件,告诉他们我觉得这样的做法已经造成了我
工作效率严重打折,并且指出了问题的要害。一个来自法国的资深工程师立即支持了我
的看法,也开始抱怨,说自己花了超过 50% 的时间来折腾这些版本号。然而 VP 听了
这些意见,却坚持的认为自己的“创新”是有价值的,对我们说:“困难是暂时的,适
应是必须的!” 为了这个问题,我们在 email 里面吵了两个星期之久。任凭我们据理
力争,拿出具体的证据证明这种做法不可行,严重的伤害了团队的开发效率,VP 凭着
自己在 JavaScript 界的资历和地位,毫不退缩。
最后无赖之下,我决定采取实际的行动。我写了一个 Python 脚本,它调用 Git 的一
些罕见命令,可以自动把多个 Git 代码库合并成一个,并且保留所有的历史 commit
信息。有了这个脚本之后,我可以随时制造出一个合并的代码库。我把这个脚本分享给
了团队,告诉他们我随时可以把代码库合并在一起,而且给了他们一个合并后的代码库
,作为试验用。我告诉他们,可以试用这个代码库,看它是否解决了 30 个代码库带来
的问题。最后法国同事和其它几个人采用了我的代码库,发现不再有之前的头痛问题。
我们用理论和切实的证据证明了所谓的“模块化代码管理”的不可行。通过对其它公司
代码的观察,我们发现 Google 的 Chrome 项目有三千多万行代码,却全都存放在同一
个 Git 代码库里。这说明一个 Git 代码库足以支持 Chrome 那么大的项目。我们的团
队总共才 20 多人,代码不超过十万行,却被强行切分成 30 多个代码库,这是非常荒
唐滑稽的。
最后在工程师们的一致同意下,再加上团队 director 委婉的支持,我用脚本将 30 个
代码库合并在了一起,结束了大家的痛苦…… 在此之后,VP 的亲信们还不死心,在合
并后的代码库里又做了一些手脚,故意加大工作的复杂性,这些我就不细说了。
总之你看到了,这位 VP 的瞎指挥,导致团队浪费很多的时间和精力。我发现很多所谓
管理人物,他们到一个新的公司出任要职,其实并没把公司的利益放在心上。他们不是
为了公司的发展和成功做出决定,而是为了自己的“仕途”。这些管理者明白,公司就
像一艘船,自己假装是在为公司服务,而其实是在利用公司的资源达成自己的目标。由
于自己挥霍公司的资源,而不作出实质的贡献,甚至瞎指挥帮倒忙,这艘船在将来很可
能会沉没。但作为管理者,自己总是可以在沉船之前跳到另外一艘船上,靠着自己的关
系网,不断找到高薪的职位……
你应该已经看明白了,到底是谁在为公司的利益和兴趣着想。我是一个只顾自己兴趣,
不管公司利益的人吗?我想有理智的人都会做出正确的判断了。像这样的例子我还有很
多。为了团队,为了公司能够达成自己的目标,我付出了很多很多。说我是一个只顾自
己兴趣的人,真是对我天大的冤枉 :)
从这样一个例子,你也可以看到我作为一个“现实理想主义者”(practical idialist
)的特征。这个 VP 可算是“理想主义”了,他提出了新颖的,其它公司都没想到的“
模块化管理”方式,结果却给大家带来了灾难。我从现实的角度,分析得知这种做法的
荒谬,与他据理力争,维护公司和团队的利益。再加上团结大多数有职业素养的工程师
,最终我们合力在理论和现实上战胜了 VP 的瞎指挥,逆转了他给团队和公司带来的伤
害。
这样的现实理想主义者,不管是作为员工,作为团队的领导,还是作为公司的统帅,都
会身体力行,给他们带来帮助,避免不必要的浪费。 | j**********r 发帖数: 3798 | 2 不明白为啥分成多个git repository不行,别的模块版本修改了,自己的是不是跟着更
新完全在于自己的设定,从来没有别人更新自己就必须更新一说。所有模块必须向后兼
容是真的,这是microservice的原则之一。 | c*******n 发帖数: 442 | 3 这是真要转型网红么……
建议王垠直播写代码,就是他那些特别精妙的代码,相信会给大家很多启发的……
?”
【在 c****x 的大作中提到】![](/moin_static193/solenoid/img/up.png) : http://www.yinwang.org/blog-cn/2017/05/17/practical-idealism : 曾经有人看了我的文章,以为我是一个“理想主义者”,来找我聊天。他说:“你知道 : 吗,我跟你一样喜欢干净优雅的代码。上次我在某公司工作,看到他们的代码乱得不成 : 样子,二话没说就给他们推翻重写了,结果有几个小地方跟原来的代码不大一样,后来 : 系统因此当掉了。老板对我说,明天你不用再来上班了!你说我是不是好心没好报啊?” : 虽然我同情他丢了工作,然而我并不认同这种不经同意就把别人的代码推翻重写的作法 : 。我对他说:“哈哈,你不可以这样改别人的代码的!如果我是你老板,虽然可能不会 : 开掉你,却也会给你一个严重警告的。” : 从我们的对话你也许已经发现了,我并不是一个通常人所谓的“理想主义者”。虽然我 : 有很多新颖而美好的想法,然而它们每一个都深深地植根在现实中。我反对一切不以现
| e***a 发帖数: 1661 | 4 what famous software products did 王垠 make? |
|
|
|
|
|