如果要問計算機領域中哪個思想對我的幫助最大,那么應該就是抽象了。本文主要介紹一下抽象(abstraction)以及它和計算機課程的關系。
第一次接觸抽象是我大學低年級學數據結構的時候,記得很清楚當時學一個概念叫抽象數據類型(abstract data type),大概意思就是一個數據結構,接口是一回事,實現是另一回事,比如棧,作為使用者你只需要知道它有push、pop、isEmpty等方法,但它的底層實現到底是array還是linked list,你是不需要知道的。
用通俗一點的話說,抽象就是你好好做自己的事,以及知道別人能幫你干什么事,至于別人是如何幫你完成的,你沒必要知道。
這個簡單的思想大大提高了開發者的效率,讓開發者只專注于要解決的問題,而不是一些細枝末節的事。
孟巖在它的博客里也提到過“關注重點”這件事,雖然沒有明顯地提及抽象二字,但他的意思和抽象表達的意思是一樣的:
我主張,在具備基礎之后,學習任何新東西,都要抓住主線,突出重點。對于關鍵理論的學習,要集中精力,速戰速決。而旁枝末節和非本質性的知識內容,完全可以留給實踐去零敲碎打。
原因是這樣的,任何一個高級的知識內容,其中都只有一小部分是有思想創新、有重大影響的,而其它很多東西都是瑣碎的、非本質的。因此,集中學習時必須把握住真正重要那部分,把其它東西留給實踐。對于重點知識,只有集中學習其理論,才能確保體系性、連貫性、正確性,而對于那些旁枝末節,只有邊干邊學能夠讓你了解它們的真實價值是大是小,才能讓你留下更生動的印象。如果你把精力用錯了地方,比如用集中大塊的時間來學習那些本來只需要查查手冊就可以明白的小技巧,而對于真正重要的、思想性東西放在平時零敲碎打,那么肯定是事倍功半,甚至適得其反。
最近發現,計算機專業的課程完全可以用抽象來解釋:每一門課想做的事就是利用下層提供的接口,實現功能,然后再給上層提供接口。這樣一層一層的抽象就構成了所有的專業課。
舉一個例子來說明這個從上往下的層級抽象是如何組織的。
先來看最高層,問題。剛學編程的時候,會先學一門課導論課或者編程入門課,我當時的入門課叫做“程序設計”,課程內容是介紹一些問題,然后介紹一點編程語言的知識,作業是一些編程問題,比如八皇后、素性測試之類的比較常規的編程題。這門課的目的一般都是介紹“問題”的,介紹不用分支科學有哪些有挑戰的問題,讓學生對計算機專業有一個感性的認識,而不是對特定編程語言或者算法的學習,所以這類課程一般用python來編程。
再往下一層,算法和數據結構。這一層的目的是學習/實現各種算法/數據結構,提供給上層抽象。比如排序,問題解決者只要知道這里應該用快速排序,而不是選擇排序,而把快速排序的實現留給這一層的開發者,從而使各種優化都可以對上層透明,比如小數組變插入排序、中位數取pivot、三向快速排序等,這些優化調用者完全不必要知道,他只需要知道:哇,這個庫提供的快速排序還真快。
再往下一層,語言層,畢竟所有算法都要由某一門語言來實現。這一層的存在使得算法的設計可以脫離具體的語言。不同語言又提供了不同的抽象,像函數式語言就比命令式語言的抽象級高,更高的抽象級意味著更加專注問題本身(不需要考慮內存布局、CPU使用等)以及更少的代碼量。
再往下一層,編譯器/解釋器。我們編寫代碼是用高級語言,而cpu上執行的是機器碼,所以這個抽象層幫我們做了這個轉化。這個抽象層的好處是,高級代碼的編寫者完全不需要知道這個程序所運行的操作系統和硬件平臺,任何有該語言編譯器/解釋器的機器,程序都可以跑(從而實現了跨平臺)。有這一層的好處是,應用層開發者可以不用知道這個由高級語言到機器碼的轉化具體是怎么實現的,畢竟編譯器優化的編寫和優化完全是一個團隊的工作量,開發者關注問題的解決,編譯器負責轉化出高效的機器碼,各干各的,這正是抽象的重點。
再往下一層,操作系統。OS向開發者抽象了硬件(CPU、內存、Disk、NIC等),并且以syscall的形式向用戶提供服務。OS的設計是最能體現抽象的,虛擬內存和進程讓程序以為自己獨占著內存和CPU,同時隔離了不同進程以防惡意進程;文件系統讓用戶可以方便地讀取存儲數據,而不需要直接操作底層的硬盤;文件描述符抽象了底層的設備(pipe?file?device?socket?)。
再往下一層,ISA(Instruction set architecture),俗稱軟件與硬件的接口。這個俗稱是非常形象的。指令集架構,說得簡單點就是機器碼,也可以理解為一個協議。ISA標準制定者指定一套指令集(比如x86、PowerPC、SPARC),然后編譯器開發者需要根據這個標準/協議來編寫對應的編譯器;CPU制造商需要根據這個標準/協議來制造出支持這套ISA的CPU(比如intel的CPU支持x86/x86_64)。也就是說,軟件/硬件都依照這個ISA來設計,那么就可以對接了。
再往下一層,組成原理和體系結構。這一層要做的事情是借助數字電路給它提供的抽象(組合電路和鎖存器),來設計一個能實現某種ISA的CPU,讓編譯器生成的指令可以在此CPU上運行。大學里一般會開一門叫“計算機組成原理”的課,一開始學單周期CPU的實現(取指、譯碼、執行......),為了提高效率又提出了流水線的實現。為了發掘更高的效率,之后又會學一門叫“計算機體系結構”的課,這門課的目的是為了發掘更高的并行,從而制造出更快的CPU。那這一層是如何用數字電路提供的抽象的?舉兩個典型例子:一、CPU為了做計算會有ALU模塊,而ALU模塊正是一個組合電路(輸入確定那么輸出確定);二、在流水線寄存器中每一個時鐘上升沿都會保存輸入的值,在這個時鐘周期內組合電路會根據這個新值計算出結果傳輸到下一級流水線寄存器的輸入,等待下一個時鐘上升沿的到來,這里的流水線寄存器正是某種鎖存器的實現,而CPU開發者并不需要這個數字電路模塊是怎么實現。
再往下一層,數字電路。這門課的目的是教學生如何利用基本的門電路(與非或門)來實現一些高級的功能(譯碼器、多路復用器、鎖存器、時序電路......),然后給上層提供功能。上數字電路是一個用磚搭房子的過程,由基本的門電路開始,慢慢構造出復雜的電路。數字電路不需要關心基本的門電路是如何實現的,因為這正是模擬電路向上層提供的抽象。
再往下一層,模擬電路。這一層實現了與或非等基本門電路。比如非門、與非門、或非門都可以通過若干個p/n型MOS晶體管構成,而與門可以通過連接一個與非門和非門構成,或門可以通過連接一個或非門和非門構成。很多同學都覺得模電對于計算機的同學不必要學,而我認為相反,它是你構建整個計算機抽象層級的基石。
再往下一層,就不是計算機領域研究的事了。
當然,還有很多專業課我并沒有提及,比如網絡,它是OS提供的功能,以文件描述符的形式提供給用戶使用;在網絡協議棧實現的細節里,又分了好幾層抽象,這就是我們熟知的OSI七層網絡模型(有時候被抽象為五層:Application、Transport、Network、DataLink、Physical)。各位可以自己再回憶一下大學里上過哪些專業課,以及它應該放在抽象的哪一層上。
仔細一想會發現,幾乎所有的技術書籍都嘗試在解決某一層上的問題,利用下層提供的抽象,然后向上層提供功能。
讓我們脫離計算機領域,再往高一點看,會發現整個計算機領域就是在為別的領域提供功能、并隱藏了細節:醫療、交通、餐飲、支付......
抽象,讓生活變得更簡單了一點。
(原文地址:http://lifeofzjs.com/blog/2016/03/06/cs-courses-and-abstraction/)