加載中 ...
首頁 > 建站 > 經驗 > 正文

一個30年老程序員的修煉之道

2019-07-10 12:07:36 來源:

黑客2

聲明:本文來自于微信公眾號InfoQ(ID:infoqchina),作者:?Julio Biason,授權站長之家轉載發布。

本文作者 Julio Biason 從 1990 年開始從事軟件開發工作,以下是他從過去 30 年軟件開發生涯總結出來的一系列冷笑話式的經驗之談。

?1 關于軟件開發

規范先行,然后才是代碼??

在知道要解決什么問題之前,請不要寫代碼。

Louis Srygley 說過:“如果沒有需求或設計,編程就成了一門往空文本里添加 bug 的藝術”。

有時候,僅僅一兩段簡單的描述就足以說明一個程序是用來干什么的。

每當你停下來,看著代碼,并開始思考下一步該做什么的時候,通常是因為你不知道下一步該做什么。這個時候,你需要做的是與同事討論,或者可能需要重新思考之前的解決方案。

用批注的方式把實現步驟寫下來??

如果你不知道從哪里下手,先使用英語(或者你的母語)把程序的流程寫下來,然后在批注中添加代碼。

你也可以把每個批注當成是一個函數,然后用代碼實現它們。

用好 Gherkin??

Gherkin 是一種測試 DSL,用來描述“系統處于某種狀態,如果發生某個事件,這就是所期望的狀態”。即使你不使用測試工具,Gherkin 仍然可以幫你更好地了解你能夠從程序中獲得哪些東西。

單元測試還不夠,最好還要有集成測試??

在我目前的工作中,我們會進行模塊和類級別的測試。這些測試可以讓我們知道模塊或類的行為,但無法讓我們知道系統整體的行為——而集成測試可以告訴我們這些。

測試讓 API 變得更健壯??

代碼是分層的:存儲層負責數據持久化,處理層負責轉換存儲的數據,視圖層負責呈現數據,等等……

分層測試可以讓你更好地了解各個層的 API,知道如何更好地調用各個層:API 會不會太復雜了?要進行一次簡單的調用,是否需要保留很多數據?

通過命令行運行測試用例??

命令行對于任何一個項目來說都很重要。在你知道了如何使用命令來執行測試用例之后,就可以進行自動化測試,然后將它們集成到持續集成工具中。

做好丟棄代碼的準備??

有很多人在開始使用 TDD 時會感到很惱火,因為他們可能需要重寫很多代碼,包括已經寫好的那些。

而這正是 TDD 的“設計哲學”:隨時準備好丟棄你的代碼。隨著對問題研究的深入,你對要解決的問題越來越了解,不管之前寫了怎樣的代碼,它們終究不是解決問題的最終方案。

不過你不用擔心,代碼并不是一堵墻,如果將它們丟棄掉,也算不上是浪費磚塊。但花在寫代碼上的時間確實會一去不復返,不過換來的是你對問題更好的了解。

好的編程語言自帶測試框架??

可以肯定地說,如果一門編程語言的標準庫自帶了測試框架,哪怕這個框架很小,它的生態系統也會得到比那些不提供測試框架的編程語言更好的測試,即使外部為這些語言提供了很好的測試框架。

想得太長遠是一種浪費??

有時候,程序員在解決一個問題時會想方設法尋找可以解決所有問題的方法,包括那些可能會在未來出現的問題。

但事實是,未來的問題可能永遠都不會出現,而你不得不去維護一大堆在未來可能永遠都用不上的代碼,甚至重寫所有代碼。

問題要一個一個解決,解決完眼前的,再解決下一個。到了某個時刻,你可能會從解決方案中找到某種模式,而這些模式才是用來解決“所有問題”的良方。

寫文檔其實是在善待未來的你??

誰都知道,給函數、類或者模塊編寫文檔是一件苦差事,但這也是在給未來的你省下很多麻煩。

文檔就是契約??

代碼的文檔實際上是一種契約:文檔里怎么寫的,這個函數就是用來做什么的。

如果后面你發現代碼和文檔不匹配,那么就是代碼有問題,而不是文檔有問題。

如果一個函數出現“和”邏輯,那一定有問題

一個函數應該只做一件事情。在給函數編寫文檔時,如果你發現需要用到“和”邏輯,那說明這個函數所做的事情不止一件。這個時候需要把函數拆成多個,不要在文檔里出現“和”邏輯。

不要將布爾類型作為參數??

程序員在設計函數時通常喜歡在參數列表里添加布爾類型,但請不要這么做。

