Skip to content

四火的唠叨

一个纯正程序员的啰嗦

Menu
  • 所有文章
  • About Me
  • 关于四火
  • 旅行映像
  • 独立游戏
  • 资源链接
Menu

编程范型:工具的选择

Posted on 03/10/201410/08/2024 by 四火

programming language 这是我写的关于编程范型的文章中最后一篇。

在 《编程的未来》里面提到过,很多时候脑子里的算法还是不容易转变成代码,大部分情况下这不是你编码技巧的问题,而是编程语言的问题,或者更严格地说,是编程语言选择的问题。除了复杂性这个软件唯一的敌人,其它真正的困难,早就被数学家们解决了,如果问题和它的解决能够用数学轻松地表述出来,那计算机只是工具而已。极端地说,如果有合适的工具,那么就选择一个;如果没有,那么可以创造一个。仅此而已。

工程师的乐趣,大抵在解决实际问题上,既有解决问题的成就感,也有解决问题的过程。而为了解决问题,又需要分析问题,选择合适的工具,再来使用工具解决问题这几部分。我们对于各种设计模式和框架结构无比熟悉,却往往忘记了编程语言这个基石一般的工具。看看有多少人成天鼓吹 “语言不重要,重要的是设计,重要的是思维”,可问题的悖论在于,当没有足够的阅历和经验去驾驭各种风格的编程语言,设计也好,思维也好,只能局限于很小的一个圈子里面。

编程范型是一种很常见但是很有趣的给工具分类的维度,如果你不熟悉它,我曾经写过一篇文章简单介绍过,请先阅读。仅仅了解它对于工程师来说,是远远不够的,我们需要使用、总结,并且感受,在选择合适的编程语言以后,问题的解决一下子变得豁然开朗。

像使用 Groovy 一样使用 Groovy

这个小小的例子其实来源于我自己,在我最初学习 Groovy 的时候,并没有真正理解它。没有理解就意味着能够写出 “正确” 的代码,但是确是 Java 代码。这么说是因为 Groovy 的语法兼容 Java,而 Java 几乎是我学习计算机除了入门的 C 以外,第一次正儿八经系统学习和使用的语言。所以那时候我看问题都明显带着它的影子。

怎么才叫做 “像使用 Groovy 一样使用 Groovy” 呢?08 年的时候,在 InfoQ 的一次关于 Groovy 的交流活动里面,我举了这样一个例子(来自当时写的胶片,年代有些久远了,胶片里面有一些不合适的例子和言论请见谅)——构造一棵 DOM 树:

def page = new MarkupBuilder()
page.html {
  head { title 'Hello' } 
  body { 
    a ( href:'http://…' ) { 'this is a link' } 
  }
} 

这是什么写法?很像自己实现的某种 DSL,但是很容易理解对不对。如果你使用 C++或者 Java,要实现类似的效果,需要自己定义好 head、body、a 等等各种各样的方法,而 Groovy 之类的动态语言呢?一个 “method missing” 特性就搞定了。

再举一个例子,这样解析 XML 文件,取某个节点值的例子:

<freeSpace>
	<total>
	    <rank level="0">
  		    <maxSize>20000000</maxSize>
	    </rank>
		……
	</total>
</freeSpace>

解析的代码:

def static node = new XmlParser().parseText(
	new File('freeSpace.xml').getText()
) 

node.total.rank.findAll{
	it.@level == "0"
}.collect{
	it.maxSize.text().toLong()
}.max()

很直观、很清晰对不对,数组、集合……全部都是对象,全部都可以使用闭包一样的写法来遍历,不需要写 for 或者 while 那样过程式的语句。

动态语言

有一种经典的学习一门新语言的方法是比较法,比如从 C 迁入 JavaScript 的工程师,就会不由自主地比较这两门语言的异同,从而快速掌握语法,和新语言的写法。但是,单纯这样训练出来的工程师,只是能写出符合 JavaScript 语法的 C 语言而已。除了满地跑的全局 function 以外,还可以从命名、代码设计上面找到很多 C 语言的影子。

