崔鹏飞的Octopress Blog

读屠龙术五卷,粗浅总结一下太祖的套路

| Comments

最近快速浏览完了太祖的五卷选集。粗浅的尝试着总结一下太祖做事情和分析问题解决问题时都会运用一些什么样的套路。

性质决定形式

革命不是请客吃饭,不是做文章,不是绘画绣花,不能那样雅致,那样从容不迫,文质彬彬,那样温良恭俭让。革命是暴动,是一个阶级推翻一个阶级的暴烈的行动。
大家明白,不论做什么事,不懂得那件事的情形,它的性质,它和它以外的事情的关联,就不知道那件事的规律,就不知道如何去做,就不能做好那件事。
中国是一个经过了一次革命的、政治经济发展不平衡的、半殖民地的大国,这是中国革命战争的第一个特点。这个特点,不但基本地规定了我们政治上的战略和战术,而且也基本地规定了我们军事上的战略和战术。
办法是跟着方针来的。方针是不抵抗主义的时候,一切办法都反映不抵抗主义,这个我们已经有了六年的教训。方针如果是坚决抗战,那就非实行合乎这个方针的一套办法不可,非实行这八大纲领不可。

要究根源

开会时要使到会的人尽量发表意见。有争论的问题,要把是非弄明白,不要调和敷衍。一次不能解决的,二次再议(以不妨碍工作为条件),以期得到明晰的结论。
战争问题中的唯心论和机械论的倾向,是一切错误观点的认识论上的根源。他们看问题的方法是主观的和片面的。或者是毫无根据地纯主观地说一顿;或者是只根据问题的一侧面、一时候的表现,也同样主观地把它夸大起来,当作全体看。 … … 反对战争问题中的唯心论和机械论的倾向,采用客观的观点和全面的观点去考察战争,才能使战争问题得出正确的结论。

抓大放小

有些同志的批评不注意大的方面,只注意小的方面。他们不明白批评的主要任务,是指出政治上的错误和组织上的错误。至于个人缺点,如果不是与政治的和组织的错误有联系,则不必多所指摘,使同志们无所措手足。而且这种批评一发展,党内精神完全集注到小的缺点方面,人人变成了谨小慎微的君子,就会忘记党的政治任务,这是很大的危险。
据衡山的调查,贫农领袖百人中八十五人都变得很好,很能干,很努力。只有百分之十五,尚有些不良习惯。这只能叫做“少数不良分子”,决不能跟着土豪劣绅的口白,笼统地骂“痞子”。要解决这“少数不良分子”的问题,也只能在农会整顿纪律的口号之下,对群众做宣传,对他们本人进行训练,把农会的纪律整好,决不能随便派兵捉人,损害贫农阶级的威信,助长土豪劣绅的气势。这一点是非常要注意的。
要充分相信青年人,绝大多数是会胜任的。个别人可能不称职,也不用怕,以后可以改选掉。这样做,基本方向是不会错的。

注重调查研究而后有解决方案

你对于那个问题不能解决吗?那末,你就去调查那个问题的现状和它的历史吧!你完完全全调查明白了,你对那个问题就有解决的办法了。一切结论产生于调查情况的末尾,而不是在它的先头。只有蠢人,才是他一个人,或者邀集一堆人,不作调查,而只是冥思苦索地“想办法”,“打主意”。须知这是一定不能想出什么好办法,打出什么好主意的。换一句话说,他一定要产生错办法和错主意。
调查就像“十月怀胎”,解决问题就像“一朝分娩”。
对于国内和国际的政治、军事、经济、文化的任何一方面,我们所收集的材料还是零碎的,我们的研究工作还是没有系统的。二十年来,一般地说,我们并没有对于上述各方面作过系统的周密的收集材料加以研究的工作,缺乏调查研究客观实际状况的浓厚空气。“闭塞眼睛捉麻雀”,“瞎子摸鱼”,粗枝大叶,夸夸其谈,满足于一知半解,这种极坏的作风,这种完全违反马克思列宁主义基本精神的作风,还在我党许多同志中继续存在着。

不要盲从

我们说上级领导机关的指示是正确的,决不单是因为它出于“上级领导机关”,而是因为它的内容是适合于斗争中客观和主观情势的,是斗争所需要的。
我们说马克思主义是对的,决不是因为马克思这个人是什么“先哲”,而是因为他的理论,在我们的实践中,在我们的斗争中,证明了是对的。
马克思说的武装起义之后一刻也不应该停止进攻,这是说乘敌不备而突然起义的群众,应该不让反动的统治者有保守政权或恢复政权的机会,趁此一瞬间把国内反动的统治势力打个措手不及,而不要满足于已得的胜利,轻视敌人,放松对于敌人的进攻,或者畏缩不前,坐失消灭敌人的时机,招致革命的失败。这是正确的。然而不是说,敌我双方已在军事对抗中,而且敌人是优势,当受敌人压迫时,革命党人也不应该采取防御手段。如果这样想,那就是第一号的傻子。
不要迷信。中国人也好,外国人也好,死人也好,活人也好,对的就是对的,不对的就是不对的,不然就叫做迷信。要破除迷信。不论古代的也好,现代的也好,正确的就信,不正确的就不信,不仅不信而且还要批评。这才是科学的态度。

注重工作细节