舉個例子:假設你有一個消息系統和一個函數,這個函數將所有消息返回給用戶,叫作“getUserMessage”。不過,有時候用戶需要獲取整條消息,有時候只需要獲取消息概要(比如消息的第一段)。于是,你加了一個布爾類型的參數,叫作“retrieveFullMessage”。

再次提醒,最好不要這么做。

因為當別人看到“getUserMessage(userId, true)”這樣的代碼時,他們可能不知道“true”是什么意思。

你可以新增兩個函數“getUserMessageSummaries”和“getUserMessageFull”,然后讓這兩個函數分別調用“getUserMessage”,并將 true 或 false 傳給它,這樣可以保證對外的接口是清晰的。

在修改接口時要小心??

上面提到了重命名函數,如果調用函數的代碼完全處在你的控制之下,那么這么做就沒什么問題,你只需要把需要修改的地方找出來,然后改掉它們就可以了。

但如果被重命名的函數是作為庫的一部分暴露給外部,那就不能隨意修改了。因為這樣做會影響到所有調用函數的代碼,而這些代碼不在你的掌控之下,修改函數名只會給這些代碼的主人帶來大麻煩。

你可以新增一個函數,然后把舊函數標記為已棄用。經過一些版本發布之后,就可以慢慢將舊函數去掉。

好的編程語言自帶集成文檔??

如果一門編程語言為函數、類、模塊提供了文檔或者生成文檔的方式,那么你就可以肯定,這門語言的函數、類、模塊、庫和框架也會有很好的文檔(即使不是最好的,但肯定不會差)。

相反,不提供集成文檔的編程語言通常只有糟糕的文檔。

在選擇編程語言時,不要只看語言本身??

編程語言是你用來解決問題的得力工具,但不僅限于此:它還有構建系統,有依賴管理系統,有工具、庫和框架,有社區……

在選擇編程語言時,不要僅僅因為它用起來很簡單。記住,你可以認為一門語言的語法很簡單,但也需要考慮到社區因素。

有時候,讓程序奔潰比什么都不做更好??

這句話聽起來有點奇怪:與其捕獲了錯誤卻什么都不做,還不如不捕獲錯誤。

