微軟資深軟件工程師:閱讀代碼真的很難

編者按:原文做者EricLippert是一名資深軟件設計工程師,從1996年起一直在微軟開發部門任職,協助設計並實現VBScript、 JScript、JScript.NET、Windows Script Host、Visual Studio Tools for Office 和 C#。程序員

Escalation的工程師JeremyK在他的博客中問到:編程

你是怎麼教人們快速深刻挖掘不熟悉的代碼(不是本身所寫的)?我學習如何編程的方法很傳統 —— 本身動手編碼。但我如今很糾結:究竟是集中精神閱讀源碼,仍是本身編寫。對我而言,彷佛惟一有效的方法就是本身寫過。

不是和Jeremy開玩笑,寫代碼的確沒有讀代碼難。數據結構

首先,我贊成你的見解,幾乎不多有人能讀代碼但不會寫代碼。這不像天然書面語或口語,理解他人的意思並不須要去理解他們爲何要那樣說。好比,若是我說:架構

「寫代碼有兩種方式:一種嚴格且詳細,另外一種模糊且草率。前者生成簡潔分層的婚禮蛋糕,後者倒是意大利麪條。」編程語言

上面這句話產生一個平衡且幽默的效果,但即便聽衆和讀者不理解我使用「零照應」和「並列句」這樣的文字技巧,也會理解我要說的意思。可是說到代碼,既要從代碼自己中理解代碼做者的意圖,又要理解代碼產生的預計效果,這二者都極爲重要。函數

所以,我又回到那個問題了,有些人須要快速切入代碼,但不須要動手寫代碼,那咱們如何編寫適合這些人的代碼?工具

下面是我在編寫代碼時,盡力去作的事,目的就是使其餘人能輕鬆閱讀:post

  • 使代碼聽從工具。Object Browsers和Intellisense雖然很好,但我告訴你,我是守舊派。若是找不到我想要的,我會不高興。什麼使得代碼成爲可查詢的呢?
    • 像"i"這樣的變量名很差。若是沒有明確的錯誤提示,你就沒法輕易查找代碼。
    • 避免使用是其餘名字前綴的名字。好比,在代碼中有個「perfExecuteManifest」,若是再有一個「perfExecuteManifestInitialize」,這就會讓我抓狂,由於每次在源碼中查詢前者時,我不得不費力地過濾掉後者全部的實例。
    • 「臨時傳遞數據」(tramp data)應使用相同的名字。所謂「臨時傳遞數據」(tramp data),就是指那些傳遞給方法A的變量,還要傳給方法B的變量。這兩類變量其實是相同的,因此若是它們有着相同的名字,則更好。
    • 別用宏來重命名東西。若是有個方法叫get_MousePosition,那別這樣GETTER(MousePosition)來聲明該方法。由於我會找不到實際的方法名。
    • Shadowing會引發不少問題,請不要用它。
  • 堅持使用一種命名模式。若是你打算用匈牙利命名法,那就堅持並普遍使用,不然將拔苗助長。使用匈牙利命名法來記錄數據,而不是存儲類型;記錄廣泛事實,而不是臨時條件。
  • 使用斷言來記錄先決條件(preconditions)和後置條件(postconditions)。
  • 別縮寫英文單詞。確切來講,別縮寫成稀奇古怪的形式。在腳本引擎中,有個變量名叫「NME」,這讓我很是抓狂!它應當叫「VariableName」。
  • C語言標準運行時庫的設計不是很優秀。別去效仿它。
  • 別寫「聰明」的代碼;當代碼出現問題,維護代碼的程序員沒時間去理解你的聰慧。
  • 理解編程語言特性的設計初衷,使用這些特性去作它們適合完成的工做,而不是它們能作到的工做。例如:別把異常當作通常的流控制機制來使用(即使你能作到),而應該用它們來報告錯誤。彆強制把接口指針轉換成類指針,即使你知道這樣沒問題。
  • 按功能單元劃分源碼樹,而不是按組織結構。好比:我目前所在團隊中,有2個根子目錄的名字是「Frameworks」和 「Integration」,這是兩個團隊的名字。不巧的是,Frameworks團隊名下有一個叫「Adaptor」的子目錄,而「Adaptor」倒是Integration的子目錄,這就很是使人迷惑。同理,(若是)有着相同子目錄的不一樣的子樹,有些是客戶端的組件,有些是服務端的組件;有些是管理組件,有些是非管理組件;有些是處理型組件,有些是非處理型組件;有些是零售組件,有些內部測試工具。這就會亂七八糟的。

