Trouble shoot a SQL Server 2012 deadlock issue

Yesterday test team started to perform stress test on our web application, and today we got reported a frequently occurring deadlock issue. This is the first time I trouble shot a deadlock issue, so I'm blogging it in any case others have the same issue.

  1. First we analysed the stored procedure which caused the deadlock. This SP has no data modifying operations, it just queries data from some tables/views based on parameters passed in. The views used in this SP all are defined with WITH (NOLOCK), and we have no transactions in the SP. So we didn't get any clue this step.

  2. Then we captured the deadlock graph using SQL Server Profiler. You can follow this MSDN article. But at start the profiler reported nothing while the deadlock was consistently occurring. After some research we found that we need to enable a trace flag so that the profiler can work correctly:

     DBCC TRACEON ( 1222, -1) 
    

    you can check the trace status by DBCC TRACESTATUS (1222, -1).

  3. Then from the deadlock report we found the deadlocked resource is a local temporary table and the statement is ALTER TABLE #abcde. But as we know, temporary table has a limited scope of specific SPID, so how can this statement cause deadlock between processes with different SPIDs?

  4. Finally we found the root cause: KB2776344 . It's a bug of SQL Server 2008 R2 / 2012 :

    Deadlocks occur when you execute a stored procedure to alter a temporary table if lock partitioning is enabled

    and lock partitioning is only available for systems with 16 or more CPUs, and is automatically enabled and cannot be disabled (Source). Our database server has 16 CPUs, so this impacted us.

You can fix this issue by installing the hotfix or remove the altering action of temp table.

My first Haskell adventure - a module for replacing a regular expression match

I have been learning Haskell for a month, and of course I'm still a novice. But one cannot master a programming language without practices. So I wrote a module to implement a missing feature of Haskell's regexp support: replace regular expression matches of a input string with the return value of a function. The design is inspired by C# Regex.Replace Method (String, String, MatchEvaluator).

Any suggestion / improvement is appreciated.

Ruby 中的 eigenclass 以及 method lookup

用 Ruby 尤其是 Ruby On Rails 来做一些简单有用的东西并不需要你理解 Ruby 语言的所有东西,Ruby 语言的设计是优美而符合惯例的,在有其它语言的基础上很容易对照官网的教程快速的达到“使用”级别。 我用了总共一个星期的业余时间,每天下班的两三个小时,从头学 Ruby 和 Ruby On Rails,并重写了这个博客程序。 但这显然不应该是专业程序员应该有的态度,而且在用 Ruby 重写博客的时候,越来越被 Ruby 吸引了。 于是仔细的重新读了一遍 Ruby 的经典书籍《The Ruby Programming Language》并试图理解有关 Ruby 语言的所有东西。这是一本非常好的书,很清晰深入地解释了 Ruby 语言的方方面面,推荐所有想要深入学习 Ruby 的人都仔细的读一遍。

It is easy to program in Ruby, but Ruby is not a simple language.

这句话很好的概括了 Ruby 语言的学习曲线。但 Ruby 的设计贯穿着概念上的一致性,这点对理解 Ruby 中那“条条大路通罗马”中的所有大路以及相对其它语言稍显复杂的 Class, Module 以及 Class 继承关系有很大的帮助。这篇博客主要想要比较清楚深入的阐述 Ruby 的 method lookup path ,顺便看一下 Ruby 设计上的概念一致性的优雅以及对理解 Ruby 语言的帮助。

Method Lookup Path

假设我们有一个 Cat 类:

class Cat
  include Miaowable
  include Runable
end

然后有一只猫 garfield = Cat.new, 假设我们想要调用 garfield.miaow,Ruby 的 method loopup 或者叫 name resolution 过程如下:

  1. 检查garfield的特征类对象(eigenclass),有则调用,没有者继续往下找。 下面类似。
  2. 检查garfield.class对象的(也就是 Cat) 的 instance methods
  3. 依次检查RunableMiaowable模块的 instance methods
  4. 重复2和3两个步骤从下往上依次检查继承链上的所有类已及该类所包含模块的 instance methods, 直到 BasicObject 类
  5. 如果都没有找到 miaow 这个方法,那么重复上面的步骤,查找method_missing方法,执行找到的第一个方法。Object 类所包含的模块 Kernel定义了这个方法,所以这次查找一定可以成功。

这里是针对一个实例的查找过程, 关于 method lookup path 的一般性步骤请参考 《The Ruby Programming Language》。

