加入星計(jì)劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 1 設(shè)計(jì)原則
    • 2 單一職責(zé)原則 (SRP)
    • 3 開放-封閉原則 (OCP)
    • 4 依賴倒置原則 (DIP)
    • 5 接口隔離原則 (ISP)
    • 6 最少知道原則(LKP)
    • 7 ?重構(gòu)
    • 8 隨想
  • 推薦器件
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

嵌入式軟件設(shè)計(jì)原則隨想

2023/11/29
2369
閱讀需 31 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

微信公眾號:嵌入式系統(tǒng)

面向?qū)ο箝_發(fā)的理論很多,對嵌入式C軟件開發(fā)也可參考,水平有限,拋磚引玉而已。

1 設(shè)計(jì)原則

SRP 單一職責(zé)原則 Single Responsibility Principle 每個函數(shù)或者功能塊只有一個職責(zé),只有一個原因會使其改變。

OCP 開放一封閉原則 The Open-Closed Principle 對于擴(kuò)展是開放的,對于修改是封閉的。

DIP 依賴倒置原則 Dependency Inversion Principle 高層模塊和低層模塊應(yīng)該依賴中間抽象層(即接口),細(xì)節(jié)應(yīng)該依賴于抽象。

ISP 接口隔離原則 Interface Segregation Principle 接口盡量細(xì)化,同時(shí)方法盡量少,不要試圖去建立功能強(qiáng)大接口供所有依賴它的接口去調(diào)用。

LKP 最少知道原則 ?Least Knowledge Principle 一個子模塊應(yīng)該與其它模塊保持最少的了解。

個人想法,設(shè)計(jì)原則主要是可在有限范圍內(nèi)指導(dǎo)功能模塊劃分,作為提高軟件復(fù)用度和質(zhì)量的思路。

2 單一職責(zé)原則 (SRP)

函數(shù)或功能應(yīng)該僅有一個引起它變化的原因。單一職責(zé)原則是最簡單但又最難運(yùn)用的原則,需要按職責(zé)分割大模塊,如果一個子模塊承擔(dān)的職責(zé)過多,就等于把這些職責(zé)耦合在一起,一個職責(zé)的變化可能會削弱或抑制這個模塊完成其他職責(zé)的能力。劃分依據(jù)是影響它改變的只有一個原因,并不是單純理解的一個模塊只實(shí)現(xiàn)一個功能,對函數(shù)層面也是如此。

2.1 什么是職責(zé)

在 SRP 中把職責(zé)定義為“變化的原因”(a reason for change),如果有可能存在多于一個的動機(jī)去改變一個子模塊,表明這個模塊就具有多個職責(zé)。有時(shí)很難注意到這點(diǎn),習(xí)慣以組的形式去考慮職責(zé)。例如Modem 程序接口,大多數(shù)人會認(rèn)為這個接口看起來非常合理。

//interface?Modem?違反?SRP
void?connect();
void?disconnect();
void?send();
void?recv();

然而,該接口中卻顯示出兩個職責(zé)。第一個職責(zé)是連接管理,第二個職責(zé)是數(shù)據(jù)通信,connect和 disconnect函數(shù)進(jìn)行調(diào)制解調(diào)器的連接處理,send 和 recv函數(shù)進(jìn)行數(shù)據(jù)通信。

這兩個職貴應(yīng)該被分開嗎?這依賴于應(yīng)用程序變化的方式。如果應(yīng)用程序的變化會影響連接函數(shù),如外設(shè)與主機(jī)熱插拔,連接后是數(shù)據(jù)收發(fā),則需要分開。如果是socket,其本身連接狀態(tài)與數(shù)據(jù)交互是綁定的關(guān)系,應(yīng)用程序的變化總是導(dǎo)致這兩個職責(zé)同時(shí)變化,那沒必分離它們,強(qiáng)行分割反而會引入復(fù)雜性。

2.2 分離耦合

