天天看點

程式員在職業生涯中如何規劃自己?

建立自己的總體計劃

我們應該建立一個總體計劃,最大限度地發揚長避短,然後把這個總體計劃應用于自己必須解決的每個問題中。

在多年的教學生涯中,我看到過很多能力不同的學生。我不能簡單地說有些程式員比其他程式員更有能力,雖然事實可能确實如此。即使是在相同能力水準的程式員之間,也存在很大的差別。我經常不可思議地看到以前學習得很掙紮的學生很快精通了某種特定的技巧,或者在其他領域天賦卓然的學生在一個新領域卻暴露出明顯的弱點。就像不存在兩個完全相同的指紋一樣,沒有兩個大腦是完全相同的,對于一個人來說非常容易的一堂課對于另一個人來說可能非常困難。

假設讀者是一位美式足球教練,正在制訂下一場比賽的進攻計劃。由于傷病的原因,無法确定兩名四分衛誰能夠首發登場。這兩名四分衛都具有高度的職業素養,但是和所有人一樣,他們也有各自的優點和缺點。為一位四分衛所制訂的完美比賽計劃套用于另一位四分衛身上卻可能帶來糟糕的結果。

在建立總體計劃時,教練需要根據隊中的四分衛進行排兵布陣。為了實作最大的獲勝機率,需要制訂一個計劃,既要認識到自己的優勢,也要明白自己的弱點。

揚長避短

在制訂自己的總體計劃時,關鍵的步驟是認識到自己的優勢和弱點。這并不困難,但它需要花費精力并且需要一個公平的自我評估。為了從錯誤中獲益,不僅需要在程式中所出現的地方修正它們,還必須對它們進行關注,至少是在大腦裡,最好是記錄在文檔中。通過這種方式,可以發現在其他情況下可能錯失的行為模式。

下面将描述兩種不同類型的弱點:編碼弱點和設計弱點。編碼弱點是指在實際編寫代碼時可能反複犯錯的領域。例如,許多程式員在編寫循環的時候,經常會出現疊代次數多1次或少1次的情況。這個錯誤稱為栅欄柱錯誤,它取材于一個古老的難題,就是建造一條總長50m的栅欄并且每根栅柱之間相隔10英尺,一共需要幾根柱子?大多數人的第一反應是5,但是如果仔細考慮,答案應該是6,如圖8.1所示。

大多數編碼弱點出現在由于程式員編寫代碼過于迅速或者缺乏充分準備而導緻語義錯誤的情況下。反之,設計弱點在問題的解決或設計階段經常出現。例如,我們可能不知道該怎麼入手或者不知道怎麼把以前所編寫的子程式內建到一個完整的解決方案中。

程式員在職業生涯中如何規劃自己?

圖 栅欄難題

盡管這兩種類型的弱點存在一些重疊,但它們會導緻不同類型的問題,是以必須按照不同的方式予以解決。

* 針對編碼弱點的計劃*

在編寫程式的時候,最令人氣惱的事情莫過于花了幾個小時的時間追蹤一處語義錯誤,結果卻發現隻是一個非常簡單而且很容易修正的錯誤。沒有任何東西是完美的,是以沒有辦法完全消除這樣的情況,但是優秀的程式員将會盡他所能避免相同的錯誤再次發生。

有一位程式員已經厭倦了C++程式設計中可能最為常見的語義錯誤:誤用指派操作符(=)代替了相等操作符(==)。由于C++的條件表達式的結果是整數,而不是嚴格的布爾值,是以下面這樣的語句在文法上是合法的:

程式員在職業生涯中如何規劃自己?

在這種情況下,整數值1被指派給number,然後1這個值成為了條件語句的結果,C++把它當作

true

處理。顯然,程式員的意圖其實是:

程式員在職業生涯中如何規劃自己?

被這類錯誤的屢次發生搞得心煩氣躁之後,這位程式員告誡自己用另一種方式來編寫相等性測試,讓數字值出現在左邊。例如:

程式員在職業生涯中如何規劃自己?

通過這種做法,如果他不小心再次犯了上面這個錯誤,1 = number這個表達式将不再是合法的C++表達式,是以會産生文法錯誤,會在編譯時被捕捉。原來的錯誤在文法上是合法的,是以它隻是個語義錯誤,在編譯時可能會被捕捉,但也可能根本不會被捕捉。由于我自己也曾經多次犯過這個錯誤(有時候在查找這個錯誤時會急得發瘋),是以也采用了這種方法,把數字值放在相等操作符的左邊。采用這種做法之後,我發現了一些奇怪的事情。由于它與我平時所使用的風格相悖,是以在編寫條件語句時把數字值放在左邊會讓我的思維暫時停頓。我會這麼思考:“我需要記住把數字值放在左邊,這樣在誤用了指派符時就能發現這種情況”。正如讀者所料,把這種想法在腦子裡過一遍之後,絕不會再錯誤地使用指派操作符,而是能夠正确地使用相等操作符。現在,我不再把數字值放在相等操作符的左邊,但在編寫條件表達式時仍然會習慣性地停頓一下,使上面這個想法再過過腦子,這樣就不會再犯這種錯誤了。

