閒談軟體設計:友善的距離

更新於 發佈於 閱讀時間約 10 分鐘
https://en.pandaid.jp/hygiene/social-distancing

https://en.pandaid.jp/hygiene/social-distancing

心態轉變

因為組織調整,我又變成 architect 了 (2016 ~ 2017),早期對 architect 有很多憧憬,面對問題,心中總有一張設計圖,但實際當 architect 後,發現這是一個很不容易的職位,特別是要能說服別人使用某個設計或不用某個設計,另外,是在有時程壓力下,對一個有點歪的架構上,如何微妙地讓它保持像比薩斜塔般不至於垮掉,還能持續成長,又是另一個難題。

在讀《建構微服務》有段內容讓我玩味很久:

我們借用其他行業的名稱,稱自己為 software engineer 或 architect,但我們不是,對吧?architect 與 engineer 的嚴謹性和紀律性是我們在現實中無法冀求的,而且他們對社會的重要性是眾所周知的。記得有一次我和一位朋友聊天,就在他成為合格 architect 的前一天,他說:『明天,如果我在酒吧裡建議你如何建立某個軟體,而它是錯誤的,我就得承擔責任,我可能會被告,因為,我在法律上是一個合格的 architect,如果我犯錯,就必須負責』

怎樣才是稱職的軟體架構師呢?老實說,我恐怕還沒那資格說。

設計抉擇

軟體架構有很多 pattern,坊間也有很多書探討,像是《Pattern-Oriented Software Architecture》系列、《Software Architecture in Practice》、《Patterns of Enterprise Application Architecture》,但當我們套了這些 pattern 時,我們真的該用嗎?用對了嗎?還是只有有個殼而已,內容根本不是那麼一回事呢?

若深究每個 pattern 的形式,都會找到情境 (context)、遭遇的問題 (forces) 和對應的平衡解 (solution),但再仔細想想,這些解都圍繞在 separation of concerns 上,將不同的問題分離,以合適的方式處理,因為我們總是希望有個 high cohesion 及 loose coupling 的系統,但在面對實際的設計抉擇時,有時反而會做出違背上述兩原則的選擇,因為每個專案要考慮的因素都不同 (參考閒談軟體設計:設計抉擇的因素)。

例如,為了不重新造輪子,我們使用第三方的函式庫,這聽起來很合理,但我們真的了解我們引入的函式庫嗎?我們對該函式庫的掌握度有多高呢?每個被引入的函式庫意味著一種 coupling,不論是在 Java 上使用 Maven 或 Gradle 或是在 Objective C 中使用 CocoaPods,在編譯時,會看到這些套件管理工具幫我們下載眾多的第三方函式庫,這意味著我們不用重寫這些東西,開發效率能提升數倍甚至數百倍,但我們真的都能掌握這些 coupling 嗎?當這其中任何一個環節出錯,我們的系統架構真的很優雅地應付嗎?

這是為什麼我蠻喜歡 Onion Architecture (洋蔥架構) 和 Hexagonal Architecture (六角架構) 的原因了,在過去的專案中,我並沒有刻意使用這二個架構,畢竟我進去時,早已有龐大的程式碼基礎,不可能說改就改,只有在 Android 專案起始時,因為我是較資深的軟體工程師 (那時還不是架構師),主導整個架構走向 (參考閒談軟體設計:Android App Architecture),即便如此,我也只堅持 domain 要與 Android SDK 分離,維持使用而不相依的關係,而這也造就了後來開發PC 版時,有完整的 domain 核心可以直接使用不需修改,雖然這也不在當初的規劃就是了,但也省去了大量重複開發的時間。當要離職準備交接時,回頭檢視架構,上述二個架構的影子就穿插在程式碼之間了。

抽象滲漏

可能跟過去在學校的 OOAD 訓練養成有關,從 problem context 和 use case 中提取名詞,接著提取動詞找出關係與函式,然後利用 GRASP 逐步建構出整個 domain model 與 design model,這中間,完全沒思考過 UI (但如何與系統互動很重要) 與框架,可能是這樣,我後來在做設計時,很自然地就與框架保持距離,不論是用 C# 寫 Windows Forms 還是用 Java 寫 Web applications 。但這其實並不容易,我剛開始工作時,看《Hibernate in Action》時,有幾句話讓我印象深刻,第一句是:

We use transparent to mean a complete separation of concerns between the persistent classes of the domain model and the persistence logic itself, where the persistent classes are unaware of — and have no dependency to — the persistence mechanism.

其實,我畢業前就自學了 JPA,之後才學 Hibernate,上面那句話讓我開始思考,雖說 JPA 的 annotation 很方便,但不正是破壞了 transparency 嗎?但 Hibernate 也無法達到完全的 transparent:

We regard transparency as required. In fact, transparent persistence should be one of the primary goals of any ORM solution. However, no automated persistence solution is completely transparent: Every automated persistence layer, including Hibernate, imposes some requirements on the persistent classes.

因為再怎麼樣抽象化,資料庫對資料的描述與物件導向語言對物件的描述,存在著無法消除的 paradigm mismatch (有興趣可以找書來看,《Hibernate in Action》在第一章的第二節,花了整整一節說明這不匹配的情況),這讓我想起《約耳趣談軟體》第 26 章抽象滲漏法則中的一段:

所有重大的抽象機制在某種程式上都是有漏洞的。

而且有時候這些框架或工具會反過來影響 domain model 的設計,舉例來說,從 OO 設計的角度來看關係,若要好維護,一般會以單向關係 (unidirectional reference) 為主,但若要使用 ORM 或 CoreData 工具,為了確保工具能檢查資料完整性,會反過來在 domain model 上加上雙向關係 (bidirectional reference),但程式碼卻不見得需要去維護這雙向關係 (部分是 ORM 工具處理),這導致讀程式碼時會有點奇怪。

另一個例子是,過去在學 RDBMS 時,會學到正規化 (normalization)、主鍵 (primary key)、外來鍵 (foreign key)、索引 (index) 及一些 RDBMS 能在資料完整性幫上忙的工具,像是 cascade delete 等東西,因此 ORM 工具也常把這些資訊滲漏出來,滲漏也許還好,但把物件關聯的維護轉交給 ORM 工具上 (依賴 cascade delete 刪除不該存在的關聯),就是值得討論的設計,到底這物件間關聯的維護是商業邏輯層的責任,還是資料儲存層的責任?如果哪天,資料儲存層換了,偏偏不支援原有的 metadata (例如不支援 JPA 的annotation),那物件關聯的維護該怎麼處理?

保持友善的距離

所以重點是如何取得平衡?以上述的 ORM 工具來說,極端的兩邊:完全不使用 ORM 工具和毫不顧忌的讓 ORM 工具散布在 domain model 中,又或者是將 ORM 的滲透透過其他方式控管在特定的範圍中,例如:再建立一層抽象 (DAO 或 Repository),在實作中建立 ORM 所需的 data model;又或者是使用污染性較低的方案,例如:以傳統的 XML 取代 JPA annotation 描述 metadata,事實上,這沒有標準答案,每個軟體架構師的選擇都不同,上述二兩種折衷方案我都用過,也曾經完全不使用 ORM 工具,完全視情境而定,但原則到沒什麼不同,與框架及工具保持友善的距離,這同樣影響我之後在開發 iOS 時使用 CoreData 的方式。

或許是這樣,感覺自己比較像是 old-school 的軟體架構師,在選擇第三方函式庫或是框架時,相對比較保守,有時,還會為組織內部重新打造輪子,像是曾經在 Android 專案中復刻 iOS SDK 的 NSNotificationCenter,事實上,在 GitHub 上可以找到類似的第三方函式庫,像是 EventBus,但要不要採用一個第三方函式庫,除了該函式庫穩不穩定、文件夠不夠充足,還要看是否符合專案與組織的特性。

