前言
這本書的封面寫道含鳞,“細(xì)節(jié)之中自有天地棱诱,整潔成就卓越代碼”忍啸,便是本書的精髓所在胡桨。對于軟件開發(fā),設(shè)計不僅存在于界面之中烫堤,更是存在于代碼之中荣赶,而對于一個軟件架構(gòu)來說,任何編碼的細(xì)節(jié)都至關(guān)重要鸽斟,代碼的整潔性關(guān)乎到后期的可維護(hù)性拔创,后人看代碼時的可讀性,所以我們要對自己寫下的每一行代碼負(fù)責(zé)富蓄。
第一章 整潔代碼
本章闡述了什么是整潔的代碼剩燥。
??首先是有代碼,現(xiàn)在人工智能風(fēng)行立倍,有傳言說以后計算機能自動敲代碼灭红,程序員將會被替代。暫且不說這個消息是真是假口注,即使是真的变擒,那被淘汰的也是“代碼猴子”。因為精確的優(yōu)雅的代碼還是需要由高級程序員來構(gòu)建的寝志。
??那為什么要寫整潔的代碼呢娇斑?如果你被爛代碼坑過那你一定會對整潔代碼更加有更深的理解和需求。當(dāng)你面對著一堆雜亂的代碼材部,不知產(chǎn)品提的需求從何做起毫缆,稍微一改動一點地方就會牽一發(fā)而動全身的時候,你一定想罵一句“WTF”了乐导。所以悔醋,寫整潔的代碼是程序員的一種職業(yè)素養(yǎng)和基本功,不僅是對自己負(fù)責(zé)兽叮,也是對以后開發(fā)這塊代碼的人負(fù)責(zé),當(dāng)別人一看就懂的時候猾愿,肯定會拉上去看一下這個膩害author是誰的哈哈哈鹦聪。
??最后,劃重點啦5倜亍T蟊尽!什么是整潔的代碼呢姻僧?書中引用了很多大神的觀點规丽,我總結(jié)了一些蒲牧,基本就是:
1. 代碼邏輯直截了當(dāng),模塊應(yīng)該盡量小赌莺,減少依賴關(guān)系冰抢,也就是我們經(jīng)常說的一個類一個責(zé)任的原則,讓人一看就懂艘狭,不引人猜測挎扰,以免誘導(dǎo)別人做出錯誤的判斷;
2. 增強對錯誤的處理巢音,例如內(nèi)存泄露遵倦,競態(tài)條件等情況,使代碼能通過所有測試官撼;
3. 沒有重復(fù)的代碼梧躺,重復(fù)的地方盡量抽取成一個函數(shù),明確該函數(shù)的功能傲绣,提高代碼的表達(dá)力掠哥。
第二章 有意義的命名
命名這件事貫穿于我們的代碼之中,小到變量斜筐,函數(shù)龙致,大到類名,包名顷链,都要用到命名目代,所以命名這件事看似很小,實則對代碼的可讀性起到重要的作用嗤练。
int d;
int day;
int elapsedTimeInDays;
如果該變量是代表流逝的天數(shù)榛了,
用elapsedTimeInDays則最佳,名副其實煞抬;
用day霜大,程序員結(jié)合下文的代碼也許可以估摸出來是什么意思,也許估摸不出來革答;
用d战坤,這個估計看的人就要懵逼了。
引用項目中的一個命名:private static final int DEFAULT_PRESSED_COLOR_ALPHA残拐,這個命名一看就能明白這個變量代表按鈕默認(rèn)按壓時顏色透明度的變化程度途茫。
現(xiàn)在來看方法的命名:
public List<int[]> getThem() {
List<int[]> list1 = new ArrayList<int[]>();
for (int[] x : theList) {
if (x[0] == 4) {
list1.add(x);
}
}
return list1;
}
單看這個方法,我會問幾個問題:
1.getThem()想得到什么溪食?
2.其次theList和list1代表什么囊卜?
3.最后為什么要有if(x[0] == 4)的邏輯判斷?
public List<Cell> getFlaggedCells() {
List<Cell> flaggedCells = new ArrayList<>();
for (Cell cell : gameBoard) {
if (cell.isFlagged()) {
flaggedCells.add(cell);
}
}
return flaggedCells;
}
我們再看上面那個方法就很清晰了,這個方法要得到被標(biāo)記的單元栅组,從這個游戲的所有單元格里面進(jìn)行遍歷雀瓢,只要遍歷到的單元格被標(biāo)記了就把它加入已標(biāo)記的容器里面。
從這個例子中我們可以看出玉掸,良好的代碼命名能簡化我們的工作量刃麸,并且令身心愉悅。
所以排截,良好的代碼命名規(guī)范主要由以下幾點:
- 代碼的命名要名副其實嫌蚤,顧名思義,能直接了解到該處代碼的作用断傲;
- 代碼的命名應(yīng)該避免歧義脱吱,這個歧義有幾種情況,
a. 減少英文字母o和l的單獨使用认罩,因為他們看起來像阿拉伯?dāng)?shù)字0和1箱蝠;
b. 是同一個模塊中的命名盡量不出現(xiàn)近義詞,例如info和data垦垂,不然看的人不知道這兩個有什么區(qū)別宦搬;
c. 是減少使用雙關(guān)語,例如add劫拗,難以區(qū)分是增加還是連接间校; - 名稱的長短應(yīng)該與作用域相對應(yīng),因為作用域越大页慷,該命名的被搜索的可能性就越大憔足,一般來說長命名勝于短命名,可以實現(xiàn)精準(zhǔn)搜索酒繁;
- 類名應(yīng)該使用名詞滓彰,方法名應(yīng)該使用動詞;
- 使用源自所涉問題領(lǐng)域的命名州袒,例如社區(qū)用forum相關(guān)揭绑,理財用finance相關(guān)。
函數(shù)
在面向?qū)ο蟮恼Z言中郎哭,我們編寫一個類他匪,基本是由變量和函數(shù)(方法)構(gòu)成的,變量主要是命名夸研,那函數(shù)的編寫原則在哪呢邦蜜?
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initView();
findView();
setView();
setListener();
}
1.函數(shù)要盡可能短小,當(dāng)一個函數(shù)過于長陈惰,比如我們一個Activity的onCreate()方法,這時候我們就應(yīng)該適當(dāng)?shù)某槿〕鲆恍┬碌暮瘮?shù)出來;
- 函數(shù)應(yīng)該只做一件事抬闯,并做好這件事井辆,例如initView()就應(yīng)該做初始化布局的工作,把findViewById(R.id.XXX)的工作留給findView()來完成溶握;
- 函數(shù)的語句應(yīng)該在同一抽象級上杯缺,例如上面onCreate()里面的方法就屬于較高抽象級,不要突然給一個控件來一句setColor()睡榆,這感覺就有點雜亂了萍肆;
Tips:無論是寫代碼還是,讀代碼胀屿,都應(yīng)遵循自頂向下的原則塘揣;
if(isExists(username)) {
setNewPassword(username, newPassword);
... ...
}
- 函數(shù)要么只做一件事,要么回答一個問題宿崭,不要同時做著兩個工作亲铡;
try {
JSONObject headValueJO = new JSONObject();
headValueJO.put("version","1.0");
headValueJO.put("bizcode", "1007");
HttpManagerHelper.getInstance().postRequest(financeWalletInfoUrl, nameValuePairs);//請求發(fā)送通知
} catch (JSONException e) {
DebugUtil.exception(TAG, e);
} catch (Exception e) {
DebugUtil.exception(TAG, e);
}
}
- 使用異常去代替返回錯誤碼,主要有以下幾點原因:
a. 異常的錯誤處理代碼能直接從主路徑中分離出來葡兑;
b. 明確錯誤處理的函數(shù)工作責(zé)任單一性奖蔓;
c. 避免”依賴磁鐵“的重新編譯和部署的麻煩性;
其他的一些原則:
- 函數(shù)的參數(shù)應(yīng)該盡可能少讹堤,如果參數(shù)太多了吆鹤,可能會增加我們對這個函數(shù)的理解和使用難度,這時候我們應(yīng)該把這些參數(shù)抽象成對象洲守;
- 抽取方法疑务,消除冗余,減少重復(fù)岖沛,這實際在我們的開發(fā)中經(jīng)常遇到暑始,可以算是一個經(jīng)驗了,功能先實現(xiàn)婴削,然后完成后慢慢打磨代碼廊镜,找到重復(fù)的地方,抽取出來進(jìn)行優(yōu)化唉俗,當(dāng)然也包括code review的時候有人會去檢查代碼的冗余程度嗤朴;
注釋
首先強調(diào)一點:若代碼足夠有表達(dá)力,那么并不需要注釋虫溜。這也是本書對注釋一個最核心的概念雹姊,所以:
- 注釋并不能美化糟糕的代碼,能用代碼闡述行為的就不要用注釋去解釋代碼衡楞;
- 好的注釋包括:
a. 法律信息吱雏,公司信息,作者等;
b. 提供信息的注釋,例如正則表達(dá)式的注釋;
c. 對意圖的注釋歧杏,代碼中可能使用了某個int值或者小技巧例如延時操作镰惦,需要注釋來解釋意圖;
d. 警示,例如不推薦使用犬绒,但還沒重構(gòu)完成的方法;
e. TODO注釋,標(biāo)注還沒完成的工作旺入,方便定位
f. API文檔的Javadoc; - 壞的注釋包括:
a. 喃喃自語的注釋,只有自己看的懂的注釋凯力;
b. 多余的注釋茵瘾,通過代碼已經(jīng)能清晰明白含義就不需要注釋了;
c. 誤導(dǎo)性的注釋咐鹤,注釋不夠精確拗秘,和代碼含義不一致的注釋(有可能是歷史遺留原因造成的);
d. 注釋掉的代碼,這樣的代碼別人看了也不敢刪慷暂,無用的代碼就會積少成多聘殖;
舉個例子解釋一下好和壞的注釋:
// private static final int DEFAULT_PRESSED_COLOR_ALPHA = 51; // 按鈕按壓時顏色的透明度變化
private static final int DEFAULT_PRESSED_COLOR_ALPHA = 51; // 透明度為20%
上面的例子首先是一個int值為51的靜態(tài)變量:
最開始的注釋為“按鈕按壓時顏色透明度的變化”,其實這就是一個多余的注釋行瑞,因為從變量名已經(jīng)能很清楚的解釋這個變量名的含義奸腺;
所以我們更改注釋為“透明度為20%”,就能很好的解釋int值為“51”的意圖血久;
當(dāng)代碼使我們自己寫的突照,又沒用到的時候就直接刪除掉,不要像上面一樣注釋掉氧吐,以防成為陳年老醋讹蘑。
格式
團(tuán)隊?wèi)?yīng)該一致同意采用一套簡單的格式規(guī)則,可以運用工具將這些規(guī)則自動化的工具筑舅。
代碼格式關(guān)乎溝通座慰,而溝通是專業(yè)開發(fā)者的頭等大事。
或許你認(rèn)為“讓代碼能工作”才是專業(yè)開發(fā)者的第一優(yōu)先級翠拣,你今天編寫的功能版仔,極有可能在下一版本中被修改,但代碼的可讀性卻會對以后可能發(fā)生的修改行為產(chǎn)生深遠(yuǎn)影響误墓。
原始代碼修改之后很久蛮粮,其代碼風(fēng)格和可讀性仍會影響到可維護(hù)性和擴展性。即便代碼已不復(fù)存在谜慌,你的風(fēng)格和律條仍存活下來然想。
每個人有不同的代碼風(fēng)格這無可厚非,但是一些被普遍認(rèn)可的代碼格式有:
//設(shè)置底部圖標(biāo)
int normalColor = SkinManager.getInstance().getSkinColor(Skinable.KEY_MAIN_BOTTOM_TEXT_COLOR);
int pressedColor = DrawableUtil.changeColorAlpha(bottomColor, DEFAULT_PRESSED_COLOR_ALPHA);
ColorStateList colorStateList = DrawableUtil.createColorStateList(bottomColor, pressedColor);
setNavBtnIcon(mNavYearTransBtn, R.drawable.nav_year_trans, Skinable.BOTTOM_NAV_ICON_1, colorStateList);
setNavBtnIcon(mNavAccountBtn, R.drawable.nav_account, Skinable.BOTTOM_NAV_ICON_2, colorStateList);
setNavBtnIcon(mNavReportBtn, R.drawable.nav_report, Skinable.BOTTOM_NAV_ICON_3, colorStateList);
setNavBtnIcon(mNavBudgetBtn, R.drawable.nav_finance, Skinable.BOTTOM_NAV_ICON_4, colorStateList);
setNavBtnIcon(mNavSettingBtn, R.drawable.nav_setting, Skinable.BOTTOM_NAV_ICON_5, colorStateList);
mNavYearTransBtn.setTextColor(colorStateList);
mNavAccountBtn.setTextColor(colorStateList);
mNavReportBtn.setTextColor(colorStateList);
mNavBudgetBtn.setTextColor(colorStateList);
mNavSettingBtn.setTextColor(colorStateList);
1.垂直方向上的間隔與靠近欣范,空白行隔開了不同概念的代碼塊变泄,靠近的代碼行則暗示了它們之間的緊密關(guān)系令哟。這有助于對代碼的理解;
2.水平方向上的空白與靠近妨蛹, 空格字符可以把相關(guān)性較弱的事物分隔開励饵,比如參數(shù)之間;
3.利用縮進(jìn)模式來看清楚自己在哪個范圍工作滑燃,這樣可以快速跳過與當(dāng)前關(guān)注的情形無關(guān)的范圍;
4.團(tuán)隊規(guī)則颓鲜,一套讀起來不錯的代碼系統(tǒng)表窘,需要一直和順暢的風(fēng)格;
對象與數(shù)據(jù)結(jié)構(gòu)
簡單介紹一下幾個主要概念:
對象:把數(shù)據(jù)隱藏于抽象之后甜滨,暴露操作數(shù)據(jù)的函數(shù)乐严;
數(shù)據(jù)結(jié)構(gòu):暴露其數(shù)據(jù),沒有提供有意義的函數(shù)衣摩;
過程式編程:便于在不改動既有數(shù)據(jù)結(jié)構(gòu)的前提下添加新函數(shù)昂验;
面向?qū)ο缶幊蹋罕阌谠诓桓膭蛹扔泻瘮?shù)的前提下添加新類;
德墨忒爾律:模塊不應(yīng)該了解他所操作對象的內(nèi)部情況艾扮。對象隱藏數(shù)據(jù)既琴,暴露操作,這意味著對象不應(yīng)該通過存取器暴露其內(nèi)部結(jié)構(gòu)泡嘴。
火車失事:連串調(diào)用是骯臟的風(fēng)格甫恩,方法不應(yīng)調(diào)用由任何函數(shù)返回的對象的方法。只和朋友談話酌予,不和陌生人談話磺箕。(同一個對象的話例外,例如RxJava的鏈?zhǔn)秸{(diào)用)
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
所以上述代碼最好切分下面這樣的代碼抛虫,也可以方便出現(xiàn)空指針異常的時候及時找出null值出現(xiàn)的位置松靡。
Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();
類
類的組成:
public class Example {
public 公共靜態(tài)常量;
private 私有靜態(tài)變量;
private 私有實體變量;
public 公共函數(shù)1() {
......
調(diào)用私有工具函數(shù)1();
}
private 私有工具函數(shù)1() {
......
}
public 公共函數(shù)2() {
......
調(diào)用私有工具函數(shù)2();
}
private 私有工具函數(shù)2() {
......
}
}
類應(yīng)該從一組變量列表開始。
如果有公共靜態(tài)常量建椰,應(yīng)該先出現(xiàn)雕欺,然后是私有靜態(tài)變量,以及私有實體變量广凸,很少會有公共變量阅茶。
公共函數(shù)應(yīng)跟在變量列表之后,把由某個公共函數(shù)調(diào)用的私有工具函數(shù)緊隨在該公共函數(shù)后面谅海,這符合了自頂向下原則脸哀。
類的設(shè)計原則:
- 類應(yīng)該短小
- 系統(tǒng)應(yīng)該由許多短小的類而不是少量巨大的類組成;
- 保持內(nèi)聚性就會得到許多短小的類扭吁;
- 如果類喪失了內(nèi)聚性撞蜂,就拆分它盲镶;
- 類只應(yīng)該有一個權(quán)責(zé)——只有一個修改的理由(單一權(quán)責(zé)原則,SRP)蝌诡;
- 類應(yīng)該對擴展開發(fā)溉贿,對修改封閉(開放-閉合原則,OCP)浦旱;
- 類應(yīng)該依賴于抽象而不是具體細(xì)節(jié)宇色,通過部件之間的解耦來隔離修改(依賴倒置原則,DIP)颁湖;
跌進(jìn)
軟件項目的主要成本在于長期維護(hù)宣蠕,所以在我們長期開發(fā)和維護(hù)代碼的過程中
主要有以下幾個簡單的設(shè)計原則:
- 運行所有測試,同樣編寫測試越多甥捺,就會越遵循DIP之類的原則抢蚀,使用依賴注入,接口和抽象等工具盡可能減少耦合镰禾;
- 重構(gòu)皿曲,在重構(gòu)過程中,可以應(yīng)用有關(guān)優(yōu)秀軟件設(shè)計的一切知識吴侦,提升內(nèi)聚性屋休,降低耦合度;
- 不可重復(fù)备韧,重復(fù)是良好設(shè)計系統(tǒng)的大敵博投。它代表著額外的工作、額外的風(fēng)險和額外不必要的復(fù)雜度盯蝴;
- 提高表達(dá)力毅哗,選用好名稱、保持函數(shù)和類尺寸短小捧挺、標(biāo)準(zhǔn)命名法虑绵,其實說白了就是提高代碼的可讀性;
- 盡可能少的類和方法闽烙,有時候一些類和方法是被刻意寫出來的翅睛,需不需要為此創(chuàng)建新的類和方法需要靈活處理。
總結(jié):
測試一定是處于第一位的黑竞,測試在保證功能正確的同時捕发,也是我們進(jìn)行重構(gòu)的最基本保證。
為了使代碼可測試很魂,我們必須在開發(fā)的過程中不斷的打磨代碼扎酷,持續(xù)的降低耦合度,提高內(nèi)聚遏匆,使類與函數(shù)功能單一法挨、簡單谁榜。
從這個意義上講,測試也是重構(gòu)的持續(xù)驅(qū)動力凡纳。
并發(fā)編程
本章只是簡單介紹了一些并發(fā)的概念以及處理并發(fā)時應(yīng)該注意的一些問題
一些并發(fā)編程的認(rèn)識:
- 編寫并發(fā)程序會在代碼上增加額外的開銷窃植。;
- 正確的并發(fā)是非常復(fù)雜的荐糜,即使對于很簡單的問題巷怜;
- 并發(fā)中的缺陷因為不易重現(xiàn)也不容易被發(fā)現(xiàn);
- 并發(fā)往往需要對設(shè)計策略從根本上進(jìn)行修改暴氏;
一些并發(fā)編程的原則:
- 單一職責(zé):并發(fā)已經(jīng)足夠復(fù)雜丛版,我們更需要代碼分離,分離線程相關(guān)代碼和非線程相關(guān)代碼偏序,單一權(quán)責(zé),盡可能降低其復(fù)雜度胖替;
- 限制臨界資源的作用域:為臨界資源加鎖是防止并發(fā)的策略研儒,但是必須正確的加鎖,如果形成等待環(huán)独令,就導(dǎo)致死鎖端朵;
- 利用數(shù)據(jù)副本在線程之間傳遞數(shù)據(jù):避免線程之前操作的并發(fā)影響;線程獨立燃箭,使其在自己的環(huán)境中運行冲呢,不能其他線程共享數(shù)據(jù);
- 線程盡可能獨立:讓線程存在于自己的世界中招狸,不與其他線程共享數(shù)據(jù)敬拓;
- 對于臨界資源加鎖應(yīng)盡量保持加鎖范圍盡可能的小。
一些并發(fā)編程的基礎(chǔ)定義:
一些并發(fā)編程的執(zhí)行模型:
1.生產(chǎn)者-消費者模型
一個或多個生產(chǎn)者線程創(chuàng)建某些工作裙戏,并置于緩存或者隊列中乘凸。
一個或者多個消費者線程從隊列中獲取并完成這些工作。生產(chǎn)者和消費者之間的隊列是一種限定資源累榜。
2.讀者-作者模型
當(dāng)存在一個主要為讀者線程提供信息源营勤,但只是偶爾被作者線程更新的共享資源,吞吐量就會是個問題壹罚。
增加吞吐量葛作,會導(dǎo)致線程饑餓和過時信息的積累。
協(xié)調(diào)讀者線程不去讀取正在更新的信息猖凛,而作者線程傾向于長期鎖定讀者線程赂蠢。
3.宴席哲學(xué)家
許多企業(yè)級應(yīng)用中會存在進(jìn)程競爭資源的情形,如果沒有用心設(shè)計辨泳,這種競爭會遭遇死鎖客年,活鎖霞幅,吞吐量和效率低等問題。