本文是Google工程師Steve Merritt的一篇部落格,向大家介紹他自己和身邊的同事解決程式設計問題的方法。
原文地址:blog.usejournal.com/how-a-googl…
在本文中,我將完整的向你介紹一種解決程式設計問題的策略,這個策略是我在日常工作中一直使用的,並且用它來幫助各個等級的程式設計師(包括新手、大學生和實習生)學習和成長。應用這個結構化流程可以最大幅度的減少那令人沮喪的除錯時間,並且能夠在儘可能短的時間內編寫出更加整潔、錯誤率更低的程式碼。
一步步
接下來我將用一個栗子來說明。
問題:有兩個字串sourceString
和searchString
,返回searchString
在sourceString
中第一次出現的位置,如果searchString
不是sourceString
的字串,就返回-1。
第一步:畫下來
當你拿到一個需求,馬上就開始著手寫程式碼是一個非常愚蠢的主意。在寫一篇文章之前,首先要弄清楚論證和論據,並且你要保證論證是有意義的。如果你沒有這麼做的話,當你意識到你所寫的東西前言不搭後語時,你可能會因為浪費了大把的時間而想請自己吃一頓大嘴巴子。程式設計也是一樣的道理,甚至比這還嚴重,嚴重到像洗澡的時候把洗髮水弄進眼睛裡。
問題的解決方法通常很重要,即使它看上去很簡單。在寫程式碼之前,首先要做的就是把這個方法在紙面上呈現出來,並且保證在不同的情況下適用。
所以不找急著寫程式碼,甚至都不要思考如何寫。後面你會有充足的時間去敲程式碼,在這之前,你要把自己當成一臺計算機,弄清楚你這臺計算機會怎麼解決這個問題。
你可以使用流程圖,或者使用其他能幫你具象化的方法,總之我們的目標是解決問題。你可以用紙和筆隨意發揮,不需要收到鍵盤的限制。
我們從一些簡單的情況開始,如果一個方法是“輸入一個字串”,那麼“abc”可以作為第一個例子。首先要搞清楚正確的結果是什麼,然後去想怎麼樣得到正確的結果,並且一步一步的進行。
我們假設輸入的字串是這樣:
sourceString: "abcdyesefgh"
searchString: "yes"
複製程式碼
我的想法是這樣的:
我看到了searchString
在sourceString
裡,但是我要怎麼做呢?我從sourceString
的第一個字元開始,逐字去讀,一直到最後,判斷每一個三個字元是不是yes
。比如abc
,bcd
,cdy
等等。當index值為4時,我找到了字串yes
,所以我知道結果是index為4。
當我們寫下演算法時,必須要保證我們考慮了所有的情況,並且處理了所有可能的場景。當我們找到匹配的字串時,返回結果。如果找不到匹配的字串,同樣要返回結果。
我們來嘗試另一組字串:
sourceString: "abcdyefg"
searchString: "yes"
複製程式碼
我們重複剛才的操作,當我們讀到下標為4的字元時,找到的字串是yef
,這是最接近的結果了,但是第三個字元卻不同,所以我們繼續往後讀,一直到最後,沒有找到匹配的字串,所以我們決定返回-1。
我們確定這一系列步驟(程式設計中,我們稱之為演算法)解決了我們的問題,並且處理了兩種不同的場景,每次都得到了正確的結果。這時,我們就對我們的演算法比較有信心了,並且可以將它形成條目。我們一起來進行下一步:
第二步:用英語寫下來
這裡我們考慮將第一步形成的演算法用英語寫下來。這可以使每一步變得更加具體,以便我們後面寫程式碼的時候有所參考。
- 從字串的第一個字元開始
- 檢視每一組3個的字元(其實是
searchString
的長度) - 如果匹配上
searchString
,就返回當前的index - 如果一直到末尾都沒有找到匹配的字串,就返回-1
看起來很棒,不是嗎
第三步:寫虛擬碼
虛擬碼並不是真正的程式碼,只是一種模擬形式,這裡我寫下上面的演算法的虛擬碼:
for each index in sourceString,
there are N characters in searchString
let N chars from index onward be called POSSIBLE_MATCH
if POSSIBLE_MATCH is equal to searchString, return index
at the end, if we haven't found a match yet, return -1.
複製程式碼
我也可以用一種更接近程式碼的形式來寫:
for each index in sourceString,
N = searchString.length
POSSIBLE_MATCH = sourceString[index to index+N]
if POSSIBLE_MATCH === searchString:
return index
return -1
複製程式碼
你的虛擬碼寫得有多像真正的程式碼,你就會發現它有多麼好用。
第四步:翻譯可以編碼的內容
注意:對於簡單的問題,這一步可以和上一步合併
到這時我們才第一次需要考慮語法、方法引數和語言規則的問題。可能你不是全部程式碼都會寫,但是沒關係,先把會寫的寫出來。
function findFirstMatch(searchString, sourceString) {
let length = searchString.length;
for (let index = 0; index < sourceString.length; index++) {
let possibleMatch = <the LENGTH chars starting at index i>
if (possibleMatch === searchString) {
return index;
}
}
return -1;
}
複製程式碼
注意我留了一部分沒有寫,這是故意的!我不確定JavaScript切分字串的語法要怎麼寫,所以我會在下一步查詢它。
第五步:不要猜測
我發現所有新手程式設計師都會犯一個共同的錯誤,就是從網上找到一個方法,覺得“可能有用”,然後不經過測試就寫進程式碼裡。你不理解的程式碼越多,就越不可能找到正確的方法。
你的程式的錯誤可能是你不瞭解程式碼的兩倍還多。有一處不理解,如果程式出錯,那麼罪魁禍首隻有一處。如果你有兩處不理解,那就有三種可能出錯(A出錯,B出錯,或者A和B都出錯)。如果有三處不理解,就會有七種情況……很快它就失控了。
邊注:程式出錯的情況種類遵循Mersenne公式 a(n) = (2^n) — 1
首先要測試你的新程式碼。從網際網路上找答案是好的,但是在寫進你的程式碼之前,你要先對它進行單獨的測試,確保它能按照你想要的方式執行。
在上一步中,我不確定JavaScript怎麼切分字串,所以我選擇面向Google程式設計
第一條結果來自w3schools,有點小過時,但比較可靠
www.w3schools.com/jsref/jsref…
基於此,我覺得我應該使用substr(index, searchString.length)
來提取sourceString
,但這只是個假設,所以我要先來測試一下:
>> let testStr = "abcdefghi"
>> let subStr = testStr.substr(3, 4); // simple, easy usage
>> console.log(subStr);
"defg"
>> subStr = testStr.substr(8, 5); // ask for more chars than exist
"i"
複製程式碼
現在我確定這個函式是可以用的,如果程式出錯,就不是這個函式不可用導致的。最後我補充上最後的程式碼。
function findFirstMatch(searchString, sourceString) {
let length = searchString.length;
for (let index = 0; index < sourceString.length; index++) {
let possibleMatch = (
sourceString.substr(index, length));
if (possibleMatch === searchString) {
return index;
}
}
return -1;
}
複製程式碼
結論
如果你讀到這裡,我要說的只有:”幹就完了!“
再嘗試處理一下上週遇到的困難,用上我教你的方法,我保證你很快就會有提高。
祝你好運,編碼愉快!
譯者注:個人認為作者還是強調要先想清楚,再動手寫程式碼。而且要學會面向Google程式設計