通過這個事情,我所得到的一個經驗就是:首先要意識到自己在編碼層次上的弱點,然後才能有效地避免它們。這是好消息,壞消息是我們必須通過實踐才能認識到自己的編碼弱點。關鍵的技巧是讓自己知道為什麼會犯某個特定的錯誤,而不僅僅是修正這個錯誤并繼續下一步工作。這可以幫助我們确認自己有沒有遵循的某個基本原則。例如,我們編寫了下面這個函數,計算一個整數數組中所有正數的平均值:

程式員在職業生涯中如何規劃自己?

初看上去,這個函數并沒有什麼問題。但是經過仔細觀察,還是可以看到它存在一個問題。如果數組中沒有任何正數,當循環結束時

positiveCount

的值将是0,這将導緻在函數結束時執行除零運算。由于這是浮點除法,是以程式可能不會實際崩潰,而是産生某種奇怪的行為,這具體取決于這個函數的傳回值在整體程式中是怎樣被使用的。

如果我們很快就設法運作了這段代碼,并且發現了這個問題,可能會添加一些代碼,處理

positiveCount

為零的情況,并繼續下一步工作。但是,如果想完善自己的程式設計能力,就應該問問自己犯了什麼錯誤。當然,這個特定的問題是沒有考慮到除零的可能性。但是,如果分析隻是到此為止,并不會對未來提供多大的幫助。顯然,此時應該考慮分母可能為零的其他情況,但這也不算一種非常常見的情況。反之,我們應該問問自己是否違背了什麼基本原則。答案是:我們要堅持尋找那些可能導緻代碼失敗的特殊情況。

考慮到這個基本原則之後,就很容易看到我們所犯錯誤的模式,是以在将來很容易捕捉到這類錯誤。問自己“這裡是不是存在除零錯誤的可能性”遠不如問自己“這個資料存在什麼特殊情況”更為有用。提出作用面更寬的問題,除了能夠想到不要出現除零運算之外,還會迫使自己考慮空資料集、超出預期範圍的資料等問題。

針對設計弱點的計劃

設計弱點需要一種不同的方法才能克服。但是,第一個步驟仍然是一樣的,就是要認識到自己的弱點所在。很多人在這個步驟上存在問題,因為他們并不願意對自己采取批評态度。人們總會想方設法隐瞞自己的失敗。就像接受工作面試時,當一位面試官問你最大的弱點是什麼時,你很可能會回答一些不會對面試結果産生影響的弱點,而不是坦率地承認自己的真正缺點。但是,就像超人也受制于氪星石一樣,就算是最優秀的程式員也存在真正的弱點。

下面是程式員弱點的一個示例清單(當然并不完整),讀者可以看看自己是否符合其中的幾條。

過于複雜的設計

存在這個弱點的程式員所建立的程式常常具有過多的組成部分,或者具有過多的步驟。雖然程式能夠完成任務,但它們無法讓自己充滿信心,就像穿上去的衣服一扯線頭就會全部散架一樣。很顯然,它們是非常低效的。

不知如何着手

這種類型的程式員具有高度的惰性。也許是由于在解決問題上缺乏信心,也可能生來就是慢性子,這類程式員花費了太多時間考慮怎麼開始解決問題。

疏于測試

這類程式員不喜歡對自己的代碼進行正式的測試。這樣的代碼在一般情況常常能夠很好地完成任務,但是面對特殊情況時常常會導緻失敗。還有一些情況下,這樣的代碼能夠順利地完成任務,但是對于程式員沒有進行測試的大型問題,它就難以表現出應有的适應能力。

過分自信

自信是件好事,本書的目标之一就是培養讀者的自信心。但是,過分自信和不夠自信一樣并非好事。過分自信會通過各種方式表現出來。過分自信的程式員可能會嘗試一種超出需要的更複雜解決方案,或者在非常短的時間内就匆匆完成一個項目,導緻粗率、缺陷叢生的程式産生。

脆弱領域

