寫在休學之後(ㄧ): 別做螺絲釘,做自己

休學後,雖然還是一樣忙,但是忙的比較有意義,也比較有時間思考自己在做的事情。趁著忙裡的空閒,決定來不定期的寫一系列文,把重要想法給收斂起來,順便幫助將腦中的思考具像化。這篇文接續自寫在休學之後(序): 從雙主修到休學的一年,有興趣可以參考以上連結。

第一篇想深入談的是一些細節,講我在大學裡感受到的問題,然後分享最近在做的一個有趣專案,用它當作不靠大學體制也可以有不亞於在大學學習的、自我成長的例子。最後會談一點自己的理念、警惕和願景。

想要寫這些細節的動機,一方面是因為我不喜歡只做抽象層次的陳述和評價,而沒有給出具體實證想法的論點。另一方面則是基於一些朋友希望我分享學東西和做專案的腦中思路,我想這是個很好的機會。

首先來談談我在大學感受到的問題。讓我們在腦中回顧一下,大學是怎麼賦予學生知識的呢?

先不論透過什麼管道入學、也不談喜歡與否,想像你現在考進台大的某個科系。系上的教授會跟你講什麼是重要的、什麼是不重要的;系上會規定,你至少要完成什麼樣的課程才能畢業。可是注意了,這裡頭的「至少」,通常不少。

以台大物理系為例,你要修普物、普化、普生,要修很多衍伸出來的實驗課程;到了大二、大三,必修還有力學、電磁學、熱物理、量子物理。為了確保你的數學能力足夠,你要修好幾門應用數學課;為了確保你達到台大最基本的素質要求,你要修國文、英文;為了確保你在學業中沒有忽視掉服務,你要修服務學習,內容可能包含掃系館等雜活;為了達成台大學生理應具備的多元智能,你要修一定量的通識,而且還不能和自己的專業有所重複。

你逐漸發現,在完成這些「至少」的過程,自由度已大幅受限。什麼是重要該學的、什麼是不重要的,學校都幫你想好了。他們把知識封裝後,放在名為課堂的平台,讓學生像電腦一樣把相同的知識一份份下載進腦中。

現在來看一下我的個案。我喜歡物理,所以來到物理系,但馬上發現兩個問題:首先,因為高中基於興趣的關係,大學物理系的必修有六七成已經自學過了,而系上許多課程的教學品質和難度並沒有達到我的期望,效率也不會比自己翻書來的快。可是,除了大一普物和微積分以外,其他的必修課竟然沒有任何免修機制,要畢業,就得花很多時間修 CP 值相對極低的必修課。

再者,我覺得物理系的應用數學不是很嚴謹,加上對數學系的不少課程很有興趣,想去數學系修課再拿回來抵應數。怎知道物理系不給抵,並給出「物理的訓練和數學的訓練有不同的價值」之類的理由。我納悶著,不是說大學要多元嗎?為什麼當學生開始思考自己想發展的方向時,卻會被規定給阻撓呢?

想了幾天後,我決定雙主修到數學系,打算直接在那畢業。會選擇雙而不是轉的原因,是因為對物理系的朋友還有點歸屬感,而且這樣學生證才能自由的進出系館,但必修學分這時已經沒在管了。

以上僅僅是在物理系碰到的一些問題。若從更整體的角度來看,你會發現大學依然有很多不合邏輯的地方。

例如,我修的課橫跨物理、數學、電機、資工……但是這些系的課,最終都被歸類在選修。照這樣的進度來看,畢業前的通識學分絕對不夠;從學校的角度來看,我的學習還不夠多元。

例如,有不少課程我很想學,但是對方系上不給外系加簽,能做的只有旁聽。例如,有的課修到一半,覺得不值得、不想修了,但此時已經不能退選,能做的只有留下一個停修的紀錄,而且一學期只限定停修一門課。例如,大部分的課程都有期中、期末考,不管你的步調如何,不管你是否正在對所學的東西做重要的思考,時間一到,你就是要放下這些思考,開始準備眼前的考試。

最根本上的問題,就是學校幫我們做太多決定了。

一直以來,我學東西其實就只有三個理由:「我很好奇」、「我喜歡它」、「我需要學它」。好奇和喜歡,通常能產生極大的效率,但因為學校把一切都決定好了,使得大學很多課程最終都淪為「需要學它」的範疇。

再仔細思考一點,大學規定你學的東西,你真的需要嗎?你修的課,是真的能對你產生實質幫助,還是只是因為修過了能拿到大學文憑,而這個文憑很有用呢?

有沒有開始看出一點端倪了?