多個職責(zé)耦合不是所希望的,但有時(shí)無法避免,有些和硬件或操作系統(tǒng)有關(guān)的原因,迫使把不愿耦合在起的東西耦合在一起。然而,對于應(yīng)用部分來說應(yīng)當(dāng)盡量分離解耦。軟件前期模塊設(shè)計(jì)真正要做的許多內(nèi)容,就是發(fā)現(xiàn)職責(zé)并把那些職責(zé)相互分離。

3 開放-封閉原則 (OCP)

如果期望開發(fā)的軟件不會在第一版后就被拋棄,就必須牢牢地記住這點(diǎn)。那怎樣的設(shè)計(jì)才能面對需求改變卻可以保持相對穩(wěn)定,從而使得系統(tǒng)可以在第一個版本以后不斷推出新的版本呢?開放-封閉原則為我們提供了指引。

軟件實(shí)體(模塊、函數(shù)等)應(yīng)該是可以擴(kuò)展的,但是不可修改的。如果程序中的一處改動會產(chǎn)生連鎖反應(yīng),導(dǎo)致相關(guān)模塊的改動,那么設(shè)計(jì)就具有僵化性的臭味。OCP 建議應(yīng)該對系統(tǒng)進(jìn)行重構(gòu),這樣以后對系統(tǒng)再進(jìn)行那樣的改動時(shí),就只需要添加新的代碼,而不必改動已經(jīng)正常運(yùn)行的代碼。

3.1 特性

開放-封閉原則設(shè)計(jì)出的模塊具有兩個主要的特征。

對于護(hù)展是開放的

    1. (Open ?for ?extension)

 

    1. 模塊的行為是可以擴(kuò)展的,當(dāng)應(yīng)用需求改變時(shí),可以對模塊進(jìn)行擴(kuò)展,使其滿足新需求。

對于更改是封閉的

    1. (Closed ? for ? modificaiton)

 

    模塊的源代碼是不能被侵犯的,不允許修改已有源代碼。

兩個特征看似互相矛盾,擴(kuò)展模塊行為的通常方式就是修改該模塊的源代碼,不允許修改的模塊常常都被認(rèn)為是具有固定的行為。怎樣可能在不改動模塊源代碼的情況下去更改它的行為呢?關(guān)鍵是抽象。

3.2 抽象隔離

在 C++等面向?qū)ο笤O(shè)計(jì)技術(shù)時(shí),可以創(chuàng)建出固定卻能夠描述一組任意個可能行為的抽象體,這個抽象體就是抽象基類,而這一組任意個可能的行為則表現(xiàn)為可能的派生類。模塊可以操作抽象體,由于模塊依賴于一個固定的抽象體,所以它對于更改可以是關(guān)閉的。同時(shí)通過從這個抽象體派生,也可以擴(kuò)展此模塊的行為。

面向?qū)ο蟮恼Z言多態(tài)特性很容易實(shí)現(xiàn),而嵌入式的C該如何呢?一個函數(shù)接口或功能,不要直接固化相關(guān)邏輯,而是把具體實(shí)現(xiàn)細(xì)節(jié)對外開放可擴(kuò)展的,便于后期添加功能,且不影響其它的功能。

3.3 違反 OCP

一個應(yīng)用程序需要在窗口上繪制圓形(Circle)和方形(Square),圓形和方形會被創(chuàng)建在同一個列表中,并保持適當(dāng)?shù)捻樞?,程序按順序遍歷列表并繪制所有的圓形和方形。

如果使用C語言,并采用不遵循OCP的過程化方法,一組數(shù)據(jù)結(jié)構(gòu),它的第一個成員都相同,但是其余的成員都不同。每個結(jié)構(gòu)中的第一個成員都是一個用來標(biāo)識該結(jié)構(gòu)是代表圓或方形的類型碼。DrawAllShapes 函數(shù)遍歷數(shù)組,該數(shù)組的元素是指向這些數(shù)據(jù)結(jié)構(gòu)的指針,根據(jù)類型碼調(diào)用對應(yīng)的函數(shù) (DrawCircle 或 DrawSquare)。

typedef?enum
{
????CIRCLE,
????SQUARE,
}?ShapeType;

typedef?struct
{
????ShapeType?itsType;
}?Shape;

typedef?struct
{
????double?x;
????double?y;
}?Point;

