初探编程设计

前言

作者并不是软件科班，所以一直以来没有真正主动或被动学习过编程的设计，而是基于观察和实践的经验主义。

而板上嵌入式事实上也不关注这些，更多的是评估 RAM/ROM 用量、运行速度、业务逻辑是否满足需求，这也就导致一个严重的问题，在这样的岗位上的产出大多特别地为某个特定的产品定制。

这里有两个讨论，

在真正资源受限的 MCU 上真的无法使用可复用的代码吗？我想是否定的，这在后续再讨论原因。

这样定制的软件代码归档，极少注释几无说明，真的可以称之为软件资产吗？我个人认为是否定的，在我的认知里，无法让初学者在 8 小时内无人指导即可使用的软件模块都是不合格的。

最近想要思考一下如何产出真正的软件资产，需要了解一下基础的编程设计。所以花了十分钟完全了解

下面是对这些设计的纸上谈兵，笑笑就好，我也没有真正对这些玩意进行过多少实践。

面向对象编程

面向对象是一个被说烂掉的词，我其实完全搞不清楚那些专有名词。

在我看来，面向对象的精髓是面向一件事物的定义，也就是定义一个事务包含有哪些功能、哪些变量、哪些定量。例如我们定义一个键盘，他有定量 104 个键、功能 “按下 A”“按下 B“。

但是不对啊，我明明在市面上看见了奇形怪状的各种键盘，这涉及到所谓 "继承"，也就是奇形怪状的键盘只是重写了我们标准键盘类的一些东西。例如屏蔽了小键盘 "按下 1" 的功能、重写定量说有 88 个键。

在面向对象的世界，一切都被定义为对象，然后我们再对对象进行定义，固化这个对象的映像，再进行继承拓展，就可以实现一个模块的落地。再对多个对象排列组合，实现项目的落地。

我认为在平衡移植难度、功能后，面向对象编程无疑是模块化封装的最好实现 (暂时是这样想)。

但在项目的角度，面向对象可能是非常不友好的，如果在项目进行的过程中，某个对象事物的定义变掉了。那么整个系统是否就产生了大变，导致排查臭味来源变得比较困难。如果在每个对象的使用上都额外编写中间层进行隔离，那岂不完全多此一举又产生了新的定义。总之定义的变化影响过于巨大，这实际上对维护者是不太友好的。

我们事实上是希望爽，瞻前顾后、水平不一、四处混沌，这样是不爽的。

函数式编程

主要参考1.7. 范畴论入门 (*) - 香蕉空间，该参考资料仅介绍数学知识，不涉及编程。

这其实不算难懂，一切皆函数。但是一切皆函数又能实现业务功能，这样就比较神奇。

在函数式编程中有这样两条最重要的规则 (我说的)

任何函数都需要有输入、有返回。

任何函数在任何情况下，相同的输入会有相同的返回。

在我看来，函数式的精髓是获得一个无副作用确定功能的函数。

下面的图中，每个箭头就是一个函数，每个点都既是输入也是输出。例如左起第一个箭头，第一个点输入这个函数，输出为第二个点，而第二个点又可以通过两个函数变成另外的两个点。这里的点可以是任何东西，当然在这个思考方式下任何东西都是函数，实数值是固定输出的函数，取变量是单位态射1。

![图片剽窃自函数式编程入门教程 - 阮一峰的网络日志 https://ruanyifeng.com/blog/2017/02/fp-tutorial.html)

例如 CRC16 算法的实现以及各种各样古典加密算法的实现一定都是函数式编程，一组待校验值 / 明文经过函数一定输出一组校验值 / 密文。包括其校验过程和解析，也同样是函数式的，所以在这两个场景下输入和输出同构2。

我们可以发现，所有纯粹的数学问题，使用函数式编程都是简单的。不要杠数学可以描述世界啊 oi oi。

但是实际的工程中，所有的态不可能简单被定义，涉及到大的输入输出，函数的编写也是困难重重，同时函数的数量规模我想不会太小。如果分解问题，那函数的数量级是海量。

其实函数式编程是符合工业思维的，确定的动作带来确定的反馈，在某些针对有限情况的业务逻辑上大可以使用函数式编程，而非管理相对困难的状态机。

这里回到前言中的 "讨论 1：在真正资源受限的 MCU 上真的无法使用可复用的代码吗？"，显然函数式的编程在任何情况下消耗的资源都是类似的，我们可以缩小范畴 3直接删掉不需要的函数以实现复用。

我的观点是函数式编写部分数学相关函数是一个好的封装，但将这种思维带到工程和业务上大可不必，工作量纯纯指数级增加。

或许指令生成也是一个好的函数式方向，有机会改造一下。

过程式编程

上述两种编程方法都是在减少函数 / 方法的副作用，使其在不同系统中几乎可以获得相同的功能。过程式就不一样了，过程式的精髓是使用副作用获得想要的功能，而非使用传入值 / 传出值在各种函数 / 方法中游走来获得。

过程式是最符合人考虑解决方案的编程方式，不赘述了，大家都懂。

总结

总而言之，对于软件资产的分层，最底层的是函数式编程形成组件模块，而功能模块采用面向对象编程并调用函数式组件，这样是我思考的结果。

我们考虑最经典的案例，” 将大象放入冰箱 “。

面向对象的编程，需要实现大象对象的定义，冰箱的定义 (包含存储空间、放入方法、取出方法)，实例化两个对象，随后使用冰箱的放入方法。

函数式编程，预先定义三个物件 —— 空物件、冰箱和大象、放着大象的冰箱，函数 1 实现传入” 空物件 “传出” 冰箱和大象 “，函数 2 实现传入” 冰箱和大象 “传出” 放着大象的冰箱 “。

过程式编程，抓大象函数 (副作用是产生大象)，买冰箱函数 (副作用是产生冰箱)，塞大象到冰箱函数 (副作用改变冰箱为塞着大象的冰箱)。

此文由 Mix Space 同步更新至 xLog
原始链接为 https://www.yono233.cn/posts/novel/25_4_25_coder

Footnotes

对于任何 X, 都有一态射 id~X~：X→X (或记为 1~X~) , 称之为 X 的单位态射 (identity morphism) ↩

如设 C 为一范畴，并给定物件 X,Y 和态射 f:X→Y. 如果存在一个态射 g:Y→X 使得 g∘f=1~X~, f∘g=1~Y~, 则称 f 为一同构 。满足此条件的 g, 我们一般记作 f−1, 称为 f 的逆态射。如果物件 X 和 Y 之间有一同构，我们也称 X 和 Y 同构，又记作 X≅Y ↩

包含所有物件、态射、聚合的定义 ↩