1. 引言
良好設計的函式具有清晰的職責和邏輯結構,提供準確的命名和適當的引數控制,它們促進代碼復用、支持團隊協作,降低維護成本,并提供可測驗的代碼基礎,通過遵循最佳實踐,我們能夠撰寫出高質量、可讀性強的代碼,從而提高開發效率和軟體質量,下面我們將一一描述函式設計時能夠遵循的最佳實踐,
2. 遵循單一職責原則
遵循單一職責原則是函式設計的重要原則之一,它要求一個函式只負責完成單一的任務或功能,而不應該承擔過多的責任,
通過遵循該原則,我們設計出來的函式將具有以下幾個優點:
- 代碼可讀性的提高:函式只關注單一的任務或功能,使其邏輯更加清晰和簡潔,這樣的函式更易于閱讀和理解,能夠更快速地理解其作用和目的,提高代碼的可讀性,
- 函式復雜度的降低:單一職責的函式具有較小的代碼量和較少的依賴關系,這使得函式的邏輯更加集中和可控,減少了函式的復雜性,在維護和修改代碼時,由于函式的功能單一,我們可以更容易地定位和修復問題,降低了維護成本,
- 代碼可測驗性的提高:遵循單一職責原則的函式更容易進行單元測驗,因為函式的功能單一,我們可以更精確地定義輸入和期望輸出,撰寫針對性的測驗用例,這有助于提高代碼的可測驗性,確保函式的正確性和穩定性,
相對的,如果函式設計時沒有遵循單一職責原則,此時將帶來函式復雜性的增加,從而導致代碼可讀性的降低以及代碼可測驗性的下降,
下面是一個沒有遵循單一職責原則的函式與一個遵循該原則的函式的對比,首先是一個未遵循該原則的代碼示例:
func processData(data []int) {
// 1. 驗證資料
// 2. 清理資料
// 3. 分析資料
// 4. 保存資料
// 5. 記錄日志
}
在上述示例中,processData 函式負責整個資料處理流程,包括驗證資料、清理資料、分析資料、保存資料和記錄日志,這個函式承擔了太多的職責,導致代碼邏輯復雜,可讀性不高,同時如果某一個節點需要變更,此時需要考慮是否對其他部分是否有影響,代碼的可維護性進一步降低,
下面我們將processData函式進行改造,使其遵循單一職責原則,從而凸顯出遵循單一職責原則的好處,代碼示例如下:
func processData(data []int) {
// 1. 驗證邏輯拆分到calidateData函式中
validateData(data)
// 2. 清理資料 拆分到cleanData函式中
cleanedData := cleanData(data)
// 3. 分析資料 拆分到 analyzeData 函式中
result := analyzeData(cleanedData)
//4. 保存資料 拆分到 saveData 函式中
saveData(result)
//5. 記錄日志 拆分到 logData 函式中
logData(result)
}
func validateData(data []int) {
// 驗證資料的邏輯
// ...
}
func cleanData(data []int) []int {
// 清理資料的邏輯
// ...
}
func analyzeData(data []int) string {
// 分析資料的邏輯
// ...
}
func saveData(result string) {
// 保存資料的邏輯
// ...
}
func logData(result string) {
// 記錄日志的邏輯
// ...
}
改造后的processData函式中,我們將不同的任務拆分到不同的函式中,每個函式只負責其中一部分功能,由于每個函式只需要專注于其中一項任務,代碼的可讀性更好,而且每個函式只負責其中一部分功能,故代碼的復雜性也明顯降低了,而且代碼也更容易測驗了,
而且由于此時每個函式只負責其中一個任務,如果其存在變更,也不會擔心影響到其他部分的內容,代碼的可維護性也更高了,
通過對比這兩個示例,我們可以很清楚得看到,遵循單一職責函式的函式,其代碼可讀性更高,復雜度更低,代碼可測驗性更強,同時也提高了代碼的可維護性,
3. 控制函式引數數量
函式在不斷進行迭代程序中,函式引數往往會不斷增多,此時我們在每次迭代程序中,都需要思考函式引數是否過多,通過避免函式引數過多,這能夠給我們一些好處:
- 首先是函式更加容易使用,過多的引數會增加函式的復雜性,使函式呼叫時的意圖不夠清晰,通過控制引數數量,可以使函式的呼叫更加簡潔和方便,
- 其次是函式的耦合度的降低: 過多的引數會增加函式與呼叫者之間的耦合度,使函式的可復用性和靈活性降低,通過封裝相關引數為物件或結構體,可以減少引數的數量,從而降低函式之間的依賴關系,提高代碼的靈活性和可維護性,
- 同時也提高了函式的擴展性,當需要對函式進行功能擴展時,過多的引數會使函式的修改變得復雜,可能需要修改大量的呼叫代碼,而通過封裝相關引數,只需修改封裝物件或結構體的定義,可以更方便地擴展函式的功能,同時對現有的呼叫代碼影響較小,
- 能夠及時識別函式是否符合單一職責原則,當函式引數過多時,同時我們又無法將其抽取為一個結構體引數,這往往意味著函式的職責不單一,從另外一個方面,迫使我們在函式還沒有堆積更多功能前,及時將其拆分為多個函式,提高代碼的可維護性,
下面,我們通過一個代碼示例,展示一個函式引數數量過多的例子和優化后的示例,首先是優化前的函式代碼示例:
func processOrder(orderID string, customerName string, customerEmail string, shippingAddress string, billingAddress string, paymentMethod string, items []string) {
// 處理訂單的邏輯
// ...
}
在這個示例中,函式 processOrder 的引數數量較多,包括訂單ID、顧客姓名、顧客郵箱、識訓地址、賬單地址、支付方式和商品串列等,呼叫該函式時,需要傳遞大量的引數,使函式呼叫變得冗長且難以閱讀,
下面,我們將processOrder的引數抽取成一個結構體,控制函式引數的數量,代碼示例如下:
type Order struct {
ID string
CustomerName string
CustomerEmail string
ShippingAddress string
BillingAddress string
PaymentMethod string
Items []string
}
func processOrder(order Order) {
// 處理訂單的邏輯
// ...
}
在優化后的示例中,我們將相關的訂單資訊封裝為一個 Order 結構體,通過將引數封裝為結構體,函式的引數數量大大減少,只需傳遞一個結構體物件即可,
這樣的設計使函式呼叫更加簡潔和易于理解,同時也提高了代碼的可讀性和可維護性,如果需要添加或修改訂單資訊的欄位,只需修改結構體定義,而不需要修改呼叫該函式的代碼,提高了代碼的擴展性和靈活性,
其次,在processOrder函式引數抽取的程序中,如果發現無法將函式引數抽取為結構體的話,也能幫助我們及時識別到函式職責不單一的問題,從而能夠及時將函式進行拆分,提高代碼的可維護性,
因此,在函式設計迭代程序中,控制函式引數過多是非常有必要的,能夠提高函式的可用性和擴展性,其次也能夠幫助我們識別函式是否滿足符合單一職責原則,也間接提高了代碼的可維護性,
4. 函式命名要準確
函式設計時,適當的函式命名是至關重要的,它能夠準確、清晰地描述函式的功能和作用,一個好的函式名能夠使代碼易于理解和使用,提高代碼的可讀性和可維護性,
相對準確的函式命名,能夠明確傳達函式的用途和功能,避免其他人對函式的誤用,同時,也提高了代碼的可讀性,其他人閱讀代碼時,能夠更加輕松得理解函式的含義和邏輯,因此,設計函式時,一個清晰準確的函式名也是至關重要的,
下面再通過一個代碼的例子,展示準確清晰的函式命名,和一個含糊不清的函式命名之間的區別:
// 不合適的函式命名示例
func F(a, b int) int {
// 函式體的邏輯
// ...
}
// 適當的函式命名示例
func Add(a, b int) int {
// 將兩個數相加
return a + b
}
在上述示例中,第一個函式命名為 F,沒有提供足夠的資訊來描述函式的功能和用途,這樣的函式命名使其他人難以理解函式的目的和作用,
而在第二個函式中,我們將函式命名為 Add,清晰地描述了函式的功能,即將兩個數相加,這樣的命名使得代碼更易于理解和使用,
因此,在函式設計中,我們需要定義一個清晰和準確的函式命名,這樣能夠提高代碼的可讀性,讓其他人更容易理解我們的意圖,
5. 控制函式長度
在函式撰寫和迭代程序中,一個超過1000行的函式,一般不是一開始實作便是如此,而是在不斷迭代程序中,不斷往其中迭代功能,才最終出現了這個大函式,由此造成的后果,各種業務邏輯在該函式中錯綜復雜,接手的同事往往難以快速理解其功能和行為,而且,在功能迭代程序中,由于各種邏輯穿插其中,此時函式將變得難以修改和維護,代碼基本不具有可讀性和可維護性,
因此,在代碼迭代程序中,時時考慮函式的長度是至關重要的,當在迭代程序中,發現函式已經過長了,此時應該盡快通過一些手段重構該函式,避免函式最終無法維護,下面是一些可能的手段:
- 確保函式只負責完成單一的任務或功能,避免函式承擔過多的責任,
- 當函式過長時,將其拆分為多個較小的函式,每個函式負責特定的功能或操作,
- 將長函式中的某些邏輯提取出來,形成獨立的輔助函式,以減少函式的長度和復雜度,
在需求迭代程序中,我們時時關注函式的長度,當長度過長時,便適當進行重構,保持代碼的可讀性和可維護性,
6. 進行防御式編程
在函式撰寫程序中,盡量考慮各種可能的錯誤和例外情況,以及相應的處理策略,這能夠帶來一些好處:
- 增強程式的健壯性: 防御式編程通過對可能的錯誤和例外情況進行處理,它可以幫助程式更好地處理無效的輸入、邊界條件和例外情況,從而提高程式的健壯性和可靠性,
- 減少程式的崩潰和故障: 通過合理的錯誤處理和例外處理機制,防御式編程可以防止程式在出現錯誤時崩潰或產生不可預測的行為,它可以使程式在遇到問題時能夠適當地處理和恢復,從而減少系統的故障和崩潰,
下面是一個對比的示例代碼,展示一個進行防御式編程的代碼和一個未進行防御式編程的代碼示例:
// 沒有防御編程的函式示例
func Divide(a, b int) int {
return a / b
}
// 有防御編程的函式示例
func SafeDivide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
在上述示例中,第一個函式 Divide 沒有進行錯誤處理,如果除數 b 為零,會導致運行時發生除以零的錯誤,可能導致程式例外終止,而第二個函式 SafeDivide 在執行除法之前,先進行了錯誤檢查,如果除數 b 為零,則回傳一個自定義的錯誤,避免了程式崩潰,
因此,我們在函式撰寫程序中,盡量考慮各種可能的錯誤和例外情況,對其進行處理,保證函式的健壯性,
7. 總結
在這篇文章中,我們總結了幾個函式設計的最佳實踐,如遵循單一職責原則,控制函式引數數量,函式命名要清晰準確等,通過遵循這些原則,能夠讓我們設計出來高質量、可讀性強的代碼,同時也具有更強的可維護性,
但是也需要注意的是,函式一開始設計時總是相對比較完美的,只是在不斷迭代中,不斷堆積代碼,最終代碼冗長,復雜,各種邏輯穿插其中,使得維護起來越發困難,因此,我們更多的應該是在迭代程序中,多考慮函式設計是否違反了我們這里提出的原則,能在一開始就識別到代碼的壞味道,從而避免最終演變成難以維護和迭代的函式,
基于此,我們完成了對函式設計最佳實踐的介紹,希望對你有所幫助,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/555821.html
標籤:其他
上一篇:一文了解函式設計的最佳實踐
下一篇:返回列表
