6.8 配置軟件和維護軟件

在前面的章節中,我們已經多次提及配置軟件和維護軟件,大家或許也認識到它們是整個平臺架構中不可或缺的一環。然而現實經常是無奈的,我的職場生涯中,這兩個軟件模塊也是命運多舛。

國內的軟件氛圍不好,企業捨得爲硬件設備花錢,但都不樂意爲純軟件項目花錢,會有一種想當然的感覺軟件不值錢。在這種大環境下,企業也只好將更多資源往嵌入式設備領域傾斜,至於外圍軟件,能省則省吧。

如我早期研發經歷,要做一款新產品,只要市場部認可,領導一般會大力支持。但如果想寫一款維護軟件,公司會立馬意識到投入大且不產生直接經濟效益,因此是否能爭取到資源就不確定了。

記得那時做一些調試工作非常艱難,如想觀察某一變量,只好臨時修改液晶輸出界面。不僅囉嗦,而且受限,如想同時觀察較多的變量就比較困難,還想觀察其他變量就需要修改程序重新來過。

在一次規約調試時,我發現自己沒有了調試手段,因爲是項目組自定義規約,連藉助外力的渠道也被堵死了。被逼無奈,我用VC和MFC框架快速構建了一個簡易對話框,雖然是手動組幀,但至少搭建出一簡陋的調試環境。
在這裏插入圖片描述
再後來,需要調試的功能越來越多,對話框放不下了,我被迫重新組織了界面,左側outlook觸發控件,右側爲了省事,乾脆約定爲表格算了。
在這裏插入圖片描述
此時,其他項目組看到我們有這個好東東,立馬想挪過去用。然而,願望是美好的,現實時殘酷的,大家發現我這個簡陋版維護軟件和具體產品緊緊綁定在一起,根本挪不動。

更爲奇葩的是,你沒有維護軟件時公司不會給你資源去做,但當你弄了一簡陋版本後,大家會默認你已經搞定了維護軟件,各種需求立馬紛至沓來,讓人苦不堪言。

相對於配置軟件,維護軟件還算幸運,畢竟總有一些公司會給用戶額外贈送免費維護軟件,我們沒見過豬跑但總還是喫過豬肉的,因此大家一有空還是會努力嘗試去構建一款維護軟件,但大家在很長一段時間內壓根認識不到配置軟件的價值。

早期產品研發時,也經常面臨多款產品數據不一致,當時我們習慣手動構建各種數據表;後來碰到液晶模塊需要提煉漢字的場合,大家在合力構建一款小程序;碰到GUI模塊需要繪製液晶界面時,我們基於MFC類庫drawCli例程快速構建出一個小程序;……,總之,所有的行爲都是被動的下意識行爲,從未主動的去思考過這些小程序對整個架構的意義。

在跨國公司研發期間,聽架構師講解程序框架時,經常會自然而然的提及配置軟件和維護軟件,尤其是配置軟件,經常處於核心的位置。也可能是厚積薄發,過往那些零零碎碎的痛苦經歷,匯成了靈感,讓我突然有一種豁然頓悟的感覺。從此,在我心目中,配置軟件和維護軟件不再是可有可無的角色,而是架構的有機一環。

◇◇◇

從整體架構的角度思考,配置軟件用於彌合統一平臺之上各產品的差異部分,是屬於研發階段的配套軟件。

我們以前無意識做的各種小程序都屬於配置軟件的範疇,如漢字字庫提煉、液晶界面繪製等。但如果將配置軟件作爲單獨概念提煉出來後,就會衍生出很多新的價值。

一個很經典的例子是關於字符串。在嵌入式系統中,字符串長度頗讓人尷尬,如下所示保護設定值結構。

struct TSetting
{
	char szName[32];			/* 定值名稱 */
	char szDescribe[64];		/* 定值描述 */
	......
}