大學體制告訴你,畢業前你要學 A,然後學 B,最後再學 C。一個畢業生,學完了這些 ABC,拿了很高的 GPA,進到業界,可能突然發現這些東西只會用到一丁點,大部分東西都要靠自己學。進到學界,腦中想說自己有一定程度的能力,但根本不知道自己想解決什麼領域的什麼問題,只好日以繼夜的搜尋著好的論文題目。最終展開研究,又發現 ABC 的知識遠遠不夠。

我在學東西時,實質的動機和流程通常是這樣的:

我沒學過 ABC,但我對一個東西感到很好奇,或是想解決某個問題。於是開始透過網路 Google、透過詢問周遭的大神,讓自己在腦中先建立一個大圖。接著我沿著這個大圖,trace 到 A 裡頭的一個分支理論、C 裡頭的一個工具,然後發現 B 對這個問題完全用不上。

有了這些資訊後,我會開始仔細地研究細節,把自己不懂的地方努力弄懂,並想辦法融合裡頭的理論、嘗試一些新的想法,試圖去解決這個問題。終於,我把問題解決了,也滿足了自己的好奇心,且透過這次的經驗,我對 A 和 C 這兩個領域有了一些個人獨特的想法。但是此時此刻,你突然拿 ABC 的期中考卷給我寫,我 A 和 C 可能只能拿個二三十分,B 直接交白卷。

這就是我覺得大學倒果為因的地方。你要花很多時間學ㄧ些,根本不知道什麼時候會用上的理論和技術細節,卻又太少問自己,為什麼要學這些東西。日子一久,細節會忘,結果因為打從一開始就沒好好思索它們的價值和精神,當初的學習就這麼白費了。

現在我舉一個真實的例子,來解釋不靠大學,可以怎麼樣讓自己成長。

最近這陣子,我基於興趣在和電機系的朋友許秉倫合作一個 io 遊戲的專案,目標是結合一些最新的技術,開發出能讓網路工程師輕易上手製作 io 遊戲的模板,另一方面也是因為這樣自己想寫遊戲會更方便。在此之前,我對遊戲引擎沒什麼認識,真正學寫程式也才剛過半年,這對我來說是個很新的領域。

首先,我去 Github 找了一些 io 遊戲的專案原始碼來讀,發現可能是因為這些遊戲很好盈利,導致能參考的公開資源相當少。裡頭為數不多的幾個開源專案,Code 則寫的沒什麼架構。但這也間接顯示了如果這個專案做得好,開源後會有一定的價值。

我大致研究了一下大部分網路遊戲專案的架構,發現其實大體上都是分為後端運算和前端繪圖這兩個部分。而在前端繪圖這部分,網路開發常用的繪圖模式包含了 SVG、Canvas、WebGL 這三種。

再深入查了些資料,我發現 SVG 的優勢在生成的圖品質極高,而且本身基於 XML 可以直接用 DOM 來操控,但缺點是很吃效能,當你要讓大量的物件移動時,每個移動都要將向量圖重新渲染過一次,對於大型的多人連線遊戲來說並不是一個很好的選擇。Canvas 相較之下,因為不受 DOM 控制,做的事情就是單純的繪圖,每個幀重新做一次像素渲染,在大量圖像繪製時,效能優勢比起 SVG 高出許多。WebGL 的優點在 GPU 加速,效能非常高,特別是在渲染 3D 圖像時比起前兩者有極大優勢,最有名的 library 就是 three.js,可以在瀏覽器裡非常順暢的製作酷炫的 3D 動畫。

針對遊戲的性能需求,顯然 SVG 不適合這個專案。而若要開發能重複使用的模板,除了性能外,可讀性、模組化的能力和對 Javascript ES6 的支援也是要考量的點,直接硬尻 Canvas 和 WebGL 原始碼顯然不是明智之舉。於是我開始去找適合的第三方函式庫來引入這個專案。

最後我挖到了一篇《What are the best JavaScript drawing libraries?》的文,裡面比對了 26 個推薦的繪圖函式庫。首先我把基於 SVG 的庫剔除掉,在剩下的選擇裡仔細讀其他使用者所給出的優缺點分析,然後再到各個庫的 Github 參考原始碼並研究了一下寫法邏輯,最終選出了 Pixi.js 作為我們的繪圖引擎。Pixi.js 本身有個很棒的優勢,就是它是基於 WebGL,但遇到不支援 WebGL 的瀏覽器時,會直接切換到 Canvas 來渲染圖像。

前端引擎找好了,接著就是要處理後端溝通的部分。我的策略是給前端的按鍵和滑鼠綁 EventEmitter,根據玩家的使用機制將資訊傳給後端,後端再持續根據當下的資料做運算,然後用固定的幀率將新的結果傳回前端讓 Pixi.js 繪製。由於我只用過 Node.js 寫過 server,再加上以前有寫過一個讓 Node.js 和 React.js 分別處理前後端、並用 webpack 優化的開發模板,所以這次就果斷的直接把原 code 搬過來在上面寫。

