傻瓜式程式設計正規化,程式設計師的基本功

2022-06-30 04:30:10 字數 4428 閱讀 9084

我從資料連續性**延伸閱讀時,在hn thread 論壇 @turingtest 的發言引用中,意外發現了一篇文章《傻瓜式程式設計正規化:程式設計師基本功》,由彼得·範·羅伊寫於 2009 年,描繪了設計程式語言的操作空間。如果你喜歡這篇文章,可能也會喜歡範·羅伊和哈利迪的書《concepts, techniques, and models of computer programming》,因為該文的主要內容正是基於這本書。

本文介紹了所有主要程式設計正規化、它們底層的概念以及它們的關係…我們給出了大約 30 個有用的程式設計正規化的分類和它們的關係。

程式設計正規化是基於數學理論或一組特定原則的方法,每一種正規化支援一組概念。範·羅伊信仰多正規化程式語言:解決乙個程式設計問題,需要選擇正確的概念;解決多個問題,則需要組合分屬不同部分的多個概念。況且,許多程式需要解決的問題本來就不止乙個。「理論上,一門語言應該以良好的方式支援多種概念,這樣程式設計師就可以在需要時選擇合適的概念,不受他人所累。」說得很直觀,但我覺得也有潛在缺點:閱讀這種語言的原始碼時,需要精通多種正規化,並了解它們是怎麼相互影響的。(範·羅伊可能在說「良好的方式」時考慮過如何改正這種缺點:真正的多正規化語言應該避免跨正規化干擾,而不僅僅是支援一大堆概念)。正如範·羅伊本人在後面討論狀態時所說的:「關鍵在於選擇具有正確概念的正規化。概念太少,則程式比較複雜;太多,則推理比較複雜。」

程式語言多如牛毛,程式設計正規化卻寥若晨星。但還是有那麼一些正規化的,本文將介紹 27 種有實際使用場景的正規化。

下圖(圖 2)展示了核心理念。「值得好好學習。」每個框是一種正規化;框和框之間的箭頭表示從一種正規化變成另一種正規化時需要增加的概念。

圖 2 是根據創造性擴充套件原則(creative extension principle)組織的:

概念組合成正規化並非任意為之。可以用創造性擴充套件原則來組織它們…在乙個給定的正規化中,程式可能由於技術原因變得複雜,導致與正在解決的特定問題沒有直接關係。這意味著我們需要去發掘新概念了。

最常見的是,需要全域性修改(而非區域性修改)才能實現單個目標。(說句題外話,針對這種問題我又忍不住想給人安利 aop(面向切面程式設計)了!)比如,若我們想要函式可以在任意時刻檢測錯誤,並把控制權轉給修正錯誤的**,我們就需要有「異常」概念,否則將要影響所有**。

(範·羅伊原文的)圖 5 異常概念是如何簡化程式的

程式設計正規化有兩個關鍵屬性:是否具有(使用者)可見的不確定性,以及對狀態的支援度。

…如果使用者用同一套配置開始執行,結果卻不同,就叫做他們見到了不確定性。這非常不可取…我們認為,只有在需要表現不確定性時,才應該讓使用者能夠遇到不確定性。

關於狀態,我們對正規化如何支援及時儲存一系列值感興趣。狀態可以是具名的或無名的、確定的或非確定的、序列的或並行的。並非所有組合都有用!下面的圖 3 展示了一些有用的:

(範·羅伊原文的)圖 3 不同等級的狀態支援度

主要正規化圖(圖 2)的橫軸就是按照上圖中粗線組織的。

最重要的四個程式設計概念是記錄、詞法範圍的閉包、獨立性(併發)和具名狀態。

記錄是若干組資料項(比如結構),可通過索引訪問其中的每一項。詞法範圍的閉包,是把乙個過程與其對外部的引用(定義閉包時引用的外部資料)結合起來。你可以建立乙個『工作包(packet of work)』在程式中傳遞,在之後某個時間才執行。獨立性在這裡指行為可以獨立發生。即,它們可以併發地執行。關於併發,最受歡迎的兩種正規化是狀態共享和訊息傳遞。具名狀態指我們可以給乙個狀態起名字,這是最簡單的級別。但對於命名可變的狀態,範·羅伊有乙個更為深刻和有趣的想法:

在程式設計裡,狀態就是乙個關於時間的抽象概念。函式式程式設計就沒有時間概念…因為函式不會發生變化。現實世界則不同。在現實世界中,沒什麼東西像函式那樣永恆不變。機體會成長和學習。機體在不同的時候受到相同的刺激,反應通常是不同的。對此,我們在程式裡要如何建模?我們得建立乙個具有唯一標識(它的名字)的實體模型,它的行為在程式執行過程中還會發生改變。為此,我們在程式裡加入了乙個抽象的時間概念。這個抽象時間概念只是乙個序列,具有唯一名字的時間值的序列。

接著範·羅伊又給了乙個建議,我覺得自相矛盾:「最好讓具名狀態永遠可見:應始終提供可以從外部訪問該狀態的途徑。」(當談及正確性時),又說「具名狀態對系統的模組化很重要」(想想《information hiding》)。

資料抽象是根據精確規則來組織資料結構用法的方法,這些精確規則保證了資料結構被正確使用。資料抽象分別有一套內部介面、外部介面和兩者間的介面。