学习一门新的语言,一定要选择自己不熟悉的编程范型,否则,获得的仅仅是掌握的语法和规范,枯燥而没有乐趣。如果你精通或熟悉 C++、Java、C#这样的编程语言(C 语系,且属静态语言),但是没有接触过像 Groovy、Ruby 和 Python 这样动态语言的话,那动态语言应当是一个非常有趣的领域,足以改变以往的思维方式,开阔眼界。

image

Lisp 是动态语言的鼻祖,动态语言是运行时能改变程序结构或变量类型的语言,它的两大特点包括:

  1. 运行时改变自身的结构甚至是函数的定义;
  2. 程序和数据形式等价。

稍微解释一下。

第一条,指的是在运行时,而非编译期,对象自身的结构是可以更改的,比如说 user 对象,里面有 name 和 age 两个属性,但是在运行期间,我要增加一个从未有过的属性 “personality”,这在静态语言里是不可能发生的。

第二条,我们都知道传统地说,程序和数据是不同的,在运行时,程序是可以反复执行的,但是却是无法修改的;而数据是可以反复修改的,却是不用来执行的——但是动态语言把这二者的区别彻底打破了,结果就是程序也可以在运行时被修改,而数据也可以取出来当做代码执行。

其实,这两条本质上说的是一回事。

提到动态语言不得不提 “鸭子类型”。鸭子类型(duck type)这个概念指的是一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。理解它,也可以帮助理解动态语言:

在静态语言中,你需要定义一个 “嘎嘎叫” 的接口,让鸭子类实现自这个接口,然后在逻辑中引用实现自这个接口的对象并且调用这个嘎嘎叫的方法,虽说程序在运行时实现了多态,在编译期间编译器并不知道这个实现自嘎嘎叫接口的对象到底是什么类型的,但是它依然知道这个对象一定是实现了这个接口,一定是一个可以 “嘎嘎叫” 的对象。

但是在动态语言中,你根本不需要定义这样的接口(“不需要”,而不是 “不可以”),只需要在你的逻辑里面调用传入对象的 “嘎嘎叫” 方法就好了,不管这个对象是什么,如果这个对象可以嘎嘎叫,那么程序就顺利执行下去;反之,一个运行时异常会被抛出。

我猜想每一个习惯于静态语言的工程师在尝试使用动态语言来解决实际问题的是时候,都会度过一段挣扎期的,这段时间里,写的代码有可能不伦不类,很不自在。根据烟斗理论,学习一个新东西往往需要退步一段时间。毕竟,改变一个人的思维习惯并不容易。

元编程的进化

元编程(meta programming)的代码,说白了就是产生代码的代码,在运行的时候产生或者修改代码(执行逻辑)。我想经过前面的介绍,你到这里应该轻车熟路了。具备元编程能力的语言被称为是 “自省的”。在静态语言中,比如 Java 当中,它就是通过 “反射” 来实现的——啰嗦而且受限(例如只能调用某个类的方法,但是却不能修改该方法本身的逻辑)。

元编程在动态语言的发展过程中,有了一个化身——对象原型(prototype)。在对象原型介绍以前,有必要先了解一下 “元类”(meta class)的概念。

既然元编程的代码就是代码的代码,那么元类就该是 “类的类”,元类正是对某一个类的 “描述”。在面向对象语言里面,我们经常看到这样的关系:

对象 –> 类 –> 元类

右边都是对左边的描述:类是对对象的描述,而元类(本身也可以是对象,但是是特殊的对象)则是对类的描述。想一想 Java 里面反射的实现,就需要用到对目标类的元类的使用(Class<T>),你可以从元类里面找到目标类所有的方法和属性,即对目标类的所有描述信息。

好,有了这个概念以后,回过头来看对象原型。对象原型是如此强大,以至于面向对象的语言可以没有类!

