惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Help Net Security
Scott Helme
Scott Helme
爱范儿
爱范儿
WordPress大学
WordPress大学
博客园 - 三生石上(FineUI控件)
阮一峰的网络日志
阮一峰的网络日志
博客园 - Franky
V
V2EX
腾讯CDC
博客园_首页
博客园 - 司徒正美
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Tailwind CSS Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
小众软件
小众软件
J
Java Code Geeks
大猫的无限游戏
大猫的无限游戏
月光博客
月光博客
Microsoft Azure Blog
Microsoft Azure Blog
B
Blog
雷峰网
雷峰网
Stack Overflow Blog
Stack Overflow Blog
IT之家
IT之家
罗磊的独立博客
Recorded Future
Recorded Future
博客园 - 聂微东
O
OpenAI News
S
Secure Thoughts
Hacker News: Ask HN
Hacker News: Ask HN
S
Schneier on Security
Hacker News - Newest:
Hacker News - Newest: "LLM"
Y
Y Combinator Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
Project Zero
Project Zero
宝玉的分享
宝玉的分享
K
Kaspersky official blog
N
Netflix TechBlog - Medium
T
The Exploit Database - CXSecurity.com
Google Online Security Blog
Google Online Security Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Webroot Blog
Webroot Blog
云风的 BLOG
云风的 BLOG
Simon Willison's Weblog
Simon Willison's Weblog
C
Check Point Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
L
LINUX DO - 热门话题
美团技术团队
L
Lohrmann on Cybersecurity

博客园 - Allen Lee

用R处理一组数据的三种方式 遇见C++ AMP:GPU的线程模型和内存模型 遇见C++ AMP:在GPU上做并行计算 遇见C++ PPL:C++ 的并行和异步 遇见C++ Lambda WP7有约(八):在ListPicker控件的选择页面上播放铃声 WP7有约(七):实现铃声设置的播放图标的效果 WP7有约(六):AppBarUtils使用指南 WP7有约:一个应用的破蛋过程 WP7有约(五):回到主页 WP7有约(四):课程全景 WP7有约(三):课堂重点 WP7有约(二):课后作业 WP7有约(一):课程安排 Ruby 101:行为驱动 Ruby 101:方法对象 Ruby 101:对象和方法 Ruby 101:重用、隐藏和多态 - Allen Lee Ruby 101:类和对象
Ruby 101:动态编程
Allen Lee · 2009-12-14 · via 博客园 - Allen Lee

Ruby 101:动态编程

Written by Allen Lee

当method_missing的魔法失效时……

      在上一篇文章里,我们通过重写Hash类的method_missing方法把Hash对象模拟成匿名对象,但是,这种做法有时会产生一些莫名其妙的问题,举个例子吧,假如我把process方法(完整实现参见上一篇文章的代码31)的options参数从这样:

代码 1

改成这样:

代码 2

我们将会发现,不论options参数的count取什么值,我们总是得到两本书,为什么?想想看,method_missing方法的触发条件是什么?仅当我们调用的方法不存在时,method_missing方法才有机会出场,但是,Hash类本身就有count方法:

图 1

这打破了method_missing方法的触发条件,换句话说,method_missing方法被count方法"截胡"(麻将术语)了。Hash类的count方法返回键/值对的个数,而options参数默认就有两个键/值对,如果我们没有添加额外的键/值对,count方法的返回值将会总是2,这就是为什么修改options参数之后我们总是得到两本书。那么,如何解决这个问题?

      显然,method_missing方法和count方法无法同时存在,否则转发消息的逻辑总是被忽略的,但我们不能移除count方法,因为这样会导致依赖它的代码不能正常工作,在这种情况下,我们只好把转发消息的逻辑移至别处了,那么,放哪呢?还记得《Ruby 101:对象和方法》最后那个BookStore类吗?我们可以仿效它的做法,为Hash类创建一个代理类,然后把转发消息的逻辑放到代理类的method_missing方法里:

代码 3

这样就不怕count方法的干扰了:

图 2

当然,如果你想要的只是这些,那就没有必要另起炉灶了,因为使用Ruby自带的OpenStruct类也可以达到相同的效果:

图 3

但是,OpenStruct类不支持嵌套的Hash对象,你只能通过person1.address[:city]来访问下面这个对象的城市信息:

代码 4

如果你希望通过person1.address.city来访问这个对象的城市信息,要么显式地把内嵌的Hash对象创建为OpenStruct对象:

