因为工作中一直用到RxJS,所以在知乎上关注了这个话题。

然后么,看到这篇文章Hello Rx。读了这位太郎写的代码,总感觉怪怪的,所以留了评论想讨论下。结果么,这位仁兄吵着说要我show code,真的show了code的结果就是举报我的评论不友善。。。我也只能呵呵了

实话实话,这位的代码应该稍微改改还是能跑出来的(恩,从评论看各种错误、漏写,估计他自己也没跑通过)。如果是个刚毕业的新人,写出这样的代码还是需要表扬的。但是呢,如果作为给别人演示代码的质量来衡量,还是差了十万八千里了。

作为比较,这里贴一个实现一样功能的代码:JS Bin,大家可以自己对比一下。

先从非常基础的命名开始吧:

const enter$ = Observable.fromEvent<KeyboardEvent>($input, 'keydown')
                        .filter(r => r.keyCode === 13)

const clickAdd$ = Observable.fromEvent<MouseEvent>($add, 'click')

const input$ = enter$.merge(clickAdd$)

const item$ = input$
     .map(() => $input.value)
     .filter(r => r !== '')
     .map(createTodoItem)
     .do((ele: HTMLLIElement) => {
        $list.appendChild(ele)
        $input.value = ''
     })
const toggle$ = item$.mergeMap(...).do(...)
const remove$ = item$.mergeMap(...).do(...)

这位太郎的代码中,把“clickAdd”和“enter”这两个事件的流合并成了一个流“input”,又把input经过map-filter-map-do变成了一个流item。

excuse me?什么叫clickAdd?指的是点击add按钮?
那为什么前面那个不叫pressEnter而直接叫enter,而这两个合并后的流直接变成名词input了? 最后的item就更confuse了,已经无法用语言来描述这个item代表的是什么了。

不要小看了代码命名,有一种说法:“看代码命名就能看出一个程序员的大致水平”,不是没有道理的。

一个屏幕上显示的代码行数有限(这就是为什么好多人把屏幕竖起来看代码),人脑的短期记忆也是有限的,如果能不看上下文,就能推测出当前这个变量的含义和作用是非常非常重要的。

相比另外一个例子里:var allOperations$ = Rx.Observable.merge(addOperations$, removeOperations$);,这位的这种随心所欲瞎命名,实在看不出有什么深厚的开发功力。


然后看看我和他主要争论的点,代码里面的item(命名命名命名,也不知道这个词在他脑海里到底代表什么):

他在这里做了什么呢?恩,他用这个流去做了映射,过滤空值,创建了todo里面的新项目,顺便do了下,把输入框清空了。仔细看看这坨代码,有什么问题么?。但这里有2个设计非常糟糕的地方。

1.事件流的污染

这里的item并不是事件流的终点,而只是非常上游的一环。在事件流上游做这种side effect的事情可是设计上的大忌。为什么?因为这意味着后续的所有人所有逻辑都必须清楚的记得在上游的这个节点做了这么个事情,必须做特别的操作来配合。

果然,他后续代码为了处理这个污染自认为很高级的用了publishReplay。事件的流就好比一条河,所有人都在河边上依据河里面的内容做自己的事情。他的这种做法就好比在河流的最上层洗马桶,结果整条河都被大便污染了,他给下游每个人装了个过滤器。“妈妈快看,我连过滤器都会用哦,我感觉自己好高级哦,可以写篇文章来装专家了“

而事实是如果不是因为item的糟糕设计,根本就没必要做这些动作。顺便一提,publishReplay的一个常用的地方是对api调用的cache,而不是把页面操作的事件流replay给别人看。

2.事件的流和处理的动作混淆

命名的混乱,事件流的上游污染,其实都说明了代码结构也就是开发者思维的混乱。

仔细看看这个item到底是个什么东西呢?看起来是用户的operation的流?no?又在上面做了过滤?又用map去创建了TodoItem?又用do把输入框置空了?后面的代码竟然又拿着这个四不像的流去继续mergeMap出toggle和remove流? 这还是做个最简单的todo的一部分小功能,如果做复杂的页面逻辑,甚至于只是把这个todo所有的逻辑做完,可想而知后面的操作很难开发下去。

小知识:为什么RxJS里面有map、do/tap、subscribe这些都能实现映射动作的function?

以我的理解:

  • map:对流的变形、映射
  • do/tap:日志等不影响流本身的动作
  • subscribe:流的订阅者,所有的action都在这里触发;流的终结者,本身不可能变成别人的流的源头了,所以可以做一些有side effect的事情

    actions.map(retrieveData).do(log).subscribe(success,error);
    

总结

  • 写出能跑的软件和写出好软件是开发的不同境界
  • 如果是要拿来展示的代码,写完请自己读一下,不要把灾难级的小学生作文发表出来
  • 别人和你讨论你的代码,其实是对你有好处的(已删除那篇文章下的所有回答,没必要把知识留给不愿意学习的人)
  • 这哥们是山寨Trello的Teambition的前端工程师,呵呵