每到一处,壁上写满了口号。惟缺绘图的技术人材,请中央和两省委送几个来。
大家要努力去发展农业和手工业的生产,多造农具,多产石灰,使明年的收获增多,恢复钨砂、木头、樟脑、纸张、烟叶、夏布、香菇、薄荷油等特产过去的产量,并把它们大批地输出到白区去。
我郑重地向大会提出,我们应该深刻地注意群众生活的问题,从土地、劳动问题,到柴米油盐问题。妇女群众要学习犁耙,找什么人去教她们呢?小孩子要求读书,小学办起了没有呢?对面的木桥太小会跌倒行人,要不要修理一下呢?许多人生疮害病,想个什么办法呢?一切这些群众生活上的问题,都应该把它提到自己的议事日程上。

不要用静态的眼光看问题

革命的道路,同世界上一切事物活动的道路一样,总是曲折的,不是笔直的。革命和反革命的阵线可能变动,也同世界上一切事物的可能变动一样。
国民党营垒中,在民族危机到了严重关头的时候,是要发生破裂的。
日本是小国,地小、物少、人少、兵少,中国是大国,地大、物博、人多、兵多这一个条件,于是在强弱对比之外,就还有小国、退步、寡助和大国、进步、多助的对比,这就是中国决不会亡的根据。强弱对比虽然规定了日本能够在中国有一定时期和一定程度的横行,中国不可避免地要走一段艰难的路程,抗日战争是持久战而不是速决战;然而小国、退步、寡助和大国、进步、多助的对比,又规定了日本不能横行到底,必然要遭到最后的失败,中国决不会亡,必然要取得最后的胜利。

先行动起来

革命战争是民众的事,常常不是先学好了再干,而是干起来再学习,干就是学习。

批判的学习

对于外国文化,排外主义的方针是错误的,应当尽量吸收进步的外国文化,以为发展中国新文化的借镜;盲目搬用的方针也是错误的,应当以中国人民的实际需要为基础,批判地吸收外国文化。苏联所创造的新文化,应当成为我们建设人民文化的范例。对于中国古代文化,同样,既不是一概排斥,也不是盲目搬用,而是批判地接收它,以利于推进中国的新文化。
后起而且发展得很快的帝国主义国家,即德日两国的军事家中,积极地鼓吹战略进攻的利益,反对战略防御。这种思想,是根本不合于中国革命战争的。德日帝国主义的军事家们指出防御的一个重要的弱点是不能振奋人心,反而使人心动摇。这是说的阶级矛盾剧烈,而战争的利益仅仅属于反动的统治阶层乃至反动的当权政派的那种国家。我们的情况不同。在保卫革命根据地和保卫中国的口号下,我们能够团结最大多数人民万众一心地作战

不耻下问,兼听则明

其中还有些问题没有弄清楚,需要先征求下级的意见。我们切不可强不知以为知,要“不耻下问”[2],要善于倾听下面干部的意见。先做学生,然后再做先生;先向下面干部请教,然后再下命令。
你们对上述计划意见如何?这个计划有何缺点?执行有何困难?统望考虑电告。
像卫立煌、翁文灏这样的有爱国心的国民党军政人员,我们应当继续调动他们的积极性。就是那些骂我们的,像龙云、梁漱溟、彭一湖之类,我们也要养起来,让他们骂,骂得无理,我们反驳,骂得有理,我们接受。这对党,对人民,对社会主义比较有利。

定量分析

胸中有“数”。这是说,对情况和问题一定要注意到它们的数量方面,要有基本的数量的分析。任何质量都表现为一定的数量,没有数量也就没有质量。我们有许多同志至今不懂得注意事物的数量方面,不懂得注意基本的统计、主要的百分比,不懂得注意决定事物质量的数量界限,一切都是胸中无“数”,结果就不能不犯错误。

分清屁股(立场)

十月革命推翻了资产阶级,这在世界上是个新鲜事情。对这个革命,国际资产阶级不管三七二十一,骂的多,总是说不好。俄国资产阶级是个反革命阶级,那个时候,国家资本主义这一套他不干,他怠工,破坏,拿起枪来打。俄国无产阶级没有别的办法,只好干掉他。这就惹火了各国资产阶级,他们就骂人。我们这里对待民族资产阶级比较缓和一点,他就舒服一点,觉得还有些好处。现在艾森豪威尔威尔、杜勒斯不让美国的新闻记者到中国来,实际上就是承认我们的政策有这个好处。如果我们这里是一塌糊涂,他们就会放那些人来,横直是写骂人文章。他们就是怕写出来的文章不专门骂人,还讲一点好话,那个事情就不好办。
江苏作了一个调查,有的地区,县区乡三级干部中间,有百分之三十的人替农民叫苦。后头一查,这些替农民叫苦的人,大多数是家里比较富裕,有余粮出卖的人。这些人的所谓“苦”,就是有余粮。所谓“帮助农民”、“关心农民”,就是有余粮不要卖给国家。这些叫苦的人到底代表谁呢?他们不是代表广大农民群众,而是代表少数富裕农民。
“人性论”。有没有人性这种东西?当然有的。但是只有具体的人性,没有抽象的人性。在阶级社会里就是只有带着阶级性的人性,而没有什么超阶级的人性。我们主张无产阶级的人性,人民大众的人性,而地主阶级资产阶级则主张地主阶级资产阶级的人性,不过他们口头上不这样说,却说成为唯一的人性。有些小资产阶级知识分子所鼓吹的人性,也是脱离人民大众或者反对人民大众的,他们的所谓人性实质上不过是资产阶级的个人主义,因此在他们眼中,无产阶级的人性就不合于人性。现在延安有些人们所主张的作为所谓文艺理论基础的“人性论”,就是这样讲,这是完全错误的。
对于革命的文艺家,暴露的对象,只能是侵略者、剥削者、压迫者及其在人民中所遗留的恶劣影响,而不能是人民大众。人民大众也是有缺点的,这些缺点应当用人民内部的批评和自我批评来克服,而进行这种批评和自我批评也是文艺的最重要任务之一。但这不应该说是什么“暴露人民”。对于人民,基本上是一个教育和提高他们的问题。

