速来报名!AICon北京站鸿蒙专场~ 了解详情
写点什么

阅读者:Ruby 的白魔法书

  • 2013-07-08
  • 本文字数:4080 字

    阅读完需:约 13 分钟

在 Ruby 的世界中,程序员们享受着各种光怪陆离的语法糖,也经历着各种各样的陷阱。而这一切的根本就在于 Ruby 强大的元编程能力。元编程就像 Ruby 世界的魔法,当其是白魔法的时候可以帮助你把程序变得异常简洁,美观;而当其是黑魔法的时候,你将会迷失在一些很难解释的 Bug 中。

《Ruby 元编程》就是一部告诉大家如何使用,控制 Ruby 元编程魔法的秘籍。该书的写作手法非常值得称道,作者把所有的知识点浓缩在了一个星期的工作过程中,通过一个菜鸟和大牛针对项目中遇到的各种问题的讨论,解决来引入各种元编程的知识点。 除此之外,在每个知识点的结尾处都还附带了有趣的小测验, 让读者可以跟随着菜鸟的思路,感受到自己在一步一步的掌握元编程的思想。这一切的编排让这本书读起来非常的有趣,并且书中的理论知识与项目中的实战相结合的讲述方式,让读者更容易去思考如何在自己的项目中运用这些知识。

我是从同事的口中听说这本书的,他读完这本书之后说:“这本书基本上改变了其写代码的习惯。”,作为一个码龄超过 10 年的程序员。如此赞誉一本书,让我决心一定要读一下这本书,读完之后,此书果然不负此赞誉。不管是初级程序员,还是编程高手,都应该读一下这本书,如果你是 Ruby 程序员,那么这本书可以算是必读书之一。该书分为 2 个部分。第一部分从对象模型,方法,代码块,类定义等方面一一剖析 Ruby 的设计原理,然后再通过实例告诉大家如何在实际应用中有效的利用这些设计原理,同时作者还非常善良的提醒了大家在使用这些技巧时的注意事项,防止这些魔法变成黑魔法。第二部分是剖析 Rails 中使用到的各种元编程技巧,读过之后,对理解 Rails 底层实现裨益良多,当然, 对 Rails 无爱的读者可以直接略过。

对象模型

提到对象,程序员首先想到的就是类这个概念,在本书第一章中,作者首先对 Ruby 世界的类进行了一番基础的讲解:

  • 不同于 JAVA 等静态语言,类定义中只能执行定义变量和方法的语句,在 Ruby 中,类定义的代码和其他的代码是一样的,可以在其中执行任何的 Ruby 语句。
  • Ruby 天生具有打开一个已经存在的类,并动态修改其内容的能力,但需注意猴子补丁的问题。
  • 类的实例变量是存储在对象中,实例变量与该对象的类没有关系,当给对象的实例变量赋值时,该实例变量就生成了,实例变量就像是一个挂载在对象上的 HashMap,每个对象都可以拥有自己不同的 HashMap。
  • 方法的定义在对象自身的类中,因为“共享同一个类的对象也必须共享同样的方法”。但是,不能说 Class 有一个叫做“method”的方法,因为无法使用"Class.method"调用该方法,而要说 Class 有一个实例方法“method”,这意味着必须创建该类的实例对象,通过实例对象调用该方法。
  • Ruby 中同样可以定义类方法,或者说类宏,定义方法时,在方法名前加“self.”或者“类名.”前缀即可, 然后可以在类中像使用关键字一样使用该方法,依靠类宏,可以实现很多非常简洁的 DSL。
  • 类本身也是对象,所有实例对象上的规则,同样可以适用于类对象本身。
  • 类的继承体系:

在第四章:类定义中, 作者引入了更多关于 Ruby 对象模型的高级概念:当前类,单件方法,EigenClass 等:

  • 不管代码执行到哪个位置,都会有一个当前对象 self,相对应的,也总会有一个当前类的存在。当定义一个方法时,该方法就会成为当前类的一个实例方法。跟踪当前类在 Ruby 中也并不困难,当使用 class 或 module 关键字打开一个类的时候,当前类就是被打开的那个类,在类定义时,当前对象 self 和当前类都是类对象本身,在调用方法时,当前对象 self 是调用方法的实例对象,当前类是该实例对象的类。
  • Ruby 中,可以针对某个实例对象添加方法,这样,该扩展就不会对该类的其他实例对象产生影响,这种只针对单个对象生效的方法称之为’单件方法‘(singleton method)。
  • 每一个对象都有一个特有的隐藏类 EigenClass,EigenClass 是一个很特殊的类,它只能有一个实例,且不能被继承,但是其自身可以继承其它类. 只对某个对象生效的方法就是保存在这个对象的 EigenClass 中,像实例对象的单件方法和类对象的类宏。
  • 引入了 EigenClass 之后的 Ruby 对象模型继承体系:

最后,作者非常简练的总结了关于 Ruby 对象模型的知识点,这些初看起来非常复杂的概念,当你深入进去之后,就会发现,复杂性慢慢褪去。一切都变得简单,清晰起来,如果把 Eigenclass、类和模块归结为一个东西的话(因为它们本质上的概念差不多,姑且统称为模块),Ruby 的对象模型可以总结为一下几条规则:

  • 关于对象,只有 2 种对象,要么是实例对象,要么是模块对象,用于存放实例变量的绑定。
  • 关于模块,它可以是 Eigenclass,类,或模块。用于存放方法和一些类实例变量。
  • 关于方法,方法必须存在于一种模块中。
  • 每个对象(包括模块对象)都有自己的 Eigenclass,用于保存当前对象的单件方法(类对象的就是类宏)。
  • 除了 BasicObjec 类无超类以外,所有的模块对象都有且只有一个父类,即从任何模块对象只有一条向上直到 BasicObject 的祖先链。
  • 一个实例对象的 Eigenclass 的父类是该实例对象的类,一个模块对象的 eigenclass 的超类是该模块对象的超类的 eigenclass。
  • 在类对象中插入一个模块时,该模块会出现在该类的祖先链的正上方。
  • 调用方法时,Ruby 总是先向“右”迈一步进入接收者真正的类中,然后向上进入祖先链。

代码块的迷思

对于 OOP 出身的程序员来说,关于 Ruby 对象模型的介绍比较容易理解,接受。而代码块则是来自于函数式编程的世界。因此,阅读本章时,OOP 程序员需要清空自己的固有思维来接收新的概念和思维方式。代码块极大的增强了 Ruby 代码的表现力。在本章中,作者先介绍了块的基础知识,如何定义,使用代码块,然后进一步介绍了 Ruby 世界中的所有可调用对象。同时,在该章节中还讲解了作用域的基本概念,以及如何使用代码块技术控制作用域的知识。

  • 定义一个代码块的方式有 2 种 ,一是使用 do … end, 另外一种是用大括号“{}”把代码内容括起来。代码块定义时也是可以接受参数的。但是,只有在调用一个方法的时候才可以定义一个块。
  • 块定义好之后,会直接传递给调用的方法,在该方法中,使用“yield”关键字即可回调这个块。
  • 如果一个方法定义的时候使用了 yield 关键字,但是调用的时候却没有传递代码块,方法会抛出“no block given (yield) (LocalJumpError)”异常。
  • 代码在运行的时候,除了需要代码外,还需要运行环境,即一组各种变量的绑定。代码块就是由代码和一组绑定组成的,代码块可以获得自己定义的局部变量的绑定,和上下文可见的实例变量,在代码块执行的时候,只会根据自己定义时可见的绑定来执行。业界把块这样的特性称之为闭包(Closure)。
  • 代码运行时,需要一组绑定, 这组绑定在代码的运行过程中,还会发生变化,这种变化发生的根本原因就是作用域发生改变,每个变量绑定都有自己的作用域,一但代码切换作用域,旧的绑定就会被一批新的绑定取代。
  • uby 程序只会在 3 个地方关闭前一个作用域,同时打开一个新的作用域, 这三个地方通常称之为作用域们(Scope Gate):
    • 类定义: class…end;
    • 模块定义: module…end;
    • 方法定义: def…end;
  • 代码块可以转化为可调用对象, 这样就可以把代码块当做对象处理。
  • Ruby 中有 4 种创建可调用对象的方法:
    • proc{…}
    • Proc.new { …}
    • lambda{…}
    • & 操作符。该操作符只有在方法调用时才有效,在方法定义时,可以给方法添加一个特殊的参数,该参数必须为参数列表中的最后一个,且以 & 符号开头,其含义就是,这是一个 Proc 对象,我想把它当做一个块来用,如果调用该函数时,没有传递代码块,那么该参数值将为 nil。
  • 可调用对象在 Ruby 中都是 Proc 对象,但是 lambda 和 proc 创建的 Proc 对象还是有些细微差别。主要体现在 2 个方面:
  • return 关键字的行为,lambda 中,return 仅表示从 lambda 中返回, 而 proc 中,则是从定义 proc 的作用域中返回。
  • 参数校验规则:lambda 中,参数个数不对,会抛 ArgumentError 错误,而 proc 中,则会尝试调整参数为自己期望的形式,参数多,则忽略多余的,参数少则自动补 nil。

法术集

