程式設計師程式設計能力層次模型

良少發表於2015-03-09

前言

程式設計師的程式設計技能隨著經驗的積累,會逐步提高。我認為程式設計能力可以分為一些層次。

下面通過兩個維度展開程式設計能力層次模型的討論。

一個維度是程式設計技能層次,另一個維度是領域知識層次。

程式設計技能層次

程式設計技能層次,指的程式設計師設計和編寫程式的能力。這是程式設計師的根本。

0段—非程式設計師:

初學程式設計者,遇到問題,完全是懵懵懂懂,不知道該怎麼程式設計解決問題。也就是說,還是門外漢,還不能稱之為“程式設計師”。計算機在他面前還是一個神祕的黑匣子。

1段—基礎程式設計師:

學習過一段時間程式設計後,接到任務,可以編寫程式完成任務。

編寫出來的程式碼,正常情況下是能夠工作的,但在實際執行中,碰到一些特殊條件就會出現各類BUG。也就是說,具備了開發Demo軟體的能力,但開發的軟體真正交付給客戶使用,恐怕會被客戶罵死。

程式設計師程式是寫好了,但到底為什麼它有時能正常工作,有時又不行,程式設計師自己也不知道。

執行中遇到了bug,或者需求改變,需要修改程式碼或者新增程式碼,很快程式就變得結構混亂,程式碼膨脹,bug叢生。很快,就連最初的開發者自己也不願意接手維護這個程式了。

2段—資料結構:

經過一段時間的程式設計實踐後,程式設計師會認識到“資料結構+演算法=程式”這一古訓的含義。他們會使用演算法來解決問題。進而,他們會認識到,演算法本質上是依附於資料結構的,好的資料結構一旦設計出來,那麼好的演算法也會應運而生。

設計錯誤的資料結構,不可能生長出好的演算法。

記得某一位外國先賢曾經說過:“給我看你的資料結構!”

3段—物件導向:

再之後,程式設計師就會領略物件導向程式設計的強大威力。大多數現代程式語言都是支援物件導向的。但並不是說,你使用物件導向程式語言程式設計,你用上了類,甚至繼承了類,你就是在寫物件導向的程式碼了。

我曾經見過很多用Java,Python,Ruby寫的程式導向的程式碼。

只有你掌握了介面,掌握了多型,掌握了類和類,物件和物件之間的關係,你才真正掌握了物件導向程式設計技術。

就算你用的是傳統的不支援物件導向的程式語言,只要你心中有“物件”,你依然可以開發出物件導向的程式。

如,我用C語言程式設計的時候,會有意識的使用物件導向的技巧來編寫和設計程式。用struct來模擬類,把同一類概念的函式放在一起模擬類。如果你懷疑用C語言是否能編寫出物件導向的程式碼,你可以看一下Linux核心,它是用C語言編寫的,但你也可以看到它的原始碼字裡行間散發出的濃濃的“物件”的味道。

真正掌握物件導向程式設計技術並不容易。

在我的技術生涯中,有兩個坎讓我最感頭疼。

一個坎是Dos向Windows開發的變遷過程中,框架的概念,很長一段時間我都理解不了。Dos時代,都是對函式庫的呼叫,你的程式主動呼叫函式。Windows時代,則換成了框架。就算是你的main程式,其實也是被框架呼叫的。UI執行緒會從作業系統獲取訊息,然後傳送給你的程式來處理。Java程式設計師熟悉的Spring框架,也是這樣一個反向呼叫的框架。

現在因為“框架”這個術語顯得很高大上,因此很多“類庫”/“函式庫”都自稱為“框架”。在我看來這都是名稱的濫用。

“類庫”/“函式庫”就是我寫的程式碼呼叫它們。

“框架”就是我註冊回撥函式到框架,框架來呼叫我寫的函式。

另一個坎就是物件導向。很長一段時間我都不知道應該怎麼設計類和類之間的關係,不能很好的設計出類層次結構來。

我記得當時看到一本外國大牛的書,他講了一個很簡單、很實用的物件導向設計技巧:“敘述問題。然後把其中的名詞找出來,用來構建類。把其中的動詞找出來,用來構建類的方法”。雖然這個技巧挺管用的,但也太草根了點,沒有理論依據,也不嚴謹。如果問題敘述的不好,那麼獲得的類系統就會是有問題的。

