# 第五章 編排 ###### tags: `Tag(Clean Code)` [TOC] ## 大綱 程式碼除了命名與註解以外,編排是很重要的,因為編排是一種溝通方式。然而程式碼的可讀性的因素就是程式的編排,影響可維護和可擴充性,縱使程式碼已被修改成面目全非,程式風格和紀律依然存在。 --- ## 垂直的編排 ### 概念間的垂直空白區隔 第一種 ``` GradientDrawable dateBack = (GradientDrawable) balLayout.getBackground(); dateBack.setColor(Color.parseColor("#008A26")); GradientDrawable actBack = (GradientDrawable) actLayout.findViewById(R.id.layout).getBackground(); actBack.setColor(Color.parseColor("#008A26")); GradientDrawable saveBack = (GradientDrawable) saveLayout.findViewById(R.id.layout).getBackground(); saveBack.setColor(Color.parseColor("#40A8B3")); GradientDrawable debtBack = (GradientDrawable) debtLayout.findViewById(R.id.layout).getBackground(); debtBack.setColor(Color.parseColor("#FAA435")); GradientDrawable insuranceBack = (GradientDrawable) insuranceLayout.findViewById(R.id.layout).getBackground(); insuranceBack.setColor(Color.parseColor("#FE80B1")); ``` 第二種 ``` GradientDrawable dateBack = (GradientDrawable) balLayout.getBackground(); dateBack.setColor(Color.parseColor("#008A26")); GradientDrawable actBack = (GradientDrawable) actLayout.findViewById(R.id.layout).getBackground(); actBack.setColor(Color.parseColor("#008A26")); GradientDrawable saveBack = (GradientDrawable) saveLayout.findViewById(R.id.layout).getBackground(); saveBack.setColor(Color.parseColor("#40A8B3")); GradientDrawable debtBack = (GradientDrawable) debtLayout.findViewById(R.id.layout).getBackground(); debtBack.setColor(Color.parseColor("#FAA435")); GradientDrawable insuranceBack = (GradientDrawable) insuranceLayout.findViewById(R.id.layout).getBackground(); insuranceBack.setColor(Color.parseColor("#FE80B1")); ``` 空白行被刪除了,造成程式碼的可讀性明顯失焦了。 每一個空白行代表一個是覺得提示。 --- ### 垂直的順序 一個被呼叫的函式,應該要出現在『執行呼叫的函式』的下方。 --- ## 水平的編排 一個被呼叫的函式,應該要出現在『執行呼叫的函式』的下方, 相近的概念應放在同一個檔案裡。 * Variable Declarations(變數宣告) 區域變數應該在每個函式的上方進行宣告。 * Instance Variables(實體變數) * Dependent Functions(相依的涵式) 函式呼叫某個函式,在垂直編排上要盡可能靠近。 * Conceptual Affinity(概念相似性) --- ### 水平的空白間隔和密度 * 設定運算子 增加空白,使其更突出。 ``` private void measureLine(String line) { lineCount++; int lineSize = line.length(); totalChars += lineSize; lineWidthHistogram.addLine(lineSize, lineCount); recordWidestLine(lineSize); } ``` * 函數和其參數 小左括不會加入空白,會誤解為非相關。 ==(2*a)== 乘法因子間沒有空白,有較高的優先權。 ``` public class Quadratic { public static double root1(double a, double b, double c) { double determinant = determinant(a, b, c); return (-b + Math.sqrt(determinant)) / (2*a); } public static double root2(int a, int b, int c) { double determinant = determinant(a, b, c); return (-b - Math.sqrt(determinant)) / (2*a); } private static double determinant(double a, double b, double c) { return b*b - 4*a*c; } } ``` ### 水平的對齊 * 第一種 這樣的對齊強調了不該注意,讓目光無法看到程式碼真正意圖,不自覺被吸引去由上往下閱讀一連串的變數名稱,忽略了型態。 ``` public class FitNesseExpediter implements ResponseSender{ private Socket socket; private InputStream input; private OutputStream output; private Request request; private Response response; private FitNesseContext context; protected long requestParsingTimeLimit; private long requestProgress; private long requestParsingDeadline; private boolean hasError; public FitNesseExpediter(Socket s,FitNesseContext context) throws Exception { this.context = context; socket = s; input = s.getInputStream(); output = s.getOutputStream(); requestParsingTimeLimit = 10000; } } ``` * 第二種 問題會出現在列表的長度(需要被拆開) ``` public class FitNesseExpediter implements ResponseSender{ private Socket socket; private InputStream input; private OutputStream output; private Request request; private Response response; private FitNesseContext context; protected long requestParsingTimeLimit; private long requestProgress; private long requestParsingDeadline; private boolean hasError; public FitNesseExpediter(Socket s,FitNesseContext context) throws Exception { this.context = context; socket = s; input = s.getInputStream(); output = s.getOutputStream(); requestParsingTimeLimit = 10000; } } ``` ### 縮排 階層結構而非大鋼結構,沒有縮排的程式碼會造成視覺上的混淆,不易閱讀。 * 沒縮排 ``` public class FitNesseServer implements SocketServer { private FitNesseContext context; public FitNesseServer(FitNesseContext context) { this.context = context; } public void serve(Socket s) { serve(s, 10000); } public void serve(Socket s, long requestTimeout) { try { FitNesseExpediter sender = new FitNesseExpediter(s, context); sender.setRequestParsingTimeLimit(requestTimeout); sender.start(); } catch(Exception e) { e.printStackTrace(); } } } ``` * 有縮排 ``` public class FitNesseServer implements SocketServer { private FitNesseContext context; public FitNesseServer(FitNesseContext context) { this.context = context; } public void serve(Socket s) { serve(s, 10000); } public void serve(Socket s, long requestTimeout) { try { FitNesseExpediter sender = new FitNesseExpediter(s, context); sender.setRequestParsingTimeLimit(requestTimeout); sender.start(); } catch (Exception e) { e.printStackTrace(); } } } ```