Kent Beck Implementation Pattern Principles(1)- Local Consequence
Kent Beck的Implementation Pattern 距離出版(2007)至今,也已經過了十幾年了。
個人覺得書中最精華的地方莫過於Value和Principle的章節(Chapter 3 Theory of Programming),今天想要來跟各位分享Principle的部分。
Kent beck認為: Principle是Value和Pattern之間的橋樑,Value較難直接應用,而Pattern相對之下比較容易應用但通常針對特定例子,因此Principle提供一個橋樑,懂了Principle後,當沒有特定的Pattern可以用或是兩個pattern都可以應用時,我們可以透過Principle去檢視Solution的可行性。
原文如下:
The principles bridge between the values, which are universal but often difficult to apply directly, and the patterns, which are clear to apply but specific. I have found it valuable to make the principles explicit for those situations where no pattern applies, or when two mutually exclusive patterns apply equally.
書中介紹的Principle總共有六個:
- 1. Local Consequences
- 2. Minimize Repetition
- 3. Logic and Data Together
- 4. Symmetry
- 5. Declarative Expression
- 6. Rate of Change
今天介紹第一個: Local Consequence。
Local Consequence: 調整程式碼的結構好讓改變可以是區域性的(ex: One Class or One Package),如果改變這邊會影響到那邊,這樣改變的成本會大幅提升。當程式碼是Local Consequence時,程式碼能夠有效地去溝通programmer的想法,因為維護者能夠逐漸的去理解程式結構,而不用了解整體才能理解。
原文如下:
Structure the code so changes have local consequences. If a change here can cause a problem there, then the cost of the change rises dramatically. Code with mostly local consequences communicates effectively. It can be understood gradually without first having to assemble an understanding of the whole.
這個Principle很重要,因為軟體最主要的目標就是能夠滿足客戶的需求,而且當客戶需求改變時,可以以最少成本的方式去修改。
以MVC為例子,為什麼View跟Model要分離?原因很簡單,因為我們不希望修改Model時影響到使用者介面,或是修改使用者介面時影響到Model,這些都是違反Local Consequence(因為要改變Model和View的Package),會讓開發更加困難。
上述是以High Level的觀點(Package)解釋,下述以實作細節(Class)的觀點解釋。
假設有兩個Class分別為:Circle和CirclePrinter。
public class Circle {
private final double radius;
public Circle(final double radius) {
this.radius = radius;
}
public double getRadius() {
return radius;
}
}public class CirclePrinter {
private final Circle circle;
public CirclePrinter(Circle circle) {
this.circle = circle;
}
public void printCircleArea() {
double result = calculateCircleArea();
System.out.println(result);
}
private double calculateCircleArea() {
return circle.getRadius()
* circle.getRadius()
* Math.PI;
}
}
如果有讀過Refactoring的人大概知道這個是Code Smell: Feature Envy,但為什麼Feature Envy不好?因為違反Local Consequence,當需求改變時(ex: radius的型態或計算的邏輯改變)需要修改兩個物件(Envy的Class和被Envy的Class)。
現在我們知道上述違反Local Consequence,所以我們要怎麼修改程式碼,好讓他遵守Local Consequence?
Refactoring的步驟如下:
- Move Method把calculateCircleArea移動到Circle Class
- Rename Method把calculateCircleArea改為calculateArea
- Self-Encapsulation把public getter改為private getter
public class Circle {
private final double radius;
public Circle(final double radius) {
this.radius = radius;
}
double calculateArea() {
return getRadius()
* getRadius()
* Math.PI;
}
private double getRadius() {
return radius;
}
}public class CirclePrinter {
private final Circle circle;
public CirclePrinter(Circle circle) {
this.circle = circle;
}
public void printCircleArea() {
double result = circle.calculateArea();
System.out.println(result);
}
}
如此一來,當我們要修改radius的型態或計算的邏輯改變時候,我們只需要修改Circle Class,而不會影響到CirclePrinter。
這也是物件導向的基本概念,物件負責自己的事情(Responsibility),Client只需要知道物件提供哪些功能(interface),而不用知道實作細節(implementation detail),也不用自己實作想要的功能(一開始的範例明顯是Client: CirclePrinter自己實作Circle應該實作的功能)。
所以當下次修改會影響到多個物件時,我們就可以知道,這是違反Local Consequence的後果,所以就可利用重構的手法,修改程式,達到Local Consequence,修改後的程式碼也會越來越容易修改,影響的範圍越來越少。