不過因為是多人連線遊戲,會需要有個資料傳輸的廣播機制。為了解決這個問題,我引入了另一個很有名的函式庫 — socket.io。這是寫 messenger 這種多人聊天室很好用的函式庫,Github 上看到的不少遊戲專案也都有用到它,我覺得最大的優點是可讀性高、好寫,而且跟 Node.js 開發環境融合的非常好。

花了不少時間,總算找完要使用的第三方資源,接下來要做的就是初始化開發環境、更深入的研究這些函式庫,然後設計整個遊戲的程式架構。初始化環境很簡單,就把東西從後端傳到前端然後畫出簡單的圖形,歸功於秉倫的高效能,這件事很快的就完成了。研究函式庫的部分,是因為我們覺得 Pixi.js 的教學文件寫的沒有很好,要完全發揮出它的優勢,最好的方法就是直接看原始碼理解其運作原理。

Pixi.js 的原始碼雖然有幾十萬行,但因為有很棒的 jsdoc 註解,包 class 也包的很有架構,使得研究起來很容易抓到方向。裡頭有許多地方很有意思也極富巧思,例如在數學核心的程式裡頭,我看到它給二維空間中的向量增廣了第三個 index,用來判斷這個向量是方向向量還是位置向量,這樣的實作讓它可以用一個三乘三的矩陣來寫任意類型向量的平移變換;在繪製圖像時,他主要利用貝茲曲線和二次曲線來做近似,以畫出任何想要畫的曲線;在繪圖物件上,不論是 Graphics 和 Sprites,本身都繼承了一個叫 Container 的物件,每個 Container 都可以包任意數量的 DisplayObject,而每個 DisplayObject 都繼承了 EventEmitter 的功能,使得變形、消失、刪除都變得相當容易。

諸如此類的巧思還有很多很多,像是連結節點、著色、轉換繪圖模式、執行運算、大量產生相同物件等各式各樣的實作原理,都寫在裡面。因為覺得有趣,我還更深入的研究了一下 WebGL 和 OpenGL 是怎麼在 GPU 上運算的,發現很多很酷的東西。像是有個很有趣的觀念叫 Shader,主要的工作是讓 GPU 執行你所給的算法,而通常物體在渲染前,會先經過一個 Vertex Shader 把所有頂點的資訊算出來,然後再經過 Pixel Shader 將其上色。

令我印象深刻的是,引擎在計算點的位置時,方式是先由物件模型的座標系轉換到世界的座標系,再從世界座標系轉換到鏡頭的座標系做映射,中間要乘上好幾個矩陣運算,而算法正是使用線性代數的 Basis Transformation。

繪圖引擎研究的差不多以後,我們開始研究後端的架構。原本把所有的程式都包在一個 server.js 裡頭,現在要想的就是怎麼把不同的東西分發拆出去。這時候因為我資工系的室友涂家銘過去有過開發經驗,從他身上得到了非常多很有建設性的想法。我們一起思索要設哪些 class、加什麼樣的 member 和 method、寫什麼樣的繼承模式、怎麼跟 client 端溝通,然後也把物件類的類別、演算類的函式、遊戲邏輯的主控台和與用戶端溝通的 server code 分裝到不同地方,設計出適合的物件導向模式。

另外,為了確保自己在開發後端的東西觀念正確,我重新學習了 Node.js 的運作原理。在這個過程中,我參加了一些免費的工作坊、爬了一些文,研究 V8 的運作方式、Node.js 的 C++ 跟 javascript 連結的寫法、可能適合用來存私有資料的 Revealing Module Pattern、官方一些好用的 module 細節等等。另外也弄懂 CommonJs 在做 module.exports 背後,是怎麼把要輸出的東西透過一個函式重構後讓其他檔案 require,並認知到這件事和 ESM 在做 import/export 時建立 graph 並再裡面做 traversal 有著根本原理上的差別。除此之外,因為家銘的經驗讓我暸解到,傳資料除了用 TCP 傳以外,比較不重要的資料其實比較推薦使用 UDP。

講到這,如果你平常不寫 code,可能已經開始感到頭昏腦脹。而我們的專案也還在開發中,所以我先就此打住。但我想強調的是,如果有仔細看剛剛講的這些細節,會發現那些在大學裡面所學的:線性代數、計算圖學、資料結構、演算法、硬體原理、設計模式、物件導向、網路開發……各式各樣的知識,都在某種程度上被用上了,它們各自所佔重要性的權重不一,但顯然你不需要在一開始就完全暸解,而是可以從做中學,在必要的時候鑽深。除了理論和技術層面以外,這些查資料、解決問題的過程也是種對自己的訓練。當今天碰到一個你不熟悉的領域時,你要怎麼認識它、跟它做好朋友?

