程序結(jié)構(gòu)的7個(gè)證明原理
程序結(jié)構(gòu)應(yīng)遵循7個(gè)證明原理,以確保程序的正確性、健壯性、靈活性、可重用和可讀性等。
1. 單純?cè)?/p>
所謂單純性原理是指變量或指針等的使用遵循單一化的原則,即為不同的用途使用不同的變量或指針。采用了單純?cè)?,程序就可以明確的反映實(shí)際的問(wèn)題。
如下面的程序,從一個(gè)文件中讀入數(shù)據(jù)放到另一個(gè)文件中:
FILE* fp = NULL;
fp = fopen(m_strSrcFilePath, “r“);
/* 讀數(shù)據(jù) */
fclose(fp);
/* 對(duì)數(shù)據(jù)進(jìn)行處理 */
fp = fopen(m_strDesFilePath, “w“);
/* 寫(xiě)數(shù)據(jù) */
同一個(gè)指針變量fp在一個(gè)子程序中被用來(lái)作為兩個(gè)不同文件的指針,雖然沒(méi)有錯(cuò)誤,但容易造成對(duì)fp理解困難,所以最好這樣:
FILE* fpSrc = NULL; /* 源文件 */
FILE* fpDes = NULL; /* 目標(biāo)文件 */
fpSrc = fopen(m_strSrcFilePath, “r“);
/* 讀數(shù)據(jù) */
fclose(fp);
/* 對(duì)數(shù)據(jù)進(jìn)行處理 */
fpDes = fopen(m_strDesFilePath, “w“);
/* 寫(xiě)數(shù)據(jù) */
2. 同型原理
同型原理是指相同邏輯的地方應(yīng)該有相同的結(jié)構(gòu);能復(fù)用的代碼就不要重寫(xiě),用宏或者子程序?qū)崿F(xiàn)。
例如下面這兩個(gè)循環(huán):
for(i = 0; i m_aLinkMan.GetSize(); i++);
for(i = 0; i = m_aLinkMan.GetSize()-1; i++);
仔細(xì)一看,會(huì)發(fā)現(xiàn)這兩個(gè)循環(huán)其實(shí)是一樣的,但對(duì)它們?yōu)槭裁葱问讲煌瑫?huì)感到費(fèi)解,引起閱讀的障礙。
3. 對(duì)稱原理
對(duì)稱原理是指成對(duì)的操作應(yīng)該成對(duì)地出現(xiàn),并且出現(xiàn)在對(duì)稱的位置上。比如:內(nèi)存的申請(qǐng)與釋放、文件的打開(kāi)與關(guān)閉、if語(yǔ)句是否需要相應(yīng)的else語(yǔ)句等。各系統(tǒng)、組成成分或模塊都應(yīng)遵循對(duì)稱原理。
在Linux下,對(duì)稱原理主要表現(xiàn)為以下幾點(diǎn):
malloc等分配內(nèi)存的函數(shù)和free函數(shù)必須成對(duì)出現(xiàn),而且必須保證釋放掉指針不再被使用。
open/fopen等打開(kāi)文件的函數(shù)和close/fclose函數(shù)必須成對(duì)出現(xiàn),而且必須保證關(guān)閉的文件描述符或者流指針不再被使用。
使用signal或者sigaction設(shè)置信號(hào)處理程序時(shí),應(yīng)該先保存舊的信號(hào)處理程序,等處理完畢進(jìn)行恢復(fù)。
還有其他一些函數(shù)也必須成對(duì)使用,如mmap/munmap,pthread_mutex_init/ pthread_mutex_destroy,sem_init/sem_destroy等等。
對(duì)于程序中的模塊、函數(shù),如有必要,也應(yīng)該保持對(duì)稱。
4. 層次原理
層次原理是形狀的層次美原理。例如,意識(shí)到事物的主從關(guān)系,前后關(guān)系,本末關(guān)系等層次關(guān)系,追求事物應(yīng)有的形態(tài)。必須使各個(gè)層次詳細(xì)化、數(shù)據(jù)抽象化。層次的規(guī)定要徹底。
例如有如下代碼:
struct p1 {};
struct p2 {
struct p1 *pp1;
struct p2 *pp2;
可以看出結(jié)構(gòu)體p1和p2之間又很明顯的層次關(guān)系,分配內(nèi)存時(shí),應(yīng)先為pp2分配內(nèi)存,然后為pp2-pp1分配內(nèi)存;釋放時(shí),應(yīng)該先釋放pp2-pp1的內(nèi)存,然后再釋放pp2的內(nèi)存。
再例如,進(jìn)行多線程編程時(shí),經(jīng)常會(huì)需要進(jìn)行互斥或者是信號(hào)量操作。那么應(yīng)該先調(diào)用pthread_mutex_lock設(shè)置mutex進(jìn)行互斥,然后再調(diào)用sem_wait進(jìn)行信號(hào)量操作。若是順序弄反了,則會(huì)引入一個(gè)race condition,從而可能產(chǎn)生死鎖。
以上的例子只是比較簡(jiǎn)單的情況,在程序中可能存在非常復(fù)雜的情況,要注意判別。
5. 線性原理
線性原理是指事物的形狀是由直線描繪出來(lái)的。例如,某個(gè)功能,是由幾個(gè)功能的重疊組合加以實(shí)現(xiàn)的。因此,在程序中應(yīng)該盡量不使用GOTO,SCHEDULE,POST/WAIT等功能。
6. 明證原理
邏輯的明證性原理,即應(yīng)該努力的說(shuō)明一些不太清除的邏輯,并且使其具有說(shuō)服力。
實(shí)例:檢查接收到的數(shù)據(jù)中帶有的數(shù)據(jù)序號(hào)的連續(xù)性。數(shù)據(jù)序號(hào)是用2個(gè)byte表示的,從0開(kāi)始,之后每個(gè)遞增1,達(dá)到 0xffff后,再重頭開(kāi)始。還有,在0xffff上加上1就會(huì)變成0。
* nowseq : 接受通知后的數(shù)據(jù)編號(hào)
* oldseq : 接受通知前的數(shù)據(jù)編號(hào)
* 已接受通知的數(shù)據(jù)編號(hào),不等于接受通知前的數(shù)據(jù)編號(hào)加一,或者
* 被通知前的數(shù)據(jù)的編號(hào)為0xffff,現(xiàn)在已接受通知的編號(hào)不為0
if ((nowseq != oldseq + 1) || (oldseq == 0xffff nowseq != 0 )) {
錯(cuò)誤處理;
這個(gè)程序好像是正確的,可是,它有很多否定形的邏輯式,所以比較難以理解。首先,用肯定形式(也就是正常的條件)表示,再加上”!”,作為錯(cuò)誤的條件,這樣做可以使人放心。按照這樣的做法操作,程序會(huì)形成下述情況,比原本的程序易懂。
if (!((nowseq == oldseq + 1) || (nowseq == 0 oldseq == 0xffff))) {
錯(cuò)誤處理;
原程序中有邏輯錯(cuò)誤。也就是, nowseq是0,oldseq是0xffff的時(shí)候,滿足了if的第一個(gè)條件項(xiàng)的nowseq!=oldseq+1,所以雖然是正確的情況卻被當(dāng)成了錯(cuò)誤。
7. 安全原理
安全原理是指意識(shí)到必然性的原理。例如,忽略沒(méi)有必然性或者含糊不清的地方,用安全的方法、思想來(lái)設(shè)計(jì)。
舉例,有調(diào)用程序(Caller)和被調(diào)用程序(Callee),兩者間調(diào)用接口用的參數(shù)list是P。Caller沒(méi)有把P內(nèi)的Pi域中的初值設(shè)定為’0’,所以Callee側(cè)是異常操作。
這個(gè)故障是在修正程序的時(shí)候產(chǎn)生的,調(diào)查故障的原因,發(fā)現(xiàn)原程序中也有相同的調(diào)用部分,那里是正確的代碼。也就是說(shuō),把P全部清為0,(由此,Pi域的初值就設(shè)定為’0’ ),然后調(diào)用Callee。
修正程序,通過(guò)追加一個(gè)調(diào)用,修正的負(fù)責(zé)人模仿先前的調(diào)用部分,進(jìn)行了編碼,認(rèn)為把P全體清為0是沒(méi)有必要的,(可能是想把程序的步驟減少),于是,P全體清0的步驟被省略了??墒?,不幸的是, Pi中也混入了錯(cuò)誤。
這個(gè)例子中的問(wèn)題是沒(méi)有照搬正確操作的代碼。雖然曾經(jīng)把它清為0,可是不能保證之后它一直為0。使用前,把參數(shù)list清為0,是coding的基本原則。這樣做是安全的,所以符合安全原理。
原文作者所屬博客:樂(lè)走天涯