[IT] Clean Architecture - 第4章 - 結構化程式設計

ch4. 結構化程式設計 Edsger Wybe Dijkstra 於 1930 年出生在鹿特丹。他在第二次世界大戰期間倖存於鹿特丹的轟炸,以及德國對荷蘭的占領,並於1948年以數學、物理、化學、生物最高分從高中畢業。1952年3月,21歲的 Dijkstra 在阿姆斯特丹的數學中心找到工作,成為荷蘭第一位程式設計師。 1955年,已經當了三年的程式設計師,且同時身為學生的 Dijkstra 得出一個結論:程式設計的智力挑戰比起理論物理學還大。因此,他選擇了程式設計作為他的長期職業。 1957年,Dijkstra 與 Maria Debets 結婚。當時,在荷蘭結婚必須登記職業,荷蘭的公家機關並不接受 programmer 這個職業,他們從未聽過這樣的職業,為了滿足他們,Dijkstra 在職業欄中將自己定位為「理論物理學家」。 Dijkstra 與他的老闆 Adriaan van Wijngaarden 討論著以程式設計當作他的生涯志向,他認為沒有人會將程式設計視為一門學科或是科學,因此他認為自己可能不會被認真以待,然而他的老闆回答說,Dijkstra 將有可能成為那位發現新學科,以致於將軟體變成一門科學的人。 Dijkstra 在真空管時代開始了他的職業生涯,當時的計算機很巨大、脆弱、緩慢、不可靠、且極其有限。在早期,程式是二進制或是非常粗糙的組合語言編寫的,並以紙帶或打孔卡片作為輸入的這種物理形式存在,編輯/編譯/測試的循環就需要數小時甚至數天的時間。 正是在這個原始的環境造就了 Dijkstra 做出了他偉的的發現。 證明 Dijkstra 很早就發現,程式設計是一件難事,且程式設計師也不容易將它做好。任何複雜的程式都包含了太多人類大腦在沒有幫助下可以管理的細節。忽視一個極小的細節程式看似可以運作正常,但卻可以以出人意料的方式失敗。 Dijkstra 的解決方案是應用數學的證明法。他的願景是建立一個如同歐基里得的公理、定理、推論和引理的層次結構。Dijkstra 認為程式設計師可以像數學家一樣使用這樣的證明方法,換句話說,程式設計師應該要運用這些經過驗證的結構,並將它們與自己證明正確的程式碼相結合。 當然,為了使這一切開始進行,Dijkstra 意識到他必須要撰寫一些範例以展示如何用基本的證明方法來證明簡單的演算法,而他發現這是一件極具挑戰性的事。 在調查的過程中,Dijkstra 發現某些使用 goto 語句的情況會阻止模塊被遞迴地分解成更小的單元,從而阻礙了使用分而治之的方法進行證明的可能性。 然而,goto 的其他用途卻沒有這個問題。Dijkstra 意識到,「好的」goto 使用方法對應到簡單的選擇和迭代控制結構,例如 if/then/else 和 do/while。只使用這些控制結構的模塊才可以被遞迴的切割成可以被證明的單元。 Dijkstra 認識到,當這些控制結構與順序執行相結合是特殊的。在當時的兩年前,已經由 Böhm 和 Jacopini 證明了,所有程式都可以由三個結構建構而成:順序(sequence)、選擇(selection)、迭代(iteration)。 這個發現非常了不起:使一個模塊可證明的控制結構,正是構成所有程式的最小控制結構集合。因此,結構化程式設計應運而生。 Dijkstra 證明了順序語句可以通過簡單的列舉來證明其正確性。這種技術利用數學方法系統性地追蹤語句的輸入與輸出,與一般的數學證明無異。 Dijkstra 通過重新應用列舉法來解決選擇問題,對於選擇中的每條路徑都進行了列舉,如果所有的路徑都產生了適當的數學結果,那麼就表示證明是可靠的。 迭代則有些不同,為了證明迭代的正確性,Dijkstra 必須使用歸納法。他通過列舉證明了 1 得情況,然後他再次通過列舉證明了如果假設 N 是正確的,則 N+1 也是正確的,他也證明了迭代的起始條件與結束條件的正確性。...

<span title='2023-10-03 22:28:44 +0800 +0800'>October 3, 2023</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;Rain Hu

[IT] Clean Architecture