固然,我實際上根本沒有回答Jeremy的問題——如何調試不是我寫的代碼?學習

這取決於個人目的。若是我只是由於一個bug,而深挖一段具體的代碼,我會在調試器中逐步跟蹤全部代碼,寫下我「走過」的調用分支,記錄下哪些方法是特定數據結構的「生產者」,哪些方法是「消費者」;我也會仔細盯着輸出窗口,查看出現的有用信息;還要打開異常捕捉器,由於異常一般是問題所在。設置斷點;我會記錄全部和我上面建議相反的地方,由於這些東西極可能誤導了我。測試

若是我想在理解一段代碼後修改它,我一般是從代碼頭部開始,或者先查找公共方法。我要知道類是如何實現的,它是如何擴展的,它的做用,它是如何嵌入整個代碼中的?我會盡力理解這些東西后,纔去瞭解這些特定部分(代碼)是如何實現的。這耗時雖更長些,但若是你準備改動複雜代碼,你應當那樣作。

如何閱讀代碼?像某些人同樣……

我已經記不清有多少次看到程序員(用鼠標)滾上滾下地看着不熟悉的代碼,幾分鐘事後,他們的臉上浮現出不悅的表情。他們不久後會宣告說,那代碼不值一讀,爲何要浪費時間呢?咱們只能用其餘方法解決問題。我不肯定(他們)在期待什麼,是經過潛移默化來吸取代碼的含義,仍是集中精神盯着代碼來獲得啓發?你不能只靠長時間盯着代碼來閱讀代碼,你要理解它並化爲己用。 這裏有一些我喜歡用的技巧,雖然這不是一份詳盡的列表,但我發現其中有些特別有用。

  • 1. 盡力構建並運行代碼。這一般是一個簡單的步驟,就像你在看可運行的代碼(這和隨機代碼相反)。不過,並不是老是如此。經過構建和執行代碼,你能從中學到不少上層代碼結構。說到工做代碼,你是否很是熟悉如何構建你的當前項目?雖然構建一般很是複雜,但經過構建並生成可執行的代碼,你能學到不少。
  • 2. 不要只注重細節。你要作的第一件事是,在你正閱讀的代碼中,找到代碼結構和風格。首先瀏覽一下代碼,盡力理解不一樣代碼段要作什麼。這會讓你熟整個代碼的上層結構,你也能領會到你正處理的代碼的一些構思(良好架構和意大利麪條等)。這時候,你能夠找到切入點(無論它是什麼,主函數、servlet 或控制器等),並查看代碼如何在那裏分支。不要在這上面花過多的時間,隨着你越發熟悉代碼,你能夠隨時回來查看。
  • 3. 確信本身理解全部結構。除非你碰巧是所用編程語言的首席專家,不然該語言有些它能作的事你可能還不知道。當你在瀏覽代碼時,記下全部你或許不熟悉的結構。若是有不少不熟悉的結構,你要作的下一步很是明顯。若是你不知道代碼要作什麼,那你就走不了很遠。 即使只有幾個你不熟悉的結構,你應當深刻查看。你如今是在探索你所用編程語言中你之前不知道的東西,爲此花幾個小時來閱讀代碼,我也很是樂意。
  • 4. 既然你對大多數結構已有很好了解,那如今是該作些隨機深刻研究了。 就像步驟2,開始瀏覽代碼,當此次要挑選一些隨機函數或類,並開始逐行詳細查看。這是硬仗開始的地方,但也是你要取得主要成功的地方。這裏的構想,會造成你正在查看的代碼庫的思惟模式。也不要在這上面花過長的時間,但在繼續前行以前,你要盡力並極大吸取一些有內容的代碼塊。這個步驟,你也能夠隨時反覆回過頭來,每次你都會了解更多的背景,並收穫更多。
  • 5. 毫無疑問,在前面這些步驟中,確定有你困惑的地方,因此這是你作些測試的最佳時間。在測試的時候,你的麻煩可能會更少,同時你也能理解代碼。 我一直感到奇怪,開發人員忽略一套寫得很好很全面的測試代碼,而盡力去閱讀並理解某些代碼。固然了,有時候並無測試。
  • 6. 若是你說沒有測試,那這聽起來是編寫測試的時候了。 (編寫測試)有不少益處,有助於你本身的理解,有助於你提高代碼庫,閱讀代碼時也能編寫代碼,這是該你出手作些事的時候。 即使已經有了測試,一般你也能夠編寫一些測試,你總能受益的。 測試代碼一般須要換種方式思考問題,那些你之前不太明瞭的概念也會變得更清晰。
  • 7. 提取奇特的代碼,使其成爲單獨的程序。我發現閱讀代碼是個很是有趣的練習,即使只爲節奏變化。即使你不瞭解代碼的底層細節,你或許能知道一些代碼在上層結構上要作什麼。爲何不提取一些特定的函數,單獨列爲獨立的程序。當你在執行小段程序時,調試也會更簡單。反過來講,可能還須要一些額外的步驟,才能理解你正查看的代碼。
  • 8. 代碼不乾淨?有異味? 爲何不重構它?我並不建議你重寫整個代碼庫,但重構部分代碼,真的有助於你的理解層次上升一層。 把你理解的函數拿出來,改爲獨立的函數。在你知道以前,原來的大函數看起來易管理,你能夠在腦海中修改它。 重構容許你把代碼變成本身的,無需完成重寫代碼。若是有好的測試,有助於重構,但即使你沒有好的測試,抽取你肯定的函數並作測試。即使測試看起來徹底不充分,但做爲一個開發人員,你得學着相信你的技能,有時候你只需努力去作(重構)。(若是你必須重構,你一般均可以把代碼恢復原狀。)
  • 9. 若是沒什麼能幫上忙,那你就找個閱讀代碼的同伴。或許並不是只有你一我的能從這代碼中獲益,因此去找一我的,一塊兒閱讀代碼吧。 但你別找專家,他們會從上層結構上,向你解釋全部東西,你會錯失那些你本身詳細查看代碼時所能學到的細微差異。然而,若是不見效的話,你也不能理解,有時候,你能作的最好的事就是去問。向你的同事請教,若是你正在閱讀開源代碼,能夠在互聯網上找人問問。可是你要記住,這是最後一步,而不是第一步。