事物的两面性

把毒草,把非马克思主义和反马克思主义的东西,摆在我们同志面前,摆在人民群众和民主人士面前,让他们受到锻炼。不要封锁起来,封锁起来反而危险。这一条我们跟苏联的做法不同。为什么要种牛痘?就是人为地把一种病毒放到人体里面去,实行“细菌战”,跟你作斗争,使你的身体里头产生一种免疫力。
日本国度比较地小,其人力、军力、财力、物力均感缺乏,经不起长期的战争。日本统治者想从战争中解决这个困难问题,但同样,将达到其所期求的反面,这就是说,它为解决这个困难问题而发动战争,结果将因战争而增加困难,战争将连它原有的东西也消耗掉。
国民党是一个复杂的政党。它虽被这个代表大地主、大银行家、大买办阶层的反动集团所统治,所领导,却并不整个儿等于这个反动集团。它有一部分领袖人物不属于这个集团,而且被这个集团所打击、排斥或轻视。它有不少的干部、党员群众和三民主义青年团的团员群众并不满意这个集团的领导,而且有些甚至是反对它的领导的。
坏事也算一种经验,也有很大的作用。我们就有陈独秀、李立三、王明、张国焘、高岗、饶漱石这些人,他们是我们的教员。此外,我们还有别的教员。在国内来说,最好的教员是蒋介石。我们说不服的人,蒋介石一教,就说得服了。蒋介石用什么办法来教呢?他是用机关枪、大炮、飞机来教。还有帝国主义这个教员,它教育了我们六亿人民。一百多年来,几个帝国主义强国压迫我们,教育了我们。所以,坏事有个教育作用,有个借鉴作用。
军队的生产自给,在我们的条件下,形式上是落后的、倒退的,实质上是进步的,具有重大历史意义的。在形式上,我们违背了分工的原则。但是,在我们的条件下——国家贫困、国家分裂(这些都是国民党主要统治集团所造成的罪恶结果)以及分散的长期的人民游击战争,我们这样做,就是进步的了。大家看,国民党的军队面黄肌瘦,解放区的军队身强力壮。大家看,我们自己,在没有生产自给的时候,何等困难,一经生产自给,何等舒服。

Desugar Scala(18) – Stackable Traits

| Comments

Stackable traits是一种怎样的特性呢?

来举一个🌰

1
2
3
4
abstract class IntQueue {
  def get(): Int
  def put(x: Int)
}

定义一个IntQueue,抽象类,定义了get和put,没有实现。

1
2
3
4
5
6
7
8
9
class BasicIntQueue extends IntQueue {
  private val buf = new ArrayBuffer[Int]

  def get() = buf.remove(0)

  def put(x: Int) = {
    buf += x
  }
}

再定义一个BasicIntQueue,把上述IntQueue实现了。 它的实现没有什么花样,就是先进先出。

接下来就有意思了:

1
2
3
4
5
6
7
8
9
10
11
trait Incrementing extends IntQueue {
  abstract override def put(x: Int) = {
    super.put(x + 1)
  }
}

trait Doubling extends IntQueue {
  abstract override def put(x: Int) = {
    super.put(2 * x)
  }
}

定义了两个trait,都扩展自IntQueue。 一个是把数字先加一再放进队列,另一个是先把数字加倍再放入队列。

要注意这里的modifier:abstract override,以及在trait中对super的调用。稍后反编译的时候可以看懂它们的真实含义。

那这两个trait可以怎么使用呢?

1
class MagicQueue extends BasicIntQueue with Incrementing with Doubling

定义一个MagicQueue,它扩展自BasicIntQueue,同时mixin了上面的两个trait。

MagicQueue它自己是一行实现代码都没有的,那么它的行为会是什么样子呢?

1
2
3
4
5
6
7
val queue = new MagicQueue

queue.put(100)
queue.get() //会返回201

queue.put(500)
queue.get() //会返回1001

可以看到,它会先把数字乘以二,然后加一再放入队列。

MagicQueue继承了BasicIntQueue,混入了Incrementing和Doubling,它的行为就会是先跑Doubling后跑Incrementing最后跑BasicIntQueue(从右到左依序生效)。

这是种很实用的语言特性,你可以写很多个不同的trait,让它们都extend IntQueue。 同时写很多class让它们实现IntQueue。 然后每一个实现了IntQueue的class都可以和任意一个或者任意多个trait随意组合应用。

这给语言的使用者提供了很强的composition的便利性。

那下面看下这个语言特性是如何实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public abstract class IntQueue
{
    public abstract int get();

    public abstract void put(final int p0);
}

public class BasicIntQueue extends IntQueue
{
    private final ArrayBuffer<Object> buf;

    private ArrayBuffer<Object> buf() {
        return this.buf;
    }

    public int get() {
        return BoxesRunTime.unboxToInt(this.buf().remove(0));
    }

    public void put(final int x) {
        this.buf().$plus$eq((Object)BoxesRunTime.boxToInteger(x));
    }

    public BasicIntQueue() {
        this.buf = (ArrayBuffer<Object>)new ArrayBuffer();
    }
}