什么是特征类(eigenclass)?

eigenclass 也被称为 singleton class 或者 metaclass 。 你应该可以猜出来了,简单来说它是定义对象的 singleton 方法的地方。 这是一个匿名类,用 class << o 打开一个对象的 eigenclass. 还记得当需要定义多个 class method 的时候 Ruby 提供了一个像下面这种简单的方式吗:

class Something
  class << self
    def class_method1
    end

    def class_method2
    end
  end
end

这是因为 Something 类的 class method 实际上就是 Something 对象(不是 Something 的 instance,是 Something 本身就是个对象)的 singleton 方法。 在上面代码中 self 出现在 Something 的定义体内而不是在某个方法的定义中,这个 时候 self 就是 Something 对象本身,所以 class << self 就打开了 Something 对象的 eigenclass, 里面定义的方法就是 Something 对象的 singleton 方法,也就是 class method 。

回到我们的 garfield 对象, 现在为这个对象定义一个 singleton 方法 s_m :

class << garfield
  def s_m
  end
end

或者

def garfield.s_m
end

为了更好的理解 eigenclass 类,我们为 Cat 类定义一个实例方法用来返回类实例对象的 eigenclass 对象:

class Cat
   def eigenclass
     class << self
       self
     end
   end
end

下面我们来看看 eigenclass 的有意思的东西:

> garfield.class
=> Cat
> garfield.eigenclass.class
=> Class
> garfield.eigenclass.superclass
=> Cat
> garfield.eigenclass.instance_methods(false)      #don't get inherited methods
=> [:s_m]                                        #我们为 garfield 定义的 singleton 方法

所以,一个对象的 eigenclass 就像是一个隐形的类,位于该对象的继承链的第一层,该对象所定义的所有 singleton 方法位于其中,其结构如下图:

object method lookup

什么是instance methods

Ruby 的 instance methods 没什么太特殊的地方,就像你在其它OOP语言中所理解的那样,在类中定义,在类的实例上调用。

Eigenclass 以及 Class Method 的继承关系

如果按照上面的方法来定位一个 Class Method 会出现一些令人迷惑的事情。 现在我们先为 Cat 定义一个 class method :

class Cat
  class << self
    def we_are_not
      "tigers"
    end
  end
end

并且增加一个子类 TabbyCat :

class TabbyCat < Cat
  class << self
    def we_have
      "distinctive coats"
    end
  end
end

> TabbyCat.we_have
=> "distinctive coats"
> TabbyCat.we_are_not
=> "tigers"

由于 Class 本身也是对象, 那么 Ruby 在查找 class method 的时候理论上也应该跟查找普通实例的方法没什么差别。 TabbyCat 是 Class 类的实例, Class 继承自 Module,Module 继承自 Object ..., 那么 we_are_not 的查找路径应该是:

TabbyCat -> eigenclass of TabbyCat -> Class -> Module -> Object -> BasicObject

你会发现在这个链上并没有 we_are_not 方法的定义。 那么是 Ruby 在处理 class method 的时候没有采用一个统一的方式么? 其实不是。 现在我们在 Object 类上同样定义一个方法来返回实例的 eigenclass ,这样我们就能够拿到 Cat 和 TabbyCat 的 eigenclass :

class Object
  def eigenclass
    class << self
      self
    end
  end
end

然后:

> TabbyCat.eigenclass
=> #<Class:TabbyCat>
> Cat.eigenclass
=> #<Class:Cat>
> TabbyCat.eigenclass.superclass
=> #<Class:Cat>
> TabbyCat.eigenclass.superclass == Cat.eigenclass
=> true
> Cat.eigenclass.superclass == Object.eigenclass
=> true
> Object.eigenclass.superclass
=> #<Class:BasicObject>

发现了吗? TabbyCat 的 eigenclass 是继承自 Cat 的 eigenclass 的。 所以在上面那个查找路径在查找到 eigenclass of TabbyCat 的时候就已经找到 了 we_are_not 。 也就是说如果一个类继承自另外一个类,那么它的 eigenclass 也继承自父类的 eigenclass 。所以当我们查找 TabbyClass.we_are_not 时候, 实际的查找链应该是如下图:

class method lookup

互联网上怎样保障你的帐号安全

经常上网的人通常会有很多个帐号,用来使用各个要求注册的网站提供的服务。 那么你的这些帐号安全么?