我們約定定值名稱爲32個字符串,最大16個漢字。這個長度一般是夠的,但每年總有一些特殊場合不夠,碰到較真的客戶,大家就會被折騰的頗爲苦惱。僅名稱還算是好的,定值描述信息更爲尷尬,大部分不需要,但恰恰有少部分需要較長的字符串。(注:上述結構中名稱在前描述在後包含着一個程序技巧,針對特定現場定值名稱過長的情況,可以佔用定值描述部分,此時可約定定值描述等於定值名稱)

定值名稱或描述大部分是靜態字符串,一個更巧妙的策略是將所有字符串信息壓縮存儲起來,而以索引的方式去訪問這些字符串。此時結構如下:

struct TSetting
{
	WORD wName;			/* 定值名稱 */
	WORD wDescribe;		/* 定值描述 */
	......
}

這種策略非常節約flash資源,但要達到這種效果,必須收集彙總去重整個產品中所有用到的靜態字符串信息。字符串信息分佈在幾乎所有模塊中,如定值、液晶界面等,在沒有統一的配置軟件之前,這幾乎是一個不可能完成的任務。

再比如字節順序問題,不同的芯片有不同的字節順序,主要有大端模式和小端模式兩類。原先設備接受到一個特定的參數文件後,必須重新調整字節順序後才能使用。有配置軟件支撐,我們可以直接生成特定字節順序的參數文件,此時設備不僅不需要調整字節順序,甚至都不需要將參數文件從flash拷貝到ram空間,僅需要完成指針映射即可。

很多國際化產品的說明書都很厚,幾百上千頁的,對比自己產品可憐的幾十頁說明書,好尷尬!在跨國公司研發的那段經歷,我終於知道這些厚厚的說明書是如何泡製出來的了,大部分附錄部分都是由配置軟件自動生成的,真正人寫的部分和我們差不了太多。

平時大家可以留意,如果哪些廠家的說明書有着厚厚的附錄,那麼該參加極有可能程序架構水平已經迭代到了一定的高度,這些公司的產品質量應該也是比較高的,如果是新人,或許這類公司也更適合加盟。

隨着高複用嚴格平臺化架構的持續迭代,配置軟件會被賦予更多的功能。如我當初參與的項目中,抽取各種可複用模塊並自動生成配置表、程序多CPU分佈時自動生成各CPU之間通訊配置表、自動生成用戶說明書附錄部分等等。

記得那個架構師經常感嘆,配置軟件的高度決定了架構設計的高度。瞅一眼自己那個頗爲寒酸的配置軟件,兩相比較,還好,我們還有比較大的成長空間,>_<。

◇◇◇

早期,維護軟件在我心目中,就是我們自己人偶然用一下的輔助軟件,頂多額外用於幫助工程人員現場設備調試。將維護軟件作爲架構設計中單獨一環考慮後,維護軟件的範疇就擴大了。如果說配置軟件是研發階段中的軟件,那麼維護軟件就是針對成品的軟件。

工業產品一般對可靠性要求極高,測試一直是比較頭疼的一環。爲了減少測試工作量,很多重複的測試工作我們會通過自動化測試完成。爲了構建自動化測試框架,就需要維護軟件配合。

如微機保護的自動化測試,通過維護軟件觸發繼保測試儀加特定電氣量,並讀取繼保遙測值,可以很容易的判斷計算精度。如果能將各種電氣量輸出驗證過程組織成腳本,並自動生成報表,就已經構建出一套自動化測試環境。

既然可以指導測試,就可以指導生產。硬件總會存在一定的差異,設備出廠時需要進行係數矯正。這是一個需要耐心且頗爲繁瑣的工作,如由維護軟件自動觸發並進行自動係數矯正,不僅效率高,精度也比人工校驗的高。

這些都可以算是維護軟件的初級功能,在跨國公司研發的那段經歷,真正觸動我的是基於維護軟件的二次研發環境。基於高複用軟件模塊和腳本化等策略,這樣做出來的產品自然具備着強大的二次工程定製化能力。基於平臺、產品、工程三層研發體系,很多特殊需求都可以在現場完成。

甚至,隨着硬件的快速發展,跨國公司可以在本部僅進行通用產品研發,然後由各地方公司人員完成具體產品研發,各自發揮自己的優勢。我帶領的團隊就曾經完成過很多這類定製化開發任務。