首先,IntQueue和BasicIntQueue反编译之后平淡无奇,一个抽象类,一个实现类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface Doubling
{
    void chap12$Doubling$$super$put(final int p0);

    void put(final int p0);
}

public abstract class Doubling$class
{
    public static void put(final Doubling $this, final int x) {
        $this.chap12$Doubling$$super$put(2 * x);
    }

    public static void $init$(final Doubling $this) {
    }
}

Doubling这个trait则被编译成了一个接口加一个抽象类,其中除了put之外还有一个名字有点奇怪的方法声明。 稍后可以看到它有什么用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface Incrementing
{
    void chap12$Incrementing$$super$put(final int p0);

    void put(final int p0);
}

public abstract class Incrementing$class
{
    public static void put(final Incrementing $this, final int x) {
        $this.chap12$Incrementing$$super$put(x + 1);
    }

    public static void $init$(final Incrementing $this) {
    }
}

Incrementing则和Doubling是一个路数。

(这里出现的chap12字样是我写代码时package的名字)

最后揭露真相的时候到了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MagicQueue extends BasicIntQueue implements Incrementing, Doubling
{
    public void chap12$Doubling$$super$put(final int x) {
        Incrementing$class.put((Incrementing)this, x);
    }

    public void put(final int x) {
        Doubling$class.put((Doubling)this, x);
    }

    public void chap12$Incrementing$$super$put(final int x) {
        super.put(x);
    }

    public MagicQueue() {
        Incrementing$class.$init$((Incrementing)this);
        Doubling$class.$init$((Doubling)this);
    }
}

MagicQueue本身被编译成了以上的样子。

我们看一下它的put方法被调用时会怎样呢?

  1. 它去调用Doubling$class.put这个静态方法,把自己和数字都传入

  2. Doubling$class.put则会先把数字乘以二,然后把乘积传给MagicQueue的chap12$Doubling$$super$put

  3. MagicQueue的chap12$Doubling$$super$put方法则会把MagicQueue自己的实例以及乘积都传给Incrementing$class.put这个静态方法

  4. Incrementing$class.put则会把接收到的参数,也就是乘积,加一,然后把加和后的数字传给MagicQueue的chap12$Incrementing$$super$put

  5. MagicQueue的chap12$Incrementing$$super$put最终把乘以二又加了一的数字传给了super.put

  6. super.put其实就是BasicIntQueue.put了,到这里终于把数字存到ArrayBuffer里面了

这样,Doubling,Incrementing,BasicIntQueue它们三个的行为就堆叠(stackable)在一起了。

当锤子遇到钉子

| Comments

此文标题党。更切题的标题应该叫做《如何用统计科学来黑星座》或者是《积极注册Facehub,促进统计科学蓬勃发展》

锤子 JS Promise

最近对JS社区里的Promises/A+规范产生了很浓的兴趣,感觉Promise这套东西确实蛮不错的,给异步算法的编写者和异步算法的消费者之间提供了一套统一的沟通手段,也为异步算法的消费者提供了更悦目易读的代码组织方式。

自己把它实现了一遍:https://www.npmjs.com/package/RWPromise

然后在武汉办公室run了3次workshop:http://cuipengfei.me/blog/2016/05/15/promise/

手里有了这么一把锤子就总想找个钉子敲一敲。总在想能去哪里找N多异步操作来让我来组织一下呢?

钉子 《异类》

无巧不成书,刚好最近在豆瓣上买了一本叫做《异类》的书在看: https://read.douban.com/ebook/10580943/

书中一开篇就提到了加拿大冰球运动员选拔机制中很有趣的一件轶事:

职业青年队绝大多数球员的生日集中在1月、2月和3月,这实在有点不可思议

加拿大冰球队按年龄分组所依据的分界线是1月1日,即从1月1日到当年12月31日之间出生的球员将会被分在同一组。也就是说,一个1月1日出生的选手,是在跟许多年纪比他小的队友争夺晋级权——在青春期到来之前,由于有将近12个月的年龄差距,球员之间在生理成熟度上将会表现出巨大的差异。

正如冰球队员的成长经历一样,因年龄大几个月而显现的微弱优势会在孩子的成长过程中不断积累,最终引导孩子走向成功或不成功,自信或不自信的轨道中,其影响会延伸许多年。

能否进入加拿大职业青年冰球队竟然和出生月份有关系,实在是太有趣了。

假想如果你运动天赋非常好,但是不幸出生在12月,于是不得不从小和大你将近一岁的少年运动员一起训练。 表现难免显得不那么出色,不受教练待见,得不到正面反馈,出场坐冷板凳……逐渐逐渐,伤仲永。

由此就联想到了,到TW来工作的人其出生月份是否也存在什么神奇的规律呢?

Facehub

生日信息哪家强? Facehub帮你忙,它可以查到每个注册用户的生日(只有月日,没有年)。

(注:Facehub是ThoughtWorks公司内部的一个社交网站,只对员工开放注册。用户可以在该网站了解其他同事的信息。)

作为一个内部推广的网站,FaceHub在公司内邮件组里总是铺天盖地、见缝插针地作广告宣传,每次看到觉得审美都疲劳了,不过到了需要的时候第一个就想到了它。 广告的作用,诚不我欺。

我先目测,Facehub用户不会超过500人。 并且用户的id是连续的自增数字,这就很好办了,我只要构造N多获取用户数据的GET请求就好了。 这N多的GET请求,肯定不能一个一个慢慢发送,那就需要异步请求了,这就是钉子啊! 这就是大量的需要被组织的异步操作啊! 钉锤终于有机会合体了。