掌握物件導向思想的途徑應該有很多種,我是從關聯式資料庫中獲得了靈感來理解和掌握物件導向設計思想的。

在我看來,關聯式資料庫的表,其實就是一個類,每一行記錄就是一個類的例項,也就是物件。表之間的關係,就是類之間的關係。O-Rmapping技術(如Hibernate),用於從物件導向程式碼到資料庫表之間的對映,這也說明了類和表確實是邏輯上等價的。

既然資料庫設計和類設計是等價的,那麼要設計物件導向系統,只需要使用關聯式資料庫的設計技巧即可。

關聯式資料庫表結構設計是很簡單的:

1、識別表和表之間的關係,也就是類和類之間的關係。是一對一,一對多,多對一,還是多對多。這就是類之間的關係。

2、識別表的欄位。一個物件當然有無數多的屬性(如,人:身高,體重,性別,年齡,姓名,身份證號,駕駛證號,銀行卡號,護照號,港澳通行證號,工號,病史,婚史etc),我們寫程式需要記錄的只是我們關心的屬性。這些關心的屬性,就是表的欄位,也就是類的屬性。“弱水三千,我取一瓢飲”!

4段—設計模式

曾經在網上看到這樣一句話:“沒有十萬行程式碼量,就不要跟我談什麼設計模式”。深以為然。

記得第一次看Gof的設計模式那本書的時候,發現雖然以前並不知道設計模式,但在實際程式設計過程中,其實還是自覺使用了一些設計模式。設計模式是程式設計的客觀規律,不是誰發明的,而是一些早期的資深程式設計師首先發現的。

不用設計模式,你也可以寫出滿足需求的程式來。但是,一旦後續需求變化,那麼你的程式沒有足夠的柔韌性,將難以為繼。而真實的程式,交付客戶後,一定會有進一步的需求反饋。而後續版本的開發,也一定會增加需求。這是程式設計師無法迴避的現實。

寫UI程式,不論是Web,Desktop,Mobile,Game,一定要使用MVC設計模式。否則你的程式面對後續變化的UI需求,將無以為繼。

設計模式,最重要的思想就是解耦,通過介面來解耦。這樣,如果將來需求變化,那麼只需要提供一個新的實現類即可。

主要的設計模式,其實都是物件導向的。因此,可以認為設計模式是物件導向的高階階段。只有掌握了設計模式,才能認為是真正徹底掌握了物件導向設計技巧。

我學習一門新語言時(包括非面嚮物件語言,如函數語言程式設計語言),總是會在瞭解了其語法後,看一下各類設計模式在這門語言中是如何實現的。這也是學習程式語言的一個竅門。

5段–語言專家:

經過一段時間的程式設計實踐,程式設計師對某一種常用的程式語言已經相當精通了。有些人還成了“語言律師”,擅長向其他程式設計師講解語言的用法和各種坑。

這一階段的程式設計師,常常是自己所用語言的忠實信徒,常在社群和論壇上和其他語言的使用者爭論哪一種語言是最好的程式語言。他們認為自己所用的語言是世界上最好的程式語言,沒有之一。他們認為,自己所用的程式語言適用於所有場景。他們眼中,只有錘子,因此會把所有任務都當成是釘子。

6段–多語言專家:

這一個階段的程式設計師,因為工作關係,或者純粹是因為對技術的興趣,已經學習和掌握了好幾種程式語言。已經領略了不同程式語言不同的設計思路,對每種語言的長處和短處有了更多的瞭解。

他們現在認為,程式語言並不是最重要的,程式語言不過是基本功而已。

他們現在會根據不同的任務需求,或者不同的資源來選擇不同的程式語言來解決問題,不再會因為沒有使用某一種喜愛的程式語言開發而埋怨。

程式語言有很多種流派和思想,有一些程式語言同時支援多種程式設計正規化。

靜態型別程式設計正規化

採用靜態型別程式設計正規化的程式語言,其變數需要明確指定型別。代表語言:C,C++,Pascal,Objective-C,Java,C#,VB.NET,Swif,Golang。

