《程式設計原來是這麼回事》演算法篇(下)
6. 怎麼避免在執行演算法時犯錯?
終於,我們要執行元演算法中的最後一步,也就是 執行演算法 了。無論演算法有多好,準備有多充分,要是執行不好的話全都白搭。
那麼,在執行演算法過程中,我們往往會犯哪些錯誤,又該如何應對呢?
1、流程錯誤
這是我們最容易犯的一類錯誤,大概可以分為下面這三種情況:
-
漏掉了某些必要的步驟 ,比如下雨天出門忘了帶傘……
-
多做了無用甚至有害的步驟,比如炒菜的時候放了兩次鹽……
-
步驟都沒錯但順序顛倒了,比如先穿好鞋再穿襪子……
導致流程錯誤的原因只有一個,那就是:忘記現在該執行哪一步了。正如我們之前所說,人的記憶是靠不住的。也許你現在能輕鬆地背出整個演算法,但是如果你被其他突發事情打斷了,或者不小心走了一下神,就很可能出現流程錯誤了。

image
我們都知道,只要不斷進行 刻意練習 ,演算法最終一定能爛熟於胸,就如魔方的公式手法一樣,形成肌肉記憶和條件反射。但是對於有可能只會執行一兩次的演算法,我們往往沒有必要付出那麼高昂的代價。其實,我們只要在執行過程中多加一個環節,就能輕鬆解決這個問題:
首先, 把演算法複製到一個可靠的媒介上 ,然後把它放在合適的位置,確保自己在執行過程中隨時都能看到。這樣一來,我們就不需要去記憶演算法的所有步驟了。比如我們可以把菜譜打印出來貼在廚房的牆上,或者把顯示菜譜的手機或IPAD放在一個穩妥的地方。
其次,我們要在 每做完一個步驟之後,把它和未做完的步驟區分開。 比如在完成的步驟上打一個對號,或者乾脆把它劃掉。如果使用手機或IPAD,可以向上滑動讓已完成的步驟移出螢幕。如果不方便騰出手來做標記的話,也可以給每個步驟編上號碼,然後記住當前正在執行步驟的編號。
這樣一來,就算我們被突發事件打斷了執行過程,也可以通過做好的標記來得知自己已經完成了哪些步驟,下一個步驟是什麼。然後檢查當前步驟是否被執行過並確認執行進度,在之前完成的進度基礎上繼續執行。
2、細節偏差
另外一個常見的問題就是 把握不好分寸,要麼做不到位要麼就做過了。很多演算法裡缺乏對細節的描述(比如:放適量鹽、倒入醬油……),這往往是因為作者認為某些細節是常識或者共識,或者認為這個細節無關緊要,於是就沒有寫到演算法裡。

image
如果這個演算法的執行結果對我們來說非常重要,不能出任何閃失,且條件允許的話, 最好能聯絡到演算法作者本人來確定丟失的細節 。但如果是從網上找來的演算法,往往根本不知道作者是誰。有時即便是演算法裡標註了細節(比如:放5克鹽),我們往往也不具備條件去度量。那到底該怎麼辦呢?
其次, 如果有專業人士在場,那就請他來給你提供反饋 ,在把調料放到鍋裡之前讓他把一下關,幫忙判斷一下量是否合適。這樣就是在利用別人長期積累下來的經驗,幫助我們快速形成一個初步的判斷標準。當然了,最好能讓他先做一次示範,你再進行模仿。
如果你是一個人孤軍奮戰,那就只能 先憑感覺嘗試,再根據反饋來進行調整 了。如果第一次做鹽放太多鹹了,第二次就少放一些;第二次做鹽放太少淡了,第三次就多放一些……幾次下來,我們就知道放多少鹽合適了。只不過,這樣的試錯成本也太高了,有沒有更好的辦法呢?
第一種思路是: 對本次操作的數量進行分解 ;其原理是通過提升精度來減少誤差。比如說給一鍋燉好的湯裡放鹽,我們可以先捏一點點鹽放進去,攪拌均勻後舀一勺嚐嚐味道,然後再放一點點,如此反覆直到味道滿意為止。這樣就可以避免一次手抖放鹽太多,結果毀了整鍋湯的情況。
第二種思路是: 對本次操作的影響面進行隔離 ;其原理是通過隔離來規避單次風險。比如說我們醃了十二隻雞翅,但不要一次性都放到油鍋裡煎,而是先煎上一隻試試。萬一不小心煎焦了,損失也可以承擔得起。第一次過後,我們就大概知道該怎麼煎、煎多久才能熟了,然後可以再煎一隻……直到煎出來的效果差不多滿意了,再把剩下的雞翅一次性都煎了。這樣就可以避免一次性全下鍋,結果全部煎焦的可能性。
說了這麼多,你現在是不是開始害怕自己犯錯搞砸了,心裡已經開始打退堂鼓了呢?俗話不是說「多做多錯,少做少錯,不做不錯」嘛,那乾脆還是不要做好了,這樣就不會錯了呢……
要知道, 我們是人不是機器,完全不犯錯誤是不可能的。 再說了,我們手中的演算法也並不是通往目標唯一的途徑,不過是無數條道路中的一條而已。當你有意或無意犯下一個錯誤時,其實就是開啟了一條新的分支。要知道,很多人類歷史上影響重大的發明(比如:青黴素),都是起源於一次無心犯下的錯誤。
事實上, 在有保護的環境下大量試錯,再總結反思,是最高效的學習方式 。正如你一直不敢下水,那麼就算讀再多的書、看再多的教學視訊,也是學不會游泳的。學游泳最快的方法就是:多下水、多撲騰,難道不是嗎?
所謂高手,不過就是把所有能犯的錯誤幾乎都犯過一遍的人而已。
「試一試」準備好一個自己從來沒有執行過的演算法,然後用上面所述的方法來執行。你在執行過程中都犯了哪些錯誤?出錯的原因是什麼?導致的結果是什麼?你的收穫是什麼?
7. 我們該怎麼應對意外和失敗?
俗話說:“計劃總是趕不上變化”。不管你做了多麼充分的準備,也不能完全避免各種 意外 的發生,比如下面這兩種最常見的情況:
- 有樣東西之前沒有考慮到,所以根本 沒準備
- 之前準備好的某樣東西 不見了 / 不夠了 / 不能用了
一旦意外發生,你不得不花額外的時間和精力去做新的準備,才能繼續執行下一步。如果你在做準備時沒有預留富餘,就很容易陷入進退兩難的境地。比如可樂雞翅演算法中的第一步是“用牙籤在雞翅上扎些洞”,如果在執行演算法時我們突然發現家裡沒有牙籤了,也沒有多餘的時間再去超市買,那該怎麼辦呢?
這時,你應該思考下面這些問題:
-
這一步的作用和意義是什麼?它對結果有什麼影響?
-
有沒有替代的方法可以達到類似的效果?代價如何?
-
省略這一步,會引發什麼後果?我能承受嗎?