這種類型的弱點可謂五花八門。有些程式員一直在順利地工作,但在遇到了某些概念後突然變得不知所措。以本書前面各章所讨論的話題為例,大多數程式員在面對某個領域時,就算完成了所有的習題,他們在這個領域的信心也要比在其他領域弱得多。例如,有些程式員會迷失于指針程式中;或者遞歸的概念會把有些程式員的腦子搞混。有些程式員在設計詳盡的類時會遇到困難。這并不是說這些程式員就沒有辦法應付這些問題,但對他們而言這些是非常艱巨的任務,就像在泥地裡開車一樣。

我們可以通過不同的方法暴露自己的主要弱點。一旦認識到自己的弱點之後,就很容易針對它們制訂計劃。例如,對于經常忽略測試的程式員,在制訂每個子產品的編寫計劃時,可以明确地把測試作為必須完成的部分,在完成測試之前不能開始下一個子產品的設計。或者,也可以考慮一種稱為“測試驅動的開發”的設計用法。在這種慣用法中,首先編寫測試代碼,再編寫填充這些測試的其他代碼。對那些遲遲不能入手的程式員,可以采用問題的分治或削減原則,一旦他覺得可以就開始編寫代碼,當然還要知道将來可能需要對這些代碼進行重寫。對于那些常常設計得過于複雜的程式員,可以在總體計劃中增加一個明确的重構步驟。關鍵在于,不管程式員的主要弱點是什麼,它們隻不過是項目成功完成的道路上的絆腳石而已。

根據自己的優點制訂計劃

根據弱點制訂計劃在很大程度上是為了避免錯誤。但是,良好的計劃并不僅僅是為了避免錯誤。它還涉及到根據自己的目前能力以及可能受到的限制,盡可能實作最佳結果。這意味着我們還必須根據自己的優點制訂總體計劃。

讀者可能覺得本節的内容不适合自己,至少目前為止還不适合。不管怎樣,如果讀者已經開始閱讀本書,就有可能成為一名程式員。讀者可能覺得自己在目前階段還談不上有任何優點,但事實上還是有的,即使自己并沒有意識到它們。下面是一些常見的程式員優點的清單,當然并不完整。我對每個優點提供了描述和提示,以幫助讀者判斷自己是否具有這些優點:

細心

這種類型的程式員能夠預料到特殊情況,在潛在的性能問題出現之前就預感到它們,而且絕不會讓整體情況掩蓋那些必須精心處理的細節,而這些細節又往往是實作完整和準确的解決方案所必需的。具有這個優點的程式員傾向于在編寫代碼之前先在紙上測試他們的計劃,他們會小心細緻地編寫代碼,并且經常進行測試。

快速學習能力

具有快速學習能力的程式員能夠很快學會新的技巧,無論是一種已經熟悉的語言中的一項新技巧還是學習一個新的應用程式架構。這種類型的程式員享受學習新事物的挑戰,可能會根據這個喜好來選擇項目。

快速編碼能力

具有快速編碼能力的程式員無需很長時間就可以根據一本參考書搗鼓出一個函數。到了開始打字的時間,不需要特别地費勁,代碼就會從指尖迅速湧出,并且其中很少出現文法錯誤。

永不放棄

對于有些程式員而言,讨厭的程式缺陷就像無法回避的個人遭遇一樣。如同程式戴着皮革手套扇了程式員一個巴掌,然後輪到程式員對此做出回應。這類程式員始終頭腦冷靜、意志堅定,不會被挫折所擊倒。他們堅信隻要付出足夠的努力,必将取得最後的勝利。

超級問題解決專家

假如讀者在閱讀本書時還不是一位超級問題解決專家,但是在了解了一些指導方針之後,覺得做所有的事情時都變得得心應手。那麼,具有這種能力的程式員在剛剛接觸一個問題的時候就會開始思考潛在的解決方案。

完美主義者

對于這類程式員而言,一個工作程式就像一件精妙的玩具。完美主義者絕不會喪失讓計算機按照他的指令行事的激情,并且喜歡想方設法找點事情讓計算機去做。在某種意義上,完美主義意味着不斷向工作程式添加更多的功能,這種症狀稱為爬行功能主義。在他們眼裡,也許可以對程式進行重構以提高性能,也許可以讓程式在程式員或使用者面前顯得更精巧。

很少有程式員能夠同時擁有上面所說的多個優點。事實上,有些優點會互相抵銷。但是,每個程式員都有自己的優點。如果讀者覺得自己不符合上面所說的任何一條,也隻是意味着對自己還不夠了解,或者其優點并不屬于上面所提到的這幾種類型。

一旦确認了自己的優點,就需要在總體計劃中利用它們。假設讀者具有快速編碼能力,很顯然它可以使任何項目更快地到達終點。但是,怎樣才能以系統的方式利用這個優點呢?在正式的軟體工程中,有一種方法稱為快速原型法,就是在一開始編寫一個程式的時候并沒有深入的計劃,需要通過連續的疊代予以完善,直到最終的結果能夠滿足問題需求。作為快速編碼者,可以嘗試采用這種方法:有了一個基本的思路之後就可以開始編寫代碼,用粗略的原型來指導最終程式代碼的設計和開發。

