編程中思考

前言

前不久在微博上關注一個ID為的微博用戶(后來百科之后才發現又是國內程序員中的牛人之一,就是池大大文章經常提到的“王垠”),無意間查看了他之前發的微博,其中許多都是對一些編程語言的犀利評價,而所發的微博之中一篇名為編程的智慧長文吸引了我注意,出于對內容的好奇就對這篇文章進行了閱讀。閱讀過程中,發現了博主對編程和設計語言的思考都有著獨立深刻的見解,文章里都是對如何在編程中成長和對代碼正確實踐的干貨指導,旨在幫助那些把編程作為主職工作的人們在技術開發的路上更好地找到屬于自己正確的方向。閱讀完這篇文章后,我發現里面提到的條條編程規范和編碼要求,對自己現在以及未來的編程之路都有著很大的價值,也讓我設想如何編程中更好融入自己的思考,對代碼的反思以及對程序設計的反思。
在閱讀了文章后幾天,自己也不時地想如何在編程中有效成長,覺得有必要將文章《編程的智慧》中對針對自己有價值的內容提取記錄下來,進行自我針對性的思考和實踐,于是也就有了寫這篇文章念頭。

內容

編程提高 - 代碼提煉

文中提到提高編程水平最有效的方法——提煉代碼,學會對自己所寫的代碼進行思考。當寫完一段代碼看看是不是太長了,還能夠縮減嗎?是不是可讀性太差了,要靠不必要的注釋讓其它人甚至以后的自己才能看懂這段代碼。用這樣的思考過程來提煉自己的代碼,減少代碼中的冗余。就算是閱讀開源庫或者一些其它人的代碼時,也應該用這樣思考方式來閱讀他們的代碼,來提取他人代碼中的精華。這里引用下文章里一句話"作為一個好的程序員,應該看他刪掉過自己多少的代碼,留下來又是怎樣的代碼。"只有對自己的代碼進行反復地思考,修改和提煉,以此才能改進自己的代碼質量,真正提高自己的編程水平。

優雅的代碼 - 讓代碼更加清晰

優雅的代碼應該是容易管理和查看的,并且邏輯結構上是應該對稱的,就像樹叉式的樹
狀結構,一個常見的場景就是在if/else語句的使用上,讓if/else的分支永遠都是成對出現,不要僅僅因為省略掉少量的重復性代碼而去省略一個else的分支,這樣一來破壞整個邏輯的結構和對稱性,使得在代碼理解上就需要多一點投入。

if (conditionA) {
  ...
  if (conditionA-1) {
      ...
  } else {
      ...
  }
  ...
} else if (conditionB) {
  ...
} else {
  ...
}

模塊化的代碼 - 讓代碼更加獨立

對代碼的模塊化,應該是對代碼所表示的邏輯進行模塊化,也可以在單個文件進行模塊化的抽取,而不是單純地將代碼分成多個不同的文件和目錄。對代碼進行模塊化處理的一種常見且比較有效就是"使用函數",通常的函數都會有一個輸入和對應的輸出,因此就可以將能夠模塊化的代碼用不同的函數進行各自的封裝,在最后的使用就是對模塊化函數的組合調用。

文章也提到了想讓代碼更好模塊化的必要方式:

  • 避免使用過長的函數。針對存在的過長的函數,就要想方設法地去拆分成更小的函數,進行整合調用。在函數長度的控制上,最好保持在40~50行以內一個函數。
  • 使用小的工具函數。針對一些雖然結構簡單(可能只是一個倆三行代碼的真假判斷)但仍然會重復使用的代碼提取成小的函數,來簡化主要函數的邏輯。
  • 保持函數的單一性原則。避免寫出通用的,用來表示可以做這個又可以做那個的函數,讓每個函數盡量只做一件簡單的事情。當兩段代碼的共同點少于它們的不同點時就應該將連兩段代碼分別放在不同函數中,可以將其中相同的部分單獨抽取成另外一個函數來調用。
  • 減少和避免全局變量和類變量的使用。將全局變量或者類變量作為類里不同函數間的共享數據,成為函數間的數據通道。一方面,這樣一來破壞函數間模塊化的結構,無法做到讓函數離開類而存在;另一方面,這個變量還很可能在其它地方進行修改,這樣讓代碼理解上更加復雜和容易出錯。

