善意提醒

如果您打开本站很慢,布局排版混乱,并且看不到图片,那么可能是因为您还没有掌握用科学的方法上网的本领。

2013-08-29

推荐几个对青少年有教育意义的老PC游戏

前言
青少年玩游戏,很容易被家人说成是「玩物丧志」。不可否认,现在绝大多数的游戏,不论是单机还是网游,其目的不外乎都是娱乐消遣发泄。其实我也认为,如果一个游戏不是为了让人玩,而是为了教育人,那么不可能是好游戏。不过,的确有那么几个游戏,有一点不同。

通常而言,所谓「有教育意义」的游戏,一般是指那些专门为了教育目的而生的游戏。特别是对于幼儿,这类游戏有很多。另外,有一些游戏,也打着所谓「寓教于乐」的旗号,——虽然实际上两者可能都做不到。
我觉得,真正有教育意义的游戏,首先要是一个优秀的游戏,从娱乐的层面的优秀。其次,如果这个游戏能让人在玩过爽过之后,有一些别的收获,那么就算是有教育意义了。
另外,这种游戏还应该具有一些特质:
  • 不容易让人沉迷:从游戏的层面来看,能让人沉迷其中对它是好事。但如果在它上面耗费了太多精力和时间,所谓教育意义就被抵消了。一个标准就是,要有始有终。开始便能很快引人入胜,结束后也不会让人过多的留恋于其中不能自拔。
  • 难度不高:如果大量的时间花在攻克难关上,就算游戏本身吸引人,但也很可能让一部分人知难而退或半途而废,所谓教育意义也就达不到了。
  • 健康向上的剧情和心态:游戏传递的,至少不应该是负面的情绪。积极、活泼、开朗、乐观、淡定泰然、不惧挫折、适当的自我调侃与解嘲、自然的正义感,等等。
  • 丰富的知识性:不是专门的教育类游戏,自然不会全部围绕知识性来构建剧情。但游戏中至少要让玩家能接触到平时接触不到的知识。日本光荣KOEI就在这方面做得非常好!
以我二十余年的经验,推荐一些这类的游戏给青少年玩家:

大航海时代Ⅱ
其实KOEI的游戏在知识性上一贯都做得非常不错。只是,我觉得有必要把这个游戏单独拿出来提升到另一个高度。因为它对我的世界地理的帮助实在是太大了。
全世界的大城市,大港口占了相当一部分,而找到并访问这些港口,是你游戏中几乎是必须完成的任务。为了找到某个地方,你得学会经纬度是怎么回事,然后还得搞一张世界地图,根据经纬度在地图上查到大致位置,然后在游戏中才能比较容易地发展剧情。幸好当时我家里墙上刚好贴了一张世界地图。
游戏中的地图当然不是完全真实的,但大致差不多。并且在航行过程中还会遇到海流、风向等问题。想远航美洲大陆?要是找对航路和时间,就会变得比较简单。那么当地理课讲到洋流和信风的时候,一下子就懂了。
另外,游戏对于历史也是有一定帮助的。虽然剧情不是真正的历史,但那个时代的一些东西还是依稀可见。各地的风土人情也刻画得很不错。这些甚至体现在了游戏的配乐中。
值得一提的是,尽管游戏中城市名称的某些译法不是很习惯,但也正好避免了学地理之类课程时背字不背义的问题。莫三比克和莫桑比克这种区别,相信一眼就能看得出来。并且,给船命名的时候,能够认识并熟悉很多繁体字。我一直到现在都能记得「號」字有十三划。
推荐给想了解世界的青少年。

Museum Madness
和别的不同,这个游戏我觉得本来就是一个「专门为了教育目的而生」的游戏。一个博物馆被病毒入侵然后Madness了,管理机器人找一个小孩子求救,然后他们一起去每个馆把搞乱了的东西复原回来。每个馆都有至少一个谜题需要解答,有的是拼图,有的是AVG/RPG性质的对话+选择,有的是玩一个小游戏,等等。在解答谜题的过程中,会穿插着对科学、历史等的说明,博物馆嘛。
推荐的意义在于:
  • 英文游戏,但英文难度不高,对话文字可以慢慢读,适合国人学习英语。
  • 可玩性不错,流畅度很好,谜题难度也适中。大部分地方不会英文也不会卡关。
  • 科普的知识还真不是小学/初中阶段所有人都知道的,看看也不错。
