↑↑
當你決(jué)定關注「日誌君」,你已然超越了99%的程序(xù)員
日誌君導讀:
本文轉載自王垠的一篇博客。本文對編程者(zhě)在選擇哪一種程序語言這(zhè)一問題上進(jìn)行指引和幫助(zhù),幫助編程者(zhě)融會貫通,寫出優(yōu)質代碼。
來源:博客,作者:王垠,點擊“閱讀原文”直達查看(kàn)原文。
對的,我這裏要講的不是如何(hé)掌握一種程序語言,而是(shì)所有的……
很多編(biān)程初學者至今還在給我寫信請教,問我該(gāi)學習什(shí)麽程序語言,怎麽學習(xí)。由於我知道如何掌握“所有”的程序語言,總是感覺(jiào)這種該學“一種”什麽語言的(de)問題比較低級,所以一直沒來得及回複他們 :P 可是逐漸的,我發現原來不隻是小白們有這個問題,就連美國大公司的很多資深工程師,其實也沒搞明白。
今(jīn)天我有動力了,想來統一(yī)回答一下這個擱置已久的“初級問(wèn)題”。類似的話題貌似曾經寫過,然(rán)而現在我想把它重新寫一遍。因為在跟很多人交流之後,我對自己頭腦中的(未轉化為語言的)想法,有(yǒu)了更精準的表達。
如果你存在以下的種(zhǒng)種困惑,那麽這(zhè)篇文章(zhāng)也許會對你有(yǒu)所幫助:
你是編程初學者,不知道該選擇什麽(me)程序語(yǔ)言來入門。
你是資深的(de)程(chéng)序(xù)員或者團隊領導,對新出現的種種語言感到困惑(huò),不知道該“投資”哪種語言。
你的團隊為使(shǐ)用(yòng)哪種程序語言(yán)爭論不休,發生各種宗教鬥爭。
你追逐潮流采用了某種時髦的語言,結果兩(liǎng)個月之後發現深陷(xiàn)泥潭,痛苦不堪……
雖然我已經不再過問這些世事,然而無可置疑的現實是,程(chéng)序語言仍然是很重要的話題,這個情況短時間(jiān)內(nèi)不會改變。程序員的崗位往往會要求熟悉某些(xiē)語言(yán),甚至某些奇葩的(de)公司要求你“深入理解 OOP 或(huò)者 FP 設計模式”。對於在職的程序員,程序語言至今仍然是可(kě)以爭得麵(miàn)紅耳赤(chì)的宗教(jiāo)話(huà)題。它的(de)宗教(jiāo)性之強,以至於我在批評和調侃某些語言(比如 Go 語言)的(de)時候,有些人會本能地以為我是另外一種語言(yán)(比如 Java)的粉(fěn)絲。
顯然我不可能是任何一(yī)種(zhǒng)語言的粉(fěn)絲,我甚至不是 Yin 語言的粉絲 ;) 對於(yú)任(rèn)何從沒見過的語言,我都是直接(jiē)拿起來(lái)就用,而不需要經過學習的過程。看了這篇文章,也許你會明白我為(wéi)什麽可以達到這個效果。理(lǐ)解了這裏麵的東西,每個程序員都應該可以做到這一點。嗯(èn),但願吧。
很多人在乎自己或者別人是否“會”某種語言,對“發明”了(le)某種語言的人倍加崇(chóng)拜,為各種語言的孰優孰劣爭(zhēng)得麵紅耳赤。這些問題(tí)對(duì)於我(wǒ)來說(shuō)都是不(bú)存在的。雖然我寫文章批評過不(bú)少語言的缺陷,在(zài)實際(jì)工作中我卻很少跟人爭論(lùn)這些。如果有其它人在我身邊爭論,我甚至會戴上耳機,都(dōu)懶得(dé)聽他們說什麽(me) ;) 為什麽呢?我發現(xiàn)歸根結底的原因,是因為(wéi)我重視的是“語(yǔ)言特性”,而不是整個的“語(yǔ)言”。我能用任何語言寫出不錯的代碼,就算再糟(zāo)糕的語言也差不了多少。
任何一種“語(yǔ)言”,都是各種“語(yǔ)言特性(xìng)”的組合。打個比方吧,一個程序語言就像一台電(diàn)腦。它的牌子可能叫“聯想”,或者“IBM”,或者“Dell”,或者(zhě)“蘋果”。那麽(me),你(nǐ)可以說蘋果一定比 IBM 好嗎?你不能。你得看看它裏麵裝的是什麽型號的處理器,有多少個核,主頻多少(shǎo),有多(duō)少 L1 cache,L2 cache……,有多(duō)少(shǎo)內存和硬盤,顯示器分辨率有多大,顯卡是什麽 GPU,網卡速度,等等各種“配置”。有時候你還得看各個組件(jiàn)之間的(de)兼容性。
這些配置對應到程序語言(yán)裏(lǐ)麵,就是所謂(wèi)“語言(yán)特性”。舉一些語言特性(xìng)的例子:
變量定義
算術運算
for 循環(huán)語句,while 循環語句
函數定(dìng)義,函數調用
遞歸
靜態類型係(xì)統
類型推(tuī)導
lambda 函數(shù)
麵(miàn)向對象
垃圾(jī)回收
指針(zhēn)算術
goto 語句(jù)
這(zhè)些語言特性,就像你在選擇一台電(diàn)腦的時候,看它裏麵是什麽配置。選電腦的時候,沒有人(rén)會說(shuō) Dell 一定是(shì)最好(hǎo)的,他們隻會說這個型號裏麵裝的是 Intel 的 i7 處理器,這個比 i5 的好,DDR3 的內存 比 DDR2 的(de)快這麽多,SSD 比磁盤快很多,ATI 的顯卡是垃圾…… 如此等等。
程序語言也是一(yī)樣的(de)道理。對於初學者來說(shuō),其實沒必要糾結(jié)到(dào)底要(yào)先學哪一種語言,再學哪一種。曾經有人給我發信問這種問題,糾結了好(hǎo)幾個星期,結果(guǒ)一個語言都還沒(méi)開始學。有這糾結(jié)的時間,其實都可以把他糾結過的語言全(quán)部掌握了。
初學者往往不理解,每一種語言(yán)裏(lǐ)麵必然(rán)有一套“通(tōng)用”的特性。比如變量,函數,整(zhěng)數(shù)和浮點數運算,等等。這些是(shì)每個通用程序語言裏麵都必須有的,一個(gè)都不(bú)能少。你隻要通過“某種語言”學(xué)會了這些特性,掌握這些特性的根本概念,就能隨時把(bǎ)這些知識應用到任何其它語言。你為此投入的時間基本不會浪費。所以初學者糾結要“先學(xué)哪種語言”,這種時(shí)間花的很不值得,還不如隨便挑一個語言,跳進去(qù)。
如果你不能用一種語言裏麵(miàn)的基本特性(xìng)寫出好的代碼,那你換成另外一種語言也無濟於事。你會寫出一樣差的(de)代碼。我經常(cháng)看(kàn)到有些人 Java 代碼寫得相(xiàng)當亂,相當糟糕,卻罵 Java 不好,雄心勃勃要換用 Go 語言。這些人沒有明白,是否能寫出好的代碼在於人(rén),而不(bú)在於語言(yán)。如果你的心中沒有清晰簡(jiǎn)單的思維模型,你用任何語言表述出來都是一(yī)堆亂麻。如果你 Java 代碼寫得很糟糕,那麽你寫 Go 語言代(dài)碼(mǎ)也會一樣糟糕,甚至更差。
很(hěn)多初學者不(bú)了解,一個高(gāo)明的程序員如果開(kāi)始用(yòng)一種新的程(chéng)序語言,他往往不是去看這個語言的大部頭手冊或者書籍,而是先有一個需要解決的問題。手頭有了問題,他可以用兩分鍾瀏覽一下這語言的手冊,看看這語(yǔ)言大(dà)概(gài)長什麽樣(yàng)。然後,他直接拿起一段例子代碼來開始修改搗鼓,想法把這代碼改成自己正想解決的問(wèn)題。在這個簡短(duǎn)的過程中,他很快的掌握了這個語言,並用(yòng)它表達出心裏的(de)想法。
在這個(gè)過程中,隨著需求的出現,他可能會問這樣的問題:
這(zhè)個語言(yán)的“變量定義(yì)”是什麽語法,需要“聲(shēng)明類型”嗎,還是可以用“類(lèi)型推導”?
它的“類(lèi)型”是什麽語法(fǎ)?是否支持“泛型”?泛型的 “variance” 如何表達?
這個語言的“函數”是什麽語法,“函數調用”是什麽語法,可否使用“缺省(shěng)參數”?
……
注(zhù)意到了嗎?上(shàng)麵每一個引號裏麵的內容,都(dōu)是一種語言特性(或者叫概(gài)念)。這些概念(niàn)可(kě)以存在於任何的語(yǔ)言裏麵,雖然語法可能不一樣,它們的本質都(dōu)是一樣的。比如,有些語言的參數(shù)類型寫在變量前麵,有些寫在(zài)後麵,有些中間隔了一個冒號,有些(xiē)沒有(yǒu)。
這些實際問(wèn)題都是隨著寫實際的代碼,解決(jué)手(shǒu)頭的問題,自然而然帶出來的,而不(bú)是一開頭就抱著語言手冊看得仔仔細細。因(yīn)為掌握了(le)語(yǔ)言特性的人都知道,自己需要的特性,在任何語言裏(lǐ)麵一定(dìng)有對應的表達方式。如(rú)果沒有(yǒu)直接(jiē)的方式表達(dá),那(nà)麽一定(dìng)有某種“繞過方式(shì)”。如果有直接的表達方式,那麽它隻是(shì)語法稍微有所不同而已。所以(yǐ),他是帶著問(wèn)題找特性,就像查字典一樣,而不是被淹沒於大部頭(tóu)的手冊裏麵,昏昏(hūn)欲睡一個月才開始寫代碼。
掌握了(le)通用(yòng)的語言特性,剩下的(de)就隻剩某些語言“特有”的特性了。研究語言的人都知道,要設計出新的,好的,無害的特性,是非常(cháng)困難的。所以一般說來,一種好的語言,它所特有的新(xīn)特性,終究不會超(chāo)過一兩種。如果有個語言號稱(chēng)自己有超(chāo)過 5 種新特性(xìng),那你就得小(xiǎo)心了(le),因為它們帶來的和可能不是優勢,而是災難!
同樣的道理,最好的語言研究者,往往不是某種語言的設(shè)計者,而是某種關鍵語言特性的設計者(或者支持者)。舉個例子,著名的計算機科學家 Dijkstra 就(jiù)是“遞歸”的強(qiáng)烈支持者。現在(zài)的語言裏(lǐ)麵都有遞歸,然而你可能不知道,早期的程序語言是不支持遞歸的。直到(dào) Dijkstra 強烈要(yào)求 Algol 60 委員會加入(rù)對遞歸的支持,這(zhè)個局(jú)麵才改變了。Tony Hoare 也是語言特性設計(jì)者。他設計了幾個重(chóng)要的語言特性,卻沒有設計過任何語言。另外大家不要忘了,有個語言(yán)專家(jiā)叫王垠,他(tā)是早期(qī) union type 的支持者和實現者,也是 checked exception 特性的支持者,他在自己的博文裏指出了 checked exception 和 union type 之間的關係(xì) :P
很多人盲目的崇拜語(yǔ)言設計者(zhě),隻要聽到有人設計(或者美其民曰“發明”)了一個語言,就熱血沸騰,佩服的五體投地。他們卻沒有(yǒu)理解,其實所有的程序(xù)語言,不過是像 Dell,聯想一樣(yàng)的“組裝機”。語言特性的設計者,才是像 Intel,AMD,ARM,Qualcomm 那樣核(hé)心技(jì)術的創造者。
所以初學者要想事(shì)半功倍,就應(yīng)該從一種“合理”的,沒有明顯嚴重問題的語言出發,掌握最關(guān)鍵的語言特性,然後由此把這些概念(niàn)應用到其它語言。哪(nǎ)些(xiē)是合(hé)理的入門(mén)語言呢?我個人覺得這些語言都可以用來入門:
Scheme
C
Java
Python
JavaScript
那麽相比之(zhī)下,我不推薦用哪些語言入(rù)門呢?
Shell
PowerShell
AWK
Perl
PHP
Basic
Go
Rust
總的說來,你不應該使用所謂“腳本語言”作為入門語言,特別(bié)是那些源於早期 Unix 係統的腳本語言(yán)工具。PowerShell 雖然比(bǐ) Unix 的 Shell 有所進步,然而它仍(réng)然沒有擺脫腳本語(yǔ)言的根本問題——他們的(de)設(shè)計者不知道他們自己在幹什麽。
采用腳本語言學編(biān)程,一個很嚴重的問題就是使得學習者抓不住關鍵。腳本語言往往把一些係統工具(jù)性質的東西(比如正則表達(dá)式,Web 概念)加入到語(yǔ)法裏麵,導致初學者為它們浪費太多時間,卻沒有理解編程最關鍵的概念:變量,函數,遞歸(guī),類型……
不推薦 Go 語言的原因類似,雖然(rán) Go 語言不算腳本語言,然而(ér)他的設計者顯然不明白自己在幹什麽。所(suǒ)以(yǐ)使用 Go 語言來學編程,你(nǐ)不能專注於最(zuì)關鍵(jiàn),最好的語言特性。關(guān)於(yú) Go 語言的各種毛病,你可以參考這篇文章。
同樣的,我不覺(jiào)得 Rust 適合作(zuò)為入門語言。Rust 花了太大精力來誇耀它的“新特性”,而這(zhè)些新特性不但不(bú)是最關鍵的部分,而且(qiě)很多是有問題的。初學者過早的關注(zhù)這些特性,不僅學不會最(zuì)關鍵的(de)編程思想,而且可能誤入歧途。關於 Rust 的一些問題,你可以參考這篇文(wén)章。
為了達到我之(zhī)前提(tí)到的融會貫通,一通百通的效果,初(chū)學者(zhě)應該專注於語(yǔ)言(yán)裏麵最關鍵的特性(xìng),而不是(shì)被次要的特性分心。
舉(jǔ)個誇張點的例子。我發現很多編程培訓班和野雞大學的編程(chéng)入門課,往往一來就教學生如何使用 printf 打印“Hello World!”,進而要他們記憶 printf 的各種“格式字(zì)符”的意(yì)義,要他們實(shí)現各種複雜格式(shì)的打印輸出(chū),甚至要求打印到文本文件裏,然後再讀出來(lái)……
可是(shì)殊不知,這種輸出輸入操作其實根(gēn)本不算是語言的一部分,而且對於掌握編程(chéng)的核心概念來說,都是次要的。有些人的 Java 課程進行了好幾個星期(qī),居然還在布置各種 printf 的作業。學生寫出幾百行的(de) printf,卻不理(lǐ)解變量和函數是什麽,甚至連算術(shù)語句和循環(huán)語句都不知道怎麽(me)用!這就是為什麽很多初學者感覺編程很難,我連 %d,%f,%.2f 的含義都記不(bú)住,還怎(zěn)麽學編程!
然而(ér)這些野雞大學的“教授”頭(tóu)銜(xián)是如此的洗腦,以至於被他們教過的學生(shēng)(比如我女朋(péng)友)到我這裏請教(jiāo),居然(rán)罵我淨教一些沒用(yòng)的東西,學(xué)了連 printf 的作業都沒法完成 :P 你別跟我(wǒ)講 for 循(xún)環,函數什麽的了…… 可不可以(yǐ)等(děng)幾個月,等(děng)我背熟了 printf 的用法再學那些啊?
所以你就(jiù)發現一旦被差勁的老師(shī)教過,這(zhè)個程序員基本就(jiù)毀了。就算遇到好的老師,他們也很難糾正過來。
當然這是(shì)一個誇張(zhāng)的例子,因為 printf 根本不算是語言(yán)特性,但這個例子從同樣的角度說明了次(cì)要膚淺的語言特性帶(dài)來(lái)的問題。
這裏(lǐ)舉一些次要語言特性的例子:
C 語言的語句塊,如果裏麵隻有一條語句,可以不打花括號。
Go 語言(yán)的函數參數類型如果一樣可以合並在一起寫,比如 func foo(s string, x, y, z int, c bool) { … }
Perl 把正則表達式作為語言的一種特殊語法
JavaScript 語句可以在某些時候省略句尾的分號
Haskell 和 ML 等語言的 currying
在基本學會(huì)了各種語言特性,能用它們來寫代碼之後,下(xià)一步的進(jìn)階就是去實現它們(men)。隻有實現了各(gè)種語言特性,你才能完全地擁有它(tā)們,成為它(tā)們的(de)主人。否則你就隻是它們的使用者,你會被語言的設計者牽著鼻子走。
有個(gè)大師說得(dé)好,完全理(lǐ)解一種語(yǔ)言最好的方法就是自己動手實現它,也就是自己寫一個解釋器來實現它的(de)語義。但我(wǒ)覺得這句話應該稍微修改一下(xià):完全理解一種“語言特性”最好的方法就是自己親自實現它。
注意我在這裏把“語言”改為了(le)“語(yǔ)言特(tè)性”。你並不需要實現整個語言來達到這個目(mù)的,因為我們(men)最終使用的是語言特性。隻(zhī)要你自己實現了一種語言特性,你就能理解這個特(tè)性在任何語言裏(lǐ)的實現方式和用法。
舉個例子,學習 SICP 的時候,大家都會親自用 Scheme 實現一個麵向對象係統。用(yòng) Scheme 實現的麵向對象係(xì)統,跟 Java,C++,Python 之類的語言語法相去甚遠,然而它卻能幫助你理解任何這些 OOP 語言裏麵的“麵向對象”這一概(gài)念,它甚至能(néng)幫助你理解各種麵向(xiàng)對象實現的(de)差異。
這種效(xiào)果是你直接學習 OOP 語言(yán)得不到(dào)的,因為在學習 Java,C++,Python 之類語言的時候,你隻是(shì)一個用戶,而用 Scheme 自己動手實現了 OO 係統之後,你成為了一個創(chuàng)造者。
類似的特(tè)性(xìng)還包括類型推導,類型檢(jiǎn)查,惰性(xìng)求值,如此等等。我(wǒ)實現過幾乎所有的語言(yán)特性(xìng),所以任何語言在我的(de)麵前,都(dōu)是(shì)可以被任意拆卸(xiè)組裝的玩具,而不再是淩駕於我之上的神聖。
寫了這麽多,重要的話重複三遍:語言特性,語言特性,語言特性,語言特性!不管是初學者還是資深程序員,應該專注於語言特(tè)性,而不是糾結於整個的“語言(yán)品牌”。隻有這樣(yàng)才能達到融會貫通,拿起任何語言幾乎立即(jí)就會用,並且寫(xiě)出高質量的(de)代碼。
福利時間:針(zhēn)對有雲服(fú)務器等(děng)雲計算資源需求的同學,為大家爭取到了UCloud 雲服務的300元代金券,注冊使用(yòng)UCloud時,活動/邀請碼填入:rizhi,即可獲得。有需求的同學可以嚐試,有問題請添加UCloud運營小妹個人微信號:Surdur進行谘詢。
程序員日誌
打造麵向資深開發者(zhě)的第一新媒體(tǐ)
深度丨有料丨有意思
【歡迎投稿】
程序員日誌微信號:IT_Log
投稿地址:IT_Log@163.com