typedef?struct
{
????ShapeType?itsType;
????double?itsSide;
????Point?itsTopLeft;
}?Square;

typedef?struct
{
????ShapeType?itsType;
????double?itsRadius;
????Point?itsCenter;
}?Circle;

void?DrawSquare(struct?Square*);
void?DrawCircle(struct?Circle*);

void?DrawAllShapes(Shape?**list,?int?n)
{
????int?i;
????Shape*?s;

????for(i?=?0;?i?<?n;?i++)
????{
????????s?=?(Shape*)list[i];
????????switch(s->itsType)
????????{
????????????case?SQUARE:
????????????????DrawSquare((struct?Square*)s);
????????????????break;
????????????case?CIRCLE:
????????????????DrawCircle((struct?Circle*)s);
????????????????break;
????????}
????}
}

DrawAllShapes 函數(shù)不符合 OCP,如果希望函數(shù)能夠繪制包含有三角形的列表,就必須得更改這個函數(shù),擴(kuò)展switch增加三角形。事實(shí)上,每增加一種新的形狀類型,都必須要更改這個函數(shù)。在這樣的應(yīng)用程序中增加一種新的形狀類型,就意味著要找出所有包含上述 switch(或 if else 語句)的函數(shù),在每一處都添加對新增的形狀類型的判斷。

在嵌入式數(shù)據(jù)流中,數(shù)據(jù)解析是常見情景,如果新手開發(fā),可能是一個萬能長函數(shù)完成全部解析功能。比如不同類型的數(shù)據(jù)解析錯誤樣例:

typedef?int?int32_t;
typedef?short?int16_t;
typedef?char?int8_t;
typedef?unsigned?int?uint32_t;
typedef?unsigned?short?uint16_t;
typedef?unsigned?char?uint8_t;

#define?NULL?((void?*)(0))

//違反OCP的樣例
//微信公眾號【嵌入式系統(tǒng)】,不同類型的數(shù)據(jù)集中在一起,使用switch-case處理,與前面DrawAllShapes一樣,后續(xù)擴(kuò)展會影響既有函數(shù)。
int16_t?cmd_handle_body_v1(uint8_t?type,?uint8_t?*data,?uint16_t?len)
{
????switch(type)
????{
????????case?0:
????????????//handle0
????????????break;
????????case??1:
????????????//handle1
????????????break;
????????default:
????????????break;
????}
????return?-1;
}

3.4 遵循 OCP

上面的數(shù)據(jù)解析樣例調(diào)整后:

//遵守OCP原則
//微信公眾號【嵌入式系統(tǒng)】
typedef?int16_t?(*cmd_handle_body)(uint8_t?*data,?uint16_t?len);
typedef?struct
{
????uint8_t?type;
????cmd_handle_body?hdlr;
}?cmd_handle_table;

static?int16_t?cmd_handle_body_0(uint8_t?*data,?uint16_t?len)
{
????//handle0
????return?0;
}

static?int16_t?cmd_handle_body_1(uint8_t?*data,?uint16_t?len)
{
????//handle1
????return?0;
}

//擴(kuò)展新指令只需要在這里加上就行,不會影響先前的
static?cmd_handle_table?cmd_handle_table_map[]?=
{
????{0,?cmd_handle_body_0},
????{1,?cmd_handle_body_1}
};

int16_t?handle_cmd_body_v2(uint8_t?type,?uint8_t?*data,?uint16_t?len)
{
????int16_t?ret=-1;
????uint16_t?i?=?0;
????uint16_t?size?=?sizeof(cmd_handle_table_map)?/?sizeof(cmd_handle_table_map[0]);

????for(i?=?0;?i?<?size;?i++)
????{
????????if((type?==?cmd_handle_table_map[i].type)?&&?(cmd_handle_table_map[i].hdlr?!=?NULL))
????????{
????????????ret=cmd_handle_table_map[i].hdlr(data,?len);
????????}
????}
????return?ret;
}