小心那些不安全的网站任何不用HTTPS登录的网站, 都应该被归为这一类。 这点非常容易被人忽视。 当你发现一个网站不使用HTTPS的时候, 就应该有帐号密码很容易被窃取的觉悟。 不管这个网站内容做的怎么样, 在帐号的保护方面做了多少工作,其安全性都只能说是最低级别的, 因为明文传输就意味着你与该网站之间的通讯都暴露在别人的监视之下, 网络层次的监听不是网站所能控制的。 对于这些网站,有以下几个建议:

  1. 在这些网站注册的时候,密码绝对不要跟其它重要的帐号一样。 否则一旦被盗,会导致你其它的重要帐号,比如支付宝,gmail等同时被盗。
  2. 你可以设置一个简单好记又稍微不那么好猜的密码,在所有的这类不安全网站上都使用这个密码。
  3. 最好不要使用重要的邮箱注册。
  4. 在心理上就要警惕, 不要在个人账户信息里面填写任何敏感重要的信息。 比如有些很SB的网站用http明文传输,却要求你设置安全账户问题... 千万不要跟你的其它重要账户的安全提问一样。
  5. 不要在这些网站上做任何的金钱交易。

注册一个Gmail帐号, 开启两步验证,并用它做其它你所有重要帐号的安全邮箱。 Google帐号的两步验证是目前我所知道的最强大的安全保障, 理论上不可能丢失你的Google帐号。 大部分网站提供用注册时提供的邮箱找回密码的功能, 如果你提供的邮箱本身是不可能被盗取的, 也就意味着这些能够用该邮箱找回密码的账户也很难彻底丢失。 Google的两步验证有些需要注意的地方:

  1. 请最好绑定2个手机, 你的和你亲人的, 因为这个帐号太重要了,要保证万无一失。 这样在你手机丢了,又没法重新找回SIM卡的情况下(这种情况应该很极端了,SIM卡一般可以在运营商出拿身份证找回), 还有机会挽回你的Google帐号。
  2. Google帐号提供10个备用安全码,可以打印下来,万一手机不在身边又需要紧急登录,可以使用。

使用KeyChain等密码保管工具。 或者如果不用MAC OS X, 其它平台上也有类似的软件。你可以用它为你的重要账户生成非常复杂的,不可记忆的密码, 然后在需要登录的时候从这些软件中copy出来, 当然你要保证你的主密码以及操作系统安全,这点在MAC OS X上相对靠谱一些。 例如有一些网站的密码对我来说非常重要,比如name.com, 而且我不需要在手机上登录, 我就用KeyChain生成了一个64位的随机密码, 这意味着自己的电脑不在身边的时候连我都登录不上... 不过我的注册邮箱是我的Gmail,所以紧急的时候我还是可以重置密码登录。

尽量不使用不支持全程HTTPS加密的邮箱, 即使迫不得已要用,也不要通过它发送任何敏感信息。 据我所知国内邮箱好像都不支持(很长时间没用过国内邮箱服务了,不知道有没有改进)。 理由同第一点, HTTP是明文传输的。 想像一下你发送的包含重要邮件都是透明的情况吧。

如果你使用360,QQ医生等国内所谓的安全产品, 你就当我上面什么都没说吧。

不要在主机在国内的网站上发布非常敏感信息 什么是非常敏感的信息就自己判断把。 再NB的软件技术也不能阻挡物理手段侵入。

在ec2 micro instance上安装配置postgresql