乾淨架構 設計(design)與架構(architecture)為何重要? 軟體架構的目標是將開發與維護軟體系統所需的人力最小化。 不好的設計會使維護的成本愈來愈高。 每次版本的發布時的生產力。 良好的開發模式(TDD)大幅減少開發時間。 行為(behavior)與架構(architecture) 行為:緊迫但並非特別重要 架構:重要不緊迫 ∵ 緊急且重要 > 不緊急但重要 > 緊急但不重要 > 不緊急且不重要 ∴ 大多情況下,架構(設計)比行為(開發)更重要。 程式設計範式(paradigms) 結構化程式設計(structed programming) 不要使用 goto,使用結構化的設計模式。(順序、選擇、迭代) 總結:對直接控制權施加限制。 關注點:功能 物件導向程式設計(object-oriented programming) 使用多型來避免函數指針的濫用。 總結:對間接控制權施加限制。 關注點:組件分離 函式程式設計(functional programming) λ演算的概念是不可變性,符號的值不會改變,意味著沒有賦值。 總結:對賦值施加限制。 關注點:數據管理 物件導向設計: 依賴反轉: 商業邏輯不依賴於 UI 與 DB,UI 與 DB 可以做為商業邏輯的插件。 小結: 三種範式都在約束你寫 code 的某些行為。這些約束就是在制定規則。 SOLID 設計原則 SRP: 單一職責原則(The Single Responsibility Principle) 一個模組只有一個原因(用戶/利益相關者)需要改變。 OCP: 開放封閉原則(The Open-Closed Principle) 軟體工程應對擴展開放,但對修改封閉。 LSP: 里氏替體原則(The Liskov Substitution Principle) 避免簡單的可替代性違規導致大量的額外機制。 ISP: 介面隔離原則(The Interface Segregation Principle) 關注點分離。將一個多功能的物件拆成繼承三個不同功能介面的物件。 DIP: 依賴反轉原則(The Dependency Inversion Principle) 組件原則 組件是部署的單位,他們是系統的最小單元。 在 Java,他們是 jar 檔。 在 Ruby,他們是 gem 檔。 在 ....

<span title='2023-09-29 02:03:47 +0800 +0800'>September 29, 2023</span>&nbsp;·&nbsp;2 min&nbsp;·&nbsp;Rain Hu

[IT] Clean Architecture - 第3章 - 程式設計範式總覽

ch3. 程式設計範式總覽 這個概述章節中包含的三種範式(paradigm)是結構化編程、物件導向編程和函數式編程。 結構化程式設計(Structured Programming) 第一個被採用的範式(但不是第一個發明的)是結構化程式設計,由艾德斯格·韋伯·迪科斯特拉(Edsger Wybe Dijkstra)在1968年發現。迪科斯特拉指出,無節制的跳躍(goto語句)對程式結構是有害的。正如我們在接下來的章節中將看到的那樣,他用更為熟悉的if/then/else和do/while/until結構取代了這些跳躍。 一句話總結結構化程式設計: 結構化程式設計對直接控制權的轉移施加限制 Structured programming imposes discipline on direct transfer of control. 物件導向程式設計(Object-Oriented Programming) 第二個採用的範式實際上是在1966年早兩年被奧利·約翰·達爾(Ole Johan Dahl)和克利斯登·奈加特(Kristen Nygaard)發現的。這兩位程式設計師注意到,在ALGOL語言中,函數呼叫的 stack frame 可以移動到 heap,從而使函數聲明的區域變數在函數返回後仍然存在。該函數成為一個類的構造函數,區域變數成為實例變數,嵌套函數則成為方法。這不可避免地導致了多態的發明,用以限制函數指針的使用。 一句話總結物件導向程式設計: 物件導向程式設計對間接控制權的轉移施加限制 Object-oriented programming imposes discipline on indirect transfer of control. 函數式程式設計 第三種範式,最近才開始被採用,卻是最早被發明的。事實上,它的發明早於程式設計本身。函數式程式設計是阿隆佐·邱奇(Alonzo Church)的工作的直接產物,他在1936年時發明了λ演算法(l-calculus),當時圖靈也在研究同樣的問題。他的λ演算法是基於9158年由約翰·麥卡錫(John McCarthy)發明的LISP語言,λ演算法有一個最基本的概念是不可變性(immutability),也就是說,變數的值不會改變。這意味著函數式程式設計並不會有賦值的敘述。事實上,大多數的函數式程式語言,有自己的方法去改變變數的值,但只有在非常嚴格的限制下可以使用。 一句話總結函數式程式設計: 函數式程式設計對賦值施加限制 Functional programming imposes discipline upon assignment 討論 注意到本章所介紹到的三個範式,都是在限制程式設計師的能力,而非增加新的能力。每個範式都在告訴我們什麼不應該做,而不是應該做什麼。 從另一角度來看,從結構化程式設計消除了go to語句,從物件導向程式設計消除了function pointers,從函數式程式設計消除了assignment。我們還有什麼可以消除的呢? 答案很可能是沒有。因此這三種範式很有可能是唯一的三種,至少是唯三限制型的範式,另一個證據是,在爾後的數十年間,也沒有再出現任何的範式。 結論 從範式的歷史,我們可以怎麼與架構做聯想呢? 1. 我們利用多型的機制來跨越架構的邊界。 2. 我們利用函數式程式設計來約束對數據的位置與訪問權限。 3. 我們利用結構化程式設計作為模塊的演算法基礎。 注意這三個與建築的三個重要關注點不謀而合:功能、組件分離、數據管理。