另外,團隊合作的過程也總是能學到很多東西。上學期跟 Vibert 和 Joey 合作 Beact 開發時,我能感受到自己在變強,一部分是因為 Vibert 會先把一些東西事先做好,然後將剩下的代辦分包成不同難度的任務丟給我做,但又給我極大的彈性,讓我自己有種潛能被激發的感覺。於是這次我也試著做類似的事情,開發前先把看似困難的專案拆成數個普通難度的任務,從中判斷哪些自己做、哪些可以循序漸進的放手交給秉倫,試著去引導夥伴的潛能,也從中互相學習。這過程讓我自己摸索出一點專案管理的能力。

而就在此時此刻,當我正在打著這篇文章的同時,磨練的是我構思表達的能力,也更能讓我弄清楚對我來說,什麼是重要的、什麼是不重要的。過去像這種文章,可能要打個半天,但現在只需要在自己比較沒有生產力時挪出三四個小時的空擋就能完成。

以上這些,只是當你在自己掌握學習和做事的主導權時,可以成長的一小部分能力而已。

重點來了。剛才講的所有技術相關的東西,有九成我以前完全沒有接觸過。更直白點,上大學前,我就是個電腦白痴。已經有不知多少人問過我同樣的問題:「你怎麼有辦法學這麼快?」,而他們大都習慣性的把這件事歸到天賦上。

真的是天賦嗎?

高中時,當周遭同學把大量時間拿去讀學校課業時,我只專心學我想學的東西、做我想做的事。到大一,許多同學開始放下書本,過著天天打英雄聯盟、耍廢、抱怨自己找不到目標的生活時,我依然在做相同的事。大一下時,有個朋友笑著對我說「你太強了啦,你在做的那些東西我可能學很久都學不會」,在那個時刻,我正坐在床上 Google 各種文件,焦頭爛額的幫我的程式 debug,而他則是無聊的滑著手機耍廢,準備等休息夠了以後快速背一背明天要考的小考,這畫面當下形成了極大的反差。

快速接觸和學會新事物的能力固然重要,但更為重要的,是努力、毅力、投注的時間。天賦再高,沒有努力,不可能做出什麼有意義的東西。我的努力不是指訂一堆時程表強迫自己痛苦的把它完成,而是指找到適合自己的節奏,一步一腳印的學習和享受知識。

這就是為什麼我選擇休學。大學有著滿滿的知識,卻沒什麼給出動機和思考,反之替我們訂了一堆作業和考試的時程表、壓縮能運用的時間、強制的扭曲了每個人不同的學習節奏。而更令我難過的是,這個體制沒有給多元學習足夠的包容。

當我今天寫一篇滿滿的程式技術文,就會有人問我是不是想轉到資工系;明天跟好朋友很熱情的聊物理,又會有人問是不是要回歸物理系。但事實上,在我腦中根本沒有「走資工」、「走物理」、「走數學」。我就是我,在每個領域都有自己喜歡的部分、討厭的部分,但是我可以讓這些知識在腦中協調的並存、讓他們互相影響激盪。然而用大學的評準,我發覺自己沒辦法被定義到某個系所裡頭,因為在這裡面任何一個系要畢業,對我來說都要浪費很多時間在做不想做的事情上。

我曾想過妥協自己去拿個學位,但緊接著卻又發現另一件可怕的事。只要大學的必修越多、畢業門檻越高、修課限制越深,最後畢業出來的人就會越缺乏多樣性。換句話說,把兩個相同系所的學生拿出來比較,強度可能稍有差距,但因為被迫修同樣的課、考同樣的試,最後能力類型的重複性便高的不可思議。這樣的高重複性,意味著高替代性,就好像一個機器裡面的螺絲釘一樣,出問題了隨時可以再去換新的。

台灣作為一個以技術代工聞名全球的國家,這樣的策略也許是正確的;但作為一個人,意識到這件事後只讓我更想掙脫。我想要創造獨一無二的東西、讓未來能產生一些奇妙的變化,也想從各式各樣的角度認識這個世界。但要達成這樣的理想,不需要成為最強的螺絲釘,而是要花更多的時間和彈性去尋找自己想追求的東西、去暸解自己可以用什麼方式給世界帶來價值,再進一步的用自己的方式實踐。

別做螺絲釘,做自己

這是我這篇文章的標題,也是最後的結論。