你好,我是徐昊,今天我们来继续学习 AI 时代的软件工程。
通过前面的学习,我们了解了如何使用大语言模型(Large Language Model,LLM)辅助软件开发,以及如何有效地提取软件开发中的关键知识——测试工序。从今天开始,我们来学习如何通过 LLM 辅助构建团队。
认知分歧
在第一部分知识工程的综述中,我们介绍了认知行为模式,并反复利用认知行为模式,解释了知识的学习、应用与传递。然而之前我们对于认知行为模式的应用,主要集中在个人视角。如果将团队看作一个整体,那么我们同样可以使用认知行为模式来理解团队行为。
而一旦站在团队视角,我们会面临一个非常重要的问题,那就是认知分歧(Cognitive Diffusion)。也就是对于同一个问题,团队中不同的成员,可能会处在完全不同的认知模式。
比如,对于如何在项目中完成用户故事指定的功能,在项目上完全胜任的成员可能处在庞杂的认知模式下(Complicated),他们完成任务的行为是:感知(理解用户故事的上下文,以及验收条件)- 分析(按照测试工序指引,分解任务)- 响应(依据任务列表逐步完成工作)。
项目上不完全胜任的成员可能处于复杂的认知模式下(Complex),他们完成任务的行为是:探测(尝试 spike 一下故事卡中某些不清楚的地方)- 感知(按照得到的结果,重新理解要如何完成整张卡片)- 响应(逐步试错,完成功能)。
而刚毕业没有什么编程经验的成员,可能完全就是混乱模式了。他们甚至不会仔细辨别到底要做什么功能,就被巨大的恐慌驱动,冲过去写代码了。那么带来的自然是大量的返工和修改。
除了不同的经验水平之外,认知分歧也会存在于不同的角色中。比如在业务分析师和开发之间,开发与测试之间都会存在认知分歧。认知分歧是无法消除的。这源于成员的不同经验水平、专业背景、个人偏好或是对问题的理解方式等等诸多原因。
认知分歧当然存在有益的一面,比如它能够带来多样的思考方式和解决方案。特别是在需要创新的领域中,趋同的认知行为也可能会让团队陷入僵局。但是,在更多的情况下,认知分歧会带来效率和质量上的严重损失。这个我们在前面的课程里,已经讲解过了。
那么对于软件开发而言,是否存在一个理想的认知行为呢?或者说,我们要怎么判断一个团队处于良好的认知行为模式下,而另外的一些团队则存在较大的问题呢?
理想的认知行为模式
在我看来,软件开发中的理想认知模式是存在的。下面是我在 20 年总结的理想的认知行为模式:
绝大部分活动处于有序的状态(清晰或庞杂),严格控制无序状态的成本。
尽可能地减少混乱的情况;
存在持续提升认知的手段;
在编写生产代码(Production Code)时,应该处于清晰认知模式;
检查软件的功能性质量时,应该处于清晰模式,极少情况处于庞杂模式;
诊断问题时,应该处在有序的认知模式下(清晰或庞杂),极少情况处于复杂模式;
按照大家现在对于认知行为模式的认识,这里的很多条要求都很容易理解。
我们挑几个重要的展开讲一讲。
首先是第一条,绝大部分活动处于有序的状态(清晰或庞杂),严格控制无序状态的成本。
这里需要强调的一个概念就是有序和无序,我们将认知行为模式分为有序——清晰和庞杂,以及无序——复杂和混沌两类。比较这两类认知行为模式就会发现,有序的行为模式是感知(sense)在先,而无序的行为模式则是行动在先,感知在后。对应到复杂的行为模式就是探测 - 感知,对应到混沌就是行为 - 感知。
这意味着什么呢?这意味着当我们处于有序的认知行为模式时,我们对于要解决的问题是有定义的,可能对于解决方案不太了解。而处于无序的认知行为时,我们甚至不清楚要解决的问题是什么。
举个例子,还是以编码为例,如果拿到的用户故事,存在明确的验收条件,且进行编码的人,对于架构知识和测试策略有充分的了解。那么他们在编写测试代码的时候,认知行为模式就可能是:感知(阅读用户故事的验收条件)—— 分析(通过测试工序,将验收条件分解)—— 响应(按照分解的结果编写测试)。这时,他们就处在有序的认知行为当中。
而如果进行编码的人,缺乏架构知识和对于测试策略的理解,那么他们就可能会进入复杂的认知模式:探测(随便编写一个功能测试)—— 感知(是否能有效地覆盖要编写的组件)—— 响应(编写测试)。
两者相对比,显然有序的认知模式可以带来更好的质量和效率。所以我们强调的第一点就是,在进入软件开发后,绝大部分的活动要处于有序的状态。只有有序才是能够管理的出发点。
那么符合这个条件的最佳实践有哪些呢?比如,正确实施的迭代交付,就完全符合这个要求。在迭代交付中,每一个迭代被看作是一个固定的时间窗(time box)。所有进入迭代的需求,需要在迭代开始前明确验收条件。也就是,要解决的问题进入迭代前,需要给出范围和验收的定义(感知 sense)。
需要注意的是,在迭代过程中,需求是不允许修改的,从而保证问题的定义在迭代过程中不变。这就有效地保证了,开发实践都可以处在有序的认知模式。而忽略了这一点,就无法保证是这样的情况了。所以站在认知行为的角度,很容易就可以发现,采纳的实践是否是走样的。
再比如,我们前面讲过的测试工序,也是一样的道理。对于如何使用架构和测试策略给出了明确的定义,从而减少了探测行为和对于架构的重新定义。前面对这个实践已经讲了很多了,这里不再重复。
那为什么要严格控制无序状态的成本呢?这是因为,当我们处于无序的活动时,不仅仅成本很高,而且时间不可控。仔细想一想无序的两个行为模式,无论是复杂还是混沌,实际上都缺乏对于待解决问题的理解。本质上讲,我们就是不会,不能胜任,我们就是在学习。学就要分学得会和学不会两种情况了。这意味着,我们可能无法完成要解决的问题。
那么站在管理的角度上来看,任何无序的认知活动,实际都是项目风险,不光是质量风险,更是进度风险。于是对于这类活动最常见的管理方式,就是卡住时间——给予一定的时间,如果无法解决,那么就要立即止损。
止损的方式包括换人、寻找外援等等。我想大家都有过看起来简单,实际是大坑,最后造成项目严重延期的经历。这实际上告诉我们,当我们处在无序认知的时候,不要相信自己的判断,理想的做法就是及时止损。
比如,敏捷社区中有一个常用实践叫 spike。它只指当开发人员碰到对于解决方案不清楚的情况时,停下手头的工作,给定一个探索时间。开始进入 spike 的时候,需要通知整个团队,告知存在一个瓶颈。而一旦超过规定的时间限制,需要马上停止并告知团队,这就是及时止损的做法。
我们再看另一条,检查软件的功能性质量时,应该处于清晰模式,极少情况处于庞杂模式。
对比清晰模式和庞杂模式的区别,清晰模式的关键是分类(categorize),庞杂模式的关键是分析(analyze)。那么这句是什么意思呢?其实是说我们检查软件质量时,主要应该依赖自动化测试,只有在极少的情况下,才需要手动测试。
当存在自动化测试时,我们对于质量的判断,会简化为测试是否全部通过(绿),还是有测试失败(红)。这时候,我们采用的就是分类行为(红还是绿)。而如果需要引入手工测试,我们就要进一步关心,要如何执行测试的过程,以及测试的结果是对是错。
为什么我们对于功能性质量的检测有这么高的要求呢?还是因为任何对于代码的修改,都需要回归测试。当质量检测处于低效的认知模式时,代码修改也会处于低效的认知模式。
另一个类似的是诊断问题时,应该处在有序的认知模式下(清晰或庞杂),极少情况处于复杂模式。
有序认知的问题诊断就是通过测试发现和定位问题。处于清晰模式的问题定位,就是通过失败的自动化测试,发现存在问题的代码。而处于庞杂的问题定位,是指根据出现的 bug,构造一个新的自动化测试来重现这个问题。测试所覆盖的范围,就是问题出现的范围,可以通过逐步分解测试范围的办法,准确定位问题出现的位置。而复杂模式的问题定位就是 debug。所以,简单来说就是多写测试,少做 debug。
小结
认知行为模式这个框架贯穿了我们的整个课程,放到团队里也是一样的。
不过切换到团队视角的时候,我们要面对的重要问题就是认知分歧。前面我也强调过认知行为模式并不是对于问题的划分,而是要从行为出发来分析,结合认知分歧的讨论,我想你应该对这点理解得更深入了。
既然认知分歧无法消除,那如何判断一个团队处于良好的认知行为模式,还是存在较大的问题呢?这里我给出了一个自己总结的理想模式。
这个理想的认知模式中的每一项,都可能在团队中出现认知分歧。因此我们需要“存在持续提升认知的手段”,这将是我们下节课讨论的问题。
思考题
你觉得有哪些可以持续提升认知的手段?
欢迎在留言区分享你的想法,我会让编辑置顶一些优质回答供大家学习讨论。