初探编程设计
前言
作者并不是软件科班,所以一直以来没有真正主动或被动学习过编程的设计,而是基于观察和实践的经验主义。
而板上嵌入式事实上也不关注这些,更多的是评估 RAM/ROM 用量、运行速度、业务逻辑是否满足需求,这也就导致一个严重的问题,在这样的岗位上的产出大多特别地为某个特定的产品定制。
这里有两个讨论,
- 在真正资源受限的 MCU 上真的无法使用可复用的代码吗?我想是否定的,这在后续再讨论原因。
- 这样定制的软件代码归档,极少注释几无说明,真的可以称之为
软件资产
吗?我个人认为是否定的,在我的认知里,无法让初学者在 8 小时内无人指导即可使用的软件模块都是不合格的。
最近想要思考一下如何产出真正的软件资产
,需要了解一下基础的编程设计。所以花了十分钟完全了解
下面是对这些设计的纸上谈兵,笑笑就好,我也没有真正对这些玩意进行过多少实践。
面向对象编程
面向对象是一个被说烂掉的词,我其实完全搞不清楚那些专有名词。
在我看来,面向对象的精髓是面向一件事物的定义,也就是定义一个事务包含有哪些功能、哪些变量、哪些定量。例如我们定义一个键盘,他有定量104个键、功能“按下A”“按下B“。
但是不对啊,我明明在市面上看见了奇形怪状的各种键盘,这涉及到所谓"继承",也就是奇形怪状的键盘只是重写了我们标准键盘类的一些东西。例如屏蔽了小键盘"按下1"的功能、重写定量说有88个键。
在面向对象的世界,一切都被定义为对象,然后我们再对对象进行定义,固化这个对象的映像,再进行继承拓展,就可以实现一个模块的落地。再对多个对象排列组合,实现项目的落地。
我认为在平衡移植难度、功能后,面向对象编程无疑是模块化封装的最好实现(暂时是这样想)。
但在项目的角度,面向对象可能是非常不友好的,如果在项目进行的过程中,某个对象事物的定义变掉了。那么整个系统是否就产生了大变,导致排查臭味来源变得比较困难。如果在每个对象的使用上都额外编写中间层进行隔离,那岂不完全多此一举又产生了新的定义。总之定义的变化影响过于巨大,这实际上对维护者是不太友好的。
我们事实上是希望爽,瞻前顾后、水平不一、四处混沌,这样是不爽的。
函数式编程
主要参考1.7. 范畴论入门 (*) - 香蕉空间,该参考资料仅介绍数学知识,不涉及编程。
这其实不算难懂,一切皆函数。但是一切皆函数又能实现业务功能,这样就比较神奇。
在函数式编程中有这样两条最重要的规则(我说的)
- 任何函数都需要有输入、有返回。
- 任何函数在任何情况下,相同的输入会有相同的返回。
在我看来,函数式的精髓是获得一个无副作用确定功能的函数。
下面的图中,每个箭头就是一个函数,每个点都既是输入也是输出。例如左起第一个箭头,第一个点输入这个函数,输出为第二个点,而第二个点又可以通过两个函数变成另外的两个点。这里的点可以是任何东西,当然在这个思考方式下任何东西都是函数,实数值是固定输出的函数,取变量是单位态射[^单位态射]。

图片剽窃自[函数式编程入门教程 - 阮一峰的网络日志https://ruanyifeng.com/blog/2017/02/fp-tutorial.html)
例如 CRC16 算法的实现以及各种各样古典加密算法的实现一定都是函数式编程,一组待校验值/明文经过函数一定输出一组校验值/密文。包括其校验过程和解析,也同样是函数式的,所以在这两个场景下输入和输出同构[^同构]。
我们可以发现,所有纯粹的数学问题,使用函数式编程都是简单的。不要杠数学可以描述世界啊oi oi。
但是实际的工程中,所有的态不可能简单被定义,涉及到大的输入输出,函数的编写也是困难重重,同时函数的数量规模我想不会太小。如果分解问题,那函数的数量级是海量。当然最重要的是万恶的产品经理会不停地改需求,新的傻子客户也会带来新的防傻子机制,这些情况会更加恶化函数式编程的开发环境。
其实函数式编程是符合工业思维的,确定的动作带来确定的反馈,在某些针对有限情况的业务逻辑上大可以使用函数式编程,而非管理相对困难的状态机。
这里回到前言中的"讨论1:在真正资源受限的 MCU 上真的无法使用可复用的代码吗?",显然函数式的编程在任何情况下消耗的资源都是类似的,我们可以缩小范畴 [^范畴]直接删掉不需要的函数以实现复用。
我的观点是函数式编写部分数学相关函数是一个好的封装,但将这种思维带到工程和业务上大可不必,工作量纯纯指数级增加。
或许标准指令生成也是一个好的函数式方向,有机会改造一下。
过程式编程
上述两种编程方法都是在减少函数/方法的副作用,使其在不同系统中几乎可以获得相同的功能。过程式就不一样了,过程式的精髓是使用副作用获得想要的功能,而非使用传入值/传出值在各种函数/方法中游走来获得。
过程式是最符合人考虑解决方案的编程方式,不赘述了,大家都懂。
总结
总而言之,对于软件资产
的分层,最底层的是函数式编程形成组件模块,而功能模块采用面向对象编程并调用函数式组件,这样是我思考的结果。
我们考虑最经典的案例,”将大象放入冰箱“。
面向对象的编程,需要实现大象对象的定义,冰箱的定义(包含存储空间、放入方法、取出方法),实例化两个对象,随后使用冰箱的放入方法。
函数式编程,预先定义三个物件——空物件、冰箱和大象、放着大象的冰箱,函数1实现传入”空物件“传出”冰箱和大象“,函数2实现传入”冰箱和大象“传出”放着大象的冰箱“。
过程式编程,抓大象函数(副作用是产生大象),买冰箱函数(副作用是产生冰箱),塞大象到冰箱函数(副作用改变冰箱为塞着大象的冰箱)。
拓展
记得不知道哪里看到过一句话,"大多数领导指挥下属都是过程式的,在这样的情况下无法正常使用函数式编程开发。"大概是这个意思。我一向认为构建软件系统和构建管理/公司/产品的系统是有相似之处的,好的设计好的架构可以极大降低后续的拓展、维护、管理成本,只是会烧掉架构者许多的脑细胞,而大多数架构者只愿意以最接近人本身思维的过程式进行思考。