代码 5

要么扩展AnonymousObject类,使它支持嵌套的Hash对象,如果你有兴趣的话,不妨把握这个机会练习一下吧!

消息代理

      既然我们可以通过代理类截获并转发消息,何不在此基础上加点想象力?假设我有这样一个类:

代码 6

我想在调用method1方法之前做一些事情,在调用method2方法之后做另一些事情,忽略method3方法的调用,把method4方法的调用转到method3方法上,你有什么建议?我可以直接在代码里表达这些需求吗?如果可以,我希望像下面这样表达:

代码 7

当我通过代理类依次调用Class1类的4个方法时,我期望这样的输出结果:

代码 8

现在的问题是,我真的可以这样吗?不知道呢,试一下吧,看看能够走到什么程度。

      首先,我们需要一个Proxy类,它提供相关的方法收集并保存我们的"需求":

代码 9

接着,我们需要重写method_missing方法:

代码 10

处理消息的逻辑并不复杂,除非@ignore包含这个方法的名字,否则将会依次进行前期处理、消息转发和后期处理。如果我们就此撒手不干,那么Proxy类就只能这样用了:

代码 11

这显然不是我想要的。仔细观察代码7,不难发现,proxy是一个方法,而object1对象则是它的参数,proxy方法接受一个代码块,用来配置proxy方法创建的代理对象,要创建这样的方法并不难:

代码 12

下面,我们来看看如何使用这个方法:

代码 13

嗯,我们离目标非常近了,但是,before等方法前面那个"p."可以去掉吗?想想看,调用对象的方法实质上就是向对象发送消息,如果去掉before等方法前面那个"p.",那么这些消息将会发往默认对象,而此时的默认对象是main,一来没有before等方法,二来亦非接收这些消息的正确对象,我们期望接收这些消息的对象是由proxy方法创建的代理对象,如果有办法把代码块执行时的默认对象改为代理对象,代码7就可以实现了,那么,能否改变代码块执行时的默认对象?当然可以,现在正是instance_eval方法大展拳脚的时候:

代码 14

然而,改变代码块执行时的默认对象有时会产生一些莫名其妙的问题,比如下面这个代理工厂:

代码 15

猜猜看,在调用method1方法和method2方法之前将会分别输出什么?由于代码块执行时的默认对象是代理对象,既没有@msg_before_meth1实例变量,又没有msg_before_meth2方法,于是,在调用method1方法之前会输出一个空行,这是puts nil的行为,而在调用method2方法之前会抛出异常,告知找不到msg_before_meth2方法。怎么办?解决方法其实很简单,由于代码块可以访问create_proxy方法的本地变量,我们可以把@msg_before_meth1实例变量和msg_before_meth2方法的值绑定到本地变量,然后在代码块里使用这些本地变量就行了:

代码 16

现在,我们来看看输出结果:

图 4

非常好!接下来,我们看看还有什么需要完善的。

      首先是before方法,目前,它只允许一个目标方法对应一个代码块,试想一下,如果我想在调用某个方法之前分别进行安全检查和资源调配,我就必须把它们混到一个代码块里,这显然不是一个好主意,我希望before方法支持一个目标方法对应多个代码块,这将有助于合理分割代码逻辑。此外,我还希望before方法允许这些代码块访问目标对象,当然,代码块有权选择是否访问。为此,我需要把before方法以及method_missing方法的相关部分修改如下:

代码 17

代码 18

需要说明的是,@before[m] ||= []相当于@before[m] || @before[m] = [],在这里的效果相当于@before[m] = [] if not @before[m]或者@before[m] = [] unless @before[m]。下面,我们来看看运行结果:

图 5

非常好!至于after方法,依样画葫芦就可以了。

      接着轮到ignore方法,目前,它只接受一个参数,这意味着我一次只能忽略一条消息,如果我要忽略多条消息,就不得不重复调用ignore方法了,如果我可以像下面这样忽略多条消息该多好啊:

代码 19

可以吗?当然可以,我们可以使ignore方法接受可变参数:

代码 20

为了避免出现重项,我使用uniq!方法把@ignore处理了一下。值得注意的是,如果你使用的是uniq方法而不是uniq!方法,@ignore将不会受到任何影响,uniq方法在不影响原来的集合的情况下返回一个新的集合,而uniq!则就地修改原来的集合。

      最后是forward方法,它的情况和ignore方法类似,目前,它只接受两个参数,这意味着我一次只能转发一条消息,如果我要转发多条消息,就不得不重复调用forward方法了,如果我可以像下面这样转发多条消息该多好啊:

代码 21

能行吗?想想看,:method4 => :method3是什么?有些同学已经反应过来了,是Hash对象,当它是最后一个参数时,Ruby允许我们把{}去掉,于是看起来就像一组消息映射关系。由于这些映射关系本来就是保存在Hash对象里的,我们只需修改forward方法,把参数指定的映射关系添加到@forward就行了:

代码 22

      有意思的是,我们还可以利用这些方法之间的微妙关系,描绘一些实用的处理逻辑,比如下面这个:

代码 23

首先,我屏蔽外界直接调用method4方法,接着,我把method3方法和method6方法的调用转到method4方法上,并设置在调用它们之前分别进行不同的资源配置,其中,object1是代码7里创建的那个目标对象。这个处理逻辑可能来源于这样的业务需求,系统原本通过method3方法来执行某些任务,由于业务的发展,method3方法的实现出现了僵化,于是催生了method4方法,我希望保留method3方法的请求途径,但背后通过method4方法来执行相应任务,同时增加一个"虚拟"的请求途径(之所以说"虚拟"是因为目标对象并不包含method6方法的定义),这两个请求途径将会针对不同的资源配置展开。

      虽然代码21的proxy2是个代理对象,但我希望它用起来就像object1一样,由于Proxy类和Class1类都是Object类的子类,根据上一节的结论,如果我在proxy2上调用从Object类继承过来的方法,那么我将会得到的proxy2对象的信息而不是object1对象的,这显然不够透明,为了获得object1对象的信息,我将不得不在Proxy类里重写这些方法,但是……Object类的实例方法有52个啊!怎么办?如果你使用的版本是1.9或以上,那么你只需让Proxy类继承自BasicObject类就行了,那么,BasicObject类是何方神圣?我想下图应该能够说明问题了:

图 6

正如你所看到的,BasicObject类是Object类的基类,同时也是整个继承体系的根类。如果你一直关注这个系列,你应该不止一次碰到"这个说法其实不够准确,但就目前而言,你大可放心这样理解"这句话了,事实上,BasicObject类就是那些时候的例外。BasicObject类的实例方法比Object类的少很多,只保留了最基本的7个:

图 7

没有多余方法的烦扰,使得BasicObject类非常适合成为代理类的基类,但是,如果没有指明基类,默认将会是Object类。如果你使用的版本是1.8或以下,那么你需要的是Jim Weirich的BlankSlate,它的用法和BasicObject类似,但额外提供了一些有趣的功能,比如隐藏/重现某些方法,如果你有兴趣的话,不妨看看它是怎么做到的。

事件回调

      正如你所看到的,method_missing方法的力量非常强大,以至于我的多篇文章都对它有所涉及,事实上,每当我们需要实现动态接口(dynamic interface)时,method_missing方法和send方法都会无可避免地牵涉进来。回顾method_missing方法的整个作用过程,首先,我们定义这样一个方法,然后,当特定条件满足时,这个方法将被调用,想想看,这像什么?有些同学可能已经看出来了,这就像为特定事件创建回调方法,事实上,我们通常把method_missing方法称作钩子方法(hook method),类似的还有method_added方法、included方法、extended方法和inherited方法等等,如果我们实现了这些方法,当它们对应的事件发生时,Ruby将会调用它们。这种回调机制是内置的,并且由解析器负责执行的,那么,Ruby有否提供现成的机制帮助我们创建自定义的事件呢?很抱歉,Ruby没有正式的事件概念,但它为我们提供了基本材料——Proc对象和Method对象,下面,我们来看看如何实现自定义的事件。

      假如我有一个Button类,我想为它实现一个click事件,我希望它用起来就像……嗯……IronRuby的那样

代码 24

以上是IronRuby支持的三种常见的做法,从Ruby的角度来看,click显然是一个方法,它接受一个代码块,那么,当我们提供代码块时,它的返回值是什么,如果我们没有提供代码块,它会否抛出异常?为了回答这些问题,我们需要借助IronRuby的irb(这里使用的是IronRuby 1.0 RC1):

图 8

从上图可以看到,IronRuby的事件是一个RubyEvent对象,当我们没有提供代码块时,click方法将会返回RubyEvent对象,而当我们提供代码块时,这个代码块会被隐式转换成Proc对象,click方法则返回这个Proc对象。那么,如何实现这个RubyEvent类?当我们感到无从下手时,不妨想像一下完成之时的使用情景,根据图8的试验结果,我们可能会这样使用它:

代码 25

从上面代码可以看到,RubyEvent类至少包含两个实例方法——add方法和call方法,它们分别负责添加单个Proc对象和调用所有Proc对象,实现这样一个类一点都不难:

代码 26

值得注意的是,add方法在添加之前会先进行重复检查。现在,请思考一个问题,如何移除现有的Proc对象?还是让我们先看看IronRuby是如何反应的吧:

图 9

我们可以通过click方法获取RubyEvent对象,然后通过RubyEvent对象的remove方法移除现有的Proc对象,这个remove方法实现起来也不难:

代码 27

至此,我们实现了一个简单的RubyEvent类,并用它为Button类创建了一个简单的click事件,如果你想创建其它事件,那么只需把代码25的"click"替换成对应的事件名字就行了,但是,如果我们想创建N个事件呢,毫无疑问,我们需要重复这样的代码N次!我相信,这绝对不是一个好主意,至少不是一份好差事,解决之道?

      还记得我们是如何创建属性的吗?比如说,我们要为Button类创建heightwidth两个属性,如果手动创建的话,我们将无可避免要写很多代码:

代码 28

但我们通常不必写这么多代码,因为我们有attr_accessor方法:

代码 29

我们知道,attr_accessor方法最终会为我们生成代码28,所以二者的效果是完全一样的,那么,我们能否仿效attr_accessor方法,为RubyEvent类创建这样一个event方法呢:

代码 30

当然可以,现在正是class_eval方法大展拳脚的时候:

代码 31

为了支持长度可变的参数列表,我们使用*event_names来表示event方法的参数,event_names数组的每个元素都会嵌入代码模板,然后传给class_eval方法处理,在这里,你可以把class_eval方法理解成把我们传给它的字符串"嵌入"类的定义里,就像我们在类的定义里写下这些代码一样。接下来,我们要使EventHandling模块的event方法"变成"Button类的类方法,怎么变呢?想想看,如果event方法不是定义在EventHandling模块里,我们又会如何定义呢?我们可能会这样定义:

代码 32

现在,仔细观察一下代码32和代码31的两个event方法的签名,是否发现了什么?有些同学可能已经反应过来了,它们都是实例方法,要把一个模块的实例方法变成一个类的实例方法,我们只需在类里调用include方法就行了:

代码 33

非常好!事实上,我们刚才是把EventHandling模块包含到Button类的单例类里,我们知道,类都是Class类的实例,类方法要么是Class类的实例方法,要么是所属类的单例方法,而这些单例方法正是存放在所属类的单例类里,于是,要使一个模块的实例方法变成一个类的类方法,只需把这个模块包含到这个类的单例类里。然而,这并非实现我们的目标的唯一途径,我们还可以通过extend方法扩展某个对象,因为Button类也是对象,于是我们可以用EventHandling模块扩展它:

代码 34

本质上,这两种方法都是把EventHandling模块嵌入Button类的单例类,使前者的实例方法变成后者的单例方法,但效果上它们会有一个小小的差别,如果我们EventHandling模块里分别定义了included方法和extended方法,那么使用include方法会触发included方法,而使用extend方法则触发extended方法。

      现在,有了RubyEvent类和EventHandling模块,我们就可以轻松实现事件回调了:

图 10

不难预料,红框里的代码也将以相同的模式一再出现,不过,有了上面这些知识,处理这个问题已经不再是难事,那么,除了使用class_eval方法之外,还有没有别的解决方案呢?当然有啦!在Ruby里,类的定义并不限于通常意义的对象的模板,而是一组可以执行的代码,你可以在里面创建本地变量、调用方法,它甚至可以拥有返回值:

图 11

需要说明的是,类的定义的返回值不是类,而是最后一条表达式的值。从这个角度来看,它和方法没啥两样,换句话说,图10的Book类和下面这个应该是等效的:

代码 35

