作爲曾經的程序員,我犯下了這些尷尬的錯誤

有句話說得很有道理:“如果你以前寫的代碼不會讓你感到尷尬,那你就沒有進步”。40多年前,我開始了編程生涯,30多年前開始成爲職業程序員,我在這個過程中犯了很多錯誤。作爲一名計算機科學教授,我鼓勵學生從錯誤中學習,無論是他們自己的錯誤、我的錯誤,還是經典的錯誤示例。我覺得是時候反省一下自己的錯誤了,讓自己保持謙遜,並希望有人能夠從中吸取教訓。

錯誤第三名:微軟的C語言編譯器

在完成麻省理工學院大二的學業後,我開始在微軟的C語言編譯器團隊實習,成了一個不折不扣的編程青少年。在做了一些繁瑣的工作後,我着手編譯器最有趣的部分:後端優化。具體地說就是改進switch語句的x86生成代碼。

我當時居然想把我能想到的每種case都生成最優的機器碼。如果case的值比較密集,我就把它們作爲跳轉表的索引。如果它們有公約數,我就利用公約數讓跳轉表更密集。如果所有值都是2的冪,我就使用另一種優化。

如果所有值都不滿足任何一個條件,我就把這些分開,並遞歸調用我的代碼。

真的是一團糟。

多年後,我聽說後來接手我代碼的人討厭死我了。

學到的教訓

正如David Patterson和John Hennessy在《計算機組成和設計》(Computer Organization and Design)一書中所寫的那樣,計算機架構(也是軟件工程)的一個重要原則是“優化常見的case”:

優化常見的case比優化罕見的case更能提高性能,且常見的case往往比罕見的case簡單。這個常識性的問題說明你知道常見的case是什麼,而它們原本只有通過仔細的實驗和測量才能獲得。

我確實試圖找出switch語句在實際當中是怎樣的(有多少種case,常量的分佈式情況),但在1988年還沒有這方面的數據。即使是這樣,也不能說明當我在看到已有的編譯器不能生成最優代碼時就可以一直往編譯器裏增加新的case。

我應該找一位有經驗的編譯器開發者聊聊,討論一下常見case是怎樣的,然後乾淨利落地處理這些case。我本來可以寫更少的代碼,正如Stack Overflow聯合創始人Jeff Atwood所寫的那樣,程序員最大的敵人就是自己:

我知道你的出發點是好的,我們都一樣。我們是程序員,我們喜歡寫代碼,寫代碼是我們的職責所在。我們從來就沒有遇到過一個無法用一撮代碼解決的問題……

但大多數程序員很難承認這一點,因爲他們非常喜歡寫代碼,但最好的代碼其實是“無代碼”。你寫的每一行代碼都需要經過調試,需要被閱讀和理解,需要別人維護。每次在寫新代碼時,你都應該這麼做,因爲你已經用盡了其他辦法。代碼就是我們的敵人,因爲我們寫了太多該死的代碼。

如果我寫的那些用來處理常見case的代碼很簡單,那麼維護起來就很容易,而不是留下一個沒有人願意(或不敢)收拾的爛攤子。

錯誤第二名:社交網絡廣告

當我在谷歌從事社交網絡廣告方面的工作時,寫了一些C++代碼,看起來像這樣:

