随想007:模块化代码

Posted 研究是为了理解

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了随想007:模块化代码相关的知识,希望对你有一定的参考价值。

你一定不止一次的听说过模块化代码

嵌入式 C 编程界到处流传着它的大名。在学校、在公司、在各种技术书籍中,你总能找到它的身影。

理想的模块化代码高内聚低耦合、逻辑清晰、经过严格测试

它被描述的像是无所不能,仿佛只要使用了模块化代码,你就可以节省大把开发时间,项目就能化腐朽为神奇。

对公司而言,这极具诱惑力。

节省开发时间就可以快速推出新产品、及时实现新需求。

以至于有些人了解模块化代码的优点后,惊喜的发现找到了一个可以无视人员素质又节省开发时间的终极方法:强制所有开发人员使用模块化代码

现实真的就是这样吗?

在现实世界中,好像哪里有点不对劲:

  • 模块化推了几年总是不成功;

  • 开发人员防(多)备(次)心(掉)很(坑)重(里),宁愿自己重复造轮子,也不愿用别人的模块

  • 强制开发人员使用模块代码,阻力很大。人们怨气冲天,影响士气

这是怎么回事?

模块化代码确实有诸多优点。

但道理说起来简单,只要实际操作起来,一线开发人员往往会直摇头:手中已有的所谓“模块”质量参差不齐、模块的开发者鱼龙混杂,很多模块别说出了问题找开发方负责维护,就是原作者是谁恐怕都找不到了。在这种情况下,谈论“强制使用模块代码”,简直就是天方夜谭,颇有几分“何不食肉糜”的傲慢

说白了,很多已有的“模块化代码”不好用,以至于开发人员对此失去了信任

信任 这东西,看不到摸不着,极其重要却很脆弱。信任需要长时间才能建立,但是一旦建立,一切都会简化起来,这会在各方面节省时间,而时间就是一切,所以说信任重要;脆弱是因为无论再坚固的信任,只要一瞬间就可以失去

我还记得这些“模块化代码”是怎么出现的:

  1. 某天大领导认识到模块化代码的好处,做出构建模块化代码库的指示,要求后续开发要使用库中的代码
  2. 模块化库中的代码从何而来?中层领导献策,可以将现有项目中的代码提取成模块化代码,形成模块化库。现有代码都是经过了现场考验的,肯定好用。
  3. 任务分配到各个项目组,开发人员全速开动,各种“模块化代码”迅速产出,然后收集到模块化库中
  4. 大领导视察模块化库成果,看着短时间内库中多出的丰富文件夹,想着此后开发必将无往而不利,心情十分愉悦。
  5. 这些匆忙产出的代码质量如何?由谁管控?我将向谁反馈 BUG ?向谁提出新需求?没人在意这些,总之库已建成,一切必将好起来。

相信这个过程不会只发生在我的身边。

所以这些披着“模块化”外衣的代码被强制使用,一线人员一次又一次被这些代码伤害过之后,人与人、人与公司之间,再难有信任。甚至有些开发人员提到模块化时竟隐隐生出反感

这是一个不好的趋势,因为模块化代码确实有用,它是被工程实践证实的一种优秀的编程策略

假如我们有一些这样的模块化代码:

  • 我们信任这些模块代码,因此可以只关注使用而不需要理解模块的实现,这可以节省时间
  • 这些模块代码都不需要修改,因此可以拿来即用,无需额外编程,这可以节省时间
  • 这些模块代码实现简洁明了,而且经过了严格测试和现场考验,因此错误少、维护少,这可以节省时间。

为了和烂大街的模块化代码区分开,我们称这些代码为合格的模块化代码,毫无疑问,使用合格的模块化代码,真的可以无往而不利,真的可以节省开发时间!使用的人越多,复用的越多,节省的时间也就越多!

所以我们不要再抱怨手中的模块化代码有多垃圾,我们应该将它们改进成合格的模块化代码,我们要产出合格的模块化代码

那么什么样的代码才是合格的模块化代码呢?

让我们来看一下合格的模块化代码具有的特性。

模块化代码一般由 .c.h 文件组成。

假如为菜单(menu)设计一个模块代码,模块一般包含 menu.cmenu.hmenu_cfg.h 这 3 个文件。