需求的持續迭代,讓維護軟件從最初的一個簡單對話框,演變爲一個龐雜的軟件模塊了。

◇◇◇

厚積薄發後的瞬間頓悟,雖然可以幫助我重新理解配置軟件和維護軟件,但並不能幫助我自動構建出來這兩個軟件。現實世界經常是無奈的,人員缺、時間緊、任務重、看不到短期效益,諸多因素決定了公司不可能投入資源單獨進行配置軟件和維護軟件的研發。所有的工作都必須在產品研發過程中見縫插針,但面對各種龐雜的功能,經常讓人有一種無力感。

如何破解這個難題呢?前文已經敘述過,我採取的策略是“借人”。我平時會承擔很多公司招聘、培訓等工作,我很清楚,職場中最好的培訓不是學,而是用。是否可以借用這些新人的力量,來構建配置軟件和維護軟件呢?

爲了讓新人快速成長,完成編程規範培訓後,每個人都需要動手寫一點程序以加深記憶。但新人能力有限,你不可能讓他們去面對複雜的架構產品,最好是一個單純的軟件模塊。維護軟件和配置軟件恰好是由諸多零碎的功能構成的,如果能構建出一套合適的軟件架構,讓新人去分別完成這些零零碎碎的功能,應該是一個好策略。

幸運的是,我做到了。

◇◇◇

當初跨國公司研發時,配置軟件和維護軟件是完全不同的兩個軟件,實際上這兩個軟件是由完全不同的兩個小組完成的。我們沒人,能否將這連個軟件整合成一個呢?

要想融合配置軟件和維護軟件,首先要統一界面。綜合各種因素,統一後的界面示意圖如下:
在這裏插入圖片描述
一些具體的界面示例如下:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
整個界面分爲三部分,頂層爲菜單欄和工具欄,側重於一些全局功能,如配置軟件的參數生成,維護軟件的通訊連接等,注意不包含任何與特定功能相關的菜單項目。

左側爲樹視圖,用於信息組織,如配置軟件的各種配置子節點,或者各種維護軟件功能。樹視圖內部以xml格式組織,不僅能保證界面的靈活,也可以降低程序模塊的耦合度。

xml格式如下示意:

<?xml version="1.0"?>
<funtree>
    <fun name="對時" class="CSystemTimeFun" ……/>
    <dir name="查詢">       
			<fun name="遙測" class="CReadAIFun" ……/>
    		<fun name="電度" class="CCIFun" ……/>
    		<fun name="遙信" class="CYXFun" ……/>
    </dir>
</funtree>

右側上半截爲特定界面,每個界面與左側節點類型有關,可以是設置界面,也可以是維護界面。可以多個界面並存,以Tab方式組織。爲了界面複用,默認提供表格、屬性頁等多種界面風格用於子類化(subClass)。一些特定的功能界面需要額外的菜單或工具欄,統一佈局在該子界面頂端。

右側下半截爲日誌視圖,用於提示各種操作記錄、參數解析錯誤信息、查詢記錄、規約幀等功能,這些功能大多由框架提供。

早年在閱讀《MFC深入淺出》那本書時,瞭解到MFC類庫中有一套宏定義,可以依據字符串信息直接創建類。這相當於在左側點擊某個樹節點,就可以直接構建出一個特定界面並在右上側顯示出來,相當於框架程序部分不需要知道上層程序。大家注意到xml配置信息中class屬性嗎,就是用於設置類名稱的。

自己手癢,將MFC這套宏機制給單獨提煉了出來。當時全球都在慶祝2000年世紀跨年,我就將這套宏名稱定義爲了2000,N和Z都是淘氣的2呢! 代碼示例如下:

/*****************************************************/
/*					.h文件部分						 */
/*****************************************************/
//支持動態創建,做法同MFC類庫
class CFunBase;
struct CRunNZNClass
{
    QString	m_lpszClassName;
    CFunBase* (*m_pfnCreateObject)();
    CRunNZNClass* m_pNextClass;

