巴别塔上的雇工


Bing帮助你做判断,但不是所有的判断
7月 23, 2009, 1:43 上午
Filed under: 技术体会

微软推出Bing搜索这个品牌以来,都反映不错,首先是界面让人感觉使用方便(Copy其他搜索引擎 + 一些快捷使用方式),主要还是真能有利于帮助用户做出一些判断。在Redmond出差的时候感觉很深,要去什么饭店,或者去买什么东西,用bing搜索出来的结果的确是我们想要的东东,比Google更加让人满意,莫非微软搜索终于出头了,打败Google指日可待?

在Redmond办公楼的大堂拿了基本内部杂志,算是内部参考,才看到bing现象的内在。微软在推出bing的之前几年收购了若干搜索技术相关的公司,都是和产品搜索相关或者和地图搜索相关,还有一个是关于语义搜索(semantic search),明显这些收购不是盲目的,整合在一起,使得Bing在搜索产品和搜索位置方面有点强。如果你搜索的初衷就是要去买一样东西,或者要找一个地方,bing搜索结果就显得很卓越;但是,这两个范围之外的搜索,就看不出什么特别突出的地方了。

虽然bing短期内给了我们很多惊喜,但是要说终结google,还是路漫漫其修远兮。



国家的Content Filtering
6月 10, 2009, 12:32 下午
Filed under: 技术体会
cc20090609005

我在Microsoft的Content Filtering项目工作期间,和同事们经常唏嘘,同样都是做内容过滤的,人家“金盾”要没有顾忌得多,既不用考虑法律问题,也不会被错误得屏蔽投诉骚扰。不过人家金盾说到底还是在服务器和网关上进行过滤,没有侵入每个用户自己的电脑无关,好歹还是给Content Filtering留了点生存空间:-)

不过时过境迁,现在已经有国家级的Content Filtering填补这一领域的空白,这就是“绿坝”,第一次听说还以为是“绿霸”。对这个软件产品现在颇有争议,我无法不判定其是好是坏,只是说说以前做Content Filtering的情况吧。

Microsoft的Content Filtering(下面简写为CF)一开始就明确原则不是为了“堵”小孩上不良网站,而是防止小孩“无意中”浏览不良网页,所以客户端实现是相当的松。CF没有绞尽脑汁做一个让小孩无论如何都没法绕过的客户端,因为这样没有多大用处,而且有可能会激发小孩的逆反心理,和小孩斗,往往就是个输。我不大清楚“绿坝”这方面怎么样,据说可以卸载,但是我想卸载肯定也是需要权限的,家长和老师可以选择不卸载,这样小孩还是在“坝”内。

据说“绿坝”将自带一个不良网站列表,不知道是不是明文方式在客户端存贮,如果是,那这个“绿坝”倒是成了想上不良网站人士的指路灯。成魔还是成佛,就在一念之间。

不管是CF还是“绿坝”,到底有没有积极意义呢?我开始干CF的时候已经非常成年了,一开始也无法想象一个十岁左右小孩看到Playboy会不会被毒害,这个问题之后同事们也讨论过,从历史案例看来,不良内容的确有将小孩引入歧途的先例,所以进行内容过滤还是恰当的。所以CF有一条不成文的原则,宁可错杀一千,不可使一各黄色网站漏网,一些著名的国内门户网站,主页上都是一些打擦边球的暧昧文字,被CF无情地判为不良网站,这样保护了不少天真的孩童。我希望“绿坝”对这些门户网站也一样不要手软,不然所谓保护小孩就是一句假话。



Natal真是酷
6月 7, 2009, 4:14 上午
Filed under: 技术体会

看Microsoft的Project Natal,虽然现在还没有正式公布发布日期和价格,但是一旦发布,肯定是一个UI控制的革命。Microsoft为了在家庭客厅抢占一席之地而不断投入XBox/360,从亏了很多钱到终于赚钱,到现在可以引领潮流,就连竞争对手Google的Blog上也介绍了这个项目,不得不说是非常有远见的坚持。



别让ToString函数改变对象的状态
6月 6, 2009, 12:59 上午
Filed under: 技术体会

