游戏中的物品交换逻辑抽象
如果总结一下游戏的内部逻辑,或许会让游戏变得很无趣,不过程序员大概会很高兴,比如说我。
如果把用户的各种信息都抽象成“物品”、“个数”,那么所有的游戏逻辑都是用户之间的物品交换。
为了方便描述,我来自定义一套语法表示这种交换。这里用的是类BNF语法,如果没耐心可跳过。
物品交换 := 物品描述 “=>” 物品描述
物品描述 := 物品描述 “+” 单个物品描述 | 单个物品描述
单个物品描述 := 用户 “(” 物品 “,” 数量 附加属性 “)”
物品 := 物品名称 | 物品函数
物品函数 := rand “(” 物品列表 “)”
物品列表 := 物品列表 “,” 物品 | 物品
附加属性 :=”,” “{” 附件属性列表 “}” | “”
附加属性列表 := 附加属性列表 “,” 属性 | 属性
属性 := 属性名 “:” 属性值
数量 := 数字 | 数字函数
数字 := 非负整数 | MAX
数字函数 := auto_change “(” 初始值 “,” 每秒变化步长 “,” 边界值 “)”
| max “(” 数字列表 “)”
数字列表 := 数字列表 “,” 数字 | 数字
函数说明:
- rand代表在一系列物品名称中返回任意选一个物品名称。
- MAX代表该物品最大数量值
- max代表从一系列数字中返回最大值
- auto_change代表从初始值开始,每隔1秒时间就加上步长,直到达到边界值为止不再变化。auto_change并非如其名一般会自动变化,而是在下次请求该物品数量的时候才计算当前值。步长可以为负。
希望不要看这些BNF晕了头……直接看例子其实也可以。
列举一下常见的游戏场景背后的数字逻辑:
- 用户A用100游戏币买一个桌子获得10点经验:
- A (游戏币, 100) => A (桌子, 1) + A (经验, 10)
- 注:这里把游戏币、经验都当做物品来处理,从程序角度来说这没有问题,虽然直观上有点怪。
- 用户A使用10点幸运点打开一个宝箱得到一个物品,这个物品是玩具1、玩具2、玩具3中随机的一个:
- A (宝箱, 1) + A (幸运点, 10) => A (rand(玩具1, 玩具2, 玩具3), 1)
- 用户A送用户B一个玩具,留言“my present”,获得10点经验:
- A (玩具, 1) => B (礼物, 1, {留言: my present, 关联物品: 玩具, 关联数量: 1, 时间: YYYY-MM-DD}) + A (经验, 10)
- B (礼物, 1, {留言: my present, 关联物品: 玩具, 关联数量: 1, 时间: YYYY-MM-DD}) => B (玩具, 1)
- 用户A在一号树坑种下一棵树,将在3000秒后长成桃子共4颗,其中最多2个可以被偷走,而B偷走了1个获得10点经验,A则收走了剩下3个,获得20点经验:
- A (树种子, 1) => A (成长中的桃子, auto_change(0, 1, 3000), {土地号: 1})
- A (成长中的桃子, 3000, {土地号: 1}) => A (私有桃子, 2, {土地号: 1}) + A (公有桃子, 2, {土地号: 1})
- A (公有桃子, 1, {土地号: 1}) => B (桃子, 1) + B (经验, 10)
- A (公有桃子, 1, {土地号: 1}) + A (私有桃子, 2, {土地号: 1}) => A (桃子, 3)
- 注:当任何一个可以看到桃子树状态的用户刷新状态且第二步中输入的数值满足要求时,第二步物品交换就会发生。
- 用户A接了一个任务X,用玩具1和玩具2,可以换得100游戏币和10点经验,任务有效时间为30,000秒,且A最多能接5个任务:
- A (可接任务, 1) => A (任务X, auto_change(30000, -1, 0))
- A (任务X, max(1, MAX)) + A (玩具1, 1) + A (玩具2, 2) => A (游戏币, 100) + A (经验, 10) + A (可接任务, 1) + A (已完成的任务X, 1)
- A (任务X, MAX) => A (可接任务, 1)
- 注1:A的初始可接任务设置为5就能限制A最多接5个任务
- 注2:第三步为取消任务逻辑
- 用户A将一个桌子从储物箱里面挪到1号房间,又从1号房挪到了2号房:
- A (桌子, 1) => A (桌子, 1, {x: 10, y: 0, room: 1})
- A (桌子, {x: 10, y: 0, room: 1}) => A (桌子, {x: 20, y: 10, room: 2})
仅仅通过这些很基本的物品交换逻辑,(理论上)就可以实现一个比较复杂的游戏数值逻辑了。只要实现一个服务,把所有这些可能的物品交换逻辑都放在某个配置里面,游戏核心就出来了。
这种做法也有不足,有些时候客户端需要从右边推导出左边的可选值就比较麻烦。比如物品1可以由游戏币或充值币其一换得,客户端需要根据物品1反查出游戏币和充值币各自需要的数量。这在技术上其实可行,只是需要花费更多精力去想明白里面的需求。
Amazon AWS使用体验
近几个月来简单体验了Amazon AWS大部分服务,现在分享一下使用感受。
Amazon EC2
从各方面来看,EC2应该算是Amazon的王牌产品。EC2功能全面,它可以在数分钟内launch一个实例,可以(理论上)自动提升/降低机器性能配置,可以绑定EBS成为具有高可用性特质的计算核心,可以摇身一变成为ELB成为一个load balancer。这些功能非常实用,特别适合针对国外市场且快速增长的小网站,可以在前期完全不受机器约束。
EC2机器的内核在申请时指定,有很多选择,如果有闲心,还可以做一个自己专用的,我们没用这个功能,实在太繁琐了。EC2提供了完全的root权限,可以随意安装软件,由于EC2一般带有足够的硬盘空间(Large Instance就自带850GB磁盘空间),装个mysql当数据库机器也完全没有问题。
EC2最大优势是灵活,最大劣势则是性能。EC2的IO性能欠佳,当EC2机器作为mysql数据库时,系统吞吐量只能到3~10 MB/s,相比而言,在同样数据同样程序条件下,使用真实机器则能轻松达到30MB/s。EC2的CPU是多个实例共享的,一般stolen的CPU都在10%左右,对于nginx+php-cgi这样的组合,EC2高端配置也不太能撑住压力,总是CPU吃紧。
虽说EC2劣势明显,但由于互联网应用一般都很容易做到水平扩展,它的灵活性可以很大程度缓解这个劣势,不会有太大问题。不过最终我们还是放弃了EC2,主要因为今年5月Amazon云计算服务出了大量的事故,仅5月头两周,在同一个机房就出了5起大事故,我们在3起事故中受影响,每次都很不幸的都是单点EC2出问题,最长的一次在高峰期中断了6个小时服务,损失巨大。最郁闷的是,到了5月下旬,有一台跑数据库的EC2机器莫名奇妙磁盘损坏,还有一台莫名其妙的无法ssh,让我们感觉越来越不靠谱,于是决定逃离。云计算的可用性始终值得担心。
EC2的价格相对传统IDC要贵不少,按高端机器High-Memory Double Extra Large每小时1.2US$来算,每月就要花864US$,再加上流量费,很容易达到每台服务器1000US$的水平。
Amazon RDS
RDS是一种数据库托管服务,通过RDS toolkit可以很方便的创建RDS实例并立即使用。据Amazon帮助文档说,RDS的数据库软件维护、升级、优化的工作全部由Amazon负责,用户只需要使用功能即可。
可惜,RDS纯粹是一个看起来很美的东西。首先是RDS为了同时支持异构的数据库,牺牲了很多灵活性,例如,因为无法访问RDS的物理机器或存储空间,使得跑着mysql的RDS实例连记录slow log都成了难事,更别说什么err log,出了问题只能一抹瞎。如果要获取bi/bo这样的数据需要使用专用的RDS toolkit完成,很难集成到现成的监控工具里去。其次,RDS底层使用的是类似于EBS的网络存储,其IO性能差到一个令人发指的地步。一个很感性的数据是:使用RDS Double Extra Large DB Instance跑mysql时慢查询占1%,同样数据转到EC2 High-Memory Double Extra Large实例后慢查询降为0.01%。最让人无语的是RDS价格高于同等配置的EC2+EBS价格,每小时都贵超过0.3US$,相当于贵25%,省下来的钱用来雇专业运维人员绰绰有余,而且RDS功能和灵活性还远弱于后者,不怕折腾但怕花钱的人不建议选择RDS。
如果执意要使用RDS,那么一定要注意RDS没有固定IP,每次重启实例(或过一段时间)都会变,程序连接的时候最好写域名,当然,这会损失性能。此外,RDS可用性也没宣传的那么高,我们5月最先挂的就是RDS实例,一挂就是好几个小时。
Amazon S3
S3是少有的看上去物美价廉的东西,只需要花几美元就可以拿到几十GB空间,对一般的互联网应用来说绰绰有余。
S3提供丰富的API和工具上传文件。对firefox用户来说,有一个专门的S3扩展可用,可直接上传本地文件。还有一个s3curl.pl工具,可以用命令行传任意文件,可以方便的做到自动化。
S3存储的文件可以直接通过域名从外网访问,不过比较郁闷的是文件尾部不能带“?“,一切基于文件名后面加随机串的避免缓存的做法都会失效。
Amazon CloudFront
CloudFront是一种CDN,它的访问速度据称不错,我们并没有实际使用过。
我们不使用CloudFront主要是因为很难控制文件在CloudFront中失效。根据我们的程序结构,我们需要在静态文件后面加version信息强制用户在必要时更新到最新版,现在由于CloudFront既不能手动invalidate某个文件,也不能保证CloudFront上缓存的文件及时更新(可能更新会花24小时),我们需要修改现在的策略才能使用到它。
CloudFront是根据用户所在网络来收费的,对于国内,CloudFront使用的是香港价格,贵于美洲和欧洲,不太划算。对于欧美市场,我也不清楚欧美CDN的价格究竟如何,无法比较。CloudFront在欧美的流量费用与EC2相同,不过由于没有按小时计的租费,总体价格会比EC2提供静态文件服务便宜很多。
Amazon CloudWatch
CloudWatch是一个性能数据监视器,对于这个东西我只能说太小儿科了,没看出来有什么实际的用处。一旦开启CloudWatch,Amazon就会帮忙收集开启了这个服务的instance数据,包括CPU和IO信息等。可惜,数据精度不高(大概是几分钟一次统计),数字也只是个大概值,能看的数据也很少,无论对入门者还是高手来说,都不能带来太多价值。
CloudWatch看上去比较好的是可以配合EC2/EBS/Load Balancer toolkit做一些auto scale的事情,自动提升/降低EC2/EBS参数,还可以(理论上)自动launch新实例。这些东西我们都没尝试过,有兴趣的同学可以试试。
CloudWatch价格不贵,一台机器就只用0.1US$而已,不过一般还是不建议开,装一个其他监控软件更靠谱。
Amazon Premiun Support
Premiun Support包括即时的电话支持和一个专有的提案系统。从服务质量上来说,Amazon的客服总是很热心,响应速度也很快,专业素质也不错,很不错。
Premium Support非常贵,简直是抢钱。它是按照每月总花费来算价格的,最多增加总花费的16%,最少为无限接近于10%,代价十分明显。
结语
Amazon AWS提供丰富功能,还有不少toolkit和API,适合小应用使用。对于要求较高性能或较低成本的应用来说则不太适合,折腾一下IDC应该达到更好效果。
amazon云计算服务初体验
本周第一次实际使用amazon的云计算服务,主要包括Amazon Elastic Compute Cloud (EC2) 和Amazon Relational Database Service (RDS) ,有一些感受先记在这里。
先说说EC2。它很类似网上的虚拟主机服务,拥有root权限,可以完全控制上面的服务,默认拥有一个对外的dns地址,并可以开放80和22端口。
从性能上来说,EC2的表现很不错。从功能上来说,EC2最大的限制就是端口和ip。对于端口,80和22只能满足最基本的web应用,如果要使用自定义端口/协议的socket方式提供服务,恐怕就比较麻烦了。对于ip,默认每个EC2服务器都是动态ip,最安全的方法是用自己的域名做CNAME转发来对外提供服务。有一点需要注意,EC2服务器的外部dns和ip是绑定的,一旦给它换ip,例如为它花钱绑定一个固定ip,它的外部dns也会变,这就需要修改自己域名的CNAME记录。可麻烦就麻烦在EC2实例换了ip后立即就换外部dns,而普通dns缓存则可能会很长,这将造成在换ip的这段时间内服务可能不稳定。为了长治久安,还是一开始就申请一个固定ip会比较稳妥呢。
EC2对外部流量收费,内部不收费,或者在跨区的内部服务器通信时收非常少的费用。EC2服务器单机流量上数十MB/s不成问题,不过随着提供服务的EC2服务多了起来,我们自己搭的load balancer服务器已经有点力不从心,看到EC2有专门的load balancer服务,并且还挺便宜,下周一定试一试。
相比EC2,RDS则非常的不自由,它不能用ssh登录,数据库程序也不能控制版本,还有不少数据库配置不能改。如果在RDS上使用mysql+innodb,那么有不少不爽的地方:
- innodb_data_file_path配置不能自己控制。RDS会将这个设置为ibdata:10M:autoextend,且不能改,这对高并发的读写会造成问题。
- 没法在本地以文件方式保存slow log。
- 无法直观的监控机器性能,只有一些RDS专用的命令行工具能得到一些实时的数据。
- 无法对操作系统进行进一步调优,甚至都不知道底层跑的是什么操作系统。
不过RDS也有一些好处:
- 存储空间可以无限增长。就像flickr的相册一样,每月都有一定的增长额度,总存储空间可以无限增加。
- 数据安全性可以保证,可以方便的制作snapshot和完整备份,备份所花的存储空间只要不大于每月增长额度就完全免费。
- amazon会负责数据库软件的安全性,给它及时打补丁。
综合来说,RDS提供的控制手段有限,不太适合对性能有较高要求的应用。从实际是用来看,同样的程序和网络环境之下,Double Extra Large DB Instance上的mysql+innodb明显慢于Double Extra Large EC2上自己安装并优化的mysql+innodb,一个典型的数据是:使用前者,慢查询比例是1%,使用后者,这个比例立即降为0.01%。
放弃RDS也就意味着放弃RDS的各种好处,为了弥补这个不足,可以为EC2申请Amazon Elastic Block Store (EBS) 。EC2+EBS就和RDS没什么两样了,只是管理起来还是有点费神,综合的价格应该和RDS差不多。我这里还没有真正尝试过EC2+EBS的方案,以后等存储出现瓶颈了再说了,现在EC2自带的850G存储绰绰有余。
amazon的服务还是挺让我满意,特别是phone support反应很快速,服务人员也挺专业,挺好。不过我并没有其他云计算服务的使用经验,没有比较,也不清楚amazon云计算在业界究竟出于何种地位,这个以后再慢慢去了解吧。
如何衡量每一次决策的价值
最近和朋友聊到这个话题,顺便发散一下,说说完整想法。
我首先觉得这是一个伪命题,不太可能有种方法能衡量决策的价值。决策有时效性,过了这个时间,决策的价值就发生变化,无法衡量。此外,决策也没有绝对的好或不好,往往都是各方权衡的结果。相对来说,决策的过程比较容易衡量,如果每次能按照最合理的过程来进行决策,决策本身就应该具有最大的价值。就能从侧面衡量决策本身的价值。
我认为合理的决策过程是:找到所有决策相关的人,推动大家在合适的时间达成一致。Find out stake-holders, and push them to reach a consensus at right time.
这句话非常的tricky,里面暗含很多意思。比如谁去负责push、stake-holders如何定义、right time究竟是什么时候等。
如果我是决策的最终负责人,那么负责push的人就应该是我,这个最容易回答。
要界定stake-holders就要看这个决策一旦制定该如何实施,所有在实施过程中会涉及到的关键人物都应该算作stake-holders,决策的过程应该是所有stake-holders buy in整个决策的过程。比如要决定一个feature是否应该做,首先可以假设要做,然后就可以知道需要有PM来决定feature细节、要dev决定如何实现、要team lead决定如何将这个插入项目计划之中,等等,这样就要引入相关的负责人来进行评估。此外,这个例子里面暗含一个角色,这个feature的提出者,他/她有可能是一个dev、客服甚至公司外的用户,他/她应该也以某种形式参与进来,直接参与或者由某个人(PM)代理。集齐所有stake-holders,每个人buy in这个决策的后果(该干活的干活、该承担风险的承担风险等),就可以让这个决策通过,否则,修改决策本身,继续讨论。
要确定right time非常困难,但可以有几个简单的衡量标准,比如要做出决策的各方面条件是否满足、决策本身是否依赖于某些时间相关的因素等,总的来说是一个仁者见仁的过程,需要决策的最终负责人来给出right time的预期。需要注意避免把right time看做是deadline,任何schedule都会遇到意外,而right time则不能出意外,deadline一定要早于right time,这样才能使得这个觉得真的right。此外,right time也不是ASAP(as soon as possible),过于草率的决策往往结果不会太好。
“如何做决策”是一个需要长期积累的技术活,我现在经验尚少,只有这些肤浅的认识,欢迎所有读者过来批评指导。 :)
Scrum的基础知识、常见问题和解决方法
Scrum是一种敏捷开发过程,最基础的介绍推荐看这个:An Overview of Scrum
关于Scrum运行当中常见问题及解决方法,我曾写过一个slides,在公司内部分享,现在重新整理了以后共享在这里:近距离接触Scrum
关于Scrum,最常见的误解是Scrum master == team lead,这是不对的。Scrum master可以是任何人,不需要担当任何管理职责,在Scrum中只是一个推动者。
除了Scrum过程本身,还需要整个team共同遵守一些游戏规则。这些规则可以根据实际情况来制定,不过尽量不要超过3项,毕竟人能同时记住的事情一 般不超过4项,还得留1项给当前的工作。
例如,可以制定以下规则:
- No suprise:大家都有义务保持信息通畅,及时有效的将stakeholders关心的消息传递给他们,尽量不要让任何人对结果产生suprise。对于不紧急的沟通,可以选择在daily scrum的时候将信息传递出来,尽量不打断其他人的日常工作。
- Focus on one thing:很少有人能够快速的在多个任务之间切换还不影响效率,集中精力干好一件事情是提高效率的最简单的做法。要做到这一点,必须懂得如何对手头工作进行排序,最简单的就是focus on high priority work,这也就是Scrum过程中安排work item并对它们排序的意义。
无论如何,Scrum过程只是一个工具,而非目的,任何事情还是以delivery为重,流程本身执行是否合乎规范完全是次要的。
一个时代的终结
明远正式宣布离开,他的时代终结了。
我在明远手下做事也有一年多了,回忆一下,我们从未有过正式的交谈,他大概也不会认识我。他是我老大的老大的老大,他是一个产品人,他喜欢足球,而我只是一个沉默的程序员,对任何体育运动都不感冒,我们没有聊头。不过我还是得感谢他,在我看来,是他让我在工作中找到了久违的激情。
明远很喜欢在季度沟通大会上不断激励我们的创业激情,讲述自己对未来、对竞争对手的分析,鼓励我们勇于接受挑战,实现心中理想。他的演讲很有特点,语调平缓,语速适中,整个过程很少带有废字,逻辑十分清楚,能用平淡的语言激起他人共鸣。他在演讲方面的老成与他的年龄很不相称,哪看得出他比我还小啊。
在团队活动中,我印象最深的是有次部门全体员工集体出游的时候他穿着短裤拖鞋混迹于员工之中的形象。明远没有架子,在团队活动中他就是个小孩,不修边幅,很不起眼。这个样子的明远才是最真实的,而他的内心也许根本没有那么成熟,站在讲台上的那个过分成熟的身影终究会成为限制他自身发展的禁锢。
由于种种我不知道的原因,明远选择了离开,有点突然。东宝为明远在公司这段惊心动魄的创业历程写了篇《为了即将忘却的纪念》,可惜我只经历了这段创业旅程的最后一段。(东宝的文章已经看不到,可以看其他媒体的转载)
明远,愿你在未来能永葆激情,创造更大辉煌。我看好你。
Firefox插件(plugins)开发实用指南
Firefox插件可实现强大功能,但其中麻烦事情不少。写这个实用指南首先是为了方便自己记忆,免得以后再次栽倒一些坑里面,如果能帮助其他人,则是更好。这个指南不是为了手把手教读者开发插件,而是作为一个FAQ,解决各种诡异问题。
Firefox拥有众多的扩展(Extension),开发扩展也非常容易,不过有一些事情还是无法用扩展解决,需要访问操作系统的底层功能,这就需要写插件(plugins)。例如flash就是一个插件而不是扩展。
Mozilla提供了一系列的教程和文档,虽然很不详尽,众多重要的API语焉不详,但至少是一个好的开始。
最需要阅读的是plugins API和使用入门。这是一个相当长的文档,如果看完所有的内容会花费大量的时间而且还会很晕,这里列一些重点供参考。
- plugins基础概念
- 写第一个插件(只需要关注Writing Plug-ins这一节所谈到的内容)
- 获得一份firefox的源码,比如firefox 3.6。plugins的例子可以在源码里找到(modules/plugin/sdk/samples),如果出了问题还可以自己编译一个debug版的firefox来调试。
- 了解浏览器能提供什么功能
- 制作插件的安装程序,推荐用扩展的方式安装插件,有无数的好处
完成以上这些内容以后差不多就已经可以实现自己的插件了,一般而言,参照着例子来做开发不会有什么问题,只是有不少细节需要留意。
Firefox plugins开发的众多奇怪的约定(假设plugins已经被正确安装)
有些约定非常奇怪,不要问我为什么,天晓得开发firefox的牛人们怎么想的。
在Windows下,plugins必须满足以下条件才能被firefox检测到:
- 插件的名字必须是np*.dll,也就是必须以np开头,.dll结尾
- 插件dll资源的语言必须为LANG_ENGLISH,code page必须为1252。在rc文件里是这么写的:
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
- 插件dll的VERSION_INFO里面必须包含以下值:
VALUE "MIMEType", "application/x-your-mimetype"
这个MIME就是<object>标签引用插件的唯一凭证。
在Linux下,plugins必须满足以下条件才能被检测到:
- 插件的名字必须是lib*plugin.so,即以lib开头,plugin.so结尾
- 插件必须实现NP_GetMIMEDescription和NP_GetPluginVersion,并返回合适MIME字符串。注意,这个字符串并不是普通的MIME,是有特殊规则的,详见前面这个链接的内容。
- 插件so不要静态链接gtk、opensll、pthread、z等系统库,这会在不同linux平台上因为符号表的问题遇到各种运行时错误
特别需要说明的是,NP_GetPluginVersion、NP_GetEntryPoints等关键函数没有任何官方文档介绍它们,只能根据例子来猜,反正没事就别改它们的实现,copy例子中的代码就好。
firefox插件开发注意事项
写firefox插件的一个基本习惯是,经常编译代码并运行它,保证你的插件还能工作。只要firefox无法加载dll/so,或者加载出现任何错误,都会悄无声息的忽略这个插件。时常关注一下about:plugins,看看插件是不是还在这个列表里。
firefox插件从窗口模式上可分为windowless和windowed两种。其中,windowless模式的文档较多较全,是firefox比较推荐的模式,坑比较少,这里就不多说了。windowed模式则相反,需要好好说说。
无论在Windows还是Linux上,windowed的插件都拥有独立于浏览器页面的窗口。firefox会通过插件的NPP_SetWindow来告诉插件当前窗口的情况。
关于windowed插件有两个诡异问题需要注意:
- Windows平台下,插件窗口默认会响应WM_CTLCOLOREDIT、WM_CTLCOLORLISTBOX、WM_CTLCOLORBTN、WM_CTLCOLORSTATIC消息,并设置一个默认的背景色。这本来没问题,但在Windows XP下,这个颜色居然永远是黑色,而不是默认系统背景色(通常是白色)。最好subclass这个窗口并且拦截这些消息,不要让firefox去处理它们。对于插件来说,firefox处理这些消息只是帮倒忙而已。至于firefox还帮了哪些倒忙,可以去源码widget/src/windows/nsWindows.cpp的nsWindow::ProcessMessage()去围观。
- Linux平台下,NPP_SetWindow传入的NPWindow指针中虽然有一个ws_info成员,这个成员里面也确实有一个display变量指向X Window的Display结构,但绝对不要真正使用它,否则可能会导致firefox直接退出,据说这可能是firefox的一个bug。
测试firefox插件小技巧,测试方面的高手可以无视
测试插件前建议先在firefox里面创建一个新的profile(帐号)。这样可以创造一个最干净的开发环境,避免被其他扩展/插件干扰。
默认的profile名叫default,在命令行里输入firefox -p default就可以使用这个profile。如果只是输入firefox -p,会弹出一个对话框用于选择profile。这个命令在Windows和Linux下都可使用。
无论是哪个平台,调试插件的方法都很类似。
Windows下可以用VC以调试方式启动firefox,载入插件时调试器会自动载入对应的符号,捕捉发生的异常或者设断点都很方便。
Linux下直接用gdb就好,细节应该不用多说。有一点需要注意,系统默认安装的firefox命令(默认放在/usr/bin/firefox)是一个shell脚本,真正的可执行文件名字需要打开这个脚本自行查找。
实现firefox插件的基本功能
firefox为插件提供的接口十分原始,很多功能默认没有实现,下面提供了一些思路和方法。
- 让插件接受焦点:默认情况下,<object>标签不能获得焦点,必须指定tabindex。
- 在插件中使用tab键跳到下一个element:没有好办法,必须自己手动将焦点还给浏览器窗口(Linux下不必如此),然后自己用NPN_*系列函数找到应该获得focus的DOM element,然后调用这个element的focus()方法。
- 隐藏和显示插件:直接设置<object>标签的style.display = “none”即可,但这里有个严重的副作用,firefox会调用插件的NS_PluginShutdown,销毁这个插件。如果不期望造成这种效果,要么别用这种方式隐藏插件,要么把插件状态保存在js里,再次显示的时候把状态设回去。
- 触发DOM事件:firefox没有提供任何便利的方法触发DOM事件,要在插件中做到这点,必须自己模拟js触发DOM事件的过程。例如,对于HTML事件,假设self是DOM element,js会这么做。
evt = document.createEvent("KeyboardEvent");
evt.initKeyEvent(
"blur", // in DOMString typeArg,
false, // in boolean canBubbleArg,
false); // in boolean cancelableArg,
self.dispatchEvent(evt);
对应的C代码就是
void FireHTMLEvent(NPP npp, const string & name)
{
NPVariant result;
NPObject *window;
NPVariant vDoc;
NPN_GetValue(npp, NPNVWindowNPObject, &window);
// 也许页面已经跳转了……
if (!window) {
return;
}
NPIdentifier sDocument = NPN_GetStringIdentifier("document");
NPN_GetProperty(npp, window, sDocument, &vDoc);
NPN_ReleaseObject(window);
// evt = document.createEvent("KeyboardEvent");
NPVariant evt;
NPObject* npDoc = NPVARIANT_TO_OBJECT(vDoc);
NPIdentifier createEvent = NPN_GetStringIdentifier("createEvent");
NPVariant eventArgs[1];
STRINGZ_TO_NPVARIANT("HTMLEvents", eventArgs[0]);
NPN_Invoke(npp, npDoc, createEvent, eventArgs, 1, &evt);
NPN_ReleaseObject(npDoc);
// evt.initKeyEvent(
// "blur", // in DOMString typeArg,
// false, // in boolean canBubbleArg,
// false); // in boolean cancelableArg,
NPObject * npEvt = NPVARIANT_TO_OBJECT(evt);
NPIdentifier initKeyEvent = NPN_GetStringIdentifier("initEvent");
NPVariant initArgs[3];
STRINGZ_TO_NPVARIANT(name.c_str(), initArgs[0]);
BOOLEAN_TO_NPVARIANT(false, initArgs[1]);
BOOLEAN_TO_NPVARIANT(false, initArgs[2]);
NPN_Invoke(npp, npEvt, initKeyEvent, initArgs, 3, &result);
NPN_ReleaseVariantValue(&result);
// this.dispatchEvent(evt);
NPObject * self;
NPN_GetValue(npp, NPNVPluginElementNPObject, &self);
NPIdentifier dispatchEvent = NPN_GetStringIdentifier("dispatchEvent");
NPVariant dispatchArgs[1];
dispatchArgs[0] = evt;
NPN_Invoke(npp, self, dispatchEvent, dispatchArgs, 1, &result);
NPN_ReleaseVariantValue(&result);
NPN_ReleaseObject(npEvt);
NPN_ReleaseObject(self);
}
暂时先写到这里,如果还有什么疑难杂症,欢迎评论,我继续添加。
我这程序员的一年
2008年12月30日,正是我在微软发farewell letter的日子。我当时已经拿到百度的offer,正在准备把自己的角色从微软的项目经理转换成百度的技术研发。角色的转变背后往往藏着各种故事,我当然也不例外。
从微软到百度,我只觉得这是我的幸运,并没什么值得夸耀的地方。我在微软的一年半时间里,技术上逐渐荒废,连自己也觉得堕落不已,有劲使不出。离开微软并非自我选择,但尝试走进百度,则是当时一个勇敢的决定,我重新抱起书本,打开已经陌生的Visual Studio,从头开始准备。
我参加的第一个百度面试是在普天大厦,部门是NS,当时有两个面试官同时面我。我用刻意的沉稳与简洁来掩盖自己的不安,整个过程好似梦魇,令我疲惫不堪。郁闷的是,也许因为HR之间没有沟通好,前一轮面试结束1小时后,我还要赶到信威通信,参加百度第二个面试,电子商务部的面试。本来在上一个面试中我已经把斗志与自信消磨的差不多了,我只好用自己的本色来面对这第二个面试。很奇怪,第二个面试反而比第一个轻松,我发现我的思维开始活跃起来,沉睡已久的程序员的细胞开始复苏,讲起各种技术竟也能变得流畅而不刻板了。
再之后,我觉得自己像撞了大运一般,两边的面试竟都走到了最后一轮,而且都还通过了,这个面试的经历为我今年的程序员之路开了个好头。
值得一提的是,在电子商务部最后一轮面试的时候,老大问我对未来的规划时,我犹豫了。我曾想,做项目经理、做管理似乎是一个程序员的必然发展道路,但对于我真的适合么?我已经厌烦那种push团队前进、营造团队氛围、制定远景方向这种事情,我更想这几年踏踏实实的做事,完善自己的知识体系。但当时我还无法打破自己的思维惯性,还是支支吾吾的说希望成为研发经理云云。直到加入百度半年后,我才坚定自己的想法,做一个简单可依赖的程序员,先从技术做起。
2009年1月,我加入了百度,这程序员的一年开始了,然后很快,这一年结束了。
我从没想过时间会过的这么快,这么紧张有趣。我从对Linux一窍不通,到现在都开始习惯完全用vi编码、在命令行中调试、负责服务器程序的优化,这种变化我自己都感到惊讶。
粗略统计了一下,这一年大约写了10K的代码,这个数字比一年前0行代码比起来当然是无穷大,但和原来本科七年写100K代码比起来,似乎也不算那么多。我当年做简历的时候就很惊讶,自己参与过的各种项目、自己写的各种小玩意,居然有那么多行代码,到今天我终于明白,其实这并不难,如果不是今年我把很多时间用在摸索上面,恐怕代码行数还得翻番。
我今年最大的收获是激活了程序员的基因,手指终于开始适应写代码啦,这是一个很好的开始。
本文写到这里似乎还没开始说这一年到底发生了什么,但确实已经要结束了。无论是接手康神留下的系统,还是从信威搬到百度大厦,这些都是外在的一些挑战与变化,相比自己重新选择未来道路这件事情来说,真的是微不足道。我这程序员的一年,恰好就是选择结果的体现,到现在我已经可以说,这个选择没有错,至少我比原来快乐。
架构成长之路
产生软件架构设计的动机是代码复用,当一段重复的代码在多个不同的模块之中重复多次、每个人都为这段代码写的吐血的时候,大家自然而然就想到了——我们要有一个“架构”,或者说准确的说是一个框架。
最原始框架一定是最基本的。从广义上来说,操作系统就是一个框架,它封装了每一个程序需要完成的基本任务,管理内存、磁盘IO、中断、输入输出等。不过操作系统能封装的东西有限,最关键的是,封装总是略显简单。比如,需要写一系列服务器程序,每个程序都要访问网络、保持长连接、管理连接池等,这些机制并不是几行代码就可以实现,需要一个稍微高一点层次的框架来解决这个问题。于是很自然的,层次的概念就出来了。
当框架有了层次概念之后,大家就可以各司其职,为完善各自的框架而奋斗,直到最终框架已经极为好用,每写一个程序就像冲杯奶茶一样容易。大家都很高兴。
到现在为止,软件架构似乎还非常的简单直白,但随着时间发展,复杂的需求涌现出来了,架构也就突破原来软件框架的范畴,变成更复杂的东西。
还是拿那一系列服务器程序为例,假设它们是一个在线交易平台的后台服务。某个程序员完成了个人转账业务的代码,另一个程序员完成了商户转账业务的代码,互相一比对发现,嘿,大家怎么写的大同小异,应该优化。不过这样的优化存在着风险,因为谁都不能完全确定,未来这两个业务会怎么发展。
保守的程序员会让这两份代码独立,静观其变,反正框架已经足够好,重写一份这种业务也花不了多少时间。这并没有错,不过会错失让架构突破框架限制的机会。
喜欢挑战的程序员会仔细思考这两个业务逻辑,从业务的层面来分析其中的异同以及未来变化的趋势。
比如转账这件事,无论是个人业务还是商户业务,创建交易、记账这些事情总少不了吧,这或许可以抽象出来,但是检查账户权限、验证密码这些事情肯定会不一样,这些东西保持尽可能的可变。如此这般,业务层面的封装逐渐成形,所谓的架构终于有了架构的意思。
在引入业务之前,前面提到的“架构”只是一个放之四海皆可用的程序员的玩具;引入业务之后,架构终于得以走向成熟,摆脱程序员的束缚,反过来影响程序员的思维。
真正让架构腾飞的是引入领域知识(domain knowledge)的概念。作为一个具有业务功能的系统,要维护业务的纯正性不容易。还是拿转账来说事,如果提供了创建交易和记账的业务抽象,再假设转账是一气呵成的,从代码上来说,先创建交易还是先记账其实没有太大区别。但这事搁在包含了领域知识的架构里面可不行,因为账务要求,必须先有单据再有流水。
领域知识把程序员变成业务专家,渐渐的程序员就更能明白什么是业务、什么算框架,也可以开始用业务接口包装子系统,理解子系统之间的逻辑,让架构日臻完善。
P.S. 上面这些文字并没有涉及具体的设计原则、方法。
P.S.S. 再完美的架构也需要持续改进,况且这世界上恐怕还不存在所谓“完美”的架构。
敏捷开发的几个误区
不少公司都在考虑采用敏捷开发,或者在项目开发过程中融入敏捷的思想,在这里,我列出几个常见的误区,希望能对大家有所帮助。
欢迎发表不同意见。
误区:敏捷开发 == 极限编程/Scrum/…
敏捷开发是一种方法论,只是一些基本原则的集合,并非具体流程。
极限编程、Scrum等流程是具体的实施方法,它们都声称符合敏捷开发的思想,但执行起来是否真的“敏捷”,还得看参与者究竟思想上是否真的接受敏捷开发的原理。
如果把结对编程、daily scrum当做是敏捷开发的表现,那更是本末倒置,可悲的是,不少人还真是这么认为的。
误区:敏捷开发 == 简化流程
敏捷开发不一定能简化工作流程,而且简化流程也并非提出敏捷开发的初衷。敏捷开发最重视的是拥抱变化,至于怎么拥抱,只能随机应变。实际应用中,既有流程相当简单的经典Scrum过程,也有极为冗繁、不亚于CMMI的RUP,根据应用场景不一样,项目组应该使用最适合的流程。
选择敏捷开发流程时也应带着敏捷开发的思维去选择,即快速响应项目实际的流程需求、拥抱流程应用过程中遇到的各种变化。没有银弹,也没有长期适合的项目流程,生搬硬套某个看似成熟的敏捷开发流程是大忌。
误区:敏捷开发 == 快速开始编码
敏捷开发强调迭代,鼓励开发人员用代码说话,不过绝对不鼓励没想明白就写代码。
符合敏捷开发思想的流程往往主张在一个稳定的基础之上迭代完成各种功能。如果基础都不牢固,迭代就无法进行,整个开发过程就退化成不断重写的过程,浪费开发时间。敏捷开发实际非常重视“设计”,并且对开发人员的设计水平提出了极高的要求,既要“持续重构”又不能“过度设计”,稍有不慎就会陷入反复推翻已有代码的窘境。对于内功不够的开发人员最好还是想好再写代码,设计的时候慢一点没关系,尽量少的做无用功才是最重要。
误区:传统开发能随时转变成敏捷开发
敏捷开发过于诱人,很容易让深受传统软件开发思想折磨的开发人员感觉敏捷开发就是灵丹妙药。
要想转变,首先需要从思想上正确认识敏捷开发的含义,了解它能解决什么问题、会带来什么新的问题、对现有软件/硬件资源有什么要求等。
例如在原先采用CMM的团队中,想利用敏捷开发简化冗余的文档、降低沟通成本,那么敏捷开发确实能在一定程度上缓解这些问题,但也会造成内部文档缺失、沟通不够深入的问题,在应用敏捷开发之前需要先确定适合团队的新的文档流程(代码注释能够自动/半自动的变成团队需要的文档么?),并且确定沟通的一些原则(比如,对于一些重要的沟通,是否还是用邮件来进行,不要过于“敏捷”?)。
有趣的是,有可能在回答这几个问题之后会发现,敏捷开发并不能解决项目中遇到的问题,反倒是其他方面出了问题。
举例来说。如果之前开发方法是简单的目标管理,遇到的问题是项目进度不可控、开发+测试的周期较长、不能及时响应需求变化,那么敏捷开发能解决的是快速响应需求变化,对项目进度和开发测试周期帮助不大(没有一种流程能够承诺改善项目的开发效率!),但前提是开发人员必须懂得怎样正确的去迭代开发,并且必须认识到一次迭代的结束是以完成测试为标准的。
在这个例子中可以看到,敏捷开发能带来的好处非常有局限性,如果开发人员达不到一定的层次是很难受益的。与其号召使用敏捷开发,不如想想如何增加执行力,以及找到周期偏长的根本原因(是因为设计不充分而返工?还是因为没有可以快速回归的测试用例?等等)。
