first commit
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
15 一起练习:手把手带你分解任务
|
||||
你好,我是郑晔。
|
||||
|
||||
前面在讨论 TDD 的时候,我们说任务分解是 TDD 的关键。但这依然是一种感性上的认识。今天,我们就来用一个更加具体的例子,让你看看任务分解到底可以做到什么程度。
|
||||
|
||||
这个例子就是最简单的用户登录。需求很简单,用户通过用户名密码登录。
|
||||
|
||||
我相信,实现这个功能对大家来说并不困难,估计在我给出这个题目的时候,很多人脑子里已经开始写代码了。今天主要就是为了带着大家体验一下任务分解的过程,看看怎样将一个待实现的需求一步步拆细,变成一个个具体可执行的任务。
|
||||
|
||||
要完成这个需求,最基本的任务是用户通过输入用户名和密码登录。
|
||||
|
||||
|
||||
|
||||
用户名和密码登录这个任务很简单,但我们在第一部分讲过沙盘推演,只要推演一下便不难发现,这不是一个完整的需求。
|
||||
|
||||
用户名和密码是哪来的呢?它们可能是用户设置的,也可能是由系统管理员设置的。这里我们就把它们简单设定成由用户设定。另外,有用户登录,一般情况下,还会有一个退出的功能。好了,这才是一个简单而完整的需求。我们就不做进一步的需求扩展。
|
||||
|
||||
所以,我们要完成的需求列表是下面这样的。
|
||||
|
||||
-
|
||||
假设我们就是拿到这个需求列表的程序员,要进行开发。我们先要分析一下要做的事情有哪些,也就是任务分解。到这里,你可以先暂停一会,尝试自己分解任务,之后,再来对比我后面给出的分解结果,看看差异有多少。
|
||||
|
||||
好,我们继续。
|
||||
|
||||
我们先来决定一下技术方案,就用最简单的方式实现,在数据库里建一张表保存用户信息。一旦牵扯到数据库表,就会涉及到数据库迁移,所以,有了下面的任务。
|
||||
|
||||
-
|
||||
这时,需要确定这两个任务自己是否知道怎么做。设计表,一般熟悉 SQL 的人都知道怎么做。数据库迁移,可能要牵扯到技术选型,不同的数据库迁移工具,写法上略有差别,我们就把还不完全明确的内容加到任务清单里。
|
||||
|
||||
-
|
||||
数据库的内容准备好了,接下来,就轮到编写代码的准备上了。我们准备用常见的 REST 服务对外提供访问。这里就采用最常规的三层技术架构,所以,一般要编写下面几项内容。
|
||||
|
||||
|
||||
领域对象,这里就是用户。
|
||||
数据访问层,在不同的项目里面叫法不一,有人从 J2EE 年代继承下来叫 DAO(数据访问对象,Data Access Obejct),有人跟着 Mybatis 叫 mapper,我现在更倾向于使用领域驱动设计的术语,叫 repository。
|
||||
服务层,提供对外的应用服务,完成业务处理。
|
||||
资源层,提供 API 接口,包括外部请求的合法性检查。
|
||||
|
||||
|
||||
根据这个结构,就可以进一步拆解我们的开发任务了。
|
||||
|
||||
|
||||
|
||||
不知道你有没有注意到,我的任务清单上列任务的顺序,是按照一个需求完整实现的过程。
|
||||
|
||||
比如,第一部分就是一个完整的用户注册过程,先写 User,然后是 UserRepository 的 save 方法,接着是 UserService 的 register 方法,最后是 UserResource 的 register 方法。等这个需求开发完了,才是 login 和 logout。
|
||||
|
||||
很多人可能更习惯一个类一个类的写,我要说,最好按照一个需求、一个需求的过程走,这样,任务是可以随时停下来的。
|
||||
|
||||
比如,同样是只有一半的时间,我至少交付了一个完整的注册过程,而按照类写的方法,结果是一个需求都没完成。这只是两种不同的安排任务的顺序,我更支持按照需求的方式。
|
||||
|
||||
我们继续讨论任务分解。任务分解到这里,需要看一下这几个任务有哪个不好实现。register 只是一个在数据库中存储对象的过程,没问题,但 login 和 logout 呢?
|
||||
|
||||
考虑到我们在做的是一个 REST 服务,这个服务可能是分布到多台机器上,请求到任何一台都能提供同样的服务,我们需要把登录信息共享出去。
|
||||
|
||||
这里我们就采用最常见的解决方案:用 Redis 共享数据。登录成功的话,就需要把用户的 Session 信息放到 Redis 里面,退出的话,就是删除 Session 信息。在我们的任务列表里,并没有出现 Session,所以,需要引入 Session 的概念。任务调整如下。
|
||||
|
||||
|
||||
|
||||
如果采用 Redis,我们还需要决定一下在 Redis 里存储对象的方式,我们可以用原生的Java序列化,但一般在开发中,我们会选择一个文本化的方式,这样维护起来更容易。这里选择常见的 JSON,所以,任务就又增加了两项。
|
||||
|
||||
|
||||
|
||||
至此,最基本的登录退出功能已经实现了,但我们需要问一个问题,这就够了吗?之所以要登录,通常是要限定用户访问一些资源,所以,我们还需要一些访问控制的能力。
|
||||
|
||||
简单的做法就是加入一个 filter,在请求到达真正的资源代码之前先做一层过滤,在这个 filter 里面,如果待访问的地址是需要登录访问的,我们就看看用户是否已经登录,现在一般的做法是用一个 Token,这个 Token 一般会从 HTTP 头里取出来。但这个 Token 是什么时候放进去的呢?答案显然是登录的时候。所以,我们继续调整任务列表。
|
||||
|
||||
|
||||
|
||||
至此,我们已经比较完整地实现了一个用户登录功能。当然,要在真实项目中应用,需求还是可以继续扩展的。比如:用户 Session 过期、用户名密码格式校验、密码加密保存以及刷新用户 Token等等。
|
||||
|
||||
这里主要还是为了说明任务分解,相信如果需求继续扩展,根据上面的讨论,你是有能力进行后续分解的。
|
||||
|
||||
来看一下分解好的任务清单,你也可以拿出来自己的任务清单对比一下,看看差别有多大。
|
||||
|
||||
-
|
||||
首先要说明的是,任务分解没有一个绝对的标准答案,分解的结果根据个人技术能力的不同,差异也会很大。
|
||||
|
||||
检验每个任务项是否拆分到位,就是看你是否知道它应该怎么做了。不过,即便你技术能力已经很强了,我依然建议你把任务分解到很细,观其大略人人行,细致入微见本事。
|
||||
|
||||
也许你会问我,我在写代码的时候,也会这么一项一项地把所有任务都写下来吗?实话说,我不会。因为任务分解我在之前已经训练过无数次,已经习惯怎么一步一步地把事情做完。换句话说,任务清单虽然我没写下来,但已经在我脑子里了。
|
||||
|
||||
不过,我会把想到的,但容易忽略的细节写下来,因为任务清单的主要作用是备忘录。一般情况下,主流程我们不会遗漏,但各种细节常常会遗漏,所以,想到了还是要记下来。
|
||||
|
||||
另外,对比我们在分解过程中的顺序,你会看到这个完整任务清单的顺序是调整过的,你可以按照这个列表中的内容一项一项地做,调整最基本的标准是,按照这些任务的依赖关系以及前面提到的“完整地实现一个需求”的原则。
|
||||
|
||||
最后,我要特别强调一点,所有分解出来的任务,都是独立的。也就是说,每做完一个任务,代码都是可以提交的。只有这样,我们才可能做到真正意义上的小步提交。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:按照完整实现一个需求的顺序去安排分解出来的任务。
|
||||
|
||||
最后,我想请你分享一下,你的任务清单和我的任务清单有哪些差异呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,150 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
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 请求中提取出 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 服务,让测试变得可控。
|
||||
|
||||
有的测试会依赖数据库,那就在执行完测试之后,将数据库环境恢复,像 Spring 的测试框架就提供了测试数据库回滚的能力。如果你的测试反复运行,不能产生相同的结果,要么是代码有问题,要么是测试有问题。
|
||||
|
||||
理解可重复性,还有一个角度,一堆测试反复运行,结果应该是一样的。这说明测试和测试之间没有任何依赖,这也是我们接下来要说的测试的另外一个特点。
|
||||
|
||||
Independent,独立的。测试和测试之间不应该有任何依赖,什么叫有依赖?比如,如果测试依赖于外部数据库或是第三方服务,测试 A 在运行时在数据库里写了一些值,测试 B 要用到数据库里的这些值,测试 B 必须在测试 A 之后运行,这就叫有依赖。
|
||||
|
||||
我们不能假设测试是按照编写顺序运行的。比如,有时为了加快测试运行速度,我们会将测试并行起来,在这种情况下,顺序是完全无法保证的。如果测试之间有依赖,就有可能出现各种问题。
|
||||
|
||||
减少外部依赖可以用 mock,实在要依赖,每个测试自己负责前置准备和后续清理。如果多个测试都有同样的准备和清理呢?那不就是 setup 和 teardown 发挥作用的地方吗?测试基础设施早就为我们做好了准备。
|
||||
|
||||
Professional,专业的。这一点是很多人观念中缺失的,测试代码,也是代码,也要按照代码的标准去维护。这就意味着你的测试代码也要写得清晰,比如:良好的命名,把函数写小,要重构,甚至要抽象出测试的基础库,在 Web 测试中常见的 PageObject 模式,就是这种理念的延伸。
|
||||
|
||||
看了这点,你或许会想,你说的东西有点道理,但我的代码那么复杂,测试路径非常多,我怎么能够让自己的测试做到满足这些要求呢?
|
||||
|
||||
我必须强调一个之前讲测试驱动开发强调过的观点:编写可测试的代码。很多人写不好测试,或者觉得测试难写,关键就在于,你始终是站在写代码的视角,而不是写测试的视角。如果你都不重视测试,不给测试留好空间,测试怎么能做好呢?
|
||||
|
||||
总结时刻
|
||||
|
||||
测试是一个说起来很简单,但很不容易写好的东西。在实际工作中,很多人都会遇到关于测试的各种各样问题。之所以出现问题,主要是因为这些测试写得太复杂了。测试一旦复杂了,我们就很难保证测试的正确性,何谈用测试保证代码的正确性。
|
||||
|
||||
我给你讲了测试的基本结构:前置准备、执行、断言和清理,还介绍了一些常见的测试“坏味道”:做了太多事的测试,没有断言的测试,还有一种看一眼就知道有问题的“坏味道”,测试里有判断语句。
|
||||
|
||||
怎么衡量测试是否做好了呢?有一个标准:A-TRIP,这是五个单词的缩写,分别是Automatic(自动化)、Thorough(全面)、Repeatable(可重复的)、Independent(独立的)和 Professional(专业的)。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:要想写好测试,就要写简单的测试。
|
||||
|
||||
最后,我想请你分享一下,经过最近持续对测试的讲解,你对测试有了哪些与之前不同的理解呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,126 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
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》和《Agile Estimating and Planning》。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:想要管理好需求,先把需求拆小。
|
||||
|
||||
最后,我想请你分享一下,你的团队在需求管理上还遇到过哪些问题呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,128 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
18 需求管理:太多人给你安排任务,怎么办?
|
||||
你好,我是郑晔。
|
||||
|
||||
上一讲我们讲了需求的分解,我以用户故事为例,给你讲了我们应该把大的需求拆分成小的需求,但是不是只要把需求拆开了就万事大吉了呢?显然不是。今天我们再来探讨另一个与需求强相关的话题:需求管理。
|
||||
|
||||
需求管理?许多程序员的第一直觉通常是,这要么是产品经理的事,要么是项目经理的事,跟我有什么关系?我知道很多人会这么想,可我想说的是,如果你不了解需求是怎么管理的,即便是进行了需求分解,最终的结果很有可能依然是你深陷泥潭苦苦挣扎而不自知。
|
||||
|
||||
为什么这么说呢?我给你讲一个发生在我身边的故事。
|
||||
|
||||
最无脑的需求管理法:老板说的
|
||||
|
||||
有一次,我们组织了一次各团队负责人的吐槽大会,让大家把遇到的问题在台面上“摆”一下。一个开发团队的负责人说:“我这边倒排期太严重了,每个产品经理到我这里都说上线日期已经定好了,我这边资源有限,实在是抗不住了。”
|
||||
|
||||
出于好奇,有人问:“这些任务都一样重要吗?”
|
||||
|
||||
这个负责人无奈地摇摇头,“他们都说自己的任务重要。”
|
||||
|
||||
“他们凭什么说自己的任务重要呢?”我也问了一个问题。
|
||||
|
||||
这个负责人说:“他们告诉我,是老板说的。”
|
||||
|
||||
这是不是一个很熟悉的场景?一堆任务压过来,只是因为这是老板的一句话。我们的老板都是这么不近人情吗?其实,大概率来看,并不是。
|
||||
|
||||
就凭一句“老板说的”,我们就可以判断出,产品经理缺乏对需求管理应有的理解。而研发团队也因为无脑地接受了需求,几乎将自己压垮。
|
||||
|
||||
这时候,CTO 发话了:“口头的东西不算数,如果他们说是老板说的,那就让老板发邮件确认。”
|
||||
|
||||
我很认可CTO的说法,但我并不放心那个开发团队的负责人,于是我问他:“你会让产品经理这么去做吗?”果然,他犹豫了。
|
||||
|
||||
“产品经理可能不会和老板这么说。那你去说好了。”我们又给他提了个建议。显然,他更犹豫了,毕竟要面对大老板。
|
||||
|
||||
针对这种情况,我们又给出了一个解决办法,“如果你担心产品经理不这么做,你可以直接发邮件给老板,同时抄送 CTO。”
|
||||
|
||||
“对,可以这么做”,CTO 把责任扛了过去。这个负责人心里一下子有底了。
|
||||
|
||||
是不是有种似曾相识的感觉?其实,这个故事只要再往下延伸一点,就到了我们程序员身边。
|
||||
|
||||
作为程序员,我们面临的场景往往是,一个需求不明就里地来了,你的周末假期全部泡汤,因为你的负责人会和你说,这是老板说的。
|
||||
|
||||
软件行业有个段子:做软件,最理想的交付日期是什么时候?答案是昨天,其次是尽快。所有提出业务需求的人都恨不得需求早就做好了。但事实总是那么不如人意,所以,他们只能寄希望于需求被尽快实现。
|
||||
|
||||
如果我们等着所有需求都开发好了再上线呢?这就是当年所谓瀑布模型做的事,放在二十年前,这种做法还有生存空间,但今天这种做法显然已经不合时宜了。
|
||||
|
||||
关于如何做软件,我们已经讨论了很多,关键点就在于这个世界有太多的不确定,我们只好把产品的“一部分”开发好,送上线。
|
||||
|
||||
这就引出了一个问题,到底是选择“哪部分”优先上线呢?我们必须在宏大的理想和骨感的现实中作出取舍。这也就牵扯出需求管理的本质,实际上是个优先级的问题。
|
||||
|
||||
需求的优先级
|
||||
|
||||
“来自老板”,这是判断优先级最简单的答案,也是推卸责任的一个答案。其潜台词是,压力大不怪我,要怪就怪老板去。“来自老板”不应该成为优先做事的指标。
|
||||
|
||||
首先,我们要明确一点,优先级这种事大家也是可以谈的,大多数能当老板的人都是可以讲道理的。但要和老板谈,我们得知道怎么讲道理。准备一些基础知识,才能与各级老板探讨怎么安排工作的优先级。
|
||||
|
||||
为什么要区分优先级?因为时间是有限的,有限的时间内你能完成工作的上限是一定的。
|
||||
|
||||
怎么充分利用好有限的时间,这其实是一个时间管理的问题。所以,我们完全可以借鉴时间管理领域的一些优秀实践,帮助我们更有效地明辨优先级。
|
||||
|
||||
谈到时间管理,一个有效的时间管理策略是艾森豪威尔矩阵(Eisenhower Matrix),这是由美国前总统艾森豪威尔开发出的一个工具。
|
||||
|
||||
这个工具到了史蒂芬·柯维(Stephen Richards Covey)手里得到了发扬光大,他那本著名的《高效能人士的七个习惯》书籍将其推广至世界各地。也许这个名字你不太熟悉,看一下下面这个图你就知道了。
|
||||
|
||||
-
|
||||
它将事情按照重要和紧急两个维度进行划分,也就形成了四个部分:重要且紧急,重要不紧急,不重要且紧急,不重要不紧急。
|
||||
|
||||
用几个程序员生活中的例子帮你理解一下。让系统不能正常运行的线上故障,就属于重要且紧急事情,不赶紧解决,就影响公司的正常运营。团队对系统升级改造就属于重要不紧急:改造好,性能也好,可维护性也得到提升;不改造,一时半会也能用。一些临时任务都属于紧急不重要,而刷朋友圈则属于既不紧急也不重要。
|
||||
|
||||
按照时间管理的理念,重要且紧急的事情要立即做。重要但不紧急的事情应该是我们重点投入精力的地方。紧急但不重要的事情,可以委托别人做。不重要不紧急的事情,尽量少做。
|
||||
|
||||
这个矩阵带给我们思维上最大的改变是,让人意识到事情和事情不是等价的。如果不把精力放在重要的事情上,到最后可能都变成紧急的事情。
|
||||
|
||||
比如,我们放任系统不做升级改造,过多的技术债会让系统的问题越来越多,新需求实现的速度越来越慢,最后几个看起来不大的需求就足以让团队加班加点,天怒人怨。
|
||||
|
||||
把这个思路带回到我们现实的需求管理中,你会发现,其实团队面临的各种需求所采用的优先级排序方式,基本上都是按照紧急程度排列的,但它们是否真的重要呢?
|
||||
|
||||
如果你把这个问题抛给需求的提出者,我几乎可以肯定,他们给你的答案是,他们提出的需求就是重要的。一种可能是,他们也分不清重要和紧急的差别,正如有时候我们也糊涂一样。
|
||||
|
||||
对于这样的场景,我们要做的就是多问一些问题。我在“精益创业:产品经理不靠谱,你该怎么办?”文章中说过,默认所有需求都不做,直到弄清楚为什么要做这件事。
|
||||
|
||||
同样,需求也没那么重要,直到产品经理能说明白它为什么重要,尤其是为什么比其他需求重要。如果一个产品经理不能把几个需求排出优先级,你就可以把上面学到的内容给他讲一遍。
|
||||
|
||||
还有另一种可能,他给你的需求在他工作的上下文中,确实是最重要的内容了。但当有多个需求来源时,我们该如何确认哪个需求是最重要的呢?这时,才到了真正需要老板出场的时刻。
|
||||
|
||||
站在老板面前
|
||||
|
||||
在“解决了很多问题,为什么你依然在‘坑’里?”文章中,我曾经讲过,大家不要局限于程序员这个角色,不同角色真正的差异是工作上下文的不同。每个人都在自己的上下文里工作,上下文也就局限了很多人的视野。
|
||||
|
||||
试想,两个产品经理出现在你面前,一个告诉你,公司要拓展新方向,这个功能要做;另一个却说,公司要进一步盈利,那个功能必须做。
|
||||
|
||||
在你看来,他们两个说得都对,听上去都挺重要的。但骨感的现实是,你把两件事都接下来,等着你的是累死都完不成的任务。
|
||||
|
||||
这个时候,我们能做的是什么呢?跳出这个上下文,到更大的上下文中。你判断不了哪个需求更重要,就请更高一级的老板来判断。
|
||||
|
||||
有了基础知识的储备,我们终于可以站在了老板面前。你可以告诉老板:我资源有限,需要将这两个需求排个序,看哪个更重要。我的上下文有限,需要你帮我判断一下。
|
||||
|
||||
老板会和你说这两个需求的起源,扩展盈利的需求是竞争对手都已经有了,客户也问这边要,再不做会影响客户关系,尤其是新财年快到了,下个阶段的合同会受到影响。而另外的新业务是某天一个高端聚会上得到的新启发,想尝试一下,他也不确定这个想法能带来多少收益,就让产品部门试一下。
|
||||
|
||||
听了老板的信息,你顿时明白这两件事的重要性,你也知道该如何面对两个产品经理了。
|
||||
|
||||
老板比你们的上下文大,因为他有看待这个问题更多的维度。所以,在你们眼里无比纠结的事情,老板几句话就云开雾散了,在他眼里,那根本不叫事。
|
||||
|
||||
如果你看过刘慈欣的《三体》,就会知道,这其实是“降维攻击”。另一个你可能熟悉的说法叫大局观。我经常和人说,当员工想不明白的事,换成老板的视角就全明白了。
|
||||
|
||||
我鼓励每个程序员在更大的上下文中工作,也就是想让人获得更多的思考维度。而今天的内容主要告诉你,如果自己的上下文不足时,我们可以引入新的元素,比如征求老板意见,扩大自己的上下文。
|
||||
|
||||
再发散讲几句,为人做事同样要不断扩展自己的上下文,这也就是我们常说的涨见识。
|
||||
|
||||
很多所谓的人生难题不过是因为见识有限造成的。比如,如果你觉得公司内总有人跟你比技术,莫不如把眼光放得长远一些,把自己放在全行业的水平上去比较。因为你是为自己的职业生涯在工作,而不是一个公司。
|
||||
|
||||
总结时刻
|
||||
|
||||
需求分解之后,最重要的是,排列需求的优先级。优先级的排列方式有很多,我们可以借鉴时间管理的方法,把事情按照重要和紧急的维度进行划分,得到了四个象限。我们要尽可能把精力放在重要的事情上,而不是把紧急的事情当成优先级排序的方式。
|
||||
|
||||
需求分解成一个个小块,其实也分解了原本合一的上下文。如果想要有效地管理需求,尤其是确定事情的重要程度,一种方式是找回丢失的上下文。如果我们自己无法判断上下文,一种好的办法是,引入外部更大的上下文。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:尽量做最重要的事。
|
||||
|
||||
最后,我想请你分享一下,你的团队在日常的需求管理中,还遇到哪些问题呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,121 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
23 可视化:一种更为直观的沟通方式
|
||||
作为一个程序员,在这个技术快速发展的时代,我们唯有不断学习,才能保证自己不为时代所抛弃。那你是怎么跟上技术发展步伐的呢?
|
||||
|
||||
就个人经验而言,我会关注一些技术网站,最典型的就是 InfoQ。这样,我可以快速了解到技术发展的动向,比如,什么时候出了个新东西、哪个项目又有了重大的更新、某些技术有了哪些新的应用场景等等。
|
||||
|
||||
另外,我还有一种更系统地了解新知识的方式:ThoughtWorks 技术雷达。之所以我很喜欢这种方式,因为它是“可视化”的。
|
||||
|
||||
什么是技术雷达?
|
||||
|
||||
ThoughtWorks 技术雷达是由 ThoughtWorks 技术咨询委员会(Technology Advisory Board)编写的一份技术趋势报告,每6个月发布一次。ThoughtWorks 的项目多样性足够丰富,所以它能够发现诸多技术趋势。因此,相比于行业中其它的预测报告,技术雷达更加具体,更具可操作性。
|
||||
|
||||
ThoughtWorks 是我的老东家,所以,我在接触技术雷达的时间很早。我在2013年就已经开始与人讨论微服务,并在项目中尝试使用 Docker,而这一切信息的来源都是技术雷达。不过,我这里想和你讨论并不是技术雷达到底有多优秀,而是带你看看技术雷达这种组织知识的可视化形式。-
|
||||
|
||||
|
||||
(图片来源:ThoughtWorks 技术雷达)
|
||||
|
||||
技术雷达用来追踪技术,在雷达图的术语里,每一项技术表示为一个 blip,也就是雷达上的一个光点。
|
||||
|
||||
然后用两个分类元素组织这些 blip:象限(quadrant)和圆环(ring),其中,象限表示一个 blip 的种类,目前有四个种类:技术、平台、工具,还有语言与框架。
|
||||
|
||||
圆环表示一个 blip 在技术采纳生命周期中所处的阶段,目前这个生命周期包含四个阶段:采用(Adopt)、试验(Trial)、评估(Assess)和暂缓(Hold)。
|
||||
|
||||
每次技术雷达发布之后,我会特别关注一下“采用” 和 “暂缓”两项。
|
||||
|
||||
“采用”表示强烈推荐,我会去对比一下自己在实际应用中是否用到了,比如,在2018年11月的技术雷达中,事件风暴(Event Storming)放到了“采用”中,如果你还不了解 事件风暴 是什么,强烈建议你点击链接了解一下。
|
||||
|
||||
“暂缓” 则表示新项目别再用这项技术了,这会给我提个醒,这项技术可能已经有了更优秀的替代品,比如,Java世界中最常见的构建工具 Maven 很早就放到了“暂缓”项中,但时至今日,很多人启动新项目依然会选择 Maven,多半这些人并不了解技术趋势。
|
||||
|
||||
从这几年的发展趋势来看,技术雷达在“采用”和“暂缓”这两项上给出的推荐,大部分是靠谱的。
|
||||
|
||||
至于“试验”和“评估”两项,有时间的时候,我会慢慢看,因为它们多半属于新兴技术的试验区,主要的作用是用来让我开拓视野的。
|
||||
|
||||
雷达图是一种很好的将知识分类组织的形式,它可以让你一目了然地看到并了解所有知识点,并根据自己的需要,决定是否深入了解。
|
||||
|
||||
所以,我的前同事们借鉴了这个形式,做出了一个程序员的读书雷达,将程序员的应该阅读的书籍做了一个整理。-
|
||||
-
|
||||
(图片来源:ThoughtWorks读书雷达)
|
||||
|
||||
事实上,这种将内容通过可视化方式的组织起来的形式非常好用,ThoughtWorks 鼓励每个组织都建立自己的知识雷达,甚至提供了一个工具辅助你将雷达图构建出来。
|
||||
|
||||
在我看来,雷达图不仅仅适用于组织,也可以适用于团队。
|
||||
|
||||
我也曾经按照雷达图的方式将自己的团队用到的技术组织起来。把最需要了解的技术必须放在内环,比如:一个 Java 项目。我会要求程序员了解 Java,向外扩展的就是你在这个团队内工作会逐渐接触到的技术,比如,像 Docker 这种与部署相关的知识。至于最外面一层,就是被我们放弃掉的技术,比如,Maven。
|
||||
|
||||
这样一来,团队成员可以更清晰地了解到团队中所用的技术。当有新人加入团队时,这个雷达可以帮助新人迅速地抓住重点,他的学习路径就是从内环向外学习。所以,我也推荐你打造自己团队的技术雷达。
|
||||
|
||||
|
||||
构建技术雷达-
|
||||
构建雷达的程序库
|
||||
|
||||
|
||||
你是否想过,为什么雷达图的形式可以帮助你更好地理解知识呢?因为人的大脑更擅长处理图像。
|
||||
|
||||
可视化的优势
|
||||
|
||||
在远古时代,人脑处理的内容大多是图像,比如,哪里有新的果实,哪里猛兽出没,文字则是很久之后才产生的。现在普遍的一种说法是,大约在公元前3500年左右,许多文明才刚刚发展出书写系统,相比于人类的历史来说,这几乎是微不足道的。
|
||||
|
||||
就人脑的进化而言,处理图像的速度远远快于处理文字,所以,有“一图胜千言”的说法。
|
||||
|
||||
通过创建图像、图标或动画等进行信息交流的形式,就是可视化(Visualization)。可视化有很多种不同的分类,我们最常用的应该是数据可视化和信息可视化。
|
||||
|
||||
我在“你的工作可以用数字衡量吗”这篇文章里说过,我上班第一件事是“看”数字,这就是典型的数据可视化,而上面介绍的技术雷达,就属于信息可视化。
|
||||
|
||||
很多做软件的人习惯于用文字进行沟通,一般在软件开发过程中,需要编写各种文档,但并不是所有的场景,文字都是好的沟通方式,所以,也会有很多人尝试着将可视化应用在软件开发过程中。
|
||||
|
||||
估计大多数程序员最熟悉的表达方式应该是流程图,如果你做过软件设计,可能还听说过 UML(统一建模语言,Unified Modeling Language)。如果使用得当,这种方式会极大地提高表达的准确性,降低其他人理解的门槛。
|
||||
|
||||
在日常工作中,你最熟悉的可视化方式,大概就是在纸上或白板上画的图。以我的经验看,很多人画这个图太随意,如果你也是这样,我给你一个建议,先写字后画框,这样图会显得整洁一些。
|
||||
|
||||
什么是看板?
|
||||
|
||||
我们再来看一个实践,这就是将“可视化”应用在工作中的典型案例:看板。
|
||||
|
||||
看板,是一种项目管理工具,它将我们正在进行的工作变得可视化。这个实践来自精益生产,前面讲精益创业时,我给介绍了“精益”这个来自丰田公司的管理理念。精益的理念在软件行业已经非常流行了,很多软件开发实践都是从“精益”而来,看板就是其中之一。
|
||||
|
||||
|
||||
|
||||
看板属于那种几乎是看一眼就知道怎么用的实践。它将工作分成几个不同的阶段,然后,把分解出来的工作做成一张卡片,根据当前状态放置到不同的阶段中。如果你采用了我们专栏之前讲过的用户故事,那么每个用户故事就是一张卡片。
|
||||
|
||||
在实际工作中,每当一个工作完成之后,它就可以挪到下一个阶段,工作怎么算完成就是由我们前面提到的 DoD 来决定的。
|
||||
|
||||
当然,要用好看板,还可以使用一些小技巧。比如,用不同颜色的卡表示不同类型的工作,给每个人一个头像,增添一些乐趣。
|
||||
|
||||
看板可以帮助你一眼看出许多问题,比如,你的团队中有5个人,却有8个正在进行的任务,那一定是有问题的。因为一个人多线程工作,效果不会好。用“精益”的术语来说,我们应该限制 WIP(Work-In-Progress);再有,如果待开发的卡最多,那就证明现在的瓶颈在于开发,而不是其它阶段。
|
||||
|
||||
运用看板的方式,还有一个有趣的细节:使用实体墙还是电子墙。实体墙不难理解,就是找一面墙把看板做出来。现在有很多公司专门在做协同办公软件,其中的项目管理部分用到的就是看板理念,这就是电子墙的由来。
|
||||
|
||||
关于这点,顺便说一下我的建议,如果你的团队是在一起工作的,请考虑使用实体墙,除非你的办公空间实在太小。因为它可以方便地调整,也可以当作站会的集合地点,还可以让别人看见你们的工作或是问题,这样做的最大优势在于增强了人与人的互动。
|
||||
|
||||
电子墙的优势在于,随处可访问、数据不会丢失、便于统计等等,但每次访问它,都需要专门打开电脑,还是比较麻烦的。一种将二者结合的办法是,使用一个大电视,专门用来展示电子墙。
|
||||
|
||||
总之,看板就是要让工作在大家面前展现出来。
|
||||
|
||||
总结时刻
|
||||
|
||||
我给你介绍了一种结构化学习新知识的方式:技术雷达。
|
||||
|
||||
技术雷达就是一种将技术信息组织起来的方式。它通过将技术按照“象限”和“圆环”两个维度进行分类,让人可以直观地看到并理解不同的技术所处的发展阶段。
|
||||
|
||||
雷达图是一种很好的形式,不仅可以用在组织技术,还可以用来组织其它信息,比如,读书雷达。每个公司都可以利用雷达图的形式组织自己所有的技术,每个团队也可以利用雷达图的形式组织自己团队用到的技术,这样,方便团队成员结构化地理解用到技术,也方便新人的学习。
|
||||
|
||||
雷达图实际上是一种可视化的方法,人脑对于图像处理速度更快,因此,可视化是改善沟通的一种方式。大多数软件过程习惯采用文字的方式进行表达,对于“可视化”利用的还不够。当然,还是有一些利用“可视化”的方法,比如,流程图、UML 等。
|
||||
|
||||
最后,我给你介绍了一个利用可视化进行信息沟通的实践:看板。看板把工作分成了几个不同的阶段,在看板上对应不同的列,然后,每个任务作为一张卡贴在上面。每完成一张卡,就把这张卡挪到下一个阶段。
|
||||
|
||||
看板可以帮你发现许多问题,比如,当前进展是否合适,是否有人同时在做很多的事,发现当前工作的瓶颈等等。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:多尝试用可视化的方式进行沟通。
|
||||
|
||||
最后,我想请你思考一下,你在工作中,有哪些用到可视化方法解决沟通问题的场景?欢迎留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,157 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
24 快速反馈:为什么你们公司总是做不好持续集成?
|
||||
你好,我是郑晔。
|
||||
|
||||
在“以终为始”那个模块,我们留下了一个巨大的尾巴。在“持续集成:集成本身就是写代码的一个环节”这篇文章中,我们是站在“以终为始”的角度阐述了集成,尤其是持续集成的重要性。
|
||||
|
||||
但怎么做好持续集成,才是很多人真正关心的内容。今天,我们就来谈谈如何做好持续集成。
|
||||
|
||||
既然我们打算讨论持续集成,不妨停下来先思考一个问题:你对持续集成的第一印象是什么。
|
||||
|
||||
持续集成?Jenkins?没错,很多人对持续集成第一印象都是持续集成服务器,也就是 CI 服务器,当年是 CruiseControl,今天换成了 Jenkins。
|
||||
|
||||
也正是因为如此,很多人就把 CI 服务器理解成了持续集成。我就曾经接触过这样的团队,他们恨不得把所有的事情都放在 CI 服务器上做:在 CI 服务器上做了编译,跑了代码检查,运行了单元测试,做了测试覆盖率的统计等等。
|
||||
|
||||
或许你会疑问,这有什么不对的吗?
|
||||
|
||||
在做软件这件事上,我们不会用对与错去衡量,我只能说,这种做法是可行的,但它不是最佳实践。我希望你去思考,有没有比这更好的做法呢?
|
||||
|
||||
想要回答这个问题,我们还是要回到持续集成的本质上去。持续集成的诞生,就是人们尝试缩短集成周期的结果。为什么要缩短周期呢?因为我们希望尽早得到反馈,知道自己的工作结果是否有效。
|
||||
|
||||
所以,想要做好持续集成,就需要顺应持续集成的本质:尽快得到工作反馈。
|
||||
|
||||
由此,我们便得到持续集成的关键点,你只要记住一句话,快速反馈。
|
||||
|
||||
快速反馈,这句分成两个部分,快速和反馈,这也就引出了持续集成的两个重要目标:怎样快速地得到反馈,以及什么样的反馈是有效的。
|
||||
|
||||
快速得到反馈
|
||||
|
||||
我们回到前面的例子上,把各种检查放到 CI 服务器上执行,它可以让我们知道代码是不是有问题,这是一个有效的反馈,但它的反馈够快速吗?虽然比起没有持续集成的状态,它是好很多。但是,我们需要问一个问题,能不能更快地得到反馈呢?
|
||||
|
||||
显然,我们还可以做得更快。在自己的开发机上执行这些检查,就会比在 CI 服务器快。也就是说,执行同样的操作,本地环境会快于 CI 服务器环境。
|
||||
|
||||
为什么会这样呢?我们先来看看所有检查在 CI 服务器上执行,每个程序员的动作是什么样的。
|
||||
|
||||
我们写好代码,然后需要提交代码,等待 CI 服务器运行检查结果,然后,用 CI 监视器查看执行结果。如果没问题,继续做下一个任务,如果有错误,修复错误,再执行同样的过程。
|
||||
|
||||
|
||||
|
||||
再来看看本地执行的动作。运行构建脚本,如果一切正确,你可以选择提交代码或是继续下一个任务,如果失败,立即修复。
|
||||
|
||||
|
||||
|
||||
对比之下,在本地运行这些检查,你不需要提交,不需要等 CI 服务器开始执行,不需要跑到额外的地方查看检查结果。所以,这个操作比提交到服务器上会快很多。
|
||||
|
||||
另外,这里还有一个关键点,我们的操作是连续的。一旦检查结果出错了,我们立刻进入修复环节。作为程序员,我们太了解连续操作的重要性了。这就像打游戏时,我们感觉不到时间流逝一般,有人把这种状态称之为“心流”。
|
||||
|
||||
而提交代码,等待 CI 服务器的检查结果,就等于强迫你停下来,你的心流就被打断了。
|
||||
|
||||
如果你对心流的概念感兴趣,可以去读米哈里·契克森米哈赖的著作《心流》,这位作者就是心流概念的提出者。
|
||||
|
||||
前面我们只是在说,你作为程序员个体,使用持续集成的效果,这只是为了简化讨论。接下来,我们向更真实的世界靠拢,引入另一个重要的因素:团队协作。
|
||||
|
||||
假设你的团队就是在 CI 服务器上执行检查。你兴高采烈地写完一段代码准备提交,结果,此时你隔壁的同事手快一筹,先提交了,你不得不停下来等他。如果很不幸,你同事的检查失败的话,那么他又要把它修复好,你等的时间就更长了。
|
||||
|
||||
一个小问题也就罢了,如果是个大问题,他可能要修很长一段时间。这个时候,你除了等待,也没有更好的选择。如此一来,大把的时间就被浪费掉了。
|
||||
|
||||
这里我们要“插播”持续集成中重要的一个提交纪律:只有 CI 服务器处于绿色的状态才能提交代码。有检查在运行不能提交,有错误不能提交。原因很简单,如果这个时候多个人提交了代码,检查失败了,那问题到底算谁的呢?
|
||||
|
||||
反之,如果一次只有一个人提交代码,责任是明确的。如果团队不大,这个纪律相对还好执行,提交之前看一眼,或是喊一声就可以了。
|
||||
|
||||
如果团队稍微有一点规模,可以用一个小东西当作令牌,谁拿到了谁来提交。如果真的有人在 CI 服务器还在运行的时候,提交了代码怎么办?很简单,谁提交谁负责,错了就他修,谁让他违反纪律了。
|
||||
|
||||
好,你已经理解了我说的重点:不能把检查只放到 CI 服务器上执行。那该怎么做呢?答案已经呼之欲出了,那就是在本地开发环境上执行。
|
||||
|
||||
想做好持续集成的一个关键点是,用好本地构建脚本(build script),保证各种各样的检查都可以在本地环境执行。
|
||||
|
||||
一旦有了构建脚本,你在 CI 服务器上的动作也简单了,就是调用这个脚本。也就是说,本地检查和 CI 服务器上的动作是一致的。
|
||||
|
||||
至于什么样的内容适合放在构建脚本里,这个话题我们先放一放,把它留到后续“自动化”模块再做讨论。
|
||||
|
||||
在“任务分解”模块中,我与你讨论了“小”动作在工作中的重要性,“小”动作完成得越快,工作反馈得到也越快,所以说,也只有坚持不懈地做“小”动作,才能缩短反馈周期。
|
||||
|
||||
现在我们把这个道理与持续集成结合起来理解,我们的工作流程就变成了这样:
|
||||
|
||||
每完成一个任务,在本地运行构建脚本,如果有问题,就修复;没问题,则可以同步代码。如果 CI 服务器上没有正在运行的服务,就可以提交代码了。
|
||||
|
||||
|
||||
|
||||
提交代码中最麻烦的动作,其实是合并代码。不过,因为我们做的是小任务,改动的代码量并不大,所以,即便有需要合并的代码,量也不会很大,所需的脑力以及工作时间都会少很多。如此一来,我们的开发效率才可能能真正得到提高。
|
||||
|
||||
当团队真正地实施起持续集成,你会发现随着时间增加,本地检查的时间会越来越长。原因有很多,比如,代码越来越多,测试也越来越多。总之,检查的时间长了,就会对集成的速度造成影响。
|
||||
|
||||
这个时候,本着快速反馈的理念,我们就必须想办法。比如,有的团队做了分布式测试运行,有的团队将测试分类,就是我们在测试金字塔中讲到的分类,在本地执行单元测试和集成测试,而把更复杂的系统测试放到 CI 服务器上运行。
|
||||
|
||||
简单来说,我们的目的就是快速地得到反馈。
|
||||
|
||||
得到有效的反馈
|
||||
|
||||
说完了“快速”,我们再来看看做好持续集成的第二个重点:反馈,也就是怎么得到有效的反馈。
|
||||
|
||||
为什么需要反馈,道理很简单,我们得知道自己做得对不对。你可能会问,根据前面的说法,如果本地和 CI 服务器上执行的是一样的脚本,我在本地通过了,还用关心 CI 服务器的反馈吗?
|
||||
|
||||
当然要。因为还会出现很多其他问题,比如说最简单的一种情况是,你漏提交了一个文件。
|
||||
|
||||
好,既然我们要关注CI 服务器的反馈,下一个问题就是,它怎么反馈给我们呢?
|
||||
|
||||
我们还是从一种常见的错误入手。有些团队做持续集成用的反馈方式是什么呢?答案是邮件。
|
||||
|
||||
以邮件进行反馈,问题出在哪里呢?很明显,邮件不是一种即时反馈的工具。
|
||||
|
||||
我不知道有多少人会把邮件客户端当作日常的工具,就我个人习惯而言,一天查看几次邮件就算不错了,如果以邮件作为反馈方式,很有可能是出错了很长时间,我都无知无觉。
|
||||
|
||||
我们前面一直在强调快速,需要的是即时反馈,一旦邮件成了持续集成链条中的一环,无论如何都快不起来。
|
||||
|
||||
那你可以怎么做呢?在前面各种讨论中,我其实已经透露了答案:持续集成监视器,也是 CI 监视器。
|
||||
|
||||
-
|
||||
(图片来源:CI 监视器的示例 projectmonitor)
|
||||
|
||||
CI 监视器的原理很简单,CI 服务器在构建完之后,会把结果以API的方式暴露出来,早期有RSS和ATOM格式,后来有JSON的格式。得到的结果就可以用不同的方式进行展现了。市面上有很多CI 监视器的软件,有的是拿到结果之后,做一个视觉呈现,有的是做桌面通知。
|
||||
|
||||
现在,我们终于要讲到这个部分的重点了:怎么呈现是有效的?
|
||||
|
||||
答案很简单:怎么引人注目,怎么呈现。
|
||||
|
||||
比如,很多团队的做法是,用一个大屏幕将持续集成的结果展示出来,这样一来,持续集成的结果所有人都能看到,一旦出错了,即便你一时疏忽,也会有人来提醒你。
|
||||
|
||||
还有一些感官刺激的做法,比如,有人用上了红绿灯,测试失败则红灯闪烁;还有人甚至配上了语音,用喇叭高喊:“测试失败了,请赶紧修复。”我在一个视频里见过一个更夸张的做法:有人用玩具枪,出错了,就瞄准提交者开上一枪。
|
||||
|
||||
你是聪明的程序员,你应该能想到更多有趣的玩法。
|
||||
|
||||
为什么要这么做呢?这里的重点是,想做好持续集成,需要整个团队都关注持续集成。
|
||||
|
||||
这些引人注目的做法,就是要提高持续集成的关注度。否则,即便持续集成的技术环节做得再出色,人的注意力不在,持续集成也很难起到作用。
|
||||
|
||||
所以,你看到了,持续集成的反馈,尤其是出错之后的反馈方式,几乎是所有实践中最为高调的,它的目的就是要引人注目。
|
||||
|
||||
这里再插播一条持续集成的纪律:CI 服务器一旦检查出错,要立即修复。原因很简单,你不修,别人就不能提交,很多人的工作就会因此停顿下来,团队的工作流就会被打断,耽误的是整个团队的工作。
|
||||
|
||||
如果你一时半会修不好怎么办,撤销你的提交。更关键的原因是,团队对于持续集成的重视度,长时间不修复,持续集成就失去了意义,人们就会放弃它,持续集成在你的项目中,也就发挥不出任何作用了。
|
||||
|
||||
总结时刻
|
||||
|
||||
持续集成是软件开发中的重要实践,做好持续集成的关键在于,快速反馈。这里面有两个目标,怎样快速地得到反馈,以及什么样的反馈是有效的。
|
||||
|
||||
做好快速反馈,要把本地能做好的事情,在本地做好;也要通过小步提交的方式,加快代码开发的节奏。什么是有效的反馈?一是即时的反馈,二是引人注目的反馈。有很多种持续集成相关的工具可以帮助我们达成有效的反馈。
|
||||
|
||||
想要做好持续集成,还要有一些纪律要遵循:
|
||||
|
||||
|
||||
只有 CI 服务器处于绿色的状态才能提交代码;
|
||||
CI 服务器一旦检查出错,要立即修复。
|
||||
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:做好持续集成的关键在于,快速反馈。
|
||||
|
||||
最后,我想请你分享一下,你的团队做持续集成吗?遇到过哪些困难呢?欢迎留言与我们分享。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,125 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
25 开发中的问题一再出现,应该怎么办?
|
||||
你好,我是郑晔。
|
||||
|
||||
看过《圣斗士星矢》的同学大多会对其中的一个说法印象颇深:圣斗士不会被同样的招数击败两次。
|
||||
|
||||
我们多希望自己的研发水平也和圣斗士一样强大,可现实却总不遂人愿:同样的线上故障反复出现,类似的 Bug 在不同的地方一再地惹祸,能力强的同学每天就在“灭火”中消耗人生。我们难道就不能稍微有所改善吗?
|
||||
|
||||
如果在开发过程中,同样的问题反复出现,说明你的团队没有做好复盘。
|
||||
|
||||
什么是复盘?
|
||||
|
||||
复盘,原本是一个围棋术语,就是对弈者下完一盘棋之后,重新把对弈过程摆一遍,看看哪些地方下得好,哪些下得不好,哪些地方可以有不同甚至是更好的下法等等。
|
||||
|
||||
这种把过程还原,进行研讨与分析的方式,就是复盘。
|
||||
|
||||
现如今,复盘的概念已经被人用到了很多方面,比如,股市的复盘、企业管理的复盘,它也成为了许多人最重要的工具,帮助个体和企业不断地提升。这其中最有名的当属联想的创始人柳传志老爷子,他甚至把“复盘”写到了联想的核心价值观里。
|
||||
|
||||
为什么复盘这么好用呢?在我看来有一个重要的原因,在于客体化。
|
||||
|
||||
俗话说,当局者迷,旁观者清。以我们的软件开发作为例子,在解决问题的时候,我们的注意力更多是在解决问题本身上,而很少会想这个问题是怎么引起的。
|
||||
|
||||
当你复盘时,你会站在另外一个视角,去思考引起这个问题的原因。这个时候,你不再是当事者,而变成了旁观者。你观察原来那件事的发生过程,就好像是别人在做的一样。你由一个主观的视角,变成了一个客观的视角。
|
||||
|
||||
用别人的视角看问题,这就是客体化。
|
||||
|
||||
在软件开发领域,复盘也是一个重要的做法,用来解决开头提到那些反复出现的问题,只不过,它会以不同的方式呈现出来。
|
||||
|
||||
回顾会议
|
||||
|
||||
回顾会议是一个常见的复盘实践,定期回顾是一个团队自我改善的前提。回顾会议怎么开呢?我给你分享我通常的做法。
|
||||
|
||||
作为组织者,我会先在白板上给出一个主题分类。我常用的是分成三类:“做得好的、做得欠佳的、问题或建议”。
|
||||
|
||||
还有不同的主题分类方式,比如海星图,分成了五大类:“继续保持、开始做、停止做、多做一些、少做一些”五类。
|
||||
|
||||
分类方式可以根据自己团队的喜好进行选择。我之所以选用了三类的分类方式,因为它简单直观,几乎不需要对各个分类进行更多的解释。
|
||||
|
||||
然后,我会给与会者五分钟时间,针对这个开发周期内团队的表现,按照分类在便签上写下一些事实。比如,你认为做得好的是按时交付了,做得不好的是 Bug 太多。
|
||||
|
||||
这里面有两个重点。一个是写事实,不要写感受。因为事实就是明摆在那里的东西,而感受无法衡量,你感觉好的东西,也许别人感觉很糟糕。
|
||||
|
||||
另外,每张便签只写一条,因为后面我要对便签归类。因为大家是分头写的,有可能很多内容是重复的,所以,要进行归类。
|
||||
|
||||
五分钟之后,我会号召大家把自己写的便签贴到白板上。等大家把便签都贴好了,我会一张一张地念过去。
|
||||
|
||||
这样做是为了让大家了解一下其他人都写了些什么,知道不同人的关注点是什么。一旦有哪一项不清楚,我会请这张便签的作者出来解释一下,保证大家对这个问题的理解是一致的。在念便签的同时,我就顺便完成了便签归类的工作。
|
||||
|
||||
等到所有的便签都归好类,这就会成为后续讨论的主题,与会者也对于大家的关注点和看到的问题有了整体的了解。
|
||||
|
||||
做得好的部分,是大家值得自我鼓励的部分,需要继续保持。而我们开回顾会议的主要目的是改善和提升,所以,我们的重点在于解决做得不好的部分和有问题出现的地方。
|
||||
|
||||
在开始更有针对性的讨论之前,我会先让大家投个票,从这些分类中选出自己认为最重要的几项。我通常是给每人三票,投给自己认为重要的主题。每个人需要在诸多内容中做出取舍,你如果认为哪一项极其重要,可以把所有的票都投给这个主题。
|
||||
|
||||
根据大家的投票结果,我就会对所有的主题排出一个顺序来,而这就是我们要讨论的顺序。我们不会无限制的开会,所以,通常来说,只有最重要的几个主题才会得到讨论。
|
||||
|
||||
无论是个人选择希望讨论的主题,还是团队选择最终讨论的主题,所有人都要有“优先级”的概念在心里。然后,我们就会根据主题的顺序,一个一个地进行讨论。
|
||||
|
||||
讨论一个具体的主题时,我们会先关注现状。我会先让写下反馈意见的人稍微详细地介绍他看到的现象。比如,测试人员会说,最近的 Bug 比较多,相比于上一个开发周期,Bug 增加了50%。
|
||||
|
||||
然后,我会让大家分析造成这个现象的原因。比如,有人会说,最近的任务量很重,没有时间写测试。
|
||||
|
||||
再下来,我们会尝试着找到一个解决方案,给出行动项。比如,任务重,我们可以让项目经理更有效地控制一下需求的输入,再把非必要的需求减少一下;测试被忽略了,我们考虑把测试覆盖率加入构建脚本,当测试覆盖率不足时,就不允许提交代码。
|
||||
|
||||
请注意,所有给出的行动项应该都是可检查的,而不是一些无法验证的内容。比如,如果行动项是让每个程序员都“更仔细一些”,这是做不到的。因为“仔细”这件事很主观,你说程序员不仔细,程序员说我仔细了,这就是扯皮的开始。
|
||||
|
||||
而我们上面给出的行动项就是可检查的,项目经理控制输入的需求,我们可以用工作量衡量,还记得我们在讨论用户故事中提到的工作量评估的方式吗?
|
||||
|
||||
控制工作量怎么衡量?就是看每个阶段开发的总点数是不是比上一个阶段少了。而测试覆盖率更直接,直接写到构建脚本中,跑不过,不允许提交代码。
|
||||
|
||||
好,列好了一个个的行动项,接下来就是找责任人了,责任人要对行动项负责。
|
||||
|
||||
比如,项目经理负责需求控制,技术负责人负责将覆盖率加入构建脚本。有了责任人,我们就可以保障这个任务不是一个无头公案。下一次做回顾的时候,我们就可以拿着一个个的检查项询问负责人任务的完成情况了。
|
||||
|
||||
5个为什么
|
||||
|
||||
无论你是否采取回顾会议的方式进行复盘,分析问题,找到根因都是重要的一环。
|
||||
|
||||
你的团队如果能一下洞见到根因固然好,如果不能,那么最好多问一些为什么。具体怎么问,有一个常见的做法是:5个为什么(5 Whys)。这种做法是丰田集团的创始人丰田佐吉提出的,后来随着丰田生产方式而广为人知。
|
||||
|
||||
为什么要多问几个为什么?因为初始的提问,你能得到的只是表面原因,只有多问几个为什么,你才有可能找到根本原因。
|
||||
|
||||
我给你举个例子。服务器经常返回504,那我们可以采用“5个为什么”的方式来问一下。
|
||||
|
||||
|
||||
为什么会出现504呢?因为服务器处理时间比较长,超时了。
|
||||
为什么会超时呢?因为服务器查询后面的 Redis 卡住了。
|
||||
为什么访问 Redis 会卡住呢?因为另外一个更新 Redis 的服务删除了大批量的数据,然后,重新插入,服务器阻塞了。
|
||||
为什么它要大批量的删除数据重新插入呢?因为更新算法设计得不合理。
|
||||
为什么一个设计得不合理的算法就能上线呢?因为这个设计没有按照流程进行评审。
|
||||
|
||||
|
||||
问到这里,你就发现问题的根本原因了:设计没有经过评审。找到了问题的原因,解决之道自然就浮出水面了:一个核心算法一定要经过相关人员的评审。
|
||||
|
||||
当然,这只是一个例子。有时候,这个答案还不足以解决问题,我们还可以继续追问下去,比如,为什么没有按流程评审等等。
|
||||
|
||||
所以,“5个为什么”中的“5”只是一个参考数字,不是目标。
|
||||
|
||||
“5个为什么”是一个简单易上手的工具,你可能听了名字就知道该怎么用它。有一点需要注意的是,问题是顺着一条主线追问,不能问5个无关的问题。
|
||||
|
||||
无论是“回顾会议”也好,“5个为什么”也罢,其中最需要注意的点在于,不要用这些方法责备某个人。我们的目标是想要解决问题,不断地改进,而不是针对某个人发起情感批判。
|
||||
|
||||
总结时刻
|
||||
|
||||
在软件研发中,许多问题是反复出现的,很多开发团队会因此陷入无限“救火”中,解决这种问题一个好的办法就是复盘。
|
||||
|
||||
复盘,就是过程还原,进行研讨与分析,找到自我改进方法的一个方式。这种方式使我们拥有了客体化的视角,能够更客观地看待曾经发生过的一切。这种方法在很多领域中都得到了广泛的应用,比如股市和企业管理。
|
||||
|
||||
在软件开发中,也有一些复盘的实践。我给你详细介绍了“回顾会议”这种形式。
|
||||
|
||||
无论哪种做法,分析问题,找到根因是一个重要的环节。“5个为什么”就是一个常用的找到根因的方式。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:定期复盘,找准问题根因,不断改善。
|
||||
|
||||
最后我想请你分享一下,你的团队是怎么解决这些反复出现的问题呢?欢迎在留言区写下你的做法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,135 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
28 结构化:写文档也是一种学习方式
|
||||
你好,我是郑晔。
|
||||
|
||||
你写文档吗?我知道,你可能并不喜欢写文档,因为在你眼中,写文档是繁琐的,是旧时代软件工程的产物。
|
||||
|
||||
最开始我对写文档的印象也不好。
|
||||
|
||||
我的职业生涯是从一个通过了 CMM 5级认证的大企业开始的。可能今天很多程序员已经对 CMM 感到陌生了,它是能力成熟度模型(Capability Maturity Model for Software)的缩写,用来评估一个组织的软件开发能力,曾在国内风靡一时,许多软件公司都以拥有 CMM 认证为努力方向。
|
||||
|
||||
在这个极其重视过程的企业里,文档是非常重要的一环。但我看到的真实场景却是,一个软件已经上线运行了,大家才开始为了应付过程纷纷补写文档。
|
||||
|
||||
每个部门都有专门的过程负责人,要求你严格按照格式写文档,保证字体字号的正确性。然后,用 A4纸将文档打印出,封印在一个仓库里,再也无人问津。
|
||||
|
||||
然而,文档却是非常重要的。后来,我到过很多公司,凡是我能够比较快上手的,通常都是有比较详尽的文档,而那些文档缺失的公司,想要把信息梳理清楚,往往会花很长时间。
|
||||
|
||||
另外,我学习很多软件开发的相关知识,通常也是依赖各种各样的文档。对我们程序员这个走在时代前列的群体来说,大量阅读文档就是我们日常工作的一部分。
|
||||
|
||||
你发现矛盾了吗?一方面,我们讨厌写文档,另一方面,文档却对我们的工作学习有着不可忽视的作用。
|
||||
|
||||
我们竟然如此依赖于一个我们讨厌的东西。问题出在哪呢?
|
||||
|
||||
你为什么不喜欢写文档?
|
||||
|
||||
很多人会说,自己不愿意写那些无聊的流程文档,文档无聊,这固然是一个原因。不过,如今很多公司已经在这方面做得相当轻量级了,基本上只要求写必要的文档。那为什么依然有很多人不愿意写文档呢?
|
||||
|
||||
其实,很多人回避写文档的真正原因是,他掌握的内容不能很好地结构化。
|
||||
|
||||
在两种场景下,我们扮演的角色是不同的。写文档时,角色是作者;而读文档时,角色是读者。
|
||||
|
||||
作为读者,我们读文档,实际上就是按照作者梳理的结构在走,因为呈现出来的内容,多数是已经结构化的,读起来自然会比较顺畅;而作为作者,没有人告诉你结构应该是什么样,我们必须创造出一个结构来,而这正是很多人不擅长的。
|
||||
|
||||
想要成为一个好程序员,有一个良好的知识结构是极其重要的。
|
||||
|
||||
很多人抱怨程序员行业难,原因就在于,新技术层出不穷。是的,当你的知识都是零散的,任何新技术的出现,都是新东西。而当你建立起自己的知识结构,任何新东西都只是在原有知识上的增量叠加。
|
||||
|
||||
举个例子,今天炒得沸沸扬扬的微服务,小粒度的理念脱胎于 Unix 哲学中的“只做一件事,把它做好”,而服务化的理念则是当年SOA(Service-Oriented Architecture)的产物。理解了这些背后的动机,微服务就只剩下工具层面的问题。
|
||||
|
||||
有了这样的知识结构,当我要构建应用时,只是需要把工具适配进去,到时我再来学习相应的知识,这是非常有针对性的,学习的效率也会得到大幅度提高。
|
||||
|
||||
将零散的知识结构化,有很多种方式,但输出是非常关键的一环。
|
||||
|
||||
知识输出
|
||||
|
||||
不知道你小时候是不是有过给同学讲题的经历,有时候,明明你已经将知识学得很好,但给同学讲解起来时,却总是讲不明白。因为你的同学总能从你想都没想过的角度问问题,这些角度和老师教的不一样。
|
||||
|
||||
输出的过程,本质上就是把知识连接起来的过程。自己以为自己懂的东西,当你真的需要把它按照一个完整的逻辑呈现出来时,那些缺失的细节就会冒出来,而补齐这些细节,一张知识地图就逐渐成型了。
|
||||
|
||||
这个模块的主题是“沟通反馈”,将知识对外输出就是一种获得反馈的方式。很多人自以为对知识的理解已经很深入了,但给别人一讲,却发现自己怎么也讲不清楚,这就说明他理解的程度,远未到达他以为的高度。
|
||||
|
||||
输出的方式有很多,对于程序员来说,最常接触到的两种应该是写作与演讲。
|
||||
|
||||
你读到很多书、很多技术文章,这都是别人通过写作的方式进行输出的结果。而很多技术大会上,常常会有各路高手在台上分享自己的所得,这就是演讲的输出方式。
|
||||
|
||||
软件行业的很多大师级程序员都是对外输出的高手。比如,开源概念的提出者 Eric Raymond,他的《大教堂与集市》推开了开源大门;前面多次提及的Kent Beck,他写了《极限编程解析》、《测试驱动开发》、《实现模式》几本书;
|
||||
|
||||
而 Martin Fowler,几乎是对外输出的典范,他重新整理了很多似是而非的概念,让人们的讨论有了更标准的词汇,比如,重构、依赖注入(Dependency Injection)等等。
|
||||
|
||||
再往前,就要提到《计算机程序设计艺术》的作者高德纳,他系统地整理了算法的概念,为了好好写作,他甚至创造了一个排版软件 TeX。
|
||||
|
||||
也许你会说,说得很有道理,但我真的不擅长啊!这是因为你没有掌握基本的方法。
|
||||
|
||||
金字塔原理
|
||||
|
||||
首先,需要明确一点,我们的第一目标不是成为作家或演讲家,而只是要求把事情说清楚,把自己的知识清晰地呈现出来。那我们最好先来了解一下金字塔原理。看看下面这张图,你就知道它是怎么回事了:
|
||||
|
||||
|
||||
|
||||
首先,我们要确定想要表达的是什么,也就是找到中心论点,然后,再确定支撑这个论点的分论点,再来就是找到支撑每个分论点的论据。
|
||||
|
||||
从中心论点、分论点至论据,这样一层层向下展开,从结构上看,就像金字塔一样,所以,这个方法称之为金字塔原理。
|
||||
|
||||
以我们的专栏为例,我们的中心论点就是“高效工作是有方法可循的”,那支撑起这个中心论点的分论点就是我们的四个原则,针对每个原则,我们给出了各种实践和思想,这是我们的论据。
|
||||
|
||||
前面我说过了,一个人不擅长输出,更多的是因为缺乏知识的结构化,现在通过这样一种方式,就可以帮助自己,将某个知识结构化起来,有了结构,剩下的就是怎么输出了。
|
||||
|
||||
具体怎么输出就可以根据自己的喜好进行选择:要么自上而下的进行表达,也就是先说中心论点,然后说分论点1,用论据证明分论点1,再说分论点2,用论据证明分论点2,以此类推。
|
||||
|
||||
或者是自下而上来表达,先用证据得出分论点1,然后再得出分论点2,最后再归纳总结出中心论点。
|
||||
|
||||
听上去很简单,但不要以为懂得了金字塔原理,天下就尽在掌握了,你还需要更多的练习。
|
||||
|
||||
无他,唯手熟尔
|
||||
|
||||
我自己也曾经很不擅长写作和公开演讲,但是,这些东西都禁不住你大量的练习。我的对外输出,是从我刚开始工作不久开始的。那时候,市面上流行写 blog,我抱着好奇的心态开始了自己的 blog 之旅。
|
||||
|
||||
刚开始写 blog 的时候,我会把写好的东西分享给周边的朋友,他们会给我提出一些反馈,有赞许、有调侃、也有针对一些细节的讨论,这会让我觉得自己写的东西是有人看的,我也就有了坚持的原动力。
|
||||
|
||||
我也很羡慕那些很会写的人,于是,也经常会模仿他人的手法不断地改进自己的写作技巧。慢慢地,我的读者就从身边的人逐渐扩展开来,我也就有了更多的反馈。
|
||||
|
||||
正是这些反馈,让我对很多东西有了全新的认识,也就有了更强的分享动力,一个正向循环逐渐建立起来。到后来,写东西就成了我的习惯,坚持至今。
|
||||
|
||||
经过 blog 写作的锻炼,我写的东西有了自己的章法和套路,也就有了越来越多机会去在不同的地方写东西:给杂志写稿子,在网站上写东西,包括今天这个专栏,都起源于最初的 blog 写作。
|
||||
|
||||
除此之外,随着时间的累积,我收获的不仅仅是一些读者的赞许,还得到了更多的机会,比如,我人生中的第一次公开演讲,机会就来自于我 blog 的一个读者的邀请。
|
||||
|
||||
后来的一些职业机会,也是通过我写 blog 认识的朋友。考虑到我当时人在 IT 边缘的东北,能有后来的职业发展,很大程度都是常年坚持对外输出的结果。
|
||||
|
||||
同样,演讲能力也需要大量的练习。1977年《Book of List》杂志曾经有一个关于“最恐惧事物”的调查,结果显示,公开演讲名列第一,超过了死亡。所以,你害怕公开演讲是很正常的。
|
||||
|
||||
我至今依然记得我第一次公开演讲时手抖的样子,今天想想还是挺傻的。我第一次在几百人的大会上做演讲,居然有一段时间,只顾着看大屏,背对着听众,也是很糗的一段经历。
|
||||
|
||||
我一直很羡慕那些在台上侃侃而谈的人,比如,乔布斯。直到我读了《乔布斯的魔力演讲》,我才知道,即便强如乔布斯,他的演讲也是经过大量练习的。
|
||||
|
||||
我自己公开演讲看上去正常一些,是我在经过一个咨询项目的大量练习之后。那时候,几乎每天要给客户讲东西,害得我只能不停地准备、不停地讲。所以,本质上,对演讲的惧怕只是因为练习不足。
|
||||
|
||||
好了,你现在已经了解获取这些技能的真谛了,无他,唯手熟尔!
|
||||
|
||||
总结时刻
|
||||
|
||||
程序员对文档有着一种矛盾的情感,一方面,需要依赖于文档获得知识,另一方面,很少有人愿意写文档。
|
||||
|
||||
文档在程序员心目中“形象不佳”,主要是传统的流程写了太多无用的文档。但对更多人来说,不愿意写文档,本质上是因为知识不能很好地结构化。
|
||||
|
||||
有结构的知识会让新知识的学习变得更加容易,今天很多人抱怨新知识层出不穷,就是因为知识过于零散,当知识有结构之后,学习新知识就只是在学习增量,效率自然就会大幅度提升。
|
||||
|
||||
输出是一种很好的方式,帮助你把知识连接起来,写作和做公开演讲都是很好的输出方式。
|
||||
|
||||
阻碍很多人进行知识输出的一个重要原因是缺乏输出的模型,金字塔原理就给出一个从中心论点到分论点,再到论据的模型,帮助我们将知识梳理出来。
|
||||
|
||||
而想要做好知识输出,还需要不断地进行练习,写作和做公开演讲都是可以通过练习提高的。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:多输出,让知识更有结构。
|
||||
|
||||
最后,我想请你分享一下,你的工作中,有哪些机会将自己的知识输出呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,119 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
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。在那个年代,我们的程序是按照官方推荐做法编写的 EJB(Enterprise JavaBean),今天很多年轻的程序员可能不了解了,它只有部署到应用服务器才能运行。
|
||||
|
||||
我的解决方案就是加上一些打印语句,然后部署到应用服务器上,看输出的结果,再加上另外一些语句,再部署,如此往复。那时我们完全是手工打包上传,每次至少要十几分钟。最终,定位到了问题,只修改了一行代码。但几个小时的时间就这样被无谓的消耗了。
|
||||
|
||||
那之后,我花了很长时间研究怎么做自动化的增量部署,最终让这个过程简化了下来。但这件事对我的影响很大,这是我第一次认识到一个部署过程可能对开发造成的影响,也让我对自动化在开发过程内的应用有了属于自己的认识。
|
||||
|
||||
相比于我刚开始工作那会。现在在工具层面做类似的事已经容易很多了,在后面的内容中,我会结合着具体的场景介绍一下现在的最佳实践。
|
||||
|
||||
你要懂得软件设计
|
||||
|
||||
最后,我们再来说说我们的本职工作,给别人打造自动化工具中需要的能力:软件设计。
|
||||
|
||||
软件设计,是很多人既熟悉又陌生的一个词,说熟悉,很多人都知道,做软件要设计,还能顺嘴说出几个设计模式的名字;说陌生,是因为在我的职业生涯中,遇到真正懂软件设计的程序员少之又少。大多数人都是混淆了设计和实现。
|
||||
|
||||
举个例子。有一次,我要在两个系统之间做一个连接器,让上游系统向下游系统发消息,或许你一听就知道了,这里需要的是一个消息队列。但实际上,我们需要的能力要比消息队列更丰富一些,比如,要将重复的消息去除。一个同事给我推荐了 Kafka 当作这个连接器的基础,我欣然地接受了。
|
||||
|
||||
不过,在后续设计的讨论中,我们就经常出现话语体系的分歧。我说,这个连接器要有怎样的能力,他会说 Kafka 能够如何如何。究其根因,我在讨论的是设计,而他说的是实现,所以,我们两个很难把问题讨论到一起。
|
||||
|
||||
为什么我会如此看重设计呢?在软件开发中,其它的东西都是易变的,唯有设计的可变性是你可以控制的。
|
||||
|
||||
同样以前面的讨论为例,尽管 Kafka 在当下比较火热,但是我不敢保证 Kafka 在未来不会被我换掉。因为就在几年前,消息队列还是传统中间件的强项,现在也渐渐被人淡忘了。
|
||||
|
||||
我不想让我的设计随着某一个技术选型而不断摇摆。如果工作许多年,知识体系只能靠各种新框架新工具支撑,我们做程序员就只剩下疲于奔命了。不懂软件设计,只专注各种工具,其结果一定是被新技术遗弃,这也是很多人经常抱怨 IT 行业变化快的重要原因。
|
||||
|
||||
回到 Larry Wall 的说法上,你要想写出一个别人挑不出毛病的程序,你先要懂得软件设计。幸运的是,软件设计这些年的变化真不大,掌握了软件设计再来看很多框架和工具,学习起来就会容易很多。在这个模块的后半部分,我会与你探讨软件设计的话题,降低自己给自己挖坑的概率。
|
||||
|
||||
总结时刻
|
||||
|
||||
Perl 语言的发明人 Larry Wall 曾经说过,优秀程序员应该有三大美德:懒惰、急躁和傲慢(Laziness, Impatience and hubris)。想要成为一个优秀的程序员,就要让机器为自己很好地工作,而这需要对自动化有着很好地理解。
|
||||
|
||||
我们学习自动化,先要知道哪些东西不要自动化,尽最大的努力不做浪费时间的事。一方面,我们要从需求上规避那些没必要做的事;另一方面,我们也从自身防止 NIH 综合症(Not Invented Here Syndrome),争取做一个懒惰的程序员。
|
||||
|
||||
对于要自动化的事,我们需要反思一下,在为别人打造自动化工具的同时,我们自己的工作过程有没有很好地自动化。而如果我们想拥有打造良好的自动化工具,我们需要对软件设计有着充分地理解。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:请谨慎地将工作自动化。
|
||||
|
||||
最后,我想请你分享一下,学习了本讲之后,你现在是怎样理解自动化的呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,259 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
30 一个好的项目自动化应该是什么样子的?
|
||||
你好,我是郑晔。
|
||||
|
||||
进入自动化这个模块,我准备从程序员的日常工作开始。介绍“迭代0”时,我提到构建脚本是项目准备的一个重要组成部分,但在那一讲中,我并没有具体说构建脚本长成什么样。
|
||||
|
||||
今天,我们以一个典型的 Java REST 服务为例,介绍一下最基本的构建脚本应该做到什么样子。这里我采用的 Java 技术中最为常见的 Spring Boot 作为基础框架,而构建工具,我选择了 Gradle。
|
||||
|
||||
估计很多 Java 程序员心中的第一个问题就是,为什么用 Gradle,而不是 Maven?Maven 明明是 Java 社区最经典的构建工具。答案是因为 Maven 不够灵活。
|
||||
|
||||
你可以回想一下,你有多少次用 Maven 实现过特定需求?估计大部分人的答案都是没有。随着持续集成、持续交付的兴起,构建脚本的订制能力会变得越来越重要,Maven 则表现得力有不逮。
|
||||
|
||||
其实,早在2012年,ThoughtWorks 技术雷达就将 Maven 放到了 暂缓(HOLD)里面,也就是说,能不用就不用。
|
||||
|
||||
为了配合这次的讲解,我写了一个 Demo,放在了 Github 上。它的功能非常简单:
|
||||
|
||||
|
||||
通过向 /users POST 一个请求,实现用户注册;
|
||||
访问 /users,查看已注册的用户。
|
||||
|
||||
|
||||
如果方便的话,你最好把这个项目 clone 下来,以便参考。这里我主要是讲解自动化要做成什么样子,如果你想了解具体是怎么实现的,可以参考 Demo 里的代码。
|
||||
|
||||
好,我们开始!
|
||||
|
||||
基础准备
|
||||
|
||||
先把这个项目从 Github 上 clone 下来。
|
||||
|
||||
git clone https://github.com/dreamhead/geektime-zero.git
|
||||
|
||||
|
||||
然后,进入到项目所在的目录中。
|
||||
|
||||
cd geektime-zero
|
||||
|
||||
|
||||
当你准备就绪,我们就开始进一步了解这个项目。
|
||||
|
||||
一般我们了解一个项目,都会用用一个 IDE 打开这个项目,这里我推荐使用 IntelliJ IDEA,这是目前行业中最好的Java IDE。自从它的社区版免费之后,它就成为了我向他人推荐的首选。
|
||||
|
||||
我知道,开发工具是除了程序设计语言之外,另外一个容易引起“宗教战争”的话题,如果你喜欢其他的 IDE,那就用你最喜欢的 IDE 打开好了,只不过,需要调整一下构建脚本中的配置。
|
||||
|
||||
怎么打开这个项目呢?我们先用 Gradle 命令生成一个 IDEA 工程。
|
||||
|
||||
./gradlew idea
|
||||
|
||||
|
||||
这个命令会生成一个.ipr 文件,这就是 IDEA 的工程文件,用 IDEA 打开即可。
|
||||
|
||||
这里有两点需要说明一下。
|
||||
|
||||
第一,这里用的 gradlew,它是 Gradle 命令的一个封装,它会自动下载一个构建这个项目所需的Gradle,重点是通过这个命令锁定了 Gradle 的版本,避免因为构建脚本的差异,造成“你成功我失败”的情况。
|
||||
|
||||
第二,IDE 的工程是由 Gradle 生成的。很多人会凭借直觉,用 IDE 直接打开。有一些团队的项目里有好多个构建文件,究竟用哪个打开,不去问人是根本不知道的,这对项目的新人是非常不友好的。
|
||||
|
||||
生成的做法与前面 Gradle 封装是类似的,它可以避免因为本地安装不同版本 IDE 造成各种问题。
|
||||
|
||||
另外,因为 IDE 的工程是生成的,如果项目里一旦增加了新的程序库依赖,你只需重新执行一次上面的命令就好了,现在的 IDE 都有很好的自动加载能力,当它检测到工程文件的变化,就会重新加载。
|
||||
|
||||
好,现在你可以用 IDE 打开,我们就可以进一步了解这个项目了。
|
||||
|
||||
初见项目
|
||||
|
||||
我们先来了解一点 Gradle 的配置文件,它也是我们做项目自动化的重点。
|
||||
|
||||
|
||||
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 简化代码,为了能让代码在你的 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。
|
||||
|
||||
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
|
||||
|
||||
{
|
||||
"username": "foo",
|
||||
"password": "bar"
|
||||
}
|
||||
|
||||
|
||||
然后,通过浏览器访问 http://localhost:8080/users-
|
||||
我们就可以看见我们刚刚注册的这个用户了。
|
||||
|
||||
总结时刻
|
||||
|
||||
总结一下今天的内容。今天我们通过一个具体的例子,展示了一个最基本的项目自动化过程,包括了:
|
||||
|
||||
|
||||
生成 IDE 工程;
|
||||
编译;
|
||||
打包;
|
||||
运行测试;
|
||||
代码风格检查;
|
||||
测试覆盖率;
|
||||
数据库迁移;
|
||||
运行应用。
|
||||
|
||||
|
||||
但这就是自动化的全部了吗?显然不是,我这里给出的只是一个最基本的示例。实际上,几乎每个重复的工作或是繁琐的工作,都应该自动化。我们不应该把时间和精力浪费在那些机器可以很好地替我们完成的工作上。
|
||||
|
||||
今天的基础设施已经让我们的自动化工作变得比以往容易了很多,比如,可执行 JAR 包就比从前部署到应用服务器上简化太多了。Gradle 也让订制构建脚本的难度降低了很多。
|
||||
|
||||
这里提到的项目自动化也是持续集成的基础,在持续集成服务上执行的命令,就应该是我们在构建脚本中写好的,比如:
|
||||
|
||||
./gradlew build
|
||||
|
||||
|
||||
2011年,我在 InfoQ 上发表了一篇《软件开发地基》,讨论的就是一个项目的构建脚本应该是什么样子。虽然其中用到的工具今天已经不再流行,但一些基础内容今天看来,依然是有效的。如果有兴趣,你也可以看一下。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:将你的工作过程自动化。
|
||||
|
||||
最后,我想请你分享一下,在日常开发工作中,你还把哪些过程自动化了呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,109 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
31 程序员怎么学习运维知识?
|
||||
你好,我是郑晔。
|
||||
|
||||
在上一讲中,我们讲到了开发过程的自动化,我们的关注点在于如何构建出一个有效的部署包,这个包最终是要上线部署的,那接下来,我们就来关心一下部署的相关工作。
|
||||
|
||||
零散的运维知识
|
||||
|
||||
在一些稍具规模的公司,为部署工作设置了一个专有职位,称之为运维。当然,这个岗位的职责远不止部署这一件事,还要维护线上系统的稳定。不过,如果你的团队规模不大,或是项目处于初始阶段,这些工作往往也要由程序员自行完成。
|
||||
|
||||
对于一个程序员来说,了解自己的程序怎么部署上线,是非常重要的。我们既要了解一个软件的逻辑,也要知道它的物理部署。只有这样,出了问题才知道怎么修复。
|
||||
|
||||
更重要的是,我们在设计时,才能尽量规避部署带来的问题。而部署,恰恰也是最适合发挥自动化本领的地方。
|
||||
|
||||
好,即便下定决心准备学习运维相关知识,你准备怎么学呢?我先来问你个问题,提到运维,你会想到什么?
|
||||
|
||||
如果你是一个刚刚步入这个行业的程序员,你或许会想到 Docker,想到 Kubernetes;如果再早一点入行,你或许还会想到 Chef、Puppet、Ansible;更早一些入行的话,你会想到 Shell 脚本。没错,这些东西都是与运维相关的。那我就这么一个一个地都学一遍吗?
|
||||
|
||||
就我个人的学习经验而言,如果所有的知识都是零散的,没有一个体系将它们贯穿起来,你原有的知识无法帮助你学习新知识,这种学习方式效率极低,过程也极其痛苦。
|
||||
|
||||
如果是有结构的知识,所谓的学习新知识不过是在学习增量,真正要理解的新东西并不多,学习效率自然会大幅度提高。所以,想学好运维知识,首先你要建立起一个有效的知识体系。
|
||||
|
||||
你可能会问,这些运维知识看上去就是一个一个独立的工具啊?我曾经也为此困惑了许久,虽然我对各个工具已经有了不少的了解,但依然缺乏一个有效的知识体系,将它们贯穿起来,直到我上了一堂课。
|
||||
|
||||
感谢 Odd-e 的柴锋,有一次,他给我上了一堂 DevOps 课,他对运维知识的讲解让我茅塞顿开,从此,我的运维知识有了体系。
|
||||
|
||||
准确地说,他的这堂课就是讲给程序员的运维课。今天,我就把这个体系按照我的理解,重新整理一遍分享给你,也算是完成一次知识输出。
|
||||
|
||||
好,我们开始!
|
||||
|
||||
Java 知识体系
|
||||
|
||||
正如我前面所说,学习一个新东西,最好的办法是学习增量,如果能够找到它与已有知识体系的联系,我们就可以把已有知识的理解方式借鉴过去。
|
||||
|
||||
作为程序员,我们其实已经有了一个完善的知识体系,这就是我们对于程序设计的理解,而理解运维的知识体系,刚好可以借鉴这个体系。怎么理解这句话呢?
|
||||
|
||||
以最常见的 Java 开发为例,如果要成为一个合格的 Java 程序员,我应该知道些什么呢?
|
||||
|
||||
首先肯定是 Java 语言,我需要了解 Java 语言的各种语法特性。不过,只了解语法是写不出什么像样程序的,我们还需要掌握核心库。
|
||||
|
||||
对于 Java 来说,就是 JDK 中的各种类,比如,最常见的 String、List、Map 等等。
|
||||
|
||||
理论上来说,掌握了基本的语法和核心库,你就可以开发任何程序了。但在实践中,为了避免重新发明“轮子”,减少不必要的工作量,我们还会用到大量的第三方类库,比如,Google Guava、SLF4J 等等。
|
||||
|
||||
除了功能实现,还有一些结构性的代码也会反复出现。比如说,在常见的 REST 服务中,我们要将数据库表和对象映射到一起,要将结果转换成 JSON,要将系统各个组件组装到一起。
|
||||
|
||||
为了减少结构上的代码重复,于是,开发框架出现了,在 Java 中最常见的开发框架就是 Spring。
|
||||
|
||||
至此,你就可以完成基本的代码编写,但这还不够。
|
||||
|
||||
在 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 的知识体系,包括了编程语言、核心库、第三方库、开发框架、单机部署和集群部署等诸多方面。我把今天提到的各种技术整理成一个表格列在下面,你可以参考它更好地理解运维知识。
|
||||
|
||||
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:有体系地学习运维知识。
|
||||
|
||||
最后,我想请你分享一下,你还能想到哪些运维知识可以放到这张知识地图上呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,137 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
34 你的代码是怎么变混乱的?
|
||||
你好,我是郑晔。
|
||||
|
||||
前面几讲,我给你讲了开发过程的各种自动化,从构建、验证到上线部署,这些内容都是站在软件外部看的。从这一讲开始,我准备带领大家进入到软件内部。今天的话题就从写代码开始说起。
|
||||
|
||||
逐步腐化的代码
|
||||
|
||||
代码是程序员改造世界最直接的武器,却也是程序员抱怨最多的东西。为什么程序员会对代码如此不满呢?
|
||||
|
||||
你会抱怨写一段代码吗?你肯定不会,毕竟这是你养家糊口的本领,最基本的职业素养我们还是有的。那抱怨的是什么呢?是维护一段代码。
|
||||
|
||||
为什么维护代码那么难?因为通常来说,你维护的这段代码是有一定年龄的,所以,你总会抱怨前人没有好好写这段代码。
|
||||
|
||||
好,现在你拿到了一个新的需求,要在这段代码上添加一个新功能,你会怎么做呢?很多人的做法是,在原有的代码上添加一段新的逻辑,然后提交完工。
|
||||
|
||||
发现问题了吗?你只是低着头完成了一项任务,而代码却变得更糟糕了。如果我问你,你为什么这么做?你的答案可能是:“这段代码都这样了,我不敢乱改。”或者是:“之前就是这么写的,我只是遵循别人的风格在写。”
|
||||
|
||||
行业里有一个段子,对程序员最好的惩罚是让他维护自己三个月前写的代码。你一不小心就成了自己最讨厌的人。
|
||||
|
||||
从前,我也认为很多程序员是不负责任,一开始就没有把代码写好,后来,我才知道很多代码其实只是每次加一点。你要知道,一个产品一旦有了生命力,它就会长期存在下去,代码也就随着时间逐渐腐烂了。
|
||||
|
||||
而几乎每个程序员的理由都是一样的,他们也很委屈,因为他们只改了一点点。
|
||||
|
||||
这样的问题有解吗?一个解决方案自然就是我们前面说过的重构,但重构的前提是,你得知道代码驶向何方。对于这个问题,更好的答案是,你需要了解一些软件设计的知识。
|
||||
|
||||
SOLID 原则
|
||||
|
||||
提到软件设计,大部分程序员都知道一个说法“高内聚、低耦合”,但这个说法如同“期待世界和平”一样,虽然没错,但并不能很好地指导我们的具体工作。
|
||||
|
||||
人们尝试着用各种方法拆解这个高远的目标,而比较能落地的一种做法就是 Robert Martin 提出的面向对象设计原则:SOLID,这其实是五个设计原则的缩写,分别是
|
||||
|
||||
|
||||
单一职责原则(Single responsibility principle,SRP)
|
||||
开放封闭原则(Open–closed principle,OCP)
|
||||
Liskov 替换原则(Liskov substitution principle,LSP)
|
||||
接口隔离原则(Interface segregation principle,ISP)
|
||||
依赖倒置原则(Dependency inversion principle,DIP)
|
||||
|
||||
|
||||
早在1995年,Robert Martin 就提出了这些设计原则的雏形,然后在他的《敏捷软件开发:原则、实践与模式》这本书中,比较完整地阐述了这五个原则。后来,他有把这些原则进一步整理,成了今天的 “SOLID”。
|
||||
|
||||
学习这些设计原则有什么用呢?
|
||||
|
||||
今天的程序员学习软件设计多半是从设计模式入门的,但不知道你是否有这样的感觉,在学习设计模式的时候,有几个设计模式看上去如此相像,如果不是精心比较,你很难记得住它们之间的细微差别。
|
||||
|
||||
而且,真正到了工作中,你还能想得起来的可能就剩下几个最简单的模式了,比如工厂方法、观察者等等。
|
||||
|
||||
另外,有人常常“为赋新词强说愁”,硬去使用设计模式,反而会让代码变得更加复杂了。你会有一种错觉,我是不是学了一个假的设计模式,人人都说好的东西,我怎么就感受不到呢?
|
||||
|
||||
初学设计模式时,我真的就被这个问题困扰了好久。直到我看到了 Robert Martin 的《敏捷软件开发:原则、实践与模式》。这是一本被名字糟蹋了的好书。
|
||||
|
||||
这本书出版之际,敏捷软件开发运动正风起云涌,Robert Martin 也不能免俗地蹭了热点,将“敏捷”挂到了书名里。其实,这是一本讲软件设计的书。
|
||||
|
||||
当我看到了 SOLID 的五个原则之后,我终于想明白了,原来我追求的方向错了。如果说设计模式是“术”,设计原则才是“道”。设计模式并不能帮你建立起知识体系,而设计原则可以。
|
||||
|
||||
当我不能理解“道”的时候,“术”只能死记硬背,效果必然是不佳的。想通这些之后,我大大方方地放弃了对于设计模式的追求,只是按照设计原则来写代码,结果是,我反而是时常能重构出符合某个设计模式的代码。至于具体模式的名字,如果不是有意识地去找,我已经记不住了。
|
||||
|
||||
当然,我并不是说设计模式不重要,之所以我能够用设计原则来写代码,前提条件是,我曾经在设计模式上下过很多功夫。
|
||||
|
||||
道和术,是每个程序员都要有的功夫,在“术”上下过功夫,才会知道“道”的价值,“道”可以帮你建立更完整的知识体系,不必在“术”的低层次上不断徘徊。
|
||||
|
||||
单一职责原则
|
||||
|
||||
好,下面我就单拿 SOLID 中单一职责原则稍微展开讲一下,虽然这个原则听上去是最简单的,但也有很多误解存在。
|
||||
|
||||
首先,什么是单一职责原则呢?如果读过《敏捷软件开发:原则、实践与模式》,你对单一职责的理解应该是,一个模块应该仅有一个修改的原因。
|
||||
|
||||
2017年,Robert Martin 出版了《架构整洁之道》(Clean Architecture),他把单一职责原则的定义修改成“一个模块应该仅对一类 actor 负责”,这里的 actor 可以理解为对系统有共同需求的人。
|
||||
|
||||
不管是哪个定义,初读起来,都不是那么好理解。我举个例子,你就知道了。我这里就用 Robert Martin 自己给出的例子:在一个工资管理系统中,有个 Employee 类,它里面有三个方法:
|
||||
|
||||
|
||||
calculatePay(),计算工资,这是财务部门关心的。
|
||||
reportHours(),统计工作时长,这是人力部门关心的。
|
||||
save(),保存数据,这是技术部门关心的。
|
||||
|
||||
|
||||
之所以三个方法在一个类里面,因为它们的某些行为是类似的,比如计算工资和统计工作时长都需要计算正常工作时间,为了避免重复,团队引入了新的方法:regularHours()。
|
||||
|
||||
|
||||
|
||||
接下来,财务部门要修改正常工作时间的统计方法,但人力部门不需要修改。负责修改的程序员只看到了 calculatePay() 调用了 regularHours(),他完成了他的工作,财务部门验收通过。但上线运行之后,人力部门产生了错误的报表。
|
||||
|
||||
这是一个真实的案例,最终因为这个错误,给公司造成了数百万的损失。
|
||||
|
||||
如果你问程序员,为什么要把 calculatePay() 和 reportHours()放在一个类里,程序员会告诉你,因为它们都用到了 Employee 这个类的数据。
|
||||
|
||||
但是,它们是在为不同的 actor 服务,所以,任何一个 actor 有了新的需求,这个类都需要改,它也就很容易就成为修改的重灾区。
|
||||
|
||||
更关键的是,很快它就会复杂到没人知道一共有哪些模块与它相关,改起来会影响到谁,程序员也就越发不愿意维护这段代码了。
|
||||
|
||||
我在专栏“开篇词”里提到过,人的大脑容量有限,太复杂的东西理解不了。所以,我们唯一能做的就是把复杂的事情变简单。
|
||||
|
||||
我在“任务分解”模块中不断强调把事情拆小,同样的道理在写代码中也适用。单一职责原则就是给了你一个指导原则,可以按照不同的 actor 分解代码。
|
||||
|
||||
上面这个问题,Robert Martin 给了一个解决方案,就是按照不同的 actor 将类分解,我把分解的结果的类图附在了下面:
|
||||
|
||||
|
||||
|
||||
编写短函数
|
||||
|
||||
好,你已经初步了解了单一职责原则,但还有一点值得注意。我先来问个问题,你觉得一个函数多长是合适的?
|
||||
|
||||
曾经有人自豪地向我炫耀,他对代码要求很高,超过50行的函数绝对要处理掉。
|
||||
|
||||
我在专栏中一直强调“小”的价值,能看到多小,就可以在多细的粒度上工作。单一职责这件事举个例子很容易,但在真实的工作场景中,你能看到一个模块在为多少 actor 服务,就完全取决于你的分解能力了。
|
||||
|
||||
回到前面的问题上,就我自己的习惯而言,通常的函数都在十行以内,如果是表达能力很强的语言,比如 Ruby,函数会更短。
|
||||
|
||||
所以,你可想而知我听到“把50行代码归为小函数”时的心情。我知道,“函数长短”又是一个非常容易引起争论的话题,不同的人对于这个问题的答案,取决于他看问题的粒度。
|
||||
|
||||
所以,不讨论前提条件,只谈论函数的长短,其实是没有意义的。
|
||||
|
||||
单一职责原则可以用在不同的层面,写一个类,你可以问问这些方法是不是为一类 actor 服务;写方法时,你可以问问这些代码是不是在一个层面上;甚至一个服务,也需要从业务上考虑一下,它在提供是否一类的服务。总之,你看到的粒度越细,也就越能发现问题。
|
||||
|
||||
总结时刻
|
||||
|
||||
今天,我讲的内容是软件设计,很多代码的问题就是因为对设计思考得不足导致的。
|
||||
|
||||
许多程序员学习设计是从设计模式起步的,但这种学法往往会因为缺乏结构,很难有效掌握。设计原则,是一个更好的体系,掌握设计原则之后,才能更好地理解设计模式这些招式。Robert Martin 总结出的“SOLID”是一套相对完整易学的设计原则。
|
||||
|
||||
我以“SOLID” 中的单一职责原则为例,给你稍做展开,更多的内容可以去看 Robert Martin 的书。不过,我也给你补充了一些维度,尤其是从“小”的角度告诉你,你能看到多小,就能发现代码里多少的问题。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:把函数写短。
|
||||
|
||||
最后我想请你思考一下,你是怎么理解软件设计的呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,133 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
35 总是在说MVC分层架构,但你真的理解分层吗?
|
||||
你好,我是郑晔。
|
||||
|
||||
作为程序员,你一定听说过分层,比如,最常见的 Java 服务端应用的三层结构,在《15 | 一起练习:手把手带你分解任务》中,我曾提到过:
|
||||
|
||||
|
||||
数据访问层,按照传统的说法,叫 DAO(Data Access Object,数据访问对象),按照领域驱动开发的术语,称之为 Repository;
|
||||
服务层,提供应用服务;
|
||||
资源层,提供对外访问的资源,采用传统做法就是 Controller。
|
||||
|
||||
|
||||
这几乎成为了写 Java 服务的标准模式。但不知道你有没有想过,为什么要分层呢?
|
||||
|
||||
设计上的分解
|
||||
|
||||
其实,分层并不是一个特别符合直觉的做法,符合直觉的做法应该是直接写在一起。
|
||||
|
||||
在编程框架还不是特别流行的时候,人们就是直接把页面和逻辑混在一起写的。如果你有机会看看写得不算理想的 PHP 程序,这种现象还是大概率会出现的。
|
||||
|
||||
即便像 Java 这个如此重视架构的社区,分层也是很久之后才出现的,早期的 JSP 和 PHP 并没有什么本质区别。
|
||||
|
||||
那为什么要分层呢?原因很简单,当代码复杂到一定程度,人们维护代码的难度就急剧上升。一旦出现任何问题,在所有一切都混在一起的代码中定位问题,本质上就是一个“大海捞针”的活。
|
||||
|
||||
前面讲任务分解的时候,我不断在强调的观点就是,人们擅长解决的是小问题,大问题怎么办?拆小了就好。
|
||||
|
||||
分层架构,实际上,就是一种在设计上的分解。
|
||||
|
||||
回到前面所说的三层架构,这是行业中最早普及的一种架构模式,最开始是 MVC,也就是 Model、View 和 Controller。
|
||||
|
||||
MVC 的概念起源于 GUI (Graphical User Interface,图形用户界面)编程,人们希望将图形界面上展示的部分(View)与 UI 的数据模型(Model)分开,它们之间的联动由 Controller 负责。这个概念在 GUI 编程中是没有问题的,但也仅限于在与 UI 有交互的部分。
|
||||
|
||||
很多人误以为这也适合服务端程序,他们就把模型部分误解成了数据库里的模型,甚至把它理解成数据库访问。于是,你会看到有人在 Controller 里访问数据库。
|
||||
|
||||
不知道你是不是了解 Ruby on Rails,这是当年改变了行业认知的一个 Web 开发框架,带来很多颠覆性的做法。它采用的就是这样一种编程模型。当年写 Rails 程序的时候我发现,当业务复杂到了一定规模,代码就开始难以维护了。我想了好久,终于发现,在 Rails 的常规做法中少了服务层(Service)的设计。
|
||||
|
||||
这个问题在 Java 领域,爆发得要比 Rails 里早,因为 Ruby 语言的优越性,Rails 实现的数据访问非常优雅。正是因为 Rails 的数据访问实在太容易了,很多服务实际上写到 Model 层里。在代码规模不大时,代码看上去是不复杂的,甚至还有些优雅。
|
||||
|
||||
而那时的 Java 可是要一行一行地写数据访问,所以,代码不太可能放在 Model 层,而放在Controller 里也会让代码变复杂,于是,为业务逻辑而生的 Service 层就呼之欲出了。
|
||||
|
||||
至此,常见的 Java 服务端开发的基础就全部成型了,只不过,由于后来 REST 服务的兴起,资源层替代了 Controller 层。
|
||||
|
||||
到这里,我给你讲了常见的 Java 服务三层架构的来龙去脉。但实际上,在软件开发中,分层几乎是无处不在的,因为好的分层往往需要有好的抽象。
|
||||
|
||||
无处不在的分层
|
||||
|
||||
作为程序员,我们几乎每天都在与分层打交道。比如说,程序员都对网络编程模型很熟悉,无论是 ISO 的七层还是 TCP/IP 的五层。
|
||||
|
||||
但不知道你有没有发现,虽然学习的时候,你要学习网络有那么多层,但在使用的时候,大多数情况下,你只要了解最上面的那层,比如,HTTP。
|
||||
|
||||
很多人对底层的协议的理解几乎就停留在“学过”的水平上,因为在大多数情况下,除非你要写协议栈,不然你很难用得到。即便偶尔用到,90%的问题靠搜索引擎就解决了,你也很少有动力去系统学习。
|
||||
|
||||
之所以你可以这么放心大胆地“忽略”底层协议,一个关键点就在于,网络模型的分层架构实现得太好了,好到你作为上层的使用者几乎可以忽略底层。而这正是分层真正的价值:构建一个良好的抽象。
|
||||
|
||||
这种构建良好的抽象在软件开发中随处可见,比如,你作为一个程序员,每天写着在 CPU 上运行的代码,但你读过指令集吗?你之所以可以不去了解,是因为已经有编译器做好了分层,让你可以只用它们构建出的“抽象”——编程语言去思考问题。
|
||||
|
||||
比如,每天写着 Java 程序的程序员,你知道 Java 程序是如何管理内存的吗?这可是令很多 C/C++程序员寝食难安的问题,而你之所以不用关心这些,正是托了 Java 这种“抽象”的福。对了,你甚至可能没有注意到编程语言也是一种抽象。
|
||||
|
||||
有抽象有发展
|
||||
|
||||
只有构建起抽象,人们才能在此基础上做出更复杂的东西。如果今天的游戏依然是面向显示屏的像素编程,那么,精彩的游戏视觉效果就只能由极少数真正的高手来开发。我们今天的大部分游戏应该依然停留在《超级玛丽》的水准。
|
||||
|
||||
同样,近些年前端领域风起云涌,但你是否想过,为什么 Web 的概念早就出现了,但前端作为一个专门的职位,真正的蓬勃发展却是最近十年的事?
|
||||
|
||||
2009年,Ryan Dahl 发布了Node.js,人们才真正认识到,原来 JavaScript 不仅仅可以用于浏览器,还能做服务器开发。
|
||||
|
||||
于是,JavaScript 社区大发展,各种在其他社区已经很流行的工具终于在 JavaScript 世界中发展了起来。正是有了这些工具的支持,人们才能用 JavaScript 构建更复杂的工程,前端领域才能得到了极大的发展。
|
||||
|
||||
如今,JavaScript 已经发展成唯一一门全平台语言,当然,发展最好的依然是在它的大本营:前端领域。前端程序员才有了今天幸福的烦恼:各种前端框架层出不穷。
|
||||
|
||||
在这里,Node.js 的出现让 JavaScript 成为了一个更好的抽象。
|
||||
|
||||
构建你的抽象
|
||||
|
||||
理解了分层实际上是在构建抽象,你或许会关心,我该怎么把它运用在自己的工作中。
|
||||
|
||||
构建抽象,最核心的一步是构建出你的核心模型。什么是核心模型呢?就是表达你业务的那部分代码,换句话说,别的东西都可以变,但这部分不能变。
|
||||
|
||||
这么说可能还是有点抽象,我们回到前面的三层架构。
|
||||
|
||||
在前面介绍三层架构的演变时,提到了一个变迁:REST服务的兴起,让 Controller 逐渐退出了历史舞台,资源层取而代之。
|
||||
|
||||
换句话说,访问服务的方式可能会变。放到计算机编程的发展中,这种趋势就更明显了,从命令行到网络,从 CS(Client-Server) 到 BS(Browser-Server),从浏览器到移动端。所以,怎么访问不应该是你关注的核心。
|
||||
|
||||
同样, 关系型数据库也不是你关注的核心,它只是今天的主流而已。从前用文件,今天还有各种 NoSQL。
|
||||
|
||||
如此说来,三层架构中的两层重要性都不是那么高,那重要的是什么?答案便呼之欲出了,没错,就是剩下的部分,我们习惯上称之为服务层,但这个名字其实不能很好地反映它的作用,更恰当的说法应该可以叫领域模型(Domain Model)。
|
||||
|
||||
它便是我们的核心模型,也是我们在做软件设计时,真正应该着力的地方。
|
||||
|
||||
为什么叫“服务层”不是一个好的说法呢?这里会遗漏领域模型中一个重要的组成部分:领域对象。
|
||||
|
||||
很多人理解领域对象有一个严重的误区,认为领域对象属于数据层。数据存储只是领域对象的一种用途,它更重要的用途还是用在各种领域服务中。
|
||||
|
||||
由此还能引出另一个常见的设计错误,领域对象中只包含数据访问,也就是常说的 getter 和 setter,而没有任何逻辑。
|
||||
|
||||
如果只用于数据存储,只有数据访问就够了,但如果是领域对象,就应该有业务逻辑。比如,给一个用户修改密码,用户这个对象上应该有一个 changePassword 方法,而不是每次去 setPassword。
|
||||
|
||||
严格地说,领域对象和存储对象应该是两个类,只不过它俩实在太像了,很多人经常使用一个类,这还是个小问题。但很多人却把这种内部方案用到了外部,比如,第三方集成。
|
||||
|
||||
为数不少的团队都在自己的业务代码中直接使用了第三方代码中的对象,第三方的任何修改都会让你的代码跟着改,你的团队就只能疲于奔命。
|
||||
|
||||
解决这个问题最好的办法就是把它们分开,你的领域层只依赖于你的领域对象,第三方发过来的内容先做一次转换,转换成你的领域对象。这种做法称为防腐层。
|
||||
|
||||
当我们把领域模型看成了整个设计的核心,看待其他层的视角也会随之转变,它们只不过是适配到不同地方的一种方式而已,而这种理念的推广,就是一些人在说的六边形架构。
|
||||
|
||||
|
||||
|
||||
怎么设计好领域模型是一个庞大的主题,推荐你去了解一下领域驱动设计(Domain Driven Design,DDD),这个话题我们后面还会再次提到。
|
||||
|
||||
讨论其实还可以继续延伸下去,已经构建好的领域模型怎么更好地提供给其他部分使用呢?一个好的做法是封装成领域特定语言(Domain Specific Language,DSL)。当然,这也是一个庞大的话题,就不继续展开了。
|
||||
|
||||
总结时刻
|
||||
|
||||
我从最常见的服务端三层架构入手,给你讲了它们的来龙去脉。分层架构实际是一种设计上的分解,将不同的内容放在不同的地方,降低软件开发和维护的成本。
|
||||
|
||||
分层,更关键的是,提供抽象。这种分层抽象在计算机领域无处不在,无论是编程语言,还是网络协议,都体现着分层抽象的价值。有了分层抽象,人们才能更好地在抽象的基础上构建更复杂的东西。
|
||||
|
||||
在日常工作中,我们应该把精力重点放在构建自己的领域模型上,因为它才是工作最核心、不易变的东西。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:构建好你的领域模型。
|
||||
|
||||
最后我想请你思考一下,你还知道哪些技术是体现分层抽象的思想吗?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,121 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
36 为什么总有人觉得5万块钱可以做一个淘宝?
|
||||
你好,我是郑晔。
|
||||
|
||||
今天,我们从软件行业的一个段子说起。
|
||||
|
||||
甲方想要做个电商网站,作为乙方的程序员问:“你要做个什么样的呢?”甲方说:“像淘宝那样就好。”程序员问:“那你打算出多少钱?”甲方想了想,“5万块钱差不多了吧!”
|
||||
|
||||
这当然是个调侃客户不懂需求的段子,但你有没有想过,为什么在甲方看来并不复杂的系统,你却觉得困难重重呢?
|
||||
|
||||
因为你们想的根本不是一个东西。
|
||||
|
||||
在客户看来,我要的不就是一个能买东西的网站吗?只要能上线商品,用户能看到能购买不就好了,5万块钱差不多了。
|
||||
|
||||
而你脑中想的却是,“淘宝啊,那得是多大的技术挑战啊,每年一到‘双11’,那就得考虑各种并发抢购。淘宝得有多少程序员,5万块你就想做一个,门都没有。”
|
||||
|
||||
如果放在前面“沟通反馈”的模块,我可能会讲双方要怎么协调,把想法统一了。但到了“自动化”的模块,我想换个角度讨论这个问题:系统是怎么变复杂的。
|
||||
|
||||
淘宝的发展历程
|
||||
|
||||
既然说到了淘宝,我们就以一些公开资料来看看淘宝的技术变迁过程。2013年,子柳出版了一本《淘宝技术这十年》,这本书里讲述了淘宝是怎么一步步变化的。
|
||||
|
||||
按照书中的说法,第一个淘宝是“买来的”,买的是一个叫做 PHPAuction 的系统,即便选择了最高配,也才花了2000美元左右。这是一个采用 LAMP 架构的系统,也就是 Linux + Apache + MySQL + PHP,这在当年可是典型的开源架构。
|
||||
|
||||
团队所做的主要就是一些订制化工作,最大的调整就是将单一数据库的读写进行了拆分,变成了一个主库和两个从库。这种结构在今天来看,依然是很多团队做调整的首选。
|
||||
|
||||
|
||||
|
||||
当访问量和数据量不断提升,MySQL 数据库率先扛不住了。当年的 MySQL 默认采用的是 MyISAM 引擎,写数据的时候会锁住表,读也会被卡住,当然,这只是诸多问题中的一个。
|
||||
|
||||
2003年底,团队将 MySQL 换成了 Oracle。由于 Oracle 的性能要好上许多,主从的数据库架构又改回了单一数据库。但由于 PHP 访问数据库的缺省方案没有连接池,只好找了开源的 SQL Relay,这也为后续的改进埋下了伏笔。
|
||||
|
||||
|
||||
|
||||
当数据量继续加大,本地存储就已经无法满足了,只能通过引入网络存储解决问题。数据量进一步增大之后,存储节点一拆再拆,依然不能解决问题,淘宝就踏上了购买小型机的道路。
|
||||
|
||||
IBM 的小型机、Oracle 的数据库和 EMC 的存储,这个阶段就踏上了 IOE 之路。
|
||||
|
||||
2004年初,SQL Relay 已经成了一个挥之不去的痛点,于是,只能从更根本的方案上动脑筋:更换程序设计语言。作为当时的主流,Java 成了不二之选。
|
||||
|
||||
替换的方案就是给业务分模块,一块一块地替换。老模块只维护,不增加新功能,新功能只在新模块开发,新老模块共用数据库。新功能上线,则关闭老模块对应功能,所有功能替换完毕,则老模块下线。
|
||||
|
||||
淘宝的数据量继续增长,单台 Oracle 很快到了上限,团队采用了今天常见的“分库分表”模式,但“分库分表”就会带来新的问题,跨数据库的数据怎么整合?于是,打造出了一个 DBRoute,用以处理分库的数据。
|
||||
|
||||
但是,这种做法也带来了一个新的问题,同时连接多个数据库,任何一个数据库出了问题,都会导致整个网站的故障。
|
||||
|
||||
当淘宝的数据量再次增长,每次访问都到了数据库,数据库很难承受。一个解决方案就是引入缓存和 CDN(Content Delivery Network,内容分发网络),这样,只读数据的压力就从数据库解放了出来。
|
||||
|
||||
当时的缓存系统还不像今天这么成熟,于是,团队基于一个开源项目改出了一个。他们用的 CDN 最开始是一个商用系统,但流量的增加导致这个系统也支撑不住了,只好开始搭建自己的 CDN。
|
||||
|
||||
后来,因为 CDN 要消耗大量的服务器资源,为了降低成本,淘宝又开始研发自己的低功耗服务器。
|
||||
|
||||
随着业务的不断发展,开发人员越来越多,系统就越来越臃肿,耦合度也逐渐提升,出错的概率也逐渐上升。这时,不得不对系统进行分解,将复用性高的模块拆分出来,比如,用户信息。
|
||||
|
||||
业务继续发展,拆分就从局部开始向更大规模发展,底层业务和上层流程逐渐剥离,并逐渐将所有业务都模块化。
|
||||
|
||||
有了一个相对清晰地业务划分之后,更多的底层业务就可以应用于不同的场景,一个基础设施就此成型,新的业务就可以使用基础设施进行构建,上层业务便如雨后春笋一般蓬勃发展起来。
|
||||
|
||||
在这个过程中,有很多技术问题在当时还没有好的解决方案,或者是不适合于它们所在的场景。所以,淘宝的工程师就不得不打造自己的解决方案,比如:分布式文件系统(TFS)、缓存系统(Tair)、分布式服务框架(HSF)等等。还有一些技术探索则是为了节省成本,比如,去 IOE 和研发低功耗服务器等等。
|
||||
|
||||
我这里以淘宝网站的发展为例,做了一个快速的梳理,只是为了让你了解一个系统的发展,如果你有兴趣了解更多细节,不妨自己找出这本书读读。当然,现在的淘宝肯定比这更加完整复杂。
|
||||
|
||||
同样的业务,不同的系统
|
||||
|
||||
为什么我们要了解一个系统的演化过程呢?因为作为程序员,我们需要知道自己面对的到底是一个什么样的系统。
|
||||
|
||||
回到我们今天的主题上,5万块钱可以不可以做一个淘宝?答案是,取决于你要的是一个什么样的系统。最开始买来的“淘宝”甚至连5万块钱都不用,而今天的淘宝和那时的淘宝显然不是一个系统。
|
||||
|
||||
从业务上说,今天的淘宝固然已经很丰富了,但最核心的业务相差并不大,无非是卖家提供商品,买家买商品。那它们的本质差别在哪呢?
|
||||
|
||||
回顾上面的过程,你就可以看到,每次随着业务量的增长,原有技术无法满足需要,于是,就需要用新的技术去解决这个问题。这里的关键点在于:不同的业务量。
|
||||
|
||||
一个只服务于几个人的系统,单机就够了,一个刚刚入行的程序员也能很好地实现这个系统。而当业务量到达一台机器抗不住的时候,就需要用多台机器去处理,这个时候就必须考虑分布式系统的问题,可能就要适当地引入中间件。
|
||||
|
||||
而当系统变成为海量业务提供服务,就没有哪个已经打造好的中间件可以提供帮助了,需要自己从更底层解决问题。
|
||||
|
||||
虽然在业务上看来,这些系统是一样的,但在技术上看来,在不同的阶段,一个系统面对的问题是不同的,因为它面对业务的量级是不同的。更准确地说,不同量级的系统根本就不是一个系统。
|
||||
|
||||
只要业务在不断地发展,问题就会不断出现,系统就需要不断地翻新。我曾听到一个很形象的比喻:把奥拓开成奥迪。
|
||||
|
||||
你用对技术了吗?
|
||||
|
||||
作为一个程序员,我们都知道技术的重要性,所以,我们都会努力地去学习各种各样的新技术。尤其是当一个技术带有大厂光环的时候,很多人都会迫不及待地去学习。
|
||||
|
||||
我参加过很多次技术大会,当大厂有人分享的时候,通常都是人山人海,大家都想学习大厂有什么“先进”技术。
|
||||
|
||||
知道了,然后呢?
|
||||
|
||||
很多人就想迫不及待地想把这些技术应用在自己的项目中。我曾经面试过很多程序员,给我讲起技术来滔滔不绝,说什么自己在设计时考虑各种分布式的场景,如果系统的压力上来时,他会如何处理。
|
||||
|
||||
我就好奇地问了一个问题,“你这个系统有多少人用?”结果,他做的只是一个内部系统,使用频率也不高。
|
||||
|
||||
为了技术而技术的程序员不在少数,过度使用技术造成的结果就是引入不必要的复杂度。即便用了牛刀杀鸡,因为缺乏真实场景检验,也不可能得到真实反馈,对技术理解的深度也只能停留在很表面的程度上。
|
||||
|
||||
在前面的例子中,淘宝的工程师之所以要改进系统,真实的驱动力不是技术,而是不断攀升的业务量带来的问题复杂度。
|
||||
|
||||
所以,评估系统当前所处的阶段,采用恰当的技术解决,是我们最应该考虑的问题。
|
||||
|
||||
也许你会说,我做的系统没有那么大的业务量,我还想提高技术怎么办?答案是到有好问题的地方去。现在的 IT 行业提供给程序员的机会很多,找到一个有好问题的地方并不是一件困难的事,当然,前提条件是,你自己得有解决问题的基础能力。
|
||||
|
||||
总结时刻
|
||||
|
||||
今天,我以淘宝的系统为例,给你介绍了一个系统逐渐由简单变复杂的发展历程,希望你能认清不同业务量级的系统本质上就不是一个系统。
|
||||
|
||||
一方面,有人会因为对业务量级理解不足,盲目低估其他人系统的复杂度;另一方面,也有人会盲目应用技术,给系统引入不必要的复杂度,让自己陷入泥潭。
|
||||
|
||||
作为拥有技术能力的程序员,我们都非常在意个人技术能力的提升,但却对在什么样情形下,什么样的技术更加适用考虑得不够。采用恰当的技术,解决当前的问题,是每个程序员都应该仔细考虑的问题。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:用简单技术解决问题,直到问题变复杂。
|
||||
|
||||
最后,我想请你回想一下,你身边有把技术做复杂而引起的问题吗?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,111 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
37 先做好DDD再谈微服务吧,那只是一种部署形式
|
||||
你好,我是郑晔。
|
||||
|
||||
在“自动化”模块的最后,我们来聊一个很多人热衷讨论却没做好的实践:微服务。
|
||||
|
||||
在今天做后端服务似乎有一种倾向,如果你不说自己做的是微服务,出门都不好意思和人打招呼。
|
||||
|
||||
一有技术大会,各个大厂也纷纷为微服务出来站台,不断和你强调自己公司做微服务带来的各种收益,下面的听众基本上也是热血沸腾,摩拳擦掌,准备用微服务拯救自己的业务。
|
||||
|
||||
我就亲眼见过这样的例子,几个参加技术大会的人回到公司,跟人不断地说微服务的好,说服了领导,在接下来大的项目改造中启用了微服务。
|
||||
|
||||
结果呢?一堆人干了几个月,各自独立开发的微服务无法集成。最后是领导站出来,又花了半个月时间,将这些“微服务”重新合到了一起,勉强将这个系统送上了线。
|
||||
|
||||
人家的微服务那么美,为什么到你这里却成了烂摊子呢?因为你只学到了微服务的形。
|
||||
|
||||
微服务
|
||||
|
||||
大部分人对微服务的了解源自 James Lewis 和 Martin Fowler 在2014年写的一篇文章,他们在其中给了微服务一个更清晰的定义,把它当作了一种新型的架构风格。
|
||||
|
||||
但实际上,早在这之前的几年,很多人就开始用“微服务”这个词进行讨论了。
|
||||
|
||||
“在企业内部将服务有组织地进行拆分”这个理念则脱胎于 SOA(Service Oriented Architecture,面向服务的架构),只不过,SOA 诞生自那个大企业操盘技术的年代,自身太过于复杂,没有真正流行开来。而微服务由于自身更加轻量级,符合程序员的胃口,才得以拥有更大的发展空间。
|
||||
|
||||
谈到微服务,你会想起什么呢?很多人对微服务的理解,就是把一个巨大的后台系统拆分成一个一个的小服务,再往下想就是一堆堆的工具了。
|
||||
|
||||
所以,市面上很多介绍微服务的内容,基本上都是在讲工具的用法,或是一些具体技术的讨论,比如,用 Spring Boot 可以快速搭建服务,用 Spring Cloud 建立分布式系统,用 Service Mesh 技术作为服务的基础设施,以及怎么在微服务架构下保证事务的一致性,等等。
|
||||
|
||||
确实,这些内容在你实现微服务时,都是有价值的。但必须先回答一个问题,我们为什么要做微服务?
|
||||
|
||||
对这个问题的标准回答是,相对于整体服务(Monolithic)而言,微服务足够小,代码更容易理解,测试更容易,部署也更简单。
|
||||
|
||||
这些道理都对,但这是做好了微服务的结果。怎么才能达到这个状态呢?这里面有一个关键因素,怎么划分微服务,也就是一个庞大的系统按照什么样的方式分解。
|
||||
|
||||
这是在很多关于微服务的讨论中所最为欠缺的,也是很多团队做“微服务”却死得很难看的根本原因。
|
||||
|
||||
不了解这一点,写出的服务,要么是服务之间互相调用,造成整个系统执行效率极低;要么是你需要花大力气解决各个服务之间的数据一致性。换句话说,服务划分不好,等待团队的就是无穷无尽的偶然复杂度泥潭。只有正确地划分了微服务,它才会是你心目中向往的样子。
|
||||
|
||||
那应该怎么划分微服务呢?你需要了解领域驱动设计。
|
||||
|
||||
领域驱动设计
|
||||
|
||||
领域驱动设计(Domain Driven Design,DDD)是 Eric Evans 提出的从系统分析到软件建模的一套方法论。它要解决什么问题呢?就是将业务概念和业务规则转换成软件系统中概念和规则,从而降低或隐藏业务复杂性,使系统具有更好的扩展性,以应对复杂多变的现实业务问题。
|
||||
|
||||
这听上去很自然,不就应该这么解决问题吗?并不然,现实情况可没那么理想。
|
||||
|
||||
在此之前,人们更多还是采用面向数据的建模方式,时至今日,还有许多团队一提起建模,第一反应依然是建数据库表。这种做法是典型的面向技术实现的做法。一旦业务发生变化,团队通常都是措手不及。
|
||||
|
||||
DDD 到底讲了什么呢?它把你的思考起点,从技术的角度拉到了业务上。
|
||||
|
||||
贴近业务,走近客户,我们在这个专栏中已经提到过很多次。但把这件事直接体现在写代码上,恐怕还是很多人不那么习惯的一件事。DDD 最为基础的就是通用语言(Ubiquitous Language),让业务人员和程序员说一样的语言。
|
||||
|
||||
这一点我在《21 | 你的代码为谁而写?》中已经提到过了。使用通用语言,等于把思考的层次从代码细节中拉到了业务层面。越高层的抽象越稳定,越细节的东西越容易变化。
|
||||
|
||||
有了通用语言做基础,然后就要进入到 DDD 的实战环节了。DDD 分为战略设计(Strategic Design)和战术设计(Tactical Design)。
|
||||
|
||||
战略设计是高层设计,它帮我们将系统切分成不同的领域,并处理不同领域的关系。我在前面的内容中给你举过“订单”和“用户”的例子。从业务上区分,把不同的概念放到不同的地方,这是从根本上解决问题,否则,无论你的代码写得再好,混乱也是不可避免的。而这种以业务的角度思考问题的方式就是 DDD 战略设计带给我的。
|
||||
|
||||
战术设计,通常是指在一个领域内,在技术层面上如何组织好不同的领域对象。举个例子,国内的程序员喜欢用 myBatis 做数据访问,而非 JPA,常见的理由是 JPA 在有关联的情况下,性能太差。但真正的原因是没有设计好关联。
|
||||
|
||||
如果能够理解 DDD 中的聚合根(Aggregate Root),我们就可以找到一个合适的访问入口,而非每个人随意读取任何数据。这就是战术设计上需要考虑的问题。
|
||||
|
||||
战略设计和战术设计讨论的是不同层面的事情,不过,这也是 Eric Evans 最初没有讲清楚的地方,导致了人们很长时间都无法理解 DDD 的价值。
|
||||
|
||||
走向微服务
|
||||
|
||||
说了半天,这和微服务有什么关系呢?微服务真正的难点并非在于技术实现,而是业务划分,而这刚好是 DDD 战略设计中限界上下文(Bounded Context)的强项。
|
||||
|
||||
虽然通用语言打通了业务与技术之间的壁垒,但计算机并不擅长处理模糊的人类语言,所以,通用语言必须在特定的上下文中表达,才是清晰的。就像我们说过的“订单”那个例子,交易的“订单”和物流的“订单”是不同的,它们都有着自己的上下文,而这个上下文就是限界上下文。
|
||||
|
||||
它限定了通用语言自由使用的边界,一旦出界,含义便无法保证。正是由于边界的存在,一个限界上下文刚好可以成为一个独立的部署单元,而这个部署单元就可以成为一个服务。
|
||||
|
||||
所以要做好微服务,第一步应该是识别限界上下文。
|
||||
|
||||
你也看出来了,每个限界上下文都应该是独立的,每个上下文之间就不应该存在大量的耦合,困扰很多人的微服务之间大量相互调用,本身就是一个没有划分好边界而带来的伪命题,靠技术解决业务问题,事倍功半。
|
||||
|
||||
有了限界上下文就可以做微服务了吧?且慢!
|
||||
|
||||
Martin Fowler 在写《企业应用架构模式》时,提出了一个分布式对象第一定律:不要分布对象。同样的话,在微服务领域也适用,想做微服务架构,首先是不要使用微服务。如果将一个整体服务贸然做成微服务,引入的复杂度会吞噬掉你以为的优势。
|
||||
|
||||
你可能又会说了,“我都把限界上下文划出来了,你告诉我不用微服务?”
|
||||
|
||||
还记得我在《30 | 一个好的项目自动化应该是什么样子的?》中提到的分模块吗?如果你划分出了限界上下文,不妨先按照它划分模块。
|
||||
|
||||
以我拙见,一次性把边界划清楚并不是一件很容易的事。大家在一个进程里,调整起来会容易很多。然后,让不同的限界上下文先各自独立演化。等着它演化到值得独立部署了,再来考虑微服务拆分的事情。到那时,你也学到各种关于微服务的技术,也就该派上用场了!
|
||||
|
||||
总结时刻
|
||||
|
||||
微服务是很多团队的努力方向,然而,现在市面上对于微服务的介绍多半只停留在技术层面上,很多人看到微服务的好,大多数是结果,到自己团队实施起来却困难重重。想要做好微服务,关键在于服务的划分,而划分服务,最好先学习 DDD。
|
||||
|
||||
Eric Evans 2003年写了《领域驱动设计》,向行业介绍了DDD 这套方法论,立即在行业中引起广泛的关注。但实话说,Eric 在知识传播上的能力着实一般,这本 DDD 的开山之作写作质量难以恭维,想要通过它去学好 DDD,是非常困难的。所以,在国外的技术社区中,有很多人是通过各种交流讨论逐渐认识到 DDD 的价值所在,而在国内 DDD 几乎没怎么掀起波澜。
|
||||
|
||||
2013年,在 Eric Evans 出版《领域驱动设计》十年之后,DDD 已经不再是当年吴下阿蒙,有了自己一套比较完整的体系。Vaughn Vernon 将十年的精华重新整理,写了一本《实现领域驱动设计》,普通技术人员终于有机会看明白 DDD 到底好在哪里了。所以,你会发现,最近几年,国内的技术社区开始出现了大量关于 DDD 的讨论。
|
||||
|
||||
再后来,因为《实现领域驱动设计》实在太厚,Vaughn Vernon 又出手写了一本精华本《领域驱动设计精粹》,让人可以快速上手 DDD,这本书也是我向其他人推荐学习 DDD 的首选。
|
||||
|
||||
即便你学了 DDD,知道了限界上下文,也别轻易使用微服务。我推荐的一个做法是,先用分模块的方式在一个工程内,让服务先演化一段时间,等到真的觉得某个模块可以“毕业”了,再去开启微服务之旅。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:学习领域驱动设计。
|
||||
|
||||
最后,我想请你分享一下,你对 DDD 的理解是什么样的呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,134 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
40 我们应该如何保持竞争力?
|
||||
你好,我是郑晔。
|
||||
|
||||
在前面两讲,我结合着两个程序员要直接面对的场景,讨论了如何综合运用前面学习到的知识,这一讲的内容可能不涉及到实际的应用场景,但与每个人的发展息息相关。我想谈谈如何走好程序员这条路。
|
||||
|
||||
焦虑的程序员
|
||||
|
||||
让我们再次用思考框架分析一下问题。首先,现状是什么?关于这个问题,我并不打算讨论个体,因为每个人的情况千差万别,我准备从整体入手。
|
||||
|
||||
IT 行业是一个快速发展变化的行业,一方面,我们不断地看到有人快速取得成功,另一方面,我们也听到了许多充满焦虑的声音。获得大的成功总是一个小概率事件,大多数人面对的还是日常的柴米油盐。
|
||||
|
||||
我们的焦虑来自于对未来的不确定性,而这种不确定性是一个特定时代加上特定行业的产物。
|
||||
|
||||
如果把时间倒回到上个世纪80年代之前,虽然当时的生活条件一般,但很少有人会为未来的发展焦虑,因为那时候,人们可以清晰地看到自己未来的人生,尽管那种人生可能是平淡的。
|
||||
|
||||
但今天的我们处在一个人类历史上少有的快速发展的时代,我们看不清以后的人生,大脑却还停留在上一代人的思维习惯上。
|
||||
|
||||
IT 行业在国内的大发展也就最近20多年的事,行业里很少有走过完整职业生涯的程序员。也正是因为如此,我们经常会产生了各种焦虑:
|
||||
|
||||
|
||||
我刚刚入行时,有人问,程序员能做到30岁吗?
|
||||
我快30岁时,有人问,35岁还能做程序员吗?
|
||||
我35岁时,讨论变成了40岁的程序员该怎么办。
|
||||
|
||||
|
||||
估计等国内有越来越多的程序员走完了整个职业生涯,就会有人关心,程序员退休之后的生活应该是什么样子了。
|
||||
|
||||
从长期来看,只要生活中还有需要用自动化解决的问题,程序员这个群体还是很有前景的。但随着时间的推移,程序员这个职业的溢价也会越来越低,单纯凭借身处这个行业就获得好发展的可能性也越来越低,想让自己的职业生涯走得更顺畅,还需要找到更好的目标,不断努力。
|
||||
|
||||
成为 T 型人
|
||||
|
||||
我们再来回答下一个问题:目标是什么。也许这时候,每个人脑子里想到的职业发展路线都不一样,但我准备用一个统一的目标回答你:成为 T 型人。
|
||||
|
||||
什么叫 T 型人?简言之,一专多能。
|
||||
|
||||
|
||||
|
||||
有了“一专”,“多能”才是有意义的,否则,就是低水平重复,而这正是很多人职业生涯不见起色的真正原因。
|
||||
|
||||
这里的“专”不是熟练,而是深入。你可能是个有着10年丰富经验的程序员,但实际上只不过是重复了10年解决同样难度的问题而已,这根本就不算深入,也就没有做到真正意义上的“一专”。
|
||||
|
||||
你会发现很多优秀的人,在很多方面都会很优秀,这是“一专”带来的触类旁通。
|
||||
|
||||
当你有了“一专”,拓展“多能”,就会拥有更宽广的职业道路。比如,我拥有了深厚的技术功底,通晓怎么做软件:
|
||||
|
||||
|
||||
如果还能够带着其他人一起做好,就成了技术领导者。
|
||||
如果能够分享技术的理解,就有机会成为培训师。
|
||||
如果能够在实战中帮助别人解决问题,就可以成为咨询师。
|
||||
|
||||
|
||||
反过来,当你有了“多能”,也可以拓宽你的视野,帮你认清自己的“一专”怎样更好地发挥价值,而不是狭隘地认为自己有了技术,就已经天下尽在掌握了。视野窄,缺乏大局观,也成为了许多程序员再进一步的阻碍。事实上,这个专栏里的很多内容都是帮你打开“多能”的视角。
|
||||
|
||||
也许你会说,我在公司已经独当一面了,应该算有“一专”了吧?但我想说的是,可能还不够。只做一个公司的专家,受一个公司的波动影响太大,而成为行业的专家,才会降低自己职业生涯的风险。
|
||||
|
||||
有时,我在面试时会问候选人这样一个问题:“如果让你在一次技术大会上做分享,你会讲什么呢?”我真正的问题是,以行业标准衡量,你觉得你在哪个方面是专家呢?
|
||||
|
||||
大多数人从来没有思考过这个问题,他们只是日常在完成自己的工作,即便在某一方面已经做得很不错了,但依然算不上专家,因为他们缺乏深度思考。
|
||||
|
||||
比如,你非常熟悉 Kafka,知道它的各种参数,也读过它的实现原理。但如果我问你,Kafka 为什么要把自己定位成一个分布式流平台,它要想成为一个流平台,还要在哪方面做得更好?你的答案是什么呢?
|
||||
|
||||
这其中的差别就是,前面所谓的熟悉,只是熟悉别人的思考结果,而后面则是一个没有现成答案的东西。学习微积分是有难度,但同发明微积分相比,难度根本不在一个层次上。当然,我不是说你要熟悉所有工具的发展过程,而是自己要在一个特定的方面拥有深度的思考。
|
||||
|
||||
也许你会说,这个要求实在是太高了吧!没错,这确实是一个很高的要求。但“取法于上,仅得为中;取法于中,故为其下。”
|
||||
|
||||
其实,很多人的焦虑就源自目标太低,找不到前进的动力。给自己定下一个可以长期努力的目标,走在职业的道路上才不致于很快丧失动力。
|
||||
|
||||
在学习区成长
|
||||
|
||||
现在我们来回答第三个问题,怎么达到目标。既然要朝着行业中的专家方向努力,那你就得知道行业中的专家是什么样。我的一个建议是,向行业中的大师学习。
|
||||
|
||||
你或许会说,我倒是想向大师学习,但哪有机会啊!好在 IT 行业中的许多人都是愿意分享的,我们可以读到很多大师级程序员分享的内容。
|
||||
|
||||
我在入行的时候,有幸读了很多经典之作,比如,出身贝尔实验室的很多大师级程序员的作品,诸如《C 程序设计语言》《程序设计实践》、《Unix 编程环境》等,还有一些像 Eric Raymond 这样沉浸编程几十年的人写出的作品,诸如《Unix 编程艺术》,以及前面提及的 Kent Beck、Martin Fowler 和 Robert Martin 等这些人的作品。
|
||||
|
||||
读这些书的一个好处在于,你的视野会打开,不会把目标放在“用别人已经打造好的工具做一个特定的需求”,虽然这可能是你的必经之路,但那只是沿途的风景,而不是目标。
|
||||
|
||||
接下来,我们要踏上征程,怎么才能让自己的水平不断提高呢?我的答案是,找一个好问题去解决,解决了一个好的问题能够让你的水平快速得到提升。什么是好问题?就是比你当前能力略高一点的问题,比如:
|
||||
|
||||
|
||||
如果你还什么都不会,那有一份编程的工作就好。
|
||||
如果你已经能够写好普通的代码,就应该尝试去编写程序库。
|
||||
如果实现一个具体功能都没问题了,那就去做设计,让程序有更好的组织。
|
||||
如果你已经能完成一个普通的系统设计,那就应该去设计业务量更大的系统。
|
||||
|
||||
|
||||
为什么要选择比自己水平高一点的问题?这与我们学习成长的方式有关。Noel Tichy 提出了一个“学习区”模型,如下图所示:
|
||||
|
||||
|
||||
|
||||
|
||||
最内层是舒适区(Comfort Zone),置身其中会让人感觉良好,但也会因为没有挑战,成长甚微,你可以把它理解成做你最熟悉的事情。
|
||||
最外层是恐慌区(Panic Zone),这是压力极大的地方,完全超出了你的能力范围,你在其中只会感到无比的焦虑。
|
||||
中间的是学习区(Learning Zone),事情有难度,又刚好是你努力一下可以完成的,这才是成长最快的区域。
|
||||
|
||||
|
||||
根据这个模型,只有一直身处学习区才能让人得到足够的成长,所以,我们应该既选择比自己能力高一点的问题去解决,不要总做自己习惯的事,没有挑战,也不要好大喜功,一下子把自己的热情全部打散。
|
||||
|
||||
在学习区成长,就不要满足于当前已经取得的成绩,那已经成为你的舒适区。因为我们有远大的目标在前面指引,完成日常的工作只不过是个人成长路上的台阶。
|
||||
|
||||
也许你会说,我的工作不能给我个人成长所需的机会,怎么办呢?实际上,别人只会关心你是否完成工作,成长是自己的事情,很多机会都要靠自己争取,前面提到的那些具体做法完全是你可以在工作范围内,自己努力的事情。
|
||||
|
||||
如果你当前的工作已经不能给你提供足够好的问题,那就去寻找一份更有挑战性的工作。在 IT 行业,跳槽似乎是一件很常见的事,但很多人跳槽的时候,并不是以提升自己为目标的。造成的结果是,不断地做同一个层面的工作,自然也就很难提升自己的水平。
|
||||
|
||||
为什么程序员都愿意到大厂工作?因为那里有高水平的人和好的问题。但如果只是到大厂去做低水平的事,那就是浪费时间了。所以,即便你真的想到大厂工作,与谁一起工作,做什么事,远比进入大厂本身要重要得多。
|
||||
|
||||
如果你真的能够不断向前进步,迟早会遇到前面已经没有铺就好的道路,这时候,就轮到你创造一个工具给别人去使用了。比如,2012年,我在项目中受困于集成问题,却找不到一个我想要的、能在单元测试框架里用的模拟服务器,于是,我写了 Moco。
|
||||
|
||||
最后,我还想鼓励你分享所得。我在《28 | 结构化:写文档也是一种学习方式》中和你说过,输出是一种将知识连接起来的方式,它会让人摆脱固步自封,也会帮你去创造自己的行业影响力,机会会随着你在行业中的影响力逐渐增多,有了行业影响力,你才有资格成为行业专家。
|
||||
|
||||
当你成为了一个行业级别的专家,就可以在这条路上一直走下去,而不必担心自己是不是拼得过年轻人了,因为你也在一直前进!
|
||||
|
||||
总结时刻
|
||||
|
||||
程序员是一个充满焦虑的群体,焦虑的本质是对未来的不确定。工作在这个时代的程序员是一个特殊的群体,一方面,这个大时代为我们创造了无数的机会,另一方面,因为程序员是一个新的行业,所以,很多人不知道未来是什么样子的,焦虑颇深。
|
||||
|
||||
从目前的发展来看,IT 行业依然是一个非常有前景的行业,但想在这条路上走好,需要我们成为 “T ”型人才,也就是“一专多能”。一专多能的前提是“一专”,让自己成为某个方面的专家。这个专家要放在行业的标准去看,这才能降低因为一个公司的波动而造成的影响。
|
||||
|
||||
成为行业专家,要向行业的大师学习,给自己定下一个高的目标,然后是脚踏实地,找适合自己的问题去解决,让自己一直在学习区成长。
|
||||
|
||||
如果今天的内容你只能记住一件事,那请记住:在学习区工作和成长。
|
||||
|
||||
最后,我想请你分享一下,你有哪些保持自己竞争力的心得呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,233 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
划重点 “综合运用”主题内容的全盘回顾
|
||||
你好,我是郑晔。
|
||||
|
||||
又到了我们划重点的时间了,因为篇幅关系,“综合运用”这个模块最为短小精悍。
|
||||
|
||||
在这个模块中,我们把前面学到的各种知识综合起来,运用在实际的工作场景中,让你知道这些内容并不是一个个孤立的实践,在实际工作中,唯有将它们结合起来,才能发挥最大功效。
|
||||
|
||||
重点复习
|
||||
|
||||
在这个模块中,我们学习到了一些新知识。
|
||||
|
||||
|
||||
“学习区”学习模型
|
||||
|
||||
|
||||
舒适区,舒适而缺乏成长。
|
||||
恐慌区,超出能力范围。
|
||||
学习区,有难度而可以达成。
|
||||
在学习区练习才能得到足够的成长。
|
||||
|
||||
|
||||
T 型人才,一专多能
|
||||
|
||||
|
||||
知识的广度。
|
||||
专业技能的深度。
|
||||
有“一专”,“多能”才是有意义的。
|
||||
|
||||
|
||||
|
||||
在这个模块中,我们还了解了一些重要的思路,让我们把工作做得更好。
|
||||
|
||||
|
||||
进入新工作,从全面了解开始
|
||||
|
||||
|
||||
业务:做什么。
|
||||
技术:怎么做。
|
||||
团队运作:怎么与人协作。
|
||||
从大到小,由外及内地了解工作。
|
||||
|
||||
|
||||
面对遗留系统,稳扎稳打,小步前行
|
||||
|
||||
|
||||
基础理念
|
||||
|
||||
|
||||
烂代码只是现象,要了解根因。
|
||||
能重构,先重构,大规模改造是迫不得已的选择。
|
||||
小步前行。
|
||||
|
||||
实际操作
|
||||
|
||||
|
||||
构建测试防护网。
|
||||
将大系统分解成小模块,逐步替换。
|
||||
新旧模块并存,由分发模块调度。
|
||||
建立好领域模型。
|
||||
寻找行业对于系统构建的最新理解。
|
||||
|
||||
|
||||
|
||||
程序员的职业发展
|
||||
|
||||
|
||||
程序员的焦虑来自于对未来的不确定性,这种不确定性是一个特定时代加上特定行业的产物。
|
||||
|
||||
|
||||
快速发展的中国经济。
|
||||
程序员在中国是一个新兴职业。
|
||||
|
||||
成为行业专家,制定高目标。
|
||||
向大师学习,开拓视野。
|
||||
找到好的问题,和高水平的人一起工作。
|
||||
|
||||
|
||||
|
||||
实战指南
|
||||
|
||||
|
||||
了解一个项目,从大图景开始。-
|
||||
——《38 | 新入职一家公司,怎么快速进入工作状态?》
|
||||
|
||||
小步改造遗留系统,不要回到老路上。-
|
||||
——《39 | 面对遗留系统,你应该这样做》
|
||||
|
||||
在学习区工作和成长。-
|
||||
——《40 | 我们应该如何保持竞争力?》
|
||||
|
||||
|
||||
额外收获
|
||||
|
||||
在这个模块的最后,针对大家在学习过程中的一些问题,我也进行了回答,帮你梳理出一个思路,更好地理解学到的内容:
|
||||
|
||||
|
||||
推行新观念,找愿意改变的人,做具体的事。
|
||||
Lead by Example.
|
||||
外部系统应该用接口隔离,这种做法体现了接口隔离原则(ISP),也是防腐层概念的体现。
|
||||
外部系统的测试,能模拟的就模拟,能本地的就本地。
|
||||
|
||||
|
||||
留言精选
|
||||
|
||||
关于入职一家新公司,怎么快速进入工作状态这个问题,西西弗与卡夫卡 同学分享了他的方法:
|
||||
|
||||
|
||||
有朋友正在转型,从乙方商业化产品的交付经理转向新公司的产品经理。原本得心应手的思维方式和工作习惯,遇到了巨大挑战。以前只需依据已有产品的功能出解决方案,能做就能做,不能实现就是不能实现,到某个时间交付什么功能很明确,考核是以交付签字为准。现在需要面对各方需求,自己想明白用户真正的问题是什么,最终要交付的价值是什么,没有一个实体的谁人来签字,只有不断地迭代。
|
||||
|
||||
借鉴领域驱动设计,可以采用以下方法。简单描述的话,是一个点、一个圈再加一个箭头线,是不是有点像丘比特?
|
||||
|
||||
一个“点”,指的是用户核心价值。这是最关键的一条,基本上只能靠自己想明白。想,不是闭门造车式的苦思冥想,可以是已有的领域经验,可以从书本中学习,可以是大家的各种吐槽,可以是自己从旁边观察用户的实践,还可以是自己变身为用户的实践。
|
||||
|
||||
有些人会纠结“点”想的对不对,迟迟不敢动手。其实一开始想得对不对不是那么重要,关键是要有这“点”,然后快速到市场上验证,根据反馈再调整。
|
||||
|
||||
一个“圈”,指的是围绕核心价值划出的范围,即领域驱动设计中的限界上下文。产品经理面临的一个现实是,各种人都会给你提需求,只要他们觉得和你有关,还时不时来问什么时候可以实现。
|
||||
|
||||
需求轰炸之下很容易焦虑,不光自己焦虑,所有的利益相关者都会焦虑。依据核心价值,框出需求范围,在和各方交流过程中可以有一种确定性,减少焦虑,利于行动。
|
||||
|
||||
大家(不光是研发团队,也包括其他需求方)就能明白,哪些和当前核心价值密切相关,我们优先考虑;哪些与核心价值有关但它不在我们的范围内,属于其他团队,需要他们协助;哪些有关系,但目前没想清楚价值大不大,并且代价可能很高建议先搁置。范围不是一成不变,它随着时间会发生变动,所以我们不要追求固定,只要保证在某个时间段内,大家一致认同即可。
|
||||
|
||||
一个“箭头”,指的是实现路径,箭头指向核心目标(核心价值)。目标(核心价值)和范围描绘的是终极,而从现实到终极还有很多路要走,可能的路径还有很多条。我们需要琢磨怎么走更稳当,怎么走代价比较低,路上关键的里程碑是什么。路径对不对是其次,重要的是思考过程,可以把关键点需要交付的价值、需要支持的资源等等梳理清楚。
|
||||
|
||||
|
||||
另外,西西弗与卡夫卡 同学还对于程序员如何保持竞争力的问题给出了非常不错的建议。
|
||||
|
||||
|
||||
补充我的一些做法。工作中不要满足当前需求,要经常从自己上级主管甚至老板角度来审视自己的工作,思考业务的终极目标,持续琢磨扩展边界,挑战工作难度。
|
||||
|
||||
平时多看书多思考,除了钻研某个领域,还要多有涉猎,拓展领域,成为终身学习者。
|
||||
|
||||
适当运动维持健康,你有更多体力和更强抗压能力的时候,就可以超过不少人。
|
||||
|
||||
保持竞争力除了上述之外,要保持乐观,相信大多数事都有解决方法,在多数人都容易放弃的时候,你的坚持,就是竞争力。
|
||||
|
||||
|
||||
对于新入职一家公司的场景,Y024 同学分享了他快速进入工作状态的方法:
|
||||
|
||||
|
||||
1.我会在权限允许的范围内,时不时的到处翻翻 ftp、内部 wiki 等资源,星星点点构建全貌(业务、技术、团队)。
|
||||
|
||||
2.梳理系统数据流。去年很火的电视剧「大江大河」里,宋运辉初入职场的方式就很值得借鉴:先走通全部流程,有个全貌,利用图书馆、师傅等资源再自己动手各个击破并绘制流程图,最终实践检验认知,以技术说话融入团队。
|
||||
|
||||
(他就每天只要天气晴朗,绕着设备上上下下、里里外外地跑。一个星期下来,全部流程走通;两个星期不到,原理搞通,仪表能读,普通故障能应付;第三星期开始,他可以开出维修单,但得给师父过目;第四星期起,谁有事请假他可以顶上,坐到仪表盘前抄表看动态做操作。师父说他学得很快。
|
||||
|
||||
第四星期起,没人可以让他顶替时候,他在仪表室后面支起绘图板。先画出工艺流程图,经现场核对无误,又让师父审核后,开始按部就班地根据液体走向,测绘所有设备的零件图、装配图、管段图等。
|
||||
|
||||
这工作最先做的时候异常艰难,首先是绘图不熟练,很多小毛病,尤其是遇到非标零件,还得到机修工段测绘,有时一天都绘不成一个小小非标件。如果车间技术档案室有图纸还好,可以对照着翻画,可档案室里的图纸残缺不全,前后混乱,想找资料,先得整理资料。
|
||||
|
||||
资料室中年女管理员乐得有个懂事的孩子来帮她整理,索性暗暗配把钥匙给宋运辉,要是她下班不在的时候,让宋运辉自己偷偷进来关上门寻找资料。
|
||||
|
||||
机修工段的人本来挺烦这个宋运辉,说他一来维修单子多得像雪片,支得他们团团转,有人还趁宋运辉上班时候冲进控制室指桑骂槐,被寻建祥骂了回去,差点还打起来。但后来集中一段维修高峰后,维修单子又少了下去,上面还表扬跑冒滴漏少很多,一工段和机修工段各加一次月奖,可见设备性能好转。
|
||||
|
||||
再以后遇到维修,他们不能确定要用什么零件,打个内线电话给控制室问宋运辉,一问就清楚。双方关系渐渐变得铁起来。基层有时候很简单,只要拿得出技术,别人就服。 )
|
||||
|
||||
|
||||
另外,Y024 同学还很认真地整理了专栏提到的部分图书:
|
||||
|
||||
|
||||
郑老师拍案惊奇书单及简评,最近各大书店有活动,可以借机囤起来了。
|
||||
|
||||
1.重构-
|
||||
作者: Martin Fowler-
|
||||
https://book.douban.com/subject/1229923/-
|
||||
严格说来,我并没有完整的读完这本书,不过,正如作者自己所说,这样的书原本就不指望能够读完,因为有一大部分其实是参考手册。正是我读过的部分让我知道了重构,让我知道这么做可以把代码写得更好。
|
||||
|
||||
2.敏捷软件开发-
|
||||
作者: Robert C·Martin-
|
||||
https://book.douban.com/subject/1140457/-
|
||||
这是一本名字赶潮流,内容很丰富的书,这本书让我开始理解软件设计,从此不再刻意追求设计模式。
|
||||
|
||||
3.测试驱动开发-
|
||||
作者: Kent Beck-
|
||||
https://book.douban.com/subject/1230036/-
|
||||
读的是英文版,因为当时中文版还没有出版,所以,我不敢说,我通过这本书很好的理解了测试驱动开发,但它却为我打开了一扇门,让我知道了一种更好的工作方式。
|
||||
|
||||
4.修改代码的艺术-
|
||||
作者: Michael Feathers-
|
||||
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 同学的理解也很棒:
|
||||
|
||||
|
||||
技术推广,不要先推广最难的部分,先推广能让对方感到最明显好处的部分。取得对方的信任,是友好沟通的基础。
|
||||
|
||||
|
||||
感谢同学们的精彩留言。我们的专栏更新已经进入尾声阶段,后续我会为大家做一些对整个专栏进行全盘复习的内容,敬请期待。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,211 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
划重点 “自动化”主题的重点内容回顾汇总
|
||||
你好,我是郑晔。
|
||||
|
||||
“自动化”模块终于全部更新完毕。至此,四个工作原则我已经给你全部介绍了一遍,相对而言,这个模块的内容比较“硬”,我也竭尽全力帮你串起更多知识的脉络,所以,信息量也是非常大的。希望你能够找到自己接下来努力的方向,不断提升自己的“硬实力”。
|
||||
|
||||
重点复习
|
||||
|
||||
在这个模块中,我们学习到了一些最佳实践。
|
||||
|
||||
|
||||
持续交付
|
||||
|
||||
|
||||
将生产部署纳入了开发的考量。
|
||||
持续交付的基础设施通常包含持续集成环境、测试环境、预生产环境和生产环境。
|
||||
构建流水线保证到了下游的交付物一定是通过上游验证的。
|
||||
随着 Docker 的诞生,交付由发布包变成了 Docker 镜像。
|
||||
|
||||
|
||||
DevOps
|
||||
|
||||
|
||||
将开发和运维结合到一起。
|
||||
环境配置工具上的进步,让基础设施即代码成了行业共识。
|
||||
|
||||
|
||||
验收测试
|
||||
|
||||
|
||||
验收测试要站在业务的角度编写。
|
||||
BDD 是一种编写验收测试的方式。
|
||||
Given…When…Then… 的描述给了一个描述业务的统一方式。
|
||||
写好验收测试,需要构建测试模型。
|
||||
|
||||
|
||||
SOLID 原则
|
||||
|
||||
|
||||
设计模式背后的道理。
|
||||
单一职责原则(Single responsibility principle,SRP)。
|
||||
开放封闭原则(Open–closed principle,OCP)。
|
||||
Liskov 替换原则(Liskov substitution principle,LSP)。
|
||||
接口隔离原则(Interface segregation principle,ISP)。
|
||||
依赖倒置原则(Dependency inversion principle,DIP)。
|
||||
用好单一职责原则,前提条件是看待问题颗粒度要小。
|
||||
|
||||
|
||||
DDD
|
||||
|
||||
|
||||
它将思考的起点拉到了业务上。
|
||||
DDD 分为战略设计和战术设计。
|
||||
|
||||
|
||||
微服务
|
||||
|
||||
|
||||
做好微服务的前提是划分好限界上下文。
|
||||
微服务的第一步,不要划分微服务。
|
||||
|
||||
|
||||
|
||||
在这个模块中,我们还了解了一些重要的思路,让我们把工作做得更好。
|
||||
|
||||
|
||||
程序员的三大美德:懒惰、急躁和傲慢(Laziness, Impatience and hubris)。
|
||||
小心 NIH 综合症(Not Invented Here Syndrome)。
|
||||
写好构建脚本,做好项目自动化。
|
||||
参照 Java 知识体系,学习运维知识。
|
||||
软件设计最基础的原则是“高内聚、低耦合”。
|
||||
分层架构是一种设计上的分解。
|
||||
不同业务量的系统本质上不是一个系统。
|
||||
采用简单技术解决问题,直到问题变复杂。
|
||||
|
||||
|
||||
实战指南
|
||||
|
||||
|
||||
请谨慎地将工作自动化。-
|
||||
——《29 | “懒惰”应该是所有程序员的骄傲》
|
||||
|
||||
将你的工作过程自动化。-
|
||||
——《30 | 一个好的项目自动化应该是什么样子的?》
|
||||
|
||||
有体系地学习运维知识。-
|
||||
——《31 | 程序员怎么学习运维知识?》
|
||||
|
||||
将部署纳入开发的考量。-
|
||||
——《32 | 持续交付:有持续集成就够了吗?》
|
||||
|
||||
将验收测试自动化。-
|
||||
——《33 | 如何做好验收测试?》
|
||||
|
||||
把函数写短。-
|
||||
——《34 | 你的代码是怎么变混乱的?》
|
||||
|
||||
构建好你的领域模型。-
|
||||
——《35 | 总是在说MVC分层架构,但你真的理解分层吗?》
|
||||
|
||||
用简单技术解决问题,直到问题变复杂。-
|
||||
——《36 | 为什么总有人觉得5万块钱可以做一个淘宝?》
|
||||
|
||||
学习领域驱动设计。-
|
||||
——《37 | 先做好DDD再谈微服务吧,那只是一种部署形式》
|
||||
|
||||
|
||||
额外收获
|
||||
|
||||
在这个模块的最后,针对大家在学习过程中的一些问题,我也进行了回答,帮你梳理出一个思路,更好地理解学到的内容:
|
||||
|
||||
|
||||
持续集成的延伸。
|
||||
|
||||
|
||||
持续集成完成系统集成。
|
||||
持续交付完成可部署上线。
|
||||
“持续验证”完成产品想法验证。
|
||||
|
||||
AB 测试,用一个软件的多个版本验证想法。
|
||||
Selenium 用以完成浏览器的自动化。
|
||||
熟练使用快捷键。
|
||||
|
||||
|
||||
——《答疑解惑 | 持续集成、持续交付,然后呢?》
|
||||
|
||||
留言精选
|
||||
|
||||
在讲到 “懒惰”应该是所有程序员的骄傲时,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
|
||||
|
||||
《DevOps知识体系与标准化的构建》也不错,下载地址:-
|
||||
https://yq.aliyun.com/download/778
|
||||
|
||||
运维知识体系:-
|
||||
https://www.unixhot.com/page/ops
|
||||
|
||||
Web缓存知识体系:-
|
||||
https://www.unixhot.com/page/cache
|
||||
|
||||
|
||||
感谢同学们的精彩留言。在下一个模块中,我将结合具体的应用场景,将之前讲过的“思考框架”和“四个原则”进行综合应用的分析。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,213 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
划重点 一次关于“沟通反馈”主题内容的复盘
|
||||
你好,我是郑晔,恭喜你,又完成了一个模块的学习。
|
||||
|
||||
在“沟通反馈”这个模块中,我与你探讨了与人打交道的一些方法,只不过,这并非是传统意义上的谈话技巧。而是希望你能克服自己的心理障碍,主动与真实世界进行沟通,获取反馈,让自己对信息的编解码能力不断得到提升。
|
||||
|
||||
重点复习
|
||||
|
||||
在这个模块中,我们学习到了一些最佳实践。
|
||||
|
||||
|
||||
看板
|
||||
|
||||
|
||||
一种来自精益生产的可视化实践。
|
||||
按阶段将任务放置其中。
|
||||
可以帮助我们发现问题。
|
||||
|
||||
|
||||
持续集成
|
||||
|
||||
|
||||
做好持续集成的关键是,快速反馈。
|
||||
本地检查通过之后再提交。
|
||||
找到有效的反馈方式,比如:CI 监视器。
|
||||
持续集成的纪律。
|
||||
|
||||
|
||||
只有 CI 服务器处于绿色的状态才能提交代码。
|
||||
CI 服务器一旦检查出错,要立即修复。
|
||||
|
||||
|
||||
|
||||
回顾会议
|
||||
|
||||
|
||||
软件团队复盘的一种实践。
|
||||
枚举关注点,选出重点,深入讨论,列出行动项,找到负责人。
|
||||
|
||||
|
||||
5个为什么
|
||||
|
||||
|
||||
又一个来自丰田的实践。
|
||||
沿着一条主线追问多个问题。
|
||||
|
||||
|
||||
|
||||
在这个模块中,我们还了解一些重要的思路,让我们把工作做得更好。
|
||||
|
||||
|
||||
用信息论理解沟通反馈
|
||||
|
||||
写代码的进阶路径
|
||||
|
||||
|
||||
编写可以运行的代码。
|
||||
编写符合代码规范的代码。
|
||||
编写人可以理解的代码。
|
||||
用业务语言写代码。
|
||||
|
||||
|
||||
会议是一种重量级的沟通方式
|
||||
|
||||
|
||||
减少参会人数。
|
||||
找人面对面沟通。
|
||||
|
||||
|
||||
聆听用户声音
|
||||
|
||||
|
||||
能做自己用户,做自己的用户。
|
||||
能接近用户,接近用户。
|
||||
没有用户,创造用户。
|
||||
|
||||
|
||||
Fail Fast
|
||||
|
||||
|
||||
一种编写代码的原则。
|
||||
出现问题尽早报错。
|
||||
|
||||
|
||||
金字塔原理
|
||||
|
||||
|
||||
从中心论点,到分论点,再到论据。
|
||||
|
||||
|
||||
|
||||
实战指南
|
||||
|
||||
在“沟通反馈”的模块,我也将每篇内容浓缩为一句实战指南,现在一起回顾一下。
|
||||
|
||||
|
||||
通过沟通反馈,不断升级自己的编解码能力。-
|
||||
——《20 | 为什么世界和你的理解不一样》
|
||||
|
||||
用业务的语言写代码。-
|
||||
——《21 | 你的代码为谁而写?》
|
||||
|
||||
多面对面沟通,少开会。-
|
||||
——《22 | 轻量级沟通:你总是在开会吗?》
|
||||
|
||||
多尝试用可视化的方式进行沟通。-
|
||||
——《23 | 可视化:一种更为直观的沟通方式》
|
||||
|
||||
做好持续集成的关键在于,快速反馈。-
|
||||
——《24 | 快速反馈:为什么你们公司总是做不好持续集成?》
|
||||
|
||||
定期复盘,找准问题根因,不断改善。-
|
||||
——《25 | 开发中的问题一再出现,应该怎么办?》
|
||||
|
||||
多走近用户。-
|
||||
——《26 | 作为程序员,你也应该聆听用户声音》
|
||||
|
||||
事情往前做,有问题尽早暴露。-
|
||||
——《27 | 尽早暴露问题: 为什么被指责的总是你?》
|
||||
|
||||
多输出,让知识更有结构。-
|
||||
——《28 | 结构化:写文档也是一种学习方式》
|
||||
|
||||
|
||||
额外收获
|
||||
|
||||
在这个模块的最后,针对大家在学习过程中的一些问题,我也进行了回答,帮你梳理出一个思路,更好地理解学到的内容:
|
||||
|
||||
|
||||
持续集成是一条主线,可以将诸多实践贯穿起来。
|
||||
|
||||
|
||||
从持续集成到稳定的开发分支,到频繁提交,足够小的任务,到任务分解。
|
||||
从持续集成到可检查,到测试防护网,到测试覆盖率,到单元测试,到可测试代码,到软件设计。
|
||||
|
||||
|
||||
安全性检查,是回顾会议的前提条件。
|
||||
|
||||
在信息获取上,国内外程序员差别不大,开拓视野,改善工作习惯,是国内程序员亟需提高的。
|
||||
|
||||
|
||||
——《答疑解惑 | 持续集成,一条贯穿诸多实践的主线》
|
||||
|
||||
留言精选
|
||||
|
||||
在讲到定期复盘,找准问题根因时,西西弗与卡夫卡 同学提到:
|
||||
|
||||
|
||||
关于复盘,孙陶然曾经说过,如果他有所成就,一半要归功于复盘。他提出了几个步骤供大家参考。首先,先对比实际结果和起初所定目标之间有什么差距。其次,情景再现,回顾项目的几个阶段。然后,对每个阶段进行得失分析,找出问题原因。最后,总结规律,化作自己的技能沉淀,再次遇到时可以规避。
|
||||
|
||||
我再补充一点,复盘资料应该记录到知识库,无论新来的或是接手的人,都能从中获益,从而提升组织的能力。另外,好的复盘需要有坦诚的文化氛围,不然有可能变成互相指责甩锅,就失去了意义。
|
||||
|
||||
|
||||
另外,西西弗与卡夫卡 同学还分享了提升开会效率的方法:
|
||||
|
||||
|
||||
其他一些提升开会效率的方法,比如会前每个人要先做准备,把观点写下来,然后发给主持人。再比如六顶思考帽,大家按相近的思考角度讨论,而不是我说一趴,你说另一趴。还有,主持人控制这轮谁能发言,控制每个人的时长。方法很多,但实际上总有人破坏规则,特别是当这个人是老板…
|
||||
|
||||
|
||||
在用信息论来讨论沟通反馈问题时,毅 同学将知识点融会贯通,提出了自己的心得:
|
||||
|
||||
|
||||
不同角色间的沟通:克服上下文差异,分段解码,理解偏差早发现早反馈。相同角色间的沟通,信号相同,解码能力因人而异,要有一个主导的人,控制沟通广度与深度,抓主线适可而止,此时结合任务分解,反向沙盘推演。
|
||||
|
||||
|
||||
关于如何做好复盘,like_jun 同学提到:
|
||||
|
||||
|
||||
要让团队认识到复盘的重要性。-
|
||||
让每个人都深入思考项目运作过程中遇到了哪些问题。才能做好复盘。
|
||||
|
||||
|
||||
在讲到通过金字塔原理进行知识输出时,Y024 同学丰富了金字塔原理的基本原则,具体如下:
|
||||
|
||||
|
||||
金字塔原理的四个基本原则:“结论先行”(一次表达只支持一个思想,且出现在开头)、“以上统下”(任一层次上的思想都必须是其下一层思想的总结概括)、“归类分组”(每组中的思想都必须属于同一范畴)和“逻辑递进”(每组中的思想都必须按照逻辑顺序排列)。
|
||||
|
||||
前面两个特点是纵向结构之间的特点,后面两个特点则是横向结构之间的特点。以上内容收集整理自李忠秋老师的《结构思考力》,感兴趣的小伙伴可以看看。
|
||||
|
||||
|
||||
另外,对于会议,Y024 同学也提出了他团队正在进行的摸索和尝试:
|
||||
|
||||
|
||||
1.沟通的指导原则之一就是在同步沟通的时候(比如开会),人越少越好。而在异步沟通的时候(比如E-mail),涉及的听众越多越好。
|
||||
|
||||
2.关于开会分享下我们正在摸索的。-
|
||||
(a)每个会开始前,会议发起人在石墨文档上以“会议记录”模版(我们持续形成自己的模版)新建一个纪要:说明议程、及讨论内容等前提内容并提前告知与会人员。会议过程中在同一个石墨文档上做纪要,保证纪要可以收集全所有的笔记和行动计划。如果是关联会议,则使用上次相关的石墨文档进行追加内容(保持事件连贯性、完整性)。-
|
||||
(b)半小时的会议设置为 25 分钟,一小时的会议设置成 50 分钟,留有冗余量应付需要换地方等临时情况,保证所有的会议不会有成员迟到的现象。
|
||||
|
||||
|
||||
对于领域驱动设计,小浩子 同学提到了要特别关注可变项和不变项的分离:
|
||||
|
||||
|
||||
领域驱动设计确实是写出合适的代码结构的一项训练,程序员会不由自主地按照自己的习惯,也就是按照计算机运行逻辑去设计代码,这样的代码很容易陷入难以维护的坑。在开始动手写代码之前跟用户交流清楚,理解设计的概念、流程、使用场景、特殊情况,这些都很重要。另外我特别关注的一点是可变项和不变项的分离,因为我们的业务场景对可扩展性要求很高。
|
||||
|
||||
|
||||
经验越丰富的程序员,越能体会到“走进客户”的重要性,关于这一点,David Mao 同学提到:
|
||||
|
||||
|
||||
我做了好多年的软件测试,前几年和销售一起去谈客户,才深深地体会到客户声音的重要性。客户关注的才是真需求,产品经理和开发想出来的很多是伪需求,很多不是客户想要的功能。
|
||||
|
||||
|
||||
感谢同学们的精彩留言。在下一个模块中,我将为你分享“自动化”这个原则的具体应用。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,117 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
加餐 你真的了解重构吗?
|
||||
今天(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 中的菜单。估计这也是很多人就此把书放下,觉得重构不过如此的原因。
|
||||
|
||||
所以,行业里流传着各种关于重构的误解,多半是没有理解这些重构手法的含义。
|
||||
|
||||
重构,本质上就是一个“微操作”的实践。如果你不能理解“微操作”的含义,自然是无法理解重构的真正含义,也就不能理解为什么说“大开大合”的重写并不在重构的范畴之内。
|
||||
|
||||
我在《大师级程序员的工作秘笈》这篇文章中曾经给你介绍过“微操作”,每一步都很小,小到甚至在很多人眼里它都是微不足道的。
|
||||
|
||||
重构,也属于微操作的行列,与我们介绍的任务分解结合起来,你就能很好地理解那些重构手法的含义了:你需要把做的代码调整分解成若干可以单独进行的“重构”小动作,然后,一步一步完成它。
|
||||
|
||||
比如,服务类中有一个通用的方法,它并不适合在这个有业务含义的类里面,所以,我们打算把它挪到一个通用的类里面。你会怎么做呢?
|
||||
|
||||
大刀阔斧的做法一定是创建一个新的通用类,然后把这个方法复制过去,修复各种编译错误。而重构的手法就会把它做一个分解:
|
||||
|
||||
|
||||
添加一个新的通用类,用以放置这个方法;
|
||||
在业务类中,添加一个字段,其类型是新添加的通用类;
|
||||
搬移实例方法,将这个方法移动到新的类里面。
|
||||
|
||||
|
||||
得益于现在的 IDE 能力的增强,最后一步,按下快捷键,它就可以帮我们完成搬移和修改各处调用的工作。
|
||||
|
||||
在这个分解出来的步骤里,每一步都可以很快完成,而且,每做完一步都是可以停下来的,这才是微操作真正的含义。这是大刀阔斧做法做不到的,你修改编译错误的时候,你不知道自己需要修改多少地方,什么时候是一个头。
|
||||
|
||||
当然,这是一个很简单的例子,大刀阔斧的改过去也无伤大雅。但事实上,很多稍有规模的修改,如果不能以重构的方式进行,常常很快就不知道自己改到哪了,这也是很多所谓“重写”项目面临的最大风险,一旦开始,不能停止。
|
||||
|
||||
你现在理解了,重构不仅仅是一堆重构手法,更重要的是,你需要有的是“把调整代码的动作分解成一个个重构小动作”的能力。
|
||||
|
||||
重构地图
|
||||
|
||||
下面我准备给你提供一张关于重构的知识地图,帮你了解它与周边诸多知识之间的关系,辅助你更好地理解重构。
|
||||
|
||||
学习重构,先要知道重构的定义。关于这点,Martin Fowler 给出了两个定义,一个名词和一个动词。
|
||||
|
||||
|
||||
重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
|
||||
|
||||
重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
|
||||
|
||||
|
||||
之所以要了解重构的定义,因为重构的知识地图就是围绕着这个定义展开的。
|
||||
|
||||
首先,我们要对软件的内部结构进行调整,第一个要回答的问题是,我们为什么要调整。Martin Fowler 对于这个问题的回答是:代码的坏味道。
|
||||
|
||||
代码的坏味道,在我看来,是这本书给行业最重要的启发。很多人常常是无法嗅到代码坏味道的,因此,他们会任由代码腐坏,那种随便添加 if 或标记的做法就是嗅不出坏味道的表现。
|
||||
|
||||
我经常给人推荐《重构》这本书,但我也常常会补上一句,如果你实在没有时间,就去看它的第三章《代码的坏味道》。
|
||||
|
||||
顺便说一下,对比两版的《重构》,你会发现它们在坏味道的定义上有所差异,在新版的《重构》中,可变数据(Mutable Data)、循环语句(Loops)都定义成了坏味道,如果你不曾关注近些年的编程发展趋势,这样的定义着实会让人为之震惊。但只要了解了函数式编程的趋势,就不难理解它们的由来了。
|
||||
|
||||
换句话说,函数式编程已然成为时代的主流。如果你还不了解,赶紧去了解。
|
||||
|
||||
我们接着回到重构的定义上,重构是要不改变软件的可观察行为。我们怎么知道是不是改变了可观察行为,最常见的方式就是测试。
|
||||
|
||||
关于测试,我在“任务分解”模块已经讲了很多,你现在已经可以更好地理解重构、TDD 这些概念是怎样相互配合一起的了吧!
|
||||
|
||||
再来,重构是要提高可理解性,那重构到什么程度算是一个头呢?当年重构讨论最火热的时候,有人给出了一个答案:重构成模式(Refactoring to Patterns)。当然,这也是一本书的名字,有兴趣的话,可以找来读一读。
|
||||
|
||||
我个人有个猜想,如果这个讨论可以延续到2008年,等到 Robert Martin 的《Clean Code》出版,也许有人会提“重构成 Clean Code”也未可知。所以,无论是设计模式,亦或是 Clean Code,都是推荐你去学习的。
|
||||
|
||||
至此,我把重构的周边知识整理了一番,让你在学习重构时,可以做到不仅仅是只见树木,也可看见森林。当然,重构的具体知识,还是去看 Martin Fowler 的书吧!
|
||||
|
||||
总结时刻
|
||||
|
||||
总结一下今天的内容。今天我介绍了一个大家耳熟能详的概念:重构。不过,这实在是一个让人误解太多的概念,大家经常认为调整代码就是在做重构。
|
||||
|
||||
重构,本质上就是一堆微操作。重构这个实践的核心,就是将调整代码的动作分解成一个一个的小动作,如果不能理解这一点,你就很难理解重构本身的价值。
|
||||
|
||||
不过,对于我们专栏的读者而言,因为大家已经学过了“任务分解”模块,理解起这个概念,难度应该降低了很多。
|
||||
|
||||
既然重构的核心也是分解,它就需要大量的锤炼。就像之前提到任务分解原则一样,我在重构上也下了很大的功夫做了专门的练习,才能让自己一小步一小步地去做。但一个有追求的软件工匠不就应该这样锤炼自己的基本功吗?
|
||||
|
||||
如果今天的内容你只记住一件事,那请记住:锤炼你的重构技能。
|
||||
|
||||
最后,我想请你分享一下,你对重构的理解。欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,247 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
总复习 重新审视“最佳实践”
|
||||
你好,我是郑晔。
|
||||
|
||||
我承诺的正文内容已经全部交付给你,恭喜你完成了整个专栏的学习!希望通过对这些内容的学习,你已经对“如何做好软件”有了一个全新的认识。
|
||||
|
||||
在这个专栏中,我给你讲了很多行业中的最佳实践,比如:测试、持续集成等等,但因为这个专栏叙述方式的关系,一些有关联的实践被放到了不同的模块下讲解。
|
||||
|
||||
所以在这一讲中,我们将按照最佳实践的维度重新审视这些内容。我会将这些知识重新串联起来,帮你做一个对专栏的整体复习。
|
||||
|
||||
产品
|
||||
|
||||
做产品,很多时候是面向不确定性解决问题。目前这方面最好的实践是“精益创业”。对于精益创业的最简单的理解就是“试”。试也有试的方法,精益创业提出了一个“开发(build)- 测量(measure)- 认知(learning)”这样的反馈循环,通过这个循环得到经过验证的认知(Validated Learning)。
|
||||
|
||||
既然是对不确定产品特性的尝试,最好的办法就是低成本地试。在精益创业中,最小可行产品(MVP)就是低成本的试法。最小可行产品,就是“刚刚好”满足用户需求的产品。理解这个说法的关键在于用最小的代价,尝试可行的路径。
|
||||
|
||||
在产品的打磨过程中,可以采用用户测试的方式,直接观察用户对产品的使用。作为程序员,我们要尽可能吃自家的狗粮,即便你做的产品不是给自己使用的产品,也可以努力走近用户。
|
||||
|
||||
|
||||
精益创业-
|
||||
相关阅读:《06 | 精益创业:产品经理不靠谱,你该怎么办?》
|
||||
|
||||
最小可行产品(MVP)-
|
||||
相关阅读:《19 | 如何用最小的代价做产品?》
|
||||
|
||||
用户测试、验证产品特性、吃自家狗粮-
|
||||
相关阅读:《26 | 作为程序员,你也应该聆听用户声音 》
|
||||
|
||||
|
||||
需求
|
||||
|
||||
当我们确定做一个产品功能时,怎么描述需求也是很重要的。产品列表式的需求描述方式最容易出现问题的地方在于,看不清需求的全貌。
|
||||
|
||||
用户故事是一个好的需求描述方式:作为一个什么角色,要做什么样的事,以便达成一种怎样的效果。
|
||||
|
||||
在用户故事中,验收标准是非常重要的一环。即便不是采用用户故事描述需求,也依然建议先将验收标准定义清楚。
|
||||
|
||||
开发团队对需求的理解普遍偏大,基本上都是一个主题。在开发之前,先将需求拆分成小粒度的。衡量一个用户故事拆分是否恰当,一个标准是 INVEST 原则。有了拆分出来的用户故事,就可以进行估算了,估算的过程也是对需求加深理解的过程,过大的用户故事应该再次拆分。
|
||||
|
||||
当我们有了拆分之后的需求,就可以对需求进行优先级讨论了。先做重要性高的事,而不是一股脑地去做所有的需求。只有分清了需求的优先级,才能方便地对需求进行管理。
|
||||
|
||||
|
||||
用户故事-
|
||||
相关阅读:《04 | 接到需求任务,你要先做哪件事? 》
|
||||
|
||||
需求的分解与估算-
|
||||
相关阅读:《17 | 程序员也可以“砍”需求吗?》
|
||||
|
||||
需求管理、优先级-
|
||||
相关阅读:《18 | 需求管理:太多人给你安排任务,怎么办?》
|
||||
|
||||
|
||||
持续集成
|
||||
|
||||
在开发中,写出代码并不是终点,我们要把代码集成起来。集成要经常做,改动量越小,集成就可以做得越频繁,频繁到每次提交都去集成,这就是持续集成。
|
||||
|
||||
持续集成发展到今天已经是一套完整的开发实践。想要做好持续集成,你需要记住持续集成的关键是“快速反馈”。
|
||||
|
||||
|
||||
怎样快速得到反馈。
|
||||
怎样反馈是有效的。
|
||||
|
||||
|
||||
持续集成,可以继续延展,将生产部署也纳入其中,这就是持续交付。如果持续交付,再向前一步,就可以同产品验证结合起来。
|
||||
|
||||
持续交付的关键点,是在不同的环境验证发布包和自动化部署。不同的环境组成了持续交付的构建流水线,而自动化部署主要是 DevOps 发挥能力的地方。持续交付的发展,让交付物从一个简单的发布包变成了一个拥有完整环境的 Docker 镜像。
|
||||
|
||||
持续集成和持续交付可以将诸多的实践贯穿起来:单元测试、软件设计、任务分解、主分支开发、DevOps 等等。所以,如果一个公司希望做过程改进,持续集成是一个好的出发点。
|
||||
|
||||
|
||||
持续集成发展史-
|
||||
相关阅读:《05 | 持续集成:集成本身就应该是写代码的一个环节》
|
||||
|
||||
快速反馈-
|
||||
相关阅读:《24 | 快速反馈:为什么你们公司总是做不好持续集成?》
|
||||
|
||||
持续集成,贯穿诸多实践-
|
||||
相关阅读:《答疑解惑 | 持续集成,一条贯穿诸多实践的主线 》
|
||||
|
||||
持续交付-
|
||||
相关阅读:《32 | 持续交付:有持续集成就够了吗?》
|
||||
|
||||
与产品结合:持续验证-
|
||||
相关阅读:《答疑解惑 | 持续集成、持续交付,然后呢? 》
|
||||
|
||||
|
||||
测试
|
||||
|
||||
测试是一个典型的程序员误区,很多程序员误以为测试只是测试人员的事。理解了软件变更成本,知道了内建质量之后,我们就应该清楚,测试应该体现在全部的开发环节中。这一思想在开发中的体现就是自动化测试。
|
||||
|
||||
想要写好自动化测试,需要先理解测试金字塔,不同的测试运行成本不同。为了让软件拥有更多的、覆盖面更广的测试,需要多写单元测试。
|
||||
|
||||
编写测试的方式有很多,一种实践是测试驱动开发(TDD)。先写测试,然后写代码,最后重构,这就是 TDD 的节奏:红——绿——重构。测试驱动开发的本质是测试驱动设计,所以,编写可测试的代码是前提。
|
||||
|
||||
要想做好 TDD,一个重要的前提是任务分解,分解到非常小的微操作。学会任务分解,是成为优秀程序员的前提条件。
|
||||
|
||||
想写好测试,需要懂得好测试是什么样子的,避免测试的坏味道。好测试有一个衡量标准:A-TRIP。
|
||||
|
||||
我们不只要写好单元测试,还要站在应用的角度写测试,这就是验收测试。验收测试现在比较成体系的做法是行为驱动开发(BDD),它让你可以用业务的语言描述测试。
|
||||
|
||||
|
||||
单元测试、自动化测试、蛋卷和冰淇淋模型-
|
||||
相关阅读:《12 | 测试也是程序员的事吗?》
|
||||
|
||||
测试驱动开发-
|
||||
相关阅读:《13 | 先写测试,就是测试驱动开发吗?》-
|
||||
相关阅读:《14 | 大师级程序员的工作秘笈 》
|
||||
|
||||
测试练习-
|
||||
相关阅读:《15 | 一起练习:手把手带你拆任务 》
|
||||
|
||||
简单的测试、测试的坏味道、A-TRIP-
|
||||
相关阅读:《16 | 为什么你的测试不够好? 》
|
||||
|
||||
验收测试、写好验收测试用例-
|
||||
相关阅读:《32 | 持续交付:有持续集成就够了吗?》
|
||||
|
||||
外部系统测试,用接口隔离-
|
||||
相关阅读:《答疑解惑 | 如何在实际工作中推行新观念? 》
|
||||
|
||||
|
||||
编码与设计
|
||||
|
||||
编码和设计,是软件开发中最重要的一环。在我看来,编码和设计是一体,想清楚才能写出好代码。很多程序员追求写好代码,却没有一个很好的标准去衡量代码的好坏。结合着软件设计的一些理念,我给你一个编写好代码的进步阶梯,希望你能达到用业务语言编写代码的程度。
|
||||
|
||||
用业务语言编写代码,需要对软件设计有着良好的理解。提到设计,人们的初步印象是“高内聚低耦合”,但这是一个太过高度抽象的描述。SOLID 原则是一个更具实践性的指导原则,有了原则做指导,就可以更好地理解设计模式了。
|
||||
|
||||
有了基础原则,我们会知道将不同的代码划分开,这样就产生了分层。好的分层可以构建出抽象,而其他人就可以在这个抽象上继续发展。对于程序员来说,构建自己的核心抽象是最关键的一步。
|
||||
|
||||
目前构建核心抽象最好的方式是领域驱动设计(DDD),它将我们思考的起点拉到了业务层面,通过战略设计将系统按照不同的上下文划分开来,再通过战术设计,指导我们有效地设计一个个的领域模型。
|
||||
|
||||
但无论怎样做设计,前提是使用适当的技术解决适当的问题,不要把技术用复杂,把团队带入泥潭。
|
||||
|
||||
|
||||
业务语言写代码-
|
||||
相关阅读:《21 | 你的代码为谁而写?》
|
||||
|
||||
架构设计-
|
||||
相关阅读:《34 | 你的代码是怎么变混乱的? 》
|
||||
|
||||
分层、抽象-
|
||||
相关阅读:《35 | 总是在说MVC分层架构,但你真的理解分层吗?》
|
||||
|
||||
业务与技术-
|
||||
相关阅读:《36 | 为什么总有人觉得5万块钱可以做一个淘宝? 》
|
||||
|
||||
微服务-
|
||||
相关阅读:《37 | 先做好DDD再谈微服务吧,那只是一种部署形式 》
|
||||
|
||||
|
||||
项目准备
|
||||
|
||||
从头开始一个项目时,一个好的实践就是把一切都准备好。迭代0就是这样一个把迭代准备好的实践,从需求到技术,做好充分的准备工作再开启项目,你会显得从容不迫。在技术方面,迭代0最重要的准备工作就是构建脚本,它是后续很多工作的基础,比如,持续集成。
|
||||
|
||||
|
||||
迭代0,做基础的准备-
|
||||
相关阅读:《10 | 迭代0: 启动开发之前,你应该准备什么?》
|
||||
|
||||
构建脚本,让项目一开始就自动化-
|
||||
相关阅读:《30 | 一个好的项目自动化应该是什么样子的? 》
|
||||
|
||||
|
||||
其余的最佳实践
|
||||
|
||||
除了几个花大篇幅介绍的最佳实践,我们还提到了很多不同的最佳实践。
|
||||
|
||||
DoD
|
||||
|
||||
完成的定义(DoD),是一个确保合作各方理解一致的实践。它是一个清单,由一个个检查项组成,每个检查项都是实际可检查的。有了 DoD,做事就只有两种状态:完成和未完成。
|
||||
|
||||
|
||||
完成的定义,DOD-
|
||||
相关阅读:《03 | DoD价值:你完成了工作,为什么他们还不满意?》
|
||||
|
||||
|
||||
站会
|
||||
|
||||
站会,一种轻量级的会议形式,用来同步每天发生的事情。一般来说,只说三件事:昨天做了什么,今天打算做什么,遇到了什么问题。
|
||||
|
||||
|
||||
站会-
|
||||
相关阅读:《22 | 轻量级沟通:你总是在开会吗? 》
|
||||
|
||||
|
||||
看板
|
||||
|
||||
看板,一种项目管理工具, 将正在进行的工作可视化。通过看板,可以发现团队正在进行工作的很多问题。看板有实体和电子之分,可以根据自己的项目特点进行选择。
|
||||
|
||||
|
||||
看板-
|
||||
相关阅读:《23 | 可视化:一种更为直观的沟通方式 》
|
||||
|
||||
|
||||
回顾会议
|
||||
|
||||
回顾会议,是一种复盘实践,让团队成员对一个周期内发生的事情进行回顾。回顾会议一般分为讲事实、找重点和制定行动项三个部分。但在开始回顾之前,会先进行安全检查,确保每个人都能放心大胆地说真话。
|
||||
|
||||
|
||||
回顾会议-
|
||||
相关阅读:《25 | 开发中的问题一再出现,应该怎么办? 》
|
||||
|
||||
回顾会议中的安全检查-
|
||||
相关阅读:《答疑解惑 | 持续集成,一条贯穿诸多实践的主线 》
|
||||
|
||||
|
||||
重构
|
||||
|
||||
重构,是程序员的基本功,把调整代码的动作分解成若干可以单独进行的“重构”小动作,一步步完成。重构的前提是识别代码的坏味道。保证代码行为不变,需要有测试配合,而重构的方向是,重构成模式(Refactoring to Patterns)。重构的过程和编写代码的过程最好结伴而行,最佳实践就是测试驱动开发。
|
||||
|
||||
|
||||
重构-
|
||||
相关阅读:《加餐 | 你真的了解重构吗?》
|
||||
|
||||
在测试驱动开发中重构-
|
||||
相关阅读:《13 | 先写测试,就是测试驱动开发吗?》
|
||||
|
||||
|
||||
分支开发
|
||||
|
||||
分支开发模型,是每个团队都要面临的问题。行业中有两种常见的分支模型,一种是基于主干的开发模型,一种是分支开发模型。分支开发符合直觉,却不是最佳实践。主分支开发模型是与其他实践配合最好的模式,但也需要开发者有着良好的开发习惯。如果并行开发多个功能,可以考虑 Feature Toggle 和 Branch by Abstraction。
|
||||
|
||||
|
||||
分支开发-
|
||||
相关阅读:《14 | 大师级程序员的工作秘笈 》
|
||||
|
||||
Feature Toggle 和 Branch by Abstraction-
|
||||
相关阅读:《答疑解惑 | 如何分解一个你不了解的技术任务? 》
|
||||
|
||||
|
||||
Fail Fast
|
||||
|
||||
Fail Fast 是一个重要的编程原则:遇到问题,尽早报错。不要以构建健壮系统为由,兼容很多奇怪的问题,使得 Bug 得以藏身。
|
||||
|
||||
|
||||
Fail Fast-
|
||||
相关阅读:《27 | 尽早暴露问题: 为什么被指责的总是你? 》
|
||||
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,135 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
总复习 重新来“看书”
|
||||
你好,我是郑晔。
|
||||
|
||||
我们继续复习,在上一讲中,我从最佳实践的角度带领大家回顾了专栏里的一些内容。这一讲,我们换个复习的角度。在专栏进行的过程中,有一些同学注意到我引用了大量的书籍,提出让我把这些书做一个整理。
|
||||
|
||||
Wei 同学提到:
|
||||
|
||||
|
||||
有一个小建议: 在每一个主题模块的小结中,把文章中提到的书籍做一个书单方便读者。
|
||||
|
||||
|
||||
刘晓林 同学提到:
|
||||
|
||||
|
||||
郑老师在专栏中推荐了很多非常好的书籍作为参考,可否考虑在某一期中,将这些参考书籍整理成一个书单,按照专栏的主题做个小分类,然后每本书简单点评两句作为领读内容。希望在专栏的结束语之前可以看到这个书单。
|
||||
|
||||
|
||||
Y024 同学甚至在留言中帮我总结了一个小清单,而有人也在豆瓣上做出了一个豆列,罗列了专栏中提到的一些书。
|
||||
|
||||
在今天这一讲中,我就站在“看书”的视角,带着你进行一次复习。这些书大多是在我个人成长过程中,给我留下深刻印象的。
|
||||
|
||||
我希望你在结束这个专栏学习之后,开启的是另外一段学习历程,用这些书提升自己的水平,夯实自己的基础知识。学习了这个专栏之后,你拥有了一个新的知识结构,再来看这些书就会有一种全新的体验。
|
||||
|
||||
此外,在这次的内容中,我会提到几本专栏中没有提到的书,算是给你在学习路上的一个补充。我还制作了一个豆列,方便你去找到这些书。
|
||||
|
||||
编码实践
|
||||
|
||||
|
||||
如果你想详细学习如何写好代码,我推荐你去读 Robert Martin 的《代码整洁之道》(Clean Code),这本书几乎覆盖了如何把代码写好的方方面面。
|
||||
|
||||
《实现模式》是一本关于如何写好代码的书,更具体一点是,编写别人能够理解的代码。它的作者 Kent Beck 是许多软件开发实践的开创者。但 Kent Beck 的写作能力一般,他的很多作品被埋没了。只有细细品味,才能体会到 Kent Beck 深厚的功力。
|
||||
|
||||
我提升自己编码水平的理解是从《程序设计实践》(The Practice of Programming)这本书开始的,这本书的作者是 Brian Kernighan 和 Rob Pike,这两个人都出身于大名鼎鼎的贝尔实验室,参与过 Unix 的开发。
|
||||
|
||||
如果你想从日常开发中提升自己的效率,可以读一下《卓有成效的程序员》。假如你不曾思考过这个问题,这本书会让看到一些不同的工作方式,我也给这本书写过一篇书评。不过,这本书里的技巧太具体了,所以,有一些已经有些过时了。
|
||||
|
||||
|
||||
设计
|
||||
|
||||
|
||||
SOLID 原则是一种面向对象软件设计原则。早在1995年,Robert Martin 就提出了这些设计原则的雏形,然后在他的《敏捷软件开发:原则、实践与模式》这本书中,比较完整地阐述了这五个原则,后来,他有把这些原则进一步整理,成了今天的 “SOLID”。有了设计原则做基础,这本书后面讲了设计模式,理解起来就容易多了。虽然书名是关于敏捷的,但这是一本讲设计的书。
|
||||
|
||||
设计和架构有什么区别?2017年,Robert Martin 出版了《架构整洁之道》(Clean Architecture),他在其中告诉我们,二者没有区别。所以,这也是一本关于设计的书,给出了 Robert Martin 对设计的最新理解。你可以把它看成《敏捷软件开发:原则、实践与模式》的修订版。
|
||||
|
||||
《设计模式》不推荐阅读,它是设计模式的开山之作,但它的起点是 Erich Gamma 的博士论文,其写作风格偏向学术,而且中文版翻译得也很一般。这里将它罗列出来只是因为其历史重要性。如果你想学习设计模式,现在有一些更容易入门的书,比如《Head First 设计模式》。
|
||||
|
||||
Martin Fowler 的《企业应用架构模式》将软件开发当时常见的解决方案汇集成模式,今天看来很多模式已经习以为常,但当年出场可是技惊四座的。从这本书的名字你不难看出,它出版的年代是企业级开发盛行的年代。Martin Fowler 一直认为这本书没有写完,希望能够继续更新,但不知道何时能看到这本书的新版。
|
||||
|
||||
《Unix 编程艺术》也是一本讲软件设计的书,只不过,它选择的切入点是 Unix 中的设计,从中你可以学到“只做一件事,把它做好”、“文本化”等编程理念,有助于你改善日常的工作。这样的书,也就只有 Eric Raymond 这样沉浸编程几十年的人才能写出来。
|
||||
|
||||
|
||||
工程实践
|
||||
|
||||
|
||||
Kent Beck 有一本知名的软件工程之作《解析极限编程》(Extreme Programming Explained),它介绍了一种软件开发方法:极限编程。但更重要的是,今天很多主流的软件开发最佳实践都是从这里出来的。这本书可以理解成诸多最佳工程实践的总纲。
|
||||
|
||||
Martin Fowler 在1999年写下软件行业的名著《重构:改善既有代码的设计》(Refactoring: Improving the Design of Existing Code),把重构这个小圈子实践带到了大众视野。2018年底,Martin Fowler 时隔近20年后,又写出了《重构》第二版。把他对这些年行业发展的新理解融入到重构实践中。重构应该有个目标,这个目标就是“重构成模式”,而这也是一本专门的书:《重构与模式》(Refactoring to Patterns)。
|
||||
|
||||
《测试驱动开发》是 Kent Beck 为世人展示 TDD 做法的一本书。它好的地方需要自己体会,Kent Beck 并没有显式的讲出来,比如:任务分解。
|
||||
|
||||
Jez Humble 和 Dave Farley 的《持续交付》(Continuous Delivery)让持续集成再进一步,将生产环境纳入了考量。乔梁,他是《持续交付》这本书的中文版译者,而且在这本书出版近十年后,他自己写了《持续交付 2.0》,把自己多年来关于持续交付的新理解整理了进去。
|
||||
|
||||
说到遗留代码和测试,我推荐一本经典的书:Michael Feathers 的《修改代码的艺术》(Working Effectively with Legacy Code),从它的英文名中,你就不难发现,它就是一本关于遗留代码的书。如果你打算处理遗留代码,也建议你读读这本书。这本书我也写过书评,你可以了解一下我对它看法。
|
||||
|
||||
|
||||
领域驱动设计
|
||||
|
||||
|
||||
Eric Evans 2003年写了《领域驱动设计》,向行业介绍一下 DDD 这套方法论,立即在行业中引起广泛的关注。但实话说,Eric 在知识传播上的能力着实一般,这本关于 DDD 的开山之作,其写作质量却难以恭维,想要通过它去学好 DDD,是非常困难的。所以,在国外的技术社区中,有很多人是通过各种交流讨论逐渐认识到 DDD 的价值所在,而在国内 ,DDD 几乎没怎么掀起波澜。
|
||||
|
||||
2013年,在 Eric Evans 出版《领域驱动设计》十年之后,DDD 已经不再是当年吴下阿蒙,有了自己一套比较完整的体系。Vaughn Vernon 将十年的精华重新整理,写了一本《实现领域驱动设计》,普通技术人员终于有机会看明白 DDD 到底好在哪里了。所以,你会发现,最近几年,国内的技术社区开始出现了大量关于 DDD 的讨论。
|
||||
|
||||
因为《实现领域驱动设计》实在太厚,Vaughn Vernon 又出手写了一本精华本《领域驱动设计精粹》,让人可以快速上手 DDD,这本书也是我向其他人推荐学习 DDD 的首选。
|
||||
|
||||
|
||||
产品与需求
|
||||
|
||||
|
||||
精益创业是 Eric Ries 最早总结出来的。他在很多地方分享他的理念,不断提炼,最终在2011年写成一本同名的书:《精益创业》。如果说精益创业是理论,《精益创业实战》这本书则给了你一个操作流程。
|
||||
|
||||
Mike Cohn 是敏捷理念的一个重要传播者,我们在讲测试金字塔时,提到了他的著作《Scrum敏捷软件开发》(Succeeding with Agile)。敏捷开发有两大流派:一派是工程实践,另一派是管理实践。如果你对 Scrum 这类管理实践感兴趣,可以读一下这本书。
|
||||
|
||||
如果你对用户故事这个话题感兴趣,推荐阅读 Mike Cohn 的两本书《用户故事与敏捷方法》(User Stories Applied)和《敏捷软件开发实践 估算与计划》(Agile Estimating and Planning)。
|
||||
|
||||
|
||||
开发文化
|
||||
|
||||
|
||||
软件行业里有一本名著叫《人月神话》,这算是软件开发领域第一本反思之作。今天,我们讨论的很多词汇都出自这本书,比如,没有银弹、焦油坑等等。虽然这本书出版于1975年,但其中提到的问题,依然困扰着今天的程序员。
|
||||
|
||||
开源概念的提出者 Eric Raymond,他的《大教堂与集市》推开了开源大门。今天开源软件已经成为程序员日常工作的一部分,但如果没有 Eric Raymond 这些人的努力,我们还必须与复杂的企业级软件搏斗。了解一下开源的历程,可以帮助你更好地理解今天的幸福。
|
||||
|
||||
程序员应该如何做,Robert Martin 也写了一本书《程序员的职业素养》(Clean Coder),其中对大多数程序员最重要的一点建议是,说“不”。
|
||||
|
||||
|
||||
软件开发拾遗
|
||||
|
||||
|
||||
高德纳的《计算机程序设计艺术》肯定是一套程序员都知道,但没几个人读完的书。算法的讲解经过几十年已经有了很好的发展,如果学算法,肯定有更好的选择。如果你想看图灵奖获得者如何从根源上思考问题,不妨找来这套书来翻翻。
|
||||
|
||||
《快速软件开发》(Rapid Development),不推荐阅读。在这本书中,作者首次提出了解决集成问题的优秀实践:Daily Build,每日构建。通过这个名字,我们便不难看出它的集成策略,即每天集成一次。其中很多实践在当时是先进的,但今天看来有些落伍了。如果你只想从中收获一些理念性的东西,可以去读读。
|
||||
|
||||
《C 程序设计语言》《Unix 编程环境》等出自贝尔实验室大师级程序员之手,他们的书都值得一读,其中的内容今天看来可能有些过时,但他们解决问题的方式和手法却值得慢慢品味。
|
||||
|
||||
我在讲淘宝技术变迁时,提到了《淘宝技术这十年》,这本书算不上经典,但可以当作休闲读物。
|
||||
|
||||
|
||||
技术之外
|
||||
|
||||
|
||||
管理大师彼得·德鲁克有一本经典著作《卓有成效的管理者》,虽然标题上带着管理者几个字,但在我看来,这是一本告诉我们如何工作的书,每个人都可以读一下。
|
||||
|
||||
尤瓦尔·赫拉利的《人类简史》或《未来简史》,是我第一次学到“大历史观”这个说法,历史不再是一个个单独的历史事件,而是一个有内在逻辑的发展脉络。
|
||||
|
||||
《从一到无穷大》是一本著名科普著作,它向我们介绍了20世纪以来的科学进展。作者乔治·伽莫夫既是热宇宙大爆炸模型的提出者,也是生物学上最早提出“遗传密码”模型的人。虽然这本书是1947年出版的,但以现在社会的整体科学素养,还是有必要读读这本书的。
|
||||
|
||||
史蒂芬·柯维(Stephen Richards Covey)的《高效能人士的七个习惯》,其中的理念我在专栏两个不同的地方提到过,一个是讲以终为始时,那段关于智力创造的论述,另一个是讲优先级时提到的艾森豪威尔矩阵。这本书值得每个人阅读,很多程序员欠缺的就是这些观念性的东西。
|
||||
|
||||
很多程序员都是科幻小说迷,编程和科幻,这两个都是需要想象力的领域。刘慈欣的《三体》,不说它给 IT 行业带来的丰富的词汇表吧,作为科幻小说来说,它就是一流的,值得阅读。它会让你仰望星空,打开思维。如果你对科幻小说有兴趣,推荐阅读阿西莫夫的《银河帝国》系列,这是科幻小说界的扛鼎之作,你会看到,一部出版于1942年的书里就有大数据的身影。
|
||||
|
||||
对于程序员来说,最好的工作状态就是进入心流,它会让你忘我工作。如果你对心流的概念感兴趣,可以去读米哈里·契克森米哈赖的著作《心流》,这位作者就是心流概念的提出者。
|
||||
|
||||
|
||||
好,今天的复习就到这里,你有哪些经典的书可以推荐给这个专栏的同学呢?欢迎在留言区写下分享你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,121 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
答疑解惑 持续集成、持续交付,然后呢?
|
||||
你好,我是郑晔。
|
||||
|
||||
“自动化”模块落下了帷幕,这是四个工作原则中最为“技术化”的一个,也应该是程序员们最熟悉的主题。
|
||||
|
||||
我从软件外部的自动化——工作流程讲起,让你能够把注意力专注于写好代码;讲到了软件内部的自动化——软件设计,选择恰当的做法,不贪图一时痛快,为后续的工作挖下深坑。
|
||||
|
||||
既然是一个大家都熟悉的话题,同学们自然也有很多经验分享,也有很多人看到了与自己不同的做法,提出了各种各样的问题。
|
||||
|
||||
在今天的答疑中,我选出了几个很有意思的问题,让大家可以在已有内容上再进一步延伸。
|
||||
|
||||
问题1:持续交付是否可以再做扩展?
|
||||
|
||||
毅 同学提到
|
||||
|
||||
|
||||
为达到有效交付的目标,用户能够尽早参与,我觉得也是比较重要的一环。从生产环境获得结果,是否可再做扩展,将用户也作为一个独立节点?-
|
||||
——《32 | 持续交付:有持续集成就够了吗?》
|
||||
|
||||
|
||||
西西弗与卡夫卡 同学提到
|
||||
|
||||
|
||||
持续交付可以是持续交付最大价值,那范围就不仅限于软件,还可以进一步延伸到运营,比如说结合ABTest,自动选择最有效的运营策略,为用户交付最大价值。-
|
||||
——《32 | 持续交付:有持续集成就够了吗?》
|
||||
|
||||
|
||||
两位同学能提出这样的想法,说明真的是已经理解了持续集成和持续交付,所以,才能在这个基础上继续延伸,思考进一步的扩展。
|
||||
|
||||
我在专栏中一直在强调,别把自己局限在程序员这个单一的角色中,应该了解软件开发的全生命周期。在前面的内容中,我讲了不少做产品的方法,比如,MVP、用户测试等等。如果只把自己定位在一个写代码的角色上,了解这些内容确实意义不大,但你想把自己放在一个更大的上下文中,这些内容就是必须要了解的。
|
||||
|
||||
回到两位同学的问题上,如果说我们一开始把持续集成定义成编写代码这件事的完成,那持续交付就把这个“完成”向前再推进了一步,只有上线的代码才算完成。
|
||||
|
||||
但放在整个软件的生命周期来说,上线并不是终点。把系统送上线,不是最终目的。那最终目的是什么呢?
|
||||
|
||||
回到思考的起点,我们为什么要做一个软件?因为我们要解决一个问题。那我们是不是真正的解决了问题呢?其实,我们还不知道。
|
||||
|
||||
在《06 | 精益创业:产品经理不靠谱,你该怎么办?》这篇文章中,我给你讲了做产品的源头。如果是采用精益创业的模式工作,我们构建产品的目的是为了验证一个想法,而怎么才算是验证了我们的想法呢?需要搜集各种数据作为证据。
|
||||
|
||||
所以,我曾经有过这样的想法,精益创业实际上是一种持续验证,验证想法的有效性,获得经过验证的认知(Validated Learning)。
|
||||
|
||||
现在有一些获取验证数据的方式,比如,西西弗与卡夫卡 同学提到的 AB 测试。
|
||||
|
||||
AB 测试是一种针对两个(或多个)变体的随机试验,常常用在 Web 或 App 的界面制作过程中,分别制作两个(或多个)版本,让两组(或多组)成分相同的用户随机访问不同版本,收集数据,用以评估哪个版本更好。每次测试时,最好只有一个变量。因为如果有多个变量,你无法确认到底是哪个变量在起作用。
|
||||
|
||||
AB 测试的概念在其他领域由来已久。2000年,Google 的工程师率先把它应用在了软件产品的测试中,时至今日,它已经成为很多产品团队常用的做事方式。
|
||||
|
||||
AB 测试的前提是用户数据搜集。我在《09 | 你的工作可以用数字衡量吗?》这篇文章给你介绍了在开发过程中,用数字帮助我们改善工作。在产品领域实际上更需要用数字说话,说到这里,我“插播”一个例子。
|
||||
|
||||
很多产品经理喜欢讲理念、讲做法,偏偏不喜欢讲数字。用数字和产品经理沟通其实是更有说服力的。
|
||||
|
||||
我就曾经遇到过这样的事情,在一个交易平台产品中,一个产品经理创造性地想出一种新的订单类型,声称是为了方便用户,提高资金利用率。如果程序员接受这个想法,就意味着要对系统做很大的调整。
|
||||
|
||||
我问了他几个问题:第一,你有没有统计过系统中现有的订单类型的使用情况?第二,你有没有了解过其他平台是否支持这种订单类型呢?
|
||||
|
||||
产品经理一下子被我问住了。我对第一个问题的答案是,除了最基础的订单类型之外,其他的订单类型用得都很少,之前做的很多号称优化的订单类型,实际上没有几个人在用。
|
||||
|
||||
第二个问题我的答案是,只有极少数平台支持类似的概念。换句话说,虽然我们想得很美,但教育用户的成本会非常高,为了这个可能存在的优点,对系统做大改造,实在是一件投资大回报小的事,不值得!
|
||||
|
||||
再回到我们的问题上,一旦决定了要做某个产品功能,首先应该回答的是如何搜集用户数据。对于前端产品,今天已经有了大量的服务,只要在代码里嵌入一段代码,收集数据就是小事一桩。
|
||||
|
||||
前端产品还好,因为用户行为是比较一致的,买服务就好了,能生成标准的用户行为数据。对于后端的数据,虽然也有各种服务,但基本上提供的能力都是数据的采集和展示,一些所谓的标准能力只是 CPU、内存、JVM 之类基础设施的使用情况。对于应用来说,具体什么样的数据需要搜集,还需要团队自己进行设计。
|
||||
|
||||
说了这些,我其实想说的是,持续验证虽然是一个好的想法,但目前为止,还不如持续集成和持续交付这些已经有比较完整体系做支撑。想做到“持续”,就要做到自动化,想做到自动化,就要有标准化支撑,目前这个方面还是“八仙过海各显神通”的状态,没法上升到行业最佳实践的程度。
|
||||
|
||||
其实道理上也很简单,从一无所有,到持续集成、再到持续交付,最后到持续验证,每过一关,就会有大多数团队掉队。所以,真正能达到持续交付的团队都少之又少,更别提要持续验证了。
|
||||
|
||||
问题2:Selenium 和 Cucumber 的区别是什么?
|
||||
|
||||
没有昵称 同学提到
|
||||
|
||||
|
||||
老师,Selenium 跟 Cucumber 有区别吗?-
|
||||
——《33 | 如何做好验收测试?》
|
||||
|
||||
|
||||
这是一个经常有人搞混的问题。为了让不熟悉的人理解,我先讲一点背景。
|
||||
|
||||
Selenium 是一个开源项目,它的定位是浏览器自动化,主要用于 Web 应用的测试。它最早是 Jason Huggins 在2004年开发出来的,用以解决 Web 前端测试难的问题。
|
||||
|
||||
之所以取了 Selenium 这个名字,主要是用来讽刺其竞争对手 Mercury 公司开发的产品。我们知道,Mercury 是水银,而 Selenium 是硒,硒可以用来解水银的毒。又一个程序员的冷幽默!
|
||||
|
||||
Cucumber 的兴起伴随着 Ruby on Rails 的蓬勃发展,我们在之前的内容中提到过,Ruby on Rails 是一个改变了行业认知的 Web 开发框架。所以,Cucumber 最初主要就是用在给 Web 应用写测试上,而 Selenium 刚好是用来操作浏览器的,二者一拍即合。
|
||||
|
||||
于是,你会在很多文章中看到,Cucumber 和 Selenium 几乎是同时出现的,这也是很多人对于二者有点傻傻分不清楚的缘由。
|
||||
|
||||
讲完了这些背景,结合我们之前讲的内容,你就不难理解了。Cucumber 提供的是一层业务描述框架,而它需要有自己对应的步骤实现,以便能够对被测系统进行操控;而 Selenium 就是在 Web 应用测试方面实现步骤定义的一个非常好的工具。
|
||||
|
||||
问题3:IntelliJ IDEA 怎么学?
|
||||
|
||||
hua168 同学提到
|
||||
|
||||
|
||||
IDEA 怎么学呢?是用到什么功能再学?还是先看个大概,用到时再仔细看?-
|
||||
——《30 | 一个好的项目自动化应该是什么样子的?》
|
||||
|
||||
|
||||
一个工具怎么学?我的经验就是去用。我没有专门学过 IntelliJ IDEA,只是不断地在使用它。遇到问题就去找相应的解决方案。
|
||||
|
||||
如果说在 IDEA 上下过功夫,应该是在快捷键上。我最早写代码时的风格应该是鼠标与键盘齐飞,实话说,起初也没觉得怎么样。加入 ThoughtWorks 之后,看到很多人把快捷键运用得出神入化,那不是在写一行代码,而是在写一片代码。我当时有一种特别震惊的感觉。
|
||||
|
||||
我自以为在写代码上做得已经相当好了,然而,有人却在你很擅长的一件事上完全碾压了你,那一瞬间,我感觉自己这些年都白学了。这种感觉后来在看到别人能够小步重构时又一次产生了。
|
||||
|
||||
看到差距之后,我唯一能做的,就是自己下来偷偷练习。幸好,无论是快捷键也好,重构也罢,都是可以单独练习的。花上一段时间就可以提高到一定的水平。后来,别人看我写代码时也会有类似的感觉,我会安慰他们说,不要紧,花点时间练习就好。
|
||||
|
||||
其实,也有一些辅助的方法可以帮助我们练习,比如,我们会给新员工发放 IntelliJ IDEA 的快捷键卡片,写代码休息之余,可以拿来看一下;再比如,IntelliJ IDEA 有一个插件叫 Key Promoter X,如果你用鼠标操作,它会给你提示,帮你记住快捷键。有一段时间,我已经练习到“看别人写代码,脑子里能够完全映射出他在按哪个键”的程度。
|
||||
|
||||
写代码是个手艺活,要想打磨手艺,需要看到高手是怎么工作的,才不致于固步自封。如果你身边没有这样的人,不如到网上搜一些视频,看看高手在写代码时是怎么做的,这样才能找到差距,不断提高。
|
||||
|
||||
好,今天的答疑就到这里,你对这些问题有什么看法呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,126 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
答疑解惑 持续集成,一条贯穿诸多实践的主线
|
||||
“沟通反馈”模块又告一段落了,在这个模块中,我们把自己与真实世界的距离又拉近了一步。
|
||||
|
||||
一方面,我们强调主动沟通,把自身的信息更有效地传达出去;另一方面,我们也重视反馈,让真实世界的信息,更多地回到我们身边。同学们分享了很多经验,也提出了不少的问题。
|
||||
|
||||
在今天的答疑中,我选择了几个非常好的问题,从不同的角度丰富一下之前讲解的内容。
|
||||
|
||||
问题1:单元测试做不好,是否会影响到 CI 的效果?
|
||||
|
||||
毅 同学提到
|
||||
|
||||
|
||||
如果单元测试做的不到位,或者不满足A-TRIP,是不是执行CI的效果就会弱很多?-
|
||||
——《24 | 快速反馈:为什么你们公司总是做不好持续集成?》
|
||||
|
||||
|
||||
这是一个非常好的问题,问到了各种实践之间的关联。我们在前面用了两讲的篇幅介绍了持续集成这个实践,为什么要做持续集成以及如何做好持续集成。
|
||||
|
||||
在自动化模块,我们还会在这个基础之上继续延伸,介绍持续交付,这些内容是从操作的层面上进行介绍,都是对单一实践的描述。
|
||||
|
||||
利用这次答疑的机会,我再补充一个维度,谈谈实践之间的关联。
|
||||
|
||||
持续集成的价值在于,它是一条主线,可以将诸多实践贯穿起来。也就是说,想要真正意义上做好持续集成,需要把周边的很多实践都要做好。
|
||||
|
||||
我们具体地说一下这些实践。但请记住我们说过的,做好持续集成的关键是,快速反馈。
|
||||
|
||||
比如,我们想要做好 CI,需要有一个稳定的开发分支,所以,最好采用主开发分支的方式。想用好主分支开发,最好能够频繁提交;而频繁提交需要你的任务足够小,能够快速完成;将任务拆解的足够小,需要你真正懂得任务分解。要想在一个分支上开发多个功能,那就需要用 Feature Toggle 或者 Branch by Abstraction。
|
||||
|
||||
|
||||
|
||||
在这条线上,你有很多机会走错路。比如,你选择了分支开发模式,合并速度就不会太快,一旦反馈快不了,CI 的作用就会降低;再者,如果不能频繁提交,每次合并代码的周期就会变长,一旦合并代码的周期变长,人们就会倾向于少做麻烦事,也就会进一步降低提交的频率,恶性循环就此开启。
|
||||
|
||||
同样,即便你懂得了前面的道理,不懂任务分解,想频繁提交,也是心有余而力不足的。而多功能并行开发,则会让你情不自禁地想考虑使用多分支模型。
|
||||
|
||||
我们再来看另外一条线,也就是这个问题中提到的测试。
|
||||
|
||||
想做好 CI,首先要有可检查的东西,什么是可检查的东西,最简单的就是编译、代码风格检查,这些检查可以无条件加入构建脚本。但更重要的检查,应该来自于测试,而要想做好 CI,我们要有测试防护网。
|
||||
|
||||
|
||||
|
||||
什么叫测试防护网呢?就是你的测试要能给你提供一个足够安全的保障,这也就意味着你要有足够多的测试。换个更技术点的术语来说,就是要有足够高的测试覆盖率。
|
||||
|
||||
如果测试覆盖率不够,即便提交了代码,CI 都通过了,你对自己的代码依然是没有信心的,这就会降低 CI 在你的心中的地位。
|
||||
|
||||
如果想有足够高的测试覆盖率,你就要多写单元测试。我们在前面讲过测试金字塔了,上层测试因为很麻烦,你不会写太多,而且很多边界条件,通过上层测试是覆盖不到的,所以,测试覆盖率在经过了初期的快速提升后,到后期无论如何是提上不去的。要想提升测试覆盖率,唯有多写单元测试。
|
||||
|
||||
要想多写单元测试,就需要编写可以测试的代码,而要想编写可测的代码,就要懂软件设计,将系统之间耦合解开。
|
||||
|
||||
通过上面的分析,你已经看出来做好持续集成,让它完全发挥自己的价值,需要做的工作还是相当多的。但也请别灰心,实际上,我做咨询时,很多团队就是从持续集成下手,开始改造他们的软件开发过程。
|
||||
|
||||
这是一个“以终为始”的思路,先锁定好目标,就是要把持续集成做好,然后围绕着这个目标改进其他做得欠佳的方面。比如,原来是多分支的,就先固定一个主分支,然后,逐步改变大家的开发习惯,让他们进入单分支的开发状态。
|
||||
|
||||
再比如,原来没有测试,那就在 CI 上先加一个最低的测试覆盖率,然后定期去提高,比如,第一周是10%,第二周是20%,这样一步一步地提高,开发团队可以一边开发新东西,一边为既有代码补测试。等到覆盖率到了一定程度,提高有困难了,团队就可以考虑怎么改进设计了。
|
||||
|
||||
所以,CI 作为一个单独的实践,本身是很简单的,但它可以成为提纲挈领的主线,帮助团队不断改善自己的开发过程。
|
||||
|
||||
问题2:老板参加复盘,不敢说真话怎么办?
|
||||
|
||||
grass10happy 同学提到
|
||||
|
||||
|
||||
复盘是不是最好是团队内部进行,每次老板参加复盘,好像就没人说出真话了。-
|
||||
——《25 | 开发中的问题一再出现,应该怎么办?》
|
||||
|
||||
|
||||
感谢 grass10happy 同学这个提问,把我因为篇幅原因省掉的一个部分给挽救了回来。
|
||||
|
||||
回顾会议的目的在于改进,它不仅仅在于让大家参与进来,更重要的是让团队成员能够敞开心扉,把问题暴露出来。暴露问题,是改进的前提条件。
|
||||
|
||||
我在《27 | 尽早暴露问题: 为什么被指责的总是你?》这篇文章中说过了,对于很多人来说,敢不敢暴露问题是个心理问题。你会发现,同事之间聊天,普遍是没有任何压力的,你几乎可以放心大胆地谈论各种问题,而一旦有领导在,很多顾虑就会出现了。
|
||||
|
||||
于是,问题就变成了怎么能够让大家放心地把问题暴露出来,一个办法就是设置一个安全的环境。
|
||||
|
||||
怎么设置一个安全的环境呢?对于标准的回顾会议来说,第一步应该是做安全性检查。
|
||||
|
||||
先由大家投票,最简单的方式是就是,给当前的环境打分。你觉得可以畅所欲言就打1分,你觉得还好,就打0分,如果你觉得不方便表达,比如,你看领导在,很多问题不适合反馈,就打-1。
|
||||
|
||||
每个与会者都投出属于自己的一票。然后,主持人根据投票结果决定回顾会议是否进行,比如,有人投-1就不能继续。
|
||||
|
||||
会议能继续固然好,一旦会议不能继续,可以有多种解决方案。比如,把在场职位最高的人请出去,这个人可能就是老板。老板也许心里很不爽,但在这个过程中,大家都是按照规则在办事,并不存在对谁另眼相待的情况。
|
||||
|
||||
当老板离席之后,我们再进行一轮投票,判断环境是否变得安全了。如此反复,也许要进行几轮投票,直到大家觉得安全了。
|
||||
|
||||
当然,也有可能进行多轮,有人始终觉得不安全,那可能最好的选择是,取消今天的回顾会议,换个时间地点从头再来。而项目负责人则需要私下里解决一下团队内心安全的问题。
|
||||
|
||||
通过安全性检查之后,我们才会进入回顾会议的正式环节,具体内容在正文中已经讲过了,这里就不再赘述了。
|
||||
|
||||
问题3:国内的技术信息落后吗?
|
||||
|
||||
One day 提到
|
||||
|
||||
|
||||
老师能否多多介绍一下技术方面的网站之类的,新技术发展见闻之类的,或者技术总结方面。国内的技术基本都多少有些滞后。-
|
||||
——《23 | 可视化:一种更为直观的沟通方式》
|
||||
|
||||
|
||||
这个问题让我感觉自己一下子回到了好多年前。我刚入行的那会,学习新知识确实要多看看英文网站,当时的信息传播速度不快,中文技术网站不多。
|
||||
|
||||
但在今天,显然已经不是这样了,如果只是想获得最新的技术信息,我在《23 | 可视化:一种更为直观的沟通方式》这篇文章中介绍了 InfoQ 和技术雷达,这上面的信息量已经很丰富了。你再只要稍微看几个网站,关注几个公众号,各种信息就会送到你面前。
|
||||
|
||||
所以,你根本不用担心会错过什么新技术,反倒是信息量太大,需要好好过滤一下。
|
||||
|
||||
国内程序员真正落后的不是信息,而是观念。
|
||||
|
||||
我讲的很多内容是软件工程方面的,以我对国内外程序员的了解来看,发达国家的程序员在这些内容的普及上,要比国内程序员好很多。
|
||||
|
||||
国内程序员的平均水平,大多停留在实现一个功能的理解上,而发达国家的程序员做事要专业许多。所以,以专业素养来看,国内程序员还有很大的提升空间。
|
||||
|
||||
在经济学里有“边际效用递减法则”(The Law Of Diminishing Marginal Utility),说的是当你手里某一物品总数越来越多时,新增一个单位该物品所获得的效用通常会越来越少。
|
||||
|
||||
当你的技术知识积累到一定程度时,还采用原来的学习方式,就很难获得真正意义上的提高,这是很多人抱怨 IT 行业不好混的原因。
|
||||
|
||||
同时,这也是我开设这个专栏的初衷,希望给大家一些不同的视角,一些新的前进动力。
|
||||
|
||||
好,今天的答疑就到这里。我想请你分享一下,你是怎么理解这些问题的呢?欢迎在留言区写下你的想法。
|
||||
|
||||
感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给你的朋友。
|
||||
|
||||
|
||||
|
||||
|
@@ -0,0 +1,109 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
结束语 少做事,才能更有效地工作
|
||||
你好,我是郑晔。
|
||||
|
||||
在这个专栏里,我讲过很多东西,几乎涉及到软件开发的方方面面,但有一个重要的方面,我却从来没有说过,那就是算法。
|
||||
|
||||
因为我一直把它当做不言而喻的基本功,认为每个程序员都应该掌握。在我们专栏的结束语中,我就用这个没有涉及过的话题来开篇吧!
|
||||
|
||||
算法的差异
|
||||
|
||||
排序算法是每个程序员都会学到的内容,大家对各种算法也是如数家珍:插入排序、冒泡排序、归并排序、堆排序、快速排序等等。我们也知道各个算法的复杂度,比如,插入排序是 O(n^2),快速排序平均情况下是 O(nlogn)等等。
|
||||
|
||||
你有没有想过一个问题,不同算法的复杂度本质差别到底是什么呢?我们就以插入排序和快速排序为例,为什么快速排序要比插入排序快呢?
|
||||
|
||||
我不打算做算法分析,直接公布答案:因为做比较的次数少。为什么同样的排序,比较次数会有差异呢?因为插入排序每次循环只关注当前的目标,循环之间没有关系,而快速排序在做不同划分时,上一次的结果对下一次有助力,因此它省下了不少的比较次数。
|
||||
|
||||
明白了这个道理,再来看所谓的算法优化,其实就是尽可能利用已知的信息,少做不必要的事。
|
||||
|
||||
再来看一个常见的面试题,给你一堆数,找出前100个。很多人直觉就会想到排序,然后选出前100个。这种做法固然可行,但一定是做多了,因为这里需要的是找出前100个数,而不是要100个有序的数字,更不是要所有的数都有序。
|
||||
|
||||
说到这里,你就知道了,只要把数据划分开就好,并不需要排序,如果划分点不是第100个元素,就向着100所在的方向继续划分就好。
|
||||
|
||||
计算机是最擅长处理繁琐重复工作的,即便如此,我们依然要做算法优化,原因是当数据规模大到一定程度时,不同复杂度的算法差别就非常明显了。算法没用好,计算机硬件再好,也是徒劳的。
|
||||
|
||||
有一则《计算机程序设计艺术》作者高德纳(Donald Knuth)的轶事,他年轻时参加算法大赛,用最差的系统击败了诸多对手,拿到算法执行效率的冠军,凭借的就是其强大的算法优化功力。
|
||||
|
||||
对于计算机,算法尚且如此重要,我们面对工作时何尝不是如此呢!
|
||||
|
||||
有效工作
|
||||
|
||||
《10x 程序员工作法》,也许有的同学最初看到这个标题就急急加入了,以为会从这个专栏中学习到一些“以一抵十”的编程技法,对不起,我彻底让你失望了。我非但没讲太多编程的技法,甚至还从各种角度劝你少写代码:无论是向产品经理提问题,还是让你在前面多考虑设计。
|
||||
|
||||
难道不是做得越多才越高效吗?
|
||||
|
||||
插入排序并不会因为干的活多,就比快速排序得到更高的评价,因为它们比的是谁排得快。工作效率高,不是因为代码写得多,而是有效工作做得多。
|
||||
|
||||
如果 CPU 都被无效指令占据了,哪有时间执行有效指令呢?即使你很忙碌,但工作进展依然是收效甚微,因为无效工作占据了你太多的大脑,让你不能聚焦在正经事上,当然就是效率不高了。
|
||||
|
||||
其实,这个专栏的内容在我脑子里已经盘旋很多年了。不过,即便在专栏筹备期,我已经备了很多篇稿子之后,我依然没有找到一个准确的说法能够描绘内心的想法。
|
||||
|
||||
我想过“程序员的职业素养”,但似乎这会让专栏朝着职场行动指南的方向努力;我想过“高效工作”,但实际上我也不打算讨论那些工作技巧。直到上线日期临近,我的编辑实在受不了我的拖延,坐下来与我交流了很久,我才终于找到了内心的那个词:有效。
|
||||
|
||||
我在这个专栏真正探讨的主题是,有效工作。
|
||||
|
||||
有效工作,需要我们把力量聚焦到正确的地方,做本质复杂度(Essential Complexity)的事情,少做无意义的事情。
|
||||
|
||||
我曾经在一个大公司做咨询,按照他们的统计,线上60%的代码从来没有运行过。我们都知道,一多半的代码增加的可不只是一多半的工作量,团队可能需要的是几倍甚至几十倍的心力去维护它。
|
||||
|
||||
当然,有效工作最终没有成为这个专栏的名字,而用了更有个性的《10x 程序员工作法》。这个名字也不错,因为在我看来,很多程序员做的是负功,比如,写那60%代码的程序员。只要能做到有效工作,效率自然会高出业界平均水平很多。
|
||||
|
||||
怎么才能有效工作呢?我在专栏中已经给你讲了很多,小结一下就是:
|
||||
|
||||
|
||||
拓展自己的上下文,看到真正的目标,更好地对准靶子,比如,多了解用户,才不至于做错了方向;站在公司的层面上,才知道哪个任务优先级更高;站在行业的角度,而不局限于只在公司内成为高手,等等。
|
||||
|
||||
去掉不必要的内容,减少浪费,比如,花时间分析需求,不做非必要的功能;花时间做好领域设计,别围着特定技术打转;花时间做好自动化,把精力集中在编码上,等等。
|
||||
|
||||
|
||||
要想有效工作,有两点非常重要。一方面,意识上要注意自己工作中无效的部分。这就像一个开关,拨过去就好了。所以,读这个专栏,有人常有恍然大悟的感觉,也有人觉得很简单。
|
||||
|
||||
很多时候,你只是不知道,就像我在专栏中提到,要问产品经理问题,这是很多人没想过的。每篇文章后面的那一句总结,就是这样的开关,拨过去就好。
|
||||
|
||||
另一方面,要构建自己关于软件开发的知识体系,这是要花时间积累的。在这个专栏中,我给你讲了很多最佳实践,就是让你知道,在某些方面,有人已经做得很好了,花时间学习,比自己从头摸索好很多。
|
||||
|
||||
这就像所有的数学公式一样,理论上你都可以自行推导,但肯定不如从教科书上学得快。
|
||||
|
||||
藏经阁目录
|
||||
|
||||
虽然我讲了这么多内容,但实际上,因为篇幅的关系,这只是冰山一角。其实,我给你讲的这部分内容并不是具体的知识,而是告诉了你哪些东西要去学习,给了你一张学习地图,把各种知识贯串了起来。
|
||||
|
||||
我曾与朋友打趣道,我的专栏实际上是藏经阁的目录,真正的经书还要等你自己去参悟。只不过,有一个人把这些经书之间的知识连接给你补齐了。这些连接恰恰是在学习相关内容时,让我苦思冥想许久的。
|
||||
|
||||
大约一年前(2018年4月),极客时间编辑找到我,问我是否有兴趣在极客时间开个专栏,作为“得到”重度用户的我,一直对知识服务很感兴趣。有这样的机会让我体验,我当然想试试,甚至最初给自己定下了写100篇的宏伟计划。
|
||||
|
||||
真正开始写,我才知道,在繁忙的日常工作之余,坚持写作还是一件很有挑战的事,今天看来,100篇的目标显得那么无知无畏。
|
||||
|
||||
不过,也正是因为压缩到一半左右的篇幅,在专栏后面的部分,我才极大地提高了知识密度,比如,微服务和DDD,这两个可以分别写成一个系列内容的话题,我用一篇文章就将其精华和知识脉络提炼呈现了出来。
|
||||
|
||||
因为我想尽我所能,帮助大家构建起一个软件开发的知识体系,让你在未来遇到问题时,知道可以在哪个方面进一步加强。希望这个专栏真的起到帮你理清思路,答疑解惑的作用。
|
||||
|
||||
还记得我在开篇词中的最后一段话吗?
|
||||
|
||||
|
||||
也许在这个专栏的最后,你发现自己并不认同我的原则,却能够用自己的原则来与我探讨,那么,恭喜你,因为那是最美妙的事情!
|
||||
|
||||
|
||||
不知道你是否形成了自己的原则呢?欢迎与大家分享。因为它代表着你已经形成了自己的知识体系。与我讲了些什么相比,你学到了什么才是一件更重要的事。
|
||||
|
||||
希望在学习了这个专栏之后,你可以用自己的工作原则做更多本质复杂度的事情,减少无意义的时间消耗。
|
||||
|
||||
其实,这个专栏的最大收益人是我自己,感谢这次的专栏之旅,我终于强行治疗了我的拖延症,把自己对于有效工作的思考完整地整理了出来,那些在脑子里模糊的印象现在终于有了一个完整的体系。这个体系就是我在专栏里提到的工作原则,现在我可以更好地表达自己的想法了。
|
||||
|
||||
不过,这个专栏对我而言也是有遗憾的。因为我想表达的内容很多,给大家打开更多大门的同时,也给很多同学留下了更多的疑问。
|
||||
|
||||
有些同学期待在某个方面再深入细节地讲一下,比如,DDD,那可是值得再写一个专栏的主题。限于这个专栏的主题和篇幅关系,我没办法深入展开,只能对大家说声抱歉了。
|
||||
|
||||
如果以后有机会,我会再来与你分享我对软件开发的理解,这次的《10x程序员工作法》之旅就暂告一段落了!
|
||||
|
||||
再见!
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user