2014年4月27日星期日

《Attached》——恋人矛盾的合理解释

最初发现《Attached》这本书,是在 Audible 上面。我在看了简介和评论后,就猜这是我想要的书,因为我觉得它解答了我一个长期以来的疑问。接着我下载了 Kindle sample,发现书中用依恋类型来解释恋爱中的矛盾非常 make sense,于是立即购买赶快看完。

这本书的开头先简单介绍了 3 种依恋类型:Secure(安全)、Anxious(焦虑)、Avoidant(回避)。这是我之前在看《Happiness Hypothesis》时就知道的,不过《Happiness Hypothesis》说成年人恋人之间的依恋类型由婴儿时期婴儿对母亲的依恋类型来决定,之后终身不变,但《Attached》说依恋类型是稳定但可塑的,这对我来说是一个很有意思的新观点。如果成年人的依恋类型必然跟他婴儿时期的依恋类型一致的话,我会觉得无法解释的反例实在是太多了。但如果说依恋类型稳定但可塑的话,那我就很有兴趣去了解如何可塑法。

Secure 的人容易接近恋人,对于依赖和被依赖感到舒服,不担心被抛弃也不担心被靠得太近。Anxious 的人觉得别人不愿意让他靠近到他想要的近距离,经常担心恋人是不是不爱自己或者不想跟自己在一起了。Avoidant 的人觉得跟别人距离太近会不舒服,难以完全信任别人也难以允许自己依赖别人。这样说可能大家不明白自己属于什么类型,看一下书中具体的例子就知道了。我觉得在通过例子理解以后,甚至不用做测试也能轻易判断类型。当然如果你不愿意去看这本书的话,可以用网上现成的测试。注意测试的结果不是直接划分到 3 种类型之一的,而是 anxiety 一个维度,avoidance 一个维度,然后组成一个二维坐标系。

现代西方的主流观点认为,恋人之间要独立一些才好,但作者认为这个观点跟美国上个世纪 20 年代主流母婴观点相似。当时的心理学观点认为,儿童成长过程中遇到的各种问题都是因为被母亲宠坏了而导致的,所以母亲必须不能宠爱孩子。所有事情都应该按规矩来做,该几点吃饭几点睡觉都是提前定好的,孩子如何哭闹都不能理更不能让步。当然后来这些观点被完全推翻,现在的主流观点认为要为婴儿建立 Secure 的依恋类型才是最好的。同理,作者认为现在恋人应该独立的观点也是错误的,如果一方需要获得安全感另一方就应该尽力提供,双方达到 Secure 的依恋类型才是最好的。

接下来这本书讲解了最常见的矛盾恋人组合——一方 Anxious 另一方 Avoidant。Anxious 的一方总会觉得距离太远,想要靠近;Avoidant 的一方一旦被靠近了就觉得不舒服,然后想要回避。接着 Anxious 的一方会由于不满足而做出过激行为,希望以此来吸引对方的注意力,但 Avoidant 则觉得这正好印证了自己的观点——靠得太近会出问题,同时 Anxious 也会因为得不到关注而觉得这正好印证了自己的疑虑——对方可能不够爱我。

这种矛盾会渗透到两个人生活细节当中去,使得两个人不停地为各种小事而吵架,同时双方还不知道其实吵架的根源是大家对距离的期望不一致。一个例子是这样的:一对恋人就要不要买洗衣机而吵架,他们列举了无数要买和不要买的理由,但他们都不知道各自立场的根源是什么。其实背景是这样子的,女方每个周末都会拿着一篮衣服到一个街区之外的姐姐家借用洗衣机洗衣服,从而在不用面对男方的情况下跟姐姐一起打发掉一个周末下午,这是典型的 Avoidant 类型。至于男方则是典型的 Anxious 类型,他不希望难得周末有时间在一起却总要有一个下午要分开度过,所以他希望通过购买洗衣机来解决这个问题。双方真正无法达成共识的地方是到底有多少时间共处,一方希望更多而另一方希望更少。

至于为什么 Anxious 和 Avoidant 搭配的恋人组合那么常见,书中给出了有趣的解释。首先 Secure 的人是兼容性最强的,他们会主动顾及对方的情感需求,所以无论搭配 Anxious 还是 Avoidant,Secure 的人都能让对方感到满足,同时自己也不会觉得不舒服。因此如果双方当中有一方是 Secure 的,估计这对组合就稳定下来了,双方都不会重新进入到待配对的市场当中。

如果是两个 Avoidant 尝试配对,因为双方都不想要靠得更近,结果自然不会选择在一起,因此很快就回归到市场当中。如果 Anxious 跟 Secure 配对,Anxious 习惯了 Avoidant 给他带来情感上大起大落的变化,反而会觉得 Secure 太过平淡没有感觉。但当一个 Anxious 和一个 Avoidant 配对时,Anxious 会不停地追赶,Avoidant 会不停地逃跑,但无论如何 Anxious 都不想放手,最终就会成为矛盾的悲剧。因此,尽管 Secure、Anxious、Avoidant 的人口比例是 50%、30%、20%,但我们总是能碰到 Anxious 和 Avoidant 的组合。

如果还是单身的话,作者建议 Anxious 和 Avoidant 尽量避免组成矛盾组合。如果已经成为恋人的话,Anxious 要么学会放手,要么尝试学习有效沟通,但后者只能缓解矛盾,不可能让 Anxious 的需求获得满足,Anxious 的一方必须接受这一折衷。如果 Anxious 或 Avoidant 有机会跟 Secure 恋爱的话,他们会被慢慢转化为 Secure 类型。因为 Secure 可以从一开始就满足到他们对距离的需求,反而能让他们变得有安全感,从而缓慢地完成转变。

最后这一点也被称之为 Dependency Paradox,意思是说当一个人相信如果他需要依赖时有人可以给他放心依赖的话,他反而不需要依赖那个人,从而能够放胆探索外面的世界。这其实非常 make sense,如果放到婴儿对母亲的依恋理论中用进化心理学的观点来看就尤其 make sense。

依恋类型的理论最初源自一个叫做 Strange Situation 的心理学实验,测试婴儿跟母亲分开后的反应,以及再团聚时的反应。实验场所是一个地上充满玩具的房间,母亲带着婴儿进入房间后跟实验助手交流,然后分别尝试母亲离开、助手离开、母亲助手都离开、母亲返回、助手返回、母亲助手都返回的情况。事实证明助手是无法对婴儿产生影响的,所以实验的关注点是母亲和婴儿的交互。母亲离开婴儿就会安全感缺失是停止玩耍,母亲返回婴儿就会因为获得安全感而继续玩耍。进化心理学对此的解释是说,安全和玩耍对于婴儿来说同等重要,缺乏安全意味着立即死亡,而缺乏玩耍意味着大脑发育减缓,长久来说缓慢失去竞争优势等同于死亡。因此进化结果使得人类婴儿同时要坚固两者,所以在缺乏安全感的情况下必须要去找母亲,或者通过哭闹来引起母亲注意。

当然上述所说的只是 Secure 类型的婴儿。Anxious 的婴儿会在母亲返回后继续哭闹,甚至做出一些过激反应来惩罚母亲。Avoidant 的婴儿则会在母亲离开后抑制着自己不哭不闹,但各项生理指标会现实 Avoidant 的婴儿其实跟其它婴儿一样,在母亲离开后变得尤其恐慌。如果我们把这 3 种类型的婴儿行为再投射回到成年恋人当中去,你就会发现这其实很 make sense。跟恋人分离后,Anxious 会比 Secure 更敏感,同时在团聚后更有可能做出激烈反应。Avoidant 本质上也类似,但他们会建立其一套机制假装自己不在乎,久而久之他们自己也就相信了这套机制,认为自己确实不在乎。

