PS:理论上,我应该在上个月 “交付” 这篇文章,自觉得有一些论据不够强有力。但是,因为疫情的原因,我离我的书架很远(电子书不方便翻阅)。所以回到杭州,搬完家后,我便继续补充这篇文章剩下的部分。
软件开发是一项复杂的集体活动,它涉及到一系列的行为和艺术,如项目管理、流程管控、知识转化、程序员心理学(狗头)等。从个体出发时,这些都是一些无关紧要的因素。作为一个 “螺丝钉”,我们所关心的是:如何去解决问题?当然了,我们把组织视为一个个体时,我们也只关心:如何去解决问题?
本文是我在写 Chapi 过程中的一个感悟与过程的思考,因为它具有一定的代表性,所以记录一下供大家参考。
TL;DR 版:没有,建议阅读全文。
问题的模式
通用问题的模式
所以,为了解决『如何解决问题』这个问题,我们开始尝试各种各样的解决方案,如 Cynefin 框架,还有 DDD(领域驱动设计),BDD(行为驱动开发),TDD(测试驱动开发)等等。我们所做的事情是寻找合理的原则与模式,以更好地解决问题,并记录这些实践,比如此文。
在一个团队、组织中,往往会强调文档和纪律的重要性 —— 你按文档上的过程来,不会出错(出错了,也不怪你)。因为前人总结了大量的经验,记录在案,告诉了你:遇到这样的问题,你应该这么做。顺便一提,这种做法各有利弊,不好的一个方面是:缺乏灵活性;缺少解决问题的能力。
回到,我们的元问题上,如何解决『如何解决问题』这个问题?
这个时候,我们可以尝试使用第一个模式,使用 Cynefin 框架。于是乎:
- 简单的问题,寻找最佳实践。
- 繁杂的问题,寻找最好的实践。
- 复杂的问题,探索、尝试,如转换为繁杂问题。
- 混沌,尝试采取行动,转向复杂问题。
- 失序。
最后,我们的问题就落到了:如何将复杂问题拆解到人能处理的范围?换句话来说,作为一个资深的管理者,又著程序员、Tech Lead,我们要做的事情就是将复杂问题拆解,并交给合适的人解决。顺便一提,合适这一点很难,因为合格的领导者阶除了要解决问题,还要考虑人的成长。
软件开发中解决问题的模式
回到软件开发的问题上来,在这个一领域,我们往往面对的都是复杂问题。除非,你遇到的问题是,你有一个亿但是你不知道做点什么,你却想做点什么,这个看上去就像是一个混沌的问题。总而言之,我们要解决的都是复杂问题,于是我们可以寻找一些合适的现成模式:
- 使用 DDD,将复杂问题转化为繁杂问题
- 使用 TDD,将繁杂问题转化为简单问题
- 在简单问题中,使用最佳实践
嗯,听上去就是这么简单。
将复杂问题转化繁杂问题:DDD
在软件开发这个行业里,人们已经总结了大量的模式和解决问题的手法。只是呢,因为你一直在加班 生活,可能没有时间去了解一些潜在的解决方案。于是,我们可以尝试的第一个方案就是使用 DDD(领域驱动设计)的方式来解决复杂的问题。
于是,我们开始了旅程:
- 提炼问题域
- 找到问题域中的第一个核心子域
- 对第一个核心子域进行战略设计
- 完善统一语言
- 视情况重复 2 ~ 5
过程中,我们所做的是聚焦、聚焦、再聚焦,解决核心的高价值问题,再逐一突破。
将繁杂问题转化简单问题:TDD
在我们分而击之之后,我们就回到一个小的问题上面,开始我们的编码。现在,我们就开始了我们的代码之旅。在不考虑一些技术细节的问题之后,我们的过程变成了:
- 拆解问题(Tasking)
- 红-绿-重构代码
- 重构模型以加深理解
步骤,我们很熟悉,那么怎么拆解问题呢?这个问题,又变成了一个复杂问题,我们需要识别出大部分的场景,而后针对每一个场景编写独立的测试。换句话来说,我们要重新尝试合适的切入点,而后再逐一解决问题,然后我们会形成最佳实战。
聚焦:核心域
如果把软件开发想象成是一场军事活动,那么核心域相当于是敌方(问题)的主力。我们所要做的就是,选择一个合适的方式来击溃敌军,以少胜多是我们一直追求的,但是没有银弹。所以,我们需要把关注力放置在核心域上面。
不同核心域
组织架构往往是金字塔式的层级架构。尽管,我们已经达到了组织所认为的关键核心所在。但是事实上,在这一个系统中,每个人所关心的核心要素都是有所不同的:
- 组织统一核心域
- 技术部分核心域
- 代码中的核心域
- 个人的核心域
每个人对于统一目标的想法,会影响整个的行动。所以,组织中往往会强调个人兴趣与组织目标的一种对齐。这样一来,才能真正有效地在核心域实施行动。所以,最后就会落到人的问题上。毕竟,每个团队都需要一些强有力的个人才能保障。
适应核心域的变化
核心域并非一成不变的,它会随着业务中心的转移发生迁移。因为核心域会随着市场门槛的降低,慢慢变成通用域。
也因此,我们需要寻找新的核心域。而原先我们的支撑域,可能因为种种原因,转变为核心域。一个有意思的故事是牛仔裤的故事,原生它只是淘金者 Levi Strauss 的支撑域,核心域是淘金,随后它凑成变成了 Levi's 的核心域。不同公司、不同项目的核心域往往有可能相同的。
重新聚焦核心域
这一点,实现上是对于个人而言。尽管你是一个系统的核心部分,但是你做的事情,是不是依旧核心,这就是一个非常有意思的问题了。因为随着焦点的转移,你做的事情可能对于你来说价值不大。
拆解:驱动开发
我们已经有太多的关于驱动开发的模式:
- 测试驱动开发
- 行为驱动开发
- 验收驱动开发
它们以不同的视角来尝试拆解一个复杂的问题,从一个用户故事的验收开始,考虑一个个的用户场景,再落到某一个函数的实现。随后,通过这一个个的用例完善促成整个用户故事的完整性,以实现代码的价值。
分解任务,分离关注点
标题即内容。
从无序分解到有序实施
开始的时候,你并不是一帆风顺的。尝试一个新技术、框架的时候,我们总是一点点领悟出来的,所以我们会从一个大方向正确,但是小小的走弯,直到正确的抽象出特定的模式。以 Chapi 语法解析作为示例,尝试了不同的方案之后,最后出现了一个统一的步骤和模式,如:
- 解析依赖
- 解析 data struct
- 解析 function name
- ……
这样一来,当我们遇到不同的语言时,我们都可以尝试这样的 solution。不过解决方案并不是通用的,还会遇到一些特殊的情况。
模式:持续改进
没有什么设计可以一次设计到位。举个最简单的例子,在我毕业的这 6 年里,我搬过 5 次家,住过 10 年或者是 20 年前设计的老房子,我经常看到一些设计不足:
- 20 年前的房子,房间的插座不够,客厅大到离谱,房间很小。
- 10 年前的房子,房间的插座够了,但是它在每个房间有个电视,还有网线。而我们用路由器和电脑(含平板)。
- ……
- 1 年前设计的房子,可能考虑不到在家办公的需求,所以疫情期间要买各种设备。
因为种种原因,比如家有老小,所以你可能像我一个同事一样在厨房办公,它又大又安静。
所以啊,你设计的系统能满足未来一年的变化,已经非常了不起了。我们能所做的就是预留空间、引入创新文化,以便持续改进。
完善:领域模型重构
慢慢地,当我们接入越来越多的需求时,会发现模型会发现一些变化,所以我们需要一些重构才能支撑起新的业务场景。如在 Chapi 中,我们遇到的第一个挑战是,有的语言它是基于函数的,如 Go等,而有的语言是基于类的,如 Java。
所以,我们需要对模型进行重构以及设计改进。
模式:演进的统一语言
如上。
回顾:缺失的地方
回顾,设计中缺失的地方,以便下次吸取一些经典(当然了,不要过度地预先设计):
- 模型的适用性。在设计的过程中,我假定了不同的编程语言使用的是同一个模型,但是模型缺少边界。
- 如何从代码中显现概念?毕竟代码上可能只有一个字段,一行注释。我所应对的一种方式是测试、查看调用方,还有知识共享的方式。
好了,这篇文章又写得太长了。
结论
事实上,你并不需要上述的驱动开发方式,你所需要的是:如何有序地解决一系列的复杂问题?
同时,你所归纳出来的这个方法,可以被快速地大规模复制。一个启发的文章是《驱动方法不能改变任何事情》,如文章所说,你需要创造出吸引人的基因(朗朗上口):
框架 | 它的承诺 | 吸引人的文化基因 |
---|---|---|
TDD | 你的产品将几乎没有可见的 bug,同时除了必须的代码外,不会生产过多的代码。 | 红 - 绿 - 重构,单元测试 |
BDD | 你将 TDD 与功能需求关联起来 | Given, When, Then |
DDD | 应用的架构完全反映现实业务,因此后续需求的实现将非常自然;没有变通方法,没有秘密路径,只有纯粹的、不受影响的以及独立的模型。 | 组成部分(Building Blocks),无歧义的语言(Unambiguous Language)和策略设计(Strategic Design) |
顺带吐槽一句,从这个基因来看,DDD 有点复杂。
如果没有的话,那么使用别人总结的方法,是一种更省事的方式。