WEBVTT

1
00:00:00.000 --> 00:00:04.766
在一个虚构的场景里，一位维护者在决定调查哪个失败的任务之前，

2
00:00:04.766 --> 00:00:06.036
先查看运行结果。

3
00:00:06.036 --> 00:00:10.008
一个“未通过”的筛选器显示了 9 号和 11 号。

4
00:00:10.350 --> 00:00:13.832
维护者打开 11 号，发现它还在运行。

5
00:00:13.832 --> 00:00:18.047
于是回到列表，改用一张手写的失败运行编号清单。

6
00:00:19.083 --> 00:00:22.066
用户发现从一件过去发生的事开始，而不是推销。

7
00:00:22.066 --> 00:00:24.506
不要问“你会用我们的智能仪表盘吗”，

8
00:00:24.506 --> 00:00:27.895
而要说“带我回顾一下你上一次查看运行结果的经过”。

9
00:00:28.233 --> 00:00:31.183
然后，把你看到的和你推断的分开。

10
00:00:31.183 --> 00:00:35.055
观察到的：筛选器包含了一个仍在运行的任务。

11
00:00:35.055 --> 00:00:40.586
推断的：运行中和失败的任务混在一起，可能会干扰对失败的调查。

12
00:00:40.933 --> 00:00:44.704
还要记下你暂时不能下结论的：这种情况有多常见，

13
00:00:44.704 --> 00:00:47.655
以及单独的筛选器是不是更合适的界面。

14
00:00:48.700 --> 00:00:53.665
亲手写下问题陈述：维护者无法单独找出失败的运行，

15
00:00:53.665 --> 00:00:57.802
因为现在的筛选器也包含了正在运行的任务。

16
00:00:58.150 --> 00:01:00.823
然后逐个分句对照观察记录检查。

17
00:01:00.823 --> 00:01:06.348
“所有人都需要即时提醒”要删掉：不管是“所有人”还是“提醒”，

18
00:01:06.348 --> 00:01:07.774
都没有证据支持。

19
00:01:08.816 --> 00:01:13.372
这份简短的产品需求文档记录这些决定：一个纯函数，

20
00:01:13.372 --> 00:01:17.358
从编号和状态组成的列表里，取出失败的编号。

21
00:01:17.358 --> 00:01:20.775
不做的事情包括网页界面、通知和重试。

22
00:01:21.116 --> 00:01:26.585
升序排列是一个设计决定，不是访谈发现，产品需求文档里也这样写明。

23
00:01:26.933 --> 00:01:32.507
验收标准是：返回状态恰好为 failed 的编号，

24
00:01:32.507 --> 00:01:38.526
按升序、不重复；没有失败时返回空列表；并且不改动输入。

25
00:01:38.526 --> 00:01:43.431
它取代了那句含糊的请求：显示需要关注的任务。

26
00:01:44.466 --> 00:01:45.054
先预测。

27
00:01:45.054 --> 00:01:47.701
生成出来的一个候选实现看起来很合理，

28
00:01:47.701 --> 00:01:50.643
它保留了所有不是 passed 的状态。

29
00:01:50.983 --> 00:01:54.689
在观察到的运行上，它返回 9 和 11。

30
00:01:54.689 --> 00:01:56.913
验收标准要求的只有 9。

31
00:01:56.913 --> 00:02:00.990
问题出在对“需要关注”的理解上，而不是语法。

32
00:02:02.032 --> 00:02:07.303
把判断条件改成状态等于 failed，然后重跑同一组检查。

33
00:02:07.649 --> 00:02:14.079
这些公开检查覆盖了混合状态、空输入、没有失败、排序、重复和另一个编号，

34
00:02:14.079 --> 00:02:16.099
并且要求输入保持不变。

35
00:02:16.099 --> 00:02:19.038
在修复后的版本上，它们全部通过。

36
00:02:20.082 --> 00:02:26.677
把这个决定追溯一遍：观察、问题陈述、验收标准、失败的检查，

37
00:02:26.677 --> 00:02:27.814
以及修复。

38
00:02:28.149 --> 00:02:32.986
排序规则的测试通过了，并不会让它变成访谈得来的证据。

39
00:02:32.986 --> 00:02:35.962
验收测试也无法说明产品是有用的。

40
00:02:36.998 --> 00:02:42.880
AC1 说明了什么必须成立，却没有说明怎样把这份工作交给智能体。

41
00:02:42.880 --> 00:02:44.902
一句“实现 AC1”，

42
00:02:44.902 --> 00:02:50.048
等于让智能体在一步之内完成调研、选择、规划、编写和评判，

43
00:02:50.048 --> 00:02:52.254
而你只能看到最后的结果。

44
00:02:52.598 --> 00:02:58.069
所以，把它拆成五个阶段：调研、提议、规划、实现、测试。

45
00:02:58.069 --> 00:03:03.134
每一个停点，都由一个人读这一阶段的产出，然后决定：

46
00:03:03.134 --> 00:03:07.389
继续、收窄任务、回到更早的阶段，或者停下。

