學寫程式為何甚艱難?
如果你想學寫程式,在網上搜尋一下,不難找到不少「輕鬆學習XXX」、「YY天輕鬆學會Python」的教材及影片;去電腦書店逛一圈,很容易就可以找到「七天學會Java」等的書籍,這些教材其實都在推廣一個信念,也就是: 學寫程式很簡單,人人都可以學會。 筆者對此信念後半部深表認同,但是對前半部所謂「學寫程式很簡單」,卻真是不敢苟同。
筆者深信No pain, no gain
的哲學,世上大部份有價值的事物,都必須努力才能達到,如果真是可以「YY天輕鬆學會編程」,那豈不是明天所有軟件工程師都要失業?學寫程式,就像練馬拉松一樣,人人皆可做到,只是必須要付出努力。將一樣要付出努力才能達成的事物,簡化成「XX天就可以輕鬆學成」,會令初學者初遇困難時,誤以為自己無甚天份,繼而失去學習動機。反而一早說明學習時會遇上的困難,才能為初學者提供正確的心理準備。就像你要去挑戰一座高峰,你找了一個曾經登上該座高峰的朋友請教,那朋友卻說:「很簡單的,不用訓練,人人也可以上去」,這可不是安慰說話,而是害人不淺。
你必須學習新語言
程式語言之所以稱為程式語言,原因是因為程式語言確是一種語言,而且皆是專為向電腦說明步驟而發明的。因此本質上與自然語言有很多不同的地方,一個經典的Programmer笑話是這樣的:
A programmer is going to the grocery store and his wife tells him, "Buy a gallon of milk, and if there are eggs, buy a dozen." So the programmer goes, buys everything, and drives back to his house. Upon arrival, his wife angrily asks him, "Why did you get 13 gallons of milk? " The programmer says, "There were eggs!"
妻子明顯的意思是想丈夫買一加侖牛奶,如果有雞蛋的話,就多買一打雞蛋,但丈夫卻理解成以下的程式碼。
buy(1, 'gallon', 'milk');
if (has('egg')) {
buy(12, 'gallon', 'milk');
}
就創造了這個經典笑話。當然笑話的內容不足為信,但程式語言與自然語言的差距卻是確實存在,現今出現了不少學習編程遊戲,幫助沒有任何編程背景的人理解一步步來的程式思維方式,算是為減低學習編程門檻的一大進步。當然為了提起玩家之興趣,亦只包括了較為基本的程式語言語法。
Picture of Human Resource Machine
當代常用的程式語言,除了基本的流程控制結構,如if-else
條件判斷式、while/do-while/for loop
迴圈外,還有function
函數、class
類別、interface
接口等高級概念,這些詞語在英語都有原來的意思,在程式語言的意思與英語相似而非完全一樣,因此對初學者而言,是要從零學起,就像重學外語的基本語法一樣。再加上很多希望學習程式語言的人,未必以英語作為母語,使學習門檻更高。幸運的是,程式語言與自然語言不同,一旦學成了一種程式語言之後,要再學習其他程式語言,斷不會像學習外語一樣又要從零開始,因此精通數門程式語言,遠比精通幾門外語要簡單得多。
你必須學會抽象式思維
寫程式常常需要運用抽象思維,例如所謂的佇列(Queue)結構,對應現實生活的就是大家不論買早餐、搭地鐵、搭巴士時都會排隊的隊列,佇列在電腦世界很有用,常常用作將任務順序執行,這種佇列因此又名為任務佇列(Task Queue)。排隊是日常常見的事,但是將排隊的概念應用於電腦程式之上,正是抽象式思維(Abstract Thinking)的典型。除了愛好數學、科學的朋友之外,大多數初學者甚少以此角度思考問題,因此覺得困難也是自然不過。
又例如函數(Function),如果你將函數理解成數學的函數如f(x) = x * x
一樣,也就是將一些輸入(input)x
,轉換成輸出(Output),那尚算容易理解。但當你將函數作為其他函數的參數(Parameter),形成所謂的回調函數(Callback Function)的時候,就需要抽象式思維了。
function square(x) {
return x * x;
}
// Callback function in use
setTimeout(function () {
// This function is called after 1000ms
}, 1000);
抽象概念另一大難處,在於難以用日常生活例子類比理解,筆者在教學時常常花九牛二虎之力,讓學生理解回調函數及非同步的概念,初學者直覺上不習慣以此方式思考。正因日常生活難以鍛煉抽象思維,於大多數人而言,唯一受過對抽象思維有系統訓練的地方,就是中小學的數學課(低年級科學課通常重直觀,非抽象思維),不過很多人不喜歡數學課就是了...
你必須分毫不差
很多職業都要求高度的精確性(Precision):醫生用藥必須準確,否則病人就有性命之虞;土木工程師需要準確計算建築承托力,否則建築物就會成為危樓。編寫程式也是一樣,一個標點符號的錯誤,就足以令整個程式錯誤,甚至停止運行,而且回報的錯誤更常常令人摸不著頭腦。 大家一定對以下的經典畫面,不會感到陌生。
當大家自己學習軟件開發時,錯誤就更加常見。曾經學習過JavaScript作網頁開發的人,都一定見過以下的錯誤:
驟看之下,該錯誤就如不知所云,只是告訴你Uncaught ReferenceError: people is not defined
,其實這個錯誤的真正原因,在於上邊定義的是person
而不是people
,因此當瀏覽器嘗試運行時,只是發覺people
未定義,因此就直接回報people is not defined
的錯誤。
電腦不是人類,不能準確指出:「你把person
寫成了people
」的錯誤,只能夠重覆告訴開發者people is not defined
。正因電腦並不真正「理解」(縱是當今最先進的人工智能,也難以稱為真正理解)人類背後行事目的,因此無法準確回報一個常人能夠即時理解的錯誤。要成為程式開發者,就需要將相關的錯誤訊 息(如people is not defined
),聯想到常見的錯誤(串錯了變數名字)。因此可以說,程式開發本質需要分毫不差,任何最微細的錯誤,都不能夠出現。 然而相對於電腦的分毫不差,人類可謂天生就常常犯錯,除非經過後天大量訓練,否則難以習慣。
你必須持續練習
有留意筆者之前文章的讀者,都知道筆者常常强調軟件工藝的重要性。要學成一門工藝,必須要持續練習,才可以精益求精。
正如學會廿六個英文字母不等如成為莎士比亞一樣,學會程式語言的基本語法,也當然不是學習軟件開發的終點。單元測試(Unit-Testing
)、設計模式(Design Pattern
)、程式碼結構(Code Organization
)等,都是需要多寫多想,才能融會貫通。
筆者在軟件工程師成長手冊一文中,提過開發者有五個層次: 初學者(Novice)、進階初學者(Advanced Beginner)、能勝任者(Competent)、熟練者(Proficient)、專家(Expert)。要算是達到職業水平,起碼要到達第三層次能勝任者的級數。也就是對本身工作所使用的技術棧(Technology Stack)有一定瞭解,以及懂得發掘新技術,同時有能力學習新技術。而科技發展日新月異,技術推陳出新,程式開發亦自然如是,要能夠掌握新的技術,也一定需要持續學習。對絕大多數初學者而言,學習程式最難跨過的障礙,正是缺乏足夠時間去持續練習。因此這個因素簡單易明,卻也是癥結所在。
你必須有創造式思維
由普羅大眾角度看來,編程是一個只與電腦、數字打交道的職業,其實創造思維(Creative Thinking)在編程中,也是不可或缺。編程的本質,就是由無到有,創造(Create)一個能夠解決問題的程式。天底下沒有兩個程式設計師會寫出一樣的程式碼,就正如不會有兩個寫出一樣文章的作家。
編程果真需要創造思維嗎?其實對一個專業程式設計師之日常工作而言,從沒有兩個問題是完全一樣,因為如果該問題已經解決過一次,就再沒有解決的必要(除了教學用途外),這也是著名的DRY原則(Don't Repeat yourself)。例如為物件排序(Sorting),也許你會認為方法只有一兩種,但光是維基百科的Sorting Algorithm條目,就有超過二十種不同算法,而且每個方法,也有各種優劣之分。
為物件排序如此目標明確的問題,都可以有超過二十種算法,真正現實世界之系統,例如會計系統、倉存系統、客戶資料管理系統,就更可謂有無限種解決方法。程式設計時要選擇最佳最有效率的方法,就如一個畫家創作時選擇最美的表達方法,都必須有創造思維在其中,只是編程稍為限制,程式碼必須正確才能運作,筆者認為,這實是一種基於邏輯之創意(Logic-based creativity)。也正因如此,學習編程,也代表必須學習如何運用創造思維。
總結
一口氣講了學寫程式之困難所在,絕不是為了使各位正在學習編程的人卻步,而是正正因為學習編程的中途,會有不少障礙攔路,早早準備,就如攀登高山前充份準備一樣,才是合適的學習心態。