其中,menu.c 文件是模块的具体实现,menu.h 定义了模块的接口, menu_cfg.h 定义了默认配置和选项。

很多人不明白为什么要单独多出来一个 menu_cfg.h 文件,这个文件蕴含的思想很重要,对于需要更改配置或选项的模块代码,这个文件是必不可少的,后续将会提到。

合格的模块代码应该是只读的。如果不是只读的,使用者要把它修改成只读的

这是合格的模块化代码非常重要的特性!

很多人对此不解或充满疑问,但我们经常使用这种只读的模块代码,比如 C 标准库。

我们信任 C 标准库,我们不会修改 C 标志库,甚至我们都没有 C 标准库的源代码,我们无需理解它是如何实现的,我们只关注如何使用。

除了 C 标准库,Keil MDK 有一个 Manage Run-Time Environment 组件,如果你使用过其中的模块代码,就能发现这个组件提供的模块代码都是只读的(当然用户配置文件除外):

将模块代码设置为只读属性,显然是深思熟虑后的结果。这明确的告诉使用者:不需要也不能修改模块代码

不能修改代码???

要是我需要定制参数怎么办?要是你的代码有错误怎么办?

先暂且压一压心中的疑问,我们这样做是有充分理由的。

  1. 用户不需要修改模块代码

    用户使用模块化代码的目的是为了复用,为了节省开发时间。用户渴望的是拿来即用、用了还不出错的模块代码,所以用户并不想修改模块代码,甚至如果一个模块需要用户去修改才能用,那么可能会吓跑这个用户。

  2. 用户不能修改模块代码

    模块代码不是只给某个用户一个人用的。如果每个人都自由的修改模块代码,代码很快就会烂掉。

    模块也会不断升级,对用户而言,升级就是替换掉模块文件。但如果用户更改了模块内容,一段时间后又升级了模块,那么之前的更改会随着文件的替换而消失掉,依赖更改的代码会出现故障。特别是修改的人已经不在这个项目组,而接手的人又不能了解所有情况时。不要给人挖坑。

那么,那些因为不能修改模块代码而产生的疑问该怎么解决呢?

  • 模块通常都是可配置的,我需要定制参数怎么办?

菜单 模块为例。进入菜单后,如果长时间无任何操作,可自动退出菜单。那多长时间合适呢,不同的项目有不同的时间,这不要修改模块代码吗?

不需要。

对于定义良好的模块化代码,所有可配置项,他们的默认值定义在 module_cfg.h 文件中module_cfg.h 文件是模块的一部分。用户需要修改的配置项,定义在 app_module_cfg.h 文件中,同时 module_cfg.h 文件中定义的对应默认值失效。 app_module_cfg.h 文件是用户自定义的头文件。

具体到 菜单 模块为例,menu_cfg.h 文件中会给出默认的 超时退出时间,代码如下所示,这里超时退出时间是 30 秒。

#ifndef __menu_config_h__
#define __menu_config_h__

#include "app_menu_cfg.h"						//包含用户自定义文件

/*菜单无操作延时自动退出时间*/
#if !defined MENU_DELAY_AUTO_EXIT_TIME
#define MENU_DELAY_AUTO_EXIT_TIME       30      //单位:秒
#endif

#endif

只要使用 菜单 模块,用户就必须提供 app_menu_cfg.h 文件。如果要更改 超时退出时间,只需要在这个文件中重新定义宏 MENU_DELAY_AUTO_EXIT_TIME,同时,menu_cfg.h 文件中的相关宏失效。代码如下所示,这里将超时时间修改为 45 秒。

#ifndef __app_menu_cfg_h_
#define __app_menu_cfg_h_

#define MENU_DELAY_AUTO_EXIT_TIME       45

#endif

这并不是什么新鲜事,操作系统 FreeRTOS 中的 FreeRTOSConfig.h 文件,网络协议栈 lwIP 中的 lwipopts.h 文件,都是类似的用户参数配置文件。

  • 那模块中缺少我需要的功能怎么办?(新的需求)

  • 那模块中有错误怎么办?(修改错误)

这种情况下,我可以在模块中增加代码或者修改代码吗?

不可以。

正确的做法是将 BUG 或需求提交给模块维护者。

只有模块的维护者可以修改模块代码。不要图快捷而修改模块的任何代码!