另外,现在看到这个游戏,我就想起了《博物馆奇妙夜》。真的是挺好的感觉。中国的历史更加悠久,但就出不来这种片子,也出不了这等游戏。
推荐给想加强英文阅读的青少年。游戏可以从这个地方下载到,点那个GET IT就可以。

轩辕剑外传——枫之舞
作为VGA时代320×240分辨率下的代表性巨作《轩辕剑Ⅱ》的外传,个人认为比原作更富教育意义。《轩辕剑Ⅱ》其实也挺值得推荐的,主要在于其对于中国传统文化——水墨山水、以及《山海经》中内容等的完美展示。而《枫之舞》在剧情和人物方面胜出。
剧情几乎都是虚构的,对历史没有太多帮助。但游戏里的不少人物都是确有其人。随着剧情发展,反派浮出水面,似乎一切都只是一个做得不错的RPG而已。然而,精髓来了——最后的大反派,并不是真正意义上的「坏人」。
这也是我日后尊敬Falcom的一个原因之一。很少有人愿意像动画片里面一样,真正甘心做一个彻彻底底的坏蛋。纵然是所谓「坏人」,也有他自己的理想。他只是在自己的实践中出了问题。不把「好」、「坏」绝对化,留给看完结局之后的玩家一点思考的空间,是这个游戏与普通的打来杀去最大的不同。
另外,游戏中主人公的对待生活、世事的态度,让我很羡慕。悠然自得,但又充满正义感。谦恭有礼,但又不时玩俏皮。自己从未做到如此。
推荐给想要成熟一点的青少年。

银河英雄传说Ⅳ
这个游戏本身也算得上是精品。但是推荐的其实不是游戏本身,而是游戏对应的文学作品。
《银河英雄传说》这部架空历史小说(不是指动画或漫画),我认为是一部对于青少年来说非常棒的三观纠正入门级教材。我认识的很多人,都是从接触它开始,慢慢学会了独立思考。对于国外的小孩,或许这方面不是问题,但对于被洗脑残害了十余年的可怜人,能有机会接触这么多不同的思想真的是太好了。

小说本身是很优秀的,但在这里就不详谈了,感兴趣的可以点这里(需「科学上网」)。而游戏值得推荐的地方在于——它非常忠实于原著。不少语句都是从原著中一字不漏地照搬过来的。这也保证了那种原汁原味的感觉,甚至连作者的写作风格都能体现在游戏中。这使得对游戏感兴趣的玩家能够有兴趣去翻阅原著,进行更深层次的思考。而对游戏不感兴趣的玩家可能就很难对原著感兴趣。实际上起到了一个引荐和筛选的作用。
推荐给希望开始思考人生的青少年。

后语
个人以为,这就是所谓的精品中的精品了。只是可惜,这少数的几个能够真正做到「寓教于乐」的游戏,基本都是目前看来很「老旧」的游戏。也许是现在的所谓「新」游戏还没有足够的时间来检验,但又也许是现在的游戏已经不追求这种目标了。看到现在的网游都是用来赚钱的,我看我还是死了这条心吧。

为避免版权问题,本文所有图片均来自Internet。

2013-06-28

Blogger上的博客如何提交完整的Sitemap

Blogger现在可以输出Atom 1.0和RSS 2.0两种版本的feed(参见官方说明)。不过无论哪种feed,都只包含最多26个Post。

对于通常的feed订阅而言,最新26个Post应该是足够了。反正RSS阅读器只需要最新的那几个Post就行。但对于想把站点的Sitemap提交到Google的网站站长工具去做SEO的情况,26个Post就太少了。既然是Sitemap,当然希望是全部的页面了,那么有什么办法呢?

这里有个老外的页面,说明了一种办法。大致说起来就是,Google在提供feed订阅的URL中,还有两个未公开说明的参数:start-index和max-results。前者表示这次的feed输出从哪个(序号的)Post开始,后者表示这次最多输出多少个Post。于是如果要提交超过(默认的)26个Post的Sitemap,就可以用类似下面这种:

http://blogname.blogspot.com/feeds/posts/default?alt=rss&max-results=500

不过,看起来max-results=500应该是一个上限。本人是没有那个条件去试了,博文数量差着一个0哩!对于博文数量超过500的情况,上面那个老外的博文中也提到了一个办法,就是分段提交Sitemap。比如:

http://blogname.blogspot.com/feeds/posts/default?alt=rss&start-index=1&max-results=500
http://blogname.blogspot.com/feeds/posts/default?alt=rss&start-index=501&max-results=500
http://blogname.blogspot.com/feeds/posts/default?alt=rss&start-index=1001&max-results=500
……

反正这些页面是都提交上去了,Google会自己把它们合并起来的。

2013-06-27

自己写了个帮助快速替换hosts的工具

替换hosts对于会的人而言并不是一件复杂的事情。不过仍然稍嫌繁琐:目录本来就比较深,还要涉及到文件复制和改名。稍一不留意还有搞错掉的可能。

两个月前,我终于无法忍受了,自己写了一个批处理来解决这个问题。人容易出错,人会烦,机器不会出错——如果你程序写对了的情况下。作为程序员,就应该有自己动手丰衣足食的觉悟。因此就有了SetHosts.bat的第一版:
@echo off
pushd %SystemRoot%\system32\drivers\etc\
copy /y %1 hosts
popd
只这样显然是不够的。在命令行下敲指令,这不是我的初衷。虽然Windows的Drag&Drop可以省很多事情,但我还想做得更多。把这个BAT注册成某个特定后缀的文件的默认打开程序不是很好吗?这样以后直接双击文件就可以完成替换了。

于是当时我自己用regedit在注册表上填写了关联文件打开方式的相关信息。现在我可以双击hosts_cn.fkw或hosts_us.fkw来快速地应用某个我事先准备好的hosts文件了。
应该说,自己用用,这样是足够了。不过我还想做的更好一点。让程序自己完成这个填写注册表的过程,不是更好吗?于是有了SetHosts.bat的第二版:
@echo off
if {%1}=={} goto install
if {%1}=={/u} goto uninstall
if {%1}=={/U} goto uninstall
pushd %SystemRoot%\system32\drivers\etc\
copy /y %1 hosts
popd
goto :eof
:install
set MY_TEMPREGFILE=%TEMP%\temp.reg
set MY_SETHOST=%~f0
set MY_SETHOST=%MY_SETHOST:\=\\%
echo Windows Registry Editor Version 5.00>%MY_TEMPREGFILE%
echo.>>%MY_TEMPREGFILE%
echo [HKEY_CLASSES_ROOT\.fkw]>>%MY_TEMPREGFILE%
echo @="Fuckwall.Hosts">>%MY_TEMPREGFILE%
echo [HKEY_CLASSES_ROOT\Fuckwall.Hosts]>>%MY_TEMPREGFILE%
echo [HKEY_CLASSES_ROOT\Fuckwall.Hosts\shell]>>%MY_TEMPREGFILE%
echo [HKEY_CLASSES_ROOT\Fuckwall.Hosts\shell\edit]>>%MY_TEMPREGFILE%
echo [HKEY_CLASSES_ROOT\Fuckwall.Hosts\shell\edit\command]>>%MY_TEMPREGFILE%
echo @="\"C:\\Windows\\Notepad.exe\" \"%%1\"">>%MY_TEMPREGFILE%
echo [HKEY_CLASSES_ROOT\Fuckwall.Hosts\shell\open]>>%MY_TEMPREGFILE%
echo [HKEY_CLASSES_ROOT\Fuckwall.Hosts\shell\open\command]>>%MY_TEMPREGFILE%
echo @="\"%MY_SETHOST%\" \"%%1\"">>%MY_TEMPREGFILE%
regedit /s %MY_TEMPREGFILE%
del /q %MY_TEMPREGFILE%
set MY_TEMPREGFILE=
set MY_SETHOST=
echo 处理程序安装成功
echo 您可以双击后缀名为fkw的文件来直接更换hosts
echo.
pause
goto :eof
:uninstall
set MY_TEMPREGFILE=%TEMP%\temp.reg
echo Windows Registry Editor Version 5.00>%MY_TEMPREGFILE%
echo.>>%MY_TEMPREGFILE%
echo [-HKEY_CLASSES_ROOT\.fkw]>>%MY_TEMPREGFILE%
echo [-HKEY_CLASSES_ROOT\Fuckwall.Hosts]>>%MY_TEMPREGFILE%
echo [-HKEY_CLASSES_ROOT\Fuckwall.Hosts\shell]>>%MY_TEMPREGFILE%
echo [-HKEY_CLASSES_ROOT\Fuckwall.Hosts\shell\edit]>>%MY_TEMPREGFILE%
echo [-HKEY_CLASSES_ROOT\Fuckwall.Hosts\shell\edit\command]>>%MY_TEMPREGFILE%
echo [-HKEY_CLASSES_ROOT\Fuckwall.Hosts\shell\open]>>%MY_TEMPREGFILE%
echo [-HKEY_CLASSES_ROOT\Fuckwall.Hosts\shell\open\command]>>%MY_TEMPREGFILE%
regedit /s %MY_TEMPREGFILE%
del /q %MY_TEMPREGFILE%
set MY_TEMPREGFILE=
echo 处理程序反安装成功
pause
goto :eof
这下不仅注册有了,注销(反安装)也有了。以后看到fkw后缀的文件,就可以快速地Fuckwall了。……于是今天方校长就请病假了。