雖然不如C++抽象與多態(tài),但整體實(shí)現(xiàn)了OCP的效果,在不修改handle_cmd_body_v2的情況下,擴(kuò)展cmd_handle_table_map。這個模式其實(shí)是通用的表驅(qū)動法??蓞⒖嘉⑿殴娞枴厩度胧较到y(tǒng)】的文章嵌入式軟件的設(shè)計(jì)模式(下) 第4章。OCP有時(shí)也可以采用回調(diào)函數(shù)的方式,底層不變,由應(yīng)用層自身擴(kuò)展實(shí)現(xiàn)差異化部分。

3.5 策略性的閉合

上面的例子其實(shí)并非是100%封閉。一般而言,無論模塊是多么的“開放-封閉”,都會存在一些無法對之封閉的變化,沒有對所有的情況都貼切的模型。既然不可能完全封閉,那么就必須有策略地對待這個問題。也就是說,設(shè)計(jì)人員必須對模塊應(yīng)該對哪種變化封閉做出選擇。必須先預(yù)估最有可能發(fā)生的變化,然后構(gòu)造隔離這些變化,這需要設(shè)計(jì)人員具備一些行業(yè)經(jīng)驗(yàn)及預(yù)測能力。

遵循OCP 的代價(jià)也是昂貴的,肆無忌憚的從軟件角度進(jìn)行抽象隔離,創(chuàng)建抽象隔離要花費(fèi)開發(fā)時(shí)間和代碼空間,同時(shí)也增加了軟件設(shè)計(jì)的復(fù)雜性。比如前面handle_cmd_body_v1比handle_cmd_body_v2,如果明確需求或者硬件資源緊缺,后者從設(shè)計(jì)原則角度更合理,但前者更直接且符合資源緊缺且需求固定的場景。對于嵌入式軟件應(yīng)該對程序中頻繁變化的部分提取抽象。

4 依賴倒置原則 (DIP)

依賴倒置原則即高層模塊(調(diào)用者)不依賴于低層模塊(被調(diào)用者),二者都應(yīng)該依賴于抽象。

結(jié)構(gòu)化程序分析和設(shè)計(jì),總是傾向于創(chuàng)建高層模塊依賴低層模塊,策略依賴于細(xì)節(jié)的結(jié)構(gòu),這是大部分嵌入式軟件的結(jié)構(gòu),從業(yè)務(wù)層到組件層,再到驅(qū)動層,自頂向下的設(shè)計(jì)思維。良好的面向?qū)ο蟮某绦?,其依賴結(jié)構(gòu)相對于傳統(tǒng)的過程式方法設(shè)計(jì)的結(jié)構(gòu)而言就是被“倒置”了。

高層模塊依賴于低層模塊,意味著低層模塊的改動會直接影響到高層模塊,從而迫使它們依次做出改動,在不同的上下文中重用高層模塊就會變得困難。

4.1 倒置的接口所有權(quán)

“Don't ?call ?us,we'll ?call ?you.”(不要調(diào)用我們,我們會調(diào)用你),低層模塊實(shí)現(xiàn)在高層模塊中聲明并被高層模塊調(diào)用的接口,也就是低層模塊按高層模塊的需求來實(shí)現(xiàn)功能。通過這種倒置的接口所有權(quán),滿足高層在任何上下文的重用。事實(shí)上,即使是嵌入式軟件,開發(fā)的重點(diǎn)是隨時(shí)變化的高層模塊,一般都是相似的上層應(yīng)用軟件在不同的硬件環(huán)境運(yùn)行,所以高層的復(fù)用更能提高軟件質(zhì)量。

4.2 樣例對比

假設(shè)控制熔爐調(diào)節(jié)器的軟件,從外界通道中讀取當(dāng)前的溫度,并通過向另一個通道發(fā)送命令來控制熔爐加熱的開或關(guān)。按數(shù)據(jù)流的結(jié)構(gòu)大概如下:

//溫度調(diào)節(jié)器的調(diào)度算法
//檢測到當(dāng)前溫度在設(shè)定范圍外,開啟或關(guān)閉熔爐的加熱器
void?temperature_regulate(int?min_temp,?int?max_temp)
{
????int?tmp;
????while(1)
????{
????????tmp?=?read_temperature();//讀取溫度
????????if(tmp?<?min_temp)
????????{
????????????furnace_enable();//啟動加熱
????????}
????????else?if(tmp?>?max_temp)
????????{
????????????furnace_disable();//停止加熱
????????}
????????wait();
????}
}