就如同进化心理学认为婴儿在安全感需求获得满足后会去玩耍一样,成年人在安全感获得满足后自然也会去探索外面的世界,这就是 Dependency Paradox。因此,应对非 Secure 恋人的最有效方法就是尽力保证对方的安全感需求得到满足,对于 Anxious 恋人来说尤其如此。不要因为主流观点认为恋人之间应该互相独立就无视对方的情感需求,照顾好对方的情感需求才能避免矛盾的发生。如果双方都不了解对方的情感需求,那就需要通过有效沟通来解决。

Anxious 通常不懂得有效沟通,而更倾向于玩游戏,然后就是过激反应。Avoidant 通常也不会有效沟通,导致 Anxious 的对方觉得 Avoidant 的回避是针对自己的。其实双方都应该陈述清楚自己的需求是什么。Anxious 的一方需要让对方明确什么事情会触发自己的焦虑反应,避免通过过激反应来吸引对方注意力。Avoidant 的一方也需要让对方知道,自己需要保持距离并不是因为对方的问题,无论跟谁在一起这个距离都是需要保持的。

书里面还有一些有趣的观点,同时引用了不少有意思的心理实验。有兴趣的人可以自己去看看。看完这本书以后你就会觉得身边很多恋人的矛盾都是能解释得同的,因为其中一方是 Anxious 而另外一方是 Avoidant 的迹象很明显。同时 PUA 训练的效用也变得很显而易见,那就是把自己从 Anxious 强行扭转为 Avoidant,然后逼对方进入 Anxious 的角色。所谓要让对方经历情绪高低起伏创造爱情感觉的技巧,本质上都是利用对方 Anxious 的特性而已。在心理学上这是有效的技巧,但你也不希望两个人长期处于这种状态,因为幸福感会比不上 Secure 的状态。

2014年3月17日星期一

了解并改变你的习惯

最近看了一本书叫做《The Power of Habit》,觉得书本身写得超好,故事和道理穿插的写法让你好像追小说一样没办法把书(或者是 Kindle)放下来,最终你理解了书中所讲的道理,但却不会像阅读一本讲道理的书一样疲劳。这本书说了很多跟习惯有关的研究和案例,解释了习惯是如何形成的,讲述了商业社会如何利用习惯改变员工改变顾客,最后还讨论了一下习惯在社会层面的影响。

书的开头通过一个叫做 Eugene Pauly(简称 E.P.)的病人引入人类对习惯形成的研究。E.P. 曾患脑炎,治愈后大脑留下了不可修复的损伤,他因此无法获得新的长期记忆。但因为他还能获得短期记忆,也就是记住最近 15 分钟发生的事情,同时他还拥有脑炎之前的长期记忆。尽管 E.P. 之后的生活变得十分奇怪,例如他会起床吃早餐然后回到床上,过一会儿又再起床吃早餐,但研究人员却在他身上发现了「奇迹」。

E.P. 自己记不住新家在哪里,所以每天只能由妻子带着在家附近绕圈散步。有一天妻子穿好衣服正准备出门时发现 E.P. 不见了,就只能挨家挨户地去找,结果回到家后却发现 E.P. 在看电视。问他发生了什么事他当然说不知道,因为他记不住 15 分钟以前的事情,但桌上的松果和他手上的泥证明他是出去回来了。之后他妻子试着跟踪他,发现他能够沿着他们每天散步的路线自己走回来,但前提是路线上的关键特征不变。之后研究人员也发现了同样的情况。在 E.P. 家客厅问他「厨房在哪」,他会说不知道。但如果让他去拿点坚果过来,他就能跑到厨房里拿坚果。显然 E.P. 在无法产生新的长期记忆的前提下,他的大脑仍然能够学习新的知识。这其实就是新习惯的形成。

那在习惯形成的过程中,大脑到底发生了什么变化?同一时代的另外一组科学家通过动物实验找到了解释。他们把老鼠放在一个丁字形迷宫里面,并且一开始让老鼠处于丁字底部的笼子里。当把笼子打开后,老鼠可以往前走到丁字路口。接下来如果它往左走将会发现巧克力,如果它往右走则会发现什么都没有。在一只老鼠最初几次重复这个实验时,它的大脑从笼子打开到找到巧克力一直出于活跃状态,因为它需要主动探索这个迷宫。但在实验重复次数多了以后,老鼠的大脑只在笼子打开那一刻和最后找到巧克力那一刻活跃,中间过程都是无意识的进行的。科学家得出的结论是,习惯由提示、例行事务、奖励组成。提示会让大脑进入自动模式,之后大脑无意识地执行例行事务,最后得到奖励强化习惯。

那习惯又是如何导致上瘾的呢?这次轮到拿猴子做实验的科学家了。他们把猴子固定在电脑屏幕前,然后在屏幕上显示有颜色的形状。如果猴子在看到这些形状时拉动控制杆,一滴黑莓汁就会被送到猴子嘴里。最初猴子对屏幕上发生的事情一点也不感兴趣,但在尝到第一滴黑莓汁后它变得专注于屏幕上的变化。在若干次重复操作后,猴子已经习惯了屏幕给出提示,自己完成拉控制杆的例行事务,然后等待黑莓汁的奖励。这时候猴子的脑电波表明,猴子是在得到黑莓汁后才感到兴奋的。但如果继续重复这个实验,猴子兴奋的时刻就会往前移动,最终移动到形状出现后控制杆拉下前。这时候猴子就上瘾了。

上瘾之前的猴子是可以被打断的。如果形状出现后打断猴子,让它离开实验出去玩,或者给食物它吃,它都会选择出去玩或者吃东西。但一旦猴子上瘾了,它就不能被打断了,因为它的大脑在见到提示后已经开始预期奖励的出现。如果这时候推迟黑莓汁或者稀释黑莓汁,猴子就会变得不开心甚至是愤怒。这些都是上瘾的标志。这也正是习惯强大的地方——让人在得到奖励之前就开始预期奖励的出现。当然,这也是培养良好习惯的有效手段,正确地组合提示、例行事务和奖励,然后通过重复训练直到上瘾。例如说每天早上醒来看到床边的跑鞋,然后起来就出去跑步,跑完回来看到计步器的数字感到满足。重复若干次后就会形成习惯。

在通过一系列的故事讲解完习惯和成瘾背后的科学后,这本书接着通过若干市场案例解释习惯对消费者的影响。第一个例子是 20 世纪初 Pepsodent 牙膏的流行。当时美国人不爱刷牙,但广告先驱 Claude Hopkins 成功让美国人培养起每天用 Pepsodent 牙膏刷牙的习惯。Pepsodent 牙膏的成功在于刷完牙后它会在口腔内留下一股凉凉的刺激性感觉。尽管这种感觉会让人感觉到略微不舒服,但 Pepsodent 成功让消费者把这种感觉和一口洁白的牙齿关联起来,最终消费者不刷牙的话就会感觉到总是少了点什么。

一个跟 Pepsodent 相反的案例是 P&G 的 Febreze。Febreze 原本是纯粹的除味剂,喷洒过后能让各种不愉快的气味消失。P&G 花了不少研发费用在 Febreze 上面,接着又投入了不少市场研究费用,但这个产品就是很少人买。在 P&G 即将要放弃 Febreze 的时候,市场研究团队终于发现了产品失败的关键——Febreze 使用后不会留下任何气味。没有气味不就是 Febreze 的目标吗?但这样就无法让消费者形成喷洒 Febreze 的习惯,大多数免费获得 Febreze 样品的消费者用了几次后就忘记使用了。最终 P&G 为 Febreze 加入了一种清新愉悦的气味。Febreze 从此热卖,因为消费者记得他们是为了这种气味带来的愉悦才喷洒的。

