写点什么

阅读者: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:446679

评论

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

ARTS 打卡第11周

steve_lee

网络安全逐渐成为程序员的必备技能

学神来啦

Linux 程序员 安全 渗透

并发王者课-黄金3:雨露均沾-不要让你的线程在竞争中被“饿死”

MetaThoughts

Java 多线程 并发 并发王者课

EMQ映云科技边缘计算里程碑—Kuiper加入LF Edge基金会

EMQ映云科技

开源 云端 边缘流式数据 emq LF Edge

击破行业痛点,区块链赋能智慧物流高速发展

旺链科技

区块链 智慧物流

【LeetCode】山脉数组的峰顶索引Java题解

Albert

算法 LeetCode 6月日更

敏捷绩效管理三剑客:OKR 、KPI、CFR

CODING DevOps

DevOps OKR 敏捷绩效 绩效管理

让宝妈宝爸告别安全顾虑,区块链构建母婴行业新生态

旺链科技

区块链 母婴

GIT基本概念与核心命令

IT视界

git 版本管理

ONES CTO 冯斌 | 大型软件研发团队如何实践高效项目管理?

万事ONES

团队管理 ONES Project 研发团队

Python——元组的使用

在即

6月日更

区块链 | 让付费的知识真正“物超所值”

旺链科技

区块链 知识付费

你应该知道的数仓安全

华为云开发者联盟

数据加密 数仓安全 透明加密 SQL函数加密

Go 语言学习路线来啦

roseduan

学习 Go 语言

政治局会议再提工业互联网产业数字化|区块链如何协同发力?

旺链科技

区块链 工业互联网

什么是Python中的套接字编程?

华为云开发者联盟

Python 编程 socket 网络 套接字

实时音视频开发理论必备:如何省流量?视频高度压缩背后的预测技术

JackJiang

音视频 即时通讯 IM 视频编解码

全新升级IoT Stack 2.0和度能2.0,百度持续加码为产业智能化安全护航

百度大脑

百度智能云

100个开箱即用的shell脚本,CV大法好,工作不费脑!

北游学Java

Java Shell

更好链接资金需求 | 区块链如何赋能“链”金融

旺链科技

金融

EasyRecovery用法进阶--高阶设置使用技巧

淋雨

数据恢复 EasyRecovery 文件恢复 硬盘恢复

【译】JavaScript 代码整洁之道-概述篇

KooFE

JavaScript 大前端 6月日更 整洁代码

重启心智解锁,重新获得一份能力精进指南,面对不确定性的未来,我们可以和世界的变化做好友。

叶小鍵

ONES CTO 冯斌 | 升级项目管理体系,加速金融行业数字化转型

万事ONES

项目管理 研发管理 数字经济 ONES

推荐学Java——应该了解的前端内容

逆锋起笔

Java 大前端 后端 JAVA开发

缓存的世界Redis(一)

卢卡多多

redis 缓存 6月日更

【融云视角】沉浸式音频与通讯技术未来趋势

融云 RongCloud

详解Vue八大生命周期钩子函数

华为云开发者联盟

Vue 对象 函数 Vue实例 八大生命周期

互联网就业系列文(一)

HZFEStudio

互联网 就业

文档代码同源

不脱发的程序猿

开发规范 文档代码同源

Flink Metric

Alex🐒

flink 翻译 flink1.13

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