我從高級軟件工程師身上學到的那些經驗與教訓「轉」

一年之前,我開始在彭博擔任全職工作。從那時起,我就在構思這篇文章。我想象自己能夠在時機成熟時,把自己的想法都傾訴於紙端。但剛剛過去一個月,我就意識到這並非易事:隨著工作的推進,我忘掉了很多自己剛剛學到的東西。這些東西快速內化,使我的大腦開始欺騙自己,令我誤以為自己早就掌握了這些清晰記得的知識,或者是認定自己從未聽說過那些實際上是被忘記了的內容。

正因為如此,我才開始保留自己的日誌。每當遇到有趣的情況,我都會把它記錄下來。感謝坐在我身邊的高級軟件工程師們,我可以認真觀察他們在做什麼、與我的做法又有何區別。我們會經常結對編程,這能夠大大降低工作的難度。另外,在我們的團隊文化當中,“窺探”其他人的編碼過程並不是什麼不光彩的事情。每當我感覺有趣的事情要發生時,總坐很快轉過身去查看。這種敏銳,讓我總能快速弄清事情的來龍去脈。

下面來看看坐在一位高級軟件工程師身旁一年,我都學到了哪些重要經驗。

編寫代碼

如何命名



我在工作中接觸的第一項任務是開發一款 React UI。當時我們擁有一個主組件,用於容納其它所有組件。我喜歡在代碼當中加點幽默元素,所以我把它命名為 GodComponent。但在代碼審查時,我才意識到為什麼命名工作如此重要、也如此困難。

計算機科學領域有兩大難題:緩存失效、命名以及緩衝溢出錯誤。 -—— Leon Bambrick

我命名的每一段代碼都包含隱藏的含義。GodComponent?這個組件的含義,就是我會把所有不知道該放在哪的組件都放在這裡。它囊括一切。如果我把它命名為 LayoutComponent,後續我才會意識到它的作用就是佈局分配,其中不包含任何狀態。

我發現的另一項心得在於:如果其體積過於龐大,就像是這裡提到的包含大量業務邏輯的 LayoutComponent,那麼我就會意識到是時候進行重構了,因為通過名稱就能看出業務邏輯並不屬於這裡。但使用 GodComponent 這個名稱,我們無法判斷業務邏輯出現在這裡是否正常。如何命名集群?最好是在運行了服務之後再對集群進行命名,而後根據運行內容的變化重新調整名稱。最終,我們用自己的團隊名稱完成了集群命名。

函數命名的情況也是一樣。doEverything() 這個名字就不怎麼樣,其會帶來嚴重的後果。如果這項函數能夠完成所有操作,那麼我們將很難測試函數當中的某些特定部分。而且無論這個函數有多大,我們都會覺得很正常,畢竟它的名字可是叫“everything”。所以,最好的辦法當然是更換名稱,進行重構。

但是,我們在命名中也要考慮到另一類問題。如果名稱的含義太過具體並忽略了某些細微差別,該怎麼辦?例如,在 SQLAlchemy 當中調用 session.close() 時,關閉會話不會關閉基礎數據庫連接。(我本應該跳出手冊限制,對這項 bug 進行處理,具體情況將在調試部分進一步說明。)在這種情況下,我們可以考慮 x, y, z 這樣的名稱,而非 count(), close(), insertIntoDB(),從而避免為其分配隱含的意義。太過具體,會迫使我們不得不在後續維護時費力檢查這些函數到底是用來幹嘛的。

最後,當時的我從來沒想到命名會成為值得單獨一提的重要工作。

遺留代碼與下一位開發者

大家有沒有面對一段代碼時,感覺摸不著頭腦?他們為什麼要這麼寫?這完全說不通啊。

我就“有幸”接手過遺留代碼庫。其中就存在類似於“跟穆罕默德確認過情況之後,取消註釋”這類說明。這話是誰說的?穆罕默德又是哪位?

