
大家好,欢迎来到停止重构的频道。
本期介绍一下,我的框架在解决什么问题。
为什么要花这么多时间,重复造轮子,十年了,今年算是基本完成了。
以后可能会写一篇更完整的论文。本期就流水账地讲述一下这期间的故事与思考。你可能也会有某个相似的经历。
软件架构的意义
在没工作之前,你可能和我一样,完全没有软件架构的概念。
只有面向对象、面向过程这些基础认知。
在工作后,我参与的第一个项目是一个摄像机系统的开发。
这是一个完整的C++操作系统,当时的代码行数已经超过了500万行,这也是我至今参与过最大的一个跨国合作项目。
但这是一个无聊的工作,在熟悉了架构规则后,看得懂if/else、函数调用,基本就能上岗。完全不需要线程同步、文件操作、总线操作等知识。
这对于一个嵌入式项目来说是不正常的。特别是这么一个需要协同4个CPU的系统上。
这一切归功于它的软件架构,总体上是三层结构:UI层、逻辑层、驱动层。
所有事件都会集中通知到逻辑层,再由逻辑层制定流程控制对应UI、驱动。

技术上而言,这个架构最精彩的地方,是它完全掩盖了线程同步、跨CPU同步,这是嵌入式开发最难的技术问题之一。
每个C++文件按照规划绑定到4个CPU中的其中一个,系统启动时,CPU会自动为每个C++文件创建对应的独立线程。
也就是说,文件A调用文件B的函数。实际上就是线程A通知线程B执行某个函数片段。
这样开发者就不需要考虑夸CPU、夸线程调用。至于线程同步,它也提供了完整的状态机机制。

没错,这是一种舍近求远的做法。为了不会像蜘蛛网一样越做越乱,为了降低对开发者的知识要求,而大幅浪费硬件资源。
在很长一段时间内,我都无法理解为什么要这样做。
而与很多前辈交流后,换一个宏观视角,正正也是因为这个架构。 项目代码越来越多,而项目的开发过程还是井井有条,人员分工、能力要求也非常精准。
业务开发人员只需要懂很基础的编程知识就能上手 ,且基础框架不允许花里胡哨的编程行为,所有业务代码都是一个样子工程,也就不会越做越乱。
而驱动、算法、框架维护等比较难的工作,则交给对应能力的程序员完成,剥离了业务,也就可以更专注于某个技术问题。
而这种明确分工,也能更准确控制项目成本。软件工程的成本,主要是人工,程序员的薪资区别其实就在经验、知识上。精准地分工也就能精准地控制成本。
这让我感觉到,软件架构指的是软件的结构,正如房子的骨架一样。这样的定义是不完全准确的。
软件架构需要考虑软件自身结构的同时,还需要考虑软件的生产过程,如人员分工、合作方式、混乱约束等等。
软件不是代码组成的,而是一群人写成的,只要这群人分工明确、合作顺畅、约束明确,那么做出来的软件一定质量偏高且可持续维护/扩展下去。

很多人都强调基础技术选型的重要性。
而事实上,很多项目的重构,绝非因为出现了什么新技术,而是因为维护下去的成本,比重新做一个的成本还要高。越做越乱才是一个软件工程短寿的根本原因。
所以,在我之前写的那本书《大型网站架构实战》中写道,软件架构应该是两层的,基础结构和上层架构。
基础结构是软件的骨架,解决软件如何实现的问题。
上层架构则是团队合作、分工的规则。很多朋友说,应该叫上层规则更加合理。
但是,单纯的规则是无法约束代码混乱度的,不会有团队在工期压力巨大时还坚持严格的代码评审的。
**只有把规则焊死在软件工程上,才能让混乱度限制在可控范围。**从源头上杜绝屎山代码的可能。

做什么样的上层架构
那要做什么样的上层架构,才能精准分工、规则明确、用较低成本完成较高质量的软件。同时还需要满足所有的软件形态。
另一个大型网站系统项目让我有了新的思考。
这个项目前期发生了一件不太合理的事情,一开始后端服务框架要求用SpringMVC,后来又需要换成SpringBoot,最后换成集团内部的微服务框架。
也就是说,我负责的后端服务模块,用三个框架写了三遍。
怨言当然是有的,但用不同Java框架实现相同的业务,为什么会这么费劲。
**因为代码复用度非常低,**框架改变了,不仅需要重新学习,还需要重新适配,一大段逻辑,可能隔两行就要改,还不如重新写。
那么,是否可以将涉及到框架、第三方库的代码,都封装一层 ,这样业务逻辑就可以复用了。
第二次重写的时候,我确实是这么做的,**但这么做,会弄出很多的类,且编码时间会非常长。**在第三次重写时,业务逻辑是可以复用了,也确实只需要集中修改工具类。
但是,这样的封装真的有意义吗?
这种一个业务在多个框架重写的场景,不是常态。几乎不可能在别的项目中发生。
正常的项目,这样的封装不就是多余的吗?
别人已经封装成了库,我又花时间在上面封装一层,这样做的意义在哪直接调用这些库不好吗?

