banner
yono

yono

哈喽~欢迎光临
follow
github

初探编程设计

前言#

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

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

这里有两个讨论,

  1. 在真正资源受限的 MCU 上真的无法使用可复用的代码吗?我想是否定的,这在后续再讨论原因。
  2. 这样定制的软件代码归档,极少注释几无说明,真的可以称之为软件资产吗?我个人认为是否定的,在我的认知里,无法让初学者在 8 小时内无人指导即可使用的软件模块都是不合格的。

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

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

面向对象编程#

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

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

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

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

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

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

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

函数式编程#

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

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

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

  1. 任何函数都需要有输入、有返回。
  2. 任何函数在任何情况下,相同的输入会有相同的返回。

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

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

  1. 对于任何 X, 都有一态射 id~X~:XX (或记为 1~X~) , 称之为 X单位态射 (identity morphism)

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

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

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。