反馈拯救世界

心流 你可能有过这样的体验:在玩一个很有趣的游戏时,时间会飞快的流逝,等你终于通关之后才发现已经是凌晨,而你的午饭和晚饭还都没吃。虽然可能饿着肚子,但是你内心却有一种很兴奋,很神清气爽的感觉。而当你在做一些不得不完成的工作/作业时(比如写年终总结报告),时间又会过得很慢,你的心情也常常变得焦虑或者暴躁。 通常来说,人的心情总是会在各种情绪中起伏不定,不过毋庸置疑,我们都希望永远或者至少是尽可能多的保持第一个场景中的状态。 这种精神高度集中,通过自己的努力不断完成挑战,并常常会有忘记时间流逝,甚至忘记自身存在,只剩下“做事情”本身的状态,在心理学上被称之为心流(Flow)。人们虽然很早就发现了这种现象,但是直到1975年,心理学家米哈里·齐克森米哈里(Mihaly Csikszentmihalyi)才将其概念化并通过科学的方式来研究。 心流(英语:Flow),也有别名以化境(Zone)表示,亦有人翻译为神驰状态,定义是一种将个人精神力完全投注在某种活动上的感觉;心流产生时同时会有高度的兴奋及充实感。 进入心流之后会有很多特征: 愉悦 全身心投入 忘我,与做的事情融为一体 主观的时间感改变 心流被普遍认为是一种绝佳的精神体验。根据齐克森米哈里的理论,与心流对应的,还有一些其他的心理状态: 当自身能力很高,但是做的事情很简单的话,你做起来会比较无聊;而当能力不足,要做的事情很困难的话,你又会陷入焦虑。有意思的是,如果你技能不足,而做的事情又比较简单的话,并不会产生“心流”体验。恰恰相反,这种状态(apathy)是很消极的,做事情的过程中,你既没有运用任何技能,也并没有接受到任何的挑战。 如何进入心流状态 齐克森米哈里要进入心流状态,需要满足至少三点: 有清晰的目标 有明确且事实的反馈 能力和挑战的平衡(都处于比较高的状态) 比如,玩游戏的时候,目标是明确的,不论是简单的通过策略消灭对方,还是将三个同一颜色的宝石移动到同一行);反馈也是实时的,同色宝石连在一起是发出的声音以及屏幕上闪过的炫目的光芒,敌人在被你手中武器杀死时的惨叫,你自己的血槽等等;最后,游戏不能过于简单,如果太简单,你很快会觉得无聊,又不能太难,这样你会觉得挑战太大。 不过要在工作上进入心流状态,远远比玩游戏要复杂。比如不明确的目标,冗长的反馈周期,能力与挑战的不均衡等等。 基于反馈的开发 2014年底,我在ThoughtWorks组织3周3页面工作坊的时候,发现了一个很有意思的现象:通常公司内部的培训/工作坊都会出现这种现象:报名的人很多,前几次课会来很多人,慢慢的人数会减少,能坚持到最后的人很少,能完成作业的就更少了。而在3周3页面中,参加的人数越来越多,而且作业的完成率非常高(接近100%)。 回想起来,我在培训的最开始就设置了一些机制,保证学员可以有一个非常容易沉浸其中的环境: 通过watch、livereload等机制,保证每次修改的CSS/HTML都可以在1秒钟内在浏览器上自动刷新 通过对比mockup和自己实现的样式的差异,来调整自己的目标 将这些工具做成开箱即用的,这样经验不足者不至于被技术细节阻塞 做完之后,学员们的作品直接发布到github的pages上 事实上,这些实践恰好满足了上述的几个条件: 目标明确 快速且准确的反馈 技能与挑战的平衡 由于工作坊是在周内下班后(8点结束),我见到很多学员在课后(很晚的时候)还在写代码、调样式,完全沉浸其中,忘记时间。到最后,参加培训的学员们被要求通过设计原则自己实际一个Web Site,很多没有前段开发背景的同事也做出了非常有“设计感”的作品。 编程语言的壁垒 使用JavaScript或者Ruby这种解释型语言的开发人员,在第一次接触之后就会深深的爱上它,并再也无法回到编译型语言中去了。想一想要用Java打印一行Hello World你得费多大劲? 解释型语言中,你很容易可以采用REPL环境,写代码会变成一个做实验的过程:函数名写错了,参数传错了,某个函数没有定义等等错误/手误,都可以在1秒钟内得到反馈,然后你再根据反馈来修正方向。 举个小例子,写一个字符串处理函数:将字符串”qiu,juntao”转换成“Qiu Juntao”,你会怎么做?你大概知道有这样一些原生的API: String.indexOf String.replace 大小写转换 正则表达式(可选) 如果用JavaScript来实现的话,你可以在Chrome的DevTools中完成大部分的工作: 注意这里的每次操作的反馈(特别是出错的情况),你可以在1秒钟内就知道明确的反馈,而不是等等待漫长的编译过程。DevTools里的console提供的REPL(read-eval-print-loop)可以帮助你建立流畅的编码体验。 如果用Java来做,你需要一大堆的准备工作,抛开JDK的安装,JAVA_HOME的设置等,你还需要编译代码,需要定义一个类,然后在类中定义main方法,然后可能要查一些API来完成函数的编写。而每一个额外的步骤都会延长反馈的时间。 测试驱动开发 那么在编译型语言中如何获得这种体验呢?又如何缩短反馈周期呢?答案是使用测试驱动开发(Test Driven Development)! 通常TDD会包含这样几个步骤: 根据用户故事做任务分解 根据分解后的Task设计测试 编写测试 编写可以通过测试的实现 重构 步骤3-5可能会不断重复,直到所有的Task都完成,用户故事也就完成了。如果仔细分析,这些步骤也恰好符合产生心流的条件:...

