摘要:函數式編程與面向對象編程編程的本質之劍目錄編程的本質讀到兩篇文章寫的不錯綜合摘錄一下復合是編程的本質函數式程序員在洞察問題方面會遵循一個奇特的路線。在面向對象編程中,類或接口的聲明就是表面。
函數式編程與面向對象編程[5]:編程的本質
之劍 2016.5.6 01:26:31
編程的本質讀到兩篇文章,寫的不錯, 綜合摘錄一下
復合是編程的本質函數式程序員在洞察問題方面會遵循一個奇特的路線。他們首先會問一些似有禪機的問題。例如,在設計一個交互式程序時,他們會問:什么是交互?在實現 基于元胞自動機的生命游戲時,他們可能又去沉思生命的意義。秉持這種精神,我將要問:什么是編程?在最基本的層面,編程就是告訴計算機去做什么,例如『從 內存地址 x 處獲取內容,然后將它與寄存器 EAX 中的內容相加』。但是即使我們使用匯編語言去編程,我們向計算機提供的指令也是某種有意義的表達式。假設我們正在解一個難題(如果它不難,就沒必要用計算 機了),那么我們是如何求解問題的?我們把大問題分解為更小的問題。如果更小的問題還是還是很大,我們再繼續進行分解,以此類推。最后,我們寫出求解這些 小問題的代碼,然后就出現了編程的本質:我么將這些代碼片段復合起來,從而產生大問題的解。如果我們不能將代碼片段整合起來并還原回去,那么問題的分解就 毫無意義。
層次化分解與重新復合的過程這個思維過程, 并非是受計算機的限制而產生,它反映的是人類思維的局限性。我們的大腦一次只能處理很少的概念。生物學中被廣為引用的 一篇論文指出我們我們的大腦中只能保存 7± 2 個信息塊。我們對人類短期記憶的認識可能會有變化,但是可以肯定的是它是有限的。底線就是我們不能處理一大堆亂糟糟的對象或像蘭州拉面似的代碼。我們需要 結構化并非是因為結構化的程序看上去有多么美好,而是我們的大腦無法有效的處理非結構化的東西。我們經常說一些代碼片段是優雅的或美觀的,實際上那只意味 著它們更容易被人類有限的思維所處理。優雅的代碼創造出尺度合理的代碼塊,它正好與我們的『心智消化系統』能夠吸收的數量相符。
那么,對于程序的復合而言,正確的代碼塊是怎樣的?它們的表面積必須要比它們的體積增長的更為緩慢。我喜歡這個比喻,因為幾何對象的表面積是以尺寸 的平方的速度增長的,而體積是以尺寸的立方的速度增長的,因此表面積的增長速度小于體積。代碼塊的表面積是是我們復合代碼塊時所需要的信息。代碼塊的體積 是我們為了實現它們所需要的信息。一旦代碼塊的實現過程結束,我們就可以忘掉它的實現細節,只關心它與其他代碼塊的相互影響。在面向對象編程中,類或接口 的聲明就是表面。在函數式編程中,函數的聲明就是表面。我把事情簡化了一些,但是要點就是這些。
范疇論在積極阻礙我們探視對象的內部方面,范疇論具有非凡的意義。范疇論中的一個對象,像一個星云。對于它,你所知的只是它與其他對象之間的關系,亦即它 與其他對象相連接的箭頭。這就是 Internet 搜索引擎對網站進行排名時所用的策略,它只分析輸入與輸出的鏈接(除非它受欺騙)。在面向對象編程中,一個理想的對象應該是只暴露它的抽象接口(純表面, 無體積),其方法則扮演箭頭的角色。如果為了理解一個對象如何與其他對象進行復合,當你發現不得不深入挖掘對象的實現之時,此時你所用的編程范式的原本優 勢就蕩然無存了。
布爾代數的邏輯體系讓我們暫時撇開平臺、框架、技術、設計模式、對象思想、敏捷開發論等。 追問程序本質。
布爾代數起源于數學領域,是一個用于集合運算和邏輯運算的公式:〈B,∨,∧,? 〉。其中B為一個非空集合,∨,∧為定義在B上的兩個二元運算,?為定義在B上的一個一元運算。
通過布爾代數進行集合運算可以獲取到不同集合之間的交集、并集或補集,進行邏輯運算可以對不同集合進行與、或、非。
在布爾代數上的運算被稱為AND(與)、OR(或)和NOT(非)。代數結構要是布爾代數,這些運算的行為就必須和兩元素的布爾代數一樣(這兩個元素是TRUE(真)和FALSE(假))。亦稱邏輯代數.布爾(Boole,G.)為研究思維規律(邏輯學)于1847年提出的數學工具.布爾代數是指代數系統B=〈B,+,·,′〉
它包含集合B連同在其上定義的兩個二元運算+,·和一個一元運算′,布爾代數具有下列性質:對B中任意元素a,b,c,有:
1.a+b=b+a, a·b=b·a. 2.a·(b+c)=a·b+a·c, a+(b·c)=(a+b)·(a+c). 3.a+0=a, a·1=a. 4.a+a′=1, a·a′=0.公理化
在 1933 年,美國數學家 Edward Vermilye Huntington (1874-1952) 展示了對布爾代數的如下公理化:
交換律: x + y = y + x。 結合律: (x + y) + z = x + (y + z)。 Huntington等式: n(n(x) + y) + n(n(x) + n(y)) = x。
一元函數符號 n 可以讀做"補"。
Herbert Robbins 接著擺出下列問題: Huntington等式能否縮短為下述的等式,并且這個新等式與結合律和交換律一起成為布爾代數的基礎? 通過一組叫做 Robbins 代數的公理,問題就變成了: 是否所有的 Robbins 代數都是布爾代數?
Robbins 代數的公理化:
交換律: x + y = y + x 結合律: (x + y) + z = x + (y + z) Robbins等式: n(n(x + y") + n(x + n(y))) = x
這個問題自從 1930 年代一直是公開的,并成為 Alfred Tarski 和他的學生最喜好的問題。
在 1996 年,William McCune 在 Argonne 國家實驗室,建造在 Larry Wos、Steve Winker 和 Bob Veroff 的工作之上,肯定的回答了這個長期存在的問題: 所有的 Robbins 代數都是布爾代數。這項工作是使用 McCune 的自動推理程序 EQP 完成的。
計算機程序的本質從本質上來說, 程序就是一系列有序執行的指令集合。 如何將指令集合組織成可靠可用可信賴的軟件(美妙的邏輯之塔), 這是個問題。
程序 = 邏輯 + 控制。 what to do + when to do.
從編程角度來說, 開發者應對的就是邏輯, 邏輯的表達、組織和維護。 邏輯是事物自此及彼的合乎事物發展規律的序列。指令是邏輯的具體實現形式。
邏輯成立的先決條件是合乎事物發展規律。 程序只能處理數值, 卻傳入了字符串, 就只能報錯而無法繼續; 當處理海量數據時, 若內存不足, 就會導致程序崩潰; 若程序存在內存泄露, 隨著時間的推移而耗盡內存, 也會導致程序崩潰。 多個線程同時修改一個共享變量, 若不加控制, 就會因為不同線程執行修改變量的時序的不確定導致該變量最終值的不確定。 這些就是程序執行的發展規律。 要編寫程序, 必定要先通悉這些規律。
規律的表現形式是:如果條件 (C1, C2, ..., Cn) 是產生結果 (R1, R2, ... , Rn) 的充分必要條件, 那么當 C1, C2, ..., Cn 任一不滿足條件時, 都不可能產生結果 (R1, R2, ..., Rn) ; 反之, 若結果 (R1, R2, ..., Rn) 沒有出現, 則必定是 C1, C2, ..., Cn 某一條件不滿足導致。 錯誤和異常即是 C1, C2, ..., Cn 任一不滿足條件的表現。規律的性質是必然的, 不存在可能之說; 只存在人們探索的是否足夠精確。編程開發首先應當懂得程序執行的規律, 然后才是實際的開發; 否則就會被程序的結果折騰得死去活來。
在通悉程序執行規律之后, 程序需要解決如下問題:
要表達什么邏輯
如何表達該邏輯;
如何維護該邏輯。
軟件的復雜性表現在如何表達和維護交互復雜的大型邏輯上
暫時先回到軟件的起點, 回顧一下這一切是如何發生的。
最初, 人們使用物理的或邏輯的二進制機器指令來編寫程序, 嘗試著表達思想中的邏輯, 控制硬件計算和顯示, 發現是可行的;
接著, 創造了助記符 —— 匯編語言, 比機器指令更容易記憶;
再接著, 創造了編譯器、解釋器和計算機高級語言, 能夠以人類友好自然的方式去編寫程序, 在犧牲少量性能的情況下, 獲得比匯編語言更強且更容易使用的語句控制能力:條件、分支、循環, 以及更多的語言特性: 指針、結構體、聯合體、枚舉等, 還創造了函數, 能夠將一系列指令封裝成一個獨立的邏輯塊反復使用;
逐漸地,產生了面向過程的編程方法;
后來, 人們發現將數據和邏輯封裝成對象, 更接近于現實世界, 且更容易維護大型軟件, 又出現了面向對象的編程語言和編程方法學, 增加了新的語言特性: 繼承、 多態、 模板、 異常錯誤。
為了不必重復開發常見工具和任務, 人們創造和封裝了容器及算法、SDK, 垃圾回收器, 甚至是并發庫;
為了讓計算機語言更有力更有效率地表達各種現實邏輯, 消解軟件開發中遇到的沖突, 還在語言中支持了元編程、 高階函數, 閉包 等有用特性。
為了更高效率地開發可靠的軟件和應用程序, 人們逐漸構建了代碼編輯器、 IDE、 代碼版本管理工具、公共庫、應用框架、 可復用組件、系統規范、網絡協議、 語言標準等, 針對遇到的問題提出了許多不同的思路和解決方案, 并總結提煉成特定的技術和設計模式, 還探討和形成了不少軟件開發過程, 用來保證最終發布的軟件質量。 盡管編寫的這些軟件和工具還存在不少 BUG ,但是它們都“奇跡般地存活”, 并共同構建了今天蔚為壯觀的軟件世界。
此外, 軟件還經歷了“單機程序 => 多機程序 => 分布式程序” 的過程 , 多機聯網程序因為多個子系統的交互變得更加復雜。 這里不再贅述。
但請注意, 無論軟件發展到多么復雜的程度, 總有一群人, 在試圖從程序的本質中探究軟件開發的基本問題, 他們試圖論證和確保程序的正確性、提煉軟件的基本屬性并進行衡量; 程序的正確性本質是邏輯學來保證的。 沒有邏輯學, 程序根本就無法立足, 更不可能有今天的大規模應用。
軟件開發工具讓我們更有效率地創造邏輯、 遠離語法錯誤的困擾;
公共庫將常用的通用邏輯塊封裝成可反復使用的組件, 避免不必要的重復勞動;
設計模式體現的是如何可擴展地解決常見的邏輯交互問題;
應用框架解決的是應用的通用邏輯流的控制的問題,讓開發者更多地聚焦具體業務邏輯上;
開發技術是在具體的應用情境下按照既定總體思路去探究具體問題解決的方法。
表達和維護大型邏輯我們要解決的是更通用的問題: 如何以更不易出錯的方式去表達和維護大型邏輯 ?
表達和維護大型邏輯的終極訣竅就是: 將大型邏輯切分為容易消化的一小塊一小塊, “不急不忙地吃掉”。
在該方法的實踐中, 可以充分利用現有的開發工具、公共庫、設計模式、應用框架、開發技術。
獨立無交互的大型邏輯或接口實現獨立無交互的邏輯通常體現為公共庫, 可以解決常用或公共的日常任務, 對其他邏輯無任何依賴和交互, 即自足邏輯。
應對獨立無交互的大型邏輯的首要方法是分解為若干的容易實現、測試和復用的小塊邏輯, 編寫和嚴格測試。
其次是運用成熟的編程模式去表達邏輯, 盡可能復用經過嚴格測試的可靠的庫。
獨立無交互的大型邏輯通過合理的邏輯塊切分、嚴格的單元測試可以獲得充分的測試和可靠度。
獨立無交互的耗時長的邏輯或接口實現快速響應的問題: “用戶要求等待時間短” 與 “請求處理耗時長” 之間的矛盾導致的。
解決獨立無交互的耗時長的邏輯依然可以采用切分邏輯塊、嚴格的單元測試的做法使之更容易處理;
此外, 有兩種設計思路可以考慮: 并發 與 異步。
并發思路是將切分的相互獨立的邏輯塊分配給不同的控制線程中執行, 從而降低請求處理時長; 并發方案獲得的性能提升取決于串行操作在總操作中的時間占比。
異步思路是“先響應, 后處理, 終通知” 的"先奏后斬"方案。
將一步分離成了三步, 為了讓用戶首先獲得初步的承諾, 再去履行承諾。 這樣做能讓用戶暫時地放心, 卻增加了新的問題: 消息中間件組件的開發與部署、異步消息發送與接收、編程模型的變化和適應。如果整個過程運作良好, 將會達到很好的體驗,容易為用戶接受。如果其中一步發生差錯, 就會導致各種問題, 比如數據不一致, 消息堆積、 請求無法被處理。最終用戶等待時間并沒有降低, 反而使體驗更加糟糕。 當然, 如果成功率為 95%, 也是“可以接受”的, 這樣用戶可能會怪自己“運氣不太好”, 而不會過多怪責系統的不完善。畢竟沒有任何事情能夠做到完美的地步。
并發與異步方案的調試難度和排查問題都比同步方案增加不少。 每一種新的設計方案都會有其優點, 同時也會有其缺點。 權衡優缺點, 擇善而從之 。值得注意的是, 并發方案是針對服務端實際處理請求邏輯而言, 而異步方案是針對請求處理之前是否立即回復的方式。 并發與順序、 異步與同步兩兩組合, 可得到四種方式:
順序同步: 最初的編程模型優點是簡單、安全、 容易維護和調試;
缺點是性能較低, 響應時間和吞吐量都不高; 若請求處理時長非常短, 采用順序同步的方案佳;
并發同步: 改進的編程模型優點是通過并發提高服務端的處理速度和吞吐量, 但若請求處理耗時較長, 響應時間仍然不高, 影響客戶端體驗;
若通過并發方案處理請求的時長非常短, 或客戶端體驗要求不高, 可以采用并發同步的方案;
順序異步: 改善客戶端體驗的編程模型優點是提高了響應時間和客戶端體驗, 由于其邏輯處理仍然采用順序方式, 請求處理時長并未有改善, 因此吞吐量并沒有改善。 是一種較好的折衷方案;
若請求處理耗時較長, 影響客戶端體驗, 且請求處理邏輯復雜, 采用并發方案容易出錯或難以并發, 可采用順序異步方案;
并發異步: 同時改善客戶端體驗和服務端處理速度優點是提高了響應時間、客戶端體驗和處理速度、吞吐量。
缺點是容易出錯, 且不易調試;
若客戶端對響應體驗要求較高, 請求處理邏輯簡單(比如簡單的數據拉取和匯總), 采用并發方式可有效提升處理速度, 可以采用并發異步方案;
邏輯塊之間的交互耦合與可擴展性軟件的復雜性真正體現在邏輯塊的持續長久的交互耦合和可擴展上。這是軟件開發與維護中極具挑戰性的部分。
邏輯塊之間的交互耦合通常體現在三種情境:
操作順序的依賴。 比如資源更新操作必須在指定資源已經創建的情況下進行。
對共享有限資源的并發申請。 比如打印機只有兩臺, 卻有多個應用程序連接上去請求打印文檔;
對共享可變狀態的并發訪問。 比如兩個操作同時要更新數據庫中的同一條記錄;
三種情境的復雜性均是由并發引起的。 假設所有操作都是串行進行的, 邏輯塊的交互無非是“你方唱罷我登場”的次序控制, 而資源對單個請求通常是足夠的; 一旦采用了并發方案, 就難以控制邏輯塊的執行次序和資源分配的具體情況了, 容易導致某資源對單個請求不足的情況, 從而阻塞多個請求的處理甚至死鎖。并發提升了應用的性能, 卻增加了出錯的風險和幾率。并發控制是大型邏輯交互的本質性難點。并發控制的難點在于時序的合理控制和有效資源的合理分配。
對于 a 情境, 通常采用添加前置條件來求解, 在操作之前校驗相關資源是否滿足、實體狀態是否合理, 實體之間的關聯是否正確; 若前置條件不滿足, 則直接返回錯誤提示, 或者暫時掛起以備后續繼續執行;
對于 b 情境, 需要創建一個可靠適用的資源分配算法 和資源分配模塊 , 應用程序不再“自行”去拉取資源, 而是向資源分配模塊申請資源, 由資源分配模塊根據實際申請的整體情況及申請條件來決定如何分配資源;
對于 c 情境, 需要進行安全的互斥訪問, 謹慎地控制。
邏輯塊之間的交互耦合應該交給交互解耦模塊去完成, 而不是在自己的接口里實現。
也就是說, 只有交互解耦模塊知道所有接口之間的交互, 而接口只做自己知道的事情就可以了。否則, 接口 A 與接口 B 必須知道彼此究竟做了什么, 才能正確地做自己的事情。 假設 接口 A 和接口 B 都修改某個資源的狀態。 接口 A 在做某項操作執行必須執行 IF (ConditionX) do something ; DoMyOwnThing ; 接 口 B 也要根據 A 的邏輯相應地執行 if (ConditionY) do anotherThing;DoMyOwnThing. 而程序員在維護和修改接口 A 的邏輯時, 不一定知道接口 B 的邏輯與之相關, 于是修改不可避免地破壞了接口 B 的邏輯。 耦合的接口數量越多, 或者耦合接口之間的耦合資源越多, 對后期維護和擴展將是一個難以應對的噩夢。
實現邏輯時的容錯考慮對于邏輯塊之間的交互解耦, 或者通俗地說, 模塊解耦.
程序中的邏輯主要是三類:
獲取值: 從數據庫、網絡或對象中獲取值。 如果數據庫或網絡訪問足夠穩定的話, 可以看成是簡單的獲取值, 數據庫訪問和網絡訪問對獲取值是透明的;
檢測值: 檢測值是否合法, 通常是前置條件校驗、 中間狀態校驗和后置結果校驗, 根據檢測結果執行“獲取值”或“設置值”的邏輯;
設置(拷貝)值: 設置數據庫、對象中的值; 或者發送數據和指令給網絡。如果數據庫或網絡訪問足夠穩定的話, 可以看成是簡單的設置值, 數據庫訪問和網絡訪問對設置值是透明的;
這三類邏輯可以稱為邏輯元。 具體業務邏輯就是基于物理的或邏輯的資源限制, 將邏輯元的組合封裝成邏輯塊, 有效控制邏輯塊的時序交互和資源分配。 時序控制不合理和資源缺乏導致錯誤和異常。兩個程序同時更新一個共享變量, 如果時序不控制, 就會導致錯誤的結果; 網絡通信錯誤, 是因為網絡帶寬資源是有限的。
如何應對錯誤和異常 ? 防御性編程預防錯誤的方法就是進行防御性編程, 進行容錯考慮。 多思考: 如果這一步發生錯誤, 會導致什么問題? 該如何做才能預防這個錯誤? 如果難以預防, 該如何描述, 才能在出現錯誤時更好地定位出這樣的錯誤? 在出現錯誤時, 如何才能恢復到正常合法的狀態 ? 如果無法程序自動恢復, 怎樣做才能讓手工處理更加簡單 ?
要健壯地表達和維護大型邏輯, 首先系統整體架構必須足夠穩固可靠, 在開發和維護過程中持續加固。 假設一棟建筑整體設計有問題, 那么, 無論里面的房間裝飾得多么漂亮優雅, 都會隨著建筑的坍塌而消亡。 這需要深入去探究所使用的應用框架, 挖出可能的不可靠風險, 并加以預防和控制。
在已確定的設計方案和業務邏輯的情況下, 如何編寫BUG更少的代碼:
簡明扼要的注釋 + 契約式/防御式編程 + 更短小的邏輯塊 + 復用公共庫 + 嚴格測試
編寫更少BUG程序的六條準則:
在方法前面編寫簡明扼要的注釋: 方法用途, 接收參數, 返回值, 注意事項, 作者, 時間。
契約式編程: 在方法入口處編寫前置條件校驗,在方法出口處編寫后置結果校驗 ;
防御式編程: 編程時嚴格校驗參數和前置條件; 仔細考慮各種錯誤與異常的定位和處理;
編寫和保持短小邏輯塊, 易于為人的腦容量一次性處理, 容易測試;
復用經過嚴格測試的可靠的公共庫; 如果庫沒有經過很好的測試,但有很好的用處, 幫助其添加測試;
對所編寫的代碼, 如果不是邏輯元, 都要進行嚴格測試。
關于作者: 陳光劍,江蘇東海人, 號行走江湖一劍客,字之劍。程序員,詩人, 作家
http://universsky.github.io/?
$(document).ready(function(){
$("h2,h3,h4,h5,h6").each(function(i,item){ var tag = $(item).get(0).localName; $(item).attr("id","wow"+i); $("#category").append(""+$(this).text()+""); $(".newh2").css("margin-left",0); $(".newh3").css("margin-left",20); $(".newh4").css("margin-left",40); $(".newh5").css("margin-left",60); $(".newh6").css("margin-left",80); });
});
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/65873.html
摘要:聲明式編程一種編程范式,與命令式編程相對立。常見的聲明式編程語言有數據庫查詢語言,正則表達式邏輯編程函數式編程組態管理系統等。函數式編程,特別是純函數式編程,嘗試最小化狀態帶來的副作用,因此被認為是聲明式的。 編程范式與函數式編程 一、編程范式的分類 常見的編程范式有:函數式編程、程序編程、面向對象編程、指令式編程等。在面向對象編程的世界,程序是一系列相互作用(方法)的對象(Class...
摘要:函數式編程的哲學就是假定副作用是造成不正當行為的主要原因。函數組合面向對象通常被比喻為名詞,而函數式編程是動詞。尾遞歸優化函數式編程語言中因為不可變數據結構的原因,沒辦法實現循環。 零、前言 說到函數式編程,想必各位或多或少都有所耳聞,然而對于函數式的內涵和本質可能又有些說不清楚。 所以本文希望針對工程師,從應用(而非學術)的角度將函數式編程相關思想和實踐(以 JavaScript 為...
摘要:函數式編程導論從屬于筆者的前端入門與工程實踐。函數式編程即是在軟件開發的工程中避免使用共享狀態可變狀態以及副作用。 JavaScript 函數式編程導論從屬于筆者的Web 前端入門與工程實踐。本文很多地方是講解函數式編程的優勢,就筆者個人而言是認可函數式編程具有一定的好處,但是不推崇徹底的函數式編程化,特別是對于復雜應用邏輯的開發。筆者在應用的狀態管理工具中就更傾向于使用MobX而不是...
摘要:中的函數式編程思想匿名函數在函數式編程語言中,函數是可以沒有名字的,匿名函數通常表示可以完成某件事的一塊代碼。匿名函數中包含對的局部變量的引用,因此當返回時,的值被保留不會被垃圾回收機制回收,持續調用,將會改變的值。 1 函數式編程簡介 函數式編程是和傳統命令式編程區分的一種編程思想,在函數式編程語言中,函數是第一類的對象,也就是說,函數 不依賴于任何其他的對象而可以獨立存在,而在面向...
閱讀 2228·2021-11-22 09:34
閱讀 1337·2021-10-11 10:59
閱讀 4435·2021-09-22 15:56
閱讀 3288·2021-09-22 15:08
閱讀 3408·2019-08-30 14:01
閱讀 779·2019-08-30 11:16
閱讀 1133·2019-08-26 13:51
閱讀 2910·2019-08-26 13:43