关于程序员:showmebadcode改善既有代码的设计谈谈我的真实案例

48次阅读

共计 4973 个字符,预计需要花费 13 分钟才能阅读完成。

作者简介:

recan RT-Thread 社区成员 http://yyds.recan-li.cn

一个专一于嵌入式软件架构设计的新生代农名工。

点这里去意识他吧

1. 写在结尾

在本流动开始之前,十分荣幸地收到小师弟的邀请,
看是否帮忙想想 1024 程序员节的 RT-Thread 论坛流动好点子,在此非常感谢对我的信赖。
想起早年在浏览一些博文的时候,对国外代码社区举办的 ” 烂代码较量 ” 印象粗浅,
看到那些牛人提交的 ” 目迷五色的 bad-but-work-well 的代码,几乎是拜服得嗤之以鼻。
天然咱们大部分的程序员还很难提供出这么秀的代码,
所以基于这个准则,联合咱们的理论状况,
批改了下比赛规则,重在娱乐参加,旨在代码晋升。

更多对于此次 show-me-bad-code 流动的介绍,请点击这里。

 我来提供一段代码
2.1 代码背景

这段代码的原型来自于早几年在一家 POS 行业内头部企业供职时的实在案例代码,基于这套代码也是过了各种 POS 机的行业认证。

通过 10 多年的积淀,基于该模板代码移植 / 革新而来的应用程序,罐装的 POS 机累计装机量应该在 KW 级别,堪称就是传说中妥妥的【祖传代码】。

晚期代码的运行环境是嵌入式 Linux 零碎,总内存高达 128MB,还是十分充裕的;前面才缓缓切为 RTOS 零碎,内存也锐减到 64MB,但即使这样,给到应用程序的内存也是十分短缺的。

2.2 代码性能

这段代码的次要性能就是在 POS 机触发交易时的界面(带 LCD 显示)下,可能自动识别出以后要应用的各种交易方式,其中包含:

是否刷了磁条卡?(刷了磁条卡,走刷磁条卡的交易流程)

是否插入了 IC 卡?(插入 IC 卡,走银联的 IC 卡交易流程,行内称 PBOC 流程)

是否应用了 IC 卡做非接触交易(俗称:挥卡)?(挥卡交易,走银联的 IC 卡疾速交易,行内称 qPBOC 流程)

是否扫描到了微信 / 支付宝这类的领取条码?(条码领取与规范银行卡交易是齐全不一样的流程)

是否按下了【勾销键】,用户被动退出了交易?

是否在规定的工夫内(个别 60S)没有任何的交易产生,导致超时?

除这些性能外,因为 POS 机交易是须要保留最近 N 笔交易记录的,所以这个解决交易的流程中,还得保护交易记录的保留。而代码中用于保留交易记录的构造体也是十分十分十分的宏大,大到一个构造体就占用靠近 2KB,这是十分恐怖的。

2.3 代码片段

为了仅阐明状况,而不走漏具体的技术细节,这里我仅提供伪代码,留神代码里的正文,是我为了辅助介绍而本人增加的,原来的代码中,正文比这少得可怜。

