1. 程式人生 > >大型軟體程式設計規範

大型軟體程式設計規範

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

“安全第一”的C語言程式設計規範

編者按:C語言是開發嵌入式應用的主要工具,然而C語言並非是專門為嵌入式系統設計,相當多的嵌入式系統較一般計算機系統對軟體安全性有更苛刻的要求。1998年,MISRA指出,一些在C看來可以接受,卻存在安全隱患的地方有127處之多。2004年,MISRA對C的限制增加到141條。

  嵌入式系統應用工程師借用計算機專家建立的C語言,使嵌入式系統應用得以飛速發展,而MISRAC是嵌入式系統應用工程師對C語言嵌入式應用做出的貢獻。如今MISRA C已經被越來越多的企業接受,成為用於嵌入式系統的C語言標準,特別是對安全性要求極高的嵌入式系統,軟體應符合MISRA標準。

  從本期開始,本刊將分6期,與讀者共同學習MISRAC。
  第一講:“‘安全第一’的C語言程式設計規範”,簡述MISRAC的概況。
  第二講:“跨越資料型別的重重陷阱”,介紹規範的資料定義和操作方式,重點在隱式資料型別轉換中的問題。
  第三講:“指標、結構體、聯合體的安全規範”,解析如何安全而高效地應用指標、結構體和聯合體。
  第四講:“防範表示式的失控”,剖析MISRAC中關於表示式、函式宣告和定義等的不良使用習慣,最大限度地減小各類潛在錯誤。
  第五講:“準確的程式流控制”,表述C語言中控制表示式和程式流控制的規範做法。
  第六講:“構建安全的編譯環境”,講解與編譯器相關的規範編寫方式,避免來自編譯器的隱患。

   C/C++語言無疑是當今嵌入式開發中最為常見的語言。早期的嵌入式程式大都是用匯編語言開發的,但人們很快就意識到組合語言所帶來的問題——難移植、難複用、難維護和可讀性極差。很多程式會因為當初開發人員的離開而必須重新編寫,許多程式設計師甚至連他們自己幾個月前寫成的程式碼都看不懂。C/C++語言恰恰可以解決這些問題。作為一種相對“低階”的高階語言,C/C++語言能夠讓嵌入式程式設計師更自由地控制底層硬體,同時享受高階語言帶來的便利。對於C語言和C++語言,很多的程式設計師會選擇C語言,而避開龐大複雜的C++語言。這是很容易理解的——C語言寫成的程式碼量比C++語言的更小些,執行效率也更高。

  對於程式設計師來說,能工作的程式碼並不等於“好”的程式碼。“好”程式碼的指標很多,包括易讀、易維護、易移植和可靠等。其中,可靠性對嵌入式系統非常重要,尤其是在那些對安全性要求很高的系統中,如飛行器、汽車和工業控制中。這些系統的特點是:只要工作稍有偏差,就有可能造成重大損失或者人員傷亡。一個不容易出錯的系統,除了要有很好的硬體設計(如電磁相容性),還要有很健壯或者說“安全”的程式。

  然而,很少有程式設計師知道什麼樣的程式是安全的程式。很多程式只是表面上可以幹活,還存在著大量的隱患。當然,這其中也有C語言自身的原因。因為C語言是一門難以掌握的語言,其靈活的程式設計方式和語法規則對於一個新手來說很可能會成為機關重重的陷阱。同時,C語言的定義還並不完全,即使是國際通用的C語言標準,也還存在著很多未完全定義的地方。要求所有的嵌入式程式設計師都成為C語言專家,避開所有可能帶來危險的程式設計方式,是不現實的。最好的方法是有一個針對安全性的C語言程式設計規範,告訴程式設計師該如何做。

1 MISRAC規範

  1994年,在英國成立了一個叫做汽車工業軟體可靠性聯合會(The Motor Industry Software Reliability Association,以下簡稱MISRA)的組織。它是致力於協助汽車廠商開發安全可靠的軟體的跨國協會,其成員包括:AB汽車電子、羅孚汽車、賓利汽車、福特汽車、捷豹汽車、路虎公司、Lotus公司、MIRA公司、Ricardo公司、TRW汽車電子、利茲大學和福特VISTEON汽車系統公司。

  經過了四年的研究和準備,MISRA於1998年釋出了一個針對汽車工業軟體安全性的C語言程式設計規範——《汽車專用軟體的C語言程式設計指南》(Guidelines for the Use of the C Language in Vehicle Based Software),共有127條規則,稱為MISRAC:1998。[Page]

  C語言並不乏國際標準。國際標準化組織(International Organization of Standardization,簡稱ISO)的“標準C語言”經歷了從C90、C96到C99的變動。但是,嵌入式程式設計師很難將ISO標準當作編寫安全程式碼的規範。一是因為標準C語言並不是針對程式碼安全的,也並不是專門為嵌入式應用設計的;二是因為“標準C語言”太龐大了,很難操作。MISRAC:1998規範的產生恰恰彌補了這方面的空白。

  隨著很多汽車廠商開始接受MISRAC程式設計規範,MISRAC:1998也成為汽車工業中最為著名的有關安全性的C語言規範。2004年,MISRA出版了該規範的新版本——MISRAC:2004。在新版本中,還將面向的物件由汽車工業擴大到所有的高安全性要求(Critical)系統。在MISRAC:2004中,共有強制規則121條,推薦規則20條,並刪除了15條舊規則。任何符合MISRAC:2004程式設計規範的程式碼都應該嚴格的遵循121條強制規則的要求,並應該在條件允許的情況下儘可能符合20條推薦規則。

  MISRAC:2004將其141條規則分為21個類別,每一條規則對應一條程式設計準則。詳細情況如表1所列。 表1MISRAC:2004規則分類
      

  最初,MISRAC:1998程式設計規範的建立是為了增強汽車工業軟體的安全性。可能造成汽車事故的原因有很多,如圖1所示,設計和製造時埋下的隱患約佔總數的15%,其中也包括軟體的設計和製造。MISRAC:1998就是為了減小這部分隱患而制定的。

  MISRAC程式設計規範的推出迎合了很多汽車廠商的需要,因為一旦廠商在程式設計上出現了問題,用來補救的費用將相當可觀。1999年7月22日,通用汽車公司(General Motors)就曾經因為其軟體設計上的一個問題,被迫召回350萬輛已經出廠的汽車,損失之大可想而知。

  MISRAC規範不僅在汽車工業開始普及,也同時影響到了嵌入式開發的其他方向。嵌入式實時作業系統μC/OSII的2.52版本雖然已經於2000年通過了美國航空管理局(FAA)的安全認證,但2003年作者就根據MISRAC:1998規範又對原始碼做了相應的修改,如將

  if ((pevent->OSEventTbl[y] &= ~bitx) == 0) {
    /*… */
  }