<span title='2023-09-07 22:07:54 +0800 +0800'>September 7, 2023</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;Rain Hu

[IT] Clean Architecture - 第二部分 從基礎構件開始: 程式設計範式(Paradigms)

軟體架構始於程式碼,因此我們將從程式碼的角度開始討論架構,看看自從程式碼被寫下以來我們所學到的內容。 1938年,艾倫·圖靈(Alan Turing)奠定了計算機編程的基礎。他並不是第一個構想可編程機器的人,但他是第一個理解程式即數據(programs are simply data)的人。到了1945年,圖靈已經在真正的電腦上用我們現在能夠認出的程式碼編寫真正的程式了。這些程式使用了循環(loops)、分支(branches)、賦值(assignment)、子程序(subroutines)、堆棧(stacks)和其他熟悉的結構。但,圖靈的語言是二進制的。 自從那些日子以來,程式設計界發生了許多革命。其中一個我們都非常熟悉的革命就是語言的革命。首先,在1940年代末期,出現了組合語言(assemblers)。這些「語言」解放了程式設計師將他們的程式轉換成二進制的苦差。1951年,格雷斯·霍珀(Grace Hopper)發明了第一個編譯器 A0。事實上,她創造了「編譯器(compiler)」這個詞彙。Fortran 在1953年被發明出來。接著,一股源源不斷的新程式語言湧入 - COBOL、PL/1、SNOBOL、C、Pascal、C++、Java等等,無窮無盡。 另一個可能更重要的革命是在程式設計範式方面。範式是編程的方式,與語言相對無關。範式指導了開發人員應該使用哪些程式結構,以及何時使用它們。 迄今為止,已經有三種這樣的範式,也不太可能再有其它的範式,原因後述。

<span title='2023-09-05 21:43:11 +0800 +0800'>September 5, 2023</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;Rain Hu

[IT] Clean Architecture - 第2章 - 兩個價值維度