1/**
2 * 保留交易记录的构造体定义
3 */
4typedef struct _transaction_log_t {
5 char time_stamp[32];
6 uint8_t trans_type;
7 uint8_t trans_no;
8
9 / 各式各样的数据定义长达 20-30 个变量 /
10 uint8_t data1[64];
11 …
12 uint8_t datan[128];
13} transaction_log_t;
14
15/ 全局的交易 log 变量,被各个解决流程的文件援用 /
16transaction_log g_log_info;
17
18/**
19 * 反对的交易方式的掩码
20 */
21#define TRANSACTION_SWIPE_CARD 0x01
22#define TRANSACTION_INSERT_CARD 0x02
23#define TRANSACTION_TAP_CARD 0x04
24#define TRANSACTION_CODE_PAY 0x08
25
26/**
27 * 解决交易的流程总入口
28 */
29int all_transaction_process(int timeouts_ms, int transaction_flag)
30{
31 int start_time, end_time;
32
33 end_time = 0;
34 start_time = get_local_time();
35
36 / 循环判断须要反对的交易方式,直到触发了某种交易或者超时 /
37 while (1) {
38 if (transaction_flag & TRANSACTION_SWIPE_CARD) {
39 / 判断刷卡:阻塞式的接口,带超时工夫 /
40 ret = get_swipe_card(10);
41 if (ret == ok) {
42 / 解决刷卡流程 /
43 …
44 return 0;
45 }
46 } else if (transaction_flag & TRANSACTION_SWIPE_CARD) {
47 / 判断插卡:阻塞式接口,带超时工夫 /
48 ret = get_insert_card(10);
49 if (ret == ok) {
50 / 解决插卡流程 /
51 …
52 return 0;
53 }
54 } else if (transaction_flag & TRANSACTION_SWIPE_CARD) {
55 / 判断挥卡:阻塞式接口,带超时工夫 /
56 ret = get_tap_card(10);
57 if (ret == ok) {
58 / 解决挥卡流程 /
59 …
60 return 0;
61 }
62 } else if (transaction_flag & TRANSACTION_SWIPE_CARD) {
63 / 判断条码领取 /
64 ret = get_code(10);
65 if (ret == ok) {
66 / 解决条码流程 /
67 …
68 return 0;
69 }
70 }
71
72 end_time = get_local_time();
73 if (start_time + timeous_ms == end_time) {
74 / timeout /
75 return -1;
76 }
77
78 / 界面显示倒计时 /
79 lcd_update_timetous();
80
81 / 循环延时 /
82 delay_ms(10);
83 }
84}
85
86/**
87 * main 函数总入口
88 */
89int main(int argc, const char *argv[])
90{
91 / 利用初始化 /
92 system_init();
93
94 / 解决备份及零碎异样断电引发的冲正交易 /
95 handle_bakup();
96
97 / main loop /
98 while (1) {
99 / 每 10ms 获取下以后有没有按键触发交易 /
100 key = get_key_ms(10);
101 if (key1 == key) {
102 / 有触发交易需要 /
103 ret = all_transaction_process(60*1000,);
104 } else if (key2 == key) {
105 / 其余菜单操作 /
106 …
107 } else {
108 / 超时或未知按键输出 /
109 …
110 }
111
112 / 刷新显示在 LCD 上的工夫 /
113 update _time_dispaly();
114 }
115
116 return 0;
117}

2.4 “ 烂 ” 的理由

这里说下面的代码 ” 烂 ” 并不是说它工作得不行,相同,它确实工作得能够,从装机量和各种行业认证就能够看得出来,还是扛得住市场对它的考验。这里提出它 ” 烂 ” 的起因,次要思考是从代码设计和代码保护的角度来看。

代码保护上:全局变量全盘援用,乱串于各个文件中,可读性十分差

代码设计上:数据结构没有规划设计,导致构造体定义太简单,太过硕大无朋,冗余空间节约大

代码设计上:所有交易流程的判断解决,太过于死板,一个 if-else 判断到底,耦合重大,可扩展性十分差

代码设计上:LCD 显示与外围业务流程跳转参杂在一起,没有解耦,往往改了业务性能的同时还要改 UI 实现

代码性能上:流程解决中,大量应用带超时工夫的阻塞式函数,导致整个流程响应的时效性不是很现实

 改善既有代码的设计
3.1 改善代码前的思考

大家都戏称祖传代码,请勿随便改变,但我感觉真正优良的代码,是缓缓被迭代,被重构而来的,否则的代码就会被一步步地堆砌起来,直到有一天,没人可能再批改保护了,那一刻就如同一栋大厦轰然倒塌,这是十分蹩脚的。

正是基于这些的思考,过后我也是很大胆地向主管提出,咱们应该从小面积开始做代码重构,逐步改善一些有缺点的代码设计。

比拟遗憾的是,主管是绝对激进的,没有承受前面大面积重构的施行,次要还是放心期间的危险,影响里面的程序降级。从商业的角度上,我是反对他的;然而,从代码的角度,我还是保持我的观点;当然这并不影响咱们的共事。
3.2 我要如何改善这些代码

先说下,主管答允我的小面积重构局部,我是怎么做的!

