<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[janlay's blog]]></title>
  <link href="http://janlay.com/atom.xml" rel="self"/>
  <link href="http://janlay.com/"/>
  <updated>2015-01-04T14:48:31+08:00</updated>
  <id>http://janlay.com/</id>
  <author>
    <name><![CDATA[Janlay Wu]]></name>
    <email><![CDATA[janlay@gmail.com]]></email>
  </author>
  <generator uri="http://octopress.org/">Octopress</generator>

  
  <entry>
    <title type="html"><![CDATA[2014 年终总结]]></title>
    <link href="http://janlay.com/blog/2015/2014-summary/"/>
    <updated>2015-01-04T13:25:17+08:00</updated>
    <id>http://janlay.com/blog/2015/2014-summary</id>
    <content type="html"><![CDATA[<p><img src="http://janlay.com/images/2014/calendar-of-the-year.png" width="626" height="435"></p>

<h2>生命的轮回</h2>

<p>开篇就拟了这么一个很大的标题，是因为一想起要回顾一下刚刚过去的 2014 年，有两件事立刻浮现在我的脑海中：外婆的去世和女儿的出生。这两件事相隔很近，以至于每每念及都会唏嘘感慨生命的更替变化，令人困惑又心生敬畏。<!-- more --></p>

<p>外婆近一年在湖北的家里卧床不起，刚过完 90 岁生日病情就急转直下。虽然已有六年没见到外婆，但是老婆的预产期临近，我最终还是没能见到外婆最后一眼。外婆第三代的晚辈有好几个，而我小时候体弱多病，所以她对我照顾有加，我也一直视她为最亲密的长辈之一。六年前为了方便外婆起夜给她买了一个台灯，她逢人就说这是我买的；母亲后来跟我说，外婆去世之前已经很不清醒，不停跟病床前的亲人说「雷雷就要到了，你们再等等他……」</p>

<p>自去年经历了<a href="http://janlay.com/blog/2013/2013-summary/">两次亲人离去</a>后，我以为我已经成熟到可以看淡生离死别，把记忆的时间线拉回过去，才意识到我永远是外婆眼里的小孩。</p>

<p>时间很快来到 2014/03/11, 女儿带着和外婆一样的白皙皮肤和清脆嗓音，来到我的身边。在她出生的第一个夜晚，我疲倦地坐在她睡觉的小床边，听着她均匀的呼吸，恍惚觉得她就是转世回来的外婆。我知道以后的日子里要一直陪伴她成长，我的世界里多住了一个人。</p>

<p>我为女儿起名「芸飞」。这可能是迄今我做的唯一一件决定他人生活的事情。我起名的原则，除了要有想象力，还必须易读好记，这个名字我很满意；查询后又欣慰地发现，这么一个简单的名字，在整个杭州都没有重名的人。</p>

<p>接下来，不仅仅是作息时间的变化，我对食物冷热、温度变化、空气质量的敏感度忽然提升到一个新的高度。这种变化就像本来冬季怕冷的老婆在孕期如神功护体般变成火炉一样，It&rsquo;s automatic, 太神奇了！</p>

<h2>工作的强度</h2>

<p>女儿出生前，为了顺利办理落户，我启动了自己的户籍迁移流程。本来以为作为一个诚实守法、合法纳税的公民，要把户口从武汉市区迁移杭州郊区，应该是很简单的事情，事实证明我还是太天真了。</p>

<p>卡在哪里了呢？开具计划生育证明：杭州说你户口在武汉，应该由户籍所在地街道开；武汉说你近几年都没在我这里住，应该在你居住地开。两边都强调这是按规定办事，我甚至让双方社区和街道电话沟通，都无法达成一致。</p>

<p>事实上这个问题在 2013 年给老婆跑准生证的时候就遇到了。当时也是打几通电话之后，才有西湖区计生委的人告诉我：<strong>办理准生证只要夫妻双方对生育情况作出书面承诺即可，无需提供计划生育证明</strong>。但是诡异的是，跑社区和街道办的时候，没有人会告诉你这个「技巧」。呵呵，说好的不让群众来回办事呢？</p>

<p>这一次我学乖了，直接找杭州和武汉的市级计生委：在武汉计生委网站投诉了街道计生科，电话咨询杭州计生局怎么操作。从反应时间、处理态度和效率看，杭州市政府比武汉靠谱，不枉我投靠杭州的拳拳之心。杭州计生局先后给我回复了两通电话，沟通结果是：我可以不用武汉方面的计生证明了；他们还给社区和街道派出所打了招呼，要求特殊处理我这个 case。武汉方面呢？过了大概一个月，收到了街道计生科科长用私人手机打来的电话，大意是说，有问题找我们沟通嘛，我们一定全力帮你解决，没必要去上面反映云云。呵呵，去你妈的，劳资以后不会找你了。</p>

<p>就这样，经过总计长达三小时的几十次电话沟通之后，我把户籍从武汉迁到了杭州，女儿也顺利完成落户。</p>

<p>回想一下，在多次去政府机构办事的时候，我发现我和那帮公务员们的工作强度差异非常大。我加入阿里以后，一个星期就干了之前创业时一个月的活（这也是创业失败的原因之一吧）；这帮纳税人们养着的公仆呢？他们的办公室就像 2002 年以前的网吧，只闻噼里啪啦的键盘敲击声，全他妈的坐在位子上聊 QQ！从另一个角度来说，虽然没有成为人生赢家，<strong>相比那些公务员们，我至少没有浪费生命</strong>，也不错哦。</p>

<h2>投资的新人</h2>

<p>2014年我正式混入投资界。首先，拿下楼下两个地下车位，其次，开始美股买卖。</p>

<p>买车位完全是无心插柳。起因是我被租用的车位旁边的人投诉到物业，说车门碰到了他的车。去实地看了下，确实留下了一点痕迹，无话可说，当面道歉去，那家人虽然对物业很凶，当面跟他沟通也还好。于是我寻思着要不要换个车位。去销售部一问，居然有，而且在心理价位的基础上还可以减一万，觉得机会不错，便拿下俩。</p>

<p>其实以小区现在的出租行情，这两个车位租出去的收益还不如把买车位的钱放余额宝。但是眼瞅着小区入住率越来越多，车位需求增加是必须趋势，所以就当做个长线先出租吧。</p>

<p>10月完成富途（可以<a href="http://www.futu5.com/invite/6ec40648b17f8647">用我的推荐号注册</a>）后，我立即开始了美股交易。早几年就开户了A股证券，只是实在看不懂大陆股市，所以一直没有进入。反观美股，我觉得比A股「正常」很多，值得投资。三个月下来（其中有一个月资金被套），完全做短线，收益 24%, 还比较满意。</p>

<p>通过这几个月的股票买卖，我明白了一个道理：<strong>那些说自己第一桶金来自股市的人，早期资金来源很可能有问题</strong>。为什么这么说呢？一个初始资金不多的股市新人，啥都不懂，不亏已经不容易了，你凭能赚几倍几十倍的收益？站在外面觉得很好看，自己进去了冷暖自知。</p>

<h2>目标的回顾</h2>

<p>2013 年年底，我给自己定了几个不算困难的目标：</p>

<blockquote><p>迎接新生命，这是头等大事</p></blockquote>

<p>Done</p>

<blockquote><p>看书比较慢，精读三本技术类书籍</p></blockquote>

<p>精读了<a href="http://book.douban.com/subject/25786645/">《乔纳森传》</a>、<a href="http://book.douban.com/subject/6756090/">《松本行弦的程序世界》</a>，<a href="http://book.douban.com/subject/25899841/">The Swift Programming Language</a>。其实 Swift 没看完，对于编程类书籍来说，入门就够了。</p>

<blockquote><p>工作方面，影响力要提升</p></blockquote>

<p>这方面简直是负增长，转岗带来的负作用，泪…</p>

<blockquote><p>房子装修，估计要到下半年</p></blockquote>

<p>计划有变，holding</p>

<blockquote><p>要增加陪家人的时间</p></blockquote>

<p>Done</p>

<blockquote><p>平均每周至少锻炼一次，要求好低啊</p></blockquote>

<p>基本没做到，唉…</p>

<blockquote><p>把户口迁到杭州，不好办也得办了</p></blockquote>

<p>Done</p>

<p>去年目标完成情况不好，所以今年的目标数量从简，难度增加：</p>

<ul>
<li>体重减到 62kg：这个真的很难（握拳）</li>
<li>写一万行代码：现在代码越写越少，乐趣也变少了</li>
<li>看几本非技术类的书：比如《环球科学》啥的</li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[我的自考求学之路]]></title>
    <link href="http://janlay.com/blog/2014/how-i-graduated-from-a-vocational-college/"/>
    <updated>2014-02-26T16:38:51+08:00</updated>
    <id>http://janlay.com/blog/2014/how-i-graduated-from-a-vocational-college</id>
    <content type="html"><![CDATA[<p>上次<a href="http://janlay.com/blog/2013/2013-summary/">年度回顾</a>里说到，我要细谈一下自学考试的经历。当初我以为只是简单地考几年试的事情，没料到却如戏剧一般起伏变化。每念及此，我都觉得应该把它记录下来，算是对这段不太平坦的求学之路的一个总结。今天我决定动笔。<!--more--></p>

<h2>一、起</h2>

<p>2005 年的我，风华正茂，各种状态值正处巅峰状态的时期，大把精力无从释放。那时候我是个 web 开发者, 写 ASP 和 VB 已经有触及天花板的感觉，寻思着要做点什么，充实一下无聊的生活。于是，我做了个「肤浅」的决定，报名参加自学考试。</p>

<p>那一年，我 25 岁，已经从中专学校毕业 6 年。</p>

<p>现在回想起来，一年后，我开始泡 <a href="https://bbs.et8.net/bbs/">CCF 论坛</a>，开始了解到各种 web 技术方兴未艾，各种编程语言甩开了 VB/VBScript 几条街，各种青年才俊指点江山…… 才知道自己还有很多要学习。如果我早一年进入这个论坛，发现新知，可能就没有无聊的空闲时间去读书了。但是那个时候，自学考试已经初战告捷，弃之何忍？</p>

<p>看着网上招生简章一溜的专业和院校，我直接按 Ctrl+F 搜索了印象中比较牛逼的「浙江大学」。如果能拿到这个学校的文凭，嘿，还是很有面子的是吧！不出意外，浙大果然提供了几个专业，竟然有我最擅长的计算机，考这个应该不难！那就「计算机及应用」吧。</p>

<h2>二、承</h2>

<p>所谓自学考试，是学员在没有老师，也没有教室的情况下，完全依赖于自己分配学习时间、寻找学习方法，到时间就去参加考试，满足一定学分要求即可毕业。这于这一点，起初我并没有很强的认知。</p>

<p>按照浙江的考试计划，2005 年 3 月报名，要等到 10 月份才能参加第一次考试。为了能顺利切入，我选择了三门比较有把握的公共课程：《大学语文》、《马克思主义哲学原理》和《法律基础与思想道德修养》。报名后，因为工作和生活上的变故，我几乎把这个学业给忘了。大概在 9 月，deadline 临近，我才捧起书本，抱起佛脚。</p>

<p>在学生时代，我就属于「考试型选手」，大多数情况下都能轻松考个不错的成绩。仰仗不错的积累，和还算好用的记性，第一次参加自学考试，报考的三门课勉强过关，最好的一门课才考出 72 分。不过那时的我已经没有节操了——「60 分万岁，多一分浪费」，多么实在的口号啊！</p>

<p>2006 ~ 2007 两年，我只通过了 6 门课。原因有几点：</p>

<ol>
<li>忽视了专业课自学难度</li>
<li>错过报考时间</li>
<li>考试时间与项目冲突</li>
</ol>


<p>对于第 3 个原因，我想说的是，就算放弃自己的考试也要完成答应客户的事情，我没有后悔。</p>

<p>这就样，原来计划三年拿下的学习，没能完成。到 2008 年，我已经通过了 14 门理论课和部分课程的实践课考试；顺利通过了全国计算机等级考试（三级PC技术）、公共英语等级考试三级。好像很牛逼的样子是吧？那你就 too young 了。事情急转直下，让我几乎想放弃学业。</p>

<h2>三、转</h2>

<p>通过了一批基础和专业课，又有足以顶替本科课程的证书在手，对照着毕业计划，我发现可以毕业了。</p>

<p>毕业申请的窗口，每年会开放两次。在错过上半年毕业申请后，2009 年 12 月我第一次去自考办申请毕业。毕业流程就像打通关游戏，你要一步步接受各级审核。那个下午，办事大厅人头攒动，在第一关提交资料后，我就被一记闷棍打得不知所措。</p>

<p>我所学的专业允许用计算机等级考试证书顶替部分课程成绩，因为考这个证周期比较短，而且等级考试比那些老掉牙的教材更贴近现实，所以我和很多学员一样，选择考证而不是一门门去考课程。</p>

<p>当我向审核人员说明，用计算机三级PC技术顶替几门课程时，听到了一句几乎能摧毁我世界观的答复：「这几门课只能用（计算机）二级顶，三级不能顶替」。天哪！他们知道这些三级比二级高的！咨询了几个在场的工作人员（虽然别人都叫他们老师，但抱歉我怎么看他们都不像老师），得到的都是一样的答复：不能顶替。</p>

<p>我手上那张笔试优秀、上机满分的计算机三级证书，竟然变成了一张废纸！悲愤地走出自考办，我做了一个无可奈何的决定：再去考计算机二级证书。没有意外，我再次拿到笔试优秀、上机满分的计算机二级证书；但是，我的毕业计划推迟了一年。</p>

<p>再次等到毕业申请机会。这一次审核时，他们说按照新的毕业计划，你的学分不够。我心里顿时有一万匹草泥马奔过；但更让我震惊的是，计算机及应用这个专业的主考院校从浙江大学改成了宁波大学，这意味着，即使马上毕业，也只能拿到了宁波大学的毕业证。</p>

<p>这次真的伤到我了。我心灰意冷，不想继续追求那已经变成无足轻重的专科文凭；慨叹衙门难缠，不想跟呆板落后的教育机构打交道。再见了，我的浙大……</p>

<h2>四、合</h2>

<p>时间一天天过去，巨大的工作压力让我忘却了自考的伤痛。心情平复后，我想，学分不够也就是差一门课而已；宁波大学就宁波大学吧，聊胜于无。但忙碌的工作让人无暇东顾，考试一再推迟。</p>

<p>终于，在 2012 年下半年和 2013 年上半年，我分别完成了最后一门的理论课和实践课。  <br/>
终于，在 2013 年 12 月我再次申请毕业，这一次我电话咨询了一个领导才勉强审核通过。  <br/>
终于，在 2014 年 1 月，我领到了盖有宁波大学钢印的毕业证书。</p>

<p>老婆说：「不容易啊，读了 8 年，抗战也才 8 年呢。」
「我再也不会读自考了」，我对老婆说。</p>

<h2>几件小事</h2>

<ul>
<li>所有理论课中，分数最高的是最难的《汇编语言》，但是按照毕业计划，它被废弃了。</li>
<li>所有理论课中，分数最低的是《计算机组成原理》，我没有没教材，看了几份往期试卷就上了考场。</li>
<li>我手上的公共英语三级证书可以免考本科阶段的英语课程，但以后不会用得上了。</li>
<li>我靠前端开发技术吃饭的时候，第一次考《网页设计与制作》却不及格，后来我看了以前试题的答案才明白原因。</li>
<li>一开始我很老实地在自考办预订教材，后来发现淘宝上很便宜，再后来发现不用看教材，看历年试试卷就行了，所有的考点都在上面。</li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[微信红包的业务逻辑]]></title>
    <link href="http://janlay.com/blog/2014/business-rules-of-wechat-bonus/"/>
    <updated>2014-02-01T08:30:46+08:00</updated>
    <id>http://janlay.com/blog/2014/business-rules-of-wechat-bonus</id>
    <content type="html"><![CDATA[<p>我这两天玩下来，最深的感受是，这是个活跃加熟人圈子（我这里不说朋友圈以免误导大家）的产品。
很多人不是没看到别人用吗？那是因为你很少用微信跟人交流，它就把你排斥在外了。那么微信红包在业务逻辑上做了哪些设定，使其区别于一般电商的红包玩法呢？</p>

<ol>
<li><p>红包对个人只能发给最近聊过天的人。如果你想发给没聊过天的好友，必须把这些人拉出来建群，不能发完就散。所以<strong>它是提供给活跃用户来玩的，这个门槛能激发入场玩家的「优越感」</strong>。<!--more--></p></li>
<li><p><strong>红包的未知性</strong>：领到红包之前你不知道：</p>

<ul>
<li>这个红包有多大？</li>
<li>哪些人已经领过这个红包？</li>
<li>领了多少？</li>
<li>说了什么祝福？</li>
<li>还有多少名额？</li>
</ul>


<p><strong>所有这些未知性叠加起来就变成趣味性</strong>。</p></li>
<li><p><strong>群红包的社交玩法</strong>：发红包前可以在群里预告，引发万众期待；发完可以哭诉或得瑟，而不是领完就各回各家。这个场景可以让红包的生命得以延续。发完就散的致命问题是，将用户粘性系于基于利益的弱关系，而不是基于社交的强关系。微信本身的社交属性给这个场景提供了完全的可能性——可以与个人、在聊天群、在朋友圈分享抢红包的乐趣。</p></li>
<li><p><strong>红包的输入输出</strong>：红包必须从绑定的银行卡「塞钱」进去，不允许使用已经收获的红包，微信支付没有余额所以更谈不上使用余额。红包只能提现到银行卡「回炉」后才能使用。这些规则背后的逻辑是：</p>

<ul>
<li>用户必须添加银行卡</li>
<li>用户对银行安全的信赖。</li>
</ul>
</li>
</ol>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[2013 年终总结]]></title>
    <link href="http://janlay.com/blog/2013/2013-summary/"/>
    <updated>2013-12-31T23:34:20+08:00</updated>
    <id>http://janlay.com/blog/2013/2013-summary</id>
    <content type="html"><![CDATA[<p><img src="http://janlay.com/images/2013/calendar-of-the-year.jpg" width="629" height="377"></p>

<p>眼看 2013 年就要跨过，眼看这个 blog 又要草长齐腰，眼看笔头日益生疏如写外语，在这最后一两个小时，还是来回顾一下走过的这个 2013 年吧。<!-- more --></p>

<h2>工作</h2>

<p>2013 年上半年，我完成了工作以来最大的一次<a href="http://janlay.com/blog/2013/goodbye-alipay/">转换</a>。从支付宝转岗到淘宝，从网页前端转向 iOS 客户端，从驾轻就熟的 JavaScript 转到写一种叫 Objective-C 的奇葩语言, 从带小团队到纯开发人员…… 这是一次自我革命。</p>

<p>一月份，我做完组内同学的绩效评估，开始思考当时我遇到了什么问题。我发现最近两年我的成长没有刚入职那两年快，似乎更习惯于使用现有的技术和熟悉的开发模式，我感到一丝不安，觉得需要一剂猛药才能让自己重新燃起对技术的渴望。于是，我主动出击，加入了现在的团队。</p>

<p>从后来的种种变化来看，我上半年的变化算是一次「我的转岗我做主」的成功实践，这甚至在原团队同学中赢得了「有远见」、「预言成真」的「美名」。而实际上又如何呢？冷暖自知，个人的发展轨迹无法复制他人，自身的行事方法和决策起着对结果有着决定性影响。目前来看，我对这次主动寻求变化的过程和现状感到满意。相信接下来的一年，会有更好的证明。</p>

<p>这个变化还引出一个问题：工程师的发展方向在哪里，35岁以后的技术之路怎么走？不得不说，即使在阿里巴巴，对于技术人员来说，这也是回避不了的问题。在我看来，这个问题的答案是变化的————不要在一开始就规划未来，如果你不是天降大任者，不必急着做人生规划。在这个快速变化的世界里，脚踏实地做好当下的事情，设想接下来半年一年的计划，并快速做出调整，才是有效求解方法。尽人事，知天命，成功是对善于把握机会的人的奖赏。</p>

<h2>生活</h2>

<p>今年六月四日，一次跟老婆的互动带来意外彻底改变了我们两人的生活节奏，也牵动着两边家人的心。是的，我们也要有自己的小孩了。虽然老婆现在在家里的享有绝对崇高的地位，但她还是会时不时会问我「将来有了宝宝你会更喜欢谁」的问题… 离三月初的预产期时间不多，而我想的几个名字都被老婆否决，起名这事儿真比写文章困难好多，更何况还要有小名、英文名、网名 orz&hellip; 让我再好好想想吧。</p>

<p>上半年我去了几趟老婆的家乡。三个月时间内，老婆的外婆和外公相继离世。在 5 月的一个晚上，我陪亲属们守灵时，作了一副挽联：六十年夫妻岁月催人老，百余日神仙轮回照无眠。平日里跟两位老人接触过几次，回忆起来，我想起了「认真生活」四个字。</p>

<h2>学业</h2>

<p>今年，算是把几乎快要放弃的自学考试给「完成」了。自学考试有没有用？老实说用处很小很小，这里就不多说了。2005年在老婆（当时还没升级）的怂恿下，我报名参加了浙大计算机及应用专业的自学考试，起初凭着工作几年积累的底子还算顺风顺水，后来随着政策变化和几次与衙门打交道受挫，开始心灰意冷想要放弃。还是在老婆的鼓励下，今年上半年跑到宁波去完成后一次考试，年底做完毕业登记，总算要「领证」啦。嗯，等这事儿结束后，容我细谈一下。</p>

<h2>其他</h2>

<p>今年还成功提交了两项专利，公司发了 1 万元奖励（税前）。待一两年后流程全部走完，会收到两项总计 4 万元奖励。很多吗？其实一点儿都不多，在<a href="http://www.36kr.com/p/98147.html">微软收购 AOL 800 项专利组合</a>的 case 中，单项专利均价达到 125 万美元。对我来说，钱是次要的，前期了解专利申请的「行业规则」意义更大。后来我还准备了三、四项专利想法，后面再找时间写写吧。</p>

<p>今年开始动手用 Go 写了几个小程序。目前对 Go 的设计还算满意，package 的丰富程度是制约它继续扩张的一个比较重要的因素。Go 团队非常努力，开发了不少高质量的 package, 但这事儿最终还得在社区形式热潮才算好现象。</p>

<p>今年年度体检结束后，我去医院复查了甲状腺结节问题。虽然不是恶性肿瘤，但每季度都得去复查，而去趟医院就得花一天时间。现在有这个问题的人真的非常多，医院甲状腺科人满为患，内网论坛上也在很多人反映这个问题。虽然人类终归会自我毁灭，还是希望大家要重视身体发出的信号，健康的身体才是幸福生活的基础。所以呢，明年，不，明天开始，我要加强锻炼啦。</p>

<h2>展望</h2>

<p>最后设想来年吧：</p>

<ul>
<li>迎接新生命，这是头等大事</li>
<li>看书比较慢，精读三本技术类书籍</li>
<li>工作方面，影响力要提升</li>
<li>房子装修，估计要到下半年</li>
<li>要增加陪家人的时间</li>
<li>平均每周至少锻炼一次，要求好低啊</li>
<li>把户口迁到杭州，不好办也得办了</li>
</ul>


<p>题图：我是重度日历使用者，下图是 2013 年全年日历视图，颜色越深代表记录的事件越多，还算有时间记录；颜色越浅表示越忙。</p>

<p>&ndash;EOF&ndash;</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[使用空列表，避免返回null]]></title>
    <link href="http://janlay.com/blog/2013/always-use-empty-list-instead-of-null/"/>
    <updated>2013-08-15T21:41:00+08:00</updated>
    <id>http://janlay.com/blog/2013/always-use-empty-list-instead-of-null</id>
    <content type="html"><![CDATA[<p>当期望输出列表而列表没有数据时，使用 null 还是 empty list? 这是让很多程序员迷惑的问题。当你有很多选择的时候，通常只有一种是最合适的（当然很多时候这句话前面要加上一个「在特定条件下」的定语）。针对输出/返回空还是空列表的问题，很多人多年程序设计经验表明，<strong>应该总是使用空列表</strong>。<!-- more --></p>

<p>我随手搜了一下网上的讨论：</p>

<ul>
<li><a href="http://www.javapractices.com/topic/TopicAction.do?Id=59">[Java] Java Practices -> Prefer empty items to null ones</a></li>
<li><a href="http://stackoverflow.com/questions/11536128/in-php-should-i-return-false-null-or-an-empty-array-in-a-method-that-would-us">[PHP] In php, should I return false, null, or an empty array in a method that would usually return an array?</a></li>
<li><a href="http://stackoverflow.com/questions/1969993/is-it-better-to-return-null-or-empty-collection">[C#] Is it better to return null or empty collection?</a></li>
<li><a href="http://stackoverflow.com/questions/5987011/should-i-return-an-empty-dict-instead-of-none">[Python] Should I return an empty dict instead of None?</a></li>
</ul>


<p>或者更直接一点，看看自己熟悉的 Framework 是如何返回列表的，比如 jQuery 的 <code>$(selector)</code>, iOS 的 <code>subViews</code>, PHP 的 <code>array_keys()</code>…</p>

<p>所有这些，被抽象为一条经典法则，也是各种最佳实践的核心原则之一——<strong>伯斯塔尔法则：发送时要保守，接收时要开放</strong>。</p>

<p>这条简短的法则只要一半就可以回答上面的问题。没有数据时如何表示返回的列表？请参考前半句。这里最保守的做法是输出接收方期待的类型，并且尽量让接收方不因收到你提供的数据而失败。此时，返回空列表是最保守最理想的选择。</p>

<p>作为接收方，后半句箴言揭示了客户端应该自行 handle 非预期的数据。接收方必须以开放的心态去处理收到的各种数据，不管它是不是符合期望的。事实上，接收方保持这样的习惯可以获得更好的安全性（恶意的输入也要纳入考虑范围）、灵活性（可以接收多样化的输入，符合不同的「预期」）和完整性（考虑周全的实现）。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[这是个分手的季节]]></title>
    <link href="http://janlay.com/blog/2013/goodbye-alipay/"/>
    <updated>2013-05-14T22:00:00+08:00</updated>
    <id>http://janlay.com/blog/2013/goodbye-alipay</id>
    <content type="html"><![CDATA[<div style="text-indent: 50px; margin-bottom: 30px; color: #999;font-size:80%">「是一个夏天的夜，我们悄悄地感觉，不知道这是个分手的季节」</div>


<p>今天，我要向大家宣布一件事儿，我将要转岗到集团无线部门。还有几个小时我就无法使用现在的 @alipay.com 邮箱了，所以赶在域账号变更前和大家 say goodbye, 并让你了解到更多信息。<!-- more --></p>

<p>加入支付宝以来，在这里我接触到很多行业中的精英，和一群聪明而有趣的人一起，参与了很多令人难忘的事情。经历了 Tracker/UMID/LightJS/前端安全产品这样一些开创性的工作，在团结紧张、严肃活泼的团队氛围中，重新发现自己，释放潜力，做一些正在改变人们生活方式的事情，太酷了！</p>

<p>这个时间有多久呢？近四年。在这四年中，团队发生了很多变化，包括我自己。作为纯后端技术人员，加入以来的这几年，这四年带给了我全新的研发体验：将思考注入到平凡的产品中，将思考注入到平淡的研发过程中，将思考注入到客户的使用体验中… 最终，我希望帮助开发人员能快速的理解复杂事物，快速学习并跟上主流技术的发展，并从中发现乐趣。没错，这就是我和团队正在做的事情——前端服务化。它不是突发奇想的行动，而是经过时间沉淀之后来看，是我们认为值得去做的事。</p>

<p>现在这些事情已经在稳步推进中，我想是时候尝试一些新的领域；并且我坚信，只有不断进入并在新的领域耕耘，专业路线才能保持生命力。</p>

<p>最后，让我来尝试回答几个问题：）</p>

<p>Q: 要去无线哪个部门？具体做什么呢？</p>

<blockquote><p>我会去无线客户端那边，平板方面的开发，可能是淘宝 HD 或其他的 App, 请大家多捧场啊。不过现阶段我在这方面还是个超级新手 ^&#94;</p></blockquote>

<p>Q: 你走了，他们怎么办？</p>

<blockquote><p>首先，在公司里没有谁是不可或缺的，问题没有想象的那么大。最近我一直在努力给团队同学和服务化产品一个平稳的过渡和清晰的未来，「革命尚未成功，同志仍需努力」，加油吧，少年~</p></blockquote>

<p>Q: 好像没提到要感谢谁？</p>

<blockquote><p>嗯，因为要感谢的人太多… 我想说，有幸和你们一起共事是我到目前为止最为难忘的工作经历。</p></blockquote>

<p>Q: 你过去是因为淘宝城离家近吗？</p>

<blockquote><p>必须承认，有这个考虑，但它不是关键原因。事实上我首选考虑的是支付宝的无线部门，现在的去向并非当初的目标，一切都是缘分吧。有人说这样就可以跟老婆一起上下班，不用买两辆车了。我想说：好吧，也有道理。</p></blockquote>

<p>Q: 弱弱地问一下，你还欠大家一次分享…</p>

<blockquote><p>是的，包括 Go 的介绍和 certCheck 代码走读。嗯， 下次芝士会的时候叫我 ：）</p></blockquote>

<p>Q: 最后感谢你对部门年轻化作出的贡献</p>

<blockquote><p>…… T_T</p></blockquote>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[千里之行始于足下，谈谈 Code Review]]></title>
    <link href="http://janlay.com/blog/2013/talk-about-code-review/"/>
    <updated>2013-05-08T23:27:00+08:00</updated>
    <id>http://janlay.com/blog/2013/talk-about-code-review</id>
    <content type="html"><![CDATA[<p>今天来说说 Code Review（代码审查，以下简称为 CR）。</p>

<p>在一支自认为还算正规的研发团队中，CR 应该是值得做而且必须要做的环节。下面是维基百科对 Code Review 的<a href="http://zh.wikipedia.org/wiki/%E4%BB%A3%E7%A0%81%E5%AE%A1%E6%9F%A5">定义</a>：</p>

<blockquote><p>是指对计算机源代码系统化地审查，常用软件同行评审的方式进行，其目的是在找出及修正在软件开发初期未发现的错误，提升软件质量及开发者的技术。</p></blockquote>

<h2>CR 的形式</h2>

<p>CR 有多种形式，结对 CR（两人坐在一起讨论）、多人 CR（一对多）；既有在线进行（借助 <a href="http://www.reviewboard.org/">Review Board</a> 之类的软件）也有离线进行（一伙人坐在一起讨论）的。</p>

<p>个人建议，在可能的情况下，应尽量选择多人坐在一起 CR，这里称之为「集体 CR」。借用一个时髦概念，集体 CR 可以说是 O2O CR，从线上到线下，哈哈。先看看为什么要进行 CR：</p>

<!-- more -->


<ul>
<li>CR 是保证研发质量的重要手段（这是不言而喻的）</li>
<li>CR 帮助提升成员专业能力</li>
<li>CR 改善研发团队沟通交流</li>
</ul>


<p>显然，集体 CR 形式，对后两者的促进作用尤为明显。代码开发完了，彼此相熟的团队成员一起坐下来，对大屏幕上某人的作品评头论足。Code wins arguments, 技术人员更容易因代码产生话题。讨论下来，我们在收获良好团队氛围的同时，代码质量和个人能力方面也获得提升。集体 CR 让三方受益，何乐而不为呢？</p>

<h2>CR 如何做？</h2>

<p>每个人、每个团队可能都会有自己的一套方法。我认为这方面没有条条框框，黑猫白猫，能发现问题的就是好猫。这里试着将我带团队以来的 CR 经验，梳理脉络，由浅入深分享一下。</p>

<h3>0. 重在参与：认真 CR 是对别人也是对自己的尊重</h3>

<p>无论是线上还是线下的 CR，一对一还是一对多，全情投入，才可能发现隐藏的问题，才对得起别人发起的 Review 请求。教学相长，CR 亦然。CR 过程中难免会遇到拿不准的问题，不能误人子弟啊，进行一番彻查验证再抛出观点，这样的 CR 既能帮助别人，又巩固了自己的知识领域。</p>

<h3>1. 代码风格：好的代码首先是错落有致的代码</h3>

<p>什么是错落有致的代码？这里不谈团队编码规范，因为它不影响代码的观赏性。流程控制 (flow control) 和逻辑块 (logic block) 总是存在于我们的代码中，所以正常的代码都会有缩进和换行。干净、有效的缩进和换行意味着清晰的流程控制和逻辑单元划分，呈现出来可能是这种效果：</p>

<p><img src="https://i.alipayobjects.com/e/201305/Ha9fQkBqv.png" alt="alipay-home" /></p>

<p>上图是<a href="https://www.alipay.com/">支付宝首页</a>底部一段 JavaScript 代码，看上去还不错。如果代码量比较多，通过 <a href="http://www.sublimetext.com/2">Sublime Text 2</a> 提供的 Minimap 可以快速观察出代码是否「错落有致」：</p>

<p><img src="https://i.alipayobjects.com/e/201305/HaBaeyCYP.png" alt="alipay-home" /></p>

<p>tips: 如果你的编辑器没有这个功能，打开代码后快速上下滚动，基本上也能判断出代码是否错落有致 :-)</p>

<p>反之，如果代码看上去凌乱不堪，往往意思低质量——低设计或低实现，以及不佳代码组织。</p>

<h3>2. 正确做事：代码的基本要求</h3>

<p>毋庸置疑，交付的代码必须能：</p>

<ol>
<li>正确实现业务逻辑</li>
<li>尽量确保没有明显的性能和安全问题。</li>
</ol>


<p>要识别代码是否正确实现了业务逻辑，CR 人员必须了解代码涉及的业务。一个简单的方法是，先让代码实现者把业务快速串讲一遍，然后结合到将要 Review 的代码分部分介绍相应的业务，这样可以帮助 CR 人员快速理解业务，才能进一步对业务逻辑实现的正确性进行评判。</p>

<p>在保证正确实现业务逻辑之后，还要关注实现的性能和安全性如何。不幸的是，几乎无法保证代码不存在性能或安全问题，这是人的弱点。除了 CR 人员的经验可以起到一些作用外，我们还可以借助机器（自动化工具）对来弥补人的问题。各种 Profiling 工具、自动化测试、代码扫描工具可以帮助我们发现肉眼很难观察到的潜在问题。</p>

<h3>3. 编码原则：围绕可维护性进行 Review</h3>

<p>完成前面几条之后，CR 过程可以打 60 分。这个分数达到了 CR 的基本要求，也许过程中大家能学习到一些东西，但没有「升华」——如何在看上去 OK 的实现中发现更多可以学习、值得改进的点，在业务中学习提高，是每个 CR 参与同学的内心的初衷。抓住这个点进行深入讨论分析，CR 过程就能获得突破。</p>

<p>「升华」、「学习提高」都是比较虚的字眼，如何落地实处去做呢？既然我们已经完成了代码观赏性和交付必须的正确性讨论，那么让我们把眼光稍微放长，关注一下代码的可维护性。这是更高的要求，需要 CR 同学能发现未来修改代码时可能会面对的「痛点」。通俗地说就是，代码好不好改。有几个方面可以考虑：</p>

<ol>
<li>现有代码是否需要进一步调整优化

<ul>
<li>目前运行没有问题，但是再改会比较麻烦</li>
<li>现有代码是否有存在歧义的地方</li>
<li>现有的组织方式是否有优化空间</li>
</ul>
</li>
<li><p>调整优化是否值得去做</p>

<p>修改带来风险，原因是受限于交付时间点，或修改带来的实现、自测等不确定因素。这是个平衡的问题，需要评估改和不改、以及改到何种程度与修改带来的风险，哪一个更值得去做。</p></li>
<li><p>未来业务逻辑会如何变化</p>

<p>这是下面要讨论的问题。</p></li>
</ol>


<h3>4. 回归业务：考虑业务发展的代码更有生命力</h3>

<p>从上面一条开始，我们就走上了优化改进的不归路。我把回归业务放到最后一条，也把它当作是 CR 的终极方向。</p>

<p>为什么代码的优化改进要回归到业务？我认为这是代码作为产品实现者的价值体现，这世界上不缺精彩绝伦的好代码，但是缺少好的产品。为什么这么说呢？因为再好的代码都是静态的，它就那里，一是一，二是二，它的逻辑永远不变，比如各种查找、排序算法。但是产品会变，业务逻辑会变，作为实现者的代码就需要变。代码的优化只有面向业务，才有可能在一段时间内轻松应对业务上的变化。</p>

<p>如何回归到业务呢？深入了解产品设计的背景、要解决问题、当下面临的问题，以及未来的发展思路。掌握这些现状和趋势，能帮助我们站在实现者的角度，去判断当下的代码需要如何调整以适应未来的业务变化。这里举个例子说明一下。</p>

<blockquote><p>L 君为数据仓库某产品做了一个牛逼轰轰的可拖放的树状结点功能。CR 下来问题可圈可点，实现质量总体还算比较高。</p>

<p>但是跳出这个功能本身的需求，我们发现它的实现偏离了「产出一套可复用的树状结点组件，将来应用在其他的系统中」这个发展思路。比如，L 君在实现场景中的功能点时，直接操作 DOM 元素，而这些 DOM 元素是与当前场景直接相关的。这样，未来移植这个功能的时候，必然涉及到 DOM 及其相关的 CSS 迁移，预期工作量会比较大，而且会有困难。</p>

<p>如何满足未来的业务需要呢？可以将组件功能点以 API 的形式向外提供，DOM 元素的管理完全在组件内部实现，并依赖一个可控的 CSS. 如此实现一个自包含的、可复用的组件。</p></blockquote>

<h2>结论</h2>

<p>CR 很有必要，也要讲求方法层层递进。好的 CR 不仅帮助改善产品质量，还能提升参与者对产品和技术的理解，更通过沟通改善团队氛围——于己于人、于业务方、于老板都是非常好的事情。</p>

<p>是不是有一点点动心？千里之行始于足下，现在就准备实践 Code Review 吧！</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[从获取 Referrer 的方法说起]]></title>
    <link href="http://janlay.com/blog/2012/ideas-about-grabbing-referrer/"/>
    <updated>2012-06-05T21:25:00+08:00</updated>
    <id>http://janlay.com/blog/2012/ideas-about-grabbing-referrer</id>
    <content type="html"><![CDATA[<p>要使页面在当前页跳转到下一页，有几种实现方式：</p>

<ul>
<li>HTML: 点击标准的链接<br/>
<code>&lt;a href="new/path"&gt;goto&lt;/a&gt;</code></li>
<li>HTTP header: 服务端跳转（301或302重定向）<br/>
<code>Location: http://host/new/path</code></li>
<li>HTML: 使用 meta 配置<br/>
<code>&lt;meta http-equiv="refresh" content="1; url=new/path /&gt;"</code></li>
<li>JavaScript: 改变 <code>location.href</code> 属性<br/>
<code>location.href = 'new/path';</code></li>
<li>JavaScript: 使用 <code>location.replace()</code> 方法<br/>
<code>location.replace('new/path');</code></li>
</ul>


<p>以上方法实现方式不同，可以灵活运用于不同场景。悲剧的是，虽然可以达成跳转需求，但通过一些方式跳转到新页面时不会带上引用页地址 (<a href="http://en.wikipedia.org/wiki/HTTP_referer">referrer</a>, HTTP header 错误地把它拼写成 &ldquo;referer&rdquo;). 为什么需要引用页？<!-- More -->因为：</p>

<ul>
<li>站长和产品经理们要了解用户使用自己网站的习惯，从而发现使用流程中的问题</li>
<li>有时候服务端需要验证来源，确保点击来自指定的地址</li>
</ul>


<p>上面说“一些方式”而没有具体指明到底是哪些，是因为实践发现，不同的浏览器甚至不同版本，在处理这些方式时，采取不同的引用页发送策略。<a href="http://www.receptional.com/blogs/web-analytics/how-redirect-and-keep-referrer-data-web-analytics">这里</a>有人做了很好的总结:</p>

<p><img src="http://janlay.com/images/2012/redirect-referrer-test-results.png" width="579" height="549" title="redirect referrer test results" ><br/>
(image <a href="http://www.receptional.com/blogs/web-analytics/how-redirect-and-keep-referrer-data-web-analytics">via</a>)</p>

<p>表格中的浏览器比较老，不过你可以通过它提供的<a href="http://labs.receptional.com/demos/referrer-testing/">测试页面</a> 验证各种方式在新浏览器中是否会发送引用。</p>

<p>那么有没有靠谱的方法，确保各种浏览器必然会发送引用页呢？答案是肯定的，相对繁琐一点而已。大概实现方式是这样的：</p>

<ol>
<li>手动创建一个 <code>&lt;a&gt;</code> 标签，设置其地址为要跳转的目标地址</li>
<li>隐藏这个链接标签</li>
<li>手动调用其 <code>click()</code> 方法，模拟用户点击</li>
</ol>


<p>stackoverflow 上面有人贴了<a href="http://stackoverflow.com/a/1890744">代码</a>。</p>

<p>似乎到这里就可以结束了。不过我想探讨的是，为什么会有这些差异？</p>

<p>在我看来，发送引用页是浏览器负责的事情，每个浏览器及其不同版本，对是否要发送引用可能持有不同的看法，无所谓规范或标准。比如，多 tab 浏览器大多支持滚轮点击链接开新 tab, 你说这种情况下要不要发送引用呢？你可能会说：“要发送”，事实上大多数国产浏览器都不会发，而 IE 会。</p>

<p>另一方面，因为引用页数据是直接跟隐私相关的，所以会导致有不同的理解。比如那个 <code>location.replace()</code> 如果我是浏览器的 PD, 会要求不发。因为按照我的理解，这个方法的功能是，抹掉历史记录中的当前页；本地都抹掉了，从隐私保护上来说，我不希望下一个页面知道来源。</p>

<p>最后推荐一下我几年写的一篇相关的 <a href="http://life.janlay.com/2006/12/ie_21.html">blog</a>（可能要翻墙）很青涩，勿笑～</p>

<p>那么，你的看法呢？</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[JavaScript 奇技淫巧背后的秘密]]></title>
    <link href="http://janlay.com/blog/2012/javascript-the-secret-behind-the-tricks/"/>
    <updated>2012-03-29T15:28:00+08:00</updated>
    <id>http://janlay.com/blog/2012/javascript-the-secret-behind-the-tricks</id>
    <content type="html"><![CDATA[<p>毫无疑问，JavaScript 是当前<a href="https://github.com/languages">最流行</a>脚本语言，不过我认为它获此境遇是历史原因造成的，而非源自优秀的设计。相反，JavaScript 的设计过于灵活和随意，以致坊间流传着各种“杂技”，其中不乏冠之以“高性能”的技巧。而初学者和 JavaScript “熟手” 面对这样的耍杂代码无所适从，不求甚解之下就往往忽略了它。</p>

<p>但是，为什么这些奇技淫巧的代码没有导致语法错误？为什么它也能运行？如何举一反三？解开这些问题，需要了解被大多数人忽略的 JavaScript 基础知识。这里试图解析一些 tricks, 告诉你它们背后的秘密。<!-- More --></p>

<blockquote><p>提示：</p>

<ul>
<li>要尝试下面的代码，你需要使用 JavaScript 控制台，或一个交互式 JavaScript 解析器。</li>
<li>下面代码中的变量如无特别注明，均假定其已通过 <code>var</code> 定义。</li>
</ul>
</blockquote>

<h2>短路语法：3 个为什么</h2>

<h3><code>1 || alert('ok')</code></h3>

<blockquote><p>先上甜点，这是大家司空见惯的短路语法。这里后面的代码不会被执行，因此它不是我要说的重点。但是，它为什么不会报错？因为它是普通的运算。</p></blockquote>

<h3><code>1 &amp;&amp; foo = 1</code></h3>

<blockquote><p>这行代码什么会报错？经工友指出，是因为 <code>=</code> 运算符优先级比 <code>&amp;&amp;</code> 低，它实现执行等价于 <code>(1 &amp;&amp; foo) = 1</code>，显示赋值给另一个值是不允许的。</p></blockquote>

<h3><code>1 &amp;&amp; (foo = 1)</code></h3>

<blockquote><p>这行代码为什么能运行？因为 <code>()</code> 运算符优先级比 <code>&amp;&amp;</code> 高。</p></blockquote>

<h3>更多优先级问题</h3>

<ul>
<li><code>1 &amp;&amp; 1, foo = 2</code> 可以运行，因为 <code>,</code> 优先级最低，表达式等级于 <code>(1 &amp;&amp; 1), foo = 2</code>。</li>
<li><p><code>1 &amp;&amp; function() { foo = 2 }()</code> 也没有问题，因为 前面说了 <code>()</code> 优先级比 <code>&amp;&amp;</code> 高。</p>

<p>PS:  使用 <code>()</code> 求值即获得 JavaScript 内部可以接受的值。例如 <code>(2.0000000000000001)</code> 在一些机器上可能会返回 <code>2</code>。感谢工友 <a href="http://twitter.com/hanyee">hanyee</a>  &amp; <a href="http://t.qq.com/dovapour">vapour</a> 对运算符优先级问题的指正。</p></li>
</ul>


<h2>运算符技巧：把字符串转换为数值类型</h2>

<p>先从一个单目运算符开始。什么是单目？只有一只眼睛，也就是说，运算符只接收一个参数。</p>

<h3>转型：<code>+'1'</code></h3>

<blockquote><p>我们知道 <code>-</code> 作为减法运算符时接收两个参数，而作为求负运算符时，接收一个参数。JavaScript 支持另一个不多见的“求正”的 <code>+</code> 运算符。显然，<code>+</code> 会尝试把任何接收到的参数转换为数值型。如此，我们就有了一个廉价转型方法，考虑一下：<code>parseInt('123')</code> vs <code>+'123'</code>.</p></blockquote>

<h3>转型并取整：<code>'123.4' | 0</code></h3>

<blockquote><p>这里使用“或运算”将左边的字符串隐式转换为数值型，再与 <code>0</code> 或。<strong>所有位运算都要求使用 32 位整数参与运算</strong>。所以这又是一个廉价的转型取整方法。考虑一下 <code>Math.floor('1234.5')</code> vs <code>'1234.5' | 0</code>.</p>

<p>但是，32 位整数表示的数据范围是有限的，因此这一招在数值超过 2<sup>31</sup> - 1 时不适用，考虑一下 <code>'12345678912.3' ^ 0</code></p></blockquote>

<h3>如何举一反三？</h3>

<ul>
<li><code>'1234.5' ^ 0</code> 完成转型并取整，与 <code>0</code> 异或，得到它本身，还记得这个<a href="http://baike.baidu.com/view/1452266.html#2">规则</a>吗？</li>
<li><code>~~'1234.5'</code> 同样可以完成转型并取整，因为两次取反得到相同的值。</li>
<li><code>--'1234.5'</code> 会失败。虽然我们说负负得正，但在 C 语系中，<code>--</code> 运算符优先级高于单个 <code>-</code>。解决办法很简单，把 <code>--</code> 写成 <code>- -</code> 就好啦，增加一个空格避免运算符被错误地识别。同理，<code>++</code> 也要这样处理。</li>
</ul>


<p>渐入佳境哈？接下来，我们把前面的知识综合运用，试图解析闭包中代码中括号的秘密。</p>

<h2>懒人的闭包</h2>

<p>闭包即 <code>function() {}</code> 代码块。通常需要控制 JavaScript 变量作用域时，我们把代码放在这个块中运行：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>    <span class="kd">var</span> <span class="nx">foo</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">bar</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
</span><span class='line'>    <span class="nx">sth</span><span class="p">(</span><span class="nx">foo</span><span class="p">,</span> <span class="nx">bar</span><span class="p">);</span>
</span><span class='line'><span class="p">})();</span>
</span></code></pre></td></tr></table></div></figure>


<h3>失败的闭包</h3>

<p>上面的代码中要写两对括号，如果代码块太长的话，上下的括号不方便对照。于是，你可能会有意无意漏掉 <code>function</code> 周围的括号：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="kd">var</span> <span class="nx">foo</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">bar</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
</span><span class='line'>  <span class="nx">sth</span><span class="p">(</span><span class="nx">foo</span><span class="p">,</span> <span class="nx">bar</span><span class="p">);</span>
</span><span class='line'><span class="p">}();</span>
</span></code></pre></td></tr></table></div></figure>


<p>很不幸，运行这段代码会报语法错误（不同 JavaScript 执行程序抛出的异常信息可能会不一样）：</p>

<p><code>Exception: SyntaxError: Unexpected token '('</code></p>

<p>背景知识：<strong><code>()</code>只能用来求值、定义参数列表或调用函数表达式（<em>expression</em>）</strong>。本来，<code>function() {}</code> 定义的一个函数字面量（如同数组字面量 <code>[]</code>）表达式是可以拿来调用的，但是由于设计上的原因，<code>function</code> 有两种表达形式：</p>

<ul>
<li><code>function fn() {};</code>: 这是函数声明（<em>declaration</em>）的语句（<em>statement</em>）；</li>
<li><code>var foo = function() {}</code>: 这是函数字面量（<em>literal</em>）表达式。与上面雷同的写法 <code>var foo = function fn() {}</code> 也是合法的表达式，不过有一点小<a href="http://ejohn.org/apps/learn/#11">区别</a>。</li>
</ul>


<p>即是说，<strong>JavaScript 需要有足够的上下文（<em>context</em>）才能判断 <code>function</code> 的使用属于语句还是表达式</strong>。</p>

<p>所以，加上一对括号，就排除了 <code>function</code> 作为语句声明的目的，既然不是语句，那就是函数表达式咯。</p>

<p>对于单独（在语句前后加上分号将其表达为独立语句）的 <code>;function() {}();</code> 来说，JavaScript 无法区分其中的 <code>function</code> 是表达式还是语句。此时，JavaScript 选择了传统的语句识别，于是它被识别为两个语句————两个有问题的语句：前者缺少函数名称声明，后者不允许使用空的 <code>()</code> 进行求值。</p>

<p>于是懒人们行动了，网上流传了一些不写第一组括号也能正确运行的闭包。</p>

<h3>懒人的解决方案</h3>

<p>这是比较常见的懒人闭包：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='javascript'><span class='line'><span class="o">+</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>    <span class="kd">var</span> <span class="nx">foo</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">bar</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
</span><span class='line'>    <span class="k">return</span> <span class="mi">10</span><span class="p">;</span>
</span><span class='line'><span class="p">}();</span>
</span></code></pre></td></tr></table></div></figure>


<p>为什么它能执行？因为它很好地“营造”了一个让 JavaScript 将其中的 <code>function</code> 识别为表达式的环境————通过单目求正运算符，让 JavaScript 知道这里的 <code>function</code> 不可能是语句。哪有对语句进行运算的啊。</p>

<h3>开始举一反三吧</h3>

<p>既然写个 <code>+</code> 运算符就行，那就好玩了：</p>

<ul>
<li><code>-function() { return -1; }()</code> 求负也行哈。PS: 如果你运行这个语句，会得到 <code>1</code>, 因为函数返回的 <code>-1</code> 被你求负了一次。</li>
<li><code>!function() { return -1; }()</code> 取非也没问题。提问：运行它会返回什么？</li>
<li><code>1 + function() { return -1; }()</code> 非常标准的表达式，当然 OK 啦…</li>
<li><code>void function() { return -1; }()</code> 亲，<code>void</code> 也是运算符哦，<code>delete</code> 能用吗？当然可以！</li>
<li><code>1, function() { return -1; }()</code> 别把逗号不当运算符！</li>
<li><code>~function() { return -1; }()</code> 位运算符也来凑热闹了哈…</li>
<li>还可以写很多，随便怎么玩，只要组成表达式就行，自由发挥吧…</li>
</ul>


<p>嗯，先解析到这里吧。后续还有新的 trick, 我也会在这里继续更新。</p>

<p>最后，我想说的是，上面这些 tricks 大多都是 JavaScript 不好的设计导致的滥用，一些变种的代码让团队协作更困难。学会运用这些并不会显著增长你的技能，但了解背后的原理则会让你更深入地理解 JavaScript.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[JavaScript 与浏览器插件互操作最佳实践]]></title>
    <link href="http://janlay.com/blog/2012/best-practice-in-javascript-interoperate-with-controls-or-plugins/"/>
    <updated>2012-03-18T23:34:00+08:00</updated>
    <id>http://janlay.com/blog/2012/best-practice-in-javascript-interoperate-with-controls-or-plugins</id>
    <content type="html"><![CDATA[<p>最近，同事遇到了 JavaScript 调用 Flash ActionScript 接口失败的问题，并总结了一些改进方法。看到分享后，结合之前遇到类似的问题，我也总结了一下浏览器插件，包括ActiveX 控件（在IE中）或插件（非IE中）中的互操作问题。<!-- more --></p>

<h2>为什么 JavaScript 调用 Flash 接口有时候会失败？</h2>

<p>Flash 实质是个浏览器插件，凡是插件都会有初始化的问题。具体来说，插件元素插入页面后，对应的插件代码才会在独立的线程中开始初始化，而页面线程会继续渲染并执行 JavaScript. 这时，如果插件初始化未完成，而 JavaScript 调用它的接口，那么调用会失败。等插件初始化好了，JavaScript 又不再调用，则业务会失败。</p>

<h2>选择合适的解决方案</h2>

<p>所以这里有两种解决方案：</p>

<ul>
<li>Flash 通知外部 JavaScript 它已经 ready, 外部 JavaScript 再调用 Flash 接口</li>
<li>外部 JavaScript 不断检测 Flash 某个接口是否可用，直到可用时才调用它</li>
</ul>


<p>两个方案的适用场景不同，前者适合用于 Flash 代码“可控”的环境，即开发人员有能力接触并修改 ActionScript, 还可以很方便地发布新版本; 后者适用于控件由第三方提供，不方便添加主动调用 JavaScript 的场景。</p>

<p>所以，通常 Flash 应用选择第一种方案，而像密码控件这类更新比较缓慢的应用选择第二种。</p>

<h2>控件接口检测方式</h2>

<p>注意检测控件接口可用时，不能使用 <code>if(element.api)</code> 这种形式，IE 会在 <code>api</code> 接口不可用时抛出异常，因为它不是普通的 JavaScript 方法或属性，即使可用的时候 <code>typeof</code> 运算符也会返回 <code>'unknown'</code>。这里应该使用 <code>if(typeof element.api !== 'undefined')</code>.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[方便地更换 Kindle 4 中文字体]]></title>
    <link href="http://janlay.com/blog/2012/a-convenient-way-to-replace-chinese-font-for-kindle-4/"/>
    <updated>2012-02-24T16:40:00+08:00</updated>
    <id>http://janlay.com/blog/2012/a-convenient-way-to-replace-chinese-font-for-kindle-4</id>
    <content type="html"><![CDATA[<p>入手 Kindle 4 之后，一直觉得用着还算顺手，就懒得折腾。除了不支持 epub 格式的电子书, 我觉得目前 Kindle 已经很不错了。听音乐、上网这些功能，个人以为，对电子阅读器来说有些画蛇添足。</p>

<p>但是再好的消费品也有折腾的空间（或者说乐趣），不是吗？嗯，那就先从字体开刀吧。<!-- More --></p>

<h2>理顺编码问题</h2>

<p>之前我已经注意到 Kindle 显示中文的问题：有些文档汉字显示差强人意，表现在拐角虚化而且线条粗细不一，有点像普通屏上的小号细圆体显示效果；而有的文档则显示还算好看的那种字体（内置的某种黑体）。</p>

<p>这是因为 Kindle 默认编码不是中文（据说是日文？）。解决方法很简单也有点繁琐：在书本列表页面调出键盘，依次输入以下三行命令：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>;debugOn
</span><span class='line'>~changeLocale zh-CN.utf8
</span><span class='line'>;debugOff</span></code></pre></td></tr></table></div></figure>


<p>每行结束时输入回车符，完成后无需重启设备即可生效。</p>

<p>通过上面的设置，可以让 Kindle 对所有中文电子书显示内置的字体，它已经可以满足大多数人的视觉要求了。</p>

<p>但是有没有想过，尝试一下其他中文字体在 Kindle 上的显示效果呢？<a href="https://twitter.com/wanderxjtu/statuses/170328311774445570">这里</a>就人推荐用雅宋体。</p>

<p>网上已经有很多介绍如何给 Kindle 更换字体的<a href="http://chuo.me/2012/01/kindle4">文章</a>，大多通过放置调试文件，然后重启 Kindle 进入调试，开启 USBnet 然后通过 SSH 登录进入修改的套路。这个流程过于繁琐，非常不利于折腾。下面介绍一下如何在不登录 SSH 的情况下更换 Kindle 4 字体的方法。</p>

<h2>准备字体</h2>

<p>首先，准备好要替换的中文字体。如果没有相应的黑体（Bold）版本，可以把 <code>foo.ttf</code> 复制一份为 <code>foo-Bold.ttf</code>。</p>

<p>然后，连接 Kindle 到电脑，并在 Kindle 上创建 <code>font</code> 目录，将刚才准备好的字体放进去。</p>

<h2>SSH 登录</h2>

<p>第一次替换字体时，你仍然需要登录 SSH 进入 Kindle 系统，具体方法请看前面的链接。</p>

<h2>写入符号连接</h2>

<p>登录后，首先要挂载 Kindle 的系统分区：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="o">[</span>root@<span class="o">[</span>192_168_15_244<span class="o">]</span> root<span class="o">]</span><span class="c"># mount /dev/mmcblk0p1 /mnt/base-mmc</span>
</span></code></pre></td></tr></table></div></figure>


<p>然后，创建字体文件的符号连接：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="o">[</span>root@<span class="o">[</span>192_168_15_244<span class="o">]</span> root<span class="o">]</span><span class="c"># ln -s /mnt/base-us/font/current.ttf /mnt/base-mmc/usr/java/lib/fonts/current.ttf</span>
</span><span class='line'><span class="o">[</span>root@<span class="o">[</span>192_168_15_244<span class="o">]</span> root<span class="o">]</span><span class="c"># ln -s /mnt/base-us/font/current-Bold.ttf  /mnt/base-mmc/usr/java/lib/fonts/current-Bold.ttf</span>
</span></code></pre></td></tr></table></div></figure>


<p>最后，修改字体配置文件：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="o">[</span>root@<span class="o">[</span>192_168_15_244<span class="o">]</span> root<span class="o">]</span><span class="c"># vi /mnt/base-mmc/usr/java/lib/font.properties</span>
</span></code></pre></td></tr></table></div></figure>


<p>打开 <code>font.properties</code> 文件后，搜索 <code>hans</code>, 将连续的四行修改为：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'>hans.0<span class="o">=</span>current.ttf
</span><span class='line'>hans.plain<span class="o">=</span>current.ttf
</span><span class='line'>hans.1<span class="o">=</span>current-Bold.ttf
</span><span class='line'>hans.bold<span class="o">=</span>current-Bold.ttf
</span></code></pre></td></tr></table></div></figure>


<p>保存退出 vi 后，就可以退出 SSH, 重启查看新字体的显示效果了。</p>

<h2>后续更换字体</h2>

<p>以后，再换字体就非常方便了。只需把 Kindle 连接到电脑，复制字体文件到 <code>font</code> 目录，并确保新字体文件包括 <code>current.ttf</code> 和 <code>current-Bold.ttf</code>. 然后重启设备即可生效。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[保护密码：从客户端到服务器端]]></title>
    <link href="http://janlay.com/blog/2012/secure-your-password-from-client-side-to-server-side/"/>
    <updated>2012-02-14T22:01:00+08:00</updated>
    <id>http://janlay.com/blog/2012/secure-your-password-from-client-side-to-server-side</id>
    <content type="html"><![CDATA[<p><a href="http://sofish.de/">sofish</a> 在知乎上邀请俺回答知乎上的问题“<a href="http://www.zhihu.com/question/20060155">如何保证用户登陆时提交密码已经加密？</a>”，下面这些也算是对他那篇<a href="http://sofish.de/2020">博客</a>的补充吧。</p>

<p>如何保证用户登陆时提交密码已经加密？密码是否已加密，需要客户端和服务端建立约定，双方按约定办事就行了。</p>

<p>这里提到的另一个问题是，如何保证传输安全？</p>

<p>最理想的方案当然是走 HTTPS 协议. HTTPS 在理论上是可靠的，但在国内会打一些折扣：你可以随便找一台电脑看看有没有安装商业公司或机构的根证书，这些根证书为线路某节点成为中间人提供了可能性；同时，在木马横行的年代，密码在加密提交前可能就被拿到了，此时 HTTPS 成了摆设，这是为什么国内流行密码控件的一个重要原因。<!-- more --></p>

<p>从成本和需求上考虑，对于众多对安全性要求不高的个人网站，仍然可以考虑采用 HTTP 传输，密码提交前通过 JavaScript 加密。由于 JavaScript 代码暴露在客户端，因此一般通过不可逆的加密方法加密密码，而对于任何摘要式的加密算法，都可以通过类似 md5 字典的方式直接查表获知弱密码，所以要混入 salt 以增加制作字典的成本。可想而知，解密只是时间成本的问题。因此这里的重要前提是“对安全性要求不高”。</p>

<p>如何验证密码呢？一个可行的方法是，客户端提交 md5(password) 密码（如上所述，此方法只是简单保护了密码，是可能被查表获取密码的）。服务端数据库通过 md5(salt+md5(password)) 的规则存储密码，该 salt 仅存储在服务端，且在每次存储密码时都随机生成。这样即使被拖库，制作字典的成本也非常高。</p>

<p>密码被 md5() 提交到服务端之后，可通过 md5(salt + form[&lsquo;password&rsquo;]) 与数据库密码比对。此方法可以在避免明文存储密码的前提下，实现密码加密提交与验证。</p>

<p>这里还有防止 replay 攻击（请求被重新发出一次即可能通过验证）的问题，由服务端颁发并验证一个带有时间戳的可信 token （或一次性的）即可。</p>

<p>当然，传输过程再有 HTTPS 加持那就更好了。</p>

<p>最后，为什么要密码控件？原因之一是上面说的，要防止密码在提交前被截获。当然，还有一些其他原因，工作所限，这里就不说了。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[TP-LINK TL-WR703N 无线路由器的信号和 VPN 使用]]></title>
    <link href="http://janlay.com/blog/2011/using-vpn-with-tp-link-tl-wr703n-router/"/>
    <updated>2011-10-23T19:36:00+08:00</updated>
    <id>http://janlay.com/blog/2011/using-vpn-with-tp-link-tl-wr703n-router</id>
    <content type="html"><![CDATA[<p>这款无线路由器最大的特点是小巧，非常小，以至于超出你对路由器的认知，俨然就是个微型的 Mac Mini. 其他特点我就不多说了，<a href="http://www.tp-link.cn/pages/product-detail.asp?d=225">官网</a>和<a href="http://www.360buy.com/product/505129.html">京东</a>上都有详细介绍。</p>

<p>最近路由器开始流行 3 根天线，这款产品则完全没有外置天线，不由得让人怀疑它的信号是否够用。经过在家里实地测试，效果不错：<!-- more --></p>

<ol>
<li>信号非常好，家里隔墙仍然是满格信号</li>
<li>1M ADSL 满速下载 (133KB/s)</li>
</ol>


<p>VPN 问题，TP-LINK 路由器默认的 MTU 值是 <code>1480</code>, 在 Mac OS 系统上连接 VPN 会发生连上后有网络请求就立即断开的问题，在<em>网络参数 > WAN口设置 > 高级设置</em>中把 MTU 修改为 <code>1492</code>, 重启路由器即可正常连接 VPN. 其他网络上 VPN 类似问题亦可参考此设置。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[iPhone 4 维修散记 & 注意事项]]></title>
    <link href="http://janlay.com/blog/2011/repairing-my-iphone-4-in-genius-bar/"/>
    <updated>2011-08-28T21:52:00+08:00</updated>
    <id>http://janlay.com/blog/2011/repairing-my-iphone-4-in-genius-bar</id>
    <content type="html"><![CDATA[<h2>一、起</h2>

<p>Home 键失灵应该是 iPhone 4 最普遍的问题了，老婆也不幸遭遇此问题，使用起来让人抓狂。更紧迫的是，一年有限保修还有两三个星期就要失效。</p>

<p>网上流传着各种大法，效果如何则基本上要看人品。上周五刚好有机会去魔都出差，就顺便去陆家嘴苹果店看看能不能修好。<!-- more --></p>

<h2>二、承</h2>

<p>地铁 2 号线坐到陆家嘴出来后，借助 GPS 和指南针定位，很快就可以确定苹果店的方向，一眼望过去就可以看到苹果的 Logo. 需要注意的是，作为一个乡下人，不要试图横穿马路，从头上的环形天桥就可以过去了。</p>

<p>苹果店其实是在地下，地上只能看到玻璃墙上的 Logo. 门口传说中的玻璃台阶非常厚，所以不用担心会被踩破；表面是一层毛玻璃，可以有效防滑，当然也可以防止楼下的色狼往上透视。不过即便如此，还是建议 MM 们不要穿裙子去，因为台阶每级之间是有缝隙的。</p>

<p>进入之后，环视一圈即可找到 Genius Bar (天才吧), 建议苹果在英文上面加一行“维修处”以照顾英语不好的用户。</p>

<p>由于天才吧预约非常困难，所以这次俺是去霸王修的。随机抓一个“天才”，开始陈述问题。后面的进展花了三个多小时，非常无聊，所以俺就把要点写一下。</p>

<h2>三、转</h2>

<ol>
<li>苹果店维修第一注意事项：不要拿越狱过的机器去。他们会以安全为理由，拒绝处理越狱机。不知道是否有程序可以经由数据线抓取苹果维修工作站的数据，嘿嘿。如果你已经越狱了，在已经备份过数据的前提下，可以自行恢复出厂设置，或是找个“天才”给你恢复一下系统。另外，他们对 iOS 5 Beta 版系统上遇到的问题不提供支持。</li>
<li>苹果店不承诺保护你的数据，所以务必事先做好备份。</li>
<li>维修时带上手机就行了，不需要携带任何单据或发票。</li>
<li>如果你没有在网上预约，那么到现场后一定要确认“天才”把你的维修要求加入他们的队列。具体来说，每个“天才”都配备一只 iPod Touch, 里面有个应用会管理当天所有的维修请求，必须确保你的信息在里面，否则等到关门也不会叫你的。</li>
<li>加入队列后，你应该到天才吧右边的长凳上去 standby. 左边是网上预约和取机的等候区域。</li>
<li>“天才”处理 Home 键失灵的问题，并不是用网上传说的各种大法，一旦确认问题可以重现，会给你更换除电池和后盖外的所有部件。这样，显示屏、主板、摄像头、无线模块等部件都会被替换掉。你拿到新机后，会发现序列号已经变更，MAC 地址也变了。</li>
<li>因为信任，所以简单。你把机器交进去之后，并不能确认他们到底有没有给你换电池（换新的或是一只更旧的）</li>
</ol>


<h2>四、合</h2>

<ol>
<li>等待大约 2 小时就可以取回新机了。拿到后再检查一下 Home 键问题是否存在，最好还要看看显示屏有没有问题。</li>
<li>港行手机换机后，主要部件就会全部成为国行，Youtube 应用会消失，是喜是悲各位自行判断。手机新序列号会记录到系统，不必担心保修问题。</li>
<li>换新机后，保修期限会有所变化，过期时间为原手机过期时间和拿到新手机后 3 个月中较晚的一个。</li>
<li>“天才”们的普通话中会夹杂大量的英语词汇，如果一时没有反应过来，不要不懂装懂，大可义正辞严地要求他说普通话。</li>
<li>不要称“天才”们为“服务员”，他们会在第一时间纠正你的错误。</li>
</ol>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[谈谈我对 OAuth 登录并关联新建帐户的看法]]></title>
    <link href="http://janlay.com/blog/2011/associate-with-another-account-system-via-oauth/"/>
    <updated>2011-08-25T10:42:00+08:00</updated>
    <id>http://janlay.com/blog/2011/associate-with-another-account-system-via-oauth</id>
    <content type="html"><![CDATA[<p>最早，我使用相关技术的产品体验，是从 <a href="http://www.yupoo.com">Yupoo</a> 开始的，当时还不是用 OAuth, 而是 OpenID. 那时候我也在关注 OpenID, 觉得 Yupoo 很潮，欣然试了一把。使用 OpenID 登录过程很顺利（我甚至开通了 <a href="https://www.myopenid.com">myopenid</a> 的语音检验），但是回到 Yupoo 之后，我发现系统居然要求我输入新用户名和密码。虽然有点纳闷，还是抱着小白鼠的心态，输入了新帐户信息。</p>

<p>而今，曾经被热捧的 OpenID 技术逐渐式微，OAuth 方案随着 Twitter 的崛起已成为事实上的帐户验证和连接标准。<!-- more --></p>

<p>毫无疑问，OAuth 可以加强用户登录的动力，而新用户登录即意味着注册用户量的增长。所以 OAuth 是个快速提升用户数的好方法（创业网站太需要这个数字了）。</p>

<p>用户首次 OAuth 登录时，可能需要输入两种密码（登录外部帐户和创建本系统帐户）才能完成全部登录流程，这样增加了用户的输入成本。不过，最近我注意到，越来越多的网站在站外登录后仍然要求创建本系统内的帐户，连一直和 Twitter 保持紧密联系的 bit.ly 都开始要求用户注册本地帐户。当然，还有今天了解到的丁香园站外登录。</p>

<p>为什么要这样？表面上看，原因很简单——用户是你网站的全部，你不能把最重要的资产托付给别人。进一步思考，即便你的网站现在完全信任外部登录提供方，你也必须走上这条路，因为：</p>

<ul>
<li>OAuth 得到的信息是有限并且受限的。你提供特定的服务往往需要进一步获知用户的资料，比方说你需要用户的真实姓名，QQ 无法提供给你。受限意味着你不能随心所欲访问你想要的信息，登录提供方和用户都可能有意无意调整授权策略，从而让你的数据请求无功而返。</li>
<li>外部登录帐户不可能永远满足你的需求。你的系统在壮大，你的业务方向可能会调整，你的系统架构必须不断改进——你需要灵活地控制帐户体系，才有可能满足这些变化。</li>
<li>关联使得新帐户仍然保有与外部网站交换信息的能力，这对社会化功能来说很重要。</li>
<li>生意场上没有永远的朋友。</li>
</ul>


<p>结论：OAuth 是个好技术，善用才能用好。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[寻找江南]]></title>
    <link href="http://janlay.com/blog/2010/looking-for-the-goodliness/"/>
    <updated>2010-12-28T10:53:00+08:00</updated>
    <id>http://janlay.com/blog/2010/looking-for-the-goodliness</id>
    <content type="html"><![CDATA[<p>每次听到看到“黄四郎”的名字，就条件反射地想起一句诗：黄四娘家花满溪，千朵万朵压枝低。还有一句“江南可采莲，莲叶何田田”、一句歌词“秦淮水暖烟波里”、《江畔独步寻花》一起构成了一个乡下少年对江南的初步印象，虽然后者不一定说的是江南。</p>

<p>那时候，头脑中YY的江南简直是人间仙境，去一趟江南似乎是个遥不可及的梦想。机缘巧合，22 岁时离开了家乡来到杭州工作，于是梦想照进现实，现实却并非梦想那样美好。<!-- more --></p>

<p>2003年，我观赏到了传说中的钱塘江大潮。在去海宁的路上，头脑中浮现的是小学课本中描述的壮观场面。而在回杭州的路上，我回味的却不是一线天，而是农庄旁、河道边、盛夏的垂柳和休憩的村民。“我打江南走过，那等在季节的容颜如莲花的开落”。</p>

<p>时光模糊了记忆，抹平了创伤。越走近江南，昔日朦胧的印象和梦想越发真实，越感距离。我开始意识到理想不过是各种现实的综合体。现实就是家、户口和莫名的距离。我寻找的不是江南，是归宿。</p>

<p>“念去去，千里烟波，暮霭沉沉楚天阔。”回家的意向日趋强烈，似乎家乡本来就很好呢。也许只是打江南走过，也许这里只是平淡人生的一座驿站。</p>

<p>人生总是充满了不期而遇，那个生命中的人终于出现。说起来只是因为她听说武汉很冷又很热，于是打消了回家的念头。于是，归宿的意义发生了变化。</p>

<p>原来，江南不在苏州，不在杭州，不在南京，它在人们的心里——它本来就在我心里。江南曾经是一幅飘渺的画卷，曾经是诗人的灵感源泉，曾经是人生旅途的风景。现在，它是一切美好的总和。</p>
]]></content>
  </entry>
  
</feed>