接着这本书开始介绍如何改变习惯。习惯一定形成了,就非常难被消除,但习惯可以被改变。如果能够维持原有的提示和奖励,例行事务是可以被替换掉的。例如说,如果你每天下午都要到公司楼下买杯咖啡,那你可以尝试分析一下提示和奖励是什么。你可以通过控制实验来寻找提示和奖励。如果你每天 3:30 去买咖啡然后跟同事一起喝,你可以实验替换习惯的不同部分,例如说找一天 3:00 就去,找一天改买奶昔,找一天出去逛一圈就回来,再找一天直接去跟同事聊天。可能你最终会发现提示是你每天 3:30 感觉无聊了,能让你感到满足的奖励是跟同事聊天,这时候你就可以把例行事务替换掉了——每天 3:30 去跟同事聊聊天,然后满足得回到自己座位继续干活,从而能够少喝一杯咖啡。

除去科学的部分以外,信念可能是改变习惯配方中的神秘成分。有时候如果一个人相信自己能够逃脱原本的坏习惯,最终他就能成功改变习惯。但如果他不相信他能够获得改变,那他也就有可能最终半途而废。这是作者对美国 AA(匿名戒酒会)帮助戒酒成功之余必须引入宗教信仰的解释。因为 AA 强调是匿名无记录的,所以外界无法对 AA 戒酒的过程进行科学的记录和分析,但不少人在 AA 戒酒成功后都会提到自己在经历过上帝显灵后才下定决心戒酒然后成功的。对此唯一的科学解释就是,改变习惯需要一点自证预言,相信自己能成功这点本身也能帮助你成功。(AA 的另一点效用源自社会压力。就算是匿名,在小组内公开宣布要戒酒的人更有动力去戒酒。)

到此这本书结束了第一部分,也就是习惯对人的影响。之后第二部分讲习惯对企业的影响,例如美国前任财政部长 Paul O’Neill 在出任 Alcoa(美国铝业公司)的 CEO 时是如何通过强调安全习惯来改变整家公司的文化的。安全是小习惯,但在公司实现零安全事故的目标后,工人和管理层就会开始相信更大的改变也是可以做到的,结果让这家濒临破差的大公司起死回生。此外,Alcoa 濒临破产的状态正好是建立新习惯的优势之一。这时候员工已经觉得管理层无能而不愿意听从管理层命令,管理层唯一能做的就是给员工危机感,让他们知道继续保持原有做事方式必定是死路一条,因此必须尝试新的做事方式。简单来说,动荡当中的公司更容易建立起新的习惯。

这对人生来说一样是成立的,所以 Target 花重金在数据挖掘上,就为了找出孕妇和新生婴儿的父母。在人生的转折点上,人更容易形成新习惯。由于孕妇和新生婴儿的父母急需购买婴儿用品的同时又对价格不敏感,所以一旦 Target 成功帮助他们培养在 Target 购买婴儿用品的习惯,之后他们就会乐意在 Target 购买任何东西。

书的第三部分讲述了习惯对社会的影响。作者尝试从习惯的角度分析美国的黑人运动和基督教传播。例如说,成功的传教应该是在维持教徒原有社交方式的前提下完成的。教徒只是把社交地点换到了教堂,只是鼓励角度在社交时谈论宗教。原有社交习惯所用的提示和奖励都不变,宗教被融入到例行事务当中去。

最后作者尝试探讨既然习惯属于无意识行为,那我们是否要对自己习惯的结果负法律责任。曾经有梦游杀妻而被判无罪的判例,理由是当事人当时无意识不能干预,那赌场刻意培养消费者上瘾导致消费者欠下大量赌债,这时候消费者又是否需要负责任呢?作者认为消费者是应该对自己的行为负责任的,因为习惯不是一个持续无意识不能干预的过程。既然你能遇见到习惯被触发后的后果,尽管习惯被触发后是无意识的过程,但你也有责任在触发前干预以保证习惯不被触发,否则的话你就要对习惯被触发后的结果负责任。

总的来说,我对这本书是非常满意的。虽然讲了那么多道理,但在如此多故事的穿插之下会让人觉得非常吸引。对于整本书来说,我觉得最有用的道理就是这一条:意志力不是技能,而更像是肌肉,反复使用就会疲劳。聪明人不是更多地使用意志力,而是更少地使用意志力。他们只在培养习惯时使用意志力,习惯一旦形成就不再需要使用意志力。如果你对这本书感兴趣,我推荐你去买一本来读一读。

P.S. 文章开头的「最近」二字是基于文章草稿阶段写的,那其实是 2013 年年底。

2014年3月15日星期六

如何捕获和分析 JavaScript Error

前端工程师都知道 JavaScript 有基本的异常处理能力。我们可以 throw new Error(),浏览器也会在我们调用 API 出错时抛出异常。但估计绝大多数前端工程师都没考虑过收集这些异常信息。反正只要 JavaScript 出错后刷新不复现,那用户就可以通过刷新解决问题,浏览器不会崩溃,当没有发生过好了。这种假设在 Single Page App 流行之前还是成立的。现在的 Single Page App 运行一段时间后状态复杂无比,用户可能进行了若干输入操作才来到这里的,说刷新就刷新啊?之前的操作岂不要完全重做?所以我们还是有必要捕获和分析这些异常信息的,然后我们就可以修改代码避免影响用户体验。

捕获异常的方式

我们自己写的 throw new Error() 想要捕获当然可以捕获,因为我们很清楚 throw 写在哪里了。但是调用浏览器 API 时发生的异常就不一定那么容易捕获了,有些 API 在标准里就写着会抛出异常,有些 API 只有个别浏览器因为实现差异或者有缺陷而抛出异常。对于前者我们还能通过 try-catch 捕获,对于后者我们必须监听全局的异常然后捕获。

try-catch

如果有些浏览器 API 是已知会抛出异常的,那我们就需要把调用放到 try-catch 里面,避免因为出错而导致整个程序进入非法状态。例如说 window.localStorage 就是这样的一个 API,在写入数据超过容量限制后就会抛出异常,在 Safari 的隐私浏览模式下也会如此。

try {
  localStorage.setItem('date', Date.now());
} catch (error) {
  reportError(error);
}

另一个常见的 try-catch 适用场景是回调。因为回调函数的代码是我们不可控的,代码质量如何,会不会调用其它会抛出异常的 API,我们一概不知道。为了不要因为回调出错而导致调用回调后的其它代码无法执行,所以把调用回到放到 try-catch 里面是必须的。

listeners.forEach(function(listener) {
  try {
    listener();
  } catch (error) {
    reportError(error);
  }
});

window.onerror

对于 try-catch 覆盖不到的地方,如果出现异常就只能通过 window.onerror 来捕获了。

window.onerror =
  function(errorMessage, scriptURI, lineNumber) {
    reportError({
      message: errorMessage,
      script: scriptURI,
      line: lineNumber
    });
}

注意不要耍小聪明使用 window.addEventListenerwindow.attachEvent 的形式去监听 window.onerror。很多浏览器只实现了 window.onerror,或者是只有 window.onerror 的实现是标准的。考虑到标准草案定义的也是 window.onerror,我们使用 window.onerror 就好了。

属性丢失

假设我们有一个 reportError 函数用来收集捕获到的异常,然后批量发送到服务器端存储以便查询分析,那么我们会想要收集哪些信息呢?比较有用的信息包括:错误类型(name)、错误消息(message)、脚本文件地址(script)、行号(line)、列号(column)、堆栈跟踪(stack)。如果一个异常是通过 try-catch 捕获到的,这些信息都在 Error 对象上(主流浏览器都支持),所以 reportError 也能收集到这些信息。但如果是通过 window.onerror 捕获到的,我们都知道这个事件函数只有 3 个参数,所以这 3 个参数以外的信息就丢失了。