如果整代码有困难,那么可以从下面两个链接之一进行下载:
Dropbox: https://dl.dropboxusercontent.com/u/44933633/SetHosts.bat
Mega: https://mega.co.nz/#!Bhx2yYoR!ElR26mi-iGqx5WU3YJks8ViINKEE_wlv1Y32Mil6UEU

2013-05-09

赶一下白宫请愿的早集

最近关于白宫请愿的话题很火,在其光环作用下,飘猪老鼠林昭神马的很快就过气了,连京温还没怎么热就已经要温了。我也想就这个事情说上几句,免得后面喋喋不休。

有意无意中看到知乎上的某个问题的一大串回答让我是又好笑来又好笑。很多人连白宫请愿是怎么回事都没搞清楚,就开喷。我们先来看看人家的Introduction。需要注意的是以下这句:
If a petition gets enough support, White House staff will review it, ensure it’s sent to the appropriate policy experts, and issue an official response.
我替不懂英文也不知道Google Translate的同志翻译一下:如果一份请愿得到足够的支持(票数达标),白宫工作人员会审读,确保它会被发送给适当的政策专家,并给出一个正式的回应。

所谓正式的(或者说官方的)回应,也可以是「这个问题不在联邦政府职权范围内,我们无能为力」这种。这只是一个政府与民众互相沟通的渠道而已——人家说得很明白,一个工具。外国人办事比较认真,回复不像我们这边这样马虎了事。但人家也并没有义务一定要给出某个特定的回答,更不是某些人理解的「票数达标就得照我说的做」——说实话,我真怀疑这种智商属于人类吗。

实际情况是,如果票数没有达标,请愿根本不会到白宫工作人员那边。系统自动把它归档(Archive)了。FAQ上说得更直接——Remove。当然,我并不怀疑一毛们的刷票能力。不过,别忘了人家还有Moderation Policy。另外,每个请愿的页面上,都有着红色的Report this Petition as Inappropriate。出于尊重读者智商的缘故,看到这里的我就不翻译了。

事实上,一毛们的搅混水行为,对白宫或白宫网站甚至都形不成什么有效的骚扰。条款规定,多份陈情可以用一份回应来应对。要是垃圾请愿多得厉害,还可以把适度条款搬出来。
骚扰行为更主要的意义,在于向国内民众展示类似这样的意思:「看,白宫网站是搞笑用的。」以期阻止将来的更多请愿可能会对干部子女和家属造成的实质性威胁。题外话戳一句:小的们真是太体恤太懂事了。大王叫伊去巡山,伊把山上的屎都给吃了。

具体在眼下这个环节,我希望能阅读到这里的读者在将来能做这样两件事情:
  1. 对于有意义的请愿,在自愿的前提下,投上自己的一票。足矣。
  2. 对于无意义的请愿,不投票,并且举报。这也算意见表达,别给脑残和一毛留情。
我相信,看到这里的读者都是有足够判别能力的。不再啰嗦了。
不过我还想对某些人说一句:注意你们的IP地址,别老用61398的。你们没看见外交部发言人每次都满嘴的沫吗?

2013-05-05

让GoAgent直接使用自己指定的Google服务器IP地址

一直很疑惑,为什么proxy.ini中把[google_cn]下面的hosts改成了自定义的IP地址,但日志中显示GoAgent客户端还在寻找其它的Google服务器IP,并试图作为GAE代理进行连接。