image
理解了一個步驟的作用和意義後,我們就可以考慮 用其他方法來實現 。如果跳過這個步驟帶來的損失很小,在我們可以承受的範圍,那麼 也可以選擇忽略 它。如果你明白在雞翅上扎洞是為了在醃製時讓調料更入味的話,就可以用菜刀在雞翅兩側劃幾個小口來起到同樣的效果。事實上,就是跳過這一步不做,影響也不大。
你可能會想問:那如果出現問題的是非常關鍵的步驟,既不能忽略、也找不到其他方法來實現怎麼辦呢?比如你只買了一瓶可樂,臨到用的時候才發現被不明真相的家人給喝了……那可樂雞翅還怎麼做呢?

image
這時你就需要站在更高的層次來思考:
-
我為什麼要執行這個演算法?
-
我想達成的目標是什麼?
-
我能不能換一個演算法來實現這個目標?
-
我設立這個目標的動機是什麼?
-
我能不能從同樣的動機出發,重設一個目標?
我們總是出於某種動機而設立目標,又為了達成目標而尋找並執行演算法。執行當前演算法只是抵達目標的途徑,而不是目標本身。我們 千萬不要本末倒置,把“執行當前的演算法”當成了自己的目標。 你可能只是想給家人一個驚喜罷了,那麼為什麼非要做可樂雞翅呢,能不能做雪碧雞翅、紅燒雞翅、蒜香雞翅……?如果因為各種原因最後雞翅沒做成或者沒做好,你衝家人發脾氣合適嗎?難道你忘了自己做雞翅的動機是什麼嗎?
事實上, 第一次嘗試就成功的概率很低,失敗才是常態。 因為沒有經驗,你可能每個步驟都沒做到位,還不斷出現各種意外……結果就是根本達不到演算法預期的效果,甚至連最基本的合格線(比如:讓人能嚥下去)都達不到,這都是十分正常的。
費了這麼多氣力,卻沒有達成預定目標,你估計會很氣餒吧?也許你還會懷疑自己是不是不適合做這件事,甚至後悔自己做出這個決定……然而,** 如果你就此放棄了繼續嘗試,那你就徹底失敗了。**這次失敗的經歷將不會給你產生任何積極意義,甚至會成為你內心揮之不去的陰影,給你持續帶來痛苦和煩惱……你一定不希望最後變成這樣,對吧?
想象一下小嬰兒學步的場景,有誰不是經歷多次跌倒和失敗,最終才搖搖晃晃站起來的呢?又有誰竟然會選擇中途放棄,最終沒能學會走路呢?我們應該 從失敗中吸取經驗和教訓,讓自己得到成長和進步,下次繼續勇敢地嘗試。 當我們把失敗看做學習和成長的機會時,它便會變成生命中寶貴的經驗和財富。

image
有了經驗積累之後,你下次執行演算法時的成功率和執行效果都會明顯提升。因為你知道哪些環節可能出問題;出了問題該怎麼解決,哪些方法試過靠譜、哪些方法試過不靠譜;哪些材料需要多準備一些;哪種調料要少放點;哪種調料要多放點…… 你下一次做的肯定會比這次更成功,不過前提是你得有下一次。
你的第一個挑戰目標應該已經完成了吧?那麼,你的下一個目標是什麼?
「試一試」
設立一個新目標,運用到目前為止你所學到的策略和方法去實現它。你有遇到意外嗎?你是怎麼處理的?你對結果滿意嗎?你一共嘗試了幾次?最近一次比起第一次來,那些方面有進步?
(全文完)