為了獲得更好的閱讀體驗(yàn),請(qǐng)?jiān)L問(wèn)原文:傳送門(mén)
<https://www.wmyskxz.com/2019/09/14/dai-ma-zheng-ji-zhi-dao-du-shu-bi-ji/>
一、前言
代碼是什么呢?或者說(shuō)作為程序員的我們,對(duì)于寫(xiě)代碼這件事又是抱著怎樣的一種態(tài)度呢?我時(shí)常都在想,如今我如愿成為了一名程序員(雖然還很菜),寫(xiě)代碼這件事成了我的工作,我期望從工作中獲得些什么?而工作又能給予我什么呢?
我在短暫的工作經(jīng)歷中(4
個(gè)月),犯下過(guò)不少錯(cuò),少部分是因?yàn)榻?jīng)驗(yàn),但大部分的情況下都是因?yàn)閷?duì)代碼沒(méi)有足夠的敬畏之心導(dǎo)致的,并且在工作中也遇到過(guò)一些很有意思的代碼,所以今天就著這本《代碼整潔之道》,來(lái)談一談對(duì)于代碼的感受和一些想法。(Ps:想吐槽一下這本書(shū)挺魔怔的..)
二、什么是整潔的代碼
我搜索「代碼」這兩個(gè)關(guān)鍵字,給出的官方解釋都特別有意思,摘一下百度百科的好了:
代碼就是程序員用開(kāi)發(fā)工具
<https://baike.baidu.com/item/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/10464557>
所支持的語(yǔ)言寫(xiě)出來(lái)的源文件 <https://baike.baidu.com/item/%E6%BA%90%E6%96%87%E4%BB%B6/6999107>
,是一組由字符 <https://baike.baidu.com/item/%E5%AD%97%E7%AC%A6/4768913>、符號(hào)或信號(hào)碼元
<https://baike.baidu.com/item/%E7%A0%81%E5%85%83>
以離散形式表示信息的明確的規(guī)則體系。代碼設(shè)計(jì)的原則包括唯一確定性、標(biāo)準(zhǔn)化和通用性、可擴(kuò)充性與穩(wěn)定性、便于識(shí)別與記憶、力求短小與格式統(tǒng)一以及容易修改等。?源代碼
<https://baike.baidu.com/item/%E6%BA%90%E4%BB%A3%E7%A0%81/3814213>
是代碼的分支,某種意義上來(lái)說(shuō),源代碼相當(dāng)于代碼。現(xiàn)代程序語(yǔ)言
<https://baike.baidu.com/item/%E7%A8%8B%E5%BA%8F%E8%AF%AD%E8%A8%80/10696489>
中,源代碼可以書(shū)籍或磁帶形式出現(xiàn),但最為常用格式是文本文件,這種典型格式的目的是為了編譯出計(jì)算機(jī)程序
<https://baike.baidu.com/item/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A8%8B%E5%BA%8F/3220205>
。計(jì)算機(jī)源代碼最終目的是將人類可讀文本翻譯成為計(jì)算機(jī)可執(zhí)行的二進(jìn)制
<https://baike.baidu.com/item/%E4%BA%8C%E8%BF%9B%E5%88%B6/361457>指令
<https://baike.baidu.com/item/%E6%8C%87%E4%BB%A4/3225201>,這種過(guò)程叫編譯
<https://baike.baidu.com/item/%E7%BC%96%E8%AF%91/1258343>,它由通過(guò)編譯器
<https://baike.baidu.com/item/%E7%BC%96%E8%AF%91%E5%99%A8/8853067>完成。
好了,學(xué)術(shù)介紹一大堆,重點(diǎn)還是在最后一句:計(jì)算機(jī)源代碼最終目的是將人類可讀文本翻譯成為計(jì)算機(jī)可執(zhí)行的二進(jìn)制指令。
再精簡(jiǎn)一下:「人類可讀」、「計(jì)算機(jī)可執(zhí)行」。
說(shuō)到底,代碼最終還是寫(xiě)給人看的,所以「可讀性」就顯得尤為重要,但總歸我們是要先有「代碼」,再有「可讀的代碼」,經(jīng)過(guò)不斷重構(gòu)or重寫(xiě),最終形成我們「簡(jiǎn)潔的代碼」。
說(shuō)幾點(diǎn)感受比較大的吧。
方法盡量短 && 職責(zé)單一
有誰(shuí)能告訴我下面這個(gè)方法究竟是在做什么嗎?
/** * @author Administrator * */ public class GeneratePrimes { /** * @param
maxValue is the generation limit. * */ public static int[] generatePrimes(int
maxValue) { if (maxValue >= 2){ //the only valid case //ddeclarations int s =
maxValue +1 ;// size of array boolean[] f = new boolean[s]; int i; //initialize
array to true. for ( i = 0;i < s;i++) { f[i] = true; } f[0] = f[1] = false; //
sieve int j; for (i = 2;i < Math.sqrt(s) + 1; i++) { if (f[i]) { // if i is
uncrossed , cross its multiples. for (j = 2 * i; j < s ;j += i) { f[j] = false;
//multiple is not prime } } } // how many primes are there? int count = 0; for
(i = 0;i < s; i++) { if (f[i]) { count ++; //bump count. } } int[] primes = new
int[count]; //move the primes into the result for (i = 0,j = 0;i < s;i++) { if
(f[i]) { primes[j++] = i; } } return primes; } else { //maxValue < 2 return new
int[0]; // return null array if bad input. } } }
如果你非常有耐心地看完了,你可能大概或許會(huì)了解到,這是一個(gè)返回 maxValue 范圍以內(nèi)的質(zhì)數(shù)的方法,但是我們經(jīng)過(guò)簡(jiǎn)單的重構(gòu)之后,會(huì)變得更加容易理解:
public class PrimeGenerator { private static boolean[] crossedOut; private
static int[] result; public static int[] generatePrimes(int maxValue) { if
(maxValue < 2) { return new int[0]; } else { uncrossIntegersUpTo(maxValue);
crossOutMultiples(); putUncrossedIntegersIntoResult(); return result; } }
private static void putUncrossedIntegersIntoResult() { result = new
int[numberOfUncrossedIntegers()]; for (int j = 0, i = 2; i < crossedOut.length;
i++) { if (notCrossed(i)) { result[j++] = i; } } } private static int
numberOfUncrossedIntegers() { int count = 0; for (int i = 2; i <
crossedOut.length; i++) { if (notCrossed(i)) { count++; } } return count; }
private static void crossOutMultiples() { int limit =
determinuIterationLimit(); for (int i = 2;i <= limit; i++) { if (notCrossed(i))
{ crossOutMultiplesOf(i); } } } private static void crossOutMultiplesOf(int i)
{ for (int multiple = 2 * i; multiple < crossedOut.length; multiple +=i) {
crossedOut[multiple] = true; } } private static boolean notCrossed(int i) {
return crossedOut[i] == false; } private static int determinuIterationLimit() {
double iterationLimit = Math.sqrt(crossedOut.length); return
(int)iterationLimit; } private static void uncrossIntegersUpTo(int maxValue) {
crossedOut = new boolean[maxValue+1]; for (int i = 2; i < crossedOut.length ;
i++) { crossedOut[i] = false; } } }
首先我們通過(guò)私有方法隱藏掉了實(shí)現(xiàn)的具體細(xì)節(jié),并且使用有意義的命名,使得我們主函數(shù) generatePrimes 更加便于理解。
函數(shù)的第一規(guī)則就是要短小,第二條規(guī)則就是要更短小。每個(gè)函數(shù)保持職責(zé)單一,并且有意識(shí)的維持在一定行數(shù)內(nèi)(JVM 就強(qiáng)制要求每個(gè)函數(shù)要小于 8000
行...也聽(tīng)過(guò)每個(gè)函數(shù)盡量維持在 15 行 or 30 行
之內(nèi)這樣的說(shuō)法..可能有點(diǎn)魔怔,但要點(diǎn)就是函數(shù)要盡量短?。?,這當(dāng)然是最理想的情況,而現(xiàn)實(shí)的情況往往要糟糕一些。
在工作中,我就遇到過(guò)一些長(zhǎng)得可怕的方法,他們或許本來(lái)保持著單純,職責(zé)單一,但是經(jīng)過(guò)業(yè)務(wù)不斷的改造,需求不斷的疊加,甚至是一些臨時(shí)邏輯的加入,這個(gè)方法就變得越來(lái)越臃腫不堪...并且因?yàn)闃I(yè)務(wù)的不斷發(fā)展,越來(lái)越少的人會(huì)
care 到它,以至于改造成本越來(lái)越大,甚至被遺忘在角落..
這其實(shí)是再正常不過(guò)的事情,但在多人協(xié)作的項(xiàng)目中,有一點(diǎn)需要自己來(lái)維持清醒,那就是:「一個(gè)方法就可以返回的為什么要寫(xiě)兩個(gè)?」
,關(guān)于這一點(diǎn),保持自己的思考就好了..
注釋要體現(xiàn)代碼之外的東西
有一句聽(tīng)起來(lái)好厲害的話叫做:「代碼即注釋」,不知道大家是怎么看待這樣一句話的,或者說(shuō)是怎么看待注釋的。其實(shí)反過(guò)來(lái)想,如果你的代碼需要大量的注釋來(lái)解釋其中的邏輯,會(huì)不會(huì)是代碼本身就存在一定問(wèn)題?或者換個(gè)角度思考,注釋是用來(lái)解釋代碼邏輯的嗎?
可怕的廢話
我們來(lái)看下面這一段代碼的注釋:
/** The name. */ private String name; /** The version. */ private String
version; /** The licenceName. */ private String licenceName; /** The version.
*/ private String info;
上面這些 Javadoc
的目的是什么?答案是:無(wú)。并且仔細(xì)閱讀,你甚至?xí)l(fā)現(xiàn)一處剪切-粘貼導(dǎo)致的錯(cuò)誤,如果作者在寫(xiě)(或粘貼)注釋時(shí)都沒(méi)有花心思,怎么能指望讀者從中收益呢?
能用函數(shù)或變量時(shí)就別用注釋
看看以下代碼概要:
// does the module from the globale list <mod> depend on the // subsystem we
are part of? if
(smodule.getDependSubsystems().contains(subSysMod.getSubSystem())
可以修改成以下沒(méi)有注釋的版本:
ArrayList moduleDependes = smodule.getDependSubsystems(); String ourSubSystem
= subSysMod.getSubSystem(); if (moduleDependes.contains(ourSubSystem))
用代碼來(lái)闡述
有時(shí),代碼本身不足以解釋其行為。但不幸的是,許多程序員以此為由,覺(jué)得大部分時(shí)候代碼都不足以解釋工作。這種觀點(diǎn)純屬錯(cuò)誤,比如你愿意看到下面這個(gè):
// Check to see if the employee is eligible for full benefits if
((employee.flags & HOURLY_FLAG) && (employee.age > 65))
還是這個(gè):
if (employee.isEligibleForFullBenefits())
只需要多思考那么幾秒鐘,就能用代碼解釋你的大部分意圖。其實(shí)很多時(shí)候,簡(jiǎn)單到只需要?jiǎng)?chuàng)建一個(gè)描述與注釋所言同一事物的函數(shù)即可。
小結(jié)
注釋終歸是要用來(lái)體現(xiàn)代碼之外的東西..
名副其實(shí)的名字
取名字這件事,真的是程序員的一門(mén)藝術(shù)。腦海里面能浮現(xiàn)出同事們用翻譯軟件取名的畫(huà)面嗎?
一個(gè)好的名字再怎么夸贊都不為過(guò),但是這個(gè)最基礎(chǔ)的前提就是,它首先得是一個(gè)「正確的名字」。我就遇到過(guò)一次,函數(shù)名字叫做類似于 listAll
這樣的東西,戳進(jìn)去看實(shí)際上還基于業(yè)務(wù)規(guī)則做了過(guò)濾..(這樣的牛肉不對(duì)馬嘴的情況又讓我聯(lián)想到了注釋這樣的東西,可能實(shí)際的代碼已經(jīng)作了更改,但是注釋還是是維持原樣沒(méi)有變化..)
并且還有一個(gè)特別有意思的點(diǎn),就是關(guān)于名字的「長(zhǎng)度」。有時(shí)候可能為了想要描述清楚一個(gè)變量 or
一個(gè)類的具體作用,我會(huì)給它起一個(gè)看起來(lái)特別長(zhǎng)的名字..關(guān)于這個(gè),這里有一些小經(jīng)驗(yàn)可以分享一下:
* 去掉 Info 和 Data 這樣的后綴:這些就像是英語(yǔ)中的 a/ an/ the 一樣,是意義含糊的廢話,廢話都是冗余的..
* 不要給變量加前綴來(lái)標(biāo)識(shí):變量不需要一個(gè) m_ or 其他什么的前綴來(lái)標(biāo)識(shí)這是一個(gè)變量..
* 思考是否有必要標(biāo)識(shí)出變量的類型:我們標(biāo)注出變量的類型的目的是什么?對(duì)于弱類型的語(yǔ)言,可能有時(shí)候還是必要的,因?yàn)槲覀冇袝r(shí)候并不能從 students
這個(gè)變量中判明我應(yīng)該怎樣對(duì)這個(gè)變量進(jìn)行操作,但是對(duì)于 Java 這樣的強(qiáng)類型的語(yǔ)言,我們就需要根據(jù)實(shí)際的場(chǎng)景思考是否真有那么必要了。
無(wú)副作用
函數(shù)承諾只做一件事,但還是會(huì)做其他被隱藏起來(lái)的事。
public class UserValidator { private Cryptographer cryptographer; public
boolean checkPassword(String userName, String password) { User user =
UserGateway.findByName(userName); if (user != User.NULL) { String codedPhrase =
user.getPhraseEncodedByPassrod(); String phrase =
cryptographer.decrypt(codedPhrase, password); if ("Vliad
Passwordw".equals(phrase)) { Session.initialize(); return true; } } return
false; } }
上面的函數(shù)副作用就在于對(duì) Session.initialize() 的調(diào)用。checkPassword
函數(shù),顧名思義就是用來(lái)檢查密碼的,該名稱并未暗示它會(huì)初始化該次會(huì)話。所以,當(dāng)某個(gè)誤信了函數(shù)名的調(diào)用者想要檢查用戶有效性時(shí),就得冒著抹除現(xiàn)有會(huì)話數(shù)據(jù)的風(fēng)險(xiǎn)。
所以這里可以重命名函數(shù)為 checkPasswordAndInitializeSession,雖然這還是違背了 "只做一件事" 的規(guī)則。
函數(shù)參數(shù)盡可能少
一個(gè)函數(shù)最理想的參數(shù)數(shù)量是 0,其次是 1,再次是 2.. 要避免使用三個(gè)以上參數(shù)的情況,因?yàn)閰?shù)帶有太多的概念性,參數(shù)過(guò)多就會(huì)帶來(lái)更多的復(fù)雜性..
我就見(jiàn)過(guò)一個(gè)查詢接口,為了滿足不同的復(fù)雜查詢場(chǎng)景,參數(shù)大概可能有接近 10 個(gè).. 就算不為接手的編碼人員考慮,測(cè)試人員也會(huì)頭疼的..
想想看,要覆蓋如此兼容如此多場(chǎng)景如此復(fù)雜的一個(gè)查詢接口,測(cè)試用例究竟應(yīng)該怎么寫(xiě)呢?
More..
這本書(shū)說(shuō)實(shí)話看下來(lái)挺魔怔的.. 里面有許多簡(jiǎn)潔實(shí)用的觀點(diǎn)可以讓我們受益,我僅僅挑了一些最近比較感同身受的幾點(diǎn),來(lái)進(jìn)行了說(shuō)明。
代碼倉(cāng)庫(kù)就像是一本《哈姆雷特》一樣,每個(gè)人都有自己不同的見(jiàn)解,這無(wú)可厚非,我覺(jué)得重要的就是要保持對(duì)代碼的敬畏之心,保持自身的思考,才能讓我們不斷向前(說(shuō)話都變魔怔了..)
三、代碼之外
每個(gè)人都能寫(xiě)出好的代碼
這就是一個(gè)非常有意思的話題了,我們可以分成幾個(gè)角度來(lái)思考:
* 好的代碼是寫(xiě)出來(lái)的嗎?(這可能有點(diǎn)類似于好的文章是寫(xiě)出來(lái)的嗎?)
* 為什么我們寫(xiě)不出好的代碼?
我記得之前在看《重構(gòu):改善既有代碼的設(shè)計(jì)》
<https://www.wmyskxz.com/2019/06/08/chong-gou-gai-shan-ji-you-dai-ma-de-she-ji-du-shu-bi-ji/>
這本老經(jīng)典的書(shū)的時(shí)候,就提到一種觀點(diǎn)說(shuō):「重構(gòu)不是一個(gè)一蹴而就的事,需要長(zhǎng)期的實(shí)踐和經(jīng)驗(yàn)才能夠完成得很好。重構(gòu)強(qiáng)調(diào)的是 Be
Better,那在此之前我們首先需要先動(dòng)起手來(lái)搭建我們的系統(tǒng),而不要一味地“完美主義”?!?br>
好的代碼也是這樣,需要一個(gè)循序漸進(jìn)的過(guò)程,雖然大部分時(shí)候,經(jīng)驗(yàn)可以讓我們少走許多彎路,但這些都是一個(gè)過(guò)程。
當(dāng)然上面所說(shuō)的全部,都是理想中的狀況,而現(xiàn)實(shí)中的情況往往不允許我們這樣做。什么之前炒起來(lái)的 996,什么
ICU,都無(wú)情的揭示著大部分程序員的現(xiàn)狀:忙。忙于各種已經(jīng)堆成山的需求 && 修復(fù)各種 BUG 中。
我學(xué)到一個(gè)很正經(jīng)的概念,叫做「管窺」,附帶的一種概念叫做「稀缺」。(看完下面這個(gè)故事應(yīng)該很容易理解,故這里不作解釋..)
我記得之前看過(guò)一篇報(bào)道,說(shuō)是香港某富豪在節(jié)目中要體驗(yàn)幾天環(huán)衛(wèi)工人,參加節(jié)目前,他曾說(shuō)過(guò):“我的人生其實(shí)沒(méi)有很多時(shí)間坐下來(lái),想想現(xiàn)在的生活不錯(cuò),享受一下。我有時(shí)間就會(huì)計(jì)劃下一步!"
可幾天下來(lái),讓他最糾結(jié)的竟然是吃飯問(wèn)題,他對(duì)著鏡頭說(shuō):“很奇怪,我這兩天只是考慮吃東西,完全沒(méi)什么盼望,什么都不想。我努力工作,就是希望吃一頓好的。”
程序員是一種很容易陷入,對(duì)于時(shí)間「稀缺」?fàn)顟B(tài)的物種。
稀缺會(huì)俘獲我們的注意力,并帶來(lái)一點(diǎn)點(diǎn)好處:我們能夠在應(yīng)對(duì)迫切需求時(shí),做得更好。但從長(zhǎng)遠(yuǎn)的角度來(lái)看,我們的損失更大:我們會(huì)忽視其他需要關(guān)注的事項(xiàng),在生活的其他方面變得不那么有成效。(摘自《稀缺》P17)
這聽(tīng)上去就像是在找借口一樣,但其實(shí)有點(diǎn)差別。我發(fā)覺(jué)每個(gè)人其實(shí)都能夠?qū)懗龊玫拇a,只是取決于你有沒(méi)有這樣的意識(shí),有沒(méi)有堅(jiān)持自己的思考,更重要的是,有沒(méi)有「跳出需求」,甚至是「跳出工作」之外來(lái)思考,就像是要跳出「我們明明知道了很多道理,卻依然過(guò)不好這一生」的怪圈一樣。
結(jié)尾
這一段時(shí)間都不怎么更新了,不是我變懶了.. 前段時(shí)間就陷入了不加班就完成不了工作的狀態(tài),一方面是因?yàn)槭虑楸容^雜.. 另外一方面就是自己效率還不夠高..
(悄悄說(shuō):雖然很忙,但是總是能抽得出時(shí)間玩兒手機(jī) hhhh...)值得反思吧.. 最近也開(kāi)始有一些覺(jué)得越來(lái)越難下筆了.. 想寫(xiě)的東西很多,但總怕寫(xiě)不好..
另外,程序員真的是很有意思的職位了,并且也覺(jué)得程序員都多少帶著點(diǎn)兒自己的驕傲來(lái)得,因?yàn)槊刻於荚谧约旱氖澜缤鎯浩磮D,自己就是世界的造物主,久了,難免有些受影響..(主要體現(xiàn)在溝通上..)
摁.. 總之這是一本很好的書(shū),建議感興趣的童鞋可以溜一遍。
按照慣例黏一個(gè)尾巴:
歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明出處!
獨(dú)立域名博客:wmyskxz.com
簡(jiǎn)書(shū)ID:@我沒(méi)有三顆心臟 <https://www.jianshu.com/u/a40d61a49221>
github:wmyskxz <https://github.com/wmyskxz/>
歡迎關(guān)注公眾微信號(hào):wmyskxz
分享自己的學(xué)習(xí) & 學(xué)習(xí)資料 & 生活
想要交流的朋友也可以加qq群:3382693
錢(qián)
熱門(mén)工具 換一換