因为一直都算还能用,所以就没下决心来解决这个问题(发现自己真的很懒)。但最近GoAgent客户端自己找到的IP越来越不靠谱,有时候甚至会导致一半左右的请求都会被重试,实在是太浪费时间了。而且第一次使用的时候DNS解析导致的等待也让人很不爽。所以终于决定来看看到底是怎么回事。

一看代码就明白了,问题很简单:GoAgent客户端针对[google_cn]这个Section有特殊行为。它会去从www.google.cn和www.g.cn这两个域名进行解析得到IP(好像会无视proxy.ini中自己设的地址或IP),然后进行建立SSL连接的测试,根据测试情况决定是否切换到[google_hk](这又是一个特殊行为)。

这种做法,对于初级用户可能会比较适合。但如果想自己控制GoAgent客户端使用哪个Google服务器IP,最好是另外开一个Section,比如[google_cn2]或[google_us]之类。这样就不会碰到代码里面预设的这些特殊行为了。

PS: 以上内容基于GoAgent 2.1.11/2.1.15测试。

2013-04-10

修改GoAgent客户端以支持Mega


为了能用来访问Mega,对GoAgent客户端代码做了略微的修改。不过首先要说明一下为什么会有这个修改。

Mega是个总部位于New Zealand的网盘服务。服务器当然全世界都有,但至少在我这边ping值不好。严重的时候600ms以上,并且丢包。这样的话,不管本地有多少带宽,实际上也是不可用的。总不可能花上一整天的工夫来传一个ISO吧。
开着VPN会快,但流量和费用都是问题。于是很自然地想到了是否可以通过GoAgent之类的GAE代理来访问。Google服务器与Mega之间的带宽应该是不成问题的,而Google服务器与我之间的速度也是我可以在一定程度上控制的。不过测试下来发现GoAgent不支持OPTIONS这种HTTP Method,而且这个局限性是GAE导致的。GAE只支持GET/POST/HEAD/POST/DELETE这五种HTTP Method。偏偏Mega在登录和上传下载的时候都会发OPTIONS请求,于是这个方案一度被搁置了。
后来Mega的速度进一步下降,有时候一整天都传不完一个100M的文件。于是这个方案又被我拿出来考虑。这次我准备绕开服务端的限制,直接从客户端下手。OPTIONS请求涉及的数据量是很小的,文件传输用到的CONNECT之类才是主要的带宽压力。因此可以让客户端在遇到GAE服务器无法处理的HTTP请求时,直接将其发到目标服务器。由于Mega目前还没有被GFW给IP黑洞,因此应该可以在一种「混合模式」下被通过GoAgent访问到。

下面介绍一下修改方法,以GoAgent 2.1.15版(2.1.17还需要服务端改动才行)为例:

首先在local/proxy.py中找到这两行:
"""rules match algorithm, need_forward= True or False"""
need_forward = False
第一行是注释。而下面这个need_forward,就是用来控制是否把一个请求直接送出(FWD),而不是送去GAE服务器进行中转。

在后面的if语句前,加入这样的内容:
if self.method != 'GET' and self.method != 'POST' and self.method != 'HEAD' and self.method != 'PUT' and self.method != 'DELETE':
    if host not in http.dns:
        http.dns[host] = list(set(http.dns_resolve(host)))
    need_forward = True
非Python程序员也应该很容易读懂这段代码,不过要提醒一下:Python中缩进是很关键的,改代码时一定要用空格正确地缩进。
最后,别忘了把下面那个原来的if改成elif。

这样改过之后的GoAgent客户端,在遇到GAE服务端不能处理的那些HTTP请求类型时,就会把它们直接发到目标服务器上。
从理论上讲,这个小修改不会对GoAgent的翻墙能力有任何的增加,但可以让它具有更大的适用范围。一些原来不能用GoAgent访问的站点(比如上面提到的Mega),现在可以用它来访问了。GAE的流量按天计算(VPN一般按月,VPS也是)。并且因为可以使用多个GAE账号,因此流量基本上是免费且无限的。Mega那50GB的大空间,终于具有一定的可用性了。

2013-04-08

论天赋与竞争


今天偶然谈到一个话题。我发现,一个健康、聪明的宝宝,能够给爸妈省下非常多的钞票。换句话说,这就是体能、智能在生存竞争上的优势的具体体现。
只是,我觉得,原始社会的时候就是这个游戏规则,到现在咱们还是这个规则,这个社会体系是不是建设得太失败了一点?

