咱们常常能看到许多技术文章从工程角度介绍各类编码实践。不过在计算机科学以外,编程语言和天然语言之间一样有着千丝万缕的联系。下面咱们会从高中水平的语文和英语出发,分析它们与代码可读性之间的关系。若是你看腻了各类花哨的技术新概念,或许回归基础的本文能给你一些启发🤔前端
大师所编写的代码与其说是给计算机看倒不如说是给人看的。真正的大师级程序员所编写的代码是十分清晰易懂的,并且他们注意创建有关文档。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 哦😀