【代码保护上:全局变量全盘援用,乱串于各个文件中,可读性十分差】

针对这一点,我重新整理了局部的 C 代码布局,明确要求非十分必要的设计,不容许全局变量在多个 C 文件中批改传递,尽量管制在一个 C 文件,同时要害的数据都封装成 get/set 接口,缩小通篇批改数据的可能性。

在 code review 环境,着重对此类代码做重点审查,一经发现,务必打回从新批改提交。

【代码设计上:数据结构没有规划设计,导致构造体定义太简单,太过硕大无朋,冗余空间节约大】

针对这一点,我的办法就是从新梳理,咱们须要用到的必备数据,把所有非必须的数据全副删除,同时一些数据 buffer 的长度,再严格评估其真正的内存需要空间,而不是一上来就定义 32 字节 /64 字节 /128 字节。

还有一个方面,就是尽量思考字节对齐的问题,定义构造体的时候,多思考思考。

最初的实际计划,省下了一半的空间。

再说下,过后我提出的实施方案,然而被按下的重构计划!

代码设计上:所有交易流程的判断解决,太过于死板,一个 if-else 判断到底,耦合重大,可扩展性十分差

代码设计上:LCD 显示与外围业务流程跳转参杂在一起,没有解耦,往往改了业务性能的同时还要改 UI 实现

代码性能上:流程解决中,大量应用带超时工夫的阻塞式函数,导致整个流程响应的时效性不是很现实

这三点,我认为都应该通过改善整体的代码架构来晋升,上面简略说说的重构思路。

为了解决此类有业务解决流程,又有 UI 输出和 UI 显示的解决场景了,有一个比拟成熟的软件模型,叫 MVC 模型。

MVC 模式代表 Model-View-Controller(模型 - 视图 - 控制器)模式。

Model(模型)– 模型代表外围业务解决模块。它也能够带有逻辑,在数据变动时更新控制器。

View(视图)– 视图代表模型蕴含的数据的可视化,即 UI 局部。

Controller(控制器)– 控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变动时更新视图。它使视图与模型分来到。

依据这个实践模型,利用到我的工程上就是:

Controller:控制器,对应在这里就是,各式各样的事件监听器,把各种用户可能输出的形式形象成一个个事件,而每个事件都有对应的监听器,当监听器辨认到了对应事件的产生,立马触发事件给到模型层。

Model: 模型,这应该整个设计中代码最重的局部,次要是各个处理事件的解决,独立形象成一个个模型,这些模型层互不烦扰,也易于扩大,有新的事件须要解决就从新定义个模型即可。

View:视图,就是最简略的 UI 界面,它负责对 Model 提供的数据最 UI 界面的更新,比方刷新工夫,比方显示倒计时,比方提醒用卡信息等等。

它的整一个示意图如下所示:

利用 MVC 框架的最大益处就是把 M 和 V 拆散开来,数据和视图解耦,使得数据易于解决并存储,同时也易于扩大,这里的扩大包含视图扩大和模型扩大,从一变 N 变得更加容易。

另外很重要的一点,在性能上,解决的实时性大大晋升了,而不是简略的阻塞、延时这种粗犷的办法,取之而来的各种异步监听,疾速响应,这一点我感觉在波及到 UI 的利用场景下,都是应该要着重思考的局部。

 更多思考

代码是无止境的,没有最好的代码实现,然而往往有更好的代码实现。

很多奇奇怪怪的 BUG,在代码设计和代码编写阶段其实曾经埋下了隐患,只不过短时间没有裸露进去而已。

这也就要求咱们这些代码工作者,在敲代码之前,多想想设计思路,尽量把你的思路,你的流程通过图表的模式表达出来,

再不济,也应该要造成文档,以便于随时能够复盘你的代码实现是否偏离了你本来的设计。

毫不夸大地说,一个在设计阶段就有缺点的代码架构,再怎么润饰也将于事无补。

这也是我目前转入软件架构师岗位,得出的最间接的心得。

最初愿这个世界的程序越来越强,BUG 越来越少;

不过,这样的话,那咱们岂不是要就业了?

戳这里进入原帖:https://club.rt-thread.org/as…

正文完
 0