摘要:軟件本身不是目的,人類的需求才是目的,而軟件只是達到目的的手段。
軟件的本質在於控制復雜性,這個復雜性並非來自於計算機,也並非來自於現實世界,而是來自於人類的思維和知識體系。
軟件被使用的廣泛性,在於它所滿足的人類需求的廣泛性。
什么是軟件?
從一個簡單的例子說起,比如我想計算兩個數的和,於是寫下這樣的python代碼
print a + b
但是,這段代碼是我的最終目的嗎?顯然不是,我需要把它在計算機上實際運行,並賦予a和b實際的數值。也許我是在水果,買了5塊錢的蘋果和10塊錢的香蕉,然后計算一共需要支付多少錢。
可以看出,軟件是我們為了達到某種目的,而指揮計算機如何去完成這個任務的一系列指令。我們的目的是滿足某種需求,而軟件只是一個手段,顯然完全可以通過其他的手段完成這一任務。
SICP中指出,計算機科學和計算機其實並沒有本質聯系,而是人類知識的一種組織形式,重在對過程性知識的形式化。如同數學是對說明性知識的形式化。也就是說,計算機科學是關於“如何完成某某任務”的知識的記錄工具。
看到這里你也許會覺得我說了半天廢話——因為人類所有的生產活動,都是為了滿足人類的某種需求。不過這一觀點將是本文的基本出發點。接下來我們簡單分析一下軟件作為一種手段,為什么存在,以及有什么特點。
為什么要有軟件?
如果軟件是為了滿足人類的某些需求,那么軟件存在的原因就很直接——因為計算機不能直接滿足人類的需求。其中的距離來自於計算機的通用性、單一性和人類需求的特定性、多樣性。從原理上講,計算機只需要0和1兩個符號,以及NAND(與非)或者NOR(或非)一種運算就夠了。而人類的需求則千差萬別。
但是,前面說到,滿足需求有多重方式,比如人工計算,或者直接使用硬件搭建電路完成某些功能(而這實際上也是電子領域早期的方式)。那么,人類為什么選擇軟件這種手段呢?其實很簡單,原因和其它的手段一樣,不外乎成本。從經濟學角度,成本,也就是人類的工作時間。(金錢,可以看做你從別人那里買時間)。經濟的一個基本規律是規模效應,生產規模越大,單位成本越低。硬件做的越通用,就越適合大規模生產,就越能降低成本。當然,這一切都要建立在下面的基礎之上:
生產軟件要比生產硬件的效率高。
軟件易於改動,另外有一個重要特點——它一旦被生產出來,就可以幾乎零成本的進行重復利用。這是一個能極大提高全人類生產力的方式。試想一下,假設你能寫一個軟件炒一盤魚香肉絲,那么全世界的人無論誰想再生產一盤魚香肉絲,只需要簡單的調用一下這個軟件。很多軟件最初並不是寫出來給大家用的,而是為了解決自己的實際問題,比如為了簡化工作流程,開發某個產品,驗證某個科研思路等等,軟件只是個副產品——這么說也許不太恰當,應當說軟件是解決問題過程的完整記錄。而完成任務之后,軟件就可以被別人復用了——全人類只需要一個人造輪子,完全消除了重復勞動,多么高效!
所以下面要談談代碼復用的問題。
軟件復用
好的軟件不會消失,而會被移植到新的平台上。
—— 《Linux/Unix設計思想》
復用軟件的成本比生產軟件要低的多。所以,軟件復用是提高生產率的重要手段。如何更好的復用軟件,以及如何編寫容易復用的軟件?要研究這個問題,需要先考慮另一個問題:軟件為什么可以復用?
仍然從基本觀點出發:軟件是滿足人類需求的手段。所以,軟件的復用,實際上是人類需求的重復性。考慮兩個需求,a)計算一組實數的平均數;b)計算高一3班全體同學的2015年數學期末考試平均成績。顯然,需求a是一個更廣泛的需求,並且可以預見這一需求在未來會一再的出現。而需求b則是一個很特定的需求,它只會在2015年期末出現一次。如果有兩個軟件分別滿足兩個需求,那么滿足需求a的軟件將會一再的被重用,而滿足需求b的軟件將會爛在硬盤上。
所以,為什么“好的軟件”會被移植到新平台上?因為它們滿足的是人類重復需要的需求,無論技術怎么發展,只要這些需求存在,相應的軟件就會存在,只是以不同的形式出現。
所以,為了編寫能夠復用的軟件,我們需要關注的是需求。然而,不幸的是,實際中的需求往往是特定的、易變的。比如,公司要在今年雙十一推出一項特定的活動,那么單純為這個活動所編寫的軟件,在活動結束后就失去了作用。
要解決這個問題,需要對軟件進行層次划分。不同層次的軟件,通用性和特定性不同。底層的軟件單一、通用,重用度高;而上層的軟件則特定性高,生命周期短。為了研究這個問題,需要考察另外的問題——復雜性與抽象。
復雜性
軟件的首要技術使命是管理復雜度。
——《代碼大全》
為什么會出現復雜性這種問題?仍然從基本觀點出發——復雜性來自於“計算機僅能提供0、1運算”和人類需求之間的距離。人類的一個需求,可能需要成千上萬的0、1運算才能完成,而人腦——很遺憾——只能同時處理7個左右的事物。可以說,軟件的復雜性,實際上來自於人類需求的復雜性以及人腦處理能力的局限。
實際上,這種復雜性不僅出現與軟件領域,而是人類所有知識和概念體系的共同特點。在SICP中,引用了洛克的一段話,對此有提綱挈領的論述:
心智的活動,除了盡力產生各種簡單的認識之外,主要表現在如下三個方面:
1)將若干簡單認識組合為一個復合認識,由此產生出各種復雜的認識。
2)將兩個認識放在一起對照,不管它們如何簡單或者復雜,在這樣做時並不將它們合而為一。由此得到有關
它們的相互關系的認識。
3)將有關認識與那些在實際中和它們同在的所有其它認識隔離開,這就是抽象,所有具有普遍性的認識都是這樣得到的。
—— John Locke 1690
軟件開發的過程,就是將簡單元素組合為一個復雜元素,再將復雜元素抽象為簡單元素,這樣一個不斷“組合->抽象->組合->抽象 ……”的迭代過程。
抽象
基於上面的討論可以看出,軟件開發過程中的抽象,本質上是人腦中概念的抽象。這種概念的結構映射到代碼上,就成為軟件。那么,怎么樣是一個好的抽象?好的抽象應該在錯綜復雜的事物中分解出不變的部分,將它和易變動的部分隔離開。而軟件的變動,則取決於人類需求的變動。也就是說,一個好的抽象,也就是一個好的概念,應當反映出人類需求中穩定不變的部分。這樣的軟件,就會在空間和時間上達到更廣泛的復用。
無論過程式、面向對象還是函數式編程,都是提供了一種看待現實世界、進行抽象的方法。如果我們明白了軟件結構設計的本質在於為問題設計一個良好的抽象概念體系,那么這些方法之間其實並無本質區別,只是各自具有優缺點的具體的抽象辦法而已。
下面我們來看一下計算機科學領域所采用的一些最基礎的抽象。
硬件層面
為什么硬件也算抽象呢?因為我們討論的起點是布爾代數,這是現代電子計算機的數學基礎。硬件層面對0、1運算進行了第一層抽象,即一系列的CPU指令,將原本只能進行簡單“與、或、非”運算的概念,抽象為提供一系列字節、整數、浮點數、地址等概念。
計算機語言
計算機語言是在硬件層之上的第一層抽象,而本身又分為不同的層次。計算機語言是一種概念抽象方式,所以它是為人設計的,而不是為計算機設計的——因為,如果要指揮計算機做事情,我們只需要“0”、“1”兩個符號就夠了。計算機語言將硬件提供的功能抽象為若干數據類型的運算、流控制結構、函數等概念,以組成更復雜的軟件。可以說,編譯器是最大的一個軟件復用的例子。然而,這兒不妨思考一個問題:在這個抽象的過程中,是否丟失了什么?不同語言的抽象方式是不一樣的,由此所能提供的功能也是有所差別。
操作系統
操作系統是又一種抽象方式,它把一些常用的操作封裝為系統調用,這些操作是軟件開發中大量的重復性的需求。
各種軟件包、框架等等
軟件包、框架等不是計算機語言的一部分,而是使用計算機語言編寫的軟件模塊。然而從抽象的角度來說,使用語言構建的模塊,和語言本身提供的特性,並無本質區別。同一種數據結構,在某種語言里屬於語言本身的一部分,而在另一種語言里則需要使用軟件包。然而無論那種情況,並沒有本質區別。
業務代碼
業務代碼是最終完成現實任務的代碼,它們最具體、最不具有一般性,所以過時最快。大部分公司的硬盤里沉睡着這類代碼。
再論軟件復用
越通用的軟件,可復用性越強,但是不能直接用來解決實際問題;而滿足特定需求的軟件,又不具有通用性,如果沒有將其中一般性的模塊抽象出來,那么將很難復用。既然每個人有自己特定的需求,而軟件開發者不可能為每個人開發一套軟件,這就要求任何軟件都要提供“組合-抽象”的能力,供軟件使用者對滿足自己的特定需求的功能進行抽象——這種功能可以叫個性化,或者二次開發,或者插件等等。下面舉幾個具體例子:
-
linux系統提供了一系列
ls
,grep
等工具,它們是通用的。我現在有個需求,檢查某服務的log中是否有error,如果有則向某個特定郵箱發送郵件。當然我可以依次手動運行這些通用工具,但是更好的方法顯然是寫一個shell腳本,並命名為check_log_err.sh
。這樣每次只要運行這一個命令就夠了。這就是一種抽象。 -
編輯word文檔時,有個特殊需求:將每段的第一個字的字體增大一號、加粗並設置為紅色背景。顯然我也可以每段每段的進行操作。更簡單的方法是錄制一個宏,然后在每段上回放這個宏。宏提供了一種抽象手段,將“第一個字字體增大一號、加粗並設置為紅色背景”這幾個操作抽象為一個操作。從這里大家大概已經能看出,從抽象的角度上講,宏和腳本沒有本質區別,都是實現抽象的手段。實際上,word的宏也是以VBA代碼形式保存的。只是對於很多非計算機專業用戶,寫代碼是不方便的,而宏用起來比較直觀。
-
用瀏覽器下載文件的時候,它每次都會詢問存放位置。我想把它放在某個download目錄下面,於是設置了一下下載路徑,這樣就不需要每次設置存放位置了。這也是一種抽象:通過配置文件,將通用的“下載”、“存盤”兩個功能組合起來,並提供具體參數,來滿足我的特定需求。
-
還有各種插件,比如chrome、sublime text、foobar等,提供了二次開發的能力,這樣用戶可以針對自己的特定需求進行開發。從這個意義上說,軟件的二次開發功能做的越好,生命力就越強。
從抽象的角度講,任何軟件都應當具有二次開發的能力:使用者將軟件提供的若干個相對通用的功能,組合、抽象為一個功能,來滿足自己的特定需求。這種組合、抽象的功能越強大、越方便,軟件的生命力就越強。
小結
本文的主要觀點總結如下:軟件不是目的,而是手段。人的需求才是目的。在布爾代數和人的需求之間,存在着巨大的距離;而人腦處理能力是有限的,這就產生了復雜性。控制復雜性是一個“組合->抽象”的不斷迭代過程。良好的抽象應當滿足人類普遍的、長期的需求,這樣的抽象以及相應的軟件將實現更大程度上的復用。
擴展:關於人工智能
在這個基礎上,筆者思考一個問題:未來是否會出現人類理解不了的人工智能?沿着本文的思路,不妨這樣考慮:人工智能建立在計算機的基礎上,那么它就不能超越布爾代數;而人工智能也是一種軟件,它的目的是滿足人類的某種需求。但是,中間的過程可能是不一樣的,也就是說,人工智能可能會采取和人類不同的抽象方式,或者它根本就不需要抽象。大量的中間層次,可能是人腦不能理解的。但是至少從基礎的層面上,人工智能只要還構建在圖靈機之上,那么它的基礎——0、1運算至少還是能被人腦理解的。