這樣做的好處是:

1、編譯器可以在編譯時就能找出型別錯誤。

2、編譯器編譯時知道型別資訊,就可以提高效能。

這種正規化認為,程式設計師肯定知道變數的型別,你丫要是不知道變數的型別,那你就別混了!編譯時,程式會報錯。

Swift和Go語言都是靜態型別程式語言,但它們都不需要明確指定型別,而是可以通過推斷由編譯器自動確定其型別。

動態型別程式設計正規化

採用靜態型別程式設計正規化的程式語言,其變數不需要明確指定型別。任意變數,可以指向任意型別的物件。代表語言:Python,Ruby,JavaScript。

動態型別的哲學可以用鴨子型別(英語:ducktyping)這個概念來概括。JamesWhitcombRiley提出的鴨子測試可以這樣表述:“當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。”

這種正規化認為,程式設計師肯定知道變數的型別和它支援的方法和屬性,你丫要是不知道變數的型別,那你就別混了!執行時程式會崩潰!程式崩潰怨誰?怨你自己唄,你不是合格的程式設計師!

動態型別的好處是:

不需要明確定義介面和抽象型別。只要一個型別支援需要的方法和屬性,那麼就OK。程式會相當靈活和簡單。C++,Java,C#視之為命脈的介面/基類,在動態語言這裡都視如無物!

缺點是:

1、如果型別不對,編譯器也無法找到錯誤,而是執行時程式崩潰。

2、因為編譯器不知道變數的型別,因此無法優化效能。

物件導向程式設計正規化

物件導向程式設計正規化,從上世紀70年代末開始興起。它支援類和類的例項作為封裝程式碼的模組。代表語言:Smalltalk,C++,Objective-C,Java,C#,VB.NET,Swift,Go,Python,Ruby,ActionScritp,OCaml.

早期程式語言都是程式導向的。就是順序,條件,迴圈,構成一個個函式。隨著程式碼規模的增大,人們發現有必要對程式碼進行模組化。一個概念對應的程式碼放在一個檔案中,這樣便於併發開發和進行程式碼管理。

人們還發現了“程式=資料結構+演算法”的規律。因此,一個概念對應的資料結構和函式應該放在一個檔案中。這就是類的概念。

物件導向程式設計正規化,確實極大地提高了生產效率,因此得到了廣泛的應用,因此在語言層面支援物件導向程式設計正規化的語言是極多的。

C語言儘管在語言層面上並不支援物件導向程式設計正規化,但現代的C語言開發都會應用物件導向的模組化思想,把同一類的資料結構和函式放在一個檔案中,採用類似的命名方式。

畢竟C語言沒有在語言層面上支援物件導向,因此就有很多程式設計師想給C語言新增物件導向支援。其中的代表是C++和Objective-C。

C++是一種新的語言,但大部分語言元素是和C相容的。

Objective-C是完全相容的C的。Objective-C是給C新增了薄薄的一層語法糖以支援介面(就是其他語言的類)和協議(就是其他語言的介面)。甚至,Objective-C一開始的實現,就是一個C語言的預編譯器。Objective-C坦白講,除了新增的語法不太符合C流外,實際上其物件導向系統設計是相當精妙的。賈伯斯早年慧眼識珠,把Objective-C收人囊中,因為封閉於Apple/NextStep系統內,因此少有人知。隨著iOs系統的普及,Objective-C近幾年才名滿天下。

函數語言程式設計正規化

函數語言程式設計正規化,是一些數學家發明的程式語言,他們認為程式就是數學函式嘛。代表語言:Lisp,Erlang,JavaScript,OCaml,Prog。

有很多大牛極力鼓吹過函數語言程式設計語言,認為其極具革命性。但我認為他們過高估計了函數語言程式設計正規化的威力,我並不認為函數語言程式設計正規化相對於物件導向程式設計正規化有何高明之處。

函數語言程式設計語言,核心就是函式,它們沒有Class類的概念。但它的函式又不是傳統程式導向語言的函式,它的函式支援“閉包”的概念。

在我看來,函數語言程式設計語言的函式,也就是“閉包”,說白了,其實就是“類”。程式語言發展到今天,就是需要模組化,就是需要把“資料結構”和“演算法”結合起來。不論何種語言,不把它們結合起來的程式設計方式,都是沒有出路的。

物件導向程式語言,用類把“資料結構”和“演算法”結合起來。類的核心是“資料結構”,也就是其“屬性”,而不是“演算法”,其“函式”。在類中,是函式依附於屬性。

而函數語言程式設計語言,用閉包把“資料結構”和“演算法”結合起來。是函式能夠抓取外部的欄位。是“屬性”依附於“函式”。

“類”本質上和“閉包”是等價的。現在很多物件導向程式語言都加上了對閉包的支援。觀察其程式碼,我們可以發現,它們實際上都是用“類”來實現“閉包”的。

“類”和“閉包”誰更易用?明顯是“類”。

而“閉包”更簡潔一些,因此“閉包”在物件導向程式語言中常用來替換匿名類。只有一個函式的類,寫成一個類太麻煩,不如寫成閉包,更加簡潔。

吐槽一下OCaml語言,其前身Caml語言本身是一種挺好的函式式語言,硬生生新增了一套完整的物件導向機制,同時支援物件導向和函數語言程式設計正規化,很容易像C++一樣腦裂的。

也有很多物件導向語言控看著JavaScript嫌煩,總是想把物件導向支援新增到JavaScript上。ActionScript就是其中一種嘗試。我用過,真的是和Java沒多少區別了。

再吐槽一下ExtJS。當初選型Web前端開發框架時比較了ExtJS和JQuery。

ExtJS明顯是Java高手開發的,硬生生用JavaScript模擬Swing的設計思想,搞了一套UI庫。

JQuery開發者明顯是領悟了JavaScript的函數語言程式設計正規化,依據JavaScript的動態函數語言程式設計語言的特點打造了一套UI庫,立刻秒殺ExtJS。

由ExtJS和JQuery的故事,我們可以看到多語言程式設計能力是多麼的重要。ExtJS的作者精通並喜愛Java,因此他把手術刀JavaScript當做錘子Java使,一通亂敲,費力不討好。

函數語言程式設計語言,還有尾遞迴等一些小技巧。尾遞迴可以不用棧,防止遞迴呼叫時棧溢位。

模板程式設計正規化

模板程式設計,就是把型別作為引數,一套函式可以支援任意多種型別。代表語言:C++。

模板程式設計的需求,是在C++開發容器庫的時候發明的。因為容器需要儲存任意型別的物件,因此就有了泛型的需求。

C++的模板程式設計,是在編譯時,根據原始碼中的使用情況,建立對應型別的程式碼。除了C++這種方式,Java,C#也有類似的機制,叫做“泛型”,但它們的實現方式和C++的模板很不同。它們的編譯器不會生成新的程式碼,而是使用強制型別轉換的方式實現。

在沒有模板/泛型的程式語言中,怎樣在容器中存放物件呢?存取公共基類型別(Java,C#)的物件,或者void*指標(C)即可,取出時自己強制型別轉換為實際型別。動態型別語言,不關心型別,更是無所謂了,隨便什麼物件直接往容器裡扔進去,取出來直接用即可。

一些C++高手又在模板的基礎上搞出了“模板超程式設計”。因為模板程式設計,就是C++的編譯器搞定的嘛,模板超程式設計就是讓編譯器運算,編譯完結果也就算出來了。我不知道除了研究和炫技,這玩意有啥用?

小結

一門語言是否值得學習,我認為有幾個標準:

1、是否要用,要用就得學,這麼沒有疑問的。畢竟我們都要吃飯的嘛。

2、其語言特性是否給你耳目一新的感覺。如果是,那就值回票價了。如Go語言廢掉了異常,改用返回多值。我深以為然。我其實已經主動不用異常好多年了。因為,我覺得既然C不支援異常也活得很好,為什麼需要異常呢?出錯了,返回錯誤碼。無法挽回的錯誤,直接Abort程式就可以嘛!而且,異常實際上是違反程式導向程式設計原則的。一個函式應該只有一個入口一個出口。丟擲異常就多了出口了。

3、是否擅長某一個領域。如果你手裡只有一把錘子,那麼你就只能把所有任務都當做釘子猛錘一通。但如果工具箱裡有多種工具,那面對不同的任務就得心應手多了。

7段—架構設計

還需要掌握架構設計的能力,才能設計出優秀的軟體。架構設計有一些技巧:

1、分層

一個軟體通常分為:

表現層–UI部分

介面層–後臺服務的通訊介面部分

服務層–實際服務部分

儲存層—持久化儲存部分,儲存到檔案或者資料庫。

分層的軟體,可以解耦各個模組,支援並行開發,易於修改,易於提升效能。

2、SOA

模組之間通過網路通訊互相連線,鬆耦合。每一個模組可以獨立部署,可以增加部署例項從而提高效能。每一個模組可以使用不同的語言和平臺開發,可以重用之前開發的服務。SOA,常用協議有WebService,REST,JSON-RPC等。

3、效能瓶頸

1)化同步為非同步。