几天前在Debug的时候碰上一件让人抓狂的事情,为了分析一个bug,我用Visual Studio连上目标进程,在一个函数上设了断点(breakpoint),然后按F10一行一行地看程序状态,很快就发现程序状态不正确,但是又看不出来状态怎么突然不对了。将Visual Studio和目标进程断开,程序状态就恢复正常了,奇怪!难不成薛定锷的猫理论又出现了?Debugger会影响到程序的行为,但是我没有通过Debugger做任何改变程序状态的事情啊。

重复操作了一遍,还是这个现象,只要用Debugger去看一眼,就会出错,但是没有Debugger就没事,这几乎要让人疯掉了!

后来终于想明白是怎么回事,原因就是某个类的作者让ToString()函数改变了对象的状态,这,编译器是容许的,但是相当不应该的,是一个Worst Practice

   1: class Foo
   2: {
   3:     private string _msg;
   4:  
   5:     public int ToStringCount
   6:     {
   7:         get;
   8:         set;
   9:     }
  10:  
  11:     public Foo(string msg)
  12:     {
  13:         _msg = msg;
  14:     }
  15:  
  16:     public override string ToString()
  17:     {
  18:         ++ToStringCount;
  19:         return _msg;
  20:     }
  21:  
  22: }
  23:  
  24: class Program
  25: {
  26:     static void Main(string[] args)
  27:     {
  28:         Foo foo = new Foo("foo");
  29:         Console.WriteLine(foo);
  30:         Console.WriteLine(foo.ToStringCount);
  31:         Console.ReadLine();
  32:     }
  33: }

 

上面的code,在Visual Studio里面按Ctrl+F5直接运行,输出是

foo

1

这在意料之中,但是如果你在Main函数第一行设上断点,F5进入Debug状态,然后一行一行走,走后的结果是什么?这取决于你的Locals Debug Window有没有打开,如果Locals打开了,每Step Over一次,Debugger都会去调用Local Variable的ToString()函数,这样ToString()函数调用的次数就比没有Debugger的调用要多。这就是我碰到问题的根源。

也许你会说,只要不用Debugger不就没问题了吗。但是,从一个类的角度来说,它并不知道自己的实例会在什么场合下如何使用,它也没发知道,所以必须保证自己public出去的方法不管怎么折腾行为都是正确的,让ToString()改变对象的行为违背了这个原则。很可惜C#并没有强制一个函数不能改变对象状态的语法,所以只能靠程序员自己注意了。



static constructor
5月 31, 2009, 2:30 上午
Filed under: 技术体会

上一次Code Inspection的时候,才知道FxCop有一条规则是建议不要使用explicit static constructor,说是这样会有损performance。我知道非显示写static constructor和显示写static constructor的区别就是会让C#编译器是否产生beforefieldinit这个flag,但是真没在意这个flag会带来多少performance影响。

   1: class Foo
   2: {
   3:     private static string _msg = "Hello world";
   4:  
   5:     public static string Msg
   6:     {
   7:         get { return _msg; }
   8:     }
   9: }
  10:  
  11: class Bar
  12: {
  13:     private static string _msg;
  14:  
  15:     static Bar()
  16:     {
  17:         _msg = "Hello world";
  18:     }
  19:  
  20:     public static string Msg
  21:     {
  22:         get { return _msg; }
  23:     }
  24: }

上面两个类Foo和Bar,功能上完全一致,区别就是Bar有explicit static constructor,C#编译器在编译这两个类的时候,Foo会有beforefieldinit,而Bar没有。性能上的影响,就是使用Bar会慢一些,因此不提倡使用explicit static constructor。

我一直不觉牺牲可读性来换取performance提高是件好事,好在大多数情况下,也不是非写explicit static constructor不可。不过让我们看看不写explicit static constructor会带来多少性能上的好处。

   1: private static void TestFoo()
   2: {
   3:     Stopwatch sw = Stopwatch.StartNew();
   4:     for (int i = 0; i < Int32.MaxValue-1; ++i)
   5:     {
   6:         string msg = Foo.Msg;
   7:     }
   8:     sw.Stop();
   9:     Console.WriteLine(sw.ElapsedMilliseconds);
  10: }
  11: private static void TestBar()
  12: {
  13:     Stopwatch sw = Stopwatch.StartNew();
  14:     for (int i = 0; i < Int32.MaxValue-1; ++i)
  15:     {
  16:         string msg = Bar.Msg;
  17:     }
  18:     sw.Stop();
  19:     Console.WriteLine(sw.ElapsedMilliseconds);
  20: }
  21:  
  22: static void Main(string[] args)
  23: {
  24:     TestFoo();
  25:     TestBar();
  26: }

