清晰架構(Clean Architecture)的Go微服務: 編碼風格
編碼風格在程式設計中是一個相對乏味的主題,但是合適的編碼風格對一個有效的程式設計師是至關重要的。 它有三個組成部分:
程式結構 ( application layout)
編碼規則或風格
命名約定
我已經在清晰架構(Clean Architecture)的Go微服務: 程式結構¹中討論了程式結構,因此本文將介紹後兩點。
編碼規則或風格
沒有包級別(package level)變數。
包級別變數打破了函式封裝並使函式有了不確定。我在本程式中遵循了這個規則,唯一的例外是在“容器”包中,因為它負責程式級配置,在這裡很難做到。其實即使在“容器”包中也可以去除掉包級別變數,但它需要付出很多努力,有些得不償失。但是,由於包級別變數僅限於“容器”包中,因此導致的破壞大大減少。
儘量少使用常量
常量也是包級別,因為具有不變性,它們作為全域性變數的危害較小,但它們仍然會破壞函式封裝並需要進行限制。
依賴於介面而不是具體型別
當你嘗試最小化外部更改對程式的影響時,無論外來者是函式,包還是應用程式,都要使程式碼依賴於介面而不是具體型別。
命名規則
最佳的程式碼是自我解釋的,良好的命名起著至關重要的作用。Go的命名約定與Java非常矛盾。嘗試之後,我發現它是有效的,在建立簡潔的程式碼同時並沒有降低可讀性。 Go是一種相對與更底層接近的語言,人們用它來編寫網路,驅動程式和docker容器程式碼。在這些環境中,使用簡潔的名稱是合適的。
但是,在編寫業務應用程式時,我們需要建立許多不同的型別或結構來處理相似的業務概念,簡潔的命名便不再適用。例如,為了處理與“使用者”相關概念,我們有“User”,“UserDataService”,“RegistrationUseCase”,“RegistrationUseCaseIterface”和“UserDataInterface”,它們都與“User”有關,但都完全不同。你確實需要一個相對較長的名字來區分它們。為了獲得良好的可讀性,我有意違反了Go的一些命名約定,我將逐一解釋它們。
我遵循的一條規則是“變數宣告(name declaration)與其使用之間的距離越大,名稱應該越長”Andrew Gerrand². 我從Dave Cheney³的一篇文章中學到了這一點。基於它,我建立了自己的命名規則:“為型別(結構,介面)或函式命名使用長名稱使其清晰易讀,為區域性變數命名使用短名稱”,因為區域性變數宣告和使用之間的距離較短。
給型別(types)命名:
當我看到一個名字時,重要的是要了解它是哪種型別,它處於哪個層。例如,“UserDataInterface”,告訴我它是域模型“User”,它提供資料服務(永續性),並且它是一個介面。
Model:
域模型層,是最容易提供名稱的層。 例如,域模型使用者的“User”。
Dataservice:
資料永續性服務層。 例如“UserDataMySql”作為使用者永續性服務MySQL資料庫的命名; “UserDataCouchdb”作為CouchDB資料庫的使用者永續性服務的命名。 使用者資料服務的不同實現共享相同的字首“UserData”,並且它們的介面是“UserDataInterface”。
“CourseDataInterface”是課程資料服務的介面,“CourseDataMySql”是具體課程資料服務MSql資料庫實現的名稱。 所有資料服務共享相同的字首“[model]Data”。
Use Case:
用例層。 “RegistrationUseCase”是註冊用例的具體型別,介面是“RegistrationUseCaseInterface”。 “ListCourseUseCase”是課程列表用例的具體型別。 所有用例共享相同的字尾“UseCase”
Interface:
在清晰架構(Clean Architecture)中,所有業務邏輯都是基於介面呼叫的。 在遇到型別時,重要的是要識別型別是介面還是具體實現,因此我在所有介面上新增字尾“Interface”,例如“UseCaseInterface”。 如果你認為它太長,你可以用“I”,“If”或“Intf”等縮略語替換“Interface”。 對我來說,打字不是一個問題,IDE在大多數時間裡都已經把問題解決了。
Constants:
對於常量,重要的是將它們與變數區分開來,所以我使用全部大寫,如果是多欄位常量(multi-words constant),我使用蛇形命名法(Snake Case)而不是駝峰命名法(Camel Case)(我知道它與Go命名約定相左)。 例如,“QUERY_USER_BY_NAME”而不是“queryUserByName”,這使它易讀性更好。
結論:
編碼風格對於使程式設計效率至關重要。 良好的命名使程式碼自我解釋。 對型別或函式使用長名稱,對區域性變數使用短名稱。 將“interface”放在介面名稱中是有幫助的。 用蛇形命名法(Snake Case)並且所有字母大寫來命名常量,以便於識別。
源程式:
完整的源程式連結 github: https://github.com/jfeng45/servicetmpl
索引:
[1][Go Microservice with Clean Architecture: Application Layout](https://jfeng45.github.io/posts/go_microservice_application_layout/)
[2][What’s in a name?](https://talks.golang.org/2014/names.slide#4)
[3][Practical Go: Real world advice for writing maintainable Go programs](https://dave.cheney.net/practical-go/presentations/qcon-china.ht