【譯】怎樣避免開發時的深坑

翻譯:瘋狂的技術宅
做者:Valinda Chan
英文標題:10 Steps to Solving a Programming Problem
英文連接:https://codeburst.io/10-steps...
本文首發微信公衆號:充實的腦洞javascript

clipboard.png

我老是聽到剛入行不久的程序員這樣說:知道本身要實現什麼功能,同時處理邏輯和基本語法也都明白,可是就不知道該怎麼寫代碼。若是把別人的的代碼給你看,或者有人給你你一些指導,或許你能明白其中的思路。可是,在實際開發時仍然障礙重重。即便語法或邏輯都明白,也很難本身的想法轉化爲代碼。在本文中我將會告訴你們我本身是怎麼作的,還有一些解決典型問題的方法,但願可以對你們有所幫助。java

1. 把給你的需求反覆閱讀三遍以上(或者直到看吐了爲止)

若是不能理解給你的需求,也就沒有辦法實現它。 實際的需求和你認爲的需求有很大的區別。假設有一個需求,當你閱讀前幾行時很是容易,可是接下來你就會假設其他部分與你曾經看到過的東西相似。好比你要作一個像「劊子手」同樣的遊戲,必定要通讀它全部的規則,即使你曾經玩過這個遊戲。我就曾經接受了一個開發任務,就是作一個像「劊子手」同樣的遊戲,可是當我看完需求中全部的規則時,才意識到要作的應該是「邪惡的劊子手」(這是一個深坑!)。程序員

有時我會試着向一個朋友解釋某個需求,看她對我解釋的理解是否和個人需求一致。若是你不想在開發了一半的時候才發現本身誤解了這個需求,那麼在開始的時候多花點時間是值得的。你對問題越瞭解,就越容易解決它。算法

假設咱們要建立一個簡單的函數selectEvenNumbers,這個函數的參數一個存放整數的數組,返回值evenNumbers 是一個只存在偶數的數組。若是沒有偶數,那麼久返回一個空數組。編程

function selectEvenNumbers() {
  // your code here
}

如下是我思考的問題:數組

  • 計算機怎樣去判斷是否是偶數? 檢查該數是否能被2整除微信

  • 我傳給這個函數的參數是什麼? 一個數組編輯器

  • 數組中保存的內容是什麼? 一個或多個整數ide

  • 數組中元素的數據類型是什麼? 整數函數

  • 這個函數的目的是什麼?之行結束後要返回什麼? 目標是獲得全部偶數,並把它們保存到數組中返回。若是沒有偶數,就返回一個空數組。

2.至少使用三組模擬數據進行手動模擬

找一張草稿紙,人工解決這個問題。至少考慮三組模擬數據,注意要考慮到極端狀況和邊界問題。

極端狀況:在正常操做參數範圍以外產生的問題或狀況。或者是多個變量或條件都在其指定範圍內,可是都同時處於極端的水平的狀況。
邊界問題:僅在極端(最大或最小值)參數的狀況下發生的問題或情況。

舉個例子,下面是一些要使用的樣本數據集:

[1]
[1, 2]
[1, 2, 3, 4, 5, 6]
[-200.25]
[-800.1, 2000, 3.1, -1000.25, 42, 600]

在剛開始的時候,很容易忽略這些步驟。

由於你的大腦對於偶數的概念十分清楚,因此只要看到一組數據,就能夠從中找到2,4,6這樣的數字,幾乎意識不到本身的大腦是怎麼思考的。能夠嘗試更多的數據,它會改變你大腦經過觀察來解決問題的習慣。這有利於幫你實現真正有效的算法。

咱們來看第一個數組:[1]

  1. 查看數組 [1] 中惟一的元素

  2. 判斷是否爲偶數:嗯,並非

  3. 肯定這個數組中沒有其餘的元素了

  4. 肯定在這個數組中沒有偶數

  5. 返回一個空數組

接下來看第二個數組:[1, 2]

  1. 先看數組[1, 2]中的第一個元素

  2. 數字是1

  3. 判斷是否爲偶數:不是

  4. 看數組中的下一個元素

  5. 數字是2

  6. 判斷是否爲偶數:是的

  7. 建立一個數組evenNumbers ,並把數字2添加到其中

  8. 肯定數組中沒有其餘元素了

  9. 返回的數組evennumbers[ 2 ]

再多看幾遍。請注意處理[1]的步驟和[ 1, 2 ]略有不一樣。這就是爲何我要嘗試多種不一樣的組合。在這些數據中,有的只存在一個元素;有些是浮點數,而不是整數;有些是一個元素中有多個數字,有些是負數。

3.簡化並優化你的步驟

