类似于组件驱动 (CDD)、测试驱动 (TDD)、类型驱动 (TDD)、领域驱动 (DDD) 开发等,”以可独立安装的软件包驱动开发” (PDD:Package-driven Development),这个概念同样很好理解:先有组件 / 测试用例 / 类型声明 / 领域模型 / 可独立安装的软件包,再有确定的业务逻辑实现,前者是后者的基础、保障、范式、限定……
为什么是 "可独立安装的软件包"?想象下,如果没有现在的包管理器提供的那么多软件包,我们实现一个应用得多费时、费力。前端,自然是以 NPM 软件包为主要形式。
项目实践中其实有大量重复工作,怎么去除
项目做多了,会发现经常在重复编写相同职能的代码,或者部分相同,就拷贝一部分,再把新的部分写出来…… 有些时候很明显,有些时候需要深度抽象一下,就算是看上去不同的业务,抽象到极致,就是对特定数据结构的增、删、改、查。数据结构可以复用,对数据结构的行为也可以复用。或者说再庞大的业务,最后也是面向数据结构编程。如果我们能够把可复用的代码都从项目中独立了出去,那么项目里就尽是简单的胶水代码、描述(用例)性代码、流程控制语句等等,这会让理解代码、业务逻辑会变得更加简单,就像看着一堆零件一样,也许我不知道这个零件的设计、生产过程多么复杂,但是我知道它的用处,拼凑在这儿的用处。曾经想过一个自认为有意思的问题:互联网里那么多项目,该有多少重复的部分。
我一直在以 NPM 软件包的形式管理我的项目、代码库,有一个感受很深刻:耦合在项目里且能运作的代码,不一定能发布成 NPM 包,因为它们可能是零散的几行代码,嵌入在各处,并非函数等,但是以 NPM 软件包的形式管理的代码,是大概率可以很好地在项目中运行的。因为你需要开放出来一个可以执行的函数,想象一下,你需要重构代码,或者说,得要以接口的方式、规范去重写代码,这本身就会让自己站在程序设计的角度去组织代码。就是说,以一个 NPM 软件包的形式管理代码,必然是经过良好设计、实现的。
在拆解代码时,这里的关键值得提一下:确定复用跨度。就是:项目内复用,项目间复用,无法复用。项目内复用的模块类似于模具,造船、做衣服、吹瓶子的模具。模具成型后,修改的成本极大,几乎不可能跨品牌、跨主题使用。所以,越是仅专注于当前项目的模块,越是要明白跨项目复用的局限性。项目间复用为了高度复用,甚至跨项目复用,定制能力强。项目内复用为了直接使用的语义性、便利性,定制能力偏弱。一定要区分这两种情况,设计一个组件时必然会在支持的特性设计上遇到这方面思考,对代码库的管理也是一样的,尤其是在代码库的管理上。或者说,可以指数级提高代码管理、复审精度。
假设我们有个专属、模块设计良好的代码库
系统实现时非常考量抽象、具象成分占比,具象容易,抽象困难,后者非常考量工程师的体验是否丰富、对本质的感知层级、程序设计能力。代码库专干后者,以形成如此场景:要做 XXX,我需要哪些零件,选料,从代码库中选择输入、输出规格符合预期的零件、骨架,装配 MVP,以模块、不可分割的用户流程为单位优化 1 轮,迭代。无论是工程师自己的思考,抑或架构师与工程师的交流,这种充斥具象的场景都很棒,对协作抑或软件效率都有着极大的正影响。
在熟悉常用数据结构、算法、设计原则、模式的基础上,熟悉一个代码库,整合它们之后,就像哲学可以作为贯穿各专业领域的通用语言一般,辅助实现具体业务领域。具体业务领域,或者说业务行为,必然有万变不离其宗的本质,只是名称、输入的内容、时序、工序等不同,但大同小异。专业领域也因这种表象的 "小异" 而显得不一样,或者说差若天渊。逐物容易迷乱即是如此。说到行为的本质,这就又回归了代码库中每个模块设计的合理性,你知道的,程序,就是一个行为的执行过程,或者纵横交错多个执行过程。材料(零件包含一颗螺丝或一个组件、模块…)、工具、设计(思维能力、想象力、知识、智慧…)…… 只要有了这些,啥都能搞出来。
画家、作家的创作受益于随笔的积累。不积跬步,无以至千里。合理拼合微观以形成合理的宏观,就像贪婪算法,每步都选择局部最优解,最终得到的就是全局最优解。因此局部显得格外重要。这里要引入一个概念:基础。局部是整体的基础,即:合理的基础至关重要。越基础复用跨度越大,也是哲学可以作为各领域的通用语言的缘由,因为越基础就更加抽象,所以越考验对它的理解,反过来想,基础越好,抽象能力也就越强,也就越容易理解、把控上层,因为对基础有较深的体验,有了体验又怎么会不懂,从基层做起也是为了做管理时可以基于对基层的深刻体验。所以这个代码库非常强调单个模块的设计,合理的 “为什么” 至关重要,因为它们会作为基础被拼合成更上层的建筑。因即是果,果即是因。基础决定上层,上层也会是基础。基础决定上层建筑。
说这些,其实就是想说明,程序设计很考量工程师的抽象能力,这极大地影响了程序质量和写作。但是有个::模块设计良好的代码库::,就不需要每个工程师深度抽象,而是直接使用,即可推进业务实现。这一切的关键点:把耦合的代码模块化,最好拆解成一个个可以独立安装的软件包,或单独的项目,或以 Monorepo 的形式维护这些模块。
完全可以解决的延伸问题
以 NPM 软件包的形式管理项目、代码库,这样的方式,可以有效提升系统的质量,有效提升业务推进的效率。
不过恰恰相反,几乎很少有个人、公司会这么做,因为会产生一个额外的问题:不停地创建项目、配置项目。想想还是很耗时、复杂的。简单来看,使用现有的生成器,都是生成一个项目,再增、改文件,再推送到代码仓库,再发布。这个过程,理想情况也得耗时个几分钟,足以打断思路、分散注意力,令人望而却步。而日常开发活动中,我们都在开发具体的业务项目,不可能一次又一次地这样创建项目,毕竟可能会拆解出几十甚至上百个模块,或者催眠自己:等空闲了再做,因为的确有些本末倒置,繁杂,如果影响到了本分工作,那就毁犊子了。
不过这个问题也不是不可解决。换个思路,我们在写项目代码时,模块化一段代码后,指定从这个代码文件,::自动化::创建并发布一个项目即可。一个指令,即可创建项目、发包、在项目中安装使用,也可以跨项目使用,耗时小到几乎可以忽略,也就不存在本末倒置的问题了。想象下,在项目中,你写好了一个功能,运行一个指令后,就可以在项目中运行 npm i XXX
安装这部分代码,整个耗时可能就几秒钟。有一个命令行工具叫 create-esm,已经可用,可以实现这段文字所描述的,不过它还在持续的完善中,我猜,说不定还会改名字。
软件包私有性,也就是项目内复用的代码不能开源发布,这类问题,可以通过 verdaccio 来解决。
似乎对于个人来说容易一些,对于组织来说难度大一些?这的确实是个现象。但两者其实是同一回事,实力不够,论谁也是实现不了的;养成好的习惯从来不是件容易的事,一般来说都是先认识到自己的坏习惯,再尝试去改,是 2 件很不容易的事,否则格物致知、知行合一怎么会成为诸多学校、名人志士的座右铭呢;没有分兵(出处:孙子兵法)的概念也是一大问题…… 能够实现上述所说,是个系统性的提升,并非单纯技术这一个角度。但本文并不会去探讨需要怎样才能做到,真的要推荐的话,《礼记・大学》有文:古之欲明明德于天下者,先治其国;欲治其国者,先齐其家;欲齐其家者,先修其身;欲修其身者,先正其心;欲正其心者,先诚其意;欲诚其意者,先致其知,致知在格物。物格而后知至,知至而后意诚,意诚而后心正,心正而后身修,身修而后家齐,家齐而后国治,国治而后天下平。反过来就是答案。其实真的应该从《易经》开始学起,盯着读圣贤的书并实操即可。这个问题的最后想点明的是:这本来就不是个技术问题。
一口吃不成个胖子,但只要一直在方向上前进着,就总有质的飞跃那天,最怕便是从开始就怀疑,或半途而非,其实只要分轻重缓急,能够根据实际情况合理调控事情的安排,外加 “对不好的习惯在不合适的客观情况下一定要格杀勿论” 的态度,不太会影响实际业务效率。
结语
总之,使用 NPM 软件包的形式管理项目,这应该成为一个习惯,也是个好习惯,可以事半功倍。性相近,但失之毫厘谬以千里,只因习相远。
附言一
看完这篇文章,可以换角度思考,“如何减少非核心业务逻辑代码?”、”一个项目,包括框架部分、自定义部分,怎么做可以快速剥离自定义的部分?”、“怎么降低修改项目的成本?”……
附言二
打算将我的笔记都以文章的形式发布出来,与大家分享,更多(80%)还是想交流,集思广益,另外也想结识些志趣相投的新朋友,好朋友,🫵🏼,我知道你就在那儿,快联系我吧!😬(微信:iyoooooooo)