程序員在職業生涯中如何規劃自己?
創建自己的總體計劃
我們應該創建一個總體計劃,最大限度地發揚長避短,然后把這個總體計劃應用于自己必須解決的每個問題中。
在多年的教學生涯中,我看到過很多能力不同的學生。我不能簡單地說有些程序員比其他程序員更有能力,雖然事實可能確實如此。即使是在相同能力水 平的程序員之間,也存在很大的區別。我經常不可思議地看到以前學習得很掙扎的學生很快精通了某種特定的技巧,或者在其他領域天賦卓然的學生在一個新領域卻 暴露出明顯的弱點。就像不存在兩個完全相同的指紋一樣,沒有兩個大腦是完全相同的,對于一個人來說非常容易的一堂課對于另一個人來說可能非常困難。
假設讀者是一位美式足球教練,正在制訂下一場比賽的進攻計劃。由于傷病的原因,無法確定兩名四分衛誰能夠首發登場。這兩名四分衛都具有高度的職 業素養,但是和所有人一樣,他們也有各自的優點和缺點。為一位四分衛所制訂的完美比賽計劃套用于另一位四分衛身上卻可能帶來糟糕的結果。
在創建總體計劃時,教練需要根據隊中的四分衛進行排兵布陣。為了實現最大的獲勝機率,需要制訂一個計劃,既要認識到自己的優勢,也要明白自己的弱點。
揚長避短
在制訂自己的總體計劃時,關鍵的步驟是認識到自己的優勢和弱點。這并不困難,但它需要花費精力并且需要一個公平的自我評估。為了從錯誤中獲益, 不僅需要在程序中所出現的地方修正它們,還必須對它們進行關注,至少是在大腦里,最好是記錄在文檔中。通過這種方式,可以發現在其他情況下可能錯失的行為 模式。
下面將描述兩種不同類型的弱點:編碼弱點和設計弱點。編碼弱點是指在實際編寫代碼時可能反復犯錯的領域。例如,許多程序員在編寫循環的時候,經 常會出現迭代次數多 1 次或少 1 次的情況。這個錯誤稱為柵欄柱錯誤,它取材于一個古老的難題,就是建造一條總長 50m 的柵欄并且每根柵柱之間相隔 10 英尺,一共需要幾根柱子?大多數人的第一反應是5,但是如果仔細考慮,答案應該是6,如圖 8.1 所示。
大多數編碼弱點出現在由于程序員編寫代碼過于迅速或者缺乏充分準備而導致語義錯誤的情況下。反之,設計弱點在問題的解決或設計階段經常出現。例如,我們可能不知道該怎么入手或者不知道怎么把以前所編寫的子程序集成到一個完整的解決方案中。
圖/柵欄難題
盡管這兩種類型的弱點存在一些重疊,但它們會導致不同類型的問題,因此必須按照不同的方式予以解決。
- 針對編碼弱點的計劃
在編寫程序的時候,最令人氣惱的事情莫過于花了幾個小時的時間追蹤一處語義錯誤,結果卻發現只是一個非常簡單而且很容易修正的錯誤。沒有任何東西是完美的,因此沒有辦法完全消除這樣的情況,但是優秀的程序員將會盡他所能避免相同的錯誤再次發生。
有一位程序員已經厭倦了 C++ 編程中可能最為常見的語義錯誤:誤用賦值操作符(=)代替了相等操作符(==)。由于 C++ 的條件表達式的結果是整數,而不是嚴格的布爾值,因此下面這樣的語句在語法上是合法的:
在這種情況下,整數值 1 被賦值給 number,然后 1 這個值成為了條件語句的結果,C++把它當作true
處理。顯然,程序員的意圖其實是:
被這類錯誤的屢次發生搞得心煩氣躁之后,這位程序員告誡自己用另一種方式來編寫相等性測試,讓數字值出現在左邊。例如:
通過這種做法,如果他不小心再次犯了上面這個錯誤,1 = number 這個表達式將不再是合法的 C++ 表達式,因此會產生語法錯誤,會在編譯時被捕捉。原來的錯誤在語法上是合法的,因此它只是個語義錯誤,在編譯時可能會被捕捉,但也可能根本不會被捕捉。由 于我自己也曾經多次犯過這個錯誤(有時候在查找這個錯誤時會急得發瘋),因此也采用了這種方法,把數字值放在相等操作符的左邊。采用這種做法之后,我發現 了一些奇怪的事情。由于它與我平時所使用的風格相悖,因此在編寫條件語句時把數字值放在左邊會讓我的思維暫時停頓。我會這么思考:“我需要記住把數字值放 在左邊,這樣在誤用了賦值符時就能發現這種情況”。正如讀者所料,把這種想法在腦子里過一遍之后,絕不會再錯誤地使用賦值操作符,而是能夠正確地使用相等 操作符。現在,我不再把數字值放在相等操作符的左邊,但在編寫條件表達式時仍然會習慣性地停頓一下,使上面這個想法再過過腦子,這樣就不會再犯這種錯誤 了。
通過這個事情,我所得到的一個經驗就是:首先要意識到自己在編碼層次上的弱點,然后才能有效地避免它們。這是好消息,壞消息是我們必須通過實踐 才能認識到自己的編碼弱點。關鍵的技巧是讓自己知道為什么會犯某個特定的錯誤,而不僅僅是修正這個錯誤并繼續下一步工作。這可以幫助我們確認自己有沒有遵 循的某個基本原則。例如,我們編寫了下面這個函數,計算一個整數數組中所有正數的平均值:
初看上去,這個函數并沒有什么問題。但是經過仔細觀察,還是可以看到它存在一個問題。如果數組中沒有任何正數,當循環結束時positiveCount
的值將是0,這將導致在函數結束時執行除零運算。由于這是浮點除法,因此程序可能不會實際崩潰,而是產生某種奇怪的行為,這具體取決于這個函數的返回值在整體程序中是怎樣被使用的。
如果我們很快就設法運行了這段代碼,并且發現了這個問題,可能會添加一些代碼,處理positiveCount
為零 的情況,并繼續下一步工作。但是,如果想完善自己的編程能力,就應該問問自己犯了什么錯誤。當然,這個特定的問題是沒有考慮到除零的可能性。但是,如果分 析只是到此為止,并不會對未來提供多大的幫助。顯然,此時應該考慮分母可能為零的其他情況,但這也不算一種非常常見的情況。反之,我們應該問問自己是否違 背了什么基本原則。答案是:我們要堅持尋找那些可能導致代碼失敗的特殊情況。
考慮到這個基本原則之后,就很容易看到我們所犯錯誤的模式,因此在將來很容易捕捉到這類錯誤。問自己“這里是不是存在除零錯誤的可能性”遠不如 問自己“這個數據存在什么特殊情況”更為有用。提出作用面更寬的問題,除了能夠想到不要出現除零運算之外,還會迫使自己考慮空數據集、超出預期范圍的數據 等問題。
針對設計弱點的計劃
設計弱點需要一種不同的方法才能克服。但是,第一個步驟仍然是一樣的,就是要認識到自己的弱點所在。很多人在這個步驟上存在問題,因為他們并不 愿意對自己采取批評態度。人們總會想方設法隱瞞自己的失敗。就像接受工作面試時,當一位面試官問你最大的弱點是什么時,你很可能會回答一些不會對面試結果 產生影響的弱點,而不是坦率地承認自己的真正缺點。但是,就像超人也受制于氪星石一樣,就算是最優秀的程序員也存在真正的弱點。
下面是程序員弱點的一個示例列表(當然并不完整),讀者可以看看自己是否符合其中的幾條。
過于復雜的設計
存在這個弱點的程序員所創建的程序常常具有過多的組成部分,或者具有過多的步驟。雖然程序能夠完成任務,但它們無法讓自己充滿信心,就像穿上去的衣服一扯線頭就會全部散架一樣。很顯然,它們是非常低效的。
不知如何著手
這種類型的程序員具有高度的惰性。也許是由于在解決問題上缺乏信心,也可能生來就是慢性子,這類程序員花費了太多時間考慮怎么開始解決問題。
疏于測試
這類程序員不喜歡對自己的代碼進行正式的測試。這樣的代碼在一般情況常常能夠很好地完成任務,但是面對特殊情況時常常會導致失敗。還有一些情況下,這樣的代碼能夠順利地完成任務,但是對于程序員沒有進行測試的大型問題,它就難以表現出應有的適應能力。
過分自信
自信是件好事,本書的目標之一就是培養讀者的自信心。但是,過分自信和不夠自信一樣并非好事。過分自信會通過各種方式表現出來。過分自信的程序員可能會嘗試一種超出需要的更復雜解決方案,或者在非常短的時間內就匆匆完成一個項目,導致粗率、缺陷叢生的程序產生。
脆弱領域
這種類型的弱點可謂五花八門。有些程序員一直在順利地工作,但在遇到了某些概念后突然變得不知所措。以本書前面各章所討論的話題為例,大多數程 序員在面對某個領域時,就算完成了所有的習題,他們在這個領域的信心也要比在其他領域弱得多。例如,有些程序員會迷失于指針程序中;或者遞歸的概念會把有 些程序員的腦子搞混。有些程序員在設計詳盡的類時會遇到困難。這并不是說這些程序員就沒有辦法應付這些問題,但對他們而言這些是非常艱巨的任務,就像在泥 地里開車一樣。
我們可以通過不同的方法暴露自己的主要弱點。一旦認識到自己的弱點之后,就很容易針對它們制訂計劃。例如,對于經常忽略測試的程序員,在制訂每 個模塊的編寫計劃時,可以明確地把測試作為必須完成的部分,在完成測試之前不能開始下一個模塊的設計。或者,也可以考慮一種稱為“測試驅動的開發”的設計 用法。在這種慣用法中,首先編寫測試代碼,再編寫填充這些測試的其他代碼。對那些遲遲不能入手的程序員,可以采用問題的分治或削減原則,一旦他覺得可以就 開始編寫代碼,當然還要知道將來可能需要對這些代碼進行重寫。對于那些常常設計得過于復雜的程序員,可以在總體計劃中增加一個明確的重構步驟。關鍵在于, 不管程序員的主要弱點是什么,它們只不過是項目成功完成的道路上的絆腳石而已。
根據自己的優點制訂計劃
根據弱點制訂計劃在很大程度上是為了避免錯誤。但是,良好的計劃并不僅僅是為了避免錯誤。它還涉及到根據自己的當前能力以及可能受到的約束,盡可能實現最佳結果。這意味著我們還必須根據自己的優點制訂總體計劃。
讀者可能覺得本節的內容不適合自己,至少目前為止還不適合。不管怎樣,如果讀者已經開始閱讀本書,就有可能成為一名程序員。讀者可能覺得自己在 當前階段還談不上有任何優點,但事實上還是有的,即使自己并沒有意識到它們。下面是一些常見的程序員優點的列表,當然并不完整。我對每個優點提供了描述和 提示,以幫助讀者判斷自己是否具有這些優點:
細心
這種類型的程序員能夠預料到特殊情況,在潛在的性能問題出現之前就預感到它們,而且絕不會讓整體情況掩蓋那些必須精心處理的細節,而這些細節又 往往是實現完整和準確的解決方案所必需的。具有這個優點的程序員傾向于在編寫代碼之前先在紙上測試他們的計劃,他們會小心細致地編寫代碼,并且經常進行測 試。
快速學習能力
具有快速學習能力的程序員能夠很快學會新的技巧,無論是一種已經熟悉的語言中的一項新技巧還是學習一個新的應用程序框架。這種類型的程序員享受學習新事物的挑戰,可能會根據這個喜好來選擇項目。
快速編碼能力
具有快速編碼能力的程序員無需很長時間就可以根據一本參考書搗鼓出一個函數。到了開始打字的時間,不需要特別地費勁,代碼就會從指尖迅速涌出,并且其中很少出現語法錯誤。
永不放棄
對于有些程序員而言,討厭的程序缺陷就像無法回避的個人遭遇一樣。如同程序戴著皮革手套扇了程序員一個巴掌,然后輪到程序員對此做出回應。這類程序員始終頭腦冷靜、意志堅定,不會被挫折所擊倒。他們堅信只要付出足夠的努力,必將取得最后的勝利。
超級問題解決專家
假如讀者在閱讀本書時還不是一位超級問題解決專家,但是在了解了一些指導方針之后,覺得做所有的事情時都變得得心應手。那么,具有這種能力的程序員在剛剛接觸一個問題的時候就會開始思考潛在的解決方案。
完美主義者
對于這類程序員而言,一個工作程序就像一件精妙的玩具。完美主義者絕不會喪失讓計算機按照他的命令行事的激情,并且喜歡想方設法找點事情讓計算 機去做。在某種意義上,完美主義意味著不斷向工作程序添加更多的功能,這種癥狀稱為爬行功能主義。在他們眼里,也許可以對程序進行重構以提高性能,也許可 以讓程序在程序員或用戶面前顯得更精巧。
很少有程序員能夠同時擁有上面所說的多個優點。事實上,有些優點會相互抵銷。但是,每個程序員都有自己的優點。如果讀者覺得自己不符合上面所說的任何一條,也只是意味著對自己還不夠了解,或者其優點并不屬于上面所提到的這幾種類型。
一旦確認了自己的優點,就需要在總體計劃中利用它們。假設讀者具有快速編碼能力,很顯然它可以使任何項目更快地到達終點。但是,怎樣才能以系統 的方式利用這個優點呢?在正式的軟件工程中,有一種方法稱為快速原型法,就是在一開始編寫一個程序的時候并沒有深入的計劃,需要通過連續的迭代予以完善, 直到最終的結果能夠滿足問題需求。作為快速編碼者,可以嘗試采用這種方法:有了一個基本的思路之后就可以開始編寫代碼,用粗略的原型來指導最終程序代碼的 設計和開發。
如果讀者具有快速學習能力,在每個項目開始的時候應該尋訪新的資源或技巧來解決當前問題。如果既不具備快速學習能力,但也不會輕易被挫折所擊垮,那么在項目開始的時候可以從最困難的部分入手,給自己最多的時間來處理它們。
因此,不管自己擁有何種優點,要保證在編程時利用它們。設計自己的總體計劃,盡可能地把時間留給自己最擅長的事情。通過這種方式,讀者在編程時不僅能夠產生最好的結果,還將體會到最多的樂趣。
制訂總體計劃
讓我們觀察創建總體計劃的一個實例。這個計劃的組成部分包括自己已經掌握的所有問題解決技巧,再加上對自身的優點和弱點的分析。我將使用自己的優點和弱點作為例子。
在問題解決技巧方面,我用了本書所討論的所有技巧,但尤其鐘愛“削減問題”技巧,因為這種技巧能夠讓我感覺到自己向最終的目標不斷邁出堅實的步伐。如果目前還無法編寫滿足完整規范的代碼,可以先忽略部分規范,直到有信心完成剩余的內容為止。
我最大的編碼弱點是過于認真。我喜歡編程,因為喜歡看到計算機按照自己的命令行事。有時候,應該分析自己所編寫的東西的正確性時,我會考慮: “直接讓它運行吧,看看會發生什么。”這種做法的危險在于程序可能會失敗。雖然程序看上去似乎很成功,但是它并沒有覆蓋所有的特殊情況。或者它雖然成功, 但并不是我應該編寫的最佳解決方案。
我喜歡優雅的程序設計,希望程序能夠很方便地被擴展和復用。當我編寫大型項目時,常常花費大量的時間開發其他途徑的設計方案。總體而言,它是一 個良好的能力,但有時這會導致我把過多的時間花在設計階段,沒有留下太多的時間實現最終所選擇的設計。另外,這也會導致解決方案的過度設計。也就是說,有 時候設計的解決方案會比實際所需要的解決方案更優雅、更容易擴展并且更健壯。由于每個項目的時間和金錢都是有限的,因此最佳的解決方案必須同時兼顧程序的 質量和資源的節約。
我覺得自己最大的編程優點就是能夠很快領會新概念并且熱愛學習。雖然有些程序員喜歡一直使用相同的技巧,但我喜歡在項目完成時能夠學到新東西,并且總是很樂于接受這類挑戰。
有了這些思路之后,下面是我對一個新項目的總體計劃。
為了彌補我的主要弱點,我嚴格地控制自己在設計階段所花費的時間,或者說控制了在設計階段所考慮的不同設計方案的數量。對于某些讀者而言,這好 像是種危險的想法。在進入編碼階段之前,難道不應該盡可能地多花點時間在設計階段嗎?大多數項目失敗的原因難道不是因為在前期所花的時間太少從而導致后期 的一連串妥協嗎?這些顧慮當然是對的,但是我現在并不是為軟件開發創建一條通用的指導方針,而是為自己制訂處理編程問題的總體計劃。我的弱點是過度設計而 不是設計不足,因此控制設計時間這個規則對我而言是合理的。對于其他程序員而言,這樣的規則很可能是災難。有些程序員可能需要一個規則迫使他們把更多的時 間花在設計上。
完成最初的分析之后,我就開始考慮這個項目是否有機會讓自己學習新的技巧、庫等知識。如果答案是肯定的,我就打算編寫一個小型的測驗臺程序,對 這些新技巧進行試驗,然后再把它們吸收到自己所開發的解決方案中。為了克服過于認真這個弱點,在完成每個模式的編碼時,可以添加一個簡單的代碼回顧步驟。 但是,這并不是我的意愿所在。當我完成每個模塊時,希望繼續前進并讓它實際運行。單純地希望我在這個時候能夠停下來就像在一個肚子餓得咕咕叫的人身邊放上 一袋打開的薯片,然后驚奇地發現這袋薯片被吃光了。在制訂克服程序員弱點的計劃時,不要讓程序員跟自己的直覺做斗爭。如果我創建了兩個版本的項目:一個是 原始的任由我處理的版本,另一個是經過優化的準備發行的版本。如果允許我對第一個版本按照自己的意愿行事,但是在經過完全的驗證之前,不要把它的代碼吸收 到另一個優化的版本中,這樣無疑更容易克服自己的弱點。
本文摘自《像程序員一樣思考(修訂版)》