放之四海皆适用的设计原则(二)
放之四海皆适用的设计原则(二)
让模块善始善终
在软件行业,模块化设计早已深入人心,因为通过模块化这种“分而治之”的方法能有效地降低设计的复杂度。如何获得更好的模块化设计不是这里要讨论的重点,本书只关注模块的初始化与终止化这两个关键点。
在大多的嵌入式系统中,模块的运行是从调用它的初始化函数开始的。与模块大多有初始化函数相比,忽视为模块设计终止化函数这种现象却很普遍。与运行在桌面操作系统上的软件不同的是,通常整个嵌入式设备就只有一个应用软件在运行,因此对软件启停不少会采用开关设备电源或按下重启按钮这种“粗暴的”方式来完成,久而久之大家将为模块设计终止化函数当做了多余。
首先,从完整性的角度来看,一个模块如果提供初始化函数,那么也应当设计终止化函数。其次,为每一个模块设计终止化函数,意味着提供了一种“优雅地”关闭系统的手段,进一步的内涵是,通过这种方式将为我们创造检测系统资源泄漏的时机。
让我们站在堆管理模块的角度来检查优雅终止模块所带来的好处。这里假设堆管理模块具备记录每一次内存分配所发生的位置信息这一功能。以它为例,是因为内存泄漏是嵌入式软件开发中比较让人头痛的问题。
如果一个系统中所有模块的终止行为都不经过各自的终止化函数的话,堆管理模块就无法通过它所记录的分配位置信息来了解是否存在内存泄漏问题。如果只为堆模块提供终止化函数也同样无法发现内存泄漏问题,因为其他模块可能在初始化时分配内存,且这些内存在整个软件生命周期中都需要使用。在系统终止时,如果这些内存不被各模块自行释放的话,堆管理模块无法在它的终止化函数中判断哪些内存发生了泄漏。
如果为每一个模块都设计终止化函数,就能做到更容易检测内存泄漏。如果那些在初始化函数中分配内存的模块在终止化时进行内存释放操作,且假设所有使用了动态内存的模块的终止化函数是在堆管理模块的终止化函数之前被调用的,那么当堆管理模块的终止化函数被调用时,就可以根据所记录的信息找到没有释放的内存(泄漏点)。
依此类推,其他的资源也可以采用这一方法发现泄漏。比如,在定时器管理模块的终止化函数中可以检查是否所有的定时器都已回收了。
在模块的终止化函数中检查所管理资源是否存在泄漏需要解决一个问题,即各个模块的依赖关系,以保证各资源管理模块的终止化函数是在使用它(所管理资源)的模块之后被调用的。第14章所引入的模块分层与分级的概念将有助于解决模块间的依赖关系。
本书操作系统篇中的多个章节采用了这一设计原则以防范资源泄漏,读者在阅读这些章节时将加深对这一设计原则的理解。
重视收集统计信息
embedded/code/platform/common/inc/primitive.h
00029:typedef unsigned int u32_t;
00047:// for statistic
00048:typedef u32_t statistic_t;
00049:
embedded/code/platform/timer/v3/src/timer.c
00068:typedef struct {
00069:dll_t dll_;
00070:statistic_t hit_;
00071:statistic_t redo_;
00072:csize_t reentrance_;
00073:csize_t level_;
00074:} bucket_t;
00075:
00076:typedef struct {
00077:statistic_t notimer_;
00078:statistic_t traversed_;
00079:statistic_t abnormal_;
00080:} timer_statistic_t;
00081:
00082:static timer_statistic_t g_statistic;
图13.7
运用这一设计原则,需要我们在设计过程中时刻思考哪些信息对软件查错和调优有帮助。统计项的设计并不要求一步到位,可以在软件生命周期的任何阶段根据需要而增加。
借助命名传达设计意图
程序代码是设计的物质外壳,再好的思想必须最终通过代码去表达,而这就离不开对函数、变量和参数进行恰当的命名,以便准确地传达设计意图。让我们通过一个例子来说明命名对设计的重要性。
假设需要设计一个双向链表的操作函数,其功能是删除链表头结点并将之当做函数的返回值返回,那如何给这一函数取名呢?
那dll_extract_head()呢?同样不好,因为“extract”也不能表达从链表中删除结点的意思。用过WinZip英文版的读者或许注意到了,其中解压文件就用了“extract”这个词。
比较合适的命名是dll_pop_head()。“pop”这一动词源于退栈操作,当从栈中“弹出”一个元素时,意味着这一元素将从栈中被删除并返回。将“pop”引入双向链表的函数名中能准确地传达设计意图。
除了函数名的命名很重要外,函数参数和变量的命名同样重要,因为它们能起到“点睛”的作用。对于图13.8中的两个函数,从参数名就能完全明白如何用,任何解释用法的注释都显得多余。
embedded/code/platform/common/inc/dll.h
void dll_insert_before (dll_t *_p_dll, dll_node_t *_p_ref,
dll_node_t *_p_inserted);
void dll_insert_after (dll_t *_p_dll, dll_node_t *_p_ref,
dll_node_t *_p_inserted);
图13.8
软件设计的最终产物不能是一堆难读的代码;相反,代码应当努力做到让人读起来“行云流水”。好的设计在看完它的接口函数和数据结构后就知道如何使用它,因为它们的命名向人传达了模块的行为。从这一点说来,花时间斟酌命名是值得的,因为它节省了他人用于理解的时间。
在不少资料中强调注释对于编码的重要性,甚至提出程序应有三分之一的篇幅是注释。对于“三分之一”这一提法,作者并不赞同。原因是:
n 我们在读程序时的第一反应是读代码而不是注释。如果代码能清楚地表达意思,那就没有写注释的必要,即使写了那也一定是多余。如果注释占了整个项目源程序的三分之一,作者怀疑其中很多都是废话,只是为了做到“占三分之一”而已。
n 注释与代码很容易在维护的过程中失步,因此会出现注释所发出的声音与源代码实现完全不同这种尴尬。一旦注释被发现不精确它就会被人遗忘,也就起不到注释应有的效果。
与“三分之一”提法不同的是,作者认为注释应当尽可能少,并将写注释所省下来的时间用于推敲命名。请注意,千万不要误认为“少注释是好程序的充分条件”,而应当理解为“少注释是好程序的必要条件”。
诚然,这并不是在否定注释。大部分情形下,通过命名就能清楚地表达程序实现的局部思想,而注释应当放眼于全局去写以起到提纲挈领的作用,或者某些行为打破了常规(比如,switch语句块内的case与break不成对)也可以考虑通过注释加以解释。如果命名实在无法做到“传神”或打破了常识的话,也可以考虑采用少量的注释进行弥补。
消除“审美告警”
人天生就是审美家,软件工程师在进行软件设计时这种天性会自然地发挥作用。将设计感觉当做是一个设计原则,多少让人觉得有点不靠谱,但这一原则也正体现了软件设计中的艺术成分。
就作者的经验来看,如果做设计时觉得别扭,那工作效率一定不高;反之,则工作效率奇高。软件设计真正花时间的是思考,而不是编码。思考的目的是从纷繁的现象中试图找到问题的本质,或者从众多的因素中找出关键。设计时之所以出现“审美告警”,一定是有什么没有考虑清楚。这种情形下停下来做进一步的思考,将有助于理清思路,以最终获得更好的设计。
相信每个软件工程师或多或少都能感觉到“审美告警”,而信号的强弱与软件工程师的设计水平可能是正相关的。软件工程师如果重视这种信号,则这种信号的灵敏度也会慢慢提高。因为重视它意味着将进行更多的思考,而思考多了就更容易形成自己的设计原则和思想;相反,如果长期忽视它的存在,则最终可能会造成这种信号的消失。忽视“审美告警”的存在,或许意味着我们并不关心所设计的主题,其质量也别指望好到哪儿去,更有甚者会酝酿出将来的一个“毒瘤”。
本文选自《专业嵌入式软件开发——全面走向高质高效编程(含DVD光盘1张)》一书
图书详细信息:http://blog.csdn.net/broadview2006/article/details/7089444
放之四海皆适用的设计原则(二)相关推荐
- 架构设计:不存在放之四海皆准的解决方案
"不存在放之四海皆准的解决方案" 软件方案设计过度,偏离了实用性和眼前的基本需求: 解决性能问题时,提出的建议不合理. 不存在万能钥匙,架构师必须培养和训练情境意识,才能更好地设计 ...
- 不存在放之四海皆准的解决方案
作者:兰迪·斯塔福特(RandyStafford) 架构师应该坚持培训"情境意识"(contextual sense)--因为我们遇到地问题千差万别,不存在放之四海皆准的解决方案. ...
- JAVA组件设计原则(二)原则一:精准解决共性问题(摘自《java组件设计》)
组件定位:精准解决共性问题 组件的产生,来源于软件工程实践中,对重复.反复出现.普遍的.有相似性的问题进行分析,剥离掉各个问题的特性,抽取各个问题之间的共性,然后确定要设计一个或多个组件,这样 ...
- 机房为什么要设计冷热通道,设计原则及如何施工?
(一)机房为什么要设计冷热通道? 因为机房通过"热通道"(Hot Aisle)和"冷通道"(Cold Aisle)的方式,改变以往数据中心机柜面朝同一方向摆放的 ...
- 牛散NO.3:MACD放之四海 假作真时真亦假
大宗商品日线"异曲同工夺命勾魂枪" 话说有实战意义的技术在任何资本市场里都能产生出神奇的效果.不能说放之四海皆准,但至少起到触类旁通的"牵强"吧.大宗商品特别是 ...
- 设计模式与设计原则 —— 一句话
一.设计原则 二.设计模式 范围\目的 创建型模式 结构型模式 行为型模式 类模式 工厂方法 (类)适配器 模板方法.解释器 对象模式 单例 原型 抽象工厂 建造者 代理 (对象)适配器 桥接 装饰 ...
- Erlang/OTP设计原则(文档翻译)
http://erlang.org/doc/design_principles/des_princ.html 图和代码皆源自以上链接中Erlang官方文档,翻译时的版本为20.1. 这个设计原则,其实 ...
- 面向对象编程,设计原则,设计模式
2019独角兽企业重金招聘Python工程师标准>>> 面向对象编程,设计原则,设计模式 面向对象编程 面向对象编程与面向过程编程的区别 面向对象软件开发的优点 面向对象编程语言 C ...
- 软件观念革命:交互设计精髓_万字干货,交互设计精髓105条设计原则(附中英PDF资料)...
<About Face: 交互设计精髓>称得上是交互设计的圣经,用"工作必备,常看常新"来形容也不为过.书中的每句话对平时的实践都有着指导意义.更难能可贵的是,这本圣经 ...
- 面向对象设计原则之三--依赖倒置(转)原则
在大多数情况下,这三个设计原则会同时出现,开闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段,它们相辅相成 如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是面向对象设计的主要实现机制之 ...
最新文章
- JavaEE Tutorials (15) - 对Java持久化API应用使用二级缓存
- 将win7电脑变身WiFi热点,让手机、笔记本共享上网
- Luogu 3008 [USACO11JAN]道路和飞机Roads and Planes
- 怎么关闭微信微众银行账户服务器,微信微众银行要怎么关闭,微信微众银行注销方法...
- 补码原理一个字节存储数值的范围如何计算
- nyoj 55 懒省事的小明 优先队列 multiset 还有暴力
- 开源开放 | DeepKE发布新版本:支持低资源、长篇章、多任务的图谱抽取开源框架(浙江大学)...
- 【java】Java实现单向链表反转
- 随想录(MFC开发有感)
- Redis(一)面试总结精讲
- maya curveflow修改
- 【在线分享】考研数学思维导图+高数思维导图+汤家凤重点笔记+武忠祥重点笔记以及高数Xmind思维导图
- 51单片机学习制作定时闹钟及《花海》演奏
- 如何按照滑环的原理来自制简易导电滑环
- pytorch中的transforms介绍
- 华为是ICT人才培养的“黑土地”,谁又是浇水人呢?
- 初识C语言--第二弹
- 20172313 2017-2018-2 《程序设计与数据结构》第十一周学习总结
- CSS3垂直水平的居中的几种方法
- 用python实现csdn博主全部博文下载,html转pdf,有了学习的电子书了。。。(附源码)