文章转载自 Google Research,是同名论文的概要说明。原文地址:http://googleresearch.blogspot.com/2014/10/all-news-thats-fit-to-read-study-of.html。译文之后我附了本文原文、初始论文等信息。原文发表于当地时间 2014 年 10 月 9 日。

作者:
Chinmay Kulkarni,斯坦福大学在读博士,前谷歌实习生
Ed H. Chi,Google Research 科学家

翻译:OrangeCLK orangeclk[at]orangeclk.com

新闻是大家日常信息食粮的一道主菜。和互联网上的其他活动一样,在线新闻阅读正在迅速地演变成一种社交体验。今天的互联网使用者可以看到各种各样来源的新闻推荐,报纸网站让读者可以互相分享新闻文章,餐厅点评网站会展示其他食客的推荐,目前一些社交网络也已经集成了社交新闻阅读器[2]。

[1] All the News That’s Fit to Print 是《纽约时报》老板奥克斯于 1896 年 10 月 25 日提出的口号,于 1897 年 2 月 10 日置于头版左上角,意指《纽约时报》刊印一切值得刊印的新闻。后来纽约时报又针对其网站提出了 All the News That’s Fit to Click。本文标题为 All the News That’s Fit to Read,化用了这一句式。参考来源:http://www.nytco.com/who-we-are/culture/our-history/#1910-1881-timeline http://en.wikipedia.org/wiki/The_New_York_Times http://www.businessinsider.com/2007/10/nyt-all-the-new——译者注。
[2] 例如 Facebook 推出了新闻阅读应用 Paper。——译者注。

新闻文章的推荐信息和赞许信息可以来自计算机与算法、发表与聚合内容的商业公司、朋友、甚至完全陌生的人。这些解释信息(即为什么这些文章会呈现给你,也就是我们所说的“标注”)会怎样影响用户的阅读选择?新闻传播中的在线社群标注已经无所不在,但是我们对标注的理解却惊人地少,用户会怎样响应这些标注,怎样才能把标注富有成效地推荐给用户?

《一切适于阅读的新闻:关于新闻阅读社群标注的研究》发表于 2013 年美国计算机学会人机交互专家协会的计算机系统人类因素研讨会[3]上,是2013年谷歌论文影响力榜单中的重点论文。在这篇论文中,我们发表了两项实验的结果。截至目今,大家普遍认为社交标注是一种提升用户参与度的常见简单方法。但实验显示,社交标注一点也不简单,不同的社交标注说服力有很大差别,提升用户参与度的能力也有很大差别。

[3] ACM 是美国计算机学会;SIGCHI 是人机交互专家协会,即 Special Interest Group on Computer-Human Interaction;译者这里将 Conference on Human Factors in Computing Systems 译作“计算机系统人类因素研讨会”——译者注。

新闻文章的不同社交标注

当用户看到的内容未经个性化定制也不是来自于他们的社交网络时,他们会怎样使用社交标注?这是第一项实验关注的问题。一个典型的情境是用户正在浏览他们尚未登录的社交网络。我们把同样的一组新闻文章展示给参与研究的志愿者,给这些新闻标上来自陌生人、计算机程序、虚构品牌公司的社交标注。此外,我们还告诉志愿者他们的名字是否会出现在他们阅读的文章旁边,作为标注呈现给其他实验参与者(也就是“记录”或“不记录”他们的阅读行为)。

令人吃惊的是,在这个“尚未登录”的情境下,不知名商业公司和计算机的标注要显著地比陌生人的社交标注有说服力。这项实验结果揭示了标注在信息推荐方面的潜力,哪怕这些标注是由用户从不知晓的品牌和推荐算法产生的。实验也表明,在用户尚未登录的情境下,计算机和商业公司的标注很有价值。实验还显示,无论标注是哪种类型,开启“记录”功能后,用户对新闻文章的总点击量会变少。这表明被试者知道他们在社交阅读应用中会展现给其他用户的怎样形象。

第一项实验说明陌生人的标注不如计算机和品牌商有说服力,那么好友的标注效果如何呢?第二项实验以谷歌用户为被试者,考查他们在登录后的情境中对好友的标注有怎样的反应,探究个性化的赞许信息能否帮助人们发现、选择可能更有意思的内容。

可能并不令人多么吃惊,实验结果显示好友标注很有说服力,提升了用户对文章选择的满意度。有趣的是,在实验后的访谈中,我们发现,影响志愿者是否阅读文章的标注主要有三类:第一类,标注者和用户的社交亲密度超过了阈值;第二类,标注者有新闻文章相关领域的专业知识;第三类,标注给被推荐的文章提供了附加信息。这说明社会情境和个性标注共同作用,总体上提升了用户体验。

有待研究的一些问题包括:高亮标注中的专业知识是否能提升用户体验;社交亲密度的阈值是否可以通过算法确定;聚合标注(例如,“110名用户点了赞”)是否能提升用户参与度。我们希望下一步的研究能够解释使得社交推荐能够提供合理解释 为什么用户应该关注 解释标注呈现的更多微妙之处。

原文:All the News that’s Fit to Read: A Study of Social Annotations for News Reading

Posted by Chinmay Kulkarni, Stanford University Ph.D candidate and former Google Intern, and Ed H. Chi, Google Research Scientist

News is one of the most important parts of our collective information diet, and like any other activity on the Web, online news reading is fast becoming a social experience. Internet users today see recommendations for news from a variety of sources; newspaper websites allow readers to recommend news articles to each other, restaurant review sites present other diners’ recommendations, and now several social networks have integrated social news readers.

With news article recommendations and endorsements coming from a combination of computers and algorithms, companies that publish and aggregate content, friends and even complete strangers, how do these explanations (i.e. why the articles are shown to you, which we call “annotations”) affect users’ selections of what to read? Given the ubiquity of online social annotations in news dissemination, it is surprising how little is known about how users respond to these annotations, and how to offer them to users productively.

In All the News that’s Fit to Read: A Study of Social Annotations for News Reading, presented at the 2013 ACM SIGCHI Conference on Human Factors in Computing Systems and highlighted in the list of influential Google papers from 2013, we reported on results from two experiments with voluntary participants that suggest that social annotations, which have so far been considered as a generic simple method to increase user engagement, are not simple at all; social annotations vary significantly in their degree of persuasiveness, and their ability to change user engagement.

News articles in different annotation conditions

The first experiment looked at how people use annotations when the content they see is not personalized, and the annotations are not from people in their social network, as is the case when a user is not signed into a particular social network. Participants who signed up for the study were suggested the same set of news articles via annotations from strangers, a computer agent, and a fictional branded company. Additionally, they were told whether or not other participants in the experiment would see their name displayed next to articles they read (i.e. “Recorded” or “Not Recorded”).

Surprisingly, annotations by unknown companies and computers were significantly more persuasive than those by strangers in this “signed-out” context. This result implies the potential power of suggestion offered by annotations, even when they’re conferred by brands or recommendation algorithms previously unknown to the users, and that annotations by computers and companies may be valuable in a signed-out context. Furthermore, the experiment showed that with “recording” on, the overall number of articles clicked decreased compared to participants without “recording,” regardless of the type of annotation, suggesting that subjects were cognizant of how they appear to other users in social reading apps.

If annotations by strangers is not as persuasive as those by computers or brands, as the first experiment showed, what about the effects of friend annotations? The second experiment examined the signed-in experience (with Googlers as subjects) and how they reacted to social annotations from friends, investigating whether personalized endorsements help people discover and select what might be more interesting content.

Perhaps not entirely surprising, results showed that friend annotations are persuasive and improve user satisfaction of news article selections. What’s interesting is that, in post-experiment interviews, we found that annotations influenced whether participants read articles primarily in three cases: first, when the annotator was above a threshold of social closeness; second, when the annotator had subject expertise related to the news article; and third, when the annotation provided additional context to the recommended article. This suggests that social context and personalized annotation work together to improve user experience overall.

Some questions for future research include whether or not highlighting expertise in annotations help, if the threshold for social proximity can be algorithmically determined, and if aggregating annotations (e.g. “110 people liked this”) help increases engagement. We look forward to further research that enable social recommenders to offer appropriate explanations for why users should pay attention, and reveal more nuances based on the presentation of annotations.

论文下载地址

http://pan.baidu.com/s/1o6ofZq6

论文 BibTex 代码:

1
2
3
4
5
6
7
8
@inproceedings{41200,
title = {All the news that’s fit to read: a study of social annotations for news reading},
author = {Chinmay Kulkarni and Ed H. Chi},
year = 2013,
URL = {http://dl.acm.org/citation.cfm?id=2481334},
booktitle = {In Proc. of CHI2013},
pages = {2407-2416}
}

引言

本文会从头到尾讲 Netflix 公司的故事。由于我精力有限,无力回头再去查找当时的第一手资料,核实重大事件发生的时间地点,只能想到哪写到哪,写成随笔的形式。不过我保证每一件事实都是我曾经在公开报道中看过的,在记忆误差范围内都可以保证正确。如果你有兴趣把它改写成严肃的双语论文,欢迎联系我,我们一起来考究材料,补充内容。

网络上的 DVD 租赁店

小时候你们家有没有租过碟子?从小店铺那里租一张电影 VCD,一家人欢聚一个周末。光盘上通常会有很多划痕,需要机器有超强的纠错能力才能看。不然不仅屏幕卡顿,扬声器还会发出粉笔在黑板上划过的惊悚声。现在这些店铺已经很难找到了,昨晚我逛街找吃的,看到一家音乐 CD 店,也没有出租服务了。

Netflix 最早就是一个 DVD 租赁公司,不过它不是普通的小铺子。

那个时候美国有一家电影出租店,叫 Blockbuster,中文名百视达。像沃尔玛一样,是个遍布全美的连锁店。不过它不卖东西,只出租电影,有VHS录影带,也有DVD光盘。用户上班下班,走过路过,付订金租电影,看完了再还回来结账。它的规模很大,片库储藏也很丰富,是这个领域的霸主。

在那个.com 飞速发展的时期,很多新兴公司利用互联网成长起来,Netflix 就是其中一个。

Netflix 颠覆了这个行业。

Netflix 一家线下连锁店都没有,它就像是电影租赁界的亚马逊,是电子商务的先驱。Netflix 是一个活在网络上的公司。想要租 Netflix 的 DVD,用户首先要在它的网站上注册一个账号,然后付费订阅它的服务。收费分几档,付的费用越高,你手头能同时持有的影片越多。举个例子,假如你买了同时持有 n部电影那一档的服务,然后你会得到一个心愿单,你可以把自己想看的电影全都写进去,Netflix 会把心愿单的前 n 部电影寄到你家里。当你把看完的影片寄回,它就会按照心愿单顺序继续给你寄新的影碟。费用是按月收的,只要你看得够快,一份价钱,一个月想看多少电影就可以。

Netflix 把碟片装在招牌式的红色信封中,人性化的是,它还会随信再寄一封回邮信封,用户可以把碟片放到回邮信封中寄回 Netflix 公司。这不仅仅是一个人性化的设定,更是一个高明的商业手段。DVD 机和 DVD 光碟是家庭客厅中的常见道具,当用户的家中有访客来访时,他们就很可能会看到被主人摆放在客厅的 Netflix 信封。当一个人在几个不同朋友的家中都看到这个耀眼的红色信封后,他很可能就会向朋友咨询有关事宜,成为 Netflix 的新用户。回邮信封这个给顾客提供体贴服务的道具,同时也成为了 Netflix 最好的广告。口耳相传的良好口碑超过一切大众传媒的狂轰滥炸。当用户在家庭情景中看到这个红色信封时,家庭的主人会成为最好的推销员,用户可以详尽地了解 Netflix 的工作流程和服务细节,他可以亲眼见证 Netflix 给主人带来的优秀体验。当他成为 Netflix 的新客户时,他已经了解 Netflix 的工作方式了,他会成为一个“熟客”,帮助 Netflix 高效完成交易。客户服务+商业广告+市场教育,完美的商业手段。

相对于线下 DVD 连锁出租店,Netflix 的优势太多了。

  • 价格低廉。9.99 美元就可以购买 Netflix 一个月的服务,想看多少电影都可以。用户如果去百视达租电影,每部电影视电影热门程度需要三到五美元。以上价格均含税。
  • 没有滞纳金。只要他的付费服务没有到期,Netflix 的用户永远都可以持有 n 部电影。百视达则不同,视具体情况,有的可以租 5 夜,有的可以租 24 小时,一旦过期再还,需要交滞纳金。
  • 影片资源丰富。不管百视达总的影片种类有多么丰富,每家线下连锁店能出租的影片总是有限的。Netflix 就没有这个问题,只要库存里有某部影片,就一定能寄给用户。在线商店的长尾服务总是要比线下好,如今这个规律依然普遍成立。
  • 方便。不需要去门店排队,不需要去门店还碟,不需要对着琳琅满目的货架仔细寻找、挑选。只需要键入影片名,搜索,下单,碟片就会寄到家里,看完之后只要投回邮筒,自己心愿单上的下一部影片就会寄来,完全自助、自动。
  • 经营成本低。Netflix 不需要支付线下的房租,不需要给每一家连锁店购买大量的碟片库存,不需要招聘大量的服务人员。Netflix只需要一些仓库、一些服务器、不多的员工,这就足够了。
  • 易于管理。Netflix 不需要计较每一笔交易的订金、租金、出租时间、滞纳金,它只需要在IT后端上关注每位用户的租碟状态和心愿单就可以了。它也不必担心门店的失窃、员工的失控等问题,机构空前精简,管理空前简单。

如是,Netflix不断发展,百视达濒临倒闭。

近来读叶嘉莹先生的《人间词话七讲》,发觉第六讲中先生对“无奈朝来寒雨晚来风”一句解析有误。叶先生说中国古诗词中但凡朝暮对举,就是朝朝暮暮;风雨对举,就是风风雨雨[1],其实道理并非如此。比如朝秦暮楚,就不是说朝朝暮暮追随秦楚;朝三暮四,也不是说朝朝暮暮三三四四。

“朝来寒雨晚来风”用到了汉语中的一种修辞——互文。百度百科的解释是:“互文,也叫互辞,是古诗文中常采用的一种修辞方法。古文中对它的解释是:‘参互成文,含而见文。’具体地说,它是这样一种互辞形式:上下两句或一句话中的两个部分,看似各说两件事,实则是互相呼应,互相阐发,互相补充,说的是一件事。由上下文义互相交错,互相渗透,互相补充来表达一个完整句子意思的修辞方法。”我认为基本讲得在理。

譬如,“烟笼寒水月笼沙”是说烟与月笼罩水与沙,“秦时明月汉时关”说的是秦汉时的关山,“将军白发征夫泪”说的是将军和征夫的白发与泪。呃写到这里发现我举的例子竟然和百度百科一样,只能说这几句互文过于经典。

为什么要用互文呢?互文可以用精简的句子表达丰富的含义。如果对于互文,我们只理解了字面意思,那很可能是狭隘而说不通的。比如“东市买骏马,西市买鞍鞯,南市买辔头,北市买长鞭。”难道真的那么巧每个超市买一样?其实就是说木兰君到处逛街终于把东西买到了,用对称铺叠的句子写出来,不仅文字精简,而且可以增加诗歌的美感。再比如《项脊轩志》“东犬西吠”,高中语文书上说“东家的狗(听到西家的声音)就对着西家叫。”这脑补得实在有些厉害,也根本讲不通。归有光其实只是说到处都有狗叫而已。《琵琶行》“主人下马客在船”,说的是主人和客人一起下马上船,如果解释成主人下马,客人在船上,那就说不通了。如果是主人送客而主人并没有上船的话,那何来“移船相近邀相见,添酒回灯重开宴”?如果主人是牵马去江边迎客,那又何来序文中“送客湓浦口”一说?不能读出互文,很多作品就理解不了。

新课标人教版中学语文书中对于古诗文的注疏,是有很多问题的。

[1] “中国的诗词凡是在对举的时候,朝暮的对举,就是朝朝暮暮;风雨的对举,就是雨雨风风。”——叶嘉莹《人间词话七讲》第六讲,139页,北京大学出版社。

我中学用过很多 Linux 发行版,在我中学尝试的诸多版本中,印象最好,使用最长的是国人制作的 Magic Linux。

我今天发现,这个版本居然还在维护,而且在今年7 月发出了 6 年来的第一次版本更新:http://www.magiclinux.org/ 这让我非常怀念,也非常开心。

Magic Linux 是一款国人制作的 Linux 发行版,对中国人非常友好。在那个上古年代,Linux 发行版对中文的支持很差,字体错乱,还经常有乱码。那个时候 Red Hat 还没有启用yum,我还没接触 Debian 系,不知道 apt。软件仓库这件事无从谈起,rpm 包也常不兼容,每次安装软件,都会遭遇巨大的痛苦,不仅要自己编译、make,还要读懂各种报错信息,去 sourceforge 下载.lib .so 依赖包,再把它们安装到合适的位置,简直不能再痛苦。那时的国际化 Linux 版本也很少有为中国人订制的软件,办公软件水土不服,不少工具汉化不完整。音频视频播放器这种桌面常见应用用起来也不方便,因为 Linux 的版权限制,MP3 等编码的解析器是不能捆绑在发行版里的,需要用户自己去安装。而那时候大多数系统上并没有软件仓库,安装难度可想而知。当时 rmvb 和 rm 这种古董级格式还非常流行,由于这个格式是 reaplayer 的私家标准,尤其难办。

Magic Linux 把这些问题都改进了,在 2005-2006 年那个年代,它就提供了一个软件管理程序,虽然没有 apt 那么完备,但是已经很有软件仓库的影子。即便我要安装仓库中没有的程序,它也能以更友好的方式向我提示依赖。Magic Linux 提供了 C++写成的 Eva,可以在 Linux 系统下使用 QQ,是我在 Linux 下使用时间最长的第三方QQ客户端。QQ 为了封禁这些第三方客户端,会经常修改通信协议,Eva 坚挺了很久,使用体验也非常好,性能卓越,背后一定有很多贡献者付出了大量的心血。最终 Eva 还是没能坚持开发下来,彻底登不了 QQ 了。音频视频播放器的解码器也有很好的傻瓜式解决方案。令人惊喜的是,它还自带了一个游戏大厅,像腾讯游戏大厅那样可以打牌玩,可惜我登录过几次,里面根本就没有人,码农真是苦逼啊。顺便吐槽一句,QQ 现在越来越慢了,2008 年的那个 QQ 呢?!

国人开发的 Linux 版本也有不少,包括中科院专资开发的红旗 Linux。我用过红旗 4.0 和红旗 5.0,比 Magic Linux 差多了,桌面应用和开发程序都很不方便,倒是外形是我见过的 Linux 中长的最像 Windows 的,连“开始”菜单都 cosplay 了一个,简直哭瞎。现在这个项目已经倒闭,项目员工集体举牌讨薪,我说当年大家如果用点功,也不至于是这个结局…… Magic Linux 要比其他官方赞助的版本优秀多了。

2008 年以后,似乎团队不再维护 Magic Linux,系统从内核到各种库都得不到更新,我也从此改用 Ubuntu8.04 和 Ubuntu8.10,开启了 apt 和“新立得”的幸福生活。

如今“新立得”也早已不见踪迹,Ubuntu 简直变得和 Windows 一样好用。时代真是在进步,当码农越来越容易了。

附 Eva 开源项目 github 地址:https://github.com/MagicGroup/eva

1947 年的铜陵汀洲,是个老师很难生存的地方。

当地家长都很有文化,总嫌弃老师水平差。老师一旦教错,家长就要起哄。

邓老师要靠教书的薪水养足一双儿女,还要天天受家长的学术质疑,内外交困,请了辞。他后来的人生看起来很悲惨,丢了教职以后,一路乘船北上,赶赴妻子的老家天津讨生活。路上又丢了一个孩子。他很怀念故乡,多年以后,回到了铜陵。为了回家,他在路中变卖了所有的行李,回家时已经一无所有,后来怎么样我不知道。

邓老师辞职后留下了一个缺,而外公当时在家赋闲,小学校长听说了,便要请外公去执教。

定教职是需要面试的,校长听课,外公试讲。第一节课讲六年级语文,第二节课讲五年级数学。讲完,午饭,饭中校长就双手捧过了高级教师聘书。在传统的年代,人们的礼数比现在更为繁复,当场录用为高级教师,并且双手捧过聘书,是很大的尊重和肯定。十年前,九十多岁的老校长还给外公送了一幅字,苏轼的《水调歌头》《念奴娇》两首。就一直挂在墙上,外公也不装裱,宣纸已经发黄。我无数次看过这幅字,写得非常漂亮,可惜我手头没有好照片。如今这幅字已经被新的字画盖住,拍起来很麻烦。

高级教师就可以当班主任了,可是这班主任并不好当,因为 1947 年的铜陵汀洲,是个老师很难生存的地方。

有一回外公批国文书法作业,给一位学生的“達”字画了一个圈,以示好评。当时大家还是都用繁体字的,“達”字,是“幸”字下加一横再添走之。可是这位学生却写成了“幸”字加走之,少了一横。家长一见,非常生气,说白字先生乱教书,不仅看不出错字,还给错字画圈。

外公脾气很大,没多久就跟校长说:“我不干了。”校长觉得纳闷,“你不是做得好好的吗?怎么突然就不干了?”外公说:“我给学校丢了人,呆不住了,学生家长不满意。”校长说:“没哪个跟我讲过啊。”外公这才一五一十把事情说了。原来,柳宗元写书法的时候,就给“達”少写了一横,所以书法上少一横不能算错,顶多是个异体字。现代人写简化字写久了不熟悉,在当时这都是广为接受的概念。

于是校长不答应了,他把这件事告诉了乡议员,说这个家长无理取闹。乡议员说这成何体统,要求家长必须检讨,要办酒道歉。那个时候,乡中给老师道歉,要办酒席,放爆竹,让全乡知晓,当众致歉,以示尊师。家长一听乡议员说是他自己搞错了,立马表示道歉,筹备酒席,毫不含糊。那一天外公没去酒席,因为他自认为脾气不好,如果去了一定会教育那位家长,溯清“達”字源流,这样两边都很尴尬。所以当天由校长代行受礼,放爆竹,设酒席,宴同乡,学校名声得以昌隆,乡民对外公也就服气了。

1947 年的铜陵汀洲,是个很热爱知识文化而又不叶公好龙的地方。

V 社大良心,制作了 Steam 这个跨平台游戏中心,提供的 source 引擎也是跨平台的。所以,我们只需要在 Ubuntu 软件仓库中安装 Steam ,就可以玩 Steam 中的游戏啦。

在软件仓库里,我们先要点击“购买”按钮购买 Steam,然后切入 Ubuntu One 的认证页面进行认证,认证完毕,就可以以 0 元的价格买入 Steam 了,此时下载安装,和普通软件包没有区别。

启动 Steam 后,找到 Dota, 下载安装。启动 Dota 后,发现只能连接到世界服,有东南亚区、欧洲区、美国区等等。至于中国区,你懂的。为了取得更快的连接速度和更好的游戏体验,我们需要连接到完美世界代理的国服。在 Steam 中右击 Dota2,点击“属性”,“设置启动选项……”,在弹出的对话框中输入“ -perfectworld steam ”。再启动 Dota2 时,连接的就是完美世界了。我体验了一把,感觉和 Windows7 下运行没什么区别,一样一样的。赞!

由于 expressjs 自带的 body-parser 默认不能解析 ‘text/xml’ 的内容,我曾考虑依照 body-parser 本身的结构自己写一个工业级的 ‘text/xml’ 解析器。后来才发现,其实可以通过调用 body-parser 的 text() 函数作为中间件,对 ‘text/xml’ 解析,只要在赋入的选项中添加 type: ‘text/xml’ 属性即可。我自己写的 ‘text/xml’ 解析器算是个副产品吧,有时间测试后也会发表,比直接调用 bodyParser.text() 在功能上要强一些,流程上也统一一些,安全性也高一些。现有的软件库中我还没有看到具有类似功能的模块。

参考代码 1: expressjs 项目的入口,载入 body-parser 模块并调用 text() 中间件,如果不赋入 ‘text/xml’ 选项,text() 将默认以 ‘text/plain’ 类型执行解析,则会发生类型匹配错误,导致解析无法完成,所以 ‘text/xml’ 选项是必须手动传入的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* Module dependencies.
*/
var express = require('express');
var routes = require('./routes');
var user = require('./routes/user');
var http = require('http');
var path = require('path');
// 载入 body-parser 模块,expressjs自带的部分没有 text() 函数。
var bodyParser = require ('body-parser');
var app = express();
// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
// 将请求体中的 xml 解析为字符串。
app.use(bodyParser.text({type: 'text/xml'}));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
// development only
if ('development' == app.get('env')) {
app.use(express.errorHandler());
}
app.get('/', routes.index);
app.get('/users', user.list);
http.createServer(app).listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});

参考代码 2:通过 xml2js 将解析出的 xml 字符串解析为 json ,并存入解析完成后的请求体中供其他代码调用。

1
2
3
4
5
6
7
8
9
10
11
12
{parseString} = require 'xml2js'
exports.index = (req, res) ->
parseString req.body, (err, result) ->
if err
err.status = 400
res.end 'error'
return
else
req.body = result
console.log req.body
res.end 'success'
return

由于需要在 expressjs 中解析 xml ,而 expressjs 默认无法解析 xml 。所以我打算自己造一个工业级轮子,于是参考了 expressjs 中关于 json 解析方面的代码。代码分析如是。

朴素实现是监听 req 的 data 事件,将读到的数据存储在字符串变量中再进行解析。body-parser 的实现与此有不少区别。首先,代码中有很多校验信息,要根据请求头中的内容对请求体做类型、长度、字符编码等校验,以此提高安全性;其次,代码都是通过调用 raw-body 模块中的 getRawBody(stream, options, callback) 来解析请求体的,而不是直接监听 data 事件进行操作。getRawBody 函数会做请求体校验、异常处理、管道卸载等工作,比朴素的做法安全很多。 getRawBody 函数也可以完成解码功能。

body-parser 的文档和代码见: https://github.com/expressjs/body-parser 。中文注释为我的注析。这里只解析 index.js 及其调用的内容, lib/types/ 下的其他文件结构类似,忽略不析。

index.jslink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/*!
* body-parser
* Copyright(c) 2014 Douglas Christopher Wilson
* MIT Licensed
*/
/**
* Module dependencies.
*/
// deprecate 是一个用来显示“不建议”消息的工具,可以用来警告用户不要使用
// 那些不建议使用的函数或模块。详见:
// https://github.com/dougwilson/nodejs-depd
var deprecate = require('depd')('body-parser')
var fs = require('fs') // 载入文件处理模块,nodejs 核心模块。
// 载入路径处理模块,nodejs 核心模块,参考:
// http://nodejs.org/api/path.html
var path = require('path')
/**
* Module exports.
*/
exports = module.exports = deprecate.function(bodyParser,
'bodyParser: use individual json/urlencoded middlewares')
/**
* Path to the parser modules.
*/
var parsersDir = path.join(__dirname, 'lib', 'types')
/**
* Auto-load bundled parsers with getters.
*/
//遍历 /lib/types 目录下的文件。
fs.readdirSync(parsersDir).forEach(function onfilename(filename) {
if (!/\.js$/.test(filename)) return //如果文件不是 .js 文件,返回。
var loc = path.resolve(parsersDir, filename) // 提取文件的绝对位置。
var mod
var name = path.basename(filename, '.js') // 提取文件的基础名。
function load() {
if (mod) {
return mod
}
// 载入位于loc位置的文件,由于load函数可能在其他地方被调用,所以这里
// loc 记录的是绝对位置,增强代码的可移植性。
return mod = require(loc)
}
// 给 exports 添加一个新的属性 name,代码还设置了 name 属性的三个属性
// 描述符。关于 Object.defineProperty ,详见:
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
Object.defineProperty(exports, name, {
configurable: true, // 使 name 属性可以被改变。
enumerable: true, // 使 name 属性可枚举。
get: load // 使 name 属性有 getter 方法。
})
})
/**
* Create a middleware to parse json and urlencoded bodies.
*
* @param {object} [options]
* @return {function}
* @deprecated
* @api public
*/
function bodyParser(options){
var opts = {}
options = options || {} // 初始化 options 。
// exclude type option
for (var prop in options) {
if ('type' !== prop) {
opts[prop] = options[prop]
}
}
var _urlencoded = exports.urlencoded(opts)
var _json = exports.json(opts)
return function bodyParser(req, res, next) {
_json(req, res, function(err){ // 将请求体解析为 json 。
if (err) return next(err);
// 如果请求体不是 json ,则将其解析为 urlencoded 内容。
_urlencoded(req, res, next);
});
}
}
lib/types/json.jslink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/*!
* body-parser
* Copyright(c) 2014 Jonathan Ong
* Copyright(c) 2014 Douglas Christopher Wilson
* MIT Licensed
*/
/**
* Module dependencies.
*/
// bytes 模块可以在字节度量的数字表示和字符串表示之
// 间互相转换。例如:
// bytes('1kb') 结果为 1024 ;
// bytes('2mb') 结果为 2097152 ;
// bytes('1gb') 结果为 1073741824 ;
// bytes(1073741824) 结果为 1gb ;
// bytes(1099511627776) 结果为 1tb 。
// 详见 github 上的 visionmedia/bytes.js 项目:
// https://github.com/visionmedia/bytes.js
var bytes = require('bytes')
var read = require('../read')
// expressjs 自带的媒体解析模块,详见:
// https://github.com/expressjs/media-typer
var typer = require('media-typer')
// type-is 是 expressjs 自带的类型判断模块,详见 github 上的 expressjs/
// type-is 项目: https://github.com/expressjs/type-is
var typeis = require('type-is')
/**
* Module exports.
*/
module.exports = json
/**
* RegExp to match the first non-space in a string.
*/
var firstcharRegExp = /^\s*(.)/
/**
* Create a middleware to parse JSON bodies.
*
* @param {object} [options]
* @return {function}
* @api public
*/
function json(options) {
options = options || {}
var limit = typeof options.limit !== 'number'
? bytes(options.limit || '100kb')
: options.limit // 设置解析请求体长度上限。
var inflate = options.inflate !== false // 设置是否要将压缩的请求体解压。
var reviver = options.reviver // 传递给 JSON.parse() 的参数。
var strict = options.strict !== false // 设置是否只解析对象和数组。
var type = options.type || 'json' // 设置解析的请求体内容类型。
var verify = options.verify || false // 设置请求体内容验证函数。
if (verify !== false && typeof verify !== 'function') {
throw new TypeError('option verify must be function')
} // 请求体的内容验证函数必须是一个函数。
function parse(body) {
if (0 === body.length) {
throw new Error('invalid json, empty body')
}
if (strict) {
var first = firstchar(body) // firstchar 函数的定义在116行。
if (first !== '{' && first !== '[') {
throw new Error('invalid json')
}
}
// 解析 body ,详见:
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
return JSON.parse(body, reviver)
}
// 返回解析函数作为 expressjs 的中间件。
return function jsonParser(req, res, next) {
// req._body 标记着请求体是否已被解析,若 req._body 为 true ,则请求体
// 已被解析。
if (req._body) return next()
req.body = req.body || {}
// 检测请求体是否与 type 类型匹配。
if (!typeis(req, type)) return next()
// RFC 7159 sec 8.1
var charset = typer.parse(req).parameters.charset || 'utf-8'
if (charset.substr(0, 4).toLowerCase() !== 'utf-') {
var err = new Error('unsupported charset')
err.status = 415
next(err)
return
}
// read
read(req, res, next, parse, {
encoding: charset,
inflate: inflate,
limit: limit,
verify: verify
})
}
}
/**
* Get the first non-whitespace character in a string.
*
* @param {string} str
* @return {function}
* @api public
*/
function firstchar(str) {
if (!str) return ''
// 见第 40 行,匹配字符串中第一个非空字符。
var match = firstcharRegExp.exec(str)
return match ? match[1] : ''
}
lib/read.jslink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/*!
* body-parser
* Copyright(c) 2014 Douglas Christopher Wilson
* MIT Licensed
*/
/**
* Module dependencies.
*/
// 将流中的所有内容载入为 buffer 或字符串。参考:
// https://github.com/stream-utils/raw-body
var getBody = require('raw-body')
// 互相转换 buffer 与 js 字符串。参考 github:
// https://github.com/ashtuchkin/iconv-lite
var iconv = require('iconv-lite')
// 使得程序可以在退出时执行一个回调函数。参考:
// https://github.com/jshttp/on-finished
var onFinished = require('on-finished')
// expressjs 自带的媒体解析模块,详见 github:
// https://github.com/expressjs/media-typer
var typer = require('media-typer')
// nodejs 核心模块,提供数据压缩和解压功能。参考:
// http://nodejs.org/api/zlib.html
var zlib = require('zlib')
/**
* Module exports.
*/
module.exports = read
/**
* Read a request into a buffer and parse.
*
* @param {object} req
* @param {object} res
* @param {function} next
* @param {function} parse
* @param {object} options
* @api private
*/
function read(req, res, next, parse, options) {
var length
var stream
// flag as parsed
req._body = true
try {
stream = contentstream(req, options.inflate) // 见第 129 行。
length = stream.length
delete stream.length
} catch (err) {
return next(err)
}
options = options || {} // 初始化 options 。
options.length = length
var encoding = options.encoding !== null
? options.encoding || 'utf-8'
: null
var verify = options.verify
options.encoding = verify
? null
: encoding
// read body
getBody(stream, options, function (err, body) {
if (err) {
if (!err.status) {
err.status = 400
}
// read off entire request
stream.resume()
onFinished(req, function onfinished() {
next(err)
})
return
}
// verify
if (verify) {
try {
verify(req, res, body, encoding)
} catch (err) {
if (!err.status) err.status = 403
return next(err)
}
}
// parse
try {
body = typeof body !== 'string' && encoding !== null
? iconv.decode(body, encoding) // 将请求体解码为 js 字符串。
: body
req.body = parse(body)
} catch (err) {
if (!err.status) {
err.body = body
err.status = 400
}
return next(err)
}
next()
})
}
/**
* Get the content stream of the request.
*
* @param {object} req
* @param {boolean} [inflate=true]
* @return {object}
* @api private
*/
// inflate 表示是否给数据解压缩。
function contentstream(req, inflate) {
// req.headers 是 http 请求的请求头,详细参数见:
// http://www.w3cschool.cc/http/http-header-fields.html
// identity 代表没有压缩编码,见 RFC 7231 , sec 3.1.2.2 。
var encoding = req.headers['content-encoding'] || 'identity'
var err
var length = req.headers['content-length'] // 见 RFC 7230 , sec 3.3.2 。
var stream
if (inflate === false && encoding !== 'identity') {
err = new Error('content encoding unsupported')
err.status = 415
throw err
}
// 参考 zlib 文档: http://nodejs.org/api/zlib.html 。
switch (encoding) {
case 'deflate':
stream = zlib.createInflate()
req.pipe(stream)
break
case 'gzip':
stream = zlib.createGunzip()
req.pipe(stream)
break
case 'identity':
stream = req
stream.length = length
break
default:
err = new Error('unsupported content encoding')
err.status = 415
throw err
}
return stream
}
lib/types/urlencoded.jslink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/*!
* body-parser
* Copyright(c) 2014 Jonathan Ong
* Copyright(c) 2014 Douglas Christopher Wilson
* MIT Licensed
*/
/**
* Module dependencies.
*/
var bytes = require('bytes')
var deprecate = require('depd')('body-parser')
var read = require('../read')
var typer = require('media-typer')
var typeis = require('type-is')
/**
* Module exports.
*/
module.exports = urlencoded
/**
* Cache of parser modules.
*/
var parsers = Object.create(null)
/**
* Create a middleware to parse urlencoded bodies.
*
* @param {object} [options]
* @return {function}
* @api public
*/
// 详见: https://github.com/expressjs/body-parser#bodyparserurlencodedoptions 。
function urlencoded(options){
options = options || {};
// notice because option default will flip in next major
if (options.extended === undefined) {
deprecate('undefined extended: provide extended option')
}
var extended = options.extended !== false // 是否采用 qs 模块解析 url 编码。
var inflate = options.inflate !== false
var limit = typeof options.limit !== 'number'
? bytes(options.limit || '100kb')
: options.limit
var type = options.type || 'urlencoded'
var verify = options.verify || false
if (verify !== false && typeof verify !== 'function') {
throw new TypeError('option verify must be function')
}
// 选择解析器。
var queryparse = extended
? extendedparser(options)
: simpleparser(options)
function parse(body) {
return body.length
? queryparse(body)
: {}
}
return function urlencodedParser(req, res, next) {
if (req._body) return next();
req.body = req.body || {}
if (!typeis(req, type)) return next();
var charset = typer.parse(req).parameters.charset || 'utf-8'
if (charset.toLowerCase() !== 'utf-8') {
var err = new Error('unsupported charset')
err.status = 415
next(err)
return
}
// read
read(req, res, next, parse, {
encoding: charset,
inflate: inflate,
limit: limit,
verify: verify
})
}
}
/**
* Get the extended query parser.
*
* @param {object} options
*/
// 利用 qs 模块解析 url ,详见: https://github.com/hapijs/qs 。
function extendedparser(options) {
var parameterLimit = options.parameterLimit !== undefined
? options.parameterLimit
: 1000
var parse = parser('qs')
if (isNaN(parameterLimit) || parameterLimit < 1) {
throw new TypeError('option parameterLimit must be a positive number')
}
if (isFinite(parameterLimit)) {
parameterLimit = parameterLimit | 0
}
return function queryparse(body) {
if (overlimit(body, parameterLimit)) {
var err = new Error('too many parameters')
err.status = 413
throw err
}
return parse(body, {parameterLimit: parameterLimit})
}
}
/**
* Determine if the parameter count is over the limit.
*
* @param {string} body
* @param {number} limit
* @api private
*/
function overlimit(body, limit) {
if (limit === Infinity) {
return false
}
var count = 0
var index = 0
while ((index = body.indexOf('&', index)) !== -1) {
count++
index++
if (count === limit) {
return true
}
}
return false
}
/**
* Get parser for module name dynamically.
*
* @param {string} name
* @return {function}
* @api private
*/
function parser(name) {
var mod = parsers[name]
if (mod) {
return mod.parse
}
// load module
mod = parsers[name] = require(name)
return mod.parse
}
/**
* Get the simple query parser.
*
* @param {object} options
*/
function simpleparser(options) {
var parameterLimit = options.parameterLimit !== undefined
? options.parameterLimit
: 1000
// 调用 nodejs 核心模块 querystring 解析 url 。详见:
// http://nodejs.org/api/querystring.html
var parse = parser('querystring')
if (isNaN(parameterLimit) || parameterLimit < 1) {
throw new TypeError('option parameterLimit must be a positive number')
}
if (isFinite(parameterLimit)) {
parameterLimit = parameterLimit | 0
}
return function queryparse(body) {
if (overlimit(body, parameterLimit)) {
var err = new Error('too many parameters')
err.status = 413
throw err
}
return parse(body, undefined, undefined, {maxKeys: parameterLimit})
}
}

由于甲骨文公司的版权限制,Linux 发行版不能包含 Oracle Java,Ubuntu 也只提供了开源的 OpenJDK。OpenJDK 会带来各种各样的兼容性问题,不推荐部署 Java 开发。甲骨文 Oracle Java 本身既包含 JRE(Java Runtime Environment Java 运行环境)又包含 JDK(Java Development Kit Java 开发套件),我们只需要安装一份 Oracle Java 8,即可满足需要。

对于 Ubuntu 及其衍生版,使用 PPA 软件仓库安装 Oracle Java 8 是最快捷的,在终端中输入命令如下即可完成安装:

1
2
3
sudo apt-get repository ppa : webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java8-installer

下面这条命令可以设置环境变量,将 Oracle Java 8 设置为系统的默认 Java 虚拟机:

1
sudo apt-get install oracle-java8-set-default

完成以上步骤之后你可以通过下面这行命令检验是否安装成功:

1
java –version

如果安装成功,终端会得到如下提示:

1
2
3
java version “1.8.0”
Java (TM) SE Runtime Environment (build 1.8.0–b132)
Java HotSpot (TM) 64-Bit Server VM (build 25.0–b70, mixed mode)

具体显示的版本号可能会略有不同,例如,32 位机器上会显示 32 位虚拟机的相关信息。

新媒体的语言

列夫·曼诺维奇著
OrangeCLK 译
orangeclk[at]orangeclk.com

引言

现状的理论

我奢望人们在 1895 年或者 1897 年,最晚 1903 年,就能够认识到电影出现的重要性,然后对这个新型媒体的产生过程做下完备的记录。记录包括观众访谈会;逐年发展的叙事手法、场景调度、机位设置;对于新生的电影语言和其他与之共生的娱乐活动之间联系的分析等。不幸的是,这些记录都不存在。相反,我们只有些纸媒报道、电影发明人的日记、影片播放流程,还有些其他散落各处的星星点点。

现在我们正在见证一种全新媒体的出现——数字计算机的元媒体。和100年前电影面世不同,我们现在完全能体会到新媒体革命的巨大影响。但我仍然担心,我们留给未来理论学者与历史学者的资料,就如同电影年代留给我们的纸媒报道和播放流程那样,信息量并没有多多少。未来的学者会发现我们这个年代的分析文章已经充分意识到了计算机对文化的重要影响,但是大部分内容都是对未来的预测而不是对现状理论的记录。未来的研究人员会疑惑,为什么我们这个时代的理论学者,已经拥有了大量分析历史文化形态的经验,却没有努力把计算机媒体的符号代码、寻址模式、用户接纳模式记录下来?电影艺术从全景摄影、光学玩具、西洋镜等上个时代的艺术形式中脱颖而出,我们煞费苦心才还原出这段历史。在计算机媒体语言刚刚诞生的此时此刻,已有文化形式的元素正在逐渐介入计算机媒体。这个过程在我们的时代如此清晰,这些元素还都一一可见,还没有融为新的一体,那么我们为什么不尝试去构建一个类似于电影发展史的计算机媒体发展谱系呢?在多媒体界面的图标和按钮还都像油漆未干的油画笔触时,在他们演变为普遍规范并迅速消失淘汰之前,我们的理论学者在哪里?当神秘岛[1]的设计者调试代码,把图片转化为字节,美化每一帧 QuickTime[2]剪辑画面的时候,我们的理论学者在哪里?有一天,网景公司一位二十来岁的程序员吐出口香糖,啜下一小口温暖的可乐。他已经在计算机前连续工作了16个小时,正努力赶上产品开发的截止日期。最终他以满意的文件大小,制作了一小段星星飞过夜空的动画。在这个历史性的时刻,我们的理论学者在哪里?这段动画就是网景网景导航者[3]右上角的标识,在下一版网景导航者发布之前,它就是世界上观众最多的动画短片。

[1]Myst Cyan公司于1993年推出的一款图形解谜游戏。——译者注
[2]苹果公司的一款媒体播放器。——译者注
[3]网景导航者(Netscape Navigator)又称领航员,是网景公司开发的网络浏览器。曾经是世界上最流行的网页浏览器。——译者注

下面我就要尝试着记录现状,描述现状的理论。就像电影史学者在电影诞生的头几十年跟踪电影语言的发展那样,我想描述并理解新媒体语言发展的动力源泉。(我并不是说新媒体只有一种语言,而是采用这个泛用术语来指代新媒体对象的设计者们采取的种种规范,这些规范用于组织数据和构建用户体验等方面。)进一步类比,电影语言在 1910 年代形成了自己的经典范式,那么现在新媒体语言是否正在接近自己最终稳定的形式呢?这项预测对我很有吸引力。因为未来计算机媒体的语言会和现在的样子完全不同,1990 年的状况可能更接近1890年而非将来的经典范式。

现状在飞速变化,我们针对现状构建理论有意义吗?我只能说这是场糟糕的赌博。如果将来的发展证明我的理论预计是正确的,我就赢了。但是如果计算机媒体语言的发展方向和我分析揭露的方向不同,我的分析就成为来未来人眼中历史发展一种可能性的记录,我们现在可以见到但是未来人难以相信。

我们现在认为电影不会走向单一语言形式,电影不会只走向虚拟现实。相反,我们已经看到电影发展出了多种多样有表达力的成功语言形式。每一种语言都有它自己的美学变量,每一种新语言都导致了一些旧语言的消亡。(这个文化逻辑和托马斯·库恩关于科学范式的分析不同)[4]相似的,计算机媒体历史的每一步都产生了新的美学机遇,包括他们自己对于未来的计划,即他们自己的“研究范式”。这本身也是一种新的语言。研究范式在发展中不断被修正和取代。本书中,我会趁着新媒体研究范式改变之前,在它发展的头几十年记录它们。

[4] Thomas S. Kuhn, The Structure of Scientific Revolutions. 2nd ed. (Chicago: University of Chicago Press, 1970)——作者注

草绘新媒体:方法

本书中,我会在现代视觉和媒体文化的历史背景下对新媒体语言展开分析。新媒体是怎样依赖传统文化形式的?新媒体又是怎样从传统文化形式中分离的?新媒体有什么独特的手法来构建现实、面向用户、表达时间和空间?诸如矩形画面、移动视角、蒙太奇这些传统媒体的惯例和技术在新媒体上是怎样实现的?如果我们想做一次考据,把新的计算机媒体技术和旧的表征仿真技术联系起来,我们应该怎样界定重大的历史突破?

为了回答这些问题,我观察了新媒体的若干领域:网站、虚拟世界[5]、虚拟现实、多媒体、电子游戏、交互设备、计算机动画、数字视频、电影、人机界面等。虽然本书的重点在于陈述理论观点和历史结论,我还是会分析很多领域内关键的历史性新媒体对象。从美国商业经典到国际新媒体艺术家和收藏者,我都会有所涉猎。前者包括神秘岛、毁灭战士[6]、侏罗纪公园、泰坦尼克号等。后者包括 ART+COM、Antirom、jodi.org、George Legrady、Olga Lialina、Jeffrey Shaw、Tamas Waliczky 等。

[5]这里“虚拟世界”指计算机生成的三维互动环境。这个定义适用于所有现在已经面世的三维计算机环境,包括:高档的虚拟现实环境,它们配有头戴式显示器和现实图像的照片;街机、CD光盘、在线多人电子游戏;QuickTime虚拟现实电影;VRML(虚拟现实建模语言)场景;诸如“皇宫”“积极世界”等图形化聊天环境。——作者注
[6]ID software 的游戏产品,一直都走在 3D 显示技术前端。——作者注

文化内容计算机化,导致了新文化形式的涌现,比如电子游戏和虚拟世界。不仅如此,它还重新定义了已经存在的一些文化形式,比如摄影和电影。所以我也散乱地调查了视觉文化中计算机革命的影响。存储和操作图片的媒介由胶卷转变为计算机,这个过程怎样重塑了静态图像与动态图像的内在规律?我们文化中视觉语言的计算机化带来了什么影响?我们由此得到了哪些新的审美可能?

在解答这些问题的过程中,我描绘了艺术、摄影、视频、通信、设计、电影的历史。其中电影是 20 世纪最关键的文化形式。电影的理论与历史是我观察新媒体的绝佳镜子。本书将会探索以下话题:

  • 电影历史和新媒体历史的比较;
  • 数字电影的特征;
  • 多媒体语言和十九世纪电影诞生前文化语言的关系;
  • 屏幕、手持摄像机、蒙太奇在新媒体和电影中运用的对比;
  • 新媒体和先锋电影之间的历史联系。

对照电影历史,本书将会从人性和科学的角度描绘新媒体的理论框架,包括艺术史、文学理论、媒体研究、社会理论、计算机科学等。我研究新媒体的总体方法可以称作“数字唯物主义”。我会从头开始构建新媒体的理论,而不是引用先验的理论。为了发现新的文化逻辑,我仔细探究了计算机硬件和软件的本质,以及在计算机上创造新文化对象所需要的操作。

大部分关于新媒体的著作充满了对未来的预测,本书则专注分析新媒体已经发展的现实。事实上现在新媒体艺术家和设计者的发展方向尚未明确。我希望我关于新媒体的这些理论不仅能帮助人们了解现状,而且而且能够为应用实验确立坐标。例如,“文化界面理论”这一章分析了打印、电影、人机界面这三项文化传统是如何塑造新媒体对象界面的。通过描述这些已经在新媒体中得到运用的传统元素,我分析指出了其他有待运用的元素和元素组合。“组合”一章列举了一批新的蒙太奇手法,为新媒体实验展示了一些新的方向。“数据库”一章中,我指出计算机数据库可以为新媒体叙事提供新的内容组合与审美可能,这是另一个发展方向。

虽然这本书没有预测未来,但是它还是隐性包含了新媒体发展的理论。这是在更大历史视角下观察新媒体的好处。我们开始观察新媒体发展至今的历史轨迹,我们推断这条轨迹今后将怎样延伸。“新媒体原则”一章描述了我眼中新媒体发展历史上最重要的几个趋势:模块化、自动化、可变性、转码性。

当然我们并不是盲目承认这些趋势的。理解塑造新媒体革命的逻辑过程,可以让我们发展出新的新媒体特性。先锋电影制作者在电影已经存在的情况下开发了新的视听叙事手法,与此类似,如今先锋新媒体艺术家的任务就是开创新的计算机媒体语言。如果我们的理论可以揭示现在“主流”语言的构造方法,先锋艺术家就可以更容易地发明创造。

草绘新媒体:组织

本书描绘了新媒体研究领域的一种可能性,希望能够促进新媒体研究(数字化研究)这一新生领域的发展。就像文学理论教材可能以叙事和声音的章节为重点,电影研究教材可能重在讨论摄影和剪辑,本书提出了新媒体理论中新范畴[7]体系的定义和精化。

[7]在哲学中,范畴(希腊文为 κατηγορια)概念被用于对所有存在的最广义的分类。比如说时间,空间,数量,质量,关系等都是范畴。在分类学中,范畴是最高层次的类的统称。它既不同于学术界对于学问按照学科的分门别类,又有别于百科全书式的以自然和人类为中心的对知识的分类,范畴论是着眼于存在的本质区别的哲学分类系统。——译者注

我已经把书分成了若干章,每章介绍一个关键概念或者关键问题。前面章节介绍的概念是后面章节分析的基础构件。我参考了与新媒体相关的各已有领域的教材,例如电影研究、文学理论、艺术历史。

很多电影教材可能从电影技术讲起,以电影流派结尾。本书的套路几乎一样,从新媒体的物质基础讲到它的形式。

术语:语言、对象、表征

我在本书标题中加入了“语言”这个词,但是这并不意味着我们在理解新媒体时需要从语言符号的结构开始。相反,现在大部分关于新媒体和计算机文化的研究都把重点放在社会学、经济学、政治学领域。我用“语言”这个词揭示本书与众不同的重点,即:新媒体新兴的表征习惯、重复出现的设计模式、关键的实现形式等。我也考虑过“美学”“诗性”这两个词,但是最终还是决定采用“语言”。“美学”有很多我不喜欢的对立——例如艺术文化和大众文化、美和丑、有价值和不重要等。“诗性”也承担了一些我不希望有的内涵。基于俄罗斯形式主义者 1910 年代的工作,1960 年代的理论家将“诗学”定义为研究某种特殊艺术形式具体属性的学科,就像叙事文学那样。例如,文学家茨维坦·托多洛夫在他的著作《诗歌导论》中说:

“与对特定作品的解读不同,它(诗性)寻求给意义命名,旨在探寻作品诞生的普遍规律。与心理学、社会学等科学相反,它只在文学自身内部探索规律。所以诗性是一条既‘抽象’又‘内在’的通往文学之路。”[8]

[8] 茨维坦·托多洛夫,《诗歌导引》,译:理查德·霍华德(明尼阿波利斯:明尼苏达大学出版社,1981年)。——作者注

与这种“内在”方式不同,我不认为新媒体的习惯、元素、形式是史上独特的,也不认为孤立地研究它们会很有意义。相反,本书希望能够将新媒体与文化的其他领域建立联系,无论是过去的元素还是现在的元素,列举如下:

  • 其他艺术和媒体的传统:他们的视觉语言和他们组织信息、营造受众体验的策略等;
  • 计算机技术:计算机的物理性质、计算机在现代社会中的使用方式,计算机交互界面的结构、计算机的关键软件应用等;
  • 当代视觉文化:内部组织、图像志、图像学,还有我们文化中多种多样视觉现场的受众体验,例如时尚和广告,超市和艺术品,电视节目和宣传横幅,办公室和电子音乐俱乐部等;
  • 当代信息文化。

“信息文化”这个概念是我发明的,可以和我们已经熟悉的另一个概念相对照——视觉文化。信息文化包括了不同场景和物品中呈现信息的方式——路标,机场和火车站的显示屏,电视机菜单,电视新闻的平面布局,书籍、报纸、杂志的版面设计,银行、旅馆、其他商业场所和休闲场所的室内设计,飞机和汽车的操作界面,计算机操作系统(Windows、Mac OS、UNIX)和软件应用(Word,Excel,PowerPoint,Eudora,Navigator,RealPlayer,Filemaker,Photoshop等)的界面等。与视觉文化相应,信息文化也包括信息组织方式、信息获取方式(影像学的相似概念)和用户与信息对象、信息呈现的交互模式等。

另一个值得一谈的术语是“对象”。整本书中我都使用了“新媒体对象”这一术语,而非“产品”“艺术作品”“交互媒介”或者其他术语。一个新媒体对象可能是数字照片、数字电影、虚拟三维环境、电子游戏、超媒体DVD、超文本网站、万维网等。“对象”这个术语和我试图描述新媒体通用规则的目的相一致,这些规则同样也适用于所有的媒体类型,适用于不同的信息组织形式,适用于所有的信息规模。我用“对象”这个词也是为了强调我对于文化的广泛领域都很关心,而不仅仅是新媒体艺术。其次,“对象”是一个计算机科学和计算机工业中的标准术语。在计算机的面向对象编程语言中,“对象”强调语言的封装模块特性。这些语言包括C++、Java、面向对象数据库、微软Office产品中的对象链接和对象嵌入技术等。这就实现了我的目标:在计算机文化的理论中采用计算机科学的术语和范式。

此外,1920 年代俄罗斯先锋艺术家们也使用了“对象”一词,我希望它也能够包含这一层内涵。俄罗斯建构主义者和生产主义者都把他们的创造叫做“对象”(对象、结构、语言[9]等)而非艺术品。就像建筑领域的包豪斯,先锋艺术家更愿意成为工业设计师、图像设计室、建筑师、服装设计师,而不是成为传统艺术家,像传统艺术家那样为博物馆和私人收藏打造独一无二的艺术品。“对象”更多意味着工业化规模生产,而不是传统艺术家的手工作坊。它也意味着艺术家愿意将合理的劳动组织和工程效能代入他们的艺术创作之中。

[9] 原作中作者列夫·曼诺维奇在这里生造了三个英文单词,来模拟俄语术语的发音,分别是 vesh、construktsia、predmet——译者注

对于新媒体对象,以上所有的内涵都是很有价值的。在新媒体的世界,艺术和设计之间的界限很模糊。一方面,很多艺术家以商业设计为生;另一方面,职业设计师往往通过系统性实验和创立新标准新风格推进新媒体语言的发展。“对象”的第二点内涵,关于工业生产的内涵,也适用于新媒体。很多新媒体工程是大型团队共同推进的(与单人制片人、小团队、经典好莱坞时代的制片体系不同)。很多新媒体对象都销售了逾百万份,例如一些流行的电子游戏和软件应用。它们需要适应花样繁多的硬件软件标准,这样新媒体领域的特征使得它需要大规模的工业生产。[10]

[10]软件标准包括但不限于操作系统(UNIX、Windows、Mac OS等)、文件格式(JPEG、MPEG、DV、QuickTime、RTF、WAV等)、脚本语言(HTML、Javascript等)、编程语言(C++、Java等)、通信协议(TCP-IP等)、人机交互规范(对话框、剪贴板、提示帮助等)、一些不成文的惯例(例如,有十年多图像大小都是固定的640×480像素)。硬件标准包括存储介质格式(极碟、爵士可扩充硬盘、只读光盘、数字光盘)、接口类型(串行接口、通用串行总线、火线),总线架构(外部控制器接口)、随机存储器类型。——作者注
极碟是美国埃美加(Iomega)公司所发明的一种高容量软式磁盘机,使用具有较坚固外壳的特制高容量软碟片,并利用部分硬盘中使用的技术,制成的个人电脑储存装置。爵士可扩充硬盘是美国埃美加(Iomega)公司利用硬盘技术制成的可移除存储设备。通用串行总线即日常生活中俗称的 USB 接口。火线(FireWire)接口是IEEE 1394的别名,是由苹果公司领导的开发联盟开发的一种高速传送接口。外部控制器接口是是一种连接电子计算机主板和外部设备的总线标准。当代计算机主板上的大部分插槽都是外部控制器接口规范插槽。——译者注

最后,也是最重要的,我用“对象”这个词暗示1920年代先锋艺术家们的实验室实验概念。现在,越来越多的艺术家转向新媒体,却没有多少人愿意针对新媒体元素,针对最基本的信息组合、表征、生成策略,展开系统的、实验室级别的研究。而这正是1920年代俄罗斯福库特马斯设计学院和德国公立包豪斯学校的先锋艺术家们所做的研究。他们探索了他们时代的新媒体:摄影、电影、新式打印技术、电话通讯等。现在,发明“可读写光盘”“数字电影”这些新产品的直接诱惑实在太大了,几乎没有人能够抵挡住这样的诱惑,转而专注于寻找镜头、语句、词语、甚至字母在新媒体概念中的等价物,去寻求惊人的发现。

第三个贯穿本书且需要点评的术语是“表征”。近几十年人类文明发展出了不少新生的文化对象,我使用“表征”一词,就是希望能够引入对这些文化对象运作方式的理解,复杂而又细致的理解。新媒体对象是文化对象。所以,任何新媒体对象,无论是网站,电子游戏,还是数字图片,都是在表征或构建一些外物,例如:实实在在的物理对象、其他文档中的历史信息、文化整体或特定社群的范畴体系等。就和其他文化表征一样,新媒体表征也难免有倾向性。它们牺牲其他内容,只描述出物理现实的部分特征。它们在诸多世界观中选择一个,在大量范畴体系中表达其一。对于操作系统和软件应用,它们的软件界面也是表征形式。本书中我会通过陈述这一点进一步论述新媒体表征的倾向性。用特定方式组织数据,新媒体就可以突出某种描述人与世界的模型。例如,目前组织计算机数据的两大常用方案分别是树形目录文件系统(自1984年麦金塔[11]计算机的图形用户界面起)和扁平的超链接网络(1990年代诞生的万维网)。它们采用了根基不同、截然相反的方式表征这个世界。树形目录文件系统认为这个世界遵循逻辑分层的秩序,每个对象都有明确不同的位置。万维网模型认为每个对象都一样重要,每个对象都可以和其他对象相联系。[12]此外,界面也可以凸显某种数据访问方式,与特定艺术作品和媒体技术相联系。例如,1990年代的万维网将网页作为数据组织的基本单元,而Acrobat软件给文本文档提供了视频播放功能。界面起到了旧文化形式和媒体的“表征”作用,强调部分内容,轻视部分内容。

[11]麦金塔电脑(Macintosh),俗称 Mac 机,也称作苹果机或麦金托什机 ,是对苹果 PC 中一系列产品的称谓。首款 Mac 于 1984 年 1 月 24 日发布,是苹果公司继 Lisa 后第二款具备图形界面的 PC 产品。——译者注
[12]首先,目录文件结构并不诞生在 1984 年,在图形界面出现以前,树形文件结构早就广泛存在。在计算机领域中,文件目录结构与超链接网络也不是对立关系。每一个位于目录中的文件都有自己的链接地址,网络中超链接的相对关系也需要依靠目录结构来表征。在计算机工业世界,两者分工合作,各有所长。而且你中有我,我中有你,互相交融,远非对立。译者认为作者在这里对两种数据组织方式有片面的处理。——译者注

我发现,在描述新媒体语言时,“表征”一词的对立意义非常有用。给“表征”选择不同的对立意义,“表征”就能体现出不同的内涵。这些对立意义的介绍分散在本书的各个章节,我总结如下:

  1. 表征——仿真(“屏幕”小节)。这里,“表征”意味着各种屏幕技术应用,例如后文艺复兴绘画、电影、雷达、电视等[13]。我将“屏幕”定义为一个矩形表面,它展现虚拟世界,但同时也是用户物理世界中的一个实际存在,不会完全遮蔽用户的视野。“仿真”则指让用户完全沉浸在虚拟世界中的技术,如巴洛克的耶稣教堂、十九世纪的全景图片、二十世纪的电影院等。
  2. 表征——控制(“文化界面”小节)。这里,我把两种图像对立起来,一者是虚构世界的视觉表征,一者是控制面板(例如,图形界面及其图标、菜单)的细节模拟。控制面板使用户可以操作计算机,这种新型的图像可以称作“界面图像”。表征和控制这一组对立对应着深度和表面的对立。例如,计算机屏幕,在表征情境下是通往虚拟空间的窗口,在控制情境下则是一个扁平的控制面板。
  3. 表征——操作(“遥控”小节)。创造幻觉(时尚、写实油画、西洋镜、军事诱饵、电影蒙太奇、数字合成等)的技术和执行操作的技术存在对立。执行操作的技术可以让用户通过表征(地图、建筑图纸、X射线图、远程监控等)操作真实对象。我把执行操作的图像成为“工具图像”。
  4. 表征——通信(“遥控”小节)。表征技术(胶卷、音频、录像磁带、数字存储格式等)和实时通信技术(电报、电话、电传[14]、电视、远程监控等)构成对立。表征技术可以让人创造传统美学对象,这些对象存在于确定的时间或空间,并且涉及它们之外的事物。现在人与人之间的沟通交流越来越重要,“通信文化”形式一般不产生任何文化对象,新媒体迫使我们重新思考传统上文化和对象的等价关系。[15]
  5. 视觉幻觉艺术——仿真(“幻想”一章的引语)。这里,幻觉艺术包含“屏幕”小节中表征和仿真的双重意义。幻觉艺术结合了传统技术和旨在创造虚拟真实视觉的技术,如视角化绘画、电影、西洋镜等。这里的“仿真”指除视觉表现外,计算机模拟现实其他内容的方法,如物理对象的移动、自然现象中的物体形变(水面、烟雾等),动机、行为、人类的言谈和语言表征等。
  6. 表征——信息(“形式”一章的引语)。新媒体有两个对立的设计目标,一者像传统小说那样,希望用户沉浸在虚拟世界;一者希望给用户提供获取信息的高效渠道(搜索引擎、网站、在线百科等)。

[13]这些设备或艺术品都需要引入屏幕技术。——译者注
[14]电传是一个合成词,是远距离打印交换的缩写形式。电传既具有电话的快速,又具有打字机的准确,尤其是当电文中有数据时,这种优点表现得特别明显。——译者注
[15]古时人们沟通会产生书信等具体的文化对象,这些文化对象往往记载着时间、地点等重要的外部事物信息。但是现在,如果没有刻意地录音、存储,大量信息就耗散在电波和光纤里,不形成文化对象,也不指涉外部。——作者注