算法的高層意圖是清楚的,但是實(shí)現(xiàn)代碼中卻夾雜著低層細(xì)節(jié)。導(dǎo)致這段代碼(控制算法)根本不能重用于不同的硬件,只是代碼很少,算法實(shí)現(xiàn)容易,看起來不會造成太大的損害。如果一個復(fù)雜的溫度控制算法,需要移植到不同平臺,或者需求改變,要求在溫度異常時(shí)發(fā)出額外警示呢?

void?temperature_regulate_v2(Thermometers?*t,Heaterk?*h,int?min_temp,?int?max_temp)
{
????int?tmp;
????while(1)
????{
????????tmp?=?t->read();
????????if(tmp?<?min_temp)
????????{
????????????h->enable();
????????}
????????else?if(tmp?>?max_temp)
????????{
????????????h->disable();
????????}
????????wait();
????}
}

這就倒置了依賴關(guān)系,使得高層的調(diào)節(jié)策略不再依賴于任何溫度計(jì)或者熔爐的特定細(xì)節(jié)。該算法具有較好的可重用性,算法不依賴細(xì)節(jié)。

依賴倒置尤其可以解決嵌入式軟件中硬件頻繁變更對軟件復(fù)用帶來的問題。比如運(yùn)動手環(huán)的計(jì)步器,在面向過程的開發(fā)按從高到低的調(diào)用關(guān)系,如果后續(xù)因?yàn)槲锪系仍蚋鼡Q加速度傳感器,則會導(dǎo)致上層必須修改,尤其是沒有內(nèi)部封裝,應(yīng)用層直接調(diào)用驅(qū)動接口的方式,需要逐個替換。如果后續(xù)不確定傳感器可能用哪顆,軟件需要根據(jù)傳感器特性自動調(diào)整,則需要大量switch-case來替換。

app??->?drv_pedometer_a
//調(diào)用關(guān)系全部替換為
app??->?drv_pedometer_b

如果采用依賴倒置,兩者依賴于抽象:

app??->?get_pedometer_interface
//底層依賴抽象
drv_pedometer_a??->?get_pedometer_interface
drv_pedometer_b??->?get_pedometer_interface

依賴倒置,即不同的硬件驅(qū)動均依賴抽象的接口,上層業(yè)務(wù)也依賴抽象層,所有的開發(fā)都圍繞get_pedometer_interface來設(shè)計(jì),這樣硬件變化不會影響上層軟件的復(fù)用。這個實(shí)現(xiàn)其實(shí)是通用的代理模式。可參考微信公眾號【嵌入式系統(tǒng)】的文章《嵌入式軟件的設(shè)計(jì)模式(上)》 第2.2章,實(shí)現(xiàn)抽象隔離就是函數(shù)指針。

4.3 結(jié)論

使用傳統(tǒng)的過程化程序設(shè)計(jì)所創(chuàng)建出來的依賴關(guān)系結(jié)構(gòu),策略是依賴于細(xì)節(jié)的,這樣會使策略受到細(xì)節(jié)改變的影響。事實(shí)上,使用何種語言來編寫程序是無關(guān)緊要的。即使是嵌入式C,如果程序的依賴關(guān)系是倒置的,它就是面向?qū)ο蟮脑O(shè)計(jì)思維。

依賴倒置原則是實(shí)現(xiàn)面向?qū)ο蠹夹g(shù)宣稱的好處的基本機(jī)制,正確應(yīng)用對于創(chuàng)建可重用的框架來說是必須的,同時(shí)它對于構(gòu)建在變化面前富有彈性的代碼也是非常重要的;由于抽象和細(xì)節(jié)被彼此隔離,所以代碼也容易維護(hù)。

5 接口隔離原則 (ISP)

