You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

93 lines
8.5 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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