YUI 3 在淘宝彩票中的实践小结
作者: 发布于:

导语:春风吹战鼓擂,YUI 3 早就扛起了高端的“前端团队开发”的大旗,昂首阔步的朝我们走来,不管是 Yahoo 对 YUI 3 的实践,还是 D2 上克军对 YUI 3 分享带来的诱惑,无不让人感觉 YUI 3 带给人的感官冲击,如今,淘宝电子杂志网络文学彩票等产品已经在使用 YUI 3,今天,让我们来对 YUI  3 在淘宝彩票项目中的一些实践做一些简介,希望给各位同仁带来一些参考和帮助。

天然优良的框架

淘宝彩票是一个包含诸多彩种的产品系列,各彩种之间有相当多的可通用部分,各种数字彩的玩法极为类似,此外,同一个彩种也包含不同的玩法,这些玩法则更加近似。比如这里是双色球、大乐透、时时彩的三星玩法和单双玩法的 UI:

可以看到,他们选球的步骤、投注增删改的操作、号码的校验、附加行为(追号、跟单),以及拆单和下单的逻辑,几乎完全一致,他们之间的区别仅仅是,选球的组合形式不同、胆拖的特例、号码位数不同、每位号码个数不同等等,此外玩法的投注原始算法是永远一致的,这样就可以将通用逻辑进行抽离,首先针对数字彩的大类做一个算法库,再者对选球、投注操作、追号跟单等通用的部分做功能的封装,形成功能库,最后最终页面的实现只是将这些零散的功能片段进行组装,并着力实现玩法的各个接口。显然,YUI 3 是非常适合这种级别的功能分割和模块封装的,比如,算法库,功能逻辑片段和玩法实现之间的层次关系为:

处理模块层次关系正是 YUI 的强项,代码示意:

YUI.addmojo({
  'ssc':{
    path:'ssc.js',
    requires:['c-framework']
  },
  'c-framework':{
    path:'c-framework.js',
    requires:['c-tools']
  },
  'c-tools':{
    path:'c-tools.js'
  }
});

在页面中引入 ssc(时时彩)的 module-name 即可:

YUI.use('ssc')

此外,各种玩法和功能区之间通过统一的接口连接起来。因为 YUI 3 的模块依赖的机制可以方便的实现高层的 module 覆盖低层的 module,这样就模拟了“重载”,以及“多态”。比如,每个玩法都是用单独的 module-name,每个玩法的接口格式均保持统一:

YUI.add(‘module-name’,function(Y){
  Abacus[‘module-name’] = {
    verify:function(){
      // 本玩法的选号球的验证
    },
    getNakedBets:function(){
      // 本玩法获得原始投注数据
    },
    getWrappedBets:function(){
      // 本玩法获得封装后的投注
    }
  };
});

将此玩法根据需要应用到页面中去:

YUI.use('module-name');

这样,在运行时就可以根据 key(module-name)得到每个玩法对象,由投注操作(C.Basket)、辅助操作区(C.Donkeyman)去调用这些即时对象的接口来完成整个投注流程,而 C.Basket 和 C.Donkeyman 这些逻辑根本不需要知道他调用了谁,只需要知道他调用了“某一类东西中的一个”的方法即可。这样的话,YUI 3 即为这种级别的功能分离提供了一个天然优良的架构,在后续开发大量功能类似、细节有差异的各个彩种的时候,只要重用度做的合理,二次开发是非常快速,也很方便维护。

最原始的解耦思想

在我学习使用 YUI 的过程中,体会最深的并不是 YUI 的实现细节是如何精妙,而是框架作者埋藏在源码字里行间最原生最裸露的设计模式精髓——接口。模块通讯依赖于接口,串接各个逻辑使用接口,多态的实现更要靠接口。在 YUI 里,从 Node 的封装到 Base 的构造,万变的架构始终没能离开这最原始的解耦方式,当架构的重心从底层转移到应用层的时候,依然能强烈的感觉到接口在功能解耦方面的威力,比如,在时时彩的页面中共有 10 中选号玩法,

每种玩法都具有相同的接口,在运行时,功能区域(比如 C.Basket 投注操作)对玩法的调用始终通过接口,而根本不用在乎调用的是谁。因为每种玩法的接口都是一样的:

YUI 3 不止是框架

有人说 YUI 3 粒度太高,淘宝又没有 combo,高粒度和高性能之间难道是鱼和熊掌吗?非也,动态合并代码是一种思路,和框架无关,和 CDN 无关,任何人理解动态合并代码的原理手写一个新的框架也不是麻烦事,还有人说 YUI 不能合并非 YUI 的代码,其实只要找对地方稍微 hack 下 yui-loader 即可,这些都不是重点,重点在于 YUI 如何适应开发、测试、发布和 bugfix 的整个过程,环境太多,调试太麻烦,环境太少,很多 bug 测不出来,工程师和 QA 似乎永远是一对矛盾,当然我们可以将线上环境原封不动拷贝到本地,做一个镜像,完全基于本地开发,这成本实在太高,万一一个项目开发时间很长,线上环境更新了好几个版本,本地镜像还是旧的,这就麻烦了,即使有 svn,找 log,对代码,也够让人头疼的。其实在 Apache 写几个 rewrite 规则就可以做到实时镜像可以参照这里),不管是合并代码,还是调试源码都很方便,不用看着一大堆的压缩代码思前想后的想切换到哪个环境?

时时彩的项目中,page.js 既是 combo 后转存下来的文件,当然,这里无论如何还是要做一下“另存为”的操作,不失为一种权宜之计。

因此,YUI 3 作为 Yahoo 前端技术的一小部分,他所带来的开发思路的灵感要远远超过 YUI 3 本身。

规范的力量

得益于 YUI 3 的解耦能力,页面功能亦可做进一步划分,纯粹的 widget 式的交互和功能性逻辑,比如彩票中的选号方法就是功能性逻辑,而页面中比较边缘的不太起眼的交互则是 widget 式的交互,这类 widget 式的交互在业务逻辑层面并无语义,仅仅是为了交互而交互,这些快捷交互的代码就可以直接在页面中拼装好,完全不用将这些在业务层无语义的代码参杂进业务逻辑的代码中,比如:

这里包含简单分页、tabview、展开折叠、下拉菜单等,这些快捷交互在页面中的实现大致如下:

TBloader.require('slide').onReady(function(Y){
  new Y.Slide('J_donkeyman_subtab',{
    eventype:'click'
  });
});

而诸如 slide、simple-page 和 collapse 这些逻辑,则更适合做成 widget,这样到处都可以使用了。这里则不得不提到 Y.Base 和 Y.Widget,为开发者开发组件搭建了一套现成的模板,其实,由此可以窥见 YUI 对规范的理解,规范的精髓是统一,效率和合理性是次要的,不管是简单的分页,还是复杂点的 tabview,但对于简单的 widget,Y.Base 和 Y.Widget 似乎有些浪费,但在规范至上的理念中,这又有什么所谓呢,毕竟统一规格可以使得开发者花更少的精力做出更健壮更具重用价值的组件,这也是 YUI 在复杂度和规范之间做出的取舍,在越复杂的项目中,这种取舍的价值越能体现出来。

迷惘在快餐式的前端开发中

这里不得不提及我们越来越没有技术含量的前端工作,相比于传统软件工程师,前端工程师似乎更习惯于堆代码,表面看起来,前端开发用不着算法,也很少用设计模式,做东西没有概设和详设,甚至于没画过流程图,因为前端太“所见即所得”了,不容易看到 view 背后的 control 和 data,外加上千变万化的需求变更,前端工程师似乎总是在忙着响应五花八门的需求、堆代码实现功能,最后的作品不是用心设计出来的,而是拼凑出来的,即便实现了功能性需求,也很难做到品质的内秀,久而久之,导致一个系统的复杂度完全超过任何人掌控的范围,最终成为二次开发中的最大障碍,那么,你是否也迷失在这快餐式的前端开发中呢?当你再次翻开那本破旧的“数据结构”的时候,是否会有一丝没落?

如果你还在迷惘其中,我只想建议你多读一读 YUI 的源码,或许你会回想起一些关于算法、设计模式、工程化、标准化所带给你最本初的编程美的感受。

YUI 3 的缺憾

无独有偶,YUI 3 太过庞大的身躯带来更便捷的开发,也带来一些显而易见的缺憾,比如 DOM 操作性能不高,学习成本太高,入门相对有难度,更不用说深入理解和熟练应用了,这似乎是 YUI 最致命的缺憾,相比其他框架,他的确太复杂了,当然,这和 YUI 所定位的使用人群有关,不过,这又有什么所谓呢,我们不能老是停留在入门级的水平,不是吗?

题图:https://unsplash.com/photos/mR57OYco4MY By @lydia harper