前言
我从研究生阶段开始了嵌入式系统开发的工作,细算差不多有和嵌入式系统打了14年交道。嵌入式系统和服务器系统最大的差异,就是它的硬件系统是在持续不断地演进地,有时候为了成本的考虑,甚至会减低硬件的能力。随着参与的软件规模越来越庞大,复杂度和要求越来越高,对架构的关注和设计越来越多,积累了很多失败的和成功的经验。本文就是这些年做架构设计的总结。
正文
嵌入式系统架构
一般来说,嵌入式软件系统大概是这样的一个分层结构:
硬件是实现细节
这里说硬件是实现细节,是指每种嵌入式系统的硬件都不一样,也即具体的硬件差异比较大,硬件存在不断的演变,所以需要增加硬件的抽象层HAL,HAL的存在是为了给上层的软件提供服务,并隐藏具体的硬件细节。 在Martin的《架构整洁之道》里面提到了一个非常常见的例子。就是GPIO口控制LED亮灭的功能,一般情况下固件可以直接操作GPIO的比特位,而HAL则会提供一个类似LedTurnOn()这样的一个函数,很多项目里面都有这样的功能。但是这种硬件抽象层的层次是相当低的。如果要将这个抽象层次提升到软件/产品的层次,就需要弄清楚这个LED到底代表的是什么?如果是代表了电池电量不足,HAL则需要提供类似IndicateLowBattery()这样的函数。所以说,HAL层是按照应用程序的需要来提供服务的,而GPIO bit为的对应关系应该是一个具体的实现细节,它应该与软件部分隔离。
操作系统是实现细节
随着软件的演进,嵌入式系统有时会采用不同的操作系统,比如uclinux或者free RTOS等等。为了延长业务代码的生命周期,我们必须将操作系统也定义未实现细节,让代码避免与操作系统层产生依赖。
###具体业务软件是实现细节 随着业务的扩大,应用软件的功能会有爆发式增长,但是其中有写应用软件是通用的,比如一些日志系统等等。为了缩短软件交付的周期,我们将应用的部分也定义为实现细节,增加framework层增加应用软件的可复用性。
编程方式的选择
通常我们有三种编程方式的选择:结构化编程、面向对象编程和面向函数编程。我所设计的系统中,主要包含嵌入式的系统和服务器系统。前者主要采用的是结构化编程,当分层完成后,模块也划分清楚了,也可以引入面向各模块接口编程。同时采用这种方式,可以提高模块的单元可测试性。
一些编码原则
DIP
通过依赖反转,打破高层组件对底层的依赖
封装和隐藏
template和class,选择泛型编程还是特定的类型,取决于这个物件到底有多么common
最少的复杂度
简单来说,就是要易于理解和详细实现。
易于维护
设计出易于维护的代码对于软件之后的维护工作帮助很大,也能避免软件危机的发生。
松耦合、高内聚
各部分的关联越少意味着你在测试,集成,维护的时候可以轻松不止一点点。 高内聚:一个模块内各个元素彼此结合的紧密程度,一个类特别是一个方法应该专注于一件事。 松耦合:一个软件架构内不同模块之间互连程度的度量,通常需要根据业务流和数据流进行功能的正确划分来达到解耦。
可扩展
做软件唯一就是变化。如果项目是不可适用变化的话,基本上就可以贴上不合格的标签了。也许觉得改代码就可以了,但面向对象的设计原则里有条,类是可扩展还不可以修改的。扩展一般是通过继承来是想的。而修改特别是接口往往会引起许多莫名其妙的问题的。在给自己软件加功能的时候不要对底层甚至架构大动。
高扇入
按照结构化设计方法,一个应用程序是由多个功能相对独立的模块所组成。 扇入:是指直接调用该模块的上级模块的个数,被其它类或方法引用。那高扇入也就是说某类/方法被很多其它类引用了。也就是利用率很高了。如果某段代码连写了三次,就要把它单独作为一个方法或类。
低扇出
扇出:是指该模块直接调用的下级模块的个数。扇出大表示模块的复杂度高,需要控制和协调过多的下级模块;但扇出过小(例如总是1)也不好。扇出过大一般是因为缺乏中间层次,应该适当增加中间层次的模块。扇出太小时可以把下级模块进一步分解成若干个子功能模块,或者合并到它的上级模块中去。 设计良好的软件结构,通常顶层扇出比较大,中间扇出小,底层模块则有大扇入。
可移植
举个例子,客户的需求是变化的,有的时候可能要求从B/S转为C/S,如果在设计的时候进行移植性的控制。那么,工作量就会得到控制,省下的就是利润。
精简性
能少不多,程序员不能觉某个功能可能有用就加上。因为这会增加测试等方面的任务,而且程序员认为用户会喜欢的往往用户偏不喜欢。
层次性
层次性可以更好的分工。经验不足的编码人员写初级代码可以模块化起来。以后想重构就重构,想换掉就换掉。也就减少了复杂度了。
使用标准技术
这样会给大家熟悉的感觉。使用相同的框架,代码风格应该使用相同的标准,可以复用设计模式的就复用,成熟的东西相对来说可靠性强。
结束
好了,今天暂时更到这,欢迎大家阅读、批评和指正,下回再见。