的寫法,改寫成

  pevent->OSEventTbl[y] &= ~bitx;
  if (pevent->OSEventTbl[y] == 0) {
  /*… */
  }

釋出了2.62的新版本,並宣稱其原始碼99%符合MISRAC:1998規範。

  一個程式能夠符合MISRAC程式設計規範,不僅需要程式設計師按照規範程式設計,編譯器也需要對所編譯的程式碼進行規則檢查。現在,很多編譯器開發商都對MISRAC規範有了支援,比如IAR的編譯器就提供了對MISRAC:1998規範127條規則的檢查功能。

2 MISRAC對安全性的理解


  MISRAC:2004的專家們大都來自於軟體工業或者汽車工業的知名公司,規範的制定不僅僅像過去一樣侷限於汽車工業的C語言程式設計,同時還涵蓋了其他高安全性系統。 圖1汽車事故原因分佈圖
  MISRAC:2004認為C程式設計中存在的風險可能由5個方面造成:程式設計師的失誤、程式設計師對語言的誤解、程式設計師對編譯器的誤解、編譯器的錯誤和執行出錯(runtime errors)。

  程式設計師的失誤是司空見慣的。程式設計師是人,難免會犯錯誤。很多由程式設計師犯下的錯誤可以被編譯器及時地糾正(如鍵入錯誤的變數名等),但也有很多會逃過編譯器的檢查。相信任何一個程式設計師都曾經犯過將“= =”誤寫成“=”的錯誤,編譯器可能不會認為

   if(x=y)

是一個程式設計師的失誤。

  再舉個例子,大家都知道++運算子。假如有下面的指令:

  i=3;
  printf(“%d”,++i);

輸出應該是多少?如果是:

  printf(“%d”,i++);

呢?如果改成-i++呢?i+++i呢?i+++++i呢?絕大多數程式設計師恐怕已經糊塗了。在MISRAC:2004中,會明確指出++或--運算子不得和其他運算子混合使用。

  C語言非常靈活,它給了程式設計師非常大的自由。但事情有好有壞,自由越大,犯錯誤的機會也就越多。[Page]

  如果說有些錯誤是程式設計師無心之失的話,那麼因為程式設計師對C語言本身或是編譯器特性的誤解而造成的錯誤就是“明”知故犯了。C語言有一些概念很難掌握,非常容易造成誤解,如表示式的計算。請看下面這條語句:

   if ( ishigh && (x == i++))

很多程式設計師認為執行了這條指令後,i變數的值就會自動加1。但真正的情況如何呢?MISRA中有一條規則:邏輯運算子&&或||的右運算元不得帶有副作用(side effect)*,就是為了避免這種情況下可能出現的問題。

*所謂帶有副作用,就是指執行某條語句時會改變執行環境,如執行x=i++之後,i的值會發生變化。

  另外,不同編譯器對同一語句的處理可能是不一樣的。例如整型變數的長度,不同編譯器的規定就不同。這就要求程式設計師不僅要清楚C語言本身的特性,還要了解所用的編譯器,難度很大。

  還有些錯誤是由編譯器(或者說是編寫編譯器的程式設計師)本身造成的。這些錯誤往往較難發現,有可能會一直存留在最後的程式中。

  執行錯誤指的是那些在執行時出現的錯誤,如除數等於零、指標地址無效等問題。執行錯誤在語法檢查時一般無法發現,但一旦發生很可能導致系統崩潰。例如:

#define NULL 0
   ……
  char* p;
  p=NULL;
  printf(“Location of 0 is %d\n”, *p);

語法上沒有任何問題,但在某些系統上卻可能執行出錯。

  C語言可以產生非常緊湊、高效的程式碼,一個原因就是C語言提供的執行錯誤檢查功能很少,雖然執行效率得以提高,但也降低了系統的安全性。

  有句話說得好,“正確的觀念重於一切”。MISRAC規範對於嵌入式程式設計師來講,一個很重要的意義就是提供給他們一些建議,讓他們逐漸樹立一些好的程式設計習慣和程式設計思路,慢慢摒棄那些可能存在風險的程式設計行為,編寫出更為安全、健壯的程式碼。比如,很多嵌入式程式設計師都會忽略註釋的重要性,但這樣的做法會降低程式的可讀性,也會給將來的維護和移植帶來風險。嵌入式程式設計師經常要接觸到各種的編譯器,而很多C程式在不同編譯器下的處理是不一樣的。MISRAC:2004有一條強制規則,要求程式設計師把所有和編譯器特性相關的C語言行為記錄下來。這樣在程式設計師做移植工作時,風險就降低了。

3 MISRAC的負面效應

  程式設計師可能會擔心採用MISRAC:2004規範會對他們的程式有負面影響,比如可能會影響程式碼量、執行效率和程式可讀性等。應該說,這種擔心不無道理。縱觀141條MISRAC:2004程式設計規範,大多數的規則並不會對程式的程式碼量、執行效率和可讀性造成什麼大的影響;一部分規則可能會以增加儲存器的佔用空間為代價來增加執行效率,或者增加程式碼的可讀性;但是,也確實存在著一些規則可能會降低程式的執行效率。

  一個典型的例子就是關於聯合體的使用。MISRAC:2004有一條規則明確指出:不得使用聯合體。這是因為,在聯合體的儲存方式(如位填充、對齊方式、位順序等)上,各種編譯器的處理可能不同。比如,經常會有程式設計師這樣做:一邊將採集得到的資料按照某種型別存入一個聯合體,而同時又採用另外一種資料型別將該資料讀出。如下面這段程式:

  typedef union{
    uint32_t word;
    uint8_t bytes[4];
  }word_msg_t;
  unit32_t read_word_big_endian (void) {
    word_msg_t tmp;
    tmp.bytes[0] = read_byte();
    tmp.bytes[1] = read_byte();
    tmp.bytes[2] = read_byte();
    tmp.bytes[3] = read_byte();
    return (tmp.word);
  }

  原理上,這種聯合體很像是一個硬體上的雙口RAM儲存器。但程式設計師必須清楚,這種做法是有風險的。MISRAC:2004推薦用下面這種方法來做:

   uint32_t read_word_big_endian (void) {
    uint32_t word;
    word=((unit32_t)read_byte())<<24;[Page]
    word=word|(((unit32_t)read_byte())<<16);
    word=word|(((unit32_t)read_byte())<<8);
    word=word| ((unit32_t)read_byte());
    return(word);
  }

  先不論為什麼這樣做會更安全,只談執行效率,這種採用二進位制數移位的方法遠遠不如使用聯合體。到底是使用更安全的做法,還是採用效率更高的做法,需要程式設計師權衡。對於一些要求執行效率很高的系統,使用聯合體仍然是可以接受的方法。當然,這是建立在程式設計師充分了解所用編譯器的基礎上的,而且程式設計師必須對這種做法配有相應的註釋。