可讀的代碼 - 恰當地命名,減少注釋

文章主張了"真正優雅可讀的代碼,是幾乎不需要注釋的",當然這樣的說法也是需要前提的。而給代碼寫注釋這件事上,在方便他人的閱讀上的確有所作用,而大量注釋存在于代碼之中,讓代碼本身閱讀起來更加困難,并且隨著代碼不斷改進和更新,也帶來了更新注釋的成本。當然注釋也有存在的必要性,在少數的情況下,使用違反常規做法的代碼實現則需要使用短注釋來解釋說明設計實現的理由。

以下就是幾個文章提到減少寫注釋的必要方法:

  • 使用有意義的函數和變量名。讓函數和變量名字能夠具體地描述它們的邏輯和用途。
  • 局部變量的聲明和它被函數使用的位置盡量接近。減少一開始就聲明大量局部變量,卻在很遠處才使用這些局部變量的做法,這樣會讓原本的代碼執行路徑更加復雜,還會出現在中間過程中被修改卻不易察覺的問題。

"局部變量的本質——作為電路中導線的存在"

  • 局部變量名盡量簡短。雖然變量名被縮簡,但結合有意義的代碼執行的上下文,要讓讀者輕易地理解局部變量的含義。
  • 減少局部變量的重用。避免對局部變量進行聲明后進行反復使用,不應該為了簡單地重用變量而擴大變量的作用域,防止在其他地方被使用.
if (...) {
  let info = "good"
  print(info)
} else {
  let info = "bad"
  print(info)
}
  • 將復雜的邏輯提取成"幫助函數".在一個很長的函數中,對于存在的不清晰的代碼片段往往可以提取出來,做成一個函數,然后在原來的地方進行調用,而將函數命名為更有意義,以此來代替原本所要添加的注釋.
  • 將復雜的表達式提取出來,用中間變量表示.控制單行代碼的長度,使用中間變量來防止代碼過度嵌套所造成的理解困難.
  • 在合理的地方換行.不要單純地依靠IDE的自動換行功能,應該根據代碼的邏輯來進行換行,這樣容易幫助理解代碼.

簡單的代碼 - 使用程序語言中經過時間考驗的特性

每一個程序語言多少會有一些自己的語言特性,對于語言特性的使用,應以其可靠,有效的前提下使用,并非語言提供什么特性,就一定需要用到什么.

幾個具體的應該避免使用的語言特性如下:

  • 避免使用自增自減表達式.表達式將讀寫不同的操作混合在了一起,讓語義更加?混亂,使用i+=1/i-=1進行代替.
  • 不要去省略花括號.不要因為語言特性支持if-else的括號省略而單單利用縮進,極易出現如下錯誤:
if (...)
    action1()
    action2()

原本if條件下只有執行action1函數,而意外地對action2進行縮進,使得也作為if條件下執行函數而等待調用.

  • 合理使用括號.在比較復雜的操作符表達式中,不要盲目利用操作符的優先級, 必要地使用括號讓表達式閱讀起來更加容易理解.
  • 避免使用break和continue. 在循環語句中使用了break或者continue就會讓循環的邏輯和終止條件變得復雜,難以保證其條件的正確性.而出現的break或者continue往往可以通過調整循環的邏輯來解決,這時候就應該重新思考循環的邏輯,重新設計。比如:
    • 出現continue,嘗試將continue的條件反向,以此消除continue
/*
  for(...) {
      if(conditon1) {
          continue;
      }
      ...
  } 
*/
  for(...) {
      if (!condition1) {
          ...
      }
  }
  • 出現break, 嘗試將break的條件放置于循環的終止條件中.