尋找模式,找到歸納問題的方法,看看能不能減小無用或重複的步驟。

  1. 建立一個函數selectEvenNumbers

  2. 建立一個保存數據的空數組evenNumbers

  3. 檢查數組[1, 2]中的每一個元素

  4. 找到第一個元素

  5. 判斷它是否能夠被2整除。若是是,就加到evennumbers

  6. 找到下一個元素

  7. 重複步驟4

  8. 重複步驟5和步驟4,一直到數組中沒有任何其餘元素

  9. 返回數組evenNumbers ,無論它是否是空數組

這個方法可能會讓你想起數學概括法

  1. 證實當 n = 1, n = 2, ... 的狀況下成立

  2. 假設當 n = k 時成立

  3. 證實當 n = k + 1 時成立

4. 寫出僞代碼

僞代碼
僞代碼

咱們已經有了處理步驟,接下來就要編寫出僞代碼了,僞代碼能夠轉換成真實的代碼,這有助於定義代碼的結構,並使編碼變得更加容易。您能夠在紙上寫僞代碼,也能夠在代碼編輯器中用註釋的形式來寫。若是你在電腦上作會分心,我建議你用紙和筆來完成。

一般僞代碼並無什麼特定的規則,不過有的時候我可能會使用本身熟悉的某種語言的語法。因此不要被語法所糾纏。把精力放在邏輯和步驟上。

對於咱們所面對的問題,能夠有不少不一樣的方法。 例如,您可使用filter,可是爲了儘量簡單地說明前面的例子,咱們如今將使用一個基本的for循環(可是當咱們重構代碼時,將會使用filter )。

下面是一個僞代碼的例子,它有比較多的語言描述:

function selectEvenNumbers
create an array evenNumbers and set that equal to an empty array
for each element in that array
  see if that element is even
    if element is even (if there is a remainder when divided by 2)
      add to that to the array evenNumbers
return evenNumbers

下面這段僞代碼比較簡潔:

function selectEvenNumbers
evenNumbers = []
for i = 0 to i = length of evenNumbers
  if (element % 2 === 0) 
    add to that to the array evenNumbers
return evenNumbers

只要你能把它逐行地寫出來,而且理解每一行的邏輯,用哪一種方式並不重要。

最後還要回顧一下,確保本身沒有走偏。

5. 把僞代碼翻譯成真正的代碼並進行調試

當僞代碼被準備好以後,就能夠把每一行僞代碼用本身正在使用的語言實現了。在這個例子中咱們將使用JavaScript。

若是你把僞代碼寫在了紙上,那麼就把它做爲註釋輸入到本身的代碼編輯器中,以後再替換爲代碼中的每一行。

而後我調用這個函數,並給它一些咱們以前使用過的樣本數據集。能夠用它們來檢查代碼執行的結果是否和預期一致。還能夠編寫測試用例來檢查實際的輸出是否符合預期。

selectEvenNumbers([1])
selectEvenNumbers([1, 2])
selectEvenNumbers([1, 2, 3, 4, 5, 6])
selectEvenNumbers([-200.25])
selectEvenNumbers([-800.1, 2000, 3.1, -1000.25, 42, 600])

我一般在每一個變量或者每一行後面都使用console.log()。這將會幫助我檢查變量值和代碼是否符合預期。經過這種方法,能夠很容易的發現代碼中的問題。下面的例子是我在運行時會檢查哪東西。在我全部的代碼中都會這樣作。

function selectEvenNumbers(arrayofNumbers) {
  let evenNumbers = []
  console.log(evenNumbers) // I remove this after checking output
  console.log(arrayofNumbers) // I remove this after checking output
}

最後使每一行僞代碼都有對應的真實代碼。//後面是僞代碼,其它部分是用JavaScript實現的真實代碼。

// function selectEvenNumbers
function selectEvenNumbers(arrayofNumbers) {
  // evenNumbers = []
  let evenNumbers = []
  // for i = 0 to i = length of evenNumbers
  for (var i = 0; i < arrayofNumbers.length; i++) {
    // if (element % 2 === 0) 
    if (arrayofNumbers[i] % 2 === 0) {
      // add to that to the array evenNumbers
      evenNumbers.push(arrayofNumbers[i])
    }
  }
  // return evenNumbers
  return evenNumbers
}

爲了不混淆,我去掉了僞代碼。

function selectEvenNumbers(arrayofNumbers) {
  let evenNumbers = []
  for (var i = 0; i < arrayofNumbers.length; i++) {
    if (arrayofNumbers[i] % 2 === 0) {
      evenNumbers.push(arrayofNumbers[i])
    }
  }
  return evenNumbers
}

有時候,初級開發人員會被語法所困擾,致使難以繼續前進。記住:語法會隨着時間的推移而逐漸熟練起來。在編碼的時候由於語法問題去翻參考材料並不丟人。

6. 簡化並優化你的代碼

clipboard.png