就專案來說,Android 專案分成兩個子專案:一個是只有 domain model 的 pure Java 專案,另一個是實際 Android UI 的專案,若要導入 EventBus,就只能在 Android UI 的專案中使用,因為 domain model 沒有相依 EventBus 所需的 Android SDK,這樣並無法滿足當初想用 NotificationCenter 減少 model 與 UI 之間 coupling 的初衷;另外,考量到希望 iOS developer 也能協助開發 Android,所當時以決定自己動手寫,並且在進行在復刻時,API 命名特意維持與 NSNotificationCenter 相似。

心得

因此,軟體架構不是一旦決定了就穩固了,它需要後續開發時,時時想著當下這個設計是否會把架構搞歪了,架構需要細心的照顧,否則很容易歪掉,歪掉也許沒事,程式也可能還能繼續正常執行,但埋伏在裡面的技術債,何時會引爆則是未知,一旦反撲,對專案的影響不僅是時程,還有開發團隊對整體架構的信心。

avatar-img
53會員
104內容數
這是從 Medium 開始的一個專題,主要是想用輕鬆閒談的方式,分享這幾年軟體開發的心得,原本比較侷限於軟體架構,但這幾年的文章不僅限於架構,也聊不少流程相關的心得,所以趁換平台,順勢換成閒談軟體設計。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
Spirit的沙龍 的其他內容
Offline first 的設計最近有越來越多的感覺,但好的 Offline first 設計要解決蠻多的問題,是否使用 offline first 設計真的需要好好思考,不然可能得不到好處,反而還引起一堆 bug,本篇先探討在 client 端可能會遇到的問題與一些可能的解法。
會後,我與其中一位創辦人聊聊他們 scrum 怎麼跑,以及程式、美術與企劃,這三種技能差異甚大的成員怎麼合作,他也苦笑,其實他們也花了很多時間磨合,但我們都提到,要引導團隊需要的不是 process,而是很多的軟技能,讓團隊自己能夠成長。
老實說,從中文書名無法聯想回原文書是《The Elements of Scrum》,雖然書名翻譯沒有太離譜(和內容無關之類的),但總覺得貼近原意會好一點。『Scrum團隊週記』這一章,整個讀完,其實就差不多可以了解Scrum的大部分,所以,若要讀這本書,又沒有太多時間,就先看這一章吧!
Immutable interface 讓封裝更有彈性,不用擔心 setter 的過度開放。當不希望物件被不允許的對象修改時,只需讓對方取得 getter 的介面即可,反之,讓能夠允許修改的對象取得有 setter 的物件即可。
創業團隊就會很在意story的內容,會有相當多的意見,refinement meeting就是一個很好的場合讓大家把對需求的想法提出來,否則讓成員失去參與感,這對創業團隊是很大的傷害。
書中畫了滿滿的筆記,不過我想每個人看這本書的想法應該都不會一樣,像是創業不一定需要創投,創投的目標是將獲得的股票回收最大化,和您的創業目標不見得一致,沒有創投可以取得最大的自主權。用既有的利潤來推動成長,而不是預期的利潤來推動成長。
Offline first 的設計最近有越來越多的感覺,但好的 Offline first 設計要解決蠻多的問題,是否使用 offline first 設計真的需要好好思考,不然可能得不到好處,反而還引起一堆 bug,本篇先探討在 client 端可能會遇到的問題與一些可能的解法。
會後,我與其中一位創辦人聊聊他們 scrum 怎麼跑,以及程式、美術與企劃,這三種技能差異甚大的成員怎麼合作,他也苦笑,其實他們也花了很多時間磨合,但我們都提到,要引導團隊需要的不是 process,而是很多的軟技能,讓團隊自己能夠成長。
老實說,從中文書名無法聯想回原文書是《The Elements of Scrum》,雖然書名翻譯沒有太離譜(和內容無關之類的),但總覺得貼近原意會好一點。『Scrum團隊週記』這一章,整個讀完,其實就差不多可以了解Scrum的大部分,所以,若要讀這本書,又沒有太多時間,就先看這一章吧!
Immutable interface 讓封裝更有彈性,不用擔心 setter 的過度開放。當不希望物件被不允許的對象修改時,只需讓對方取得 getter 的介面即可,反之,讓能夠允許修改的對象取得有 setter 的物件即可。
創業團隊就會很在意story的內容,會有相當多的意見,refinement meeting就是一個很好的場合讓大家把對需求的想法提出來,否則讓成員失去參與感,這對創業團隊是很大的傷害。
書中畫了滿滿的筆記,不過我想每個人看這本書的想法應該都不會一樣,像是創業不一定需要創投,創投的目標是將獲得的股票回收最大化,和您的創業目標不見得一致,沒有創投可以取得最大的自主權。用既有的利潤來推動成長,而不是預期的利潤來推動成長。
你可能也想看
Google News 追蹤
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
這篇文章探討了工程師在如何有效提升自己,強調不僅僅是多coding,而是要對程式碼有更深層的理解。隨著職涯發展,工程師需要從單純的技術執行者轉變為團隊領導者,具備解決複雜問題和與他人有效溝通的能力。
Thumbnail
「所以,你想要用A框架,但又覺得B框架也不錯?」David挑眉問道,一臉的疑惑和一絲不易察覺的笑意。 .... David神秘地笑了笑,「技術選擇可不是簡單的喜好問題,它牽扯到技術轉移的成本、技術負債的累積,還有整個團隊的長期發展。先來聽聽我的想法吧。」
※ 工廠模式 定義: 工廠模式是一種實現了「工廠」概念的物件導向設計模式。它提供一個通用的工廠介面,將創建instance(實例)的程式碼交由子類別各自實現,並根據需求去動態地生成相應的物件。這種模式將物件的創建邏輯與使用邏輯分開,使程式碼更容易維護和擴展。 特點: 具有高度標準化和同質性的
架构师(Architect)在不同领域有不同的职位需求。这里主要讨论的是软件架构师(Software Architect)的职位需求,包括以下几个方面: 1. 专业技能 编程语言:熟悉多种编程语言,如Java、C#、Python、JavaScript等。 框架和工具:熟悉常用的开发框架(如Sp
Thumbnail
追求乾淨的程式碼是好的開始,但不要陷入過度設計的陷阱,導致程式難以維護。實際上,考慮團隊狀況和專注於解決真正的問題更為重要。了解公司的規模和現實情況,適時調整工作重心。技術不斷進步,使得寫程式變得更加容易,但這並不意味著工程師的角色會消失。在選擇技術時,也要考慮隱形成本有時簡單的解決方案反而更有效。
Thumbnail
資料的統合 在程式設計中,其他人通常關心是否注意到執行的細節。作為程式設計師,主要應該關心的是程式的表現,但往往忽略了很多細節,這些細節可以決定程式的好壞。程式的好壞很大程度上取決於資料的統合,也就是資料是否被正規化。 不同類型的資料在系統中呈現一致 正規化可能對一些人來說聽起來很抽象,有些人
Thumbnail
系統的分析與規劃 在談到程式設計時,首要的是進行系統的分析與規劃。程式設計的起點通常是系統分析與規劃,這涉及到如何分析和設計系統的大原則和方向。為了達到預期效果,重要的是擁有對產業的清晰邏輯認識和深入了解。 進行深入了解 若要進行系統分析,必須對企業的設計和程式設計的對象進行深入了解,以充分理
這個系列的文章主要專注於物件導向到函數式編程的差異與分析,並針對概念與機制上的不同進行比較。很多人說物件導向和函數式編程沒有哪個比較好的問題,只有哪個比較適合的問題,然而我並不這麼認為,我透過這一系列的文章從各個角度討論它們之間的優缺點就是為了闡述我的觀點。物件導向錯在沒有理論基礎,但它贏在熟悉性,
讓我在這篇文章總結一下前面對物件導向設計的討論,我們討論了物件導向的四個特性:繼承、抽象、多型、封裝,分析了它們的問題,並跟函數式編程的思維做比較。我們引入了與之相對應的特性:泛型、特性系統、模組化,有些特性雖然跟那四個特性很像,但在一些細微的地方有不同的詮釋,使得整體思考方式很不一樣。 「繼
物件導向設計的一個重點就是封裝,這有很多層面上的意義,但基本上就是控制物件的成員變數和方法的存取權。物件導向的封裝還跟繼承機制有關,這使得有一些時候我們逼不得已必須把函式定義在類別上,這種做法使得物件的功能變得難以拆解。封裝應該是模組的職責,並不需要再給物件相同的能力。 一般的模組系統就是把相
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
這篇文章探討了工程師在如何有效提升自己,強調不僅僅是多coding,而是要對程式碼有更深層的理解。隨著職涯發展,工程師需要從單純的技術執行者轉變為團隊領導者,具備解決複雜問題和與他人有效溝通的能力。
Thumbnail
「所以,你想要用A框架,但又覺得B框架也不錯?」David挑眉問道,一臉的疑惑和一絲不易察覺的笑意。 .... David神秘地笑了笑,「技術選擇可不是簡單的喜好問題,它牽扯到技術轉移的成本、技術負債的累積,還有整個團隊的長期發展。先來聽聽我的想法吧。」
※ 工廠模式 定義: 工廠模式是一種實現了「工廠」概念的物件導向設計模式。它提供一個通用的工廠介面,將創建instance(實例)的程式碼交由子類別各自實現,並根據需求去動態地生成相應的物件。這種模式將物件的創建邏輯與使用邏輯分開,使程式碼更容易維護和擴展。 特點: 具有高度標準化和同質性的
架构师(Architect)在不同领域有不同的职位需求。这里主要讨论的是软件架构师(Software Architect)的职位需求,包括以下几个方面: 1. 专业技能 编程语言:熟悉多种编程语言,如Java、C#、Python、JavaScript等。 框架和工具:熟悉常用的开发框架(如Sp
Thumbnail
追求乾淨的程式碼是好的開始,但不要陷入過度設計的陷阱,導致程式難以維護。實際上,考慮團隊狀況和專注於解決真正的問題更為重要。了解公司的規模和現實情況,適時調整工作重心。技術不斷進步,使得寫程式變得更加容易,但這並不意味著工程師的角色會消失。在選擇技術時,也要考慮隱形成本有時簡單的解決方案反而更有效。
Thumbnail
資料的統合 在程式設計中,其他人通常關心是否注意到執行的細節。作為程式設計師,主要應該關心的是程式的表現,但往往忽略了很多細節,這些細節可以決定程式的好壞。程式的好壞很大程度上取決於資料的統合,也就是資料是否被正規化。 不同類型的資料在系統中呈現一致 正規化可能對一些人來說聽起來很抽象,有些人
Thumbnail
系統的分析與規劃 在談到程式設計時,首要的是進行系統的分析與規劃。程式設計的起點通常是系統分析與規劃,這涉及到如何分析和設計系統的大原則和方向。為了達到預期效果,重要的是擁有對產業的清晰邏輯認識和深入了解。 進行深入了解 若要進行系統分析,必須對企業的設計和程式設計的對象進行深入了解,以充分理
這個系列的文章主要專注於物件導向到函數式編程的差異與分析,並針對概念與機制上的不同進行比較。很多人說物件導向和函數式編程沒有哪個比較好的問題,只有哪個比較適合的問題,然而我並不這麼認為,我透過這一系列的文章從各個角度討論它們之間的優缺點就是為了闡述我的觀點。物件導向錯在沒有理論基礎,但它贏在熟悉性,
讓我在這篇文章總結一下前面對物件導向設計的討論,我們討論了物件導向的四個特性:繼承、抽象、多型、封裝,分析了它們的問題,並跟函數式編程的思維做比較。我們引入了與之相對應的特性:泛型、特性系統、模組化,有些特性雖然跟那四個特性很像,但在一些細微的地方有不同的詮釋,使得整體思考方式很不一樣。 「繼
物件導向設計的一個重點就是封裝,這有很多層面上的意義,但基本上就是控制物件的成員變數和方法的存取權。物件導向的封裝還跟繼承機制有關,這使得有一些時候我們逼不得已必須把函式定義在類別上,這種做法使得物件的功能變得難以拆解。封裝應該是模組的職責,並不需要再給物件相同的能力。 一般的模組系統就是把相