运行上面的code,结果是:

16395
16113

奇了怪了,不是说Foo会比Bar快吗?结果看来是Bar比Foo快。

不过上面用的是Visual Studio缺省配置Debug版,换成Release Build试试,结果是:

1011
7229

这么一看,Foo的确比Bar要快,而且要快得多。

在Release Build下,2,147,483,646(Int32.MaxValue-2)次调用,能够节省大约6秒,当必须使用explicit static constructor的时候,是否要在意这写performacen gains,就要看你自己来决定了。

值得一提的是,有时候我们就是需要explicit static constructor,比如我们构造一个singleton模式的时候,而beforefieldinit也并不像我们想象的那么简单,如果照办Spec上的说法,被标为beforefieldinit的类(也就是没有explicit static constructor的类)“有可能”会在使用某个static 方法的时候,type initializer还没有被调用,这就麻烦了。我问过Jeff Richter这个问题,Jeff说他觉得虽然Spec是这么写的,但是并不表示CLR是这么实现的,而且似乎实际中不会出现这种情况。但是,后来我在stackoverflow上看有人说自己遇到了这个现象,所以我们还是认为实际上CLR的确是按照Spec来实现的吧。

 



Konami Code
5月 14, 2009, 10:05 上午
Filed under: 技术体会

3172160 打《忍者龙剑传2》受到相当大的挫折,到第二关就完全打不过去,于是去网上搜集“秘籍”,一无所获。

我不大喜欢这样游戏,门槛忒高了点,难度非常大不说,也不给玩家投机取巧的方法。据说《忍龙2》的制作团队是除了名的不照顾入门级玩家的感受,有很多骨灰级玩家就喜欢这样风格的游戏,不过我不是,所以我只好放弃了。

180px-MSX2_Contra_screenshot

有个术语叫Konami Code,源自于Konami出的游戏中的Cheat Code,当《魂斗罗》还是很高水平的游戏的时代,这段操作序列也是广为流传。

这个序列就是“上上下下左右左右BA”,真希望《忍龙2》也支持类似的Cheat Code,可惜没有。

写一段检测Konami Code的JavaScript code:

<html>
    <head>
        <script>
            var konami = new function $konami()
            {
                this._codes = "38,38,40,40,37,39,37,39,66,65",
                this._codeCount = 10,
 
                this.init = function $konami$init()
                {
                    this.contentDiv = document.getElementById("content");
                    this.keySeq = [];
 
                    var that = this;
                    window.onkeydown = function(ev) { that.onKeyDown(ev);};
                };
 
                this.onKeyDown = function $konami$onKeyDown(ev)
                {
                    var keyCode= ev.keyCode;
 
                    this.keySeq.push(keyCode);
 
                    //this.contentDiv.innerHTML += this.keySeq.toString() + "<br/>";
 
                    if (this.keySeq.toString() == this._codes)
                    {
                        alert("Konami Hit!!!");
                        this.keySeq = [];
                    }
 
                    if (this.keySeq.length > this._codeCount)
                    {
                        this.keySeq.shift;
                    }
 
                };
            };
 
        </script>
    </head>
    <body onload="konami.init();">
        <div id="content">
        </div>
    </body>
</html>

 

值得一提的是,使用onkeydown是为了cross-browser,在keydonw/keyup的事件中中检查keyCode,是唯一各浏览器行为一致的方式,这篇文章介绍得很详细。



Kill Bill
5月 2, 2009, 4:49 上午
Filed under: 技术体会

按照达尔文的进化论,并不是简单的“适者生存”,而是适应环境变化的物种能够生存。环境一直在变,不能适应变化就要北淘汰。