使用多個專門的接口,而不使用單一的總接口,即客戶端不應(yīng)該依賴那些它不需要的接口。面向?qū)ο箝_發(fā)時(shí),繼承的基類中包含本不需要的接口,原本特定需求擴(kuò)展的接口成了通用,導(dǎo)致所有派生類都要去實(shí)現(xiàn)沒有意義的接口,即為接口污染。

5.1 接口污染

接口隔離原則”的重點(diǎn)是“接口”二字,在嵌入式C層面有兩種理解:
1、如果把“接口”理解為一組API接口集合,可以是某個子功能的一系列接口。如果部分接口只被部分調(diào)用者使用,就需要將這部分接口隔離出來,單獨(dú)給這部分調(diào)用者使用,而不強(qiáng)迫其它調(diào)用者也依賴這部分本不會被用到的接口。類似購物,不需要捆綁銷售,只買自己需要的。
2、如果把“接口”理解為單個API接口或函數(shù),部分調(diào)用者只需要函數(shù)中的部分功能,可把函數(shù)拆分成粒度更細(xì)的多個函數(shù),讓調(diào)用者只依賴它需要的那個細(xì)粒度函數(shù)。即一個函數(shù)不要傳入過多的參數(shù)配置,寧可拆分為多個同類接口簡化調(diào)用,也不要提供一個萬能的需要一些不相關(guān)參數(shù)的接口。模塊對外接口不要過度封裝,參數(shù)太多也不便于閱讀和使用。

5.2 風(fēng)險(xiǎn)與解決

如果一個程序依賴于部分它不使用的方法,這程序就面臨著由于這些未使用方法的改變所帶來的變更,這無意中導(dǎo)致了所有相關(guān)程序之間的耦合。換種說法,如果一個客戶程序依賴于它不使用的方法,但是其他客戶程序卻要使用這些方法,那當(dāng)其他客戶要求這個方法改變時(shí),就會影響到這個客戶程序。應(yīng)該盡可能地避免這種耦合,分離接口。

在嵌入式C中,隨著迭代升級,也會擴(kuò)展新功能,或者直接為函數(shù)增加傳入?yún)?shù),或者函數(shù)內(nèi)部增加額外的處理,導(dǎo)致接口產(chǎn)生冗余,對不同版本的調(diào)用者并不友好(如果本身是功能迭代升級沒問題,避免不同版本的差異是平級關(guān)系)。更改的代價(jià)和影響就變得不可預(yù)測,并且更改所附帶的風(fēng)險(xiǎn)也會增加。更改一個和自己不相關(guān)的功能也可能產(chǎn)生影響,表面是修改A功能卻導(dǎo)致B功能異常,“城門失火,殃及池魚”,這種對單元測試覆蓋也難以把握。

模塊層面,不相關(guān)的接口可以使用預(yù)編譯宏屏蔽,這樣也節(jié)省代碼空間;函數(shù)層面擴(kuò)展新功能時(shí)可以新建接口,重新實(shí)現(xiàn)和原來接口功能平級的擴(kuò)展版或者v2,盡量不要通過傳參合并,除非明確兩者是遞進(jìn)關(guān)系而不是并列關(guān)系。

微信公眾號【嵌入式系統(tǒng)】建議,子模塊分多個c文件,內(nèi)部函數(shù)務(wù)必加static,僅模塊內(nèi)部的使用全局函數(shù)可以在c內(nèi)使用extern,不要加到h頭文件。功能類似但應(yīng)用場景不同的函數(shù)可以放在一起,且注釋里互相提到對方,說明差異。更多編碼規(guī)范和編碼技巧可以參考嵌入式C編碼規(guī)范》、《代碼的保養(yǎng)

6 最少知道原則(LKP)

迪米特法則(Law of Demeter,縮寫是 LOD),也叫最小知道(知識)原則,一個功能對其依賴的子功能知道的越少越好,對于被依賴的子功能無論邏輯多么復(fù)雜,都盡量將邏輯封裝在內(nèi)部。通俗的解釋就是,使用某個子模塊,不需要關(guān)注其內(nèi)部實(shí)現(xiàn),調(diào)用盡可能少的API接口。