本书在每个章节的知识点讲解过程中,还包含了很多实战的技巧,这些小技巧有的可以帮助程序员快速定位问题,比方说,使用 Object#instance_eval(), 查看一个对象的内部行为,一些可以帮助开发者优雅实现一些的功能,比方说通过类宏和动态定义方法实现的 attr_accessor。

  • 动态调用方法,通过使用 Object#send() 方法,可以直到最后一刻才决定到底运行哪个方法。
  • 动态定义方法,通过使用 Module#define_method() 方法可以传入一个方法名和一个代码块动态定义一个方法。
  • Kernal#method_missing() 方法,通过该方法的特性和动态调用方法结合,可以优雅的实现程序调用的动态代理。
  • 扁平化作用域,通过使用 Class.new() 代替 class 关键字,Module.new() 代替 module 关键字,Module#define_method() 代替 def 关键字,这样所有的定义都在一个作用域,共享了该作用域的所有绑定。
  • 上下文探针,通过使用 Object#instance_eval() 和 Object#instance_exec() 方法,可以轻松查看实例对象的内部状态。
  • 环绕别名,通过 alias 关键字从一个新定义的方法中调用原始的,被重命名的版本。该法术可以很容易的扩展一个已存在的方法。
  • 代码字符串,通过使用 Kernal#eval() 方法,可以把字符串自己当作代码执行。
  • 钩子方法,Ruby 中提供了很多监控对象模型变化的钩子方法,比方说 Class#inherited(), 当类被继承时会调用该方法,还有 Module#method_added,Method#method_removed 等等。这个技术给人很多想象空间。

更多的法术,等待着读者到书中去找寻。

结语

“其实世界上根本就没有什么元编程,有的只是编程而已”,作者在第 6 章中的点睛之句,升华了这本书的主题。所谓编程就是通过代码去解决实际的问题,作为程序员,我们总是尽力去寻找最精巧、最舒服的解决问题的方式。而元编程所展示的所有技巧,手法就正好为我们提供了这样的方式。


感谢张逸对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2013-07-08 10:446187

评论

发布
暂无评论
发现更多内容

耗时1年的前端技术框架切换之旅

华为云开发者联盟

大前端 软件工程 React 框架

SICP 习题2.6之丘奇数

程序员历小冰

函数式编程 SICP 5月日更

聊聊 Zabbix 和 Prometheus(一)

耳东@Erdong

Prometheus zabbix 5月日更

Golang Chaos Engineering

escray

学习 极客时间 Go 语言 5月日更

【LeetCode】解码异或后的排列Java题解

Albert

算法 LeetCode 5月日更

百度大脑UNIT拍了拍你,提醒你报名智能对话训练营

百度大脑

物联网 智能对话

如何更改 Notion 中的字体?

彭宏豪95

效率 Notion 工具 5月日更

技术干货|7个 React 性能提升技巧

拍乐云Pano

React

Python OOP-5

若尘

oop Python编程 5月日更

工商银行分布式服务 C10K 场景解决方案

阿里巴巴云原生

容器 微服务 云原生 监控 应用服务中间件

五一假期回乡,跟大家聊聊感触

架构精进之路

心情 杂记 5月日更

HarmonyOS开发者创新大赛预选赛结束,23支队伍脱颖而出闯入决赛

Geek_283163

鸿蒙

技术实践丨如何解决异步接口请求快慢不均导致的数据错误问题?

华为云开发者联盟

JavaScript typescript 大前端 浏览器 angular

jmeter命令行执行测试并对测试报表进行初步分析

行者AI

测试 Jmeter

使用docker compose快速部署前后端分离项目案例

皮特王

nginx Docker Vue Docker-compose

恍然大悟丨Java 中 RMI 的使用

Java架构师迁哥

腾讯云大神亲码“redis深度笔记”,从基础到源码,应有尽有

Java 编程 程序员 架构

大数据实战:网站流量日志数据分析

大数据技术指南

大数据 5月日更

自动发布.NET Core Web应用

吴脑的键客

DevOps 运维自动化 jenkins dotnet

只会重装系统的运维,不是好运维

运维研习社

Linux 5月日更 系统修复

开发者必看,面试官心中的最佳数据库人才模型是什么样?

华为云开发者联盟

面试 开发者 华为云数据库 数据库人才

这个英语学习APP更适合小白

小天同学

学习 英语 5月日更 APP推荐

阿里云入选 2021 Gartner APM 魔力象限,国内唯一入选云厂商

阿里巴巴云原生

阿里云 容器 微服务 运维 云原生

华为云PB级数据库GaussDB(for Redis)揭秘第九期:与HBase的对比

华为云开发者联盟

HBase 华为云 开源数据库 NoSQL数据库 数据库GaussDB(for Redis)

打破思维定式(六)

Changing Lin

5月日更

精选Hive高频面试题11道,附答案详细解析

五分钟学大数据

大数据 hive 5月日更

挖矿从入门到放弃:Chia

程序员架构进阶

数字货币 28天写作 Chia奇亚挖矿 5月日更

你到底想做什么

ES_her0

5月日更

那些激情燃烧的NFT们

zhoo299

区块链 加密货币 艺术 5月日更 NFT

一个朋友学会Java泛型后直接薪资翻倍!

北游学Java

Java 泛型

架构实战营-详细架构设计文档

Simon

架构实战营

阅读者:Ruby的白魔法书_Ruby_刘先宁_InfoQ精选文章