4 發展中的MISRAC

  MISRAC並非完美,它自身的發展也印證了這一點。MISRAC:2004就去掉了MISRAC:1998中的15條規則。今後的發展,MISRAC仍然要解決很多問題。比如,MISRAC:2004是基於C90標準的,但最新的國際C標準是C99,而C99中沒有確切定義的C語言特性幾乎比C90多了一倍,MISRAC如何適應新的標準還需要進一步探討。

  另外,C++在嵌入式應用中也越來越受到重視,MISRA正在著手製定MISRAC++程式設計規範。讀者可以通過訪問網站http://www.misra.org.uk瞭解MISRAC的發展動向。

5 對MISRAC的思考

  嵌入式系統並不算是一個獨立的學科,但作為一個發展中的行業,它確實需要有一些自己的創新之處。嵌入式工程師們不應僅僅侷限於從計算機專家那裡學習相關理論知識,並運用於自己的專案,還應該共同努力去完善自己行業的標準和規範,為嵌入式系統的發展做出貢獻。MISRAC程式設計規範就是一個很好的典範。它始於汽車工程師和軟體工程師經驗的總結,然後逐漸發展成為一種對整個嵌入式行業都有指導意義的規範。對於推動整個嵌入式行業的正規化發展,MISRAC無疑有著重要意義。

  從另一個角度講,MISRAC規範也可以看成是嵌入式工程師對軟體業的一種完善。嵌入式工程師雖然不是計算機專家,但卻對嵌入式應用有著最深刻的瞭解,將自己在嵌入式應用中的經驗和體會貢獻給其他行業,也是他們應該肩負的責任。

參考文獻
1 MISRAC:2004, Guidelines for the use of the C language in critical systems. The Motor Industry Software Reliability Association, 2004
2 Harbison III. Samuel P, Steele Jr. Guy L. C語言參考手冊. 邱仲潘,等譯. 第5版. 北京:機械工業出版社,2003
3 Kernighan. Brian W, Ritchie. Dennis M. C程式設計語言. 徐寶文,等譯. 第2版. 北京:機械工業出版社,2001
4 Koenig Andrew. C陷阱與缺陷. 高巍譯. 北京:人民郵電出版社,2002
5 McCall Gavin. Introduction to MISRAC:2004, Visteon UK, http://www.MISRAC2.com/
6 Hennell Mike. MISRA CIts role in the bigger picture of critical software development, LDRA. http://www.MISRAC2.com/
7 Hatton Les. The MISRA C Compliance Suite—The next step, Oakwood Computing. http://www.MISRAC2.com/
8 Montgomery Steve. The role of MISRA C in developing automotive software, Ricardo Tarragon. http://www.MISRAC2.com/

非計算機專業C語言初學者程式設計規範(學生用)—概述

注:以下資訊來源於成都資訊工程學院

對於程式設計師來說,能工作的程式碼並不等於“好”的程式碼。“好”程式碼的指標很多,包括易讀、易維護、易移植和可靠等。其中,可靠性對嵌入式系統非常重要,尤其是在那些對安全性要求很高的系統中,如飛行器、汽車和工業控制中。這些系統的特點是:只要工作稍有偏差,就有可能造成重大損失或者人員傷亡。一個不容易出錯的系統,除了要有很好的硬體設計(如電磁相容性),還要有很健壯或者說“安全”的程式。

然而,很少有程式設計師知道什麼樣的程式是安全的程式。很多程式只是表面上可以幹活,還存在著大量的隱患。當然,這其中也有C語言自身的原因。因為C語言是一門難以掌握的語言,其靈活的程式設計方式和語法規則對於一個新手來說很可能會成為機關重重的陷阱。同時,C語言的定義還並不完全,即使是國際通用的C語言標準,也還存在著很多未完全定義的地方。要求所有的嵌入式程式設計師都成為C語言專家,避開所有可能帶來危險的程式設計方式,是不現實的。最好的方法是有一個針對安全性的C語言程式設計規範,告訴程式設計師該如何做。

本規範在制定過程中,主要參考了業界比較推崇的《華為軟體程式設計規範和範例》和《MISRA 2004規則》,適合於非計算機專業的C語言初學者使用,目的在於在教學中培養學生良好的程式設計規範和意識、素質,促進所設計程式安全、健壯、可靠、可讀與可維護(程式簡單、清晰)。考慮到面向的是初學者,為便於教學和課程考核操作,本規範中的要求比較基本。事實上,很多公司都有自己規定的程式碼風格,包括命名規則、縮排規則等,學生參加工作後,應再進一步學習和應用公司的規範。

建議學生在學習本規範的同時,花點時間閱讀本規範的參考文獻原文,特別是熟讀本規範的參考文獻之一的《“安全第一”的C語言程式設計規範》,深刻理解程式設計規範與程式安全、健壯、可靠、可讀、可維護間的關係和作用,在學習和工作中養成良好的程式設計風格。

非計算機專業C語言初學者程式設計規範(學生用)—排版

1.1 嚴格採用階梯層次組織程式程式碼

函式或過程的開始、結構的定義及迴圈、判斷等語句中的程式碼都要採用縮排風格,case 語句下的情況處理語句也要遵從語句縮排要求。

程式塊的分界符(如C/C++ 語言的大括號‘{’ 和‘}’)應各獨佔一行並且位於同一列,同時與引用它們的語句左對齊。在函式體的開始、類的定義、結構的定義、列舉的定義以及if 、for 、do 、while 、switch 、case 語句中的程式都要採用如上的縮排方式。

各層次縮排的風格採用TAB縮排(TAB寬度原則上使用系統預設值,TC使用8空格寬度,VC使用4空格寬度)。示例:
if (x is true)
{
we do y
}
else
{
if (a > b)
{
  ...
}
else
{
  ...
}
}
和:
if (x == y)
{
...
}
else if (x > y)
{
...
}
else
{
....
}

注意,右括號所在的行不應當有其它東西,除非跟隨著一個條件判斷。也就是do-while語句中的“while”,象這樣:
do
{
body of do-loop
} while (condition);

說明:程式碼離不開縮排,縮排背後的思想是:清楚地定義一個控制塊從哪裡開始,到哪裡結束。尤其是在你連續不斷的盯了20個小時的屏幕後,如果你有大尺寸的縮排。你將更容易發現縮排的好處。

關於縮排主要有兩個爭論,一個是該用 空格(Space)還是用 製表符(Tab),另外一個是該用4格縮排還是8格縮排甚至都不是。建議總是使用Tab縮排,因為幾乎所有的程式碼(不僅僅是C程式碼)都在使用Tab縮排。

現在,有些人說8個字元大小的縮排導致程式碼太偏右了,並且在一個80字元寬的終端螢幕上看著很不舒服。對這個問題的回答是:如果你有超過3個級別的縮排,你就有點犯糊塗了,應當修改你的程式。簡而言之,8個字元的縮排使程式更易讀,而且當你把功能隱藏的太深時,多層次的縮排還會對此很直觀的給出警告。要留心這種警告資訊。

