文章目錄
- 1. 題外話
- 2. 我來提供一段代碼
- 2.1 代碼背景
- 2.2 代碼功能
- 2.3 代碼片段
- 2.4 "爛"的理由
- 3. 改善既有代碼的設計
- 3.1 改善代碼前的思考
- 3.2 我要如何改善這些代碼
- 4. 更多思考
1. 題外話
在本活動開始之前,非常榮幸地收到RRT小師弟的邀請,
看能否幫忙想想1024程式員節的論壇活動好點子,在此非常感謝對我的信任,
想起早年在瀏覽一些博文的時候,對國外代碼社區舉辦的"爛代碼比賽"印象深刻,
看到那些牛人提交的"眼花繚亂的bad-but-work-well的代碼,簡直是佩服得五體投地,
自然我們大部分的程式員還很難提供出這么秀的代碼,
所以基于這個原則,結合我們的實際情況,
修改了下比賽規則,重在娛樂參與,旨在代碼提升,
更多關于此次show-me-bad-code活動的介紹,請點擊這里,
2. 我來提供一段代碼
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 代碼片段
為了僅說明情況,而不透露具體的技術細節,這里我僅提供偽代碼,注意代碼里的注釋,是我為了輔助介紹而自己添加的,原來的代碼中,注釋比這少得可憐,
/**
* 保存交易記錄的結構體定義
*/
typedef struct _transaction_log_t {
char time_stamp[32];
uint8_t trans_type;
uint8_t trans_no;
/* 各式各樣的資料定義長達20-30個變數 */
uint8_t data1[64];
...
uint8_t datan[128];
} transaction_log_t;
/* 全域的交易log變數,被各個處理流程的檔案參考 */
transaction_log g_log_info;
/**
* 支持的交易方式的掩碼
*/
#define TRANSACTION_SWIPE_CARD 0x01
#define TRANSACTION_INSERT_CARD 0x02
#define TRANSACTION_TAP_CARD 0x04
#define TRANSACTION_CODE_PAY 0x08
/**
* 處理交易的流程總入口
*/
int all_transaction_process(int timeouts_ms, int transaction_flag)
{
int start_time, end_time;
end_time = 0;
start_time = get_local_time();
/* 回圈判斷需要支持的交易方式,直到觸發了某種交易或者超時 */
while (1) {
if (transaction_flag & TRANSACTION_SWIPE_CARD) {
/* 判斷刷卡:阻塞式的介面,帶超時時間 */
ret = get_swipe_card(10);
if (ret == ok) {
/* 處理刷卡流程 */
...
return 0;
}
} else if (transaction_flag & TRANSACTION_SWIPE_CARD) {
/* 判斷插卡:阻塞式介面,帶超時時間 */
ret = get_insert_card(10);
if (ret == ok) {
/* 處理插卡流程 */
...
return 0;
}
} else if (transaction_flag & TRANSACTION_SWIPE_CARD) {
/* 判斷揮卡:阻塞式介面,帶超時時間 */
ret = get_tap_card(10);
if (ret == ok) {
/* 處理揮卡流程 */
...
return 0;
}
} else if (transaction_flag & TRANSACTION_SWIPE_CARD) {
/* 判斷條碼支付 */
ret = get_code(10);
if (ret == ok) {
/* 處理條碼流程 */
...
return 0;
}
}
end_time = get_local_time();
if (start_time + timeous_ms == end_time) {
/* timeout */
return -1;
}
/* 界面顯示倒計時 */
lcd_update_timetous();
/* 回圈延時 */
delay_ms(10);
}
}
/**
* main函式總入口
*/
int main(int argc, const char *argv[])
{
/* 應用初始化 */
system_init();
/* 處理備份及系統例外斷電引發的沖正交易 */
handle_bakup();
/* main loop */
while (1) {
/* 每10ms獲取下當前有沒有按鍵觸發交易 */
key = get_key_ms(10);
if (key1 == key) {
/* 有觸發交易需求 */
ret = all_transaction_process(60*1000, );
} else if (key2 == key) {
/* 其他選單操作 */
...
} else {
/* 超時或未知按鍵輸入 */
...
}
/* 重繪顯示在LCD上的時間 */
update _time_dispaly();
}
return 0;
}
2.4 "爛"的理由
這里說上面的代碼"爛"并不是說它作業得不行,相反,它的確作業得可以,從裝機量和各種行業認證就可以看得出來,還是扛得住市場對它的考驗,這里提出它"爛"的原因,主要考慮是從代碼設計和代碼維護的角度來看,
-
代碼維護上:全域變數全盤參考,亂串于各個檔案中,可讀性非常差
-
代碼設計上:資料結構沒有規劃設計,導致結構體定義太復雜,太過龐然大物,冗余空間浪費大
-
代碼設計上:所有交易流程的判斷處理,太過于死板,一個if-else判斷到底,耦合嚴重,可擴展性非常差
-
代碼設計上:LCD顯示與核心業務流程跳轉參雜在一起,沒有解耦,往往改了業務功能的同時還要改UI實作
-
代碼性能上:流程處理中,大量使用帶超時時間的阻塞式函式,導致整個流程回應的時效性不是很理想
3. 改善既有代碼的設計
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界面的更新,比如重繪時間,比如顯示倒計時,比如提示用卡資訊等等,
它的整一個示意圖如下所示:
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-axIJ4jSL-1635150688123)(C:\Users\redtea\AppData\Roaming\Typora\typora-user-images\image-20211025153225831.png)]](https://img.uj5u.com/2021/10/26/277838260839532.png)
應用MVC框架的最大好處就是把M和V分離開來,資料和視圖解耦,使得資料易于處理并存盤,同時也易于擴展,這里的擴展包括視圖擴展和模型擴展,從一變N變得更加容易,
另外很重要的一點,在性能上,處理的實時性大大提升了,而不是簡單的阻塞、延時這種粗暴的方法,取之而來的各種異步監聽,快速回應,這一點我覺得在涉及到UI的應用場景下,都是應該要著重考慮的部分,
4. 更多思考
代碼是無止境的,沒有最好的代碼實作,但是往往有更好的代碼實作,
很多奇奇怪怪的BUG,在代碼設計和代碼撰寫階段其實已經埋下了隱患,只不過短時間沒有暴露出來而已,
這也就要求我們這些代碼作業者,在敲代碼之前,多想想設計思路,盡量把你的思路,你的流程通過圖表的形式表達出來,
再不濟,也應該要形成檔案,以便于隨時可以復盤你的代碼實作是否偏離了你原本的設計,
毫不夸張地說,一個在設計階段就有缺陷的代碼架構,再怎么修飾也將于事無補,
這也是我目前轉入軟體架構師崗位,得出的最直接的心得,
最后愿這個世界的程式越來越強,BUG越來越少;
不過,這樣的話,那我們豈不是要失業了?
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/336301.html
標籤:區塊鏈
上一篇:以太坊搭建私鏈