我总觉得,人类社会发展到现在这个阶段,从技术或生产力上讲,应该已经有能力解决成员的基本生存问题(温饱)了。那么,区分成功还是失败,或者说人们奋斗的目标,应该是看一个人能不能实现自我的价值,做自己想做的事。有的人可能不擅长打猎,有的人不会种田,但是他可能会唱歌,这可以使他人愉悦。不需要一个人什么都会。即使他不会任何直接产生食物的技能,也不会饿死。社会化分工,这就是人类社会出现的意义。

在社会形态发展还不完善的时候,可能有的人还是会有生存问题。艺术家要是画卖不出去,也会有生计问题。所以这才需要社会形态继续发展。一方面生产力要更高,才能有足够多的资源来供养这些不直接生产生活资料的人。另一方面,需要保障制度的逐步建立,需要合同方式的完善,来消除因为成员个体的分离带来的沟通上的问题。——无论政治还是经济,本质上都是在解决沟通问题。

一个能够保障个人基本权利(包括生存权)的社会,才能更好地保障多样性。否则世界上将只存在猎人和农民,人类社会也就不会再进步。基因需要多样性,社会成员同样需要多样性。即使是所谓人渣,也有他存在的意义和价值。所谓的天赋,包括体能、智能,乃至于道德上的高低,都不应该成为淘汰个体的理由。

我并不是说天赋不应该有优势,而是说这些天赋上的(貌似)弱者不应该必然被淘汰。丛林法则是一种自然状态下低等级的演化规则,其重点是「适者生存」。而既然是「适者」才能生存,那么其演化所产生的结果必然越来越趋向于适应某一种特定的环境。一旦环境发生非常大的改变,或者说「游戏规则改了」。那么演化的结果就可能会变得毫无意义,甚至整个种族都会有危险。而其原因就是,本来也许能适应新环境的成员,在之前的演化中被无情地淘汰掉了。

我们人类是(目前看来)有一定智力的种族,也希望能拥有其它动物所不具备的改变自身命运的能力,那么就一定不能按照动物的演化规则来生活。所以我觉得,如果一个现代社会里面,成员还要为了基本的生存条件而奋斗,要像动物一样靠天赋进行生存竞争,这个社会的设计和演化是失败的。

不过也许这个社会中有一部分成员就是想让其他人活得像动物,弱肉强食。谁知道呢?!

2013-04-03

也谈「量刑要适度」


看到有的人在说什么「量刑适度」,真心觉得搞笑。
这种人,就是把公权和私权混为一谈分不清的那类人。

要动用公权力侵犯私权时,疑罪从无原则无疑是非常重要的。因为双方力量并不对等。
而个人之间的质疑乃至问罪,没有必要搞什么疑罪从无。双方力量都是对等的。一方可以质疑,另一方也可以回应。一方可以乱扣罪名,另一方也可以把真相公之于天下让大家看看。就算捅到媒体,舆论一边倒。你可以骂别人傻逼,可以觉得别人真的是傻逼,可以骂群众无脑盲从,可以愤世嫉俗愤然删号滚蛋。但人家就是有这个权利。发表自己的见解,没有问题,但想修改游戏规则,是很不明智的。

从另一方面看,对于公权,不仅不能疑罪从无,还应该要用最恶意的角度去揣测。这个原则,也应该可以适用于双方力量明显不对等的情况。没有人会认为保护伞公司不应该接受政府同样的待遇吧?

更何况,有的时候,超出目前状况之外的恶意揣测,往往是为了自我保护,而不是要加害于他人。黄浦江上的猪,没有检测出可感染人类的病毒,就应该为此放一百二十个心了吗?目前还没有证据显示H7N9可以在人类之间传染,于是友人咳嗽高烧也可以不用防护地去探病吗?没有上过CSDN,于是就不用修改密码了吗?没有反对过伟大的党,于是就可以举手支持73条了吗?如果只是走一步看一步,人类存活不到现在。你可以自己这样做,但要求别人也跟自己一样,要么是站着说话不腰疼,否则不是脑残就是别有用心。

2013-03-21

如此错误检查,还不如不要

今天在查一个Bug。表面现象很奇怪,至少让测试组觉得很奇怪。故障现象是这样的:
有一个列表框,如果里面只有两条记录,那么没事。如果有三条以上的记录,那么在删除记录时会「随机」出现程序无响应的情况。

