自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

程序的本質(zhì)復雜性和元語言抽象

開發(fā) 開發(fā)工具
常聽到有人講“我寫代碼很講究,一直嚴格遵循DRY原則, 把重復使用的功能都封裝成可復用的組件,使得代碼簡短優(yōu)雅,同時也易于理解和維護”

組件復用技術的局限性

常聽到有人講“我寫代碼很講究,一直嚴格遵循DRY原則, 把重復使用的功能都封裝成可復用的組件,使得代碼簡短優(yōu)雅,同時也易于理解和維護”。顯然,DRY原則和組件復用技術是最常見的改善代碼質(zhì)量的方法,不 過,在我看來以這類方法為指導,能幫助我們寫出“不錯的程序”,但還不足以幫助我們寫出簡短、優(yōu)雅、易理解、易維護的“好程序”。對于熟悉Martin Fowler《重構》和GoF《設計模式》的程序員,我常常提出這樣一個問題幫助他們進一步加深對程序的理解:

  如果目標是代碼“簡短、優(yōu)雅、易理解、易維護”,組件復用技術是***的方法嗎?這種方法有沒有根本性的局限?

雖然基于函數(shù)、類等形式的組件復用技術從一定程度上消除了冗余,提升了代碼的抽象層次,但是這種技術卻有著本質(zhì)的局限性,其根源在于 每種組件形式都代表了特定的抽象維度,組件復用只能在其維度上進行抽象層次的提升。比如,我們可以把常用的HashMap等功能封裝為類庫,但是不管怎么封裝復用類永遠是類,封裝雖然提升了代碼的抽象層次,但是它永遠不會變成Lambda,而實際問題所代表的抽象維度往往與之并不匹配。

以常見的二進制消息的解析為例,組件復用技術所能做到的只是把讀取字節(jié),檢查約束,計算CRC等功能封裝成函數(shù),這是遠遠不夠的。比如,下面的表格定義了二進制消息X的格式:

它的解析函數(shù)大概是這個樣子:

  1. bool parse_message_x(char* data, int32 size, MessageX& x) { 
  2.     char *ptr = data
  3.     if (ptr + sizeof(int8) <= data + size) { 
  4.         x.message_type = read_int8(ptr); 
  5.         if (0x01 != x.message_type) return false; 
  6.         ptr += sizeof(int8); 
  7.     } else { 
  8.         return false; 
  9.     } 
  10.     if (ptr + sizeof(int16) <= data + size) { 
  11.         x.payload_size = read_int16(ptr); 
  12.         ptr += sizeof(int16); 
  13.     } else { 
  14.         return false; 
  15.     } 
  16.     if (ptr + x.payload_size <= data + size) { 
  17.         x.payload = new int8[x.payload_size]; 
  18.         read(ptr, x.payload, x.payload_size); 
  19.         ptr += x.payload_size; 
  20.     } else { 
  21.         return false; 
  22.     } 
  23.     if (ptr + sizeof(int32) <= data + size) { 
  24.         x.crc = read_int32(ptr); 
  25.         ptr += sizeof(int32); 
  26.     } else { 
  27.         delete x.payload; 
  28.         return false; 
  29.     } 
  30.     if (crc(data, sizeof(int8) + sizeof(int16) + x.payload_size) != x.crc) { 
  31.         delete x.payload; 
  32.         return false; 
  33.     } 
  34.     return true; 

很明顯,雖然消息X的定義非常簡單,但是它的解析函數(shù)卻顯得很繁瑣,需要小心翼翼地處理很多細節(jié)。在處理其他消息Y時,雖然雖然Y和X很相似,但是 卻不得不再次在解析過程中處理這些細節(jié),就是組件復用方法的局限性,它只能幫我們按照函數(shù)或者類的語義把功能封裝成可復用的組件,但是消息的結(jié)構特征既不 是函數(shù)也不是類,這就是抽象維度的失配。

程序的本質(zhì)復雜性

上面分析了組件復用技術有著根本性的局限性,現(xiàn)在我們要進一步思考:

如果目標還是代碼“簡短、優(yōu)雅、易理解、易維護”,那么代碼優(yōu)化是否有一個理論極限?這個極限是由什么決定的?普通代碼比起***代碼多出來的“冗余部分”到底干了些什么事情?

回答這個問題要從程序的本質(zhì)說起。Pascal語言之父Niklaus Wirth在70年代提出:Program = Data Structure + Algorithm,隨后邏輯學家和計算機科學家R Kowalski進一步提出:Algorithm = Logic + Control。誰更深刻更有啟發(fā)性?當然是后者!而且我認為數(shù)據(jù)結(jié)構和算法都屬于控制策略,綜合二位的觀點,加上我自己的理解,程序的本質(zhì) 是:Program = Logic + Control。換句話說,程序包含了邏輯和控制兩個維度。

邏輯就是問題的定義,比如,對于排序問題來講,邏輯就是“什么叫做有序,什么叫大于,什么叫小于,什么叫相等”?控制就是如何合理地安排時間和空間 資源去實現(xiàn)邏輯。邏輯是程序的靈魂,它定義了程序的本質(zhì);控制是為邏輯服務的,是非本質(zhì)的,可以變化的,如同排序有幾十種不同的方法,時間空間效率各不相 同,可以根據(jù)需要采用不同的實現(xiàn)。

程序的復雜性包含了本質(zhì)復雜性和非本質(zhì)復雜性兩個方面。套用這里的術語, 程序的本質(zhì)復雜性就是邏輯,非本質(zhì)復雜性就是控制。邏輯決定了代碼復雜性的下限,也就是說不管怎么做代碼優(yōu)化,Office程序永遠比Notepad程序復雜,這是因為前者的邏輯就更為復雜。如果要代碼簡潔優(yōu)雅,任何語言和技術所能做的只是盡量接近這個本質(zhì)復雜性,而不可能超越這個理論下限。

理解”程序的本質(zhì)復雜性是由邏輯決定的”從理論上為我們指明了代碼優(yōu)化的方向:讓邏輯和控制這兩個維度保持正交關系。來看Java的Collections.sort方法的例子:

  1. interface Comparator<T> { 
  2.     int compare(T o1, T o2); 
  3. public static <T> void sort(List<T> list, Comparator<? super T> comparator) 

使用者只關心邏輯部份,即提供一個Comparator對象表明序在類型T上的定義;控制的部分完全交給方法實現(xiàn)者,可以有多種不同的實現(xiàn),這就是 邏輯和控制解耦。同時,我們也可以斷定,這個設計已經(jīng)達到了代碼優(yōu)化的理論極限,不會有本質(zhì)上比它更簡潔的設計(忽略相同語義的語法差異),為什么?因為 邏輯決定了它的本質(zhì)復雜度,Comparator和Collections.sort的定義完全是邏輯的體現(xiàn),不包含任何非本質(zhì)的控制部分。

另外需要強調(diào)的是,上面講的“控制是非本質(zhì)復雜性”并不是說控制不重要,控制往往直接決定了程序的性能,當我們因為性能等原因必須采用某種控制的時 候,實際上被固化的控制策略也是一種邏輯。比如,當你的需求是“從進程虛擬地址ptr1拷貝1024個字節(jié)到地址ptr2“,那么它就是問題的定義,它就 是邏輯,這時,提供進程虛擬地址直接訪問語義的底層語言就與之完全匹配,反而是更高層次的語言對這個需求無能為力。

介紹了邏輯和控制的關系,可能很多朋友已經(jīng)開始意識到了上面二進制文件解析實現(xiàn)的問題在哪里,其實這也是 絕大多數(shù)程序不夠簡潔優(yōu)雅的根本原因:邏輯與控制耦合。上面那個消息定義表格就是不包含控制的純邏輯,我相信即使不是程序員也能讀懂它;而相應的代碼把邏輯和控制攪在一起之后就不那么容易讀懂了。

#p#

熟悉OOP和GoF設計模式的朋友可能會把“邏輯與控制解耦”與經(jīng)常聽說的“接口和實現(xiàn)解耦”聯(lián)系在一起,他們是不是一回事呢?其實,把這里所說的 邏輯和OOP中的接口劃等號是似是而非的, 而GoF設計模式***的問題就在于有意無意地讓人們以為“what就是interface, interface就是what”,很多朋友一想到要表達what,要抽象,馬上寫個接口出來,這就是潛移默化的慣性思維,自己根本意識不到問題在哪里。 其實,接口和前面提到的組件復用技術一樣,同樣受限于特定的抽象維度,它不是表達邏輯的通用方法,比如,我們無法把二進制文件格式特征用接口來表示。

另外,我們熟悉的許多GoF模式以“邏輯與控制解耦”的觀點來看,都不是***的。比如,很多時候Observer模式都是典型的以控制代邏輯,來看一個例子:

  對于某網(wǎng)頁的超鏈接,要求其顏色隨著狀態(tài)不同而變化,點擊之前的顏色是#FF0000,點擊后顏色變成#00FF00。

基于Observer模式的實現(xiàn)是這樣的:

  1. $(a).css('color', '#FF0000'); 
  2.   
  3. $(a).click(function() { 
  4.     $(this).css('color', '#00FF00'); 
  5. }); 

而基于純CSS的實現(xiàn)是這樣的:

  1. a:link {color#FF0000
  2. a:visited {color#00FF00

通過對比,您看出二者的差別了嗎?顯然,Observer模式包含了非本質(zhì)的控制,而CSS是只包含邏輯。理論上講,CSS能做的事情,JavaScript都能通過控制做到,那么為什么瀏覽器的設計者要引入CSS呢,這對我們有何啟發(fā)呢?

元語言抽象

好的,我們繼續(xù)思考下面這個問題:

  邏輯決定了程序的本質(zhì)復雜性,但接口不是表達邏輯的通用方式,那么是否存在表達邏輯的通用方式呢?

答案是:有!這就是元(Meta),包括元語言(Meta Language)和元數(shù)據(jù)(Meta Data)兩個方面。元并不神秘,我們通常所說的配置就是元,元語言就是配置的語法和語義,元數(shù)據(jù)就是具體的配置,它們之間的關系就是C語言和C程序之間 的關系;但是,同時元又非常神奇,因為元既是數(shù)據(jù)也是代碼,在表達邏輯和語義方面具有***的靈活性。至此,我們終于找到了讓代碼變得簡潔、優(yōu)雅、易理 解、易維護的***方法,這就是: 通過元語言抽象讓邏輯和控制徹底解耦

比如,對于二進制消息解析,經(jīng)典的做法是類似Google的Protocol Buffers, 把消息結(jié)構特征抽象出來,定義消息描述元語言,再通過元數(shù)據(jù)描述消息結(jié)構。下面是Protocol Buffers元數(shù)據(jù)的例子,這個元數(shù)據(jù)是純邏輯的表達,它的復雜度體現(xiàn)的是消息結(jié)構的本質(zhì)復雜度,而如何序列化和解析這些控制相關的部分被 Protocol Buffers編譯器隱藏起來了。

  1. message Person { 
  2.   required int32 id = 1
  3.   required string name = 2
  4.   optional string email = 3

元語言解決了邏輯表達問題,但是最終要與控制相結(jié)合成為具體實現(xiàn),這就是元語言到目標語言的映射問題。通常有這兩種方法:

1) 元編程(Meta Programming),開發(fā)從元語言到目標語言的編譯器,將元數(shù)據(jù)編譯為目標程序代碼;

2) 元驅(qū)動編程(Meta Driven Programming),直接在目標語言中實現(xiàn)元語言的解釋器。

這兩種方法各有優(yōu)勢,元編程由于有靜態(tài)編譯階段,一般產(chǎn)生的目標程序代碼性能更好,但是這種方式混合了兩個層次的代碼,增加了代碼配置管理的難度, 一般還需要同時配備Build腳本把整個代碼生成自動集成到Build過程中,此外,和IDE的集成也是問題;元驅(qū)動編程則相反,沒有靜態(tài)編譯過程,元語 言代碼是動態(tài)解析的,所以性能上有損失,但是更加靈活,開發(fā)和代碼配置管理的難度也更小。除非是性能要求非常高的場合,我推薦的是元驅(qū)動編程,因為它更輕 量,更易于與目標語言結(jié)合。

下面是用元驅(qū)動編程解決二進制消息解析問題的例子,meta_message_x是元數(shù)據(jù),parse_message是解釋器:

  1. var meta_message_x = { 
  2.     id: 'x', 
  3.     fields: [ 
  4.         { name: 'message_type', type: int8, value: 0x01 }, 
  5.         { name: 'payload_size', type: int16 }, 
  6.         { name: 'payload', type: bytes, size: '$payload_size' }, 
  7.         { name: 'crc', type: crc32, source: ['message_type', 'payload_size', 'payload'] } 
  8.     ] 
  9.   
  10. var message_x = parse_message(meta_message_x, data, size); 

這段代碼我用的是JavaScript語法,因為對于支持Literal的類似JSON對象表示的語言中,實現(xiàn)元驅(qū)動編程最為簡單。如果是Java 或C++語言,語法上稍微繁瑣一點,不過本質(zhì)上是一樣的,或者引入JSON配置文件,然后解析配置,或者定義MessageConfig類,直接把這個類 對象作為配置信息。

二進制文件解析問題是一個經(jīng)典問題,有Protocol Buffers、Android AIDL等大量的實例,所以很多人能想到引入消息定義元語言,但是如果我們把問題稍微變換,能想到采用這種方法的人就不多了。來看下面這個問題:

  某網(wǎng)站有新用戶注冊、用戶信息更新,和個性設置等Web表單。出于性能和用戶體驗的考慮,在用戶點擊提交表單時,會先進行瀏覽器端的驗證,比如:name 字段至少3個字符,password字段至少8個字符,并且和repeat password要一致,email要符合郵箱格式;通過瀏覽器端驗證以后才通過HTTP請求提交到服務器。

普通的實現(xiàn)是這個樣子的:

  1. function check_form_x() { 
  2.     var name = $('#name').val(); 
  3.     if (null == name || name.length <= 3) { 
  4.         return { status : 1, message: 'Invalid name' }; 
  5.     } 
  6.   
  7.     var password = $('#password').val(); 
  8.     if (null == password || password.length <= 8) { 
  9.         return { status : 2, message: 'Invalid password' }; 
  10.     } 
  11.   
  12.     var repeat_password = $('#repeat_password').val(); 
  13.     if (repeat_password != password.length) { 
  14.         return { status : 3, message: 'Password and repeat password mismatch' }; 
  15.     } 
  16.   
  17.     var email = $('#email').val(); 
  18.     if (check_email_format(email)) { 
  19.         return { status : 4, message: 'Invalid email' }; 
  20.     } 
  21.   
  22.     ... 
  23.   
  24.     return { status : 0, message: 'OK' }; 
  25.   

#p#

上面的實現(xiàn)就是按照組建復用的思想封裝了一下檢測email格式之類的通用函數(shù),這和剛才的二進制消息解析非常相似,沒法在不同的表單之間進行大規(guī)模復用,很多細節(jié)都必須被重復編寫。下面是用元語言抽象改進后的做法:

  1. var meta_create_user = { 
  2.     form_id : 'create_user', 
  3.     fields : [ 
  4.         { id : 'name', type : 'text', min_length : 3 }, 
  5.         { id : 'password', type : 'password', min_length : 8 }, 
  6.         { id : 'repeat-password', type : 'password', min_length : 8 }, 
  7.         { id : 'email', type : 'email' } 
  8.     ] 
  9. }; 
  10.   
  11. var r = check_form(meta_create_user); 

過定義表單屬性元語言,整個邏輯頓時清晰了,細節(jié)的處理只需要在check_form中編寫一次,完全實現(xiàn)了“簡短、優(yōu)雅、易理解、以維護”的目 標。其實,不僅Web表單驗證可以通過元語言描述,整個Web頁面從布局到功能全部都可以通過一個元對象描述,完全將邏輯和控制解耦。此外,我編寫的用于 解析命令行參數(shù)的lineparser.js庫也是基于元語言的,有興趣的朋友可以參考并對比它和其他命令行解析庫的設計差異。

***,我們再來從代碼長度的角度來分析一下元驅(qū)動編程和普通方法之間的差異。假設一個功能在系統(tǒng)中出現(xiàn)了n次,對于普通方法來講,由于邏輯和控制的 耦合,它的代碼量是n * (L + C),而元驅(qū)動編程只需要實現(xiàn)一次控制,代碼長度是C + n * L,其中L表示邏輯相關的代碼量,C表示控制相關的代碼量。通常情況下L部分都是一些配置,不容易引入bug,復雜的主要是C的部分,普通方法中C被重復 了n次,引入bug的可能性大大增加,同時修改一個bug也可能要改n個地方。所以,對于重復出現(xiàn)的功能,元驅(qū)動編程大大減少了代碼量,減小了引入bug 的可能,并且提高了可維護性。

總結(jié)

《人月神話》的作者Fred Brooks曾在80年代闡述了它對于軟件復雜性的看法,即著名的No Silver Bullet。他認為不存在一種技術能使得軟件開發(fā)在生產(chǎn)力、可靠性、簡潔性方面提高一個數(shù)量級。我不清楚Brooks這一論斷詳細的背景,但是就個人的開發(fā)經(jīng)驗而言,元驅(qū)動編程和普通編程方法相比在生產(chǎn)力、可靠性和簡潔性方面的確是數(shù)量級的提升,在我看來它就是軟件開發(fā)的銀彈!

原文鏈接:http://coolshell.cn/articles/10652.html#more-10652

責任編輯:陳四芳 來源: 酷殼網(wǎng)
相關推薦

2017-06-23 08:45:02

存儲技術復雜性

2012-09-19 13:18:37

復雜設計UI設計

2012-12-26 10:53:26

2009-01-20 15:23:33

存儲安全密鑰數(shù)據(jù)保護

2019-05-13 15:47:29

Kubernetes云計算云復雜性

2014-08-21 08:54:03

2024-04-10 11:56:33

2014-12-01 09:41:25

2019-08-21 13:24:25

KubernetesHadoop容器

2019-11-23 23:30:55

Python數(shù)據(jù)結(jié)構時間復雜性

2019-07-29 12:35:15

云計算復雜性云計算平臺

2020-06-15 09:58:23

云計算云安全數(shù)據(jù)

2020-03-24 09:52:34

大數(shù)據(jù)IT技術

2018-05-16 07:34:52

NFV虛擬化數(shù)據(jù)中心

2019-03-18 09:00:04

Linux密碼cracklib

2018-07-31 14:47:51

Kubernetes開發(fā)應用程序

2015-10-27 10:06:16

因素數(shù)據(jù)復雜

2015-04-16 15:06:34

2022-03-09 10:51:19

云安全網(wǎng)絡安全

2024-04-03 09:03:05

點贊
收藏

51CTO技術棧公眾號