例外:對於由開發工具自動生成的程式碼可以有不一致。

1.2 及時折行

較長的語句(>80 字元)要分成多行書寫,長表示式要在低優先順序操作符處劃分新行,操作符放在新行之首,劃分出的新行要進行適當的縮排(至少1個TAB位置),使排版整齊,語句可讀。示例:
report_or_not_flag = ((taskno < MAX_ACT_TASK_NUMBER)
   && (n7stat_stat_item_valid (stat_item))
   && (act_task_table[taskno].result_data != 0));

迴圈、判斷等語句中若有較長的表示式或語句,則要進行適應的劃分,長表示式要在低優先順序操作符處劃分新行,操作符放在新行之首。示例:
if ((taskno < max_act_task_number)
  && (n7stat_stat_item_valid (stat_item)))
{
... // program code
}

for (i = 0, j = 0; (i < BufferKeyword[word_index].word_length)
     && (j < NewKeyword.word_length); i++, j++)
{
... // program code
}

for (i = 0, j = 0;
(i < first_word_length) && (j < second_word_length);
  i++, j++)
{
... // program code
}

若函式或過程中的引數較長,則要進行適當的劃分。示例:
n7stat_str_compare((BYTE *) & stat_object,
     (BYTE *) & (act_task_table[taskno].stat_object),
   sizeof (_STAT_OBJECT));
n7stat_flash_act_duration( stat_item, frame_id *STAT_TASK_CHECK_NUMBER
       + index, stat_object );

1.3 一行只寫一條語句

不允許把多個短語句寫在一行中,即一行只寫一條語句。示例,如下例子不符合規範:
rect.length = 0;  rect.width = 0;
應如下書寫
rect.length = 0;
rect.width  = 0;
1.4 if、for、do、while等語句格式規定
if 、for 、do 、while 、case 、switch 、default 等語句自佔一行,且if 、for 、do 、while 等語句的執行語句部分無論多少都要加花括號{}。

1.5 空行

(1)變數說明之後必須加空行。
(2)相對獨立的程式塊之間應加空行。

1.6 空格

在兩個以上的關鍵字、變數、常量進行對等操作時,它們之間的操作符之前、之後或者前後要加空格;進行非對等操作時,如果是關係密切的立即操作符(如-> ),後不應加空格。採用這種鬆散方式編寫程式碼的目的是使程式碼更加清晰。
由於留空格所產生的清晰性是相對的,所以,在已經非常清晰的語句中沒有必要再留空格,如果語句已足夠清晰則括號內側(即左括號後面和右括號前面)不需要加空格,多重括號間不必加空格,因為在C/C++語言中括號已經是最清晰的標誌了。
在長語句中,如果需要加的空格非常多,那麼應該保持整體清晰,而在區域性不加空格。給操作符留空格時不要連續留兩個以上空格。
(1)逗號、分號只在後面加空格。
int a, b, c;

(2)比較操作符, 賦值操作符"="、 "+=",算術操作符"+"、"%",邏輯操作符"&&"、"&",位域操作符"<<"、"^"等雙目操作符的前後加空格。
if (current_time >= MAX_TIME_VALUE)
{
a = b + c;
}
a *= 2;
a = b ^ 2;

(3)"!"、"~"、"++"、"--"、"&"(地址運算子)等單目操作符前後不加空格。

*p = 'a';        // 內容操作"*"與內容之間
flag = !isEmpty; // 非操作"!"與內容之間
p = &mem;        // 地址操作"&" 與內容之間
i++;             // "++","--"與內容之間

(4)"->"、"."前後不加空格。
p->id = pid;     // "->"指標前後不加空格

(5) if、for、while、switch等與後面的括號間應加空格,使if等關鍵字更為突出、明顯。
if (a >= b && c > d)

1.7 對變數的定義,儘量位於函式的開始位置

(1)應避免分散定義變數。
(2)同一行內不要定義過多變數。
(3)同一類的變數在同一行內定義,或者在相鄰行定義。
(4)陣列、指標等複雜型別的定義放在定義區的最後。
(5)變數定義區不做較複雜的變數賦值。

1.8 程式各部分的放置順序

在較小的專案中,按如下順序組織安排程式各部分:
(1)#include <C的標準標頭檔案>。
(2)#include 〞使用者自定義的檔案〞。
(3)#define 巨集定義。
(4)全域性變數定義。
(5)函式原型宣告。
(6)main函式定義。
(7)使用者自定義函式。

以上各部分之間、使用者自定義的函式之間應加空行。注意,函式原型宣告統一集中放在main函式之前,不放在某個函式內部。

非計算機專業C語言初學者程式設計規範(學生用)—註釋

2.1 註釋的原則和目的

註釋的原則是有助於對程式的閱讀理解,在該加的地方都加了,註釋不宜太多也不能太少,註釋語言必須準確、易懂、簡潔。通過對函式或過程、變數、結構等正確的命名以及合理地組織程式碼的結構,使程式碼成為自注釋的——清晰準確的函式、變數等的命名,可增加程式碼可讀性,並減少不必要的註釋——過量的註釋則是有害的。
註釋的目的是解釋程式碼的目的、功能和採用的方法,提供程式碼以外的資訊,幫助讀者理解程式碼,防止沒必要的重複註釋資訊。 示例:如下注釋意義不大。
/* if receive_flag is TRUE */
if (receive_flag)
而如下的註釋則給出了額外有用的資訊。
/* if mtp receive a message from links */
if (receive_flag)

2.2 函式頭部應進行註釋

函式頭部應進行註釋,列出:函式的目的/ 功能、輸入引數、輸出引數、返回值、呼叫關係(函式、表)等。
示例1:下面這段函式的註釋比較標準,當然,並不侷限於此格式,但上述資訊建議要包含在內。

/*************************************************
  Function:       // 函式名稱
  Description:    // 函式功能、效能等的描述
  Calls:          // 被本函式呼叫的函式清單
  Called By:      // 呼叫本函式的函式清單
  Input:          // 輸入引數說明,包括每個引數的作
                  // 用、取值說明及引數間關係。
  Output:         // 對輸出引數的說明。
  Return:         // 函式返回值的說明
  Others:         // 其它說明
*************************************************/

對於某些函式,其部分引數為傳入值,而部分引數為傳出值,所以對引數要詳細說明該引數是入口引數,還是出口引數,對於某些意義不明確的引數還要做詳細說明(例如:以角度作為引數時,要說明該角度引數是以弧度(PI),還是以度為單位),對既是入口又是出口的變數應該在入口和出口處同時標明。等等。

在註釋中詳細註明函式的適當呼叫方法,對於返回值的處理方法等。在註釋中要強調呼叫時的危險方面,可能出錯的地方。