序列化消息

如果 Error 对象是我们自己创建的话,那么 error.message 就是由我们控制的。基本上我们把什么放进 error.message 里面,window.onerror 的第一个参数(message)就会是什么。(浏览器其实会略作修改,例如加上 'Uncaught Error: ' 前缀。)因此我们可以把我们关注的属性序列化(例如 JSON.Stringify)后存放到 error.message 里面,然后在 window.onerror 读取出来反序列化就可以了。当然,这仅限于我们自己创建的 Error 对象。

第五个参数

浏览器厂商也知道大家在使用 window.onerror 时受到的限制,所以开始往 window.onerror 上面添加新的参数。考虑到只有行号没有列号好像不是很对称的样子,IE 首先把列号加上了,放在第四个参数。然而大家更关心的是能否拿到完整的堆栈,于是 Firefox 说不如把堆栈放在第五个参数吧。但 Chrome 说那还不如把整个 Error 对象放在第五个参数,大家想读取什么属性都可以了,包括自定义属性。结果由于 Chrome 动作比较快,在 Chrome 30 实现了新的 window.onerror 签名,导致标准草案也就跟着这样写了。

window.onerror = function(
  errorMessage,
  scriptURI,
  lineNumber,
  columnNumber,
  error
) {
  if (error) {
    reportError(error);
  } else {
    reportError({
      message: errorMessage,
      script: scriptURI,
      line: lineNumber,
      column: columnNumber
    });
  }
}

属性正规化

我们之前讨论到的 Error 对象属性,其名称都是基于 Chrome 命名方式的,然而不同浏览器对 Error 对象属性的命名方式各不相同,例如脚本文件地址在 Chrome 叫做 script 但在 Firefox 叫做 filename。因此,我们还需要一个专门的函数来对 Error 对象进行正规化处理,也就是把不同的属性名称都映射到统一的属性名称上。具体做法可以参考这篇文章。尽管浏览器实现会更新,但人手维护一份这样的映射表并不会太难。

类似的是堆栈跟踪(stack)的格式。这个属性以纯文本的形式保存一份异常在发生时的堆栈信息,由于各个浏览器使用的文本格式不一样,所以也需要人手维护一份正则表达,用于从纯文本中提取每一帧的函数名(identifier)、文件(script)、行号(line)和列号(column)。

安全限制

如果你也遇到过消息为 'Script error.' 的错误,你会明白我在说什么的,这其实是浏览器针对不同源(origin)脚本文件的限制。这个安全限制的理由是这样的:假设一家网银在用户登录后返回的 HTML 跟匿名用户看到的 HTML 不一样,一个第三方网站就能把这家网银的 URI 放到 script.src 属性里面。HTML 当然不可能被当做 JS 解析啦,所以浏览器会抛出异常,而这个第三方网站就能通过解析异常的位置来判断用户是否有登录。为此浏览器对于不同源脚本文件抛出的异常一律进行过滤,过滤得只剩下 'Script error.' 这样一条不变的消息,其它属性统统消失。

对于有一定规模的网站来说,脚本文件放在 CDN 上,不同源是很正常的。现在就算是自己做个小网站,常见框架如 jQuery 和 Backbone 都能直接引用公共 CDN 上的版本,加速用户下载。所以这个安全限制确实造成了一些麻烦,导致我们从 Chrome 和 Firefox 收集到的异常信息都是无用的 'Script error.'

CORS

想要绕过这个限制,只要保证脚本文件和页面本身同源即可。但把脚本文件放在不经 CDN 加速的服务器上,岂不降低用户下载速度?一个解决方案是,脚本文件继续放在 CDN 上,利用 XMLHttpRequest 通过 CORS 把内容下载回来,再创建 <script> 标签注入到页面当中。在页面当中内嵌的代码当然是同源的啦。

这说起来很简单,但实现起来却有很多细节问题。用一个简单的例子来说:

<script src="http://cdn.com/step1.js"></script>
<script>
  (function step2() {})();
</script>
<script src="http://cdn.com/step3.js"></script>

我们都知道这个 step1、step2、step3 如果存在依赖关系的话,则必须严格按照这个顺序执行,否则就可能出错。浏览器可以并行请求 step1 和 step3 的文件,但在执行时顺序是保证的。如果我们自己通过 XMLHttpRequest 获取 step1 和 step3 的文件内容,我们就需要自行保证其顺序正确性。此外不要忘记了 step2,在 step1 以非阻塞形式下载的时候 step2 就可以被执行了,所以我们还必须人为干预 step2 让它等待 step1 完成后再执行。

如果我们已经有一整套工具来生成网站上不同页面的 <script> 标签的话,我们就需要调整一下这套工具让它对 <script> 标签做出改动:

<script>
  scheduleRemoteScript('http://cdn.com/step1.js');
</script>
<script>
  scheduleInlineScript(function code() {
    (function step2() {})();
  });
</script>
<script>
  scheduleRemoteScript('http://cdn.com/step3.js');
</script>

我们需要实现 scheduleRemoteScriptscheduleInlineScript 这两个函数,并且保证它们在第一个引用外部脚本文件的 <script> 标签之前就被定义好,然后余下的 <script> 标签都会被改写成上面这种形式。注意原本立即执行的 step2 函数被放到了一个更大的 code 函数里面了。code 函数并不会被执行,它只是一个容器而已,这样使得原本 step2 的代码不需要转义就能保留下来,但又不会被立即执行。

接下来我们还需要实现一套完整的机制,保证这些由 scheduleRemoteScript 根据地址下载回来的文件内容和由 scheduleInlineScript 直接获取到的代码能够按照正确的顺序一个接一个地执行。详细的代码我就不在这里给出了,大家有兴趣可以自己去实现。

行号反查

通过 CORS 获取内容再把代码注入页面能够突破安全限制,但会引入一个新的问题,那就是行号冲突。原本通过 error.script 可以定位到唯一的脚本文件,再通过 error.line 可以定位到唯一的行号。现在由于都是页面内嵌的代码,多个 <script> 标签并不能通过 error.script 来区分,然而每一个 <script> 标签内部的行号都是从 1 算起的,结果就导致我们无法利用异常信息定位错误所在的源代码位置。

为了避免行号冲突,我们可以浪费一些行号,使得每一个 <script> 标签中有实际代码所使用的行号区间互相不重叠。举个例子来说,假设每个 <script> 标签中的实际代码都不超过 1000 行,那么我可以让第一个 <script> 标签中的代码占用第 1–1000 行,让第二个 <script> 标签中的代码占用第 1001–2000 行(前面插入 1000 行空行),第三个 <script> 标签种的代码占用第 2001–3000 行(前面插入 2000 行空行),以此类推。然后我们使用 data-* 属性记录这些信息,便于反查。

<script
  data-src="http://cdn.com/step1.js"
  data-line-start="1"
>
  // code for step 1
</script>
<script data-line-start="1001">
  // '\n' * 1000
  // code for step 2
</script>
<script
  data-src="http://cdn.com/step3.js"
  data-line-start="2001"
>
  // '\n' * 2000
  // code for step 3
</script>

经过这样处理后,如果一个错误的 error.line3005 的话,那意味着实际的 error.script 应该是 'http://cdn.com/step3.js',而实际的 error.line 则应该是 5。我们可以在之前提到的 reportError 函数里面完成这项行号反查工作。

当然,由于我们没办法保证每一个脚本文件只有 1000 行,也有可能有些脚本文件明显小于 1000 行,所以其实不需要固定分配 1000 行的区间给每一个 <script> 标签。我们可以根据实际脚本行数来分配区间,只要保证每一个 <script> 标签所使用的区间互不重叠就可以了。

crossorigin 属性

浏览器对于不同源的内容进行的安全限制当然不仅限于 <script> 标签。既然 XMLHttpRequest 可以通过 CORS 来突破这个限制,为什么直接通过标签引用的资源就不可以呢?这当然是可以的。

针对 <script> 标签引用不同源脚本文件的限制同样作用于 <img> 标签引用不同源图片文件。如果一个 <img> 标签是不同源的话,一旦在 <canvas> 绘图时用到了,该 <canvas> 将变为只写状态,保证网站不能通过 JavaScript 窃取未授权的不同源图片数据。后来 <img> 标签通过引入 crossorigin 属性解决了这个问题。如果使用 crossorigin="anonymous",则相当于匿名 CORS;如果使用 `crossorigin=“use-credentials”,则相当于带认证的 CORS。

既然 <img> 标签能这样做,为什么 <script> 标签就不能这样做?于是浏览器厂商就为 <script> 标签加入了同样的 crossorigin 属性用于解决上述安全限制问题。现在 Chrome 和 Firefox 对这个属性的支持是完全没有问题的。Safari 则会把 crossorigin="anonymous" 当做 crossorigin="use-credentials" 处理,结果是如果服务器只支持匿名 CORS 则 Safari 会当做认证失败。由于 CDN 服务器出于性能的考虑被设计为只能返回静态内容,不可能动态的根据请求返回认证 CORS 所需的 HTTP Header,Safari 相当于不能利用此特性来解决上述问题。

总结

JavaScript 异常处理看起来很简单,跟其它语言没什么区别,但真的要把异常都捕获了然后对属性做分析,其实还不是那么容易的事情。现在尽管有一些第三方服务提供捕获 JavaScript 异常的类 Google Analytics 服务,但如果要弄明白其中的细节和原理还是必须自己亲手做一次。

2014年3月10日星期一

Facebook 发布「流程」

时不时就会在面试过程中碰到有候选人问 Facebook 是否采用 Scrum 之类的敏捷方法,偶尔也会有中国的朋友问及 Facebook 上线流程。我通常会简单说几句,然后说「如果你真感兴趣的话,去搜索 Chuck Rossi 在 Velocity 2012 San Fancisco 演讲的视频」。无论从 Scrum 的角度来看,还是大多数中国公司的上线流程来看,Facebook 的发布流程都显得很不一样,但其实又非常合理,看完那个视频你就明白了。尽管里面提到的内部工具都没有在 Facebook 的 GitHub 上开源,但那些截图已经足够清晰说明其功能和用途了。

工具固然是重要的一方面,但我觉得更重要的是文化。我知道很多中国公司的上线流程都涉及各种签字,例如我在百度的时候上线就需要 RD、FE、QA、OP 等众多角色签字,有时候还需要对应经理甚至总监签字。相对这种上线流程来说,Facebook 的发布流程简单得很反流程,这也是我为标题中的「流程」二字加上引号的原因。考虑到我之后会专门写一篇文章讨论文化,所以在这里就不深入展开了。

至于工具,最重要的就是通过引入自动化来解答一些简单但涉及大量手工操作的问题。例如视频当中提到的,「一个 PHP 异常是由哪个 commit 引入的?」在没有工具的情况下,这只能手工 git bisect 来查找。万一这个异常不是稳定复现的话,那基本上就没办法定位到 commit 了。Facebook 的工具能自动把异常堆栈跟踪里面每一帧和 git blame 关联起来,再用异常发生频率的历史图谱跟 commit 合并时间做对比,很容易就能得到答案。

最后,如果你看完这个视频觉得还不满足的话,可以去看看 Jay Parikh 在 Velocity 2012 Santa ClaraGirish Patangay 在 Velocity 2012 London 的视频。

2014年2月27日星期四

The Crazy Trip (Point 7 - Atlanta)

12/30

天还没亮我们就从酒店出发了,避开了 Houston 星期一的早高峰,沿着 I–10 继续往东行驶。这一天要穿越 4 次州界,从 Texas 到 Louisiana、Mississippi、Alabama 最终抵达 Georgia。离开 Texas 进入 Louisiana 后,I–10 两侧的地貌就变得跟之前的完全不一样了。巨大的岩石不见了,荒漠变得有植被了,然后路的两旁还出现了灌木。在接近 Mississippi River 时,由于河流湖泊增多 I–10 直接就变成了在水面上的高架,能够看到河流边上半泡在水里的丛林。

Untitled

由于在 Louisiana 的一段一直是阴天,车速那么快的情况下快门根本跟不上,拍不了什么好的照片,所以在 Mississippi River 过桥时多拍了几张也就算了。尽管路过 Louisiana 的 I–10 不算长,但一路上电子狗被触发的频率特别高,从进入 Louisiana 到离开一共看到 5 起被 pull over 的,不知道是不是警察要赶在年底之前努力完成任务。

我们最终还是日落后才抵达 Atlanta,找了一家叫做 Spondivits 的海鲜餐厅吃饭,坐下来看到顾客和服务员才意识到这里的黑人比例明显高于我们之前去过的几个州。虽然餐厅看起来不怎么高档,旁边就是一家麦当劳,但海鲜还是非常好吃的,一磅的什锦就有龙虾、虾、蟹,我们要了三磅再加沙拉和意粉。吃完出来发现餐厅免费的代客泊车还顺便帮忙洗车了,服务真好。

12/31

由于前一天晚上某人开车时对 Atlanta I–75/I–85 的评价是「很有极品飞车的感觉」,所以我就顺便拍了几张。其实 I–75/I–85 在穿过 Atlanta 的部分限速非常低(55 mph),只是略微弯曲的封闭式高速公路再加上一些立交和匝道,那感觉就差不多了。

Untitled

这一天的日程是参观可口可乐公司总部(旁边的)World of Coca-Cola。由于买了正午 12 点入场的门票,所以早上起床一点都不着急,去 Waffle House 吃了个早餐。话说 Atlanta 的 Waffle House 真多,在来 Georgia 之前都没听说过这个连锁。在 Waffle House 第一次见识到 city ham 和 country ham 的区别,前者跟超市里卖的火腿肉差不多,后者则是猪腿上的一块完整的切片,还保留了大腿骨在中间。

Untitled

吃完早餐来到了 World of Coca-Cola,跟之前在 NASA Johnson Space Center 一样拿 iPad 出来显示网上购买的门票让他们扫描条形码就能进去了。第一部分的讲解是有场次的,所以如果上一场在讲了就要等下一场的开放时间才能进去,这时候只能在等候区域拍拍照。尽管圣诞节刚刚过去,但因为可口可乐的红色主题装饰,使得整个地方很有春节气氛。

Untitled

讲解厅内展示了各式各样可口可乐经典视觉设计,从可乐瓶到贩卖机,再到世界各地不同年代不同媒体的广告,真的挺有意思的。讲解人会简单介绍可口可乐公司的历史,然后再介绍这是展品有趣的地方。在讲解过后,我们进入到一个小放映厅,看了最新一系列可乐广告的主题片,就是有北极熊的那个系列,之后就解散自由活动了。作为广告的主角,World of Coca-Cola 大厅中央当然免不了有一位工作人员在那里扮演北极熊与大家合照,而且排队还不短。

Untitled

我们没有人去排队跟北极熊拍照,主要是觉得这有点傻,因此我们直接去排 Vault of the Secret Formula 的队伍了。这个区域的外观设计得像是一个真的保险库一样,进去后有可乐秘方的历史介绍。可口可乐被发明出来之后,公司做到有一定规模就被卖掉了,然后再扩大到一定规模又被卖了一次。只不过这一次 4 位风险投资人没有足够的资金把公司完全买下来,所以需要向银行贷款,秘方作为抵押物被放在保险柜里运送到纽约去。这是唯一一次秘方离开其发源地。在贷款还清后秘方马上又运了回来。之后秘方一只保留在可口可乐公司总部,直到最近几年才被搬到 World of Coca-Cola 来。

Untitled

然后大家就看到一个巨大的保险柜,还被告知拍照不要超越黄线否则它真会响的。其实谁会相信秘方会存放在一个游客能看得到的地方呢?只不过可乐的宣传做的真好,连秘方一事也能做出如此正面的形象,引起游客的兴趣去看各种相关展品。

之后的 Milestones of Refreshment 展示的是可口可乐公司的发展史。其实这家公司的所有国际性产品都不是自己灌装的,可口可乐公司卖的只是糖浆而已,世纪各国的经销商负责进口糖浆然后灌装成饮料。为此每一个国家的经销商都可以灵活地制定自己的销售策略和渠道,从而做出很多本地化设计来。

Untitled Untitled

一楼最后的 Bottle Works 展示了可口可乐的灌装流程。没错,在 World of Coca-Cola 里面有一条迷你生产线在不停地进行灌装。灌装出来的瓶装可乐会给每位游客发一瓶,有时候游客不够多发不完就只能把部分灌装好的可乐倒掉,然后瓶子放回生产线上继续演示。生产线需要玻璃瓶、糖浆和纯净水。后两者都是管道接入进来的,但玻璃瓶是一箱子一箱子地运进来的。本来有一个机械臂负责把一个箱子里的玻璃瓶逐个放到流水线上,不过今天它坏了所以就需要有个人来一个一个放玻璃瓶……

Untitled

然后玻璃瓶会经过检测,及格的继续往前走,有缺陷的放到一边去。

Untitled

之后糖浆和经过检测的纯净水也会被送进来。在流水线上的瓶子会经过一个机器,先在每个的底部加几毫米糖浆,然后再加水加到够为止。

Untitled

灌装好的瓶装可乐会有螺旋传送带送到二楼去,然后再分发给准备离开的游客。

Untitled

看着一堆机器在流水线上工作对于 geek 来说还是很有意思的!

参观完一楼上二楼。二楼主要是可乐历代广告展示。有一个厅是专门展示平面广告的,很多早期广告的油画草图和油画原件都被保留了下来,使得大家能看到当初的广告创作过程。还有一个展厅是专门展示电视广告的。不是单纯地播放历史上的电视广告给大家看,而是有主持人解说的。主持人会介绍可乐在什么历史背景下选择了以什么样的主题做广告,因此做出来怎么样的广告。

Untitled

最后一个展厅就是品尝区了。这里有可口可乐公司在世界各地投放的不同产品,有些可能只在少数地区销售所以在其他地区听都没听说过。不要以为可口可乐公司只做可乐和各种水果味道的汽水,还有一些味道非常奇怪的东西,就跟中药稀释后加冰加气一样难喝。反正如果你有时间的话,可以每样拿一点点来品尝一下,除了某些熟悉的味道以外,保证有惊喜。

Untitled

品尝区头顶上的轨道运送的就是楼下灌装好的可乐,最后都会被送到大转盘上让游客在离开之前领取。领取之后出去就是礼品店了,出去了就无法再回来了。

Untitled

礼品店里面自然少不了各式杯具。看起来跟过往中国麦当劳套餐送的差不多的玻璃杯,在这里竟然要卖 $2,瞬间觉得自己在离开北京时扔了不少值钱的东西。尽管礼品店里面的商品款式不少,不过看来看去最终没有什么是我特别喜欢的。尽管我曾经喜欢喝可乐,但其实我对这家公司没有非常高的品牌认同度,所以买可乐主题的礼品貌似没什么意思。

Untitled

在离开 World of Coca-Cola 后,我们想到旁边的 Georgia Aquarium 看看。结果发现票价不便宜,但剩余时间不多了,所以没有买票进去。现在想想有点后悔,反正是出来旅游的,就算看不完也不在乎 $35 门票钱。(不过其实里面有些项目是独立卖票的,不知道只买门票的话包括什么。)

离开 World of Coca-Cola 后,我们想要到 Georgia Tech 看看,结果发现不知道有什么值得我们去看的。一般的学校就算有导游项目的,也需要提前预约,所以就不用想了。我们开车进去绕了一圈,发现环境很不错但没见到有什么非常有特色的东西。

Untitled

随后我们想要到 Centennial Olympic Park 看看,结果发现 GPS 把我们带回了 World of Coca-Cola,原来这个公园就是旁边的一块大草坪。下去看看不是不好,但是附近都没有停车位。回去给 World of Coca-Cola 或 Georgia Aquarium 再交 $10 停车费?又好像不是很值得,尤其是我们才刚刚把车从那里开出来。于是我们决定回酒店吃饭,然后晚上玩 Cards Against Humanity。难得我们的若干副桌游,跟随我们旅行了这么久,终于有一副是被拿出来玩过的。

我们 12 天旅行的行程到这里基本上就结束了。最后一天睡个懒觉,起床去 Atlanta 的唐人街再喝个茶,然后就去机场了。值得一提的是,因为我们下了高速才想起来忘记加油了,结果剩下半缸油被扣了 $150。虽然我们的大 SUV 邮箱也大,但 87 号汽油加满一缸也不用 $150,这实在是太贵了。以后不能因为 Atlanta 油价便宜就以为按两三倍油价来补钱也值得,因为人家可以要求五倍油价来补钱。

2014年2月24日星期一

The Crazy Trip (Point 6 - Houston)

12/28

随着 I–45 车道的增加,我们意识到已经开进 Houston 外围了,并且再次感觉到乡下人进城的震撼,因为很久没有见到过有如此灿烂灯火照亮着的高速了。在进入 Houston 以后,GPS 让我们走 Sam Houston Tollway,这是我来美国以后第一次见到非桥梁的收费高速。(Sam Houston 曾是德克萨斯共和国总统及德州州长。)

晚餐计划去朋友推荐的中国餐厅。当我们在电话上听到「中国餐厅」这四个字的时候就崩溃了,这么通用的名字怎么搜索啊?结果还是能搜到的,只要先搜到其英文名字 Sinh Sinh ,然后就能在地图上搜索到了。到了之后我们发现傍边那家香港食街看起来更吸引,随后放弃中国餐厅直奔香港食街。坐下来之后看到菜单里全部都是粤菜和海鲜,立即又乡下人一般样样都想点一份。

12/29

Untitled

早上先回到唐人街找了家东海海鲜喝早茶,然后在旁边的阳光超市补给瓶装水和饼干。接着出发去 Houston 东南面的 NASA Johnson Space Center。由于我在停车后太过兴奋,忘记把加载车内的 GoPro 取下来了,结果在 Space Center 内部只有拍照没有录视频。而由于室内的展区都布置得灯光比较昏暗,所以其实照片很难拍好。

Untitled

在扫描门票进入 Space Center 后,发现这里跟大多数的游乐园(如 Universal Studio)一样,都是围绕着娱乐性来做设计的,不会涉及到多少实际性的东西。最接近 NASA 实际工作场地的,就是那个 tram tour 了,不过也就是在巨大的厂房里面走走天桥,跟在 Seattle 参观波音工厂差不多,而且 NASA 的模拟训练场还没有波音的工厂大。

Untitled

所谓的模拟训练场,就是在地面上建造一堆飞行器和空间站的 1:1 模型,然后让宇航员在里面进行训练。这里云集了不同国家制造的不同宇宙飞船、航天飞机和空间站模块的模型,使得宇航员能够熟悉他们可能将要接触到的环境。

Untitled Untitled Untitled Untitled

美国当初制造航天飞机是为了省钱,说航天飞机可以反复使用不像宇宙飞船那样发射一次就废了。结果发生几次航天飞机事故后,又发现航天飞机的维护成本极高,还不如使用一次性的宇宙飞船。所以现在航天飞机都退役后,美国又继续研究宇宙飞船了。同时 NASA 还在研究机器人,以及更大的月球车、火星车,希望将来能在月球和火星上执行更加复杂的任务。(彩蛋是背后半遮半掩的 SpaceX Dragon。)

Untitled Untitled

离开训练场之后,我们被带到了火箭公园。在途中会经过一大片草地,草地上面的每一棵树代表着一位在任务中身亡的宇航员。树的下面有个块石碑,上面刻着宇航员的名字以及任务。

Untitled

火箭公园的户外部分放着两支火箭和三个不同规模的引擎。因为火箭是分级的,所以每一级所带的引擎大小都不一样。走近看引擎上面复杂的管线还是挺有意思的,尽管都不知道这些管线是用来干什么的。

Untitled Untitled Untitled Untitled

室内则是巨大的 Saturn V(土星 5 号运载火箭),包括三级没有发射过推进器以及一个锈迹斑斑的宇宙飞船。这个一百多米高的东西没有广角镜头是无论如何都拍不下来的,实在是有点后悔忘记把 GoPro 带进来。

Untitled Untitled Untitled

展馆一侧的墙上列举了所有的阿波罗任务,介绍了每一次任务的目的、宇航员的名字以及任务执行的情况。从 Apollo 11 之前的各次训练任务,到 Apollo 11 成功登月后难度递增的任务,都做了详细的介绍。我就在这个火箭公园里看了一个多小时才返回展览中心的。

Untitled

回到展览中心后,就去看看剩下的各种室内寓教于乐的展示性项目啦。我觉得 NASA 最厉害的地方莫过于能够让真正的科学家和宇航员周末加班来给我们这些游客做讲解。这些展示性项目有些每 20 分钟一轮,有些每 60 分钟一轮,估计中间是预留了休息时间给讲解人员的。由于讲解使用 1:1 到 1:n 的模型都放在一个人造的星空背景下,所以其实没什么好拍的,我就只能拍拍月球土壤了。话说 NASA 还真有一块月球石头能让大家去摸一摸的,不过也就只能把手指伸进去触摸到而已。

Untitled

离开之前最后去看了一下航天飞机的驾驶舱模型。看起来很多东西都跟普通飞机很类似,不过我也不懂什么是航天飞机特有的。航天飞机其实是两层的,驾驶舱在上面一层,背后有一个巨大的货仓。

Untitled

总的来说,NASA Johnson Space Center 还是挺值得看的,如果路过 Houston 并且有完整的一天的话,我会建议大家去参观一下。我觉得美国在这方面的科普做得还是不错的,航天技术不仅仅是少数科学家秘密研究的事情,还需要培养大众对航天科学的兴趣,以便培养未来的科学家。当然这估计跟美国的民主也有点关系,NASA 花了纳税人那么多的钱不能仅仅把事情做好就算,还要把故事说得动听,然后纳税人才会支持更长远的航天项目。

Untitled Untitled

返回 Houston 市区后,我们决定再次去唐人街吃饭,这次总该来尝尝这家所谓的中国餐厅了,吃完后感觉不如昨天的香港食街,估计是因为不如昨天赶路后的饥饿吧。

行程在 Houston 之后的部分,由于跳过了原定计划中的 New Orleans,所以需要安排一个完整的白天开车,以便直达 Atlanta。考虑到 Atlanta 犯罪率击败美国 99% 的城市,所以我们希望日落之前抵达,为此必须早上 5 点离开 Houston,顺便避开 Houston 星期一的早高峰。如果你对我余下的行程仍然感兴趣的话,欢迎订阅本博客,我还有不少 World of Coca-Cola 的照片还没发呢。

2014年2月23日星期日

The Crazy Trip (Point 5 - Austin + Dallas)

12/27

离开 San Antonio 之后,我们沿着 I–35 一路往北开,目的地是 Dallas,不过我们选择了在 Austin 短暂停留参观一下 Facebook 在 Austin 的办公室顺便看看有没有午餐吃(结果是没有的)。

San Antonio 到 Austin 的距离很近,所以我们不需要很早出发,大概午餐时间到达 Austin 就可以了。我们 12:30 抵达 Facebook 办公室,上楼后发现 cafe 区域空空的,只好拿点零食吃。随后我们在 Facebook 的那两层楼转了几圈,找找有什么好玩的,为此而停留了一个多小时。

离开 Austin 以后,我们又开了几个小时才到达 Dallas,这时候已经天黑了。我们迅速在酒店附近找了一家牛扒店解决晚餐,吃完买单时才发现 6 个人吃了 $800。大家心算一下发现不对,如果每个人都是点一个 $70 上下的牛扒的话,再加上一起点的前菜和素菜,总数估计就 $500。把清单拿过来一看,原来某人点的和牛就 $280 了……当初他听说有和牛后很爽快地就点了,不过他没有听清楚和牛 $72 两磅,然后一下子点了八磅。土豪由此诞生!

12/28

Untitled

Dallas 有什么值得好看的呢?我来之前也不知道,所有就让其它人做计划吧。必须去看的是 The Sixth Floor Museum,也就是肯尼迪遇刺博物馆。因为当时刺客就在这栋教科书仓库的 6 楼开枪刺杀肯尼迪的,所以有了这样的名字。尽管我们一早去到博物馆,但买到的票已经是 12:30 进场的,所以只能先找个地方打发时间。有人说想去 The Dallas Arts District,考虑到相距就 5 分钟车程,那就开车过去看看吧。

这个艺术区如果拿来跟北京 798 比,那肯定是高大上很多的。四周是 Chase Tower 这样的高层写字楼或公寓,里面连个酒吧都是独栋两层豪宅,各种展厅画廊都装修得非常精致。我们在一栋写字楼的下面发现了一个亚洲文化展厅,里面有各种看起来很中式的东西。

Untitled

最初吸引到我们注意的是大楼两侧一对这样的中西合璧石狮子。从侧面看是石狮子,但中间一块大大的玻璃板上分别写了两个汉字。然后我们就开始绕着这栋大楼走了一圈,结果发现中式的雕塑还不少,而且还有个展厅专门展出亚洲文化相关艺术品的。

Untitled

展厅分作好几个主题,例如有一个玉石主题的展厅就有不少东南亚各国不同历史时期的玉石制品,还有科普说明说玉石这个感念其实包含了两种看起来相似但晶格完全不一样的结晶体。玉石最有趣的地方在于,在凿开之前没有人能够预测一块石头里面的玉石质量高低。

Untitled

看完这个展厅后,时间差不多了,该回去 The Sixth Floor Museum 排队了。回到之后被告知,这个队伍是 12:00 的,你们 12:30 的门票就等 12:30 再回来排吧。结果呢?我们在礼品店逛了 15 分钟,12:25 去排队发现 12:30 的队伍已经排起来了。早知道就应该 12:20 去开始排 12:30 的队伍。

队伍排到以后,先一人发了一个解说设备,然后搭电梯上去 6 层参观。这个解说设备其实就是一台 Android,看起来好像是 2.x 的。把耳机带上后,就可以输入展板的编号让它朗读对应的解说。(如果按顺序看展板的话,一路点「next」就可以了。)由于整个 6 层展区都不能拍照,所以没什么照片好发的。我是每块展板都慢慢看的人,结果花了差不多两个小时才看完。里面还有一些电视片段循环播放肯尼迪时代的一些经典片段。

Untitled

尽管杀手狙击肯尼迪的那个角落被落地大玻璃保护起来了,而且整个 6 层都不能拍照,但 7 层的那个角落是可以拍照的,因此我们就可以看到狙击手所能看到的角度。当时狙击手用装教科书的箱子把 6 层的这个角落围起来了,以便隐藏自己的行动。在刺杀后他迅速下楼然后跑回家里。其实警察在楼内就碰见过他,只是因为他是员工所以就让他离开了。后来他是在路上杀死了巡逻警察才被抓住的。

博物馆里面展示了众多跟刺杀案相关的资料,例如说大量路人的照片和录像。由于当时有一个专业的艺术家拿着比较好的录像机在录像,清晰地把整个过程都录下来了,所以时候的时间点都以这段录像的第几帧来做标记,其它人的照片和录像也对以此为时间基准。

Untitled

在看完 The Sixth Floor Museum 后,我们已经饿得受不了了。在 Subway 简单吃了午餐后又回到了 The Dallas Arts District。由于剩下的时间不多了,所以大家各自去看自己感兴趣的东西,而我就选择了去 Dalls Museum of Art 看看。

Untitled

之所以要在日落之前集合,是因为要赶往 Houston,然后在 Houston 吃晚餐。我们原定的行程没有 Austin 和 Dallas,San Antonio 之后就是 Houston。加上 Dallas 后相当于走了这 3 个城市组成的等腰三角形中的两条长边,如果不是之前跳过 Carlsbad Caverns National Park 导致了时间过剩,我们也就不会来看这么有趣的 The Sixth Floor Museum 了。

接下来 Houston 的重点在于 NASA Johnson Space Center,感兴趣的话请订阅我的博客。

2014年2月9日星期日

The Crazy Trip (Point 4 - San Antonio)

12/25

圣诞节的早上离开 Alpine 开往 San Antonio。由于没有考虑中间哪里可以吃午餐,中途也没有规模大一点的镇(小镇总觉得不知道是否安全),所以在加油时买了个汉堡就把午餐解决了。I–10 沿途这一段都是很荒凉的丘陵,时不时就能看到明显是从山中凿开的路。

Untitled

开到 San Antonio 之后,大家的感觉就如同从乡下出来没见过大城市一样——哇!好多高楼大厦啊,路上好多行人啊。(之前玩了那么多天国家公园,确实是在乡下。)在酒店放下行李后就已经是下午 4 点多了,我们决定在市区先闲逛一两个小时,然后再确定吃晚饭的地方。

Untitled

San Antonio 市区有一条河,叫做 San Antonio River。过去为了治理水患,人工地为 San Antonio River 开凿过一些分支的水道,后来被美化为 San Antonio Riverwalk,成为 San Antonio 市区的一大景点,同时也带旺了水道两旁的商业。相对于马路来说,Riverwalk 处于地下 1 层的位置,跟马路旁边的人行通道有楼梯连接。

我们在沿着 Riverwalk 走了一段之后走到了 Alamo。这里曾经是 Mission San Antonio,也就是西班牙殖民时代的传教场所。由于当天是圣诞节,Alamo 不开门,所以我们只好第二天再来参观。之后太阳开始下山了,我们也就开始找餐厅吃「圣诞大餐」了。当然,在没有预约的情况下好的餐厅都是不可能有座位的,所以最后在 Riverwalk 河畔找了一家美式的餐厅解决。

Untitled

12/26

在 San Antonio 的第二天,我们选择了去参观各种历史古迹。第一个景点是 Mission San José,位于 San Antonio 的南面。其实在去之前我也不知道这有什么好看的,只是在之前就制定好的行程上有这么一个地方,那就开车过去看看吧。去到之后才发现原来是个这么有文化的地方。

Mission San José 跟 Mission San Antonio 一样,是 San Antonio 的 5 个西班牙时代传教场所之一。这 5 个传教场所都是沿着 San Antonio River 修建的,因为这条河不仅仅能提供灌溉和饮用用水,甚至还能提供水能(不是发电而是推动磨坊)。除了 Mission San Antonio 以外,其它 4 个传教场所现在都归国家公园管,归到 San Antonio Missions National Historical Park 的名义下。

论建筑面积(而非公园面积)的话,Mission San José 应该是最大的一个了,而且 4 个传教场所公用的唯一一个 visitor center 就设在它旁边,难怪大家要选择来这里了(继续盖章)。所谓建筑面积大,其实就是围墙圈起来的土地面积大,里面有大片的草地和少数的树木。

Untitled

在围墙以内,有一座教堂,一栋两层的住宅,以及沿着围墙而建的一圈平房。话说当年西班牙传教士在这里的时候,就是住在那栋两层的住宅里。他们在教堂里举行宗教仪式,然后闲时用住宅前面的小花园种菜。围墙以外还有个磨坊,使用 San Antonio River 的水力驱动。

Untitled

当印第安人来到传教场所后,传教士就会让他们住到围墙里面来,并让他们沿着围墙扩建平房,然后教育他们接受教会的同时接受 18 世纪先进的生活方式。传教士会为印第安人提供一定的帮助,但印第安人的粮食还是要靠劳动来获得的。当印第安人完全融入西班牙的宗教文化后,一个传教场所的使命也就完成了,之后传教士会把四周的田地划分给印第安人,让他们按照新的生活方式继续独立生活下去。

Untitled

这听起来好像很美好的样子,假如不是墨西哥宣布脱离西班牙独立,然后德州宣布脱离墨西哥独立的话,或许 18 世纪之后的日子也就这样了。不过后来爆发战争后,西班牙传教士和印第安人怎么样都没有人关心了,大家更关心的是德州到底是谁的国土。

在离开 Mission San José 后,我们回到了市区,去参观 Spanish Governor’s Palace。这里曾经是西班牙驻军首领住的地方,所以保留了 18 世纪西班牙的建筑方式。尽管这里现在也就是市区当中的一栋小房子,当年这栋小房子门前的广场上曾经有上百的西班牙驻军,负责保卫周边区域包括上述 5 个传教场所。

Untitled

作为一栋历史建筑,当年是烧柴取暖的显然依然如此。里面总共有 9 个房间,分别是不同时期扩建的,内部的家具和装饰都有所体现。为了展示当时的墙壁建造材料,这里还专门有一面墙挖开了一部分然后用玻璃挡上,好让大家观察墙壁的横断面。

离开 Spanish Governor’s Palace 之后,我们的最后一个景点是 Alamo。尽管这里曾经也是一个传教场所,但它同时还拥有军事和历史意义,这对德州独立来说尤其重要。话说当年 Mission San Antonion 在完成其传教使命以后就被遗弃了,后来墨西哥军队占领了它,并且开始使用 Alamo 这个名字。再后来德州军队从墨西哥军队手上把 Alamo 抢了过来,并且留下少数士兵把手,结果导致守军被墨西哥军队围城,最后沦陷并且被屠城。然而屠城一事激发起德州人民和美国人民的斗志,反而帮助了德州赢得独立战争。

Untitled

现在的 Alamo 展览包括两部分。一部分是在教堂内的,展示当年西班牙传教的历史;另一部分在教堂背后的庭院里,展示西班牙、墨西哥、德州、美国交替的历史。有很多人还真的很耐心地在那里看,难怪说 Alamo 是德州最热门地景点了。由于我绕圈的方向不对,所以我是反着时间轴方向看的,导致我只是粗略看了一边就算了。

晚餐是在酒店大堂的酒吧吃寿司。这听起来很奇怪,但酒店附属的酒吧确实有寿司吃,而且味道还可以。我们 6 个人要了 6 份一模一样的东西,就是寿司饭上面放一层海鲜。考虑到 San Antonio 离墨西哥湾还是有点距离的,我们就不对这里的海鲜有什么特别的期望了。

Untitled

这是我们在 San Antonio 的最后一个晚上了,之后我们就要出发去 Austin 和 Dallas 了。关注后继行程的话欢迎订阅我的博客哦。