前言
前不久在微博上關(guān)注一個(gè)ID為垠的微博用戶(后來百科之后才發(fā)現(xiàn)又是國內(nèi)程序員中的牛人之一铐达,就是池大大文章經(jīng)常提到的“王垠”),無意間查看了他之前發(fā)的微博,其中許多都是對一些編程語言的犀利評價(jià),而所發(fā)的微博之中一篇名為編程的智慧長文吸引了我注意巧还,出于對內(nèi)容的好奇就對這篇文章進(jìn)行了閱讀鞭莽。閱讀過程中,發(fā)現(xiàn)了博主對編程和設(shè)計(jì)語言的思考都有著獨(dú)立深刻的見解澎怒,文章里都是對如何在編程中成長和對代碼正確實(shí)踐的干貨指導(dǎo),旨在幫助那些把編程作為主職工作的人們在技術(shù)開發(fā)的路上更好地找到屬于自己正確的方向阶牍。閱讀完這篇文章后喷面,我發(fā)現(xiàn)里面提到的條條編程規(guī)范和編碼要求星瘾,對自己現(xiàn)在以及未來的編程之路都有著很大的價(jià)值,也讓我設(shè)想如何編程中更好融入自己的思考惧辈,對代碼的反思以及對程序設(shè)計(jì)的反思琳状。
在閱讀了文章后幾天,自己也不時(shí)地想如何在編程中有效成長盒齿,覺得有必要將文章《編程的智慧》中對針對自己有價(jià)值的內(nèi)容提取記錄下來念逞,進(jìn)行自我針對性的思考和實(shí)踐,于是也就有了寫這篇文章念頭县昂。
內(nèi)容
編程提高 - 代碼提煉
文中提到提高編程水平最有效的方法——提煉代碼肮柜,學(xué)會對自己所寫的代碼進(jìn)行思考。當(dāng)寫完一段代碼看看是不是太長了倒彰,還能夠縮減嗎审洞?是不是可讀性太差了,要靠不必要的注釋讓其它人甚至以后的自己才能看懂這段代碼待讳。用這樣的思考過程來提煉自己的代碼芒澜,減少代碼中的冗余。就算是閱讀開源庫或者一些其它人的代碼時(shí)创淡,也應(yīng)該用這樣思考方式來閱讀他們的代碼痴晦,來提取他人代碼中的精華。這里引用下文章里一句話"作為一個(gè)好的程序員琳彩,應(yīng)該看他刪掉過自己多少的代碼誊酌,留下來又是怎樣的代碼。"只有對自己的代碼進(jìn)行反復(fù)地思考露乏,修改和提煉碧浊,以此才能改進(jìn)自己的代碼質(zhì)量,真正提高自己的編程水平瘟仿。
優(yōu)雅的代碼 - 讓代碼更加清晰
優(yōu)雅的代碼應(yīng)該是容易管理和查看的箱锐,并且邏輯結(jié)構(gòu)上是應(yīng)該對稱的,就像樹叉式的樹
狀結(jié)構(gòu)劳较,一個(gè)常見的場景就是在if/else語句的使用上驹止,讓if/else的分支永遠(yuǎn)都是成對出現(xiàn),不要僅僅因?yàn)槭÷缘羯倭康闹貜?fù)性代碼而去省略一個(gè)else的分支观蜗,這樣一來破壞整個(gè)邏輯的結(jié)構(gòu)和對稱性臊恋,使得在代碼理解上就需要多一點(diǎn)投入。
if (conditionA) {
...
if (conditionA-1) {
...
} else {
...
}
...
} else if (conditionB) {
...
} else {
...
}
模塊化的代碼 - 讓代碼更加獨(dú)立
對代碼的模塊化墓捻,應(yīng)該是對代碼所表示的邏輯進(jìn)行模塊化捞镰,也可以在單個(gè)文件進(jìn)行模塊化的抽取,而不是單純地將代碼分成多個(gè)不同的文件和目錄。對代碼進(jìn)行模塊化處理的一種常見且比較有效就是"使用函數(shù)"岸售,通常的函數(shù)都會有一個(gè)輸入和對應(yīng)的輸出,因此就可以將能夠模塊化的代碼用不同的函數(shù)進(jìn)行各自的封裝厂画,在最后的使用就是對模塊化函數(shù)的組合調(diào)用凸丸。
文章也提到了想讓代碼更好模塊化的必要方式:
- 避免使用過長的函數(shù)。針對存在的過長的函數(shù)袱院,就要想方設(shè)法地去拆分成更小的函數(shù)屎慢,進(jìn)行整合調(diào)用。在函數(shù)長度的控制上忽洛,最好保持在40~50行以內(nèi)一個(gè)函數(shù)腻惠。
- 使用小的工具函數(shù)。針對一些雖然結(jié)構(gòu)簡單(可能只是一個(gè)倆三行代碼的真假判斷)但仍然會重復(fù)使用的代碼提取成小的函數(shù)欲虚,來簡化主要函數(shù)的邏輯集灌。
- 保持函數(shù)的單一性原則。避免寫出通用的复哆,用來表示可以做這個(gè)又可以做那個(gè)的函數(shù)欣喧,讓每個(gè)函數(shù)盡量只做一件簡單的事情。當(dāng)兩段代碼的共同點(diǎn)少于它們的不同點(diǎn)時(shí)就應(yīng)該將連兩段代碼分別放在不同函數(shù)中梯找,可以將其中相同的部分單獨(dú)抽取成另外一個(gè)函數(shù)來調(diào)用唆阿。
- 減少和避免全局變量和類變量的使用。將全局變量或者類變量作為類里不同函數(shù)間的共享數(shù)據(jù)锈锤,成為函數(shù)間的數(shù)據(jù)通道驯鳖。一方面,這樣一來破壞函數(shù)間模塊化的結(jié)構(gòu)久免,無法做到讓函數(shù)離開類而存在浅辙;另一方面,這個(gè)變量還很可能在其它地方進(jìn)行修改妄壶,這樣讓代碼理解上更加復(fù)雜和容易出錯(cuò)摔握。
可讀的代碼 - 恰當(dāng)?shù)孛〖模瑴p少注釋
文章主張了"真正優(yōu)雅可讀的代碼,是幾乎不需要注釋的"伊磺,當(dāng)然這樣的說法也是需要前提的。而給代碼寫注釋這件事上屑埋,在方便他人的閱讀上的確有所作用豪筝,而大量注釋存在于代碼之中续崖,讓代碼本身閱讀起來更加困難,并且隨著代碼不斷改進(jìn)和更新严望,也帶來了更新注釋的成本多艇。當(dāng)然注釋也有存在的必要性像吻,在少數(shù)的情況下峻黍,使用違反常規(guī)做法的代碼實(shí)現(xiàn)則需要使用短注釋來解釋說明設(shè)計(jì)實(shí)現(xiàn)的理由。
以下就是幾個(gè)文章提到減少寫注釋的必要方法:
- 使用有意義的函數(shù)和變量名拨匆。讓函數(shù)和變量名字能夠具體地描述它們的邏輯和用途姆涩。
- 局部變量的聲明和它被函數(shù)使用的位置盡量接近。減少一開始就聲明大量局部變量惭每,卻在很遠(yuǎn)處才使用這些局部變量的做法骨饿,這樣會讓原本的代碼執(zhí)行路徑更加復(fù)雜,還會出現(xiàn)在中間過程中被修改卻不易察覺的問題洪鸭。
"局部變量的本質(zhì)——作為電路中導(dǎo)線的存在"
- 局部變量名盡量簡短样刷。雖然變量名被縮簡,但結(jié)合有意義的代碼執(zhí)行的上下文览爵,要讓讀者輕易地理解局部變量的含義置鼻。
- 減少局部變量的重用。避免對局部變量進(jìn)行聲明后進(jìn)行反復(fù)使用,不應(yīng)該為了簡單地重用變量而擴(kuò)大變量的作用域,防止在其他地方被使用.
if (...) {
let info = "good"
print(info)
} else {
let info = "bad"
print(info)
}
- 將復(fù)雜的邏輯提取成"幫助函數(shù)".在一個(gè)很長的函數(shù)中,對于存在的不清晰的代碼片段往往可以提取出來,做成一個(gè)函數(shù),然后在原來的地方進(jìn)行調(diào)用,而將函數(shù)命名為更有意義,以此來代替原本所要添加的注釋.
- 將復(fù)雜的表達(dá)式提取出來,用中間變量表示.控制單行代碼的長度,使用中間變量來防止代碼過度嵌套所造成的理解困難.
- 在合理的地方換行.不要單純地依靠IDE的自動(dòng)換行功能,應(yīng)該根據(jù)代碼的邏輯來進(jìn)行換行,這樣容易幫助理解代碼.
簡單的代碼 - 使用程序語言中經(jīng)過時(shí)間考驗(yàn)的特性
每一個(gè)程序語言多少會有一些自己的語言特性,對于語言特性的使用,應(yīng)以其可靠,有效的前提下使用,并非語言提供什么特性蜓竹,就一定需要用到什么.
幾個(gè)具體的應(yīng)該避免使用的語言特性如下:
- 避免使用自增自減表達(dá)式.表達(dá)式將讀寫不同的操作混合在了一起,讓語義更加?混亂,使用
i+=1/i-=1
進(jìn)行代替. - 不要去省略花括號.不要因?yàn)檎Z言特性支持if-else的括號省略而單單利用縮進(jìn),極易出現(xiàn)如下錯(cuò)誤:
if (...)
action1()
action2()
原本if條件下只有執(zhí)行action1函數(shù),而意外地對action2進(jìn)行縮進(jìn),使得也作為if條件下執(zhí)行函數(shù)而等待調(diào)用.
- 合理使用括號.在比較復(fù)雜的操作符表達(dá)式中,不要盲目利用操作符的優(yōu)先級, 必要地使用括號讓表達(dá)式閱讀起來更加容易理解.
- 避免使用break和continue. 在循環(huán)語句中使用了break或者continue就會讓循環(huán)的邏輯和終止條件變得復(fù)雜,難以保證其條件的正確性.而出現(xiàn)的break或者continue往往可以通過調(diào)整循環(huán)的邏輯來解決,這時(shí)候就應(yīng)該重新思考循環(huán)的邏輯箕母,重新設(shè)計(jì)。比如:
- 出現(xiàn)continue,嘗試將continue的條件反向,以此消除continue
/*
for(...) {
if(conditon1) {
continue;
}
...
}
*/
for(...) {
if (!condition1) {
...
}
}
- 出現(xiàn)break, 嘗試將break的條件放置于循環(huán)的終止條件中.
/*
while (condition1) {
...
if (condition2) {
break;
}
}
*/
while (condition1 & !condition2) {
...
}
- 有時(shí)break可以被return代替,而循環(huán)中使用return是沒有問題.
- 將循環(huán)中復(fù)雜的實(shí)現(xiàn)部分提取出來,作為函數(shù)去調(diào)用,然后再去嘗試處理消除break/continue.
直觀的代碼 - 永遠(yuǎn)用更直接,更清晰的寫法
表達(dá)復(fù)雜的執(zhí)行條件判斷時(shí),減少邏輯運(yùn)算符的使用,利用簡單的if-else嵌套,可以讓函數(shù)執(zhí)行條件更加清晰.
正確處理錯(cuò)誤
- 對錯(cuò)誤異常的catch,必須做出合理的處理,而不是選擇忽略掉.
- catch到的異常應(yīng)該明確異常的類型和對應(yīng)信息,不要將其作為寬泛的Exception類型來處理.
- 對可能出現(xiàn)異常的函數(shù)需要throw時(shí),盡量避免多層次的異常傳遞,應(yīng)該在異常出現(xiàn)的當(dāng)時(shí)就進(jìn)行處理.
- try-catch的代碼應(yīng)該盡可能地少,使得調(diào)試更加容易.
正確處理null指針
null其實(shí)不是一個(gè)合法的對象,類型應(yīng)該是NULL,不該是任何其他一種普通類型.而充分利用窮舉法的思想,去更好處理null指針:
- 函數(shù)盡量不要返回null.針對需要返回"沒有"的函數(shù),允許使用"null" 表示沒有;而對于返回"錯(cuò)誤"的函數(shù),應(yīng)該使用拋異常應(yīng)對,而不是返回null值,"沒有"與"出錯(cuò)"是完全不同的概念.
- 不要去catch null相關(guān)的異常.針對可能存在null值的情況必須進(jìn)行null檢查.
- 不要將null放進(jìn)"容器數(shù)據(jù)結(jié)構(gòu)".在一些集合如Array,List,Set,Map等中加入null,會由于容器的動(dòng)態(tài)性變化更加難以控制,使得取值時(shí)必須進(jìn)行null檢查,讓調(diào)試也更加困難,而對應(yīng)的解決方案:可以用一個(gè)特殊,合法的對象來表示集合容器中的"沒有".
- 函數(shù)調(diào)用時(shí)對null盡早處理.明確null表示的意義,盡早地檢查和處理null值,必須減少它的傳播.
- 函數(shù)設(shè)計(jì)時(shí)明確聲明不接受null參數(shù).不要對null值進(jìn)行容錯(cuò)處理,針對null值的輸入應(yīng)該終止程序的繼續(xù).面對null?值,最正確的做法就是最強(qiáng)硬的做法俱济。
- 使用Optional類型.null指針問題的存在,是由于其允許在沒有檢查null的情況下進(jìn)行訪問;而Optional則將檢查和訪問操作進(jìn)行合二為一,這樣一來使得值被使用時(shí)必須先檢查.
防止過渡工程和過渡設(shè)計(jì)
- 先把眼前的問題解決掉,解決好,再去考慮以后的擴(kuò)展問題.
- 先寫出可用的代碼,再進(jìn)行推敲和改進(jìn),考慮重用的問題.
- 先寫出有效, 簡單, 沒有明顯Bug的代碼, 再考慮測試的問題.
總結(jié)
面對長長一篇的干貨文章嘶是,自己粗淺的總結(jié)下來也需要不少文字,更何況里面有著許多的內(nèi)容需要反復(fù)的推敲和琢磨,也有著不少內(nèi)容需要大量的實(shí)踐和思考后才會有更深一步理解,.而每一段時(shí)間的實(shí)踐和思考想必讓這篇文章帶來的價(jià)值不斷彰顯,的確值得在不同時(shí)期都多看幾遍,一定要多回顧蛛碌。