first commit

This commit is contained in:
张乾
2024-10-15 21:07:49 +08:00
parent 58cbf6795b
commit 1b0c35dd30
115 changed files with 6918 additions and 21 deletions

View File

@ -0,0 +1,92 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
00 开篇词 程序员解决的问题,大多不是程序问题
你好!我是郑晔,一个程序员。
很多人都说,程序员很辛苦,与这个角色联系在一起的词儿,通常是忙碌、加班、熬夜等。
作为程序员,我们将其看作一个值得全情投入的职业,希望能够把精力放在设计算法、改进设计、优化系统这些具有创造性与成就感的本职工作上。
但现实情况却是,许多人因为一些“意外”,陷入了无休止的忙碌,比如:
你辛辛苦苦写的代码还没上线,产品经理就告诉你需求变了;
你拼命加班只因错估了工作量,自己造的“孽”,含着泪也要搞定;
你累死累活做出来的东西和要求不符,只能从头再来;
你大面积地修改代码只是因为设计糟糕,无法适应新的需求变化;
……
诸如此类,不胜枚举。我们很辛苦,但耗费我们大量时间和精力去应付的工作,并不是技术工作,反而是这些看似很“不值当”的事儿。
为什么会这样?
软件行业里有一本名著叫《人月神话》其中提到两个非常重要的概念本质复杂度Essential Complexity和偶然复杂度Accident Complexity
简单来说,本质复杂度就是解决一个问题时,无论怎么做都必须要做的事,而偶然复杂度是因为选用的做事方法不当,而导致要多做的事。
比如你要做一个网站,网站的内容是你无论如何都要写的,这就是“本质复杂度”。而如果今天你还在用汇编写一个网站,效率是不可能高起来的,因为你选错了工具。这类选错方法或工具而引发的问题就是“偶然复杂度”。
作为一个在软件行业奋斗了近二十年的程序员,我深刻意识到一个遗憾的事实:大部分程序员忙碌解决的问题,都不是程序问题,而是由偶然复杂度导致的问题。
换句话说,只要选择了正确的做事方法,减少偶然复杂度带来的工作量,软件开发是可以有条不紊进行的。
如何减少偶然复杂度引发的问题,让软件开发工作有序、高效地进行,这正是我希望通过这个专栏帮你解决的问题。
许多人工作做事主要依靠直觉,在这个科学越发昌明的时代,我们清楚地看到,人类的直觉常常是错的,就像古人凭直觉认为大地是平的一样。
软件开发也不例外,如果你不曾在做软件这件事上有过学习和思考,形成一套高效的工作方法,只是凭直觉行事,在真实世界中往往会举步维艰。
幸运的是总会有不同的人在不同的方向上探索不同的做法一旦通过真实世界的验证就会沉淀出可供行业直接应用的最佳实践Best Practice
在软件行业中,这样能够提升工作效率的最佳实践已经有很多,但是,学习掌握这些最佳实践是有难度的,其根源就在于,很难找到这些实践彼此间的内在联系。
直觉大多是错误的,最佳实践又多而琐碎,所以在这个专栏中,我会尝试给你提供一个思考框架,帮你在遇到问题时梳理自己真正要做的事情。围绕着这个框架,我还会给你一些原则。
这些原则,是我从软件行业的诸多软件开发最佳实践中总结出来的,也是我如今在工作中所坚持的。这些原则就是一条主线,将各种最佳实践贯穿起来。
这些原则不多,总结起来就四个:
以终为始;
任务分解;
沟通反馈;
自动化。
也许看到这四个原则的名字,你会不以为然,这些说法你在很多地方都看到过,但我想与你分享的内容可能与你想的并不完全一致。
比如:你以为的“终”可能不是终,因为你只是站在自己的角度;你以为自己做了任务分解,在我看来,可能还不够,因为我希望你能够做到微操作;你以为的沟通反馈就是说话聊天,我想告诉你很多技术实践的存在也是为了沟通反馈;你以为自动化就是写代码,我会告诉你,有时候不写代码而解决问题,可能才是一个好方案。
在我看来,想要将精力聚焦在本质复杂度上,提高工作效率,摆脱直觉的束缚,只要掌握上面的四个原则就可以了。
或许你此时会问,这些原则很难吧?其实并不难,在探讨这个专栏的内容时,我的编辑作为软件开发的局外人,经常发出感叹:“这事真的就这么简单吗?这不就是正常做事应该有的逻辑吗?”
是的,就是这样简单,但大多数人没有这样做,因为这些原则在实际工作中很可能是反直觉的。只要打破思维误区,你的整个人都会变得不一样。
下面是整个专栏的目录,我希望能帮助你回答,或者厘清一些开发过程中,曾经遇到,又未曾深入的问题。
当我们详谈这些原则时,我会给你讲述一些最佳实践,让你看到这些原则是如何应用于不同的实践中的。希望我对这些实践的理解成为你的知识地图,让你拥有继续探索的方向。
我做这个专栏的原则是“授人以鱼,不如授人以渔”。我希望你很好地理解这些原则,掌握高效工作的方法。至于最佳实践,你可以自行决定,是直接采纳还是曲线救国更为合适。
介绍一下我自己,我是郑晔,目前在火币网担任首席架构师,写过代码、带过团队、做过咨询,创过业,还维护着一个拿过 Oracle Duke 选择奖的开源项目 Moco至今仍然在编程一线写着代码。
很长时间里,我一直对如何做好软件充满了好奇,了解过各种技术以及开发方法。做咨询的经历让我有机会见识到不同公司面临的问题;带团队的时候,我也看到很多小兄弟因为不会工作,虽然很努力却收效甚微;而我自己菜鸟时期的笨拙依然是历历在目。
在我看来,所有做软件的人能力都很强,这些问题都只是因为不会工作造成的,但更可怕的是,许多人深陷泥潭而不自知。
在这些年的工作里,我一遍又一遍给别人讲如何工作,逐渐总结出一套自己的工作原则,如今呈现在你面前的就是我这些年思考的总结。
我不指望所有人都能从这个专栏受益,我只想把这个专栏写给那些愿意成长的人。我只是来做一次信息分享,分享一些思考,分享一些做法,希望可以将你从常见的思维误区中带出来。
也许在这个专栏的最后,你发现自己并不认同我的原则,却能够用自己的原则来与我探讨,那么,恭喜你,因为那是最美妙的事情!

View File