for (int i = 0; i < user->interests->length(); i++) {
  for (int j = 0; j < user->interests(i)->keywords.length(); j++) {
      keywords->add(user->interests(i)->keywords(i)) {
  }
}

看到這段代碼的程序員可能會看出這個問題:最後一個參數應該是j而不是i。但當時單元測試沒有發現這個錯誤,評審代碼的人也沒有發現。

這段代碼在一天深夜裏發佈了,導致數據中心所有的計算機都崩潰了。

不過,這也沒什麼大不了的。因爲代碼先是在一個數據中心進行測試,然後纔會被髮布到其他地方。所以,只要讓SRE回滾一些代碼就可以解決問題。第二天早上,我收到了一封電子郵件,郵件中附帶了堆棧崩潰的信息。我修復了代碼,並添加了一些可以檢測到這個錯誤的單元測試。

學到的教訓

有些人認爲,犯這麼大的錯可能會丟掉工作。但程序員確實會犯錯,但同樣的錯誤不太可能一直犯。

事實上,我確實認識一個程序員,儘管他是一名優秀的工程師,但因爲一個無心的錯誤而被解僱。後來他被谷歌錄用(還升職了),谷歌根本不在乎他犯的這個錯誤,他在面試過程中也承認了這一點。

IBM傳奇董事長兼CEO Thomas Watson有一個非常有趣的故事:

當時有一個接近100萬美元的政府大訂單。IBM公司——不,應該說是老Thomas Watson——要求搞定每一筆訂單。不幸的是,銷售代表搞砸了,IBM在競標中失利了。那天,銷售代表來到Watson的辦公室。他坐下來,把辭職信放在Watson的辦公桌上。Watson看都沒看一眼就知道那是什麼了,這也正是他希望看到的東西。

他問:“發生了什麼事?”

銷售代表說明了談判的每一個過程,說明了哪些地方出了錯,以及他本可以採取哪些不同的做法。最後他說:“謝謝你,Watson先生,你給了我一個解釋的機會。我知道我們需要這筆訂單。我知道這對我們意味着什麼”。說完,他起身離開了。

Thomas Watson在門口把他攔住了,並把辭職信遞還給他,然後看着他的眼睛說:“我剛剛花了一百萬美元給你上了一課,爲什麼還要接受這封辭職信呢?”

我的一件T恤上寫着:“如果人們能從錯誤中吸取教訓,現在一定可以拿到碩士學位了”。但實際上,我已經有博士學位了。

錯誤第一名:App Inventor API

真正令人難堪的是有一種錯誤影響到了大量的用戶,但卻存在了很長一段時間,而犯下這個錯誤的人本來應該對它很瞭解。我犯下的最大的錯誤在所有方面都符合這種情況。

更糟即更好

90年代,當我還是個研究生的時候,我看過Richard Gabriel寫的“更糟即更好”這篇文章(https://www.dreamsongs.com/RiseOfWorseIsBetter.html)。我非常喜歡這篇文章,所以我把它推薦給了我的學生們。如果你還沒看過這篇文章,現在就看吧,文章很短。

這篇文章從幾個維度對比了“做正確的事”和“更糟即更好”的哲學:

做正確的事:設計必須簡單,無論是實現還是接口。保持接口簡單比保持實現簡單更爲重要。

更糟即更好:設計必須簡單,無論是實現還是接口。保持實現簡單比保持接口簡單更爲重要。

App Inventor

我是谷歌App Inventor團隊的成員之一,App Inventor是一個在線編程環境,讓初學者能夠通過拖放的方式創建Android應用程序。

早在2009年,我們就急於發佈alpha版本,這樣就可以趕上夏季的教師研討會,並在秋季的課堂上使用。我自願要求實現sprite。當時我天真地回憶起年輕時用它們在TI-99/4上開發遊戲時的情景。sprite是一個2D對象(比如宇宙飛船、小行星、球和槳),可以移動,並與其他程序元素髮生交互。

我們用Java實現了App Inventor,它本身就是面向對象的,所有東西都是面向對象的。由於球和圖像sprite在行爲上非常相似,我創建了一個抽象類,帶有一些屬性,比如X、Y、Speed和Heading。它們有一些常見的方法,比如碰撞檢測、從屏幕邊緣反彈等。

球和圖像sprite之間的主要區別是繪製的內容不同:球或位圖。因爲我先實現了圖像sprite,所以很自然地讓x和y座標作爲圖像放置的起始位置。

在實現了圖像sprite之後,我認爲只需要一點額外的代碼就可以實現球sprite對象。問題在於我實現的方式太簡單了:X和Y軸座標指定了球的左上角位置。

但其實我應該用X和Y軸指定圓心的位置,就像數學書裏教的那樣。

與我犯下的其他錯誤不一樣,其他錯誤主要影響到了我的同事,但這個錯誤卻影響到了數以百萬計的App Inventor用戶,其中有很多是兒童或編程新手。他們不得不在每個使用了球組件的App中做一些額外工作。雖然我可以一笑置之,但我真的爲自己犯下的錯誤感到羞愧。

十年後,也就是在最近,我給這個錯誤打了個“補丁”。我說“打了個補丁”而不是“修復”,因爲正如偉大的Joshua Bloch所說的——“API是永恆的”。我們不能做出任何會影響現有程序的改動,所以我們添加了一個叫作OriginAtCenter的屬性,它在舊程序中默認值爲false,在未來會改爲true。如果用戶想知道爲什麼球的原點總是不在中心位置,答案是:十年前,一個程序員因爲偷懶沒有把API設計好。

學到的教訓

如果你需要開發API(幾乎所有的程序員都需要),應該遵循最佳實踐,你可以從Joshua Bloch的視頻“如何設計一個好的API以及它爲什麼這麼重要”(https://www.youtube.com/watch?v=aAb7hSCtvGw)中學到一些東西,或者看看這篇總結性的文章(https://www.infoq.com/articles/API-Design-Joshua-Bloch/),其中包括:

API可能是你最大的資產或負債之一。好的API能夠長久地留住用戶,不好的API會成爲長久的維護噩夢。

公開的API就像鑽石一樣,是永恆的。你只有一次機會把它做好,所以要全力以赴。

早期的API草稿應該儘量簡短,通常是類、方法簽名和一行描述。這樣,如果沒能一次性設計好API,就可以很容易地進行重新調整。

在實現API之前,爲它們編寫測試用例代碼,這樣可以避免實現不好的API

如果做了以上這些事情,我可能就會發現設計方面存在的錯誤,並將其修正。如果沒有,或許我的其他隊友會。任何一個可能與人們伴隨多年的決策都值得至少花一天時間去思考(不管是否與編程有關)。

Richard Gabriel的文章標題(“更糟即更好”)是說,即使產品有缺陷,也要率先推向市場,而不是老想着創造完美。然而,當我回看sprite代碼時,我發現用正確的方法做好事情並不需要更多的代碼。無論如何,我當時做了一個糟糕的決定。

結論

程序員每天都會犯錯誤,無論是代碼錯誤還是嘗試新事物失敗。成爲一名程序員不一定都會犯跟我一樣嚴重的錯誤,但如果不承認錯誤並從錯誤中學習,就不可能成爲一名優秀的程序員。

作爲一名老師,我經常遇到一些學生,他們擔心自己不適合學習計算機科學,因爲他們會犯錯,而且我知道“騙子綜合症”在技術行業十分盛行。我希望你們會記得這篇文章中提到的經驗教訓,但我最希望你們記住的是,不管是笑、還是哭,我們都會犯錯。

原文鏈接:

https://stackoverflow.blog/2019/10/29/my-most-embarrassing-mistakes-as-a-programmer-so-far/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章