于是我就写了这么个脚本: https://github.com/cuipengfei/Spikes/blob/master/js/birth/birth.js

其中使用Promise把大量的异步Http请求组织起来,然后统计其结果。

(注:如果需要自己跑这个脚本,需要把第七行的token替换成你自己的合法值,如果您没有账号的话就无需尝试了哦)

以下是统计结果,获取到用户数量348:

由此可以看到,来TW工作的人,11月,10月,8月出生的最多,5月出生的最少。

由于样本量不够大(只有348人),所以统计数据的含义还不太好说。

下面是我胡乱猜测的、不科学的、不严谨的理论

我们小学入学时卡生日印象中是用8月卡的,那么这就和加拿大的冰球挑队员卡1月类似。

那这样,小学入学后,优势的积累就应该会倾向于8,9,10这几月的学生(类比冰球青年职业队队员集中分布在1,2,3这几个月)。

这几个月生日的小学生从小积累优势,并在成年后把优势携带到了求职之中去。

那为什么我们统计出来是8,10,11月份的人份额多呢?怎么不是8,9,10这几个月呢?统计出来的数据为什么把9月给跳过去了呢?可怜的9月得罪谁了呢?

我猜是由于9月这群人会成为某种星座的几率实在是太高了,被命运之神无情的给镇压掉了。:(抱歉

既然已经有了这份代码,可以统计出生月份,索性再统计些其他侧面的数据:

可见天秤和狮子座的最多,金牛座的最少。和上面的月份排名差异不大。

仔细看下的话,发现我只得罪了7.4%的人,好欣慰:)

看入职日期,大学毕业和跳槽的高峰期很凸显。9,10月最少,这倒不算什么惊人的发现。

最后

由于只统计了348人的信息,所以以上猜测仅供娱乐,请勿当真。

请大家积极注册Facehub,并填写真实的生日信息,以促进统计科学的繁荣发展。 (如果您无法注册,可以考虑投一份简历,然后再来促进统计科学的繁荣发展哦)

自己动手实现Promises/A+规范

| Comments

Promise并不是一个新的概念,它已经有将近30年的历史.

其早期的雏形还有里氏替换原则的提出者Barbara Liskov的贡献在其中.

https://en.wikipedia.org/wiki/Futures_and_promises#History

而Promises/A+这个规范的出现,则为JavaScript世界中众多Promise实现库提供了一套统一的API和交互机制.

Promises/A+提供了配套的测试集:https://github.com/promises-aplus/promises-tests.

其中共有872个测试,如果你的实现能够让全部测试绿起来,则可以认为该实现符合了标准.

我的Promise实现:https://github.com/cuipengfei/Spikes/tree/master/js/promise

在npm上的发布:https://www.npmjs.com/package/RWPromise

要实现Promises/A+的规范其实并不需要很多代码,我的实现只有88行.当然,仅仅是符合规范和一个可用,易用的Promise库之间还有很大的差距.

如果作为教学或者演示的目的,我认为我的这份实现是已有实现中最简洁的一版.

自己实现Promise规范时需要注意的几点:

1. promise的状态一旦确定,不可更改

一个符合规范的promise有三种可能的状态:pending,resolved,rejected。

这三者是互斥的。

一个pending的promise可以变成resolved,或者rejected。

但是一旦进入resolved或者rejected状态,就再也不能变了。

用形象的语言来描述的话:一个promise就是一个关于未来的承诺,诺言一旦履行,不能反悔。

假设有如下代码:

1
2
var p = ???();//首先以某种方式拿到一个promise,假设这个promise现在是pending的
p.then(x,y);//然后把你希望在成功和失败时执行的x,y通过then方法挂进去

时间流逝,假设???()方法内部在未来某个不确定的时间执行了:

1
p.resolve();

然后,你的x函数应该会被调用。

再然后,无论p的resolve方法或者reject方法再怎么被调用,p的状态都不会再变更,x和y也再不会被执行了

2. 树状结构

对then方法的多次调用会形成一个树状的数据结构。

假设有如下代码:

1
2
3
4
var p = ???();//首先以某种方式拿到一个promise
p
    .then(a,b) //假设这次then的调用返回的是一个新的promise实例,称之为p1
    .then(c,d);//假设这次then的调用返回的是一个新的promise实例,称之为p2

上述代码等价于:

1
2
3
var p = ???();//首先以某种方式拿到一个promise
var p1 = p.then(a,b);
var p2 = p1.then(c,d);

当然,这个代码形成的会是类似于一个链表的结构,可以把它看作是树状结构的一个特例,也就是树中每个节点都最多只有一个子节点。

而如下的代码则会形成我们惯常看到的树:

1
2
3
4
5
6
7
var p = ???();
var p1 = p.then(a,b);
var p2 = p.then(c,d);
var p3 = p.then(e,f);

var p4 = p1.then(g,h);
var p5 = p3.then(i,j);

这时,树中每一个节点可以有任意多的子节点(取决于它的then被调用了多少次)。

了解promise的树状结构,将有助于实现promise时在自己脑子里构造递归模型。

3. 回调的执行时机

这是实现promise的时候,最容易把人搞晕的一点。

1
2
3
var p = ???();//首先以某种方式拿到一个promise,假设这时p是pending的状态
var p1 = p.then(a,b);
var p2 = p1.then(c,d);

以上代码执行完之后,我们手里有3个promise:p,p1,p2.

这时,a,b,c,d都还没有执行。

