最近有朋友推薦了clean-code, 簡單閱讀之后氯窍,發(fā)現(xiàn)是一些好的編程實踐啤挎,決定在閱讀過程中記錄下自己沒做到的或做的不好的奴饮,便于后面翻閱反思
文檔在這:clean-code-js
使用說明變量:
- 文檔中例子:
const cityStateRegex = /^(.+)[,\\s]+(.+?)\s*(\d{5})?$/;
saveCityState(cityStateRegex.match(cityStateRegex)[1], cityStateRegex.match(cityStateRegex)[2]);
// =>
saveCityState(city, state);
- 該例子個人理解:使用說明變量的目的是提高代碼可讀性严里,但該例子是否需要遵循可以完全看個人意愿
- 原因:對于閱讀代碼的人來說歇拆,提出參數(shù)固然能提升代碼可讀性鞋屈,但對于使用者來說,函數(shù)本身在定義時故觅,已經為其兩個參數(shù)賦予了意義厂庇,這種情況下,閱讀代碼的人只要懂這個函數(shù)输吏,肯定能明白參數(shù)的含義权旷,多使用兩個變量反而有點浪費
避免無意義的條件判斷
- 這一點就是代碼簡化的一部分,在不影響代碼可讀性的情況下,可以使用 || 或者 三目運算符等來簡化代碼拄氯,個人平時有注意到躲查,但是有些地方還做的不好
函數(shù)
函數(shù)參數(shù)(理想情況下應不超過2個)
- 參數(shù)數(shù)量這塊沒有注意過,不過對于分離函數(shù)功能這塊译柏,自己確實做的不好镣煮,后面需要深入了解一些SOLID之類的東西
函數(shù)功能的單一性
- 即是SOLID中的S,但自己對于功能單一的粒度難以掌控鄙麦,平時拆分函數(shù)都是看到函數(shù)過長典唇,操作過多,或者函數(shù)中的某一部分功能需要復用胯府,才會去進行拆分介衔,比如例子中:
function emailClients(clients) {
clients.forEach(client => {
let clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}
// =>
function emailClients(clients) {
clients.forEach(client => {
emailClientIfNeeded(client);
});
}
function emailClientIfNeeded(client) {
if (isClientActive(client)) {
email(client);
}
}
function isClientActive(client) {
let clientRecord = database.lookup(client);
return clientRecord.isActive();
}
- 如果讓我個人來做的話, 會覺得原本的函數(shù)已經足夠簡單,無需再簡化, 后面需要請教一下朋友關于功能單一的理解
不要使用標記(Flag)作為函數(shù)參數(shù)
這通常意味著函數(shù)的功能的單一性已經被破壞骂因。此時應考慮對函數(shù)進行再次劃分炎咖。
- 個人經常做這樣的操作,比如正在做的畢設里就有這樣的代碼:
<el-input
class="username-input"
@focus="() => {changeLoginImg('drawn')}"
></el-input>
<el-input
class="password-input"
@focus="() => {changeLoginImg('muffle')}"
></el-input>
changeLoginImg(signal) {
const img = signal === 'drawn'?drawn:muffle;
this.$refs['login-img'].setAttribute('src', img);
}
- 但個人感覺這種做法的也并未破壞函數(shù)單一性寒波,反而若是將兩個操作分開乘盼,倒是使得代碼更加冗余?
- 考慮一下影所,這個點應該是不需硬性要求的蹦肴,像我上面的情況僚碎,個人感覺現(xiàn)在的處理反而更合理
避免副作用
當函數(shù)產生了除了“接受一個值并返回一個結果”之外的行為時猴娩,稱該函數(shù)產生了副作用。比如寫文件勺阐、修改全局變量或將你的錢全轉給了一個陌生人等卷中。
程序在某些情況下確實需要副作用這一行為,如先前例子中的寫文件渊抽。這時應該將這些功能集中在一起蟆豫,不要用多個函數(shù)/類修改某個文件。用且只用一個 service 完成這一需求懒闷。
- 這個點的意思即是代碼中應該總是使用純函數(shù)十减,并將所有的非純函數(shù)集合在一起,這一點與redux三大原則中的使用純函數(shù)來執(zhí)行修改是同一個理念
不要寫全局函數(shù)
- 例子給出了一個為全局Array擴充自己函數(shù)的一個方式愤估,不同于定義在Array.prototype的做法帮辟,這樣可以避免污染全局變量:
class SuperArray extends Array {
constructor(...args) {
super(...args)
}
myFun() {
// ...
}
}
- ES6的class寫法自己早就了解過了,但平時卻并不會去使用它玩焰,究其原因由驹,便是自己對面向對象思想并不熟悉,思維方式還是面向過程的昔园,沒有類和對象的概念蔓榄,后面需要學一下
封裝判斷條件
- 書中只給了一個例子:
// 反例:
if (fsm.state === 'fetching' && isEmpty(listNode)) {
/// ...
}
// 正例:
function shouldShowSpinner(fsm, listNode) {
return fsm.state === 'fetching' && isEmpty(listNode);
}
if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
// ...
}
- 個人經驗并炮,在讀代碼時候確實會有花幾分鐘去讀一個復雜的“&& || ===”這種復雜的判斷條件的時候,這種寫法對寫的人來說很清晰甥郑,但對于讀代碼來說確實意義不明
- 因此逃魄,將判斷條件封裝一下是個比較好的實踐,后面編碼時需要注意
避免條件判斷
這看起來似乎不太可能壹若。
大多人聽到這的第一反應是:“怎么可能不用 if 完成其他功能呢嗅钻?”許多情況下通過使用多態(tài)(polymorphism)可以達到同樣的目的。
第二個問題在于采用這種方式的原因是什么店展。答案是我們之前提到過的:保持函數(shù)功能的單一性养篓。
// 反例:
class Airplane {
getCruisingAltitude() {
switch (this.type) {
case '777':
return getMaxAltitude() - getPassengerCount();
case 'Air Force One':
return getMaxAltitude();
case 'Cessna':
return getMaxAltitude() - getFuelExpenditure();
}
}
}
// 正例:
class Airplane {
//...
}
class Boeing777 extends Airplane {
//...
getCruisingAltitude() {
return getMaxAltitude() - getPassengerCount();
}
}
class AirForceOne extends Airplane {
//...
getCruisingAltitude() {
return getMaxAltitude();
}
}
class Cessna extends Airplane {
//...
getCruisingAltitude() {
return getMaxAltitude() - getFuelExpenditure();
}
}
- 個人并不能理解這種做法,這種不使用條件判斷的做法本質上只是把判斷向上提了一層而已赂蕴,這樣修改之后柳弄,也會需要使用判斷來決定調用哪個類的實例,只要程序的邏輯上存在分支,總是需要判斷的
- 而保持功能的單一性這個功能來說概说,這樣的改動確實合理碧注,將原本函數(shù)的三個功能抽離到各自的類中去
- 因此,個人感覺這個例子也應該作為功能單一性的實踐糖赔,至于標題所說的避免條件判斷萍丐,感覺不必這樣考慮
對象和數(shù)據結構
使用getters和setters
- 文檔中說明了這種方式相對于點操作符的好處,加些自己的理解:
- 當需要對獲取的對象屬性執(zhí)行額外操作時放典。
- 執(zhí)行 set 時可以增加規(guī)則對要變量的合法性進行判斷逝变。
- 封裝了內部邏輯。
- 在存取時可以方便的增加日志和錯誤處理奋构。
- 繼承該類時可以重載默認行為壳影。
- 從服務器獲取數(shù)據時可以進行懶加載。
- 這些優(yōu)點對比的是類的點操作和gettters弥臼,放在Object類型數(shù)據上并不一定適用宴咧,這點需要注意
- 第4點可以類比vuex中的getter和mutation&action,只要將數(shù)據的存取集合在一起径缅,就能方便數(shù)據的跟蹤和調試
Class
SOLID
單一職責原則(SRP):
最小化對一個類需要修改的次數(shù)是非常有必要的掺栅。如果一個類具有太多太雜的功能,當你對其中一小部分進行修改時纳猪,將很難想象到這一修夠對代碼庫中依賴該類的其他模塊會帶來什么樣的影響氧卧。
- 個人對于單一職責的范圍并不是很理解,但上面這句話有些經驗:
- 編碼過程中,修改一個類的功能之后,往往需要檢查其它使用該類的地方近上,檢查修改是否會造成一些意外的影響阀捅,當一個類的功能過于復雜時宿饱,就代表它會在多個完全不同的地方使用熏瞄,我們修改后的影響范圍也就越大,修改后檢查的工作量也就越大.單一職責的必要性就源于此
開/閉原則(OCP):
“代碼實體(類谬以,模塊强饮,函數(shù)等)應該易于擴展,難于修改为黎∮史幔”
這一原則指的是我們應允許用戶方便的擴展我們代碼模塊的功能,而不需要打開 js 文件源碼手動對其進行修改铭乾。
// 反例:
class AjaxRequester {
constructor() {
// What if we wanted another HTTP Method, like DELETE? We would have to
// open this file up and modify this and put it in manually.
this.HTTP_METHODS = ['POST', 'PUT', 'GET'];
}
}
// 正例:
class AjaxRequester {
constructor() {
this.HTTP_METHODS = ['POST', 'PUT', 'GET'];
}
addHTTPMethod(method) {
this.HTTP_METHODS.push(method);
}
}
- 這個例子展示的是一個擴展代碼的過程剪廉,當這個類需要一個DELETE的方法時,不去修改源碼炕檩,而是在設計類時斗蒋,就設計了這樣一個擴展方法,可以在使用的過程中隨時添加方法
- 感覺這個例子有些不太恰當笛质,封裝的ajax工具應當是一個通用的工具類泉沾,它內部的
HTTP_METHODS
應該是可以預知(請求的methods就那么幾種),且在程序中無論何時都適用的(不需要頻繁的增加或刪除methods), 因此個人覺得addHTTPMethod的設計并無必要 - 個人經驗:平時代碼過程中妇押,是否需要這樣設計類(設計擴展方法)是需要根據具體的功能需求來設計的跷究,這樣的設計雖然避免了直接修改源文件,但卻也有一些壞處:
- 搞清楚兩個代碼分別的含義 :
- 構造器中的
HTTP_METHODS
是初始化的方法敲霍,每次打開程序俊马,或者new一個AjaxRequester
實例,它們都是存在的色冀;- 而使用addHTTPMethod創(chuàng)建的方法潭袱,只會在程序運行過程中存在柱嫌,且只存在于當前實例中锋恬。
- 因此:
對于前者,缺點即是不能隨時使用代碼去增加修改编丘,必須去修改代碼
而后者与学,雖然我們在使用Instance1.addHTTPMethod('DELETE')
后,當前實例確實添加了這個method
嘉抓,但若是從該類中再實例化一個Instance2
索守,Instance2
上還是沒有DELETE方法,仍舊需要我們去手動調用方法抑片,再添加一次
- 因此卵佛,這樣的設計適合于當初始化的數(shù)據并不能滿足程序需求,以及我們對數(shù)據有頻繁的修改需求時使用, 如:
class SuperMarket{
constructor() {
this.goodsStore = ['candy', 'cookie']
}
addGoods(goods) {
this.goodsStore.push(goods)
}
}
利斯科夫替代原則(LSP)
“子類對象應該能夠替換其超類對象被使用”截汪。
也就是說疾牲,如果有一個父類和一個子類,當采用子類替換父類時不應該產生錯誤的結果衙解。
接口隔離原則(ISP)
“客戶端不應該依賴它不需要的接口阳柔;一個類對另一個類的依賴應該建立在最小的接口上◎韭停”
在 JS 中舌剂,當一個類需要許多參數(shù)設置才能生成一個對象時,或許大多時候不需要設置這么多的參數(shù)暑椰。此時減少對配置參數(shù)數(shù)量的需求是有益的霍转。
依賴反轉原則 (DIP)
該原則有兩個核心點:
- 高層模塊不應該依賴于低層模塊。他們都應該依賴于抽象接口一汽。
- 抽象接口應該脫離具體實現(xiàn)谴忧,具體實現(xiàn)應該依賴于抽象接口。
錯誤處理
代碼中 try/catch 的意味著你認為這里可能出現(xiàn)一些錯誤角虫,你應該對這些可能的錯誤存在相應的處理方案
書中沒有自己體會的部分:
采用函數(shù)式編程
- 個人對于函數(shù)式編程還是一知半解沾谓,找到了一本書JS函數(shù)式編程, 定在我下一本讀書計劃吧
使用方法鏈 & 優(yōu)先使用組合模式而非繼承 & 測試
- 這塊個人沒有經驗,也沒有自己的理解戳鹅,照著文檔抄并沒有任何意義均驶,等后期有所了解后再補充
避免類型判斷
- 這個點是為彌補js弱類型帶來的問題,個人打算后面抽時間學習TS