你可能已經注意到,簡化和優化是常常性的話題。

「簡單性是可靠性的先決條件。」
——荷蘭計算機科學家Edsger W. Dijkstra,計算科學研究領域的先驅

在這個例子中,優化的方法之一就是經過使用filter 返回一個新數組來過濾原來數組中的項。這樣咱們就不用再去定義另一個變量evenNumbers,由於filter 將返回一個新的數組,其中包含與過濾器匹配的元素並複製一個新的數組。 這樣就不會改變原來的數組。咱們也不用使用for循環來進行遍歷。過濾器將會遍歷每一個項,若是在數組中的元素符合條件就返回true,不然就返回false將其忽略。

function selectEvenNumbers(arrayofNumbers) {
  let evenNumbers = arrayofNumbers.filter(n => n % 2 === 0)
  return evenNumbers
}

簡化和優化代碼可能須要迭代屢次,以肯定進一步簡化和優化代碼的方法。

這裏有一些須要牢記的問題:

  • 簡化和優化的目標是什麼?目標會被你的團隊風格或我的喜愛所左右。是儘量地壓縮代碼仍是使代碼更易閱讀? 若是是後者,你可能會用單獨的代碼行來定義變量或計算某些變量,而不是試圖在一行中作這些事。

  • 怎樣作才能使代碼容易閱讀?

  • 還有沒有多餘的步驟能夠去掉?

  • 有沒有變量或函數始終沒有被用到過?

  • 是否是存在重複的步驟?看能不能在另一個函數中定義它們。

  • 有沒有更好的處理邊界問題的辦法?

編寫程序的本意是爲了供人閱讀,只是順便讓計算機可以執行它。
——「計算機程序的結構與解釋」做者Gerald Jay Sussman和Hal Abelson

7.調試

這一步應該貫穿始終。在調試的過程當中,您會很容易發現邏輯上的錯誤或漏洞。要充分利用集成開發環境(IDE)和調試器。當我遇到bug時,會逐行跟蹤代碼,來檢查是否存在不符合預期地方。如下是我使用的一些技巧:

  • 實用控制檯能夠查看錯誤信息,有時候它會告訴我須要檢查哪一行,這就給了我一個大概的思路:從哪裏開始。儘管有時候問題並不在提示給出的那一行。

  • 註釋掉某些代碼塊或者行,並輸出調試信息,來檢查剩餘的代碼是否能正常運行。能夠根據實際狀況對代碼進行註釋。

  • 使用不一樣的測試數據,看看代碼是否仍然能夠工做。以此來檢查是否存在我沒有想到的狀況。

  • 若是想要嘗試另一種徹底不一樣的方法,能夠保存不一樣版本的文件。我可不想在恢復原來代碼的時候後悔莫及!

最有效的調試工具是仔細的思考,再加上輸出清晰的調試信息。
——普林斯頓大學計算機科學教授Brian W. Kernighan

8.添加有效的註釋

頗有可能在一個月以後你會忘記本身的代碼都是什麼意思,使用你代碼的其餘人可能也不知道。這就是爲何要添加有效的註釋的緣由:爲了讓你在回頭看這些代碼時節省時間。

不要這樣去註釋:

// 這是一個數組,而且遍歷它

// 這是一個變量

我試着作一些簡要、高級的註釋,在出問題的時候能夠幫我搞明白這段代碼究竟是起到什麼做用。尤爲是在處理更復雜的問題時很是有用。它有助於理解某個特定功能在作什麼以及爲何這樣作。經過使用清晰的變量名、函數名和註釋,你(和其餘人)應該可以理解:

  • 這段代碼是作什麼用的?

  • 它是怎樣工做的?

9.經過代碼評審得到反饋

clipboard.png

從你的團隊成員、教授和其餘開發者那裏獲得反饋。檢查堆棧是否會溢出。看別人如何解決這個問題並從中吸收教訓。有時解決問題的方法有好幾種。把它們都找出來,這樣你進步會很快。

別在乎你寫出良好風格的代碼會花費多少時間,由於一旦你寫出了糟糕的代碼,那將會更慢。
——Bob Martin,軟件工程師,敏捷宣言的合著者之一

10.實踐,不停的實踐

哪怕是經驗再豐富的開發人員也老是在不停的實踐與學習。若是你獲得了有用的建議,那麼就要去照着作。重複作相同或相似的事情,不停的鞭策本身。隨着一個又一個的問題的解決,最終你會成長起來。在每一次成功以後,必定要對問題進行回顧。記住,編程和任何事同樣,會隨時間的推移變得更加簡單、更加天然而然。


歡迎掃描二維碼關注微信公衆號:充實的腦洞,第一時間推送我翻譯的國外最新技術文章。

clipboard.png

相關文章
相關標籤/搜索