若是我時間緊迫,須要快速合理地理解某些代碼,而且我只能挑選上述步驟的其中一個,那我會選擇「重構」(即:第 8 個步驟)。雖然你能理解的東西不會不少,但那些你領會的東西,你會緊緊記住的。總之,有件事你須要記在內心。若是你新接觸一個重要的代碼庫,你不可能當即能理解它。這須要數天、數週和數月的潛心努力,接受這個事實。即使有一位專家和你在一塊兒,也不能明顯地縮短期。然而,當涉及到代碼庫時,若是你能耐心並有條不紊地閱讀(和編寫)代碼,你最終能熟悉項目的方方面面,你能成爲大牛。你或者是逃避閱讀代碼,常常尋求某人幫你講解某事。我知道我會成爲哪種人。

尋找閱讀代碼的機遇 – 不要錯失

咱們喜歡編寫新代碼,是由於咱們此次能正確處理問題。好吧,也許不是此次,但必定是下次。事實上是,你常常改進你的技術,但你從沒有恰當地處理問題。這就是編寫新代碼的價值所在,你能夠歷練並磨練你的技能,但閱讀和把玩其餘人編寫的代碼,(若是沒有更多的價值,)也是有一樣多的價值。你不只能從中得到一些有價值的技術知識,也能收穫領域知識,領域知識一般仍具更多價值(畢竟,代碼是文檔的最終形式)。

即使代碼寫得很神祕,無任何慣例可言,但仍是有價值。你知道我在說的代碼,它幾乎看起來晦澀難懂,但不是有意而爲之(因某些緣由,Perl 語言代碼一般是這樣的)。無論何時我看到那樣的代碼,我都會這樣想: 把它想象成只有你破譯它後才能學到的東西。是的,這是主要的痛楚之處,但要接受它,有時候你本身也會因瑣碎的緣由而寫出那種令人困惑的代碼(否定沒有用,你知道這是真的)。好了,若是你花些時間來閱讀那樣的代碼,你更有可能最終寫出一樣的代碼。並不說你將會寫出那樣的代碼,但你有能力寫出那樣的代碼。最後,態度一般是最重要的(編注:態度決定一切)。若是你視閱讀代碼爲平常繁瑣的工做,那它就是(繁瑣的工做),而且你會逃避,但若是你視其爲一個機遇,那好事終將到來。

 

注:本文轉載自http://blog.csdn.net/sdulibh/article/details/38412955

相關文章
相關標籤/搜索