软件世界中,这种进化很大程度上是人为的,人(对于软件体来说就是神)要它进化,它就要进化。

曾经是Microsoft的成功产品,但是现在Microsoft正积极地将IE6和Windows XP人道毁灭,因为需要推广IE8和Windows 7,不能停留在过去,要向前看。如果用户不放起旧地产品,新的产品就没法卖。

image

在上面的刺杀列表中,应该加一个Windows Vista,过几年后,也许还会有IE8和Windows 7。



“重大设计决策失误”
4月 4, 2009, 12:40 上午
Filed under: 技术体会

和以前部门的同事聊,都觉得以前犯了几个“重大设计决策失误”:第一,使用了LINQ,第二,使用了WCF。这两个都是被宣传得天花乱坠的技术,使用它们怎么会被认为是缺陷呢?

LINQ和WCF都体现了很好的软件思维,但是并不是适合任何场合,比如,对于效率要求高和I/O特别频繁的场合,LINQ和WCF就有问题了。

LINQ

LINQ,全称Language Integrated Query,C# 2.0还只是增加了一批不是很相关的feature,但C# 3.0增加的几乎所有feature都是为了一个feature服务——LINQ,所以很难界定怎么样的coding算是使用了LINQ。写SQL语句模样的LINQ query expression当然算是使用LINQ,只用lambda expression也可以说是使用LINQ,这里我们说的“重大缺陷”指的是用LINQ query expression来处理IEnumerable<T>。

LINQ还是体现了很棒的软件思维,但是效率上有些问题。访问一个数组或者List的速度要比访问一个IEnumerable<T>要快,首先访问IEnumerable<T>不可能用indexing的方式访问某个位置上的元素,只能从头开始找,其次,就算是一个从头到尾的遍历,访问IEnumrable<T>也要比访问数组或者List慢,因为访问IEnumerable<T>要先获得IEnumrator<T>,然后不断地调用MoveNext()函数和Current属性。总之,和操作数组和List相比,使用LINQ去操作IEnumerable<T>会慢。

LINQ这头牛吃进去的是IEnumerable<T>的草,挤出来的是IEnumerable<T>的奶(为了简单,我就忽略LINQ可以吞其他类型数据的事实了),这样即使我们已经获得了一个数组,经LINQ一操作,我们得到的是IEnumerable<T>,丧失了输入类型是数组的好处。当然我们可以再ToArray一把,但是这样带来的浪费更大。

还有,LINQ expression不能异步运行(Asynchronous),这几乎就是决定LINQ在涉及到I/O上无法scale,LINQ to SQL被毙掉也可以理解了。

这是.NET专家Jeff Richter在培训课上被问了很多关于LINQ的缺点的问题之后的原话:

“After you guys asked these questions, it seems LINQ is NOT that good.”

WCF

WCF全称Windows Communication Foundation,是.NET 3.0推出的四个Library之一。本来出发点挺好,让程序可以只使用一种API,底层可以使用任意一种Binding方式,假如一开始使用HTTP作为通讯方式(HTTP Binding),过一阵子想直接用TCP/IP来提高速度,之前的code不用改变,因为WCF把底层给抽象(Abstraction)了嘛。

对于Abstraction,最讨厌的就是有Abstraction Leakage,也就是说没有抽象好,漏了,让上层还是被迫了解被抽象的东东是怎么回事。WCF就存在Abstaction Leakage,当调换底层Binding的时候,并不能保证上面完全没有知觉。

WCF还有一个大问题,client端获得一个server的proxy,然后通过这个proxy往server发请求,而且还支持异步(这点比LINQ做得好,eh)。问题是,WCF规定,一个proxy上如果抛出一个Exception,那么表示这个proxy坏了,不能再用了,好,现在client端通过一个proxy依次异步发送3个请求A、B、C,Server端成功处理了B和C,但是对A失败了,返回一个错误,而且这个错误信息赶在B和C的成功信息之前回到了proxy,那么按照WCF,B和C也无法获得结果,因为A的失败导致这个proxy进入失败状态。

********