資料抽象可以按照兩個主要維度進行組織:是否使用具名狀態,是否將操作繫結到具有資料的單個實體中。

(範·羅伊原文的)圖 14 組織資料抽象的 4 種方法

範·羅伊接著討論了多型和繼承(注意,範·羅伊傾向於組合優於繼承,但若你必須用繼承,請確保遵循替換原則)。

併發的核心問題是不確定性。

若程式的使用者碰到不確定性,就會很難處理。使用者可見的不確定性有時被稱為競態條件(race condition)…

若禁止不確定性,編寫具有獨立部分的程式將會受限。但我們能夠限制不確定行為的可見程度。有兩種選擇:定義一種語言,所有不確定性都是不可見的;或把不確定性的可見範圍限制在真正需要它的地方。

至少有四種程式設計模式是併發而又遮蔽了不確定性的(沒有競態條件)。表 2(下方)羅列了它們四個以及訊息傳遞式併發。

併發正規化

是否存在競態

輸入可以是不確定的

語言例子

宣告式併發否否

oz、alice

約束程式設計否否

gecode、numerica

函式響應式程式設計否否

frtime、yampa

離散同步程式設計否是

esterel、lustre、signal

訊息傳遞併發是是

erlang、e

表 2 四種確定性併發正規化和一種非確定性的

宣告式併發也稱為單調資料流(monotonic dataflow)。程式接收確定性輸入並用於計算確定性輸出。

函式響應式程式設計縮寫為 frp(又名「連續同步程式設計(continuous synchronous programming)」)。我們在其中以函式形式編寫程式,但函式引數可以更改,並且會影響輸出。

離散同步程式設計(也稱為「響應式(reactive)」),系統等待輸入事件,執行內部計算,然後發布輸出事件。響應式和函式響應式程式設計之間的主要區別是,響應式程式設計是離散的而非連續的。

在約束程式設計中,我們把要解決的問題表述為約束滿足問題(constraint satisfaction problem,縮寫為 csp)…約束程式設計是所有實際程式設計正規化中「最宣告式的」(most declarative)。

在約束程式設計中,不是寫一組要執行的指令,而是對問題建模:將問題表述為一組變數、對變數的約束、以及實現了約束的傳播器(propagators)。然後把模型傳遞給求解器(solver)。

到這裡,我們已經將一些概念和正規化匆匆過了一遍,接下來我想完成範·羅伊關於設計程式語言的一些想法。有一類有趣的語言叫「雙正規化」語言。雙正規化語言通常用一種正規化寫小型程式,用另一種正規化寫大型程式。第二種正規化通常用來支援抽象和模組化。比如,在物件導向語言中內建的支援約束程式設計的求解器。

更一般地說,範·羅伊看到了乙個分層語言設計,它有四個核心層,這是乙個在眾多專案中都自發出現的結構:

常見語言的層次結構有四層:嚴格的功能核心,然後是宣告性併發,然後是非同步訊息傳遞,最後是全域性具名狀態。這種分層結構天然地支援四種正規化。

範·羅伊從他的分析中得出四個結論:

宣告式程式設計是程式語言的核心。

在可預見的未來,宣告式程式設計將保持其核心地位,因為分布式、安全性和容錯是程式語言需要支援的基本主題。

確定性併發是一種重要的併發形式,不應忽視。這是充分利用多核處理器並行性的妙招。

用於處理一般併發的正確預設方法是訊息傳遞,而非共享狀態。

對於大型軟體,範·羅伊認為我們需要採用自給自足的系統設計風格,系統可以自行配置、修復、調整等。系統將元件作為一等實體(由閉包規定),可通過高階程式設計來操作。元件間通過傳遞訊息來通訊。由具名狀態和事務來支援系統配置和維護。除此之外,系統本身應設計為一組聯動反饋迴路(interlocking feedback loops)。說到這,我想起了《系統思考和因果迴圈圖》。

最後再說一句

每個正規化都有它的「靈魂」,只有實際使用該正規化才能理解。我們建議你通過在程式設計中實操來探索正規化。

檢視英文原文:

Linux平台下java程式設計師的基本功(二)

一 安裝linux和putty 1 安裝比較簡單,不再重複貼圖,具體參考以下文件 如果是在一台全新的機器上安裝,文件中虛擬機器安裝部分忽略即可。也可以參考 2 安裝putty 順便掃掃盲 1 何為putty?putty是乙個免費的windows平台下的telnet rlogin和ssh客戶端,put...

讓人懊惱的面試 看程式設計師的基本功

今天去參加了微軟的乙個外包的公司的招聘,當然,這個招聘的公司不在國內,不過好在出人意料的是這個公司竟然在武漢來招聘了,我的乙個非常優秀的c 的朋友被應聘了,然後他極力推薦我去參加應聘,我第一次去,那個boss 老外 非常讚賞我,因為我的簡歷的原因,原來做了shangducms這個專案並且還出了一本書...

基本概念 程式設計師基本功 鍊錶的基本概念

程式設計師必須掌握資料結構 資料結構中必講鍊錶 所以,程式設計師必須掌握鍊錶 鍊錶是資料元素的線性集合 linear collection 物理儲存不連續。那麼,這種設計的優點是什麼?缺點又是什麼?鍊錶的基本結構 鍊錶是由一系列的 節點 組成在一起的集合,節點 node 由資料域 data 和指標域...