如果可以一般化price_notifier方法,使之根据参数执行这些代码,我们的目的就达到了,但是,我们遇到问题了:首先,我不希望每次调用方法时都创建property_changed事件,其次,我要动态获取/设置实例变量的值,最后,也是最难的,写访问器的名字如何动态修改?

      对于第一个问题,就目前而言,我们无法直接判断某个事件是否已被创建,但是,我们不妨换个角度来看,如果property_changed事件已被创建,Book类会有什么变化,比如说,多了一些什么?有些同学可能已经反应过来了,多了一个property_changed方法,很好,我们可以通过method_defined?方法判断property_changed方法是否已被创建,从而推断property_changed事件是否已被创建。虽然创建property_changed事件还会创建on_property_changed方法,但由于此方法是私有的,而method_defined?方法只对公有和受保护方法有效,所以我们不能借助判断on_property_changed方法是否已被创建来推断property_changed事件是否已被创建。当然,更好的做法是修改event方法,在创建事件的时候登记一下,然后提供event_defined?方法判断事件是否已被创建。对于第二个问题,我们可以通过instance_variable_get方法和instance_variable_set方法做到。至于第三个问题,显然,我们无法继续使用def … end定义方式了,一方面,它不支持动态指定方法名字,除非你回到代码31的做法,另一方面,即使方法名字是固定的,由于def … end会打开一个新的作用域,外面的变量将会无法穿透,致使我们无法向内传递变量的名字。这个时候,我们就要改用define_method方法了,它接受一个参数和一个代码块,参数用于指定目标方法的名字,代码块的参数将会成为目标方法的参数,而代码块的逻辑则成为目标方法的逻辑,由于闭包的作用,我们可以在代码块里使用外面的变量。有了这些知识,我们就可以着手实现attr_notifier方法了:

代码 36

毫无疑问,这个方法不应该被Book类独享,我们可以把它提取到一个模块里,并使它支持多个参数:

代码 37

值得注意的是,attr_notifier方法变成实例方法了,这和event方法一样,此外,为了使用event方法,我们需要把EventHandling模块包含进来,这样,我们只需使用NotifyPropertyChanged模块扩展Book类,Book类就能同时使用EventHandling模块和NotifyPropertyChanged模块的功能了。现在,有了NotifyPropertyChanged模块,创建带有变更通知的属性将会非常便捷:

代码 38

在创建attr_notifier方法的过程中,正如你所看到的,我们不知不觉地使用了反射,Ruby把反射的功能融入现有的对象模型而不是单独提供一套对象模型,这样便于我们随时随地创建更具动态的对象。而提到反射,第一个出现在我脑海里的就是插件系统,下一节,我们将会尝试使用Ruby实现一个简单的插件系统。

插件扩展

      以前开发插件系统总是首先定义介于宿主和插件之间的接口,现在稍稍有点不同,我会首先考虑插件是如何"描述"的,举个例子吧,假如我想为下面这个购物车提供导出插件:

代码 39

那么,我可能会这样"描述"其中一个插件:

代码 40

其中,meta部分提供插件的基本信息,包括插件的名字、作者、版本以及一些附加信息等,params部分定义插件所需的参数以及参数的默认值,最后,logic部分包含了插件的主体逻辑。插件的"描述"将会放在一个单独的代码文件里,比如说,我们可以把代码40保存到yaml_exp.rb文件,然后在某个地方统一登记这些文件:

代码 41

假设这些插件是通过AddInStore模块来管理的,那么我可能会这样使用YAML Exporter插件:

代码 42

上面这些好像天方夜谭,但是,运用我们前面学到的知识,你会发现这些效果只是小菜一碟,就像魔术的秘密被揭开之后,里面只有一些基本的东西,外加小小想象力。

      首先是插件的"描述",这种效果我们在前面实现代理对象时已经玩过了,无非就是把一个代码块传给addin方法,由addin方法负责创建一个插件对象,然后通过这个插件对象的instance_eval方法执行这个代码块,而代码块里的metaparamslogic三个部分也只不过是插件对象的三个实例方法,前两个方法的参数采用了Ruby 1.9引入的Hash对象的新写法,最后那个方法只是简单地保存代码块隐式转换成的Proc对象,以备后用。是不是觉得很简单呢?事实也是这么简单:

代码 43

需要说明的是,meta方法和params方法是"两用"方法,在"描述"插件时负责写入数据,在使用插件时负责读出数据,这两种情况对参数分别有着不同的要求,前者需要参数,后者则刚好相反,为了使得meta方法和params方法同时支持这两种不同的参数要求,我给参数设置了默认值——nil,在使用插件时,由于我们没有提供参数,参数将会维持默认值,即nil,此时,meta方法和params方法只需返回相关的数据就行了,否则,保存参数的数据,此外,为了把Hash对象模拟成匿名对象,在保存时我用OpenStruct对象包装了一下。

      接着,我们来看看AddInStore模块,