简单来说这是一篇“how to”性质的文章,用来说明如何在 Amazon Linux micro instance上面配置使用 postgresql 数据库。

  1. 安装 postgresql, postgresql-server and postgresql-devel:

     sudo yum install postgresql postgresql-server postgresql-devel
    

    之所以需要安装postgresql-devel是因为我需要在这台机器上跑一个rails程序, 通常数据库驱动gem都需要devel包来编译本地扩展。

  2. 初始化数据库: sudo service postgresql initdb

  3. 启动数据库服务: sudo service postgresql start

  4. postgresql 初始账户是 postgres, 并且只接受Peer方式认证, 也就是说你要先 runas postgres, 才能以这个账户登录去进行数据库管理。

    如果你用sudo -u postgres psql, Amazon linux 会要你提供 ec2-user 的密码,而这个密码是不知道的, 因为一般是用ssh通过 private key 的方式登录的ec2 (我不知道Amazon是不是会提供这个密码,起码我是不知道)。

    所以需要先 root 登录,su, 会要求你提供root账户的密码,这个密码我记得第一次用的时候 Amazon Linux 会要求你设置。 然后再运行 sudo -u postgres psql 就可以以管理员权限连接到postgresql了。

  5. 创建用户和密码:

    由于我需要从 Ruby 程序中连接postgresql, 所以需要创建一个带有密码的用户, 然后再在 postgresql 中启用本地密码认证 (见下一步), 这样就可以不用"在系统中创建一个同名用户并且 runas 这个本地账户来运行我的ruby程序了"。

    create user USERNAME with password 'PASSWORD'

  6. 编辑 pg_hba.conf 文件以允许刚才创建的用户在本地以密码方式登录,这个文件一般在:/var/lib/pgsql9/data/pg_hba.conf:

     local   all         USERNAME                                  md5
    

    注意要把这行放在local all all ident前面。 然后重启service: sudo service postgresql restart

  7. 验证一切配置是否正确: psql -U USERNAME -d DBNAME, 应该会提示输入密码,然后正确连接数据库。

SSIS Package的七宗罪

很久没有写长一点儿的东西了。 一半是由于太懒,另一半是因为这半年来在忙一个最终不算太成功项目。 而我认为,我们在这个项目中大量使用了SSIS package来实现业务逻辑,部分地导致了项目目前的结果。

  1. SSIS Package 的编辑器,就是visual studio 2008, 是我用过的最烂的编辑器,没有之一。

    我不明白为什么总有人鼓吹用画流程图一样的方式去写程序, 我也不知道如果使用一个相当强大的对这种编程方式有良好支持的编辑器/IDE会不会非常高效。 但是,如果编辑器的质量是像visual studio 2008这样,那简直就是个噩梦。

    • 在打开script task的时候你经常会看到Error: 0xxxxxxx, 后面跟着一句废话,msdn搜到的信息也是一堆废话, 唯一的解决方案是删掉重建这个task,甚至整个package。 已经记不清楚具体的错误了,项目结束之后再未碰过ssis package。
    • 打开一个稍微大一点儿的package需要不短的时间,因为SSIS会验证连接和相关的组件。 有好几种workaround可以绕过这个验 证过程, 但是没有一个one-click的方案, 每种都有局限。

    更糟糕的是,没有可替代品。 不像文本编辑器你可以有很多选择,而且差别不算太大, 就算你在记事本里写c#, 也就损失智能提示跟语法高亮而已。但是你不可能用文本编辑器写SSIS Package, 你唯一的选择就是visual studio 2008。更离谱的是, 直到vs2012发布, vs2010都没有支持ssis package。

  2. 隐藏了太多东西

    编辑一个task的时候,通常你需要双击这个task打开一个对话框,因为重要的属性有很多都不在visual studio 的“属性”窗口 里。 这还不算, 有很多属性甚至要右键点击这个task, 在所谓的advanced editor里面才有。 task中充满了隐晦的参数设置和模态对话框,定位问题时相当头痛, 编程效率非常低下。

  3. 几乎不可能进行code review

    SSIS Package的源码是xml。 xml是文本格式, 但问题在于SSIS Package为每个变量和task生产guid,所有变量和task的引用都是通过guid,并且会生产大量的只有SSIS 本身才能理解的属性, 再加上每次build一次package, guid会重新生产,xml节点的顺序也会打乱,code review就变成了一个不可能的事情。 每次有同事发package的code review邀请, 你都需要把package复制到本地, 用vs打开, 才能review。 这样你无法有效的diff,只能打开每一个task, 一层一层的打开模态对话框,然后去看每一个属性该了没有。 这根本是个不可能的任务。

  4. 没有人愿意再去动一个已经写好的package

    基于1和2的原因, 除非这个package确实有问题,不能正常运行。 否则大家倾向于能不动就不动,就算有点小问题,也会改其它地方来迁就package。 这是很可怕的事情, 没有优化,没有改进, 并且为了迁就它做的其它部分的改动扭曲了本来应该有的良好设计。

这就是七宗罪了,谁说一定得有七条来着? :)

提高效率之 mac os x 技巧数则

