提高代碼可讀性的8個技巧

編程有很大一部分時間是在閱讀代碼,不僅要閱讀自己的代碼,而且要閱讀別人的代碼。因此,可讀性良好的代碼能夠大大提高編程效率??勺x性良好的代碼往往會讓代碼架構(gòu)更好,因為程序員更愿意去修改這部分代碼,而且也更容易修改。只有在核心領(lǐng)域為了效率才可以放棄可讀性,否則可讀性是第一位。
用名字代表代碼含義
一些比較有表達力的單詞:
單詞 可替代單詞
send deliver、dispatch、announce、distribute、route
find search、extract、locate、recover
start launch、create、begin、open
make create、set up、build、generate、compose、add、new
使用 i、j、k 作為循環(huán)迭代器的名字過于簡單,user_i、member_i 這種名字會更有表達力。因為循環(huán)層次越多,代碼越難理解,有表達力的迭代器名字可讀性會更高。
為名字添加形容詞等信息能讓名字更具有表達力,但是名字也會變長。名字長短的準則是:作用域越大,名字越長。因此只有在短作用域才能使用一些簡單名字。
名字不能帶來歧義
起完名字要思考一下別人會對這個名字有何解讀,會不會誤解了原本想表達的含義。
布爾相關(guān)的命名加上 is、can、should、has 等前綴。
? 用 min、max 表示數(shù)量范圍;
? 用 first、last 表示訪問空間的包含范圍;
? begin、end 表示訪問空間的排除范圍,即 end 不包含尾部。
良好的代碼風(fēng)格
適當(dāng)?shù)目招泻涂s進。
排列整齊的注釋:
int a = 1;? ?// 注釋
int b = 11;? // 注釋
int c = 111; // 注釋
語句順序不能隨意,比如與 html 表單相關(guān)聯(lián)的變量的賦值應(yīng)該和表單在 html 中的順序一致。
為何編寫注釋
閱讀代碼首先會注意到注釋,如果注釋沒太大作用,那么就會浪費代碼閱讀的時間。那些能直接看出含義的代碼不需要寫注釋,特別是不需要為每個方法都加上注釋,比如那些簡單的 getter 和 setter 方法,為這些方法寫注釋反而讓代碼可讀性更差。
不能因為有注釋就隨便起個名字,而是爭取起個好名字而不寫注釋。
可以用注釋來記錄采用當(dāng)前解決辦法的思考過程,從而讓讀者更容易理解代碼。
注釋用來提醒一些特殊情況。
用 TODO 等做標記:
標記 用法
TODO 待做
FIXME 待修復(fù)
HACK 粗糙的解決方案
XXX 危險!這里有重要的問題
如何編寫注釋
盡量簡潔明了:
// The first String is student's name
// The Second Integer is student's score
Map<String, Integer> scoreMap = new HashMap<>();
// Student's name -> Student's score
Map<String, Integer> scoreMap = new HashMap<>();
添加測試用例來說明:
// ...
// Example: add(1, 2), return 3
int add(int x, int y) {
? return x + y;
}
使用專業(yè)名詞來縮短概念上的解釋,比如用設(shè)計模式名來說明代碼。
提高控制流的可讀性
條件表達式中,左側(cè)是變量,右側(cè)是常數(shù)。比如下面第一個語句正確:
if (len < 10)
if (10 > len)
只有在邏輯簡單的情況下使用 ? : 三目運算符來使代碼更緊湊,否則應(yīng)該拆分成 if / else;
do / while 的條件放在后面,不夠簡單明了,并且會有一些迷惑的地方,最好使用 while 來代替。
如果只有一個 goto 目標,那么 goto 尚且還能接受,但是過于復(fù)雜的 goto 會讓代碼可讀性特別差,應(yīng)該避免使用 goto。
在嵌套的循環(huán)中,用一些 return 語句往往能減少嵌套的層數(shù)。
拆分長表達式
長表達式的可讀性很差,可以引入一些解釋變量從而拆分表達式:
if line.split(':')[0].strip() == "root":
? ? ...
username = line.split(':')[0].strip()
if username == "root":
? ? ...
使用摩根定理簡化一些邏輯表達式:
if (!a && !b) {
? ? ...
}
if (!(a || b)) {
? ? ...
}
變量與可讀性
去除控制流變量 。在循環(huán)中通過使用 break 或者 return 可以減少控制流變量的使用。
boolean done = false;
while (/* condition */ && !done) {
? ...
? if ( ... ) {
? ? done = true;
? ? continue;
? }
}
while(/* condition */) {
? ...
? if ( ... ) {
? ? break;
? }
}
減小變量作用域 。作用域越小,越容易定位到變量所有使用的地方。
JavaScript 可以用閉包減小作用域。以下代碼中 submit_form 是函數(shù)變量,submitted 變量控制函數(shù)不會被提交兩次。第一個實現(xiàn)中 submitted 是全局變量,第二個實現(xiàn)把 submitted 放到匿名函數(shù)中,從而限制了起作用域范圍。
submitted = false;
var submit_form = function(form_name) {
? if (submitted) {
? ? return;
? }
? submitted = true;
};
var submit_form = (function() {
? var submitted = false;
? return function(form_name) {
? ? if(submitted) {
? ? ? return;
? ? }
? ? submitted = true;
? }
}());? // () 使得外層匿名函數(shù)立即執(zhí)行
JavaScript 中沒有用 var 聲明的變量都是全局變量,而全局變量很容易造成迷惑,因此應(yīng)當(dāng)總是用 var 來聲明變量。
變量定義的位置應(yīng)當(dāng)離它使用的位置最近。
實例解析
在一個網(wǎng)頁中有以下文本輸入字段:
<input type = "text" id = "input1" value = "a">
<input type = "text" id = "input2" value = "b">
<input type = "text" id = "input3" value = "">
<input type = "text" id = "input4" value = "d">
現(xiàn)在要接受一個字符串并把它放到第一個空的 input 字段中,初始實現(xiàn)如下:
var setFirstEmptyInput = function(new_alue) {
? var found = false;
? var i = 1;
? var elem = document.getElementById('input' + i);
? while (elem != null) {
? ? if (elem.value === '') {
? ? ? found = true;
? ? ? break;
? ? }
? ? i++;
? ? elem = document.getElementById('input' + i);
? }
? if (found) elem.value = new_value;
? return elem;
}
以上實現(xiàn)有以下問題:
? found 可以去除;
? elem 作用域過大;
? 可以用 for 循環(huán)代替 while 循環(huán);
var setFirstEmptyInput = function(new_value) {
? for (var i = 1; true; i++) {
? ? var elem = document.getElementById('input' + i);
? ? if (elem === null) {
? ? ? return null;
? ? }
? ? if (elem.value === '') {
? ? ? elem.value = new_value;
? ? ? return elem;
? ? }
? }
};
抽取函數(shù)
工程學(xué)就是把大問題拆分成小問題再把這些問題的解決方案放回一起。
首先應(yīng)該明確一個函數(shù)的高層次目標,然后對于不是直接為了這個目標工作的代碼,抽取出來放到獨立的函數(shù)中。
介紹性的代碼:
int findClostElement(int[] arr) {
? ? int clostIdx;
? ? int clostDist = Interger.MAX_VALUE;
? ? for (int i = 0; i < arr.length; i++) {
? ? ? ? int x = ...;
? ? ? ? int y = ...;
? ? ? ? int z = ...;
? ? ? ? int value = x * y * z;
? ? ? ? int dist = Math.sqrt(Math.pow(value, 2), Math.pow(arr[i], 2));
? ? ? ? if (dist < clostDist) {
? ? ? ? ? ? clostIdx = i;
? ? ? ? ? ? clostDist = value;
? ? ? ? }
? ? }
? ? return clostIdx;
}
以上代碼中循環(huán)部分主要計算距離,這部分不屬于代碼高層次目標,高層次目標是尋找最小距離的值,因此可以把這部分代替提取到獨立的函數(shù)中。這樣做也帶來一個額外的好處有:可以單獨進行測試、可以快速找到程序錯誤并修改。
public int findClostElement(int[] arr) {
? ? int clostIdx;
? ? int clostDist = Interger.MAX_VALUE;
? ? for (int i = 0; i < arr.length; i++) {
? ? ? ? int dist = computDist(arr, i);
? ? ? ? if (dist < clostDist) {
? ? ? ? ? ? clostIdx = i;
? ? ? ? ? ? clostDist = value;
? ? ? ? }
? ? }
? ? return clostIdx;
}
并不是函數(shù)抽取的越多越好,如果抽取過多,在閱讀代碼的時候可能需要不斷跳來跳去。只有在當(dāng)前函數(shù)不需要去了解某一塊代碼細節(jié)而能夠表達其內(nèi)容時,把這塊代碼抽取成子函數(shù)才是好的。
函數(shù)抽取也用于減小代碼的冗余。
一次只做一件事
只做一件事的代碼很容易讓人知道其要做的事;
基本流程:列出代碼所做的所有任務(wù);把每個任務(wù)拆分到不同的函數(shù),或者不同的段落。
用自然語言表述代碼
先用自然語言書寫代碼邏輯,也就是偽代碼,然后再寫代碼,這樣代碼邏輯會更清晰。
減少代碼量
不要過度設(shè)計,編碼過程會有很多變化,過度設(shè)計的內(nèi)容到最后往往是無用的。
多用標準庫實現(xiàn)。