47
00:03:07.731 --> 00:03:13.499
智能体可以为每个阶段起草产出，但它不能替你做这些决定，

48
00:03:13.499 --> 00:03:16.276
也不能自己检查自己的结果。

49
00:03:17.314 --> 00:03:21.637
到了提议阶段，假设智能体给出三个选项。

50
00:03:21.637 --> 00:03:25.960
A：直接改坏掉的那个函数里的判断条件。

51
00:03:25.960 --> 00:03:30.283
改动最小，却拿掉了课堂要用的那个反例。

52
00:03:30.631 --> 00:03:34.299
B：另外加一个 failed_ids 函数，

53
00:03:34.299 --> 00:03:36.967
用同一个 verify 来检查。

54
00:03:36.967 --> 00:03:39.301
只多一个函数，反例也保留了。

55
00:03:39.301 --> 00:03:42.303
C：在运行列表页面上加一个状态选项。

56
00:03:42.303 --> 00:03:44.470
可网页界面属于不做的事情。

57
00:03:44.814 --> 00:03:45.812
你选了 B。

58
00:03:45.812 --> 00:03:50.633
拒绝 C 是根据产品需求文档做出的范围决定，不是技术判断，

59
00:03:50.633 --> 00:03:53.127
所以就把它当作范围决定记下来。

60
00:03:54.164 --> 00:03:56.336
到了规划阶段，每一步都指向一项检查，

61
00:03:56.336 --> 00:03:59.594
而这些检查是在任何代码出现之前，根据 AC1 写好的。

62
00:03:59.594 --> 00:04:01.766
只保留状态正好是 failed 的：

63
00:04:01.766 --> 00:04:05.145
对应 mixed 和 no-failures 两项检查。

64
00:04:05.145 --> 00:04:08.282
每个编号只返回一次：对应 duplicate 检查。

65
00:04:08.631 --> 00:04:11.157
升序排列：对应 sort 检查。

66
00:04:11.157 --> 00:04:14.315
没有运行记录时：对应 empty 检查。

67
00:04:14.315 --> 00:04:18.736
使用输入，而不是固定的值：对应 other-id 检查。

68
00:04:18.736 --> 00:04:22.841
还有，不改动输入：对应那条报告“输入被修改”的断言。

69
00:04:23.181 --> 00:04:27.039
如果哪一步没有检查，要么先写检查，要么去掉这一步。

70
00:04:27.039 --> 00:04:29.508
规划阶段，正是你收窄任务的地方。

71
00:04:30.547 --> 00:04:34.019
到了实现阶段，边界本身就是请求的一部分：

72
00:04:34.019 --> 00:04:39.401
只写 failed_ids，放在一个新文件里，不导入任何东西，

73
00:04:39.401 --> 00:04:40.963
也不改动验收检查。

74
00:04:40.963 --> 00:04:45.303
没有模型的时候，课程为这个停点写好了三个候选实现。

75
00:04:45.647 --> 00:04:51.577
把每一个都当作文本读一遍，在测试之前判断它有没有越出边界。

76
00:04:51.577 --> 00:04:54.849
越出边界的，即使通过了也要拒绝。

77
00:04:54.849 --> 00:04:59.961
在课堂活动里，有一个候选通过了测试，却越出了边界。

78
00:04:59.961 --> 00:05:00.983
是哪一个？

79
00:05:00.983 --> 00:05:03.028
测试为什么没有发现？

80
00:05:03.028 --> 00:05:04.664
这要你自己去找。

81
00:05:05.014 --> 00:05:08.536
然后测试，记下退出码和第一个失败的检查，

82
00:05:08.536 --> 00:05:10.826
或者那一行全部通过的提示。

83
00:05:10.826 --> 00:05:15.229
在我们对坏版本的运行里，mixed 检查最先失败，

84
00:05:15.229 --> 00:05:17.518
所以其余五项根本没有运行。

85
00:05:17.518 --> 00:05:22.626
一次失败之后，后面的检查是没有运行，而没有运行不等于通过。

86
00:05:23.664 --> 00:05:26.076
一次失败会告诉你该回到哪里。

87
00:05:26.076 --> 00:05:30.210
错误的判断条件，就像我们运行坏版本时看到的那样，

88
00:05:30.210 --> 00:05:31.933
会让你回到实现阶段。

89
00:05:31.933 --> 00:05:35.550
规划从没覆盖到的条款，会让你回到规划阶段。

90
00:05:35.550 --> 00:05:38.823
误读了需求，会让你回到提议或调研阶段。

91
00:05:38.823 --> 00:05:41.752
还有，绝不要靠修改检查来修复失败。

92
00:05:42.097 --> 00:05:47.542
用几行字记下这一轮：每个阶段的任务说明、得到了什么产出，

93
00:05:47.542 --> 00:05:49.876
以及你在每个停点的决定。

94
00:05:49.876 --> 00:05:55.321
因为第一步还不够小而在规划之后停下，也是一个正当的结果。