我跟我的同事讨论出了其必要性。
我们发现,每个项目都基本上要从零开始 。感觉每个项目的代码实际上都是一次性的胶水代码,用于粘合各种第三方库以实现业务功能。
每个项目积累的,其实仅有人的经验。这批人离职了,就意味着下一个项目就是全新的项目。
如果,我们将这些第三方库封装成统一的使用方式。
那新项目是不是就可以复用这些封装类,业务开发人员就不需要懂太多的知识就能上手。
且多个项目下来会积累足够多的工具类,这样新项目的成本就可以足够低。

跟很多同事讨论过后,发现很多人都有积累团队工具类的想法。
甚至,有些前辈还实际推行过,但基本都失败了。
统一的使用方式是什么?会比直接用第三方库方便吗?别到头来既要看封装库的文档、还要看三方库的文档。
工具类封装的颗粒度是什么?可能会让工具类多如繁星、功能重叠。
某个工具类功能缺陷要修复如何更新?
这一切都花大成本做好了,不就相当于自己内部再弄一个高标准的npm、maven平台吗?
每个项目都这么急,谁愿意做好这些多余的事情。
如何实现
但我始终觉得这个方向是对的。因为这种业务、工具模块分离 的做法,可以优化团队合作方式。
更通俗地讲,比如我们需要将大象放进冰箱。
业务就是逻辑流程,只需要明确第一步、第二步、第三步做什么就可以了,至于每一步具体的行为交由某个工具模块完成。

工具模块需要由经验丰富的程序员开发,但是可以脱离业务,多个项目复用。
而且工具模块没有想象中多,很多时候,10个以内就能完成一个项目,所以很多新项目甚至不需要再开发新工具模块。
业务可以交由经验尚浅的程序员开发 ,业务开发时只需要专注业务逻辑,不需要了解五花八门的基础知识,同时也能约束规范,不会让业务代码越来越混乱。

但是,我需要一种全新的封装形态,不能是传统的封装类或工具函数。
传统的类封装最大的问题 ,就是需要在工具类A和工具类B间添加黏合代码,比如工具类A的返回结果,需要我们写代码转换,才能放到工具类B中使用。
如果这个问题不解决。那么封装工具模块的意义并不大,还不如直接使用基础库来的方便。
我大学是学轻工业相关的,这让我联想到机械零件,两个齿轮拼接就能使用了。
但是,类/函数根本没有'连接'的概念。每个类/函数都是完全独立的。
这些工具类/函数就像是各种自定义的异形齿轮,需要开发者自己捏一个中间连接件,才能让一个异形齿轮带动另一个异形齿轮。

这个问题很长一段时间都没解决,这好像不符合编码的基本规律。
直到一个物联网项目,调研了微软的一个软件架构后,才有了新的启发。
他们提出了message bus的概念,业务流程其实就像是流水线加工,每一个工序完成特定的数据处理后,这组数据会被直接送到下一个工序继续加工,直到最后产出处理完成的数据。
这个软件架构具体是怎么实现的我已经忘了,对我有启发的仅有这个概念。

在逻辑的开始,将初始数据放入一个数据池当中。
这个数据池其实就是一个Json类型的对象,这是一种树形结构数据,任意节点的数据都可以被添加/修改。

在调用每一个工具模块时,有两个调用参数,第一个是放入这个数据池,第二个是设置工具模块参数。
比如,我们用检查必要参数的模块检查数据,那么模块参数就应该告诉工具模块检查哪些字段。
工具模块可以对数据池中的数据进行修改或者获取。

**工具模块只需要返回错误码结果,**用以判断是否处理成功。
发生错误,中断后续业务逻辑。

如果结果正常,就继续将数据池直接传入下一个工具模块继续处理。
由于编程语言都有引用传参的机制,所以工具模块对数据池的修改都会被保留下来,相当于数据池是当前逻辑的全局变量。

由于数据池的加入,业务代码就真的像流程图一样,只需要编排工具模块使用顺序,且对每一个工具模块进行参数设置即可。

这个基本的架构规则想明白后,后端框架Once也就在2017年的时候做出来了。
业务代码可以由Json文本配置,再由代码生成器生成为真实的代码。
工具模块可以无条件复制黏贴到别的项目。