代码 44

目前,我只是简单地为它提供插件注册和搜索功能,稍后,我们将会为它添加更多枚举功能。

      最后,我们来看看addin方法和addins方法:

代码 45

从上面可以看到,addin方法在创建插件之后还会注册插件,而addins方法则负责加载插件的"描述",换句话说,当我们调用addins方法时,插件就可用了。

      至此,我们已经实现了一个简单的插件系统,我把代码43、代码44和代码45保存到addin.rb文件,把代码41保存到addins.rb文件,把代码40保存到yaml_exp.rb文件,把代码39和代码42保存到main.rb文件,并在开头添加require 'addin'require 'addins',把所有文件放在同一个文件夹里,然后运行main.rb文件……嗯,什么也没有,因为YAML Exporter的逻辑还是空的呢!我们把它补充完整吧:

代码 46

然后再次运行main.rb文件:

图 12

你可以在main.rb文件所在的文件夹里找到cart.yaml:

图 13

什么?没听过YAML?YAML是一个数据序列化标准,支持多种语言,官方提到的有C/C++、Java、Python、Ruby、Perl、C#、PHP、OCaml、Javascript、Actionscript和Haskell。此外,根据Wikipedia上面的说法,JSON语法是YAML 1.2的子集,而且多数JSON文档都可以被YAML解析器解析。

      现在,回到AddInStore模块,毫无疑问,只有一个find方法是远远不够的,我希望AddInStore模块支持Enumerable模块的其他方法,最简单的做法就是使用Enumerable模块扩展AddInStore模块,并为AddInStore模块提供一个each方法:

代码 47

这样,假如我想输出所有导出插件的名字,我可以这样:

代码 48

我们又回到熟悉的集合操作了,当然,AddInStore模块应该具备的功能绝对不止这些,如果你有兴趣的话,不妨试试扩展它。

新的旅程

      第一次接触动态语言时,第一个出现在我脑海里的问题是IDE如何提供智能感知?随着Visual Studio的日益完善,我无法想象没有智能感知的日子,它不但减少我的输入失误、提高我的编码效率,还弥补我日益模糊的记忆,基本上可以说,没有智能感知就会处处不便。情况开始有所改变是在后来学习F#的时候,那时F#还只是个研究项目,虽然也提供了Visual Studio的插件,但功能极其有限,加上大部分时间都是通过命令行使用F#的,一开始很不习惯,慢慢地,我发现没有智能感知的日子并没有想象中的那么糟糕,逐渐地,情况变成使用C#时依然很依赖智能感知,而换用F#时则极少依赖智能感知了,这段经历可以说是为我后来不依赖智能感知使用Ruby铺平了道路。当初选择NetBeans作为Ruby的IDE主要是因为它的智能感知做得比较好(个人感觉),但由于它的提示速度很多时候都不及我的脑子来得快,加上等待它的提示窗口出来常常阻塞我的思路,于是不得不放弃使用智能感知,随后的体会是,输入失误并没有想象中的那么多,出现了一些新的途径提高编码效率,还有的就是,我的记忆似乎变得比以前更清晰了,嗯,相比之前担心智能感知的不完善会妨碍我使用动态语言,现在此番感受真是不同啊,有时我在想,究竟智能感知是弥补我的记忆模糊还是促进我的记忆模糊呢?

      智能感知的问题绝对不是我接触动态语言时的唯一担心,然而,随着深入的了解,我发现很多担心都没有想象中的那么糟糕,有时我觉得,或许这些担心只是我用来维持现状的借口罢了。当然,正如我们都知道的,做出改变是有风险的,就其对于有限的生命来说,面对如何改变这个问题还是需要审慎而行,不过,正如李子勋在《心灵飞舞》里说的:

看不懂没有关系,知道它存在,你就会变得丰富多彩!

每个人都曾经历那个充满好奇、渴望知识的人生阶段,如果因为某些原因丢弃了这些宝贵的特质未免有点可惜,或许我所学的知识未能成就一番伟业,但却能拓宽我的思维视野、丰富我的人生体验,还可能成为别人进入某个领域的响导,就像why the lucky stiff当初把我领进Ruby的世界一样。

      下一次,我们将会看看如何处理使用Ruby进行开发的时候出现的错误。

P.S. 祝伟杰生日快乐~