這裡作者所指稱的編排,以我理解後的白話文就是:「在視覺上如何對程式碼進行排版。」
大至不同檔案、模組間的安排,小至類別 (class) 內變數與方法的書寫疏密度,都包含在此議題內。
......程式的編排是很重要的。因為太重要,所以我們不能忽略它,也因為太重要,所以我們必須嚴肅加以看待。程式的編排是一種溝通方式,而溝通是專業開發者的首要之務。
也許你覺得「讓程式碼能工作」才是專業開發者的首要之務。然而,我希望從現在開始,這本書能夠讓你重新反思這個想法。
在目前這 8 天的共讀時間裡,多少能感受到 Uncle Bob 非常在意程式碼易於閱讀這件事。而這件事是很容易理解的。
在該書作者的另一部著作【無瑕的程式碼:整潔的軟體設計與架構篇】中提到,軟體 (software) 之所以為軟體,就是在於它易於更改,否則就該稱之為硬體 (hardware)。而既然軟體易於更改也很常被更改,它的可讀性就至關重要,因為每當我們要修改程式的時候,都勢必要讀過一次。是以「可讀性」,就將是「可維護性」避不開的一環。
然而作者認真看待編排的程度還是大大出乎我的意料。我甚至沒想過有任何一本講程式、講軟體的書,會提到編排這件事。
我是會對程式的視覺呈現做布局的人,但我也一直認為那只是自己對於段落的偏執,直到我讀了這篇章。
程式需要垂直的編排,而在作者的觀點裡,越是相關的變數、函式,就會放的越近,反之亦然。接下來我們會分別從垂直面上的密度、距離及順序來認識它。
程式與程式之間,會以空白來區隔,我們先來看點程式碼:
package futness.wikitext.widget
import java.util.regex
public class BoldWidget extends ParentWidget{
public static final String REGEXP = "''.+?''";
public static final Pattern pattern = Pattern.compile("''(.+?)''", Pattern.MULTILINE + Pattern.DOTALL);
public BoldWidget(ParentWidget parent, String text) throws Exception {
super(parent);
Matcher match pattern.matcher(text);
match.find();
addChildWidgets(match.group(1));
}
public String render() throws Exception {
StringBuffer html = new StringBuffer("<b>");
html.append(childHtml()).append("<b>");
return html.toString();
}
}
package futness.wikitext.widget
import java.util.regex
public class BoldWidget extends ParentWidget{
public static final String REGEXP = "''.+?''";
public static final Pattern pattern = Pattern.compile("''(.+?)''", Pattern.MULTILINE + Pattern.DOTALL);
public BoldWidget(ParentWidget parent, String text) throws Exception {
super(parent);
Matcher match pattern.matcher(text);
match.find();
addChildWidgets(match.group(1));}
public String render() throws Exception {
StringBuffer html = new StringBuffer("<b>");
html.append(childHtml()).append("<b>");
return html.toString();}}
上面是兩段一模一樣的簡短程式,但缺少空白行使得第二段程式的閱讀性顯著地降低,且一旦程式的數量變多,其中的效應將更加明顯。
間隔讓程式間不同的概念被凸顯出來,變數與變數、變數與函式、函式與函式,隨著彼此間關聯性的疏密,我們也會給予它們彼此間有不同的密度。
變數的宣告應該盡可能靠近變數被使用的地方,而函式也是如此。否則,我們將被迫在努力想了解整個系統在做什麼
時,卻花費大把時間在尋找及記憶程式碼在哪裡
。所以我會在函式的正上方去宣告變數。
let teacher;
const hireTeacher = (teacherName: string) => {
teacher = teacherName;
}
const fireTeacher = () => {
teacher = '';
}
如果函式的概念相近,那麼隨著它們之間的同質性越高,距離就該越短;同樣地,如果函式彼此相依,也就是說某個函式呼叫了另一個函式,那這兩個函式在垂直編排上也需要盡可能靠近,這將增加程式的可讀性。而這也呼應到了作者前面所說的「降層準則」,並帶出順序的概念。
總括來說,我們希望函式呼叫呈現一種向下的相依性。也就是說,一個被呼叫的函式,應該要出現在『執行呼叫的函式』的下方。這也產生了一個良好的向下流程,由上往下查看原始碼時,可以依序發現高層模組,再接著找低層模組。
這就像是我們在寫作文,最重要的主旨會先出現,接著我們再細節地闡述、剖悉,同樣地,在程式的撰寫上,我們也會盡量先將大的概念呈現出來,再慢慢揭露出底層實現的細節。
除了在垂直的面向上,水平也有其編排的原則,雖然這些原則因為現在許多 IDE 都已提供自動化的排版,而已漸漸不再被重視。書中列舉了些水平上的編排,例如縮排、加減乘除運算子的間隔,但一方面覺得這些過於細節,另一方面目前確實也不太會用到水平的編排,所以就不再細講。事實上縱然費力設計,通常在 IDE 自動重新編排後也會消失,所以現在的編排原則,最重要的應該是與團隊討論出統一的風格。
......每個程式設計師有自己偏愛的編排,但如果他身處在一個團隊裡,那他必須以團隊的編排準則為主。......我們維持一致的軟體編排風格。我們並不想讓它看起來是由一群意見不合的個體所寫成的。