前端网页框架Trick和C++框架Bees,也先后做出来了。
但是这两个框架的业务代码不能Json配置化,仍然需要写真实的代码,因为二维UI、多线程同步等问题太复杂,而当时的我也认为这些问题很难在短时间内解决,甚至可能无法解决。
为什么业务代码一定要配置化?
因为这样能简化业务代码的表达,业务逻辑更清晰,更容易理解的同时,也能防止各显神通地瞎写代码,和总是因为一些小的语法问题而调试半天等情况。
项目实践
在接下来的很长一段时间,只要我主导的项目,包括以前上班时的项目,也包括后来作为独立开发者参与的项目,我都会用这几套框架。希望在实际项目中继续完善。
你可能有疑问,领导或团队其他人怎么会同意的?
一方面,由于它们实际上都是SpringBoot、Vue工程,我只是在其基础上套皮增加了规则而已。
另一方面,成本节省就是硬道理 ,这些框架确实超出了预期,特别是后端框架Once。
前端和后端人员的比例一般是1:1左右,一些团队可能后端人员稍多,我说的是大型项目,不是随便AI生成的网页都能验收的项目。
而Once框架做到了,1个后端人员足够匹配4-5个前端人员的开发进度,而且由于业务、模块分离,刚毕业的程序员也能立刻投入生产。
当然前端网页框架Trick也是能减点成本,但是不多,因为很长一段时间都没有突破UI代码配置化的问题,所以在效率上是欠缺的。
但是这个业务、工具模块分离的做法,也让修改BUG、维护成本降低。

框架完整版本
然后就到了23年,开始重新整理框架。这些年用自研框架做项目,让我看到了他们更完整的形状。
当然了,下定决心专心整理框架,是因为有个视频下面有个小黑粉写了几百字的评论,由于一些部分有些过激已经删掉了。
在决心先做好框架后,在进度上确实有了很大的提升,24、25年 Once2、Trick2、Bees2也完成了整理。主要是对多个项目实践过程中的问题进行完善。
这两年确实没有太多深刻的启发,因为这么多年的项目实践,与那么多人交流讨论,我已经知道肯定能做出来。

在第二个版本中,增加了VSCode、JetBrains插件,不再要求开发者在终端敲命令,使用更加方便。

增加了模块、框架更新机制,可以一键下载、更新模块,框架也可以一键更新,这样就不需要某个部分出现BUG时,十几个工程统一手工修改了。

最后是3套框架全部实现了业务代码配置化,包括前端框架Trick的二维UI配置化,也包括C++框架Bees多线程逻辑的配置化。

提前了最终版本
系列框架的版本2,确实是完成了所有的设想。
在25年年头,我用Trick2重新写官网的时候,我承认Json配置业务代码的方式还是太繁琐了。虽然这个吐槽点在之前的项目实践中很少听到,毕竟Json再麻烦,也比直接编码来的轻松。

之前很多人都建议设计成图形脱拽的方式,这样就可以像画流程图一样拼出个逻辑来。这也是很多低代码框架的方向。
这本来是第四阶段的设想,因为图形脱拽最终也是转换为Json配置,想着以后再加UI拖拽层就可以了。
但是这个Json配置确实太麻烦了,配置一多感觉,哪都是括号于是,就开始做图形界面了。
本来我都做了1个多月了,在看完了《我们程序员》这本书之前,有推荐过。
看到纸片打孔演变到代码的过程,不禁让我重新思考,**为什么过了这么多年,编程还是码的形态,**而不是已经变成更容易理解的图形,至少图形没有成为主流。
我的理解是,**文字更紧凑,**试想一下如果将1万行代码转变成图形的话,那不就是个巨型蜘蛛网吗,写代码的效率远超图形脱拽的效率。

然后我又有了一个大胆的想法,那就设计一种极简编程语言,用于配置业务代码,以替代哪里都是括号且没有代码提示的Json。
通过插件可以像写其他编程语言一样有语法提示、错误检查等,且由于这种语言只用于配置业务代码,所以语法非常简单,关键字也只有4个左右。
代码生成器Christmas的新版本将包含这样的可配置解析语言,VSCode、JetBrains插件和解析器都做好了。
之后会发布框架的新版本:Trick3、Once3、Bees3。

总结
不知道从哪里看到过的,第一次解决问题才能清晰地描述问题,第二次解决问题才能比较完整地解决问题本身。
要成熟、优秀的解决方案,至少是第三次。
那么回到开篇的问题,重复造轮子的意义在哪里?
优秀的软件框架有很多,SpringBoot、React、Next等等。优秀的原因不在于他的设计理念有多好,而在于其社区规模和特定领域的生态。
所以重新做一个类似的框架,即使有更优秀的设计思想,也一定会由于生态不完整而无人问津。
所以一开始我就不是在重复造轮子,我是希望做使用好这些轮子的车。
Trick框架用的是React这个轮子,Once框架用的是SpringBoot这个轮子。
这个车,也就是之前所说的上层架构,关于团队合作、分工的规则。