January 13, 2018 1 min

Reflux 101

一点背景 React在设计之初就只关注在View本身上,其余部分如数据的获取,事件处理等,全然不在考虑之内。不过构建大型的Web前端应用,这些点又实在不可避免。所以Facebook的工程师提出了前端的Flux架构,这个架构的最大特点是单向数据流(后面详述)。但是Flux本身的实现有很多不合理的地方,比如单例的Dispatcher会在系统中有多种事件时导致臃肿的switch-cases等。 这里是Facebook官方提供的提供Flux的结构图。 其实整个Flux背后的思想也不是什么新东西。在很久之前,Win32的消息机制(以及很多的GUI系统)就在使用这个模型,而且这也是一种被证实可以用来构建大型软件的模型。 鉴于Flux本身只是一个架构,而且Facebook提供的参考实现又有一些问题,所以社区有了很多版本的Flux实现。比如我们这里会用到的Reflux。 Reflux简介 简而言之,Reflux里有两个组件:Store和Action。Store负责和数据相关的内容:从服务器上获取数据,并更新与其绑定的React组件(view controller);Action是一个事件的集合。Action和Store通过convention来连接起来。 具体来说,一个典型的过程是: 用户的动作(或者定时器)在组件上触发一个Action Reflux会调用对应的Store上的callback(自动触发) 这个callback在执行结束之后,会显式的触发(trigger)一个数据 对应的组件(可能是多个)的state会被更新 React组件检测到state的变化后,会自动重绘自身 一个例子 我们这里将使用React/Reflux开发一个实际的例子,从最简单的功能开始,逐步将其构建为一个较为复杂的应用。 这个应用是一个书签展示应用(数据来源于我的Google Bookmarks)。第一个版本的界面是这样的: 要构建这样一个列表应用,我们需要这样几个部分: 一个用来fetch数据,存储数据的store (BookmarkStore) 一个用来表达事件的Action(BookmarkActions) 一个列表组件(BookmarkList) 一个组件条目组件(Bookmark) 定义Actions var Reflux = require('reflux'); var BookmarkActions = Reflux.createActions([ 'fetch' ]); module.exports = BookmarkActions; 第一个版本,我们只需要定义一个fetch事件即可。然后在Store中编写这个Action的回调: 定义Store var $ = require('jquery'); var Reflux = require('reflux'); var BookmarkActions = require('../actions/bookmark-actions'); var Utils = require('../utils/fetch-client'); var BookmarkStore = Reflux.createStore({ listenables: [BookmarkActions], init: function() { this.onFetch(); }, onFetch: function() { var self = this; Utils....

November 9, 2015 3 min

Mapfile解析器

前言 Mapfile是MapServer用来描述一个地图的配置文件。它是一个很简单的声明式语言,一个地图(Map)可以有多个层(Layer),每个层可以有很多属性(键值对)。在一个层的定义中,还可以定义若干个类(Class),这个类用以管理不同的样式(Style)。而每个类或者样式都可以由若干个属性(键值对)。 这里有一个实际的例子: LAYER NAME "counties" DATA "counties-in-shaanxi-3857" STATUS default TYPE POLYGON TRANSPARENCY 70 CLASS NAME "polygon" STYLE COLOR 255 255 255 OUTLINECOLOR 40 44 52 END END END 最简单的层的定义 最简单的情形是,我们定义了一个层Layer,但是没有指定任何的属性: LAYER END 我们期望parser可以输出: {layer: null} 要做到这一步,首先需要定义符号LAYER和END,以及一些对空格,非法字符的处理等: \s+ /* skip whitespace */ \n|\r\n /* skip whitespace */ "LAYER" return "LAYER" "END" return "END" <<EOF>> return 'EOF' . return 'INVALID' 对于,空格,回车换行等,我们都直接跳过。对应的BNF也非常简单: expressions : decls EOF {return $1;} ; decls : LAYER END {$$ = {layer: null}} ; 为层添加属性 接下来我们来为层添加Name属性,首先还是添加符号NAME和对字符串的定义。这里的字符串被定义为:由双引号括起来的所有内容。...

October 5, 2015 3 min

如何手写一个解释器

在代码编写中,很多时候我们都会处理字符串:发现字符串中的某些规律,然后将想要的部分抽取出来。对于发杂一些的场景,我们会使用`正则表达式`来帮忙,正则表达式强大而灵活,主流的变成语言如`Java`,`Ruby`的标准库中都对其由很好的支持

September 30, 2015 3 min

又论函数式编程

前言 4月初在北京的时候,徐昊同学表示我们公司的同事们写的文章都太简单,太注重细节,然后捡起了芝麻丢了西瓜,于是我就不再更新博客(其实根本原因是项目太忙)。上周和其他几个同事一起参加“Martin Fowler深圳行”的活动,我和同事扎西贡献了一个《FullStack Language JavaScript》,一起的还有杨云(江湖人称大魔头)的话题是《掌握函数式编程,控制系统复杂度》,李新(江湖人称新爷)的话题是《并发:前生来世》。 和其他同事预演的时候,突然发现其实我们的主题或多或少都有些关联,我讲的部分也涉及到了基于事件的并发机制和函数式编程。仔细想想,应该与JavaScript本身的特性不无关系: 基于事件(Event-Based)的Node.js的正是并发中很典型的一个模型 函数式编程使其天然支持回调,从而非常适合异步/事件机制 函数式编程特性使其非常适合DSL的编写 会后的第二天,我在项目代码里忽然想要将一个聚合模型用函数式编程的方式重写一下,结果发现思路竟然与NoSQL依稀有些联系,进一步发现自己很多不足。 下面这个例子来自于实际项目中的场景,不过Domain做了切换,但是丝毫不影响阅读和理解背后的机制。 一个书签应用 设想有这样一个应用:用户可以看到一个订阅的RSS的列表。列表中的每一项(称为一个Feed),包含一个id,一个文章的标题title和一个文章的链接url。 数据模型看起来是这样的: var feeds = [ { 'id': 1, 'url': 'http://abruzzi.github.com/2015/03/list-comprehension-in-python/', 'title': 'Python中的 list comprehension 以及 generator' }, { 'id': 2, 'url': 'http://abruzzi.github.com/2015/03/build-monitor-script-based-on-inotify/', 'title': '使用inotify/fswatch构建自动监控脚本' }, { 'id': 3, 'url': 'http://abruzzi.github.com/2015/02/build-sample-application-by-using-underscore-and-jquery/', 'title': '使用underscore.js构建前端应用' } ]; 当这个简单应用没有任何用户相关的信息时,模型非常简单。但是很快,应用需要从单机版扩展到Web版,也就是说,我们引入了用户的概念。每个用户都能看到一个这样的列表。另外,用户还可以收藏Feed。当然,收藏之后,用户还可以查看收藏的Feed列表。 由于每个用户可以收藏多个Feed,而每个Feed也可以被多个用户收藏,因此它们之间的多对多关系如上图所示。可能你还会想到诸如: $ curl http://localhost:9999/user/1/feeds 来获取用户1的所有feed等,但是这些都不重要,真正的问题是,当你拿到了所有Feed之后,在UI上,需要为每个Feed填加一个属性makred。这个属性用来标示该feed是否已经被收藏了。对应到界面上,可能是一枚黄色的星星,或者一个红色的心。 服务器端聚合 由于关系型数据库的限制,你需要在服务器端做一次聚合,比如将feed对象包装一下,生成一个FeedWrapper之类的对象: public class FeedWrapper { private Feed feed; private boolean marked; public boolean isMarked() { return marked; } public void setMarked(boolean marked) { this....

May 23, 2015 2 min