/*
while (condition1) {
    ...
    if (condition2) {
        break;
    }
}
*/
while (condition1 & !condition2) {
...  
}
  • 有時break可以被return代替,而循環中使用return是沒有問題.
  • 將循環中復雜的實現部分提取出來,作為函數去調用,然后再去嘗試處理消除break/continue.

直觀的代碼 - 永遠用更直接,更清晰的寫法

表達復雜的執行條件判斷時,減少邏輯運算符的使用,利用簡單的if-else嵌套,可以讓函數執行條件更加清晰.

正確處理錯誤

  • 對錯誤異常的catch,必須做出合理的處理,而不是選擇忽略掉.
  • catch到的異常應該明確異常的類型和對應信息,不要將其作為寬泛的Exception類型來處理.
  • 對可能出現異常的函數需要throw時,盡量避免多層次的異常傳遞,應該在異常出現的當時就進行處理.
  • try-catch的代碼應該盡可能地少,使得調試更加容易.

正確處理null指針

null其實不是一個合法的對象,類型應該是NULL,不該是任何其他一種普通類型.而充分利用窮舉法的思想,去更好處理null指針:

  • 函數盡量不要返回null.針對需要返回"沒有"的函數,允許使用"null" 表示沒有;而對于返回"錯誤"的函數,應該使用拋異常應對,而不是返回null值,"沒有"與"出錯"是完全不同的概念.
  • 不要去catch null相關的異常.針對可能存在null值的情況必須進行null檢查.
  • 不要將null放進"容器數據結構".在一些集合如Array,List,Set,Map等中加入null,會由于容器的動態性變化更加難以控制,使得取值時必須進行null檢查,讓調試也更加困難,而對應的解決方案:可以用一個特殊,合法的對象來表示集合容器中的"沒有".
  • 函數調用時對null盡早處理.明確null表示的意義,盡早地檢查和處理null值,必須減少它的傳播.
  • 函數設計時明確聲明不接受null參數.不要對null值進行容錯處理,針對null值的輸入應該終止程序的繼續.面對null?值,最正確的做法就是最強硬的做法。
  • 使用Optional類型.null指針問題的存在,是由于其允許在沒有檢查null的情況下進行訪問;而Optional則將檢查和訪問操作進行合二為一,這樣一來使得值被使用時必須先檢查.

防止過渡工程和過渡設計

  • 先把眼前的問題解決掉,解決好,再去考慮以后的擴展問題.
  • 先寫出可用的代碼,再進行推敲和改進,考慮重用的問題.
  • 先寫出有效, 簡單, 沒有明顯Bug的代碼, 再考慮測試的問題.

總結

面對長長一篇的干貨文章,自己粗淺的總結下來也需要不少文字,更何況里面有著許多的內容需要反復的推敲和琢磨,也有著不少內容需要大量的實踐和思考后才會有更深一步理解,.而每一段時間的實踐和思考想必讓這篇文章帶來的價值不斷彰顯,的確值得在不同時期都多看幾遍,一定要多回顧。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 通過這本書,我們可以開始理解數據可視化,探索數據的模式,并尋找數據間的關聯,進而選擇適合自己的數據和目的的可視化方...
    踏雁尋花閱讀 2,060評論 1 2
  • 水果觀念 中國營養協會建議一個人一天攝入250g到300g的水果,大概兩個拳頭的量。眾多水果中,榴蓮、鮮棗、香蕉含...
    亦亦大閱讀 262評論 0 0
  • 愛情是燈,友情是影子,當燈滅了,你會發現你的周圍都是影子。朋友,是在最后可以給你力量的人。 ——電影《當...
    燕子的留聲館閱讀 328評論 0 2
  • 一切速成都是耍流氓,捷徑就是最長的彎路。
    禾顏越瑟閱讀 164評論 0 1