在未来某个不确定的时间,如果p的resolve方法被调用了,接下来会发生的事情是:

  • p会把传给resolve方法的参数value记住,并把自己的状态标记为resolved (以后就再也不能变了)
  • a会被调用到,其参数为value
    • 如果a执行过程中不出错
      • p1的状态被变成resolved,p1会把a的返回值记住
      • c会被调用到,其参数为a的返回值
        • 如果c执行过程中不出错
          • p2的状态被变成resolved,p2会把c的返回值记住
        • 如果c执行过程中出错
          • p2的状态被变成rejected,p2会把c抛出的异常记住
    • 如果a执行过程中出错
      • p1的状态被变成rejected,p1会把a抛出的异常记住
      • d会被调用到,参数为a抛出的异常
        • 如果d执行过程中不出错
          • p2的状态被变成resolved,p2会把d的返回值记住
        • 如果d执行过程中出错
          • p2的状态被变成rejected,p2会把d抛出的异常记住

这样,就看出递归的意思来了。不过b并没有在上面出现,这是因为p本身是被resolve的,b只有在p被reject的时候才会执行。

在未来某个不确定的时间,如果p的reject方法被调用了,接下来会发生的事情是:

。。。 。。。

就不用写了,把上面的a替换为b就好了。

以上的例子中,我们拿到p的时候它的状态是pending的,我们会先调用p的then,然后p才会被resolve(或者reject掉)。 也就是说当我们通过调用then传递给promise两个回调的时候,promise还没有能力确定应该执行哪个回调,只有当未来promise自己被resolve或者reject了的时候,它自己的状态确定了,它才知道该挑哪一个回调来执行。

还有另一种可能性,那就是当你拿到p的时候p就已经被resolve(或者reject掉了),这时如果你再调用then方法的话,所传入的两个回调,到底哪个应该被调用,马上就可以决定了。

也就是说回调被调用的触发点一共有三个,then,resolve,reject这三个方法。

利用CouchBase为弱网环境构建云同步Android应用

| Comments

背景

Wifi,4G,3G,这些我们习以为常的东西,未必对所有人来说都是随时可用的。

以我当前所在项目为例,应用场景是某欠发达地区医疗服务机构的药品库存管理。

所谓欠发达,具体怎样呢?

  • 没有台式电脑
  • 没有笔记本
  • 只能使用低端的安卓平板
  • 4G,3G信号不要想
  • 我们去过现场的一位同事甚至要爬到树上去,才能勉强收到2G信号 tree
  • 即便是2G信号,也是时断时续,非常不稳定

因此,需要随时保持连通的BS结构基本不可行,我们选择了重度依赖移动端设备本地存储的CS结构(胖客户端)。

网络不可用时,库存变动存储在安卓本地,何时网络可用,再将数据与服务器同步。

问题

以上描述的解决方案似乎合情合理,但是真实实施中还是遇到不少问题:

  • 本地schema与服务器schema不一致,中间涉及数据转换与回转
  • 本地到服务器的同步数据流动链条过长(本地orm->本地Json serialization->服务器Json deserialization->服务器orm),链条中任何一环都有出差池的可能性。 换句话说,导致数据健全性受损的可能性分散在了太多的点上,一旦出错,难以定位
  • 服务器到本地的数据同步,上一条中所描述的链条的逆向,同样是链条太长,潜在的出现错误的点太多
  • 服务器端所掌握的数据只是客户端真实数据的一个变体,并且还未必是最新的,这样就导致当移动端应用因其本地数据而出错的时候,我们只能对着服务器干着急

以上描述的问题并不是偶发性的,它不像这里有个bug今天修了,明天那里有个bug再修一次就好。

只要我们仍然要在弱网环境中运行应用,我们就需要重度依赖本地存储,就需要持续的在移动端和服务端进行双向数据同步,以上的问题就将会一直存在。

这是自然环境限制与技术选择所带来的固有的内在的问题。

解决方案

上面提到:

这是自然环境限制与技术选择所带来的固有的内在的问题。

这句话再解释明白一些,自然环境限制指的是很差的网络可用性,技术选择指的是服务器端提供REST API,移动端利用该API进行通信。

以上这二者相结合导致了上述情况成为了固有的内在的问题。

自然环境的限制我们无法突破,我们不能把基站部署过去,让大家打电话之前不用再爬到树上去。

但是技术选择是完全受我们控制的,是有做文章的空间的。

这就引出了文章标题提到的CouchBase。

CouchBase

关于CouchBase是一个怎样的DB,请大家自行搜索。

我们主要关注它推出的CouchBase-Lite(android和iOS均有对应版本)。

replicate

左边的绿色方框是移动端应用,它通过蓝色标示的Sync Gateway与CouchBase Server通信。

请注意图中的箭头都是双向的,任何一方对本地数据库的写操作,都会导致对方的更新。任何一方的网络暂时中断也没有关系,在网络恢复的时候将会自动重试。

这样一来,数据同步的思路就变了,不再是在服务器端定义上传下载的API,移动端进行调用。而是利用DB自有的replication机制进行数据同步。

这就意味着我们在移动端只需要关注建立领域特定的模型,并将其存储入移动端本地的CouchBase即可,至于后面的序列化、网络通信等等问题就不需要我们去担心了。

关注点中很大一部分就这样被分离了出去,交由infrastructure去完成。

至于DB自有的replication机制的可靠性,应该可以比较安全的做出假设,认为一个有商用场景的DB厂商的通用数据备份机制不会比我们自己拼凑出来的更差。

