2010年8月16日星期一

正确地对待bug的逻辑和态度

刚看了李笑来老师写的《关于举证责任》的系列短文,又联想到平时程序员在开发中常见的错误认识,结合自己的经验写一些总结。
以下内容不是讲调试和分析问题的技巧,而遇到bug时正确地对待问题的逻辑和态度。
  • 你写了一堆代码,不能因为别人没有证明你的代码有错误而认为自己的代码没有错误。
  • 自己要尽可能证明自己的代码没错误,这不是别人的责任。
  • 别人证明你的代码有错误的难度是很大的
    • 因为证明本身难度就很大,1000行的代码中有多少个可能的状态、条件变量有多少、它们之间的组合又有多少。
    • 多线程的偶然现象很难手工复现,往往需要编程来主动创造那种特殊的条件下才行,而这需要对复杂的代码有相当深入的整体和细节的掌握后才能做到。
    • 别人可能对你的代码实现一无所知,别人只是使用中发现了问题而已。
    • 出现问题后,如何复现问题往往很难。找到问题发生的条件,就像在一个巨大的状态空间中找到其中一点一样,可能需要做大量的实验。
  • 出现问题了就说明确实存在问题,只是还不清楚问题来自哪里,什么条件下发生的。不能因为没有找到复现问题的办法,而认为那个问题不存在。
  • 想办法复现问题、分析问题可能的情况(假设+求证)、定位问题、找到问题,这个过程往往是最费时间精力的事情。解决问题的主要工作量就在这里,而不是解决本身。
  • 找到问题后,解决问题往往很简单,可能就是1、2分钟的事情,甚至就是在找到问题的同时就解决完了。
  • 分析问题或证明问题不存在的责任在于程序的实现者,而不在于发现问题的人(尤其是普通用户)。
  • 发现问题的人最好能提供更多信息、最好能够说明问题复现方法、甚至最好能说明问题出在哪(但这个不太现实)。但反之,不能因为发现问题的人无法做到这些而否定问题。更不能要求别人把这个问题找到并且解决了,才承认有这个问题。
  • 最终找到的问题点可能离问题的表象很远,这也说明找到问题是不太容易的,也容易让人难于判断问题出在谁那里。
  • 做前端的人,往往首先承受了所有的问题,尽管问题很可能不在前端。因为程序的所有交互都要从前端进行,用户也只能看到前端的东西而看不到后台的东西。所以前端工程师往往要证明问题不是出在他这里,但也很难说出问题出在哪里,这等于要他来解决问题。
  • 问题最终可能不是出在你负责的这里,而是别的地方。
  • 程序员不应该推卸问题,尽管可能不是你的问题,但每个人都要积极的想协助解决问题。因为解决问题是整个团队的共同目标。
  • 自己写的简单测试没问题,并不能证明代码都没问题。因为测试代码只是证明给定那几个测试条件、数据下的结果是对的,既没有完整的覆盖所有情况(其它情况下是不是对的还不知道),也不能证明实现的过程是对的(结果对不一定过程对,可能碰巧、可能测试条件没有触发问题点)。
  • 代码测试覆盖率,你的测试覆盖了多少代码?如果非常低,那么隐藏问题的可能性很大;很高的覆盖率也很难做到,而且工作量很大。
  • 当你能够主动想到问题,而不是由别人告诉你哪里有问题的时候,你就已经提高一大截了。

2010年8月11日星期三

ExtJs 加载优化

ExtJS 是个功能丰富、强大的 javascript 库,适合做一些富客户端界面。但是其庞大的体积会导致网页加载时间长,给人很慢的感觉。

为了找到网页加载慢的真正原因,首先你应该用 YSlow 这样的工具仔细分析一下,看看到底是哪些方面导致的。ExtJS可能只是其中的一个原因,也许还有别的地方影响了加载速度。下面只谈谈如何解决 ExtJs 加载慢的问题。

就像任何软件一样,功能越多就会体积越大,这个在所难免。
对于ExtJs,最简单的傻瓜式使用方法就是在页面中引入这三个文件:
ext-all.css(138K)、ext-base.js(32K)、ext-all.js(635K)
另外js文件还有对应的debug版:ext-base-debug.js(78K)、ext-all-debug.js(1.13M)
这仅仅是ExtJS,你的页面中肯定还有很多其它东西,加在一起可就不小了。按照一般的网速,下载这些东西就要等半天了。

1.不要使用debug的js文件(体积缩小1/2)
debug版的js和非debug版的js完全一样,只是没有压缩而已,但体积大了一倍,ext-all-debug.js达到1.13M!
所以,不要使用debug的,除非你需要做相关调试。就算要调试,那也只是在开发的时候,别忘了换回去。

2.自定义裁减 extjs(体积进一步缩小几分之一)
extjs 功能众多,但大多数人只用到其中的少数功能。傻瓜式的方式只是在开发时简单,正是环境应该用多少使多少定制一份自己用到的部分,这样可以小很多。extjs也是模块化实现的,在其网站上可以自定义一份:http://www.sencha.com/products/js/build/