2.3 進行註釋時的注意事項

(1)建議邊寫程式碼邊註釋,修改程式碼同時修改相應的註釋,以保證註釋與程式碼的一致性。不再有用的註釋要刪除。

(2)註釋的內容要清楚、明瞭,含義準確,防止註釋二義性。說明:錯誤的註釋不但無益反而有害。

(3)避免在註釋中使用縮寫,特別是非常用縮寫。在使用縮寫時或之前,應對縮寫進行必要的說明。

(4)註釋應與其描述的程式碼相近,對程式碼的註釋應放在其上方或右方(對單條語句的註釋)相鄰位置,不可放在下面。除非必要,不應在程式碼或表達中間插入註釋,否則容易使程式碼可理解性變差。

示例:如下例子不符合規範。

例1:
/* get replicate sub system index and net indicator */

repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;

例2:
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;
/* get replicate sub system index and net indicator */

應如下書寫
/* get replicate sub system index and net indicator */
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;

(5)對於所有有物理含義的變數、常量,如果其命名不是充分自注釋的,在宣告時都必須加以註釋,說明其物理含義。變數、常量、巨集的註釋應放在其上方相鄰位置或右方。

示例:
/* active statistic task number */
#define MAX_ACT_TASK_NUMBER 1000
#define MAX_ACT_TASK_NUMBER 1000 /* active statistic task number */

(6)資料結構宣告( 包括陣列、結構、類、列舉等) ,如果其命名不是充分自注釋的,必須加以註釋。對資料結構的註釋應放在其上方相鄰位置,不可放在下面;對結構中的每個域的註釋放在此域的右方。

示例:可按如下形式說明列舉/資料/聯合結構。
/* sccp interface with sccp user primitive message name */
enum  SCCP_USER_PRIMITIVE
{
N_UNITDATA_IND, /* sccp notify sccp user unit data come */
N_NOTICE_IND,   /* sccp notify user the No.7 network can not */
     /* transmission this message */
N_UNITDATA_REQ, /* sccp user's unit data transmission request*/
};

(7)全域性變數要有較詳細的註釋,包括對其功能、取值範圍、哪些函式或過程存取它以及存取時注意事項等的說明。

示例:
/* The ErrorCode when SCCP translate */
/* Global Title failure, as follows */      // 變數作用、含義
/* 0 - SUCCESS   1 - GT Table error */
/* 2 - GT error  Others - no use  */       // 變數取值範圍
/* only  function  SCCPTranslate() in */
/* this modual can modify it,  and  other */
/* module can visit it through call */
/* the  function GetGTTransErrorCode() */    // 使用方法
BYTE g_GTTranErrorCode;

(8)註釋與所描述內容進行同樣的縮排,讓程式排版整齊,並方便註釋的閱讀與理解。

示例:如下例子,排版不整齊,閱讀稍感不方便。
void example_fun( void )
{
/* code one comments */
CodeBlock One

  /* code two comments */
    CodeBlock Two
}
應改為如下佈局。
void example_fun( void )
{
/* code one comments */
CodeBlock One

/* code two comments */
CodeBlock Two
}

(9)將註釋與其上面的程式碼用空行隔開。

示例:如下例子,顯得程式碼過於緊湊。
/* code one comments */
program code one
/* code two comments */
program code two
應如下書寫
/* code one comments */
program code one

/* code two comments */
program code two

(10)對變數的定義和分支語句(條件分支、迴圈語句等)必須編寫註釋。這些語句往往是程式實現某一特定功能的關鍵,對於維護人員來說,良好的註釋幫助更好的理解程式,有時甚至優於看設計文件。

(11)對於switch 語句下的case 語句,如果因為特殊情況需要處理完一個case 後進入下一個case 處理(即上一個case後無break),必須在該case 語句處理完、下一個case 語句前加上明確的註釋,以清楚表達程式編寫者的意圖,有效防止無故遺漏break語句(可避免後期維護人員對此感到迷惑:原程式設計師是遺漏了break語句還是本來就不應該有)。示例:
case CMD_DOWN:
ProcessDown();
break;
case CMD_FWD:
ProcessFwd();
if (...)
{
  ...
  break;
} else
{
  ProcessCFW_B();   // now jump into case CMD_A
}
case CMD_A:
ProcessA();
break;
...

(12)在程式塊的結束行右方加註釋標記,以表明某程式塊的結束。當代碼段較長,特別是多重巢狀時,這樣做可以使程式碼更清晰,更便於閱讀。示例:參見如下例子。
if (...)
{
program code
while (index < MAX_INDEX)
{
  program code
} /* end of while (index < MAX_INDEX) */ // 指明該條while語句結束
} /* end of  if (...)*/ // 指明是哪條if語句結束

(13)在順序執行的程式中,每隔3—5行語句,應當加一個註釋,註明這一段語句所組成的小模組的作用。對於自己的一些比較獨特的思想要求在註釋中標明。

(14)註釋格式儘量統一,建議使用“/* …… */”。

(15)註釋應考慮程式易讀及外觀排版的因素,使用的語言若是中、英兼有的,建議多使用中文,除非能用非常流利準確的英文表達——註釋語言不統一,影響程式易讀性和外觀排版,出於對維護人員的考慮,建議使用中文。

非計算機專業C語言初學者程式設計規範(學生用)—命名規則

C是一門樸素的語言,你使用的命名也應該這樣。與Modula-2和Pascal程式設計師不同,C程式設計師不使用諸如“ThisVariableIsATemporaryCounter”這樣“聰明”的名字。C程式設計師應該叫它“tmp”,這寫起來更簡單,也不會更難懂。
然而,當面對複雜情況時就有些棘手,給全域性變數取一個描述性的名字是必要的。把一個全域性函式叫做“foo”是一種目光短淺的行為。全域性函式也一樣,如果你有一個統計當前使用者個數的函式,應當把它命名為“count_active_user()”或者簡單點些的類似名稱,不應該命名為“cntusr()”。

3.1 三種流行的命名法則

目前,業界共有四種命名法則:駝峰命名法、匈牙利命名法、帕斯卡命名法和下劃線命名法,其中前三種是較為流行的命名法。

(1)駝峰命令法。正如它的名稱所表示的那樣,是指混合使用大小寫字母來構成變數和函式的名字。例如,下面是分別用駱駝式命名法和下劃線法命名的同一個函式:
printEmployeePaychecks();
print_employee_paychecks();

第一個函式名使用了駝峰命名法,函式名中的每一個邏輯斷點都有一個大寫字母來標記。第二個函式名使用了下劃線法,函式名中的每一個邏輯斷點都有一個下劃線來標記。

駝峰命名法近年來越來越流行了,在許多新的函式庫和Microsoft Windows這樣的環境中,它使用得當相多。另一方面,下劃線法是C出現後開始流行起來的,在許多舊的程式和UNIX這樣的環境中,它的使用非常普遍。