一个原型

https://github.com/cuipengfei/Spikes/tree/master/android/sync-prototype

上面的链接是一个基本可用的购物清单应用。全部代码都在,供参考。

下面谈如何把玩它。

第一步

下载CouchBase Server: http://www.couchbase.com/nosql-databases/downloads#,安装,配置管理员账户,不赘述。

在CouchBase Server的Admin console(默认地址: http://127.0.0.1:8091/index.html)中创建一个bucket,命名为demodb。

第二步

安装sync_gateway,Mac用户可以: brew install sync_gateway

以上github代码克隆下来后,sync-gateway路径下有个名为start_sync_gateway_server.sh的脚本,运行它来启动sync gateway。

第三步

运行同一个路径下的create_user.sh,来创建一个名为user1的用户,然后运行create_session.sh,为该用户创建一个session。

create_session.sh脚本有类似如下的输出:

{"session_id":"a469f18027647e4957ffd1743e2ea33ce0386dbc","expires":"2016-02-21T17:51:43.071175586+08:00","cookie_name":"SyncGatewaySession"}

把其中的session id记下备用。

(注:这里的用户和session都是sync gateway需要的,与CouchBase Server无直接关系)

第四步

找到代码中的MainActivity类,在startSync方法中加入session id:

1
2
3
4
5
6
7
8
9
10
//......
Replication pullReplication = database.createPullReplication(syncUrl);
pullReplication.setCookie("SyncGatewaySession", "a469f18027647e4957ffd1743e2ea33ce0386dbc", null, 86400000000000L, false, false);
pullReplication.setChannels(asList("user1"));
pullReplication.setContinuous(true);

Replication pushReplication = database.createPushReplication(syncUrl);
pushReplication.setCookie("SyncGatewaySession", "a469f18027647e4957ffd1743e2ea33ce0386dbc", null, 86400000000000L, false, false);
pushReplication.setChannels(asList("user1"));
pushReplication.setContinuous(true);

这段代码负责启动replication,双向同步从此而起。

找到createGroceryItem,为其中创建document的代码指定其所归属的用户:

1
2
3
4
5
//......
Document document = database.createDocument();

Map<String, Object> properties = new HashMap<String, Object>();
properties.put("channels", asList("user1"));

这几行代码可以保证各个移动端用户之间的数据不会混杂在一起。

第五步

在genymotion中启动android虚拟机(如果使用其他虚拟设备或者真机,请注意修改代码中的服务器ip地址)。

在购物清单中创建几条记录,然后清空移动端本机数据,重启应用,可以看到刚刚被清空的购物清单会从服务器上同步回来。

也可以尝试把虚拟机的网络连接断掉,创建或者修改几条记录,稍后重新连通网络,可以发现数据仍然可以上传到服务器。

还可以尝试用第三步中提到的脚本多创建几个用户,在不同的android虚拟机中使用不同用户,可以发现它们对彼此的数据是没有访问权的。

总结

以上第五步提到的双向同步,离线操作,不同用户之间的数据隔离,都不需要我们写任何特殊的代码来实现。

我们移动端的代码与CouchBase的集成基本就只涉及到第四步中提到的启动replication和创建document,那这样移动端剩下的工作就只有构建业务逻辑了。

如果你的移动端应用也需要在弱网环境下进行离线操作,在网络恢复时与服务器同步数据的话,不妨尝试一下CouchBase。

2015

| Comments

2015年结束了,一如已经结束了的每一年,非常迅速。

按惯例,从博客说起。

博客

15年写了16篇博客,其中9篇是与Scala,Reactive,OODP相关的。这个数字倒还不算太坏。

14年总结的时候说:

明年可以改进的是不要嫌话题小,不要嫌话题不够深。 有了有价值的想法就记下来,形成惯性。

15年第一季度没有执行这一条原则。其原因在于懒惰。

15年第二季度超额执行。其原因在于找到了主线任务,足够的探索便促成了足够的产出。

第三季度和第四季度未执行。其原因在于到客户现场去工作彻底打乱了所有的日常习惯与日程计划。

这么看来,博客这一块有得有失,得者为要找到一个足够有趣的主线任务,用来催生产出。失者为习惯的打破与难以重补。

由此得出16年需要执行的事项:

主线任务要明确,暂定为Reactive以及与之相关的一切。Review不能每个季度做一次,要每个月做一次,每次可以短,不能没有,这样强迫自己去关注进度,不可斜视。

读书

这个数字可以,分布情况不太漂亮。5月和9月是两个空档。

5月是因为刚去TWU,说得过去。9月是因为去了客户现场,其实也说得过去。

但是内容控制的不好。

14年总结说的是:

另外一个改进点是领域,我明年需要看一些轻量级的经济、哲学和社会心理学的书。口说无凭,于此立字为据。

这方面第一季度执行的可以,第二季度开始用trello track,也不错。 但是下半年就废了,还是前文提过的同样的原因。

16年的trello重新建了一个board,遵循上段同样的方式,每个月review。

MOOC

极其差。

一年只上完了一门课。没有勇气提16年的目标了。看际遇吧。

健身

这个坚持执行的很好。还探索出了好的玩法。

数据统计不完整,但是可以凑合看,增重0.4,减脂0.1,算是净增1斤肌肉。曲线不难看,但是这个数字不算漂亮。

从8月4号开始用bodyspace来track运动量,至此共91次,重量累计935吨。

这个数字累积到1000吨(也就是100万千克)时就换方法,不着重统计重量了,而是统计围度。

下面统计下出勤率:

从14年6月8日到现在,共574天。出勤362次,出勤率63%。

时间管理与统计

这个统计是从15年4月1日开始的,9,10月份由于去客户现场的原因,中断掉了。数据就放在这儿,不分析了。

其他

这一年脾气见好,不错。

2015第三季度

| Comments

惨。

八月份上了一个大客户的项目,到了晚上没力气也没心情做任何事情。

博客

没写。上个季度六月的系列告一段落后7月没写。到了8月就不用说了。

读书

七月八月读了7本书,9月一本未读。

在trello中建的计划,其中7月执行的不错,8月一般,九月,当让,啥都没做。

MOOC

无。

翻译

上次提到的书已经交稿了,坐等出版。

健身

这项不错,我从一开始就想好,无论什么事没时间没心情做,这件事不能停。

从八月份开始,有了系统的计划

Alt text

三个大肌群,所有小肌群,各自一个计划。

从八月初执行至今,共记录41次workout,举铁345吨。

Alt text

平均每次去健身房8吨,日最高记录15吨。

这段时间出勤率67%。

从去年6月8日到现在,共485天,出勤314次,出勤率64%。

其中武汉201次,印度72次,成都41次。

总之

Alt text

2015第二季度

| Comments

2015竟然这么快就过去一半了。逝者如斯夫。

博客

第一季度总结的时候说:

今年的博客主线任务定为OO与FP的比较和结合应用吧。

这个任务完成的不错,这个主题写了7篇博客。

迭代器已经irrelevant了,中介者和备忘录太简单就没写,状态模式没找到好的FP实现方式。 这样11种行为模式除去上述4个,算是基本覆盖完了。

Principles of Reactive Programming作业导学写了两篇,后面的有点难,写不出来了。。。 这一点后面MOOC再说。

还有一篇gender pay gap的博客,算是今年到目前为止阅读量最多的得意之作。

在武汉做了一次学英语的workshop,在TWU做了pecha kucha,总结成了两篇。

这样,不算季度总结的博客,写了12篇,数量和内容自己都满意。和技术相关的还可以充当复习和刷新记忆的资料。

不错,博客这方面达标了。

读书

这个季度比较惨。这一季度只读了五本书。

一部分原因在于TWU的日程太紧,一部分原因在于博客,MOOC和翻译占用的时间很多,不可兼得。

还好第一季度看书比较多,所以到目前为止这半年一共读过16本书,总体数量还不算太惨。

这十六本书里有7本有笔记,可以充当复习和刷新记忆的资料,而且效果很好。自己写的笔记,瞄一眼,整本书的内容和重点就全部复活。

不过看书的领域方面有点杂,2014总结的时候说: >明年需要看一些轻量级的经济、哲学和社会心理学的书。

这方面执行的不好。

http://i1.tietuku.com/26b6dd870a313983.png

于是建了一个trello,把2015下半年要读的书预先plan出来。每个月plan两三本,一部分符合上述他山之石的领域,一部分符合OOP和FP的技术主线。

http://i1.tietuku.com/09607b3bd8c47d1d.png

这样plan的数量并不大,如果突然出现兴趣很高,或者优先级很高的书的话,可以随时插入计划中。

另外,pipeline定义清晰,每本书要读完,有笔记,有复习,有某种形式的产出(笔记也算)。

MOOC

把Principles of Reactive Programming跟完了,证书拿到了。

http://i1.tietuku.com/cff384ac4788e10b.png

但是照实说,这门课没学懂,只是应付过了。后四周的作业导学没写出来。

主要原因在于事先对课程难度估计过低,投入时间不够。下次开课,需要再跟一遍。

除此之外,下半年对于MOOC这方面不做过多预期,有特别好的特别感兴趣的就跟,没有就算了。

算是对年初说过的话彻底食言了。。。

体重

在印度这段时间健身房出勤率74%左右,还挺好。

现在,60.5公斤,第一季度结束时是61。基本算是没变。充分说明了没有改进目标就不会有成绩。

不过鉴于我现在已经不算胖子了,这方面仍然不做过多奢求,维持就好。

另外,腹肌的轮廓开始出现了,我很开心:)