3.启用gzip压缩(体积又缩小了几分之一)
这个很重要。一般web服务器都支持gzip压缩,gzip压缩对于文本类型的文件都有很好的压缩效果。比如 635K 的 ext-all.js 经过gzip压缩后只有 168K,太棒了!
apache、nginx等服务器均支持gzip压缩,建议配置一下对所有超过2k的 js、css 文件启用gzip压缩。除了web服务器,像tomcat、jetty这样的应用服务器也都支持gzip压缩。具体查看配置文档就知道了,很简单的。

提示:建议使用 静态gzip压缩 或者叫 预压缩静态内容,因为server在运行时进行压缩要消耗一定的cpu资源,而对于js这些静态内容而言完全可以预先压缩好,如果有以 .gz 结尾的同名文件,server直接返回压缩好的内容即可,这样减轻一些服务器的负担。
常见的后台组合是动内容和静态内容分离,然后让Web服务器处理所有的静态资源,应用服务器专心处理动态内容。
中小型应用也可以只用tomcat这样的应用服务器(简单一些),如果JavaEE的servlet容器不支持静态压缩,可以编写一个filter来实现同样的压缩或缓存效果。
话说多了,这方面都属于性能优化了。

最后,别忘了利用客户端浏览器上的缓存,下载一次就可以了,以后全部使用本地缓存。主要适用于用户会多次访问的情况。

网上还有人说使用JSA这样的 javascript 脚本压缩、混淆工具还可以进一步缩小体积,实际上非debug的js已经是经过一定压缩的了,继续做语言层面的压缩效果不明显。并且如果已经使用gzip压缩了,就没必要做这个了。


2010年8月9日星期一

Ajax 同源策略限制的简单说明

出于安全原因,浏览器对页面中的ajax请求(XMLHTTPRequest)有同源策略的限制。

如果两个页面的协议、域名和端口是完全相同的,那么它们就是同源的。当前加载页面只能发出同源的ajax请求。
比如说你的当前页面是http://www.example.com/test.html,那么这个页面中的只能发出 http://www.example.com/ 下的请求。

一、协议指:http、https、ftp等属于不同的协议,尤其是http和https也是不同的协议。
二、域名指不同:
  1. 顶级域名不同。如 www.abc.com的页面中不能向 www.def.com发出http请求。
  2. IP与域名也被认为是不同的,即便他们是一回事。比如在开发的时候,你自己的本机局域网地址是192.168.1.106,你用 localhost 打开当前页面,而页面中请求的却是192.168.1.106也不行的。总之,域名和IP被认为是不同的域名,包括 localhost ,所以要么全用域名,要么全用IP。
  3. 相同顶级域名,但二级域名不同也是不同的。这个要求对同一域名下的各个子服务之间访问造成了一些限制。比如 www.example.com 和 download.example.com 就是不同的,www.example.com 和 example.com也是不同的。不过javascript中允许设置 document.domain 变量为 当前域名更短的域名,比如当前访问的是www.example.com,可以设置 document.domain 为example.com,这样就可以访问 example.com 下的请求。一种跨子域的解决方法就是,x子域的当前页面包括一个y子域的iframe,当前页面和这个frame分别将自己的document.domain设置为根域名,这样当前x子域的页面就可以通过这个y子域的页面代理访问y子域的请求。
三、端口不同:这个大家都明白,比如80和8080是不同的。

注意:script标签的src属性是没有同源限制的,你的网页上可以加载互联网上任何url的js,比如在页面中嵌入Google 分析的js代码。类似的,img标签也可以加载任何url的图片,比如你的网站上链接的是别人的图片,这些都不受同源策略的限制。利用这一点,也可以实现一定程度的跨域请求,比如你可以利用js修改script或img等标签的src属性,就等于在向任意url发出了一个get请求。
iframe也可以是任意url,但当前页面只能向自己的同源发出请求。


同源策略在带来安全性的同时,也对应用程序造成了很多限制,所以又引出了ajax跨域问题决方法。除了前面说到的方法,一般的跨域就是类似代理的方法了。

2010年8月8日星期日

Geo Location 地理位置信息小结

周末调研了一下Geo Location 地理位置信息方面的内容,自己小结一下。
一、通过 IP 地址获得用户的地理位置信息
也就是根据用户的IP,通过IP数据库查询获得信息。一般IP数据库中,
每条记录的基本结构:
IP地址段(起始、结束),以及对应的信息数据
一般包含的信息:国家、区域(省/州)、城市、街道、经纬度、ISP提供商等信息

因为IP数据库随着时间经常变化(不过一段时间内变化很小),所以需要有人经常维护和更新。这个数据也不可能完全准确、也不可能覆盖全。这是maxmind的城市准确度 http://www.maxmind.com/app/city_accuracy
因为没有权威的数据组织机构,且经常有变化。各家数据供应商,基本上做着做着就形成自己的一套数据了。