其实这件事情发生得很自然,既然对象是我最关心的工作的个体,通过元类我可以修改对类的定义,从而作用到对象上面,那么,为什么我还需要中间的 “类” 这一层呢?于是,上面的关联关系演进成了:

对象 –> 原型

想想 JavaScript,还有相对小众、但是我很喜欢的 Io 语言,就是这样设计的。这两者本身都是 “基于原型” 的语言,也就是说,从语言本身定义上是没有 “类” 这个概念了,但是,没有类依然具备完善的对象系统,我们依然可以写出优秀的面向对象程序,而且,原型本身,也是对象。为了帮助那些写惯了类定义的程序员,我们可以 “模拟” 出一套类定义的代码来,作为对一类对象的规约和抽象。

既然它也来自元编程这个老祖宗,那么怎么理解元编程在 “对象原型” 上面的体现呢?在 Groovy 当中可以通过操纵类的 “metaClass” 属性来给这个类的所有对象增添方法;在 JavaScript 当中,则是利用 “prototype” 这个特殊的属性(我就不举例子了,不清楚的话你可以阅读以下 《JavaScript 实现继承的几种方式》,里面有利用原型链来实现的例子,相信你就豁然开朗了)。

最后我要说明的是,对于一些静态语言,虽然没法使用 “对象原型” 这样的好工具来帮助程序员,但是也拐弯抹角地引入一些其他的 workaround 达到同样的效果。AOP(Aspect Oriented Programming)就是一个例子。

我想很多人都知道 AOP 的含义,并且也写过 AOP 的代码,但是我们可以更多地从上面的故事中去类比和理解 AOP 的行为。比如你要统计某个方法的调用次数,如果使用任何一种动态语言,那么很简单,找到原型中的这个方法,使用元编程的方式,动态地在这个方法的前部增加统计方法调用次数的语句。但是如果是静态语言,那事情就变得麻烦一些,因为方法本身是不能够随便改变的(前面已经说了,静态语言中对类本身的修改是 “受限” 的),所以常见的办法包括静态织入和动态代理这两种。这两种方法严格说来都没有真正改变某个方法的行为,但是从实现的效果看起来,确是做到了 “仿佛改变了某个类方法调用的逻辑” 了。这便是元编程在静态语言施拳脚的一个体现。AOP 在网上的原理介绍和例子有很多,在这里就不展开了。

编程范型不仅仅是一个程序员手中工具划分的维度,它是一个非常值得思考和实践的魔杖,强大到足以扩展和改变一个人的思维方式。如果以后能有时间和精力,并且最重要的是要的是如果能具备足够的能力,我还想按照时间的顺序,理一理编程范型演进的过程。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》

×Scan to share with WeChat

你可能也喜欢看:

  1. 从 Java 和 JavaScript 来学习 Haskell 和 Groovy(元编程)
  2. 从 Java 和 JavaScript 来学习 Haskell 和 Groovy(DSL)
  3. 从 Java 和 JavaScript 来学习 Haskell 和 Groovy(汇总)
  4. 从 Java 和 JavaScript 来学习 Haskell 和 Groovy(类型系统)
  5. JavaScript 实现继承的几种方式

2 thoughts on “编程范型:工具的选择”

  1. Anonymous says:
    04/18/2018 at 2:01 PM

    nice+1 有深度的文章

    Reply
  2. ymtotti says:
    03/20/2014 at 5:27 PM

    nice!!!感觉眼界和思维开阔了不少!!

    Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

订阅·联系

四火,啰嗦的程序员一枚,现居西雅图

Amazon Google Groovy Hadoop Haskell Java JavaScript LeetCode Oracle Spark 互联网 亚马逊 前端 华为 历史 同步 团队 图解笔记 基础设施 工作 工作流 工具 工程师 应用系统 异步 微博 思考 技术 数据库 曼联 测试 生活 眼界 程序员 管理 系统设计 缓存 编程范型 美股 英语 西雅图 设计 问题 面向对象 面试

