咱們常常能看到許多技術文章從工程角度介紹各類編碼實踐。不過在計算機科學以外,編程語言和天然語言之間一樣有着千絲萬縷的聯繫。下面咱們會從高中水平的語文和英語出發,分析它們與代碼可讀性之間的關係。若是你看膩了各類花哨的技術新概念,或許迴歸基礎的本文能給你一些啓發🤔前端
大師所編寫的代碼與其說是給計算機看倒不如說是給人看的。真正的大師級程序員所編寫的代碼是十分清晰易懂的,並且他們注意創建有關文檔。git
——《代碼大全》程序員
不妨思考一下,咱們對某段代碼【十分清晰易讀】的評價,比起對某篇文章【寫得通俗易懂】的評價,是否具備相近的評價標準呢?進一步說,編程語言的代碼和天然語言的文章之間,是否存在着某些技術以外的共通性呢?這裏咱們拿出和代碼同樣死板的高考做文做爲對比,不難發現一些有趣的類似之處:github
是否是有着很多相近之處呢?不過,高考做文的記敘、議論、抒情等文體已是人類思惟的高級抽象,尤爲是抒情文這類涉及感情的文體,其內容與理念是很難和講求邏輯的程序代碼作類比的。而且,編寫做文所用的漢語也更不是主流編程語言所用的英語,這也就意味着從中文做文的角度着手分析可能過於宏大且不夠貼切。所以,下面咱們會改從英語的角度來探討代碼與語言之間的關係。web
中英文裏都有詞性的概念,詞彙能夠分類爲名詞、動詞、形容詞、連詞、代詞等不一樣詞性。而在計算機語言中,內置的【詞彙】就是 for
/ if
/ else
這些關鍵字了。那麼這些關鍵字的詞性,和計算機語言的性質之間有什麼關係呢?算法
實際上,不一樣用途的計算機語言,其關鍵字中對詞性的選擇會有很大的不一樣。請注意,編程語言其實只是計算機語言的子集。好比,經典的前端三件套 HTML + CSS + JavaScript 中:編程
它們都歸屬於計算機語言,但它們各自所用的關鍵字,在詞性上有什麼區別呢?瀏覽器
<head>
/ <body>
/ <img>
/ <table>
這些標籤。這些標籤的名稱都是名詞。.xxx { background: black; }
這樣的規則。這裏,規則的名稱基本都是形如 background
/ position
/ width
/ color
的名詞,而規則的值則常見各類形容詞。function
/ var
/ class
這三個名詞之外,它的控制流邏輯幾乎都是由 if
/ else
/ for
/ while
這些虛詞控制的。而且,還有大量 return
/ break
/ new
這樣的動詞。咱們能夠發現,標記語言和樣式語言中,關鍵字幾乎徹底由實詞組成,徹底不須要虛詞的起承轉合。而實詞可以表達什麼呢?它可以表達一個東西是什麼。因此,HTML 和 CSS 中,你須要告訴機器的是你想要的是什麼,而不關心怎麼去實現。好比,你告訴 HTML 解析器這裏有個 <img>
圖片,而無需操心圖片的格式、如何載入等細節;你告訴 CSS 引擎去把標題顏色渲染爲紅色,但無需關心佈局的如何計算、GPU 如何渲染等實現方式。所以,在計算機科學中咱們把 HTML 和 CSS 歸爲聲明式的語言,而這類語言的一大特點就是其關鍵字幾乎所有是實詞。bash
聲明式的語言通常而言比較簡單易懂(類比一下,你以爲最難維護的代碼是 HTML 和 CSS 嗎?),而三件套中剩下的 JavaScript 顯然不是這樣。爲了搞懂它所用關鍵字詞性和它做爲編程語言之間的關係,咱們有必要更詳細地對它的常見關鍵字作一個分類:網絡
名詞
function var class
動詞
import export extends return break continue
delete switch new try catch throw yield
介詞
for in else
連詞
if while
代詞
this複製代碼
聯想一下編程語言平常的使用場景:告訴瀏覽器要先請求某個接口、拿到數據後若是格式怎樣怎樣就作什麼什麼事情、若是點擊肯定那麼發一份新數據看後臺回覆了什麼……這些內容所編寫的代碼都是在描述問題怎麼作而非問題是什麼。因此,編程語言須要大量的虛詞,來用分支、循環等方式表達等各個語句間的邏輯關係。這種代碼的【文體】就是所謂的過程式編程了。
除了出現許多表達控制流的虛詞之外,編程語言的一大特點在於它具有大量的動詞做爲關鍵字。若是說 function
/ var
/ class
可以讓咱們定義基本數據概念的話,大量的動詞關鍵字則提供了對這些概念的操做能力。好比,咱們會用 import
/ export
來操做 模塊
這個概念模型;用 try
/ catch
/ throw
來處理 異常
這個概念模型;用 new
/ extends
來處理 類
這個概念模型……因此,過程式的編程語言中須要大量的動詞,來表達對數據的操做。
對動詞的使用並不只僅體如今關鍵字中,在實際的編碼實踐中也會大量運用。好比,Python 2 中的 print
語句在 Python 3 中變成了 print
函數,不就說明平常編寫的函數和語言關鍵字之間是能夠互相轉化的嗎?因此,在咱們編寫對數據的處理代碼時,相應的代碼也應當可以用命名爲動詞的函數來封裝。固然,真實世界中的函數定製型通常很是強,比起編程語言中的精粹關鍵字來要具體的多,所以函數名多半不能簡單地用一個動詞來表達,這時候用一個形如 getElementById
的動賓短語結構來命名函數,就可以達到很好的效果了。
在現代的編程語言中,除了變量、類對應的名詞;函數、方法對應的動詞、控制流對應的介詞、連詞之外,還有一類很是特殊的存在:this
對應的代詞。代詞在編程語言中起到了什麼做用呢?天然語言中,代詞能夠在語境中天然地指代先前提到的概念,而 this
則用來指向某個上下文中的引用,在概念上是否是很是接近呢?
不過遺憾的是,從類比天然語言的角度來看,JavaScript 中的 this
初始設計是十分失敗的。在早期的前端開發中,this
常常不可以在代碼的【語境】中指向你所認爲天然的地方,而是有各類奇怪的規則來指向不一樣的上下文。對這類語言機制上的缺陷,社區也作了很多改進,來讓使用了 this
的代碼更易寫易讀。這其實也能夠理解爲天然語言的可讀性對編程語言設計的影響吧。
天然語言中,咱們能夠將詞彙整理爲句子,而句子則具備不一樣的結構,如陳述句、祈使句、疑問句、感嘆句等。
類比到編程語言中,在一門語言的新手課程裏,通常會提到 Statement 語句和 Expression 表達式的概念。好比,if (color === 'RYB') fxck();
總體就是一個語句,而其中的 color === 'RYB'
則是一個表達式。
編程語言的語句、表達式比起天然語言的句子,它們之間有什麼關係呢?祈使句、疑問句、感嘆句都夾雜了必定的感情,和咱們的主題不太相關。讓咱們從天然語言中最簡單的陳述句語序來作些探索吧。咱們選出其中兩種最具備表明性的結構,即主謂賓結構和主系表結構:
孩子去上學。
這就是一個很是容易理解的主謂賓結構了。這個結構也很是容易對應到編程語言裏的代碼:
child.go(school);
主語對應一種數據模型,謂語對應函數方法,賓語對應函數的參數。沒有問題,很是清晰易讀吧?
學校是黑色的。
主語 + 系動詞 + 表語的結構一樣很是易讀。但這裏存在着一個很是大的陷阱:天然語言不區分語句和表達式,而上面這句話既能夠理解爲語句,也能夠理解爲表達式:
school = 'black';
是一種語句類型的代碼實現,語句沒有返回值。school === 'black'
是一種表達式類型的代碼實現,表達式會返回 true
或 false
。這樣就出現了很是大的歧義了:這句話在翻譯爲代碼的時候,到底指的是 把 school 賦值爲 'black'
,仍是 判斷 school 是否爲 'black'
呢?在控制流裏,這樣的歧義就會形成問題:
if (school = 'black') fxck()
if (school === 'black') fxck()複製代碼
在表達 若是學校是黑色的,那麼 xxx
時,就會在代碼裏形成混淆。上面的代碼裏,前一種無論學校黑不黑都會 xxx,然後一種纔是合理的實現。
從這個例子中咱們能夠發現,在將可讀的天然語言轉換爲代碼邏輯時,天然語言的簡單陳述句能夠對應到編程語言的語句上,而表達邏輯的複合句中,從句則更接近表達式的概念。編程的一大挑戰就是去理清天然語言中模糊不清的邏輯,這須要對編程語言的學習和不斷的訓練才能更好地作到。
詞彙能夠組成句子,而英語中的句子是存在時態的概念的。巧合的是,數據的狀態也是程序運行時很是重要的概念。在這裏,咱們也能創建很好的類比關係。
同步和異步,在真實世界的程序中很是常見。好比用戶在頁面上點擊肯定按鈕向後臺提交數據的時候,網絡請求和響應就須要時間來傳輸,請求的結果就是異步展現的。那麼,同步與異步可以類比到天然語言中的什麼時態呢?
同步代碼不存在時態的問題,你大能夠用通常如今時來命名變量和函數,整個執行流程會十分清晰。但牽扯到異步時,你就會發如今你訪問某個變量的時候,它可能尚未值。這時候怎麼處理呢?
Promise 對象是處理異步邏輯的一大利器。一個 Promise 具備 pending
/ resolved
/ rejected
三種狀態,咱們能夠用 resolve
和 reject
來在狀態間遷移。這裏咱們表達操做的命名仍然是動詞,但這時咱們能夠注意到,不一樣的狀態是用如今進行時和如今完成時來命名的。更通常地,咱們能夠抽象出這樣的規則:
這樣一來,咱們就可以把天然語言中對時態的思惟模型,平滑地遷移到代碼裏表達異步的狀態中,從而讓代碼更加易讀了。
上面的諸多內容其實都僅僅是 Grammar 語法層面的內容,但【語文】的外延是【語言學】這一學科,其研究領域遠不只僅是高中語法知識這麼簡單。能夠很是確定地說,做爲文科的語言學,對編程語言的設計和實現都有着很是重要的影響。這麼說有什麼根據呢?讓咱們從語言學中的一個分支【句法學】提及吧。
句法即 Syntax,編譯器的常見報錯 SyntaxError
指的就是句法錯誤。句法學的研究領域中,涉及到了對句子的結構分析。早期的研究者們提出過兩種分析法,即【雙切分法】和【方括弧法】。好比下面的句子:
The teacher abuses a child.複製代碼
按照雙切分法,咱們先把整句話一分爲二,而後把謂語分開,最後分解名詞短語,就能夠一步步地獲得這樣的結果:
第一次切分
The teacher / abuses a child.
第二次切分
The // teacher / abuses // a child.
第三次切分
The // teacher / abuses // a /// child.複製代碼
這樣咱們就可以拆解出句子的主謂賓結構了。
而方括弧法的解釋方式則是這樣的:
[3 [1 The teacher] [2 abuses [1 a child]]]複製代碼
咱們先爲名詞短語 The teacher
和 a child
添加方括弧,而後組成更高層的動賓結構,最後合成爲句子。
那麼這兩種方法和編程語言有什麼關係呢?從上例中咱們能夠看到,雙切分法的處理方式是自頂向下,而方括弧法的處理方式是自底向上。若是瞭解過編譯原理的同窗,看到這裏應該會馬上想起語法分析器中的 LL 算法和 LR 算法吧?LL 算法遞歸向下地處理代碼語句,而 LR 算法則是自底向上地歸約詞法元素。因此,編譯器前端在將代碼字符串轉換爲語法樹的時候,語法分析算法的運行方式和語言學中的方法論是共通的。
除告終構分析外,句法學對編程語言的一大貢獻在於它提出瞭如何定義一門語言的方式。在句法學的課本中,會說起 Chomsky 在 1957 年提出的《句法結構》一書,這本書中提出了生成文法的概念,可以抽象地用數學符號定義任意一門語言。好比一條代表【名詞短語(Noun Phrase)包含形容詞(Adjective)和名詞(Noun)】的句法規則,形如:
NP → A, N複製代碼
這樣,語法樹中 NP: damn school
的節點就能被拆分爲 A: damn
和 N: school
的子節點了。推廣到計算機語言,這個文法一樣適用。好比這條規則:【一個 HTML 標籤(Tag)要包含開始標籤(TagOpen)、值(Value)和結束標籤(TagClose)】:
Tag -> TagOpen, Value, TagClose複製代碼
經過這樣的句法規則,咱們就能把 HTML 樹中形如 <p>123</p>
的字符串拆分爲 TagOpen: <p>
、Value: 123
和 TagClose: </p>
的三個子節點了。在現代的 LLVM 編譯器前端中,咱們只須要提供這樣的句法規則,就可以定義出本身的一門新計算機語言了,是否是徹底相通呢?因此,Chomsky 文法在《編譯原理》中也有詳細的介紹,這也是一個計算機科學中橫跨文理的概念了。
值得一提的是,實現一個語法分析器的輪子是件頗有趣的事情。筆者在大學時的編譯原理大做業中,實現的就是一個 JavaScript 版的 LALR 語法分析器。這個過程能讓你深入地認識到弱類型語言到底有多坑…歡迎有興趣的同窗嘗試😀
在語言學的範疇中,還有一個和編碼密切相關的地方出如今【語義學】中。不太準確地說,這個學科研究的是【詞彙到底有什麼涵義】的問題。好比,一個 望遠鏡
在何時會讓人以爲噁心?這其實能夠對應到編程語言中另外一個很是重要的概念,即做用域。
語義學中認爲,名詞指稱事物、動詞指稱行爲、形容詞指稱屬性、副詞指稱方式,故而詞語具備指稱的功能。詞語的指稱分爲有指和無指兩種,好比 school
可以指代明確的對象,是爲有指;而 if
指稱的是抽象的條件和假設關係,是爲無指。有指能夠進一步分爲衡指和變指兩種。好比,長城
就是一個所指對象不變的衡指,而 he
就是所指對象因場合而變的變指。在不一樣的語境下,詞彙的實際指稱會發生改變。
衡指和變指的概念,是否是和編程語言中的全局變量很接近呢?好比,document
就是一個老是指向 DOM 的全局變量,而 this
的指向就會因代碼所在的上下文而變。在語言中明確而無歧義的指代關係,剛好可以和編程語言中嚴謹的做用域相類比。
天然的指代關係,可以讓咱們在寫文章時流暢許多。而代碼中的局部變量,只要處在受限的做用域(如函數做用域)中,就能夠有精煉易讀的命名。若是沒有做用域機制,咱們就必須用笨拙而沉重的命名約定來防止指代不清和重名(例如早先前端的 BEM 命名法)。
因此,咱們不妨把做用域機制也當作現代編程語言爲了【更接近天然語言】所做出的努力。在爲變量起名的時候,咱們更能夠從語義學的角度來考慮這個變量名的指稱是什麼,具有怎樣的涵義。
從天然語言來類比編程語言,咱們確實能發現不少從技術角度被忽略的地方:
能夠看到,編程並非理科生的枯燥工做,它和人文學科之間的關係一樣緊密。
不過,有的同窗可能會有疑問:寫了這麼多,到底該怎樣能寫出更好的代碼呢?這不是一篇文章可以解決的問題。寬泛地說,這仍然須要多作、多思考、多向更好的代碼學習。若是本文可以激發你對於編程和人文間某些交叉點的興趣,那就足夠啦。
在寫做本文的過程當中,筆者還有了一個額外的發現,那就是若是人工智能會取代人類,那麼編程恐怕也是最後幾個被取代的崗位。從上面的論述中咱們能夠發現,好的代碼須要清晰的模塊拆分和流暢的表達,而這兩點實際上都和人文科學有着莫大的關係。這個角度上說,編程並非重複性的工做,而是智慧的沉澱。
因爲做者只是計算機科學和語言學的【用戶】而非【研究者】,所以本文的內容其實很是粗淺,對於錯漏,但願這兩個領域中更加專業的同窗斧正。
最後,這個專欄後續還會不按期更新一些將編程與真實世界現象相結合的雜文。有興趣的同窗,歡迎關注做者的掘金或 Github 哦😀