翻译

接了出版社一个翻译的活儿,《Seven more languages in seven weeks》,是本蛮不错的书。这个额外的任务投入时间较多也是读书方面有欠缺的原因之一。

对出版充满期待。

时间管理与统计

从四月一号开始,开始使用pomotodo这款超赞的番茄钟软件。从开始的第一天开始,到现在为止,凡是需要坐下来专心执行的事情都有记录。

http://i1.tietuku.com/50d083b12ab39342.png

这款软件的统计分析功能很好用,每个番茄钟还可以加tag。可以看到,这三个月做多的时间放在了TWU的备课上(26%),其次是翻译书(16%),mooc和blog紧跟其后(13%和10%),然后还有读书和写TWU需要的总结反馈(都是7%)。

这样,有数据,为什么读书少就一目了然了。

另外,可以看到这三个月平均日完成7.55个番茄钟。这个数量很说明问题,工作的效率靠感觉是感觉不出来的,要靠统计数据和分析。 7.55个番茄钟,相当于四个小时左右。每天平均专心工作,执行任务的时间只有四个小时啊!

这里面有一部分原因是TWU过程中需要听别的讲师的很多sessions,如果没有这个因素,日均数量应该是9个左右。

下个季度这方面的数据会是什么样的很难说,如果做的工作中有很大部分涉及沟通和协调的话,这个数字或许会走低。