分类

  • Algorithm and Data Structure (30)
  • Concurrency and Asynchronization (6)
  • System Architecture and Design (43)
  • Distributed System (18)
  • Tools Frameworks and Libs (13)
  • Storage and Data Access (8)
  • Front-end Development (33)
  • Programming Languages and Paradigms (55)
  • Testing and Quality Assurance (4)
  • Network and Communication (6)
  • Authentication and Authorization (6)
  • Automation and Operation Excellence (13)
  • Machine Learning and Artificial Intelligence (6)
  • Product Design (7)
  • Hiring and Interviews (14)
  • Project and Team Management (14)
  • Engineering Culture (17)
  • Critical Thinking (25)
  • Career Growth (57)
  • Life Experience and Thoughts (45)

推荐文章

  • 聊一聊分布式系统中的时间
  • 谈谈分布式锁
  • 常见分布式系统设计图解(汇总)
  • 系统设计中的快速估算技巧
  • 从链表存在环的问题说起
  • 技术面试中,什么样的问题才是好问题?
  • 从物理时钟到逻辑时钟
  • 近期面试观摩的一些思考
  • RSA 背后的算法
  • 谈谈 Ops(汇总 + 最终篇):工具和实践
  • 不要让业务牵着鼻子走
  • 倔强的程序员
  • 谈谈微信的信息流
  • 评审的艺术——谈谈现实中的代码评审
  • Blog 安全问题小记
  • 求第 K 个数的问题
  • 一些前端框架的比较(下)——Ember.js 和 React
  • 一些前端框架的比较(上)——GWT、AngularJS 和 Backbone.js
  • 工作流系统的设计
  • Spark 的性能调优
  • “残酷” 的事实
  • 七年工作,几个故事
  • 从 Java 和 JavaScript 来学习 Haskell 和 Groovy(汇总)
  • 一道随机数题目的求解
  • 层次
  • Dynamo 的实现技术和去中心化
  • 也谈谈全栈工程师
  • 多重继承的演变
  • 编程范型:工具的选择
  • GWT 初体验
  • java.util.concurrent 并发包诸类概览
  • 从 DCL 的对象安全发布谈起
  • 不同团队的困惑
  • 不适合 Hadoop 解决的问题
  • 留心那些潜在的系统设计问题
  • 再谈大楼扔鸡蛋的问题
  • 几种华丽无比的开发方式
  • 我眼中的工程师文化
  • 观点的碰撞
  • 谈谈盗版软件问题
  • 对几个软件开发传统观点的质疑和反驳
  • MVC 框架的映射和解耦
  • 编程的未来
  • DAO 的演进
  • 致那些自嘲码农的苦逼程序员
  • Java 多线程发展简史
  • 珍爱生命,远离微博
  • 网站性能优化的三重境界
  • OSCache 框架源码解析
  • “ 你不适合做程序员”
  • 画圆画方的故事

近期评论

  • Ticket: TRANSACTION 1.922915 BTC. Go to withdrawal >> https://yandex.com/poll/enter/BXidu5Ewa8hnAFoFznqSi9?hs=20bd550f65c6e03103876b28cabc4da6& on 倔强的程序员
  • panshenlian.com on 初涉 ML Workflow 系统:Kubeflow Pipelines、Flyte 和 Metaflow
  • panzhixiang on 关于近期求职的近况和思考
  • Anonymous on 闲聊投资:亲自体验和护城河
  • 四火 on 关于近期求职的近况和思考
  • YC on 关于近期求职的近况和思考
  • mafulong on 常见分布式基础设施系统设计图解(四):分布式工作流系统
  • 四火 on 常见分布式基础设施系统设计图解(八):分布式键值存储系统
  • Anonymous on 我裸辞了
  • https://umlcn.com on 资源链接
© 2025 四火的唠叨 | Powered by Minimalist Blog WordPress Theme