2011年4月18日 星期一

轉文_ 心靈與程式碼的協奏曲

原文來自 http://www.codemud.net/~thinker/GinGin_CGI.py/show_id_doc/445

功力很弱的我,認為這篇對我有幫助,請作者見諒

----------------------------------------------------------------------------
你是怎麼寫程式的? 特別是,你怎麼寫一個 function? 我相信大部分人和我過去一樣,從 function 的第一行,一直寫到最後一行 return。 在寫 function 的第一步時,或許你會先想一下 function 要 return 什麼樣的值, 接著開始在腦海裡規劃該怎麼進行,以達到要求的功能。 待規劃到一個程度之後,你會開始寫第一行程式,然後慢慢的寫到最後一行 return。 或許你也注意到了,雖然大體上是從第一行寫到最後一行,但過程中你是修修改改, 不斷的回頭去修改之前寫下的程式碼,來來去去的。 為何無法很順暢的一路寫到尾? 或許你可能搞錯寫程式的方法。
我們先將問題限制在一個會 return 資料 function 類別上。 回想一下,當你在規劃一個 function 時,你一定先想到 function 的輸出應該 是什麼。 接著你會開始想,要做什麼事才能合成該輸出? 於是你規劃出,要做 A 、 B 、 C 三個動作,才能合成正確的輸出。 而 A 、 B 、 C 又有一些細節,你繼規劃這些細節,直到一定的程度。 接下來開始從頭到尾,一行接著一行寫下來。 但大腦通常沒那麼厲害,寫的過程會發覺一些細節沒考慮清楚,或者不正確。 於是寫到一半又要回頭在前面補齊或修改所需的資料。 個過程是不順利的,因為大腦運作的方向是和程式的順序相反的。 何以見得?
如前面所說的,你是先想到 function 的結果,接著想該完成的步驟,然後再細節。 而程式碼的排列的方式,卻是先寫下細節,由細接組成主要步驟的輸出, 最後再由主要步驟的輸出組合成最後的結果,然後 return 結果。 於是,和大腦思考的方向剛相反。 這可能是造成如此混亂的原因。 你必需先憑空的從結果一直規劃到細節,然後再從細節寫回到結果,這對大腦是一大考 驗,除非你有超強的記憶力。 我沒有。
何不試試從最後一行回頭寫到第一行,完全反過來? 按照前面所說的,這樣應該和大腦運作的順序一致。 如果我們真的這樣作,情況又會如何? 會比較順利嗎? 不如寫個程式來試試。
假設問題是, function 接受一字串,裡面的每一行有兩個欄位,皆是整數。 欄位以一個空白相隔。 function 必把每一行的兩個欄位的數字相乘,然後再將每一行的結果加總。 反方向的方法寫大概會是如此。第一步,先定義 function 的 prototype。
001 def mul_n_sum(data):
002     return result
我們先不管 result 怎麼達成,總之我們必需想辨法產生 result。 接下來, result 應該是每一個相乘的結果的加總。
001 def mul_n_sum(data):
002     result = sum(value_of_lines)
003     return result
一樣,我先不管 value_of_lines 是怎麼來的,總之我們會想出辨法算出每一行的值。 接著,我們每一行值應該是由每一行的兩個欄位相乘的結果。
001 def mul_n_sum(data):
002     value_of_lines = [filed1 * field2
003                       for field1, field2 in field_lines]
004     result = sum(value_of_lines)
005     return result
一樣的,我們先不管 field_liens 是怎麼來的,總之我們會想出辨法。 field_lines 是每一行的兩個欄位,因此我們必需從每一行的兩個文字欄文,轉成整數。
001 def mul_n_sum(data):
002     field_lines = [int(txt_field1), int(txt_field2)
003                    for txt_field1, txt_field2 in txt_field_lines]
004     value_of_lines = [filed1 * field2
005                       for field1, field2 in field_lines]
006     result = sum(value_of_lines)
007     return result
同樣的,txt_field_lines 是每一行的兩個文字欄位,因此我們必需從每一行, 把兩個文字欄位個別分開、最出。
001 def mul_n_sum(data):
002     txt_field_lines = [line.split() for line in lines]
003     field_lines = [int(txt_field1), int(txt_field2)
004                    for txt_field1, txt_field2 in txt_field_lines]
005     value_of_lines = [filed1 * field2
006                       for field1, field2 in field_lines]
007     result = sum(value_of_lines)
008     return result
最後,我們再定義 lines,從輸入的 data 分割成每一行文字。
001 def mul_n_sum(data):
002     lines = data.strip().split('\n')
003     txt_field_lines = [line.split() for line in lines]
004     field_lines = [int(txt_field1), int(txt_field2)
005                    for txt_field1, txt_field2 in txt_field_lines]
006     value_of_lines = [filed1 * field2
007                       for field1, field2 in field_lines]
008     result = sum(value_of_lines)
009     return result
完成了。 反方向寫似乎是比較順利。 或許你會質疑,這只是個簡單的例子,複雜的 function 呢? 根據我的實驗,似乎同樣適用。
雖然這樣的寫法比較符合大腦的運作方式,但還是需要一些練習才能習慣。 不過,練習的時間不需很長,很快就能適應這種新的寫法。 這種寫法,讓大腦規劃的過程,按照思考的順序,一步一步寫下來。 因此不需把所有的東西都塞在腦子裡,把空間騰出來,用於思考。 因此思考能更順暢。
練習時要特別注意! 我發覺剛開始試用這種方法的人,可能習慣性進行跳躍式的思考。 例如,一想到 result 是每一行的兩個欄位的相乘結果的加總, 就想在下一步,直接把欄位 parse 和加總的動作都一次完成。 於是就回到原本從頭寫到尾的老方法。 這要特別注意。
另外,用這個寫法有一個特色。 loop 的使用減少了,幾乎都是 list comprehension。 你可能認為,這個例子是特例。 但實驗的結果,發覺這是常態。 以這個例子,如果以傳統的寫法可能會變成
001 def mul_n_sum(data):
002     result = 0
003     for line in data.strip().split('\n'):
004         field1, field2 = line.split()
005         v1 = int(field1)
006         v2 = int(field2)
007         result = result + v1 * v2
008     return result
你會發覺 loop 跑出來了。 而且 "result = 0" 這一行,可能是寫到 "result = result + v1 * v2" 這一行時, 才回頭補寫的。
這是我最近自我觀察時,所發現的一個方法。 我不知是否有其它人提出相同的作法,但我覺的還蠻不錯的。 或許你會質疑,用那麼多 list comprehension 會降低效率。 但,大部分時侯,效能可能不是這麼重要。 另一方面,或許未來會有一個程式語言,或是 compiler 特別支援這樣的寫法, 自動展開這些 list comprehension。 屆時,這些效能問題也就解決了。

沒有留言:

張貼留言