「見山是山,見山不是山,見山只是山」[1],這是禪學領域知名的人生哲理,描述著人生會經歷的三個階段。其意思是,在人生年輕歷練尚淺時,看到山就是山,不會有太多的想法;當進入中年,有一定人生歷練的時候,看到山總是會有些不一樣的體悟出來,或許覺得壯闊、或許覺得莊嚴、或許覺得寧靜,端看山所帶給自己的感覺,而不是山本身;最後到了經歷更多的老年,在看透了許多事情後,看到山反而沒有之前的包袱,認為山就是山,沒有必要過度解讀。
這樣的人生三境界,在寫程式的領域也是如此。
初次接觸寫程式的時候,通常都是從語法開始學起,所以看到語法就是語法,也就是見山是山。比方說,當學到了 if、for、class、function 等等的語法,心中所想的是,需要判斷式就加入 if、需要批次處理就加入 for、需要將資料包起來就加入 class、需要打包一段程式碼就加入 function。見山是山,語法就是語法,任何語法使用起來都是直觀的。
隨著寫程式的時間越來越長,不論是自己經驗的心得、還是前人提供的指導、或者書本撰寫的知識,對於語法的使用概念會越來越不單純。可能會有以下情境:
自此語法不再是語法,都無形地被延伸概念所取代,見山不是山。從上述情境可以發現,這境界的程式設計人員有很重的包袱,可能是擔心違背了哪個程式觀念、也可能是對自己沒信心,導致在看到不錯的程式寫法或觀念之後,就拼命套用、彷彿非要用上一堆方法才是寫程式的正道。當寫程式就是套方法、套公式的話,產出的程式碼通常不直觀。由於是套用公式,通常從某些來角度看,水準會還不錯,可惜寫起來稍嫌不夠精準[2];運氣不好的話,可能用得不恰當,反而把程式碼變得難以維護。
在經歷過許多的程式觀念、方法、公式的轟炸後,漸漸地回歸語法與觀念的本質,也就是定義。任何寫法都是在清楚定義、明白優缺點的前提下完成。心中所想的是,需要判斷式就加入 if、需要批量處理就加入 for、需要將資料包起來就加入 class、需要打包一段程式碼就加入 function。文字描述看起來與見山是山的境界沒有差別,但實際上要如何判斷「需要」卻是非常不同的。以 class 與 function 的例子來說,見山只是山的境界會很清楚要完成目標去包裝 function 或 class 的利與弊,判斷的準則都是從定義做起,然後必要就去做、非必要就不去做,而不是見山是山的單純想到就去做。
見山只是山的程式設計人員,對於語法與觀念的掌握度很高,所以處理問題的方法與工具會有很多,也因此總能產生很靈活的應對解決方案;各種精妙的程式碼,通常也是在這個境界才能寫出來的。這應該是最能享受寫程式的境界,不但思考的過程愉悅,完成程式後也頗具成就感。
如果還是覺得這三個境界很抽象,沒關係,下面透過 C/C++ 的重要觀念, Pointer 來做說明。相信透過這個經典例子,可以更清楚寫程式的三個境界是什麼概念。
剛接觸 Pointer 的時候,於定義上會學到「Pointer 的用途是儲存位址」;因此當書本或課堂範例提到位址,就會與 Pointer 做連結。這樣的連結,通常像是在背課文一樣,看到 int* p = &a;
就翻譯成 p
是 a
的位址。沒有什麼雜念,沒有什麼額外的想法,卻不知道應該在什麼地方使用。唯一可能使用的情境,就是當有些地方程式碼行為不如預期,像是想改變什麼值卻無效、或改變值卻造成 memory corruption,才會想到要調整 Pointer 的寫法試試。一個星號不對就用兩個,兩個星號還是不對,就再改寫法以避開 Pointer。由於很清楚 Pointer 的定義是儲存位址,所以見 Pointer 是 Pointer,只是可能不清楚怎麼使用,所以能免則免。
可是在 C/C++ 的領域要完全避開 Pointer 是非常困難的,很多情境都會需要用到。在不知道什麼時候該用,什麼時候不該用的狀況下,最常見的使用方式就是套公式。就像學生寫數學題目,很多時候不求甚解,只要題型與對應公式背好,考試就套公式,反正能拿高分就可以了。
下面舉幾個常見的 Pointer 使用公式:Swap 交換兩個數值、Array 傳遞、額外的返回值。當然,會使用到 Pointer 的公式不止於此,這裡提出 Pointer 初學最常見的案例來做說明。
如果有寫過與 Pointer 相關的程式碼,肯定對這些公式不陌生,或多或少會用過這些公式。在見 Pointer 不是 Pointer 的境界,其實對 Pointer 如何使用還是不清楚的,但在遇到這些公式的情境,就會很自然地將對應的程式碼寫出來讓程式行為符合預期。就因為該怎麼使用 Pointer 還不是很熟,所以在學習過的公式之外,依然是不碰 Pointer 的。
進入到這個境界,使用 Pointer 並不拘泥於公式,僅針對定義「Pointer 的用途是儲存位址」就能發揮 Pointer 的各種效用了。或是用於通用介面、或是用於抽換 function、或是用於實現多型(Polymorphism)、或是用於隱藏實作等等。效用之多,不是隨意舉幾個例子就能概括的;各種靈活運用已經無法用套公式來辦到,而是在清楚定義的前提下,去建立自己想要的實作方案。一旦拋棄了公式的成見,就能見 Pointer 只是 Pointer,當用則用,不當用則不用。
如前面所說,當到達見山只是山的境界,不但寫程式的思考過程愉悅,能寫出精妙的程式碼,而且完成後的成就感極高。那是不是未達此境界的人,只要程式繼續寫下去、繼續練習就能達成呢?答案是否定的。人生可惜的就是不一定能見山只是山,可能終其一生都還是見山不是山,見到山都還是有想法、有感慨、有包袱,看不見山的本質。寫程式也是如此,許多人寫程式陷在套公式的泥沼裡,實作方案永遠都在找相似題與公式,沒有脫離套公式的時候。
為什麼會停滯在見山不是山的境界呢?主要是套公式之餘,沒有去思考公式的組成原理、也沒有去理解各個原理的定義、更沒有在套公式之餘問自己是否有更好的做法。長久下來,再也不問程式觀念的定義是什麼,反正程式能動就好,自然無法見山只是山。
其實停留在見山不是山的境界也不見得是糟糕的,畢竟每個人的理想與目標不同,非得在寫程式的旅程上追求最高境界是不合理的。倘若工作不需要、或者目的不需要,即使寫程式停留在最初的見山是山又如何?應該要看自己的需要,再來追求應該要達到什麼境界。
那要如何才能邁向見山只是山的境界呢?下面分享幾個具體訣竅:
條條大路通羅馬,其實要讓自己往下一個境界邁進的方法有很多,並不限於上述的訣竅,這裡僅就經驗提供作參考。只要有心要讓自己進步,不要一直覺得自己已經很厲害了、不要拒絕接收更多知識、不要拒絕檢討自己、不要成天只想著程式能動就好,總會有達到見山只是山的時候!