抱怨 JS 疲勞就像是在抱怨人類發明了太多解決問題的工具:從郵件到飛機到宇宙飛船。
上週我在 NebraskaJS 2017 會議上做了一個和這個話題極其類似的演講,我也收到了許多積極的反饋,所以我就想這個演講也可以寫成一篇文章發表出來,讓更多的人知道,並幫助他們應對 JS 疲勞,理解我們行業的真相。這篇文章的目的是希望改變你對軟體工程行業的普遍的看法,助你在你可能工作的領域上一臂之力。
激勵我寫下這篇文章並且徹底改變我生活的一個原因是 Patrick McKenzie 寫的這篇很讚的文章,文章名叫《請不要自稱程式設計師和一些職業生涯建議》。強烈推薦你閱讀下上面這篇文章。本文的大部分內容都是基於 Patrick 的那篇關於 JavaScript 生態系統的文章的建議,其中也夾雜了最近幾年我在科技行業工作的一些想法。
第一個章節可能會有點哲學化,但是我保證絕對值得一讀。
我們行業的真相 101
就像 Patrick 在 他的文章 裡寫到的,我們先從一些最基礎、最根本的真相說起:
軟體是用來解決業務問題的
實事就是這樣。軟體存在的意義並不是用來取悅我們程式設計師的,並不是為了讓我們寫出漂亮程式碼的,也不是為科技行業創造就業機會的。實際上,軟體的存在扼殺了太多的工作崗位,其中也包括我們的,這就是為什麼基本工資在未來的幾年將會變得更加重要,但是這就完全是另一個話題了。
我這樣說很抱歉,但是歸根結底原因在於:在軟體工程中(其他行業也是如此)只有兩樣東西至關重要:
支出和收益
支出削減的越多,收益提升的越多,那麼你的價值就越大,削減支出、提升收益最通用的一個方法就是用機器代替人工,從長遠來看,這種方法是更有效的,而且支出也更少。
你不是被僱傭來寫程式碼的
科技不是目的。沒有人關心你使用的是什麼程式語言,沒有人關心你的團隊選擇的是什麼框架,沒有人關心你的資料結構有多麼優雅,也沒有人關心你的程式碼寫得有多漂亮。人們唯一關心的是:你的軟體支出是多少,產生的收益是多少,僅此而已。
寫出漂亮的程式碼對於你的客戶而言沒有任何卵用。我們之所以要寫漂亮的程式碼,是因為長遠來看這樣會更高效,能夠減少支出,增加收益。
我們努力避免 bug 不是說我們重視正確性,而是我們的客戶重視正確性。如果你曾經遇到過一個 bug 成為了一個特性的話,那麼你就知道我在說什麼了。這個 bug 確實存在,但是我們不會去修復。出現這種情況並不是說我們的目的就是製造 bug,我們的目的是創造價值。但是如果我們的 bug 能夠使客戶開心,能夠提升他們的收益,我們也能達成我們的目標,如此皆大歡喜,何樂而不為呢。
可重複使用的太空火箭、自動駕駛汽車、機器人、AI:這些東西之所以存在,並不是因為我們覺得它們很酷,而是因為在它們的背後有商業利益存在。我並不是說這些東西背後的那群人只在乎金錢,我確定他們也認為這東西很酷,但事實是,如果沒有經濟利益或者說沒有潛在的經濟價值,這些東西是不會存在的。
也許我不應該將這一章節取名為“我們行業的真相 101”,也許應該取名為“資本主義真相 101”。
說到我們的目標——減少支出提升收益——我認為作為程式設計師的我們應該更加關注需求和設計,積極思考,積極參與業務決策,這就是為什麼瞭解我們正在開展的問題領域變得極其重要。之前你有多少次在你的經理或商務人士沒有考慮到的某些邊緣案例中想到應該發生什麼?
1975 年,Boehm 做過一項研究,研究發現,軟體中 64% 的錯誤都是由設計引起的,只有 36% 的錯誤是程式碼錯誤。另一項叫做 “高階軟體——軟體定義方法論” 的研究也顯示:在 NASA 的阿波羅計劃中,73% 的錯誤都是設計錯誤。
設計和需求存在的唯一目的就是定義我們將要解決的問題,而解決問題就能創造收益。
沒有了需求或設計,程式設計就是往一個空的 text 檔案裡面新增 bug。
- Louis Srygley
這個原則同樣適用於 JavaScript 生態系統的工具。Babel、webpack、react、Redux、Mocha、Chai、Typescript,所有這些工具之所以存在就是為了解決對應的問題,我們要理解它們要解決的是什麼問題,要仔細思考什麼時候需要這些工具,否則,我們就會感到 JS 疲勞,因為:
當我們使用我們不需要的工具來解決根本就不存在的問題的時候,JS 疲勞就出現了。
正如 Donald Knuth 曾今說的:“過早優化是萬惡之源”。請記著,軟體的存在是為了解決相應的業務問題,大部分的軟體其實都挺令人厭煩的,既沒有多強的擴充套件性,也沒有高效能約束。要專注於解決業務問題,專注於減少支出、提升收益,這才是需要關注的焦點。只有在當你需要優化的時候才去優化,否則,軟體可能會增加一些不必要的複雜性,而這些複雜性會增加支出,並且不能產生足夠的收益來抵消這些支出。
這就是為什麼我認為應該在我們的工作當中應用 測試驅動開發 原則。我說的測試驅動開發並不是說僅僅去做測試。我說的是在問題暴露之前將其扼殺在搖籃裡。這才是 TDD 要做的。正如 Kent Beck 說的“TDD 減少了恐懼”,因為它能夠指導你的開發節奏,允許你慢慢地逐步解決你的問題,一步一個腳印,一次解決一個問題。當我們要使用新的技術時,這樣做同樣也會減少恐懼。
一次解決一個問題同時也降低了 分析麻痺,舉個例子,就好比你開啟了 Netflix,你本可以看一些視訊的,但是卻花了三個小時來決定看什麼。一次解決一個問題的方式可以縮小我們做決定的範圍,縮小了做決定的範圍我們的選擇就會相對減少,選擇減少了我們就降低了分析麻痺。
不知道你有木有想過,如果只有幾個可看的電視訊道,決定看哪個頻道會變得多麼簡單?如果家裡只有幾張遊戲盤,決定玩兒哪個遊戲會變得多麼簡單?
那麼對於 JavaScript 而言呢?
截止到我寫這篇文章時,NPM 上有 489,989 個包,第二天將會有差不多 515 個包在上面釋出。
我們使用、抱怨的這些包都是有一個歷史出發點的的,為了理解我們為什麼需要這些包,我們必須理解這個歷史出發點:它們是用來解決問題的。
Babel、Dart、CoffeeScript 和其他轉義器之所以會出現,是因為我們不僅僅使用 JavaScript 寫程式碼,但是我們又想使其能夠在瀏覽器中正常執行。Babel 甚至能夠使我們使用 JavaScript 新版本語法寫的程式碼在舊版本瀏覽器中執行,因為眾所周知,不同版本的 ECMA 規範在各個瀏覽器中的相容是一個很大的問題。儘管現在 ECMA 規範已經越來越可靠,但是我們仍然需要 Babel。如果你想了解更多關於 Babel 的相關知識,我強烈推薦你讀讀 這篇由 Henry Zhu 寫的很讚的文章。
像 Webpack 和 Browserify 這樣的模組化打包工具也有它們存在的理由。想必你們依然記得,曾幾何時,我們使用大量的 script
標籤將指令碼引入使其能夠正常執行。這樣做的結果就是汙染了全域性名稱空間,當一個指令碼依賴另一個指令碼時,很難合理地將它們整合起來。為了解決這個問題,Require.js
誕生了,但是它仍然有它自己的問題:它不夠簡單,語法也會引起其他問題,正如你在這篇文章中看到的。然後 Node.js 借鑑了 CommonJS
的 import,這種 import 是同步的,簡單整潔,但是我們仍然需要一種可以在瀏覽器中執行的方式,這就是為什麼我們需要 Webpack 和 Browserify 的原因。
Webpack 確實解決了很多問題,比如可以像處理 JavaScript 依賴那樣處理 CSS、圖片和許多其他的資源。
前端框架確實有點複雜,但是由於它們的存在,使得我們寫程式碼時減少了同步載入,如此一來,我們就不必擔心 DOM 操作,甚至也不用和那些亂七八糟的瀏覽器 API(JQuery 已經解決了這個問題)直接打交道,眾所周知,瀏覽器的相容性處理錯誤百出,而且效率低下。
這就是我們在電腦科學中一直在做的事情。我們使用低階抽象,並在其上構建更多的抽象。我們應該更多考慮的是,我們的軟體應該如何執行,而不是怎麼讓它執行,這樣的話,才能更高效。
但是所有這些工具都有一個共同之處:它們之所以存在是因為 web 平臺發展太快了。如今任何地方都有 web 技術的存在:web 瀏覽器,桌面應用,電話應用,甚至手錶應用。
這個革命性發展同樣也暴露出了我們需要解決的問題。比如,漸進式 web 應用(PWA),它們之所以存在不是因為它們很炫酷,不是因為作為程式設計師的我們樂於寫 PWA。請牢記本文的第一節:PWA 之所以存在,是因為它們創造了商業價值。
通常情況下,標準的制定速度並沒有那麼快,因此,針對對應的問題我們需要自己尋求解決方法,這就是為什麼有一個活躍度高、有創造力的社群是一件很 nice 的事情。我們不是在解決問題,就是在去解決問題的路上。當然,我們也會順其自然。
適用於我們的工具會更好地成長,獲得更多的貢獻者,更快地發展,有時一些工具最終將融合來自於其他工具的優秀想法,並且變得比它們更受歡迎。這就是我們演變的程式。
擁有越多的工具,我們就會擁有越多的選擇。想必你還記著 UNIX 思想,它主張我們在程式設計時,一次只做一件事情,而且要將它做到極致。
我們可以清晰的看到這種思想在 JS 測試環境中重現,比如,我們使用 Mocha 跑測試,使用 Chai 做斷言,而在 Java 中,JUnit 把這些事情全部包攬了。這就意味著,如果你在使用某一個工具的過程中遇到了問題,並且發現另一個工具更適合我們的話,那麼我們就可以直接簡單的替換掉之前的工具就可以了,其他工具的優勢我們依然能夠保留。
UNIX 思想同時主張我們應該去寫能夠“和諧相處”的程式。這正是我們在做的!比如說 Babel、Webpack 和 React。同時使用它們完全能夠正常執行,但是我們並不需要使用一個工具而去依賴另一個工具。比如我們在測試環境中使用 Mocha 和 Chai,那麼我們也可以安裝 Karma 在多種環境中來跑同樣的測試。
如何應對
針對正在遭受 JS 疲勞的同學,我的第一個建議是:你要清醒的認識到你並不需要掌握所有東西。有時我們會一次性學習過多的知識——甚至當我們並不需要的時候,這樣做只會增加疲勞感。你喜歡的領域你要保持積極的學習動力,可以深入瞭解,而對於其他的知識,你大可保持慵懶的態度。我這裡說的慵懶不是讓你懶惰,而是在你需要某些知識的時候再去學習。當你遇到了問題,且需要使用某項知識技能來解決的時候,直接現學就可以了。
另一個重要的建議是:腳踏實地,從頭開始。在你使用任何 JavaScript 框架之前,請確保你對原生 JavaScript 學習的已經足夠透徹。這是你掌握 JavaScript 並能夠將其“玩弄於鼓掌之中”的唯一途徑,否則,當你遇到了你之前從來沒有見過的問題時,你就不知道該如何下手。學習核心的 web 技術——CSS、HTML5、JavaScript和電腦科學基礎,甚至是 HTTP 協議的工作原理——將會有助於你快速掌握任何其他的技術。
但是,請務必不要用力過度。偶爾你要挑戰一下自己,親自動手做一些專案。正如 Sacha Greif 在這篇文章裡寫到的那樣:花費過多的時間學習基礎知識就像你要學習游泳時總是在學習流體動力學。學到一定程度以後,你就應該跳到游泳池裡去嘗試游泳。
同時,請務必不要拘泥於一項技能。我們現在可用的技術其實在過去都早已被發明出來了。當然,它們特性不同,名字不同,但是,本質上它們都是相同的。
如果你看看 NPM 的話,它也不是什麼新技術,很早之前就有 Maven Central 和 Ruby Gems 了。
用來轉義程式碼的 Babel,也是借鑑了早期一些非常出名的編譯器的原則和理論,比如 GCC。
甚至 JSX 也不是什麼新想法。E4X(ECMAScript for XML)10 多年前就存在了。
那麼現在你可能會問:“那 Gulp、Grunt 和 NPM 指令碼又如何呢?”額,好吧,很遺憾的告訴你,這些問題 GNU Make 在 1976 年都已經解決了。實際上,現在仍然有大量的 JavaScript 工程在使用 GNU Make,比如 Chai.js。但是我們不會那樣做,因為我們是喜歡復古的潮人。我們使用 make
,因為它解決了我們的問題,正如我們之前討論過,這就是你的目標所在。
如果你真的想要理解某項技術,想要在面對任何問題時都能夠得心應手,那麼,請深入瞭解。成功最關鍵的一個因素就是好奇心,所以請深入瞭解你喜歡的技術。嘗試自下而上的理解它們,每當你認為某些東西如“魔法”一般時,那麼請通過探索程式碼庫來揭開它的神祕面紗。
在我看來,說到學習這塊兒,Richard Feinman 的這句名言最適合不過了:
我創造不了的東西,我理解不了
再來看看下面這句,這是 Richard 在同樣的一塊兒黑板上寫下的:
對於已經存在答案的問題要知道如何解決
是不是很贊?
當 Richard 說這些話的時候,他正在討論的是關於獲取理論結果並如何復現的問題,但是我認為,該原則同樣適用於軟體工程。能夠解決我們的問題的這些工具已經被發明出來了,它們已經存在了,所以我們也應該能夠自己來實現它們。
這正是我喜歡 Egghead.io 中一些視訊的原因,視訊中 Dan Abramov 解釋瞭如何從頭開始實現 Redux 中存在的某些功能,或者教你如何構建自己的 JSX 渲染器。
那麼我們為什麼不去嘗試著自己來實現或者去 GitHub 上閱讀程式碼庫理解它們的原理來實現這些東西呢?我確定你一定能夠發現很多有用的知識。評論和 demo 也許會撒謊,也許會誤導,但是程式碼不會。
另一個我們在這篇文章中談論的最多的話題是:你不應該超前你自己。遵循 TDD 原則,一次只解決一個問題。你被僱傭是來降低成本提升收益的,你所做的都是為了解決問題,這就是軟體存在的意義。
既然我們喜歡拿我們自身的角色和土木工程相關的作對比,那麼就讓我們快速對比下軟體開發和土木工程,就像Sam Newman 在《構建微服》中做的那樣。
我們喜歡自稱“工程師”或“架構師”,但是這樣真的好麼?我們一直在為不到一百年前的計算機開發我們所知的計算機軟體,而競技場都存在大約兩千年了。
還記得最近一次看到一座橋坍塌是什麼時候嗎?還記得最近一次你的手機或瀏覽器奔潰是什麼時候嗎?
為了更好的解釋,我借用一個我比較喜歡的栗子。
這是美麗驚豔的巴塞羅那。
當我們從這個距離看這座城市的話,它開起來和世界上的其他城市一般無二,但是當我們從上面俯瞰時,巴塞羅那看起來是這個樣子的:
正如你看到的,每一個塊兒都有著相同的尺寸,所有的塊兒都有條不紊的排列著。如果你曾經去過巴塞羅那的話,你一定知道穿越這座城市有多麼爽,知道它執行的多麼良好。
但是坐在飛機上俯瞰巴塞羅那的人無法預知兩百年或者三百年後它會成為什麼樣子。城市裡的人們進進出出,他們要做的是讓城市隨著時間的流逝有機地成長、適應。他們必須做好應對變化的準備。
同樣的事情也發生在軟體方面。軟體更新迭代很快,會經常需要重構,需求也會頻繁的變更,這完全在我們的預期之外。
所以,不要把自己當做軟體工程師,要把自己當做城市規劃者。讓你的軟體有機成長,按需適應。兵來將擋,水來土掩,問題來了就去解決,但是要確保一切都有其所在。
在軟體領域做這些事情比在城市裡做這些事情要容易的多,因為軟體是靈活的,土木工程並不是,在軟體世界裡,我們的“建築時長”就是編譯時長。在巴塞羅那我們不能通過簡單地毀掉建築給新的建築騰出空地兒,但是在軟體世界裡我們可以非常簡單的實現。我們隨時可以推翻重做,隨時可以做實驗,我們想構建幾次就構建幾次,時間花費也就那麼幾秒鐘,但是我們思考的時間卻比構建的要多得多。我們的工作是純智力型的。
所以把自己當做城市規劃者,讓你的軟體按需成長、適應。
通過這樣做,你就能夠更好的抽象,也會知道在什麼合適的時間來採用它們。
正如 Sam Koblenski 所說:
抽象只適用於合適的語境,而合適的語境是隨著系統的發展而發展的。
有時候我經常會看到這種現象:有些同學在學習一項新技術時會去尋找模板,但是在我看來,當你開始學習的時候,應當避免使用模板。當然,如果你已經有了一定經驗,那麼模板和生成器還是很有用的。模板剝奪了大部分的控制權,因此你就學不到如何去新建一個工程,你也無法準確理解每個程式碼片段適合哪裡。
當你無法輕輕鬆鬆地把事情搞定,當你感受到的都是苦苦的掙扎時,也許是時候另闢蹊徑了。我們的宗旨是力爭懶惰,你應該為了以後不工作的目標而去工作。如此一來,你就會有更多的自由時間去做其他的事情,從而減低了成本,提升了收益,所以這也是你達成目的的另一條途徑。你不應該沒頭沒腦的努力工作,你要更加聰明的工作。
也許有些人也會擁有你現在的煩惱,但是如果沒人這樣做的話,那麼你的機會就到了,你可以找到你自己的解決方法,可以給其他人伸出援助之手。
但是有時候在你沒有看到別人做的更優秀之前,你並不會意識到其實你也可以做的更高效。這就是與人交流的重要性。
通過和其他人交流,你可以分享你的經驗,為對方的職業生涯提供幫助,也能發現能夠提升我們工作流的新工具,而且更重要的是,你能夠學到解決問題的方法。這就是我喜歡閱讀分享公司解決問題方法的文章的原因。
尤其是在我們的領域裡,我們總是認為 Google 和 StackOverflow 能夠回答我們的所有問題,但是我們仍然有必要知道我們要問的問題。我確定會有這麼一種場景:你遇到了一個你無法解決的問題,因為你無法準確的知道發生了什麼事情,所以你自己都不清楚你應該問什麼問題。
但是如果我需要用一個建議來總結整篇文章的話,那就是:
解決問題。
軟體不是魔法箱,也不是一首詩(很不幸)。它的存在是為了解決問題,提高人們的生活水平。軟體的存在是為了讓世界向前發展。
年輕人,該你出動解決問題了。