课程提交

This commit is contained in:
fantasticbin 2022-09-03 22:05:03 +08:00
commit 7f19ee9c59
7280 changed files with 1263083 additions and 0 deletions

View File

@ -0,0 +1,75 @@
# 10x程序员工作法
## 你将获得
* “反直觉式”工作法,带你突破思维惯性;
* 1个框架+4个原则可复制的“高效模版”
* 顶级程序员的40个实操秘笈。
## 讲师介绍
郑晔网名dreamhead火币网首席架构师前ThoughtWorks首席咨询师 TGO鲲鹏会会员Oracle Duke选择奖获奖作品Moco的创始人在软件行业奋斗了近二十年。
## 课程介绍
一个好程序员的工作效率是普通程序员的10倍成为10x程序员应该是每个开发者的追求。
但效率是由编程能力决定的吗?答案是“未必”。工作中,你可能有过这样的困惑:作为程序员,你很想潜心钻研技术,却发现每天耗费大量精力解决的问题,多数都不是纯粹的技术问题,总会有一些令你抓狂的意外。你写完代码,但需求变了;你做出的东西,总是和要求不符,经常返工;你错估了工作量,因而要拼命加班弥补……诸如此类,不胜枚举。
于是,你的工作很被动,经常加班、熬夜、忙忙碌碌,却总在解决这类看似很“不值当”的事儿。
归根结底程序员面对的并不是单纯的线性工作你的编程技术提高并不代表工作效率也会相应提高。你的工作方法正在成倍地影响着你的开发效率。而只有了解并掌握了高效工作的方法和原理你才真正有机会成为10x程序员甚至是100x程序员。
为此,我们特意邀请了郑晔,在这个课程里为你分享顶级程序员的工作方法和思维方式。
# 模块介绍
本课程共分为六大模块。
**有效工作的思考框架篇**,为你提供一个可套用的思考框架,帮你在遇到问题时梳理自己真正要做的事情。同时还会给你提供践行这套框架所需的几项基本原则。
**以终为始篇**,从完成的定义、需求的完成、代码的完成、产品特性的完成等几个方面,来帮你明确真正的目标。通过跳出角色、数字校验、沙盘推演等方式确保目标的可行性,让你的每一丝努力都不白费。
**任务分解篇**,通过德雷克公式、埃隆·马斯克、“老虎”伍兹等案例,为你分析任务分解的重要性。通过对测试驱动开发进行完整地了解,让你更好地掌握任务分解的“度”。对需求以及产品的分解以及对优先级的讲解,可以让你与其他人的协作更加游刃有余,掌握主动权。
**沟通反馈篇**,从信息论的角度出发,让你明白“理解的偏差”是如何产生的。专栏将从代码的沟通、团队的沟通、可视化的沟通等角度为你讲解许多实践背后的沟通法则,通过开发的反馈、团队的反馈以及用户的反馈为你介绍反馈在软件开发中的价值,消除你与真实世界间的理解偏差。
**自动化篇**你以为自动化就是写代码其实不然。有时候不写代码而解决问题才是一个好方案。自动化版块中从构建脚本、shell 编程出发,给你介绍了持续交付、验收测试背后的自动化理念,以及做好自动化需要了解的单一职责、分层架构等思想,让你知道什么情况下,你可以骄傲地“偷懒”。
**综合运用篇**结合程序员日常工作场景将1个思考框架和4个基本原则进行综合应用分析。让你能够学会高效工作方法并融会贯通从各个维度帮你解决效率问题。
## 课程目录
![](https://static001.geekbang.org/resource/image/10/3e/10dfc61006956121316d73090ec9cc3e.jpg)
## 特别放送
#### 免费领取福利
[![](https://static001.geekbang.org/resource/image/69/dc/69c52d08278a2164dc5b061ba342a5dc.jpg?wh=960x301)](https://time.geekbang.org/article/427012)
#### 限时活动推荐
[![](https://static001.geekbang.org/resource/image/67/a0/6720f5d50b4b38abbf867facdef728a0.png?wh=1035x360)](https://shop18793264.m.youzan.com/wscgoods/detail/2fmoej9krasag5p?dc_ps=2913145716543073286.200001)
## 订阅须知
1. 订阅成功后推荐通过“极客时间”App端、Web端学习。
2. 本专栏为虚拟商品,交付形式为图文+音频,一经订阅,概不退款。
3. 订阅后分享海报,每邀一位好友订阅有现金返现。
4. 戳此[先充值再购课更划算](https://shop18793264.m.youzan.com/wscgoods/detail/2fmoej9krasag5p?scan=1&activity=none&from=kdt&qr=directgoods_1541158976&shopAutoEnter=1),还有最新课表、超值赠品福利。
5. 企业采购推荐使用“[极客时间企业版](https://b.geekbang.org/?utm_source=geektime&utm_medium=columnintro&utm_campaign=newregister&gk_source=2021020901_gkcolumnintro_newregister)”便捷安排员工学习计划,掌握团队学习仪表盘。
6. 戳此[申请学生认证](https://promo.geekbang.org/activity/student-certificate?utm_source=geektime&utm_medium=caidanlan1)订阅课程享受原价5折优惠。
7. 价格说明:划线价、订阅价为商品或服务的参考价,并非原价,该价格仅供参考。未划线价格为商品或服务的实时标价,具体成交价格根据商品或服务参加优惠活动,或使用优惠券、礼券、赠币等不同情形发生变化,最终实际成交价格以订单结算页价格为准。

View File

@ -0,0 +1,63 @@
# SUMMARY
* [简介](./README.md)
* [开篇词 | 程序员解决的问题,大多不是程序问题](./docs/73980.md)
* [01 | 10x程序员是如何思考的](./docs/74471.md)
* [02 | 以终为始:如何让你的努力不白费?](./docs/74834.md)
* [03 | DoD的价值你完成了工作为什么他们还不满意](./docs/74828.md)
* [04 | 接到需求任务,你要先做哪件事?](./docs/75100.md)
* [05 | 持续集成:集成本身就是写代码的一个环节](./docs/75977.md)
* [06 | 精益创业:产品经理不靠谱,你该怎么办?](./docs/76260.md)
* [07 | 解决了很多技术问题,为什么你依然在“坑”里?](./docs/76567.md)
* [08 | 为什么说做事之前要先进行推演?](./docs/76716.md)
* [09 | 你的工作可以用数字衡量吗?](./docs/76929.md)
* [10 | 迭代0: 启动开发之前,你应该准备什么?](./docs/77294.md)
* [答疑解惑 | 如何管理你的上级?](./docs/77752.md)
* [划重点 | 关于“以终为始”你要记住的9句话](./docs/77773.md)
* [11 | 向埃隆·马斯克学习任务分解](./docs/77913.md)
* [12 | 测试也是程序员的事吗?](./docs/77917.md)
* [13 | 先写测试,就是测试驱动开发吗?](./docs/78104.md)
* [14 | 大师级程序员的工作秘笈](./docs/78507.md)
* [15 | 一起练习:手把手带你分解任务](./docs/78542.md)
* [16 | 为什么你的测试不够好?](./docs/79494.md)
* [17 | 程序员也可以“砍”需求吗?](./docs/79520.md)
* [18 | 需求管理:太多人给你安排任务,怎么办?](./docs/80428.md)
* [19 | 如何用最小的代价做产品?](./docs/80691.md)
* [答疑解惑 | 如何分解一个你不了解的技术任务?](./docs/81515.md)
* [划重点 | 关于“任务分解”,你要重点掌握哪些事?](./docs/81870.md)
* [20 | 为什么世界和你的理解不一样?](./docs/80755.md)
* [21 | 你的代码为谁而写?](./docs/82581.md)
* [22 | 轻量级沟通:你总是在开会吗?](./docs/82844.md)
* [23 | 可视化:一种更为直观的沟通方式](./docs/83082.md)
* [24 | 快速反馈:为什么你们公司总是做不好持续集成?](./docs/83461.md)
* [25 | 开发中的问题一再出现,应该怎么办?](./docs/83841.md)
* [26 | 作为程序员,你也应该聆听用户声音](./docs/84185.md)
* [用户故事 | 站在前人的肩膀上,领取属于你的高效工作秘籍](./docs/84274.md)
* [27 | 尽早暴露问题: 为什么被指责的总是你?](./docs/84374.md)
* [28 | 结构化:写文档也是一种学习方式](./docs/84663.md)
* [答疑解惑 | 持续集成,一条贯穿诸多实践的主线](./docs/85049.md)
* [划重点 | 一次关于“沟通反馈”主题内容的复盘](./docs/85625.md)
* [加餐 | 你真的了解重构吗?](./docs/85915.md)
* [29 | “懒惰”应该是所有程序员的骄傲](./docs/86210.md)
* [30 | 一个好的项目自动化应该是什么样子的?](./docs/86561.md)
* [31 | 程序员怎么学习运维知识?](./docs/87008.md)
* [32 | 持续交付:有持续集成就够了吗?](./docs/87229.md)
* [33 | 如何做好验收测试?](./docs/87582.md)
* [34 | 你的代码是怎么变混乱的?](./docs/87845.md)
* [35 | 总是在说MVC分层架构但你真的理解分层吗](./docs/88309.md)
* [36 | 为什么总有人觉得5万块钱可以做一个淘宝](./docs/88764.md)
* [37 | 先做好DDD再谈微服务吧那只是一种部署形式](./docs/89049.md)
* [答疑解惑 | 持续集成、持续交付,然后呢?](./docs/89050.md)
* [划重点 | “自动化”主题的重点内容回顾汇总](./docs/89662.md)
* [38 | 新入职一家公司,怎么快速进入工作状态?](./docs/89981.md)
* [39 | 面对遗留系统,你应该这样做](./docs/90231.md)
* [40 | 我们应该如何保持竞争力?](./docs/90864.md)
* [答疑解惑 | 如何在实际工作中推行新观念?](./docs/91127.md)
* [划重点 | “综合运用”主题内容的全盘回顾](./docs/91433.md)
* [总复习 | 重新审视“最佳实践”](./docs/92154.md)
* [总复习 | 重新来“看书”](./docs/92361.md)
* [结束语 | 少做事,才能更有效地工作](./docs/92912.md)
* [第二季回归 | 我带着软件设计课回来了!](./docs/240265.md)
* [结课测试 | 这些10x程序员工作法的知识你都掌握了吗](./docs/247410.md)
* [第三季回归 | 我们一起来写好代码!](./docs/326184.md)
* [第四季回归 | 通向高质量代码之路](./docs/404270.md)

View File

@ -0,0 +1,4 @@
{
"title": "10x程序员工作法",
"language": "zh"
}

View File

@ -0,0 +1,37 @@
# 第二季回归 | 我带着软件设计课回来了!
你好,我是郑晔!
在这个专栏的结束语中,我说过,如果以后有机会,我会再来与你分享我对软件开发的理解。我也和编辑说过,如果这个专栏合作顺利的话,我可能会写下一个专栏。
没错,我又回来了!
我们都知道《10x程序员工作法》这个专栏本质上是在讲如何有效工作尽量地排除外部干扰**不让别人给我们添麻烦**,把精力放在值得做的事情上。然而,当我们真正开始做值得做的事情后,我们做得又怎么样呢?
* 别人修改了他的程序,结果你的代码崩溃了,因为你们在底层共用了一个变量。
* 不同的项目组竞争公共的测试环境,测出来的常常是一些可以在单元测试里就解决的小问题。如果问他为什么不自己做好单元测试,他的回答常常是不好测。
* 为了提高一段代码的处理性能我们使用了多线程处理。结果更多的Bug随之而来。
* ……
是不是有一种熟悉的感觉扑面而来?在我的职业生涯里,我接触过的许多项目都会不停地出现类似的问题。事后究其原因,很大一部分都是软件设计没做好所造成的。换句话说,都是程序员自己给自己挖的坑。
**如何避免给自己找麻烦**,这就是我要在新专栏里讨论的内容。
所以这次,我准备和你聊一下软件设计。
或许你会好奇,这些东西和软件设计有关系吗?软件设计不就是设计模式吗?我会在新专栏里告诉你,软件设计可比你想的内容多得多。它不仅可以教会我们如何组织代码,还会给我们提供一把尺子,用以衡量我们的设计。
学会了软件设计,我们甚至可以把许多问题消灭在萌芽阶段,不会让它们为害人间。
如果说**这个专栏是在告诉你要做正确的事**,做有价值的需求,别把时间浪费在不该做的事情上。那[软件设计专栏](http://gk.link/a/10iul)就是告诉你如何把事做对,**如何建立有效的模型,划清模块之间的边界**。所以,二者可谓一脉相承。
这个专栏是我工作之外的思考,新专栏则是我本职工作的深思。来吧!加入我的软件设计课,和我一起继续扫除障碍,让我们的开发之旅更加顺畅!
为了感谢老同学,我还准备了一个「专属优惠」:
5 月 25 日,专栏上新时,我会送你一张 10 元专属优惠券,可与上新优惠同享,有效期截止 6 月 5 日,建议尽早使用。
**点击下方图片**,立即免费试读新专栏。
[![](https://static001.geekbang.org/resource/image/e0/37/e0651b2b18a5f584b6798792e6bcb337.png)](https://time.geekbang.org/column/intro/313?utm_term=zeusBE50C&utm_source=app&utm_medium=10xjiacan&utm_campaign=presell-313&utm_content=yinliu)

View File

@ -0,0 +1,12 @@
# 结课测试 | 这些10x程序员工作法的知识你都掌握了吗
你好,我是郑晔。
到这里《10x程序员工作法》这门课程已经全部结束了。我给你准备了一个结课小测试来帮助你检验自己的学习效果。
这套测试题共有 20 道题目包括13道单选题和7道多选题满分 100 分,系统自动评分。
还等什么,点击下面按钮开始测试吧!
[![](https://static001.geekbang.org/resource/image/28/a4/28d1be62669b4f3cc01c36466bf811a4.png?wh=1142*201)](http://time.geekbang.org/quiz/intro?act_id=178&exam_id=417)

View File

@ -0,0 +1,36 @@
# 第三季回归 | 我们一起来写好代码!
你好,我是郑晔,我又回来了!
在《[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)》!请再次和我一起踏上程序员精进之路,我们一起修炼,不断打磨自己编写代码的手艺!

View File

@ -0,0 +1,50 @@
# 第四季回归 | 通向高质量代码之路
你好,我是郑晔!
细心的老同学可能已经注意到,我的第四个专栏已经上线了。
前面我用三个专栏 100 讲的篇幅给你讲了如何做一个不断精进的程序员通过各种方式把代码写好《10x 程序员工作法》让我们找准正确的目标,使程序不偏航;《软件设计之美》让程序更加灵活,适应未来的变化;《代码之丑》让我们不犯低级错误,使代码更容易理解。
其实,在这三个专栏里,有一条隐隐的线索一直贯穿始终:**我们怎么要怎么验证自己写的程序是对的?能够用来保证程序正确性,唯有测试。**
在《10x 程序员工作法》里,我讲了一个测试的小专题;《软件设计之美》中,我讲了用可测试性衡量软件设计的正确性;而在《代码之丑》中,我修正的很多坏味道,就是为了让代码更容易测试。
保证代码的正确性是每个程序员口中的目标,但它是多少程序员行动中的目标呢?这件事在行业中的真实情况是,从思想到行动之间有巨大的落差。我们不能质疑程序员的专业精神,但大部分人对代码正确性的要求都停留在个人努力上。
可遗憾的是很多团队并没有对编写测试硬性的要求即便有也是很低的要求比如测试覆盖率达到50%。为什么团队不要求?一个很可悲的答案是**大多数程序员不会写测试**。对于不会做的事情,人们自然的反应就是少做或者不做。
因为测试并不是光知道 xUnit 框架就能够很好完成的,很多人只会用系统测试这种大粒度的测试,结果必然是整个测试又慢覆盖度又不高,还会有很多测不到的地方。反过来,这种笨拙的测试方式也会进一步劣化测试在程序员心目中的形象,导致更多的人不愿意写测试。
**程序员写测试就是为了编写高质量的代码**。这里所说的高质量代码分成两个部分,一方面自然是我们常规理解的:经过测试的代码,质量会更高。另一方面,要想写好测试,代码本身的质量也要高。
不过,想真正做好测试,需要有很多基础的铺垫,在不少人看来,一些东西看上去与测试并没有直接的关联,比如任务分解,再比如要有高质量的代码等等。其实只有把这些知识连接起来,我们才能知道如何做好测试。好在测试基础的部分,我在前面的几个专栏中已经讲过了,比如:
* 我们要懂得任务分解这是《10x 程序员工作法》中讲过的;
* 我们要懂软件设计,这是《软件设计之美》中讲过的;
* 每个函数要整洁小巧,这是《代码之丑》中讲过的。
亲爱的老同学们,你们已经掌握了打开通向编写高质量代码大门的通行证,接下来,就是怎样把这些知识运用到编写测试的过程中了。所以,这次我就把“测试”这条隐藏在自己专栏中许久的线索给拿出来做一次完整地呈现,也就是这次的《程序员的测试课》。
在《程序员的测试课》中,你会看到:
* 程序员的测试和测试人员的测试会有什么不同;
* 怎么编写单元测试;
* 如何做到 100%的测试覆盖;
* 如何在遗留系统上写测试;
* ……
除了测试本身,这个专栏中还实现了一件很多老同学期待已久的事情。在前几个专栏中,很多同学都希望看到一个实战,把所学的知识在一个具体的项目中运用起来。在《程序员的测试课》中,我就用了一个实战的例子作为开篇,你会看到:
* 我是怎样做项目准备的,这是迭代 0 和项目自动化;
* 如何进行设计,这是软件设计;
* 将需求分解成一个个的具体任务,这是任务分解;
* 如何设计一个函数接口,如何封装参数,这是编码的过程;
* ……
在这次实战中,你熟悉的那些概念就在这个过程中慢慢地施展了出来。它们不再是我说出来的原则,而变成了一行行具体的代码。
作为老同学,你们已经在不经意间具备了写好测试的基础,剩下的,就是把这些知识贯穿起来,推开高质量代码的大门,一步一步地向前走。
欢迎回归,这一次,我们一起来编写高质量的代码!

View File

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

View File

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

View File

@ -0,0 +1,116 @@
# 03 | DoD的价值你完成了工作为什么他们还不满意
你好,我是郑晔。
在开始今天的讨论之前,我们先来看一个小故事。小李是一个程序员,有一天,项目经理老张来到他身边,和他商量一个功能特性的进度:
> 老张:这有一个任务需要完成,你看一下。
> 小李:这个不难,两天就能做完,两天以后就能上线。
两天以后,老张又来到小李的身边验收工作:
> 老张:怎么样,做完了吗?今天能上线吗?
> 小李:我的代码写完了。
> 老张:测试人员测过了吗?
> 小李:还没有。
> 老张:那今天能测完吗?
> 小李:那我就不知道了。
> 老张:什么?我可是答应了业务的人,今天一定要上线的!
很明显,老张有些愤怒,而小李也有些委屈。于是,老张、小李和测试人员一起度过了一个不眠之夜。
听完这个故事,你有什么感想呢?先不急,我们继续看后面的故事。
又过了几天,老张又来找小李,给小李安排一个很简单的功能。在小李看来,一天就能搞定,而按照老张给出的时间表,小李有两天时间处理这个功能。小李心中暗喜:看来我可以“偷得浮生一日闲”了。
两天以后,老张又来检查工作。
> 老张:这个功能开发完了吗?
> 小李:写完了,你看我给你演示一下。
小李熟练地演示了这个新写好的功能,这次老张很满意:
> 老张:做得不错。单元测试都写了吧?
> 小李:啊?还要写单元测试吗?
> 老张:要不为啥给你两天的时间?
怎么会这样?小李心里很委屈,自己明明已经很好地完成了工作,老张是不是故意在找自己的麻烦呢?
好,故事讲完了。是不是有些似曾相识的感觉呢?为什么小李辛辛苦苦地工作,老张却总能挑出毛病来呢?老张是不是来挑刺的呢?其实,老张才没那么闲,小李的委屈主要是因为他和老张对于“完成”有着不一样的理解。换句话说,他们之间存在一个理解的鸿沟。
## 理解的鸿沟
在这个模块里,我们讨论的主题是“以终为始”。那我们第一个问题就是,“终”到底是什么?在前面这个例子里,“终”就是“完成”,可是,小李认为他的活已经做完了,老张却认为他没做完。
怎么会这样?二人之所以有分歧,归根结底,就在于二人对“完成”的定义理解的不同。
在第一个故事里,作为项目经理,老张认为“完成”应该是“上线运行”,而程序员小李则认为“完成”是“功能代码编写完毕”。这中间存在的理解偏差,包括了测试人员的测试工作,可能还包括了运维人员的上线工作。
在第二个故事里,老张给了小李两天时间。小李认为这两天都是编写功能代码的,而老张想的是,小李应该自己写好功能代码和单元测试,可能还包括了功能测试,这中间的差异是测试代码的工作量。
因为双方的理解不一致,所以无论怎样努力,小李都不可能达成项目经理老张的要求,正所谓“南辕北辙”。
那该怎么办呢?小李会说,我又不是老张肚子里的蛔虫,怎么才能和他达成一致呢?答案很简单,既然双方的理解有差异,那就把这个差异弥合上,后面的问题便也不是问题了。
弥合差异的方式有很多,有一个最佳实践,它的名字叫 DoDDefinition of Done完成的定义从这个概念的名字便不难看出它就是为了解决软件开发中常见的“完成”问题而生的。
## 完成的定义
DoD 这个概念本身并不复杂,它就是告诉我们怎样算是完成了,尽量减少因为理解偏差造成的各种浪费。具体怎么做呢?就是团队在开始工作前,先制定 DoD。以前面的场景为例团队可以规定
> 特性开发完成,表示开发人员经过了需求澄清、功能设计、编写代码、单元测试,通过了测试人员的验收,确保代码处于一个可部署的状态,相关文档已经编写完毕。
> 开发完成,表示开发人员编写好功能代码,编写好单元测试代码,编写好集成测试代码,测试可以通过,代码通过了代码风格检查、测试覆盖率检查。
大家都是聪明人,一旦 DoD 确定好了,谁该做什么事就一目了然了。这个时候,如果小李说“我已经开发完了”,却只是写好了功能代码,那就别怪老张手下无情了。
好了,你已经知道 DoD 是什么了,它简单到让人一目了然,相信你很快就能知道该怎样把它用到你的工作里。不过,我们不仅要知道怎么用,还要知道怎样让 DoD 更好地发挥作用。
* **DoD 是一个清单,清单是由一个个的检查项组成的,用来检查我们的工作完成情况。**DoD 的检查项,就是我们开发产品所需的一系列有价值的活动。比如:编写代码、编写测试代码、通过测试人员验收等。什么样的活动是有价值的,也许每个团队的认识是不同的。但如果你的团队认为除了功能代码,其他都没价值,也许这是个信号,说明你的团队整体上是缺乏职业素养的,在这样的团队工作,前景并不乐观。
* **DoD 的检查项应该是实际可检查的。**你说代码写好了,代码在哪里;你说测试覆盖率达标了,怎么看到;你说你功能做好了,演示一下。
* **DoD 是团队成员间彼此汇报的一种机制。**别把“汇报”想复杂了,最简单的汇报就是说一句“这个功能做完了”。**当我们有了 DoD做事只有两种状态即“做完”和“没做完”。**在团队协作中我们经常会听到有人说“这个事做完了80%”对不起那叫没做完根本没有80%做完的说法。
在前面的讨论中,我们所说的 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 的概念以后,你是不是有了一些新的想法呢?欢迎在留言区留言。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,127 @@
# 02 | 以终为始:如何让你的努力不白费?
你好,我是郑晔。
今天内容的开始,我希望你可以先来思考一个问题:**如果让你设计一个登录功能,你会怎么做?**
我曾在公司内部做过这样一个练习,我扮演客户,让大家帮我设计一个登录功能。同事们一听就高兴了,登录不就是用户名加密码嘛,我熟啊,我还可以设计出验证码、找回密码、第三方登录等等功能。
更有个别动作快的同事甚至已经开始设计数据库表考虑用Redis做缓存了。整个过程下来大家彼此讨论得热火朝天唯一没人理会的就是我这个“客户”。
讨论结束,扮演客户的我告诉大家,作为一个“土豪”,我打算做一个打车软件,用户可以通过手机号接收验证码的方式进行登录。你可以想见,同事们一副“被套路了”的表情。是的,他们设计那套用户名密码登录完全是文不对题。
虽然这是一个简单的练习,但反映的却是我们日常面对的真实工作场景:许多人都是刚刚听到别人要求做的一个功能,就开始脑补接下来的一切。导致的结果,就是付出的努力毫无意义。
那么问题出在哪呢?因为我们欠缺了“以终为始”的思维习惯。
## 一种反直觉的思维方式
以终为始,就是在做事之前,先想想结果是什么样子的。
说起来很简单,但做到并不容易。因为我们习以为常的思维模式是线性而顺序的,第一步做完,做第二步;第二步做完,做第三步。
这也情有可原。我们人类都是从远古时代演化而来,在那个食不果腹的时代里,倒着思考的用途并不大,人们甚至不确定自己能否见到明天的太阳。几十万年的进化留给我们很多短视的行为和思考习惯,因为这样的做法最为节省能量,把目光放长远是需要额外消耗能量的。
“以终为始”是一种反直觉的思维方式,是大多数人不具备的。所以,日常生活中,我们看到很多有趣的现象。
比如,大学毕业时,有很多人想考研,如果你问他为什么要考研,得到的理由通常是为了找个好工作。但考研真的能帮他找个好工作吗?不一定,因为找工作和考研根本就不是同一棵技能树。
如果真的是想找个好工作,那你就应该了解工作的要求是什么,怎样才能掌握工作要求的技能。
从后面这个角度出发,你会发现考研只是通往工作诸多道路中的一条,其他的路径也是可以到达的。比如,你应该找个实习的地方锻炼一下职业技能。这就是“以终为始”思考问题的方式。
回到前面“设计登录功能”的例子,对比“以终为始”的思维,你也许会替我的同事抱不平,他们或许也有“以终为始”的思路,只不过,他们的“终”和我这个客户的“终”不一样罢了。这就要说到做软件,本质上是在构建一个“集体想象”。
## 想象的共同体
如果你读过尤瓦尔·赫拉利的《人类简史》或《未来简史》,有一个说法你一定不陌生:想象的共同体。作者认为,人类历史发展的一个重要因素是“集体想象”,无论是国家、宗教,还是法律、习俗,都是人们达成的“集体想象”。人类就是认同了这些“集体想象”的一个共同体。
我们这些做软件的人其实就是一个想象的共同体,这个“集体想象”就是我们要做的软件,任何想象都需要一个载体将其展现出来,我们编写软件的过程就是将这个“集体想象”落实的过程。
既然是“集体想象”,那么在载体将想象呈现出来之前,我们的想象很难统一起来,都或多或少存在差异。
所以,**任何事物都要经过两次创造一次是在头脑中的创造也就是智力上的或者第一次创造Mental/First Creation然后才是付诸实践也就是实际的构建或第二次创造Physical/Second Creation。**
我们在工作中遇到的很多问题,其实就是在于第一次创造没有做好,就进入到第二次创造。所以,我们在工作中会遇到很多“惊喜”,准确地说,是惊吓。
相比于第一次创造,第二次创造是一件成本很高的事。我们知道,软件开发最费时费力,一旦投入大量精力做出来,却发现与理解偏差甚大,所有人都会欲哭无泪。
所以,在动手做事之前,我们要在第一次创造上多下一些功夫,将相关各方的“集体想象”统一起来。以建筑为例,就是先在图纸上构思各种细节。对应到做软件,我们也可以做很多事,比如:
* 要给用户看产品的样子,可以用原型工具把它做出来,而不是非得把完整功能开发出来;
* 要呈现服务接口的样子,可以用模拟服务器搭出一个服务,而不用等后端全部开发完毕;
* 要让程序员知道要开发产品的细节,可以在任务上描述出软件各种场景给出的各种行为。
再回到前面“设计一个登录功能”的例子上,我的同事们在构建的其实是他们自己的想象,而不是我们共同的想象。这其中最大的一个区别就在于,没有人会为他们自己的想象买单的。
所以说,他们看到的“终”不是真正的终,只是一个自我的“终”,至于看到什么样的“终”,这取决于每个人的见识。
**对做软件的人来说,我们应该把“终”定位成做一个对用户有价值的软件**,能够为别人带来价值,自己的价值才能体现出来。
至此,你对“以终为始”已经有了一个初步的认识,有了这种思维方式,我们可以在工作中怎样运用它呢?
## 规划和发现
软件行业有很多英雄传说,一个人或者一个团队连续奋战一段时间,写好了一个软件,在上线前夜发现了一个问题,然后冒着“不成功便成仁”的风险,通宵达旦解决了问题,一战成名。
这种故事听起来让人热血沸腾,但仔细想想,为什么总在最后一刻发现问题?除了时间压力确实大的情况以外,大多数情况,他们还是一开始没有想好就动手了。
在团队内部,我一直坚持“以终为始”,让大家在执行任务之前,先倒着想想再动手规划,这样规划出来的工作更能瞄准真正的目标。举一个之前做产品的例子,当年在创业的时候,我们打算做一个物联网开发平台,但具体应该做成什么样子呢?
有了“以终为始”的思维,我们考虑的是别人会怎么用我们的平台。我们设计的方式是,用户到我们的网站,阅读相关文档,然后参考文档一步一步照着做。
这其中的一个关键点是:文档,特别是《起步走》的文档,这是用户接触我们这个平台的第一步,决定了他对我们产品的第一印象。
所以我们决定从写《起步走》这个文档开始这个文档描绘了用户怎样一步一步使用我们的开发平台完成第一个“Hello World”级别的应用。**请注意,这个时候,我们一行代码都没有写。**
写好了这个《起步走》文档,团队的所有人对于我们的平台要做成什么样子,已经有了一个比较初步的认识。更重要的是,我们可以拿着这个文档,去和外部的人讨论这个尚未出世的平台。
人类是一个擅长脑补的群体,一旦有人看到了这个文档,他就已经可以构想出这个平台已经存在的样子,进而给出各种各样的反馈:“我认为这个地方可以这样做”“我觉得那个地方可以改改”。
所有这些反馈都是真实的,因为他们已经“看到了”一个真实的东西。正是这些真实的反馈,让我们逐渐地锁定了目标。之后,我们才开始动手写代码。
**“以终为始”的方式,不仅仅可以帮我们规划工作,还可以帮我们发现工作中的问题。**
有一次,我的团队在开发一个大功能,要将现有的系统改造成支持多租户的系统。也就是说,别的商家可以到我们的平台上发起申请,拥有和我们现有平台一样的能力。
功能来了,各个团队将任务分解,然后就各忙各的去了。但我有着习惯性的不安,总担心丢点什么,于是催着项目经理梳理一下上线流程。
是的,上线流程,虽然我们的代码还没开发完,但是本着“以终为始”的态度,我们就假设各个部分已经开发好了,来想一想上线应该怎么做。
果不其然一梳理上线流程我们便发现了问题怎么识别不同的租户呢有人给出的方案是设置一个HTTP头。但谁来设置这个HTTP头呢没人仔细想过。于是一个潜在的问题就这样被发现了至少不用在未来为它加班了。至于解决方案作为程序员我们有的是办法。
事实上,在今天的软件开发实践中,已经有很多采用了“以终为始”原则的实践。
比如测试驱动开发。测试是什么?就是你这段代码的“终”,只有通过测试了,我们才有资格说代码完成了。当然,测试驱动开发想做好,并不是先写测试这么简单的。
比如持续集成,我们是要交付一个可运行的软件,倒着来想,最好的做法就是让软件一直处于可运行的状态,那就是持续地做集成。
概括地说,**践行“以终为始”就是在做事之前,先考虑结果,根据结果来确定要做的事情。**
这是“以终为始”这个内容版块的开篇,后面我会给你介绍这个原则在不同场景下的应用,也会引入一些现在行业内的最佳实践进行解析。相信会对你的实际工作有帮助。
## 总结时刻
有一段时间,网上流传着一个帖子,亚马逊 CTO 介绍亚马逊是如何开发一项产品的,简单来说,他们采用向后工作的方法,开发一项产品的顺序为:
1. 写新闻稿;
2. 写FAQ常见问题解答
3. 写用户文档;
4. 写代码。
今天我带你了解了“以终为始”的做事思路,回过头再来看这个帖子,相信你不难理解为什么亚马逊要这么做事情了。
人们习惯采用顺序思考的思维方式,几十万年的进化将这种思考模式刻在了我们的基因里。要成为更好的自己,我们要克服自身的不足,而这个做法很简单,那就是“以终为始”,做事倒着想,先考虑结果。
人类是一个想象的共同体,做软件的团队更是如此,而我们写出来的软件是我们将“集体想象”落地的载体。
任何事物都要经过两次创造一次是在头脑中的创造也就是智力上的或者第一次创造Mental/First Creation然后才是付诸实践也就是实际的或第二次创造Physical/Second Creation。我们应该在第一次创造上多下功夫统一集体想象让目标更明确。
“以终为始”的思维可以帮助我们更好地规划我们手头任务,也可以帮助我们发现过程中的问题。
如果今天的内容你只能记住一件事,那请记住:**遇到事情,倒着想。**
最后,我想请你思考一下,在实际的工作或生活中,你有运用“以终为始”的思维方式吗?帮助你解决过哪些问题?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,127 @@
# 04 | 接到需求任务,你要先做哪件事?
你好,我是郑晔。
我们书接上文,继续讲程序员小李的故事。这次小李接到一个新的需求,让他开发一个单点登录的服务,经过几天的奋战,他顺利地写完了所有的代码。正好产品经理小王路过他身边,顺便问了他一下。
> 小王:单点登录做得咋样了?
> 小李:做完了,我给你演示一下。
小李演示了一遍自己做的功能,小王看上去很满意。
> 小王:不错。不过,怎么没有支持验证码?
> 小李:为什么要做这个?
> 小王:这不就是登录的一部分吗?
> 小李:哪里规定要做验证码了?
> 小王:现在做登录哪有不用验证码的?
我想你已经嗅到了双方谈话的火药味,这个时候如果双方都不能很好地控制自己的情绪,那接下来一场体力的较量可能就一触即发了。
为什么双方会有这么大的分歧呢?其中一个重要的原因是,开始实现这个需求之前,任务双方都没有清晰地定义好边界,没能把需求描述清楚。
## 需求描述的问题
在软件开发中,程序员做什么一般都由需求来定义。我们都知道,需求是软件开发的一个重要组成部分,但你可能并没有仔细想过,不同的需求描述方式,可能会影响我们程序员对需求的理解。
因为信息的传递是会衰减的你不可能把你理解的信息100%传递给另外一个人,而这中间,如何传递,也就是如何描述将直接决定衰减的比例。
很多公司的软件开发模式是基于功能列表的,这个列表“规定”了程序员要做的功能,各个组从产品经理那里领来开发列表,然后“照单抓药”开始写代码。但是,通常这种功能列表只是一些简单的描述,你并不能看到全局。
很多团队的一个状态就是,程序员们都知道要开发的功能是什么,但这个功能是谁在什么样的场景下使用的,很多人却回答不上来。如果你去问他为什么要开发这个功能,他通常会说:这是功能列表里规定的。
**这种功能列表式的需求描述方式,将一个完整的需求敲成了碎片。** 只有所有功能全部开发完成,对接在一起的时候,才是“破镜重圆”的时刻。
也就是说,不到最后一刻,大多数人并没有一个完整的图景,这就相当于看不到完整的“终”。顺着这个思路做下去,你会在最后关头遇到许多意料之外的问题,其结果必然是手忙脚乱。
根据这种基于功能列表的需求描述,每个组在安排工作的时候,都会按照自己的理解进行功能排列。
所以,当你的组完成了一个功能时,这个功能却可能上不了线,因为你还要依赖于其他组的工作,而这个组不巧,却刚好把相关的功能开发排在了后面。
这还只是两个组之间有依赖的情况,如果需要多个组协同,可以想象,状况会多么糟糕。
所以,当我们对产品经理说“时间不足,砍掉一些需求吧。”得到的答案肯定是,“对不起,做不到,因为需求已破碎,没办法调整。”
因此一些新的需求描述方式也就应运而生这其中用户故事User Story是我最喜欢的一种方式。它是站在用户的角度来描述了一个用户希望得到的功能关注用户在系统中完成一个动作需要经过怎样的路径。既然它是“故事”它就需要是一个完整的场景可以讲述出来。
## “用户故事”有什么用?
我们先来以用户密码登录为例,看看用户故事长什么样?一个完整的用户故事大致包含以下几个部分:
* 标题,简要地说明这个用户故事的主要内容,比如:注册用户使用用户名密码登录。
* 概述,简要地介绍这个用户故事的主要内容,一般会用这样的格式:
As a Role, I want to Activity, so that Business Value.
意思就是:作为一个什么角色,要做什么样的事,以便达成一种怎样的效果。其中最重要的是,告诉别人为什么要做这件事,虽然只有一句话,却往往是很多人欠缺的思考,只知做,不知为何做。
举个概述的例子:作为一个注册用户,我想要通过用户密码登录,以便我可以使用注册用户才能够使用的服务。
* 详述,详细地描述这个用户故事的完整流程,我们会把操作流程、用户界面等信息都放到这里。
比如:用户使用正确用户名和密码登录,就可以登录成功;如果密码不正确,则登录页面提示用户“用户名密码不正确”。基本上,看到这个部分,程序员就可以在心中描绘出这个用户故事的样子了。
超出范围的部分,比如:第三方登录不在范围内,这个部分主要是限定人们不要进一步发散。
* 验收标准,这个部分会描述一个正常使用的流程是怎样的,以及各种异常流程系统是如何给出响应的,这是程序员常常会欠缺的思考。它会把详述中很多叙述的部分变成一个具体的测试用例。比如,下面我给出的两个验收用例:
正常场景给定一个注册用户张三其用户名是zhangsan密码是foobar当张三使用zhangsan 和 foobar 登录系统时,可以成功登录,登录成功后,跳转到用户中心。
异常场景给定一个注册用户张三其用户名是zhangsan密码是foobar当张三使用zhangsan 和 wrong 登录系统时,登录失败,在登录页面上提示“用户名密码不正确”。
在前面的例子中,小张和小王之所以会对需求是否完成产生分歧,是因为大家对于需求完成的定义不同。对于这种情况,我们能怎么办呢?
这个模块的主题是“以终为始”现在你看到了用户故事是如何描述需求的你或许已经知道我要说什么了没错这里非常关键的一点就是“验收标准”。很多人学习用户故事认为最重要的是记住“As…, I want to …, so that …”这样的需求描述方式。
在我看来,无论采用哪种需求描述方式,这部分也都是能说清楚的。那我们要从用户故事中学到什么呢?我认为就是用户故事的关键点:验收标准,它可以清晰地定义出需求边界。
**验收标准非常重要的一环是异常流程的描述。**大部分程序员都擅长解决正常流程,而异常流程则是最容易忽略的,也是产生扯皮的关键环节。既然容易扯皮,我们就在一开始把它定义清楚。怎么才算做完需求呢?验收标准说了算。
采用用户故事之后,我经常在写完了主要流程之后,再去看一下验收标准,为自己的开发查缺补漏。因为我知道,那是标准,达不成就不算任务完成。
当我们说自己开发完成,可以交给测试人员测试时,我们需要照着验收标准给测试人员演示一遍,证明我们的系统确实能够跑通。这之后,测试人员才会把系统接手过去,做更系统的测试。
**验收标准给出了这个需求最基本的测试用例,它保证了开发人员完成需求最基本的质量。**如果你了解 BDDBehavior-Driven Development也就是“行为驱动开发”就可以按照验收标准中给出的内容编写验收测试用例了。
在实际工作中,许多产品经理把需求交给开发人员之前,很多细节是没想清楚的,那种功能列表式的需求常常只包含了正常路径,那些缺失的细节就是在后续的过程中,由开发人员补全的。用户故事就是一种固定的格式,让他们把这些应该想清楚的问题想清楚。
**如果你的团队采用用户故事的格式进行需求描述固然好,如果不能,在功能列表中,补充验收标准也会极大程度地改善双方协作的效率。**
## 你的角色
或许你会有这样的疑问,如果产品经理通过用户故事的方式,将需求实现细节都描绘得清清楚楚,那我们程序员的发挥空间在哪里?请注意,验收标准所给出实现细节应该是业务上的,程序员在这种问题上思考才是真正意义上的浪费时间,我们的发挥空间应该是在技术实现上。
然而,在现实情况中,很多团队做不到这种程度。
你会发现,我们在开发中之所以会“丢三落四”,很重要的一个原因是,在开发一个功能特性的时候,因为一些环节的缺失,我们不得已扮演了很多的角色,其中之一就是产品经理。你是一个专业的程序员,但大多数情况下,你却只是一个业余的产品经理,“丢三落四”就在所难免了。
或许你会说,我在一个小公司工作,公司没那么多人,没有专门的产品经理,只有我们几个“全世界都缺”的程序员,需求都是老板扔给我们的,谁来帮我们写验收标准呢?
没办法,答案只能是你自己。虽然你名义上是程序员,但当拿到一个需求的时候,你要做的事不是立即动手写代码,而是扮演产品经理的角色,分析需求,圈定任务范围。相信我,事前分析绝对比你拿一个写好的系统给老板,而他却告诉你这不是他想要的,好太多了。
另外我想提醒你注意的是,**扮演不同角色的时候,我们的思考模式是不同的。**还是以开发用户名密码登录为例,你想到的可能是:输入正确的用户名和密码可以正常登录,输入错误的用户名和密码不能登录,并且给出提示。
如果你只扮演开发人员的角色,想到这些就算不错了。但如果你扮演的是产品经理的角色,会从产品的角度进行思考,也就会看到不同的内容,比如:
* 登录是否需要验证码
* 是否需要第三方登录
* 用户名和密码的长度在系统内是否有限制
* 密码是否需要满足一定的规则
* ……
我知道,如果让你来填写,这个列表会更长。可能这并不是我们都需要完成的功能,但站在分析的角度,这都是我们要考虑的问题,一个登录功能,绝不仅仅是用户名和密码校验那么简单的。我们能想到这些,仅仅是因为我们正在扮演一个不同的角色。
所以,如果你要兼顾开发人员和产品经理两个角色,建议你先扮演好产品经理的角色,多花点时间把验收标准制定好,再回到开发人员的角色上去写代码。毕竟,**最好维护的代码是没有写出来的代码。**
## 总结时刻
需求,是软件开发中的一个关键环节,一旦需求理解出现问题,势必会造成大量的浪费。传统的功能列表只是简单罗列了要实现的功能,丢失了大量的上下文,会导致团队成员对于需求“只见树木不见森林”。
而在比较大的团队中,更是会将一个功能分拆到多个小团队中,每个人看到的只是功能碎片。于是,后来产生了其他的需求描述方式,比如用例和用户故事。
在实际的开发过程中,大量的分歧来自于对“需求完成”的定义。当我们把“以终为始”的原则应用在需求领域中,就会注意到,用户故事有一个非常重要的组成部分是验收标准。
验收标准不仅仅描述出了正常流程,也会关注到异常流程的处理,它也是我们验收测试用例的起点。一旦事先定义好验收标准,大量的扯皮工作就随之烟消云散了。
理解了验收标准的作用,即便我们不使用用户故事来定义需求,依然可以把用户故事中的关键点应用到自己的实践中,在功能列表的每个功能定义中,增加验收标准。
如果今天的内容你只能记住一件事,那请记住:**在做任何需求或任务之前,先定好验收标准。**
最后,我想请你回想一下,在实际工作中,你是如何澄清你的需求,或者因为需求不清晰给你造成了哪些困扰?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,114 @@
# 05 | 持续集成:集成本身就是写代码的一个环节
你好,我是郑晔。
上一讲我们探讨了需求的“完成”,你现在知道如何去界定一个需求是否算做完了,这要看它是不是能够满足验收标准,如果没有验收标准,就要先制定验收标准。这一点,对于每一个程序员来说都至关重要。
在今天这一讲中,我们假设需求的验收标准已经制定清楚,接下来作为一个优秀的程序员,你就要撸起袖子准备开始写代码了。
不过在这里,我要问你一个问题:“是不是写完代码,工作就算完成了呢?”你或许会疑惑,难道不是这样吗?那我再问你:“代码是技术团队的交付物吗?”
你是不是发现什么不对劲了。没有人需要这堆文本,人们真正需要的是一个可运行的软件。**写代码是程序员的职责,但我们更有义务交付一个可运行的软件。**
交付一个可运行的软件,通常不是靠程序员个体奋战就能完成的,它是开发团队协作的结果。我们大多数人都工作在一个团队中,那我们写的代码是不是能够自然而然地就和其他人的代码配合到一起呢?显然没那么简单。
如果想将每个程序员编写的代码很好地组合在一起,我们就必须做一件事:**集成。**
但是集成这件事情,该谁做,该怎么做呢?我不知道你有没有思考过这个问题。在开始这个话题之前,我先给你讲个故事。
## 集成之“灾”
2009年我在一个大公司做咨询。对接合作的部门里有很多个小组正在共同研发一个项目。他们工作流程是先开发一个月等到开发阶段告一段落大项目经理再把各个小组最精锐成员调到一起开始集成。对他们来说集成是一件大事难度很大所以要聚集精英来做。
这个项目是用 C 语言编写的,所以,集成的第一步就是编译链接。大家把各个小组写好的程序模块编译到一起,哪个模块有问题,哪个小组的精英就出手解决它。
如果第一天,所有模块能够编译链接到一起,大家就要谢天谢地了。之后才进入到一个正式“联调”的过程。
“联调”的目标,是把一个最基本的流程跑通,这样,集成才算完成。而对他们这个项目来说,“联调”阶段更像是场“灾难”。
为什么?你想想,一个大部门有若干个团队,每个团队都在为同一个项目进行代码开发,周期为一个月。这一个月期间,所有团队的程序模块汇总在一起,体量会非常庞大。那么这些内容中,出现错误需要改动的可能性也就非常大,需要改动的量也就非常大。因此他们集成“联调”所需要的时间也会非常长。
即便他们调动各组精英完成一次项目集成的时间至少也需要23天改动量稍大可能就要一周了。虽然我不知道你所处公司的现状是什么样的但大概率地说你在职业生涯中会遇到过类似的场景。那怎么去解决这个问题呢
## 迈向持续集成
聪明的你作为旁观者一定会想,在这个故事里,**为什么他们要在开发一个月后才做集成呢?为什么不能在开发一周后,甚至是更短的时间内就集成一次?**
这是一个行业中常见的痛点,所以,就会有人不断地尝试改进,最先取得的突破是“每日构建”。
1996年Steve McConnel出版了一本著作《Rapid Development》国内译作《快速软件开发》。在这本书中作者首次提出了解决集成问题的优秀实践**Daily Build每日构建。**通过这个名字,我们便不难看出它的集成策略,**即每天集成一次。**
这在当时的人看来,已经是“惊为天人”了。就像上面提到的例子一样,当时的人普遍存在一种错误认知:集成不是一件容易的事,需要精英参与,需要很长时间,如果每天都进行集成,这是想都不敢想的事情。
实际上,每日构建背后的逻辑很简单:既然一段时间累积下来的改动量太过巨大,那一天的时间,累积的改动量就小多了,集成的难度也会随之降低。
你会看到,对比最后做集成和每日构建,这两种不同的做法都是在处理改动量和集成时间的关系。只不过,一个是朝着“长”的方向在努力,一个则瞄准“短”的方向。最后的事实证明,“长”的成了恶性循环,“短”的成了最佳实践。
![](https://static001.geekbang.org/resource/image/68/f9/68d9d0d361240a24769d7f57069915f9.jpg)
既然,我们认同了只要增加集成的频率,就可以保证在每次集成时有较少的改动量,从而降低集成难度。
那问题来了?究竟要在开发后多久才进行一次集成呢?是半天、两个小时、还是一个小时呢?**倘若这个想法推演到极致,是否就变成了只要有代码提交,就去做集成?**
没错,正是基于这样的想法,有人尝试着让开发和集成同时进行,诞生了一个关于集成的全新实践:持续集成。
持续集成一个关键的思维破局是,将原来分成两个阶段的开发与集成合二为一了,也就是一边开发一边集成。
持续集成这个想法固然好,但是不是需要有专人负责盯着大家的工作,只要有人提交了代码,这个负责人就要去集成呢?显然,这在真实工作中是行不通的。
既然是程序员的想法,程序员解决问题的方案自然就是自动化这个过程。于是,有人编写了一个脚本,定期去源码服务器上拉代码,出现程序更新时,就自动完成构建。
后来,人们发现这段脚本与任何具体项目都是无关的。于是,把它进一步整理并发布出来,逐步迭代发展成为今天广为人知的持续集成服务器。
在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 的文章以及持续集成服务器。持续集成的思维让我们认识到,开发和集成可以合二为一。我们应该把开发的完成定义为代码已经集成起来,而站在个体的角度,我们应该尽早提交自己的代码,早点开始集成。
如果今天的内容你只能记住一件事,那请记住:**尽早提交代码去集成。**
最后,我想请你分享一下,在实际工作中,你遇到过哪些由集成带来的困扰?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,108 @@
# 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
![](https://static001.geekbang.org/resource/image/ed/d0/ed9927aea92c657610bb1e3270a8fbd0.jpg)
既然是试,既然是不确定这个想法的有效性,最好的办法就是以最低的成本试,达成同样一个目标,尽可能少做事。精益创业提出一个非常重要的概念,最小可行产品,也就是许多人口中的 MVPMinimum Viable Product。简言之少花钱多办事。
许多软件团队都会陷入一个非常典型的误区,不管什么需求都想做出来看看,殊不知,把软件完整地做出来是最大的浪费。
## 你为什么要学习精益创业?
或许你会问,我就是一个程序员,也不打算创业,学习精益创业对我来说有什么用呢?答案在于,**精益创业提供给我们的是一个做产品的思考框架,我们能够接触到的大多数产品都可以放在这个框架内思考。**
有了框架结构,我们的生活就简单了,当产品经理要做一个新产品或是产品的一个新特性,我们就可以用精益创业的这几个概念来检验一下产品经理是否想清楚了。
比如,你要做这个产品特性,你要验证的东西是什么呢?他要验证的目标是否有数据可以度量呢?要解决的这个问题是不是当前最重要的事情,是否还有其他更重要的问题呢?
如果上面的问题都得到肯定的答复,那么验证这个目标是否有更简单的解决方案,是不是一定要通过开发一个产品特性来实现呢?
有了这个基础,回到前面的案例中,我对产品经理提的问题,其实就是在确定这件事要不要做。事实上,他们当时是用一个表单工具在收集用户信息,也就是说,这件事有一个可用的替代方案。鉴于当时还有很多其它需求要完成。我建议把这个需求延后考虑。
## 总结时刻
程序员与产品经理的关系是 IT 行业一个经典的话题。许多程序员都会倾向于不问为什么就接受来自产品经理的需求,然后暗自憋气。
实际上,产品经理是一个新兴职业,即便在 IT 这个新兴行业来看,也算是新兴的。因为从前的 IT 行业更多的是面向确定性的问题,所以,需要更多的是分析。只有当面向不确定性工作时,产品经理才成为一个行业普遍存在的职业。所以,在当下,产品经理并不是一个有很好行业标准的职位。
比较早成型的面向不确定创造新事物的方法论是精益创业它提出了“开发build-测量measure-认知learn”这样一个反馈循环和最小可行产品的概念。
当产品经理让我们做一个新的产品特性时,我们可以从精益创业这个实践上得到启发,向产品经理们问一些问题,帮助我们确定产品经理提出的需求确实是经过严格思考的。
如果今天的内容你只记住一件事,那请记住:**默认所有需求都不做,直到弄清楚为什么要做这件事。**
最后,我想请你回想一下,你和产品经理日常是怎样做交流的呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

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

View File

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

View File

@ -0,0 +1,101 @@
# 09 | 你的工作可以用数字衡量吗?
你好,我是郑晔。
今天的分享从日常工作开始。请你回想一下,你每天到岗之后做的第一件事是什么呢?然后你来猜猜我的答案是什么?你可能猜不到,我每天到公司之后,第一件正事是**看数字**。
我现在服务于一家做数字资产的公司我们提供的是一个24小时运行的服务。从加入这家公司的第一天开始公司的人就给我不断灌输一个重要理念——看数字。在我座位的正前方摆着一个巨大的显示器上面展示着各种不断变换的曲线、柱状图和数字这些数字反映的是各种系统运行的指标。
我们就是每天看着这些指标,来发掘一些线上系统问题的,一旦某些指标出现自己不能理解的异常,就要着手调查。
你或许会纳闷,我们不是在探讨“以终为始”吗?怎么变成了一个关于监控的讨论呢?别急,我们确实还在讨论“以终为始”,因为数字是诠释“终”的最好方式。
我们前面讨论了各种“终”但通常靠语言定义的“终”或多或少都会存在一些模糊的地方也就是容易产生误解的地方。而数字却是一个明明白白的“终”。比如测试覆盖率要求100%即便你做到了99.9%,不达标就是不达标,没什么好说的,说破天也是不达标。
再比如之前内容我们讲到精益创业时提到了一个重要的反馈循环开发build-测量measure-认知learn。你会发现在这个循环中开发build是可控的认知learn必须是得到反馈之后才能有的。所以这里面最需要我们回答的问题是测量measure。而这里的测量最好的检验标准当然就是数字。
或许你会说,数字我们都很熟,还用讲吗?不过在我看来,你还真的未必习惯于使用数字。
## 熟悉而陌生的数字
从进化的角度来看,人们做事更多是依赖于直觉的。数字,是人类在非洲大草原上奔跑了许久之后才创造出来的东西。著名科普著作《从一到无穷大》的开篇有这么一个故事:
> 两个匈牙利贵族决定做一次数数的游戏,看谁说出的数字大。
> 一个贵族说:“好,那你先说吧!”
> 另一个绞尽脑汁想了好几分钟说了一个数字“3”。
> 现在轮到第一个贵族苦思冥想了,他想了一刻钟,然后说:“好吧,你赢啦!”
这个故事听起来有些荒诞但一些非洲探险家证实在某些原始部族里不存在比3大的数词。如果问他们有几个孩子而这个数字大于3的话他就会回答“许多个”。
虽然我们中华民族是一个重视教育的民族,现在也都承认数学是一门重要的基础知识。但我们还是习惯性地观其大略,因为在日常生活领域里,除了买东西发工资,需要对数字斤斤计较的场合并不多。
历史的车轮在不停地滚滚向前,当今社会所面临的复杂度已经远远超过凭直觉就能把事情做好的程度。
**一些人说自己靠直觉就能把事情做好其实这是一种误解因为那种所谓的直觉通常是一种洞见Insight洞见很大程度上依赖于一个人在一个领域长期的沉淀和积累而这其实是某种意义上的大数据。**
我们都在说人类马上就要进入智能时代了。之所以这么说主要是现在人工智能技术不断地向前发展着。而人工智能作为一门在50年代就已经问世的技术直到最近几年才得到大踏步的前进主要归功于基础设施的发展。
在人工智能领域,基于统计的方法早就在学术界提了出来,但由于当时的技术条件所限,人们的数据采集和存储能力都有限,当时的“大”数据和今天的大数据完全不是一个量级的概念。
直到进入到互联网时代,随着处理数据量的增加,基础设施在不断地拓展,进而促使人们采集更多的数据,这个正向反馈也造就了今天的大数据。
原本因为缺乏足够数据支撑,难以施展拳脚的 AI 算法,在今天一下子有了足够的表演空间,从一个边缘角色成为了舞台中心的主角。
今天谈到人工智能,人们主要会谈三件事:**算法、算力和数据。**算法几乎是行业共有的,而算力在云计算普及的今天也不再是稀缺资源,所以,数据几乎成了兵家必争之“物”。于是,我们看到的现象是各个公司都在努力地搜集各种数据,让数据成为自己的竞争力。所以,在大方向上,数据采集是一个行业共识。
但是,作为这个世界上最了解数据价值的一批人,我们程序员只是在努力地把数据用于不断改善别人的生活,而对于自己日常工作的改善,则思考得少之又少。
我们更习惯的讨论方式依然是靠直觉。比如:增加了这个特性**可能**会让用户增长,做了这个调整**应该**会让系统的压力变小。
在一些简单的情形下,或者说大家信息对称、知识背景相差无几的情况下,这样的讨论是很容易得到认同的。**而当事情复杂到一定程度时,简单地靠感觉是很难让人相信的。**
所以,在我们的工作中,经常会发生的一个现象是,一个人说,我觉得这个有作用,另一个人说,我觉得那个没有。几个“觉得”下来,双方就开始进入了隔空对话的环节,谁也无法说服谁。
如果换成用数字的方式进行讨论,效果就会更好。有一次,为了改善用户体验,我们准备进行一次主页改版。产品团队希望在主页上加上大量的内容,而开发团队则认为太多的内容会导致主页加载变慢,进而造成用户体验下降。
正当这个对话即将进入“空对空”的讨论之时,我们找到了一个测量指标:主页加载速度。只要保证主页加载速度,产品团队就可以按照自己的理解来做调整。于是,一个即将不可挽回的讨论,变成了在一定约束条件下的讨论,双方谁也不再思维发散,讨论就能继续推进了。
如果你认同了数据本身的价值,那么再结合“以终为始”的理念,我们就应该在着手做一件事之前,先来想怎么去测量。无论是在讨论产品特性,还是功能开发,“信口雌黄”是容易的,落到数字上,人们就会多想一下,这是对彼此的约束。
## 从数字出发
前面的内容我们都是在说应该重视测量指标,重视数字。接下来,我就分享下几个我在实际工作中运用数字的案例,让你看看习惯用数字去思考问题之后,会拓宽哪些思考的维度。
首先是基于数字进行技术决策。有一次,我们打算做一个技术改进,给系统增加一些缓存,减轻数据库的压力。大家一起设计了两个技术方案。如果查询是特定的,我们就准备简单地在某些方法上加上缓存;如果查询是五花八门的,就准备用一个中间件,使用它的查询方案。
系统现在的情况到底是什么样的呢?我们发现并不能立刻回答这个问题。于是,大家决定在系统中增加一些统计指标,让数据给我们答案。然后根据数据反映出的情况,进行具体的决策。
其次是一个准备上线的案例。当时,我们是要做一个影响力比较大的系统升级,因为这是一个系统的关键模块,上下游系统都会受到影响。
谁也不能确定哪个模块会在上线过程中出问题。于是,设计了一个全新的数据面板,将相关的几个模块的核心指标都摆在上面。而我们要做的就是在上线的同时,观察这些指标的变化。
所幸的是,这次上线影响不大,几个指标一路平稳,而大家的信心就源自这些提前准备好的指标。
再次,看一个从数字中发现问题的例子。由于各种历史原因,我们的重点指标面板上,会有几个指标表示的是类似的东西。
比如,某个模块的处理能力,一个指标是站在这个模块内部度量的,而另一个指标则是由这个模块上下游系统度量的。在大多数情况下,它们的表现是一致的。结果有一天两者突然出现了很大的差异,内部度量表现依然良好,而外部度量则出现了很大的延迟。
于是,我们开始追问为什么。一路追寻下来,我们发现,是这个模块内部需要定期将内部状态持久化下来,而在那个时间段内,这个模块就会停止从上游读取数据。所以,在内部看一切正常,而外部看则延迟较大。随后,我们找到了方案,解决了这一问题。
最后再说一个行业中的例子,据我所知,行业里的某些公司已经开始做所谓的 AIOps也就是通过人工智能的方式从数据中发现更多运维的问题。无论哪种做法都是为了**从数字中发现问题,让系统更稳定。**
我的一个同事有个观点非常值得玩味,他说,从数字上看,好的系统应该是“死水一潭”。
我是赞同这个观点的,因为出现波动尤其是大幅度波动,又不能给出一个合理解释的话,就说明系统存在着隐患。而让系统稳定,正是我们工作的一个重要组成部分。
回到这一讲的开头,我说每天工作中的一个重要组成部分就是看数字,其实就是在尝试着从数字的趋势中发现问题,如今团队已经习惯了“给个数字看看”这样的沟通方式,内部扯皮的机会也相应地减少了一些。
## 总结时刻
随着智能时代的来临,人类社会开始逐渐认识到数据的重要性。但我们这群 IT 人在通过数据为其他人服务的同时,却很少把数字化的思维带到自己的工作范围内。这也是工作中很多“空对空”对话的根源所在。
结合着“以终为始”的思考,如果我们可以在一开始,就设计好测量工作有效性的指标,那么就可以更有目的性地去工作了。
而如果我们习惯了用数字去思考,就可以在很多方面让数字帮助我们。我举了几个小例子,比如:基于数据进行技术决策、预先设定系统指标,以及发现系统中的问题等等。希望你也可以把数字思维带到你的日常工作中。
如果今天的内容你只记住一件事,那请记住:**问一下自己,我的工作是不是可以用数字衡量。**
最后,我想请你分享一下,你的工作中,有哪些应用数字解决问题的场景呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,126 @@
# 10 | 迭代0: 启动开发之前,你应该准备什么?
你好,我是郑晔。
关于“以终为始”,我们已经从各个方面讲了很多。你或许会想,既然我们应该有“以终为始”的思维,那么在项目刚开始,就把该准备的东西准备好,项目进展是不是就能稍微顺畅一点儿呢?
是这样的,事实上这已经是一种常见的实践了。今天,我们就来谈谈在一开始就把项目准备好的实践:**迭代0**。
为什么叫迭代0呢在“敏捷”已经不是新鲜词汇的今天软件团队对迭代的概念已经不陌生了它就是一个完整的开发周期各个团队在迭代上的差别主要是时间长度有所不同。
一般来说第一个迭代周期就是迭代1然后是迭代2、迭代3依次排列。从名字上你就不难发现所谓迭代0就是在迭代1之前的一个迭代所以我们可以把它理解成开发的准备阶段。
既然迭代0是项目的准备阶段我们就可以把需要提前准备好的各项内容在这个阶段准备好。事先声明**这里给出的迭代0它的具体内容只是基本的清单**。在了解了这些内容之后,你完全可以根据自己项目的实际情况,扩展或调整这个清单。
我们来看看我为你准备的迭代0清单都包含了哪些内容。
## 需求方面
### 1\. 细化过的迭代1需求
一个项目最重要的是需求而在迭代0里最重要的是弄清楚第一步怎么走。当我们决定做一个项目时需求往往是不愁的哪些需求先做、哪些需求后做这是我们必须做的决策。迭代0需要做的事就是把悬在空中的内容落到地上。
在需求做好分解之后,我们就会有一大堆待开发的需求列表。注意,这个时候需求只是一个列表,还没有细化。因为你不太可能这个时候把所有的内容细化出来。如果你做过 Scrum 过程,你的 backlog 里放的就是这些东西。
然后我们要根据优先级从中挑出迭代1要开发的需求优先级是根据我们要完成的最小可行产品minimum viable productMVP来确定的这个最小可行产品又是由我们在这个迭代里要验证的内容决定的。一环扣一环我们就得到了迭代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清单。
![](https://static001.geekbang.org/resource/image/f5/99/f52c136533bb782d51f8097942561d99.jpg)
根据我的经验,对比这个清单,大多数新项目都在一项或几项上准备得不够充分。**即便你做的不是一个从头开始的项目,对照这个清单,也会发现项目在某些项上的欠缺,可以有针对性地做一些补充。**
如果今天的内容你只记住一件事,那么请记住:**设计你的迭代0清单给自己的项目做体检。**
最后我想请你思考一下如果让你来设计迭代0清单它会包含哪些内容呢欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

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

View File

@ -0,0 +1,115 @@
# 划重点 | 关于“以终为始”你要记住的9句话
你好,我是郑晔。
“以终为始”这个主题模块已经全部更新完毕,相信通过对各种实践的深入讲解,你已经对“以终为始”这个原则有了更为全面和透彻的理解。
为了帮助你更好地回顾和复习,我为每个主题模块增设了“划重点”的加餐内容。现在,我就带你一起梳理一下“以终为始”主题的核心要点。
## 重点复习
在这个模块中,我们学习到了一些行业最佳实践。
* DoD确定好完成的定义减少团队内部的理解不一致。
* 用户故事,细化出有价值的需求。
* 持续集成,通过尽早集成,减少改动量,降低集成的难度。
* 精益创业,减少过度开发不确定性产品带来的浪费。
* 迭代0在项目开始之前做好一些基础准备。
还学习到一些重要的思维转变。
* 任何事物都要经过两次创造一次是在头脑中的创造也就是智力上的或者第一次创造Mental/First Creation然后才是付诸实践也就是实际的构建或第二次创造Physical/Second Creation
* 在更大的上下文内发现自己的“终”。
* 通过推演,找到通往“终”的路径。
* 用可度量的“数字”定义自己的“终”。
## 实战指南
在每一篇文章的结尾,我们还将全篇内容浓缩为“一句话”的实战指南,希望你可以迅速上手,把“以终为始”的原则运用在实际工作之中,我们一起来回顾一下这些实战指南。
* 遇到事情,倒着想。
——《[02 | 以终为始:如何让你的努力不白费?](http://time.geekbang.org/column/article/74834)》
* 在做任何事之前,先定义完成的标准。
——《[03 | DoD的价值你完成了工作为什么他们还不满意](http://time.geekbang.org/column/article/74828)》
* 在做任何需求或任务之前,先定好验收标准。
——《[04 | 接到需求任务,你要先做那件事?](http://time.geekbang.org/column/article/75100)》
* 尽早提交代码去集成。
——《[05 | 持续集成:集成本身就是写代码的一个环节](http://time.geekbang.org/column/article/75977l)》
* 默认所有需求都不做,直到弄清楚为什么要做这件事。
——《 [06 | 精益创业:产品经理不靠谱,你该怎么办?](http://time.geekbang.org/column/article/76260)》
* 扩大自己工作的上下文,别把自己局限在一个“程序员”的角色上。
——《[07 | 解决了很多问题,为什么你依然在“坑”里?](http://time.geekbang.org/column/article/76567)》
* 在动手做一件事之前,先推演一番。
——《[08 | 为什么说做事之前要先进行推演?](http://time.geekbang.org/column/article/76716)》
* 问一下自己,我的工作是不是可以用数字衡量。
——《[09 | 你的工作可以用数字衡量吗?](http://time.geekbang.org/column/article/76929)》
* 设计你的迭代0清单给自己的项目做体检。
——《[10 | 启动开发之前,你应该准备什么?](http://time.geekbang.org/column/article/77294)》
## 额外收获
在这个部分的最后,针对大家在学习过程中的热门问题,我也进行了回答,希望你懂得:
* 作为程序员,你可以管理你的上级;
* 拿老板说事的产品经理,你可以到老板面前澄清;
* 喜欢无脑抄袭的产品经理,让他回去先想清楚到底抄的是什么;
* 分清楚需求和技术,产品经理和开发团队各自做好各自的事。
——《[答疑解惑 | 如何管理你的上司?](http://time.geekbang.org/column/article/77752)》
## 留言精选
同学们的留言很踊跃,也很有价值。精彩的留言本身就是对文章内容的补充与丰富,在此我挑出一些优秀的留言与你分享。
在讲高效工作的思考框架时,**张维元** 同学提到:
> 思考框架是道,原则是演化下的术,我们从 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,112 @@
# 11 | 向埃隆·马斯克学习任务分解
你好,我是郑晔。
这次我们从一个宏大的话题开始:银河系中存在多少与我们相近的文明。我想,即便这个专栏的读者主力是程序员这个平均智商极高的群体,在面对这样一个问题时,大多数人也不知道从何入手。
我来做一个科普给大家介绍一下德雷克公式这是美国天文学家法兰克·德雷克Frank Drake于1960年代提出的一个公式用来推测“可能与我们接触的银河系内外星球高等文明的数量”。
下面,我要放出德雷克公式了,看不懂一点都不重要,反正我也不打算讲解其中的细节,我们一起来感受一下。
![](https://static001.geekbang.org/resource/image/1f/32/1f099ca7941fb792f5d284fab98d0432.jpg)
不知道你看了德雷克公式做何感想,但对于科学家们来说,德雷克公式最大的作用在于:**它将一个原本毫无头绪的问题分解了,分成若干个可以尝试回答的问题。**
随着观测手段的进步,我们对宇宙的了解越来越多,公式中大多数数值,都可以得到一个可以估算的答案。有了这些因子,人们就可以估算出银河系内可以与我们通信的文明数量。
虽然不同的估算结果会造成很大的差异,而且我们迄今为止也没能找到一个可以联系的外星文明,但这个公式给了我们一个方向,一个尝试解决问题的手段。
好吧,我并不打算将这个专栏变成一个科普专栏,之所以在这讲解德雷克公式,因为它体现了一个重要的思想:任务分解。
通过任务分解,一个原本复杂的问题,甚至看起来没有头绪的问题,逐渐有了一个通向答案的方向。而“任务分解”就是我们专栏第二模块的主题。
## 马斯克的任务分解
如果大家对德雷克公式有些陌生,我们再来看一个 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根据页面原型编写页面样式根据交互效果编写页面逻辑等几个步骤。
**不同的可执行定义差别在于,你是否能清楚地知道这个问题该如何解决。**
对于马斯克来说,他的解决方案可能是成立一个公司,找到这方面的专家帮助他实现。对你的日常工作来说,你要清楚具体每一步要做的事情,如果不能,说明任务还需要进一步分解。
比如,你要把一个信息存起来,假设你们用的是关系型数据库,对大多数人来说,这个任务分解就到了可执行的程度。但如果你的项目选用了一个新型的数据库,比如图数据库,你的任务分解里可能要包含学习这个数据库的模型,然后还要根据模型设计存储方案。
不过,在实际工作中,大多数人都高估了自己可执行粒度,低估任务分解的程度。换句话说,如果你没做过任务分解的练习,你分解出来的大部分任务,粒度都会偏大。
只有能把任务拆分得非常小,你才能对自己的执行能力有一个更清楚地认识,真正的高手都是有很强的分解能力。这个差别就相当于,同样观察一个物品,你用的是眼睛,而高手用的是显微镜。在你看来,高手全是微操作。关于这个话题,后面我们再来细聊。
一旦任务分解得很小,调整也会变得很容易。很多人都在说计划赶不上变化,而真正的原因就是计划的粒度太大,没法调整。
从当年的瀑布模型到今天的迭代模型,实际上,就是缩减一次交付的粒度。几周调整一次计划,也就不存在“计划赶不上变化”的情况了,因为我的计划也一直在变。
**如今软件行业都在提倡拥抱变化,而任务分解是我们拥抱变化的前提。**
## 总结时刻
我们从外星人探索和马斯克的火星探索入手,介绍了任务分解在人类社会诸多方面的应用,引出了分而治之这个人类面对复杂问题的基本解决方案。接着,我给你讲了这一思想在软件开发领域中的一个常见应用,分而治之的算法。
虽然我们很熟悉这一思想,但在日常工作中,我们却没有很好地应用它,这也使得大多数人的工作有很大改进空间。运用这一思想的难点在于,给出一个可执行的分解。
一方面,对复杂工作而言,给出一个分解是巨大的挑战;另一方面,面对日常工作,人们更容易忽略的是,分解的任务要可执行。每个人对可执行的理解不同,只要你清楚地知道接下来的工作该怎么做,任务分解就可以告一段落。
大多数人对于可执行的粒度认识是不足的,低估了任务分解的程度,做到好的分解你需要达到“微操作”的程度。有了分解得很小的任务,我们就可以很容易完成一个开发循环,也就让计划调整成为了可能。软件行业在倡导拥抱变化,而任务分解是拥抱变化的前提。
如果今天的内容你只记住一件事,那么请记住:**动手做一个工作之前,请先对它进行任务分解。**
最后,我想请你回想一下,你在实际工作中,有哪些依靠任务分解的方式解决的问题呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,130 @@
# 12 | 测试也是程序员的事吗?
你好,我是郑晔。
在“任务分解”这个模块,我准备从一个让我真正深刻理解了任务分解的主题开始,这个主题就是“测试”。
这是一个让程序员又爱有恨的主题,爱测试,因为它能让项目的质量有保证;恨测试,因为测试不好写。而实际上,很多人之所以写不好测试,主要是因为他不懂任务分解。
在上一个模块,我们提到了一些最佳实践,但都是从“以终为始”这个角度进行讲解的。这次,我准备换个讲法,用五讲的篇幅,完整地讲一下“开发者测试”,让你和我一起,重新认识这个你可能忽视的主题。
准备好了吗?我们先从让很多人疑惑的话题开始:程序员该写测试吗?
## 谁要做测试?
你是一个程序员,你当然知道为什么要测试,因为是我们开发的软件,我们得尽可能地保证它是对的,毕竟最基本的职业素养是要有的。
但测试工作应该谁来做,这是一个很有趣的话题。很多人凭直觉想到的答案是,测试不就该是测试人员的事吗,这还用问?
**测试人员应该做测试,这是没错的,但是测试只是测试人员的事吗?**
事实上,作为程序员,你多半已经做了很多测试工作。比如,在提交代码之前,你肯定会把代码跑一遍,保证提交的基本功能是正确的,这就是最基本的测试。但通常,你并不把它当成测试,所以,你的直觉里面,测试是测试人员的事。
但我依然要强调,测试应该是程序员工作的一部分,为什么这么说呢?
我们不妨想想,测试人员能测的是什么?没错,他们只能站在系统外部做功能特性的测试。而一个软件是由它内部诸多模块组成的,测试人员只从外部保障正确性,所能达到的效果是有限的。
打个比方,你做一台机器,每个零部件都不保证正确性,却要让最后的结果正确,这实在是一个可笑的要求,但这却真实地发生在软件开发的过程中。
在软件开发中有一个重要的概念:[软件变更成本,它会随着时间和开发阶段逐步增加。](http://www.agilemodeling.com/essays/costOfChange.htm)也就是说我们要尽可能早地发现问题,修正问题,这样所消耗掉的成本才是最低的。
上一个模块讲“以终为始”,就是在强调尽早发现问题。能从需求上解决的问题,就不要到开发阶段。同样,在开发阶段能解决的问题,就不要留到测试阶段。
你可以想一下,是你在代码中发现错误改代码容易,还是测试了报了 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 统称这些测试框架。
**这种测试框架最大的价值,是把自动化测试作为一种最佳实践引入到开发过程中,使得测试动作可以通过标准化的手段固定下来。**
## 测试模型:蛋卷与金字塔
在前面的讨论里,我们把测试分为人工测试和自动化测试。即便我们只关注自动化测试,也可以按照不同的层次进行划分:将测试分成关注最小程序模块的单元测试、将多个模块组合在一起的集成测试,将整个系统组合在一起的系统测试。
有人喜欢把验收测试也放到这个分类里。为了简化讨论,我们暂时忽略验收测试。
随之而来的一个问题是,我们应该写多少不同层次的测试呢?理论上固然是越多越好了,但实际上,做任何事都是有成本的,所以,人们必须有所取舍。根据不同测试的配比,也就有了不同的测试模型。
有一种直觉的做法是,既然越高层的测试覆盖面越广,那就多写高层测试,比如系统测试。
当然,有些情景高层的测试不容易覆盖到的,所以,还要有一些底层的测试,比如单元测试。在这种情况下,底层的测试只是作为高层测试的补充,而主力就是高层测试。这样就会形成下面这样一种测试模型:冰淇淋蛋卷。
![](https://static001.geekbang.org/resource/image/68/5a/68032e09ba926c260f18c0f1a51f3d5a.jpg)
听说过冰淇淋蛋卷测试模型的人并不多,它是一种费时费力的模型,要准备高层测试实在是太麻烦了。
之所以要在这里提及它,是因为虽然这个概念很多人没听说过,但是有不少团队的测试实际采用的就是这样一种模型,这也是很多团队觉得测试很麻烦却不明就里的原因。
接下来,要说说另一种测试模型,也是行业里的最佳实践:测试金字塔。
![](https://static001.geekbang.org/resource/image/83/39/833986a5a99d5fyy5e54abfaa213b239.jpg)
Mike Cohn 在自己的著作[《Succeeding with Agile》](http://book.douban.com/subject/5334585/)提出了测试金字塔,但大多数人都是通过 [Martin Fowler 的文章](http://martinfowler.com/bliki/TestPyramid.html)知道的这个概念。
从图中我们不难看出,它几乎是冰淇淋蛋卷的反转,测试金字塔的重点就是越底层的测试应该写得越多。
想要理解测试金字塔成为行业最佳实践的缘由,我们需要理解不同层次测试的差异。**越是底层的测试,牵扯到相关内容越少,而高层测试则涉及面更广。**
比如单元测试,它的关注点只有一个单元,而没有其它任何东西。所以,只要一个单元写好了,测试就是可以通过的;而集成测试则要把好几个单元组装到一起才能测试,测试通过的前提条件是,所有这些单元都写好了,这个周期就明显比单元测试要长;系统测试则要把整个系统的各个模块都连在一起,各种数据都准备好,才可能通过。
这个模块的主题是“任务分解”,我必须强调一点:**小事反馈周期短,而大事反馈周期长。**小事容易做好,而大事难度则大得多。所以,以这个标准来看,底层的测试才更容易写好。
另外,因为涉及到的模块过多,任何一个模块做了调整,都有可能破坏高层测试,所以,高层测试通常是相对比较脆弱的。
此外,在实际的工作中,有些高层测试会牵扯到外部系统,这样一来,复杂度又在不断地提升。
人们会本能地都会倾向于少做复杂的东西,所以,人们肯定不会倾向于多写高层测试,其结果必然是,高层测试的测试量不会太多,测试覆盖率无论如何都上不来。而且,一旦测试失败,因为牵扯的内容太多,定位起来也是非常麻烦的。
而反过来,将底层测试定义为测试主体,因为牵扯的内容少,更容易写,才有可能让团队得到更多的测试,而且一旦出现问题,也会更容易发现。
**所以,虽然冰淇淋蛋卷更符合直觉,但测试金字塔才是行业的最佳实践。**
## 当测试金字塔遇到持续集成
测试金字塔是一个重要实践的基础,它就是持续集成。当测试数量达到一定规模,测试运行的时间就会很长,我们可能无法在本地环境一次性运行所有测试。一般我们会选择在本地运行所有单元测试和集成测试,而把系统测试放在持续集成服务器上执行。
这个时候,底层测试的数量就成了关键,按照测试金字塔模型,底层测试数量会很多,测试可以覆盖主要的场景;而按照冰淇淋蛋卷模型,底层测试的数量则有限。
作为提交代码的防护网,测试数量多寡决定着得到反馈的早晚。所以,金字塔模型与持续集成天然就有着很好的配合。
需要特别注意的是,**不是用单元测试框架写的测试就是单元测试。**很多人用单元测试框架写的是集成测试或是系统测试。单元测试框架只是一个自动化测试的工具而已,并不是用来定义测试类型的。
在实际工作中,区分不同测试有很多种做法,比如,将不同的测试放到不同的目录下,或是给不同类型的测试一个统一的命名规范。
区分不同类型测试主要目的,主要是在不同的场景下,运行不同类型的测试。就像前面提到的做法是,在本地运行单元测试和集成测试,在持续集成服务器上运行系统测试。
## 总结时刻
测试是软件开发重要的组成部分,测试应该是软件开发团队中所有人的事,而不仅仅是测试人员的事。因为软件变更成本会随着时间和开发阶段逐步增加,能在早期解决的问题,就不要将它延后至下一个阶段。
在测试问题上,程序员有着天生的优势,会写代码,于是,程序员拥有了一个突出的强项,自动化测试。写测试应该是程序员工作完成的重要组成部分。
随着人们对于测试理解的加深,各种各样的测试都出现了,也开始有了测试的分类:单元测试、集成测试、系统测试等等。越在底层测试,成本越低,执行越快;越在高层测试,成本越高,执行越慢。
人的时间和精力是有限的,所以,人们开始思考不同的测试如何组合。在这个方面的最佳实践称之为测试金字塔,它强调的重点是,越底层的测试应该写得越多。只有按照测试金字塔的方式写测试,持续集成才能更好地发挥作用。
如果今天的内容你只能记住一件事,那请记住:**多写单元测试。**
最后,我想请你分享一下,你的团队在写测试上遇到哪些困难呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,124 @@
# 13 | 先写测试,就是测试驱动开发吗?
你好,我是郑晔。
在上一讲中,我向你说明了为什么程序员应该写测试,今天我准备与你讨论一下程序员应该在什么阶段写测试。
或许你会说,写测试不就是先写代码,然后写测试吗?没错,这是一个符合直觉的答案。但是,这个行业里确实有人探索了一些不同的做法。接下来,我们就将进入不那么直觉的部分。
既然自动化测试是程序员应该做的事,那是不是可以做得更极致一些,在写代码之前就把测试先写好呢?
有人确实这么做了于是形成了一种先写测试后写代码的实践这个实践的名字是什么呢它就是测试先行开发Test First Development
我知道,当我问出这个问题的时候,一个名字已经在很多人的脑海里呼之欲出了,那就是**测试驱动开发Test Driven Development**,也就是大名鼎鼎的 **TDD**TDD 正是我们今天内容的重点。
在很多人看来TDD 就是先写测试后写代码。在此我必须澄清一下,这个理解是错的。先写测试,后写代码的实践指的是测试先行开发,而非测试驱动开发。
下一个问题随之而来,测试驱动开发到底是什么呢?测试驱动开发和测试先行开发只差了一个词:驱动。只有理解了什么是驱动,才能理解了测试驱动开发。要理解驱动,先来看看这两种做法的差异。
## 测试驱动开发
学习 TDD 的第一步是要记住TDD的节奏“红-绿-重构”。
![](https://static001.geekbang.org/resource/image/01/e9/01975cfc49ac170f566b7f96ae0751e9.jpg)
红,表示写了一个新的测试,测试还没有通过的状态;绿,表示写了功能代码,测试通过的状态;而重构,就是再完成基本功能之后,调整代码的过程。
这里说到的“红和绿”,源自单元测试框架,测试不过的时候展示为红色,通过则是绿色。这在单元测试框架形成之初便已经约定俗成,各个不同语言的后代也将它继承了下来。
我们前面说过,让单元测试框架流行起来的是 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,134 @@
# 14 | 大师级程序员的工作秘笈
你好,我是郑晔。
前面我和大家分享了 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 的关键点。只有把任务分解到可以测试的地步,才能够有针对性地写测试。
同样听到任务分解这个说法,不同的人理解依然是不一样的。我把任务分解的结果定义成微操作,它远比大多数人理解得小。我们能将任务分解到多小,就决定了我们原子操作的粒度是多大。软件开发中的许多问题正是由于粒度太大造成的,比如,分支策略。
如果今天的内容你只能记住一件事,那请记住:**将任务拆小,越小越好。**
最后,我想请你分享一下,你身边是否有一些由于任务分解得不够小带来的问题。欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,93 @@
# 15 | 一起练习:手把手带你分解任务
你好,我是郑晔。
前面在讨论 TDD 的时候,我们说任务分解是 TDD 的关键。但这依然是一种感性上的认识。今天,我们就来用一个更加具体的例子,让你看看任务分解到底可以做到什么程度。
这个例子就是最简单的用户登录。需求很简单,用户通过用户名密码登录。
我相信,实现这个功能对大家来说并不困难,估计在我给出这个题目的时候,很多人脑子里已经开始写代码了。今天主要就是为了带着大家体验一下任务分解的过程,看看怎样将一个待实现的需求一步步拆细,变成一个个具体可执行的任务。
要完成这个需求,最基本的任务是用户通过输入用户名和密码登录。
![](https://static001.geekbang.org/resource/image/df/1b/df3bcd76b68787145c5d6e16804a191b.jpg)
用户名和密码登录这个任务很简单,但我们在第一部分讲过沙盘推演,只要推演一下便不难发现,这不是一个完整的需求。
用户名和密码是哪来的呢?它们可能是用户设置的,也可能是由系统管理员设置的。这里我们就把它们简单设定成由用户设定。另外,有用户登录,一般情况下,还会有一个退出的功能。好了,这才是一个简单而完整的需求。我们就不做进一步的需求扩展。
所以,我们要完成的需求列表是下面这样的。
![](https://static001.geekbang.org/resource/image/a0/6f/a01d61a6ebfe1ccb1ff38eaec4f4c96f.jpg)
假设我们就是拿到这个需求列表的程序员,要进行开发。我们先要分析一下要做的事情有哪些,也就是任务分解。到这里,你可以先暂停一会,尝试自己分解任务,之后,再来对比我后面给出的分解结果,看看差异有多少。
好,我们继续。
我们先来决定一下技术方案,就用最简单的方式实现,在数据库里建一张表保存用户信息。一旦牵扯到数据库表,就会涉及到数据库迁移,所以,有了下面的任务。
![](https://static001.geekbang.org/resource/image/5d/2e/5d88a1849a51d566e8702142b16e112e.jpg)
这时,需要确定这两个任务自己是否知道怎么做。设计表,一般熟悉 SQL 的人都知道怎么做。数据库迁移,可能要牵扯到技术选型,不同的数据库迁移工具,写法上略有差别,我们就把还不完全明确的内容加到任务清单里。
![](https://static001.geekbang.org/resource/image/23/a3/23d543d679448bd5250081168ba12da3.jpg)
数据库的内容准备好了,接下来,就轮到编写代码的准备上了。我们准备用常见的 REST 服务对外提供访问。这里就采用最常规的三层技术架构,所以,一般要编写下面几项内容。
* 领域对象,这里就是用户。
* 数据访问层,在不同的项目里面叫法不一,有人从 J2EE 年代继承下来叫 DAO数据访问对象Data Access Obejct有人跟着 Mybatis 叫 mapper我现在更倾向于使用领域驱动设计的术语叫 repository。
* 服务层,提供对外的应用服务,完成业务处理。
* 资源层,提供 API 接口,包括外部请求的合法性检查。
根据这个结构,就可以进一步拆解我们的开发任务了。
![](https://static001.geekbang.org/resource/image/03/8a/036a5560bcde11be857b04ce610bc18a.jpg)
不知道你有没有注意到,我的任务清单上列任务的顺序,是按照一个需求完整实现的过程。
比如,第一部分就是一个完整的用户注册过程,先写 User然后是 UserRepository 的 save 方法,接着是 UserService 的 register 方法,最后是 UserResource 的 register 方法。等这个需求开发完了,才是 login 和 logout。
**很多人可能更习惯一个类一个类的写,我要说,最好按照一个需求、一个需求的过程走,这样,任务是可以随时停下来的。**
比如,同样是只有一半的时间,我至少交付了一个完整的注册过程,而按照类写的方法,结果是一个需求都没完成。这只是两种不同的安排任务的顺序,我更支持按照需求的方式。
我们继续讨论任务分解。任务分解到这里需要看一下这几个任务有哪个不好实现。register 只是一个在数据库中存储对象的过程,没问题,但 login 和 logout 呢?
考虑到我们在做的是一个 REST 服务,这个服务可能是分布到多台机器上,请求到任何一台都能提供同样的服务,我们需要把登录信息共享出去。
这里我们就采用最常见的解决方案:用 Redis 共享数据。登录成功的话,就需要把用户的 Session 信息放到 Redis 里面,退出的话,就是删除 Session 信息。在我们的任务列表里,并没有出现 Session所以需要引入 Session 的概念。任务调整如下。
![](https://static001.geekbang.org/resource/image/6f/13/6fb2e0509c4b05b5b8ed18225d180913.jpg)
如果采用 Redis我们还需要决定一下在 Redis 里存储对象的方式我们可以用原生的Java序列化但一般在开发中我们会选择一个文本化的方式这样维护起来更容易。这里选择常见的 JSON所以任务就又增加了两项。
![](https://static001.geekbang.org/resource/image/8a/c2/8a26b41dafe2b0704b291d44b3fc99c2.jpg)
至此,最基本的登录退出功能已经实现了,但我们需要问一个问题,这就够了吗?之所以要登录,通常是要限定用户访问一些资源,所以,我们还需要一些访问控制的能力。
简单的做法就是加入一个 filter在请求到达真正的资源代码之前先做一层过滤在这个 filter 里面,如果待访问的地址是需要登录访问的,我们就看看用户是否已经登录,现在一般的做法是用一个 Token这个 Token 一般会从 HTTP 头里取出来。但这个 Token 是什么时候放进去的呢?答案显然是登录的时候。所以,我们继续调整任务列表。
![](https://static001.geekbang.org/resource/image/1d/cd/1dc9b55d2ccf024bc66b7e90e6f0bbcd.jpg)
至此,我们已经比较完整地实现了一个用户登录功能。当然,要在真实项目中应用,需求还是可以继续扩展的。比如:用户 Session 过期、用户名密码格式校验、密码加密保存以及刷新用户 Token等等。
这里主要还是为了说明任务分解,相信如果需求继续扩展,根据上面的讨论,你是有能力进行后续分解的。
来看一下分解好的任务清单,你也可以拿出来自己的任务清单对比一下,看看差别有多大。
![](https://static001.geekbang.org/resource/image/94/ab/94f217310da9fe77e6739f3b15702cab.jpg)
首先要说明的是,任务分解没有一个绝对的标准答案,分解的结果根据个人技术能力的不同,差异也会很大。
**检验每个任务项是否拆分到位,就是看你是否知道它应该怎么做了。**不过,即便你技术能力已经很强了,我依然建议你把任务分解到很细,观其大略人人行,细致入微见本事。
也许你会问我,我在写代码的时候,也会这么一项一项地把所有任务都写下来吗?实话说,我不会。因为任务分解我在之前已经训练过无数次,已经习惯怎么一步一步地把事情做完。换句话说,任务清单虽然我没写下来,但已经在我脑子里了。
不过,我会把想到的,但容易忽略的细节写下来,因为任务清单的主要作用是备忘录。一般情况下,主流程我们不会遗漏,但各种细节常常会遗漏,所以,想到了还是要记下来。
另外,对比我们在分解过程中的顺序,你会看到这个完整任务清单的顺序是调整过的,你可以按照这个列表中的内容一项一项地做,调整最基本的标准是,按照这些任务的依赖关系以及前面提到的“完整地实现一个需求”的原则。
最后,我要特别强调一点,所有分解出来的任务,都是独立的。也就是说,**每做完一个任务,代码都是可以提交的。**只有这样,我们才可能做到真正意义上的小步提交。
如果今天的内容你只能记住一件事,那请记住:**按照完整实现一个需求的顺序去安排分解出来的任务。**
最后,我想请你分享一下,你的任务清单和我的任务清单有哪些差异呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,139 @@
# 16 | 为什么你的测试不够好?
你好!我是郑晔。今天是除夕,我在这里给大家拜年了,祝大家在新的一年里,开发越做越顺利!
关于测试,我们前面讲了很多,比如:开发者应该写测试;要写可测的代码;要想做好 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专业的
如果今天的内容你只能记住一件事,那请记住:**要想写好测试,就要写简单的测试。**
最后,我想请你分享一下,经过最近持续对测试的讲解,你对测试有了哪些与之前不同的理解呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,115 @@
# 17 | 程序员也可以“砍”需求吗?
你好,我是郑晔。
我们前面讲的任务分解,主要是在讲开发任务的分解。今天我们换个角度,看看需求的分解。是的,需求也要分解。
有一次,我和一个做开发的同事聊天,他给我讲了他近期的烦恼。
> **同事:**我们现在就是需求太多,开发的人太少,再这么干下去,哪天觉得自己抗不住了,我就拍拍屁股走人。
> **我:**你没尝试着砍砍需求?
> **同事:**怎么没尝试?产品的人都不同意。这批功能他们都说是关键功能。
> **我:**你有没有尝试把需求拆开了再砍呢?
> **同事:**还可以这样?
同事很惊讶,我一点都不意外。我们都是在说需求,但彼此对需求的理解却是大不相同。我先来问个问题,提到需求这个词,你会想到什么呢?
以我们用了好多次的登录为例,如果我问你这个需求是什么,大多数人的第一直觉还是用户名密码登录。
**基本上闯入你脑海的需求描述是主题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/)。
如果今天的内容你只能记住一件事,那请记住:**想要管理好需求,先把需求拆小。**
最后,我想请你分享一下,你的团队在需求管理上还遇到过哪些问题呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,121 @@
# 18 | 需求管理:太多人给你安排任务,怎么办?
你好,我是郑晔。
上一讲我们讲了需求的分解,我以用户故事为例,给你讲了我们应该把大的需求拆分成小的需求,但是不是只要把需求拆开了就万事大吉了呢?显然不是。今天我们再来探讨另一个与需求强相关的话题:需求管理。
需求管理?许多程序员的第一直觉通常是,这要么是产品经理的事,要么是项目经理的事,跟我有什么关系?我知道很多人会这么想,可我想说的是,如果你不了解需求是怎么管理的,即便是进行了需求分解,最终的结果很有可能依然是你深陷泥潭苦苦挣扎而不自知。
为什么这么说呢?我给你讲一个发生在我身边的故事。
## 最无脑的需求管理法:老板说的
有一次,我们组织了一次各团队负责人的吐槽大会,让大家把遇到的问题在台面上“摆”一下。一个开发团队的负责人说:“我这边倒排期太严重了,每个产品经理到我这里都说上线日期已经定好了,我这边资源有限,实在是抗不住了。”
出于好奇,有人问:“这些任务都一样重要吗?”
这个负责人无奈地摇摇头,“他们都说自己的任务重要。”
“他们凭什么说自己的任务重要呢?”我也问了一个问题。
这个负责人说:“他们告诉我,是老板说的。”
这是不是一个很熟悉的场景?一堆任务压过来,只是因为这是老板的一句话。我们的老板都是这么不近人情吗?其实,大概率来看,并不是。
**就凭一句“老板说的”,我们就可以判断出,产品经理缺乏对需求管理应有的理解。**而研发团队也因为无脑地接受了需求,几乎将自己压垮。
这时候CTO 发话了:“口头的东西不算数,如果他们说是老板说的,那就让老板发邮件确认。”
我很认可CTO的说法但我并不放心那个开发团队的负责人于是我问他“你会让产品经理这么去做吗”果然他犹豫了。
“产品经理可能不会和老板这么说。那你去说好了。”我们又给他提了个建议。显然,他更犹豫了,毕竟要面对大老板。
针对这种情况,我们又给出了一个解决办法,“如果你担心产品经理不这么做,你可以直接发邮件给老板,同时抄送 CTO。”
“对可以这么做”CTO 把责任扛了过去。这个负责人心里一下子有底了。
是不是有种似曾相识的感觉?其实,这个故事只要再往下延伸一点,就到了我们程序员身边。
作为程序员,我们面临的场景往往是,一个需求不明就里地来了,你的周末假期全部泡汤,因为你的负责人会和你说,这是老板说的。
软件行业有个段子:做软件,最理想的交付日期是什么时候?答案是昨天,其次是尽快。所有提出业务需求的人都恨不得需求早就做好了。但事实总是那么不如人意,所以,他们只能寄希望于需求被尽快实现。
如果我们等着所有需求都开发好了再上线呢?这就是当年所谓瀑布模型做的事,放在二十年前,这种做法还有生存空间,但今天这种做法显然已经不合时宜了。
关于如何做软件,我们已经讨论了很多,关键点就在于这个世界有太多的不确定,我们只好把产品的“一部分”开发好,送上线。
这就引出了一个问题,到底是选择“哪部分”优先上线呢?我们必须在宏大的理想和骨感的现实中作出取舍。这也就牵扯出需求管理的本质,实际上是个优先级的问题。
## 需求的优先级
“来自老板”,这是判断优先级最简单的答案,也是推卸责任的一个答案。其潜台词是,压力大不怪我,要怪就怪老板去。“来自老板”不应该成为优先做事的指标。
首先,我们要明确一点,优先级这种事大家也是可以谈的,大多数能当老板的人都是可以讲道理的。但要和老板谈,我们得知道怎么讲道理。准备一些基础知识,才能与各级老板探讨怎么安排工作的优先级。
为什么要区分优先级?因为时间是有限的,有限的时间内你能完成工作的上限是一定的。
怎么充分利用好有限的时间,这其实是一个时间管理的问题。所以,我们完全可以借鉴时间管理领域的一些优秀实践,帮助我们更有效地明辨优先级。
谈到时间管理一个有效的时间管理策略是艾森豪威尔矩阵Eisenhower Matrix这是由美国前总统艾森豪威尔开发出的一个工具。
这个工具到了史蒂芬·柯维Stephen Richards Covey手里得到了发扬光大他那本著名的《高效能人士的七个习惯》书籍将其推广至世界各地。也许这个名字你不太熟悉看一下下面这个图你就知道了。
![](https://static001.geekbang.org/resource/image/6f/f8/6f0fdcb6e2d9c9955fd6e2b2210a03f8.jpg)
它将事情按照重要和紧急两个维度进行划分,也就形成了四个部分:重要且紧急,重要不紧急,不重要且紧急,不重要不紧急。
用几个程序员生活中的例子帮你理解一下。让系统不能正常运行的线上故障,就属于重要且紧急事情,不赶紧解决,就影响公司的正常运营。团队对系统升级改造就属于重要不紧急:改造好,性能也好,可维护性也得到提升;不改造,一时半会也能用。一些临时任务都属于紧急不重要,而刷朋友圈则属于既不紧急也不重要。
**按照时间管理的理念,重要且紧急的事情要立即做。重要但不紧急的事情应该是我们重点投入精力的地方。紧急但不重要的事情,可以委托别人做。不重要不紧急的事情,尽量少做。**
这个矩阵带给我们思维上最大的改变是,让人意识到事情和事情不是等价的。**如果不把精力放在重要的事情上,到最后可能都变成紧急的事情。**
比如,我们放任系统不做升级改造,过多的技术债会让系统的问题越来越多,新需求实现的速度越来越慢,最后几个看起来不大的需求就足以让团队加班加点,天怒人怨。
把这个思路带回到我们现实的需求管理中,你会发现,其实团队面临的各种需求所采用的优先级排序方式,基本上都是按照紧急程度排列的,但它们是否真的重要呢?
如果你把这个问题抛给需求的提出者,我几乎可以肯定,他们给你的答案是,他们提出的需求就是重要的。一种可能是,他们也分不清重要和紧急的差别,正如有时候我们也糊涂一样。
对于这样的场景,我们要做的就是多问一些问题。我在[“精益创业:产品经理不靠谱,你该怎么办?”](http://time.geekbang.org/column/article/76260)文章中说过,默认所有需求都不做,直到弄清楚为什么要做这件事。
同样,需求也没那么重要,直到产品经理能说明白它为什么重要,尤其是为什么比其他需求重要。如果一个产品经理不能把几个需求排出优先级,你就可以把上面学到的内容给他讲一遍。
还有另一种可能,他给你的需求在他工作的上下文中,确实是最重要的内容了。但当有多个需求来源时,我们该如何确认哪个需求是最重要的呢?这时,才到了真正需要老板出场的时刻。
## 站在老板面前
在[“解决了很多问题,为什么你依然在‘坑’里?”](http://time.geekbang.org/column/article/76567)文章中,我曾经讲过,大家不要局限于程序员这个角色,不同角色真正的差异是工作上下文的不同。每个人都在自己的上下文里工作,上下文也就局限了很多人的视野。
试想,两个产品经理出现在你面前,一个告诉你,公司要拓展新方向,这个功能要做;另一个却说,公司要进一步盈利,那个功能必须做。
在你看来,他们两个说得都对,听上去都挺重要的。但骨感的现实是,你把两件事都接下来,等着你的是累死都完不成的任务。
这个时候,我们能做的是什么呢?跳出这个上下文,到更大的上下文中。你判断不了哪个需求更重要,就请更高一级的老板来判断。
有了基础知识的储备,我们终于可以站在了老板面前。你可以告诉老板:我资源有限,需要将这两个需求排个序,看哪个更重要。我的上下文有限,需要你帮我判断一下。
老板会和你说这两个需求的起源,扩展盈利的需求是竞争对手都已经有了,客户也问这边要,再不做会影响客户关系,尤其是新财年快到了,下个阶段的合同会受到影响。而另外的新业务是某天一个高端聚会上得到的新启发,想尝试一下,他也不确定这个想法能带来多少收益,就让产品部门试一下。
听了老板的信息,你顿时明白这两件事的重要性,你也知道该如何面对两个产品经理了。
老板比你们的上下文大,因为他有看待这个问题更多的维度。所以,在你们眼里无比纠结的事情,老板几句话就云开雾散了,在他眼里,那根本不叫事。
如果你看过刘慈欣的《三体》,就会知道,这其实是“降维攻击”。另一个你可能熟悉的说法叫大局观。我经常和人说,**当员工想不明白的事,换成老板的视角就全明白了。**
我鼓励每个程序员在更大的上下文中工作,也就是想让人获得更多的思考维度。而今天的内容主要告诉你,如果自己的上下文不足时,我们可以引入新的元素,比如征求老板意见,扩大自己的上下文。
再发散讲几句,为人做事同样要不断扩展自己的上下文,这也就是我们常说的涨见识。
很多所谓的人生难题不过是因为见识有限造成的。比如,如果你觉得公司内总有人跟你比技术,莫不如把眼光放得长远一些,把自己放在全行业的水平上去比较。因为你是为自己的职业生涯在工作,而不是一个公司。
## 总结时刻
需求分解之后,最重要的是,排列需求的优先级。优先级的排列方式有很多,我们可以借鉴时间管理的方法,把事情按照重要和紧急的维度进行划分,得到了四个象限。我们要尽可能把精力放在重要的事情上,而不是把紧急的事情当成优先级排序的方式。
需求分解成一个个小块,其实也分解了原本合一的上下文。如果想要有效地管理需求,尤其是确定事情的重要程度,一种方式是找回丢失的上下文。如果我们自己无法判断上下文,一种好的办法是,引入外部更大的上下文。
如果今天的内容你只能记住一件事,那请记住:**尽量做最重要的事。**
最后,我想请你分享一下,你的团队在日常的需求管理中,还遇到哪些问题呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

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

View File

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

View File

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

View File

@ -0,0 +1,138 @@
# 划重点 | 关于“任务分解”,你要重点掌握哪些事?
你好,我是郑晔,恭喜你,又完成了一个模块的学习。
在这个模块中,我主要讲解的是“任务分解”这个知易行难的工作原则。普通人与高手之间的差异,很大程度上取决于任务分解的粒度大小。但真正理解并应用好“任务分解”的原则并不容易,希望你能勤于练习,将知识内化成为你的能力。
## 重点复习
在这个模块中,我们学习到了一些最佳实践:
* **测试金字塔**
\-- 行业中测试组合的最佳实践。
\-- 多写单元测试是关键。
* **测试驱动开发**
\-- 测试驱动开发的节奏是:红——绿——重构,重构是测试驱动开发区别于测试先行的关键。
\-- 有人把测试驱动开发理解成测试驱动设计,它给行业带来的思维改变是,编写可测的代码。
* **艾森豪威尔矩阵Eisenhower Matrix**
\-- 将事情按照重要和紧急进行划分。
\-- 重要且紧急的事情要立即做。重要但不紧急的事情应该是我们重点投入精力的地方。紧急但不重要的事情,可以委托别人做。不重要不紧急的事情,尽量少做。
* **最小可行产品**
\-- “刚刚好”满足客户需求的产品。
\-- 在实践中,要用最小的代价找到一条可行的路径。
**另外,我还提到了一些可以直接在工作中应用的做法和评判标准:**
* 尽量不写 static 方法;
* 主分支开发模型是一种更好的开发分支模型;
* 好的用户故事应该符合 INVEST 原则;
* 估算是一个加深对需求理解的过程,好的估算是以任务分解为基础的;
* 好的测试应该符合 A-TRIP。
**我也带你学习了一些重要的思想,帮你更好地改善自己的开发工作:**
* 分而治之,是人类解决问题的基本手段;
* 软件变更成本,它会随着时间和开发阶段逐步增加;
* 测试框架把自动化测试作为一种最佳实践引入到开发过程中,使得测试动作可以通过标准化的手段固定下来;
* 极限编程之所以叫“极限”,它背后的理念就是把好的实践推向极限;
* 大师级程序员的工作秘笈是任务分解,分解到可以进行的微操作;
* 按照完整实现一个需求的顺序安排开发任务。
## 实战指南
在“任务分解”的板块,我也将每篇内容浓缩为“一句话”的实战指南,现在一起回顾一下。
* 动手做一个工作之前,请先对它进行任务分解。
—— 《[11 | 向埃隆·马斯克学习任务分解](http://time.geekbang.org/column/article/77913)》
* 多写单元测试。
——《[12 | 测试也是程序员的事吗?](http://time.geekbang.org/column/article/77917)》
* 我们应该编写可测的代码。
——《[13 | 先写测试,就是测试驱动开发吗?](https://time.geekbang.org/column/article/78104)》
* 将任务拆小,越小越好。
——《[14 | 大师级程序员的工作秘笈](http://time.geekbang.org/column/article/78507)》
* 按照完整实现一个需求的顺序去安排分解出来的任务。
——《[15 | 一起练习:手把手带你拆任务](http://time.geekbang.org/column/article/78542)》
* 要想写好测试,就要写简单的测试。
——《[16 | 为什么你的测试不够好?](http://time.geekbang.org/column/article/79494)》
* 想要管理好需求,先把需求拆小。
——《[17 | 程序员也可以“砍”需求吗?](http://time.geekbang.org/column/article/79520)》
* 尽量做最重要的事。
——《[18 | 需求管理:太多人给你安排任务,怎么办?](http://time.geekbang.org/column/article/80428)》
* 做好产品开发,最可行的方式是采用 MVP。
——《[19 | 如何用最小的代价做产品?](http://time.geekbang.org/column/article/80691)》
## 额外收获
在这个部分的最后,针对大家在学习过程中的热门问题,我也进行了回答,希望你懂得:
* 对不了解技术的任务,先要去了解技术,然后再做任务分解;
* 通过一次技术 Spike ,学习新技术;
* 丢弃掉在 Spike 过程中开发的原型代码;
* 分清目标与现状,用目标作为方向,指导现状的改变;
* 多个功能并行开发可以考虑使用 Feature Toggle
* 在遗留系统上做改造可以考虑使用 Branch by Abstraction 。
——《[答疑解惑 | 如何分解一个你不了解的技术任务?](http://time.geekbang.org/column/article/81515)》
## 留言精选
在“任务分解”的模块中,有很多同学非常用心,将自己的学习心得和工作中的经验进行了分享,在此我挑选了一些同学的留言,与你一起学习。
在讲大师级程序员的工作秘笈时,西西弗与卡夫卡 同学提到:
> 最近在做战略拆解,都是一样的道理。战略飘在空中遥不可及,要落地就必须拆解。比如说达成目标有哪几个方面可以努力,各方面都需要做哪些事,这是路径。这些路径里哪些优先级最高,需要配置哪些组织资源。心里有数之后就是制订计划时间表。
另外,西西弗与卡夫卡 同学还为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,122 @@
# 21 | 你的代码为谁而写?
你好,我是郑晔。
关于“沟通反馈”的话题,我准备从代码开始讲起,毕竟我们程序员是靠代码与机器进行沟通的。
写代码是每个程序员的职责,程序员们都知道要把代码写好。但究竟什么叫写好呢?每个人的理解却是各有差异。
## 编写可维护的代码
初涉编程的程序员可能觉得能把功能实现出来的代码,就是好代码,这个阶段主要是基本功的学习,需要掌握的是各种算法、数据结构、典型的处理手法、常用的框架等等。
经过一段时间工作,日常工作所需的大多数代码,在你看来都是不在话下的。尤其像搜索和问答网站蓬勃发展之后,你甚至不需要像我初入职场时那样,记住很多常见的代码模式,现在往往是随手一搜,答案就有了。
再往后,更有追求的程序员会知道,仅仅实现功能是不够的,还需要写出可维护的代码。于是,这样的程序员就会找一些经典的书来看。
> 我在这方面的学习是从一本叫做《程序设计实践》The Practice of Programming的书开始的这本书的作者是 Brian Kernighan 和 Rob Pike这两个人都出身于大名鼎鼎的贝尔实验室参与过 Unix 的开发。
写出可维护的代码并不难,它同样有方法可循。今天,我们用写代码中最简单的一件事,深入剖析怎样才能写出可维护的代码,这件事就是命名。
![](https://static001.geekbang.org/resource/image/8e/28/8e2ec5013ed2185324e4b902bb6a1828.jpg)
## 命名难题
> 计算机科学中只有两大难题:缓存失效和命名。
> —— Phil Karlton
这是行业里流传的一个经典说法,无论是哪本写代码风格的书,都会把命名放在靠前的位置。
估计你开始写程序不久,就会有人告诉你不要用 a、b、c 做变量名,因为它没有意义;步入职场,就会有人扔给你一份编程规范,告诉你这是必须遵循的。
不管怎样,你知道命名是很重要的,但在你心目中,合格的命名是什么样的呢?
想必你知道命名要遵循编码规范比如Java 风格的 camelCase常量命名要用全大写。
但是,这类代码规范给出的要求,大多是格式上的要求。在我看来,这只是底线,不应该成为程序员的追求,因为现在很多编码规范的要求,都可以用静态检查工具进行扫描了。
我们的讨论要从名字的意义说起。作为程序员,我们大多数人理解为什么要避免起无意义的名字,但对于什么样的名字是有意义的,每个人的理解却是不同的。
**名字起得是否够好,一个简单的评判标准是,拿着代码给人讲,你需要额外解释多少东西。**
比如,我们在代码评审中会看到类似这样的场景:
> 评审者:这个叫 map 的变量是做什么用的?
> 程序员:它是用来存放账户信息的,它的键值是账户 ID值就是对应的账户信息。
> 评审者:那为什么不直接命名成 accounts
你知道评审者给出的这个建议是什么意思吗?如果不能一下子意识到,遇到类似的问题,你可能会和这个程序员一样委屈:这个变量本来就是一个 map我把它命名成 map 怎么了?
变量的命名,实际上牵扯到一个重要问题,**代码到底是给谁写的?**
## 代码为谁而写?
> 任何人都能写出计算机能够理解的代码,只有好程序员才能写出人能够理解的代码。
> —— Martin Fowler
代码固然是程序员与机器沟通的重要途径,但是,机器是直白的,你写的代码必须是符合某种规则的,这一点已经由编译器保证了,不符合规则的代码,你想运行,门都没有。
所以,只要你的代码是符合语言规则的,机器一定认。要让机器认,这并不难,你写得再奇怪它都认。行业里甚至有专门的混乱代码比赛。比如,著名的 IOCCCThe International Obfuscated C Code Contest国际 C 语言混乱代码大赛)。
**但是,我们写代码的目的是与人沟通,因为我们要在一个团队里与人协同工作。**
与人沟通,就要用与人沟通的方式和语言写代码。人和机器不同,人需要理解的不仅是语言规则,还需要将业务背景融入其中,因为人的目的不是执行代码,而是要理解,甚至扩展和维护这段代码。
**人要负责将业务问题和机器执行连接起来,缺少了业务背景是不可能写出好代码的。**
我们在“[为什么世界和你理解的不一样](http://time.geekbang.org/column/article/80755)”这篇内容中就讲过,沟通的时候,输出时的编码器很重要,它是保证了信息输出准确性的关键。
很多程序员习惯的方式是用计算机的语言进行表达,就像前面这个例子里面的 map这是一种数据结构的名字是面向计算机的而评审者给出的建议把变量名改成 accounts这是一个业务的名字。
**虽然只是一个简单的名字修改,但从理解上,这是一步巨大的跨越,缩短了其他人理解这段代码所需填补的鸿沟,工作效率自然会得到提高。**
## 用业务语言编程
写代码的时候,尽可能用业务语言,会让你转换一个思路。前面还只是一个简单的例子,我们再来看一个。
我们用最常用的电商下单过程来说,凭直觉我们会构建一个订单类 Order。什么东西会放在这个类里呢
首先,商品信息应该在这个类里面,这听上去很合理。然后,既然是电商的订单,可能要送货,所以,应该有送货的信息,没问题吧。再来,买东西要支付,我们会选择一些支付方式,所以,还应该有支付信息。
就这样,你会发现这个订单类里面的信息会越来越多:会员信息可能也要加进去,折扣信息也可能会加入。
你是一个要维护这段代码的人,这个类会越来越庞大,每个修改都要到你这里来,不知不觉中,你就陷入了一个疲于奔命的状态。
如果只是站在让代码运行的角度,这几乎是一个无法解决的问题。我们只是觉得别扭,但没有好的解决方案,没办法,改就改呗!
但如果我们有了看业务的视角,我们会问一个问题,这些信息都放在“订单”是合理的吗?
我们可以与业务人员交流,询问这些信息到底在什么场景下使用。这时候你就会发现,商品信息主要的用途是下单环节,送货信息是在物流环节,而支付信息则用在支付环节。
有了这样的信息,你会知道一件事,虽然我们在用一个“订单”的概念,但实际上,在不同的场景下,用到信息是不同的。
所以,更好地做法是,把这个“订单”的概念拆分了,也就有了:交易订单、物流订单和支付订单。我们原来陷入的困境,就是因为我们没有业务知识,只能笼统地用订单去涵盖各种场景。
如果你在一个电商平台工作,这几个概念你可能并不陌生,但实际上,类似的错误我们在很多代码里都可以看到。
再举个例子,在很多系统里,大家特别喜欢一个叫“用户”的概念,也把很多信息塞到了“用户”里。但实际上,在不同的场景下,它也应该是不同的东西:比如,在项目管理软件中,它应该是项目管理员和项目成员,在借贷的场景下,它应该是借款方和贷款方等等。
要想把这些概念很好地区分出来,你得对业务语言有理解,为了不让自己“分裂”,最好的办法就是把这些概念在代码中体现出来,给出一个好的名字。这就要求你最好和业务人员使用同样的语言。
如果了解领域驱动设计Domain Driven DesignDDD你可能已经分辨出来了我在这里说的实际上就是领域驱动设计。把不同的概念分解出来这其实是限界上下文Bounded Context的作用而在代码里尽可能使用业务语言这是通用语言Ubiquitous Language的作用。
所以,一个好的命名需要你对业务知识有一个深入的理解,遗憾的是,这并不是程序员的强项,需要我们额外地学习,但这也是我们想写好代码的前提。现在,你已经理解了,取个好名字,并不是一件容易的事。
## 总结时刻
代码是程序员与机器沟通的桥梁,写好代码是每个程序员的追求,一个专业程序员,追求的不仅是实现功能,还要追求代码可维护。如果你想详细学习如何写好代码,我推荐你去读 Robert Martin 的《代码整洁之道》Clean Code这本书几乎覆盖了把代码写好的方方面面。
命名,是写程序中最基础,也是一个程序员从业余走向专业的门槛。我以命名为基础,给你解释了写好代码的提升路径。最初的层次是编写可以运行的代码,然后是编写符合代码规范的代码。
对于命名,最粗浅的理解是不要起无意义的名字,遵循编码规范。但名字起得是否够好,主要看是否还需要额外的解释。很多程序员起名字习惯于采用面向实现的名字,比如,采用数据结构的名字。
再进一步提升,编写代码是要写出人可以理解的代码。因为代码更重要的作用是人和人沟通的桥梁,起一个降低其他人理解门槛的名字才是好名字。
实际上,我们很多没写好的程序有一些原因就是名字起错,把一些概念混淆在一起了。想起好名字,就要学会用业务语言写代码,需要尽可能多地学习业务知识,把业务领域的名字用在代码中。
如果今天的内容你只能记住一件事,那请记住:**用业务的语言写代码。**
最后,我想请你思考一下,想要写好代码,还有哪些因素是你特别看重的?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

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

View File

@ -0,0 +1,112 @@
# 23 | 可视化:一种更为直观的沟通方式
作为一个程序员,在这个技术快速发展的时代,我们唯有不断学习,才能保证自己不为时代所抛弃。那你是怎么跟上技术发展步伐的呢?
就个人经验而言,我会关注一些技术网站,最典型的就是 InfoQ。这样我可以快速了解到技术发展的动向比如什么时候出了个新东西、哪个项目又有了重大的更新、某些技术有了哪些新的应用场景等等。
另外我还有一种更系统地了解新知识的方式ThoughtWorks 技术雷达。之所以我很喜欢这种方式,因为它是**“可视化”**的。
## 什么是技术雷达?
[ThoughtWorks 技术雷达](http://www.thoughtworks.com/radar)是由 ThoughtWorks 技术咨询委员会Technology Advisory Board编写的一份技术趋势报告每6个月发布一次。ThoughtWorks 的项目多样性足够丰富,所以它能够发现诸多技术趋势。因此,相比于行业中其它的预测报告,技术雷达更加具体,更具可操作性。
ThoughtWorks 是我的老东家所以我在接触技术雷达的时间很早。我在2013年就已经开始与人讨论微服务并在项目中尝试使用 Docker而这一切信息的来源都是技术雷达。不过我这里想和你讨论并不是技术雷达到底有多优秀而是带你看看技术雷达这种组织知识的可视化形式。
![](https://static001.geekbang.org/resource/image/85/40/856da8d06911c547f59c845aa1e94f40.jpg)
(图片来源:[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多半这些人并不了解技术趋势。
从这几年的发展趋势来看,技术雷达在“采用”和“暂缓”这两项上给出的推荐,大部分是靠谱的。
至于“试验”和“评估”两项,有时间的时候,我会慢慢看,因为它们多半属于新兴技术的试验区,主要的作用是用来让我开拓视野的。
**雷达图是一种很好的将知识分类组织的形式,它可以让你一目了然地看到并了解所有知识点,并根据自己的需要,决定是否深入了解。**
所以,我的前同事们借鉴了这个形式,做出了一个程序员的读书雷达,将程序员的应该阅读的书籍做了一个整理。
![](https://static001.geekbang.org/resource/image/a3/70/a3458d87858d50b0b638c8d5c1f3bd70.png)
(图片来源:[ThoughtWorks读书雷达](http://insights.thoughtworkers.org/reading-radar-2016/)
事实上这种将内容通过可视化方式的组织起来的形式非常好用ThoughtWorks 鼓励每个组织都建立自己的知识雷达,甚至提供了一个工具辅助你将雷达图构建出来。
**在我看来,雷达图不仅仅适用于组织,也可以适用于团队。**
我也曾经按照雷达图的方式将自己的团队用到的技术组织起来。把最需要了解的技术必须放在内环,比如:一个 Java 项目。我会要求程序员了解 Java向外扩展的就是你在这个团队内工作会逐渐接触到的技术比如像 Docker 这种与部署相关的知识。至于最外面一层就是被我们放弃掉的技术比如Maven。
这样一来,团队成员可以更清晰地了解到团队中所用的技术。当有新人加入团队时,这个雷达可以帮助新人迅速地抓住重点,他的学习路径就是从内环向外学习。所以,我也推荐你打造自己团队的技术雷达。
> [构建技术雷达](https://www.thoughtworks.com/cn/radar/byor)
> [构建雷达的程序库](https://github.com/thoughtworks/build-your-own-radar)
你是否想过,为什么雷达图的形式可以帮助你更好地理解知识呢?**因为人的大脑更擅长处理图像。**
## 可视化的优势
在远古时代人脑处理的内容大多是图像比如哪里有新的果实哪里猛兽出没文字则是很久之后才产生的。现在普遍的一种说法是大约在公元前3500年左右许多文明才刚刚发展出书写系统相比于人类的历史来说这几乎是微不足道的。
就人脑的进化而言,**处理图像的速度远远快于处理文字,**所以,有“一图胜千言”的说法。
通过创建图像、图标或动画等进行信息交流的形式就是可视化Visualization。可视化有很多种不同的分类我们最常用的应该是数据可视化和信息可视化。
我在“[你的工作可以用数字衡量吗](http://time.geekbang.org/column/article/76929)”这篇文章里说过,我上班第一件事是“看”数字,这就是典型的数据可视化,而上面介绍的技术雷达,就属于信息可视化。
很多做软件的人习惯于用文字进行沟通,一般在软件开发过程中,需要编写各种文档,但并不是所有的场景,文字都是好的沟通方式,所以,也会有很多人尝试着将可视化应用在软件开发过程中。
估计大多数程序员最熟悉的表达方式应该是流程图,如果你做过软件设计,可能还听说过 UML统一建模语言Unified Modeling Language。如果使用得当这种方式会极大地提高表达的准确性降低其他人理解的门槛。
在日常工作中,你最熟悉的可视化方式,大概就是在纸上或白板上画的图。以我的经验看,很多人画这个图太随意,如果你也是这样,我给你一个建议,先写字后画框,这样图会显得整洁一些。
## 什么是看板?
我们再来看一个实践,这就是将“可视化”应用在工作中的典型案例:看板。
看板,是一种项目管理工具,它将我们正在进行的工作变得可视化。这个实践来自精益生产,前面讲精益创业时,我给介绍了“精益”这个来自丰田公司的管理理念。精益的理念在软件行业已经非常流行了,很多软件开发实践都是从“精益”而来,看板就是其中之一。
![](https://static001.geekbang.org/resource/image/2c/07/2c0754dd615fac0467eaa68d627d9307.png)
看板属于那种几乎是看一眼就知道怎么用的实践。它将工作分成几个不同的阶段,然后,把分解出来的工作做成一张卡片,根据当前状态放置到不同的阶段中。如果你采用了我们专栏之前讲过的用户故事,那么每个用户故事就是一张卡片。
在实际工作中,每当一个工作完成之后,它就可以挪到下一个阶段,工作怎么算完成就是由我们前面提到的 DoD 来决定的。
当然,要用好看板,还可以使用一些小技巧。比如,用不同颜色的卡表示不同类型的工作,给每个人一个头像,增添一些乐趣。
看板可以帮助你一眼看出许多问题比如你的团队中有5个人却有8个正在进行的任务那一定是有问题的。因为一个人多线程工作效果不会好。用“精益”的术语来说我们应该限制 WIPWork-In-Progress再有如果待开发的卡最多那就证明现在的瓶颈在于开发而不是其它阶段。
运用看板的方式,还有一个有趣的细节:使用实体墙还是电子墙。实体墙不难理解,就是找一面墙把看板做出来。现在有很多公司专门在做协同办公软件,其中的项目管理部分用到的就是看板理念,这就是电子墙的由来。
关于这点,顺便说一下我的建议,如果你的团队是在一起工作的,请考虑使用实体墙,除非你的办公空间实在太小。因为它可以方便地调整,也可以当作站会的集合地点,还可以让别人看见你们的工作或是问题,这样做的最大优势在于增强了人与人的互动。
电子墙的优势在于,随处可访问、数据不会丢失、便于统计等等,但每次访问它,都需要专门打开电脑,还是比较麻烦的。一种将二者结合的办法是,使用一个大电视,专门用来展示电子墙。
总之,看板就是要让工作在大家面前展现出来。
## 总结时刻
我给你介绍了一种结构化学习新知识的方式:技术雷达。
技术雷达就是一种将技术信息组织起来的方式。它通过将技术按照“象限”和“圆环”两个维度进行分类,让人可以直观地看到并理解不同的技术所处的发展阶段。
雷达图是一种很好的形式,不仅可以用在组织技术,还可以用来组织其它信息,比如,读书雷达。每个公司都可以利用雷达图的形式组织自己所有的技术,每个团队也可以利用雷达图的形式组织自己团队用到的技术,这样,方便团队成员结构化地理解用到技术,也方便新人的学习。
雷达图实际上是一种可视化的方法人脑对于图像处理速度更快因此可视化是改善沟通的一种方式。大多数软件过程习惯采用文字的方式进行表达对于“可视化”利用的还不够。当然还是有一些利用“可视化”的方法比如流程图、UML 等。
最后,我给你介绍了一个利用可视化进行信息沟通的实践:看板。看板把工作分成了几个不同的阶段,在看板上对应不同的列,然后,每个任务作为一张卡贴在上面。每完成一张卡,就把这张卡挪到下一个阶段。
看板可以帮你发现许多问题,比如,当前进展是否合适,是否有人同时在做很多的事,发现当前工作的瓶颈等等。
如果今天的内容你只能记住一件事,那请记住:**多尝试用可视化的方式进行沟通。**
最后,我想请你思考一下,你在工作中,有哪些用到可视化方法解决沟通问题的场景?欢迎留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,148 @@
# 24 | 快速反馈:为什么你们公司总是做不好持续集成?
你好,我是郑晔。
在“以终为始”那个模块,我们留下了一个巨大的尾巴。在“[持续集成:集成本身就是写代码的一个环节](http://time.geekbang.org/column/article/75977)”这篇文章中,我们是站在“以终为始”的角度阐述了集成,尤其是持续集成的重要性。
但怎么做好持续集成,才是很多人真正关心的内容。今天,我们就来谈谈如何做好持续集成。
既然我们打算讨论持续集成,不妨停下来先思考一个问题:你对持续集成的第一印象是什么。
持续集成Jenkins没错很多人对持续集成第一印象都是持续集成服务器也就是 CI 服务器,当年是 CruiseControl今天换成了 Jenkins。
也正是因为如此,很多人就把 CI 服务器理解成了持续集成。我就曾经接触过这样的团队,他们恨不得把所有的事情都放在 CI 服务器上做:在 CI 服务器上做了编译,跑了代码检查,运行了单元测试,做了测试覆盖率的统计等等。
或许你会疑问,这有什么不对的吗?
在做软件这件事上,我们不会用对与错去衡量,我只能说,这种做法是可行的,但它不是最佳实践。我希望你去思考,有没有比这更好的做法呢?
想要回答这个问题,我们还是要回到持续集成的本质上去。持续集成的诞生,就是人们尝试缩短集成周期的结果。为什么要缩短周期呢?因为我们希望尽早得到反馈,知道自己的工作结果是否有效。
所以,想要做好持续集成,就需要顺应持续集成的本质:尽快得到工作反馈。
由此,我们便得到持续集成的关键点,你只要记住一句话,**快速反馈。**
快速反馈,这句分成两个部分,快速和反馈,这也就引出了持续集成的两个重要目标:**怎样快速地得到反馈,以及什么样的反馈是有效的。**
## 快速得到反馈
我们回到前面的例子上,把各种检查放到 CI 服务器上执行,它可以让我们知道代码是不是有问题,这是一个有效的反馈,但它的反馈够快速吗?虽然比起没有持续集成的状态,它是好很多。但是,我们需要问一个问题,能不能更快地得到反馈呢?
显然,我们还可以做得更快。在自己的开发机上执行这些检查,就会比在 CI 服务器快。也就是说,**执行同样的操作,本地环境会快于 CI 服务器环境。**
为什么会这样呢?我们先来看看所有检查在 CI 服务器上执行,每个程序员的动作是什么样的。
我们写好代码,然后需要提交代码,等待 CI 服务器运行检查结果,然后,用 CI 监视器查看执行结果。如果没问题,继续做下一个任务,如果有错误,修复错误,再执行同样的过程。
![](https://static001.geekbang.org/resource/image/31/cc/318016501ec6de73ac39ae9392c5eecc.jpg)
再来看看本地执行的动作。运行构建脚本,如果一切正确,你可以选择提交代码或是继续下一个任务,如果失败,立即修复。
![](https://static001.geekbang.org/resource/image/99/c1/99294ff1d2b2c25c5433ca653d4ffcc1.jpg)
对比之下,在本地运行这些检查,你不需要提交,不需要等 CI 服务器开始执行,不需要跑到额外的地方查看检查结果。所以,这个操作比提交到服务器上会快很多。
另外,这里还有一个关键点,我们的操作是连续的。一旦检查结果出错了,我们立刻进入修复环节。作为程序员,我们太了解连续操作的重要性了。这就像打游戏时,我们感觉不到时间流逝一般,有人把这种状态称之为“心流”。
而提交代码,等待 CI 服务器的检查结果,就等于强迫你停下来,你的心流就被打断了。
如果你对心流的概念感兴趣,可以去读米哈里·契克森米哈赖的著作[《心流》](http://book.douban.com/subject/27186106/),这位作者就是心流概念的提出者。
前面我们只是在说,你作为程序员个体,使用持续集成的效果,这只是为了简化讨论。接下来,我们向更真实的世界靠拢,引入另一个重要的因素:团队协作。
假设你的团队就是在 CI 服务器上执行检查。你兴高采烈地写完一段代码准备提交,结果,此时你隔壁的同事手快一筹,先提交了,你不得不停下来等他。如果很不幸,你同事的检查失败的话,那么他又要把它修复好,你等的时间就更长了。
一个小问题也就罢了,如果是个大问题,他可能要修很长一段时间。这个时候,你除了等待,也没有更好的选择。如此一来,大把的时间就被浪费掉了。
这里我们要“插播”持续集成中重要的一个提交纪律:**只有 CI 服务器处于绿色的状态才能提交代码。**有检查在运行不能提交,有错误不能提交。原因很简单,如果这个时候多个人提交了代码,检查失败了,那问题到底算谁的呢?
反之,如果一次只有一个人提交代码,责任是明确的。如果团队不大,这个纪律相对还好执行,提交之前看一眼,或是喊一声就可以了。
如果团队稍微有一点规模,可以用一个小东西当作令牌,谁拿到了谁来提交。如果真的有人在 CI 服务器还在运行的时候,提交了代码怎么办?很简单,谁提交谁负责,错了就他修,谁让他违反纪律了。
好,你已经理解了我说的重点:**不能把检查只放到 CI 服务器上执行。**那该怎么做呢?答案已经呼之欲出了,那就是在本地开发环境上执行。
想做好持续集成的一个关键点是,**用好本地构建脚本build script保证各种各样的检查都可以在本地环境执行。**
一旦有了构建脚本,你在 CI 服务器上的动作也简单了,就是调用这个脚本。也就是说,本地检查和 CI 服务器上的动作是一致的。
至于什么样的内容适合放在构建脚本里,这个话题我们先放一放,把它留到后续“自动化”模块再做讨论。
在“任务分解”模块中,我与你讨论了“小”动作在工作中的重要性,“小”动作完成得越快,工作反馈得到也越快,所以说,也只有坚持不懈地做“小”动作,才能缩短反馈周期。
现在我们把这个道理与持续集成结合起来理解,我们的工作流程就变成了这样:
每完成一个任务,在本地运行构建脚本,如果有问题,就修复;没问题,则可以同步代码。如果 CI 服务器上没有正在运行的服务,就可以提交代码了。
![](https://static001.geekbang.org/resource/image/85/43/8589e69a2774c1f7c9553afb4d644843.jpg)
提交代码中最麻烦的动作,其实是合并代码。不过,因为我们做的是小任务,改动的代码量并不大,所以,即便有需要合并的代码,量也不会很大,所需的脑力以及工作时间都会少很多。如此一来,我们的开发效率才可能能真正得到提高。
当团队真正地实施起持续集成,你会发现随着时间增加,本地检查的时间会越来越长。原因有很多,比如,代码越来越多,测试也越来越多。总之,检查的时间长了,就会对集成的速度造成影响。
这个时候,本着快速反馈的理念,我们就必须想办法。比如,有的团队做了分布式测试运行,有的团队将测试分类,就是我们在测试金字塔中讲到的分类,在本地执行单元测试和集成测试,而把更复杂的系统测试放到 CI 服务器上运行。
**简单来说,我们的目的就是快速地得到反馈。**
## 得到有效的反馈
说完了“快速”,我们再来看看做好持续集成的第二个重点:**反馈,也就是怎么得到有效的反馈。**
为什么需要反馈,道理很简单,我们得知道自己做得对不对。你可能会问,根据前面的说法,如果本地和 CI 服务器上执行的是一样的脚本,我在本地通过了,还用关心 CI 服务器的反馈吗?
当然要。因为还会出现很多其他问题,比如说最简单的一种情况是,你漏提交了一个文件。
既然我们要关注CI 服务器的反馈,下一个问题就是,它怎么反馈给我们呢?
我们还是从一种常见的错误入手。有些团队做持续集成用的反馈方式是什么呢?答案是邮件。
以邮件进行反馈,问题出在哪里呢?很明显,邮件不是一种即时反馈的工具。
我不知道有多少人会把邮件客户端当作日常的工具,就我个人习惯而言,一天查看几次邮件就算不错了,如果以邮件作为反馈方式,很有可能是出错了很长时间,我都无知无觉。
我们前面一直在强调快速,需要的是即时反馈,一旦邮件成了持续集成链条中的一环,无论如何都快不起来。
那你可以怎么做呢?在前面各种讨论中,我其实已经透露了答案:持续集成监视器,也是 CI 监视器。
![](https://static001.geekbang.org/resource/image/e0/03/e0971c121dbce75487d4033131711603.png)
(图片来源:[CI 监视器的示例 projectmonitor](http://github.com/pivotal-legacy/projectmonitor)
CI 监视器的原理很简单CI 服务器在构建完之后会把结果以API的方式暴露出来早期有RSS和ATOM格式后来有JSON的格式。得到的结果就可以用不同的方式进行展现了。市面上有很多CI 监视器的软件,有的是拿到结果之后,做一个视觉呈现,有的是做桌面通知。
现在,我们终于要讲到这个部分的重点了:**怎么呈现是有效的?**
答案很简单:**怎么引人注目,怎么呈现。**
比如,很多团队的做法是,用一个大屏幕将持续集成的结果展示出来,这样一来,持续集成的结果所有人都能看到,一旦出错了,即便你一时疏忽,也会有人来提醒你。
还有一些感官刺激的做法,比如,有人用上了红绿灯,测试失败则红灯闪烁;还有人甚至配上了语音,用喇叭高喊:“测试失败了,请赶紧修复。”我在一个视频里见过一个更夸张的做法:有人用玩具枪,出错了,就瞄准提交者开上一枪。
你是聪明的程序员,你应该能想到更多有趣的玩法。
为什么要这么做呢?这里的重点是,想做好持续集成,需要整个团队都关注持续集成。
这些引人注目的做法,就是要提高持续集成的关注度。否则,即便持续集成的技术环节做得再出色,人的注意力不在,持续集成也很难起到作用。
所以,你看到了,持续集成的反馈,尤其是出错之后的反馈方式,几乎是所有实践中最为高调的,它的目的就是要引人注目。
这里再插播一条持续集成的纪律:**CI 服务器一旦检查出错,要立即修复。**原因很简单,你不修,别人就不能提交,很多人的工作就会因此停顿下来,团队的工作流就会被打断,耽误的是整个团队的工作。
如果你一时半会修不好怎么办,撤销你的提交。更关键的原因是,团队对于持续集成的重视度,长时间不修复,持续集成就失去了意义,人们就会放弃它,持续集成在你的项目中,也就发挥不出任何作用了。
## 总结时刻
持续集成是软件开发中的重要实践,做好持续集成的关键在于,快速反馈。这里面有两个目标,怎样快速地得到反馈,以及什么样的反馈是有效的。
做好快速反馈,要把本地能做好的事情,在本地做好;也要通过小步提交的方式,加快代码开发的节奏。什么是有效的反馈?一是即时的反馈,二是引人注目的反馈。有很多种持续集成相关的工具可以帮助我们达成有效的反馈。
想要做好持续集成,还要有一些纪律要遵循:
* 只有 CI 服务器处于绿色的状态才能提交代码;
* CI 服务器一旦检查出错,要立即修复。
如果今天的内容你只能记住一件事,那请记住:**做好持续集成的关键在于,快速反馈。**
最后,我想请你分享一下,你的团队做持续集成吗?遇到过哪些困难呢?欢迎留言与我们分享。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,116 @@
# 25 | 开发中的问题一再出现,应该怎么办?
你好,我是郑晔。
看过《圣斗士星矢》的同学大多会对其中的一个说法印象颇深:圣斗士不会被同样的招数击败两次。
我们多希望自己的研发水平也和圣斗士一样强大,可现实却总不遂人愿:同样的线上故障反复出现,类似的 Bug 在不同的地方一再地惹祸,能力强的同学每天就在“灭火”中消耗人生。我们难道就不能稍微有所改善吗?
如果在开发过程中,同样的问题反复出现,说明你的团队没有做好复盘。
## 什么是复盘?
复盘,原本是一个围棋术语,就是对弈者下完一盘棋之后,重新把对弈过程摆一遍,看看哪些地方下得好,哪些下得不好,哪些地方可以有不同甚至是更好的下法等等。
**这种把过程还原,进行研讨与分析的方式,就是复盘。**
现如今,复盘的概念已经被人用到了很多方面,比如,股市的复盘、企业管理的复盘,它也成为了许多人最重要的工具,帮助个体和企业不断地提升。这其中最有名的当属联想的创始人柳传志老爷子,他甚至把“复盘”写到了联想的核心价值观里。
为什么复盘这么好用呢?在我看来有一个重要的原因,在于**客体化。**
俗话说,当局者迷,旁观者清。以我们的软件开发作为例子,在解决问题的时候,我们的注意力更多是在解决问题本身上,而很少会想这个问题是怎么引起的。
当你复盘时,你会站在另外一个视角,去思考引起这个问题的原因。这个时候,你不再是当事者,而变成了旁观者。你观察原来那件事的发生过程,就好像是别人在做的一样。你由一个主观的视角,变成了一个客观的视角。
**用别人的视角看问题,这就是客体化。**
在软件开发领域,复盘也是一个重要的做法,用来解决开头提到那些反复出现的问题,只不过,它会以不同的方式呈现出来。
## 回顾会议
回顾会议是一个常见的复盘实践,定期回顾是一个团队自我改善的前提。回顾会议怎么开呢?我给你分享我通常的做法。
作为组织者,我会先在白板上给出一个主题分类。我常用的是分成三类:“做得好的、做得欠佳的、问题或建议”。
还有不同的主题分类方式,比如海星图,分成了五大类:“继续保持、开始做、停止做、多做一些、少做一些”五类。
分类方式可以根据自己团队的喜好进行选择。我之所以选用了三类的分类方式,因为它简单直观,几乎不需要对各个分类进行更多的解释。
然后,我会给与会者五分钟时间,针对这个开发周期内团队的表现,按照分类在便签上写下一些事实。比如,你认为做得好的是按时交付了,做得不好的是 Bug 太多。
这里面有两个重点。**一个是写事实,不要写感受。**因为事实就是明摆在那里的东西,而感受无法衡量,你感觉好的东西,也许别人感觉很糟糕。
另外,**每张便签只写一条,因为后面我要对便签归类。**因为大家是分头写的,有可能很多内容是重复的,所以,要进行归类。
五分钟之后,我会号召大家把自己写的便签贴到白板上。等大家把便签都贴好了,我会一张一张地念过去。
这样做是为了让大家了解一下其他人都写了些什么,知道不同人的关注点是什么。一旦有哪一项不清楚,我会请这张便签的作者出来解释一下,保证大家对这个问题的理解是一致的。在念便签的同时,我就顺便完成了便签归类的工作。
等到所有的便签都归好类,这就会成为后续讨论的主题,与会者也对于大家的关注点和看到的问题有了整体的了解。
做得好的部分,是大家值得自我鼓励的部分,需要继续保持。而我们开回顾会议的主要目的是改善和提升,所以,我们的重点在于解决做得不好的部分和有问题出现的地方。
在开始更有针对性的讨论之前,我会先让大家投个票,从这些分类中选出自己认为最重要的几项。我通常是给每人三票,投给自己认为重要的主题。每个人需要在诸多内容中做出取舍,你如果认为哪一项极其重要,可以把所有的票都投给这个主题。
根据大家的投票结果,我就会对所有的主题排出一个顺序来,而这就是我们要讨论的顺序。我们不会无限制的开会,所以,通常来说,只有最重要的几个主题才会得到讨论。
无论是个人选择希望讨论的主题,还是团队选择最终讨论的主题,所有人都要有“优先级”的概念在心里。然后,我们就会根据主题的顺序,一个一个地进行讨论。
讨论一个具体的主题时,我们会先关注现状。我会先让写下反馈意见的人稍微详细地介绍他看到的现象。比如,测试人员会说,最近的 Bug 比较多相比于上一个开发周期Bug 增加了50%。
然后,我会让大家分析造成这个现象的原因。比如,有人会说,最近的任务量很重,没有时间写测试。
再下来,我们会尝试着找到一个解决方案,给出行动项。比如,任务重,我们可以让项目经理更有效地控制一下需求的输入,再把非必要的需求减少一下;测试被忽略了,我们考虑把测试覆盖率加入构建脚本,当测试覆盖率不足时,就不允许提交代码。
请注意,**所有给出的行动项应该都是可检查的,而不是一些无法验证的内容。**比如,如果行动项是让每个程序员都“更仔细一些”,这是做不到的。因为“仔细”这件事很主观,你说程序员不仔细,程序员说我仔细了,这就是扯皮的开始。
而我们上面给出的行动项就是可检查的,项目经理控制输入的需求,我们可以用工作量衡量,还记得我们在讨论用户故事中提到的工作量评估的方式吗?
控制工作量怎么衡量?就是看每个阶段开发的总点数是不是比上一个阶段少了。而测试覆盖率更直接,直接写到构建脚本中,跑不过,不允许提交代码。
好,列好了一个个的行动项,接下来就是找责任人了,责任人要对行动项负责。
比如,项目经理负责需求控制,技术负责人负责将覆盖率加入构建脚本。有了责任人,我们就可以保障这个任务不是一个无头公案。下一次做回顾的时候,我们就可以拿着一个个的检查项询问负责人任务的完成情况了。
## 5个为什么
无论你是否采取回顾会议的方式进行复盘,分析问题,找到根因都是重要的一环。
你的团队如果能一下洞见到根因固然好如果不能那么最好多问一些为什么。具体怎么问有一个常见的做法是5个为什么5 Whys。这种做法是丰田集团的创始人丰田佐吉提出的后来随着丰田生产方式而广为人知。
为什么要多问几个为什么?因为初始的提问,你能得到的只是表面原因,只有多问几个为什么,你才有可能找到根本原因。
我给你举个例子。服务器经常返回504那我们可以采用“5个为什么”的方式来问一下。
* 为什么会出现504呢因为服务器处理时间比较长超时了。
* 为什么会超时呢?因为服务器查询后面的 Redis 卡住了。
* 为什么访问 Redis 会卡住呢?因为另外一个更新 Redis 的服务删除了大批量的数据,然后,重新插入,服务器阻塞了。
* 为什么它要大批量的删除数据重新插入呢?因为更新算法设计得不合理。
* 为什么一个设计得不合理的算法就能上线呢?因为这个设计没有按照流程进行评审。
问到这里,你就发现问题的根本原因了:设计没有经过评审。找到了问题的原因,解决之道自然就浮出水面了:一个核心算法一定要经过相关人员的评审。
当然,这只是一个例子。有时候,这个答案还不足以解决问题,我们还可以继续追问下去,比如,为什么没有按流程评审等等。
**所以“5个为什么”中的“5”只是一个参考数字不是目标。**
“5个为什么”是一个简单易上手的工具你可能听了名字就知道该怎么用它。有一点需要注意的是问题是顺着一条主线追问不能问5个无关的问题。
无论是“回顾会议”也好“5个为什么”也罢其中最需要注意的点在于不要用这些方法责备某个人。我们的目标是想要解决问题不断地改进而不是针对某个人发起情感批判。
## 总结时刻
在软件研发中,许多问题是反复出现的,很多开发团队会因此陷入无限“救火”中,解决这种问题一个好的办法就是复盘。
复盘,就是过程还原,进行研讨与分析,找到自我改进方法的一个方式。这种方式使我们拥有了客体化的视角,能够更客观地看待曾经发生过的一切。这种方法在很多领域中都得到了广泛的应用,比如股市和企业管理。
在软件开发中,也有一些复盘的实践。我给你详细介绍了“回顾会议”这种形式。
无论哪种做法分析问题找到根因是一个重要的环节。“5个为什么”就是一个常用的找到根因的方式。
如果今天的内容你只能记住一件事,那请记住:**定期复盘,找准问题根因,不断改善。**
最后我想请你分享一下,你的团队是怎么解决这些反复出现的问题呢?欢迎在留言区写下你的做法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,100 @@
# 26 | 作为程序员,你也应该聆听用户声音
你好,我是郑晔。
在前面的专栏内容中,我们讨论过几次与产品经理的交流:你应该问问产品经理为什么要做这个产品特性,要用 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 行业的经典。
在今天这一讲,我给你介绍了“了解用户需求”的不同做法,但其归根结底就是一句话,想办法接近用户。
无论是自己做用户,还是找机会接触已有用户,亦或是没有用户创造用户。只有多多听取来自真实用户的声音,我们才不致于盲目自信或是偏颇地相信产品经理。**谁离用户近,谁就有发言权,无论你的角色是什么。**
如果今天的内容你只能记住一件事,那请记住:**多走近用户。**
最后,我想请你思考一下,在你的实际工作中,有哪些因为走近客户而发现的问题,或者因为没有走近客户造成的困扰呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,136 @@
# 用户故事 | 站在前人的肩膀上,领取属于你的高效工作秘籍
丁雪丰
极客时间《玩转Spring全家桶》专栏作者平安壹钱包高级架构师
**1.为什么订阅《10x程序员工作法》**
我与郑晔老师是多年好友不过大家平时联系并不多去年在一次聚会上得知他要在极客时间上开专栏当时并没有去了解专栏的内容直到专栏上线后我才知道这门课就是《10x程序员工作法》。
从专栏名称上就能大概得知,这门课程的目的主要是为了提升大家的工作效率,开篇词中有一句大实话:
> 大部分程序员忙碌解决的问题,都不是程序问题,而是由偶然复杂度导致的问题。
正所谓前人的经验我们的阶梯我想看看作为一个超级资深的从业者郑晔老师是怎么看待这个问题的他又是怎么总结自己的心得的。要知道他曾经是Thoughtworks的首席咨询师他帮这么多公司做过各种项目一定是有秘籍的。所以我毫不犹豫地在第一时间订阅了这个专栏。
**2.对于高效工作有哪些心得?**
郑晔老师在专栏每一课的最后,都会用一句话来总结这一课的内容,我很喜欢这个风格,那我也先用一句话来说一下,我对高效工作的理解:能用尽可能小的时间颗粒度来安排自己的工作。
高效工作,如果按字面理解,就是在指定时间里能完成更多的工作(当然还得是合格的工作)。《五分钟商学院》的作者刘润老师曾提到过时间颗粒度的概念,你会发现,很多成功人士划分时间的颗粒度都比较小。王健林的时间颗粒度是十五分钟,比尔盖茨的时间颗粒度是五分钟。
你可以思考一下你是按什么颗粒度来安排自己工作的呢1天半天1小时还是15分钟
另外,在把时间拆细的同时,也不能忽略对工作内容的分解。其实,对工作内容进行分解的颗粒度大小,同样对工作效率有很大影响。
讲到这里又不得不提到《10x程序员工作法》专栏我看到郑晔老师在“任务分解”这个模块里详细地讲解了怎样将工作任务进行拆解如果对这部分内容感兴趣的同学也可以去订阅专栏。
**3.学习《10x程序员工作法》专栏的感受**
我认为这个专栏最大的价值,就是为我们程序员的工作,划出了清晰的、可以遵循的高效工作原则。
我举个具体的例子吧,我团队中不止有一个同学是技术能力非常突出的,他们在拿到一个任务后,立马就能想到大概的解决方案,紧接着就开始写代码了……没错,撸袖子就开始写代码了。
结果就是,他们之后要开始漫长地“填坑”之旅。运气好的话,是可以在上线前把“坑”填完,上线后没什么事;运气不好的话,得要好几个版本才能把“坑”填完。
作为过来人,我在看到他们这么快就开始写代码后,就会把他们拦下来,跟他们聊聊各种需要考虑的事项,他们之前忽略掉的问题,或者是需要细化的点出来。
在我看了专栏中的“以终为始”这个模块内容之后,我发现这不正是我一直在寻找的东西吗?我平时零散地问各种问题,让他们考虑各种点,现在郑晔老师都已经把这些常见的实践,提炼总结成为高效工作的原则。
我们可以不用在针对工作中的各种问题孤立地作出反应,而是有了一套行之有效的原则可以遵循。
如果说我们平时实践的是“术”,那郑晔老师讲的就是“道”。我觉得只“以终为始”这一部分的内容就已经完全值回票价了。
Ivan
10+年研发经验,坐标苏州,目前在医疗信息化软件公司从事研发兼管理工作。
**1.为什么订阅《10x程序员工作法》**
记得刚开始工作时,一门心思想着多做项目,多学技能,多攒经验,当然,战斗数值也增长不少,但就像武侠小说中的习武之人都会碰到的困惑,总觉得是围绕着招式在打转,而修为不足。
之后我也逐渐也有意识培养自己的思维方式和从多角度看问题的习惯,这些年也算形成了一些自己的价值观和方法论。
我现在服务的公司是一家初创公司,产品化和定制项目开发之路走的并不轻松,我发现不是团队效率低下,也不是不知道如何解决各种问题,而是我们投入很多精力在处理“偶然复杂度”的问题,我想这可能也是工作不轻松的原因之一吧。
所以在听郑晔老师的开篇词时,我就有一种被吸引的感觉,因为我觉得问题既然抛出了,后续肯定会有解决方案。
当然,每个人都会碰到自己的问题,所以要想解决问题需要有系统的思考框架,而大多数问题又存在共性,应该会有一些普适原则,这些在开篇词中都有提及,听着听着继续探究的兴趣也越来越浓了。
我是个偏向实用主义的人除了“醍醐灌顶”的那一时快感更看重方法在实践中的运用和反思我相信《10x程序员工作法》中介绍的经验和最佳实践一定能给我和团队带来工作效率和工作绩效的提升
为了保持学习思考的连贯,我都是紧跟一周三次的更新节奏同步收听,即使春节期间也未间断,而且部分内容会反复听,每次重听都有新的感受。
而从第二部分开始,我觉得不光是收听,还要用文字表达出来,毕竟有互动有交流的学习才是有效的学习,而且我是强制自己一定要留言,有思考的留言。
**2\. 学习《10x程序员工作法》的心得**
基于之前的学习过程,我再总结下我的学习心得。
**a.思维方式的改变**
课程虽然针对程序员,但受众却超过程序员范围。即使对于程序员来说,在工作中也要拓展自己的上下文,将自己放在更大的范围、平台上去思考问题,主动发掘问题关键点,在面对不同职能人员间沟通时,多运用“以终为始”模块中的知识尝试解决分歧,达成共识。
此时我或许明白了之前公司老板常说的一句话“你们都是工程师思维”,或许并不是将自己想象成其他角色,而是在更大的上下文背景中寻找突破点。
**b.工作习惯的培养**
程序员的工作习惯不仅仅是每天上班打开邮箱,提交代码前编译通过等等事务性习惯,还包括:**你的思维方式是否合适,由此产生的执行方式是否到位,检验方式是否科学,理论修正是否能产生更好的思维方式。**
就像专栏中讲精益创业时,提到的“想法->产品->数据”对应的“开发->测量->认知”这样的反馈模型。
**c.可以做的更好**
我们可能习惯于之前的工作方式,有时即便感觉别扭,但仍然只是希望在原有方式上修修补补,而不愿去仔细区分现实和期望,常常会以忙为理由,为自己辩解。
**但如果不忙,你知道该怎么做吗?**这恐怕是绝大多数程序员都要自省的一个问题,但这也恰恰说明我们在工作方式上是有很大的提升空间。
**d.主动思考也很重要**
孔子说“学而不思则罔思而不学则殆”《10x程序员工作法》并不是技术技能课可以直接准备环境代码走起而是一种方法论课、答疑解惑课、实践验证课听起来很过瘾很能戳中痛点但如何解决自己的实际问题则需要主动思考主动交流。
这也是我后来逼着自己每学完一课都要思考后留言的初衷,并且我还会向周围人推荐这种学习方式。
其实,对于任何方面的知识,我都奉行“一学二懂三要用”,四要知道局限性的准则,不断更新自己的认知体系,力求做到融会贯通,逐步形成适合自身的知识架构和行动准则。
One day
4年研发工作经验坐标上海目前就职于一家汽车行业的公司做后端开发
**1.为什么订阅《10x程序员工作法》**
想起刚毕业的时候我还是能够前后端通吃的因为那个时候很多公司使用的还是jsp+servlet等简单的单体应用所有功能全部集成在一个工程项目中。
等所有人把各自模块开发完就丢一个war包到服务器然后就进行测试就完事了。等测试小哥哥或小姐姐发现Bug的时候自己就从前端到后端一步步的debug虽然有很多血与泪的历史但是累并快乐着。
但工作久了之后我发现很多工作是重复且无意义的然后就是加班与熬夜并存。现在我已经在软件开发行业已经混迹4年多从一个混沌不知的菜鸟也渐渐有了自己学习方法。
但是个人经历总是有限,还是会遇到很多问题。工作久了也越发觉得时间的宝贵,也越来越迫切地想要追求更为高效的工作方法。
最开始看到专栏标题的时候还心有疑惑极客时间会有标题党我学了这个专栏工作学习能提高10倍吗
但当看到10X程序员工作法的内容我就已经被郑晔老师的能力所折服因为专栏目录中提到的各种思考原则真的是很贴合实际。
工作能力的提升,本身是经验的积累,但是如果长时间堆砌同类型的业务知识,对于大脑来说是一种重复,也不利于个人经验的沉淀。实践和理论总是相辅相成的,我是一个实践派,但是对于理论的方法,我也非常赞赏。
这相当于是站在前人的肩膀上,你在实践的过程中,得到与之类似或者不同的结果。以结果为导向,再结合理论就有不同的火花,我认为这是一件很神奇的事情。
**2\. 学习《10x程序员工作法》专栏的心得**
**a. 多思、多问**
我们的实际工作常常会被打乱,需求频繁变更,做出的东西与产品有出入等等。这其中很多情况我都经历过,比如,拿到新需求任务时,头脑没有思绪,大脑出现空白,直接上手就做。然后,到测试和交付的时候就经常出现问题。
结合老师说四项基本原则,简化思考框架,遇到问题多思考、多提问、多练习,去梳理真正要做的事情。清楚事物背后的本质,有效剔除不必要的思虑,让自己专注于任务本身。
现在我在接收一个新需求的时候,常常会思考这个需求的意义是什么?到底需不需要做?要怎么做才能更好?有没有好的替代方案?需求应用的场景是什么?这样做以后方便维护吗?
之类的问题,思考的越多,在以后的写代码的时候,就会考虑更多。需要并发吗?并发量多少?我们服务器的承受压力多大?我应该用多少个线程跑这个才合适呢?这样写出来的代码也会很健壮。修修改改,缝缝补补的代码既不利于个人发展,对后面的迭代也是一种煎熬。
**b.以结果为导向**
我们日常都是按部就班的完成需求任务确定好任务安排于是就开始写代码。这个时候常常会有不那么预期的事情发生因为测试一旦给提了Bug计划的周末约会可能就泡汤了陷入加班的窘态中。
这时,我们就要以结果为导向,考虑到即将要做的事情,再将要做的事情一步步细分,然后做项目推演,带着明确的目的去工作,这样可以少走弯路。
**c.站在不同角色角度看问题**
我们工作出现的很多问题,都是因为我们只站在程序员的角度,只能看到局部,缺少大局观导致的。不同职位在协作的过程中,认知和理解的的差异直接导致最终不能达成一致性意见,到最后还是苦逼了程序员,在做无限制的修改。
项目本身是有不同人员角色组合的共同体,大家为了达到共同的目的在做事,所以有必要跳出程序员角色思维的限制,站在项目本身去看待每一个问题。必要的沟通是需要的,对待不合理的需求,我们程序员也要敢于说不。

View File

@ -0,0 +1,124 @@
# 27 | 尽早暴露问题: 为什么被指责的总是你?
你好,我是郑晔。
今天我准备讨论一个经常会让很多程序员郁闷的事情,为什么你已经工作得很辛苦了,但依然会被指责。在讨论这个问题之前,我们先来讲一个小故事。
程序员小李这天接到了一个新的任务。系统要做性能提升,原先所有的订单都要下到数据库里,由于后来有很多订单都撤了,反复操作数据库,对真正成交过程的性能造成了影响。所以,技术负责人老赵决定把订单先放到缓存里。
这就会牵扯到一个技术选型的问题,于是,老赵找了几个可以用作缓存的中间件。为了给大家一个交代,老赵决定让小李给这几个中间件做一个测试,给出测试结果,让大家一起评估。小李高兴了,做这种技术任务最开心,可以玩新东西了。
老赵问他:“多长时间可以搞定?”
小李说:“一个星期吧!”
老赵也很爽快,“一个星期就一个星期。不过,我得提个要求,不能是纯测中间件,得带着业务跑。”
“没问题。”小李一口答应下来。老赵怕小李做多了,还特意嘱咐他,只测最简单的下单撤单环节就好。
等真的开始动手做了,小李发现,带着业务跑没那么容易,因为原来的代码耦合度太高,想把新的中间件加进去,要先把下单和撤单环节隔离开来。而这两个操作遍布在很多地方,需要先做一些调整。
于是,小李只好开始不分白天黑夜地干起来。随着工作的深入,小李越发觉得这个活是个无底洞,因为时间已经过半了,他的代码还没调整完。
这时,老赵来问他工作进展,他满面愁容地说,估计干不完了。老赵很震惊,“不就是测试几个中间件吗?”
小李也一脸委屈,“我们为啥要带着业务跑啊?我这几天的时间都在调整代码,以便能够把中间件的调用加进去。”
老赵也很疑惑,“你为啥要这么做?”
“你不是说要带着业务跑吗?”
“我是说要带着业务跑啊!但你可以自己写一个下单撤单的程序,主要过程保持一致就好了。”小李很无奈,心里暗骂,你咋不早说呢?
是啊!你咋不早说呢?不过,我想说不是老赵,而是小李。
## 谁知道有问题?
我们来分析一下问题出在哪。在这个故事里,小李和老赵也算有“以终为始”的思维,在一开始就确定了一个目标,做一个新中间件测试,要带着业务跑。
小李可以说是很清楚目标的,但在做的过程中,小李发现了问题,原有代码很复杂,改造的工作量很大,工作可能没法按时完成。
到此为止,所有的做法都没有错。**但接下来,发现问题的小李选择了继续埋头苦干,直到老赵来询问,无奈的小李才把问题暴露出来。**
在老赵看来,这并不是大事,调整一下方案就好了。但是小李心生怨气,在他看来,老赵明明有简单方案,为啥不早说,害得自己浪费了这么多时间。
但反过来,站在老赵的角度,他是怎么想的呢?“我的要求是带着业务跑,最理想的方案当然是和系统在一起,你要是能搞定,这肯定是最好的;既然你搞不定,退而求其次,自己写一个隔离出来的方案,我也能接受。”
你看出来问题在哪了吗?老赵的选择没有任何问题,问题就出在,**小李发现自己可能搞不定任务的时候,他的选择是继续闷头做,而不是把问题暴露出来,寻求帮助。**
作为一个程序员,克服技术难题是我们工作的一个重要组成部分,所以,一旦有困难我们会下意识地把自己投入进去。但这真的是最好的做法吗?并不是,**不是所有的问题,都是值得解决的技术难题。**
在工作中遇到问题,这简直是一件正常得不能再正常的事儿了,即便我们讲了各种各样的工作原则,也不可避免会在工作中遇到问题。
既然是你遇到的问题,你肯定是第一个知道问题发生的人,如果你不把问题暴露出来,别人想帮你也是有心无力的。
如果老赵不过问,结果会怎么样?必然是小李一条路跑到黑。然后,时间到了,任务没完成。
更关键的是,通常项目计划是一环套一环的,小李这边的失败,项目的后续部分都会受到影响,项目整体延期几乎是必然的。这种让人措手不及的情况,是很多项目负责人最害怕见到的。
所以,虽然单从小李的角度看,这只是个人工作习惯的事,但实际上,处于关键节点的人可能会带来项目的风险。而小李的问题被提前发现,调整的空间则会大很多。
**遇到问题,最好的解决方案是尽早把问题暴露出来。**其实,这个道理你并不陌生,因为你在写程序的时候,可能已经用到了。
## 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很多程序员因为没有坚持这个原则不断妥协造成了程序越来越复杂团队就陷入了无尽的泥潭。
原则很简单,真正的挑战在于克服自己的心理障碍。很多人都会下意识地隐瞒问题,但请相信你的队友,大家都是聪明人,问题是藏不住的。
如果今天的内容你只记住一件事,那请记住:**事情往前做,有问题尽早暴露。**
最后,我想请你回想一下,如果遵循了这样的工作原则,你之前犯过的哪些错误是可以规避掉的呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,128 @@
# 28 | 结构化:写文档也是一种学习方式
你好,我是郑晔。
你写文档吗?我知道,你可能并不喜欢写文档,因为在你眼中,写文档是繁琐的,是旧时代软件工程的产物。
最开始我对写文档的印象也不好。
我的职业生涯是从一个通过了 CMM 5级认证的大企业开始的。可能今天很多程序员已经对 CMM 感到陌生了它是能力成熟度模型Capability Maturity Model for Software的缩写用来评估一个组织的软件开发能力曾在国内风靡一时许多软件公司都以拥有 CMM 认证为努力方向。
在这个极其重视过程的企业里,文档是非常重要的一环。但我看到的真实场景却是,一个软件已经上线运行了,大家才开始为了应付过程纷纷补写文档。
每个部门都有专门的过程负责人,要求你严格按照格式写文档,保证字体字号的正确性。然后,用 A4纸将文档打印出封印在一个仓库里再也无人问津。
然而,文档却是非常重要的。后来,我到过很多公司,凡是我能够比较快上手的,通常都是有比较详尽的文档,而那些文档缺失的公司,想要把信息梳理清楚,往往会花很长时间。
另外,我学习很多软件开发的相关知识,通常也是依赖各种各样的文档。对我们程序员这个走在时代前列的群体来说,大量阅读文档就是我们日常工作的一部分。
你发现矛盾了吗?一方面,我们讨厌写文档,另一方面,文档却对我们的工作学习有着不可忽视的作用。
我们竟然如此依赖于一个我们讨厌的东西。问题出在哪呢?
## 你为什么不喜欢写文档?
很多人会说,自己不愿意写那些无聊的流程文档,文档无聊,这固然是一个原因。不过,如今很多公司已经在这方面做得相当轻量级了,基本上只要求写必要的文档。那为什么依然有很多人不愿意写文档呢?
其实,**很多人回避写文档的真正原因是,他掌握的内容不能很好地结构化。**
在两种场景下,我们扮演的角色是不同的。写文档时,角色是作者;而读文档时,角色是读者。
作为读者,我们读文档,实际上就是按照作者梳理的结构在走,因为呈现出来的内容,多数是已经结构化的,读起来自然会比较顺畅;而作为作者,没有人告诉你结构应该是什么样,我们必须创造出一个结构来,而这正是很多人不擅长的。
想要成为一个好程序员,有一个良好的知识结构是极其重要的。
很多人抱怨程序员行业难,原因就在于,新技术层出不穷。是的,当你的知识都是零散的,任何新技术的出现,都是新东西。而当你建立起自己的知识结构,任何新东西都只是在原有知识上的增量叠加。
举个例子,今天炒得沸沸扬扬的微服务,小粒度的理念脱胎于 Unix 哲学中的“只做一件事把它做好”而服务化的理念则是当年SOAService-Oriented Architecture的产物。理解了这些背后的动机微服务就只剩下工具层面的问题。
有了这样的知识结构,当我要构建应用时,只是需要把工具适配进去,到时我再来学习相应的知识,这是非常有针对性的,学习的效率也会得到大幅度提高。
**将零散的知识结构化,有很多种方式,但输出是非常关键的一环。**
## 知识输出
不知道你小时候是不是有过给同学讲题的经历,有时候,明明你已经将知识学得很好,但给同学讲解起来时,却总是讲不明白。因为你的同学总能从你想都没想过的角度问问题,这些角度和老师教的不一样。
**输出的过程,本质上就是把知识连接起来的过程。**自己以为自己懂的东西,当你真的需要把它按照一个完整的逻辑呈现出来时,那些缺失的细节就会冒出来,而补齐这些细节,一张知识地图就逐渐成型了。
这个模块的主题是“沟通反馈”,将知识对外输出就是一种获得反馈的方式。很多人自以为对知识的理解已经很深入了,但给别人一讲,却发现自己怎么也讲不清楚,这就说明他理解的程度,远未到达他以为的高度。
输出的方式有很多,对于程序员来说,最常接触到的两种应该是写作与演讲。
你读到很多书、很多技术文章,这都是别人通过写作的方式进行输出的结果。而很多技术大会上,常常会有各路高手在台上分享自己的所得,这就是演讲的输出方式。
软件行业的很多大师级程序员都是对外输出的高手。比如,开源概念的提出者 Eric Raymond他的《大教堂与集市》推开了开源大门前面多次提及的Kent Beck他写了《极限编程解析》、《测试驱动开发》、《实现模式》几本书
而 Martin Fowler几乎是对外输出的典范他重新整理了很多似是而非的概念让人们的讨论有了更标准的词汇比如重构、依赖注入Dependency Injection等等。
再往前,就要提到《计算机程序设计艺术》的作者高德纳,他系统地整理了算法的概念,为了好好写作,他甚至创造了一个排版软件 TeX。
也许你会说,说得很有道理,但我真的不擅长啊!这是因为你没有掌握基本的方法。
## 金字塔原理
首先,需要明确一点,我们的第一目标不是成为作家或演讲家,而只是要求把事情说清楚,把自己的知识清晰地呈现出来。那我们最好先来了解一下金字塔原理。看看下面这张图,你就知道它是怎么回事了:
![](https://static001.geekbang.org/resource/image/d9/eb/d9552d4414fd2884378752a955a490eb.jpg)
首先,我们要确定想要表达的是什么,也就是找到中心论点,然后,再确定支撑这个论点的分论点,再来就是找到支撑每个分论点的论据。
从中心论点、分论点至论据,这样一层层向下展开,从结构上看,就像金字塔一样,所以,这个方法称之为**金字塔原理。**
以我们的专栏为例,我们的中心论点就是“高效工作是有方法可循的”,那支撑起这个中心论点的分论点就是我们的四个原则,针对每个原则,我们给出了各种实践和思想,这是我们的论据。
前面我说过了,一个人不擅长输出,更多的是因为缺乏知识的结构化,现在通过这样一种方式,就可以帮助自己,将某个知识结构化起来,有了结构,剩下的就是怎么输出了。
具体怎么输出就可以根据自己的喜好进行选择要么自上而下的进行表达也就是先说中心论点然后说分论点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/),我才知道,即便强如乔布斯,他的演讲也是经过大量练习的。
我自己公开演讲看上去正常一些,是我在经过一个咨询项目的大量练习之后。那时候,几乎每天要给客户讲东西,害得我只能不停地准备、不停地讲。所以,本质上,对演讲的惧怕只是因为练习不足。
好了,你现在已经了解获取这些技能的真谛了,**无他,唯手熟尔!**
## 总结时刻
程序员对文档有着一种矛盾的情感,一方面,需要依赖于文档获得知识,另一方面,很少有人愿意写文档。
文档在程序员心目中“形象不佳”,主要是传统的流程写了太多无用的文档。但对更多人来说,不愿意写文档,本质上是因为知识不能很好地结构化。
有结构的知识会让新知识的学习变得更加容易,今天很多人抱怨新知识层出不穷,就是因为知识过于零散,当知识有结构之后,学习新知识就只是在学习增量,效率自然就会大幅度提升。
输出是一种很好的方式,帮助你把知识连接起来,写作和做公开演讲都是很好的输出方式。
阻碍很多人进行知识输出的一个重要原因是缺乏输出的模型,金字塔原理就给出一个从中心论点到分论点,再到论据的模型,帮助我们将知识梳理出来。
而想要做好知识输出,还需要不断地进行练习,写作和做公开演讲都是可以通过练习提高的。
如果今天的内容你只能记住一件事,那请记住:**多输出,让知识更有结构。**
最后,我想请你分享一下,你的工作中,有哪些机会将自己的知识输出呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,113 @@
# 答疑解惑 | 持续集成,一条贯穿诸多实践的主线
“沟通反馈”模块又告一段落了,在这个模块中,我们把自己与真实世界的距离又拉近了一步。
一方面,我们强调主动沟通,把自身的信息更有效地传达出去;另一方面,我们也重视反馈,让真实世界的信息,更多地回到我们身边。同学们分享了很多经验,也提出了不少的问题。
在今天的答疑中,我选择了几个非常好的问题,从不同的角度丰富一下之前讲解的内容。
## 问题1单元测试做不好是否会影响到 CI 的效果?
毅 同学提到
> 如果单元测试做的不到位或者不满足A-TRIP是不是执行CI的效果就会弱很多
> ——[《24 | 快速反馈:为什么你们公司总是做不好持续集成?》](http://time.geekbang.org/column/article/83461)
这是一个非常好的问题,问到了各种实践之间的关联。我们在前面用了两讲的篇幅介绍了持续集成这个实践,为什么要做持续集成以及如何做好持续集成。
在自动化模块,我们还会在这个基础之上继续延伸,介绍持续交付,这些内容是从操作的层面上进行介绍,都是对单一实践的描述。
利用这次答疑的机会,我再补充一个维度,谈谈实践之间的关联。
**持续集成的价值在于,它是一条主线,可以将诸多实践贯穿起来。**也就是说,想要真正意义上做好持续集成,需要把周边的很多实践都要做好。
我们具体地说一下这些实践。但请记住我们说过的,做好持续集成的关键是,快速反馈。
比如,我们想要做好 CI需要有一个稳定的开发分支所以最好采用主开发分支的方式。想用好主分支开发最好能够频繁提交而频繁提交需要你的任务足够小能够快速完成将任务拆解的足够小需要你真正懂得任务分解。要想在一个分支上开发多个功能那就需要用 Feature Toggle 或者 Branch by Abstraction。
![](https://static001.geekbang.org/resource/image/02/db/02787851076da320932782a672dfafdb.jpg)
在这条线上你有很多机会走错路。比如你选择了分支开发模式合并速度就不会太快一旦反馈快不了CI 的作用就会降低;再者,如果不能频繁提交,每次合并代码的周期就会变长,一旦合并代码的周期变长,人们就会倾向于少做麻烦事,也就会进一步降低提交的频率,恶性循环就此开启。
同样,即便你懂得了前面的道理,不懂任务分解,想频繁提交,也是心有余而力不足的。而多功能并行开发,则会让你情不自禁地想考虑使用多分支模型。
我们再来看另外一条线,也就是这个问题中提到的测试。
想做好 CI首先要有可检查的东西什么是可检查的东西最简单的就是编译、代码风格检查这些检查可以无条件加入构建脚本。但更重要的检查应该来自于测试而要想做好 CI我们要有测试防护网。
![](https://static001.geekbang.org/resource/image/51/4c/51f200d383681e36ddf1bb127c03894c.jpg)
什么叫测试防护网呢?就是你的测试要能给你提供一个足够安全的保障,这也就意味着你要有足够多的测试。换个更技术点的术语来说,就是要有足够高的测试覆盖率。
如果测试覆盖率不够即便提交了代码CI 都通过了,你对自己的代码依然是没有信心的,这就会降低 CI 在你的心中的地位。
如果想有足够高的测试覆盖率,你就要多写单元测试。我们在前面讲过测试金字塔了,上层测试因为很麻烦,你不会写太多,而且很多边界条件,通过上层测试是覆盖不到的,所以,测试覆盖率在经过了初期的快速提升后,到后期无论如何是提上不去的。要想提升测试覆盖率,唯有多写单元测试。
要想多写单元测试,就需要编写可以测试的代码,而要想编写可测的代码,就要懂软件设计,将系统之间耦合解开。
通过上面的分析,你已经看出来做好持续集成,让它完全发挥自己的价值,需要做的工作还是相当多的。但也请别灰心,实际上,我做咨询时,很多团队就是从持续集成下手,开始改造他们的软件开发过程。
这是一个“以终为始”的思路,先锁定好目标,就是要把持续集成做好,然后围绕着这个目标改进其他做得欠佳的方面。比如,原来是多分支的,就先固定一个主分支,然后,逐步改变大家的开发习惯,让他们进入单分支的开发状态。
再比如,原来没有测试,那就在 CI 上先加一个最低的测试覆盖率然后定期去提高比如第一周是10%第二周是20%,这样一步一步地提高,开发团队可以一边开发新东西,一边为既有代码补测试。等到覆盖率到了一定程度,提高有困难了,团队就可以考虑怎么改进设计了。
**所以CI 作为一个单独的实践,本身是很简单的,但它可以成为提纲挈领的主线,帮助团队不断改善自己的开发过程。**
## 问题2老板参加复盘不敢说真话怎么办
grass10happy 同学提到
> 复盘是不是最好是团队内部进行,每次老板参加复盘,好像就没人说出真话了。
> ——[《25 | 开发中的问题一再出现,应该怎么办?》](http://time.geekbang.org/column/article/83841)
感谢 grass10happy 同学这个提问,把我因为篇幅原因省掉的一个部分给挽救了回来。
回顾会议的目的在于改进,它不仅仅在于让大家参与进来,更重要的是让团队成员能够敞开心扉,把问题暴露出来。**暴露问题,是改进的前提条件。**
我在[《27 | 尽早暴露问题: 为什么被指责的总是你?》](http://https://time.geekbang.org/column/article/84374)这篇文章中说过了,对于很多人来说,敢不敢暴露问题是个心理问题。你会发现,同事之间聊天,普遍是没有任何压力的,你几乎可以放心大胆地谈论各种问题,而一旦有领导在,很多顾虑就会出现了。
于是,问题就变成了怎么能够让大家放心地把问题暴露出来,一个办法就是设置一个安全的环境。
怎么设置一个安全的环境呢?对于标准的回顾会议来说,第一步应该是做安全性检查。
先由大家投票最简单的方式是就是给当前的环境打分。你觉得可以畅所欲言就打1分你觉得还好就打0分如果你觉得不方便表达比如你看领导在很多问题不适合反馈就打-1。
每个与会者都投出属于自己的一票。然后,主持人根据投票结果决定回顾会议是否进行,比如,有人投-1就不能继续。
会议能继续固然好,一旦会议不能继续,可以有多种解决方案。比如,把在场职位最高的人请出去,这个人可能就是老板。老板也许心里很不爽,但在这个过程中,大家都是按照规则在办事,并不存在对谁另眼相待的情况。
当老板离席之后,我们再进行一轮投票,判断环境是否变得安全了。如此反复,也许要进行几轮投票,直到大家觉得安全了。
当然,也有可能进行多轮,有人始终觉得不安全,那可能最好的选择是,取消今天的回顾会议,换个时间地点从头再来。而项目负责人则需要私下里解决一下团队内心安全的问题。
通过安全性检查之后,我们才会进入回顾会议的正式环节,具体内容在正文中已经讲过了,这里就不再赘述了。
## 问题3国内的技术信息落后吗
One day 提到
> 老师能否多多介绍一下技术方面的网站之类的,新技术发展见闻之类的,或者技术总结方面。国内的技术基本都多少有些滞后。
> ——[《23 | 可视化:一种更为直观的沟通方式》](http://time.geekbang.org/column/article/83082)
这个问题让我感觉自己一下子回到了好多年前。我刚入行的那会,学习新知识确实要多看看英文网站,当时的信息传播速度不快,中文技术网站不多。
但在今天,显然已经不是这样了,如果只是想获得最新的技术信息,我在[《23 | 可视化:一种更为直观的沟通方式》](http://time.geekbang.org/column/article/83082)这篇文章中介绍了 InfoQ 和技术雷达,这上面的信息量已经很丰富了。你再只要稍微看几个网站,关注几个公众号,各种信息就会送到你面前。
所以,你根本不用担心会错过什么新技术,反倒是信息量太大,需要好好过滤一下。
**国内程序员真正落后的不是信息,而是观念。**
我讲的很多内容是软件工程方面的,以我对国内外程序员的了解来看,发达国家的程序员在这些内容的普及上,要比国内程序员好很多。
国内程序员的平均水平,大多停留在实现一个功能的理解上,而发达国家的程序员做事要专业许多。所以,以专业素养来看,国内程序员还有很大的提升空间。
在经济学里有“边际效用递减法则”The Law Of Diminishing Marginal Utility说的是当你手里某一物品总数越来越多时新增一个单位该物品所获得的效用通常会越来越少。
当你的技术知识积累到一定程度时,还采用原来的学习方式,就很难获得真正意义上的提高,这是很多人抱怨 IT 行业不好混的原因。
同时,这也是我开设这个专栏的初衷,希望给大家一些不同的视角,一些新的前进动力。
好,今天的答疑就到这里。我想请你分享一下,你是怎么理解这些问题的呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,153 @@
# 划重点 | 一次关于“沟通反馈”主题内容的复盘
你好,我是郑晔,恭喜你,又完成了一个模块的学习。
在“沟通反馈”这个模块中,我与你探讨了与人打交道的一些方法,只不过,这并非是传统意义上的谈话技巧。而是希望你能克服自己的心理障碍,主动与真实世界进行沟通,获取反馈,让自己对信息的编解码能力不断得到提升。
## 重点复习
在这个模块中,我们学习到了一些最佳实践。
* **看板**
* 一种来自精益生产的可视化实践。
* 按阶段将任务放置其中。
* 可以帮助我们发现问题。
* **持续集成**
* 做好持续集成的关键是,快速反馈。
* 本地检查通过之后再提交。
* 找到有效的反馈方式比如CI 监视器。
* 持续集成的纪律。
* 只有 CI 服务器处于绿色的状态才能提交代码。
* CI 服务器一旦检查出错,要立即修复。
* **回顾会议**
* 软件团队复盘的一种实践。
* 枚举关注点,选出重点,深入讨论,列出行动项,找到负责人。
* **5个为什么**
* 又一个来自丰田的实践。
* 沿着一条主线追问多个问题。
在这个模块中,我们还了解一些重要的思路,让我们把工作做得更好。
* **用信息论理解沟通反馈**
* **写代码的进阶路径**
* 编写可以运行的代码。
* 编写符合代码规范的代码。
* 编写人可以理解的代码。
* 用业务语言写代码。
* **会议是一种重量级的沟通方式**
* 减少参会人数。
* 找人面对面沟通。
* **聆听用户声音**
* 能做自己用户,做自己的用户。
* 能接近用户,接近用户。
* 没有用户,创造用户。
* **Fail Fast**
* 一种编写代码的原则。
* 出现问题尽早报错。
* **金字塔原理**
* 从中心论点,到分论点,再到论据。
## 实战指南
在“沟通反馈”的模块,我也将每篇内容浓缩为一句实战指南,现在一起回顾一下。
* **通过沟通反馈,不断升级自己的编解码能力。**
——《[20 | 为什么世界和你的理解不一样](http://time.geekbang.org/column/article/80755)》
* **用业务的语言写代码。**
——《[21 | 你的代码为谁而写?](http://time.geekbang.org/column/article/82581)》
* **多面对面沟通,少开会。**
——《[22 | 轻量级沟通:你总是在开会吗?](http://time.geekbang.org/column/article/82844)》
* **多尝试用可视化的方式进行沟通。**
——《[23 | 可视化:一种更为直观的沟通方式](http://time.geekbang.org/column/article/83082)》
* **做好持续集成的关键在于,快速反馈。**
——《[24 | 快速反馈:为什么你们公司总是做不好持续集成?](http://time.geekbang.org/column/article/83461)》
* **定期复盘,找准问题根因,不断改善。**
——《[25 | 开发中的问题一再出现,应该怎么办?](http://time.geekbang.org/column/article/83841)》
* **多走近用户。**
——《[26 | 作为程序员,你也应该聆听用户声音](http://time.geekbang.org/column/article/84185)》
* **事情往前做,有问题尽早暴露。**
——《[27 | 尽早暴露问题: 为什么被指责的总是你?](http://time.geekbang.org/column/article/84374)》
* **多输出,让知识更有结构。**
——《[28 | 结构化:写文档也是一种学习方式](http://time.geekbang.org/column/article/84663)》
## 额外收获
在这个模块的最后,针对大家在学习过程中的一些问题,我也进行了回答,帮你梳理出一个思路,更好地理解学到的内容:
* **持续集成是一条主线,可以将诸多实践贯穿起来。**
* 从持续集成到稳定的开发分支,到频繁提交,足够小的任务,到任务分解。
* 从持续集成到可检查,到测试防护网,到测试覆盖率,到单元测试,到可测试代码,到软件设计。
* **安全性检查,是回顾会议的前提条件。**
* **在信息获取上,国内外程序员差别不大,开拓视野,改善工作习惯,是国内程序员亟需提高的。**
——《[答疑解惑 | 持续集成,一条贯穿诸多实践的主线](http://time.geekbang.org/column/article/85049)》
## 留言精选
在讲到定期复盘,找准问题根因时,西西弗与卡夫卡 同学提到:
> 关于复盘,孙陶然曾经说过,如果他有所成就,一半要归功于复盘。他提出了几个步骤供大家参考。首先,先对比实际结果和起初所定目标之间有什么差距。其次,情景再现,回顾项目的几个阶段。然后,对每个阶段进行得失分析,找出问题原因。最后,总结规律,化作自己的技能沉淀,再次遇到时可以规避。
> 我再补充一点,复盘资料应该记录到知识库,无论新来的或是接手的人,都能从中获益,从而提升组织的能力。另外,好的复盘需要有坦诚的文化氛围,不然有可能变成互相指责甩锅,就失去了意义。
另外,西西弗与卡夫卡 同学还分享了提升开会效率的方法:
> 其他一些提升开会效率的方法,比如会前每个人要先做准备,把观点写下来,然后发给主持人。再比如六顶思考帽,大家按相近的思考角度讨论,而不是我说一趴,你说另一趴。还有,主持人控制这轮谁能发言,控制每个人的时长。方法很多,但实际上总有人破坏规则,特别是当这个人是老板…
在用信息论来讨论沟通反馈问题时,毅 同学将知识点融会贯通,提出了自己的心得:
> 不同角色间的沟通:克服上下文差异,分段解码,理解偏差早发现早反馈。相同角色间的沟通,信号相同,解码能力因人而异,要有一个主导的人,控制沟通广度与深度,抓主线适可而止,此时结合任务分解,反向沙盘推演。
关于如何做好复盘like\_jun 同学提到:
> 要让团队认识到复盘的重要性。
> 让每个人都深入思考项目运作过程中遇到了哪些问题。才能做好复盘。
在讲到通过金字塔原理进行知识输出时Y024 同学丰富了金字塔原理的基本原则,具体如下:
> 金字塔原理的四个基本原则:“结论先行”(一次表达只支持一个思想,且出现在开头)、“以上统下”(任一层次上的思想都必须是其下一层思想的总结概括)、“归类分组”(每组中的思想都必须属于同一范畴)和“逻辑递进”(每组中的思想都必须按照逻辑顺序排列)。
> 前面两个特点是纵向结构之间的特点,后面两个特点则是横向结构之间的特点。以上内容收集整理自李忠秋老师的《结构思考力》,感兴趣的小伙伴可以看看。
另外对于会议Y024 同学也提出了他团队正在进行的摸索和尝试:
> 1.沟通的指导原则之一就是在同步沟通的时候比如开会人越少越好。而在异步沟通的时候比如E-mail涉及的听众越多越好。
> 2.关于开会分享下我们正在摸索的。
> a每个会开始前会议发起人在石墨文档上以“会议记录”模版我们持续形成自己的模版新建一个纪要说明议程、及讨论内容等前提内容并提前告知与会人员。会议过程中在同一个石墨文档上做纪要保证纪要可以收集全所有的笔记和行动计划。如果是关联会议则使用上次相关的石墨文档进行追加内容保持事件连贯性、完整性
> b半小时的会议设置为 25 分钟,一小时的会议设置成 50 分钟,留有冗余量应付需要换地方等临时情况,保证所有的会议不会有成员迟到的现象。
对于领域驱动设计,小浩子 同学提到了要特别关注可变项和不变项的分离:
> 领域驱动设计确实是写出合适的代码结构的一项训练,程序员会不由自主地按照自己的习惯,也就是按照计算机运行逻辑去设计代码,这样的代码很容易陷入难以维护的坑。在开始动手写代码之前跟用户交流清楚,理解设计的概念、流程、使用场景、特殊情况,这些都很重要。另外我特别关注的一点是可变项和不变项的分离,因为我们的业务场景对可扩展性要求很高。
经验越丰富的程序员越能体会到“走进客户”的重要性关于这一点David Mao 同学提到:
> 我做了好多年的软件测试,前几年和销售一起去谈客户,才深深地体会到客户声音的重要性。客户关注的才是真需求,产品经理和开发想出来的很多是伪需求,很多不是客户想要的功能。
**感谢同学们的精彩留言。在下一个模块中,我将为你分享“自动化”这个原则的具体应用。**
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,106 @@
# 加餐 | 你真的了解重构吗?
今天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 的书吧!
## 总结时刻
总结一下今天的内容。今天我介绍了一个大家耳熟能详的概念:重构。不过,这实在是一个让人误解太多的概念,大家经常认为调整代码就是在做重构。
重构,本质上就是一堆微操作。重构这个实践的核心,就是将调整代码的动作分解成一个一个的小动作,如果不能理解这一点,你就很难理解重构本身的价值。
不过,对于我们专栏的读者而言,因为大家已经学过了“任务分解”模块,理解起这个概念,难度应该降低了很多。
既然重构的核心也是分解,它就需要大量的锤炼。就像之前提到任务分解原则一样,我在重构上也下了很大的功夫做了专门的练习,才能让自己一小步一小步地去做。但一个有追求的软件工匠不就应该这样锤炼自己的基本功吗?
如果今天的内容你只记住一件事,那请记住:**锤炼你的重构技能。**
最后,我想请你分享一下,你对重构的理解。欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,110 @@
# 29 | “懒惰”应该是所有程序员的骄傲
你好,我是郑晔。
经过前面几个模块的学习,我们的专栏终于进入到程序员看上去最熟悉的一个主题:自动化。
每每提及自动化,我就会想起 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。在那个年代我们的程序是按照官方推荐做法编写的 EJBEnterprise JavaBean今天很多年轻的程序员可能不了解了它只有部署到应用服务器才能运行。
我的解决方案就是加上一些打印语句,然后部署到应用服务器上,看输出的结果,再加上另外一些语句,再部署,如此往复。那时我们完全是手工打包上传,每次至少要十几分钟。最终,定位到了问题,只修改了一行代码。但几个小时的时间就这样被无谓的消耗了。
那之后,我花了很长时间研究怎么做自动化的增量部署,最终让这个过程简化了下来。但这件事对我的影响很大,这是我第一次认识到一个部署过程可能对开发造成的影响,也让我对自动化在开发过程内的应用有了属于自己的认识。
相比于我刚开始工作那会。现在在工具层面做类似的事已经容易很多了,在后面的内容中,我会结合着具体的场景介绍一下现在的最佳实践。
## 你要懂得软件设计
最后,我们再来说说我们的本职工作,给别人打造自动化工具中需要的能力:软件设计。
软件设计,是很多人既熟悉又陌生的一个词,说熟悉,很多人都知道,做软件要设计,还能顺嘴说出几个设计模式的名字;说陌生,是因为在我的职业生涯中,遇到真正懂软件设计的程序员少之又少。**大多数人都是混淆了设计和实现。**
举个例子。有一次,我要在两个系统之间做一个连接器,让上游系统向下游系统发消息,或许你一听就知道了,这里需要的是一个消息队列。但实际上,我们需要的能力要比消息队列更丰富一些,比如,要将重复的消息去除。一个同事给我推荐了 Kafka 当作这个连接器的基础,我欣然地接受了。
不过,在后续设计的讨论中,我们就经常出现话语体系的分歧。我说,这个连接器要有怎样的能力,他会说 Kafka 能够如何如何。究其根因,我在讨论的是设计,而他说的是实现,所以,我们两个很难把问题讨论到一起。
为什么我会如此看重设计呢?**在软件开发中,其它的东西都是易变的,唯有设计的可变性是你可以控制的。**
同样以前面的讨论为例,尽管 Kafka 在当下比较火热,但是我不敢保证 Kafka 在未来不会被我换掉。因为就在几年前,消息队列还是传统中间件的强项,现在也渐渐被人淡忘了。
我不想让我的设计随着某一个技术选型而不断摇摆。如果工作许多年,知识体系只能靠各种新框架新工具支撑,我们做程序员就只剩下疲于奔命了。不懂软件设计,只专注各种工具,其结果一定是被新技术遗弃,这也是很多人经常抱怨 IT 行业变化快的重要原因。
回到 Larry Wall 的说法上,你要想写出一个别人挑不出毛病的程序,你先要懂得软件设计。幸运的是,软件设计这些年的变化真不大,掌握了软件设计再来看很多框架和工具,学习起来就会容易很多。在这个模块的后半部分,我会与你探讨软件设计的话题,降低自己给自己挖坑的概率。
## 总结时刻
Perl 语言的发明人 Larry Wall 曾经说过优秀程序员应该有三大美德懒惰、急躁和傲慢Laziness, Impatience and hubris。想要成为一个优秀的程序员就要让机器为自己很好地工作而这需要对自动化有着很好地理解。
我们学习自动化,先要知道哪些东西不要自动化,尽最大的努力不做浪费时间的事。一方面,我们要从需求上规避那些没必要做的事;另一方面,我们也从自身防止 NIH 综合症Not Invented Here Syndrome争取做一个懒惰的程序员。
对于要自动化的事,我们需要反思一下,在为别人打造自动化工具的同时,我们自己的工作过程有没有很好地自动化。而如果我们想拥有打造良好的自动化工具,我们需要对软件设计有着充分地理解。
如果今天的内容你只能记住一件事,那请记住:**请谨慎地将工作自动化。**
最后,我想请你分享一下,学习了本讲之后,你现在是怎样理解自动化的呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,283 @@
# 30 | 一个好的项目自动化应该是什么样子的?
你好,我是郑晔。
进入自动化这个模块,我准备从程序员的日常工作开始。介绍“[迭代0](http://time.geekbang.org/column/article/77294)”时,我提到构建脚本是项目准备的一个重要组成部分,但在那一讲中,我并没有具体说构建脚本长成什么样。
今天,我们以一个典型的 Java REST 服务为例,介绍一下最基本的构建脚本应该做到什么样子。这里我采用的 Java 技术中最为常见的 Spring Boot 作为基础框架,而构建工具,我选择了 [Gradle](http://gradle.org)。
估计很多 Java 程序员心中的第一个问题就是,为什么用 Gradle而不是 MavenMaven 明明是 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 的配置文件,它也是我们做项目自动化的重点。
* build.gradle它是 Gradle 的配置文件。因为 Gradle 是由 Groovy 编写而成build.gradle 本质上就是一个 Groovy 的脚本,其中的配置就是 Groovy 代码,这也是 Gradle 能够灵活订制的基础。
* settings.gradle这也是一个 Gradle 配置文件,用以支持多模块。如果说一个项目的每个模块都可以有一个 build.gradle那整个项目只有一个 settings.gradle。
在 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”时我还提到了数据库迁移也就是怎样修改数据库。在示例项目中我选择的数据库迁移工具是
[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)
我们就可以看见我们刚刚注册的这个用户了。
## 总结时刻
总结一下今天的内容。今天我们通过一个具体的例子,展示了一个最基本的项目自动化过程,包括了:
* 生成 IDE 工程;
* 编译;
* 打包;
* 运行测试;
* 代码风格检查;
* 测试覆盖率;
* 数据库迁移;
* 运行应用。
但这就是自动化的全部了吗?显然不是,我这里给出的只是一个最基本的示例。实际上,几乎每个重复的工作或是繁琐的工作,都应该自动化。我们不应该把时间和精力浪费在那些机器可以很好地替我们完成的工作上。
今天的基础设施已经让我们的自动化工作变得比以往容易了很多,比如,可执行 JAR 包就比从前部署到应用服务器上简化太多了。Gradle 也让订制构建脚本的难度降低了很多。
这里提到的项目自动化也是持续集成的基础,在持续集成服务上执行的命令,就应该是我们在构建脚本中写好的,比如:
```
./gradlew build
```
2011年我在 InfoQ 上发表了一篇《[软件开发地基](http://www.infoq.cn/article/zy-software-development-foundation)》,讨论的就是一个项目的构建脚本应该是什么样子。虽然其中用到的工具今天已经不再流行,但一些基础内容今天看来,依然是有效的。如果有兴趣,你也可以看一下。
如果今天的内容你只能记住一件事,那请记住:**将你的工作过程自动化。**
最后,我想请你分享一下,在日常开发工作中,你还把哪些过程自动化了呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,102 @@
# 31 | 程序员怎么学习运维知识?
你好,我是郑晔。
在上一讲中,我们讲到了开发过程的自动化,我们的关注点在于如何构建出一个有效的部署包,这个包最终是要上线部署的,那接下来,我们就来关心一下部署的相关工作。
## 零散的运维知识
在一些稍具规模的公司,为部署工作设置了一个专有职位,称之为运维。当然,这个岗位的职责远不止部署这一件事,还要维护线上系统的稳定。不过,如果你的团队规模不大,或是项目处于初始阶段,这些工作往往也要由程序员自行完成。
对于一个程序员来说,了解自己的程序怎么部署上线,是非常重要的。我们既要了解一个软件的逻辑,也要知道它的物理部署。只有这样,出了问题才知道怎么修复。
更重要的是,我们在设计时,才能尽量规避部署带来的问题。而部署,恰恰也是最适合发挥自动化本领的地方。
好,即便下定决心准备学习运维相关知识,你准备怎么学呢?我先来问你个问题,提到运维,你会想到什么?
如果你是一个刚刚步入这个行业的程序员,你或许会想到 [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 的知识体系,包括了编程语言、核心库、第三方库、开发框架、单机部署和集群部署等诸多方面。我把今天提到的各种技术整理成一个表格列在下面,你可以参考它更好地理解运维知识。
![](https://static001.geekbang.org/resource/image/fe/3c/fec8c728c492fyyce018ed1816fe583c.jpg)
如果今天的内容你只能记住一件事,那请记住:**有体系地学习运维知识。**
最后,我想请你分享一下,你还能想到哪些运维知识可以放到这张知识地图上呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,109 @@
# 32 | 持续交付:有持续集成就够了吗?
你好,我是郑晔。
在前面两讲,我给你讲了开发过程的自动化,将我们的程序打成发布包;然后讲了部署过程的自动化,通过各种工具将发布包部署起来。
有了这些基础,我们就可以考虑在每次开发完之后,将程序打包部署到环境中。开发完就自动打包,然后自动部署,听起来很像持续集成是不是?
关于持续集成,我在专栏里已经讲过两次,分别讨论了“[为什么要做持续集成](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 他们对于这个产品的构想远远超过了我当时的想象,他们将生产环境也纳入了考量。
什么是持续交付?简言之,它就是一种让软件随时处于可以部署到生产环境的能力。从一个打好的发布包到部署到生产环境可用,这中间还差了什么呢?那就是验证发布包,部署到环境中。
验证发布包,你或许会想,这不是测试的事吗?这不是已经在持续集成阶段完成的吗?不尽然。在持续集成阶段验证的包,往往缺少了环境的支持。
因为持续集成的环境往往是单机的,主要强调功能验证,而一些与生产环境相关的测试往往是欠缺的。所以,这里就引出了持续交付中一个需要关注的点:环境。
一般来说,在构建持续交付的基础设施时,会有下面几个不同的环境。
* 持续集成环境,持续集成是持续交付的前提,这个过程主要是执行基本的检查,打出一个可以发布的包。
* 测试环境Test这个环境往往是单机的主要负责功能验证这里运行的测试基本上都是验收测试级别的而一般把单元测试和集成测试等执行比较快的测试放到持续集成环境中执行。
* 预生产环境Staging这个环境通常与生产环境配置是相同的比如负载均衡集群之类的都要有只是机器数量上会少一些主要负责验证部署环境比如可以用来发现由多机并发带来的一些问题。
* 生产环境Production这就是真实的线上环境了。
![](https://static001.geekbang.org/resource/image/72/e8/721909eac3d1f75308cee268992275e8.jpg)
![](https://static001.geekbang.org/resource/image/ac/3e/ac69b56b11f3c19cd88bd3cf1559af3e.jpg)
你也看出来了每个环境的作用是有差异的所以通常不会将所有的验证放在一起执行而是要分阶段的去执行一个阶段不通过是不能进入下一阶段的这种按照不同阶段组织构建的方式称之为构建流水线Build Pipeline
一旦通过了各种验证,就会到构建流水线的最后一个阶段,生产发布。通常来说,生产发布这个过程不是自动化的。我们说,持续交付的关注点在于,让软件具备随时可以发布的能力,但并不等于它要立刻上线,所以,最后这一下,还要由人来决定,到底是不是要上线。
如果把由人决定的是否上线变成自动化的,就成了另外一个实践:持续部署。但通常人们都会比较谨慎,最后这一下还是由人拍板比较稳妥,所以,持续交付是现在的主流。
![](https://static001.geekbang.org/resource/image/5e/ce/5e7261b528b4eee8f290c0611ee054ce.jpg)
至此,我们讨论了持续交付的第一个方面,验证发布包。接下来,我们再来看看另外一个重要部分:部署。
## 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 镜像。
![](https://static001.geekbang.org/resource/image/e1/54/e1f55e949e02faef89dbaf6cfae95254.jpg "交付物的变迁")
Docker 在开发中扮演的角色,是一个构建在我们应用与具体机器之间的中间层。对应用而言,它就是机器,但对机器而言,它只是一个可以部署的镜像,统一了各种应用千奇百怪的部署差异,让部署本身变得更简单了。
到这里,我给你介绍了持续交付中最基础的东西,让你有了一个基本的框架理解持续交付。当然,如果你关注这个领域,就会发现,它早已超出了一个实践的层面,有更多组织、文化的内容。
Jez Humble 写《持续交付》时就已经想到如此完整的一个体系,受限于当时的环境,书中介绍的自动化还比较宽泛,不像今天有更加完善的工具支撑。
只可惜,虽然当时他对持续交付的理解已经到达如此高度,他所在的团队也做出了一个颇具先锋气质的持续交付工具,但是受限于产品推广策略,这个工具并没有成为主流,即便后来开源了。(如果你想了解一下[这个工具](http://www.gocd.org),可以点击链接去查看)
## 总结时刻
总结一下今天的内容。我们延续了前两讲的内容,在准备好发布包和部署的基础设施之后,我们顺着持续集成的思路,将部署过程也加了进来,这就是持续交付。
持续交付,是一种让软件随时处于可以部署到生产环境的能力。让软件具备部署到生产环境的能力,这里面有两个关键点:验证发布包和部署。
验证发布包不仅是功能上的验证还包括与环境结合在一起的验证。所以通常会用几个不同的环境验证每一个环境都是一个单独的阶段一个阶段不通过是不能进入下一阶段的这种按照不同阶段组织构建的方式称之为构建流水线Build Pipeline
与部署相关的一个重要概念是 DevOps也就是将开发和运维结合起来。DevOps 包含了很多方面对程序员最直接的影响是各种工具的发展这些工具推动着另一个理念的发展基础设施即代码Infrastructure as code 。有赖于这些工具的发展,今天定义交付,就不再是一个发布包,而是一个可以部署的镜像。
如果今天的内容你只能记住一件事,那请记住:**将部署纳入开发的考量。**
最后,我想请你分享一下,你对持续交付的理解是什么样的呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,162 @@
# 33 | 如何做好验收测试?
你好,我是郑晔。
经过前面三讲的讲解,相信你对一个项目自动化应该是什么样子有了一个相对完整的认识:程序员写好程序,用构建脚本执行检查,提交代码,在服务器上打出一个发布镜像,部署到各个环境进行检查,检查好了,随时可以发布上线。
我们在前面的内容中只说了该检查,但怎么检查呢?这就轮到测试发挥作用了。
在“任务分解”的模块,我给你完整地介绍了一下开发者测试的概念,但在那个部分讲解的测试基本上还停留在单元测试和集成测试的范畴。对于整个应用该怎么测,我们并没有仔细讨论。
今天我们就来说说应用测试的话题:验收测试。
## 验收测试
验收测试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 DevelopmentBDD为核心给你介绍了一种自动化验收测试的方式。这个在2003年由 Dan North 提出的概念已经成为了一套比较完善的体系,尤其是一些 BDD 框架的发展,让人们可以自己的项目中实践 BDD。
我以 Cucumber 为样例,给你介绍了 BDD 验收用例的编写方式你知道“Given…When…Then”的基本格式也知道了要编写步骤定义Step Definition将测试用例与实现连接起来。
我还给你介绍了编写 BDD 测试用例的最佳实践:用业务的视角描述测试用例。在编写步骤定义时,还要考虑设计自己的业务测试模型。
其实,验收测试的方法不止 BDD 一种,像[实例化需求](http://en.wikipedia.org/wiki/Specification_by_example)Specification by ExampleSbE也是一种常见的方法。验收测试框架也不止 BDD 框架一类,像 Concordion 这样的工具甚至可以让你把一个验收用例写成一个完整的参考文档。
如果你有兴趣,可以深入地去了解。无论哪种做法,都是为了缩短业务人员与开发团队之间的距离,让开发变得更加高效。
如果今天的内容你只能记住一件事,那请记住:**将验收测试自动化。**
最后,我想请你分享一下,你的团队是怎么做验收测试的呢?欢迎在留言区分享你的做法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,126 @@
# 34 | 你的代码是怎么变混乱的?
你好,我是郑晔。
前面几讲,我给你讲了开发过程的各种自动化,从构建、验证到上线部署,这些内容都是站在软件外部看的。从这一讲开始,我准备带领大家进入到软件内部。今天的话题就从写代码开始说起。
## 逐步腐化的代码
代码是程序员改造世界最直接的武器,却也是程序员抱怨最多的东西。为什么程序员会对代码如此不满呢?
你会抱怨写一段代码吗?你肯定不会,毕竟这是你养家糊口的本领,最基本的职业素养我们还是有的。那抱怨的是什么呢?是维护一段代码。
为什么维护代码那么难?因为通常来说,你维护的这段代码是有一定年龄的,所以,你总会抱怨前人没有好好写这段代码。
好,现在你拿到了一个新的需求,要在这段代码上添加一个新功能,你会怎么做呢?很多人的做法是,在原有的代码上添加一段新的逻辑,然后提交完工。
发现问题了吗?**你只是低着头完成了一项任务,而代码却变得更糟糕了。**如果我问你,你为什么这么做?你的答案可能是:“这段代码都这样了,我不敢乱改。”或者是:“之前就是这么写的,我只是遵循别人的风格在写。”
行业里有一个段子,**对程序员最好的惩罚是让他维护自己三个月前写的代码。**你一不小心就成了自己最讨厌的人。
从前,我也认为很多程序员是不负责任,一开始就没有把代码写好,后来,我才知道很多代码其实只是每次加一点。你要知道,一个产品一旦有了生命力,它就会长期存在下去,代码也就随着时间逐渐腐烂了。
而几乎每个程序员的理由都是一样的,他们也很委屈,因为他们只改了一点点。
这样的问题有解吗?一个解决方案自然就是我们前面说过的重构,但重构的前提是,你得知道代码驶向何方。对于这个问题,更好的答案是,你需要了解一些软件设计的知识。
## SOLID 原则
提到软件设计,大部分程序员都知道一个说法“高内聚、低耦合”,但这个说法如同“期待世界和平”一样,虽然没错,但并不能很好地指导我们的具体工作。
人们尝试着用各种方法拆解这个高远的目标,而比较能落地的一种做法就是 Robert Martin 提出的面向对象设计原则:[SOLID](http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod),这其实是五个设计原则的缩写,分别是
* 单一职责原则Single responsibility principleSRP
* 开放封闭原则Openclosed principleOCP
* Liskov 替换原则Liskov substitution principleLSP
* 接口隔离原则Interface segregation principleISP
* 依赖倒置原则Dependency inversion principleDIP
早在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()。
![](https://static001.geekbang.org/resource/image/aa/50/aabd9d105df157db95739fb628c00250.jpg)
接下来,财务部门要修改正常工作时间的统计方法,但人力部门不需要修改。负责修改的程序员只看到了 calculatePay() 调用了 regularHours(),他完成了他的工作,财务部门验收通过。但上线运行之后,人力部门产生了错误的报表。
这是一个真实的案例,最终因为这个错误,给公司造成了数百万的损失。
如果你问程序员,为什么要把 calculatePay() 和 reportHours()放在一个类里,程序员会告诉你,因为它们都用到了 Employee 这个类的数据。
但是,它们是在为不同的 actor 服务,所以,任何一个 actor 有了新的需求,这个类都需要改,它也就很容易就成为修改的重灾区。
更关键的是,很快它就会复杂到没人知道一共有哪些模块与它相关,改起来会影响到谁,程序员也就越发不愿意维护这段代码了。
我在专栏“[开篇词](http://time.geekbang.org/column/article/73980)”里提到过,人的大脑容量有限,太复杂的东西理解不了。所以,我们唯一能做的就是把复杂的事情变简单。
我在“任务分解”模块中不断强调把事情拆小,同样的道理在写代码中也适用。单一职责原则就是给了你一个指导原则,可以按照不同的 actor 分解代码。
上面这个问题Robert Martin 给了一个解决方案,就是按照不同的 actor 将类分解,我把分解的结果的类图附在了下面:
![](https://static001.geekbang.org/resource/image/ae/bb/ae09f384694f4c7a503da64f7ab34cbb.jpg)
## 编写短函数
好,你已经初步了解了单一职责原则,但还有一点值得注意。我先来问个问题,你觉得一个函数多长是合适的?
曾经有人自豪地向我炫耀他对代码要求很高超过50行的函数绝对要处理掉。
我在专栏中一直强调“小”的价值,能看到多小,就可以在多细的粒度上工作。单一职责这件事举个例子很容易,但在真实的工作场景中,**你能看到一个模块在为多少 actor 服务,就完全取决于你的分解能力了。**
回到前面的问题上,就我自己的习惯而言,通常的函数都在十行以内,如果是表达能力很强的语言,比如 Ruby函数会更短。
所以你可想而知我听到“把50行代码归为小函数”时的心情。我知道“函数长短”又是一个非常容易引起争论的话题不同的人对于这个问题的答案取决于他看问题的粒度。
所以,不讨论前提条件,只谈论函数的长短,其实是没有意义的。
单一职责原则可以用在不同的层面,写一个类,你可以问问这些方法是不是为一类 actor 服务;写方法时,你可以问问这些代码是不是在一个层面上;甚至一个服务,也需要从业务上考虑一下,它在提供是否一类的服务。总之,你看到的粒度越细,也就越能发现问题。
## 总结时刻
今天,我讲的内容是软件设计,很多代码的问题就是因为对设计思考得不足导致的。
许多程序员学习设计是从设计模式起步的但这种学法往往会因为缺乏结构很难有效掌握。设计原则是一个更好的体系掌握设计原则之后才能更好地理解设计模式这些招式。Robert Martin 总结出的“SOLID”是一套相对完整易学的设计原则。
我以“SOLID” 中的单一职责原则为例,给你稍做展开,更多的内容可以去看 Robert Martin 的书。不过,我也给你补充了一些维度,尤其是从“小”的角度告诉你,你能看到多小,就能发现代码里多少的问题。
如果今天的内容你只能记住一件事,那请记住:**把函数写短。**
最后我想请你思考一下,你是怎么理解软件设计的呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,124 @@
# 35 | 总是在说MVC分层架构但你真的理解分层吗
你好,我是郑晔。
作为程序员,你一定听说过分层,比如,最常见的 Java 服务端应用的三层结构,在《[15 | 一起练习:手把手带你分解任务](http://time.geekbang.org/column/article/78542)》中,我曾提到过:
* 数据访问层,按照传统的说法,叫 DAOData 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 逐渐退出了历史舞台,资源层取而代之。
换句话说,访问服务的方式可能会变。放到计算机编程的发展中,这种趋势就更明显了,从命令行到网络,从 CSClient-Server 到 BSBrowser-Server从浏览器到移动端。所以怎么访问不应该是你关注的核心。
同样, 关系型数据库也不是你关注的核心,它只是今天的主流而已。从前用文件,今天还有各种 NoSQL。
如此说来三层架构中的两层重要性都不是那么高那重要的是什么答案便呼之欲出了没错就是剩下的部分我们习惯上称之为服务层但这个名字其实不能很好地反映它的作用更恰当的说法应该可以叫领域模型Domain Model
它便是我们的核心模型,也是我们在做软件设计时,真正应该着力的地方。
为什么叫“服务层”不是一个好的说法呢?这里会遗漏领域模型中一个重要的组成部分:领域对象。
很多人理解领域对象有一个严重的误区,认为领域对象属于数据层。数据存储只是领域对象的一种用途,它更重要的用途还是用在各种领域服务中。
由此还能引出另一个常见的设计错误,领域对象中只包含数据访问,也就是常说的 getter 和 setter而没有任何逻辑。
如果只用于数据存储,只有数据访问就够了,但如果是领域对象,就应该有业务逻辑。比如,给一个用户修改密码,用户这个对象上应该有一个 changePassword 方法,而不是每次去 setPassword。
严格地说,领域对象和存储对象应该是两个类,只不过它俩实在太像了,很多人经常使用一个类,这还是个小问题。但很多人却把这种内部方案用到了外部,比如,第三方集成。
为数不少的团队都在自己的业务代码中直接使用了第三方代码中的对象,第三方的任何修改都会让你的代码跟着改,你的团队就只能疲于奔命。
解决这个问题最好的办法就是把它们分开,**你的领域层只依赖于你的领域对象,第三方发过来的内容先做一次转换,转换成你的领域对象**。这种做法称为防腐层。
当我们把领域模型看成了整个设计的核心,看待其他层的视角也会随之转变,它们只不过是适配到不同地方的一种方式而已,而这种理念的推广,就是一些人在说的六边形架构。
![](https://static001.geekbang.org/resource/image/6b/d9/6bfe53c81c92634f81765870181b63d9.jpg)
怎么设计好领域模型是一个庞大的主题推荐你去了解一下领域驱动设计Domain Driven DesignDDD这个话题我们后面还会再次提到。
讨论其实还可以继续延伸下去已经构建好的领域模型怎么更好地提供给其他部分使用呢一个好的做法是封装成领域特定语言Domain Specific LanguageDSL。当然这也是一个庞大的话题就不继续展开了。
## 总结时刻
我从最常见的服务端三层架构入手,给你讲了它们的来龙去脉。分层架构实际是一种设计上的分解,将不同的内容放在不同的地方,降低软件开发和维护的成本。
分层,更关键的是,提供抽象。这种分层抽象在计算机领域无处不在,无论是编程语言,还是网络协议,都体现着分层抽象的价值。有了分层抽象,人们才能更好地在抽象的基础上构建更复杂的东西。
在日常工作中,我们应该把精力重点放在构建自己的领域模型上,因为它才是工作最核心、不易变的东西。
如果今天的内容你只能记住一件事,那请记住:**构建好你的领域模型。**
最后我想请你思考一下,你还知道哪些技术是体现分层抽象的思想吗?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,114 @@
# 36 | 为什么总有人觉得5万块钱可以做一个淘宝
你好,我是郑晔。
今天,我们从软件行业的一个段子说起。
甲方想要做个电商网站作为乙方的程序员问“你要做个什么样的呢”甲方说“像淘宝那样就好。”程序员问“那你打算出多少钱”甲方想了想“5万块钱差不多了吧
这当然是个调侃客户不懂需求的段子,但你有没有想过,为什么在甲方看来并不复杂的系统,你却觉得困难重重呢?
**因为你们想的根本不是一个东西。**
在客户看来我要的不就是一个能买东西的网站吗只要能上线商品用户能看到能购买不就好了5万块钱差不多了。
而你脑中想的却是“淘宝啊那得是多大的技术挑战啊每年一到双11那就得考虑各种并发抢购。淘宝得有多少程序员5万块你就想做一个门都没有。”
如果放在前面“沟通反馈”的模块,我可能会讲双方要怎么协调,把想法统一了。但到了“自动化”的模块,我想换个角度讨论这个问题:系统是怎么变复杂的。
## 淘宝的发展历程
既然说到了淘宝我们就以一些公开资料来看看淘宝的技术变迁过程。2013年子柳出版了一本《[淘宝技术这十年](http://book.douban.com/subject/24335672/)》,这本书里讲述了淘宝是怎么一步步变化的。
按照书中的说法,第一个淘宝是“买来的”,买的是一个叫做 PHPAuction 的系统即便选择了最高配也才花了2000美元左右。这是一个采用 LAMP 架构的系统,也就是 Linux + Apache + MySQL + PHP这在当年可是典型的开源架构。
团队所做的主要就是一些订制化工作,最大的调整就是将单一数据库的读写进行了拆分,变成了一个主库和两个从库。这种结构在今天来看,依然是很多团队做调整的首选。
![](https://static001.geekbang.org/resource/image/9a/2c/9a82f8edfe722ceb05acf1f35e7c372c.jpg)
当访问量和数据量不断提升MySQL 数据库率先扛不住了。当年的 MySQL 默认采用的是 MyISAM 引擎,写数据的时候会锁住表,读也会被卡住,当然,这只是诸多问题中的一个。
2003年底团队将 MySQL 换成了 Oracle。由于 Oracle 的性能要好上许多,主从的数据库架构又改回了单一数据库。但由于 PHP 访问数据库的缺省方案没有连接池,只好找了开源的 SQL Relay这也为后续的改进埋下了伏笔。
![](https://static001.geekbang.org/resource/image/d9/9d/d97299ce3efb3070369915dbcd60da9d.jpg)
当数据量继续加大,本地存储就已经无法满足了,只能通过引入网络存储解决问题。数据量进一步增大之后,存储节点一拆再拆,依然不能解决问题,淘宝就踏上了购买小型机的道路。
IBM 的小型机、Oracle 的数据库和 EMC 的存储,这个阶段就踏上了 IOE 之路。
2004年初SQL Relay 已经成了一个挥之不去的痛点于是只能从更根本的方案上动脑筋更换程序设计语言。作为当时的主流Java 成了不二之选。
替换的方案就是给业务分模块,一块一块地替换。老模块只维护,不增加新功能,新功能只在新模块开发,新老模块共用数据库。新功能上线,则关闭老模块对应功能,所有功能替换完毕,则老模块下线。
淘宝的数据量继续增长,单台 Oracle 很快到了上限,团队采用了今天常见的“分库分表”模式,但“分库分表”就会带来新的问题,跨数据库的数据怎么整合?于是,打造出了一个 DBRoute用以处理分库的数据。
但是,这种做法也带来了一个新的问题,同时连接多个数据库,任何一个数据库出了问题,都会导致整个网站的故障。
当淘宝的数据量再次增长,每次访问都到了数据库,数据库很难承受。一个解决方案就是引入缓存和 CDNContent Delivery Network内容分发网络这样只读数据的压力就从数据库解放了出来。
当时的缓存系统还不像今天这么成熟,于是,团队基于一个开源项目改出了一个。他们用的 CDN 最开始是一个商用系统,但流量的增加导致这个系统也支撑不住了,只好开始搭建自己的 CDN。
后来,因为 CDN 要消耗大量的服务器资源,为了降低成本,淘宝又开始研发自己的低功耗服务器。
随着业务的不断发展,开发人员越来越多,系统就越来越臃肿,耦合度也逐渐提升,出错的概率也逐渐上升。这时,不得不对系统进行分解,将复用性高的模块拆分出来,比如,用户信息。
业务继续发展,拆分就从局部开始向更大规模发展,底层业务和上层流程逐渐剥离,并逐渐将所有业务都模块化。
有了一个相对清晰地业务划分之后,更多的底层业务就可以应用于不同的场景,一个基础设施就此成型,新的业务就可以使用基础设施进行构建,上层业务便如雨后春笋一般蓬勃发展起来。
在这个过程中有很多技术问题在当时还没有好的解决方案或者是不适合于它们所在的场景。所以淘宝的工程师就不得不打造自己的解决方案比如分布式文件系统TFS、缓存系统Tair、分布式服务框架HSF等等。还有一些技术探索则是为了节省成本比如去 IOE 和研发低功耗服务器等等。
我这里以淘宝网站的发展为例,做了一个快速的梳理,只是为了让你了解一个系统的发展,如果你有兴趣了解更多细节,不妨自己找出这本书读读。当然,现在的淘宝肯定比这更加完整复杂。
## 同样的业务,不同的系统
为什么我们要了解一个系统的演化过程呢?**因为作为程序员,我们需要知道自己面对的到底是一个什么样的系统。**
回到我们今天的主题上5万块钱可以不可以做一个淘宝答案是取决于你要的是一个什么样的系统。最开始买来的“淘宝”甚至连5万块钱都不用而今天的淘宝和那时的淘宝显然不是一个系统。
从业务上说,今天的淘宝固然已经很丰富了,但最核心的业务相差并不大,无非是卖家提供商品,买家买商品。那它们的本质差别在哪呢?
回顾上面的过程,你就可以看到,每次随着业务量的增长,原有技术无法满足需要,于是,就需要用新的技术去解决这个问题。这里的关键点在于:**不同的业务量。**
一个只服务于几个人的系统,单机就够了,一个刚刚入行的程序员也能很好地实现这个系统。而当业务量到达一台机器抗不住的时候,就需要用多台机器去处理,这个时候就必须考虑分布式系统的问题,可能就要适当地引入中间件。
而当系统变成为海量业务提供服务,就没有哪个已经打造好的中间件可以提供帮助了,需要自己从更底层解决问题。
虽然在业务上看来,这些系统是一样的,但在技术上看来,在不同的阶段,一个系统面对的问题是不同的,因为它面对业务的量级是不同的。**更准确地说,不同量级的系统根本就不是一个系统。**
只要业务在不断地发展,问题就会不断出现,系统就需要不断地翻新。我曾听到一个很形象的比喻:把奥拓开成奥迪。
## 你用对技术了吗?
作为一个程序员,我们都知道技术的重要性,所以,我们都会努力地去学习各种各样的新技术。尤其是当一个技术带有大厂光环的时候,很多人都会迫不及待地去学习。
我参加过很多次技术大会,当大厂有人分享的时候,通常都是人山人海,大家都想学习大厂有什么“先进”技术。
知道了,然后呢?
很多人就想迫不及待地想把这些技术应用在自己的项目中。我曾经面试过很多程序员,给我讲起技术来滔滔不绝,说什么自己在设计时考虑各种分布式的场景,如果系统的压力上来时,他会如何处理。
我就好奇地问了一个问题,“你这个系统有多少人用?”结果,他做的只是一个内部系统,使用频率也不高。
为了技术而技术的程序员不在少数,过度使用技术造成的结果就是引入不必要的复杂度。即便用了牛刀杀鸡,因为缺乏真实场景检验,也不可能得到真实反馈,对技术理解的深度也只能停留在很表面的程度上。
在前面的例子中,**淘宝的工程师之所以要改进系统,真实的驱动力不是技术,而是不断攀升的业务量带来的问题复杂度。**
所以,评估系统当前所处的阶段,采用恰当的技术解决,是我们最应该考虑的问题。
也许你会说,我做的系统没有那么大的业务量,我还想提高技术怎么办?答案是到有好问题的地方去。现在的 IT 行业提供给程序员的机会很多,找到一个有好问题的地方并不是一件困难的事,当然,前提条件是,你自己得有解决问题的基础能力。
## 总结时刻
今天,我以淘宝的系统为例,给你介绍了一个系统逐渐由简单变复杂的发展历程,希望你能认清不同业务量级的系统本质上就不是一个系统。
一方面,有人会因为对业务量级理解不足,盲目低估其他人系统的复杂度;另一方面,也有人会盲目应用技术,给系统引入不必要的复杂度,让自己陷入泥潭。
作为拥有技术能力的程序员,我们都非常在意个人技术能力的提升,但却对在什么样情形下,什么样的技术更加适用考虑得不够。采用恰当的技术,解决当前的问题,是每个程序员都应该仔细考虑的问题。
如果今天的内容你只能记住一件事,那请记住:**用简单技术解决问题,直到问题变复杂。**
最后,我想请你回想一下,你身边有把技术做复杂而引起的问题吗?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,104 @@
# 37 | 先做好DDD再谈微服务吧那只是一种部署形式
你好,我是郑晔。
在“自动化”模块的最后,我们来聊一个很多人热衷讨论却没做好的实践:微服务。
在今天做后端服务似乎有一种倾向,如果你不说自己做的是微服务,出门都不好意思和人打招呼。
一有技术大会,各个大厂也纷纷为微服务出来站台,不断和你强调自己公司做微服务带来的各种收益,下面的听众基本上也是热血沸腾,摩拳擦掌,准备用微服务拯救自己的业务。
我就亲眼见过这样的例子,几个参加技术大会的人回到公司,跟人不断地说微服务的好,说服了领导,在接下来大的项目改造中启用了微服务。
结果呢?一堆人干了几个月,各自独立开发的微服务无法集成。最后是领导站出来,又花了半个月时间,将这些“微服务”重新合到了一起,勉强将这个系统送上了线。
人家的微服务那么美,为什么到你这里却成了烂摊子呢?因为你只学到了微服务的形。
## 微服务
大部分人对微服务的了解源自 James Lewis 和 Martin Fowler 在2014年写的[一篇文章](http://www.martinfowler.com/articles/microservices.html),他们在其中给了微服务一个更清晰的定义,把它当作了一种新型的架构风格。
但实际上,早在这之前的几年,很多人就开始用“微服务”这个词进行讨论了。
“在企业内部将服务有组织地进行拆分”这个理念则脱胎于 SOAService Oriented Architecture面向服务的架构只不过SOA 诞生自那个大企业操盘技术的年代,自身太过于复杂,没有真正流行开来。而微服务由于自身更加轻量级,符合程序员的胃口,才得以拥有更大的发展空间。
谈到微服务,你会想起什么呢?很多人对微服务的理解,就是把一个巨大的后台系统拆分成一个一个的小服务,再往下想就是一堆堆的工具了。
所以,市面上很多介绍微服务的内容,基本上都是在讲工具的用法,或是一些具体技术的讨论,比如,用 Spring Boot 可以快速搭建服务,用 Spring Cloud 建立分布式系统,用 Service Mesh 技术作为服务的基础设施,以及怎么在微服务架构下保证事务的一致性,等等。
确实,这些内容在你实现微服务时,都是有价值的。但必须先回答一个问题,我们为什么要做微服务?
对这个问题的标准回答是相对于整体服务Monolithic而言微服务足够小代码更容易理解测试更容易部署也更简单。
这些道理都对,但这是做好了微服务的结果。怎么才能达到这个状态呢?这里面有一个关键因素,**怎么划分微服务,也就是一个庞大的系统按照什么样的方式分解。**
这是在很多关于微服务的讨论中所最为欠缺的,也是很多团队做“微服务”却死得很难看的根本原因。
不了解这一点,写出的服务,要么是服务之间互相调用,造成整个系统执行效率极低;要么是你需要花大力气解决各个服务之间的数据一致性。换句话说,服务划分不好,等待团队的就是无穷无尽的偶然复杂度泥潭。只有正确地划分了微服务,它才会是你心目中向往的样子。
**那应该怎么划分微服务呢?你需要了解领域驱动设计。**
## 领域驱动设计
领域驱动设计Domain Driven DesignDDD是 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 的理解是什么样的呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,106 @@
# 答疑解惑 | 持续集成、持续交付,然后呢?
你好,我是郑晔。
“自动化”模块落下了帷幕,这是四个工作原则中最为“技术化”的一个,也应该是程序员们最熟悉的主题。
我从软件外部的自动化——工作流程讲起,让你能够把注意力专注于写好代码;讲到了软件内部的自动化——软件设计,选择恰当的做法,不贪图一时痛快,为后续的工作挖下深坑。
既然是一个大家都熟悉的话题,同学们自然也有很多经验分享,也有很多人看到了与自己不同的做法,提出了各种各样的问题。
在今天的答疑中,我选出了几个很有意思的问题,让大家可以在已有内容上再进一步延伸。
## 问题1持续交付是否可以再做扩展
毅 同学提到
> 为达到有效交付的目标,用户能够尽早参与,我觉得也是比较重要的一环。从生产环境获得结果,是否可再做扩展,将用户也作为一个独立节点?
> ——《[32 | 持续交付:有持续集成就够了吗?](http://time.geekbang.org/column/article/87229)》
西西弗与卡夫卡 同学提到
> 持续交付可以是持续交付最大价值那范围就不仅限于软件还可以进一步延伸到运营比如说结合ABTest自动选择最有效的运营策略为用户交付最大价值。
> ——《[32 | 持续交付:有持续集成就够了吗?](http://time.geekbang.org/column/article/87229)》
两位同学能提出这样的想法,说明真的是已经理解了持续集成和持续交付,所以,才能在这个基础上继续延伸,思考进一步的扩展。
我在专栏中一直在强调别把自己局限在程序员这个单一的角色中应该了解软件开发的全生命周期。在前面的内容中我讲了不少做产品的方法比如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 之类基础设施的使用情况。对于应用来说,具体什么样的数据需要搜集,还需要团队自己进行设计。
说了这些,我其实想说的是,持续验证虽然是一个好的想法,但目前为止,还不如持续集成和持续交付这些已经有比较完整体系做支撑。想做到“持续”,就要做到自动化,想做到自动化,就要有标准化支撑,目前这个方面还是“八仙过海各显神通”的状态,没法上升到行业最佳实践的程度。
其实道理上也很简单,从一无所有,到持续集成、再到持续交付,最后到持续验证,每过一关,就会有大多数团队掉队。所以,真正能达到持续交付的团队都少之又少,更别提要持续验证了。
## 问题2Selenium 和 Cucumber 的区别是什么?
没有昵称 同学提到
> 老师Selenium 跟 Cucumber 有区别吗?
> ——《[33 | 如何做好验收测试?](http://time.geekbang.org/column/article/87582)》
这是一个经常有人搞混的问题。为了让不熟悉的人理解,我先讲一点背景。
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 应用测试方面实现步骤定义的一个非常好的工具。
## 问题3IntelliJ IDEA 怎么学?
hua168 同学提到
> IDEA 怎么学呢?是用到什么功能再学?还是先看个大概,用到时再仔细看?
> ——《[30 | 一个好的项目自动化应该是什么样子的?](http://time.geekbang.org/column/article/86561)》
一个工具怎么学?我的经验就是去用。我没有专门学过 IntelliJ IDEA只是不断地在使用它。遇到问题就去找相应的解决方案。
如果说在 IDEA 上下过功夫,应该是在快捷键上。我最早写代码时的风格应该是鼠标与键盘齐飞,实话说,起初也没觉得怎么样。加入 ThoughtWorks 之后,看到很多人把快捷键运用得出神入化,那不是在写一行代码,而是在写一片代码。我当时有一种特别震惊的感觉。
我自以为在写代码上做得已经相当好了,然而,有人却在你很擅长的一件事上完全碾压了你,那一瞬间,我感觉自己这些年都白学了。这种感觉后来在看到别人能够小步重构时又一次产生了。
看到差距之后,我唯一能做的,就是自己下来偷偷练习。幸好,无论是快捷键也好,重构也罢,都是可以单独练习的。花上一段时间就可以提高到一定的水平。后来,别人看我写代码时也会有类似的感觉,我会安慰他们说,不要紧,花点时间练习就好。
其实,也有一些辅助的方法可以帮助我们练习,比如,我们会给新员工发放 IntelliJ IDEA 的快捷键卡片写代码休息之余可以拿来看一下再比如IntelliJ IDEA 有一个插件叫 [Key Promoter X](http://plugins.jetbrains.com/plugin/9792-key-promoter-x),如果你用鼠标操作,它会给你提示,帮你记住快捷键。有一段时间,我已经练习到“看别人写代码,脑子里能够完全映射出他在按哪个键”的程度。
写代码是个手艺活,要想打磨手艺,需要看到高手是怎么工作的,才不致于固步自封。如果你身边没有这样的人,不如到网上搜一些视频,看看高手在写代码时是怎么做的,这样才能找到差距,不断提高。
好,今天的答疑就到这里,你对这些问题有什么看法呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,159 @@
# 划重点 | “自动化”主题的重点内容回顾汇总
你好,我是郑晔。
“自动化”模块终于全部更新完毕。至此,四个工作原则我已经给你全部介绍了一遍,相对而言,这个模块的内容比较“硬”,我也竭尽全力帮你串起更多知识的脉络,所以,信息量也是非常大的。希望你能够找到自己接下来努力的方向,不断提升自己的“硬实力”。
## 重点复习
在这个模块中,我们学习到了一些最佳实践。
* 持续交付
* 将生产部署纳入了开发的考量。
* 持续交付的基础设施通常包含持续集成环境、测试环境、预生产环境和生产环境。
* 构建流水线保证到了下游的交付物一定是通过上游验证的。
* 随着 Docker 的诞生,交付由发布包变成了 Docker 镜像。
* DevOps
* 将开发和运维结合到一起。
* 环境配置工具上的进步,让基础设施即代码成了行业共识。
* 验收测试
* 验收测试要站在业务的角度编写。
* BDD 是一种编写验收测试的方式。
* Given...When...Then... 的描述给了一个描述业务的统一方式。
* 写好验收测试,需要构建测试模型。
* SOLID 原则
* 设计模式背后的道理。
* 单一职责原则Single responsibility principleSRP
* 开放封闭原则Openclosed principleOCP
* Liskov 替换原则Liskov substitution principleLSP
* 接口隔离原则Interface segregation principleISP
* 依赖倒置原则Dependency inversion principleDIP
* 用好单一职责原则,前提条件是看待问题颗粒度要小。
* DDD
* 它将思考的起点拉到了业务上。
* DDD 分为战略设计和战术设计。
* 微服务
* 做好微服务的前提是划分好限界上下文。
* 微服务的第一步,不要划分微服务。
在这个模块中,我们还了解了一些重要的思路,让我们把工作做得更好。
* 程序员的三大美德懒惰、急躁和傲慢Laziness, Impatience and hubris
* 小心 NIH 综合症Not Invented Here Syndrome
* 写好构建脚本,做好项目自动化。
* 参照 Java 知识体系,学习运维知识。
* 软件设计最基础的原则是“高内聚、低耦合”。
* 分层架构是一种设计上的分解。
* 不同业务量的系统本质上不是一个系统。
* 采用简单技术解决问题,直到问题变复杂。
## 实战指南
* 请谨慎地将工作自动化。
——《[29 | “懒惰”应该是所有程序员的骄傲](http://time.geekbang.org/column/article/86210)》
* 将你的工作过程自动化。
——《[30 | 一个好的项目自动化应该是什么样子的?](http://time.geekbang.org/column/article/86561)》
* 有体系地学习运维知识。
——《[31 | 程序员怎么学习运维知识?](http://time.geekbang.org/column/article/87008)》
* 将部署纳入开发的考量。
——《[32 | 持续交付:有持续集成就够了吗?](http://time.geekbang.org/column/article/87229)》
* 将验收测试自动化。
——《[33 | 如何做好验收测试?](http://time.geekbang.org/column/article/87582)》
* 把函数写短。
——《[34 | 你的代码是怎么变混乱的?](http://time.geekbang.org/column/article/87845)》
* 构建好你的领域模型。
——《[35 | 总是在说MVC分层架构但你真的理解分层吗](http://time.geekbang.org/column/article/88309)》
* 用简单技术解决问题,直到问题变复杂。
——《[36 | 为什么总有人觉得5万块钱可以做一个淘宝](http://time.geekbang.org/column/article/88764)》
* 学习领域驱动设计。
——《[37 | 先做好DDD再谈微服务吧那只是一种部署形式](http://time.geekbang.org/column/article/89049)》
## 额外收获
在这个模块的最后,针对大家在学习过程中的一些问题,我也进行了回答,帮你梳理出一个思路,更好地理解学到的内容:
* 持续集成的延伸。
* 持续集成完成系统集成。
* 持续交付完成可部署上线。
* “持续验证”完成产品想法验证。
* AB 测试,用一个软件的多个版本验证想法。
* Selenium 用以完成浏览器的自动化。
* 熟练使用快捷键。
——《[答疑解惑 | 持续集成、持续交付,然后呢?](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 同学对文章内容进行了补充:
> 现在运维流行DevOps高级一点就是AI其中一篇文章《DevOps 详解》不错,链接如下:
> [https://infoq.cn/article/detail-analysis-of-devops](https://infoq.cn/article/detail-analysis-of-devops)
> 《DevOps知识体系与标准化的构建》也不错下载地址
> [https://yq.aliyun.com/download/778](https://yq.aliyun.com/download/778)
> 运维知识体系:
> [https://www.unixhot.com/page/ops](https://www.unixhot.com/page/ops)
> Web缓存知识体系
> [https://www.unixhot.com/page/cache](https://www.unixhot.com/page/cache)
**感谢同学们的精彩留言。在下一个模块中,我将结合具体的应用场景,将之前讲过的“思考框架”和“四个原则”进行综合应用的分析。**
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,123 @@
# 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](http://developers.google.com/protocol-buffers/)、[Thrift](http://thrift.apache.org) 等等。一些有年头的系统可能会采用那时候流行的协议比如XML有一些系统则采用自己特定领域的协议比如通信领域有大量3GPP 定义的协议。
一般来说,从外部接口这件事就能看出一个项目所处的年代,至少是技术负责人对技术理解的年代。
了解完外部,就该了解内部了。了解内部系统也要从业务入手,对应起来就是,这个系统由哪些模块组成,每个模块承担怎样的职责。如果系统已经是微服务,每个服务就应该是一个独立的模块。
通常这也是一个发现问题的点,很多系统的模块划分常常是职责不清的,因此会产生严重的依赖问题。在前面的内容中,我多次提到限界上下文,用限界上下文的视角衡量这些模块,通常会发现问题,这些问题可以成为后续工作改进的出发点。
业务之后是技术,对应着我需要了解分层。前面说过,[分层结构反映着系统的抽象。](http://time.geekbang.org/column/article/88309)我希望了解一个模块内部分了多少个层,每个层的职责是什么。了解了这些对系统的设计,也就对系统有了一个整体的认识。
设计之后,就到了动手的环节,但还不到写代码的时候。我会先从构建脚本开始,了解项目的常用命令。我预期从版本控制里得到的是一个可以构建成功的脚本,如果不是这样,我就知道哪里需要改进了。
最后才是代码,比如,代码的目录结构、配置文件的位置、模块在源码上的体现等等,这是程序员最熟悉的东西,我就不多说了。作为初步的接触,了解基本的东西就够了,代码是我们后期会投入大量精力的地方,不用太着急。
#### 团队运作
最后,我们还要了解一下团队运作。同样从外部开始,这个团队有哪些外部接口,比如,需求是从哪来的,产品最终会由谁使用,团队需要向谁汇报。如果有外部客户,日常沟通是怎么安排的。
再来就是内部的活动,一方面是定期的活动,比如,站会、回顾会议、周会,这些不同活动的时间安排是怎样的;另一方面是团队的日常活动,比如,是否有每天的代码评审、是否有内部的分享机制等等。
通过了解这些内容,基本上可以大致判断出一个团队的专业程度,也可以知道自己需要帮助的时候,可以找谁帮忙,为自己更好地融入团队打下基础。
你也许会问,了解这么多东西需要很长时间吧?其实不然,因为只需要从整体上有认知,如果有人很清楚团队现状的话,你可以去请教,也许一天就够了,这也是我往往能够快速上手的原因。接下来,就该卷起袖子干活了!
## 总结时刻
我给你介绍了怎么把前面学到的知识运用在了解一个项目上,按照业务、技术和团队运作三个方面去了解。
大多数程序员习惯的工作方式,往往是从细节入手,很难建立起一个完整的图景,常常是“只见树木不见森林”,而我的方式则是**从大到小、由外而内**,将要了解的内容层层分解,有了大图景之后,很容易知道自己做的事情到底在整体上处于什么样的位置。我把上面的内容总结了成一份供你参考。
![](https://static001.geekbang.org/resource/image/6e/e7/6e2248978ff0b4c8957925792292f2e7.jpg)
附赠一点小技巧:使用“行话”。在交流的过程中,学习一点”行话“。这会让人觉得你懂行,让你很快得到信任,尽早融入团队。
如果今天的内容你只能记住一件事,那请记住:**了解一个项目,从大图景开始。**
最后,我想请你分享一下,你在入职一个新公司遇到过哪些困难呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,111 @@
# 39 | 面对遗留系统,你应该这样做
你好,我是郑晔。
在上一讲中,结合着“新入职一家公司”的场景,我给你讲了如何在具体情况下应用我们前面学到的知识。这一讲,我们再来选择一个典型的实际工作场景,将所学综合应用起来。这个场景就是面对遗留系统。
在《[34 | 你的代码是怎么变混乱的?](http://time.geekbang.org/column/article/87845)》中,我给你讲了代码是会随着时间腐化的,无论是有意,还是无意。即便是最理想的场景,代码设计得很好,维护得也很精心,但随着技术的不断升级进步,系统也需要逐步升级换代。
比如,我们一直认为电信是一个独特的领域,与 IT 技术是完全独立的,学好 CTCommunication 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 系统的升级过程,就是将业务分成若干的小模块,每次只升级一个模块,老模块只维护,不增加新功能,新功能只在新模块开发,新老模块共用数据库。新功能上线,则关闭老模块对应功能,所有功能替换完毕,则老模块下线。
这个道理是普遍适用的,差别只是体现在模块的大小上。如果你的“小模块”是一个系统,那就部署新老两套系统,在前面的流量入口做控制,逐步把流量从老系统转到新系统上去;如果“小模块”只在代码层面,那就要有一段分发的代码,根据参数将流程转到不同的代码上去,然后,根据开发的进展,逐步减少对老代码的调用,一直到完全不依赖于老代码。
![](https://static001.geekbang.org/resource/image/35/8c/35b5beb135cd01e701a78df559c4e38c.jpg)
这里还有一个小的建议按照分模块的做法将新代码放到新模块里按照新的标准去写新的代码比如测试覆盖率要达到100%,然后,让调用入口的地方依赖于这个新的模块。
最后,有了测试,有了替换方案,但还有一个关键问题,新代码要怎么写?
要回答这个问题,我们必须回到一开始的地方,我们为什么要做这次调整。因为这个系统已经不堪重负了,那我们新做的修改是不是一定能解决这个问题呢?答案是不好说。
很多程序员都会认为别人给留下的代码是烂摊子,但真有一个机会让你重写代码,你怎么保证不把摊子弄烂?这是很多人没有仔细思考过的问题。
如果你不去想这个问题,即便今天你重写了这段代码,明天你又会怨恨写这段代码的人没把这段代码写好,只不过,这个被抱怨的人是你自己而已。
要想代码腐化的速度不那么快,一定要在软件设计上多下功夫。**一方面,建立好领域模型,另一方面,寻找行业对于系统构建的最新理解。**
关于领域模型的价值,我在专栏前面已经提到过不少次了。有不少行业已经形成了自己在领域模型上的最佳实践,比如,电商领域,你可以作为参考,这样可以节省很多探索的成本。
我们稍微展开说说后面一点,“寻找行业中的最新理解”。简言之,我们需要知道现在行业已经发展到什么水平了。
比如说,今天做一个大访问量的系统,我们要用缓存系统,要用 CDN而不是把所有流量都直接转给数据库。而这么做的前提是内存成本已经大幅度降低缓存系统才成为了标准配置。拜 REST 所赐,行业对于 HTTP 的理解已经大踏步地向前迈进CDN 才有了巨大的进步空间。
而今天的缓存系统已经不再是简单的大 Map有一些实现得比较好的缓存系统可以支持很多不同的数据结构甚至支持复杂的查询。从某种程度上讲它们已经变成了一个性能更好的“数据库”。
有了这些理解,做技术选型时,你就可以根据自己系统的特点,选择适合的技术,而不是以昨天的技术解决今天的问题,造成的结果就是,代码写出来就是过时的。
前面这个例子用到的是技术选型,关于“最新理解”还有一个角度是,行业对于最佳实践的理解。
其实在这个专栏里,我讲的内容很多都是各种“最佳实践”,比如,要写测试,要有持续集成,要有自动化等等,这些内容看似很简单,但如果你不做,结果就是团队很容易重新陷入泥潭,继续苦苦挣扎。
既然选择重写代码,至少新的代码应该按照“最佳实践”来做,才能够尽可能减缓代码腐化的速度。
总之,**改造遗留系统,一个关键点就是,不要回到老路上。**
## 总结时刻
我们把前面学到的各种知识运用到了“改造遗留系统”上。只要产品还在发展,系统改造就是不可避免的。改造遗留系统,前提条件是要弄清楚现状,知道系统为什么要改造,是架构有问题,还是领域模型混乱,只有知道根因,才可能有的放矢地进行改造。
改造遗留系统,我给你几个建议:
* 构建测试防护网,保证新老模块功能一致;
* 分成小块,逐步替换;
* 构建好领域模型;
* 寻找行业中关于系统构建的最新理解。
如果今天的内容你只能记住一件事,那请记住:**小步改造遗留系统,不要回到老路上。**
最后,我想请你分享一下,你有哪些改造遗留系统的经验呢?欢迎在留言区分享你的做法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,119 @@
# 40 | 我们应该如何保持竞争力?
你好,我是郑晔。
在前面两讲,我结合着两个程序员要直接面对的场景,讨论了如何综合运用前面学习到的知识,这一讲的内容可能不涉及到实际的应用场景,但与每个人的发展息息相关。我想谈谈如何走好程序员这条路。
## 焦虑的程序员
让我们再次用思考框架分析一下问题。首先,现状是什么?关于这个问题,我并不打算讨论个体,因为每个人的情况千差万别,我准备从整体入手。
IT 行业是一个快速发展变化的行业,一方面,我们不断地看到有人快速取得成功,另一方面,我们也听到了许多充满焦虑的声音。获得大的成功总是一个小概率事件,大多数人面对的还是日常的柴米油盐。
**我们的焦虑来自于对未来的不确定性,而这种不确定性是一个特定时代加上特定行业的产物。**
如果把时间倒回到上个世纪80年代之前虽然当时的生活条件一般但很少有人会为未来的发展焦虑因为那时候人们可以清晰地看到自己未来的人生尽管那种人生可能是平淡的。
但今天的我们处在一个人类历史上少有的快速发展的时代,我们看不清以后的人生,大脑却还停留在上一代人的思维习惯上。
IT 行业在国内的大发展也就最近20多年的事行业里很少有走过完整职业生涯的程序员。也正是因为如此我们经常会产生了各种焦虑
* 我刚刚入行时有人问程序员能做到30岁吗
* 我快30岁时有人问35岁还能做程序员吗
* 我35岁时讨论变成了40岁的程序员该怎么办。
估计等国内有越来越多的程序员走完了整个职业生涯,就会有人关心,程序员退休之后的生活应该是什么样子了。
从长期来看,只要生活中还有需要用自动化解决的问题,程序员这个群体还是很有前景的。但随着时间的推移,程序员这个职业的溢价也会越来越低,单纯凭借身处这个行业就获得好发展的可能性也越来越低,想让自己的职业生涯走得更顺畅,还需要找到更好的目标,不断努力。
## 成为 T 型人
我们再来回答下一个问题:目标是什么。也许这时候,每个人脑子里想到的职业发展路线都不一样,但我准备用一个统一的目标回答你:成为 T 型人。
**什么叫 T 型人?简言之,一专多能。**
![](https://static001.geekbang.org/resource/image/a9/19/a9274fd47bf59fd4d795e7e319616b19.jpg)
**有了“一专”,“多能”才是有意义的,否则,就是低水平重复,而这正是很多人职业生涯不见起色的真正原因。**
**这里的“专”不是熟练,而是深入。**你可能是个有着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 提出了一个“学习区”模型,如下图所示:
![](https://static001.geekbang.org/resource/image/62/b1/6236b2bd674fe1ff0edcd9485755b8b1.jpg)
* 最内层是舒适区Comfort Zone置身其中会让人感觉良好但也会因为没有挑战成长甚微你可以把它理解成做你最熟悉的事情。
* 最外层是恐慌区Panic Zone这是压力极大的地方完全超出了你的能力范围你在其中只会感到无比的焦虑。
* 中间的是学习区Learning Zone事情有难度又刚好是你努力一下可以完成的这才是成长最快的区域。
根据这个模型,只有一直身处学习区才能让人得到足够的成长,所以,我们应该既选择比自己能力高一点的问题去解决,不要总做自己习惯的事,没有挑战,也不要好大喜功,一下子把自己的热情全部打散。
在学习区成长,就不要满足于当前已经取得的成绩,那已经成为你的舒适区。因为我们有远大的目标在前面指引,完成日常的工作只不过是个人成长路上的台阶。
也许你会说,我的工作不能给我个人成长所需的机会,怎么办呢?实际上,别人只会关心你是否完成工作,成长是自己的事情,很多机会都要靠自己争取,前面提到的那些具体做法完全是你可以在工作范围内,自己努力的事情。
如果你当前的工作已经不能给你提供足够好的问题,那就去寻找一份更有挑战性的工作。在 IT 行业,跳槽似乎是一件很常见的事,但很多人跳槽的时候,并不是以提升自己为目标的。造成的结果是,不断地做同一个层面的工作,自然也就很难提升自己的水平。
为什么程序员都愿意到大厂工作?因为那里有高水平的人和好的问题。但如果只是到大厂去做低水平的事,那就是浪费时间了。所以,即便你真的想到大厂工作,与谁一起工作,做什么事,远比进入大厂本身要重要得多。
如果你真的能够不断向前进步迟早会遇到前面已经没有铺就好的道路这时候就轮到你创造一个工具给别人去使用了。比如2012年我在项目中受困于集成问题却找不到一个我想要的、能在单元测试框架里用的模拟服务器于是我写了 [Moco](http://github.com/dreamhead/moco)。
最后,我还想鼓励你分享所得。我在《[28 | 结构化:写文档也是一种学习方式](http://time.geekbang.org/column/article/84663)》中和你说过,输出是一种将知识连接起来的方式,它会让人摆脱固步自封,也会帮你去创造自己的行业影响力,机会会随着你在行业中的影响力逐渐增多,有了行业影响力,你才有资格成为行业专家。
当你成为了一个行业级别的专家,就可以在这条路上一直走下去,而不必担心自己是不是拼得过年轻人了,因为你也在一直前进!
## 总结时刻
程序员是一个充满焦虑的群体,焦虑的本质是对未来的不确定。工作在这个时代的程序员是一个特殊的群体,一方面,这个大时代为我们创造了无数的机会,另一方面,因为程序员是一个新的行业,所以,很多人不知道未来是什么样子的,焦虑颇深。
从目前的发展来看IT 行业依然是一个非常有前景的行业,但想在这条路上走好,需要我们成为 “T ”型人才,也就是“一专多能”。一专多能的前提是“一专”,让自己成为某个方面的专家。这个专家要放在行业的标准去看,这才能降低因为一个公司的波动而造成的影响。
成为行业专家,要向行业的大师学习,给自己定下一个高的目标,然后是脚踏实地,找适合自己的问题去解决,让自己一直在学习区成长。
如果今天的内容你只能记住一件事,那请记住:**在学习区工作和成长。**
最后,我想请你分享一下,你有哪些保持自己竞争力的心得呢?欢迎在留言区写下你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

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

View File

@ -0,0 +1,181 @@
# 划重点 | “综合运用”主题内容的全盘回顾
你好,我是郑晔。
又到了我们划重点的时间了,因为篇幅关系,“综合运用”这个模块最为短小精悍。
在这个模块中,我们把前面学到的各种知识综合起来,运用在实际的工作场景中,让你知道这些内容并不是一个个孤立的实践,在实际工作中,唯有将它们结合起来,才能发挥最大功效。
## 重点复习
在这个模块中,我们学习到了一些新知识。
* **“学习区”学习模型**
* 舒适区,舒适而缺乏成长。
* 恐慌区,超出能力范围。
* 学习区,有难度而可以达成。
* 在学习区练习才能得到足够的成长。
* **T 型人才,一专多能**
* 知识的广度。
* 专业技能的深度。
* 有“一专”,“多能”才是有意义的。
在这个模块中,我们还了解了一些重要的思路,让我们把工作做得更好。
* **进入新工作,从全面了解开始**
* 业务:做什么。
* 技术:怎么做。
* 团队运作:怎么与人协作。
* 从大到小,由外及内地了解工作。
* 面对遗留系统,稳扎稳打,小步前行
* 基础理念
* 烂代码只是现象,要了解根因。
* 能重构,先重构,大规模改造是迫不得已的选择。
* 小步前行。
* 实际操作
* 构建测试防护网。
* 将大系统分解成小模块,逐步替换。
* 新旧模块并存,由分发模块调度。
* 建立好领域模型。
* 寻找行业对于系统构建的最新理解。
* **程序员的职业发展**
* 程序员的焦虑来自于对未来的不确定性,这种不确定性是一个特定时代加上特定行业的产物。
* 快速发展的中国经济。
* 程序员在中国是一个新兴职业。
* 成为行业专家,制定高目标。
* 向大师学习,开拓视野。
* 找到好的问题,和高水平的人一起工作。
## 实战指南
* 了解一个项目,从大图景开始。
——《[38 | 新入职一家公司,怎么快速进入工作状态?](http://time.geekbang.org/column/article/89981)》
* 小步改造遗留系统,不要回到老路上。
——《[39 | 面对遗留系统,你应该这样做](http://time.geekbang.org/column/article/90231)》
* 在学习区工作和成长。
——《[40 | 我们应该如何保持竞争力?](http://time.geekbang.org/column/article/90864)》
## 额外收获
在这个模块的最后,针对大家在学习过程中的一些问题,我也进行了回答,帮你梳理出一个思路,更好地理解学到的内容:
* 推行新观念,找愿意改变的人,做具体的事。
* Lead by Example.
* 外部系统应该用接口隔离这种做法体现了接口隔离原则ISP也是防腐层概念的体现。
* 外部系统的测试,能模拟的就模拟,能本地的就本地。
## 留言精选
关于入职一家新公司,怎么快速进入工作状态这个问题,西西弗与卡夫卡 同学分享了他的方法:
> 有朋友正在转型,从乙方商业化产品的交付经理转向新公司的产品经理。原本得心应手的思维方式和工作习惯,遇到了巨大挑战。以前只需依据已有产品的功能出解决方案,能做就能做,不能实现就是不能实现,到某个时间交付什么功能很明确,考核是以交付签字为准。现在需要面对各方需求,自己想明白用户真正的问题是什么,最终要交付的价值是什么,没有一个实体的谁人来签字,只有不断地迭代。
> 借鉴领域驱动设计,可以采用以下方法。简单描述的话,是一个点、一个圈再加一个箭头线,是不是有点像丘比特?
> 一个“点”,指的是用户核心价值。这是最关键的一条,基本上只能靠自己想明白。想,不是闭门造车式的苦思冥想,可以是已有的领域经验,可以从书本中学习,可以是大家的各种吐槽,可以是自己从旁边观察用户的实践,还可以是自己变身为用户的实践。
> 有些人会纠结“点”想的对不对,迟迟不敢动手。其实一开始想得对不对不是那么重要,关键是要有这“点”,然后快速到市场上验证,根据反馈再调整。
> 一个“圈”,指的是围绕核心价值划出的范围,即领域驱动设计中的限界上下文。产品经理面临的一个现实是,各种人都会给你提需求,只要他们觉得和你有关,还时不时来问什么时候可以实现。
> 需求轰炸之下很容易焦虑,不光自己焦虑,所有的利益相关者都会焦虑。依据核心价值,框出需求范围,在和各方交流过程中可以有一种确定性,减少焦虑,利于行动。
> 大家(不光是研发团队,也包括其他需求方)就能明白,哪些和当前核心价值密切相关,我们优先考虑;哪些与核心价值有关但它不在我们的范围内,属于其他团队,需要他们协助;哪些有关系,但目前没想清楚价值大不大,并且代价可能很高建议先搁置。范围不是一成不变,它随着时间会发生变动,所以我们不要追求固定,只要保证在某个时间段内,大家一致认同即可。
> 一个“箭头”,指的是实现路径,箭头指向核心目标(核心价值)。目标(核心价值)和范围描绘的是终极,而从现实到终极还有很多路要走,可能的路径还有很多条。我们需要琢磨怎么走更稳当,怎么走代价比较低,路上关键的里程碑是什么。路径对不对是其次,重要的是思考过程,可以把关键点需要交付的价值、需要支持的资源等等梳理清楚。
另外,西西弗与卡夫卡 同学还对于程序员如何保持竞争力的问题给出了非常不错的建议。
> 补充我的一些做法。工作中不要满足当前需求,要经常从自己上级主管甚至老板角度来审视自己的工作,思考业务的终极目标,持续琢磨扩展边界,挑战工作难度。
> 平时多看书多思考,除了钻研某个领域,还要多有涉猎,拓展领域,成为终身学习者。
> 适当运动维持健康,你有更多体力和更强抗压能力的时候,就可以超过不少人。
> 保持竞争力除了上述之外,要保持乐观,相信大多数事都有解决方法,在多数人都容易放弃的时候,你的坚持,就是竞争力。
对于新入职一家公司的场景Y024 同学分享了他快速进入工作状态的方法:
> 1.我会在权限允许的范围内,时不时的到处翻翻 ftp、内部 wiki 等资源,星星点点构建全貌(业务、技术、团队)。
> 2.梳理系统数据流。去年很火的电视剧「大江大河」里,宋运辉初入职场的方式就很值得借鉴:先走通全部流程,有个全貌,利用图书馆、师傅等资源再自己动手各个击破并绘制流程图,最终实践检验认知,以技术说话融入团队。
> (他就每天只要天气晴朗,绕着设备上上下下、里里外外地跑。一个星期下来,全部流程走通;两个星期不到,原理搞通,仪表能读,普通故障能应付;第三星期开始,他可以开出维修单,但得给师父过目;第四星期起,谁有事请假他可以顶上,坐到仪表盘前抄表看动态做操作。师父说他学得很快。
> 第四星期起,没人可以让他顶替时候,他在仪表室后面支起绘图板。先画出工艺流程图,经现场核对无误,又让师父审核后,开始按部就班地根据液体走向,测绘所有设备的零件图、装配图、管段图等。
> 这工作最先做的时候异常艰难,首先是绘图不熟练,很多小毛病,尤其是遇到非标零件,还得到机修工段测绘,有时一天都绘不成一个小小非标件。如果车间技术档案室有图纸还好,可以对照着翻画,可档案室里的图纸残缺不全,前后混乱,想找资料,先得整理资料。
> 资料室中年女管理员乐得有个懂事的孩子来帮她整理,索性暗暗配把钥匙给宋运辉,要是她下班不在的时候,让宋运辉自己偷偷进来关上门寻找资料。
> 机修工段的人本来挺烦这个宋运辉,说他一来维修单子多得像雪片,支得他们团团转,有人还趁宋运辉上班时候冲进控制室指桑骂槐,被寻建祥骂了回去,差点还打起来。但后来集中一段维修高峰后,维修单子又少了下去,上面还表扬跑冒滴漏少很多,一工段和机修工段各加一次月奖,可见设备性能好转。
> 再以后遇到维修,他们不能确定要用什么零件,打个内线电话给控制室问宋运辉,一问就清楚。双方关系渐渐变得铁起来。基层有时候很简单,只要拿得出技术,别人就服。
另外Y024 同学还很认真地整理了专栏提到的部分图书:
> 郑老师拍案惊奇书单及简评,最近各大书店有活动,可以借机囤起来了。
> 1.重构
> 作者: Martin Fowler
> [https://book.douban.com/subject/1229923/](https://book.douban.com/subject/1229923/)
> 严格说来,我并没有完整的读完这本书,不过,正如作者自己所说,这样的书原本就不指望能够读完,因为有一大部分其实是参考手册。正是我读过的部分让我知道了重构,让我知道这么做可以把代码写得更好。
> 2.敏捷软件开发
> 作者: Robert C·Martin
> [https://book.douban.com/subject/1140457/](https://book.douban.com/subject/1140457/)
> 这是一本名字赶潮流,内容很丰富的书,这本书让我开始理解软件设计,从此不再刻意追求设计模式。
> 3.测试驱动开发
> 作者: Kent Beck
> [https://book.douban.com/subject/1230036/](https://book.douban.com/subject/1230036/)
> 读的是英文版,因为当时中文版还没有出版,所以,我不敢说,我通过这本书很好的理解了测试驱动开发,但它却为我打开了一扇门,让我知道了一种更好的工作方式。
> 4.修改代码的艺术
> 作者: Michael Feathers
> [https://book.douban.com/subject/2248759/](https://book.douban.com/subject/2248759/)
> 这是一本讲解如何编写测试的书。至于这本书的具体内容我的评价是实用。如果说不足那么这本书缺少一个列表就像Martin Fowler为《重构》所做的那样出什么样的问题应该采用怎样的手法进行处理。
对于如何面对遗留系统, 毅 同学提到:
> 1.了解原系统已实现的功能,没有文档就在心中划分好内部功能模块;
> 2.各模块的边界及关联,对于业务交叉点先思考通信机制;
> 3.看代码,通常是瓶颈优先,业务上是先复杂后简单;
> 4.选定切入点;
> 5.正式改造时先把原有功能抽象出来使用现有实现,改造的过程完成前不会受影响;
> 6.改造完成后切换到新实现进行测试;
> 7.稳定后替换旧实现;
> 8.重复4-7。
Wei 同学对于“T型人”的说法感触很深
> “T型人”这个太说到点了到底是做“专”还是做“广”哪条路线一直是我思考的方向工作上跟大牛工作过给我感觉几乎是全能的我一直都想像他们那样做一个多面手但是如何做广这一直是困扰我的一个问题。
> 我是dev出身但是现实遇到的问题往往跟数据库发布的平台相关这样说下来各种相关领域数据库、k8s、网络协议、DNS 都需要大量时间去积累有时候什么都懂一点反而让自己应该定位什么角色感到迷茫了掌握的水平不足以让自己去应聘DBA、Ops但是只是应聘dev似乎又有点“浪费”跟那些熟悉最新语言/框架的对比起来没特殊竞争力。
> 今天学习“T型人”这个概念让我好好思考了自己到底应该怎么定位。我首先是一个developer这个是根对语言特性的熟练掌握各种best practices例如课程中提到的TDD等应该熟练应用起来然后在这上面拓展学习架构知识多思考对不同系统应该怎么设计老师提到的DDD会认真学习应用再有软件最终还是给用户使用而不是单单提交代码。相关的数据库、k8s、监控运用根据实际遇到的问题再学习解决。
> 最重要的是,在学习区终身学习和工作!
对于如何持续保持竞争力的问题enjoylearning 同学提到:
> 程序员如何保持竞争力很重要在这个年轻人学习能力不断提升的IT行业作为老程序员经验阅历眼光以及技术前沿判断力就显得越来越重要。
> 说起来这个职业是一个需要终身学习的职业,年龄不重要,能力才重要,是不是让自己永远呆在学习区更重要。
对于技术推广desmond 同学的理解也很棒:
> 技术推广,不要先推广最难的部分,先推广能让对方感到最明显好处的部分。取得对方的信任,是友好沟通的基础。
**感谢同学们的精彩留言。我们的专栏更新已经进入尾声阶段,后续我会为大家做一些对整个专栏进行全盘复习的内容,敬请期待。**
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,221 @@
# 总复习 | 重新审视“最佳实践”
你好,我是郑晔。
我承诺的正文内容已经全部交付给你,恭喜你完成了整个专栏的学习!希望通过对这些内容的学习,你已经对“如何做好软件”有了一个全新的认识。
在这个专栏中,我给你讲了很多行业中的最佳实践,比如:测试、持续集成等等,但因为这个专栏叙述方式的关系,一些有关联的实践被放到了不同的模块下讲解。
所以在这一讲中,我们将按照最佳实践的维度重新审视这些内容。我会将这些知识重新串联起来,帮你做一个对专栏的整体复习。
## 产品
做产品很多时候是面向不确定性解决问题。目前这方面最好的实践是“精益创业”。对于精益创业的最简单的理解就是“试”。试也有试的方法精益创业提出了一个“开发build- 测量measure- 认知learning”这样的反馈循环通过这个循环得到经过验证的认知Validated Learning
既然是对不确定产品特性的尝试最好的办法就是低成本地试。在精益创业中最小可行产品MVP就是低成本的试法。最小可行产品就是“刚刚好”满足用户需求的产品。理解这个说法的关键在于用最小的代价尝试可行的路径。
在产品的打磨过程中,可以采用用户测试的方式,直接观察用户对产品的使用。作为程序员,我们要尽可能吃自家的狗粮,即便你做的产品不是给自己使用的产品,也可以努力走近用户。
* **精益创业**
相关阅读:《[06 | 精益创业:产品经理不靠谱,你该怎么办?](http://time.geekbang.org/column/article/76260)》
* **最小可行产品MVP**
相关阅读:《[19 | 如何用最小的代价做产品?](http://time.geekbang.org/column/article/80691)》
* **用户测试、验证产品特性、吃自家狗粮**
相关阅读:《[26 | 作为程序员,你也应该聆听用户声音](http://time.geekbang.org/column/article/84185) 》
## 需求
当我们确定做一个产品功能时,怎么描述需求也是很重要的。产品列表式的需求描述方式最容易出现问题的地方在于,看不清需求的全貌。
用户故事是一个好的需求描述方式:作为一个什么角色,要做什么样的事,以便达成一种怎样的效果。
在用户故事中,验收标准是非常重要的一环。即便不是采用用户故事描述需求,也依然建议先将验收标准定义清楚。
开发团队对需求的理解普遍偏大,基本上都是一个主题。在开发之前,先将需求拆分成小粒度的。衡量一个用户故事拆分是否恰当,一个标准是 INVEST 原则。有了拆分出来的用户故事,就可以进行估算了,估算的过程也是对需求加深理解的过程,过大的用户故事应该再次拆分。
当我们有了拆分之后的需求,就可以对需求进行优先级讨论了。先做重要性高的事,而不是一股脑地去做所有的需求。只有分清了需求的优先级,才能方便地对需求进行管理。
* **用户故事**
相关阅读:《[04 | 接到需求任务,你要先做哪件事?](http://time.geekbang.org/column/article/75100) 》
* **需求的分解与估算**
相关阅读:《[17 | 程序员也可以“砍”需求吗?](http://time.geekbang.org/column/article/79520)》
* **需求管理、优先级**
相关阅读:《[18 | 需求管理:太多人给你安排任务,怎么办?](http://time.geekbang.org/column/article/80428)》
## 持续集成
在开发中,写出代码并不是终点,我们要把代码集成起来。集成要经常做,改动量越小,集成就可以做得越频繁,频繁到每次提交都去集成,这就是持续集成。
持续集成发展到今天已经是一套完整的开发实践。想要做好持续集成,你需要记住持续集成的关键是“快速反馈”。
* 怎样快速得到反馈。
* 怎样反馈是有效的。
持续集成,可以继续延展,将生产部署也纳入其中,这就是持续交付。如果持续交付,再向前一步,就可以同产品验证结合起来。
持续交付的关键点,是在不同的环境验证发布包和自动化部署。不同的环境组成了持续交付的构建流水线,而自动化部署主要是 DevOps 发挥能力的地方。持续交付的发展,让交付物从一个简单的发布包变成了一个拥有完整环境的 Docker 镜像。
持续集成和持续交付可以将诸多的实践贯穿起来单元测试、软件设计、任务分解、主分支开发、DevOps 等等。所以,如果一个公司希望做过程改进,持续集成是一个好的出发点。
* **持续集成发展史**
相关阅读:《[05 | 持续集成:集成本身就应该是写代码的一个环节](http://time.geekbang.org/column/article/75977)》
* **快速反馈**
相关阅读:《[24 | 快速反馈:为什么你们公司总是做不好持续集成?](http://time.geekbang.org/column/article/83461)》
* **持续集成,贯穿诸多实践**
相关阅读:《[答疑解惑 | 持续集成,一条贯穿诸多实践的主线](http://time.geekbang.org/column/article/85049) 》
* **持续交付**
相关阅读:《[32 | 持续交付:有持续集成就够了吗?](http://time.geekbang.org/column/article/87229)》
* **与产品结合:持续验证**
相关阅读:《[答疑解惑 | 持续集成、持续交付,然后呢?](http://time.geekbang.org/column/article/89050) 》
## 测试
测试是一个典型的程序员误区,很多程序员误以为测试只是测试人员的事。理解了软件变更成本,知道了内建质量之后,我们就应该清楚,测试应该体现在全部的开发环节中。这一思想在开发中的体现就是自动化测试。
想要写好自动化测试,需要先理解测试金字塔,不同的测试运行成本不同。为了让软件拥有更多的、覆盖面更广的测试,需要多写单元测试。
编写测试的方式有很多一种实践是测试驱动开发TDD。先写测试然后写代码最后重构这就是 TDD 的节奏:红——绿——重构。测试驱动开发的本质是测试驱动设计,所以,编写可测试的代码是前提。
要想做好 TDD一个重要的前提是任务分解分解到非常小的微操作。学会任务分解是成为优秀程序员的前提条件。
想写好测试需要懂得好测试是什么样子的避免测试的坏味道。好测试有一个衡量标准A-TRIP。
我们不只要写好单元测试还要站在应用的角度写测试这就是验收测试。验收测试现在比较成体系的做法是行为驱动开发BDD它让你可以用业务的语言描述测试。
* **单元测试、自动化测试、蛋卷和冰淇淋模型**
相关阅读:《[12 | 测试也是程序员的事吗?](http://time.geekbang.org/column/article/77917)》
* **测试驱动开发**
相关阅读:《[13 | 先写测试,就是测试驱动开发吗?](http://time.geekbang.org/column/article/78104)》
相关阅读:《[14 | 大师级程序员的工作秘笈](http://time.geekbang.org/column/article/78507) 》
* **测试练习**
相关阅读:《[15 | 一起练习:手把手带你拆任务](http://time.geekbang.org/column/article/78542) 》
* **简单的测试、测试的坏味道、A-TRIP**
相关阅读:《[16 | 为什么你的测试不够好?](http://time.geekbang.org/column/article/79494) 》
* **验收测试、写好验收测试用例**
相关阅读:《[32 | 持续交付:有持续集成就够了吗?](http://time.geekbang.org/column/article/87229)》
* **外部系统测试,用接口隔离**
相关阅读:《[答疑解惑 | 如何在实际工作中推行新观念?](http://time.geekbang.org/column/article/91127) 》
## 编码与设计
编码和设计,是软件开发中最重要的一环。在我看来,编码和设计是一体,想清楚才能写出好代码。很多程序员追求写好代码,却没有一个很好的标准去衡量代码的好坏。结合着软件设计的一些理念,我给你一个编写好代码的进步阶梯,希望你能达到用业务语言编写代码的程度。
用业务语言编写代码需要对软件设计有着良好的理解。提到设计人们的初步印象是“高内聚低耦合”但这是一个太过高度抽象的描述。SOLID 原则是一个更具实践性的指导原则,有了原则做指导,就可以更好地理解设计模式了。
有了基础原则,我们会知道将不同的代码划分开,这样就产生了分层。好的分层可以构建出抽象,而其他人就可以在这个抽象上继续发展。对于程序员来说,构建自己的核心抽象是最关键的一步。
目前构建核心抽象最好的方式是领域驱动设计DDD它将我们思考的起点拉到了业务层面通过战略设计将系统按照不同的上下文划分开来再通过战术设计指导我们有效地设计一个个的领域模型。
但无论怎样做设计,前提是使用适当的技术解决适当的问题,不要把技术用复杂,把团队带入泥潭。
* **业务语言写代码**
相关阅读:《[21 | 你的代码为谁而写?](http://time.geekbang.org/column/article/82581)》
* **架构设计**
相关阅读:《[34 | 你的代码是怎么变混乱的?](http://time.geekbang.org/column/article/87845) 》
* **分层、抽象**
相关阅读:《[35 | 总是在说MVC分层架构但你真的理解分层吗](http://time.geekbang.org/column/article/88309)》
* **业务与技术**
相关阅读:《[36 | 为什么总有人觉得5万块钱可以做一个淘宝](http://time.geekbang.org/column/article/88764) 》
* **微服务**
相关阅读:《[37 | 先做好DDD再谈微服务吧那只是一种部署形式](http://time.geekbang.org/column/article/89049) 》
## 项目准备
从头开始一个项目时一个好的实践就是把一切都准备好。迭代0就是这样一个把迭代准备好的实践从需求到技术做好充分的准备工作再开启项目你会显得从容不迫。在技术方面迭代0最重要的准备工作就是构建脚本它是后续很多工作的基础比如持续集成。
* **迭代0做基础的准备**
相关阅读:《[10 | 迭代0: 启动开发之前,你应该准备什么?](http://time.geekbang.org/column/article/77294)》
* **构建脚本,让项目一开始就自动化**
相关阅读:《[30 | 一个好的项目自动化应该是什么样子的?](http://time.geekbang.org/column/article/86561) 》
## 其余的最佳实践
除了几个花大篇幅介绍的最佳实践,我们还提到了很多不同的最佳实践。
## DoD
完成的定义DoD是一个确保合作各方理解一致的实践。它是一个清单由一个个检查项组成每个检查项都是实际可检查的。有了 DoD做事就只有两种状态完成和未完成。
* **完成的定义DOD**
相关阅读:《[03 | DoD价值你完成了工作为什么他们还不满意](http://time.geekbang.org/column/article/74828)》
## 站会
站会,一种轻量级的会议形式,用来同步每天发生的事情。一般来说,只说三件事:昨天做了什么,今天打算做什么,遇到了什么问题。
* **站会**
相关阅读:《[22 | 轻量级沟通:你总是在开会吗?](http://time.geekbang.org/column/article/82844) 》
## 看板
看板,一种项目管理工具, 将正在进行的工作可视化。通过看板,可以发现团队正在进行工作的很多问题。看板有实体和电子之分,可以根据自己的项目特点进行选择。
* **看板**
相关阅读:《[23 | 可视化:一种更为直观的沟通方式](http://time.geekbang.org/column/article/83082) 》
## 回顾会议
回顾会议,是一种复盘实践,让团队成员对一个周期内发生的事情进行回顾。回顾会议一般分为讲事实、找重点和制定行动项三个部分。但在开始回顾之前,会先进行安全检查,确保每个人都能放心大胆地说真话。
* **回顾会议**
相关阅读:《[25 | 开发中的问题一再出现,应该怎么办?](http://time.geekbang.org/column/article/83841) 》
* **回顾会议中的安全检查**
相关阅读:《[答疑解惑 | 持续集成,一条贯穿诸多实践的主线](http://time.geekbang.org/column/article/85049) 》
## 重构
重构是程序员的基本功把调整代码的动作分解成若干可以单独进行的“重构”小动作一步步完成。重构的前提是识别代码的坏味道。保证代码行为不变需要有测试配合而重构的方向是重构成模式Refactoring to Patterns。重构的过程和编写代码的过程最好结伴而行最佳实践就是测试驱动开发。
* **重构**
相关阅读:《[加餐 | 你真的了解重构吗?](http://time.geekbang.org/column/article/85915)》
* **在测试驱动开发中重构**
相关阅读:《[13 | 先写测试,就是测试驱动开发吗?](http://time.geekbang.org/column/article/78104)》
## 分支开发
分支开发模型,是每个团队都要面临的问题。行业中有两种常见的分支模型,一种是基于主干的开发模型,一种是分支开发模型。分支开发符合直觉,却不是最佳实践。主分支开发模型是与其他实践配合最好的模式,但也需要开发者有着良好的开发习惯。如果并行开发多个功能,可以考虑 Feature Toggle 和 Branch by Abstraction。
* **分支开发**
相关阅读:《[14 | 大师级程序员的工作秘笈](http://time.geekbang.org/column/article/78507) 》
* **Feature Toggle 和 Branch by Abstraction**
相关阅读:《[答疑解惑 | 如何分解一个你不了解的技术任务?](http://time.geekbang.org/column/article/81515) 》
## Fail Fast
Fail Fast 是一个重要的编程原则:遇到问题,尽早报错。不要以构建健壮系统为由,兼容很多奇怪的问题,使得 Bug 得以藏身。
* **Fail Fast**
相关阅读:《[27 | 尽早暴露问题: 为什么被指责的总是你?](http://time.geekbang.org/column/article/84374) 》
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,114 @@
# 总复习 | 重新来“看书”
我们继续复习,在上一讲中,我从最佳实践的角度带领大家回顾了专栏里的一些内容。这一讲,我们换个复习的角度。在专栏进行的过程中,有一些同学注意到我引用了大量的书籍,提出让我把这些书做一个整理。
Wei 同学提到:
> 有一个小建议: 在每一个主题模块的小结中,把文章中提到的书籍做一个书单方便读者。
刘晓林 同学提到:
> 郑老师在专栏中推荐了很多非常好的书籍作为参考,可否考虑在某一期中,将这些参考书籍整理成一个书单,按照专栏的主题做个小分类,然后每本书简单点评两句作为领读内容。希望在专栏的结束语之前可以看到这个书单。
Y024 同学甚至在留言中帮我总结了一个[小清单](http://time.geekbang.org/column/article/90231),而有人也在豆瓣上做出了一个[豆列](http://www.douban.com/doulist/112766085/),罗列了专栏中提到的一些书。
在今天这一讲中,我就站在“看书”的视角,带着你进行一次复习。这些书大多是在我个人成长过程中,给我留下深刻印象的。
我希望你在结束这个专栏学习之后,开启的是另外一段学习历程,用这些书提升自己的水平,夯实自己的基础知识。学习了这个专栏之后,你拥有了一个新的知识结构,再来看这些书就会有一种全新的体验。
此外,在这次的内容中,我会提到几本专栏中没有提到的书,算是给你在学习路上的一个补充。我还制作了一个[豆列](http://www.douban.com/doulist/113366760/),方便你去找到这些书。
## 编码实践
* 如果你想详细学习如何写好代码,我推荐你去读 Robert Martin 的《[代码整洁之道](http://book.douban.com/subject/4199741/)》Clean Code这本书几乎覆盖了如何把代码写好的方方面面。
* 《[实现模式](http://book.douban.com/subject/3324516/)》是一本关于如何写好代码的书,更具体一点是,编写别人能够理解的代码。它的作者 Kent Beck 是许多软件开发实践的开创者。但 Kent Beck 的写作能力一般,他的很多作品被埋没了。只有细细品味,才能体会到 Kent Beck 深厚的功力。
* 我提升自己编码水平的理解是从《[程序设计实践](http://book.douban.com/subject/1173548/)》The Practice of Programming这本书开始的这本书的作者是 Brian Kernighan 和 Rob Pike这两个人都出身于大名鼎鼎的贝尔实验室参与过 Unix 的开发。
* 如果你想从日常开发中提升自己的效率,可以读一下《[卓有成效的程序员](http://book.douban.com/subject/3558788/)》。假如你不曾思考过这个问题,这本书会让看到一些不同的工作方式,我也给这本书写过一篇[书评](http://book.douban.com/review/1517237/)。不过,这本书里的技巧太具体了,所以,有一些已经有些过时了。
## 设计
* SOLID 原则是一种面向对象软件设计原则。早在1995年Robert Martin 就提出了这些[设计原则的雏形](http://groups.google.com/d/topic/comp.object/WICPDcXAMG8?hl=en),然后在他的《[敏捷软件开发:原则、实践与模式](http://book.douban.com/subject/1140457/)》这本书中,比较完整地阐述了这五个原则,后来,他有把这些原则进一步整理,成了今天的 “SOLID”。有了设计原则做基础这本书后面讲了设计模式理解起来就容易多了。虽然书名是关于敏捷的但这是一本讲设计的书。
* 设计和架构有什么区别2017年Robert Martin 出版了《[架构整洁之道](http://book.douban.com/subject/30333919/)》Clean Architecture他在其中告诉我们二者没有区别。所以这也是一本关于设计的书给出了 Robert Martin 对设计的最新理解。你可以把它看成《[敏捷软件开发:原则、实践与模式](http://book.douban.com/subject/1140457/)》的修订版。
* 《[设计模式](http://book.douban.com/subject/1052241/)》**不推荐阅读**,它是设计模式的开山之作,但它的起点是 Erich Gamma 的博士论文,其写作风格偏向学术,而且中文版翻译得也很一般。这里将它罗列出来只是因为其历史重要性。如果你想学习设计模式,现在有一些更容易入门的书,比如《[Head First 设计模式](http://book.douban.com/subject/2243615/)》。
* Martin Fowler 的《[企业应用架构模式](http://book.douban.com/subject/1230559/)》将软件开发当时常见的解决方案汇集成模式,今天看来很多模式已经习以为常,但当年出场可是技惊四座的。从这本书的名字你不难看出,它出版的年代是企业级开发盛行的年代。[Martin Fowler 一直认为这本书没有写完](http://www.martinfowler.com/eaaDev/),希望能够继续更新,但不知道何时能看到这本书的新版。
* 《[Unix 编程艺术](http://book.douban.com/subject/1467587/)》也是一本讲软件设计的书,只不过,它选择的切入点是 Unix 中的设计,从中你可以学到“只做一件事,把它做好”、“文本化”等编程理念,有助于你改善日常的工作。这样的书,也就只有 Eric Raymond 这样沉浸编程几十年的人才能写出来。
## 工程实践
* Kent Beck 有一本知名的软件工程之作《[解析极限编程](http://book.douban.com/subject/6828074/)》Extreme Programming Explained它介绍了一种软件开发方法极限编程。但更重要的是今天很多主流的软件开发最佳实践都是从这里出来的。这本书可以理解成诸多最佳工程实践的总纲。
* 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
* 《[测试驱动开发](http://book.douban.com/subject/1230036/)》是 Kent Beck 为世人展示 TDD 做法的一本书。它好的地方需要自己体会Kent Beck 并没有显式的讲出来,比如:任务分解。
* Jez Humble 和 Dave Farley 的《[持续交付](http://book.douban.com/subject/6862062/)》Continuous Delivery让持续集成再进一步将生产环境纳入了考量。乔梁他是《持续交付》这本书的中文版译者而且在这本书出版近十年后他自己写了《[持续交付 2.0](http://book.douban.com/subject/30419555/)》,把自己多年来关于持续交付的新理解整理了进去。
* 说到遗留代码和测试我推荐一本经典的书Michael Feathers 的《[修改代码的艺术](http://book.douban.com/subject/2248759/)》Working Effectively with Legacy Code从它的英文名中你就不难发现它就是一本关于遗留代码的书。如果你打算处理遗留代码也建议你读读这本书。这本书我也写过[书评](http://book.douban.com/review/1226942/),你可以了解一下我对它看法。
## 领域驱动设计
* 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 的首选。
## 产品与需求
* 精益创业是 Eric Ries 最早总结出来的。他在很多地方分享他的理念不断提炼最终在2011年写成一本同名的书《[精益创业](http://book.douban.com/subject/10945606/)》。如果说精益创业是理论,《[精益创业实战](http://book.douban.com/subject/20505765/)》这本书则给了你一个操作流程。
* Mike Cohn 是敏捷理念的一个重要传播者,我们在讲测试金字塔时,提到了他的著作《[Scrum敏捷软件开发](http://book.douban.com/subject/5334585/)》Succeeding with Agile。敏捷开发有两大流派一派是工程实践另一派是管理实践。如果你对 Scrum 这类管理实践感兴趣,可以读一下这本书。
* 如果你对用户故事这个话题感兴趣,推荐阅读 Mike Cohn 的两本书《[用户故事与敏捷方法](http://book.douban.com/subject/4743056/)》User Stories Applied和《[敏捷软件开发实践 估算与计划](http://book.douban.com/subject/26811747/)》Agile Estimating and Planning
## 开发文化
* 软件行业里有一本名著叫《[人月神话](http://book.douban.com/subject/1102259/)》这算是软件开发领域第一本反思之作。今天我们讨论的很多词汇都出自这本书比如没有银弹、焦油坑等等。虽然这本书出版于1975年但其中提到的问题依然困扰着今天的程序员。
* 开源概念的提出者 Eric Raymond他的《[大教堂与集市](http://book.douban.com/subject/25881855/)》推开了开源大门。今天开源软件已经成为程序员日常工作的一部分,但如果没有 Eric Raymond 这些人的努力,我们还必须与复杂的企业级软件搏斗。了解一下开源的历程,可以帮助你更好地理解今天的幸福。
* 程序员应该如何做Robert Martin 也写了一本书《[程序员的职业素养](http://book.douban.com/subject/11614538/)》Clean Coder其中对大多数程序员最重要的一点建议是说“不”。
## 软件开发拾遗
* 高德纳的《[计算机程序设计艺术](http://book.douban.com/subject/26681685/)》肯定是一套程序员都知道,但没几个人读完的书。算法的讲解经过几十年已经有了很好的发展,如果学算法,肯定有更好的选择。如果你想看图灵奖获得者如何从根上思考问题,不妨找来这套书来翻翻。
* 《[快速软件开发](http://book.douban.com/subject/3151486/)》Rapid Development**不推荐阅读**。在这本书中作者首次提出了解决集成问题的优秀实践Daily Build每日构建。通过这个名字我们便不难看出它的集成策略即每天集成一次。它其中很多实践在当时是先进的但今天看来有些落伍了。如果你只想从中收获一些理念性的东西可以去读读。
* 《[C 程序设计语言](http://book.douban.com/subject/1139336/)》、《[Unix 编程环境](http://book.douban.com/subject/1033144/)》等出自贝尔实验室大师级程序员之手,他们的书都值得一读,其中的内容今天看来可能有些过时,但他们解决问题的方式和手法却值得慢慢品味。
* 我在讲淘宝技术变迁时,提到了《[淘宝技术这十年](http://book.douban.com/subject/24335672/)》,这本书算不上经典,但可以当做休闲读物。
## 技术之外
* 管理大师彼得·德鲁克有一本经典著作《[卓有成效的管理者](http://book.douban.com/subject/1322025/)》,虽然标题上带着管理者几个字,但在我看来,这是一本告诉我们如何工作的书,每个人都可以读一下。
* 尤瓦尔·赫拉利的《[人类简史](http://book.douban.com/subject/25985021/)》或《[未来简史](http://book.douban.com/subject/26943161/)》,是我第一次学到“大历史观”这个说法,历史不再是一个个单独的历史事件,而是一个有内在逻辑的发展脉络。
* 《[从一到无穷大](http://book.douban.com/subject/1102715/)》是一本著名科普著作它向我们介绍了20世纪以来的科学进展。作者乔治·伽莫夫既是热宇宙大爆炸模型的提出者也是生物学上最早提出“遗传密码”模型的人。虽然这本书出版自1947年但以现在社会的整体科学素养还是有必要读读这本书的。
* 史蒂芬·柯维Stephen Richards Covey的《[高效能人士的七个习惯](http://book.douban.com/subject/26284789/)》,其中的理念我在专栏两个不同的地方提到过,一个是讲以终为始时,那段关于智力创造的论述,另一个是讲优先级时提到的艾森豪威尔矩阵。这本书值得每个人阅读,很多程序员欠缺的就是这些观念性的东西。
* 很多程序员都是科幻小说迷,编程和科幻,这两个都是需要想象力的领域。刘慈欣的《[三体](http://book.douban.com/subject/6518605/)》,不说它给 IT 行业带来的丰富的词汇表吧,作为科幻小说来说,它就是一流的,值得阅读。它会让你仰望星空,打开思维。如果你对科幻小说有兴趣,推荐阅读阿西莫夫的《[银河帝国](http://book.douban.com/subject/26389895/)》系列这是科幻小说界的扛鼎之作你会看到一部出版于1942年的书里就有大数据的身影。
* 对于程序员来说,最好的工作状态就是进入心流,它会让你忘我工作。如果你对心流的概念感兴趣,可以去读米哈里·契克森米哈赖的著作《[心流](http://book.douban.com/subject/27186106/)》,这位作者就是心流概念的提出者。
好,今天的复习就到这里,你有哪些经典的书可以推荐给这个专栏的同学呢?欢迎在留言区写下分享你的想法。
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@ -0,0 +1,99 @@
# 结束语 | 少做事,才能更有效地工作
你好,我是郑晔。
在这个专栏里,我讲过很多东西,几乎涉及到软件开发的方方面面,但有一个重要的方面,我却从来没有说过,那就是算法。
因为我一直把它当做不言而喻的基本功,认为每个程序员都应该掌握。在我们专栏的结束语中,我就用这个没有涉及过的话题来开篇吧!
## 算法的差异
排序算法是每个程序员都会学到的内容,大家对各种算法也是如数家珍:插入排序、冒泡排序、归并排序、堆排序、快速排序等等。我们也知道各个算法的复杂度,比如,插入排序是 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%代码的程序员。只要能做到有效工作,效率自然会高出业界平均水平很多。
怎么才能有效工作呢?我在专栏中已经给你讲了很多,小结一下就是:
* 拓展自己的上下文,看到真正的目标,更好地对准靶子,比如,多了解用户,才不至于做错了方向;站在公司的层面上,才知道哪个任务优先级更高;站在行业的角度,而不局限于只在公司内成为高手,等等。
* 去掉不必要的内容,减少浪费,比如,花时间分析需求,不做非必要的功能;花时间做好领域设计,别围着特定技术打转;花时间做好自动化,把精力集中在编码上,等等。
要想有效工作,有两点非常重要。一方面,意识上要注意自己工作中无效的部分。这就像一个开关,拨过去就好了。所以,读这个专栏,有人常有恍然大悟的感觉,也有人觉得很简单。
很多时候,你只是不知道,就像我在专栏中提到,要问产品经理问题,这是很多人没想过的。每篇文章后面的那一句总结,就是这样的开关,拨过去就好。
另一方面,要构建自己关于软件开发的知识体系,这是要花时间积累的。在这个专栏中,我给你讲了很多最佳实践,就是让你知道,在某些方面,有人已经做得很好了,花时间学习,比自己从头摸索好很多。
这就像所有的数学公式一样,理论上你都可以自行推导,但肯定不如从教科书上学得快。
## 藏经阁目录
虽然我讲了这么多内容,但实际上,因为篇幅的关系,这只是冰山一角。其实,我给你讲的这部分内容并不是具体的知识,而是告诉了你哪些东西要去学习,给了你一张学习地图,把各种知识贯串了起来。
我曾与朋友打趣道,我的专栏实际上是藏经阁的目录,真正的经书还要等你自己去参悟。只不过,有一个人把这些经书之间的知识连接给你补齐了。这些连接恰恰是在学习相关内容时,让我苦思冥想许久的。
大约一年前2018年4月极客时间编辑找到我问我是否有兴趣在极客时间开个专栏作为“得到”重度用户的我一直对知识服务很感兴趣。有这样的机会让我体验我当然想试试甚至最初给自己定下了写100篇的宏伟计划。
真正开始写我才知道在繁忙的日常工作之余坚持写作还是一件很有挑战的事今天看来100篇的目标显得那么无知无畏。
不过也正是因为压缩到一半左右的篇幅在专栏后面的部分我才极大地提高了知识密度比如微服务和DDD这两个可以分别写成一个系列内容的话题我用一篇文章就将其精华和知识脉络提炼呈现了出来。
因为我想尽我所能,帮助大家构建起一个软件开发的知识体系,让你在未来遇到问题时,知道可以在哪个方面进一步加强。希望这个专栏真的起到帮你理清思路,答疑解惑的作用。
还记得我在开篇词中的最后一段话吗?
> 也许在这个专栏的最后,你发现自己并不认同我的原则,却能够用自己的原则来与我探讨,那么,恭喜你,因为那是最美妙的事情!
不知道你是否形成了自己的原则呢?欢迎与大家分享。因为它代表着你已经形成了自己的知识体系。与我讲了些什么相比,你学到了什么才是一件更重要的事。
希望在学习了这个专栏之后,你可以用自己的工作原则做更多本质复杂度的事情,减少无意义的时间消耗。
其实,这个专栏的最大收益人是我自己,感谢这次的专栏之旅,我终于强行治疗了我的拖延症,把自己对于有效工作的思考完整地整理了出来,那些在脑子里模糊的印象现在终于有了一个完整的体系。这个体系就是我在专栏里提到的工作原则,现在我可以更好地表达自己的想法了。
不过,这个专栏对我而言也是有遗憾的。因为我想表达的内容很多,给大家打开更多大门的同时,也给很多同学留下了更多的疑问。
有些同学期待在某个方面再深入细节地讲一下比如DDD那可是值得再写一个专栏的主题。限于这个专栏的主题和篇幅关系我没办法深入展开只能对大家说声抱歉了。
如果以后有机会我会再来与你分享我对软件开发的理解这次的《10x程序员工作法》之旅就暂告一段落了
再见!
[![](https://static001.geekbang.org/resource/image/a8/1a/a89ba340ba096c375c1b84403c78121a.jpg)](http://jinshuju.net/f/4b8yu2)

View File

@ -0,0 +1,89 @@
# A/B测试从0到1
## 你将获得
* 互联网人必懂的A/B测试方法论
* 手把手带你构建A/B测试流程体系
* A/B测试常见使用场景及误区
* A/B测试面试真题详解
## 讲师介绍
张博伟目前在硅谷大厂FLAG任资深数据科学家7年+数据科学从业经验。
擅长A/B测试在增长方面的应用与工程、营销和产品团队合作通过A/B测试已累积为公司带来百万级用户增长作为数据科学的接头人和工程团队一起改进提升公司内部的A/B测试平台。目前在FLAG作为数据科学Lead参与制定千万级用户产品的增长计划。
博伟老师还是热心的传道解惑者。在FLAG任职期间已为数据分析、营销和产品团队提供数十场A/B测试的讲座和上百次的咨询给他们讲解A/B测试的最佳实践以及避坑经验。
## 课程介绍
在大数据时代,业务的科学增长要靠数据来驱动。但是有很多人认为,数据驱动不就是做几次数据分析、产生一些报表嘛?当然不是了。要把数据真正用在公司/团队的业务决策流程中A/B测试是非常关键的一环。
但A/B测试是一种实践性非常强的方法。其实A/B测试的原理并不难难的就是在面对千变万化的业务场景和数据时该怎么灵活处理。毕竟在A/B测试都实施过程中有太多琐碎的环节也存在着太多的误区。
* 一个科学、规范的做A/B测试的框架是什么样的
* 选取样本量时,真的是越多越好吗?
* 复杂业务场景下,怎么选取评价指标呢?
* 测试结果在预计时间之前达到了统计显著,实验是不是提前成功了呢?
很多人被这样的问题所困扰这也是为什么我们经常会看到这样一个怪现象如果没有扎实的统计学基础那么肯定做不好A/B测试可即使掌握了理论基础在实施A/B测试中还是会遇到各种数据问题或者工程Bug。要是一不小心哪怕忽视了很小的一个点实验结果就会变得不准确所有的功夫就都白费了。
所以我们特意邀请了美国互联网大厂FLAG的资深数据科学家张博伟从框架搭建和实战解析两个层面帮助你学会使用A/B测试以及用好A/B测试。
![](https://static001.geekbang.org/resource/image/ee/51/ee45a1a23eb8cf03ea2dfb818f04e751.png)
老师将结合自己多年参与、主导A/B测试的经验依据大量实践案例从0到1帮你建立起一个科学、规范的做A/B测试的流程。除此之外他还会梳理、总结出一些方法论分享一些避坑经验让你真正用A/B测试做好数据决策让业务增长不止翻倍
## 模块设计
课程主体包括三大模块。
**统计篇带你快速掌握A/B测试的统计学基础**
这个模块精选了做A/B测试必须掌握的两大块统计学知识包括A/B测试的理论基础假设检验、A/B测试的前提指标的统计属性带你有针对性地、快速掌握A/B测试的理论知识。
**基础篇从0到1建立规范的A/B测试流程**
这个模块将会按照做A/B测试的5个关键步骤来展开也就是确定目标和假设、确定指标、选取实验单位、计算所需样本大小以及分析测试结果。在讲解流程的同时告诉你背后的原理帮助你在实际应用时能举一反三。
**进阶篇带你绕过A/B测试实践中的坑**
这个模块主要针对实际业务场景中潜在的坑包括测试结果不显著怎么改善、A/B测试是不是万能的、多重检验等问题还会为你梳理面试中常考的知识点。
此外,这个模块还会带你实战制作一个实用的样本量计算器,来解决网上工具参差不齐、适用范围有限等问题。
## 课程目录
![](https://static001.geekbang.org/resource/image/76/98/76edf45889f7630f030d412a14a89f98.png)
## 特别放送
#### 免费领取福利
[![](https://static001.geekbang.org/resource/image/69/dc/69c52d08278a2164dc5b061ba342a5dc.jpg?wh=960x301)](https://time.geekbang.org/article/427012)
#### 限时活动推荐
[![](https://static001.geekbang.org/resource/image/67/a0/6720f5d50b4b38abbf867facdef728a0.png?wh=1035x360)](https://shop18793264.m.youzan.com/wscgoods/detail/2fmoej9krasag5p?dc_ps=2913145716543073286.200001)
## 订阅须知
1. 订阅成功后推荐通过“极客时间”App端、Web端学习。
2. 本专栏为虚拟商品,交付形式为图文+音频,一经订阅,概不退款。
3. 订阅后分享海报,每邀一位好友订阅有现金返现。
4. 戳此[先充值再购课更划算](https://shop18793264.m.youzan.com/wscgoods/detail/2fmoej9krasag5p?scan=1&activity=none&from=kdt&qr=directgoods_1541158976&shopAutoEnter=1),还有最新课表、超值赠品福利。
5. 企业采购推荐使用“[极客时间企业版](https://b.geekbang.org/?utm_source=geektime&utm_medium=columnintro&utm_campaign=newregister&gk_source=2021020901_gkcolumnintro_newregister)”便捷安排员工学习计划,掌握团队学习仪表盘。
6. 戳此[申请学生认证](https://promo.geekbang.org/activity/student-certificate?utm_source=geektime&utm_medium=caidanlan1)订阅课程享受原价5折优惠。
7. 价格说明:划线价、订阅价为商品或服务的参考价,并非原价,该价格仅供参考。未划线价格为商品或服务的实时标价,具体成交价格根据商品或服务参加优惠活动,或使用优惠券、礼券、赠币等不同情形发生变化,最终实际成交价格以订单结算页价格为准。

View File

@ -0,0 +1,23 @@
# SUMMARY
* [简介](./README.md)
* [开篇词用好A/B测试你得这么学](./docs/316179.md)
* [01 | 统计基础(上):系统掌握指标的统计属性](./docs/316218.md)
* [02统计基础深入理解A/B测试中的假设检验](./docs/318454.md)
* [导读 | 科学、规范的A/B测试流程是什么样的](./docs/318459.md)
* [03确定目标和假设好的目标和假设是什么](./docs/318478.md)
* [04确定指标指标这么多到底如何来选择](./docs/321007.md)
* [05选取实验单位什么样的实验单位是合适的](./docs/321734.md)
* [06 | 选择实验样本量:样本量越多越好吗?](./docs/322706.md)
* [07 分析测试结果:你得到的测试结果真的靠谱吗?](./docs/323696.md)
* [08 | 案例串讲从0开始搭建一个规范的A/B测试框架](./docs/324297.md)
* [09 |测试结果不显著,要怎么改善?](./docs/325280.md)
* [10常见误区及解决方法多重检验问题和学习效应](./docs/326479.md)
* [11 | 常见误区及解决方法(下):辛普森悖论和实验组/对照组的独立性](./docs/326826.md)
* [12什么情况下不适合做A/B测试](./docs/328267.md)
* [13融会贯通A/B测试面试必知必会](./docs/328989.md)
* [14举一反三A/B测试面试必知必会](./docs/329673.md)
* [15用R/Shiny教你制作一个样本量计算器](./docs/330981.md)
* [结束语|实践是检验真理的唯一标准](./docs/331418.md)
* [结课测试题这些A/B测试的知识你都掌握了吗](./docs/332833.md)
* [加餐|试验意识改变决策模式,推动业务增长](./docs/333710.md)

View File

@ -0,0 +1,4 @@
{
"title": "A/B测试从0到1",
"language": "zh"
}

View File

@ -0,0 +1,128 @@
# 开篇词用好A/B测试你得这么学
你好我是博伟。欢迎和我一起学习A/B测试。
可能你对我还不是很熟悉我先来做个自我介绍。我目前呢在美国的互联网大厂FLAG工作是一名资深数据科学家。在过去的7年多时间里我一直在做A/B测试、机器学习建模、大数据分析的相关工作。
在从事A/B测试的经历中我参与到从设计测试、实施测试到最后分析测试结果给出业务指导的全过程后来逐步在团队中主导A/B测试领域的相关工作开发与A/B测试相关的数据产品还和工程团队合作来改进提升内部的A/B测试平台通过持续的A/B测试为公司的新业务带来上百万用户的增长。经过多年的经验积累现在也为数据分析、营销和产品团队提供数十场A/B测试的讲座和上百次的咨询给他们讲解A/B测试的最佳实践以及避坑经验。
在我多年的数据分析实践中,我越来越觉得,**A/B测试是促进业务持续增长的最实用、最有效的方式。**
不过我也发现在这些不同的数据分析方法中A/B测试也是最容易用错的方法。
究其原因是因为A/B测试是一种实践性很强的方法学校的教学中往往没有相关的课程。你可能会在统计课上学到过它的理论基础——假设检验但是还是太过理论不知道该怎么应用。A/B测试的难点就在于如果你只有理论基础而没有实践经验那么实践过程由于业务场景千变万化可能就会有各种各样潜在的陷阱在等着你。只有兼顾了理论基础和实践经验才能得出值得信赖的测试结果。
也因此,我非常希望能够系统地梳理和总结下自己在硅谷成熟科技公司学到的知识经验,并分享出来。在你即将学习的**这门课程中我会先带你建立起一个做A/B测试的框架让你在应对不同业务场景时都能通过框架来按图索骥灵活运用。**
不过在讲具体的学习方法之前我想先和你聊一聊A/B测试到底可以帮我们解决什么问题
## 为什么想要获得持续的业务增长就必须学习A/B测试
在大数据时代,每个公司都在说数据驱动产品和业务的快速迭代,这当然没有错。但是,有很多人都认为,数据驱动就是做几次数据分析,产生一些报表,并没有把数据放在公司的业务决策流程中。
这是一个非常严重的误区。
**多年的专业经验告诉我看一个公司或者团队是不是真正做到了数据驱动就要看它的决策流程中有没有A/B测试这一环节。**
为什么这么说呢,我们先来了解下决策流程,也就是产品/业务迭代的流程。
![](https://static001.geekbang.org/resource/image/18/d1/188586f14d066d8c9bb1b322316f84d1.jpg)
你可以看到,产品/业务迭代的流程大概分为3步
1. 具体的业务问题催生出迭代的想法,比如出现业务问题后,团队会提出具体的迭代方案;
2. 团队论证方案的可行性和效果;
3. 论证完成后,具体实施迭代方案。
很明显,只要论证环节结束了,就要开始进行迭代了。所以,做好充分而正确的论证,就是至关重要的环节。
这也很容易理解,你想,如果刚刚有了迭代的想法,不去论证就直接实施,就很难达到预期,甚至会产生负面效果。
这就好比一个刚刚研制成功的药品,不经过临床实验就直接推入市场,去治疗病人,那承担的风险是非常高的。因为这样不仅可能无法治愈病人,甚至还可能会产生严重的副作用。这么一想,你是不是就体会到论证的重要性了?
而A/B测试就是保证这个关键环节不出现问题的最佳方案。因为它不仅可以让我们清楚地知道产品/迭代方案到底有没有效果,能产生多大效果,还可以在结果不如预期时,快刀斩乱麻,有理有据地放弃这个想法。
这样既能大大节省公司的成本,又能加快想法迭代的速度。如果在花费了大量时间和资源实施想法后,还收不到预期的效果,那就得不偿失了。
所以,**只有在决策流程中加入A/B测试这个环节根据值得信赖的测试结果而不是所谓的经验来做业务和产品决策时才是真正的数据驱动决策。**
这其实也是所有公司都会面临一个问题业务增长从来都不是一步到位的那么如何保持业务的持续增长呢A/B测试在提升业务和产品迭代上真的很管用能持续带来营收和用户的增长。
无论是美国硅谷的FLAG还是中国的BAT每年都会进行成千上万次的线上A/B测试参与测试的用户超百万事实上大部分用户是在不知情的情况下被参与的。即使是一些初创公司或者是像沃尔玛、美国航空这样的传统企业也会通过小规模的A/B测试来优化提升业务。
以必应Bing搜索为例A/B测试每个月都能帮助他们发掘数十个提升收益的方法每个搜索的收益一年可以提升10%~25%这些AB测试带来的改进和其他提升用户满意度的努力是必应搜索的盈利提升以及其美国市场份额从2009年刚成立时的8%上升到2017年的23%的主要原因。
讲到这里你可能会比较好奇这些公司用A/B测试来解决什么具体的业务问题呢你看下面我给你总结的表格就了解啦。
![](https://static001.geekbang.org/resource/image/13/df/13059daa26b391d55e4dc357124b51df.png)
正因为发现了A/B测试在产品迭代、算法优化、市场营销等领域的巨大作用越来越多的公司开始使用A/B测试这方面的人才的需求量也越来越大。无论是偏技术的数据科学家、数据分析师还是偏业务和产品的市场营销分析师、产品经理以及增长黑客都需要在工作中掌握和应用A/B测试。而且从我多年做面试官的经验来看A/B测试也是这些职位面试中必考的一块内容重要程度可想而知。
看到这里你可能已经非常想要学习A/B测试了先别着急。我发现很多人对A/B测试是既熟悉又陌生。
说熟悉是因为A/B测试的基本概念很好理解它就是指科学中的控制变量实验。说陌生是因为A/B测试涉及到千变万化的业务场景、不同的数据以及在实施过程中的多种琐碎环节也存在着太多的误区。
## 理解A/B测试的原理很简单想用好却很难
为什么这么说呢?我们直接看几个真实的案例吧。
我经常和营销、产品团队一起合作A/B测试他们一般会提出一些A/B测试的想法比如想要提升某款App的推送效果希望能通过改变推送中的不同因素来提升推送的点击率。
刚开始他们的很多想法完全不适合A/B测试。比如说实验组和对照组相比他们会想到同时改变推送的标题和内容或者同时改变推送的内容和时间等等这就违反了控制变量实验中**实验组和对照组只能有一个因素不同**的原则。因为当我们同时变化多个因素时,即使最后得到了显著的测试结果,也没有办法确定到底是哪个因素造成的。
这就是基础不扎实导致的。这是一个非常严重的问题,因为不清楚原理,就很容易在设计实验和分析实验结果中采取错误的方法。
你可能会问那我掌握好理论基础是不是做A/B测试就没问题啦
当然不是。A/B测试是一种实践性很强的方法。你可能会在统计课上学到过它的理论基础——假设检验但是怎么在实际业务场景中应用呢这就是学习A/B测试的难点。
**理论上的东西是死的但A/B测试的应用场景和相关的数据却是千变万化的**在实施A/B测试中会遇到各种各样的数据问题或者工程Bug要是一不小心哪怕忽视了很小的一个点就会有各种各样的陷阱在等着你实验结果就会变得不准确之前的所有功夫就白费了。
我再跟你分享一个小例子。
某个专门测试App推送的平台有一类流程是比较发推送有没有效果。对照组不发推送实验组发推送。
在正式发送前,该平台还会做一个过滤,过滤掉那些不符合推送的用户,比如用户是未成年人,或者用户手机设备太旧不支持推送,等等。但是由于只有实验组会发推送,对照组并不会发推送,所以平台只在实验组实施了过滤机制:
![](https://static001.geekbang.org/resource/image/7e/c6/7e6b84b5ed0a2026f83152d6b2d316c6.jpg)
但是,仔细想想,这个流程会使实验组和对照组有两个不同:有无推送和有无过滤。第一个不同是在实验设计中,但是第二个不同就纯粹是流程中加进来的,是偏差,会造成实验结果的不准确。
正确的流程如下图。对照组即使最后不发推送,也要经过和实验组同样的过滤,这样才能保证实验的准确性:
![](https://static001.geekbang.org/resource/image/5d/b2/5dbeebaa2a292826df470106aa8f9ab2.jpeg)
你看这么一个细小的问题就可能会导致整个A/B测试失败。
## 这门课程是如何设计的?
所以为了让你快速且扎实地掌握A/B测试这门手艺我结合我的从业经验从统计原理、基本流程和进阶实战三个层面为你梳理出了一条学习A/B测试的最佳路径。
![](https://static001.geekbang.org/resource/image/ee/51/ee45a1a23eb8cf03ea2dfb818f04e751.png)
第一模块是“统计篇”。
想要做好A/B测试统计原理的学习肯定是不能漏掉的。统计学知识纷繁复杂但做A/B测试其实不需要掌握全部。所以**我精选了与A/B测试密切相关的统计理论主要讲解A/B测试的理论基础-假设检验以及A/B测试指标的统计属性这两块知识**让你有针对性地学习理论知识真正打好做A/B测试的理论基础。
即使你没有很好的统计学基础也可以在这个模块快速掌握A/B测试的统计学基础完全不用担心。
第二模块是“基础篇”。
在这个模块我梳理了做A/B测试的几个关键步骤包括确定目标和假设、确定指标、选取实验单位、计算所需样本大小以及分析测试结果。我会在讲解流程的同时也告诉你背后的原理帮助你在实际应用时能举一反三。
第三模块是“进阶篇”。
想要让做A/B测试的技能更上一层楼掌握了关键流程还不够。你还需要能够识别那些在实际业务场景中潜在的坑并且要有相应的解决方法。
除此之外你应该知道A/B测试并不是万能的所以我会专门拿出一节课来给你讲解A/B测试的适用范围及替代方法。
如果你是想面试A/B测试相关职位呢也不用担心我会花两节课带你掌握面试中的常见考点及应对方法。
最后,我还会通过实战,带你亲自制作一个实用的样本量计算器,来解决网上工具参差不齐、适用范围有限等问题。
A/B测试其实并不难因为它并不需要你掌握非常高深的计算机算法或者高等数学所以说理解基本的统计知识就足够了。不过想要把A/B测试做好肯定是有难度的。它的难度就在于如果不遵循科学化的流程那么在实践过程中就可能会出现各种状况和问题。
所以我也希望你能在学习这门课程的时候边学边实践在实践中学习、总结前人的经验把A/B测试慢慢变成你在工作中的一项核心竞争力。
最后,今天是开篇,你可以在评论区写下你对这门课的期待,或者你的学习计划,让我们一起见证彼此的成长吧!

View File

@ -0,0 +1,196 @@
# 01 | 统计基础(上):系统掌握指标的统计属性
你好,我是博伟。
在学习、解决技术问题的时候我们都知道有这么一句话“知其然知其所以然”。那么A/B测试的“所以然”是什么呢在我看来就是A/B测试背后的计算原理知道A/B测试为什么要这么设计最佳实践中为什么要选择这样的指标、那样的检验方法。
那说到A/B测试背后的计算原理我们首先得知道A/B测试的理论基础是假设检验Hypothesis Testing。可以说假设检验贯穿了A/B测试从实验设计到分析测试结果的整个流程。
如果要一句话解释“假设检验”的话就是选取一种合适的检验方法去验证在A/B测试中我们提出的假设是否正确。现在你只要知道“假设检验”中最重要也最核心的是“检验”就可以了因为选取哪种检验方法取决于指标的统计属性。
也就是说理解指标的统计属性是我们掌握假设检验和A/B测试的前提也是“知其所以然”的第一步。
而至于深入理解并用好“假设检验”的任务,我们就留着下一讲去完成吧。
## 指标的统计属性,指的是什么?
在实际业务中,我们常用的指标其实就是两类:
* **均值类的指标**,比如用户的平均使用时长、平均购买金额、平均购买频率,等等。
* **概率类的指标**,比如用户点击的概率(点击率)、转化的概率(转化率)、购买的概率(购买率),等等。
很明显这些指标都是用来表征用户行为的。而用户的行为是非常随机的这也就意味着这些指标是由一系列随机事件组成的变量也就是统计学中的随机变量Random Variable
“随机”就代表着可以取不同的数值。比如一款社交App每天的使用时间对轻度用户来说可能不到1小时而对重度用户来说可能是4、5小时以上。那么问题来了在统计学中怎么表征呢
没错,我们可以用**概率分布Probability Distribution**来表征随机变量取不同值的概率和范围。所以A/B测试指标的统计属性其实就是要看这些指标到底服从什么概率分布。
在这里,我可以先告诉你结论:**在数量足够大时,均值类指标服从正态分布;概率类指标本质上服从二项分布,但当数量足够大时,也服从正态分布。**
看到这两个结论你可能会有很多问题:
* 什么是正态分布?什么是二项分布?
* “数量足够大”具体是需要多大的数量?
* 概率类指标,为什么可以既服从二项分布又服从正态分布?
不要着急,我这就来一一为你解答。
## 正态分布Normal Distribution
正态分布是A/B测试的指标中最主要的分布是计算样本量大小和分析测试结果的前提。
在统计上如果一个随机变量x的概率密度函数Probability Density Function
$$
f(x)=\\frac{1}{\\sigma \\sqrt{2 \\pi}} e^{-\\frac{1}{2}\\left(\\frac{x-\\mu}{\\sigma}\\right)^{2}}
$$
$$
\\begin{aligned}
\\mu &=\\frac{x\_{1}+x\_{2}+\\cdots+x\_{n}}{n} \\\\\\
\\sigma &=\\sqrt{\\frac{\\sum\_{i}^{n}\\left(x\_{i}-\\mu\\right)^{2}}{n}}
\\end{aligned}
$$
那么x就服从正态分布。
其中 μ为x的平均值Meanσ为x的**标准差**Standard Deviationn为随机变量x的个数xi为第i个x的值。
随机变量x服从正态分布时的直方图Histogram如下
![](https://static001.geekbang.org/resource/image/6e/20/6e074f64708841dcf94dd81bf95d7520.png)
直方图是表征随机变量分布的图表其中横轴为x可能的取值纵轴为每个值出现的概率。通过直方图你可以看到**距离平均值μ越近的值出现的概率越高。**
除了平均值μ,你还能在直方图和概率密度函数中看到另一个非常重要的参数:**标准差σ**。σ通过计算每个随机变量的值和平均值μ的差值,来表征随机变量的离散程度(偏离平均值的程度)。
接下来,我们就来看看标准差σ是怎么影响随机变量的分布的。
为了方便理解我们用Python做一个简单的模拟选取服从正态分布的随机变量x其平均值μ=0分别把x的标准差σ设置为1.0、2.0、3.0、4.0 然后分别做出直方图。对应的Python代码和直方图如下
```
from scipy.stats import norm
import numpy as np
import matplotlib.pyplot as plt
## 构建图表
fig, ax = plt.subplots()
x = np.linspace(-10,10,100)
sigma = [1.0, 2.0, 3.0, 4.0]
for s in sigma:
ax.plot(x, norm.pdf(x,scale=s), label='σ=%.1f' % s)
## 加图例
ax.set_xlabel('x')
ax.set_ylabel('Density')
ax.set_title('Normal Distribution')
ax.legend(loc='best', frameon=True)
ax.set_ylim(0,0.45)
ax.grid(True)
```
![](https://static001.geekbang.org/resource/image/90/39/9096e03d08e2f6c249f74676b69fc439.jpeg)
通过这个直方图去看标准差σ对随机变量分布的影响是不是就更直观了σ越大x偏离平均值μ的程度越大x的取值范围越广波动性越大直方图越向两边分散。
咱们再举个生活中的例子来理解标准差。在一次期末考试中有A和B两个班的平均分都是85分。其中A班的成绩范围在70~100分通过计算得到成绩的标准差是5分B的成绩范围在50~100分计算得到的成绩标准差是10分。你看A班的成绩分布范围比较小集中在85分左右所以标准差也就更小。
说到标准差,你应该还会想到另一个用来表征随机变量离散程度的概念,就是**方差**Variance。其实方差就是标准差的平方。所以标准差σ和方差在表征离散程度上其实是可以互换的。
有了方差和标准差我们就可以描述业务指标的离散程度了但要计算出业务指标的波动范围我会在第4讲展开具体的计算方法我们还差一步。这一步就是z分数。
要解释z分数就要引出一种特殊的正态分布也就是标准正态分布Standard Normal Distribution其实就是平均值μ=0、标准差σ=1的正态分布。
标准正态分布的直方图如下所示:
![](https://static001.geekbang.org/resource/image/12/63/1226ea7777e38098a5yye506cb196d63.jpg)
这里的横轴就是z分数Z Score也叫做标准分数Standard Score
$$
\\mathrm{z} \\text { score }=\\frac{x-\\mu}{\\sigma}
$$
事实上任何一个正态分布都可以通过标准化Standardization变成标准正态分布。而标准化的过程就是按照上面这个公式把随机变量x变为z分数。不同z分数的值代表x的不同取值偏离平均值μ多少个标准差σ。比如当z分数等于1时说明该值偏离平均值1个标准差σ
我们再用一个社交App业务指标的例子来强化下对正态分布的理解。
现在有一个社交App我们想要了解用户日均使用时间t的概率分布。根据现有的数据1万个用户在一个月内每天使用App的时间我们做出了一个直方图
![](https://static001.geekbang.org/resource/image/2c/c5/2c53064d4f4306a2383b8b5e1ba180c5.png)
可以看出这1万个用户的日均使用时间t大约在3-5小时这个范围而且是近似正态分布的钟形曲线说明t的分布也可以近似为正态分布。
### 中心极限定理Central Limit Theorem
这其实是均值类变量的特性:当样本量足够大时,均值类变量会趋近于正态分布。这背后的理论基础,就是中心极限定理。
[中心极限定理](https://en.wikipedia.org/wiki/Central_limit_theorem)的数学证明和推理过程十分复杂,但不用害怕,我们只要能理解它的大致原理就可以了:**不管随机变量的概率分布是什么,只要取样时的样本量足够大,那么这些样本的平均值的分布就会趋近于正态分布。**
那么,这个足够大的样本量到底是多大呢?
统计上约定俗成的是样本量大于30就属于足够大了。在现在的大数据时代我们的样本量一般都能轻松超过30这个阈值所以均值类指标可以近似为正态分布。
到这里,“数量足够大”具体是需要多大的数量,以及什么是正态分布,这两个问题我们就都明白了。接下来,我们再学习下什么是二项分布,之后我们就可以理解为什么概率类指标可以既服从二项分布又服从正态分布了。
## 二项分布Binomial Distribution
业务中的概率类指标,具体到用户行为时,结果只有两种:要么发生,要么不发生。比如点击率,就是用来表征用户在线上点击特定内容的概率,一个用户要么点击,要么不点击,不会有第三种结果发生。
这种只有两种结果的事件叫做二元事件Binary Event。二元事件在生活中很常见比如掷硬币时只会出现正面或者反面这两种结果所以统计学中有专门有一个描述二元事件概率分布的术语也就是**二项分布**Binomial Distribution
这里我们还是结合着社交App的例子来学习下二元分布。
这款社交App在网上投放了广告来吸引人们点击广告从而下载App。现在我们想通过数据看看App下载率的分布情况
下载率 = 通过广告下载App的用户数量 / 看到广告的用户数量。
因为单个二元事件的结果只能是发生或者不发生发生的概率要么是100%要么是0%,所以我们要分析下载率就必须把数据进行一定程度的聚合。这里,我们就以分钟为单位来举例,先计算每分钟的下载率,再看它们的概率分布。
我们有一个月的用户及下载数据一个月一共有43200分钟60\*24\*30因为我们关注的是每分钟的下载率所以一共有43200个数据点。通过数据分析发现每分钟平均有10个人会看到广告下载率集中分布在0-30%之间。
下图是每分钟下载率的概率分布:
![](https://static001.geekbang.org/resource/image/81/20/8119d8c26b655c9ba806f68fc63e5e20.png)
你可能会说概率在某种程度上也是平均值可以把这里的下载率理解为“看到广告的用户的平均下载量”那我们已经有43200个数据点了样本量远远大于30但为什么下载率的分布没有像中心极限定理说的那样趋近于正态分布呢
这是因为在二项分布中中心极限定理说的样本量指的是计算概率的样本量。在社交App的例子中概率的样本量是10因为平均每分钟有10人看到广告还没有达到中心极限定理中说的30这个阈值。所以我们现在要提高这个样本量才能使下载率的分布趋近正态分布。
提高样本量的方法也很简单可以计算每小时的下载率。因为每小时平均有600人看到广告这样我们的样本量就从10提高到了600。下图是每小时下载率的概率分布
![](https://static001.geekbang.org/resource/image/2c/37/2c1f1e08aacc5dec5eb9e55e8a55fd37.png)
现在再看这张直方图每小时下载率的分布是不是就趋近于正态分布了图中下载率的平均值大约为10%。
在二项分布中有一个从实践中总结出的经验公式min(np,n(1-p)) >= 5。其中n为样本大小p为概率的平均值。
这个公式的意思是说np或者n(1-p)中相对较小的一方大于等于5只有二项分布符合这个公式时才可以近似于正态分布。这是中心极限定理在二项分布中的变体。
在我们的例子中计算每分钟下载率的概率分布时np=10\*10%=1小于5所以不能近似成正态分布计算每小时下载率的概率分布时np=600\*10%=60大于等于5所以可以近似成正态分布。
我们可以利用这个公式来快速判断概率类指标是不是可以近似成正态分布。不过你也可以想象在实践中的A/B测试由于样本量比较大一般都会符合以上公式的。
## 小结
今天这节课我们主要学习了A/B测试和假设检验的前提也就是指标的统计属性。我给你总结成了一个定理、两个分布和三个概念
1. 一个定理:中心极限定理。
2. 两个分布:正态分布和二项分布。
3. 三个概念方差标准差和z分数。
生活中随机变量的分布有很多种,今天我重点给你介绍了正态分布和二项分布,它们分别对应的是最普遍的两类业务指标:均值类和概率类。
而且你要知道有了中心极限定理我们就可以把业务中的大部分指标都近似成正态分布了。这一点非常重要因为A/B测试中的很多重要步骤比如计算样本量大小和分析测试结果都是以指标为正态分布为前提的。
同时你还可以用通过方差和标准差来了解业务指标的离散程度再结合z分数就可以计算出业务指标的波动范围了。只有理解了指标的波动范围才能够帮助我们得到更加准确的测试结果。
在下节课中我们继续学习A/B测试的统计基础也就是假设检验及其相关的统计概念。
## 思考题
我在刚开始接触概率类指标的二项分布时对于其如何能近似成正态分布很迷惑大家可以在这里聊一聊在学习A/B测试的统计过程中有什么难理解的地方以及是如何解决的
欢迎在留言区写下你的思考和想法,我们可以一起交流讨论。如果你觉得有所收获,欢迎你把课程分享给你的同事或朋友,一起共同进步!

View File

@ -0,0 +1,211 @@
# 02统计基础深入理解A/B测试中的假设检验
你好,我是博伟。
在上节课学习A/B测试指标的统计属性时我用一句话给你简单解释了下假设检验选取一种合适的检验方法去验证在A/B测试中我们提出的假设是否正确。
这句话其实很抽象,所以今天这一讲,我们就具体展开下,看看假设检验是什么,以及如何利用假设检验来做出推断。
## 假设检验(Hypothesis Testing)是什么?
假设检验,顾名思义,就是要检验我们提出的假设是不是正确的,在事实上能否成立。
在统计中我们很难获取总体数据Population。不过我们可以取得样本数据Sample然后根据样本数据的情况产生对总体数据的假设。所以我们所说的假设检验其实就是检测通过样本数据产生的假设在总体数据即事实上是否成立。
在A/B测试的语境中假设一般是指**关于实验组和对照组指标的大小的推断。**
为了更加形象地帮你理解假设检验,这节课我就从一个推荐系统的案例出发,从中抽象出假设检验的基本原理和相关概念,让你在实践中学习理论,同时把理论应用到实践中去。
新闻App中的推荐系统是重要的组成部分可以根据用户过往的浏览记录来推荐用户喜欢的内容。最近工程团队改进了推荐系统的算法就想通过A/B测试来验证改进的效果。
实验组中使用新算法,对照组中使用旧算法,然后通过点击率来表征算法的效果:推荐效果越好,点击率越高。那么,我们提出的假设就是:实验组(新算法)的点击率比对照组(旧算法)的点击率高。
你可能会有些疑惑,我们提出的“假设”,和假设检验中的“假设”是相同的吗?
其实不完全相同。
## 假设检验中的“假设”是什么?
为什么这么说呢因为在假设检验中的“假设”是一对零假设Null Hypothesis和备择假设Alternative Hypothesis它们是完全相反的。在A/B测试的语境下零假设指的是实验组和对照组的指标是相同的备择假设指的是实验组和对照组的指标是不同的。
为了更好地理解零假设和备择假设,我们可以回到推荐系统的案例中,把最开始提出的假设转化成假设检验中的零假设和备择假设:
* 零假设是,实验组和对照组的点击率是相同的。
* 备择假设是,实验组和对照组的点击率是不同的。
你可能会问,我们最开始提出的假设不是“实验组的点击率比对照组的点击率高”吗?为什么备择假设中仅仅说两组的点击率不同,却没说谁大谁小呢?
要回答这个问题我们就得先了解单尾检验One-tailed Test和双尾检验Two-tailed Test这两个概念。
* 单尾检验又叫单边检验One-sided Test它不仅在假设中说明了两个比较对象不同并且还明确了谁大谁小比如实验组的指标比对照组的指标大。
* 双尾检验又叫双边检验Two-sided Test指的是仅仅在假设中说明了两个比较对象不同但是并没有明确谁大谁小。
回到推荐系统案例中的最初假设,我们已经明确了实验组的点击率比对照组的高,那就应该选用单尾检验。但是,我们的备择假设却变成了两组的点击率不同,这是双尾检验的假设,为什么呢?
这就是理论和实践的不同之处也是为什么我们觉得A/B测试的理论好掌握但实践总出问题的原因。这里我先告诉你结论再给你说明为什么。结论是**在A/B测试的实践中更推荐使用双尾检验。**
更推荐你使用双尾检验的原因,主要有两个。
第一个原因是,双尾检验可以让数据自身在决策中发挥更大的作用。
我们在实践中使用A/B测试就是希望能够通过数据来驱动决策。我们要尽量减少在使用数据前产生的任何主观想法来干扰数据发挥作用。所以双尾检验这种不需要我们明确谁大谁小的检验更能发挥数据的作用。
第二个原因是,双尾检验可以帮助我们全面考虑变化带来的正、负面结果。
在实践中,我们期望改变可以使指标朝着好的方向变化,但是万一指标实际的变化与期望的正好相反呢?这就可以体现双尾检验的优势了。双尾检验可以同时照顾到正面和负面的结果,更接近多变的现实情况。但是单尾检验只会适用于其中一种,而且通常是我们期望的正面效果。
所以正因为我们选择双尾测试,在备择假设中我们才只说了两组不同,并没有说谁大谁小。
## 假设检验中的“检验”都有哪些,该怎么选取?
现在,我们知道了假设检验中的“假设”包括零假设和备择假设两种,那么“检验”都包括什么呢?
其实检验有很多种单尾检验和双尾检验是从“假设”的角度来分类的。除此之外常见的“检验”还可以根据比较样本的个数进行分类包括单样本检验One-Sample Test、 双样本检验Two-Sample Test和配对检验Paired Test。那么问题来了在测试中到底该选择哪种检验方法呢
答案是:**在A/B测试中使用双样本检验**。
其中的原因其实很简单,我给你解释下它们各自的适用范围,你就知道了。
* 当两组样本数据进行比较时就用双样本检验。比如A/B测试中实验组和对照组的比较。
* 当一组样本数据和一个具体数值进行比较时就用单样本检验。比如我想比较极客时间用户的日均使用时间有没有达到15分钟这个时候我就可以把一组样本数据抽样所得的极客时间用户的每日使用时间和一个具体数值15来进行比较。
* 当比较同一组样本数据发生变化前和发生变化后时就用配对检验。比如我现在随机抽取1000个极客时间的用户给他们“全场专栏一律1折”这个优惠然后在这1000个人中我们会比较他们在收到优惠前一个月的日均使用时间和收到优惠后一个月的日均使用时间。
看到这里,你可能会问,我还听说过**T检验**T Test和**Z检验**Z Test那这两个检验在A/B测试中该怎么选择呢
选择T检验还是Z检验主要看样本量的大小和是否知道总体方差Population Variance
* 当我们不知道总体方差时使用T检验。
* 当我们已知总体方差且样本量大于30时使用Z检验。
我还给你画了张图,你一看就明白了。
![](https://static001.geekbang.org/resource/image/a9/04/a96d2d6ddeb4100b4d595d863693yy04.png?wh=6079*3575)
那么这些理论具体到A/B测试实践中一个经验就是**均值类指标一般用T检验概率类指标一般用Z检验比例检验。**
为什么会有这样的经验呢?
因为上节课我讲了样本量大的情况下均值类指标是正态分布正态分布的总体方差的计算需要知道总体中各个数据的值这在现实中几乎做不到因为我们能获取的只是样本数据。所以总体方差不可知选用T检验。
而概率类指标是二项分布二项分布的总体方差的计算不需要知道总体中各个数据的值可以通过样本数据求得总体方差。而且现实中A/B测试的样本量一般都远大于30所以选用Z检验。这里的**比例检验**Proportion Test)是专指用于检验概率类指标的Z检验。
讲了这么多检验,我现在来总结一下:**对于A/B测试来说要选用双尾、双样本的比例检验概率类指标或T检验均值类指标。**
再次回到我们的案例中来,由于点击率为概率类指标,所以这里选用双尾、双样本的比例检验。
## 如何利用假设检验做出推断?
选取了正确的假设和检验方法接下来就要检验我们的假设是不是正确了这在A/B测试中就是分析测试结果这一步啦。
### A/B测试可能出现的结果
假设检验会推断出两种结果:
1. 接受零假设,拒绝备择假设,也就是说实验组和对照组的指标是相同的。
2. 接受备择假设,拒绝零假设,也就是说实验组和对照组的指标是不同的。
但是请注意,这两个结果只是假设检验根据样本数据,通过一系列统计计算推断出的结果,并不代表事实情况(总体数据情况)。如果考虑到事实情况的话,结合假设检验的推断结果会有四种可能:
![](https://static001.geekbang.org/resource/image/a8/f3/a831c6b06fede368102b38c7af6f45f3.png?wh=7759*3836)
可以看出,只有当假设检验推断的情况和事实完全相符时,推断才正确,否则就会出现两类错误。
**第一类错误Type I Error)**统计上的定义是拒绝了事实上是正确的零假设。在A/B测试中零假设是两组的指标是相同的当假设检验推断出两组指标不同但事实上两组指标相同时就是第一类错误。我们把两组指标不同称作阳性Positive。所以第一类错误又叫假阳性False Positive
发生第一类错误的概率用α表示,也被称为**显著水平**Significance Level。“显著”是指错误发生的概率大统计上把发生率小于5%的事件称为小概率事件代表这类事件不容易发生。因此显著水平一般也为5%。
**第二类错误Type II Error)**统计上的定义是接受了事实上是错误的零假设。在A/B测试中当假设检验推断出两组指标相同但事实上两组指标是不同时就是第二类错误。我们把两组指标相同称作阴性Negative所以第二类错误又叫假阴性False Negative。发生第二类错误的概率用β表示统计上一般定义为20%。
这两种错误的概念读起来可能比较拗口,也不太容易理解,那么我就举一个新冠病毒核酸检测的例子来给你具体解释一下。
我们在这里的零假设是:被测试者是健康的,没有携带新冠病毒。
把携带新冠病毒作为阳性,没有携带作为阴性。如果一个健康的人去检测,结果检测结果说此人携带新冠病毒,这就犯了第一类错误,拒绝了事实上正确的零假设,是假阳性。如果一个新冠肺炎患者去检测,结果检测结果说此人没有携带新冠病毒,这就犯了第二类错误,接受了事实上错误的零假设,是假阴性。
现在我们了解了假设检验推断的可能结果,那么,如何通过假设检验得到测试结果呢?
实践中常用的有两种方法P值P Value法和置信区间Confidence Interval法。
### P值法
在统计上P值就是当零假设成立时我们所观测到的样本数据出现的概率。在A/B测试的语境下P值就是当对照组和实验组指标事实上是相同时在A/B测试中用样本数据所观测到的“实验组和对照组指标不同”出现的概率。
如果我们在A/B测试中观测到“实验组和对照组指标不同”的概率P值很小比如小于5%,是个小概率事件,虽然这在零假设成立时不太可能发生,但是确实被我们观测到了,所以肯定是我们的零假设出了问题。那么,这个时候就应该拒绝零假设,接受备择假设,即两组指标是不同的。
与此相反的是当我们在A/B测试中观测到“实验组和对照组指标不同”的概率P值很大比如70%,那么在零假设成立时,我们观测到这个事件还是很有可能的。所以这个时候我们接受零假设,拒绝备择假设,即两组指标是相同的。
在统计中我们会用P值和显著水平α进行比较又因为α一般取5%所以就用P值和5%进行比较,就可以得出假设检验的结果了:
* 当P值小于5%时,我们拒绝零假设,接受备择假设,得出两组指标是不同的结论,又叫做结果显著。
* 当P值大于5%时,我们接受零假设,拒绝备择假设,得出两组指标是相同的结论,又叫做结果不显著。
至于P值具体的计算我推荐你用工具来完成比如Python或者R
* 比例检验可以用Python的[proportions\_ztest](https://www.statsmodels.org/stable/generated/statsmodels.stats.proportion.proportions_ztest.html)函数、R的[prop.test](https://www.rdocumentation.org/packages/stats/versions/3.6.2/topics/prop.test)函数。
* T检验可以用Python的[ttest\_ind](https://www.statsmodels.org/stable/generated/statsmodels.stats.weightstats.ttest_ind.html)函数、R的[t.test](https://www.rdocumentation.org/packages/stats/versions/3.6.2/topics/t.test)函数。
### 置信区间法
置信区间是一个范围一般前面会跟着一个百分数最常见的是95%的置信区间。这是什么意思呢在统计上对于一个随机变量来说有95%的概率包含总体平均值Population mean的范围就叫做95%的**置信区间**。
置信区间的统计定义其实不是特别好懂,其实你可以直接把它理解为**随机变量的波动范围**95%的置信区间就是包含了整个波动范围的95%的区间。
**A/B测试本质上就是要判断对照组和实验组的指标是否相等那怎么判断呢**答案就是计算实验组和对照组指标的差值δ。因为指标是随机变量,所以它们的差值δ也会是随机变量,具有一定的波动性。
这就意味着我们就要计算出δ的置信区间然后看看这个置信区间是否包括0。如果包括0的话则说明δ有可能为0意味着两组指标有可能相同如果不包括0则说明两组指标不同。
至于置信区间的具体的计算我也推荐你使用Python或者R等工具完成
* 比例检验可以使用Python的[proportion\_confint](https://www.statsmodels.org/stable/generated/statsmodels.stats.proportion.proportion_confint.html)函数、R的[prop.test](https://www.rdocumentation.org/packages/stats/versions/3.6.2/topics/prop.test)函数。
* T检验可以使用Python的[tconfint\_diff](https://www.statsmodels.org/stable/generated/statsmodels.stats.weightstats.CompareMeans.tconfint_diff.html#statsmodels.stats.weightstats.CompareMeans.tconfint_diff)函数、R的[t.test](https://www.rdocumentation.org/packages/stats/versions/3.6.2/topics/t.test)函数。
现在回到推荐系统的案例中我会分别用P值法和置信区间法来根据A/B测试的结果进行判断。
* 实验组新推荐算法样本量为43578其中有2440个点击点击率为5.6%。
* 对照组旧推荐算法样本量为43524其中有2089个点击点击率为4.8%。
这时候我用R中的比例检验函数[prop.test](https://www.rdocumentation.org/packages/stats/versions/3.6.2/topics/prop.test)来计算P值和置信区间。
```
prop.test(x = c(2440, 2089), n = c(43578, 43524), alternative = "two.sided", conf.level = 0.95)
```
得到了如下结果:
![](https://static001.geekbang.org/resource/image/75/03/75c56fdd59dee61dea812c26c187aa03.png?wh=404*163)
可以得出P值=$1.167 e^{-7}$ 远远小于5%且接近于0所以我们拒绝零假设接受备择假设并且推断出实验组和对照组指标显著不同。
同时我们也可以得出两组指标差值δ的95%置信区间为\[0.005,0.011\]不包含0也可以推断出两组指标显著不同。
## 小结
今天这节课我们针对A/B测试的理论基础—假设检验学习了假设、检验以及相关的统计概念。你只要记住以下两个知识点就可以了。
第一对于A/B测试来说要选用双尾、双样本的比例检验概率类指标或T检验均值类指标。这决定了你在计算分析A/B测试结果时如何选取检验的参数所以很重要。
第二在A/B测试实践中计算样本量大小、指标波动性和分析测试结果的时候会用到这些统计概念。
* 计算样本量大小时,会用到: 第一类/第二类错误及其概率α和β。
* 计算指标波动性时,会用到:方差和置信区间。
* 分析A/B测试结果时会用到各类检验、置信区间、P值。
本节课中的关于假设检验的概念和知识点比较琐碎,为了方便你日后理解记忆,我也给你准备了下面的导图:
![](https://static001.geekbang.org/resource/image/6d/41/6dd0059d264833ce89f7acdc11b25b41.png?wh=2250*2047)
到这里我们的统计篇就告一段落了现在你应该已经掌握了A/B测试所需的基本统计知识啦。其实前两节的内容比较偏理论会不太好理解。不过理论知识的学习如果只是填鸭式地讲效果可能并不好。那该怎么掌握这些理论知识呢在我这些年做A/B测试的实践中发现要想真正把理论知识理解透化为己用还是需要自己多思考多实践。等你有了一些实战后自然就能自己体悟到理论学习的好处了。而且这时候再回过头来看理论就会非常容易看懂。
所以在今天的内容中如果有哪些地方你还不能理解那也没关系不要给自己设置心理障碍可以先放一放。之后的课程中我都会运用今天讲到的理论去解决在A/B测试中遇到的问题。你可以在学习的过程中不断回顾这些理论或者发挥主观能动性多查阅一些资料。等你学完整个课程再回头看这两节理论知识一定会发现理论原来如此简单。
那么接下来我们就进入“基础篇”模块去详细学习A/B测试的主要流程吧
## 思考题
这节课涉及的统计概念都是虽然经常听到,但是难理解的,你们在学习统计中有没有对这些概念的理解有独特的心得?可以拿出来分享给大家。
欢迎在留言区写下你的思考和想法,我们可以一起交流讨论。如果你觉得有所收获,欢迎你把课程分享给你的同事或朋友,一起共同进步!

View File

@ -0,0 +1,40 @@
# 导读 | 科学、规范的A/B测试流程是什么样的
你好,我是博伟。
前面两节课啊我们花了很大力气去学习做A/B测试的理论前提这也是为了让你夯实理论基础。不过啊除非你是统计科班出身否则我都会推荐你在学习实战的时候呢也要不断温习统计篇的内容把理论与实践结合起来。如果觉得有必要也可以把我在统计篇讲的统计概念和理论延伸开来通过查看相关统计专业书籍来加深理解。
学完了统计理论接下来就要开始设计实现做A/B测试了。不过在我总结A/B测试的流程之前呢我要简单介绍下在实践中做A/B测试的准备工作主要有两部分**数据**和**测试平台**。
一方面我们要有数据包括用户在我们产品和业务中的各种行为营销广告的表现效果等等以便用来构建指标。因为A/B测试是建立在数据上的分析方法正如“巧妇难为无米之炊”没有数据的话我们就不能通过A/B测试来比较谁好谁坏。
一般来说,只要是公司的数据基础架构做得好,埋点埋得到位的话,基本的常用指标都是可以满足的。
如果说我们要进行的A/B测试的指标比较新、比较特别或者数据库没有很全面没有现成的数据可以用来计算相应的指标那么可以和数据团队进行协商看能不能在现有的数据中找出可以替代的指标计算方法。
如果找不到相近的替代指标,那么就要和数据工程团队协商,看能不能构建这个数据,可能需要新的埋点,或者从第三方获得。
另一方面呢我们要有合适的测试平台来帮助我们具体实施A/B测试。可以是公司内部工程团队搭建的平台也可以是第三方提供的平台。对于这些平台我们在做A/B测试之前都是需要事先熟悉的以便可以在平台上面设置和实施新的A/B测试。
当然在做A/B测试时数据库和测试平台是要通过API等方式有机结合起来的这样我们在测试平台上设置和实施的A/B测试才能通过数据来计算相应的指标。
以上的准备工作并不是每次A/B测试都要做的更多的是第一次做A/B测试时才需要去做的准备所以更像是A/B测试的基础设施。以下的流程才是我们每次去做A/B测试都要经历的我把它们总结在一张图中你可以看一下。
![](https://static001.geekbang.org/resource/image/86/e5/86a26b183d53e37a247d54f06e38bae5.png?wh=2250*2993)
以上就是一个规范的做A/B测试的流程了。你看啊A/B测试的实践性很强但大体就是这么几步。在这门课里我会着重讲最核心的5个部分也就是确定目标和假设、确定指标、确定实验单位、估算样本量以及分析测试结果。
在整个流程中,除了随机分组的具体实施细节和具体实施测试外,其余环节我都会逐个讲解。你可能会问,为什么不能把全部环节讲解一遍呢?
其实啊我会侧重讲解A/B测试的基本原理实践中的具体流程还有实践中遇到的常见问题及解决办法这些都是偏经验和方法论的内容。不管你在哪家公司处在哪个行业用什么平台去实施A/B测试这些经验和方法论都是通用的学完之后你就可以应用到实践中。
至于随机分组的不同随机算法以及实施测试所用的平台这些更偏工程实施的细节公司不同平台不同那么实施A/B测试时也会有很大的差别。比如A/B测试的平台大公司一般会自己开发内部的测试平台中小型公司则会利用第三方的测试平台。
所以啊基础篇这几节课呢我也希望你能在学习的同时能够跟自己的工作联系起来。如果你在工作中做过A/B测试但是觉得流程没有很系统化你就可以把平时做的A/B测试和基础篇的流程进行参照对比看看还有哪些不足的地方。同时通过学习基础篇也会让你知道为什么会有这些流程它们背后的原理是什么让你加深对流程的理解应用起来更加得心应手。
如果你还没做过A/B测试也没关系。我会结合实际案例来给你深入讲解。如果有条件学习完之后你就可以尝试做自己的第一个A/B测试啦
最后我还要说明一点。A/B测试的前提是数据这里牵涉到一个公司的数据架构和埋点策略更多的是工程和数据库建设的问题不是我们A/B测试的重点。所以在接下来讲课的时候我就假设我们已经能够追踪A/B测试所需要的数据了至于如何追踪这些数据如何埋点这种工程实施的细节我们这里就不展开讨论了。
好啦了解了这些就让我们正式开始A/B测试的旅程吧

View File

@ -0,0 +1,165 @@
# 03确定目标和假设好的目标和假设是什么
你好,我是博伟。
今天这节课我们就进入到“基础篇”模块了通过前面的学习你已经清楚了做A/B测试的基本流程接下来呢我会带你去看看在实践中确定目标和假设、确定指标、选取实验单位、估算样本量大小以及分析测试结果这5步具体应该怎么操作。
我们知道确定目标和假设、确定指标这两步决定了测试的方向可谓至关重要。那么如何一步步地把业务问题转化为A/B测试的目标和假设呢又如何根据目标来选择合适的指标呢在接下来的两节课我会通过大量的案例来给你解答这两个问题。在讲解案例的同时我也会结合我的实践经验给你一些可落地执行、切实可操作的建议让你知道该如何规避坑点。
## 确定目标和假设
首先我们要明确做A/B测试肯定是为了解决业务上遇到的问题而绝不是为了做而做。所以找到了要解决的业务问题也就基本找到了A/B测试目标。为什么这么说呢
让我们来回顾下开篇词中讲的A/B测试解决的常见业务问题看看A/B测试可以用在什么领域解决什么问题
![](https://static001.geekbang.org/resource/image/13/df/13059daa26b391d55e4dc357124b51df.png)
总结一下这些业务问题,我们就会发现一些共性:
1. 所有的业务问题都会有一个目标,比如提升用户粘性是业务问题的目标,同时我们也把这个目标称作“**结果**”。
2. 有的业务问题会有明确的努力方向,比如,通过改变外观来提升点击率,这里的“改变外观”就是明确的努力方向,同时我们也把“改变外观”等变化称作“**原因**”。不过有的业务问题没有明确的努力方向,这时候我们需要根据具体的情况去发现原因。比如对于“如何确定最优的营销时间”这个业务问题,我们分析发掘之后会发现,周五晚上的营销效果会比较好。那么这里的“原因”就是大家结束了一周忙碌的工作,就会比较有时间。
你看,**把产品/业务的变化作为原因,把业务目标变成结果,我们就把业务问题转换成了因果推断**。而对于做A/B测试来说把业务问题转换成因果推断也就意味着找到了测试的目标。**所谓的假设在A/B测试的语境下就是既包含了想要做出的改变又包含了期望达到的结果。**
接下来我就以一款按月付费的音乐App要提高营收为例带你看看该如何确定目标和假设。
**首先,分析问题,确定想要达到的结果。**
想要提高营收我们首先得清楚问题出在哪里。这个时候我们可以进行数据分析。比如和竞品进行对比分析后发现我们App的用户留存率低于行业平均水平。因此用户留存率就是我们这款App目前存在的问题。
**其次,提出解决业务问题的大致方案。**
影响用户留存的原因有很多种。比如内容是否足够丰富能满足不同用户的音乐需求产品是否有足够多的便利功能可以给用户更好的使用体验App的开启和运行速度是否足够流畅
通过进一步的分析发现我们的产品在歌曲库的内容和丰富程度上都在行业平均水平之上而且App的运行也十分流畅但是缺少一些便利的产品功能。所以我们提出的大致解决方案就是要通过增加产品功能来提升用户留存。
**最后,从大致的解决方案中提取出具体的假设。**
那针对这款音乐App可以增加什么具体的产品功能呢你可能会想到**在每个专辑/歌单播放完成后增加“自动播放下一个专辑/歌单”的功能,以此来提升用户留存。**
这样一来,我们就通过三个步骤基本确定了目标和假设。
为什么说是“基本确定”了呢?因为确定目标和假设到这里还没有完全完成。要注意了,我们在上面确定目标和假设的时候其实还忽略了一个隐形的坑:这个假设中的“提升用户留存”还不能算是一个好的目标。因为这个假设还不够具体,目标没有被量化,而没有量化就没有办法提升。所以在这里,我们还需要做的就是量化“用户留存率”这个概念。
在按月付费的音乐App这个案例中用户只要每个月按时付费续订就是留存。所以我们可以把用户留存定义为下个月的续订率这样我们就把假设变得更加具体并且目标可被量化。
那我们优化后这个A/B测试的假设就变成了**在每个专辑/歌单播放完成后增加“自动播放下一个专辑/歌单”的功能,可以提升用户下个月的续订率。**
为了帮你理解怎样才能做出好的假设,我根据自己的经验,把到底啥是好的假设,啥是不好的假设归纳到了一张图中,你一看就明白了:
![](https://static001.geekbang.org/resource/image/07/c3/074305ea12510d47c7b76547fa2yy2c3.png)
以上就是确定目标和假设的核心内容,你只要记住以下两点就够了:
* A/B测试是因果推断所以我们首先要确定原因和结果。
* 目标决定了结果(用户留存), 而假设又决定了原因增加自动播放的功能所以目标和假设对于A/B测试来说是缺一不可。
有了测试目标和假设我们就可以进入A/B测试的第二步了确定指标。具体该如何确定指标呢在解答这个问题之前我们还需要先熟悉下指标的分类。
## A/B测试的指标有哪几类
一般来说, A/B测试的指标分为评价指标Evaluation Metrics和护栏指标Guardrail Metrics这两类。
**评价指标**,一般指能驱动公司/组织实现核心价值的指标,又被称作驱动指标。评价指标通常是短期的、比较敏感、有很强的可操作性,例如点击率、转化率、人均使用时长等。
可以说评价指标是能够直接评价A/B测试结果的指标是我们要重点关注的。
那有了评价指标就可以保证A/B测试的成功了吗显然不是的。很多时候我们可能考虑得不够全面忽略了测试本身的合理性不确定测试是否会对业务有负面效果因此很可能得出错误的结论。
举个例子。如果为了优化一个网页的点击率,就给网页添加了非常酷炫的动画效果。结果点击率是提升了,网页加载时间却增加了,造成了不好的用户体验。长期来看,这就不利于业务的发展。
所以,我们还需要从产品长远发展的角度出发,找到**护栏指标**。概括地说护栏指标属于A/B测试中基本的合理性检验Sanity Check就像飞机起飞前的安全检查一样。它的作用就是作为辅助来保障A/B测试的质量
* 衡量A/B测试是否符合业务上的长期目标不会因为优化短期指标而打乱长期目标。
* 确保从统计上尽量减少出现各种偏差Bias得到尽可能值得信任的实验结果。
到这里我们小结一下。在确定指标这一步,其实就是要确定评价指标和护栏指标。而护栏指标作为辅助性的指标,需要在选好了评价指标后才能确定。
那么问题来了,什么样的指标才能作为评价指标呢?
## 什么样的指标可以作为评价指标?
既然A/B测试的本质是因果推断那么我们选择的业务指标的变化结果必须要可以归因到实验中的变量原因。所以**评价指标的第一个特征,就是可归因性。**
比如我们要测试增加“自动播放”功能是否可以提升App的续订率。那么这里的评价指标续订率的变化就必须可以归因于增加了“自动播放”功能。在测试中我们控制其他可能影响续订率的因素都相同的情况下增加了“自动播放”功能的变化就成了续订率的唯一影响因素。
刚才我们提到了,好的假设要能够被量化,否则就没有办法进行实验组和对照组的比较。这也就是**评价指标要有的第二个特征:可测量性。**
比如对于音乐App来说像用户满意度这个指标就不是很好量化。但是像用户续订率这样的指标就可以量化。所以我们就可以把“用户满意度”转化成“用户续订率”这种可以量化的指标。
可测量性和可归因性这两个特征都比较容易判断,除此之外,评价指标还具有第三个特征:**敏感性和稳定性**。那怎么理解呢?我用一句话来解释下:如果实验中的变量变化了,评价指标要能敏感地做出相应的变化;但如果是其他因素变化了,评价指标要能保持相应的稳定性。
看一个例子吧。还是在音乐App中如果我想测试某一个具体内容的推送效果比如推送周杰伦的新专辑那么续订率会是一个好的指标吗答案是否定的。
因为具体的推送是一次性的,而且推送只会产生短期效果(比如增加用户对杰伦新专辑的收听率),但不太会产生长期效果(比如增加续订率)。所以,续订率这个指标就对杰伦的推送不是很敏感。相反,短期的收听率是对单次推送更加敏感且合适的指标。
从这个例子中,我们可以得出两个结论:
* 用A/B测试来检测单次的变化时比如单次推送/邮件)一般选用短期效果的指标,因为长期效果目标通常对单次变化并不敏感。
* 用A/B测试来检测连续的、永久的变化时比如增加产品功能可以选用长期效果的指标。
可见如果选取的评价指标对A/B测试中的变化不敏感或者对其他变化太敏感我们的实验都会失败。那么具体该如何测量评价指标的敏感性和稳定性呢业界通常采用A/A测试来测量稳定性用回溯性分析来表征敏感性。我来给你具体解释一下。
和A/B测试类似A/A测试A/A Test也是把被测试对象分成实验组和对照组。但不同的是A/A测试中两组对象拥有的是完全相同的体验如果A/A测试的结果发现两组的指标有显著不同那么就说明要么分组分得不均匀每组的数据分布差异较大要么选取的指标波动范围太大稳定性差。
如果没有之前实验的数据,或者是因为某些原因(比如时间不够)没有办法跑新的实验,那我们也可以通过分析历史数据,进行**回溯性分析Retrospective Analysis**。也就是在分析之前不同的产品变化时,去看我们感兴趣的指标是否有相应的变化。
比如,我们选取续订率作为衡量增加“自动播放”功能是否有用的指标,那么我们就要去分析,在过去增加其他有利于用户留存的产品功能前后,续订率是不是有明显的变化。
好了,知道了应该选择什么样的指标作为评价指标之后,我们就可以开始选取适合我们自己业务的指标了。
## 如何选取具体的评价指标?
正像我们今天所看到的确定评价指标的方法林林总总但到底哪些是好用的是真正可落地的呢经过这些年的实践我逐步总结积累了3种经验证确实简单、可落地的方法。
我还是以音乐App为例和你解释下。
**第一,要清楚业务或产品所处的阶段,根据这个阶段的目标,来确定评价指标。**
这是因为,不同的业务/产品,甚至是同一个业务/产品的不同阶段,目标不同评价指标也会差别较大。
拿音乐App来说在起步阶段我们一般把增加新用户作为主要目标把在拉新过程中的各种点击率、转化率作为评价指标在发展和成熟期一般会重点关注现有用户的使用和留存情况把用户的平均使用时间和频率、产品特定功能的使用率以及用户的留存率等作为评价指标。
比如要提高留存,首先要明确什么是留存:用户只要每个月按时付费续订,就是留存。那么这个时候,我们可以把用户留存的评价指标定义为下个月的续订率。
**第二,如果目标比较抽象,我们就需要采用定性+定量相结合的方法了。**
对于一些比较抽象的目标,比如用户的满意度,我们可以使用一些定性的方法,确定一些假设和想法,像**问卷调查、用户调研**等。同时,我们还可以利用用户使用产品时的各种数据,进行定量的数据分析,来了解他们的使用行为。
最后,我们把定性的用户调研结果和定量的用户使用行为分析结合起来,找出哪些使用行为和用户的满意度有着强烈的关系。
对于音乐App来说我们具体可以这么做
* 首先,通过定性的用户调研,来确定哪些用户满意、哪些用户不满意,完成分组。
* 接着我们对每组用户满意的用户和不满意的用户分别做定量的用户使用习惯的数据分析发现把音乐收藏到自己曲库的用户有较高的满意度说明收藏音乐这个行为和用户满意度有强烈的正相关性。这时候我们就可以把收藏音乐作为评价指标比如收藏音乐的数量。更进一步我们还可以通过数据分析确定“收藏X首以上音乐的用户非常满意”中X的最优值是多少。
**第三,如果有条件的话,你还可以通过公开或者非公开的渠道,参考其他公司相似的实验或者研究,根据自己的情况去借鉴他们使用的评价指标。**
公开的渠道是指网络上公开的各个公司关于A/B测试的文章或者论文。我经常会看的大公司的博客是[Facebook](https://www.facebook.com/business/insights/advertising/measurement)、[Google](https://www.thinkwithgoogle.com/)、[Twitter](https://blog.twitter.com/engineering/en_us/topics/insights.html)也推荐给你你可以重点看Facebook中Measurement相关的文章都是介绍评价广告效果的指标。
另外你还可以去看一下《精益数据分析》这本书。在这本书里你几乎可以找到所有重要互联网商业模式电商社交网络移动App等在各个阶段的典型指标。
为什么其他公司的评价指标有借鉴意义呢原因很简单To C的产品用到A/B测试的场景都很相似。比如我们想要通过A/B测试提升音乐App中广告的效果那么Facebook在广告业务上的经验就能给我们很大的启发。
相应地非公开的渠道是指你的从事A/B测试并愿意和你分享经验的朋友以及A/B测试相关的行业峰会。
在实践中,大部分的指标是根据产品/业务发展阶段的目标来确定的;如果实验的目标比较抽象或者比较新,通过经验和数据分析无法产生,你就可以采用定性+定量的方法了。
## 小结
今天这一讲,我们解决了下面两个问题。
第一,确定目标和假设,其实就是三大步:分析问题,确定结果;找出大致的解决方案;确定假设。
第二,确定指标,就是要确定评价指标和护栏指标。这节课主要讲了评价指标,其中关键的是我们要从目标入手,把目标量化。
最后我要再和你强调一下在A/B测试中确定目标和假设的重要性。A/B测试是和业务紧密相关的但我们往往会忽视业务中的目标把注意力过多地放在选取评价指标上。在我看来这就是本末倒置就像一个不知道终点在哪里却一直在奔跑的运动员如果能先明确终点朝着终点的方向努力会更快地取得成功。所以你一定要按照今天学的内容在做A/B测试时先试着找出你的目标和假设。
实际的业务场景大多比较复杂很多时候单一的评价指标不足以帮助我们达成目标而且指标也有波动性。所以下节课我会给你讲一讲综合多个指标建立总体评价标准的方法以及指标的波动性。同时我还会具体给你介绍护栏指标保证你的A/B测试在业务和统计上的品质和质量。
## 思考题
根据生活和工作中的经历结合今天所学内容说说你认为有哪些指标是不适合做A/B测试的评价指标的为什么呢
欢迎在留言区写下你的思考和想法,我们可以一起交流讨论。如果你觉得有所收获,欢迎你把课程分享给你的同事或朋友,一起共同进步!

View File

@ -0,0 +1,208 @@
# 04确定指标指标这么多到底如何来选择
你好,我是博伟。
上节课,我们学习了确定评价指标的几种方法,包括量化产品/业务不同阶段的目标,采取定量+定性的方法,或者借鉴行业内其他公司的经验等。你也发现了,这些方法的局限性在于只能选出单个评价指标,而且也没有考虑到评价指标的波动性对结果准确度的影响。
今天我们会更进一步去看看在实际的复杂业务场景中确定评价指标的方法以及计算指标的波动性的方法。然后我们再看看为了确保A/B测试结果的可靠性应该如何去确定护栏指标。
## **综合多个指标,建立总体评价标准**
在实际的业务需求中,有时会出现多个目标,同一目标也可能有多个都很重要的评价指标,需要我们把它们都综合起来考虑。对于单个指标,我们可以用上一讲的方法来确定;但如果要综合考虑多个指标时,又要如何考虑呢?
我们先看一个例子。
亚马逊和用户沟通的一个重要渠道就是电子邮件,它有一个专门给用户发送电子邮件的平台,通过两种方式来精准定位用户:
* 基于用户的历史购买数据来构建用户的个人喜好,通过推荐算法来发邮件给用户做推荐;
* 亚马逊的编辑团队会人工精选出推荐产品,通过电子邮件发送给用户。
确定了精准用户以后,亚马逊还面临一个问题:要用什么指标来衡量电子邮件的效果呢?
你可能会想到,给用户发送邮件是为了让他们购买,所以邮件产生的收入可以作为评价指标。
实际上,亚马逊最初就是这么做的:他们确定的假设是通过多发电子邮件来增加额外的收入,评价指标就是邮件产生的收入。
那么这个时候一个假想的A/B测试就被设计了出来。
* 在实验组,我们给用户发邮件。
* 在对照组,我们不给用户发邮件。
结果可想而知。对照组没有收到任何邮件,也就不会有邮件产生的收入。而在实验组的用户,由于收到很多邮件,所以产生了不少收入。
出现这个结果的根本原因是,这个指标本身是单调递增的。也就是说,发的电子邮件越多,点击的用户也会越多,从邮件中获得的收入也会越多。所以,想要有更多的收入,就要发更多的邮件。
但现实情况是用户收到的邮件多到一定程度后他们就会觉得是垃圾邮件被骚扰了结果就是影响了用户体验用户选择退订Unsubscribe邮件。而用户一旦退订以后就再也接收不到来自亚马逊的邮件了。
把邮件产生的收入作为评价指标,可以说只是用来优化短期的收入,并没有考虑到长期的用户价值。用户一旦因为被骚扰而退订,亚马逊就失去了在未来给他们发邮件做营销的机会了。所以,邮件产生的收入并不适合作为评价指标,我们需要综合考虑发邮件所带来的好处和潜在的损失,结合多个指标,构建一个总体评价标准 Overall Evaluation Criteria简称OEC
那具体怎么做呢?我们可以给每个实验/对照组计算OEC
$$
\\mathrm{OEC}=\\frac{\\left(\\Sigma\_{i}{ Revenue-S\*Unsubscribe\\\_lifetime\\\_loss}\\right)} {n}
$$
我来具体解释下这个公式。
* i代表每一个用户。
* S代表每组退订的人数。
* Unsubscribe\_lifetime\_loss ,代表用户退订邮件带来的预计的损失。
* n代表每组的样本大小。
当亚马逊实施了这个OEC之后惊讶地发现有一半以上电子邮件的OEC都是负的这就说明多发邮件并不总是能带来正的收益。
当亚马逊发现退订会造成这么大的长期损失以后,就改进了它的退订页面:从退订所有的亚马逊邮件到退订某一个类别的邮件。比如可以选择只退订亚马逊图书,从而避免了全部退订,也减少了长期的潜在损失。
通过刚刚的分析我们可以看到当要考察的事物包含多个方面时只有综合各方面的指标才能把握总体的好坏。这也是使用OEC最明显的一个好处。最常见的一类OEC就是亚马逊的这种结合变化带来的潜在收益和损失的OEC。需要注意的是这里的“损失”还有可能是护栏指标也就是说OEC有可能会包含护栏指标。
另外使用OEC的另一个好处就是可以避免多重检验问题Multiple Testing Problem。如果我们不把不同的指标加权结合起来分析而是单独比较它们就会出现多重检验的问题导致A/B测试的结果不准确。多重检验问题是A/B测试中一个非常常见的误区我在进阶篇中会具体讲解。
解决了单一评价指标不能应对复杂A/B测试的场景的问题后我们继续学习评价指标的最后一个要点波动性。在实际业务场景中评价指标的值会因各种因素的影响而发生波动。如果忽视了这一点就很有可能得出错误的测试结论。
## 如何衡量评价指标的波动性?
还记得我们上节课所学的音乐App要“增加自动播放功能”的例子吗
假如这个音乐App没有自动播放功能之前每个月的用户续订率的波动范围是\[65%-70%\]。我们在A/B测试中发现实验组有自动播放功能的续订率69%确实比对照组没有自动播放功能的续订率66%要高。
那么这个结果是可信的吗达到A/B测试的目的了吗答案显然是否定的。
虽然实验组的数据要比对照组的好,但是这些数据都在正常的波动范围内。所以,**增加自动播放功能**和**提升续订率**之间的因果关系,在这次实验中就没有被建立起来,因为这两组指标的差异也可能只是正常的波动而已。但是,如果我们事先不知道评价指标的波动性和正常波动范围,就很有可能建立错误的因果关系。
那么,如何才能知道评价指标的这个正常波动范围呢?
在统计学里面,指标的波动性通常用其平均值和平均值的标准差来表示,一个指标平均值的标准差越大,说明其波动性越大。**这里面要注意,**变量平均值的标准差又叫做标准误差********Standard Error****。关于标准差的概念你可以再回顾下第1节课的统计学基础。
评价指标的正常波动范围,就是置信区间。那具体该怎么计算呢?
在实践中,计算波动范围一般有统计公式和实践经验两类方法。
**第一,根据统计公式来计算。**
在统计学中,一般是用以下公式构建置信区间的:
置信区间 = 样本均值Sample Mean ± Z分数\*标准误差
根据中心极限定理当样本量足够大时大部分情况下数据服从正态分布所以这里选用z分数。在一般情况下我们选用95%的置信区间对应的z分数为1.96。
为了给你形象地展示置信区间我们在这里假设指标的样本均值为50、标准误差为0.1服从正态分布那么该指标的95%的置信区间为 \[50-1.96\*0.1 50+1.96\*0.1\] = \[49.8, 50.2\]。
你可能注意到了,我在用上面这个公式计算置信区间,假设了一个标准误差。但实际情况上,标准误差是需要我们来计算的。而且,计算标准误差是非常关键的一步。
对于简单的指标,主要是概率类和均值类,我们可以用统计公式来计算标准误差。
**概率类的指标**,常见的有用户点击的概率(点击率)、转化的概率(转化率)、购买的概率(购买率),等等。
这类指标在统计上通常服从二项分布在样本量足够大的情况下也可以近似为正态分布关于二项分布和正态分布你可以回顾下第1节课的相关内容
所以,概率指标的标准误差,我们可以通过下面这个公式计算:
$$
\\text { Standard Error }=\\sqrt{\\frac{p(1-p)}{n}}
$$
其中p代表事件发生的概率。
**均值类的指标**,常见的有用户的平均使用时长、平均购买金额、平均购买频率,等等。根据中心极限定理,这类指标通常也是正态分布。
所以,均值类指标的标准误差,我们可以这样计算:
$$
\\text {Standard Error}=\\sqrt{\\frac{s^{2}}{\\mathrm{n}}}=\\sqrt{\\frac{\\sum\_{i}^{n}\\left(x\_{i}-\\bar{x}\\right)^{2}}{n(n-1)}}
$$
其中s代表样本的标准差
n=样本大小,
$x\_{i}$=第i个用户的使用时长或者购买金额等
$\\bar{x}$= 用户的平均使用时长或者购买金额等。
**第二,根据实践经验来确定。**
在实际应用中,有些复杂的指标可能不符合正态分布,或者我们根本不知道它们是什么分布,就很难甚至是没办法找到相应的统计公式去计算了。这时候,要得到评价指标的波动范围,我们需要结合实践经验来估算。
**1.A/A测试**
我们可以跑多个不同样本大小的A/A测试然后分别计算每个样本的指标大小计算出来后再把这些指标从小到大排列起来并且去除最小2.5% 和最大2.5%的值剩下的就是95%的置信区间。
**2.Bootstrapping算法**
我们可以先跑一个样本很大的A/A测试然后在这个大样本中进行随机可置换抽样Random Sample with Replacement 抽取不同大小的样本来分别计算指标。然后采用和A/A测试一样的流程把这些指标从小到大排列起来并且去除最小2.5% 和最大2.5%的值得到95%的置信区间。
在实际应用中Bootstrapping会更流行些因为只需要跑一次A/A测试既节省时间也节省资源。
不过要注意的是即使对于那些简单的、符合正态分布的、可以用统计方法直接计算方差的指标如果有条件、有时间的话我也推荐你同时用统计公式和Bootstrapping两种方法分别计算方差。如果两者的结果差距较大就需要再去跑更多的A/A测试所以从两方面验证得到的结果会更保险。
到这里评价指标的选取方法以及波动性这个易错点我们就都学习完了。接下来我们进入到选取指标的最后一部分内容如何选取护栏指标为A/B测试提供质量保障。
## **护栏指标**
A/B测试往往改变的是业务中的某一部分指标评价指标所以我们很容易只关注短期的改变却失去了对业务的大局观比如长期的盈利能力/用户体验)的掌控或者统计上合理性的检查。因此在实践中,我会推荐**每个A/B测试都要有相应的护栏指标。**
接下来,我们就从业务品质和统计品质这两个维度,来学习如何选取护栏指标。这里我先用一张图,帮你总结下:
![](https://static001.geekbang.org/resource/image/bb/c7/bbf3c75f0cebaf6029e2c37e6ec197c7.png)
### **业务品质层面**
在业务层面的护栏指标是在保证用户体验的同时兼顾盈利能力和用户的参与度。所以我们通常会用到的护栏指标主要是三个网络延迟Latency、闪退率Crash Rate和人均指标。
1. **网络延迟**
**网页加载时间、App响应时间等都是表征网络延迟的护栏指标**。增加产品功能可能会增加网页或App的响应时间而且用户可以敏感地察觉出来。这个时候就需要在A/B测试中加入表征网络延迟的护栏指标确保在增加产品功能的同时尽可能减少对用户体验的影响 (一般是通过优化底层代码)。
2. **闪退率**
对于不同的应用程序App来说不管是在个人电脑端还是在移动端都有可能因为CPU、内存或者其他原因出现闪退导致程序突然关闭。
说到这儿我想和你分享一件趣事。我在用MS Word写这节课的内容时就出现了软件闪退。关键是我当时还没有保存心想几个小时的努力不就白费了嘛特别心灰意冷。万幸的是MS Word有自动保存功能。
你看,闪退发生的概率虽然不大,但是会严重影响用户体验。所以,在测试应用程序的新功能时,尤其是针对一些大的改动,闪退率就是一个比较好的护栏指标。
3. **人均指标**
人均指标可以从两个角度来考虑:
* **收入角度**,比如人均花费、人均利润等。
* **用户参与度**,比如人均使用时长、人均使用频率等。
这两个角度一般都是实际业务中追求的目标收入角度代表了产品的盈利能力用户参与度代表了用户的满意程度。但是在具体的A/B测试中我们往往会只关注产品的被测试部分的功能忽视了对大局的把握。
举个例子。应用商店优化了推荐算法后推荐的内容更贴近用户的喜好提高了用户对推荐内容的点击率。我们关注的评价指标点击率提高了是不是皆大欢喜呢不是的因为我们分析后发现这个新算法推荐内容中的免费App的比例增加了使得人均花费降低了进而影响到了应用商店的总体收入。
这个时候,我们可以把人均收入作为护栏指标,去继续优化推荐算法。
### **统计品质层面**
统计方面主要是尽可能多地消除偏差,使实验组和对照组尽可能相似,比如检测两组样本量的比例,以及检测两组中特征的分布是否相似。
造成偏差的原因有很多可能是随机分组的算法出现了Bug也可能是样本不够大还有可能是触发实验条件的数据出现了延迟不过更多的是具体实施中的工程问题造成的。这些偏差都会影响我们获得准确的实验结果而护栏指标就是我们发现这些偏差的利器
**1.实验/对照组样本大小的比例**
在设计A/B测试的时候我们就会预先分配好实验组和对照组通常是把样本等分。也就是说实验组和对照组样本大小的比例预期是1:1=1。但有的时候当实验结束后却发现两者的比例并不等于1甚至也没有很接近1。这就说明这个实验在具体实施的过程中出现了问题导致实验组和对照组出现了偏差。
**2.实验/对照组中特征的分布**
A/B 测试中一般采取随机分组来保证两组实验对象是相似的从而达到控制其他变量、只变化我们关心的唯一变量即A/B测试中的原因的目的。
比如说,如果以用户作为实验单位的话,那么,在试验结束后去分析两组数据时,两组中用户的年龄、性别、地点等基本信息的分布应该是大体一致的,这样才能消除偏差。否则,实验本身就是有问题的,得出的结果也不是可信赖的。
## **小结**
今天,我们学习了复杂业务场景下如何选取评价指标、评价指标的波动性这个易错点,以及如何选取护栏指标。
1. 有多个指标出现的情况下我们可以把它们结合在一起建立总体评价标准也就是OEC。这里面需要注意的一点是不同指标的单位、大小可能不在一个尺度上需要先要对各个指标进行归一化Normalization处理使它们的取值都在一定的范围内比如\[0,1\] 之后再进行结合,从而剔除指标单位/大小的影响。
2. 评价指标的正常波动范围就是置信区间。计算置信区间是一个重点对于分布比较复杂的指标我推荐用bootstrapping来计算对于概率类或者均值类的这些符合二项分布或者正态分布的指标建议同时用统计公式和Bootstrapping两种方法来计算。
3. 在实践中选取护栏指标的时候,我们主要是从业务品质和统计品质这两个维度来考虑。可以选择的护栏指标有,网络延迟、闪退率、人均指标、**实验/对照组样本大小的比例和实验/对照组中特征的分布等。**
## 思考题
你之前在工作中接触过的A/B测试都会有相应的护栏指标吗如果有的话是什么具体的指标呢这些护栏指标的作用又是什么呢
欢迎在留言区写下你的思考和想法,我们可以一起交流讨论。如果你觉得有所收获,欢迎你把课程分享给你的同事或朋友,一起共同进步!

View File

@ -0,0 +1,153 @@
# 05选取实验单位什么样的实验单位是合适的
你好,我是博伟。
上节课我们确定了实验的目标、假设以及各类指标那么今天我们就来讲一讲A/B测试的第三步如何选取合适的实验单位。
前面我提到A/B测试的本质就是控制变量实验。既然是实验那就要有实验单位。毕竟只有确定了实验单位我们才能在这个单位层面进行合理的样本分配Assignment从而决定哪些样本在实验组Treatment/Test Group哪些样本在对照组Control Group
谈到实验单位,你可能会问,这有什么难理解的,实验单位不就是用户吗?
其实,这是一个非常常见的认知误区。除了测试系统的表现外,在绝大部分情况下,准确地说,实验单位都是用户的行为。因为**我们在产品、营销、业务上所做的调整,本质上都是为了观察用户的行为是否会有相应的变化**。
那么问题就来了,很多单位都可以表征用户的行为。那到底是以用户为单位,以用户的每次浏览、访问为单位,还是以用户浏览的每个页面为单位呢?
这节课,我们就来学习下常用的实验单位有哪些,以及实践中选择实验单位的三大原则。
## **实验单位有哪些?**
虽然可以表征用户行为的实验单位有很多,但综合来看,我们可以从用户层面、访问层面和页面层面这三个维度来分别学习。
### **用户层面User Level**
用户层面是指,把单个的用户作为最小单位,也就是以用户为单位来划分实验组和对照组。
那么具体到数据中用户层面都包括什么呢其实主要是4种ID。
第一种ID是**用户ID**,也就是用户注册、登录时的用户名、手机号、电子邮箱,等等。
这类ID包含个人信息它的特点就是稳定不会随着操作系统和平台的变化而变化。用户ID和真实的用户一般是一一对应的关系也是代表用户的最准确的ID。
第二种ID是**匿名ID**一般是用户浏览网页时的Cookies。
Cookies是用户浏览网页时随机生成的并不需要用户注册、登录。需要注意的是用户使用的iOS和安卓操作系统也会随机生成Cookies但是这些Cookies仅限于该操作系统内部和用户浏览时使用的设备或者浏览器有很大关系。所以综合来看Cookies一般不包含个人信息而且可以被抹除因此准确度不如用户ID高。
第三种ID是**设备ID**。它是和设备绑定的一旦出厂就不可改变。设备ID虽然不会被抹除但是如果用户和家人、朋友共享上网设备的话它就不能区分用户了。所以设备ID的准确度也低于用户ID。
第四种ID是**IP地址**,它和实际的地理位置以及使用的网络都有关系。
同一个用户即使用同一个设备在不同的地方上网IP地址也是不同的。同时在一些大的互联网提供商中很多用户往往共享一个IP地址。所以IP地址的准确度是最差的一般只有在用户ID、匿名ID和设备ID都得不到的情况下才考虑使用IP地址。
这就是用户层面的4个实验单位它们的准确度从高到低的顺序是
用户ID > 匿名IDCookies/设备ID > IP地址。
为什么我要强调这4种ID类型的准确度呢这是因为**实验单位的准确度越高A/B测试结果的准确度才会越高。**
因此当我们确定了选择用户层面的实验单位时如果数据中有用户ID就优先选择用户ID如果数据中没有用户ID比如用户出于对隐私的考虑没有注册和登录或者是测试网页的功能无需用户注册和登录那么就可以选用匿名ID或者设备ID当这些ID都没有时再选择准确度最低的IP地址。
### 访问层面Visit/Session Level
访问层面是指把用户的每次访问作为一个最小单位。
当我们访问网站或者App的时候都会有后台系统来记录我们的每次访问动作。那么我们怎么定义一次访问的开始和结束呢
访问的开始很好理解就是进入到这个网站或者App的那一瞬间。但难点就在于怎么定义一次访问的结束。在一次访问中我们可能会点开不同的页面上下左右滑动一番然后退出也有可能只是访问了一下没有啥操作甚至都没有退出就进入了其他的页面或者App。
因此考虑到用户访问的复杂性通常情况下如果用户在某个网站、App连续30分钟之内没有任何动作系统就认定这次访问已经结束了。
如果一个用户经常访问的话就会有很多个不同的访问ID。那在进行A/B测试的时候如果以访问层面作为实验单位就可能会出现一个用户既在实验组又在对照组的问题。
比如我今天和昨天都访问了极客时间App相当于我有两个访问ID如果以访问ID作为实验单位的话我就有可能同时出现在对照组和实验组当中。
### **页面层面**Page Level
页面层面指的是把每一个新的页面浏览Pageview作为最小单位。
这里有一个关键词“新的”它指的是即使是相同的页面如果它们被相同的人在不同的时间浏览也会被算作不同的页面。举个例子我先浏览了极客时间的首页然后点进一个专栏最后又回到了首页。那么如果以页面浏览ID作为实验单位的话这两个首页的页面浏览ID就有可能一个被分配到实验组一个被分配到对照组。
到这里,我们就可以对比着理解下这三个层面了。
1. 访问层面和页面层面的单位比较适合变化不易被用户察觉的A/B测试比如测试算法的改进、不同广告的效果等等如果变化是容易被用户察觉的那么建议你选择用户层面的单位。
2. 从用户层面到访问层面再到页面层面,实验单位颗粒度越来越细,相应地可以从中获得更多的样本量。原因也很简单,一个用户可以有多个访问,而一个访问又可以包含多个页面浏览。
看到这儿你可能觉得信息量有些大这么多单位具体操作时到底怎么选呢不用担心下面我就通过一个“视频App增加产品功能来提升用户留存率”的具体案例来带你一步步地选出合适的实验单位。
## 一个案例:如何选择实验单位?
某视频App最近收到了不少用户反馈其中很大一部分用户希望在没有网络或者网络不好的情况下也能看视频。于是产品经理希望增加“离线下载”的功能来提高用户的留存率。
现在产品经理要通过A/B测试来看看增加“离线下载”的功能是否真的能提升留存。那应该怎么选取实验单位呢
如果把用户层面的ID作为实验单位的话即把每个用户作为最小单位来分组由于收集样本的时间比较紧迫可能收集到的样本量就不够。因此我们要去寻找颗粒度更细的实验单位来产生更大的样本量。所以我们可以选择访问层面或者页面层面作为实验单位。
数据分析师通过查看发现数据中有访问ID但没有pageview ID所以这里选择访问层面把每一次访问作为最小单位来分组因为一个用户可以产生多次访问。
这样一来样本量是足够了,但是我们分析计算实验结果之后发现,实验组的用户的留存率不仅没有上升,反而低于对照组。
这就很奇怪了,难道是因为“离线下载”功能导致用户体验变差了吗?这不是和之前用户反馈的结果相反了吗?
于是,我们再次对这些用户进行采访调研,得到的结论确实是用户体验确实变差了,但并不是因为用户不喜欢新增加的功能。那么问题究竟出在哪儿了呢?
其实,这里的问题就在于选择了不恰当的实验单位。在刚才的实验中,我们把每一次访问作为最小单位来分实验组和对照组,就造成了同一个用户因为有多个访问而被分到了不同的组。
所以,用户在实验组时可以使用新功能,但是被分到对照组时就会发现没有新功能,让用户很困惑。就好比,昨天你还在用一个很好用的功能今天突然消失了,是不是很沮丧呢?
所以,当业务的变化是用户可以察觉的时候,我建议你一定要选择用户层面作为实验单位。
在这种情况下如果样本量不足那就要和业务去沟通明确样本量不足需要更多的时间做测试而不是选取颗粒度更小的单位。如果不能说服业务方增加测试时间的话我们就要通过其他方法来弥补样本量不足会给实验造成的影响比如增加这次A/B测试使用的流量在总流量中的比例选用波动性方差更小的评价指标等方法我会在第9节课和你讲这些方法
回过头我们再看看这个案例,是不是可以提炼些选取实验单位的经验和坑点呢?没错儿,我将其归纳为了三个原则:
1. 保证用户体验的连贯性。
2. 实验单位应与评价指标的单位保持一致。
3. 样本数量要尽可能多。
掌握了这三条原则,你就能根据实际情况去选择最佳的实验单位啦!
## **确定实验单位的三大原则**
**1.保证用户体验的连贯性**
保证用户获得最好的体验几乎是所有产品的目标之一用户体验的连贯性尤其重要视频App的例子告诉我们**如果A/B测试中的变化是用户可以察觉到的****那么****实验单位就要****选择****用户层面****。**
否则,同一个用户同时出现在实验组和对照组,就会体验到不同的功能、得到不同的体验。这种体验的不连贯性,就会给用户带来困惑和沮丧,很容易导致用户流失。
**2.实验单位要和评价指标的单位保持一致**
为什么这么说呢?我们还得从统计学上入手去理解。
A/B测试的一个前提是实验单位相互独立且分布相同的Independent and identically distributed简称IID。如果两个单位不一致就会违反相互独立这一前提破坏了A/B测试的理论基础从而导致实验结果不准确。
举个例子。如果用A/B测试来检测音乐App推送新专辑的效果评价指标为用户的新专辑收听率收听新专辑的用户数量/收到推送的用户数量),这里评价指标是建立在用户层面上的,那么实验单位一般也要为用户。
假如我们把实验单位变为新专辑页面层面由于每个用户可以多次浏览该页面所以对于同一个用户的多次页面浏览每次页面浏览其实并不是独立的IID的假设前提就被破坏了那么实验结果也就变得不准确了。
所以,在选择实验单位时,你一定要记住:**A/B测试中的实验单位应与评价指标的单位保持一致。**
**3.样本数量要尽可能多**
在A/B测试中样本数量越多实验结果就越准确。但增加样本量的方法有很多我们绝对不能因为要获得更多的样本量就选择颗粒度更细的实验单位而不考虑前面两个原则。
所以我们选取实验单位的第三个原则就是:在保证用户体验连贯性、实验单位和评价指标的单位一致的前提下,可以尽可能地选择颗粒度更细的实验单位来增加样本量。
那么现在三个原则就讲完啦,我来给你总结下:前两个原则是一定要考虑和满足的,第三个原则则是锦上添花,有条件的情况下可以考虑。
## **小结**
这节课,我详细讲解了实践中常用的实验单位及其适用范围,也结合我的实际经验,给你总结了选取不同单位时需要考量的主要因素,让你真正理解并掌握背后的逻辑,从而帮助你在将来的实践中做出正确的判断。
我还给你总结了一个简化版的决策图,便于你回顾和记忆:
![](https://static001.geekbang.org/resource/image/65/d0/65b9c0277a2ef429ccd32832057147d0.png)
在实践中,我们要考虑的最重要的两点就是:**用户体验的连贯性、实验单位和评价指标单位的一致性**。毕竟用户是上帝,维持好的用户体验适用于所有的业务/产品。所以针对用户可见的变化比如UI的改进大部分的实验都是把用户作为最小的实验单位用户ID/匿名ID/设备ID同时也把用户作为评价指标的单位。
如果你想要更多的样本量同时A/B测试的变化是用户不易察觉到的比如推荐算法的提升可以用比用户颗粒度更细的访问或者页面作为实验单位。与此同时也要让评价指标与实验单位保持一致。
## **思考题**
你平时做A/B测试时是不是都以用户为单位的学完了这节课以后你可以再回想一下有些A/B测试是不是可以用其他单位为什么
欢迎在留言区写下你的思考和想法,我们一起交流讨论。如果你有所收获,也欢迎你把今天的内容分享给你的朋友,一起共同进步!

View File

@ -0,0 +1,220 @@
# 06 | 选择实验样本量:样本量越多越好吗?
你好,我是博伟。
前面聊了很多A/B测试的准备工作我们确定了目标和指标也选取了实验单位那么现在可以正式开始测试了吗?
先别着急,我们还需要解决正式测试前的最后一个问题:**到底多少样本量是合适的呢?**
## 打破误区:样本量并不是越多越好
如果我问你做A/B测试时多少样本量合适你的第一反应肯定是那当然是越多越好啊。样本量越多实验结果才会越准确嘛
从统计理论上来说,确实是这样。因为样本量越大,样本所具有的代表性才越强。但在实际业务中,样本量其实是越少越好。
为什么会这样说呢?我来带你分析一下。
要弄明白这个问题你首先要知道A/B需要做多长时间我给你一个公式**A/B测试所需的时间 = 总样本量 / 每天可以得到的样本量。**
你看,从公式就能看出来,样本量越小,意味着实验所进行的时间越短。在实际业务场景中,时间往往是最宝贵的资源,毕竟,快速迭代贵在一个“快”字。
另外我们做A/B测试的目的就是为了验证某种改变是否可以提升产品、业务当然也可能出现某种改变会对产品、业务造成损害的情况所以**这就有一定的试错成本**。那么,实验范围越小,样本量越小,试错成本就会越低。
你看,实践和理论上对样本量的需求,其实是一对矛盾。所以,我们就要在统计理论和实际业务场景这两者中间做一个平衡:**在A/B测试中既要保证样本量足够大又要把实验控制在尽可能短的时间内**。
那么,样本量到底该怎么确定呢?
你可能会说网上有很多计算样本量的网站我用这些网站来计算出合适的样本量难道不可以吗这当然也是一种方法但你有没有想过这些网上的计算器真的适用于所有的A/B测试吗如果不适用的话应该怎么计算呢
事实上,我们只有掌握了样本量计算背后的原理,才能正确地计算出样本量。
所以,这节课,我会先带你熟悉统计学上的理论基础,再带你进行实际的计算,让你学会计算不同评价指标类型所需的样本量大小。最后,我再通过一个案例来给你串讲下,帮助你掌握今天的内容。
## **样本量计算背后的原理**
这里咱们开门见山,我先把样本量的计算公式贴出来,然后再来详细讲解:
$$
\\mathrm{n}=\\frac{\\left(Z\_{1-\\frac{\\alpha}{2}}+Z\_{1-\\beta}\\right)^{2}}{\\left(\\frac{\\delta}{\\sigma\_{\\text {pooled}}}\\right)^{2}}=\\frac{\\left(Z\_{1-\\frac{\\alpha}{2}}+Z\_{\\text {power}}\\right)^{2}}{\\left(\\frac{\\delta}{\\sigma\_{\\text {pooled}}}\\right)^{2}}
$$
其中:
$Z\_{1-\\frac{\\alpha}{2}}$ 为 $\\left(1-\\frac{\\alpha}{2}\\right)$ 对应的 $Z$ Score。 $Z\_{\\text {Power}}$ 为 Power 对应的 Z Score。
$\\delta$ 为实验组和对照组评价指标的差值。
$\\sigma\_{\\text {pooled}}^{2}$ 为实验组和对照组的综合方差Pooled Variance
从公式中我们可以看出来样本量主要由α、Power、δ和$\\sigma\_{\\text {pooled}}^{2}$决定。我们要调整样本量的大小就靠这4个因素了下面我们就来具体聊聊每个因素怎样影响样本量n的。
这四个因素里,α、δ和 $\\sigma\_{\\text {pooled}}^{2}$我在前几节课已经讲过了所以在聊每个因素是如何影响样本量n这个问题之前我先来给你介绍下Power到底是什么。
### 如何理解Power?
Power又被称作Statistical Power。在第二节讲统计基础时我讲解过第二类错误率βType II Error。在统计理论中Power = 1β。
Power的本质是概率在A/B测试中**如果实验组和对照组的指标事实上是不同的Power指的就是通过A/B测试探测到两者不同的概率。**
可能这么说还是有些抽象不过没关系Power确实是比较难理解的统计概念我刚开始接触时也是一头雾水。所以我再举个例子来帮助你理解Power。
某社交App的用户注册率偏低产品经理想要通过优化用户注册流程来提高用户注册率。用户注册率在这里的定义是完成注册的用户的总数 / 开始注册的用户的总数 \* 100%
那么现在我们就可以用A/B测试来验证这种优化是否真的能提高用户注册率。
我们先把用户分为对照组和实验组,其中:
* 对照组是正常的用户注册流程,输入个人基本信息——短信/邮箱验证——注册成功。
* 实验组是,在正常的用户注册流程中,还加入了微信、微博等第三方账号登录的功能,用户可以通过第三方账号一键注册登录。
相信不用我说,你也能猜到,实验组用户的注册率肯定比对照组的要高,因为实验组帮用户省去了繁琐的注册操作。这就说明,在**事实上**这两组用户的注册率是不同的。
那么现在如果A/B测试有80%的Power就意味着这个A/B测试有80%的概率可以准确地检测到这两组用户注册率的不同得出统计显著的结果。换句话说这个A/B测试有20%的概率会错误地认为这两组用户的注册率是相同的。
可见Power越大说明A/B测试越能够准确地检测出实验组与对照组的不同如果两组**事实上**是不同的)。
我再给你打个比方。你可以把A/B测试看作是探测空中飞行物的雷达。那么专门探测小型无人机的雷达的灵敏度就要比专门探测大型客机的雷达的灵敏度高。因为探测物越小就越需要灵敏度更高的雷达。在这里雷达的灵敏度就相当于A/B测试的Power**Power越大就越能探测到两组的不同。**
所以啊你把Power看成A/B测试的灵敏度就可以了。
### 四个因素和样本量n的关系
认识完Power那现在就让我们来看下α、Power、δ和$\\sigma\_{\\text {pooled}}^{2}$这四个因素和样本量n的关系。
**1.显著水平Significance Levelα**
显著水平和样本量成反比显著水平越小样本量就越大。这个也不难理解。因为显著水平又被称为第一类错误率Type I Error**α**,想要第一类错误率越小,结果越精确,就需要更大的样本量。
**2.Power 1 β)**
Power和样本量成正比Power越大样本量就越大。Power越大就说明第二类错误率Type II Errorβ越小。和第一类错误类似想要第二类错误率越小结果越精确就需要更大的样本量。
**3.实验组和对照组的综合方差$\\sigma\_{\\text {pooled}}^{2}$**
方差和样本量成正比:方差越大,样本量就越大。
前面讲过,方差是用来表征评价指标的波动性的,方差越大,说明评价指标的波动范围越大,也越不稳定,那就更需要更多的样本来进行实验,从而得到准确的结果。
**4.实验组和对照组评价指标的差值δ**
差值和样本量成反比差值越小样本量就越大。因为实验组和对照组评价指标的差值越小越不容易被A/B测试检测到所以我们需要提高Power也就是说需要更多的样本量来保证准确度。
## **实践中该怎么计算样本量?**
在实践中绝大部分的A/B测试都会遵循统计中的惯例把显著水平设置为默认的5%把Power设置为默认的80%。这样的话我们就确定了公式中的Z分数而且四个因素也确定了两个α、Power。那么样本量大小就主要取决于剩下的两个因素实验组和对照组的综合方差$\\sigma\_{\\text {pooled}}^{2}$,以及两组评价指标的差值δ。因此样本量计算的公式可以简化为:
$$
\\mathrm{n} \\approx \\frac{8 \\sigma\_{p o o l e d}^{2}}{\\delta^{2}}
$$
现在,我们就可以用这个简化版的公式来估算样本量大小了。
其中方差是数据本身的属性代表了数据的波动性而两组间评价指标的差值则和A/B测试中的变量以及变量对评价指标的影响有关。
以上公式其实是在两组评价指标的综合方差为 $\\sigma\_{\\text {pooled}}^{2}$两组评价指标的差值为δ的情况下要使A/B测试结果**达到统计显著性的最小样本量。**
注意,这里重点强调“最小”二字。理论上样本量越大越好,上不封顶,但实践中样本量越小越好,那我们就需要在这两者间找一个平衡。所以由公式计算得到的样本量,其实是平衡二者后的最优样本量。
样本量计算出来了,接下来就要分对照组和实验组了,那这里就涉及到一个问题,实验组和对照组的样本量应该如何分配?在这个问题中,其实存在一个很常见的误解。那么接下来,我就带你来好好分析一下样本量分配这个问题。
### 实验组和对照组的样本量应保持相等
如果A/B测试的实验组和对照组样本量相等即为50%/50%均分,那么我们的总样本量(实验组样本量和对照组样本量之和)为:
![](https://static001.geekbang.org/resource/image/3e/b8/3e573dd7ef99eyy1b520a7c651ab30b8.png?wh=574*122)
你可能会问,实验组和对照组的样本量必须要相等吗?
虽然两组的样本量不相等在理论上是可行的,实践中也可以如此操作,但是我强烈不建议你这样做。下面听我来仔细分析。
一个常见的误解是如果实验组的样本量大一些对照组的样本量小一些比如按照80%/20%分配),就能更快地获得统计上显著的结果。其实现实正好相反:两组不均分的话反而会延长测试的时间。
为什么会这样呢?因为我们计算的达到统计显著性的最小样本量,是以每组为单位的,并不是以总体为单位。也就是说,**在非均分的情况下,只有相对较小组的样本量达到最小样本量,实验结果才有可能显著,并不是说实验组越大越好,因为瓶颈是在样本量较小的对照组上。**
相对于50%/50%的均分,非均分会出现两种结果,这两种结果均对业务不利。
* 准确度降低。如果保持相同的测试时间不变那么对照组样本量就会变小测试的Power也会变小测试结果的准确度就会降低
* 延长测试时间。如果保持对照组的样本量不变,那么就需要通过延长测试时间来收集更多的样本。
**所以只有两组均分,才能使两组的样本量均达到最大,****并且****使总样本量发挥最大使用效率,****从而保证****A/B测试更快更准确****地****进行。**
你可能会问这个样本量的估算是在A/B测试前进行的但我还没有做这个实验怎么知道两组间评价指标的差值δ呢
### 估算实验组和对照组评价指标的差值δ
这里呢,我们当然不会事先知道实验结束后的结果,不过可以通过下面的两种方法估算出两组评价指标的差值δ。
第一种方法是从收益和成本的角度进行估算。
业务/产品上的任何变化都会产生相应的成本,包括但不限于人力成本、时间成本、维护成本、机会成本,那么变化带来的总收益能否抵消掉成本,达到净收益为正呢?
举个例子我们现在想要通过优化注册流程来增加某App的用户注册率。假设优化流程的成本大约是3万元主要是人力和时间成本优化前的注册率为60%每天开始注册的人数为100人每个新用户平均花费10元。如果优化后的注册率提升为70%这样一年下来就多了3.65万元70%-60%\*100\*10\*365的收入这样的话一年之内的净收益就为正的这就说明此次优化流程不仅回本而且还带来了利润也就证明10%的差值是一个理想的提升。
当然,我们进行相应的改变肯定是希望获得净收益,所以一般我们会算出当收支平衡时差值为 $\\delta\_{\\text {收支平衡}}$,我们希望差值$\\delta \\geq \\delta\_{\\text {收支平衡 }}$。在这个例子中, $\\delta\_{\\text {收支平衡}}$= 8.2% (30000/10/100/365)所以我们希望的差值δ至少为8.2%。
第二种方法是如果收益和成本不好估算的话我们可以从历史数据中寻找蛛丝马迹根据我在第4节课介绍的计算指标波动性的方法算出这些评价指标的平均值和波动范围从而估算一个大概的差值。
比如说我们的评价指标是点击率通过历史数据算出点击率的平均值为5%,波动范围是\[3.5%, 6.5%\]那么我们对实验组评价指标的期望值就是至少要大于这个波动范围比如7%那么这时δ就等于2%7%5%)。
### 计算实验组和对照组的综合方差$\\sigma\_{\\text {pooled}}^{2}$
至于两组综合方差$\\sigma\_{\\text {pooled}}^{2}$的计算,主要是选取历史数据,根据不同的评价指标的类型,来选择相应的统计方法进行计算。评价指标的类型主要分为概率类和均值类这两种。
概率类指标在统计上通常是二项分布,综合方差为:
$$
\\sigma\_{\\text {pooled}}^{2}=p\_{\\text {test}}\\left(1-p\_{\\text {test}}\\right)+p\_{\\text {control}}\\left(1-p\_{\\text {control}}\\right)
$$
其中,$p\_{\\text {control}}$为对照组中事件发生的概率也就是没有A/B测试变化的情况一般可以通过历史数据计算得出$p\_{\\text {test}}=p\_{\\text {control}}+\\delta$,得出的是期望的实验组中事件发生的概率。
均值类指标通常是正态分布,在样本量大的情况下,根据中心极限定理,综合方差为:
$$
\\sigma\_{p o o l e d}^{2}=\\frac{2 \* \\sum\_{i}^{n}\\left(x\_{i}-\\bar{x}\\right)^{2}}{n-1}
$$
其中:
* n为所取历史数据样本的大小。
* $x\_{i}$为所取历史数据样本中第i个用户的使用时长/购买金额等。
* $\\bar{x}$为所取历史数据样本中用户的平均使用时长/购买金额等。
好了,到这里,这节课的核心内容就全部讲完了。不过为了帮助你更好地掌握这些公式原理和计算方式,现在我就用优化注册流程来增加用户注册率的这个例子,来给你串一下该怎么计算样本大小。
### 案例串讲
我们可以根据前面介绍总样本量的公式来计算样本量:
![](https://static001.geekbang.org/resource/image/a0/bd/a09ed6094ffyy19f3c7baac0f86610bd.png?wh=574*122)
首先我们来计算实验组和对照组之间评价指标的差值δ。在前面某App优化用户注册率的案例中可以看到我们从成本和收益的角度估算出$\\delta\_{\\text {收支平衡}}$=8.2%。
其次,我们来计算$\\sigma\_{\\text {pooled}}^{2}$。根据历史数据我们算出注册率大约为60%$p\_{\\text {control}}$),结合前面算出的$\\delta\_{\\text {收支平衡}}$=8.2%这时就可以把流程改变后的注册率定为68.2% 然后再根据概率类指标的计算公式求出$\\sigma\_{\\text {pooled}}^{2}$ = 60%\*(1-60%) + 68.2%\*(1-68.2%)=0.46。
最后我们在A/B测试中把实验组和对照组进行50%/50%均分,利用公式最终求得样本总量为:
![](https://static001.geekbang.org/resource/image/66/b0/6644bc25879e53d8681d9f3e2a4983b0.png?wh=480*128)
这样我们就求得每组样本量至少要有548完成了样本量的计算。
还记得开头我提到的网上各种各样的A/B测试的样本量计算器吗比如[这款](https://abtestguide.com/abtestsize/)。如果你仔细研究这些计算器就会发现这些计算器几乎全部是让你输入以下4个参数
1. 原始转化率 $p\_{\\text {control}}$Baseline Conversion Rate
2. 最小可检测提升δMinimum Detectable Lift或者优化版本转化率$p\_{\\text {test}}$ 。
3. 置信水平 (1-αConfident Level或者显著水平αSignificance Level
4. Statistical Power1-β)。
细心的你可能已经发现:上面这些参数都是计算概率类指标要用的参数,所以现在网上的这些样本量计算器只能计算概率类的指标,并不能计算均值类的指标,所以我们在使用时一定要注意要求输入的参数是什么,才能根据不同类型的指标选择正确的计算方法。对于均值类指标,现在网上还没有比较好的样本量计算器,在这种情况下我建议你通过公式来计算。
为了方便大家日后计算A/B测试中各类指标的样本量我会在专栏的最后一节课教大家用R做一个既可以计算概率类指标还可以计算均值类指标的线上样本量计算器敬请期待
## **小结**
这节课我们主要学习了怎么确定A/B测试所需的样本量大小了解了背后的理论基础我给你总结了影响样本量的四个因素其中向上箭头表示增大向下箭头表示减小。
![](https://static001.geekbang.org/resource/image/16/50/1643bf408780d8621a5d94149f1f1750.png?wh=7694*4263)
这里我想要再强调一下这节课介绍的计算A/B测试样本量的方法是测试前对样本量的估计值是为了让A/B测试结果达到统计显著性的最小样本量所以只要最终的实际样本量大于最小样本量就行。当然如果业务条件允许的话样本量自然是越多越好。
最后我想说的是当我们用网上的A/B测试样本量计算器时要注意输入的参数是什么因为绝大部分的计算器都是让用户输入转化率只能计算概率类的指标所以当计算概率类指标时我们可以用网上的计算器但如果是其他类的指标如均值类的话不能用网上的计算器还是得靠你自己利用公式计算测试所需的最小样本量或者跟着我在专栏的最后一起做一个既包含概率类指标又包含均值类指标的线上样本量计算器。
## **思考题**
你有用过网上的A/B测试样本量计算器吗有没有想过为什么网上大部分的样本量计算器只能算概率类的指标而不能计算均值类指标呢
欢迎在评论区留言、讨论,也欢迎点击“请朋友读”,把今天的内容分享给你的同事、好友,和他一起学习、成长。好,感谢你的收听,我们下节课再见。

View File

@ -0,0 +1,201 @@
# 07 分析测试结果:你得到的测试结果真的靠谱吗?
你好,我是博伟。
经过前面的确定目标和假设、确定指标、选取实验单位、计算所需样本大小后我们终于来到了A/B测试的最后一站分析测试结果。
在正式开始之前我想先问你一个问题拿到测试结果之后就可以马上进行分析了吗肯定不行。因为只有确定测试结果值得信赖之后才可以进行分析。其实分析A/B测试结果并不难难的是如何得出值得信赖的结果从而给业务以正确的指导。
为什么这么说呢接下来我就通过一个音乐App要提高用户升级率的例子和你先拆解下导致测试结果不可靠的因素有哪些然后再看看具体该怎么分析。
## 案例导入
通常情况下音乐App有两种盈利模式一种是提供免费音乐但是会在App中加广告通过广告赚钱一种是让用户付费订阅App享受高品质的免广告音乐。
我们的这款音乐App是两种盈利模式都有但是从长期盈利效果和用户体验来看采用用户付费订阅的模式会更胜一筹。因此我们计划在双十一前后针对App里的免费用户做一次促销吸引他们付费。
现在有这么两条广告语为了通过A/B测试验证哪条更有效将其分别放到实验组和对照组
* 对照组广告语:千万曲库免广告无限畅听,用户升级,免费试用半年!
* 实验组广告语即日起到11月15日用户升级免费试用半年
现在我们来完成A/B测试的整体设计方案。
* 确定目标:使更多的免费用户升级成为付费用户。
* 提出假设:通过在广告语中加入倒计时这种增加紧迫感的信息,能够提升免费用户的升级率。
* 确定实验单位免费用户的用户ID。
* 实验组/对照组随机分配50%/50%。
* 评价指标:用户升级率 = 点击广告升级的用户数 / 看到广告的用户数。
* 评价指标的波动范围:\[1.86%2.14%\]。
设计好了A/B测试的框架实施了A/B测试后我们就可以等待分析测试结果了。那什么时候可以查看测试结果停止A/B测试呢这是保证测试结果可信赖要解决的第一个问题。
## **什么时候可以查看测试结果?**
还记得我们上节课,在计算测试要达到显著性结果所需的最小样本量时,用到的一个公式吗?
A/B测试所需的时间 = 总样本量 / 每天可以得到的样本量。
结合这个公式再根据App中每天免费用户的流量我们可以计算出这个测试在理论上需要跑10天。
其实,这个公式只是理论上推导,**具体到A/B测试的实践中我们要确定测试时间除了考虑样本量的大小外还要考虑指标周期性变化的因素**。
如果指标具有强烈的周期性变化,比如周中和周末的变化很大,那么这时候的测试时间要包含至少一个周期的时间,以排除指标周期性变化的影响。
在音乐App这个案例中我们通过历史数据发现在周末升级的用户要比周中多。这就说明用户升级率这个评价指标会以每周为单位形成周期性的变化所以我们的测试至少要跑7天。而我们通过最小样本量已经算出了本次测试需要跑10天包含了一个周期所以我们可以放心地把测试时间定为10天。
我再多补充一句,如果计算出的测试时间小于一个周期的时间,那么最好也按照一个周期来算,这样做更为保险。
不过啊,在测试实际进行的过程中,有可能出现这样一种情况:在预计时间之前,评价指标出现了显著不同。这时候你就要小心了,如果提前结束测试,就会前功尽弃。我来给你具体解释下。
假设负责这个测试的数据分析师是第一次做A/B测试所以特别激动兴奋每天都在观测实验计算测试结果。在实验进行到第6天的时候样本量还没有达到预期他发现实验组和对照组的评价指标出现了显著的不同。这位数据分析师就在想**测试结果在预计时间之前达到了统计显著,这个实验是不是提前成功了呢?**
答案当然是否定的。
一方面因为样本量是不断变化的所以每次观测到的测试其实都可以算作新的实验。根据统计上的惯例A/B测试一般有5%的第一类错误率α也就是说每重复测试100次平均就会得到5次错误的统计显著性的结果。
这就意味着如果我们观测的次数变多的话那么观测到错误的统计显著结果的概率就会大大提升这是多重检验问题Multiple Testing Issue的一种体现。关于多重检验问题我会在第9节课中详细讲解。
另一方面提前观测到统计显著的结果这就意味着样本量并没有达到事先估算的最小样本量那么这个所谓的“统计显著的结果”就极有可能是错误的假阳性False Positive。“假阳性”是指两组事实上是相同的而测试结果错误地认为两组显著不同。
因此这位数据分析师还不能提前结束这次测试,仍然需要继续观测实验。
但**如果测试已经跑到了第10天样本量也达到了之前计算的量那是不是就可以开始分析A/B测试的结果了呢**
答案依旧是不行。
俗话说心急吃不了热豆腐为了确保实验在具体实施过程中按照我们预先设计的进行保证中途不出现Bug那么在正式分析实验结果前我们还要进行测试的合理性检验Sanity Check从而保证实验结果的准确性。
在第3和第4节课我们学过为了确保在具体实施过程中不会出现破坏统计合理性的Bug我们可以用护栏指标来保证统计品质。这时我们可以使用实验/对照组样本大小的比例和实验/对照组中特征的分布这两个护栏指标。这是保证测试结果可信赖,我们要关注的第二个问题。
## **保障统计品质的合理性检验**
### **检验实验/对照组样本量的比例**
我们预设的是实验组和对照组的样本量各占总样本量的50%,现在我们来看看实验过程中有没有发生什么变化。
各组样本量占总样本量的比例也是概率也是符合二项分布的所以具体的操作方法参见第4节课指标波动性的相关内容
* 首先根据二项分布的公式$\\sqrt{\\frac{p(1-p)}{n}}$算出标准误差。
* 然后以0.550%为中心构建95%的置信区间。
* 最后,确认实际的两组样本量的比例是否在置信区间内。
如果总的比例在置信区间内的话就说明即使总的比例不完全等于50%/50%,也是非常接近,属于正常波动,两组样本量大小就符合预期。否则,就说明实验有问题。那该如何确认和解决潜在问题呢?
回到我们的A/B测试上来我们实验组的样本量315256对照组的样本量为315174。通过公式我们求得标准误差为![](https://static001.geekbang.org/resource/image/11/68/11bbd59c8bb96e725b0279bc3f31f268.png)
计算出来的结果是0.06%我们构建了95%的置信区间\[50%-1.96\*0.06%, 50%+1.96\*0.06%\] = \[49.88%,50.12%\],也就是两组占比的波动范围,然后算出总体的实验组/对照组的样本量比例=50.01%/49.99%。
可以看到,两组占比均在置信区间内,属于正常波动。也就是说,两组样本量符合均分的预期,成功通过了**实验/对照组样本量的比例**这个合理性检验。那我们接下来就可以进行实验/对照组中特征的分布这个合理性检验了。
### **检验实验/对照组中特征的分布**
A/B测试中实验组和对照组的数据要相似才具有可比性。这里的相似我们可以通过比较两组的特征分布来判断。
常用的特征包括用户的年龄、性别、地点等基本信息或者可能影响评价指标的特征。比如在音乐App这个案例中我们还可以查看用户平时的活跃程度。如果这些特征在两组中分布比例相差较大则说明实验有问题。
一旦从合理性检验中发现了问题就不要急着分析实验结果了实验结果大概率是不准确的。我们要做的就是找到出现问题的原因解决问题并重新实施改进后的A/B测试。
找原因的方法主要有以下两种:
* 和工程师一起从实施的流程方面进行检查看看是不是具体实施层面上两组有偏差或者bug。
* 从不同的维度来分析现有的数据看看是不是某一个特定维度存在偏差。常用的维度有时间、操作系统、设备类型等。比如从操作系统维度去看两组中iOS和Android的用户的比例是否存在偏差如果是的话那说明原因和操作系统有关。
通过数据分析发现这两组数据中重要特征的分布基本一致说明两组数据是相似的。这就意味着我们已经通过了合理性检验接下来我们就可以分析A/B测试的结果了。
最后,我还想跟你强调一下,这两个合理性检验是都要进行的,这是保障实验质量的关键。这两种检验如果没有通过的话都会使实验结果不准确,具体来说,实验/对照组样本量的比例和实验设计不相同时会出现样本比例不匹配问题Sample Ratio Mismatch实验/对照组的特征分布不相似则会导致辛普森悖论问题Simpson Paradox这两类问题我们会在第11节课中重点讲解。
## **如何分析A/B测试的结果**
其实分析A/B测试的结果主要就是对比实验组和对照的评价指标是否有显著不同。那怎么理解“显著”呢其实“显著”就是要排除偶然随机性的因素通过统计的方法来证明两者的不同是事实存在的而不是由于波动性造成的巧合。
那具体怎么做呢?
首先我们可以用统计中的假设检验Hypothesis Testing计算出相关的统计量然后再来分析测试的结果。最常用的统计量是用P值P value和置信区间(Confidence Interval)这两种统计量。
你可能会说假设检验中有各种各样的检验Test我应该选取什么检验来计算P值和置信区间呢这里我们不需要理解这些检验的复杂理论解释只要熟悉实践中常用的3种检验方法的使用场景就可以了
1. Z检验Z Test
当评价指标为概率类指标时比如转化率注册率等等一般选用Z检验在A/B测试中有时又被称为比例检验Proportion Test来计算出相应的P值和置信区间。
2. T检验T Test
当评价指标为均值类指标时比如人均使用时间人均使用频率等等且在大样本量下可以近似成正态分布时一般选用T 检验来计算相应的P值和置信区间。
3. Bootstrapping
当评价指标的分布比较复杂在大样本量下也不能近似成正态分布时比如70%用户的使用时间OEC等一般采用Bootstrapping的方法从P值或者置信区间的定义来计算P值和置信区间具体方法请参见第三节课指标波动性的相关内容
现在我们已经拿到了如下的测试结果:
* 实验组样本量为315256升级的用户为7566升级率为2.4%。
* 对照组样本量为315174升级的用户为6303升级率为2.0%。
因为评价指标的波动范围是\[1.86%,2.14%\]所以我们可以得出实验组的升级率2.4%并不属于正常范围,很有可能显著不同于对照组。
接下来我们就可以通过P值法和置信区间法来分析这个测试结果验证我们的假设是否正确。
### **P值法**
首先我们可以采取P值法借助一些计算工具常见有Python、R还有网上的一些在线工具比如这个[网站](https://abtestguide.com/calc/)都可以计算P值。具体选择哪个工具根据自己的喜好来就可以。我个人比较喜欢选用R来计算
```
results <- prop.test(x = c(7566, 6303), n = c(315256, 315174))
```
因为用户升级率这个评价指标属于概率类指标,所以我们选择了专门针对概率类指标的函数[prop.test](https://www.rdocumentation.org/packages/stats/versions/3.6.2/topics/prop.test)。
通过计算我们可以得到P值 < $2.2 e^{-16}$
![](https://static001.geekbang.org/resource/image/75/c1/752de9629816c6124ed3d549cae367c1.png)
根据统计惯例一般我们会把测试的显著水平Significance Levelα定为5%统计上的约定俗成再把计算出来的P值和5%相比。当P值小于5%时说明两组指标具有显著的不同。当P值大于5%时,说明两组指标没有显著的不同。如果你对这块概念还不是很清楚,可以回顾下第二节课中假设检验的内容。
从上面的结果可以看出P值远远小于5%且接近于0说明两组指标具有显著的不同这就意味着实验组的广告语确实能提升免费用户的升级率。
### **置信区间法**
在第三节课介绍指标时我们学习了该怎样构建置信区间。现在我们要比较实验组和对照组的评价指标是否显著不同也就是看两者的差值是不是为0。这时候我们就要构建两组指标差值$\\left(p\_{\\text {test}}-p\_{\\text {control}}\\right)$的置信区间了。
置信区间的具体计算我们也可以借助Python和R等软件当然你也可以使用我在第二讲时介绍过的具体函数这里我们还是用R的[prop.test](https://www.rdocumentation.org/packages/stats/versions/3.6.2/topics/prop.test)这个函数。
其实当我们在上面用这个函数计算P值时R也顺便把95%的置信区间算出来了:
![](https://static001.geekbang.org/resource/image/75/c1/752de9629816c6124ed3d549cae367c1.png)
由图可见95%的置信区间为\[0.0033, 0.0047\]。
接下来我们需要比较一下两组指标是否有统计显著的不同也就是要看看这个置信区间是否包括0。
我们知道数值在置信区间内均为正常波动如果置信区间包括0的话就说明两组指标的差值也有可能为0两组指标是相等的。而如果置信区间不包括0的话就说明两组指标的差值不为0两组指标是显著不同的。
显然,\[0.0033, 0.0047\]这个置信区间是不包括0的也就是说我们的测试结果是统计显著的。那对应到业务上与对照组的广告语千万曲库免广告无限畅听用户升级免费试用半年相比带有紧迫感的实验组广告语实验组广告语即日起到11月15日用户升级免费试用半年能吸引更多用户升级也就验证了我们最开始的假设是成立的。
学到这里,我们发现**无论是P值法还是置信区间法都可以用来分析A/B测试结果是否具有统计显著性。那么在实际应用中该如何选择呢两者有什么差别吗**
其实,在大部分情况下这两种方法是通用的,只要选择一种就可以。但如果需要考虑实施变化后的收益和成本的关系时,我们就要选择置信区间法了。
因为要考虑收益和成本的关系时除了满足结果在统计上是显著的两组指标不相同差值的置信区间不包括0还不够更要让结果在业务上也是显著的两组指标不仅要不相等而且其差值δ >= $\\delta\_{\\text {收支平衡}}$,并且差值的置信区间的范围都要比$\\delta\_{\\text {收支平衡}}$大)。
## **小结**
这节课我们主要讲解了A/B测试中如何分析结果根据实践经验我给你总结了3个要点
* 切莫心急,一定要等到达到足够样本量时再分析测试结果。
* 分析结果前一定要做合理性检验来确保测试的质量否则一旦实施过程中出现Bug就会功亏一篑。
* 一定要根据指标和数据的特点,选择正确的分析方法来得出可以驱动业务的结论。
数据领域有一句名言“Garbage in, garbage out”意思就是“放进去的是垃圾产出的还是垃圾”。这句话放在A/B测试中同样适用如果A/B测试没有设置好或者虽然计划得很好但要是在实施过程中出现了问题也会得到错误的结果和结论从而给业务带来难以估量的损失。
所以前面我们用4节课来讲怎么设置实验今天又花了很多篇幅来介绍确保结果是可信赖的都是在给“分析测试结果”做铺垫。
好了今天这个音乐App的测试得到了显著的结果皆大欢喜。但是如果结果不显著又该怎么办呢
关于这个问题我们在第9节课再来好好讨论
## **思考题**
你觉得分析结果前的合理性检验还可以参考哪些护栏指标呢?为什么?
欢迎在留言区写下你的思考和答案,我们一起交流讨论。如果你觉得有所收获,欢迎你把这一讲分享给你的朋友,邀请他一起学习。

View File

@ -0,0 +1,141 @@
# 08 | 案例串讲从0开始搭建一个规范的A/B测试框架
你好,我是博伟。
经过前面几节课的学习相信你不仅掌握了做A/B测试的统计原理还知道了一个规范的A/B测试的流程是什么样的以及关键步骤中有哪些需要注意的地方。
今天这节课的内容整体来说不会太难主要是用一个音乐App提升留存率的案例来串讲一下我们学过的统计知识以及做A/B测试的几个核心步骤。
在学习这节课的过程中,一方面,如果你还有一些没有完全搞懂的内容,可以再针对性地复习下,查漏补缺;另一方面,之前几节课的内容容量都比较大,今天的案例串讲相当于帮助你理清思路,清空大脑,然后再有效地去吸收进阶篇的知识。
好了那我就通过下面音乐App这个案例来带你走一遍流程。
## 从业务问题出发确定A/B测试的目标和假设
咱们今天案例里的产品是一款音乐App用户只要每月付费就可以免广告畅听千万首音乐。当然除了最基本的播放音乐功能产品经理还给这款App设计了很多便利的功能比如用户可以把喜欢的音乐加入收藏夹可以创建不同的歌单还可以离线下载以便随时随地畅听自己喜欢的音乐等等。
数据科学家通过数据分析也发现,使用这些便利功能的用户往往有着高于平均水平的续订率,说明这些便利功能确实有助于提升用户留存。但是也有一个问题一直困扰着团队:这些功能虽然方便实用,有助于优化用户的听歌体验,但是使用率却一直不高。使用率不高,从长期来看,势必会影响用户留存。
团队通过用户调研才发现其中的原因。
由于App的页面设计崇尚简洁这些功能一般就存放在每首歌曲的功能列表中而用户往往需要点击两次才能使用第一次先点击功能列表第二次再点击具体的产品功能。一方面很多用户尤其是新用户并没有发现这些功能。另一方面点击两次才能使用用户体验并不好慢慢地也就退订了。
那么,我们现在的目标就非常明确了:**增加用户对产品功能的使用率。**
如何增加这个使用率呢?你可能会说,把每个功能都直接显示出来,让用户一目了然,不就可以提高它们的使用率了嘛!产品经理刚开始就想到了这一点,但是后来发现功能太多,全部直接显示出来,会让歌曲界面看起来非常杂乱,会让用户体验更糟糕。
既然产品交互界面的改动被否定了,那么我们可不可以主动告知用户这些功能怎么使用呢?
比如说,在新用户刚注册登录后就告知他们每个功能的用法。不过这个想法很快也被产品经理否定了,毕竟新用户刚登录时并不会用到所有功能。这很好理解,因为没有需求嘛,新用户在看到这些功能时肯定也没有什么反应,所以新用户在第一次登录时一般都会跳过产品功能介绍。
之前的A/B测试也验证了这一点。只有在用户有使用这个功能的需求时再告知他们才最有效果。
于是团队的假设就是:**在用户有需求时,通过弹窗的形式告知用户相关使用功能,以此提升相关功能的使用率**。这样的话,既能避免对每一个新用户的打扰,又能满足有需求的用户,两全其美。
## 确定A/B测试的评价指标
确定了目标和假设之后,就可以开始定义评价指标了。
团队准备先拿“把喜欢的音乐加入收藏夹”这个功能来做一个A/B测试验证以上的假设是否成立。
因为要在用户有需求的时候再告知用户,所以我们就需要一个条件来触发这个告知。那么,我们的首要任务就是**确定触发条件**只有当用户从来没有用过这个功能如果用户知道这个功能的话我们就没有必要告知了并且播放同一首歌曲达到x次时以此来判断用户对某首歌曲的喜爱程度我们才会给用户发送弹窗通知。
经过数据科学家的数据分析最终确定了x的最优值为4所以该功能的弹窗的最终触发条件为
* 该用户从来没用过“把喜欢的音乐加入收藏夹”这个功能。
* 该用户已经对某首歌听了4次当播放第5次时触发弹窗。
需要说明的是,因为弹窗是为了要告知用户,不需要重复提醒,所以每个符合触发条件的用户也只能收到一次,不能多次触发。
![](https://static001.geekbang.org/resource/image/1e/ac/1e95041c0ea9f0494b3c3110d04eb0ac.png?wh=3713*2613)
在这个A/B测试中把用户随机分为实验组和对照组每组50%。
* 在实验组中,如果用户满足了触发条件,系统就会发送弹窗提醒(如上图)。
* 在对照组中,用户不会收到弹窗提醒,不管是否符合触发条件。
确定了目标和假设,现在我们来具体定义下评价指标:
**“把喜欢的音乐加入收藏夹”功能的使用率 = 使用了“把喜欢的音乐加入收藏夹”的用户总数 / 实验中的用户总数。**
很明显,这是一个概率类的指标,也就是说在实验中的这些用户,使用了“把喜欢的音乐加入收藏夹”这个功能的概率有多少。不过,为了使我们的评价指标更加具体,也方便之后的计算,所以这里我们要搞清楚两个问题。
**第一个问题,如何定义“实验中的用户”?**
鉴于用户只有满足了条件才会触发弹窗,并不是所有在实验中的人都会受到影响,所以测试时不能用所有被分配在实验中的用户,因为这样就会引入没有受到影响的用户(那些被分配在实验中但是却没有满足触发条件的用户),从而降低测试的准确性。所以一定要注意,这里的“实验中的用户”应该是符合触发条件的用户(下图中虚线部分)。
在实验组中就是触发弹窗的用户,在对照组中则为符合触发条件的用户(因为对照组中的用户不管符合不符合触发条件都不会触发弹窗)。
![](https://static001.geekbang.org/resource/image/f0/2f/f0fe9cd48fc055f77d95d1413f8dac2f.png?wh=5930*4162)
**第二个问题,如何确定用户从触发弹窗开始到最终使用功能的时间窗口期呢?**
因为本次A/B测试是要检测弹窗是否会对相关功能的使用率有所提升而且每个用户触发弹窗的时间不同所以需要事先规定一个统一的时间窗口期来衡量比如触发后x天之内的使用率这样统一化是为了使指标更加清晰准确。
因为弹窗告知在这里具有及时性及时性也就是说在用户有需求时所以如果用户是受到弹窗的影响才使用相关功能时肯定会在看到弹窗不久后就使用了。我们这里就把x设为1即触发后1天内的使用率。
搞清楚了这两个问题,我们就可以把评价指标最终定义为:
**“把喜欢的音乐加入收藏夹”功能的使用率 = 在符合触发条件后1天之内使用了“把喜欢的音乐加入收藏夹”的用户总数 / 实验中的符合触发条件的用户总数**
光确定评价指标的具体定义还不够,为了更了解咱们的评价指标,得出准确的实验结果,我们还要从统计的角度来看下这个指标的波动性如何。
通过对历史数据的回溯性分析得到了用户在符合触发条件后一天之内使用相关功能的平均概率为2.0%通过统计公式最后求得该指标95%的置信区间为\[1.82%2.18%\]。这就说明如果测试结束后两组评价指标的值均落入这个波动范围内,则说明两组无显著不同,属于正常波动范围。
## 选取实验对象的单位
确定了A/B测试的评价指标后接下来我们要确定下实验对象的单位了。
因为本次实验的弹窗是用户可见的变化,而且评价指标是以用户为单位,**所以我们选择用户作为最小实验对象单位具体来说可以选用用户ID**,因为这些用户必须登录后才能享受音乐服务。
## 计算所需的样本大小和实验所需时间
我们继续往下走就该计算实验所需的样本量了。这里我们需要先确定4个统计量
* 显著水平Significance Levelα
* Power 1 β)。
* 实验组和对照组的综合方差 $\\sigma\_{\\text {pooled}}^{2}$。
* 实验组和对照组评价指标的差值δ。
一般A/B测试中显著水平默认为5%Power默认为80% 我们的案例也遵循这样的原则。至于两组评价指标之间的差值根据我们之前算出的波动性两者的差值要在0.18%以上才算是统计显著的变化那么我们就取0.2%。至于综合方差,因为是概率类的指标,我们就可以用统计公式直接算出。
确定了这些统计量后我们算出实验组和对照组各需要至少8.07万个符合触发条件的用户一共需要16.14万用户。而数据分析显示每天符合触发条件的新用户大约为1.7万人所以本次实验大约需要10天时间完成。
那么当我们完成了对整个A/B测试的设计工作后现在就让测试跑起来收集数据等到样本量达到预期时就开始分析测试结果。
## 分析测试结果
经过了一周多的等待我们的样本量终于达标可以来分析最终的结果啦。不过在分析结果前我们还要确保A/B测试在具体实施过程中符合我们最初的设计保证测试的质量品质这时候就要做合理性检验。
我们用最常见的护栏指标来做检验。
* 实验/对照组样本大小的比例是否为50%/50%。
* 实验/对照组中特征的分布是否相似。
经过分析发现本次A/B测试完全通过了这两项护栏指标的合理性检验说明试验实施的正如预期。
那么,现在我们就开始正式分析实验结果了。
* 实验组样本量为80723符合触发条件一天之内使用功能的用户为3124使用率为3.87%。
* 对照组样本量为80689符合触发条件一天之内使用功能的用户为1598使用率为1.98%。
![](https://static001.geekbang.org/resource/image/ca/4b/ca7cb0f33a0fa3e9d485d975ce1ff04b.png?wh=1360*440)
根据结果我们得到的P值接近于0而且远远小于5%同时我们计算出两组评价指标差值的95%的置信区间为\[1.72%2.05%\]不包括0说明这两组的使用率显著不同事实上实验组的使用率几乎等于对照组的两倍证明了在用户需要时的弹窗提醒确实有效果
得到这个振奋人心的结果后团队决定把“把喜欢的音乐加入收藏夹”功能的弹窗提醒推广到所有符合触发条件的用户同时也计划对其他功能的弹窗做类似的A/B测试来验证它们的效果。如果一切进行顺利的话就将这些弹窗全部推广长期来看肯定会增加用户的留存率
**小结**
通过这个案例串讲你肯定对做A/B测试的关键步骤有了更具体、更深层次的认识了。
![](https://static001.geekbang.org/resource/image/ae/c6/ae4816aeb940032499b93aa5bc6039c6.png?wh=7941*3779)
那么基础篇的内容到这里也就结束了。接下来我们就会进入到进阶篇的学习。
在进阶篇我会给你讲解更多偏经验和方法论的知识。针对做A/B测试时经常出现的一些问题我会给你讲解它们的成因给出解决办法。针对面试中常出现的一些考点我会结合我做面试官的经验来给你一些解题思路。
最后我还想强调一下学习这件事本来就是反复和持续的。进阶篇的内容会和基础篇有不少联系。所以在学习进阶篇的课程时我也希望你能够不断温习、思考之前学过的知识。待课程结束再回头看基础篇这些内容相信你会有一种“蓦然回首原来A/B测试如此简单”的畅快感和收获感。
**思考题**
回忆你之前做过或者经历过的A/B测试它们是否有这些基本的流程步骤如果缺少的话是缺少哪些步骤为什么如果还有其他步骤也和我分享一下吧。
如果你学完今天的案例串讲对A/B测试的流程、步骤有了更清晰的认识欢迎你点击“请朋友读”把今天的内容分享给你的同事、好友大家一起学习、成长。好感谢你的收听我们进阶篇的课程再见。

View File

@ -0,0 +1,184 @@
# 09 |测试结果不显著,要怎么改善?
你好,我是博伟。
通过“基础篇”的学习你已经掌握了A/B测试的整体流程那就可以参照这些流程设计一次A/B测试了。不过在具体实施的过程中你会因为业务的复杂性、没有严格遵守规范的流程或者数据本身的属性等不可避免地遇到一些问题。
没错儿这就是我在开篇词中和你说的A/B测试的实践性非常强你需要能够识别那些潜在的坑并找到相应的解决方法。所以在接下来的三节课里我会带你去看看我积累的经验曾经踩过的坑让你在实践时能提前规避少出错。
今天我们就先从一个很痛的问题开始吧。在第7节课我们学习如何得到可信赖的测试结果以及如何分析测试结果时非常顺利地得出了对照组和实验组指标显著不同的结果。不知道你脑海中会不会一直萦绕着这么一个问题我也是按照这个流程来设计A/B测试的啊为什么我的实验结果不显著呢我应该据此得出“两组指标事实上是相同的”结论吗
今天这节课,我们就来深入剖析“测试结果如何不显著怎么办”这个大家经常遇到的问题。
## **为什么会****出现“****实验结果不显著****”******
首先我们要搞清楚,为什么会出现“实验结果不显著”?有两方面原因。
* A/B测试中的变化确实没有效果所以两组的指标在事实上是相同的。
* A/B测试中的变化有效果所以两组的指标在事实上是不同的。但是由于变化的程度很小测试的灵敏度也就是Power不足所以并没有检测到两组指标的不同。
如果是第一种原因,就证明这个变化对产品/业务优化没有效果。那我们要考虑放弃这个变化,或者去测试新的变化。
如果是第二种原因那我们可以从A/B测试的角度进行一些优化和调整。具体来说就是通过提高Power来提高A/B测试检测到实验结果不同的概率。在第6节课我讲过了Power越大越能够准确地检测出实验组与对照组的不同。所以当我们提高了Power之后如果仍然发现测试结果不显著这样才能得出“两组指标事实上是相同的”的结论。
有什么方法可以提高Power呢
我们再来回顾下第6节课讲到的样本量估算公式
$\\mathrm{n}=\\frac{\\left(Z\_{1-\\frac{\\alpha}{2}}+Z\_{1-\\beta}\\right)^{2}}{\\left(\\frac{\\delta}{\\sigma\_{\\text {pooled}}}\\right)^{2}}=\\frac{\\left(Z\_{1-\\frac{\\alpha}{2}}+Z\_{\\text {power}}\\right)^{2}}{\\left(\\frac{\\delta}{\\sigma\_{\\text {pooled}}}\\right)^{2}}$
其中:
$Z\_{1-\\frac{\\alpha}{2}}$ 为 $\\left(1-\\frac{\\alpha}{2}\\right)$ 对应的 $Z$ Score。
$Z\_{\\text {Power}}$ 为 Power 对应的 $Z$ Score。
$\\delta$ 为实验组和对照组评价指标的差值。
$\\sigma\_{\\text {pooled}}^{2}$ 为实验组和对照组的综合方差 (Pooled Variance)。
在公式里我们找出影响Power的因素也就是样本量和方差。其中
* 样本量和Power成正比。即通过增大样本量就可以提高Power。
* 方差和Power成反比。即通过减小方差就可以提高Power。
具体来说实践中在有条件获得更大样本量的情况下可以选择增大样本量的方法来提高Power相对简单易操作。如果受流量或时间限制没有条件获得更多的样本量此时可以通过减小方差来提高Power。
接下来我就分别从增大样本量和减小方差这两个维度来讲解6种提高Power的具体方法。
## **如何通过增加样本量来提高Power**
实践中,用来增加样本量的方法主要有三种:**延长测试时间,增加测试使用流量在总流量中的占比,以及多个测试共用同一个对照组。**
### **延长测试时间**
对于延长测试时间你肯定不陌生我在第6节课讲样本量估算时就讲过。每天产生的可以测试的流量是固定的那么测试时间越长样本量也就越大。所以在条件允许的情况下可以延长测试的时间。
### **增加测试使用流量在总流量中的占比**
假设某个产品每天有1万流量如果我要做A/B测试并不会用100%的流量一般会用总流量的一部分比如10%,也就是测试使用流量在总流量中的占比。
为什么不使用全部流量呢?
一方面A/B测试有试错成本虽然出现的概率较低但是我们在测试中做出的任何改变都有可能对业务造成损害。所以使用的流量越少试错成本越低也就越保险。
另一方面,在大数据时代,对于互联网巨头来说,由于本身就拥有巨大的流量,那么产品本身做出的任何比较明显的改变,都有可能成为新闻。
比如要测试是否要增加一个新功能时公司并不想在测试阶段就把这个新功能泄露给用户以免给用户造成困扰。所以它们一般会先使用很小比例的流量来做A/B测试比如1%确定得到显著结果后再把A/B测试中的变化慢慢推广到100%的流量。
所以,在保持测试时间不变的情况下,还可以通过增加测试使用流量在总流量中的占比,来达到增加样本量的目的。
### **多个测试共用同一个对照组**
有时我们会在同一个产品上同时跑多个A/B测试比如我们想要提升推送的点击率就会在原推送的基础上改变推送的标题、推送的内容、推送的时间、推送的受众等等。
对于这四个不同的影响因素事实上改变每一个因素都是一个独立的A/B测试。那理论上我们就需要设计4个实验需要有4个实验组和4个对照组。
假设我们现在的可用流量一共是8万那么每组就有1万流量。但是你会发现这样流量的利用率太低了因为每个实验的对照组其实都是一样的原推送。但如果我们把4个对照组合并成一个这样的话就变成了4个实验组和1个对照组每组就有1.6万流量。
你看在同一个基础上想同时验证多个变化也就是跑多个A/B测试有相同的对照组的时候我们可以把对照组合并减少分组数量这样每组的样本量也会增加。这种测试又叫做A/B/n测试。
总结来说,在实践中:
* 如果时间允许,最常用的是延长测试时间的方法,因为操作起来最简单。
* 如果时间不充足的,可以优先选择增加测试使用流量在总流量中的占比,因为可以节省时间。
* 当有多个测试同时进行,而且对照组又相同的情况下,就可以把多个对照组合并成一个。
通过增加样本量来提高Power是实践中最常见的方法但是业务场景千变万化虽然不常见但有时候确实没有办法获得更多的样本比如时间紧迫同时已经使用了100%的总流量结果还是不显著那这个时候就要通过减少方差来提高Power了。
## **如何通过减****小****方差来提高Power**
实践中常用的减少方差的方法也有三种:**减小指标的方差,倾向评分匹配,以及在触发阶段计算指标**。
### **减小指标的方差**
减小指标的方差有两种方式。
**第一种方式:****保持原指标不变通过剔除离群值Outlier的方法来减小方差****。**
如果我们通过指标的直方图发现实验的指标分布中有很明显的离群值就可以通过设定封顶阈值Capping Threshold的方法把离群值剔除掉。
比如可以根据指标的分布只选取95%的取值范围然后把剩下的5%的离群值剔除掉。常见的指标比如电商中的人均花费或者音乐App中的人均收听时间由于会有些极少热衷于线上购物的用户花费居多或者音乐发烧友一直在听歌那么这些极少部分的用户就可能变成离群值从而增加方差。
**第二种方式:选用方差较小的指标。**
取值范围窄的指标比取值范围广的指标方差要小。比如点击者量比点击量的方差小(因为一个点击者可以产生多个点击,点击比点击者多,取值范围广);购买率比人均花费的方差小(因为购买率是表征买或不买的二元事件,只有两个取值,人均花费则可以是任何数量的金钱,理论上有无限的取值范围);收听率比人均收听时间的方差小;等等。
可以看到,对于表征类似的行为(比如买买买,听音乐,看视频,等等),概率类指标要比均值类指标的方差小。所以在满足业务需求的情况下,如果我们想要减少方差,就可以把均值类的指标转化成表征相似行为的概率类指标,也就是修改原定指标,选用取值范围窄的指标。
### **倾向评分匹配Propensity Score Matching**
倾向评分匹配简称PSM是因果推断的一种方法目的是解决实验组和对照组分布不均匀的问题。
你一定还记得我们在第7节课中学习过分析结果前要进行合理性检验那么它和PSM是什么关系呢
我来总结下。如果说合理性检验是帮我们确定两组分布是否相似的方法那么PSM就是帮我们找出两组中相似部分的回溯性分析方法。简单来说**两组的各个特征越相似,就说明两组的方差越小。**
PSM的基本原理就是把一组中的数据点找到在另一组和它们相似的数据点进行一对一的匹配这个相似性是通过比较每个数据点的倾向评分Propensity Score得到的。如果不理解倾向评分也没关系你只需要知道这一点就够了倾向评分越接近说明两个数据点越相似。这里的一个数据点指的就是A/B测试中的一个实验单位。
PSM的具体做法如下。
1. 首先,把我们要匹配的两组中每个数据点的各个特征(比如用户的性别,年龄,地理位置,使用产品/服务的特征等)放进一个[逻辑回归](https://en.wikipedia.org/wiki/Logistic_regression)Logistics Regression中。
2. 然后,算出每个数据点的倾向评分,然后再用诸如[最邻近](https://en.wikipedia.org/wiki/Nearest_neighbor_search)Nearest Neighbor等方法进行匹配。
3. 最后我们只需要比较匹配后的两组相似的部分即可。
[PSM](https://en.wikipedia.org/wiki/Propensity_score_matching)的原理有些复杂我放了一些资料链接你可以查看。不过幸运的是在主要的编程语言Python和R中都有相应的库比如Python中的[pymatch](https://github.com/benmiroglio/pymatch)和R中的[Matching](https://cran.r-project.org/web/packages/Matching/Matching.pdf),让我们的实施变得相对容易些。
在倾向评分匹配这个部分,你只需要记住一个结论就可以了,那就是:**PSM能够有效****地****减少两组的方差****。****通过比较倾向评分匹配后的两组的相似部分,我们可以来查看结果是否显著。**
### **在触发阶段计算指标**
在A/B测试中我们把实验单位进行随机分组的这个过程叫做分配Assignment 同时你要知道在有些A/B测试中比如在第8节课的案例中我们要测试的变化是需要满足一定条件才能触发的。
所以从分配和触发的关系来看A/B测试可以分为两种。
1. 变化不需要条件触发。所有用户在被分配到实验组后就都可以体验到A/B测试中的变化。
2. 变化需要条件触发。在被分配到实验组的所有用户中只有满足一定条件的用户才会触发A/B测试中的变化。
![](https://static001.geekbang.org/resource/image/a9/10/a9e72fb1a7cfdff6232974146f31a410.png)
实践中大部分的A/B测试都属于第一种而且也比较好理解。
但是要注意了我们这里讲的减小方差的方法只适用于第二种A/B测试简单来说就是在计算指标时只考虑每组符合触发条件黄圆圈的用户而不是考虑每组中的所有用户绿圆圈。这个不是很好理解我来举例说明下。
还记得第8节课中我们讲到可以利用弹窗的形式来告知用户“把喜欢的音乐加入收藏夹”新功能的A/B测试吗在A/B测试的设计中并不是在实验组的所有用户都会收到弹窗提醒的。
所以为了避免打扰到不相关的用户,把弹窗发送给需要这个功能的用户,我们事先规定了触发弹窗的规则:
1. 该用户从来没用过“把喜欢的音乐加入收藏夹”这个功能。
2. 该用户已经对某首歌听了4次当播放第5次时触发弹窗。
那么当我们计算案例中的评价指标时,各组中“把喜欢的音乐加入收藏夹”功能的使用率 = 各组中使用了“把喜欢的音乐加入收藏夹”的用户总数 / 实验中各组的用户总数。
这里的分母“实验中各组的用户总数”就只算满足弹窗触发规则的用户,而不是算所有被分配到实验中各组的用户,这就是在触发阶段计算指标。
这里要注意的是,在对照组也会有用户满足弹窗触发规则的,只不过因为他们在对照组,所以即使他们满足了弹窗触发规则,我们也不会给这些用户发弹窗,我们还是要统计这些人因为要用他们做分母来计算评价指标。
这里对数据埋点熟悉的小伙伴可能要问了:这些符合弹窗触发规则的对照组用户并没有触发弹窗,在数据中并没有相关的记录,我怎么在数据中去记录他们呢?
在工程实现上其实是有一个小技巧的:对于对照组的用户,如果他们符合触发规则,我们就给他们发送一个只有一个像素点的看不见的隐形弹窗,这样的话我们在数据中会有相关记录,方便之后的指标计算,同时又保证了对照组不会受到弹窗的影响。
通过把评价指标的分母变为满足弹窗触发规则的用户,在计算指标时就会排除掉数据中的噪音(那些被分配到实验中但是没有触发弹窗的用户),从而减小方差。
这种需要触发的A/B测试多出现在有固定的用户使用路径的业务中比如电商。在电商中用户一般有较明确的多层级的使用路径进入网站/App —> 浏览商品列表 —> 查看具体商品 —> 加入购物车 —> 购买。
在电商的A/B测试中一般是在用户进入网站/App时就被分配到实验组或者对照组如果我们测试之后的环节比如在“购物车”页面测试新功能这时候只有进入到“购物车”页面的用户才能触发A/B测试中的变化。
总体而言通过减少方差来提高Power的情况不多常见的是通过增加样本量来提高Power。如果真的遇到这种情况那么比较简单快速的方法就是减小指标的方差。当然如果有条件的话我还是推荐更加科学的倾向评分匹配PSM。那么对于在A/B测试中的变化存在触发的情况下就一定要在触发阶段计算指标。
## **小结**
为了解决A/B测试结果不显著的问题这节课我们主要讲解了提高A/B测试Power的6种方法我把它们从原理上分成了两大类
![](https://static001.geekbang.org/resource/image/3f/2c/3f9540dc32354f5539d7439cda32b72c.png)
你可以根据我对每种方法的介绍,以及在什么情况下选用哪种方法,灵活应用。
如果在尝试过这些方法后重新跑实验得出的测试结果还不显著那就说明两组的指标事实上是相同的我们就要放弃这个A/B测试中的变化用其他的变化来优化业务和产品。
最后再强调一下做出一个能真正提升业务的改变并不容易。从美国FLAG这些大厂披露出来的实验数据来看A/B测试得到真正显著的结果并最终实施变化的概率还不到三分之一。
所以也不要气馁,虽然不是每次实验都会有显著的结果,但是你可以从每次实验中学到新的知识(比如变化到底对业务有没有效果),沉淀新的方法论,还能从中发现业务、数据或者工程上潜在的一些问题。这些个人技能上的成长、业务流程上的完善,都是非常宝贵的。
## **思考题**
在某次A/B测试中你是不是也遇到过没能得到显著结果的情况你当时是怎么处理的有没有从实验中获得一些宝贵的经验
欢迎在评论区留言、讨论,也欢迎点击“请朋友读”,把今天的内容分享给你的同事、好友,和他一起学习、成长。好,感谢你的收听,我们下节课再见。

View File

@ -0,0 +1,160 @@
# 10常见误区及解决方法多重检验问题和学习效应
你好,我是博伟。
上节课我们讲了一个在做A/B测试时普遍存在的一个问题那么接下来我就根据自己这些年做A/B测试的经验精选了一些在实际业务中会经常遭遇的误区主要是多重检验问题、学习效应、辛普森悖论和实验/对照组的独立性这四大误区。
这四个误区,其实也可以被看作在实际业务中经常出现的几个问题。不过我在题目中之所以强调说这是误区,是因为你很可能会在这些问题的理解上产生一些偏差。
所以接下来我在讲这两节课时,会按照“问题阐述—问题解析—总结引申—课后思考”的范式来给你讲。也就是说,我会先带你深入剖析问题的成因,然后再举例分析这些问题在实践中的表现形式,最后给出对应的解决方法。
毕竟,在搞清楚问题原理的前提下,再学习问题的表现形式和解决方法,不仅你的学习效果会事半功倍,而且在实际应用时,你也能根据变化多端的业务场景,随机应变,灵活运用。
## **多重检验问题Multiple Testing Problem**
多重检验问题又叫多重测试问题或多重比较问题Multiple Comparison Problem指的是当同时比较多个检验时第一类错误率α就会增大而结果的准确性就会受到影响这个问题。我在基础篇讲A/B测试流程时就多次提到过它比如[第4节课](https://time.geekbang.org/column/article/321007)讲OEC的好处时还有[第7节课](https://time.geekbang.org/column/article/323696)讲什么时间才能查看测试结果时。
### **多重检验为什么会是一个问题?**
要搞清楚多重检验为什么会是一个问题我们还得先从第一类错误率α又叫假阳性率显著水平是测试前的预设值一般为5%说起。我在第2节课也讲过第一类错误率指的就是当事实上两组指标是相同的时候假设检验推断出两组指标不同的概率或者说由于偶然得到显著结果的概率。而且它在统计上的约定俗成是5%。
5%看上去是个小概率事件但是如果我们同时比较20个检验测试你可以先思考一下如果每个检验出现第一类错误的概率是5%那么在这20个检验中至少出现一个第一类错误的概率是多少呢
要直接求出这个事件的概率不太容易我们可以先求出这个事件发生情况的反面也就是在这20个检验中完全没有出现第一类错误的概率然后再用100%减去这个反面事件的概率。
这里我们用PA来表示出现事件A的概率。P每个检验出现第一类错误=5%那么P每个检验不出现第一类错误 = 1-5%=95%所以P20个检验中完全没有第一类错误= 95%的20次方。
这样我们就可以求得这个概率:
![](https://static001.geekbang.org/resource/image/d2/74/d2cfa2e855ca2d90815e5a00c8deb374.png)
这里的 P至少出现一个第一类错误的概率又叫做 FWER Family-wise Error Rate
通过计算得出来的概率是64%。这就意味着当同时比较20个检验时在这20个结果中至少出现一个第一类错误的概率是64%。看看,这是不是个很大的概率了呢?事实上,随着检验次数的增加,这个概率会越来越大,你看看下面这个图就明白了。
![](https://static001.geekbang.org/resource/image/5e/1e/5e933ae0b496b062295a26304d8ac51e.png)
图中的蓝线和橙线分别表示当α=5%和1%时FWER的变化情况。根据这个图我们可以得出两个结论
1. 随着检验次数的增加FWER也就是出现第一类错误的概率会显著升高。
2. 当α越小时FWER会越小上升的速度也越慢。
第一个结论讲的就是多重检验带来的问题。第二个结论其实为我们提供了一种潜在的解决方法:降低α。
这就意味着当我们同时比较多个检验时就增加了得到第一类错误的概率FWER这就变成了一个潜在的多重检验问题。
### **什么时候会遇到多重检验问题?**
你可能会说我平时都是一个测试一个测试去跑不会同时跑多个测试是不是就不会遇到这个问题了呢其实不是的实践中出现多重检验问题比你想象的要普遍得多它在实践中主要以4种形式出现。
**第一种形式当A/B测试有不止一个实验组时。**
当我们想要改变不止一个变量且样本量充足时,我们可以不必等测试完一个变量后再去测试下一个,而是可以同时测试这些变量,把它们分在不同的实验组当中。
每个实验组只变化一个变量在分析结果时分别用每个实验组和共同的对照组进行比较这种测试方法也叫做A/B/n测试。比如我想要改变广告来提升其效果那么想要改变的变量包括内容、背景颜色、字体大小等等这个时候我就要有相对应的3个实验组然后把它们分别和对照组进行比较。
这就相当于同时进行了3个检验就会出现多重检验问题。
**第二种形式当A/B测试有不止一个评价指标时。**
这个很好理解,因为我们分析测试结果,其实就是比较实验组和对照组的评价指标。如果有多个评价指标的话,就会进行多次检验,产生多重检验问题。
**第三种形式当你在分析A/B测试结果按照不同的维度去做细分分析Segmentation Analysis时。**
当我们分析测试结果时,根据业务需求,有时我们并不满足于只把实验组和对照组进行总体比较。
比如对于一个跨国公司来说很多A/B测试会在全球多个国家同时进行这时候如果我们想要看A/B测试中的变化对于各个国家的具体影响时就会以国家为维度来做细分的分析会分别比较单个国家中的两组指标大小那么此时分析每个国家的测试结果就是一个检验多个国家则是多个检验。
**第四种形式当A/B测试在进行过程中你不断去查看实验结果时。**
这种情况我在[第7节课](https://time.geekbang.org/column/article/323696)中提到过,因为当测试还在进行中,所以每次查看的测试都和上一次的不一样,每查看一次结果都算是一次检验,这样也会产生多重检验问题。
了解了多重检验问题在实践中的表现形式,那么在实践中该如何解决它呢?
### **如何解决多重检验问题?**
首先我要提前说明的是接下来我介绍的解决方法只适用于前3种表现形式。对于第4种表现形式的解决办法我已经在第7节课介绍了那就是不要在A/B测试还在进行时就过早地去查看结果一定要等样本量达到要求后再去计算结果所以这里就不再赘述。
鉴于多重检验问题的普遍性,在统计上有很多学者提出了自己的解决方法,大致分为两类:
1. 保持每个检验的P值不变调整α
2. 保持α不变调整每个检验的P值。
为什么会做这两种调整呢?
在[第2节课](https://time.geekbang.org/column/article/318454)我们介绍了用P值来判断假设检验的结果是否显著时是用检验中计算出的P值和α进行比较的。当P值<α时我们才说结果显著
所以我们要么调整α要么调整P值。前面我也说了降低α是一种解决办法最常用的调整α的方法是[Bonferroni校正](https://en.wikipedia.org/wiki/Bonferroni_correction)Bonferroni Correction其实很简单就是把α变成α/n。
其中n是检验的个数。比如α=5%那当我们比较20个检验时校正之后的α=5%/20 = 0.25%此时的FWER =$1-(1-0.25 \\%)^{20}$ = 4.88% ,和我们最初设定的α=5%差不多。
Bonferroni校正由于操作简单在A/B测试的实践中十分流行但是这种方法只是调整了α对于不同的P值都采取了一刀切的办法所以显得有些保守检测次数较少时还可以适用。
根据实践经验在检测次数较大时比如上百次这种情况在A/B测试中出现的情况一般是做不同维度的细分分析时比如对于跨国公司来说有时会有上百个markets Bonferroni校正会显著增加第二类错误率β这时候一个比较好的解决办法就是去调整P值常用的方法就是通过控制[FDR](https://en.wikipedia.org/wiki/False_discovery_rate)False Discovery Rate来实现。
控制FDR的原理比较复杂我就不展开讲了你只需要记住它指的是一类方法其中最常用的是[BH法](https://www.statisticshowto.com/benjamini-hochberg-procedure/)Benjamini-Hochberg Procedure就行了。BH法会考虑到每个P值的大小然后做不同程度的调整。大致的调整方法就是把各个检验计算出的P值从小到大排序然后根据排序来分别调整不同的P值最后再用调整后的P值和α进行比较。
实践中我们一般会借助像Python这样的工具来计算Python中的[multipletests](https://www.statsmodels.org/dev/generated/statsmodels.stats.multitest.multipletests.html)函数很强大里面有各种校正多重检验的方法其中就包括我们今天讲的Bonferroni校正和BH法我们使用时只需要把不同的P值输入选取校正方法这个函数就会给我们输出校正后的P值。
这里我总结一下虽然Bonferroni校正十分简单但由于过于严格和保守所以在实践中我会更推荐使用BH法来矫正P值。
聊完了多重检验问题我们再聊一下A/B测试中另一个常见问题——学习效应。
## **学习效应(Learning Effect)**
当我们想通过A/B测试检验非常明显的变化时比如改变网站或者产品的交互界面和功能那些网站或者产品的老客户往往适应了之前的交互界面和功能而新的交互界面和功能对他们来说需要一段时间来适应和学习。所以往往老用户在学习适应阶段的行为会跟平时有些不同这就是学习效应。
### **学习效应在实践中有哪些表现形式?**
根据不同的改变,老用户在学习适应期的反应也不同,一般分为两种。
第一种是积极的反应一般也叫做新奇效应Novelty Effect指的是老用户对于变化有很强的好奇心愿意去尝试。
比如把点击按钮的颜色,由之前的冷色调变成了非常艳丽的大红色,在短期内可能会使诸如点击率之类的指标提升,但是当用户适应了新的大红色后,长期的指标也可能回归到之前的水平。
第二种是消极的反应一般也叫做改变厌恶Change Aversion。指的是老用户对于变化比较困惑甚至产生抵触心理。
比如你经常光顾的电商网站,之前的加入购物车功能是在屏幕的左上方,但是交互界面改变后加入购物车的位置变到了屏幕的右下方,这个时候你可能就需要在屏幕上找一阵子才能找到,甚至找了一圈没找到,因为烦躁就关掉了页面,那么这时候短期的指标就会下降。
可以想象这些在学习适应期的不同反应一般是短期的长期来看这些短期反应也是会慢慢消退的。但是要注意的是这些短期的学习效应确实会给A/B测试的结果带来干扰使结果变得过于好或者过于差。那么我们如何来及时发现学习效应从而剔除学习效应带来的干扰呢
### **学习效应该如何检测?**
在实践中,主要有两种方法可以用来检测学习效应。
**第一种方法是表征实验组的指标随着时间(以天为单位)的变化情况。**
在没有学习效应的情况下,实验组的指标随着时间的变化是相对稳定的。
但是当有学习效应时,因为学习效应是短期的,长期来看慢慢会消退,那么实验组(有变化的组)的指标就会有一个随着时间慢慢变化的过程,直到稳定。
* 如果是新奇效应,实验组的指标可能会由刚开始的迅速提升,到随着时间慢慢降低。
* 如果是改变厌恶,实验组的指标可能会由刚开始的迅速降低,到随着时间慢慢回升。
当然我们在使用这个方法时需要注意:随着时间表征实验组的指标变化,但并不是让你每天去比较实验组和对照组的大小。如果每天都去比较,就会出现我们刚才讲的多重检验的问题。一定要记住,只有达到样本量之后才可以去比较两组大小,分析测试结果。
**第二种方法是只比较实验组和对照组中的新用户。**
学习效应是老用户为了学习适应新的变化产生的,所以对于新用户,也就是在实验期间才第一次登录的用户来说,并不存在“学习适应新的变化”这个问题,那么我们可以先在两组找出新用户(如果是随机分组的话,两组中新用户的比例应该是相似的),然后只在两组的新用户中分别计算我们的指标,最后再比较这两个指标。
如果我们在新用户的比较中没有得出显著结果(在新用户样本量充足的情况下),但是在总体的比较中得出了显著结果,那就说明这个变化对于新用户没有影响,但是对于老用户有影响,那么大概率是出现了学习效应。
在实践中我们可以用以上方法检测出学习效应,不过要想真正排除学习效应的影响,得到准确的实验结果,还是要延长测试时间,等到实验组的学习效应消退再来比较两组的结果。
## **小结**
今天这节课我们重点讲解了A/B测试中两个常见的实验误区多重检验问题和学习效应。我把这两个问题出现的原理、在实践中的多种表现形式以及相应的解决方法都给你详细讲解了。
不过我还想特别强调一下多重检验问题。多重检验问题的表现形式多种多样所以在A/B测试中尤为常见。我在刚接触A/B测试时就已经知道了这个问题的存在不过当时了解到的是它会在A/B/n测试中出现但后来才发现原来在做细分分析时也会出现多重检验的问题。
幸好这个问题发现得及时,才没有让整个测试功亏一篑。现在再去复盘,主要还是因为当时只知道多重检验问题的存在,了解其中一两个表现形式。但对于为什么会出现多重检验问题,什么时候可能会出现多重检验问题,我都不清楚,所以在问题出现新的表现形式时就没有及时识别出来。
这也是我想要跟你强调的,**知道为什么会出现这个问题,并且发现问题,和解决问题同样重要。**
**思考题**
结合自己的经验想一想过去有没有在A/B测试中遇到多重检验问题和学习效应以及当时是如何处理的呢
欢迎在评论区写下你学习本节课的收获和深度思考,如果今天的内容能帮你解答了一些困惑问题,也欢迎点击“请朋友读”,和他一起学习、成长。感谢你的收听,我们下节课再见。

View File

@ -0,0 +1,150 @@
# 11 | 常见误区及解决方法(下):辛普森悖论和实验组/对照组的独立性
你好我是博伟。这节课我们继续来学习A/B测试中的常见误区和解决方法。
今天我们要解决的问题,是辛普森悖论和实验/对照组的独立性。这两个问题在A/B测试的实践中也是常客。
对于辛普森悖论呢由于遇到的次数太多以至于我每次做A/B测试结果的细分分析时都要先检查该细分领域在两组的比例是否符合两组整体的比例来确保实验结果的准确。
对于实验/对照组独立性被破坏这个问题我在早期做营销预算固定的A/B测试时也经常遇到但是它的表现形式其实非常多变各个业务类型中都有它的身影所以就需要有针对性地进行分析。
听了我的经历你可能还是不太明白这两个问题到底是什么它们对A/B测试有什么影响不用担心今天我就会为你深度剖析带你在实践中去识别它们并解决它们
## **辛普森悖论**
听到“辛普森悖论”这个概念你可能会有点迷茫不知道它具体说的是什么问题。所以我还是先用音乐App来举个例子告诉你辛普森悖论是什么以及它在A/B测试中到底指的是什么。
一款音乐App优化了新用户的注册流程并且希望通过A/B测试在北京、上海这两个主要的市场来验证优化注册后的转化率是否有所提升。
* 实验组:使用优化后的注册流程。
* 对照组:使用原有的注册流程。
在设计好了实验之后实验组和对照组的样本量均分。等跑完实验获得了足够的实验样本之后结果得到实验组的转化率为1.44% 对照组的转化率为2.02%。
这就很让人意外了,为什么实验组(使用优化后的注册流程)的转化率,反而比对照组(使用原有的注册流程)的转化率要低呢?
而且更让人意外的是,当你在分别分析北京和上海这两个市场时,会发现它们的实验组转化率都比对照组的要高。
![](https://static001.geekbang.org/resource/image/48/5a/486e5fcb4e67a20b909e3620d107575a.png)
在确认了数据和计算没有错误后,你的本能反应可能会是:同样的数据,我在进行总体分析和细分分析时得出的结果却完全相反,这怎么可能呢!
这就是在数学理论中很有名的辛普森悖论,它是指当多组数据内部组成分布不均匀时,从总体上比较多组数据和分别在每个细分领域中比较多组数据可能会得出相反的结论。在数学上,它的形式要更加抽象:即使 $\\frac{a}{b}<\\frac{A}{B}, \\frac{c}{d}<\\frac{C}{D}$那么$\\frac{a+c}{b+d}>\\frac{A+C}{B+D}$也是可能成立的。
真是奇怪了这个有些反直觉的现象在数学上竟然是完全可以成立的。而这种不可思议的现象也出现在了A/B测试中。
究其原因,在这个例子当中,其实是因为实验组和对照组虽然在总体上实现了我们在设计实验时要求的样本量均分。但是在北京和上海这两个细分市场中却分布不均匀,没有实现样本量均分。
![](https://static001.geekbang.org/resource/image/5b/fd/5b1257c8294e32688cd4b70a662b71fd.png)
关于细分分析我也再补充一下。在A/B测试的实践中我们一般根据市场不同的城市、国家等设备类型手机、桌面、平板等用户的互动或者花费程度轻度、中度、重度等来进行细分分析。
好了还是回到我们的案例当中。如果你对基础篇的内容掌握得足够扎实就会发现我们在第7节课中讲分析结果前的合理性检验时其中有一项就是**检验实验/对照组中特征的分布,意思是说要检验两组中的特征分布是否相似,是否符合实验设计要求的比例分布。**
所以,咱们今天讲的辛普森悖论,实际上就是**由于实验中两组在不同细分领域中的分布不均造成的。**
当然在音乐App这个例子中只有两个细分领域。如果是多个细分领域比如要在全国几十个大城市进行A/B测试那么**只要是实验组和对照组在任何一个细分领域的分布与实验设计的不相符时,都有可能出现辛普森悖论。**
所以既然辛普森悖论是我们做A/B测试时需要规避的问题/现象,那有没有合适的解决方案呢?
**其实,解决一个问题的最好方法就是减少或者避免它的产生**。就像我刚才说的,辛普森悖论是两组在不同细分领域中的分布不均造成的。想想看,如果我们在分析测试结果前做好了合理性检验,那出现辛普森悖论的几率就会大大减小。
不过,如果在分析结果前我们没有做好合理性检验,那还有别的办法可以解决吗?
当然有,不过会比较麻烦。如果我们在进行总体分析和细分分析时发现了辛普森悖论,最好的解决办法就是重新跑实验,看看两组在不同细分领域的分布不均会不会消失。
如果分布不均的情况还是没有消失,那就说明这很可能不是偶然事件。这个时候就要检查看看是不是工程或者实施层面出现了问题,由此造成了分布的不均匀。如果是工程层面出现了问题,那就要有针对性地去解决。
当然如果时间比较紧迫,没有时间重新跑实验和检查问题的原因,那么就以细分领域的结果为准,因为总体结果出现了辛普森悖论会变得不准确。不过这里因为是比较细分领域,会出现多重检验问题,要用我们上节课讲的方式做相应的处理。
好啦,现在你对辛普森悖论的理解肯定比之前更深刻了,那么我们接下来聊聊实验组和对照组的独立性这个问题。
## **实验组和对照组要相互独立**
首先要说明的是A/B测试有一个前提****实验组和对照组的实验单位是要相互独立的,意思是说测试中各组实验单位的行为仅受本组体验的影响,不能受其他组的影响****。这个前提又叫做Stable Unit Treatment Value Assumption SUTVA
针对实验组与对照组保持独立的问题可能很多人都会觉得这有什么好说的都分成两个组了肯定是各自独立的啊在实践中还真不是这样的。我们在做A/B测试时经常会在不知不觉中因为实践中碰到了一些业务场景导致检验两组的独立性被破坏了而这就会破坏实验结果的准确性。
这还是因为A/B测试的本质是因果推断所以只有在实验组和对照组相互独立互不干扰的情况下如果测试结果有显著的不同那么才能把这个显著不同归因成实验组相对于对照组的变化否则就很难建立准确的因果关系。
这么说你可能理解得还不够深刻下面我就结合具体的业务场景来看下在A/B测试中两组实验单位的独立性都是如何被破坏的。
### **破坏两组独立性的表现形式有哪些?**
在A/B测试中两组的独立性被破坏主要表现在社交网络/通讯,共享经济以及共享资源这三类业务上,下面就让我来为你一一讲解。
**第一类业务是社交网络/通讯类业务。**
这类业务主要是用户之间的交流和信息交换典型代表包括微信、微博、领英Linkedin、语音/视频通讯、电子邮件,等等。
在这类业务中会存在网络效应。网络效应也就是网络中相邻的各个节点会相互影响。如果节点A在实验组而它相邻的节点B在对照组这时候两者就不是独立的。
我举个例子来帮助你理解。某社交App改进了信息流的推荐算法通过推荐给用户更相关的内容来增加用户的互动现在呢我们想通过A/B测试来检测算法改进的效果。
* 对照组:使用旧算法。
* 实验组:使用改进后的新算法。
* 评价指标:用户的平均使用时间。
这样我们就可以做个假设实验组的用户A体验到了改进后的新算法看到了更多喜欢的内容就花了更多的时间在这个App上同时也在App中分享了更多有趣的内容和朋友有了更多的互动。而他的好友用户B恰巧在对照组那么当B看到A分享的内容和互动后可能也会花更多的时间在App中浏览并且参与到和A的互动当中即使B并没有体验到改进后的算法。
这就是一个典型的A/B测试中网络效应的例子。在实验组的用户A会因为A/B测试中的变化而改变使用行为并且这个行为上的改变会通过网络效应传递给在对照组的好友B从而改变了用户B的使用行为这就使得对照组也间接受到了实验组中新算法的影响。
这显然违背了我们在对照组给用户旧算法体验的实验设计,所以测试结果很可能是两组的指标都升高,造成结果不准确。
**第二类业务是共享经济类业务。**
共享经济类业务一般是双边市场Two-Sided Market即公司只提供交易平台供给方和需求方均是用户。典型代表包括淘宝、滴滴、Uber、共享单车、共享租赁、爱彼迎(Airbnb),等等。在这类业务中,由于供需关系是动态平衡的,一方的变化必然会引起另一方的变化,从而造成实验中两组相互影响。
比如说我们在用A/B测试验证不同的优化是否有效时往往只能一次验证一个优化。如果我们用A/B测试检验一个需求侧的优化就要在需求侧分成实验组和对照组这样实验组由于受到了优化就会导致需求增加。那么在供给一定的情况下更多的供给流向了实验组就会造成对照组的供给减少对照组的用户体验会变得更差从而进一步打击对照组的需求。
我给你举个例子假设某共享打车服务优化了用户在App中的打车流程现在我们要通过A/B测试来验证这个优化是否有效果。
这里实验组依然是使用优化流程对照组则使用旧流程。实验组的用户因为流程的优化打车更加方便吸引了更多的司机。而由于司机的数量是稳定的这就会导致可供对照组选择的司机减少对照组的用户更难打到车用户体验变差那么通过A/B测试得出的流程优化的效果相比较对照组就会被高估。
**第三类业务是共享资源类业务。**
有些共享资源类业务有固定的资源或者预算,最常见的就是广告营销了。
在营销预算固定的情况下我们用A/B测试来验证不同广告的效果。如果发现我们在实验组改进后的广告效果更好点击率更高那么这就会造成对照组的广告预算减少从而影响到对照组的广告效果。因为线上的广告大部分是按点击次数付费的所以这时候实验组广告花的钱就越多在营销预算固定的情况下就会抢占对照组的预算。以此来看通过A/B测试得出的实验组的广告效果就会被高估。
### **如何避免破坏两组的独立性?**
那么,从我刚才讲的三类业务中你也能看出来,在实际业务场景中,由于违反两组实验单位独立性的表现形式和原因有很多,所以也会有不同的方法来解决,不过总的原则就是**通过不同形式的分离来排除两组之间的干扰****。**具体而言主要有以下4种分离方法
**第一种方法是从地理上进行分离。**
这类方法主要适用于受到地理位置影响的线下服务,比如共享出行和共享租赁,这种本地化的服务一般不同的地域之间不会有干扰,这时候就可以按照不同的市场来分类。
最常用的是从城市这个维度进行分类。比如把北京的用户作为实验组,把上海的用户作为对照组,这样就可以排除两组间的干扰。需要注意的是,这里选取的不同市场要尽量相似,具有可比较性。我所说的相似,包括但不限于:该项业务在当地的发展情况,当地的经济状况,人口分布情况等等。
**第二种方法是从资源上进行分离。**
这类方法主要适用于由于共享资源造成的两组之间的干扰。具体操作就是A/B测试中每组的资源分配比例要和每组样本量的比例一致。比如在做广告营销中如果通过A/B测试比较不同组的广告的效果那么每组分配的广告预算的比例要和每组的样本量比例相等比如两组样本量均分时广告预算也要均分这样两组之间的广告预算才能互不干扰。
**第三种方法是从时间上进行分离。**
这类方法主要适用于不易被用户察觉的变化上,比如算法的改进。这类方法的原理就是实验组和对照组都是同一组用户,在一段时间内实施变化,给他们实验组的体验,然后在另一段时间内不实施变化,给他们对照组的体验。
需要注意的是,这个时间段的单位可以是分钟、小时或者天,这样的话因为在同一时间内所有的实验单位都属于同一组,也就不存在不同组之间的干扰了。不过用这种方法时要特别注意用户的行为可能会在每天的不同时段,或者周中/周末有所波动。如果有周期性波动的话,就要在比较时尽量在不同周期的同一个阶段进行比较,比如只把周中和周中比较,周末和周末比较,但是不能把周中和周末比较。
**第四种方法是通过聚类Clustering的方法进行分离。**
这种方法主要适应于社交网络类业务。社交网络中用户之间的连接其实也不是均匀的有远近亲疏那就可以通过模型的方法根据不同用户之间交流的程度来分离出不同集群cluster每个cluster都会有不同的联系很紧密的用户我们可以把每个cluster作为实验单位随机分组这样就能从一定程度上减少不同组之间的干扰。
这种方法比较复杂,实施难度大,需要数据模型和工程团队的支持,有兴趣的话可以参考[Google](https://www.unofficialgoogledatascience.com/2018/01/designing-ab-tests-in-collaboration.html)和[Linkedin](https://engineering.linkedin.com/blog/2019/06/detecting-interference--an-a-b-test-of-a-b-tests)的经验。
## **小结**
在这节课,我具体讲解了辛普森悖论和实验/对照组的独立性这两个常见的实验误区,以及在实践中的常用解决办法。相信通过这节课的学习,你能够在实践中及时发现并解决它们。
另外啊,我把这两节误区系列的内容呢也总结成了一张图,放在了文稿里,你可以保存下来,方便之后的复习与巩固。
![](https://static001.geekbang.org/resource/image/f1/60/f15c4e61a25649f4bd8316ec9edb1860.png)
到这里我们的常见实验误区系列就告一段落了通过这两节课的学习你也体会到了我在讲课时不断说的真实业务场景的复杂多变潜在的各种各样的坑这些误区和问题其实都是通过一次次A/B测试去积累试错得出的所以就更加体现出了实践在A/B测试中的重要性。
如果你能在A/B测试中及时识别和解决这些常见的潜在的误区不仅能为公司挽回潜在的损失获得持续的增长也是区别你和A/B测试新手的重要标志。
## **思考题**
结合自己的经验想一想过去有没有在A/B测试中遇到辛普森悖论和实验/对照组的独立性被破坏的情况?以及当时是如何处理的呢?
欢迎把你的思考和收获分享在留言区,我们一起学习、讨论。如果这两节的误区系列帮你解答了一些疑难问题,也欢迎你把课程分享给你的同事、好友。我们下节课再见。

View File

@ -0,0 +1,121 @@
# 12什么情况下不适合做A/B测试
你好,我是博伟。
我们知道A/B测试是帮助公司实现持续增长的利器。然而没有任何一种方法能解决所有的问题让我们一劳永逸。A/B测试也是如此。
A/B测试可以解决大部分因果推断的问题。但在有些因果推断的业务场景下A/B测试就不适用了。这个时候我们就需要另辟蹊径换一种思路和方法来解决问题。
所以今天这节课我们就来学习A/B测试在什么情况下不适用如果不适用的话有哪些相应的解决方法。
## **A/B测试在什么情况下不适用**
在实践中主要有3种情况下A/B测试不适用
### **当****没有办法控制想要测试的变量****时**
A/B测试是控制变量实验它的一个前提就是我们必须可以控制想要测试的变量的变化这样才能人为地给实验组和对照组的实验单位不同的用户体验。但是在有些情况下我们就没有办法控制变量的变化。
你可能会有疑问,有这样的变量吗?
当然是有的,主要是用户个人的选择。我们能够控制的变量其实都是在产品和业务端,但是对于用户个人的选择,我们其实是没有办法、也不可能去控制的。毕竟用户都是有自由意志的,所以我们所有的营销方法都是努力去说服用户,但最终选择权还是在用户手里。
比如我们想要了解用户从QQ音乐换到网易云音乐后使用情况的变化那更换音乐App就是我们想要测试的变量。需要注意的是我们无法帮助用户决定是否要更换音乐App的行为因此我们也没有办法做到真正的随机分组。
你可能会说我们可以通过营销给用户优惠甚至付费让用户去更换音乐App这在实践上是可行的但是在实验中就会产生新的偏差。因为对于外界激励不同的用户会有不同的反应我们可能只研究了对外界激励有反应的用户而忽略了对外界激励没有反应的用户。这样得到的实验结果是不准确的。
### **当有重大事件发布时**
重大事件的发布,主要指的是新产品/业务的发布,或者涉及产品形象的一些改变,比如商标/代言人的改变我们往往是不能进行A/B测试的。因为凡是重大事件的发布会都想要让尽可能多的用户知道并且也花了大量营销的钱。在当下这个信息流通极度发达的互联网时代不存在我公开发布了一个新品只有一小部分用户知道这种情况即使是中小企业也是如此。
比如苹果公司每年的新品发布会并不会、也不可能事先去做大规模的用户A/B测试来看看新品的效果如何然后再决定是否要发布。
再比如,一个公司如果想要改变自己的商标,就不能事先把用户进行分组,让实验组的用户接触新商标,对照组的用户接触旧商标。因为商标是一个公司或者产品的形象,你想想看,如果把用户进行分组,就会出现同一个产品同时有多个商标在市场流通的情况,那就会对用户造成困惑,而且也不利于产品形象的打造。
### **当用户数量很少时**
这个其实很好理解如果我们没有一定的流量能让我们在短时间内达到所需要的样本量的情况下那么A/B测试也就不再适用了不过这种情况其实在大数据的互联网行业中比较少见这里我们就不展开讲解了。
## **当A/B测试不适用时有哪些替代方法**
当A/B测试不适用时我们通常会选用非实验的因果推断方法和用户研究两类方法来替代让你在想做因果推断却又不能进行A/B测试时有新的思路和方法。
### **倾向评分匹配(****Propensity Score Matching**
非实验的因果推断方法,又叫[观察性研究](https://en.wikipedia.org/wiki/Observational_study)这其中最常用的就是倾向评分匹配Propensity Score Matching简称PSM。我在第9节课已经介绍了PSM它的本质就是**在历史数据中,通过模型的方法,人为地(而不是像实验那样随机地)构建出相似的实验组和对照组,最后再****对两组****进行比较。**
这里我会通过一个音乐App的案例来详细讲解下用PSM替代A/B测试时是怎么在因果推断中应用的。
这款音乐App是付费订阅模式有两种订阅方式
* 个人订阅每月10块钱只能供一个人使用。
* 家庭订阅每月20块钱最多可以5人同时使用。
此外不管是个人订阅还是家庭订阅只要是新用户都会有3个月的免费试用期。
数据分析师通过大量的数据分析发现,家庭订阅比个人订阅用户的长期留存率(即续订率)更高。仔细想想其实也很好理解,家庭订阅可以和他人分享,所以每个订阅中的用户会更多一些,一般不止一个。而订阅中的用户越多,就越不容易取消这个订阅,所以长期留存率会越高。
于是这位数据分析师就根据这个分析发现,向营销经理推荐:可以向个人订阅的用户发广告,宣传家庭订阅的好处,鼓励他们升级到家庭订阅。
不过营销经理却提出了不同的意见:选择家庭订阅的用户和选择个人订阅的用户,在本质上就是不同的。比如他们的用户画像、使用行为等,都存在很大差异。也就是说,并不是升级本身导致了用户留存的提高,而是由于他们本来就是不同的用户,所以留存才不同。
为了验证营销经理的想法,数据分析师详细地分析了两种订阅方式的用户画像和使用行为,发现果然如营销经理所说,从个人订阅升级到家庭订阅的用户和没有升级的用户差别很大,比如升级的用户平均年龄更大,使用的时间更长等等。
![](https://static001.geekbang.org/resource/image/15/e0/1512f3c5fee200148e2673924b534be0.png)
看到这里,你大概已经知道了,个人订阅升级到家庭订阅是否会提升用户留存率,其实是一个因果推断的问题。
数据分析师的观点是“从个人订阅升级到家庭订阅”这个原因,可以导致“用户留存提升”这个结果。
但是营销经理的意见是影响用户留存的因素有很多,在用户升级这个情境下并不能排除其他因素,因为升级是用户自己的选择,那么很有可能升级和不升级的用户本来就是两类不同的人,所以在其他因素不相似的情况下就不能只比较升级这一个因素。
两者的观点看起来都很合理,那我们该通过什么方法来验证谁对谁错呢?
验证因果推断的最好方法当然是做A/B测试了但是在这个业务情景下由于是否升级这个变化因素是用户的自主选择我们并不能控制所以就并不能做随机分配的实验。那么这个时候非实验的因果推断方法PSM就可以派上用场啦。具体方法如下。
首先,我们从历史数据中选取在同一个时间范围内开始个人订阅的试用期用户。
在三个月试用期结束后还在付费的用户中有的依旧是个人订阅有的则升级成了家庭订阅。而在这自然形成的两类用户中我们通过PSM的方法对用户的画像和使用行为等因素进行匹配在没有升级的用户中选出和升级用户相似的用户然后在这些相似用户中比较长期的用户留存
![](https://static001.geekbang.org/resource/image/03/a1/039bf75eb85b3ac53d0bb23b4a4c8ba1.png)
接着进行完PSM后呢我们再来比较下个人订阅和家庭订阅各自的用户画像和使用行为。
![](https://static001.geekbang.org/resource/image/0f/cc/0f022b49bdf629d2c6c97eda8f02fdcc.png)
从数据中我们可以发现经过PSM处理后的没有升级的用户和升级的用户在各个特征上都已经非常相似了那么这个时候我们就可以进行比较了。当我们比较时因为已经控制了其他特征相似两组只有“是否升级”这一项不同所以如果用户留存有变化那就说明是升级这个变化因素造成的。
最后我们来看一下最终的比较结果。下图中的纵轴是用户留存率横轴是从试用期开始时的月份因为试用期是3个月且试用期内不存在续费问题所以留存率就是100% 那我们就从第4个月开始算用户留存率。
![](https://static001.geekbang.org/resource/image/0f/c7/0f72d69608847c0d312e900e8af22ac7.png)
从图中可以看到如果我们不做PSM的话就像最开始数据分析师发现的那样个人订阅升级到家庭订阅能够使一年的留存率提升28% 但这是在没有剔除其他因素的情况下所以28%这个结果就不够准确(营销经理的观点)。
那么经过PSM处理后我们得到了和升级用户相似的非升级用户结果发现升级确实能提升用户留存不过只能提高13%那就说明只有13%的用户留存率的提升可以归因于用户升级。
这里我们通过PSM在剔除了其他因素的影响之后模拟出了一个控制变量实验从而确定了个人订阅升级到家庭订阅对用户留存所带来的准确影响。
### **用户研究**
用户研究适用于A/B测试无法进行时比如新产品/业务发布前的测评,我们就可以通过直接或间接的方式,和用户交流沟通来获取信息,从而判断相应的变化会对用户产生什么影响。
用户研究的方法有很多种我们今天主要来聊一聊常用的几种深度用户体验研究Deep User Experience Research焦点小组Focus Group和调查问卷Survey
深度用户体验研究,指的是通过选取几个潜在用户进行深度的信息提取,比如通过用户眼球的运动来追踪用户的选择过程的[眼动研究](https://vwo.com/blog/eye-tracking-website-optimization/),或者用户自己记录的[日记研究](http://www.woshipm.com/user-research/3073582.html)。
* 眼动研究能让我们了解到用户的正常使用流程是什么样的,在哪些阶段会有卡顿或者退出。
* 日记研究通过用户自己记录的使用情况和意向,来了解他们的反馈。
[焦点小组](https://baike.baidu.com/item/%E7%84%A6%E7%82%B9%E5%B0%8F%E7%BB%84%E8%AE%BF%E8%B0%88%E6%B3%95/1054398)是有引导的小组讨论,由主持人把潜在的用户组织起来,引导大家讨论不同的话题,然后根据大家在讨论中发表的不同意见,综合得出反馈意见。从小组讨论这个形式就可以看出,每次焦点小组能够组织的用户一般要比深度用户体验研究的用户数量要多,但是比调查问卷的用户数量要少。
[调查问卷](https://baike.baidu.com/item/%E8%B0%83%E6%9F%A5%E9%97%AE%E5%8D%B7/10015839)就是通过事先设计好想要了解的问题,可以是选择题或者开放式的问题,比如对新品/新业务的想法和感受。然后把这些问题做成问卷发放给潜在的用户。交流方式可以是面对面、线上或者是电话等等,然后根据不同用户的回答结果,统计出大致的反馈结果。
![](https://static001.geekbang.org/resource/image/e1/47/e1070c0834b76879c0578ba42c4b8247.png)
从图中可以看出,从深度的用户体验研究,到焦点小组,再到调查问卷,虽然参与的用户越来越多,但是团队从每个用户身上获得的信息深度会越来越浅,所以选择何种方法也取决于你能招募到多少潜在用户,有没有相应的条件与设备(比如眼动研究需要眼动仪来完成),还有想要得到的信息深度。
## **小结**
今天这节课我们讲解了A/B测试的局限性通过案例介绍了非实验的因果推断方法-倾向评分匹配PSM也带你简单了解了用户研究的相关方法。实践中出现较多的还是我们没有办法控制的用户选择这种变量主要会用到PSM这种非实验的因果推断方法。那么用户研究在实践中不仅可以用于新产品/业务的测评还可以用于产生新指标的想法比如我在第3节课中讲到的定性+定量相结合的方法来确定指标)。
那么从开头到今天这节课呢我们的专栏讲解了A/B测试的统计原理标准的流程以及实践中各种常见问题及解决方法。说到应用这些经验和方法论呢工作场景自然是最佳场所不过还有另一个实践的好机会那就是在面试中。
那么在接下来的两节课我就会带你去过一遍面试中常考的A/B测试问题。同时我也建议你先梳理下自己面试时曾被问到的那些问题以及自己当时自己是怎么回答的。这样我们在学习后面两讲内容的时候也会更有针对性。
## **思考题**
结合自己的经验想一想你有没有见到过或经历过想进行因果推断相关的分析但是A/B测试却不适用的情况详细说一说原因和结果。
欢迎你把对本节课的思考和想法分享在留言区,我会在第一时间给你反馈。

View File

@ -0,0 +1,164 @@
# 13融会贯通A/B测试面试必知必会
你好,我是博伟。
在接下来的两节课呢我们换换脑子来聊一个相对轻松点的话题与A/B测试相关的面试应用。
近几年随着A/B测试在互联网、电商、广告等各个行业的广泛应用已经成为数据、产品、增长等相关职位面试的一个重要组成部分。所以我就根据自己多年做面试官的经验帮你总结了常见的A/B测试相关面试考点一方面我会通过典型真题来讲解面试思路另一方面也会把我在面试中的一些沉淀与思考分享出来。
另外我还想强调的是,这两节课虽然是在讲面试题,但其实也是在以另一种方式考查你对所学知识的灵活运用。面试中考察的不仅是你对知识的掌握,更关注你在工作场景中要怎么运用。所以希望你能通过这两节面试课的学习,既能学会拆解题目,提高面试能力,同时也能把我们学过的知识融会贯通。
面试题目是无穷的,但考点是有限的。我把相关的考点总结成了一张图,方便你着重复习。接下来我们就开始正式的面试讲解吧。
![](https://static001.geekbang.org/resource/image/3b/b6/3b83b40462ea671e5b42d16cfd08cab6.png)
## 面试应用一
某共享出行公司改进了司机使用App的用户界面希望能给司机更好的用户体验在提高司机使用App频率的同时也能提高司机的收入。那么问题就是请你设计一个A/B测试来验证新的司机App是否比旧的司机App体验要好。
**考点:**
* A/B测试的流程。
* 实验组/对照组的独立性。
**解题思路:**
很多同学遇到这个面试题首先想到的就是串一遍A/B测试的流程于是就按照以下流程开始回答。
**确定目标和假设** —> **确定指标**(说出评价指标和潜在的护栏指标)—> **确定实验单位****—**\> **随机分组**(一般为均分) —> **确定样本量**(这里注意,强调需要已知哪些统计量来确定样本量)—> **实施测试** —> **合理性检验**(要说出具体的检验都有哪些)—> **分析结果**注意说明P值法和置信区间法的判断标准
如果你只回答了设计流程这一点可能仅仅是个及格分因为题中还设置了至少1个隐藏的坑点这也恰恰就能拉开你与其他面试者的距离。
首先要注意在回答流程时一定要结合题目的具体内容展开讲解否则就是照本宣科会给面试官留下不能活学活用的印象。如果你不知道怎么回答比较好可以参照我在第8节课串讲案例的思路和方法。
不过这道题最大的坑点还不在这儿你需要再细心点儿。仔细看“共享出行”这个具体情境如果你对第11节课中两组独立性这个知识点掌握得足够牢固就会发现面试官在这道题中想考查你的绝不是串讲一遍流程这么简单。
面试现场也是工作的实际场景,那你就需要具体情境具体分析,洞察出设计实验时需要保持实验组和对照组的独立性。
我们来通过一个例子深度剖析一下。
假设我们选取在上海使用该共享出行的司机把他们随机分成实验组和对照组每组各占50%。其中在实验组司机使用新的司机App对照组则使用旧的司机App。
我们先来看实验组: 如果新App的确提升了司机的用户体验司机的使用频率提高这意味着实验组的订单量就会增加。因为订单总量需求是一定这样就会导致对照组的订单量减少。
与此相反的是如果新App降低了司机的用户体验司机使用App的频率降低那么实验组的订单量就会减少对照组的订单量则会增加。
这时你就会发现:**实验组和对照组不是独立的,而是相互影响的。**这就违背了A/B测试中实验组和对照组必须是相互独立的前提假设从而导致实验结果不准确。
在题目中的场景下比较好的解决方法是在不同的城市进行测试我们找到两个相似的城市A和B相似的目的是使两组具有可比性比如业务在当地的发展程度、经济发展程度、人们的出行习惯等实验组是城市A中的司机使用新App 对照组是城市B中的司机使用旧App。这样的话两组就不会相互影响。
所以针对这道题,完整且正确的回答方式应该是:先指出两组独立性被破坏的问题,通过举例分析说明两组是相互影响的;然后提出你的解决方案;最后结合实际情境串讲流程。
其实啊,如果你是个高手,就应该看出题中还有一个隐藏的考点:学习效应。
因为题目中是测试新的用户界面,所以还可能会有老用户的学习效应:新奇效应或改变厌恶。关于这个考点,你在这里简单提及,说明识别及解决方法即可,不需要长篇大论再进行展开。因为这道题考察的核心重点依旧是两组的独立性和设计流程,但是如果你能留心到潜在的学习效应问题这个坑,这就相当于你在优秀的回答之外,还给了面试官一个惊喜,证明你有填坑的能力。
## 面试应用二
在过去的实践中你有没有经历过这种情况A/B测试虽然得到了显著的结果比如P值小于5%),但最终还是决定不在业务/产品中实施测试中的变化。原因是什么呢?请举例说明。
**考点实施A/B测试中的变化要考虑的因素**
**解题思路:**
这道题很简短,乍看上去会觉得很容易,往往这个时候你就要小心谨慎了。仔细想想,面试官想通过这道题来考查什么知识点呢?考察你的什么能力呢?
在知识点上面试官主要考查的是在实践中实施A/B测试中的变化时需要考虑的因素有哪些。
这个问题其实是非常直接的,你很容易知道面试官在考察什么知识点。不过我想强调的是这类问题在面试中还有很多的变体,你需要在不同的变体中识别出本质问题。
* 核心问题:面试官会从结论出发(最终没有实施变化),问你可能会有哪些原因。
* 变体1面试官会给你测试结果的数据数据中的P值虽然小于5%但是十分接近5%比如4%。说明两组变量间的不同其实非常小,对实际业务的影响十分有限。
* 变体2面试官会直接问你实施A/B测试中变化的成本是什么。
无论怎么变化,归根结底都是一句话:结果是统计显著的,但是业务并不显著,因此在实践中没有实施变化。
在实践中统计上的显著结果只是最终实施变化的原因之一另一个方面还要考虑到实施变化的成本和收益。收益的话我们可以根据显著结果的差值来估算但是就成本而言我们需要考虑的因素是多方面的就像我在第7节课中讲的需要估算业务上的显著性。
所以在回答这类题目时,结合案例围绕着以上这些成本展开讲解,提出结果是统计显著,但是业务上不显著,所以最后才没有在实践中实施变化。
具体来说,在实践中实施变化主要有以下几种成本。
**人力成本**
指的是要实施变化的相关人员的时间成本,比如工程师需要花时间去实施具体的变化,编写相关代码。产品经理需要花时间去收集整理新的要求,组织相关会议,编写文档。如果变化会引起用户困惑的话,那么客服人员还要花时间去给用户答疑解惑。
**机会成本**
在实践中,时间和资源在业务/产品的不断迭代当中是永远不够用的请你想象一个场景在新版本上线前如果同时有A和B两个变化都具有统计显著性P值均小于5%),但我们的时间和资源有限,在上线前只能实施一个变化,那这个时候肯定会选择对业务影响较大的变化。
那你就会问了当这两个的P值都小于5%时,我该怎么比较哪个变化对业务产生的影响更大呢?
具体来说有两种方法。
第一种方法就是估算变化带来的业务影响。这种方法适用于不同变化有着不同的评价指标,或者不同的受众范围。
比如变化A使转化率提升了2%每年可以多带来10万的新用户。变化B使留存率提高了0.5%每年可以多留住5万的现有用户。此时我们就要衡量增加10万新用户和留住5万现有用户的价值哪个更大比如可以通过数据分析或者建模的方式确定新用户和现有用户的平均价值
当然这也和所处阶段的业务目标有很大关系,你需要看当时的业务重点是拉新还是留存。一旦我们量化估算出变化带来的业务影响,就可以决定该优先实施哪个变化了。
第二种方法是计算[效应值](https://www.simplypsychology.org/effect-size.html)Effect Size。这种方法适用于变化相似且评价指标相同时。
比如改进推荐算法的实验大都以点击率作为评价指标。那么现在有新算法A和新算法B和老算法相比都有提升那么这时就要计算每个实验的效应值
![](https://static001.geekbang.org/resource/image/ff/e5/ff979c4bdbc122a405832ebd196547e5.png)
效应值在统计中是用来表示指标变化的幅度的,效应值越大,就说明两组指标越不同。
如果我们计算得到的新算法A的效应值比新算法B的大就说明A的改进效果幅度更大影响也更大那就可以决定优先实施A变化了。
计算效应值其实也是估算变化带来的影响,不过因为这些变化都有相同的评价指标,所以我们只需要算出效应值来进行比较即可。
**代码成本**
实施变化一般需要代码的改动,这种改动会潜在地增加代码出错的概率,同时随着代码库越来越复杂,也会增加未来代码改动的成本。
## 面试应用三
我们对公司网站进行了改版想要以此来提升用户参与度。通过A/B测试发现新版本的用户参与度确实显著提升了所以我们随后就对所有用户显示了新版网站。但是过了一段时间后用户参与度又回到了之前的水平。假设这个A/B测试本身没有技术上或者统计计算上的问题。你觉得导致这种情况的原因会是什么呢又该怎么解决呢
**考点:学习效应**
**解题思路:**
这道题在知识点上的难度并不高,主要考察的是学习效应的问题。不过你要是只回答了这一个原因,这其实是大多数面试者都容易想到的,也仅仅只是一个合格的分数。
我先把自己更推荐的回答方式写出来,然后再带你仔细分析这道面试题。
比较推荐的回答方式是:先列举导致这种情况可能的原因有哪些,再结合题目的具体场景进行一一排除,最后得出自己的结论,给出解决方法。
为什么要这么回答呢?主要是因为相比较仅仅回答一个原因,或者直接给出解决方法,这种回答方式更能体现你对问题的全面理解。我在之前的课程中也强调过,知道为什么会出现这个问题,并发现问题,有时候甚至比解决问题还要重要。所以面试官在这里重点想要考察的,就是你对出现问题的原因的探究。
变化实施后的实际效果和A/B测试的结果不一致其中的原因有很多种最常见的原因主要是两个
* 实施A/B测试中出现的技术Bug。
* 在计算测试结果时出现错误(比如还没到足够的样本量就去计算结果)。
接下来我们进行一一排除。
首先题目中明确说了技术上和统计计算上都没有问题那接下来就要排除A/B测试常见的误区。
其次,由于题目中的场景并不是社交网络或者像共享经济的双边市场,实验组和对照组不会相互干扰,所以也不存在实验/对照组独立性被破坏的情况。
接着,从测试本身的设置和对结果的描述来看,没有细分分析或者多个实验组,又不会有多重假设问题或者辛普森悖论。
最后,对于网站不同版本这种问题,其实最常见的问题是学习效应,就像我刚才分析的那样,把其他常见的原因都排除了,那么其实考察的知识点就是学习效应。考察学习效应的面试题形式有很多种,有的会直接问你学习效应,有的就会像是本题中,给你一个具体的场景,让你判断。
根据题目描述的情况,应该是学习效应中的新奇效应:用户刚开始对于新版本很好奇,所以参与度会上升。但随着时间的推移,慢慢又会回归到正常平均水平。
至于如何识别和解决学习效应如果你还不能顺利回答出来那就得再回去复习第10节课的内容了。
所以你看,在面试中,面试官考察你的不仅是知识点,更重要的是你对问题的发散理解,以及思考问题的方式。
## **小结**
在这节课里我主要讲了3道面试题通过我的详细分析你也能够发现拆解题目是一项很重要的能力。
很多人在面试前都会去刷题,刷题固然重要,但是在面试这种高压场景下,可能回出现大脑短暂空白的情况。其实面试题目也是有套路的,就像搏击中的双方,你需要猜测对方可能会出什么招式,如果你能在对方出招前反应出他的下一步动作,哪怕是一秒钟,就有机会制胜对方。所以相对于海量刷题,学会拆解题目就显得更重要了。
相信你通过今天的学习对于A/B测试相关面试的形式和考点有了初步的了解你一定还意犹未尽没关系我们下节课接着来剖析典型面试题及考点。
## 思考题
你有遇到过什么有意思的A/B测试的面试题吗或者是有什么好的面试经验吗欢迎分享出来我们一起探讨。
欢迎分享出来,我们一起交流、探讨。也欢迎你把本节课推荐给你的朋友,一起进步、成长。

View File

@ -0,0 +1,128 @@
# 14举一反三A/B测试面试必知必会
你好,我是博伟。
今天这节面试课,在学习的过程中你会发现考察的知识点都已经掌握得差不多了。不过我想要强调的是,知识是你业务精进的基础,也是面试时考察的一个重要方面。但更为关键的,是你能够把知识举一反三,知道在不同的场景中如何应用,这也正是把知识转化为解决问题的能力,是你的面试竞争力。
好了,那我们就趁热打铁,继续来讲面试这个主题,帮你夯实基础,做到面试不慌!
## 面试应用一
假设你现在负责跑一个A/B测试根据样本量计算测试需要跑2周。但是业务上的同事会每天关注测试结果一周之后就观察到显著结果了这时候他觉得既然结果已经显著了就想让你停止测试然后实施测试中的变化。由于业务上的同事对统计不是很熟悉所以你该怎么用直白的语言来给他解释现在还不能停止实验呢
**考点:**
1. 多重检验问题
2. 统计原理的通俗解释
**解题思路:**
其实这道题我在第7节讲分析测试结果时就给出了一个类似的实践背景由于在样本量还没有达到规定前不断查看结果就会造成多重检验问题。而一旦出现多重检验我们之前花费的功夫就会功亏一篑。所以在这道题中你需要首先指出多重检验问题接着说明出现的原因以及可能造成的具体后果得到假阳性的概率增大实验结果不准确
你也能看出来,如果只是考多重检验问题,那就太简单了。斟酌一下题目中的问题,就能知道面试官想要考察的是其实是你的表达与理解能力,也就是说你该怎么用**通俗直白的语言**来给业务同事解释复杂难懂的统计概念和原理。在实际工作中很多时候需要和没有统计背景的同事去沟通交流A/B测试的相关内容所以面试官也非常喜欢考察面试者这方面的能力。
不仅如此,这其实也是在变相考察面试者是不是真正内化了相关统计知识。毕竟如果只是死记硬背概念,肯定是不能在实践中灵活运用这些原理的,更别说再把这些原理用直白的语言去讲给没有统计背景的人听。
你可能会问,通俗直白的语言到底是什么呢?其实也很简单,就是说人话。我的经验就是“一个避免,两个多用”。
**一个避免,指的是尽量****避免使用统计术语P值****、****第一类错误****、****假设检验等)****。**
一方面,专业统计术语会加大你们沟通的时间成本和沟通障碍。业务同事是不懂这些术语的,当你用专业术语去向他说明时,你就需要花更多的时间来解释术语,不仅对方难以理解,而且你们的沟通目的也没有达到。
另一方面,仔细想想,你为什么要去给业务同事解释呢?主要就是为了告诉对方,现在还不能停止实验。所以啊,说清楚为什么不能在此时停止实验就可以了,术语能少则少。
**两个多用指的是多打比方、多举例子。**尤其是通过日常生活中的事物来打比方、举例子,这会是非常好的一种方式。
比如A/B测试其实是比较两组的表现既然有比较那就有好坏输赢的概念。那**你就可以选择生活中任何有好坏输赢结果,但是每次发生结果都有可能不同的事件来****打比方****。**
我比较喜欢拿体育比赛来打比方比如篮球就和A/B测试非常类似。每场NBA篮球比赛都会有事先规定的时间48分钟。而且篮球比赛的结果是以比赛结束后的最终结果为准。如果在比赛结束前的任何时间查看比分任何一方都有可能领先但是我们并不会以比赛中间的结果作为最终结果。
同理回到A/B测试当中来如果我们还没有到达规定的时间看到显著的结果就宣布实验已经完成从而停止实验这就和在比赛中看到一方领先就宣布领先的一方获胜、比赛结束是一样的道理。
再回到我们的面试场景中多重检验问题在工作中其实是很常见的。尤其是在业务上的同事没有很强的统计背景的情况下可能只是依靠P值来做决定不会考虑样本量是否充足这个前提所以用通俗的语言来解释这些统计原理尤为重要。
## 面试应用二
某产品现在想改变商标,所以想衡量新商标对业务的影响,该如何做?
**考点:**
A/B测试的适用范围及替代方法
**解题思路:**
如果你对第12节课讲“什么情况下不适合用A/B测试”的知识足够熟悉就知道这里并不能用A/B测试来衡量商标改变的影响性。我讲过“当有重大事件发布时”是不适合去做A/B测试的商标即是其中之一。毕竟商标代表了产品和公司的形象如果一个产品有多个商标同时在市场流通就会给用户带来困扰从而会对产品形象有不利的影响。
还记得A/B测试的两种替代方法吗分别是非实验的因果推断方法和用户研究。不过啊在这个情境下非实验的因果推断方法也行不通因为这个商标是全新的并没有历史的相关数据。所以用户研究就是我们最终选定的方法。
在这个案例中,我们只需要收集用户对新商标的看法如何,所以就需要的样本尽可能大一些,这样意见才有代表性,但是并不会涉及到用户体验等很有深度的问题。那么我们就可以选用调查问卷的方式来收集用户反馈,从而给我们一些方向性的指导。让我们知道相较于现有的商标,用户对新商标偏正面反馈,还是更偏负面反馈。
如果从调查问卷中得到总体正面的反馈后,团队决定在市场中废除现有商标,推出新商标。这时候就可以来衡量更换商标后的影响,相对于比较推出新商标前后产品的北极星指标的变化来计算出差值去推断出新商标影响,一个更加准确的方法是建立模型。
我们可以用历史数据建立起对北极星指标的时间序列模型,用推出新商标前的数据去训练这个模型,它也可以预测出没有新商标的北极星指标的走势,然后我们可以把模型的预测数据和推出新商标后的实际数据进行比较,从两者的差值来推断出新商标的影响。
总结一下这道题的答题思路即说明题中场景下A/B测试不适用及其原因然后再给出用户研究和模型的办法来作为替代解决方法。
## 面试应用三
某社交网站准备给用户推荐好友在首页的右上角推出“你可能认识的人”这个新功能怎么设计A/B测试才能真正衡量这个功能底层的推荐算法的效果呢假设这里没有网络效应。
**考点:**
A/B测试分组设计
**解题思路:**
当拿到题目一看到社交网站,你会立马想到网络效应,但是读完题发现这里假设没有网络效应。
你可能会想想要推出一个新功能而且还不考虑网络效应那肯定就是常规的A/B测试设计了呗。所以就把用户随机均分成两组对照组的用户没有“你可能认识的人”这个新功能实验组的用户有这个新功能。最后比较两组的指标来确定推荐新功能的推荐算法的效果如何。
你看,这没有什么难的!如果真的这么想,那你就在不知不觉中掉进面试官给你设的坑了。
我们再仔细读题中的场景描述,就会发现这个新功能是在页面的右上角,这意味着增加这个新功能还涉及到用户交互界面的改变。
如果按照我们刚才所说的实验分组进行设计,把实验组和对照组相比,其实是既增加了推荐算法,又改变了交互界面,是同时改变了两个因素。以此来看,即使实验组的指标相对于对照组有所提升,我们也无法确定究竟是哪个因素在起作用。
所以这道题的关键点就是如何分离这两个潜在的影响因素。在实践中,解决的方法一般是设计多个实验组,每个实验组只改变一个因素,同时共用一个对照组,也就是改变前的状态。
是不是觉得这个方法有点熟悉呢没错儿这就是我在第9节课中提到的A/B/n测试。不过这个案例的情况比较特殊因为要增加推荐算法的话肯定会改变交互界面也就是说其中一个因素必须依赖另一个因素不能单独存在。
但是如果反过来想,其实改变交互界面并不一定要增加推荐算法,所以我们可以把各个分组设计成递进关系:
* 对照组:改变前的原始版本。
* 实验组A: 增加“你可能认识的人”这个新功能, 其中推荐的内容随机产生。
* 实验组B: 增加“你可能认识的人”这个新功能, 其中推荐的内容由推荐算法产生。
我们可以发现实验组A相对于对照组只是改变了交互界面因为它的推荐内容是随机产生的。而实验组B相对于实验组A则是只增加了推荐算法而二者的交互界面是相同的。这样我们就可以通过比较对照组和实验组A来衡量改变交互界面是否有影响比较实验组A和实验组B来判断新功能的底层推荐算法是否有效果。
## 面试应用四
某社交平台开发出了一个新的交互界面希望能增加用户的点赞次数。团队通过把一部分用户随机分组进行A/B测试发现用了新界面的实验组的用户平均点赞次数比对照组高出了5%结果也是显著的。那么如果把新界面推广给所有用户你认为用户的平均点赞次数会提升多少呢是大于5%还是小于5%?为什么呢?在这个案例中,我们假设没有学习效应的影响。
**考点:**
网络效应
**解题思路:**
看到“社交平台”就要想到“网络效应”,经过前面的学习,你应该对这一点形成肌肉记忆。
这道题其实难度不大,考察的是网络效应及其形成原因。不过我想通过这道题,一方面让你清楚网络效应的具体场景,另一方面,也想让你知道在有网络效应的影响下,社交平台开发新交互界面后的真实提升效果和实验结果之间的关系。
在没有学习效应的情况下,因为是社交平台,存在网络效应,所以随机分组并不能保证实验组和对照组的独立性,意味着两组的独立性被破坏了。
具体而言如果实验组的用户A因为用了的新界面点赞了一个内容那么这个被点赞的内容也会被A的好友在对照组的B看到B也有可能点赞这个内容。所以这个新界面改动既影响了实验组还会通过网络效应影响对照组即实验组的用户平均点赞次数提升对照组的也会提升。
以此来看这里的5%的提升其实是受到网络效应影响后的结果真实的提升效果应该会更大即只有实验组的指标提升而对照组的指标不变即大于5%。
所以当我们把这个新的交互界面推广到所有用户也就是在没有对照组的情况下那么和旧版本相比真实的提升效果应该是大于5%的。
## 小结
我们两节课的A/B测试的面试之旅到这里也就告一段落了。你应该也能发现这些常见的考点我们在前面的课程中都有讲解过只要你认真学习了专栏的内容是不会有太大的问题的。
在最后呢我还想强调一点。我们在这两节课讲的面试题大都是题目中直接提到A/B测试的在面试中A/B测试的考查形式是多种多样的。有时题目中并没有明确提到A/B测试但是A/B测试是这些题目中答案的有机组成部分比如让你衡量产品新功能的好坏是不是应该推进这个产品变化这种问题你的答案中肯定会有要如何定义目标和指标去表征新功能的影响如何设计A/B测试去验证新功能是否有效。总之只要让你进行因果推断需要量化改变带来的影响时A/B测试都是你的好帮手
## 思考题
这里呢我们开动脑筋如果让你用直白通俗的语言不用统计上的定义不引用其他术语解释A/B测试的相关术语的话你会怎么解释呢选取一两个尝试着解释下。
欢迎把你的解释分享在评论区,我们一起交流、讨论。同时如果你有所收获,也欢迎你把这节面试课分享给你有需要的朋友。

View File

@ -0,0 +1,263 @@
# 15用R/Shiny教你制作一个样本量计算器
你好,我是博伟。
A/B测试前的样本量计算是设计实验时不可或缺的一步。在第6节讲样本量计算时我提到了现在网上的样本量计算器参差不齐的问题并且网上大部分的计算器都是只能计算概率类指标不能计算均值类指标在实际业务应用时十分局限。
鉴于这些问题加上我在实践中也想要更加快速且正确地计算样本量提高工作效率于是就从统计理论出发去钻研A/B测试样本量计算的原理让自己能够识别和掌握正确的计算方法。
后来渐渐发现身边的同事和朋友在做A/B测试时也有这方面的需求于是我就把样本量的计算方法工具化做成App放在了网上。
所以我今天教你的,就是**把样本量计算工具化的详细过程——我会带你制作一个可以发布在网上的实时计算的A/B测试样本量计算器。**
## 实战指南
既然是制作App我们还是需要进行一些简单的编程包括前端和后端使用R语言及其前端库Shiny。不过你也不用担心制作这款App不需要你掌握前端的JavaScript、HTML、CSS也不需要你会在后端如何搭建数据库。你只需要掌握以下3点就足够了。
1. **A/B测试样本量计算的原理**。关于原理,重点学习咱们这门课"统计篇"的两节课和基础篇的第6节课即可。
2. **最基本的编程知识**。我指的是通用的编程,不细指特定的语言。包括变量赋值、基本的数据类型(字符串,数字等),这些最基础的编程知识,即使不是专业的编程人员,也是大部分互联网从业者都会知道和掌握的,难度不大。
3. **R和Shiny的基本语法**。如果你对R和Shiny很熟悉的话那就可以直接跳过这一点。如果你之前没有接触过R和Shiny也没关系这些语法也是可以快速学习和掌握的。我在这里给你一些拓展资料供你参考学习。
* [如何安装R和Rstudio](https://ourcodingclub.github.io/tutorials/intro-to-r/#download)
* [R的基本语法](https://www.tutorialspoint.com/r/index.htm)只需看R Tutorial这部分即可
* [Shiny教程](https://deanattali.com/blog/building-shiny-apps-tutorial/)
如果你没有时间和精力学习R和Shiny的知识也别担心我会把我的代码开源贴出来你可以结合代码和本节课的内容学习理解。
相信如果你是跟我从头开始认真学习这门课的话现在你肯定已经掌握了第一点A/B测试样本量计算的原理。至于第二点最基本的编程知识相信作为互联网人的你已经掌握或者有概念性的认知了。那么我们今天就重点讲解下如何把这两点结合起来制作一个简单方便的样本量计算器在教你制作的同时呢我也会穿插讲解R和Shiny的相关知识还有一些实战案例。
在讲解前呢,我先把我的代码库和样本量计算器贴出来,供作参考:
* [代码库](https://github.com/pieces201020/AB-Test-Sample-Size-Calculator)
* [样本量计算器App](https://bowei-zhang.shinyapps.io/Sample_Size_Calculator/)
首先如果你点开GitHub上的代码库就会发现主要的文件有两个server.R和ui.R。这是Shiny App的标准文件结构其实从文件名就能看出它们大概的功能
* server.R负责后端逻辑的文件比如我们这次的样本量计算的逻辑都在server.R当中。
* ui.R负责前端用户交互界面的你的App做得好不好看全靠它了。
接着,你点开我贴出来的样本量计算器链接就会发现,它已经按照指标类型分成了概率类和均值类:
![](https://static001.geekbang.org/resource/image/13/2a/1329cc84db8fa9519e254cd22984fc2a.png)
那么我今天就按照这两类指标来分别进行讲解。
## 制作过程
### 概率类指标
从概率类指标的样本量计算的逻辑参看第6节课上来看我们需要函数[power.prop.test](https://www.rdocumentation.org/packages/stats/versions/3.6.2/topics/power.prop.test)。下面这段代码L31-35是在server.R文件中的具体实施
```
number_prop_test <-reactive({ceiling(power.prop.test(
p1=input$avgRR_prop_test/100,
p2=input$avgRR_prop_test/100*(1+input$lift_prop_test/100),
sig.level=1-numsif_prop_test(),
power=0.8)[[1]])
})
```
函数的输入参数这里,我们需要输入以下四项信息:
* 两组的指标p1和p2。
* 显著水平sig.level。
* Power。
* 单双尾检验。
我们来对照实际的前端交互界面来看下应该怎么输入:
![](https://static001.geekbang.org/resource/image/81/bf/81f7e0ec90dc4ce5c4314d4353011ebf.png)
**两组的指标p1和p2**
在这里我会让用户输入原始指标也就是p1和最小可检测提升。注意这里的“提升”是相对提升=p2-p1/p1而不是绝对提升=p2-p1注意和均值类指标的绝对提升进行区别。通过这两个参数就可以算出p2了。
这是根据我平时实践中的实际使用情况来设置的因为一般是有原始指标和想要获得的提升当然你也可以根据自己的需求直接让用户输入p1和p2。
**显著水平sig.level**
在这里我会让用户输入置信水平1-α),而不是显著水平α,这也是根据实践中的习惯调整的。
**Power**和**单双尾检验**
我把Power和单双尾检验都设定成了默认值是不需要用户改变的。因为很多用我制作的计算器的用户他们的统计背景并不强所以我就把Power隐藏了起来并且设定为默认的80%,这样就可以减少他们的困惑。
至于单双尾检验我在第2节课中也讲了A/B测试中更推荐使用双尾检验所以我也把它设定为默认的双尾检验从代码可以看到并没有涉及这个参数那是因为函数本身对这个参数的默认值就为“two.sided”即“双尾”
如果你还记得第6节讲的样本量计算公式的话就会知道影响样本量的因素有显著水平α、Power、两组间的差值δ和两组的综合方差$\\sigma\_{\\text {pooled}}^{2}$。
你可能会有疑问为什么不让用户直接输入以上4个影响因素呢而是让用户输入现在交互界面上的参数呢
这其实是在帮用户省事,通过在实践中最常出现的参数,来帮助用户计算综合方差。
通过把函数的输入参数和这些影响因素对比之后你就会发现其实通过函数的输入参数完全可以确定这4个影响因素从而求得样本量。
输入完这四项信息之后,就可以开始运行了。
如果你仔细比较server.R和ui.R这两个文件就会发现整个App中两个文件通过以下方式运行的
* 整个App是通过ui.R这个文件接收到用户的输入值然后把这些输入值存到input函数中。
* 接着呢server.R再读取input进行计算再把结果存储到output函数中返回给ui.R。
* 最后ui.R再把这些结果显示在用户的交互界面上。
这里再来举个例子说明下我刚才讲的整个过程。
首先在ui.R中我们需要用户输入**原始指标**avgRR\_prop\_testL11-12
```
numericInput("avgRR_prop_test", label = "原始指标", value = 5, min = 0, step=1)
```
**最小可检测相对提升**lift\_prop\_testL18-19
```
numericInput("lift_prop_test", label = "最小可检测相对提升", value = 5,min = 0, step=1)
```
**置信水平**sif\_prop\_testL42-44:
```
radioButtons("sif_prop_test", label = "置信水平",
choices = list("80%","85%","90%","95%"),
selected = "95%",inline=T)
```
那么这些用户输入的参数呢最后都通过input这个函数传递到server.R文件当中去进行样本量计算L31-35
```
number_prop_test <-reactive({ceiling(power.prop.test(p1=input$avgRR_prop_test/100,
p2=input$avgRR_prop_test/100*(1+input$lift_prop_test/100),
sig.level=1-numsif_prop_test(),
power=0.8)[[1]])
})
```
当计算完成后再把结果存在output函数中L44-51
```
output$resulttext1_prop_test <- renderText({
"每组的样本量为 "
})
output$resultvalue1_prop_test<-renderText({
tryIE(number_prop_test())
})
```
最后output函数再把结果传递给ui.R供前端显示L57-63
```
tabPanel("结果",
br(),
textOutput("resulttext1_prop_test"),
verbatimTextOutput("resultvalue1_prop_test"),
textOutput("resulttext2_prop_test"),
verbatimTextOutput("resultvalue2_prop_test")
)
```
这里要注意的一点是,因为通过[power.prop.test](https://www.rdocumentation.org/packages/stats/versions/3.6.2/topics/power.prop.test)函数计算出来的样本量是单组的,如果需要求总样本量时,就要用单组样本量乘上分组数量。这里的分组数量也是需要用户手动输入的。
同时你可能也注意到了我在这款App中加入了大量的解释性语言对于每个需要用户输入的参数都进行了解释说明这样做其实也是吸取了实践中的用户反馈。就像我刚才说的很多用户的统计背景并不强对于这些统计量并不熟悉所以我们要尽可能地将这些输入参数解释清楚减少用户使用时的困惑。
### 均值类指标
从均值类指标的样本量计算的逻辑上参看第6节课来看我们需要函数[power.t.test](https://www.rdocumentation.org/packages/stats/versions/3.6.2/topics/power.t.test)。下面这段代码L105-109是在server.R文件中的具体实施
```
number_t_test <-
reactive({ceiling(power.t.test(delta=input$lift_t_test,
sd=input$sd_t_test,
sig.level=1-numsif_t_test(),
power=0.8)[[1]])
})
```
从这段代码我们会发现,和概率类指标相比,函数要求的输入参数有所变化,变成了:
* 标准差sd。
* 最小可检测差值delta这里是两组指标的绝对差值
* 显著水平sig.level。
* Power。
* 还有单双尾检验。
这是因为均值类指标的标准差(方差)并不能仅仅通过两组指标的值来计算,而是需要知道每个数据点来计算。
我们先看标准差的计算公式:
![](https://static001.geekbang.org/resource/image/b5/17/b549a8a6fe9689bb364683b13408f817.png)
所以标准差需要用户根据以上公式在数据中计算得出。
最小可检测差值delta是用户根据实际业务情况自定的显著水平一般为95%。
**Power**和**单双尾检验**这两项我们还沿用概率类指标的设定去设置默认值Power为80%的双尾检测。
均值类指标代码的其他部分和概率类指标均类似,这里就不再展开举例了,具体的内容,代码里也写得十分清楚了。
## 应用场景和使用案例
实践中使用样本量计算的应用场景主要分两类:
* 已知单位时间的流量,求测试时间。
* 已知测试时间,求单位时间的流量。
所以你会发现在App交互界面右下角区域的结果板块就有关于测试时间和测试流量的选项。
下面我来举一个例子来分别说明这两种情况。
假设我们现在做A/B测试的指标为下载率原始指标为5%最小可检测的相对提升为10%置信水平为95%一共有一个实验组和一个对照组通过咱们的样本量计算器求得的每组样本量为31234总样本量为62468。
**在单位时间测试可用流量固定的情况下,求测试时间**
这种场景是比较常见的。我们假设以周为单位每周可用的测试流量约为10000。输入参数后计算得出一共需要6到7周的时间才能达到充足的样本量
![](https://static001.geekbang.org/resource/image/20/e9/205d6a9b6e43cf3e90f44c22c03430e9.png)
### **在测试时间固定的情况下,求单位时间内的流量**
这种场景适用于时间紧急的情况比如一周之内要出结果一周有7天那么输入参数后计算得出我们每天的测试流量至少要有8924。
知道每天需要的流量后,我们就可以根据这个数字去调整我们的测试流量占总流量的比例了。
![](https://static001.geekbang.org/resource/image/27/0b/27327b6e389cfc2029ea13635d7ee20b.png)
最后我要说明一点,虽然我举的是一个概率类指标的例子,但这两个使用场景对于均值类指标是同样适用的。
### 使用案例
在使用案例这个版块,我会针对概率类指标和均值类指标各举一个例子,来说明具体情况下应该如何输入不同的参数。
先看概率类指标的案例。
![](https://static001.geekbang.org/resource/image/02/36/02544d86608ecf014bba6f89d0979d36.png)
再看均值类指标的案例。
![](https://static001.geekbang.org/resource/image/fe/d7/fed41dc163edb25c95ebb996e4bfyyd7.png)
## 如何把Shiny App发布在网上
现在我们完成了server.R和ui.R在下图中点击右上角的“Run App”即可在本地打开我们的App。
![](https://static001.geekbang.org/resource/image/c1/bf/c15db4c1cc380b153f5e14e7baa5b9bf.png)
但是如果你想把App发布在网上还是得借助ShinyApps.io它是专门发布 Shiny App的平台你需要在上面注册一个免费账户具体过程呢也不难你可以查看这里的[教程](https://shiny.rstudio.com/articles/shinyapps.html)。
## 小结
那么到这里呢关于制作A/B测试样本量计算器的讲解就结束了相信你通过本节课的学习结合着我提供的代码和App已经能成功制作出自己的样本量计算器啦。
不过有一点也需要再说明一下。虽然样本量计算的逻辑是固定的但是对于用户交互前端的话在保证基本功能的前提下你可以根据自己的喜好来设计的这里我贴出来Shiny前端的[案例集](https://shiny.rstudio.com/gallery/)和[常用语句](https://shiny.rstudio.com/images/shiny-cheatsheet.pdf)供你参考。
## **思考题**
其实样本量计算可以在各种编程语言中实施这里我选用R和Shiny的原因是它们使用起来相对简单些。那如果让你用Python来实施的话对于概率类和均值类指标样本量的计算你会选择哪些函数呢
如果你在制作样本量计算器的过程中有遇到什么问题,或者有什么经验,欢迎在留言区和我分享,也欢迎你把课程分享给你的朋友、同事。

View File

@ -0,0 +1,62 @@
# 结束语|实践是检验真理的唯一标准
你好,我是博伟。
在过去的这些年里我一直在和A/B测试打交道研究的时间越久越能体会出其中蕴含的深意。所以在设计课程大纲时结束语这一讲的标题我毫不犹豫地选择了“实践是检验真理的唯一标准”。这句话很朴素却是我这么多年和A/B测试朝夕相处的真实体会。
首先,我想和你聊聊为什么我在课程中会反复强调实践的重要性。
A/B测试本身就是一种偏经验和方法论的工具。掌握了我在课程中讲的这些统计原理、规范流程后也不能保证你在实践中如鱼得水游刃有余。毕竟**听到的经验和方法,想要深刻理解,还是要拿到实际业务场景中反复试炼,才能不断迭代和完善。**
即使是我讲过的一些常见误区和问题,以及一些隐形的坑点,由于业务环境的千差万别,你在实践时大概率还是会遇到,而且还会遇到其他的坑。不过不要害怕,因为**有些“弯路”非走不可正是在和“弯路”的博弈中你才能摸透A/B测试中的招式和套路精进业务。**
A/B测试带给公司和团队的不光是持续提升的结果更重要的是实验意识。团队中的成员提出一个想法究竟是突发奇想还是真正可靠的呢能不能有效落地实施呢我们完全可以把这个想法通过A/B测试放在实际场景中检验最后得出具有说服力的结论。正所谓**大胆猜想,小心求证,不断调整,快速更迭**。
**“实践”并不是件容易的事儿,真正去做的时候,还需要主动、耐心、有勇气。**
与A/B测试相关的项目都可以主动参与。不管你是亲自做测试还是观摩整个过程这都是在学习积累。主动提出业务需求主动尝试**只要去实践,肯定就有收获。**
**对“失败”多点耐心。**这里的失败我是打引号的你可能会觉得做完A/B测试后只要没有把A/B测试中的变化在产品或业务中实施就算“失败”不过我想告诉你的是从实践数据来看一大半的A/B测试中的变化最终都没有最终实施。在做测试的时候我们肯定希望结果是显著的但实际上不显著的概率比较大。这就是期望与现实的落差。那这就算是失败吗
在A/B测试领域显然不是这样的。每一次“失败”的测试对我们来说都是宝贵的经验你可能会从中发现从而改进测试设置、工程实施等方面的问题。哪怕测试结果真的不显著也没关系因为这也能帮我们排除不同的想法减少给业务带来的潜在损失从而让我们快速迭代到下一个想法中去。
**要敢于提出自己的想法。**我已经记不清有多少次我和同事、领导意见不一致时A/B测试就成了我们解决问题的法宝。长此以往也帮助我们在团队中形成了一种实验的氛围大家越来越敢提出与别人不一样的想法和意见而不是管理人员的一言堂。
你看,“实践是检验真理的唯一标准”中包含的朴素智慧,一旦和我们当下的生活、工作结合起来,是不是就有更生动、更丰富的理解了呢?这也正是我平时爱好历史的原因,前人的智慧总能历久弥新。
不过在今天课程结束之际我还想给你分享更多我自己学习A/B测试的故事聊一聊我的学习心得希望能带给你一些启发和勉励。
**第一个心得,搭建自己的知识框架,能让你的学习效率更高。**
就拿我自己来说吧。我呢,其实并不是科班出身的数据从业者,所以想要在这一陌生领域有立足之地,术业有专攻,就要付出更多的努力。
举个小例子。为了搞清楚中心极限定理、P值、Power这些难懂的统计概念我把各种版本的统计课本学了不下20遍。为了全面掌握数据科学方面的知识就利用业余时间学习了将近10门与数据科学相关的网课。系统化的学习给我打下了实践的坚实基础实践中再遇到其他问题我就知道怎么去搜索资料、寻找解决方法而不会无从下手。
所以这也是我想要做这门课的初衷。根据我多年积累的经验系统总结A/B测试领域的经验和方法帮助想要学习这个领域的人搭建一个知识框架让你能够在短时间内获得非线性的突破。
**第二个心得,学习要有目的,并且要把学到的知识及时应用到实践中,学以致用。**
在学校期间的学习大都是为了学习而学习,工作后的学习就不同了,有目的的学习更能达到一举多得的效果。
我刚才谈到自己曾把统计课本学习了不下20遍这只是相对系统的学习遍数如果算上实际翻看的次数那远不止百次了。因为我这个人呢记性一般纯知识性的东西一旦不常用就很容易忘记。所以统计书对我来说就像小学学习语文时的《现代汉语词典》一样平时用到了就去查形成肌肉动作。
所以如果你学完这个课程有些内容没能完全消化没有关系我希望你能把它当做你在A/B测试上的工具书遇到棘手的问题就来翻看、学习有问题也欢迎继续留言我也会时不时地回复你的问题。
**还想分享一个我做专栏的心得:文字输出是检验输入质量的重要标准。**
在实践中丰富和完善的知识要怎么检验?文字输出就是一个很好的方式。这也是我在做专栏这几个月保持连续输出的一个重要心得。
这几年我会经常带学生做项目或者做讲座,但文字输出和“讲”是不一样的。脑海中把一个问题想清楚了,也能给别人讲出来,但要落到笔上,就得斟酌每一个细枝末节:全文是不是有逻辑、用词是不是够精准、例子是不是够恰当等等。这对文字表达、专业逻辑都是不小的磨练。
做专栏的文字输出,跟我平时写文章也不一样。写专栏文章,我需要去掉那些学术派的语言,调整粗糙的表达,力求用最简单明了的语言去讲清楚一个问题,同时还要考虑读者的阅读习惯等等。所以反复打磨的不仅是文字内容,还有对问题的周密思考。当然了,这对精力、意志力也都是考验。
我和A/B测试已经亲密相处了7年多对我来说它不仅是工作中的一种增长方法在深入体会它的精妙之后它代表的**“实验意识”更成了我生活中的重要理念和原则。**
当生活中偶遇迷茫找不准方向,或者面对未知的不确定心有焦虑时,我不会畏首畏尾,更不会退缩,而是大胆地去尝试。在考虑到可能的结果后,勇于试错。因为不亲自经历,我可能永远也不知道这件事对自己来说是好是坏。
就像过去的2020年注定是个特别的年份突如其来的疫情打乱了很多人在学习、生活和工作上的节奏。在这种生存与挑战、安稳与不确定的摇摆之间不如把**心里的思绪和想法,投放到实践中,从而突破自己内心的围城。**
这也是我最后想与你共勉的:**唯有步履不停,人生路上才能遇到更多的惊喜。**
最后的最后,我也为你准备了[调查问卷](https://jinshuju.net/f/RSZSBZ),题目不多,希望你可以花两分钟填一下。十分期待能听到你的反馈,说说你对这门课程的想法和建议。

View File

@ -0,0 +1,14 @@
# 结课测试题这些A/B测试的知识你都掌握了吗
你好,我是博伟。
到这里《A/B测试从0到1》这门课程已经全部结束了。我给你准备了一个结课小测试来帮助你检验自己的学习效果。
这套测试题共有 20 道题目,有单选题,也有多选题,满分 100 分,系统自动评分。
还等什么,点击下面按钮开始测试吧!
[![](https://static001.geekbang.org/resource/image/28/a4/28d1be62669b4f3cc01c36466bf811a4.png?wh=1142*201)](http://time.geekbang.org/quiz/intro?act_id=361&exam_id=981)
最后,我为你准备了[调查问卷](https://jinshuju.net/f/RSZSBZ),题目不多,希望你可以花两分钟填一下。十分期待能听到你的反馈,说说你对这门课程的想法和建议。

View File

@ -0,0 +1,179 @@
# 加餐|试验意识改变决策模式,推动业务增长
你好我是凯悦。很荣幸能为博伟老师的专栏写篇加餐写这篇文章一方面跟我学习A/B测试的经历有关。另一方面作为极客时间的产品经理我们团队的试验意识也经历了一个从0到1的过程。
一年半前我开始自学A/B测试当时在网上找了很多文章和课程来学习。但有用的资料较少质量也参差不齐讲得也不够透彻所以我花了很长时间来判断资料的正确与否也因此踩了很多坑。
所以在博伟老师这个专栏上线之后,我每周追更,越是往后学习兴趣越浓,心想如果在我学习初期就遇到这个专栏,那是多美好的事。
这篇加餐中我把我们团队从引入、应用A/B测试到建立起试验意识的整个过程分享给正在学习的你。
# 试验意识改变决策模式,推动业务增长
极客时间不是从产品初期就开始使用A/B测试的而是经历了纠偏、引入、应用、总结四个阶段最终形成了较强的试验意识。
* 纠偏改变对A/B测试的错误认识建立正确认识。
* 引入将A/B测试的方法和工具引入到决策过程中而非拍脑袋决定。
* 应用用A/B测试解决一个个实际问题。
* 总结:复盘经验,形成试验意识。
经历了四个阶段的发展,我们建立了完整的试验流程,形成了试验意识,关键点有两个:
1. 每当遇到产品决策问题时第一时间想到A/B测试。
2. 长期坚持使用A/B测试。
这里我着重想说明一下我们在试验意识上的纠偏。正是意识上的纠偏,让我们改变了决策模式,将依据经验决策的单一决策模式切换为依据经验+试验意识的系统决策模式,持续推动业务增长。
# 意识纠偏
曾经以为A/B测试就是设置两个版本分别让两组用户使用转化率高的胜出然后就可以发布上线了。
但事实真是如此吗这样做决策科学吗如果A/B测试如此简单那为什么还是有很多互联网公司没有使用呢
先举个例子一个详情页版本A转化率是1.76%版本B转化率是2.07%。如果你是产品经理你会选用哪个版本呢?
假如再有版本C转化率是2.76%呢再有版本D转化率是11.76%呢?
按照“哪个版本转化率高就上线哪个版本”的决策模式我们应该立即上线版本D。过去我也是这么认为的但实际上是错误地理解了A/B测试。
在我刚才举的例子中,存在三个问题:
* 第一试验只是抽取了一部分用户得出了结论不是全部用户那么当全部用户都使用版本D时转化率还会是11.76%吗?
* 第二版本B、C和D的转化率分别是2.07%、2.76%和11.76%相对于版本A的提升分别是0.31%、1%和10%。是差异越大我们上线这个版本的信心指数就会越高吗显然不是还需要考虑0.31%、1%和10%的提升是实际存在的,还是试验误差导致的?
* 第三当差异多大时我们才能下判断呢换句话说如果上线版本B它是否确实能带来转化率的提升呢实际的提升会是多少呢
由于这三个问题缺少数据支持所以无法回答因此就没法做出是上线版本A还是上线版本B的决策。我们还需要收集更多的信息来回答这三个问题。
回答这三个问题就涉及对科学A/B测试的理解。什么是科学、规范的A/B测试呢博伟老师的专栏已经给出了答案。A/B测试并没有想象中的简单它是一项科学试验涉及到抽样、显著性检验、软件工程、心理学等方方面面。重点要关注试验过程是否科学严谨试验结果是否可信依据这样的A/B测试结果做决策才真正的能推动业务的发展。
# 引入A/B测试
为什么要引入A/B测试呢极客时间用户早已破百万需要实现从野蛮生长到精耕细作的阶段跨越用户增长、数据决策都离不开A/B测试这个工具。它能够在不进行较大改变的情况下使用小部分流量进行试验验证假设得出结论达到优化产品、促进用户留存和活跃的目的。
引入过程中我们采取了三方面的行动。
第一系统学习A/B测试。开始学习时找了大量的资料量虽然多但大部分千篇一律。不过经过不断的学习我们还是总结出了自己的试验流程并尝试应用。当然中间也踩了很多坑进入了不少误区。
所以当编辑同学策划《A/B测试从0到1》这个专栏时我们就发现这个专栏非常实用初学者或进阶者学习过程中遇到的问题不清楚的细节以及需要避免的“坑”博伟老师都有详细的讲解。
第二自建分流系统。学习了理论知识之后就要给研发同学提需求做工具了。我们自建了分流系统然后将整个A/B测试流程跑通这样才能真正地帮助到决策者做判断。
第三将A/B测试纳入产品迭代的流程。现在在做重要产品的迭代前都会做多个版本进行A/B测试这已经成为了团队的共识。
引入并建立了A/B测试观念和意识后接下来就需要动手实践了。博伟老师在专栏中也多次讲过A/B测试的实践性非常强需要在实际业务场景中不断迭代、精进。下面我就通过两个实际案例来看看极客时间是如何从0到1利用A/B测试验证假设以及进行产品迭代的。
# A/B测试实践应用
极客时间有多个重要业务指标其中转化率和复购率两项指标尤为重要。所以我就选择了具有代表性的两个案例来讲解。案例一我们通过A/B测试检验了一个提升复购率的假设。案例二利用A/B测试选出高转化率的详情页。两个案例都说明了试验和试验意识的必要性。
## 案例一:醒目的优惠券样式可以提高复购率吗?
### 案例背景
运营同学想提高完成首单用户的复购率,于是提出想法:在用户完成首单后,让优惠券的展示更加醒目,以促进用户使用。但是这个想法却不被产品经理认可。主要有以下几方面的原因:
1. 首先,现有版本已经有了优惠券展示模块。
2. 其次,整体优惠券使用率不高,而且分析历史数据得知优惠券对促进用户再次购买的效果并不理想。
3. 最后,也是最重要的一点,现有版本有“分享有赏”功能,用户将课程以海报形式分享到朋友圈,其好友通过该海报购买后,该用户能够得到返现。通过这种形式也能促成复购,还有拉新效果。
运营同学和产品经理各有理由,所以在双方互相不能说服的情况下,我们就决定用 A/B测试来解决这一问题而试验结果也让大家颇感意外。
### 试验设计
现有方案是用户完成首单后,系统弹出弹窗,用户可以选择使用优惠券购课或者分享给其他用户获得现金奖励。运营同学提出假设,认为以更醒目的样式展示大额优惠券可以提高复购率,试验的假设就可以表述为“醒目的优惠券能促进用户立即使用优惠券,进而增加复购的概率”。
这里需要说明的是,用户完成首单后,系统会自动将优惠券发送给用户,不需要用户手动领取。
于是产生了实验组的UI样式
![](https://static001.geekbang.org/resource/image/bc/38/bc03585e4d6673ca35b132b056ba6838.jpg?wh=500x408)
接下来就是按照A/B测试的规范流程来设计试验了
* **明确目标和假设。**目标是增加复购,零假设是实验组复购率与控制组没有差异。
* **确定指标。**用复购率作为衡量指标,同时考虑新用户数和营收。(复购率=已支付订单数大于等于两单的用户数/已支付订单数等于一单的用户数)
* **确定试验单位。**使用uid作为试验单位。
* **确定样本量。**我们将实验组与控制组的差值设置为0.6%。这个差值也有其他叫法比如最小可检测效应、实际显著性。算出来最少需要8074个样本。
**实施测试**
经过对历史数据的分析,用户分享率和领取优惠券的领取率没有明显的周期性变化,因此按照样本量与流量确定了试验时长。
做好准备后,开发同学开始使用自建的分流系统,上线测试。
### **结果分析**
进入试验的用户有17652人在功效80%置信度95%时置信区间不收敛并且P值大于0.05,不拒绝原假设。我们又试验了一段时间,发现依然如此。因此判断实验组并不比原版本效果好。
使用R语言的prop.test函数计算结果如下图
![](https://static001.geekbang.org/resource/image/3c/a5/3c5e63a713bf21bb327d3ed12d89a3a5.png?wh=1188*456)
试验结果汇总如下表所示:
![](https://static001.geekbang.org/resource/image/0c/07/0c429d67d9dd7d56ff237f8b5c680007.png?wh=1712x692)
试验过程中我们还收集了另外两个指标:
![](https://static001.geekbang.org/resource/image/a8/01/a8ab68992d5346233a25017af22c9901.png?wh=1666x734)
通过辅助指标,我们发现原版本能带来更多的用户,且用户更有动力分享促进用户购买。并且经过分析,排除了“大部分新用户是由少数几个老用户的分享带来的”这种情况。
### **做出决策**
从试验数据来看置信区间包含“0”值意味着实验组比控制组的转化率有可能增加0.098%也有可能降低0.733%。
此外在拉新能力上原版本是实验组的5倍成交金额上前者是后者的3.6倍。差别之大令我们感到意外幸好有试验的意识先通过A/B测试对idea做了检验如果拍脑袋决策直接采纳这个建议那会给公司带来损失。
基于以上两个原因我们决定继续使用原版本。
### 案例思考
该案例中采取了“大胆假设小心求证”的决策方式当提出了“通过醒目的优惠券设计刺激复购”的idea时产品经理第一时间想到用A/B测试的方法来验证想法是否可行。既不臆断拒绝也不盲目接受。而是试验意识驱动采用A/B测试方法收集数据分析数据科学决策。这也就是我在文章开头所说的试验意识的第一个关键点当涉及产品变化的决策时首先想到A/B测试。
## 案例二:选出高转化率的详情页
有了前车之鉴我们在产品迭代时也开始养成肌肉记忆不断使用A/B测试。
### 案例背景
APP的课程详情页需要版本迭代。产品经理思考通过强化促销价格能否提升详情页的转化率
### **试验设计**
设计了两种UI样式如下图
![](https://static001.geekbang.org/resource/image/d6/24/d66276cb562f64e487477a2ff33b9b24.jpg?wh=500x430)
* **确定指标。**用转化率作为衡量指标。
* **确定试验单位。**使用uid作为试验单位。
* **确定样本量。**我们将实验组与控制组的差值设置为1.5%计算后大概需要样本量1.7万。因为我们流量较大按照原定分流计划1-2天的时间就能达到最小样本量。由于用户在周末活跃数据会骤降为了覆盖一个用户活跃周期同时为了尽量避免新奇效应我们适量缩小试验流量占总流量的比例将试验时长设置为一周。
* **实施测试。**做好准备后,开发同学上线测试。
### **结果分析**
为避免“学习效应”,上线试验后,我们持续监测每天的指标;各项指标的变化都很稳定,符合预期,排除了“学习效应”。
试验结果如下:
![](https://static001.geekbang.org/resource/image/43/64/43af1348d1f71eceb83dd1ecd2262164.png?wh=1400*296)
进入试验的用户有23686人在功效80%置信度95%时置信区间不收敛p值大于0.05不拒绝原假设,两个版本没有显著差异。
此时,陷入僵局,试验结果不显著,增加样本量降低方差都没有改变结果。如何决策呢?
### 做出决策
由于置信区间不收敛无法根据试验结果决定使用哪个版本。因此需要考虑其他因素做决策。APP整体风格简洁明快没有大色块设计而且醒目的“大色块”并没有带来转化率的提升却将页面分割成上下两个部分。
基于UI样式的考虑我们决定使用版本A。
### 案例思考
试验结果有时会与直觉相左。通过严格试验得出的数据能有效反应用户的真实情况,数据驱动的前提是有数据,有数据的前提是有意识的做试验并收集数据。
很多试验的结果并不能给出明确的决策依据也需要产品经理主观决策这并不意味着试验没有作用试验的作用是将能够用试验验证错误的idea全部排除且证据充分将无法用试验解决的问题交给“专家系统”来决策权即依据负责人或团队的经验决策。
# 总结
今天的核心内容到这里就讲完了,我总结了团队在优化决策模式、推动业务增长过程中积攒的一些经验。
A/B测试方法是经过验证的最佳实践Best Practice要将试验意识写入我们的心智模式每当遇到增长问题、决策问题时第一时间想到“A/B测试可能是一个好的解决方法”这是试验意识的第一个关键点。
试验意识的第二个关键点是A/B测试需要长期坚持要形成循环而不仅仅是闭环。如果说从发现问题到试验结果上线再到效果回归是一个闭环的话那么还需要在发现问题前加一个动词“持续”“持续发现问题”这就让试验意识形成了循环在循环中形成持续向上的趋势。这个意识的重要性不在于一次两次试验有效还是无效而是能让我们在决策前先用试验验证并长期这样做形成习惯。
试验意识的建立,让我们的决策模式不再局限于依赖经验和直觉。试验意识加经验的决策模式成为我们的决策系统,由于这个系统有概率优势,虽然单次决策有时有效有时无效,但长期来看每一次微小进步的叠加效果就能驱动业务的整体增长,而其中的经验必将带来惊艳的效果。

BIN
AI技术内参/.DS_Store vendored Normal file

Binary file not shown.

84
AI技术内参/README.md Normal file
View File

@ -0,0 +1,84 @@
# AI技术内参
## 你将获得
* 人工智能国际顶级学术会议论文精讲;
* 人工智能五大核心技术模块深度剖析;
* 人工智能工程师进阶之路;
* 人工智能业界热点独家解读。
## 讲师介绍
洪亮劼电子商务平台Etsy数据科学主管前雅虎研究院高级研发经理。长期从事机器学习与人工智能的基础以及应用研究对推荐系统、搜索引擎、计算广告学、社交网络以及自然语言处理等领域有非常深入的理解。
他在国际顶级学术会议中发表重要论文20余篇文章引用量超过2100次H-Index17。长期担任多个国际著名会议及期刊的评审委员会成员和审稿人并且组织过多个关于推荐、搜索、用户体验优化的国际研讨会拥有3项美国专利。
洪亮劼有丰富的学术研究和工业界实践经验,这些经验给予他全面、独特的视角,和他一起看懂人工智能,也是这个专栏的初心。
## 课程介绍
人工智能科学家吴恩达反复强调“AI is the new electricity” 。今天的人工智能,犹如一百年前的电,正在给人类带来同样巨大,甚至更加精彩的变化。
毋庸置疑,这是一个属于人工智能的时代。人工智能正在渗透到各行各业,并且离我们越来越近,新的时代中,我们应该如何利用好新武器?
“AI技术内参”专栏将为你系统剖析人工智能核心技术精讲人工智能国际顶级学术会议核心论文解读技术发展前沿与最新研究成果分享数据科学家以及数据科学团队的养成秘笈。希望能够帮助你在人工智能领域找到最佳学习路径不断进阶。
专栏精心打磨了四大模块,具体如下:
**模块一人工智能国际顶级学术会议深入解读与技术展望10周左右**
精选10个国际人工智能顶级学术会议剖析其论文精髓解读最新技术成果全览人工智能发展趋势。模块中包含的顶级会议有机器学习方面的ICML、NIPS机器视觉方面的CVPR、ICCV自然语言处理方面的ACL、EMNLP数据挖掘和数据科学方面的KDD、WSDM信息检索和搜索方面的SIGIR互联网综合方面的WWW。
**模块二人工智能核心技术剖析32周左右**
分专题讲解人工智能和机器学习的核心技术,帮你拨开层层迷雾,快速入门。这其中包括搜索核心技术、推荐系统核心技术、广告系统核心技术、自然语言处理及文本处理核心技术、计算机视觉核心技术等。
**模块三人工智能工程师、科学家的养成和人工智能团队的构建8周左右**
国内外各大公司都在纷纷组建自己的人工智能团队,洪亮劼将结合个人经历,分享人工智能团队的组建以及运作经验。另外,针对立志成为数据科学家或者人工智能科学家的同学,他将会梳理对应的技能图谱以及知识脉络,并进行系统剖析。
**模块四人工智能业界热点2周左右**
在这整个一年的课程当中,相信会有很多的与人工智能相关的行业或者技术热点出现。洪亮劼也会结合这些热点,即时解读最新的业界动态,探讨最新的研究成果,分享个人洞见。
## 课程目录
![](https://static001.geekbang.org/resource/image/5c/08/5c89fe07fe0e5a5f1e4f8491ac592408.jpg)
## 适合人群
希望拓展人工智能视野、系统学习人工智能核心技术的数据科学家、工程师;勤勉且不断精进的人工智能从业者;人工智能团队负责人;有志于从事人工智能事业的入门者;对人工智能/机器学习感兴趣的各界人士。
## 特别放送
#### 免费领取福利
[![](https://static001.geekbang.org/resource/image/69/dc/69c52d08278a2164dc5b061ba342a5dc.jpg?wh=960x301)](https://time.geekbang.org/article/427012)
#### 限时活动推荐
[![](https://static001.geekbang.org/resource/image/67/a0/6720f5d50b4b38abbf867facdef728a0.png?wh=1035x360)](https://shop18793264.m.youzan.com/wscgoods/detail/2fmoej9krasag5p?dc_ps=2913145716543073286.200001)
## 订阅须知
1. 订阅成功后推荐通过“极客时间”App端、Web端学习。
2. 本专栏为虚拟商品,交付形式为图文+音频,一经订阅,概不退款。
3. 订阅后分享海报,每邀一位好友订阅有现金返现。
4. 戳此[先充值再购课更划算](https://shop18793264.m.youzan.com/wscgoods/detail/2fmoej9krasag5p?scan=1&activity=none&from=kdt&qr=directgoods_1541158976&shopAutoEnter=1),还有最新课表、超值赠品福利。
5. 企业采购推荐使用“[极客时间企业版](https://b.geekbang.org/?utm_source=geektime&utm_medium=columnintro&utm_campaign=newregister&gk_source=2021020901_gkcolumnintro_newregister)”便捷安排员工学习计划,掌握团队学习仪表盘。
6. 戳此[申请学生认证](https://promo.geekbang.org/activity/student-certificate?utm_source=geektime&utm_medium=caidanlan1)订阅课程享受原价5折优惠。
7. 价格说明:划线价、订阅价为商品或服务的参考价,并非原价,该价格仅供参考。未划线价格为商品或服务的实时标价,具体成交价格根据商品或服务参加优惠活动,或使用优惠券、礼券、赠币等不同情形发生变化,最终实际成交价格以订单结算页价格为准。

169
AI技术内参/SUMMARY.md Normal file
View File

@ -0,0 +1,169 @@
# SUMMARY
* [简介](./README.md)
* [开篇词 | 你的360度人工智能信息助理](./docs/153.md)
* [001 | 聊聊2017年KDD大会的时间检验奖](./docs/159.md)
* [002 | 精读2017年KDD最佳研究论文](./docs/391.md)
* [003 | 精读2017年KDD最佳应用数据科学论文](./docs/394.md)
* [004 | 精读2017年EMNLP最佳长论文之一](./docs/397.md)
* [005 | 精读2017年EMNLP最佳长论文之二](./docs/658.md)
* [006 | 精读2017年EMNLP最佳短论文](./docs/661.md)
* [007 | 精读2017年ICCV最佳研究论文](./docs/2681.md)
* [008 | 精读2017年ICCV最佳学生论文](./docs/2717.md)
* [009 | 如何将“深度强化学习”应用到视觉问答系统?](./docs/2782.md)
* [010 | 精读2017年NIPS最佳研究论文之一如何解决非凸优化问题](./docs/2868.md)
* [011 | 精读2017年NIPS最佳研究论文之二KSD测试如何检验两个分布的异同](./docs/2941.md)
* [012 | 精读2017年NIPS最佳研究论文之三如何解决非完美信息博弈问题](./docs/3211.md)
* [013 | WSDM 2018论文精读看谷歌团队如何做位置偏差估计](./docs/3946.md)
* [014 | WSDM 2018论文精读看京东团队如何挖掘商品的替代信息和互补信息](./docs/3961.md)
* [015 | WSDM 2018论文精读深度学习模型中如何使用上下文信息](./docs/4024.md)
* [016 | The Web 2018论文精读如何对商品的图片美感进行建模](./docs/8106.md)
* [017 | The Web 2018论文精读如何改进经典的推荐算法BPR](./docs/8234.md)
* [018 | The Web 2018论文精读如何从文本中提取高元关系](./docs/8293.md)
* [019 | SIGIR 2018论文精读偏差和“流行度”之间的关系](./docs/11367.md)
* [020 | SIGIR 2018论文精读如何利用对抗学习来增强排序模型的普适性](./docs/11636.md)
* [021 | SIGIR 2018论文精读如何对搜索页面上的点击行为进行序列建模](./docs/11851.md)
* [022 | CVPR 2018论文精读如何研究计算机视觉任务之间的关系](./docs/12010.md)
* [023 | CVPR 2018论文精读如何从整体上对人体进行三维建模](./docs/12100.md)
* [024 | CVPR 2018论文精读如何解决排序学习计算复杂度高这个问题](./docs/12190.md)
* [025 | ICML 2018论文精读模型经得起对抗样本的攻击这或许只是个错觉](./docs/12443.md)
* [026 | ICML 2018论文精读聊一聊机器学习算法的“公平性”问题](./docs/12648.md)
* [027 | ICML 2018论文精读优化目标函数的时候有可能放大了“不公平”](./docs/12834.md)
* [028 | ACL 2018论文精读问答系统场景下如何提出好问题](./docs/13014.md)
* [029 | ACL 2018论文精读什么是对话中的前提触发如何检测](./docs/13193.md)
* [030 | ACL 2018论文精读什么是“端到端”的语义哈希](./docs/13276.md)
* [复盘 7 | 一起来读人工智能国际顶级会议论文](./docs/41364.md)
* [031 | 经典搜索核心算法TF-IDF及其变种](./docs/822.md)
* [032 | 经典搜索核心算法BM25及其变种内附全年目录](./docs/828.md)
* [033 | 经典搜索核心算法:语言模型及其变种](./docs/830.md)
* [034 | 机器学习排序算法:单点法排序学习](./docs/949.md)
* [035 | 机器学习排序算法:配对法排序学习](./docs/950.md)
* [036 | 机器学习排序算法:列表法排序学习](./docs/952.md)
* [037 | “查询关键字理解”三部曲之分类](./docs/1077.md)
* [038 | “查询关键字理解”三部曲之解析](./docs/1079.md)
* [039 | “查询关键字理解”三部曲之扩展](./docs/1081.md)
* [040 | 搜索系统评测,有哪些基础指标?](./docs/1296.md)
* [041 | 搜索系统评测,有哪些高级指标?](./docs/1299.md)
* [042 | 如何评测搜索系统的在线表现?](./docs/1300.md)
* [043 | 文档理解第一步:文档分类](./docs/1448.md)
* [044 | 文档理解的关键步骤:文档聚类](./docs/1449.md)
* [045 | 文档理解的重要特例:多模文档建模](./docs/1450.md)
* [046 | 大型搜索框架宏观视角:发展、特点及趋势](./docs/1702.md)
* [047 | 多轮打分系统概述](./docs/1762.md)
* [048 | 搜索索引及其相关技术概述](./docs/1821.md)
* [049 | PageRank算法的核心思想是什么](./docs/1883.md)
* [050 | 经典图算法之HITS](./docs/1938.md)
* [051 | 社区检测算法之“模块最大化 ”](./docs/1940.md)
* [052 | 机器学习排序算法经典模型RankSVM](./docs/2026.md)
* [053 | 机器学习排序算法经典模型GBDT](./docs/2052.md)
* [054 | 机器学习排序算法经典模型LambdaMART](./docs/2099.md)
* [055 | 基于深度学习的搜索算法:深度结构化语义模型](./docs/2297.md)
* [056 | 基于深度学习的搜索算法:卷积结构下的隐含语义模型](./docs/2298.md)
* [057 | 基于深度学习的搜索算法:局部和分布表征下的搜索模型](./docs/2332.md)
* [复盘 1 | 搜索核心技术模块](./docs/3215.md)
* [058 | 简单推荐模型之一:基于流行度的推荐模型](./docs/4090.md)
* [059 | 简单推荐模型之二:基于相似信息的推荐模型](./docs/4212.md)
* [060 | 简单推荐模型之三:基于内容信息的推荐模型](./docs/4278.md)
* [061 | 基于隐变量的模型之一:矩阵分解](./docs/4421.md)
* [062 | 基于隐变量的模型之二:基于回归的矩阵分解](./docs/4484.md)
* [063 | 基于隐变量的模型之三:分解机](./docs/4569.md)
* [064 | 高级推荐模型之一:张量分解模型](./docs/4680.md)
* [065 | 高级推荐模型之二:协同矩阵分解](./docs/4764.md)
* [066 | 高级推荐模型之三:优化复杂目标函数](./docs/4784.md)
* [067 | 推荐的Exploit和Explore算法之一EE算法综述](./docs/4881.md)
* [068 | 推荐的Exploit和Explore算法之二UCB算法](./docs/4903.md)
* [069 | 推荐的Exploit和Explore算法之三汤普森采样算法](./docs/4915.md)
* [070 | 推荐系统评测之一:传统线下评测](./docs/5075.md)
* [071 | 推荐系统评测之二:线上评测](./docs/5117.md)
* [072 | 推荐系统评测之三:无偏差估计](./docs/5221.md)
* [073 | 现代推荐架构剖析之一:基于线下离线计算的推荐架构](./docs/5434.md)
* [074 | 现代推荐架构剖析之二:基于多层搜索架构的推荐系统](./docs/5519.md)
* [075 | 现代推荐架构剖析之三:复杂现代推荐架构漫谈](./docs/5571.md)
* [076 | 基于深度学习的推荐模型之一:受限波兹曼机](./docs/5624.md)
* [077 | 基于深度学习的推荐模型之二基于RNN的推荐系统](./docs/5646.md)
* [078 | 基于深度学习的推荐模型之三:利用深度学习来扩展推荐系统](./docs/5709.md)
* [复盘 2 | 推荐系统核心技术模块](./docs/5915.md)
* [079 | 广告系统概述](./docs/8601.md)
* [080 | 广告系统架构](./docs/8691.md)
* [081 | 广告回馈预估综述](./docs/8913.md)
* [082 | Google的点击率系统模型](./docs/370.md)
* [083 | Facebook的广告点击率预估模型](./docs/9037.md)
* [084 | 雅虎的广告点击率预估模型](./docs/9069.md)
* [085 | LinkedIn的广告点击率预估模型](./docs/9264.md)
* [086 | Twitter的广告点击率预估模型](./docs/9370.md)
* [087 | 阿里巴巴的广告点击率预估模型](./docs/9488.md)
* [088 | 什么是“基于第二价位的广告竞拍”?](./docs/9695.md)
* [089 | 广告的竞价策略是怎样的?](./docs/9747.md)
* [090 | 如何优化广告的竞价策略?](./docs/9864.md)
* [091 | 如何控制广告预算?](./docs/10029.md)
* [092 | 如何设置广告竞价的底价?](./docs/10147.md)
* [093 | 聊一聊“程序化直接购买”和“广告期货”](./docs/10182.md)
* [094 | 归因模型:如何来衡量广告的有效性](./docs/10360.md)
* [095 | 广告投放如何选择受众?如何扩展受众群?](./docs/10599.md)
* [096 | 如何利用机器学习技术来检测广告欺诈?](./docs/10719.md)
* [复盘 4 | 广告系统核心技术模块](./docs/41115.md)
* [097 | LDA模型的前世今生](./docs/376.md)
* [098 | LDA变种模型知多少](./docs/5998.md)
* [099 | 针对大规模数据如何优化LDA算法](./docs/6024.md)
* [100 | 基础文本分析模型之一:隐语义分析](./docs/6190.md)
* [101 | 基础文本分析模型之二:概率隐语义分析](./docs/6274.md)
* [102 | 基础文本分析模型之三EM算法](./docs/6366.md)
* [103 | 为什么需要Word2Vec算法](./docs/6430.md)
* [104 | Word2Vec算法有哪些扩展模型](./docs/6578.md)
* [105 | Word2Vec算法有哪些应用](./docs/6586.md)
* [106 | 序列建模的深度学习利器RNN基础架构](./docs/6681.md)
* [107 | 基于门机制的RNN架构LSTM与GRU](./docs/6840.md)
* [108 | RNN在自然语言处理中有哪些应用场景](./docs/6925.md)
* [109 | 对话系统之经典的对话模型](./docs/7144.md)
* [110 | 任务型对话系统有哪些技术要点?](./docs/7326.md)
* [111 | 聊天机器人有哪些核心技术要点?](./docs/7423.md)
* [112 | 什么是文档情感分类?](./docs/7649.md)
* [113 | 如何来提取情感“实体”和“方面”呢?](./docs/7757.md)
* [114 | 文本情感分析中如何做意见总结和搜索?](./docs/7954.md)
* [复盘 3 | 自然语言处理及文本处理核心技术模块](./docs/8553.md)
* [115 | 什么是计算机视觉?](./docs/14002.md)
* [116 | 掌握计算机视觉任务的基础模型和操作](./docs/14193.md)
* [117 | 计算机视觉中的特征提取难在哪里?](./docs/14349.md)
* [118 | 基于深度学习的计算机视觉技术(一):深度神经网络入门](./docs/14574.md)
* [119 | 基于深度学习的计算机视觉技术(二):基本的深度学习模型](./docs/14853.md)
* [120 | 基于深度学习的计算机视觉技术(三):深度学习模型的优化](./docs/16938.md)
* [121 | 计算机视觉领域的深度学习模型AlexNet](./docs/17874.md)
* [122 | 计算机视觉领域的深度学习模型VGG & GoogleNet](./docs/18126.md)
* [123 | 计算机视觉领域的深度学习模型ResNet](./docs/22892.md)
* [124 | 计算机视觉高级话题(一):图像物体识别和分割](./docs/39727.md)
* [125 | 计算机视觉高级话题(二):视觉问答](./docs/39929.md)
* [126 | 计算机视觉高级话题(三):产生式模型](./docs/40063.md)
* [复盘 5 | 计算机视觉核心技术模块](./docs/41179.md)
* [127 | 数据科学家基础能力之概率统计](./docs/308.md)
* [128 | 数据科学家基础能力之机器学习](./docs/311.md)
* [129 | 数据科学家基础能力之系统](./docs/316.md)
* [130 | 数据科学家高阶能力之分析产品](./docs/382.md)
* [131 | 数据科学家高阶能力之评估产品](./docs/385.md)
* [132 | 数据科学家高阶能力之如何系统提升产品性能](./docs/388.md)
* [133 | 职场话题:当数据科学家遇见产品团队](./docs/2504.md)
* [134 | 职场话题:数据科学家应聘要具备哪些能力?](./docs/2565.md)
* [135 | 职场话题:聊聊数据科学家的职场规划](./docs/2625.md)
* [136 | 如何组建一个数据科学团队?](./docs/156.md)
* [137 | 数据科学团队养成:电话面试指南](./docs/3261.md)
* [138 | 数据科学团队养成Onsite面试面面观](./docs/3361.md)
* [139 | 成为“香饽饽”的数据科学家,如何衡量他们的工作呢?](./docs/3614.md)
* [140 | 人工智能领域知识体系更新周期只有56年数据科学家如何培养](./docs/3744.md)
* [141 | 数据科学家团队组织架构:水平还是垂直,这是个问题](./docs/3909.md)
* [142 | 数据科学家必备套路之一:搜索套路](./docs/10801.md)
* [143 | 数据科学家必备套路之二:推荐套路](./docs/10972.md)
* [144 | 数据科学家必备套路之三:广告套路](./docs/11307.md)
* [145 | 如何做好人工智能项目的管理?](./docs/13471.md)
* [146 | 数据科学团队必备的工程流程三部曲](./docs/13665.md)
* [147 | 数据科学团队怎么选择产品和项目?](./docs/13816.md)
* [148 | 曾经辉煌的雅虎研究院](./docs/379.md)
* [149 | 微软研究院:工业界研究机构的楷模](./docs/40617.md)
* [150 | 聊一聊谷歌特立独行的混合型研究](./docs/40765.md)
* [复盘 6 | 数据科学家与数据科学团队是怎么养成的?](./docs/41257.md)
* [151 | 精读AlphaGo Zero论文](./docs/654.md)
* [152 | 2017人工智能技术发展盘点](./docs/3944.md)
* [153 | 如何快速学习国际顶级学术会议的内容?](./docs/8480.md)
* [154 | 在人工智能领域,如何快速找到学习的切入点?](./docs/40221.md)
* [155 | 人工智能技术选择,该从哪里获得灵感?](./docs/40403.md)
* [156 | 近在咫尺,走进人工智能研究](./docs/40456.md)
* [内参特刊 | 和你聊聊每个人都关心的人工智能热点话题](./docs/2771.md)
* [结束语 | 雄关漫道真如铁,而今迈步从头越](./docs/41595.md)

4
AI技术内参/book.json Normal file
View File

@ -0,0 +1,4 @@
{
"title": "AI技术内参",
"language": "zh"
}

View File

@ -0,0 +1,52 @@
# 091 | 如何控制广告预算?
我们在前面的一系列分享里讲了广告位竞价的基本原理,那就是目前广泛使用的基于第二价位的广告竞拍。也分享了广告的竞价策略,以及具体的竞价策略优化方法,比如单个广告推广计划的优化等。
今天,我们来看在广告竞价策略中一个比较重要的问题,这个问题我们在前一篇的分享里也提到过,那就是如何能够比较流畅地利用广告商的预算,而不是把广告商的钱一下子都花完。
## 预算步调优化
控制广告预算的第一种方法是**预算步调优化**Budget Pacing这个方法的目的就是在某一个时间段里均匀地分配广告商的预算。同时在每一个时段发布商所面临的受众都有可能不太一样所以对于广告商而言比较理想的状态是一个广告可以在一天的不同时段被不同的受众所看到从而达到扩大受众面的目的。
预算步调优化有两种比较常见的思路,一种叫“**节流**”Throttling一种叫“**修改出价**”。
节流这种方法主要是把单位时间的支出或者是成本给控制在某一个速率内,使得预算能够被均匀地使用。这种方法往往是在我们已经介绍过的竞价模型之外运行。修改出价这个思路很直观,也就是直接修改我们的竞价,从而带来预算均匀化的结果。
关于节流思路,有一种做法\[1\]是把如何节流当做一种**“线性优化”问题**,并且是**有限制的最大化问题**。具体来说,对于每一个出价的请求,我们都可以做一个二元的决定,决定我们是否接受这个出价请求。当然,对于每一个出价请求,这里都有一个价值和一个成本。根据对不同出价请求的设置,我们来做优化,从而能够最大化总价值。但同时,我们需要遵守一个限制,总的成本不能超过预算。这其实就是在两种目标之间实现一个均衡,简言之,我们需要在不超过总预算的情况下达到总价值的最大化。
虽然这种算法本身能够通过我们之前介绍过的“拉格朗日乘数法”来求解,但是还存在一个根本的问题,那就是这种算法并不能**实时地**对整个竞价的安排进行计算和更新。因为,这种线性优化方法一般都是在线下计算好了以后再到线上运行。很明显,这种方法并不适合快速变化的竞价环境。因此,也就有一些工作\[2\]和\[3\],尝试通过节流,或者更确切地说,通过**在线优化**来控制预算的使用情况。
对竞价直接进行修改的相关工作也很多\[4\]和\[5\],这个思路是把**控制理论**中的一些思想借鉴到了对竞价的直接优化上目标是让广告商的预算能够平滑使用。这里面的控制是指什么呢主要是指我们引入一个新的模块在DSP中从而能够**实时监测各种指标**,例如竞价赢的比率、点击率等,然后利用这些数据作为一个参考点,从而能够形成一种**回馈信息**以供控制系统来对出价进行实时的调整。
和节流的思想相对比,利用控制理论对出价进行直接优化这种思路明显要更加灵活。然而在实际的工作中,更加灵活的框架依赖于对点击率以及竞价全景观的准确预测,这其实是很困难的。在真实的情况下,利用节流的思想,也就是不去修改出价,只是在其基础上直接进行操作,则往往来得简单有效。
## 频率上限
在工业界,还有一种经常会使用的控制预算的方法叫“**频率上限**”Frequency Cap。简单来说这种策略就是**限制某一个或者某一种广告在某一种媒介上一段时间内出现的次数**。比如是否限制一个肯德基的广告在半天之内让同一个用户看见的次数5次、10次还是20次
为什么要限制频率呢?一个因素当然是我们希望广告的预算不要在短时间内消耗完。另外,短时间内反复观看某一个广告,很可能会让用户对某一个广告或者广告商产生厌烦情绪,那么广告的有效度就会降低。这对于一些广告商来说,其实是消耗了一些资源。因此,限制广告的投放是一种策略选择,从而让广告的投放花钱少、效率高。
这种频率上限的做法在工业界非常普遍,不过比较遗憾的是,关于这样做究竟是不是有很大的效果,用户多次看到广告是否会真正产生非常大的厌烦情绪从而使得广告效果降低,有没有理论支持等问题,目前还没有比较好的研究来解决。
## 总结
今天我为你介绍了广告竞价中的预算步调优化和频率上限两个思路。
一起来回顾下要点:第一,预算步调优化有两种常见思路,分别是“节流”和“修改出价”;第二,频率上限是一种工业界常用的方法,但是目前这方面缺乏理论依据。
最后,给你留一个思考题:今天我们介绍了使用节流的方法来控制预算,其中一种方法是线性优化,需要在预算允许的情况下最大化广告的价值。那么,对于广告商来说,如何衡量广告的价值?
欢迎你给我留言,和我一起讨论。
**参考文献**
1\. Lee, K.-C., Jalali, A., and Dasdan, A. **Real Time Bid Optimization with Smooth Budget Delivery in Online Advertising**. Proceedings of the Seventh International Workshop on Data Mining for Online Advertising, page 1. ACM, 2013.
2\. Xu, J., Lee, K.-c., Li, W., Qi, H., and Lu, Q. **Smart Pacing for Effective Online Ad Campaign Optimization**. Proceedings of the 21st ACM SIGKDD International Conference on Knowledge Discovery and Data Mining, pages 22172226. ACM, 2015.
3\. Agarwal, D., Ghosh, S., Wei, K., and You, S. **Budget Pacing for Targeted Online Advertisements at Linkedin**. Proceedings of the 20th ACM SIGKDD International Conference on Knowledge Discovery and Data Mining, pages 16131619. ACM, 2014.
4\. Chen, Y., Berkhin, P., Anderson, B., and Devanur, N. R. **Real-time Bidding Algorithms for Performance-based Display Ad Allocation**. Proceedings of the 17th ACM SIGKDD International Conference on Knowledge Discovery and Data Mining, pages 13071315. ACM, 2011.
5\. Zhang, W., Rong, Y., Wang, J., Zhu, T., and Wang, X. **Feedback Control of Real-time Display Advertising**. Proceedings of the Ninth ACM International Conference on Web Search and Data Mining, pages 407416. ACM, 2016.

View File

@ -0,0 +1,62 @@
# 092 | 如何设置广告竞价的底价?
在互联网广告生态系统的环境中,我们已经分享了不少关于点击率优化和竞价排名以及如何优化出价的内容,相信你对于广告的整体运作以及其中的核心算法都有了一定的了解。
我们首先来简单回顾一下发布商和广告商或者DSP之间的关系。发布商往往是类似于新闻网站、社交媒体网站和视频网站这样的内容提供方。这些网站的一个特点就是他们本身并不产生收益甚至往往是免费提供服务。因为流量巨大这些内容提供方希望能够通过在自身的网站上发布广告从而获得巨额收益。对于广告商和DSP来说则是希望能够利用发布商的巨大流量来接触更多的用户从而推销自己的服务和产品。
我们之前的很多讨论其实重点都放到了需求侧平台也就是常说的DSP方面包括点击率预估和很多调整竞价排名的方法等等。今天我们就来看一个**发布商**在广告竞价流程中可以参与调优的地方,那就是广告竞价中的底价优化。
## 底价
底价,顾名思义,就是在广告的竞价中给竞拍设定一个最低价。
为什么需要这么做呢?其实在理想的状态下,一个充分竞争的,并且有着充分广告源的市场,广告的单价应该是逐渐升高的。因为广告位资源毕竟是有限的,在有充分广告源的情况下,所有的广告商为了竞争这些有限的广告位,必定是会逐渐抬高广告位的价格。而作为内容发布商,在这个过程中则可以享受到逐渐升高的广告位价值。
然而,在现实中的很多情况下,这种理想状态的竞争态势并不完全存在。比如,对于一个新闻内容提供商来说,在新闻首页顶端出现的广告位一般更能吸引眼球,这种广告位常常可以引起充分竞争,但是在新闻页面下方的广告位则很有可能无法带来充分竞争,因为这些广告位的点击率可能只有顶端广告位点击率的十分之一甚至更少。那么,对于那些无法带来充分竞争的广告位,内容发布商就有可能无法收取理想状态下的收益,甚至在一些比较极端的情况下,会以非常便宜的价格给予广告商。
也就是说,**在真实的广告竞争市场中,很多时候广告位都无法得到充分竞争**。除了我们刚才所说的因为广告位的位置所导致的不充分竞争以外,同一广告位在一天中的不同时段的竞争程度也是不尽相同的。另外,在搜索广告中,不同的搜索关键词也会有不同的竞争情况。
综合这些原因,对于内容发布商来说,如何保护自己的广告位价值并且保证最低收益呢?一种方法就是设置一个广告竞价的最低价格,也就是我们这里所说的**底价**。当我们设置了底价以后,所有的广告竞价都不会低于这个价格,也就人为地抬高了广告位的竞争水准。
既然这是一种保护广告位价值的简单做法,那么会不会带来一些其他的问题呢?答案是,当然会。一个重要的因素就是,这个底价设置得太高,会打击广告商的积极性,进一步影响广告位的竞争,从而让整个市场变得竞争不足拉低价格。而如果这个底价设置得太低,则没有起到实际的作用,广告商仍然可以利用较低的价格获得广告位,而内容发布商可能也没有获得足够的收益。
## 底价优化
在了解了这些关于底价的背景知识以后,我们来思考一下该**如何设置底价**。
在一个基于第二价位的竞价系统中,底价存在三种情况,这些情况的不同会导致发布商有不同的收益。
第一种情况底价高于竞价的最高价。很明显这个时候发布商没有收益因为所有其他的出价都低于底价也就是说底价过高。在实际的操作中这一次广告位请求可能会被重新拍卖Re-Sell
第二种情况,底价高于第二价位。因为是基于第二价位的竞价,所以已经用第一价位获取了广告位的广告商,这个时候就需要支付底价,而不是原本的第二价位的价格。这种情况下,发布商就获取了额外的收益。这个额外的收益就是底价减去之前原本的第二价位。
第三种情况,底价低于第二价位。同理,因为是基于第二价位的竞价,所以这个时候的底价并没有影响原本的第二价位,因此发布商的收益没有变化。
我们讨论了这三种情况以后,就会发现,对于发布商来说,在绝大部分情况下,第二种情况是最理想的,因为这种时候会有额外的收益。那么,如何学习到这个底价就成为了一个挑战。
这里面发布商面临的一个困难是,广告商在提交出价的时候,发布商往往是不知道这个出价的。因此,发布商需要去“猜”所有出价的分布,这无疑是一件非常困难的任务。
在比较早期的研究中\[1\],研究者们借用了“**最优化竞拍理论**”Optimal Auction Theory来研究究竟该如何设置这个出价。
最优化竞拍理论其实假设了发布商知道**出价的一个概率密度函数**再进一步假设这个密度函数是服从“对数正态”Log-Normal分布的然后推导出了一个最佳的底价。在有了这个假设之后就可以利用最佳的底价对广告的竞价进行管理最终在实验中显示对于某一些广告发布商的收益增加了10%以上。
一个更加近期的研究\[2\]则指出在实时竞价RTB的很多场景中出价的分布未必是对数正态分布整个竞价的环境中也有很多并不符合最优化竞拍理论的情况比如广告商出价未必是按照心中的价值出价而是为了赢得更多的广告位。
在这项研究中,作者们提出了一种非常直观的**类似于决策树的策略**,然后研究了在不同情况下发布商策略的不同所带来收益的区别。总体说来,发布商可以采用这样一种策略来调整底价:当发现底价低于最高的出价时,保持或者提高底价;当发现底价高于最高出价时,降级底价。在这种策略的指导下,发布商能够达到一种最佳的收益。
## 总结
今天我为你介绍了广告竞价中底价的设置。
一起来回顾下要点:第一,在真实的广告竞争市场中,很多时候广告位都无法得到充分竞争,为了保证发布商的最佳收益,需要给竞拍设置一个最低价,也就是底价;第二,如何设置底价是一个很困难的任务,以往的研究给我们提供了两种策略可以借鉴,分别是最优化竞拍理论和类似于决策树的策略。
最后,给你留一个思考题,我们应该对所有广告位设置统一的底价吗?还是不同的广告位有不同的底价呢?
欢迎你给我留言,和我一起讨论。
**参考文献**
1\. Ostrovsky, M. and Schwarz, M. **Reserve Prices in Internet Advertising Auctions: A Field Experiment**. Search, pages 118, 2009.
2\. Yuan, S., Wang, J., Chen, B., Mason, P., and Seljan, S. **An Empirical Study of Reserve Price Optimisation in Real-Time Bidding**. Proceedings of the 20th ACM SIGKDD International Conference on Knowledge Discovery and Data Mining, pages 18971906. ACM, 2014.

View File

@ -0,0 +1,58 @@
# 093 | 聊一聊“程序化直接购买”和“广告期货”
在周一的分享里,我们讨论了内容发布商优化自己收益策略的**底价方案**。如果能够把这种底价方案运用得当,就可以增加市场的竞争程度,从而人为地抬高竞价,达到增加收益的目的。
今天,我们来看关于计算广告竞价的另外两个话题:一个是程序化直接购买,另一个是广告期货。
## 程序化直接购买
程序化直接购买Programmatic Direct是指广告商不通过竞价的方式获取发布商的广告位。这往往意味着广告商需要和发布商**直接签订合同来购买一定量的展示机会**Impression
在互联网的早期,其实有很多广告合同是通过直接购买的方式进行的。这一类合同的签订通常是经过相对比较传统的模式,也就是由公司的销售人员直接进行接洽。
直接购买的广告合作合同往往是比较大的。例如,可口可乐公司需要在雅虎主页显示一类新饮料的广告。这种广告要求涵盖的人群广,并且时间也比较长。如果按照竞标的方式,这可能是要竞标上百万次甚至上千万次的展示机会。在这样的情况下,对于广告商和发布商来说,比较快捷的方式反而可能是一次性购买下这些展示机会。
以雅虎为例在很长的一段时间里广告的销售都是分为“有保证的销售”和“无保证的销售”。后者类似于今天的RTB市场而前者就是我们现在所说的直接购买。
时至今日对于顶级的内容发布商来说大家依然喜欢把最优价值的一些广告位比如较好的位置或者尺寸较大的广告留下来当做“独家”Premium广告位用于直接购买的合同。而近些年如何使用程序让直接购买更加便捷就成为了很多广告中间平台的一个重要任务。
那么对于内容发布商或者SSP供应侧平台来说需要做什么事情来推动程序化直接购买呢
首先内容发布商需要预估未来一段时间内展示机会的数量。例如在下一个小时内一共有多少展示机会。这种预估其实就是网站或者服务对流量的估计。然后把这些预估的展示机会分为两个部分一部分送入RTB用于我们之前介绍过的广告竞价排名而另一部分则用于程序化直接购买。
和传统的直接购买不同的是这时候的直接购买是程序化的因此并不需要广告商和发布商之间直接建立联系而是通过平台进行交易。从某种意义上来说这种交易和股票交易十分类似。通常情况下平台显示的是对这一批展示机会的一个统一价格。广告商以及DSP需求侧平台可以根据自己的需要直接购买这个统一价格的展示机会。一般来说这种购买可以提前几个星期甚至几个月。
一旦直接购买和通过竞价排名的方式都程序化以后,对于广告商来说,他们愿意提前直接购买广告位,因为这样购买的广告位价值低于他们的一个心理价位。而对于发布商来说,就需要权衡这两种渠道之间的收益平衡,其实在某种情况下,特别是市场竞争不完全的情况下,这也是发布商希望确保一定收益的方法,也就是在有一定折扣的情况下卖掉广告位。
在程序化直接购买方面进行研究的相关论文非常稀少\[1\],一个原因是这种技术的探讨往往需要比较高级的广告系统作为支撑。
## 广告的期权
到现在为止,我们已经讨论了广告的竞价排名以及程序化直接购买等话题,你是不是已经慢慢感受到,广告生态系统的构架和我们熟悉的另外一个领域的很多概念有着千丝万缕的联系。对,这个领域就是金融系统,特别是股票或者大宗商品的交易。
这里面的联系其实是非常直观的。第一广告和股票交易一样都有大量的交易机会。这就需要出现第三方系统和平台对于股票来说是股票交易所而对于广告来说则是广告的DSP和SSP。第二广告和股票交易一样价值和价格都有可能因为交易带来瞬息万变的差别因此越来越多的金融工具被制造出来来为这个生态系统中的种种角色进行风险控制。
比如对于RTB来说虽然这种机制为广告商和发布商创造了一种交易的模式但是这种模式中基于第二价位的竞价让广告商无法对最终的成交价进行有效控制而且对于发布商而言对于利润的把握也有一定的风险同时广告商和发布商之间也谈不上什么“忠诚度”因为相同的广告位还有可能在其他的发布商那里找到。在这种情况下**“期权”Option这种金融工具就被介绍到了计算广告的环境中**。
最近一段时间以来,已经有了一些零星的研究工作讨论**广告期权的理论和应用**\[2\]和\[3\])。当前,很多发布商是这么设置广告期权的。发布商设置一个未来某个时间点的某个或某些广告展示机会的一个提前价格。这个价格并不是展示机会的实际价格,而是一个权利。对于广告商来说,可以购买这个权利,用于得到未来的这个展示机会。当然,广告商在未来并不一定购买这个展示机会,也可以放弃这个权利。
对于广告商来说,如何参与竞拍,如何在最佳的时机去购买期权,就变成了一个**复杂的优化问题**。当下关于这方面的很多研究,都借用了金融领域的一些模型和算法。
## 总结
今天我为你介绍了在线计算广告的另外两个重要话题程序化直接购买和广告期权。到此为止我们就完整地介绍了DSP和SSP中所有有关出价和竞价的话题。
一起来回顾下要点:第一,我们从广告的历史发展中介绍了程序化直接购买的意义;第二,我们简单聊了聊广告期权存在的目的。
最后给你留一个思考题对于一个DSP来说能不能通过直接购买获得大量的展示机会然后又通过RTB竞价排名把这些机会卖出去这样做的风险是什么
欢迎你给我留言,和我一起讨论。
**参考文献**
1\. Chen, B., Yuan, S., and Wang, J. **A dynamic pricing model for unifying programmatic guarantee and real-time bidding in display advertising**. In Proceedings of the Eighth International Workshop on Data Mining for Online Advertising, pages 1:11:9. ACM, 2014.
2\. Chen, B., Wang, J., Cox, I. J., and Kankanhalli, M. S. **Multi-keyword multi-click advertisement option contracts for sponsored search**. ACM Transactions on Intelligent Systems and Technology (TIST), 7(1):5, 2015.
3\. Chen, B. and Wang, J. **A lattice framework for pricing display advertisement options with the stochastic volatility underlying model**. Electronic Commerce Research and Applications, 14(6):465479, 2015.

View File

@ -0,0 +1,60 @@
# 094 | 归因模型:如何来衡量广告的有效性
在互联网广告生态系统的环境中,我们已经分享了不少关于点击率优化和竞价排名以及如何优化出价的内容。接下来我们开始讨论一些计算广告相关的高级话题。之所以说这些是高级话题,是因为作为机器学习在计算广告的应用,这些话题往往都比较偏冷,但在现实中又特别有实用价值。
今天我们先来聊一聊归因模型,这种技术在计算广告业中被广泛使用。
## 什么是归因模型
**归因模型Attribution Model是一种计算广告中分配“贡献”的机制**。
在现代网站或者应用中,每一个用户都有可能在每一次会话中看到多个不同的广告,或者在多个不同的会话中看到相同广告的不同展示。那么,当用户点击了某个广告,或者是当用户转化以后,比如购买了某个商品或是订阅了某种服务,广告商通常希望知道究竟是哪一个广告起了更大的作用。也就是说,广告商想知道用户接收到的不同广告对这个最后的转化事件都起了什么作用,这个问题就是归因模型研究的核心。
归因模型之所以重要,是因为这里面牵涉到了**广告有效性**这个话题。那么,如何来衡量广告的有效性呢?
衡量广告的有效性,就需要利用归因模型,针对每一个转化来分配“贡献”。这样,对于广告商来说,就可以通过**贡献值的叠加**来看某一个渠道或者某一个内容发布平台的转化效果。
然而归因模型的难点在于这里面并没有完全的“基本事实”Ground Truth全部都基于一定的假设。同时归因模型直接关系到广告是否有效的计算也就关系到我们能否推行一个“公平”的市场以及能否防止其他的广告商在整个平台上进行博弈。
那么,现在各个平台普遍都在使用的是什么样的归因模型呢?下面我给你介绍几个最基本的归因模型。当然了,说这些方法是模型其实也是不够准确的,因为这些方法大多没有理论支撑,主要是基于经验或者基于传统的方法。
第一种经验方法叫“**最后触碰**”Last Touch
顾名思义,最后触碰指的就是**在转化前的最后一个广告拿走100%的贡献值**。这是目前使用最广泛的归因方法,主要是因为它的简单直观。
我们之前讨论过的所有点击率或者转化率的计算都是基于这个归因方法的。一个可以去博弈“最后触碰”的方法就是让DSP需求侧平台把广告投放给那些已经对品牌或者服务产生兴趣的人从而能够以较大的概率获得用户的转化。在这个过程中广告的投放其实并没有起作用而DSP也并没有试图去转化新用户。
举一个例子,如果我们已知一个用户喜欢可口可乐,并且很可能在过去购买过可口可乐,那么给这个用户展示最新的可口可乐促销广告,就很有可能让这个用户点击广告并购买了一箱促销的可乐。但是,在这个情况下,我们还可以认为,这个用户很有可能不需要看这个广告也会购买可乐,所以这个广告其实是浪费了资源。**“最后触碰”其实是鼓励了DSP采用更加保守的投放方式**。
既然有“最后触碰”,那肯定就有“**第一次触碰**”First Touch的经验方法。这种方式和“最后触碰”截然相反那就是只要一个用户最后转化了那么这个用户第一次看到的广告就获得了100%的贡献值。尽管用户可能在第一次看到这个广告后还看了其他的广告,但是这些其他广告都不算数了。**“第一次触碰”其实鼓励了DSP尽可能广地投放广告把广告的投放当做品牌宣传**。
除了这两种比较极端但是被广泛使用的归因方法以外,还有一系列的经验方法,都算是这两种方法的某种平衡状态。比如一种叫“**线性碰触**”Linear Touch的方法是给用户在转化的道路上每一个广告都赋予一样的贡献值。还有一种叫“**位置触碰**”Position Based的方法其实就是“最后触碰”和“第一次触碰”的结合。另外一种经验方法“**时间递减**”Time Decay则是按照由远到近对所有的广告位都给一定的贡献值。离转化事件时间越近的广告获得的贡献值越多。
总之,你可以看到,这些林林总总的经验方法虽然都比较直观,但是在实践中,都有可能给一些广告商利用系统进行不公平投放的机会。
## 基于模型的归因方法
下面我们来看一些具备一定理论基础的归因方法,介绍一个在这方面比较早的探索研究\[1\]。在这个研究里,作者们首先介绍了一种叫**Bagged Logistic Regression**的方法这个方法根据当前广告的“触碰”信息也就是用户看了什么广告来预测用户是否将会转化。在这个模型里所有的特征就是二元的用户是否观看了某个广告的信息然后标签就是用户是否转化。通过这些二元的特征学习到的系数就表达了这个广告在这个预测模型下的贡献度。当然作者们利用了Bagged的方法学习到所有的系数都是正的确保能够解释这个模型的含义。
同时,作者们还提出了一个对归因问题的概率解法,我来介绍下这个概率解法的直观思路。某一个广告对用户转化的最后作用都来自两个部分:第一部分是这个广告对用户转化的直接作用;第二个部分是当前这个广告和另外一个广告一起作用使用户转化的概率。当然,这个第二部分的联合作用需要减去这两个广告分别单独作用于用户的情况。那么,一个广告对于用户的影响,就是这两个部分概率的加和,这其实就是考虑了一阶和二阶的关系下的归因模型。
知道了归因信息之后,我们还可以把这个信息利用到广告的竞价中。直白来说,就是针对有价值的渠道进行有效的出价,而对没有效果的渠道进行控制\[2\]。除此以外,归因信息还可以帮助广告商来分配自己的预算,把大部分的预算用在优质的渠道中来投放广告\[3\]。
## 总结
今天我为你介绍了在线计算广告的一个高级话题:归因模型。
一起来回顾下要点:第一,归因模型是一种计算广告中分配贡献的机制,广泛使用的方法有最后触碰和第一次触碰等;第二,有一些有一定理论基础的归因方法,我们其实可以拓展归因信息的应用场景。
最后,给你留一个思考题,如何来衡量一个归因方法是否有效呢?
欢迎你给我留言,和我一起讨论。
**参考文献**
1. Shao, X. and Li, L. **Data-driven multi-touch attribution models**. Proceedings of the 17th ACM SIGKDD International Conference on Knowledge Discovery and Data Mining, pages 258264. ACM, 2011.
2. Xu, J., Shao, X., Ma, J., Lee, K.-c., Qi, H., and Lu, Q. **Lift-based bidding in ad selection**. Proceedings of the 30th AAAI Conference on Artificial Intelligence, 2016.
3. Geyik, S. C., Saxena, A., and Dasdan, A. **Multitouch attribution based budget allocation in online advertising**. Proceedings of 20th ACM SIGKDD Conference on Knowledge Discovery and Data Mining, pages 19. ACM, 2014.

View File

@ -0,0 +1,60 @@
# 095 | 广告投放如何选择受众?如何扩展受众群?
从上一期的分享开始,我们来讨论计算广告相关的一些高级话题。作为机器学习在计算广告的应用,这些话题往往偏冷,但在现实中又有很大的实用价值。我们首先聊了归因模型,介绍了几种经验方法和一些基于模型的归因方法,这种模型在计算广告业中举足轻重,不过也常常被人忽视。
今天我们来看另外一个重要的话题那就是如何帮助广告商扩大受众群我们也把这种技术称为受众扩展Audience Expansion技术。
## 什么是受众
广告商在投放广告时有一个最根本的需求,就是希望通过广告平台接触到更多有可能被转化的受众群。所以,对于绝大多数的广告平台而言,满足广告商的这个需求就成为了一个非常重要的功能。
为了让广告商来选择受众,不少广告平台提供两种最基本的功能。
第一种方式是**搜索广告的模式**,也就是广告商可以选择**通过某个关键词或者一系列关键词**来接触到希望投放的受众。这里面其实有一个假设,那就是受众的兴趣或者意图是和关键词联系在一起的,而如果投放的广告内容和受众的兴趣以及意图相符,那么对于广告商来说,就可以假设这种情况下受众的转化率是最高的。
第二种就是**通过某种选择受众群的方式来让广告商自由地选择广告投放的对象**。这里最基本的方式是通过受众的“人口”Demographic信息来进行投放。典型的人口信息包括年龄、性别和地域。
不管是采用关键词还是人口信息来进行受众选择,这些方法看似简单直观,但其实也给广告商带来了不小的挑战。
首先,我们来看关于搜索关键词的难点。作为一个广告商,你怎么知道所有的跟你产品或者服务相关的关键词呢?理论上说,可能会有无穷无尽的关键词可供投放。但是关键词的投放数量也和成本有着密切的关系。所以,从现实的角度来讲,肯定是无法投放所有的关键词的。
其次,利用人口信息来选择受众,那如何来找到比较合适的人口信息呢?这里面就有很大的挑战了。广告商可以利用一些研究结果来找到对应的人口信息从而增强广告投放的效果。然而针对很多中小广告商来说,花费很大的精力和时间去研究这些不同的人口信息和广告效果之间的关系显然是不可能的。
除了我们刚才所说的这两种广告商选择受众的方式以外,现在也有不少的广告平台并不需要广告商进行“显式”的受众选择。这些服务其实就是看到了这种选择带给广告商的复杂性,与其让广告商来选择,还不如让广告平台来优化。于是,有很多广告平台提供的就是“**傻瓜式**”的广告服务,广告商仅需要设置预算信息,对于人群的投放则完全由广告平台来负责。
## 受众扩展
了解了受众的选择以后,一个很现实的问题就摆在了广告平台商的面前,如何帮助广告商来扩展已经选择了的受众群体,从而能够实现受众转化的最大化呢?
来自LinkedIn的几位作者就探讨了在社交媒体广告中受众扩展的这个问题\[1\]。在LinkedIn平台上广告商也就是雇主可以针对不同的群体限制条件也就是我们所说的受众来投放广告以吸引潜在的雇员和候选人。广告商在投放广告的时候可以按照雇员的职业技能比如是否会Java是否会机器学习等以及一些其他的信息例如来自哪个公司、地理位置来选择投放的受众。这和我们之前介绍的场景一样很明显即便是广告商精心选择一个看似比较有效的受众在这种情况下其实依然有很多种其他选择的可能性。
在这篇文章里,作者们介绍了这么几种受众扩展的思路。
第一种思路是和某一个广告推广计划Campaign无关的。这里主要是**通过一种“类似”算法而找到类似的公司、类似的技能等等**。这种扩展的好处是可以对任何广告推广进行扩展而无需积累数据。
第二种思路是广告推广相关的扩展。这里其实还是利用了“类似”算法,但是在扩展的时候是根据广告商**对当前这个广告推广所选择的条件来进行选择**,这样的选择自然就会和当前的广告推广更加相关。
在实际操作中LinkedIn采用了这两种思路结合的方法。先利用于推广无关的扩展方法来获取最初的一些扩展用户尽管这部分用户可能质量不高。然后当广告推广已经运行了一段时间以后再针对这个广告推广的选择进行扩展就可以找到更加高质量的扩展用户群体。
我们看到这些扩展方法都依赖于“**类似**”算法,这里我简单说一下这个算法的核心思想。
总体来说这个算法是针对某一个实体可以是公司、人名、地域、技能等通过搜索的方法来返回最相关的K个其他实体。作者们把这个问题看成了一个**监督学习的问题**。其核心就是利用了一个**对数几率模型**,对相似的正例和负例进行学习。
那么,哪些实体是正例,哪些是负例呢?作者们把用户频繁选择放在一起投放的实体当做了正例,而把其他的实体当做负例。对于特性来说,这里广泛采用了**文本特性**包括文本的词包表达、以及N元语法N-gram组成的特性。同时这里还利用了**图相关度**来推算,比如两个公司在社交关系上的相关程度。然后,两个实体之间的**余弦相关度**也作为一种特性被包含在了模型中。
在线上实验的结果中所有受众扩展的效果都比不用扩展有显著的提升。特别是在混合扩展的模式下展示机会、点击率和总的收益都提升了10%以上。这个实验结果可以用来说明受众扩展的重要性。
## 总结
今天我为你介绍了在线计算广告的另外一个高级话题:受众扩展。
一起来回顾下要点:第一,广告商可以通过关键词或者人口信息等方式来选择受众,不过受众选择也并不容易,有很大的挑战性;第二,我们介绍了和推广计划有关的与无关的两种受众扩展思路,以及将两种思路结合的方法,并简单介绍了两种思路都依赖的“类似”算法。
最后,给你留一个思考题,在什么情况下受众扩展可能会出现问题,如何来衡量这些问题?
欢迎你给我留言,和我一起讨论。
**参考文献**
1. Haishan Liu, David Pardoe, Kun Liu, Manoj Thakur, Frank Cao, and Chongzhe Li. **Audience Expansion for Online Social Network Advertising**. Proceedings of the 22nd ACM SIGKDD International Conference on Knowledge Discovery and Data Mining (KDD '16). ACM, New York, NY, USA, 165-174, 2016.

View File

@ -0,0 +1,76 @@
# 096 | 如何利用机器学习技术来检测广告欺诈?
在上一期的内容中,我们聊了如何帮助广告商扩大受众群这个话题,也就是受众扩展技术。受众扩展的目的是让广告商投放的广告能够接触到更广泛的受众,甚至有可能提高广告效果。
在计算广告高级话题的最后一篇分享同时也是整个广告模块的最后一篇分享里我想来聊一聊广告中一个非常棘手同时也是一个非常实际的问题欺诈检测Fraud Detection
## 什么是广告欺诈
广告欺诈是一个多大规模的问题呢?
根据一个统计数字\[1\]到2015年的时候就因为广告欺诈全美的市场营销和媒体业每年的耗费约为82亿美元。这个数字中大约有56%也就是46亿多美元的耗费来自于“**非法流量**”Invalid Traffic。我们把这个数字和全美每年596亿的广告支出进行对比就可以看出这是一个惊人的数字。当然因为各种欺诈手段层出不穷并不是所有的欺诈都能够被甄别出来。因此我们其实有理由相信真实的数字会更高。
那么,怎么来定义广告欺诈呢?什么样的行为算是广告欺诈呢?
我们这里主要讨论三种形式的广告欺诈。这三种广告欺诈模式其实对应着三种流行的广告计费模式。
第一种欺诈叫“**展示欺诈**”Impression Fraud也就是造假者产生虚假的竞价信息然后把这些竞价展示放到广告交易平台上去贩卖并且在广告商购买了这些展示后获利。
第二种欺诈叫“**点击欺诈**”Click Fraud也就是造假者在广告商产生虚假的点击行为。
第三种欺诈叫“**转化欺诈**”Conversion Fraud也就是造假者完成某种虚假的动作例如填写表格下载某个应用等来虚拟真实的转化事件。
在真实的场景中,这三种欺诈手段经常混合出现。例如点击欺诈和展示欺诈可能同时出现,这样就能在报表中展示一个看似合理的点击率。
## 广告欺诈的产生源
了解了广告欺诈的基本形式之后,我们来看一下这些欺诈产生的源头都在什么地方。因为广告产业的有利可图,产生欺诈的途径也是多种多样的,我们这里就看一些经典的形式。
首先,有一种欺诈来源途径叫**PPV**Pay-Per-View网络。
利用PPV进行欺诈的主要流程就是尝试通过购买流量然后在一些合法的展示机会中插入用户肉眼看不见的0像素的标签Tag诱导广告商让广告商以为产生了更多的合法流量。
对于这样的欺诈一般来说广告商必须去检测展示机会用户是不是看不见或者是否是由0像素产生的。然后还可以采用黑名单的方式对屡次利用PPV来进行欺诈的IP地址进行屏蔽。
另外一种欺诈手段是通过“**僵尸网络**”Botnets
这种方法主要是试图直接控制用户的终端电脑或者其他的移动设备从而进行很多方面的攻击。在过去僵尸网络的一大应用主要是来产生拒接服务的DDoSDistributed Denial of Service攻击和发送垃圾信息。
近年来因为其灵活性很多僵尸网络也被用于广告欺诈。僵尸网络的一大作用就是产生浏览信息而这些浏览的行为是宿主电脑的用户所无法得知的。因此对付僵尸网络的一大方法就是检测从某些IP地址或者DNS产生的流量行为是否发生了突然的根本性的变化。
第三类欺诈手段是“**竞者攻击**”Competitor Attack
正常的广告商设立预算参与竞价购买广告位。而竞争对手可以利用“点击欺诈”的方式产生虚假无效的点击信息,从而消耗广告商的预算。当把竞争对手的预算消耗光以后,攻击者反而可以用比较小的成本拿到这些广告位,因为竞争减少了。
另外,还有一种情况是仅仅大量调入竞争对手的广告而不点击。在这样的情况下,就容易产生非常低的点击率。而很多广告平台依赖点击率来进行排序,因此,如果点击率很低,那代价就是难以赢得竞价,通过这种方式也就间接打压了竞争对手。
## 欺诈检测
了解了什么是广告欺诈以及不同的广告欺诈来源之后,我们来看一看如何利用机器学习技术,来对各种不同的欺诈行为进行检测和挖掘。
首先介绍一个研究\[2\],作者们提出了一种技术,**利用“同访问”图来分析异常的浏览行为**。这里面有一个最基本的假设:对于大多数用户来说,对两个不同的网站并不具有相同的喜好程度,除非这些网站非常流行。也就是说,对于绝大多数的网站来说,其用户群体是不一样的。
如果用户和这些网站的相互关系发生了变化,那可能就是出现了一些异常的情况。当然,利用图分析的方法,就是把异常发掘当成了一种无监督学习的任务,自然也就会有无标签的困难。
还有一个研究\[3\],作者们提出了一种方法,来**分析用户到底需要花多少时间来浏览显示的像素**。这个方法其实就是来检测是否是0像素的展示欺诈。作者们通过研究发现对于50%以上的像素绝大多数用户至少需要1~3秒时间来观看。于是广告商或者平台就可以用这种停留时间来作为一个最基本的检测手段。
当然,一种最普遍的做法就是把广告欺诈当做一个**监督学习任务**。通过产生各种格样的特性以及把过去已知的欺诈数据当做训练数据来进行学习。这种做法的难点是,欺诈数据在真实世界中毕竟是少数。于是,我们就有了数据不足以及需要训练和不平衡的分类问题。正是因为存在这些问题,欺诈检测依然是一个非常前沿的研究领域。
## 总结
今天我为你介绍了在线计算广告的最后一个高级话题:欺诈检测。
一起来回顾下要点第一我们讲了三种形式的广告欺诈分别是展示欺诈、点击欺诈和转化欺诈在真实场景中这三种欺诈手段经常混合出现第二产生欺诈的源头很多我们简单介绍了三种不同类型的广告欺诈来源分别是PPV网络、僵尸网络和“竞者攻击第三我们讨论了欺诈检测的一些基本思路比如利用图分析、利用停留时间的方法等等。
最后,给你留一个思考题,如何来检测转化欺诈,也就是我们怎么知道广告转化中哪些是虚假的呢?
欢迎你给我留言,和我一起讨论。
**参考文献**
1\. Interactive Advertising Bureau (2015). **What is an untrustworthy supply chain costing the us digital advertising industry**?
2\. Stitelman, O., Perlich, C., Dalessandro, B., Hook, R., Raeder, T., and Provost, F. **Using co-visitation networks for detecting large scale online display advertising exchange fraud**. In Proceedings of the 19th ACM SIGKDD International Conference on Knowledge Discovery and Data Mining, pages 12401248. ACM, 2013.
3\. Zhang, W., Pan, Y., Zhou, T., and Wang, J. **An empirical study on display ad impression viewability measurements**. arXiv preprint arXiv:1505.05788, 2015.

View File

@ -0,0 +1,79 @@
# 037 | “查询关键字理解”三部曲之分类
我们在前两周的专栏里主要讲解了最经典的信息检索Information Retrieval技术和基于机器学习的排序算法Learning to Rank
经典的信息检索技术为2000年之前的搜索引擎提供了基本的算法支持。从中衍生出的TF-IDF、BM25还有语言模型Language Model以及这些方法的各类变种都还在很多领域不限文本里继续发挥着作用。
另一方面排序学习算法引领了2000年到2010年各类基于机器学习的搜索算法的产生和发展也带来了搜索引擎技术的进一步成熟。
这周我们从排序算法转移到排序问题中一个非常重要的部分查询关键字理解Query Understanding。也就是说我们希望通过查询关键字来了解用户种种行为背后的目的。查询关键字产生的特征Feature往往是很强的指导因素也是个性化搜索结果非常重要的源泉。因此深入了解并掌握查询关键字理解方面的技术就变得很有必要。
**查询关键字理解最基本的一个步骤就是给查询关键字分类Classification看这些查询关键字有什么用户意图Intent**。今天我就来聊一聊查询关键字分类的一些基本概念和技术,让你对这方面的开发和研究有一个基本认识。
## 查询关键字分类的历史
从商业搜索引擎开始面世的第一天起人们就发现可以从查询关键字中得到很多用户的信息特别是理解用户的意图。早在1997年商业搜索引擎Excite就开始了百万级别查询关键字的研究工作。然而真正对查询关键字分类进行系统阐述的是安德烈·布罗德Andrei Broder的论文《网页搜索分类》A Taxonomy of Web Search
安德烈很有名头在斯坦福大学攻读博士期间师从图灵奖得主高德纳Donald Knuth然后在曾经名噪一时的第一代搜索引擎公司AltaVista后被雅虎收购担任首席科学家之后加入位于纽约的IBM研究院组建企业级搜索平台2012年后加入Google担任杰出科学家Distinguished Scientist。他还是ACMAssociation of Computing Machinery计算机协会和IEEEInstitute of Electrical and Electronics Engineers电气电子工程师学会的双料院士。
安德烈的这篇论文可以说是奠定了查询关键字分类的坚实基础。这之后研究人员的很多工作都是围绕着如何自动化分类、如何定义更加精细的用户意图来展开的。
## 查询关键字分类详解
我就从安德烈这篇非常有名的文章说起。在网络搜索Web Search成为比较主流的咨询查询手段之前传统的信息检索认为查询的主要目的是完成一个抽象的“信息需求”Information Needs。在传统信息检索的世界里最主要的应用应该是图书馆检索或者政府学校等企事业单位的检索。因此在这样的场景下假定每一个查询主要是满足某个“信息需求”就显得很有道理了。
然而早在2002年安德烈就认为这样的传统假定已经不适合网络时代了。他开始把查询关键字所代表的目的划分为三个大类
1. 导航目的Navigational
2. 信息目的Informational
3. 交易目的Transactional
此后十多年里,查询关键字的这三大分类都是这个方向研究和实践的基石。我们先来看这个分类的内涵。
**第一类,以导航为意图的查询关键字,这类查询关键字的目标是达到某个网站**。这有可能是用户以前访问过这个网站,或者是用户假设有这么一个关于所提交查询关键字的网站。这一类查询关键字包括公司的名字(如“微软”)、人的名字(如“奥巴马”)或者某个服务的名字(如“联邦快递”)等。
此类查询关键字的一个重要特点就是,在大多数情况下,这些查询关键字都对应唯一的或者很少的“标准答案”网站。比如,搜索“微软公司”,希望能够找到的就是微软公司的官方网站。另一方面是说,某些“信息集成”网站也是可以接受的“答案”。比如,查询“奥巴马”,搜索返回的结果是一个列举了所有美国总统的网站。
**第二类,以信息为意图的查询关键字,这类查询关键字的目标是搜集信息**。这一类的查询和传统的信息检索非常接近。值得提及的是从后面的研究结论来看这一类查询关键字所包含的目标不仅仅是寻找到某类权威性质Authority的网页还包括列举权威信息的俗称“结点”Hub的网站。
**第三类以交易为意图的查询关键字这类查询关键字的目标是到达一个中间站点从而进一步完成“交易”Transaction**。这一类查询关键字的主要对象就是“购物”。现在我们对“电子商务”的态度可以说是非常自然了,但是十多年前,在传统信息检索界统治的搜索研究领域,提出“交易”类型的查询关键字可以说是很有新意的。
当然这样的分类如果仅仅是概念上的区分那就没有太大的意义。安德烈利用搜索引擎AltaVista进行了一次调查研究这次调查有大约3千多的用户反馈。想到这是在2001年的调查可以说已经是大规模的研究了。
这次调研的结果是这样的在用户提交的信息中导航类型的查询关键字占26%交易类型的查询关键字占到了24%而剩下的将近50%是信息类型的查询关键字用户的日志Log分析进一步证实了这一数据。
你可以看到,**这种把查询关键字进行分类的研究是对用户行为进行建模的必要步骤**。于是,很快就有不少研究人员嗅到了查询关键字分类的价值。然而,完全依靠用户直接反馈来获取这类信息则变得越发困难。
这里主要有三个原因。第一,不可能寄希望于用户汇报自己所有关键字的意图;第二,面对亿万用户输入的查询关键字,手工标注也是不可能的;最后,安德烈的三类分类还是太粗犷了,在实际应用中希望得到更加细颗粒度的用户意图。
把查询关键字分类问题转换成为标准的机器学习任务其实很直观。确切地说,这里需要做的是**把查询关键字分类转换成为监督学习任务**。这里,每一个查询关键字,就是一个数据样本,而响应变量,则是对应的类别。具体情况取决于我们的任务是仅仅把查询关键字分为几个类别,并且认为这些类别之间是互相独立的,还是认为这些类别是可以同时存在的。
在最简单的假设下,查询关键字分类就是一个普通的**多类分类问题**可以使用普适的多类分类器比如支持向量机SVM、随机森林Random Forest以及神经网络Neural Networks等来解决这类问题。
**对于绝大多数监督学习任务而言,最重要的一个组成部分就是选取特征**。随后很多年的研究开发工作中,有一部分就集中在尝试使用不同的特征,然后来看对提高分类的精度是否有效果。
过去的研究反复证明,以下几类特征非常有效。
**第一类特征就是查询关键字本身的信息**。比如,查询关键字中已经包括了已知的人名或者公司名,这种时候,分类结果就不太可能是交易意图的类别。也就是说,查询关键字,特别是某些词或者词组和类别有某种关联信息,而这种关联很大程度上能被直接反映出来。
**第二类特征是搜索引擎返回的查询关键字相关的页面本身的信息**。你可以想象一下,假如搜索“奥巴马”这个关键字,返回的页面都是维基百科的页面以及奥巴马基金会的页面,那么这些页面上面的内容可能很难包含任何商业的购买信息。而对于“佳能相机”这个查询关键字而言,返回的页面很可能都是电子商务网站的商品信息,从而能够更加准确地判断“佳能相机”的分类。
**第三类特征则是用户的行为信息,那就是用户在输入查询关键字以后会点击什么网站,会在哪些网站停留**。一般来说,哪些网站点击率高、停留时间长,就表明这些网站在返回结果中可能更相关。于是,采用这些网站来作为查询关键字所代表的内容,就可能更加靠谱。
在实际的应用中,查询关键字的分类往往还是有很大难度的。因为在普通的现代搜索引擎上,每天可能有三分之一、甚至更多的关键字是之前没有出现过的。因此,如何处理从来没有出现过的关键字、如何处理长尾中的低频关键字,就成了让搜索结果的精度再上一个台阶的重要因素。我今天就不展开相应的话题了,如果你有兴趣,可以查看相关论文。
## 小结
今天我为你讲了现代搜索技术中一个非常基础但是也在实际应用中至关重要的环节,那就是查询关键字理解中的用户意图分类问题。你可以看到**查询关键字从大类上分为信息意图、交易意图以及导航意图三类**。
一起来回顾下要点:第一,简要介绍了查询关键字分类提出的历史背景,安德烈·布罗德的论文奠定了查询关键字分类的坚实基础。第二,详细介绍了主要的分类以及如何通过多类分类器的构建来达到自动化的目的。
最后,给你留一个思考题,在机器学习排序算法中,我们应该如何使用查询关键字分类的结果呢?
欢迎你给我留言,和我一起讨论。
**拓展阅读**[A taxonomy of web search](https://www.cis.upenn.edu/~nenkova/Courses/cis430/p3-broder.pdf)

View File

@ -0,0 +1,66 @@
# 038 | “查询关键字理解”三部曲之解析
这周我分享的核心内容是查询关键字理解Query Understanding。周一介绍了查询关键字分类Query Classification的基本概念和思想。今天我来讲一个更加精细的查询关键字理解模块**查询关键字解析**Parsing
如果说查询关键字分类是对查询关键字的宏观把握,那么,对查询关键字的解析就是微观分析。其实,查询关键字解析是一类技术的统称,我今天就来聊几个比较热的话题。
## 查询关键字分割
首先让我们设想这么一个场景在英文的搜索引擎中如果一个用户输入的是“White House Opening”这个查询关键字这个用户的意图Intent是什么呢要想理解用户的意图我们就得知道用户输入的单词的涵义。
那么在上面这个查询关键字里我们到底是分别理解每一个单词“White”、“House”和“Opening”呢还是“White House”和“Opening”呢还是有可能“White House Opening”是一个整体呢这里说的其实就是“**查询关键字分割**”Query Segmentation这个概念。
在刚才的例子中如何把“White House Opening”进行分割直接关系到搜索结果的质量。试想在一个比较标准的现代搜索引擎里一般来说都会有一个模块根据查询关键字来提取“**倒排索引**”Inverted Index中的文档。这个阶段的提取数目一般是几百到几千这个过程常常被称为“**检索流程**”Retrieval Phase
当有了这些文档以后现代搜索引擎会利用比较复杂的排序算法通常就是我们之前提到过的基于机器学习的排序学习模型来对文档进行重新排序Re-Rank
你可以看到,在这样两个阶段的流程里,如果好的文档没有在第一个阶段被提取出来,不管第二个阶段的功能有多强大,搜索的整体结果都不可能有多好。而对于“检索流程”而言,在“倒排索引”中进行查询的关键就是使用什么“单词”或者“词组”进行查找。
用刚才的例子来说就是看文档究竟是符合“White House”还是“White或House”还是“White House Opening”。很明显这三种情况得到的文档集合是不尽相同的。如果用户的真实意图是搜索美国总统府白宫的开放时间那么把这个搜索关键字给分割成“White或House”很明显就会影响提取的文档集合。
那究竟该怎样做查询关键字分割呢?
这里我介绍一篇论文《重新审视查询关键字分割》Query Segmentation Revisited )。在这篇论文里,作者们集中介绍了一些主流的“查询关键字分割”技术,文章非常值得精读。下面我为你归纳一下要点。
**第一种技术就是尝试从查询关键字里面产生“N元语法”N-Grams**。所谓N元语法其实就是从一组词语中产生连续的子词语。比如刚才的“White House Opening”的例子我们就可以从这个词组里面产生“White House”和“House Opening”两个二元语法。
而第一种基于N元语法的方法就是通过这些N元语法在一个大语料中出现的词频来判断这个“分割”是否有意义。当然直接采用词频可能会比较偏好短的单词所以在论文中作者们分别介绍了两种矫正词频的方法。
一种是基于词频本身的矫正一种是基于维基百科作为一个外部资源的矫正方式。两种方法的目的都是为了让长短语的打分Scoring有机会高于短的单词。文章中所需要的词频采用了谷歌2005年发布的“N元语法”语料也就是说所有单词出现的频率都是直接在这个语料中获得的。
**第二种技术是基于短语“互信息”Mutual Information的方法**。“互信息”计算了两个随机事件的相关程度。在这里就是计算查询关键字中每两个相邻短语的“互信息”。当这个“互信息”的取值大于某一个预设阈值的时候我们就认为相邻的两个单词组成了短语。“互信息”的计算需要知道某个单词出现的概率这些概率是从微软发布的一个“N元语法”语料获得的。
**第三种技术则是基于“条件随机场”Conditional Random Field**。“条件随机场”是机器学习著名学者乔治·拉菲迪John D. Lafferty、安德鲁·麦卡伦Andrew McCallum和费尔南多·佩雷拉Fernando Pereira在2001年发表的“序列学习”模型Sequence Model中提出的。条件随机场的基本思想是对输出的复杂标签进行建模尝试从特征空间建立到复杂标签的一个对应关系。
在“查询关键字分割”的场景下,我们其实可以把复杂标签看作是从一个查询关键字到多个短语的多个二元决策问题。这里的二元决策是指某一个备选短语是否可以作为分割的短语。条件随机场可以比较直观地对这类问题进行建模,而传统的二分分类器则很难对序列信息进行建模。我在这里就不详细展开条件随机场的介绍了,有兴趣的话可以翻看相关的论文。
## 查询关键字标注
刚才我聊了查询关键字理解最基本的“分割“问题。可以说,“分割问题”是查询关键字理解的第一步。那么,下一步则是更细致地分析查询关键字。
回到刚才的例子“White House Opening”我们其实不仅是想知道这个查询关键字可以分割为“White House”和“Opening”而且希望知道“White House”是一个建筑物的名字或者一个地理位置的名字而“Opening”则可能是一个名词暗指“开门时间”。也就是说我们希望为查询关键字中的词组进行“标注”Annotation来获取其“属性”Attribute信息。希望为查询关键字中分割出来的词组进行标注的组件就叫做“**查询关键字标注**”。
那么,标注信息又是怎样帮助搜索结果的呢?试想一下“苹果价格”这个查询关键字。这取决于用户搜索的场景,如果“苹果”代表“水果”这个属性,那么这个查询的结果是希望找到水果的价格,可能还需要搜索引擎返回附近超市的一些信息。但如果“苹果”其实代表的是“手机”,那这个查询的结果也许最好是返回苹果公司的官方销售网站。你看,“苹果”所代表的属性不同,最优的返回结果可能会有非常大的差别。
对查询关键字进行标注的方法也有很多。我这里再推荐一篇经典的论文《使用伪相关反馈针对搜索查询关键字进行结构化标注》Structural annotation of search queries using pseudo-relevance feedback这篇论文**利用一个叫做PRFPseudo-Relevance Feedback的方法来进行标注**。这里面的一个技术难点是查询关键字的信息实在是太少需要利用大量的辅助信息来进行标注因此PRF作为一个技术在这里得到了应用。
另外一个主流的查询关键字标注的方法,依然是利用条件随机场。我前面讲了,条件随机场是很好的序列建模工具。那么,在这里,以“苹果价格”为例,条件随机场是需要预测标签是否是“手机名词”还是“水果名词”这样的组合输出结果。而传统的二分或者多类分类器很难捕捉到这里的序列信息,条件随机场就是解决这方面的利器。
于是我们需要做的就是为查询关键字构建特征Feature然后直接放入条件随机场中。有一点需要注意条件随机场的应用成功与否与数据的多少有很大关系。因此**构建一个有标注信息的数据集就变成了查询关键字标注的一个核心挑战**。
## 小结
今天我为你讲了现代搜索技术中的一个重要环节,那就是查询关键字理解中的查询关键字解析问题。你可以看到查询关键字解析从大类上分为查询关键字分割和查询关键字标注两个比较重要的模块。
一起来回顾下要点第一简要介绍了查询关键字分割的场景和三种主要技术分别是“N元语法”、“互信息”和“条件随机场”。第二详细介绍了查询关键字标注的场景和主要技术包括利用PRF和利用条件随机场两种主流的标注方法。
最后,给你留一个思考题,我举了英语的查询关键字的解析问题,那么对于中文而言,又有哪些特殊的挑战呢?
欢迎你给我留言,和我一起讨论。
**参考文献**
1. Matthias Hagen, Martin Potthast, Benno Stein, and Christof Bräutigam. Query segmentation revisited. _Proceedings of the 20th international conference on World wide web (WWW '11)_. ACM, New York, NY, USA, 97-106. 2011.
2. Michael Bendersky, W. Bruce Croft, and David A. Smith. Structural annotation of search queries using pseudo-relevance feedback. _Proceedings of the 19th ACM international conference on Information and knowledge management (CIKM '10)_. ACM, New York, NY, USA, 1537-1540. 2010.

View File

@ -0,0 +1,78 @@
# 142 | 数据科学家必备套路之一:搜索套路
到目前为止,我们已经完整地介绍了搜索、推荐和广告的主流技术,为你呈现了这些产品技术方向的整个生态系统。在这些系列的分享里,我们重点介绍了这些技术方向的基本模型,然后花了不少篇幅讲如何评测模型的好坏,包括如何进行线下评测以及线上评测。同时,我们从传统的经典模型讲到最近几年利用深度学习对这些技术方向的提升,帮助你理顺了这些技术发展的脉络。
尽管我们已经在之前的文章中分享了这些技术的方方面面,但是对于很多经验较少的数据科学家或者人工智能工程师来说,依然会感到无法得心应手地把这些模型和知识给应用到真实场景中。
其实,出现这种情况一方面是个人经验积累的原因,毕竟从初学者到能够熟练应用各种模型工具应对实际产品的需要,是一个长时间磨炼的结果;然而另一方面,也是因为搜索、推荐和广告这些产品场景其实是有一些套路,在没有接触到这些套路的时候往往会觉得不得要领,而在慢慢熟悉了这些套路之后,进步也就会慢慢加快。
那么,在接下来的三篇文章里,我就有针对性地来分享在这三个领域里的一些常见套路。今天,我们首先从搜索产品套路说起。
## 多轮打分套路
我们前面已经介绍过多轮打分的系统架构了。**当我们想要构建任何一个搜索引擎时,都应该立刻想到多轮打分这个架构**,这是一个基本套路。
我们先来回顾一下多轮打分最基本的模式:针对一个搜索关键词,我们首先从索引中找到第一批,也是数目相对比较多的相关文档,这就是第一轮打分;然后,我们再根据一个相对比较复杂的模型,对剩余的文档进行打分排序,这是第二轮打分。
很多时候,两轮打分就已经能够满足需求了,可以直接给用户返回结果集。当然了,我们也常常加入第三轮打分,这个时候经常是实现一个商业逻辑层,可以针对最后的搜索结构加上一些商业规则。
多轮打分这个套路之所以关键,是因为它其实影响了一系列的技术决定。
首先,只有第一轮是直接作用在索引上的。这一轮往往可以并行化,并且不需要太多考虑文档之间的顺序。
我来举个例子说明。在一个大型搜索引擎的架构下假设我们有一亿个文档每五百万个文档存放在一个数据节点上。如果我们有一个关键词是“人工智能”那么就要到这20个数据节点上分别查找包含这个关键词的文档。当我们把所有的文档汇集起来以后排序取出前1000个文档这个时候就进入第二轮。最简单的第二轮打分是在一个计算节点上进行的而且这个时候我们只针对1000个文档进行打分对计算时间的要求就大幅度降低了。那么在这样的情况下第二轮打分使用的模型可以是比较复杂的模型。后面每一轮打分所针对的文档往往是越来越少因此模型的复杂度可以越来越高。
从解决问题的角度上来说,**第一轮在索引上的打分是要解决“召回”Recall的问题**。这一步有可能是从非常多甚至是成千上万的文档中返回几百到几千不等的文档,因此一旦一些文档没有在这一轮中被返回,就无法在后面的轮数中被重新筛选出来。所以,我们要理清这么一个思路,那就是如果你认定系统有“召回”的问题,也就是说,本该搜出来的东西,完全搜索不出来,那肯定是在第一轮打分就出了问题。**第二轮以后的打分解决的就是“精度”Precision的问题**。
同时,我们可以看到,什么时候解决“召回”问题,什么时候解决“精度”问题,这其实是**取决于具体的业务场景**。
对于“精度”非常看重的搜索场景比如说网页的信息类关键词例如“特朗普”、“比尔盖茨”人们往往只关注前10位甚至是前3位的搜索结果。那么很明显我们可以先有一个比较简单的第一轮架构比如就是文字匹配而把功夫都下在第二轮以后的打分上。
而对于“召回”比较看重场景,比如说法律文档搜索,那必须要做好的就是第一轮的打分,这个时候可能需要采用简单的文字直接匹配和语义的模糊匹配。
## 高频和长尾的套路
刚开始接触搜索产品的朋友往往会有一个困惑,那就是不知道该如何提升一个搜索产品,有一种无从下手的感觉。那么,对于搜索产品的提高有没有什么套路可言呢?
一个比较基本的套路,就是**把搜索关键词按照某种频率或者是流量分为“高频关键词”和“长尾关键词”**,从而为这两类不同的关键词设计排序算法。
为什么要把关键词按照频率分开呢?我来介绍一下最主要的思路。
对于很多搜索网站来说,一些高频的关键词往往占据了相对来说比较大的流量,而很多长尾的关键词,也就是仅仅出现过几次的关键词则并没有太多人搜。因此,如果我们先解决了高频的关键词,也就解决了大部分的搜索问题,从而可以把精力留下来慢慢解决低频的长尾关键词。
而实际上,高频关键词因为有足够多的数据量,反而往往比较容易得以解决,而低频关键词,因为数据的匮乏,往往需要更多的精力和时间。所以说,从投资回报的角度来看,我们也需要做区分,首先来解决高频的搜索关键词。
刚才我们提到了高频关键词的一个特点,就是有足够多的用户数据。那么,这里有一种非常简单的思路,或者说是在没有较好模型的时候可以首先使用的一种方法,那就是**每天记录下高频关键词文档的用户点击数据**。然后我们可以直接按照**点击率**,或者是文档的**转换率**排序,并且把这个排序存在某种存储中。当用户在这一天搜索这些高频关键词的时候,我们甚至可以直接从存储中调出事先算好的排序结果。
更加极端的做法,就是**手工对高频词进行更频繁的标注**这种做法往往也是非常有效的。例如我们刚才说的“特朗普”的例子我们可以手工标注好前10名的结果然后存下来。只需要每几天更新一下这个标注我们甚至不需要使用任何模型就可以提供非常高质量的搜索结果。
当然使用这种方法显然无法对几百万的搜索关键词都这么一一处理。不过我们这里针对的主要是高频关键词所以即便是针对最高频的1千个关键词进行手工标注也会对整体的搜索效果有非常明显的提升。
相反,长尾的关键词往往需要花比较多的心思。对于长尾来说,我们还可以细分。比如对于有一定数据量的关键词,我们可以尝试**针对这些关键词单独训练一个模型**。之所以要单独训练一个模型,原因也很简单,如果针对所有的关键词只有一个模型的话,高频的关键词因为流量大,往往就会让模型偏重于去解释高频的信息,而忽略了这些中低频的关键词的作用。
因此,先把**高频词**单独处理了,然后就可以针对依然可以训练的**中频关键词**再选取一个单独的模型。而针对非常**低频的关键词**,我们往往需要借助其他的方法来挖掘这些关键词的信息,例如利用同类的其他关键词的数据,或者利用外界的知识库、知识图谱的信息等。
## 三大模型套路
除了分开处理高频和长尾关键词以外,搜索模型的提升还有一个非常简单的“三大模型套路”。
我们构建一个搜索引擎,从最原始的简单系统,慢慢到比较复杂的以至于到后期非常复杂的系统,从模型上来说要跨越三个台阶。在这里我们主要是针对第二轮的打分系统来进行讨论。
**第一个台阶是使用线性模型**。当我们设置好了最基本的第一轮打分系统以后,首先要做好的是能够利用线性模型对文档进行排序。这一步其实往往是**搜索系统从“无人工智能”到“有人工智能”的第一步**。这一步对搜索效果性能的提升可能会有10%~20%。
**第二个台阶是使用配对法线性模型**。一般来说这一步搜索效果会有2%~5%的提升。
**第三个台阶是使用树模型特别是GBDT模型**。这一步搜效果的提升和第二步相似约有2%~5%的提升。然而,要从第二个台阶到达这个步骤,模型的特性可能会发生不小的变化。这一个台阶可以算是一个比较困难的台阶。
**从工程研发的角度来说,可以采用一年一个台阶的做法**。在已经穷尽了当前台阶所有可能用到的特性以后,再进入到下一个台阶,也就是说要尽可能地“榨干”当前台阶模型效果的“养分”。
## 总结
今天我为你介绍了做搜索产品的几个套路。
一起来回顾下要点:第一,我们回顾和总结了多轮打分系统的架构套路;第二,我们介绍了区分高频关键词和长尾关键词的套路;第三,我们简单讨论了“三大模型套路”,跨越三个台阶,逐步提升搜索效果。
最后,给你留一个思考题,为什么不鼓励直接采用深度学习模型呢?
欢迎你给我留言,和我一起讨论。

View File

@ -0,0 +1,66 @@
# 039 | “查询关键字理解”三部曲之扩展
我们在本周的前两篇文章中分别介绍了查询关键字分类Query Classification和查询关键字解析Query Parsing的基本概念和思想。今天我来讲一个稍微有一些不同的查询关键字理解模块**查询关键字扩展**Query Expansion
查询关键字扩展想要解决的问题和分类以及解析略微不同。其主要目的不仅仅是希望能够对用户输入的关键字进行理解,还希望能够补充用户输入的信息,从而达到丰富查询结果的效果。
## 查询关键字扩展的概念
为什么要提供查询关键字扩展主要原因还是用户输入的查询关键字信息不足。还记得我们上次提到的“苹果价格”这个例子吗在这个例子中用户到底是希望查询“苹果”作为一种水果的价格还是“苹果”作为手机的价格其实无法真正从这个查询关键字中得出。因此作为搜索引擎如果为用户提供一些“扩展选项”也就是一个被改写Reformulated过的查询关键字会提供更加好的用户体验和更加精准的搜索结果。
**查询关键字扩展除了显示出来能够让用户有更好的体验以外还有一个作用是增加文档的“召回”Recall从而为提高搜索结果奠定基础**。设想这样一个例子用户搜索“iphone 6 backup”希望了解如何备份iPhone 6的信息。因为苹果手机的绝大多数机型的备份流程都大同小异因此如果把“iphone 6”给扩展到“iphone”其他机型然后看是否有比较好的介绍备份的网页可以显示。
值得注意的是在扩展的过程中也有可能失去“精度”Precision。比如假设苹果对iPhone 7的备份流程做了很大的改进那么其他机型的流程也许就不适用了所以当用户搜索“iphone 7 backup”的时候如果我们扩展到了其他机型那让用户看到的很可能就是不那么相关的信息了。因此**对“精度”和“召回”的平衡,成了查询关键字扩展的一个重要的权衡点**。
**查询关键字扩展的另外一个重要应用就是对同义词和缩写的处理**。比如唐纳德·特朗普Donald Trump是美国现任总统。那么如果用户在搜索“Donald Trump”、“Trump”、“US President”、“POTUS”这是“President Of The United States”的简称等类似词汇的时候搜索引擎应该提供相似的结果。而从词汇的直接联系上这些词汇在表面形式上可能有很大的差异比如“Trump”和“POTUS”因此需要其他手段学习到这些词语内涵的同义。
## 查询关键字扩展的技术
知道了查询关键字扩展的含义以后,我们就来看看有哪些技术可以为查询关键字扩展提供支持。
根据上面提供的一些例子,你可以看到,这里的**核心就是找到搜索结果意义上的“同义词”**。那么,在搜索中,如何挖掘“同义词”呢?
今天我在这里分享两种思路。
**第一种思路,是根据查询关键字和查询结果之间的自然结合产生的同义效果**。这需要对用户的搜索行为数据进行大规模挖掘。这里的基本假设是这样的假设我们有两个搜索关键字A和B。从A的搜索结果中用户可能点击了一些网页从B的结果中用户可能点击了另外的一些网页。如果这些被点击的网页恰好非常类似那么我们就可以认为A和B其实是同义的查询关键字。
更加完整的做法是把查询关键字和网页分别表示成“图”Graph中的两类节点Node。每个关键字节点和多个网页节点建立联系Edge或者边Link象征这些网页对应这个关键字的相关页面。而从每个网页的角度上看多个关键字节点又和同一个网页节点相连表示这些关键字都有可能和某个网页相关。
拿上面提到的特朗普的例子来说美国白宫的首页作为一个节点的话就有可能会和“Trump”、“US President”以及“POTUS”这几个查询关键字相关。因此你可以看到寻找同义词的工作就变成了如何在这个图上进行相似节点特别是相似关键字节点的挖掘工作。
如果把查询关键字的节点放在一边,把网页节点放在一边,我们就看到了典型的“**二分图**”Bipartite Graph。二分图的特点是同边的节点之间没有连接比如关键字和关键字之间没有连接而所有的连接都发生在不同边的节点之间关键字和网页之间
二分图的聚类问题Clustering是机器学习界的经典的问题。而利用二分图的聚类问题来做查询关键字的同义词挖掘也是很多研究人员尝试的方向。文末我列了几个参考文献比如参考文献\[2\]就是利用二分图上的“随机游走”Random Walk以及随机游走所产生的“到达时间”Hitting Time来挖掘出类似的关键字。如果你有兴趣可以查看这篇经典论文。
说了基于用户行为信息和关键字挖掘的思路以后,我们再来看看第二种思路。
**第二种思路的核心是从海量的文本信息中分析出词语之间的相关度**。这里面需要注意的是这些词语的相关度有可能是语言本身带来的。比如单词“Male”和“Man”。也可能是语境带来的比如谈论手机的网页中对于“iPhone 6”和“iPhone 7”的谈论。
总之这一个思路的想法就是如何为每一个词组都建一个“表达”Representation从而通过这个表达找到同义词。近年来流行的一个做法是为单词找到数值表达也就是通常所说的“嵌入”Embedding。如果两个词在“嵌入空间”Embedding Space通常是“欧式空间”中距离相近那么我们就可以认为这两个词是同义词。
如何为词组产生“嵌入”向量呢这里面也有很多做法。比较通用的有算法Word2Vec参考文献\[3\]目标是通过一个文档的每一句话中某一个词周围的词来预测这个词出现的概率。可以设想一下在苹果手机的很多帮助文档或者帮助站点中描述如何帮助iPhone 6或者iPhone 7来做数据备份的字句都是相似的甚至可能唯一的区别就是描述机型的名字。
因此在这样的情况下通过文字周围的“上下文信息”Contextual来对单词本身的“嵌入向量”进行学习可以有效地学习到单词的语义。而通过语义我们就能够找到其他的同义词。当然要想真正应用到查询关键字扩展中可能还需要有其他的调试比如文末我列的参考文献\[4\],就是其中的一种。如果你感兴趣,建议去精读。
最后我需要说明的是,第一种思路需要已经有不少的用户交互数据,而第二种思路可以通过其他的语料(比如维基百科)加以学习,并不需要用户数据。这也是另一个值得参考的信息点。
## 小结
今天我为你讲了查询关键字理解中的查询关键字扩展问题。你可以看到,查询关键字扩展从技术上的两种流派,一个是通过用户的交互数据来产生一个图,并且利用图挖掘技术来得到查询关键字之间的关系;另外一个就是通过产生词汇的嵌入向量从而得到同义词。
一起来回顾下要点:第一,简要介绍了查询关键字扩展的内涵。由于用户输入的查询关键字信息不足,通过查询关键字扩展可以提供更好的用户体验和更加精准的搜索结果。第二,详细介绍了查询关键字扩展的两个主要技术。
最后,给你留一个思考题,如何来测试查询关键字扩展的优劣呢?
欢迎你给我留言,和我一起讨论。
**参考文献**
1. Claudio Carpineto and Giovanni Romano. A Survey of Automatic Query Expansion in Information Retrieval. _ACM Computing Surveys_. 44, 1, Article 1 (January 2012), 50 pages.2012.
2. Qiaozhu Mei, Dengyong Zhou, and Kenneth Church. Query suggestion using hitting time. _Proceedings of the 17th ACM conference on Information and knowledge management (CIKM '08)_. ACM, New York, NY, USA, 469-478. 2008.
3. Mikolov, Tomas, Kai Chen, Greg Corrado, and Jeffrey Dean. Efficient estimation of word representations in vector space. arXiv preprint arXiv:1301.3781 (2013).
4. Diaz, Fernando, Bhaskar Mitra, and Nick Craswell. Query expansion with locally-trained word embeddings. arXiv preprint arXiv:1605.07891 (2016).

Some files were not shown because too many files have changed in this diff Show More