目前,国内用的比较有名的是“纯真IP数据库”,国外常用的是 maxmind、ip2location。

IP数据库是否收费:收费、免费都有。一般有人维护的数据往往都是收费的,准确率和覆盖率会稍微高一些。

质量方面:
  1. 主要概念是准确率和覆盖率。
  2. 记录数据总条数。纯真现在是38万条(2010年07月30日更新)
  3. 是否有人维护。
  4. 数据库更新频率:每月、每周。数据库会定期更新的,maxmind开源版是每月更新一次。

查询形式:
  • 本地,将IP数据库下载到本地使用,查询效率高、性能好。常用在统计分析方面。具体形式又分为:
    • 内存查询:将全部数据直接加载到内存中,便于高性能查询
    • 数据库查询:将数据导入到数据库,再用数据库查询。效率没有内存查询快。
  • 远程(web service或ajax),调用远程第三方服务。查询效率自然比较低,一般用在网页应用中。


是否提供API:有的IP数据库提供API,支持多语言(java、javascript、C#等),这样你就不用自己直接分析数据格式、整理、写查询代码了。

是否提供经纬度:纯真IP数据库不提供经纬度,Maxmind提供。如果做地图应用,一般是需要经纬度的。

语言方面:英文还是中文。maxmind提供的数据,所有信息都是英文的,国家是iso的国家码,如中国是China,北京是Beijing,国内环境使用不方便,除非你自己再维护一个中英对照表。而纯真IP数据库就中文信息。

另外,有些Web服务器也有这方面的功能集成。因为对Web应用而言,IP数据显然是通过Web服务器获得的,因此这部分工作也可以交给web 服务器来做。比如 Nginx 就带了一个GeoIP 的可选模块( http://wiki.nginx.org/NginxHttpGeoIPModule),集成了 MaxMind,安装时需要在configure中指定该模块。


Maxmindhttp://www.maxmind.com
有收费版,也有开源版本。
数据下载,http://geolite.maxmind.com/download/geoip/database/
准确度,http://www.maxmind.com/app/city_accuracy
支持多种语言,http://www.maxmind.com/app/city
Open Source binary API available for the following languages:
ip2location http://www.ip2location.com/ 和Maxmind类似,不多介绍了。

纯真IP数据库
http://www.cz88.net/
主要是国内信息,比较准确,也比较精确(可以到小区、网吧什么的),数据也是中文的。比较适合分析国内数据,国内够用了。国外数据非常少,如果主要处理国外的,不要用这个。没有经纬度。

官方组织:APNIC
http://www.apnic.net/
APNIC is an open, membership-based, not-for-profit organization providing Internet addressing services to the Asia Pacific.
数据下载:http://ftp.apnic.net/apnic/dbase/data/
http://ftp.apnic.net/apnic/dbase/data/country-ipv4.lst 提供了IP段到国家的映射。小工具chnroutes 就是利用这个数据。


二、通过 W3C Geo API 获得用户地理位置

也就是html5中关于地理位置的方面

geolocation api,地理位置由浏览器提供,需要浏览器支持。很多基于地理位置的网站都开始使用了,一般需要浏览器用户同意。

W3C 的 Geolocation API Specification:http://dev.w3.org/geo/api/spec-source.html

三、移动领域
手机:GPS(精确度高、刚开启时搜星比较慢、手机上比较费电)、根据手机信号的基站定位(不太精确、范围较大),
Wifi信号应该也可以,但要有这方面的数据收集


其它参考:

Web 地理定位(Geo-Location)知识大全,http://www.javaeye.com/news/13813-web-geo

根据IP定位地理位置,http://www.huachu.com.cn/read/readbookinfo.asp?sectionid=1000004203

2010年8月7日星期六

Tomcat无法正常关闭

Tomcat的正常启动和停止是用 startup 和 shutdown 两个脚本,但有时候tomcat因为其中部署的某个应用导致不能正常 shutdown 。判断是不是由应用引起的问题,很简单,试试tomcat不部署任何应用时是不是也有这个问题。

其实,不能正常shutdown八成是由于应用自身没有释放资源造成的,比如在应用中使用了非daemon的线程或Timer(timer是使用独立线程来实现的),而在容器stop时自己又不销毁就导致容器不能正常停止,只能kill。容器只按照Java EE规范来管理应用中标准组件的生命周期,但你自己创建出来的资源要自己负责处理,容器是不会替你管理的。JVM中,所有的非守护线程都停止了,JVM自然就停止了。

解决方法无非两种:
1.将应用自己创建的线程、timer、scheduler这类的资源设为守护线程(daemon)。因为这些东西一般就是用来在应用运行期间做些例行维护的工作。
2.自己管理非守护线程的生命周期,当容器停止时手工释放资源。比如你可以在 Servlet 或 ServletContextListener 的 init 方法中初始化资源,在 destroy 方法中释放资源。

后话:很多应用开发人员并没有很好地理解原理,不清楚容器替你做了什么也就不清楚自己应该负责什么。
这是在我维护别人的写的一个web应用时发现的问题。