模块维护者需要保证对模块化代码而言,除代码质量外,另一些生死攸关的特性:

  • 一致性
  • 简洁性
  • 向下兼容性

模块维护者更了解全局情况,他可能会因为需求不合理而拒你的请求,也可能会因为追求更通用性而扩展你的需求。他要考虑的东西更多,不会局限在某一个项目应用上。他要保证代码修改后运行不出问题,绝对不能出现用户更新代码后,程序编译就失败了,运行就不正确了

因此,从这方面来看,在公司层面推行模块化,这是一个系统工程。这里没有万能药水,也没有不劳而获,在享受模块化便利之前,必须要投入相应的资源:

  • 要有相应的制度:规定哪些部门负责发布维护模块、规定模块的组成、规定合格模块的标准……
  • 要有模块代码化发布和需求收集平台:方便的获取模块、方便代码更新、方便提需求、方便反馈 BUG……
  • 要有质量管控的部门:代码审查、第三方测试……

靠某个领导的意愿推行不了模块化,靠拼凑而来的代码推行不了模块化,它需要公司全方位的投入。

此外,它还需要人,能写出合格模块化代码的人。

只读,是合格模块化代码的重要特性,这还是一个可以量化的特性

可量化 意味着可测量:是否达成指标可唯一确定。用户将模块代码设置为只读后,如果不能正常使用,那么这个模块代码就不具备只读特性,这是可以唯一确定的。

而合格模块化代码的其它特性,比如代码质量,则不具有可量化特性。满足什么样的指标才是好代码呢?没有精确的数据可以描述。我们简要说明一下这些特性:

  • 代码简单直接,明显没有错误

  • 高内聚,封装可以封装的一切,隐藏可以隐藏的一切,绝不暴露不该暴露的信息

  • 低耦合,模块自成一体,尽一切可能减少依赖关系

  • 谨慎的规划好接口,有一组严密、定义良好的 API

  • 严格遵循一致性、向下兼容性

  • 严格测试,先试用再推广,需要经历现场的考验

这些特性都是合格模块化代码的重要特性,然而它们也是所有麻烦的起点,这些特性不可量化,需要依赖特定的人。只有哪些长久苦思、有数年或数十年从业经验、经过无数尝试和错误经历的人才会真正明白、真正写出符合这些特性的代码。

编写模块化代码有技术门槛,这就是麻烦的原因。

编写出稳定可靠的生产代码已经很不容易,但编写合格的模块化代码要求更高,需要程序员站在更高的层次。

如果一个好苗子没有辜负大学四年,当他走出新手村走向社会时,可以认为他的技术水平达到了20级。这个好苗子进入公司,经过一年的实践锻炼,他升到了50级,可以在老员工指导下更改需求了;又过了三五年,好苗子成了技术骨干,他升到了100级,可以独自干项目了;又过了几年,好苗子成了小组长,他升到了200级,可以做系统架构了。那多少级可以写出合格的模块化代码?可能至少需要500级!

这意味着并非所有人都能写出合格的模块化代码

这个结论多少有点让人沮丧,也多少会让那些一心降低人为因素影响的领导恼怒。但“并非所有人都能写出合格的模块化代码”并不代表“没有人能写出合格的模块化代码”,既然知道了模块化代码的好处,那就要努力编写出合格的模块化代码

如果一个人有了努力编写合格模块化代码的决心,也有进益求精的态度,那么他需要什么知识来支撑自己呢?

这需要细心、谨慎、并不断的学习,多看、多想、多练。

一个不好的消息是,这个过程没有捷径,时间、经验、态度缺一样都不行。

好消息是网上有很多优秀的开源代码可以参考,可以从开源项目 EmbedSummary 中找到这些代码。

在编写模块化代码的过程中,必须时刻提醒自己,模块代码是很多同事工作的基础,代码必须精益求精

以上是关于随想007:模块化代码的主要内容,如果未能解决你的问题,请参考以下文章

随想2

《嵌入式 - 模块》RW007 WIFI模块联网与使用

《嵌入式 - 模块》RW007 WIFI模块联网与使用

Python3-笔记-C-007-函数-导入模块并调用

RW007系列综合实战2使用RW007模块连接小米蓝牙温湿度计2代

JSX 和 template 随想