概念
linux底层驱动开发 通常是基于C语言,相对其他面向对象高级语言 缺少了 完善的异常处理机制 和 各种面向对象特性支持,但同时驱动开发对安全性要求又是极高的(毕竟是内核系统的一部分)。
此时在设计中容易引发两类问题:
(1)、自由的底层语言开发过程,容易引起 缺少抽象的接口和模块设计,混乱的调用关系 和 耦合的数据结构。
(2)、极其复杂的异常处理设计,容易包含 冗余无效的异常处理,和 不合理异常分支 和 资源管理机制。
这些问题 会引起实际应该关注的业务代码部分 可读性、可维护性、可扩展性的降低,开发维护过程会变得低效。
如何避免这些问题发生,总结有3个方向出发: 1、模块与接口的设计约束 2、异常处理的设计思路 3、编码过程优化方法
思路一:模块与接口设计约束
1 .接口的设计 要基于合理分层分模块的功能抽象
在一个任何系统中,都是先划分层次 APP、业务框架、内核、逻辑驱动,层次之间有清晰的业务划分和接口抽象。对于一个模块内部也是一样,任何设计都是先基于 分成分模块的抽象出发。
如常见的思路 接口层负责对外封装,业务层负责功能实现,业务层根据功能划分 成不同的子模块(模块间避免数据耦合和相互引用),划分合理的调用流程。
在此要特别注意不同层次和模块间的头文件的包含关系坚持最小化原则,从数据结构的层面杜绝数据耦合,初始的框架合理后续的设计编码才能合理。
2.函数设计要保持单一功能原则,入参尽可能简单。
这应该设计的常见常识,尽量做到50行以内,常见的方法 把 异常检查的部分抽象出来,重复的代码抽象为公共函数,对复杂功能的实现进行子函数拆分。
函数的入参尽可能要少,低于4个为优。常见的处理方式为 剔除无效的入参 只看到需要的参数,针对多个入参进行打包封装降低个数。注意:如果入参非常复杂,比如包含一个巨大的数据结构,此时最好提供一个获取default入参的接口 提供给调用者,调用者只需要修改关注的参数值,降低调用者的使用复杂度。
3.函数内部调用 要基于同一层次的清晰流程步骤
在实现一个函数时,内部调用的子函数接口 也应该处于统一层次的封装。和写文章一样,分段落要合理。
举个例子,当你描述把大象装进冰箱需要几步?1 把冰箱打开 2 把大象放进去 3 把冰箱关闭。这种描述方式就非常合理,反过来,如果你先用 1 到 98 步 去描述 如何打开一个冰箱的细节,然后 99 把大象放进去 100 把冰箱关闭,显然让阅读的人很难识别业务重点 和 流程。
4 函数出参避免使用全局变量,返回值要能体现成功失败
任何情况 都避免使用全局变量查询 作为出参数,如果需要出参,要清晰的在函数的形参体现 入参和出参;
如果函数存在调用失败的可能,返回值要能体现函数调用的成功或失败。如果函数不存在失败的情况,建议申明函数为void返回值类型,降低使用者是否要做返回值检查的判断,同时也降低异常处理分支的复杂度。例如 配置底层逻辑寄存器,这类接口 一般不存在失败的场景。
5 函数尽可能设计为 可重入接口,避免全局变量或者静态变量
函数设计尽可能取消 全局变量或者静态变量,设计为可重入接口。这样能降低调用约束,并且支持多实例并发访问。
6 资源的管理和使用要隔离,资源生命周期遵循最小化设计
资源的申请释放和使用要隔离,在使用资源的时候 不要再考虑各种异常场景的释放回收,这样会让业务处理非常分散而且容易遗漏处理分支。申请成功就使用,使用的时候就聚焦业务处理,使用完成后释放资源。
任何系统资源如文件,如果能做到打开后读出内容立马关闭文件,然后业务处理针对读取到ddr的数据。要优与打开文件后持有handle,一边读取一边处理业务,最后再关闭文件。要减少系统资源的使用窗口。
思路二:异常处理设计约束
1. 入参校验在对外入口处,避免内部反复冗余检查
一个模块应该尽可能在对外接口 入口处做异常条件检查,不要在内部模块或者不同层次还反复进行入参校验。
2. 异常错误码要设置在统一层次,避免链式传递
通过定义返回错误码来指导调用者 问题定位 是一个常见的手段。设计错误码注意几点:
(1)、错误码一定要有明确的意义,能知道调用者分析流程或者入参问题。
(2)、错误码 一定要设计在统一层次的接口,避免出现 一个接口返回一个错误码,但是点进去一看,发生异常的场景 这个错误码可能来自当前函数的异常分支,也可能来自他调用几个函数的错误场景,要去确定一个错误码需要去按调用链路跟踪,给问题定位带来巨大的复杂度。
3. 函数不存在失败的情况,建议申明函数为void返回类型
如果函数不存在失败的情况,建议申明函数为void返回值类型,降低使用者是否要做返回值检查的判断,同时也降低异常处理分支的复杂度。
4.异常处理尽可能集中,避免边执行业务边考虑异常处理
异常处理设计尽可能集中,并且能通过检查资源handle是否存在,如果存在释放资源并去初始化handle,这样能兼容各种问题场景而不是每种问题场景分析设计要释放哪些资源。
思路三:编码实现的简洁写法
1.功能实现优先选择 现有库或者平台接口,避免自己造轮子
遇到一个如list链表管理,字符串比对等 基础操作,优先选择linux或者平台提供的公共接口,避免重复实现,这样更安全而且往往效率更高
2.入参基本类型用传值 数据结构用指针(优先const)
基本的类型用传值更安全,数据结构为了提升效率(用数值会有参数压栈消耗)会选择指针,优先选择const指针比避免被修改。
3.使用卫语句 和 查表法 简化条件分支
少数分支建议用if else,分支较多建议用查表法,少用switch case;
4. 避免不必要的初始化 赋值语句,局部变量
比如:int a = 0; a = getXXXXX();建议直接写为 int a = getXXXXX();
5. 使用函数返回值作为出参,可以简化调用者使用;
比如int a; getXXXX(&a); return a; 建议直接写为 return getXXXXX();