(2)匈牙利命名法。廣泛應用於象Microsoft Windows這樣的環境中。Windows 程式設計中用到的變數(還包括巨集)的命名規則為匈牙利命名法,這種命名技術是由一位能幹的 Microsoft 程式設計師查爾斯-西蒙尼(Charles Simonyi) 提出的。

匈牙利命名法通過在變數名前面加上相應的小寫字母的符號標識作為字首,標識出變數的作用域、型別等。這些符號可以多個同時使用,順序是先m_(成員變數)、再指標、再簡單資料型別、再其它。這樣做的好處在於能增加程式的可讀性,便於對程式的理解和維護。

例如:m_lpszStr, 表示指向一個以0字元結尾的字串的長指標成員變數。
匈牙利命名法關鍵是:識別符號的名字以一個或者多個小寫字母開頭作為字首;字首之後的是首字母大寫的一個單詞或多個單詞組合,該單詞要指明變數的用途。

(3)帕斯卡(pascal)命名法。與駝峰命名法類似,二者的區別在於:駝峰命名法是首字母小寫,而帕斯卡命名法是首字母大寫,如:
DisplayInfo();
string UserName;
二者都是採用了帕斯卡命名法。

(4)三種命名規則的小結:MyData就是一個帕斯卡命名的示例;myData是一個駝峰命名法,它第一個單詞的第一個字母小寫,後面的單詞首字母大寫,看起來像一個駱駝;iMyData是一個匈牙利命名法,它的小寫的i說明了它的型態,後面的和帕斯卡命名相同,指示了該變數的用途。

3.2 命名的基本原則

(1)識別符號的命名要清晰、明瞭,有明確含義,同時使用完整的單詞或大家基本可以理解的縮寫,避免使人產生誤解——儘量採用採用英文單詞或全部中文全拼表示,若出現英文單詞和中文混合定義時,使用連字元“_”將英文與中文割開。較短的單詞可通過去掉“母音”形成縮寫;較長的單詞可取單詞的頭幾個字母形成縮寫;一些單詞有大家公認的縮寫。例如:temp->tmp、flag->flg、statistic->stat、increment->inc、message->msg等縮寫能夠被大家基本認可。

(2)命名中若使用特殊約定或縮寫,則要有註釋說明。應該在原始檔的開始之處,對檔案中所使用的縮寫或約定,特別是特殊的縮寫,進行必要的註釋說明。

(3)自己特有的命名風格,要自始至終保持一致,不可來回變化。個人的命名風格,在符合所在專案組或產品組的命名規則的前提下,才可使用。(即命名規則中沒有規定到的地方才可有個人命名風格)。

(4)對於變數命名,禁止取單個字元(如i 、j 、k... ),建議除了要有具體含義外,還能表明其變數型別、資料型別等,但i 、j 、k 作區域性迴圈變數是允許的。變數,尤其是區域性變數,如果用單個字元表示,很容易敲錯(如i寫成j),而編譯時又檢查不出來,有可能為了這個小小的錯誤而花費大量的查錯時間。

(5)除非必要,不要用數字或較奇怪的字元來定義識別符號。

(6)命名規範必須與所使用的系統風格保持一致,並在同一專案中統一。

(7)在同一軟體產品內,應規劃好介面部分識別符號(變數、結構、函式及常量)的命名,防止編譯、連結時產生衝突。對介面部分的識別符號應該有更嚴格限制,防止衝突。如可規定介面部分的變數與常量之前加上“模組”標識等。

(8)用正確的反義片語命名具有互斥意義的變數或相反動作的函式等。
下面是一些在軟體中常用的反義片語。
add / remove       begin / end        create / destroy
insert / delete       first / last         g et / release
increment / decrement                 put / get
add / delete         lock / unlock      open / close
min / max          old / new         start / stop
next / previous      source / target     show / hide
send / receive       source / destination
cut / paste          up / down

示例:
int  min_sum;
int  max_sum;
int  add_user( BYTE *user_name );
int  delete_user( BYTE *user_name );

(9)除了編譯開關/ 標頭檔案等特殊應用,應避免使用_EXAMPLE_TEST_ 之類以下劃線開始和結尾的定義。

3.3 變數名的命名規則

(1)變數的命名規則要求用“匈牙利法則”。
即開頭字母用變數的型別,其餘部分用變數的英文意思、英文的縮寫、中文全拼或中文全拼的縮寫,要求單詞的第一個字母應大寫。
即: 變數名=變數型別+變數的英文意思(或英文縮寫、中文全拼、中文全拼縮寫)
對非通用的變數,在定義時加入註釋說明,變數定義儘量可能放在函式的開始處。
見下表:
bool 用b開頭 bFlg
int 用i開頭 iCount
short int 用n開頭 nStepCount
long int 用l開頭 lSum
char  用c開頭 cCount
unsigned char 用by開頭
float 用f開頭 fAvg
double 用d開頭 dDeta
unsigned int(WORD) 用w開頭 wCount
unsigned long int(DWORD) 用dw開頭 dwBroad
字串 用s開頭 sFileName
用0結尾的字串 用sz開頭 szFileName

(2)指標變數命名的基本原則為:
對一重指標變數的基本原則為:“p”+變數型別字首+命名,如一個float*型應該表示為pfStat。對二重指標變數的基本規則為:“pp”+變數型別字首+命名。對三重指標變數的基本規則為:“ppp”+變數型別字首+命名。

(3)全域性變數用g_開頭,如一個全域性的長型變數定義為g_lFailCount。即:變數名=g_+變數型別+變數的英文意思(或縮寫)。此規則還可避免區域性變數和全域性變數同名而引起的問題。

(4)靜態變數用s_開頭,如一個靜態的指標變數定義為s_plPerv_Inst。即: 變數名=s_+變數型別+變數的英文意思(或縮寫)

(5)對列舉型別(enum)中的變數,要求用列舉變數或其縮寫做字首。並且要求用大寫。如:
enum cmEMDAYS
{
EMDAYS_MONDAY;
EMDAYS_TUESDAY;
……
};

(6)對struct、union變數的命名要求定義的型別用大寫。並要加上字首,其內部變數的命名規則與變數命名規則一致。

結構一般用S開頭,如:
struct ScmNPoint
{
int nX;//點的X位置
int nY; //點的Y位置
};

聯合體一般用U開頭,如:
union UcmLPoint
{
LONG lX;
LONG lY;
}

(7)對常量(包括錯誤的編碼)命名,要求常量名用大寫,常量名用英文表達其意思。當需要由多個單詞表示時,單詞與單詞之間必須採用連字元“_”連線。
如:#define CM_FILE_NOT_FOUND CMMAKEHR(0X20B) 其中CM表示類別。

(8)對const 的變數要求在變數的命名規則前加入c_。即:c_+變數命名規則;示例:const char* c_szFileName;