用mac os x 一段时间了,分享一些提高使用效率小技巧:

  1. 一定要充分利用Spotlight:

    查找文档/程序速度很快,control + space呼出,敲几个字母一个回车就能打开你想要的程序和文档了,用惯了以后我几乎不用Launchpad了,在windows下也开始无法忍受从开始菜单里面一层一层去找一个应用程序了。

  2. 为系统自带的词典软件Dictionay添加自定义词典:

    mac os x自带的词典还是很强大的,美中不足就是默认只有英英词典,而且其释义不是特别适合非英语母语的人。

    1. 下载 Dictunifier, 它可以把stardict格式的词典转换成mac os x词典软件能够支持的格式并自动安装。
    2. 你可以从这里找到数量众多的stardict词典,推荐牛津英汉双解朗文

    stardict词典格式是开放的,你可以在这里找到具体的格式说明。 我曾经写过一段go语言程序,用来把stardict词典导入到mongodb数据库, 如果你也需要把stardict词典转换成其它你需要的形式,可以参考这段代码

  3. 如何在pinyin输入法下输入英文字母:

    用惯了windows上的谷歌输入法shift键一键切换中英文输入,真的很不习惯mac os x上每次输入英文都需要command + space 切换到英文输入法,输入中文再切换回来。 无意中发现在pinyin输入法激活的情况下,如果caps lock也激活,就可以直接输入英文和半角符号了。

  4. command + tab 是切换应用程序,而不是切换窗口:

    这点跟windows也有较大差别,windows中的alt+tab会在每个窗口中切换。 mac中同一个程序的多个窗口切换需要先用command +tab激活这个程序,再用command + ` (1旁边那个)在不同窗口间切换。

  5. 如何快速的锁定屏幕(不是log out):

    这点是很不能理解的事情,max os x没有类似windows中windows键 + L锁定屏幕的快捷键 (同样也没有关机/重启的快捷键...),不明白为什么不提供这些常用功能的快捷方式 。 可以打开keychain access程序,然后在偏好设置里选中“show keychain status in menu bar”, 然后就可以从menu bar上面那个小锁下面锁定屏幕了。

  6. 利用好Dashboard:

    这是个非常好用的功能,单独桌面, 快捷的一键呼入呼出, 功能丰富的组件。 我在dashboard里面查看天气,玩数独游戏,查找git命令文档,等等。

个人做好自己的事情才能谈所谓的团队精神

海贼王剑士索隆:

团队精神到底是什么?互相帮助,互相袒护就算是吗? 也有人这么认为吧。我是认为那根本只是唬人!应该是每个人抱着必死决心做自己的事,“我做好自己的部分”“接下来轮到你”“做不好的话我就揍扁你”要是有这种决心才能算是团队精神吧!

这个题目根本用不着写长篇大论来说明,因为实在是太显而易见了。 但最近几个月的工作中确实又连着碰到违背这个原则的事情,稍微吐个槽。

这个事情就不写具体案例了,太容易被对号入座。 我承认项目很急的时候应该优先集中力量把事情搞定,而不是追究责任。 但是,如果交给某人一个任务拖到最后无法完成,总是会有其他同事加班加点帮他搞定,并美其名曰团队精神, 谁还愿意主动的按时按量完成自己的任务? 反正最后会有人帮我搞定嘛,又增加了团队精神,多好。

我乐意在你生病或休假的时候帮你把事情做掉,也愿意毫无保留的把我的经验与知识共享给你,也愿意集思广益跟你一同思考技术难题的解决方案,如果在尽最大努力把自己的事情做好之外团队精神还需要一些东西来证明的话。

html5离线应用

离线应用(offline application / applicationCache)是html5的一个功能, 目的是为了让web应用程序能够缓存必要的资源在客户端,以便在网络不可用的时候仍然可以正常使用web应用。 对于一些大部分工作都是在客户端通过javascript和html5完成的web应用, 比如web版的图片编辑程序, 这显然是个非常有用的功能。

基于这几天的了解以及一些试验, 我觉得对于以内容发布为主的网站, 也可以考虑采用离线应用的方式:

  1. 提高响应速度, 减轻服务器端压力。 页面是优先从applicationCache加载的,几乎没有延迟。 服务器大部分时候要做的事情只是提供cache manifest文件。

  2. 可控的缓存。 你可以精确地控制缓存哪些东西, 什么时候需要更新。 而且applicationCache 不应该被浏览器设为过期, 除非用户手动清除。

  3. 越来越多的人使用移动设备访问网站, 使用离线缓存可以让他们在没有网络可用的时候仍然可以访问你的网站。 对于中国这样网络环境差的地方, 这一点有的时候很重要。

需要解决的问题:

  1. 需要有合适而且正确的策略来保证cache manifest被及时的更新。 浏览器更新applicationCache的唯一条件就是cache manifest被更新了, 所以假设你发布了新的内容却没有更新cache manifest, 用户是看不到的,无论用户是否在线。

  2. 对用需要跟服务器交互的部分,需要考虑离线时的处理。 这点可以用web storage / web sql database在离线的时候存储用户的交互内容, 当用户上线时再同步过去。

  3. 需要控制缓存的大小。 对于移动设备而言, 这个上限应该会比较小。

我正在尝试在本站点引入离线缓存, 首先从words review部分开始, 然后会使博客的内容也可以离线阅读。 或许这不是特别必要, 但这个站点的重要使命之一就是能够让我实践各种有用的技术:).

下面是一个典型的cache manifest:

CACHE MANIFEST

#Cache Manifest for blogs v1.7

/styles/bootstrap.min.css
/styles/mysite.css

/scripts/Markdown.Converter.js
/scripts/Markdown.Sanitizer.js
/scripts/Markdown.Editor.js

/images/wmd-buttons.png
/images/stars.gif
/images/rss/google.gif
/images/rss/msn.gif
/images/rss/xianguo.gif
/images/rss/youdao.gif
/images/rss/zhuaxia.gif

NETWORK:
*

FALLBACK:
/ /offline.html

比较重要的几点:

  • 首先第一行必须是CACHE MANIFEST
  • 建议加上一行类似#Cache Manifest for blogs v1.7的注释, 这样在需要浏览器更新缓存的时候只需要更新一下注释中的版本号就可以了。 manifest中的任何更改都会是浏览器更新缓存, 即使是注释。
  • NETWORK:节中的*表示所有没有明确列在manifest中的资源浏览器都会尝试去从服务器请求, 而不是直接视为找不到。

具体的语法查阅官方文档.

转换pdf或txt文档为kindle电子书

有些书找不到适合在kindle阅读的版本, 很多只有pdf和txt格式, 在kindle上看实在是太痛苦。 可以用一些现成的免费工具通过简单的步骤把它们转换成mobi格式,并加上目录。

前提条件:

  1. 原始文档内容要有基本的格式,比如要有换行。
  2. 如果要生成目录,每一段的标题必须有某种规律,比如单独为一行,或者统一有某个前缀, 比如“Unit 1 ....”, “Unit 2 ....”, “第一章 ....”, “第二章 ....”等。
  3. pdf文件先把所有内容复制出来保存成txt。
  4. 安装免费的电子书转换软件 calibre, 完全免费而且支持windows/mac os/linux
  5. 安装一个支持正则表达式替换的文本编辑器,我用的是 vim

步骤:

  1. 如果要生成目录,首先要为每个段落的标题加上特殊的html标签, 比如在vim中可以用如下命令:

     :%s/^\(UNIT\s\+\d\{1,3}\.*\)/<h3 name="title">\1<\/h3>/g
    

    这样可以把所有类似UNIT 1 Section 1, UNIT 2 Section 2 ... UNIT 103 Section 103的行替换成<h3 name="title">UNIT 1 Section 1</h3>等对应的h3标签.

  2. 接下来把文本文件的换行符号替换成<br/>, Vim下可以用:%s/\n$/<br\/>/g
  3. 在txt文件头和尾分别加上<html><head>....</head><body></body></html>, 然后把文件另存为html文档。
  4. 打开calibre, 导入刚才处理好的html文件,右键单击导入的文件选择生成目录。 这时候需要告诉calibre哪些html标签应该生成为目录项, 指定name为“title“的h3标签即可,界面操作很简单,这里就不具体说了。

今天用这个方法转换了《English Grammar In Use》, 总算免除了这几天看txt版的痛苦,可以比较方便的在做完每章的习题以后跳到附录去对比正确答案了。

Tags
Recent tweets

Don't register $(window).load in $(document).ready ......

这一周人生五味除了甜全都有了,就像时间浓缩了一样

How programmers see each other: http://shuklan.com/haskell/L01_files/langs.jpg

Where programming languages Geek should go: http://rosettacode.org/wiki/Rosetta_Code

After 3 months' development, finally launched the new MVP site: http://mvp.microsoft.com

Douban