比如執(zhí)行A操作需要按順序調(diào)用1-2-3-4四個接口,執(zhí)行B操作需要按順序調(diào)用1-2-4-3四個接口,對于調(diào)用者需要清楚知道模塊內(nèi)細(xì)節(jié)才能正確使用,這種完全可以合并接口,封裝A和B兩個動作,在其內(nèi)部執(zhí)行具體的細(xì)節(jié),對外隱藏封閉,外界使用時(shí)無需關(guān)注。

最少知道原則(迪米特原則)的初衷在于降低模塊間的耦合,模塊更好的信息隱藏和更少的信息重載,將部分信息固化封閉。但過度的封閉也有缺點(diǎn),一旦客制化需求變更,如果新增C操作是4-3-2-1就需要擴(kuò)展新接口。

7 ?重構(gòu)

重構(gòu)是持續(xù)進(jìn)行的,好比用餐后對廚房的清理工作。第一次沒有清理用餐會快一點(diǎn),但是由于沒有對盤碟和用餐環(huán)境進(jìn)行清潔,第二天做準(zhǔn)備工作的時(shí)間就要更長一點(diǎn)。這會再一次促使放棄清潔工作。的確,跳過清潔工作能夠很快用餐,但是臟亂在逐漸積累。最終,得花費(fèi)大量的時(shí)間去尋找合適的烹飪器具,鑿去盤碟上已經(jīng)干硬的食物殘余,并把它們洗擦干凈。飯是天天要吃的,忽略掉清潔工作并不能真正加快做飯速度,片面追求速度早晚要翻車,欲速則不達(dá)。重構(gòu)的目的就是為了每天清潔代碼,保持代碼的清潔。

軟件開發(fā)大部分是基于這種理不清的混沌狀態(tài)的迭代開發(fā),所有的原則和模式對于臟亂的代碼來說將沒有任何價(jià)值。在應(yīng)用各種設(shè)計(jì)原則、設(shè)計(jì)模式前(嵌入式軟件的設(shè)計(jì)模式(上)》、《嵌入式軟件的設(shè)計(jì)模式(下)),首先學(xué)習(xí)編寫清潔的代碼。

8 隨想

面向?qū)ο蟮脑O(shè)計(jì)原則還有很多,基于類的繼承、封裝、多態(tài)有各種通用指導(dǎo)規(guī)則,而這些設(shè)計(jì)原則對于嵌入式C并不完全適用。嵌入式C是結(jié)構(gòu)化程序設(shè)計(jì),自頂向下的方式,在需求多變時(shí)或多或少存在弊端,其特點(diǎn)是快但亂。所以重構(gòu)是必不可少的,在不改變外在行為的前提下,改進(jìn)代碼的內(nèi)部結(jié)構(gòu);但修改成什么樣式才是合適的,就可以參考前面的五種規(guī)則。

現(xiàn)在的嵌入式軟件開發(fā)極少像以前把一個字節(jié)掰成八瓣使用,資源足夠的情況下,嵌入式應(yīng)用開發(fā)可適當(dāng)參考面向?qū)ο蟮姆绞綄?shí)現(xiàn)高質(zhì)量的軟件;具體方案思路兩種,函數(shù)指針,抽象隔離?!皼]有什么問題是不能通過增加一個抽象層解決的,如果有,再增加一層”。

推薦器件

更多器件
器件型號 數(shù)量 器件廠商 器件描述 數(shù)據(jù)手冊 ECAD模型 風(fēng)險(xiǎn)等級 參考價(jià)格 更多信息
HFBR-2522ETZ 1 Foxconn Receiver, 1Mbps, Through Hole Mount, ROHS COMPLIANT, 6 PIN
$17.11 查看
LE88276DLCT 1 Microsemi Corporation Analog Transmission Interface,
$33.67 查看
74HC132D,653 1 NXP Semiconductors 74HC(T)132 - Quad 2-input NAND Schmitt trigger SOIC 14-Pin
$0.42 查看

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

嵌入式系統(tǒng)開發(fā)技術(shù)交流,軟件開發(fā)的思路與方案共享,行業(yè)資訊的分享。