LINQ和WCF都是很多软件工程师的心血结晶,我不是说它们烂得不能用,只是提醒它们没有你想象的那么好。当你使用一种技术的时候,你就依赖了这种技术,之后它的成败就会影响你的成败,所以不要只是看一个技术被宣传的优点,也要看到它的缺点,知己知彼才能百战不殆。



输入法体验
3月 26, 2009, 1:28 下午
Filed under: 技术体会
公司组织了一次输入法体验活动,这种活动我当然积极参加。和众多其他参与者一样,我被安排分别用四种拼音输入法敲一篇汉字,然后对每个输入法进行评价,相关人员会对过程进行记录和分析用以提高微软输入法的用户体验。

我花了四十多分钟敲了四遍,感觉还是微软的“新新体验”输入法最好(虽然官方说还是“新体验”,但是这个名字在Office 2007发布的时候就用了,新版本应该算是“新新体验”了)。拼音输入法对用户拼音输入的判断准确率只是一方面,在别的方面也要下些功夫才能带来最好的用户体验。比如,如果拼音判断准确,那么用户就会不是一个词一个词地输入,而是一口气把一句话的拼音都打完,如果我输入“我最近喜欢看电视剧”,能够直接不停顿地敲“wozuijinxihuankandianshiju”,写完一句之后,我会输入标点符号,搜狗输入法的缺省行为是认为逗号之前的东西就是英文,所以进入文档的就是“wozuijinxihuankandianshiju,”,这很让我抓狂;而微软新新体验输入法的行为是,用户输入逗号表示用户对目前显示的文字满意,所以进入文档的就是“我最近喜欢看电视剧,”。这只是一个UI设计上的小功夫,但是带来的体验就很不一样。

积极参加公司产品的测试活动没错,今天参与者都收到了意外的礼物,一个微软无线鼠标3000微笑



BT: Behavior Targeting
3月 24, 2009, 1:54 下午
Filed under: 技术体会
前一整子Google宣布AdSense将具备BT功能,这个BT不是用来下载的Bit Torrent,也不是用来通讯的Blue Tooth,而是Behavior Targeting。在线广告领域,具有BT功能是很了不得的,价钱会比没有BT的高得多。

在线广告网最要紧的,就是把合适的广告内容在合适的时间展示给合适的人,要做到这一点,就得知己知彼——知道广告的内容,还知道嵌入广告的网页内容,只是知己;了解了用户的行为才是知彼。在没有BT的情况下,AdSense这样的服务只了解广告和包含广告的网页,但是不了解用户,所以只是知己,不知彼。有了BT,AdSense根据跟踪用户在Internet上的行为,可以探测每个用户的喜好倾向,比如某个用户总是搜索汽车相关的关键字,也浏览了很多汽车相关资讯,那么AdSense就将此用户定位为“汽车爱好者”这一类,当向这个用户展示广告的时候,就会选择“汽车”相关广告。

上面只是简单的介绍了一下BT,实际上BT是一个很复杂很高深的学问。但是,搞BT归根到底就是要跟踪用户行为,问题是——你希望自己的行为被跟踪吗?

《三体2》中,刘慈欣展示了未来世界BT达到极致,或者说达到极端的恐怖景象,广告系统能够定位每个人的所在位置,了解每个人的生活情况,然后随时随地将广告劈头盖脸地砸来。这样几乎毫无隐私的状况,诞生了这样的计算机病毒,可以利用这样的广告系统定位某个人,然后通过干扰自动化设备将其杀死。

当然,BT的倡导者们,对于BT涉及到的隐私(Privacy)问题也有说法——如果用户暴露他们的行为能够给他们带来更好的体验,比如他们看到的广告都是他们真的感兴趣的广告,那么他们会愿意使用BT的。要明白,说这话的都是卖家,卖家当然会说自己的东西好。

对BT应该是什么态度,每个人都可以有自己的看法,是否愿意让别人跟踪自己的行为,是每个人自己的选择。需要注意的是,当你下载安装一个浏览器Toolbar的时候,当你对某个超长的EULA(End User License Agreement)选择I Agree的时候,甚至当你仅仅只是使用某个搜索引擎的时候,你都可能已经选择让别人跟踪自己的行为了。