在 Java 里,經常會有人這么干:

    image.png

    這幾行代碼除了把異常打印出來之外,什么都沒做。

    如果你不知道該怎么處理它,還不如把它拋出來,這樣起碼可以知道什么時候會出現這樣的異常。

    如果你知道怎么處理異常,那就處理好它??

    這與上一條剛好相反:如果你知道什么時候會拋出異常、錯誤或得到返回結果,并且知道怎么處理它們,那就處理好它們。可以把錯誤消息顯示出來,試著把數據保存到某個地方,把用戶的輸入寫入日志文件,等后面再回頭來處理。

    類型系統會告訴你數據長什么樣子??

    內存里存的只不過是一系列字節,而字節只不過是從 0 到 255 的數字,這些數字的意義需要通過編程語言的類型系統來說明。

    例如,在 C 語言中,“char”類型的 65 其實就是字母“A”,而“int”類型的 65 就是數字 65。

    在處理數據時要牢記這個。

    下面是我最近看到的一些 JavaScript 代碼,有人用這種方式判斷 True 的值,但顯然是錯的。

      image.png

      如果數據有模式,用結構來保持數據模式??

      你可能會用 list(或者 tuple)來保存數據簡單的數據,比如只有兩個字段的數據。

      但如果數據是有模式的,也就是有固定格式的,那么就應該使用合適的結構來保持數據的模式,比如使用結構體或類。

      停止盲目跟風??

      “盲目跟風”的意思是:如果有人這么做了,那我們也可以這么做。大多數時候,盲目跟風是解決問題最簡單的方式。

      “如果某個大公司是這樣保存數據的,那么我們也可以這樣”。

      “如果有大公司撐腰,那它就是好東西”。

      “正確的工具”可能只是個人喜好???

      “正確的工具”本來應該是指使用合適的工具來完成某個任務,例如,使用合適的編程語言或框架來代替目前使用的語言或框架。

      但每當我聽到有人提到這個說法時,他們只不過是想用他們喜歡的語言或框架來替代真正合適的語言或框架。

      “正確的工具”不一定是正確的??

      假設你所在的項目需要處理一些文本,你可能會說:“讓我們使用 Perl 吧”,因為你知道 Perl 很適合用來處理文本。

      但你忽略了一點:你周邊的人只懂 C 語言,不懂 Perl。

      當然,如果這個項目只是個無關緊要的小項目,那么可以嘗試使用 Perl,但如果這個項目對公司來說很重要,那么最好是使用 C 語言。

      不要去修改項目以外的東西??

      有時候,人們會去直接修改外部的工具、庫或框架,比如直接修改 WordPress 或 Django 的代碼。

      這樣很快會讓項目變得難以維護。當外部工具發布新版本時,你不得不去同步變更,但很快你會發現,之前修改的東西對新版本不再有效了,所以只能保留舊版本,而舊版本可能有很多 bug。

      數據流勝過設計模式??

      如果你知道數據是怎么流經系統的,就可以寫出更好的代碼,這比應用各種設計模式要好得多(這只是個人觀點)。

      設計模式是描述解決方案,不是解決方案

      同樣,這也只是我的個人觀點。

      大多數時候,人們會應用設計模式,試圖通過設計模式來找到解決方案,但結果是不得不對解決方案(甚至是問題本身)作出調整來匹配設計模式。

      這樣的事情我見得多了:先是遇到問題,然后找到一個接近解決方案的設計模式,接著開始應用設計模式,然后在解決方案里添加很多東西,讓它與設計模式相匹配。

      學會基本的函數式編程??

      你不一定要成為函數式編程專家,但請記住,有時候需要保持數據的不變性。使用新值來創建新元素,如果有可能,不要讓函數或類擁有內部狀態。

      認知成本是代碼可讀性殺手??

      “認知沖突”是“需要同時記住兩件(甚至更多)東西才能幫你理解事物”的另一種說法。同時記住不同的東西會給大腦增加負擔,并且會削弱事物的相關性(因為你需要在腦子里保留更多的東西)。

      例如,通過相加布爾類型來判斷 True 值的個數就是一種輕微的認知沖突。假設有一個“sum()”函數,你一看就會知道這個函數是用來計算一個列表中所有數值的和。而我卻見過有人用這個函數來計算一個布爾值列表中所有 True 值的個數,這樣很讓人感到困惑。

      魔術數 7??

      魔術數7(https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two)解釋了人類在同一時間可以記住多少件東西。

      如果你有一個函數,這個函數調用第二個函數,第二個函數又調用第三個函數,第三個函數又調用第四個函數,第四個函數又調用第五個函數,第五個函數又調用第六個函數,到最后你會發現這樣的代碼可讀性很差。

      或者更進一步,你拿到一個函數返回的結果,把它傳給第二個函數,然后拿到第二個函數返回的結果,把它傳給第三個函數,一直這樣重復下去……

      但問題是:

      1. 現如今,人們談論更多的是魔術數 4,而不是 7;

      2. 考慮使用函數組合(先調用第一個函數,再調用第二個……)代替函數調用(第一個調用第二個,第二個調用第三個……)。

      捷徑雖好,但好處是短暫的??

      有很多編程語言、庫或框架會幫你簡化代碼,減少輸入。

      但走捷徑會在以后給你帶來更多麻煩,甚至會讓你不得不重新使用復雜的代碼代替簡單的代碼。

      所以,在采取捷徑之前,要先了解它們。

      你不一定要先寫出復雜的代碼,然后使用捷徑來簡化:你可以采取捷徑,但一定要知道走捷徑可能會導致什么后果,或者知道在不走捷徑的情況下該如何實現代碼。

      抵制“簡單”的誘惑??

      IDE 為我們提供了很多自動完成功能,讓我們可以更容易地構建項目,但你知道背后都發生了什么嗎?

      你知道構建系統的工作原理嗎?如果沒有 IDE,你知道怎么構建項目嗎?

      如果不借助自動完成功能,你記得那些函數的名字嗎?

      所以,我們要對這些背后的東西保持一顆好奇心。

      給日期帶上時區??

      在處理日期時,記得帶上時區。因為你的電腦或服務器的時區有可能不對,在排查問題時可能會因為接口返回錯誤的時區而浪費你很多時間。

      總是使用 UTF-8??

      在進行字符編碼時也會遇到與日期類似的問題。所以,總是把字符串轉成 UTF8 格式,使用 UTF8 格式保存在數據庫中,API 返回的字符串也使用 UTF8 格式。

      極簡主義??

      擺脫 IDE 可以從“極簡主義”開始:只使用編譯器和帶有代碼高亮功能的編輯器,并用它們構建和運行代碼……

      但其實這樣做并不容易。不過當你再次使用 IDE 時,你就會知道按下那些按鈕之后 IDE 會做些什么。

      日志用于記錄事件,不需要展現給用戶??

      在很長一段時間內,我一直通過日志告訴用戶系統發生了什么。

      但其實我們可以使用標準輸出告訴用戶系統發生了什么事件,使用標準錯誤輸出告訴用戶系統發生了什么錯誤,然后使用日志記錄事件,便于后續分析這些事件。

      你可以把日志看成是以后需要從中抽取信息的數據,它們不是面向用戶的,所以不一定非要人類可讀的。

      調試器被過度高估了??

      有很多人認為不帶有調試功能的代碼編輯器都不是好編輯器。

      但是,代碼一旦進入生產環境,你就用不了調試器了,即使是你最喜歡的 IDE 也用不上了,但日志卻無處不在。在發生故障時你可能不知道是怎么回事,但你可以從日志中查找原因。

      我并不是說調試器毫無用處,只是它不像大多數人認為得那樣有用。

      一定要使用版本控制系統???

      你可以說“我只是想使用這個項目來學點東西”,但它不能成為你不使用版本控制系統的理由。

      如果你從一開始就使用版本控制系統,在出現問題之后可以很容易回滾。

      一個變更對應一個提交??

      我經常看到代碼提交里有這樣的消息:“修復問題 #1、#2 和 #3”。除非這三個問題是重復的(其中兩個應該是已關閉的),否則應該分成三次提交,每個提交對應一個問題。

      如果代碼改過頭,可以使用“git add-p”????

      Git 允許用戶通過“-p”參數進行部分提交,也就是選擇只提交部分代碼變更,把剩下的留到后面再提交。”?

      按照數據或類型來組織代碼,而不是功能??

      很多項目的代碼結構類似下面這樣:

        image.png

        也就是說,他們是基于功能來組織代碼的(所有模型放在同一個目錄中,所有過濾器放在另一個目錄中)。

        這樣做其實也沒有什么問題,只是如果按照數據來組織代碼的,那么在將項目拆分成小項目時就會更容易些。

          image.png

          這樣你就可以獨立出各個模塊,比如只處理 Data1 的模塊,或者只處理 Data2 的模塊。

          如果有另外一個項目需要處理 Data1,你就可以重用 Data1 模塊了。

          創建公共庫??

          我見過很多項目使用同一個單獨的大代碼庫,與其這樣,為什么不把公共部分提取出來做成公共庫,然后在各個項目里引用這些庫呢?

          學會使用監控??

          以前,為了了解系統的行為,我會往系統里添加很多度量指標。在習慣了這些之后,如果一個系統沒有監控,我就會覺得很奇怪。只是通過發送簡單的請求根本不足以判斷一個系統是否健康。

          盡早給系統加入監控可以讓你更好地了解系統的行為。

          使用配置文件??

          假設你寫了一個函數,它只接受一個值作為參數。如果你有兩個值需要分兩次傳給它,就需要調用這個函數兩次。

          你也可以使用配置文件,把這兩個值分別寫在兩個配置文件里,然后運行這個程序兩次。

          命令行選項很有用??

          在將參數寫到配置文件里之后,你還可以增加命令行參數,用來指定使用哪個配置文件。

          有很多編程語言都提供了命令行參數解析器,可以用它們構建一個好用的命令行程序。

          不只有函數組合,我們還有程序組合??

          Unix 的哲學是“一個程序只做一件事,而且做到極致”。

          你可以使用一個程序和多個配置文件,但如你需要使用所有程序的運行結果,那該怎么辦?

          你可以再寫一個程序,把多次運行結果合并起來變成一個。

          程序組合也可以從簡單的開始??

          程序組合模式到最后會變成微服務架構,而微服務架構要求服務之間具有良好的通信機制。

          不過你也不用擔心個問題,你可以讓程序通過文件來通信,一個往文件里寫,一個從文件里讀。

          在你了解了網絡通信機制之后再去考慮其他更復雜的通信問題吧。

          把代碼優化工作留給編譯器??

          你想要獲得更好的代碼性能,于是總想著在這里優化一點,在那里優化一點。

          但你要知道,優化代碼正是編譯器的拿手好戲。聰明的編譯器甚至會幫你把返回相同結果的代碼移除掉。

          你需要做的是如何更好地設計你的代碼,而不是想方設法改進已有的代碼。

          延遲求值??

          在很早以前,一些編程語言會在表達式被用到時求值,而不是在它們出現時求值。

          Lips 在很早以前就這么做了,現在有很多編程語言也這么做了。

          “廣域創業網”的新聞頁面文章、圖片、音頻、視頻等稿件均為自媒體人、第三方機構發布或轉載。如稿件涉及版權等問題,請與

          我們聯系刪除或處理,客服郵箱,稿件內容僅為傳遞更多信息之目的,不代表本網觀點,亦不代表本網站贊同

          其觀點或證實其內容的真實性。

          狗狗币官方钱包 国标麻将 日本麻将 时时每天赢5万的方法 1l选5开奖结果 今晚南粤36选7开奖结果 秒速时时软件 湖北快三200期开奖走势图 福建快3开奖结果今天 快3福彩网网址 香港赛马排位 安徽时时有没有单双