分类: 技术

各种开发心得,包括语言、软件工程、开发工具等

  • 悲催的Android Webview——记新版广告墙开发

    悲催的Android Webview——记新版广告墙开发

    前一阵很辛劳,所以荒废了博客。前几天终于完成了这项艰苦卓绝的工程:HTML5版广告墙,决定写篇文章,记录一下踩过的坑。

    项目介绍

    广告墙属于典型的列表式应用:打开后是无尽列表,通过滑动手指驱使列表滚动,上拉加载更多内容,下拉刷新列表(这次没做)。单击列表中的某项,打开详细页;单击上面的后退按钮,退回上一级页面。

    考虑到目标平台是Android系统,内嵌WebKit内核,我采用了时下流行的HTML5+CSS3技术,并且做好了向下兼容的准备。为了减少代码量,我放弃了jQuery,也没有考虑半调子Zepto,全部使用原生开发。操作上,开始想用Hammer.js封装手势,后来处理滚动效果处理得不好,最终选择了iScroll 5。模板方面,前端使用Handlebars.js,后端使用Mustache,所以编译时还写了段代码做转换。

    项目代码托管在github上,因为前端不涉及到业务逻辑,所以仓库是公开的,有兴趣的同学以clone下来看看。

    技术选型大概聊这些,接下来开始进入正题。

    性能vs兼容性

    实际上,随着硬件的发展,对于列表式这种非性能敏感性应用来说,性能早已不是制约其发展的首要因素了。另一方面,有一定专业精神的前端如我,平时也会注重积累各种性能优化的点,并不断在项目中实验、实施——所以我就很搞不明白,技术实力强大如jQuery,为啥会弄出个jQuery Mobile这种完全不能用的东西出来……

    相反,在这个项目中,由于平台分裂带来的兼容性问题成了最大的绊脚石。尤其是这些兼容性问题,极少找到现成的解决方案……下面举两个例子:

    Vivo X909 和z-index

    正常情况下,position:absolute;的dom节点在不显式设置z-index属性区分层级的时候,默认后面的节点会显示在前面的节点上方。大多数浏览器里都是这样,可是Vivo X909就不行。

    列表大家都知道,点击某项,详情会从右边滚动进来。由于没有设置-webkit-tap-highlight-color(现在想想应该做的,下周回去加上吧),所以这个bug当时的的表现是“单击没有反应”。这种情况很难调试,我开始以为JS有错,试了很久,后来配置好Weinre(见上篇博客),才找到症结所在。最后把几个图层分别加上z-index,问题解决。

    早期Android的animation-fill-mode

    我们仍有20%的用户在使用2.3.x版本的Android,所以这块市场必然不能放弃。在小米2上调是好的动画,在老HTC G10上,滑出的图层在动画结束后会消失。这次好在有了上次的经验,直接上Weinre;加上caniuse.com也有明确的描述,所以很快解决了。


    各种难以定位、缺少文档、找不到解决方案的兼容性问题层出不穷,让我不禁感慨:本以为买上了WebKit的康庄大道,谁知尼玛这豆腐渣工程下面全是坑啊……

    滚屏

    前面提到,开始时间比较富裕,我也比较自信,想借助Hammer.js,慢慢打磨出色的滚动体验。后来无尽的bug环伺左右,实在无法专心研究,便退而求其次,使用iScroll了。

    iScroll如今已经发展到5,其作者自信的表示:“我尽全力提升了其在Android平台上的表现,我相信现在它已经达到了极致。”实验效果确实很理想,无论是高配置的小米,还是老旧的HTC G10,都表现出色。将来有空的时候得好好研究下他是怎么做的。

    工程实践

    随着时代发展,Web开发这种脚本语言也开始分裂成开发、部署两个阶段,这是个好事情。核心代码需要为维护、测试留下足够的空间,势必在性能表现上有所不足,部署时根据应用场景的不同输出不同的结果正好能解决这个问题。这个项目中我使用Grunt,花费了不少精力在编写Gruntfile.js上。

    开发时,我将测试用的数据,按照设计好的数据结构写在define.js中,生产环境中用服务器生成真实数据来替代。不同的应用场景,也用其配置。如今,目标支持的6个场景都能稳定工作,效果不错。

    Windows下不好配ruby,使用SASS是个问题,不过后来找到了Prepros,问题迎刃而解。

    技术细节分享

    简单构造选择器

    jQuery功能确实强大,不过实在太重型了;备胎Zepto轻便一些,不过功能也弱一些,语法做不到全兼容,用起来不舒服。仔细考虑这个项目的需求,无非是:找到某个元素,给它添加事件侦听;或者判断它是否包含某个class,那么自己写一个直接到Dom节点的选择器就足够了。后来看到篇文章,里面详细测试了querySelectorgetElementById众的性能,发现还是后者大大领先——尤其在移动平台上,于是基本放弃了用前者的念头。

    这个选择器实现起来很容易:

        var $ = window.$ = function (selector, root) {
          root = root || document;
          var dom;
          switch (selector.charAt(0)) {
            case '#':
              selector = selector.substr(1);
              dom = document.getElementById(selector);
              break;
    
            case '.':
              selector = selector.substr(1);
              dom = root.getElementsByClassName(selector)[0];
              break;
    
            default :
              dom = root.getElementsByTagName(selector)[0];
              break;
          }
          return dom;
        };
    

    另外,$还可以用来当命名空间,保存一些需要全局使用的变量和方法。

    使用Handlebars.js生成预编译模板

    预编译模版可以降低运行时的运算量,对于移动开发这种能省则省的应用场景,实在是必备之物。另一方面,为了方便开发,把模版放在HTML里更好调整。好在有“编译输出”这个步骤,可以两全其美。

    Handlebars.js的预编译是把模版转换成根据数据生成HTML的JS代码,可以使用uglify压缩。编译之前最好过滤掉多余的空格和换行符,这样生成的JS文件也会小很多。

    当然,再预编译也不如直接渲染HTML来的快,所以在服务器端生成页面的时候,一定要包含实际内容,让用户尽快得到信息。而PHP没有现成的Handlebars实现(我也不打算写),好在有mustache.php,并且handlebars兼容mustache(其实不完全,也被坑了),所以我写了一小段代码将Handlebars模版转换成Mustache模版

    使用attr()实现CSS中的多语言

    除了传统的中文版,这次还要制作英文版。英文版与中文版的功能几乎完全一致,只是文字全部换成英文。最简单的做法就是搞个en.html——因为使用了模版,JS里不包含任何文字内容;但是“上拉加载更多”这个功能目前使用CSS的:after实现——初衷是不修改dom,不引起relayout——当然,反正我用着Sass,增加个语言包不难,不过我还是希望把修改集中在一个地方。

    后来查了下CSS表达式的兼容性,发现Android 2.1起就支持,那就好办了,先把各阶段文案放到HTML的属性里:

        // 中文
        <div id="list" data-normal="继续上拉,加载更多广告" data-more="松开加载" data-loading="加载中..." data-no-more="没有其它广告了,再看看吧" data-error="加载失败,请稍候重试" data-over=""></div>
        // 英文
        <div id="list" data-normal="Pull up to load" data-more="Release to load" data-loading="Loading..." data-no-more="No more ads" data-error="Load failed" data-over=""></div>
    

    然后在CSS里使用attr()访问即可

        #list.over
          &:after
            content: attr(data-over)
    
        #list.more
          &:after
            content: attr(data-more)
    
        #list.loading
          &:after
            content: attr(data-loading)
    
        #list.no-more
          &:after
            content: attr(data-no-more)
    
        #list.error
          &:after
            content: attr(data-error)
    

    WebKit中z-indextranslateZ(0)混用,导致click事件半失灵的解决

    1. 要在浏览器里触发下载,只要链接到一个浏览器打不开的文件就行。链接跳转是在click触发的浏览器内建行为,换言之,链接跳转依赖于click的正常触发。
    2. clicktouch event晚200~300ms,所以为了保证用户体验,我们通常用taptouchstart + touchend)来替代click响应用户操作
    3. 所以一个“点击下载按钮”的操作,就会先响应tap事件,弹出详情浮层,告知用户获得积分的最终条件,然后等待浏览器开始下载
    4. CSS动画最好使用translateZ(0)促使浏览器启用GPU加速
    5. 这个bug在以上条件下产生,影响这个过程

    之前我在PC版Chrome里也曾见识过这个bug(最新版没验证,大概20+的时候吧)。具体表现为,一个层,它的translateZ有赋值,它可能被移到某处(通过translateXtranslateY,或者动画过程中),不过还未到达那里,但是它仍然会拦截那里mousedown/mouseup事件,导致click事件无法正常触发。

    这个bug非常难排查,因为它并不影响tap;视觉上看不到那个层,会想当然的认为不应该跟那个层有关系;并且,WTF,如果按得时间稍长,比如半秒,就可以触发click……因为触发条件比较复杂,我甚至很长时间没找到规律。最后只好请出“小黄鸭调试法”,试图向索隆解释发生了什么,然后发现无法正常下载的按钮都是被详情浮层遮盖着,继而回忆起以前解决过类似问题。

    最后解决这个问题的方案我其实不很满意,不过确实能顺利工作:

    1. 层的当前位置不会覆盖按钮,先不给层增加动画
    2. tap后,给click留出足够的时间(我这里用400ms),再给层添加动画
    3. 确保click可以响应,通过setTimeout给层添加动画——本来300ms就测试通过,不过为了保险,我还是决定写成400

    仍然悬而未决的问题

    虽说项目已经快上线了,但仍有几个不解之谜……

    1. 某些手机仍然无法下载(启动<a>
    2. 快速拖动的时候可能把内容拖走(应该是触发了浏览器默认行为)

    总结

    做完项目回头看,系统分裂造成的兼容性问题比想象中多很多,原先以为最多是有些2.x版本不支持,做向下兼容就好,谁知还有各种实现细节问题。不过,在PC Chrome + 手机Chrome + Weinrealert之后,只要有耐心,大多数问题时可以解决的。

    说完开发效率,另一方面再来看看产品表现。我们对性能不需要太乐观,也不用太悲观,敏感型应用,老老实实用原生开发,或编译成原生应用;非敏感型,跨平台高兼容性(咦,这眼泪哪里来的)的优势很明显。通过我实际看到移动端的表现,我们这些页面仔应该更有信心,将来移动大市场里,Web App的份额绝对不比Native App要少。

    这项事业任重而道远,需要我们一起努力。

  • 本地部署weinre帮助移动开发

    本地部署weinre帮助移动开发

    weinre是个开源项目,用来在Web开发中做远程调试的工作,相当给力。后来也捐给了Apache基金会,并且从ruby移植到了JavaScript,现在可以直接通过npm安装。在最近的广告墙大重构中,这个工具帮了我很大的忙。

    安装weinre首先要装node.js,后者在Windows和Mac上直接下包就能装,并且自动配置环境,很方便;在Linux上需要自己编译一次,也不复杂,我之前有篇日志写了,现在还好使,可以看看。

    node.js环境搞定后,直接用npm就可以安装weinre了,直接装在全局中最好:

    sudo npm install weinre -g

    接下来启动weinre,我在这里卡了一阵,大概因为不太了解端口侦听的缘故,我以为直接启动就好,结果从外面连不上(手机连不上连点反应都没有,很难排查),因为侦听的是localhost的ip,也就是127.0.0.1。后来尝试绑定内网ip,才算解决问题:

    # 8081是想找一个不常用的端口,后面的ip是我在内网的ip
    weinre --httpPort 8081 --boundHost 192.168.10.54

    这次无论是本机还是手机都可以正常访问了。然后Mac这里可能还会遇到点小问题。虽然我关闭了防火墙,但是Unix自带的ipfw还在工作,会阻止从外面过来的访问(不知道为啥80没问题),所以要给8081端口专门的许可:

    sudo ipfw add 8081 allow from any to any

    之后就万事大吉了,在HTML里添加 <script src="http://192.168.10.54:8081/target/target-script-min.js#meathill"></script> (域名根据具体情况修改),然后打开 http://192.168.10.54:8081/client/#meathill 就可以查看了,如果有多台终端在调试的话,还可以点选目标机器。

    好了,享受下weinre带来的方便快捷的移动端调试吧。(不自己截图了,借用了weinre官网上的图片)

    weinre使用截图
    weinre使用截图
  • 也谈提升代码可读性的小技巧

    看完了《编写可维护的JavaScript》,觉得他技巧性的东西讲得太少,还不如看《重构》来得实在。这里分享两个我常用的优化代码的技巧,好学常用。范例代码用的是JavaScript,其实几乎所有语言都用的到。

    逻辑扁平化

    我们工作中,可能会碰到一些比较复杂的逻辑,比如:满足条件1,不然XX;满足条件2,不然XX……如此都满足了才OO,写出来大概是这样:

    if (condition1) {
      if (condition2) {
        if (condition3) {
          // do
          // a
          // lot
        } else {
          // do zz
        }
      } else {
        // do oo
      }
    } else {
      // do xx
    }

    这样带来的结果就是逻辑嵌套后显得很复杂,不好读。如果再赶上缩进没搞好或者换行就更惨了。这个时候,通常可以提前中断代码执行来使得逻辑扁平化。提前中断的方法包括returnthrow,以及PHP中的die()exit()。重新整理后的代码如下:

    if (!condition1) {
      // do xx
      return;
    }
    
    if (!condition2) {
      // do oo
      return;
    }
    
    if (!condition3) {
      // do zz
      return;
    }
    
    // do
    // a
    // lot

    当然,有例外情况要排除的地方也很适用这种方法。甚至有激进的言论,表示不应该使用else,而都要这样提前中断。我觉得那样也有点过分,if ... else ... 的意义还是非常明确的。

    缩短超长逻辑判断

    有个从上古时期传下的不成文的规定,一行代码80个字符。随着显示器变大,这个老规矩应该打破。但是单行代码太长的话左右看起来也会很累,所以我们还是有必要控制。超长的代码经常出现在逻辑判断里,比如游戏中常见的检测子弹有没有打中玩家的代码,用较好的格式写出来大概是这样:

    if (bullet.x > enemy.x + enemy.width || bullet.y > enemy.y + enemy.height || bullet.x + bullet.width < enemy.x || bullet.y + bullet.width < enemy.y) {
      console.log('没打中');
    }

    这就已经不短了。有些时候我们还要加上enemy.damagable、!enemy.immunePhysical之类的其它条件判断,把这一行写的特别长。这个时候就需要我们用点小技巧将它缩短了。方法可能有多种,思路是一致的,就是提炼“目的”——将某一组判断的目的提炼成一个变量,既好读又能缩短代码。接下来还是用一段代码来说明吧:

    var isGod = !enemy.damagable || eneymy.immunePhysical, // 判断敌人是否不能攻击/上帝模式
        isHit = bullet.hitTest(eneymy); // 将刚才那一大串抽出方法“碰撞检测”
    if (!isGod && isHit) {
      enemy.die();
    }

    总结

    这两个小技巧都很简单,但对代码的可读性提高却很显著。各种各种的小技巧叠加起来,会让代码更好读,更容易维护。

  • 书评:《编写可维护的JavaScript》

    书评:《编写可维护的JavaScript》

    编写可维护的JavaScript
    书的封面

    评价:

    3星。如果手头宽裕,或者公司给买就买本看看。如果觉得自己水平欠佳,也推荐学习下。不然就算了。

    其中内容3.5,编辑2.5,综合起来就是3星了。

    适合人群:

    • 缺少规范编程训练的开发者,比如学生
    • 对整个开发环境缺乏了解的新人

    读后感

    很早就关注这本书,当时还只有英文版,前几日看到中文版上线,又是淘宝几个人译的,就赶紧买了,结果看后大失所望。

    最让我失望的是书里印刷错误很多,甚至连!=都能印成!==(第50页),估计道行浅的看到这儿就迷茫了。全书我看得比较细的是前面8章,几乎每章都有错误,实在让人如鲠在喉。

    整书的内容偏浅,讨论的多是差不多的几种方案选择其一,比如空格缩进、4空格2空格、注释要不要有空行之类。其实这种东西绝大多数语言都会涉及到,也没什么好坏之分,团队领导随便选一个就是了。真正对代码质量有较大帮助的是命名空间(第6章)、UI层解耦(第5章)、事件和逻辑接偶(第7章)。读完这几章本以为书会渐入佳境,结果后面都是很浅显的否定浏览器推断的做法,以及介绍使用Ant进行编译,就没什么意思了。

    在我看来,有些内容是不用多说的,比如用浏览器推断某种方法是否存在,提一嘴就行了;有些内容则是初段码农和高阶程序员的分水岭,这才值得大书特书。比如,复杂逻辑的扁平化处理、超长条件分拣成更易读的名字、模式的应用、大型项目的架构、消息机制,等等。可惜这些书里都没有提。至于Ant的介绍,至少我这种半路出家的非Java开发者并不熟悉,倒是直接node.js+grunt用起来挺顺手,而且每家公司的工具使用状况也不一样,是否直接写好几章真的有待商榷。

    我认为,如果自我感觉缺乏系统可靠的编程培训,可以看看这本书。如果想真正提高自己的代码质量,这本书价值不高。

  • 前端框架点评

    前两天被人@,提问前端框架的问题。这还是第一次在微博上被陌生人提问,感觉有点小激动。今天写篇小文章,简单点评一下我用过的前端框架,希望对大家有所帮助。

    本文涉及的范围

    本文要点评的前端框架,都是我用过的,以JavaScript框架为主,也可能包括一些工具类库。由于前端的特殊性,弱语言HTML和CSS是必不可少的工具,运行机制导致很多成熟的工程学方法没法用,必须用工具库补充。烦请大家不要抠字眼,我一向很马虎,呵呵。

    Bootstrap

    一定要先推荐Bootstrap。它由Twitter的两位工程师首创,当前版本2.3.1,涵盖了文本、布局、表单、工具,除了暂时没有日期选择器之外,几乎包含了网页所需的一切。我们知道,网页出现是为了传递信息,而成熟的印刷业对其影响很大,表现出来就是,HTML中很多标签,比如H1~H6p等,都是有具体语义的。但很多前端框架并未给予他们足够的重视,所以往往需要我们重置或者自己动手写。Bootstrap则不是这样。它不仅包含大量组件,还帮助我们给每种可能用到的语义标签都定义了样式,又设计出很多helper;而其网格系统也能帮我们出色完成排版工作。总而言之,使用它几乎足以满足任何网页开发需求。

    值得一提的是,Bootstrap的组件和Widget都基于HTML标签构建,非常好用;而响应用户操作则基于对document的侦听,所以几乎不会对我们其它代码逻辑造成影响,我们可以放心大胆的在项目当中使用。当然,Bootstrap依赖jQuery,后者近期遭遇到一些纷争。不过我还是要强烈推荐这个框架。

    jQuery UI

    比较遗憾的是,虽然号称UI,但jQuery UI只能算作一个组件库。因为缺少基础排版支持,所以既想使用jQuery UI,又想和自己的样式保持一致会有些困难,需要花费不少时间做修改。

    抛开这一点,jQuery UI也是个很好的工具,几个widget都很实用,文档丰富详实,如果项目本身对交互要求不高,只是个别地方需要一些功能补充,jQuery UI确实是不错的选择。尤其当我们需要draggablesortableresizable这几个功能时,选择的余地真的不大。

    HTML5 Boilerplate

    这个工具力图给开发者提供一个舒适的起点。他们为项目建立了标准化的目录结构,并且把常用的资源都整理在合适的位置,这样我们只需要替换它们就能保证项目对所有浏览器都有完美表现。同时,他们重置了CSS,引入了GA统计,在页面中标记了合适的输入点,这样我们能尽快投入到项目逻辑的开发中。

    但这个工具的问题也很明显,它努力达成的目标,是消弭浏览期差异。假如我们只需要统一的环境就能开展工作,那H5B就足够了;而实际上,我们通常需要更多,比如排版、比如组件。所以,这个工具对我来说,学习的意义大于使用的意义。

    HTML KickStart

    我见过的前端框架里只有这个跟Bootstrap的覆盖面接近。它的设计更好,更鲜艳更有活力,不过最近的开发好像慢了下来。我并没有真正使用它,因为它的组件太少了。

    Backbone

    非常好的MV*框架,真的像根脊柱一样,把被jQuery拆的七零八散的js统一在合理的框架内,让整个项目都变得协调了。引入Collection真的很天才,结合其出色的View设计——其实Backbone的View并不是传统意义上的View,更像mediator——可以看出,Backbone的设计者对Web开发有着深刻理解。哦,差点忘了,Router还能帮我们很好的整理单页应用的逻辑。

    不过js毕竟是一门很烂的语言——或者让我换种表述方式,缺陷很多,所以它尚无法做到像AS3的Robotlegs那样好。不过,如果我们希望把代码组织的更好,Backbone类的框架是不可或缺的。

    Rachet

    Rachet的目的和架构,很像Bootstrap,不过它瞄准在移动端。Rachet现在仍处于比较低级的阶段,组件太少,开发起来不太方便,最多相当于Kickstart把。可惜的是,移动端移动框架普遍较弱,在我看来,基于它的Junior仍然是不二之选。

    Zepto

    说到移动端开发就不能不提Zepto。它力图实现一个十分类似jQuery的库,不过只考虑支持移动端,所以体积会小很多,速度也会快不少。Zepto的升级频率最近也不高,不过还是到了1.0。新版Zepto放弃了ruby,使用coffee作为编译工具,支持使用者自由组合功能模块,相当贴心。

    不过具体使用时,还是会有一些函数的用法和jQuery不同,需要注意。举个例子,css函数,jQuery里支持数字,比如$('div').css('height', 100),会把页面中所有div的高度设置为100px;而Zepto不行,必须$('div').css('height', '100px')才会正常工作。

    jQuery Mobile

    强大的jQuery团队最失败的作品。贪恋于风光的过去,迷信“全覆盖”,使得jQuery Mobile步履蹒跚,在移动平台上无法取得足够好的性能表现(早期版本甚至不支持固定底部)。而这直接带来了对HTML在移动端的质疑。有很多本不擅长前端开发的人,看到Phonegap,就直接上jQuery Mobile,结果发现性能不行,体验很差,便在各种渠道大放厥词,说HTML不行。这种行为直接伤害了HTML和Hybrid应用。

    jQuery Mobile失败不仅在于此。jQuery本身方便快捷,使用它很容易养成不重视代码组织的习惯;而在移动端,单页应用占据主流,这意味着,使用jQuery Mobile很可能无法良好的组织代码,直接带来项目维护成本的提高。另外,早期版本的设计也不可避免的带有大量Web痕迹,导致实用性不足。所以直到今天,jQuery Mobile都还是个悲剧。

    不过,jQuery 2.0已经beta2了,我们知道,jQuery 2.0里放弃了对IE678的支持,性能会得到不小的提升;jQuery Mobile 1.3已经引入了对hash的支持,组织代码好多了;同时,随着3次小版本更新,越来越多适用于移动应用的组件加入进来,大大扩充了军备库。我认为,未来的jQuery Mobile,值得一试。

    Sencha Touch

    大家可能还记得,Facebook闹过一阵,说HTML5不够好,还把他们的手机客户端用原生重做了一遍。之后Sencha的两位工程师表示不服,以HTML5为基础开发了功能几乎完全一样的客户端,他们用到的框架就是Sencha Touch。据说,它的性能比jQuery Mobile好不少。

    但是,真正用了就知道,sencha touch实在太不“前端”了。它里面几乎没有给HTML和CSS留下太多位置,明明十分强力的布局样式工具,被封装成了单薄的接口;但是没少往JS框架里加内容。结果就是用起来非常复杂,随时需要看文档;自定义困难,它里面甚至有一个属性叫“html”,用来填入大段文本类型的html;数级嵌套起来的对象也很让人头疼,而且感觉维护也不会太容易。所以我做了一半就还是放弃了。劝大家也别去尝试了。

    如果有哪位高人愿意以实际经验教育我的,我洗耳恭听。

  • 适用于垂直媒体官方微信的智能答复系统设想

    微信开放了公众平台的接口,很多网站和个人都接入了,包括我的老东家。不过大家好像都没有太大热情,也没投入多少技术实力,只把它当成微博外的另一个信息发布平台,每天定点发个手机报什么的。

    我前一段读了《数学之美》,还看了《黑客与画家》,很想尝试下里面介绍的机器学习法。看到微信公众平台,觉得在合适不过,于是难免要YY一下:

    1. 首先是“分词”。好在已经有很多开源免费的分词工具,能够提供比较好的分词结果。对于垂直网站来说,还需要能人工添加新词,或修正分词结果。
    2. 收集语料。把用户发给官方帐号的消息收集起来,留待分析。
    3. 分析语料。找垂直媒体做实验的好处就在这儿。通用平台的用户诉求各种各样,什么都有,做语义分析会很难(参见各种半残废的“语音助手”);垂直媒体的用户相对来说诉求简单些,无非是“查询”、“问价”、“求推荐”等。所以可以先给消息定性,然后统计在每种类型的消息中每个词语出现的概率。
    4. 按照贝叶斯推断,当我们收到用户发来的消息时,可以根据消息中的词来判定他的诉求。
    5. 系统实战:
      1. 用户发来一条消息
      2. 系统对消息进行分词
      3. 按照分词结果,判断用户诉求
      4. 按照设定好的策略,给出答复

    比如,用户发了一条“iPhone5水货多少钱”,那么会经历如下的过程:

    1. 分词结果为“iPhone5”,“水货”,“多少钱”
    2. 按照统计结果,出现“多少钱”的话,用户“问价”的概率是100%(实际肯定不是,这里当例子),所以系统判断这则消息是用户在问价
    3. 按照制定好的“问价答复”策略,我们找到产品名称“iPhone5”,限制“水货”,然后从经销商那里取出价格数据
    4. 按照“问价答复”模版,回填数据,发给用户,完成。

    又比如,用户发了一条“什么电信手机比较好啊,真心实意求推荐”,那么:

    1. 分词:“什么”,“电信”,“手机”,“比较”,“好”,“啊”,“真心实意”,“求”,“推荐”
    2. 按照统计结果,出现“比较”,“好”,“推荐”这些词的消息,“推荐”的可能性是90%,系统即可确认
    3. 按照制定好的“推荐答复”策略,找到限制因素,“电信”,“手机“,从产品库中查出来
    4. 按照“推荐答复”摸板,回填数据,发送,完成。

    似乎不复杂,对吧,哈哈?实际上应该会有一些技术门槛,不过大体上应该这样,有机会拿到数据的话,我就动手搞搞看。

  • 开始付费使用Adobe Creative Cloud

    几天前,我终于下定决心成为了Adobe Creative Cloud的付费用户,当然,感谢老板帮我买单。成为付费用户可以享受以下服务:

    1. 全套正版的Adobe软件
    2. 20G云存储
    3. 5个网站,用来发布作品
    4. 免费版的服务可以这里

    我很早就开始关注这个东西,一个月50刀的使用费其实不算贵,毕竟有全套Adobe软件可用,而且还附送那么多其它服务。不过还是纠结了一段时间,跟老板商量了商量,才终于下手。

    以后终于可以用上正版的Adobe软件了!

    PS:感谢Adobe!我的今天,有赖于我在过去10年中使用的盗版软件,其中很大一部分来自Adobe。感谢他们提供这种机会让我有机会对他们付出一些回报。(当然按照老罗的逻辑,这不叫感恩……)

  • 诡异BUG之:IE下表单必须提交两次

    做前端遇鬼是常事儿,比如今天,就遇到个:

    1. IE6~10
    2. jQuery 1.8.3
    3. Backbone 0.9.2
    4. HTML5头
    5. 一个Backbone.View,内部有一个formView托管formsubmit事件,根据classaction进行不同的操作,或者验证数据提交表单
    6. 第一次提交,验证通过,return true,没反应
    7. 第二次提交,验证通过,return true,表单提交
    8. 其它浏览器表现正常

    提炼这些Bug描述就花了不少时间,反复Google也没有什么结果。比如,StackOverflow上类似问题的解答是:submit原本不会冒泡,所以应该直接侦听form;但实际上jQuery1.4之后已经支持submit事件的托管了,我的实验也支持这个结果。

    至于导致问题的原因,我还没想明白也没测出来,留着以后再做吧。最后用Hack的方式暂时解决:

    if ($.browser.msie && isIEFirstSubmit) {
      isIEFirstSubmit = false;
      setTimeout(function () {
        form.submit();
      }, 50);
    }

    有了解这个的高人还请不吝赐教。有感兴趣的同学可以去试试:http://www.dianjoy.com/dev/#/user/updateuser

  • 一个诡异问题的排查

    这个问题本身其实不能称之为问题,记下来只是我觉得排查经历挺典型的,值得分享。

    先说下我的环境:MacOS 10.8.2 + Chrome 23.x + Bootstrap 2.2.1 + jQuery 1.8.2。事情是这样的,我本来在开发一个新功能,顺便把老页面重构一下,貌似一切正常。重构完毕后,我想随手测试一下重构后的页面,发现下拉菜单竟然不能点击了,或者说,点击完全失效了。

    这让我很恐慌,因为类似的重构过程我近段时间来做的不是一次两次,这次我应该没有任何冒险的改动才是。这种情况下出现的Bug,一般都很棘手。于是,我打开以前的某个页面——同样经历了重构,但是通过了测试,并且在一顿时间的使用中完全没有问题——发现里面的下拉菜单同样点击失效了。于是,真正的排查开始了。

    (更多…)

  • MySQL 同表复制数据

    MySQL 同表复制数据

    我觉得再这么下去,我真敢说我写过PHP了……

    需求很简单,在同一个表中复制数据。以前的代码是在PHP里先 select *,然后 extract 成变量,再组合成一个大 sql,最后插入。我觉得这样不好,首先要执行两次 sql,其次写那么一大篇 sql 也挺麻烦的。于是研究了下,发现并不复杂,这里总结一下:

    如果是从别的表里导入数据,可以这样写:

    INSERT INTO `table`
    SELECT *
    FROM `table2`
    WHERE `id`=1
    

    如果是同表,并且表里没有主键,这样也好使;但是有主键的话,会被告知主键重复,这个时候就只能把字段都写出来了:

    # 假设表结构为 id, col1, col2, col3
    INSERT INTO `table`
    SELECT NULL, col1, col2, col3
    FROM `table`
    WHERE `id`=1
    

    这里字段的顺序很重要,要参照表的顺序来写(我是用 MySQL Wordbench 连上库,然后用Alert table 看的);不过好处在于,如果我们需要更改其中某个字段的值,只要在 sql 里直接写就好。比如我们复制后想交换后两个字段,或者改变某个字段的值,就可以这样:

    # 假设表结构为 id, name, age, sex
    # 交换age和sex
    INSERT INTO `table`
    SELECT NULL, `name`, `sex`, `age`
    FROM `table`
    WHERE `id`=1
    
    # 更改某字段的值:将复制得到的name加上copy_前缀
    INSERT INTO `table`
    SELECT NULL, CONCAT('copy_', `name`), `age`, `sex`
    FROM `table`
    WHERE `id`=1
    

    id 这种自增的主键,直接插入 NULL 就可以了,MySQL 会自动帮我们补全(如前几段所示)。

    同时插入多条数据也很简单,这样即可:

    # 假设表结构为id, name, age, sex
    # 复制 id<10 的字段,加上copy_标识
    INSERT INTO `table`
    SELECT NULL, CONCAT('copy_', `name`), `age`, `sex`
    FROM `table`
    WHERE `id`<10
    

    实话实说我不太懂MySQL,不过这样写我觉得有几个好处:

    1. 省事儿,可以不再把变量一一抄上
    2. 可以一次复制多条数据
    3. 只执行一行sql,速度应该会更快