3.4 函式的命名規範

(1)函式的命名應該儘量用英文(或英文縮寫、中文全拼、中文全拼縮寫)表達出函式完成的功能——函式名應準確描述函式的功能。遵循動賓結構的命名法則,函式名中動詞在前,並在命名前加入函式的字首,函式名的長度不得少於8個字母。函式名首字大寫,若包含有兩個單詞的每個單詞首字母大寫。如果是OOP 方法,可以只有動詞(名詞是物件本身)。示例:
LONG GetDeviceCount(……);
void print_record( unsigned int rec_ind ) ;
int  input_record( void ) ;
unsigned char get_current_color( void ) ;

(2)避免使用無意義或含義不清的動詞為函式命名。如使用process、handle等為函式命名,因為這些動詞並沒有說明要具體做什麼。

(3)必須使用函式原型宣告。函式原型宣告包括:引用外來函式及內部函式,外部引用必須在右側註明函式來源: 模組名及檔名;內部函式,只要註釋其定義檔名——和呼叫者在同一檔案中(簡單程式)時不需要註釋。
應確保每個函式宣告中的引數的名稱、型別和定義中的名稱、型別一致。

3.5 函式引數命名規範

(1)引數名稱的命名參照變數命名規範。
(2)為了提高程式的執行效率,減少引數佔用的堆疊,傳遞大結構的引數,一律採用指標或引用方式傳遞。
(3)為了便於其他程式設計師識別某個指標引數是入口引數還是出口引數,同時便於編譯器檢查錯誤,應該在入口引數前加入const標誌。
如:……cmCopyString(const CHAR * c_szSource, CHAR * szDest)

3.6 檔名(包括動態庫、元件、控制元件、工程檔案等)的命名規範

檔名的命名要求表達出檔案的內容,要求檔名的長度不得少於5個字母,嚴禁使用象file1,myfile之類的檔名。

非計算機專業C語言初學者程式設計規範(學生用)—可讀性

4.1 避免使用預設的運算優先順序

注意運算子的優先順序,並用括號明確表示式的操作順序,避免使用預設優先順序,可防止閱讀程式時產生誤解,防止因預設的優先順序與設計思想不符而導致程式出錯。

示例:下列語句中的表示式
word = (high << 8) | low     (1)
if ((a | b) && (a & c))      (2)
if ((a | b) < (c & d))       (3)

如果書寫為:
high << 8 | low
a | b && a & c
a | b < c & d

由於
high << 8 | low = ( high << 8) | low,
a | b && a & c = (a | b) && (a & c),
(1)(2)不會出錯,但語句不易理解;a | b < c & d = a | (b < c) & d,(3)造成了判斷條件出錯。

4.2 使用有意義的標識,避免直接使用數字

避免使用不易理解的數字,用有意義的標識來替代。涉及物理狀態或者含有物理意義的常量,不應直接使用數字,必須用有意義的列舉或巨集來代替。

示例:如下的程式可讀性差。
if (Trunk[index].trunk_state == 0)
{
Trunk[index].trunk_state = 1;
...  // program code
}

應改為如下形式。
#define TRUNK_IDLE 0
#define TRUNK_BUSY 1

if (Trunk[index].trunk_state == TRUNK_IDLE)
{
Trunk[index].trunk_state = TRUNK_BUSY;
...  // program code
}

4.3 源程式中關係較為緊密的程式碼應儘可能相鄰

這樣做的好處是便於程式閱讀和查詢。示例:以下程式碼佈局不太合理。
rect.length = 10;
char_poi = str;
rect.width = 5;

若按如下形式書寫,可能更清晰一些。
rect.length = 10;
rect.width = 5; // 矩形的長與寬關係較密切,放在一起。
char_poi = str;

4.4 不要使用難懂的技巧性很高的語句、複雜的表示式

除非很有必要時,原則上不要使用難懂的技巧性很高的語句和複雜的表示式——高技巧語句不等於高效率的程式,源程式佔用空間的節約並不等於目標程式佔用空間的節約,實際上程式的效率關鍵在於演算法。

(1)如下表達式,考慮不周就可能出問題,也較難理解。
* stat_poi ++ += 1;
* ++ stat_poi += 1;
應分別改為如下:
*stat_poi += 1;
stat_poi++;     // 此二語句功能相當於“ * stat_poi ++ += 1; ”
++ stat_poi;
*stat_poi += 1; // 此二語句功能相當於“ * ++ stat_poi += 1; ”

(2)如下表達式,不同的編譯器給出的結果不一樣,b[i]是否先執行?
x=b[i] + i++;
應改為:
x = b[i] + i;
i++;

非計算機專業C語言初學者程式設計規範(學生用)—變數與結構

5.1 謹慎使用全域性(公共)變數

(1)去掉沒必要的公共變數。公共變數是增大模組間耦合的原因之一,故應減少沒必要的公共變數以降低模組間的耦合度。

(2)仔細定義並明確公共變數的含義、作用、取值範圍及公共變數間的關係。在對變數宣告的同時,應對其含義、作用及取值範圍進行註釋說明,同時若有必要還應說明與其它變數的關係。

(3)防止區域性變數與公共變數同名——通過使用較好的命名規則來消除此問題。

5.2 資料型別間的轉換

(1)程式設計時,要注意資料型別的強制轉換。當進行資料型別強制轉換時,其資料的意義、轉換後的取值等都有可能發生變化,而這些細節若考慮不周,就很有可能留下隱患。

(2)對編譯系統預設的資料型別轉換,也要有充分的認識。
示例:如下賦值,多數編譯器不產生告警,但值的含義還是稍有變化。
char chr;
unsigned short int exam;
chr = -1;
exam = chr; // 編譯器不產生告警,此時exam為0xFFFF。

(3)儘量減少沒有必要的資料型別預設轉換與強制轉換。例如,所有的 unsigned型別都應該有後綴“U”以明確其型別。

(4)合理地設計資料並使用自定義資料型別,避免資料間進行不必要的型別轉換。

(5)對自定義資料型別進行恰當命名,使它成為自描述性的,以提高程式碼可讀性。注意其命名方式在同一產品中的統一,並且保證沒有多重定義。使用自定義型別,可以彌補程式語言提供型別少、資訊量不足的缺點,並能使程式清晰、簡潔。
示例:可參考如下方式宣告自定義資料型別。下面的宣告可使資料型別的使用簡潔、明瞭。
typedef unsigned char  BYTE;
typedef unsigned short WORD;
typedef unsigned int   DWORD;

下面的宣告可使資料型別具有更豐富的含義。
typedef float DISTANCE;
typedef float SCORE;

(6)不要用八進位制數——整型常數以”0“開始會被認為是8進位制。示例:
code[1]=109
code[2]=100
code[3]=052
code[4]=071
如果是對匯流排訊息初始化,會有危險。

