經(jīng)常有人微信問我,什么樣的代碼才算是好代碼茂洒。這個問題其實見仁見智搜变,業(yè)內(nèi)也沒有統(tǒng)一的標(biāo)準(zhǔn)可以使用采缚。我仔細梳理了一下自己評價代碼的方法,總結(jié)了五個評價指標(biāo)挠他。
- 規(guī)模
- 執(zhí)行效率
- 占用空間
- 可讀性
- 擴展性
這五個維度相互之間有著或強或弱的關(guān)聯(lián)扳抽,任意兩份代碼之間可以參考這個體系進行大概的比較,但沒有絕對的高下之分殖侵。
1. 規(guī)模
這里的規(guī)模說的是代碼的規(guī)模贸呢,也就是解決同樣問題的程序包含的代碼行數(shù)。如果單從這個因素講拢军,那一定是代碼規(guī)模越小越好楞陷。但規(guī)模越小往往就會讓代碼本身的復(fù)雜程度變高,影響可讀性茉唉。
有個很有趣的情況固蛾,初學(xué)者和技術(shù)大牛兩種水平相差巨大的人都有對代碼規(guī)模的執(zhí)念。不過他們的訴求卻是完全不同的度陆。
1.1 初學(xué)者追求簡單
初學(xué)者評價代碼是不是簡單的最樸素的方法就是看代碼規(guī)模艾凯,他們總是覺得代碼行數(shù)越少的程序就越簡單。經(jīng)常有人在微信中問為什么我給出的解法要寫二十幾行代碼坚芜,而網(wǎng)上的解法卻只有十幾行览芳。于是就讓我講一下那個十幾行的代碼。我只能說鸿竖,那個十幾行的代碼來自《算法導(dǎo)論》沧竟,我需要用4~5個篇幅來講,還不保證能講透徹缚忧。
在編程領(lǐng)域悟泵,往往簡單并不表示易懂,它可能蘊含著更高級更復(fù)雜的思想闪水。這些對于初學(xué)者是有難度的糕非。
for (k = 0; k < n; k++)
{
for (i = 0; i < n; i++)
{
for(j = 0; j < n; j++)
{
if (D[i][j] > D[i][k] + D[k][j])
{
D[i][j] = D[i][k] + D[k][j];
P[i][j] = P[i][k];
}
}
}
這是一個計算臨接矩陣中任意兩點之間距離的一個經(jīng)典算法,叫Floyd球榆,只有六行代碼朽肥。當(dāng)年參加ACM比賽的時候就死記硬背下了這段代碼,后來一直沒有仔細研究過這個算法的原理持钉,目前也只是會用而已衡招。在大部分情況下,它也不是最優(yōu)的算法每强。
1.2 大牛們追求省事
真正的大牛追求代碼行數(shù)少的原因一定是為了提高執(zhí)行效率始腾,但也不乏一些從業(yè)多年沒養(yǎng)成好習(xí)慣也被人稱為“大胖莨簦”的人仗著自己經(jīng)驗豐富圖省事的一些寫法。我就見過這樣的代碼:
typedef struct _tagNode
{
int m_nID;
int m_nSN;
int m_nMode;
int m_nCode;
}Node;
Node arrNodes[100];
本來應(yīng)該寫這段代碼定義一個數(shù)據(jù)結(jié)構(gòu)浪箭,結(jié)果被某位“大潘胍危”寫成這樣:
int arr[100][4];
九行代碼一下變成了一行,就為了少敲一些奶栖。當(dāng)然匹表,換做初學(xué)者,這樣的二維數(shù)組可能已經(jīng)駕馭不了驼抹。我還見過更夸張的代碼:
int arr[100][50][30][5];
寫這行代碼的人依然是個有多年工作經(jīng)驗的“大派:ⅲ”,這個四維數(shù)組用的風(fēng)生水起框冀。只是坑苦了后來接手他工作的同事。
這樣追求代碼規(guī)模的行為都是不可取的敏簿。
2. 執(zhí)行效率
從某種意義上講明也,如今對程序的第一要求應(yīng)該就是執(zhí)行效率。人們說的最多的就是執(zhí)行效率和運行空間的關(guān)系惯裕,還有執(zhí)行效率和可讀性的關(guān)系温数。
2.1 以空間換時間
隨著硬件設(shè)備的成本越來越低,越來越多的行業(yè)都提倡以空間換時間的設(shè)計思想蜻势。一些能夠通過記錄中間數(shù)據(jù)減少計算量的地方就成了首選的優(yōu)化點撑刺。
最經(jīng)典的利用這個思想的算法就是桶排序:
void main()
{
int arr[10] = {2, 5, 15, 18, 7, 10, 13, 11, 9, 0};
int arrSort[20] = { 0 };
for (int i = 0; i < 10; i++)
{
arrSort[arr[i]]++;
}
for (int i = 0; i < 20; i++)
{
if (arrSort[i] > 0)
{
printf("%d ", i);
}
}
}
這段代碼通過一個空間為20的一維數(shù)組下標(biāo)進行排序,空間利用是土豪級的握玛。它的特點是排序范圍有多大够傍,就需要一個多大的數(shù)組。
2.2 不能犧牲可讀性
底層程序員喜歡用位運算挠铲,于是常有人把簡單的計算用位運算進行優(yōu)化冕屯,比如把
int a = 10;
int b = a / 2;
改成
int a = 10;
int b = a >>1;
由于位運算的物理特性,下面這段代碼的確效率會更高一些拂苹。不過安聘,很多人看到這種寫法都不一定能反應(yīng)上來。
3. 占用空間
對于一些特殊的行業(yè)瓢棒,比如嵌入式開發(fā)浴韭,編程過程中一定要注意的就是節(jié)省空間。因為嵌入式設(shè)備的RAM普遍比較小脯宿。這時候念颈,桶排序的方法一定是不允許的。另外嗅绰,在申請堆空間時都有嚴格的限制舍肠。
嵌入式開發(fā)中常有類似這樣的代碼:
#define NEED_MAX 800
int* p = new int[NEED_MAX];
if (p == NULL)
{
return -l;
}
delete[] p;
沒有嵌入式經(jīng)驗的人一定會問搀继,這段代碼申請了一段空間后什么也沒做就釋放掉了,這不是畫蛇添足嗎翠语。其實叽躯,這是一段容錯代碼,就是為了保證系統(tǒng)中有足夠的空間供后面的代碼執(zhí)行肌括。
是不是想想就很可憐点骑,程序運行中突然發(fā)現(xiàn)內(nèi)存不夠了,不得不停掉谍夭。
4. 可讀性
對于越來越提倡代碼規(guī)范的中國軟件行業(yè)來說黑滴,可讀性開始成為不可忽視的重要因素。無論是統(tǒng)一的代碼風(fēng)格紧索,還是規(guī)范的命名袁辈、函數(shù)設(shè)計和注釋,這些都必須注意珠漂。
在某些公司晚缩,代碼規(guī)范被認為是評價代碼的第一要素。鐵打的項目流水的程序員媳危,一段可讀性差的代碼對項目而言很可能意味著滅頂之災(zāi)荞彼。
對于初學(xué)者,代碼規(guī)范這個要素必須非常重視待笑,如果錯過了這個培養(yǎng)良好習(xí)慣的黃金時期鸣皂,后面再改就很難了。
行業(yè)內(nèi)有一些沿襲了很久的陋習(xí)暮蹂,因為追求程序執(zhí)行效率損失可讀性寞缝、為了減少代碼行數(shù)損失可讀性、為了趕工期損失可讀性甚至還有為了省事兒損失可讀性椎侠。在這些思想的驅(qū)使下第租,產(chǎn)生了很多不好的代碼習(xí)慣。
void Swap(int& a, int& b)
{
a = a ^ b;
b = a ^ b;
a = a ^ b;
}
這是一個實現(xiàn)變量交換功能的函數(shù)我纪,它利用了^運算的特性慎宾,完成了不借助第三個變量進行交換的動作。有些公司的面試題甚至還會考這個浅悉。但無論從執(zhí)行效率還是從輸入效率來講趟据,它都沒有什么優(yōu)勢。也許唯一的作用就是炫技术健。我建議還是老老實實地這么寫:
void Swap(int& a, int& b)
{
int t = a;
a = b;
b = t;
}
在如今的編譯技術(shù)中汹碱,這段代碼已經(jīng)能夠被優(yōu)化到一個相當(dāng)高的性能了。
再舉個例子:
g_nScore = student.GetScore() >= p->m_pNext->m_nScore ? student.GetScore() : p->m_pNext->m_nScore;
這句話還是盡量寫成下面這種形式:
if (student.GetScore() >= p->m_pNext->m_nScore)
{
g_nScore = student.GetScore();
}
else
{
g_nScore = p->m_pNext->m_nScore;
}
雖然功能上沒有問題荞估,但下面這種寫法更有助于開發(fā)者理清自己的邏輯咳促。
如果你仔細閱讀任意一個公司的代碼規(guī)范文檔稚新,你都會發(fā)現(xiàn)它有一條最重要的指導(dǎo)思想,那就是為了提高代碼可讀性跪腹,允許犧牲一些其他方面的利益褂删。
5. 擴展性
對于一些大型的、生命周期久的項目而言冲茸,擴展性相當(dāng)重要屯阀。但擴展性有一個死敵就是代碼量。仔細研究一下經(jīng)典的23種設(shè)計模式轴术,沒有哪一個不是成倍地提高了代碼量难衰。
在很多資深程序員中,還常常因為是否使用設(shè)計模式引發(fā)爭論逗栽。而這些爭論的焦點就是代碼量和擴展性這對矛盾盖袭。究竟這二者孰輕孰重呢,其實也沒有一定之規(guī)彼宠,完全取決于具體的項目情況苍凛。具體問題具體分析才是王道。
6. 初學(xué)者的權(quán)衡
對于初學(xué)者而言兵志,究竟哪些指標(biāo)應(yīng)該最關(guān)注呢?我認為宣肚,當(dāng)然是可讀性想罕。
初學(xué)者學(xué)習(xí)編程時,最重要的一點就是能夠把樸素的算法用編程語言來實現(xiàn)霉涨。其他的都不重要按价。有時,過早地追求其他四種指標(biāo)會讓你誤入歧途笙瑟。
面對一道題目的多種解法楼镐,你要去做選擇首先該去鉆研哪一個。是那個代碼函數(shù)最少的嗎往枷?是那個運行時間最短的嗎框产?是那個開辟空間最少的嗎?還是那個擴展性最強的错洁。這些都不是秉宿,應(yīng)該是那個可讀性最好的。
可讀性好的代碼一般都不是最短的那一個屯碴,但一定是你最容易學(xué)會的描睦。當(dāng)你掌握了一個正確的解法之后,你的心里就有了底导而,之后再了解其他解法時就更加自信忱叭,學(xué)習(xí)的動力就這樣悄悄地到來了隔崎。
很多新同學(xué)害怕代碼量大的程序,所謂的代碼量大也不過三四十行代碼韵丑,一看到就先緊張爵卒。其實,當(dāng)你靜下心來以子功能為單位一點點地讀下去埂息,你會發(fā)現(xiàn)它不過是幾道課后作業(yè)解法的簡單堆疊技潘,并不難理解。相反千康,很多看似簡單只有十幾行代碼的程序往往是一個大坑享幽,一旦你扎進去,憑自己的本事根本爬不出來拾弃。
可能我說的這些很多初學(xué)者還無法明白值桩,沒關(guān)系先記住,相信在不久的將來你完成了一定數(shù)量的練習(xí)之后豪椿,你就會明白我今天在講些什么奔坟。
我是天花板,讓我們一起在軟件開發(fā)中自我迭代搭盾。
如有任何問題咳秉,歡迎與我聯(lián)系。