之劍 2016.5.6 01:26:31javascript
<!--目錄-->css
<div id="category"></div>java
讀到兩篇文章,寫的不錯, 綜合摘錄一下jquery
函數式程序員在洞察問題方面會遵循一個奇特的路線。他們首先會問一些似有禪機的問題。例如,在設計一個交互式程序時,他們會問:什麼是交互?在實現 基於元胞自動機的生命遊戲時,他們可能又去沉思生命的意義。秉持這種精神,我將要問:什麼是編程?在最基本的層面,編程就是告訴計算機去作什麼,例如『從 內存地址 x 處獲取內容,而後將它與寄存器 EAX 中的內容相加』。可是即便咱們使用匯編語言去編程,咱們向計算機提供的指令也是某種有意義的表達式。假設咱們正在解一個難題(若是它不難,就不必用計算 機了),那麼咱們是如何求解問題的?咱們把大問題分解爲更小的問題。若是更小的問題仍是仍是很大,咱們再繼續進行分解,以此類推。最後,咱們寫出求解這些 小問題的代碼,而後就出現了編程的本質:我麼將這些代碼片斷複合起來,從而產生大問題的解。若是咱們不能將代碼片斷整合起來並還原回去,那麼問題的分解就 毫無心義。git
這個思惟過程, 並不是是受計算機的限制而產生,它反映的是人類思惟的侷限性。咱們的大腦一次只能處理不多的概念。生物學中被廣爲引用的 一篇論文指出咱們咱們的大腦中只能保存 7± 2 個信息塊。咱們對人類短時間記憶的認識可能會有變化,可是能夠確定的是它是有限的。底線就是咱們不能處理一大堆亂糟糟的對象或像蘭州拉麪似的代碼。咱們須要 結構化並不是是由於結構化的程序看上去有多麼美好,而是咱們的大腦沒法有效的處理非結構化的東西。咱們常常說一些代碼片斷是優雅的或美觀的,實際上那隻意味 着它們更容易被人類有限的思惟所處理。優雅的代碼創造出尺度合理的代碼塊,它正好與咱們的『心智消化系統』可以吸取的數量相符。程序員
那麼,對於程序的複合而言,正確的代碼塊是怎樣的?它們的表面積必需要比它們的體積增加的更爲緩慢。我喜歡這個比喻,由於幾何對象的表面積是以尺寸 的平方的速度增加的,而體積是以尺寸的立方的速度增加的,所以表面積的增加速度小於體積。代碼塊的表面積是是咱們複合代碼塊時所須要的信息。代碼塊的體積 是咱們爲了實現它們所須要的信息。一旦代碼塊的實現過程結束,咱們就能夠忘掉它的實現細節,只關心它與其餘代碼塊的相互影響。在面向對象編程中,類或接口 的聲明就是表面。在函數式編程中,函數的聲明就是表面。我把事情簡化了一些,可是要點就是這些。github
在積極阻礙咱們探視對象的內部方面,範疇論具備非凡的意義。範疇論中的一個對象,像一個星雲。對於它,你所知的只是它與其餘對象之間的關係,亦即它 與其餘對象相鏈接的箭頭。這就是 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程序的六條準則:
在方法前面編寫簡明扼要的註釋: 方法用途, 接收參數, 返回值, 注意事項, 做者, 時間。
契約式編程: 在方法入口處編寫前置條件校驗,在方法出口處編寫後置結果校驗 ;
防護式編程: 編程時嚴格校驗參數和前置條件; 仔細考慮各類錯誤與異常的定位和處理;
編寫和保持短小邏輯塊, 易於爲人的腦容量一次性處理, 容易測試;
複用通過嚴格測試的可靠的公共庫; 若是庫沒有通過很好的測試,但有很好的用處, 幫助其添加測試;
對所編寫的代碼, 若是不是邏輯元, 都要進行嚴格測試。
關於做者: 陳光劍,江蘇東海人, 號行走江湖一劍客,字之劍。程序員,詩人, 做家
<link rel="stylesheet" href="http://yandex.st/highlightjs/6.2/styles/googlecode.min.css">
<script src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
<script src="http://yandex.st/highlightjs/6.2/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script type="text/javascript">
$(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('<a class="new'+tag+'" href="#wow'+i+'">'+$(this).text()+'</a></br>'); $(".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); });
});
</script>