非計算機專業C語言初學者程式設計規範(學生用)—函式與過程

6.1 函式的功能與規模設計

(1)函式應當短而精美,而且只做一件事。

不要設計多用途面面俱到的函式,多功能集於一身的函式,很可能使函式的理解、測試、維護等變得困難。 一個函式應最多佔滿1或2個螢幕(就象我們知道的那樣,ISO/ANSI的螢幕大小是80X24),只做一件事並且把它做好。

一個函式的最大長度與它的複雜度和縮排級別成反比。所以,如果如果你有一個概念上簡單(案,“簡單”是simple而不是easy)的函式,它恰恰包含著一個很長的case語句,這樣你不得不為不同的情況準備不懂的處理,那麼這樣的長函式是沒問題的。

然而,如果你有一個複雜的函式,你猜想一個並非天才的高一學生可能看不懂得這個函式,你就應當努力把它減縮得更接近前面提到的最大函式長度限制。可以使用一些輔助函式,給它們取描述性的名字(如果你認為這些輔助函式的呼叫是效能關鍵的,可以讓編譯器把它們內聯進來,這比在單個函式內完成所有的事情通常要好些)。
對函式還存在另一個測量標準:區域性變數的數目。這不該超過5到10個,否則你可能會弄錯。應當重新考慮這個函式,把它分解成小片。人類的大腦一般能同時記住7個不同的東西,超過這個數目就會犯糊塗。或許你認為自己很聰明,那麼請你理解一下從現在開始的2周時間你都做什麼了。

(2)為簡單功能編寫函式。

雖然為僅用一兩行就可完成的功能去編函式好象沒有必要,但用函式可使功能明確化,增加程式可讀性,亦可方便維護、測試。 示例:如下語句的功能不很明顯。

value = ( a > b ) ? a : b ;改為如下就很清晰了。
int max (int a, int b)
{
return ((a > b) ? a : b);
}

value = max (a, b);或改為如下。
#define MAX (a, b) (((a) > (b)) ? (a) : (b))
value = MAX (a, b);

當一個過程(函式)中對較長變數(一般是結構的成員)有較多引用時,可以用一個意義相當的巨集代替——這樣可以增加程式設計效率和程式的可讀性。 示例:在某過程中較多引用TheReceiveBuffer[FirstSocket].byDataPtr,則可以通過以下巨集定義來代替:# define pSOCKDATA TheReceiveBuffer[FirstScoket].byDataPtr

(3)防止把沒有關聯的語句放到一個函式中,防止函式或過程內出現隨機內聚。

隨機內聚是指將沒有關聯或關聯很弱的語句放到同一個函式或過程中。隨機內聚給函式或過程的維護、測試及以後的升級等造成了不便,同時也使函式或過程的功能不明確。使用隨機內聚函式,常常容易出現在一種應用場合需要改進此函式,而另一種應用場合又不允許這種改進,從而陷入困境。

在程式設計時,經常遇到在不同函式中使用相同的程式碼,許多開發人員都願把這些程式碼提出來,並構成一個新函式。若這些程式碼關聯較大並且是完成一個功能的,那麼這種構造是合理的,否則這種構造將產生隨機內聚的函式。
示例:如下函式就是一種隨機內聚。
void Init_Var( void )
{
Rect.length = 0;
Rect.width = 0; /* 初始化矩形的長與寬 */
Point.x = 10;
Point.y = 10;   /* 初始化“點”的座標 */
}
矩形的長、寬與點的座標基本沒有任何關係,故以上函式是隨機內聚。應如下分為兩個函式:
void Init_Rect( void )
{
Rect.length = 0;
Rect.width = 0; /* 初始化矩形的長與寬 */
}

void Init_Point( void )
{
Point.x = 10;
Point.y = 10;   /* 初始化“點”的座標 */
}

(4)如果多段程式碼重複做同一件事情,那麼在函式的劃分上可能存在問題。若此段程式碼各語句之間有實質性關聯並且是完成同一件功能的,那麼可考慮把此段程式碼構造成一個新的函式。

(5)減少函式本身或函式間的遞迴呼叫。遞迴呼叫特別是函式間的遞迴呼叫(如A->B->C->A),影響程式的可理解性;遞迴呼叫一般都佔用較多的系統資源(如棧空間);遞迴呼叫對程式的測試有一定影響。故除非為某些演算法或功能的實現方便,應減少沒必要的遞迴呼叫,對於safe-related 系統不能用遞迴,因為超出堆疊空間很危險。

6.2 函式的返回值

(1)對於函式的返回位置,儘量保持單一性,即一個函式儘量做到只有一個返回位置。(單入口單出口)。

要求大家統一函式的返回值,所有的函式的返回值都將以編碼的方式返回。
例如編碼定義如下:
#define CM_POINT_IS_NULL CMMAKEHR(0X200)
:
:
建議函式實現如下:
LONG 函式名(引數,……)
{
LONG lResult; //保持錯誤號
lResult=CM_OK;
//如果引數有錯誤則返回錯誤號
if(引數==NULL)
{
lResult=CM_POINT_IS_NULL;
goto END;
}
……
END:
return lResult;
}

(2)除非必要,最好不要把與函式返回值型別不同的變數,以編譯系統預設的轉換方式或強制的轉換方式作為返回值返回。

(3)函式的返回值要清楚、明瞭,讓使用者不容易忽視錯誤情況。函式的每種出錯返回值的意義要清晰、明瞭、準確,防止使用者誤用、理解錯誤或忽視錯誤返回碼。

(4)函式的功能應該是可以預測的,也就是隻要輸入資料相同就應產生同樣的輸出。帶有內部“儲存器”的函式的功能可能是不可預測的,因為它的輸出可能取決於內部儲存器(如某標記)的狀態。這樣的函式既不易於理解又不利於測試和維護。在C/C++語言中,函式的static區域性變數是函式的內部儲存器,有可能使函式的功能不可預測,然而,當某函式的返回值為指標型別時,則必須是STATIC的區域性變數的地址作為返回值,若為AUTO類,則返回為錯針。

示例:如下函式,其返回值(即功能)是不可預測的。
unsigned int integer_sum( unsigned int base )
{
unsigned int index;
static unsigned int sum = 0; // 注意,是static型別的。
                              // 若改為auto型別,則函式即變為可預測。
for (index = 1; index <= base; index++)
{
  sum += index;
}
return sum;
}

6.3 函式引數

(1)只當你確實需要時才用全域性變數,函式間應儘可能使用引數、返回值傳遞訊息。

(2)防止將函式的引數作為工作變數。將函式的引數作為工作變數,有可能錯誤地改變引數內容,所以很危險。對必須改變的引數,最好先用區域性變數代之,最後再將該區域性變數的內容賦給該引數。
示例:下函式的實現不太好。
void sum_data( unsigned int num, int *data, int *sum )
{
unsigned int count;
*sum = 0;

for (coun