如果讀者具有快速學習能力,在每個項目開始的時候應該尋訪新的資源或技巧來解決目前問題。如果既不具備快速學習能力,但也不會輕易被挫折所擊垮,那麼在項目開始的時候可以從最困難的部分入手,給自己最多的時間來處理它們。

是以,不管自己擁有何種優點,要保證在程式設計時利用它們。設計自己的總體計劃,盡可能地把時間留給自己最擅長的事情。通過這種方式,讀者在程式設計時不僅能夠産生最好的結果,還将體會到最多的樂趣。

制訂總體計劃

讓我們觀察建立總體計劃的一個執行個體。這個計劃的組成部分包括自己已經掌握的所有問題解決技巧,再加上對自身的優點和弱點的分析。我将使用自己的優點和弱點作為例子。

在問題解決技巧方面,我用了本書所讨論的所有技巧,但尤其鐘愛“削減問題”技巧,因為這種技巧能夠讓我感覺到自己向最終的目标不斷邁出堅實的步伐。如果目前還無法編寫滿足完整規範的代碼,可以先忽略部分規範,直到有信心完成剩餘的内容為止。

我最大的編碼弱點是過于認真。我喜歡程式設計,因為喜歡看到計算機按照自己的指令行事。有時候,應該分析自己所編寫的東西的正确性時,我會考慮:“直接讓它運作吧,看看會發生什麼。”這種做法的危險在于程式可能會失敗。雖然程式看上去似乎很成功,但是它并沒有覆寫所有的特殊情況。或者它雖然成功,但并不是我應該編寫的最佳解決方案。

我喜歡優雅的程式設計,希望程式能夠很友善地被擴充和複用。當我編寫大型項目時,常常花費大量的時間開發其他途徑的設計方案。總體而言,它是一個良好的能力,但有時這會導緻我把過多的時間花在設計階段,沒有留下太多的時間實作最終所選擇的設計。另外,這也會導緻解決方案的過度設計。也就是說,有時候設計的解決方案會比實際所需要的解決方案更優雅、更容易擴充并且更健壯。由于每個項目的時間和金錢都是有限的,是以最佳的解決方案必須同時兼顧程式的品質和資源的節約。

我覺得自己最大的程式設計優點就是能夠很快領會新概念并且熱愛學習。雖然有些程式員喜歡一直使用相同的技巧,但我喜歡在項目完成時能夠學到新東西,并且總是很樂于接受這類挑戰。

有了這些思路之後,下面是我對一個新項目的總體計劃。

為了彌補我的主要弱點,我嚴格地控制自己在設計階段所花費的時間,或者說控制了在設計階段所考慮的不同設計方案的數量。對于某些讀者而言,這好像是種危險的想法。在進入編碼階段之前,難道不應該盡可能地多花點時間在設計階段嗎?大多數項目失敗的原因難道不是因為在前期所花的時間太少進而導緻後期的一連串妥協嗎?這些顧慮當然是對的,但是我現在并不是為軟體開發建立一條通用的指導方針,而是為自己制訂處理程式設計問題的總體計劃。我的弱點是過度設計而不是設計不足,是以控制設計時間這個規則對我而言是合理的。對于其他程式員而言,這樣的規則很可能是災難。有些程式員可能需要一個規則迫使他們把更多的時間花在設計上。

完成最初的分析之後,我就開始考慮這個項目是否有機會讓自己學習新的技巧、庫等知識。如果答案是肯定的,我就打算編寫一個小型的測驗台程式,對這些新技巧進行試驗,然後再把它們吸收到自己所開發的解決方案中。為了克服過于認真這個弱點,在完成每個模式的編碼時,可以添加一個簡單的代碼回顧步驟。但是,這并不是我的意願所在。當我完成每個子產品時,希望繼續前進并讓它實際運作。單純地希望我在這個時候能夠停下來就像在一個肚子餓得咕咕叫的人身邊放上一袋打開的薯片,然後驚奇地發現這袋薯片被吃光了。在制訂克服程式員弱點的計劃時,不要讓程式員跟自己的直覺做鬥争。如果我建立了兩個版本的項目:一個是原始的任由我處理的版本,另一個是經過優化的準備發行的版本。如果允許我對第一個版本按照自己的意願行事,但是在經過完全的驗證之前,不要把它的代碼吸收到另一個優化的版本中,這樣無疑更容易克服自己的弱點。

本文摘自《像程式員一樣思考(修訂版)》

繼續閱讀