mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-09-17 08:46:40 +08:00
del
This commit is contained in:
parent
8bafaef34d
commit
bf99793fd0
BIN
极客时间专栏/geek.zip
Normal file
BIN
极客时间专栏/geek.zip
Normal file
Binary file not shown.
BIN
极客时间专栏/geek/.DS_Store
vendored
Normal file
BIN
极客时间专栏/geek/.DS_Store
vendored
Normal file
Binary file not shown.
126
极客时间专栏/geek/10x程序员工作法/以终为始/02 | 以终为始:如何让你的努力不白费?.md
Normal file
126
极客时间专栏/geek/10x程序员工作法/以终为始/02 | 以终为始:如何让你的努力不白费?.md
Normal file
@ -0,0 +1,126 @@
|
||||
<audio id="audio" title="02 | 以终为始:如何让你的努力不白费?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d4/da/d4d107916b8de739ec9f7e903b90e4da.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
今天内容的开始,我希望你可以先来思考一个问题:**如果让你设计一个登录功能,你会怎么做?**
|
||||
|
||||
我曾在公司内部做过这样一个练习,我扮演客户,让大家帮我设计一个登录功能。同事们一听就高兴了,登录不就是用户名加密码嘛,我熟啊,我还可以设计出验证码、找回密码、第三方登录等等功能。
|
||||
|
||||
更有个别动作快的同事,甚至已经开始设计数据库表,考虑用Redis做缓存了。整个过程下来,大家彼此讨论得热火朝天,唯一没人理会的就是我这个“客户”。
|
||||
|
||||
讨论结束,扮演客户的我告诉大家,作为一个“土豪”,我打算做一个打车软件,用户可以通过手机号接收验证码的方式进行登录。你可以想见,同事们一副“被套路了”的表情。是的,他们设计那套用户名密码登录完全是文不对题。
|
||||
|
||||
虽然这是一个简单的练习,但反映的却是我们日常面对的真实工作场景:许多人都是刚刚听到别人要求做的一个功能,就开始脑补接下来的一切。导致的结果,就是付出的努力毫无意义。
|
||||
|
||||
那么问题出在哪呢?因为我们欠缺了“以终为始”的思维习惯。
|
||||
|
||||
## 一种反直觉的思维方式
|
||||
|
||||
以终为始,就是在做事之前,先想想结果是什么样子的。
|
||||
|
||||
说起来很简单,但做到并不容易。因为我们习以为常的思维模式是线性而顺序的,第一步做完,做第二步;第二步做完,做第三步。
|
||||
|
||||
这也情有可原。我们人类都是从远古时代演化而来,在那个食不果腹的时代里,倒着思考的用途并不大,人们甚至不确定自己能否见到明天的太阳。几十万年的进化留给我们很多短视的行为和思考习惯,因为这样的做法最为节省能量,把目光放长远是需要额外消耗能量的。
|
||||
|
||||
“以终为始”是一种反直觉的思维方式,是大多数人不具备的。所以,日常生活中,我们看到很多有趣的现象。
|
||||
|
||||
比如,大学毕业时,有很多人想考研,如果你问他为什么要考研,得到的理由通常是为了找个好工作。但考研真的能帮他找个好工作吗?不一定,因为找工作和考研根本就不是同一棵技能树。
|
||||
|
||||
如果真的是想找个好工作,那你就应该了解工作的要求是什么,怎样才能掌握工作要求的技能。
|
||||
|
||||
从后面这个角度出发,你会发现考研只是通往工作诸多道路中的一条,其他的路径也是可以到达的。比如,你应该找个实习的地方锻炼一下职业技能。这就是“以终为始”思考问题的方式。
|
||||
|
||||
回到前面“设计登录功能”的例子,对比“以终为始”的思维,你也许会替我的同事抱不平,他们或许也有“以终为始”的思路,只不过,他们的“终”和我这个客户的“终”不一样罢了。这就要说到做软件,本质上是在构建一个“集体想象”。
|
||||
|
||||
## 想象的共同体
|
||||
|
||||
如果你读过尤瓦尔·赫拉利的《人类简史》或《未来简史》,有一个说法你一定不陌生:想象的共同体。作者认为,人类历史发展的一个重要因素是“集体想象”,无论是国家、宗教,还是法律、习俗,都是人们达成的“集体想象”。人类就是认同了这些“集体想象”的一个共同体。
|
||||
|
||||
我们这些做软件的人其实就是一个想象的共同体,这个“集体想象”就是我们要做的软件,任何想象都需要一个载体将其展现出来,我们编写软件的过程就是将这个“集体想象”落实的过程。
|
||||
|
||||
既然是“集体想象”,那么在载体将想象呈现出来之前,我们的想象很难统一起来,都或多或少存在差异。
|
||||
|
||||
所以,**任何事物都要经过两次创造:一次是在头脑中的创造,也就是智力上的或者第一次创造(Mental/First Creation),然后才是付诸实践,也就是实际的构建或第二次创造(Physical/Second Creation)。**
|
||||
|
||||
我们在工作中遇到的很多问题,其实就是在于第一次创造没有做好,就进入到第二次创造。所以,我们在工作中会遇到很多“惊喜”,准确地说,是惊吓。
|
||||
|
||||
相比于第一次创造,第二次创造是一件成本很高的事。我们知道,软件开发最费时费力,一旦投入大量精力做出来,却发现与理解偏差甚大,所有人都会欲哭无泪。
|
||||
|
||||
所以,在动手做事之前,我们要在第一次创造上多下一些功夫,将相关各方的“集体想象”统一起来。以建筑为例,就是先在图纸上构思各种细节。对应到做软件,我们也可以做很多事,比如:
|
||||
|
||||
- 要给用户看产品的样子,可以用原型工具把它做出来,而不是非得把完整功能开发出来;
|
||||
- 要呈现服务接口的样子,可以用模拟服务器搭出一个服务,而不用等后端全部开发完毕;
|
||||
- 要让程序员知道要开发产品的细节,可以在任务上描述出软件各种场景给出的各种行为。
|
||||
|
||||
再回到前面“设计一个登录功能”的例子上,我的同事们在构建的其实是他们自己的想象,而不是我们共同的想象。这其中最大的一个区别就在于,没有人会为他们自己的想象买单的。
|
||||
|
||||
所以说,他们看到的“终”不是真正的终,只是一个自我的“终”,至于看到什么样的“终”,这取决于每个人的见识。
|
||||
|
||||
**对做软件的人来说,我们应该把“终”定位成做一个对用户有价值的软件**,能够为别人带来价值,自己的价值才能体现出来。
|
||||
|
||||
至此,你对“以终为始”已经有了一个初步的认识,有了这种思维方式,我们可以在工作中怎样运用它呢?
|
||||
|
||||
## 规划和发现
|
||||
|
||||
软件行业有很多英雄传说,一个人或者一个团队连续奋战一段时间,写好了一个软件,在上线前夜发现了一个问题,然后冒着“不成功便成仁”的风险,通宵达旦解决了问题,一战成名。
|
||||
|
||||
这种故事听起来让人热血沸腾,但仔细想想,为什么总在最后一刻发现问题?除了时间压力确实大的情况以外,大多数情况,他们还是一开始没有想好就动手了。
|
||||
|
||||
在团队内部,我一直坚持“以终为始”,让大家在执行任务之前,先倒着想想再动手规划,这样规划出来的工作更能瞄准真正的目标。举一个之前做产品的例子,当年在创业的时候,我们打算做一个物联网开发平台,但具体应该做成什么样子呢?
|
||||
|
||||
有了“以终为始”的思维,我们考虑的是别人会怎么用我们的平台。我们设计的方式是,用户到我们的网站,阅读相关文档,然后参考文档一步一步照着做。
|
||||
|
||||
这其中的一个关键点是:文档,特别是《起步走》的文档,这是用户接触我们这个平台的第一步,决定了他对我们产品的第一印象。
|
||||
|
||||
所以,我们决定从写《起步走》这个文档开始,这个文档描绘了用户怎样一步一步使用我们的开发平台,完成第一个“Hello World”级别的应用。**请注意,这个时候,我们一行代码都没有写。**
|
||||
|
||||
写好了这个《起步走》文档,团队的所有人对于我们的平台要做成什么样子,已经有了一个比较初步的认识。更重要的是,我们可以拿着这个文档,去和外部的人讨论这个尚未出世的平台。
|
||||
|
||||
人类是一个擅长脑补的群体,一旦有人看到了这个文档,他就已经可以构想出这个平台已经存在的样子,进而给出各种各样的反馈:“我认为这个地方可以这样做”“我觉得那个地方可以改改”。
|
||||
|
||||
所有这些反馈都是真实的,因为他们已经“看到了”一个真实的东西。正是这些真实的反馈,让我们逐渐地锁定了目标。之后,我们才开始动手写代码。
|
||||
|
||||
**“以终为始”的方式,不仅仅可以帮我们规划工作,还可以帮我们发现工作中的问题。**
|
||||
|
||||
有一次,我的团队在开发一个大功能,要将现有的系统改造成支持多租户的系统。也就是说,别的商家可以到我们的平台上发起申请,拥有和我们现有平台一样的能力。
|
||||
|
||||
功能来了,各个团队将任务分解,然后就各忙各的去了。但我有着习惯性的不安,总担心丢点什么,于是催着项目经理梳理一下上线流程。
|
||||
|
||||
是的,上线流程,虽然我们的代码还没开发完,但是本着“以终为始”的态度,我们就假设各个部分已经开发好了,来想一想上线应该怎么做。
|
||||
|
||||
果不其然,一梳理上线流程,我们便发现了问题:怎么识别不同的租户呢?有人给出的方案是设置一个HTTP头。但谁来设置这个HTTP头呢?没人仔细想过。于是,一个潜在的问题就这样被发现了,至少不用在未来为它加班了。至于解决方案,作为程序员,我们有的是办法。
|
||||
|
||||
事实上,在今天的软件开发实践中,已经有很多采用了“以终为始”原则的实践。
|
||||
|
||||
比如测试驱动开发。测试是什么?就是你这段代码的“终”,只有通过测试了,我们才有资格说代码完成了。当然,测试驱动开发想做好,并不是先写测试这么简单的。
|
||||
|
||||
比如持续集成,我们是要交付一个可运行的软件,倒着来想,最好的做法就是让软件一直处于可运行的状态,那就是持续地做集成。
|
||||
|
||||
概括地说,**践行“以终为始”就是在做事之前,先考虑结果,根据结果来确定要做的事情。**
|
||||
|
||||
这是“以终为始”这个内容版块的开篇,后面我会给你介绍这个原则在不同场景下的应用,也会引入一些现在行业内的最佳实践进行解析。相信会对你的实际工作有帮助。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
有一段时间,网上流传着一个帖子,亚马逊 CTO 介绍亚马逊是如何开发一项产品的,简单来说,他们采用向后工作的方法,开发一项产品的顺序为:
|
||||
|
||||
1. 写新闻稿;
|
||||
1. 写FAQ(常见问题解答);
|
||||
1. 写用户文档;
|
||||
1. 写代码。
|
||||
|
||||
今天我带你了解了“以终为始”的做事思路,回过头再来看这个帖子,相信你不难理解为什么亚马逊要这么做事情了。
|
||||
|
||||
人们习惯采用顺序思考的思维方式,几十万年的进化将这种思考模式刻在了我们的基因里。要成为更好的自己,我们要克服自身的不足,而这个做法很简单,那就是“以终为始”,做事倒着想,先考虑结果。
|
||||
|
||||
人类是一个想象的共同体,做软件的团队更是如此,而我们写出来的软件是我们将“集体想象”落地的载体。
|
||||
|
||||
任何事物都要经过两次创造:一次是在头脑中的创造,也就是智力上的或者第一次创造(Mental/First Creation),然后才是付诸实践,也就是实际的或第二次创造(Physical/Second Creation)。我们应该在第一次创造上多下功夫,统一集体想象,让目标更明确。
|
||||
|
||||
“以终为始”的思维可以帮助我们更好地规划我们手头任务,也可以帮助我们发现过程中的问题。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**遇到事情,倒着想。**
|
||||
|
||||
最后,我想请你思考一下,在实际的工作或生活中,你有运用“以终为始”的思维方式吗?帮助你解决过哪些问题?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
134
极客时间专栏/geek/10x程序员工作法/以终为始/03 | DoD的价值:你完成了工作,为什么他们还不满意?.md
Normal file
134
极客时间专栏/geek/10x程序员工作法/以终为始/03 | DoD的价值:你完成了工作,为什么他们还不满意?.md
Normal file
@ -0,0 +1,134 @@
|
||||
<audio id="audio" title="03 | DoD的价值:你完成了工作,为什么他们还不满意?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4f/37/4fb55ea506e2b998452f2b1608415737.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
在开始今天的讨论之前,我们先来看一个小故事。小李是一个程序员,有一天,项目经理老张来到他身边,和他商量一个功能特性的进度:
|
||||
|
||||
>
|
||||
<p>老张:这有一个任务需要完成,你看一下。<br>
|
||||
小李:这个不难,两天就能做完,两天以后就能上线。</p>
|
||||
|
||||
|
||||
两天以后,老张又来到小李的身边验收工作:
|
||||
|
||||
>
|
||||
<p>老张:怎么样,做完了吗?今天能上线吗?<br>
|
||||
小李:我的代码写完了。<br>
|
||||
老张:测试人员测过了吗?<br>
|
||||
小李:还没有。<br>
|
||||
老张:那今天能测完吗?<br>
|
||||
小李:那我就不知道了。<br>
|
||||
老张:什么?我可是答应了业务的人,今天一定要上线的!</p>
|
||||
|
||||
|
||||
很明显,老张有些愤怒,而小李也有些委屈。于是,老张、小李和测试人员一起度过了一个不眠之夜。
|
||||
|
||||
听完这个故事,你有什么感想呢?先不急,我们继续看后面的故事。
|
||||
|
||||
又过了几天,老张又来找小李,给小李安排一个很简单的功能。在小李看来,一天就能搞定,而按照老张给出的时间表,小李有两天时间处理这个功能。小李心中暗喜:看来我可以“偷得浮生一日闲”了。
|
||||
|
||||
两天以后,老张又来检查工作。
|
||||
|
||||
>
|
||||
<p>老张:这个功能开发完了吗?<br>
|
||||
小李:写完了,你看我给你演示一下。</p>
|
||||
|
||||
|
||||
小李熟练地演示了这个新写好的功能,这次老张很满意:
|
||||
|
||||
>
|
||||
<p>老张:做得不错。单元测试都写了吧?<br>
|
||||
小李:啊?还要写单元测试吗?<br>
|
||||
老张:要不为啥给你两天的时间?</p>
|
||||
|
||||
|
||||
怎么会这样?小李心里很委屈,自己明明已经很好地完成了工作,老张是不是故意在找自己的麻烦呢?
|
||||
|
||||
好,故事讲完了。是不是有些似曾相识的感觉呢?为什么小李辛辛苦苦地工作,老张却总能挑出毛病来呢?老张是不是来挑刺的呢?其实,老张才没那么闲,小李的委屈主要是因为他和老张对于“完成”有着不一样的理解。换句话说,他们之间存在一个理解的鸿沟。
|
||||
|
||||
## 理解的鸿沟
|
||||
|
||||
在这个模块里,我们讨论的主题是“以终为始”。那我们第一个问题就是,“终”到底是什么?在前面这个例子里,“终”就是“完成”,可是,小李认为他的活已经做完了,老张却认为他没做完。
|
||||
|
||||
怎么会这样?二人之所以有分歧,归根结底,就在于二人对“完成”的定义理解的不同。
|
||||
|
||||
在第一个故事里,作为项目经理,老张认为“完成”应该是“上线运行”,而程序员小李则认为“完成”是“功能代码编写完毕”。这中间存在的理解偏差,包括了测试人员的测试工作,可能还包括了运维人员的上线工作。
|
||||
|
||||
在第二个故事里,老张给了小李两天时间。小李认为这两天都是编写功能代码的,而老张想的是,小李应该自己写好功能代码和单元测试,可能还包括了功能测试,这中间的差异是测试代码的工作量。
|
||||
|
||||
因为双方的理解不一致,所以无论怎样努力,小李都不可能达成项目经理老张的要求,正所谓“南辕北辙”。
|
||||
|
||||
那该怎么办呢?小李会说,我又不是老张肚子里的蛔虫,怎么才能和他达成一致呢?答案很简单,既然双方的理解有差异,那就把这个差异弥合上,后面的问题便也不是问题了。
|
||||
|
||||
弥合差异的方式有很多,有一个最佳实践,它的名字叫 DoD(Definition of Done,完成的定义),从这个概念的名字便不难看出,它就是为了解决软件开发中常见的“完成”问题而生的。
|
||||
|
||||
## 完成的定义
|
||||
|
||||
DoD 这个概念本身并不复杂,它就是告诉我们怎样算是完成了,尽量减少因为理解偏差造成的各种浪费。具体怎么做呢?就是团队在开始工作前,先制定 DoD。以前面的场景为例,团队可以规定:
|
||||
|
||||
>
|
||||
特性开发完成,表示开发人员经过了需求澄清、功能设计、编写代码、单元测试,通过了测试人员的验收,确保代码处于一个可部署的状态,相关文档已经编写完毕。
|
||||
|
||||
|
||||
>
|
||||
开发完成,表示开发人员编写好功能代码,编写好单元测试代码,编写好集成测试代码,测试可以通过,代码通过了代码风格检查、测试覆盖率检查。
|
||||
|
||||
|
||||
大家都是聪明人,一旦 DoD 确定好了,谁该做什么事就一目了然了。这个时候,如果小李说“我已经开发完了”,却只是写好了功能代码,那就别怪老张手下无情了。
|
||||
|
||||
好了,你已经知道 DoD 是什么了,它简单到让人一目了然,相信你很快就能知道该怎样把它用到你的工作里。不过,我们不仅要知道怎么用,还要知道怎样让 DoD 更好地发挥作用。
|
||||
|
||||
<li>
|
||||
**DoD 是一个清单,清单是由一个个的检查项组成的,用来检查我们的工作完成情况。**DoD 的检查项,就是我们开发产品所需的一系列有价值的活动。比如:编写代码、编写测试代码、通过测试人员验收等。什么样的活动是有价值的,也许每个团队的认识是不同的。但如果你的团队认为除了功能代码,其他都没价值,也许这是个信号,说明你的团队整体上是缺乏职业素养的,在这样的团队工作,前景并不乐观。
|
||||
</li>
|
||||
<li>
|
||||
**DoD 的检查项应该是实际可检查的。**你说代码写好了,代码在哪里;你说测试覆盖率达标了,怎么看到;你说你功能做好了,演示一下。
|
||||
</li>
|
||||
<li>
|
||||
**DoD 是团队成员间彼此汇报的一种机制。**别把“汇报”想复杂了,最简单的汇报就是说一句“这个功能做完了”。**当我们有了 DoD,做事只有两种状态,即“做完”和“没做完”。**在团队协作中,我们经常会听到有人说“这个事做完了80%”,对不起,那叫没做完,根本没有80%做完的说法。
|
||||
</li>
|
||||
|
||||
在前面的讨论中,我们所说的 DoD 只是从个人层面入手。在团队层面,我们也可以定义 DoD。
|
||||
|
||||
- 某个功能的 DoD,比如:这个功能特性已经开发完成,经过产品负责人的验收,处于一个可部署的状态。
|
||||
- 一个迭代的 DoD,比如:这个迭代规划的所有功能已经完成。
|
||||
- 一次发布的 DoD,比如:整个软件处于可发布的状态,上线计划已经明确。
|
||||
|
||||
## 站在 DoD 的肩膀上
|
||||
|
||||
至此,我们只是从软件开发团队内部协作的角度来谈 DoD。但实际上,**它不仅局限在团队内部协作上,如果你可以放开思路,会发现 DoD 的思维在工作中用途非常广泛。**比如,当我们需要和其他团队合作开发一个接口时,我们都知道第一步就是要把接口定义下来。
|
||||
|
||||
那么,怎样才算定义完成?很多团队认为落在字面上就够了。但是有了 DoD 的思维,我们定义接口,就会去明确定义可检查的检查项。那么在定义接口这件事上,什么才是“可检查”的呢?我们可以参照一个可运行的接口来进行评估。只要检查:
|
||||
|
||||
- 服务方提供的接口是不是和这个可运行的接口返回值是一样的;
|
||||
- 调用方是否可以和这个可运行的接口配合使用。
|
||||
|
||||
谁错了,谁改去。你可能会问,应该参照哪些可运行的接口呢?这不难解决,现在模拟服务器的框架到处都是。如果你不介意的话,我的 [Moco](http://github.com/dreamhead/moco) 就是这样一个开源项目,你可以看一下。
|
||||
|
||||
在协作中一旦确立好 DoD,我们甚至可以通过流程把它固化下来,从而更高效高质地完成工作。当然,我们在工作生活中难免会有一些临时的工作,它们没有复杂到需要一个流程,但是也可以用 DoD 思维来高效地解决。比如:
|
||||
|
||||
>
|
||||
经常会有人过来,让我帮忙做些事。运用 DoD 的思维,我首先会问他我具体要做哪些事,确认好细节(相当于定义好“检查项”),然后我就知道,这个忙我能帮到什么程度。
|
||||
|
||||
|
||||
>
|
||||
我请别人帮忙的时候,也会很清楚告诉他,哪些事是需要他做的,尽量减少不必要的误解。
|
||||
|
||||
|
||||
**DoD 是一个思维模式,是一种尽可能消除不确定性,达成共识的方式。**我们本着“以终为始”的方式做事情,DoD 让我们能够在一开始就把“终”清晰地定义出来。
|
||||
|
||||
人与人协作中,经常会出现各种问题,根本原因就是,有太多因为理解差异造成的误解,进而浪费了大量的时间,而DoD 就是一种将容易产生歧义的理念落到实处的方法。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
好,我们来总结一下今天学到的内容。首先,你应该知道,人与人协作,总会有这样或那样的理解差异。开始协作之前,我们最好先同步一下彼此的理解,确保之后不会因为理解不一致,而让协作方措手不及。
|
||||
|
||||
怎样解决大家的理解偏差呢,我介绍了 DoD(完成的定义),它是行业中的一种最佳实践,能够在团队内部很好地同步大家对“完成”的理解。好的 DoD 是一个可以检查的清单,可以确保你不遗漏任何事情。
|
||||
|
||||
如果深入领会 DoD,你会发现 DoD 可以灵活应用在不同的协作场景中。比如应用于个人工作、团队工作,甚至跨团队工作。当然,你也可以将它灵活地运用于各种生活场景,弥合人与人理解之间的差异,更好地协作与沟通。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**在做任何事之前,先定义完成的标准。**
|
||||
|
||||
最后,我想请你回想一下,你在工作或生活中,是否发生过因为双方理解差异导致的问题或不快呢?有了 DoD 的概念以后,你是不是有了一些新的想法呢?欢迎在留言区留言。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
134
极客时间专栏/geek/10x程序员工作法/以终为始/04 | 接到需求任务,你要先做哪件事?.md
Normal file
134
极客时间专栏/geek/10x程序员工作法/以终为始/04 | 接到需求任务,你要先做哪件事?.md
Normal file
@ -0,0 +1,134 @@
|
||||
<audio id="audio" title="04 | 接到需求任务,你要先做哪件事?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/32/2f/326dfc6bf54f060bb73961b2298a5d2f.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
我们书接上文,继续讲程序员小李的故事。这次小李接到一个新的需求,让他开发一个单点登录的服务,经过几天的奋战,他顺利地写完了所有的代码。正好产品经理小王路过他身边,顺便问了他一下。
|
||||
|
||||
>
|
||||
<p>小王:单点登录做得咋样了?<br>
|
||||
小李:做完了,我给你演示一下。</p>
|
||||
|
||||
|
||||
小李演示了一遍自己做的功能,小王看上去很满意。
|
||||
|
||||
>
|
||||
<p>小王:不错。不过,怎么没有支持验证码?<br>
|
||||
小李:为什么要做这个?<br>
|
||||
小王:这不就是登录的一部分吗?<br>
|
||||
小李:哪里规定要做验证码了?<br>
|
||||
小王:现在做登录哪有不用验证码的?</p>
|
||||
|
||||
|
||||
我想你已经嗅到了双方谈话的火药味,这个时候如果双方都不能很好地控制自己的情绪,那接下来一场体力的较量可能就一触即发了。
|
||||
|
||||
为什么双方会有这么大的分歧呢?其中一个重要的原因是,开始实现这个需求之前,任务双方都没有清晰地定义好边界,没能把需求描述清楚。
|
||||
|
||||
## 需求描述的问题
|
||||
|
||||
在软件开发中,程序员做什么一般都由需求来定义。我们都知道,需求是软件开发的一个重要组成部分,但你可能并没有仔细想过,不同的需求描述方式,可能会影响我们程序员对需求的理解。
|
||||
|
||||
因为信息的传递是会衰减的,你不可能把你理解的信息100%传递给另外一个人,而这中间,如何传递,也就是如何描述将直接决定衰减的比例。
|
||||
|
||||
很多公司的软件开发模式是基于功能列表的,这个列表“规定”了程序员要做的功能,各个组从产品经理那里领来开发列表,然后“照单抓药”开始写代码。但是,通常这种功能列表只是一些简单的描述,你并不能看到全局。
|
||||
|
||||
很多团队的一个状态就是,程序员们都知道要开发的功能是什么,但这个功能是谁在什么样的场景下使用的,很多人却回答不上来。如果你去问他为什么要开发这个功能,他通常会说:这是功能列表里规定的。
|
||||
|
||||
** 这种功能列表式的需求描述方式,将一个完整的需求敲成了碎片。** 只有所有功能全部开发完成,对接在一起的时候,才是“破镜重圆”的时刻。
|
||||
|
||||
也就是说,不到最后一刻,大多数人并没有一个完整的图景,这就相当于看不到完整的“终”。顺着这个思路做下去,你会在最后关头遇到许多意料之外的问题,其结果必然是手忙脚乱。
|
||||
|
||||
根据这种基于功能列表的需求描述,每个组在安排工作的时候,都会按照自己的理解进行功能排列。
|
||||
|
||||
所以,当你的组完成了一个功能时,这个功能却可能上不了线,因为你还要依赖于其他组的工作,而这个组不巧,却刚好把相关的功能开发排在了后面。
|
||||
|
||||
这还只是两个组之间有依赖的情况,如果需要多个组协同,可以想象,状况会多么糟糕。
|
||||
|
||||
所以,当我们对产品经理说“时间不足,砍掉一些需求吧。”得到的答案肯定是,“对不起,做不到,因为需求已破碎,没办法调整。”
|
||||
|
||||
因此,一些新的需求描述方式也就应运而生,这其中,用户故事(User Story)是我最喜欢的一种方式。它是站在用户的角度来描述了一个用户希望得到的功能,关注用户在系统中完成一个动作需要经过怎样的路径。既然它是“故事”,它就需要是一个完整的场景,可以讲述出来。
|
||||
|
||||
## “用户故事”有什么用?
|
||||
|
||||
我们先来以用户密码登录为例,看看用户故事长什么样?一个完整的用户故事大致包含以下几个部分:
|
||||
|
||||
<li>
|
||||
标题,简要地说明这个用户故事的主要内容,比如:注册用户使用用户名密码登录。
|
||||
</li>
|
||||
<li>
|
||||
<p>概述,简要地介绍这个用户故事的主要内容,一般会用这样的格式:<br>
|
||||
As a (Role), I want to (Activity), so that (Business Value).<br>
|
||||
意思就是:作为一个什么角色,要做什么样的事,以便达成一种怎样的效果。其中最重要的是,告诉别人为什么要做这件事,虽然只有一句话,却往往是很多人欠缺的思考,只知做,不知为何做。<br>
|
||||
举个概述的例子:作为一个注册用户,我想要通过用户密码登录,以便我可以使用注册用户才能够使用的服务。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>详述,详细地描述这个用户故事的完整流程,我们会把操作流程、用户界面等信息都放到这里。<br>
|
||||
比如:用户使用正确用户名和密码登录,就可以登录成功;如果密码不正确,则登录页面提示用户“用户名密码不正确”。基本上,看到这个部分,程序员就可以在心中描绘出这个用户故事的样子了。<br>
|
||||
超出范围的部分,比如:第三方登录不在范围内,这个部分主要是限定人们不要进一步发散。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>验收标准,这个部分会描述一个正常使用的流程是怎样的,以及各种异常流程系统是如何给出响应的,这是程序员常常会欠缺的思考。它会把详述中很多叙述的部分变成一个具体的测试用例。比如,下面我给出的两个验收用例:<br>
|
||||
正常场景:给定一个注册用户张三,其用户名是zhangsan,密码是foobar,当张三使用zhangsan 和 foobar 登录系统时,可以成功登录,登录成功后,跳转到用户中心。<br>
|
||||
异常场景:给定一个注册用户张三,其用户名是zhangsan,密码是foobar,当张三使用zhangsan 和 wrong 登录系统时,登录失败,在登录页面上提示“用户名密码不正确”。</p>
|
||||
</li>
|
||||
|
||||
在前面的例子中,小张和小王之所以会对需求是否完成产生分歧,是因为大家对于需求完成的定义不同。对于这种情况,我们能怎么办呢?
|
||||
|
||||
这个模块的主题是“以终为始”,现在你看到了用户故事是如何描述需求的,你或许已经知道我要说什么了,没错,这里非常关键的一点就是“验收标准”。很多人学习用户故事,认为最重要的是记住“As…, I want to …, so that …”这样的需求描述方式。
|
||||
|
||||
在我看来,无论采用哪种需求描述方式,这部分也都是能说清楚的。那我们要从用户故事中学到什么呢?我认为就是用户故事的关键点:验收标准,它可以清晰地定义出需求边界。
|
||||
|
||||
**验收标准非常重要的一环是异常流程的描述。**大部分程序员都擅长解决正常流程,而异常流程则是最容易忽略的,也是产生扯皮的关键环节。既然容易扯皮,我们就在一开始把它定义清楚。怎么才算做完需求呢?验收标准说了算。
|
||||
|
||||
采用用户故事之后,我经常在写完了主要流程之后,再去看一下验收标准,为自己的开发查缺补漏。因为我知道,那是标准,达不成就不算任务完成。
|
||||
|
||||
当我们说自己开发完成,可以交给测试人员测试时,我们需要照着验收标准给测试人员演示一遍,证明我们的系统确实能够跑通。这之后,测试人员才会把系统接手过去,做更系统的测试。
|
||||
|
||||
**验收标准给出了这个需求最基本的测试用例,它保证了开发人员完成需求最基本的质量。**如果你了解 BDD(Behavior-Driven Development,也就是“行为驱动开发”),就可以按照验收标准中给出的内容编写验收测试用例了。
|
||||
|
||||
在实际工作中,许多产品经理把需求交给开发人员之前,很多细节是没想清楚的,那种功能列表式的需求常常只包含了正常路径,那些缺失的细节就是在后续的过程中,由开发人员补全的。用户故事就是一种固定的格式,让他们把这些应该想清楚的问题想清楚。
|
||||
|
||||
**如果你的团队采用用户故事的格式进行需求描述固然好,如果不能,在功能列表中,补充验收标准也会极大程度地改善双方协作的效率。**
|
||||
|
||||
## 你的角色
|
||||
|
||||
或许你会有这样的疑问,如果产品经理通过用户故事的方式,将需求实现细节都描绘得清清楚楚,那我们程序员的发挥空间在哪里?请注意,验收标准所给出实现细节应该是业务上的,程序员在这种问题上思考才是真正意义上的浪费时间,我们的发挥空间应该是在技术实现上。
|
||||
|
||||
然而,在现实情况中,很多团队做不到这种程度。
|
||||
|
||||
你会发现,我们在开发中之所以会“丢三落四”,很重要的一个原因是,在开发一个功能特性的时候,因为一些环节的缺失,我们不得已扮演了很多的角色,其中之一就是产品经理。你是一个专业的程序员,但大多数情况下,你却只是一个业余的产品经理,“丢三落四”就在所难免了。
|
||||
|
||||
或许你会说,我在一个小公司工作,公司没那么多人,没有专门的产品经理,只有我们几个“全世界都缺”的程序员,需求都是老板扔给我们的,谁来帮我们写验收标准呢?
|
||||
|
||||
没办法,答案只能是你自己。虽然你名义上是程序员,但当拿到一个需求的时候,你要做的事不是立即动手写代码,而是扮演产品经理的角色,分析需求,圈定任务范围。相信我,事前分析绝对比你拿一个写好的系统给老板,而他却告诉你这不是他想要的,好太多了。
|
||||
|
||||
另外我想提醒你注意的是,**扮演不同角色的时候,我们的思考模式是不同的。**还是以开发用户名密码登录为例,你想到的可能是:输入正确的用户名和密码可以正常登录,输入错误的用户名和密码不能登录,并且给出提示。
|
||||
|
||||
如果你只扮演开发人员的角色,想到这些就算不错了。但如果你扮演的是产品经理的角色,会从产品的角度进行思考,也就会看到不同的内容,比如:
|
||||
|
||||
- 登录是否需要验证码
|
||||
- 是否需要第三方登录
|
||||
- 用户名和密码的长度在系统内是否有限制
|
||||
- 密码是否需要满足一定的规则
|
||||
- ……
|
||||
|
||||
我知道,如果让你来填写,这个列表会更长。可能这并不是我们都需要完成的功能,但站在分析的角度,这都是我们要考虑的问题,一个登录功能,绝不仅仅是用户名和密码校验那么简单的。我们能想到这些,仅仅是因为我们正在扮演一个不同的角色。
|
||||
|
||||
所以,如果你要兼顾开发人员和产品经理两个角色,建议你先扮演好产品经理的角色,多花点时间把验收标准制定好,再回到开发人员的角色上去写代码。毕竟,**最好维护的代码是没有写出来的代码。**
|
||||
|
||||
## 总结时刻
|
||||
|
||||
需求,是软件开发中的一个关键环节,一旦需求理解出现问题,势必会造成大量的浪费。传统的功能列表只是简单罗列了要实现的功能,丢失了大量的上下文,会导致团队成员对于需求“只见树木不见森林”。
|
||||
|
||||
而在比较大的团队中,更是会将一个功能分拆到多个小团队中,每个人看到的只是功能碎片。于是,后来产生了其他的需求描述方式,比如用例和用户故事。
|
||||
|
||||
在实际的开发过程中,大量的分歧来自于对“需求完成”的定义。当我们把“以终为始”的原则应用在需求领域中,就会注意到,用户故事有一个非常重要的组成部分是验收标准。
|
||||
|
||||
验收标准不仅仅描述出了正常流程,也会关注到异常流程的处理,它也是我们验收测试用例的起点。一旦事先定义好验收标准,大量的扯皮工作就随之烟消云散了。
|
||||
|
||||
理解了验收标准的作用,即便我们不使用用户故事来定义需求,依然可以把用户故事中的关键点应用到自己的实践中,在功能列表的每个功能定义中,增加验收标准。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**在做任何需求或任务之前,先定好验收标准。**
|
||||
|
||||
最后,我想请你回想一下,在实际工作中,你是如何澄清你的需求,或者因为需求不清晰给你造成了哪些困扰?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
113
极客时间专栏/geek/10x程序员工作法/以终为始/05 | 持续集成:集成本身就是写代码的一个环节.md
Normal file
113
极客时间专栏/geek/10x程序员工作法/以终为始/05 | 持续集成:集成本身就是写代码的一个环节.md
Normal file
@ -0,0 +1,113 @@
|
||||
<audio id="audio" title="05 | 持续集成:集成本身就是写代码的一个环节" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/42/3e/428c9928331948e58599f0c74490c23e.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
上一讲我们探讨了需求的“完成”,你现在知道如何去界定一个需求是否算做完了,这要看它是不是能够满足验收标准,如果没有验收标准,就要先制定验收标准。这一点,对于每一个程序员来说都至关重要。
|
||||
|
||||
在今天这一讲中,我们假设需求的验收标准已经制定清楚,接下来作为一个优秀的程序员,你就要撸起袖子准备开始写代码了。
|
||||
|
||||
不过在这里,我要问你一个问题:“是不是写完代码,工作就算完成了呢?”你或许会疑惑,难道不是这样吗?那我再问你:“代码是技术团队的交付物吗?”
|
||||
|
||||
你是不是发现什么不对劲了。没有人需要这堆文本,人们真正需要的是一个可运行的软件。**写代码是程序员的职责,但我们更有义务交付一个可运行的软件。**
|
||||
|
||||
交付一个可运行的软件,通常不是靠程序员个体奋战就能完成的,它是开发团队协作的结果。我们大多数人都工作在一个团队中,那我们写的代码是不是能够自然而然地就和其他人的代码配合到一起呢?显然没那么简单。
|
||||
|
||||
如果想将每个程序员编写的代码很好地组合在一起,我们就必须做一件事:**集成。**
|
||||
|
||||
但是集成这件事情,该谁做,该怎么做呢?我不知道你有没有思考过这个问题。在开始这个话题之前,我先给你讲个故事。
|
||||
|
||||
## 集成之“灾”
|
||||
|
||||
2009年,我在一个大公司做咨询。对接合作的部门里有很多个小组,正在共同研发一个项目。他们工作流程是,先开发一个月,等到开发阶段告一段落,大项目经理再把各个小组最精锐成员调到一起开始集成。对他们来说,集成是一件大事,难度很大,所以要聚集精英来做。
|
||||
|
||||
这个项目是用 C 语言编写的,所以,集成的第一步就是编译链接。大家把各个小组写好的程序模块编译到一起,哪个模块有问题,哪个小组的精英就出手解决它。
|
||||
|
||||
如果第一天,所有模块能够编译链接到一起,大家就要谢天谢地了。之后才进入到一个正式“联调”的过程。
|
||||
|
||||
“联调”的目标,是把一个最基本的流程跑通,这样,集成才算完成。而对他们这个项目来说,“联调”阶段更像是场“灾难”。
|
||||
|
||||
为什么?你想想,一个大部门有若干个团队,每个团队都在为同一个项目进行代码开发,周期为一个月。这一个月期间,所有团队的程序模块汇总在一起,体量会非常庞大。那么这些内容中,出现错误需要改动的可能性也就非常大,需要改动的量也就非常大。因此他们集成“联调”所需要的时间也会非常长。
|
||||
|
||||
即便他们调动各组精英,完成一次项目集成的时间至少也需要2~3天,改动量稍大,可能就要一周了。虽然我不知道你所处公司的现状是什么样的,但大概率地说,你在职业生涯中,会遇到过类似的场景。那怎么去解决这个问题呢?
|
||||
|
||||
## 迈向持续集成
|
||||
|
||||
聪明的你作为旁观者一定会想,在这个故事里,**为什么他们要在开发一个月后才做集成呢?为什么不能在开发一周后,甚至是更短的时间内就集成一次?**
|
||||
|
||||
这是一个行业中常见的痛点,所以,就会有人不断地尝试改进,最先取得的突破是“每日构建”。
|
||||
|
||||
1996年,Steve McConnel出版了一本著作《Rapid Development》,国内译作《快速软件开发》。在这本书中,作者首次提出了解决集成问题的优秀实践:**Daily Build,每日构建。**通过这个名字,我们便不难看出它的集成策略,**即每天集成一次。**
|
||||
|
||||
这在当时的人看来,已经是“惊为天人”了。就像上面提到的例子一样,当时的人普遍存在一种错误认知:集成不是一件容易的事,需要精英参与,需要很长时间,如果每天都进行集成,这是想都不敢想的事情。
|
||||
|
||||
实际上,每日构建背后的逻辑很简单:既然一段时间累积下来的改动量太过巨大,那一天的时间,累积的改动量就小多了,集成的难度也会随之降低。
|
||||
|
||||
你会看到,对比最后做集成和每日构建,这两种不同的做法都是在处理改动量和集成时间的关系。只不过,一个是朝着“长”的方向在努力,一个则瞄准“短”的方向。最后的事实证明,“长”的成了恶性循环,“短”的成了最佳实践。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/68/f9/68d9d0d361240a24769d7f57069915f9.jpg" alt="">
|
||||
|
||||
既然,我们认同了只要增加集成的频率,就可以保证在每次集成时有较少的改动量,从而降低集成难度。
|
||||
|
||||
那问题来了?究竟要在开发后多久才进行一次集成呢?是半天、两个小时、还是一个小时呢?**倘若这个想法推演到极致,是否就变成了只要有代码提交,就去做集成?**
|
||||
|
||||
没错,正是基于这样的想法,有人尝试着让开发和集成同时进行,诞生了一个关于集成的全新实践:持续集成。
|
||||
|
||||
持续集成一个关键的思维破局是,将原来分成两个阶段的开发与集成合二为一了,也就是一边开发一边集成。
|
||||
|
||||
持续集成这个想法固然好,但是不是需要有专人负责盯着大家的工作,只要有人提交了代码,这个负责人就要去集成呢?显然,这在真实工作中是行不通的。
|
||||
|
||||
既然是程序员的想法,程序员解决问题的方案自然就是自动化这个过程。于是,有人编写了一个脚本,定期去源码服务器上拉代码,出现程序更新时,就自动完成构建。
|
||||
|
||||
后来,人们发现这段脚本与任何具体项目都是无关的。于是,把它进一步整理并发布出来,逐步迭代发展成为今天广为人知的持续集成服务器。
|
||||
|
||||
在2000年时,“软件行业最会总结的人” Martin Fowler 发布了一篇重量级文章“[Continuous Integration](http://martinfowler.com/articles/continuousIntegration.html)”。
|
||||
|
||||
之后一年,由 Martin Fowler 所在的 ThoughtWorks 公司发布了市面上第一款持续集成服务器 CruiseControl。CruiseControl 可谓是持续集成服务器的鼻祖,后来市面上的服务器基本都是在它的基础上改良而来的。
|
||||
|
||||
Martin Fowler 的重磅文章和首款持续集成服务器的问世,让软件行业对持续集成进行了更为深入的探讨,人们对于持续集成的认知程度一路走高,持续集成服务器成为了开发团队在集成阶段最得心应手的工具。围绕着持续集成的一系列行为准则逐渐成型。
|
||||
|
||||
以至于发展到2006年,Martin Fowler 不得不重写了“[Continuous Integration](http://martinfowler.com/articles/continuousIntegration.html)”这篇文章。之后人们更是以持续集成为基础,进一步拓展出**持续交付**的概念。
|
||||
|
||||
人类对工具是有偏爱的,持续集成服务器的发布,将持续集成从一项小众实践逐步发展成为今天行业的“事实”标准。
|
||||
|
||||
## “地面上”的持续集成
|
||||
|
||||
然而,即便持续集成已经发展多年,至今整个行业在对它的应用上,却并未达到同步的状态。有趣的是,有一部分公司虽然还无法实现持续集成,但是**因为持续集成服务器的出现,反而可以做到每日构建。**
|
||||
|
||||
这不难理解,每日构建的概念虽然早早就提出来了,但在那个时期,行业里真正践行每日构建的公司并不多,其根本原因就在于,每日构建最初都是一些指导原则,缺乏工具的支持。而每日构建和持续集成最根本的区别在于构建时机,而这只是持续集成服务器的一个配置选项而已。
|
||||
|
||||
当然,行业内有一部分公司已经可以将持续集成运用得得心应手,而也有相当大的一部分人还在为集成而痛苦不堪,比如我前面提到的咨询项目。
|
||||
|
||||
这个项目是我在2009年时参与的。也就是说,此时距离 Martin Fowler 最初写下“[Continuous Integration](http://martinfowler.com/articles/continuousIntegration.html)”已经过去了9年,甚至距离这篇文章的更新版发布也已经过去了3年,更不要说距离 McConnell 提出“每日构建”已经13年。
|
||||
|
||||
即便以当时的时间坐标系来看,这个项目的集成实践水平至少落后行业10年以上。没错,他们甚至连每日构建都还差很远。
|
||||
|
||||
时至今日,持续集成早就是成熟得不能再成熟的实践了。然而,据我所知,许多公司依然处于集成要依赖于“英雄”的蛮荒阶段。
|
||||
|
||||
**虽然我们在同一个时代写代码做开发,但在技术实践层面,不同的团队却仿佛生活在不同的年代。**这也是我们要学习的原因。
|
||||
|
||||
也许,目前国内对于持续集成的实践水平还处于较为原始的状态,这是个坏消息。但好消息是,我们可以通过更多的学习,对集成有足够的了解,从而一步到位地进入到最先进的状态中。
|
||||
|
||||
无需停留在以精英为核心的集成时代,也可以完全不理会每日构建,我希望你拥有这个时代的集成观,直接开始持续集成。
|
||||
|
||||
如果有了持续集成的集成观,我们该怎么看待开发这件事呢?开发和集成就不再是两个独立的过程,而是合二为一成为一体。
|
||||
|
||||
基于这样的理解,我们就不能再说代码写完了,就差集成了,因为这不叫开发的完成。**一个好的做法是尽早把代码和已有代码集成到一起,而不应该等着所有代码都开发完了,再去做提交。**
|
||||
|
||||
怎样尽早呢?你需要懂得任务分解,这是我们在之后的“任务分解”主题下会讲到的内容。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
在软件开发中,编写代码是很重要的一环,但程序员的交付物并不应该是代码,而是一个可工作的软件。当我们在一个团队中工作的时候,把不同人的代码放在一起,使之成为一个可工作软件的过程就是集成。
|
||||
|
||||
在很长一段时间内,集成都是软件行业的难题,改动量和集成时间互相影响。幸运的是,不同的人在不同的方向尝试着改变,结果,同时加大改动量和集成时间的人陷入了泥潭,而调小这两个参数的人看到了曙光。
|
||||
|
||||
每日构建作为早期的一种“最佳实践”被提了出来,但因为它基本上都是原则,没有得到广泛的应用。当人们进一步“调小”参数后,诞生了一个更极致的实践:持续集成,也就是每次提交代码都进行集成。
|
||||
|
||||
真正让持续集成成为行业最佳实践的是,Martin Fowler 的文章以及持续集成服务器。持续集成的思维让我们认识到,开发和集成可以合二为一。我们应该把开发的完成定义为代码已经集成起来,而站在个体的角度,我们应该尽早提交自己的代码,早点开始集成。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**尽早提交代码去集成。**
|
||||
|
||||
最后,我想请你分享一下,在实际工作中,你遇到过哪些由集成带来的困扰?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
109
极客时间专栏/geek/10x程序员工作法/以终为始/06 | 精益创业:产品经理不靠谱,你该怎么办?.md
Normal file
109
极客时间专栏/geek/10x程序员工作法/以终为始/06 | 精益创业:产品经理不靠谱,你该怎么办?.md
Normal file
@ -0,0 +1,109 @@
|
||||
<audio id="audio" title="06 | 精益创业:产品经理不靠谱,你该怎么办?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/53/80/53c13c047cccd346ccf640f41b31fb80.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
前面谈到验收标准时,我们说的实际上是确定性需求,也就是说,我们已经知道了这个需求要怎么做,就差把它做出来了。而有时候,我们面对的需求却是不确定的,比如,产品经理有了一个新想法,那我们该如何应对呢?
|
||||
|
||||
今天,我们从 IT 行业一个极为经典的话题开始:程序员如何面对产品经理。我先给你讲一件发生在我身边的事。
|
||||
|
||||
有一次,我们一大群人在一个大会议室里做一个产品设计评审,来自产品团队和技术团队的很多人都参与到这个评审中。一个产品经理正对着自己的设计稿,给大家讲解一个新的产品特性。
|
||||
|
||||
这个公司准备将自己的服务变成了一个云服务,允许第三方厂商申请,这个产品经理给大家讲解的就是第三方厂商自行申报开通服务的流程。听完前面基本情况的介绍,我举手问了几个问题。
|
||||
|
||||
>
|
||||
<p>我:这个服务会有多少人用?<br>
|
||||
产品经理:这是给第三方厂商的人用的。<br>
|
||||
我:我问的是,这个服务会有多少人用。<br>
|
||||
产品经理:每个第三方厂商的申请人都会用。<br>
|
||||
我:好,那你有预期会有多少第三方厂商申请呢?<br>
|
||||
产品经理:呃,这个……我们没仔细想过。<br>
|
||||
我:那现在给第三方厂商开通服务的具体流程是什么。<br>
|
||||
产品经理:第三方厂商申请,然后,我们这边开通。<br>
|
||||
我:好,这个过程中,现在的难点在哪里?这个审批过程能让我们的工作简化下来吗?<br>
|
||||
产品经理:……<br>
|
||||
我:那我来告诉你,现在开通第三方厂商服务,最困难的部分是后续开通的部分,有需要配置服务信息的,有需要配置网络信息的。目前,这个部分还没有很好的自动化,前面审批的部分能够自动化,对整个环节优化的影响微乎其微。</p>
|
||||
|
||||
|
||||
我的问题问完了,开发团队的人似乎明白了什么,纷纷表示赞同我的观点。这个审批流程本身的产品设计并不是问题,但我们的时间和资源是有限的,关键在于,要不要在这个时间节点做这个事。准确地说,这是优先级的问题。
|
||||
|
||||
此刻,作为开发团队一员的你,或许会有种快感,把产品经理怼回去,简直大快人心。好吧,作为一个正经的专栏,我们并不打算激化产品经理和开发团队的矛盾,而是要探讨如何做事情才是合理的。
|
||||
|
||||
之所以我们能很好地回绝了产品经理不恰当的需求,是因为我们问了一些好问题,但更重要的是,我们为什么能问出这些问题。
|
||||
|
||||
## 产品经理是个新职业
|
||||
|
||||
在做进一步讨论之前,我们必须认清一个可悲的现状,**IT 行业中大多数人的专业程度是不够的。**
|
||||
|
||||
IT 行业是一个快速发展的行业,这个行业里有无数的机会,相对于其它行业来说,薪资水平也要高一些,这就驱使大量的人涌入到这个行业。
|
||||
|
||||
也因为这是一个快速发展的行业,很多职位都是新近才涌现出来的,比如,在2010年之前很少有专职的前端工程师,之前的工程师往往要前后端通吃。
|
||||
|
||||
产品经理便是随着创业浪潮才风起云涌的职位。既然这是个“新”职位,往往是没有什么行业标准可言的。所以,你会看到很多行业乱象:很多人想进入IT行业,一看程序员需要会写代码,觉得门槛高,那就从产品经理开始吧!这些人对产品经理岗位职责的理解是,告诉程序员做什么。
|
||||
|
||||
这和郭德纲口中外行人“如何认识相声”是一个道理,以为会说话就能说相声,殊不知,这是个门槛极高的行业。产品经理也一样,没有良好的逻辑性,怎么可能在这个行业中有好的发展。
|
||||
|
||||
如果你遇到的产品经理能给出一个自洽的逻辑,那么恭喜你,你遇到了还算不错的产品经理。多说一句,这个行业中专业度不够的程序员也有很多,人数比产品经理还多,道理很简单,因为程序员的数量比产品经理的数量多。
|
||||
|
||||
这么说并不是为了黑哪个职位,而是要告诉大家,**我们必须要有自己的独立思考,多问几个为什么,尽可能减少掉到“坑”里之后再求救的次数。**
|
||||
|
||||
回到前面的主题,我们该怎么与产品经理交流呢?答案还在这个部分的主题上,以终为始。我们是要做产品,那就需要倒着思考,这个产品会给谁用,在什么场景下怎么用呢?
|
||||
|
||||
这个问题在 IT 行业诞生之初并不是一个显学,因为最初的 IT 行业多是为企业服务的。企业开发的一个特点是,有人有特定的需求。在这种情况下,开发团队只要把需求分析清楚就可以动手做了,在这个阶段,团队中的一个关键角色是业务分析师。即便开发出来的软件并不那么好用,企业中强行推动,最终用户也就用了。
|
||||
|
||||
后来,面向个人的应用开始出现。在 PC 时代和早期的互联网时代,软件开发还基本围绕着专业用户的需求,大部分软件只要能解决问题,大家还是会想办法用起来的。
|
||||
|
||||
但是随着互联网深入人心,软件开始向各个领域蔓延。越来越多的人进入到 IT 行业,不同的人开始在各个方向上进行尝试。这时候,**软件开发的主流由面向确定性问题,逐渐变成了面向不确定性问题。**
|
||||
|
||||
IT 行业是这样一个有趣的行业,一旦一个问题变成通用问题,就有人尝试总结各种最佳实践,一旦最佳实践积累多了,就会形成一套新的方法论。敏捷开发的方法论就是如此诞生的,这次也不例外。
|
||||
|
||||
## 精益创业
|
||||
|
||||
最早成型的面向不确定性创造新事物的方法论是精益创业(Lean Startup),它是 Eric Ries 最早总结出来的。他在很多地方分享他的理念,不断提炼,最终在2011年写成一本同名的书:《精益创业》。
|
||||
|
||||
看到精益创业这个名字,大多数人会优先注意到“创业(Startup)”这个词。虽然这个名字里有“创业”二字,但它并不是指导人们创业挣大钱的方法论。正如前面所说,**它要解决的是面向不确定性创造新事物。**
|
||||
|
||||
只不过,创业领域是不确定性最强而且又需要创造新事物的一个领域,而只要是面向不确定性在解决问题,精益创业都是一个值得借鉴的方法论。比如,打造一个新的产品。
|
||||
|
||||
精益创业里的“精益”(Lean)是另外一个有趣的词。精益这个词来自精益生产,这是由丰田公司的大野耐一和新乡重夫发展出来的一套理论。
|
||||
|
||||
这个理论让人们开始理解价值创造与浪费之间的关系。创造价值是每个人都能理解的,但减少浪费却是很多人忽略的。所以,把这几个理念结合起来,精益创业就是在尽可能少浪费的前提下,面向不确定性创造新事物。
|
||||
|
||||
那精益创业到底说的是什么呢?其实很简单。我们不是要面向不确定性创造新事物吗?**既然是不确定的,那你唯一能做的事情就是“试”。**
|
||||
|
||||
怎么试呢?试就要有试的方法。精益创业的方法论里,提出“开发(build)-测量(measure)-认知(learn)”这样一个反馈循环。就是说,当你有了一个新的想法(idea)时,就把想法开发成产品(code)投入市场,然后,收集数据(data)获取反馈,看看前面的想法是不是靠谱。
|
||||
|
||||
得到的结果无非是两种:好想法继续加强,不靠谱的想法丢掉算了。不管是哪种结果,你都会产生新的想法,再进入到下一个循环里。在这个反馈循环中,你所获得的认知是最重要的,因为它是经过验证的。在精益创业中,这也是一个很重要的概念:经过验证的认知(Validated Learning)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ed/d0/ed9927aea92c657610bb1e3270a8fbd0.jpg" alt="">
|
||||
|
||||
既然是试,既然是不确定这个想法的有效性,最好的办法就是以最低的成本试,达成同样一个目标,尽可能少做事。精益创业提出一个非常重要的概念,最小可行产品,也就是许多人口中的 MVP(Minimum Viable Product)。简言之,少花钱,多办事。
|
||||
|
||||
许多软件团队都会陷入一个非常典型的误区,不管什么需求都想做出来看看,殊不知,把软件完整地做出来是最大的浪费。
|
||||
|
||||
## 你为什么要学习精益创业?
|
||||
|
||||
或许你会问,我就是一个程序员,也不打算创业,学习精益创业对我来说有什么用呢?答案在于,**精益创业提供给我们的是一个做产品的思考框架,我们能够接触到的大多数产品都可以放在这个框架内思考。**
|
||||
|
||||
有了框架结构,我们的生活就简单了,当产品经理要做一个新产品或是产品的一个新特性,我们就可以用精益创业的这几个概念来检验一下产品经理是否想清楚了。
|
||||
|
||||
比如,你要做这个产品特性,你要验证的东西是什么呢?他要验证的目标是否有数据可以度量呢?要解决的这个问题是不是当前最重要的事情,是否还有其他更重要的问题呢?
|
||||
|
||||
如果上面的问题都得到肯定的答复,那么验证这个目标是否有更简单的解决方案,是不是一定要通过开发一个产品特性来实现呢?
|
||||
|
||||
有了这个基础,回到前面的案例中,我对产品经理提的问题,其实就是在确定这件事要不要做。事实上,他们当时是用一个表单工具在收集用户信息,也就是说,这件事有一个可用的替代方案。鉴于当时还有很多其它需求要完成。我建议把这个需求延后考虑。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
程序员与产品经理的关系是 IT 行业一个经典的话题。许多程序员都会倾向于不问为什么就接受来自产品经理的需求,然后暗自憋气。
|
||||
|
||||
实际上,产品经理是一个新兴职业,即便在 IT 这个新兴行业来看,也算是新兴的。因为从前的 IT 行业更多的是面向确定性的问题,所以,需要更多的是分析。只有当面向不确定性工作时,产品经理才成为一个行业普遍存在的职业。所以,在当下,产品经理并不是一个有很好行业标准的职位。
|
||||
|
||||
比较早成型的面向不确定创造新事物的方法论是精益创业,它提出了“开发(build)-测量(measure)-认知(learn)”这样一个反馈循环和最小可行产品的概念。
|
||||
|
||||
当产品经理让我们做一个新的产品特性时,我们可以从精益创业这个实践上得到启发,向产品经理们问一些问题,帮助我们确定产品经理提出的需求确实是经过严格思考的。
|
||||
|
||||
如果今天的内容你只记住一件事,那请记住:**默认所有需求都不做,直到弄清楚为什么要做这件事。**
|
||||
|
||||
最后,我想请你回想一下,你和产品经理日常是怎样做交流的呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
117
极客时间专栏/geek/10x程序员工作法/以终为始/07 | 解决了很多技术问题,为什么你依然在“坑”里?.md
Normal file
117
极客时间专栏/geek/10x程序员工作法/以终为始/07 | 解决了很多技术问题,为什么你依然在“坑”里?.md
Normal file
@ -0,0 +1,117 @@
|
||||
<audio id="audio" title="07 | 解决了很多技术问题,为什么你依然在“坑”里?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a0/c0/a07a6c09d42f097390f14df7a0a270c0.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
在前面的内容中,我给你介绍了几个体现“以终为始”原则的实践,包括怎样界定工作是否完成的 DoD、怎样判定需求是否完成的验收标准、还有怎样验证产品经理给出的产品特性是否合理的精益创业理念。
|
||||
|
||||
了解了这些内容,可能你会想:我为什么要关心这些啊?我是程序员啊!难道我不应该安安静静地写程序吗?为什么要操心其他人的工作做得好坏?如果我管了那么多事,我还是不是一个程序员,到底哪里才是我的“终”呢?
|
||||
|
||||
今天这一讲,我们就来聊聊这个让许多人困惑的问题。因为只有要跳出程序员的角色看问题,工作才会变得更加高效。
|
||||
|
||||
## “独善其身”不是好事
|
||||
|
||||
在需要与人协作的今天,独善其身可不一定是好的做法。我先给你讲一个发生在我身边的故事。
|
||||
|
||||
有一次,我的团队要开发一个数据服务层,准备作为一个基础设施提供给核心业务系统。开发没多久,一个团队成员和我说,他的工作进展不顺利,卡在了一个重要问题上,他想不明白该如何在多个实例之间分配 ID。
|
||||
|
||||
我听完之后,有些疑惑,为什么要考虑这个和功能无关的问题呢?他解释说,因为我们的系统需要保证消息的连续性,所以他设计了消息 ID,这样下游系统就可以通过消息 ID 识别出是否有消息丢失。
|
||||
|
||||
这是没错的,但我奇怪的是,他为什么要在多个实例之间协调呢?他给出的理由是,这么做,是出于考虑应对将来有多实例并发场景的出现。然而事实是,我们当下的需求应对的是单实例的情况。
|
||||
|
||||
我了解情况之后,马上跟他说清楚这一点,让他先把第一步做出来。这个同事还是有些担心未来如何做扩展。我告诉他,别纠结,先把第一步做出来,等后面真的有需求,我们再考虑。同事欣然答应了。
|
||||
|
||||
其实,这个同事的技术能力非常强,如果我不拦着他,他或许真能实现出一个完美的技术方案,但正如他自己所纠结的那样,这个方案可能要花掉他很长时间。但这真的是我们想要的吗?以现阶段的目标来看,根本没有这样的需求。
|
||||
|
||||
我们一直在强调“以终为始”。所谓“终”,其实就是我们的做事目标。虽然大家工作在一起,朝着一个共同的大目标前进,但真的到了一个具体的问题上,每个人看到的目标却不尽相同。
|
||||
|
||||
我之所以能把同事从一个纠结的状态中拉出来,是因为我看到的是需求,而他看到的是一个要解决的技术问题。所以,我们俩在对目标的理解上是有根本差异的。
|
||||
|
||||
你也许会认为,我和同事之所有这样的差异,是角色上的差异,我在项目里承担的角色要重一些,而且我的工作时间比同事要长一些。但不知道你有没有想过,不同角色的差异到底在哪里呢?
|
||||
|
||||
## 角色的差异
|
||||
|
||||
作为一个在职场工作的人,每个人都有一颗渴望得到认可的心,希望自己在职业的阶梯上步步高升。假如今天就让你往上走一个台阶,比如,你原来在项目里打杂,现在成为项目的主力,或者,你已经对项目细节驾轻就熟,即将委任你为项目负责人。你是否能胜任呢?
|
||||
|
||||
你需要补充的东西是什么?换句话说,你和你职业台阶中的上一级那个人,差异到底是什么?
|
||||
|
||||
也许你会说,他比我来的时间长,或者说,他每天的主要工作就是开会。如果真的是这样,那是不是只要你凑足这个条件,就可以到达他的位置呢?显然不是。
|
||||
|
||||
**不同角色工作上真正的差异是上下文的不同。**
|
||||
|
||||
这是什么意思呢?以前面的问题为例,你在项目里打杂,你只能关注到一个具体的任务,而项目主力心目中是整个系统。**虽然写的代码都一样,但你看到的是树木,人家看到的是森林,他更能从全局思考。**
|
||||
|
||||
同样,项目负责人的工作,虽然包括在项目组内的协调,但还有一部分工作是跨项目组的,他需要考虑你们项目组与其他组的互动。所以,他工作的上下文是在各组之间,包括技术和产品等方面。
|
||||
|
||||
再上升一个层面,部门负责人要协调内部各个组,同时要考虑部门之间的协调。而公司负责人考虑的上下文甚至要跳脱公司内部,进入到行业层面。
|
||||
|
||||
你可能会问,好了,我知道不同角色的上下文有差异了,但这对我意味着什么呢?
|
||||
|
||||
我们先从工作角度看。回到前面我分享的那个故事,你可能注意到了,**我并不是靠技术能力解决了问题,而是凭借对需求的理解把这个问题绕过去了。**
|
||||
|
||||
之所以我能这样做,原因就在于我是在一个更大的上下文里工作。类似的故事在我的职业生涯中发生过无数次,许多令程序员愁眉不展的问题,换个角度可能都不是问题。
|
||||
|
||||
技术是一把利刃,程序员相信技术可以改变世界,但并不是所有问题都要用技术解决。有这样一种说法,手里有了锤子,眼里都是钉子。花大力气去解决一个可能并不是问题的问题,常常是很多程序员的盲区。
|
||||
|
||||
之所以称之为盲区,是因为很多人根本看不见它,而看不见的原因就在于上下文的缺失,也就是说,你只在程序员的维度看问题。
|
||||
|
||||
多问几个为什么,交流一下是不是可以换个做法,许多困惑可能就烟消云散了。**而能想到问这样的问题,前提就是要跳出程序员角色思维,扩大自己工作的上下文。**
|
||||
|
||||
虽然我不是项目主力,但不妨碍我去更深入地了解系统全貌;虽然我不是项目负责人,但不妨碍我去了解系统与其他组的接口;同样,虽然我不是项目经理,但我可以去了解一下项目经理是怎样管理项目的;虽然我不是产品经理,但了解一个产品的设计方法对我来说也是有帮助的。
|
||||
|
||||
**当你对软件开发的全生命周期都有了认识之后,你看到的就不再是一个点了,而是一条线。**与别人讨论问题的时候,你就会有更多的底气,与那些只在一个点上思考的人相比,你就拥有了降维攻击的能力。
|
||||
|
||||
现在你知道为什么你的工作总能让老板挑出毛病了吧!没错,工作的上下文不同,看到的维度差异很大。单一维度的思考,在多维度思考者的眼里几乎就是漏洞百出的。
|
||||
|
||||
当扩大了自己工作的上下文时,我们的目标就不再局限于一个单点,而是会站在更高的维度去思考,解决问题还有没有更简单的方案。许多在低一级难以解决的问题,放到更大的上下文里,根本就不是问题。
|
||||
|
||||
我的职业生涯中经常遇到这样的情况,在一个特定的产品设计下,我总觉得设计的技术方案有些不优雅的地方,而只要产品设计微调一下,技术方案一下子就会得到大幅度提升。在这种情况下,我会先去问产品经理,是否可以这样调整。只要不是至关重要的地方,产品经理通常会答应我的要求。
|
||||
|
||||
## 在更大的上下文工作
|
||||
|
||||
扩展自己工作的上下文,目光不再局限于自己的一亩三分地,还可以为自己的职业发展做好布局。在这个方面,我给你分享一个不太成功的案例,就是我自己的故事。
|
||||
|
||||
我是属于愚钝型的程序员,工作最初的几年,一直把自己限定在程序员的上下文里,最喜欢的事就是安安静静地写代码,把一个系统运作机理弄清楚会让我兴奋很长一段时间。
|
||||
|
||||
我的转变始于一次机缘巧合,当时有一个咨询项目,负责这个项目的同事家里有些事,需要一个人来顶班,公司就把我派去了。
|
||||
|
||||
到了咨询项目中,我自己习惯的节奏完全乱掉了,因为那不是让代码正常运作就可以解决的问题,更重要的是与人打交道。
|
||||
|
||||
有很长一段时间,我一直处于很煎熬的状态,感谢客户没有把我从这个项目赶出来,让我有了“浴火重生”的机会。
|
||||
|
||||
为了让自己从这种煎熬的状态中摆脱出来,我必须从代码中走出来,尽量扩大自己思考的边界。经过一段时间的调整,我发现与人打交道也没那么难,我也能更好地理解一个项目运作的逻辑,因为项目运作本质上就是不同人之间的协作。
|
||||
|
||||
突破了自己只愿意思考技术的限制,世界一下子宽阔了许多。所以,后来才有机会更多地走到客户现场,看到更多公司的项目运作。虽然我工作过的公司数量并不多,但我却见过很多公司是如何工作的。
|
||||
|
||||
再后来,我有机会参与一个新的分公司建设工作中,这让我有了从公司层面进行思考的角度。对于员工招聘和培养,形成了自己一套独立的思考。
|
||||
|
||||
这些思考在我创业的过程中,帮我建立了一支很不错的团队。而创业的过程中,我又有了更多机会,去面对其他公司的商务人员,从而建立起一个更大的上下文,把思考从公司内部向外拓展了一些。
|
||||
|
||||
回过头来看自己的生涯时,我发现,因为不愿意拓展自己的上下文,我其实错过了很多职业发展的机会。所幸我还有机会突破自己,让自己走出来,虽然走的速度不如理想中快,但至少一直在前进,而不是原地打转。这也是我告诫你一定要不断扩大自己工作上下文的原因。
|
||||
|
||||
机会总是垂青那些有准备的人,尤其在公司规模不大的时候,总有一些跳跃式的发展机会。
|
||||
|
||||
我见过有人几年之内从程序员做到公司中国区负责人,只是因为起初公司规模不大,而他特别热心公司的很多事情,跳出了固定角色的思维。所以,当公司不断发展,需要有人站出来的时候,虽然没有人是完全合格的,但正是他的热心,让他有了更多的维度,才有机会站到了前排。
|
||||
|
||||
当然,随着公司规模越来越大,这种幅度极大的跳跃是不大可能的。江湖上流传着一个华为的故事,一个新员工给任正非写了封万言书,大谈公司发展,任正非回复:“此人如果有精神病,建议送医院治疗,如果没病,建议辞退。”
|
||||
|
||||
因为一旦公司规模大了,你很难了解更大的上下文,很多关于公司的事情,你甚至需要从新闻里才知道。
|
||||
|
||||
本质上,一个人能在自己的工作范围内多看到两三级都是有可能的。在公司规模不大时,从基层到老板没有太多层级,跳跃就显得很明显,而公司一大,层级一多,从低到顶的跳跃就不太可能了,但跨越级别跳跃是可能的。
|
||||
|
||||
所以我希望你跳出程序员思维,这不仅仅是为了工作能够更高效,也是希望你有更好的发展机会。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
程序员总喜欢用技术去解决一切问题,但很多令人寝食难安的问题其实根本不是问题。之所以找不出更简单的解决方案,很多时候原因在于程序员被自己的思考局限住了。
|
||||
|
||||
不同角色工作真正的差异在于上下文的差异。在一个局部上下文难以解决的问题,换到另外一个上下文甚至是可以不解决的。所以说无论单点有多努力也只是局部优化,很难达到最优的效果。
|
||||
|
||||
想把工作做好,就需要不断扩大自己工作的上下文,多了解一下别人的工作逻辑是什么样的,认识软件开发的全生命周期。
|
||||
|
||||
扩大自己的上下文,除了能对自己当前的工作效率提高有帮助,对自己的职业生涯也是有好处的。随着你看到的世界越来越宽广,得到的机会也就越来越多。
|
||||
|
||||
如果今天的内容你只记住一件事,那请记住:**扩大自己工作的上下文,别把自己局限在一个“程序员”的角色上。**
|
||||
|
||||
最后,我想请你分享一下,在你的工作中,有哪些因为你扩大了工作上下文而解决的问题呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
125
极客时间专栏/geek/10x程序员工作法/以终为始/08 | 为什么说做事之前要先进行推演?.md
Normal file
125
极客时间专栏/geek/10x程序员工作法/以终为始/08 | 为什么说做事之前要先进行推演?.md
Normal file
@ -0,0 +1,125 @@
|
||||
<audio id="audio" title="08 | 为什么说做事之前要先进行推演?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/09/0d/0955ac54f899da5c6d75ac26c746910d.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
经过前面的学习,想必你已经对“以终为始”这个原则有了自己的理解。你知道接到一个任务后,要做的不是立即埋头苦干,而是要学会思考,找出真正的目标。那目标明确之后,我们是不是就可以马上开始执行了呢?
|
||||
|
||||
先不着急给出你的答案,今天的内容从一个技术任务开始。
|
||||
|
||||
## 一个技术任务
|
||||
|
||||
你现在在一家发展还不错的公司工作。随着业务的不断发展,原来采用的关系型数据库越发无法满足快速的变化。于是,项目负责人派你去做个技术选型,把一部分业务迁移到更合适的存储方式上。
|
||||
|
||||
经过认真的调研和思考,你给负责人提出了自己的建议,“我们选择 MongoDB。”出于对你的信任,负责人无条件地同意了你的建议,你获得了很大的成就感。
|
||||
|
||||
在你的喜悦尚未消退时,负责人进一步对你委以重任,让你来出个替代计划。替代计划?你有些不相信自己的耳朵,嘴里嘟囔着:“把现在存到数据库的内容写到 MongoDB 不就成了,我就一个表一个表地替换。难道我还要把哪天替换哪个表列出来吗?”
|
||||
|
||||
刚刚还对你欣赏有加的负责人,脸色一下子沉了下来。“只有表改写吗?”他问你。你一脸懵地看着他,心里想,“不然呢?”
|
||||
|
||||
“上线计划呢?”负责人问。
|
||||
|
||||
“我还一行代码都没写呢?”你很无辜地看着负责人。
|
||||
|
||||
“我知道你没写代码,我们就假设代码已经写好了,看看上线是怎样一个过程。”
|
||||
|
||||
“不是发新版本就好了吗?”你还是不知道负责人到底想说什么。
|
||||
|
||||
“你能确定新版代码一定是对的吗?”
|
||||
|
||||
虽然你已经叱咤编程很多年,但作为老江湖,一听这话反而是有些怯的。“不能。”你痛快地承认了。
|
||||
|
||||
“一旦出错,我们就回滚到上一个版本不就成了。”常规的处理手段你还是有的。
|
||||
|
||||
“但数据已经写到了不同的存储里面,查询会受到影响,对不对?”负责人一针见血。
|
||||
|
||||
“如果这个阶段采用两个数据存储双写的方案,新代码即便出问题,旧存储的代码是正常,我们还有机会回滚。”你一下子就给出了一个解决方案,咱最不怕出问题了。
|
||||
|
||||
“对。”负责人认同了你的做法,一副没看错人的神情。“让你出上线方案,就是为了多想想细节。”
|
||||
|
||||
你终于明白了负责人的良苦用心,也就不再大意。很快,你就给出了一份更详尽的上线方案。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/67/2c/6700b6a9aea51d3a2500f0c42e15df2c.jpg" alt="">
|
||||
|
||||
你把这个方案拿给负责人看,信心满满,觉得自己够小心,一步一步做,没有任何问题。但负责人看了看你的上线计划,眉头逐渐锁了起来,你知道负责人还是不满意,但不知道还差在哪里?
|
||||
|
||||
“原有的数据怎么办?”负责人又问了一个问题。你一下子意识到,确实是问题。“没有原有数据,一旦查询涉及到原有数据,查询的结果一定是错的。所以,还应该有一个原有数据的迁移任务。”你尴尬地笑了笑。
|
||||
|
||||
负责人微笑着看着你。“好吧,从我的角度看差不多了,你可以再仔细想想。然后,排一个开发任务出来吧!”
|
||||
|
||||
你当然不会辜负负责人的信任,很快排出了开发任务。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/98/68/98d8540e65bf361fdf4c882c39e89068.jpg" alt="">
|
||||
|
||||
看着排出的任务,你忽然困惑了。最开始只是想写个读写新库的组件,怎么就多出这么些任务。此外,你还很纳闷为什么负责人总是能找到这么多问题。
|
||||
|
||||
## 一次个人回顾
|
||||
|
||||
你想起之前的工作里有过类似的场景,那个负责人也是让你独立安排任务。通常,你最初得到的也是一个简单的答案,从当时的心境上看,你是很有成就感的。
|
||||
|
||||
只是后来的故事就不那么美妙了,上线时常常出现各种问题,你和其他同事们手忙脚乱地处理各种异常。当时顶着巨大压力解决问题的场景,你依然记忆犹新。解决完问题离开公司时,天空已经泛起鱼肚白。
|
||||
|
||||
而似乎自从加入了现在的公司,这种手忙脚乱的场景少了很多。你开始仔细回想现在这个负责人在工作中的种种。从给大家机会的角度来看,这个负责人确实不错,他总会让一个人独立承担一项任务。只不过,他会要求大家先将任务分解的结果给他看。
|
||||
|
||||
拿到组里任何一个人的开发列表之后,他都会问一大堆问题,而且大多数情况下,他都会问到让人哑口无言。说句心里话,每次被他追问心里是挺不舒服的,就像今天这样。
|
||||
|
||||
本来在你看来挺简单的一件事,经过他的一系列追问,变成了一个长长的工作列表,要做的事一下子就变多了。毕竟谁不愿意少做点活呢!
|
||||
|
||||
不过,你不得不承认的一点是,加入这个公司后,做事更从容了。你知道无论做的事是什么,那些基本的部分是一样的,差别体现在事前忙,还是事后忙,而现在这家公司属于事前忙。于是,你开始把前一家公司上线时所忙碌的内容,和现在负责人每次问的问题放在一起做对比。
|
||||
|
||||
这样一梳理,你才发现,原来负责人问的问题,其实都是与上线相关的问题。包括这次的问题也是,上线出问题怎么办,线上数据怎么处理等等。
|
||||
|
||||
你突然意识到一个关键问题,其实负责人每次问的问题都是类似的,无论是你还是其他人,他都会关心上线过程是什么样,给出一个上线计划。即便我们还一行代码都没有,他依然会让我们假设如果一切就绪,应该怎样一步一步地做。
|
||||
|
||||
你终于明白了,之前的项目之所以手忙脚乱,因为那时候只想了功能实现,却从来没考虑过上线,而且问题基本上都是出在上线过程中的。你想到了上次参加一个社区活动,其中的一个大牛提到了一个说法:“**最后一公里**”。
|
||||
|
||||
想到这,你赶紧上网搜了一下“最后一公里”,这个说法指的是完成一件事,在最后也是最关键的步骤。你才意识到,“最后一公里”这个说法已经被应用在很多领域了,负责人就是站在“最后一公里”的角度来看要发生的事情。
|
||||
|
||||
嗯,你学会了一招,以后你也可以站在“最后一公里”去发现问题了,加上你已经具备的推演能力,给出一个更令人满意的任务列表似乎更容易一些。
|
||||
|
||||
把这个问题想清楚了,你重新整理了自己的思路,列出了一个自己的问题解决计划。
|
||||
|
||||
- 先从结果的角度入手,看看最终上线要考虑哪些因素。
|
||||
- 推演出一个可以一步一步执行的上线方案,用前面考虑到的因素作为衡量指标。
|
||||
- 根据推演出来的上线方案,总结要做的任务。
|
||||
|
||||
不过,更令你兴奋的是,你拥有了一个看问题的新角度,让自己可以再上一个台阶,向着资深软件工程师的级别又迈进了一步。
|
||||
|
||||
## 通往结果之路
|
||||
|
||||
好了,这个小故事告一段落。作为我们专栏的用户,你可能已经知道了这个故事要表达的内容依旧是“以终为始”。关于“以终为始”,我们前面讲的内容一直是看到结果,结果是重要的。然而,**通向结果的路径才是更重要的。**
|
||||
|
||||
这个世界不乏有理想的人,大多数人都能看到一个宏大的未来,但这个世界上,真正取得与这些理想相配成绩的人却少之又少,大部分人都是泯然众生的。
|
||||
|
||||
宏大理想是一个目标,而走向目标是需要一步一个脚印地向前走的。唐僧的目标是求取真经,但他依然用了十几年时间才来到大雷音寺。唐僧西天取经有一个极大的优势,他达成目标的路径是清晰的,从长安出发,向着西天一路前行就好。
|
||||
|
||||
**对比我们的工作,多数情况下,即便目标清晰,路径却是模糊的。**所以,不同的人有不同的处理方式。有些人是走到哪算哪,然后再看;有些人则是先推演一下路径,看看能走到什么程度。
|
||||
|
||||
在我们做软件的过程中,这两种路径所带来的差异,已经在前面的小故事里体现出来了。一种是前期其乐融融,后期手忙脚乱;一种是前面思前想后,后面四平八稳。我个人是推崇后一种做法的。
|
||||
|
||||
或许你已经发现了,这就是我们在“以终为始”主题的开篇中,提到的第一次创造或者智力上的创造。如果不记得了,不妨回顾一下[《02 | 以终为始:如何让你的努力不白费?》](http://time.geekbang.org/column/article/74834)。
|
||||
|
||||
实际上,早就有人在熟练运用这种思想了。**在军事上,人们将其称为沙盘推演,或沙盘模拟。**军队通过沙盘模拟军事双方的对战过程,发现战略战术上存在的问题。这一思想也被商界借鉴过来,用来培训各级管理者。
|
||||
|
||||
这个思想并不难理解,我们可以很容易地将它运用在工作中的很多方面。比如:
|
||||
|
||||
- 在做一个产品之前,先来推演一下这个产品如何推广,通过什么途径推广给什么样的人;
|
||||
- 在做技术改进之前,先来考虑一下上线是怎样一个过程,为可能出现的问题准备预案;
|
||||
- 在设计一个产品特性之前,先来考虑数据由谁提供,完整的流程是什么样的。
|
||||
|
||||
最后这个例子也是软件开发中常遇到的,为数不少的产品经理在设计产品时,只考虑到用户界面是怎样交互的,全然不理会数据从何而来,造成的结果是:累死累活做出来的东西,完全跑不通,因为没有数据源。
|
||||
|
||||
很多时候,我们欠缺的只是在开始动手之前做一遍推演,所以,我们常常要靠自己的小聪明忙不迭地应对可能发生的一切。
|
||||
|
||||
希望通过今天的分享,能让你打破手忙脚乱的工作循环,让自己的工作变得更加从容。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
即便已经确定了自己的工作目标,我们依然要在具体动手之前,把实施步骤推演一番,完成一次头脑中的创造,也就是第一次创造或智力上的创造。这种思想在军事上称之为沙盘推演,在很多领域都有广泛地应用。
|
||||
|
||||
在软件开发过程中,我们就假设软件已经就绪,看就绪之后,要做哪些事情,比如,如何上线、如何推广等等,这样的推演过程会帮我们发现前期准备的不足之处,进一步丰富我们的工作计划。为了不让我们总在“最后一公里”摔跟头,前期的推演是不可或缺的,也是想让团队进入有条不紊状态的前提。
|
||||
|
||||
如果今天的内容你只记住一件事,那请记住:**在动手做一件事之前,先推演一番。**
|
||||
|
||||
最后,我想请你思考一下,如果把你在做的事情推演一番,你会发现哪些可以改进的地方呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
102
极客时间专栏/geek/10x程序员工作法/以终为始/09 | 你的工作可以用数字衡量吗?.md
Normal file
102
极客时间专栏/geek/10x程序员工作法/以终为始/09 | 你的工作可以用数字衡量吗?.md
Normal file
@ -0,0 +1,102 @@
|
||||
<audio id="audio" title="09 | 你的工作可以用数字衡量吗?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b7/fd/b7ecd328cab92d9a04a82e7767da29fd.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
今天的分享从日常工作开始。请你回想一下,你每天到岗之后做的第一件事是什么呢?然后你来猜猜我的答案是什么?你可能猜不到,我每天到公司之后,第一件正事是**看数字**。
|
||||
|
||||
我现在服务于一家做数字资产的公司,我们提供的是一个24小时运行的服务。从加入这家公司的第一天开始,公司的人就给我不断灌输一个重要理念——看数字。在我座位的正前方,摆着一个巨大的显示器,上面展示着各种不断变换的曲线、柱状图和数字,这些数字反映的是各种系统运行的指标。
|
||||
|
||||
我们就是每天看着这些指标,来发掘一些线上系统问题的,一旦某些指标出现自己不能理解的异常,就要着手调查。
|
||||
|
||||
你或许会纳闷,我们不是在探讨“以终为始”吗?怎么变成了一个关于监控的讨论呢?别急,我们确实还在讨论“以终为始”,因为数字是诠释“终”的最好方式。
|
||||
|
||||
我们前面讨论了各种“终”,但通常靠语言定义的“终”,或多或少都会存在一些模糊的地方,也就是容易产生误解的地方。而数字却是一个明明白白的“终”。比如,测试覆盖率要求100%,即便你做到了99.9%,不达标就是不达标,没什么好说的,说破天也是不达标。
|
||||
|
||||
再比如,之前内容我们讲到精益创业时,提到了一个重要的反馈循环:开发(build)-测量(measure)-认知(learn)。你会发现,在这个循环中,开发(build)是可控的,认知(learn)必须是得到反馈之后才能有的。所以,这里面最需要我们回答的问题是测量(measure)。而这里的测量,最好的检验标准当然就是数字。
|
||||
|
||||
或许你会说,数字我们都很熟,还用讲吗?不过在我看来,你还真的未必习惯于使用数字。
|
||||
|
||||
## 熟悉而陌生的数字
|
||||
|
||||
从进化的角度来看,人们做事更多是依赖于直觉的。数字,是人类在非洲大草原上奔跑了许久之后才创造出来的东西。著名科普著作《从一到无穷大》的开篇有这么一个故事:
|
||||
|
||||
>
|
||||
<p>两个匈牙利贵族决定做一次数数的游戏,看谁说出的数字大。<br>
|
||||
一个贵族说:“好,那你先说吧!”<br>
|
||||
另一个绞尽脑汁想了好几分钟,说了一个数字:“3”。<br>
|
||||
现在轮到第一个贵族苦思冥想了,他想了一刻钟,然后说:“好吧,你赢啦!”</p>
|
||||
|
||||
|
||||
这个故事听起来有些荒诞,但一些非洲探险家证实,在某些原始部族里,不存在比3大的数词。如果问他们有几个孩子,而这个数字大于3的话,他就会回答“许多个”。
|
||||
|
||||
虽然我们中华民族是一个重视教育的民族,现在也都承认数学是一门重要的基础知识。但我们还是习惯性地观其大略,因为在日常生活领域里,除了买东西发工资,需要对数字斤斤计较的场合并不多。
|
||||
|
||||
历史的车轮在不停地滚滚向前,当今社会所面临的复杂度已经远远超过凭直觉就能把事情做好的程度。
|
||||
|
||||
**一些人说,自己靠直觉就能把事情做好,其实这是一种误解,因为那种所谓的直觉,通常是一种洞见(Insight),洞见很大程度上依赖于一个人在一个领域长期的沉淀和积累,而这其实是某种意义上的大数据。**
|
||||
|
||||
我们都在说,人类马上就要进入智能时代了。之所以这么说,主要是现在人工智能技术不断地向前发展着。而人工智能作为一门在50年代就已经问世的技术,直到最近几年才得到大踏步的前进,主要归功于基础设施的发展。
|
||||
|
||||
在人工智能领域,基于统计的方法早就在学术界提了出来,但由于当时的技术条件所限,人们的数据采集和存储能力都有限,当时的“大”数据和今天的大数据完全不是一个量级的概念。
|
||||
|
||||
直到进入到互联网时代,随着处理数据量的增加,基础设施在不断地拓展,进而促使人们采集更多的数据,这个正向反馈也造就了今天的大数据。
|
||||
|
||||
原本因为缺乏足够数据支撑,难以施展拳脚的 AI 算法,在今天一下子有了足够的表演空间,从一个边缘角色成为了舞台中心的主角。
|
||||
|
||||
今天谈到人工智能,人们主要会谈三件事:**算法、算力和数据。**算法几乎是行业共有的,而算力在云计算普及的今天也不再是稀缺资源,所以,数据几乎成了兵家必争之“物”。于是,我们看到的现象是各个公司都在努力地搜集各种数据,让数据成为自己的竞争力。所以,在大方向上,数据采集是一个行业共识。
|
||||
|
||||
但是,作为这个世界上最了解数据价值的一批人,我们程序员只是在努力地把数据用于不断改善别人的生活,而对于自己日常工作的改善,则思考得少之又少。
|
||||
|
||||
我们更习惯的讨论方式依然是靠直觉。比如:增加了这个特性**可能**会让用户增长,做了这个调整**应该**会让系统的压力变小。
|
||||
|
||||
在一些简单的情形下,或者说大家信息对称、知识背景相差无几的情况下,这样的讨论是很容易得到认同的。**而当事情复杂到一定程度时,简单地靠感觉是很难让人相信的。**
|
||||
|
||||
所以,在我们的工作中,经常会发生的一个现象是,一个人说,我觉得这个有作用,另一个人说,我觉得那个没有。几个“觉得”下来,双方就开始进入了隔空对话的环节,谁也无法说服谁。
|
||||
|
||||
如果换成用数字的方式进行讨论,效果就会更好。有一次,为了改善用户体验,我们准备进行一次主页改版。产品团队希望在主页上加上大量的内容,而开发团队则认为太多的内容会导致主页加载变慢,进而造成用户体验下降。
|
||||
|
||||
正当这个对话即将进入“空对空”的讨论之时,我们找到了一个测量指标:主页加载速度。只要保证主页加载速度,产品团队就可以按照自己的理解来做调整。于是,一个即将不可挽回的讨论,变成了在一定约束条件下的讨论,双方谁也不再思维发散,讨论就能继续推进了。
|
||||
|
||||
如果你认同了数据本身的价值,那么再结合“以终为始”的理念,我们就应该在着手做一件事之前,先来想怎么去测量。无论是在讨论产品特性,还是功能开发,“信口雌黄”是容易的,落到数字上,人们就会多想一下,这是对彼此的约束。
|
||||
|
||||
## 从数字出发
|
||||
|
||||
前面的内容我们都是在说应该重视测量指标,重视数字。接下来,我就分享下几个我在实际工作中运用数字的案例,让你看看习惯用数字去思考问题之后,会拓宽哪些思考的维度。
|
||||
|
||||
首先是基于数字进行技术决策。有一次,我们打算做一个技术改进,给系统增加一些缓存,减轻数据库的压力。大家一起设计了两个技术方案。如果查询是特定的,我们就准备简单地在某些方法上加上缓存;如果查询是五花八门的,就准备用一个中间件,使用它的查询方案。
|
||||
|
||||
系统现在的情况到底是什么样的呢?我们发现并不能立刻回答这个问题。于是,大家决定在系统中增加一些统计指标,让数据给我们答案。然后根据数据反映出的情况,进行具体的决策。
|
||||
|
||||
其次是一个准备上线的案例。当时,我们是要做一个影响力比较大的系统升级,因为这是一个系统的关键模块,上下游系统都会受到影响。
|
||||
|
||||
谁也不能确定哪个模块会在上线过程中出问题。于是,设计了一个全新的数据面板,将相关的几个模块的核心指标都摆在上面。而我们要做的就是在上线的同时,观察这些指标的变化。
|
||||
|
||||
所幸的是,这次上线影响不大,几个指标一路平稳,而大家的信心就源自这些提前准备好的指标。
|
||||
|
||||
再次,看一个从数字中发现问题的例子。由于各种历史原因,我们的重点指标面板上,会有几个指标表示的是类似的东西。
|
||||
|
||||
比如,某个模块的处理能力,一个指标是站在这个模块内部度量的,而另一个指标则是由这个模块上下游系统度量的。在大多数情况下,它们的表现是一致的。结果有一天两者突然出现了很大的差异,内部度量表现依然良好,而外部度量则出现了很大的延迟。
|
||||
|
||||
于是,我们开始追问为什么。一路追寻下来,我们发现,是这个模块内部需要定期将内部状态持久化下来,而在那个时间段内,这个模块就会停止从上游读取数据。所以,在内部看一切正常,而外部看则延迟较大。随后,我们找到了方案,解决了这一问题。
|
||||
|
||||
最后再说一个行业中的例子,据我所知,行业里的某些公司已经开始做所谓的 AIOps,也就是通过人工智能的方式,从数据中,发现更多运维的问题。无论哪种做法,都是为了**从数字中发现问题,让系统更稳定。**
|
||||
|
||||
我的一个同事有个观点非常值得玩味,他说,从数字上看,好的系统应该是“死水一潭”。
|
||||
|
||||
我是赞同这个观点的,因为出现波动尤其是大幅度波动,又不能给出一个合理解释的话,就说明系统存在着隐患。而让系统稳定,正是我们工作的一个重要组成部分。
|
||||
|
||||
回到这一讲的开头,我说每天工作中的一个重要组成部分就是看数字,其实就是在尝试着从数字的趋势中发现问题,如今团队已经习惯了“给个数字看看”这样的沟通方式,内部扯皮的机会也相应地减少了一些。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
随着智能时代的来临,人类社会开始逐渐认识到数据的重要性。但我们这群 IT 人在通过数据为其他人服务的同时,却很少把数字化的思维带到自己的工作范围内。这也是工作中很多“空对空”对话的根源所在。
|
||||
|
||||
结合着“以终为始”的思考,如果我们可以在一开始,就设计好测量工作有效性的指标,那么就可以更有目的性地去工作了。
|
||||
|
||||
而如果我们习惯了用数字去思考,就可以在很多方面让数字帮助我们。我举了几个小例子,比如:基于数据进行技术决策、预先设定系统指标,以及发现系统中的问题等等。希望你也可以把数字思维带到你的日常工作中。
|
||||
|
||||
如果今天的内容你只记住一件事,那请记住:**问一下自己,我的工作是不是可以用数字衡量。**
|
||||
|
||||
最后,我想请你分享一下,你的工作中,有哪些应用数字解决问题的场景呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
137
极客时间专栏/geek/10x程序员工作法/以终为始/10 | 迭代0: 启动开发之前,你应该准备什么?.md
Normal file
137
极客时间专栏/geek/10x程序员工作法/以终为始/10 | 迭代0: 启动开发之前,你应该准备什么?.md
Normal file
@ -0,0 +1,137 @@
|
||||
<audio id="audio" title="10 | 迭代0: 启动开发之前,你应该准备什么?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/1d/71/1d9390b6ee712937be3e54928fc99671.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
关于“以终为始”,我们已经从各个方面讲了很多。你或许会想,既然我们应该有“以终为始”的思维,那么在项目刚开始,就把该准备的东西准备好,项目进展是不是就能稍微顺畅一点儿呢?
|
||||
|
||||
是这样的,事实上这已经是一种常见的实践了。今天,我们就来谈谈在一开始就把项目准备好的实践:**迭代0**。
|
||||
|
||||
为什么叫迭代0呢?在“敏捷”已经不是新鲜词汇的今天,软件团队对迭代的概念已经不陌生了,它就是一个完整的开发周期,各个团队在迭代上的差别主要是时间长度有所不同。
|
||||
|
||||
一般来说,第一个迭代周期就是迭代1,然后是迭代2、迭代3,依次排列。从名字上你就不难发现,所谓迭代0,就是在迭代1之前的一个迭代,所以,我们可以把它理解成开发的准备阶段。
|
||||
|
||||
既然迭代0是项目的准备阶段,我们就可以把需要提前准备好的各项内容,在这个阶段准备好。事先声明,**这里给出的迭代0,它的具体内容只是基本的清单**。在了解了这些内容之后,你完全可以根据自己项目的实际情况,扩展或调整这个清单。
|
||||
|
||||
好,我们来看看我为你准备的迭代0清单都包含了哪些内容。
|
||||
|
||||
## 需求方面
|
||||
|
||||
### 1. 细化过的迭代1需求
|
||||
|
||||
一个项目最重要的是需求,而在迭代0里最重要的是,弄清楚第一步怎么走。当我们决定做一个项目时,需求往往是不愁的,哪些需求先做、哪些需求后做,这是我们必须做的决策。迭代0需要做的事,就是把悬在空中的内容落到地上。
|
||||
|
||||
在需求做好分解之后,我们就会有一大堆待开发的需求列表。注意,这个时候需求只是一个列表,还没有细化。因为你不太可能这个时候把所有的内容细化出来。如果你做过 Scrum 过程,你的 backlog 里放的就是这些东西。
|
||||
|
||||
然后,我们要根据优先级从中挑出迭代1要开发的需求,优先级是根据我们要完成的最小可行产品(minimum viable product,MVP)来确定的,这个最小可行产品又是由我们在这个迭代里要验证的内容决定的。一环扣一环,我们就得到了迭代1要做的需求列表。
|
||||
|
||||
确定好迭代1要做的需求之后,接下来就要把这些需求细化了,细化到可执行的程度。前面讲[用户故事](http://time.geekbang.org/column/article/75100)时,我们已经说过一个细化需求应该是什么样子的,这里的关键点就是要把验收标准定义清楚。
|
||||
|
||||
所以,我们要在迭代0里,根据优先级来确定迭代1要做的需求,然后进行细化。
|
||||
|
||||
### 2.用户界面和用户交互
|
||||
|
||||
如果你的项目是一个有用户界面的产品,给出用户界面,自然也是要在迭代0完成的。另外,还有一个东西也应该在迭代0定义清楚,那就是用户交互。
|
||||
|
||||
我见过很多团队只给出用户界面,然后,让前端程序员或者 App 程序员根据界面去实现。程序员实现功能没问题,但定义交互并不是程序员这个角色的强项,它应该是需求的一部分。
|
||||
|
||||
如何让用户用着舒服,这是一门学问。我们在市面上看到很多难用的网站或 App,基本上都是程序员按照自己习惯设计出来的。
|
||||
|
||||
现如今,我们可以很容易地在市面上找到画原型的工具,某些工具用得好的话,甚至可以达到以假乱真的地步。如果能再进一步的话,甚至可以用一些模拟服务器的工具,把整个交互的界面都做出来。作为 Moco 这个模拟服务器的开发者,我很清楚,一个原型可以达到怎样的高度。
|
||||
|
||||
所以,一个有用户界面的项目需要在迭代0中给出用户界面和用户交互。
|
||||
|
||||
## 技术方面
|
||||
|
||||
### 1. 基本技术准备
|
||||
|
||||
技术方面,需要在项目一开始就准备好的事比较多。其中有一些是你很容易想到的,比如:在进入迭代1开始写代码之前,我们需要确定技术选型,确定基本的技术架构等等。也许你还能想到,数据库表结构也是这个阶段应该准备的。
|
||||
|
||||
确实,这些东西都应该是在一个项目初期准备的,也是大家容易想到的。接下来,我来补充一些大家可能会遗漏的。
|
||||
|
||||
- 持续集成
|
||||
|
||||
对于持续集成,我们通常的第一反应是搭建一个持续集成服务器。没错,但还不够。这里的重点其实是构建脚本。因为持续集成服务器运行的就是构建脚本。
|
||||
|
||||
那要把什么东西放在构建脚本里呢?最容易想到的是编译打包这样的过程。感谢现在的构建工具,它们一般还会默认地把测试也放到基本的构建过程中。
|
||||
|
||||
但仅有这些还是不够,我们还会考虑把更多的内容放进去,比如:构建 IDE 工程、代码风格检查、常见的 bug 模式检查、测试覆盖率等等。
|
||||
|
||||
持续集成还有一个很重要的方面,那就是持续集成的展示。为什么展示很重要?当你的持续集成失败时,你怎么发现呢?
|
||||
|
||||
一个简单的解决方案是:摆个大显示器,用一个 CI Monitor 软件,把持续集成的状态展示在上面。更有甚者,会用一个实体的灯,这样感官刺激更强一些。
|
||||
|
||||
在“以终为始”这个模块中,我们提到集成的部分时,只讲了要做持续集成,后面我们还会再次讲到持续集成,和你说说持续集成想做好,应该做成什么样子。
|
||||
|
||||
- 测试
|
||||
|
||||
测试是个很有趣的东西,程序员对它又爱又恨。一般来说,运行测试已经成为现在很多构建工具的默认选项,如果你采用的工具没有这个能力,建议你自己将它加入构建脚本。
|
||||
|
||||
让你为一个项目补测试,那是一件非常痛苦的事,如果在一开始就把测试作为规范加入进去的话,那么在边开发边写测试的情况下,相对来说,写测试痛苦度就低多了,团队成员也就容易遵守这样的开发规范。
|
||||
|
||||
**把测试当作规范确定下来的办法就是把测试覆盖率加入构建脚本。**
|
||||
|
||||
大多数团队提起测试,尤其是开发者测试,多半想到的都是单元测试和集成测试。把整个系统贯穿在一起的“端到端测试”却基本上交给其他人来做,也有不少团队是交给测试团队专门开发的特定程序来做。
|
||||
|
||||
在今天的软件开发中,有一些更适合描述这类测试的方法,比如BDD,再比如Specification by Example。你可以简单地把它们理解成一种描述系统行为的方式。还有一点做得好的地方是,有一些软件框架很好地支持了这种开发方法,比如Cucumber。如果你有这种测试,不妨也将它加入构建脚本。
|
||||
|
||||
### 2.发布准备
|
||||
|
||||
- 数据库迁移
|
||||
|
||||
如果你做的是服务器端开发,多半离不开与数据库打交道。只要是和数据库打交道,强烈建议你把数据库变更管理起来。
|
||||
|
||||
管理数据库变更的方式曾是很多团队面临的困扰。好在现在已经有了很多工具支持,比如,我最近喜欢的工具是 flyway,它可以把每一次数据库变更都当作一个文件。这样一来,我们就可以把数据库变更放到版本控制工具里面,方便进行管理。
|
||||
|
||||
管理变更有几种不同的做法,一种是每个变更是一个文件,一种是每一次发布是一个文件。各有各的好处,你可以根据需要,自行选择。
|
||||
|
||||
- 发布
|
||||
|
||||
技术团队擅长做功能开发,但上线部署或打包发布却是很多团队在前期最欠考量的内容,也是很多团队手忙脚乱的根源。
|
||||
|
||||
如果一开始就把部署或发布过程自动化,那么未来的生活就会轻松很多。如果你采用的是 Docker,就准备好第一个可以部署的 Dockerfile;如果是自己部署,就编写好 Shell 脚本。
|
||||
|
||||
其实你会发现,上面提到的所有内容即便不在迭代0做,在项目的各个阶段也会碰到。而且一般情况下,即便你在迭代0把这些工作做好了,后续依然要不断调整。但我依然建议你在迭代0把这些事做好,因为它会给你的项目定下一个基调,一个自动化的基调。
|
||||
|
||||
## 日常工作
|
||||
|
||||
最后,我们来看一下,如果在迭代0一切准备就绪,你在迭代1应该面对的日常工作是什么样的。
|
||||
|
||||
>
|
||||
你从已经准备好的任务卡中选了一张,与产品经理确认了一些你不甚清楚的几个细节之后,准备实现它。你从代码仓库更新了最新的代码,然后,开始动手写代码。
|
||||
|
||||
|
||||
>
|
||||
这个任务要在数据库中添加一个字段,你打开开发工具,添加了一个数据库迁移文件,运行了一下数据库迁移工具,一切正常,新的字段已经出现在数据库中。
|
||||
|
||||
|
||||
>
|
||||
这个任务很简单,你很快实现完了代码,运行一下构建脚本,代码风格检查有个错误,你顺手修复了它。再运行,测试通过了,但测试覆盖率不够,你心里说,偷懒被发现了。不过,这是小事,补几个测试就好了。一切顺利!
|
||||
|
||||
|
||||
>
|
||||
你又更新了一下代码,有几个合并的问题。修复之后,再运行构建脚本,全过,提交代码。
|
||||
|
||||
|
||||
>
|
||||
你伸了一个懒腰,完成任务之后,你决定休息片刻。忽然,持续集成的大屏幕红了,你的提交搞砸了。你立刻看了一下代码,有一个新文件忘提交了,你吐了一下舌头赶紧把这个文件补上了。不一会儿,持续集成大屏幕又恢复了代表勃勃生机的绿色。
|
||||
|
||||
|
||||
>
|
||||
你休息好了,准备开始拿下下一个任务。
|
||||
|
||||
|
||||
这就是一个正常开发该有的样子,在迭代0时,将准备工作做好,后续你的一切工作就会变得井然有序,出现的简单问题会很快地被发现,所有人都在一种有条不紊的工作节奏中。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
在这一讲中,我给你介绍了迭代0的概念,它是在正式开发迭代开始之前,进行一些基础准备的实践。我给了一份我自己的迭代0准备清单,这份清单包含了需求和技术两个大方面,你可以参照它设计你自己的迭代0清单。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f5/99/f52c136533bb782d51f8097942561d99.jpg" alt="">
|
||||
|
||||
根据我的经验,对比这个清单,大多数新项目都在一项或几项上准备得不够充分。**即便你做的不是一个从头开始的项目,对照这个清单,也会发现项目在某些项上的欠缺,可以有针对性地做一些补充。**
|
||||
|
||||
如果今天的内容你只记住一件事,那么请记住:**设计你的迭代0清单,给自己的项目做体检。**
|
||||
|
||||
最后,我想请你思考一下,如果让你来设计迭代0清单,它会包含哪些内容呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
143
极客时间专栏/geek/10x程序员工作法/以终为始/划重点 | 关于“以终为始”,你要记住的9句话.md
Normal file
143
极客时间专栏/geek/10x程序员工作法/以终为始/划重点 | 关于“以终为始”,你要记住的9句话.md
Normal file
@ -0,0 +1,143 @@
|
||||
<audio id="audio" title="划重点 | 关于“以终为始”,你要记住的9句话" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a9/bc/a984e67a29b1535ec335a1b565ccb8bc.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
“以终为始”这个主题模块已经全部更新完毕,相信通过对各种实践的深入讲解,你已经对“以终为始”这个原则有了更为全面和透彻的理解。
|
||||
|
||||
为了帮助你更好地回顾和复习,我为每个主题模块增设了“划重点”的加餐内容。现在,我就带你一起梳理一下“以终为始”主题的核心要点。
|
||||
|
||||
## 重点复习
|
||||
|
||||
在这个模块中,我们学习到了一些行业最佳实践。
|
||||
|
||||
- DoD,确定好完成的定义,减少团队内部的理解不一致。
|
||||
- 用户故事,细化出有价值的需求。
|
||||
- 持续集成,通过尽早集成,减少改动量,降低集成的难度。
|
||||
- 精益创业,减少过度开发不确定性产品带来的浪费。
|
||||
- 迭代0,在项目开始之前,做好一些基础准备。
|
||||
|
||||
还学习到一些重要的思维转变。
|
||||
|
||||
- 任何事物都要经过两次创造:一次是在头脑中的创造,也就是智力上的或者第一次创造(Mental/First Creation),然后才是付诸实践,也就是实际的构建或第二次创造(Physical/Second Creation)。
|
||||
- 在更大的上下文内发现自己的“终”。
|
||||
- 通过推演,找到通往“终”的路径。
|
||||
- 用可度量的“数字”定义自己的“终”。
|
||||
|
||||
## 实战指南
|
||||
|
||||
在每一篇文章的结尾,我们还将全篇内容浓缩为“一句话”的实战指南,希望你可以迅速上手,把“以终为始”的原则运用在实际工作之中,我们一起来回顾一下这些实战指南。
|
||||
|
||||
<li>
|
||||
<p>遇到事情,倒着想。<br>
|
||||
——《[02 | 以终为始:如何让你的努力不白费?](http://time.geekbang.org/column/article/74834)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>在做任何事之前,先定义完成的标准。<br>
|
||||
——《[03 | DoD的价值:你完成了工作,为什么他们还不满意?](http://time.geekbang.org/column/article/74828)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>在做任何需求或任务之前,先定好验收标准。<br>
|
||||
——《[04 | 接到需求任务,你要先做那件事?](http://time.geekbang.org/column/article/75100)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>尽早提交代码去集成。<br>
|
||||
——《[05 | 持续集成:集成本身就是写代码的一个环节](http://time.geekbang.org/column/article/75977l)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>默认所有需求都不做,直到弄清楚为什么要做这件事。<br>
|
||||
——《 [06 | 精益创业:产品经理不靠谱,你该怎么办?](http://time.geekbang.org/column/article/76260)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>扩大自己工作的上下文,别把自己局限在一个“程序员”的角色上。<br>
|
||||
——《[07 | 解决了很多问题,为什么你依然在“坑”里?](http://time.geekbang.org/column/article/76567)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>在动手做一件事之前,先推演一番。<br>
|
||||
——《[08 | 为什么说做事之前要先进行推演?](http://time.geekbang.org/column/article/76716)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>问一下自己,我的工作是不是可以用数字衡量。<br>
|
||||
——《[09 | 你的工作可以用数字衡量吗?](http://time.geekbang.org/column/article/76929)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>设计你的迭代0清单,给自己的项目做体检。<br>
|
||||
——《[10 | 启动开发之前,你应该准备什么?](http://time.geekbang.org/column/article/77294)》</p>
|
||||
</li>
|
||||
|
||||
## 额外收获
|
||||
|
||||
在这个部分的最后,针对大家在学习过程中的热门问题,我也进行了回答,希望你懂得:
|
||||
|
||||
- 作为程序员,你可以管理你的上级;
|
||||
- 拿老板说事的产品经理,你可以到老板面前澄清;
|
||||
- 喜欢无脑抄袭的产品经理,让他回去先想清楚到底抄的是什么;
|
||||
<li>分清楚需求和技术,产品经理和开发团队各自做好各自的事。<br>
|
||||
——《[答疑解惑 | 如何管理你的上司?](http://time.geekbang.org/column/article/77752)》</li>
|
||||
|
||||
## 留言精选
|
||||
|
||||
同学们的留言很踊跃,也很有价值。精彩的留言本身就是对文章内容的补充与丰富,在此我挑出一些优秀的留言与你分享。
|
||||
|
||||
在讲高效工作的思考框架时,**张维元** 同学提到:
|
||||
|
||||
>
|
||||
思考框架是道,原则是演化下的术,我们从 A → B,有无穷无尽的路径,最有效的唯有那条直线。本质上,各个维度、原则(不限于作者提到的四项原则)都是帮助我们更好地定位 A 在哪里,B 在哪里,那条直线在哪里。
|
||||
|
||||
|
||||
对于以终为始的原则,**WTF** 同学提到:
|
||||
|
||||
>
|
||||
“以终为始”,最常见的一个实践就是计划倒排了。先定时间,然后看功能是不是做不过来得砍掉一些,人力是不是不够需要补充一些,提前预知规避风险。
|
||||
|
||||
|
||||
对于用户故事的验收标准,**liu** 同学提到:
|
||||
|
||||
>
|
||||
程序员的核心职责是如何实现产品功能,怎么实现功能;前提是理解产品功能,需要实现哪些功能。有些项目经理,产品经理与程序员角色混淆。你同他谈功能,他同你谈技术实现,你同他谈技术,他同你谈产品(需要实现哪些功能)。
|
||||
|
||||
|
||||
大家对沙盘推演的话题很感兴趣。其中,**西西弗与卡夫卡** 同学提到:
|
||||
|
||||
>
|
||||
推演可以发现达成目标会涉及到哪些部门、哪些利益相关者,需要哪些资源,以及他们需要何时怎样的配合。
|
||||
|
||||
|
||||
**ZackZeng** 同学也针对这个话题留言:
|
||||
|
||||
>
|
||||
项目上线之前,一般都会有一个launch plan, 数据库迁移这种项目,不去考虑上线回滚我认为是设计上的缺失。我们公司的launch plan一般是写成一步一步的checklist, 在上线之前会做同伴审查。
|
||||
|
||||
|
||||
**Scott** 同学也提到:
|
||||
|
||||
>
|
||||
我觉得领导说先跑通再说和事前推演是不矛盾的,很多时候,我们需要一个poc来证明这个项目是可行的,这其实也是事前推演的一部分。上线要事无巨细的检查推演,和快速跑通poc不矛盾,当然现实世界是,大家就急着把poc当正式产品上线了,这是无数个悲剧故事的序章。
|
||||
|
||||
|
||||
**休息一下马上回来** 同学对推演过程进行了很好地补充:
|
||||
|
||||
>
|
||||
上线前,哪些机器什么配置,应该有一个预期,甚至提前准备好。
|
||||
|
||||
|
||||
**adang** 同学也分享了他在工作中的感悟:
|
||||
|
||||
>
|
||||
想清楚了才能写清楚,这是我在编程工作非常认可的一句话,并且我也认为它是区分合格与不合格开发工程师的重要区别。软件开发过程中,最常见的例子就是拿到需求后不管三七二十一,上来就开始撸代码,但最后往往返工不断,质量问题层出不穷,而且加班没完没了,这里面一个根本原因就是没有系统地想清楚,但很多人都觉得前期澄清需求、分析设计是浪费时间,只有编码才是真正的创造价值,这就是差距。
|
||||
|
||||
|
||||
在讲到工作要尽量用数字衡量时,**西西弗与卡夫卡** 同学提到:
|
||||
|
||||
>
|
||||
比如开发常常关注的是产品经理提的功能有没有实现,实际上也应该了解做出来的有多少人使用,每个页面有多少人使用。此外,看开发是否努力勤奋,不要光听他说,而是要看看他提交git有多频繁、提交的时间段、代码量有多少。代码质量可以用bug数/代码量来衡量。当然,这些量化未必科学,甚至会被误用,但总胜过凭印象拍脑袋的判断。
|
||||
|
||||
|
||||
**大彬** 同学也提到:
|
||||
|
||||
>
|
||||
上周我把一个方案进行推迟了,让同事去搜集某项指标的数据,没数据,一切方案都是空谈。AB测试,留言量,阅读量,转发量一切数据都是下一步决策和改进的基础。
|
||||
|
||||
|
||||
篇幅限制,就为大家分享这么多,感谢同学们的精彩留言。留言区还有很多同学提出了各种问题,其实都可以用任务分解的方式去解决。不着急,我们下一个主题的内容就是“任务分解”。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
142
极客时间专栏/geek/10x程序员工作法/以终为始/答疑解惑 | 如何管理你的上级?.md
Normal file
142
极客时间专栏/geek/10x程序员工作法/以终为始/答疑解惑 | 如何管理你的上级?.md
Normal file
@ -0,0 +1,142 @@
|
||||
<audio id="audio" title="答疑解惑 | 如何管理你的上级?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ad/e7/ad6e94375021a9f205e18cc38cc583e7.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
在这个模块里,我围绕着“以终为始”这个原则为你进行了详细地讲解,还给你介绍了应用“以终为始”原则的一些行业最佳实践。
|
||||
|
||||
同学们的留言特别踊跃,很多同学表示有收获的同时,也提出了大量的问题,大家比较关心怎样将这些实践在自己的实际工作中落地,部分问题我已经在留言中回复了。在今天的答疑环节中,我挑选了一些非常典型的问题来更详细地回答一下。
|
||||
|
||||
## 问题1:领导要求的,无力反驳怎么办?
|
||||
|
||||
achenbj 同学提到
|
||||
|
||||
>
|
||||
<p>讲得很好,感觉落地还需努力。我们就是领导给了功能,跟你说下要做啥,就那么做就行。没有了。<br>
|
||||
[—— 04 | 接到需求任务,你要先做哪件事?](http://time.geekbang.org/column/article/75100)</p>
|
||||
|
||||
|
||||
Alexdown 同学提到
|
||||
|
||||
>
|
||||
<p>考虑到地位的不对等以及我的“人设”已经定型了,实施起来有点难度。<br>
|
||||
[—— 02 | 以终为始:如何让你的努力不白费?](http://time.geekbang.org/column/article/74834)</p>
|
||||
|
||||
|
||||
这类问题很经典,很多同学留言提到,我就不一一列举了。
|
||||
|
||||
在我的职业生涯中,无数次听到不同的人有过同样的抱怨。我最初也觉得这是一个无解的问题,直到后来我读到了一本书。
|
||||
|
||||
管理大师彼得·德鲁克有一本经典著作《卓有成效的管理者》,虽然标题上带着管理者几个字,但在我看来,这是一本告诉我们如何工作的书,每个人都可以读一下。
|
||||
|
||||
当年我读这本书时,其中的一个观点让我很受震撼:如何管理上级。
|
||||
|
||||
什么?上级也可以管理?这对于我们这些习惯了接受上级指挥的人来说,观念上的转变几乎是天翻地覆一般。
|
||||
|
||||
在很多人看来,自己累死累活,只是因为自己的笨蛋上级,没有很好地处理好他该处理好的事情,还把“锅”扣到了自己的头上。
|
||||
|
||||
不过,在德鲁克看来,上级也是人,一样有着长处和短处。我们应该发挥其长处,减少其短处带来的不良影响。管理上级,也就是要发挥上级的长处,不能唯命是从,应该从正确的事情入手,以上级能够接受的方式向其提出建议。
|
||||
|
||||
具体到我们的日常工作中该怎么管理上级呢?我给你一些小建议。
|
||||
|
||||
**我们要敢于管理上级,**对上级不合理的要求说“不”,这是一个思想上的转变。很多人受到传统官本位思想的影响,对上级的服从达到了不健康的程度。勇于改变,是有效管理上级的前提条件。如果不从思想上转变,我接下来的建议都是没有价值的。
|
||||
|
||||
那具体要从哪些方面着手呢?
|
||||
|
||||
**第一,管理上级的预期。**
|
||||
|
||||
上级问你:“一个产品特性,你多长时间能做完?两天?一天行不行?”你想了想,如果不写测试,确实能够省下不少时间,于是,你决定答应上级的要求。是的,大部分人就是这么妥协的。
|
||||
|
||||
妥协很容易,但再往回扳就不容易了。下次,他还会再进一步压缩:“半天能不能搞定?两小时行不行?”人的欲望是无限的,所以,就不要让上级有错误的预期。
|
||||
|
||||
如果是我,我会告诉上级,这个压缩会影响到什么。比如,要想做这个调整,你需要放弃的内容是什么;或者,我可以给出一个快速上线的临时方案,但接下来的几天,我需要调整,让代码回到一个正常的状态中。所以,你就不要给我安排新工作了。
|
||||
|
||||
**这个过程,相当于我把自己看到的问题暴露给上级,让他选择。**他有更多的上下文,他会平衡该做的事情。
|
||||
|
||||
**第二,帮助上级丰富知识。**
|
||||
|
||||
不是每个上级都是经验丰富的,知道所有事情。比如,有些成长得比较快的负责人,自己甚至都还没来得及了解软件开发全生命周期。在IT这个快速发展的行业里,这是非常可能出现的情况。所以,在某些局部,你比他了解得多是非常有可能的。
|
||||
|
||||
在那些他做得不够好的领域,他肯定有许多烦恼。比如,盲目给需求的产品经理,可能也会影响到他对需求的判断。
|
||||
|
||||
这个时候,你就不妨把自己知道的内容找个机会给他讲讲。一个简单的方式是,把我专栏的内容发给他,和他一起探讨怎么做是合理的。然后,大家一起协同,改进工作方式。因为你是在帮他解决问题,他会更愿意接受。
|
||||
|
||||
**第三,说出你的想法。**
|
||||
|
||||
如果你什么都不做,上级会按照他自己的理解安排工作。比如,小李擅长处理消息队列,那消息队列的活都给他。
|
||||
|
||||
如果你有自己的想法和打算,不妨提出来,主动承担一些职责。比如,你接下来打算多学点消息队列,那就大大方方地告诉上级,下次有相关的活,考虑一下自己,上级再安排工作的时候,他就会多想想。这其实就是我们熟悉的一个最简单的道理:会哭的孩子有奶吃。
|
||||
|
||||
如果经过你的种种努力,发现你的上级真的是完全没法影响,只能以令人无语的方式行事,那你需要仔细考虑一下与他合作的前景了。
|
||||
|
||||
不过,更可能出现的场景是,你还没去尝试改变就放弃了,将全部责任都归结于上级的问题。如果你是这种思考问题的逻辑,不论到哪个公司,结果都不会比现在更好。
|
||||
|
||||
## 问题2:产品经理总拿老板说事,怎么办?
|
||||
|
||||
此方彼方Francis 同学提到
|
||||
|
||||
>
|
||||
<p>很多时候产品要做这需求的理由就一个:老板要的!<br>
|
||||
[——01 | 10x程序员是如何思考的?](http://time.geekbang.org/column/article/74471)</p>
|
||||
|
||||
|
||||
西西弗与卡夫卡 同学提到
|
||||
|
||||
>
|
||||
<p>有的产品经理会使出必杀技——这是老板的需求<br>
|
||||
[——06 | 精益创业:产品经理不靠谱,你该怎么办?](http://time.geekbang.org/column/article/76260)</p>
|
||||
|
||||
|
||||
用老板来“甩锅”,这在软件行业中特别常见。
|
||||
|
||||
实际上,老板要求的是方向,不是产品特性。大老板不会安排那么细的细节。所以,一个产品经理该做的事就是把老板给的方向,变成一个个可以实现的产品特性,他要分析其中的合理与不合理。
|
||||
|
||||
**不合理的部分应该是他和老板去沟通的,而不是让开发团队来实现。**
|
||||
|
||||
在真实世界中,更有可能的情形是,产品经理“拿着鸡毛当令箭”,老板说的是试一下,到他这里就变成了必须完成。他不敢对老板提问,就只能压迫下游了。
|
||||
|
||||
这种情况,你就不妨和产品经理一起去见老板。我们在[《解决了很多技术问题,为什么你依然在”坑“里?》](http://time.geekbang.org/column/article/76567)这篇文章中提到,要扩大自己工作的上下文,这种做法也可以帮助你解决问题,在自己上下文中解决不了的问题,就放到更大的上下文中去解决。
|
||||
|
||||
## 问题3:别人能做的,我们也要做
|
||||
|
||||
Xunqf 同学提到
|
||||
|
||||
>
|
||||
<p>当你和产品经理理论的时候,他往往会拿出来一个现有的产品给你看:“人家怎么就能做到,人家能做到说明技术上是可行的,做吧。”时间久了你会发现他的需求全是抄的的别的APP,然后就觉得别人能做到的我们也一定能做。<br>
|
||||
[——《06 | 精益创业:产品经理不靠谱,你该怎么办?》](http://time.geekbang.org/column/article/76260)</p>
|
||||
|
||||
|
||||
你会发现,在这个问题里,提到了两个与产品经理交流可能出现的典型问题:一个是竞争对手有的产品,我们也要有;另一个是人家能做到的,说明技术上可行,我们也能开发。
|
||||
|
||||
我带你来分别看下,这两种说法你该如何应对。
|
||||
|
||||
**第一,竞争对手有的产品,我们也要有。**
|
||||
|
||||
没有哪个企业是靠纯粹抄袭成功的。我知道,你想说腾讯。腾讯当年做 QQ,从形式上看,是和 ICQ 极其相似的,甚至名字都是极为相似的:OICQ。但腾讯却做了自己的微创新,它将信息保存到了服务器端,而 ICQ 是保存在客户端的。
|
||||
|
||||
正是有了这样看似微小的创新,让当时大部分家里没有电脑的普通用户,可以在网吧里不同的电脑上继续自己的网络社交,适应了时代发展的需要。腾讯“抄”得好的东西,都是有自己微创新的,包括如今的微信。
|
||||
|
||||
**“抄”不是问题,问题是无脑地抄。**
|
||||
|
||||
所以,如果你的产品经理只想无脑抄袭,本质上,他就是在偷懒,没干好他该干的活。竞争对手有这个特性,他为什么要做?他做这个特性与它其他特性是怎么配合的?我们做这个特性,在我们的产品里怎样发挥价值?作为产品经理,你必须给我讲清楚这些。
|
||||
|
||||
即便我们最终的结果是,做的与竞争对手一模一样,经过思考之后的“抄袭”也是一件价值更大的事。
|
||||
|
||||
**第二:人家能做到,说明技术上是可行的。**
|
||||
|
||||
关于这一点,我不得不说,产品经理说得对。别人能做到,说明技术上肯定是可行的。
|
||||
|
||||
**不过,我们必须分清楚两件事:需求和技术。**
|
||||
|
||||
要做什么是需求,怎么做是技术。与产品经理要确认的是,这个需求是不是合理,该不该做。技术上能否实现,这是开发团队要考虑的事情,并不是产品经理说事的理由。
|
||||
|
||||
还有一种情况是,需求确实合理,但技术实现的成本极高,所需花费的时间很长。在这种情况下,你和产品经理之间很难互相说服。
|
||||
|
||||
解决方案是,将问题升级,放到更大的上下文中,让上一层的领导来决定,此时此刻,在现有的资源约束下,是否要按照这种方式做。同时,你最好再提供一个可选的替换方案,这样领导才能更好做选择。
|
||||
|
||||
还有一些同学问了很好的问题。比如,程序员充当了太多角色,很困惑。这个问题我在专栏中已经回答了,在[《 接到需求任务,你要先做哪件事?》](http://time.geekbang.org/column/article/75100)中,我们说,每次扮演好一个角色。在[《 解决了很多技术问题,为什么你依然在“坑”里?》](http://time.geekbang.org/column/article/76567)中,我们提到程序员应该多了解不同的角色。
|
||||
|
||||
还有人问,计划赶不上变化快,怎么办?简单回答就是靠任务分解,因为这个话题涉及到“任务分解”这个模块的内容,等这个主题讲完之后,如果大家还有疑惑,我们再来详细讨论。
|
||||
|
||||
好,今天的答疑就到这里,请你回想一下,你在你工作中是否也遇到过类似的问题呢?你又是怎么解决的呢?欢迎在留言区写下你的想法。我会从中筛选出典型的问题,与大家进行互动交流。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
111
极客时间专栏/geek/10x程序员工作法/任务分解/11 | 向埃隆·马斯克学习任务分解.md
Normal file
111
极客时间专栏/geek/10x程序员工作法/任务分解/11 | 向埃隆·马斯克学习任务分解.md
Normal file
@ -0,0 +1,111 @@
|
||||
<audio id="audio" title="11 | 向埃隆·马斯克学习任务分解" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/31/b2/31511a2846124a0a34ec9a1a0305f9b2.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
这次我们从一个宏大的话题开始:银河系中存在多少与我们相近的文明。我想,即便这个专栏的读者主力是程序员这个平均智商极高的群体,在面对这样一个问题时,大多数人也不知道从何入手。
|
||||
|
||||
我来做一个科普,给大家介绍一下德雷克公式,这是美国天文学家法兰克·德雷克(Frank Drake)于1960年代提出的一个公式,用来推测“可能与我们接触的银河系内外星球高等文明的数量”。
|
||||
|
||||
下面,我要放出德雷克公式了,看不懂一点都不重要,反正我也不打算讲解其中的细节,我们一起来感受一下。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1f/32/1f099ca7941fb792f5d284fab98d0432.jpg" alt="">
|
||||
|
||||
不知道你看了德雷克公式做何感想,但对于科学家们来说,德雷克公式最大的作用在于:**它将一个原本毫无头绪的问题分解了,分成若干个可以尝试回答的问题。**
|
||||
|
||||
随着观测手段的进步,我们对宇宙的了解越来越多,公式中大多数数值,都可以得到一个可以估算的答案。有了这些因子,人们就可以估算出银河系内可以与我们通信的文明数量。
|
||||
|
||||
虽然不同的估算结果会造成很大的差异,而且我们迄今为止也没能找到一个可以联系的外星文明,但这个公式给了我们一个方向,一个尝试解决问题的手段。
|
||||
|
||||
好吧,我并不打算将这个专栏变成一个科普专栏,之所以在这讲解德雷克公式,因为它体现了一个重要的思想:任务分解。
|
||||
|
||||
通过任务分解,一个原本复杂的问题,甚至看起来没有头绪的问题,逐渐有了一个通向答案的方向。而“任务分解”就是我们专栏第二模块的主题。
|
||||
|
||||
## 马斯克的任务分解
|
||||
|
||||
如果大家对德雷克公式有些陌生,我们再来看一个 IT 人怎样用任务分解的思路解决问题。
|
||||
|
||||
我们都知道埃隆·马斯克(Elon Musk),他既是电动汽车公司特斯拉(Tesla)的创始人,同时还创建了太空探索公司 SpaceX。SpaceX 有一个目标是,送100万人上火星。
|
||||
|
||||
美国政府曾经算过一笔账,把一个人送上火星,以现有技术是可实现的,需要花多少钱呢?答案是100亿美金。如果照此计算,实现马斯克的目标,送100万人上火星就要1万万亿。这是什么概念呢?这笔钱相当于美国500年的GDP,实在太贵了,贵到连美国政府都无法负担。
|
||||
|
||||
马斯克怎么解决这个问题呢?他的目标变了,他准备把人均费用降到50万美元,也就是一个想移民的人,把地球房子卖了能够凑出的钱。原来需要100亿美金,现在要降到50万美金,需要降低2万倍。
|
||||
|
||||
当然,降低2万倍依然是一个听起来很遥远的目标。所以,我们关注的重点来了:马斯克的第二步是,把2万分解成20×10×100。这是一道简单的数学题,也是马斯克三个重点的努力方向。
|
||||
|
||||
先看“20”:现在的火星飞船一次只能承载5个人,马斯克的打算是,把火箭造大一点,一次坐100人,这样,就等于把成本降低20倍。如果你关注新闻的话,会发现 SpaceX 确实在进行这方面的尝试,
|
||||
|
||||
再来看“10”:马斯克认为自己是私营公司,效率高,成本可以降到十分之一。他们也正在向这个方向努力,SpaceX 的成本目前已经降到了同行的五分之一。
|
||||
|
||||
最后的“100”是什么呢?就是回收可重复使用的火箭。如果这个目标能实现,发射火箭的成本就只是燃料成本了。这也就是我们频频看到的 SpaceX 试飞火箭新闻的原因。
|
||||
|
||||
这么算下来,你是不是觉得,马斯克的目标不像最开始听到的那样不靠谱了呢?**正是通过将宏大目标进行任务分解,马斯克才能将一个看似不着边际的目标向前推进。**
|
||||
|
||||
## 软件开发的任务分解
|
||||
|
||||
好了,和大家分享这两个例子只是为了热热身,说明人类解决问题的方案是差不多的。当一个复杂问题摆在面前时,我们解决问题的一个主要思路是分而治之。
|
||||
|
||||
**一个大问题,我们都很难给出答案,但回答小问题却是我们擅长的。**所以,当我们学会将问题分解,就相当于朝着问题的解决迈进了一大步。
|
||||
|
||||
我们最熟悉的分而治之的例子,应该是将这个理念用在算法上,比如归并排序。将待排序的元素分成大小基本相同的两个子集,然后,分别将两个子集排序,最后将两个排好序的子集合并到一起。
|
||||
|
||||
一说到技术,大家就觉得踏实了许多,原来无论是外星人搜寻,还是大名鼎鼎的马斯克太空探索计划,解决问题时用到的思路都是大同小异啊!确实是这样。
|
||||
|
||||
**那么,用这种思路解决问题的难点是什么呢?给出一个可执行的分解。**
|
||||
|
||||
在前面两个例子里面,最初听到要解决的问题时,估计你和我一样,是一脸懵的。但一旦知道了分解的结果,立即会有一种“柳暗花明又一村”的感觉。你会想,我要是想到了这个答案,我也能做一个 SpaceX 出来。
|
||||
|
||||
但说到归并排序的时候,你的心里可能会有一丝不屑,这是一个学生级别的问题,甚至不值得你为此费脑子思考。因为归并排序你已经知道了答案,所以,你会下意识地低估它。
|
||||
|
||||
任务分解就是这样一个有趣的思想,一旦分解的结果出来,到了可执行的步骤,接下来的工作,即便不是一马平川,也是比原来顺畅很多,因为问题的规模小了。
|
||||
|
||||
在日常工作中,我们会遇到很多问题,既不像前两个问题那样宏大,也不像归并排序那样小,但很多时候,我们却忘记了将任务分解这个理念运用其中,给工作带来很多麻烦。
|
||||
|
||||
举一个例子,有一个关于程序员的经典段子:这个工作已经做完了80%,剩下的20%还要用和前面的一样时间。
|
||||
|
||||
为什么我们的估算差别如此之大,很重要的一个原因就在于没有很好地分解任务,所以,我们并不知道要做的事情到底有多少。
|
||||
|
||||
前面我们在[“为什么说做事之前要先进行推演?”](http://time.geekbang.org/column/article/76716)文章中,讲到沙盘推演,这也是一个很好的例子,推演的过程就是一个任务分解的过程。上手就做,多半的结果都是丢三落四。你会发现,真正把工作完全做好,你落掉的工作也都要做,无论早晚。
|
||||
|
||||
**与很多实践相反,任务分解是一个知难行易的过程。**知道怎么分解是困难的,一旦知道了,行动反而要相对来说容易一些。
|
||||
|
||||
在“任务分解”这个主题下,我还会给你介绍一些实践,让你知道,这些最佳实践的背后思想就是任务分解。如果你不了解这些实践,你也需要知道,在更多的场景下,先分解任务再去做事情是个好办法。
|
||||
|
||||
也许你会说,任务分解并不难于理解,我在解决问题的过程中也是先做任务分解的,但“依然过不好这一生。”这就要提到我前面所说难点中,很多人可能忽略的部分:可执行。
|
||||
|
||||
可执行对于每个人的含义是不同的,对于马斯克而言,他把2万分解成20×10×100,剩下的事情对他来说就是可执行的,但如果你在 SpaceX 工作,你就必须回答每个部分究竟是怎样执行的。
|
||||
|
||||
同样,假设我们做一个 Web 页面,如果你是一个经验丰富的前端工程师,你甚至可能认为这个任务不需要分解,顶多就是再多一个获取网页资源的任务。
|
||||
|
||||
而我如果是一个新手,我就得把任务分解成:根据内容编写 HTML;根据页面原型编写页面样式;根据交互效果编写页面逻辑等几个步骤。
|
||||
|
||||
**不同的可执行定义差别在于,你是否能清楚地知道这个问题该如何解决。**
|
||||
|
||||
对于马斯克来说,他的解决方案可能是成立一个公司,找到这方面的专家帮助他实现。对你的日常工作来说,你要清楚具体每一步要做的事情,如果不能,说明任务还需要进一步分解。
|
||||
|
||||
比如,你要把一个信息存起来,假设你们用的是关系型数据库,对大多数人来说,这个任务分解就到了可执行的程度。但如果你的项目选用了一个新型的数据库,比如图数据库,你的任务分解里可能要包含学习这个数据库的模型,然后还要根据模型设计存储方案。
|
||||
|
||||
不过,在实际工作中,大多数人都高估了自己可执行粒度,低估任务分解的程度。换句话说,如果你没做过任务分解的练习,你分解出来的大部分任务,粒度都会偏大。
|
||||
|
||||
只有能把任务拆分得非常小,你才能对自己的执行能力有一个更清楚地认识,真正的高手都是有很强的分解能力。这个差别就相当于,同样观察一个物品,你用的是眼睛,而高手用的是显微镜。在你看来,高手全是微操作。关于这个话题,后面我们再来细聊。
|
||||
|
||||
一旦任务分解得很小,调整也会变得很容易。很多人都在说计划赶不上变化,而真正的原因就是计划的粒度太大,没法调整。
|
||||
|
||||
从当年的瀑布模型到今天的迭代模型,实际上,就是缩减一次交付的粒度。几周调整一次计划,也就不存在“计划赶不上变化”的情况了,因为我的计划也一直在变。
|
||||
|
||||
**如今软件行业都在提倡拥抱变化,而任务分解是我们拥抱变化的前提。**
|
||||
|
||||
## 总结时刻
|
||||
|
||||
我们从外星人探索和马斯克的火星探索入手,介绍了任务分解在人类社会诸多方面的应用,引出了分而治之这个人类面对复杂问题的基本解决方案。接着,我给你讲了这一思想在软件开发领域中的一个常见应用,分而治之的算法。
|
||||
|
||||
虽然我们很熟悉这一思想,但在日常工作中,我们却没有很好地应用它,这也使得大多数人的工作有很大改进空间。运用这一思想的难点在于,给出一个可执行的分解。
|
||||
|
||||
一方面,对复杂工作而言,给出一个分解是巨大的挑战;另一方面,面对日常工作,人们更容易忽略的是,分解的任务要可执行。每个人对可执行的理解不同,只要你清楚地知道接下来的工作该怎么做,任务分解就可以告一段落。
|
||||
|
||||
大多数人对于可执行的粒度认识是不足的,低估了任务分解的程度,做到好的分解你需要达到“微操作”的程度。有了分解得很小的任务,我们就可以很容易完成一个开发循环,也就让计划调整成为了可能。软件行业在倡导拥抱变化,而任务分解是拥抱变化的前提。
|
||||
|
||||
如果今天的内容你只记住一件事,那么请记住:**动手做一个工作之前,请先对它进行任务分解。**
|
||||
|
||||
最后,我想请你回想一下,你在实际工作中,有哪些依靠任务分解的方式解决的问题呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
129
极客时间专栏/geek/10x程序员工作法/任务分解/12 | 测试也是程序员的事吗?.md
Normal file
129
极客时间专栏/geek/10x程序员工作法/任务分解/12 | 测试也是程序员的事吗?.md
Normal file
@ -0,0 +1,129 @@
|
||||
<audio id="audio" title="12 | 测试也是程序员的事吗?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/9c/d1/9c3d4d6ec9e51aa7c9aa26c1769537d1.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
在“任务分解”这个模块,我准备从一个让我真正深刻理解了任务分解的主题开始,这个主题就是“测试”。
|
||||
|
||||
这是一个让程序员又爱有恨的主题,爱测试,因为它能让项目的质量有保证;恨测试,因为测试不好写。而实际上,很多人之所以写不好测试,主要是因为他不懂任务分解。
|
||||
|
||||
在上一个模块,我们提到了一些最佳实践,但都是从“以终为始”这个角度进行讲解的。这次,我准备换个讲法,用五讲的篇幅,完整地讲一下“开发者测试”,让你和我一起,重新认识这个你可能忽视的主题。
|
||||
|
||||
准备好了吗?我们先从让很多人疑惑的话题开始:程序员该写测试吗?
|
||||
|
||||
## 谁要做测试?
|
||||
|
||||
你是一个程序员,你当然知道为什么要测试,因为是我们开发的软件,我们得尽可能地保证它是对的,毕竟最基本的职业素养是要有的。
|
||||
|
||||
但测试工作应该谁来做,这是一个很有趣的话题。很多人凭直觉想到的答案是,测试不就该是测试人员的事吗,这还用问?
|
||||
|
||||
**测试人员应该做测试,这是没错的,但是测试只是测试人员的事吗?**
|
||||
|
||||
事实上,作为程序员,你多半已经做了很多测试工作。比如,在提交代码之前,你肯定会把代码跑一遍,保证提交的基本功能是正确的,这就是最基本的测试。但通常,你并不把它当成测试,所以,你的直觉里面,测试是测试人员的事。
|
||||
|
||||
但我依然要强调,测试应该是程序员工作的一部分,为什么这么说呢?
|
||||
|
||||
我们不妨想想,测试人员能测的是什么?没错,他们只能站在系统外部做功能特性的测试。而一个软件是由它内部诸多模块组成的,测试人员只从外部保障正确性,所能达到的效果是有限的。
|
||||
|
||||
打个比方,你做一台机器,每个零部件都不保证正确性,却要让最后的结果正确,这实在是一个可笑的要求,但这却真实地发生在软件开发的过程中。
|
||||
|
||||
在软件开发中有一个重要的概念:[软件变更成本,它会随着时间和开发阶段逐步增加。](http://www.agilemodeling.com/essays/costOfChange.htm)也就是说我们要尽可能早地发现问题,修正问题,这样所消耗掉的成本才是最低的。
|
||||
|
||||
上一个模块讲“以终为始”,就是在强调尽早发现问题。能从需求上解决的问题,就不要到开发阶段。同样,在开发阶段能解决的问题,就不要留到测试阶段。
|
||||
|
||||
你可以想一下,是你在代码中发现错误改代码容易,还是测试了报了 bug,你再定位找问题方便。
|
||||
|
||||
更理想的情况是,质量保证是贯穿在软件开发全过程中,从需求开始的每一个环节,都将“测试”纳入考量,每个角色交付自己的工作成果时,都多问一句,你怎么保证交付物的质量。
|
||||
|
||||
需求人员要确定验收标准,开发人员则要交出自己的开发者测试。这是一个来自于精益原则的重要思想:内建质量(Build Quality In)。
|
||||
|
||||
**所以,对于每个程序员来说,只有在开发阶段把代码和测试都写好,才有资格说,自己交付的是高质量的代码。**
|
||||
|
||||
## 自动化测试
|
||||
|
||||
不同于传统测试人员只通过手工的方式进行验证,程序员这个群体做测试有个天然的优势:会写代码,这个优势可以让我们把测试自动化。
|
||||
|
||||
早期测试代码,最简单的方式是另外写一个程序入口,我初入职场的时候,也曾经这么做过,毕竟这是一种符合直觉的做法。不过,既然程序员有写测试的需求,如此反复出现的东西,就会有更好的自动化方案。于是开始测试框架出现了。
|
||||
|
||||
最早的测试框架起源是 Smalltalk。这是一门早期的面向对象程序设计语言,它有很多拥趸,很多今天流行的编程概念就来自于 Smalltalk,测试框架便是其中之一。
|
||||
|
||||
真正让测试框架广泛流行起来,要归功于 Kent Beck 和 Erich Gamma。Kent Beck 是极限编程的创始人,在软件工程领域大名鼎鼎,而 Erich Gamma 则是著名的《设计模式》一书的作者,很多人熟悉的 Visual Studio Code 也有他的重大贡献。
|
||||
|
||||
有一次,二人一起从苏黎世飞往亚特兰大参加 OOPLSA(Object-Oriented Programming, Systems, Languages & Applications)大会,在航班上两个人结对编程写出了JUnit。从这个名字你便不难看出,它的目标是打造一个单元测试框架。
|
||||
|
||||
顺便说一下,如果你知道 Kent Beck 是个狂热的 Smalltalk 粉丝,写过 SUnit 测试框架,就不难理解这两个人为什么能在一次航班上就完成这样的力作。
|
||||
|
||||
JUnit 之后,测试框架的概念逐渐开始流行起来。如今的“程序世界”,测试框架已经成为行业标配,每个程序设计语言都有自己的测试框架,甚至不止一种,一些语言甚至把它放到了标准库里,行业里也用 XUnit 统称这些测试框架。
|
||||
|
||||
**这种测试框架最大的价值,是把自动化测试作为一种最佳实践引入到开发过程中,使得测试动作可以通过标准化的手段固定下来。**
|
||||
|
||||
## 测试模型:蛋卷与金字塔
|
||||
|
||||
在前面的讨论里,我们把测试分为人工测试和自动化测试。即便我们只关注自动化测试,也可以按照不同的层次进行划分:将测试分成关注最小程序模块的单元测试、将多个模块组合在一起的集成测试,将整个系统组合在一起的系统测试。
|
||||
|
||||
有人喜欢把验收测试也放到这个分类里。为了简化讨论,我们暂时忽略验收测试。
|
||||
|
||||
随之而来的一个问题是,我们应该写多少不同层次的测试呢?理论上固然是越多越好了,但实际上,做任何事都是有成本的,所以,人们必须有所取舍。根据不同测试的配比,也就有了不同的测试模型。
|
||||
|
||||
有一种直觉的做法是,既然越高层的测试覆盖面越广,那就多写高层测试,比如系统测试。
|
||||
|
||||
当然,有些情景高层的测试不容易覆盖到的,所以,还要有一些底层的测试,比如单元测试。在这种情况下,底层的测试只是作为高层测试的补充,而主力就是高层测试。这样就会形成下面这样一种测试模型:冰淇淋蛋卷。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/68/5a/68032e09ba926c260f18c0f1a51f3d5a.jpg" alt="">
|
||||
|
||||
听说过冰淇淋蛋卷测试模型的人并不多,它是一种费时费力的模型,要准备高层测试实在是太麻烦了。
|
||||
|
||||
之所以要在这里提及它,是因为虽然这个概念很多人没听说过,但是有不少团队的测试实际采用的就是这样一种模型,这也是很多团队觉得测试很麻烦却不明就里的原因。
|
||||
|
||||
接下来,要说说另一种测试模型,也是行业里的最佳实践:测试金字塔。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/83/39/833986a5a99d5fyy5e54abfaa213b239.jpg" alt="">
|
||||
|
||||
Mike Cohn 在自己的著作[《Succeeding with Agile》](http://book.douban.com/subject/5334585/)提出了测试金字塔,但大多数人都是通过 [Martin Fowler 的文章](http://martinfowler.com/bliki/TestPyramid.html)知道的这个概念。
|
||||
|
||||
从图中我们不难看出,它几乎是冰淇淋蛋卷的反转,测试金字塔的重点就是越底层的测试应该写得越多。
|
||||
|
||||
想要理解测试金字塔成为行业最佳实践的缘由,我们需要理解不同层次测试的差异。**越是底层的测试,牵扯到相关内容越少,而高层测试则涉及面更广。**
|
||||
|
||||
比如单元测试,它的关注点只有一个单元,而没有其它任何东西。所以,只要一个单元写好了,测试就是可以通过的;而集成测试则要把好几个单元组装到一起才能测试,测试通过的前提条件是,所有这些单元都写好了,这个周期就明显比单元测试要长;系统测试则要把整个系统的各个模块都连在一起,各种数据都准备好,才可能通过。
|
||||
|
||||
这个模块的主题是“任务分解”,我必须强调一点:**小事反馈周期短,而大事反馈周期长。**小事容易做好,而大事难度则大得多。所以,以这个标准来看,底层的测试才更容易写好。
|
||||
|
||||
另外,因为涉及到的模块过多,任何一个模块做了调整,都有可能破坏高层测试,所以,高层测试通常是相对比较脆弱的。
|
||||
|
||||
此外,在实际的工作中,有些高层测试会牵扯到外部系统,这样一来,复杂度又在不断地提升。
|
||||
|
||||
人们会本能地都会倾向于少做复杂的东西,所以,人们肯定不会倾向于多写高层测试,其结果必然是,高层测试的测试量不会太多,测试覆盖率无论如何都上不来。而且,一旦测试失败,因为牵扯的内容太多,定位起来也是非常麻烦的。
|
||||
|
||||
而反过来,将底层测试定义为测试主体,因为牵扯的内容少,更容易写,才有可能让团队得到更多的测试,而且一旦出现问题,也会更容易发现。
|
||||
|
||||
**所以,虽然冰淇淋蛋卷更符合直觉,但测试金字塔才是行业的最佳实践。**
|
||||
|
||||
## 当测试金字塔遇到持续集成
|
||||
|
||||
测试金字塔是一个重要实践的基础,它就是持续集成。当测试数量达到一定规模,测试运行的时间就会很长,我们可能无法在本地环境一次性运行所有测试。一般我们会选择在本地运行所有单元测试和集成测试,而把系统测试放在持续集成服务器上执行。
|
||||
|
||||
这个时候,底层测试的数量就成了关键,按照测试金字塔模型,底层测试数量会很多,测试可以覆盖主要的场景;而按照冰淇淋蛋卷模型,底层测试的数量则有限。
|
||||
|
||||
作为提交代码的防护网,测试数量多寡决定着得到反馈的早晚。所以,金字塔模型与持续集成天然就有着很好的配合。
|
||||
|
||||
需要特别注意的是,**不是用单元测试框架写的测试就是单元测试。**很多人用单元测试框架写的是集成测试或是系统测试。单元测试框架只是一个自动化测试的工具而已,并不是用来定义测试类型的。
|
||||
|
||||
在实际工作中,区分不同测试有很多种做法,比如,将不同的测试放到不同的目录下,或是给不同类型的测试一个统一的命名规范。
|
||||
|
||||
区分不同类型测试主要目的,主要是在不同的场景下,运行不同类型的测试。就像前面提到的做法是,在本地运行单元测试和集成测试,在持续集成服务器上运行系统测试。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
测试是软件开发重要的组成部分,测试应该是软件开发团队中所有人的事,而不仅仅是测试人员的事。因为软件变更成本会随着时间和开发阶段逐步增加,能在早期解决的问题,就不要将它延后至下一个阶段。
|
||||
|
||||
在测试问题上,程序员有着天生的优势,会写代码,于是,程序员拥有了一个突出的强项,自动化测试。写测试应该是程序员工作完成的重要组成部分。
|
||||
|
||||
随着人们对于测试理解的加深,各种各样的测试都出现了,也开始有了测试的分类:单元测试、集成测试、系统测试等等。越在底层测试,成本越低,执行越快;越在高层测试,成本越高,执行越慢。
|
||||
|
||||
人的时间和精力是有限的,所以,人们开始思考不同的测试如何组合。在这个方面的最佳实践称之为测试金字塔,它强调的重点是,越底层的测试应该写得越多。只有按照测试金字塔的方式写测试,持续集成才能更好地发挥作用。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**多写单元测试。**
|
||||
|
||||
最后,我想请你分享一下,你的团队在写测试上遇到哪些困难呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
123
极客时间专栏/geek/10x程序员工作法/任务分解/13 | 先写测试,就是测试驱动开发吗?.md
Normal file
123
极客时间专栏/geek/10x程序员工作法/任务分解/13 | 先写测试,就是测试驱动开发吗?.md
Normal file
@ -0,0 +1,123 @@
|
||||
<audio id="audio" title="13 | 先写测试,就是测试驱动开发吗?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/53/6b/53e15fbc5426cab28097f78996e7366b.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
在上一讲中,我向你说明了为什么程序员应该写测试,今天我准备与你讨论一下程序员应该在什么阶段写测试。
|
||||
|
||||
或许你会说,写测试不就是先写代码,然后写测试吗?没错,这是一个符合直觉的答案。但是,这个行业里确实有人探索了一些不同的做法。接下来,我们就将进入不那么直觉的部分。
|
||||
|
||||
既然自动化测试是程序员应该做的事,那是不是可以做得更极致一些,在写代码之前就把测试先写好呢?
|
||||
|
||||
有人确实这么做了,于是,形成了一种先写测试,后写代码的实践,这个实践的名字是什么呢?它就是测试先行开发(Test First Development)。
|
||||
|
||||
我知道,当我问出这个问题的时候,一个名字已经在很多人的脑海里呼之欲出了,那就是**测试驱动开发(Test Driven Development)**,也就是大名鼎鼎的 **TDD**,TDD 正是我们今天内容的重点。
|
||||
|
||||
在很多人看来,TDD 就是先写测试后写代码。在此我必须澄清一下,这个理解是错的。先写测试,后写代码的实践指的是测试先行开发,而非测试驱动开发。
|
||||
|
||||
下一个问题随之而来,测试驱动开发到底是什么呢?测试驱动开发和测试先行开发只差了一个词:驱动。只有理解了什么是驱动,才能理解了测试驱动开发。要理解驱动,先来看看这两种做法的差异。
|
||||
|
||||
## 测试驱动开发
|
||||
|
||||
学习 TDD 的第一步,是要记住TDD的节奏:“红-绿-重构”。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/01/e9/01975cfc49ac170f566b7f96ae0751e9.jpg" alt="">
|
||||
|
||||
红,表示写了一个新的测试,测试还没有通过的状态;绿,表示写了功能代码,测试通过的状态;而重构,就是再完成基本功能之后,调整代码的过程。
|
||||
|
||||
这里说到的“红和绿”,源自单元测试框架,测试不过的时候展示为红色,通过则是绿色。这在单元测试框架形成之初便已经约定俗成,各个不同语言的后代也将它继承了下来。
|
||||
|
||||
我们前面说过,让单元测试框架流行起来的是 JUnit,它的作者之一是 Kent Beck。同样,也是 Kent Beck 将 TDD 从一个小众圈子带到了大众视野。
|
||||
|
||||
考虑到 Kent Beck 是单元测试框架和 TDD 共同的贡献者,你就不难理解为什么 TDD 的节奏叫“红-绿-重构”了。
|
||||
|
||||
测试先行开发和测试驱动开发在第一步和第二步是一样的,先写测试,然后写代码完成功能。二者的差别在于,测试驱动开发并没有就此打住,它还有一个更重要的环节:**重构(refactoring)。**
|
||||
|
||||
也就是说,在功能完成而且测试跑通之后,我们还会再次回到代码上,处理一下代码上写得不好的地方,或是新增代码与旧有代码的重复。因为我们第二步“绿”的关注点,只在于让测试通过。
|
||||
|
||||
**测试先行开发和测试驱动开发的差异就在重构上。**
|
||||
|
||||
很多人通过了测试就认为大功告成,其实,这是忽略了新增代码可能带来的“坏味道(Code Smell)”。
|
||||
|
||||
如果你真的理解重构,你就知道,它就是一个消除代码坏味道的过程。一旦你有了测试,你就可以大胆地重构了,因为任何修改错误,测试会替你捕获到。
|
||||
|
||||
在测试驱动开发中,重构与测试是相辅相成的:没有测试,你只能是提心吊胆地重构;没有重构,代码的混乱程度是逐步增加的,测试也会变得越来越不好写。
|
||||
|
||||
**因为重构和测试的互相配合,它会驱动着你把代码写得越来越好。这是对“驱动”一词最粗浅的理解。**
|
||||
|
||||
## 测试驱动设计
|
||||
|
||||
接下来,我们再来进一步理解“驱动”:**由测试驱动代码的编写。**
|
||||
|
||||
许多人抗拒测试有两个主要原因:第一,测试需要“额外”的工作量。这里我特意把额外加上引号,因为,你也许本能上认为,测试是额外的工作,但实际上,测试也应该是程序员工作的一部分,这在上一篇文章中我已经讲过。
|
||||
|
||||
第二,很多人会觉得代码太多不好测。之所以这些人认为代码不好测,其中暗含了一个假设:代码已经写好了,然后,再写测试来测它。
|
||||
|
||||
如果我们把思路反过来,我有一个测试,怎么写代码能通过它。一旦你先思考测试,设计思路就完全变了:**我的代码怎么写才是能测试的,也就是说,我们要编写具有可测试性的代码。**用这个角度,测试是不是就变得简单了呢?
|
||||
|
||||
这么说还是有些抽象,我们举个写代码中最常见的问题:static 方法。
|
||||
|
||||
很多人写代码的时候喜欢使用 static 方法,因为用着省事,随便在哪段代码里面,直接引用这个 static 方法就可以。可是,一旦当你写测试的时候,你就会发现一个问题,如果你的代码里直接调用一个static 方法,这段代码几乎是没法测的。尤其是这个 static 方法里面有一些业务逻辑,根据不同业务场景返回各种值。为什么会这样?
|
||||
|
||||
我们想想,常见的测试手法应该是什么样的?如果我们在做的是单元测试,那测试的目标应该就是一个单元,在这个面向对象作为基础设施流行的时代,这个单元大多是一个类。测试一个类,尤其是一个业务类,一般会涉及到一些与之交互的类。
|
||||
|
||||
比如,常见的 REST 服务三层架构中,资源层要访问服务层,而在服务层要访问数据层。编写服务层代码时,因为要依赖数据层。所以,测试服务层通常的做法是,做一个假的数据层对象,这样即便数据层对象还没有编写,依然能够把服务层写完测好。
|
||||
|
||||
在之前的“蛮荒时代”,我们通常会写一个假的类,模拟被依赖那个类,因为它是假的,我们会让它返回固定的值,使用这样的类创建出来的对象,我们一般称之为 Stub 对象。
|
||||
|
||||
这种“造假”的方案之所以可行,一个关键点在于,这个假对象和原有对象应该有相同的接口,遵循同样的契约。从设计上讲,这叫符合 Liskov 替换法则。这不是我们今天讨论的重点,就不进一步展开了。
|
||||
|
||||
因为这种“造假”的方案实在很常见,所以,有人做了框架支持它,就是常用的 Mock 框架。使用 Mock 对象,我们可以模拟出被依赖对象的各种行为,返回不同的值,抛出异常等等。
|
||||
|
||||
它之所以没有用原来 Stub 这个名字,是因为这样的 Mock 对象往往有一个更强大的能力:验证这个 Mock 对象在方法调用过程中的使用情况,比如调用了几次。
|
||||
|
||||
我们回到 static 的讨论上,你会发现 Mock 对象的做法面对 static 时行不通了。因为它跳出了对象体系,static 方法是没法继承的,也就是说,没法用一系列面向对象的手法处理它。你没有办法使用 Mock 对象,也就不好设置对应的方法返回值。
|
||||
|
||||
要想让这个方法返回相应的值,你必须打开这个 static 方法,了解它的实现细节,精心地按照里面的路径,小心翼翼地设置对应的参数,才有可能让它给出一个你预期的结果。
|
||||
|
||||
更糟糕的是,因为这个方法是别人维护的,有一天他心血来潮修改了其中的实现,你小心翼翼设置的参数就崩溃了。而要重新进行设置的话,你只能把代码重读一遍。
|
||||
|
||||
如此一来,你的工作就退回到原始的状态。更重要的是,它并不是你应该关注的重点,这也不会增加你的 KPI。显然,你跑偏了。
|
||||
|
||||
讨论到这里你已经知道了 static 方法对测试而言,并不友好。所以,如果你要想让你的代码更可测,**一个好的解决方案是尽量不写 static 方法。**
|
||||
|
||||
这就是“从测试看待代码,而引起的代码设计转变”的一个典型例子。
|
||||
|
||||
关于 static 方法,我再补充几点。static 方法从本质上说,是一种全局方法,static 变量就是一种全局变量。我们都知道,全局方法也好,全局变量也罢,都是我们要在程序中努力消除的。一旦放任 static 的使用,就会出现和全局变量类似的效果,你的程序崩溃了,因为别人在另外的地方修改了代码,代码变得脆弱无比。
|
||||
|
||||
static 是一个方便但邪恶的东西。所以,要限制它的使用。除非你的 static 方法是不涉及任何状态而且行为简单,比如,判断字符串是否为空。否则,不要写 static 方法。你看出来了,这样的 static 方法更适合做库函数。所以,我们日常写应用时,能不用尽量不用。
|
||||
|
||||
前面关于 static 方法是否可以 Mock 的讨论有些绝对,市面上确实有某些框架是可以 Mock static方法的,但我不建议使用这种特性,因为它不是一种普遍适用的解决方案,只是某些特定语言特定框架才有。
|
||||
|
||||
更重要的是,正如前面所说,它会在设计上将你引到一条不归路上。
|
||||
|
||||
如果你在自己的代码遇到第三方的 static 方法怎么办,很简单,将第三方代码包装一下,让你的业务代码面对的都是你自己的封装就好了。
|
||||
|
||||
以我对大多数人编程习惯的认知,上面这个说法是违反许多人编程直觉的,但如果你从代码是否可测的角度分析,你就会得到这样的结论。
|
||||
|
||||
先测试后写代码的方式,会让你看待代码的角度完全改变,甚至要调整你的设计,才能够更好地去测试。所以,很多懂 TDD 的人会把 TDD 解释为测试驱动设计(Test Driven Design)。
|
||||
|
||||
还有一个典型的场景,从测试考虑会改变的设计,那就是依赖注入(Dependency Injection)。
|
||||
|
||||
不过,因为 Spring 这类 DI 容器的流行,现在的代码大多都写成了符合依赖注入风格的代码。原始的做法是直接 new 一个对象,这是符合直觉的做法。但是,你也可以根据上面的思路,自己推演一下,从 new 一个对象到依赖注入的转变。
|
||||
|
||||
有了编写可测试代码的思路,即便你不做 TDD,依然对你改善软件设计有着至关重要的作用。所以,**写代码之前,请先想想怎么测。**
|
||||
|
||||
即便我做了调整,是不是所有的代码就都能测试了呢?不尽然。从我个人的经验上看,不能测试的代码往往是与第三方相关的代码,比如访问数据库的代码,或是访问第三方服务之类的。但不能测试的代码已经非常有限了。我们将它们隔离在一个小角落就好了。
|
||||
|
||||
至此,我们已经从理念上讲了怎样做好 TDD。有的人可能已经跃跃欲试了,但更多的人会用自己所谓的“经验”告诉你,TDD 并不是那么好做的。
|
||||
|
||||
怎么做好 TDD 呢?下一讲,我会给你继续讲解,而且,我们“任务分解大戏”这个时候才开始真正拉开大幕!
|
||||
|
||||
## 总结时刻
|
||||
|
||||
一些优秀的程序员不仅仅在写测试,还在探索写测试的实践。有人尝试着先写测试,于是,有了一种实践叫测试先行开发。还有人更进一步,一边写测试,一边调整代码,这叫做测试驱动开发,也就是 TDD。
|
||||
|
||||
从步骤上看,关键差别就在,TDD 在测试通过之后,要回到代码上,消除代码的坏味道。
|
||||
|
||||
测试驱动开发已经是行业中的优秀实践,学习测试驱动开发的第一步是,记住测试驱动开发的节奏:红——绿——重构。把测试放在前面,还带来了视角的转变,要编写可测的代码,为此,我们甚至需要调整设计,所以,有人也把 TDD 称为测试驱动设计。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**我们应该编写可测的代码。**
|
||||
|
||||
最后,我想请你分享一下,你对测试驱动开发的理解是怎样的呢?学习过这篇内容之后,你又发现了哪些与你之前理解不尽相同的地方呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
133
极客时间专栏/geek/10x程序员工作法/任务分解/14 | 大师级程序员的工作秘笈.md
Normal file
133
极客时间专栏/geek/10x程序员工作法/任务分解/14 | 大师级程序员的工作秘笈.md
Normal file
@ -0,0 +1,133 @@
|
||||
<audio id="audio" title="14 | 大师级程序员的工作秘笈" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/6c/0e/6cf6da937c1fcd2e5919210f75cf550e.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
前面我和大家分享了 TDD 的来龙去脉,那些尚未将 TDD 烂熟于胸的同学会分为两个派别。一派是摩拳擦掌,准备动手实践一番;另一派是早就自我修炼过,但实践之路不通。所以,市面上经常会听到有人说,TDD 不实用。
|
||||
|
||||
但是 TDD 真的不实用吗?
|
||||
|
||||
和任何一门技能一样,TDD 也是需要练习的。更重要的是,你需要打通 TDD 的“任督二脉”,而这关键正是我们这个模块的主题:任务分解。而且,在今天的内容中,我还将带你领略大师级程序员的工作风范。让我们开始吧!
|
||||
|
||||
## TDD从何而来?
|
||||
|
||||
要学最原汁原味的 TDD ,莫过于从源头学起。
|
||||
|
||||
从前 TDD 只在小圈子里流行,真正让它在行业里广为人知的是 Kent Beck 那本知名的软件工程之作[《解析极限编程》](http://book.douban.com/subject/6828074/)(Extreme Programming Explained)。这是一本重要的作品,它介绍了一种软件开发方法:[极限编程](http://en.wikipedia.org/wiki/Extreme_programming)。
|
||||
|
||||
当年他写作之时,许多人都在努力探寻瀑布开发方法之外的软件工程方法,除了极限编程,还有[特征驱动开发](http://en.wikipedia.org/wiki/Feature-driven_development)、[水晶开发方法](http://en.wikiversity.org/wiki/Crystal_Methods)等等,正是这些开发方法的探索,才有了后面敏捷方法的诞生。
|
||||
|
||||
极限编程对于行业最大的贡献在于,它引入了大量的实践,比如,前面提到过的持续集成、这里提到的 TDD,还有诸如结对编程、现场客户等等。
|
||||
|
||||
极限编程之所以叫“极限”,它背后的理念就是把好的实践推向极限。
|
||||
|
||||
前面提到持续集成时,我们已经介绍过这个理念,如果集成是好的,我们就尽早集成,推向极限每一次修改都集成,这就是持续集成。
|
||||
|
||||
如果开发者测试是好的,我们就尽早测试,推向极限就是先写测试,再根据测试调整代码,这就是测试驱动开发。
|
||||
|
||||
如果代码评审是好的,我们就多做评审,推向极限就是随时随地地代码评审,这就是结对编程。
|
||||
|
||||
如果客户交流是好的,我们就和客户多交流,推向极限就是客户与开发团队时时刻刻在一起,这就是现场客户。这种极限思维是一种很好的思考问题方式,推荐你也在工作中尝试使用一下。
|
||||
|
||||
虽然 TDD 只是《解析极限编程》介绍的诸多实践的一种,它却是与开发人员关系最为密切的一个实践。
|
||||
|
||||
随着 TDD 逐渐流行开来,人们对如何做 TDD 也越来越感兴趣,于是,Kent Beck 又专门为 TDD 写了一本书,叫[《测试驱动开发》](http://book.douban.com/subject/1230036/)。
|
||||
|
||||
## 大师级程序员的秘笈
|
||||
|
||||
《测试驱动开发》这本书很有意思。如果你只是为了了解 TDD,这本书可能很无聊。Kent Beck 在第一部分只是在写一个功能,写完一段又写一段。
|
||||
|
||||
这本书我看过两遍,第一遍觉得平淡无奇,这种代码我也能写。第二遍看懂他的思路时,我几乎是震惊的感觉,因为它完全是在展示 Kent Beck 的工作方式。这也是我把 TDD 放到这个部分来讲的重要原因,Kent Beck 在做的就是任务分解。任务分解,也是这本书的真正价值所在。
|
||||
|
||||
当时,我已经工作了很多年,自以为自己在写代码上已经很专业了。看懂 Kent Beck 的思路,我才知道,与他相比,我还不够专业。
|
||||
|
||||
Kent Beck 是怎么做的呢?每当遇到一件要做的事,Kent Beck 总会先把它分解成几个小任务,记在一个清单上,然后,才是动手写测试、写代码、重构这样一个小循环。等一个循环完成了,他会划掉已经做完的任务,开始下一个。
|
||||
|
||||
一旦在解决问题的过程中遇到任何新的问题,他会把这个要解决的问题记录在清单上,保证问题不会丢失,然后,继续回到自己正在处理的任务上。当他把一个个任务完成的时候,问题就解决完了。
|
||||
|
||||
你或许会纳闷,这有什么特别的吗?你不妨回答这样一个问题,你多长时间能够提交一次代码?如果你的答案超过半天,对不起,你的做法步子一定是太大了。你之所以不能小步提交,一定是牵扯了太多相关的部分。
|
||||
|
||||
**Kent Beck 的做法清晰而有节奏,每个任务完成之后,代码都是可以提交的。**看上去很简单,但这是大多数程序员做不到的。
|
||||
|
||||
只有把任务分解到很小,才有可能做到小步提交。你能把任务分解到很小,其实是证明你已经想清楚了。**而大多数程序员之所以开发效率低,很多时候是没想清楚就动手了。**
|
||||
|
||||
我在 ThoughtWorks 工作时,每个人都会有个 Sponsor,类似于工厂里师傅带徒弟的关系。我当时的 Sponsor 是 ThoughtWorks 现任的 CEO 郭晓,他也是写代码出身的。有一次,他给我讲了他和 Wiki 的发明者 Ward Cunningham 一起结对编程的场景。
|
||||
|
||||
Ward 每天拿到一个需求,他并不急于写代码,而是和郭晓一起做任务分解,分解到每个任务都很清晰了,才开始动手做。接下来就简单了,一个任务一个任务完成就好了。
|
||||
|
||||
当时,郭晓虽然觉得工作节奏很紧张,但思路则是非常清晰的。有时,他也很奇怪,因为在开始工作之前,他会觉得那个问题非常难以解决。结果一路分解下来,每一步都是清晰的,也没遇到什么困难就完成了。
|
||||
|
||||
之所以这里要和你讲 Ward Cunningham 的故事,因为他就是当年和 Kent Beck 在同一个小圈子里一起探讨进步的人,所以,在解决问题的思路上,二人如出一辙。
|
||||
|
||||
为什么任务分解对于 TDD 如此重要呢?因为只有当任务拆解得足够小了,你才能知道怎么写测试。
|
||||
|
||||
**很多人看了一些 TDD 的练习觉得很简单,但自己动起手来却不知道如何下手。中间就是缺了任务分解的环节。**
|
||||
|
||||
任务分解是个好习惯,但想要掌握好它,大量的练习是必须的。我自己也着实花不少时间进行练习,每接到一个任务,我都会先做任务分解,想着怎么把它拆成一步一步可以完成的小任务,之后再动手解决。
|
||||
|
||||
## 微操作
|
||||
|
||||
随着我在任务分解上练习的增多,我越发理解任务分解的关键在于:**小。**
|
||||
|
||||
小到什么程度呢?有时甚至可以小到你可能认为这件事不值得成为一件独立的事。比如升级一个依赖的版本,做一次变量改名。
|
||||
|
||||
这样做的好处是什么呢?它保证了我可以随时停下来。
|
||||
|
||||
我曾在一本书里读到过关于著名高尔夫球手“老虎”伍兹的故事。高尔夫球手在打球的时候,可能会受到一些外界干扰。一般情况下还好,如果他已经开始挥杆,这时候受到了干扰,一般选手肯定是继续把杆挥下去,但通常的结果是打得不理想。
|
||||
|
||||
而伍兹遇到这种情况,他会停下来,重新做挥杆的动作,保证了每一杆动作的标准。
|
||||
|
||||
伍兹能停下来,固然是经过了大量的练习,但还有一个关键在于,对于别人而言,挥杆击球是一个动作,必须一气呵成。而对伍兹来说,这个动作是由若干小动作组成的,他只不过是刚好完成了某个小动作,而没有做下一个小动作而已。
|
||||
|
||||
换句话说,大家同样都是完成一个原子操作,只不过,伍兹的原子操作比其他人的原子操作小得多。
|
||||
|
||||
同样,我们写程序的时候,都不喜欢被打扰,因为一旦被打扰,接续上状态需要很长一段时间,毕竟,我们可不像操作系统那么容易进行上下文切换。
|
||||
|
||||
但如果任务足够小,完成一个任务,我们选择可以进入到下一个任务,也可以停下来。这样,即便被打扰,我们也可以很快收尾一个任务,不至于被影响太多。
|
||||
|
||||
其实,这种极其微小的原子操作在其他一些领域也有着自己的应用。有一种实践叫微习惯,以常见的健身为例,很多人难以坚持,主要是人们一想到健身,就会想到汗如雨下的健身场景,想想就放弃了。
|
||||
|
||||
但如果你一次只做一个俯卧撑呢?对大多数人来说,这就不是很难的一件事,那就先做一个。做完了一个如果你还想做,就接着做,不想做就不做了。
|
||||
|
||||
一个俯卧撑?你会说这也叫健身,一个俯卧撑确实是一个很小的动作,重要的是,一个俯卧撑是你可以坚持完成的,如果每天做10个,恐怕这都是大多数人做不到的。我们知道,养成一个习惯,最难的是坚持。**如果你有了一个微习惯,坚持就不难了。**
|
||||
|
||||
我曾经在 github 上连续提交代码1000天,这是什么概念?差不多三年的时间里,每天我都能够坚持写代码,提交代码,这还不算工作上写的代码。
|
||||
|
||||
对于大多数人来说,这是不可思议的。但我坚持做到了,不是因为我有多了不起,而是我养成了自己的微习惯。
|
||||
|
||||
这个连续提交的基础,就是我自己在练习任务分解时,不断地尝试把一件事拆细,这样,我每天都至少能保证完成一小步。当然,如果有时间了,我也会多写一点。正是通过这样的方法,我坚持了1000天,也熟练掌握了任务分解的技巧。
|
||||
|
||||
**一个经过分解后的任务,需要关注的内容是有限的,我们就可以针对着这个任务,把方方面面的细节想得更加清晰。**很多人写代码之所以漏洞百出,一个重要的原因就是因为任务粒度太大。
|
||||
|
||||
我们作为一个普通人,能考虑问题的规模是有限的,也就很难方方面面都考虑仔细。
|
||||
|
||||
## 微操作与分支模型
|
||||
|
||||
经过这种练习之后,任务分解也就成了我的本能,不再局限于写程序上。我遇到任何需要解决的问题,脑子里的第一反应一定是,它可以怎么一步一步地完成,确定好分解之后,解决问题就是一步一步做了。
|
||||
|
||||
如果不能很好地分解,那说明我还没想清楚,还需要更多信息,或者需要找到更好的解决方案。
|
||||
|
||||
一旦你懂得了把任务分解的重要性,甚至通过训练能达到微操作的水准,你就很容易理解一些因为步子太大带来的问题。举一个在开发中常见的问题,代码开发的分支策略。
|
||||
|
||||
关于分支策略,行业里有很多不同的做法。有的团队是大家都在一个分支上写代码,有的是每个人拉出一个分支,写完了代码再合并回去。你有没有想过为什么会出现这种差异呢?
|
||||
|
||||
行业中的最佳实践是,基于主分支的模型。大家都在同一个分支上进行开发,毕竟拉分支是一个麻烦事,虽然 git 的出现极大地降低了拉分支的成本。
|
||||
|
||||
但为什么还有人要拉出一个分支进行开发呢?多半的原因是他写的代码太多了,改动量太大,很难很快地合到开发的主分支上来。
|
||||
|
||||
那下一个问题就来了,为什么他会写那么多代码,没错,答案就是步子太大了。
|
||||
|
||||
如果你懂得任务分解,每一个分解出来的任务要改动的代码都不会太多,影响都在一个可控的范围内,代码都可以很快地合并到开发的主分支上,也就没有必要拉分支了。
|
||||
|
||||
在我的实际工作中,我带的团队基本上都会采用基于主分支的策略。只有在做一些实验的时候,才会拉出一个开发分支来,但它并不是常态。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
TDD 在很多人眼中是不实用的,一来他们并不理解测试“驱动”开发的含义,但更重要的是,他们很少会做任务分解。而任务分解是做好 TDD 的关键点。只有把任务分解到可以测试的地步,才能够有针对性地写测试。
|
||||
|
||||
同样听到任务分解这个说法,不同的人理解依然是不一样的。我把任务分解的结果定义成微操作,它远比大多数人理解得小。我们能将任务分解到多小,就决定了我们原子操作的粒度是多大。软件开发中的许多问题正是由于粒度太大造成的,比如,分支策略。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**将任务拆小,越小越好。**
|
||||
|
||||
最后,我想请你分享一下,你身边是否有一些由于任务分解得不够小带来的问题。欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
92
极客时间专栏/geek/10x程序员工作法/任务分解/15 | 一起练习:手把手带你分解任务.md
Normal file
92
极客时间专栏/geek/10x程序员工作法/任务分解/15 | 一起练习:手把手带你分解任务.md
Normal file
@ -0,0 +1,92 @@
|
||||
<audio id="audio" title="15 | 一起练习:手把手带你分解任务" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/26/bc/26353184acf10a96610fcf1273898bbc.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
前面在讨论 TDD 的时候,我们说任务分解是 TDD 的关键。但这依然是一种感性上的认识。今天,我们就来用一个更加具体的例子,让你看看任务分解到底可以做到什么程度。
|
||||
|
||||
这个例子就是最简单的用户登录。需求很简单,用户通过用户名密码登录。
|
||||
|
||||
我相信,实现这个功能对大家来说并不困难,估计在我给出这个题目的时候,很多人脑子里已经开始写代码了。今天主要就是为了带着大家体验一下任务分解的过程,看看怎样将一个待实现的需求一步步拆细,变成一个个具体可执行的任务。
|
||||
|
||||
要完成这个需求,最基本的任务是用户通过输入用户名和密码登录。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/df/1b/df3bcd76b68787145c5d6e16804a191b.jpg" alt="">
|
||||
|
||||
用户名和密码登录这个任务很简单,但我们在第一部分讲过沙盘推演,只要推演一下便不难发现,这不是一个完整的需求。
|
||||
|
||||
用户名和密码是哪来的呢?它们可能是用户设置的,也可能是由系统管理员设置的。这里我们就把它们简单设定成由用户设定。另外,有用户登录,一般情况下,还会有一个退出的功能。好了,这才是一个简单而完整的需求。我们就不做进一步的需求扩展。
|
||||
|
||||
所以,我们要完成的需求列表是下面这样的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a0/6f/a01d61a6ebfe1ccb1ff38eaec4f4c96f.jpg" alt=""><br>
|
||||
假设我们就是拿到这个需求列表的程序员,要进行开发。我们先要分析一下要做的事情有哪些,也就是任务分解。到这里,你可以先暂停一会,尝试自己分解任务,之后,再来对比我后面给出的分解结果,看看差异有多少。
|
||||
|
||||
好,我们继续。
|
||||
|
||||
我们先来决定一下技术方案,就用最简单的方式实现,在数据库里建一张表保存用户信息。一旦牵扯到数据库表,就会涉及到数据库迁移,所以,有了下面的任务。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/5d/2e/5d88a1849a51d566e8702142b16e112e.jpg" alt=""><br>
|
||||
这时,需要确定这两个任务自己是否知道怎么做。设计表,一般熟悉 SQL 的人都知道怎么做。数据库迁移,可能要牵扯到技术选型,不同的数据库迁移工具,写法上略有差别,我们就把还不完全明确的内容加到任务清单里。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/23/a3/23d543d679448bd5250081168ba12da3.jpg" alt=""><br>
|
||||
数据库的内容准备好了,接下来,就轮到编写代码的准备上了。我们准备用常见的 REST 服务对外提供访问。这里就采用最常规的三层技术架构,所以,一般要编写下面几项内容。
|
||||
|
||||
- 领域对象,这里就是用户。
|
||||
- 数据访问层,在不同的项目里面叫法不一,有人从 J2EE 年代继承下来叫 DAO(数据访问对象,Data Access Obejct),有人跟着 Mybatis 叫 mapper,我现在更倾向于使用领域驱动设计的术语,叫 repository。
|
||||
- 服务层,提供对外的应用服务,完成业务处理。
|
||||
- 资源层,提供 API 接口,包括外部请求的合法性检查。
|
||||
|
||||
根据这个结构,就可以进一步拆解我们的开发任务了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/03/8a/036a5560bcde11be857b04ce610bc18a.jpg" alt="">
|
||||
|
||||
不知道你有没有注意到,我的任务清单上列任务的顺序,是按照一个需求完整实现的过程。
|
||||
|
||||
比如,第一部分就是一个完整的用户注册过程,先写 User,然后是 UserRepository 的 save 方法,接着是 UserService 的 register 方法,最后是 UserResource 的 register 方法。等这个需求开发完了,才是 login 和 logout。
|
||||
|
||||
**很多人可能更习惯一个类一个类的写,我要说,最好按照一个需求、一个需求的过程走,这样,任务是可以随时停下来的。**
|
||||
|
||||
比如,同样是只有一半的时间,我至少交付了一个完整的注册过程,而按照类写的方法,结果是一个需求都没完成。这只是两种不同的安排任务的顺序,我更支持按照需求的方式。
|
||||
|
||||
我们继续讨论任务分解。任务分解到这里,需要看一下这几个任务有哪个不好实现。register 只是一个在数据库中存储对象的过程,没问题,但 login 和 logout 呢?
|
||||
|
||||
考虑到我们在做的是一个 REST 服务,这个服务可能是分布到多台机器上,请求到任何一台都能提供同样的服务,我们需要把登录信息共享出去。
|
||||
|
||||
这里我们就采用最常见的解决方案:用 Redis 共享数据。登录成功的话,就需要把用户的 Session 信息放到 Redis 里面,退出的话,就是删除 Session 信息。在我们的任务列表里,并没有出现 Session,所以,需要引入 Session 的概念。任务调整如下。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/6f/13/6fb2e0509c4b05b5b8ed18225d180913.jpg" alt="">
|
||||
|
||||
如果采用 Redis,我们还需要决定一下在 Redis 里存储对象的方式,我们可以用原生的Java序列化,但一般在开发中,我们会选择一个文本化的方式,这样维护起来更容易。这里选择常见的 JSON,所以,任务就又增加了两项。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8a/c2/8a26b41dafe2b0704b291d44b3fc99c2.jpg" alt="">
|
||||
|
||||
至此,最基本的登录退出功能已经实现了,但我们需要问一个问题,这就够了吗?之所以要登录,通常是要限定用户访问一些资源,所以,我们还需要一些访问控制的能力。
|
||||
|
||||
简单的做法就是加入一个 filter,在请求到达真正的资源代码之前先做一层过滤,在这个 filter 里面,如果待访问的地址是需要登录访问的,我们就看看用户是否已经登录,现在一般的做法是用一个 Token,这个 Token 一般会从 HTTP 头里取出来。但这个 Token 是什么时候放进去的呢?答案显然是登录的时候。所以,我们继续调整任务列表。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1d/cd/1dc9b55d2ccf024bc66b7e90e6f0bbcd.jpg" alt="">
|
||||
|
||||
至此,我们已经比较完整地实现了一个用户登录功能。当然,要在真实项目中应用,需求还是可以继续扩展的。比如:用户 Session 过期、用户名密码格式校验、密码加密保存以及刷新用户 Token等等。
|
||||
|
||||
这里主要还是为了说明任务分解,相信如果需求继续扩展,根据上面的讨论,你是有能力进行后续分解的。
|
||||
|
||||
来看一下分解好的任务清单,你也可以拿出来自己的任务清单对比一下,看看差别有多大。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/94/ab/94f217310da9fe77e6739f3b15702cab.jpg" alt=""><br>
|
||||
首先要说明的是,任务分解没有一个绝对的标准答案,分解的结果根据个人技术能力的不同,差异也会很大。
|
||||
|
||||
**检验每个任务项是否拆分到位,就是看你是否知道它应该怎么做了。**不过,即便你技术能力已经很强了,我依然建议你把任务分解到很细,观其大略人人行,细致入微见本事。
|
||||
|
||||
也许你会问我,我在写代码的时候,也会这么一项一项地把所有任务都写下来吗?实话说,我不会。因为任务分解我在之前已经训练过无数次,已经习惯怎么一步一步地把事情做完。换句话说,任务清单虽然我没写下来,但已经在我脑子里了。
|
||||
|
||||
不过,我会把想到的,但容易忽略的细节写下来,因为任务清单的主要作用是备忘录。一般情况下,主流程我们不会遗漏,但各种细节常常会遗漏,所以,想到了还是要记下来。
|
||||
|
||||
另外,对比我们在分解过程中的顺序,你会看到这个完整任务清单的顺序是调整过的,你可以按照这个列表中的内容一项一项地做,调整最基本的标准是,按照这些任务的依赖关系以及前面提到的“完整地实现一个需求”的原则。
|
||||
|
||||
最后,我要特别强调一点,所有分解出来的任务,都是独立的。也就是说,**每做完一个任务,代码都是可以提交的。**只有这样,我们才可能做到真正意义上的小步提交。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**按照完整实现一个需求的顺序去安排分解出来的任务。**
|
||||
|
||||
最后,我想请你分享一下,你的任务清单和我的任务清单有哪些差异呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
138
极客时间专栏/geek/10x程序员工作法/任务分解/16 | 为什么你的测试不够好?.md
Normal file
138
极客时间专栏/geek/10x程序员工作法/任务分解/16 | 为什么你的测试不够好?.md
Normal file
@ -0,0 +1,138 @@
|
||||
<audio id="audio" title="16 | 为什么你的测试不够好?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d4/af/d47e8c94a219c6f86185008dee520baf.mp3"></audio>
|
||||
|
||||
你好!我是郑晔。今天是除夕,我在这里给大家拜年了,祝大家在新的一年里,开发越做越顺利!
|
||||
|
||||
关于测试,我们前面讲了很多,比如:开发者应该写测试;要写可测的代码;要想做好 TDD,先要做好任务分解,我还带你进行了实战操作,完整地分解了一个任务。
|
||||
|
||||
但有一个关于测试的重要话题,我们始终还没聊,那就是测试应该写成什么样。今天我就来说说怎么把测试写好。
|
||||
|
||||
你或许会说,这很简单啊,前面不都讲过了吗?不就是用测试框架写代码吗?其实,理论上来说,还真应该就是这么简单,但现实情况却往往相反。我看到过很多团队在测试上出现过各种各样的问题,比如:
|
||||
|
||||
- 测试不稳定,这次能过,下次过不了;
|
||||
- 有时候是一个测试要测的东西很简单,测试周边的依赖很多,搭建环境就需要很长的时间;
|
||||
- 这个测试要运行,必须等到另外一个测试运行结束;
|
||||
- ……
|
||||
|
||||
如果你也在工作中遇到过类似的问题,那你理解的写测试和我理解的写测试可能不是一回事,那问题出在哪呢?
|
||||
|
||||
为什么你的测试不够好呢?
|
||||
|
||||
**主要是因为这些测试不够简单。只有将复杂的测试拆分成简单的测试,测试才有可能做好。**
|
||||
|
||||
## 简单的测试
|
||||
|
||||
测试为什么要简单呢?有一个很有趣的逻辑,不知道你想没想过,测试的作用是什么?显然,它是用来保证代码的正确性。随之而来的一个问题是,谁来保证测试的正确性?
|
||||
|
||||
许多人第一次面对这个问题,可能会一下子懵住,但脑子里很快便会出现一个答案:测试。但是,你看有人给测试写测试吗?肯定没有。因为一旦这么做,这个问题会随即上升,谁来保证那个测试的正确性呢?你总不能无限递归地给测试写测试吧。
|
||||
|
||||
既然无法用写程序的方式保证测试的正确性,我们只有一个办法:**把测试写简单,简单到一目了然,不需要证明它的正确性。**所以,如果你见到哪个测试写得很复杂,它一定不是一个好的测试。
|
||||
|
||||
既然说测试应该简单,我们就来看看一个简单的测试应该是什么样子。下面我给出一个简单的例子,你可以看一下。
|
||||
|
||||
```
|
||||
@Test
|
||||
void should_extract_HTTP_method_from_HTTP_request() {
|
||||
// 前置准备
|
||||
request = mock(HttpRequest.class);
|
||||
when(request.getMethod()).thenReturn(HttpMethod.GET);
|
||||
HttpMethodExtractor extractor = new HttpMethodExtractor();
|
||||
|
||||
// 执行
|
||||
HttpMethod method = extractor.extract(request);
|
||||
|
||||
// 断言
|
||||
assertThat(method, is(HttpMethod.GET);
|
||||
|
||||
// 清理
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
这个测试来自我的开源项目 [Moco](http://github.com/dreamhead/moco),我稍做了一点调整,便于理解。这个测试很简单,从一个 HTTP 请求中提取出 HTTP 方法。
|
||||
|
||||
我把这段代码分成了四段,分别是**前置准备、执行、断言和清理**,这也是一般测试要具备的四段。
|
||||
|
||||
- 这几段的核心是中间的执行部分,它就是测试的目标,但实际上,它往往也是最短小的,一般就是一行代码调用。其他的部分都是围绕它展开的,在这里就是调用 HTTP 方法提取器提取 HTTP 方法。
|
||||
- 前置准备,就是准备执行部分所需的依赖。比如,一个类所依赖的组件,或是调用方法所需要的参数。在这个测试里面,我们准备了一个 HTTP 请求,设置了它的方法是一个 GET 方法,这里面还用到了之前提到的 Mock 框架,因为完整地设置一个 HTTP 请求很麻烦,而且与这个测试也没什么关系。
|
||||
- 断言是我们的预期,就是这段代码执行出来怎么算是对的。这里我们判断了提取出来的方法是否是 GET 方法。另外补充一点,断言并不仅仅是 assert,如果你用 Mock 框架的话,用以校验 mock 对象行为的 verify 也是一种断言。
|
||||
- 清理是一个可能会有的部分,如果你的测试用到任何资源,都可以在这里释放掉。不过,如果你利用好现有的测试基础设施(比如,JUnit 的 Rule),遵循好测试规范的话,很多情况下,这个部分就会省掉了。
|
||||
|
||||
怎么样,看着很简单吧,是不是符合我前面所说的不证自明呢?
|
||||
|
||||
## 测试的坏味道
|
||||
|
||||
有了对测试结构的了解,我们再来说说常见的测试“坏味道”。
|
||||
|
||||
首先是执行部分。不知道你有没有注意到,前面我提到执行部分时用了一个说法,一行代码调用。是的,第一个“坏味道”就来自这里。
|
||||
|
||||
很多人总想在一个测试里做很多的事情,比如,出现了几个不同方法的调用。请问,你的代码到底是在测试谁呢?
|
||||
|
||||
**这个测试一旦出错,就需要把所有相关的几个方法都查看一遍,这无疑是增加了工作的复杂度。**
|
||||
|
||||
也许你会问,那我有好几个方法要测试,该怎么办呢?很简单,多写几个测试就好了。
|
||||
|
||||
另一个典型“坏味道”的高发区是在断言上,请记住,**测试一定要有断言。**没有断言的测试,是没有意义的,就像你说自己是世界冠军,总得比个赛吧!
|
||||
|
||||
我见过不少人写了不少测试,但测试运行几乎从来就不会错。出于好奇,我打开代码一看,没有断言。
|
||||
|
||||
没有断言当然就不会错了,写测试的同事还很委屈地说,测试不好写,而且,他已经验证了这段代码是对的。就像我前面讲过的,测试不好写,往往是设计的问题,应该调整的是设计,而不是在测试这里做妥协。
|
||||
|
||||
还有一种常见的“坏味道”:复杂。最典型的场景是,当你看到测试代码里出现各种判断和循环语句,基本上这个测试就有问题了。
|
||||
|
||||
举个例子,测试一个函数,你的断言写在一堆 if 语句中,美其名曰,根据条件执行。还是前面提到的那个观点,你怎么保证这个测试函数写的是对的?除非你用调试的手段,否则,你都无法判断你的条件分支是否执行到了。
|
||||
|
||||
你或许会疑问,我有一大堆不同的数据要测,不用循环不用判断,我怎么办呢?**你真正应该做的是,多写几个测试,每个测试覆盖一种场景。**
|
||||
|
||||
## 一段旅程(A-TRIP)
|
||||
|
||||
怎么样的测试算是好的测试呢?有人做了一个总结 A-TRIP,这是五个单词的缩写,分别是
|
||||
|
||||
- Automatic,自动化;
|
||||
- Thorough,全面的;
|
||||
- Repeatable,可重复的;
|
||||
- Independent,独立的;
|
||||
- Professional,专业的。
|
||||
|
||||
下面,我们看看这几个单词分别代表什么意思。
|
||||
|
||||
**Automatic,自动化。**有了前面关于自动化测试的铺垫,这可能最好理解,就是把测试尽可能交给机器执行,人工参与的部分越少越好。
|
||||
|
||||
这也是我们在前面说,测试一定要有断言的原因,因为一个测试只有在有断言的情况下,机器才能自动地判断测试是否成功。
|
||||
|
||||
**Thorough,全面,应该尽可能用测试覆盖各种场景。**理解这一点有两个角度。一个是在写代码之前,要考虑各种场景:正常的、异常的、各种边界条件;另一个角度是,写完代码之后,我们要看测试是否覆盖了所有的代码和所有的分支,这就是各种测试覆盖率工具发挥作用的场景了。
|
||||
|
||||
当然,你想做到全面,并非易事,如果你的团队在补测试,一种办法是让测试覆盖率逐步提升。
|
||||
|
||||
**Repeatable,可重复的。**这里面有两个角度:某一个测试反复运行,结果应该是一样的,这说的是,每一个测试本身都不应该依赖于任何不在控制之下的环境。如果有,怎么办,想办法。
|
||||
|
||||
比如,如果有外部的依赖,就可以采用模拟服务的手段,我的 [Moco](http://github.com/dreamhead/moco) 就是为了解决外部依赖而生的,它可以模拟外部的 HTTP 服务,让测试变得可控。
|
||||
|
||||
有的测试会依赖数据库,那就在执行完测试之后,将数据库环境恢复,像 Spring 的测试框架就提供了测试数据库回滚的能力。如果你的测试反复运行,不能产生相同的结果,要么是代码有问题,要么是测试有问题。
|
||||
|
||||
理解可重复性,还有一个角度,一堆测试反复运行,结果应该是一样的。这说明测试和测试之间没有任何依赖,这也是我们接下来要说的测试的另外一个特点。
|
||||
|
||||
**Independent,独立的。**测试和测试之间不应该有任何依赖,什么叫有依赖?比如,如果测试依赖于外部数据库或是第三方服务,测试 A 在运行时在数据库里写了一些值,测试 B 要用到数据库里的这些值,测试 B 必须在测试 A 之后运行,这就叫有依赖。
|
||||
|
||||
我们不能假设测试是按照编写顺序运行的。比如,有时为了加快测试运行速度,我们会将测试并行起来,在这种情况下,顺序是完全无法保证的。如果测试之间有依赖,就有可能出现各种问题。
|
||||
|
||||
减少外部依赖可以用 mock,实在要依赖,每个测试自己负责前置准备和后续清理。如果多个测试都有同样的准备和清理呢?那不就是 setup 和 teardown 发挥作用的地方吗?测试基础设施早就为我们做好了准备。
|
||||
|
||||
**Professional,专业的。**这一点是很多人观念中缺失的,测试代码,也是代码,也要按照代码的标准去维护。这就意味着你的测试代码也要写得清晰,比如:良好的命名,把函数写小,要重构,甚至要抽象出测试的基础库,在 Web 测试中常见的 PageObject 模式,就是这种理念的延伸。
|
||||
|
||||
看了这点,你或许会想,你说的东西有点道理,但我的代码那么复杂,测试路径非常多,我怎么能够让自己的测试做到满足这些要求呢?
|
||||
|
||||
我必须强调一个之前讲测试驱动开发强调过的观点:**编写可测试的代码。**很多人写不好测试,或者觉得测试难写,关键就在于,你始终是站在写代码的视角,而不是写测试的视角。如果你都不重视测试,不给测试留好空间,测试怎么能做好呢?
|
||||
|
||||
## 总结时刻
|
||||
|
||||
测试是一个说起来很简单,但很不容易写好的东西。在实际工作中,很多人都会遇到关于测试的各种各样问题。之所以出现问题,主要是因为这些测试写得太复杂了。测试一旦复杂了,我们就很难保证测试的正确性,何谈用测试保证代码的正确性。
|
||||
|
||||
我给你讲了测试的基本结构:前置准备、执行、断言和清理,还介绍了一些常见的测试“坏味道”:做了太多事的测试,没有断言的测试,还有一种看一眼就知道有问题的“坏味道”,测试里有判断语句。
|
||||
|
||||
怎么衡量测试是否做好了呢?有一个标准:A-TRIP,这是五个单词的缩写,分别是Automatic(自动化)、Thorough(全面)、Repeatable(可重复的)、Independent(独立的)和 Professional(专业的)。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**要想写好测试,就要写简单的测试。**
|
||||
|
||||
最后,我想请你分享一下,经过最近持续对测试的讲解,你对测试有了哪些与之前不同的理解呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
116
极客时间专栏/geek/10x程序员工作法/任务分解/17 | 程序员也可以“砍”需求吗?.md
Normal file
116
极客时间专栏/geek/10x程序员工作法/任务分解/17 | 程序员也可以“砍”需求吗?.md
Normal file
@ -0,0 +1,116 @@
|
||||
<audio id="audio" title="17 | 程序员也可以“砍”需求吗?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7b/91/7bf707f1eb2a7bce43495d43bb316491.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
我们前面讲的任务分解,主要是在讲开发任务的分解。今天我们换个角度,看看需求的分解。是的,需求也要分解。
|
||||
|
||||
有一次,我和一个做开发的同事聊天,他给我讲了他近期的烦恼。
|
||||
|
||||
>
|
||||
<p>**同事:**我们现在就是需求太多,开发的人太少,再这么干下去,哪天觉得自己抗不住了,我就拍拍屁股走人。<br>
|
||||
**我:**你没尝试着砍砍需求?<br>
|
||||
**同事:**怎么没尝试?产品的人都不同意。这批功能他们都说是关键功能。<br>
|
||||
**我:**你有没有尝试把需求拆开了再砍呢?<br>
|
||||
**同事:**还可以这样?</p>
|
||||
|
||||
|
||||
同事很惊讶,我一点都不意外。我们都是在说需求,但彼此对需求的理解却是大不相同。我先来问个问题,提到需求这个词,你会想到什么呢?
|
||||
|
||||
以我们用了好多次的登录为例,如果我问你这个需求是什么,大多数人的第一直觉还是用户名密码登录。
|
||||
|
||||
**基本上,闯入你脑海的需求描述是主题(epic),在敏捷开发中,有人称之为主用户故事(master story)。**
|
||||
|
||||
如果你对需求的管理粒度就是主题,那好多事情就没法谈了。比如,时间紧迫的时候,我想砍需求,你问产品经理,我不做登录行不行,你就等着被拒绝吧。
|
||||
|
||||
但是,如果你说时间比较紧,我能不能把登录验证码放到后面做,或是邮件地址验证的功能放到后面,这种建议产品经理是可以和你谈的。
|
||||
|
||||
这其中的差别就在于,后者将需求分解了。
|
||||
|
||||
大多数人可以理解需求是要分解的,但是,分解的程度不同,就是导致执行效果差异极大的根源。
|
||||
|
||||
以我的经验而言,**绝大多数问题都是由于分解的粒度太大造成的,少有因为粒度太小而出问题的。**所以,需求分解的一个原则是,粒度越小越好。
|
||||
|
||||
## 需求要分解
|
||||
|
||||
“主题”只是帮你记住大方向,真正用来进行需求管理,还是要靠进一步分解出来的需求。这里的讨论,我们会继续沿用前面专栏文章中已经介绍过的需求描述方式:用户故事,它将是我们这里讨论需求管理的基本单位。
|
||||
|
||||
如果你的团队用的是其他方式描述需求,你也可以找找是否有对应的管理方式。
|
||||
|
||||
上一个模块介绍“以终为始”,我们对用户故事的关注点主要在:用户故事一定要有验收标准,以确保一个需求的完整性。而在“任务分解”这个模块,我们看用户故事,则主要关注它作为需求分解的结果,也就是分拆出来要解决的一个个需求点。
|
||||
|
||||
在前面的讨论中,我们已经知道了用户故事的“长相”,但更重要的问题是,划分需求的方式有无数种,就像一块蛋糕,你可以横着切,也可以竖着切。如果你一刀不切,那就是拿着主题当用户故事。你也可以快刀飞起,把主题切碎。
|
||||
|
||||
每个人都会有自己喜欢的拆分方式,我相信知道拆分的重要性之后,你总会有办法的。这里,我主要想和你聊聊怎样评判拆分结果,毕竟我们要把它当作需求管理的基本单位。
|
||||
|
||||
只有细分的需求才能方便进行管理。什么样的需求才是一个好的细分需求呢?我们先来看看用户故事的衡量标准。
|
||||
|
||||
评价用户故事有一个“ INVEST 原则”,这是六个单词的缩写,分别是:
|
||||
|
||||
- **Independent,独立的。**一个用户故事应该完成一个独立的功能,尽可能不依赖于其它用户故事,因为彼此依赖的用户故事会让管理优先级、预估工作量都变得更加困难。如果真的有依赖,一种好的做法是,将依赖部分拆出来,重新调整。
|
||||
- **Negotiable,可协商的。**有事大家商量是一起工作的前提,我们无法保证所有的细节都能100%落实到用户故事里,这个时候最好的办法是大家商量。它也是满足其它评判标准的前提,就像前面提到的,一个用户故事不独立,需要分解,这也需要大家一起商量的。
|
||||
- **Valuable,有价值的。**一个用户故事都应该有其自身价值,这一项应该最容易理解,没有价值的事不做。但正如我们一直在说的那样,做任何一个事情之前,先问问价值所在。
|
||||
- **Estimatable,可估算的。**我们会利用用户故事估算的结果安排后续的工作计划。不能估算的用户故事,要么是因为有很多不确定的因素,要么是因为需求还是太大,这样的故事还没有到一个能开发的状态,还需要产品经理进一步分析。
|
||||
- **Small,小。**步子大了,不行。不能在一定时间内完成的用户故事只应该有一个结果,拆分。小的用户故事才方便调度,才好安排工作。
|
||||
- **Testable,可测试的。**不能测试谁知道你做得对不对。这个是我们在前面已经强调过的内容,也就是验收标准,你得知道怎样才算是工作完成。
|
||||
|
||||
“INVEST 原则”的说法是为了方便记忆,我们这里着重讨论两个点。
|
||||
|
||||
第一个关注点是可协商。作为实现者,我们要问问题。只是被动接受的程序员,价值就少了一半,只要你开始发问,你就会发现很多写需求的人没有想清楚的地方。
|
||||
|
||||
在我的职业生涯中,我无数次将需求挡了回去,不是我不合作,而是我不想做一些糊涂的需求。我之所以能问出问题,一方面是出于常识,另一方面就是这里说的用户故事是否有价值。**用户故事,之所以是故事,就是要讲,要沟通。**
|
||||
|
||||
还有一个更重要的关注点,也是这个模块的核心:小。无论是独立性也好,还是可估算的也罢,其前提都是小。只有当用户故事够小了,我们后续的腾挪空间才会大。
|
||||
|
||||
那接下来就是一个重要的问题,怎么才算小?这就牵扯到用户故事另一个重要方面:估算。
|
||||
|
||||
## 需求的估算
|
||||
|
||||
估算用户故事,首先要选择一个度量标准。度量用户故事大小的方式有很多种,有人用 T 恤大小的方式,也就是S、M、L、XL、XXL。也有人用费波纳契数列,也就是1、2、3、5、8等等。有了度量标准之后,就可以开始估算了。
|
||||
|
||||
我们从分解出来的用户故事挑出一个最简单的,比如,某个信息的查询。这个最简单的用户故事,其作用就是当作基准。
|
||||
|
||||
比如,我们采用费波纳契数列,那这个最简单的用户故事就是基准点1。其他的用户故事要与它一一比较,如果一个用户故事比它复杂,那可以按照复杂程度给个估计。
|
||||
|
||||
你或许会问,我怎么知道复杂程度是什么样的呢?这时候,我们前面讲过的任务分解就派上用场了,你得在大脑中快速地做一个任务分解,想想有哪些步骤要完成,然后才好做对比。
|
||||
|
||||
所以,你会发现,任务分解是基础中的基础,不学会分解,工作就只能依赖于感觉,很难成为一个靠谱的程序员。
|
||||
|
||||
**估算的结果是相对的,不是绝对精确的,我们不必像做科研一样,只要给出一个相对估算就好。**
|
||||
|
||||
同一个用户故事,不同的人估算出的结果可能会有差别。怎么样尽可能在团队中达成一致呢?这就需要团队中的很多人参与进来,如果团队规模不大,全员参与也可以。
|
||||
|
||||
如果多人进行估算,你就会发现一个有趣的现象,针对同一个用户故事,不同的人估算的结果差异很大。
|
||||
|
||||
如果差别不大,比如,你觉得3个点,我觉得2个点,我们协调一下就好。但如果差异很大,比如,你认为2个点,我认为8个点,那绝对是双方对任务的理解出现了巨大的差异,这个时候,我们就可以把刚才在脑中进行的任务分解“摆”到桌面上,看看差异在哪。
|
||||
|
||||
通常情况下,是双方对需求的理解出现了偏差,这时候负责用户故事编写的同事就要站出来,帮助大家澄清需求。所以,**一般来说,估算的过程也是大家加深对需求理解的过程。**
|
||||
|
||||
估算还有另外一个重要的作用:发现特别大的用户故事。一般而言,一个用户故事应该在一个迭代内完成。
|
||||
|
||||
比如,你预计大小为1点的用户故事要用1天完成,而你团队的迭代周期是两周,也就是10个工作日,那13点的任务是无论如何都完不成的。那该怎么办呢?很简单,把它拆分成多个小任务,这样一来,每个小任务都可以在一个迭代中完成了。
|
||||
|
||||
所以,一般来说,用户故事有可能经过两次拆分。一次是由负责业务需求的同事,比如,产品经理,根据业务做一次拆分。另外一次就是在估算阶段发现过大的用户故事,就再拆分一次。
|
||||
|
||||
当我们有了一个合适的用户故事列表,接下来,我们就可以安排我们的开发计划了。只要厘清用户故事之间的依赖关系,安排工作是每一个团队都擅长的事情。
|
||||
|
||||
我在这里想回到我们开头讨论的话题。我们常说,需求来自产品经理,但需求到底是什么,这是一个很宽泛的话题。到这里,我们已经有了一个更清晰更可管理的需求,用户故事。这时候我们再说需求调整,调整的就不再是一个大主题,而是一个个具体的用户故事了。
|
||||
|
||||
许多团队真正的困境在于,在开发过程中缺少需求分解的环节。在这种情况下,需求的管理基本单位就是一个主题,既然是基本单位,那就是一个不可分割的整体。团队就被生生绑死在一个巨大的需求上,没有回旋的余地。
|
||||
|
||||
如果团队可以将需求分解,需求的基本单位就会缩小,每个人看到的就不再是“铁板”一块,才能更方便地进行调整,才会有比较大的腾挪空间。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
软件开发中,需求管理是非常重要的一环。在需求管理上常见的错误是,需求管理的粒度太大,很多团队几乎是在用一个大主题在管理需求,这就让需求调整的空间变得很小。
|
||||
|
||||
结合用户故事,我给你讲了一个好的需求管理基本单位是什么样子的,它要符合“INVEST原则”。其中的一个关键点是“小”,只有小的需求才方便管理和调整。
|
||||
|
||||
什么样的需求才算小呢?我给你介绍了一种需求估算的方式,每个团队都可以根据自己的特点决定在自己的团队里,多大的需求算大。大需求怎么办?只要再进行分解就好了。
|
||||
|
||||
如果你对用户故事这个话题感兴趣,推荐阅读 Mike Cohn 的两本书[《User Stories Applied》](http://book.douban.com/subject/4743056/)和[《Agile Estimating and Planning》](http://book.douban.com/subject/26811747/)。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**想要管理好需求,先把需求拆小。**
|
||||
|
||||
最后,我想请你分享一下,你的团队在需求管理上还遇到过哪些问题呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
120
极客时间专栏/geek/10x程序员工作法/任务分解/18 | 需求管理:太多人给你安排任务,怎么办?.md
Normal file
120
极客时间专栏/geek/10x程序员工作法/任务分解/18 | 需求管理:太多人给你安排任务,怎么办?.md
Normal file
@ -0,0 +1,120 @@
|
||||
<audio id="audio" title="18 | 需求管理:太多人给你安排任务,怎么办?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/8d/33/8d14eb676cc579d686b1986c6a29fb33.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
上一讲我们讲了需求的分解,我以用户故事为例,给你讲了我们应该把大的需求拆分成小的需求,但是不是只要把需求拆开了就万事大吉了呢?显然不是。今天我们再来探讨另一个与需求强相关的话题:需求管理。
|
||||
|
||||
需求管理?许多程序员的第一直觉通常是,这要么是产品经理的事,要么是项目经理的事,跟我有什么关系?我知道很多人会这么想,可我想说的是,如果你不了解需求是怎么管理的,即便是进行了需求分解,最终的结果很有可能依然是你深陷泥潭苦苦挣扎而不自知。
|
||||
|
||||
为什么这么说呢?我给你讲一个发生在我身边的故事。
|
||||
|
||||
## 最无脑的需求管理法:老板说的
|
||||
|
||||
有一次,我们组织了一次各团队负责人的吐槽大会,让大家把遇到的问题在台面上“摆”一下。一个开发团队的负责人说:“我这边倒排期太严重了,每个产品经理到我这里都说上线日期已经定好了,我这边资源有限,实在是抗不住了。”
|
||||
|
||||
出于好奇,有人问:“这些任务都一样重要吗?”
|
||||
|
||||
这个负责人无奈地摇摇头,“他们都说自己的任务重要。”
|
||||
|
||||
“他们凭什么说自己的任务重要呢?”我也问了一个问题。
|
||||
|
||||
这个负责人说:“他们告诉我,是老板说的。”
|
||||
|
||||
这是不是一个很熟悉的场景?一堆任务压过来,只是因为这是老板的一句话。我们的老板都是这么不近人情吗?其实,大概率来看,并不是。
|
||||
|
||||
**就凭一句“老板说的”,我们就可以判断出,产品经理缺乏对需求管理应有的理解。**而研发团队也因为无脑地接受了需求,几乎将自己压垮。
|
||||
|
||||
这时候,CTO 发话了:“口头的东西不算数,如果他们说是老板说的,那就让老板发邮件确认。”
|
||||
|
||||
我很认可CTO的说法,但我并不放心那个开发团队的负责人,于是我问他:“你会让产品经理这么去做吗?”果然,他犹豫了。
|
||||
|
||||
“产品经理可能不会和老板这么说。那你去说好了。”我们又给他提了个建议。显然,他更犹豫了,毕竟要面对大老板。
|
||||
|
||||
针对这种情况,我们又给出了一个解决办法,“如果你担心产品经理不这么做,你可以直接发邮件给老板,同时抄送 CTO。”
|
||||
|
||||
“对,可以这么做”,CTO 把责任扛了过去。这个负责人心里一下子有底了。
|
||||
|
||||
是不是有种似曾相识的感觉?其实,这个故事只要再往下延伸一点,就到了我们程序员身边。
|
||||
|
||||
作为程序员,我们面临的场景往往是,一个需求不明就里地来了,你的周末假期全部泡汤,因为你的负责人会和你说,这是老板说的。
|
||||
|
||||
软件行业有个段子:做软件,最理想的交付日期是什么时候?答案是昨天,其次是尽快。所有提出业务需求的人都恨不得需求早就做好了。但事实总是那么不如人意,所以,他们只能寄希望于需求被尽快实现。
|
||||
|
||||
如果我们等着所有需求都开发好了再上线呢?这就是当年所谓瀑布模型做的事,放在二十年前,这种做法还有生存空间,但今天这种做法显然已经不合时宜了。
|
||||
|
||||
关于如何做软件,我们已经讨论了很多,关键点就在于这个世界有太多的不确定,我们只好把产品的“一部分”开发好,送上线。
|
||||
|
||||
这就引出了一个问题,到底是选择“哪部分”优先上线呢?我们必须在宏大的理想和骨感的现实中作出取舍。这也就牵扯出需求管理的本质,实际上是个优先级的问题。
|
||||
|
||||
## 需求的优先级
|
||||
|
||||
“来自老板”,这是判断优先级最简单的答案,也是推卸责任的一个答案。其潜台词是,压力大不怪我,要怪就怪老板去。“来自老板”不应该成为优先做事的指标。
|
||||
|
||||
首先,我们要明确一点,优先级这种事大家也是可以谈的,大多数能当老板的人都是可以讲道理的。但要和老板谈,我们得知道怎么讲道理。准备一些基础知识,才能与各级老板探讨怎么安排工作的优先级。
|
||||
|
||||
为什么要区分优先级?因为时间是有限的,有限的时间内你能完成工作的上限是一定的。
|
||||
|
||||
怎么充分利用好有限的时间,这其实是一个时间管理的问题。所以,我们完全可以借鉴时间管理领域的一些优秀实践,帮助我们更有效地明辨优先级。
|
||||
|
||||
谈到时间管理,一个有效的时间管理策略是艾森豪威尔矩阵(Eisenhower Matrix),这是由美国前总统艾森豪威尔开发出的一个工具。
|
||||
|
||||
这个工具到了史蒂芬·柯维(Stephen Richards Covey)手里得到了发扬光大,他那本著名的《高效能人士的七个习惯》书籍将其推广至世界各地。也许这个名字你不太熟悉,看一下下面这个图你就知道了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/6f/f8/6f0fdcb6e2d9c9955fd6e2b2210a03f8.jpg" alt=""><br>
|
||||
它将事情按照重要和紧急两个维度进行划分,也就形成了四个部分:重要且紧急,重要不紧急,不重要且紧急,不重要不紧急。
|
||||
|
||||
用几个程序员生活中的例子帮你理解一下。让系统不能正常运行的线上故障,就属于重要且紧急事情,不赶紧解决,就影响公司的正常运营。团队对系统升级改造就属于重要不紧急:改造好,性能也好,可维护性也得到提升;不改造,一时半会也能用。一些临时任务都属于紧急不重要,而刷朋友圈则属于既不紧急也不重要。
|
||||
|
||||
**按照时间管理的理念,重要且紧急的事情要立即做。重要但不紧急的事情应该是我们重点投入精力的地方。紧急但不重要的事情,可以委托别人做。不重要不紧急的事情,尽量少做。**
|
||||
|
||||
这个矩阵带给我们思维上最大的改变是,让人意识到事情和事情不是等价的。**如果不把精力放在重要的事情上,到最后可能都变成紧急的事情。**
|
||||
|
||||
比如,我们放任系统不做升级改造,过多的技术债会让系统的问题越来越多,新需求实现的速度越来越慢,最后几个看起来不大的需求就足以让团队加班加点,天怒人怨。
|
||||
|
||||
把这个思路带回到我们现实的需求管理中,你会发现,其实团队面临的各种需求所采用的优先级排序方式,基本上都是按照紧急程度排列的,但它们是否真的重要呢?
|
||||
|
||||
如果你把这个问题抛给需求的提出者,我几乎可以肯定,他们给你的答案是,他们提出的需求就是重要的。一种可能是,他们也分不清重要和紧急的差别,正如有时候我们也糊涂一样。
|
||||
|
||||
对于这样的场景,我们要做的就是多问一些问题。我在[“精益创业:产品经理不靠谱,你该怎么办?”](http://time.geekbang.org/column/article/76260)文章中说过,默认所有需求都不做,直到弄清楚为什么要做这件事。
|
||||
|
||||
同样,需求也没那么重要,直到产品经理能说明白它为什么重要,尤其是为什么比其他需求重要。如果一个产品经理不能把几个需求排出优先级,你就可以把上面学到的内容给他讲一遍。
|
||||
|
||||
还有另一种可能,他给你的需求在他工作的上下文中,确实是最重要的内容了。但当有多个需求来源时,我们该如何确认哪个需求是最重要的呢?这时,才到了真正需要老板出场的时刻。
|
||||
|
||||
## 站在老板面前
|
||||
|
||||
在[“解决了很多问题,为什么你依然在‘坑’里?”](http://time.geekbang.org/column/article/76567)文章中,我曾经讲过,大家不要局限于程序员这个角色,不同角色真正的差异是工作上下文的不同。每个人都在自己的上下文里工作,上下文也就局限了很多人的视野。
|
||||
|
||||
试想,两个产品经理出现在你面前,一个告诉你,公司要拓展新方向,这个功能要做;另一个却说,公司要进一步盈利,那个功能必须做。
|
||||
|
||||
在你看来,他们两个说得都对,听上去都挺重要的。但骨感的现实是,你把两件事都接下来,等着你的是累死都完不成的任务。
|
||||
|
||||
这个时候,我们能做的是什么呢?跳出这个上下文,到更大的上下文中。你判断不了哪个需求更重要,就请更高一级的老板来判断。
|
||||
|
||||
有了基础知识的储备,我们终于可以站在了老板面前。你可以告诉老板:我资源有限,需要将这两个需求排个序,看哪个更重要。我的上下文有限,需要你帮我判断一下。
|
||||
|
||||
老板会和你说这两个需求的起源,扩展盈利的需求是竞争对手都已经有了,客户也问这边要,再不做会影响客户关系,尤其是新财年快到了,下个阶段的合同会受到影响。而另外的新业务是某天一个高端聚会上得到的新启发,想尝试一下,他也不确定这个想法能带来多少收益,就让产品部门试一下。
|
||||
|
||||
听了老板的信息,你顿时明白这两件事的重要性,你也知道该如何面对两个产品经理了。
|
||||
|
||||
老板比你们的上下文大,因为他有看待这个问题更多的维度。所以,在你们眼里无比纠结的事情,老板几句话就云开雾散了,在他眼里,那根本不叫事。
|
||||
|
||||
如果你看过刘慈欣的《三体》,就会知道,这其实是“降维攻击”。另一个你可能熟悉的说法叫大局观。我经常和人说,**当员工想不明白的事,换成老板的视角就全明白了。**
|
||||
|
||||
我鼓励每个程序员在更大的上下文中工作,也就是想让人获得更多的思考维度。而今天的内容主要告诉你,如果自己的上下文不足时,我们可以引入新的元素,比如征求老板意见,扩大自己的上下文。
|
||||
|
||||
再发散讲几句,为人做事同样要不断扩展自己的上下文,这也就是我们常说的涨见识。
|
||||
|
||||
很多所谓的人生难题不过是因为见识有限造成的。比如,如果你觉得公司内总有人跟你比技术,莫不如把眼光放得长远一些,把自己放在全行业的水平上去比较。因为你是为自己的职业生涯在工作,而不是一个公司。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
需求分解之后,最重要的是,排列需求的优先级。优先级的排列方式有很多,我们可以借鉴时间管理的方法,把事情按照重要和紧急的维度进行划分,得到了四个象限。我们要尽可能把精力放在重要的事情上,而不是把紧急的事情当成优先级排序的方式。
|
||||
|
||||
需求分解成一个个小块,其实也分解了原本合一的上下文。如果想要有效地管理需求,尤其是确定事情的重要程度,一种方式是找回丢失的上下文。如果我们自己无法判断上下文,一种好的办法是,引入外部更大的上下文。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**尽量做最重要的事。**
|
||||
|
||||
最后,我想请你分享一下,你的团队在日常的需求管理中,还遇到哪些问题呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
105
极客时间专栏/geek/10x程序员工作法/任务分解/19 | 如何用最小的代价做产品?.md
Normal file
105
极客时间专栏/geek/10x程序员工作法/任务分解/19 | 如何用最小的代价做产品?.md
Normal file
@ -0,0 +1,105 @@
|
||||
<audio id="audio" title="19 | 如何用最小的代价做产品?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/df/ae/df57a27cb87bba82c70ba3eeca767bae.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
前面我们讲了开发任务的分解和需求管理的分解,这些都是针对“已经确定好要做的事情”的分解策略,今天我们再上一个台阶,聊聊面对那些不确定的产品功能该如何分解。
|
||||
|
||||
产品经理的想法层出不穷,但是,如果我们一味闷着头实现产品经理的想法,无论你有多大的开发团队都是不够用的。我们要学会用最小的代价做产品。
|
||||
|
||||
谈到产品这个话题,在[“精益创业:产品经理不靠谱,你该怎么办?”](http://time.geekbang.org/column/article/76260)这篇文章中,我给你分享了精益创业的理念,任何的想法都要放到真实世界中检验。
|
||||
|
||||
我们的直觉当然是把所有的东西都实现了再去检验,但是世界不会停下来等着我们。事实也一次又一次教育我们,“憋大招”的瀑布式软件开发已经成为不合时宜的“老古董”。那我们的理想怎么实现呢?唯有分解。
|
||||
|
||||
我们前面提到,精益创业就是通过不断地尝试在真实世界中验证产品想法,其中一个重要的实践是最小可行产品(Minimum Viable Product,MVP),我们这次就把这个实践展开讨论一下。
|
||||
|
||||
什么叫最小可行产品?就是“刚刚好”满足客户需求的产品。客户需求好理解,怎么算“刚刚好”呢?其中的关键在于理解“最小”和“可行”。
|
||||
|
||||
## 最小的代价
|
||||
|
||||
先说“最小”。这里的“最小”,指的是最小的代价。怎么叫最小的代价,就是能不做的事情就不做,能简化的事情就简化。
|
||||
|
||||
首先,我们必须清楚一件事,**我们要做的是验证一个想法的可行性,甚至不是为了开发一个软件,开发软件只是一种验证手段。**
|
||||
|
||||
很多程序员都会有一个认识上的误区,容易把解决方案当成问题。我们开发软件的目的是为了解决问题,如果不写软件就把问题解决了,岂不是更好。
|
||||
|
||||
我先讲一个自己的经历,帮你理解一下什么叫“最小”。有一次,有一个朋友找我帮忙,他手头有一些制造业的客户,想做一个物联网相关的项目,帮助这些客户改造设备,实现物联网功能。
|
||||
|
||||
该怎么着手呢?把软件写好,给客户试用吗?这样时间太长,成本太高。那么,我们是怎么做的呢?
|
||||
|
||||
第一步,我们要验证这样一个想法是否可行。我们做了一个产品文档,就好像我们已经有了这个产品一样,让负责销售的同事拿着这个文档给客户讲讲,看看客户对这个想法的反映。
|
||||
|
||||
在这个过程中,我们验证了基本的想法,已有设备进行物联网化改造的需求存在,客户看到了这样的一个东西,各种各样的想法和要求就会冒出来。
|
||||
|
||||
此外,我们还获得了一个额外的收获,我们知道了客户对于这样一个产品能够接受的价格区间,这可以帮助团队给产品进行适当的定价。
|
||||
|
||||
验证了方向上的想法,我们开始进入到具体的产品设计阶段。这个阶段我们想验证的是,我们给出的产品设计用户是否可以接受。于是,我们决定把这个产品的交互做出来。
|
||||
|
||||
得益于原型工具的快速发展,我们用一个原型工具做出了相对完整的用户界面,而且把各种交互流都做出来了。在用户看来,这几乎就是完整的软件了。
|
||||
|
||||
他们甚至可以在自己的设备上体验一下这个产品用起来是什么感觉的。一旦上手用起来,他们就会抛出各种细节的问题:如果这样就好了,如果能做到这个就太棒了。当然,他们也会说,这个东西我不需要。
|
||||
|
||||
这个时候,我们就可以知道,我们在产品上的假设哪些是好的,哪些是不流畅的。团队拿到这些反馈,就可以再调整产品设计,然后,再给到用户去测试,如此反复进行。有的时候,产品会在一天之内改好几个版本。
|
||||
|
||||
经过多轮测试下来,团队有了一大堆的用户反馈,而且是来自真实用户的反馈。接下来,就是整理这些用户反馈,决定哪些可以真正的开发出来,这时候,团队才真正进入到开发阶段。
|
||||
|
||||
不知道你注意到了没有,迄今为止,**这个团队验证了一大堆的想法,而代码却是一行都没有写,所有花费的工作量都是有针对性的验证。**
|
||||
|
||||
我们经常听到一个段子,叫“就差一个程序员了”。这说的是,一个创业者把前期的准备都做好,就差程序员把产品开发出来了。
|
||||
|
||||
按照 MVP 的思想,这个创业者做的就是对的,前提是他真的把前期准备都做好了。
|
||||
|
||||
开发软件是一件成本很高的事情。如果只是验证想法,无论是创业方向,还是产品设计,我们可以找到各种各样的手段,不用写代码。
|
||||
|
||||
即便我们不是在做一个新产品,我们依然可以运用这个“最小代价”的理念在日常工作中做事。比如,怎么来衡量产品经理的产品设计是不是好的。我会问,这个功能不做,用户会怎么样?有没有什么替代方案等等。以此来帮助产品经理想清楚自己的产品设计是否真的有价值。
|
||||
|
||||
## 可行的路径
|
||||
|
||||
说完了"最小",我们再来看"可行"。可行是要找到一条路径,给用户一个完整的体验。做程序员出身的人,对软件系统的认识总是一个模块一个模块的,相对比较弱的方面是缺少一个完整的图景。
|
||||
|
||||
但从产品可行的角度,我们需要转换一下思路,**不是一个模块做得有多完整,而一条用户路径是否通畅。**
|
||||
|
||||
我再给你分享一个我当年做 P2P 项目经历,这里的 P2P 指的是个人对个人的互联网借贷平台。
|
||||
|
||||
这是一个从头开始的项目,项目方和所有的项目方一样,希望昨天这个项目就上线了,如果不能,那就尽快上线一个版本。他们给我们一个时间线,第一个上线的版本是一个月之后。
|
||||
|
||||
摆在我们面前的问题是,无论如何,在一个“一穷二白”的基础上,要在一个月内完成一个完整的借贷平台是不太可能的。
|
||||
|
||||
时间有限,我们只能做最基本的东西,许多运营上的想法,比如,发红包代金券之类的,第一期一律不做。即便如此,我们仍然认为完成完整的借贷循环是不现实的。
|
||||
|
||||
于是,我们就开始从需求完整性的角度动脑筋。这是一个借贷系统,其最基本的模型是:贷款方贷款之后,一次性拿到所有的钱,然后用等额本息的方式每个月还款,最后一个月剩多少钱一次性全还了。
|
||||
|
||||
我们在这个模型中找到了一个关键点,每个月还款。换句话说,第一笔贷款发生之后,最早的一笔还款是发生在一个月之后的。
|
||||
|
||||
于是,我们做了一个决定,第一个版本只包含贷款能力。是的,这个版本只能贷款,不能还款。因为用户一个月之内不会用到这个功能,你从页面上,完全看不出这样的能力缺失,因为一个月内,根本没有任何用户有可还的款项。
|
||||
|
||||
因为缩减了项目规模,我们在预期的一个月内完成所有开发,成功地把项目送上了线。第一批早期用户就开始了使用。从用户的视角看,这是一个功能完整的项目,虽然简单了点,但它是完整的。
|
||||
|
||||
当然,我们把还款排到了下一期。按照我们两周一迭代的节奏,在第一期上线两周之后,我们就会上线还款功能,届时贷款方将拥有一个真正的还款功能。
|
||||
|
||||
不过,这个还款功能只是每期的等额本息还款,最后的一次性还剩余所有贷款的功能,我们依然是不支持的。因为根据需求设计,最后一次还款最早发生在一年之后。
|
||||
|
||||
在我们把基本的功能全部送上线之后,这个系统就是一个真正的、完整的借贷平台了。但是,相对于其他提供相同能力的平台而言,这个系统依然还是很简单。比如,常见的运营功能、短期借贷计划,这个平台都没有。
|
||||
|
||||
但我们有了基础,接下来,就是在基础上叠加,而且随着项目方自己团队的构建,我们拥有了够大的团队,可以同时做几个大需求了。
|
||||
|
||||
就这样,几个月之后,我们就逐步上线了一个功能相对完整的P2P平台。在这个过程中,我们每个阶段都会上线新功能,从用户可见的角度,他看到的始终是一个完整的平台,其中的变化只有站在内部实现者的角度才能看得清楚。
|
||||
|
||||
和大家分享这个例子,主要是想破除大家对于一个“完整”系统概念的认识。**当时间有限时,我们需要学会找到一条可行的路径,在完整用户体验和完整系统之间,找到一个平衡。**
|
||||
|
||||
站在开发团队的角度,我们怎样把 MVP 理念运用在自己的工作中呢?当产品经理有一大堆要实现的功能时,我们就可以根据 MVP 理念,从这些产品功能中找出一条最小的可行路径,重新安排一个合理的开发计划。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
产品同样需要分解,目前在探索产品的不确定性上的最佳实践是精益创业,而精益创业就包含了将庞大的产品分而治之的方式:最小可行产品(Minimum Viable Product,MVP)。最小可行产品就是“刚刚好”满足客户需求的产品。
|
||||
|
||||
**想要在实践中运用好最小可行产品的理念,就是要用最小的代价找到一条可行的路径。**最小的代价就是能不做的事就不做,能简化的事情就简化。
|
||||
|
||||
程序员通常愿意用自己的代码解决问题,而写代码通常是代价非常高的解决方案,它应该成为最后的产品解决方案。
|
||||
|
||||
可行的路径,是一条完整的用户体验路径,至少在用户眼中是这样的。我们常常会想给客户一个完整的系统,但在时间有限的情况下,我们必须学会分解。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**做好产品开发,最可行的方式是采用 MVP。**
|
||||
|
||||
最后,我想请你分享一下,你遇到或听说过采用 MVP 或类似方法解决问题的案例吗?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
164
极客时间专栏/geek/10x程序员工作法/任务分解/划重点 | 关于“任务分解”,你要重点掌握哪些事?.md
Normal file
164
极客时间专栏/geek/10x程序员工作法/任务分解/划重点 | 关于“任务分解”,你要重点掌握哪些事?.md
Normal file
@ -0,0 +1,164 @@
|
||||
<audio id="audio" title="划重点 | 关于“任务分解”,你要重点掌握哪些事?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/6f/34/6f0ec008f6d969f9d9f591ceedf46034.mp3"></audio>
|
||||
|
||||
你好,我是郑晔,恭喜你,又完成了一个模块的学习。
|
||||
|
||||
在这个模块中,我主要讲解的是“任务分解”这个知易行难的工作原则。普通人与高手之间的差异,很大程度上取决于任务分解的粒度大小。但真正理解并应用好“任务分解”的原则并不容易,希望你能勤于练习,将知识内化成为你的能力。
|
||||
|
||||
## 重点复习
|
||||
|
||||
在这个模块中,我们学习到了一些最佳实践:
|
||||
|
||||
<li>**测试金字塔**<br>
|
||||
-- 行业中测试组合的最佳实践。<br>
|
||||
-- 多写单元测试是关键。</li>
|
||||
<li>**测试驱动开发**<br>
|
||||
-- 测试驱动开发的节奏是:红——绿——重构,重构是测试驱动开发区别于测试先行的关键。<br>
|
||||
-- 有人把测试驱动开发理解成测试驱动设计,它给行业带来的思维改变是,编写可测的代码。</li>
|
||||
<li>**艾森豪威尔矩阵(Eisenhower Matrix)**<br>
|
||||
-- 将事情按照重要和紧急进行划分。<br>
|
||||
-- 重要且紧急的事情要立即做。重要但不紧急的事情应该是我们重点投入精力的地方。紧急但不重要的事情,可以委托别人做。不重要不紧急的事情,尽量少做。</li>
|
||||
<li>**最小可行产品**<br>
|
||||
-- “刚刚好”满足客户需求的产品。<br>
|
||||
-- 在实践中,要用最小的代价找到一条可行的路径。</li>
|
||||
|
||||
**另外,我还提到了一些可以直接在工作中应用的做法和评判标准:**
|
||||
|
||||
- 尽量不写 static 方法;
|
||||
- 主分支开发模型是一种更好的开发分支模型;
|
||||
- 好的用户故事应该符合 INVEST 原则;
|
||||
- 估算是一个加深对需求理解的过程,好的估算是以任务分解为基础的;
|
||||
- 好的测试应该符合 A-TRIP。
|
||||
|
||||
**我也带你学习了一些重要的思想,帮你更好地改善自己的开发工作:**
|
||||
|
||||
- 分而治之,是人类解决问题的基本手段;
|
||||
- 软件变更成本,它会随着时间和开发阶段逐步增加;
|
||||
- 测试框架把自动化测试作为一种最佳实践引入到开发过程中,使得测试动作可以通过标准化的手段固定下来;
|
||||
- 极限编程之所以叫“极限”,它背后的理念就是把好的实践推向极限;
|
||||
- 大师级程序员的工作秘笈是任务分解,分解到可以进行的微操作;
|
||||
- 按照完整实现一个需求的顺序安排开发任务。
|
||||
|
||||
## 实战指南
|
||||
|
||||
在“任务分解”的板块,我也将每篇内容浓缩为“一句话”的实战指南,现在一起回顾一下。
|
||||
|
||||
<li>
|
||||
<p>动手做一个工作之前,请先对它进行任务分解。<br>
|
||||
—— 《[11 | 向埃隆·马斯克学习任务分解](http://time.geekbang.org/column/article/77913)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>多写单元测试。<br>
|
||||
——《[12 | 测试也是程序员的事吗?](http://time.geekbang.org/column/article/77917)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>我们应该编写可测的代码。<br>
|
||||
——《[13 | 先写测试,就是测试驱动开发吗?](https://time.geekbang.org/column/article/78104)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>将任务拆小,越小越好。<br>
|
||||
——《[14 | 大师级程序员的工作秘笈](http://time.geekbang.org/column/article/78507)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>按照完整实现一个需求的顺序去安排分解出来的任务。<br>
|
||||
——《[15 | 一起练习:手把手带你拆任务](http://time.geekbang.org/column/article/78542)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>要想写好测试,就要写简单的测试。<br>
|
||||
——《[16 | 为什么你的测试不够好?](http://time.geekbang.org/column/article/79494)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>想要管理好需求,先把需求拆小。<br>
|
||||
——《[17 | 程序员也可以“砍”需求吗?](http://time.geekbang.org/column/article/79520)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>尽量做最重要的事。<br>
|
||||
——《[18 | 需求管理:太多人给你安排任务,怎么办?](http://time.geekbang.org/column/article/80428)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>做好产品开发,最可行的方式是采用 MVP。<br>
|
||||
——《[19 | 如何用最小的代价做产品?](http://time.geekbang.org/column/article/80691)》</p>
|
||||
</li>
|
||||
|
||||
## 额外收获
|
||||
|
||||
在这个部分的最后,针对大家在学习过程中的热门问题,我也进行了回答,希望你懂得:
|
||||
|
||||
- 对不了解技术的任务,先要去了解技术,然后再做任务分解;
|
||||
- 通过一次技术 Spike ,学习新技术;
|
||||
- 丢弃掉在 Spike 过程中开发的原型代码;
|
||||
- 分清目标与现状,用目标作为方向,指导现状的改变;
|
||||
- 多个功能并行开发可以考虑使用 Feature Toggle;
|
||||
- 在遗留系统上做改造可以考虑使用 Branch by Abstraction 。
|
||||
|
||||
——《[答疑解惑 | 如何分解一个你不了解的技术任务?](http://time.geekbang.org/column/article/81515)》
|
||||
|
||||
## 留言精选
|
||||
|
||||
在“任务分解”的模块中,有很多同学非常用心,将自己的学习心得和工作中的经验进行了分享,在此我挑选了一些同学的留言,与你一起学习。
|
||||
|
||||
在讲大师级程序员的工作秘笈时,西西弗与卡夫卡 同学提到:
|
||||
|
||||
>
|
||||
最近在做战略拆解,都是一样的道理。战略飘在空中遥不可及,要落地就必须拆解。比如说达成目标有哪几个方面可以努力,各方面都需要做哪些事,这是路径。这些路径里哪些优先级最高,需要配置哪些组织资源。心里有数之后就是制订计划时间表。
|
||||
|
||||
|
||||
另外,西西弗与卡夫卡 同学还为Spike给出了一个很生动的解释:
|
||||
|
||||
>
|
||||
“技术Spike”可以翻译成“技术撩”,就是撩妹的那个撩。试探下,有戏就继续,撩不动就算或者放一段时间再说。
|
||||
|
||||
|
||||
针对分解的粒度问题,大彬 同学也分享了自己的心得:
|
||||
|
||||
>
|
||||
<p>我会的任务分解,不仅可执行,粒度还很细。比如说,我要修复一个rpc接口的bug。我会列出每个代码的修改点,要修改的测试,要增加的测试,合并到哪个分支,修改rpc文档,文档中有哪些点要修改。<br>
|
||||
每一步都非常容易执行,看起来没多少必要,但在我当前的工作环境特别有用:(1)事前思考,不会造成遗漏;(2)任务实施过程中经常被打断,比如,测试有疑问和你讨论、主管找你谈事、紧急会议来了,这种“硬中断”完全打破了节奏,而任务列表,让我清楚知道当前做了多少,该从哪一步继续。</p>
|
||||
|
||||
|
||||
对于单元测试,树根 同学提到:
|
||||
|
||||
>
|
||||
<p>我的想法可以在复杂度高,重要核心的模块先开始写单元测试。特别是公用、底层的,因为这些靠功能测试很难覆盖。<br>
|
||||
单元测试难以推行主要是没有碰到质量的痛点,通常都依靠测试工程师来保证质量。我们之前就遇到过质量崩塌,倒逼着我们去做,以保证质量。</p>
|
||||
|
||||
|
||||
树根 同学还分享了自己的任务分解实践心得:
|
||||
|
||||
>
|
||||
<p>刚改了编程习惯,先在notion写出思路、需要用到的知识点,api等,写出各个小任务,然后对应写出关键代码段。最后真正敲代码就花了10来分钟。<br>
|
||||
重新开始看极客时间就看到这篇,实践过来读,很认同。<br>
|
||||
我特别佩服国外的工程师写的代码,代码块很小,非常清晰易读。特别记得之前参加infoq会议,听socketio作者的分享,看他现场撸码,思路、代码结构都非常顺畅和清晰。</p>
|
||||
|
||||
|
||||
关于TDD的具体应用, 萧 同学提到了遇到的问题:
|
||||
|
||||
>
|
||||
<p>不久前第一次接触TDD时为它的思想而惊叹,感觉它能极大的提升编码效率,编码后期的大量重构,还能保障代码质量。后面自己在写代码的时候也注意使用它的思想,但说实话,理解是一回事,用起来就不是那么回事了,很多的东西还不是太熟练,前期说实话比较耗时间,有些拖进度。<br>
|
||||
由于也毕业不久,经验上有些欠缺,还不太熟练,有些测试还不知道怎么写。现在写多了一点,感受到的是代码质量上的提高,bug比起以前少了,需求变更下改动,也不伤筋动骨了,但还是有许多感觉做的不够好的地方。看了这篇文章,补充了对TDD的认知,感受到如果和任务分解结合起来,TDD会有更好的效果,期待后面的文章!</p>
|
||||
|
||||
|
||||
关于“任务分解”的执行问题,如明如月 同学分享了感悟:
|
||||
|
||||
>
|
||||
对任务分解的体会非常深刻,刚入职的时候任务评估不准。现在想想主要是两个原因:(1)需求梳理的不清晰,还没清楚地搞明白需求就动手写代码,导致返工和一些“意想不到”的情况。(2)任务分解做的不好,没有将任务分解成非常清晰地可执行的单元,导致有些时候无从下手,而且任务时间评估不准确。
|
||||
|
||||
|
||||
在讲到为什么很多人的测试不够好这个问题时, 毅 同学提到:
|
||||
|
||||
>
|
||||
<p>本节课我有以下几点体会:<br>
|
||||
(1)从开发者的视角看,编码和测试是不分家的,是可以通过重构形成良性生态圈的,类似之前课程中的反馈模型和红绿重构模型;<br>
|
||||
(2)A-TRIP是个很好的总结和行动指南,在今后工作中应一以贯之,把工作做到扎实有成效;<br>
|
||||
(3)对文中提到的数据库依赖的问题,我也说说自己的浅见。我觉得在测试代码中,尽量避免与数据库打交道,测试更关注领域与业务,往往爆雷更多的是resource和service,模型的变化往往牵动着表结构的变化,与其两头兼顾不如多聚焦模型。<br>
|
||||
我常用的做法是用例配合若干小文件(数据忠实于模型),保证库操作临门一脚前所有环节都是正确的,同时方便适应变化。一旦出现异常,也比较容易定位是否是数据库操作引发的问题。 (此点基于,我在工作中发现,项目型程序员大多是先急于把表结构定义出来,好像不这么做,写代码就不踏实)</p>
|
||||
|
||||
|
||||
针对需求的管理问题,WL 同学提到的点也非常关键:
|
||||
|
||||
>
|
||||
程序员也应该更积极主动一些, 最好能推动事情发展, 当这件事情由你推动时,主动权就在你的手里了。
|
||||
|
||||
|
||||
**感谢同学们的精彩留言。在下一个模块中,我将为大家分享“沟通反馈”这个原则的具体应用。**
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
137
极客时间专栏/geek/10x程序员工作法/任务分解/答疑解惑 | 如何分解一个你不了解的技术任务?.md
Normal file
137
极客时间专栏/geek/10x程序员工作法/任务分解/答疑解惑 | 如何分解一个你不了解的技术任务?.md
Normal file
@ -0,0 +1,137 @@
|
||||
<audio id="audio" title="答疑解惑 | 如何分解一个你不了解的技术任务?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ed/74/ed4192f0bcb14c629d4821221b4f2974.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
在“任务分解”这个模块,我以测试为核心,讲解了任务分解这个原则,同时也给你介绍了一些最佳实践,帮助你更好地理解任务分解的重要性,以及应该怎样分解任务。
|
||||
|
||||
同学们对任务分解这个原则大多是表示认同的,但就一些具体应用的场景,还是提出了自己的问题。
|
||||
|
||||
在今天的答疑中,我选择了几个非常典型的问题来进行深入讨论。
|
||||
|
||||
## 问题1:面对不了解的技术,我该如何分解任务?
|
||||
|
||||
pyhhou 同学提到
|
||||
|
||||
>
|
||||
<p>很想听听老师的意见,就是在一个自己不熟悉的,充满未知的项目中该怎么更好地进行任务分解?<br>
|
||||
[——《11 | 向埃隆·马斯克学习任务分解》](http://time.geekbang.org/column/article/77913)</p>
|
||||
|
||||
|
||||
shniu 同学提到
|
||||
|
||||
>
|
||||
<p>想请问一下老师,面对探索型的需求,调研型的需求如何做任务分解呢?<br>
|
||||
[——《15 | 一起练习:手把手带你分解任务》](http://time.geekbang.org/column/article/78542)</p>
|
||||
|
||||
|
||||
这是一个很好的问题。在这个模块讨论开发中的任务分解时,我说的都是确定了解的某项技术,比如,数据库、REST 服务等等,因为这是开发中最常见的场景,也是最基础的能力,连熟悉的技术都做不好分解,就别说不熟悉的技术了。
|
||||
|
||||
那如果不了解这项技术呢?**答案很简单,先把它变成你熟悉的技术。**一旦变成了你熟悉的技术,你就可以应用在这个模块中学到的,面对确定性技术的分解方案。
|
||||
|
||||
我知道,这个答案你并不满意。其实,你真正的问题是,怎么把它变成你熟悉的技术。
|
||||
|
||||
我的答案是,**做一次技术 Spike。**这里之所以用英文,是因为我没有找到一个特别合适的词来翻译。Spike 这个词的原意是轻轻地刺,有人把它翻译成调研,我觉得是有些重了。
|
||||
|
||||
Spike 强调的重点在于快速地试,和调研的意思不太一样。既然是快速地试,就要在一定的时间内完成,比如,五人天,也就是一个人一周的时间,再多就不叫 Spike 了。一些简单的技术,用一天时间做 Spike 就差不多了。
|
||||
|
||||
这里强调的重点在于,要做一次技术 Spike。**Spike 的作用就在于消除不确定性,让项目经理知道这里要用到一项全团队没有人懂的技术,需要花时间弄清楚。**
|
||||
|
||||
项目经理比你更担心不确定性,你清楚地把问题呈现在他面前,项目经理是可以理解的,他更害怕的是,做到一半你突然告诉他,项目进度要延期。
|
||||
|
||||
把事情做在前面,尽早暴露问题,正是我们要在下一个模块要讨论的一个主题。
|
||||
|
||||
好,那么接下来的问题变成了:怎么做技术 Spike 呢?
|
||||
|
||||
这里,我假设你已经通过各种渠道,无论是新闻网站,还是技术 blog,又或是上级的安排,对要用的技术有了一些感性的认识,至少你已经知道这项技术是干什么的了。
|
||||
|
||||
接下来,我们要进入到技术 Spike 的任务分解。
|
||||
|
||||
首先,快速地完成教程上的例子。稍微像样点的技术都会有一个教程,跟着教程走一遍,最多也就是半天的时间。之所以要快速地完成教程上的例子,是为了让你有一个直观的认识,这时候,你对这项技术的认识就会超过新闻网站的报道。
|
||||
|
||||
其次,我们要确定两件事:**这项技术在项目中应用场景和我们的关注点。**
|
||||
|
||||
技术最终是要应用到项目中的,本着“以终为始”的原则,我们就应该奔着结果做,整个的 Spike 都应该围绕着最终的目标做。
|
||||
|
||||
很多程序员见到新技术都容易很兴奋,会把所有的文档通读一遍。如果是技术学习,这种做法无可厚非,但我们的目标是做 Spike,快速地试,没有那么多时间,必须一切围绕结果来。
|
||||
|
||||
项目中的场景有无数,我们需要选择最重要的一个场景,而针对着这项最重要的场景,我们还要从这项技术无数功能中选取最需要的几个,而不是“满天撒网”。
|
||||
|
||||
再有是我们要找准关注点,比如,采用新的缓存中间件是为了提高性能,那关注点就是性能,采用新的消息队列是为了提升吞吐,那关注点就是吞吐。我们选用一项新技术总是有自己的一些假设,但这些假设真的成立吗?这是我们需要验证的。
|
||||
|
||||
无论是场景,还是关注点,我们要在前面先想清楚,其目的就是为了防止发散。当时间有限时,我们只能做最重要的事,这也是我在专栏中不断强调的。
|
||||
|
||||
确定好场景和关注点,接下来,我们要开发出一个验证我们想法的原型了。这个原型主要目的就是快速地验证我们对这项技术的理解是否能够满足我们的假设。开发一个只有主线能力的原型,对大部分程序员来说并不难,这里就不赘述了。
|
||||
|
||||
当你把想法全部验证完毕,这项技术就已经由一项不熟悉的技术变成了熟悉的技术。我们前面的问题也就迎刃而解了。这时候,你就可以决定,对于这项技术,是采纳还是放弃了。
|
||||
|
||||
但是,我这里还有一点要提醒,当你确定要使用这项技术时,**请丢弃掉你的原型代码。**
|
||||
|
||||
你或许会说,我辛辛苦苦写了几天的代码就这么丢了?是的,因为它是原型,你需要为你的项目重新设计。
|
||||
|
||||
如果顺着原型接着做,你可能不会去设计,代码中会存在着大量对这项技术直接依赖的代码,这是值得警惕的,所有第三方技术都是值得隔离的。这是我们会在“自动化”模块讨论的内容。
|
||||
|
||||
## 问题2:项目时间紧,该怎么办?
|
||||
|
||||
在这个模块里,我花了大量的篇幅在讲测试,很多同学虽然认同测试的价值,却提出了开发中普遍存在的一些情况。
|
||||
|
||||
玄源 同学提到
|
||||
|
||||
>
|
||||
<p>很多时候,项目时间很紧,经常会提测后,再补测试,或者直接code review,测试就不写了。<br>
|
||||
[——《12 | 测试也是程序员的事吗?》](http://time.geekbang.org/column/article/77917)</p>
|
||||
|
||||
|
||||
这是一个非常典型的问题,我在之前做咨询的时候,经常会遇到很多团队说,项目时间紧,所以,他们没有时间做测试。
|
||||
|
||||
这里面有一个非常经典误区:**混淆了目标与现状。**目标是应该怎么做,现状是我们正在怎么做。我们都知道现状是什么样的,问题是,你对现状满意吗?如果每个人都对现状是满意的,就不会有人探索更好的做法。
|
||||
|
||||
**假设现在不忙了,你知道该怎么改进吗?**
|
||||
|
||||
遗憾的是,很多人根本回答不了这个问题,因为忙是一种借口,一种不去思考改进的借口。
|
||||
|
||||
我之所以要开这个专栏,就是为了与大家探讨行业中一些好的做法。
|
||||
|
||||
回到这个具体问题上,我们在专栏开始就在讲以终为始,首先要有一个目标,专栏中介绍的各种实践都可以成为你设置目标的参考。有了这个目标再来考虑,如何结合我们工作的现状来谈改进。
|
||||
|
||||
接下来,我们以测试为例,讨论一下具体的改进过程。用我们专栏最初讲过的思考框架看一下,假如我们的现状是团队之前没什么自动化测试,而我们的目标是业务代码100%测试覆盖。如果要达成这个目标,我们需要做一个任务分解。
|
||||
|
||||
这时你会发现,分解的过程主要需要解决两方面的问题,一个是与人的沟通,另一方面是自动化的过程。
|
||||
|
||||
与人的沟通,就是要与团队达成共识。关于这点,你可以尝试将专栏里讲到的各种最佳实践以及其背后的逻辑,与团队进行沟通,也可以把专栏文章分享给他们。
|
||||
|
||||
再来,我们考虑一下自动化的改进,因为我们的现状是没什么测试,所以,不能强求一步到位,只能逐步改进。下面我给出了一个具体的改进过程:
|
||||
|
||||
- 把测试覆盖率检查加入到工程里,得到现有的测试覆盖率。
|
||||
- 将测试覆盖率加入持续集成,设定当前测试覆盖率为初始值。测试覆盖率不达标,不许提交代码。
|
||||
- 每周将测试覆盖率调高,比如,5%或10%,直到测试覆盖率达到100%。
|
||||
|
||||
这样,我们就找到了一条由现状通往目标的路径,接下来,就是一步一步地具体实施了,由团队成员逐步为已有代码补充测试。
|
||||
|
||||
## 问题3:多个功能同时开发,怎么办?
|
||||
|
||||
妮可 同学提到
|
||||
|
||||
>
|
||||
<p>公司经常存在有两个需求同时开发的情况。请问老师所在的团队如何解决单分支上线不同步的情况呢?<br>
|
||||
[——《14 | 大师级程序员的工作秘笈》 ](http://time.geekbang.org/column/article/78507)</p>
|
||||
|
||||
|
||||
在主分支开发模型中,有一些常见的解决多功能并行开发的方法,其中,Feature Toggle 是最常用的一个,也就是通过开关,决定哪个功能是对外可用的。
|
||||
|
||||
关于这一点,Y024 同学也补充了一些信息。
|
||||
|
||||
>
|
||||
<p>Feature toggle(功能开关)分享两篇文章:<br>
|
||||
[1. Feature Toggles (aka Feature Flags)](http://martinfowler.com/articles/feature-toggles.html)<br>
|
||||
[2.使用功能开关更好地实现持续部署](http://www.infoq.cn/article/function-switch-realize-better-continuous-implementations)</p>
|
||||
|
||||
|
||||
不过,如果用户故事划分得当,你可以很快完成一个完整的业务需求。实际上,Feature Toggle 只是一个非常临时的存在。但如果你在一个遗留系统上工作,一个功能要跨越很长的周期,Feature Toggle 才显得很有用。
|
||||
|
||||
额外补充一个与主分支开发模型相关的常用技术,如果你想对遗留系统做改造,传统的做法是,拉出一个分支。
|
||||
|
||||
如果在一个分支上怎么做呢?可以考虑采用 [Branch by Abstraction](http://www.martinfowler.com/bliki/BranchByAbstraction.html),简言之,再动手改造之前,先提取出来一个抽象,把原先的实现变成这个抽象的一个实现,然后,改造的过程就是提供这个抽象的一个新实现。这种做法对设计能力有一定要求,所以,对很多团队来说,这是一个挑战。
|
||||
|
||||
好,今天的答疑就到这里,请你回想一下,你在工作中是否也遇到过类似的问题呢?你又是怎么解决的呢?欢迎大家在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
82
极客时间专栏/geek/10x程序员工作法/开篇词/开篇词 | 程序员解决的问题,大多不是程序问题.md
Normal file
82
极客时间专栏/geek/10x程序员工作法/开篇词/开篇词 | 程序员解决的问题,大多不是程序问题.md
Normal file
@ -0,0 +1,82 @@
|
||||
<audio id="audio" title="开篇词 | 程序员解决的问题,大多不是程序问题" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/23/f6/23ddbb631ca8fb4eef93dc2cb7fe8cf6.mp3"></audio>
|
||||
|
||||
你好!我是郑晔,一个程序员。
|
||||
|
||||
很多人都说,程序员很辛苦,与这个角色联系在一起的词儿,通常是忙碌、加班、熬夜等。
|
||||
|
||||
作为程序员,我们将其看作一个值得全情投入的职业,希望能够把精力放在设计算法、改进设计、优化系统这些具有创造性与成就感的本职工作上。
|
||||
|
||||
但现实情况却是,许多人因为一些“意外”,陷入了无休止的忙碌,比如:
|
||||
|
||||
- 你辛辛苦苦写的代码还没上线,产品经理就告诉你需求变了;
|
||||
- 你拼命加班只因错估了工作量,自己造的“孽”,含着泪也要搞定;
|
||||
- 你累死累活做出来的东西和要求不符,只能从头再来;
|
||||
- 你大面积地修改代码只是因为设计糟糕,无法适应新的需求变化;
|
||||
- ……
|
||||
|
||||
诸如此类,不胜枚举。我们很辛苦,但耗费我们大量时间和精力去应付的工作,并不是技术工作,反而是这些看似很“不值当”的事儿。
|
||||
|
||||
为什么会这样?
|
||||
|
||||
软件行业里有一本名著叫《人月神话》,其中提到两个非常重要的概念:**本质复杂度(Essential Complexity)和偶然复杂度(Accident Complexity)。**
|
||||
|
||||
简单来说,本质复杂度就是解决一个问题时,无论怎么做都必须要做的事,而偶然复杂度是因为选用的做事方法不当,而导致要多做的事。
|
||||
|
||||
比如你要做一个网站,网站的内容是你无论如何都要写的,这就是“本质复杂度”。而如果今天你还在用汇编写一个网站,效率是不可能高起来的,因为你选错了工具。这类选错方法或工具而引发的问题就是“偶然复杂度”。
|
||||
|
||||
作为一个在软件行业奋斗了近二十年的程序员,我深刻意识到一个遗憾的事实:**大部分程序员忙碌解决的问题,都不是程序问题,而是由偶然复杂度导致的问题。**
|
||||
|
||||
换句话说,只要选择了正确的做事方法,减少偶然复杂度带来的工作量,软件开发是可以有条不紊进行的。
|
||||
|
||||
**如何减少偶然复杂度引发的问题,让软件开发工作有序、高效地进行,这正是我希望通过这个专栏帮你解决的问题。**
|
||||
|
||||
许多人工作做事主要依靠直觉,在这个科学越发昌明的时代,我们清楚地看到,人类的直觉常常是错的,就像古人凭直觉认为大地是平的一样。
|
||||
|
||||
软件开发也不例外,如果你不曾在做软件这件事上有过学习和思考,形成一套高效的工作方法,只是凭直觉行事,在真实世界中往往会举步维艰。
|
||||
|
||||
幸运的是,总会有不同的人在不同的方向上探索不同的做法,一旦通过真实世界的验证,就会沉淀出可供行业直接应用的最佳实践(Best Practice)。
|
||||
|
||||
在软件行业中,这样能够提升工作效率的最佳实践已经有很多,但是,学习掌握这些最佳实践是有难度的,其根源就在于,很难找到这些实践彼此间的内在联系。
|
||||
|
||||
直觉大多是错误的,最佳实践又多而琐碎,所以在这个专栏中,**我会尝试给你提供一个思考框架,帮你在遇到问题时梳理自己真正要做的事情。围绕着这个框架,我还会给你一些原则。**
|
||||
|
||||
这些原则,是我从软件行业的诸多软件开发最佳实践中总结出来的,也是我如今在工作中所坚持的。这些原则就是一条主线,将各种最佳实践贯穿起来。
|
||||
|
||||
这些原则不多,总结起来就四个:
|
||||
|
||||
- 以终为始;
|
||||
- 任务分解;
|
||||
- 沟通反馈;
|
||||
- 自动化。
|
||||
|
||||
也许看到这四个原则的名字,你会不以为然,这些说法你在很多地方都看到过,但我想与你分享的内容可能与你想的并不完全一致。
|
||||
|
||||
比如:你以为的“终”可能不是终,因为你只是站在自己的角度;你以为自己做了任务分解,在我看来,可能还不够,因为我希望你能够做到微操作;你以为的沟通反馈就是说话聊天,我想告诉你很多技术实践的存在也是为了沟通反馈;你以为自动化就是写代码,我会告诉你,有时候不写代码而解决问题,可能才是一个好方案。
|
||||
|
||||
在我看来,想要将精力聚焦在本质复杂度上,提高工作效率,摆脱直觉的束缚,只要掌握上面的四个原则就可以了。
|
||||
|
||||
或许你此时会问,这些原则很难吧?其实并不难,在探讨这个专栏的内容时,我的编辑作为软件开发的局外人,经常发出感叹:“这事真的就这么简单吗?这不就是正常做事应该有的逻辑吗?”
|
||||
|
||||
是的,就是这样简单,但大多数人没有这样做,因为这些原则在实际工作中很可能是反直觉的。只要打破思维误区,你的整个人都会变得不一样。
|
||||
|
||||
下面是整个专栏的目录,我希望能帮助你回答,或者厘清一些开发过程中,曾经遇到,又未曾深入的问题。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/10/3e/10dfc61006956121316d73090ec9cc3e.jpg" alt="">
|
||||
|
||||
当我们详谈这些原则时,我会给你讲述一些最佳实践,让你看到这些原则是如何应用于不同的实践中的。希望我对这些实践的理解成为你的知识地图,让你拥有继续探索的方向。
|
||||
|
||||
我做这个专栏的原则是“授人以鱼,不如授人以渔”。我希望你很好地理解这些原则,掌握高效工作的方法。至于最佳实践,你可以自行决定,是直接采纳还是曲线救国更为合适。
|
||||
|
||||
介绍一下我自己,我是郑晔,目前在火币网担任首席架构师,写过代码、带过团队、做过咨询,创过业,还维护着一个拿过 Oracle Duke 选择奖的开源项目 Moco,至今仍然在编程一线写着代码。
|
||||
|
||||
很长时间里,我一直对**如何做好软件**充满了好奇,了解过各种技术以及开发方法。做咨询的经历让我有机会见识到不同公司面临的问题;带团队的时候,我也看到很多小兄弟因为不会工作,虽然很努力却收效甚微;而我自己菜鸟时期的笨拙依然是历历在目。
|
||||
|
||||
**在我看来,所有做软件的人能力都很强,这些问题都只是因为不会工作造成的,但更可怕的是,许多人深陷泥潭而不自知。**
|
||||
|
||||
在这些年的工作里,我一遍又一遍给别人讲如何工作,逐渐总结出一套自己的工作原则,如今呈现在你面前的就是我这些年思考的总结。
|
||||
|
||||
我不指望所有人都能从这个专栏受益,我只想把这个专栏写给那些愿意成长的人。我只是来做一次信息分享,分享一些思考,分享一些做法,希望可以将你从常见的思维误区中带出来。
|
||||
|
||||
也许在这个专栏的最后,你发现自己并不认同我的原则,却能够用自己的原则来与我探讨,那么,恭喜你,因为那是最美妙的事情!
|
||||
|
||||
|
128
极客时间专栏/geek/10x程序员工作法/思考框架/01 | 10x程序员是如何思考的?.md
Normal file
128
极客时间专栏/geek/10x程序员工作法/思考框架/01 | 10x程序员是如何思考的?.md
Normal file
@ -0,0 +1,128 @@
|
||||
<audio id="audio" title="01 | 10x程序员是如何思考的?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/5f/4a/5fbb6224f077f05d5ce9f7328bed2a4a.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
在开篇词中我们提到,程序员在工作中遇到的很多问题,大多不是程序问题,辛苦而低效的工作,多数是由偶然复杂度导致的。那这个由于偶然复杂度造成的差距会有多大呢?
|
||||
|
||||
1975年,弗雷德里克·布鲁克斯(Frederick Brooks)出版了软件行业的名著《人月神话》,他给出了一个统计结果,**优秀程序员的开发效率是普通程序员的10倍。**40多年过去了,这个数字得到了行业的普遍认同。
|
||||
|
||||
成为10x程序员是很多程序员的追求。但工作产出并不只是由写代码的效率决定的,一些不恰当工作方法很大程度上影响着你的产出。
|
||||
|
||||
在接下来的这段时间里,我希望通过这个专栏和你一起探讨,作为一个程序员,该如何更高效地工作,怎样才能把时间和精力尽可能地放在处理本质复杂度的事情上,减少在偶然复杂度上的消耗。
|
||||
|
||||
作为整个课程第一讲,我就从我常用的一个思考框架开始。
|
||||
|
||||
## 一个思考框架
|
||||
|
||||
我曾经组织过针对应届毕业生的培训,第一堂课我往往亲自操刀,其中有一个头脑风暴的环节“畅想未来”,我会让大家思考三个问题:
|
||||
|
||||
- 我现在是个什么水平?
|
||||
- 我想达到一个什么水平?
|
||||
- 我将怎样到达那个目标?
|
||||
|
||||
大家会围绕着这三个问题,从各种角度展开讨论。这是一个有趣的练习,你会发现大家“最擅长”的是回答第一个问题:我现在处于什么水平?和有经验的人相比,他们大多自认为比较“菜”。但对于后两个问题的讨论,却可以切实看出人和人之间处理问题的能力差异。
|
||||
|
||||
有人通过之前的资料搜集,已经对自己的未来有了一个打算。比如想成为一个研发大牛,或者想做一个开源软件等,也就是说,对于第二个问题,他有明确的答案。
|
||||
|
||||
而有的人则是一脸茫然,他很可能根本没有考虑过这个问题。而从题目本身来看,**目标相对清晰的同学,才会进入到第三个问题,而茫然的同学,则完全无从下手。**
|
||||
|
||||
那么我为什么会问这几个问题呢?我是想让大家跳出现有的思考模式,摆脱仅凭直觉“闷头做事”的习惯方式,把低着的头抬起来,看一眼未来,给自己找一个方向。
|
||||
|
||||
否则,如果你对未来没有定位,是茫然的,尽管你也知道要努力,但劲儿该往哪里使呢?如果使劲的方向不对,那么你越使劲儿,可能会在错误的路上跑得越远。南辕北辙的道理大家都懂,但具体到自己的工作和发展上,真正能体会并实践的却是少数。
|
||||
|
||||
其实,这三个问题来自一个思考框架。在给其他公司团队做咨询时,我也经常会运用到它,原来的问题是:
|
||||
|
||||
- Where are we?(我们现在在哪?)
|
||||
- Where are we going?(我们要到哪儿去?)
|
||||
- How can we get there?(我们如何到达那里?)
|
||||
|
||||
这三个问题实际上是帮我们确定:
|
||||
|
||||
- 现状;
|
||||
- 目标;
|
||||
- 实现路径。
|
||||
|
||||
**如果一个人能够清晰地回答出这三个问题,通常意味着他对要做的事有着清晰的认识。**这个框架虽然看似简单,但却非常有效,它已经成为我工具箱里一件非常称手的思考工具。
|
||||
|
||||
在我的职业生涯里,与很多人讨论不同的事时,我都会用到这个思考框架的不同变体,而在这个专栏里,我也会用它来帮助回答“怎样高效工作、怎样做好软件”这件事。
|
||||
|
||||
## 四个思考原则
|
||||
|
||||
在实际的工作中,这个思考框架会帮助我更好地了解自己的工作。比如,当一个产品经理给我交代一个要开发的功能特性时,我通常会问他这样一些问题:
|
||||
|
||||
- 为什么要做这个特性,它会给用户带来怎样的价值?
|
||||
- 什么样的用户会用到这个特性,他们在什么场景下使用,他们又会怎样使用它?
|
||||
- 达成这个目的是否有其它手段?是不是一定要开发一个系统?
|
||||
- 这个特性上线之后,怎么衡量它的有效性?
|
||||
|
||||
如果产品经理能够回答好这些问题,说明他基本上已经把这个工作想得比较清楚了,这个时候,我才会放心地去了解后续的细节。
|
||||
|
||||
我们用思考框架对照一下,为什么我会问这些问题。一般来说,一个新特性要开发时,现状我是知道的。所以,我更关心目标,这里“为什么要做这个特性?”就是在问目标,“给用户带来怎样的价值”是在确定这个目标的有效性。
|
||||
|
||||
接下来,我会关注实现路径,用户会怎么用,是否有其他的替代手段,我需要了解产品经理的设计是经过思考的,还是“拍着脑袋”给出的。衡量有效性,则是要保证我的工作不会被浪费。
|
||||
|
||||
通过这个例子,我给你展示了怎么用这个思考框架提出问题。但我估计你更想了解的是,我怎么会想到问这些问题。**给出思考框架是为了让你明白为什么要提出问题,而具体问题要怎么问,就可以遵循下面这四项原则:**
|
||||
|
||||
- 以终为始;
|
||||
- 任务分解;
|
||||
- 沟通反馈;
|
||||
- 自动化。
|
||||
|
||||
这是我从思考框架延伸出来的。在这个专栏里,我会围绕这四项原则和你详细讨论。
|
||||
|
||||
解释一下,**以终为始**就是在工作的一开始就确定好自己的目标。我们需要看到的是真正的目标,而不是把别人交代给我们的工作当作目标。你可以看出这个原则是在帮助我们回答思考框架中,Where are we going?(我们要到哪儿去?)这个问题。
|
||||
|
||||
**任务分解**是将大目标拆分成一个一个可行的执行任务,工作分解得越细致,我们便越能更好地掌控工作,它是帮助我们回答思维框架中,How can we get there?(我们如何到达那里?)的问题。
|
||||
|
||||
如果说前两个原则是要在动手之前做的分析,那后面两个原则就是在通往目标的道路上,为我们保驾护航,因为**在实际工作中,我们少不了与人和机器打交道**。
|
||||
|
||||
**沟通反馈**是为了疏通与其他人交互的渠道。一方面,我们保证信息能够传达出去,减少因为理解偏差造成的工作疏漏;另一方面,也要保证我们能够准确接收外部信息,以免因为自我感觉良好,阻碍了进步。
|
||||
|
||||
**自动化**就是将繁琐的工作通过自动化的方式交给机器执行,这是我们程序员本职工作的一部分,我们擅长的是为其他人打造自动化的服务,但自己的工作却应用得不够,这也是我们工作中最值得优化的部分。
|
||||
|
||||
这四个原则互相配合,形成了一个对事情的衡量标准。总体上可以保证我的工作是有效的,在明确目标和完成目标的过程中,都可以尽量减少偶然复杂度。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/6f/02/6f6f4cf46f321db1cbf0d770327e5602.jpg" alt="">
|
||||
|
||||
怎么把这四个原则用在工作中呢?我们回过头来看一下前面的场景,产品经理把要做的功能特性摆在我面前。站在**以终为始**的角度,我需要了解真正的目标是什么,所以,我会关心**为什么要做这个特性**。为了保证目标是有效的,我会关心**它给用户带来的价值**。
|
||||
|
||||
有了**任务分解**的视角,我需要将一个大的目标进行拆解,如果我要达成这个目标,整体解决方案是远远不够的,我需要把任务分解成一个一个小的部分。所以,我会关心一个一个**具体的使用场景**。
|
||||
|
||||
一方面,我会了解到更多的细节,另一方面,当时间紧迫的时候,我会和产品经理来谈谈究竟优先实现哪个场景。
|
||||
|
||||
为什么要学会**沟通反馈**?因为我需要明确,自己是否真正理解了产品经理提交的需求。所以,我要不断地问问题,确保自己的理解和产品经理交代的内容一致。
|
||||
|
||||
另外,我也需要保证我的产品做出来确实能够达到目标。所以,我会关心它**上线后的衡量手段**。因为我知道,这个行业里有太多代码上线后,从来没有运行过。
|
||||
|
||||
自动化的角度很有意思,**我们做的方案通常是一个自动化方案,但我们需要了解这个方案没有自动化之前是怎么做的。**如果不自动化,用户会怎么用。所以,我会关心是不是还有其它替换方案,比如,买一个现成的服务。因为很多需求的提出,只是因为我们有了一个开发团队而已。
|
||||
|
||||
好,现在你已经对这四个原则在工作中的应用有了一个直观的认识。但你也会发现,我问的这些问题似乎已经“超纲”了,超过了一个普通程序员应该关注的范围。但这就是真实世界,它不像考试一样,有一个标准答案。
|
||||
|
||||
**我们不是一个人孤独地在工作,而是与其他人在协作,想要做到高效工作,我们就要“抬起头”来,跳出写代码这件事本身。所以,我在开篇词里说,程序员解决的问题,大多不是程序问题。**
|
||||
|
||||
可能你对这些原则的了解还没过瘾,没关系,这篇文章只是让大家清晰地了解思考框架和原则的背后逻辑。接下来,我会结合行业里的最佳实践,给你进一步讲解这些原则和具体应用。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
大多数人工作低效是由于工作中偶然复杂度太多造成的,只要能够更多地将注意力放到本质复杂度上,减少偶然复杂度造成的消耗,我们“真实”的工作效率自然会得到大幅度提升。
|
||||
|
||||
而想要减少偶然复杂度的消耗,就要了解一些高效的工作方式和行业的最佳实践,而这一切是可以用统一的框架进行思考的。
|
||||
|
||||
运用这个思考框架,我们需要问自己一些问题:
|
||||
|
||||
- Where are we?(我们现在在哪?)
|
||||
- Where are we going?(我们要到哪儿去?)
|
||||
- How can we get there?(我们如何到达那里?)
|
||||
|
||||
为了把这个框架应用在我们程序员的工作中,我给了你四个思考原则:
|
||||
|
||||
- 以终为始,确定好真实目标;
|
||||
- 任务分解,找到实施路径;
|
||||
- 沟通反馈,解决与人打交道出现的问题;
|
||||
- 自动化,解决与机器打交道出现的问题。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**面对问题时,用思考框架问问自己,现状、目标和路径。**
|
||||
|
||||
最后,我想请你思考一下,如果把这个思考框架运用在你的职业发展规划上,你会如何回答这三个问题呢?
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
117
极客时间专栏/geek/10x程序员工作法/沟通反馈/20 | 为什么世界和你的理解不一样?.md
Normal file
117
极客时间专栏/geek/10x程序员工作法/沟通反馈/20 | 为什么世界和你的理解不一样?.md
Normal file
@ -0,0 +1,117 @@
|
||||
<audio id="audio" title="20 | 为什么世界和你的理解不一样?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/09/a4/099b264c7b4b904d3d0fe65005a51da4.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
从今天起,我们要开启一个新的模块:**沟通反馈。**
|
||||
|
||||
如果看到沟通反馈几个字,你就以为我打算在这里教一些谈话技巧,那你还真的想错了。
|
||||
|
||||
在这个模块里,我打算与你讨论的主题是,生活在真实世界中。沟通反馈和生活在真实世界这两个话题是怎么联系到一起的呢?请听我慢慢道来。
|
||||
|
||||
《大富翁》里的沙隆巴斯有句口头禅:人生不如意的事,十有八九!但是不知道你有没有想过这样的一个问题,为什么人生如此不如意?如果这是一篇鸡汤文,我应该告诉你世事艰辛。但我要说的是,真实的原因往往是因为你想得太美好,用我们做软件的例子来看一下:
|
||||
|
||||
- 在我们的愿望中,做出来的产品应该一举成名,现实却是惨淡经营;
|
||||
- 在我们的愿望中,产品经理给出的需求应该是清晰明了的,现实却是模模糊糊;
|
||||
- 在我们的愿望中,写出来的代码,应该是快捷无错的,维护也很容易,现实却是 Bug 百出,越修改,修改的时间就越长;
|
||||
- 在我们的愿望中,你给我布置任务,我应该迅速地理解到关键,现实却是做出来的与你的目标根本就是天差地别;
|
||||
- ……
|
||||
|
||||
为什么会这样?欢迎来到真实世界,真实世界不是以美好愿望驱动的,它有着自己的运行规律。虽然我们都生活在同一个世界中,但每个人理解世界的方式确实是千差万别。
|
||||
|
||||
**我们努力地学习各种知识,为的就是更好地理解这个世界的运作方式,而沟通反馈,就是我们与真实世界互动的最好方式。**
|
||||
|
||||
你也许会好奇,为什么我们对世界的理解会出现偏差?接下来,让我们一起用一个信息论的视角看一下。
|
||||
|
||||
## 一个信息论视角的解释
|
||||
|
||||
1948年,克劳德·香农(Claude Elwood Shannon)在《贝尔系统技术学报》(Bell System Technical Journal)上发表了一篇论文《通信的数学原理》(A Mathematical Theory of Communication),这是现代信息论的开端。我们程序员熟知的通信、数据压缩、密码学、自然语言处理等诸多领域都有信息论的身影。
|
||||
|
||||
我们这里要借鉴的是香农信息论中的一个通信模型,如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f6/92/f60780417b2b7a43yy89f12d8240c692.jpg" alt="">
|
||||
|
||||
这个图中包含了几个要素:
|
||||
|
||||
- 信源(Information Source),它负责产生信息(Message)。
|
||||
- 发送器(Transmitter),它会对信息进行某些操作,也就是对信息编码,产生信号(Signal)。
|
||||
- 信道(Channel),它是信号传送的媒介。
|
||||
- 接收器(Receiver),它是对信号执行发送器的逆操作,解码信号,提取出信息。
|
||||
- 信宿(Destination),它负责接收信息。
|
||||
|
||||
当然,图中还有一个因素叫做噪声(Noise),指的是削弱信号的东西。不过,它并不是我们这里讨论的重点,我们暂时忽略它。
|
||||
|
||||
我们用一个实际工作中的例子来理解一下这个过程。假设你的项目经理来给你布置一项工作,在这里,项目经理就是一个信源。他的想法就是他的消息,他要把这件事告诉你,先要在大脑中做一次编码,转换成语言表达出来。他说出来的这段话就是信号。
|
||||
|
||||
比如,这个信号是“完成一个需求”。这段话通过信道,也就是空气传播到你耳朵里,接收到这段话之后,你会按照自己对这段话的理解进行解码,作为信宿的你,形成了自己的想法,这就是你接到的消息,整个过程就完成了。
|
||||
|
||||
我们来看一下,理解偏差是怎么产生的。
|
||||
|
||||
项目经理给你传输的信号是“完成一个需求”,在项目经理脑子中,这个信号的原始信息可能是这样的:编写完成这个功能所需的代码,然后为这段代码写好自动化测试,再将它与现有系统集成好,通过测试人员的验证。
|
||||
|
||||
而在学习这个专栏之前,你从“完成一个需求”这个信号中解码出来的信息却是:把功能代码写完。这样,问题就出现了。即便这里忽略了噪声的干扰,当编码和解码不是一个版本的时候,无论如何,项目经理的信息都很难准确地传达到你这里。
|
||||
|
||||
**这就是人们往往对世界产生误解的原因。**
|
||||
|
||||
信息的传达要经过编码和解码两个过程,无论是编码出现问题,还是解码出现问题,都会造成信息的不准确。
|
||||
|
||||
一方面,有些人表达不清楚,一件简单的事,他说了半天,你依然是云里雾里。这就相当于,信源发出的信息经过编码得到的信号已经不准确了。
|
||||
|
||||
另一方面,就像听一些技术演讲,人家说得很清楚,但因为自己没有相关背景,依然无法得知人家表达的信息。这就相当于信号虽然准确,但我们没有对应的解码装置,信号无法转成有效信息。
|
||||
|
||||
再有就是像前面这个例子,收发双方编解码器不配套,同样的信号得到的信息截然不同,信息传达的目的也不能很好地完成。
|
||||
|
||||
有了理论做基础,我们就容易理解世界为什么总和我的理解不一样,这就是编解码的过程出了问题。**因为每个人经历见识的差异,造成了各自编解码器的差异。**世界是同一个世界,每个人看到的却是千姿百态。
|
||||
|
||||
如果想在这个真实的世界中生活得更幸福一些,我们能做点什么呢?那就是改善我们的编解码器。怎么改善自己的编解码器呢?这就是“沟通反馈”这个模块要讨论的内容。
|
||||
|
||||
## 改善编解码
|
||||
|
||||
站在改善编解码效果的角度,我们要考虑哪些问题呢?
|
||||
|
||||
首先,我们要考虑一下编码器的效果。换句话说,当我们想把信息传达给别人的时候,我们得把信息编码成一个有效的信号,至少要保证在我们这里信息不丢失。
|
||||
|
||||
我举个例子,有一次,我在客户现场做咨询,客户的一个程序员给我介绍他们的系统,他讲了二十分钟,我还是听得一头雾水。于是,我打断他,花了五分钟用我的语言给他讲了一遍,然后问他:“你想说的是不是这个意思?”他猛劲点头:“就是这样的。”
|
||||
|
||||
为什么会这样呢?究其原因就是,他上来就在给我讲实现细节,完全没有任何铺垫。
|
||||
|
||||
要知道,我是来了解情况的,所以,我的背景知识肯定是不足的,凭空理解这些细节是非常困难的一件事。从沟通的角度上看,这么做浪费了大量的时间,因为在过程中,我要不断地让他给我补充这些缺失的背景。**这几乎是很多程序员讲东西的通病:讲东西直奔细节。**
|
||||
|
||||
我在面试中也经常遇到过类似的情况,一些候选人上来就给我讲技术细节,我对他做过的系统一无所知,所以,我只好打断他,让他先把背景给我介绍一下。
|
||||
|
||||
同样,很多人抱怨别人不能理解自己,其实,首先应该想的问题是,自己到底有没有把话说清楚。这就是编码器出现问题的情况。
|
||||
|
||||
其次,我们还要考虑一下解码器的效果,也就是说,当一个信号呈现在我们面前时,作为接收者,我们是否能够有效地解码信息。
|
||||
|
||||
著名作家王小波曾经讲过一个花剌子模信使的故事,说的是中亚古国花剌子模有一个奇怪的风俗,凡是给君王带来好消息的信使,就会得到提升,给君王带来坏消息的人则会被送去喂老虎。如此一来,谁还敢把坏消息带给君王呢?但问题是,君王不听坏消息,坏消息就不存在了吗?
|
||||
|
||||
这就相当于解码器出了问题,过滤掉了很多真实的信息。但真实世界就是真实世界,它不会按照人们的美好愿望运行。
|
||||
|
||||
再举一个我们身边的例子,能做程序员的人,大多是很聪明的人, 当几个人一起讨论问题时,别人往往刚开了个头,他就认为自己已经理解了别人的想法,然后开始表达自己的观点。信息都不全,何谈解码。所以,开发团队的讨论中常常出现一个人高谈阔论,却离题万里的情况。
|
||||
|
||||
我们要想让自己更好地工作生活,就必须接纳真实世界的反馈,而接纳真实世界的反馈,一是需要我们打开自己的接收器,把信号接纳进来,让反馈进来,这是解码的前提;二是扩展见识,提升自己解码器的效果,更好地理解别人要表达的内容到底是什么。
|
||||
|
||||
说了编码器和解码器可能出现的问题,我们再来看另外一个可能造成影响的问题:**编解码器算法,也就是怎么协调沟通双方更有效地进行沟通。**
|
||||
|
||||
既然前面已经说了算法不够好会影响到信息的传递,那接下来的问题就是怎样找到一个好的算法。其实,我们从始至终在讲的各种最佳实践就是一个个好的算法,帮助我们改善沟通的效果。
|
||||
|
||||
还是回到前面提到“完成一个需求”的例子,我们在“以终为始”模块已经讲过了,通过制定“完成的定义”就可以帮助改善这个过程。**这就相当于,沟通的双方都有了一个编解码手册。**
|
||||
|
||||
当“完成一个需求”这样的信号发出时,作为接收方,你的解码动作就变成了,先要查一下手册里,关于“完成一个需求”的标准动作都有哪些。于是,你就不会对事情做那么简单的估计了。
|
||||
|
||||
在“沟通反馈”这个模块下,我还会给你介绍各种“算法”,也就是最佳实践,帮你在工作中提高“信息”传递的效率。
|
||||
|
||||
回到我们这部分主题上,**沟通反馈就是改善编码、解码以及算法的方式。**无论是“发送”得更清楚,还是“接收”得更明白,抑或是通过各种协调算法,都是为了让通信的双方做好准备。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
人生不如意之事,十有八九,之所以很多人有如此多的不如意,很大原因在于我们对真实世界有着很多不切实际的幻想,美好的愿望并不能驱动这个世界,在软件开发中也是如此。虽然人和人生活在一个世界中,但对世界的理解却是千差万别的。
|
||||
|
||||
我们借用了信息论的一个通信模型解释为什么每个人看到的世界会有如此大的差异,其核心就在于,人和人拥有不同的编解码器。想要在这个真实世界中生活得更幸福一些,需要我们不断地改善自己的编解码器。
|
||||
|
||||
改善编解码,需要从几个角度着手,分别是:编码器,让信息能输出更准确;解码器,减少信号过滤,改善解码能力;还有编解码算法,也就是各种来自行业的“最佳实践”,协调沟通的双方。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**通过沟通反馈,不断升级自己的编解码能力。**
|
||||
|
||||
最后,我想请你回想一下,你在工作中遇到过哪些因为沟通反馈不畅造成的问题呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
129
极客时间专栏/geek/10x程序员工作法/沟通反馈/21 | 你的代码为谁而写?.md
Normal file
129
极客时间专栏/geek/10x程序员工作法/沟通反馈/21 | 你的代码为谁而写?.md
Normal file
@ -0,0 +1,129 @@
|
||||
<audio id="audio" title="21 | 你的代码为谁而写?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/94/ff/94f0968fc790b62f6b15f5b6b03d83ff.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
关于“沟通反馈”的话题,我准备从代码开始讲起,毕竟我们程序员是靠代码与机器进行沟通的。
|
||||
|
||||
写代码是每个程序员的职责,程序员们都知道要把代码写好。但究竟什么叫写好呢?每个人的理解却是各有差异。
|
||||
|
||||
## 编写可维护的代码
|
||||
|
||||
初涉编程的程序员可能觉得能把功能实现出来的代码,就是好代码,这个阶段主要是基本功的学习,需要掌握的是各种算法、数据结构、典型的处理手法、常用的框架等等。
|
||||
|
||||
经过一段时间工作,日常工作所需的大多数代码,在你看来都是不在话下的。尤其像搜索和问答网站蓬勃发展之后,你甚至不需要像我初入职场时那样,记住很多常见的代码模式,现在往往是随手一搜,答案就有了。
|
||||
|
||||
再往后,更有追求的程序员会知道,仅仅实现功能是不够的,还需要写出可维护的代码。于是,这样的程序员就会找一些经典的书来看。
|
||||
|
||||
>
|
||||
我在这方面的学习是从一本叫做《程序设计实践》(The Practice of Programming)的书开始的,这本书的作者是 Brian Kernighan 和 Rob Pike,这两个人都出身于大名鼎鼎的贝尔实验室,参与过 Unix 的开发。
|
||||
|
||||
|
||||
写出可维护的代码并不难,它同样有方法可循。今天,我们用写代码中最简单的一件事,深入剖析怎样才能写出可维护的代码,这件事就是命名。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8e/28/8e2ec5013ed2185324e4b902bb6a1828.jpg" alt="">
|
||||
|
||||
## 命名难题
|
||||
|
||||
>
|
||||
<p>计算机科学中只有两大难题:缓存失效和命名。<br>
|
||||
—— Phil Karlton</p>
|
||||
|
||||
|
||||
这是行业里流传的一个经典说法,无论是哪本写代码风格的书,都会把命名放在靠前的位置。
|
||||
|
||||
估计你开始写程序不久,就会有人告诉你不要用 a、b、c 做变量名,因为它没有意义;步入职场,就会有人扔给你一份编程规范,告诉你这是必须遵循的。
|
||||
|
||||
不管怎样,你知道命名是很重要的,但在你心目中,合格的命名是什么样的呢?
|
||||
|
||||
想必你知道,命名要遵循编码规范,比如:Java 风格的 camelCase,常量命名要用全大写。
|
||||
|
||||
但是,这类代码规范给出的要求,大多是格式上的要求。在我看来,这只是底线,不应该成为程序员的追求,因为现在很多编码规范的要求,都可以用静态检查工具进行扫描了。
|
||||
|
||||
我们的讨论要从名字的意义说起。作为程序员,我们大多数人理解为什么要避免起无意义的名字,但对于什么样的名字是有意义的,每个人的理解却是不同的。
|
||||
|
||||
**名字起得是否够好,一个简单的评判标准是,拿着代码给人讲,你需要额外解释多少东西。**
|
||||
|
||||
比如,我们在代码评审中会看到类似这样的场景:
|
||||
|
||||
>
|
||||
<p>评审者:这个叫 map 的变量是做什么用的?<br>
|
||||
程序员:它是用来存放账户信息的,它的键值是账户 ID,值就是对应的账户信息。<br>
|
||||
评审者:那为什么不直接命名成 accounts?</p>
|
||||
|
||||
|
||||
你知道评审者给出的这个建议是什么意思吗?如果不能一下子意识到,遇到类似的问题,你可能会和这个程序员一样委屈:这个变量本来就是一个 map,我把它命名成 map 怎么了?
|
||||
|
||||
变量的命名,实际上牵扯到一个重要问题,**代码到底是给谁写的?**
|
||||
|
||||
## 代码为谁而写?
|
||||
|
||||
>
|
||||
<p>任何人都能写出计算机能够理解的代码,只有好程序员才能写出人能够理解的代码。<br>
|
||||
—— Martin Fowler</p>
|
||||
|
||||
|
||||
代码固然是程序员与机器沟通的重要途径,但是,机器是直白的,你写的代码必须是符合某种规则的,这一点已经由编译器保证了,不符合规则的代码,你想运行,门都没有。
|
||||
|
||||
所以,只要你的代码是符合语言规则的,机器一定认。要让机器认,这并不难,你写得再奇怪它都认。行业里甚至有专门的混乱代码比赛。比如,著名的 IOCCC(The International Obfuscated C Code Contest,国际 C 语言混乱代码大赛)。
|
||||
|
||||
**但是,我们写代码的目的是与人沟通,因为我们要在一个团队里与人协同工作。**
|
||||
|
||||
与人沟通,就要用与人沟通的方式和语言写代码。人和机器不同,人需要理解的不仅是语言规则,还需要将业务背景融入其中,因为人的目的不是执行代码,而是要理解,甚至扩展和维护这段代码。
|
||||
|
||||
**人要负责将业务问题和机器执行连接起来,缺少了业务背景是不可能写出好代码的。**
|
||||
|
||||
我们在“[为什么世界和你理解的不一样](http://time.geekbang.org/column/article/80755)”这篇内容中就讲过,沟通的时候,输出时的编码器很重要,它是保证了信息输出准确性的关键。
|
||||
|
||||
很多程序员习惯的方式是用计算机的语言进行表达,就像前面这个例子里面的 map,这是一种数据结构的名字,是面向计算机的,而评审者给出的建议,把变量名改成 accounts,这是一个业务的名字。
|
||||
|
||||
**虽然只是一个简单的名字修改,但从理解上,这是一步巨大的跨越,缩短了其他人理解这段代码所需填补的鸿沟,工作效率自然会得到提高。**
|
||||
|
||||
## 用业务语言编程
|
||||
|
||||
写代码的时候,尽可能用业务语言,会让你转换一个思路。前面还只是一个简单的例子,我们再来看一个。
|
||||
|
||||
我们用最常用的电商下单过程来说,凭直觉我们会构建一个订单类 Order。什么东西会放在这个类里呢?
|
||||
|
||||
首先,商品信息应该在这个类里面,这听上去很合理。然后,既然是电商的订单,可能要送货,所以,应该有送货的信息,没问题吧。再来,买东西要支付,我们会选择一些支付方式,所以,还应该有支付信息。
|
||||
|
||||
就这样,你会发现这个订单类里面的信息会越来越多:会员信息可能也要加进去,折扣信息也可能会加入。
|
||||
|
||||
你是一个要维护这段代码的人,这个类会越来越庞大,每个修改都要到你这里来,不知不觉中,你就陷入了一个疲于奔命的状态。
|
||||
|
||||
如果只是站在让代码运行的角度,这几乎是一个无法解决的问题。我们只是觉得别扭,但没有好的解决方案,没办法,改就改呗!
|
||||
|
||||
但如果我们有了看业务的视角,我们会问一个问题,这些信息都放在“订单”是合理的吗?
|
||||
|
||||
我们可以与业务人员交流,询问这些信息到底在什么场景下使用。这时候你就会发现,商品信息主要的用途是下单环节,送货信息是在物流环节,而支付信息则用在支付环节。
|
||||
|
||||
有了这样的信息,你会知道一件事,虽然我们在用一个“订单”的概念,但实际上,在不同的场景下,用到信息是不同的。
|
||||
|
||||
所以,更好地做法是,把这个“订单”的概念拆分了,也就有了:交易订单、物流订单和支付订单。我们原来陷入的困境,就是因为我们没有业务知识,只能笼统地用订单去涵盖各种场景。
|
||||
|
||||
如果你在一个电商平台工作,这几个概念你可能并不陌生,但实际上,类似的错误我们在很多代码里都可以看到。
|
||||
|
||||
再举个例子,在很多系统里,大家特别喜欢一个叫“用户”的概念,也把很多信息塞到了“用户”里。但实际上,在不同的场景下,它也应该是不同的东西:比如,在项目管理软件中,它应该是项目管理员和项目成员,在借贷的场景下,它应该是借款方和贷款方等等。
|
||||
|
||||
要想把这些概念很好地区分出来,你得对业务语言有理解,为了不让自己“分裂”,最好的办法就是把这些概念在代码中体现出来,给出一个好的名字。这就要求你最好和业务人员使用同样的语言。
|
||||
|
||||
如果了解领域驱动设计(Domain Driven Design,DDD),你可能已经分辨出来了,我在这里说的实际上就是领域驱动设计。把不同的概念分解出来,这其实是限界上下文(Bounded Context)的作用,而在代码里尽可能使用业务语言,这是通用语言(Ubiquitous Language)的作用。
|
||||
|
||||
所以,一个好的命名需要你对业务知识有一个深入的理解,遗憾的是,这并不是程序员的强项,需要我们额外地学习,但这也是我们想写好代码的前提。现在,你已经理解了,取个好名字,并不是一件容易的事。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
代码是程序员与机器沟通的桥梁,写好代码是每个程序员的追求,一个专业程序员,追求的不仅是实现功能,还要追求代码可维护。如果你想详细学习如何写好代码,我推荐你去读 Robert Martin 的《代码整洁之道》(Clean Code),这本书几乎覆盖了把代码写好的方方面面。
|
||||
|
||||
命名,是写程序中最基础,也是一个程序员从业余走向专业的门槛。我以命名为基础,给你解释了写好代码的提升路径。最初的层次是编写可以运行的代码,然后是编写符合代码规范的代码。
|
||||
|
||||
对于命名,最粗浅的理解是不要起无意义的名字,遵循编码规范。但名字起得是否够好,主要看是否还需要额外的解释。很多程序员起名字习惯于采用面向实现的名字,比如,采用数据结构的名字。
|
||||
|
||||
再进一步提升,编写代码是要写出人可以理解的代码。因为代码更重要的作用是人和人沟通的桥梁,起一个降低其他人理解门槛的名字才是好名字。
|
||||
|
||||
实际上,我们很多没写好的程序有一些原因就是名字起错,把一些概念混淆在一起了。想起好名字,就要学会用业务语言写代码,需要尽可能多地学习业务知识,把业务领域的名字用在代码中。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**用业务的语言写代码。**
|
||||
|
||||
最后,我想请你思考一下,想要写好代码,还有哪些因素是你特别看重的?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
113
极客时间专栏/geek/10x程序员工作法/沟通反馈/22 | 轻量级沟通:你总是在开会吗?.md
Normal file
113
极客时间专栏/geek/10x程序员工作法/沟通反馈/22 | 轻量级沟通:你总是在开会吗?.md
Normal file
@ -0,0 +1,113 @@
|
||||
<audio id="audio" title="22 | 轻量级沟通:你总是在开会吗?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/61/84/610b9bca53d47b7ed447ecece62c0c84.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
今天我们来探讨一个很多程序员日常工作中,经常碰到却会带来困扰的话题:开会。
|
||||
|
||||
## 头疼的开会
|
||||
|
||||
有一次,我听到两个程序员在聊天。一个资深程序员说:“还是晚上好,我可以一门心思写代码”,另一个年轻程序员不解地问:“你白天也可以写啊。”
|
||||
|
||||
资深程序员很无奈,“我倒是这样想,可是白天参加那么多会,哪有工夫啊!我的代码就只能加班写了。”
|
||||
|
||||
这段对话听上去让人有点心酸,但这种现象,确确实实广泛存在于程序员的日常工作中,尤其是你经验丰富又在一个大组织中工作,这几乎成了你的宿命。在这些程序员的认知中,开会太多影响了他们写代码。
|
||||
|
||||
你以为我想讨伐开会吗?并不是,开会本身并没有错,因为开会的本意是将大家组织起来解决问题。但请你回想一下,你参加的会议有多少解决了问题呢?
|
||||
|
||||
**开会是为了解决问题,但真实情况却是开了会又没有解决多少问题,这真是一个奇特的矛盾。**
|
||||
|
||||
回想一下,你参加过的会议里面,有没有效果特别好的呢?在我职业生涯中,**凡是效果特别好的会议,基本上都是用来做信息同步的。**比如,领导宣布一个事情,这种会议几乎不会浪费时间。宣布消息,大家收到消息,结束。
|
||||
|
||||
那效果不好的会议是什么样呢?几乎都是那些讨论会,你一言我一语,每个会几乎无一例外,都有几个擅长打岔的,这个会基本上都会跑偏,时间就会这样一分一秒地流逝了。
|
||||
|
||||
我给你举个例子,我之前参加过一个上线计划的评审会,这个团队的负责人要把相关利益方都召集起来,其中包括上下游可能会受影响的团队、测试、运维等等,一个不大的会议室里挤满了人。
|
||||
|
||||
这个负责人刚开始讲方案没几分钟,下游团队的负责人就站出来问:“这个方案为什么要这么做?我担心会对我们系统造成影响。”讲方案的人只好停下来解释。结果是越解释,细节越多,双方你来我往,一个方案评审会,就转变成一个技术讨论会了。
|
||||
|
||||
测试和运维的同事本来是想来听技术方案,以便为后续的工作做准备的。看着双方的讨论,一脸无奈,因为他们知道,方案没确定好,所有的事情还是下回再说吧!
|
||||
|
||||
怎么样?是不是很熟悉的感觉。为什么会这样?**因为他们选错了沟通方式。**
|
||||
|
||||
开会是一种重量级的沟通,几乎是我们日常工作中最重的。它有很强的仪式感,所以,大家相对来说会很重视。而且会议通常会牵扯到很多人,尤其是与这个事情相关度不那么高的人。
|
||||
|
||||
你可以想一下,有多少次开会,你是在精力集中的?如果你是高度集中的,那恭喜你,你是高效地参与其中。但更多时候,你可能神游天外,因为讨论的内容可能与你关系不大,或者你已经听不懂了,你坐在那里的唯一原因是,主持人还没宣布会议结束。
|
||||
|
||||
用开会这种重量级的方式讨论问题,就好比杀鸡用了牛刀,这是不恰当的。那该怎么解决这个问题呢?很简单,杀鸡用鸡刀。
|
||||
|
||||
## 轻量级沟通
|
||||
|
||||
实际上,真正在会议上能够积极参与讨论的人并不会觉得会议是浪费时间,因为高度参与其中,人是进入到心流状态的,时间流逝很快。觉得浪费时间的,往往是没有参与其中的人。
|
||||
|
||||
换句话说,会议之所以给人留下如此不堪的印象,一个重要的原因是,真正参与讨论的人并不多。所以,我们换个角度思考一下,只要把这些真正参与讨论的人拉到一起讨论不就好了?
|
||||
|
||||
**所以,改善会议的第一个行动项是,减少参与讨论的人数。**
|
||||
|
||||
有人会说,我这个讨论有好几个议题,每个议题要不同的人参与,那你要做的是,分别找这几个人专门讨论,而不是把大家放到一起。
|
||||
|
||||
不知道你发现没有,在讨论行动项的时候,我用的是“讨论”,而没有提到“会议”两个字。我之前说过了,会议是一种重量级的沟通方式。所以,我们会倾向于选择一种轻量级的沟通方式,比如面对面沟通,这样一来,每个人的压力就会小很多。
|
||||
|
||||
相比于会议的形式,面对面沟通因为注意力有限,参与的人数不可能太多。也因为参与的人数相对少一些,每个人的投入也会更多一些。
|
||||
|
||||
**所以,我们的第二个行动项是,如果你要讨论,找人面对面沟通。**
|
||||
|
||||
一旦理解了这些改进方式,我们就可以改进自己的行为方式。如果有一个问题需要讨论,我要做的是,分别找到相关人针对关心的主题进行讨论,然后,我把讨论的结果汇总再去征求大家意见。如果大家达成一致了,我才会选择开会。
|
||||
|
||||
这个时候,开会的目的不再是讨论,而是信息同步:我准备这么干了,相关各方已经同意了,知会大家一下,结束。
|
||||
|
||||
## 站立会议
|
||||
|
||||
我前面说过了,开会并非都是不好的,一些信息同步的会还是有必要的。
|
||||
|
||||
举个例子,有一种实践叫站会(Standup)。很多公司都在实践它,站会甚至成为每天的开工仪式。一般的做法是,早上大家来上班了,先开一个站会,让大家同步一下昨天的工作,然后开始今天的工作。
|
||||
|
||||
有的人一听到站会这个形式就会皱起眉头。如果是这样,多半是你的团队“站”错了。
|
||||
|
||||
你知道,这个会为什么是“站”会吗?因为按照一般人的习惯,站的时间不会太长,因为站的时间长,累啊!所以,如果站会超过10分钟,你的站会一定是错的。
|
||||
|
||||
也许你会说,这点时间恐怕不够给我们站会吧?因为每个人都有一大堆要说的。请问,你觉得其他人说那么多,你关心吗?现实是,一旦一个人说多了,跟你关系又不大,你就开始思维发散了。
|
||||
|
||||
所以,在总长固定的情况下,每个人发言的时间一定是有限的。在有限的时间内,你能说什么呢?我建议你只说三件事:
|
||||
|
||||
- 我昨天做了什么?
|
||||
- 我今天打算做什么?
|
||||
- 我在过程中遇到了什么问题,需要请求帮助。
|
||||
|
||||
**“做了什么” **,是为了与其他人同步进展,看事情是否在计划上。一旦偏离计划,请主动把它提出,这样,项目经理可以过问,因为这会涉及到是否要调整项目计划;
|
||||
|
||||
**“要做什么” **,是同步你接下来的工作安排。如果涉及到与其他人协作,也就是告诉大家,让他们有个配合的心理准备;
|
||||
|
||||
**“问题和求助”**, 就是与其他人的协作,表示:我遇到不懂的问题,你们有信息的话,可以给我提供一下。
|
||||
|
||||
这三件事都是与别人相关的,几句话快速说完,结束。因为这些事情与别人相关,所以,大家的注意力可以相对集中一些。
|
||||
|
||||
你或许会问,如果我的问题很复杂,需要讨论该怎么办。对不起,那是另外一件事,你可以在站会结束之后,找相关人去讨论,不要在这个会上浪费大家时间。在站会上,你只要在问题和求助中告诉大家,你有一个问题,需要相关人讨论,结束。
|
||||
|
||||
为了让大家保持注意力集中,我的一些团队还用过发言令牌的方式。比如,找一个毛绒玩具,谁拿到“令牌”谁发言,然后,随机地扔给一个人,一旦这个人走神,大家一下子就能发现了。
|
||||
|
||||
一些有趣的方式、短暂的时间,以及与所有人相关的事情,因为满足了这三点,所以普遍来说,这种站会效果还可以。
|
||||
|
||||
关于站会,有一个典型的错误是,有些团队把站会开成了汇报会。项目负责人指定一个个轮流发言,说的人都向负责人在汇报工作,其他人自然就容易走神了,因为事情与己无关。
|
||||
|
||||
还有一点你可能会有疑问,我所在的团队比较大,一个人几句话时间也会很长。
|
||||
|
||||
当团队很大时,更应该做的是把团队拆分了,因为你不太可能与20个人紧密地工作在一起。沃顿商学院曾经做过一项研究,5-12个人是一个恰当的团队规模,每个人在其中都能发挥自己的重要作用。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
开会是很多程序员的困扰,太多的会议甚至会影响到你工作的进展。开会的本意是为了解决问题,但实际上,大多数会议并不能很好地解决问题。因为会议是一种重量级的沟通方式,很多人参加会议时,并不能很好地参与其中。
|
||||
|
||||
如果你想用会议的形式与别人讨论问题,最好放弃这种打算,面对面的沟通是最好的方式。因为面对面沟通很轻,人数相对少,每个人参与度就会高很多。基于这种改进,我们可以把大部分会议都改成信息同步的会,效率就会得到提高。
|
||||
|
||||
我还给你介绍了一种特殊的会议:站会。之所以采用站会的方式,就是要控制时间。在站会上每个人说什么,我给了你一个建议的格式:
|
||||
|
||||
- 我昨天做了什么?
|
||||
- 我今天打算做什么?
|
||||
- 我在过程中遇到了什么问题,需要请求帮助。
|
||||
|
||||
如果你经常组织别人开会,请你想一下,是不是自己没有利用好开会这件事;如果你经常被别人组织开会,不妨把这篇文章转发给他,让他别总是开会“讨论”问题。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**多面对面沟通,少开会。**
|
||||
|
||||
最后,我想请你思考一下,你在工作中,还遇到过哪些因为开会带来的问题呢?欢迎留言与我写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
113
极客时间专栏/geek/10x程序员工作法/沟通反馈/23 | 可视化:一种更为直观的沟通方式.md
Normal file
113
极客时间专栏/geek/10x程序员工作法/沟通反馈/23 | 可视化:一种更为直观的沟通方式.md
Normal file
@ -0,0 +1,113 @@
|
||||
<audio id="audio" title="23 | 可视化:一种更为直观的沟通方式" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/17/dd/1784c96894eb88974790adba589430dd.mp3"></audio>
|
||||
|
||||
作为一个程序员,在这个技术快速发展的时代,我们唯有不断学习,才能保证自己不为时代所抛弃。那你是怎么跟上技术发展步伐的呢?
|
||||
|
||||
就个人经验而言,我会关注一些技术网站,最典型的就是 InfoQ。这样,我可以快速了解到技术发展的动向,比如,什么时候出了个新东西、哪个项目又有了重大的更新、某些技术有了哪些新的应用场景等等。
|
||||
|
||||
另外,我还有一种更系统地了解新知识的方式:ThoughtWorks 技术雷达。之所以我很喜欢这种方式,因为它是**“可视化”**的。
|
||||
|
||||
## 什么是技术雷达?
|
||||
|
||||
[ThoughtWorks 技术雷达](http://www.thoughtworks.com/radar)是由 ThoughtWorks 技术咨询委员会(Technology Advisory Board)编写的一份技术趋势报告,每6个月发布一次。ThoughtWorks 的项目多样性足够丰富,所以它能够发现诸多技术趋势。因此,相比于行业中其它的预测报告,技术雷达更加具体,更具可操作性。
|
||||
|
||||
ThoughtWorks 是我的老东家,所以,我在接触技术雷达的时间很早。我在2013年就已经开始与人讨论微服务,并在项目中尝试使用 Docker,而这一切信息的来源都是技术雷达。不过,我这里想和你讨论并不是技术雷达到底有多优秀,而是带你看看技术雷达这种组织知识的可视化形式。<br>
|
||||
<img src="https://static001.geekbang.org/resource/image/85/40/856da8d06911c547f59c845aa1e94f40.jpg" alt="">
|
||||
|
||||
(图片来源:[ThoughtWorks 技术雷达](http://www.thoughtworks.com/radar))
|
||||
|
||||
技术雷达用来追踪技术,在雷达图的术语里,每一项技术表示为一个 blip,也就是雷达上的一个光点。
|
||||
|
||||
然后用两个分类元素组织这些 blip:象限(quadrant)和圆环(ring),其中,象限表示一个 blip 的种类,目前有四个种类:技术、平台、工具,还有语言与框架。
|
||||
|
||||
圆环表示一个 blip 在技术采纳生命周期中所处的阶段,目前这个生命周期包含四个阶段:采用(Adopt)、试验(Trial)、评估(Assess)和暂缓(Hold)。
|
||||
|
||||
每次技术雷达发布之后,我会特别关注一下**“采用”** 和 **“暂缓”**两项。
|
||||
|
||||
“采用”表示强烈推荐,我会去对比一下自己在实际应用中是否用到了,比如,在2018年11月的技术雷达中,事件风暴(Event Storming)放到了“采用”中,如果你还不了解 [事件风暴](http://www.eventstorming.com) 是什么,强烈建议你点击链接了解一下。
|
||||
|
||||
**“暂缓”** 则表示新项目别再用这项技术了,这会给我提个醒,这项技术可能已经有了更优秀的替代品,比如,Java世界中最常见的构建工具 Maven 很早就放到了**“暂缓”**项中,但时至今日,很多人启动新项目依然会选择 Maven,多半这些人并不了解技术趋势。
|
||||
|
||||
从这几年的发展趋势来看,技术雷达在“采用”和“暂缓”这两项上给出的推荐,大部分是靠谱的。
|
||||
|
||||
至于“试验”和“评估”两项,有时间的时候,我会慢慢看,因为它们多半属于新兴技术的试验区,主要的作用是用来让我开拓视野的。
|
||||
|
||||
**雷达图是一种很好的将知识分类组织的形式,它可以让你一目了然地看到并了解所有知识点,并根据自己的需要,决定是否深入了解。**
|
||||
|
||||
所以,我的前同事们借鉴了这个形式,做出了一个程序员的读书雷达,将程序员的应该阅读的书籍做了一个整理。<br>
|
||||
<img src="https://static001.geekbang.org/resource/image/a3/70/a3458d87858d50b0b638c8d5c1f3bd70.png" alt=""><br>
|
||||
(图片来源:[ThoughtWorks读书雷达](http://insights.thoughtworkers.org/reading-radar-2016/))
|
||||
|
||||
事实上,这种将内容通过可视化方式的组织起来的形式非常好用,ThoughtWorks 鼓励每个组织都建立自己的知识雷达,甚至提供了一个工具辅助你将雷达图构建出来。
|
||||
|
||||
**在我看来,雷达图不仅仅适用于组织,也可以适用于团队。**
|
||||
|
||||
我也曾经按照雷达图的方式将自己的团队用到的技术组织起来。把最需要了解的技术必须放在内环,比如:一个 Java 项目。我会要求程序员了解 Java,向外扩展的就是你在这个团队内工作会逐渐接触到的技术,比如,像 Docker 这种与部署相关的知识。至于最外面一层,就是被我们放弃掉的技术,比如,Maven。
|
||||
|
||||
这样一来,团队成员可以更清晰地了解到团队中所用的技术。当有新人加入团队时,这个雷达可以帮助新人迅速地抓住重点,他的学习路径就是从内环向外学习。所以,我也推荐你打造自己团队的技术雷达。
|
||||
|
||||
>
|
||||
<p>[构建技术雷达](https://www.thoughtworks.com/cn/radar/byor)<br>
|
||||
[构建雷达的程序库](https://github.com/thoughtworks/build-your-own-radar)</p>
|
||||
|
||||
|
||||
你是否想过,为什么雷达图的形式可以帮助你更好地理解知识呢?**因为人的大脑更擅长处理图像。**
|
||||
|
||||
## 可视化的优势
|
||||
|
||||
在远古时代,人脑处理的内容大多是图像,比如,哪里有新的果实,哪里猛兽出没,文字则是很久之后才产生的。现在普遍的一种说法是,大约在公元前3500年左右,许多文明才刚刚发展出书写系统,相比于人类的历史来说,这几乎是微不足道的。
|
||||
|
||||
就人脑的进化而言,**处理图像的速度远远快于处理文字,**所以,有“一图胜千言”的说法。
|
||||
|
||||
通过创建图像、图标或动画等进行信息交流的形式,就是可视化(Visualization)。可视化有很多种不同的分类,我们最常用的应该是数据可视化和信息可视化。
|
||||
|
||||
我在“[你的工作可以用数字衡量吗](http://time.geekbang.org/column/article/76929)”这篇文章里说过,我上班第一件事是“看”数字,这就是典型的数据可视化,而上面介绍的技术雷达,就属于信息可视化。
|
||||
|
||||
很多做软件的人习惯于用文字进行沟通,一般在软件开发过程中,需要编写各种文档,但并不是所有的场景,文字都是好的沟通方式,所以,也会有很多人尝试着将可视化应用在软件开发过程中。
|
||||
|
||||
估计大多数程序员最熟悉的表达方式应该是流程图,如果你做过软件设计,可能还听说过 UML(统一建模语言,Unified Modeling Language)。如果使用得当,这种方式会极大地提高表达的准确性,降低其他人理解的门槛。
|
||||
|
||||
在日常工作中,你最熟悉的可视化方式,大概就是在纸上或白板上画的图。以我的经验看,很多人画这个图太随意,如果你也是这样,我给你一个建议,先写字后画框,这样图会显得整洁一些。
|
||||
|
||||
## 什么是看板?
|
||||
|
||||
我们再来看一个实践,这就是将“可视化”应用在工作中的典型案例:看板。
|
||||
|
||||
看板,是一种项目管理工具,它将我们正在进行的工作变得可视化。这个实践来自精益生产,前面讲精益创业时,我给介绍了“精益”这个来自丰田公司的管理理念。精益的理念在软件行业已经非常流行了,很多软件开发实践都是从“精益”而来,看板就是其中之一。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/2c/07/2c0754dd615fac0467eaa68d627d9307.png" alt="">
|
||||
|
||||
看板属于那种几乎是看一眼就知道怎么用的实践。它将工作分成几个不同的阶段,然后,把分解出来的工作做成一张卡片,根据当前状态放置到不同的阶段中。如果你采用了我们专栏之前讲过的用户故事,那么每个用户故事就是一张卡片。
|
||||
|
||||
在实际工作中,每当一个工作完成之后,它就可以挪到下一个阶段,工作怎么算完成就是由我们前面提到的 DoD 来决定的。
|
||||
|
||||
当然,要用好看板,还可以使用一些小技巧。比如,用不同颜色的卡表示不同类型的工作,给每个人一个头像,增添一些乐趣。
|
||||
|
||||
看板可以帮助你一眼看出许多问题,比如,你的团队中有5个人,却有8个正在进行的任务,那一定是有问题的。因为一个人多线程工作,效果不会好。用“精益”的术语来说,我们应该限制 WIP(Work-In-Progress);再有,如果待开发的卡最多,那就证明现在的瓶颈在于开发,而不是其它阶段。
|
||||
|
||||
运用看板的方式,还有一个有趣的细节:使用实体墙还是电子墙。实体墙不难理解,就是找一面墙把看板做出来。现在有很多公司专门在做协同办公软件,其中的项目管理部分用到的就是看板理念,这就是电子墙的由来。
|
||||
|
||||
关于这点,顺便说一下我的建议,如果你的团队是在一起工作的,请考虑使用实体墙,除非你的办公空间实在太小。因为它可以方便地调整,也可以当作站会的集合地点,还可以让别人看见你们的工作或是问题,这样做的最大优势在于增强了人与人的互动。
|
||||
|
||||
电子墙的优势在于,随处可访问、数据不会丢失、便于统计等等,但每次访问它,都需要专门打开电脑,还是比较麻烦的。一种将二者结合的办法是,使用一个大电视,专门用来展示电子墙。
|
||||
|
||||
总之,看板就是要让工作在大家面前展现出来。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
我给你介绍了一种结构化学习新知识的方式:技术雷达。
|
||||
|
||||
技术雷达就是一种将技术信息组织起来的方式。它通过将技术按照“象限”和“圆环”两个维度进行分类,让人可以直观地看到并理解不同的技术所处的发展阶段。
|
||||
|
||||
雷达图是一种很好的形式,不仅可以用在组织技术,还可以用来组织其它信息,比如,读书雷达。每个公司都可以利用雷达图的形式组织自己所有的技术,每个团队也可以利用雷达图的形式组织自己团队用到的技术,这样,方便团队成员结构化地理解用到技术,也方便新人的学习。
|
||||
|
||||
雷达图实际上是一种可视化的方法,人脑对于图像处理速度更快,因此,可视化是改善沟通的一种方式。大多数软件过程习惯采用文字的方式进行表达,对于“可视化”利用的还不够。当然,还是有一些利用“可视化”的方法,比如,流程图、UML 等。
|
||||
|
||||
最后,我给你介绍了一个利用可视化进行信息沟通的实践:看板。看板把工作分成了几个不同的阶段,在看板上对应不同的列,然后,每个任务作为一张卡贴在上面。每完成一张卡,就把这张卡挪到下一个阶段。
|
||||
|
||||
看板可以帮你发现许多问题,比如,当前进展是否合适,是否有人同时在做很多的事,发现当前工作的瓶颈等等。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**多尝试用可视化的方式进行沟通。**
|
||||
|
||||
最后,我想请你思考一下,你在工作中,有哪些用到可视化方法解决沟通问题的场景?欢迎留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
147
极客时间专栏/geek/10x程序员工作法/沟通反馈/24 | 快速反馈:为什么你们公司总是做不好持续集成?.md
Normal file
147
极客时间专栏/geek/10x程序员工作法/沟通反馈/24 | 快速反馈:为什么你们公司总是做不好持续集成?.md
Normal file
@ -0,0 +1,147 @@
|
||||
<audio id="audio" title="24 | 快速反馈:为什么你们公司总是做不好持续集成?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/bc/48/bc1ba63e0fb90c3423694d63c82e1148.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
在“以终为始”那个模块,我们留下了一个巨大的尾巴。在“[持续集成:集成本身就是写代码的一个环节](http://time.geekbang.org/column/article/75977)”这篇文章中,我们是站在“以终为始”的角度阐述了集成,尤其是持续集成的重要性。
|
||||
|
||||
但怎么做好持续集成,才是很多人真正关心的内容。今天,我们就来谈谈如何做好持续集成。
|
||||
|
||||
既然我们打算讨论持续集成,不妨停下来先思考一个问题:你对持续集成的第一印象是什么。
|
||||
|
||||
持续集成?Jenkins?没错,很多人对持续集成第一印象都是持续集成服务器,也就是 CI 服务器,当年是 CruiseControl,今天换成了 Jenkins。
|
||||
|
||||
也正是因为如此,很多人就把 CI 服务器理解成了持续集成。我就曾经接触过这样的团队,他们恨不得把所有的事情都放在 CI 服务器上做:在 CI 服务器上做了编译,跑了代码检查,运行了单元测试,做了测试覆盖率的统计等等。
|
||||
|
||||
或许你会疑问,这有什么不对的吗?
|
||||
|
||||
在做软件这件事上,我们不会用对与错去衡量,我只能说,这种做法是可行的,但它不是最佳实践。我希望你去思考,有没有比这更好的做法呢?
|
||||
|
||||
想要回答这个问题,我们还是要回到持续集成的本质上去。持续集成的诞生,就是人们尝试缩短集成周期的结果。为什么要缩短周期呢?因为我们希望尽早得到反馈,知道自己的工作结果是否有效。
|
||||
|
||||
所以,想要做好持续集成,就需要顺应持续集成的本质:尽快得到工作反馈。
|
||||
|
||||
由此,我们便得到持续集成的关键点,你只要记住一句话,**快速反馈。**
|
||||
|
||||
快速反馈,这句分成两个部分,快速和反馈,这也就引出了持续集成的两个重要目标:**怎样快速地得到反馈,以及什么样的反馈是有效的。**
|
||||
|
||||
## 快速得到反馈
|
||||
|
||||
我们回到前面的例子上,把各种检查放到 CI 服务器上执行,它可以让我们知道代码是不是有问题,这是一个有效的反馈,但它的反馈够快速吗?虽然比起没有持续集成的状态,它是好很多。但是,我们需要问一个问题,能不能更快地得到反馈呢?
|
||||
|
||||
显然,我们还可以做得更快。在自己的开发机上执行这些检查,就会比在 CI 服务器快。也就是说,**执行同样的操作,本地环境会快于 CI 服务器环境。**
|
||||
|
||||
为什么会这样呢?我们先来看看所有检查在 CI 服务器上执行,每个程序员的动作是什么样的。
|
||||
|
||||
我们写好代码,然后需要提交代码,等待 CI 服务器运行检查结果,然后,用 CI 监视器查看执行结果。如果没问题,继续做下一个任务,如果有错误,修复错误,再执行同样的过程。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/31/cc/318016501ec6de73ac39ae9392c5eecc.jpg" alt="">
|
||||
|
||||
再来看看本地执行的动作。运行构建脚本,如果一切正确,你可以选择提交代码或是继续下一个任务,如果失败,立即修复。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/99/c1/99294ff1d2b2c25c5433ca653d4ffcc1.jpg" alt="">
|
||||
|
||||
对比之下,在本地运行这些检查,你不需要提交,不需要等 CI 服务器开始执行,不需要跑到额外的地方查看检查结果。所以,这个操作比提交到服务器上会快很多。
|
||||
|
||||
另外,这里还有一个关键点,我们的操作是连续的。一旦检查结果出错了,我们立刻进入修复环节。作为程序员,我们太了解连续操作的重要性了。这就像打游戏时,我们感觉不到时间流逝一般,有人把这种状态称之为“心流”。
|
||||
|
||||
而提交代码,等待 CI 服务器的检查结果,就等于强迫你停下来,你的心流就被打断了。
|
||||
|
||||
如果你对心流的概念感兴趣,可以去读米哈里·契克森米哈赖的著作[《心流》](http://book.douban.com/subject/27186106/),这位作者就是心流概念的提出者。
|
||||
|
||||
前面我们只是在说,你作为程序员个体,使用持续集成的效果,这只是为了简化讨论。接下来,我们向更真实的世界靠拢,引入另一个重要的因素:团队协作。
|
||||
|
||||
假设你的团队就是在 CI 服务器上执行检查。你兴高采烈地写完一段代码准备提交,结果,此时你隔壁的同事手快一筹,先提交了,你不得不停下来等他。如果很不幸,你同事的检查失败的话,那么他又要把它修复好,你等的时间就更长了。
|
||||
|
||||
一个小问题也就罢了,如果是个大问题,他可能要修很长一段时间。这个时候,你除了等待,也没有更好的选择。如此一来,大把的时间就被浪费掉了。
|
||||
|
||||
这里我们要“插播”持续集成中重要的一个提交纪律:**只有 CI 服务器处于绿色的状态才能提交代码。**有检查在运行不能提交,有错误不能提交。原因很简单,如果这个时候多个人提交了代码,检查失败了,那问题到底算谁的呢?
|
||||
|
||||
反之,如果一次只有一个人提交代码,责任是明确的。如果团队不大,这个纪律相对还好执行,提交之前看一眼,或是喊一声就可以了。
|
||||
|
||||
如果团队稍微有一点规模,可以用一个小东西当作令牌,谁拿到了谁来提交。如果真的有人在 CI 服务器还在运行的时候,提交了代码怎么办?很简单,谁提交谁负责,错了就他修,谁让他违反纪律了。
|
||||
|
||||
好,你已经理解了我说的重点:**不能把检查只放到 CI 服务器上执行。**那该怎么做呢?答案已经呼之欲出了,那就是在本地开发环境上执行。
|
||||
|
||||
想做好持续集成的一个关键点是,**用好本地构建脚本(build script),保证各种各样的检查都可以在本地环境执行。**
|
||||
|
||||
一旦有了构建脚本,你在 CI 服务器上的动作也简单了,就是调用这个脚本。也就是说,本地检查和 CI 服务器上的动作是一致的。
|
||||
|
||||
至于什么样的内容适合放在构建脚本里,这个话题我们先放一放,把它留到后续“自动化”模块再做讨论。
|
||||
|
||||
在“任务分解”模块中,我与你讨论了“小”动作在工作中的重要性,“小”动作完成得越快,工作反馈得到也越快,所以说,也只有坚持不懈地做“小”动作,才能缩短反馈周期。
|
||||
|
||||
现在我们把这个道理与持续集成结合起来理解,我们的工作流程就变成了这样:
|
||||
|
||||
每完成一个任务,在本地运行构建脚本,如果有问题,就修复;没问题,则可以同步代码。如果 CI 服务器上没有正在运行的服务,就可以提交代码了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/85/43/8589e69a2774c1f7c9553afb4d644843.jpg" alt="">
|
||||
|
||||
提交代码中最麻烦的动作,其实是合并代码。不过,因为我们做的是小任务,改动的代码量并不大,所以,即便有需要合并的代码,量也不会很大,所需的脑力以及工作时间都会少很多。如此一来,我们的开发效率才可能能真正得到提高。
|
||||
|
||||
当团队真正地实施起持续集成,你会发现随着时间增加,本地检查的时间会越来越长。原因有很多,比如,代码越来越多,测试也越来越多。总之,检查的时间长了,就会对集成的速度造成影响。
|
||||
|
||||
这个时候,本着快速反馈的理念,我们就必须想办法。比如,有的团队做了分布式测试运行,有的团队将测试分类,就是我们在测试金字塔中讲到的分类,在本地执行单元测试和集成测试,而把更复杂的系统测试放到 CI 服务器上运行。
|
||||
|
||||
**简单来说,我们的目的就是快速地得到反馈。**
|
||||
|
||||
## 得到有效的反馈
|
||||
|
||||
说完了“快速”,我们再来看看做好持续集成的第二个重点:**反馈,也就是怎么得到有效的反馈。**
|
||||
|
||||
为什么需要反馈,道理很简单,我们得知道自己做得对不对。你可能会问,根据前面的说法,如果本地和 CI 服务器上执行的是一样的脚本,我在本地通过了,还用关心 CI 服务器的反馈吗?
|
||||
|
||||
当然要。因为还会出现很多其他问题,比如说最简单的一种情况是,你漏提交了一个文件。
|
||||
|
||||
好,既然我们要关注CI 服务器的反馈,下一个问题就是,它怎么反馈给我们呢?
|
||||
|
||||
我们还是从一种常见的错误入手。有些团队做持续集成用的反馈方式是什么呢?答案是邮件。
|
||||
|
||||
以邮件进行反馈,问题出在哪里呢?很明显,邮件不是一种即时反馈的工具。
|
||||
|
||||
我不知道有多少人会把邮件客户端当作日常的工具,就我个人习惯而言,一天查看几次邮件就算不错了,如果以邮件作为反馈方式,很有可能是出错了很长时间,我都无知无觉。
|
||||
|
||||
我们前面一直在强调快速,需要的是即时反馈,一旦邮件成了持续集成链条中的一环,无论如何都快不起来。
|
||||
|
||||
那你可以怎么做呢?在前面各种讨论中,我其实已经透露了答案:持续集成监视器,也是 CI 监视器。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e0/03/e0971c121dbce75487d4033131711603.png" alt=""><br>
|
||||
(图片来源:[CI 监视器的示例 projectmonitor](http://github.com/pivotal-legacy/projectmonitor))
|
||||
|
||||
CI 监视器的原理很简单,CI 服务器在构建完之后,会把结果以API的方式暴露出来,早期有RSS和ATOM格式,后来有JSON的格式。得到的结果就可以用不同的方式进行展现了。市面上有很多CI 监视器的软件,有的是拿到结果之后,做一个视觉呈现,有的是做桌面通知。
|
||||
|
||||
现在,我们终于要讲到这个部分的重点了:**怎么呈现是有效的?**
|
||||
|
||||
答案很简单:**怎么引人注目,怎么呈现。**
|
||||
|
||||
比如,很多团队的做法是,用一个大屏幕将持续集成的结果展示出来,这样一来,持续集成的结果所有人都能看到,一旦出错了,即便你一时疏忽,也会有人来提醒你。
|
||||
|
||||
还有一些感官刺激的做法,比如,有人用上了红绿灯,测试失败则红灯闪烁;还有人甚至配上了语音,用喇叭高喊:“测试失败了,请赶紧修复。”我在一个视频里见过一个更夸张的做法:有人用玩具枪,出错了,就瞄准提交者开上一枪。
|
||||
|
||||
你是聪明的程序员,你应该能想到更多有趣的玩法。
|
||||
|
||||
为什么要这么做呢?这里的重点是,想做好持续集成,需要整个团队都关注持续集成。
|
||||
|
||||
这些引人注目的做法,就是要提高持续集成的关注度。否则,即便持续集成的技术环节做得再出色,人的注意力不在,持续集成也很难起到作用。
|
||||
|
||||
所以,你看到了,持续集成的反馈,尤其是出错之后的反馈方式,几乎是所有实践中最为高调的,它的目的就是要引人注目。
|
||||
|
||||
这里再插播一条持续集成的纪律:**CI 服务器一旦检查出错,要立即修复。**原因很简单,你不修,别人就不能提交,很多人的工作就会因此停顿下来,团队的工作流就会被打断,耽误的是整个团队的工作。
|
||||
|
||||
如果你一时半会修不好怎么办,撤销你的提交。更关键的原因是,团队对于持续集成的重视度,长时间不修复,持续集成就失去了意义,人们就会放弃它,持续集成在你的项目中,也就发挥不出任何作用了。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
持续集成是软件开发中的重要实践,做好持续集成的关键在于,快速反馈。这里面有两个目标,怎样快速地得到反馈,以及什么样的反馈是有效的。
|
||||
|
||||
做好快速反馈,要把本地能做好的事情,在本地做好;也要通过小步提交的方式,加快代码开发的节奏。什么是有效的反馈?一是即时的反馈,二是引人注目的反馈。有很多种持续集成相关的工具可以帮助我们达成有效的反馈。
|
||||
|
||||
想要做好持续集成,还要有一些纪律要遵循:
|
||||
|
||||
- 只有 CI 服务器处于绿色的状态才能提交代码;
|
||||
- CI 服务器一旦检查出错,要立即修复。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**做好持续集成的关键在于,快速反馈。**
|
||||
|
||||
最后,我想请你分享一下,你的团队做持续集成吗?遇到过哪些困难呢?欢迎留言与我们分享。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
115
极客时间专栏/geek/10x程序员工作法/沟通反馈/25 | 开发中的问题一再出现,应该怎么办?.md
Normal file
115
极客时间专栏/geek/10x程序员工作法/沟通反馈/25 | 开发中的问题一再出现,应该怎么办?.md
Normal file
@ -0,0 +1,115 @@
|
||||
<audio id="audio" title="25 | 开发中的问题一再出现,应该怎么办?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f8/c4/f842b938482bb777c428408b1c882cc4.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
看过《圣斗士星矢》的同学大多会对其中的一个说法印象颇深:圣斗士不会被同样的招数击败两次。
|
||||
|
||||
我们多希望自己的研发水平也和圣斗士一样强大,可现实却总不遂人愿:同样的线上故障反复出现,类似的 Bug 在不同的地方一再地惹祸,能力强的同学每天就在“灭火”中消耗人生。我们难道就不能稍微有所改善吗?
|
||||
|
||||
如果在开发过程中,同样的问题反复出现,说明你的团队没有做好复盘。
|
||||
|
||||
## 什么是复盘?
|
||||
|
||||
复盘,原本是一个围棋术语,就是对弈者下完一盘棋之后,重新把对弈过程摆一遍,看看哪些地方下得好,哪些下得不好,哪些地方可以有不同甚至是更好的下法等等。
|
||||
|
||||
**这种把过程还原,进行研讨与分析的方式,就是复盘。**
|
||||
|
||||
现如今,复盘的概念已经被人用到了很多方面,比如,股市的复盘、企业管理的复盘,它也成为了许多人最重要的工具,帮助个体和企业不断地提升。这其中最有名的当属联想的创始人柳传志老爷子,他甚至把“复盘”写到了联想的核心价值观里。
|
||||
|
||||
为什么复盘这么好用呢?在我看来有一个重要的原因,在于**客体化。**
|
||||
|
||||
俗话说,当局者迷,旁观者清。以我们的软件开发作为例子,在解决问题的时候,我们的注意力更多是在解决问题本身上,而很少会想这个问题是怎么引起的。
|
||||
|
||||
当你复盘时,你会站在另外一个视角,去思考引起这个问题的原因。这个时候,你不再是当事者,而变成了旁观者。你观察原来那件事的发生过程,就好像是别人在做的一样。你由一个主观的视角,变成了一个客观的视角。
|
||||
|
||||
**用别人的视角看问题,这就是客体化。**
|
||||
|
||||
在软件开发领域,复盘也是一个重要的做法,用来解决开头提到那些反复出现的问题,只不过,它会以不同的方式呈现出来。
|
||||
|
||||
## 回顾会议
|
||||
|
||||
回顾会议是一个常见的复盘实践,定期回顾是一个团队自我改善的前提。回顾会议怎么开呢?我给你分享我通常的做法。
|
||||
|
||||
作为组织者,我会先在白板上给出一个主题分类。我常用的是分成三类:“做得好的、做得欠佳的、问题或建议”。
|
||||
|
||||
还有不同的主题分类方式,比如海星图,分成了五大类:“继续保持、开始做、停止做、多做一些、少做一些”五类。
|
||||
|
||||
分类方式可以根据自己团队的喜好进行选择。我之所以选用了三类的分类方式,因为它简单直观,几乎不需要对各个分类进行更多的解释。
|
||||
|
||||
然后,我会给与会者五分钟时间,针对这个开发周期内团队的表现,按照分类在便签上写下一些事实。比如,你认为做得好的是按时交付了,做得不好的是 Bug 太多。
|
||||
|
||||
这里面有两个重点。**一个是写事实,不要写感受。**因为事实就是明摆在那里的东西,而感受无法衡量,你感觉好的东西,也许别人感觉很糟糕。
|
||||
|
||||
另外,**每张便签只写一条,因为后面我要对便签归类。**因为大家是分头写的,有可能很多内容是重复的,所以,要进行归类。
|
||||
|
||||
五分钟之后,我会号召大家把自己写的便签贴到白板上。等大家把便签都贴好了,我会一张一张地念过去。
|
||||
|
||||
这样做是为了让大家了解一下其他人都写了些什么,知道不同人的关注点是什么。一旦有哪一项不清楚,我会请这张便签的作者出来解释一下,保证大家对这个问题的理解是一致的。在念便签的同时,我就顺便完成了便签归类的工作。
|
||||
|
||||
等到所有的便签都归好类,这就会成为后续讨论的主题,与会者也对于大家的关注点和看到的问题有了整体的了解。
|
||||
|
||||
做得好的部分,是大家值得自我鼓励的部分,需要继续保持。而我们开回顾会议的主要目的是改善和提升,所以,我们的重点在于解决做得不好的部分和有问题出现的地方。
|
||||
|
||||
在开始更有针对性的讨论之前,我会先让大家投个票,从这些分类中选出自己认为最重要的几项。我通常是给每人三票,投给自己认为重要的主题。每个人需要在诸多内容中做出取舍,你如果认为哪一项极其重要,可以把所有的票都投给这个主题。
|
||||
|
||||
根据大家的投票结果,我就会对所有的主题排出一个顺序来,而这就是我们要讨论的顺序。我们不会无限制的开会,所以,通常来说,只有最重要的几个主题才会得到讨论。
|
||||
|
||||
无论是个人选择希望讨论的主题,还是团队选择最终讨论的主题,所有人都要有“优先级”的概念在心里。然后,我们就会根据主题的顺序,一个一个地进行讨论。
|
||||
|
||||
讨论一个具体的主题时,我们会先关注现状。我会先让写下反馈意见的人稍微详细地介绍他看到的现象。比如,测试人员会说,最近的 Bug 比较多,相比于上一个开发周期,Bug 增加了50%。
|
||||
|
||||
然后,我会让大家分析造成这个现象的原因。比如,有人会说,最近的任务量很重,没有时间写测试。
|
||||
|
||||
再下来,我们会尝试着找到一个解决方案,给出行动项。比如,任务重,我们可以让项目经理更有效地控制一下需求的输入,再把非必要的需求减少一下;测试被忽略了,我们考虑把测试覆盖率加入构建脚本,当测试覆盖率不足时,就不允许提交代码。
|
||||
|
||||
请注意,**所有给出的行动项应该都是可检查的,而不是一些无法验证的内容。**比如,如果行动项是让每个程序员都“更仔细一些”,这是做不到的。因为“仔细”这件事很主观,你说程序员不仔细,程序员说我仔细了,这就是扯皮的开始。
|
||||
|
||||
而我们上面给出的行动项就是可检查的,项目经理控制输入的需求,我们可以用工作量衡量,还记得我们在讨论用户故事中提到的工作量评估的方式吗?
|
||||
|
||||
控制工作量怎么衡量?就是看每个阶段开发的总点数是不是比上一个阶段少了。而测试覆盖率更直接,直接写到构建脚本中,跑不过,不允许提交代码。
|
||||
|
||||
好,列好了一个个的行动项,接下来就是找责任人了,责任人要对行动项负责。
|
||||
|
||||
比如,项目经理负责需求控制,技术负责人负责将覆盖率加入构建脚本。有了责任人,我们就可以保障这个任务不是一个无头公案。下一次做回顾的时候,我们就可以拿着一个个的检查项询问负责人任务的完成情况了。
|
||||
|
||||
## 5个为什么
|
||||
|
||||
无论你是否采取回顾会议的方式进行复盘,分析问题,找到根因都是重要的一环。
|
||||
|
||||
你的团队如果能一下洞见到根因固然好,如果不能,那么最好多问一些为什么。具体怎么问,有一个常见的做法是:5个为什么(5 Whys)。这种做法是丰田集团的创始人丰田佐吉提出的,后来随着丰田生产方式而广为人知。
|
||||
|
||||
为什么要多问几个为什么?因为初始的提问,你能得到的只是表面原因,只有多问几个为什么,你才有可能找到根本原因。
|
||||
|
||||
我给你举个例子。服务器经常返回504,那我们可以采用“5个为什么”的方式来问一下。
|
||||
|
||||
- 为什么会出现504呢?因为服务器处理时间比较长,超时了。
|
||||
- 为什么会超时呢?因为服务器查询后面的 Redis 卡住了。
|
||||
- 为什么访问 Redis 会卡住呢?因为另外一个更新 Redis 的服务删除了大批量的数据,然后,重新插入,服务器阻塞了。
|
||||
- 为什么它要大批量的删除数据重新插入呢?因为更新算法设计得不合理。
|
||||
- 为什么一个设计得不合理的算法就能上线呢?因为这个设计没有按照流程进行评审。
|
||||
|
||||
问到这里,你就发现问题的根本原因了:设计没有经过评审。找到了问题的原因,解决之道自然就浮出水面了:一个核心算法一定要经过相关人员的评审。
|
||||
|
||||
当然,这只是一个例子。有时候,这个答案还不足以解决问题,我们还可以继续追问下去,比如,为什么没有按流程评审等等。
|
||||
|
||||
**所以,“5个为什么”中的“5”只是一个参考数字,不是目标。**
|
||||
|
||||
“5个为什么”是一个简单易上手的工具,你可能听了名字就知道该怎么用它。有一点需要注意的是,问题是顺着一条主线追问,不能问5个无关的问题。
|
||||
|
||||
无论是“回顾会议”也好,“5个为什么”也罢,其中最需要注意的点在于,不要用这些方法责备某个人。我们的目标是想要解决问题,不断地改进,而不是针对某个人发起情感批判。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
在软件研发中,许多问题是反复出现的,很多开发团队会因此陷入无限“救火”中,解决这种问题一个好的办法就是复盘。
|
||||
|
||||
复盘,就是过程还原,进行研讨与分析,找到自我改进方法的一个方式。这种方式使我们拥有了客体化的视角,能够更客观地看待曾经发生过的一切。这种方法在很多领域中都得到了广泛的应用,比如股市和企业管理。
|
||||
|
||||
在软件开发中,也有一些复盘的实践。我给你详细介绍了“回顾会议”这种形式。
|
||||
|
||||
无论哪种做法,分析问题,找到根因是一个重要的环节。“5个为什么”就是一个常用的找到根因的方式。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**定期复盘,找准问题根因,不断改善。**
|
||||
|
||||
最后我想请你分享一下,你的团队是怎么解决这些反复出现的问题呢?欢迎在留言区写下你的做法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
99
极客时间专栏/geek/10x程序员工作法/沟通反馈/26 | 作为程序员,你也应该聆听用户声音.md
Normal file
99
极客时间专栏/geek/10x程序员工作法/沟通反馈/26 | 作为程序员,你也应该聆听用户声音.md
Normal file
@ -0,0 +1,99 @@
|
||||
<audio id="audio" title="26 | 作为程序员,你也应该聆听用户声音" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b8/fd/b8df2a8c770271bb0b5e9df3b55c98fd.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
在前面的专栏内容中,我们讨论过几次与产品经理的交流:你应该问问产品经理为什么要做这个产品特性,要用 MVP(最小可行产品)的角度,衡量当前做的产品特性是不是一个好的选择。
|
||||
|
||||
但还有一个问题可能困扰着我们:怎么判断产品经理说的产品特性是不是用户真的需要的呢?
|
||||
|
||||
很多时候,产品经理让你实现一个产品特性,你感觉这么做好像不太对,却又说不出哪不对,想提出自己的看法,却不知道从哪下手。之所以会遇到这样的问题,一个重要的原因就是,你少了一个维度:用户视角,你需要来自真实世界的反馈。
|
||||
|
||||
## 吃自家的狗粮
|
||||
|
||||
产品经理无论要做什么,他都必须有一个立足的根基:为用户服务。所以,如果你了解了用户怎么想,你就有资本判断产品经理给出的需求,是否真的是用户需要的了。
|
||||
|
||||
**而作为一个程序员,欠缺用户视角,在与产品经理的交流中,你是不可能有机会的,因为他很容易用一句话就把你打败:“这就是用户需求。”**
|
||||
|
||||
很多程序员只希望安安静静地写好代码,但事实上,对于大多数人来说,安安静静是不太可能写好代码的,只有不断扩大自己的工作范围,才可能对准“靶子”。
|
||||
|
||||
今天我们讨论的角度,就是要你把工作范围扩大,**由听产品经理的话,扩大成倾听用户的声音。**
|
||||
|
||||
作为程序员,你应该听说过一个说法“Eat your own dog food”(吃自家的狗粮)。这个说法有几个不同的来源,都是说卖狗粮的公司真的用了自家的狗粮。
|
||||
|
||||
从1988年开始,这个说法开始在 IT 行业流行的,微软的保罗·马瑞兹(Paul Maritz)写了一封“Eating our dog food”的邮件,提到要“提高自家产品在内部使用的比例。”从此,这个说法在微软迅速传播开来。
|
||||
|
||||
如今,自己公司用自己的产品几乎成了全行业的共识。抛开一些大公司用这个说法做广告的因素,不断使用自家的产品,会让你多出一个用户的视角。
|
||||
|
||||
在挑毛病找问题这件事上,人是不需要训练的,哪里用着不舒服,你一下子就能感受到。所以,不断地使用自家产品,你自己就是产品的用户,这会促使你不断去思考怎么改进产品,再与产品经理讨论时,你就自然而然地拥有了更多的维度。
|
||||
|
||||
比如,前面在讨论 MVP 时,我曾经讲过一个我做 P2P 产品的经历。在这个项目中,我就作为用户在上面进行了一些操作。当自己作为用户使用时,就发现了一些令人不爽的地方。
|
||||
|
||||
比如,一开始设计的代金券只能一次性使用,如果代金券金额比较大,又没那么多本金,只能使用代金券的一部分,就会让人有种“代金券浪费了”的感觉。
|
||||
|
||||
于是,我就提出是不是可以把代金券多次使用。很快,产品就改进了设计。**这种改进很细微,如果你不是用户,只从逻辑推演的角度是很难看到这种差异的。**
|
||||
|
||||
## 当你吃不到狗粮时
|
||||
|
||||
不过,不是每家公司的产品都那么“好吃”。“吃自家狗粮”的策略对于那些拥有“to C”产品的公司来说,相对是比较有效的。但有时候,你做的产品你根本没有机会用到。
|
||||
|
||||
我曾经与很多海外客户合作过,我做的很多产品,自己根本没有机会使用。比如,我做过五星级酒店的审计平台。除了能对界面上的内容稍微有点感觉之外,对于使用场景,我是完全不知道的。
|
||||
|
||||
如果没有机会用到自己的产品,我们该怎么办呢?我们能做的就是尽可能找机会,去到真实场景里,看看用户是如何使用我们软件的。
|
||||
|
||||
比如,做那个酒店审计平台时,我就和客户一起到了一家五星级酒店,看着他们怎样一条一条地按照审计项核查,然后把审计结果登记在我们的平台上。
|
||||
|
||||
那些曾经只在写程序时见到的名词,这回就活生生地呈现在我眼前了。后来再面对代码时,我看到就不再是一个死板的程序了,我和产品经理的讨论也就更加扎实了。
|
||||
|
||||
有的团队在这方面有比较好的意识,会主动创造一些机会,让开发团队成员有更多机会与用户接触。
|
||||
|
||||
比如,让开发团队到客服团队轮岗。接接电话,听听用户的抱怨,甚至是谩骂。你会觉得心情非常不好,但当你静下来的时候,你就会意识到自己的软件有哪些问题,如果软件做得不好,影响会有多大。
|
||||
|
||||
这时,你也就能理解,为什么有的时候,很多业务人员会对开发团队大发雷霆了,因为他们是直接面对用户“炮火”的人。
|
||||
|
||||
我们为什么要不断地了解用户的使用情况呢?因为用户的声音是来自真实世界的反馈。不去聆听用户声音,很容易让人自我感觉良好。还记得在 “[为什么世界和你的理解不一样](http://time.geekbang.org/column/article/80755)” 中,我们提到的那个只接收好消息的花剌子模国国王的例子吗?
|
||||
|
||||
我们要做一个有价值的产品,这个“价值”,不是对产品经理有价值,而是要对用户有价值。华为总裁任正非就曾经说过,“让听得见炮声的人来做决策。”
|
||||
|
||||
我们做什么产品,本质上不是由产品经理决定的,而是由用户决定的。只有听见“炮声”,站在一线,我们才更有资格判断产品经理给出的需求是否真的是用户所需。
|
||||
|
||||
## 当产品还没有用户时
|
||||
|
||||
如果你的团队做的是一个新的产品,还没有真正的用户,那又该怎么办呢?你可以尝试一下“用户测试”的方法。
|
||||
|
||||
之前我做过一个海外客户的项目。因为项目处于启动阶段,我被派到了客户现场。刚到那边,客户就兴高采烈地告诉我,他们要做一个用户测试,让我一起参加。当时,我还有点不知所措,因为我们的项目还没有开始开发,一个什么都没有的项目就做用户测试了?是的,他们只做了几个页面,就开始测试了。
|
||||
|
||||
站在今天的角度,我前面已经给你讲过了精益创业和 MVP,你现在理解起来就会容易很多。是的,他们就是要通过最小的代价获取用户反馈。
|
||||
|
||||
他们是怎么做测试的呢?首先是一些准备工作,找几个普通用户,这些人各有特点,能够代表不同类型的人群。准备了一台摄像机,作为记录设备,拍摄用户测试的全过程。还准备了一些表格,把自己关注的问题罗列上去。
|
||||
|
||||
然后,就是具体的用户测试了。他们为用户介绍了这个测试的目的、流程等一些基本信息。然后,请用户执行几个任务。
|
||||
|
||||
在这个过程中,测试者会适时地让用户描述一下当时的感受,如果用户遇到任何问题,他们会适当介入,询问出现的问题,并提供适当的帮助。
|
||||
|
||||
最后,让用户为自己使用的这个产品进行打分,做一番评价。测试者的主要工作是观察和记录用户的反应,寻找对用户使用造成影响的部分。做用户测试的目的就是看用户会怎样用这个网站,这样的网站设计会对用户的使用有什么影响。
|
||||
|
||||
当天测试结束之后,大家一起整理了得到的用户反馈,重新讨论那些给用户体验造成一定影响的设计,然后调整一版,再来做一次用户测试。
|
||||
|
||||
对我来说,那是一个难忘的下午,我第一次这么近距离地感受用户。他们的关注点,他们的使用方式都和我曾经的假设有很多不同。后面再来设计这个系统时,我便有了更多的发言权,因为产品经理有的角度,我作为开发人员也有。
|
||||
|
||||
最后,我还想说一个程序员常见的问题:**和产品经理没有“共同语言。”**
|
||||
|
||||
因为他们说的通常是业务语言,而我们程序员的口中,基本上是计算机语言。这是两个领域的东西,很难互通。前面在讨论代码的时候,我提到要用业务的语言写代码,实际上,这种做法就是领域驱动设计中的通用语言(Ubiquitous Language)。
|
||||
|
||||
所谓通用语言,不只是我们写代码要用到,而是要让所有人说一套语言,而这个语言应该来自业务,来自大家一起构建出的领域模型。
|
||||
|
||||
这样大家在交流的时候,才可能消除歧义。所以,如果你想让项目顺利进行,先邀请产品经理一起坐下来,确定你们的通用语言。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
今天我们讨论了一个重要的话题:倾听用户声音。这是开发团队普遍欠缺的一种能力,更准确地说,是忽略的一种能力。所以,“吃自家的狗粮”这种听上去本来是理所当然的事情,才被反复强调,成为 IT 行业的经典。
|
||||
|
||||
在今天这一讲,我给你介绍了“了解用户需求”的不同做法,但其归根结底就是一句话,想办法接近用户。
|
||||
|
||||
无论是自己做用户,还是找机会接触已有用户,亦或是没有用户创造用户。只有多多听取来自真实用户的声音,我们才不致于盲目自信或是偏颇地相信产品经理。**谁离用户近,谁就有发言权,无论你的角色是什么。**
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**多走近用户。**
|
||||
|
||||
最后,我想请你思考一下,在你的实际工作中,有哪些因为走近客户而发现的问题,或者因为没有走近客户造成的困扰呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
123
极客时间专栏/geek/10x程序员工作法/沟通反馈/27 | 尽早暴露问题: 为什么被指责的总是你?.md
Normal file
123
极客时间专栏/geek/10x程序员工作法/沟通反馈/27 | 尽早暴露问题: 为什么被指责的总是你?.md
Normal file
@ -0,0 +1,123 @@
|
||||
<audio id="audio" title="27 | 尽早暴露问题: 为什么被指责的总是你?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/9d/44/9dec858196f8d679317ab0dec9b28244.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
今天我准备讨论一个经常会让很多程序员郁闷的事情,为什么你已经工作得很辛苦了,但依然会被指责。在讨论这个问题之前,我们先来讲一个小故事。
|
||||
|
||||
程序员小李这天接到了一个新的任务。系统要做性能提升,原先所有的订单都要下到数据库里,由于后来有很多订单都撤了,反复操作数据库,对真正成交过程的性能造成了影响。所以,技术负责人老赵决定把订单先放到缓存里。
|
||||
|
||||
这就会牵扯到一个技术选型的问题,于是,老赵找了几个可以用作缓存的中间件。为了给大家一个交代,老赵决定让小李给这几个中间件做一个测试,给出测试结果,让大家一起评估。小李高兴了,做这种技术任务最开心,可以玩新东西了。
|
||||
|
||||
老赵问他:“多长时间可以搞定?”
|
||||
|
||||
小李说:“一个星期吧!”
|
||||
|
||||
老赵也很爽快,“一个星期就一个星期。不过,我得提个要求,不能是纯测中间件,得带着业务跑。”
|
||||
|
||||
“没问题。”小李一口答应下来。老赵怕小李做多了,还特意嘱咐他,只测最简单的下单撤单环节就好。
|
||||
|
||||
等真的开始动手做了,小李发现,带着业务跑没那么容易,因为原来的代码耦合度太高,想把新的中间件加进去,要先把下单和撤单环节隔离开来。而这两个操作遍布在很多地方,需要先做一些调整。
|
||||
|
||||
于是,小李只好开始不分白天黑夜地干起来。随着工作的深入,小李越发觉得这个活是个无底洞,因为时间已经过半了,他的代码还没调整完。
|
||||
|
||||
这时,老赵来问他工作进展,他满面愁容地说,估计干不完了。老赵很震惊,“不就是测试几个中间件吗?”
|
||||
|
||||
小李也一脸委屈,“我们为啥要带着业务跑啊?我这几天的时间都在调整代码,以便能够把中间件的调用加进去。”
|
||||
|
||||
老赵也很疑惑,“你为啥要这么做?”
|
||||
|
||||
“你不是说要带着业务跑吗?”
|
||||
|
||||
“我是说要带着业务跑啊!但你可以自己写一个下单撤单的程序,主要过程保持一致就好了。”小李很无奈,心里暗骂,你咋不早说呢?
|
||||
|
||||
是啊!你咋不早说呢?不过,我想说不是老赵,而是小李。
|
||||
|
||||
## 谁知道有问题?
|
||||
|
||||
我们来分析一下问题出在哪。在这个故事里,小李和老赵也算有“以终为始”的思维,在一开始就确定了一个目标,做一个新中间件测试,要带着业务跑。
|
||||
|
||||
小李可以说是很清楚目标的,但在做的过程中,小李发现了问题,原有代码很复杂,改造的工作量很大,工作可能没法按时完成。
|
||||
|
||||
到此为止,所有的做法都没有错。**但接下来,发现问题的小李选择了继续埋头苦干,直到老赵来询问,无奈的小李才把问题暴露出来。**
|
||||
|
||||
在老赵看来,这并不是大事,调整一下方案就好了。但是小李心生怨气,在他看来,老赵明明有简单方案,为啥不早说,害得自己浪费了这么多时间。
|
||||
|
||||
但反过来,站在老赵的角度,他是怎么想的呢?“我的要求是带着业务跑,最理想的方案当然是和系统在一起,你要是能搞定,这肯定是最好的;既然你搞不定,退而求其次,自己写一个隔离出来的方案,我也能接受。”
|
||||
|
||||
你看出来问题在哪了吗?老赵的选择没有任何问题,问题就出在,**小李发现自己可能搞不定任务的时候,他的选择是继续闷头做,而不是把问题暴露出来,寻求帮助。**
|
||||
|
||||
作为一个程序员,克服技术难题是我们工作的一个重要组成部分,所以,一旦有困难我们会下意识地把自己投入进去。但这真的是最好的做法吗?并不是,**不是所有的问题,都是值得解决的技术难题。**
|
||||
|
||||
在工作中遇到问题,这简直是一件正常得不能再正常的事儿了,即便我们讲了各种各样的工作原则,也不可避免会在工作中遇到问题。
|
||||
|
||||
既然是你遇到的问题,你肯定是第一个知道问题发生的人,如果你不把问题暴露出来,别人想帮你也是有心无力的。
|
||||
|
||||
如果老赵不过问,结果会怎么样?必然是小李一条路跑到黑。然后,时间到了,任务没完成。
|
||||
|
||||
更关键的是,通常项目计划是一环套一环的,小李这边的失败,项目的后续部分都会受到影响,项目整体延期几乎是必然的。这种让人措手不及的情况,是很多项目负责人最害怕见到的。
|
||||
|
||||
所以,虽然单从小李的角度看,这只是个人工作习惯的事,但实际上,处于关键节点的人可能会带来项目的风险。而小李的问题被提前发现,调整的空间则会大很多。
|
||||
|
||||
**遇到问题,最好的解决方案是尽早把问题暴露出来。**其实,这个道理你并不陌生,因为你在写程序的时候,可能已经用到了。
|
||||
|
||||
## Fail Fast
|
||||
|
||||
写程序有一个重要的原则叫 [Fail Fast](http://www.martinfowler.com/ieeeSoftware/failFast.pdf),这是什么意思呢?就是如果遇到问题,尽早报错。
|
||||
|
||||
举个例子,我做了一个查询服务,可以让你根据月份查询一些信息,一年有12个月,查询参数就是从1到12。
|
||||
|
||||
问题来了,参数校验应该在哪做呢?如果什么都不做,这个查询参数就会穿透系统,传到你的数据库上。
|
||||
|
||||
如果传入的参数是合法的,当然没有任何问题,这个查询会返回一个正常的结果。但如果这个参数是无意义的,比如,传一个“13”,那这个查询依然会传到数据库上。
|
||||
|
||||
事实上,很多不经心的系统就是这么做的,一旦系统出了什么状况,你很难判断问题的根源。
|
||||
|
||||
在这个极度简化的例子里,你可以一眼看出问题出在输入参数上,一旦系统稍具规模,请求来自不同的地方,这些请求最终都汇集到数据库上,识别来源的难度就会大幅度增加。尤其是系统并发起来,很难从日志中找出这个请求的来源。
|
||||
|
||||
你可能会说,“为了方便服务对不同数据来源进行识别,可以给每个请求加上一个唯一的请求ID吧?”
|
||||
|
||||
看,系统就是这么变复杂的,我经常调侃这种解决方案,就是没有困难创造困难也要上。当然,即便以后真的加上请求ID,理由也不是现在这个。
|
||||
|
||||
其实,要解决这个问题,做法很简单。稍微有经验的人都知道,参数校验应该放在入口的位置上,不合法的请求就不让它往后走了。这种把可能预见的失败拦在外面的做法就是 Fail Fast,有问题不可怕,让失败尽早到来。
|
||||
|
||||
上面这个例子很简单,我再给你举一个例子。如果配置文件缺少了一个重要参数,比如,缺少了数据库最大连接数,你打算怎么处理?很多人会选择给一个缺省值,这就不是 Fail Fast 的做法。既然是重要参数,少了就报错,这才叫 Fail Fast。
|
||||
|
||||
其实,Fail Fast 也有一些反直觉的味道,很多人以构建健壮系统为由,兼容了很多奇怪的问题,而不是把它暴露出来。反而会把系统中的 Bug 隐藏起来。
|
||||
|
||||
我们都知道,靠 debug 来定位问题是最为费时费力的一种做法。所以,别怕系统有问题,有问题就早点报出来。
|
||||
|
||||
顺便说一下,在前面这个例子里,透传参数还有几个额外的问题。一是会给数据库带来额外的压力,如果有人用无意义查询作为一种攻击手段,它会压垮你的数据库。再有一点,也是安全问题,一些SQL攻击,利用的就是这种无脑透传。
|
||||
|
||||
## 克服心理障碍
|
||||
|
||||
对我们来说,在程序中尽早暴露问题是很容易接受的。但在工作中暴露自己的问题,却是很大的挑战,因为这里还面临着一个心理问题:会不会让别人觉得自己不行。
|
||||
|
||||
说实话,这种担心是多余的。因为每个人的能力是强是弱,大家看得清清楚楚。只有你能把问题解决了大家才会高看你,而把问题遮盖住,并不能改善你在别人心目中的形象。
|
||||
|
||||
既然是问题,藏是藏不住的,就像最开始那个故事里的小李,即便他试图隐藏问题,但最后他还是不可能完成的,问题还是会出来,到那时,别人对他的评价,只会更加糟糕。
|
||||
|
||||
比起尽早暴露问题,还有更进一步的工作方式,那就是把自己的工作透明化,让别人尽可能多地了解自己的工作进展,了解自己的想法。
|
||||
|
||||
如果能做到这一点,其他人在遇到与你工作相关的事情,都会给你提供信息,帮助你把工作做得更好。当然,这种做法对人的心理挑战,比尽早暴露问题更大。
|
||||
|
||||
从专栏开始到现在,我们讲了这么多原则和实践,其实,大多数都是在告诉你,有事先做。
|
||||
|
||||
一方面,这是从软件变更成本的角度在考虑;另一方面,也是在从与人打交道的角度在考虑。
|
||||
|
||||
越往前做,给人留下的空间和余地越大,调整的机会也就越充足。而在最后一刻出现问题的成本实在太高,大到让人无法负担。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
我们今天讨论了一个重要的工作原则,把事情往前做,尽早暴露问题。我们前面讲的很多内容说的都是这个原则,比如,要先确定结果,要在事前做推演等等。越早发现问题,解决的成本就越低,不仅仅是解决问题本身的成本,更多的是对团队整体计划的影响。
|
||||
|
||||
一方面,事前我们要通过“以终为始”和“任务分解”早点发现问题;另一方面,在做事过程中,一旦在有限时间内搞不定,尽早让其他人知道。
|
||||
|
||||
这个原则在写程序中的体现就是 Fail Fast,很多程序员因为没有坚持这个原则,不断妥协,造成了程序越来越复杂,团队就陷入了无尽的泥潭。
|
||||
|
||||
原则很简单,真正的挑战在于克服自己的心理障碍。很多人都会下意识地隐瞒问题,但请相信你的队友,大家都是聪明人,问题是藏不住的。
|
||||
|
||||
如果今天的内容你只记住一件事,那请记住:**事情往前做,有问题尽早暴露。**
|
||||
|
||||
最后,我想请你回想一下,如果遵循了这样的工作原则,你之前犯过的哪些错误是可以规避掉的呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
127
极客时间专栏/geek/10x程序员工作法/沟通反馈/28 | 结构化:写文档也是一种学习方式.md
Normal file
127
极客时间专栏/geek/10x程序员工作法/沟通反馈/28 | 结构化:写文档也是一种学习方式.md
Normal file
@ -0,0 +1,127 @@
|
||||
<audio id="audio" title="28 | 结构化:写文档也是一种学习方式" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/09/a9/097e20cd5a7d5c1a7b70025b6c03d3a9.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
你写文档吗?我知道,你可能并不喜欢写文档,因为在你眼中,写文档是繁琐的,是旧时代软件工程的产物。
|
||||
|
||||
最开始我对写文档的印象也不好。
|
||||
|
||||
我的职业生涯是从一个通过了 CMM 5级认证的大企业开始的。可能今天很多程序员已经对 CMM 感到陌生了,它是能力成熟度模型(Capability Maturity Model for Software)的缩写,用来评估一个组织的软件开发能力,曾在国内风靡一时,许多软件公司都以拥有 CMM 认证为努力方向。
|
||||
|
||||
在这个极其重视过程的企业里,文档是非常重要的一环。但我看到的真实场景却是,一个软件已经上线运行了,大家才开始为了应付过程纷纷补写文档。
|
||||
|
||||
每个部门都有专门的过程负责人,要求你严格按照格式写文档,保证字体字号的正确性。然后,用 A4纸将文档打印出,封印在一个仓库里,再也无人问津。
|
||||
|
||||
然而,文档却是非常重要的。后来,我到过很多公司,凡是我能够比较快上手的,通常都是有比较详尽的文档,而那些文档缺失的公司,想要把信息梳理清楚,往往会花很长时间。
|
||||
|
||||
另外,我学习很多软件开发的相关知识,通常也是依赖各种各样的文档。对我们程序员这个走在时代前列的群体来说,大量阅读文档就是我们日常工作的一部分。
|
||||
|
||||
你发现矛盾了吗?一方面,我们讨厌写文档,另一方面,文档却对我们的工作学习有着不可忽视的作用。
|
||||
|
||||
我们竟然如此依赖于一个我们讨厌的东西。问题出在哪呢?
|
||||
|
||||
## 你为什么不喜欢写文档?
|
||||
|
||||
很多人会说,自己不愿意写那些无聊的流程文档,文档无聊,这固然是一个原因。不过,如今很多公司已经在这方面做得相当轻量级了,基本上只要求写必要的文档。那为什么依然有很多人不愿意写文档呢?
|
||||
|
||||
其实,**很多人回避写文档的真正原因是,他掌握的内容不能很好地结构化。**
|
||||
|
||||
在两种场景下,我们扮演的角色是不同的。写文档时,角色是作者;而读文档时,角色是读者。
|
||||
|
||||
作为读者,我们读文档,实际上就是按照作者梳理的结构在走,因为呈现出来的内容,多数是已经结构化的,读起来自然会比较顺畅;而作为作者,没有人告诉你结构应该是什么样,我们必须创造出一个结构来,而这正是很多人不擅长的。
|
||||
|
||||
想要成为一个好程序员,有一个良好的知识结构是极其重要的。
|
||||
|
||||
很多人抱怨程序员行业难,原因就在于,新技术层出不穷。是的,当你的知识都是零散的,任何新技术的出现,都是新东西。而当你建立起自己的知识结构,任何新东西都只是在原有知识上的增量叠加。
|
||||
|
||||
举个例子,今天炒得沸沸扬扬的微服务,小粒度的理念脱胎于 Unix 哲学中的“只做一件事,把它做好”,而服务化的理念则是当年SOA(Service-Oriented Architecture)的产物。理解了这些背后的动机,微服务就只剩下工具层面的问题。
|
||||
|
||||
有了这样的知识结构,当我要构建应用时,只是需要把工具适配进去,到时我再来学习相应的知识,这是非常有针对性的,学习的效率也会得到大幅度提高。
|
||||
|
||||
**将零散的知识结构化,有很多种方式,但输出是非常关键的一环。**
|
||||
|
||||
## 知识输出
|
||||
|
||||
不知道你小时候是不是有过给同学讲题的经历,有时候,明明你已经将知识学得很好,但给同学讲解起来时,却总是讲不明白。因为你的同学总能从你想都没想过的角度问问题,这些角度和老师教的不一样。
|
||||
|
||||
**输出的过程,本质上就是把知识连接起来的过程。**自己以为自己懂的东西,当你真的需要把它按照一个完整的逻辑呈现出来时,那些缺失的细节就会冒出来,而补齐这些细节,一张知识地图就逐渐成型了。
|
||||
|
||||
这个模块的主题是“沟通反馈”,将知识对外输出就是一种获得反馈的方式。很多人自以为对知识的理解已经很深入了,但给别人一讲,却发现自己怎么也讲不清楚,这就说明他理解的程度,远未到达他以为的高度。
|
||||
|
||||
输出的方式有很多,对于程序员来说,最常接触到的两种应该是写作与演讲。
|
||||
|
||||
你读到很多书、很多技术文章,这都是别人通过写作的方式进行输出的结果。而很多技术大会上,常常会有各路高手在台上分享自己的所得,这就是演讲的输出方式。
|
||||
|
||||
软件行业的很多大师级程序员都是对外输出的高手。比如,开源概念的提出者 Eric Raymond,他的《大教堂与集市》推开了开源大门;前面多次提及的Kent Beck,他写了《极限编程解析》、《测试驱动开发》、《实现模式》几本书;
|
||||
|
||||
而 Martin Fowler,几乎是对外输出的典范,他重新整理了很多似是而非的概念,让人们的讨论有了更标准的词汇,比如,重构、依赖注入(Dependency Injection)等等。
|
||||
|
||||
再往前,就要提到《计算机程序设计艺术》的作者高德纳,他系统地整理了算法的概念,为了好好写作,他甚至创造了一个排版软件 TeX。
|
||||
|
||||
也许你会说,说得很有道理,但我真的不擅长啊!这是因为你没有掌握基本的方法。
|
||||
|
||||
## 金字塔原理
|
||||
|
||||
首先,需要明确一点,我们的第一目标不是成为作家或演讲家,而只是要求把事情说清楚,把自己的知识清晰地呈现出来。那我们最好先来了解一下金字塔原理。看看下面这张图,你就知道它是怎么回事了:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d9/eb/d9552d4414fd2884378752a955a490eb.jpg" alt="">
|
||||
|
||||
首先,我们要确定想要表达的是什么,也就是找到中心论点,然后,再确定支撑这个论点的分论点,再来就是找到支撑每个分论点的论据。
|
||||
|
||||
从中心论点、分论点至论据,这样一层层向下展开,从结构上看,就像金字塔一样,所以,这个方法称之为**金字塔原理。**
|
||||
|
||||
以我们的专栏为例,我们的中心论点就是“高效工作是有方法可循的”,那支撑起这个中心论点的分论点就是我们的四个原则,针对每个原则,我们给出了各种实践和思想,这是我们的论据。
|
||||
|
||||
前面我说过了,一个人不擅长输出,更多的是因为缺乏知识的结构化,现在通过这样一种方式,就可以帮助自己,将某个知识结构化起来,有了结构,剩下的就是怎么输出了。
|
||||
|
||||
具体怎么输出就可以根据自己的喜好进行选择:要么自上而下的进行表达,也就是先说中心论点,然后说分论点1,用论据证明分论点1,再说分论点2,用论据证明分论点2,以此类推。
|
||||
|
||||
或者是自下而上来表达,先用证据得出分论点1,然后再得出分论点2,最后再归纳总结出中心论点。
|
||||
|
||||
听上去很简单,但不要以为懂得了金字塔原理,天下就尽在掌握了,你还需要更多的练习。
|
||||
|
||||
## 无他,唯手熟尔
|
||||
|
||||
我自己也曾经很不擅长写作和公开演讲,但是,这些东西都禁不住你大量的练习。我的对外输出,是从我刚开始工作不久开始的。那时候,市面上流行写 blog,我抱着好奇的心态开始了自己的 blog 之旅。
|
||||
|
||||
刚开始写 blog 的时候,我会把写好的东西分享给周边的朋友,他们会给我提出一些反馈,有赞许、有调侃、也有针对一些细节的讨论,这会让我觉得自己写的东西是有人看的,我也就有了坚持的原动力。
|
||||
|
||||
我也很羡慕那些很会写的人,于是,也经常会模仿他人的手法不断地改进自己的写作技巧。慢慢地,我的读者就从身边的人逐渐扩展开来,我也就有了更多的反馈。
|
||||
|
||||
正是这些反馈,让我对很多东西有了全新的认识,也就有了更强的分享动力,一个正向循环逐渐建立起来。到后来,写东西就成了我的习惯,坚持至今。
|
||||
|
||||
经过 blog 写作的锻炼,我写的东西有了自己的章法和套路,也就有了越来越多机会去在不同的地方写东西:给杂志写稿子,在网站上写东西,包括今天这个专栏,都起源于最初的 blog 写作。
|
||||
|
||||
除此之外,随着时间的累积,我收获的不仅仅是一些读者的赞许,还得到了更多的机会,比如,我人生中的第一次公开演讲,机会就来自于我 blog 的一个读者的邀请。
|
||||
|
||||
后来的一些职业机会,也是通过我写 blog 认识的朋友。考虑到我当时人在 IT 边缘的东北,能有后来的职业发展,很大程度都是常年坚持对外输出的结果。
|
||||
|
||||
同样,演讲能力也需要大量的练习。1977年《Book of List》杂志曾经有[一个关于“最恐惧事物”的调查](http://joyfulpublicspeaking.blogspot.com/2009/10/14-worst-human-fears-according-to-1977.html),结果显示,公开演讲名列第一,超过了死亡。所以,你害怕公开演讲是很正常的。
|
||||
|
||||
我至今依然记得我第一次公开演讲时手抖的样子,今天想想还是挺傻的。我第一次在几百人的大会上做演讲,居然有一段时间,只顾着看大屏,背对着听众,也是很糗的一段经历。
|
||||
|
||||
我一直很羡慕那些在台上侃侃而谈的人,比如,乔布斯。直到我读了[《乔布斯的魔力演讲》](http://book.douban.com/subject/4860526/),我才知道,即便强如乔布斯,他的演讲也是经过大量练习的。
|
||||
|
||||
我自己公开演讲看上去正常一些,是我在经过一个咨询项目的大量练习之后。那时候,几乎每天要给客户讲东西,害得我只能不停地准备、不停地讲。所以,本质上,对演讲的惧怕只是因为练习不足。
|
||||
|
||||
好了,你现在已经了解获取这些技能的真谛了,**无他,唯手熟尔!**
|
||||
|
||||
## 总结时刻
|
||||
|
||||
程序员对文档有着一种矛盾的情感,一方面,需要依赖于文档获得知识,另一方面,很少有人愿意写文档。
|
||||
|
||||
文档在程序员心目中“形象不佳”,主要是传统的流程写了太多无用的文档。但对更多人来说,不愿意写文档,本质上是因为知识不能很好地结构化。
|
||||
|
||||
有结构的知识会让新知识的学习变得更加容易,今天很多人抱怨新知识层出不穷,就是因为知识过于零散,当知识有结构之后,学习新知识就只是在学习增量,效率自然就会大幅度提升。
|
||||
|
||||
输出是一种很好的方式,帮助你把知识连接起来,写作和做公开演讲都是很好的输出方式。
|
||||
|
||||
阻碍很多人进行知识输出的一个重要原因是缺乏输出的模型,金字塔原理就给出一个从中心论点到分论点,再到论据的模型,帮助我们将知识梳理出来。
|
||||
|
||||
而想要做好知识输出,还需要不断地进行练习,写作和做公开演讲都是可以通过练习提高的。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**多输出,让知识更有结构。**
|
||||
|
||||
最后,我想请你分享一下,你的工作中,有哪些机会将自己的知识输出呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
195
极客时间专栏/geek/10x程序员工作法/沟通反馈/划重点 | 一次关于“沟通反馈”主题内容的复盘.md
Normal file
195
极客时间专栏/geek/10x程序员工作法/沟通反馈/划重点 | 一次关于“沟通反馈”主题内容的复盘.md
Normal file
@ -0,0 +1,195 @@
|
||||
<audio id="audio" title="划重点 | 一次关于“沟通反馈”主题内容的复盘" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f2/5e/f244ec98e14d66902a986a684bdfad5e.mp3"></audio>
|
||||
|
||||
你好,我是郑晔,恭喜你,又完成了一个模块的学习。
|
||||
|
||||
在“沟通反馈”这个模块中,我与你探讨了与人打交道的一些方法,只不过,这并非是传统意义上的谈话技巧。而是希望你能克服自己的心理障碍,主动与真实世界进行沟通,获取反馈,让自己对信息的编解码能力不断得到提升。
|
||||
|
||||
## 重点复习
|
||||
|
||||
在这个模块中,我们学习到了一些最佳实践。
|
||||
|
||||
<li>
|
||||
**看板**
|
||||
<ul>
|
||||
- 一种来自精益生产的可视化实践。
|
||||
- 按阶段将任务放置其中。
|
||||
- 可以帮助我们发现问题。
|
||||
|
||||
**持续集成**
|
||||
|
||||
- 做好持续集成的关键是,快速反馈。
|
||||
- 本地检查通过之后再提交。
|
||||
- 找到有效的反馈方式,比如:CI 监视器。
|
||||
<li>持续集成的纪律。
|
||||
<ul>
|
||||
- 只有 CI 服务器处于绿色的状态才能提交代码。
|
||||
- CI 服务器一旦检查出错,要立即修复。
|
||||
|
||||
**回顾会议**
|
||||
|
||||
- 软件团队复盘的一种实践。
|
||||
- 枚举关注点,选出重点,深入讨论,列出行动项,找到负责人。
|
||||
|
||||
**5个为什么**
|
||||
|
||||
- 又一个来自丰田的实践。
|
||||
- 沿着一条主线追问多个问题。
|
||||
|
||||
在这个模块中,我们还了解一些重要的思路,让我们把工作做得更好。
|
||||
|
||||
<li>
|
||||
**用信息论理解沟通反馈**
|
||||
</li>
|
||||
<li>
|
||||
**写代码的进阶路径**
|
||||
<ul>
|
||||
- 编写可以运行的代码。
|
||||
- 编写符合代码规范的代码。
|
||||
- 编写人可以理解的代码。
|
||||
- 用业务语言写代码。
|
||||
|
||||
**会议是一种重量级的沟通方式**
|
||||
|
||||
- 减少参会人数。
|
||||
- 找人面对面沟通。
|
||||
|
||||
**聆听用户声音**
|
||||
|
||||
- 能做自己用户,做自己的用户。
|
||||
- 能接近用户,接近用户。
|
||||
- 没有用户,创造用户。
|
||||
|
||||
**Fail Fast**
|
||||
|
||||
- 一种编写代码的原则。
|
||||
- 出现问题尽早报错。
|
||||
|
||||
**金字塔原理**
|
||||
|
||||
- 从中心论点,到分论点,再到论据。
|
||||
|
||||
## 实战指南
|
||||
|
||||
在“沟通反馈”的模块,我也将每篇内容浓缩为一句实战指南,现在一起回顾一下。
|
||||
|
||||
<li>
|
||||
<p>**通过沟通反馈,不断升级自己的编解码能力。**<br>
|
||||
——《[20 | 为什么世界和你的理解不一样](http://time.geekbang.org/column/article/80755)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**用业务的语言写代码。**<br>
|
||||
——《[21 | 你的代码为谁而写?](http://time.geekbang.org/column/article/82581)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**多面对面沟通,少开会。**<br>
|
||||
——《[22 | 轻量级沟通:你总是在开会吗?](http://time.geekbang.org/column/article/82844)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**多尝试用可视化的方式进行沟通。**<br>
|
||||
——《[23 | 可视化:一种更为直观的沟通方式](http://time.geekbang.org/column/article/83082)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**做好持续集成的关键在于,快速反馈。**<br>
|
||||
——《[24 | 快速反馈:为什么你们公司总是做不好持续集成?](http://time.geekbang.org/column/article/83461)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**定期复盘,找准问题根因,不断改善。**<br>
|
||||
——《[25 | 开发中的问题一再出现,应该怎么办?](http://time.geekbang.org/column/article/83841)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**多走近用户。**<br>
|
||||
——《[26 | 作为程序员,你也应该聆听用户声音](http://time.geekbang.org/column/article/84185)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**事情往前做,有问题尽早暴露。**<br>
|
||||
——《[27 | 尽早暴露问题: 为什么被指责的总是你?](http://time.geekbang.org/column/article/84374)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**多输出,让知识更有结构。**<br>
|
||||
——《[28 | 结构化:写文档也是一种学习方式](http://time.geekbang.org/column/article/84663)》</p>
|
||||
</li>
|
||||
|
||||
## 额外收获
|
||||
|
||||
在这个模块的最后,针对大家在学习过程中的一些问题,我也进行了回答,帮你梳理出一个思路,更好地理解学到的内容:
|
||||
|
||||
<li>
|
||||
**持续集成是一条主线,可以将诸多实践贯穿起来。**
|
||||
<ul>
|
||||
- 从持续集成到稳定的开发分支,到频繁提交,足够小的任务,到任务分解。
|
||||
- 从持续集成到可检查,到测试防护网,到测试覆盖率,到单元测试,到可测试代码,到软件设计。
|
||||
|
||||
**安全性检查,是回顾会议的前提条件。**
|
||||
|
||||
**在信息获取上,国内外程序员差别不大,开拓视野,改善工作习惯,是国内程序员亟需提高的。**
|
||||
|
||||
——《[答疑解惑 | 持续集成,一条贯穿诸多实践的主线](http://time.geekbang.org/column/article/85049)》
|
||||
|
||||
## 留言精选
|
||||
|
||||
在讲到定期复盘,找准问题根因时,西西弗与卡夫卡 同学提到:
|
||||
|
||||
>
|
||||
关于复盘,孙陶然曾经说过,如果他有所成就,一半要归功于复盘。他提出了几个步骤供大家参考。首先,先对比实际结果和起初所定目标之间有什么差距。其次,情景再现,回顾项目的几个阶段。然后,对每个阶段进行得失分析,找出问题原因。最后,总结规律,化作自己的技能沉淀,再次遇到时可以规避。
|
||||
|
||||
|
||||
>
|
||||
我再补充一点,复盘资料应该记录到知识库,无论新来的或是接手的人,都能从中获益,从而提升组织的能力。另外,好的复盘需要有坦诚的文化氛围,不然有可能变成互相指责甩锅,就失去了意义。
|
||||
|
||||
|
||||
另外,西西弗与卡夫卡 同学还分享了提升开会效率的方法:
|
||||
|
||||
>
|
||||
其他一些提升开会效率的方法,比如会前每个人要先做准备,把观点写下来,然后发给主持人。再比如六顶思考帽,大家按相近的思考角度讨论,而不是我说一趴,你说另一趴。还有,主持人控制这轮谁能发言,控制每个人的时长。方法很多,但实际上总有人破坏规则,特别是当这个人是老板…
|
||||
|
||||
|
||||
在用信息论来讨论沟通反馈问题时,毅 同学将知识点融会贯通,提出了自己的心得:
|
||||
|
||||
>
|
||||
不同角色间的沟通:克服上下文差异,分段解码,理解偏差早发现早反馈。相同角色间的沟通,信号相同,解码能力因人而异,要有一个主导的人,控制沟通广度与深度,抓主线适可而止,此时结合任务分解,反向沙盘推演。
|
||||
|
||||
|
||||
关于如何做好复盘,like_jun 同学提到:
|
||||
|
||||
>
|
||||
<p>要让团队认识到复盘的重要性。<br>
|
||||
让每个人都深入思考项目运作过程中遇到了哪些问题。才能做好复盘。</p>
|
||||
|
||||
|
||||
在讲到通过金字塔原理进行知识输出时,Y024 同学丰富了金字塔原理的基本原则,具体如下:
|
||||
|
||||
>
|
||||
金字塔原理的四个基本原则:“结论先行”(一次表达只支持一个思想,且出现在开头)、“以上统下”(任一层次上的思想都必须是其下一层思想的总结概括)、“归类分组”(每组中的思想都必须属于同一范畴)和“逻辑递进”(每组中的思想都必须按照逻辑顺序排列)。
|
||||
|
||||
|
||||
>
|
||||
前面两个特点是纵向结构之间的特点,后面两个特点则是横向结构之间的特点。以上内容收集整理自李忠秋老师的《结构思考力》,感兴趣的小伙伴可以看看。
|
||||
|
||||
|
||||
另外,对于会议,Y024 同学也提出了他团队正在进行的摸索和尝试:
|
||||
|
||||
>
|
||||
1.沟通的指导原则之一就是在同步沟通的时候(比如开会),人越少越好。而在异步沟通的时候(比如E-mail),涉及的听众越多越好。
|
||||
|
||||
|
||||
>
|
||||
<p>2.关于开会分享下我们正在摸索的。<br>
|
||||
(a)每个会开始前,会议发起人在石墨文档上以“会议记录”模版(我们持续形成自己的模版)新建一个纪要:说明议程、及讨论内容等前提内容并提前告知与会人员。会议过程中在同一个石墨文档上做纪要,保证纪要可以收集全所有的笔记和行动计划。如果是关联会议,则使用上次相关的石墨文档进行追加内容(保持事件连贯性、完整性)。<br>
|
||||
(b)半小时的会议设置为 25 分钟,一小时的会议设置成 50 分钟,留有冗余量应付需要换地方等临时情况,保证所有的会议不会有成员迟到的现象。</p>
|
||||
|
||||
|
||||
对于领域驱动设计,小浩子 同学提到了要特别关注可变项和不变项的分离:
|
||||
|
||||
>
|
||||
领域驱动设计确实是写出合适的代码结构的一项训练,程序员会不由自主地按照自己的习惯,也就是按照计算机运行逻辑去设计代码,这样的代码很容易陷入难以维护的坑。在开始动手写代码之前跟用户交流清楚,理解设计的概念、流程、使用场景、特殊情况,这些都很重要。另外我特别关注的一点是可变项和不变项的分离,因为我们的业务场景对可扩展性要求很高。
|
||||
|
||||
|
||||
经验越丰富的程序员,越能体会到“走进客户”的重要性,关于这一点,David Mao 同学提到:
|
||||
|
||||
>
|
||||
我做了好多年的软件测试,前几年和销售一起去谈客户,才深深地体会到客户声音的重要性。客户关注的才是真需求,产品经理和开发想出来的很多是伪需求,很多不是客户想要的功能。
|
||||
|
||||
|
||||
**感谢同学们的精彩留言。在下一个模块中,我将为你分享“自动化”这个原则的具体应用。**
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
136
极客时间专栏/geek/10x程序员工作法/沟通反馈/用户故事 | 站在前人的肩膀上,领取属于你的高效工作秘籍.md
Normal file
136
极客时间专栏/geek/10x程序员工作法/沟通反馈/用户故事 | 站在前人的肩膀上,领取属于你的高效工作秘籍.md
Normal file
@ -0,0 +1,136 @@
|
||||
|
||||
丁雪丰<br>
|
||||
极客时间《玩转Spring全家桶》专栏作者,平安壹钱包高级架构师
|
||||
|
||||
**1.为什么订阅《10x程序员工作法》?**
|
||||
|
||||
我与郑晔老师是多年好友,不过大家平时联系并不多,去年在一次聚会上得知他要在极客时间上开专栏,当时并没有去了解专栏的内容,直到专栏上线后我才知道,这门课就是《10x程序员工作法》。
|
||||
|
||||
从专栏名称上就能大概得知,这门课程的目的主要是为了提升大家的工作效率,开篇词中有一句大实话:
|
||||
|
||||
>
|
||||
大部分程序员忙碌解决的问题,都不是程序问题,而是由偶然复杂度导致的问题。
|
||||
|
||||
|
||||
正所谓,前人的经验,我们的阶梯,我想看看作为一个超级资深的从业者,郑晔老师是怎么看待这个问题的,他又是怎么总结自己的心得的。要知道他曾经是Thoughtworks的首席咨询师,他帮这么多公司做过各种项目,一定是有秘籍的。所以,我毫不犹豫地在第一时间订阅了这个专栏。
|
||||
|
||||
**2.对于高效工作有哪些心得?**
|
||||
|
||||
郑晔老师在专栏每一课的最后,都会用一句话来总结这一课的内容,我很喜欢这个风格,那我也先用一句话来说一下,我对高效工作的理解:能用尽可能小的时间颗粒度来安排自己的工作。
|
||||
|
||||
高效工作,如果按字面理解,就是在指定时间里能完成更多的工作(当然还得是合格的工作)。《五分钟商学院》的作者刘润老师曾提到过时间颗粒度的概念,你会发现,很多成功人士划分时间的颗粒度都比较小。王健林的时间颗粒度是十五分钟,比尔盖茨的时间颗粒度是五分钟。
|
||||
|
||||
你可以思考一下,你是按什么颗粒度来安排自己工作的呢?1天,半天,1小时还是15分钟?
|
||||
|
||||
另外,在把时间拆细的同时,也不能忽略对工作内容的分解。其实,对工作内容进行分解的颗粒度大小,同样对工作效率有很大影响。
|
||||
|
||||
讲到这里,又不得不提到《10x程序员工作法》专栏,我看到郑晔老师在“任务分解”这个模块里,详细地讲解了怎样将工作任务进行拆解,如果对这部分内容感兴趣的同学,也可以去订阅专栏。
|
||||
|
||||
**3.学习《10x程序员工作法》专栏的感受**
|
||||
|
||||
我认为这个专栏最大的价值,就是为我们程序员的工作,划出了清晰的、可以遵循的高效工作原则。
|
||||
|
||||
我举个具体的例子吧,我团队中不止有一个同学是技术能力非常突出的,他们在拿到一个任务后,立马就能想到大概的解决方案,紧接着就开始写代码了……没错,撸袖子就开始写代码了。
|
||||
|
||||
结果就是,他们之后要开始漫长地“填坑”之旅。运气好的话,是可以在上线前把“坑”填完,上线后没什么事;运气不好的话,得要好几个版本才能把“坑”填完。
|
||||
|
||||
作为过来人,我在看到他们这么快就开始写代码后,就会把他们拦下来,跟他们聊聊各种需要考虑的事项,他们之前忽略掉的问题,或者是需要细化的点出来。
|
||||
|
||||
在我看了专栏中的“以终为始”这个模块内容之后,我发现这不正是我一直在寻找的东西吗?我平时零散地问各种问题,让他们考虑各种点,现在郑晔老师都已经把这些常见的实践,提炼总结成为高效工作的原则。
|
||||
|
||||
我们可以不用在针对工作中的各种问题孤立地作出反应,而是有了一套行之有效的原则可以遵循。
|
||||
|
||||
如果说我们平时实践的是“术”,那郑晔老师讲的就是“道”。我觉得只“以终为始”这一部分的内容就已经完全值回票价了。
|
||||
|
||||
Ivan(毅)<br>
|
||||
10+年研发经验,坐标苏州,目前在医疗信息化软件公司从事研发兼管理工作。
|
||||
|
||||
**1.为什么订阅《10x程序员工作法》?**
|
||||
|
||||
记得刚开始工作时,一门心思想着多做项目,多学技能,多攒经验,当然,战斗数值也增长不少,但就像武侠小说中的习武之人都会碰到的困惑,总觉得是围绕着招式在打转,而修为不足。
|
||||
|
||||
之后我也逐渐也有意识培养自己的思维方式和从多角度看问题的习惯,这些年也算形成了一些自己的价值观和方法论。
|
||||
|
||||
我现在服务的公司是一家初创公司,产品化和定制项目开发之路走的并不轻松,我发现不是团队效率低下,也不是不知道如何解决各种问题,而是我们投入很多精力在处理“偶然复杂度”的问题,我想这可能也是工作不轻松的原因之一吧。
|
||||
|
||||
所以在听郑晔老师的开篇词时,我就有一种被吸引的感觉,因为我觉得问题既然抛出了,后续肯定会有解决方案。
|
||||
|
||||
当然,每个人都会碰到自己的问题,所以要想解决问题需要有系统的思考框架,而大多数问题又存在共性,应该会有一些普适原则,这些在开篇词中都有提及,听着听着继续探究的兴趣也越来越浓了。
|
||||
|
||||
我是个偏向实用主义的人,除了“醍醐灌顶”的那一时快感,更看重方法在实践中的运用和反思,我相信《10x程序员工作法》中介绍的经验和最佳实践一定能给我和团队带来工作效率和工作绩效的提升!
|
||||
|
||||
为了保持学习思考的连贯,我都是紧跟一周三次的更新节奏同步收听,即使春节期间也未间断,而且部分内容会反复听,每次重听都有新的感受。
|
||||
|
||||
而从第二部分开始,我觉得不光是收听,还要用文字表达出来,毕竟有互动有交流的学习才是有效的学习,而且我是强制自己一定要留言,有思考的留言。
|
||||
|
||||
**2. 学习《10x程序员工作法》的心得**
|
||||
|
||||
基于之前的学习过程,我再总结下我的学习心得。
|
||||
|
||||
**a.思维方式的改变**
|
||||
|
||||
课程虽然针对程序员,但受众却超过程序员范围。即使对于程序员来说,在工作中也要拓展自己的上下文,将自己放在更大的范围、平台上去思考问题,主动发掘问题关键点,在面对不同职能人员间沟通时,多运用“以终为始”模块中的知识尝试解决分歧,达成共识。
|
||||
|
||||
此时我或许明白了之前公司老板常说的一句话“你们都是工程师思维”,或许并不是将自己想象成其他角色,而是在更大的上下文背景中寻找突破点。
|
||||
|
||||
**b.工作习惯的培养**
|
||||
|
||||
程序员的工作习惯不仅仅是每天上班打开邮箱,提交代码前编译通过等等事务性习惯,还包括:**你的思维方式是否合适,由此产生的执行方式是否到位,检验方式是否科学,理论修正是否能产生更好的思维方式。**
|
||||
|
||||
就像专栏中讲精益创业时,提到的“想法->产品->数据”对应的“开发->测量->认知”这样的反馈模型。
|
||||
|
||||
**c.可以做的更好**
|
||||
|
||||
我们可能习惯于之前的工作方式,有时即便感觉别扭,但仍然只是希望在原有方式上修修补补,而不愿去仔细区分现实和期望,常常会以忙为理由,为自己辩解。
|
||||
|
||||
**但如果不忙,你知道该怎么做吗?**这恐怕是绝大多数程序员都要自省的一个问题,但这也恰恰说明我们在工作方式上是有很大的提升空间。
|
||||
|
||||
**d.主动思考也很重要**
|
||||
|
||||
孔子说“学而不思则罔,思而不学则殆”,《10x程序员工作法》并不是技术技能课,可以直接准备环境,代码走起,而是一种方法论课、答疑解惑课、实践验证课,听起来很过瘾,很能戳中痛点,但如何解决自己的实际问题则需要主动思考,主动交流。
|
||||
|
||||
这也是我后来逼着自己每学完一课都要思考后留言的初衷,并且我还会向周围人推荐这种学习方式。
|
||||
|
||||
其实,对于任何方面的知识,我都奉行“一学二懂三要用”,四要知道局限性的准则,不断更新自己的认知体系,力求做到融会贯通,逐步形成适合自身的知识架构和行动准则。
|
||||
|
||||
One day<br>
|
||||
4年研发工作经验,坐标上海,目前就职于一家汽车行业的公司做后端开发
|
||||
|
||||
**1.为什么订阅《10x程序员工作法》?**
|
||||
|
||||
想起刚毕业的时候,我还是能够前后端通吃的,因为那个时候,很多公司使用的还是jsp+servlet等简单的单体应用,所有功能全部集成在一个工程项目中。
|
||||
|
||||
等所有人把各自模块开发完,就丢一个war包到服务器,然后就进行测试就完事了。等测试小哥哥或小姐姐发现Bug的时候,自己就从前端到后端一步步的debug,虽然有很多血与泪的历史,但是累并快乐着。
|
||||
|
||||
但工作久了之后,我发现很多工作是重复且无意义的,然后就是加班与熬夜并存。现在,我已经在软件开发行业已经混迹4年多,从一个混沌不知的菜鸟也渐渐有了自己学习方法。
|
||||
|
||||
但是个人经历总是有限,还是会遇到很多问题。工作久了也越发觉得时间的宝贵,也越来越迫切地想要追求更为高效的工作方法。
|
||||
|
||||
最开始看到专栏标题的时候,还心有疑惑,极客时间会有标题党?我学了这个专栏工作学习能提高10倍吗?
|
||||
|
||||
但当看到10X程序员工作法的内容,我就已经被郑晔老师的能力所折服,因为专栏目录中提到的各种思考原则,真的是很贴合实际。
|
||||
|
||||
工作能力的提升,本身是经验的积累,但是如果长时间堆砌同类型的业务知识,对于大脑来说是一种重复,也不利于个人经验的沉淀。实践和理论总是相辅相成的,我是一个实践派,但是对于理论的方法,我也非常赞赏。
|
||||
|
||||
这相当于是站在前人的肩膀上,你在实践的过程中,得到与之类似或者不同的结果。以结果为导向,再结合理论就有不同的火花,我认为这是一件很神奇的事情。
|
||||
|
||||
**2. 学习《10x程序员工作法》专栏的心得**
|
||||
|
||||
**a. 多思、多问**<br>
|
||||
我们的实际工作常常会被打乱,需求频繁变更,做出的东西与产品有出入等等。这其中很多情况我都经历过,比如,拿到新需求任务时,头脑没有思绪,大脑出现空白,直接上手就做。然后,到测试和交付的时候就经常出现问题。
|
||||
|
||||
结合老师说四项基本原则,简化思考框架,遇到问题多思考、多提问、多练习,去梳理真正要做的事情。清楚事物背后的本质,有效剔除不必要的思虑,让自己专注于任务本身。
|
||||
|
||||
现在我在接收一个新需求的时候,常常会思考这个需求的意义是什么?到底需不需要做?要怎么做才能更好?有没有好的替代方案?需求应用的场景是什么?这样做以后方便维护吗?
|
||||
|
||||
之类的问题,思考的越多,在以后的写代码的时候,就会考虑更多。需要并发吗?并发量多少?我们服务器的承受压力多大?我应该用多少个线程跑这个才合适呢?这样写出来的代码也会很健壮。修修改改,缝缝补补的代码既不利于个人发展,对后面的迭代也是一种煎熬。
|
||||
|
||||
**b.以结果为导向**<br>
|
||||
我们日常都是按部就班的完成需求任务,确定好任务安排,于是就开始写代码。这个时候常常会有不那么预期的事情发生,因为测试一旦给提了Bug,计划的周末约会可能就泡汤了,陷入加班的窘态中。
|
||||
|
||||
这时,我们就要以结果为导向,考虑到即将要做的事情,再将要做的事情一步步细分,然后做项目推演,带着明确的目的去工作,这样可以少走弯路。
|
||||
|
||||
**c.站在不同角色角度看问题**<br>
|
||||
我们工作出现的很多问题,都是因为我们只站在程序员的角度,只能看到局部,缺少大局观导致的。不同职位在协作的过程中,认知和理解的的差异直接导致最终不能达成一致性意见,到最后还是苦逼了程序员,在做无限制的修改。
|
||||
|
||||
项目本身是有不同人员角色组合的共同体,大家为了达到共同的目的在做事,所以有必要跳出程序员角色思维的限制,站在项目本身去看待每一个问题。必要的沟通是需要的,对待不合理的需求,我们程序员也要敢于说不。
|
118
极客时间专栏/geek/10x程序员工作法/沟通反馈/答疑解惑 | 持续集成,一条贯穿诸多实践的主线.md
Normal file
118
极客时间专栏/geek/10x程序员工作法/沟通反馈/答疑解惑 | 持续集成,一条贯穿诸多实践的主线.md
Normal file
@ -0,0 +1,118 @@
|
||||
<audio id="audio" title="答疑解惑 | 持续集成,一条贯穿诸多实践的主线" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/57/77/57cb55b9d56c910367d46db964857c77.mp3"></audio>
|
||||
|
||||
“沟通反馈”模块又告一段落了,在这个模块中,我们把自己与真实世界的距离又拉近了一步。
|
||||
|
||||
一方面,我们强调主动沟通,把自身的信息更有效地传达出去;另一方面,我们也重视反馈,让真实世界的信息,更多地回到我们身边。同学们分享了很多经验,也提出了不少的问题。
|
||||
|
||||
在今天的答疑中,我选择了几个非常好的问题,从不同的角度丰富一下之前讲解的内容。
|
||||
|
||||
## 问题1:单元测试做不好,是否会影响到 CI 的效果?
|
||||
|
||||
毅 同学提到
|
||||
|
||||
>
|
||||
<p>如果单元测试做的不到位,或者不满足A-TRIP,是不是执行CI的效果就会弱很多?<br>
|
||||
——[《24 | 快速反馈:为什么你们公司总是做不好持续集成?》](http://time.geekbang.org/column/article/83461)</p>
|
||||
|
||||
|
||||
这是一个非常好的问题,问到了各种实践之间的关联。我们在前面用了两讲的篇幅介绍了持续集成这个实践,为什么要做持续集成以及如何做好持续集成。
|
||||
|
||||
在自动化模块,我们还会在这个基础之上继续延伸,介绍持续交付,这些内容是从操作的层面上进行介绍,都是对单一实践的描述。
|
||||
|
||||
利用这次答疑的机会,我再补充一个维度,谈谈实践之间的关联。
|
||||
|
||||
**持续集成的价值在于,它是一条主线,可以将诸多实践贯穿起来。**也就是说,想要真正意义上做好持续集成,需要把周边的很多实践都要做好。
|
||||
|
||||
我们具体地说一下这些实践。但请记住我们说过的,做好持续集成的关键是,快速反馈。
|
||||
|
||||
比如,我们想要做好 CI,需要有一个稳定的开发分支,所以,最好采用主开发分支的方式。想用好主分支开发,最好能够频繁提交;而频繁提交需要你的任务足够小,能够快速完成;将任务拆解的足够小,需要你真正懂得任务分解。要想在一个分支上开发多个功能,那就需要用 Feature Toggle 或者 Branch by Abstraction。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/02/db/02787851076da320932782a672dfafdb.jpg" alt="">
|
||||
|
||||
在这条线上,你有很多机会走错路。比如,你选择了分支开发模式,合并速度就不会太快,一旦反馈快不了,CI 的作用就会降低;再者,如果不能频繁提交,每次合并代码的周期就会变长,一旦合并代码的周期变长,人们就会倾向于少做麻烦事,也就会进一步降低提交的频率,恶性循环就此开启。
|
||||
|
||||
同样,即便你懂得了前面的道理,不懂任务分解,想频繁提交,也是心有余而力不足的。而多功能并行开发,则会让你情不自禁地想考虑使用多分支模型。
|
||||
|
||||
我们再来看另外一条线,也就是这个问题中提到的测试。
|
||||
|
||||
想做好 CI,首先要有可检查的东西,什么是可检查的东西,最简单的就是编译、代码风格检查,这些检查可以无条件加入构建脚本。但更重要的检查,应该来自于测试,而要想做好 CI,我们要有测试防护网。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/51/4c/51f200d383681e36ddf1bb127c03894c.jpg" alt="">
|
||||
|
||||
什么叫测试防护网呢?就是你的测试要能给你提供一个足够安全的保障,这也就意味着你要有足够多的测试。换个更技术点的术语来说,就是要有足够高的测试覆盖率。
|
||||
|
||||
如果测试覆盖率不够,即便提交了代码,CI 都通过了,你对自己的代码依然是没有信心的,这就会降低 CI 在你的心中的地位。
|
||||
|
||||
如果想有足够高的测试覆盖率,你就要多写单元测试。我们在前面讲过测试金字塔了,上层测试因为很麻烦,你不会写太多,而且很多边界条件,通过上层测试是覆盖不到的,所以,测试覆盖率在经过了初期的快速提升后,到后期无论如何是提上不去的。要想提升测试覆盖率,唯有多写单元测试。
|
||||
|
||||
要想多写单元测试,就需要编写可以测试的代码,而要想编写可测的代码,就要懂软件设计,将系统之间耦合解开。
|
||||
|
||||
通过上面的分析,你已经看出来做好持续集成,让它完全发挥自己的价值,需要做的工作还是相当多的。但也请别灰心,实际上,我做咨询时,很多团队就是从持续集成下手,开始改造他们的软件开发过程。
|
||||
|
||||
这是一个“以终为始”的思路,先锁定好目标,就是要把持续集成做好,然后围绕着这个目标改进其他做得欠佳的方面。比如,原来是多分支的,就先固定一个主分支,然后,逐步改变大家的开发习惯,让他们进入单分支的开发状态。
|
||||
|
||||
再比如,原来没有测试,那就在 CI 上先加一个最低的测试覆盖率,然后定期去提高,比如,第一周是10%,第二周是20%,这样一步一步地提高,开发团队可以一边开发新东西,一边为既有代码补测试。等到覆盖率到了一定程度,提高有困难了,团队就可以考虑怎么改进设计了。
|
||||
|
||||
**所以,CI 作为一个单独的实践,本身是很简单的,但它可以成为提纲挈领的主线,帮助团队不断改善自己的开发过程。**
|
||||
|
||||
## 问题2:老板参加复盘,不敢说真话怎么办?
|
||||
|
||||
grass10happy 同学提到
|
||||
|
||||
>
|
||||
<p>复盘是不是最好是团队内部进行,每次老板参加复盘,好像就没人说出真话了。<br>
|
||||
——[《25 | 开发中的问题一再出现,应该怎么办?》](http://time.geekbang.org/column/article/83841)</p>
|
||||
|
||||
|
||||
感谢 grass10happy 同学这个提问,把我因为篇幅原因省掉的一个部分给挽救了回来。
|
||||
|
||||
回顾会议的目的在于改进,它不仅仅在于让大家参与进来,更重要的是让团队成员能够敞开心扉,把问题暴露出来。**暴露问题,是改进的前提条件。**
|
||||
|
||||
我在[《27 | 尽早暴露问题: 为什么被指责的总是你?》](http://https://time.geekbang.org/column/article/84374)这篇文章中说过了,对于很多人来说,敢不敢暴露问题是个心理问题。你会发现,同事之间聊天,普遍是没有任何压力的,你几乎可以放心大胆地谈论各种问题,而一旦有领导在,很多顾虑就会出现了。
|
||||
|
||||
于是,问题就变成了怎么能够让大家放心地把问题暴露出来,一个办法就是设置一个安全的环境。
|
||||
|
||||
怎么设置一个安全的环境呢?对于标准的回顾会议来说,第一步应该是做安全性检查。
|
||||
|
||||
先由大家投票,最简单的方式是就是,给当前的环境打分。你觉得可以畅所欲言就打1分,你觉得还好,就打0分,如果你觉得不方便表达,比如,你看领导在,很多问题不适合反馈,就打-1。
|
||||
|
||||
每个与会者都投出属于自己的一票。然后,主持人根据投票结果决定回顾会议是否进行,比如,有人投-1就不能继续。
|
||||
|
||||
会议能继续固然好,一旦会议不能继续,可以有多种解决方案。比如,把在场职位最高的人请出去,这个人可能就是老板。老板也许心里很不爽,但在这个过程中,大家都是按照规则在办事,并不存在对谁另眼相待的情况。
|
||||
|
||||
当老板离席之后,我们再进行一轮投票,判断环境是否变得安全了。如此反复,也许要进行几轮投票,直到大家觉得安全了。
|
||||
|
||||
当然,也有可能进行多轮,有人始终觉得不安全,那可能最好的选择是,取消今天的回顾会议,换个时间地点从头再来。而项目负责人则需要私下里解决一下团队内心安全的问题。
|
||||
|
||||
通过安全性检查之后,我们才会进入回顾会议的正式环节,具体内容在正文中已经讲过了,这里就不再赘述了。
|
||||
|
||||
## 问题3:国内的技术信息落后吗?
|
||||
|
||||
One day 提到
|
||||
|
||||
>
|
||||
<p>老师能否多多介绍一下技术方面的网站之类的,新技术发展见闻之类的,或者技术总结方面。国内的技术基本都多少有些滞后。<br>
|
||||
——[《23 | 可视化:一种更为直观的沟通方式》](http://time.geekbang.org/column/article/83082)</p>
|
||||
|
||||
|
||||
这个问题让我感觉自己一下子回到了好多年前。我刚入行的那会,学习新知识确实要多看看英文网站,当时的信息传播速度不快,中文技术网站不多。
|
||||
|
||||
但在今天,显然已经不是这样了,如果只是想获得最新的技术信息,我在[《23 | 可视化:一种更为直观的沟通方式》](http://time.geekbang.org/column/article/83082)这篇文章中介绍了 InfoQ 和技术雷达,这上面的信息量已经很丰富了。你再只要稍微看几个网站,关注几个公众号,各种信息就会送到你面前。
|
||||
|
||||
所以,你根本不用担心会错过什么新技术,反倒是信息量太大,需要好好过滤一下。
|
||||
|
||||
**国内程序员真正落后的不是信息,而是观念。**
|
||||
|
||||
我讲的很多内容是软件工程方面的,以我对国内外程序员的了解来看,发达国家的程序员在这些内容的普及上,要比国内程序员好很多。
|
||||
|
||||
国内程序员的平均水平,大多停留在实现一个功能的理解上,而发达国家的程序员做事要专业许多。所以,以专业素养来看,国内程序员还有很大的提升空间。
|
||||
|
||||
在经济学里有“边际效用递减法则”(The Law Of Diminishing Marginal Utility),说的是当你手里某一物品总数越来越多时,新增一个单位该物品所获得的效用通常会越来越少。
|
||||
|
||||
当你的技术知识积累到一定程度时,还采用原来的学习方式,就很难获得真正意义上的提高,这是很多人抱怨 IT 行业不好混的原因。
|
||||
|
||||
同时,这也是我开设这个专栏的初衷,希望给大家一些不同的视角,一些新的前进动力。
|
||||
|
||||
好,今天的答疑就到这里。我想请你分享一下,你是怎么理解这些问题的呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
35
极客时间专栏/geek/10x程序员工作法/结束语/第三季回归 | 我们一起来写好代码!.md
Normal file
35
极客时间专栏/geek/10x程序员工作法/结束语/第三季回归 | 我们一起来写好代码!.md
Normal file
@ -0,0 +1,35 @@
|
||||
<audio id="audio" title="第三季回归 | 我们一起来写好代码!" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/01/43/013ffe3361243b5cfde19bae89274743.mp3"></audio>
|
||||
|
||||
你好,我是郑晔,我又回来了!
|
||||
|
||||
在《[10x 程序员工作法](https://time.geekbang.org/column/intro/100022301)》中,我们讲了工作原则,在《[软件设计之美](https://time.geekbang.org/column/intro/100052601)》,我们讲了设计原则。有不少同学通过各种途径和我表示,这两个专栏让他们受益匪浅。但也有人和我提出,虽然觉得有收获,但还不过瘾。
|
||||
|
||||
这些原则虽然很好,但怎么应用到自己的实际工作中,完全取决于个人的理解,经验丰富的人或许可以直接改变自己的行为,而经验少的人,从中的获得就完全取决于个人的悟性了。
|
||||
|
||||
比如,我在两个专栏中都讲到了单一职责原则,最终得出的结论都是要把代码写短小。但什么叫写短小,不同的人理解起来就是有差异的。
|
||||
|
||||
有一次,我在一些人面前演示了如何将一段代码重构成小函数,然后,我问听众,你们可以接受一个函数代码行数的上限是多少?一个听众很认真地说,100 行。我默默地看了看被我重构掉的那个“不好”的函数,好像也没有 100 行,按照他的标准,那个函数根本不需要改。
|
||||
|
||||
还有一次,一个颇有经验的前辈在我面前说自己写代码的要求很高,函数要求写得很短。我不明就里地问了一句,你要求一个函数不得超过多少呢?他说 50 行。
|
||||
|
||||
50 行也好,100 行也罢,在我看来,这简直是一个天文数字。我通常对自己的要求是,像 Java 语言这种表达能力一般的语言尽可能 10 行之内搞定,而像 Python、Ruby 这类动态语言,5 行代码就可以解决大多数问题,而且很多代码一行就够了。
|
||||
|
||||
在自己实际的项目中,考虑到团队的协作,我在静态检查中配置的参数是 20 行。换言之,一个函数超过 20 行,连构建都是无法通过的。
|
||||
|
||||
从这些例子中你可以看到,虽然大家都遵循了同样的原则,但具体体现在代码上,却是千差万别的。
|
||||
|
||||
也正是因为理解的差异,造成的结果是,虽然许多人懂得了很多道理,依然不能很好地完成自己的本职工作。许多人日夜辛苦地调试的代码,其实在写出来的那一刻就已经漏洞百出了。
|
||||
|
||||
如果能够知道这些代码是有问题的,在写代码之初就把这些问题消灭在萌芽中,日后的辛苦就可以节省出不少。
|
||||
|
||||
Martin Fowler 在《[重构](https://book.douban.com/subject/30468597/)》这本书里给这种有问题的代码起了一个很有特点的名字:代码的坏味道。
|
||||
|
||||
有追求的程序员都希望自己能够写出整洁的代码,而这一切的出发点就是坏味道。只有拥有对于坏味道的嗅觉,才有机会对代码进行重构,也才有机会写出整洁的代码。
|
||||
|
||||
所以我做了第三个专栏,在这个专栏里,我们就从代码的坏味道出发。我会给你提供一些非常直观的坏味道,让你看一眼就知道代码有问题。在这些坏味道中,有一些是你已经深恶痛绝的,比如,长函数和大类;有一些则是在挑战你的编程习惯,比如,else 语句和循环语句。这些坏味道的知识即学即用,对照你的代码,你立刻就能发现很多问题。
|
||||
|
||||
按照我们专栏一贯的风格,我不仅仅会告诉你一段代码是坏味道,也会告诉你这些坏味道之所以为坏味道背后的道理,还会和你讨论如何去重构这段代码。
|
||||
|
||||
有了《[10x 程序员工作法](https://time.geekbang.org/column/intro/100022301)》或《[软件设计之美](https://time.geekbang.org/column/intro/100052601)》这两个专栏的积淀,当你再去学习新专栏的时候,之前学习的这些原则就实打实地体现在对于代码的改进上,让你修炼的内功有了更好的用武之地。
|
||||
|
||||
来吧,欢迎加入《[代码之丑](https://time.geekbang.org/column/intro/100068401)》!请再次和我一起踏上程序员精进之路,我们一起修炼,不断打磨自己编写代码的手艺!
|
36
极客时间专栏/geek/10x程序员工作法/结束语/第二季回归 | 我带着软件设计课回来了!.md
Normal file
36
极客时间专栏/geek/10x程序员工作法/结束语/第二季回归 | 我带着软件设计课回来了!.md
Normal file
@ -0,0 +1,36 @@
|
||||
<audio id="audio" title="第二季回归 | 我带着软件设计课回来了!" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/65/d5/6570ecfdfc0df2cf03b5c1dee525a7d5.mp3"></audio>
|
||||
|
||||
你好,我是郑晔!
|
||||
|
||||
在这个专栏的结束语中,我说过,如果以后有机会,我会再来与你分享我对软件开发的理解。我也和编辑说过,如果这个专栏合作顺利的话,我可能会写下一个专栏。
|
||||
|
||||
没错,我又回来了!
|
||||
|
||||
我们都知道,《10x程序员工作法》这个专栏,本质上是在讲如何有效工作,尽量地排除外部干扰,**不让别人给我们添麻烦**,把精力放在值得做的事情上。然而,当我们真正开始做值得做的事情后,我们做得又怎么样呢?
|
||||
|
||||
- 别人修改了他的程序,结果你的代码崩溃了,因为你们在底层共用了一个变量。
|
||||
- 不同的项目组竞争公共的测试环境,测出来的常常是一些可以在单元测试里就解决的小问题。如果问他为什么不自己做好单元测试,他的回答常常是不好测。
|
||||
- 为了提高一段代码的处理性能,我们使用了多线程处理。结果,更多的Bug随之而来。
|
||||
- ……
|
||||
|
||||
是不是有一种熟悉的感觉扑面而来?在我的职业生涯里,我接触过的许多项目都会不停地出现类似的问题。事后究其原因,很大一部分都是软件设计没做好所造成的。换句话说,都是程序员自己给自己挖的坑。
|
||||
|
||||
**如何避免给自己找麻烦**,这就是我要在新专栏里讨论的内容。
|
||||
|
||||
所以这次,我准备和你聊一下软件设计。
|
||||
|
||||
或许你会好奇,这些东西和软件设计有关系吗?软件设计不就是设计模式吗?我会在新专栏里告诉你,软件设计可比你想的内容多得多。它不仅可以教会我们如何组织代码,还会给我们提供一把尺子,用以衡量我们的设计。
|
||||
|
||||
学会了软件设计,我们甚至可以把许多问题消灭在萌芽阶段,不会让它们为害人间。
|
||||
|
||||
如果说**这个专栏是在告诉你要做正确的事**,做有价值的需求,别把时间浪费在不该做的事情上。那[软件设计专栏](http://gk.link/a/10iul)就是告诉你如何把事做对,**如何建立有效的模型,划清模块之间的边界**。所以,二者可谓一脉相承。
|
||||
|
||||
这个专栏是我工作之外的思考,新专栏则是我本职工作的深思。来吧!加入我的软件设计课,和我一起继续扫除障碍,让我们的开发之旅更加顺畅!
|
||||
|
||||
为了感谢老同学,我还准备了一个「专属优惠」:
|
||||
|
||||
5 月 25 日,专栏上新时,我会送你一张 10 元专属优惠券,可与上新优惠同享,有效期截止 6 月 5 日,建议尽早使用。
|
||||
|
||||
**点击下方图片**,立即免费试读新专栏。
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/e0/37/e0651b2b18a5f584b6798792e6bcb337.png" alt="">](https://time.geekbang.org/column/intro/313?utm_term=zeusBE50C&utm_source=app&utm_medium=10xjiacan&utm_campaign=presell-313&utm_content=yinliu)
|
102
极客时间专栏/geek/10x程序员工作法/结束语/结束语 | 少做事,才能更有效地工作.md
Normal file
102
极客时间专栏/geek/10x程序员工作法/结束语/结束语 | 少做事,才能更有效地工作.md
Normal file
@ -0,0 +1,102 @@
|
||||
<audio id="audio" title="结束语 | 少做事,才能更有效地工作" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/03/89/033abb6b19b037cc938c25ba719ac189.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
在这个专栏里,我讲过很多东西,几乎涉及到软件开发的方方面面,但有一个重要的方面,我却从来没有说过,那就是算法。
|
||||
|
||||
因为我一直把它当做不言而喻的基本功,认为每个程序员都应该掌握。在我们专栏的结束语中,我就用这个没有涉及过的话题来开篇吧!
|
||||
|
||||
## 算法的差异
|
||||
|
||||
排序算法是每个程序员都会学到的内容,大家对各种算法也是如数家珍:插入排序、冒泡排序、归并排序、堆排序、快速排序等等。我们也知道各个算法的复杂度,比如,插入排序是 O(n^2),快速排序平均情况下是 O(nlogn)等等。
|
||||
|
||||
你有没有想过一个问题,不同算法的复杂度本质差别到底是什么呢?我们就以插入排序和快速排序为例,为什么快速排序要比插入排序快呢?
|
||||
|
||||
我不打算做算法分析,直接公布答案:因为做比较的次数少。为什么同样的排序,比较次数会有差异呢?因为插入排序每次循环只关注当前的目标,循环之间没有关系,而快速排序在做不同划分时,上一次的结果对下一次有助力,因此它省下了不少的比较次数。
|
||||
|
||||
明白了这个道理,再来看所谓的算法优化,**其实就是尽可能利用已知的信息,少做不必要的事。**
|
||||
|
||||
再来看一个常见的面试题,给你一堆数,找出前100个。很多人直觉就会想到排序,然后选出前100个。这种做法固然可行,但一定是做多了,因为这里需要的是找出前100个数,而不是要100个有序的数字,更不是要所有的数都有序。
|
||||
|
||||
说到这里,你就知道了,只要把数据划分开就好,并不需要排序,如果划分点不是第100个元素,就向着100所在的方向继续划分就好。
|
||||
|
||||
计算机是最擅长处理繁琐重复工作的,即便如此,我们依然要做算法优化,原因是当数据规模大到一定程度时,不同复杂度的算法差别就非常明显了。算法没用好,计算机硬件再好,也是徒劳的。
|
||||
|
||||
有一则《计算机程序设计艺术》作者[高德纳(Donald Knuth)的轶事](http://book.douban.com/subject/10432364/),他年轻时参加算法大赛,用最差的系统击败了诸多对手,拿到算法执行效率的冠军,凭借的就是其强大的算法优化功力。
|
||||
|
||||
对于计算机,算法尚且如此重要,我们面对工作时何尝不是如此呢!
|
||||
|
||||
## 有效工作
|
||||
|
||||
《10x 程序员工作法》,也许有的同学最初看到这个标题就急急加入了,以为会从这个专栏中学习到一些“以一抵十”的编程技法,对不起,我彻底让你失望了。我非但没讲太多编程的技法,甚至还从各种角度劝你少写代码:无论是向产品经理提问题,还是让你在前面多考虑设计。
|
||||
|
||||
难道不是做得越多才越高效吗?
|
||||
|
||||
插入排序并不会因为干的活多,就比快速排序得到更高的评价,因为它们比的是谁排得快。工作效率高,不是因为代码写得多,而是有效工作做得多。
|
||||
|
||||
如果 CPU 都被无效指令占据了,哪有时间执行有效指令呢?即使你很忙碌,但工作进展依然是收效甚微,因为无效工作占据了你太多的大脑,让你不能聚焦在正经事上,当然就是效率不高了。
|
||||
|
||||
其实,这个专栏的内容在我脑子里已经盘旋很多年了。不过,即便在专栏筹备期,我已经备了很多篇稿子之后,我依然没有找到一个准确的说法能够描绘内心的想法。
|
||||
|
||||
我想过“程序员的职业素养”,但似乎这会让专栏朝着职场行动指南的方向努力;我想过“高效工作”,但实际上我也不打算讨论那些工作技巧。直到上线日期临近,我的编辑实在受不了我的拖延,坐下来与我交流了很久,我才终于找到了内心的那个词:有效。
|
||||
|
||||
**我在这个专栏真正探讨的主题是,有效工作。**
|
||||
|
||||
**有效工作,需要我们把力量聚焦到正确的地方,做本质复杂度(Essential Complexity)的事情,少做无意义的事情。**
|
||||
|
||||
我曾经在一个大公司做咨询,按照他们的统计,线上60%的代码从来没有运行过。我们都知道,一多半的代码增加的可不只是一多半的工作量,团队可能需要的是几倍甚至几十倍的心力去维护它。
|
||||
|
||||
当然,有效工作最终没有成为这个专栏的名字,而用了更有个性的《10x 程序员工作法》。这个名字也不错,因为在我看来,很多程序员做的是负功,比如,写那60%代码的程序员。只要能做到有效工作,效率自然会高出业界平均水平很多。
|
||||
|
||||
怎么才能有效工作呢?我在专栏中已经给你讲了很多,小结一下就是:
|
||||
|
||||
<li>
|
||||
拓展自己的上下文,看到真正的目标,更好地对准靶子,比如,多了解用户,才不至于做错了方向;站在公司的层面上,才知道哪个任务优先级更高;站在行业的角度,而不局限于只在公司内成为高手,等等。
|
||||
</li>
|
||||
<li>
|
||||
去掉不必要的内容,减少浪费,比如,花时间分析需求,不做非必要的功能;花时间做好领域设计,别围着特定技术打转;花时间做好自动化,把精力集中在编码上,等等。
|
||||
</li>
|
||||
|
||||
要想有效工作,有两点非常重要。一方面,意识上要注意自己工作中无效的部分。这就像一个开关,拨过去就好了。所以,读这个专栏,有人常有恍然大悟的感觉,也有人觉得很简单。
|
||||
|
||||
很多时候,你只是不知道,就像我在专栏中提到,要问产品经理问题,这是很多人没想过的。每篇文章后面的那一句总结,就是这样的开关,拨过去就好。
|
||||
|
||||
另一方面,要构建自己关于软件开发的知识体系,这是要花时间积累的。在这个专栏中,我给你讲了很多最佳实践,就是让你知道,在某些方面,有人已经做得很好了,花时间学习,比自己从头摸索好很多。
|
||||
|
||||
这就像所有的数学公式一样,理论上你都可以自行推导,但肯定不如从教科书上学得快。
|
||||
|
||||
## 藏经阁目录
|
||||
|
||||
虽然我讲了这么多内容,但实际上,因为篇幅的关系,这只是冰山一角。其实,我给你讲的这部分内容并不是具体的知识,而是告诉了你哪些东西要去学习,给了你一张学习地图,把各种知识贯串了起来。
|
||||
|
||||
我曾与朋友打趣道,我的专栏实际上是藏经阁的目录,真正的经书还要等你自己去参悟。只不过,有一个人把这些经书之间的知识连接给你补齐了。这些连接恰恰是在学习相关内容时,让我苦思冥想许久的。
|
||||
|
||||
大约一年前(2018年4月),极客时间编辑找到我,问我是否有兴趣在极客时间开个专栏,作为“得到”重度用户的我,一直对知识服务很感兴趣。有这样的机会让我体验,我当然想试试,甚至最初给自己定下了写100篇的宏伟计划。
|
||||
|
||||
真正开始写,我才知道,在繁忙的日常工作之余,坚持写作还是一件很有挑战的事,今天看来,100篇的目标显得那么无知无畏。
|
||||
|
||||
不过,也正是因为压缩到一半左右的篇幅,在专栏后面的部分,我才极大地提高了知识密度,比如,微服务和DDD,这两个可以分别写成一个系列内容的话题,我用一篇文章就将其精华和知识脉络提炼呈现了出来。
|
||||
|
||||
因为我想尽我所能,帮助大家构建起一个软件开发的知识体系,让你在未来遇到问题时,知道可以在哪个方面进一步加强。希望这个专栏真的起到帮你理清思路,答疑解惑的作用。
|
||||
|
||||
还记得我在开篇词中的最后一段话吗?
|
||||
|
||||
>
|
||||
也许在这个专栏的最后,你发现自己并不认同我的原则,却能够用自己的原则来与我探讨,那么,恭喜你,因为那是最美妙的事情!
|
||||
|
||||
|
||||
不知道你是否形成了自己的原则呢?欢迎与大家分享。因为它代表着你已经形成了自己的知识体系。与我讲了些什么相比,你学到了什么才是一件更重要的事。
|
||||
|
||||
希望在学习了这个专栏之后,你可以用自己的工作原则做更多本质复杂度的事情,减少无意义的时间消耗。
|
||||
|
||||
其实,这个专栏的最大收益人是我自己,感谢这次的专栏之旅,我终于强行治疗了我的拖延症,把自己对于有效工作的思考完整地整理了出来,那些在脑子里模糊的印象现在终于有了一个完整的体系。这个体系就是我在专栏里提到的工作原则,现在我可以更好地表达自己的想法了。
|
||||
|
||||
不过,这个专栏对我而言也是有遗憾的。因为我想表达的内容很多,给大家打开更多大门的同时,也给很多同学留下了更多的疑问。
|
||||
|
||||
有些同学期待在某个方面再深入细节地讲一下,比如,DDD,那可是值得再写一个专栏的主题。限于这个专栏的主题和篇幅关系,我没办法深入展开,只能对大家说声抱歉了。
|
||||
|
||||
如果以后有机会,我会再来与你分享我对软件开发的理解,这次的《10x程序员工作法》之旅就暂告一段落了!
|
||||
|
||||
再见!
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/a8/1a/a89ba340ba096c375c1b84403c78121a.jpg" alt="">](http://jinshuju.net/f/4b8yu2)
|
10
极客时间专栏/geek/10x程序员工作法/结束语/结课测试 | 这些10x程序员工作法的知识你都掌握了吗?.md
Normal file
10
极客时间专栏/geek/10x程序员工作法/结束语/结课测试 | 这些10x程序员工作法的知识你都掌握了吗?.md
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
到这里,《10x程序员工作法》这门课程已经全部结束了。我给你准备了一个结课小测试,来帮助你检验自己的学习效果。
|
||||
|
||||
这套测试题共有 20 道题目,包括13道单选题和7道多选题,满分 100 分,系统自动评分。
|
||||
|
||||
还等什么,点击下面按钮开始测试吧!
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/28/a4/28d1be62669b4f3cc01c36466bf811a4.png" alt="">](http://time.geekbang.org/quiz/intro?act_id=178&exam_id=417)
|
122
极客时间专栏/geek/10x程序员工作法/综合运用/38 | 新入职一家公司,怎么快速进入工作状态?.md
Normal file
122
极客时间专栏/geek/10x程序员工作法/综合运用/38 | 新入职一家公司,怎么快速进入工作状态?.md
Normal file
@ -0,0 +1,122 @@
|
||||
<audio id="audio" title="38 | 新入职一家公司,怎么快速进入工作状态?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d0/10/d0023484fa7c8442eba027784d1eac10.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
经过前面几个模块的学习,我们分别领略了各个原则在不同场景下的应用,相信你对于这些原则的理解也上了一个台阶。但实际工作并不会清晰地告诉你,到底该运用哪个原则来解决问题。
|
||||
|
||||
所以,在接下来的三讲中,我挑选了程序员职业生涯中三个非常经典的场景,与你一起看看怎么在实际的工作中运用好已经学习到的这些原则。
|
||||
|
||||
在综合运用这个模块的第一讲,我们就来谈谈,当你加入一家新公司时,应该怎么做。
|
||||
|
||||
IT 行业快速发展,无数的机会涌现了出来,程序员频繁流动是这个行业的一个典型特征。频繁换工作,无论是对公司,还是对个人都是成本很高的一件事。所以,在加入一个新公司时,怎么让自己快速融入,尽快发挥价值,是摆在我们面前的一个重要问题。
|
||||
|
||||
以行业标准来看,我换工作的速度是很低的,但因为之前工作的原因,我需要到不同的公司与不同的人合作,每到一个新公司,工作的内容就是全新的,就如同换了一个新工作一般。因为合作周期有限,我不可能像普通员工入职新公司一样,花几个月时间慢慢熟悉,只能在尽可能短的时间内,快速上手,而且还要提出自己的新想法。
|
||||
|
||||
那我是怎么做的呢?其实,我就是运用这个专栏里提到的各种方法解决这个问题。下面我就来分享一下具体的做法。
|
||||
|
||||
## 运用思考框架
|
||||
|
||||
还记得专栏之初我提出的思考框架吗?我们要问三个问题:
|
||||
|
||||
- Where are we?(我们现在在哪?)
|
||||
- Where are we going?(我们要到哪儿去?)
|
||||
- How can we get there?(我们如何到达那里?)
|
||||
|
||||
先来看第一个问题,如果刚刚加入一家公司,哪怕我们不是一脸懵,也只是对公司业务有一个简单地了解,这是我们的现状。
|
||||
|
||||
第二个问题来看看我们的目标。一般来说,我们都是打算在新公司大展身手,但这个答案太宽泛了,我们还需要进一步细化。在这个公司长远的发展是以后的事,我们还是把第一步的目标制定成能够达到上手工作的程度,比如,能够解决日常的项目问题。
|
||||
|
||||
那接下来,我们就需要回答第三个问题了,怎么才能够达到这个目标呢?我们需要做一个分解。
|
||||
|
||||
你可以回想一下过往的工作经验,要在一个项目上工作起来,先要了解什么呢?很多人的第一反应是技术,我是程序员嘛,当然就是技术优先了。估计大多数人进到项目里,都是一头奔进代码里,然后,从各种细节研究起来。技术肯定是你要了解的,但它不应该是第一位的。
|
||||
|
||||
**技术解决的是“怎么做”的问题,而我们第一个应该了解的问题是“做什么”。**一个软件到底在做什么,能够回答这个问题的就是业务。所以,我们排在第一优先级的事情应该是业务。
|
||||
|
||||
了解业务和技术都只是让你扮演好你个人的角色,但我们通常都是在一个团队内工作的,所以,还要解决好与其他人协作的问题,这就需要我们了解团队本身是如何运作的。
|
||||
|
||||
好,我们已经将大目标做了一个分解,得到了三个小目标:
|
||||
|
||||
- 业务;
|
||||
- 技术;
|
||||
- 团队运作。
|
||||
|
||||
## 从大图景入手
|
||||
|
||||
接下来,我们来针对每一个目标,进一步看看需要了解哪些内容。
|
||||
|
||||
#### 业务
|
||||
|
||||
首先是业务。这是程序员入手新项目时最容易忽略的点。在这个专栏中,我在不同的模块中都说到了知识结构的重要性,没有结构的知识是零散的。所以,不管做任何项目,都要先从大图景入手。只有了解了大图景,各种知识才能各归其位。
|
||||
|
||||
**对于一个普通的程序员来说,业务就是这个大图景。**
|
||||
|
||||
如果你了解了业务,你自己就可以推演出基本的代码结构。但反过来,如果让你看了代码,从中推演出业务,那几乎是不可能的。
|
||||
|
||||
事实上,每次了解到一个业务,我都会在脑子中过一下,如果是我做这个业务,我会怎么做。这样一来,我就会先在整体上有一个预判,后面再对应到实际的代码上,就不会那么陌生了。
|
||||
|
||||
要了解业务,我一般都会请人给我讲一下,这个业务是做什么的,解决什么样的问题,具体的业务流程是什么样子的,等等。
|
||||
|
||||
在初期的了解中,我并不会试图弄懂所有的细节,因为我的目标只是建立起一个基本的框架,有了这个初步的了解,后续再有问题,我就知道该从哪里问起了。
|
||||
|
||||
理论上,了解业务是每个程序员都该做的事,但事实上,这也常常是出问题的地方。在请别人给我讲解业务的过程中,我发现,很多人是分不清业务和技术的,经常把二者混在一起讲。如果你跟着他的思路走,很容易就会陷入到对细节的讨论中。
|
||||
|
||||
所以,了解业务时,一定要打起精神,告诉自己,这个阶段,我要了解的只是业务,千万别给我讲技术。
|
||||
|
||||
#### 技术
|
||||
|
||||
了解完业务,就该到技术了。这是程序员最喜欢的话题。但即便是了解技术,也要有个顺序,所以,我们先从宏观内容开始。第一个问题就是这个系统的技术栈:Java、JavaScript 还是.NET,这样,我就可以对用到的工具和框架有个大致的预期。
|
||||
|
||||
接下来是系统的业务架构,这个系统包含了哪些模块,与哪些外部系统有交互等等。最好能够有一张或几张图将架构展现出来。现实情况是,不少项目并没有现成的图,那就大家一起讨论,在白板上一起画一张出来,之后再来慢慢整理。
|
||||
|
||||
有了一个初步的体系,接下来,就可以稍微深入一些。
|
||||
|
||||
我会选择从外向内的顺序了解起。首先是外部,这里的外部包括两个部分:
|
||||
|
||||
- 这个系统对外提供哪些接口,这对应着系统提供的能力;
|
||||
- 这个系统需要集成哪些外部系统,对应着它需要哪些支持。
|
||||
|
||||
一旦涉及到与外部打交道,就涉及到外部接口是什么样子的,比如,是用 REST 接口还是 RPC(Remote Procedure Call,远程方法调用) 调用,抑或是通过 MQ(Message queue,消息队列)传递消息。
|
||||
|
||||
不要简单地认为所有接口都是你熟悉的,总有一些项目会采用不常见的方式,比如,我曾见过有系统用 FTP 做接口的。
|
||||
|
||||
所有这些都相当于信息承载方式,再进一步就是了解具体的信息是什么格式,也就是协议。
|
||||
|
||||
今天常见的协议是 JSON 格式,或者是基于某个开源项目的二进制编码,比如:[Protocol Buffers](http://developers.google.com/protocol-buffers/)、[Thrift](http://thrift.apache.org) 等等。一些有年头的系统可能会采用那时候流行的协议,比如:XML;有一些系统则采用自己特定领域的协议,比如,通信领域有大量3GPP 定义的协议。
|
||||
|
||||
一般来说,从外部接口这件事就能看出一个项目所处的年代,至少是技术负责人对技术理解的年代。
|
||||
|
||||
了解完外部,就该了解内部了。了解内部系统也要从业务入手,对应起来就是,这个系统由哪些模块组成,每个模块承担怎样的职责。如果系统已经是微服务,每个服务就应该是一个独立的模块。
|
||||
|
||||
通常这也是一个发现问题的点,很多系统的模块划分常常是职责不清的,因此会产生严重的依赖问题。在前面的内容中,我多次提到限界上下文,用限界上下文的视角衡量这些模块,通常会发现问题,这些问题可以成为后续工作改进的出发点。
|
||||
|
||||
业务之后是技术,对应着我需要了解分层。前面说过,[分层结构反映着系统的抽象。](http://time.geekbang.org/column/article/88309)我希望了解一个模块内部分了多少个层,每个层的职责是什么。了解了这些对系统的设计,也就对系统有了一个整体的认识。
|
||||
|
||||
设计之后,就到了动手的环节,但还不到写代码的时候。我会先从构建脚本开始,了解项目的常用命令。我预期从版本控制里得到的是一个可以构建成功的脚本,如果不是这样,我就知道哪里需要改进了。
|
||||
|
||||
最后才是代码,比如,代码的目录结构、配置文件的位置、模块在源码上的体现等等,这是程序员最熟悉的东西,我就不多说了。作为初步的接触,了解基本的东西就够了,代码是我们后期会投入大量精力的地方,不用太着急。
|
||||
|
||||
#### 团队运作
|
||||
|
||||
最后,我们还要了解一下团队运作。同样从外部开始,这个团队有哪些外部接口,比如,需求是从哪来的,产品最终会由谁使用,团队需要向谁汇报。如果有外部客户,日常沟通是怎么安排的。
|
||||
|
||||
再来就是内部的活动,一方面是定期的活动,比如,站会、回顾会议、周会,这些不同活动的时间安排是怎样的;另一方面是团队的日常活动,比如,是否有每天的代码评审、是否有内部的分享机制等等。
|
||||
|
||||
通过了解这些内容,基本上可以大致判断出一个团队的专业程度,也可以知道自己需要帮助的时候,可以找谁帮忙,为自己更好地融入团队打下基础。
|
||||
|
||||
你也许会问,了解这么多东西需要很长时间吧?其实不然,因为只需要从整体上有认知,如果有人很清楚团队现状的话,你可以去请教,也许一天就够了,这也是我往往能够快速上手的原因。接下来,就该卷起袖子干活了!
|
||||
|
||||
## 总结时刻
|
||||
|
||||
我给你介绍了怎么把前面学到的知识运用在了解一个项目上,按照业务、技术和团队运作三个方面去了解。
|
||||
|
||||
大多数程序员习惯的工作方式,往往是从细节入手,很难建立起一个完整的图景,常常是“只见树木不见森林”,而我的方式则是**从大到小、由外而内**,将要了解的内容层层分解,有了大图景之后,很容易知道自己做的事情到底在整体上处于什么样的位置。我把上面的内容总结了成一份供你参考。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/6e/e7/6e2248978ff0b4c8957925792292f2e7.jpg" alt="">
|
||||
|
||||
附赠一点小技巧:使用“行话”。在交流的过程中,学习一点”行话“。这会让人觉得你懂行,让你很快得到信任,尽早融入团队。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**了解一个项目,从大图景开始。**
|
||||
|
||||
最后,我想请你分享一下,你在入职一个新公司遇到过哪些困难呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
110
极客时间专栏/geek/10x程序员工作法/综合运用/39 | 面对遗留系统,你应该这样做.md
Normal file
110
极客时间专栏/geek/10x程序员工作法/综合运用/39 | 面对遗留系统,你应该这样做.md
Normal file
@ -0,0 +1,110 @@
|
||||
<audio id="audio" title="39 | 面对遗留系统,你应该这样做" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/02/0e/02f57380fce929f7c50e35d74825210e.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
在上一讲中,结合着“新入职一家公司”的场景,我给你讲了如何在具体情况下应用我们前面学到的知识。这一讲,我们再来选择一个典型的实际工作场景,将所学综合应用起来。这个场景就是面对遗留系统。
|
||||
|
||||
在《[34 | 你的代码是怎么变混乱的?](http://time.geekbang.org/column/article/87845)》中,我给你讲了代码是会随着时间腐化的,无论是有意,还是无意。即便是最理想的场景,代码设计得很好,维护得也很精心,但随着技术的不断升级进步,系统也需要逐步升级换代。
|
||||
|
||||
比如,我们一直认为电信是一个独特的领域,与 IT 技术是完全独立的,学好 CT(Communication Technology,通信技术)就可以高枕无忧了。但随着 IT 技术的不断发展,今天的电信领域也开始打破壁垒,拥抱 IT 技术,提出了 ICT 的概念(Information and Communications Technology,信息通信技术)。
|
||||
|
||||
所以,无论怎样,系统不断升级改造是不可避免的事。问题是,你连自己三个月前写的代码都不愿意维护,那当面对庞杂的遗留系统时,你又该何去何从呢?
|
||||
|
||||
很多人的第一直觉是,我把系统重写一下就好了。不经思考的重写,就像买彩票一样,运气好才能写好,但大多数人没有这么好运气的,我们不能总指望买彩票中大奖改变生活。那有什么稍微靠谱的一点的路呢?
|
||||
|
||||
## 分清现象与根因
|
||||
|
||||
面对庞大的遗留系统,我们可以再次回到思考框架上寻找思路。
|
||||
|
||||
- Where are we?(我们现在在哪?)
|
||||
- Where are we going?(我们要到哪儿去?)
|
||||
- How can we get there?(我们如何到达那里?)
|
||||
|
||||
第一个问题,面对遗留系统,我们的现状是什么呢?
|
||||
|
||||
我在这个专栏前面的部分,基本上讨论的都是怎么回答目标和实现路径的问题。而对于“现状”,我们关心的比较少。因为大多数情况下,现状都是很明显的,但这一次不一样。也许你会说,有什么不一样,不就是遗留系统,烂代码,赶紧改吧。但请稍等!
|
||||
|
||||
请问,遗留系统和烂代码到底是不是问题呢?其实并不是,**它们只是现象,不是根因。**
|
||||
|
||||
在动手改动之前,我们需要先分析一下,找到问题的根因。比如,实现一个直觉上需要两天的需求,要做两周或更长时间,根因是代码耦合太严重,改动影响的地方太多;再比如,性能优化遇到瓶颈,怎么改延迟都降不下来,根因是架构设计有问题,等等。
|
||||
|
||||
所以,最好先让团队坐到一起,让大家一起来回答第一个问题,现状到底是什么样的。还记得我在《[25 | 开发中的问题一再出现,应该怎么办?](http://time.geekbang.org/column/article/83841)》中提到的复盘吗?这就是一种很好的手段,让团队共同确认现状是什么样子的,找到根因。
|
||||
|
||||
为什么一定要先做这个分析,直接重写不就好了?因为如果不进行根因分析,你很难确定问题到底出在哪,更关键的是,你无法判断重写是不是真的能解决问题。
|
||||
|
||||
如果是架构问题,你只进行模型的调整是解决不了问题的。同样,如果是模型不清楚,你再优化架构也是浪费时间。所以,我们必须要找到问题的根源,防止自己重新走上老路。
|
||||
|
||||
## 确定方案
|
||||
|
||||
假定你和团队分析好了遗留系统存在问题的根因,顺利地回答了第一个问题。接下来,我们来回答第二个问题:目标是什么。对于遗留系统而言,这个问题反而是最好回答的:重写某些代码。
|
||||
|
||||
你可能会问,为什么不是重构而是重写呢?以我对大部分企业的了解,如果重构能够解决的问题,他们要么不把它当作问题,要么早就改好了,不会让它成为问题。所以我们的目标大概率而言,就是要重写某些代码。
|
||||
|
||||
但是,在继续讨论之前,我强烈建议你,**先尝试重构你的代码,尽可能在已有代码上做小步调整,不要走到大规模改造的路上,因为重构的成本是最低的。**
|
||||
|
||||
我们真正的关注点在于第三个问题:怎么做?我们需要将目标分解一下。
|
||||
|
||||
要重写一个模块,这时你需要思考,怎么才能保证我们重写的代码和原来的代码功能上是一致的。对于这个问题,唯一靠谱的答案是测试。对两个系统运行同样的测试,如果返回的结果是一样的,我们就认为它们的功能是一样的。
|
||||
|
||||
不管你之前对测试是什么看法,这个时候,你都会无比希望自己已经有了大量的测试。如果没,你最好是先给这个模块补测试。因为只有当你构建起测试防护网了,后续的修改才算是走在坚实的道路上。
|
||||
|
||||
说到遗留代码和测试,我推荐一本经典的书:Michael Feathers 的《[修改代码的艺术](http://book.douban.com/subject/2248759/)》(Working Effectively with Legacy Code),从它的英文名中,你就不难发现,它就是一本关于遗留代码的书。如果你打算处理遗留代码,也建议你读读这本书。
|
||||
|
||||
在2007年,我就给这本书写了一篇[书评](http://book.douban.com/review/1226942/),我将它评价为“这是一本关于如何编写测试的书”,它会教你如何给真实的代码写测试。
|
||||
|
||||
这本书对于遗留系统的定义在我脑中留下了深刻印象:遗留代码就是没有测试的代码。这个定义简直就是振聋发聩。按照这个标准,很多团队写出来的就是遗留代码,换言之,自己写代码就是在伤害自己。
|
||||
|
||||
有了测试防护网,下一个问题就是怎么去替换遗留系统,答案是分成小块,逐步替换。你看到了,这又是任务分解思想在发挥作用。
|
||||
|
||||
我在《[36 | 为什么总有人觉得5万块钱可以做一个淘宝?](http://time.geekbang.org/column/article/88764)》中提到,淘宝将系统改造成 Java 系统的升级过程,就是将业务分成若干的小模块,每次只升级一个模块,老模块只维护,不增加新功能,新功能只在新模块开发,新老模块共用数据库。新功能上线,则关闭老模块对应功能,所有功能替换完毕,则老模块下线。
|
||||
|
||||
这个道理是普遍适用的,差别只是体现在模块的大小上。如果你的“小模块”是一个系统,那就部署新老两套系统,在前面的流量入口做控制,逐步把流量从老系统转到新系统上去;如果“小模块”只在代码层面,那就要有一段分发的代码,根据参数将流程转到不同的代码上去,然后,根据开发的进展,逐步减少对老代码的调用,一直到完全不依赖于老代码。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/35/8c/35b5beb135cd01e701a78df559c4e38c.jpg" alt="">
|
||||
|
||||
这里还有一个小的建议,按照分模块的做法,将新代码放到新模块里,按照新的标准去写新的代码,比如,测试覆盖率要达到100%,然后,让调用入口的地方依赖于这个新的模块。
|
||||
|
||||
最后,有了测试,有了替换方案,但还有一个关键问题,新代码要怎么写?
|
||||
|
||||
要回答这个问题,我们必须回到一开始的地方,我们为什么要做这次调整。因为这个系统已经不堪重负了,那我们新做的修改是不是一定能解决这个问题呢?答案是不好说。
|
||||
|
||||
很多程序员都会认为别人给留下的代码是烂摊子,但真有一个机会让你重写代码,你怎么保证不把摊子弄烂?这是很多人没有仔细思考过的问题。
|
||||
|
||||
如果你不去想这个问题,即便今天你重写了这段代码,明天你又会怨恨写这段代码的人没把这段代码写好,只不过,这个被抱怨的人是你自己而已。
|
||||
|
||||
要想代码腐化的速度不那么快,一定要在软件设计上多下功夫。**一方面,建立好领域模型,另一方面,寻找行业对于系统构建的最新理解。**
|
||||
|
||||
关于领域模型的价值,我在专栏前面已经提到过不少次了。有不少行业已经形成了自己在领域模型上的最佳实践,比如,电商领域,你可以作为参考,这样可以节省很多探索的成本。
|
||||
|
||||
我们稍微展开说说后面一点,“寻找行业中的最新理解”。简言之,我们需要知道现在行业已经发展到什么水平了。
|
||||
|
||||
比如说,今天做一个大访问量的系统,我们要用缓存系统,要用 CDN,而不是把所有流量都直接转给数据库。而这么做的前提是,内存成本已经大幅度降低,缓存系统才成为了标准配置。拜 REST 所赐,行业对于 HTTP 的理解已经大踏步地向前迈进,CDN 才有了巨大的进步空间。
|
||||
|
||||
而今天的缓存系统已经不再是简单的大 Map,有一些实现得比较好的缓存系统可以支持很多不同的数据结构,甚至支持复杂的查询。从某种程度上讲,它们已经变成了一个性能更好的“数据库”。
|
||||
|
||||
有了这些理解,做技术选型时,你就可以根据自己系统的特点,选择适合的技术,而不是以昨天的技术解决今天的问题,造成的结果就是,代码写出来就是过时的。
|
||||
|
||||
前面这个例子用到的是技术选型,关于“最新理解”还有一个角度是,行业对于最佳实践的理解。
|
||||
|
||||
其实在这个专栏里,我讲的内容很多都是各种“最佳实践”,比如,要写测试,要有持续集成,要有自动化等等,这些内容看似很简单,但如果你不做,结果就是团队很容易重新陷入泥潭,继续苦苦挣扎。
|
||||
|
||||
既然选择重写代码,至少新的代码应该按照“最佳实践”来做,才能够尽可能减缓代码腐化的速度。
|
||||
|
||||
总之,**改造遗留系统,一个关键点就是,不要回到老路上。**
|
||||
|
||||
## 总结时刻
|
||||
|
||||
我们把前面学到的各种知识运用到了“改造遗留系统”上。只要产品还在发展,系统改造就是不可避免的。改造遗留系统,前提条件是要弄清楚现状,知道系统为什么要改造,是架构有问题,还是领域模型混乱,只有知道根因,才可能有的放矢地进行改造。
|
||||
|
||||
改造遗留系统,我给你几个建议:
|
||||
|
||||
- 构建测试防护网,保证新老模块功能一致;
|
||||
- 分成小块,逐步替换;
|
||||
- 构建好领域模型;
|
||||
- 寻找行业中关于系统构建的最新理解。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**小步改造遗留系统,不要回到老路上。**
|
||||
|
||||
最后,我想请你分享一下,你有哪些改造遗留系统的经验呢?欢迎在留言区分享你的做法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
118
极客时间专栏/geek/10x程序员工作法/综合运用/40 | 我们应该如何保持竞争力?.md
Normal file
118
极客时间专栏/geek/10x程序员工作法/综合运用/40 | 我们应该如何保持竞争力?.md
Normal file
@ -0,0 +1,118 @@
|
||||
<audio id="audio" title="40 | 我们应该如何保持竞争力?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/69/9f/69d72c90040f9090f6a70f70d8c0809f.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
在前面两讲,我结合着两个程序员要直接面对的场景,讨论了如何综合运用前面学习到的知识,这一讲的内容可能不涉及到实际的应用场景,但与每个人的发展息息相关。我想谈谈如何走好程序员这条路。
|
||||
|
||||
## 焦虑的程序员
|
||||
|
||||
让我们再次用思考框架分析一下问题。首先,现状是什么?关于这个问题,我并不打算讨论个体,因为每个人的情况千差万别,我准备从整体入手。
|
||||
|
||||
IT 行业是一个快速发展变化的行业,一方面,我们不断地看到有人快速取得成功,另一方面,我们也听到了许多充满焦虑的声音。获得大的成功总是一个小概率事件,大多数人面对的还是日常的柴米油盐。
|
||||
|
||||
**我们的焦虑来自于对未来的不确定性,而这种不确定性是一个特定时代加上特定行业的产物。**
|
||||
|
||||
如果把时间倒回到上个世纪80年代之前,虽然当时的生活条件一般,但很少有人会为未来的发展焦虑,因为那时候,人们可以清晰地看到自己未来的人生,尽管那种人生可能是平淡的。
|
||||
|
||||
但今天的我们处在一个人类历史上少有的快速发展的时代,我们看不清以后的人生,大脑却还停留在上一代人的思维习惯上。
|
||||
|
||||
IT 行业在国内的大发展也就最近20多年的事,行业里很少有走过完整职业生涯的程序员。也正是因为如此,我们经常会产生了各种焦虑:
|
||||
|
||||
- 我刚刚入行时,有人问,程序员能做到30岁吗?
|
||||
- 我快30岁时,有人问,35岁还能做程序员吗?
|
||||
- 我35岁时,讨论变成了40岁的程序员该怎么办。
|
||||
|
||||
估计等国内有越来越多的程序员走完了整个职业生涯,就会有人关心,程序员退休之后的生活应该是什么样子了。
|
||||
|
||||
从长期来看,只要生活中还有需要用自动化解决的问题,程序员这个群体还是很有前景的。但随着时间的推移,程序员这个职业的溢价也会越来越低,单纯凭借身处这个行业就获得好发展的可能性也越来越低,想让自己的职业生涯走得更顺畅,还需要找到更好的目标,不断努力。
|
||||
|
||||
## 成为 T 型人
|
||||
|
||||
我们再来回答下一个问题:目标是什么。也许这时候,每个人脑子里想到的职业发展路线都不一样,但我准备用一个统一的目标回答你:成为 T 型人。
|
||||
|
||||
**什么叫 T 型人?简言之,一专多能。**
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a9/19/a9274fd47bf59fd4d795e7e319616b19.jpg" alt="">
|
||||
|
||||
**有了“一专”,“多能”才是有意义的,否则,就是低水平重复,而这正是很多人职业生涯不见起色的真正原因。**
|
||||
|
||||
**这里的“专”不是熟练,而是深入。**你可能是个有着10年丰富经验的程序员,但实际上只不过是重复了10年解决同样难度的问题而已,这根本就不算深入,也就没有做到真正意义上的“一专”。
|
||||
|
||||
你会发现很多优秀的人,在很多方面都会很优秀,这是“一专”带来的触类旁通。
|
||||
|
||||
当你有了“一专”,拓展“多能”,就会拥有更宽广的职业道路。比如,我拥有了深厚的技术功底,通晓怎么做软件:
|
||||
|
||||
- 如果还能够带着其他人一起做好,就成了技术领导者。
|
||||
- 如果能够分享技术的理解,就有机会成为培训师。
|
||||
- 如果能够在实战中帮助别人解决问题,就可以成为咨询师。
|
||||
|
||||
反过来,当你有了“多能”,也可以拓宽你的视野,帮你认清自己的“一专”怎样更好地发挥价值,而不是狭隘地认为自己有了技术,就已经天下尽在掌握了。视野窄,缺乏大局观,也成为了许多程序员再进一步的阻碍。事实上,这个专栏里的很多内容都是帮你打开“多能”的视角。
|
||||
|
||||
也许你会说,我在公司已经独当一面了,应该算有“一专”了吧?但我想说的是,可能还不够。只做一个公司的专家,受一个公司的波动影响太大,而成为行业的专家,才会降低自己职业生涯的风险。
|
||||
|
||||
有时,我在面试时会问候选人这样一个问题:“如果让你在一次技术大会上做分享,你会讲什么呢?”我真正的问题是,以行业标准衡量,你觉得你在哪个方面是专家呢?
|
||||
|
||||
大多数人从来没有思考过这个问题,他们只是日常在完成自己的工作,即便在某一方面已经做得很不错了,但依然算不上专家,因为他们缺乏深度思考。
|
||||
|
||||
比如,你非常熟悉 Kafka,知道它的各种参数,也读过它的实现原理。但如果我问你,Kafka 为什么要把自己定位成一个分布式流平台,它要想成为一个流平台,还要在哪方面做得更好?你的答案是什么呢?
|
||||
|
||||
这其中的差别就是,前面所谓的熟悉,只是熟悉别人的思考结果,而后面则是一个没有现成答案的东西。学习微积分是有难度,但同发明微积分相比,难度根本不在一个层次上。当然,我不是说你要熟悉所有工具的发展过程,而是自己要在一个特定的方面拥有深度的思考。
|
||||
|
||||
也许你会说,这个要求实在是太高了吧!没错,这确实是一个很高的要求。但“取法于上,仅得为中;取法于中,故为其下。”
|
||||
|
||||
其实,很多人的焦虑就源自目标太低,找不到前进的动力。给自己定下一个可以长期努力的目标,走在职业的道路上才不致于很快丧失动力。
|
||||
|
||||
## 在学习区成长
|
||||
|
||||
现在我们来回答第三个问题,怎么达到目标。既然要朝着行业中的专家方向努力,那你就得知道行业中的专家是什么样。我的一个建议是,向行业中的大师学习。
|
||||
|
||||
你或许会说,我倒是想向大师学习,但哪有机会啊!好在 IT 行业中的许多人都是愿意分享的,我们可以读到很多大师级程序员分享的内容。
|
||||
|
||||
我在入行的时候,有幸读了很多经典之作,比如,出身贝尔实验室的很多大师级程序员的作品,诸如《[C 程序设计语言](http://book.douban.com/subject/1139336/)》《[程序设计实践](http://book.douban.com/subject/1173548/)》、《[Unix 编程环境](http://book.douban.com/subject/1033144/)》等,还有一些像 Eric Raymond 这样沉浸编程几十年的人写出的作品,诸如《[Unix 编程艺术](http://book.douban.com/subject/1467587/)》,以及前面提及的 Kent Beck、Martin Fowler 和 Robert Martin 等这些人的作品。
|
||||
|
||||
读这些书的一个好处在于,你的视野会打开,不会把目标放在“用别人已经打造好的工具做一个特定的需求”,虽然这可能是你的必经之路,但那只是沿途的风景,而不是目标。
|
||||
|
||||
接下来,我们要踏上征程,怎么才能让自己的水平不断提高呢?我的答案是,找一个好问题去解决,解决了一个好的问题能够让你的水平快速得到提升。什么是好问题?就是比你当前能力略高一点的问题,比如:
|
||||
|
||||
- 如果你还什么都不会,那有一份编程的工作就好。
|
||||
- 如果你已经能够写好普通的代码,就应该尝试去编写程序库。
|
||||
- 如果实现一个具体功能都没问题了,那就去做设计,让程序有更好的组织。
|
||||
- 如果你已经能完成一个普通的系统设计,那就应该去设计业务量更大的系统。
|
||||
|
||||
为什么要选择比自己水平高一点的问题?这与我们学习成长的方式有关。Noel Tichy 提出了一个“学习区”模型,如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/62/b1/6236b2bd674fe1ff0edcd9485755b8b1.jpg" alt="">
|
||||
|
||||
- 最内层是舒适区(Comfort Zone),置身其中会让人感觉良好,但也会因为没有挑战,成长甚微,你可以把它理解成做你最熟悉的事情。
|
||||
- 最外层是恐慌区(Panic Zone),这是压力极大的地方,完全超出了你的能力范围,你在其中只会感到无比的焦虑。
|
||||
- 中间的是学习区(Learning Zone),事情有难度,又刚好是你努力一下可以完成的,这才是成长最快的区域。
|
||||
|
||||
根据这个模型,只有一直身处学习区才能让人得到足够的成长,所以,我们应该既选择比自己能力高一点的问题去解决,不要总做自己习惯的事,没有挑战,也不要好大喜功,一下子把自己的热情全部打散。
|
||||
|
||||
在学习区成长,就不要满足于当前已经取得的成绩,那已经成为你的舒适区。因为我们有远大的目标在前面指引,完成日常的工作只不过是个人成长路上的台阶。
|
||||
|
||||
也许你会说,我的工作不能给我个人成长所需的机会,怎么办呢?实际上,别人只会关心你是否完成工作,成长是自己的事情,很多机会都要靠自己争取,前面提到的那些具体做法完全是你可以在工作范围内,自己努力的事情。
|
||||
|
||||
如果你当前的工作已经不能给你提供足够好的问题,那就去寻找一份更有挑战性的工作。在 IT 行业,跳槽似乎是一件很常见的事,但很多人跳槽的时候,并不是以提升自己为目标的。造成的结果是,不断地做同一个层面的工作,自然也就很难提升自己的水平。
|
||||
|
||||
为什么程序员都愿意到大厂工作?因为那里有高水平的人和好的问题。但如果只是到大厂去做低水平的事,那就是浪费时间了。所以,即便你真的想到大厂工作,与谁一起工作,做什么事,远比进入大厂本身要重要得多。
|
||||
|
||||
如果你真的能够不断向前进步,迟早会遇到前面已经没有铺就好的道路,这时候,就轮到你创造一个工具给别人去使用了。比如,2012年,我在项目中受困于集成问题,却找不到一个我想要的、能在单元测试框架里用的模拟服务器,于是,我写了[ Moco](http://github.com/dreamhead/moco)。
|
||||
|
||||
最后,我还想鼓励你分享所得。我在《[28 | 结构化:写文档也是一种学习方式](http://time.geekbang.org/column/article/84663)》中和你说过,输出是一种将知识连接起来的方式,它会让人摆脱固步自封,也会帮你去创造自己的行业影响力,机会会随着你在行业中的影响力逐渐增多,有了行业影响力,你才有资格成为行业专家。
|
||||
|
||||
当你成为了一个行业级别的专家,就可以在这条路上一直走下去,而不必担心自己是不是拼得过年轻人了,因为你也在一直前进!
|
||||
|
||||
## 总结时刻
|
||||
|
||||
程序员是一个充满焦虑的群体,焦虑的本质是对未来的不确定。工作在这个时代的程序员是一个特殊的群体,一方面,这个大时代为我们创造了无数的机会,另一方面,因为程序员是一个新的行业,所以,很多人不知道未来是什么样子的,焦虑颇深。
|
||||
|
||||
从目前的发展来看,IT 行业依然是一个非常有前景的行业,但想在这条路上走好,需要我们成为 “T ”型人才,也就是“一专多能”。一专多能的前提是“一专”,让自己成为某个方面的专家。这个专家要放在行业的标准去看,这才能降低因为一个公司的波动而造成的影响。
|
||||
|
||||
成为行业专家,要向行业的大师学习,给自己定下一个高的目标,然后是脚踏实地,找适合自己的问题去解决,让自己一直在学习区成长。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**在学习区工作和成长。**
|
||||
|
||||
最后,我想请你分享一下,你有哪些保持自己竞争力的心得呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
253
极客时间专栏/geek/10x程序员工作法/综合运用/划重点 | “综合运用”主题内容的全盘回顾.md
Normal file
253
极客时间专栏/geek/10x程序员工作法/综合运用/划重点 | “综合运用”主题内容的全盘回顾.md
Normal file
@ -0,0 +1,253 @@
|
||||
<audio id="audio" title="划重点 | “综合运用”主题内容的全盘回顾" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/00/c0/009d4185fa58cd570b1bb438bfe9f5c0.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
又到了我们划重点的时间了,因为篇幅关系,“综合运用”这个模块最为短小精悍。
|
||||
|
||||
在这个模块中,我们把前面学到的各种知识综合起来,运用在实际的工作场景中,让你知道这些内容并不是一个个孤立的实践,在实际工作中,唯有将它们结合起来,才能发挥最大功效。
|
||||
|
||||
## 重点复习
|
||||
|
||||
在这个模块中,我们学习到了一些新知识。
|
||||
|
||||
<li>
|
||||
**“学习区”学习模型**
|
||||
<ul>
|
||||
- 舒适区,舒适而缺乏成长。
|
||||
- 恐慌区,超出能力范围。
|
||||
- 学习区,有难度而可以达成。
|
||||
- 在学习区练习才能得到足够的成长。
|
||||
|
||||
**T 型人才,一专多能**
|
||||
|
||||
- 知识的广度。
|
||||
- 专业技能的深度。
|
||||
- 有“一专”,“多能”才是有意义的。
|
||||
|
||||
在这个模块中,我们还了解了一些重要的思路,让我们把工作做得更好。
|
||||
|
||||
<li>
|
||||
**进入新工作,从全面了解开始**
|
||||
<ul>
|
||||
- 业务:做什么。
|
||||
- 技术:怎么做。
|
||||
- 团队运作:怎么与人协作。
|
||||
- 从大到小,由外及内地了解工作。
|
||||
|
||||
面对遗留系统,稳扎稳打,小步前行
|
||||
|
||||
<li>基础理念
|
||||
<ul>
|
||||
- 烂代码只是现象,要了解根因。
|
||||
- 能重构,先重构,大规模改造是迫不得已的选择。
|
||||
- 小步前行。
|
||||
|
||||
- 构建测试防护网。
|
||||
- 将大系统分解成小模块,逐步替换。
|
||||
- 新旧模块并存,由分发模块调度。
|
||||
- 建立好领域模型。
|
||||
- 寻找行业对于系统构建的最新理解。
|
||||
|
||||
**程序员的职业发展**
|
||||
|
||||
<li>程序员的焦虑来自于对未来的不确定性,这种不确定性是一个特定时代加上特定行业的产物。
|
||||
<ul>
|
||||
- 快速发展的中国经济。
|
||||
- 程序员在中国是一个新兴职业。
|
||||
|
||||
## 实战指南
|
||||
|
||||
<li>
|
||||
<p>了解一个项目,从大图景开始。<br>
|
||||
——《[38 | 新入职一家公司,怎么快速进入工作状态?](http://time.geekbang.org/column/article/89981)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>小步改造遗留系统,不要回到老路上。<br>
|
||||
——《[39 | 面对遗留系统,你应该这样做](http://time.geekbang.org/column/article/90231)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>在学习区工作和成长。<br>
|
||||
——《[40 | 我们应该如何保持竞争力?](http://time.geekbang.org/column/article/90864)》</p>
|
||||
</li>
|
||||
|
||||
## 额外收获
|
||||
|
||||
在这个模块的最后,针对大家在学习过程中的一些问题,我也进行了回答,帮你梳理出一个思路,更好地理解学到的内容:
|
||||
|
||||
- 推行新观念,找愿意改变的人,做具体的事。
|
||||
- Lead by Example.
|
||||
- 外部系统应该用接口隔离,这种做法体现了接口隔离原则(ISP),也是防腐层概念的体现。
|
||||
- 外部系统的测试,能模拟的就模拟,能本地的就本地。
|
||||
|
||||
## 留言精选
|
||||
|
||||
关于入职一家新公司,怎么快速进入工作状态这个问题,西西弗与卡夫卡 同学分享了他的方法:
|
||||
|
||||
>
|
||||
有朋友正在转型,从乙方商业化产品的交付经理转向新公司的产品经理。原本得心应手的思维方式和工作习惯,遇到了巨大挑战。以前只需依据已有产品的功能出解决方案,能做就能做,不能实现就是不能实现,到某个时间交付什么功能很明确,考核是以交付签字为准。现在需要面对各方需求,自己想明白用户真正的问题是什么,最终要交付的价值是什么,没有一个实体的谁人来签字,只有不断地迭代。
|
||||
|
||||
|
||||
>
|
||||
借鉴领域驱动设计,可以采用以下方法。简单描述的话,是一个点、一个圈再加一个箭头线,是不是有点像丘比特?
|
||||
|
||||
|
||||
>
|
||||
一个“点”,指的是用户核心价值。这是最关键的一条,基本上只能靠自己想明白。想,不是闭门造车式的苦思冥想,可以是已有的领域经验,可以从书本中学习,可以是大家的各种吐槽,可以是自己从旁边观察用户的实践,还可以是自己变身为用户的实践。
|
||||
|
||||
|
||||
>
|
||||
有些人会纠结“点”想的对不对,迟迟不敢动手。其实一开始想得对不对不是那么重要,关键是要有这“点”,然后快速到市场上验证,根据反馈再调整。
|
||||
|
||||
|
||||
>
|
||||
一个“圈”,指的是围绕核心价值划出的范围,即领域驱动设计中的限界上下文。产品经理面临的一个现实是,各种人都会给你提需求,只要他们觉得和你有关,还时不时来问什么时候可以实现。
|
||||
|
||||
|
||||
>
|
||||
需求轰炸之下很容易焦虑,不光自己焦虑,所有的利益相关者都会焦虑。依据核心价值,框出需求范围,在和各方交流过程中可以有一种确定性,减少焦虑,利于行动。
|
||||
|
||||
|
||||
>
|
||||
大家(不光是研发团队,也包括其他需求方)就能明白,哪些和当前核心价值密切相关,我们优先考虑;哪些与核心价值有关但它不在我们的范围内,属于其他团队,需要他们协助;哪些有关系,但目前没想清楚价值大不大,并且代价可能很高建议先搁置。范围不是一成不变,它随着时间会发生变动,所以我们不要追求固定,只要保证在某个时间段内,大家一致认同即可。
|
||||
|
||||
|
||||
>
|
||||
一个“箭头”,指的是实现路径,箭头指向核心目标(核心价值)。目标(核心价值)和范围描绘的是终极,而从现实到终极还有很多路要走,可能的路径还有很多条。我们需要琢磨怎么走更稳当,怎么走代价比较低,路上关键的里程碑是什么。路径对不对是其次,重要的是思考过程,可以把关键点需要交付的价值、需要支持的资源等等梳理清楚。
|
||||
|
||||
|
||||
另外,西西弗与卡夫卡 同学还对于程序员如何保持竞争力的问题给出了非常不错的建议。
|
||||
|
||||
>
|
||||
补充我的一些做法。工作中不要满足当前需求,要经常从自己上级主管甚至老板角度来审视自己的工作,思考业务的终极目标,持续琢磨扩展边界,挑战工作难度。
|
||||
|
||||
|
||||
>
|
||||
平时多看书多思考,除了钻研某个领域,还要多有涉猎,拓展领域,成为终身学习者。
|
||||
|
||||
|
||||
>
|
||||
适当运动维持健康,你有更多体力和更强抗压能力的时候,就可以超过不少人。
|
||||
|
||||
|
||||
>
|
||||
保持竞争力除了上述之外,要保持乐观,相信大多数事都有解决方法,在多数人都容易放弃的时候,你的坚持,就是竞争力。
|
||||
|
||||
|
||||
对于新入职一家公司的场景,Y024 同学分享了他快速进入工作状态的方法:
|
||||
|
||||
>
|
||||
1.我会在权限允许的范围内,时不时的到处翻翻 ftp、内部 wiki 等资源,星星点点构建全貌(业务、技术、团队)。
|
||||
|
||||
|
||||
>
|
||||
2.梳理系统数据流。去年很火的电视剧「大江大河」里,宋运辉初入职场的方式就很值得借鉴:先走通全部流程,有个全貌,利用图书馆、师傅等资源再自己动手各个击破并绘制流程图,最终实践检验认知,以技术说话融入团队。
|
||||
|
||||
|
||||
>
|
||||
(他就每天只要天气晴朗,绕着设备上上下下、里里外外地跑。一个星期下来,全部流程走通;两个星期不到,原理搞通,仪表能读,普通故障能应付;第三星期开始,他可以开出维修单,但得给师父过目;第四星期起,谁有事请假他可以顶上,坐到仪表盘前抄表看动态做操作。师父说他学得很快。
|
||||
|
||||
|
||||
>
|
||||
第四星期起,没人可以让他顶替时候,他在仪表室后面支起绘图板。先画出工艺流程图,经现场核对无误,又让师父审核后,开始按部就班地根据液体走向,测绘所有设备的零件图、装配图、管段图等。
|
||||
|
||||
|
||||
>
|
||||
这工作最先做的时候异常艰难,首先是绘图不熟练,很多小毛病,尤其是遇到非标零件,还得到机修工段测绘,有时一天都绘不成一个小小非标件。如果车间技术档案室有图纸还好,可以对照着翻画,可档案室里的图纸残缺不全,前后混乱,想找资料,先得整理资料。
|
||||
|
||||
|
||||
>
|
||||
资料室中年女管理员乐得有个懂事的孩子来帮她整理,索性暗暗配把钥匙给宋运辉,要是她下班不在的时候,让宋运辉自己偷偷进来关上门寻找资料。
|
||||
|
||||
|
||||
>
|
||||
机修工段的人本来挺烦这个宋运辉,说他一来维修单子多得像雪片,支得他们团团转,有人还趁宋运辉上班时候冲进控制室指桑骂槐,被寻建祥骂了回去,差点还打起来。但后来集中一段维修高峰后,维修单子又少了下去,上面还表扬跑冒滴漏少很多,一工段和机修工段各加一次月奖,可见设备性能好转。
|
||||
|
||||
|
||||
>
|
||||
再以后遇到维修,他们不能确定要用什么零件,打个内线电话给控制室问宋运辉,一问就清楚。双方关系渐渐变得铁起来。基层有时候很简单,只要拿得出技术,别人就服。 )
|
||||
|
||||
|
||||
另外,Y024 同学还很认真地整理了专栏提到的部分图书:
|
||||
|
||||
>
|
||||
郑老师拍案惊奇书单及简评,最近各大书店有活动,可以借机囤起来了。
|
||||
|
||||
|
||||
>
|
||||
<p>1.重构<br>
|
||||
作者: Martin Fowler<br>
|
||||
[https://book.douban.com/subject/1229923/](https://book.douban.com/subject/1229923/)<br>
|
||||
严格说来,我并没有完整的读完这本书,不过,正如作者自己所说,这样的书原本就不指望能够读完,因为有一大部分其实是参考手册。正是我读过的部分让我知道了重构,让我知道这么做可以把代码写得更好。</p>
|
||||
|
||||
|
||||
>
|
||||
<p>2.敏捷软件开发<br>
|
||||
作者: Robert C·Martin<br>
|
||||
[https://book.douban.com/subject/1140457/](https://book.douban.com/subject/1140457/)<br>
|
||||
这是一本名字赶潮流,内容很丰富的书,这本书让我开始理解软件设计,从此不再刻意追求设计模式。</p>
|
||||
|
||||
|
||||
>
|
||||
<p>3.测试驱动开发<br>
|
||||
作者: Kent Beck<br>
|
||||
[https://book.douban.com/subject/1230036/](https://book.douban.com/subject/1230036/)<br>
|
||||
读的是英文版,因为当时中文版还没有出版,所以,我不敢说,我通过这本书很好的理解了测试驱动开发,但它却为我打开了一扇门,让我知道了一种更好的工作方式。</p>
|
||||
|
||||
|
||||
>
|
||||
<p>4.修改代码的艺术<br>
|
||||
作者: Michael Feathers<br>
|
||||
[https://book.douban.com/subject/2248759/](https://book.douban.com/subject/2248759/)<br>
|
||||
这是一本讲解如何编写测试的书。至于这本书的具体内容,我的评价是实用。如果说不足,那么,这本书缺少一个列表,就像Martin Fowler为《重构》所做的那样,出什么样的问题,应该采用怎样的手法进行处理。</p>
|
||||
|
||||
|
||||
对于如何面对遗留系统, 毅 同学提到:
|
||||
|
||||
>
|
||||
<p>1.了解原系统已实现的功能,没有文档就在心中划分好内部功能模块;<br>
|
||||
2.各模块的边界及关联,对于业务交叉点先思考通信机制;<br>
|
||||
3.看代码,通常是瓶颈优先,业务上是先复杂后简单;<br>
|
||||
4.选定切入点;<br>
|
||||
5.正式改造时先把原有功能抽象出来使用现有实现,改造的过程完成前不会受影响;<br>
|
||||
6.改造完成后切换到新实现进行测试;<br>
|
||||
7.稳定后替换旧实现;<br>
|
||||
8.重复4-7。</p>
|
||||
|
||||
|
||||
Wei 同学对于“T型人”的说法感触很深:
|
||||
|
||||
>
|
||||
“T型人”这个太说到点了,到底是做“专”还是做“广”,哪条路线一直是我思考的方向;工作上跟大牛工作过,给我感觉几乎是全能的,我一直都想像他们那样,做一个多面手,但是如何做广,这一直是困扰我的一个问题。
|
||||
|
||||
|
||||
>
|
||||
我是dev出身,但是现实遇到的问题往往跟数据库,发布的平台相关;这样说下来,各种相关领域,数据库、k8s、网络协议、DNS ,都需要大量时间去积累;有时候什么都懂一点,反而让自己应该定位什么角色感到迷茫了,掌握的水平不足以让自己去应聘DBA、Ops,但是只是应聘dev似乎又有点“浪费”,跟那些熟悉最新语言/框架的对比起来没特殊竞争力。
|
||||
|
||||
|
||||
>
|
||||
今天学习“T型人”这个概念,让我好好思考了自己到底应该怎么定位。我首先是一个developer,这个是根;对语言特性的熟练掌握,各种best practices,例如课程中提到的TDD等应该熟练应用起来;然后在这上面拓展,学习架构知识,多思考对不同系统应该怎么设计,老师提到的DDD会认真学习应用;再有软件最终还是给用户使用,而不是单单提交代码。相关的数据库、k8s、监控运用根据实际遇到的问题再学习解决。
|
||||
|
||||
|
||||
>
|
||||
最重要的是,在学习区终身学习和工作!
|
||||
|
||||
|
||||
对于如何持续保持竞争力的问题,enjoylearning 同学提到:
|
||||
|
||||
>
|
||||
程序员如何保持竞争力很重要,在这个年轻人学习能力不断提升的IT行业,作为老程序员经验阅历眼光以及技术前沿判断力就显得越来越重要。
|
||||
|
||||
|
||||
>
|
||||
说起来这个职业是一个需要终身学习的职业,年龄不重要,能力才重要,是不是让自己永远呆在学习区更重要。
|
||||
|
||||
|
||||
对于技术推广,desmond 同学的理解也很棒:
|
||||
|
||||
>
|
||||
技术推广,不要先推广最难的部分,先推广能让对方感到最明显好处的部分。取得对方的信任,是友好沟通的基础。
|
||||
|
||||
|
||||
**感谢同学们的精彩留言。我们的专栏更新已经进入尾声阶段,后续我会为大家做一些对整个专栏进行全盘复习的内容,敬请期待。**
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
250
极客时间专栏/geek/10x程序员工作法/综合运用/总复习 | 重新审视“最佳实践”.md
Normal file
250
极客时间专栏/geek/10x程序员工作法/综合运用/总复习 | 重新审视“最佳实践”.md
Normal file
@ -0,0 +1,250 @@
|
||||
<audio id="audio" title="总复习 | 重新审视“最佳实践”" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/1f/f5/1f6e2baf484f5a95464a719781910df5.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
我承诺的正文内容已经全部交付给你,恭喜你完成了整个专栏的学习!希望通过对这些内容的学习,你已经对“如何做好软件”有了一个全新的认识。
|
||||
|
||||
在这个专栏中,我给你讲了很多行业中的最佳实践,比如:测试、持续集成等等,但因为这个专栏叙述方式的关系,一些有关联的实践被放到了不同的模块下讲解。
|
||||
|
||||
所以在这一讲中,我们将按照最佳实践的维度重新审视这些内容。我会将这些知识重新串联起来,帮你做一个对专栏的整体复习。
|
||||
|
||||
## 产品
|
||||
|
||||
做产品,很多时候是面向不确定性解决问题。目前这方面最好的实践是“精益创业”。对于精益创业的最简单的理解就是“试”。试也有试的方法,精益创业提出了一个“开发(build)- 测量(measure)- 认知(learning)”这样的反馈循环,通过这个循环得到经过验证的认知(Validated Learning)。
|
||||
|
||||
既然是对不确定产品特性的尝试,最好的办法就是低成本地试。在精益创业中,最小可行产品(MVP)就是低成本的试法。最小可行产品,就是“刚刚好”满足用户需求的产品。理解这个说法的关键在于用最小的代价,尝试可行的路径。
|
||||
|
||||
在产品的打磨过程中,可以采用用户测试的方式,直接观察用户对产品的使用。作为程序员,我们要尽可能吃自家的狗粮,即便你做的产品不是给自己使用的产品,也可以努力走近用户。
|
||||
|
||||
<li>
|
||||
<p>**精益创业**<br>
|
||||
相关阅读:《[06 | 精益创业:产品经理不靠谱,你该怎么办?](http://time.geekbang.org/column/article/76260)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**最小可行产品(MVP)**<br>
|
||||
相关阅读:《[19 | 如何用最小的代价做产品?](http://time.geekbang.org/column/article/80691)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**用户测试、验证产品特性、吃自家狗粮**<br>
|
||||
相关阅读:《[26 | 作为程序员,你也应该聆听用户声音 ](http://time.geekbang.org/column/article/84185)》</p>
|
||||
</li>
|
||||
|
||||
## 需求
|
||||
|
||||
当我们确定做一个产品功能时,怎么描述需求也是很重要的。产品列表式的需求描述方式最容易出现问题的地方在于,看不清需求的全貌。
|
||||
|
||||
用户故事是一个好的需求描述方式:作为一个什么角色,要做什么样的事,以便达成一种怎样的效果。
|
||||
|
||||
在用户故事中,验收标准是非常重要的一环。即便不是采用用户故事描述需求,也依然建议先将验收标准定义清楚。
|
||||
|
||||
开发团队对需求的理解普遍偏大,基本上都是一个主题。在开发之前,先将需求拆分成小粒度的。衡量一个用户故事拆分是否恰当,一个标准是 INVEST 原则。有了拆分出来的用户故事,就可以进行估算了,估算的过程也是对需求加深理解的过程,过大的用户故事应该再次拆分。
|
||||
|
||||
当我们有了拆分之后的需求,就可以对需求进行优先级讨论了。先做重要性高的事,而不是一股脑地去做所有的需求。只有分清了需求的优先级,才能方便地对需求进行管理。
|
||||
|
||||
<li>
|
||||
<p>**用户故事**<br>
|
||||
相关阅读:《[04 | 接到需求任务,你要先做哪件事?](http://time.geekbang.org/column/article/75100) 》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**需求的分解与估算**<br>
|
||||
相关阅读:《[17 | 程序员也可以“砍”需求吗?](http://time.geekbang.org/column/article/79520)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**需求管理、优先级**<br>
|
||||
相关阅读:《[18 | 需求管理:太多人给你安排任务,怎么办?](http://time.geekbang.org/column/article/80428)》</p>
|
||||
</li>
|
||||
|
||||
## 持续集成
|
||||
|
||||
在开发中,写出代码并不是终点,我们要把代码集成起来。集成要经常做,改动量越小,集成就可以做得越频繁,频繁到每次提交都去集成,这就是持续集成。
|
||||
|
||||
持续集成发展到今天已经是一套完整的开发实践。想要做好持续集成,你需要记住持续集成的关键是“快速反馈”。
|
||||
|
||||
- 怎样快速得到反馈。
|
||||
- 怎样反馈是有效的。
|
||||
|
||||
持续集成,可以继续延展,将生产部署也纳入其中,这就是持续交付。如果持续交付,再向前一步,就可以同产品验证结合起来。
|
||||
|
||||
持续交付的关键点,是在不同的环境验证发布包和自动化部署。不同的环境组成了持续交付的构建流水线,而自动化部署主要是 DevOps 发挥能力的地方。持续交付的发展,让交付物从一个简单的发布包变成了一个拥有完整环境的 Docker 镜像。
|
||||
|
||||
持续集成和持续交付可以将诸多的实践贯穿起来:单元测试、软件设计、任务分解、主分支开发、DevOps 等等。所以,如果一个公司希望做过程改进,持续集成是一个好的出发点。
|
||||
|
||||
<li>
|
||||
<p>**持续集成发展史 **<br>
|
||||
相关阅读:《[05 | 持续集成:集成本身就应该是写代码的一个环节](http://time.geekbang.org/column/article/75977)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**快速反馈**<br>
|
||||
相关阅读:《[24 | 快速反馈:为什么你们公司总是做不好持续集成?](http://time.geekbang.org/column/article/83461)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**持续集成,贯穿诸多实践**<br>
|
||||
相关阅读:《[答疑解惑 | 持续集成,一条贯穿诸多实践的主线 ](http://time.geekbang.org/column/article/85049)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**持续交付**<br>
|
||||
相关阅读:《[32 | 持续交付:有持续集成就够了吗?](http://time.geekbang.org/column/article/87229)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**与产品结合:持续验证**<br>
|
||||
相关阅读:《[答疑解惑 | 持续集成、持续交付,然后呢?](http://time.geekbang.org/column/article/89050) 》</p>
|
||||
</li>
|
||||
|
||||
## 测试
|
||||
|
||||
测试是一个典型的程序员误区,很多程序员误以为测试只是测试人员的事。理解了软件变更成本,知道了内建质量之后,我们就应该清楚,测试应该体现在全部的开发环节中。这一思想在开发中的体现就是自动化测试。
|
||||
|
||||
想要写好自动化测试,需要先理解测试金字塔,不同的测试运行成本不同。为了让软件拥有更多的、覆盖面更广的测试,需要多写单元测试。
|
||||
|
||||
编写测试的方式有很多,一种实践是测试驱动开发(TDD)。先写测试,然后写代码,最后重构,这就是 TDD 的节奏:红——绿——重构。测试驱动开发的本质是测试驱动设计,所以,编写可测试的代码是前提。
|
||||
|
||||
要想做好 TDD,一个重要的前提是任务分解,分解到非常小的微操作。学会任务分解,是成为优秀程序员的前提条件。
|
||||
|
||||
想写好测试,需要懂得好测试是什么样子的,避免测试的坏味道。好测试有一个衡量标准:A-TRIP。
|
||||
|
||||
我们不只要写好单元测试,还要站在应用的角度写测试,这就是验收测试。验收测试现在比较成体系的做法是行为驱动开发(BDD),它让你可以用业务的语言描述测试。
|
||||
|
||||
<li>
|
||||
<p>**单元测试、自动化测试、蛋卷和冰淇淋模型**<br>
|
||||
相关阅读:《[12 | 测试也是程序员的事吗?](http://time.geekbang.org/column/article/77917)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**测试驱动开发**<br>
|
||||
相关阅读:《[13 | 先写测试,就是测试驱动开发吗?](http://time.geekbang.org/column/article/78104)》<br>
|
||||
相关阅读:《[14 | 大师级程序员的工作秘笈](http://time.geekbang.org/column/article/78507) 》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**测试练习**<br>
|
||||
相关阅读:《[15 | 一起练习:手把手带你拆任务](http://time.geekbang.org/column/article/78542) 》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**简单的测试、测试的坏味道、A-TRIP**<br>
|
||||
相关阅读:《[16 | 为什么你的测试不够好?](http://time.geekbang.org/column/article/79494) 》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**验收测试、写好验收测试用例**<br>
|
||||
相关阅读:《[32 | 持续交付:有持续集成就够了吗?](http://time.geekbang.org/column/article/87229)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**外部系统测试,用接口隔离**<br>
|
||||
相关阅读:《[答疑解惑 | 如何在实际工作中推行新观念?](http://time.geekbang.org/column/article/91127) 》</p>
|
||||
</li>
|
||||
|
||||
## 编码与设计
|
||||
|
||||
编码和设计,是软件开发中最重要的一环。在我看来,编码和设计是一体,想清楚才能写出好代码。很多程序员追求写好代码,却没有一个很好的标准去衡量代码的好坏。结合着软件设计的一些理念,我给你一个编写好代码的进步阶梯,希望你能达到用业务语言编写代码的程度。
|
||||
|
||||
用业务语言编写代码,需要对软件设计有着良好的理解。提到设计,人们的初步印象是“高内聚低耦合”,但这是一个太过高度抽象的描述。SOLID 原则是一个更具实践性的指导原则,有了原则做指导,就可以更好地理解设计模式了。
|
||||
|
||||
有了基础原则,我们会知道将不同的代码划分开,这样就产生了分层。好的分层可以构建出抽象,而其他人就可以在这个抽象上继续发展。对于程序员来说,构建自己的核心抽象是最关键的一步。
|
||||
|
||||
目前构建核心抽象最好的方式是领域驱动设计(DDD),它将我们思考的起点拉到了业务层面,通过战略设计将系统按照不同的上下文划分开来,再通过战术设计,指导我们有效地设计一个个的领域模型。
|
||||
|
||||
但无论怎样做设计,前提是使用适当的技术解决适当的问题,不要把技术用复杂,把团队带入泥潭。
|
||||
|
||||
<li>
|
||||
<p>**业务语言写代码**<br>
|
||||
相关阅读:《[21 | 你的代码为谁而写?](http://time.geekbang.org/column/article/82581)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**架构设计**<br>
|
||||
相关阅读:《[34 | 你的代码是怎么变混乱的? ](http://time.geekbang.org/column/article/87845)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**分层、抽象**<br>
|
||||
相关阅读:《[35 | 总是在说MVC分层架构,但你真的理解分层吗?](http://time.geekbang.org/column/article/88309)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**业务与技术**<br>
|
||||
相关阅读:《[36 | 为什么总有人觉得5万块钱可以做一个淘宝?](http://time.geekbang.org/column/article/88764) 》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**微服务**<br>
|
||||
相关阅读:《[37 | 先做好DDD再谈微服务吧,那只是一种部署形式 ](http://time.geekbang.org/column/article/89049)》</p>
|
||||
</li>
|
||||
|
||||
## 项目准备
|
||||
|
||||
从头开始一个项目时,一个好的实践就是把一切都准备好。迭代0就是这样一个把迭代准备好的实践,从需求到技术,做好充分的准备工作再开启项目,你会显得从容不迫。在技术方面,迭代0最重要的准备工作就是构建脚本,它是后续很多工作的基础,比如,持续集成。
|
||||
|
||||
<li>
|
||||
<p>**迭代0,做基础的准备**<br>
|
||||
相关阅读:《[10 | 迭代0: 启动开发之前,你应该准备什么?](http://time.geekbang.org/column/article/77294)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**构建脚本,让项目一开始就自动化**<br>
|
||||
相关阅读:《[30 | 一个好的项目自动化应该是什么样子的? ](http://time.geekbang.org/column/article/86561)》</p>
|
||||
</li>
|
||||
|
||||
## 其余的最佳实践
|
||||
|
||||
除了几个花大篇幅介绍的最佳实践,我们还提到了很多不同的最佳实践。
|
||||
|
||||
## DoD
|
||||
|
||||
完成的定义(DoD),是一个确保合作各方理解一致的实践。它是一个清单,由一个个检查项组成,每个检查项都是实际可检查的。有了 DoD,做事就只有两种状态:完成和未完成。
|
||||
|
||||
<li>**完成的定义,DOD**<br>
|
||||
相关阅读:《[03 | DoD价值:你完成了工作,为什么他们还不满意?](http://time.geekbang.org/column/article/74828)》</li>
|
||||
|
||||
## 站会
|
||||
|
||||
站会,一种轻量级的会议形式,用来同步每天发生的事情。一般来说,只说三件事:昨天做了什么,今天打算做什么,遇到了什么问题。
|
||||
|
||||
<li>**站会**<br>
|
||||
相关阅读:《[22 | 轻量级沟通:你总是在开会吗?](http://time.geekbang.org/column/article/82844) 》</li>
|
||||
|
||||
## 看板
|
||||
|
||||
看板,一种项目管理工具, 将正在进行的工作可视化。通过看板,可以发现团队正在进行工作的很多问题。看板有实体和电子之分,可以根据自己的项目特点进行选择。
|
||||
|
||||
<li>**看板**<br>
|
||||
相关阅读:《[23 | 可视化:一种更为直观的沟通方式 ](http://time.geekbang.org/column/article/83082)》</li>
|
||||
|
||||
## 回顾会议
|
||||
|
||||
回顾会议,是一种复盘实践,让团队成员对一个周期内发生的事情进行回顾。回顾会议一般分为讲事实、找重点和制定行动项三个部分。但在开始回顾之前,会先进行安全检查,确保每个人都能放心大胆地说真话。
|
||||
|
||||
<li>
|
||||
<p>**回顾会议**<br>
|
||||
相关阅读:《[25 | 开发中的问题一再出现,应该怎么办?](http://time.geekbang.org/column/article/83841) 》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**回顾会议中的安全检查**<br>
|
||||
相关阅读:《[答疑解惑 | 持续集成,一条贯穿诸多实践的主线](http://time.geekbang.org/column/article/85049) 》</p>
|
||||
</li>
|
||||
|
||||
## 重构
|
||||
|
||||
重构,是程序员的基本功,把调整代码的动作分解成若干可以单独进行的“重构”小动作,一步步完成。重构的前提是识别代码的坏味道。保证代码行为不变,需要有测试配合,而重构的方向是,重构成模式(Refactoring to Patterns)。重构的过程和编写代码的过程最好结伴而行,最佳实践就是测试驱动开发。
|
||||
|
||||
<li>
|
||||
<p>**重构**<br>
|
||||
相关阅读:《[加餐 | 你真的了解重构吗?](http://time.geekbang.org/column/article/85915)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**在测试驱动开发中重构**<br>
|
||||
相关阅读:《[13 | 先写测试,就是测试驱动开发吗?](http://time.geekbang.org/column/article/78104)》</p>
|
||||
</li>
|
||||
|
||||
## 分支开发
|
||||
|
||||
分支开发模型,是每个团队都要面临的问题。行业中有两种常见的分支模型,一种是基于主干的开发模型,一种是分支开发模型。分支开发符合直觉,却不是最佳实践。主分支开发模型是与其他实践配合最好的模式,但也需要开发者有着良好的开发习惯。如果并行开发多个功能,可以考虑 Feature Toggle 和 Branch by Abstraction。
|
||||
|
||||
<li>
|
||||
<p>**分支开发**<br>
|
||||
相关阅读:《[14 | 大师级程序员的工作秘笈](http://time.geekbang.org/column/article/78507) 》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>**Feature Toggle 和 Branch by Abstraction**<br>
|
||||
相关阅读:《[答疑解惑 | 如何分解一个你不了解的技术任务?](http://time.geekbang.org/column/article/81515) 》</p>
|
||||
</li>
|
||||
|
||||
## Fail Fast
|
||||
|
||||
Fail Fast 是一个重要的编程原则:遇到问题,尽早报错。不要以构建健壮系统为由,兼容很多奇怪的问题,使得 Bug 得以藏身。
|
||||
|
||||
<li>**Fail Fast**<br>
|
||||
相关阅读:《[27 | 尽早暴露问题: 为什么被指责的总是你?](http://time.geekbang.org/column/article/84374) 》</li>
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
152
极客时间专栏/geek/10x程序员工作法/综合运用/总复习 | 重新来“看书”.md
Normal file
152
极客时间专栏/geek/10x程序员工作法/综合运用/总复习 | 重新来“看书”.md
Normal file
@ -0,0 +1,152 @@
|
||||
<audio id="audio" title="总复习 | 重新来“看书”" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/23/21/23525c4e466c9a9714f94df40727cc21.mp3"></audio>
|
||||
|
||||
我们继续复习,在上一讲中,我从最佳实践的角度带领大家回顾了专栏里的一些内容。这一讲,我们换个复习的角度。在专栏进行的过程中,有一些同学注意到我引用了大量的书籍,提出让我把这些书做一个整理。
|
||||
|
||||
Wei 同学提到:
|
||||
|
||||
>
|
||||
有一个小建议: 在每一个主题模块的小结中,把文章中提到的书籍做一个书单方便读者。
|
||||
|
||||
|
||||
刘晓林 同学提到:
|
||||
|
||||
>
|
||||
郑老师在专栏中推荐了很多非常好的书籍作为参考,可否考虑在某一期中,将这些参考书籍整理成一个书单,按照专栏的主题做个小分类,然后每本书简单点评两句作为领读内容。希望在专栏的结束语之前可以看到这个书单。
|
||||
|
||||
|
||||
Y024 同学甚至在留言中帮我总结了一个[小清单](http://time.geekbang.org/column/article/90231),而有人也在豆瓣上做出了一个[豆列](http://www.douban.com/doulist/112766085/),罗列了专栏中提到的一些书。
|
||||
|
||||
在今天这一讲中,我就站在“看书”的视角,带着你进行一次复习。这些书大多是在我个人成长过程中,给我留下深刻印象的。
|
||||
|
||||
我希望你在结束这个专栏学习之后,开启的是另外一段学习历程,用这些书提升自己的水平,夯实自己的基础知识。学习了这个专栏之后,你拥有了一个新的知识结构,再来看这些书就会有一种全新的体验。
|
||||
|
||||
此外,在这次的内容中,我会提到几本专栏中没有提到的书,算是给你在学习路上的一个补充。我还制作了一个[豆列](http://www.douban.com/doulist/113366760/),方便你去找到这些书。
|
||||
|
||||
## 编码实践
|
||||
|
||||
<li>
|
||||
如果你想详细学习如何写好代码,我推荐你去读 Robert Martin 的《[代码整洁之道](http://book.douban.com/subject/4199741/)》(Clean Code),这本书几乎覆盖了如何把代码写好的方方面面。
|
||||
</li>
|
||||
<li>
|
||||
《[实现模式](http://book.douban.com/subject/3324516/)》是一本关于如何写好代码的书,更具体一点是,编写别人能够理解的代码。它的作者 Kent Beck 是许多软件开发实践的开创者。但 Kent Beck 的写作能力一般,他的很多作品被埋没了。只有细细品味,才能体会到 Kent Beck 深厚的功力。
|
||||
</li>
|
||||
<li>
|
||||
我提升自己编码水平的理解是从《[程序设计实践](http://book.douban.com/subject/1173548/)》(The Practice of Programming)这本书开始的,这本书的作者是 Brian Kernighan 和 Rob Pike,这两个人都出身于大名鼎鼎的贝尔实验室,参与过 Unix 的开发。
|
||||
</li>
|
||||
<li>
|
||||
如果你想从日常开发中提升自己的效率,可以读一下《[卓有成效的程序员](http://book.douban.com/subject/3558788/)》。假如你不曾思考过这个问题,这本书会让看到一些不同的工作方式,我也给这本书写过一篇[书评](http://book.douban.com/review/1517237/)。不过,这本书里的技巧太具体了,所以,有一些已经有些过时了。
|
||||
</li>
|
||||
|
||||
## 设计
|
||||
|
||||
<li>
|
||||
SOLID 原则是一种面向对象软件设计原则。早在1995年,Robert Martin 就提出了这些[设计原则的雏形](http://groups.google.com/d/topic/comp.object/WICPDcXAMG8?hl=en),然后在他的《[敏捷软件开发:原则、实践与模式](http://book.douban.com/subject/1140457/)》这本书中,比较完整地阐述了这五个原则,后来,他有把这些原则进一步整理,成了今天的 “SOLID”。有了设计原则做基础,这本书后面讲了设计模式,理解起来就容易多了。虽然书名是关于敏捷的,但这是一本讲设计的书。
|
||||
</li>
|
||||
<li>
|
||||
设计和架构有什么区别?2017年,Robert Martin 出版了《[架构整洁之道](http://book.douban.com/subject/30333919/)》(Clean Architecture),他在其中告诉我们,二者没有区别。所以,这也是一本关于设计的书,给出了 Robert Martin 对设计的最新理解。你可以把它看成《[敏捷软件开发:原则、实践与模式](http://book.douban.com/subject/1140457/)》的修订版。
|
||||
</li>
|
||||
<li>
|
||||
《[设计模式](http://book.douban.com/subject/1052241/)》**不推荐阅读**,它是设计模式的开山之作,但它的起点是 Erich Gamma 的博士论文,其写作风格偏向学术,而且中文版翻译得也很一般。这里将它罗列出来只是因为其历史重要性。如果你想学习设计模式,现在有一些更容易入门的书,比如《[Head First 设计模式](http://book.douban.com/subject/2243615/)》。
|
||||
</li>
|
||||
<li>
|
||||
Martin Fowler 的《[企业应用架构模式](http://book.douban.com/subject/1230559/)》将软件开发当时常见的解决方案汇集成模式,今天看来很多模式已经习以为常,但当年出场可是技惊四座的。从这本书的名字你不难看出,它出版的年代是企业级开发盛行的年代。[Martin Fowler 一直认为这本书没有写完](http://www.martinfowler.com/eaaDev/),希望能够继续更新,但不知道何时能看到这本书的新版。
|
||||
</li>
|
||||
<li>
|
||||
《[Unix 编程艺术](http://book.douban.com/subject/1467587/)》也是一本讲软件设计的书,只不过,它选择的切入点是 Unix 中的设计,从中你可以学到“只做一件事,把它做好”、“文本化”等编程理念,有助于你改善日常的工作。这样的书,也就只有 Eric Raymond 这样沉浸编程几十年的人才能写出来。
|
||||
</li>
|
||||
|
||||
## 工程实践
|
||||
|
||||
<li>
|
||||
Kent Beck 有一本知名的软件工程之作《[解析极限编程](http://book.douban.com/subject/6828074/)》(Extreme Programming Explained),它介绍了一种软件开发方法:极限编程。但更重要的是,今天很多主流的软件开发最佳实践都是从这里出来的。这本书可以理解成诸多最佳工程实践的总纲。
|
||||
</li>
|
||||
<li>
|
||||
Martin Fowler 在1999年写下软件行业的名著《[重构:改善既有代码的设计](http://book.douban.com/subject/4262627/)》(Refactoring: Improving the Design of Existing Code),把重构这个小圈子实践带到了大众视野。2018年底,Martin Fowler 时隔近20年后,又写出了[《重构》第二版](http://book.douban.com/subject/30468597/)。把他对这些年行业发展的新理解融入到重构实践中。重构应该有个目标,这个目标就是“重构成模式”,而这也是一本专门的书:《[重构与模式](http://book.douban.com/subject/5360962/)》(Refactoring to Patterns)。
|
||||
</li>
|
||||
<li>
|
||||
《[测试驱动开发](http://book.douban.com/subject/1230036/)》是 Kent Beck 为世人展示 TDD 做法的一本书。它好的地方需要自己体会,Kent Beck 并没有显式的讲出来,比如:任务分解。
|
||||
</li>
|
||||
<li>
|
||||
Jez Humble 和 Dave Farley 的《[持续交付](http://book.douban.com/subject/6862062/)》(Continuous Delivery)让持续集成再进一步,将生产环境纳入了考量。乔梁,他是《持续交付》这本书的中文版译者,而且在这本书出版近十年后,他自己写了《[持续交付 2.0](http://book.douban.com/subject/30419555/)》,把自己多年来关于持续交付的新理解整理了进去。
|
||||
</li>
|
||||
<li>
|
||||
说到遗留代码和测试,我推荐一本经典的书:Michael Feathers 的《[修改代码的艺术](http://book.douban.com/subject/2248759/)》(Working Effectively with Legacy Code),从它的英文名中,你就不难发现,它就是一本关于遗留代码的书。如果你打算处理遗留代码,也建议你读读这本书。这本书我也写过[书评](http://book.douban.com/review/1226942/),你可以了解一下我对它看法。
|
||||
</li>
|
||||
|
||||
## 领域驱动设计
|
||||
|
||||
<li>
|
||||
Eric Evans 2003年写了《[领域驱动设计](http://book.douban.com/subject/1629512/)》,向行业介绍一下 DDD 这套方法论,立即在行业中引起广泛的关注。但实话说,Eric 在知识传播上的能力着实一般,这本关于 DDD 的开山之作,其写作质量却难以恭维,想要通过它去学好 DDD,是非常困难的。所以,在国外的技术社区中,有很多人是通过各种交流讨论逐渐认识到 DDD 的价值所在,而在国内 ,DDD 几乎没怎么掀起波澜。
|
||||
</li>
|
||||
<li>
|
||||
2013年,在 Eric Evans 出版《领域驱动设计》十年之后,DDD 已经不再是当年吴下阿蒙,有了自己一套比较完整的体系。Vaughn Vernon 将十年的精华重新整理,写了一本《[实现领域驱动设计](http://book.douban.com/subject/25844633/)》,普通技术人员终于有机会看明白 DDD 到底好在哪里了。所以,你会发现,最近几年,国内的技术社区开始出现了大量关于 DDD 的讨论。
|
||||
</li>
|
||||
<li>
|
||||
因为《实现领域驱动设计》实在太厚,Vaughn Vernon 又出手写了一本精华本《[领域驱动设计精粹](http://book.douban.com/subject/30333944/)》,让人可以快速上手 DDD,这本书也是我向其他人推荐学习 DDD 的首选。
|
||||
</li>
|
||||
|
||||
## 产品与需求
|
||||
|
||||
<li>
|
||||
精益创业是 Eric Ries 最早总结出来的。他在很多地方分享他的理念,不断提炼,最终在2011年写成一本同名的书:《[精益创业](http://book.douban.com/subject/10945606/)》。如果说精益创业是理论,《[精益创业实战](http://book.douban.com/subject/20505765/)》这本书则给了你一个操作流程。
|
||||
</li>
|
||||
<li>
|
||||
Mike Cohn 是敏捷理念的一个重要传播者,我们在讲测试金字塔时,提到了他的著作《[Scrum敏捷软件开发](http://book.douban.com/subject/5334585/)》(Succeeding with Agile)。敏捷开发有两大流派:一派是工程实践,另一派是管理实践。如果你对 Scrum 这类管理实践感兴趣,可以读一下这本书。
|
||||
</li>
|
||||
<li>
|
||||
如果你对用户故事这个话题感兴趣,推荐阅读 Mike Cohn 的两本书《[用户故事与敏捷方法](http://book.douban.com/subject/4743056/)》(User Stories Applied)和《[敏捷软件开发实践 估算与计划](http://book.douban.com/subject/26811747/)》(Agile Estimating and Planning)。
|
||||
</li>
|
||||
|
||||
## 开发文化
|
||||
|
||||
<li>
|
||||
软件行业里有一本名著叫《[人月神话](http://book.douban.com/subject/1102259/)》,这算是软件开发领域第一本反思之作。今天,我们讨论的很多词汇都出自这本书,比如,没有银弹、焦油坑等等。虽然这本书出版于1975年,但其中提到的问题,依然困扰着今天的程序员。
|
||||
</li>
|
||||
<li>
|
||||
开源概念的提出者 Eric Raymond,他的《[大教堂与集市](http://book.douban.com/subject/25881855/)》推开了开源大门。今天开源软件已经成为程序员日常工作的一部分,但如果没有 Eric Raymond 这些人的努力,我们还必须与复杂的企业级软件搏斗。了解一下开源的历程,可以帮助你更好地理解今天的幸福。
|
||||
</li>
|
||||
<li>
|
||||
程序员应该如何做,Robert Martin 也写了一本书《[程序员的职业素养](http://book.douban.com/subject/11614538/)》(Clean Coder),其中对大多数程序员最重要的一点建议是,说“不”。
|
||||
</li>
|
||||
|
||||
## 软件开发拾遗
|
||||
|
||||
<li>
|
||||
高德纳的《[计算机程序设计艺术](http://book.douban.com/subject/26681685/)》肯定是一套程序员都知道,但没几个人读完的书。算法的讲解经过几十年已经有了很好的发展,如果学算法,肯定有更好的选择。如果你想看图灵奖获得者如何从根上思考问题,不妨找来这套书来翻翻。
|
||||
</li>
|
||||
<li>
|
||||
《[快速软件开发](http://book.douban.com/subject/3151486/)》(Rapid Development),**不推荐阅读**。在这本书中,作者首次提出了解决集成问题的优秀实践:Daily Build,每日构建。通过这个名字,我们便不难看出它的集成策略,即每天集成一次。它其中很多实践在当时是先进的,但今天看来有些落伍了。如果你只想从中收获一些理念性的东西,可以去读读。
|
||||
</li>
|
||||
<li>
|
||||
《[C 程序设计语言](http://book.douban.com/subject/1139336/)》、《[Unix 编程环境](http://book.douban.com/subject/1033144/)》等出自贝尔实验室大师级程序员之手,他们的书都值得一读,其中的内容今天看来可能有些过时,但他们解决问题的方式和手法却值得慢慢品味。
|
||||
</li>
|
||||
<li>
|
||||
我在讲淘宝技术变迁时,提到了《[淘宝技术这十年](http://book.douban.com/subject/24335672/)》,这本书算不上经典,但可以当做休闲读物。
|
||||
</li>
|
||||
|
||||
## 技术之外
|
||||
|
||||
<li>
|
||||
管理大师彼得·德鲁克有一本经典著作《[卓有成效的管理者](http://book.douban.com/subject/1322025/)》,虽然标题上带着管理者几个字,但在我看来,这是一本告诉我们如何工作的书,每个人都可以读一下。
|
||||
</li>
|
||||
<li>
|
||||
尤瓦尔·赫拉利的《[人类简史](http://book.douban.com/subject/25985021/)》或《[未来简史](http://book.douban.com/subject/26943161/)》,是我第一次学到“大历史观”这个说法,历史不再是一个个单独的历史事件,而是一个有内在逻辑的发展脉络。
|
||||
</li>
|
||||
<li>
|
||||
《[从一到无穷大](http://book.douban.com/subject/1102715/)》是一本著名科普著作,它向我们介绍了20世纪以来的科学进展。作者乔治·伽莫夫既是热宇宙大爆炸模型的提出者,也是生物学上最早提出“遗传密码”模型的人。虽然这本书出版自1947年,但以现在社会的整体科学素养,还是有必要读读这本书的。
|
||||
</li>
|
||||
<li>
|
||||
史蒂芬·柯维(Stephen Richards Covey)的《[高效能人士的七个习惯](http://book.douban.com/subject/26284789/)》,其中的理念我在专栏两个不同的地方提到过,一个是讲以终为始时,那段关于智力创造的论述,另一个是讲优先级时提到的艾森豪威尔矩阵。这本书值得每个人阅读,很多程序员欠缺的就是这些观念性的东西。
|
||||
</li>
|
||||
<li>
|
||||
很多程序员都是科幻小说迷,编程和科幻,这两个都是需要想象力的领域。刘慈欣的《[三体](http://book.douban.com/subject/6518605/)》,不说它给 IT 行业带来的丰富的词汇表吧,作为科幻小说来说,它就是一流的,值得阅读。它会让你仰望星空,打开思维。如果你对科幻小说有兴趣,推荐阅读阿西莫夫的《[银河帝国](http://book.douban.com/subject/26389895/)》系列,这是科幻小说界的扛鼎之作,你会看到,一部出版于1942年的书里就有大数据的身影。
|
||||
</li>
|
||||
<li>
|
||||
对于程序员来说,最好的工作状态就是进入心流,它会让你忘我工作。如果你对心流的概念感兴趣,可以去读米哈里·契克森米哈赖的著作《[心流](http://book.douban.com/subject/27186106/)》,这位作者就是心流概念的提出者。
|
||||
</li>
|
||||
|
||||
好,今天的复习就到这里,你有哪些经典的书可以推荐给这个专栏的同学呢?欢迎在留言区写下分享你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
142
极客时间专栏/geek/10x程序员工作法/综合运用/答疑解惑 | 如何在实际工作中推行新观念?.md
Normal file
142
极客时间专栏/geek/10x程序员工作法/综合运用/答疑解惑 | 如何在实际工作中推行新观念?.md
Normal file
@ -0,0 +1,142 @@
|
||||
<audio id="audio" title="答疑解惑 | 如何在实际工作中推行新观念?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/40/c0/40f03dd93abd997603b2e37aadb6e6c0.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
在整个专栏的最后一个大模块"综合运用"中,我们把前面学到的各种原则和知识穿插在一起应用在了不同的场景中。在这个模块的答疑中,我们也综合汇总一次,把整个专栏中出现的一些有趣却还没有来得及讨论的问题放在一起。
|
||||
|
||||
## 问题1:想要推行 DDD,阻力很大怎么办?
|
||||
|
||||
段启超 同学提到
|
||||
|
||||
>
|
||||
<p>想在公司内推行DDD,阻力真的很大,首先是很多人对DDD没概念,需要一定的学习成本,二是团队间相互隔离,沟通成本很高,起码的通用语言都很难达成。<br>
|
||||
——《[37 | 先做好DDD再谈微服务吧,那只是一种部署形式](http://time.geekbang.org/column/article/89049)》</p>
|
||||
|
||||
|
||||
段启超同学提到的这个问题是一个非常典型的问题,而且,这个问题并不仅仅局限于 DDD。你在一个地方看到了一些好东西:技术、实践,或是想法,然后想把它运用在自己的项目中,希望项目越做越好,越来越顺利。但在实际情况中,想在一个组织内推广一些不一样的东西,都会面临层层阻力。
|
||||
|
||||
我在《[40 | 我们应该如何保持竞争力?](http://time.geekbang.org/column/article/90864)》中提到了一个学习模型,你只要在学习区不断地练习新技能,很快就可以超越同侪。其中的原因是,大部分人只习惯待在舒适区,在舒适区的人能力上的进步非常有限。也因为在舒适区实在太舒适了,走出舒适区会让人产生焦虑,所以,人的内心是惧怕改变的。
|
||||
|
||||
你有良好的愿望,驱动你自己去改变是一件可控的事,有愿意和你一起改变的人是一件幸运的事,但你指望所有人一下子和你走上同一条道路,这是一件几乎不可能的事,即便你是很高层的领导,让所有人与你保持一致也不现实。
|
||||
|
||||
我曾经在一个大公司做过敏捷咨询,这还是由他们顶层领导推动的敏捷转型,但依然是困难重重。那些习惯于待在自己舒适区的人总会找到各种神奇的理由告诉你,他们的情况有多么特殊,这些最佳实践在他们那里是不适用的。
|
||||
|
||||
我们放弃了吗?并没有。我们的做法是,找一个团队来做试点。
|
||||
|
||||
换句话说,我们找到了几个愿意改变的人,把这些最佳实践应用在他们的项目上。在这种情况下,大家的目标是一致的,就是希望让这个项目得到改善。所以,大家自然会想尽一切办法,克服遇到的困难。比如,我们当时的切入点是持续集成。
|
||||
|
||||
<li>
|
||||
他们的代码都在老旧的 ClearCase 上,每个人修改文件要先去竞争文件锁,特别不利于小步提交,所以,我们推动着将 ClearCase 改成了稍微进步一点的 Subversion。(好吧,你能听出来这是一个有些年头的故事。)
|
||||
</li>
|
||||
<li>
|
||||
代码是用 C 语言编写的,在他们的代码规模下,编译时间会很长。于是,我们决定搭建一个分布式构建系统,这需要有很多台电脑。不过,他们的硬件是严格管控的,申请电脑是很困难的,虽然花了很大的力气,但最终我们做到了。
|
||||
</li>
|
||||
<li>
|
||||
以往团队都是几天甚至几周才提交一次代码,我们先将代码提交的要求限定在每人每天至少提交一次,为此,我们专门坐下来与团队成员一起分解任务,将他们理解的大任务拆分成一个一个的小任务。
|
||||
</li>
|
||||
<li>
|
||||
……
|
||||
</li>
|
||||
|
||||
想做事,只需要一个理由就够了,不想做,理由有一万个。劝那些不想改变的人改变是异常耗时而且收效甚微。最好的办法是,**找到愿意和你一起改变的人,做一件具体的事。**
|
||||
|
||||
我们并没有劝说谁去听从我们的想法,只是在一个一个地解决问题。我们花了很长时间,最终建立起了持续集成,看到大屏幕上的绿色标识,我颇为感动。原本只需要一两天搭建的持续集成,在一个复杂组织中,它要花费那么长时间,这也是我从未经历过的。
|
||||
|
||||
当我们把这件事做成之后,其他团队看到了效果,开始纷纷效仿。于是,原本复杂的各种规定也开始纷纷松绑,比如,他们再也不需要为申请电脑发愁了。至于之前质疑我们的人,因为看到了成效,他们的关注点就成了怎么把事能做成。
|
||||
|
||||
后来我听说,他们在组织内部专门建立了一个持续集成中心,为各个团队提供了公共的构建资源,提升了整体的效率。
|
||||
|
||||
Linus Torvalds 曾经说过:“Talk is cheap. Show me the code. ”讲道理很容易,但也难以让人真正的信服。同样,做事很难,但成果摆在那里,让人不得不信服。
|
||||
|
||||
在英文中对这种行为有一个说法叫“Lead by Example”,通常用来形容团队领导以身作则的行事风格。当你寻求改变时,无论你的角色是什么,你都需要扮演好领导者的角色,“Lead by Example”送给你!
|
||||
|
||||
## 问题2:测试怎么写?
|
||||
|
||||
andyXH 同学提到
|
||||
|
||||
>
|
||||
<p>目前对于 TDD 还是处于理解状态,不知道如何真正的在项目工程中使用。因为项目工程往往还有很多其他调用,如rpc,数据库服务,第三方服务,不知道在这个过程如何处理。期待老师在之后文章中讲解。<br>
|
||||
——《[13 | 先写测试,就是测试驱动开发吗?](http://time.geekbang.org/column/article/78104)》</p>
|
||||
|
||||
|
||||
梦倚栏杆 同学提到
|
||||
|
||||
>
|
||||
<p>从数据库或者第三方api查询类内容需要写测试吗?这种测试怎么写呢?如果不需要写,会发现大量展示类系统不需要写测试了,感觉怪怪的。<br>
|
||||
——《[16 | 为什么你的测试不够好?](http://time.geekbang.org/column/article/79494)》</p>
|
||||
|
||||
|
||||
闷骚程序员 同学提到
|
||||
|
||||
>
|
||||
<p>假设我要测试的函数是一个关于tcp的网络发送函数,我想问一下,老师在写类似这样功能的单元测试是怎么实现的?<br>
|
||||
——《[39 | 面对遗留系统,你应该这样做](http://time.geekbang.org/column/article/90231)》</p>
|
||||
|
||||
|
||||
TimFruit 同学提到
|
||||
|
||||
>
|
||||
<p>问个问题,一般web服务依赖数据库,这部分如何做好单元测试?如果去掉数据库,很难测试相应的sql语句。<br>
|
||||
——《[39 | 面对遗留系统,你应该这样做](http://time.geekbang.org/column/article/90231)》</p>
|
||||
|
||||
|
||||
大家看到了,这是一类非常典型的问题。一般来说,如果写的测试是一些业务逻辑的测试,大多数人还知道怎么测,一旦涉及到外部系统、数据库,很多人就不知道该怎么办了。
|
||||
|
||||
我们先来回答一个问题,你要测外部系统的什么?
|
||||
|
||||
你当然会说,我的整个系统都依赖于外部系统,没有了它,我的系统根本运行不起来,不能完成工作啊!但是,我的问题是你要测的是什么?
|
||||
|
||||
我知道很多人一想到外部系统,第一反应是:“我的整段代码都是依赖于外部系统的,因为外部系统不好测,所以,我这段代码都没法测了。”如果你是这样想的,说明你的代码将对外部系统的依赖在业务代码中散播开了,这是一种严重的耦合。**外部系统对你来说,应该只是一个接口。**
|
||||
|
||||
我在《[13 | 先写测试,就是测试驱动开发吗?](http://time.geekbang.org/column/article/78104)》中说过,想写好测试,先要站在可测试的角度思考。假设我同意你关于外部系统不好测的观点,那应该做的是尽量把能测的部分测好。将对外部系统的依赖控制在一个小的范围内。
|
||||
|
||||
一个好的做法就是设计一个接口,让业务代码依赖于这个接口,而第三方依赖都放在这个接口的一个具体实现中。我在《[34 | 你的代码是怎么变混乱的?](http://time.geekbang.org/column/article/87845)》中提到了 SOLID 原则,这种做法就是 **接口隔离原则(ISP)**的体现。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f9/1d/f96a509dcb40199c2c7388c2060fc91d.jpg" alt="">
|
||||
|
||||
如果你能够站在系统集成的角度思考,这个部分就是系统与系统之间的集成点。我在《37 | 先做好DDD再谈微服务吧,那只是一种部署形式》提到了 DDD。在 DDD 的战略设计中,有一个概念叫上下文映射图(Context Map),在不同上下文中集成最常见的一种模式是防腐层(Anti-Corruption Layer,ACL)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/89/ac/89d0199dfa5b3d2c226e23549fbbe5ac.jpg" alt="">
|
||||
|
||||
很多系统在实现时就是缺少了防腐层,造成的结果就是系统耦合极其严重。因为外部服务的任何修改都会造成自己的代码跟着大幅度变动,更极端的情况是,我见过一个网关系统在自己的业务逻辑中直接依赖于第三方服务传过来的 JSON 对象,造成内存资源的极大浪费,网关本身极其不稳定。
|
||||
|
||||
至此,你知道了,**如果有任何外部系统,都要设计防腐层,用接口做隔离。**这样,才能保证你的业务代码是可测的。如果外部系统真的不好测,这种做法将大幅度降低不可测的比例,尽可能提高测试覆盖率。
|
||||
|
||||
我们前面的假设是,外部系统不好测,但真的不好测吗?
|
||||
|
||||
作为 Moco 这个模拟服务器的作者,我肯定是不会同意这个说法。如果你的系统依赖的外部系统是最常见的 REST 服务,那 Moco 就是给这种场景准备的。我给你看一个最简单的[例子](http://github.com/dreamhead/moco/blob/master/moco-doc/usage.md#api-example),这是 Moco 中最简单的用法:
|
||||
|
||||
```
|
||||
|
||||
@Test
|
||||
public void should_response_as_expected() throws Exception {
|
||||
HttpServer server = httpServer(12306);
|
||||
server.response("foo");
|
||||
|
||||
running(server, new Runnable() {
|
||||
@Override
|
||||
public void run() throws IOException {
|
||||
Content content = Request.Get("http://localhost:12306").execute().returnContent();
|
||||
assertThat(content.asString(), is("foo"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
在这个例子里,你设置外部服务的行为,让它按照你的需求返回特定的内容,然后,运行你的服务去访问这个外部服务,它和你访问真实服务效果是一样的。而且,通过 Moco,你还可以模拟出一些真实服务不可能给你做出的效果,比如,连接超时。
|
||||
|
||||
这里给出的是一个用 Java 编写的例子。如果你采用的是其他语言,也可以使用 Moco 的 [Standalone](http://github.com/dreamhead/moco/blob/master/moco-doc/usage.md#standalone) 模式,用 JSON 配置出一个模拟服务器。
|
||||
|
||||
对于数据库的测试,如果你采用的是 [Spring Framework](http://spring.io/projects/spring-framework),它就提供了一套完整的方案,比如:你可以在运行测试时插入一些数据,然后,在测试执行完毕之后,回滚回去,保证测试的可重复性。
|
||||
|
||||
事实上,它对测试的支持已经非常强大了,远不止于数据库。如果你采用的是[ Spring Boot](http://spring.io/projects/spring-boot),对[测试的支持](http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html)就更加完整了,但基础还是 Spring Framework 提供的。如果用到真实的数据库,最好是一套独立的本地数据库,保证环境的可控。
|
||||
|
||||
对于外部服务的测试,简言之,**能模拟的就模拟,能本地的就本地。**如果你的服务没有现成的工具支持,也许就是一个打造新工具的好时机。
|
||||
|
||||
总结一下。**关于外部系统的测试,你可以先通过接口隔离开来,然后,通过模拟服务或本地可控的方式进行测试。**
|
||||
|
||||
好,今天的答疑就到这里,你对这些问题有什么看法呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
115
极客时间专栏/geek/10x程序员工作法/自动化/29 | “懒惰”应该是所有程序员的骄傲.md
Normal file
115
极客时间专栏/geek/10x程序员工作法/自动化/29 | “懒惰”应该是所有程序员的骄傲.md
Normal file
@ -0,0 +1,115 @@
|
||||
<audio id="audio" title="29 | “懒惰”应该是所有程序员的骄傲" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/09/df/09629a1c920a3d9498d13dfe001e11df.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
经过前面几个模块的学习,我们的专栏终于进入到程序员看上去最熟悉的一个主题:自动化。
|
||||
|
||||
每每提及自动化,我就会想起 Perl 语言的发明人 Larry Wall 一个经典叙述:优秀程序员应该有三大美德:懒惰、急躁和傲慢(Laziness, Impatience and hubris)。
|
||||
|
||||
有人甚至为此专门打造了一个三大美德的网站,阐释这个初看起来匪夷所思的说法。
|
||||
|
||||
>
|
||||
懒惰,是一种品质,它会使你花很大力气去规避过度的精力消耗,敦促你写出节省体力的程序,别人也能很好地利用,你还会为此写出完善的文档,以免别人来问问题。
|
||||
|
||||
|
||||
>
|
||||
急躁,是计算机偷懒时,你会感到的一种愤怒。它会促使你写出超越预期的程序,而不只是响应需求。
|
||||
|
||||
|
||||
>
|
||||
傲慢,极度自信,写出(或维护)别人挑不出毛病的程序。
|
||||
|
||||
|
||||
不知道你是否感受到,程序员独有的幽默和透露出的那种骄傲:我做的东西就应该是最好的。
|
||||
|
||||
之所以要从 Larry Wall 的这段话开启“自动化”这个模块,因为只要一说到自动化,我就会情不自禁地联想到“偷懒”这个词。是的,我们程序员的工作,本质上就是打造各种自动化的工具,让人们从各种繁复的工作中解脱出来,让人有机会“偷懒”。
|
||||
|
||||
不过,我也知道,从机器那里偷来的“懒”很快就被更多的工作填满了。但 Larry Wall 的这段话却可以鼓励我们不断地打造出更好的工具。
|
||||
|
||||
作为程序员,你当然知道“自动化”这件事的价值,在日常工作中,也实实在在地践行着打造自动化工具的任务,但很多人对自动化的理解可能有些单薄。今天,我就从一个你可能会忽略的主题开始讨论:不要自动化。
|
||||
|
||||
## 不要自动化
|
||||
|
||||
我先给你讲一个让我印象深刻的“不自动化”的例子。
|
||||
|
||||
之前在 ThoughtWorks 工作时,我们有一项工作是,帮助其他公司启动一些新产品。有一次,我的两个同事被一个公司请去启动一个视频网站的项目。那时候还不像如今的市场,已经由几大视频网站瓜分完毕,当时不少公司看到了视频网站的苗头,觉得自己有机会。这个来请我们的公司也不例外,觉得自己也能分一杯羹。
|
||||
|
||||
两个星期之后,我的两个同事回来了。我们饶有兴趣地去问项目的进展,因为项目启动之后,通常会有后续的开发合作,但结果令我们很意外,这个项目停止了。
|
||||
|
||||
“出了什么状况吗?”我们问。
|
||||
|
||||
“是我们建议用户停掉这个项目的。”他们回答道。
|
||||
|
||||
我们“恨恨地”问他们为什么丢掉了一个这么重要的机会。这两个同事的回答也很直白,他们结合着客户的想法算了一笔账:这个项目需要大量的资金投入,投入规模之大,是超出客户想象的,按照现有的规划投入,这个项目肯定会亏本。要么重新规划,要么取消这个项目。客户认真研究了一番,最终决定取消项目。
|
||||
|
||||
这件事大约发生在10年前,今天我们都看到各大视频网站在烧钱上的投入,以那个公司的实力,想要参加这场比拼,确实还差太多。
|
||||
|
||||
这件事之所以给我留下深刻印象,因为它是我职业生涯中见到的第一个通过“主动取消项目”获取项目成功的案例。
|
||||
|
||||
或许你不能理解我这里所说的“项目成功”。在我看来,**做有价值的事是重要的,这里面的有价值,不仅仅是“做”了什么,通过“不做”节省时间和成本也是有价值的**。我的两个同事阻止了客户的浪费,所以,我将这个项目视为成功。
|
||||
|
||||
对于开发来说,也遵循同样的道理。程序员这个群体技术能力实在太强,做一个技术方案简直是太符合直觉的做法,我们就是忠实地把一个个需求做出来,把“全世界”都自动化了。
|
||||
|
||||
但事实上,这个世界太多的浪费就是做了不该做的东西。在我们的专栏里,我反复地说,我们要多问问题,目的就是为了不做那些不该做的事。
|
||||
|
||||
## 小心 NIH 综合症
|
||||
|
||||
你可以从需求的角度判断哪些工作是可以不做的,但我们也要防止程序员自己“加戏”,我再给你讲一个技术人员普遍存在的问题:NIH 综合症(Not Invented Here Syndrome)。
|
||||
|
||||
NIH 是什么意思?就是有人特别看不上别人做的东西,非要自己做出一套来,原因只是因为那个东西不是我做的,可能存在各种问题。
|
||||
|
||||
这种现象在开源之前尤为流行,很多公司都要做自己的中间件,做自己的数据库封装。虽然很多公司因此有了自己特色的框架,但是因为水平有限,做出来的东西通常极为难用,很多人一边骂,一边还要继续在上面开发。
|
||||
|
||||
开源运动兴起之后,我以为这种现象会好一些,但事实证明,我想多了。
|
||||
|
||||
比如,这种乱象在前端领域也出现了,各种各样的框架,让很多前端程序员哭诉,实在学不动了。再比如,我曾经面试过一个接触 Go 比较早的程序员,他就是恨不得把所有框架都自己写。
|
||||
|
||||
因为他学 Go 的时候,确实框架比较少,但问题是,如今的 Go 已经不是他学习时的那个 Go 了,现在各种框架已经很丰富了,不需要什么都自己做。当时我问他,如果有一天你离开了,公司怎么办呢?实际上,他从来没考虑过这个问题。
|
||||
|
||||
说了这么多,无非就是想说明一件事,写代码之前,先问问自己真的要做吗?能不做就不做,直到你有了足够的理由去做。对应到 Larry Wall 的说法,你要懒惰,花大力气去规避精力消耗。
|
||||
|
||||
## 做好自动化
|
||||
|
||||
说完了不要自动化的部分,再来说说要自动化的部分。
|
||||
|
||||
我还是先从你可能会忽略的问题入手,**你的日常工作是给别人打造自动化,但你自己的工作够自动化吗?**还是问一个更具体的问题吧!如果你写的代码要上线,会经过怎样的过程?
|
||||
|
||||
我先给你看一个极其糟糕的例子。刚开始工作不久,我有一次出差到客户现场。临近下班时,我发现了程序的一个Bug。在那个年代,我们的程序是按照官方推荐做法编写的 EJB(Enterprise JavaBean),今天很多年轻的程序员可能不了解了,它只有部署到应用服务器才能运行。
|
||||
|
||||
我的解决方案就是加上一些打印语句,然后部署到应用服务器上,看输出的结果,再加上另外一些语句,再部署,如此往复。那时我们完全是手工打包上传,每次至少要十几分钟。最终,定位到了问题,只修改了一行代码。但几个小时的时间就这样被无谓的消耗了。
|
||||
|
||||
那之后,我花了很长时间研究怎么做自动化的增量部署,最终让这个过程简化了下来。但这件事对我的影响很大,这是我第一次认识到一个部署过程可能对开发造成的影响,也让我对自动化在开发过程内的应用有了属于自己的认识。
|
||||
|
||||
相比于我刚开始工作那会。现在在工具层面做类似的事已经容易很多了,在后面的内容中,我会结合着具体的场景介绍一下现在的最佳实践。
|
||||
|
||||
## 你要懂得软件设计
|
||||
|
||||
最后,我们再来说说我们的本职工作,给别人打造自动化工具中需要的能力:软件设计。
|
||||
|
||||
软件设计,是很多人既熟悉又陌生的一个词,说熟悉,很多人都知道,做软件要设计,还能顺嘴说出几个设计模式的名字;说陌生,是因为在我的职业生涯中,遇到真正懂软件设计的程序员少之又少。**大多数人都是混淆了设计和实现。**
|
||||
|
||||
举个例子。有一次,我要在两个系统之间做一个连接器,让上游系统向下游系统发消息,或许你一听就知道了,这里需要的是一个消息队列。但实际上,我们需要的能力要比消息队列更丰富一些,比如,要将重复的消息去除。一个同事给我推荐了 Kafka 当作这个连接器的基础,我欣然地接受了。
|
||||
|
||||
不过,在后续设计的讨论中,我们就经常出现话语体系的分歧。我说,这个连接器要有怎样的能力,他会说 Kafka 能够如何如何。究其根因,我在讨论的是设计,而他说的是实现,所以,我们两个很难把问题讨论到一起。
|
||||
|
||||
为什么我会如此看重设计呢?**在软件开发中,其它的东西都是易变的,唯有设计的可变性是你可以控制的。**
|
||||
|
||||
同样以前面的讨论为例,尽管 Kafka 在当下比较火热,但是我不敢保证 Kafka 在未来不会被我换掉。因为就在几年前,消息队列还是传统中间件的强项,现在也渐渐被人淡忘了。
|
||||
|
||||
我不想让我的设计随着某一个技术选型而不断摇摆。如果工作许多年,知识体系只能靠各种新框架新工具支撑,我们做程序员就只剩下疲于奔命了。不懂软件设计,只专注各种工具,其结果一定是被新技术遗弃,这也是很多人经常抱怨 IT 行业变化快的重要原因。
|
||||
|
||||
回到 Larry Wall 的说法上,你要想写出一个别人挑不出毛病的程序,你先要懂得软件设计。幸运的是,软件设计这些年的变化真不大,掌握了软件设计再来看很多框架和工具,学习起来就会容易很多。在这个模块的后半部分,我会与你探讨软件设计的话题,降低自己给自己挖坑的概率。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
Perl 语言的发明人 Larry Wall 曾经说过,优秀程序员应该有三大美德:懒惰、急躁和傲慢(Laziness, Impatience and hubris)。想要成为一个优秀的程序员,就要让机器为自己很好地工作,而这需要对自动化有着很好地理解。
|
||||
|
||||
我们学习自动化,先要知道哪些东西不要自动化,尽最大的努力不做浪费时间的事。一方面,我们要从需求上规避那些没必要做的事;另一方面,我们也从自身防止 NIH 综合症(Not Invented Here Syndrome),争取做一个懒惰的程序员。
|
||||
|
||||
对于要自动化的事,我们需要反思一下,在为别人打造自动化工具的同时,我们自己的工作过程有没有很好地自动化。而如果我们想拥有打造良好的自动化工具,我们需要对软件设计有着充分地理解。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**请谨慎地将工作自动化。**
|
||||
|
||||
最后,我想请你分享一下,学习了本讲之后,你现在是怎样理解自动化的呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
284
极客时间专栏/geek/10x程序员工作法/自动化/30 | 一个好的项目自动化应该是什么样子的?.md
Normal file
284
极客时间专栏/geek/10x程序员工作法/自动化/30 | 一个好的项目自动化应该是什么样子的?.md
Normal file
@ -0,0 +1,284 @@
|
||||
<audio id="audio" title="30 | 一个好的项目自动化应该是什么样子的?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/5c/08/5c91af07f6c9cfc5930750446cacc008.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
进入自动化这个模块,我准备从程序员的日常工作开始。介绍“[迭代0](http://time.geekbang.org/column/article/77294)”时,我提到构建脚本是项目准备的一个重要组成部分,但在那一讲中,我并没有具体说构建脚本长成什么样。
|
||||
|
||||
今天,我们以一个典型的 Java REST 服务为例,介绍一下最基本的构建脚本应该做到什么样子。这里我采用的 Java 技术中最为常见的 Spring Boot 作为基础框架,而构建工具,我选择了 [Gradle](http://gradle.org)。
|
||||
|
||||
估计很多 Java 程序员心中的第一个问题就是,为什么用 Gradle,而不是 Maven?Maven 明明是 Java 社区最经典的构建工具。答案是因为 Maven 不够灵活。
|
||||
|
||||
你可以回想一下,你有多少次用 Maven 实现过特定需求?估计大部分人的答案都是没有。随着持续集成、持续交付的兴起,构建脚本的订制能力会变得越来越重要,Maven 则表现得力有不逮。
|
||||
|
||||
其实,早在2012年,ThoughtWorks 技术雷达就将 Maven 放到了 **暂缓(HOLD)**里面,也就是说,能不用就不用。
|
||||
|
||||
为了配合这次的讲解,我写了一个 Demo,放在了 Github 上。它的功能非常简单:
|
||||
|
||||
- 通过向 /users POST 一个请求,实现用户注册;
|
||||
- 访问 /users,查看已注册的用户。
|
||||
|
||||
如果方便的话,你最好把这个项目 clone 下来,以便参考。这里我主要是讲解自动化要做成什么样子,如果你想了解具体是怎么实现的,可以参考 Demo 里的代码。
|
||||
|
||||
好,我们开始!
|
||||
|
||||
## 基础准备
|
||||
|
||||
先把这个项目从 Github 上 clone 下来。
|
||||
|
||||
```
|
||||
git clone https://github.com/dreamhead/geektime-zero.git
|
||||
|
||||
```
|
||||
|
||||
然后,进入到项目所在的目录中。
|
||||
|
||||
```
|
||||
cd geektime-zero
|
||||
|
||||
```
|
||||
|
||||
当你准备就绪,我们就开始进一步了解这个项目。
|
||||
|
||||
一般我们了解一个项目,都会用用一个 IDE 打开这个项目,这里我推荐使用 IntelliJ IDEA,这是目前行业中最好的Java IDE。自从它的社区版免费之后,它就成为了我向他人推荐的首选。
|
||||
|
||||
我知道,开发工具是除了程序设计语言之外,另外一个容易引起“宗教战争”的话题,如果你喜欢其他的 IDE,那就用你最喜欢的 IDE 打开好了,只不过,需要调整一下构建脚本中的配置。
|
||||
|
||||
怎么打开这个项目呢?我们先用 Gradle 命令生成一个 IDEA 工程。
|
||||
|
||||
```
|
||||
./gradlew idea
|
||||
|
||||
```
|
||||
|
||||
这个命令会生成一个.ipr 文件,这就是 IDEA 的工程文件,用 IDEA 打开即可。
|
||||
|
||||
这里有两点需要说明一下。
|
||||
|
||||
第一,这里用的 gradlew,它是 Gradle 命令的一个封装,它会自动下载一个构建这个项目所需的Gradle,重点是通过这个命令锁定了 Gradle 的版本,避免因为构建脚本的差异,造成“你成功我失败”的情况。
|
||||
|
||||
第二,IDE 的工程是由 Gradle 生成的。很多人会凭借直觉,用 IDE 直接打开。有一些团队的项目里有好多个构建文件,究竟用哪个打开,不去问人是根本不知道的,这对项目的新人是非常不友好的。
|
||||
|
||||
生成的做法与前面 Gradle 封装是类似的,它可以避免因为本地安装不同版本 IDE 造成各种问题。
|
||||
|
||||
另外,因为 IDE 的工程是生成的,如果项目里一旦增加了新的程序库依赖,你只需重新执行一次上面的命令就好了,现在的 IDE 都有很好的自动加载能力,当它检测到工程文件的变化,就会重新加载。
|
||||
|
||||
好,现在你可以用 IDE 打开,我们就可以进一步了解这个项目了。
|
||||
|
||||
## 初见项目
|
||||
|
||||
我们先来了解一点 Gradle 的配置文件,它也是我们做项目自动化的重点。
|
||||
|
||||
<li>
|
||||
build.gradle,它是 Gradle 的配置文件。因为 Gradle 是由 Groovy 编写而成,build.gradle 本质上就是一个 Groovy 的脚本,其中的配置就是 Groovy 代码,这也是 Gradle 能够灵活订制的基础。
|
||||
</li>
|
||||
<li>
|
||||
settings.gradle,这也是一个 Gradle 配置文件,用以支持多模块。如果说一个项目的每个模块都可以有一个 build.gradle,那整个项目只有一个 settings.gradle。
|
||||
</li>
|
||||
|
||||
在 Gradle 里,许多能力都是以插件的形式提供的,比如,前面生成 IDEA 工程就是配置文件中的一句话。
|
||||
|
||||
```
|
||||
apply plugin: 'idea'
|
||||
|
||||
```
|
||||
|
||||
所以,如果你是其他 IDE 的死忠粉,你可以把这句话,换成你喜欢的 IDE。
|
||||
|
||||
(注:这个项目采用[ Lombok ](http://projectlombok.org)简化代码,为了能让代码在你的 IntelliJ IDEA 编译运行,你可以安装 Lombok 插件,然后,在 “Build, Execution, Deployment”-> “Compiler” -> “Annotation Processors“”中,选中 Enable annotation processing)
|
||||
|
||||
好,有了基础知识之后,我们来了解一下代码组织。
|
||||
|
||||
首先是分模块。除非你的代码库规模非常小,否则,分模块几乎是一种必然。一种恰当的划分方式是根据业务划分代码。比如,把用户相关的内容放到一个模块里,把交易订单信息放到一个模块里,把物流信息放到另一个模块里。
|
||||
|
||||
如果你未来打算做微服务,那每一个模块就可以成为一个独立的服务。
|
||||
|
||||
在我们的项目里,我示例性地划分了两个模块:
|
||||
|
||||
- zero-identity,是用户信息的模块;
|
||||
- zero-bootstrap,是多个模块打包成一个可部署应用的模块。
|
||||
|
||||
这两个模块的信息都配置在 settings.gradle 中。
|
||||
|
||||
```
|
||||
include 'zero-bootstrap'
|
||||
include 'zero-identity'
|
||||
|
||||
```
|
||||
|
||||
再来是目录结构。具体要怎么样组织代码,在 Java 世界里已经是一件约定俗成的事情了。
|
||||
|
||||
src/main/java 下放着你的源代码,src/main/resources 下放配置文件,src/test/java 放测试代码。这是约定优于配置(Convention over Configuration)思想的体现。如果你用的工具没有约定,你只能自己定好,让其他人遵守。
|
||||
|
||||
## 检查
|
||||
|
||||
在自动化过程中,一个最基本的工作是检查。检查的工作在我们的项目中通过一个 check 任务来执行。
|
||||
|
||||
```
|
||||
./gradlew check
|
||||
|
||||
```
|
||||
|
||||
这个检查会检查什么呢?这取决于配置。在这个项目里,我们应用了 Java 插件,它就可以编译Java 文件,检查代码是否可以正常编译,运行测试,检查代码是否功能正常等等。但我要求更多。
|
||||
|
||||
讲“迭代0”时,我说过,最基本的代码风格检查要放在构建脚本中,这里我用了 CheckStyle 来做这件事。缺省情况下,你只要应用 Checkstyle 插件即可。
|
||||
|
||||
```
|
||||
apply plugin: 'checkstyle'
|
||||
|
||||
```
|
||||
|
||||
在这个项目里,我做了一些订制,比如,指定某些文件可以不做检查。
|
||||
|
||||
```
|
||||
style.excludePackages = [
|
||||
]
|
||||
|
||||
style.excludeClasses = [
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
测试覆盖率也应该加入到构建脚本中,这里我用了 JaCoCo。同样,缺省情况下,只要应用 JaCoCo 插件即可。
|
||||
|
||||
```
|
||||
apply plugin: 'jacoco'
|
||||
|
||||
```
|
||||
|
||||
我依然是做了一些订制,比如,生成结果的 HTML 报表,还有可以忽略某些文件不做检查。
|
||||
|
||||
```
|
||||
coverage.excludePackages = [
|
||||
]
|
||||
|
||||
coverage.excludeClasses = [
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
这里最特别的地方是,我将测试覆盖率固定在1.0,也就是100%的测试覆盖。这是我做新项目的缺省配置,也是我对团队的要求。
|
||||
|
||||
如果一个新项目,能把这几个检查都通过,腐坏的速度应该就不会那么快了。当然,你也可以根据自己的需要,添加更多的检查。
|
||||
|
||||
## 数据库迁移
|
||||
|
||||
讲“迭代0”时,我还提到了数据库迁移,也就是怎样修改数据库。在示例项目中,我选择的数据库迁移工具是<br>
|
||||
[Flyway](http://flywaydb.org)。
|
||||
|
||||
```
|
||||
plugins {
|
||||
id "org.flywaydb.flyway" version "5.2.4"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
下面先要做一些基本的配置,保证可以连接到数据库。(注:如果你想直接使用这里的配置,可以在本机的 MySQL 数据库上,创建一个 zero 的用户,密码是 geektime,然后,再创建一个 zero_test 的数据库。)
|
||||
|
||||
```
|
||||
flyway {
|
||||
url = 'jdbc:mysql://localhost:3306/zero_test?useUnicode=true&characterEncoding=utf-8&useSSL=false'
|
||||
user = 'zero'
|
||||
password = 'geektime'
|
||||
locations = ["filesystem:$rootDir/gradle/config/migration"]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
那修改数据库会怎么做呢?先添加一个数据库迁移文件,比如,在示例项目中,我创建一个迁移文件(gradle/config/migration/V2019.02.15.07.43__Create_user_table.sql),在其中创建了一个 User 表。
|
||||
|
||||
```
|
||||
CREATE TABLE zero_users(
|
||||
id bigint(20) not null AUTO_INCREMENT,
|
||||
name varchar(100) not null unique,
|
||||
password varchar(100) not null,
|
||||
primary key(id)
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
这里的迁移文件版本,我选择了以时间戳的方式进行命名,还有一种方式是以版本号的方式,比如 V1、V2。
|
||||
|
||||
时间戳命名方式的好处是,不同的人可以同时开发,命名冲突的几率很小,而采用版本号命名的方式,命名冲突的概率会大一些。
|
||||
|
||||
添加好数据库迁移文件之后,只要执行下面这个命令就好:
|
||||
|
||||
```
|
||||
./gradlew flywayMigrate
|
||||
|
||||
```
|
||||
|
||||
这样,对数据库的修改就在数据库里了,你可以打开数据库查看一下。
|
||||
|
||||
## 构建应用
|
||||
|
||||
做好了最基本的检查,数据库也准备就绪,接下来,我们就应该构建我们的应用了。
|
||||
|
||||
首先是生成构建产物,它只要一个命令。
|
||||
|
||||
```
|
||||
./gradlew build
|
||||
|
||||
```
|
||||
|
||||
这个命令会在 zero-bootstrap/build/libs 下生成一个可执行 JAR 包,它就是我们最终的构建产物。此外,build 任务会依赖于 check 任务,也就是说,构建之前,会先对代码进行检查。
|
||||
|
||||
从前 Java 程序只是打出一个可部署的包,然后,部署到应用服务器上。感谢现在基础设施的进步,我们可以省去部署的环节,这个包本身就是一个可执行的。我们可以通过命令执行将 JAR 执行起来。
|
||||
|
||||
```
|
||||
java -jar zero-bootstrap/build/libs/zero-bootstrap-*-boot.jar
|
||||
|
||||
```
|
||||
|
||||
在开发过程中,并不需要每次都将 JAR 包打出来,我们还可以直接通过 Gradle 命令将应用运行起来。
|
||||
|
||||
```
|
||||
./gradlew bootRun
|
||||
|
||||
```
|
||||
|
||||
不过,我估计你更常用的方式是,在 IDE 中找到 Bootstrap 这个入口类,然后,直接运行它。
|
||||
|
||||
既然程序已经运行起来,我们不妨测试一下。我们通过一些工具,比如 Postman 或者 Curl,把下面的内容 POST 到 [http://localhost:8080/users](http://localhost:8080/users)
|
||||
|
||||
```
|
||||
{
|
||||
"username": "foo",
|
||||
"password": "bar"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
然后,通过浏览器访问 [http://localhost:8080/users](http://localhost:8080/users)<br>
|
||||
我们就可以看见我们刚刚注册的这个用户了。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
总结一下今天的内容。今天我们通过一个具体的例子,展示了一个最基本的项目自动化过程,包括了:
|
||||
|
||||
- 生成 IDE 工程;
|
||||
- 编译;
|
||||
- 打包;
|
||||
- 运行测试;
|
||||
- 代码风格检查;
|
||||
- 测试覆盖率;
|
||||
- 数据库迁移;
|
||||
- 运行应用。
|
||||
|
||||
但这就是自动化的全部了吗?显然不是,我这里给出的只是一个最基本的示例。实际上,几乎每个重复的工作或是繁琐的工作,都应该自动化。我们不应该把时间和精力浪费在那些机器可以很好地替我们完成的工作上。
|
||||
|
||||
今天的基础设施已经让我们的自动化工作变得比以往容易了很多,比如,可执行 JAR 包就比从前部署到应用服务器上简化太多了。Gradle 也让订制构建脚本的难度降低了很多。
|
||||
|
||||
这里提到的项目自动化也是持续集成的基础,在持续集成服务上执行的命令,就应该是我们在构建脚本中写好的,比如:
|
||||
|
||||
```
|
||||
./gradlew build
|
||||
|
||||
```
|
||||
|
||||
2011年,我在 InfoQ 上发表了一篇《[软件开发地基](http://www.infoq.cn/article/zy-software-development-foundation)》,讨论的就是一个项目的构建脚本应该是什么样子。虽然其中用到的工具今天已经不再流行,但一些基础内容今天看来,依然是有效的。如果有兴趣,你也可以看一下。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**将你的工作过程自动化。**
|
||||
|
||||
最后,我想请你分享一下,在日常开发工作中,你还把哪些过程自动化了呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
101
极客时间专栏/geek/10x程序员工作法/自动化/31 | 程序员怎么学习运维知识?.md
Normal file
101
极客时间专栏/geek/10x程序员工作法/自动化/31 | 程序员怎么学习运维知识?.md
Normal file
@ -0,0 +1,101 @@
|
||||
<audio id="audio" title="31 | 程序员怎么学习运维知识?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a3/62/a3032560e3d3ccce3938f71109922462.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
在上一讲中,我们讲到了开发过程的自动化,我们的关注点在于如何构建出一个有效的部署包,这个包最终是要上线部署的,那接下来,我们就来关心一下部署的相关工作。
|
||||
|
||||
## 零散的运维知识
|
||||
|
||||
在一些稍具规模的公司,为部署工作设置了一个专有职位,称之为运维。当然,这个岗位的职责远不止部署这一件事,还要维护线上系统的稳定。不过,如果你的团队规模不大,或是项目处于初始阶段,这些工作往往也要由程序员自行完成。
|
||||
|
||||
对于一个程序员来说,了解自己的程序怎么部署上线,是非常重要的。我们既要了解一个软件的逻辑,也要知道它的物理部署。只有这样,出了问题才知道怎么修复。
|
||||
|
||||
更重要的是,我们在设计时,才能尽量规避部署带来的问题。而部署,恰恰也是最适合发挥自动化本领的地方。
|
||||
|
||||
好,即便下定决心准备学习运维相关知识,你准备怎么学呢?我先来问你个问题,提到运维,你会想到什么?
|
||||
|
||||
如果你是一个刚刚步入这个行业的程序员,你或许会想到 [Docker](http://www.docker.com),想到 [Kubernetes](http://kubernetes.io);如果再早一点入行,你或许还会想到 [Chef](http://www.chef.io)、[Puppet](http://puppet.com)、[Ansible](http://www.ansible.com);更早一些入行的话,你会想到 Shell 脚本。没错,这些东西都是与运维相关的。那我就这么一个一个地都学一遍吗?
|
||||
|
||||
就我个人的学习经验而言,如果所有的知识都是零散的,没有一个体系将它们贯穿起来,你原有的知识无法帮助你学习新知识,这种学习方式效率极低,过程也极其痛苦。
|
||||
|
||||
如果是有结构的知识,所谓的学习新知识不过是在学习增量,真正要理解的新东西并不多,学习效率自然会大幅度提高。所以,想学好运维知识,首先你要建立起一个有效的知识体系。
|
||||
|
||||
你可能会问,这些运维知识看上去就是一个一个独立的工具啊?我曾经也为此困惑了许久,虽然我对各个工具已经有了不少的了解,但依然缺乏一个有效的知识体系,将它们贯穿起来,直到我上了一堂课。
|
||||
|
||||
感谢 Odd-e 的[柴锋](https://chaifeng.com/about/),有一次,他给我上了一堂 [DevOps 课](http://chaifeng.com/devops-tech-stack/),他对运维知识的讲解让我茅塞顿开,从此,我的运维知识有了体系。
|
||||
|
||||
准确地说,他的这堂课就是讲给程序员的运维课。今天,我就把这个体系按照我的理解,重新整理一遍分享给你,也算是完成一次[知识输出](http://time.geekbang.org/column/article/84663)。
|
||||
|
||||
好,我们开始!
|
||||
|
||||
## Java 知识体系
|
||||
|
||||
正如我前面所说,学习一个新东西,最好的办法是学习增量,如果能够找到它与已有知识体系的联系,我们就可以把已有知识的理解方式借鉴过去。
|
||||
|
||||
作为程序员,我们其实已经有了一个完善的知识体系,这就是我们对于程序设计的理解,而理解运维的知识体系,刚好可以借鉴这个体系。怎么理解这句话呢?
|
||||
|
||||
以最常见的 Java 开发为例,如果要成为一个合格的 Java 程序员,我应该知道些什么呢?
|
||||
|
||||
首先肯定是 Java 语言,我需要了解 Java 语言的各种语法特性。不过,只了解语法是写不出什么像样程序的,我们还需要掌握核心库。
|
||||
|
||||
对于 Java 来说,就是 JDK 中的各种类,比如,最常见的 String、List、Map 等等。
|
||||
|
||||
理论上来说,掌握了基本的语法和核心库,你就可以开发任何程序了。但在实践中,为了避免重新发明“轮子”,减少不必要的工作量,我们还会用到大量的第三方类库,比如,[Google Guava](http://github.com/google/guava)、[SLF4J ](http://www.slf4j.org)等等。
|
||||
|
||||
除了功能实现,还有一些结构性的代码也会反复出现。比如说,在常见的 REST 服务中,我们要将数据库表和对象映射到一起,要将结果转换成 JSON,要将系统各个组件组装到一起。
|
||||
|
||||
为了减少结构上的代码重复,于是,开发框架出现了,在 Java 中最常见的开发框架就是 [Spring](http://spring.io)。
|
||||
|
||||
至此,你就可以完成基本的代码编写,但这还不够。
|
||||
|
||||
在 Java 中,你不会从底层完成所有事情,比如,虽然你写 REST 服务,但你很少会接触到最底层的 HTTP 实现,因为这些工作由运行时环境承担了。
|
||||
|
||||
我们要做的只是把打好的包部署到这些运行时环境上,在 Java 的世界里,这是 Tomcat、Jetty 之类的容器承担的职责。
|
||||
|
||||
如果你刚刚加入这一行,上来就用 Spring Boot 之类的框架写代码,你可能并没有碰到这样的部署过程,因为这些框架已经把容器封装其中,简化了部署过程。
|
||||
|
||||
Tomcat、Jetty 往往还只是在一台机器上部署,在现实的场景中,一台机器通常是不够用的,我们可能需要的是一个集群。
|
||||
|
||||
你可能会想到用 Nginx 来做一个负载均衡,但如果用原生的 Java 解决方案,这时候就轮到企业级的应用服务器登场了,比如:IBM WebSphere、Oracle WebLogic Server、JBoss Enterprise Application Platform 等等。
|
||||
|
||||
至此,一套完整的 Java 应用解决方案已经部署起来了。但我们知道了这些,和我们运维知识有什么关系呢?我们可以用同样的体系去理解运维知识。
|
||||
|
||||
## 运维知识体系
|
||||
|
||||
首先,要理解运维体系的语言。运维的语言是什么呢?是 Shell,人们最熟悉的应该是 Bash。我们通过操作系统与计算机打交道,但我们无法直接使用操作系统内核,Shell 为我们提供了一个接口,让我们可以访问操作系统内核提供的服务。
|
||||
|
||||
你可能会以为我这里用的是比喻,将 Shell 比喻成语言,但还真不是,Shell 本身就是一门编程语言。绝大多数人都知道 Shell 可以编程,但几乎没有人把 Shell 当成一门编程语言来学习,基本上都是在需要的时候,搜索一下,然后照猫画虎地将代码复制上去。
|
||||
|
||||
这样造成的结果就是,一旦写一个脚本,就要花费大量的时间与语法做斗争,只是为了它能够运行起来。
|
||||
|
||||
有了语言,再来就是核心库了。运维的核心库是什么?就是 Shell 提供的各种 Unix/Linux 的核心命令,比如:ls、cd、ps、grep、kill、cut、sort、uniq 等等,它们几乎与操作系统绑定在一起,随着操作系统一起发布。
|
||||
|
||||
了解了核心的部分,还需要了解一些第三方库,运维知识的第三方库就是那些不属于操作系统核心命令的命令,比如:rsync、curl 等等。
|
||||
|
||||
Java 有框架可用,运维也有框架吗?你可以想一下,Java 的框架提供的是一些通用的能力,在运维工作中,也是有一些通用能力的,比如:在安装某个包之前,要检查一下这个包是否已经安装了;在启动一个服务前,要检查这个服务是否启动了,等等。所以,能够帮我们把这些工作做好的工具,就是我们的运维框架。
|
||||
|
||||
到这里,你应该已经明白了,我在说的运维框架其实就是像 Chef、Puppet、Ansible 之类的配置管理工具。它们做的事就是把那些繁琐的工作按照我们的定义帮我们做好。
|
||||
|
||||
有了对软件环境的基本配置,接下来,就要找一个运行时的环境将软件跑起来了。这时候,我们要了解像虚拟机、Docker 之类的技术,它们帮我们解决的问题就是在单机上的部署。
|
||||
|
||||
一般来说,了解了这些内容,我们就可以构建出一个开发环境或测试环境。除非用户非常少,我们可以在生产环境考虑单机部署,否则,我们迄今为止讨论的各种技术还都是在开发环节的。
|
||||
|
||||
如果我们需要一个集群或是高可用环境,我们还需要进一步了解其他技术,这时候,就轮到一些更复杂的技术登场了,比如,云技术,Amazon AWS、OpenStack,包括国内的阿里云。如果你采用的是 Docker 这样的基础技术,就需要 Kubernetes、Docker Swarm 之类的技术。
|
||||
|
||||
至此,一个相对完整的运维知识体系已经建立起来了,现在你有了一张知识地图,走在运维大陆上,应该不会轻易地迷失了。希望你可以拿着它,继续不断地开疆拓土。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
我们今天的关注点在于,将开发过程产生的构建产物部署起来。部署过程要依赖于运维知识,每个程序员都应该学习运维知识,保证我们对软件的运行有更清楚地认识,而且部署工作是非常适合自动化的。
|
||||
|
||||
但是,对运维工具的学习是非常困难的,因为我们遇到的很多工具是非常零散的,缺乏体系。
|
||||
|
||||
这里,我给你介绍了一个运维的知识体系,这个体系借鉴自 Java 的知识体系,包括了编程语言、核心库、第三方库、开发框架、单机部署和集群部署等诸多方面。我把今天提到的各种技术整理成一个表格列在下面,你可以参考它更好地理解运维知识。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fe/3c/fec8c728c492fyyce018ed1816fe583c.jpg" alt="">
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**有体系地学习运维知识。**
|
||||
|
||||
最后,我想请你分享一下,你还能想到哪些运维知识可以放到这张知识地图上呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
112
极客时间专栏/geek/10x程序员工作法/自动化/32 | 持续交付:有持续集成就够了吗?.md
Normal file
112
极客时间专栏/geek/10x程序员工作法/自动化/32 | 持续交付:有持续集成就够了吗?.md
Normal file
@ -0,0 +1,112 @@
|
||||
<audio id="audio" title="32 | 持续交付:有持续集成就够了吗?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/65/92/655290cae461a36cc45ca1b3e7131b92.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
在前面两讲,我给你讲了开发过程的自动化,将我们的程序打成发布包;然后讲了部署过程的自动化,通过各种工具将发布包部署起来。
|
||||
|
||||
有了这些基础,我们就可以考虑在每次开发完之后,将程序打包部署到环境中。开发完就自动打包,然后自动部署,听起来很像持续集成是不是?
|
||||
|
||||
关于持续集成,我在专栏里已经讲过两次,分别讨论了“[为什么要做持续集成](http://time.geekbang.org/column/article/75977)”和“[怎么做好持续集成](http://time.geekbang.org/column/article/83461)”。但持续集成的讨论只停留在开发环节。
|
||||
|
||||
有了前面两讲的准备,我们就可以把这个过程再进一步延伸。聪明的你或许已经听出来了,这次我要讲的主题是持续交付。
|
||||
|
||||
## 持续交付
|
||||
|
||||
让持续交付这个概念广为人知的是一本书,Jez Humble 和 Dave Farley 的《[持续交付](http://book.douban.com/subject/6862062/)》(Continuous Delivery)。
|
||||
|
||||
前面讲持续集成的发展历史时,我提到了 CruiseControl,它是持续集成服务器的鼻祖。因为持续集成的不断发展,2007年,我的老东家 ThoughtWorks 公司有意以 CruiseControl 为基础,提供企业级服务,于是成立了一个团队,打造一个更好的持续集成服务器,Jez Humble 就是在这个团队中工作的。
|
||||
|
||||
同样在这个团队工作的还有一个人,乔梁,他是《持续交付》这本书的中文版译者,而且在这本书出版近十年后,他自己写了《[持续交付 2.0](http://book.douban.com/subject/30419555/)》,把自己多年来关于持续交付的新理解整理了进去。
|
||||
|
||||
那么,什么叫更好的持续集成服务器呢?当时我的理解很浅薄,只是希望它有更好的界面,更快的构建速度,而 Jez Humble 他们对于这个产品的构想远远超过了我当时的想象,他们将生产环境也纳入了考量。
|
||||
|
||||
什么是持续交付?简言之,它就是一种让软件随时处于可以部署到生产环境的能力。从一个打好的发布包到部署到生产环境可用,这中间还差了什么呢?那就是验证发布包,部署到环境中。
|
||||
|
||||
验证发布包,你或许会想,这不是测试的事吗?这不是已经在持续集成阶段完成的吗?不尽然。在持续集成阶段验证的包,往往缺少了环境的支持。
|
||||
|
||||
因为持续集成的环境往往是单机的,主要强调功能验证,而一些与生产环境相关的测试往往是欠缺的。所以,这里就引出了持续交付中一个需要关注的点:环境。
|
||||
|
||||
一般来说,在构建持续交付的基础设施时,会有下面几个不同的环境。
|
||||
|
||||
<li>
|
||||
持续集成环境,持续集成是持续交付的前提,这个过程主要是执行基本的检查,打出一个可以发布的包。
|
||||
</li>
|
||||
<li>
|
||||
测试环境(Test),这个环境往往是单机的,主要负责功能验证,这里运行的测试基本上都是验收测试级别的,而一般把单元测试和集成测试等执行比较快的测试放到持续集成环境中执行。
|
||||
</li>
|
||||
<li>
|
||||
预生产环境(Staging),这个环境通常与生产环境配置是相同的,比如,负载均衡,集群之类的都要有,只是机器数量上会少一些,主要负责验证部署环境,比如,可以用来发现由多机并发带来的一些问题。
|
||||
</li>
|
||||
<li>
|
||||
生产环境(Production),这就是真实的线上环境了。
|
||||
</li>
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/72/e8/721909eac3d1f75308cee268992275e8.jpg" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ac/3e/ac69b56b11f3c19cd88bd3cf1559af3e.jpg" alt="">
|
||||
|
||||
你也看出来了,每个环境的作用是有差异的,所以,通常不会将所有的验证放在一起执行,而是要分阶段的去执行,一个阶段不通过,是不能进入下一阶段的,这种按照不同阶段组织构建的方式,称之为构建流水线(Build Pipeline)。
|
||||
|
||||
一旦通过了各种验证,就会到构建流水线的最后一个阶段,生产发布。通常来说,生产发布这个过程不是自动化的。我们说,持续交付的关注点在于,让软件具备随时可以发布的能力,但并不等于它要立刻上线,所以,最后这一下,还要由人来决定,到底是不是要上线。
|
||||
|
||||
如果把由人决定的是否上线变成自动化的,就成了另外一个实践:持续部署。但通常人们都会比较谨慎,最后这一下还是由人拍板比较稳妥,所以,持续交付是现在的主流。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/5e/ce/5e7261b528b4eee8f290c0611ee054ce.jpg" alt="">
|
||||
|
||||
至此,我们讨论了持续交付的第一个方面,验证发布包。接下来,我们再来看看另外一个重要部分:部署。
|
||||
|
||||
## DevOps
|
||||
|
||||
早期人们做部署都是自己编写 Shell 脚本完成的,但在上一讲中,我提到的一些工具,比如:Chef、Puppet、Ansible 等等,大幅度地简化了部署脚本的编写。这些工具在业界的兴起与一个概念息息相关:DevOps。
|
||||
|
||||
DevOps 是一种软件交付的理念和方法,目的是增强软件的可靠性。从名字便不难发现,DevOps 是将开发(Development)和运维(Operations)组合在了一起。
|
||||
|
||||
在传统的 IT 公司中,开发和运维往往是井水不犯河水的两个职位,甚至是两个不同的部门,由此带来了很多问题,比如,开发人员修改了配置,但没有通知运维,造成了新代码不能运行。
|
||||
|
||||
DevOps 提倡的就是将二者融合起来,打破壁垒。2009年,Flickr 做了一个分享《[每天部署10次](http://www.slideshare.net/jallspaw/10-deploys-per-day-dev-and-ops-cooperation-at-flickr)》,整个行业受到了极大的冲击,从此 DevOps 运动风起云涌。DevOps 给这个行业带来的理念冲击是很大的,想要做好 DevOps,需要在文化、流程和工具等诸多方面不断改善。
|
||||
|
||||
但对我们程序员的日常工作来说,最直接的影响是体现在各种工具上。Chef、Puppet、Ansible 这些工具基本上都是在那之后,兴起或广为人知的。
|
||||
|
||||
在上一讲中,我给你讲了这些配置管理工具在运维体系中的角色,它们相当于提供了一个框架。但对于行业来说,这些工具给行业带来了部署的规范。
|
||||
|
||||
从前写 Shell 的方式,那就是各村有各村的高招。你在 A 公司学会的东西,到 B 公司是没法用的,甚至在很多人的印象中,部署这件事就应该属于某个特定的场景,换台机器脚本都要重新写过。这种形势就如同 Spring 出现之前,几乎所有的公司都在写自己的框架一样。
|
||||
|
||||
Spring 的出现打破这一切,让你的 Java 技能从归属于一个公司变成了行业通用。同样,运维体系中这些配置工具也起到了这样的作用。它们甚至带来了一个新的理念:基础设施即代码(Infrastructure as code),将计算机的管理与配置变成了代码。
|
||||
|
||||
一旦成了代码,就可以到处运行,可以版本管理,那种强烈依赖于“英雄”的机器配置工作终于可以平民化了。这在从前是想都不敢想的事。
|
||||
|
||||
这些工具采用的都是声明式接口,从 Shell 那种描述怎么做,到描述做什么,抽象程度上了一个台阶,让开发者或系统管理员从琐碎的细节中脱身,把更多的注意力用于思考应该把机器配置成什么样子。
|
||||
|
||||
如果这些配置管理工具还需要有一台具体的机器去部署,放在持续交付中,也只能扮演一个部署环境的次要角色,那 Docker 的出现则彻底地改变最终交付物。
|
||||
|
||||
我在上一讲说过,Docker 相当于是一台机器。Docker 非常好的一点是,它是一台可以用代码描述的机器,在 Docker 配置文件中描述的就是我们预期中那台机器的样子,然后,生成镜像,部署到具体的机器上。
|
||||
|
||||
既然是要描述机器的样子,我们就可以在 Docker 的配置文件中使用前面提到的配置工具,如此一来,我们的配置工作就简单了。那既然我们在讨论持续交付,还可以通过配置工具将我们的发布包也部署到最终的镜像中。这样一来,最终生成的镜像就是包含了我们自己应用的镜像。
|
||||
|
||||
你或许已经知道我要说什么了,结合着这些工具,我们的生成产物就由一个发布包变成了一个 Docker 镜像。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e1/54/e1f55e949e02faef89dbaf6cfae95254.jpg" alt="" title="交付物的变迁">
|
||||
|
||||
Docker 在开发中扮演的角色,是一个构建在我们应用与具体机器之间的中间层。对应用而言,它就是机器,但对机器而言,它只是一个可以部署的镜像,统一了各种应用千奇百怪的部署差异,让部署本身变得更简单了。
|
||||
|
||||
到这里,我给你介绍了持续交付中最基础的东西,让你有了一个基本的框架理解持续交付。当然,如果你关注这个领域,就会发现,它早已超出了一个实践的层面,有更多组织、文化的内容。
|
||||
|
||||
Jez Humble 写《持续交付》时就已经想到如此完整的一个体系,受限于当时的环境,书中介绍的自动化还比较宽泛,不像今天有更加完善的工具支撑。
|
||||
|
||||
只可惜,虽然当时他对持续交付的理解已经到达如此高度,他所在的团队也做出了一个颇具先锋气质的持续交付工具,但是受限于产品推广策略,这个工具并没有成为主流,即便后来开源了。(如果你想了解一下[这个工具](http://www.gocd.org),可以点击链接去查看)
|
||||
|
||||
## 总结时刻
|
||||
|
||||
总结一下今天的内容。我们延续了前两讲的内容,在准备好发布包和部署的基础设施之后,我们顺着持续集成的思路,将部署过程也加了进来,这就是持续交付。
|
||||
|
||||
持续交付,是一种让软件随时处于可以部署到生产环境的能力。让软件具备部署到生产环境的能力,这里面有两个关键点:验证发布包和部署。
|
||||
|
||||
验证发布包,不仅是功能上的验证,还包括与环境结合在一起的验证。所以,通常会用几个不同的环境验证,每一个环境都是一个单独的阶段,一个阶段不通过,是不能进入下一阶段的,这种按照不同阶段组织构建的方式,称之为构建流水线(Build Pipeline)。
|
||||
|
||||
与部署相关的一个重要概念是 DevOps,也就是将开发和运维结合起来。DevOps 包含了很多方面,对程序员最直接的影响是各种工具的发展,这些工具推动着另一个理念的发展:基础设施即代码(Infrastructure as code) 。有赖于这些工具的发展,今天定义交付,就不再是一个发布包,而是一个可以部署的镜像。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**将部署纳入开发的考量。**
|
||||
|
||||
最后,我想请你分享一下,你对持续交付的理解是什么样的呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
161
极客时间专栏/geek/10x程序员工作法/自动化/33 | 如何做好验收测试?.md
Normal file
161
极客时间专栏/geek/10x程序员工作法/自动化/33 | 如何做好验收测试?.md
Normal file
@ -0,0 +1,161 @@
|
||||
<audio id="audio" title="33 | 如何做好验收测试?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/c3/b0/c363c556a0f8096641e463a34029afb0.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
经过前面三讲的讲解,相信你对一个项目自动化应该是什么样子有了一个相对完整的认识:程序员写好程序,用构建脚本执行检查,提交代码,在服务器上打出一个发布镜像,部署到各个环境进行检查,检查好了,随时可以发布上线。
|
||||
|
||||
我们在前面的内容中只说了该检查,但怎么检查呢?这就轮到测试发挥作用了。
|
||||
|
||||
在“任务分解”的模块,我给你完整地介绍了一下开发者测试的概念,但在那个部分讲解的测试基本上还停留在单元测试和集成测试的范畴。对于整个应用该怎么测,我们并没有仔细讨论。
|
||||
|
||||
今天我们就来说说应用测试的话题:验收测试。
|
||||
|
||||
## 验收测试
|
||||
|
||||
验收测试(Acceptance Testing),是确认应用是否满足设计规范的测试。这种测试往往是站在用户的角度,看整个应用能否满足业务需求。
|
||||
|
||||
从名字上来看,验收应该是业务人员的事,但业务人员能做的最多只是验收,测试是他们无论如何也不太可能做仔细的。
|
||||
|
||||
所以,验收测试这件事,往往还是由技术团队自己完成,而且在很多公司,这就是测试人员的事。
|
||||
|
||||
时至今日,很多测试团队都拥有自动化的能力。所以,自动化验收测试自然是重点考虑对象。今天,我们的重点就是怎么做好自动化的验收测试。
|
||||
|
||||
其实,验收测试应该是人们最早想到的自动化测试,早在单元测试还不流行的年代,人们就开始了对自动化验收测试的探索。有不少团队甚至还构建了自己的框架,只不过,这种框架不是我们今天理解的测试框架,而是针对着一个应用的测试框架。
|
||||
|
||||
比如,我曾经见过有人为通信软件构建的一套完整的测试框架,甚至构建了属于自己的语言,测试人员的工作就是用这种特定的语言,对系统进行设置、运行,看它是否满足自己的预期。
|
||||
|
||||
相对来说,他们的这种做法已经非常成熟了。但更多团队的现实情况是,自己把对应用的访问做一个简单的封装,然后,写测试就是编写代码调用这个封装。
|
||||
|
||||
让验收测试从各自为战的混乱中逐渐有了体系的是行为驱动开发(Behavior Driven Development)这个概念的诞生,也就是很多人知道的 BDD。
|
||||
|
||||
## 行为驱动开发
|
||||
|
||||
行为驱动开发中的行为,指的是业务行为。BDD 希望促进业务人员与开发团队之间的协作,换句话说,**如果你想做 BDD,就应该用业务语言进行描述。**
|
||||
|
||||
这与我们传统上理解的系统测试有着很大的差别,传统意义上的系统测试是站在开发团队的角度,所以,更多的是在描述系统与外部系统之间的交互,用的都是计算机的术语。
|
||||
|
||||
而 BDD 则让我们换了一个视角,用业务语言做系统测试,所以,它是一个更高级别的抽象。
|
||||
|
||||
BDD 是2003年由 Dan North 提出了来的。Dan North 不仅仅提出了概念,为了践行他的想法,他还创造了第一个 BDD 的框架:[JBehave](http://jbehave.org)。后来又改写出基于 [Ruby](http://www.ruby-lang.org/en/) 的版本[ RBehave](http://dannorth.net/2007/06/17/introducing-rbehave/),这个项目后来被并到 [RSpec](http://rspec.info) 中。
|
||||
|
||||
今天最流行的 BDD 框架应该是 [Cucumber](http://cucumber.io),它的作者就是 RSpec 的作者之一 Aslak Hellesøy。
|
||||
|
||||
Cucunber 从最开始的 Ruby BDD 框架发展成今天支持很多不同程序设计语言的 BDD 测试框架,比如,常见的 Java、JavaScript、PHP 等等。
|
||||
|
||||
BDD 框架给我们最直观的感受就是它给我们提供的一套语言体系,供我们描述应用的行为,下面是一个例子,它描述了一个交易场景,应用需要根据交易结果判定是否要发出警告。你可以感受一下:
|
||||
|
||||
```
|
||||
Scenario: trader is not alerted below threshold
|
||||
|
||||
Given a stock of symbol STK1 and a threshold of 10.0
|
||||
When the stock is traded at 5.0
|
||||
Then the alert status should be OFF
|
||||
|
||||
Scenario: trader is alerted above threshold
|
||||
|
||||
Given a stock of symbol STK1 and a threshold of 10.0
|
||||
When the stock is traded at 11.0
|
||||
Then the alert status should be ON
|
||||
|
||||
```
|
||||
|
||||
我们在这里的关注点是这个例子的样子,首先是描述格式:“Given…When…Then”,这个结构对应着这个测试用例中的执行步骤。Given 表示的一个假设前提,When 表示具体的操作,Then 则对应着这个用例要验证的结果。
|
||||
|
||||
还记得我们讲过的测试结构吗?前置准备、执行、断言和清理,这刚好与“Given…When…Then”做一个对应,Given 对应前置条件,When 对应执行,Then 则对应着断言。至于清理,它会做一些资源释放,属于实现层面的内容,在业务层面上意义不大。
|
||||
|
||||
了解了格式,我们还要关心一下内容。你会看到这里描述的行为都是站在业务的角度进行叙述的,而且 Given、When、Then 都是独立的,可以自由组合。也就是说,一旦基础框架搭好了,我们就可以用这些组成块来编写新的测试用例,甚至可以不需要技术人员参与。
|
||||
|
||||
不过,这些内容都是站在业务角度的描述,没有任何实现的内容,那实现的内容放在哪呢?
|
||||
|
||||
我们还需要定义一个胶水层,把测试用例与实现联系起来的胶水层,在 Cucumber 的术语里,称之为步骤定义(Step Definition)。这里我也给出了一个例子,你可以参考一下:
|
||||
|
||||
```
|
||||
public class TraderSteps implements En {
|
||||
private Stock stock;
|
||||
|
||||
public TraderSteps() {
|
||||
Given("^a stock of symbol {string} and a threshold of {double}", (String symbol, double threshold) -> {
|
||||
stock = new Stock(symbol, threshold);
|
||||
});
|
||||
|
||||
When("^the stock is traded at {double}$", (double price) -> {
|
||||
stock.tradeAt(price);
|
||||
});
|
||||
|
||||
Then("the alert status should be {string}", (String status) -> {
|
||||
assertThat(stock.getStatus().name()).isEqualTo(status);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 写好验收测试用例
|
||||
|
||||
有了对 BDD 框架的基本了解,接下来的问题就是,怎么用好 BDD 框架。我们举个简单的例子,如果我们要写一个登录的测试用例,你会怎么写呢?
|
||||
|
||||
有一种写法是这样的,为了方便叙述,我把它转成了中文描述的格式,Cucumber 本身是支持本地化的,你可以使用自己熟悉的语言编写用例:
|
||||
|
||||
```
|
||||
假定 张三是一个注册用户,其用户名密码分别是 zhangsan 和 zspassword
|
||||
当 在用户名输入框里输入 zhangsan,在密码输入框里输入 zspassword
|
||||
并且 点击登录
|
||||
那么 张三将登录成功
|
||||
|
||||
```
|
||||
|
||||
这个用例怎么样呢?或许你会说,这个用例挺好的。如果你这么想,说明你是站在程序员的视角。我在前面已经说过了,BDD 需要站在业务的角度,而这个例子完全是站在实现的角度。
|
||||
|
||||
如果登录方式有所调整,用户输完用户名密码自动登录,不需要点击,那这个用例是不是需要改呢?下面我换了一种方式描述,你再感受一下:
|
||||
|
||||
```
|
||||
假定 张三是一个注册用户,其用户名密码是分别是 zhangsan 和 zspassword
|
||||
当 用户以用户名 zhangsan 和密码 zspassword 登录
|
||||
那么 张三将登录成功
|
||||
|
||||
```
|
||||
|
||||
这是一个站在业务视角的描述,除非做业务的调整,不用用户名密码登录了,否则,这个用例不需要改变,即便实现的具体方式调整了,需要改变的也是具体的步骤定义。
|
||||
|
||||
所以,**想写好 BDD 的测试用例,关键点在用业务视角描述。**
|
||||
|
||||
编写验收测试用例的步骤定义时,还有一个人们经常忽略的点:业务测试的模型。很多人的第一直觉是,一个测试要啥模型?还记得我们讲好测试应该具备的属性吗?其中一点就是 Professional,专业性。想要写好测试,同写好代码是一样的,一个好的模型是不可或缺的。
|
||||
|
||||
这方面一个可以参考的例子是,做 Web 测试常用的一个模型:[Page Object](http://martinfowler.com/bliki/PageObject.html)。它把对页面的访问封装了起来,即便你在写的是步骤定义,你也不应该在代码中直接操作 HTML 元素,而是应该访问不同的页面对象。
|
||||
|
||||
以前面的登录为例,我们可能会定义这样的页面对象:
|
||||
|
||||
```
|
||||
public class LoginPage {
|
||||
public boolean login(String name, String password) {
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
如此一来,在步骤定义中,你就不必关心具体怎么定位到输入框,会让代码的抽象程度得到提升。
|
||||
|
||||
当然,这只是一个参考,面对你自己的应用时,你要考虑构建自己的业务测试模型。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
今天我和你分享了自动化验收测试的话题。验收测试(Acceptance Testing),是确认应用是否满足设计规范的测试。验收测试是技术交付必经的环节,只不过,各个团队实践水平有所差异,有的靠人工,有的用简单自动化,一些做得比较好的团队才有完善的自动化。
|
||||
|
||||
自动化验收测试也是一个逐步发展的过程,从最开始的各自为战,到后来逐渐形成了一个完整的自动化验收测试的体系。
|
||||
|
||||
今天,我以行为驱动开发(Behavior Driven Development,BDD)为核心,给你介绍了一种自动化验收测试的方式。这个在2003年由 Dan North 提出的概念已经成为了一套比较完善的体系,尤其是一些 BDD 框架的发展,让人们可以自己的项目中实践 BDD。
|
||||
|
||||
我以 Cucumber 为样例,给你介绍了 BDD 验收用例的编写方式,你知道“Given…When…Then”的基本格式,也知道了要编写步骤定义(Step Definition)将测试用例与实现连接起来。
|
||||
|
||||
我还给你介绍了编写 BDD 测试用例的最佳实践:用业务的视角描述测试用例。在编写步骤定义时,还要考虑设计自己的业务测试模型。
|
||||
|
||||
其实,验收测试的方法不止 BDD 一种,像[实例化需求](http://en.wikipedia.org/wiki/Specification_by_example)(Specification by Example,SbE)也是一种常见的方法。验收测试框架也不止 BDD 框架一类,像 Concordion 这样的工具甚至可以让你把一个验收用例写成一个完整的参考文档。
|
||||
|
||||
如果你有兴趣,可以深入地去了解。无论哪种做法,都是为了缩短业务人员与开发团队之间的距离,让开发变得更加高效。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**将验收测试自动化。**
|
||||
|
||||
最后,我想请你分享一下,你的团队是怎么做验收测试的呢?欢迎在留言区分享你的做法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
125
极客时间专栏/geek/10x程序员工作法/自动化/34 | 你的代码是怎么变混乱的?.md
Normal file
125
极客时间专栏/geek/10x程序员工作法/自动化/34 | 你的代码是怎么变混乱的?.md
Normal file
@ -0,0 +1,125 @@
|
||||
<audio id="audio" title="34 | 你的代码是怎么变混乱的?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/99/06/995f63a54ba2642d3886d0ad7edf4206.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
前面几讲,我给你讲了开发过程的各种自动化,从构建、验证到上线部署,这些内容都是站在软件外部看的。从这一讲开始,我准备带领大家进入到软件内部。今天的话题就从写代码开始说起。
|
||||
|
||||
## 逐步腐化的代码
|
||||
|
||||
代码是程序员改造世界最直接的武器,却也是程序员抱怨最多的东西。为什么程序员会对代码如此不满呢?
|
||||
|
||||
你会抱怨写一段代码吗?你肯定不会,毕竟这是你养家糊口的本领,最基本的职业素养我们还是有的。那抱怨的是什么呢?是维护一段代码。
|
||||
|
||||
为什么维护代码那么难?因为通常来说,你维护的这段代码是有一定年龄的,所以,你总会抱怨前人没有好好写这段代码。
|
||||
|
||||
好,现在你拿到了一个新的需求,要在这段代码上添加一个新功能,你会怎么做呢?很多人的做法是,在原有的代码上添加一段新的逻辑,然后提交完工。
|
||||
|
||||
发现问题了吗?**你只是低着头完成了一项任务,而代码却变得更糟糕了。**如果我问你,你为什么这么做?你的答案可能是:“这段代码都这样了,我不敢乱改。”或者是:“之前就是这么写的,我只是遵循别人的风格在写。”
|
||||
|
||||
行业里有一个段子,**对程序员最好的惩罚是让他维护自己三个月前写的代码。**你一不小心就成了自己最讨厌的人。
|
||||
|
||||
从前,我也认为很多程序员是不负责任,一开始就没有把代码写好,后来,我才知道很多代码其实只是每次加一点。你要知道,一个产品一旦有了生命力,它就会长期存在下去,代码也就随着时间逐渐腐烂了。
|
||||
|
||||
而几乎每个程序员的理由都是一样的,他们也很委屈,因为他们只改了一点点。
|
||||
|
||||
这样的问题有解吗?一个解决方案自然就是我们前面说过的重构,但重构的前提是,你得知道代码驶向何方。对于这个问题,更好的答案是,你需要了解一些软件设计的知识。
|
||||
|
||||
## SOLID 原则
|
||||
|
||||
提到软件设计,大部分程序员都知道一个说法“高内聚、低耦合”,但这个说法如同“期待世界和平”一样,虽然没错,但并不能很好地指导我们的具体工作。
|
||||
|
||||
人们尝试着用各种方法拆解这个高远的目标,而比较能落地的一种做法就是 Robert Martin 提出的面向对象设计原则:[SOLID](http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod),这其实是五个设计原则的缩写,分别是
|
||||
|
||||
- 单一职责原则(Single responsibility principle,SRP)
|
||||
- 开放封闭原则(Open–closed principle,OCP)
|
||||
- Liskov 替换原则(Liskov substitution principle,LSP)
|
||||
- 接口隔离原则(Interface segregation principle,ISP)
|
||||
- 依赖倒置原则(Dependency inversion principle,DIP)
|
||||
|
||||
早在1995年,Robert Martin 就提出了[这些设计原则的雏形](http://groups.google.com/forum/?hl=en#!topic/comp.object/WICPDcXAMG8),然后在他的《[敏捷软件开发:原则、实践与模式](http://groups.google.com/forum/?hl=en#!topic/comp.object/WICPDcXAMG8)》这本书中,比较完整地阐述了这五个原则。后来,他有把这些原则进一步整理,成了今天的 “SOLID”。
|
||||
|
||||
学习这些设计原则有什么用呢?
|
||||
|
||||
今天的程序员学习软件设计多半是从设计模式入门的,但不知道你是否有这样的感觉,在学习设计模式的时候,有几个设计模式看上去如此相像,如果不是精心比较,你很难记得住它们之间的细微差别。
|
||||
|
||||
而且,真正到了工作中,你还能想得起来的可能就剩下几个最简单的模式了,比如工厂方法、观察者等等。
|
||||
|
||||
另外,有人常常“为赋新词强说愁”,硬去使用设计模式,反而会让代码变得更加复杂了。你会有一种错觉,我是不是学了一个假的设计模式,人人都说好的东西,我怎么就感受不到呢?
|
||||
|
||||
初学设计模式时,我真的就被这个问题困扰了好久。直到我看到了 Robert Martin 的《敏捷软件开发:原则、实践与模式》。这是一本被名字糟蹋了的好书。
|
||||
|
||||
这本书出版之际,敏捷软件开发运动正风起云涌,Robert Martin 也不能免俗地蹭了热点,将“敏捷”挂到了书名里。其实,这是一本讲软件设计的书。
|
||||
|
||||
当我看到了 SOLID 的五个原则之后,我终于想明白了,原来我追求的方向错了。如果说设计模式是“术”,设计原则才是“道”。设计模式并不能帮你建立起知识体系,而设计原则可以。
|
||||
|
||||
当我不能理解“道”的时候,“术”只能死记硬背,效果必然是不佳的。想通这些之后,我大大方方地放弃了对于设计模式的追求,只是按照设计原则来写代码,结果是,我反而是时常能重构出符合某个设计模式的代码。至于具体模式的名字,如果不是有意识地去找,我已经记不住了。
|
||||
|
||||
当然,我并不是说设计模式不重要,之所以我能够用设计原则来写代码,前提条件是,我曾经在设计模式上下过很多功夫。
|
||||
|
||||
道和术,是每个程序员都要有的功夫,在“术”上下过功夫,才会知道“道”的价值,“道”可以帮你建立更完整的知识体系,不必在“术”的低层次上不断徘徊。
|
||||
|
||||
## 单一职责原则
|
||||
|
||||
好,下面我就单拿 SOLID 中单一职责原则稍微展开讲一下,虽然这个原则听上去是最简单的,但也有很多误解存在。
|
||||
|
||||
首先,什么是单一职责原则呢?如果读过《敏捷软件开发:原则、实践与模式》,你对单一职责的理解应该是,一个模块应该仅有一个修改的原因。
|
||||
|
||||
2017年,Robert Martin 出版了《架构整洁之道》(Clean Architecture),他把单一职责原则的定义修改成“一个模块应该仅对一类 actor 负责”,这里的 actor 可以理解为对系统有共同需求的人。
|
||||
|
||||
不管是哪个定义,初读起来,都不是那么好理解。我举个例子,你就知道了。我这里就用 Robert Martin 自己给出的例子:在一个工资管理系统中,有个 Employee 类,它里面有三个方法:
|
||||
|
||||
- calculatePay(),计算工资,这是财务部门关心的。
|
||||
- reportHours(),统计工作时长,这是人力部门关心的。
|
||||
- save(),保存数据,这是技术部门关心的。
|
||||
|
||||
之所以三个方法在一个类里面,因为它们的某些行为是类似的,比如计算工资和统计工作时长都需要计算正常工作时间,为了避免重复,团队引入了新的方法:regularHours()。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/aa/50/aabd9d105df157db95739fb628c00250.jpg" alt="">
|
||||
|
||||
接下来,财务部门要修改正常工作时间的统计方法,但人力部门不需要修改。负责修改的程序员只看到了 calculatePay() 调用了 regularHours(),他完成了他的工作,财务部门验收通过。但上线运行之后,人力部门产生了错误的报表。
|
||||
|
||||
这是一个真实的案例,最终因为这个错误,给公司造成了数百万的损失。
|
||||
|
||||
如果你问程序员,为什么要把 calculatePay() 和 reportHours()放在一个类里,程序员会告诉你,因为它们都用到了 Employee 这个类的数据。
|
||||
|
||||
但是,它们是在为不同的 actor 服务,所以,任何一个 actor 有了新的需求,这个类都需要改,它也就很容易就成为修改的重灾区。
|
||||
|
||||
更关键的是,很快它就会复杂到没人知道一共有哪些模块与它相关,改起来会影响到谁,程序员也就越发不愿意维护这段代码了。
|
||||
|
||||
我在专栏“[开篇词](http://time.geekbang.org/column/article/73980)”里提到过,人的大脑容量有限,太复杂的东西理解不了。所以,我们唯一能做的就是把复杂的事情变简单。
|
||||
|
||||
我在“任务分解”模块中不断强调把事情拆小,同样的道理在写代码中也适用。单一职责原则就是给了你一个指导原则,可以按照不同的 actor 分解代码。
|
||||
|
||||
上面这个问题,Robert Martin 给了一个解决方案,就是按照不同的 actor 将类分解,我把分解的结果的类图附在了下面:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ae/bb/ae09f384694f4c7a503da64f7ab34cbb.jpg" alt="">
|
||||
|
||||
## 编写短函数
|
||||
|
||||
好,你已经初步了解了单一职责原则,但还有一点值得注意。我先来问个问题,你觉得一个函数多长是合适的?
|
||||
|
||||
曾经有人自豪地向我炫耀,他对代码要求很高,超过50行的函数绝对要处理掉。
|
||||
|
||||
我在专栏中一直强调“小”的价值,能看到多小,就可以在多细的粒度上工作。单一职责这件事举个例子很容易,但在真实的工作场景中,**你能看到一个模块在为多少 actor 服务,就完全取决于你的分解能力了。**
|
||||
|
||||
回到前面的问题上,就我自己的习惯而言,通常的函数都在十行以内,如果是表达能力很强的语言,比如 Ruby,函数会更短。
|
||||
|
||||
所以,你可想而知我听到“把50行代码归为小函数”时的心情。我知道,“函数长短”又是一个非常容易引起争论的话题,不同的人对于这个问题的答案,取决于他看问题的粒度。
|
||||
|
||||
所以,不讨论前提条件,只谈论函数的长短,其实是没有意义的。
|
||||
|
||||
单一职责原则可以用在不同的层面,写一个类,你可以问问这些方法是不是为一类 actor 服务;写方法时,你可以问问这些代码是不是在一个层面上;甚至一个服务,也需要从业务上考虑一下,它在提供是否一类的服务。总之,你看到的粒度越细,也就越能发现问题。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
今天,我讲的内容是软件设计,很多代码的问题就是因为对设计思考得不足导致的。
|
||||
|
||||
许多程序员学习设计是从设计模式起步的,但这种学法往往会因为缺乏结构,很难有效掌握。设计原则,是一个更好的体系,掌握设计原则之后,才能更好地理解设计模式这些招式。Robert Martin 总结出的“SOLID”是一套相对完整易学的设计原则。
|
||||
|
||||
我以“SOLID” 中的单一职责原则为例,给你稍做展开,更多的内容可以去看 Robert Martin 的书。不过,我也给你补充了一些维度,尤其是从“小”的角度告诉你,你能看到多小,就能发现代码里多少的问题。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**把函数写短。**
|
||||
|
||||
最后我想请你思考一下,你是怎么理解软件设计的呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
123
极客时间专栏/geek/10x程序员工作法/自动化/35 | 总是在说MVC分层架构,但你真的理解分层吗?.md
Normal file
123
极客时间专栏/geek/10x程序员工作法/自动化/35 | 总是在说MVC分层架构,但你真的理解分层吗?.md
Normal file
@ -0,0 +1,123 @@
|
||||
<audio id="audio" title="35 | 总是在说MVC分层架构,但你真的理解分层吗?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4e/3f/4ea757fa53a688a7d28835522258f23f.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
作为程序员,你一定听说过分层,比如,最常见的 Java 服务端应用的三层结构,在《[15 | 一起练习:手把手带你分解任务](http://time.geekbang.org/column/article/78542)》中,我曾提到过:
|
||||
|
||||
- 数据访问层,按照传统的说法,叫 DAO(Data Access Object,数据访问对象),按照领域驱动开发的术语,称之为 Repository;
|
||||
- 服务层,提供应用服务;
|
||||
- 资源层,提供对外访问的资源,采用传统做法就是 Controller。
|
||||
|
||||
这几乎成为了写 Java 服务的标准模式。但不知道你有没有想过,为什么要分层呢?
|
||||
|
||||
## 设计上的分解
|
||||
|
||||
其实,分层并不是一个特别符合直觉的做法,符合直觉的做法应该是直接写在一起。
|
||||
|
||||
在编程框架还不是特别流行的时候,人们就是直接把页面和逻辑混在一起写的。如果你有机会看看写得不算理想的 PHP 程序,这种现象还是大概率会出现的。
|
||||
|
||||
即便像 Java 这个如此重视架构的社区,分层也是很久之后才出现的,早期的 JSP 和 PHP 并没有什么本质区别。
|
||||
|
||||
那为什么要分层呢?原因很简单,当代码复杂到一定程度,人们维护代码的难度就急剧上升。一旦出现任何问题,在所有一切都混在一起的代码中定位问题,本质上就是一个“大海捞针”的活。
|
||||
|
||||
前面讲任务分解的时候,我不断在强调的观点就是,人们擅长解决的是小问题,大问题怎么办?拆小了就好。
|
||||
|
||||
**分层架构,实际上,就是一种在设计上的分解。**
|
||||
|
||||
回到前面所说的三层架构,这是行业中最早普及的一种架构模式,最开始是 MVC,也就是 Model、View 和 Controller。
|
||||
|
||||
MVC 的概念起源于 GUI (Graphical User Interface,图形用户界面)编程,人们希望将图形界面上展示的部分(View)与 UI 的数据模型(Model)分开,它们之间的联动由 Controller 负责。这个概念在 GUI 编程中是没有问题的,但也仅限于在与 UI 有交互的部分。
|
||||
|
||||
很多人误以为这也适合服务端程序,他们就把模型部分误解成了数据库里的模型,甚至把它理解成数据库访问。于是,你会看到有人在 Controller 里访问数据库。
|
||||
|
||||
不知道你是不是了解 [Ruby on Rails](http://rubyonrails.org),这是当年改变了行业认知的一个 Web 开发框架,带来很多颠覆性的做法。它采用的就是这样一种编程模型。当年写 Rails 程序的时候我发现,当业务复杂到了一定规模,代码就开始难以维护了。我想了好久,终于发现,在 Rails 的常规做法中少了服务层(Service)的设计。
|
||||
|
||||
这个问题在 Java 领域,爆发得要比 Rails 里早,因为 Ruby 语言的优越性,Rails 实现的数据访问非常优雅。正是因为 Rails 的数据访问实在太容易了,很多服务实际上写到 Model 层里。在代码规模不大时,代码看上去是不复杂的,甚至还有些优雅。
|
||||
|
||||
而那时的 Java 可是要一行一行地写数据访问,所以,代码不太可能放在 Model 层,而放在Controller 里也会让代码变复杂,于是,为业务逻辑而生的 Service 层就呼之欲出了。
|
||||
|
||||
至此,常见的 Java 服务端开发的基础就全部成型了,只不过,由于后来 REST 服务的兴起,资源层替代了 Controller 层。
|
||||
|
||||
到这里,我给你讲了常见的 Java 服务三层架构的来龙去脉。但实际上,在软件开发中,分层几乎是无处不在的,因为好的分层往往需要有好的抽象。
|
||||
|
||||
## 无处不在的分层
|
||||
|
||||
作为程序员,我们几乎每天都在与分层打交道。比如说,程序员都对网络编程模型很熟悉,无论是 ISO 的七层还是 TCP/IP 的五层。
|
||||
|
||||
但不知道你有没有发现,虽然学习的时候,你要学习网络有那么多层,但在使用的时候,大多数情况下,你只要了解最上面的那层,比如,HTTP。
|
||||
|
||||
很多人对底层的协议的理解几乎就停留在“学过”的水平上,因为在大多数情况下,除非你要写协议栈,不然你很难用得到。即便偶尔用到,90%的问题靠搜索引擎就解决了,你也很少有动力去系统学习。
|
||||
|
||||
之所以你可以这么放心大胆地“忽略”底层协议,一个关键点就在于,网络模型的分层架构实现得太好了,好到你作为上层的使用者几乎可以忽略底层。而这正是分层真正的价值:**构建一个良好的抽象。**
|
||||
|
||||
这种构建良好的抽象在软件开发中随处可见,比如,你作为一个程序员,每天写着在 CPU 上运行的代码,但你读过指令集吗?你之所以可以不去了解,是因为已经有编译器做好了分层,让你可以只用它们构建出的“抽象”——编程语言去思考问题。
|
||||
|
||||
比如,每天写着 Java 程序的程序员,你知道 Java 程序是如何管理内存的吗?这可是令很多 C/C++程序员寝食难安的问题,而你之所以不用关心这些,正是托了 Java 这种“抽象”的福。对了,你甚至可能没有注意到编程语言也是一种抽象。
|
||||
|
||||
## 有抽象有发展
|
||||
|
||||
只有构建起抽象,人们才能在此基础上做出更复杂的东西。如果今天的游戏依然是面向显示屏的像素编程,那么,精彩的游戏视觉效果就只能由极少数真正的高手来开发。我们今天的大部分游戏应该依然停留在《超级玛丽》的水准。
|
||||
|
||||
同样,近些年前端领域风起云涌,但你是否想过,为什么 Web 的概念早就出现了,但前端作为一个专门的职位,真正的蓬勃发展却是最近十年的事?
|
||||
|
||||
2009年,Ryan Dahl 发布了Node.js,人们才真正认识到,原来 JavaScript 不仅仅可以用于浏览器,还能做服务器开发。
|
||||
|
||||
于是,JavaScript 社区大发展,各种在其他社区已经很流行的工具终于在 JavaScript 世界中发展了起来。正是有了这些工具的支持,人们才能用 JavaScript 构建更复杂的工程,前端领域才能得到了极大的发展。
|
||||
|
||||
如今,JavaScript 已经发展成唯一一门全平台语言,当然,发展最好的依然是在它的大本营:前端领域。前端程序员才有了今天幸福的烦恼:各种前端框架层出不穷。
|
||||
|
||||
在这里,Node.js 的出现让 JavaScript 成为了一个更好的抽象。
|
||||
|
||||
## 构建你的抽象
|
||||
|
||||
理解了分层实际上是在构建抽象,你或许会关心,我该怎么把它运用在自己的工作中。
|
||||
|
||||
构建抽象,最核心的一步是构建出你的核心模型。什么是核心模型呢?就是表达你业务的那部分代码,换句话说,别的东西都可以变,但这部分不能变。
|
||||
|
||||
这么说可能还是有点抽象,我们回到前面的三层架构。
|
||||
|
||||
在前面介绍三层架构的演变时,提到了一个变迁:REST服务的兴起,让 Controller 逐渐退出了历史舞台,资源层取而代之。
|
||||
|
||||
换句话说,访问服务的方式可能会变。放到计算机编程的发展中,这种趋势就更明显了,从命令行到网络,从 CS(Client-Server) 到 BS(Browser-Server),从浏览器到移动端。所以,怎么访问不应该是你关注的核心。
|
||||
|
||||
同样, 关系型数据库也不是你关注的核心,它只是今天的主流而已。从前用文件,今天还有各种 NoSQL。
|
||||
|
||||
如此说来,三层架构中的两层重要性都不是那么高,那重要的是什么?答案便呼之欲出了,没错,就是剩下的部分,我们习惯上称之为服务层,但这个名字其实不能很好地反映它的作用,更恰当的说法应该可以叫领域模型(Domain Model)。
|
||||
|
||||
它便是我们的核心模型,也是我们在做软件设计时,真正应该着力的地方。
|
||||
|
||||
为什么叫“服务层”不是一个好的说法呢?这里会遗漏领域模型中一个重要的组成部分:领域对象。
|
||||
|
||||
很多人理解领域对象有一个严重的误区,认为领域对象属于数据层。数据存储只是领域对象的一种用途,它更重要的用途还是用在各种领域服务中。
|
||||
|
||||
由此还能引出另一个常见的设计错误,领域对象中只包含数据访问,也就是常说的 getter 和 setter,而没有任何逻辑。
|
||||
|
||||
如果只用于数据存储,只有数据访问就够了,但如果是领域对象,就应该有业务逻辑。比如,给一个用户修改密码,用户这个对象上应该有一个 changePassword 方法,而不是每次去 setPassword。
|
||||
|
||||
严格地说,领域对象和存储对象应该是两个类,只不过它俩实在太像了,很多人经常使用一个类,这还是个小问题。但很多人却把这种内部方案用到了外部,比如,第三方集成。
|
||||
|
||||
为数不少的团队都在自己的业务代码中直接使用了第三方代码中的对象,第三方的任何修改都会让你的代码跟着改,你的团队就只能疲于奔命。
|
||||
|
||||
解决这个问题最好的办法就是把它们分开,**你的领域层只依赖于你的领域对象,第三方发过来的内容先做一次转换,转换成你的领域对象**。这种做法称为防腐层。
|
||||
|
||||
当我们把领域模型看成了整个设计的核心,看待其他层的视角也会随之转变,它们只不过是适配到不同地方的一种方式而已,而这种理念的推广,就是一些人在说的六边形架构。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/6b/d9/6bfe53c81c92634f81765870181b63d9.jpg" alt="">
|
||||
|
||||
怎么设计好领域模型是一个庞大的主题,推荐你去了解一下领域驱动设计(Domain Driven Design,DDD),这个话题我们后面还会再次提到。
|
||||
|
||||
讨论其实还可以继续延伸下去,已经构建好的领域模型怎么更好地提供给其他部分使用呢?一个好的做法是封装成领域特定语言(Domain Specific Language,DSL)。当然,这也是一个庞大的话题,就不继续展开了。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
我从最常见的服务端三层架构入手,给你讲了它们的来龙去脉。分层架构实际是一种设计上的分解,将不同的内容放在不同的地方,降低软件开发和维护的成本。
|
||||
|
||||
分层,更关键的是,提供抽象。这种分层抽象在计算机领域无处不在,无论是编程语言,还是网络协议,都体现着分层抽象的价值。有了分层抽象,人们才能更好地在抽象的基础上构建更复杂的东西。
|
||||
|
||||
在日常工作中,我们应该把精力重点放在构建自己的领域模型上,因为它才是工作最核心、不易变的东西。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**构建好你的领域模型。**
|
||||
|
||||
最后我想请你思考一下,你还知道哪些技术是体现分层抽象的思想吗?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
113
极客时间专栏/geek/10x程序员工作法/自动化/36 | 为什么总有人觉得5万块钱可以做一个淘宝?.md
Normal file
113
极客时间专栏/geek/10x程序员工作法/自动化/36 | 为什么总有人觉得5万块钱可以做一个淘宝?.md
Normal file
@ -0,0 +1,113 @@
|
||||
<audio id="audio" title="36 | 为什么总有人觉得5万块钱可以做一个淘宝?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/50/a9/506c3cc307bccf5a776696b7516f03a9.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
今天,我们从软件行业的一个段子说起。
|
||||
|
||||
甲方想要做个电商网站,作为乙方的程序员问:“你要做个什么样的呢?”甲方说:“像淘宝那样就好。”程序员问:“那你打算出多少钱?”甲方想了想,“5万块钱差不多了吧!”
|
||||
|
||||
这当然是个调侃客户不懂需求的段子,但你有没有想过,为什么在甲方看来并不复杂的系统,你却觉得困难重重呢?
|
||||
|
||||
**因为你们想的根本不是一个东西。**
|
||||
|
||||
在客户看来,我要的不就是一个能买东西的网站吗?只要能上线商品,用户能看到能购买不就好了,5万块钱差不多了。
|
||||
|
||||
而你脑中想的却是,“淘宝啊,那得是多大的技术挑战啊,每年一到‘双11’,那就得考虑各种并发抢购。淘宝得有多少程序员,5万块你就想做一个,门都没有。”
|
||||
|
||||
如果放在前面“沟通反馈”的模块,我可能会讲双方要怎么协调,把想法统一了。但到了“自动化”的模块,我想换个角度讨论这个问题:系统是怎么变复杂的。
|
||||
|
||||
## 淘宝的发展历程
|
||||
|
||||
既然说到了淘宝,我们就以一些公开资料来看看淘宝的技术变迁过程。2013年,子柳出版了一本《[淘宝技术这十年](http://book.douban.com/subject/24335672/)》,这本书里讲述了淘宝是怎么一步步变化的。
|
||||
|
||||
按照书中的说法,第一个淘宝是“买来的”,买的是一个叫做 PHPAuction 的系统,即便选择了最高配,也才花了2000美元左右。这是一个采用 LAMP 架构的系统,也就是 Linux + Apache + MySQL + PHP,这在当年可是典型的开源架构。
|
||||
|
||||
团队所做的主要就是一些订制化工作,最大的调整就是将单一数据库的读写进行了拆分,变成了一个主库和两个从库。这种结构在今天来看,依然是很多团队做调整的首选。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9a/2c/9a82f8edfe722ceb05acf1f35e7c372c.jpg" alt="">
|
||||
|
||||
当访问量和数据量不断提升,MySQL 数据库率先扛不住了。当年的 MySQL 默认采用的是 MyISAM 引擎,写数据的时候会锁住表,读也会被卡住,当然,这只是诸多问题中的一个。
|
||||
|
||||
2003年底,团队将 MySQL 换成了 Oracle。由于 Oracle 的性能要好上许多,主从的数据库架构又改回了单一数据库。但由于 PHP 访问数据库的缺省方案没有连接池,只好找了开源的 SQL Relay,这也为后续的改进埋下了伏笔。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d9/9d/d97299ce3efb3070369915dbcd60da9d.jpg" alt="">
|
||||
|
||||
当数据量继续加大,本地存储就已经无法满足了,只能通过引入网络存储解决问题。数据量进一步增大之后,存储节点一拆再拆,依然不能解决问题,淘宝就踏上了购买小型机的道路。
|
||||
|
||||
IBM 的小型机、Oracle 的数据库和 EMC 的存储,这个阶段就踏上了 IOE 之路。
|
||||
|
||||
2004年初,SQL Relay 已经成了一个挥之不去的痛点,于是,只能从更根本的方案上动脑筋:更换程序设计语言。作为当时的主流,Java 成了不二之选。
|
||||
|
||||
替换的方案就是给业务分模块,一块一块地替换。老模块只维护,不增加新功能,新功能只在新模块开发,新老模块共用数据库。新功能上线,则关闭老模块对应功能,所有功能替换完毕,则老模块下线。
|
||||
|
||||
淘宝的数据量继续增长,单台 Oracle 很快到了上限,团队采用了今天常见的“分库分表”模式,但“分库分表”就会带来新的问题,跨数据库的数据怎么整合?于是,打造出了一个 DBRoute,用以处理分库的数据。
|
||||
|
||||
但是,这种做法也带来了一个新的问题,同时连接多个数据库,任何一个数据库出了问题,都会导致整个网站的故障。
|
||||
|
||||
当淘宝的数据量再次增长,每次访问都到了数据库,数据库很难承受。一个解决方案就是引入缓存和 CDN(Content Delivery Network,内容分发网络),这样,只读数据的压力就从数据库解放了出来。
|
||||
|
||||
当时的缓存系统还不像今天这么成熟,于是,团队基于一个开源项目改出了一个。他们用的 CDN 最开始是一个商用系统,但流量的增加导致这个系统也支撑不住了,只好开始搭建自己的 CDN。
|
||||
|
||||
后来,因为 CDN 要消耗大量的服务器资源,为了降低成本,淘宝又开始研发自己的低功耗服务器。
|
||||
|
||||
随着业务的不断发展,开发人员越来越多,系统就越来越臃肿,耦合度也逐渐提升,出错的概率也逐渐上升。这时,不得不对系统进行分解,将复用性高的模块拆分出来,比如,用户信息。
|
||||
|
||||
业务继续发展,拆分就从局部开始向更大规模发展,底层业务和上层流程逐渐剥离,并逐渐将所有业务都模块化。
|
||||
|
||||
有了一个相对清晰地业务划分之后,更多的底层业务就可以应用于不同的场景,一个基础设施就此成型,新的业务就可以使用基础设施进行构建,上层业务便如雨后春笋一般蓬勃发展起来。
|
||||
|
||||
在这个过程中,有很多技术问题在当时还没有好的解决方案,或者是不适合于它们所在的场景。所以,淘宝的工程师就不得不打造自己的解决方案,比如:分布式文件系统(TFS)、缓存系统(Tair)、分布式服务框架(HSF)等等。还有一些技术探索则是为了节省成本,比如,去 IOE 和研发低功耗服务器等等。
|
||||
|
||||
我这里以淘宝网站的发展为例,做了一个快速的梳理,只是为了让你了解一个系统的发展,如果你有兴趣了解更多细节,不妨自己找出这本书读读。当然,现在的淘宝肯定比这更加完整复杂。
|
||||
|
||||
## 同样的业务,不同的系统
|
||||
|
||||
为什么我们要了解一个系统的演化过程呢?**因为作为程序员,我们需要知道自己面对的到底是一个什么样的系统。**
|
||||
|
||||
回到我们今天的主题上,5万块钱可以不可以做一个淘宝?答案是,取决于你要的是一个什么样的系统。最开始买来的“淘宝”甚至连5万块钱都不用,而今天的淘宝和那时的淘宝显然不是一个系统。
|
||||
|
||||
从业务上说,今天的淘宝固然已经很丰富了,但最核心的业务相差并不大,无非是卖家提供商品,买家买商品。那它们的本质差别在哪呢?
|
||||
|
||||
回顾上面的过程,你就可以看到,每次随着业务量的增长,原有技术无法满足需要,于是,就需要用新的技术去解决这个问题。这里的关键点在于:**不同的业务量。**
|
||||
|
||||
一个只服务于几个人的系统,单机就够了,一个刚刚入行的程序员也能很好地实现这个系统。而当业务量到达一台机器抗不住的时候,就需要用多台机器去处理,这个时候就必须考虑分布式系统的问题,可能就要适当地引入中间件。
|
||||
|
||||
而当系统变成为海量业务提供服务,就没有哪个已经打造好的中间件可以提供帮助了,需要自己从更底层解决问题。
|
||||
|
||||
虽然在业务上看来,这些系统是一样的,但在技术上看来,在不同的阶段,一个系统面对的问题是不同的,因为它面对业务的量级是不同的。**更准确地说,不同量级的系统根本就不是一个系统。**
|
||||
|
||||
只要业务在不断地发展,问题就会不断出现,系统就需要不断地翻新。我曾听到一个很形象的比喻:把奥拓开成奥迪。
|
||||
|
||||
## 你用对技术了吗?
|
||||
|
||||
作为一个程序员,我们都知道技术的重要性,所以,我们都会努力地去学习各种各样的新技术。尤其是当一个技术带有大厂光环的时候,很多人都会迫不及待地去学习。
|
||||
|
||||
我参加过很多次技术大会,当大厂有人分享的时候,通常都是人山人海,大家都想学习大厂有什么“先进”技术。
|
||||
|
||||
知道了,然后呢?
|
||||
|
||||
很多人就想迫不及待地想把这些技术应用在自己的项目中。我曾经面试过很多程序员,给我讲起技术来滔滔不绝,说什么自己在设计时考虑各种分布式的场景,如果系统的压力上来时,他会如何处理。
|
||||
|
||||
我就好奇地问了一个问题,“你这个系统有多少人用?”结果,他做的只是一个内部系统,使用频率也不高。
|
||||
|
||||
为了技术而技术的程序员不在少数,过度使用技术造成的结果就是引入不必要的复杂度。即便用了牛刀杀鸡,因为缺乏真实场景检验,也不可能得到真实反馈,对技术理解的深度也只能停留在很表面的程度上。
|
||||
|
||||
在前面的例子中,**淘宝的工程师之所以要改进系统,真实的驱动力不是技术,而是不断攀升的业务量带来的问题复杂度。**
|
||||
|
||||
所以,评估系统当前所处的阶段,采用恰当的技术解决,是我们最应该考虑的问题。
|
||||
|
||||
也许你会说,我做的系统没有那么大的业务量,我还想提高技术怎么办?答案是到有好问题的地方去。现在的 IT 行业提供给程序员的机会很多,找到一个有好问题的地方并不是一件困难的事,当然,前提条件是,你自己得有解决问题的基础能力。
|
||||
|
||||
## 总结时刻
|
||||
|
||||
今天,我以淘宝的系统为例,给你介绍了一个系统逐渐由简单变复杂的发展历程,希望你能认清不同业务量级的系统本质上就不是一个系统。
|
||||
|
||||
一方面,有人会因为对业务量级理解不足,盲目低估其他人系统的复杂度;另一方面,也有人会盲目应用技术,给系统引入不必要的复杂度,让自己陷入泥潭。
|
||||
|
||||
作为拥有技术能力的程序员,我们都非常在意个人技术能力的提升,但却对在什么样情形下,什么样的技术更加适用考虑得不够。采用恰当的技术,解决当前的问题,是每个程序员都应该仔细考虑的问题。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:**用简单技术解决问题,直到问题变复杂。**
|
||||
|
||||
最后,我想请你回想一下,你身边有把技术做复杂而引起的问题吗?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
103
极客时间专栏/geek/10x程序员工作法/自动化/37 | 先做好DDD再谈微服务吧,那只是一种部署形式.md
Normal file
103
极客时间专栏/geek/10x程序员工作法/自动化/37 | 先做好DDD再谈微服务吧,那只是一种部署形式.md
Normal file
@ -0,0 +1,103 @@
|
||||
<audio id="audio" title="37 | 先做好DDD再谈微服务吧,那只是一种部署形式" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d1/f9/d1bfb2b6d48610dab625f1e668d0b2f9.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
在“自动化”模块的最后,我们来聊一个很多人热衷讨论却没做好的实践:微服务。
|
||||
|
||||
在今天做后端服务似乎有一种倾向,如果你不说自己做的是微服务,出门都不好意思和人打招呼。
|
||||
|
||||
一有技术大会,各个大厂也纷纷为微服务出来站台,不断和你强调自己公司做微服务带来的各种收益,下面的听众基本上也是热血沸腾,摩拳擦掌,准备用微服务拯救自己的业务。
|
||||
|
||||
我就亲眼见过这样的例子,几个参加技术大会的人回到公司,跟人不断地说微服务的好,说服了领导,在接下来大的项目改造中启用了微服务。
|
||||
|
||||
结果呢?一堆人干了几个月,各自独立开发的微服务无法集成。最后是领导站出来,又花了半个月时间,将这些“微服务”重新合到了一起,勉强将这个系统送上了线。
|
||||
|
||||
人家的微服务那么美,为什么到你这里却成了烂摊子呢?因为你只学到了微服务的形。
|
||||
|
||||
## 微服务
|
||||
|
||||
大部分人对微服务的了解源自 James Lewis 和 Martin Fowler 在2014年写的[一篇文章](http://www.martinfowler.com/articles/microservices.html),他们在其中给了微服务一个更清晰的定义,把它当作了一种新型的架构风格。
|
||||
|
||||
但实际上,早在这之前的几年,很多人就开始用“微服务”这个词进行讨论了。
|
||||
|
||||
“在企业内部将服务有组织地进行拆分”这个理念则脱胎于 SOA(Service Oriented Architecture,面向服务的架构),只不过,SOA 诞生自那个大企业操盘技术的年代,自身太过于复杂,没有真正流行开来。而微服务由于自身更加轻量级,符合程序员的胃口,才得以拥有更大的发展空间。
|
||||
|
||||
谈到微服务,你会想起什么呢?很多人对微服务的理解,就是把一个巨大的后台系统拆分成一个一个的小服务,再往下想就是一堆堆的工具了。
|
||||
|
||||
所以,市面上很多介绍微服务的内容,基本上都是在讲工具的用法,或是一些具体技术的讨论,比如,用 Spring Boot 可以快速搭建服务,用 Spring Cloud 建立分布式系统,用 Service Mesh 技术作为服务的基础设施,以及怎么在微服务架构下保证事务的一致性,等等。
|
||||
|
||||
确实,这些内容在你实现微服务时,都是有价值的。但必须先回答一个问题,我们为什么要做微服务?
|
||||
|
||||
对这个问题的标准回答是,相对于整体服务(Monolithic)而言,微服务足够小,代码更容易理解,测试更容易,部署也更简单。
|
||||
|
||||
这些道理都对,但这是做好了微服务的结果。怎么才能达到这个状态呢?这里面有一个关键因素,**怎么划分微服务,也就是一个庞大的系统按照什么样的方式分解。**
|
||||
|
||||
这是在很多关于微服务的讨论中所最为欠缺的,也是很多团队做“微服务”却死得很难看的根本原因。
|
||||
|
||||
不了解这一点,写出的服务,要么是服务之间互相调用,造成整个系统执行效率极低;要么是你需要花大力气解决各个服务之间的数据一致性。换句话说,服务划分不好,等待团队的就是无穷无尽的偶然复杂度泥潭。只有正确地划分了微服务,它才会是你心目中向往的样子。
|
||||
|
||||
**那应该怎么划分微服务呢?你需要了解领域驱动设计。**
|
||||
|
||||
## 领域驱动设计
|
||||
|
||||
领域驱动设计(Domain Driven Design,DDD)是 Eric Evans 提出的从系统分析到软件建模的一套方法论。它要解决什么问题呢?就是将业务概念和业务规则转换成软件系统中概念和规则,从而降低或隐藏业务复杂性,使系统具有更好的扩展性,以应对复杂多变的现实业务问题。
|
||||
|
||||
这听上去很自然,不就应该这么解决问题吗?并不然,现实情况可没那么理想。
|
||||
|
||||
在此之前,人们更多还是采用面向数据的建模方式,时至今日,还有许多团队一提起建模,第一反应依然是建数据库表。这种做法是典型的面向技术实现的做法。一旦业务发生变化,团队通常都是措手不及。
|
||||
|
||||
**DDD 到底讲了什么呢?它把你的思考起点,从技术的角度拉到了业务上。**
|
||||
|
||||
贴近业务,走近客户,我们在这个专栏中已经提到过很多次。但把这件事直接体现在写代码上,恐怕还是很多人不那么习惯的一件事。DDD 最为基础的就是通用语言(Ubiquitous Language),让业务人员和程序员说一样的语言。
|
||||
|
||||
这一点我在《[21 | 你的代码为谁而写?](http://time.geekbang.org/column/article/82581)》中已经提到过了。使用通用语言,等于把思考的层次从代码细节中拉到了业务层面。越高层的抽象越稳定,越细节的东西越容易变化。
|
||||
|
||||
有了通用语言做基础,然后就要进入到 DDD 的实战环节了。DDD 分为战略设计(Strategic Design)和战术设计(Tactical Design)。
|
||||
|
||||
战略设计是高层设计,它帮我们将系统切分成不同的领域,并处理不同领域的关系。我在[前面的内容](http://time.geekbang.org/column/article/82581)中给你举过“订单”和“用户”的例子。从业务上区分,把不同的概念放到不同的地方,这是从根本上解决问题,否则,无论你的代码写得再好,混乱也是不可避免的。而这种以业务的角度思考问题的方式就是 DDD 战略设计带给我的。
|
||||
|
||||
战术设计,通常是指在一个领域内,在技术层面上如何组织好不同的领域对象。举个例子,国内的程序员喜欢用 myBatis 做数据访问,而非 JPA,常见的理由是 JPA 在有关联的情况下,性能太差。但真正的原因是没有设计好关联。
|
||||
|
||||
如果能够理解 DDD 中的聚合根(Aggregate Root),我们就可以找到一个合适的访问入口,而非每个人随意读取任何数据。这就是战术设计上需要考虑的问题。
|
||||
|
||||
战略设计和战术设计讨论的是不同层面的事情,不过,这也是 Eric Evans 最初没有讲清楚的地方,导致了人们很长时间都无法理解 DDD 的价值。
|
||||
|
||||
## 走向微服务
|
||||
|
||||
说了半天,这和微服务有什么关系呢?微服务真正的难点并非在于技术实现,而是业务划分,而这刚好是 DDD 战略设计中限界上下文(Bounded Context)的强项。
|
||||
|
||||
虽然通用语言打通了业务与技术之间的壁垒,但计算机并不擅长处理模糊的人类语言,所以,通用语言必须在特定的上下文中表达,才是清晰的。就像我们说过的“订单”那个例子,交易的“订单”和物流的“订单”是不同的,它们都有着自己的上下文,而这个上下文就是限界上下文。
|
||||
|
||||
**它限定了通用语言自由使用的边界,一旦出界,含义便无法保证。**正是由于边界的存在,一个限界上下文刚好可以成为一个独立的部署单元,而这个部署单元就可以成为一个服务。
|
||||
|
||||
所以要做好微服务,第一步应该是识别限界上下文。
|
||||
|
||||
你也看出来了,每个限界上下文都应该是独立的,每个上下文之间就不应该存在大量的耦合,困扰很多人的微服务之间大量相互调用,本身就是一个没有划分好边界而带来的伪命题,靠技术解决业务问题,事倍功半。
|
||||
|
||||
有了限界上下文就可以做微服务了吧?且慢!
|
||||
|
||||
Martin Fowler 在写《[企业应用架构模式](http://book.douban.com/subject/1230559/)》时,提出了一个分布式对象第一定律:不要分布对象。同样的话,在微服务领域也适用,想做微服务架构,首先是不要使用微服务。如果将一个整体服务贸然做成微服务,引入的复杂度会吞噬掉你以为的优势。
|
||||
|
||||
你可能又会说了,“我都把限界上下文划出来了,你告诉我不用微服务?”
|
||||
|
||||
还记得我在《[30 | 一个好的项目自动化应该是什么样子的?](http://time.geekbang.org/column/article/86561)》中提到的分模块吗?如果你划分出了限界上下文,不妨先按照它划分模块。
|
||||
|
||||
以我拙见,一次性把边界划清楚并不是一件很容易的事。大家在一个进程里,调整起来会容易很多。然后,让不同的限界上下文先各自独立演化。等着它演化到值得独立部署了,再来考虑微服务拆分的事情。到那时,你也学到各种关于微服务的技术,也就该派上用场了!
|
||||
|
||||
## 总结时刻
|
||||
|
||||
微服务是很多团队的努力方向,然而,现在市面上对于微服务的介绍多半只停留在技术层面上,很多人看到微服务的好,大多数是结果,到自己团队实施起来却困难重重。想要做好微服务,关键在于服务的划分,而划分服务,最好先学习 DDD。
|
||||
|
||||
Eric Evans 2003年写了《[领域驱动设计](http://book.douban.com/subject/1629512/)》,向行业介绍了DDD 这套方法论,立即在行业中引起广泛的关注。但实话说,Eric 在知识传播上的能力着实一般,这本 DDD 的开山之作写作质量难以恭维,想要通过它去学好 DDD,是非常困难的。所以,在国外的技术社区中,有很多人是通过各种交流讨论逐渐认识到 DDD 的价值所在,而在国内 DDD 几乎没怎么掀起波澜。
|
||||
|
||||
2013年,在 Eric Evans 出版《领域驱动设计》十年之后,DDD 已经不再是当年吴下阿蒙,有了自己一套比较完整的体系。Vaughn Vernon 将十年的精华重新整理,写了一本《[实现领域驱动设计](http://book.douban.com/subject/25844633/)》,普通技术人员终于有机会看明白 DDD 到底好在哪里了。所以,你会发现,最近几年,国内的技术社区开始出现了大量关于 DDD 的讨论。
|
||||
|
||||
再后来,因为《实现领域驱动设计》实在太厚,Vaughn Vernon 又出手写了一本精华本《[领域驱动设计精粹](http://book.douban.com/subject/30333944/)》,让人可以快速上手 DDD,这本书也是我向其他人推荐学习 DDD 的首选。
|
||||
|
||||
即便你学了 DDD,知道了限界上下文,也别轻易使用微服务。我推荐的一个做法是,先用分模块的方式在一个工程内,让服务先演化一段时间,等到真的觉得某个模块可以“毕业”了,再去开启微服务之旅。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:学习领域驱动设计。
|
||||
|
||||
最后,我想请你分享一下,你对 DDD 的理解是什么样的呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
203
极客时间专栏/geek/10x程序员工作法/自动化/划重点 | “自动化”主题的重点内容回顾汇总.md
Normal file
203
极客时间专栏/geek/10x程序员工作法/自动化/划重点 | “自动化”主题的重点内容回顾汇总.md
Normal file
@ -0,0 +1,203 @@
|
||||
<audio id="audio" title="划重点 | “自动化”主题的重点内容回顾汇总" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/66/ca/664e49a56576560927518b6a035843ca.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
“自动化”模块终于全部更新完毕。至此,四个工作原则我已经给你全部介绍了一遍,相对而言,这个模块的内容比较“硬”,我也竭尽全力帮你串起更多知识的脉络,所以,信息量也是非常大的。希望你能够找到自己接下来努力的方向,不断提升自己的“硬实力”。
|
||||
|
||||
## 重点复习
|
||||
|
||||
在这个模块中,我们学习到了一些最佳实践。
|
||||
|
||||
<li>
|
||||
持续交付
|
||||
<ul>
|
||||
- 将生产部署纳入了开发的考量。
|
||||
- 持续交付的基础设施通常包含持续集成环境、测试环境、预生产环境和生产环境。
|
||||
- 构建流水线保证到了下游的交付物一定是通过上游验证的。
|
||||
- 随着 Docker 的诞生,交付由发布包变成了 Docker 镜像。
|
||||
|
||||
DevOps
|
||||
|
||||
- 将开发和运维结合到一起。
|
||||
- 环境配置工具上的进步,让基础设施即代码成了行业共识。
|
||||
|
||||
验收测试
|
||||
|
||||
- 验收测试要站在业务的角度编写。
|
||||
- BDD 是一种编写验收测试的方式。
|
||||
- Given...When...Then... 的描述给了一个描述业务的统一方式。
|
||||
- 写好验收测试,需要构建测试模型。
|
||||
|
||||
SOLID 原则
|
||||
|
||||
- 设计模式背后的道理。
|
||||
- 单一职责原则(Single responsibility principle,SRP)。
|
||||
- 开放封闭原则(Open–closed principle,OCP)。
|
||||
- Liskov 替换原则(Liskov substitution principle,LSP)。
|
||||
- 接口隔离原则(Interface segregation principle,ISP)。
|
||||
- 依赖倒置原则(Dependency inversion principle,DIP)。
|
||||
- 用好单一职责原则,前提条件是看待问题颗粒度要小。
|
||||
|
||||
DDD
|
||||
|
||||
- 它将思考的起点拉到了业务上。
|
||||
- DDD 分为战略设计和战术设计。
|
||||
|
||||
微服务
|
||||
|
||||
- 做好微服务的前提是划分好限界上下文。
|
||||
- 微服务的第一步,不要划分微服务。
|
||||
|
||||
在这个模块中,我们还了解了一些重要的思路,让我们把工作做得更好。
|
||||
|
||||
- 程序员的三大美德:懒惰、急躁和傲慢(Laziness, Impatience and hubris)。
|
||||
- 小心 NIH 综合症(Not Invented Here Syndrome)。
|
||||
- 写好构建脚本,做好项目自动化。
|
||||
- 参照 Java 知识体系,学习运维知识。
|
||||
- 软件设计最基础的原则是“高内聚、低耦合”。
|
||||
- 分层架构是一种设计上的分解。
|
||||
- 不同业务量的系统本质上不是一个系统。
|
||||
- 采用简单技术解决问题,直到问题变复杂。
|
||||
|
||||
## 实战指南
|
||||
|
||||
<li>
|
||||
<p>请谨慎地将工作自动化。<br>
|
||||
——《[29 | “懒惰”应该是所有程序员的骄傲](http://time.geekbang.org/column/article/86210)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>将你的工作过程自动化。<br>
|
||||
——《[30 | 一个好的项目自动化应该是什么样子的?](http://time.geekbang.org/column/article/86561)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>有体系地学习运维知识。<br>
|
||||
——《[31 | 程序员怎么学习运维知识?](http://time.geekbang.org/column/article/87008)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>将部署纳入开发的考量。<br>
|
||||
——《[32 | 持续交付:有持续集成就够了吗?](http://time.geekbang.org/column/article/87229)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>将验收测试自动化。<br>
|
||||
——《[33 | 如何做好验收测试?](http://time.geekbang.org/column/article/87582)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>把函数写短。<br>
|
||||
——《[34 | 你的代码是怎么变混乱的?](http://time.geekbang.org/column/article/87845)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>构建好你的领域模型。<br>
|
||||
——《[35 | 总是在说MVC分层架构,但你真的理解分层吗?](http://time.geekbang.org/column/article/88309)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>用简单技术解决问题,直到问题变复杂。<br>
|
||||
——《[36 | 为什么总有人觉得5万块钱可以做一个淘宝?](http://time.geekbang.org/column/article/88764)》</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>学习领域驱动设计。<br>
|
||||
——《[37 | 先做好DDD再谈微服务吧,那只是一种部署形式](http://time.geekbang.org/column/article/89049)》</p>
|
||||
</li>
|
||||
|
||||
## 额外收获
|
||||
|
||||
在这个模块的最后,针对大家在学习过程中的一些问题,我也进行了回答,帮你梳理出一个思路,更好地理解学到的内容:
|
||||
|
||||
<li>持续集成的延伸。
|
||||
<ul>
|
||||
- 持续集成完成系统集成。
|
||||
- 持续交付完成可部署上线。
|
||||
- “持续验证”完成产品想法验证。
|
||||
|
||||
——《[答疑解惑 | 持续集成、持续交付,然后呢?](http://time.geekbang.org/column/article/89050)》
|
||||
|
||||
## 留言精选
|
||||
|
||||
在讲到 “懒惰”应该是所有程序员的骄傲时,jxin 同学提到:
|
||||
|
||||
>
|
||||
有价值的事并不局限于事情本身。做自动化很重要,写代码很重要。但根据现有情况判断是否需要自动化,是否需要写代码也很重要。有的放矢,任务分解。权衡跟设计是件很艺术的事情,令人着迷。
|
||||
|
||||
|
||||
另外,关于持续交付,Jxin 同学也提出了自己的理解:
|
||||
|
||||
>
|
||||
分而治之是解决复杂问题的一大利器。持续交互就像重构中小步快走(每次微调后运行测试代码验证),都能保证大工程的稳步前进。同时由于单元小了,所以也灵活了,持续交互可以结合最小产品的理念,以小成本做test,收集数据后,即时调整产品发展方向。
|
||||
|
||||
|
||||
关于软件设计, 毅 同学分享了自己的感悟:
|
||||
|
||||
>
|
||||
我们常说任务到手不要着急去做,要从设计入手,把时间多花在前面。工作中发现大家都是思考了才动手的,那为什么越往后偏差越大呢?
|
||||
|
||||
|
||||
>
|
||||
共性原因有二:一是全局观不够,用咱们课里的话说就是上下文局限和反馈延迟(看到问题不提,直到代码写到那绕不过去了再沟通);
|
||||
|
||||
|
||||
>
|
||||
二是没有领域的概念和有意识地去实践(纸上谈兵),尤其是做流程型任务,都喜欢先把表结构定义出来,再去生成实体,所以从领域层面来看这些实体就很不合适了。结果必然是用面向对象的工具写出了面向过程的代码,既然是面向过程那OO设计原则就鲜有用武之地了。这两点也是我个人理解要做好软件设计的两个必要条件。
|
||||
|
||||
|
||||
讲到分层架构时, desmond 同学提到:
|
||||
|
||||
>
|
||||
学了REST和DDD,感觉两者有相通的地方:两者都以数据(一个是资源,另外一个是领域对象)为中心,并制定一套标准的数据操作(一个是HTTP Verb,另外一个项目主要用JPA这一套);而核心是业务建模。
|
||||
|
||||
|
||||
对于微服务的理解,风翱 同学提到:
|
||||
|
||||
>
|
||||
公司说我们的开发方式是敏捷开发,实际上只是使用了一些敏捷开发的方法,只有遵循敏捷开发的价值观和原则,才能算是敏捷开发。微服务也是一样,不是说拆分成多个服务去部署,就叫做微服务。也不是采用市面上常用的微服务框架,就是微服务了。
|
||||
|
||||
|
||||
对于一个好的项目自动化应该是什么样子这个问题,西西弗与卡夫卡 同学提到:
|
||||
|
||||
>
|
||||
设想过这样的情景(还没实现,打算实践一把):我们新招一名比较熟练的程序员,从TA入职拿到机器,到开发示意代码,再提交SCM,然后CI/CD,再发布到线上交付给用户,整个过程可以在入职当天的午饭之前完成。
|
||||
|
||||
|
||||
>
|
||||
这不光要求构建和集成自动化,甚至要求从入职开始的各个环节都能提前准备好,包括机器、开发环境、线上环境等,甚至连示范的需求都要能及时传递给TA。理想情况下,程序员只需要开发好程序,保证质量,提交到SCM即可,其他事情都应该交给机器。
|
||||
|
||||
|
||||
>
|
||||
要知道程序员都很贵,越早给用户交付价值越好。
|
||||
|
||||
|
||||
对于自动化验收测试, shniu 同学分享了他的学习感悟:
|
||||
|
||||
>
|
||||
自动化验收测试确实是很好的东西,比如在回归测试,省去了很多的重复工作。但我理解BDD的初衷是驱动产品、业务、开发、测试等去深入讨论沟通需求,在还没有真的写代码的时候去实例化story,并一起定义验收用例,让每个人对需求的理解都很透彻,当然特别注意的是要从统一的业务角度去描述,可见,真的做好BDD是需要不断的尝试和总结的。
|
||||
|
||||
|
||||
对于“5万块做淘宝”这个话题,enjoylearning 同学提到:
|
||||
|
||||
>
|
||||
做一个淘宝那样的,客户指的是业务类似,但用户量多少,需要多少并发数,搜索性能等如何都是需要跟客户沟通后才能决定技术选型的。现实中我们的有些系统已经满足了业务需求,就没有必要为了追求技术复杂度而去拆分了,只有面向问题技术选型才会有成效。
|
||||
|
||||
|
||||
关于运维知识,hua168 同学对文章内容进行了补充:
|
||||
|
||||
>
|
||||
<p>现在运维流行DevOps,高级一点就是AI,其中一篇文章《DevOps 详解》不错,链接如下:<br>
|
||||
[https://infoq.cn/article/detail-analysis-of-devops](https://infoq.cn/article/detail-analysis-of-devops)</p>
|
||||
|
||||
|
||||
>
|
||||
<p>《DevOps知识体系与标准化的构建》也不错,下载地址:<br>
|
||||
[https://yq.aliyun.com/download/778](https://yq.aliyun.com/download/778)</p>
|
||||
|
||||
|
||||
>
|
||||
<p>运维知识体系:<br>
|
||||
[https://www.unixhot.com/page/ops](https://www.unixhot.com/page/ops)</p>
|
||||
|
||||
|
||||
>
|
||||
<p>Web缓存知识体系:<br>
|
||||
[https://www.unixhot.com/page/cache](https://www.unixhot.com/page/cache)</p>
|
||||
|
||||
|
||||
**感谢同学们的精彩留言。在下一个模块中,我将结合具体的应用场景,将之前讲过的“思考框架”和“四个原则”进行综合应用的分析。**
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
111
极客时间专栏/geek/10x程序员工作法/自动化/加餐 | 你真的了解重构吗?.md
Normal file
111
极客时间专栏/geek/10x程序员工作法/自动化/加餐 | 你真的了解重构吗?.md
Normal file
@ -0,0 +1,111 @@
|
||||
<audio id="audio" title="加餐 | 你真的了解重构吗?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2a/ff/2ab333a584e2e781fd5f27fae7bdfbff.mp3"></audio>
|
||||
|
||||
今天(3月15日),Martin Fowler 《重构》第二版的中文版正式发布。前不久,人邮的杨海灵老师找到我,让我帮忙给这本书写推荐语,我毫不犹豫地就答应了,有机会为经典之作写推荐语,实属个人荣幸。
|
||||
|
||||
不过,我随即想到,在专栏里,我只是在谈 TDD 的时候提到了重构,并没有把它作为一个专门的话题来讲,于是,我决定给我的专栏读者加餐,专门谈谈重构,毕竟重构是几乎每个程序员都会用到的词汇。但你真的了解重构吗?
|
||||
|
||||
## 每个程序员都要做的事
|
||||
|
||||
作为程序员,我们都希望自己的代码是完美的。但没有代码是完美的,因为只要你的代码还有生命力,一定会有新的需求进来,而新的需求常常是你在编写这段代码之初始料未及的。
|
||||
|
||||
很多人直觉的选择是,顺着既有的代码结构继续写下去,这里添一个 if,那里加一个标记位,长此以往,代码便随时间腐坏了。
|
||||
|
||||
如果用一个物理学术语描述这种现象,那就是“熵增”,这也就是大名鼎鼎的热力学第二定律。如果没有外部干预,系统会朝着越来越混乱的方向发展。对抗熵增的一个办法就是引入负熵,让系统变得更加有序。而在代码中引入负熵的过程就是“重构”。
|
||||
|
||||
调整代码这件事是程序员都会有的习惯,但把这件事做到比较系统,上升为“重构”这个值得推广的实践是从一个小圈子开始的,这个小圈子的核心就是我们在专栏里前面提到过的两位大师级程序员:Ward Cunningham 和 Kent Beck。
|
||||
|
||||
而真正让这个概念走出小圈子,来到大众面前的,则是 Martin Fowler 在1999年写下那本软件行业的名著《重构:改善既有代码的设计》(Refactoring: Improving the Design of Existing Code)。
|
||||
|
||||
Martin Fowler 的本事就在于他极强的阐述能力,很多名词经过他的定义就会成为行业的流行语(Buzzword),重构就是其中之一。
|
||||
|
||||
重构这个说法可比“调整代码”听上去高级多了。时至今日,很多人都会把重构这个词挂在嘴边:“这个系统太乱了,需要重构一下。”
|
||||
|
||||
**但遗憾的是,很多程序员对重构的理解是错的。**
|
||||
|
||||
## 重构是一种微操作
|
||||
|
||||
你理解的重构是什么呢?就以前面那句话为例:这个系统太乱了,需要重构一下。如果我们接着问,你打算怎么重构呢?一些人就会告诉你,他们打算另立门户,重新实现这套系统。对不起,**你打算做的事叫重写(rewrite),而不是重构(refactoring)。**
|
||||
|
||||
《重构》是一本畅销书,但以我的了解,很少有人真正读完它,因为 Martin Fowler 是按照两本书(Duplex Book)来写的,这是他常用写书的风格,前半部分是内容讲解,后半部分是手册。
|
||||
|
||||
让这本书真正声名鹊起的就是前半部分,这部分写出了重构这件事的意义,而后半部分的重构手册很少有人会看完。很多人以为看了前半部分就懂了重构,所以,在他们看来,重构就是调整代码。调整代码的方法我有很多啊,重写也是其中之一。
|
||||
|
||||
如果真的花时间去看这本书的后半部分,你多半会觉得很无聊,因为每个重构手法都是非常细微的,比如,变量改名,提取方法等等。尤其是在今天,这些手法已经成了 IDE 中的菜单。估计这也是很多人就此把书放下,觉得重构不过如此的原因。
|
||||
|
||||
所以,行业里流传着各种关于重构的误解,多半是没有理解这些重构手法的含义。
|
||||
|
||||
**重构,本质上就是一个“微操作”的实践。**如果你不能理解“微操作”的含义,自然是无法理解重构的真正含义,也就不能理解为什么说“大开大合”的重写并不在重构的范畴之内。
|
||||
|
||||
我在《[大师级程序员的工作秘笈](http://time.geekbang.org/column/article/78507)》这篇文章中曾经给你介绍过“微操作”,每一步都很小,小到甚至在很多人眼里它都是微不足道的。
|
||||
|
||||
重构,也属于微操作的行列,与我们介绍的任务分解结合起来,你就能很好地理解那些重构手法的含义了:**你需要把做的代码调整分解成若干可以单独进行的“重构”小动作,然后,一步一步完成它。**
|
||||
|
||||
比如,服务类中有一个通用的方法,它并不适合在这个有业务含义的类里面,所以,我们打算把它挪到一个通用的类里面。你会怎么做呢?
|
||||
|
||||
大刀阔斧的做法一定是创建一个新的通用类,然后把这个方法复制过去,修复各种编译错误。而重构的手法就会把它做一个分解:
|
||||
|
||||
- 添加一个新的通用类,用以放置这个方法;
|
||||
- 在业务类中,添加一个字段,其类型是新添加的通用类;
|
||||
- 搬移实例方法,将这个方法移动到新的类里面。
|
||||
|
||||
得益于现在的 IDE 能力的增强,最后一步,按下快捷键,它就可以帮我们完成搬移和修改各处调用的工作。
|
||||
|
||||
**在这个分解出来的步骤里,每一步都可以很快完成,而且,每做完一步都是可以停下来的,这才是微操作真正的含义。**这是大刀阔斧做法做不到的,你修改编译错误的时候,你不知道自己需要修改多少地方,什么时候是一个头。
|
||||
|
||||
当然,这是一个很简单的例子,大刀阔斧的改过去也无伤大雅。但事实上,很多稍有规模的修改,如果不能以重构的方式进行,常常很快就不知道自己改到哪了,这也是很多所谓“重写”项目面临的最大风险,一旦开始,不能停止。
|
||||
|
||||
你现在理解了,重构不仅仅是一堆重构手法,更重要的是,**你需要有的是“把调整代码的动作分解成一个个重构小动作”的能力。**
|
||||
|
||||
## 重构地图
|
||||
|
||||
下面我准备给你提供一张关于重构的知识地图,帮你了解它与周边诸多知识之间的关系,辅助你更好地理解重构。
|
||||
|
||||
学习重构,先要知道重构的定义。关于这点,Martin Fowler 给出了两个定义,一个名词和一个动词。
|
||||
|
||||
>
|
||||
重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
|
||||
|
||||
|
||||
>
|
||||
重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
|
||||
|
||||
|
||||
之所以要了解重构的定义,因为重构的知识地图就是围绕着这个定义展开的。
|
||||
|
||||
首先,我们要对软件的内部结构进行调整,第一个要回答的问题是,我们为什么要调整。Martin Fowler 对于这个问题的回答是:代码的坏味道。
|
||||
|
||||
代码的坏味道,在我看来,是这本书给行业最重要的启发。很多人常常是无法嗅到代码坏味道的,因此,他们会任由代码腐坏,那种随便添加 if 或标记的做法就是嗅不出坏味道的表现。
|
||||
|
||||
我经常给人推荐《重构》这本书,但我也常常会补上一句,如果你实在没有时间,就去看它的第三章《代码的坏味道》。
|
||||
|
||||
顺便说一下,对比两版的《重构》,你会发现它们在坏味道的定义上有所差异,在新版的《重构》中,可变数据(Mutable Data)、循环语句(Loops)都定义成了坏味道,如果你不曾关注近些年的编程发展趋势,这样的定义着实会让人为之震惊。但只要了解了函数式编程的趋势,就不难理解它们的由来了。
|
||||
|
||||
换句话说,**函数式编程已然成为时代的主流**。如果你还不了解,赶紧去了解。
|
||||
|
||||
我们接着回到重构的定义上,重构是要不改变软件的可观察行为。我们怎么知道是不是改变了可观察行为,最常见的方式就是测试。
|
||||
|
||||
关于测试,我在“任务分解”模块已经讲了很多,你现在已经可以更好地理解重构、TDD 这些概念是怎样相互配合一起的了吧!
|
||||
|
||||
再来,重构是要提高可理解性,那重构到什么程度算是一个头呢?当年重构讨论最火热的时候,有人给出了一个答案:[重构成模式](http://book.douban.com/subject/1917706/)(Refactoring to Patterns)。当然,这也是一本书的名字,有兴趣的话,可以找来读一读。
|
||||
|
||||
我个人有个猜想,如果这个讨论可以延续到2008年,等到 Robert Martin 的《Clean Code》出版,也许有人会提“重构成 Clean Code”也未可知。所以,无论是设计模式,亦或是 Clean Code,都是推荐你去学习的。
|
||||
|
||||
至此,我把重构的周边知识整理了一番,让你在学习重构时,可以做到不仅仅是只见树木,也可看见森林。当然,重构的具体知识,还是去看 Martin Fowler 的书吧!
|
||||
|
||||
## 总结时刻
|
||||
|
||||
总结一下今天的内容。今天我介绍了一个大家耳熟能详的概念:重构。不过,这实在是一个让人误解太多的概念,大家经常认为调整代码就是在做重构。
|
||||
|
||||
重构,本质上就是一堆微操作。重构这个实践的核心,就是将调整代码的动作分解成一个一个的小动作,如果不能理解这一点,你就很难理解重构本身的价值。
|
||||
|
||||
不过,对于我们专栏的读者而言,因为大家已经学过了“任务分解”模块,理解起这个概念,难度应该降低了很多。
|
||||
|
||||
既然重构的核心也是分解,它就需要大量的锤炼。就像之前提到任务分解原则一样,我在重构上也下了很大的功夫做了专门的练习,才能让自己一小步一小步地去做。但一个有追求的软件工匠不就应该这样锤炼自己的基本功吗?
|
||||
|
||||
如果今天的内容你只记住一件事,那请记住:**锤炼你的重构技能。**
|
||||
|
||||
最后,我想请你分享一下,你对重构的理解。欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
113
极客时间专栏/geek/10x程序员工作法/自动化/答疑解惑 | 持续集成、持续交付,然后呢?.md
Normal file
113
极客时间专栏/geek/10x程序员工作法/自动化/答疑解惑 | 持续集成、持续交付,然后呢?.md
Normal file
@ -0,0 +1,113 @@
|
||||
<audio id="audio" title="答疑解惑 | 持续集成、持续交付,然后呢?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a9/6c/a970ed634c1f8c622967f79e377aee6c.mp3"></audio>
|
||||
|
||||
你好,我是郑晔。
|
||||
|
||||
“自动化”模块落下了帷幕,这是四个工作原则中最为“技术化”的一个,也应该是程序员们最熟悉的主题。
|
||||
|
||||
我从软件外部的自动化——工作流程讲起,让你能够把注意力专注于写好代码;讲到了软件内部的自动化——软件设计,选择恰当的做法,不贪图一时痛快,为后续的工作挖下深坑。
|
||||
|
||||
既然是一个大家都熟悉的话题,同学们自然也有很多经验分享,也有很多人看到了与自己不同的做法,提出了各种各样的问题。
|
||||
|
||||
在今天的答疑中,我选出了几个很有意思的问题,让大家可以在已有内容上再进一步延伸。
|
||||
|
||||
## 问题1:持续交付是否可以再做扩展?
|
||||
|
||||
毅 同学提到
|
||||
|
||||
>
|
||||
<p>为达到有效交付的目标,用户能够尽早参与,我觉得也是比较重要的一环。从生产环境获得结果,是否可再做扩展,将用户也作为一个独立节点?<br>
|
||||
——《[32 | 持续交付:有持续集成就够了吗?](http://time.geekbang.org/column/article/87229)》</p>
|
||||
|
||||
|
||||
西西弗与卡夫卡 同学提到
|
||||
|
||||
>
|
||||
<p>持续交付可以是持续交付最大价值,那范围就不仅限于软件,还可以进一步延伸到运营,比如说结合ABTest,自动选择最有效的运营策略,为用户交付最大价值。<br>
|
||||
——《[32 | 持续交付:有持续集成就够了吗?](http://time.geekbang.org/column/article/87229)》</p>
|
||||
|
||||
|
||||
两位同学能提出这样的想法,说明真的是已经理解了持续集成和持续交付,所以,才能在这个基础上继续延伸,思考进一步的扩展。
|
||||
|
||||
我在专栏中一直在强调,别把自己局限在程序员这个单一的角色中,应该了解软件开发的全生命周期。在前面的内容中,我讲了不少做产品的方法,比如,MVP、用户测试等等。如果只把自己定位在一个写代码的角色上,了解这些内容确实意义不大,但你想把自己放在一个更大的上下文中,这些内容就是必须要了解的。
|
||||
|
||||
回到两位同学的问题上,如果说我们一开始把持续集成定义成编写代码这件事的完成,那持续交付就把这个“完成”向前再推进了一步,只有上线的代码才算完成。
|
||||
|
||||
但放在整个软件的生命周期来说,上线并不是终点。把系统送上线,不是最终目的。那最终目的是什么呢?
|
||||
|
||||
回到思考的起点,我们为什么要做一个软件?因为我们要解决一个问题。那我们是不是真正的解决了问题呢?其实,我们还不知道。
|
||||
|
||||
在《[06 | 精益创业:产品经理不靠谱,你该怎么办?](http://time.geekbang.org/column/article/76260)》这篇文章中,我给你讲了做产品的源头。如果是采用精益创业的模式工作,我们构建产品的目的是为了验证一个想法,而怎么才算是验证了我们的想法呢?需要搜集各种数据作为证据。
|
||||
|
||||
所以,我曾经有过这样的想法,**精益创业实际上是一种持续验证**,验证想法的有效性,获得经过验证的认知(Validated Learning)。
|
||||
|
||||
现在有一些获取验证数据的方式,比如,西西弗与卡夫卡 同学提到的 AB 测试。
|
||||
|
||||
AB 测试是一种针对两个(或多个)变体的随机试验,常常用在 Web 或 App 的界面制作过程中,分别制作两个(或多个)版本,让两组(或多组)成分相同的用户随机访问不同版本,收集数据,用以评估哪个版本更好。每次测试时,最好只有一个变量。因为如果有多个变量,你无法确认到底是哪个变量在起作用。
|
||||
|
||||
AB 测试的概念在其他领域由来已久。2000年,Google 的工程师率先把它应用在了软件产品的测试中,时至今日,它已经成为很多产品团队常用的做事方式。
|
||||
|
||||
AB 测试的前提是用户数据搜集。我在《[09 | 你的工作可以用数字衡量吗?](http://time.geekbang.org/column/article/76929)》这篇文章给你介绍了在开发过程中,用数字帮助我们改善工作。在产品领域实际上更需要用数字说话,说到这里,我“插播”一个例子。
|
||||
|
||||
很多产品经理喜欢讲理念、讲做法,偏偏不喜欢讲数字。用数字和产品经理沟通其实是更有说服力的。
|
||||
|
||||
我就曾经遇到过这样的事情,在一个交易平台产品中,一个产品经理创造性地想出一种新的订单类型,声称是为了方便用户,提高资金利用率。如果程序员接受这个想法,就意味着要对系统做很大的调整。
|
||||
|
||||
我问了他几个问题:第一,你有没有统计过系统中现有的订单类型的使用情况?第二,你有没有了解过其他平台是否支持这种订单类型呢?
|
||||
|
||||
产品经理一下子被我问住了。我对第一个问题的答案是,除了最基础的订单类型之外,其他的订单类型用得都很少,之前做的很多号称优化的订单类型,实际上没有几个人在用。
|
||||
|
||||
第二个问题我的答案是,只有极少数平台支持类似的概念。换句话说,虽然我们想得很美,但教育用户的成本会非常高,为了这个可能存在的优点,对系统做大改造,实在是一件投资大回报小的事,不值得!
|
||||
|
||||
再回到我们的问题上,一旦决定了要做某个产品功能,首先应该回答的是如何搜集用户数据。对于前端产品,今天已经有了大量的服务,只要在代码里嵌入一段代码,收集数据就是小事一桩。
|
||||
|
||||
前端产品还好,因为用户行为是比较一致的,买服务就好了,能生成标准的用户行为数据。对于后端的数据,虽然也有各种服务,但基本上提供的能力都是数据的采集和展示,一些所谓的标准能力只是 CPU、内存、JVM 之类基础设施的使用情况。对于应用来说,具体什么样的数据需要搜集,还需要团队自己进行设计。
|
||||
|
||||
说了这些,我其实想说的是,持续验证虽然是一个好的想法,但目前为止,还不如持续集成和持续交付这些已经有比较完整体系做支撑。想做到“持续”,就要做到自动化,想做到自动化,就要有标准化支撑,目前这个方面还是“八仙过海各显神通”的状态,没法上升到行业最佳实践的程度。
|
||||
|
||||
其实道理上也很简单,从一无所有,到持续集成、再到持续交付,最后到持续验证,每过一关,就会有大多数团队掉队。所以,真正能达到持续交付的团队都少之又少,更别提要持续验证了。
|
||||
|
||||
## 问题2:Selenium 和 Cucumber 的区别是什么?
|
||||
|
||||
没有昵称 同学提到
|
||||
|
||||
>
|
||||
<p>老师,Selenium 跟 Cucumber 有区别吗?<br>
|
||||
——《[33 | 如何做好验收测试?](http://time.geekbang.org/column/article/87582)》</p>
|
||||
|
||||
|
||||
这是一个经常有人搞混的问题。为了让不熟悉的人理解,我先讲一点背景。
|
||||
|
||||
Selenium 是一个开源项目,它的定位是浏览器自动化,主要用于 Web 应用的测试。它最早是 Jason Huggins 在2004年开发出来的,用以解决 Web 前端测试难的问题。
|
||||
|
||||
之所以取了 Selenium 这个名字,主要是用来讽刺其竞争对手 Mercury 公司开发的产品。我们知道,Mercury 是水银,而 Selenium 是硒,硒可以用来解水银的毒。又一个程序员的冷幽默!
|
||||
|
||||
Cucumber 的兴起伴随着 Ruby on Rails 的蓬勃发展,我们在之前的内容中提到过,Ruby on Rails 是一个改变了行业认知的 Web 开发框架。所以,Cucumber 最初主要就是用在给 Web 应用写测试上,而 Selenium 刚好是用来操作浏览器的,二者一拍即合。
|
||||
|
||||
于是,你会在很多文章中看到,Cucumber 和 Selenium 几乎是同时出现的,这也是很多人对于二者有点傻傻分不清楚的缘由。
|
||||
|
||||
讲完了这些背景,结合我们之前讲的内容,你就不难理解了。Cucumber 提供的是一层业务描述框架,而它需要有自己对应的步骤实现,以便能够对被测系统进行操控;而 Selenium 就是在 Web 应用测试方面实现步骤定义的一个非常好的工具。
|
||||
|
||||
## 问题3:IntelliJ IDEA 怎么学?
|
||||
|
||||
hua168 同学提到
|
||||
|
||||
>
|
||||
<p>IDEA 怎么学呢?是用到什么功能再学?还是先看个大概,用到时再仔细看?<br>
|
||||
——《[30 | 一个好的项目自动化应该是什么样子的?](http://time.geekbang.org/column/article/86561)》</p>
|
||||
|
||||
|
||||
一个工具怎么学?我的经验就是去用。我没有专门学过 IntelliJ IDEA,只是不断地在使用它。遇到问题就去找相应的解决方案。
|
||||
|
||||
如果说在 IDEA 上下过功夫,应该是在快捷键上。我最早写代码时的风格应该是鼠标与键盘齐飞,实话说,起初也没觉得怎么样。加入 ThoughtWorks 之后,看到很多人把快捷键运用得出神入化,那不是在写一行代码,而是在写一片代码。我当时有一种特别震惊的感觉。
|
||||
|
||||
我自以为在写代码上做得已经相当好了,然而,有人却在你很擅长的一件事上完全碾压了你,那一瞬间,我感觉自己这些年都白学了。这种感觉后来在看到别人能够小步重构时又一次产生了。
|
||||
|
||||
看到差距之后,我唯一能做的,就是自己下来偷偷练习。幸好,无论是快捷键也好,重构也罢,都是可以单独练习的。花上一段时间就可以提高到一定的水平。后来,别人看我写代码时也会有类似的感觉,我会安慰他们说,不要紧,花点时间练习就好。
|
||||
|
||||
其实,也有一些辅助的方法可以帮助我们练习,比如,我们会给新员工发放 IntelliJ IDEA 的快捷键卡片,写代码休息之余,可以拿来看一下;再比如,IntelliJ IDEA 有一个插件叫 [Key Promoter X](http://plugins.jetbrains.com/plugin/9792-key-promoter-x),如果你用鼠标操作,它会给你提示,帮你记住快捷键。有一段时间,我已经练习到“看别人写代码,脑子里能够完全映射出他在按哪个键”的程度。
|
||||
|
||||
写代码是个手艺活,要想打磨手艺,需要看到高手是怎么工作的,才不致于固步自封。如果你身边没有这样的人,不如到网上搜一些视频,看看高手在写代码时是怎么做的,这样才能找到差距,不断提高。
|
||||
|
||||
好,今天的答疑就到这里,你对这些问题有什么看法呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
65
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/001 | 聊聊2017年KDD大会的时间检验奖.md
Normal file
65
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/001 | 聊聊2017年KDD大会的时间检验奖.md
Normal file
@ -0,0 +1,65 @@
|
||||
<audio id="audio" title="001 | 聊聊2017年KDD大会的时间检验奖" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/df/bb/dffb365263f11ccc866754d3d84a89bb.mp3"></audio>
|
||||
|
||||
国际数据挖掘与知识发现大会**ACM SIGKDD**(ACM SIGKDD Conference on Knowledge Discovery and Data Mining),简称**KDD**,是由美国计算机协会**ACM**(The Association for Computing Machinery)的数据挖掘与知识发现专委会**SIGKDD**(Special Interest Group on Knowledge Discovery and Data Mining)主办,堪称数据挖掘研究领域的顶级会议。
|
||||
|
||||
KDD最早是从1989年开始的KDD 研讨班(Workshop)发展而来,当时的研讨班依托于人工智能顶级会议IJCAI大会或者AAAI大会,而后在1995年升级成为会议的模式,到现在已经有20多年的历史。今年的KDD大会于8月13日至17日在加拿大哈利法克斯成功召开。
|
||||
|
||||
SIGKDD每年都会奖励一篇论文,这篇论文要在过去十年间对研究、方法论以及实践产生重大影响,这就是所谓的**时间检验奖**(Test of Time Award),引用次数以及对一个领域的影响力度是评选这个奖项的重要指标。
|
||||
|
||||
2017年的KDD时间检验奖授予了美国康奈尔大学信息科学系主任、计算机科学系教授索斯藤·乔基姆斯(Thorsten Joachims)。这次授予是为了表彰他的论文《线性时间内训练线性支持向量机》(Training Linear SVMs in Linear Time),这篇论文也是2006年的KDD最佳论文,引用数超过1600多次。
|
||||
|
||||
## Thorsten的学术贡献
|
||||
|
||||
Thorsten是一位机器学习界享有盛誉的学者,也是ACM和AAAI的双料院士,他所有论文的引用数加起来超过了4万次。2001年从德国多特蒙德大学博士毕业后,他正式加入康奈尔大学从事机器学习研究。
|
||||
|
||||
获得这个奖项之前,Thorsten曾多次获得重要奖项,比如2017年ACM WSDM的最佳论文奖(Best Paper Award)、2016年ACM SIGIR的时间检验奖、2015年ACM KDD的时间检验奖、2009年ECML的最佳论文奖、2009年ICML的10年最佳论文奖(Best 10-Year Paper Award)、2006年ACM KDD的最佳论文奖、2005年ICML的最佳论文奖、2005年ICML的优秀学生论文奖、2005年ACM KDD的最佳学生论文奖等。
|
||||
|
||||
Thorsten在机器学习领域一直有着非常特殊的贡献。首先,他在支持向量机(SVM)的应用上做出了诸多努力。比如这次的时间检验奖,**就是奖励他如何把支持向量机的训练达到线性复杂度,从而使支持向量机在大规模数据上的应用成为可能。**
|
||||
|
||||
Thorsten还致力于把支持向量机的基本算法,也就是仅仅支持分类问题和回归问题的算法,应用到更加复杂的有结构的输出结果上,俗称结构化的支持向量机算法。得益于这项工作,支持向量机可以对信息检索中很多复杂的、非二分的评估指标进行直接优化,如F1值(F-score)、平均精度均值(Mean Average Precision),从而让支持向量机的应用变得更加广阔。
|
||||
|
||||
在让支持向量机能够顺利应用到信息检索的过程中,Thorsten还发现了另外一个问题,那就是如何利用搜索引擎的间接用户反馈(Implicit Feedback)来训练排序算法(经常是一个结构化的支持向量机模型)。具体来说,传统的搜索系统和信息检索系统主要是依靠人工标注的训练数据来进行优化和评估。这里所说的人工标注训练数据,主要是指人为地评价目标查询关键字和所对应的网页是否相关。
|
||||
|
||||
早期大家发现,虽然搜索引擎可以利用这样的数据来优化排序算法,但是搜索引擎在使用过程中会产生很多用户数据。这些数据可以是用户点击搜索页面结果产生的信息,也可以是其他的信息(比如用户在搜索页面的驻留时间等等)。早期这些信息并没有用于优化搜索引擎。以Thorsten为主的一批学者意识到点击信息的重要性,然后开始利用这些数据来训练和评估排序算法。这是Thorsten的第二个主要学术贡献。
|
||||
|
||||
Thorsten第三个主要学术贡献,也是他最近几年的学术成功,那就是把**因果推论(Causal Inference)**和机器学习相结合,从而能够更加无偏差地训练模型。可以说这部分工作开创了一个新领域。
|
||||
|
||||
长期以来,如何有效地应用用户产生的交互数据来进行模型训练,都是大规模机器学习特别是工业界机器学习的难点。一方面,工业系统能够产生很多用户数据;另一方面,这些用户数据又受到当前部署系统的影响,一般都有一定的偏差。
|
||||
|
||||
因此工业级机器学习系统面临一个长期挑战,那就是,如何能够在评估模型以及训练模型的时候考虑到这样的偏差,从而去除这样的偏差。
|
||||
|
||||
Thorsten利用因果推论中的倾向评分(Propensity Scoring)技术以及多臂赌博机(Multi-armed Bandit)思想,把这样的方法成功地引入到机器学习中,使得无偏差地训练模型成为可能。目前,这方面的新研究和新思想正在机器学习以及应用界产生越来越多的共鸣。
|
||||
|
||||
## 线性大规模支持向量机
|
||||
|
||||
回到这篇时间检验奖的论文,它解决的是大规模优化支持向量机的问题,特别是线性支持向量机。这篇文章**第一次提出了简单易行的线性支持向量机实现**,包括对有序回归(Ordinal Regression)的支持。算法对于分类问题达到了O(sn)(其中s是非0的特征数目而n是数据点的个数),也就是实现了线性复杂度,而对有序回归的问题达到了O(snlog(n))的复杂度。算法本身简单、高效、易于实现,并且理论上可以扩展到核函数(Kernel)的情况。
|
||||
|
||||
在此之前,很多线性支持向量机的实现都无法达到线性复杂度 。比如当时的LibSVM(台湾国立大学的学者发明)、SVM-Torch、以及早期的SVM-Light中采用的分解算法(Decomposition Method)都只能比较有效地处理大规模的特征。而对于大规模的数据(n),则是超线性(Super-Linear)的复杂度。
|
||||
|
||||
另外的一些方法,能够训练复杂度线性地随着训练数据的增长而增长,但是却对于特征数N呈现了二次方(N^2)的复杂度。因此之前的这些方法无法应用到大规模的数据上。这样的情况对于有序回归支持向量机更加麻烦。从德国学者拉尔夫·赫布里希(Ralf Herbrich)提出有序回归支持向量机以来,一直需要通过转化为普通的支持向量机的分类问题而求解。这个转换过程需要产生O(n^2)的训练数据,使得整个问题的求解也在这个量级的复杂度。
|
||||
|
||||
这篇文章里,Thorsten首先做的是对普通的支持向量机算法的模型形式(Formalism)进行了变形。他把传统的分类支持向量机(Classification SVM)写成了**结构化分类支持向量机(Structural Classification SVM)**,并且提供了一个定理来证明两者之间的等价性。粗一看,这个等价的结构化分类支持向量机并没有提供更多有价值的信息。然而这个新的优化目标函数的对偶(Dual)形式,由于它特殊的稀疏性,使它能够被用来进行大规模训练。紧接着,Thorsten又把传统的有序回归支持向量机的优化函数,写成了结构化支持向量机的形式,并且证明了两者的等价性。
|
||||
|
||||
把两种模型表达成结构化向量机的特例之后,Thorsten开始把解决结构化向量机的一种算法——**切割平面算法(Cutting-Plane)**,以下称CP算法,运用到了这两种特例上。首先,他展示了CP算法在分类问题上的应用。简单说来,这个算法就是保持一个工作集合(Working Set),来存放当前循环时依然被违反的约束条件(Constraints),然后在下一轮中集中优化这部分工作集合的约束条件。
|
||||
|
||||
整个流程开始于一个空的工作集合,每一轮优化的是一个基于当前工作集合的支持向量机子问题,算法直到所有的约束条件的误差小于一个全局的参数误差为止。Thorsten在文章中详细证明了这个算法的有效性和时间复杂度。相同的方法也使得有序回归支持向量机的算法能够转换成为更加计算有效的优化过程。
|
||||
|
||||
Thorsten在文章中做了详尽的实验来展现新算法的有效性。从数据的角度,他使用了5个不同的数据集,分别是路透社RCV1数据集的好几个子集。数据的大小从6万多数据点到80多万数据点不等,特征数也从几十到四万多特征不等,这几种不同的数据集还是比较有代表性的。从方法的比较上来说,Thorsten主要比较了传统的分解方法。
|
||||
|
||||
有两个方面是重点比较的,第一就是训练时间。在所有的数据集上,这篇文章提出的算法都比传统算法快几个数量级,提速达到近100倍。而有序回归的例子中,传统算法在所有数据集上都无法得到最后结果。Thorsten进一步展示了训练时间和数据集大小的线性关系,从而验证了提出算法在真实数据上的表现。
|
||||
|
||||
第二个重要的比较指标是算法的准确度是否有所牺牲。因为有时候算法的提速是在牺牲算法精度的基础上做到的,因此验证算法的准确度就很有意义。在这篇文章里,Thorsten展示,提出的算法精度,也就是分类准确度并没有统计意义上的区分度,也让这个算法的有效性有了保证。
|
||||
|
||||
Thorsten在他的软件包SVM-Perf中实现了这个算法。这个软件包一度成了支持向量机研究和开发的标准工具。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我和你分享了Thorsten的这篇论文,堪称支持向量机文献史上的经典。一起来回顾下要点:第一,Thorsten在机器学习领域有三大主要学术贡献;第二,这篇论文理论论证非常扎实,算法清晰,而且之后通过有效的实验完全验证了提出算法的有效性。文章开启了支持向量机在搜索领域的广泛应用,不愧为2006年的KDD最佳论文以及今年的时间检验奖论文。
|
||||
|
||||
最后,给你留一个思考题,在什么应用场景下,线性大规模支持向量机可以有比较好的效果?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
扩展阅读:[Training Linear SVMs in Linear Time](https://www.cs.cornell.edu/people/tj/publications/joachims_06a.pdf)
|
||||
|
||||
|
77
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/002 | 精读2017年KDD最佳研究论文.md
Normal file
77
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/002 | 精读2017年KDD最佳研究论文.md
Normal file
@ -0,0 +1,77 @@
|
||||
<audio id="audio" title="002 | 精读2017年KDD最佳研究论文" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/dc/75/dcd0d2117a91b5d1d79a389c57800a75.mp3"></audio>
|
||||
|
||||
前面我们介绍过KDD大会的时间检验奖,每年大会的另外一个亮点奖项就是最佳论文奖,有两类,一类是最佳研究论文,一类是最佳应用数据科学论文。今天我就先来说说前者。
|
||||
|
||||
大会每年都会在众多的学术研究论文中,选择最有新意和价值的研究论文,评选出最佳研究论文的第一名和第二名。从过去十多年的经验来看,KDD历年的最佳研究论文,都会对之后很多领域的研究有开创性的影响。因此,不论是从阅读经典文献的角度,还是从学习最新研究成果的角度来说,认真分析和探讨每年的最佳研究论文都是一个不错的选择。
|
||||
|
||||
今天,我就带你认真剖析一下KDD 2017年的最佳研究论文《通过挖掘类比关系加速创新》(Accelerating Innovation Through Analogy Mining)。
|
||||
|
||||
## 作者群信息介绍
|
||||
|
||||
第一作者汤姆·霍普(Tom Hope)来自耶路撒冷的希伯来大学(The Hebrew University of Jerusalem),计算机博士,在读第三年。同时,他还是英特尔以色列的资深数据科学员,对深度学习的很多方面都有研究。目前他正在写一本基于TensorFlow的深度学习简明技术书籍。
|
||||
|
||||
第四作者达夫娜·沙哈夫(Dafna Shahaf)是霍普的博士导师,目前在希伯来大学计算机系任助理教授。达夫娜于2012年从卡内基梅隆大学博士毕业。她曾经在微软研究院以及富士通公司实习,并在斯坦福大学攻读博士后。达夫娜的论文曾获得2010年的KDD最佳研究论文,可以说她一直站在机器学习研究的前沿。
|
||||
|
||||
第二作者乔尔(Joel Chan)是来自卡内基梅隆大学人机交互学院的科学家。乔尔于2014年从匹兹堡大学毕业,获得认知心理学博士学位。他一直在人机交互领域进行研究。
|
||||
|
||||
第三作者安尼凯特·科图(Aniket Kittur)是来自卡内基梅隆大学人机交互学院的副教授。他于2009年从加州大学洛杉矶分校毕业,获得认知心理学博士学位,之后就一直在卡内基梅隆大学任教。
|
||||
|
||||
从整个作者群的情况来看,这篇文章是一个比较典型的机器学习技术与人机交互领域的交叉成果。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
我们先来看一下这篇文章的主要贡献。当然,要想深入理解这篇文章的贡献,我们还要先弄明白,这篇文章主要解决的是一个什么场景下的问题。
|
||||
|
||||
**这篇文章主要阐述了帮助创新的一个重要步骤,那就是如何找到合适并且有效的类比案例**。什么叫作类比案例?在人类发展的历史上,特别是科学技术的革新历程中,有很多重要的发明发现,都是因为当时的科学家借鉴了一些类似场景中的解决方案,或者是从这些场景中获取了灵感,进一步做出了创新。在这个步骤中,从类似场景中借鉴往往被称作类比。
|
||||
|
||||
比如,莱特兄弟从自行车中得到灵感,制造出了可以滑行的飞行器。再比如,诺贝尔生理学或医学奖得主萨尔瓦多·卢里亚从对赌博机运行的观察中,进一步发现了细菌基因突变的规律。同时,类比在很多国家的法律,不管是成文中或者是运行中都比比皆是。因此,我们可以看到,**如何找到合适的类比,并能从中获取灵感,可能就是创新的一个关键因素**。
|
||||
|
||||
时至互联网时代的今天,我们已经有很多数据库、文献库可以作为获取类比灵感的重要数据源泉。比如,谷歌学术搜索(Google Scholar)有上百万的论文和专利信息;OpenIDEO有上百份关于社会问题的分析;Quirky有超过两百万份的产品构想;InnoCentive有超过四万份的社会、政治以及科技方面的解决方案;美国专利局有超过九百万份的专利信息,类似的例子还有很多。
|
||||
|
||||
与此同时,海量数据也为寻找灵感带来了很大的挑战。如何才能在这些百万级别的信息库里快速定位可能的类比场景,就成了阻碍创新速度的一大瓶颈。
|
||||
|
||||
找到类比场景有一个很大的挑战,那就是如何定义“类似”或者“相似”。如果仅从一些表面特征来看,很多场景之间的相似程度是很低的。因此,好的类比场景一定是能够找到,刨除表象相似以外的深层次相似的案例。另外一个挑战就是,寻找类比场景中,是否能有一个**非常丰富的数据结构**来支持推理,如果有,往往就能有比较简单的方法。
|
||||
|
||||
这篇论文的重点是如何从海量的无结构或者弱结构的文本数据中找到这样的类比。我们可以看出,这确实是一个巨大的挑战。
|
||||
|
||||
理解了这篇论文的目的,我这里就直接给你总结一下它的贡献,那就是提出了一种自动的在海量无结构的文本数据中挖掘类比场景的方法。这篇文章关注的是产品信息数据。作者们通过实际的数据,验证了提出的方法比之前的一些文本处理方法更加有效。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
了解了这篇文章的目的和贡献后,接下来,我就来剖析一下作者们究竟提出了一个什么方法。
|
||||
|
||||
首先,作者们提出了一组叫“**目的**”(Purpose)和“**机制**”(Mechanism)的概念。什么叫“目的”呢?那就是当前的产品是要解决什么问题的。什么叫“机制”呢?那就是当前的产品是使用什么手段或者方法来解决这个问题的。对于一个产品,如果我们能够明确这个产品的目的和机制,找到类比就变得更加容易。
|
||||
|
||||
比如,我们可以针对某一个问题,相同的目的,采用不同的机制或者对不同的问题采用相同的机制。作者们认为,**这种对产品信息的分类符合很多工程设计的过程,是创新过程中的一个必要环节**。
|
||||
|
||||
有了这种想法以后,很自然的下一个步骤就是如何从数据中学习到目的和机制,如何自动挖掘出海量产品信息的目的和机制。要想学习到这样的信息,作者们提出了一种依靠标签数据的监督学习(Supervised Leanring)机制。具体说来,作者们把文本信息中的每句话、短语交给亚马逊土耳其机器人(Amazon Mechanical Turk)上的在线工人,来标注每个文本信息是目的信息还是机制信息。也就是说,作者们依靠有标注的数据来训练提出的算法。
|
||||
|
||||
首先,我们有一组文本,每组文本都有这些文本的原始文字。**针对每个文档,我们都收集K个目的标注和K个机制标注**。这时,我们定义一组“目的标注”(Purpose Annotation)向量,其实也就是一组0或者1的向量。当文本原始文字中的某个字被标识为目的的时候,这个向量的相应元素置1,反之置0。类似的,我们也可以定义“机制标注”(Mechanism Annotation)向量。因为我们有K个标注,因此我们也有相应的K个“目的标注”向量和“机制标注”向量。这两组向量可以说是原始标签信息的一种向量的表达。
|
||||
|
||||
下一步就是从每一个有标签信息的文档里**产生唯一的目的向量和机制向量**。这篇文章采用的方法是,利用每个单词的**嵌入向量**(Embedding)来获得这个唯一的向量。
|
||||
|
||||
具体方法是这样的,首先,针对每一个标注(总共有K个),我们收集属于这个标注的单词的嵌入向量,并把这些嵌入向量都拼接起来。然后计算这组拼接好的向量所对应单词的**TF-IDF值**(Term Frequency–Inverse Document Frequency,词频-逆向文件频率),并且取TF-IDF值最高的一些单词相对应的嵌入向量,加权平均以后,就得到了相应的唯一的目的向量或者是机制向量。作者们发现这种利用TF-IDF值加权的方法可以更加有效地表达文本的各种重要信息。注意,这个步骤是依赖于文档标签的,也就是说,我们只能对训练数据进行这样的构造。
|
||||
|
||||
到目前为止,我们描述了如何从文本到达文本对应的目的向量和机制向量的步骤。那么,如何基于这样的数据以及向量,来对未知的文档进行提取目的向量和机制向量呢?文章采用了深度模型**RNN**(Recurrent Neural Network,循环神经网络),具体说来是双向的RNN并且有一个**GRU**(Gated Recurrent Unit,门控循环单元)。
|
||||
|
||||
这里我就不复述细节了,总体的思路就是,根据文档的嵌入向量信息,我们希望得到一组文档的**隐含表达**(中间参数),然后可以从这个隐含表达来预测目的向量和机制向量。注意,因为需要预测两组目标,目的向量和机制向量,因此,这里至少需要分别有两组参数。
|
||||
|
||||
除了预测文档的目的向量和机制向量以外,作者们还提出了一个用**少数关键词**来解释这两组变量的机制。具体说来,就是设立一个新的学习目标函数,希望通过少数关键词所对应的嵌入向量来重构目的向量或者机制向量,让得到的误差最小。
|
||||
|
||||
## 方法的实验效果
|
||||
|
||||
作者们使用了Quirky数据集,通过亚马逊土耳其机器人标注了八千多组产品信息。首先,检测了利用学习到的目的向量和机制向量,是否能够比较容易地从海量数据中提取相应的类比信息。这里,作者们直接利用把目的向量和机制向量拼接的方式来表达问题。答案是,效果非常显著。**在前1%到25%的提取结果中,精度(Precision)和召回(Recall)都比之前的标准文本处理方法,比如LDA、TF-IDF、全局的嵌入向量要好10%到20%**,可以说这是非常有效果的。
|
||||
|
||||
作者们还测试了,通过提出的方法,是否能够为用户推荐比较好的类比场景。这里,文章又找了38个亚马逊土耳其机器人的虚拟工人来为12个产品思路打分。在不知道推荐方法的情况下,虚拟工人认为这篇文章提出的方法能够推荐更有新意、在深层次上更加类似的场景。这也部分解决了我们前面说到的文章希望解决的问题。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了KDD 2017年的年度最佳研究论文,这篇论文提出了一种自动的方法来挖掘类比信息,为快速创新铺平道路。一起来回顾下要点:第一,我简单地介绍了这篇文章的作者群信息,帮你了解到这篇文章是机器学习和人机交互研究的一个结合。第二,我详细地介绍了这篇文章想要解决的问题以及贡献。第三,我简要地介绍了文章所提出方法的核心内容。
|
||||
|
||||
最后,给你留一个思考题,这篇文章提出的是使用标注信息来获取目的向量和机制向量,我们有没有办法能够不使用标注信息,采用完全无监督的方式呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
拓展阅读:[Accelerating Innovation Through Analogy Mining](http://www.hyadatalab.com/papers/analogy-kdd17.pdf)
|
||||
|
||||
|
80
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/003 | 精读2017年KDD最佳应用数据科学论文.md
Normal file
80
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/003 | 精读2017年KDD最佳应用数据科学论文.md
Normal file
@ -0,0 +1,80 @@
|
||||
<audio id="audio" title="003 | 精读2017年KDD最佳应用数据科学论文" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/eb/c7/eb69e4c4e629845fa8e03551e9f831c7.mp3"></audio>
|
||||
|
||||
周一我们讲了2017年KDD最佳研究论文,今天我们继续来聊今年的KDD最佳应用数据科学论文。
|
||||
|
||||
与研究类论文不同的是,KDD的应用类学术论文更加强调论文所描述的方法或者系统在实际应用中发挥的作用。比如,很多论文都是对现有的已部署的系统进行总结,对工业界的很多研究人员和工程师往往都有不小的借鉴意义。和研究类论文一样,从阅读经典文献和学习最新研究成果的角度,我们都应该认真分析和探讨每年的最佳应用类论文。
|
||||
|
||||
2017年KDD最佳应用数据科学论文题目是,《HinDroid:基于结构性异构信息网络的智能安卓恶意软件检测系统》(HinDroid: An Intelligent Android Malware Detection System Based on Structured Heterogeneous Information Network)。可以说2017年是信息安全备受关注的一年,2016年美国大选过程中传出了种种关于俄罗斯利用黑客入侵大选候选人的新闻,让整个社会对信息安全的话题变得异常敏感。这是一篇有关如何智能地分析安卓恶意软件的论文,真是非常应景。
|
||||
|
||||
## 作者群信息介绍
|
||||
|
||||
文章的第一作者和第二作者都来自西弗吉尼亚大学(West Virginia University)的计算机科学与电气工程系。第一作者Shifu Hou是该系的博士生,先后发表过多篇论文。第二作者叶艳芳(Yanfang Ye)是该系的助理教授。叶艳芳2010年从厦门大学博士毕业,先后在金山公司和科摩多(Comodo Security Solutions)从事信息安全方面的研究和开发工作。2013年,她加入西弗吉尼亚大学任教。这篇KDD论文因为第一作者也是在读学生,因此也是最佳学生论文。
|
||||
|
||||
第三作者宋阳秋(Yangqiu Song)是来自香港科技大学的计算机系助理教授。宋阳秋有丰富的学术和工业界经历。2016年加入香港科技大学,在这之前曾经在西弗吉尼亚大学任教。2012年到2015年之间他曾在伊利诺伊大学香槟分校、香港科技大学、华为诺亚方舟实验室等地访问。2009年到2012年曾在微软亚洲研究院和IBM研究院工作。2009年于清华大学博士毕业。
|
||||
|
||||
最后一位作者是土耳其企业家米勒夫·阿杜勒哈尤格鲁(Melih Abdulhayoğlu)。他是科摩多(Comodo)的CEO,于1998年创立了公司。这篇论文挂了他的名字是因为使用了科摩多的数据。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
我们首先来看一下这篇文章的主要贡献。类似地,按照我们周一分析最佳研究论文的思路,首先必需弄明白,这篇文章主要解决了什么场景下的问题。
|
||||
|
||||
这篇文章希望解决的问题描述起来很直观,那就是**如何有效地监测安卓手机系统下的恶意软件**。经预测,到2019年,全球的手机市场中,77.7%将是智能手机,这里面安卓系统的市场占有率至少是80%。由于安卓系统的开放性以及分散的各类安卓手机软件市场,对安卓软件进行监控和分析存在很大难度。各类恶意软件在安卓生态系统中可以说层出不穷,比如Geinimi、DroidKungfu以及Lotoor等等。更悲观的统计来自赛门铁克(Symantec)的《互联网安全威胁报告》,认为五分之一的安卓软件是恶意软件。
|
||||
|
||||
之前很多恶意软件的分析和检测都是基于某种“**指纹签字**”技术,然而这种技术常常被恶意软件开发者的新手段绕过。因此,寻找更加复杂有效的检测方式就成了各种信息安全公司所追逐的目标。
|
||||
|
||||
**这篇论文的主要贡献是根据安卓的API,提出了一种新的基于结构性异构信息网络的方法,来对安卓程序的API模式进行更加复杂的建模,从而能够理解整个安卓程序的语义**。作者们还采用了**多核学习**(Multi-Kernel Learning)的方法,在结构性异构信息网络的基础上对程序语义模式进行分类。
|
||||
|
||||
最后,文章提出的方法在科摩多的真实数据上达到了非常高的准确度,远远优于现在的一些主流方法。并且,科摩多已经在产品中部署了这个方法。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
了解了这篇文章的目的和贡献,接下来,我就来剖析一下作者们提出的方法。
|
||||
|
||||
首先,需要**将安卓的程序代码转换为可以分析的形式**。一般来说,安卓的软件被打包为后缀名为Dex的Dalivik执行文件,这个执行文件无法被直接分析。于是,需要把这个执行文件通过一个叫 **Smali** 的反汇编器解析成Smali代码。这个时候,软件的语义就能够通过Smali代码来解析了。作者们从Smali代码中提取所有的API调用,通过对API的分析来对程序行为建模。
|
||||
|
||||
下一步,就是要**从繁复的API调用中摸索出这里面的规律**。作者们这个时候构建了**四类矩阵**来表达API和某个App之间的基本特征:
|
||||
|
||||
<li>
|
||||
某一个App是否包含了某一个API;
|
||||
</li>
|
||||
<li>
|
||||
某两个API是否同时出现在某一段代码中;
|
||||
</li>
|
||||
<li>
|
||||
某两个API是否出现在同一个App中;
|
||||
</li>
|
||||
<li>
|
||||
某两个API是否使用了相同的调用方法。
|
||||
</li>
|
||||
|
||||
可以看出,这些矩阵可以抓住API和App之间的一个基本信息,还可以抓住一系列API同时出现进行某种操作的特征信息。这些矩阵成了发现高阶规律的基础。
|
||||
|
||||
为了发现更加复杂的规律,作者们在这里引入了一个工具叫**异构信息网络**。异构信息网络的概念最早由伊利诺伊大学香槟分校的数据挖掘权威韩家炜(Jiawei Han)和他当时的学生孙怡舟(Yizhou Sun,目前在加州大学洛杉矶分校任教)提出。异构信息网络的核心思想就是希望能够表达一系列实体(Entity)之间的复杂规律。
|
||||
|
||||
传统的方法把实体表达成图(Graph)的节点,实体之间的关系表达成节点之间的链接。这样的方式忽略了实体本身的不同以及关系的类型也有所不同。异构信息网络就是更加完整和系统地表达多种不同实体和实体关系的一种建模工具。在这篇文章中,有两类实体:App和API调用,有四类关系(和刚才定义的矩阵相同)。而刚才定义的矩阵其实就是这四类关系所对应的图的邻接矩阵。
|
||||
|
||||
把App和API的关系描述成为异构信息网络以后,下面的工作就是**定义更高阶的规律关系**。为了更好地定义这些复杂关系,作者们使用了一个叫**元路径**(Meta-Path)的工具。元路径其实是提供一个描述性的模板语言来定义高阶关系。
|
||||
|
||||
比如,我们可以定义一个从App到API再到App的“路径”,用于描述两个App可能都含有相同的API调用。这个路径就可以帮助我们从最开始的四个矩阵推出更加复杂的矩阵来表达一些信息。那么,根据人们的领域知识(这里就是安全领域),作者们就定义了多达**16种元路径**,从而全面捕捉App和API之间的各种关系。
|
||||
|
||||
利用异构信息网络和元路径构建了程序的语义表达后,下一步就是**进行恶意软件的判别**。这里,作者们采用了多核学习的思想。简而言之,就是把之前通过元路径所产生的新矩阵看作一个“核”。这里的多核学习就是要学习一个线性的分类器,特征是每个App到某一个核的一个非线性转换,这个转换是在学习过程中得到的。换句话说,这个多核学习的流程要同时学习一个分类器来判断一个程序是不是恶意程序,还需要在这个过程中学习从App到核的转换。
|
||||
|
||||
## 方法的实验效果
|
||||
|
||||
作者们使用了科摩多的数据集,收集了2017年两个月里1834个App的信息。正常程序和恶意程序几乎各一半。另外还有一个数据集包含3万个App信息,也几乎是正例负例各一半。从实验结果来看,结合了16个定义好的元路径的多核学习能够**实现高达98%的F1值**。F1值可以认为是精度和召回的一个平衡,同时**准确率也是高达98%**。
|
||||
|
||||
文章还比较了一些其他比较流行的方法,比如神经网络、朴素贝叶斯(Naïve Bayes)分类器、决策树以及支持向量机,这些方法基本的F1值都在85%和95%之间,和文章提到的方法有较大差距。另外,文章还和现在的一些商业软件,比如Norton、Lookout、CM做了比较。这些商业软件的准确度也在92%上下徘徊。因此,文章所采用的方法的确比之前的很多方法都更有效果。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了KDD 2017年的最佳应用类论文。这篇论文提出了,如何来分析安卓手机软件的行为进而检测手机应用是否是恶意软件。一起来回顾下要点:第一,简要介绍了这篇文章的作者群信息。第二,详细介绍了这篇文章要解决的问题以及贡献。第三,简要分析了文章提出方法的核心内容 。
|
||||
|
||||
总结一下,文章解决的问题就是如何有效监测安卓手机系统下的恶意软件,主要贡献是提出了一种新的基于结构性异构信息网络的方法,来理解安卓程序的语义。使用元路径的工具定义复杂关系,还采用了多核学习的方法完成恶意软件的判别。论文使用科摩多的数据集,验证了所提出的方法比当下流行的一些其他方法都更加有效。
|
||||
|
||||
最后,给你留一个思考题,文章中提到的多核学习方法这个步骤,是不是必需的?能否换成其他方法呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
拓展阅读:[HinDroid: An Intelligent Android Malware Detection System Based on Structured Heterogeneous Information Network](http://delivery.acm.org/10.1145/3100000/3098026/p1507-hou.pdf?ip=104.245.8.202&id=3098026&acc=OPENTOC&key=4D4702B0C3E38B35%2E4D4702B0C3E38B35%2E4D4702B0C3E38B35%2E054E54E275136550&CFID=824613284&CFTOKEN=25201339&__acm__=1509500476_9d244f060207e966c107eb505646ed55)
|
||||
|
||||
|
63
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/004 | 精读2017年EMNLP最佳长论文之一.md
Normal file
63
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/004 | 精读2017年EMNLP最佳长论文之一.md
Normal file
@ -0,0 +1,63 @@
|
||||
<audio id="audio" title="004 | 精读2017年EMNLP最佳长论文之一" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/1d/e4/1d11479cd402bbcc29efa206ec16eee4.mp3"></audio>
|
||||
|
||||
自然语言处理实证方法会议**EMNLP**(Conference on Empirical Methods in Natural Language Processing),是由国际计算语言学协会**ACL**(Association for Computational Linguistics)的专委会**SIGDAT**(Special Interest Group on Linguistic Data and Corpus-based Approaches to NLP)主办,每年召开一次,颇具影响力和规模,是自然语言处理类的顶级国际会议。从1996年开始举办,已经有20多年的历史。2017年的EMNLP大会于9月7日到11日在丹麦的哥本哈根举行。
|
||||
|
||||
每年大会都会在众多的学术论文中挑选出两篇最具价值的论文作为最佳长论文(Best Long Paper Award)。 今天,我就带你认真剖析一下EMNLP今年的最佳长论文,题目是《男性也喜欢购物:使用语料库级别的约束条件减少性别偏见的放大程度》(Men Also Like Shopping: Reducing Gender Bias Amplification using Corpus-level Constraints) 。这篇文章也是很应景,近期学术圈对于数据和机器学习算法有可能带来的“**偏见**”(Bias)感到关切,有不少学者都在研究如何能对这些偏见进行评估、检测,进而可以改进甚至消除。
|
||||
|
||||
## 作者群信息介绍
|
||||
|
||||
第一作者赵洁玉(Jieyu Zhao),论文发表的时候在弗吉尼亚大学计算机系攻读博士学位,目前,已转学到加州大学洛杉矶分校,从事如何从机器学习算法中探测和消除偏见的研究。之前她从北京航空航天大学获得学士和硕士学位,曾于2016年在滴滴研究院实习。
|
||||
|
||||
第二作者王天露(Tianlu Wang)也是来自弗吉尼亚大学计算机系的博士生,之前在浙江大学获得计算机学士学位。第三作者马克·雅茨卡尔(Mark Yatskar)是来自华盛顿大学的计算机系博士生,已在自然语言处理以及图像处理领域发表过多篇高质量论文。
|
||||
|
||||
第四作者文森特(Vicente Ordóñez)目前在弗吉尼亚大学计算机系任助理教授。他的研究方向是自然语言处理以及计算机视觉的交叉学科。他于2015年从北卡罗来纳大学教堂山分校计算机系博士毕业。博士期间,他在微软研究院、eBay研究院以及谷歌都有过实习经历。他是第二作者王天露的博士导师。
|
||||
|
||||
文章最后一位作者是Kai-Wei Chang,也是第一作者赵洁玉的导师。他目前在加州大学洛杉矶分校任助理教授,之前在弗吉尼亚大学任职。他于2015年从伊利诺伊大学香槟分校博士毕业,师从著名教授丹·罗斯(Dan Roth)。在之前的研究生涯中,曾先后3次在微软研究院实习,也在谷歌研究院实习过。在他研究的早期,曾参与了LibLinear这个著名支持向量机软件的研发工作。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
机器学习的一个重要任务就是通过数据来学习某些具体事项。最近机器学习的研究人员发现,数据中可能蕴含着一些社会赋予的偏见,而机器学习算法很有可能会放大这些偏见。这种情况在自然语言处理的相关任务中可能更为明显。比如,在一些数据集里,“做饭”这个词和“女性”这个词一起出现的比例可能要比和“男性”一起出现的比例高30%,经过机器学习算法在这个数据集训练之后,这个比例在测试数据集上可能就高达68%了。因此,虽然在数据集里,社会偏见已经有所呈现,但是这种偏见被机器学习算法放大了。
|
||||
|
||||
因此,**这篇文章的核心思想就是,如何设计出算法能够消除这种放大的偏见,使得机器学习算法能够更加“公平”**。注意,这里说的是消除放大的偏见,而不是追求绝对的平衡。比如,我们刚才提到的数据集,训练集里已经表现出“女性”和“做饭”一起出现的频率要高于“男性”和“做饭”一起出现的频率。那么,算法需要做的是使这个频率不会进一步在测试集里升高,也就是说,保持之前的30%的差距,而不把这个差距扩大。这篇文章并不是追求把这个差距人为地调整到相同的状态。
|
||||
|
||||
文章提出了一个**限制优化(Constrained Optimization)算法**,为测试数据建立限制条件,使机器学习算法的结果在测试集上能够得到和训练集上相似的偏见比例。注意,这是对已有测试结果的一个调整(Calibration),因此可以应用在多种不同的算法上。
|
||||
|
||||
作者们使用提出的算法在两个数据集上做了实验,得到的结果是,新的测试结果不但能够大幅度(高达30%至40%)地减小偏见,还能基本保持原来的测试准确度。可见,提出的算法效果显著。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
那么,作者们提出的究竟是一种什么方法呢?
|
||||
|
||||
首先,引入了一个叫“**偏见值**”(Bias Score)的概念。这个值检测某一个变量和目标变量之间的比例关系。例如,“男性”这个词和某个动词(比如之前我们举了“做饭”)一起出现的比例关系以及“女性”这个词和同一个动词一起出现的比例关系。
|
||||
|
||||
注意,因为“男性”和“女性”都是“性别”的可选项,因此,这两个词对于同一个动词的比例关系的和一定是1。偏见值在训练集上和测试集上的差别,构成了衡量偏见是否被放大的依据。在之前的例子中,“女性”和“做饭”一起出现的的偏见值在训练集上是0.66,而到了测试集则变成了0.84,这个偏见被算法放大。
|
||||
|
||||
有了偏见值这个概念以后,作者们开始**为测试集的结果定义限制条件**(Constraint)。这里的一个基本思想就是,要对测试集的预测标签进行重新选择,使测试标签的预测结果和我们期待的分布相近。用刚才的例子就是说,我们要让“女性”在“做饭”这个场景下出现的可能性从0.84回归到0.66附近。能够这么做是因为这个算法需要对测试结果直接进行调整。
|
||||
|
||||
对所有的限制条件建模其实就变成了一个经典的限制优化问题。这个问题需要对整个测试数据的预测值进行优化,那么,这个优化就取决于测试数据集的大小,往往是非常困难的。于是,作者们在这里采用了**拉格朗日简化法**(Lagrangian Relaxation)来对原来的优化问题进行简化。
|
||||
|
||||
也就是说,原来的限制优化问题经过拉格朗日简化法后,变成了非限制优化问题,原来的算法就可以成为一个动态更新的过程。针对每一个测试用例,都得到当前最优的标签更改方案,然后又进一步更新拉格朗日参数,这样对整个测试数据集遍历一次后算法就中止了。
|
||||
|
||||
## 方法的实验效果
|
||||
|
||||
作者们使用了两个实验数据。一个是**imSitu**,一个是**MS-COCO**。imSitu是一个视觉语义角色识别(Visual Semantic Role Labeling)的任务,里面有多达12万张图片和这些图片的文字语义信息。比如一些图片是关于做饭场景的,里面的角色就是男性或者是女性。作者们整理出了212个动词用作实验。MS-COCO是一个多标签图片分类问题(Multi-label Classification),需要对80类物品进行标签预测。
|
||||
|
||||
对于这两个任务,作者们都选择了**条件随机场**(Conditional Random Field)来作为基础模型。条件随机场往往是解决往往是解决这类问题方法的方法的第一选择。对于特征,作者们采用了数据集提供的基于深度学习的各种特征。在条件随机场的基础上,对测试集采用了提出的偏见调整算法。
|
||||
|
||||
值得指出的是,虽然算法本身需要使用测试数据,但并不需要知道测试数据的真实标签。标签信息仅仅是从训练集中得到。这一点也是作者们反复强调的。
|
||||
|
||||
从两个数据集的结果来看,效果都不错。原本的预测准确度并没有很大的降低,但是性别偏见值则在测试集的调整结果后大幅度降低,最大的结果可以降低40%以上。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了EMNLP 2017年的年度最佳长论文,这篇论文针对数据集可能带来的社会偏见以及机器学习算法可能进一步扩大这种偏见的问题,提出了一个对测试数据集的预测结果进行调整的算法。这个算法的核心是减小这种偏见,使偏见值在测试数据集中和训练数据集中的水平相当。
|
||||
|
||||
一起来回顾下要点:第一,简要介绍了这篇文章的作者群信息。第二,详细介绍了这篇文章要解决的问题以及贡献 。第三,介绍了文章提出方法的的核心内容 。
|
||||
|
||||
最后,给你留一个思考题,为什么机器学习算法可能扩大训练集上已有的偏见呢?这跟某些具体的算法有什么关系呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
拓展阅读:[Men Also Like Shopping: Reducing Gender Bias Amplification using Corpus-level Constraints](https://arxiv.org/abs/1707.09457)
|
||||
|
||||
|
81
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/005 | 精读2017年EMNLP最佳长论文之二.md
Normal file
81
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/005 | 精读2017年EMNLP最佳长论文之二.md
Normal file
@ -0,0 +1,81 @@
|
||||
<audio id="audio" title="005 | 精读2017年EMNLP最佳长论文之二" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/3f/4e/3ff146c0c0df7f59838a0ffe3bf48b4e.mp3"></audio>
|
||||
|
||||
EMNLP每年都会选出两篇最佳长论文,我们已经分析过第一篇《男性也喜欢购物:使用语料库级别的约束条件减少性别偏见的放大程度》。今天我继续来讲第二篇。
|
||||
|
||||
EMNLP 2017年最佳长论文的第二篇是《在线论坛中抑郁与自残行为风险评估》(Depression and Self-Harm Risk Assessment in Online Forums)。这篇文章探讨了利用自然语言处理技术来解决一个社会问题。最近一段时间以来,如何利用机器学习、数据科学等技术来解决和处理社会问题,正逐渐成为很多社会科学和机器学习研究的交叉领域。
|
||||
|
||||
## 作者群信息介绍
|
||||
|
||||
第一作者安德鲁·耶特斯(Andrew Yates),计算机博士,毕业于美国华盛顿的乔治城大学(Georgetown Univeristy),目前在德国马克思普朗克信息学院(Max Planck Institute for Informatics)攻读博士后。他在博士阶段已经发表了多篇采用深度学习技术和信息检索、自然语言处理相关的论文。
|
||||
|
||||
第二作者阿曼·可汗(Arman Cohan),来自伊朗,是乔治城大学计算机系博士生。阿曼已在信息检索和自然语言处理相关方向发表了多篇论文。2016年,在华盛顿的Medstar Health实习并发表了两篇论文。2017年暑假,在美国加州圣何塞(San Jose)的奥多比(Adobe)研究院实习。
|
||||
|
||||
第三作者纳兹利·哥汗(Nazli Goharian)也来自乔治城大学计算机系,目前在系里担任计算机教授。第一作者是他之前的学生,第二作者是他当前的学生。纳兹利在长达20年的职业生涯中先后在工业界和学术圈任职,可以说有很深厚的学术和工业背景,他在信息检索和文本分析领域已发表20多篇论文。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
在理解这篇文章的主要贡献之前,我们还是先来弄明白,这篇文章主要解决了一个什么场景下的问题。
|
||||
|
||||
现代社会,人们生活工作的压力越来越大。研究表明,很多人都可能受到各式各样精神疾病(Mental Conditions)的困扰。在当下发达的互联网时代,在线场所为这些精神疾病患者寻求帮助提供了大量的资源和信息,特别是一些专业的在线支持社区,或是一些更大的在线社区比如Twitter或者Reddit。
|
||||
|
||||
因此,研究这些人在各种在线社区的行为,对设计更加符合他们需要的系统有很大帮助。对于很多社会研究人员来说,分析这些人的精神状态,才能更好地帮助他们长期发展。
|
||||
|
||||
这篇文章提出了一个比较通用的框架,来分析这些精神疾患者的在线行为。在这个框架下,可以比较准确地分析发布信息的人是否有自残(Self-Harm)行为,还可以比较容易地分析哪些用户有可能有抑郁症(Depression)的状况。
|
||||
|
||||
整个框架利用了近年来逐渐成熟的深度学习技术对文本进行分析。所以,这里的应用思路很值得借鉴和参考,也可以用于其他场景。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
在介绍这篇文章提出的方法之前,作者们用不小的篇幅介绍了文章使用的数据集和如何产生数据的标签。
|
||||
|
||||
首先,作者们从著名的在线社区Reddit中找到和精神疾病有明确联系的帖子。这些帖子是按照一个事先准备的语料库来筛选的,这个语料库是为了比较高精度地发现与精神疾病相关的帖子。利用语料库里的句式,比如“我已经被诊断得了抑郁症”,这样就可以保证,找到的帖子在很大程度上是来自精神疾病患者的。
|
||||
|
||||
如果一个用户发布了这样的帖子,但在这之前发布的帖子少于100条,这个用户就不会包含在数据库中。做这样的筛选可能作者们的考虑是,太少的帖子无法比较全面地包含用户方方面面的行为。
|
||||
|
||||
作者们在Reddit社区中挖掘了从2006年到2016年十年时间里符合条件的所有帖子,并利用人工标注的方式筛选出了9210个有精神疾病困扰的用户。这些可以当做机器学习的正例。
|
||||
|
||||
那么如何寻找负例呢?作者们当然可以利用所有的用户,但是这样带来的后果很可能是研究没有可比性。如果正例的用户和负例的用户之间差别太大,我们就很难说这些差别是因为精神疾病造成的还是由其他区别带来的。于是,作者们想到的方法则是尽可能地对于每一个正例的用户都找到最接近的负例用户。
|
||||
|
||||
实际操作中,作者们采取了更加严格的方式,那就是负例的用户必须没有发布过任何与精神疾病相关的帖子,并且在其他方面都需要和正例用户类似。在这样的条件下,作者们找到了107274个负例用户。
|
||||
|
||||
对于数据集中的用户而言,每个用户平均发布969个帖子,平均长度都多于140个字。可以说,由这些用户构成的这个数据集也是本文的一个主要贡献,这个数据集用于分析抑郁症。
|
||||
|
||||
对于自残行为而言,作者们利用了一个叫ReachOut的在线社区的数据,收集了包括65024个论坛的帖子,其中有1227个帖子提到了自残。而对于提及自残的程度,数据分了五个等级用于表示不同的紧急情况。
|
||||
|
||||
这篇论文主要提出了基于卷积神经网络的文本分析框架,分别用于检测抑郁症用户和检测自残倾向度的两个任务中。虽然这两个任务使用的数据不同,最终采用的模型细节不同,但是两个任务使用的都是同一个框架。下面我就来说一说这个框架的主要思想。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/99/92/9973fb1c41652299ba033610c5979392.png" alt="" />
|
||||
|
||||
首先,作者们利用每个用户的发帖信息来对每一个用户进行建模,基本的思路是通过神经网络来对用户的每一个帖子建模,从中提取出有效信息,然后把有效信息汇总成用户的一个表达。有了这个思路,我们再来看看具体是怎么做的。
|
||||
|
||||
每个帖子一个范围内的单词首先通过卷积层(Convolutional Layer)提取特征,然后提取的特征再经过最大抽取层(Max Pooling Layer)集中。这个步骤基本上就是把目前图像处理的标准卷积层应用到文本信息上。每一个帖子经过这样的变换就成了特征向量(Feature Vector)。有了这样的特征向量之后,用户的多个特征向量整合到一起,根据不同的任务形成用户的整体表征。
|
||||
|
||||
在检测抑郁症的任务上,作者们采用的是“平均”的方式,也就是把左右的帖子特征向量直接平均得到。而在检测自残的任务上,作者们则采用了一种比较复杂的形式,把所有的帖子都平铺到一起,然后再把当前帖子之前的帖子,作为负例放在一起,注意,不是平均的形式,而是完全平铺到一起,从而表达为用户的整体特征。
|
||||
|
||||
在经过了这样的信息提取之后,后面的步骤就是构建分类器。这个步骤其实也是深度学习实践中比较常见的做法,那就是利用多层全联通层(Fully Connected Layer),最终把转换的信息转换到目标的标签上去。
|
||||
|
||||
可以说在整体的思路上,作者们提出的方法清晰明了。这里也为我们提供了一种用深度学习模型做文本挖掘的基本模式,那就是用卷积网络提取特征,然后通过联通层学习分类器。
|
||||
|
||||
## 方法的实验效果
|
||||
|
||||
作者们在上面提到的实验数据集上做了很充分的实验,当然也对比了不少基本的方法,比如直接采用文本特征然后用支持向量机来做分类器。
|
||||
|
||||
在辨别抑郁症的任务上,本文提出的方法综合获取了0.51的F1值,其中召回(Recall)达到0.45,而直接采用支持向量机的方法,精度(Precision)高达0.72,但是召回指数非常低只有0.29。
|
||||
|
||||
而在检测自残的任务上,提出方法的准确度能够达到0.89,F1值达到0.61,都远远高于其他方法。
|
||||
|
||||
应该说,从可观的数值上,本文的方法效果不错。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了EMNLP 2017年的第二篇年度最佳长论文,这篇文章介绍了一个采用深度学习模型对论坛文本信息进行分析的应用,那就是如何识别有精神疾病的用户的信息。
|
||||
|
||||
一起来回顾下要点:第一,我简要介绍了这篇文章的作者群信息。第二,这篇文章是利用自然语言处理技术解决一个社会问题的应用,论文构建的数据集很有价值。第三,文章把目前图像处理的标准卷积层应用到文本信息上,提出了基于卷积神经网络的文本分析框架,用于辨别抑郁症和检测自残倾向,都实现了不错的效果。
|
||||
|
||||
最后,给你留一个思考题,如果说在图像信息上采用卷积层是有意义的,那为什么同样的操作对于文本信息也是有效的呢?文本上的卷积操作又有什么物理含义呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
拓展阅读:[Depression and Self-Harm Risk Assessment in Online Forums](https://arxiv.org/pdf/1709.01848.pdf)
|
||||
|
||||
|
85
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/006 | 精读2017年EMNLP最佳短论文.md
Normal file
85
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/006 | 精读2017年EMNLP最佳短论文.md
Normal file
@ -0,0 +1,85 @@
|
||||
<audio id="audio" title="006 | 精读2017年EMNLP最佳短论文" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/db/f4/db7012e0e647b8a33cc17e54bbcedbf4.mp3"></audio>
|
||||
|
||||
在今年的EMNLP大会上,有两类研究论文得到发表,一类是8页的长研究论文,主要是比较完整的研究结果;另一类是4页的短研究论文,主要是比较新的有待进一步推敲的研究结果。大会从长研究论文中选出两篇最佳论文,从短论文中选出一篇最佳论文。
|
||||
|
||||
前面我们分别讨论了两篇最佳长论文,今天,我就带你认真剖析一下EMNLP 2017年的最佳短论文《多智能体对话中,自然语言并非“自然”出现》(Natural Language Does Not Merge ‘Naturally’ in Multi-Agent Dialog)。我们今天讲的论文虽然是最佳短论文,但是作者们已经在arXiv发表了较长的文章版本,因此我今天的讲解将基于arXiv的长版本。
|
||||
|
||||
这篇文章研究的一个主要命题就是,多个“机器人”(Agent)对话中如何才能避免产生“非自然”(Unnatural)的对话。以前很多机器人对话的研究都关注准确率的高低,但实际上机器人产生的对话是不自然的,人类交流不会用这样的方式。这篇文章希望探讨的就是这样非自然的对话是如何产生的,有没有什么方式避免这样的结果。
|
||||
|
||||
## 作者群信息介绍
|
||||
|
||||
第一作者萨特维克·库托儿(Satwik Kottur)来自卡内基梅隆大学,博士第四年,研究领域为计算机视觉、自然语言和机器学习。2016年暑假他在Snapchat的研究团队实习,研究对话系统中的个性化问题。2017年暑假在Facebook研究院实习,做视觉对话系统(Visual Dialog System)的研究。近两年,萨特维克已在多个国际顶级会议如ICML 2017、IJCAI 2017、CVPR 2017、ICCV 2017以及NIPS 2017发表了多篇高质量研究论文,包括这篇EMNLP 2017的最佳短论文,可以说是一颗冉冉升起的学术新星。
|
||||
|
||||
第二作者何塞·毛拉(José M. F. Moura)是萨特维克在卡内基梅隆大学的导师。何塞是NAE(美国国家工程院)院士和IEEE(电气电子工程师学会)院士,长期从事信号处理以及大数据、数据科学的研究工作。他当选2018年IEEE总裁,负责IEEE下一个阶段的发展。
|
||||
|
||||
第三作者斯特凡·李(Stefan Lee)是来自乔治亚理工大学的研究科学家,之前在弗吉尼亚理工大学任职,长期从事计算机视觉、自然语言处理等多方面的研究。斯特凡2016年博士毕业于印第安纳大学计算机系。
|
||||
|
||||
第四作者德鲁·巴塔(Dhruv Batra)目前是Facebook研究院的科学家,也是乔治亚理工大学的助理教授。德鲁2010年博士毕业于卡内基梅隆大学;2010年到2012年在位于芝加哥的丰田理工大学担任研究助理教授;2013年到2016年在弗吉尼亚大学任教。德鲁长期从事人工智能特别是视觉系统以及人机交互系统的研究工作。文章的第三作者斯特凡是德鲁长期的研究合作者,他们一起已经发表了包括本文在内的多篇高质量论文。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
我们先来看看这篇文章主要解决了一个什么场景下的问题。
|
||||
|
||||
人工智能的一个核心场景,或者说想要实现的一个目标,就是能够建立一个目标导向(Goal-Driven)的自动对话系统(Dialog System)。具体来说,在这样的系统中,机器人能够感知它们的环境(包括视觉、听觉以及其他感官),然后能和人或者其他机器人利用自然语言进行对话,从而实现某种目的。
|
||||
|
||||
目前对目标导向的自动对话系统的研究主要有两种思路。
|
||||
|
||||
一种思路是把整个问题看做静态的监督学习任务(Supervised Learning),希望利用大量的数据,通过神经对话模型(Neural Dialog Models)来对对话系统进行建模。这个模式虽然在近些年的研究中取得了一些成绩,但是仍然很难解决一个大问题,那就是产生的“对话”其实不像真人对话,不具备真实语言的很多特性。
|
||||
|
||||
另外一种思路则把学习对话系统的任务看做一个连续的过程,然后用强化学习(Reinforcement Learning)的模式来对整个对话系统建模。
|
||||
|
||||
这篇文章尝试探讨,在什么样的情况下能够让机器人学习到类似人的语言。文章的一个核心发现就是,自然语言并不是自然出现的。在目前的研究状态下,**自然语言的出现还是一个没有确定答案的开放问题**。可以说,这就是这篇最佳短论文的主要贡献。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
整篇文章其实是建立在一个虚拟的机器人交互场景里,也就是有两个机器人互相对话的一个环境。这个环境里有非常有限的物件(Object),每个物件包括三种属性(颜色、形状和样式),每一个属性包括四种可能取值,这样,在这个虚拟的环境中一共就有64个物件。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4d/b9/4d90d38df883967df26166748c9346b9.png" alt="" />
|
||||
|
||||
交互任务其实是两个机器人进行“猜谜”。为了区分,我们把两个机器人分为Q机器人和A机器人。猜谜一开始的时候,A机器人得到一个物件,也就是三种属性的某种实现组合,Q机器人并不知道这个物件。这个时候,Q机器人拿到两个属性的名字,需要通过对话最终猜出A拿到的这个物件所对应属性的取值。
|
||||
|
||||
在这个“游戏”的过程中,A是不知道Q手上的两个属性究竟是什么的,而Q也不知道A所拿的物件以及物件所对应属性的取值。因此,对话就是Q能够取得成功的关键因素。
|
||||
|
||||
在这篇文章里,Q和A的这个游戏**通过强化学习进行建模**。Q保持一组参数用于记录当前的状态。这组状态有最开始需要猜的属性,以及后面到当前状态为止所有Q的回答以及A的问题。类似地,A也保持这么一组状态,用于记录到目前位置的信息。这个强化学习最终的回馈是,当最后的预测值完全正确时,会有一个正1的反馈,而错误的话就是负10的反馈。
|
||||
|
||||
Q和A的模型都有三个模块:听、说和预测。以Q来举例,“听”模块是从要猜的属性这个任务开始,往后每一个步骤接受A的语句,从而更新自己的内部状态。“说”模块是根据当前的内部状态,决定下一步需要说的语句。最后“预测”模块则是根据所有的状态预测最后的属性值。
|
||||
|
||||
A机器人的结构是对称的。每一个模块本身都是一个 **LSTM** (Long Short-Term Memory,长短期记忆)模型。当然,所有这些LSTM模型的参数是不一样的。整个模型采用了**REINFORCE算法**(也被称作“vanilla” policy gradient,“基本”策略梯度)来学习参数,而具体的实现则采用了PyTorch软件包。
|
||||
|
||||
## 方法的实验效果
|
||||
|
||||
在提出的方法上,作者们展示了Q均能很快地以比较高的准确度做出预测,并且在和A的互动中产生了“语言”。不过遗憾的是,通过观察,作者们发现这样的“语言”往往并不自然。最直观的一种情况就是,A可以忽视掉Q的各种反应,而直接把A的内部信息通过某种编码直接“暴露”给Q,从而Q可以很快赢得游戏,取得几乎完美的预测结果。这显然不是想要的结果。
|
||||
|
||||
作者们发现,在词汇量(Vocabulary)非常大的情况下,这种情况尤其容易发生,那就是A把自己的整个状态都暴露给Q。于是,作者们假定**要想出现比较有意义的交流,词汇数目一定不能过大**。
|
||||
|
||||
于是,作者们采用了限制词汇数目的方式,让词汇数目与属性的可能值和属性数目相等,这样就限制了在完美情况下交流的复杂度,使得A没办法过度交流。然而,这样的策略可以很好地对一个属性做出判断,但是无法对属性的叠加(因为Q最终是要猜两个属性)做出判断。
|
||||
|
||||
文章给出的一个解决方案是,让A机器人忘记过去的状态,强行让A机器人学习使用相同的一组状态来表达相同的意思,而不是有可能使用新的状态。**在这样的限制条件以及无记忆两种约束下,A和Q的对话呈现出显著的自然语言的叠加性特征,而且在没有出现过的属性上表现出了接近两倍的准确率**,这是之前的方法所不能达到的效果。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了EMNLP 2017年的最佳短论文,这篇文章介绍了在一个机器人对话系统中,如何能让机器人的对话更贴近人之间的行为。
|
||||
|
||||
这篇文章也是**第一篇从谈话的自然程度**,而不是从预测准确度去分析对话系统的论文。文章的一个核心观点是,如果想让对话自然,就必须避免机器人简单地把答案泄露给对方,或者说要避免有过大的词汇库。
|
||||
|
||||
一起来回顾下要点:第一,我简要介绍了这篇文章的作者群信息,文章作者在相关领域均发表过多篇高质量研究成果论文。第二,这篇文章论证了多智能体对话中自然语言的出现并不自然。第三,论文提出在词汇量限制条件和无记忆约束下,机器人对话可以呈现出一定的自然语言特征。
|
||||
|
||||
最后,给你留一个思考题,文章讲的是一个比较简单的对话场景,有一个局限的词汇库,如果是真实的人与人或者机器与机器的对话,我们如何来确定需要多大的词汇量呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
**名词解释**:
|
||||
|
||||
**ICML 2017**,International Conference on Machine Learning ,国际机器学习大会。
|
||||
|
||||
**IJCAI 2017**, International Joint Conference on Artificial Intelligence,人工智能国际联合大会。
|
||||
|
||||
**CVPR 2017**,Conference on Computer Vision and Pattern Recognition,国际计算机视觉与模式识别会议。
|
||||
|
||||
**ICCV 2017**,International Conference on Computer Vision,国际计算机视觉大会。
|
||||
|
||||
**NIPS 2017**,Annual Conference on Neural Information Processing Systems,神经信息处理系统大会。
|
||||
|
||||
拓展阅读:[Natural Language Does Not Merge ‘Naturally’ in Multi-Agent Dialog](http://aclweb.org/anthology/D17-1320)
|
||||
|
||||
|
78
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/007 | 精读2017年ICCV最佳研究论文.md
Normal file
78
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/007 | 精读2017年ICCV最佳研究论文.md
Normal file
@ -0,0 +1,78 @@
|
||||
<audio id="audio" title="007 | 精读2017年ICCV最佳研究论文" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/9a/3b/9a879f1e99f1fa461334db4c6486f83b.mp3"></audio>
|
||||
|
||||
ICCV(International Conference on Computer Vision,国际计算机视觉大会),是每两年举办一次的计算机视觉顶级会议。从1987年开始举办,已经有30年的历史。2017年的ICCV大会于10月22日至29日在意大利的水城威尼斯举行。
|
||||
|
||||
在每届ICCV大会上,都会从众多学术论文中挑选出两篇最有新意和价值的论文作为最佳研究论文和最佳学生论文。ICCV的最佳论文奖又叫作“马尔奖项”(Marr Prize),是为了纪念英国的心理学家和神经科学家大卫·马尔(David Marr)而设计的奖项。马尔将心理学、人工智能和神经生理学的研究成果结合起来,提出了全新的关于视觉处理的理论,他被认为是计算神经科学的创始人。
|
||||
|
||||
今天,我就来带你认真剖析一下ICCV 2017年的最佳研究论文“[Mask R-CNN](https://research.fb.com/wp-content/uploads/2017/08/maskrcnn.pdf)”。这篇论文是一个集大成的工作,介绍了一个新的方法可以用于同时解决图像的“**物体识别**”(Object Detection)、“**语义分割**”(Semantic Segmentation)和“**数据点分割**”(Instance Segmentation)的工作。
|
||||
|
||||
什么意思呢?通俗地讲,那就是给定一个输入的图像,利用这篇论文提出的模型可以分析这个图像里究竟有哪些物体,比如是一只猫,还是一条狗;同时能够定位这些物体在整个图像中的位置;并且还能针对图像中的每一个像素,知道其属于哪一个物体,也就是我们经常所说的,把物体从图像中“抠”出来。
|
||||
|
||||
## 作者群信息介绍
|
||||
|
||||
这篇论文的作者全部来自Facebook的人工智能研究院(Facebook AI Research)。
|
||||
|
||||
第一作者就是近几年在计算机视觉领域升起的学术之星何恺明博士(Kaiming He)。他于2016年加入Facebook人工智能研究院,之前在微软亚洲研究院进行计算机视觉的研究工作;他还是CVPR 2016年和CVPR 2009年的最佳论文得主。目前,何恺明在计算机视觉领域有三项重大贡献。
|
||||
|
||||
第一,他与其他合作者发明的ResNet从2016年以来成为了计算机视觉深度学习架构中的重要力量,被应用到了计算机视觉以外的一些领域,比如机器翻译和AlphaGo等,相关论文引用数超过5千次。
|
||||
|
||||
第二,他与其他合作者开发的Faster R-CNN技术,发表于NIPS 2015上,是图像物体识别和语义分析的重要技术手段,也是今天我们要讨论的这篇论文的基础,论文引用数超过2千次。
|
||||
|
||||
第三,他与其他合作者在ICCV 2015年发表论文《深入研究整流器:在ImageNet分类上超越人类水平》([Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification](https://www.cv-foundation.org/openaccess/content_iccv_2015/papers/He_Delving_Deep_into_ICCV_2015_paper.pdf)),研究了一种改进的ReLU(Rectified Linear Unit,线性整流函数,又称修正线性单元)结构从而达到了更好的效果,论文引用数近2千次。
|
||||
|
||||
第二作者乔治亚⋅吉克里奥夏里(Georgia Gkioxari)目前是Facebook人工智能研究院的博士后研究员。乔治亚可以说是师出名门,在Facebook工作之前才从加州大学伯克利毕业,师从计算机视觉泰斗吉腾德拉⋅马利克(Jitendra Malik)。乔治亚之前还分别在谷歌大脑和谷歌研究院实习过。在过去几年中,乔治亚在计算机视觉界已经发表了多篇高质量论文。
|
||||
|
||||
第三作者皮奥特⋅多拉(Piotr Dollár)是Facebook人工智能研究院的一名经理。2007年从加州大学圣地亚哥分校获得博士学位,2014年加入Facebook,这之前在微软研究院工作。皮奥特长期从事计算机视觉的研究工作。
|
||||
|
||||
最后一个作者罗斯⋅吉尔什克(Ross Girshick)是Facebook人工智能研究院的一名科学家。他于2012年毕业于芝加哥大学,获得计算机博士。罗斯之前也在微软研究院工作,也曾在计算机视觉泰斗吉腾德拉的实验室里担任博士后的研究工作。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
我们首先来看一下这篇文章的主要贡献。还是要先去理解,这篇文章主要解决的是一个什么场景下的问题。
|
||||
|
||||
刚才我们已经简单地谈到了,这篇文章要解决的问题,就是对输入图像的物体识别、语义分割,以及数据点分割,是这三个任务的一个集成。在之前的一个工作中,“Faster R-CNN”[1]已经解决了前两个任务。那么,这篇论文其实就是Faster R-CNN在逻辑上的一个扩展。然而,这个扩展也并不是那么显而易见的。**为了解决数据点分割的任务,Mask R-CNN提出了深度学习网络结构上的一个创新,这是本篇论文的一个重要贡献**。
|
||||
|
||||
本文提出的模型不仅在数据点分割的标准数据集COCO上表现强劲,击败所有之前提出的模型以外,还能够很容易地扩展到其他的任务中,比如“人体形态估计”(Human Pose Estimation),从而**奠定了Mask R-CNN作为一个普适性框架的地位**。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
要想理解Mask R-CNN的核心思想,我们就必须先简要理解Faster R-CNN的一些基本原理。刚才说到了,Mask R-CNN就是在其之上的一种改进和延伸。
|
||||
|
||||
**Faster R-CNN对于每一个输入图像中的每一个候选物体,都会有两个输出**,一个是候选物体的**标签**(比如,猫、狗、马等),还有一个就是一个**矩形框**(Bounding Box),用于表达这个物体在图像中的位置。第一个**标签输出是一个分类问题**(Classification),而第二个**位置预测则是一个回归问题**(Regression)。
|
||||
|
||||
Faster R-CNN分为两个阶段(Stage)。第一个阶段叫作“**区域提交网络**”(Region Proposal Network),目的是从图像中提出可能存在的候选矩形框。第二个阶段,从这些候选框中使用一个叫“**RoIPool**”的技术来提取特征从而进行标签分类和矩形框位置定位这两个任务。这两个阶段的一些特性可以共享。
|
||||
|
||||
区域提交网络的大体流程是这样的。最原始的输入图像经过经典的卷积层变换之后形成了一个图像特征层。在这个新的图像特征层上,模型使用了一个移动的小窗口(Sliding Window)来对区域进行建模。这个移动小窗口有这么三个任务需要考虑。
|
||||
|
||||
首先移动小窗口所覆盖的特征经过一个变换达到一个中间层,然后经过这个中间层,直接串联到两个任务,也就是物体的分类和位置的定位。其次,移动的小窗口用于提出一个候选区域,有时候也叫ROI,也就是矩形框。而这个矩形框也参与刚才所说的定位信息的预测。
|
||||
|
||||
当区域提交网络“框”出了物体的大致区域和类别之后,模型再使用一个“物体检测”(Object Detection)的网络来对物体进行最终的检测。在这里,物体检测实际是使用了Fast R-CNN[2]的架构。所以,也就是为什么Faster R-CNN的名字里用“Faster”来做区分。Faster R-CNN的贡献,在于区域提交网络和Fast R-CNN的部分,也就是物体检测的部分达到了共享参数,或者叫共享网络架构,这样也就起到了加速的作用。
|
||||
|
||||
**Mask R-CNN在第一部分完全使用Faster R-CNN所提出的区域提交网络,在此基础上,对第二部分进行了更改**。也就是说,不仅仅在第二部分输出区域的类别和框的相对位置,同时,还输出具体的像素分割。然而,和很多类似工作的区别是,像素分割、类别判断、位置预测是三个独立的任务,并没有互相的依赖,这是作者们认为Mask R-CNN能够成功的一个重要的关键。对比之前的一些工作,像素分割成了类别判断的依赖,从而导致这几个任务之间互相干扰。
|
||||
|
||||
Mask R-CNN在进行像素分割的时候,因为要在原始的图像上进行分割,因此需要在整个流程中保留原始图像的位置关系。这个需求是类别判断和位置预测所不具备的。而在Faster R-CNN中,因为不需要这个需求,因此类别判断和位置预测所依赖的信息是一个压缩过后的中间层。那么很明显,Mask R-CNN依靠这个压缩层就不够了。在这篇文章中,作者们**提出了一个叫RoIAlign的技术来保证中间提取的特征能够反映在最原始的像素中**。如果对这部分内容感兴趣,建议你去细读文章。
|
||||
|
||||
## 方法的实验效果
|
||||
|
||||
作者们使用Mask R-CNN在目前流行的图像物体检测任务数据集COCO 2015和COCO 2016上做了检测,相对于之前的这两个竞赛的冠军,实验结果表明Mask R-CNN的精度都大幅度增加。在一个“平均精度”(Average Precision)的度量上,Mask R-CNN比COCO 2015的最佳结果好了近13%,而比COCO 2016的最佳结果好了4%,可以说效果非常明显。在实验结果中,作者们非常细致地测试了整个Mask R-CNN中每一个部件的效果。其中,把三个任务分开、以及RoIAlign方法都有非常显著的作用,证明了这些模型组件是优秀结果的必要步骤。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了ICCV 2017年的最佳研究论文,这篇文章介绍了目前在图像物体识别中的最新算法Mask R-CNN的大概内容。
|
||||
|
||||
一起来回顾下要点:第一,我们简要介绍了这篇文章的作者群信息。第二,我们详细介绍了这篇文章要解决的问题以及贡献 。第三,我们简要地介绍了文章提出方法的核心内容 。
|
||||
|
||||
最后,给你留一个思考题,你觉得为什么Mask R-CNN,包括之前的一些工作,要把物体检测的工作分为两步,第一步先分析一个大的矩形框,第二步进行物体检测,这两步都是必要的吗?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
**参考文献**
|
||||
|
||||
<li>
|
||||
Shaoqing Ren, Kaiming He, Ross Girshick, and Jian Sun. [Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks](https://arxiv.org/pdf/1506.01497.pdf). IEEE Trans. Pattern Anal. Mach. Intell. 39, 6 (June 2017), 1137-1149, 2017.
|
||||
</li>
|
||||
<li>
|
||||
Ross Girshick. [Fast R-CNN](https://www.cv-foundation.org/openaccess/content_iccv_2015/papers/Girshick_Fast_R-CNN_ICCV_2015_paper.pdf). Proceedings of the 2015 IEEE International Conference on Computer Vision (ICCV) (ICCV '15). IEEE Computer Society, Washington, DC, USA, 1440-1448, 2015.
|
||||
</li>
|
||||
|
||||
|
71
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/008 | 精读2017年ICCV最佳学生论文.md
Normal file
71
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/008 | 精读2017年ICCV最佳学生论文.md
Normal file
@ -0,0 +1,71 @@
|
||||
<audio id="audio" title="008 | 精读2017年ICCV最佳学生论文" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b8/5e/b850a90d2a62f32fef86ae437a3ce65e.mp3"></audio>
|
||||
|
||||
周一我们认真剖析了ICCV 2017年的最佳研究论文“Mask R-CNN”。今天我们来分享ICCV 2017的最佳学生论文《焦点损失用于密集物体检测》([Focal Loss for Dense Object Detection](https://arxiv.org/pdf/1708.02002.pdf))。
|
||||
|
||||
可以说,这篇文章是我们周一分享的最佳论文的孪生兄弟。首先,这篇论文的作者群也基本是Facebook人工智能研究院的班底。其次,这篇文章解决的问题也很类似,也是物体识别和语义分割,只是不解决数据点分割的问题。
|
||||
|
||||
## 作者群信息介绍
|
||||
|
||||
除第一作者外,这篇论文的作者都来自Facebook的人工智能研究院。
|
||||
|
||||
第一作者林仓义(Tsung-Yi Lin),目前在谷歌大脑(Google Brain)团队工作,发表论文的时候在Facebook人工智能研究院实习。林仓义在台湾国立大学获得本科学位,在加州大学圣地亚哥分校获得硕士学位,2017年刚从康奈尔大学博士毕业。博士期间,他师从计算机视觉专家塞尔盖⋅比隆基(Serge Belongie),发表了多篇高质量的计算机视觉论文。
|
||||
|
||||
第二作者皮里亚⋅高耶(Priya Goyal)是Facebook人工智能研究院的一名研究工程师。在加入Facebook之前,皮里亚从印度理工大学获得了学士和硕士学位。
|
||||
|
||||
第三作者罗斯⋅吉尔什克(Ross Girshick),第四作者何恺明,还有最后一个作者皮奥特⋅多拉(Piotr Dollár),这三位作者也是周一的最佳研究论文的作者,我们已经介绍过了,你可以回去再了解一下。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
我们首先来看一下这篇文章的主要贡献。
|
||||
|
||||
刚才我们已经简单地谈到了,**这篇文章要解决的问题,就是对输入图像进行物体识别和语义分割这两个任务**。对于这个问题有两种主要的思路,这两个思路都在不断地发展。
|
||||
|
||||
第一种思路,那就是直接从输入图像入手,希望能够从输入图像中提取相应的特征,从而能够直接从这些特征中判断当前的图像区域是否属于某个物体,然后也能够一次性地找到矩形框的位置用于定位这个物体。
|
||||
|
||||
这种思路虽然直观,但有一个致命的问题,那就是对于一个输入图像来说,大量的区域其实并不包含目标物体,因此也就可以被认为是学习过程中的“负例”(Negative Instance)。如何有效地学习这么一个“不均衡”(Imbalanced)的数据集是这种思路需要考虑的问题。
|
||||
|
||||
因为这个因素,研究者们就开始思考另外一种思路,那就是先学习一个神经网络用于找到一些候选区域,然后在第二个阶段根据候选区域再去最终确定物体的类别和矩形框的位置。
|
||||
|
||||
在最近几年的实际评测中,基于**两个阶段**(Two-stage)的模型,包括我们在上一篇分享中提到的Faster R-CNN以及其他变种一般都有比较好的表现。而基于**一个阶段**(One-stage)的模型,在这篇文章发布之前还不能达到两个阶段模型的水平。
|
||||
|
||||
**本篇文章提出了一个新的目标函数,叫作“焦点损失”(Focal Loss),用于取代传统的“交叉熵”(Cross Entropy)的目标函数**。这个新目标函数的主要目的就是让一个阶段模型能够在正负例比例非常不协调的情况下,依然能够训练出较好的模型,从而使得一个阶段模型在效果上能够和两个阶段模型媲美。同时,文章还提出了一种比较简单易用的深度网络结构,可以简单地训练出整个模型。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
在这一节,我们来讲一讲“焦点损失”的含义。因为这是一个新的目标函数,建议你还是阅读原文来理解这个目标函数的数学性质。这里,我们针对这个新的目标函数进行一个高度概括性的解释。
|
||||
|
||||
我们从普通的二分分类问题中常用的交叉熵,我们简称为CE目标函数说起。首先,我们认为模型预测类别是正例的概率是P。CE目标函数基本上可以认为是这个概率的对数的负数,也就是在机器学习中经常使用的“**负对数似然**”(Negative Log Likelihood)。模型的目的是最小化“负对数似然”,从而学习模型参数。
|
||||
|
||||
作者们观测到这么一个现象,那就是CE目标函数在P是一个比较大的数值时,比如大于0.5的时候,依然会有一个“损失”(Loss)。什么意思呢?就是说,某一个数值点,我们现在已经知道它可能是正例的可能性大于0.5了,也就是我们其实已经大体知道这个结果了,但是目标函数依然认为学习算法需要去对这个数据点进行作用,从而减少这个“损失”。
|
||||
|
||||
这其实也就是整个问题的核心,那就是传统的CE目标函数,并没有指导机器学习算法用在“应该使劲”的地方,而是分散到了一些原本已经不需要再去关注的数据点上。当然,这也就造成了学习困难的局面。
|
||||
|
||||
这篇文章提出的“焦点损失”对CE进行了一个看上去很小的改动,那就是在CE目标函数的“负对数似然”之前乘以一个“相反概率”的**系数**,并且这个系数有一个指数参数去调节这个系数的作用。如果你对这个内容感兴趣,建议你参考原论文查看细节。如果对细节不感兴趣,那重点理解这个目标函数的作用就可以了。
|
||||
|
||||
**“焦点损失”有两个性质**。第一,当一个数据点被分错类的时候,并且这个数据点的真实概率很小,那么,损失依然和CE类似。当一个数据点的真实概率趋近1,也就是原本算法就可以比较自信的时候,损失会相对于CE变小。第二,刚才所说的系数起到了一个调节作用,决定究竟需要对哪些“容易分类的数据点”降低损失到什么程度。
|
||||
|
||||
**文章在新的“焦点损失”的基础上提出了一个新的网络结构叫RetinaNet,使用一个阶段的思路来解决物体检测和语义分割的任务**。这里我简要概括一下RetinaNet的一些特点。
|
||||
|
||||
第一,RetinaNet使用了ResNet来从原始的输入图像中抽取基本的图像特性。
|
||||
|
||||
第二,文章采用了一种叫FPN(Feature Pyramid Net)的网络架构来对图像的不同分辨率或者不同大小的情况进行特性抽取。
|
||||
|
||||
第三,和Faster R-CNN相似的,RetinaNet也是用了Anchor的思想,也就是说从小的一个移动窗口中去寻找一个比较大的矩形框的可能性。
|
||||
|
||||
最后,RetinaNet把从FPN抽取出来的特性用于两个平行的网络结构,一个用于物体分类,一个用于矩形框的定位。这一点很类似两个阶段模型的做法。
|
||||
|
||||
## 方法的实验效果
|
||||
|
||||
作者们使用RetinaNet在目前流行的图像物体检测任务数据集COCO上做了检测。首先,RetinaNet的“平均精度” (Average Precision)要好于之前的所有一个阶段模型,初步验证了提出的目标函数和网络架构的优越性。并且,在实验中,作者们分别使用了不同的“焦点损失”指数参数来展示这个参数对于结果的重要性。同时,作者们还展示了,RetinaNet能够比Faster R-CNN这种经典的两阶段模型,以及一些变种在实验结果上至少持平甚至要更好。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了2017年ICCV的最佳学生论文,这篇文章介绍了目前在图像物体识别中的最新目标函数“焦点损失”的大概内容。
|
||||
|
||||
一起来回顾下要点:第一,我们简要介绍了这篇文章的作者群信息。第二,我们分析了这篇文章要解决的问题和主要贡献 。第三,我们详细介绍了文章提出方法的核心内容 。
|
||||
|
||||
最后,给你留一个思考题,除了这篇文章介绍的更改目标函数的方法,针对不平衡的数据集,你觉得还有哪些通常使用的方法?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
65
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/009 | 如何将“深度强化学习”应用到视觉问答系统?.md
Normal file
65
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/009 | 如何将“深度强化学习”应用到视觉问答系统?.md
Normal file
@ -0,0 +1,65 @@
|
||||
<audio id="audio" title="009 | 如何将“深度强化学习”应用到视觉问答系统?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/24/ae/24f7ce448f53d3d5eeb7b3e9f0a5bbae.mp3"></audio>
|
||||
|
||||
本周我们一起来剖析ICCV 2017的论文,周一和周三分别讲了最佳研究论文和最佳学生论文。今天,我们来分享一篇完全不同的文章,题目是《使用深度强化学习研究协作性视觉对话机器人》(Learning Cooperative Visual Dialog Agents with Deep Reinforcement Learning),讲的是如何通过“深度强化学习”来解决视觉问答系统。
|
||||
|
||||
## 作者群信息介绍
|
||||
|
||||
第一作者阿布谢克·达斯(Abhishek Das)是一名来自佐治亚理工大学的在读博士生。他于2017年和2018年在Facebook人工智能研究院实习,已经获得了Adobe的研究奖学金和Snapchat的研究奖学金,可以说是一名非常卓越的博士生。之前在智能系统,特别是在利用强化学习研究智能机器人会话系统的领域已经发表了多篇论文。
|
||||
|
||||
共同第一作者萨特维克·库托儿(Satwik Kottur)来自卡内基梅隆大学,博士第四年,研究领域为计算机视觉、自然语言和机器学习。2016年暑假他在Snapchat的研究团队实习,研究对话系统中的个性化问题。2017年暑假在Facebook研究院实习,研究视觉对话系统。近两年,萨特维克已在多个国际顶级会议如ICCV 2017、ICML 2017、IJCAI 2017、CVPR 2017、NIPS 2017以及EMNLP 2017发表了多篇高质量研究论文,可以说是一颗冉冉升起的学术新星。
|
||||
|
||||
第三作者何塞·毛拉(José M. F. Moura)是萨特维克在卡内基梅隆大学的导师。何塞是美国工程院院士和IEEE院士,长期从事信号处理以及大数据、数据科学的研究工作。他当选2018年IEEE总裁,负责IEEE下一个阶段的发展。
|
||||
|
||||
第四作者斯特凡·李(Stefan Lee)是来自乔治亚理工大学的研究科学家,之前在弗吉尼亚理工大学任职,长期从事计算机视觉、自然语言处理等多方面的研究。斯特凡2016年博士毕业于印第安纳大学计算机系。
|
||||
|
||||
第五作者德鲁·巴塔(Dhruv Batra)目前是Facebook研究院的科学家,也是乔治亚理工大学的助理教授。德鲁2010年博士毕业于卡内基梅隆大学;2010年到2012年在位于芝加哥的丰田理工大学担任研究助理教授;2013年到2016年在弗吉尼亚大学任教。德鲁长期从事人工智能特别是视觉系统以及人机交互系统的研究工作。文章的第四作者斯特凡是德鲁长期的研究合作者,他们一起已经发表了包括本文在内的多篇高质量论文。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
我们首先来看一下这篇文章的主要贡献,理解这篇文章主要解决了什么场景下的问题。
|
||||
|
||||
这篇论文是建立在这么一个虚拟“游戏”(Game)的基础上的。
|
||||
|
||||
首先,我们有两个“机器人”(Agent),一个叫“Q机器人”(Q-Bot),一个叫“A机器人”(A-Bot)。这个游戏的规则是这样的。一开始,A机器人得到一张图片I,Q机器人一开始得到I的一个文字描述c,而并不知道图片本身。然后,Q机器人开始问A机器人关于图片的各种问题,A机器人听到问题之后进行作答,帮助Q机器人更进一步理解图片。Q机器人最终的目的是能够把这个图片“猜到”,也就是说能够把图片从一个数据库中“提取”(Retrieve)出来。当然在实际的操作中,这一步可以是去衡量Q机器人对于图像的理解,也就是“描述图像的向量”和“真实图像的描述向量”的差距,差距越小说明越成功。
|
||||
|
||||
那么,你可以看到,这其实是一个很难的问题。Q机器人必须从A机器人提供的图像文字描述中寻找线索,并且能够提出有意义的问题。而A机器人必须了解Q机器人到目前为止究竟理解什么信息,才能帮助Q机器人成功。
|
||||
|
||||
整个游戏,或者叫任务,常常被称作是“协作性的视觉对话系统”(Cooperative Visual Dialog System)。**这篇文章的主要贡献就是第一个利用深度加强学习来对这样一个系统进行建模,并且,与之前的非加强学习模型相比,提出的解决方案极大地提高了准确度**。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
那么,既然要把整个问题使用深度强化学习来建模,我们肯定就需要定义强化学习的一些构件。
|
||||
|
||||
第一,我们来看看模型的**“动作”(Action)**。两个机器人的动作空间就是自然语言的词汇表。因为,在这个游戏或者说在强化学习的每一轮中,两个机器人都是需要根据现在的状态,来进行下一步的动作,也就是问问题的语句。这是一个离散的动作空间。除此以外,Q机器人还需要在每一轮之后对自己理解的图像向量进行更新。那么,这是一个连续的动作空间。
|
||||
|
||||
第二,我们来看看模型的**“状态”(State)**。对于Q机器人来说,每一轮的状态,是一个这些信息的集合,包括最初的A机器人提供的图像的描述,以及到目前为止所有轮问答的每一句话。而A机器人的状态空间,则包括最初的图像本身,图像的描述,以及到目前为止所有轮的对话。
|
||||
|
||||
第三,我们来看看模型的**“策略”(Policy)**。对A机器人和Q机器人来说,都是要根据现在的状态,来评估下面的语句的可能性。这里,评估的机制其实分别用两个神经网络来学习A机器人和Q机器人的策略。同时,Q机器人还需要有一个神经网络来根据现有的A机器人的回答,来更新对图像的一个认识。
|
||||
|
||||
第四,我们来看一看模型的**“环境”(Environment)和“回馈”(Reward)**。在这个游戏里,两个机器人都会得到一样的回馈,而这个回馈的根据是Q机器人对图像的认识所表达的向量和图像的真实表达向量的一个距离,或者更加准确地说是距离的变化量。
|
||||
|
||||
以上就是整个模型的设置。
|
||||
|
||||
那么,我们来看两个模型策略神经网络的一些细节。首先,对于Q机器人来说,有这么四个重要的部件。第一,Q机器人把当前轮自己问的问题和A给的回答,当做一个组合,用LSTM进行编码产生一个中间变量F。第二,当前步骤的F和以前的所有F都结合起来,再经过一个LSTM,产生一个中间变量S。然后第三步,我们根据这个S来产生下一步的语句,以及当前对图像的一个重新的认识。也就是说,**F其实就是一个对历史所有状态的描述,而S则是一个压缩了的当前描述信息,并且我们使用S来作为下一步的一个跳板**。A机器人的策略神经网络的架构非常类似,这里就不赘述了,区别在于不需要去产生图像的理解。
|
||||
|
||||
整个模型采用了目前深度强化学习流行的**REINFORCE算法**来对模型的参数进行估计。
|
||||
|
||||
这篇文章其实有不少技术细节,我们在今天的分享里只能从比较高的维度帮助你进行总结,如果有兴趣一定要去阅读原文。
|
||||
|
||||
## 方法的实验效果
|
||||
|
||||
作者们在一个叫VisDial的数据集上做了实验。这个数据集有6万8千幅图像,是从我们之前提到过的COCO数据集里抽取出来的,并且提供了超过68万对问答。可以说这个数据集还是比较大型的。
|
||||
|
||||
文章比较了利用普通的监督学习以及“课程学习”(Curriculum Learning)的方法。从效果来看,强化学习的效果还是很明显的。**最直接的效果是,强化学习能够产生和真实对话相近的对话效果**,而其他的办法,比如监督学习,则基本上只能产生“死循环”的对话,效果不理想。不过从图像提取的角度来讲,强化学习虽然比监督学习的效果好,但是差距并不是特别明显,基本上可以认为目前的差距依然是在误差范围内的。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了ICCV 2017的一篇有意思的文章。这篇文章介绍了如何利用深度强化学习来搭建一个模型去理解两个机器人的对话并能够理解图像信息。
|
||||
|
||||
一起来回顾下要点:第一,我们简要介绍了这篇文章的作者群信息。第二,我们详细介绍了这篇文章要解决的问题以及贡献 。第三,我们重点介绍了的文章提出方法核心内容 。
|
||||
|
||||
最后,给你留一个思考题,你认为把强化学习用在这样的对话场景中,难点是什么?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
@ -0,0 +1,59 @@
|
||||
<audio id="audio" title="010 | 精读2017年NIPS最佳研究论文之一:如何解决非凸优化问题?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/31/9f/31fc8e9bbdbc2f4171d6195711d3799f.mp3"></audio>
|
||||
|
||||
机器学习与人工智能领域的顶级会议NIPS(Conference on Neural Information Processing Systems,神经信息处理系统大会)从1987年开始举办,已经有30多年的历史。NIPS 2017大会于2017年12月4日到9日在美国加利福尼亚州的长滩(Long Beach)举行。
|
||||
|
||||
每年大会都会在众多的学术论文中挑选出几篇最有新意和价值的论文作为最佳研究论文。在NIPS 2017上,一共有三篇论文获得了最佳论文的称号。今天,我就来带你认真剖析一下其中的一篇《具有凸目标的基于方差的正则化》([Variance-based Regularization with Convex Objectives](https://papers.nips.cc/paper/6890-variance-based-regularization-with-convex-objectives.pdf))。这篇论文的两位作者都是来自斯坦福大学的学者。
|
||||
|
||||
这篇文章理论性很强,主要研究的是一种“健壮的优化问题”(Robust Optimization),也就是说我们在优化一个“损失函数”(Loss Function)的时候,不仅要考虑损失函数的“均值”(Mean),还要考虑损失函数的“方差”(Variance)。然而,一个既要考虑均值又要考虑方差的综合的损失函数,往往是一个“非凸”(Non Convex)的问题。对于一般的非凸优化问题来说,我们往往不能找到一个全局的最优解,甚至是找到局部最优解也很困难。这篇文章就是要来解决这么一个问题。
|
||||
|
||||
## 作者群信息介绍
|
||||
|
||||
第一作者洪升⋅南空(Hongseok Namkoong)是斯坦福大学“运筹学”(Operations Research)的一名在读博士研究生。他的导师分别是约翰⋅达齐(John C. Duchi)和彼得⋅格林(Peter W. Glynn)。2013年到斯坦福之前,南空在韩国的韩国科学与技术高级研究所(Korea Advanced Institute of Science and Technology),有时候又称为KAIST,获得工业工程和数学学士学位。最近两三年,南空已经在发表了两篇NIPS的文章(包括这篇最佳论文),以及一篇ICML的论文。
|
||||
|
||||
第二作者约翰⋅达齐(John C. Duchi)是南空的导师之一。达奇可以说是师出名门,他于2007年从斯坦福本科毕业,接着在斯坦福跟随机器学习权威达菲⋅科勒(Daphne Koller),拿到了计算机科学的硕士学位;然后又到加州大学伯克利分校跟随统计学习权威迈克尔⋅乔丹(Michael Jordan)拿到了计算机科学的博士学位。在博士阶段的暑假里,达奇还到Google研究院中追随约然⋅辛格(Yoram Singer)积累了非常有价值的实习经验。之后,他来到了斯坦福大学担任统计和电气电子工程系的助理教授。
|
||||
|
||||
有了这些良好的基础,达奇的学术成绩也是非常扎实。他于2010年获得了ICML最佳论文奖。紧接着,2011年在Google实习期间的工作AdaGrad,成为了现在机器学习优化领域的经典算法,这个工作的论文有超过2500次的引用,而且也是深度学习优化算法的一个重要基础。目前,达奇所有论文的引用数超过6千次。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
我们首先来看一下这篇文章的主要贡献,理解文章主要解决了一个什么场景下的问题。
|
||||
|
||||
**很多机器学习问题其实都可以最终归结于优化一个目标函数(Objective Function)或者有时候叫做损失函数(Loss Function)的问题**。针对训练数据集上损失函数的优化(即最大化或最小化)并且在测试集上表现优异,是可以被证明为最终能够较好“泛化”(Generalization)的一种体现。
|
||||
|
||||
那么,**通常情况下,这个损失函数都是针对均值的一个描述,比如在整个训练数据集上的平均误差,或者说在整个训练数据集上的平均准确度**。然而,我们都知道,在一些很“偏斜”(Skewed)的数据分布上,均值并不是很好的一个数据描述。即便我们的函数能够在“平均”的情况下优化一个损失函数,这个函数也有可能在一些,甚至大部分数据点上表现得不尽如人意。
|
||||
|
||||
于是,研究人员就引入了“健壮的优化问题”。也就是我们希望损失函数在更多的点上有优异的表现。那么,**损失函数的健壮性是用损失函数的方差来衡量的**。也就是说,我们希望损失函数在不同数据点上的波动要小。
|
||||
|
||||
有了这个概念之后,下一步就显得比较自然了,那就是把损失函数的均值部分,也就是我们通常要做的部分和有方差的部分串联起来,形成一个新的目标函数。**这个目标函数有两个部分,第一部分就是均值部分,第二个部分就是方差的部分,中间有一个自由的参数,把这两个部分衔接起来**。这样,我们就有了一个既考虑均值又考虑方差的新的健壮化的优化问题。
|
||||
|
||||
然而,一个既要考虑均值又要考虑方差的综合的损失函数,往往是一个“非凸”(Non Convex)的问题。什么叫做非凸函数?**一个“凸”(Convex)问题可以简单理解为函数只有唯一的最小值,并且我们具备有效算法来找到这个最小值**。而对于非凸问题来说,我们往往不能找到一个全局的最优解,或者找到局部最优解也很困难。
|
||||
|
||||
健壮优化问题已经在之前的研究中提了出来,那么这篇文章的主要贡献在于,为健壮优化问题找到了一个“凸”问题的逼近表达,并基于此提出了一个优化算法,解决了这个新提出的凸问题的近似解。
|
||||
|
||||
这里,值得注意的一点是,**对于非凸问题提出凸问题的近似表达,是解决非凸问题的一个重要思路**。有很多经典的非凸问题,都是通过凸问题的近似来得到解决或者部分解决的。从这个思路来说,这篇文章是延续了解决这种问题的一贯的策略。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
这篇论文的核心方法以及证明都有很强的理论性,需要有一定的数学功底和类似研究背景,才能更好地理解。如果对文章内容有兴趣,建议不仅要阅读原本的NIPS论文,还需要去阅读其附加的文档,一共有50多页,才能比较全面地理解这篇文章的细节。我们在这里仅仅从概念上做一个高度浓缩的概括。
|
||||
|
||||
作者们在文章中**提出了一种叫“健壮化的正则风险”(Robustly Regularized Risk)的目标函数**。这个新的目标函数是建立在一个叫“经验分布”(Empirical Distribution)上的“散度”(Divergence)。而这个新的健壮化正则风险是一个凸问题。
|
||||
|
||||
直白一点说,这个健壮化的正则风险可以被认为是一个包含两项的式子,这两项是在数据集上的损失函数的期望加上一个损失函数的方差。在这个新的两项的式子中,期望和方差都是定义在数据的经验分布上的。于是这样就把这个新提出的风险式子和我们实际需要解决的问题挂上了钩。当然后面大段的论文就是要证明这两个式子之间的差距到底有多少,是不是新的式子提供了一个比较“紧”的“界限“(Bound)。
|
||||
|
||||
紧接着,这篇文章其实讨论了这个健壮化的正则风险可以写成一个更加简单的优化问题,然后文章在附录中提供了这个简化后的优化问题的求解。
|
||||
|
||||
## 方法的实验效果
|
||||
|
||||
虽然这篇文章的核心内容是一个理论结果,或者是算法革新。但是这篇文章依然是在两个数据集中做了实验,一个是在UCI ML的数据集上,展示了提出的新的健壮化的目标函数达到了比一般的目标函数更好的效果;另外一个则是在RCV1文本分类的问题上比一般的优化目标函数有更好的效果。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了NIPS 2017年的最佳研究论文之一,文章非常理论化。文章的一个核心观点是希望能够通过对损失函数的均值和方差同时建模从而达到让目标函数健壮化的目的。
|
||||
|
||||
一起来回顾下要点:第一,我们简要介绍了这篇文章的作者群信息。第二,我们详细介绍了这篇文章要解决的问题以及贡献 。第三,我们简要地介绍的文章提出方法的核心内容 。
|
||||
|
||||
最后,给你留一个思考题,要想控制目标函数的预测结果的方差,除了本文提出的把均值和方差都设计到目标函数里,还有没有别的方法?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
@ -0,0 +1,69 @@
|
||||
<audio id="audio" title="011 | 精读2017年NIPS最佳研究论文之二:KSD测试如何检验两个分布的异同?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/c2/1b/c2667a33d98e596142028ce3df67881b.mp3"></audio>
|
||||
|
||||
本周我们来分析和探讨NIPS 2017上的三篇最佳论文。周一我们分享的文章主要研究的是一种“健壮的优化问题”,也就是说我们在优化一个“损失函数”的时候,不仅要考虑损失函数的“均值”,还要考虑损失函数的“方差”。
|
||||
|
||||
今天,我们来看另外一篇最佳论文《线性时间内核拟合优度测试》([A Linear-Time Kernel Goodness-of-Fit Test](https://papers.nips.cc/paper/6630-a-linear-time-kernel-goodness-of-fit-test.pdf)),讲的是如何来衡量一组数据是否来自于某一个分布。
|
||||
|
||||
今天的这篇文章理论性也很强,这里我尝试从更高的维度为你做一个归纳,如果对文章内容感兴趣,建议你一定要去阅读原文。
|
||||
|
||||
## 作者群信息介绍
|
||||
|
||||
本文一共有五位作者,我们在这里进行一个简要介绍。
|
||||
|
||||
第一作者叫维特瓦特·吉特克鲁特(Wittawat Jitkrittum),刚从伦敦大学学院(University College London)的“加斯比计算人脑科学所”(Gatsby Computational Neuroscience Unit)博士毕业。他在博士期间的主要研究是“统计测试”(Statistical Tests),特别是如何利用“核方法”(Kernel Method)来对“分布特征”(Distributional Features)进行测试。吉特克鲁特在泰国完成本科学习,于日本京的东京科技学院(Tokyo Institute Of Technology)获得硕士学位。最近几年,吉特克鲁特已经在NIPS、ICML、UAI等会议连续发表了多篇高质量论文,可以说是统计测试界的学者新秀。
|
||||
|
||||
第二作者许文凯(Wenkai Xu)是加斯比计算人脑科学所的一名博士生。
|
||||
|
||||
第三作者佐尔坦·萨博(Zoltán Szabó)来自法国一所著名的理工大学“巴黎综合理工学院”(École Polytechnique)。萨博之前也曾在加斯比计算人脑科学所工作过,目前在巴黎综合理工学院任职研究副教授(类似于研究员),长期从事核方法、信息论(Information Theory)、统计机器学习等方面的研究。
|
||||
|
||||
第四作者福水健次(Kenji Fukumizu)是“统计数学学院”(The Institute of Statistical Mathematics)的教授,长期从事核方法的研究,可以说是这方面的专家。
|
||||
|
||||
最后一个作者阿瑟·格里顿(Arthur Gretton)是加斯比计算人脑科学所的机器学习教授,长期从事机器学习,特别是核方法的研究。他的论文有9千多次的引用数。
|
||||
|
||||
## 论文的主要贡献和核心方法
|
||||
|
||||
我们首先来看一下这篇文章的主要贡献,理解这篇文章主要解决了什么场景下的问题。
|
||||
|
||||
在一般的建模场景里,我们常常会对一组数据提出一个模型,来描述产生这些数据背后的过程。这个过程我们通常是看不见的,是一个隐含的过程。那么,当我们提出了模型之后,如何知道用这个模型描述现实就是准确的呢?这时候我们就需要用到一些**统计检验**(Statistical Testing)的方法。
|
||||
|
||||
一种比较普遍的方法,那就是假设我们的模型是P,而数据的产生分布是Q。说得直白一些,就需要去验证P是不是等于Q,也就是需要验证两个分布是否相等。一个基本的做法就是,从P里“产生”(Generate)一组样本,或者叫一组数据,然后我们已经有了一组从Q里产生的数据,于是用“**两个样本假设检验**”(Two Sample Tests)来看这两组数据背后的分布是否相等。
|
||||
|
||||
这个想法看似无懈可击,但是在实际操作中往往充满困难。**最大的操作难点就是从P中产生样本**。比如P是一个深度神经网络模型,那从中产生样本就不是一个简单且计算效率高的流程,这就为基于“两个样本假设检验”带来了难度。
|
||||
|
||||
另一方面,我们在做这样的统计检验的时候,最好能够针对每一个数据点,得到一个数值,来描述当前数据点和模型之间的关系,从而能够给我们带来更加直观的认识,看模型是否符合数据。
|
||||
|
||||
这里,有一种叫作“**最大均值差别**”(Maximum Mean Discrepancy),或者简称为 **MMD** 的检验方法能够达到这样的效果。MMD的提出者就是这篇论文的最后一位作者阿瑟·格里顿,MMD是在NIPS 2016提出的一个检验两个样本是否来自同一个分布的一种方法。当MMD值大的时候,就说明这两个样本更有可能来自不同的分布。
|
||||
|
||||
和一般的衡量两个分布距离的方法相比,MMD的不同之处是把两个分布都通过核方法转换到了另外一个空间,也就是通常所说的“**再生核希尔伯特空间**”(Reproducing Kernel Hilbert Space),或者简称为 **RKHS**。在这个空间里,测量会变得更加容易。然而遗憾的是,MMD依然需要得到两个分布的样本,也就是说我们依然需要从P里得到样本。
|
||||
|
||||
那么,**这篇文章的最大贡献,就是使用了一系列的技巧让P和Q的比较不依赖于从P中得到样本,从而让数据对于模型的验证,仅仅依赖于P的一个所谓的“打分函数”**(Score Function)。
|
||||
|
||||
其实在MMD里,这个打分函数就是存在的,那就是针对我们从P或者是Q里抽取出来的样本,我们先经过一个函数F的变换,然后再经过一个叫“核函数”T的操作,最后两个样本转换的结果相减。
|
||||
|
||||
在这篇文章里,作者们提出了一个叫“核斯特恩差异”(Kernel Stein Discrepancy),或者叫**KSD测试**的概念,本质上就是希望能够让这两个式子中关于P的项等于零。
|
||||
|
||||
什么意思呢?刚才我们说了MMD的一个问题是依然要依赖于P,依赖于P的样本。假设我们能够让依赖P的样本这一项成为零,那么我们这个测试就不需要P的样本了,那也就是绕过了刚才所说的难点。
|
||||
|
||||
**KSD的本质就是让MMD的第二项在任何时候都成为零**。注意,我们这里所说的是“任何时候”,也就是说,KSD构造了一个特殊的T,这个T叫作“斯特恩运算符”(Stein Operator),使得第二项关于P的样本的计算,在任何函数F的情况下都是零,这一点在文章中提供了详细证明。于是,整个KSD就不依赖于P的样本了。
|
||||
|
||||
这篇文章不仅阐述了KSD的思想,而且在KSD的思想上更进了一步,**试图把KSD的计算复杂度,也就是在平方级别的计算复杂度变为线性复杂度**。什么意思呢?也就是说,希望能够让KSD的计算复杂度随着数据点的增加而线性增加,从而能够应用到大数据上。这个内容我们就不在这里复述了。
|
||||
|
||||
## 方法的实验效果
|
||||
|
||||
虽然这篇文章的核心内容是一个理论结果,或者是算法革新,文章还是在“受限波兹曼机”(Restricted Boltzmann Machine),简称RBM上做了实验。本质上就是在RBM的某一个链接上进行了简单的改变而整个模型都保持原样。
|
||||
|
||||
如果我们有从这两个RBM中得到的样本,其实是很难知道他们之间的区别的。在实验中,传统的MMD基本上没法看出这两个样本的差别。然而不管是KSD,还是线性的KSD都能够得出正确的结论,而最终的线性KSD基本上是随着数据点的增多而性能增加,达到了线性的效果。
|
||||
|
||||
最后,作者们用了芝加哥犯罪记录来作为说明,使用“打分函数”来形象地找到哪些点不符合模型。应该说,理论性这么强的论文有如此直观的结果,实在难能可贵。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了NIPS 2017年的另外一篇最佳研究论文,文章的一个核心观点是希望能够通过构建一个特殊的运算符,使得传统的通过样本来检验两个分布的异同的方法,比如MMD方法,可以不依赖于目标分布的样本,并且还能达到线性计算速度。
|
||||
|
||||
一起来回顾下要点:第一,我们简要介绍了这篇文章的作者群信息。第二,我们详细介绍了这篇文章要解决的问题以及贡献 。第三,我们简要地介绍了文章的实验结果 。
|
||||
|
||||
最后,给你留一个思考题,这种衡量分布之间距离的想法,除了在假设检验中使用以外,在机器学习的哪个环节也经常碰到?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
@ -0,0 +1,61 @@
|
||||
<audio id="audio" title="012 | 精读2017年NIPS最佳研究论文之三:如何解决非完美信息博弈问题?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b4/0a/b451347ad7854896eaf800e347d2f80a.mp3"></audio>
|
||||
|
||||
今天,我们来分享一下NIPS 2017的最后一篇最佳论文《安全和嵌套子博弈解决非完美信息博弈问题》([Safe and Nested Subgame Solving for Imperfect-Information Games](http://https://www.cs.cmu.edu/~sandholm/safeAndNested.aaa17WS.pdf))。这篇文章讲的是什么内容呢?讲的是如何解决“非完美信息的博弈”问题。
|
||||
|
||||
和前两篇分享的文章类似,这篇文章也是理论性很强,并不适合初学者,我们在这里仅仅对文章的主要思想进行一个高度概括。如果你对文章内容感兴趣,还是建议要阅读原文。
|
||||
|
||||
另外一个值得注意的现象是,即便在深度学习如日中天的今日,我们本周分享的三篇NIPS最佳论文均和深度学习无关。这一方面展现了深度学习并不是人工智能的全部,另一方面也让我们看到机器学习和人工智能领域的宽广。
|
||||
|
||||
## 作者群信息介绍
|
||||
|
||||
本文一共两位作者。
|
||||
|
||||
第一作者叫诺阿·布朗(Noam Brown)。布朗是卡内基梅隆大学计算机系的博士生,目前的主要研究方向是利用强化学习和博弈论的思想来解决大规模的多机器人交互的问题。这篇文章提到的“非完美信息博弈”也是这里面的一个分支问题。布朗已经在这个方向发表了多篇论文,包括三篇AAAI论文、两篇NIPS论文、一篇ICML论文、以及一篇IJCAI论文。
|
||||
|
||||
和本文非常相关的一个研究内容在2017年发表于《科学》(Science)杂志上,讲述了如何利用博弈论来解决“Heads-up无限制扑克”(Heads-up No Limit Poker)的问题,并且在现实比赛中已经超过了人类的表现。这个工作也得到了不少媒体的报道。布朗2017年也在伦敦的Google DeepMind实习;在博士阶段之前,他曾经在金融领域工作。
|
||||
|
||||
第二作者是布朗的导师托马斯·桑德霍姆(Tuomas Sandholm)。桑德霍姆是卡内基梅隆大学计算机系的教授,其在“机制设计”(Mechanism Design)以及“拍卖理论”(Auction Theory)等领域有长期的研究,发表了450多篇学术论文,并且有超过2万多的引用数。除了他在学术上的造诣以外,桑德霍姆还有一些轶事,比如,他还有非常广泛的兴趣爱好,在他的主页就列举了他冲浪、喜好魔术以及对飞行的热爱。
|
||||
|
||||
## 论文的主要贡献和核心方法
|
||||
|
||||
我们首先来看一下这篇文章的主要贡献,弄明白这篇文章主要解决了什么场景下的问题。
|
||||
|
||||
对于一篇理论性很强的文章来说,我们通常需要不断地提问,这篇文章的核心主旨到底是什么,这样才能够帮助我们了解到文章的主干。
|
||||
|
||||
首先,文章讲的是一个“**非完美信息的博弈**”问题。这是什么意思呢?要理解“非完美信息博弈”,我们就必须要说一下“**完美信息博弈**”。
|
||||
|
||||
简单来说,“完美信息博弈”指的是博弈双方对目前的整个博弈状况都完全了解,对于博弈之前,以及整个博弈时候的初始状态也完全了解。在这种定义下,很多大家熟悉的游戏都是“完美信息博弈”,比如围棋、象棋等等。那么,DeepMind开发的AlphaGo以及后来的AlphaGo Zero都是典型的针对“完美信息博弈”的人工智能算法。
|
||||
|
||||
“非完美信息博弈”并不是说我们不知道对方的任何信息,而只是说信息不充分。什么意思呢?比如,我们可能并不知道对手在这一轮里的动作,但我们知道对手是谁,有可能有怎样的策略或者他们的策略的收益(Payoff)等。
|
||||
|
||||
除了在表面定义上的区别以外,在整个问题的机构上也有不同。
|
||||
|
||||
“完美信息博弈”有这样的特征,那就是在某一个时刻的最优策略,往往仅需要在问题决策树当前节点的信息以及下面子树对应的所有信息,而并不需要当前节点之前的信息,以及其他的旁边节点的信息。
|
||||
|
||||
什么意思呢?比如我们看AlphaGo。本质上在这样“完美信息博弈”的场景中,理论上,我们可以列出所有的棋盘和棋手博弈的可能性,然后用一个决策方案树来表达当前的决策状态。在这样的情况下,走到某一个决策状态之后,往往我们仅仅需要分析后面的状态。尽管这样的情况数目会非常巨大,但是从方法论的角度来说,并不需要引用其他的信息来做最优决策。
|
||||
|
||||
“非完美信息博弈”的最大特点就正好和这个相反,也就是说,每一个子问题,或者叫子博弈的最佳决策,都需要引用其他信息。而实际上,**本篇论文讲述了一个事实,那就是“非完美信息博弈”在任何一个决策点上的决策往往取决于那些根本还没有“达到”(Reach)的子博弈问题**。
|
||||
|
||||
在这一点上,论文其实引用了一个“掷硬币的游戏”来说明这个问题。限于篇幅,我们就不重复这个比较复杂的问题设置了,有兴趣的话可以深读论文。
|
||||
|
||||
但是从大体上来说,这个“掷硬币的游戏”,其核心就是想展示,两个人玩掷硬币,在回报不同,并且两个人的玩法在游戏规则上有一些关联的情况下,其中某一个玩家总可以根据情况完全改变策略,而如果后手的玩家仅仅依赖观测到先手玩家的回馈来决策,则有可能完全意识不到这种策略的改变,从而选择了并非优化的办法。这里的重点在于先后手的玩家之间因为规则的牵制,导致后手玩家无法观测到整个游戏状态,得到的信息并不能完全反应先手玩家的策略,从而引起误判。
|
||||
|
||||
为解决这样博弈问题,**这篇文章提出的一个核心算法就是根据当前的情况,为整个现在的情况进行一个“抽象”(Abstraction)**。这个抽象是一个小版本的博弈情况,寄希望这个抽象能够携带足够的信息。然后,我们根据这个抽象进行求解,当在求解真正的全局信息的时候,我们利用这个抽象的解来辅助我们的决策。**有时候,这个抽象又叫作“蓝图”(Blueprint)策略**。**这篇文章的核心在于如何构造这样的蓝图,以及如何利用蓝图来进行求解**。
|
||||
|
||||
## 方法的实验效果
|
||||
|
||||
文章在“Heads-up无限制扑克”的数据集上做了实验,并且还比较了之前在《科学》杂志上发表的叫作“利不拉图斯”(Libratus)的算法版本。人工智能算法都大幅度领先人类的玩家。
|
||||
|
||||
有一种算法叫“非安全子博弈算法”(Unsafe Subgame Solving),也就是说并不考虑“非完美信息的博弈”状态,把这个情况当做完美信息来做的一种算法,在很多盘游戏中均有不错的表现,但是有些时候会有非常差的结果,也就是说不能有“健壮”(Robust)的结果。这里也从实验上证明了为什么需要本文提出的一系列方法。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了NIPS 2017的第三篇最佳研究论文,文章的一个核心观点是希望能够通过构建蓝图来引导我们解决非完美信息博弈的问题,特别是在扑克上面的应用。
|
||||
|
||||
一起来回顾下要点:第一,我们简要介绍了这篇文章的作者群信息。第二,我们详细介绍了这篇文章要解决的问题以及贡献 。第三,我们简要地介绍了文章的实验结果 。
|
||||
|
||||
最后,给你留一个思考题,为什么非完美博弈的整个问题求解现在并没有依靠**深度加强学习**呢,大家在这个问题上有什么直观上的体会呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
@ -0,0 +1,75 @@
|
||||
<audio id="audio" title="013 | WSDM 2018论文精读:看谷歌团队如何做位置偏差估计" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/11/93/11075ab149c4f160abf97209a57a7893.mp3"></audio>
|
||||
|
||||
WSDM(International Conference on Web Search and Data Mining,国际搜索和数据挖掘大会)是每年举办一次的搜索、数据挖掘以及机器学习的顶级会议,其从2008年开始举办,已经有11届的历史。
|
||||
|
||||
尽管WSDM仅仅举办了11届,在计算机科学领域算是一个非常年轻的会议。但是,WSDM快速积累的影响力已经使其成为了数据挖掘领域的一个顶级会议。根据谷歌学术搜索公布的数据,目前WSDM已经是数据挖掘领域仅次于KDD的学术会议,而KDD已经举办了20多年。
|
||||
|
||||
WSDM的一大特点就是有大量工业界的学者参与,不管是投稿和发表论文还是评审委员会或者大会组织委员会的成员,都有很多工业界背景的人员参加。这可能也是WSDM备受关注的一个原因,那就是大家对于工业界研究成果的重视,同时也希望能够从中学习到最新的经验。
|
||||
|
||||
2018年的WSDM大会于2月5日到9日在的美国的洛杉矶举行。今天,我们就来分享WSDM 2018上来自谷歌的一篇文章《无偏排序学习在个人搜索中的位置偏差估计》([Position Bias Estimation for Unbiased Learning to Rank in Personal Search](https://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/46485.pdf))。这篇文章的核心内容是如何结合“因果推断”(Causal Inference)和排序学习(Learning to Rank)来对用户数据进行进一步无偏差的估计。
|
||||
|
||||
## 作者群信息介绍
|
||||
|
||||
这篇论文的所有作者都来自谷歌,我们这里对作者群做一个简单的介绍。
|
||||
|
||||
第一作者王选珲(Xuanhui Wang)2015年起在谷歌工作。他之前在Facebook工作了三年,一直从事广告系统的开发;再往前,是在雅虎担任了两年的科学家。王选珲于2009年毕业于伊利诺伊大学香槟分校,获得计算机博士学位,他的博士生导师是信息检索界著名的华人学者翟成祥(Chengxiang Zhai)。
|
||||
|
||||
第二作者纳达夫⋅古尔班迪(Nadav Golbandi)于2016年加入谷歌,之前在雅虎研究院担任了8年的主任级研究工程师(Principal Research Engineer),一直从事搜索方面的研发工作。在雅虎研究院之前,古尔班迪在以色列的IBM研究院工作了6年。他拥有以色列理工大学的计算机硕士学位。
|
||||
|
||||
第三作者迈克尔⋅本德斯基(Michael Bendersky)于2012年加入谷歌,一直从事个人以及企业信息系统(Google Drive)的研发工作。本德斯基于2011年从马萨储塞州阿姆赫斯特分校(University of Massachusetts Amherst)毕业,获得计算机博士学位,他的导师是信息检索界的学术权威布鲁斯⋅夸夫特(Bruce Croft)。
|
||||
|
||||
第四作者唐纳德⋅梅泽尔(Donald Metzler)也是2012年加入谷歌的,一直负责个人以及企业信息系统(Google Drive)搜索质量的研发工作。梅泽尔曾在雅虎研究院工作过两年多,然后还在南加州大学(University of South California)担任过教职。梅泽尔是2007年从马萨储塞州阿姆赫斯特分校计算机博士毕业,导师也是信息检索界的学术权威布鲁斯⋅夸夫特。
|
||||
|
||||
文章的最后一个作者是马克⋅诺瓦克(Marc Najork)于2014年加入谷歌,目前担任研发总监(Research Engineering Director)的职位。诺瓦克之前在微软研究院硅谷分部工作了13年,再之前在DEC研究院工作了8年。诺瓦克是信息检索和互联网数据挖掘领域的学术权威,之前担任过ACM顶级学术期刊ACM Transactions on the Web的主编。他发表过很多学术文章,引用数在七千以上。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
按照我们阅读论文的方法,首先来看这篇文章的主要贡献,梳理清楚这篇文章主要解决了什么场景下的问题。
|
||||
|
||||
众所周知,所有的搜索系统都会有各种各样的“**偏差**”(Bias),如何能够更好地对这些偏差进行建模就成为了对搜索系统进行机器学习的一个重要的挑战。
|
||||
|
||||
一种方式就是像传统的信息检索系统一样,利用人工来获得“相关度”(Relevance)的标签,不需要通过通过人机交互来获取相关度的信息。所以,也就更谈不上估计偏差的问题。
|
||||
|
||||
第二种,文章中也有谈到的,那就是利用传统的“**点击模型**”(Click Model)。点击模型是一种专门用来同时估计相关度和偏差的概率图模型,在过去10年左右的时间内已经发展得相对比较成熟。文章中也提到,大多数点击模型的应用主要是提取相关度信息,而并不在乎对偏差的估计是否准确。
|
||||
|
||||
第三种,也是最近几年兴起的一个新的方向,那就是利用“因果推断”(Causal Inference)和排序学习的结合直接对偏差进行建模。在WSDM 2017的最佳论文[1]中,已经让我们见识了这个思路。然而,在去年的那篇文章里,并没有详细探讨这个偏差的估计和点击模型的关系。
|
||||
|
||||
简言之,**这篇论文主要是希望利用点击模型中的一些思路来更加准确地估计偏差,从而能够学习到更好的排序结果**。同时,这篇文章还探讨了如何能够在较少使用随机数据上来对偏差进行更好的估计。这里,作者们提出了一种叫作“**基于回归的期望最大化**”(Regression-based EM)算法。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
文章首先讨论了如果已知“偏差值”(Propensity Score),也就是用户看到每一个文档或者物品时的概率,我们就可以构造“无偏差”的指标,比如“**无偏差的精度**”(Unbiased Precision)来衡量系统的好坏。
|
||||
|
||||
这里,无偏差的效果主要是来自于重新对结果进行权重的调整。意思就是说,并不是每一个点击都被认为是同样的价值。总的来说,如果文档位于比较高的位置上,那权重反而会比较低,反之,如果文档位于比较低的位置上,权重反而较高。**这里的假设是一种“位置偏差”(Position Bias)假设。意思就是不管什么文档,相对来说,放在比较高的位置时都有可能获得更多的点击。因此,在较低位置的文档被点击就显得更加难得**。
|
||||
|
||||
这种情况下,一般都无法直接知道“偏差值”。因此,如何去估计偏差值就成了一个核心问题。
|
||||
|
||||
这篇文章在进行“偏差值”估计的方法上,首先利用了一个叫“**位置偏差模型”**(Position Bias Model)的经典点击模型,对偏差值和相关度进行了建模。“位置偏差模型”的假设是用户对于每一个查询关键字的某一个位置上的文档点击概率,都可以分解为两个概率的乘积,一个是用户看到这个位置的概率,一个就是文档本身相关度的概率。那么,位置偏差模型的主要工作就是估计这两个概率值。
|
||||
|
||||
如果我们能够对每一个查询关键字的结果进行随机化,那么,我们就不需要估计第一个概率,而可以直接利用文档的点击率来估计文档的相关度。但是,作者们展示了,彻底的随机化对于用户体验的影响。
|
||||
|
||||
另外一种方法,相对来说比较照顾用户体验,那就是不对所有的结果进行随机化,而仅仅针对不同的“配对”之间进行随机化。比如,排位第一的和第二的文档位置随机互换,然后第二的和第三的随机互换等等。在这样的结果下,作者们依然能够对偏差和相关度进行估计,不过用户的体验就要比第一种完全随机的要好。只不过,在现实中,这种方法依然会对用户体验有所损失。
|
||||
|
||||
于是,作者们提出了第三种方法,那就是**直接对位置偏差模型进行参数估计**。也就是说,不希望利用随机化来完全消除其中的位置概率,而是估计位置概率和相关度概率。
|
||||
|
||||
这里,因为有两个概率变量需要估计,于是作者利用了传统的“期望最大化”(EM)算法,并且提出了一种叫做“基于回归的期望最大化”的方法。为什么这么做呢?原因是在传统的期望最大化中,作者们必须对每一个关键字和文档的配对进行估计。然而在用户数据中,这样的配对其实可能非常有限,会陷入数据不足的情况。因此,作者们提出了利用一个回归模型来估计文档和查询关键字的相关度。也就是说,**借助期望最大化来估计位置偏差,借助回归模型来估计相关度**。
|
||||
|
||||
## 方法的实验效果
|
||||
|
||||
这篇文章使用了谷歌的邮件和文件存储的搜索数据,采用了2017年4月两个星期的日志。数据大约有四百万个查询关键字,每个关键字大约有五个结果。作者们在这个数据集上验证了提出的方法能够更加有效地捕捉文档的偏差。利用了这种方法训练的排序模型比没有考虑偏差的模型要好出1%~2%。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了WSDM 2018年的一篇来自谷歌团队的文章,这篇文章介绍了如何估计文档的位置偏差,然后训练出更加有效的排序算法。
|
||||
|
||||
一起来回顾下要点:第一,我们简要介绍了这篇文章的作者群信息;第二,我们详细介绍了这篇文章要解决的问题以及贡献;第三,我们简要地介绍了文章提出方法的核心内容 。
|
||||
|
||||
最后,给你留一个思考题,如果要估计位置偏差,对数据的随机性有没有要求?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
**参考文献**
|
||||
|
||||
1. Thorsten Joachims, Adith Swaminathan, and Tobias Schnabel. [Unbiased Learning-to-Rank with Biased Feedback](http://delivery.acm.org/10.1145/3020000/3018699/p781-joachims.pdf?ip=185.211.133.206&id=3018699&acc=CHORUS&key=4D4702B0C3E38B35%2E4D4702B0C3E38B35%2E4D4702B0C3E38B35%2E6D218144511F3437&__acm__=1518920527_3cf6dd96729ff3e596bb6901c8230cb1). Proceedings of the Tenth ACM International Conference on Web Search and Data Mining (WSDM '17). ACM, New York, NY, USA, 781-789, 2017.
|
||||
|
||||
|
@ -0,0 +1,69 @@
|
||||
<audio id="audio" title="014 | WSDM 2018论文精读:看京东团队如何挖掘商品的替代信息和互补信息" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/47/e8/47c63b5a8de67ff6c0f90fd1bc3852e8.mp3"></audio>
|
||||
|
||||
本周我们来精读WSDM的几篇论文,周一我们分享了一篇来自谷歌团队的文章,其核心是利用点击模型来对位置偏差进行更加有效的估计,从而能够学习到更好的排序算法。
|
||||
|
||||
今天,我们来介绍WSDM 2018的最佳学生论文《电子商务中可替代和互补产品的路径约束框架》([A Path-constrained Framework for Discriminating Substitutable and Complementary Products in E-commerce](http://http://delivery.acm.org/10.1145/3160000/3159710/p619-wang.pdf?ip=185.211.133.206&id=3159710&acc=OPEN&key=4D4702B0C3E38B35%2E4D4702B0C3E38B35%2E4D4702B0C3E38B35%2E6D218144511F3437&__acm__=1519027969_cc4a857a03d3bba81f9e3e69a6b774cc)),这篇文章来自于京东的数据科学实验室。
|
||||
|
||||
## 作者群信息介绍
|
||||
|
||||
这篇论文的所有作者都来自京东大数据实验室,我们这里对几位主要作者做一个简单介绍。
|
||||
|
||||
第三作者任昭春(Zhaochun Ren)目前在京东数据科学实验室担任高级研发经理。他于2016年毕业于荷兰阿姆斯特丹大学,获得计算机博士学位,师从著名的信息检索权威马丁⋅德里杰克(Maarten de Rijke)。任昭春已经在多个国际会议和期刊上发表了多篇关于信息检索、文字归纳总结、推荐系统等多方面的论文。
|
||||
|
||||
第四作者汤继良(Jiliang Tang)目前是密歇根州立大学的助理教授。汤继良于2015年从亚利桑那州立大学毕业,获得计算机博士学位,师从著名的数据挖掘专家刘欢(Huan Liu)教授。他于2016年加入密歇根州立大学,这之前是雅虎研究院的科学家。汤继良是最近数据挖掘领域升起的一颗华人学术新星,目前他已经发表了70多篇论文,并且有四千多次的引用。
|
||||
|
||||
最后一位作者殷大伟(Dawei Yin)目前是京东数据科学实验室的高级总监。2016年加入京东,之前在雅虎研究院工作,历任研究科学家和高级经理等职务。殷大伟2013年从里海大学(Lehigh University)获得计算机博士学位,师从信息检索领域的专家戴维森(Davison)教授。目前已经有很多高质量的研究工作发表。殷大伟和笔者是博士期间的实验室同学以及在雅虎研究院期间的同事。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
我们首先来看一下这篇文章的主要贡献,梳理清楚文章主要解决了一个什么场景下的问题。
|
||||
|
||||
对于工业级商品推荐系统而言,一般通过两个步骤来产生推荐结果。第一步,产生候选集合,这里主要是从海量的物品中选择出几百到几千款用户可能会购买的商品;第二步,利用复杂的机器学习模型来对所有候选集中的产品进行排序。
|
||||
|
||||
**这篇文章主要探讨了如何能够更好地产生候选集产品,即如何更好地产生“替代品”(Substitutes)和“互补品”(Complements)来丰富用户的购买体验。**
|
||||
|
||||
那么,什么是替代品和互补品呢?
|
||||
|
||||
根据这篇文章的定义,替代品就是用户觉得这些商品可以互相被替换的;而互补品则是用户会一起购买的。挖掘这些商品不仅对于产生候选集具有很重要的意义,也对于某些场景下的推荐结果有很好的帮助,比如当用户已经购买了某一件商品之后,给用户推荐其他的互补品。
|
||||
|
||||
虽然替代品和互补品对于互联网电商来说是很重要的推荐源,但并没有多少文献和已知方法来对这两类商品进行有效挖掘。而且这里面一个很大的问题是数据的“稀缺”(Sparse)问题。因为替代品或者互补品都牵扯至少两个商品,而对于巨型的商品库来说,绝大多数的商品都不是两个商品一起被同时考虑和购买过,因此如何解决数据的稀缺问题是一大难点。
|
||||
|
||||
另一方面,商品的属性是复杂的。同一款商品有可能在某些情况下是替代品,而在另外的情况下是互补品。因此,如何在一个复杂的用户行为链路中挖掘出商品的属性,就成为了一个难题。很多传统方法都是静态地看待这个问题,并不能很好地挖掘出所有商品的潜力。
|
||||
|
||||
归纳起来,这篇文章有两个重要贡献。**第一,作者们提出了一种“多关系”(Multi-Relation)学习的框架来挖掘替代品和互补品。第二,为了解决数据的稀缺问题,两种“路径约束”(Path Constraints)被用于区别替代品和互补品。**作者们在实际的数据中验证了这两个新想法的作用。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
**文章提出方法的第一步是通过关系来学习商品的表征**(Representation)。这里文章并没有要区分替代品和互补品。**表征的学习主要是用一个类似Word2Vec的方式来达到的**。
|
||||
|
||||
也就是说,商品之间如果有联系,不管是替代关系还是互补关系,都认为是正相关,而其他的所有商品都认为是负相关。于是,我们就可以通过Word2Vec的思想来学习商品的表征向量,使得所有正相关的商品之间的向量点积结果较高,而负相关的向量点积结果较低。这一步基本上是Word2Vec在商品集合上的一个应用。
|
||||
|
||||
通过第一步得到的每个商品的表征,是一个比较笼统的**综合的表征**。而我们之前已经提到了,那就是不同的情况下,商品可能呈现出不同的属性。因此,我们就需要根据不同的场景来刻画产品的不同表征。**文章采用的方法是,对于不同类型的关系,每个商品都有一个对应的表征**。这个关系特定的表征是从刚才我们学到的全局表征“投影”(Project)到特定关系上的,这里需要学习的就是**一个投影的向量**。
|
||||
|
||||
**第三个步骤就是挖掘替代关系和互补关系了**。这篇文章使用了一个不太常见的技术,用“**模糊逻辑**”(Fuzzy Logic)来表达商品之间的约束关系。在这里我们并不需要对模糊逻辑有完整的理解,只需要知道这是一种把“硬逻辑关系”(Hard Constraints)转换成为通过概率方法表达的“软逻辑关系”(Soft Constraints)的技术。
|
||||
|
||||
在这篇文章里,作者们重点介绍的是如何利用一系列的规则来解决数据稀缺的问题。具体来说,那就是利用一些人们对于替代关系或者互补关系的观察。
|
||||
|
||||
比如,商品A是商品B的替代品,那很可能商品A所在的类别就是商品B所在类别的替代品。再比如,商品B是商品A的替代品,而商品C又是商品B的替代品,而如果A、B和C都属于一个类别,那么我们也可以认为商品C是A的替代品。
|
||||
|
||||
总之,作者们人工地提出了这样一系列的规则,或者叫做约束关系,希望能够使用这样的约束关系来尽可能地最大化现有数据的影响力。当然,我们可以看到,这样的约束并不是百分之百正确的,这也就是作者们希望用“软逻辑关系”来进行约束的原因,因为这其实也是一个概率的问题。
|
||||
|
||||
**整个提出的模型最终是一个集大成的优化目标函数,也就是最开始的物品的综合表征,在特定的关系下的投影的学习,以及最后的软逻辑关系的学习,这三个组件共同组成了最后的优化目标。**
|
||||
|
||||
## 方法的实验效果
|
||||
|
||||
这篇文章使用了京东商城的五大类商品来做实验,商品的综述大大超过之前亚马逊的一个公开数据的数量。作者重点比较了之前的一个来自加州大学圣地亚哥团队的模型,以及几个矩阵分解的经典模型,还比较了一个基于协同过滤的模型。
|
||||
|
||||
从总的效果上来看,这篇文章提出的模型不管是在关系预测的子任务上,还是在最后的排序任务上均要大幅度地好于其他模型。同时,作者们也展示了逻辑关系的确能够帮助目标函数把替代关系和互补关系的商品区分开来。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了WSDM 2018年的一篇来自京东数据科学团队的文章,这篇文章介绍了如何利用多关系学习以及模糊逻辑来挖掘商品的替代信息和互补信息,然后训练出更加有效的排序算法。
|
||||
|
||||
一起来回顾下要点:第一,我们简要介绍了这篇文章的作者群信息;第二,我们详细介绍了这篇文章要解决的问题以及贡献 ;第三,我们简要地介绍了文章提出方法的核心内容以及实验的结果。
|
||||
|
||||
最后,给你留一个思考题,互补商品或者替代商品是双向关系还是单向关系,为什么呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
@ -0,0 +1,53 @@
|
||||
<audio id="audio" title="015 | WSDM 2018论文精读:深度学习模型中如何使用上下文信息?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/25/36/2517763117a3f7087e1377899de76d36.mp3"></audio>
|
||||
|
||||
今天,我们继续来精读WSDM 2018的一篇论文《隐含交叉:在循环推荐系统中利用上下文信息》(Latent Cross: Making Use of Context in Recurrent Recommender Systems)。这篇文章同样出自谷歌团队,其核心思想是希望通过深度模型来模拟并实现在推荐系统中广泛使用的“交叉特征”(Cross Feature)的效果。
|
||||
|
||||
## 作者群信息介绍
|
||||
|
||||
这篇论文的所有作者都来自谷歌,我们这里对其中的主要作者做一个简单介绍。
|
||||
|
||||
文章的第一作者亚力克斯·布伦特(Alex Beutel)是谷歌的资深科学家,于2016年加入谷歌。布伦特毕业于卡内基梅隆大学,获得计算机科学博士学位,师从机器学习的权威亚力克斯·斯莫拉(Alex Smola)。
|
||||
|
||||
最后一位作者艾德·池(Ed H. Chi)是谷歌的主任科学家,他拥有39项专利,已经发表了110多篇论文。在加入谷歌之前,池是帕罗奥图(Palo Alto)研究中心的主任研究员。池毕业于明尼苏达大学,获得计算机科学博士学位。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
我们首先来看这篇文章的主要贡献,梳理文章主要解决了一个什么场景下的问题。
|
||||
|
||||
推荐系统经常需要对当下的场景进行建模,有时候,这些场景被称作“**上下文**”(Context)。在过去比较传统的方法中,已经有不少方法是探讨如何利用上下文信息进行推荐的,比如使用“张量”(Tensor)的形式进行建模;还有一些方法是利用对时间特性的把握,从而对上下文信息进行处理。
|
||||
|
||||
近些年,随着深度学习的发展,越来越多的深度学习模型被应用到推荐系统领域中,但还没有直接探究如何在深度学习模型中使用上下文。这篇文章就想在这一方面做一个尝试。
|
||||
|
||||
这里面有一个比较棘手的问题。过去,这样的上下文常常使用“**交叉特性**”,也就是两个特征的乘积成为一个新的特征。这样的方法在矩阵分解或者张量分解的模型中得到了非常广泛的使用。然而在深度学习中,过去的经验是不直接使用这样的特性。但是,在上下文非常重要的推荐系统中,不使用交叉特性的的结果,往往就是效果不尽如人意。
|
||||
|
||||
这篇文章提出了一个叫“隐含交叉”(**Latent Cross**)的概念,直接作用在嵌入(Embedding)这一层,从而能够在深度模型的架构上模拟出“交叉特性”的效果。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
作者们首先探讨了推荐系统中一个常见的特性,那就是利用交叉特性来达到一个“**低维**”(Low-Rank)的表达方式,这是矩阵分解的一个基本假设。比如每一个评分(Rating)都可以表达成一个用户向量和物品向量的点积。
|
||||
|
||||
那么,作者们就提出了这样一个问题:作为深度学习的基石,**前馈神经网络**(Feedforward Neural Network)是否能够很好地模拟这个结构呢?
|
||||
|
||||
通过模拟和小规模实验,作者们从经验上验证了深度学习的模型其实并不能很好地抓住这样的交叉特性所带来的“低维”表达。实际上,深度学习模型必须依赖更多的层数和更宽的层数,才能得到相同的交叉特性所达到的效果。对于这一点我们或多或少会感到一些意外。同时,作者们在传统的RNN上也作了相应的比较,这里就不复述了。
|
||||
|
||||
得到了这样的结果之后,作者们提出了一个叫作“隐含交叉”的功能。这个功能其实非常直观。传统的深度学习建模,是把多种不同的信息输入直接拼接在一起。“隐含交叉”是**让当前的普通输入特性和上下文信息进行乘积,从而直接对“交叉特性”进行建模**。
|
||||
|
||||
这样做的好处是不言而喻的。之前,我们寄希望于深度学习模型自身能够学习到这样的交叉关系。而现在,作者们直接让上下文信息作用于输入信息和其他的中间特征,使得上下文信息的作用得到了提升。
|
||||
|
||||
这篇文章提出的办法可以说是第一个尝试解决传统推荐系统的一些想法,使之移植到深度学习的语境中。
|
||||
|
||||
## 方法的实验效果
|
||||
|
||||
这篇文章使用了谷歌的Youtube数据来做实验。作者们比较了一系列的方法,得出的结论是RNN配合“隐含交叉”比仅仅使用RNN的效果要好2%~3%,这个提升已经是一个非常可观的数字了。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了WSDM 2018的一篇来自谷歌团队的文章,这篇文章介绍了在传统推荐系统的模型中(比如矩阵分解等)都有的交叉特性如何应用在深度学习中。
|
||||
|
||||
一起来回顾下要点:第一,我们简要介绍了这篇文章的作者群信息;第二,我们详细介绍了这篇文章要解决的问题以及贡献 ;第三,我们分析了文章提出方法的核心内容以及实验结果。
|
||||
|
||||
最后,给你留一个思考题,深度学习模型在默认状态下并不能很好地抓住交叉特性,这是深度模型的问题吗?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
@ -0,0 +1,65 @@
|
||||
<audio id="audio" title="016 | The Web 2018论文精读:如何对商品的图片美感进行建模?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/6e/5c/6e43f655e9aa5e0bb9b527e0649eb75c.mp3"></audio>
|
||||
|
||||
“万维网大会”(The Web Conference 2018)前身叫作“国际万维网大会”(International World Wide Web Conference),从1994年开始举办,已有20多年的历史了,在Google学术排名上,是“信息系统”排名第一的国际顶级学术会议。
|
||||
|
||||
从万维网大会最初举办开始,这个会议就成为了互联网方面独一无二的权威学术会议。会议包含搜索、推荐、广告、数据库、信息提取、互联网安全等诸多领域的优秀论文,每年都吸引着上千名世界各地的学者和工程师来分享他们的最新研究成果。
|
||||
|
||||
2018年的万维网大会于4月23日~27日在法国里昂举行。整个会议收录了171篇论文,还有27个研讨班(Workshop)、19个讲座(Tutorial)、61个展板论文(Poster)和30个演示(Demo)。
|
||||
|
||||
万维网大会的一大特点就是论文成果涵盖了非常广的领域。要在这些论文中找到有价值的学习信息是一件非常耗时、很辛苦的任务。这里给你分享几篇我认为今年这个会议上最有价值的论文,希望能起到抛砖引玉的作用。
|
||||
|
||||
今天,我们就来看一篇优秀论文提名,题目是《基于美感的服装推荐》([Aesthetic-based Clothing Recommendation](https://www.comp.nus.edu.sg/~xiangnan/papers/www18-clothing-rec.pdf))。这篇论文一共有六位作者,除了两位分别来自新加坡国立大学和美国的埃默里大学之外,绝大多数作者都来自清华大学。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
在现代的电商推荐系统中,商品特别是服装服饰的图片,其美观和质量是用户进行购买决策的关键参考因素。不少过去的商品推荐系统已经考虑了图片的属性,特别是尝试同时利用图片信息和文字信息来实现**多模(Multi-Modal)数据理解**的目的,从而能够进行更加智能的推荐。不过,当前的大多数方案都只是考虑基本的图片特性。
|
||||
|
||||
从思路上来说,大多数的类似工作都是利用某种深度神经网络提取图片特性,然后和其他特性(例如我们说过的文本信息)加以组合,从而能够扩宽我们对商品信息的提取。这样提取出来的图像特征自然没有显式地对图像的“美感”(Aesthetic)进行建模。
|
||||
|
||||
这篇文章的作者们认为,商品图片的“美感”是非常重要的属性,针对美感进行建模会有更显著的商品推荐效果。概括来说,这篇论文的一个贡献就是提供了一种模型,来对图片的美感和一般性的图片语义特性同时进行建模。这是一个在过去的工作中都没有的创新点,我们接下来会详细说明一这个模型的架构。
|
||||
|
||||
当作者们提取出了图片的美感信息以后,接下来的一个问题就是如何利用这些特性。这篇论文使用了**张量分解**(Tensor Factorization)的思路。我们在前面介绍推荐系统的时候曾经提到过,张量分解是一种很有效且常用的**利用上下文语义信息**的推荐模型。和一些之前的工作类似,这里作者们采用了三维的张量来表达用户、商品和时间之间的关系。同时,作者们还把图片信息有效地结合到了张量分解中,从而能够利用美感信息来影响推荐结果。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
了解了这篇论文的大体思路以后,我们现在来看看论文的第一个核心部件:**如何利用深度神经网络来提取图片的美感信息?**
|
||||
|
||||
首先,这篇论文提出的模型假设对于每一个商品,我们都有一个**综合的美感标签**,并且还有一个**细节标签**来表达这个商品图案的“**图像风格**”(Style)。美感的综合标签是一个1~10的打分,而图像风格则是文字的图像特征,比如“高曝光”、“对比色”等。那么,我们需要一个神经网络模型,来同时对美感标签和细节的图像风格进行建模。
|
||||
|
||||
具体来说,文章提出的模型分为了两个层次。第一个层次是用来解释细节的图像风格。在本文采用的数据中,一共有14种图像风格,作者们就用了14个**子网络**(Sub Network)来针对这些风格。每个风格都对应一个独立的**子神经网络**。每一个子神经网络都是标准的“**卷积网络**”(CNN)。他们的目标是尽可能地学习到特性来表示每个细节的图像风格。
|
||||
|
||||
当我们有了第一层的14个子网络之后,再把这些子网络学习到的特性都整合起来,形成**中间特性层**,然后再经过一个卷积网络,从而学习到一个对商品的整体美感评分进行解释的神经网络。
|
||||
|
||||
在文章中,作者们提到这两个层次的神经网络并不是分类进行训练的,而是**在一个整体中进行训练**。意思就是说,我们同时训练底层的针对图像风格的14个子网络的参数,以及高层次的针对美感评分的网络的参数。
|
||||
|
||||
当我们得到了图片的美感信息之后,下一步,就来看一下**如何利用张量分解来进行商品推荐**。
|
||||
|
||||
相比于传统的张量分解,在这篇文章中,作者们提出了一种新颖的,针对商品推荐的张量表达模式,叫作“动态协同过滤”(Dynamic Collaborative Filtering),或简称 **DCF**。
|
||||
|
||||
DCF认为,每一个用户对于某个商品的购买取决于两个方面的因素。第一,用户是否对这个商品有喜好。第二,这个商品是不是符合时间维度上面的“流行度”。作者们认为,只有当这两个条件同时满足的时候,也就是用户喜欢某个当季的商品时才会做出购买的决定。因此,作者们使用了**两个矩阵分解**来分别代表这两个假设。
|
||||
|
||||
第一个矩阵分解是针对用户和商品这个矩阵,这里我们会学习到用户对商品的**喜好度**。第二个矩阵分解是针对时间和商品这个矩阵,这里我们会学习到时间和商品的**流行度**。然后,作者把这两个矩阵分解(或者说是把两个矩阵)相乘,这就得到了一个张量,来表达**用户在时间维度上对商品的喜好**。
|
||||
|
||||
那么,如何把刚才学习到的图片美感信息给融入到这个新的张量学习框架下呢?作者们是这么做的,针对我们刚才所说的两个矩阵分解进行“扩展”。
|
||||
|
||||
刚才我们说,这个张量分解是基于一个假设,那就是用户在时间维度上的购买决定取决于,用户是否对这个商品有喜好,以及这个商品是不是符合时间维度上面的“流行度”。我们用了两个矩阵分解来表达这两个假设。每一个矩阵分解都是把一个大的矩阵分解成两个向量,比如用户和商品的矩阵就被分解为用户特性和商品特性。
|
||||
|
||||
基于此,作者们就在这个用户和商品的矩阵后面,再加上一个商品和图片美感信息矩阵,用来混合这两种信息。也就是说,我们刚才的第一个假设,用户对商品的好感,就被扩展成了**两个矩阵的加和**,用户和商品矩阵以及商品和图片信息矩阵,这两个矩阵的加和依然是一个矩阵。同理,时间和商品的流行度,被扩展成了时间和商品矩阵以及商品和图片信息矩阵的加和。也就是说,新的模型是两个矩阵的乘积组成的张量分解,而这里的每个矩阵分别又是两个矩阵的加和。这就是作者们最终提出的模型。
|
||||
|
||||
## 方法的实验效果
|
||||
|
||||
作者们在亚马逊的衣服数据集上做了实验来验证模型的有效性。这个亚马逊的数据集由将近四万的用户、两万多的商品和超过二十七万的购买信息构成。除了这篇文章提出的模型以外,作者们还比较了一些其他算法,例如完全随机的算法、只推荐最流行的商品、传统的矩阵分解模型以及只有基本图像信息但没有美感信息的算法。文章汇报了排序的精度NDCG以及“召回”(Recall)等指标。
|
||||
|
||||
从实验效果来看,这篇文章提出的模型要明显好于矩阵分解以及只有基本图像信息的算法,表明针对产品的图像美感进行建模是有价值的。并且,作者们提出的新的张量分解方法也被证明是切实有效的。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了今年万维网大会的一篇优秀论文。文章介绍了如何对商品的图片美感进行建模,以及如何把提取到的信息融入到一个基于张量分解的推荐系统中。
|
||||
|
||||
一起来回顾下要点:第一,我们详细介绍了这篇文章要解决的问题以及贡献;第二,我们简要地介绍了文章提出方法的核心内容;第三,我们简单分享了一下模型的实验成果。
|
||||
|
||||
最后,给你留一个思考题,有没有在没有标签情况下对图片的美感进行建模的呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
@ -0,0 +1,47 @@
|
||||
<audio id="audio" title="017 | The Web 2018论文精读:如何改进经典的推荐算法BPR?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/78/5d/784defb4b0e3f96b1189c541c12bef5d.mp3"></audio>
|
||||
|
||||
今天,我们来看万维网大会上的一篇优秀短论文。在万维网大会上,主要发表两类论文。一类是10页的长论文,一类是2页的短论文或称作展板论文。短论文主要是发表短小的成果或者是还在研究过程中的重要成果。每一届的万维网大会,都会评选出一篇最佳短论文奖。
|
||||
|
||||
今天我和你分享的论文,题目是《利用查看数据,贝叶斯个性化排序的一种改进的取样器》([An Improved Sampler for Bayesian Personalized Ranking by Leveraging View Data](https://www.comp.nus.edu.sg/~xiangnan/papers/www18-improvedBPR.pdf))。这篇论文也有六位作者,和我们介绍的上一篇论文一样,都来自清华大学和新加坡国立大学。
|
||||
|
||||
## 贝叶斯个性化排序
|
||||
|
||||
要想理解这篇论文的内容,我们必须要讲一下什么是“**贝叶斯个性化排序**”(Bayesian Personalized Ranking),或者简称是**BPR**。有关BPR的详细介绍,可以阅读参考文献[1]。我们在这里仅对BPR进行一个高维度的总结。
|
||||
|
||||
简单来说,**BPR是推荐系统中的一个配对排序(Pairwise)学习算法**。在我们前面介绍搜索算法的时候,曾经提到了各种配对排序学习算法。配对排序学习不是针对每一个数据实例来学习其标签或者响应变量,而是学习一个相对的顺序,希望能够把所有的正例都排列到负例之前。也就是说,对于配对排序来说,每一个数据实例的预测值本身并不重要,排序算法在意的是对于一正一负的一个配对来说,是否能够把正例给准确地排列到负例之上。这其实就要求BPR在数值上对正例的预测值能够比负例的预测值高。
|
||||
|
||||
BPR主要是解决了在推荐系统中长期以来只对单个数据点进行预测,比如需要对用户物品的喜好矩阵建模的时候,之前的大多数算法都无法有效地对没有观测到的数据进行建模。而BPR是配对算法,因此我们只需要关注观测的数据以及他们之间的关系,从而能够对用户的喜好,特别是有“**隐反馈**”(Implicit Feedback)数据的时候,取得更加明显的效果。这里的隐反馈指的并不是用户告诉系统其对每一个物品的喜好程度,而是用户在和系统的交互过程中通过一些行为表达出的喜好。这些用户的行为往往并不全面,因此需要算法和模型能够对这些行为进行有效建模。
|
||||
|
||||
## 论文的主要贡献和核心方法
|
||||
|
||||
了解了BPR大概是怎么回事以后,我们来看一看这篇论文的主要贡献和核心方法。
|
||||
|
||||
首先我们刚才讲到BPR的核心是学习一个配对的排序问题。那么在训练的时候,我们需要对一个正例和一个负例的配对进行学习,更新参数。然而在一个自然的用户隐反馈数据集里,正例相对来说往往是少数,负例则是绝大多数。因此,一个传统的方法就是在组成一个配对的时候,相对于一个正例来说,我们都“均匀地”(Uniformly)选取负样本来组成配对,这个过程有时候也叫“采样”(Sampling)。
|
||||
|
||||
这篇论文有两个主要贡献。第一个贡献是,作者们发现,如果在全局均匀地采样负样本,第一没有必要,第二可能反而会影响最后学习的效果。第二个贡献是,针对电子商务的应用,作者们发明了一种负样本采样的方法,使得学习算法可以利用到更多的用户“浏览”(View)信息,从而能够对算法的整体训练效果有大幅度的提升。
|
||||
|
||||
## 方法的实验效果
|
||||
|
||||
这篇论文的数据集分别使用了母婴产品“贝贝网”和天猫的数据。其中,贝贝网有约16万用户、12万商品、260万次购买和4600万次浏览;天猫的数据则有3万用户、3万多商品、46万次购买和150多万次浏览。两个数据集都呈现了大于99%的“稀疏度”(Sparsity)。
|
||||
|
||||
首先,作者们实验了不从全局中选取负样本而仅仅采样一部分,而且是相比于原来的空间非常小的样本,比如仅仅几百个负样本而不是几万个的情况。实验效果在贝贝网上不仅没有影响算法的精确度,算法的精确度反而还有提升。而在天猫的数据集上,算法效果没有提升,而有一些小幅度的下降,但是作者们认为这样的代价还是值得的,因为数据集的减少,算法的训练时间会大幅度降低。从这个实验中,作者们得出了不需要从全局进行采样的结论。
|
||||
|
||||
紧接着,作者们提出了一个新的概念,那就是,对用户的数据集合进行划分,把用户的行为分为“购买集”(C1)、“浏览但没有购买集”(C2)、“剩下的数据”(C3)这三个集合。作者们提出,BPR要想能够达到最好的效果,需要对这三种数据集进行采样。也就是说,我们需要组成C1和C2、C1和C3以及C2和C3的配对来学习。
|
||||
|
||||
具体来说,用户在贝贝网和天猫的数据中尝试了不同的比例来对这三种集合进行采样。总体的经验都是C3中采样的数据要大于C2中的,然后要大于C1中的。这其实就是说训练算法要更好地学习到用户不喜欢某件东西的偏好。采用这样的采样方式,作者们展示了模型的效果比传统的BPR或仅仅使用“最流行的物品”作为推荐结果要好60%左右。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了今年万维网大会的一篇优秀短论文。文章介绍了如何对一个经典的推荐算法BPR进行改进,从而提高效率并且大幅度提升算法有效度。
|
||||
|
||||
一起来回顾下要点:第一,我们从高维度介绍了BPR的含义;第二,我们简要介绍了论文的主要贡献和思路;第三,我们简单分享了论文的实验成果。
|
||||
|
||||
最后,给你留一个思考题,除了这篇论文提出的组成正例和负例的配对思路以外,你能不能想到在用户浏览网站的时候,还有哪些信息可以帮助我们组成更多的配对呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
**参考文献**
|
||||
|
||||
1. Steffen Rendle, Christoph Freudenthaler, Zeno Gantner, and Lars Schmidt-Thieme. BPR: Bayesian personalized ranking from implicit feedback. Proceedings of the Twenty-Fifth Conference on Uncertainty in Artificial Intelligence (UAI '09). AUAI Press, Arlington, Virginia, United States, 452-461, 2009.
|
||||
|
||||
|
@ -0,0 +1,55 @@
|
||||
<audio id="audio" title="018 | The Web 2018论文精读:如何从文本中提取高元关系?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/73/4d/738339e0acafabf51dad743e363eb84d.mp3"></audio>
|
||||
|
||||
今天我们来看万维网大会2018的最佳论文,标题是“HighLife: Higher-arity Fact Harvesting”。作者都来自德国著名的“马克斯·普朗克计算机科学研究所”(Max Plank Institute for Informatics)。这个研究所是德国最大的基础科学研究组织“马克斯·普朗克学会”(Max-Planck-Gesellschaft)的分支研究机构,致力于在科学刊物上发表新的研究成果,开发软件系统和培养新的科学研究工作者。马克斯·普朗克学会因其杰出的科研成果在德国甚至全世界都获得了很高的声誉。
|
||||
|
||||
## 什么是高元关系?
|
||||
|
||||
这篇论文主要是涉及到**高元(Higher-Artiy)关系**的提取。那什么是高元关系呢?
|
||||
|
||||
传统的信息提取和知识库主要是关注二元关系的提取和存储。例如,我们可以知道居里夫人分别于1903年和1911年获得了诺贝尔奖。但是关系数据库中并不知道这两年的奖项分别是物理和化学。同理,我们可以在知识库中存放居里夫人获得过诺贝尔物理奖以及诺贝尔化学奖的信息,但是就无法和1903年和1911年这两个信息进行配对。通过这个例子我们可以看出,基于二元关系的信息提取和知识库虽然简单易行,但是有其先天的局限性。
|
||||
|
||||
这篇论文要讨论的高元关系,就是希望能够直接对“居里夫人在1903年获得了诺贝尔物理学奖”这样的三元甚至更高元的关系进行提取和表征。作者们认为这篇论文是较少的关注高元关系提取的先驱工作。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
我们刚才说了,这篇论文的一个重要贡献就是针对高元关系的提取所作出了很多努力。
|
||||
|
||||
具体来说,作者们使用“**种子事实**”(Seed Facts)作为一种监督信息来学习**模式**(Patterns),并且利用这些学习到的模式来寻找更多的“**候选事实**”(Facts Candidates),如此循环。这是把过去的一种针对二元关系提取的方法给扩展到高元关系。这个方法的潜在问题是:在能够保证“高召回”(High Recall)的情况下,得到的很多关系可能存在“**噪声**”和“**目标浮动**”(Target Drifts)。这里所说的目标浮动指的是我们提取的事实有可能存在主题上的偏差。
|
||||
|
||||
为了解决这个问题,作者们在这篇论文里利用了“**限制推理**”(Constraint Reasoning),来对已经得到的事实进一步筛选以得到最后的结果。这里的限制可以是“类型”(Type)上的,比如,我们限制提取到的普利策奖为“书籍”而非“电影”或“音乐”。通过这些在取值或者类型上的限制,我们可以对获取到的事实进行清理。
|
||||
|
||||
论文解决的另外一个难点就是很多高元信息在原始的文本中就是缺失的,或者是不完全的。比如,“Google于2014年收购了Nest”这个事实就没有提及金额,而“Google以32亿美元收购了Nest”这个事实又没有提及时间。作者们针对这个情况,把整个框架给扩展到了缺失信息中,从而能够从原始文本中拼凑多元关系。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
文章提出了一个由好几个组件组成的系统用于信息的提取。
|
||||
|
||||
首先,有一个叫作 **NERD**的组件,即“人名识别和去歧义”组件,用于从句子中提取不同的“实体”。这里面运用到了很多外部的信息库,比如医疗生物实体库“联合医疗语言系统”(Unified Medical Language System)、支持新闻实体的AIDA系统以及WordNet语料库。同时,在这个部分,NERD还依赖于“斯坦福自然语言处理核心库”(Stanford CoreNLP)提供“人名识别”以及“词类分析”(Part of Speech)等基础功能。
|
||||
|
||||
在提取了人名和实体名之后,作者们就开始构建一个从词类分析得到的**树型数据结构**。这个数据结构的目的是反映N元关系和内部信息的架构。这个部分基本上也是依赖传统的自然语言处理所得到的树结构,只不过进行了简单的修正。
|
||||
|
||||
得到树结构之后,接下来的一系列工作都是**在这个树结构上获取不同的模式,从而能够得到想要的高元关系**。这里面有很多细节,我们在这里就不赘述了。比如,作者们利用“**树挖掘**”(Tree Mining)技术来发现频繁出现的子树结构,从而认定某个子树模式是不是一个好的候选事实。这里的思路其实和经典的“**频繁模式挖掘**”(Frequent Pattern Mining)一样,都是去不断地计算一个结构的“**支持度**”(Support)和“**置信度**”(Confidence),从而通过两个值来决定是不是要把这个模式给留下来。
|
||||
|
||||
除此以外,这一部分的部件还需要支持“**部分N元候选事实**”(Partial N-ary Fact Candidate)的匹配。之前我们也讲过了,这个功能也算是这篇论文的一个贡献。这里面的重要职能就是能够对树的一部分结构进行匹配,而不需要对所有的部分都能够完全一致。
|
||||
|
||||
当作者们通过树挖掘从而发现了基本的候选事实之后,下面需要做的工作就是针对这些候选事实进行推理盘查,看是不是所有的事实都能经得住推敲。也就是说,我们需要查看有没有存在多个事实不一致的地方。
|
||||
|
||||
需要指出的是,从整体上来看,所有组件的流程基本上都是**无监督的数据挖掘操作**。也就是说,整个系统并不需要依赖于什么训练数据。
|
||||
|
||||
## 方法的实验效果
|
||||
|
||||
作者们在纽约时报数据集以及PubMed数据集上都进行了实验,主要观测的指标是“**精度**”(Precision)。我们之前提到过,这篇文章所研究的高元关系提取,这个问题很新颖。因此,作者们还利用CrowdFlower众包平台来获取了数据的标签,用于检测所提取关系的准确度。当然这部分数据量相对来说是比较小的。
|
||||
|
||||
从实验的效果上来说,文章提出的方法能够达到平均接近80%~90%的精度,这可以说是非常令人振奋的结果了,而达到这样的结果仅仅需要几百个种子事实。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了今年万维网大会的优秀论文。文章介绍了如何从文本中提取高元关系,这是一个比较新的研究领域。
|
||||
|
||||
一起来回顾下要点:第一,我们简单讨论了高元关系的含义;第二,我们重点介绍了论文的主要贡献和核心思路;第三,我们简单分享了提出方法的实验成果。
|
||||
|
||||
最后,给你留一个思考题,在什么样的应用中,我们可以利用到这篇文章提出的高元关系?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
@ -0,0 +1,69 @@
|
||||
<audio id="audio" title="019 | SIGIR 2018论文精读:偏差和“流行度”之间的关系" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d7/bc/d7ba4ead70962379830a4a41cdfdf8bc.mp3"></audio>
|
||||
|
||||
2018年的SIGIR(国际信息检索研究与发展大会)于7月8日~12日在美国密歇根州的安娜堡举行。从今天开始,我将精选几篇大会上最有价值的论文,和你一起来读。
|
||||
|
||||
我先简单介绍一下这个大会。SIGIR从1978年开始举办,有40年的历史,是信息检索和搜索领域的顶级会议。SIGIR 2018,全称是The 41st International ACM SIGIR Conference on Research and Development in Information Retrieval。
|
||||
|
||||
从最初举办开始,这个会议就成为了信息检索领域,特别是搜索技术和推荐技术方面的权威学术会议。会议的内容往往包含了搜索、推荐、广告、信息提取、互联网数据挖掘等诸多领域的优秀论文,每年都吸引着来自世界各地的学者和工程师参会,来分享他们最新的研究成果。
|
||||
|
||||
今天,我们首先来看一看今年的最佳论文,标题是《推荐系统中流行度有效性的概率分析》([Should I Follow the Crowd? A Probabilistic Analysis of the Effectiveness of Popularity in Recommender Systems](http://ir.ii.uam.es/pubs/sigir2018.pdf))。
|
||||
|
||||
这篇论文一共有两位作者,均来自马德里自治大学(Universidad Autónoma de Madrid)。第一作者罗西奥·卡纳马雷斯(Rocio Cañamares)已经发表了好几篇相关主题的论文,第二作者帕布罗·卡斯蒂罗斯(Pablo Castells)是马德里自治大学、甚至是整个欧洲的信息检索学术权威。论文有超过5千次的引用。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
想要理解清楚这篇论文的主要贡献,我们首先要从推荐系统,或者是从更大的方向上,来看所有信息检索系统都存在的一个核心问题,那就是“**偏差**”(Bias)。偏差会带来一系列问题。这对推荐系统甚至信息检索系统的建模和评价都带来了巨大的挑战。
|
||||
|
||||
那么,为什么信息检索系统会有偏差呢?
|
||||
|
||||
我这里举一个简单的例子来说明。假设我们有两个物品和很多用户。对于每一个用户来说,系统都按照随机的顺序,分别给用户展示这两个物品,并且询问用户是否喜欢。
|
||||
|
||||
在这样的假设里,顺序是随机的,因此对于同一个用户来说,用户是否喜欢某一个商品,就完全是取决于这个物品本身的属性。对于所有用户来说,在整体上呈现的用户对这两个物品的喜好,则完全来自于大家对这两个物品本身的一种评价。那么,我们可以看到这里面没有任何的偏差。
|
||||
|
||||
然而,只要这个场景稍微有一些改变,就很容易引入各种偏差。比如,我们有超过一万件物品。尽管我们还是随机地展示给用户,但用户可能在看过一定数量的物品之后就慢慢厌倦了,那么,用户对于物品的喜好判断或许就会受到厌倦的影响,甚至,用户还很有可能直接放弃查看后面的物品。
|
||||
|
||||
还有很多相似的情况,比如我们不是把每个商品逐一展示给用户看,而是提供一个列表。那么,用户很有可能会以为这个列表有一定的顺序,比如在列表排名上方的物品可能是比较重要的。有研究表明,在有列表的情况下,用户很可能会按照列表的顺序提供某种喜好判断。很明显,在这样的情况下,用户的喜好判断就受到了这个列表顺序的干扰。
|
||||
|
||||
上面我们提到的都是“**表现偏差**”(Presentation Bias)。除此以外,一个信息系统其实还有很多类型的偏差,比如**系统性偏差**:一个新闻系统,只给用户推荐娱乐新闻,而不给用户看时政新闻,在这样的情况下,用户表现出来的喜好性就是有偏差的,因为系统没有给用户表达对时政新闻喜好的可能性。
|
||||
|
||||
信息检索和推荐系统的学者其实很早就意识到了偏差对于建模的影响。不管是我们这里提到的表现偏差还是系统性偏差,如果我们直接利用用户和系统交互产生的数据,那么训练出来的模型以及我们采用的衡量模型的办法也会有偏差,那我们得出的结论有可能就是不精准的。
|
||||
|
||||
这篇论文就是希望能够**系统性地讨论偏差在推荐系统中所带来的问题**。具体来说,这篇论文主要是探讨偏差和“**流行度**”(Popularity)之间的关系。
|
||||
|
||||
这里描述的是这样一种情况:有一些物品很有可能曾经给很多人推荐过,或者同时还被很多人喜欢过或者评价过,那么,这种流行度高的物品会不会对推荐结果的评价带来意想不到的偏差呢?
|
||||
|
||||
在过去的研究中,大家只是对这种流行度高的物品有一种直观上的怀疑,认为如果一个推荐系统仅仅能够推荐流行的物品,那肯定是有偏差的。但之前的很多工作并没有定量地去解释这里面偏差和评价之间的关系。**这篇论文就提供了一个理论框架,指导我们去理解偏差以及偏差带来的评测指标的一些变化**。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
今天我们不去讲这篇论文的理论框架细节,我会重点提供一个大体的思路,帮助你理解这篇论文希望达到的目的。
|
||||
|
||||
简单来说,为了表达偏差和流行度之间的关系,作者们用了这么几个**随机变量**:用户是否对某个物品打分,用户是否对某个物品有喜好,以及用户是否观看某个物品。这里面的一个细节,或者说技巧,就是如何用概率的语言把这三者之间的关系给表达清楚。
|
||||
|
||||
作者其实采用了一些简化的假设,比如假设在测试集上的物品是训练集上没有出现过的等等。这样,就能够写出在测试集上用户对物品评价的一个**期望关系**,这个期望关系包含用户对所有测试物品是否有喜好。有了这层期望关系以后,就开始推导出,在测试集上**理想状态下的最佳排序是一个什么样子**。在这里的理论讨论其实并没有很大的现实意义,但是这是第一次研究人员用数学模型去详细表征一个最优的在测试集上的按照流行度排序的结论。
|
||||
|
||||
紧接着,作者们还讨论了这个最优排序在两种极端情况下的变化。一种情况是用户过往的行为都是仅依赖于物品本身的属性,而没有任何其他偏差。另外一种情况是用户过往的行为和物品本身的属性无关,意思就是仅依赖于其他的偏差。
|
||||
|
||||
在第一种极端情况下,最优的排序其实也就是我们所能观测到的最优排序,那就是按照物品的流行度。在第二种极端情况下,最优的排序其实是**按照平均打分**。
|
||||
|
||||
当然,你可能会说讨论这两种极端情况并没有现实意义呀。但这两种极端情况的讨论其实就证明了,**只有在没有偏差的情况下,按照物品的流行度排序才是平均情况下最优的**。而很明显,现实存在偏差,因此依靠流行度的排序,即便是平均情况下,也不是最优的选择。
|
||||
|
||||
然后,论文讨论了用户是否观看某一个物品对用户行为的影响。关于这一部分的讨论,其实之前已经有很多工作都做了一些类似的探索。不过这篇论文得出了一个有意思的结论。在考虑了用户观看物品的偏差以后,通过模拟的方法,我们会发现:**随机结果的效果其实要比之前的观测值要好很多,而按照流行度的排序虽然不错,但是比随机的效果并没有好很多,而基于平均打分的结果其实要优于按照流行度的排序**。可以说,这是一个有别于之前很多工作的新发现。
|
||||
|
||||
## 延申讨论
|
||||
|
||||
虽然这篇论文获得了SIGIR 2018的最佳论文奖,但是如果我们站在更大的角度上来分析这篇论文,其实就会发现,作实际上作者们是开发了一套特有的理论框架来描述推荐系统中的某一种偏差。**更加普适化的对偏差的建模其实需要有随机化的数据,以及利用因果推断的办法来对任意情况下的偏差进行分析**。文章提出的概率模型仅仅在这篇文章讨论的假设情况下才能成立。
|
||||
|
||||
当然,瑕不掩瑜,这篇文章不管是从结论上,还是从实际的分析过程中,都为我们提供了很多有意义的内容,帮我们去思考偏差对于建模所带来的挑战以及我们应该如何应对。
|
||||
|
||||
## 总结
|
||||
|
||||
今天我为你讲了今年SIGIR 2018的最佳论文。
|
||||
|
||||
一起来回顾下要点:第一,我们详细介绍了这篇文章要解决的问题和贡献,探讨偏差和流行度之间的关系,系统性地来讨论偏差在推荐系统中所带来的问题;第二,我们简要地介绍了文章提出方法的核心内容,包括设定随机变量、期望关系以及推导理想状态下的最佳排序;第三,针对论文我们简单进行了讨论。
|
||||
|
||||
最后,给你留一个思考题,在不考虑偏差的情况下,为什么一般的推荐系统会偏好于推荐流行物品的算法呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
@ -0,0 +1,63 @@
|
||||
<audio id="audio" title="020 | SIGIR 2018论文精读:如何利用对抗学习来增强排序模型的普适性?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b2/f2/b2663ec0b6928eaabe7f0e80dbd4c4f2.mp3"></audio>
|
||||
|
||||
今天,我们继续来精读SIGIR 2018(国际信息检索研究与发展大会)的论文,今天分享的是本次大会的最佳短论文,标题是《使用对抗学习实现神经排序模型的跨领域正则化》([Cross Domain Regularization for Neural Ranking Models using Adversarial Learning](https://arxiv.org/pdf/1805.03403.pdf))。
|
||||
|
||||
非常有必要先简单介绍一下这篇文章的作者群,可以说这是一个“明星阵容”。
|
||||
|
||||
第一作者丹尼尔·科恩(Daniel Cohen)来自马萨诸塞大学阿默斯特分校(University of Massachusetts Amherst),是计算机科学系的博士生。2017年曾经在微软研究院剑桥分部实习。这篇获奖论文就是实习项目的总结。在深度学习模型在信息检索的应用这个方向上,科恩已经发表了多篇论文。
|
||||
|
||||
第二作者巴斯卡·米特拉(Bhaskar Mitra)是微软研究院剑桥分部的主任级科学家。近些年米特拉在信息检索领域很活跃,并且极力推动深度学习在这个领域的发展,他在这个领域发表了很多篇论文,在过去几年的很多学术会议上,也主持了多个关于深度学习和信息检索相结合的讲座。
|
||||
|
||||
第三作者卡特娜·霍夫曼(Katja Hofmann)也是来自微软研究院剑桥分部的科学家。霍夫曼在信息检索领域的主要贡献是研究在线排序学习。
|
||||
|
||||
论文的最后一位作者,布鲁斯·克罗夫特(W. Bruce Croft)是信息检索领域的学术权威,也是科恩的博士导师。他是美国ACM院士,还是信息检索领域最高学术荣誉奖杰拉德·索尔顿(Gerard Salton)奖的获得者。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
这篇论文主要涉及了这么两个重要概念的结合。第一个概念是“**跨领域**”(Cross Domain)信息检索,第二个概念就是“**对抗学习**”(Adversarial Learning)。
|
||||
|
||||
跨领域信息检索主要是指我们需要对一个以上的领域进行搜索。这里所说的领域主要是指不太相同,或者非常不同的文档集合。例如,如果我们要针对体育新闻、金融新闻等进行搜索,这里的“体育”和“金融”就是不同的领域。
|
||||
|
||||
跨领域信息检索的核心挑战是我们如何针对不同的领域都能进行有效搜索。比如,如果我们的排序算法本身或者其特性依赖于某个特定领域的信息,例如关于体育方面的搜索,需要依赖体育运动员的名字,那这种信息肯定会很少甚至完全不出现在另外一个领域。因此,**依靠某些领域特有的信息很难做到真正的跨领域信息检索**。
|
||||
|
||||
这篇文章的贡献是作者们认为,想要对跨领域的信息进行较好地检索,就**需要训练这样的排序模型:不容易被某一个特定的领域所主导,同时也尽量不偏好某一个特定领域的具体信息**。
|
||||
|
||||
如何实现这个目的呢?作者们使用了一种叫做“**对抗学习**”的技术。这也是这篇论文能够获奖的重要原因。
|
||||
|
||||
我在这里简单介绍一下对抗学习的原理。对抗学习最初的思想来自于利用深度产生模型解决计算机视觉中的一系列问题。最基本的对抗学习的模式主要是用于产生数据,而且是比较复杂的数据,例如图像。
|
||||
|
||||
**对抗学习有两个模块,一个模块叫产生器,一个模块叫判别器**。产生器的作用就是产生数据。判别器的作用是判断产生的数据是不是“真实数据”。产生器的最终目的是产生能够以假乱真的数据来扰乱判别器的判断能力。判别器的最终目的是不断提高判断能力从而能够分辨出数据的真假。
|
||||
|
||||
当然,最初的时候,产生器产生数据来源于随机噪声,因此判别器可以很容易地判断数据的真假。但是慢慢的,产生器产生的数据就会越来越接近真实数据,而判别器也很快在这个过程中得到优化,从而能够判别数据的真假。当然,这是一个动态的过程,最终,判别器和产生器的状态会稳定下来。
|
||||
|
||||
对抗学习这种思想最初被提出来的时候,主要是应用在计算机视觉领域中,用来产生以假乱真的图片。之后,这种技术被广泛应用到人工智能的各个领域。
|
||||
|
||||
这篇论文最大的一个贡献,就是**利用了对抗学习的理念来增强学习到的排序模型的普适性**,从而尽量避免学习到仅仅对一个领域有用的信息。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
具体来说,这篇文章提出了这样一种方法。首先,我们有两套模型,一套是用于学习查询关键词和文档之间的相关关系的;一套是对抗学习的模型。然后,这两套模型的首要任务是更加精准地针对相关的文档和不相关的文档进行建模。这是整个框架里最主要的目标函数。
|
||||
|
||||
文章提出框架中新的模块是**利用对抗学习来分别产生相关的和不相关的文档**。具体来说,某一种类型的文档就像我们刚才提到的图片一样,我们希望能够利用产生器来进行产生这类数据。当然,我们依然需要判别器来引导产生器的工作。
|
||||
|
||||
在这篇文章中,相关的概念主要是看一个文档是不是某一个领域的。也就是说,我们希望对抗学习能够帮助识别某一个文档是不是来自于一个领域。当对抗学习模型被训练好的时候,对于查询关键词和文档的相关模型,我们就会利用一种叫做“**梯度反转**”的技术,强行偏离模型希望去拟合某一个领域的倾向。
|
||||
|
||||
从网络结构上看文章提出的模型,查询关键词和文档都需要经过**卷积层**、**提取层等变换**,然后进行俗称的“**哈达马积**”(Hadamard product),其实就是**对应项乘积**。这样,文档和查询关键词所提取出来的隐含特征就结合在一起。这个结果之后又经过一系列**稠密层的变换**,最终预测一个相关度的标签。对于对抗学习模型来说,对抗中的判别器从刚才所说的架构中提取一些中间层作为输入,然后判断这个文档是不是出现在某个领域中。
|
||||
|
||||
## 实验结果
|
||||
|
||||
在一个雅虎的搜索数据集以及另外两个数据集上,作者们对论文所提出的模型进行了实验。实验主要是看如果我们在某一个领域上训练出的模型,会不会在另外一个领域上表现优异。
|
||||
|
||||
一个不令人意外的结果是,如果我们在全部领域上进行训练数据,自然在所有的领域上效果都不错。当然,文章展示了,如果利用文章提出的方法,针对某一个领域,比如运动类文档,在训练的时候完全移除所有的文档,在测试集上依然有不错的表现。实验的结果比不进行对抗训练的效果要好5%以上。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了今年SIGIR上的最佳短论文。
|
||||
|
||||
一起来回顾下要点:第一,这篇论文主要涉及了两个概念,分别是跨领域信息检索和对抗学习,我们详细介绍了这篇文章的主要贡献,就是利用对抗学习的理念来增强所学排序模型的普适性;第二,我们简要地介绍了文章提出的方法核心内容,训练两套模型,利用对抗学习来分别产生相关的和不相关的文档;第三,我们简单介绍了论文的实验结果,进行对抗训练会有更好的效果。
|
||||
|
||||
最后,给你留一个思考题,除了使用对抗训练,你还能想到什么方法,能够比较好地学习到不属于某一个特定领域信息的排序模型?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
@ -0,0 +1,63 @@
|
||||
<audio id="audio" title="021 | SIGIR 2018论文精读:如何对搜索页面上的点击行为进行序列建模?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/dd/69/dd4c3b4fdd01500eaef838d5f3337169.mp3"></audio>
|
||||
|
||||
今天我们将继续来精读SIGIR 2018的论文。
|
||||
|
||||
我们已经分享了SIGIR 2018的最佳论文,介绍了如何对推荐系统中的偏差进行建模,从而能够利用这种对偏差的理解,来更加准确地对待基于流行度的推荐结果。周一我们分享了本次大会的最佳短论文,主要讲了如何利用对抗学习的技术来让学习的排序模型更加“健壮”,可以被应用到多个领域上。
|
||||
|
||||
今天我们分享的论文题目是《页面搜索的点击序列模型》(A Click Sequence Model for Web Search)。
|
||||
|
||||
文章的第一作者阿列克谢·博里索夫(Alexey Borisov)来自俄罗斯的搜索引擎Yandex,并且在阿姆斯特丹大学攻读博士学位。之前,他已经发表过了多篇关于“点击模型”(Click Model)和深度学习模型结合的论文。
|
||||
|
||||
文章的第二作者马丁·万德纳(Martijn Wardenaar)、第三作者伊雅·马尔科夫(Ilya Markov)和最后的作者马顿·德里克(Maarten de Rijke)也都来自阿姆斯特丹大学。其中,马顿是荷兰的计算机科学家,欧洲的信息检索学术权威,并且还是荷兰皇家科学院成员。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
我先对这篇论文的核心思想做一个提炼,就是利用深度学习模型,来对用户在搜索页面上的点击行为进行建模。
|
||||
|
||||
传统上,这种对用户在搜索页面上的点击行为进行建模的思路就是“**点击模型**”。从2000年开始,对点击模型的研究,就成为了信息检索以及搜索领域中一个非常活跃的课题。在最近10年的时间里,研究人员提出了几十种不同的点击模型。总体来说,不同的点击模型主要是对不同的用户行为进行编码,从而能够更加准确地对用户的点击行为进行预测。
|
||||
|
||||
在很多传统的点击模型中,为了简化模型,经常使用的一个假设是:针对每一个查询关键词,用户在搜索结果页只进行一次点击。在这种简化了的假设下,研究人员对用户的浏览、点击以及页面的偏差(例如位置偏差)进行建模,就会变得更加容易。然而,在很多场景中,这种假设就显得过于简化了。在同一个查询关键词的搜索结果页面下,很多用户都会点击多个结果。因此,对于多个点击结果的建模就变得重要起来。
|
||||
|
||||
这篇论文就是**针对用户在搜索页面上的点击行为进行了序列建模**,使得我们可以轻松地对每一个搜索页面进行预测,比如会有多少点击以及在什么位置点击等。
|
||||
|
||||
同时,这篇论文还有一个贡献,就是**利用了深度学习中的循环神经网络(RNN)来对查询关键词的结果进行建模,扩宽了传统的完全基于概率建模的点击模型在深度学习时代下的表现力**。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
论文提出方法的核心思路是针对每一个查询关键词,模型需要对所有可能的点击序列进行建模。这个任务是通过构建一个神经网络来完成的。
|
||||
|
||||
具体来说,文章提出的模型有两个重要的模块,**编码器**(Encoder)和**解码器**(Decoder)。
|
||||
|
||||
编码器的作用是利用查询关键词和搜索结果为输入,生成它们的“**嵌入向量**”(Embedding Vector)。近年来,嵌入向量是深度学习建模中的一个重要技术手段,它的目的往往是先把一些离散变量信息转化为连续信息。在这里,查询关键词和搜索结果都可以首先表征为离散的输入信息,然后需要映射到一个共同的语义空间。这可以被认为是一个中间结果,或者在概率模型中,这往往被认为是一个隐含变量。
|
||||
|
||||
解码器的作用是根据这个中间的嵌入向量表达下的查询关键词和搜索结果,然后决定在哪一个位置上可能会或者不会发生点击。这其实就是一个**多类的分类问题**。那么,怎么才能让解码器终止在某一个状态呢?作者们引入了一个特殊的符号代表序列的终止。这样,解码器也需要预测是否需要终止。类似的对解码器的操作在深度序列建模中十分常见。
|
||||
|
||||
可以说,作者们在设计编码器和解码器的结构上也是费了一番功夫的。
|
||||
|
||||
对于编码器而言,作者们认为一个好的嵌入向量必须包含当前的结果信息,以及当前结果周围的结果,或者说是上下文的信息,以及查询关键词的信息。这样,可以把每一个搜索结果都当做是一个独立的单元,有着足够丰富的信息来进行后面的建模。
|
||||
|
||||
因此,作者们首先把查询关键词和每一个搜索结果转换成为第一个层次的嵌入向量,组成一个大的第一层次的嵌入向量。然后,作者们利用这个第一层次的嵌入向量,并且引入了循环神经网络,来对当前结果前后的结果进行了两次编码,一次正向,一次逆向,从而形成了第二层次的嵌入向量。这个第二层次的嵌入向量就是最终表征每一个搜索结果的向量。
|
||||
|
||||
对于解码器而言,作者们利用了“**关注**”(Attention)机制来对每一个搜索结果施加不同的权重,或者说是关注度。每个时间点,也就是每一次做“是否要点击”的决策之后,都会重新生成一个关注向量,或者说是一组新的关注权重。这里的核心是一个**循环神经网络**,自己更新内部的状态变量,并且根据关注向量以及输入的嵌入向量,来预测下面一个点击的位置。
|
||||
|
||||
有了编码器和解码器之后,一个难点是**如何生成最有可能的点击序列**。我们刚才提到了,整个模型其实可以预测多种不同的点击序列。因此,**生成最优可能的K个序列**就成为了必要的一个步骤。在这篇文章里,作者们利用了“**集束搜索**”(Beam Search)的方法来近似生成最佳的K个序列,在文章中,K的值是1024。
|
||||
|
||||
模型的训练采用了标准的**SGD**以及**Adam优化法**,同时作者们还采用了“**梯度裁剪**”(Gradient Clipping)的方式来防止在优化过程中发生“爆炸问题”(Gradient Clipping)。
|
||||
|
||||
## 实验结果
|
||||
|
||||
作者们在Yandex,俄罗斯的搜索引擎数据上进行了实验。因为之前没有类似的模型,因此文章并没有可以直接比较的其他模型。作者们主要进行评估的地方是,看历史数据中已经发生的点击序列,会不会被正确预测出,会不会出现在K个模型认为最有可能发生的点击序列中。这也就是作者们为什么选择K等于1024的原因,因为在这种情况下,接近97%的历史序列都在模型的预测序列中。
|
||||
|
||||
作者们还评估了模型能否预测出总的点击次数等一系列和点击预测有关的任务,论文中提出的模型都能够以接近1的概率预测所有的点击,并击败一些过去的基于概率的点击模型。可以说,提出的模型的确可以对用户在搜索页面的点击行为进行有效的建模。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了今年SIGIR 2018的一个篇精彩论文。
|
||||
|
||||
一起来回顾下要点:第一,我们详细介绍了这篇文章要解决的问题以及贡献,主要是对用户在搜索页面上的点击行为进行序列建模;第二,我们简要介绍了文章提出方法的核心内容,主要是编码器和解码器两个模块;第三,我们简单介绍了论文的实验结果。
|
||||
|
||||
最后,给你留一个思考题,如果针对多个连续的查询关键词的点击行为进行建模,你能否用这篇论文提出的思路来扩展模型呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
@ -0,0 +1,67 @@
|
||||
<audio id="audio" title="022 | CVPR 2018论文精读:如何研究计算机视觉任务之间的关系?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2c/f0/2c637f688fae676b5aaaf4138ab82af0.mp3"></audio>
|
||||
|
||||
今年6月18 日~22日,计算机视觉和模式识别大会CVPR(Conference on Computer Vision and Pattern Recognition),在美国的盐湖城举行。CVPR大会从1985年开始举办,已经有30多年的历史,是计算机视觉领域的顶级会议。
|
||||
|
||||
最近几年,CVPR大会发展成为了人工智能领域的盛会。受人工智能浪潮的影响,大会的投稿数量和参会人数都有了明显增加。大会今年共收到了3300份论文投稿,录取了979篇,录取率将近30%。最终,选出了70篇论文做口头报告,224篇论文做快速汇报。近两年的参会人数都保持着近1千人的增长势头,而今年更是达到了6千多人,是2014年参会人数的3倍多。同时,大会的审稿人也达到了惊人的1万人。
|
||||
|
||||
除了主会议以外,CVPR大会还组织了21个讲座,48个研讨班和博士论坛,有超过115家公司的赞助。
|
||||
|
||||
想要在这么多论文里找到最有价值、最有影响力的信息,可以说是大海捞针。我在这里为你精选了三篇今年CVPR的论文,希望能够起到抛砖引玉的作用。
|
||||
|
||||
今天,我们来分享大会的最佳论文,题目是——Taskonomy: Disentangling Task Transfer Learning。
|
||||
|
||||
我先来简单介绍下论文的作者群。
|
||||
|
||||
第一作者阿米尔·扎米尔(Amir R. Zamir)目前是斯坦福大学和加州大学伯克利分校的博士后研究员,已经在计算机视觉领域发表了30多篇论文,还获得过CVPR 2016的最佳学生论文奖。
|
||||
|
||||
第二作者亚历山大·萨克斯(Alexander Sax)刚刚从斯坦福大学计算机系硕士毕业,即将前往加州大学伯克利分校攻读博士,已经以硕士生的身份发表了两篇CVPR论文。
|
||||
|
||||
第三作者沈博魁刚从斯坦福大学计算机系本科毕业,即将在本校继续攻读博士。尽管是本科刚刚毕业,他已经发表了2篇CVPR论文和1篇ICCV论文。
|
||||
|
||||
第四作者利昂奈达·圭巴斯(Leonidas Guibas)是斯坦福大学计算机系教授,也是ACM和IEEE院士,还是美国工程院和科学院院士。他的博士导师是图灵奖获得者高德纳(Donald Knuth)。
|
||||
|
||||
第五作者吉腾德拉·马立克(Jitendra Malik)是加州大学伯克利分校计算机系教授,也是ACM和IEEE院士,并且是美国工程院以及科学院院士。马立克是计算机视觉方向的学术权威。
|
||||
|
||||
最后一位作者西尔维奥·萨瓦瑞斯(Silvio Savarese)是斯坦福大学计算机系的教授。他的研究方向是计算机视觉和计算机图形学。我们对华人学者李飞飞都很熟悉,萨瓦瑞斯是李飞飞的丈夫。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
概括来说,这篇论文主要是研究了计算机视觉任务之间的关系,并且提出了一个计算框架,能够定量地学习到这些任务之间的相似度。同时,这些相似的任务可以帮助数据较少的任务达到比较好的效果。这其实就是**迁移学习**(Transfer Learning)的核心思想:如何从已经学习到的任务或者领域迁移到数据较少、学习更加困难的任务或者领域。
|
||||
|
||||
很多研究人员在平时的研究过程中可能都会有这样的感觉,一些计算机视觉任务之间有某种逻辑或者直觉上的联系。例如,在计算机视觉界,像物体识别(Object Recognition)、景深估计(Depth Estimation)、边界发掘(Edge Detection)以及姿势估计(Pose Estimation)这些任务,大家都普遍认为它们是有关系的一系列任务。但是,有一些视觉任务之间的关系则显得没有那么直观,比如,边界发掘和光影(Shading)如何帮助姿势估计,就不得而知了。
|
||||
|
||||
如果我们单独来解决每一类任务,必然会有很大的挑战。这篇论文其实展示了,很多任务之间是有关联性的,而利用这些任务的关联性其实可以带来数据上的巨大便利。也就是说,我们可以利用更少的数据来学习到更多的任务。从这个角度来看,迁移学习也为新任务带来了希望,当我们没有大量的人工标注的数据时,依然能够在新任务上获得有效的结果。
|
||||
|
||||
这篇论文的另外一个重要贡献是提出了一个计算框架,这个框架并不需要事先准备的知识,比如人为地决定哪两个任务之间是有关联的,或者说,并不像之前的一些利用概率建模的方法,需要对任务之间的结构加以一个先验概率。**这篇论文提出的框架完全从数据和结果的角度出发,从而避免了这些先验信息的不完整和不准确**。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
这篇论文提出的方法由四个组成部分,分别是:**任务相关的建模、迁移建模、任务与任务关系归一化以及最后计算任务的关系图谱**。每一个组成部分都有不同的目标。
|
||||
|
||||
**首先,我们需要建立的是每一个独立任务自己的一个模型**。这些模型有两个任务:第一就是尽可能地提高对自身任务的精度;第二就是在这个过程中,尽可能提取有代表性的中间表征结果,能够有助于迁移学习。
|
||||
|
||||
**第二个部分就是迁移建模**。这个部分主要是利用第一部分学习到的中间表现层,然后再在目标任务上学习到从原本的表现层到任务目标的迁移。这里面,除了一个原表现层,或者是原任务可以借鉴以外,作者们提出还可以利用多个原任务,来达到提升效果的目的。这样也就把多个任务和一个目标任务关联了起来。
|
||||
|
||||
**第三个部分是任务关系的归一化**。这一部分其实是这篇文章的一个亮点。当我们得到迁移学习的结果以后,我们就可以利用每两个任务之间的关系来获得一个矩阵,这个矩阵就完全表征了所有任务的联系。然而,如果直接利用任务的迁移损失函数的值来刻画两个任务之间的关系,那么每两个任务之间的这个数值其实是没办法直接比较的。如果我们采用机器学习界归一化数据的办法,比如把数据归一到0和1之间,也是不行的,因为这样就完全没有考虑损失函数变化的速度和目标任务精度之间的关系。
|
||||
|
||||
所以,这篇论文的作者们提出了一种**按照次序来做归一化的方法**。简单来说,就是不再看两个任务之间的绝对的迁移数值,而是看在测试集上哪一个原任务相比于其他任务能够更多地获取目标任务的精度。这样所有的任务就可比了。总之,**任务关系归一化的目的就是构建了任务与任务之间关系的矩阵**。
|
||||
|
||||
最后一个部分的目的就是**从这个关系矩阵中提取出所有任务的一个真正的关系图谱**。也就是说,我们希望从一个完全的全连通图,找到一个最有价值的子图。在这里,作者们采用了一种叫作“**布尔值整数规划**”(Boolean Integer Programming)的方法,在一些限制条件下,挖掘出了一个有代表性的子图。
|
||||
|
||||
## 实验结果
|
||||
|
||||
作者们提出了一个有4百多万张图片的新的数据集。在这个数据集里,有26个计算机视觉任务。从实验中,作者们挖掘出了这样一些情况,例如3D的、2D的任务自然被归类到了一起,而其他的例如上下文分割、景象识别这种高层次的任务则被分在了一起。
|
||||
|
||||
为了研究这种挖掘出的结构是否真的能够实现迁移学习的目的,作者们还把不同的两两任务随机组合在一起,也就是某种随机任务的图谱,按照学习到的结构进行迁移学习,看是不是比随机结果要好。答案是,的确要好很多。在这篇论文里,作者们展示了学习到的结构不仅能够帮助目标任务提升性能,而且在任务之间关系的解释性上效果也非常不错。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了CVPR 2018的最佳论文。
|
||||
|
||||
一起来回顾下要点:第一,我们详细介绍了这篇文章要解决的问题以及贡献,论文研究了计算机视觉任务之间的关系,并且提出了一个计算框架,能够起到迁移学习的作用;第二,我们简要介绍了文章提出的核心方法,主要有四个组成部分;第三,我们简单介绍了论文的实验结果。
|
||||
|
||||
最后,给你留一个思考题,当前挖掘的关系主要是任务的两两关系,能否有一个方法挖掘任务的高维度关系,比如三个任务之间的关系?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
@ -0,0 +1,73 @@
|
||||
<audio id="audio" title="023 | CVPR 2018论文精读:如何从整体上对人体进行三维建模?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/81/f4/8183ffd2d1c3e1c4a1c07bef6b80e0f4.mp3"></audio>
|
||||
|
||||
今天我们来分享CVPR大会的最佳学生论文,标题是《全方位捕捉:用于跟踪面部表情,手势和身体运动的3D变形模型》([Total Capture: A 3D Deformation Model for Tracking Faces, Hands and Bodies](http://www.cs.cmu.edu/~hanbyulj/totalbody/totalcapture.pdf))。
|
||||
|
||||
很多学术会议都利用最佳学生论文这个奖项来鼓励学生参与学术研究活动,所以这个奖项的一般要求是第一作者必须是在校学生。
|
||||
|
||||
这篇论文的作者群来自卡内基梅隆大学。
|
||||
|
||||
第一作者周寒星(Hanbyul Joo)是来自韩国的学者,目前在卡内基梅隆大学机器人学院(The Robotics Institute)攻读博士。他的博士论文方向是“计算行为科学”(Computational Behavioral Science)。他已经在计算机视觉方向发表了多篇CVPR、ICCV论文。
|
||||
|
||||
第二作者托马斯·西蒙(Tomas Simon)也是卡内基梅隆大学机器人学院的博士生。他的研究方向是“三维运动的时空建模”(Spatiotemporal Modeling of 3D Motion)。
|
||||
|
||||
最后一位作者是这两位学生的导师亚瑟尔·舍艾克(Yaser Sheikh),是机器人学院的教授。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
这篇论文想要解决的问题很直观,那就是希望对人体进行三维建模,并且能够跟踪(Track)人体的行为以及活动。
|
||||
|
||||
这个任务看似简单,但有不少难点。
|
||||
|
||||
首先,过去绝大多数的对人体进行三维建模的工作,都是针对人体的不同部分分别进行的,比如对脸部、对身体和对手部分别建模。在对这些部位进行建模的时候,整体的设定都不太一样。例如,对脸部的建模一般是近景(Close-Up),而对身体则主要是看身体的整体行动。也就是,对于人体不同部位的建模经常在不同的尺度下进行,那就无法把各个部分的建模很容易地对接上。
|
||||
|
||||
其次,还有一些人体的部位,过去并没有太多专门的建模工作,比如针对头发和脚,但这些在全身的建模中也是必不可少的部分。
|
||||
|
||||
这篇论文就加入了对头发和脚这些部分建模的讨论,提供了对人体从整体上进行建模的一个框架。确切地说,论文提供了两个模型:一个叫“弗兰肯斯坦”(Frankenstein),一个叫“亚当”(Adam)。
|
||||
|
||||
“弗兰肯斯坦”主要还是依靠对人体不同部分的建模,然后把这些模型连接起来,通过一些处理,能够让最终呈现的三维建模符合现实。在这个模型的基础上,“亚当”则加入了头发和脚的元素,抛弃了“弗兰肯斯坦”的一些特殊处理的地方,从模型的角度来说更加简单,并且达到了更加逼真的程度。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
首先,我们来看一看这个叫“弗兰肯斯坦”的模型。这个模型的思路是尽量把能够对人体各个部分建模的最好的模型先拼接到一起。总体来说,每一个部分基本上都由三组参数组成:**运动参数**(Motion Parameters)、**形状参数**(Shape Parameters)和**全局翻译参数**(Global Translation Parameter)。
|
||||
|
||||
对于人的身体部分,作者们采用了**SMPL模型**[1]。这个模型根据**人体形状的均值**和**形状的变化量**进行线性的叠加,然后经过一个**LBS变换**来得到对身体部分的最终建模。
|
||||
|
||||
对人脸的部分,作者们采用了一个叫**FaceWarehouse的模型**[2]。这个模型是根据**人脸形状的均值**、**形状的变化量,<strong>以及**动态的变化量</strong>来进行线性的叠加。
|
||||
|
||||
对于手而言,目前并没有模型可以直接用。作者们在这篇论文中**提出了自己的模型**,总的来说就是对手的骨架和关节进行建模,然后进行类似于身体的LBS变换。同样,也对人体的脚进行了类似的建模。
|
||||
|
||||
当我们有了身体、人脸、手和脚的模型以后,下面的工作就是把这些部分衔接上。首先,作者们保留了人体模型,移除这个模型上的人脸、手和脚。然后利用人脸模型、手的模型以及脚的模型相应的全局翻译参数,使得这些部分的坐标能够拼接上。最后,作者们还应用了一个“**融合函数**”来构建出一个平滑的人体结构。
|
||||
|
||||
“弗兰肯斯坦”的模型有一个**四项的目标优化函数**。这个函数的第一项是拟合“**关键点**”(Key Points),让人体的躯干骨架能够拟合上运动轨迹。第二项是拟合“**三维点云**”(3D Point Cloud),也就是让人体大部分躯体的实体能够拟合运动轨迹。第三项就是作者们附加的一个“小技巧”(Heuristic)用来把人体的每个部分连接在一起。这一项解决的就是整个模型设计带来的问题,也就是每个部分都是单独的形状参数,而并没有完全在模型上连接起来。最后一项是**高斯先验概率**,用来帮助模型找到唯一的解。
|
||||
|
||||
在“弗兰肯斯坦”的基础上,作者们开发了“亚当”模型。为了构建“亚当”,他们捕捉了70个人体的形态数据,首先构建这些人体的“弗兰肯斯坦”模型。在这些模型基础之上,作者们加入了人体的**头发和衣服的形态**,并且重新定义了整个模型的框架。
|
||||
|
||||
和“弗兰肯斯坦”相比,“亚当”是对所有的人体部件直接进行建模。这个模型和我们前面描述的某个具体部分的模型其实很类似,也是把人体的形态均值、形态的变化值和人脸表现值进行线性叠加,然后进行LBS变换。
|
||||
|
||||
因为“亚当”在模型上进行了简化和创新,所以在目标优化函数中只有三项变量。而我们刚刚讲过的用于“弗兰肯斯坦”模型的小技巧在“亚当”中就变得不需要了。
|
||||
|
||||
## 实验结果
|
||||
|
||||
在实验中,作者们使用了140个VGA照相机对三维身体的关键点进行重建,用了480个VGA照相机,对脚进行重建,31个高清照相机用于脸部和手部关键点的重建以及三维点云的构建。
|
||||
|
||||
作者们显示了利用“弗兰肯斯坦”和“亚当”这两个模型对人体的三维运动进行建模。总体来说,这两个模型的表现非常相似。“亚当”因为有了头发和衣服的形态,在运动中显得更加真实。只是在有一些情况下,“亚当”构建的腿会显得有一些不协调的瘦,出现这种情况的原因,作者们归结于数据的缺失。
|
||||
|
||||
不过,从总体效果上来讲,这篇论文作为第一个对于人体的全身进行三维建模并动态跟踪的工作,应该算是达到了满意的结果。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了CVPR 2018的最佳学生论文。
|
||||
|
||||
一起来回顾下要点:第一,我们详细介绍了这篇文章要解决的问题,就是从整体上对人体的运动进行三维建模;第二,我们简要介绍了文章提出的两个模型,“弗兰肯斯坦”和“亚当”核心内容;第三,我们简单介绍了这篇论文所取得的不错的实验结果。
|
||||
|
||||
最后,给你留一个思考题,如果我们需要对“亚当”这个模型进行改进,你认为下一步应该做什么?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
**参考文献**
|
||||
|
||||
1. M. Loper, N. Mahmood, J. Romero, G. Pons-Moll, and M. J. Black. **SMPL: A Skinned Multi-Person Linear Model**. In TOG, 2015.
|
||||
|
||||
2. C. Cao, Y. Weng, S. Zhou, Y. Tong, and K. Zhou. **FaceWareHouse: A 3D Facial Expression Database for Visual Computing**. In TVCG, 2014.
|
||||
|
||||
|
@ -0,0 +1,67 @@
|
||||
<audio id="audio" title="024 | CVPR 2018论文精读:如何解决排序学习计算复杂度高这个问题?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/85/c7/85e857ebc2f6aa722cd5ff53fe7637c7.mp3"></audio>
|
||||
|
||||
今天,我们来看这次大会的一篇最佳论文提名,标题是《基于排序的损失函数的有效优化》(Efficient Optimization for Rank-based Loss Functions)。
|
||||
|
||||
还是先简单介绍下论文的作者群。这篇论文的作者来自好几个不同的学术机构。
|
||||
|
||||
第一作者普里迪什·莫哈帕德拉(Pritish Mohapatra)是印度海得拉巴的国际信息科技大学(International Institute of Information Technology,Hyderabad)的计算机科学博士生。他已经在NIPS、CVPR、ICCV、AISTATS等国际机器学习权威会议上发表了多篇论文。
|
||||
|
||||
第二作者米卡尔·罗莱内克(Michal Rolinek)来自德国的马克思普朗克智能系统大学(Max Planck Institute for Intelligent Systems),博士后研究员。在这篇论文中,第一作者和第二作者的贡献相当。
|
||||
|
||||
第三作者贾瓦哈(C.V. Jawahar)是来自印度国际信息科技学院的教授。他是第一作者莫哈帕德拉的博士生导师。
|
||||
|
||||
第四作者弗拉迪米尔·科莫格罗夫(Vladimir Kolmogorov)是奥地利科技大学(Institute of Science and Technology Austria)的机器学习教授。
|
||||
|
||||
最后一个作者帕万·库玛(M. Pawan Kumar)来自牛津大学。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
这篇论文提出了一个针对排序学习中**基于整个排序的损失函数**的快速优化算法,这是一个重要贡献。
|
||||
|
||||
在计算机视觉中,有很多机器学习的任务都需要针对两个图像进行一个偏好的排序。而在信息检索或者搜索中,排序是一个核心问题。因此,任何对于排序学习算法的重大改进都会有广泛的应用。
|
||||
|
||||
先来回顾下我们学过的三种形态的排序学习算法。
|
||||
|
||||
第一种是**单点法排序**。这个算法针对每一个查询关键词和相对应的某个文档,我们仅仅判断每一个文档是不是相关的。大多数的单点法排序算法都把整个问题转换成为分类或者回归问题。这样就可以利用大规模机器学习的便利来快速地学习排序函数。
|
||||
|
||||
第二种是**配对法排序**。这个算法是以单点法为基础。因为单点法完全忽略两个文档之间的相对关系。所以配对法是对两个文档与同一个查询关键词的相对相关度,或者说是相关度的差值进行建模。
|
||||
|
||||
第三种是**列表法排序**。列表法是直接针对排序的目标函数或者指标进行优化。这种方法虽然在理论上有优势,但是计算复杂度一般都比较高,在现实中对排序效果的提升比较有限,因此在实际场景中,依然有大量的应用采用单点法或者配对法排序。
|
||||
|
||||
这篇论文就是针对列表法排序学习的“**计算复杂度高**”这个问题,作者们发明了一套叫作“**基于快速排序机制**”(Quicksort flavoured algorithm)的优化框架。在这个优化框架下,排序学习计算复杂度高的这个问题得到了大幅度优化。作者们然后证明了流行的针对NDCG和MAP进行排序学习都满足所发明的优化框架,这样也就在理论上提供了快速优化的可能性。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
要理解这篇论文的核心方法,我们先从配对法排序学习讲起。
|
||||
|
||||
针对每一个查询关键词,我们可以构建一个**文档和文档的矩阵**。这个矩阵的每一个元素代表两个文档在当前查询关键词下的关系。如果这个矩阵元素是+1,那么就表明这一行所代表的文档排位要优先于这一列所代表的文档。如果这个矩阵元素是-1,那么就表明这一行所代表的文档要比这一列所代表的文档排位低。当然,还有矩阵元素是0的情况,那就是这两个文档的排位可以是一样的。在这个数据基础上,我们可以从所有这些二元关系中推导出一个整体的排序。
|
||||
|
||||
下面来看配对法排序的核心思路。对于同一个查询关键词而言,我们从和这个查询关键词相关的文档中,随机抽取一个文档,然后从和这个查询关键词不相关的文档中也抽取一个文档,这两个抽取出来的文档就组成一个配对。我们希望**建立一个模型或者函数**,对于这样任意的配对,总能够让**相关文档的函数值大于不相关文档的函数值**。
|
||||
|
||||
如果我们对这个配对法稍微做一些更改,得到的就是列表法排序。首先,我们依然针对每一个正相关的文档进行函数值预测,也针对每一个负相关的文档进行函数值预测。我们把这两个函数值的差值,当做是预测的配对矩阵中这两个文档相对应的那一个元素。只不过在这个时候,我们关注的不是这两个文档的关系,而是配对矩阵所代表的排序和真实排序之间的**差别**。这个差别越小,我们就认为最终的基于列表的损失函数就小;如果差别大,那损失函数的差别就大。
|
||||
|
||||
如何针对这个基于列表的损失函数进行优化,从而能让我们针对单一文档的函数打分最优呢?这就是列表法排序学习的一个核心困难。
|
||||
|
||||
有一个优化办法,就是找到在当前函数打分的情况下,有哪个文档配对违反了排序原则。什么是**违反排序原则**呢?我们刚才说了,模型是希望把正相关的文档排在负相关的文档前面。但是,如果函数并没有完全被学习好,那么负相关的文档也会排到正相关的文档之前,这就叫违反排序原则。
|
||||
|
||||
如果我们找到这样的配对,那么就可以通过调整函数的参数,让这样的违反配对不出现。很显然,当我们有很多这样的配对时,找到违反排序原则最严重的那个配对,也就是负相关的函数值要远远大于正相关函数值的这个配对,对于我们改进函数的参数就会很有帮助。所以,这里的关键就变成了**如何找到违反排序原则最严重的配对**(Most-violating ranking)。
|
||||
|
||||
作者们针对这个任务发明了一个框架,叫作“**基于快速排序机制**”。具体来说,作者们发现,违反排序原则最严重的配对需要满足一些原则。我们需要对当前的数据序列进行快速排序,从而能够找到这个违反排序原则的配对。这里有很多的细节,有兴趣的话建议去读读原论文。你只需要记住,这个快速排序机制利用了**快速排序的时间复杂度**,来实现寻找违反排序原则最严重配对的这个目的。
|
||||
|
||||
那么,是不是大多数排序指标都符合这个机制呢?作者们提供的答案是普遍的MAP和NDCG都符合这个机制。论文给出了证明,因此我们就可以直接使用论文的结论。
|
||||
|
||||
## 实验结果
|
||||
|
||||
作者们在PASCAL VOC 2011数据集上进行了实验,主要是比较了直接进行单点法排序以及直接进行列表法优化,和这篇论文提出的优化算法之间的性能差距。在这个比较下,本文提出的方法优势非常明显,基本上是以单点法的时间复杂度达到了列表法的性能。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了CVPR 2018的最佳论文提名。
|
||||
|
||||
一起来回顾下要点:第一,这篇文章的主要贡献是提出了一个基于整个排序的损失函数的快速优化算法;第二,文章提出方法的核心内容是发明了一个框架,叫作“基于快速排序机制”;第三,我们简单介绍了一下论文的实验结果。
|
||||
|
||||
最后,给你留一个思考题,回忆一下我们曾经讲过的LambdaMART算法,那里其实也有这么一个寻找违反排序原则配对的步骤,你能想起来是什么步骤吗?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
@ -0,0 +1,89 @@
|
||||
<audio id="audio" title="025 | ICML 2018论文精读:模型经得起对抗样本的攻击?这或许只是个错觉" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/63/4d/63c900a940750bc8d41ca27085e4494d.mp3"></audio>
|
||||
|
||||
2018年7月10日~15日,国际机器学习大会ICML 2018(The 35th International Conference on Machine Learning),在瑞典的斯德哥尔摩举行。
|
||||
|
||||
ICML从1980年开始举办,已有30多年的历史 ,是机器学习、人工智能领域的顶级会议。
|
||||
|
||||
今年ICML大会共收到了2473份投稿,投稿量和去年相比增加了45%。今年最后录取了621篇论文,录取率近25%。除了主会议以外,ICML大会还组织了9个讲座,67个研讨班。
|
||||
|
||||
在接下来的几期内容里,我会为你精选三篇ICML 2018的论文,我们一起来讨论。
|
||||
|
||||
今天,我和你分享的是大会的最佳论文,题目是《梯度混淆带来的安全错觉:绕过对对抗样本的防御》(Obfuscated Gradients Give a False Sense of Security: Circumventing Defenses to Adversarial Examples)。
|
||||
|
||||
先简单介绍下这篇论文的作者群。
|
||||
|
||||
第一作者阿尼什·阿提耶(Anish Athalye)是麻省理工大学的博士生,主要研究方向是机器学习算法的安全。他在今年的ICML大会上就发表了3篇论文。
|
||||
|
||||
第二作者尼古拉·泽多维奇(Nickolai Zeldovich)是阿提耶的导师。他是麻省理工大学计算机系的教授,做安全相关的研究。
|
||||
|
||||
第三作者大卫·瓦格纳(David Wagner)来自加州大学伯克利分校,是计算机系教授,也是安全研究方面的专家。
|
||||
|
||||
## 论文的背景
|
||||
|
||||
这篇论文的内容对于大对数人来说可能是比较陌生的。想要弄清楚这篇论文的主要贡献,我们首先来熟悉一下这篇论文所要解决的问题。
|
||||
|
||||
试想我们比较熟悉的监督学习任务。一般来说,在监督学习任务中,我们会有一个数据集,用各种特性(Feature)来表征这个数据集里的数据点。拿最普通的监督学习来说,比如需要把图像分类为“猫”、“狗”等,机器学习算法就是学习一个分类器,可以根据不同的输入信息来做分类的决策。
|
||||
|
||||
当然,我们所说的是在正常情况下使用分类器的场景。有一类**特别的应用场景**,或者说是“对抗”场景,其实是希望**利用一切方法来破坏或者绕开分类器的决策结果**。
|
||||
|
||||
一个大类的“**对抗机制**”是尝试使用“**对抗样本**”(Adversarial Examples)。什么是对抗样本呢?就是说一个数据样本和原来正常的某个样本是非常类似的,但是可以导致分类决策出现很大不同。例如在我们刚才的图像识别的例子中,一个有效的对抗样本就是一张非常像狗的图片,但是可以导致分类器认为这是一只猫或者别的动物。利用这种类似的样本,可以使分类器的训练和测试都产生偏差,从而达到攻击分类器的目的。
|
||||
|
||||
除了“对抗样本”的概念以外,我们再来看一看**攻击分类器**的一些基本的模式。
|
||||
|
||||
一般来说,对分类器的攻击有两种模式,一种叫作“**白盒攻击**”(White-Box),一种叫作“**黑盒攻击**”(Black-Box)。白盒攻击主要是指攻击者可以完全接触到分类器的所有内部细节,比如深度模型的架构和各种权重,但无法接触到测试数据。而黑盒攻击则是指攻击者无法接触分类器的细节。
|
||||
|
||||
这篇论文考虑的场景是白盒攻击。攻击方尝试针对每一个合法的数据点,去寻找一个距离最近的数据变形,使得分类器的结果发生变化。通俗地说,就是希望对数据进行最小的改变,从而让分类器的准确率下降。
|
||||
|
||||
在完全白盒的场景下,最近也有一系列的工作,希望让神经网络更加健壮,从而能够抵御对抗样本的攻击。但是到目前为止,学术界还并没有完全的答案。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
通过上面的介绍,我们知道目前有一些防御对抗样本的方法,似乎为分类器提供了一些健壮性的保护。这篇文章的一个重要贡献,就是指出,**这些防御方法有可能只是带来了一种由“梯度混淆”(Obfuscated Gradients)所导致的错觉**。
|
||||
|
||||
梯度混淆是“梯度屏蔽”(Gradient Masking)的一种特殊形式。对于迭代攻击方法来说,如果发生梯度混淆,防御方会形成防御成功的假象。
|
||||
|
||||
作者们在这篇论文中对梯度混淆进行了分析,提出了三种类型的梯度混淆:“**扩散梯度**”(Shattered Gradients)、“**随机梯度**”(Stochastic Gradients)和“**消失梯度或者爆炸梯度**”(Vanishing/Exploding gradients)。
|
||||
|
||||
针对这三种不同的梯度混淆,作者们提出了相应的一些攻击方案,使得攻击方可以绕过梯度混淆来达到攻击的目的,并且在ICLR 2018的数据集上展示了很好的效果。
|
||||
|
||||
值得注意的是,这篇论文针对的是在防御过程中“**防御方**”的方法所导致的梯度混淆的问题。目前学术界还有相应的工作是从攻击方的角度出发,试图学习打破梯度下降,例如让梯度指向错误的方向。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
我们首先来看一看这三种类型的梯度混淆。
|
||||
|
||||
**扩散梯度主要是指防御方发生了“不可微分”(Non-Differentiable)的情况**。不可微分的后果是直接导致数值不稳定或者梯度不存在。扩散梯度其实并不意味着防御方有意识地希望这么做,这很有可能是因为防御方引入了一些看似可以微分但是并没有优化目标函数的情况。
|
||||
|
||||
**随机梯度主要是由随机防御(Randomized Defense)引起的**。这有可能是神经网络本身被随机化了,或者是输入的数据被随机化,造成了梯度随机化。
|
||||
|
||||
**消失梯度和爆炸梯度主要是通过神经网络的多次迭代估值(Evaluation)所导致**。例如,让一次迭代的结果直接进入下一次迭代的输入。
|
||||
|
||||
刚才我们说了,梯度混淆可能是防御方无意识所产生的结果,并不是设计为之。那么,攻击方有什么方法来识别防御方是真的产生了有效果的防御,还是仅仅发生了梯度混淆的情况呢?
|
||||
|
||||
作者们做了一个总结,如果出现了以下这些场景,可能就意味着出现了梯度混淆的情况。
|
||||
|
||||
第一种情况,**一步攻击的效果比迭代攻击(也就是攻击多次)好**。在白盒攻击的情况下,迭代攻击是一定好于一步攻击的。因此如果出现了这种一步攻击好于迭代攻击的情况,往往就意味着异常。
|
||||
|
||||
第二种情况,**黑盒攻击的效果比白盒好**。理论上,白盒攻击的效果应该比黑盒好。出现相反的情况,往往意味着不正常。
|
||||
|
||||
第三种情况,**无局限(Unbounded Attack)效果没有达到100%**。最后的这种情况,就是随机寻找对抗样本,发现了比基于梯度下降的攻击要好的对抗样本。
|
||||
|
||||
那么,针对梯度混淆,攻击方有什么办法呢?
|
||||
|
||||
针对扩散梯度,作者们提出了一种叫**BPDA**(Backward Pass Differentiable Approximation)的方法。如果有兴趣,建议你阅读论文来了解这种算法的细节。总体说来,BPDA就是希望找到神经网络不可微分的地方,利用简单的可微分的函数对其前后进行逼近,从而达到绕过阻碍的目的。
|
||||
|
||||
针对随机梯度,作者们提出了“**变换之上的期望**”(Expectation over Transformation)这一方法。这个方法的特点是针对随机变化,变换的期望应该还是能够反映真实的梯度信息。于是作者们就让攻击方作用于变换的期望值,从而能够对梯度进行有效的估计。
|
||||
|
||||
针对消失或者爆炸的梯度,作者们提出了“**重新参数化**”(Reparameterization)这一技术。重新参数化是深度学习中重要的技术。在这里,作者们使用重新参数化,其实就是对变量进行变换,从而使得新的变量不发生梯度消失或者爆炸的情况。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了今年ICML的最佳论文。
|
||||
|
||||
一起来回顾下要点:第一,这篇论文讨论了一个比较陌生的主题,我们简要介绍了论文的背景;第二,我们详细介绍了论文提出的三种类型的梯度混淆。
|
||||
|
||||
最后,给你留一个思考题,我们为什么要研究深度学习模型是否健壮,是否能够经得起攻击呢?有什么现实意义吗?
|
||||
|
||||
欢迎你给我留言,我们一起讨论。
|
||||
|
||||
|
@ -0,0 +1,57 @@
|
||||
<audio id="audio" title="026 | ICML 2018论文精读:聊一聊机器学习算法的“公平性”问题" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a6/9d/a6e0fbd9a97a51e84f9e9f61a9fafa9d.mp3"></audio>
|
||||
|
||||
在上一次的分享里,我们介绍了今年ICML大会的一篇最佳论文,这是一篇非常优秀的机器学习和计算机安全相结合的论文。这篇论文剖析了目前在白盒攻击的场景下,攻击方如何绕过一种叫作“混淆梯度”的情况,来实现有效攻击的目的。
|
||||
|
||||
今天,我们来分享ICML 2018的另一篇最佳论文,题目Delayed Impact of Fair Machine Learning。这篇论文主要探讨了“公平”(Fair)在机器学习中的应用。论文的五位作者都来自加州大学伯克利分校。
|
||||
|
||||
## 论文的背景
|
||||
|
||||
这篇论文所探讨的主题是机器学习的“公平性”问题。近些年,这个主题受到了学术界越来越多的关注,但是对于普通的人工智能工程师和数据科学家来说,这个议题依然显得比较陌生和遥远。所以,我先来简单梳理一下这方面研究的核心思想。
|
||||
|
||||
机器学习有一个重要的应用,就是在各类**决策场景**中提供帮助,例如申请贷款、大学入学、警察执勤等。一个不可否认的特点是,这些决策很有可能会对社会或者个人产生重大的不可逆转的后果。其中一个重要的后果就是,针对不同的人群,有可能会产生意想不到的“不公平”的境况。比如,有一些普遍使用的算法,在帮助警察判断一个人是否可能是罪犯的时候,系统会认为美国黑人相对于白人更容易犯罪,这个判断显然存在一定的问题。
|
||||
|
||||
机器学习研究者已经注意到了这种算法中的“公平”问题,并且开始探讨没有任何限制条件的机器学习算法,是否会对少数族裔(Underrepresented Group)产生不公平的决策判断。基于这些探索,研究者们提出了一系列的算法,对现有的各种机器学习模型增加附带了公平相关的限制条件,希望通过这种方法来解决各种不公平定义下的决策问题。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
这篇论文从理论角度展开讨论,基于什么样假设和条件下的具有公平性质的机器学习算法,在决策场景中能够真正为少数族群带来长期的福祉。值得注意的是,这里所谓的少数族裔是一个抽象化的概念,指的是数目相对较少的,或者在某种特性下比较少的一组数据群体。这篇论文并不直接讨论社会学意义下的少数族群的定义。
|
||||
|
||||
作者们主要是比较两个人群A和B,在不同的公平条件下,看这两组人群的某种“**效用**”(Utility)的差值会发生什么变化。这个差值可以是正的,没变化或者是负的。
|
||||
|
||||
论文的主要结论是,在不同的公平条件下,效用差值会有各种可能性。这其实是一个非常重要的结论。有一些公平条件,直觉上我们感觉会促进少数族群的效用,但这篇论文向我们展示了,即便出发点是好的,在某些情况下,效用差值也可能是负的。
|
||||
|
||||
除此以外,这篇论文还探讨了“**测量误差**”(Measurement Error)对效用差值的影响。作者们认为测量误差也应该被纳入整个体系中去思考公平的问题。
|
||||
|
||||
需要指出的是,论文的分析方法主要建立在时序关系的“**一步预测**”(One Time Epoch)基础上的。也就是说,我们利用当前的数据和模型对**下一步的决策判断**进行分析,并不包括对未来时间段所有的预测。从理论上说,如果在无限未来时间段的情况下,结论有可能发生变化。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
这篇文章的核心思路是探讨针对人群A和B所采取的一种“策略”(Policy),是怎么样影响这两组人群的效用差别的。如果某种策略会导致某个群体的**效用差别为负**,那么我们就说这个策略对群体产生了“**绝对损坏**”(Active Harm)作用;如果**效用差别是零**,就说明这个策略对群体产生了“**停滞**”(Stagnation)作用;如果**效用差别是正的**,就说明这个策略对群体产生了“**推动**”(Improvement)作用。
|
||||
|
||||
除此以外,我们认为有一种不考虑人群A和B具体特征的期望最大化效用的策略,称之为“**最大化效用**”(MaxUtil)。这种策略其实就是在没有约束条件的情况下,利用一般的机器学习算法达到的效果。我们需要把新策略和这个策略进行比较,如果新的策略比这个策略好,就是产生了“**相对推动**”(Relative Improvement),反之我们说新的策略产生了“**相对损害**”(Relative Harm)。
|
||||
|
||||
为了进一步进行分析,作者们引入了一个叫“**结果曲线**”(Outcome Curve)的工具来视觉化策略和效用差值的关系。具体来说,曲线的横轴就是因为策略所导致的对某一个群体的选择概率,纵轴就是效用差值。当我们有了这个曲线之后,就能非常直观地看到效用差值的变化。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/98/ab/98a16f6db74ef6470792ef6cd618c6ab.png" alt="">
|
||||
|
||||
从这个曲线上我们可以看到,效用差值的确在一个区间内是“相对推动”的,而在另一个区间是“相对损害”的,在最右边的一个区间里是“绝对损害”的。这就打破了我们之前的看法,认为有一些选择策略会一致性地导致唯一结果。
|
||||
|
||||
在此基础上,我们专门来看这两种特殊的策略。第一种叫“**种族公平**”(Demographic Parity),思路是希望在两个人群中保持一样的选择概率。另一种策略叫“**公平机会**”(Equal Opportunity),思路是希望某个人群中成功的概率(例如申请到贷款、学校录取等)和人群无关。这两种策略都是典型的试图利用限制条件来达到公平的方法。我们希望来比较的就是这两种策略以及之前说的最大化效用之间的一些关系,得出以下三个主要结论。
|
||||
|
||||
第一个比较出乎意料的结论是最大化效用这个策略并不会导致“绝对损害”。意思就是说,和人们之前的一些想法不同,最大化效用也有可能让少数族裔的效用得到提升或者不变。
|
||||
|
||||
第二个结论是,这两种公平策略都可能会造成“相对推动”。这也是推出这两种策略的初衷,希望能够在选择概率上进行调整,从而让少数族裔的效用得到提升。
|
||||
|
||||
第三个结论是,这两种公平策略都可能会造成“相对损害”。这是本篇论文的一个重要结论,正式地证明了公平策略在某个区间上其实并没有带来正向的“推动”反而是“损害”了少数族群。作者们进一步比较了“种族公平”和“公平机会”这两个策略,发现“公平机会”可以避免“绝对损害”而“种族公平”则无法做到。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了今年ICML的另一篇最佳论文。
|
||||
|
||||
一起来回顾下要点:第一,这篇论文讨论了计算机算法的公平性问题;第二,我们详细介绍了论文提出的两种策略以及得出的主要结论。
|
||||
|
||||
最后,给你留一个思考题,研究算法的公平性对我们日常的应用型工作有什么启发作用?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
@ -0,0 +1,55 @@
|
||||
<audio id="audio" title="027 | ICML 2018论文精读:优化目标函数的时候,有可能放大了“不公平”?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/8b/35/8b02b3d8ab4296c81a56120e2a0c8f35.mp3"></audio>
|
||||
|
||||
今天我们要分享的是ICML 2018的一篇最佳论文提名,题目是Fairness Without Demographics in Repeated Loss Minimization。
|
||||
|
||||
这篇论文讨论了这样一个话题,在优化目标函数的时候,如何能够做到针对不同的子群体,准确率是相当的,从而避免优化的过程中过分重视多数群体。这篇论文的作者都来自斯坦福大学。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
这篇论文其实也是希望讨论算法带来的“公平性”问题,但是出发的角度和我们上一篇讨论公平性的论文非常不一样。这篇论文的核心思想,是希望通过机器学习目标函数优化的原理,来讨论机器学习和公平性的关系。
|
||||
|
||||
作者们发现,基于“平均损失”(Average Loss)优化的机器学习算法,常常会给某一些少数群体带来巨大的不准确性。这其实并不是模型本身的问题,而是优化的目标函数的问题。在这样的情况下,目标函数主要是关注有较多数据的群体,保证这些群体的损失最小化,而可能忽略了在数量上不占优势的少数群体。
|
||||
|
||||
在此基础上,还带来了另外一个用户“**留存度**”(Retention)的问题。因为少数群体忍受了比较大的优化损失,因此这些群体有可能离开或者被这个系统剔除。所以,长期下去,少数群体的数目就可能逐渐变少。这也许是目标函数的设计者们无从想到的一个平均损失函数的副产品。作者们还把这个现象命名为“**不公平的放大**”(Disparity Amplification)。
|
||||
|
||||
这篇论文的一个重要贡献是发现**ERM(Empirical Risk Minimization,经验风险最小化)其实就存在这种不公平的放大性**。ERM包含了很多经典的机器学习模型的目标函数,例如支持向量机(Support Vector Machines)、对数回归模型(Logistic Regression)以及线性回归等。作者们还发现,ERM可以让即便在最初看上去公平的模型,在迭代的过程中逐渐倾向于不公平。
|
||||
|
||||
为了解决ERM的问题,作者们开发了一种新的算法框架,DRO(Distributionally Robust Optimization,分布式健壮优化)。这种框架是为了最小化“最差场景”(Worst-Case)的风险,而不是平均风险。作者们在真实的数据中展示了DRO相比于ERM更能够解决小众群体的不公平性问题。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
为了解决在ERM下的对不同群体的不公平性问题,作者们首先对数据做了一种**新的假设**。
|
||||
|
||||
作者们假设数据中有隐含的K个群体。每一个数据点,都有一定的概率属于这K个群体。我们当然并不知道这K个群体本身的数据分布,也不知道每个数据点对于这K个群体的归属概率,这些都是我们的模型需要去估计的隐含变量。
|
||||
|
||||
对于每一个数据点而言,在当前模型下,我们都可以估计一个“**期望损失**”(Expected Loss)。在新的假设框架下,因为每个数据点可能属于不同的K个群体,而每个群体有不同的数据分布,因此会导致在当前群体下的期望损失不一样,也就是会出现K个不一样的期望损失。我们的目的,是要控制这K个损失中的**最差的损失**,或者叫**最差场景**。如果我们可以让最差的损失都要小于某一个值,那么平均值肯定就要好于这种情况。这也就从直观上解决了不公平放大的问题。
|
||||
|
||||
那么,如果我们直接在这样的设置上运用ERM,会有什么效果呢?这里,有一个数值是我们比较关注的,那就是在整个框架假设下,每个群体的**期望人数**。这个数值等于在期望损失的情况下,当前群体剩余的人数加上新加入的人数。作者们在论文中建立了对这个期望人数的理论界定。
|
||||
|
||||
这个结论的直观解释是,如果在当前更新的过程中,期望人数的数值估计能够达到一个稳定的数值状态,那么就有可能稳定到这里,不公平放大的情况就不会发生;而如果没有达到这个稳定的数值状态,那么不公平放大的情况就一定会发生。也就是说,在ERM优化的情况下,群体的大小有可能会发生改变,从而导致人群的流失。
|
||||
|
||||
在这个理论结果的基础上,作者们提出了DRO。DRO的核心想法就是**要改变在优化过程中,可能因为数据分配不均衡,而没有对当前小群体进行足够的采样**。
|
||||
|
||||
具体来说,**DRO对当前群体中损失高的人群以更高的权重,也就是说更加重视当前目标函数表现不佳的区域**。对于每一个数据点而言,损失高的群体所对应的群体概率会被放大,从而强调这个群体当前的损失状态。换句话说,DRO优先考虑那些在当前情况下损失比较大的小群体。这样的设置就能够**实现对最差情况的优化从而避免不公平放大**。
|
||||
|
||||
作者们在文章中展示了DRO所对应的目标函数可以在递归下降的框架下进行优化,也就是说任何当前利用ERM的算法,都有机会更改为DRO的优化流程,从而避免不公平放大的情况。
|
||||
|
||||
## 论文的实验结果
|
||||
|
||||
作者们在一个模拟的和一个真实的数据集上进行了实验。我们这里简单讲一讲真实数据的实验情况。
|
||||
|
||||
作者们研究了一个“自动完成”(Auto Completion)的任务。这个任务是给定当前的词,来预测下一个词出现的可能性。而数据则来自两个不同人群,美国白人和黑人所产生的推特信息。在这个实验中,作者们就是想模拟这两个人群的留存度和模型损失。这里面的隐含假设是,美国白人和黑人的英语词汇和表达方式是不太一样的。如果把两个人群混合在一起进行优化,很有可能无法照顾到黑人的用户体验从而留不住黑人用户。
|
||||
|
||||
在实验之后,DRO相比于ERM更能让黑人用户满意,并且黑人用户的留存度也相对比较高。从这个实验中,DRO得到了验证,的确能够起到照顾少数人群的作用。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了今年ICML的最佳论文提名。
|
||||
|
||||
一起来回顾下要点:第一,这篇论文也讨论了算法带来的“公平性”问题,是从机器学习目标函数优化的角度来考虑这个问题的;第二,这篇论文的一个重要贡献是发现ERM确实存在不公平的放大性,基于此,作者们开发了一种新的算法框架DRO;第三,文章的实验结果验证了DRO的思路,确实能够解决小众群体的不公平性问题。
|
||||
|
||||
最后,给你留一个思考题,这两期内容我们从不同的角度讨论了算法的公平性问题,你是否有自己的角度来思考这个问题?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
@ -0,0 +1,73 @@
|
||||
<audio id="audio" title="028 | ACL 2018论文精读:问答系统场景下,如何提出好问题?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/35/18/354c07ed4fee23e07ec7d0a0a9ebca18.mp3"></audio>
|
||||
|
||||
今年7月15日~20日,计算语言学协会年会ACL 2018(56th Annual Meeting of the Association for Computational Linguistics),在澳大利亚的墨尔本举行,这是自然语言处理和计算语言学领域的顶级会议。
|
||||
|
||||
计算语言学协会(ACL)最早成立于1962年,每年都赞助举行各种学术交流和研讨大会。ACL大会是ACL的旗舰会议,可以说这个会议是了解自然语言处理每年发展情况的重量级场所。
|
||||
|
||||
会议今年收到了1018篇长论文和526篇短论文的投稿。最终,大会接收了256篇长论文以及125篇短论文,综合录用率达到24.7%。
|
||||
|
||||
今天,我们来看这次会议的一篇最佳论文,题目是《学习提出好问题:使用完美信息的神经期望价值对澄清问题进行排序》([Learning to Ask Good Questions: Ranking Clarification Questions using Neural Expected Value of Perfect Information](http://aclweb.org/anthology/P18-1255))。
|
||||
|
||||
首先给你简单介绍下论文的作者。
|
||||
|
||||
第一作者萨德哈·饶(Sudha Rao)来自马里兰大学学院市分校(University of Maryland, College Park),是计算机系的博士生。她已经在ACL,EMNLP、NAACL等自然语言处理大会上发表了多篇论文,并且在微软研究院实习过。
|
||||
|
||||
第二作者是饶的导师哈尔·道姆三世(Hal Daume III),是马里兰大学学院市分校计算机系的一名教授,目前也在纽约的微软研究院工作。他是机器学习和自然语言处理领域的专家,在诸多领域都发表过不少研究成果,论文引用数达到9千多次。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
这篇论文关注的是“**问答系统**”(Question & Answering)。问答系统不仅在实用领域受到大量用户的青睐,产生了诸如Quora、知乎、Stack Overflow等知名的在线问答服务,也在人工智能系统开发领域受到研究者的关注。
|
||||
|
||||
我们曾经提到过“图灵测试”,用来衡量一个系统或者说是一个机器人是否具有真正的人工智能,这个测试其实就是建立在人机问答的交互场景下的。因此,建立有效的问答系统一直是人工智能研究,特别是自然语言处理研究的核心课题之一。
|
||||
|
||||
这篇论文的作者们认为,在问答系统的场景中,一个非常重要的手段是针对已经提出的问题进行“**澄清式**”(Clarification)提问,从而能够引导其他回答者更加有效地进行回答。也就是说,作者们研究的主题是,**如何找到这些具有桥梁作用的“澄清式问题”**,这是这篇论文的第一个重要贡献。
|
||||
|
||||
论文的第二个主要贡献是利用了“决策论”(Decision Theoretic)框架下的**EVPI**(Expected Value of Perfect Information,完美信息的期望价值),来衡量一个澄清式问题会对原始的问题增加多少有用的信息。简而言之,**EVPI就是这篇论文提出来的一个衡量有用信息的测度**(Measure)。
|
||||
|
||||
论文的第三个贡献是通过Stack Exchange平台(Stack Overflow是其一个子站点),构造了一个7万7千条含有澄清式问题的数据集。作者们从这个数据集中选取了500个样本进行了实验,并且发现,提出的模型要明显好于一些之前在问题系统中的类似算法。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
既然这篇论文的一个核心贡献是提出了“澄清式提问”这么一个新的概念,用于帮助问答系统的开发。那么,**究竟什么是“澄清式提问”呢**?
|
||||
|
||||
实际上在这篇文章里,作者们并没有对“澄清式提问”给出一个清晰的定义,而是仅仅提供了一个实例来解释什么是“澄清式提问”。
|
||||
|
||||
例如,一个用户在Ask Ubuntu这个子论坛里,询问在安装APE程序包时遇到的问题。这个时候,如果我们需要问“澄清式问题”,究竟什么样的问题可以激发其他人或者提出澄清式问题的人来进一步解答原始的问题呢?
|
||||
|
||||
我们看下面几个从不同角度提出的问题:可以问这个用户使用的Ubuntu系统具体的版本号;也可以问用户的WiFi网卡信息,还可以问用户是不是在X86体系下运行Ubuntu。
|
||||
|
||||
那么,在这一个场景下,后两个问题要么无法为原始的问题提供更多有价值的信息,要么就是彻底的不相关,而第一个问题关于具体的版本号,很明显是用户可以提供的,并且可以帮助回答问题的人来缩小问题的范围。
|
||||
|
||||
这也带出了这篇论文的第二个贡献点,**如何衡量一个帖子的价值呢**?
|
||||
|
||||
要回答这个问题,我们需要知道这里有两种帖子是模型需要处理的。第一种帖子集合是候选的澄清式问题集合。第二种帖子集合是候选的最终回答集合。我们最终的目的是得到最佳的最终回答。这里面起到“搭桥”作用的就是澄清式问题。
|
||||
|
||||
所以,作者们就构造了一个针对每一个最终问题的EVPI值,用于衡量这个问题的“期望价值”。为什么是期望价值呢?因为这里面有一个不确定的因素,那就是根据不同的澄清式问题,可能会产生不同的回答。因此,作者们在这里使用了概率化的表达。
|
||||
|
||||
也就是说,EVPI的核心其实就是计算给定当前的原始问题以及某一个澄清式回答的情况下,某一个最终回答的概率,乘以这个回答所带来的“收益”。当我们针对候选最终回答集合中所有的回答都进行了计算以后,然后求平均,就得到了我们针对某一个澄清式回答的EVPI。换句话说,**某一个澄清式回答的EVPI就是其所能产生的所有可能的最终回答的加权平均收益**。
|
||||
|
||||
从上面这个定义中,我们有两点不确定。第一,我们并不知道给定当前的原始问题以及某一个澄清式回答的情况下,某一个最终回答的条件概率;第二,我们并不知道问题的收益。因此,作者们利用了两个**神经网络模型**来对这两个未知量进行**联合学习**(Joint Learning)。这可以算是本文在建模上的一个创新之处。
|
||||
|
||||
具体来说,首先,作者们利用**LSTM**来针对原始问题、候选澄清问题、以及最后解答产生相应的**表达向量**。然后,原始问题和某一个候选澄清问题的表达向量,通过一个神经网络产生一个**综合的表达**。最后,作者们定义了一个**目标函数**来针对这些初始的表达向量进行优化。
|
||||
|
||||
这个目标是需要我们学习到的答案的表达靠近初始得到的答案的表达,同时,也要靠近最终答案的表达,如果这个最终答案所对应的问题也靠近原来的问题。换句话说,**如果两个问题的表达相近,答案的表达也需要相近**。
|
||||
|
||||
那什么样的问题是相近的问题呢?作者们利用了Lucene这个信息检索工具,根据一个原始的问题寻找相近的问题。这里,作者们并没有真实的标签信息,所以利用了一些方法来标注数据,从而能够让模型知道两个问题是否相关。
|
||||
|
||||
## 论文的实验结果
|
||||
|
||||
作者们利用了Stack Exchange来构建一个分析澄清式问题的数据集。具体的思路是,如果原始问题曾经被作者修改过,那么后面的某一个帖子中所提出的问题就会被当作是澄清式问题,而原始问题就被当作是因为澄清式问题而得以改进的帖子。很明显,这是一个非常粗略的数据收集条件。当原始问题被作者修改过以后,并且最后因为这个修改得到回复,就被认为是一个最终的答案。经过这么一番构建,作者们整理了7万7千多条帖子。
|
||||
|
||||
作者们利用论文提出的方式和其他的经典模型进行比较。最后的结论是,提出的模型能够更好地找到最佳的澄清式问题,效果要好于仅仅是简单利用神经网络,来匹配原始问题和相应的澄清式问题。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了ACL 2018的一篇最佳论文。
|
||||
|
||||
一起来回顾下要点:第一,这篇论文提出了“澄清式提问”这个概念,来帮助问答系统的开发;第二,文章提出了一系列方法,对澄清式问题进行描述和衡量;第三,文章构建了一个数据集,通过实验论证了所提出方法的有效性。
|
||||
|
||||
最后,给你留一个思考题,通过这篇文章关于澄清式问题的介绍,你能否给澄清式问题下一个定义呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
@ -0,0 +1,72 @@
|
||||
<audio id="audio" title="029 | ACL 2018论文精读:什么是对话中的前提触发?如何检测?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/75/e9/7580bed8b6466802745c89444609d2e9.mp3"></audio>
|
||||
|
||||
今天,我来和你分享ACL 2018的第二篇最佳论文,题目是《让我们“再”次做到:检测副词前提触发词的第一种计算方法》([Let’s do it “again”: A First Computational Approach to Detecting Adverbial Presupposition Triggers](https://www.cs.mcgill.ca/~jkabba/acl2018paper.pdf))。
|
||||
|
||||
这篇论文的作者都来自加拿大麦吉尔大学(McGill University)的计算机系。前三位学生作者是这篇论文的共同第一作者,对论文的贡献相同。他们的导师张智杰(Jackie Chi Kit Cheung)助理教授是这篇论文的最后一个作者。张智杰于2014年从多伦多大学博士毕业,之前曾两次在微软研究院实习过,他长期从事自然语言处理的研究。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
这篇论文的背景要从“语用学”(Pragmatics)说起。语用学是语言学的一个分支学科,与符号学理论相互交叉、渗透,研究语境对语言含义产生的影响和贡献。语用学包括言语行为理论、对话内涵义、交流中的对话,以及从哲学、社会学、语言学以及人类学等角度解析人类语言行为的研究。
|
||||
|
||||
语用学分析研究语言行为(如招呼、回答、劝说)的文化准绳和发言规则。不同的文化之间皆有约定俗成、客套的对话,在跨文化交流中,为了避免因为语言规范的差异而在交谈之中产生误解,社会语言学的知识与务实能力是语言学习者所不能忽视的。
|
||||
|
||||
在语用学中,“前提”(Presuppositions)是交谈的参与者共同约定的假设和认知,而且在谈话中被广泛使用。同时,在这篇论文中,作者们把提示“前提”的“表达”(Expression)定义为“**前提触发**”(Presupposition Triggers),包括一些动词、副词和其他短语。为了更加清晰地说明这些概念,作者们举了这么一个例子。
|
||||
|
||||
假设我们现在有两句话:
|
||||
|
||||
<li>
|
||||
约翰再次要去那家餐厅(John is going to the restaurant **again**)。
|
||||
</li>
|
||||
<li>
|
||||
约翰已经去过了那家餐厅(John has been to the restaurant)。
|
||||
</li>
|
||||
|
||||
第一句话要能够成立必须要建立在第二句话的基础上。特别是“前提触发”词“再”(Again)的使用,是建立在第二句话真实的情况下。换句话说,第一句话必须在第二句话的上下文中才能够被理解。值得一提的是,即便我们对第一句话进行否定,“约翰不打算再去那家餐厅了”(John is not going to the restaurant again),依然需要第二句话的支持。也就是说,“前提触发”词在这里并不受到否定的影响。
|
||||
|
||||
**这篇论文的核心贡献就是对以副词为主的前提触发词进行检测**。这里面包括“再”(Again)、“也”(Also)和“还”(Still)等。再此之前,还没有对这方面词汇进行检测的学术研究工作。能够对这类前提触发词进行检测,可以应用到**文本的归纳总结**(Summarization)和**对话系统**等场景中。
|
||||
|
||||
为了更好地研究这个任务,作者们还基于著名的自然语言处理数据Penn Treebank和English Gigaword,建立了两个新的数据集从而能够进行触发词的分类检测工作。最后,作者们设计了一个基于“关注”(Attention)机制的时间递归神经网络(RNN)模型来针对前提触发词进行检测,达到了很好的效果。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
现在,我们来讨论这篇论文的一些细节。
|
||||
|
||||
首先,我们来看看**数据集是如何生成的**。数据中的每一个数据点都是一个**三元组**,分别是标签信息(正例还是负例),文本的单词,文本单词所对应的“词类标签”或简称为POS标签(例如动词、名词)。
|
||||
|
||||
数据点正例就表明当前数据包含前提触发词,反之则是负例。另外,因为我们需要检测的是副词性的前提触发词,因此我们还需要知道这个词所依靠的动词。作者们把这个词叫作副词的“**管理词**”(Governor)。
|
||||
|
||||
作者们首先针对文档扫描,看是否含有前提触发词。当发现有前提触发词的时候,提取这个触发词的管理词,然后提取管理词前50个单词,以及管理词后面到句子结束的所有的单词。这就组成了正例中的单词。当找到了所有的正例之后,作者们利用管理词来构建负例。也就是说,在文本中寻找哪些句子含有一样的管理词,但并不包括后面的前提触发词,这样的句子就是负例。
|
||||
|
||||
下面,我们来看一下作者们提出模型的一些构成。从大的角度来说,为了识别前提触发词,作者们考虑了一个**双向LSTM**的基本模型架构,在此之上有一个“关注机制”,在不同的情况下来选择LSTM的中间状态。
|
||||
|
||||
具体来说,整个模型的输入有两部分内容。
|
||||
|
||||
第一部分,是**文本的单词进行了词向量(Embedding)的转换**。我们已经反复看到了,这是在自然语言处理场景中利用深度学习模型必不可少的步骤。这样做的好处就是把离散数据转换成了连续的向量数据。
|
||||
|
||||
第二部分,是**输入这些单词相对应的POS标签**。和单词不一样的是,POS标签依然采用了离散的特性表达。
|
||||
|
||||
然后,连续的词向量和离散POS标签表达合并在一起,成了双向LSTM的输入。这里,利用双向LSTM的目的是让模型针对输入信息的顺序进行建模。跟我们刚才提到的例子一样,前提触发词和其所依靠的动词,在一个句子的段落中很明显是和前后的其他单词有关联的。因此,双向LSTM就能够达到对这个结构进行记忆的目的,并且提取出有用的中间变量信息。
|
||||
|
||||
下面需要做的就是**从中间变量信息到最终的分类结果的变换**。这里,作者们提出了一个叫“**加权池化网络**”(Weighted Pooling Network)的概念,并且和“关注”机制一起来进行这一步的中间转换。
|
||||
|
||||
可以说,作者们这一步其实是借助了计算机视觉中的经常使用的卷积神经网络CNN中的池化操作来对文档进行处理。具体来说,作者们把所有LSTM产生的中间状态堆积成一个矩阵,然后利用同一个矩阵乘以其自身的转置就得到了一个类似于相关矩阵的新矩阵。可以说,这个新矩阵是完全抓住了当前句子通过LSTM中间变量转换后所有中间状态的两两关系。
|
||||
|
||||
然后,作者们认为最后的分类结构就是从这个矩阵中抽取信息而得到的。至于怎么抽取,那就需要不同的权重。这种根据不同的情况来设置权重的机制就叫作“关注”机制。经过矩阵中信息的抽取,然后再经过全联通层,最终就形成了标准的分类输出。
|
||||
|
||||
## 论文的实验结果
|
||||
|
||||
作者们在我们上面提到的两个新数据集上进行了实验,并且和一系列的方法进行了比较。其他的方法包括简单的对数几率回归方法(Logistic Regression),简化了的但是依然利用了双向LSTM结构的模型,还有一个利用CNN来进行提取信息的模型。
|
||||
|
||||
在两个数据集上,论文提出的方法比对数几率回归以及CNN的方法都要好10%~20%左右。和简化的LSTM模型相比,优势并没有那么大,但依然有统计意义上的好效果。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了ACL 2018的另外一篇最佳论文。
|
||||
|
||||
一起来回顾下要点:第一,这篇论文的背景是语用学,核心贡献是对以副词为主的前提触发词进行检测;第二,论文的核心方法是提出一个双向LSTM的基本模型架构,并利用“关注机制”,根据不同的情况来设置权重;第三,论文构建了两个数据集,取得了较好的实验结果。
|
||||
|
||||
最后,给你留一个思考题,这篇论文使用了双向LSTM的架构,能不能使用单向LSTM呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
@ -0,0 +1,69 @@
|
||||
<audio id="audio" title="030 | ACL 2018论文精读:什么是“端到端”的语义哈希?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/6f/93/6f13c827752aeadead7b12a61299fe93.mp3"></audio>
|
||||
|
||||
今天,我们来看今年ACL大会的一篇最佳论文提名,题目是《NASH:面向生成语义哈希的端到端神经架构》([NASH: Toward End-to-End Neural Architecture for Generative Semantic Hashing](http://people.ee.duke.edu/~lcarin/acl2018_hashing.pdf))。
|
||||
|
||||
先来简单介绍下论文的作者群,我着重介绍三位。
|
||||
|
||||
第一作者沈丁涵(Dinghan Shen音译)是杜克大学计算机科学系的博士生。他已经发表了多篇自然语言处理和机器学习相关的论文,并且在NEC实验室和微软研究院都实习过。
|
||||
|
||||
论文的共同第一作者苏勤亮(Qinliang Su音译),目前是中山大学数据科学与计算机学院的副教授。他在香港大学取得博士学位,之后曾在杜克大学从事博士后研究工作。
|
||||
|
||||
作者中的劳伦斯·卡林(Lawrence Carin)是杜克大学教授。卡林是机器学习的权威,也是沈丁涵的导师。
|
||||
|
||||
## 论文的主要贡献
|
||||
|
||||
在很多的应用中,我们都需要根据一个已有的文档表达和一个文档库,找到最相近的,或者说最类似的文档。这经常被叫作“**相似查找**”(Similarity Search)或者“**最近邻查找**”(Nearest-Neighbor Search),在推荐系统、信息检索、图片检索等领域都有非常广泛的应用。
|
||||
|
||||
**“语义哈希”(Semantic Hashing)被认为是解决“相似查找”的一个重要并且行之有效的方法**。简单来说,“语义哈希”要做的就是把文档表达为离散的,也就是二元的向量。这些向量保留了文档在原始空间中的相似关系。因此常常被认为是带有语义的哈希过程,这也就是“语义哈希”这个名字的来历。
|
||||
|
||||
当我们把文档转换为语义哈希空间之后,文档之间相似度的计算就变成了利用“**汉明距离**”(Hamming Distance)来计算离散向量之间的距离。在当下的计算机体系架构中,上百万文档之间的“汉明距离”都可以在几个毫秒间完成计算。因此,我们可以看到,**“语义哈希”的一个优势就是计算快捷,并且保持了原始空间的语义信息**。
|
||||
|
||||
那么,看似这么有优势的“语义哈希”有没有什么劣势呢?
|
||||
|
||||
虽然已经有相当多的研究针对文字数据产生哈希,但是这些现有的方法都有一些明显的问题,其中最紧要的一个问题就是这些方法大多都需要两个阶段。
|
||||
|
||||
具体是哪些方法呢?我把这些方法归纳为两种思路。
|
||||
|
||||
第一种思路,我们首先需要在无监督的条件下学习文档的二元哈希;然后,我们需要训练L个二元分类器来预测L个二元位的哈希值,这个步骤是监督学习过程。
|
||||
|
||||
第二种思路,我们首先针对文档学习连续的表达向量,然后在测试阶段再把连续值进行二元离散化。
|
||||
|
||||
很明显,不管是哪一种思路,这种两个步骤的方法都不可避免地会仅仅得到次优的结果。这是因为两个步骤的优化流程是脱节的。而且,在从连续的表达向量到二元离散化的过程中,往往利用的是经验法则(Heuristic),因此语义信息可能被丢失。
|
||||
|
||||
基于这些问题,这篇论文提出了“**端到端**”(End-to-End)的“语义哈希”训练过程。作者们认为,经过一个阶段就可以得到完整哈希值的研究工作,这篇文章是第一个。在此之上,作者们利用了最新的**NVI框架**(Neural Variational Inference,神经化的变分推断),来学习文档的二元编码,在无监督和监督环境下都取得了不错的结果。
|
||||
|
||||
这篇论文的另一个贡献就是在提出的方法和“比率损失理论”(Rate Distortion Theory)之间建立了联系。在这个联系的基础上,作者们展示了如何在模型的训练过程中“注入”(Inject)“**数据相关的噪音**”(Data-Dependent Noise)来达到更好的效果。
|
||||
|
||||
## 论文的核心方法
|
||||
|
||||
作者们首先把从文档生成“语义哈希”看作是一种**编码(Encode)和解码(Decode)的流程**。文档的二元哈希向量则被看成了表达文档的一种隐变量(Latent Variable)。也就是说,作者们认为文档的哈希向量是从文档的特性(可以是TF-IDF值)产生的一组隐变量,这也被认为是一种编码的过程,是从文档的特性向量到哈希向量的编码。
|
||||
|
||||
在过去的模型中,编码过程是被反复关注的,但是解码过程则很少有模型去直接建模。所谓的解码过程就是从已经产生的哈希向量转换成为文档的特性向量的过程。也就是说,我们希望能够重新从哈希向量中生成原始的数据。
|
||||
|
||||
对原始数据和中间隐变量的编码过程统一进行建模,是当前神经网络生成式模型的一种标准方法。在这里,编码和解码都各自有不同的神经网络,用于表达相应的条件概率分布。
|
||||
|
||||
具体来说,数据的原始信息X首先经过一个多层感知网,然后再变换成为二元的中间变量Z。这时候,Z其实就是我们需要得到的哈希向量了。只不过在提出的模型中,还有第二个部分,那就是从Z得到X的一个重现,也就是我们刚才提到的利用哈希来重构数据。很明显,我们希望重构的X和原始的X之间要非常相似,也就是说距离最小。
|
||||
|
||||
作者们发现,从数据中学习一个二元编码是“信息论”(Information Theory)中典型的“有损源编码”(Lossy Source Coding)问题。因此,**“语义哈希”其实也可以被看作是一个“比率损失平衡”(Rate Distortion Tradeoff)问题**。
|
||||
|
||||
什么意思呢?就是说,我们希望用较少的比率来对信息进行编码,同时又希望从编码中重构的数据能够和原始的数据尽量相近。很明显,这两者有一点“鱼与熊掌不可兼得”的意思,也就是这两者需要一个平衡才能达到最优。
|
||||
|
||||
把重写模型的目标函数定为“比率损失平衡”,通过这种形式,作者们意识到模型中的从编码到重构数据的条件分布,也就是一个高斯分布中的**方差值**,其实控制了这个平衡的关系。那么,就需要针对不同的文档对这个方差值进行调整,从而达到最优的编码效果,同时又是比率损失平衡的。作者们并没有采用去优化这个方差值的办法,而是在一个固定的方差值周围加入一些随机噪声,从而在实际实验中收到了不错的效果。
|
||||
|
||||
## 论文的实验结果
|
||||
|
||||
作者们利用了三个数据集进行实验,所有的数据集都首先转换成为TF-IDF的形式。作者们把提出的方法和其他的五种基本方法进行了比较。
|
||||
|
||||
从总体上来说,文章提出的方法在没有随机噪声的情况下,已经比其他五种方法要好得多。加入随机噪声之后,模型就有了更好的表现力。同时,作者还展示了学到的二元哈希值的确能够保持语义信息,相同文本类别的文档,它们的哈希值非常类似,也就是我们之间说过的,他们之间的汉明距离很近。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你讲了今年ACL的一篇最佳论文提名,至此,我们关于ACL 2018的分享就告一段落。
|
||||
|
||||
一起来回顾下要点:第一,这篇文章针对语义哈希产生过程的劣势,提出了“端到端”的语义哈希训练过程;第二,论文的核心方法是把文档生成语义哈希看作是一种编码和解码的流程,进一步发现“语义哈希”其实也可以被看作是一个“比率损失平衡”问题;第三,论文取得了不错的实验效果。
|
||||
|
||||
最后,给你留一个思考题,在现实中利用语义哈希,有没有什么障碍?比如要在推荐系统中做语义哈希,最大的挑战会是什么?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
100
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/复盘 7 | 一起来读人工智能国际顶级会议论文.md
Normal file
100
极客时间专栏/geek/AI技术内参/人工智能国际顶级会议/复盘 7 | 一起来读人工智能国际顶级会议论文.md
Normal file
@ -0,0 +1,100 @@
|
||||
|
||||
今天我准备了 30 张知识卡,和你一起来复盘“人工智能国际顶级会议”模块。在这个模块里,我总共介绍了10个顶级会议,包括机器学习方面的ICML、NIPS;机器视觉的CVPR、ICCV;自然语言处理的ACL、EMNLP;数据挖掘和数据科学的KDD、WSDM;信息检索和搜索的SIGIR;互联网综合的WWW。
|
||||
|
||||
提示:点击知识卡跳转到你最想看的那篇文章,温故而知新。
|
||||
|
||||
## KDD 2017(数据挖掘与知识发现大会)论文精讲
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/10/06/10cb191bddde32920cfb9d48971ce806.jpg" alt="" />](https://time.geekbang.org/column/article/159)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/b7/cc/b77e0dd9b5422fc483605dfa18519fcc.jpg" alt="" />](https://time.geekbang.org/column/article/391)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/6e/42/6edea80c0a378d8e4a8196aa3cb34942.jpg" alt="" />](https://time.geekbang.org/column/article/394)
|
||||
|
||||
## EMNLP 2017(自然语言处理实证方法会议)论文精讲
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/99/0c/998b153a3e799873d0a74490b073170c.jpg" alt="" />](https://time.geekbang.org/column/article/397)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/32/d5/32bbe7013a33e4e6f4902e465104edd5.jpg" alt="" />](https://time.geekbang.org/column/article/658)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/76/3b/767fc3b2298a9705c7b2f731c7b12f3b.jpg" alt="" />](https://time.geekbang.org/column/article/661)
|
||||
|
||||
## ICCV 2017(国际计算机视觉大会)论文精讲
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/55/38/55eb3ea8693c70200aafd3f5f4277038.jpg" alt="" />](https://time.geekbang.org/column/article/2681)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/fb/2f/fbb4b50ec209879b3d1e1da5a426212f.jpg" alt="" />](https://time.geekbang.org/column/article/2717)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/e2/03/e2bd4edfd5d3cc2b818d9e371584fe03.jpg" alt="" />](https://time.geekbang.org/column/article/2782)
|
||||
|
||||
## NIPS 2017(神经信息处理系统大会)论文精讲
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/61/2c/617ffdf73e8f41a36a11ac5ea5f0862c.jpg" alt="" />](https://time.geekbang.org/column/article/2868)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/11/27/115484986db70b94b363237a09d3d227.jpg" alt="" />](https://time.geekbang.org/column/article/2941)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/28/2a/2811bcf14a2e16759d6afaa8e15dcc2a.jpg" alt="" />](https://time.geekbang.org/column/article/3211)
|
||||
|
||||
## WSDM 2018(网络搜索与数据挖掘国际会议)论文精讲
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/80/ae/809abe856cc2482c2b8f80728dda82ae.jpg" alt="" />](https://time.geekbang.org/column/article/3946)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/5b/ec/5b47edfe3db4fec97e5b1975bdc983ec.jpg" alt="" />](https://time.geekbang.org/column/article/3961)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/e6/d5/e6d5f263155a9d7e8a964516dedfa4d5.jpg" alt="" />](https://time.geekbang.org/column/article/4024)
|
||||
|
||||
## The Web 2018(国际万维网大会)论文精讲
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/40/f7/40c75bfcf277690085923effd015e1f7.jpg" alt="" />](https://time.geekbang.org/column/article/8106)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/7d/95/7d273597ba638065e3b1286e07a4e495.jpg" alt="" />](https://time.geekbang.org/column/article/8234)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/80/9d/80b6558a51f71d7fc5eda9b3a31f5a9d.jpg" alt="" />](https://time.geekbang.org/column/article/8293)
|
||||
|
||||
## CVPR 2018(国际计算机视觉与模式识别会议)论文精讲
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/45/86/45b1fa4c83680b7be3e2247298a33086.jpg" alt="" />](https://time.geekbang.org/column/article/12010)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/ab/75/ab3714f22d7729600aaaa2fff2c8fa75.jpg" alt="" />](https://time.geekbang.org/column/article/12100)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/a8/9b/a867d7e402b26eef0ac152e24dc90a9b.jpg" alt="" />](https://time.geekbang.org/column/article/12190)
|
||||
|
||||
## SIGIR 2018(国际信息检索大会)论文精讲
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/e9/ba/e9384d0b367a2a54570b345acd7bf2ba.jpg" alt="" />](https://time.geekbang.org/column/article/11367)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/11/13/11039fa5ef4bae821c3b0e2313195f13.jpg" alt="" />](https://time.geekbang.org/column/article/11636)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/c2/dc/c21379bb9909723a3f1df50aa33fd9dc.jpg" alt="" />](https://time.geekbang.org/column/article/11851)
|
||||
|
||||
## ICML 2018(国际机器学习大会)论文精讲
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/6a/80/6aa4bbbe32cd70673416c1cd31705280.jpg" alt="" />](https://time.geekbang.org/column/article/12443)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/d3/6b/d3bffb14410259a1ca768447bd41f36b.jpg" alt="" />](https://time.geekbang.org/column/article/12648)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/09/2e/09dadd6ef090c20da82ba628caf5f52e.jpg" alt="" />](https://time.geekbang.org/column/article/12834)
|
||||
|
||||
## ACL 2018(计算语言学学会年会)论文精讲
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/ad/4f/adc4f44971972753870205b15016244f.jpg" alt="" />](http://uhttps://time.geekbang.org/column/article/13014)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/7c/77/7c2fca243d7761290af3286f7f20fa77.jpg" alt="" />](https://time.geekbang.org/column/article/13193)
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/80/fd/8093da74b265592edd46f74dcb8f53fd.jpg" alt="" />](https://time.geekbang.org/column/article/13276)
|
||||
|
||||
## 积跬步以至千里
|
||||
|
||||
学习是独立的,需要你一个人去完成。但学习者从来都不必孤独,我们走进这些国际顶级学术会议的论文,其实就是和每一篇论文背后的作者进行一场对话。与优秀的人同行一定能让我们走得更快。
|
||||
|
||||
这个模块我根据自己的经验,为你选择了10个顶级会议。针对每一个会议,我都会在会议结束后用3篇文章来详细剖析这个会议的精髓和一些前沿信息。我希望通过我的眼睛和思考让你看到在这个领域里那些激动人心的发展,收获新知、拓展视野,同时也把我的学习方法分享给你。
|
||||
|
||||
我想你应该已经掌握了我分析论文的套路了,对于每一篇文章,我一定会先去做一些背景研究,了解作者群,了解对应的学术机构或者公司信息;然后弄清楚论文解决了什么问题,核心贡献是什么;再详细研究论文的具体方法。这个方法很简单,就是牢牢抓住一个主线,找到最核心的内容来消化吸收。但是**真正让这个方法内化成你的思维模式,还是需要大量的阅读和练习**。相信我,如果想在人工智能领域继续深耕,阅读大量论文,一定是一个最值得做的投资,因为回报极大。
|
||||
|
||||
那回到阅读论文本身,最后想跟你分享的一点只有八个字:**学好英语,阅读原文**。我知道你可能会说我英语还真不好,但是到达能够阅读原文的水平其实也并没那么难。你不妨直接找一篇我们专栏里讲过的论文原文,就把每一段的第一句读一下,看看能否学到东西。先开始看起来,遇到不会的且影响你理解的单词或句子再去查,你的英语水平就已经开始变得越来越好了。
|
||||
|
||||
以上就是我们对论文精读这个模块的一个复盘,希望专栏里的这三十篇论文是一个起点,能够帮助你养成关注国际顶级会议、阅读论文的习惯,拥有这一强大的学习利器,提升自己的学习效率。
|
||||
|
||||
最后,关于这一模块,关于国际会议,或者是在论文阅读方面,你还有什么问题和想法,欢迎你留言和我讨论。
|
||||
|
||||
|
59
极客时间专栏/geek/AI技术内参/广告系统核心技术/079 | 广告系统概述.md
Normal file
59
极客时间专栏/geek/AI技术内参/广告系统核心技术/079 | 广告系统概述.md
Normal file
@ -0,0 +1,59 @@
|
||||
<audio id="audio" title="079 | 广告系统概述" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/cb/c9/cbe017c26182dca91bc8ccf0a49fcec9.mp3"></audio>
|
||||
|
||||
在之前的分享里,我们已经介绍了搜索、推荐系统以及文本分析这三个重要的机器学习、人工智能领域。从基本的思想、算法以及架构说起,我们针对这几个领域要解决的问题和在解决这些问题的过程中出现的经典思路都做了比较详细的讲解。
|
||||
|
||||
可以说,搜索是最早利用机器学习以及人工智能思想和技术的应用领域。不管是从最初的图书馆信息检索系统还是到最近二十年年兴起的大规模现代搜索引擎,围绕搜索系统所产生的诸多方法论、算法和模型都影响着很多相关领域。**特别是基于排序学习(Learning To Rank)的搜索排序算法的提出和成熟,让机器学习的一大批技术和搜索的现实问题得以结合,成为了机器学习和某个应用领域相互发展的一大典范**。类似的,对于推荐系统来说,也在过去十几年间,通过机器学习技术和推荐问题的结合,迸发出了一系列经典方法,这些方法也影响了其他应用领域的发展。
|
||||
|
||||
从今天起,我们就来看另一个重要的应用领域:**计算广告**。计算广告这个领域深受搜索系统和推荐系统的影响,可以说有很多技术都是从这两个领域直接借鉴过来的。然而,因为其应用对象的不同和场景的不同,计算广告又有很多自己独特的问题,以及解决这些问题需要注意的限制条件。因此,在过去十多年间,计算广告慢慢发展出了一条独特的道路。
|
||||
|
||||
那么,我们首先来聊一聊广告系统的概述,看一下这个领域要解决的主要问题以及发展的简要历史。
|
||||
|
||||
## 计算广告的主要场景
|
||||
|
||||
为了给你建立一个感性的认识,我们先来讲一下计算广告的主要场景,了解这个领域需要解决什么样的问题。我们还会在后面的讲解中逐一展开这些场景下的问题设置,因此,这里主要是从大方向上来对这些场景进行一个把握。
|
||||
|
||||
互联网的应用和内容服务商慢慢意识到,提供广告可以作为一种重要的盈利手段,这以后,一个新兴的行业就此诞生了。
|
||||
|
||||
从最简单的场景来说,一个互联网内容服务商,例如一个新闻网站或者一个社交媒体网站,希望在自己的服务里插入广告,这基本上就是一个内容的匹配问题。我们把**在自己的服务里提供广告的内容服务商叫作“发布商”**(Publisher)。
|
||||
|
||||
发布商的目的是匹配广告和目标人群,有时候又叫“受众”(Audience),使得目标人群能够对广告产生兴趣并且点击,然后最终产生行动。这里的行动,可以是购买了广告里的商品,订阅了广告里的服务,也可以是非常广义下的行动,比如对某个品牌有了更加深刻的好印象等。
|
||||
|
||||
在这个最简单的场景里,有两种最基本的广告模式,一种叫作“**搜索广告**”,一种叫作“**展示广告**”。搜索广告是说发布商提供某种形式的搜索服务,然后将广告显示在搜索结果页面上。在这样的情况下,广告需要和查询关键词的语义有关系。从本质上说,这种情况下的广告是搜索结果的自然延伸。而对于展示广告来说,往往就是显示在发布商的普通页面上,一般并不直接和任何查询关键词相关。这又和经典的推荐系统的场景非常类似,也可以看做是推荐的一个自然延伸。
|
||||
|
||||
如果仅从这样的角度来看广告系统,那么我们基本上可以认定,广告就是特殊的搜索或者特殊的推荐。但其实广告系统还有另外一个和搜索以及推荐非常不一样的方面,最终决定了广告系统的特殊性,那就是“**广告商**”(Advertiser)。
|
||||
|
||||
**广告商是那些愿意把自己的广告放到发布商上来显示给受众观看的服务提供商**。广告商对于自己的目标往往有明确的定位,比如希望能够通过广告来增加销售,提高服务订阅数量,提高自己的品牌效应等等。因此,广告商自然希望能够在以下这一系列的决策上有更多的发言权,比如发布商究竟如何显示广告,在什么情况下显示广告,针对什么人群显示广告等,从而能够让自己的利益最大化。
|
||||
|
||||
也正是因为这个原因,为了满足广告商的需求,从而能够吸引更多的广告商来自己的服务打广告,发布商往往提供各种工具,帮助广告商了解自己的用户群体以及广告的效果,使得广告商认为这个发布商是一个可以值得依赖的合作伙伴。
|
||||
|
||||
然而,即便如此,发布商和广告商的利益往往是不完全一致的。发布商,作为一个内容的提供平台或是一个社交媒体平台,需要照顾自己用户群的感受。例如,一个医院希望在一个新闻网站上投放自己的广告,即便可以为新闻网站带来丰厚的广告收益,新闻网站也需要考虑,从长远来看,这些广告内容和自己的新闻内容是否和谐,是否慢慢地会让用户产生厌烦情绪等。从这个角度来看,发布商不仅仅是简单地进行广告和受众的匹配,还有一个最重要的工作,是从宏观上平衡广告投放效果和内容服务效果,从而达到自己价值的最大化。
|
||||
|
||||
随着发布商和广告商数目的增多,众多的发布商发现,如果都需要依靠自己的力量来构建大型广告管理系统,那无疑是非常耗时也是很多中小发布商所不具备的能力。同样,对于广告商来说,如果希望自己的广告能够投放到众多发布商中,逐一研究发布商的工具以及各个发布商用户的区别,就会为广告投放增加不小的难度。因此,针对发布商以及针对广告商的中间平台很快就应运而生。
|
||||
|
||||
对于发布商的中间平台而言,整合众多发布机会,从而能够让发布商接触到更多的机会,成为了“**供应侧平台**”(Supply Side Platform)。而对于广告商的中间平台而言,整合众多广告商机会,从而能够让广告商接触更广的发布机会,成为了“**需求侧平台**”(Demand-side Platform)。而连接这两大平台的则是“**广告交换平台**”(Ads Exchange Platform)。关于这些平台的具体功效,我们后面会慢慢接触到。
|
||||
|
||||
总之,现在你可以了解到,**广告系统的重要场景是涉及广告商、发布商以及受众“三种角色”的复杂系统之间的平衡以及利益的最大化**。
|
||||
|
||||
## 计算广告的简要历史
|
||||
|
||||
当我们熟悉了广告系统的一些经典场景之后,我们现在来回顾一下计算广告系统的一个发展简史。
|
||||
|
||||
最早的互联网广告出现在1994年11月27日。AT & T这个电信运营商在当时展现了一个叫HotWired的广告商的广告。那个时候,整个互联网只有三千多万用户。
|
||||
|
||||
1998年的时候,Goto.com的比尔⋅格罗斯(Bill Gross)发明了“搜索广告”(Sponsored Search),或者有些搜索引擎也把它叫作“竞价排名”。Goto.com后来在2001年成为了Overtune,并最终在2003年被雅虎公司收购。
|
||||
|
||||
2002年,谷歌也开始了自己的搜索广告产品AdWords并且很快引入了Generalized Second-Price-Auction,或者简称**GSP**的机制来对广告进行投标竞价。2007年,雅虎也跟进了这种方式来管理所有的广告投标。值得一提的是,当时的谷歌还针对一个广告专利技术纠纷,支付了240万股股票给雅虎。
|
||||
|
||||
另一方面,1998年已经有公司开始尝试利用自己的内容页面展示广告。2003年,谷歌收购了一家叫Oingo的公司并且将其业务更名为AdSense,从而开始了其展示广告服务。其后,雅虎和微软等服务商也相继效仿,扩展了自己的广告业务线。
|
||||
|
||||
早在2005年前后,各种广告交换平台也纷纷出现。这些交换平台的出现为“**实时竞价**”(Real-Time-Bidding),或叫**RTB**提供了可能性。今天,绝大多数广告平台都支持RTB来为广告商以及内容发布商提供服务。
|
||||
|
||||
## 总结
|
||||
|
||||
今天我为你简单介绍了广告系统的概况。一起来回顾下要点:第一,我们讲了广告系统的基本应用场景以及其中的重要角色;第二,我们回顾了计算广告在过去发展的一个简单历史。
|
||||
|
||||
最后,给你留一个思考题,除了我们所说的搜索广告和展示广告,你还见过什么其他类型的互联网广告呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
74
极客时间专栏/geek/AI技术内参/广告系统核心技术/080 | 广告系统架构.md
Normal file
74
极客时间专栏/geek/AI技术内参/广告系统核心技术/080 | 广告系统架构.md
Normal file
@ -0,0 +1,74 @@
|
||||
<audio id="audio" title="080 | 广告系统架构" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/61/86/61ab8f6f5f86c3c12b2a3a40fee9ae86.mp3"></audio>
|
||||
|
||||
从本周开始,我们就进入了计算广告这个重要的应用领域。周一我们首先介绍了广告系统的概述,了解了这个领域要解决的主要问题以及发展的简要历史。我们知道了广告系统中有发布商、广告商、受众群这些实体,还有应运而生的各类中间平台。
|
||||
|
||||
今天,我们就更加细致地来看一下**广告系统的架构**,熟悉各个组件都是怎么运作的。
|
||||
|
||||
## 实时广告竞标的重要生态圈
|
||||
|
||||
在前一篇分享里,我们提到了“**实时竞标**”广告系统(简称为**RTB**)这个概念。现在,我们首先来回顾一下这个系统的重要生态圈。
|
||||
|
||||
实时竞标系统的生态圈里有四个重要的“角色”:广告商、发布商、广告交换商和用户。在此基础上,这个生态圈产生了一些重要的新的中间平台。
|
||||
|
||||
<li>
|
||||
**供应侧平台**,简称SSP,负责管理众多发布商的展示机会,接受来自需求侧平台的竞价(简称Bid),同时自动地展示广告。
|
||||
</li>
|
||||
<li>
|
||||
**广告交易平台**,简称ADX,是负责多个SSP和需求侧平台进行匹配的中间平台。
|
||||
</li>
|
||||
<li>
|
||||
**需求侧平台**,简称DSP,是负责管理众多广告商诸多广告的平台。
|
||||
</li>
|
||||
<li>
|
||||
**数据处理平台**,简称DMP,是为SSP、ADX、DSP提供数据服务的中间商。
|
||||
</li>
|
||||
|
||||
值得注意的是,以上的这种区别仅仅是一种概念上的区分,目的是为了让从业人员能够更加清晰地理解各个系统的目标和作用。在实际的运作中,不少平台都充当了多个角色,甚至有比较大的互联网广告平台在这几个子系统中都有所涉及。
|
||||
|
||||
## 用户行为定向
|
||||
|
||||
了解了实时竞价系统的生态环境之后,我们来看在一次广告显示的流程中,这些生态伙伴都参与了什么样的动作。
|
||||
|
||||
第一步,用户来到某个网站,网站产生了一个对实时竞价系统广告网络的请求。
|
||||
|
||||
第二步,实时竞价系统广告网络向某个DSP发送请求,这个请求里包含了用户是谁,当前页面是什么,以及一些上下文的数据。
|
||||
|
||||
第三步,DSP收到请求以后,就向DMP发送一个数据请求,用于收集用户更多的信息,包括用户的年龄、性别以及喜好。
|
||||
|
||||
第四步,DSP收到DMP的信息以后,会向实时竞价系统发出一个自己认为合适的广告以及竞价的价格。
|
||||
|
||||
第五步,实时竞价系统广告网络收集到所有的广告竞价以后,会举行一个拍卖(Auction)。每个实时竞价系统的拍卖规则可以不同。
|
||||
|
||||
第六步,实时竞价系统会向赢得广告位的DSP发送最后的收款价格,这个价格是根据某种拍卖规则决定的。
|
||||
|
||||
第七步,广告显示给了用户。
|
||||
|
||||
第八步,用户对广告的反馈,例如是否点击,是否购买广告相应的产品,是否订阅广告对应的服务等,这些信息会返回给DSP。
|
||||
|
||||
注意,这里提到的实时竞价系统广告网络既可以是一个SSP,也可以是一个ADX。
|
||||
|
||||
我们通过这个流程可以看出,在广告生态系统中,几乎所有的角色,都要在每一个广告请求中参与其中。每一个流程的不精确都有可能让最后现实的广告不符合用户的喜好。
|
||||
|
||||
## 用户追踪
|
||||
|
||||
从上面这个广告请求的流程中,我们可以看到,在整个广告生态系统中,**对用户的追踪是一种非常重要的能力**。如果广告平台的任何一个部件无法对用户信息进行有效的管理,那么,我们就无法显示相关的广告。
|
||||
|
||||
广告生态群对用户信息的追踪有一个基本的技术,那就是**存储用户的Cookie**。实际上,在广告生态圈里,就是用Cookie来对用户的身份进行识别的。当用户第一次访问一个网站的时候,一段Cookie就会被建立并且存储在用户的浏览器里。当用户下一次再访问的时候,这段Cookie就会被重新访问并且可能被更改。
|
||||
|
||||
需要注意的是,Cookie是和某一个域名(Domain)相关联的。比如,在通常情况下,你访问了A网站,B网站就无法访问你在A网站的Cookie。这样做的初衷是在互联网上可以做到保护用户的隐私以及有限制的信息共享。但作为广告平台来说,这样做当然是无助于平台对于用户信息的访问。
|
||||
|
||||
那么,一种方法就是B网站直接得到A网站的允许,到A网址植入脚本从而来收取用户的Cookie信息。例如,在某个时期内,纽约时报的网站就有多达17个合作方在对用户的数据进行收集。然而,即便是这样,每个单独的数据收集方都只能对用户在互联网上的行为进行局部的数据采集。也就是说,这些收集方很难对用户在互联网上的全部行为进行建模。很明显,这是不利于展示最有价值的广告信息的。
|
||||
|
||||
在这样的情况下,也就慢慢催生了一个新的技术——**Cookie的整合**。简单说来,Cookie整合要做的事情就是把多个由不同的数据收集方收集的Cookie进行匹配,从而能够在逻辑上把这些Cookie都当做同一个人处理。据统计,一个用户在30次点击内,就有99%的概率会被互联网上前10大“数据追踪机构”所追踪,而至少有40%的Cookie可以得到有效的整合。
|
||||
|
||||
当然,用Cookie来追踪用户并不是万能的。用户可以删除Cookie信息甚至在浏览器的层面禁止Cookie信息。这就给广告平台提出了不小的挑战。最近几年,基于其他存储技术的用户追踪手段,例如Canvas API或者Flash Cookie等也慢慢流行起来。
|
||||
|
||||
## 总结
|
||||
|
||||
今天我为你介绍了广告系统的基本架构。一起来回顾下要点:第一,我们分享了广告系统中各个系统的角色;第二,我们聊了各个子系统在一个广告显示请求中都干了什么事情,从感性上为你建立一个整体的观念;第三,我们简单提及了用户追踪的概念以及现在最常见的用户追踪技术的一些基本思路。
|
||||
|
||||
最后,给你留一个思考题,对于一个电子商务网站来说,卖家希望通过在站内显示的广告来扩大收益,这种情况下,谁是DSP、SSP和ADX呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
61
极客时间专栏/geek/AI技术内参/广告系统核心技术/081 | 广告回馈预估综述.md
Normal file
61
极客时间专栏/geek/AI技术内参/广告系统核心技术/081 | 广告回馈预估综述.md
Normal file
@ -0,0 +1,61 @@
|
||||
<audio id="audio" title="081 | 广告回馈预估综述" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/bf/9a/bf7b32f9b64f28c02552fb3601db119a.mp3"></audio>
|
||||
|
||||
在上一篇的分享里,我们详细地讨论了广告系统的架构,熟悉了各个组件都是怎么运作的,特别是我们重点剖析了对于每一个广告请求,供应侧平台(SSP)、广告交易平台(ADX)、需求侧平台(DSP)以及数据处理平台(DMP)都扮演了什么样的角色。同时,我们介绍了对于用户信息的追踪和整合,业界的基本技术就是存储用户的Cookie,以及慢慢催生的Cookie的整合技术。
|
||||
|
||||
今天,我们就来看一看整个计算广告领域最核心的一个问题:**广告回馈预估**。
|
||||
|
||||
## 什么是广告回馈预估
|
||||
|
||||
什么是广告回馈预估?广告回馈预估要解决什么问题?我们先来弄明白这个问题。
|
||||
|
||||
我们说过计算广告有两大应用领域:搜索广告和展示广告,以及围绕这些广告的生态系统。这些系统或者领域都希望达到一个最终的目的,那就是用户和广告进行**交互**,并且能够对广告所代表的服务或者产品产生印象,从而达成某种程度的**交易**。
|
||||
|
||||
这里的“交互”包括对传统广告的点击,也包括对视频广告的观看。而在和广告交互之后,用户对于广告所代表的服务或商品达成的“交易”,包括购买、订阅甚至是改变印象等等。那么,这一切和广告本身的交互以及和广告所代表的服务或者商品达成的交易,我们都通通称为“**回馈**”(Feedback)。
|
||||
|
||||
而我们所说的“**回馈预估**”,**就是要预测用户这种行为的可能性,或者说是概率**。也就是说,我们希望了解用户是不是有可能点击这个广告;有多大概率观看完这段视频广告;有多大可能去购买这个广告所代表的商品。
|
||||
|
||||
**对广告的回馈概率进行有效估计是很多广告生态系统组件的一个核心需求**。对于发布商来说,显示广告,不管是通过搜索结果还是通常的页面,都希望能够有用户交互从而带来收入。很多广告系统的收入模式就是依靠用户的点击从而让广告商为此支付一定费用给发布商。
|
||||
|
||||
因此,对于发布商来说,越是能准确估计这部分的点击率,就越能保证自己的收入。相应地,对于广告商来说,很有必要知道某一种类型的广告在哪个发布商能够带来更多的点击,从而能够有针对性地对于某个发布商进行投放。由此看来,广告回馈预估是一个非常重要的有待解决的技术问题。
|
||||
|
||||
## 广告回馈预估的普遍挑战和技术难点
|
||||
|
||||
既然广告回馈预估很重要,我们是不是可以直接用现成的机器学习工具就可以解决这个问题呢?这个问题有什么自身的特点,又有哪些挑战和技术难点呢?
|
||||
|
||||
在比较简单的设定下,广告回馈预估可以看做是某种**监督学习的任务**。在这类监督学习任务里,标签是用户的动作,例如点击或者观看,或者购买等。我们需要建立的是一个用户在某种上下文中对广告标签的一个模型。这里的上下文包括查询关键词、用户信息、广告信息以及一切其他有用的信息。
|
||||
|
||||
那么,在这样的设定下,广告回馈预估的核心挑战是什么呢?
|
||||
|
||||
**核心挑战其实来自于稀疏的数据**。
|
||||
|
||||
不管是在搜索广告中也好,还是在展示广告中也好,从平均的角度来说,相比于用户和正常的搜索结果或者展示结果(比如新闻内容等)的互动,用户与广告的互动要成倍地减少。有一项研究表明,在同样一个位置,广告的点击率可以是正常内容的十分之一、百分之一甚至是千分之一。也就是说,从概率的角度来看,用户普遍是不点击广告的。这个观察基本上是符合我们对用户的普遍理解的。但是,较少的点击数据造成的结果就是,从监督学习的角度来说,大量的数据点都是未交互的数据,只能当做**负例**来处理。
|
||||
|
||||
实际上,**在广告点击率预估的问题中,正例的数目常常是负例的百分之一或者千分之一**。这样造成的就是非常“**不均衡**”的数据集。而要想在不均衡的数据里中进行概率估计,往往都是一件困难的事情。
|
||||
|
||||
而购买事件相对于广告点击来说就更加稀少,这一点其实也很正常。在点击了广告之后,又有多少人真正会去购买这些产品呢?因此,提高广告的转化率,也就是交易发生的概率,往往就是更富有挑战的任务。
|
||||
|
||||
值得一提的是,在监督学习的框架中,除了数据问题以外,广告回馈预估还有**目标函数的挑战**。具体是什么情况呢?
|
||||
|
||||
在真实的系统中,我们需要在很多候选可能的广告中,选出最优的一个或者几个显示在页面上。从某种程度上来说,这更像是一个**排序问题**。同时,对于不少DSP(需求侧平台)来说,广告排序的最终目的是进行“竞拍”(Auction)。因此,最后估算广告的点击率以后,还需要看广告的竞价,由此来对广告是否会赢得竞拍从而被显示在页面上进行一个更加全面的估计。很显然,和传统的推荐或者搜索比较,这些问题都要复杂许多。
|
||||
|
||||
## 广告回馈预估的算法和模型
|
||||
|
||||
广告回馈预估的难点和挑战来自两方面,一方面是稀疏的数据,会造成不均衡的数据集;一方面是目标函数的挑战。那么,广告回馈预估有哪些比较常见的算法或者模型呢?
|
||||
|
||||
我们接下来会对这一系列有关的算法和模型进行详细讨论。今天,我会带你从宏观上进行一下总结。
|
||||
|
||||
从最直接的监督学习的角度来看,广告回馈预估的一个常见算法就是把这个问题当做**二元分类问题**,并且直接利用“**对数几率回归**”(Logistic Regression)来对这个问题建模。实际上,直到今天,对数几率回归依然是广告回馈预估领域的重要方法。
|
||||
|
||||
第二类经常使用的就是**树模型**,特别是 **GBDT** 这个通常在搜索中使用的模型。我们前面已经提到了这类模型对于排序学习的作用。
|
||||
|
||||
第三类目前比较火热的领域就是**如何利用深度学习来对回馈预估进行建模**。这一类模型在最近几年有了比较大的进展。
|
||||
|
||||
## 总结
|
||||
|
||||
今天我为你介绍了广告系统中最核心的一个问题:广告回馈预估。一起来回顾下要点:第一,广告回馈预估就是预测“用户与广告的交互以及达成交易这种行为”的概率;第二,广告回馈预估有两方面的难点和挑战,分别来自数据和目标函数;第三,在这个领域有一些流行的模型,比如对数几率回归和数模型等。
|
||||
|
||||
最后,给你留一个思考题,当我们有不均衡数据集的时候,我们一般都有哪些解决方案?针对广告预估,是否需要对这些方案进行额外的处理呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
69
极客时间专栏/geek/AI技术内参/广告系统核心技术/082 | Google的点击率系统模型.md
Normal file
69
极客时间专栏/geek/AI技术内参/广告系统核心技术/082 | Google的点击率系统模型.md
Normal file
@ -0,0 +1,69 @@
|
||||
<audio id="audio" title="082 | Google的点击率系统模型" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/0f/c6/0f878e871430c4e54a67c0a896f96dc6.mp3"></audio>
|
||||
|
||||
广告是很多互联网公司的重要收入来源,比如Google、Facebook、微软、阿里巴巴、百度、腾讯等。以Facebook为例,它的2017年第一季度财报显示,公司总营收为78.4亿美元,这其中98%的收入来自广告。同样,在这些公司内部,都有着完善的广告系统来支撑其广告业务。
|
||||
|
||||
当然,大型广告系统的成功需要依靠很多相互协调的子系统和组件。今天我要和你聊的是广告系统里最基础的一个子系统,也是整个广告系统的核心功能之一——**点击率预估系统**。点击率预估,顾名思义就是根据环境和广告的类型,来估计用户有多大的可能性点击当前的广告。这个预估值会用于广告系统的其他组件,比如对广告主(投放广告的客户)的计费模块。因此,点击率预估的准确性和实时性就变得十分重要。
|
||||
|
||||
今天和你分享一篇广告点击率预估文献史上非常重要的论文,它来自Google广告团队,标题是《工程实践视角下的广告点击率预估》(“Ad Click Prediction: a View from the Trenches”)。
|
||||
|
||||
## 论文背景
|
||||
|
||||
这篇论文发表于KDD 2013年的工业论文组,在短短几年时间里就获得了近200次的文章引用数,不少公司争相研究其中的内容,希望能够复制类似的算法和技术。
|
||||
|
||||
这篇文章的作者群多达16人,他们都是来自Google西雅图、匹兹堡、硅谷以及剑桥等地办公室的研究人员和工程师,文章的致谢部分也有9人。可见整个论文以及里面的技术的确是团队协作的结果。
|
||||
|
||||
这里面有两位作者值得介绍一下。第一位是论文的第一作者布兰登(H. Brendan McMahan)。布兰登早年在卡内基梅隆大学计算机系获得博士学位。他的博士生导师是戈登(Geoff Gordon)以及布卢姆(Avrim Blum),这两位都是卡内基梅隆大学机器学习界的权威教授。布兰登本人长期对优化算法有深入的研究,这篇论文的重要核心算法就来自于他的研究成果。
|
||||
|
||||
文章的另外一位作者斯卡利(D. Sculley)从塔夫茨大学(Tufts University)博士毕业之后,一直在Google的匹兹堡分部工作,并着手研究大规模机器学习系统,其中重要的代表性研究成果是如何把回归问题和排序问题结合起来(发表于KDD 2010年)。斯卡利曾经是一个著名的开源大规模机器学习软件包sofia-ml的作者,里面实现了一个大规模版本的RankSVM,一度受到关注。
|
||||
|
||||
## 在线逻辑回归(Logistic Regression)
|
||||
|
||||
文章首先讲解的是点击率预估的核心算法。因为Google要处理的数据集非常庞大,不管是样本数量还是样本的特征数都是百亿级别的,所以选用什么样的算法至关重要。2013年,也就是这篇论文发表的时候,当时大规模深度学习的环境还没有完全成熟起来,Google的科学家和工程师选择了**逻辑回归**,这是一个非常传统但也非常强大的线性分类工具。
|
||||
|
||||
我们这里简单回顾一下逻辑回归模型。
|
||||
|
||||
逻辑回归是要对二元分类问题进行建模,模型的核心是通过一组(有可能是非常巨大规模的)特征以及所对应的参数来对目标的标签进行拟合。这个拟合的过程是通过一个叫逻辑转换或函数来完成的,使得线性的特征以及参数的拟合能够非线性转换为二元标签。
|
||||
|
||||
普通的逻辑回归并不适应大规模的广告点击率预估。有两个原因,第一,数据量太大。传统的逻辑回归参数训练过程都依靠牛顿法(Newton’s Method)或者L-BFGS等算法。这些算法并不太容易在大规模数据上得以处理。第二,不太容易得到比较稀疏(Sparse)的答案(Solution)。也就是说,虽然数据中特征的总数很多,但是对于单个数据点来说,有效特征是有限而且稀疏的。
|
||||
|
||||
我们希望最终学习到的模型也是稀疏的,也就是对于单个数据点来说,仅有少量特征是被激活的。传统的解法,甚至包括一些传统的在线逻辑回归,都不能很好地解决答案的稀疏性问题。
|
||||
|
||||
这篇文章提出了用一种叫**FTRL**(Follow The Regularized Leader)的在线逻辑回归算法来解决上述问题。FTRL是一种在线算法,因此算法的核心就是模型的参数会在每一个数据点进行更新。FTRL把传统的逻辑回归的目标函数进行了改写。
|
||||
|
||||
新的目标函数分为三个部分:第一部分是一个用过去所有的梯度值(Gradients)来重权(Re-Weight)所有的参数值;第二部分是当前最新的参数值尽可能不偏差之前所有的参数值;第三个部分则是希望当前的参数值能够有稀疏的解(通过L1来直接约束)。从这三个部分的目标函数来看,这个算法既能让参数的变化符合数据规律(从梯度来控制),也能让参数不至于偏离过去已有的数值,从而整个参数不会随着一些异常的数据点而发生剧烈变化。
|
||||
|
||||
在算法上另外一个比较新颖的地方,就是对每一个特征维度的学习速率都有一个动态的自动调整。传统的随机梯度下降(Stochastic Gradient Descent)算法或是简单的在线逻辑回归都没有这样的能力,造成了传统的算法需要花很长时间来手工调学习速率等参数。
|
||||
|
||||
同时,因为每一个特征维度上特征数值的差异,造成了没法对所有特征选取统一的学习速率。而FTRL带来的则是对每一个维度特征的动态学习速率,一举解决了手动调整学习算法的学习速率问题。简单说来,学习速率就是根据每一个维度目前所有梯度的平方和的倒数进行调整,这个平方和越大,则学习速率越慢。
|
||||
|
||||
## 系统调优工程
|
||||
|
||||
很明显,光有一个比较优化的在线逻辑回归算法,依然很难得到最好的效果,还会有很多细小的系统调优过程。
|
||||
|
||||
比如文章介绍了利用**布隆过滤器**(Bloom Filter)的方法,来动态决定某一个特征是否需要加入到模型中。虽然这样的方法是概率性的,意思是说,某一个特征即便可能小于某一个值,也有可能被错误加入,但是发生这样事件的概率是比较小的。通过布隆过滤器调优之后,模型的AUC仅仅降低了0.008%,但是内存的消耗却减少了60%之多,可见很多特征仅仅存在于少量的数据中。
|
||||
|
||||
文章还介绍了一系列的方法来减少内存的消耗。比如利用更加紧凑的存储格式,而不是简单的32位或者64位的浮点数存储。作者们利用了一种叫q2.13的格式,更加紧凑地存储节省了另外75%的内存空间。
|
||||
|
||||
此外,前面我们提到的计算每一步FTRL更新的时候,原则上都需要存储过去所有的梯度信息以及梯度的平方和的信息。文章介绍了一种非常粗略的估计形式,使得这些信息可以不必完全存储,让内存的消耗进一步降低。这部分内容可能并非对所有读者都有益处,然而我们可以看到的是,Google的工程师为了把一种算法应用到实际中做出了非常多的努力。
|
||||
|
||||
另外,文章也特别提出,虽然大家都知道在点击率预估这样非常不对称的问题上(也就是正例会远远少于负例)需要对负样本进行采样,但是这里面需要注意的是直接采样会对参数的估计带来偏差。同时文章也提出了需要对模型的最后预测进行调整(Calibration),使得模型的输出可以和历史的真实点击率分布相近。这一点对于利用点击率来进行计费显得尤为重要,因为有可能因为系统性的偏差,预测的数值整体高出或者整体低于历史观测值,从而对广告主过多计费或者过少计费。
|
||||
|
||||
## 失败的实验
|
||||
|
||||
这篇文章难能宝贵之处是不仅介绍了成功的经验,还介绍了一些失败的或者是不怎么成功的实验结果,让后来的学者和工程师能够少走弯路。
|
||||
|
||||
比如著名的**Hashing Trick**,在这篇文章里,Google的工程师们经过实验发现,特征经过哈希之后并没有显著降低内存而且模型的精准度有所下降,同时哈希也让模型变得不可解释,于是Google的工程师觉得没有必要对特征进行哈希。
|
||||
|
||||
另外一个热门的技术**Dropout**也被作者们尝试了,在Google的实验数据上并没有显著的效果。还有一个经常见到的技术,那就是对学到的参数进行归一化(Normalization),这是让参数能够在一定的范围内不随便波动。遗憾的是,Google的作者们也发现这个技术没有太大作用,模型的效果经常还会降低。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我为你分享了这篇关于广告点击率预估的重要论文,你需要理解的核心要点有几个,一是FTRL模型的创新;二是这个模型如何应用到工业界的环境中特别是如何对内存的消耗进行调优;三是Google一系列失败尝试的总结。
|
||||
|
||||
总之,这篇论文是难得一见的工业界级别的科技论文分享。从KDD工业组的角度来说,很有借鉴意义;从业界贡献来说,除了广告之外,FTRL也被广泛应用到推荐系统等领域。
|
||||
|
||||
最后,我们再来探讨个问题。假设你在负责公司的广告系统,那你应该如何判断自己的场景是不是应该使用FTRL呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
|
53
极客时间专栏/geek/AI技术内参/广告系统核心技术/083 | Facebook的广告点击率预估模型.md
Normal file
53
极客时间专栏/geek/AI技术内参/广告系统核心技术/083 | Facebook的广告点击率预估模型.md
Normal file
@ -0,0 +1,53 @@
|
||||
<audio id="audio" title="083 | Facebook的广告点击率预估模型" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/fe/b1/fef1a31dc0a23e7d320e63fc815530b1.mp3"></audio>
|
||||
|
||||
上一篇文章我们讲了整个计算广告领域最核心的一个问题:广告回馈预估。广告回馈预估,就是预测“用户与广告的交互以及达成交易这种行为”的概率,也就是点击率预估和转化率预估。广告回馈预估存在着数据稀疏等难点和挑战,目前在这个领域比较流行的模型有对数几率回归和数模型等。
|
||||
|
||||
今天,我们就来看一个广告回馈预估的实例:**Facebook的广告点击率预估**。我们会结合2014年发表的一篇论文《Facebook的广告点击率预估实践经验》(Practical Lessons from Predicting Clicks on Ads at Facebook)来进行分析[1]。
|
||||
|
||||
## Facebook广告预估
|
||||
|
||||
Facebook的广告不是我们之前介绍过的搜索广告或者展示广告的简单应用,而是**社交广告**。可以说,社交广告是最近10年慢慢崛起的一种新的广告类型。在论文发表的时候,也就是2014年,Facebook有7.5亿“日活跃用户”(Daily Active Users)和超过1百万的广告商,这个数字在当时是相当惊人的。而今天,在Facebook上活跃的大约有14.5亿用户和5百万广告商。因此,广告系统所需要应对的规模是成倍增加的。
|
||||
|
||||
我们说Facebook的广告是社交广告,也就是说,这些广告不依赖于用户输入的搜索关键词。从Facebook的角度来说,广告商在其平台上投放广告的巨大优势,在于能够精准地根据用户的地理位置、年龄、性别等重要信息进行有针对性的投放,因此这些信息能够帮助平台选择什么样的广告适合什么样的人群。那这里的难点就是,对于某一个人群来说,可能符合的广告数量是巨大的,这对广告的回馈预估以及整个系统都是一个不小的挑战。
|
||||
|
||||
## 广告点击率的评估
|
||||
|
||||
在我们详细解释Facebook点击率系统的一些核心组件之前,我们首先来看一看Facebook的研究人员是怎么评测他们的系统的。
|
||||
|
||||
我们之前提到过,广告系统中的一个巨大挑战就是数据的不均衡。负例,也就是用户没有点击过的广告非常多;而正例,也就是点击过的广告相对比较少。这个比例,根据不同的广告系统会不太一样,但是大体说来,负例与正例的比大概是10:1、100:1甚至1000:1。
|
||||
|
||||
在这样的情况下,如果把点击率预估当做是一个分类问题,按照一般分类问题的评价标准,例如准确率,我们只要预测绝大多数,甚至是全部的实例为负例,那么就可以取得很高的准确率。因此,单独看准确率并不是一个很好的评测标准。
|
||||
|
||||
这个时候,一个比较通行的评测不均衡数据分类问题的指标是“**曲线下面积**”,或者简称为 **AUC**,这个评测办法可以算是一种替代方法。简单来说,AUC就是看我们是不是能够把正例给排序到负例上面。也就是说,如果每一个正例和负例都有一个预测数值,那么我们按照这个数值排序,去数每一个正例下面有多少负例,然后对所有正例所对应的数取平均。**AUC的数值高,则代表我们可以把绝大多数正例排序到负例前面**。
|
||||
|
||||
当然,AUC也不是万能的。AUC的一个最大问题就是它并不在乎所有实例的绝对预测数值,而只在乎它们的相对位置。这在广告系统中可以说是一个非常大的缺陷。我们之前也提过,有很多广告系统组件依赖于对于广告点击率的精确预估,比如收费系统,流量预测等。因此,仅有一个相对位置的正确是不够的。
|
||||
|
||||
在这篇论文中,Facebook团队提到了一个概念叫“**归一化的交叉熵**”,简称 **NE**,用于衡量广告系统的好坏。NE实际上是一个比值,比值的分母是数据中观测到的实际的点击率的数值,也可以叫作数据的“背景估计”(Background Estimation);而分子是某一个模型对点击率的估计。这样做的归一化,目的就是来看,在去除了背景估计的情况下,对点击率的估计是否依然好或者坏。
|
||||
|
||||
## 层次化的点击率预估模型
|
||||
|
||||
Facebook的研究人员在这篇论文中提出的点击率预估模型分为两个层次。也就是说,从最初的模型特性输入,需要经过两个不同的模型才对点击率做出最终的预测。这个两层架构对后来的很多点击率预估模型有巨大的影响。
|
||||
|
||||
我们首先来看第一层模型,这里的输入是最初的特性,其中连续数值的特性已经被转换成了**离散的数值**。然后,这些离散的数值经过了一个**GBDT树**来进行特性转换。这里为什么会用GBDT呢?主要有两层意义。
|
||||
|
||||
第一,GBDT可以对特性进行非线性组合。也就是说,GBDT的输出一定是之前特性的非线性的转换,这是由树模型原本的性质所带来的,这个性质对于线性模型来说会有巨大的优势。
|
||||
|
||||
第二,经过GBDT转换之后,树模型其实选择出了对目标有用的特性,因此这里还起到一个“特性筛选”(Feature Selection)的作用。也就是说,经过GBDT的模型,最后剩下的特性肯定是要远小于最初的输入特性的,毕竟有作用的特性是少数的。
|
||||
|
||||
在经过了GBDT之后,Facebook的研究者用树模型最后的叶节点当做新的特性,然后再学习了一个**线性的分类模型**。这里的思想其实和后来流行的深度学习的想法很类似,也就是先对输入特性进行非线性转换,然后再经过一个线性分类器来进行最后的预测。这个第二层的线性分类器可以用类似SGD的方法进行“在线学习”(Online Learning)。因此,学习到这样一个模型就相对比较容易。
|
||||
|
||||
在论文的实验中,作者们不仅展示了两层模型的优势,并且还讨论了很多选取特性方面的经验以及训练模型的经验,比如广告过去的历史信息非常重要,而绝大多数重要的特性都和历史信息有关。
|
||||
|
||||
## 总结
|
||||
|
||||
今天我为你介绍了Facebook的广告点击率预估的核心算法。一起来回顾下要点:第一,Facebook的广告是社交广告,有其自身的特点和难点;第二,Facebook对广告进行评测的指标主要有AUC和NE;第三,Facebook提出了两层模型的架构,其主要思想是先经过GBDT来进行特性转化,再经过一个线性分类器进行最后的预测。
|
||||
|
||||
最后,给你留一个思考题,对于两层架构来说,除了模型性能上的优势以外,在训练的方便程度上,这样的架构还没有什么优势或者劣势呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
**参考文献**
|
||||
|
||||
1. Xinran He, Junfeng Pan, Ou Jin, Tianbing Xu, Bo Liu, Tao Xu, Yanxin Shi, Antoine Atallah, Ralf Herbrich, Stuart Bowers, and Joaquin Quiñonero Candela. Practical Lessons from Predicting Clicks on Ads at Facebook. Proceedings of the Eighth International Workshop on Data Mining for Online Advertising (ADKDD’14). ACM, New York, NY, USA, , Article 5 , 9 pages, 2014.
|
||||
|
||||
|
59
极客时间专栏/geek/AI技术内参/广告系统核心技术/084 | 雅虎的广告点击率预估模型.md
Normal file
59
极客时间专栏/geek/AI技术内参/广告系统核心技术/084 | 雅虎的广告点击率预估模型.md
Normal file
@ -0,0 +1,59 @@
|
||||
<audio id="audio" title="084 | 雅虎的广告点击率预估模型" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/78/a9/78450cebccacf76f8b0ae03726be21a9.mp3"></audio>
|
||||
|
||||
在上一篇文章中,我和你分享了广告回馈预估的一个实例:Facebook的广告点击率预估。我们重点讲了Facebook利用“归一化的交叉熵”这个概念,来衡量广告模型的精准度。另外,我们还分享了Facebook提出的两层模型架构,也就是一开始先利用树模型GBDT来对特性进行非线性转换和选择,然后再利用一个在线学习的线性模型进行最后的预测,这个架构后来得到了很多研究者和实践者的追随。
|
||||
|
||||
今天,我们来分析另外一个经典的公司实例:**雅虎的广告点击率预估模型**。
|
||||
|
||||
## 雅虎早期的广告预估模型
|
||||
|
||||
对于曾经的互联网巨头雅虎来说,广告系统很早就成为其重要的经济支柱。早在2007年甚至更早的时候,雅虎的工程师和研究人员就投入到了对广告点击率预估的研究中。这方面的几个代表作我列在了文末的参考文献([1]、[2]和[3])里,如感兴趣,你可以进一步学习。
|
||||
|
||||
我们在这里集中讲讲这些工作的核心思路。
|
||||
|
||||
首先,雅虎的研发人员很早就注意到了广告预估模型的一个难点,那就是**数据的稀疏性**。这些具有稀疏性的数据带来了一个后果,那就是**模型对很多“罕见事件”(Rare Events)的估计会出现很大的不稳定性**。
|
||||
|
||||
我们来举个例子,如果广告1在纽约地区展示了1万次,点击200次,点击率是0.02;而广告2在旧金山地区展示了1百次,点击3次,点击率是0.03,在这样的情况下,我们能判断广告2就一定比广告1有更高的点击率吗?这里面至少有两个因素需要考虑。
|
||||
|
||||
第一,**上下文的特性信息非常重要**。这两个广告可能是类型不同,可能展示的地区不同,因此并不能完全直接来对这两个广告进行比较。第二,广告2在旧金山地区的展示次数还比较少,因此0.03这个**预估值**可能是非常不准确的,或者说至少是**不稳定的**,它的误差要大于第一个广告。
|
||||
|
||||
这个时候,研发人员就会思考,如何对这些广告的预估进行更加精确的处理呢?
|
||||
|
||||
在这样的背景下,雅虎的研发人员提出了一个点击率估计方法,其实也是一种**两层模型**。第一层模型就是最原始的对点击率的估计,也就是类似我们上面所说的直接按照数据进行估计。当然,这里的问题我们刚才也已经提到了,就是估计的不稳定性。第二层模型是对第一层模型的修正。所谓修正,就是**利用层次化信息来对原始的估计值进行“平滑”**(Smoothing)。
|
||||
|
||||
什么是层次化信息呢?我们举例来说明。比如,两个广告来自于同一个广告商,因此它们应该有一定的类似的点击率;两个广告被展示到同一个地区,它们也应该有一定的类似的点击率。这些层次信息给了我们一些启示,来对原始估计值进行修正。当然,根据我们这两个例子你就可以看出,一个广告可以受到多个层次信息的影响,比如广告商的层次信息,地理位置的层次信息,以及类别的层次信息等。所以,要想设计一套完善的基于层次信息的平滑方案也并非易事。
|
||||
|
||||
这个时期,雅虎在这方面的工作都围绕着一个主题,那就是**如何对平滑方案进行创新**。一种方法是利用“产生式模型”(Generative Model)的概念,把层次信息的叶子节点的数据产生过程,定义为基于其父节点数据的一个概率分布产生过程,从而把整个平滑方案的问题转换成为了一个有向无环图上的每个节点的后验概率参数的估计问题(参考文献[1]和[2])。另外一种方法则采取了一个不太一样的思路,那就是在做平滑的时候,在这种产生式建模之后,还追加了一个过程,利用树模型来对平滑的结果进行再次修正,使得最后的结果能够达到更高的精度(参考文献[3])。
|
||||
|
||||
这一系列工作虽然在概念上有很高的学术和实践价值,特别是如何利用层次性信息来对预测进行平滑这个方面,但是从整体来说,预估方案变得非常复杂而且环节太多。
|
||||
|
||||
## 雅虎后期的广告预估模型
|
||||
|
||||
雅虎后期的广告预估模型又从比较复杂的两层模式转换为了**一层模式**。这个转换主要是考虑到了整个流水线(Pipeline)的复杂度以及需要处理的数据规模逐渐变大,那么利用更加稳定和简单的方法就势在必行了。
|
||||
|
||||
对于雅虎后期的广告预估模型,我参考论文《简单和可扩展的展示广告响应预测》(Simple and Scalable Response Prediction for Display Advertising)[4],在这里为你简单做一个总结。
|
||||
|
||||
总体来说,整个模型回到了相对简单的“**对数几率回归**”(Logistic Regression),并且直接对所有的特性(Feature)进行建模。这里面唯一可能和之前的很多工作不太一样的地方,是大量使用了“**特性哈希**”(Feature Hashing)的方法。简单来说,特性哈希就是把原来大规模的有可能是极其稀疏的特性给压缩到了一个固定维度的特性空间里。当然,这肯定会对精度等性能有一定影响,因此这是一个需要有一定取舍的决策。
|
||||
|
||||
在这篇论文中,作者们还介绍了如何对大量的数据进行采样,以及如何利用配对的特性(也就是把两种不同的特性,比如广告商和地理位置进行配对)来自动产生更多的非线性因素的方法。
|
||||
|
||||
那么这个一层模式的方法所达到的效果怎样呢?论文中论述,相比于之前的两层结构,这个方法所达到的效果有很大程度的提升。
|
||||
|
||||
## 总结
|
||||
|
||||
今天我为你介绍了雅虎的广告点击率预估模型。一起来回顾下要点:第一,雅虎早期的广告预估算法,其重点放在了一种两层模型架构上;第二,雅虎广告预估后期的一些思路,重点则放在了回归到一种更加简单的架构上。
|
||||
|
||||
最后,给你留一个思考题,如何在对数几率回归这样的线性模型中引入层次化的平滑思路呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
**参考文献**
|
||||
|
||||
1. Deepak Agarwal, Andrei Zary Broder, Deepayan Chakrabarti, Dejan Diklic, Vanja Josifovski, and Mayssam Sayyadian. Estimating rates of rare events at multiple resolutions. Proceedings of the 13th ACM SIGKDD international conference on Knowledge discovery and data mining (KDD '07). ACM, New York, NY, USA, 16-25, 2007.
|
||||
|
||||
2. Deepak Agarwal, Rahul Agrawal, Rajiv Khanna, and Nagaraj Kota. Estimating rates of rare events with multiple hierarchies through scalable log-linear models. Proceedings of the 16th ACM SIGKDD international conference on Knowledge discovery and data mining (KDD '10). ACM, New York, NY, USA, 213-222, 2010.
|
||||
|
||||
3. Nagaraj Kota and Deepak Agarwal.Temporal multi-hierarchy smoothing for estimating rates of rare events. Proceedings of the 17th ACM SIGKDD international conference on Knowledge discovery and data mining (KDD '11). ACM, New York, NY, USA, 1361-1369, 2011.
|
||||
|
||||
4. Olivier Chapelle, Eren Manavoglu, and Romer Rosales. Simple and Scalable Response Prediction for Display Advertising. ACM Trans. Intell. Syst. Technol. 5, 4, Article 61 (December 2014), 34 pages, 2014.
|
||||
|
||||
|
51
极客时间专栏/geek/AI技术内参/广告系统核心技术/085 | LinkedIn的广告点击率预估模型.md
Normal file
51
极客时间专栏/geek/AI技术内参/广告系统核心技术/085 | LinkedIn的广告点击率预估模型.md
Normal file
@ -0,0 +1,51 @@
|
||||
<audio id="audio" title="085 | LinkedIn的广告点击率预估模型" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b6/e7/b665f3ea503a107f705ec1bb30b7d6e7.mp3"></audio>
|
||||
|
||||
上一篇文章我们讲了雅虎的广告预估模型。雅虎早期的模型主要集中在如何利用两轮架构来对点击率进行精确建模,后期的模型回归到了比较传统的利用线性模型外加特性哈希来进行大规模点击率预估的模式。
|
||||
|
||||
今天,我们继续来做公司的案例分析,结合论文《LASER:在线广告的可扩展响应预测平台》(LASER: a scalable response prediction platform for online advertising)[1],来了解LinkedIn这家公司是怎么来做最基本的广告预估的。
|
||||
|
||||
## LinkedIn广告预估模型
|
||||
|
||||
我们首先来看一看LinkedIn的广告预估模型。这个模型的一大“卖点”就是直接充分考虑了“冷启动”和“热启动”两种模式。
|
||||
|
||||
那么,什么是“冷启动”,什么又是“热启动”呢?
|
||||
|
||||
从我们之前的分享中可以看出,很多点击率预估的模型,都强烈依赖于对用户过去信息以及对广告过去表现的建模。比如刚刚讲过的雅虎预估模型,在它早期的模式中就已经见到了这种信息的作用。
|
||||
|
||||
然而,当我们出现新用户或者新广告时,就会有“冷启动”的问题。也就是说,“冷启动”主要是针对新用户或者新广告而言的。这时候基于历史信息的特性都无法起作用了,一般来说需要有专门的处理。
|
||||
|
||||
相对于“冷启动”,“热启动”指的是我们已经掌握了用户或者广告的一定信息,然后利用这些历史信息来对点击率进行预测。
|
||||
|
||||
这么说来,我们一般需要有两套对策,一套针对“冷启动”,一套针对“热启动”。LinkedIn的方法就是**希望通过一个模型来同时解决这两个问题**。
|
||||
|
||||
具体来说,LinkedIn把对点击率的预估拆成了三个部分。
|
||||
|
||||
**第一部分,是利用用户、广告和上下文所建立的全局性预测**。什么意思呢?就是我们利用用户特性、广告特性以及上下文特性来对点击率进行预测。这部分的核心思路就是**这些特性所对应的系数是全局性的**。也就是说,对于不同的用户、不同的广告以及不同的上下文所对应的系数是相同的。因为是全局性的系数,因此这部分其实提供了一种“冷启动”的需求,也就是不管是任何新的用户或是广告,只要有一定的特性,我们总能通过这部分得到一种粗略的估计。
|
||||
|
||||
**第二部分,是利用第一部分的用户、广告和上下文信息组成交叉特性,从而学习这些特性之间的关系**。如果说第一部分直接就是线性的预测,那么第二部分其实就是“交叉项”形成的非线性的部分。我们之前在讲推荐系统的时候提到过“分解机”(Factorization Machines)这个模型,讲到过这种“交叉项”所带来的非线性预测的好处。虽然这里和分解机的构成不完全一样,但是整体上表达了相似的意思。
|
||||
|
||||
**第三部分,是LinkedIn模型提出来的独特之处(和其他公司模型不太一样的地方)**。那就是同样是利用用户、广告和上下文特性,但是LinkedIn所提模型的系数则是每个用户、广告和上下文都不同。作者们认为这可以实现“热启动”效果。也就是说,当某个用户、某个广告或者某个上下文已经有比较多的数据以后,就可以依靠这些用户、广告或者上下文自己的系数了,而不仅仅依靠第一部分的全局系数。这个第三部分只有当数据比较多的时候才能够起作用。
|
||||
|
||||
## 模型的其他特性
|
||||
|
||||
这个模型在增加了这些系数的先验概率信息之后变得相对比较复杂。这篇论文介绍了一系列的模型训练思路,都是不错的可以借鉴的工业界经验。
|
||||
|
||||
首先,作者们认为,刚才模型中所说的**三个部分所需要的模型更新频率是不一样的**。比如第一部分和第二部分都可以认为是全局模型,也就是说系数是全局性的。因此这些模型的变化会比较慢,作者们建议一个星期对模型进行一次更新。而第三部分则是在已经积累了历史信息后慢慢呈现出的效果,因此对于数据会非常敏感,而且每个用户和每个广告都是不同的系数,因此需要在短时间内,比如半个小时甚至几分钟内,就重新训练模型,以达到个性化的目的。
|
||||
|
||||
其次,作者们还把**提出的模型和EE(Exploit & Explore)策略结合了起来**。我们在讲推荐系统时介绍过EE的思路,简单回顾一下EE的目的,主要就是探索那些并没有太多机会被展示的物品,在这里也就是广告。我们刚才说了,所有的系数都加上了先验概率,因此其实可以很容易结合数据计算**后验概率分布**。有了后验概率分布,作者们提出了**以汤普森采样为主的EE模式**。这也可以算是论文提出模型的一大亮点。
|
||||
|
||||
最后我们提一下这个模型的训练算法。因为要在大规模的数据上对模型进行训练,这篇文章采用了一种**ADMM算法**。在文章提出来的时候,作者们还是希望能够利用单个服务器对所有的模型参数进行训练。和其他的算法相比,一般认为ADMM这种算法的收敛速度更快,但是,利用这种算法的其他公司并不太多。
|
||||
|
||||
## 总结
|
||||
|
||||
今天我为你介绍了LinkedIn广告点击率预估的核心算法。一起来回顾下要点:第一,我们讲了LinkedIn把点击率预估分为三个部分,从而分别解决“冷启动”和“热启动”的思路;第二,我们聊了如何更加有效地对这个提出的模型进行训练学习。
|
||||
|
||||
最后,给你留一个思考题,回顾我们讲过的推荐系统模块,我们还介绍过什么方法可以结合“冷启动”和“热启动”呢?
|
||||
|
||||
欢迎你给我留言,和我一起讨论。
|
||||
|
||||
**参考文献**
|
||||
|
||||
1. Deepak Agarwal, Bo Long, Jonathan Traupman, Doris Xin, and Liang Zhang. LASER: a scalable response prediction platform for online advertising. Proceedings of the 7th ACM international conference on Web search and data mining (WSDM '14). ACM, New York, NY, USA, 173-182, 2014.
|
||||
|
||||
|
55
极客时间专栏/geek/AI技术内参/广告系统核心技术/086 | Twitter的广告点击率预估模型.md
Normal file
55
极客时间专栏/geek/AI技术内参/广告系统核心技术/086 | Twitter的广告点击率预估模型.md
Normal file
@ -0,0 +1,55 @@
|
||||
<audio id="audio" title="086 | Twitter的广告点击率预估模型" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/37/5a/3754416daa48e31a146625e0aef1765a.mp3"></audio>
|
||||
|
||||
在上一篇文章的分享里,我们了解了LinkedIn这家公司是怎么来做最基本的广告预估的。LinkedIn的广告预估模型分为三部分,这里面的核心思想是直接对“冷启动”和“热启动”进行建模,外加和EE策略(Exploit & Explore)结合在一起,从而提升了效果。
|
||||
|
||||
今天,我们就结合论文《Twitter时间轴上的广告点击率预估》(Click-through Prediction for Advertising in Twitter Timeline)[1],来看看Twitter的广告预估到底是怎么做的。
|
||||
|
||||
## Twitter的广告预估
|
||||
|
||||
我们前面提到过最基本的广告形态分类,可以分为“搜索广告”和“展示广告”。当计算广告在互联网上出现以后,这两种广告形态就迅速被很多网站和服务商所采纳。
|
||||
|
||||
在最近的10年里,随着社交媒体的发展,希望在社交媒体的**用户信息流**里投放广告的需求逐渐增强。我们之前谈到的Facebook的案例,其实也是往用户的信息流中插入广告。很多类似的社交媒体都争先恐后地开始进行相似的项目,这一类广告经常被称为**社交广告**。
|
||||
|
||||
社交广告的特点是,需要根据用户的社交圈子以及这些社交圈所产生的内容,而动态产生广告的内容。广告商和社交媒体平台都相信,不管是在投放的精准度上,还是在相关性上,社交广告都有极大的可能要强过搜索广告和展示广告。毕竟,在社交媒体上,用户有相当多的信息,例如年龄、性别,甚至在哪里工作、在哪里上学等,这些信息都有助于广告商的精准投放。而用户自己在社交媒体上追踪的各种信息,又可以让广告商清晰地知道用户的喜好。
|
||||
|
||||
Twitter的工程师们在这篇论文里介绍的也是在信息流里投放的社交广告。只不过,Twitter的工程师们认为,我们之前分享的Facebook的解决方案,并没有真正考虑往信息流里插入广告的难点,也就是**广告的排序**,依然把广告的排序问题当做分类问题,也就是用对数几率回归(Logistic Regression)来解决。
|
||||
|
||||
另外,Twitter的工程师们认为,**社交广告比类似Google的搜索广告更具挑战性**。因为在社交信息流里,用户所看到的信息都是随时变化的,比如用户在Twitter中,可能随时有新的信息进入到信息流中,因此信息流的上下文会随时发生变化。那么,如果要投放和上下文相关的广告,这种变化无疑会带来前所未有的挑战。
|
||||
|
||||
## 利用排序学习来对广告排序
|
||||
|
||||
既然Twitter的工程师们认为,信息流广告的建模最重要的就是借鉴排序学习的办法。那么,我们就来看一看他们是怎么利用排序学习来为信息流社交广告建模的。
|
||||
|
||||
首先,排序学习中最基本的就是“**单点法**”(Pointwise)排序学习。回顾一下,单点法其实就是把排序学习的任务转化为分类问题。其实典型的就是直接利用“支持向量机”(SVM)或者对数几率回归模型。
|
||||
|
||||
第二种比较常用的排序学习的方法就是“**配对法**”(Pairwise)排序学习。通俗地讲,配对法排序学习的核心就是学习哪些广告需要排到哪些广告之前。这种二元关系是根据一组一组的配对来体现的。学习的算法,主要是看能否正确学习这些配对的关系,从而实现整个排序正确的目的。对于配对法排序,我们依然可以使用对数几率回归。只是这个时候,我们针对的正负示例变成了某个广告比某个广告排名靠前,或者靠后。
|
||||
|
||||
值得一提的是,通过配对法学习排序学习,对于一般的搜索结果来说,得到最后的排序结果以后就可以了。而对于广告来说,我们还需要**对点击率进行准确的预测**。这个我们之前提到过。于是在这篇文章中专门提到了如何从配对结果到点击率的预测。
|
||||
|
||||
具体来说,原理其实很简单,根据配对法学习排序完成以后的广告之间顺序是绝对的,但是绝对的数值可能是不太精确的。这里进行校准的目的是根据配对法产生的预测值,再去尽可能准确地转换为实际的点击率的数值。一般来说,这里就可以再使用一次对数几率回归。也就是说,这个模型的唯一特性就是配对法产生的预测数值,然后模型的目的是去估计或者说是预测最后的实际数值。**这种使用一个回归模型来进行校准的方法,也用在比如要将支持向量机的结果转换成概率结果这一应用上**。
|
||||
|
||||
虽然从原理上讲,先有一个配对模型进行排序,然后再有一个校准模型对模型的绝对估计值进行重新校正,这是很自然的。但是在实际的工业级应用中,这意味着需要训练两个模型,那无疑就变成了比较繁复的步骤。
|
||||
|
||||
所以,在这篇文章里,作者们想到了一种结合的办法,那就是**结合单点法和配对法**。具体来说,就是直接把两者的目标函数串联在一起。这样做的好处是,可以直接用现在已经有的训练方法,而且同时解决了排序和更加准确预测点击率的问题。我们回顾一下,单点法的特性是尽可能准确地预测每一个广告的点击率,也就是刚才提到的校准的这一个步骤所需要干的事情。这种直接串联的好处是,只需要学习一个模型就可以做到既考虑了排序,又考虑了预测的绝对精准度的问题。
|
||||
|
||||
**在机器学习应用中,串联多个目标函数是经常使用的一种技术**。其目的和作用也就和这个串联的想法一样,就是希望针对多个不同的目标进行优化。一般来说,这里面的核心是,多个串联的目标函数需要**共享模型参数**才能形成有关联的总的大的目标函数;如果没有共享参数,那就仅仅是一种形式上的串联。
|
||||
|
||||
## 模型的实验
|
||||
|
||||
在这篇文章里,作者们也是用了Facebook提出的“归一化的交叉熵”,简称NE的概念以及业界比较常用的AUC来针对模型进行线下评价。
|
||||
|
||||
在线下实验中,配对法以及单点法和配对法结合的混合方法都在AUC上要超过单点法本身。这非常容易理解。只是配对法针对单点法在NE上的表现要差很多。这和我们刚才所说的没有对点击率进行估计有很大关系。这一点也在实验中得到了证实。
|
||||
|
||||
在在线实验中,单点法相对于以前的自然排序,点击率好了将近14%,而混合法则好了26%左右。可以说效果非常明显。
|
||||
|
||||
## 总结
|
||||
|
||||
今天我为你介绍了Twitter广告点击率预估的核心算法。一起来回顾下要点:第一,我们讲了Twitter认为社交广告的难点是要解决广告的排序问题;第二,我们聊了如何利用排序学习来为点击率预估进行效果提升。
|
||||
|
||||
最后,给你留一个思考题,为什么Twitter不尝试使用树模型来对点击率进行提升呢?
|
||||
|
||||
**参考文献**
|
||||
|
||||
1. Cheng Li, Yue Lu, Qiaozhu Mei, Dong Wang, and Sandeep Pandey. Click-through Prediction for Advertising in Twitter Timeline. Proceedings of the 21th ACM SIGKDD International Conference on Knowledge Discovery and Data Mining (KDD '15). ACM, New York, NY, USA, 1959-1968, 2015.
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user