在這方面,我們不妨做個角色轉換——考慮下一位接手我所編寫代碼的開發者。他們同樣會發現我的代碼非常奇怪。同行評審能夠很好地解決這個問題。這不禁讓我想到上下文原則,即:瞭解團隊開展工作時的實際處境。

如果我跑去忙別的事,稍後又回來,我可能也無法重新建立這種上下文。我坐說,“當時我是怎麼想的?這根本沒道理……哦等等,我原來是這麼幹的。”

正是為了實現這種提示作用,文檔與代碼註釋才會如此重要。

文檔與代碼註釋

文檔與代碼註釋的意義,在於保持上下文並分享知識。

正如 Li 在如何構建良好軟件中所言,“軟件的主要價值並不在於生成的代碼,而在於生成代碼的過程中開發者所積累下來的知識。”

“軟件的主要價值並不在於生成的代碼,而在於生成代碼的過程中開發者所積累下來的知識。” - Li

我們當時有一套面向 API 端點的隨機客戶端,好像從來就沒人用過。那麼要不要把它刪除掉?畢竟這也屬於技術債務。

但如果我告訴大家,每年在特定的國家 / 地區,都會有 10 名記者將新聞發送到該端點,又該怎麼辦?我們是如何測試的?如果沒有文檔(也確實沒有),我們找不到答案。因此,我們刪除了該端點,並在對應時間點上發現了問題——這 10 名記者無法發送 10 份重要的報道,因為該端點已經不復存在。

瞭解產品的成員已經離開了團隊,現在只能靠代碼當中的註釋來解釋該端點的作用。

從這件事上,我意識到文檔是每個團隊都在努力解決、但卻難以奏效的問題。除了代碼文檔之外,與代碼相關的流程也有類似的情況。

時至今日,我們也沒有找到完美的解決方案。

原子提交

如果必須要回滾(而且回滾需求早晚會出現,我們將在測試部分具體討論),此次提交還是否有意義?

在刪除垃圾代碼時要充滿信心

刪除垃圾或者過時的代碼總是讓我感覺很不舒服。我總覺得以往的工作成果有種神聖不可侵犯的意義。我那時候認為,“在他們寫與這些代碼時,肯定是有所考量的。”這是一種傳統的理解方式,而且與第一性原則有所衝突。出於類似的理由,我在每年進行代碼審查與清理時也是困難重重。這樣的糟糕習慣,讓我吃了不少苦頭。

我曾經嘗試調整代碼問題,也有些老成員習慣於繞過這些代碼。但刪除,刪除聽起來更嚴重正經。一個永遠用不上的 if 語句、一個永遠用不上的函數,會在我的一聲令下徹底消失,這樣不好。因此,我更多是把自己的函數覆蓋在上面。但這並沒有減少技術債務,只是增加了代碼的複雜性與誤導性。如此一來,後繼者將更難把這些片段以有意義的方式拼湊起來。

我現在採取的方式是:總會存在我們無法理解的代碼,也總會存在我們永遠不會使用的代碼。刪除這些永遠不會使用的代碼,但對無法理解的代碼保持謹慎的態度。

代碼審查

代碼審查是學習中的重要組成部分。審查的過程,就是從編寫代碼、到了解如何更好地編寫代碼的反饋循環。我們自己的編碼思路,跟其他人的編碼思路有何不同?我在每一次代碼審查時都會問自己:“他們為什麼要這樣做?”如果實在找不到合理的答案,我就會跟他們當面聊聊。在第一個月的過渡期結束之後,我開始瘋狂地從同事的代碼當中查找錯誤(當然,他們也不會放過我)。真的很瘋狂,這也讓評審工作變成一項有趣的調劑——或者說像是一種遊戲,能夠改善我們編碼水平的小遊戲。

我的心得:在理解代碼作用之前,不要輕下斷言。


原文鏈接:https://neilkakkar.com/things-I-learnt-from-a-senior-dev.html

中文譯文:https://www.infoq.cn/article/BJEejiFAe6giebc14tep


分享到:


相關文章: