UserAgent的历史变迁译言-电脑网络 » 车东's shared items in Google Reader
Shared by AllenT
funny

原文作者:Aaron Andersen
原文链接:History of the browser user-agent string
译者:heart5

最早的时候有一个浏览器叫NCSA Mosaic,把自己标称为NCSA_Mosaic/2.0 (Windows 3.1),它支持文字显示的同时还支持图片,于是Web开始好玩起来。

然后出现了一个新的网页浏览器,“Mozilla”,其实就是“Mosaic终结者”的意思,这搞的Mosaic很不爽,(毕竟Mosaic出道早,江湖老),新浏览器最后正式公布的名称是Netscape,它把自己标称为Mozilla/1.0 (Win3.1),更好玩了。Netscape支持框架显示,后来框架在大家中间流行起来了,但Mosaic不支持框架啊,于是伟大的“用户代理人探测”技术出现了,如果是“Mozilla”,那就发给支持框架的页面,至于其他的浏览器,则发给不含框架的页面。

Netscape想逗Microsoft玩儿,把Windows叫做“几乎不曾做过调试的设备驱动器”,后者很恼火。Microsoft于是推出了自己的 网页浏览器,叫做Internet Explorer,希望它能成为“Netscape终结者”。Internet Explorer也支持框架,但它不是Mozilla啊,所以没人给它发送带有框架的页面。Microsoft慢慢烦躁起来,不再寄希望于网站管理员逐渐 认识IE并给它发框架,而是宣称自己是“兼容Mozilla”的,开始模仿Netscape,把自己标称为Mozilla/1.22 (compatible; MSIE 2.0; Windows 95),这样Internet Explorer也能收到框架了,整个Microsoft狂喜,但网站管理员开始有点被搞糊涂了。 

Microsoft把IE和Windows一起卖,并且把产品也弄得比Netscape更好了,拉开了第一场浏览器之战。结果和大家知道的一样,Netscape被干掉了,Microsoft大胜、大喜。但是后来Netscape以Mozilla的新名称重生了,构造了Gecko,标称其为Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.1) Gecko/20020826,Gecko属于渲染引擎,表现优异。Mozilla开发了Firefox,标称为Mozilla/5.0 (Windows; U; Windows NT 5.1; sv-SE; rv:1.7.5) Gecko/20041108 Firefox/1.0,并且Firefox表现也非常优秀。Gecko扩张迅速,一些浏览器使用了它的代码并标称为Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.7.2) Gecko/20040825 Camino/0.8.1 ,这是一个,还有Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.8.1.8) Gecko/20071008 SeaMonkey/1.0,另一个,它们都伪装成Mozilla,同时也都是基于Gecko支持的。

Gecko表现优秀,IE则很差劲,于是身份甄别再次发生,输送给Gecko的是设计良好的网页代码,其他浏览器就没有这个待遇了。Linux的跟随者很伤心,因为他们创建了基于KHTML引擎支持的Konqueror,但却不会被输送好代码,虽然他们自己认为KHTML和Gecko一样优秀,于是Konquerer开始伪装自己“像Gecko”那样以得到好的网页,并标称自己为Mozilla/5.0 (compatible; Konqueror/3.2; FreeBSD) (KHTML, like Gecko),这个世界更让人困惑了.

后来出现了Opera这样的主儿,宣称“允许用户自己决定让浏览器装成谁”,它的菜单中提供了Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.51Mozilla/5.0 (Windows NT 6.0; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.51Opera/9.51 (Windows NT 5.1; U; en) 供大家来选择,选谁是谁。

Apple开发了Safari,使用了KHTML,同时也增加了很多新特性,后来干脆一锅煮,另起炉灶叫了WebKit,但是它有希望能够得到那些为KHTML编写的网页,于是Safari标称自己为Mozilla/5.0 (Macintosh; U; PPC Mac OS X; de-de) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.5,这个世界更混乱了。

Microsoft越来越担心Firefox的发展,重新启动了Internet Explorer的开发,标称自己为Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ,可以很好的渲染代码,但那要看网站管理员是否指示它这么做。

Google也开发了自己的浏览器Chrome,使用了Webkit,有点像Safari,希望能得到为Safari编写的网页,于是决定装成Safari。这样啊,Chrome使用了WebKit渲染引擎,想装成Safari,而WebKit呢又伪装自己是KHTML,KHTML呢又是伪装成Gecko的,同时所有的浏览器又都宣称自己是Mozilla,于是,Chrome宣称自己是Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.2.149.27 Safari/525.13,,UserAgent字符串彻底混乱了,也几乎不再发挥任何作用,每个人都宣称自己是别人,混乱充斥人间啊。

添加评论

相关文章:

  圣经数字的钥义

  剑桥猪肉史

  当杰克逊将准备关闭美国银行时,引起了美国的经济危机

20:39 MySQL的性能调优工具:比mysqlreport更方便的tuning-primer.sh » 车东[Blog^2]

年初的时候收藏过一篇关于mysqlreport的报表解读,和内置的show status,show variables相比mysqlreport输出一个可读性更好的报表;但Sundry MySQL提供的脚本相比mysqlreport更进一步:除了报表还进一步提供了修改建议。安装和使用非常简单:

wget http://www.day32.com/MySQL/tuning-primer.sh
chmod +x tuning-primer.sh
./tuning-primer.sh

和mysqlreport一样,tuning-primer.sh也支持.my.cnf
[client]
user = USERNAME
password = PASSWORD
socket = /tmp/mysql.sock

样例输出:在终端上按照问题重要程度分别用黄色/红色字符标记问题

-- MYSQL PERFORMANCE TUNING PRIMER --
- By: Matthew Montgomery -

MySQL Version 5.0.45 i686

Uptime = 19 days 8 hrs 32 min 54 sec
Avg. qps = 0
Total Questions = 264260
Threads Connected = 1

Server has been running for over 48hrs.
It should be safe to follow these recommendations

To find out more information on how each of these
runtime variables effects performance visit:
http://dev.mysql.com/doc/refman/5.0/en/server-system-variables.html
Visit http://www.mysql.com/products/enterprise/advisors.html
for info about MySQL's Enterprise Monitoring and Advisory Service

SLOW QUERIES
The slow query log is NOT enabled.
Current long_query_time = 10 sec.
You have 0 out of 264274 that take longer than 10 sec. to complete
Your long_query_time may be too high, I typically set this under 5 sec.

BINARY UPDATE LOG
The binary update log is NOT enabled.
You will not be able to do point in time recovery
See http://dev.mysql.com/doc/refman/5.0/en/point-in-time-recovery.html

WORKER THREADS
Current thread_cache_size = 0
Current threads_cached = 0
Current threads_per_sec = 1
Historic threads_per_sec = 0
Your thread_cache_size is fine

MAX CONNECTIONS
Current max_connections = 100
Current threads_connected = 1
Historic max_used_connections = 33
The number of used connections is 33% of the configured maximum.
Your max_connections variable seems to be fine.

MEMORY USAGE
Max Memory Ever Allocated : 96 M
Configured Max Per-thread Buffers : 268 M
Configured Max Global Buffers : 7 M
Configured Max Memory Limit : 276 M
Physical Memory : 1.97 G
Max memory limit seem to be within acceptable norms

KEY BUFFER
Current MyISAM index space = 8 M
Current key_buffer_size = 7 M
Key cache miss rate is 1 : 1817
Key buffer fill ratio = 6.00 %
Your key_buffer_size seems to be too high.
Perhaps you can use these resources elsewhere

QUERY CACHE
Query cache is supported but not enabled
Perhaps you should set the query_cache_size

SORT OPERATIONS
Current sort_buffer_size = 2 M
Current read_rnd_buffer_size = 256 K
Sort buffer seems to be fine

JOINS
Current join_buffer_size = 132.00 K
You have had 0 queries where a join could not use an index properly
Your joins seem to be using indexes properly

OPEN FILES LIMIT
Current open_files_limit = 1024 files
The open_files_limit should typically be set to at least 2x-3x
that of table_cache if you have heavy MyISAM usage.
Your open_files_limit value seems to be fine

TABLE CACHE
Current table_cache value = 64 tables
You have a total of 125 tables
You have 64 open tables.
Current table_cache hit rate is 9%, while 100% of your table cache is in use
You should probably increase your table_cache

TEMP TABLES
Current max_heap_table_size = 16 M
Current tmp_table_size = 32 M
Of 564 temp tables, 6% were created on disk
Effective in-memory tmp_table_size is limited to max_heap_table_size.
Created disk tmp tables ratio seems fine

TABLE SCANS
Current read_buffer_size = 128 K
Current table scan ratio = 1 : 1
read_buffer_size seems to be fine

TABLE LOCKING
Current Lock Wait ratio = 0 : 264392
Your table locking seems to be fine

更有用是作者总结的处理MySQL性能问题处理的优先级:尤其是头3条,基本上可以解决大部分瓶颈问题的原因。
# Slow Query Log 慢查询 尤其是like操作,性能杀手,轻易不要使用,让全文索引交给Lucene或者利用Tag机制减少like操作;
# Max Connections 并发连接数:一个MySQL deamon缺省最大连接数是100,调到更高只是为了出现问题是给我们更多的缓冲时间而不是任其一直处于那么高的状态,并发连接数类似于等候大厅:当等候人数过多的时候,一味扩大等候厅不是根本解决问题的办法,提高业务的处理速度,多开几个窗口才是更好的解决方法;我的经验就是超过100: 数据就要想办法(镜像或者分片)分布到更多Deamon上
# Worker Threads: Jeremy Zawondy 曾在部落格上說到:Thread caching 並不是我們最需要關心的問題,但當你解決了所有其他更嚴重的問題之後,它就會是最嚴重的問題。(thread caching really wasn't the worst of our problems. But it became the worst after we had fixed all the bigger ones.)
# Key Buffer
# Query Cache
# Sort Buffer
# Joins
# Temp Tables 临时表
# Table (Open & Definition) Cache 表缓存;
# Table Locking 表锁定
# Table Scans (read_buffer)
# Innodb Status

19:22 二进制包还是源代码包 » 花开的地方

我建议对“LAMP”构架应用不太熟悉的朋友直接使用系统提供的二进制代码安装——假如不是有什么特殊需求的话,比如领导强制要求必须使用最新版本的代码或者有特殊需求,比如有自己的“FHS”。其官方提供的二进制代码理论上是经过官方的技术人员的优化、以及多方测试的,甚至比不太熟悉的人用源代码编译得来的二进制,性能更高。随便说一下,这也是我觉得“服务器版”与“通用版本”之间可能存在的微弱或者重大的差别。并且,网上很多文章均使用源代码的方式安装程序,而很少有提及编译时strip代码的问题,这真是一个讽刺,对“优化”、“性能”如此注重之人,却使用包含着“symbols”(编译成生二进制程序时为调试和诊断而保留的非必需的结构)的二进制(strip之后,性能提高不敢乱言,至少文件大小会有惊人的变化:php-cgi strip之前12M,strip之后只有3M,无压力情况下,php-cgi占用的内存由5.5M降低到1.8M——数据仅供参考。),系统提供的二进制,绝大多数都是“striped”的(使用file命令可以查看到二进制文件的相关信息)——不敢妄言“全部”,因为隐约记得TSL(TSL是一个声称像OpenBSD一样安全的Linux系统)的系统上,发现过没有strip过的二进制,可能是谬误。

即然提到了strip,随便也说一下如何strip二进制,Linux提供了事后strip程序的工具“strip”命令:

1. 查看程序是否是”striped”的

file  /opt/httpd/bin/httpd

假如需要strip,那么

2. strip /opt/httpd/bin/httpd

即可!

另外,其实编译代码的时候可以要求生成”striped”的代码,绝大多数开源代码,make里都提供了该参数,就是install-strip,即在编译完之后安装的时候,使用install-strip而不是install来strip程序再安装。 

MySQL安装的时候使用make install-strip即可。

Apache要在./configure 时,加上 LDFLAGS=’-s’ 的选项即可。

PHP则需要在./configure之前,先执行一下export LDFLAGS=’-s’  (bash环境)。

可以细读金步国的作品:深入理解软件包的配置、编译与安装

19:14 使用arpwatch和arping来排查ARP攻击 » 花开的地方

ARPWATCH监听广播域内的ARP通信,记录每台设备的IP与MAC的对应关系,当发生变化时,发邮件通知。

ARP攻击的常见迹象就是发现网关的MAC地址变生变化。

当然,ARPWATCH不是万能的。因为一般情况下,你的安装arpwatch的服务器不可能总是能接收到整个网络的arp事件。想要完全监听,不得不在交换上做端口镜象。

另外,对路由器的攻击,因为攻击包是发给路由器的,假如在适当的位置没有端口镜象的支持,也是收不到任何信息的,所以也发现不了攻击。

####################
# ARP攻击监控、处理
####################

#系统环境:CentOS 5.2

# 安装 ARPWACTH
yum -y install arpwatch
# 设备成自动启动
chkconfig arpwatch on
# 启动arpwatch服务
serivce arpwatch start 、/etc/init.d/arpwatch start
# 设置arpwatch
vi /etc/sysconfig/arpwatch
# -u : defines with what user id arpwatch should run
# -e : the where to send the reports
# -s : the -address
OPTIONS=”-u pcap -e caoyuwei@vip.qq.com -s ‘root (Arpwatch)’”

使用arping查询得到mac地址
# 遍历VLAN内的MAC地址
#!/bin/bash
for i in `seq 254` ; do
arping -c2 210.51.44.$i | awk ‘/Unicast reply from/{print $4,$5}’ | sed ’s/\[//' | sed 's/\]//’
done

报警样例:

hostname: <unknown>
          ip address: 210.51.44.1
    ethernet address: 0:0:24:5b:bb:ac
     ethernet vendor: CONNECT AS
old ethernet address: 0:b:bf:29:d0:56
 old ethernet vendor: Cisco Systems
           timestamp: Sunday, December 7, 2008 14:46:34 +0800
  previous timestamp: Sunday, December 7, 2008 14:46:34 +0800
               delta: 0 seconds

其中,知道网关正确的MAC是0:b:bf:29:d0:56,所以0:0:24:5b:bb:ac就是攻击者的MAC地址(有一点需要注意,MAC地址也是可以改变的)。使用arping遍历同网段的IP,得到MAC列表,再grep出这个MAC的IP地址即可。可以把这个MAC地址表保存起来,下次使用。当然,要保证这个文件是正确的,所以还是需要保持更新。

15:46 Cacti的安装(for CentOS 5.2) » 花开的地方

系统环境:CentOS 5.2 (Linux)

#Cacti Readme 中描述所需要的软件包
PHP 4.3.6+
MySQL 4.1.x or 5.x
RRDTool 1.0.49+ or 1.2.x
NET-SNMP 5.1.2+

#更详细的依赖关系
* httpd
* php
* php-mysql
* php-snmp
* mysql
* mysql-server
* net-snmp

1. Disable SELinux 关闭SELinux
vi /etc/sysconfig/selinux
#SELINUX=enforcing
SELINUX=disabled
设置完需要重新启动

之所以要关闭SELinux是因为国内对SELinux的应用还不普及,假如了解SELinux的使用,那么可以不关闭。正确设置相应的权限即可。

2. 下载 Cacti http://www.cacti.net
wget http://www.cacti.net/downloads/cacti-0.8.7b.tar.gz

3. 安装所需的包

yum -y install httpd
yum -y install php
yum -y install php-mysql
yum -y install php-snmp
yum -y install mysql-server
yum -y install perl
yum -y install net-snmp-utils

4. 将httpd和mysqld设置成自动启动
chkconfig httpd on
chkconfig mysqld on
5. 下载 rrdtool http://oss.oetiker.ch/rrdtool/download.var
wget http://dag.wieers.com/rpm/packages/rrdtool/perl-rrdtool-1.2.23-1.el5.rf.i386.rpm
wget http://dag.wieers.com/rpm/packages/rrdtool/rrdtool-1.2.23-1.el5.rf.i386.rpm
6. 安装 rrdtool
rpm -ivh perl-rrdtool-1.2.23-1.el5.rf.i386.rpm rrdtool-1.2.23-1.el5.rf.i386.rpm

7. 修改mysqld配置,将默认字符集设置成utf-8,这样可以方便cacti中显示中文菜单
vi /etc/my.cnf
[mysqld]
collation-server = utf8_general_ci
default-collation = utf8_general_ci
character-set-server = utf8
default-character-set = utf8
[mysql]
default-character-set = utf8

8. 建立cacti所需数据库,并设置相关权限,下面其实建了两个用户,适应不同mysql环境。
mysqladmin create cacti
mysql cacti < cacti.sql ( cacti.sql cacti目录下)
mysql> create user cacti@’localhost’;
mysql> create user cacti@’127.0.0.1′;
mysql> grant all privileges on cacti.* to cacti@’localhost’;
mysql> grant all privileges on cacti.* to cacti@’127.0.01′;
mysql> set password for cacti@’localhost’ = password(’cactipasswd’);
mysql> set password for cacti@’127.0.0.1′ = password(’cactipasswd’);

9. 修改cacti配置文件
vi include/config.php ( cacti目录下)
$database_type = “mysql”;
$database_default = “cacti”;
$database_hostname = “localhost”;
$database_username = “cacti”;
$database_password = “cacitpasswd”;
$database_port = “3306″;

10. 为cacti配置apache访问
vi /etc/httpd/conf.d/cacti.conf
Alias /cacti /srv/_webapp/cacti

<Directory /srv/_webapp/cacti>

Options Indexes FollowSymLinks
AllowOverride None
Order allow,deny
Allow from all

</Directory>

11. 设置cacti数据保存目标权限
chown -R apache rra log ( cacti目录下)

12.设置cacti采集
vi /etc/cron.d/cacti
*/5 * * * * apache /usr/bin/php -q /srv/_webapp/cacti/poller.php > /srv/_webapp/cacti/log/poller.log 2>&1

13. 未提到过的配置文件,均使用系统默认。

hadoop使用中的几个小细节(一)淘宝数据仓库团队 » 车东's shared items in Google Reader

最近在hadoop实际使用中有以下几个小细节分享:
1 中文问题
    从url中解析出中文,但hadoop中打印出来仍是乱码?我们曾经以为hadoop是不支持中文的,后来经过查看源代码,发现hadoop仅仅是不支持以gbk格式输出中文而己。

    这是TextOutputFormat.class中的代码,hadoop默认的输出都是继承自FileOutputFormat来的,FileOutputFormat的两个子类一个是基于二进制流的输出,一个就是基于文本的输出TextOutputFormat。

    public class TextOutputFormat<K, V> extends FileOutputFormat<K, V> {
  protected static class LineRecordWriter<K, V>
    implements RecordWriter<K, V> {
    private static final String utf8 = “UTF-8″;//这里被写死成了utf-8
    private static final byte[] newline;
    static {
      try {
        newline = “\n”.getBytes(utf8);
      } catch (UnsupportedEncodingException uee) {
        throw new IllegalArgumentException(”can’t find ” + utf8 + ” encoding”);
      }
    }

    public LineRecordWriter(DataOutputStream out, String keyValueSeparator) {
      this.out = out;
      try {
        this.keyValueSeparator = keyValueSeparator.getBytes(utf8);
      } catch (UnsupportedEncodingException uee) {
        throw new IllegalArgumentException(”can’t find ” + utf8 + ” encoding”);
      }
    }

    private void writeObject(Object o) throws IOException {
      if (o instanceof Text) {
        Text to = (Text) o;
        out.write(to.getBytes(), 0, to.getLength());//这里也需要修改
      } else {
        out.write(o.toString().getBytes(utf8));
      }
    }
 …
}
    可以看出hadoop默认的输出写死为utf-8,因此如果decode中文正确,那么将Linux客户端的character设为utf-8是可以看到中文的。因为hadoop用utf-8的格式输出了中文。
    因为大多数数据库是用gbk来定义字段的,如果想让hadoop用gbk格式输出中文以兼容数据库怎么办?
    我们可以定义一个新的类:
    public class GbkOutputFormat<K, V> extends FileOutputFormat<K, V> {
  protected static class LineRecordWriter<K, V>
    implements RecordWriter<K, V> {
//写成gbk即可
    private static final String gbk = “gbk”;

    private static final byte[] newline;
    static {
      try {
        newline = “\n”.getBytes(gbk);
      } catch (UnsupportedEncodingException uee) {
        throw new IllegalArgumentException(”can’t find ” + gbk + ” encoding”);
      }
    }

    public LineRecordWriter(DataOutputStream out, String keyValueSeparator) {
      this.out = out;
      try {
        this.keyValueSeparator = keyValueSeparator.getBytes(gbk);
      } catch (UnsupportedEncodingException uee) {
        throw new IllegalArgumentException(”can’t find ” + gbk + ” encoding”);
      }
    }

    private void writeObject(Object o) throws IOException {
      if (o instanceof Text) {
//        Text to = (Text) o;
//        out.write(to.getBytes(), 0, to.getLength());
//      } else {

        out.write(o.toString().getBytes(gbk));
      }
    }
 …
}
    然后在mapreduce代码中加入conf1.setOutputFormat(GbkOutputFormat.class)
    即可以gbk格式输出中文。

2 关于计算过程中的压缩和效率的对比问题
    之前曾经介绍过对输入文件采用压缩可以提高部分计算效率。现在作更进一步的说明。
    为什么压缩会提高计算速度?这是因为mapreduce计算会将数据文件分散拷贝到所有datanode上,压缩可以减少数据浪费在带宽上的时间,当这些时间大于压缩/解压缩本身的时间时,计算速度就会提高了。
    hadoop的压缩除了将输入文件进行压缩外,hadoop本身还可以在计算过程中将map输出以及将reduce输出进行压缩。这种计算当中的压缩又有什么样的效果呢?
    测试环境:35台节点的hadoop cluster,单机2 CPU,8 core,8G内存,redhat 2.6.9, 其中namenode和second namenode各一台,namenode和second namenode不作datanode
    输入文件大小为2.5G不压缩,records约为3600万条。mapreduce程序分为两个job:
    job1:map将record按user字段作key拆分,reduce中作外连接。这样最后reduce输出为87亿records,大小540G
    job2:map读入这87亿条数据并输出,reduce进行简单统计,最后的records为2.5亿条,大小16G
    计算耗时54min

    仅对第二个阶段的map作压缩(第一个阶段的map输出并不大,没有压缩的必要),测试结果:计算耗时39min

    可见时间上节约了15min,注意以下参数的不同。
    不压缩时:
     Local bytes read=1923047905109
     Local bytes written=1685607947227
     压缩时:
     Local bytes read=770579526349
     Local bytes written=245469534966
     本地读写的的数量大大降低了

     至于对reduce输出的压缩,很遗憾经过测试基本没有提高速度的效果。可能是因为第一个job的输出大多数是在本地机上进行map,不经过网络传输的原因。
     附:对map输出进行压缩,只需要添加jobConf.setMapOutputCompressorClass(DefaultCodec.class)

3 关于reduce的数量设置问题
    reduce数量究竟多少是适合的。目前测试认为reduce数量约等于cluster中datanode的总cores的一半比较合适,比如cluster中有32台datanode,每台8 core,那么reduce设置为128速度最快。因为每台机器8 core,4个作map,4个作reduce计算,正好合适。
    附小测试:对同一个程序
            reduce num=32,reduce time = 6 min
            reduce num=128, reduce time = 2 min
            reduce num=320, reduce time = 5min

Java语言学校的危险性(译文)阮一峰的网络日志 » 车东's shared items in Google Reader

面的文章是More Joel on Software一书的第8篇。

我觉得翻译难度很大,整整两个工作日,每天8小时以上,才译出了5000字。除了Joel大量使用俚语,另一个原因是原文涉及“编程原理”,好多东西我根本不懂。希望懂的朋友帮我看看,译文有没有错误,包括我写的注解。

====================

JAVA语言学校的危险性

作者:Joel Spolsky

译者:阮一峰

原文: http://www.joelonsoftware.com/articles/ThePerilsofJavaSchools.html

发表日期 2005年12月29日,星期四

如今的孩子变懒了。

多吃一点苦,又会怎么样呢?

我一定是变老了,才会这样喋喋不休地抱怨和感叹“如今的孩子”。为什么他们不再愿意、或者说不再能够做艰苦的工作呢。

当我还是孩子的时候,学习编程需要用到穿孔卡片(punched cards)。那时可没有任何类似“退格”键(Backspace key)这样的现代化功能,如果你出错了,就没有办法更正,只好扔掉出错的卡片,从头再来。

回想1991年,我开始面试程序员的时候。我一般会出一些编程题,允许用任何编程语言解题。在99%的情况下,面试者选择C语言。

如今,面试者一般会选择Java语言。

说到这里,不要误会我的意思。Java语言本身作为一种开发工具,并没有什么错。

等一等,我要做个更正。我只是在本篇特定的文章中,不会提到Java语言作为一种开发工具,有什么不好的地方。事实上,它有许许多多不好的地方,不过这些只有另找时间来谈了。

我在这篇文章中,真正想要说的是,总的来看,Java不是一种非常难的编程语言,无法用来区分优秀程序员和普通程序员。它可能很适合用来完成工作,但是这个不是今天的主题。我甚至想说,Java语言不够难,其实是它的特色,不能算缺点。但是不管怎样,它就是有这个问题。

如果我听上去像是妄下论断,那么我想说一点我自己的微不足道的经历。大学计算机系的课程里,传统上有两个知识点,许多人从来都没有真正搞懂过的,那就是指针(pointers)和递归(recursion)。

你进大学后,一开始总要上一门“数据结构”课(data structure), 然后会有线性链表(linked list)、哈希表(hash table),以及其他诸如此类的课程。这些课会大量使用“指针”。它们经常起到一种优胜劣汰的作用。因为这些课程非常难,那些学不会的人,就表明他们的能力不足以达到计算机科学学士学位的要求,只能选择放弃这个专业。这是一件好事,因为如果你连指针很觉得很难,那么等学到后面,要你证明不动点定理(fixed point theory)的时候,你该怎么办呢?

有些孩子读高中的时候,就能用BASIC语言在Apple II型个人电脑上,写出漂亮的乒乓球游戏。等他们进了大学,都会去选修计算机科学101课程,那门课讲的就是数据结构。当他们接触到指针那些玩意以后,就一下子完全傻眼了,后面的事情你都可以想像,他们就去改学政治学,因为看上去法学院是一个更好的出路[1]。关于计算机系的淘汰率,我见过各式各样的数字,通常在40%到70%之间。校方一般会觉得,学生拿不到学位很可惜,我则视其为必要的筛选,淘汰那些没有兴趣编程或者没有能力编程的人。

对于许多计算机系的青年学生来说,另一门有难度的课程是有关函数式编程(functional programming)的课程,其中就包括递归程序设计(recursive programming)。MIT将这些课程的标准提得很高,还专门设立了一门必修课(课程代号6.001[2]),它的教材(Structure and Interpretation of Computer Programs,作者为Harold Abelson和Gerald Jay Sussman Abelson,MIT出版社1996年版)被几十所、甚至几百所著名高校的计算系机采用,充当事实上的计算机科学导论课程。(你能在网上找到这本教材的旧版本,应该读一下。)

这些课程难得惊人。在第一堂课,你就要学完Scheme语言[3]的几乎所有内容,你还会遇到一个不动点函数(fixed-point function),它的自变量本身就是另一个函数。我读的这门导论课,是宾夕法尼亚大学的CSE 121课程,真是读得苦不堪言。我注意到很多学生,也许是大部分的学生,都无法完成这门课。课程的内容实在太难了。我给教授写了一封长长的声泪俱下的Email,控诉这门课不是给人学的。宾夕法尼亚大学里一定有人听到了我的呼声(或者听到了其他抱怨者的呼声),因为如今这门课讲授的计算机语言是Java。

我现在觉得,他们还不如没有听见呢。

这就是争议所在。许多年来,像当年的我一样懒惰的计算机系本科生不停地抱怨,再加上计算机业界也在抱怨毕业生不够用,这一切终于造成了重大恶果。过去十年中,大量本来堪称完美的好学校,都百分之百转向了Java语言的怀抱。这真是好得没话说了,那些用“grep”命令[4]过滤简历的企业招聘主管,大概会很喜欢这样。最妙不可言的是,Java语言中没有什么太难的地方,不会真的淘汰什么人,你搞不懂指针或者递归也没关系。所以,计算系的淘汰率就降低了,学生人数上升了,经费预算变大了,可谓皆大欢喜。

学习Java语言的孩子是幸运的,因为当他们用到以指针为基础的哈希表时,他们永远也不会遇到古怪的“段错误”[5](segfault)。他们永远不会因为无法将数据塞进有限的内存空间,而急得发疯。他们也永远不用苦苦思索,为什么在一个纯函数的程序中,一个变量的值一会保持不变,一会又变个不停!多么自相矛盾啊!

他们不需要怎么动脑筋,就可以在专业上得到4.0的绩点。

我是不是有点太苛刻了?就像电视里的“四个约克郡男人”[6](Four Yorkshiremen)那样,成了老古板?就在这里吹嘘我是多么刻苦,完成了所有那些高难度的课程?

我再告诉你一件事。1900年的时候,拉丁语和希腊语都是大学里的必修课,原因不是因为它们有什么特别的作用,而是因为它们有点被看成是受过高等教育的人士的标志。在某种程度上,我的观点同拉丁语支持者的观点没有不同(下面的四点理由都是如此):“(拉丁语)训练你的思维,锻炼你的记忆。分析拉丁语的句法结构,是思考能力的最佳练习,是真正对智力的挑战,能够很好地培养逻辑能力。”以上出自Scott Barker之口(http://www.promotelatin.org/whylatin.htm)。但是,今天我找不到一所大学,还把拉丁语作为必修课。指针和递归不正像计算机科学中的拉丁语和希腊语吗?

说到这里,我坦率地承认,当今的软件代码中90%都不需要使用指针。事实上,如果在正式产品中使用指针,这将是十分危险的。好的,这一点没有异议。与此同时,函数式编程在实际开发中用到的也不多。这一点我也同意。

但是,对于某些最激动人心的编程任务来说,指针仍然是非常重要的。比如说,如果不用指针,你根本没办法开发Linux的内核。如果你不是真正地理解了指针,你连一行Linux的代码也看不懂,说实话,任何操作系统的代码你都看不懂。

如果你不懂函数式编程,你就无法创造出MapReduce[7],正是这种算法使得Google的可扩展性(scalable)达到如此巨大的规模。单词“Map”(映射)和“Reduce”(化简)分别来自Lisp语言和函数式编程。回想起来,在类似6.001这样的编程课程中,都有提到纯粹的函数式编程没有副作用,因此可以直接用于并行计算(parallelizable)。任何人只要还记得这些内容,那么MapRuduce对他来说就是显而易见的。发明MapReduce的公司是Google,而不是微软,这个简单的事实说出了原因,为什么微软至今还在追赶,还在试图提供最基本的搜索服务,而Google已经转向了下一个阶段,开发世界上最大的并行式超级计算机——Skynet[8]的H次方的H次方的H次方的H次方的H次方的H次方。我觉得,微软并没有完全明白,在这一波竞争中它落后多远。

除了上面那些直接就能想到的重要性,指针和递归的真正价值,在于那种你在学习它们的过程中,所得到的思维深度,以及你因为害怕在这些课程中被淘汰,所产生的心理抗压能力,它们都是在建造大型系统的过程中必不可少的。指针和递归要求一定水平的推理能力、抽象思考能力,以及最重要的,在若干个不同的抽象层次上,同时审视同一个问题的能力。因此,是否真正理解指针和递归,与是否是一个优秀程序员直接相关。

如果计算机系的课程都与Java语言有关,那么对于那些在智力上无法应付复杂概念的学生,就没有东西可以真的淘汰他们。作为一个雇主,我发现那些100%Java教学的计算机系,已经培养出了相当一大批毕业生,这些学生只能勉强完成难度日益降低的课程作业,只会用Java语言编写简单的记账程序,如果你让他们编写一个更难的东西,他们就束手无策了。他们的智力不足以成为程序员。这些学生永远也通不过MIT的6.001课程,或者耶鲁大学的CS 323课程。坦率地说,为什么在一个雇主的心目中,MIT或者耶鲁大学计算机系的学位的份量,要重于杜克大学,这就是原因之一。因为杜克大学最近已经全部转为用Java语言教学。宾夕法尼亚大学的情况也很类似,当初CSE 121课程中的Scheme语言和ML语言,几乎将我和我的同学折磨至死,如今已经全部被Java语言替代。我的意思不是说,我不想雇佣来自杜克大学或者宾夕法尼亚大学的聪明学生,我真的愿意雇佣他们,只是对于我来说,确定他们是否真的聪明,如今变得难多了。以前,我能够分辨出谁是聪明学生,因为他们可以在一分钟内看懂一个递归算法,或者可以迅速在计算机上实现一个线性链表操作函数,所用的时间同黑板上写一遍差不多。但是对于Java语言学校的毕业生,看着他们面对上述问题苦苦思索、做不出来的样子,我分辨不出这到底是因为学校里没教,还是因为他们不具备编写优秀软件作品的素质。Paul Graham将这一类程序员称为“Blub程序员”[9](www.paulgraham.com/avg.html)。

Java语言学校无法淘汰那些永远也成不了优秀程序员的学生,这已经是很糟糕的事情了。但是,学校可以无可厚非地辩解,这不是校方的错。整个软件行业,或者说至少是其中那些使用grep命令过滤简历的招聘经理,确实是在一直叫嚷,要求学校使用Java语言教学。

但是,即使如此,Java语言学校的教学也还是失败的,因为学校没有成功训练好学生的头脑,没有使他们变得足够熟练、敏捷、灵活,能够做出高质量的软件设计(我不是指面向对象式的“设计”,那种编程只不过是要求你花上无数个小时,重写你的代码,使它们能够满足面向对象编程的等级制继承式结构,或者说要求你思考到底对象之间是“has-a”从属关系,还是“is-a”继承关系,这种“伪问题”将你搞得烦躁不安)。你需要的是那种能够在多个抽象层次上,同时思考问题的训练。这种思考能力正是设计出优秀软件架构所必需的。

你也许想知道,在教学中,面向对象编程(object-oriented programming,缩写OOP)是否是指针和递归的优质替代品,是不是也能起到淘汰作用。简单的回答是:“不”。我在这里不讨论OOP的优点,我只指出OOP不够难,无法淘汰平庸的程序员。大多数时候,OOP教学的主要内容就是记住一堆专有名词,比如“封装”(encapsulation)和“继承”(inheritance)”,然后再做一堆多选题小测验,考你是不是明白“多态”(polymorphism)和“重载”(overloading)的区别。这同历史课上,要求你记住重要的日期和人名,难度差不多。OOP不构成对智力的太大挑战,吓不跑一年级新生。据说,如果你没学好OOP,你的程序依然可以运行,只是维护起来有点难。但是如果你没学好指针,你的程序就会输出一行段错误信息,而且你对什么地方出错了毫无想法,然后你只好停下来,深吸一口气,真正开始努力在两个不同的抽象层次上,同时思考你的程序是如何运行的。

顺便说一句,我有充分理由在这里说,那些使用grep命令过滤简历的招聘经理真是荒谬可笑。我从来没有见过哪个能用Scheme语言、Haskell语言和C语言中的指针编程的人,竟然不能在二天里面学会Java语言,并且写出的Java程序,质量竟然不能胜过那些有5年Java编程经验的人士。不过,人力资源部里那些平庸的懒汉,是无法指望他们听进去这些话的。

再说,计算机系承担的发扬光大计算机科学的使命该怎么办呢?计算机系毕竟不是职业学校啊!训练学生如何在这个行业里工作,不应该是计算机系的任务。这应该是社区高校和政府就业培训计划的任务,那些地方会教给你工作技能。计算机系给予学生的,理应是他们日后生活所需要的基础知识,而不是为学生第一周上班做准备。对不对?

还有,计算机科学是由证明(递归)、算法(递归)、语言(λ演算[10])、操作系统(指针)、编译器(λ演算)所组成的。所以,这就是说那些不教C语言、不教Scheme语言、只教Java语言的学校,实际上根本不是在教授计算机科学。虽然对于真实世界来说,有些概念可能毫无用处,比如函数的科里化(function currying)[11],但是这些知识显然是进入计算机科学研究生院的前提。我不明白,计算机系课程设置委员会中的教授为什么会同意,将课程的难度下降到如此低的地步,以至于他们既无法培养出合格的程序员,甚至也无法培养出合格的能够得到哲学博士PhD学位[12]、进而能够申请教职、与他们竞争工作岗位的研究生。噢,且慢,我说错了。也许我明白原因了。

实际上,如果你回顾和研究学术界在“Java大迁移”(Great Java Shift)中的争论,你会注意到,最大的议题是Java语言是否还不够简单,不适合作为一种教学语言。

我的老天啊,我心里说,他们还在设法让课程变得更简单。为什么不用匙子,干脆把所有东西一勺勺都喂到学生嘴里呢?让我们再请助教帮他们接管考试,这样一来就没有学生会改学“美国研究”[13](American studies)了。如果课程被精心设计,使得所有内容都比原有内容更容易,那么怎么可能期望任何人从这个地方学到任何东西呢?看上去似乎有一个工作小组(Java task force)正在开展工作,创造出一个简化的Java的子集,以便在课堂上教学[14]。这些人的目标是生成一个简化的文档,小心地不让学生纤弱的思想,接触到任何EJB/J2EE的脏东西[15]。这样一来,学生的小脑袋就不会因为遇到有点难度的课程,而感到烦恼了,除非那门课里只要求做一些空前简单的计算机习题。

计算机系如此积极地降低课程难度,有一个理由可以得到最多的赞同,那就是节省出更多的时间,教授真正的属于计算机科学的概念。但是,前提是不能花费整整两节课,向学生讲解诸如Java语言中int和Integer有何区别[16]。好的,如果真是这样,课程6.001就是你的完美选择。你可以先讲Scheme语言,这种教学语言简单到聪明学生大约只用10分钟,就能全部学会。然后,你将这个学期剩下的时间,都用来讲解不动点。

唉。

说了半天,我还是在说要学1和0。

(你学到了1?真幸运啊!我们那时所有人学到的都是0。)

================

注解:

[1] 在美国,法学院的入学者都必须具有本科学位。通常来说,主修政治学的学生升入法学院的机会最大。

[2] 在麻省理工学院,计算机系的课程代码都是以6开头的,6.001表明这是计算机系的最基础课程。

[3] Scheme语言是LISP语言的一个变种,诞生于1975年的MIT,以其对函数式编程的支持而闻名。这种语言在商业领域的应用很少,但是在计算机教育领域内有着广泛影响。

[4] grep是Unix/Linux环境中用于搜索或者过滤内容的命令。这里指的是,某些招聘人员仅仅根据一些关键词来过滤简历,比如本文中的Java。

[5] 段错误(segfault)是segmentation fault的缩写,指的是软件中的一类特定的错误,通常发生在程序试图读取不允许读取的内存地址、或者以非法方式读取内存的时候。

[6] 《四个约克郡男人》(Four Yorkshiremen),是英国电视系列喜剧At Last the 1948 Show中的一部,与上个世纪70年代问世。内容是四个约克郡男人竞相吹嘘,各自的童年是多么困苦,由于内容太夸张,所以显得非常可笑。

[7] MapReduce是一种由Google引入使用的软件框架,用于支持计算机集群环境下,海量数据(PB级别)的并行计算。

[8] Skynet是美国系列电影《终结者》(Terminator)中一个控制一切、与人类为敌的超级计算机系统的名称,通常将其看作虚构的人工智能的代表。

[9] Blub程序员(Blub programmers)指的是那些企图用一种语言,解决所有问题的程序员。Blub是Paul Graham假设的一种高级编程语言。

[10] λ演算(lambda calculus)是一套用于研究函数定义、函数应用和递归的形式系统,在递归理论和函数式编程中有着广泛的应用。

[11] 函数的科里化(function currying)指的是一种多元函数的消元技巧,将其变为一系列只有一元的链式函数。它最早是由美国数学家哈斯格尔·科里(Haskell Curry)提出的,因此而得名。

[12] 在美国,所有基础理论的学科,一律授予的都是哲学博士学位(Doctor of Philosophy),计算机科学系亦是如此。

[13] 美国研究(American studies)是对美国社会的经济、历史、文化等各个方面进行研究的一门学科。这里指的是,计算机系学生不会因为课程太难被淘汰,所以就不用改学相对容易的“美国研究”。

[14] 参见http://www.sigcse.org/topics/javataskforce/java-task-force.pdf。

[15] J2EE是Java2平台企业版(Java 2 Platform,Enterprise Edition),指的是一整套企业级开发架构。EJB(Enterprise JavaBean)属于J2EE的一部分,是一个基于组件的企业级开发规范。它们通常被认为是Java中相对较难的部分。

[16] 在Java语言中,int是一种数据类型,表示整数,而Integer是一个适用于面向对象编程的类,表示整数对象。两者的涵义和性质都不一样。

(完)

11:26 博客趋热,但并不适合于每个营销活动 » laolu's blog: Blog

原文见eMarketer的Blogs Hot, but Not for Every Campaign(一个月后将进入收费归档区),以下为翻译:

博客趋热,但并不适合于每个营销活动

[博客]用于产品评论很好,但在获得买卖方面就没有这么好。

越来越多的营销经理正在把博客组合进营销活动,而许多博客作者也说他们被联络邀请成为品牌拥护者。但博客并不是大多数美国网络购物者的第一站。

下面是过去几个月来,博客相关领域的几家研究公司的一些发现。它们共同形成了一个日益流行的[博客营销]手法,最好用来实现特定的目的(goal),而不是营销目标(objectives)的全部。

在2008年10月Marketing Executives Networking Group (MENG)的调查中,超过2/3的美国的市场营销经理说,他们在营销尝试中采用了博客。

美国的营销经理正在使用的社会化媒体工具,2008年10月,单位:受访者%
社交网站 Social networking sites (e.g., MySpace, Facebook and LinkedIn) 73.0%
博客 Blogs 66.4%
视频分享网站 Video-sharing sites (e.g., YouTube) 45.1%
留言板 Message boards 36.9%
播客 Podcasts 33.6%
维基 Wikis 24.6%
视频播客 Vidcasts 15.6%

MENG的研究很好地反映了整体趋势,虽然其样本太小,而难以代表所有影响者的活动。但其他研究也提示出,博客是一种逐渐趋热的网络营销方法。

Technorati在9月发布了其最新的“博客状况(State of the Blogosphere)”(*注1)。该公司说,在接受调查的博客作者中,有1/3受到了一个品牌或代理机构的联络,请他们成为品牌的倡导者。大约4/5的博客作者论述其日常经历中的产品和品牌,或张贴有关的评论。

全球博客作者在博客中讨论产品或品牌的频率,按产品主题,2008年7-8月,
单位:受调查者%
  经常 偶尔 从不
产品或品牌的评论 Product or brand reviews 37% 45% 18%
喜欢(或憎恶)的品牌 Loved (or hated) brands 41% 48% 11%
个人日常经历中的公司或品牌
Personal everyday experences with companies or brands
34% 45% 21%
公司信息或闲话 Company information or gossip 31% 32% 37%

Richard Jalichandra (Technorati的CEO)告诉eMarketer,博客受众和博客作者威信的不断增加,引发了营销者的回应。然而,他说其中有个不利因素是博客的个人受众有限。 eMarketer预计到2013年,超过2/3的互联网用户将会阅读博客。

“典型地说,博客作者不具有满足营销者目标的抵达和频率,”Jalichandra先生说。“营销者正在认识到,‘我们需要沿着长尾前进一点,得到足够宽的抵达组合,并且树立起影响的氛围。’”

所以,当博客成为品牌对话的一个重要部分时,它们通常并不是直接销售的一部分。2008年9月,在Harris InteractiveRetailMeNot.com所做的调查中,只有5%的美国网络购物者说,他们利用博客寻找好的交易(**注2)。

*注1:关于Technorati今年的博客状况调查,参见WebLeOn的“Blog是不是已经走向没落?

**注2:这是个有关优惠券的调查,参见“优惠券重受青睐,近半消费者打算削减假日季节的开支(Coupons Make a Comeback as Nearly Half of Consumers Plan to Spend Less This Holiday Season)”,与上文相关的数据是:

在网络购物方面,消费者正日益掌握在网上寻找优惠商品的方法。85%的在线购物者利用各种工具和网站,查找网上的好买卖。不出意料,56%的网络购物者使用搜索引擎查询特价商品,但他们也有许多其他方式来省钱,包括:

  • 32%使用价格比较网站
  • 25%使用网络优惠券网站
  • 23%使用网络广告
  • 12%使用特价商品追踪网站
  • 5%使用购物为主题的社交网站

 


^==Back Home: www.chedong.com

^==Back Digest Home: www.chedong.com/digest/

<== 2008-12-06
  十二月 2008  
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        
==> 2008-12-08