[关闭]
@jtong 2017-07-19T08:38:43.000000Z 字数 1717 阅读 650

像机器一样思考(四-旧)—— 一图抵千言

教学 Graphic-Language


当我们把一个完整的功能拆解为一个个输入输出穷尽,互相独立的任务后,它是容易转化为代码了,可是这种方式并不容易思考规模更大的问题(光从哪来到哪去就够我们绕的)。把我们的大脑看成一台电脑,我们就是那种内存很低的电脑,问题规模一大,我们就会死机,然后就只能重启了。具体表现为我们思考时会觉得晕。每次晕的时候可能都重启了一下:)。

怎么办呢?其实也很简单,内存不够硬盘来凑。对我们的大脑来说,最常见的“硬盘”就是纸。而正如电脑的硬盘传输速度总是不如内存的,加了硬盘计算效率不一定快。我们需要一种对传输友好的编码方式,这种方式就是画图。

画图的规则

我们的画图方法受时序图启发而发明,具体的规则如下:

  1. 本图基本元素由方块和带箭头的线组成
  2. 一个方块只代表一个函数或一个代码块,通常是函数,方块中可以写字,可以表达函数是属于哪个类或哪个实例等信息。
  3. 指向方块的线代表该函数的输入,背离方块的线代表函数的输出。
  4. 数据流动的时间轴遵守先从左到右,再从上到下的顺序。
  5. 每一对输入输出(输入在上,输出在下)加一个方块,表达了一次函数调用。

举例:
比如下列代码:

  1. function c(){
  2. }
  3. function b(){
  4. c();
  5. }
  6. function a(){
  7. b();
  8. }
  9. a();

画成图是这个样子的

a-b-c.png-89.5kB

在这个图上我们可以清晰的看出来,函数a调用了函数b,函数b调用了函数c。而函数a自己,是在最顶层调用的,也就是所谓的程序入口。
整张图是从左往右表示时间顺序。

什么情况下既有从左到右,也有从上到下呢?比如下面这个代码:

  1. function b(){
  2. // b codes
  3. }
  4. function c(){
  5. // c codes
  6. }
  7. function a(){
  8. b();
  9. //a codes;
  10. c();
  11. //a codes;
  12. }

函数a先调用了函数b,然后再执行一段a里面的代码,再调了函数c,然后再执行了一段a里面的代码,然后返回。

a-b+a-c.png-115.1kB

正常的使用方式

我们正常使用这个实践的时候,这个过程是反过来的,我们可能先看画出了上面这张图。不过这回,我们要画的认真一点,为了后面可以导出任务列表,我需要加上标号,如下图:

(a-b)+(a-c) with detail.png-124.8kB

然后从这张图里按照标号导出任务列表,如下:

  1. #1 函数a
  2. 输入:
  3. paramX: TypaX
  4. 输出:
  5. bValue: TypeA
  6. #2 函数b
  7. 输入:
  8. paramY: TypeY
  9. 输出:
  10. cValue: TypeB
  11. #3 函数c
  12. 输入:
  13. paramZ: TypeZ
  14. 输出:
  15. aValue: TypeC

接着我们就可以照着任务列表写代码,如下:

  1. function b(){
  2. // b codes
  3. return result;
  4. }
  5. function c(){
  6. // c codes
  7. return result;
  8. }
  9. function a(){
  10. let bValue = b(paramX);
  11. //a codes;
  12. let cValue = c(paramZ);
  13. //a codes;
  14. return result;
  15. }

最棒的是,我们可以照着任务列表写出测试

  1. it("test case 1 for function b", () =>{
  2. let paramX = // TypeX的测试数据
  3. let actualBValue = b(paramX); // 调用b函数的实际返回值
  4. let exceptedBValue = // 调用b函数的期望的返回值
  5. expect(actualBValue).is(expectedBValue); //断言
  6. })
  7. // 以此类推...

所以,我们是在以测试驱动的方式做任务划分,你可以叫它测试驱动的任务切分。

如果要映射到测试,我们的任务列表就缺了一些东西,那就是所谓的测试用例。因为同一个函数可能有不同的测试用例,所以加上用例我们的任务列表应该长成这个样子:

  1. #1 函数a
  2. 输入:
  3. paramX: TypaX
  4. 输出:
  5. bValue: TypeA
  6. 测试用例:
  7. 用例1
  8. inputValue1
  9. ----
  10. outputValue1
  11. 用例2
  12. inputValue2
  13. ----
  14. outputValue2
  15. // 以此类推...

我们看到因为输入输出的顺序已经定好了,为了书写的速度,我们就省略了名字。如果你觉得不舒服也可以写上名字。如果你觉得这样太浪费时间,你可以把每组用例用一句话描述。毕竟一切为了实用嘛。

练习

请把上一篇的任务列表,画成图,并补上测试用例。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注