重構
在系統重構過程中,系統改造強度一步一步強大,原有程式差異越來越大,引入新的BUG風險越高一步一步加大。
避免有效系統重構帶來風險:時間計劃與版控加強管理。
時間上將重構每個步驟劃分不同時間點(第一步與第二步合併成一個節點),每個時間點完成該步驟工作,在節點結束時打基線,形成一個獨立版本。
好處:後面重構遇到重大BUG可以回到某個基線點,確保重構工作不至於失敗,同時系統需要發佈,產品經理一步一步逐步發布版本,使重構逐步展開。
在前面步驟解決程式復用率,後續可以考慮系統擴展問題,主要目的可以更輕鬆應付系統需求變更,提升系統易變更性。
程式可擴展性的一般原則
預先進行可擴展性設計不要太多
需要進行設計前,應客觀評估該功能日後的可能性,針對可能性較大需求變更進行預先可擴展性設計。
常見可擴展設計在淤求變更到來才進行
運用兩頂帽子設計模式,先對原程式進行可擴展設計,再加入新的、要實現的功能。
開放—封閉原則與客擴展點設計
OCP設計原則:開發的軟體系統,對於功能是開放的。
當系統需求發生變更時候,可以針對軟體功能進行擴展,滿足新的需求,同時對軟體的修改是封閉。
這邊的意義是修改程式是無可避免,透過修改程式來實現新需求,關鍵新需求修改程式碼時候同時修改原有功能類別,勢必會為原有功能,引入BUG的風險。
上述的解法式,在不修改原有程式碼過的基礎上實現新功能。
常見問題:要實現新的功能必然會改到原有程式碼,但又不能修改原有程式碼,關鍵在於原有程式碼與新程式有效隔離。
為了保證原有功能不受影響,我們不能修改原有程式,但為了新的功能,我們可以增加新的類別,這是OCP原則關鍵所在。
以系統匯出功能為例
if(type =="All")
{
//全部匯出
}
else if(type == "exportChoosen")
{
//選擇匯出
}
else if(type == "exportOnePage")
{
//本頁匯出
}
若是增加一個需求、按頁匯出
if(type =="All")
{
//全部匯出
}
else if(type == "exportChoosen")
{
//選擇匯出
}
else if(type == "exportOnePage")
{
//本頁匯出
}
else if(type == "exportPageRange")
{
//按頁匯出
}
在多個不同選擇當中放在一個類別中必會為公能擴展帶來麻煩。
筆者會比較常用消除的選擇結構會有,目前所知的在C#中會有
- 字典、集合做法,若遇到物件可以採用Action或Fun方式去做
- 責任練
這樣設計會違反OCP原則,造成系統品質下降,使用IF大量的狀況會造成:降低可讀性、降低維護性、降低易變性。
總結:主要讓類別承受過多職責,降低功能內聚、提高功能耦合。
造成增加修改程式難度,測試的成本,主要是每一項修改,要進行所有功能測試。
再來先前寫的if else 重構也可以用這樣方式陳現
https://www.dotblogs.com.tw/bda605/2021/08/25/095349
上述按照這樣幾個思維解法,一旦遇到匯出新功能需要擴展時候,不用在加if相關程式進行,其實可以增加一個新類別,依據Exporter Interface實作新的類別進行,再進行相對應的配置功能就可以實現,這樣就可以滿足OCP原則,在系統就可以實現這種可擴展性設計的功能,稱之可擴展點。
以下展示以下該範例,不過這邊用運算類別為例,展示一下簡化的範例
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Dictionary<string, IMathOperator> strategies = new Dictionary<string, IMathOperator>() {
{ "+", new MathAdd() },
{ "-", new MathSub() }
};
IMathOperator selectedStrategy = Instantiate<IMathOperator>($"MathAdd");
int result = selectedStrategy.Oper(1, 2);
Console.WriteLine(result);
}
private static T Instantiate<T>(string className)
{
Type typeImplemented = typeof(T);
Type selectedType = Assembly.GetExecutingAssembly() // i.e. our project/library
.GetTypes()
.First(t => typeImplemented.IsAssignableFrom(t) && t.Name == className);
return (T)Activator.CreateInstance(selectedType);
}
}
public interface IMathOperator
{
int Oper(int num1, int num2);
}
public class MathAdd : IMathOperator
{
public int Oper(int num1, int num2)
{
return num1 + num2;
}
}
public class MathSub : IMathOperator
{
public int Oper(int num1, int num2)
{
return num1 - num2;
}
}
}
上述用加減,定義一個運算類別的Interface,接者再搭配新增類別去進行擴充。
該小節重點:
- 要遵守兩頂帽子設計原則,先重構原有程式碼,使具有可擴展性功能,再增加新功能,滿足OCP設計原則。
- 可擴展設計會增加程式複雜度,降低系統效能。
新需求到來,在不增加新功能條件下實現可擴展功能:
- 首先透過抽取方法將原有程式IF的語句內容,抽取到一個方法。
- 在運用抽取類別將方法抽離到多個類別中。
- 最後再抽離介面歸納不同的的類別,最後再抽離共同方法放到介面中。
流程擴展運用樣板模式增加可擴展點
系統需要新功能,又要符合ocp原則,新功能不影響原有功能設計,做擴展點設計將各個功能封裝一個介面下多個實作中,新功能擴展就是增加一個新類別實作,這樣設計解決許多問題,但也帶來新問題,各實作類別程式碼,複用的問題。
接者登場就是,處理流程中相同,相似程式碼很合適使用樣板模式,將相同程式碼抽取出來形成函數,將這些函數升級抽象類別與介面,鄉不同程式碼統一函數名稱,放在各個實作類別中各自實現,這樣程式碼複用問題就解決。
介面導向的可擴展設計
AOP是很像做手術一樣切出一些橫切面,這些橫切面就是程式可擴點,這些橫切面就是程式可擴展點。
常見問題:業務檢驗、授權檢查、交易處理。
在AOP設計中,最典型就是業務處理前的檢驗程式。
怎麼知道切面橫向思想?以下圖為例
上圖的系統模組分成訂單管理、庫存管理、商品管理,而這三部分是業務邏輯功能,這三個系統模組有共用的功能,授權認證、LOG處理,不可能在每一個模組寫授權驗證和LOG處理,而授權驗證不屬於具體業務,他屬於功能模組,並區分橫跨多個功能,可以看到這些是橫向,AOP就是把這些公用功能提取出來,這些公用功能發生變化,就只要修改公用功能程式碼即可,其他地方不需要更改,關注通用功能,而不用關注業務類別,而且不修改原有類別。
AOP特性:
1.通用功能業務邏輯衝離出來,提升程式重複使用率,好維護與擴展。
2.設計通用功能,有利模組化,降低軟體複雜度。
接者如何實現AOP的手段有兩種方式、靜態代理實現、動態代理實現(後續再補充)。
軟體的特性是越來越複雜,第一次開發品質最高,開始逐漸下降,每變更一次下降一次,軟體品質下降不是平穩下降,而是往後面,程式設計師越看不懂程式,越無法掌握程式開發高品質程式碼,選擇妥協,不論糟糕設計,只完成此次任務就好,一次一次糟糕設計,軟體品質呈指數下降。
修改軟體必須要思考過。
元哥的筆記