應該說,我們中的許多人,編程的歷史並不短,但很多時候,我們對自己編寫出來的代碼卻毫無自信,有時候自己見了都怕,盡管這段代碼實現了要求的功能。歸其原因,往往是代碼風格差導致的代碼凌亂沒有美感,本文的目的就是要講解一般的良好風格,幫助讀者寫出“美麗”的代碼,事先要說明的是文中所涉及到的語言有C、C++、JAVA及BASIC,我之所以用了三種語言而不是只用一種語言是為了向讀者表明風格對語言的通用的。
1)標識符(命名規則)
標識符應當直觀且可以拼讀,可望文知意,最好采用英文單詞或其組合,便於記憶和閱讀,切忌使用漢語拼音來命名。長名字能更好地表達含義,所以函數名、變量名、類名長達十幾個字符不足為怪,例如:
好的命名 int student_age,teacher_age;
壞的命名 int age1,age2;
但名字是否越長越好呢?不是的,請看下面的例子:
struct student
int student_age; /* 壞的命名 */
char *student_name;
struct student
int age; /* 好的命名 */
char *name;
為什麼前者不好呢,因為很多余,結構體的名student已經表達了student_age前面的student的意思。
再比如字符串拷貝函數:void StringCopy(char *str1, char *str2);我們很難搞清楚究竟是把str1拷貝到str2中,還是剛好倒過來。可
以把參數名字起得更有意義,如叫strSource和trDestination。這樣從名字上就可以看出應該把strSource拷貝到strDestination。
單字符的名字也是有用的,常見的如i,j,k,m,n,x,y,z等,它們通常可用作函數內的局部變量。
2)運算符的優先級
如果代碼行中的運算符比較多,應該用括號確定表達式的操作順序,避免使用默認的優先級。因為熟記各運算符的優先級是比較困難的,
就算你熟記並正確使用了,寫出來的代碼也容易產生歧義而使其可讀性較差。
好的風格 if ((a b) && (a & c))
壞的風格 if (a b && a & c)
雖然後者和前者功能一樣,但後者是很恐怖的,難以閱讀。
3)不要編寫太復雜的復合表達式。
復合表達式使用在適當的場合可以使代碼更加簡潔,但不能因為這個簡潔而帶來理解的復雜。
例如:
max = a > b ?(a > c ? a : c) : (b > c ? b : c)//復合表達式過於復雜
應該修改為:
max = a;
max = b;
max = c;
上面的if的執行語句只有一行也加了,是因為遵循了“不論if、for、while的執行語句有多少都要加”的規則,這樣可以防止書寫失誤,當這樣的語句層層嵌套的時候你就會知道這樣做的好處。
4)各種數據類型與零值比較
在JAVA中,對於布爾變量flag,與零值(注意:不是0)比較的方式自然是if (flag
應當將整型變量用“==”或“!=”直接與0比較。
if (value == 0)
if (value != 0)
不可以寫成
if (value) // 會讓人誤解 value是布爾變量
if (!value)
指針變量的零值是NULL。盡管NULL的值與0相同,但是兩者意義不同。對於指針變量p ,它與零值比較的if語句如下:
if (p == NULL)
if (p != NULL)
不要寫成
if (p == 0) // 容易讓人誤解p是整型變量
if (p != 0)
5)多層if語句
不要出現這樣的結構:
if (condition1)
…
if (condition2)
…
if (condition3)
…
…
而應該代之以if-else-if結構:
if (condition1)
…
else if (condition2)
…
else if (condition3)
…
…
這樣的結構條理清楚,前者則容易導致寫到後來自己都不知道寫了些什麼的事實。
可以用switch語句替換嵌套的if語句來實現多分支選擇。
6)改善循環的效率
對於字符串name,看下面的循環:
for (i = 0; i < strlen(name); i++)效率明顯差於下面的循環:
n = strlen(name);
for (i = 0; i < n; i++)
後者只要計算name的長度一次。
7)少用、慎用goto語句,並不禁用
goto語句能從多重循環體或者代碼堆裡一下子跳到外面, 例如:
…
…
…
on error goto errorhandler;
errorhandler:
…
在Visual Basic中這一招是常用的。
8)消除魔鬼數
魔鬼數者,沒名字的常數也,你若看英文資料,它們的說法是magicdata,我們的一些作品將其翻譯為“魔術數”,我更願意將其翻譯為“魔鬼數”,因為它是一個導致代碼的可讀性極差的“魔鬼”。
假使你在程序裡寫下下面的一段代碼:
for (i=0; i < 100; i++);for (i=0; i <99;i++);誰都不知道100、99是個什麼玩
意,你可能意味著100是范圍的邊界(最大值),就應該給出定義,代碼的讀者才能明白你的意思:
#define MAX 100 /* C語言的宏常量 */
const int MAX = 100; // C++ 語言的const常量
for (i=0; i < MAX; i++);for (i=0; i
並且如果某一常量與其它常量密切相關,應在定義中包含這種關系,而不應給出一些孤立的值。
例如:
const float RADIUS = 100;
const float DIAMETER = RADIUS * 2;
9)函數返回值
函數名字與返回值類型在語義上不可沖突,C標准庫函數getchar違反了這一規則。
例如:
char c;
c = getchar();
if (c == EOF)
按照getchar名字的意思,將變量c聲明為char類型是很自然的事情。但不幸的是getchar的確不是char類型,而是int類型,其原型如下:
int getchar(void);
10)亂指一氣的指針
“野指針”者,亂指一氣的指針也,它不是NULL指針,是指向“垃圾”內存的指針。野指針是很危險的,是經常導致bug的原因,它的成因主有兩種:一是指針變量沒有被初始化。在C/C++中任何指針變量剛被創建時不會自動成為NULL指針,它的缺省值是隨機的。所以,指針變量在創建的同時應當被初始化,要麼將指針設置為NULL,要麼讓它指向合法的內存。例如
char *p = NULL;
char *str = (char *) malloc(100);
二是指針p被free或者delete之後,沒有置為NULL,讓人誤以為p是個合法的指針。
關於編碼的風格,筆者還有許多需要講解的,限於本文的篇幅,筆者暫時講到這裡,希望能對編程者有所幫助。