Ch2. 兩個價值維度 每個軟體系統都為利益相關者(stakeholders)提供兩種不同的價值維度:行為(behavior)與結構(structure)。軟體開發人員負責確保這兩種價值保持高水準。不幸的是,他們往往只關注其中一個而忽略另一個,更不幸的是,他們往往只關注較低價值的那一個,最終使軟體失去價值。 行為 歉體的第一個價值來自於行為。程式設計師被聘請來使機器以一種能為利益相關者帶來獲利或節省成本的方式運作。我們透過協助利益相關者制定功能規格或者需求文件,然後編寫程式碼,使利益相關者的機器滿足這些需求。當機器違反這些要求時,程式設計師便開始除錯以修復這些問題。 結構 軟體的第二個價值來自於結構,軟體之所以為「軟」體,是因為它被創造出來是為了方便改變機器的行為。為了實現其目的,軟體必須要有足夠的彈性,易於修改。當利益相關者對某項功能改變主意時,這種改變應該要是可以簡單且容易進行的。進行這種改變的困難程度應該只與改變的範圍成比例,而不是與改變的形式成比例。 然正是範圍和形狀之間的差異常常推動軟體開發成本的增長。這就是為什麼成本與所需求的變更規模不成比例。這也是為什麼開發前期比開發後期便宜得多的原因。 從利益相關者的角度來看,他們只是提供了一系列大致相似範圍的變更。從開發者的角度來看,利益相關者給予他們一系列拼圖碎片,他們必須將其放入日益複雜的拼圖中。每個新的要求都比上一個更難擬合,因為系統的形狀與要求的形狀不匹配。 我在這裡以一種非傳統的方式使用「形狀」這個詞,但我認為隱喻很貼切。軟體開發人員常常感覺自己被迫將方形木塊塞進圓洞中。 問題當然在於系統的架構。如果這個架構偏好某種形式,那麼新功能愈來愈難以適應這種結構。因此,架構應該盡可能地不受形式的限制。 更大的價值 行為與架構,何者提供更大的價值?對軟體系統來說,它的功能更重要,還是易於修改更重要? 如果你問企業經理,他們通常會說軟體系統的工作更重要。開發人員通常也會贊同。但這是錯誤的態度,我們可以用簡單的邏輯工具來證明它是錯誤的,那就是檢查極端情況。 如果你有一個完美運作但無法更改的程式,那麼當需求改變時它將無法運作,意謂著這個程式將變得沒有用處;然而一個不起作用但容易修改的程式,那麼我們可以透過簡單的修改使其運作起來,並在需求變化時保持運作,因此這個程式將持續保持有用。 也許這個論點對有些人來說不那麼具有說服力,畢竟沒有什麼程式是真的不能改變的。但是,有些系統在實際上是幾乎不可能改變的,因為改變的成本大過於了改變的好處。許多系統在某些功能或配置上達到了這一點。 如果你問企業經理們是否希望能夠進行變更,他們當然會說是,但可能會在回答中指出目前的功能比任何後續的靈活性更重要。相反地,如果企業經理們向你提出變更要求,而你估計的成本過高而無法負擔,他們很可能會對你允許系統達到變更變得不切實際的程度感到憤怒。 艾森豪威爾矩陣 下圖為艾森豪威爾(Dwight D. Eisenhower)總統的重要性(importance)與緊迫性(urgency)的矩陣,艾森豪是這麼說的: 我有兩種問題,一種是緊急的,一種是重要的。緊急的問題並不重要,而重要的問題從不緊急。 軟體的第一個價值 - 行為,是緊迫但並非總是特別重要。 軟體的第二個價值 - 架構,重要但從不特別緊迫。 當然,有些事情既緊急又重要,或有些事情既不緊急又不重要。故最終,我們可以將這四種問題安排成優先順序: 緊急且重要 不緊急但重要 緊急但不重要 不緊急也不重要 需要注意的是,程式碼的架構位於前兩個位置,而程式碼的行為則佔據第一與第三的位置。商業經理和開發人員常犯的錯誤是將第三位的事項提升至第一位。換句話說,他們未能將那些緊急但不重要的功能與真正緊急且重要的功能區分開來。這種失誤導致忽視系統的重要架構,而偏好系統中不重要的功能。 軟體開發人員面臨的困境是,商業經理無法評估架構的重要性。這就是軟體開發人員被聘用的原因。因此,軟體開發團隊有責任強調架構的重要性,而不是功能的緊迫性。 為架構而戰 履行這個責任意味著投入一場戰鬥,或者也許更好的詞是「奮鬥」。坦白說,這些事情總是這樣做的方式。開發團隊必須為他們認為對公司最好的事情而奮鬥,管理團隊、市場團隊、銷售團隊和運營團隊也是如此。這總是一場奮鬥。 有效的軟體開發團隊會毫不畏懼地與其他利益相關者平起平坐地爭論。請記住,作為一名軟體開發者,您也是一個利益相關者。您需要保護您所需的軟體。這是您的角色和責任的一部分,也是您被雇用的重要原因之一。 如果你是一位軟體架構師,這個挑戰對你來說尤其重要。軟體架構師根據他們的工作描述,更關注系統的結構而非其特性和功能。架構師創建一個架構,使得這些特性和功能能夠容易地開發、修改和擴展。 只要記住:如果架構放在最後,那麼系統的開發成本將會愈來愈高,最終對系統的某個部分或整個系統的變更幾乎變得不可能。如果這種情況發生,那意味著軟體開發團隊沒有為他們知道的必要事項進行足夠的努力。

<span title='2023-09-04 15:18:12 +0800 +0800'>September 4, 2023</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;Rain Hu