    static CRunNZNClass* m_pFirstClass;
    static CRunNZNClass* load(QString lpszClassName);
    CFunBase* createObject();
};

struct CRunNZNClassInit{CRunNZNClassInit(CRunNZNClass* pNewClass);};

#define DECLARE_FUN(class_name) \
public: \
    static CRunNZNClass _nzn_##class_name; \
    virtual CRunNZNClass* getRunNZNClass() const; \
    static CFunBase* createObject();

#define IMPLEMENT_FUN(class_name) \
    CRunNZNClass class_name::_nzn_##class_name = { \
        #class_name, \
        class_name::createObject, \
        NULL \
    }; \
    static CRunNZNClassInit _init_##class_name(&class_name::_nzn_##class_name); \
    CRunNZNClass* class_name::getRunNZNClass() const \
    { return &class_name::_nzn_##class_name; } \
    CFunBase* class_name::createObject() \
        { return new class_name; }

/*****************************************************/
/*					   .cpp文件部分					 */
/*****************************************************/
CRunNZNClass* CRunNZNClass::m_pFirstClass = 0;

CRunNZNClass* CRunNZNClass::load(QString lpszClassName)
{
    CRunNZNClass* pClass = 0;
    for (pClass = m_pFirstClass; pClass != NULL; pClass = pClass->m_pNextClass)
    {
        if (lpszClassName == pClass->m_lpszClassName)
            return pClass;
    }
    return 0;
}

CFunBase* CRunNZNClass::createObject()
{
    if (m_pfnCreateObject == 0)
        return 0;
    CFunBase* pFunBaseObject = 0;
    pFunBaseObject = (*m_pfnCreateObject)();
    return pFunBaseObject;
}

CRunNZNClassInit::CRunNZNClassInit(CRunNZNClass* pNewClass)
{
    pNewClass->m_pNextClass = CRunNZNClass::m_pFirstClass;
    CRunNZNClass::m_pFirstClass = pNewClass;
}

限於篇幅,這套代碼的具體細節我就不展開敘述了,大家如感興趣可參考《MFC深入淺出》一書。藉助這套神奇的宏,每個新人鍛鍊的代碼可以拿過來直接用,僅需要簡單修改xml文件即可。

◇◇◇

配置軟件的主要功能是統一配置產品屬性,驗證後生成各類代碼、參數、文檔等。在具體實施時,我們習慣採用“統一配置,分散發布”的策略。採用這種策略的最大優點是減少了信息的交互索引,如統一組織定值和液晶界面,定值名稱的字符串信息可直接用於定值液晶界面結構。如果分爲多個配置軟件,就需要生成參數互相索引。

配置文件原先我們採用了數據庫,後期統一調整爲JSON格式。因爲裝置的各類配置信息會自然的呈現爲樹狀結構,採用JSON格式會比較契合。更爲關鍵的是數據庫必須統一組織,而JSON格式可以自然擴展,便於各模塊獨立組織整體複用。

配置軟件中比較複雜的是參數合法性驗證部分,各節點本身的驗證不難,難的是各類需要全局信息的驗證項。爲了支持這類驗證功能,我們借鑑了編譯系統中“遍”的概念,可基於全體信息構建查詢映射表,獨立抽象這些複雜驗證模塊。

相對與配置軟件,維護軟件額外增加的主要功能項是通訊和規約,一般需要提供多種通訊功能和規約,如串口、以太網、虛擬設備通訊等。配置軟件的整體架構圖如下所示:
在這裏插入圖片描述
◇◇◇

維護軟件和配置軟件的設計細節,如果詳細展開敘述,估計都可以獨立成書了。本章限於篇幅,側重於讓大家從程序架構的角度重新認識配置軟件和維護軟件。只要認知到位了,配置軟件和維護軟件雖然頗具工作量,但其本身的架構設計並不難。

——————————————

返回目錄

我是小馬兒,一個渴望良知與靈魂的嵌入式軟件工程師,歡迎您的陪伴與同行,如感興趣可加個人微信號nzn_xiaomaer交流,需備註“異維”二字。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章