用記憶體佇列(Redis),工作流引擎(JBpm)等實現。記憶體佇列容易丟失資料,但是速度快。工作流引擎會把請求儲存到資料庫中。

通過化同步請求為非同步請求,基本上99.99%的效能問題都可以解決。

2)用單機並行硬體處理。

如,使用GPU,FPGA等硬體來處理,提高效能。

3)用叢集計算機來處理。

如,Hadoop叢集,用多臺計算機來並行處理資料。

自己的軟體棧中,也可以把一個模組部署多份,並行處理。

4)用cache來滿足請求。常用的內容加熱cache後,大量的使用者請求都只是記憶體讀取資料而已,效能會得到很大的提升。

cache是上帝演算法,記得好像它的效能只比最佳效能低一些,就好像你是上帝,能夠預見未來一樣。現在X86CPU遇到了主頻限制,CPU提升效能的主要途徑就是增加高速Cache了。

4、大系統小做

遇到大型系統不要慌,把它切分成多個模組,用多個小程式,通過SOA協作來解決。這秉承了Unix的設計思想。Unix上開發了大量單一目的的小程式,它主張使用者通過管道來讓多個小程式協作,解決使用者的需求。當然,管道方式通訊限制太多,不夠靈活。因此,現在我們可以通過URI,通過SOA的方式來讓多個程式協作。Andorid和iOS上的應用程式,現在都是通過URI實現協作的。這也算是Unix設計思想的現代發展吧?!

5、Sharding切片

現在有一個潮流,就是去IOE。I-IBM大型機,O-Oracle資料庫,E-EMC儲存。之前,大型系統常用IOE去架構,在大型機上部署一個Oracle資料庫,Oracle資料庫用EMC儲存儲存資料。IOE是當今最強的計算機,資料庫和儲存。但他們面對海量系統也有抗不住的一天。

Oracle資料庫是Shareeverything的,它可以在一個計算機叢集(伺服器節點不能超過16個)上執行。計算機叢集都共用一個儲存。

去IOE運動,標誌著ShareEverything模式的破產。必須使用ShareNothing,系統才能無限擴充套件。

用MySQL資料庫就可以應付任意規模的資料了。前提是,你會Sharding分片。把大系統切分成若干個小系統,切分到若干臺廉價伺服器和儲存上。更Modern一些,就是切分到大量虛擬機器上。

如,鐵道部的12306網站。我們知道火車票都是從屬於某一列列車的。那麼我們把每一個列車作為一個單元來切分,就可以把12306網站切分成幾千個模組。一臺虛擬機器可以承載若干個模組。當某些列車成為效能瓶頸之後,就可以把它們遷移到獨立的虛擬機器上。即使最終有部分列出服務不可用,系統也不會完全不可用。

12306網站,只有一個全域性的部分,就是使用者登入。這個可以交給第三方負責。如可以讓使用者用微信,微博,qq等賬戶登入。