上面这段错误描述中,「随机」二字之所以打引号,是因为其实不是随机的。只是测试组的同事没有找到规律而已。只要按从下到上的顺序进行逐个删除,就很容易遇到故障。

这种现象,有经验的程序员一看就知道跟序号、数组之类的东西有关。
果不其然,在代码中我找到了这样的一段:
pItem = ListView->Selected;
for (; pItem != NULL;)
{
    int iWhich = (int*)pItem->data;
    if (iWhich >=0 && iWhich < ItemArray.GetSize())
    {
        ItemArray.RemoveAt(iWhich);
        ListView->Delete(pItem->Index);
    }
    pItem = ListView->Selected;
}

这里解释一下,ListView->Selected能拿到列表内当前选中的第一行,而ItemArray是一个项目组自己实现的数组对象。这两个东西都没啥毛病。

坑爹的代码就是这一句:
if (iWhich >=0 && iWhich < ItemArray.GetSize())
我能理解,写的人是想把iWhich的取值限定在某个区间。因为这个iWhich接下来会被用作数组下标。如果越界,后果不堪设想。有这个意识,是好的。
但是,仅仅意识到这个问题,还不够。接下来还有问题了:iWhich会越界吗?什么情况下会?

我相信,如果写这段代码的人当时有问过自己这个问题,那么就不会有这个Bug了。
这个iWhich来自一个pItem->Data,这是一个ListView行对象附带的DWORD类型的数据,其值是由使用者(程序员)赋值的。
也就是说:如果你给它正确赋值,那么就不会有不正确的值出现。如果它有问题,那么是你前面的程序造成的。

也许还是有程序员会担心,这个pItem->Data会不会什么时候被改掉。也可能赋值的地方是另一个程序员写的。而写这段代码的程序员出于防御性编码的目的,写下了这样的判断。那么OK,没有关系,判断就判断吧。可是判断为FALSE的时候怎么办呢?
他什么都没有做。

其实,判断为FALSE之后,从ListView中删掉这一行,应该是安全的。从上面的代码中可以看出,删除行的时候,只用到了pItem->Index。这是由操作系统自行维护的值,不会存在Data那种「可能没有正确赋值」的情况。
又或者,实在不放心,那么直接把整个函数return掉,甚至报个错,也是可以的。
现在的处理方式就直接导致了死循环。这是很糟糕的情况,单核机器的用户甚至可能会几乎没有机会来做什么处理。

好了,上面这些就是我这篇博文想说的:有防御性编码的意识,很好!但是处理方式也要正确有效。你不应该在避免一个错误的同时,引入另一个错误。

那么,最后还有个遗留问题:iWhich为什么会越界呢?
我想,对于合格的程序员,这个问题不应该成为问题。所以我就不说了。

2013-03-03

用了五年的笔记本XP系统重装了

其实重装系统这种事情也没什么可写的。无论是重装系统还是拆机换CPU,我都已经轻车熟路了。不过过程中还是遇到了几件值得加深记忆的事情,权且当作流水帐记一下吧。

这次换了个CPU:把AMD MK-36换成了AMD TL-66。MK-36只是64位而已,但由于用的是XP 32位版,其实没有任何优势。TL-66增加了主频,而且变了双核,可以说是鸟枪换炮了。这要搁在当年买笔记本的时候,不说TL-66根本没面世,就算有得选,也不是加几百块钱能了事的。现在淘宝上一百来块就搞定了,还送了个耳机。

换了CPU,Chrome卡的问题还是没能解决,于是把系统给重装了。本来准备上Win7,结果发现装64位版本的话disk.sys不支持,也没有官方驱动能解决这个问题。如果上32位版Win7,显卡驱动可能也是个问题。这款笔记本官方只提供Vista的驱动。最后还是装回了XP。反正2G内存也只有XP能舒服点。以后这台机器就不要考虑其它操作系统了。

但重装了系统,仍然没有解决Chrome卡的问题,而且卡得愈发厉害。最后终于发现是电池的问题,拔掉电池就一切正常了。现在依然很正常地在用,只是谷歌拼音的切换略慢,有时候刚呼叫出来的时候会丢几个键击。电池上有个垫脚,导致现在本子变成三脚猫,不得已只好找了一叠名片来垫上。