@ -0,0 +1,150 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
01 10x程序员是如何思考的
你好,我是郑晔。
在开篇词中我们提到,程序员在工作中遇到的很多问题,大多不是程序问题,辛苦而低效的工作,多数是由偶然复杂度导致的。那这个由于偶然复杂度造成的差距会有多大呢?
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?(我们如何到达那里?)的问题。
如果说前两个原则是要在动手之前做的分析,那后面两个原则就是在通往目标的道路上,为我们保驾护航,因为在实际工作中,我们少不了与人和机器打交道。
沟通反馈是为了疏通与其他人交互的渠道。一方面,我们保证信息能够传达出去,减少因为理解偏差造成的工作疏漏;另一方面,也要保证我们能够准确接收外部信息,以免因为自我感觉良好,阻碍了进步。
自动化就是将繁琐的工作通过自动化的方式交给机器执行,这是我们程序员本职工作的一部分,我们擅长的是为其他人打造自动化的服务,但自己的工作却应用得不够,这也是我们工作中最值得优化的部分。
这四个原则互相配合,形成了一个对事情的衡量标准。总体上可以保证我的工作是有效的,在明确目标和完成目标的过程中,都可以尽量减少偶然复杂度。
怎么把这四个原则用在工作中呢?我们回过头来看一下前面的场景,产品经理把要做的功能特性摆在我面前。站在以终为始的角度,我需要了解真正的目标是什么,所以,我会关心为什么要做这个特性。为了保证目标是有效的,我会关心它给用户带来的价值。
有了任务分解的视角,我需要将一个大的目标进行拆解,如果我要达成这个目标,整体解决方案是远远不够的,我需要把任务分解成一个一个小的部分。所以,我会关心一个一个具体的使用场景。
一方面,我会了解到更多的细节,另一方面,当时间紧迫的时候,我会和产品经理来谈谈究竟优先实现哪个场景。
为什么要学会沟通反馈?因为我需要明确,自己是否真正理解了产品经理提交的需求。所以,我要不断地问问题,确保自己的理解和产品经理交代的内容一致。
另外,我也需要保证我的产品做出来确实能够达到目标。所以,我会关心它上线后的衡量手段。因为我知道,这个行业里有太多代码上线后,从来没有运行过。
自动化的角度很有意思,我们做的方案通常是一个自动化方案,但我们需要了解这个方案没有自动化之前是怎么做的。如果不自动化,用户会怎么用。所以,我会关心是不是还有其它替换方案,比如,买一个现成的服务。因为很多需求的提出,只是因为我们有了一个开发团队而已。
好,现在你已经对这四个原则在工作中的应用有了一个直观的认识。但你也会发现,我问的这些问题似乎已经“超纲”了,超过了一个普通程序员应该关注的范围。但这就是真实世界,它不像考试一样,有一个标准答案。
我们不是一个人孤独地在工作,而是与其他人在协作,想要做到高效工作,我们就要“抬起头”来,跳出写代码这件事本身。所以,我在开篇词里说,程序员解决的问题,大多不是程序问题。
可能你对这些原则的了解还没过瘾,没关系,这篇文章只是让大家清晰地了解思考框架和原则的背后逻辑。接下来,我会结合行业里的最佳实践,给你进一步讲解这些原则和具体应用。
总结时刻
大多数人工作低效是由于工作中偶然复杂度太多造成的,只要能够更多地将注意力放到本质复杂度上,减少偶然复杂度造成的消耗,我们“真实”的工作效率自然会得到大幅度提升。
而想要减少偶然复杂度的消耗,就要了解一些高效的工作方式和行业的最佳实践,而这一切是可以用统一的框架进行思考的。
运用这个思考框架,我们需要问自己一些问题:
Where are we?(我们现在在哪?)
Where are we going?(我们要到哪儿去?)
How can we get there?(我们如何到达那里?)
为了把这个框架应用在我们程序员的工作中,我给了你四个思考原则:
以终为始,确定好真实目标;
任务分解,找到实施路径;
沟通反馈,解决与人打交道出现的问题;
自动化,解决与机器打交道出现的问题。
如果今天的内容你只能记住一件事,那请记住:面对问题时,用思考框架问问自己,现状、目标和路径。
最后,我想请你思考一下,如果把这个思考框架运用在你的职业发展规划上,你会如何回答这三个问题呢?
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,121 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
05 持续集成:集成本身就是写代码的一个环节
你好,我是郑晔。
上一讲我们探讨了需求的“完成”,你现在知道如何去界定一个需求是否算做完了,这要看它是不是能够满足验收标准,如果没有验收标准,就要先制定验收标准。这一点,对于每一个程序员来说都至关重要。
在今天这一讲中,我们假设需求的验收标准已经制定清楚,接下来作为一个优秀的程序员,你就要撸起袖子准备开始写代码了。
不过在这里,我要问你一个问题:“是不是写完代码,工作就算完成了呢?”你或许会疑惑,难道不是这样吗?那我再问你:“代码是技术团队的交付物吗?”
你是不是发现什么不对劲了。没有人需要这堆文本,人们真正需要的是一个可运行的软件。写代码是程序员的职责,但我们更有义务交付一个可运行的软件。
交付一个可运行的软件,通常不是靠程序员个体奋战就能完成的,它是开发团队协作的结果。我们大多数人都工作在一个团队中,那我们写的代码是不是能够自然而然地就和其他人的代码配合到一起呢?显然没那么简单。
如果想将每个程序员编写的代码很好地组合在一起,我们就必须做一件事:集成。
但是集成这件事情,该谁做,该怎么做呢?我不知道你有没有思考过这个问题。在开始这个话题之前,我先给你讲个故事。
集成之“灾”
2009年我在一个大公司做咨询。对接合作的部门里有很多个小组正在共同研发一个项目。他们工作流程是先开发一个月等到开发阶段告一段落大项目经理再把各个小组最精锐成员调到一起开始集成。对他们来说集成是一件大事难度很大所以要聚集精英来做。
这个项目是用 C 语言编写的,所以,集成的第一步就是编译链接。大家把各个小组写好的程序模块编译到一起,哪个模块有问题,哪个小组的精英就出手解决它。
如果第一天,所有模块能够编译链接到一起,大家就要谢天谢地了。之后才进入到一个正式“联调”的过程。
“联调”的目标,是把一个最基本的流程跑通,这样,集成才算完成。而对他们这个项目来说,“联调”阶段更像是场“灾难”。
为什么?你想想,一个大部门有若干个团队,每个团队都在为同一个项目进行代码开发,周期为一个月。这一个月期间,所有团队的程序模块汇总在一起,体量会非常庞大。那么这些内容中,出现错误需要改动的可能性也就非常大,需要改动的量也就非常大。因此他们集成“联调”所需要的时间也会非常长。
即便他们调动各组精英完成一次项目集成的时间至少也需要23天改动量稍大可能就要一周了。虽然我不知道你所处公司的现状是什么样的但大概率地说你在职业生涯中会遇到过类似的场景。那怎么去解决这个问题呢
迈向持续集成
聪明的你作为旁观者一定会想,在这个故事里,为什么他们要在开发一个月后才做集成呢?为什么不能在开发一周后,甚至是更短的时间内就集成一次?
这是一个行业中常见的痛点,所以,就会有人不断地尝试改进,最先取得的突破是“每日构建”。
1996年Steve McConnel出版了一本著作《Rapid Development》国内译作《快速软件开发》。在这本书中作者首次提出了解决集成问题的优秀实践Daily Build每日构建。通过这个名字我们便不难看出它的集成策略即每天集成一次。
这在当时的人看来,已经是“惊为天人”了。就像上面提到的例子一样,当时的人普遍存在一种错误认知:集成不是一件容易的事,需要精英参与,需要很长时间,如果每天都进行集成,这是想都不敢想的事情。
实际上,每日构建背后的逻辑很简单:既然一段时间累积下来的改动量太过巨大,那一天的时间,累积的改动量就小多了,集成的难度也会随之降低。
你会看到,对比最后做集成和每日构建,这两种不同的做法都是在处理改动量和集成时间的关系。只不过,一个是朝着“长”的方向在努力,一个则瞄准“短”的方向。最后的事实证明,“长”的成了恶性循环,“短”的成了最佳实践。
既然,我们认同了只要增加集成的频率,就可以保证在每次集成时有较少的改动量,从而降低集成难度。
那问题来了?究竟要在开发后多久才进行一次集成呢?是半天、两个小时、还是一个小时呢?倘若这个想法推演到极致,是否就变成了只要有代码提交,就去做集成?
没错,正是基于这样的想法,有人尝试着让开发和集成同时进行,诞生了一个关于集成的全新实践:持续集成。
持续集成一个关键的思维破局是,将原来分成两个阶段的开发与集成合二为一了,也就是一边开发一边集成。
持续集成这个想法固然好,但是不是需要有专人负责盯着大家的工作,只要有人提交了代码,这个负责人就要去集成呢?显然,这在真实工作中是行不通的。
既然是程序员的想法,程序员解决问题的方案自然就是自动化这个过程。于是,有人编写了一个脚本,定期去源码服务器上拉代码,出现程序更新时,就自动完成构建。
后来,人们发现这段脚本与任何具体项目都是无关的。于是,把它进一步整理并发布出来,逐步迭代发展成为今天广为人知的持续集成服务器。
在2000年时“软件行业最会总结的人” Martin Fowler 发布了一篇重量级文章“Continuous Integration”。
之后一年,由 Martin Fowler 所在的 ThoughtWorks 公司发布了市面上第一款持续集成服务器 CruiseControl。CruiseControl 可谓是持续集成服务器的鼻祖,后来市面上的服务器基本都是在它的基础上改良而来的。
Martin Fowler 的重磅文章和首款持续集成服务器的问世,让软件行业对持续集成进行了更为深入的探讨,人们对于持续集成的认知程度一路走高,持续集成服务器成为了开发团队在集成阶段最得心应手的工具。围绕着持续集成的一系列行为准则逐渐成型。
以至于发展到2006年Martin Fowler 不得不重写了“Continuous Integration”这篇文章。之后人们更是以持续集成为基础进一步拓展出持续交付的概念。
人类对工具是有偏爱的,持续集成服务器的发布,将持续集成从一项小众实践逐步发展成为今天行业的“事实”标准。
“地面上”的持续集成
然而,即便持续集成已经发展多年,至今整个行业在对它的应用上,却并未达到同步的状态。有趣的是,有一部分公司虽然还无法实现持续集成,但是因为持续集成服务器的出现,反而可以做到每日构建。
这不难理解,每日构建的概念虽然早早就提出来了,但在那个时期,行业里真正践行每日构建的公司并不多,其根本原因就在于,每日构建最初都是一些指导原则,缺乏工具的支持。而每日构建和持续集成最根本的区别在于构建时机,而这只是持续集成服务器的一个配置选项而已。
当然,行业内有一部分公司已经可以将持续集成运用得得心应手,而也有相当大的一部分人还在为集成而痛苦不堪,比如我前面提到的咨询项目。
这个项目是我在2009年时参与的。也就是说此时距离 Martin Fowler 最初写下“Continuous Integration”已经过去了9年甚至距离这篇文章的更新版发布也已经过去了3年更不要说距离 McConnell 提出“每日构建”已经13年。
即便以当时的时间坐标系来看这个项目的集成实践水平至少落后行业10年以上。没错他们甚至连每日构建都还差很远。
时至今日,持续集成早就是成熟得不能再成熟的实践了。然而,据我所知,许多公司依然处于集成要依赖于“英雄”的蛮荒阶段。
虽然我们在同一个时代写代码做开发,但在技术实践层面,不同的团队却仿佛生活在不同的年代。这也是我们要学习的原因。
也许,目前国内对于持续集成的实践水平还处于较为原始的状态,这是个坏消息。但好消息是,我们可以通过更多的学习,对集成有足够的了解,从而一步到位地进入到最先进的状态中。
无需停留在以精英为核心的集成时代,也可以完全不理会每日构建,我希望你拥有这个时代的集成观,直接开始持续集成。
如果有了持续集成的集成观,我们该怎么看待开发这件事呢?开发和集成就不再是两个独立的过程,而是合二为一成为一体。
基于这样的理解,我们就不能再说代码写完了,就差集成了,因为这不叫开发的完成。一个好的做法是尽早把代码和已有代码集成到一起,而不应该等着所有代码都开发完了,再去做提交。
怎样尽早呢?你需要懂得任务分解,这是我们在之后的“任务分解”主题下会讲到的内容。
总结时刻
在软件开发中,编写代码是很重要的一环,但程序员的交付物并不应该是代码,而是一个可工作的软件。当我们在一个团队中工作的时候,把不同人的代码放在一起,使之成为一个可工作软件的过程就是集成。
在很长一段时间内,集成都是软件行业的难题,改动量和集成时间互相影响。幸运的是,不同的人在不同的方向尝试着改变,结果,同时加大改动量和集成时间的人陷入了泥潭,而调小这两个参数的人看到了曙光。
每日构建作为早期的一种“最佳实践”被提了出来,但因为它基本上都是原则,没有得到广泛的应用。当人们进一步“调小”参数后,诞生了一个更极致的实践:持续集成,也就是每次提交代码都进行集成。
真正让持续集成成为行业最佳实践的是Martin Fowler 的文章以及持续集成服务器。持续集成的思维让我们认识到,开发和集成可以合二为一。我们应该把开发的完成定义为代码已经集成起来,而站在个体的角度,我们应该尽早提交自己的代码,早点开始集成。
如果今天的内容你只能记住一件事,那请记住:尽早提交代码去集成。
最后,我想请你分享一下,在实际工作中,你遇到过哪些由集成带来的困扰?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,117 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
06 精益创业:产品经理不靠谱,你该怎么办?
你好,我是郑晔。
前面谈到验收标准时,我们说的实际上是确定性需求,也就是说,我们已经知道了这个需求要怎么做,就差把它做出来了。而有时候,我们面对的需求却是不确定的,比如,产品经理有了一个新想法,那我们该如何应对呢?
今天,我们从 IT 行业一个极为经典的话题开始:程序员如何面对产品经理。我先给你讲一件发生在我身边的事。
有一次,我们一大群人在一个大会议室里做一个产品设计评审,来自产品团队和技术团队的很多人都参与到这个评审中。一个产品经理正对着自己的设计稿,给大家讲解一个新的产品特性。
这个公司准备将自己的服务变成了一个云服务,允许第三方厂商申请,这个产品经理给大家讲解的就是第三方厂商自行申报开通服务的流程。听完前面基本情况的介绍,我举手问了几个问题。
我:这个服务会有多少人用?-
产品经理:这是给第三方厂商的人用的。-
我:我问的是,这个服务会有多少人用。-
产品经理:每个第三方厂商的申请人都会用。-
我:好,那你有预期会有多少第三方厂商申请呢?-
产品经理:呃,这个……我们没仔细想过。-
我:那现在给第三方厂商开通服务的具体流程是什么。-
产品经理:第三方厂商申请,然后,我们这边开通。-
我:好,这个过程中,现在的难点在哪里?这个审批过程能让我们的工作简化下来吗?-
产品经理:……-
我:那我来告诉你,现在开通第三方厂商服务,最困难的部分是后续开通的部分,有需要配置服务信息的,有需要配置网络信息的。目前,这个部分还没有很好的自动化,前面审批的部分能够自动化,对整个环节优化的影响微乎其微。
我的问题问完了,开发团队的人似乎明白了什么,纷纷表示赞同我的观点。这个审批流程本身的产品设计并不是问题,但我们的时间和资源是有限的,关键在于,要不要在这个时间节点做这个事。准确地说,这是优先级的问题。
此刻,作为开发团队一员的你,或许会有种快感,把产品经理怼回去,简直大快人心。好吧,作为一个正经的专栏,我们并不打算激化产品经理和开发团队的矛盾,而是要探讨如何做事情才是合理的。
之所以我们能很好地回绝了产品经理不恰当的需求,是因为我们问了一些好问题,但更重要的是,我们为什么能问出这些问题。
产品经理是个新职业
在做进一步讨论之前我们必须认清一个可悲的现状IT 行业中大多数人的专业程度是不够的。
IT 行业是一个快速发展的行业,这个行业里有无数的机会,相对于其它行业来说,薪资水平也要高一些,这就驱使大量的人涌入到这个行业。
也因为这是一个快速发展的行业很多职位都是新近才涌现出来的比如在2010年之前很少有专职的前端工程师之前的工程师往往要前后端通吃。
产品经理便是随着创业浪潮才风起云涌的职位。既然这是个“新”职位往往是没有什么行业标准可言的。所以你会看到很多行业乱象很多人想进入IT行业一看程序员需要会写代码觉得门槛高那就从产品经理开始吧这些人对产品经理岗位职责的理解是告诉程序员做什么。
这和郭德纲口中外行人“如何认识相声”是一个道理,以为会说话就能说相声,殊不知,这是个门槛极高的行业。产品经理也一样,没有良好的逻辑性,怎么可能在这个行业中有好的发展。
如果你遇到的产品经理能给出一个自洽的逻辑,那么恭喜你,你遇到了还算不错的产品经理。多说一句,这个行业中专业度不够的程序员也有很多,人数比产品经理还多,道理很简单,因为程序员的数量比产品经理的数量多。
这么说并不是为了黑哪个职位,而是要告诉大家,我们必须要有自己的独立思考,多问几个为什么,尽可能减少掉到“坑”里之后再求救的次数。
回到前面的主题,我们该怎么与产品经理交流呢?答案还在这个部分的主题上,以终为始。我们是要做产品,那就需要倒着思考,这个产品会给谁用,在什么场景下怎么用呢?
这个问题在 IT 行业诞生之初并不是一个显学,因为最初的 IT 行业多是为企业服务的。企业开发的一个特点是,有人有特定的需求。在这种情况下,开发团队只要把需求分析清楚就可以动手做了,在这个阶段,团队中的一个关键角色是业务分析师。即便开发出来的软件并不那么好用,企业中强行推动,最终用户也就用了。
后来,面向个人的应用开始出现。在 PC 时代和早期的互联网时代,软件开发还基本围绕着专业用户的需求,大部分软件只要能解决问题,大家还是会想办法用起来的。
但是随着互联网深入人心,软件开始向各个领域蔓延。越来越多的人进入到 IT 行业,不同的人开始在各个方向上进行尝试。这时候,软件开发的主流由面向确定性问题,逐渐变成了面向不确定性问题。
IT 行业是这样一个有趣的行业,一旦一个问题变成通用问题,就有人尝试总结各种最佳实践,一旦最佳实践积累多了,就会形成一套新的方法论。敏捷开发的方法论就是如此诞生的,这次也不例外。
精益创业
最早成型的面向不确定性创造新事物的方法论是精益创业Lean Startup它是 Eric Ries 最早总结出来的。他在很多地方分享他的理念不断提炼最终在2011年写成一本同名的书《精益创业》。
看到精益创业这个名字大多数人会优先注意到“创业Startup”这个词。虽然这个名字里有“创业”二字但它并不是指导人们创业挣大钱的方法论。正如前面所说它要解决的是面向不确定性创造新事物。
只不过,创业领域是不确定性最强而且又需要创造新事物的一个领域,而只要是面向不确定性在解决问题,精益创业都是一个值得借鉴的方法论。比如,打造一个新的产品。
精益创业里的“精益”Lean是另外一个有趣的词。精益这个词来自精益生产这是由丰田公司的大野耐一和新乡重夫发展出来的一套理论。
这个理论让人们开始理解价值创造与浪费之间的关系。创造价值是每个人都能理解的,但减少浪费却是很多人忽略的。所以,把这几个理念结合起来,精益创业就是在尽可能少浪费的前提下,面向不确定性创造新事物。
那精益创业到底说的是什么呢?其实很简单。我们不是要面向不确定性创造新事物吗?既然是不确定的,那你唯一能做的事情就是“试”。
怎么试呢试就要有试的方法。精益创业的方法论里提出“开发build-测量measure-认知learn”这样一个反馈循环。就是说当你有了一个新的想法idea就把想法开发成产品code投入市场然后收集数据data获取反馈看看前面的想法是不是靠谱。
得到的结果无非是两种好想法继续加强不靠谱的想法丢掉算了。不管是哪种结果你都会产生新的想法再进入到下一个循环里。在这个反馈循环中你所获得的认知是最重要的因为它是经过验证的。在精益创业中这也是一个很重要的概念经过验证的认知Validated Learning
既然是试,既然是不确定这个想法的有效性,最好的办法就是以最低的成本试,达成同样一个目标,尽可能少做事。精益创业提出一个非常重要的概念,最小可行产品,也就是许多人口中的 MVPMinimum Viable Product。简言之少花钱多办事。
许多软件团队都会陷入一个非常典型的误区,不管什么需求都想做出来看看,殊不知,把软件完整地做出来是最大的浪费。
你为什么要学习精益创业?
或许你会问,我就是一个程序员,也不打算创业,学习精益创业对我来说有什么用呢?答案在于,精益创业提供给我们的是一个做产品的思考框架,我们能够接触到的大多数产品都可以放在这个框架内思考。
有了框架结构,我们的生活就简单了,当产品经理要做一个新产品或是产品的一个新特性,我们就可以用精益创业的这几个概念来检验一下产品经理是否想清楚了。
比如,你要做这个产品特性,你要验证的东西是什么呢?他要验证的目标是否有数据可以度量呢?要解决的这个问题是不是当前最重要的事情,是否还有其他更重要的问题呢?
如果上面的问题都得到肯定的答复,那么验证这个目标是否有更简单的解决方案,是不是一定要通过开发一个产品特性来实现呢?
有了这个基础,回到前面的案例中,我对产品经理提的问题,其实就是在确定这件事要不要做。事实上,他们当时是用一个表单工具在收集用户信息,也就是说,这件事有一个可用的替代方案。鉴于当时还有很多其它需求要完成。我建议把这个需求延后考虑。
总结时刻
程序员与产品经理的关系是 IT 行业一个经典的话题。许多程序员都会倾向于不问为什么就接受来自产品经理的需求,然后暗自憋气。
实际上,产品经理是一个新兴职业,即便在 IT 这个新兴行业来看,也算是新兴的。因为从前的 IT 行业更多的是面向确定性的问题,所以,需要更多的是分析。只有当面向不确定性工作时,产品经理才成为一个行业普遍存在的职业。所以,在当下,产品经理并不是一个有很好行业标准的职位。
比较早成型的面向不确定创造新事物的方法论是精益创业它提出了“开发build-测量measure-认知learn”这样一个反馈循环和最小可行产品的概念。
当产品经理让我们做一个新的产品特性时,我们可以从精益创业这个实践上得到启发,向产品经理们问一些问题,帮助我们确定产品经理提出的需求确实是经过严格思考的。
如果今天的内容你只记住一件事,那请记住:默认所有需求都不做,直到弄清楚为什么要做这件事。
最后,我想请你回想一下,你和产品经理日常是怎样做交流的呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,125 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
07 解决了很多技术问题,为什么你依然在“坑”里?
你好,我是郑晔。
在前面的内容中,我给你介绍了几个体现“以终为始”原则的实践,包括怎样界定工作是否完成的 DoD、怎样判定需求是否完成的验收标准、还有怎样验证产品经理给出的产品特性是否合理的精益创业理念。
了解了这些内容,可能你会想:我为什么要关心这些啊?我是程序员啊!难道我不应该安安静静地写程序吗?为什么要操心其他人的工作做得好坏?如果我管了那么多事,我还是不是一个程序员,到底哪里才是我的“终”呢?
今天这一讲,我们就来聊聊这个让许多人困惑的问题。因为只有要跳出程序员的角色看问题,工作才会变得更加高效。
“独善其身”不是好事
在需要与人协作的今天,独善其身可不一定是好的做法。我先给你讲一个发生在我身边的故事。
有一次,我的团队要开发一个数据服务层,准备作为一个基础设施提供给核心业务系统。开发没多久,一个团队成员和我说,他的工作进展不顺利,卡在了一个重要问题上,他想不明白该如何在多个实例之间分配 ID。
我听完之后,有些疑惑,为什么要考虑这个和功能无关的问题呢?他解释说,因为我们的系统需要保证消息的连续性,所以他设计了消息 ID这样下游系统就可以通过消息 ID 识别出是否有消息丢失。
这是没错的,但我奇怪的是,他为什么要在多个实例之间协调呢?他给出的理由是,这么做,是出于考虑应对将来有多实例并发场景的出现。然而事实是,我们当下的需求应对的是单实例的情况。
我了解情况之后,马上跟他说清楚这一点,让他先把第一步做出来。这个同事还是有些担心未来如何做扩展。我告诉他,别纠结,先把第一步做出来,等后面真的有需求,我们再考虑。同事欣然答应了。
其实,这个同事的技术能力非常强,如果我不拦着他,他或许真能实现出一个完美的技术方案,但正如他自己所纠结的那样,这个方案可能要花掉他很长时间。但这真的是我们想要的吗?以现阶段的目标来看,根本没有这样的需求。
我们一直在强调“以终为始”。所谓“终”,其实就是我们的做事目标。虽然大家工作在一起,朝着一个共同的大目标前进,但真的到了一个具体的问题上,每个人看到的目标却不尽相同。
我之所以能把同事从一个纠结的状态中拉出来,是因为我看到的是需求,而他看到的是一个要解决的技术问题。所以,我们俩在对目标的理解上是有根本差异的。
你也许会认为,我和同事之所有这样的差异,是角色上的差异,我在项目里承担的角色要重一些,而且我的工作时间比同事要长一些。但不知道你有没有想过,不同角色的差异到底在哪里呢?
角色的差异
作为一个在职场工作的人,每个人都有一颗渴望得到认可的心,希望自己在职业的阶梯上步步高升。假如今天就让你往上走一个台阶,比如,你原来在项目里打杂,现在成为项目的主力,或者,你已经对项目细节驾轻就熟,即将委任你为项目负责人。你是否能胜任呢?
你需要补充的东西是什么?换句话说,你和你职业台阶中的上一级那个人,差异到底是什么?
也许你会说,他比我来的时间长,或者说,他每天的主要工作就是开会。如果真的是这样,那是不是只要你凑足这个条件,就可以到达他的位置呢?显然不是。
不同角色工作上真正的差异是上下文的不同。
这是什么意思呢?以前面的问题为例,你在项目里打杂,你只能关注到一个具体的任务,而项目主力心目中是整个系统。虽然写的代码都一样,但你看到的是树木,人家看到的是森林,他更能从全局思考。
同样,项目负责人的工作,虽然包括在项目组内的协调,但还有一部分工作是跨项目组的,他需要考虑你们项目组与其他组的互动。所以,他工作的上下文是在各组之间,包括技术和产品等方面。
再上升一个层面,部门负责人要协调内部各个组,同时要考虑部门之间的协调。而公司负责人考虑的上下文甚至要跳脱公司内部,进入到行业层面。
你可能会问,好了,我知道不同角色的上下文有差异了,但这对我意味着什么呢?
我们先从工作角度看。回到前面我分享的那个故事,你可能注意到了,我并不是靠技术能力解决了问题,而是凭借对需求的理解把这个问题绕过去了。
之所以我能这样做,原因就在于我是在一个更大的上下文里工作。类似的故事在我的职业生涯中发生过无数次,许多令程序员愁眉不展的问题,换个角度可能都不是问题。
技术是一把利刃,程序员相信技术可以改变世界,但并不是所有问题都要用技术解决。有这样一种说法,手里有了锤子,眼里都是钉子。花大力气去解决一个可能并不是问题的问题,常常是很多程序员的盲区。
之所以称之为盲区,是因为很多人根本看不见它,而看不见的原因就在于上下文的缺失,也就是说,你只在程序员的维度看问题。
多问几个为什么,交流一下是不是可以换个做法,许多困惑可能就烟消云散了。而能想到问这样的问题,前提就是要跳出程序员角色思维,扩大自己工作的上下文。
虽然我不是项目主力,但不妨碍我去更深入地了解系统全貌;虽然我不是项目负责人,但不妨碍我去了解系统与其他组的接口;同样,虽然我不是项目经理,但我可以去了解一下项目经理是怎样管理项目的;虽然我不是产品经理,但了解一个产品的设计方法对我来说也是有帮助的。
当你对软件开发的全生命周期都有了认识之后,你看到的就不再是一个点了,而是一条线。与别人讨论问题的时候,你就会有更多的底气,与那些只在一个点上思考的人相比,你就拥有了降维攻击的能力。
现在你知道为什么你的工作总能让老板挑出毛病了吧!没错,工作的上下文不同,看到的维度差异很大。单一维度的思考,在多维度思考者的眼里几乎就是漏洞百出的。
当扩大了自己工作的上下文时,我们的目标就不再局限于一个单点,而是会站在更高的维度去思考,解决问题还有没有更简单的方案。许多在低一级难以解决的问题,放到更大的上下文里,根本就不是问题。
我的职业生涯中经常遇到这样的情况,在一个特定的产品设计下,我总觉得设计的技术方案有些不优雅的地方,而只要产品设计微调一下,技术方案一下子就会得到大幅度提升。在这种情况下,我会先去问产品经理,是否可以这样调整。只要不是至关重要的地方,产品经理通常会答应我的要求。
在更大的上下文工作
扩展自己工作的上下文,目光不再局限于自己的一亩三分地,还可以为自己的职业发展做好布局。在这个方面,我给你分享一个不太成功的案例,就是我自己的故事。
我是属于愚钝型的程序员,工作最初的几年,一直把自己限定在程序员的上下文里,最喜欢的事就是安安静静地写代码,把一个系统运作机理弄清楚会让我兴奋很长一段时间。
我的转变始于一次机缘巧合,当时有一个咨询项目,负责这个项目的同事家里有些事,需要一个人来顶班,公司就把我派去了。
到了咨询项目中,我自己习惯的节奏完全乱掉了,因为那不是让代码正常运作就可以解决的问题,更重要的是与人打交道。
有很长一段时间,我一直处于很煎熬的状态,感谢客户没有把我从这个项目赶出来,让我有了“浴火重生”的机会。
为了让自己从这种煎熬的状态中摆脱出来,我必须从代码中走出来,尽量扩大自己思考的边界。经过一段时间的调整,我发现与人打交道也没那么难,我也能更好地理解一个项目运作的逻辑,因为项目运作本质上就是不同人之间的协作。
突破了自己只愿意思考技术的限制,世界一下子宽阔了许多。所以,后来才有机会更多地走到客户现场,看到更多公司的项目运作。虽然我工作过的公司数量并不多,但我却见过很多公司是如何工作的。
再后来,我有机会参与一个新的分公司建设工作中,这让我有了从公司层面进行思考的角度。对于员工招聘和培养,形成了自己一套独立的思考。
这些思考在我创业的过程中,帮我建立了一支很不错的团队。而创业的过程中,我又有了更多机会,去面对其他公司的商务人员,从而建立起一个更大的上下文,把思考从公司内部向外拓展了一些。
回过头来看自己的生涯时,我发现,因为不愿意拓展自己的上下文,我其实错过了很多职业发展的机会。所幸我还有机会突破自己,让自己走出来,虽然走的速度不如理想中快,但至少一直在前进,而不是原地打转。这也是我告诫你一定要不断扩大自己工作上下文的原因。
机会总是垂青那些有准备的人,尤其在公司规模不大的时候,总有一些跳跃式的发展机会。
我见过有人几年之内从程序员做到公司中国区负责人,只是因为起初公司规模不大,而他特别热心公司的很多事情,跳出了固定角色的思维。所以,当公司不断发展,需要有人站出来的时候,虽然没有人是完全合格的,但正是他的热心,让他有了更多的维度,才有机会站到了前排。
当然,随着公司规模越来越大,这种幅度极大的跳跃是不大可能的。江湖上流传着一个华为的故事,一个新员工给任正非写了封万言书,大谈公司发展,任正非回复:“此人如果有精神病,建议送医院治疗,如果没病,建议辞退。”
因为一旦公司规模大了,你很难了解更大的上下文,很多关于公司的事情,你甚至需要从新闻里才知道。
本质上,一个人能在自己的工作范围内多看到两三级都是有可能的。在公司规模不大时,从基层到老板没有太多层级,跳跃就显得很明显,而公司一大,层级一多,从低到顶的跳跃就不太可能了,但跨越级别跳跃是可能的。
所以我希望你跳出程序员思维,这不仅仅是为了工作能够更高效,也是希望你有更好的发展机会。
总结时刻
程序员总喜欢用技术去解决一切问题,但很多令人寝食难安的问题其实根本不是问题。之所以找不出更简单的解决方案,很多时候原因在于程序员被自己的思考局限住了。
不同角色工作真正的差异在于上下文的差异。在一个局部上下文难以解决的问题,换到另外一个上下文甚至是可以不解决的。所以说无论单点有多努力也只是局部优化,很难达到最优的效果。
想把工作做好,就需要不断扩大自己工作的上下文,多了解一下别人的工作逻辑是什么样的,认识软件开发的全生命周期。
扩大自己的上下文,除了能对自己当前的工作效率提高有帮助,对自己的职业生涯也是有好处的。随着你看到的世界越来越宽广,得到的机会也就越来越多。
如果今天的内容你只记住一件事,那请记住:扩大自己工作的上下文,别把自己局限在一个“程序员”的角色上。
最后,我想请你分享一下,在你的工作中,有哪些因为你扩大了工作上下文而解决的问题呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,137 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
08 为什么说做事之前要先进行推演?
你好,我是郑晔。
经过前面的学习,想必你已经对“以终为始”这个原则有了自己的理解。你知道接到一个任务后,要做的不是立即埋头苦干,而是要学会思考,找出真正的目标。那目标明确之后,我们是不是就可以马上开始执行了呢?
先不着急给出你的答案,今天的内容从一个技术任务开始。
一个技术任务
你现在在一家发展还不错的公司工作。随着业务的不断发展,原来采用的关系型数据库越发无法满足快速的变化。于是,项目负责人派你去做个技术选型,把一部分业务迁移到更合适的存储方式上。
经过认真的调研和思考,你给负责人提出了自己的建议,“我们选择 MongoDB。”出于对你的信任负责人无条件地同意了你的建议你获得了很大的成就感。
在你的喜悦尚未消退时,负责人进一步对你委以重任,让你来出个替代计划。替代计划?你有些不相信自己的耳朵,嘴里嘟囔着:“把现在存到数据库的内容写到 MongoDB 不就成了,我就一个表一个表地替换。难道我还要把哪天替换哪个表列出来吗?”
刚刚还对你欣赏有加的负责人,脸色一下子沉了下来。“只有表改写吗?”他问你。你一脸懵地看着他,心里想,“不然呢?”
“上线计划呢?”负责人问。
“我还一行代码都没写呢?”你很无辜地看着负责人。
“我知道你没写代码,我们就假设代码已经写好了,看看上线是怎样一个过程。”
“不是发新版本就好了吗?”你还是不知道负责人到底想说什么。
“你能确定新版代码一定是对的吗?”
虽然你已经叱咤编程很多年,但作为老江湖,一听这话反而是有些怯的。“不能。”你痛快地承认了。
“一旦出错,我们就回滚到上一个版本不就成了。”常规的处理手段你还是有的。
“但数据已经写到了不同的存储里面,查询会受到影响,对不对?”负责人一针见血。
“如果这个阶段采用两个数据存储双写的方案,新代码即便出问题,旧存储的代码是正常,我们还有机会回滚。”你一下子就给出了一个解决方案,咱最不怕出问题了。
“对。”负责人认同了你的做法,一副没看错人的神情。“让你出上线方案,就是为了多想想细节。”
你终于明白了负责人的良苦用心,也就不再大意。很快,你就给出了一份更详尽的上线方案。
你把这个方案拿给负责人看,信心满满,觉得自己够小心,一步一步做,没有任何问题。但负责人看了看你的上线计划,眉头逐渐锁了起来,你知道负责人还是不满意,但不知道还差在哪里?
“原有的数据怎么办?”负责人又问了一个问题。你一下子意识到,确实是问题。“没有原有数据,一旦查询涉及到原有数据,查询的结果一定是错的。所以,还应该有一个原有数据的迁移任务。”你尴尬地笑了笑。
负责人微笑着看着你。“好吧,从我的角度看差不多了,你可以再仔细想想。然后,排一个开发任务出来吧!”
你当然不会辜负负责人的信任,很快排出了开发任务。
看着排出的任务,你忽然困惑了。最开始只是想写个读写新库的组件,怎么就多出这么些任务。此外,你还很纳闷为什么负责人总是能找到这么多问题。
一次个人回顾
你想起之前的工作里有过类似的场景,那个负责人也是让你独立安排任务。通常,你最初得到的也是一个简单的答案,从当时的心境上看,你是很有成就感的。
只是后来的故事就不那么美妙了,上线时常常出现各种问题,你和其他同事们手忙脚乱地处理各种异常。当时顶着巨大压力解决问题的场景,你依然记忆犹新。解决完问题离开公司时,天空已经泛起鱼肚白。
而似乎自从加入了现在的公司,这种手忙脚乱的场景少了很多。你开始仔细回想现在这个负责人在工作中的种种。从给大家机会的角度来看,这个负责人确实不错,他总会让一个人独立承担一项任务。只不过,他会要求大家先将任务分解的结果给他看。
拿到组里任何一个人的开发列表之后,他都会问一大堆问题,而且大多数情况下,他都会问到让人哑口无言。说句心里话,每次被他追问心里是挺不舒服的,就像今天这样。
本来在你看来挺简单的一件事,经过他的一系列追问,变成了一个长长的工作列表,要做的事一下子就变多了。毕竟谁不愿意少做点活呢!
不过,你不得不承认的一点是,加入这个公司后,做事更从容了。你知道无论做的事是什么,那些基本的部分是一样的,差别体现在事前忙,还是事后忙,而现在这家公司属于事前忙。于是,你开始把前一家公司上线时所忙碌的内容,和现在负责人每次问的问题放在一起做对比。
这样一梳理,你才发现,原来负责人问的问题,其实都是与上线相关的问题。包括这次的问题也是,上线出问题怎么办,线上数据怎么处理等等。
你突然意识到一个关键问题,其实负责人每次问的问题都是类似的,无论是你还是其他人,他都会关心上线过程是什么样,给出一个上线计划。即便我们还一行代码都没有,他依然会让我们假设如果一切就绪,应该怎样一步一步地做。
你终于明白了,之前的项目之所以手忙脚乱,因为那时候只想了功能实现,却从来没考虑过上线,而且问题基本上都是出在上线过程中的。你想到了上次参加一个社区活动,其中的一个大牛提到了一个说法:“最后一公里”。
想到这,你赶紧上网搜了一下“最后一公里”,这个说法指的是完成一件事,在最后也是最关键的步骤。你才意识到,“最后一公里”这个说法已经被应用在很多领域了,负责人就是站在“最后一公里”的角度来看要发生的事情。
嗯,你学会了一招,以后你也可以站在“最后一公里”去发现问题了,加上你已经具备的推演能力,给出一个更令人满意的任务列表似乎更容易一些。
把这个问题想清楚了,你重新整理了自己的思路,列出了一个自己的问题解决计划。
先从结果的角度入手,看看最终上线要考虑哪些因素。
推演出一个可以一步一步执行的上线方案,用前面考虑到的因素作为衡量指标。
根据推演出来的上线方案,总结要做的任务。
不过,更令你兴奋的是,你拥有了一个看问题的新角度,让自己可以再上一个台阶,向着资深软件工程师的级别又迈进了一步。
通往结果之路
好了,这个小故事告一段落。作为我们专栏的用户,你可能已经知道了这个故事要表达的内容依旧是“以终为始”。关于“以终为始”,我们前面讲的内容一直是看到结果,结果是重要的。然而,通向结果的路径才是更重要的。
这个世界不乏有理想的人,大多数人都能看到一个宏大的未来,但这个世界上,真正取得与这些理想相配成绩的人却少之又少,大部分人都是泯然众生的。
宏大理想是一个目标,而走向目标是需要一步一个脚印地向前走的。唐僧的目标是求取真经,但他依然用了十几年时间才来到大雷音寺。唐僧西天取经有一个极大的优势,他达成目标的路径是清晰的,从长安出发,向着西天一路前行就好。
对比我们的工作,多数情况下,即便目标清晰,路径却是模糊的。所以,不同的人有不同的处理方式。有些人是走到哪算哪,然后再看;有些人则是先推演一下路径,看看能走到什么程度。
在我们做软件的过程中,这两种路径所带来的差异,已经在前面的小故事里体现出来了。一种是前期其乐融融,后期手忙脚乱;一种是前面思前想后,后面四平八稳。我个人是推崇后一种做法的。
或许你已经发现了这就是我们在“以终为始”主题的开篇中提到的第一次创造或者智力上的创造。如果不记得了不妨回顾一下《02 | 以终为始:如何让你的努力不白费?》。
实际上,早就有人在熟练运用这种思想了。在军事上,人们将其称为沙盘推演,或沙盘模拟。军队通过沙盘模拟军事双方的对战过程,发现战略战术上存在的问题。这一思想也被商界借鉴过来,用来培训各级管理者。
这个思想并不难理解,我们可以很容易地将它运用在工作中的很多方面。比如:
在做一个产品之前,先来推演一下这个产品如何推广,通过什么途径推广给什么样的人;
在做技术改进之前,先来考虑一下上线是怎样一个过程,为可能出现的问题准备预案;
在设计一个产品特性之前,先来考虑数据由谁提供,完整的流程是什么样的。
最后这个例子也是软件开发中常遇到的,为数不少的产品经理在设计产品时,只考虑到用户界面是怎样交互的,全然不理会数据从何而来,造成的结果是:累死累活做出来的东西,完全跑不通,因为没有数据源。
很多时候,我们欠缺的只是在开始动手之前做一遍推演,所以,我们常常要靠自己的小聪明忙不迭地应对可能发生的一切。
希望通过今天的分享,能让你打破手忙脚乱的工作循环,让自己的工作变得更加从容。
总结时刻
即便已经确定了自己的工作目标,我们依然要在具体动手之前,把实施步骤推演一番,完成一次头脑中的创造,也就是第一次创造或智力上的创造。这种思想在军事上称之为沙盘推演,在很多领域都有广泛地应用。
在软件开发过程中,我们就假设软件已经就绪,看就绪之后,要做哪些事情,比如,如何上线、如何推广等等,这样的推演过程会帮我们发现前期准备的不足之处,进一步丰富我们的工作计划。为了不让我们总在“最后一公里”摔跟头,前期的推演是不可或缺的,也是想让团队进入有条不紊状态的前提。
如果今天的内容你只记住一件事,那请记住:在动手做一件事之前,先推演一番。
最后,我想请你思考一下,如果把你在做的事情推演一番,你会发现哪些可以改进的地方呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,137 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
12 测试也是程序员的事吗?
你好,我是郑晔。
在“任务分解”这个模块,我准备从一个让我真正深刻理解了任务分解的主题开始,这个主题就是“测试”。
这是一个让程序员又爱有恨的主题,爱测试,因为它能让项目的质量有保证;恨测试,因为测试不好写。而实际上,很多人之所以写不好测试,主要是因为他不懂任务分解。
在上一个模块,我们提到了一些最佳实践,但都是从“以终为始”这个角度进行讲解的。这次,我准备换个讲法,用五讲的篇幅,完整地讲一下“开发者测试”,让你和我一起,重新认识这个你可能忽视的主题。
准备好了吗?我们先从让很多人疑惑的话题开始:程序员该写测试吗?
谁要做测试?
你是一个程序员,你当然知道为什么要测试,因为是我们开发的软件,我们得尽可能地保证它是对的,毕竟最基本的职业素养是要有的。
但测试工作应该谁来做,这是一个很有趣的话题。很多人凭直觉想到的答案是,测试不就该是测试人员的事吗,这还用问?
测试人员应该做测试,这是没错的,但是测试只是测试人员的事吗?
事实上,作为程序员,你多半已经做了很多测试工作。比如,在提交代码之前,你肯定会把代码跑一遍,保证提交的基本功能是正确的,这就是最基本的测试。但通常,你并不把它当成测试,所以,你的直觉里面,测试是测试人员的事。
但我依然要强调,测试应该是程序员工作的一部分,为什么这么说呢?
我们不妨想想,测试人员能测的是什么?没错,他们只能站在系统外部做功能特性的测试。而一个软件是由它内部诸多模块组成的,测试人员只从外部保障正确性,所能达到的效果是有限的。
打个比方,你做一台机器,每个零部件都不保证正确性,却要让最后的结果正确,这实在是一个可笑的要求,但这却真实地发生在软件开发的过程中。
在软件开发中有一个重要的概念:软件变更成本,它会随着时间和开发阶段逐步增加。也就是说我们要尽可能早地发现问题,修正问题,这样所消耗掉的成本才是最低的。
上一个模块讲“以终为始”,就是在强调尽早发现问题。能从需求上解决的问题,就不要到开发阶段。同样,在开发阶段能解决的问题,就不要留到测试阶段。
你可以想一下,是你在代码中发现错误改代码容易,还是测试了报了 bug你再定位找问题方便。
更理想的情况是,质量保证是贯穿在软件开发全过程中,从需求开始的每一个环节,都将“测试”纳入考量,每个角色交付自己的工作成果时,都多问一句,你怎么保证交付物的质量。
需求人员要确定验收标准开发人员则要交出自己的开发者测试。这是一个来自于精益原则的重要思想内建质量Build Quality In
所以,对于每个程序员来说,只有在开发阶段把代码和测试都写好,才有资格说,自己交付的是高质量的代码。
自动化测试
不同于传统测试人员只通过手工的方式进行验证,程序员这个群体做测试有个天然的优势:会写代码,这个优势可以让我们把测试自动化。
早期测试代码,最简单的方式是另外写一个程序入口,我初入职场的时候,也曾经这么做过,毕竟这是一种符合直觉的做法。不过,既然程序员有写测试的需求,如此反复出现的东西,就会有更好的自动化方案。于是开始测试框架出现了。
最早的测试框架起源是 Smalltalk。这是一门早期的面向对象程序设计语言它有很多拥趸很多今天流行的编程概念就来自于 Smalltalk测试框架便是其中之一。
真正让测试框架广泛流行起来,要归功于 Kent Beck 和 Erich Gamma。Kent Beck 是极限编程的创始人,在软件工程领域大名鼎鼎,而 Erich Gamma 则是著名的《设计模式》一书的作者,很多人熟悉的 Visual Studio Code 也有他的重大贡献。
有一次,二人一起从苏黎世飞往亚特兰大参加 OOPLSAObject-Oriented Programming, Systems, Languages & Applications大会在航班上两个人结对编程写出了JUnit。从这个名字你便不难看出它的目标是打造一个单元测试框架。
顺便说一下,如果你知道 Kent Beck 是个狂热的 Smalltalk 粉丝,写过 SUnit 测试框架,就不难理解这两个人为什么能在一次航班上就完成这样的力作。
JUnit 之后,测试框架的概念逐渐开始流行起来。如今的“程序世界”,测试框架已经成为行业标配,每个程序设计语言都有自己的测试框架,甚至不止一种,一些语言甚至把它放到了标准库里,行业里也用 XUnit 统称这些测试框架。
这种测试框架最大的价值,是把自动化测试作为一种最佳实践引入到开发过程中,使得测试动作可以通过标准化的手段固定下来。
测试模型:蛋卷与金字塔
在前面的讨论里,我们把测试分为人工测试和自动化测试。即便我们只关注自动化测试,也可以按照不同的层次进行划分:将测试分成关注最小程序模块的单元测试、将多个模块组合在一起的集成测试,将整个系统组合在一起的系统测试。
有人喜欢把验收测试也放到这个分类里。为了简化讨论,我们暂时忽略验收测试。
随之而来的一个问题是,我们应该写多少不同层次的测试呢?理论上固然是越多越好了,但实际上,做任何事都是有成本的,所以,人们必须有所取舍。根据不同测试的配比,也就有了不同的测试模型。
有一种直觉的做法是,既然越高层的测试覆盖面越广,那就多写高层测试,比如系统测试。
当然,有些情景高层的测试不容易覆盖到的,所以,还要有一些底层的测试,比如单元测试。在这种情况下,底层的测试只是作为高层测试的补充,而主力就是高层测试。这样就会形成下面这样一种测试模型:冰淇淋蛋卷。
听说过冰淇淋蛋卷测试模型的人并不多,它是一种费时费力的模型,要准备高层测试实在是太麻烦了。
之所以要在这里提及它,是因为虽然这个概念很多人没听说过,但是有不少团队的测试实际采用的就是这样一种模型,这也是很多团队觉得测试很麻烦却不明就里的原因。
接下来,要说说另一种测试模型,也是行业里的最佳实践:测试金字塔。
Mike Cohn 在自己的著作《Succeeding with Agile》提出了测试金字塔但大多数人都是通过 Martin Fowler 的文章知道的这个概念。
从图中我们不难看出,它几乎是冰淇淋蛋卷的反转,测试金字塔的重点就是越底层的测试应该写得越多。
想要理解测试金字塔成为行业最佳实践的缘由,我们需要理解不同层次测试的差异。越是底层的测试,牵扯到相关内容越少,而高层测试则涉及面更广。
比如单元测试,它的关注点只有一个单元,而没有其它任何东西。所以,只要一个单元写好了,测试就是可以通过的;而集成测试则要把好几个单元组装到一起才能测试,测试通过的前提条件是,所有这些单元都写好了,这个周期就明显比单元测试要长;系统测试则要把整个系统的各个模块都连在一起,各种数据都准备好,才可能通过。
这个模块的主题是“任务分解”,我必须强调一点:小事反馈周期短,而大事反馈周期长。小事容易做好,而大事难度则大得多。所以,以这个标准来看,底层的测试才更容易写好。
另外,因为涉及到的模块过多,任何一个模块做了调整,都有可能破坏高层测试,所以,高层测试通常是相对比较脆弱的。
此外,在实际的工作中,有些高层测试会牵扯到外部系统,这样一来,复杂度又在不断地提升。
人们会本能地都会倾向于少做复杂的东西,所以,人们肯定不会倾向于多写高层测试,其结果必然是,高层测试的测试量不会太多,测试覆盖率无论如何都上不来。而且,一旦测试失败,因为牵扯的内容太多,定位起来也是非常麻烦的。
而反过来,将底层测试定义为测试主体,因为牵扯的内容少,更容易写,才有可能让团队得到更多的测试,而且一旦出现问题,也会更容易发现。
所以,虽然冰淇淋蛋卷更符合直觉,但测试金字塔才是行业的最佳实践。
当测试金字塔遇到持续集成
测试金字塔是一个重要实践的基础,它就是持续集成。当测试数量达到一定规模,测试运行的时间就会很长,我们可能无法在本地环境一次性运行所有测试。一般我们会选择在本地运行所有单元测试和集成测试,而把系统测试放在持续集成服务器上执行。
这个时候,底层测试的数量就成了关键,按照测试金字塔模型,底层测试数量会很多,测试可以覆盖主要的场景;而按照冰淇淋蛋卷模型,底层测试的数量则有限。
作为提交代码的防护网,测试数量多寡决定着得到反馈的早晚。所以,金字塔模型与持续集成天然就有着很好的配合。
需要特别注意的是,不是用单元测试框架写的测试就是单元测试。很多人用单元测试框架写的是集成测试或是系统测试。单元测试框架只是一个自动化测试的工具而已,并不是用来定义测试类型的。
在实际工作中,区分不同测试有很多种做法,比如,将不同的测试放到不同的目录下,或是给不同类型的测试一个统一的命名规范。
区分不同类型测试主要目的,主要是在不同的场景下,运行不同类型的测试。就像前面提到的做法是,在本地运行单元测试和集成测试,在持续集成服务器上运行系统测试。
总结时刻
测试是软件开发重要的组成部分,测试应该是软件开发团队中所有人的事,而不仅仅是测试人员的事。因为软件变更成本会随着时间和开发阶段逐步增加,能在早期解决的问题,就不要将它延后至下一个阶段。
在测试问题上,程序员有着天生的优势,会写代码,于是,程序员拥有了一个突出的强项,自动化测试。写测试应该是程序员工作完成的重要组成部分。
随着人们对于测试理解的加深,各种各样的测试都出现了,也开始有了测试的分类:单元测试、集成测试、系统测试等等。越在底层测试,成本越低,执行越快;越在高层测试,成本越高,执行越慢。
人的时间和精力是有限的,所以,人们开始思考不同的测试如何组合。在这个方面的最佳实践称之为测试金字塔,它强调的重点是,越底层的测试应该写得越多。只有按照测试金字塔的方式写测试,持续集成才能更好地发挥作用。
如果今天的内容你只能记住一件事,那请记住:多写单元测试。
最后,我想请你分享一下,你的团队在写测试上遇到哪些困难呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,131 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
13 先写测试,就是测试驱动开发吗?
你好,我是郑晔。
在上一讲中,我向你说明了为什么程序员应该写测试,今天我准备与你讨论一下程序员应该在什么阶段写测试。
或许你会说,写测试不就是先写代码,然后写测试吗?没错,这是一个符合直觉的答案。但是,这个行业里确实有人探索了一些不同的做法。接下来,我们就将进入不那么直觉的部分。
既然自动化测试是程序员应该做的事,那是不是可以做得更极致一些,在写代码之前就把测试先写好呢?
有人确实这么做了于是形成了一种先写测试后写代码的实践这个实践的名字是什么呢它就是测试先行开发Test First Development
我知道当我问出这个问题的时候一个名字已经在很多人的脑海里呼之欲出了那就是测试驱动开发Test Driven Development也就是大名鼎鼎的 TDDTDD 正是我们今天内容的重点。
在很多人看来TDD 就是先写测试后写代码。在此我必须澄清一下,这个理解是错的。先写测试,后写代码的实践指的是测试先行开发,而非测试驱动开发。
下一个问题随之而来,测试驱动开发到底是什么呢?测试驱动开发和测试先行开发只差了一个词:驱动。只有理解了什么是驱动,才能理解了测试驱动开发。要理解驱动,先来看看这两种做法的差异。
测试驱动开发
学习 TDD 的第一步是要记住TDD的节奏“红-绿-重构”。
红,表示写了一个新的测试,测试还没有通过的状态;绿,表示写了功能代码,测试通过的状态;而重构,就是再完成基本功能之后,调整代码的过程。
这里说到的“红和绿”,源自单元测试框架,测试不过的时候展示为红色,通过则是绿色。这在单元测试框架形成之初便已经约定俗成,各个不同语言的后代也将它继承了下来。
我们前面说过,让单元测试框架流行起来的是 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 称为测试驱动设计。
如果今天的内容你只能记住一件事,那请记住:我们应该编写可测的代码。
最后,我想请你分享一下,你对测试驱动开发的理解是怎样的呢?学习过这篇内容之后,你又发现了哪些与你之前理解不尽相同的地方呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,141 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
14 大师级程序员的工作秘笈
你好,我是郑晔。
前面我和大家分享了 TDD 的来龙去脉,那些尚未将 TDD 烂熟于胸的同学会分为两个派别。一派是摩拳擦掌准备动手实践一番另一派是早就自我修炼过但实践之路不通。所以市面上经常会听到有人说TDD 不实用。
但是 TDD 真的不实用吗?
和任何一门技能一样TDD 也是需要练习的。更重要的是,你需要打通 TDD 的“任督二脉”,而这关键正是我们这个模块的主题:任务分解。而且,在今天的内容中,我还将带你领略大师级程序员的工作风范。让我们开始吧!
TDD从何而来
要学最原汁原味的 TDD ,莫过于从源头学起。
从前 TDD 只在小圈子里流行,真正让它在行业里广为人知的是 Kent Beck 那本知名的软件工程之作《解析极限编程》Extreme Programming Explained。这是一本重要的作品它介绍了一种软件开发方法极限编程。
当年他写作之时,许多人都在努力探寻瀑布开发方法之外的软件工程方法,除了极限编程,还有特征驱动开发、水晶开发方法等等,正是这些开发方法的探索,才有了后面敏捷方法的诞生。
极限编程对于行业最大的贡献在于,它引入了大量的实践,比如,前面提到过的持续集成、这里提到的 TDD还有诸如结对编程、现场客户等等。
极限编程之所以叫“极限”,它背后的理念就是把好的实践推向极限。
前面提到持续集成时,我们已经介绍过这个理念,如果集成是好的,我们就尽早集成,推向极限每一次修改都集成,这就是持续集成。
如果开发者测试是好的,我们就尽早测试,推向极限就是先写测试,再根据测试调整代码,这就是测试驱动开发。
如果代码评审是好的,我们就多做评审,推向极限就是随时随地地代码评审,这就是结对编程。
如果客户交流是好的,我们就和客户多交流,推向极限就是客户与开发团队时时刻刻在一起,这就是现场客户。这种极限思维是一种很好的思考问题方式,推荐你也在工作中尝试使用一下。
虽然 TDD 只是《解析极限编程》介绍的诸多实践的一种,它却是与开发人员关系最为密切的一个实践。
随着 TDD 逐渐流行开来,人们对如何做 TDD 也越来越感兴趣于是Kent Beck 又专门为 TDD 写了一本书,叫《测试驱动开发》。
大师级程序员的秘笈
《测试驱动开发》这本书很有意思。如果你只是为了了解 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 的关键点。只有把任务分解到可以测试的地步,才能够有针对性地写测试。
同样听到任务分解这个说法,不同的人理解依然是不一样的。我把任务分解的结果定义成微操作,它远比大多数人理解得小。我们能将任务分解到多小,就决定了我们原子操作的粒度是多大。软件开发中的许多问题正是由于粒度太大造成的,比如,分支策略。
如果今天的内容你只能记住一件事,那请记住:将任务拆小,越小越好。
最后,我想请你分享一下,你身边是否有一些由于任务分解得不够小带来的问题。欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,113 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
19 如何用最小的代价做产品?
你好,我是郑晔。
前面我们讲了开发任务的分解和需求管理的分解,这些都是针对“已经确定好要做的事情”的分解策略,今天我们再上一个台阶,聊聊面对那些不确定的产品功能该如何分解。
产品经理的想法层出不穷,但是,如果我们一味闷着头实现产品经理的想法,无论你有多大的开发团队都是不够用的。我们要学会用最小的代价做产品。
谈到产品这个话题,在“精益创业:产品经理不靠谱,你该怎么办?”这篇文章中,我给你分享了精益创业的理念,任何的想法都要放到真实世界中检验。
我们的直觉当然是把所有的东西都实现了再去检验,但是世界不会停下来等着我们。事实也一次又一次教育我们,“憋大招”的瀑布式软件开发已经成为不合时宜的“老古董”。那我们的理想怎么实现呢?唯有分解。
我们前面提到精益创业就是通过不断地尝试在真实世界中验证产品想法其中一个重要的实践是最小可行产品Minimum Viable ProductMVP我们这次就把这个实践展开讨论一下。
什么叫最小可行产品?就是“刚刚好”满足客户需求的产品。客户需求好理解,怎么算“刚刚好”呢?其中的关键在于理解“最小”和“可行”。
最小的代价
先说“最小”。这里的“最小”,指的是最小的代价。怎么叫最小的代价,就是能不做的事情就不做,能简化的事情就简化。
首先,我们必须清楚一件事,我们要做的是验证一个想法的可行性,甚至不是为了开发一个软件,开发软件只是一种验证手段。
很多程序员都会有一个认识上的误区,容易把解决方案当成问题。我们开发软件的目的是为了解决问题,如果不写软件就把问题解决了,岂不是更好。
我先讲一个自己的经历,帮你理解一下什么叫“最小”。有一次,有一个朋友找我帮忙,他手头有一些制造业的客户,想做一个物联网相关的项目,帮助这些客户改造设备,实现物联网功能。
该怎么着手呢?把软件写好,给客户试用吗?这样时间太长,成本太高。那么,我们是怎么做的呢?
第一步,我们要验证这样一个想法是否可行。我们做了一个产品文档,就好像我们已经有了这个产品一样,让负责销售的同事拿着这个文档给客户讲讲,看看客户对这个想法的反映。
在这个过程中,我们验证了基本的想法,已有设备进行物联网化改造的需求存在,客户看到了这样的一个东西,各种各样的想法和要求就会冒出来。
此外,我们还获得了一个额外的收获,我们知道了客户对于这样一个产品能够接受的价格区间,这可以帮助团队给产品进行适当的定价。
验证了方向上的想法,我们开始进入到具体的产品设计阶段。这个阶段我们想验证的是,我们给出的产品设计用户是否可以接受。于是,我们决定把这个产品的交互做出来。
得益于原型工具的快速发展,我们用一个原型工具做出了相对完整的用户界面,而且把各种交互流都做出来了。在用户看来,这几乎就是完整的软件了。
他们甚至可以在自己的设备上体验一下这个产品用起来是什么感觉的。一旦上手用起来,他们就会抛出各种细节的问题:如果这样就好了,如果能做到这个就太棒了。当然,他们也会说,这个东西我不需要。
这个时候,我们就可以知道,我们在产品上的假设哪些是好的,哪些是不流畅的。团队拿到这些反馈,就可以再调整产品设计,然后,再给到用户去测试,如此反复进行。有的时候,产品会在一天之内改好几个版本。
经过多轮测试下来,团队有了一大堆的用户反馈,而且是来自真实用户的反馈。接下来,就是整理这些用户反馈,决定哪些可以真正的开发出来,这时候,团队才真正进入到开发阶段。
不知道你注意到了没有,迄今为止,这个团队验证了一大堆的想法,而代码却是一行都没有写,所有花费的工作量都是有针对性的验证。
我们经常听到一个段子,叫“就差一个程序员了”。这说的是,一个创业者把前期的准备都做好,就差程序员把产品开发出来了。
按照 MVP 的思想,这个创业者做的就是对的,前提是他真的把前期准备都做好了。
开发软件是一件成本很高的事情。如果只是验证想法,无论是创业方向,还是产品设计,我们可以找到各种各样的手段,不用写代码。
即便我们不是在做一个新产品,我们依然可以运用这个“最小代价”的理念在日常工作中做事。比如,怎么来衡量产品经理的产品设计是不是好的。我会问,这个功能不做,用户会怎么样?有没有什么替代方案等等。以此来帮助产品经理想清楚自己的产品设计是否真的有价值。
可行的路径
说完了”最小”,我们再来看”可行”。可行是要找到一条路径,给用户一个完整的体验。做程序员出身的人,对软件系统的认识总是一个模块一个模块的,相对比较弱的方面是缺少一个完整的图景。
但从产品可行的角度,我们需要转换一下思路,不是一个模块做得有多完整,而一条用户路径是否通畅。
我再给你分享一个我当年做 P2P 项目经历,这里的 P2P 指的是个人对个人的互联网借贷平台。
这是一个从头开始的项目,项目方和所有的项目方一样,希望昨天这个项目就上线了,如果不能,那就尽快上线一个版本。他们给我们一个时间线,第一个上线的版本是一个月之后。
摆在我们面前的问题是,无论如何,在一个“一穷二白”的基础上,要在一个月内完成一个完整的借贷平台是不太可能的。
时间有限,我们只能做最基本的东西,许多运营上的想法,比如,发红包代金券之类的,第一期一律不做。即便如此,我们仍然认为完成完整的借贷循环是不现实的。
于是,我们就开始从需求完整性的角度动脑筋。这是一个借贷系统,其最基本的模型是:贷款方贷款之后,一次性拿到所有的钱,然后用等额本息的方式每个月还款,最后一个月剩多少钱一次性全还了。
我们在这个模型中找到了一个关键点,每个月还款。换句话说,第一笔贷款发生之后,最早的一笔还款是发生在一个月之后的。
于是,我们做了一个决定,第一个版本只包含贷款能力。是的,这个版本只能贷款,不能还款。因为用户一个月之内不会用到这个功能,你从页面上,完全看不出这样的能力缺失,因为一个月内,根本没有任何用户有可还的款项。
因为缩减了项目规模,我们在预期的一个月内完成所有开发,成功地把项目送上了线。第一批早期用户就开始了使用。从用户的视角看,这是一个功能完整的项目,虽然简单了点,但它是完整的。
当然,我们把还款排到了下一期。按照我们两周一迭代的节奏,在第一期上线两周之后,我们就会上线还款功能,届时贷款方将拥有一个真正的还款功能。
不过,这个还款功能只是每期的等额本息还款,最后的一次性还剩余所有贷款的功能,我们依然是不支持的。因为根据需求设计,最后一次还款最早发生在一年之后。
在我们把基本的功能全部送上线之后,这个系统就是一个真正的、完整的借贷平台了。但是,相对于其他提供相同能力的平台而言,这个系统依然还是很简单。比如,常见的运营功能、短期借贷计划,这个平台都没有。
但我们有了基础,接下来,就是在基础上叠加,而且随着项目方自己团队的构建,我们拥有了够大的团队,可以同时做几个大需求了。
就这样几个月之后我们就逐步上线了一个功能相对完整的P2P平台。在这个过程中我们每个阶段都会上线新功能从用户可见的角度他看到的始终是一个完整的平台其中的变化只有站在内部实现者的角度才能看得清楚。
和大家分享这个例子,主要是想破除大家对于一个“完整”系统概念的认识。当时间有限时,我们需要学会找到一条可行的路径,在完整用户体验和完整系统之间,找到一个平衡。
站在开发团队的角度,我们怎样把 MVP 理念运用在自己的工作中呢?当产品经理有一大堆要实现的功能时,我们就可以根据 MVP 理念,从这些产品功能中找出一条最小的可行路径,重新安排一个合理的开发计划。
总结时刻
产品同样需要分解目前在探索产品的不确定性上的最佳实践是精益创业而精益创业就包含了将庞大的产品分而治之的方式最小可行产品Minimum Viable ProductMVP。最小可行产品就是“刚刚好”满足客户需求的产品。
想要在实践中运用好最小可行产品的理念,就是要用最小的代价找到一条可行的路径。最小的代价就是能不做的事就不做,能简化的事情就简化。
程序员通常愿意用自己的代码解决问题,而写代码通常是代价非常高的解决方案,它应该成为最后的产品解决方案。
可行的路径,是一条完整的用户体验路径,至少在用户眼中是这样的。我们常常会想给客户一个完整的系统,但在时间有限的情况下,我们必须学会分解。
如果今天的内容你只能记住一件事,那请记住:做好产品开发,最可行的方式是采用 MVP。
最后,我想请你分享一下,你遇到或听说过采用 MVP 或类似方法解决问题的案例吗?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,129 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
20 为什么世界和你的理解不一样?
你好,我是郑晔。
从今天起,我们要开启一个新的模块:沟通反馈。
如果看到沟通反馈几个字,你就以为我打算在这里教一些谈话技巧,那你还真的想错了。
在这个模块里,我打算与你讨论的主题是,生活在真实世界中。沟通反馈和生活在真实世界这两个话题是怎么联系到一起的呢?请听我慢慢道来。
《大富翁》里的沙隆巴斯有句口头禅:人生不如意的事,十有八九!但是不知道你有没有想过这样的一个问题,为什么人生如此不如意?如果这是一篇鸡汤文,我应该告诉你世事艰辛。但我要说的是,真实的原因往往是因为你想得太美好,用我们做软件的例子来看一下:
在我们的愿望中,做出来的产品应该一举成名,现实却是惨淡经营;
在我们的愿望中,产品经理给出的需求应该是清晰明了的,现实却是模模糊糊;
在我们的愿望中,写出来的代码,应该是快捷无错的,维护也很容易,现实却是 Bug 百出,越修改,修改的时间就越长;
在我们的愿望中,你给我布置任务,我应该迅速地理解到关键,现实却是做出来的与你的目标根本就是天差地别;
……
为什么会这样?欢迎来到真实世界,真实世界不是以美好愿望驱动的,它有着自己的运行规律。虽然我们都生活在同一个世界中,但每个人理解世界的方式确实是千差万别。
我们努力地学习各种知识,为的就是更好地理解这个世界的运作方式,而沟通反馈,就是我们与真实世界互动的最好方式。
你也许会好奇,为什么我们对世界的理解会出现偏差?接下来,让我们一起用一个信息论的视角看一下。
一个信息论视角的解释
1948年克劳德·香农Claude Elwood Shannon在《贝尔系统技术学报》Bell System Technical Journal上发表了一篇论文《通信的数学原理》A Mathematical Theory of Communication这是现代信息论的开端。我们程序员熟知的通信、数据压缩、密码学、自然语言处理等诸多领域都有信息论的身影。
我们这里要借鉴的是香农信息论中的一个通信模型,如下图所示:
这个图中包含了几个要素:
信源Information Source它负责产生信息Message
发送器Transmitter它会对信息进行某些操作也就是对信息编码产生信号Signal
信道Channel它是信号传送的媒介。
接收器Receiver它是对信号执行发送器的逆操作解码信号提取出信息。
信宿Destination它负责接收信息。
当然图中还有一个因素叫做噪声Noise指的是削弱信号的东西。不过它并不是我们这里讨论的重点我们暂时忽略它。
我们用一个实际工作中的例子来理解一下这个过程。假设你的项目经理来给你布置一项工作,在这里,项目经理就是一个信源。他的想法就是他的消息,他要把这件事告诉你,先要在大脑中做一次编码,转换成语言表达出来。他说出来的这段话就是信号。
比如,这个信号是“完成一个需求”。这段话通过信道,也就是空气传播到你耳朵里,接收到这段话之后,你会按照自己对这段话的理解进行解码,作为信宿的你,形成了自己的想法,这就是你接到的消息,整个过程就完成了。
我们来看一下,理解偏差是怎么产生的。
项目经理给你传输的信号是“完成一个需求”,在项目经理脑子中,这个信号的原始信息可能是这样的:编写完成这个功能所需的代码,然后为这段代码写好自动化测试,再将它与现有系统集成好,通过测试人员的验证。
而在学习这个专栏之前,你从“完成一个需求”这个信号中解码出来的信息却是:把功能代码写完。这样,问题就出现了。即便这里忽略了噪声的干扰,当编码和解码不是一个版本的时候,无论如何,项目经理的信息都很难准确地传达到你这里。
这就是人们往往对世界产生误解的原因。
信息的传达要经过编码和解码两个过程,无论是编码出现问题,还是解码出现问题,都会造成信息的不准确。
一方面,有些人表达不清楚,一件简单的事,他说了半天,你依然是云里雾里。这就相当于,信源发出的信息经过编码得到的信号已经不准确了。
另一方面,就像听一些技术演讲,人家说得很清楚,但因为自己没有相关背景,依然无法得知人家表达的信息。这就相当于信号虽然准确,但我们没有对应的解码装置,信号无法转成有效信息。
再有就是像前面这个例子,收发双方编解码器不配套,同样的信号得到的信息截然不同,信息传达的目的也不能很好地完成。
有了理论做基础,我们就容易理解世界为什么总和我的理解不一样,这就是编解码的过程出了问题。因为每个人经历见识的差异,造成了各自编解码器的差异。世界是同一个世界,每个人看到的却是千姿百态。
如果想在这个真实的世界中生活得更幸福一些,我们能做点什么呢?那就是改善我们的编解码器。怎么改善自己的编解码器呢?这就是“沟通反馈”这个模块要讨论的内容。
改善编解码
站在改善编解码效果的角度,我们要考虑哪些问题呢?
首先,我们要考虑一下编码器的效果。换句话说,当我们想把信息传达给别人的时候,我们得把信息编码成一个有效的信号,至少要保证在我们这里信息不丢失。
我举个例子,有一次,我在客户现场做咨询,客户的一个程序员给我介绍他们的系统,他讲了二十分钟,我还是听得一头雾水。于是,我打断他,花了五分钟用我的语言给他讲了一遍,然后问他:“你想说的是不是这个意思?”他猛劲点头:“就是这样的。”
为什么会这样呢?究其原因就是,他上来就在给我讲实现细节,完全没有任何铺垫。
要知道,我是来了解情况的,所以,我的背景知识肯定是不足的,凭空理解这些细节是非常困难的一件事。从沟通的角度上看,这么做浪费了大量的时间,因为在过程中,我要不断地让他给我补充这些缺失的背景。这几乎是很多程序员讲东西的通病:讲东西直奔细节。
我在面试中也经常遇到过类似的情况,一些候选人上来就给我讲技术细节,我对他做过的系统一无所知,所以,我只好打断他,让他先把背景给我介绍一下。
同样,很多人抱怨别人不能理解自己,其实,首先应该想的问题是,自己到底有没有把话说清楚。这就是编码器出现问题的情况。
其次,我们还要考虑一下解码器的效果,也就是说,当一个信号呈现在我们面前时,作为接收者,我们是否能够有效地解码信息。
著名作家王小波曾经讲过一个花剌子模信使的故事,说的是中亚古国花剌子模有一个奇怪的风俗,凡是给君王带来好消息的信使,就会得到提升,给君王带来坏消息的人则会被送去喂老虎。如此一来,谁还敢把坏消息带给君王呢?但问题是,君王不听坏消息,坏消息就不存在了吗?
这就相当于解码器出了问题,过滤掉了很多真实的信息。但真实世界就是真实世界,它不会按照人们的美好愿望运行。
再举一个我们身边的例子,能做程序员的人,大多是很聪明的人, 当几个人一起讨论问题时,别人往往刚开了个头,他就认为自己已经理解了别人的想法,然后开始表达自己的观点。信息都不全,何谈解码。所以,开发团队的讨论中常常出现一个人高谈阔论,却离题万里的情况。
我们要想让自己更好地工作生活,就必须接纳真实世界的反馈,而接纳真实世界的反馈,一是需要我们打开自己的接收器,把信号接纳进来,让反馈进来,这是解码的前提;二是扩展见识,提升自己解码器的效果,更好地理解别人要表达的内容到底是什么。
说了编码器和解码器可能出现的问题,我们再来看另外一个可能造成影响的问题:编解码器算法,也就是怎么协调沟通双方更有效地进行沟通。
既然前面已经说了算法不够好会影响到信息的传递,那接下来的问题就是怎样找到一个好的算法。其实,我们从始至终在讲的各种最佳实践就是一个个好的算法,帮助我们改善沟通的效果。
还是回到前面提到“完成一个需求”的例子,我们在“以终为始”模块已经讲过了,通过制定“完成的定义”就可以帮助改善这个过程。这就相当于,沟通的双方都有了一个编解码手册。
当“完成一个需求”这样的信号发出时,作为接收方,你的解码动作就变成了,先要查一下手册里,关于“完成一个需求”的标准动作都有哪些。于是,你就不会对事情做那么简单的估计了。
在“沟通反馈”这个模块下,我还会给你介绍各种“算法”,也就是最佳实践,帮你在工作中提高“信息”传递的效率。
回到我们这部分主题上,沟通反馈就是改善编码、解码以及算法的方式。无论是“发送”得更清楚,还是“接收”得更明白,抑或是通过各种协调算法,都是为了让通信的双方做好准备。
总结时刻
人生不如意之事,十有八九,之所以很多人有如此多的不如意,很大原因在于我们对真实世界有着很多不切实际的幻想,美好的愿望并不能驱动这个世界,在软件开发中也是如此。虽然人和人生活在一个世界中,但对世界的理解却是千差万别的。
我们借用了信息论的一个通信模型解释为什么每个人看到的世界会有如此大的差异,其核心就在于,人和人拥有不同的编解码器。想要在这个真实世界中生活得更幸福一些,需要我们不断地改善自己的编解码器。
改善编解码,需要从几个角度着手,分别是:编码器,让信息能输出更准确;解码器,减少信号过滤,改善解码能力;还有编解码算法,也就是各种来自行业的“最佳实践”,协调沟通的双方。
如果今天的内容你只能记住一件事,那请记住:通过沟通反馈,不断升级自己的编解码能力。
最后,我想请你回想一下,你在工作中遇到过哪些因为沟通反馈不畅造成的问题呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,137 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
21 你的代码为谁而写?
你好,我是郑晔。
关于“沟通反馈”的话题,我准备从代码开始讲起,毕竟我们程序员是靠代码与机器进行沟通的。
写代码是每个程序员的职责,程序员们都知道要把代码写好。但究竟什么叫写好呢?每个人的理解却是各有差异。
编写可维护的代码
初涉编程的程序员可能觉得能把功能实现出来的代码,就是好代码,这个阶段主要是基本功的学习,需要掌握的是各种算法、数据结构、典型的处理手法、常用的框架等等。
经过一段时间工作,日常工作所需的大多数代码,在你看来都是不在话下的。尤其像搜索和问答网站蓬勃发展之后,你甚至不需要像我初入职场时那样,记住很多常见的代码模式,现在往往是随手一搜,答案就有了。
再往后,更有追求的程序员会知道,仅仅实现功能是不够的,还需要写出可维护的代码。于是,这样的程序员就会找一些经典的书来看。
我在这方面的学习是从一本叫做《程序设计实践》The Practice of Programming的书开始的这本书的作者是 Brian Kernighan 和 Rob Pike这两个人都出身于大名鼎鼎的贝尔实验室参与过 Unix 的开发。
写出可维护的代码并不难,它同样有方法可循。今天,我们用写代码中最简单的一件事,深入剖析怎样才能写出可维护的代码,这件事就是命名。
命名难题
计算机科学中只有两大难题:缓存失效和命名。-
—— Phil Karlton
这是行业里流传的一个经典说法,无论是哪本写代码风格的书,都会把命名放在靠前的位置。
估计你开始写程序不久,就会有人告诉你不要用 a、b、c 做变量名,因为它没有意义;步入职场,就会有人扔给你一份编程规范,告诉你这是必须遵循的。
不管怎样,你知道命名是很重要的,但在你心目中,合格的命名是什么样的呢?
想必你知道命名要遵循编码规范比如Java 风格的 camelCase常量命名要用全大写。
但是,这类代码规范给出的要求,大多是格式上的要求。在我看来,这只是底线,不应该成为程序员的追求,因为现在很多编码规范的要求,都可以用静态检查工具进行扫描了。
我们的讨论要从名字的意义说起。作为程序员,我们大多数人理解为什么要避免起无意义的名字,但对于什么样的名字是有意义的,每个人的理解却是不同的。
名字起得是否够好,一个简单的评判标准是,拿着代码给人讲,你需要额外解释多少东西。
比如,我们在代码评审中会看到类似这样的场景:
评审者:这个叫 map 的变量是做什么用的?-
程序员:它是用来存放账户信息的,它的键值是账户 ID值就是对应的账户信息。-
评审者:那为什么不直接命名成 accounts
你知道评审者给出的这个建议是什么意思吗?如果不能一下子意识到,遇到类似的问题,你可能会和这个程序员一样委屈:这个变量本来就是一个 map我把它命名成 map 怎么了?
变量的命名,实际上牵扯到一个重要问题,代码到底是给谁写的?
代码为谁而写?
任何人都能写出计算机能够理解的代码,只有好程序员才能写出人能够理解的代码。-
—— Martin Fowler
代码固然是程序员与机器沟通的重要途径,但是,机器是直白的,你写的代码必须是符合某种规则的,这一点已经由编译器保证了,不符合规则的代码,你想运行,门都没有。
所以,只要你的代码是符合语言规则的,机器一定认。要让机器认,这并不难,你写得再奇怪它都认。行业里甚至有专门的混乱代码比赛。比如,著名的 IOCCCThe International Obfuscated C Code Contest国际 C 语言混乱代码大赛)。
但是,我们写代码的目的是与人沟通,因为我们要在一个团队里与人协同工作。
与人沟通,就要用与人沟通的方式和语言写代码。人和机器不同,人需要理解的不仅是语言规则,还需要将业务背景融入其中,因为人的目的不是执行代码,而是要理解,甚至扩展和维护这段代码。
人要负责将业务问题和机器执行连接起来,缺少了业务背景是不可能写出好代码的。
我们在“为什么世界和你理解的不一样”这篇内容中就讲过,沟通的时候,输出时的编码器很重要,它是保证了信息输出准确性的关键。
很多程序员习惯的方式是用计算机的语言进行表达,就像前面这个例子里面的 map这是一种数据结构的名字是面向计算机的而评审者给出的建议把变量名改成 accounts这是一个业务的名字。
虽然只是一个简单的名字修改,但从理解上,这是一步巨大的跨越,缩短了其他人理解这段代码所需填补的鸿沟,工作效率自然会得到提高。
用业务语言编程
写代码的时候,尽可能用业务语言,会让你转换一个思路。前面还只是一个简单的例子,我们再来看一个。
我们用最常用的电商下单过程来说,凭直觉我们会构建一个订单类 Order。什么东西会放在这个类里呢
首先,商品信息应该在这个类里面,这听上去很合理。然后,既然是电商的订单,可能要送货,所以,应该有送货的信息,没问题吧。再来,买东西要支付,我们会选择一些支付方式,所以,还应该有支付信息。
就这样,你会发现这个订单类里面的信息会越来越多:会员信息可能也要加进去,折扣信息也可能会加入。
你是一个要维护这段代码的人,这个类会越来越庞大,每个修改都要到你这里来,不知不觉中,你就陷入了一个疲于奔命的状态。
如果只是站在让代码运行的角度,这几乎是一个无法解决的问题。我们只是觉得别扭,但没有好的解决方案,没办法,改就改呗!
但如果我们有了看业务的视角,我们会问一个问题,这些信息都放在“订单”是合理的吗?
我们可以与业务人员交流,询问这些信息到底在什么场景下使用。这时候你就会发现,商品信息主要的用途是下单环节,送货信息是在物流环节,而支付信息则用在支付环节。
有了这样的信息,你会知道一件事,虽然我们在用一个“订单”的概念,但实际上,在不同的场景下,用到信息是不同的。
所以,更好地做法是,把这个“订单”的概念拆分了,也就有了:交易订单、物流订单和支付订单。我们原来陷入的困境,就是因为我们没有业务知识,只能笼统地用订单去涵盖各种场景。
如果你在一个电商平台工作,这几个概念你可能并不陌生,但实际上,类似的错误我们在很多代码里都可以看到。
再举个例子,在很多系统里,大家特别喜欢一个叫“用户”的概念,也把很多信息塞到了“用户”里。但实际上,在不同的场景下,它也应该是不同的东西:比如,在项目管理软件中,它应该是项目管理员和项目成员,在借贷的场景下,它应该是借款方和贷款方等等。
要想把这些概念很好地区分出来,你得对业务语言有理解,为了不让自己“分裂”,最好的办法就是把这些概念在代码中体现出来,给出一个好的名字。这就要求你最好和业务人员使用同样的语言。
如果了解领域驱动设计Domain Driven DesignDDD你可能已经分辨出来了我在这里说的实际上就是领域驱动设计。把不同的概念分解出来这其实是限界上下文Bounded Context的作用而在代码里尽可能使用业务语言这是通用语言Ubiquitous Language的作用。
所以,一个好的命名需要你对业务知识有一个深入的理解,遗憾的是,这并不是程序员的强项,需要我们额外地学习,但这也是我们想写好代码的前提。现在,你已经理解了,取个好名字,并不是一件容易的事。
总结时刻
代码是程序员与机器沟通的桥梁,写好代码是每个程序员的追求,一个专业程序员,追求的不仅是实现功能,还要追求代码可维护。如果你想详细学习如何写好代码,我推荐你去读 Robert Martin 的《代码整洁之道》Clean Code这本书几乎覆盖了把代码写好的方方面面。
命名,是写程序中最基础,也是一个程序员从业余走向专业的门槛。我以命名为基础,给你解释了写好代码的提升路径。最初的层次是编写可以运行的代码,然后是编写符合代码规范的代码。
对于命名,最粗浅的理解是不要起无意义的名字,遵循编码规范。但名字起得是否够好,主要看是否还需要额外的解释。很多程序员起名字习惯于采用面向实现的名字,比如,采用数据结构的名字。
再进一步提升,编写代码是要写出人可以理解的代码。因为代码更重要的作用是人和人沟通的桥梁,起一个降低其他人理解门槛的名字才是好名字。
实际上,我们很多没写好的程序有一些原因就是名字起错,把一些概念混淆在一起了。想起好名字,就要学会用业务语言写代码,需要尽可能多地学习业务知识,把业务领域的名字用在代码中。
如果今天的内容你只能记住一件事,那请记住:用业务的语言写代码。
最后,我想请你思考一下,想要写好代码,还有哪些因素是你特别看重的?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,125 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
22 轻量级沟通:你总是在开会吗?
你好,我是郑晔。
今天我们来探讨一个很多程序员日常工作中,经常碰到却会带来困扰的话题:开会。
头疼的开会
有一次,我听到两个程序员在聊天。一个资深程序员说:“还是晚上好,我可以一门心思写代码”,另一个年轻程序员不解地问:“你白天也可以写啊。”
资深程序员很无奈,“我倒是这样想,可是白天参加那么多会,哪有工夫啊!我的代码就只能加班写了。”
这段对话听上去让人有点心酸,但这种现象,确确实实广泛存在于程序员的日常工作中,尤其是你经验丰富又在一个大组织中工作,这几乎成了你的宿命。在这些程序员的认知中,开会太多影响了他们写代码。
你以为我想讨伐开会吗?并不是,开会本身并没有错,因为开会的本意是将大家组织起来解决问题。但请你回想一下,你参加的会议有多少解决了问题呢?
开会是为了解决问题,但真实情况却是开了会又没有解决多少问题,这真是一个奇特的矛盾。
回想一下,你参加过的会议里面,有没有效果特别好的呢?在我职业生涯中,凡是效果特别好的会议,基本上都是用来做信息同步的。比如,领导宣布一个事情,这种会议几乎不会浪费时间。宣布消息,大家收到消息,结束。
那效果不好的会议是什么样呢?几乎都是那些讨论会,你一言我一语,每个会几乎无一例外,都有几个擅长打岔的,这个会基本上都会跑偏,时间就会这样一分一秒地流逝了。
我给你举个例子,我之前参加过一个上线计划的评审会,这个团队的负责人要把相关利益方都召集起来,其中包括上下游可能会受影响的团队、测试、运维等等,一个不大的会议室里挤满了人。
这个负责人刚开始讲方案没几分钟,下游团队的负责人就站出来问:“这个方案为什么要这么做?我担心会对我们系统造成影响。”讲方案的人只好停下来解释。结果是越解释,细节越多,双方你来我往,一个方案评审会,就转变成一个技术讨论会了。
测试和运维的同事本来是想来听技术方案,以便为后续的工作做准备的。看着双方的讨论,一脸无奈,因为他们知道,方案没确定好,所有的事情还是下回再说吧!
怎么样?是不是很熟悉的感觉。为什么会这样?因为他们选错了沟通方式。
开会是一种重量级的沟通,几乎是我们日常工作中最重的。它有很强的仪式感,所以,大家相对来说会很重视。而且会议通常会牵扯到很多人,尤其是与这个事情相关度不那么高的人。
你可以想一下,有多少次开会,你是在精力集中的?如果你是高度集中的,那恭喜你,你是高效地参与其中。但更多时候,你可能神游天外,因为讨论的内容可能与你关系不大,或者你已经听不懂了,你坐在那里的唯一原因是,主持人还没宣布会议结束。
用开会这种重量级的方式讨论问题,就好比杀鸡用了牛刀,这是不恰当的。那该怎么解决这个问题呢?很简单,杀鸡用鸡刀。
轻量级沟通
实际上,真正在会议上能够积极参与讨论的人并不会觉得会议是浪费时间,因为高度参与其中,人是进入到心流状态的,时间流逝很快。觉得浪费时间的,往往是没有参与其中的人。
换句话说,会议之所以给人留下如此不堪的印象,一个重要的原因是,真正参与讨论的人并不多。所以,我们换个角度思考一下,只要把这些真正参与讨论的人拉到一起讨论不就好了?
所以,改善会议的第一个行动项是,减少参与讨论的人数。
有人会说,我这个讨论有好几个议题,每个议题要不同的人参与,那你要做的是,分别找这几个人专门讨论,而不是把大家放到一起。
不知道你发现没有,在讨论行动项的时候,我用的是“讨论”,而没有提到“会议”两个字。我之前说过了,会议是一种重量级的沟通方式。所以,我们会倾向于选择一种轻量级的沟通方式,比如面对面沟通,这样一来,每个人的压力就会小很多。
相比于会议的形式,面对面沟通因为注意力有限,参与的人数不可能太多。也因为参与的人数相对少一些,每个人的投入也会更多一些。
所以,我们的第二个行动项是,如果你要讨论,找人面对面沟通。
一旦理解了这些改进方式,我们就可以改进自己的行为方式。如果有一个问题需要讨论,我要做的是,分别找到相关人针对关心的主题进行讨论,然后,我把讨论的结果汇总再去征求大家意见。如果大家达成一致了,我才会选择开会。
这个时候,开会的目的不再是讨论,而是信息同步:我准备这么干了,相关各方已经同意了,知会大家一下,结束。
站立会议
我前面说过了,开会并非都是不好的,一些信息同步的会还是有必要的。
举个例子有一种实践叫站会Standup。很多公司都在实践它站会甚至成为每天的开工仪式。一般的做法是早上大家来上班了先开一个站会让大家同步一下昨天的工作然后开始今天的工作。
有的人一听到站会这个形式就会皱起眉头。如果是这样,多半是你的团队“站”错了。
你知道这个会为什么是“站”会吗因为按照一般人的习惯站的时间不会太长因为站的时间长累啊所以如果站会超过10分钟你的站会一定是错的。
也许你会说,这点时间恐怕不够给我们站会吧?因为每个人都有一大堆要说的。请问,你觉得其他人说那么多,你关心吗?现实是,一旦一个人说多了,跟你关系又不大,你就开始思维发散了。
所以,在总长固定的情况下,每个人发言的时间一定是有限的。在有限的时间内,你能说什么呢?我建议你只说三件事:
我昨天做了什么?
我今天打算做什么?
我在过程中遇到了什么问题,需要请求帮助。
“做了什么” ,是为了与其他人同步进展,看事情是否在计划上。一旦偏离计划,请主动把它提出,这样,项目经理可以过问,因为这会涉及到是否要调整项目计划;
“要做什么” ,是同步你接下来的工作安排。如果涉及到与其他人协作,也就是告诉大家,让他们有个配合的心理准备;
“问题和求助”, 就是与其他人的协作,表示:我遇到不懂的问题,你们有信息的话,可以给我提供一下。
这三件事都是与别人相关的,几句话快速说完,结束。因为这些事情与别人相关,所以,大家的注意力可以相对集中一些。
你或许会问,如果我的问题很复杂,需要讨论该怎么办。对不起,那是另外一件事,你可以在站会结束之后,找相关人去讨论,不要在这个会上浪费大家时间。在站会上,你只要在问题和求助中告诉大家,你有一个问题,需要相关人讨论,结束。
为了让大家保持注意力集中,我的一些团队还用过发言令牌的方式。比如,找一个毛绒玩具,谁拿到“令牌”谁发言,然后,随机地扔给一个人,一旦这个人走神,大家一下子就能发现了。
一些有趣的方式、短暂的时间,以及与所有人相关的事情,因为满足了这三点,所以普遍来说,这种站会效果还可以。
关于站会,有一个典型的错误是,有些团队把站会开成了汇报会。项目负责人指定一个个轮流发言,说的人都向负责人在汇报工作,其他人自然就容易走神了,因为事情与己无关。
还有一点你可能会有疑问,我所在的团队比较大,一个人几句话时间也会很长。
当团队很大时更应该做的是把团队拆分了因为你不太可能与20个人紧密地工作在一起。沃顿商学院曾经做过一项研究5-12个人是一个恰当的团队规模每个人在其中都能发挥自己的重要作用。
总结时刻
开会是很多程序员的困扰,太多的会议甚至会影响到你工作的进展。开会的本意是为了解决问题,但实际上,大多数会议并不能很好地解决问题。因为会议是一种重量级的沟通方式,很多人参加会议时,并不能很好地参与其中。
如果你想用会议的形式与别人讨论问题,最好放弃这种打算,面对面的沟通是最好的方式。因为面对面沟通很轻,人数相对少,每个人参与度就会高很多。基于这种改进,我们可以把大部分会议都改成信息同步的会,效率就会得到提高。
我还给你介绍了一种特殊的会议:站会。之所以采用站会的方式,就是要控制时间。在站会上每个人说什么,我给了你一个建议的格式:
我昨天做了什么?
我今天打算做什么?
我在过程中遇到了什么问题,需要请求帮助。
如果你经常组织别人开会,请你想一下,是不是自己没有利用好开会这件事;如果你经常被别人组织开会,不妨把这篇文章转发给他,让他别总是开会“讨论”问题。
如果今天的内容你只能记住一件事,那请记住:多面对面沟通,少开会。
最后,我想请你思考一下,你在工作中,还遇到过哪些因为开会带来的问题呢?欢迎留言与我写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,107 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
26 作为程序员,你也应该聆听用户声音
你好,我是郑晔。
在前面的专栏内容中,我们讨论过几次与产品经理的交流:你应该问问产品经理为什么要做这个产品特性,要用 MVP最小可行产品的角度衡量当前做的产品特性是不是一个好的选择。
但还有一个问题可能困扰着我们:怎么判断产品经理说的产品特性是不是用户真的需要的呢?
很多时候,产品经理让你实现一个产品特性,你感觉这么做好像不太对,却又说不出哪不对,想提出自己的看法,却不知道从哪下手。之所以会遇到这样的问题,一个重要的原因就是,你少了一个维度:用户视角,你需要来自真实世界的反馈。
吃自家的狗粮
产品经理无论要做什么,他都必须有一个立足的根基:为用户服务。所以,如果你了解了用户怎么想,你就有资本判断产品经理给出的需求,是否真的是用户需要的了。
而作为一个程序员,欠缺用户视角,在与产品经理的交流中,你是不可能有机会的,因为他很容易用一句话就把你打败:“这就是用户需求。”
很多程序员只希望安安静静地写好代码,但事实上,对于大多数人来说,安安静静是不太可能写好代码的,只有不断扩大自己的工作范围,才可能对准“靶子”。
今天我们讨论的角度,就是要你把工作范围扩大,由听产品经理的话,扩大成倾听用户的声音。
作为程序员你应该听说过一个说法“Eat your own dog food”吃自家的狗粮。这个说法有几个不同的来源都是说卖狗粮的公司真的用了自家的狗粮。
从1988年开始这个说法开始在 IT 行业流行的微软的保罗·马瑞兹Paul Maritz写了一封“Eating our dog food”的邮件提到要“提高自家产品在内部使用的比例。”从此这个说法在微软迅速传播开来。
如今,自己公司用自己的产品几乎成了全行业的共识。抛开一些大公司用这个说法做广告的因素,不断使用自家的产品,会让你多出一个用户的视角。
在挑毛病找问题这件事上,人是不需要训练的,哪里用着不舒服,你一下子就能感受到。所以,不断地使用自家产品,你自己就是产品的用户,这会促使你不断去思考怎么改进产品,再与产品经理讨论时,你就自然而然地拥有了更多的维度。
比如,前面在讨论 MVP 时,我曾经讲过一个我做 P2P 产品的经历。在这个项目中,我就作为用户在上面进行了一些操作。当自己作为用户使用时,就发现了一些令人不爽的地方。
比如,一开始设计的代金券只能一次性使用,如果代金券金额比较大,又没那么多本金,只能使用代金券的一部分,就会让人有种“代金券浪费了”的感觉。
于是,我就提出是不是可以把代金券多次使用。很快,产品就改进了设计。这种改进很细微,如果你不是用户,只从逻辑推演的角度是很难看到这种差异的。
当你吃不到狗粮时
不过不是每家公司的产品都那么“好吃”。“吃自家狗粮”的策略对于那些拥有“to C”产品的公司来说相对是比较有效的。但有时候你做的产品你根本没有机会用到。
我曾经与很多海外客户合作过,我做的很多产品,自己根本没有机会使用。比如,我做过五星级酒店的审计平台。除了能对界面上的内容稍微有点感觉之外,对于使用场景,我是完全不知道的。
如果没有机会用到自己的产品,我们该怎么办呢?我们能做的就是尽可能找机会,去到真实场景里,看看用户是如何使用我们软件的。
比如,做那个酒店审计平台时,我就和客户一起到了一家五星级酒店,看着他们怎样一条一条地按照审计项核查,然后把审计结果登记在我们的平台上。
那些曾经只在写程序时见到的名词,这回就活生生地呈现在我眼前了。后来再面对代码时,我看到就不再是一个死板的程序了,我和产品经理的讨论也就更加扎实了。
有的团队在这方面有比较好的意识,会主动创造一些机会,让开发团队成员有更多机会与用户接触。
比如,让开发团队到客服团队轮岗。接接电话,听听用户的抱怨,甚至是谩骂。你会觉得心情非常不好,但当你静下来的时候,你就会意识到自己的软件有哪些问题,如果软件做得不好,影响会有多大。
这时,你也就能理解,为什么有的时候,很多业务人员会对开发团队大发雷霆了,因为他们是直接面对用户“炮火”的人。
我们为什么要不断地了解用户的使用情况呢?因为用户的声音是来自真实世界的反馈。不去聆听用户声音,很容易让人自我感觉良好。还记得在 “为什么世界和你的理解不一样” 中,我们提到的那个只接收好消息的花剌子模国国王的例子吗?
我们要做一个有价值的产品,这个“价值”,不是对产品经理有价值,而是要对用户有价值。华为总裁任正非就曾经说过,“让听得见炮声的人来做决策。”
我们做什么产品,本质上不是由产品经理决定的,而是由用户决定的。只有听见“炮声”,站在一线,我们才更有资格判断产品经理给出的需求是否真的是用户所需。
当产品还没有用户时
如果你的团队做的是一个新的产品,还没有真正的用户,那又该怎么办呢?你可以尝试一下“用户测试”的方法。
之前我做过一个海外客户的项目。因为项目处于启动阶段,我被派到了客户现场。刚到那边,客户就兴高采烈地告诉我,他们要做一个用户测试,让我一起参加。当时,我还有点不知所措,因为我们的项目还没有开始开发,一个什么都没有的项目就做用户测试了?是的,他们只做了几个页面,就开始测试了。
站在今天的角度,我前面已经给你讲过了精益创业和 MVP你现在理解起来就会容易很多。是的他们就是要通过最小的代价获取用户反馈。
他们是怎么做测试的呢?首先是一些准备工作,找几个普通用户,这些人各有特点,能够代表不同类型的人群。准备了一台摄像机,作为记录设备,拍摄用户测试的全过程。还准备了一些表格,把自己关注的问题罗列上去。
然后,就是具体的用户测试了。他们为用户介绍了这个测试的目的、流程等一些基本信息。然后,请用户执行几个任务。
在这个过程中,测试者会适时地让用户描述一下当时的感受,如果用户遇到任何问题,他们会适当介入,询问出现的问题,并提供适当的帮助。
最后,让用户为自己使用的这个产品进行打分,做一番评价。测试者的主要工作是观察和记录用户的反应,寻找对用户使用造成影响的部分。做用户测试的目的就是看用户会怎样用这个网站,这样的网站设计会对用户的使用有什么影响。
当天测试结束之后,大家一起整理了得到的用户反馈,重新讨论那些给用户体验造成一定影响的设计,然后调整一版,再来做一次用户测试。
对我来说,那是一个难忘的下午,我第一次这么近距离地感受用户。他们的关注点,他们的使用方式都和我曾经的假设有很多不同。后面再来设计这个系统时,我便有了更多的发言权,因为产品经理有的角度,我作为开发人员也有。
最后,我还想说一个程序员常见的问题:和产品经理没有“共同语言。”
因为他们说的通常是业务语言而我们程序员的口中基本上是计算机语言。这是两个领域的东西很难互通。前面在讨论代码的时候我提到要用业务的语言写代码实际上这种做法就是领域驱动设计中的通用语言Ubiquitous Language
所谓通用语言,不只是我们写代码要用到,而是要让所有人说一套语言,而这个语言应该来自业务,来自大家一起构建出的领域模型。
这样大家在交流的时候,才可能消除歧义。所以,如果你想让项目顺利进行,先邀请产品经理一起坐下来,确定你们的通用语言。
总结时刻
今天我们讨论了一个重要的话题:倾听用户声音。这是开发团队普遍欠缺的一种能力,更准确地说,是忽略的一种能力。所以,“吃自家的狗粮”这种听上去本来是理所当然的事情,才被反复强调,成为 IT 行业的经典。
在今天这一讲,我给你介绍了“了解用户需求”的不同做法,但其归根结底就是一句话,想办法接近用户。
无论是自己做用户,还是找机会接触已有用户,亦或是没有用户创造用户。只有多多听取来自真实用户的声音,我们才不致于盲目自信或是偏颇地相信产品经理。谁离用户近,谁就有发言权,无论你的角色是什么。
如果今天的内容你只能记住一件事,那请记住:多走近用户。
最后,我想请你思考一下,在你的实际工作中,有哪些因为走近客户而发现的问题,或者因为没有走近客户造成的困扰呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,131 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
27 尽早暴露问题: 为什么被指责的总是你?
你好,我是郑晔。
今天我准备讨论一个经常会让很多程序员郁闷的事情,为什么你已经工作得很辛苦了,但依然会被指责。在讨论这个问题之前,我们先来讲一个小故事。
程序员小李这天接到了一个新的任务。系统要做性能提升,原先所有的订单都要下到数据库里,由于后来有很多订单都撤了,反复操作数据库,对真正成交过程的性能造成了影响。所以,技术负责人老赵决定把订单先放到缓存里。
这就会牵扯到一个技术选型的问题,于是,老赵找了几个可以用作缓存的中间件。为了给大家一个交代,老赵决定让小李给这几个中间件做一个测试,给出测试结果,让大家一起评估。小李高兴了,做这种技术任务最开心,可以玩新东西了。
老赵问他:“多长时间可以搞定?”
小李说:“一个星期吧!”
老赵也很爽快,“一个星期就一个星期。不过,我得提个要求,不能是纯测中间件,得带着业务跑。”
“没问题。”小李一口答应下来。老赵怕小李做多了,还特意嘱咐他,只测最简单的下单撤单环节就好。
等真的开始动手做了,小李发现,带着业务跑没那么容易,因为原来的代码耦合度太高,想把新的中间件加进去,要先把下单和撤单环节隔离开来。而这两个操作遍布在很多地方,需要先做一些调整。
于是,小李只好开始不分白天黑夜地干起来。随着工作的深入,小李越发觉得这个活是个无底洞,因为时间已经过半了,他的代码还没调整完。
这时,老赵来问他工作进展,他满面愁容地说,估计干不完了。老赵很震惊,“不就是测试几个中间件吗?”
小李也一脸委屈,“我们为啥要带着业务跑啊?我这几天的时间都在调整代码,以便能够把中间件的调用加进去。”
老赵也很疑惑,“你为啥要这么做?”
“你不是说要带着业务跑吗?”
“我是说要带着业务跑啊!但你可以自己写一个下单撤单的程序,主要过程保持一致就好了。”小李很无奈,心里暗骂,你咋不早说呢?
是啊!你咋不早说呢?不过,我想说不是老赵,而是小李。
谁知道有问题?
我们来分析一下问题出在哪。在这个故事里,小李和老赵也算有“以终为始”的思维,在一开始就确定了一个目标,做一个新中间件测试,要带着业务跑。
小李可以说是很清楚目标的,但在做的过程中,小李发现了问题,原有代码很复杂,改造的工作量很大,工作可能没法按时完成。
到此为止,所有的做法都没有错。但接下来,发现问题的小李选择了继续埋头苦干,直到老赵来询问,无奈的小李才把问题暴露出来。
在老赵看来,这并不是大事,调整一下方案就好了。但是小李心生怨气,在他看来,老赵明明有简单方案,为啥不早说,害得自己浪费了这么多时间。
但反过来,站在老赵的角度,他是怎么想的呢?“我的要求是带着业务跑,最理想的方案当然是和系统在一起,你要是能搞定,这肯定是最好的;既然你搞不定,退而求其次,自己写一个隔离出来的方案,我也能接受。”
你看出来问题在哪了吗?老赵的选择没有任何问题,问题就出在,小李发现自己可能搞不定任务的时候,他的选择是继续闷头做,而不是把问题暴露出来,寻求帮助。
作为一个程序员,克服技术难题是我们工作的一个重要组成部分,所以,一旦有困难我们会下意识地把自己投入进去。但这真的是最好的做法吗?并不是,不是所有的问题,都是值得解决的技术难题。
在工作中遇到问题,这简直是一件正常得不能再正常的事儿了,即便我们讲了各种各样的工作原则,也不可避免会在工作中遇到问题。
既然是你遇到的问题,你肯定是第一个知道问题发生的人,如果你不把问题暴露出来,别人想帮你也是有心无力的。
如果老赵不过问,结果会怎么样?必然是小李一条路跑到黑。然后,时间到了,任务没完成。
更关键的是,通常项目计划是一环套一环的,小李这边的失败,项目的后续部分都会受到影响,项目整体延期几乎是必然的。这种让人措手不及的情况,是很多项目负责人最害怕见到的。
所以,虽然单从小李的角度看,这只是个人工作习惯的事,但实际上,处于关键节点的人可能会带来项目的风险。而小李的问题被提前发现,调整的空间则会大很多。
遇到问题,最好的解决方案是尽早把问题暴露出来。其实,这个道理你并不陌生,因为你在写程序的时候,可能已经用到了。
Fail Fast
写程序有一个重要的原则叫 Fail Fast这是什么意思呢就是如果遇到问题尽早报错。
举个例子我做了一个查询服务可以让你根据月份查询一些信息一年有12个月查询参数就是从1到12。
问题来了,参数校验应该在哪做呢?如果什么都不做,这个查询参数就会穿透系统,传到你的数据库上。
如果传入的参数是合法的当然没有任何问题这个查询会返回一个正常的结果。但如果这个参数是无意义的比如传一个“13”那这个查询依然会传到数据库上。
事实上,很多不经心的系统就是这么做的,一旦系统出了什么状况,你很难判断问题的根源。
在这个极度简化的例子里,你可以一眼看出问题出在输入参数上,一旦系统稍具规模,请求来自不同的地方,这些请求最终都汇集到数据库上,识别来源的难度就会大幅度增加。尤其是系统并发起来,很难从日志中找出这个请求的来源。
你可能会说“为了方便服务对不同数据来源进行识别可以给每个请求加上一个唯一的请求ID吧
系统就是这么变复杂的我经常调侃这种解决方案就是没有困难创造困难也要上。当然即便以后真的加上请求ID理由也不是现在这个。
其实,要解决这个问题,做法很简单。稍微有经验的人都知道,参数校验应该放在入口的位置上,不合法的请求就不让它往后走了。这种把可能预见的失败拦在外面的做法就是 Fail Fast有问题不可怕让失败尽早到来。
上面这个例子很简单,我再给你举一个例子。如果配置文件缺少了一个重要参数,比如,缺少了数据库最大连接数,你打算怎么处理?很多人会选择给一个缺省值,这就不是 Fail Fast 的做法。既然是重要参数,少了就报错,这才叫 Fail Fast。
其实Fail Fast 也有一些反直觉的味道,很多人以构建健壮系统为由,兼容了很多奇怪的问题,而不是把它暴露出来。反而会把系统中的 Bug 隐藏起来。
我们都知道,靠 debug 来定位问题是最为费时费力的一种做法。所以,别怕系统有问题,有问题就早点报出来。
顺便说一下在前面这个例子里透传参数还有几个额外的问题。一是会给数据库带来额外的压力如果有人用无意义查询作为一种攻击手段它会压垮你的数据库。再有一点也是安全问题一些SQL攻击利用的就是这种无脑透传。
克服心理障碍
对我们来说,在程序中尽早暴露问题是很容易接受的。但在工作中暴露自己的问题,却是很大的挑战,因为这里还面临着一个心理问题:会不会让别人觉得自己不行。
说实话,这种担心是多余的。因为每个人的能力是强是弱,大家看得清清楚楚。只有你能把问题解决了大家才会高看你,而把问题遮盖住,并不能改善你在别人心目中的形象。
既然是问题,藏是藏不住的,就像最开始那个故事里的小李,即便他试图隐藏问题,但最后他还是不可能完成的,问题还是会出来,到那时,别人对他的评价,只会更加糟糕。
比起尽早暴露问题,还有更进一步的工作方式,那就是把自己的工作透明化,让别人尽可能多地了解自己的工作进展,了解自己的想法。
如果能做到这一点,其他人在遇到与你工作相关的事情,都会给你提供信息,帮助你把工作做得更好。当然,这种做法对人的心理挑战,比尽早暴露问题更大。
从专栏开始到现在,我们讲了这么多原则和实践,其实,大多数都是在告诉你,有事先做。
一方面,这是从软件变更成本的角度在考虑;另一方面,也是在从与人打交道的角度在考虑。
越往前做,给人留下的空间和余地越大,调整的机会也就越充足。而在最后一刻出现问题的成本实在太高,大到让人无法负担。
总结时刻
我们今天讨论了一个重要的工作原则,把事情往前做,尽早暴露问题。我们前面讲的很多内容说的都是这个原则,比如,要先确定结果,要在事前做推演等等。越早发现问题,解决的成本就越低,不仅仅是解决问题本身的成本,更多的是对团队整体计划的影响。
一方面,事前我们要通过“以终为始”和“任务分解”早点发现问题;另一方面,在做事过程中,一旦在有限时间内搞不定,尽早让其他人知道。
这个原则在写程序中的体现就是 Fail Fast很多程序员因为没有坚持这个原则不断妥协造成了程序越来越复杂团队就陷入了无尽的泥潭。
原则很简单,真正的挑战在于克服自己的心理障碍。很多人都会下意识地隐瞒问题,但请相信你的队友,大家都是聪明人,问题是藏不住的。
如果今天的内容你只记住一件事,那请记住:事情往前做,有问题尽早暴露。
最后,我想请你回想一下,如果遵循了这样的工作原则,你之前犯过的哪些错误是可以规避掉的呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,117 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
32 持续交付:有持续集成就够了吗?
你好,我是郑晔。
在前面两讲,我给你讲了开发过程的自动化,将我们的程序打成发布包;然后讲了部署过程的自动化,通过各种工具将发布包部署起来。
有了这些基础,我们就可以考虑在每次开发完之后,将程序打包部署到环境中。开发完就自动打包,然后自动部署,听起来很像持续集成是不是?
关于持续集成,我在专栏里已经讲过两次,分别讨论了“为什么要做持续集成”和“怎么做好持续集成”。但持续集成的讨论只停留在开发环节。
有了前面两讲的准备,我们就可以把这个过程再进一步延伸。聪明的你或许已经听出来了,这次我要讲的主题是持续交付。
持续交付
让持续交付这个概念广为人知的是一本书Jez Humble 和 Dave Farley 的《持续交付》Continuous Delivery
前面讲持续集成的发展历史时,我提到了 CruiseControl它是持续集成服务器的鼻祖。因为持续集成的不断发展2007年我的老东家 ThoughtWorks 公司有意以 CruiseControl 为基础提供企业级服务于是成立了一个团队打造一个更好的持续集成服务器Jez Humble 就是在这个团队中工作的。
同样在这个团队工作的还有一个人,乔梁,他是《持续交付》这本书的中文版译者,而且在这本书出版近十年后,他自己写了《持续交付 2.0》,把自己多年来关于持续交付的新理解整理了进去。
那么,什么叫更好的持续集成服务器呢?当时我的理解很浅薄,只是希望它有更好的界面,更快的构建速度,而 Jez Humble 他们对于这个产品的构想远远超过了我当时的想象,他们将生产环境也纳入了考量。
什么是持续交付?简言之,它就是一种让软件随时处于可以部署到生产环境的能力。从一个打好的发布包到部署到生产环境可用,这中间还差了什么呢?那就是验证发布包,部署到环境中。
验证发布包,你或许会想,这不是测试的事吗?这不是已经在持续集成阶段完成的吗?不尽然。在持续集成阶段验证的包,往往缺少了环境的支持。
因为持续集成的环境往往是单机的,主要强调功能验证,而一些与生产环境相关的测试往往是欠缺的。所以,这里就引出了持续交付中一个需要关注的点:环境。
一般来说,在构建持续交付的基础设施时,会有下面几个不同的环境。
持续集成环境,持续集成是持续交付的前提,这个过程主要是执行基本的检查,打出一个可以发布的包。
测试环境Test这个环境往往是单机的主要负责功能验证这里运行的测试基本上都是验收测试级别的而一般把单元测试和集成测试等执行比较快的测试放到持续集成环境中执行。
预生产环境Staging这个环境通常与生产环境配置是相同的比如负载均衡集群之类的都要有只是机器数量上会少一些主要负责验证部署环境比如可以用来发现由多机并发带来的一些问题。
生产环境Production这就是真实的线上环境了。
你也看出来了每个环境的作用是有差异的所以通常不会将所有的验证放在一起执行而是要分阶段的去执行一个阶段不通过是不能进入下一阶段的这种按照不同阶段组织构建的方式称之为构建流水线Build Pipeline
一旦通过了各种验证,就会到构建流水线的最后一个阶段,生产发布。通常来说,生产发布这个过程不是自动化的。我们说,持续交付的关注点在于,让软件具备随时可以发布的能力,但并不等于它要立刻上线,所以,最后这一下,还要由人来决定,到底是不是要上线。
如果把由人决定的是否上线变成自动化的,就成了另外一个实践:持续部署。但通常人们都会比较谨慎,最后这一下还是由人拍板比较稳妥,所以,持续交付是现在的主流。
至此,我们讨论了持续交付的第一个方面,验证发布包。接下来,我们再来看看另外一个重要部分:部署。
DevOps
早期人们做部署都是自己编写 Shell 脚本完成的但在上一讲中我提到的一些工具比如Chef、Puppet、Ansible 等等大幅度地简化了部署脚本的编写。这些工具在业界的兴起与一个概念息息相关DevOps。
DevOps 是一种软件交付的理念和方法目的是增强软件的可靠性。从名字便不难发现DevOps 是将开发Development和运维Operations组合在了一起。
在传统的 IT 公司中,开发和运维往往是井水不犯河水的两个职位,甚至是两个不同的部门,由此带来了很多问题,比如,开发人员修改了配置,但没有通知运维,造成了新代码不能运行。
DevOps 提倡的就是将二者融合起来打破壁垒。2009年Flickr 做了一个分享《每天部署10次》整个行业受到了极大的冲击从此 DevOps 运动风起云涌。DevOps 给这个行业带来的理念冲击是很大的,想要做好 DevOps需要在文化、流程和工具等诸多方面不断改善。
但对我们程序员的日常工作来说最直接的影响是体现在各种工具上。Chef、Puppet、Ansible 这些工具基本上都是在那之后,兴起或广为人知的。
在上一讲中,我给你讲了这些配置管理工具在运维体系中的角色,它们相当于提供了一个框架。但对于行业来说,这些工具给行业带来了部署的规范。
从前写 Shell 的方式,那就是各村有各村的高招。你在 A 公司学会的东西,到 B 公司是没法用的,甚至在很多人的印象中,部署这件事就应该属于某个特定的场景,换台机器脚本都要重新写过。这种形势就如同 Spring 出现之前,几乎所有的公司都在写自己的框架一样。
Spring 的出现打破这一切,让你的 Java 技能从归属于一个公司变成了行业通用。同样运维体系中这些配置工具也起到了这样的作用。它们甚至带来了一个新的理念基础设施即代码Infrastructure as code将计算机的管理与配置变成了代码。
一旦成了代码,就可以到处运行,可以版本管理,那种强烈依赖于“英雄”的机器配置工作终于可以平民化了。这在从前是想都不敢想的事。
这些工具采用的都是声明式接口,从 Shell 那种描述怎么做,到描述做什么,抽象程度上了一个台阶,让开发者或系统管理员从琐碎的细节中脱身,把更多的注意力用于思考应该把机器配置成什么样子。
如果这些配置管理工具还需要有一台具体的机器去部署,放在持续交付中,也只能扮演一个部署环境的次要角色,那 Docker 的出现则彻底地改变最终交付物。
我在上一讲说过Docker 相当于是一台机器。Docker 非常好的一点是,它是一台可以用代码描述的机器,在 Docker 配置文件中描述的就是我们预期中那台机器的样子,然后,生成镜像,部署到具体的机器上。
既然是要描述机器的样子,我们就可以在 Docker 的配置文件中使用前面提到的配置工具,如此一来,我们的配置工作就简单了。那既然我们在讨论持续交付,还可以通过配置工具将我们的发布包也部署到最终的镜像中。这样一来,最终生成的镜像就是包含了我们自己应用的镜像。
你或许已经知道我要说什么了,结合着这些工具,我们的生成产物就由一个发布包变成了一个 Docker 镜像。
Docker 在开发中扮演的角色,是一个构建在我们应用与具体机器之间的中间层。对应用而言,它就是机器,但对机器而言,它只是一个可以部署的镜像,统一了各种应用千奇百怪的部署差异,让部署本身变得更简单了。
到这里,我给你介绍了持续交付中最基础的东西,让你有了一个基本的框架理解持续交付。当然,如果你关注这个领域,就会发现,它早已超出了一个实践的层面,有更多组织、文化的内容。
Jez Humble 写《持续交付》时就已经想到如此完整的一个体系,受限于当时的环境,书中介绍的自动化还比较宽泛,不像今天有更加完善的工具支撑。
只可惜,虽然当时他对持续交付的理解已经到达如此高度,他所在的团队也做出了一个颇具先锋气质的持续交付工具,但是受限于产品推广策略,这个工具并没有成为主流,即便后来开源了。(如果你想了解一下这个工具,可以点击链接去查看)
总结时刻
总结一下今天的内容。我们延续了前两讲的内容,在准备好发布包和部署的基础设施之后,我们顺着持续集成的思路,将部署过程也加了进来,这就是持续交付。
持续交付,是一种让软件随时处于可以部署到生产环境的能力。让软件具备部署到生产环境的能力,这里面有两个关键点:验证发布包和部署。
验证发布包不仅是功能上的验证还包括与环境结合在一起的验证。所以通常会用几个不同的环境验证每一个环境都是一个单独的阶段一个阶段不通过是不能进入下一阶段的这种按照不同阶段组织构建的方式称之为构建流水线Build Pipeline
与部署相关的一个重要概念是 DevOps也就是将开发和运维结合起来。DevOps 包含了很多方面对程序员最直接的影响是各种工具的发展这些工具推动着另一个理念的发展基础设施即代码Infrastructure as code 。有赖于这些工具的发展,今天定义交付,就不再是一个发布包,而是一个可以部署的镜像。
如果今天的内容你只能记住一件事,那请记住:将部署纳入开发的考量。
最后,我想请你分享一下,你对持续交付的理解是什么样的呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,159 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
33 如何做好验收测试?
你好,我是郑晔。
经过前面三讲的讲解,相信你对一个项目自动化应该是什么样子有了一个相对完整的认识:程序员写好程序,用构建脚本执行检查,提交代码,在服务器上打出一个发布镜像,部署到各个环境进行检查,检查好了,随时可以发布上线。
我们在前面的内容中只说了该检查,但怎么检查呢?这就轮到测试发挥作用了。
在“任务分解”的模块,我给你完整地介绍了一下开发者测试的概念,但在那个部分讲解的测试基本上还停留在单元测试和集成测试的范畴。对于整个应用该怎么测,我们并没有仔细讨论。
今天我们就来说说应用测试的话题:验收测试。
验收测试
验收测试Acceptance Testing是确认应用是否满足设计规范的测试。这种测试往往是站在用户的角度看整个应用能否满足业务需求。
从名字上来看,验收应该是业务人员的事,但业务人员能做的最多只是验收,测试是他们无论如何也不太可能做仔细的。
所以,验收测试这件事,往往还是由技术团队自己完成,而且在很多公司,这就是测试人员的事。
时至今日,很多测试团队都拥有自动化的能力。所以,自动化验收测试自然是重点考虑对象。今天,我们的重点就是怎么做好自动化的验收测试。
其实,验收测试应该是人们最早想到的自动化测试,早在单元测试还不流行的年代,人们就开始了对自动化验收测试的探索。有不少团队甚至还构建了自己的框架,只不过,这种框架不是我们今天理解的测试框架,而是针对着一个应用的测试框架。
比如,我曾经见过有人为通信软件构建的一套完整的测试框架,甚至构建了属于自己的语言,测试人员的工作就是用这种特定的语言,对系统进行设置、运行,看它是否满足自己的预期。
相对来说,他们的这种做法已经非常成熟了。但更多团队的现实情况是,自己把对应用的访问做一个简单的封装,然后,写测试就是编写代码调用这个封装。
让验收测试从各自为战的混乱中逐渐有了体系的是行为驱动开发Behavior Driven Development这个概念的诞生也就是很多人知道的 BDD。
行为驱动开发
行为驱动开发中的行为指的是业务行为。BDD 希望促进业务人员与开发团队之间的协作,换句话说,如果你想做 BDD就应该用业务语言进行描述。
这与我们传统上理解的系统测试有着很大的差别,传统意义上的系统测试是站在开发团队的角度,所以,更多的是在描述系统与外部系统之间的交互,用的都是计算机的术语。
而 BDD 则让我们换了一个视角,用业务语言做系统测试,所以,它是一个更高级别的抽象。
BDD 是2003年由 Dan North 提出了来的。Dan North 不仅仅提出了概念,为了践行他的想法,他还创造了第一个 BDD 的框架JBehave。后来又改写出基于 Ruby 的版本 RBehave这个项目后来被并到 RSpec 中。
今天最流行的 BDD 框架应该是 Cucumber它的作者就是 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。它把对页面的访问封装了起来即便你在写的是步骤定义你也不应该在代码中直接操作 HTML 元素,而是应该访问不同的页面对象。
以前面的登录为例,我们可能会定义这样的页面对象:
public class LoginPage {
public boolean login(String name, String password) {
...
}
}
如此一来,在步骤定义中,你就不必关心具体怎么定位到输入框,会让代码的抽象程度得到提升。
当然,这只是一个参考,面对你自己的应用时,你要考虑构建自己的业务测试模型。
总结时刻
今天我和你分享了自动化验收测试的话题。验收测试Acceptance Testing是确认应用是否满足设计规范的测试。验收测试是技术交付必经的环节只不过各个团队实践水平有所差异有的靠人工有的用简单自动化一些做得比较好的团队才有完善的自动化。
自动化验收测试也是一个逐步发展的过程,从最开始的各自为战,到后来逐渐形成了一个完整的自动化验收测试的体系。
今天我以行为驱动开发Behavior Driven DevelopmentBDD为核心给你介绍了一种自动化验收测试的方式。这个在2003年由 Dan North 提出的概念已经成为了一套比较完善的体系,尤其是一些 BDD 框架的发展,让人们可以自己的项目中实践 BDD。
我以 Cucumber 为样例,给你介绍了 BDD 验收用例的编写方式你知道“Given…When…Then”的基本格式也知道了要编写步骤定义Step Definition将测试用例与实现连接起来。
我还给你介绍了编写 BDD 测试用例的最佳实践:用业务的视角描述测试用例。在编写步骤定义时,还要考虑设计自己的业务测试模型。
其实,验收测试的方法不止 BDD 一种像实例化需求Specification by ExampleSbE也是一种常见的方法。验收测试框架也不止 BDD 框架一类,像 Concordion 这样的工具甚至可以让你把一个验收用例写成一个完整的参考文档。
如果你有兴趣,可以深入地去了解。无论哪种做法,都是为了缩短业务人员与开发团队之间的距离,让开发变得更加高效。
如果今天的内容你只能记住一件事,那请记住:将验收测试自动化。
最后,我想请你分享一下,你的团队是怎么做验收测试的呢?欢迎在留言区分享你的做法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,136 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
38 新入职一家公司,怎么快速进入工作状态?
你好,我是郑晔。
经过前面几个模块的学习,我们分别领略了各个原则在不同场景下的应用,相信你对于这些原则的理解也上了一个台阶。但实际工作并不会清晰地告诉你,到底该运用哪个原则来解决问题。
所以,在接下来的三讲中,我挑选了程序员职业生涯中三个非常经典的场景,与你一起看看怎么在实际的工作中运用好已经学习到的这些原则。
在综合运用这个模块的第一讲,我们就来谈谈,当你加入一家新公司时,应该怎么做。
IT 行业快速发展,无数的机会涌现了出来,程序员频繁流动是这个行业的一个典型特征。频繁换工作,无论是对公司,还是对个人都是成本很高的一件事。所以,在加入一个新公司时,怎么让自己快速融入,尽快发挥价值,是摆在我们面前的一个重要问题。
以行业标准来看,我换工作的速度是很低的,但因为之前工作的原因,我需要到不同的公司与不同的人合作,每到一个新公司,工作的内容就是全新的,就如同换了一个新工作一般。因为合作周期有限,我不可能像普通员工入职新公司一样,花几个月时间慢慢熟悉,只能在尽可能短的时间内,快速上手,而且还要提出自己的新想法。
那我是怎么做的呢?其实,我就是运用这个专栏里提到的各种方法解决这个问题。下面我就来分享一下具体的做法。
运用思考框架
还记得专栏之初我提出的思考框架吗?我们要问三个问题:
Where are we?(我们现在在哪?)
Where are we going?(我们要到哪儿去?)
How can we get there?(我们如何到达那里?)
先来看第一个问题,如果刚刚加入一家公司,哪怕我们不是一脸懵,也只是对公司业务有一个简单地了解,这是我们的现状。
第二个问题来看看我们的目标。一般来说,我们都是打算在新公司大展身手,但这个答案太宽泛了,我们还需要进一步细化。在这个公司长远的发展是以后的事,我们还是把第一步的目标制定成能够达到上手工作的程度,比如,能够解决日常的项目问题。
那接下来,我们就需要回答第三个问题了,怎么才能够达到这个目标呢?我们需要做一个分解。
你可以回想一下过往的工作经验,要在一个项目上工作起来,先要了解什么呢?很多人的第一反应是技术,我是程序员嘛,当然就是技术优先了。估计大多数人进到项目里,都是一头奔进代码里,然后,从各种细节研究起来。技术肯定是你要了解的,但它不应该是第一位的。
技术解决的是“怎么做”的问题,而我们第一个应该了解的问题是“做什么”。一个软件到底在做什么,能够回答这个问题的就是业务。所以,我们排在第一优先级的事情应该是业务。
了解业务和技术都只是让你扮演好你个人的角色,但我们通常都是在一个团队内工作的,所以,还要解决好与其他人协作的问题,这就需要我们了解团队本身是如何运作的。
好,我们已经将大目标做了一个分解,得到了三个小目标:
业务;
技术;
团队运作。
从大图景入手
接下来,我们来针对每一个目标,进一步看看需要了解哪些内容。
业务
首先是业务。这是程序员入手新项目时最容易忽略的点。在这个专栏中,我在不同的模块中都说到了知识结构的重要性,没有结构的知识是零散的。所以,不管做任何项目,都要先从大图景入手。只有了解了大图景,各种知识才能各归其位。
对于一个普通的程序员来说,业务就是这个大图景。
如果你了解了业务,你自己就可以推演出基本的代码结构。但反过来,如果让你看了代码,从中推演出业务,那几乎是不可能的。
事实上,每次了解到一个业务,我都会在脑子中过一下,如果是我做这个业务,我会怎么做。这样一来,我就会先在整体上有一个预判,后面再对应到实际的代码上,就不会那么陌生了。
要了解业务,我一般都会请人给我讲一下,这个业务是做什么的,解决什么样的问题,具体的业务流程是什么样子的,等等。
在初期的了解中,我并不会试图弄懂所有的细节,因为我的目标只是建立起一个基本的框架,有了这个初步的了解,后续再有问题,我就知道该从哪里问起了。
理论上,了解业务是每个程序员都该做的事,但事实上,这也常常是出问题的地方。在请别人给我讲解业务的过程中,我发现,很多人是分不清业务和技术的,经常把二者混在一起讲。如果你跟着他的思路走,很容易就会陷入到对细节的讨论中。
所以,了解业务时,一定要打起精神,告诉自己,这个阶段,我要了解的只是业务,千万别给我讲技术。
技术
了解完业务就该到技术了。这是程序员最喜欢的话题。但即便是了解技术也要有个顺序所以我们先从宏观内容开始。第一个问题就是这个系统的技术栈Java、JavaScript 还是.NET这样我就可以对用到的工具和框架有个大致的预期。
接下来是系统的业务架构,这个系统包含了哪些模块,与哪些外部系统有交互等等。最好能够有一张或几张图将架构展现出来。现实情况是,不少项目并没有现成的图,那就大家一起讨论,在白板上一起画一张出来,之后再来慢慢整理。
有了一个初步的体系,接下来,就可以稍微深入一些。
我会选择从外向内的顺序了解起。首先是外部,这里的外部包括两个部分:
这个系统对外提供哪些接口,这对应着系统提供的能力;
这个系统需要集成哪些外部系统,对应着它需要哪些支持。
一旦涉及到与外部打交道,就涉及到外部接口是什么样子的,比如,是用 REST 接口还是 RPCRemote Procedure Call远程方法调用 调用,抑或是通过 MQMessage queue消息队列传递消息。
不要简单地认为所有接口都是你熟悉的,总有一些项目会采用不常见的方式,比如,我曾见过有系统用 FTP 做接口的。
所有这些都相当于信息承载方式,再进一步就是了解具体的信息是什么格式,也就是协议。
今天常见的协议是 JSON 格式或者是基于某个开源项目的二进制编码比如Protocol Buffers、Thrift 等等。一些有年头的系统可能会采用那时候流行的协议比如XML有一些系统则采用自己特定领域的协议比如通信领域有大量3GPP 定义的协议。
一般来说,从外部接口这件事就能看出一个项目所处的年代,至少是技术负责人对技术理解的年代。
了解完外部,就该了解内部了。了解内部系统也要从业务入手,对应起来就是,这个系统由哪些模块组成,每个模块承担怎样的职责。如果系统已经是微服务,每个服务就应该是一个独立的模块。
通常这也是一个发现问题的点,很多系统的模块划分常常是职责不清的,因此会产生严重的依赖问题。在前面的内容中,我多次提到限界上下文,用限界上下文的视角衡量这些模块,通常会发现问题,这些问题可以成为后续工作改进的出发点。
业务之后是技术,对应着我需要了解分层。前面说过,分层结构反映着系统的抽象。我希望了解一个模块内部分了多少个层,每个层的职责是什么。了解了这些对系统的设计,也就对系统有了一个整体的认识。
设计之后,就到了动手的环节,但还不到写代码的时候。我会先从构建脚本开始,了解项目的常用命令。我预期从版本控制里得到的是一个可以构建成功的脚本,如果不是这样,我就知道哪里需要改进了。
最后才是代码,比如,代码的目录结构、配置文件的位置、模块在源码上的体现等等,这是程序员最熟悉的东西,我就不多说了。作为初步的接触,了解基本的东西就够了,代码是我们后期会投入大量精力的地方,不用太着急。
团队运作
最后,我们还要了解一下团队运作。同样从外部开始,这个团队有哪些外部接口,比如,需求是从哪来的,产品最终会由谁使用,团队需要向谁汇报。如果有外部客户,日常沟通是怎么安排的。
再来就是内部的活动,一方面是定期的活动,比如,站会、回顾会议、周会,这些不同活动的时间安排是怎样的;另一方面是团队的日常活动,比如,是否有每天的代码评审、是否有内部的分享机制等等。
通过了解这些内容,基本上可以大致判断出一个团队的专业程度,也可以知道自己需要帮助的时候,可以找谁帮忙,为自己更好地融入团队打下基础。
你也许会问,了解这么多东西需要很长时间吧?其实不然,因为只需要从整体上有认知,如果有人很清楚团队现状的话,你可以去请教,也许一天就够了,这也是我往往能够快速上手的原因。接下来,就该卷起袖子干活了!
总结时刻
我给你介绍了怎么把前面学到的知识运用在了解一个项目上,按照业务、技术和团队运作三个方面去了解。
大多数程序员习惯的工作方式,往往是从细节入手,很难建立起一个完整的图景,常常是“只见树木不见森林”,而我的方式则是从大到小、由外而内,将要了解的内容层层分解,有了大图景之后,很容易知道自己做的事情到底在整体上处于什么样的位置。我把上面的内容总结了成一份供你参考。
附赠一点小技巧:使用“行话”。在交流的过程中,学习一点”行话“。这会让人觉得你懂行,让你很快得到信任,尽早融入团队。
如果今天的内容你只能记住一件事,那请记住:了解一个项目,从大图景开始。
最后,我想请你分享一下,你在入职一个新公司遇到过哪些困难呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,122 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
39 面对遗留系统,你应该这样做
你好,我是郑晔。
在上一讲中,结合着“新入职一家公司”的场景,我给你讲了如何在具体情况下应用我们前面学到的知识。这一讲,我们再来选择一个典型的实际工作场景,将所学综合应用起来。这个场景就是面对遗留系统。
在《34 | 你的代码是怎么变混乱的?》中,我给你讲了代码是会随着时间腐化的,无论是有意,还是无意。即便是最理想的场景,代码设计得很好,维护得也很精心,但随着技术的不断升级进步,系统也需要逐步升级换代。
比如,我们一直认为电信是一个独特的领域,与 IT 技术是完全独立的,学好 CTCommunication Technology通信技术就可以高枕无忧了。但随着 IT 技术的不断发展,今天的电信领域也开始打破壁垒,拥抱 IT 技术,提出了 ICT 的概念Information and Communications Technology信息通信技术
所以,无论怎样,系统不断升级改造是不可避免的事。问题是,你连自己三个月前写的代码都不愿意维护,那当面对庞杂的遗留系统时,你又该何去何从呢?
很多人的第一直觉是,我把系统重写一下就好了。不经思考的重写,就像买彩票一样,运气好才能写好,但大多数人没有这么好运气的,我们不能总指望买彩票中大奖改变生活。那有什么稍微靠谱的一点的路呢?
分清现象与根因
面对庞大的遗留系统,我们可以再次回到思考框架上寻找思路。
Where are we?(我们现在在哪?)
Where are we going?(我们要到哪儿去?)
How can we get there?(我们如何到达那里?)
第一个问题,面对遗留系统,我们的现状是什么呢?
我在这个专栏前面的部分,基本上讨论的都是怎么回答目标和实现路径的问题。而对于“现状”,我们关心的比较少。因为大多数情况下,现状都是很明显的,但这一次不一样。也许你会说,有什么不一样,不就是遗留系统,烂代码,赶紧改吧。但请稍等!
请问,遗留系统和烂代码到底是不是问题呢?其实并不是,它们只是现象,不是根因。
在动手改动之前,我们需要先分析一下,找到问题的根因。比如,实现一个直觉上需要两天的需求,要做两周或更长时间,根因是代码耦合太严重,改动影响的地方太多;再比如,性能优化遇到瓶颈,怎么改延迟都降不下来,根因是架构设计有问题,等等。
所以最好先让团队坐到一起让大家一起来回答第一个问题现状到底是什么样的。还记得我在《25 | 开发中的问题一再出现,应该怎么办?》中提到的复盘吗?这就是一种很好的手段,让团队共同确认现状是什么样子的,找到根因。
为什么一定要先做这个分析,直接重写不就好了?因为如果不进行根因分析,你很难确定问题到底出在哪,更关键的是,你无法判断重写是不是真的能解决问题。
如果是架构问题,你只进行模型的调整是解决不了问题的。同样,如果是模型不清楚,你再优化架构也是浪费时间。所以,我们必须要找到问题的根源,防止自己重新走上老路。
确定方案
假定你和团队分析好了遗留系统存在问题的根因,顺利地回答了第一个问题。接下来,我们来回答第二个问题:目标是什么。对于遗留系统而言,这个问题反而是最好回答的:重写某些代码。
你可能会问,为什么不是重构而是重写呢?以我对大部分企业的了解,如果重构能够解决的问题,他们要么不把它当作问题,要么早就改好了,不会让它成为问题。所以我们的目标大概率而言,就是要重写某些代码。
但是,在继续讨论之前,我强烈建议你,先尝试重构你的代码,尽可能在已有代码上做小步调整,不要走到大规模改造的路上,因为重构的成本是最低的。
我们真正的关注点在于第三个问题:怎么做?我们需要将目标分解一下。
要重写一个模块,这时你需要思考,怎么才能保证我们重写的代码和原来的代码功能上是一致的。对于这个问题,唯一靠谱的答案是测试。对两个系统运行同样的测试,如果返回的结果是一样的,我们就认为它们的功能是一样的。
不管你之前对测试是什么看法,这个时候,你都会无比希望自己已经有了大量的测试。如果没,你最好是先给这个模块补测试。因为只有当你构建起测试防护网了,后续的修改才算是走在坚实的道路上。
说到遗留代码和测试我推荐一本经典的书Michael Feathers 的《修改代码的艺术》Working Effectively with Legacy Code从它的英文名中你就不难发现它就是一本关于遗留代码的书。如果你打算处理遗留代码也建议你读读这本书。
在2007年我就给这本书写了一篇书评我将它评价为“这是一本关于如何编写测试的书”它会教你如何给真实的代码写测试。
这本书对于遗留系统的定义在我脑中留下了深刻印象:遗留代码就是没有测试的代码。这个定义简直就是振聋发聩。按照这个标准,很多团队写出来的就是遗留代码,换言之,自己写代码就是在伤害自己。
有了测试防护网,下一个问题就是怎么去替换遗留系统,答案是分成小块,逐步替换。你看到了,这又是任务分解思想在发挥作用。
我在《36 | 为什么总有人觉得5万块钱可以做一个淘宝》中提到淘宝将系统改造成 Java 系统的升级过程,就是将业务分成若干的小模块,每次只升级一个模块,老模块只维护,不增加新功能,新功能只在新模块开发,新老模块共用数据库。新功能上线,则关闭老模块对应功能,所有功能替换完毕,则老模块下线。
这个道理是普遍适用的,差别只是体现在模块的大小上。如果你的“小模块”是一个系统,那就部署新老两套系统,在前面的流量入口做控制,逐步把流量从老系统转到新系统上去;如果“小模块”只在代码层面,那就要有一段分发的代码,根据参数将流程转到不同的代码上去,然后,根据开发的进展,逐步减少对老代码的调用,一直到完全不依赖于老代码。
这里还有一个小的建议按照分模块的做法将新代码放到新模块里按照新的标准去写新的代码比如测试覆盖率要达到100%,然后,让调用入口的地方依赖于这个新的模块。
最后,有了测试,有了替换方案,但还有一个关键问题,新代码要怎么写?
要回答这个问题,我们必须回到一开始的地方,我们为什么要做这次调整。因为这个系统已经不堪重负了,那我们新做的修改是不是一定能解决这个问题呢?答案是不好说。
很多程序员都会认为别人给留下的代码是烂摊子,但真有一个机会让你重写代码,你怎么保证不把摊子弄烂?这是很多人没有仔细思考过的问题。
如果你不去想这个问题,即便今天你重写了这段代码,明天你又会怨恨写这段代码的人没把这段代码写好,只不过,这个被抱怨的人是你自己而已。
要想代码腐化的速度不那么快,一定要在软件设计上多下功夫。一方面,建立好领域模型,另一方面,寻找行业对于系统构建的最新理解。
关于领域模型的价值,我在专栏前面已经提到过不少次了。有不少行业已经形成了自己在领域模型上的最佳实践,比如,电商领域,你可以作为参考,这样可以节省很多探索的成本。
我们稍微展开说说后面一点,“寻找行业中的最新理解”。简言之,我们需要知道现在行业已经发展到什么水平了。
比如说,今天做一个大访问量的系统,我们要用缓存系统,要用 CDN而不是把所有流量都直接转给数据库。而这么做的前提是内存成本已经大幅度降低缓存系统才成为了标准配置。拜 REST 所赐,行业对于 HTTP 的理解已经大踏步地向前迈进CDN 才有了巨大的进步空间。
而今天的缓存系统已经不再是简单的大 Map有一些实现得比较好的缓存系统可以支持很多不同的数据结构甚至支持复杂的查询。从某种程度上讲它们已经变成了一个性能更好的“数据库”。
有了这些理解,做技术选型时,你就可以根据自己系统的特点,选择适合的技术,而不是以昨天的技术解决今天的问题,造成的结果就是,代码写出来就是过时的。
前面这个例子用到的是技术选型,关于“最新理解”还有一个角度是,行业对于最佳实践的理解。
其实在这个专栏里,我讲的内容很多都是各种“最佳实践”,比如,要写测试,要有持续集成,要有自动化等等,这些内容看似很简单,但如果你不做,结果就是团队很容易重新陷入泥潭,继续苦苦挣扎。
既然选择重写代码,至少新的代码应该按照“最佳实践”来做,才能够尽可能减缓代码腐化的速度。
总之,改造遗留系统,一个关键点就是,不要回到老路上。
总结时刻
我们把前面学到的各种知识运用到了“改造遗留系统”上。只要产品还在发展,系统改造就是不可避免的。改造遗留系统,前提条件是要弄清楚现状,知道系统为什么要改造,是架构有问题,还是领域模型混乱,只有知道根因,才可能有的放矢地进行改造。
改造遗留系统,我给你几个建议:
构建测试防护网,保证新老模块功能一致;
分成小块,逐步替换;
构建好领域模型;
寻找行业中关于系统构建的最新理解。
如果今天的内容你只能记住一件事,那请记住:小步改造遗留系统,不要回到老路上。
最后,我想请你分享一下,你有哪些改造遗留系统的经验呢?欢迎在留言区分享你的做法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,149 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
划重点 关于“以终为始”你要记住的9句话
你好,我是郑晔。
“以终为始”这个主题模块已经全部更新完毕,相信通过对各种实践的深入讲解,你已经对“以终为始”这个原则有了更为全面和透彻的理解。
为了帮助你更好地回顾和复习,我为每个主题模块增设了“划重点”的加餐内容。现在,我就带你一起梳理一下“以终为始”主题的核心要点。
重点复习
在这个模块中,我们学习到了一些行业最佳实践。
DoD确定好完成的定义减少团队内部的理解不一致。
用户故事,细化出有价值的需求。
持续集成,通过尽早集成,减少改动量,降低集成的难度。
精益创业,减少过度开发不确定性产品带来的浪费。
迭代0在项目开始之前做好一些基础准备。
还学习到一些重要的思维转变。
任何事物都要经过两次创造一次是在头脑中的创造也就是智力上的或者第一次创造Mental/First Creation然后才是付诸实践也就是实际的构建或第二次创造Physical/Second Creation
在更大的上下文内发现自己的“终”。
通过推演,找到通往“终”的路径。
用可度量的“数字”定义自己的“终”。
实战指南
在每一篇文章的结尾,我们还将全篇内容浓缩为“一句话”的实战指南,希望你可以迅速上手,把“以终为始”的原则运用在实际工作之中,我们一起来回顾一下这些实战指南。
遇到事情,倒着想。-
——《02 | 以终为始:如何让你的努力不白费?》
在做任何事之前,先定义完成的标准。-
——《03 | DoD的价值你完成了工作为什么他们还不满意
在做任何需求或任务之前,先定好验收标准。-
——《04 | 接到需求任务,你要先做那件事?》
尽早提交代码去集成。-
——《05 | 持续集成:集成本身就是写代码的一个环节》
默认所有需求都不做,直到弄清楚为什么要做这件事。-
——《 06 | 精益创业:产品经理不靠谱,你该怎么办?》
扩大自己工作的上下文,别把自己局限在一个“程序员”的角色上。-
——《07 | 解决了很多问题,为什么你依然在“坑”里?》
在动手做一件事之前,先推演一番。-
——《08 | 为什么说做事之前要先进行推演?》
问一下自己,我的工作是不是可以用数字衡量。-
——《09 | 你的工作可以用数字衡量吗?》
设计你的迭代0清单给自己的项目做体检。-
——《10 | 启动开发之前,你应该准备什么?》
额外收获
在这个部分的最后,针对大家在学习过程中的热门问题,我也进行了回答,希望你懂得:
作为程序员,你可以管理你的上级;
拿老板说事的产品经理,你可以到老板面前澄清;
喜欢无脑抄袭的产品经理,让他回去先想清楚到底抄的是什么;
分清楚需求和技术,产品经理和开发团队各自做好各自的事。-
——《答疑解惑 | 如何管理你的上司?》
留言精选
同学们的留言很踊跃,也很有价值。精彩的留言本身就是对文章内容的补充与丰富,在此我挑出一些优秀的留言与你分享。
在讲高效工作的思考框架时,张维元 同学提到:
思考框架是道,原则是演化下的术,我们从 A → B有无穷无尽的路径最有效的唯有那条直线。本质上各个维度、原则不限于作者提到的四项原则都是帮助我们更好地定位 A 在哪里B 在哪里,那条直线在哪里。
对于以终为始的原则WTF 同学提到:
“以终为始”,最常见的一个实践就是计划倒排了。先定时间,然后看功能是不是做不过来得砍掉一些,人力是不是不够需要补充一些,提前预知规避风险。
对于用户故事的验收标准liu 同学提到:
程序员的核心职责是如何实现产品功能,怎么实现功能;前提是理解产品功能,需要实现哪些功能。有些项目经理,产品经理与程序员角色混淆。你同他谈功能,他同你谈技术实现,你同他谈技术,他同你谈产品(需要实现哪些功能)。
大家对沙盘推演的话题很感兴趣。其中,西西弗与卡夫卡 同学提到:
推演可以发现达成目标会涉及到哪些部门、哪些利益相关者,需要哪些资源,以及他们需要何时怎样的配合。
ZackZeng 同学也针对这个话题留言:
项目上线之前一般都会有一个launch plan, 数据库迁移这种项目不去考虑上线回滚我认为是设计上的缺失。我们公司的launch plan一般是写成一步一步的checklist, 在上线之前会做同伴审查。
Scott 同学也提到:
我觉得领导说先跑通再说和事前推演是不矛盾的很多时候我们需要一个poc来证明这个项目是可行的这其实也是事前推演的一部分。上线要事无巨细的检查推演和快速跑通poc不矛盾当然现实世界是大家就急着把poc当正式产品上线了这是无数个悲剧故事的序章。
休息一下马上回来 同学对推演过程进行了很好地补充:
上线前,哪些机器什么配置,应该有一个预期,甚至提前准备好。
adang 同学也分享了他在工作中的感悟:
想清楚了才能写清楚,这是我在编程工作非常认可的一句话,并且我也认为它是区分合格与不合格开发工程师的重要区别。软件开发过程中,最常见的例子就是拿到需求后不管三七二十一,上来就开始撸代码,但最后往往返工不断,质量问题层出不穷,而且加班没完没了,这里面一个根本原因就是没有系统地想清楚,但很多人都觉得前期澄清需求、分析设计是浪费时间,只有编码才是真正的创造价值,这就是差距。
在讲到工作要尽量用数字衡量时,西西弗与卡夫卡 同学提到:
比如开发常常关注的是产品经理提的功能有没有实现实际上也应该了解做出来的有多少人使用每个页面有多少人使用。此外看开发是否努力勤奋不要光听他说而是要看看他提交git有多频繁、提交的时间段、代码量有多少。代码质量可以用bug数/代码量来衡量。当然,这些量化未必科学,甚至会被误用,但总胜过凭印象拍脑袋的判断。
大彬 同学也提到:
上周我把一个方案进行推迟了让同事去搜集某项指标的数据没数据一切方案都是空谈。AB测试留言量阅读量转发量一切数据都是下一步决策和改进的基础。
篇幅限制,就为大家分享这么多,感谢同学们的精彩留言。留言区还有很多同学提出了各种问题,其实都可以用任务分解的方式去解决。不着急,我们下一个主题的内容就是“任务分解”。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,172 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
划重点 关于“任务分解”,你要重点掌握哪些事?
你好,我是郑晔,恭喜你,又完成了一个模块的学习。
在这个模块中,我主要讲解的是“任务分解”这个知易行难的工作原则。普通人与高手之间的差异,很大程度上取决于任务分解的粒度大小。但真正理解并应用好“任务分解”的原则并不容易,希望你能勤于练习,将知识内化成为你的能力。
重点复习
在这个模块中,我们学习到了一些最佳实践:
测试金字塔-
-- 行业中测试组合的最佳实践。-
-- 多写单元测试是关键。
测试驱动开发-
-- 测试驱动开发的节奏是:红——绿——重构,重构是测试驱动开发区别于测试先行的关键。-
-- 有人把测试驱动开发理解成测试驱动设计,它给行业带来的思维改变是,编写可测的代码。
艾森豪威尔矩阵Eisenhower Matrix-
-- 将事情按照重要和紧急进行划分。-
-- 重要且紧急的事情要立即做。重要但不紧急的事情应该是我们重点投入精力的地方。紧急但不重要的事情,可以委托别人做。不重要不紧急的事情,尽量少做。
最小可行产品-
-- “刚刚好”满足客户需求的产品。-
-- 在实践中,要用最小的代价找到一条可行的路径。
另外,我还提到了一些可以直接在工作中应用的做法和评判标准:
尽量不写 static 方法;
主分支开发模型是一种更好的开发分支模型;
好的用户故事应该符合 INVEST 原则;
估算是一个加深对需求理解的过程,好的估算是以任务分解为基础的;
好的测试应该符合 A-TRIP。
我也带你学习了一些重要的思想,帮你更好地改善自己的开发工作:
分而治之,是人类解决问题的基本手段;
软件变更成本,它会随着时间和开发阶段逐步增加;
测试框架把自动化测试作为一种最佳实践引入到开发过程中,使得测试动作可以通过标准化的手段固定下来;
极限编程之所以叫“极限”,它背后的理念就是把好的实践推向极限;
大师级程序员的工作秘笈是任务分解,分解到可以进行的微操作;
按照完整实现一个需求的顺序安排开发任务。
实战指南
在“任务分解”的板块,我也将每篇内容浓缩为“一句话”的实战指南,现在一起回顾一下。
动手做一个工作之前,请先对它进行任务分解。-
—— 《11 | 向埃隆·马斯克学习任务分解》
多写单元测试。-
——《12 | 测试也是程序员的事吗?》
我们应该编写可测的代码。-
——《13 | 先写测试,就是测试驱动开发吗?》
将任务拆小,越小越好。-
——《14 | 大师级程序员的工作秘笈》
按照完整实现一个需求的顺序去安排分解出来的任务。-
——《15 | 一起练习:手把手带你拆任务》
要想写好测试,就要写简单的测试。-
——《16 | 为什么你的测试不够好?》
想要管理好需求,先把需求拆小。-
——《17 | 程序员也可以“砍”需求吗?》
尽量做最重要的事。-
——《18 | 需求管理:太多人给你安排任务,怎么办?》
做好产品开发,最可行的方式是采用 MVP。-
——《19 | 如何用最小的代价做产品?》
额外收获
在这个部分的最后,针对大家在学习过程中的热门问题,我也进行了回答,希望你懂得:
对不了解技术的任务,先要去了解技术,然后再做任务分解;
通过一次技术 Spike ,学习新技术;
丢弃掉在 Spike 过程中开发的原型代码;
分清目标与现状,用目标作为方向,指导现状的改变;
多个功能并行开发可以考虑使用 Feature Toggle
在遗留系统上做改造可以考虑使用 Branch by Abstraction 。
——《答疑解惑 | 如何分解一个你不了解的技术任务?》
留言精选
在“任务分解”的模块中,有很多同学非常用心,将自己的学习心得和工作中的经验进行了分享,在此我挑选了一些同学的留言,与你一起学习。
在讲大师级程序员的工作秘笈时,西西弗与卡夫卡 同学提到:
最近在做战略拆解,都是一样的道理。战略飘在空中遥不可及,要落地就必须拆解。比如说达成目标有哪几个方面可以努力,各方面都需要做哪些事,这是路径。这些路径里哪些优先级最高,需要配置哪些组织资源。心里有数之后就是制订计划时间表。
另外,西西弗与卡夫卡 同学还为Spike给出了一个很生动的解释
“技术Spike”可以翻译成“技术撩”就是撩妹的那个撩。试探下有戏就继续撩不动就算或者放一段时间再说。
针对分解的粒度问题,大彬 同学也分享了自己的心得:
我会的任务分解不仅可执行粒度还很细。比如说我要修复一个rpc接口的bug。我会列出每个代码的修改点要修改的测试要增加的测试合并到哪个分支修改rpc文档文档中有哪些点要修改。-
每一步都非常容易执行看起来没多少必要但在我当前的工作环境特别有用1事前思考不会造成遗漏2任务实施过程中经常被打断比如测试有疑问和你讨论、主管找你谈事、紧急会议来了这种“硬中断”完全打破了节奏而任务列表让我清楚知道当前做了多少该从哪一步继续。
对于单元测试,树根 同学提到:
我的想法可以在复杂度高,重要核心的模块先开始写单元测试。特别是公用、底层的,因为这些靠功能测试很难覆盖。-
单元测试难以推行主要是没有碰到质量的痛点,通常都依靠测试工程师来保证质量。我们之前就遇到过质量崩塌,倒逼着我们去做,以保证质量。
树根 同学还分享了自己的任务分解实践心得:
刚改了编程习惯先在notion写出思路、需要用到的知识点api等写出各个小任务然后对应写出关键代码段。最后真正敲代码就花了10来分钟。-
重新开始看极客时间就看到这篇,实践过来读,很认同。-
我特别佩服国外的工程师写的代码代码块很小非常清晰易读。特别记得之前参加infoq会议听socketio作者的分享看他现场撸码思路、代码结构都非常顺畅和清晰。
关于TDD的具体应用 萧 同学提到了遇到的问题:
不久前第一次接触TDD时为它的思想而惊叹感觉它能极大的提升编码效率编码后期的大量重构还能保障代码质量。后面自己在写代码的时候也注意使用它的思想但说实话理解是一回事用起来就不是那么回事了很多的东西还不是太熟练前期说实话比较耗时间有些拖进度。-
由于也毕业不久经验上有些欠缺还不太熟练有些测试还不知道怎么写。现在写多了一点感受到的是代码质量上的提高bug比起以前少了需求变更下改动也不伤筋动骨了但还是有许多感觉做的不够好的地方。看了这篇文章补充了对TDD的认知感受到如果和任务分解结合起来TDD会有更好的效果期待后面的文章
关于“任务分解”的执行问题,如明如月 同学分享了感悟:
对任务分解的体会非常深刻刚入职的时候任务评估不准。现在想想主要是两个原因1需求梳理的不清晰还没清楚地搞明白需求就动手写代码导致返工和一些“意想不到”的情况。2任务分解做的不好没有将任务分解成非常清晰地可执行的单元导致有些时候无从下手而且任务时间评估不准确。
在讲到为什么很多人的测试不够好这个问题时, 毅 同学提到:
本节课我有以下几点体会:-
1从开发者的视角看编码和测试是不分家的是可以通过重构形成良性生态圈的类似之前课程中的反馈模型和红绿重构模型-
2A-TRIP是个很好的总结和行动指南在今后工作中应一以贯之把工作做到扎实有成效-
3对文中提到的数据库依赖的问题我也说说自己的浅见。我觉得在测试代码中尽量避免与数据库打交道测试更关注领域与业务往往爆雷更多的是resource和service模型的变化往往牵动着表结构的变化与其两头兼顾不如多聚焦模型。-
我常用的做法是用例配合若干小文件(数据忠实于模型),保证库操作临门一脚前所有环节都是正确的,同时方便适应变化。一旦出现异常,也比较容易定位是否是数据库操作引发的问题。 (此点基于,我在工作中发现,项目型程序员大多是先急于把表结构定义出来,好像不这么做,写代码就不踏实)
针对需求的管理问题WL 同学提到的点也非常关键:
程序员也应该更积极主动一些, 最好能推动事情发展, 当这件事情由你推动时,主动权就在你的手里了。
感谢同学们的精彩留言。在下一个模块中,我将为大家分享“沟通反馈”这个原则的具体应用。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,147 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
答疑解惑 如何分解一个你不了解的技术任务?
你好,我是郑晔。
在“任务分解”这个模块,我以测试为核心,讲解了任务分解这个原则,同时也给你介绍了一些最佳实践,帮助你更好地理解任务分解的重要性,以及应该怎样分解任务。
同学们对任务分解这个原则大多是表示认同的,但就一些具体应用的场景,还是提出了自己的问题。
在今天的答疑中,我选择了几个非常典型的问题来进行深入讨论。
问题1面对不了解的技术我该如何分解任务
pyhhou 同学提到
很想听听老师的意见,就是在一个自己不熟悉的,充满未知的项目中该怎么更好地进行任务分解?-
——《11 | 向埃隆·马斯克学习任务分解》
shniu 同学提到
想请问一下老师,面对探索型的需求,调研型的需求如何做任务分解呢?-
——《15 | 一起练习:手把手带你分解任务》
这是一个很好的问题。在这个模块讨论开发中的任务分解时我说的都是确定了解的某项技术比如数据库、REST 服务等等,因为这是开发中最常见的场景,也是最基础的能力,连熟悉的技术都做不好分解,就别说不熟悉的技术了。
那如果不了解这项技术呢?答案很简单,先把它变成你熟悉的技术。一旦变成了你熟悉的技术,你就可以应用在这个模块中学到的,面对确定性技术的分解方案。
我知道,这个答案你并不满意。其实,你真正的问题是,怎么把它变成你熟悉的技术。
我的答案是,做一次技术 Spike。这里之所以用英文是因为我没有找到一个特别合适的词来翻译。Spike 这个词的原意是轻轻地刺,有人把它翻译成调研,我觉得是有些重了。
Spike 强调的重点在于快速地试,和调研的意思不太一样。既然是快速地试,就要在一定的时间内完成,比如,五人天,也就是一个人一周的时间,再多就不叫 Spike 了。一些简单的技术,用一天时间做 Spike 就差不多了。
这里强调的重点在于,要做一次技术 Spike。Spike 的作用就在于消除不确定性,让项目经理知道这里要用到一项全团队没有人懂的技术,需要花时间弄清楚。
项目经理比你更担心不确定性,你清楚地把问题呈现在他面前,项目经理是可以理解的,他更害怕的是,做到一半你突然告诉他,项目进度要延期。
把事情做在前面,尽早暴露问题,正是我们要在下一个模块要讨论的一个主题。
好,那么接下来的问题变成了:怎么做技术 Spike 呢?
这里,我假设你已经通过各种渠道,无论是新闻网站,还是技术 blog又或是上级的安排对要用的技术有了一些感性的认识至少你已经知道这项技术是干什么的了。
接下来,我们要进入到技术 Spike 的任务分解。
首先,快速地完成教程上的例子。稍微像样点的技术都会有一个教程,跟着教程走一遍,最多也就是半天的时间。之所以要快速地完成教程上的例子,是为了让你有一个直观的认识,这时候,你对这项技术的认识就会超过新闻网站的报道。
其次,我们要确定两件事:这项技术在项目中应用场景和我们的关注点。
技术最终是要应用到项目中的,本着“以终为始”的原则,我们就应该奔着结果做,整个的 Spike 都应该围绕着最终的目标做。
很多程序员见到新技术都容易很兴奋,会把所有的文档通读一遍。如果是技术学习,这种做法无可厚非,但我们的目标是做 Spike快速地试没有那么多时间必须一切围绕结果来。
项目中的场景有无数,我们需要选择最重要的一个场景,而针对着这项最重要的场景,我们还要从这项技术无数功能中选取最需要的几个,而不是“满天撒网”。
再有是我们要找准关注点,比如,采用新的缓存中间件是为了提高性能,那关注点就是性能,采用新的消息队列是为了提升吞吐,那关注点就是吞吐。我们选用一项新技术总是有自己的一些假设,但这些假设真的成立吗?这是我们需要验证的。
无论是场景,还是关注点,我们要在前面先想清楚,其目的就是为了防止发散。当时间有限时,我们只能做最重要的事,这也是我在专栏中不断强调的。
确定好场景和关注点,接下来,我们要开发出一个验证我们想法的原型了。这个原型主要目的就是快速地验证我们对这项技术的理解是否能够满足我们的假设。开发一个只有主线能力的原型,对大部分程序员来说并不难,这里就不赘述了。
当你把想法全部验证完毕,这项技术就已经由一项不熟悉的技术变成了熟悉的技术。我们前面的问题也就迎刃而解了。这时候,你就可以决定,对于这项技术,是采纳还是放弃了。
但是,我这里还有一点要提醒,当你确定要使用这项技术时,请丢弃掉你的原型代码。
你或许会说,我辛辛苦苦写了几天的代码就这么丢了?是的,因为它是原型,你需要为你的项目重新设计。
如果顺着原型接着做,你可能不会去设计,代码中会存在着大量对这项技术直接依赖的代码,这是值得警惕的,所有第三方技术都是值得隔离的。这是我们会在“自动化”模块讨论的内容。
问题2项目时间紧该怎么办
在这个模块里,我花了大量的篇幅在讲测试,很多同学虽然认同测试的价值,却提出了开发中普遍存在的一些情况。
玄源 同学提到
很多时候项目时间很紧经常会提测后再补测试或者直接code review测试就不写了。-
——《12 | 测试也是程序员的事吗?》
这是一个非常典型的问题,我在之前做咨询的时候,经常会遇到很多团队说,项目时间紧,所以,他们没有时间做测试。
这里面有一个非常经典误区:混淆了目标与现状。目标是应该怎么做,现状是我们正在怎么做。我们都知道现状是什么样的,问题是,你对现状满意吗?如果每个人都对现状是满意的,就不会有人探索更好的做法。
假设现在不忙了,你知道该怎么改进吗?
遗憾的是,很多人根本回答不了这个问题,因为忙是一种借口,一种不去思考改进的借口。
我之所以要开这个专栏,就是为了与大家探讨行业中一些好的做法。
回到这个具体问题上,我们在专栏开始就在讲以终为始,首先要有一个目标,专栏中介绍的各种实践都可以成为你设置目标的参考。有了这个目标再来考虑,如何结合我们工作的现状来谈改进。
接下来我们以测试为例讨论一下具体的改进过程。用我们专栏最初讲过的思考框架看一下假如我们的现状是团队之前没什么自动化测试而我们的目标是业务代码100%测试覆盖。如果要达成这个目标,我们需要做一个任务分解。
这时你会发现,分解的过程主要需要解决两方面的问题,一个是与人的沟通,另一方面是自动化的过程。
与人的沟通,就是要与团队达成共识。关于这点,你可以尝试将专栏里讲到的各种最佳实践以及其背后的逻辑,与团队进行沟通,也可以把专栏文章分享给他们。
再来,我们考虑一下自动化的改进,因为我们的现状是没什么测试,所以,不能强求一步到位,只能逐步改进。下面我给出了一个具体的改进过程:
把测试覆盖率检查加入到工程里,得到现有的测试覆盖率。
将测试覆盖率加入持续集成,设定当前测试覆盖率为初始值。测试覆盖率不达标,不许提交代码。
每周将测试覆盖率调高比如5%或10%直到测试覆盖率达到100%。
这样,我们就找到了一条由现状通往目标的路径,接下来,就是一步一步地具体实施了,由团队成员逐步为已有代码补充测试。
问题3多个功能同时开发怎么办
妮可 同学提到
公司经常存在有两个需求同时开发的情况。请问老师所在的团队如何解决单分支上线不同步的情况呢?-
——《14 | 大师级程序员的工作秘笈》
在主分支开发模型中有一些常见的解决多功能并行开发的方法其中Feature Toggle 是最常用的一个,也就是通过开关,决定哪个功能是对外可用的。
关于这一点Y024 同学也补充了一些信息。
Feature toggle功能开关分享两篇文章-
1. Feature Toggles (aka Feature Flags)-
2.使用功能开关更好地实现持续部署
不过如果用户故事划分得当你可以很快完成一个完整的业务需求。实际上Feature Toggle 只是一个非常临时的存在。但如果你在一个遗留系统上工作一个功能要跨越很长的周期Feature Toggle 才显得很有用。
额外补充一个与主分支开发模型相关的常用技术,如果你想对遗留系统做改造,传统的做法是,拉出一个分支。
如果在一个分支上怎么做呢?可以考虑采用 Branch by Abstraction简言之再动手改造之前先提取出来一个抽象把原先的实现变成这个抽象的一个实现然后改造的过程就是提供这个抽象的一个新实现。这种做法对设计能力有一定要求所以对很多团队来说这是一个挑战。
好,今天的答疑就到这里,请你回想一下,你在工作中是否也遇到过类似的问题呢?你又是怎么解决的呢?欢迎大家在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,145 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
答疑解惑 如何在实际工作中推行新观念?
你好,我是郑晔。
在整个专栏的最后一个大模块”综合运用”中,我们把前面学到的各种原则和知识穿插在一起应用在了不同的场景中。在这个模块的答疑中,我们也综合汇总一次,把整个专栏中出现的一些有趣却还没有来得及讨论的问题放在一起。
问题1想要推行 DDD阻力很大怎么办
段启超 同学提到
想在公司内推行DDD阻力真的很大首先是很多人对DDD没概念需要一定的学习成本二是团队间相互隔离沟通成本很高起码的通用语言都很难达成。-
——《37 | 先做好DDD再谈微服务吧那只是一种部署形式》
段启超同学提到的这个问题是一个非常典型的问题,而且,这个问题并不仅仅局限于 DDD。你在一个地方看到了一些好东西技术、实践或是想法然后想把它运用在自己的项目中希望项目越做越好越来越顺利。但在实际情况中想在一个组织内推广一些不一样的东西都会面临层层阻力。
我在《40 | 我们应该如何保持竞争力?》中提到了一个学习模型,你只要在学习区不断地练习新技能,很快就可以超越同侪。其中的原因是,大部分人只习惯待在舒适区,在舒适区的人能力上的进步非常有限。也因为在舒适区实在太舒适了,走出舒适区会让人产生焦虑,所以,人的内心是惧怕改变的。
你有良好的愿望,驱动你自己去改变是一件可控的事,有愿意和你一起改变的人是一件幸运的事,但你指望所有人一下子和你走上同一条道路,这是一件几乎不可能的事,即便你是很高层的领导,让所有人与你保持一致也不现实。
我曾经在一个大公司做过敏捷咨询,这还是由他们顶层领导推动的敏捷转型,但依然是困难重重。那些习惯于待在自己舒适区的人总会找到各种神奇的理由告诉你,他们的情况有多么特殊,这些最佳实践在他们那里是不适用的。
我们放弃了吗?并没有。我们的做法是,找一个团队来做试点。
换句话说,我们找到了几个愿意改变的人,把这些最佳实践应用在他们的项目上。在这种情况下,大家的目标是一致的,就是希望让这个项目得到改善。所以,大家自然会想尽一切办法,克服遇到的困难。比如,我们当时的切入点是持续集成。
他们的代码都在老旧的 ClearCase 上,每个人修改文件要先去竞争文件锁,特别不利于小步提交,所以,我们推动着将 ClearCase 改成了稍微进步一点的 Subversion。好吧你能听出来这是一个有些年头的故事。
代码是用 C 语言编写的,在他们的代码规模下,编译时间会很长。于是,我们决定搭建一个分布式构建系统,这需要有很多台电脑。不过,他们的硬件是严格管控的,申请电脑是很困难的,虽然花了很大的力气,但最终我们做到了。
以往团队都是几天甚至几周才提交一次代码,我们先将代码提交的要求限定在每人每天至少提交一次,为此,我们专门坐下来与团队成员一起分解任务,将他们理解的大任务拆分成一个一个的小任务。
……
想做事,只需要一个理由就够了,不想做,理由有一万个。劝那些不想改变的人改变是异常耗时而且收效甚微。最好的办法是,找到愿意和你一起改变的人,做一件具体的事。
我们并没有劝说谁去听从我们的想法,只是在一个一个地解决问题。我们花了很长时间,最终建立起了持续集成,看到大屏幕上的绿色标识,我颇为感动。原本只需要一两天搭建的持续集成,在一个复杂组织中,它要花费那么长时间,这也是我从未经历过的。
当我们把这件事做成之后,其他团队看到了效果,开始纷纷效仿。于是,原本复杂的各种规定也开始纷纷松绑,比如,他们再也不需要为申请电脑发愁了。至于之前质疑我们的人,因为看到了成效,他们的关注点就成了怎么把事能做成。
后来我听说,他们在组织内部专门建立了一个持续集成中心,为各个团队提供了公共的构建资源,提升了整体的效率。
Linus Torvalds 曾经说过“Talk is cheap. Show me the code. ”讲道理很容易,但也难以让人真正的信服。同样,做事很难,但成果摆在那里,让人不得不信服。
在英文中对这种行为有一个说法叫“Lead by Example”通常用来形容团队领导以身作则的行事风格。当你寻求改变时无论你的角色是什么你都需要扮演好领导者的角色“Lead by Example”送给你
问题2测试怎么写
andyXH 同学提到
目前对于 TDD 还是处于理解状态不知道如何真正的在项目工程中使用。因为项目工程往往还有很多其他调用如rpc数据库服务第三方服务不知道在这个过程如何处理。期待老师在之后文章中讲解。-
——《13 | 先写测试,就是测试驱动开发吗?》
梦倚栏杆 同学提到
从数据库或者第三方api查询类内容需要写测试吗这种测试怎么写呢如果不需要写会发现大量展示类系统不需要写测试了感觉怪怪的。-
——《16 | 为什么你的测试不够好?》
闷骚程序员 同学提到
假设我要测试的函数是一个关于tcp的网络发送函数我想问一下老师在写类似这样功能的单元测试是怎么实现的-
——《39 | 面对遗留系统,你应该这样做》
TimFruit 同学提到
问个问题一般web服务依赖数据库这部分如何做好单元测试如果去掉数据库很难测试相应的sql语句。-
——《39 | 面对遗留系统,你应该这样做》
大家看到了,这是一类非常典型的问题。一般来说,如果写的测试是一些业务逻辑的测试,大多数人还知道怎么测,一旦涉及到外部系统、数据库,很多人就不知道该怎么办了。
我们先来回答一个问题,你要测外部系统的什么?
你当然会说,我的整个系统都依赖于外部系统,没有了它,我的系统根本运行不起来,不能完成工作啊!但是,我的问题是你要测的是什么?
我知道很多人一想到外部系统,第一反应是:“我的整段代码都是依赖于外部系统的,因为外部系统不好测,所以,我这段代码都没法测了。”如果你是这样想的,说明你的代码将对外部系统的依赖在业务代码中散播开了,这是一种严重的耦合。外部系统对你来说,应该只是一个接口。
我在《13 | 先写测试,就是测试驱动开发吗?》中说过,想写好测试,先要站在可测试的角度思考。假设我同意你关于外部系统不好测的观点,那应该做的是尽量把能测的部分测好。将对外部系统的依赖控制在一个小的范围内。
一个好的做法就是设计一个接口让业务代码依赖于这个接口而第三方依赖都放在这个接口的一个具体实现中。我在《34 | 你的代码是怎么变混乱的?》中提到了 SOLID 原则,这种做法就是 接口隔离原则ISP的体现。
如果你能够站在系统集成的角度思考这个部分就是系统与系统之间的集成点。我在《37 | 先做好DDD再谈微服务吧那只是一种部署形式》提到了 DDD。在 DDD 的战略设计中有一个概念叫上下文映射图Context Map在不同上下文中集成最常见的一种模式是防腐层Anti-Corruption LayerACL
很多系统在实现时就是缺少了防腐层,造成的结果就是系统耦合极其严重。因为外部服务的任何修改都会造成自己的代码跟着大幅度变动,更极端的情况是,我见过一个网关系统在自己的业务逻辑中直接依赖于第三方服务传过来的 JSON 对象,造成内存资源的极大浪费,网关本身极其不稳定。
至此,你知道了,如果有任何外部系统,都要设计防腐层,用接口做隔离。这样,才能保证你的业务代码是可测的。如果外部系统真的不好测,这种做法将大幅度降低不可测的比例,尽可能提高测试覆盖率。
我们前面的假设是,外部系统不好测,但真的不好测吗?
作为 Moco 这个模拟服务器的作者,我肯定是不会同意这个说法。如果你的系统依赖的外部系统是最常见的 REST 服务,那 Moco 就是给这种场景准备的。我给你看一个最简单的例子,这是 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 模式,用 JSON 配置出一个模拟服务器。
对于数据库的测试,如果你采用的是 Spring Framework它就提供了一套完整的方案比如你可以在运行测试时插入一些数据然后在测试执行完毕之后回滚回去保证测试的可重复性。
事实上,它对测试的支持已经非常强大了,远不止于数据库。如果你采用的是 Spring Boot对测试的支持就更加完整了但基础还是 Spring Framework 提供的。如果用到真实的数据库,最好是一套独立的本地数据库,保证环境的可控。
对于外部服务的测试,简言之,能模拟的就模拟,能本地的就本地。如果你的服务没有现成的工具支持,也许就是一个打造新工具的好时机。
总结一下。关于外部系统的测试,你可以先通过接口隔离开来,然后,通过模拟服务或本地可控的方式进行测试。
好,今天的答疑就到这里,你对这些问题有什么看法呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,150 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
答疑解惑 如何管理你的上级?
你好,我是郑晔。
在这个模块里,我围绕着“以终为始”这个原则为你进行了详细地讲解,还给你介绍了应用“以终为始”原则的一些行业最佳实践。
同学们的留言特别踊跃,很多同学表示有收获的同时,也提出了大量的问题,大家比较关心怎样将这些实践在自己的实际工作中落地,部分问题我已经在留言中回复了。在今天的答疑环节中,我挑选了一些非常典型的问题来更详细地回答一下。
问题1领导要求的无力反驳怎么办
achenbj 同学提到
讲得很好,感觉落地还需努力。我们就是领导给了功能,跟你说下要做啥,就那么做就行。没有了。-
—— 04 | 接到需求任务,你要先做哪件事?
Alexdown 同学提到
考虑到地位的不对等以及我的“人设”已经定型了,实施起来有点难度。-
—— 02 | 以终为始:如何让你的努力不白费?
这类问题很经典,很多同学留言提到,我就不一一列举了。
在我的职业生涯中,无数次听到不同的人有过同样的抱怨。我最初也觉得这是一个无解的问题,直到后来我读到了一本书。
管理大师彼得·德鲁克有一本经典著作《卓有成效的管理者》,虽然标题上带着管理者几个字,但在我看来,这是一本告诉我们如何工作的书,每个人都可以读一下。
当年我读这本书时,其中的一个观点让我很受震撼:如何管理上级。
什么?上级也可以管理?这对于我们这些习惯了接受上级指挥的人来说,观念上的转变几乎是天翻地覆一般。
在很多人看来,自己累死累活,只是因为自己的笨蛋上级,没有很好地处理好他该处理好的事情,还把“锅”扣到了自己的头上。
不过,在德鲁克看来,上级也是人,一样有着长处和短处。我们应该发挥其长处,减少其短处带来的不良影响。管理上级,也就是要发挥上级的长处,不能唯命是从,应该从正确的事情入手,以上级能够接受的方式向其提出建议。
具体到我们的日常工作中该怎么管理上级呢?我给你一些小建议。
我们要敢于管理上级,对上级不合理的要求说“不”,这是一个思想上的转变。很多人受到传统官本位思想的影响,对上级的服从达到了不健康的程度。勇于改变,是有效管理上级的前提条件。如果不从思想上转变,我接下来的建议都是没有价值的。
那具体要从哪些方面着手呢?
第一,管理上级的预期。
上级问你:“一个产品特性,你多长时间能做完?两天?一天行不行?”你想了想,如果不写测试,确实能够省下不少时间,于是,你决定答应上级的要求。是的,大部分人就是这么妥协的。
妥协很容易,但再往回扳就不容易了。下次,他还会再进一步压缩:“半天能不能搞定?两小时行不行?”人的欲望是无限的,所以,就不要让上级有错误的预期。
如果是我,我会告诉上级,这个压缩会影响到什么。比如,要想做这个调整,你需要放弃的内容是什么;或者,我可以给出一个快速上线的临时方案,但接下来的几天,我需要调整,让代码回到一个正常的状态中。所以,你就不要给我安排新工作了。
这个过程,相当于我把自己看到的问题暴露给上级,让他选择。他有更多的上下文,他会平衡该做的事情。
第二,帮助上级丰富知识。
不是每个上级都是经验丰富的知道所有事情。比如有些成长得比较快的负责人自己甚至都还没来得及了解软件开发全生命周期。在IT这个快速发展的行业里这是非常可能出现的情况。所以在某些局部你比他了解得多是非常有可能的。
在那些他做得不够好的领域,他肯定有许多烦恼。比如,盲目给需求的产品经理,可能也会影响到他对需求的判断。
这个时候,你就不妨把自己知道的内容找个机会给他讲讲。一个简单的方式是,把我专栏的内容发给他,和他一起探讨怎么做是合理的。然后,大家一起协同,改进工作方式。因为你是在帮他解决问题,他会更愿意接受。
第三,说出你的想法。
如果你什么都不做,上级会按照他自己的理解安排工作。比如,小李擅长处理消息队列,那消息队列的活都给他。
如果你有自己的想法和打算,不妨提出来,主动承担一些职责。比如,你接下来打算多学点消息队列,那就大大方方地告诉上级,下次有相关的活,考虑一下自己,上级再安排工作的时候,他就会多想想。这其实就是我们熟悉的一个最简单的道理:会哭的孩子有奶吃。
如果经过你的种种努力,发现你的上级真的是完全没法影响,只能以令人无语的方式行事,那你需要仔细考虑一下与他合作的前景了。
不过,更可能出现的场景是,你还没去尝试改变就放弃了,将全部责任都归结于上级的问题。如果你是这种思考问题的逻辑,不论到哪个公司,结果都不会比现在更好。
问题2产品经理总拿老板说事怎么办
此方彼方Francis 同学提到
很多时候产品要做这需求的理由就一个:老板要的!-
——01 | 10x程序员是如何思考的
西西弗与卡夫卡 同学提到
有的产品经理会使出必杀技——这是老板的需求-
——06 | 精益创业:产品经理不靠谱,你该怎么办?
用老板来“甩锅”,这在软件行业中特别常见。
实际上,老板要求的是方向,不是产品特性。大老板不会安排那么细的细节。所以,一个产品经理该做的事就是把老板给的方向,变成一个个可以实现的产品特性,他要分析其中的合理与不合理。
不合理的部分应该是他和老板去沟通的,而不是让开发团队来实现。
在真实世界中,更有可能的情形是,产品经理“拿着鸡毛当令箭”,老板说的是试一下,到他这里就变成了必须完成。他不敢对老板提问,就只能压迫下游了。
这种情况,你就不妨和产品经理一起去见老板。我们在《解决了很多技术问题,为什么你依然在”坑“里?》这篇文章中提到,要扩大自己工作的上下文,这种做法也可以帮助你解决问题,在自己上下文中解决不了的问题,就放到更大的上下文中去解决。
问题3别人能做的我们也要做
Xunqf 同学提到
当你和产品经理理论的时候,他往往会拿出来一个现有的产品给你看:“人家怎么就能做到人家能做到说明技术上是可行的做吧。”时间久了你会发现他的需求全是抄的的别的APP然后就觉得别人能做到的我们也一定能做。-
——《06 | 精益创业:产品经理不靠谱,你该怎么办?》
你会发现,在这个问题里,提到了两个与产品经理交流可能出现的典型问题:一个是竞争对手有的产品,我们也要有;另一个是人家能做到的,说明技术上可行,我们也能开发。
我带你来分别看下,这两种说法你该如何应对。
第一,竞争对手有的产品,我们也要有。
没有哪个企业是靠纯粹抄袭成功的。我知道,你想说腾讯。腾讯当年做 QQ从形式上看是和 ICQ 极其相似的甚至名字都是极为相似的OICQ。但腾讯却做了自己的微创新它将信息保存到了服务器端而 ICQ 是保存在客户端的。
正是有了这样看似微小的创新,让当时大部分家里没有电脑的普通用户,可以在网吧里不同的电脑上继续自己的网络社交,适应了时代发展的需要。腾讯“抄”得好的东西,都是有自己微创新的,包括如今的微信。
“抄”不是问题,问题是无脑地抄。
所以,如果你的产品经理只想无脑抄袭,本质上,他就是在偷懒,没干好他该干的活。竞争对手有这个特性,他为什么要做?他做这个特性与它其他特性是怎么配合的?我们做这个特性,在我们的产品里怎样发挥价值?作为产品经理,你必须给我讲清楚这些。
即便我们最终的结果是,做的与竞争对手一模一样,经过思考之后的“抄袭”也是一件价值更大的事。
第二:人家能做到,说明技术上是可行的。
关于这一点,我不得不说,产品经理说得对。别人能做到,说明技术上肯定是可行的。
不过,我们必须分清楚两件事:需求和技术。
要做什么是需求,怎么做是技术。与产品经理要确认的是,这个需求是不是合理,该不该做。技术上能否实现,这是开发团队要考虑的事情,并不是产品经理说事的理由。
还有一种情况是,需求确实合理,但技术实现的成本极高,所需花费的时间很长。在这种情况下,你和产品经理之间很难互相说服。
解决方案是,将问题升级,放到更大的上下文中,让上一层的领导来决定,此时此刻,在现有的资源约束下,是否要按照这种方式做。同时,你最好再提供一个可选的替换方案,这样领导才能更好做选择。
还有一些同学问了很好的问题。比如,程序员充当了太多角色,很困惑。这个问题我在专栏中已经回答了,在《 接到需求任务,你要先做哪件事?》中,我们说,每次扮演好一个角色。在《 解决了很多技术问题,为什么你依然在“坑”里?》中,我们提到程序员应该多了解不同的角色。
还有人问,计划赶不上变化快,怎么办?简单回答就是靠任务分解,因为这个话题涉及到“任务分解”这个模块的内容,等这个主题讲完之后,如果大家还有疑惑,我们再来详细讨论。
好,今天的答疑就到这里,请你回想一下,你在你工作中是否也遇到过类似的问题呢?你又是怎么解决的呢?欢迎在留言区写下你的想法。我会从中筛选出典型的问题,与大家进行互动交流。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。