也可以自己實現使用者登入服務。還是用切片的方式用多臺Redis伺服器提供服務。Redis伺服器儲存每一個登入使用者的sessionId和userId,角色,許可權等資訊。sessionId是隨機生成的,可選擇其部分bit用於標識它在哪一個Redis伺服器上。使用者登入後,把sessionId發給客戶。使用者每次請求時把sessionId發回給伺服器。伺服器把sessionId發給Redis伺服器查詢得到其使用者資訊,對使用者請求進行處理。如果在redis伺服器上找不到sessionId,則讓使用者去登入。即使所有註冊使用者同時登陸,也不需要太多的記憶體。而且,可以在session記憶體過多時,刪除最早登陸的使用者的session,強制他再次登陸。同時活躍的使用者數不會太多。

領域知識層次

前面的所有層次,都是關注程式設計本身的技能,說白了,就是基本功,本身並不能產生太大的價值。但有太多的程式設計師浪費太多的時間在那些築基的層次上。

有些程式設計師特別喜歡鑽研程式語言,每有一種新的程式語言出來或者舊語言被熱炒,就會投入精力進去研究。我就是其中之一,浪費了很多精力在程式語言上,在奇技淫巧上。

我覺得C++語言是一個特別大的坑。剛開始是作為物件導向的C被開發的。後來發現了模板程式設計,就大力鼓吹模板程式設計和進一步的模板超程式設計。最近又推出了C++11,C++14等新標準,進一步新增了很多新東西,函數語言程式設計,型別推斷等。C++過分複雜,太多的坑消耗了大量程式設計師的大量精力。我使用C++時,只使用物件導向部分和模板部分,其他過於精深的特性都不使用。

電腦科學是一個面相當廣泛的學科,有很多領域知識需要和值得我們深入研究,我們才能寫出有價值的程式來。軟體必須要和行業結合起來,要落地才有價值。僅僅研究程式設計技巧,不懂領域知識是寫不出有價值的程式的。

電腦科學領域有很多,列舉一些如下:

儲存—-塊裝置,檔案系統,叢集檔案系統,分散式檔案系統,光纖SCSI,iSCSI,RAID等。

網路—-乙太網,光纖網,蜂窩網路,WIFI,VLAN等。

計算機體系結構,主要就是CPU指令集。x86,ARM等。

USB協議。需要知道URB包。

PCI協議,PCI-E協議。現代計算機的外設都是PCI協議和PCI-E協議的。顯示卡現在全是通過 PCI-E協議連線到計算機上的。相對來說減少了很多需要學習的知識。搞虛擬化就需要深入掌握PCI協議。

影像處理–影像壓縮,視訊實時編碼等。

3D遊戲

關聯式資料庫

NoSQL資料庫

作業系統

分散式作業系統

編譯原理

機器學習–現在大資料要用哦!

瞭解這些領域知識,也包括瞭解該領域現有的商用硬體、商用軟體和開源軟體。很多時候,你要完成的工作,已經有現成的工具了。你只要使用現成的工具就可以完成任務,不需要進行開發。有時候,只需要組合現有的工具,寫一些指令碼就可以完成任務。

如,我一次要實現一個雙向同步任務。找到了一個優秀的開源軟體Unison,編寫一下配置檔案就圓滿地完成了任務。不需要編寫任何程式碼。

還有一次,要做高可用,用Python呼叫了幾個開源軟體就輕鬆實現了。

編寫安裝程式,定製作業系統,知道了作業系統的領域知識,寫幾行指令碼就可以輕鬆搞定。

不具備領域知識的人,就可能不得不進行大量無謂的開發,甚至開發很久之後才發現,這根本就是一條死路。

另外,紮實的領域知識,可以大大提高程式設計除錯、查錯的能力。知道編譯器和程式語言執行時工作原理,就能快速根據編譯錯誤和警告資訊修改程式碼。

知道作業系統底層執行機制,就能快速找到執行時錯誤的問題根源。如,有一次我編寫一個windows升級服務程式。它是一個windows服務,需要執行dos指令碼,這個指令碼會替換掉這個windows服務本身。發現有時指令碼執行無效,查了一晚上,發現當windows服務安裝後,第一次啟動就執行指令碼時就會有許可權問題,log都正確,但實際執行這個指令碼沒有任何效果。但一旦windows服務程式啟動一次之後就ok。這必然是windows作業系統底層安全機制的問題,因為我對Windows核心瞭解不多,因此花了很長時間才發現這個問題,並對造成這個問題的根%E

相關文章