95
00:05:56.364 --> 00:06:00.492
在阶段之间，每个阶段都交给下一个阶段一份文件。

96
00:06:00.492 --> 00:06:03.723
调研文件保存你留下的、带位置的论断；

97
00:06:03.723 --> 00:06:06.595
规划文件保存步骤和检查的对应表。

98
00:06:06.595 --> 00:06:11.979
下一个阶段在一个新的会话里开始，只读这份文件和它点名的路径，

99
00:06:11.979 --> 00:06:14.133
而不读产生它的那段对话。

100
00:06:14.133 --> 00:06:18.620
课程提供的调研技能和规划技能会返回这些论断和表格；

101
00:06:18.620 --> 00:06:20.954
停点上的决定仍然由你来做。

102
00:06:21.997 --> 00:06:24.917
下面是在项目仓库上走的同一轮。

103
00:06:24.917 --> 00:06:28.616
这份记录是课程写的，没有调用任何模型。

104
00:06:28.616 --> 00:06:34.846
验收标准是：所有者的总览页列出进行中的项目，不列出已归档的项目；

105
00:06:34.846 --> 00:06:37.961
已归档项目自己的页面仍然能打开。

106
00:06:38.297 --> 00:06:41.709
调研返回了四条论断，每条都带着位置。

107
00:06:41.709 --> 00:06:42.656
逐个打开。

108
00:06:42.656 --> 00:06:48.721
第四条说，总览页的查询已经跳过了已归档的项目，所以只需要改标签。

109
00:06:48.721 --> 00:06:51.943
可是查询里根本没有关于归档的条件。

110
00:06:51.943 --> 00:06:52.891
把它划掉。

111
00:06:53.230 --> 00:06:58.706
假如它留了下来，计划就只会改标签，已归档的项目也仍然会被列出。

112
00:06:58.706 --> 00:07:02.415
一条错误的调研论断，会变成一个错误的选项，

113
00:07:02.415 --> 00:07:05.417
再变成错误的计划步骤和错误的代码。

114
00:07:05.417 --> 00:07:09.833
在第一个停点就把它划掉，是这一轮里代价最小的修复。

115
00:07:10.180 --> 00:07:14.498
到了提议阶段，在查询里过滤，而不是在模板里隐藏：

116
00:07:14.498 --> 00:07:20.254
如果一个所有者的项目全都归档了，模板的空列表分支就永远不会执行，

117
00:07:20.254 --> 00:07:21.693
表格会是空白的。

118
00:07:21.693 --> 00:07:25.471
计划只改一行，并加两个测试，每个条款一个。

119
00:07:25.814 --> 00:07:30.113
接着，写测试时暴露出一个验收标准从没提到的情况：

120
00:07:30.113 --> 00:07:32.801
一个所有项目都已归档的所有者。

121
00:07:32.801 --> 00:07:36.742
这位所有者会看到“还没有项目”，这不是事实；

122
00:07:36.742 --> 00:07:41.221
而且只有手动输入项目地址，才能回到取消归档的按钮。

123
00:07:41.221 --> 00:07:43.192
这两个都不是代码问题。

124
00:07:43.192 --> 00:07:47.849
所以这一轮在规划之后停下，两个问题都退回到规格里去。

125
00:07:48.896 --> 00:07:51.250
把规格当作源代码来对待。

126
00:07:51.250 --> 00:07:56.937
给意图做版本管理，并且把它的改动和测试、代码放在一起评审。

127
00:07:56.937 --> 00:08:00.663
需求的改动应当指出受影响的测试和实现；

128
00:08:00.663 --> 00:08:03.998
代码的改动也仍然要满足当前的需求。

129
00:08:03.998 --> 00:08:07.920
每一项商定的改动，都记进规格的变更记录：

130
00:08:07.920 --> 00:08:12.627
改了哪条条款、影响哪些测试、为什么改、谁同意了。

131
00:08:12.963 --> 00:08:16.649
但文字并不会自动变得可执行、完整或者正确。

132
00:08:16.649 --> 00:08:21.214
一份错误的规格，可能产出彼此一致、却并不想要的代码。

133
00:08:21.214 --> 00:08:25.953
而一份 AI 给出的一致性报告，只是评审时的一项输入，

134
00:08:25.953 --> 00:08:27.533
不是正确性的证明。

135
00:08:28.579 --> 00:08:32.903
所以，从一件具体的、发生过的事出发，而不是从推销出发。

136
00:08:32.903 --> 00:08:34.505
给设计决定贴上标签。

137
00:08:34.505 --> 00:08:38.668
用验收标准来评判生成的代码，而不是看它显得有多合理。

138
00:08:39.013 --> 00:08:40.165
试一个问题。

139
00:08:40.165 --> 00:08:45.351
筛选器返回的记录是对的，但排序和约定的显示顺序不一样。

140
00:08:45.351 --> 00:08:47.079
这算满足需求了吗？

141
00:08:47.079 --> 00:08:50.344
写下预期的行为，以及接下来的检查。
