崔鹏飞的Octopress Blog

[译] FP vs OO

| Comments

原文地址:https://blog.cleancoder.com/uncle-bob/2018/04/13/FPvsOO.html

原作者:Robert C. Martin (Uncle Bob)

在过去的几年中,我通过与人结对来学习函数式编程,他们中的很多人表达了反对OO的偏见。他们经常会说:“啊,这太像对象了。”

他们会这样说是因为他们认为FP和OO在某种程度上是互斥的。许多人似乎认为程序FP的程度等同于其非OO的程度。我认为这种观点是学习新事物的自然结果。

当我们采用一种新技术时,我们通常倾向于避开以前使用的旧技术。这很自然,因为我们认为新技术“更好”,因此旧技术就一定是“更糟”的。

在此博客中,我将说明OO和FP是正交的,但它们并不互斥。一个好的函数式程序可以(并且应该)是面向对象的。而且一个好的面向对象程序可以(并且应该)是函数式的。在此之前,我们必须非常谨慎地给FP和OO这两个词语下个定义。

什么是OO?

我将在这里采取非常还原主义的立场。OO有许多有效的定义,涵盖了丰富的概念,原理,技术,模式和哲学。在这里,我将忽略所有这些内容,而将重点放在最基础的东西上。我采取这种还原主义的原因是,很多围绕着OO的丰富特性实际上根本不是OO所特有的,而是整体软件开发丰富性的一部分。在这里,我将重点介绍面向对象不可分割的那部分。

看看以下两个表达式:

1
2
1:f(o);
2:o.f();

有什么区别?

显然,没有实际的语义差异。差异完全在语法上。但是一个看起来是FP的,另一个看起来是OO的。这是因为我们会推断表达式2具有特殊的语义行为,同时我们推断表达式1不具有这种特殊语义行为。这种特殊语义行为就是:多态性。

当我们看到表达式1时,我们看到名为f的函数被应用在了o上。我们推断只有一个名为f的函数,并且它可能不是围绕着o的标准函数中的一员。

另一方面,当我们看到表达式2时,我们看到一个名为f的消息被发送给了一个名为o的对象。我们推测可能还有其他类型的对象可以接受该消息f,因此我们不知道被调用的f具体是哪一个。其行为取决于o的类型,即f是多态的。

对多态性的这种预期是OO编程的本质。这是还原论的定义;它与OO密不可分。没有多态性的OO不是OO。OO的所有其他属性,例如封装的数据,绑定到该数据的方法,甚至继承,与表达式1的关系要比与表达式2的关系更多。

C和Pascal程序员(甚至在某种程度上甚至包括Fortran和Cobol程序员)都创建了包含封装函数和数据结构的系统。要创建和使用这种封装的结构并不一定非得需要OO语言。封装,甚至简单的继承,在此类语言中都是显而易见且自然的。(在C和Pascal中比其他更自然。)

因此,真正将OO程序与非OO程序区分开的是多态性。

您可能会说可以通过在f内部使用switch语句或if/else来实现多态。的确如此,因此我必须向OO添加一个约束。

多态机制一定不能创建从调用方到被调用方的源码依赖关系。

为了解释这一点,请再次看看上文的两个表达式。表达式1:f(o)似乎对f函数的源码有依赖。我们之所以如此推断是因为我们推断只有一个f,所以调用者必须认识被调用者。

但是,当我们看表达式2时,从o.f()我们推断出一些不同的东西。我们知道可能会有很多个f的实现,而且我们不知道真正要被调用到的是其中哪个。因此表达式2对于f函数的源码没有依赖。

具体来说,这意味着包含对函数进行多态调用的模块(源文件)绝对不能以任何方式引用包含这些函数实现的模块(源文件)。不可以有任何include或use或require或任何其它这样的声明使得一个源文件依赖另一个。

因此,我们对OO的简化定义是:

调用者的源码对于被调用者的源码不产生依赖的一种动态多态技巧。

什么是FP?

同样,我将采用还原主义。FP具有悠久的历史和传统,可追溯到软件之外的其他领域。FP范式里存在很多原理,技术,定理,哲学和概念。我将忽略所有这些内容,直接进入将FP与任何其他范式区分开的不可分割的属性。简而言之,就是:

1
当 a==b 时 f(a)==f(b)

在函数式程序中,每次调用同一个函数并给出同一个参数时,都会得到相同的结果。无论程序执行了多长时间。这叫做引用透明性。

这意味着函数f不可以更改任何影响函数f行为方式的全局状态。而且,如果我们说函数f可以代表系统中的所有函数 – 系统中的所有函数都必须是引用透明的 – 那么系统中的任何函数都无法改变任何全局状态。系统中的任何函数都无法执行任何操作,来导致系统中的另一个函数对相同的输入返回不同的值。

其更深的含义是,任何命名值都无法更改。也就是说,不能有赋值运算符。

现在,如果您仔细地考虑一下,您可能会得出这样的结论:仅由引用透明的函数组成的程序根本无法执行任何操作-因为任何有用的系统行为都会改变某些事物的状态。即使只是打印机或显示器的状态。但是,如果我们从引用透明性约束中排除硬件以及外界的任何元素,那么事实证明我们确实可以创建非常有用的系统。

诀窍当然是递归。考虑一个以state数据结构作为参数的函数。此参数包含函数工作需要的所有状态信息。该函数将创建一个新的state,里面包含更新过的值。该函数做的最后一件事就是调用它自己并把新的state作为参数传递进去。

这是函数式程序可以用来跟踪内部状态的变化而无需真正改变任何内部状态的简单技巧之一。

因此,函数式编程的简化定义为:

引用透明 – 没有重新赋值。

FP vs OO

现在OO和FP社区都要向我开炮了。还原主义不是赢得朋友的好方法。但这有时很有用。我认为有必要在似乎正在传播的FP vs OO的迷因上说两句。

显然,我选择的两个归约定义是完全正交的。多态和引用透明之间没有任何关系。它们之间没有交集。

但是正交并不意味着相互排斥(问问麦克斯韦就知道了)。建立同时使用动态多态性和引用透明性的系统是完全可能的。不仅可能,而且是可取的!

为什么是可取的?二者各自独立可取,合一仍可取!我们希望系统具有动态多态性,为了解耦。依赖关系可以跨架构边界反转。可以使用Mocks and Fakes和其他类型的Test Doubles进行测试。可以在不强制更改其他模块的情况下修改模块。这使得系统更易于更改和改进。

我们还希望系统具有引用透明性,为了可预测性。无法更改内部状态使系统更易于理解,更改和改进。它大大减少了竞态和其他并发更新问题的机会。

底线是:

没有FP vs OO。

FP和OO可以很好地合作。这两个属性都是现代系统所希望具有的。同时基于OO和FP原理构建的系统将最大限度地提高灵活性,可维护性,可测试性,简单性和健壮性。排斥一个赞成另一个只会削弱系统的结构。

被诅咒的与被祝福的

| Comments

身陷南洋囹圄,心系故园风物,无以聊赖之中,撰文权作慰藉。

从Neal Ford的一段话讲开去

前段时间看了Neal Ford的一个演讲,原视频

其中一段讲到了传统工程与软件工程的区别。

於我心甚有戚戚焉。

译如下:

软件工程和传统工程是截然不同的。

在传统工程的世界中,当你进入施工阶段时,你基本上是在给原子塑形,原子结构是很难重构的。一旦你将它们塑造成特定的形状,它们往往会保持这种形状,很难将它们变成其他形状。除非你熔化它们再回炉重造。因此,制造物理物品是一个非常昂贵的过程。这就意味着,我们会希望在施工开始之前正确做出设计。

但在软件世界中,我们不是在铸造原子。我们在比特(bit)的世界里,这些比特(bit)都非常具有可塑性。对比特(bit)进行更改非常容易。我们来比较一下,传统工程中有施工,软件世界中与之对应的是什么呢?那就是是编译和部署。编译和部署实际上是我们所设计的东西在现实世界中的表征,电子沿导电线路移动,对现实世界施加影响力。这就表明,对于我们软件世界而言,我们的设计蓝图就是我们的源代码。

在传统工程业中,最昂贵的部分是施工阶段。在桥梁和其他种类的物理制造中存在许多预测性数学,这样做的原因并不是为了获得更安全的结构,虽说它在客观上确实会达成这一点。初衷其实是为了节约成本。造完了一座桥梁,然后让重物碾压过去,观察桥梁是否会崩塌,从而来确认桥梁质量达标与否,这样做,万一垮塌了,这成本就实在是太昂贵了。所以你需要可预测性,因为制造这些东西太贵了。

但在软件世界中,因为施工过程对应的是编译和部署,所以改变的成本非常低,可以随时进行。我们甚至不必为此操心,每当我们停止打字时,我们的计算机内都会涌现出一小群精灵把我们设计的东西给制造出来,以便我们能够以一种逼真的方式对其进行测试。

所以Reeves先生说,“鉴于软件设计相对容易并且基本上可以免费构建,一个不足为奇的启示是,软件设计往往非常庞大和复杂。”

我相信软件是现在人类思维创造出来的最复杂的东西,有几个原因导致它们如此复杂,其中一个原因是物理世界中存在许多有用的约束,而软件的世界中这样的约束太少。


蓝图设计对应源码编写,建造施工对应编译部署,这着实有趣。

我们使用建筑这个隐喻来关照软件的时候,通常都是把架构设计对照到蓝图上去,而把源码编写对照到建造施工上去。

而Neal Ford不是这样使用建筑隐喻的。

Markdown

可以看出来,Neal Ford这个隐喻在落地程度上更进一层。

牛老爵爷的诅咒

Neal Ford这段话的内容非常丰富。

除了上述隐喻之外,还有桥梁不能像软件一样测试,软件如此复杂的原因是我们缺乏真实世界中的物理限制。

诚哉斯言,与土木工程师比起来,我们软件工程师确实是具有非常奢侈的环境。这个代码怎样?跑跑测试就知道了。

能想象土木工程师说这种话吗?

老板娘: 那栋楼设计搞的怎么样了?有没有仔细的检查过?

工程师: 我觉得没问题,直接施工建造出来看看就好了。

老板娘: 好,我相信你,既然你这么有信心,那就来吧。

Markdown

Markdown

工程师: 😳 😳 😳

老板娘: 请问您信心的来源是啥?有依据吗?你这么乱搞,我得陪多少钱?

工程师: 这是个小工程,有点粗心了。另一个,那个大桥的项目,我一定用心!

Markdown

老板娘: 合龙这部分,你是不是忘了设计喜鹊了?你打算让牛郎织女自己组成一个平行四边形吗?

工程师: 卒

老板娘: 破产

一旦发现失误,拆掉重造太贵,土木工程师没法像我们软件工程师一样动不动就跑测试,动不动就debug,只能事先把设计做好。

不过,也正是由于牛顿老爵爷设下了诅咒,土木工程师被逼练就了严谨的作风,扎实的推算能力。

(此处用牛老爵爷代表包括麦克斯韦在内的诸多先贤)

蛮荒西部 牛顿管不了 图灵没来得及立法

牛爷的诅咒持久而坚固,不过,还是有它管不到的法外之地。

引力,电磁力,强力,弱力,你们能耐程序员何?程序员就是要任性地玩弄bits。

祖师爷死得早,一口苹果啃完,留待我等程序员飘零纵欲至今。

福兮?祸兮?

Robert C. Martin:还是要设立并遵守基本法

无独有偶。最近看了《架构整洁之道》这本书。作者Robert C. Martin讲述了编程范式的发展历史,字里行间表达了与Neal Ford颇为神似的一些观点。

摘抄如下:

结构化编程是第一个普遍被采用的编程范式(但是却不是第一个被提出的),由Edsger Wybe Dijkstra于1968年最先提出。与此同时,Dijkstra还论证了使用goto这样的无限制跳转语句将会损害程序的整体结构。接下来的章节我们还会说到,也是这位Dijkstra最先主张用我们现在熟知的if/then/else语句和do/while/until语句来代替跳转语句的。我们可以将结构化编程范式归结为一句话:结构化编程对程序控制权的直接转移进行了限制和规范。

面向对象编程对程序控制权的间接转移进行了限制和规范。

函数式编程对程序中的赋值进行了限制和规范。

它们都从某一方面限制和规范了程序员的能力。没有一个范式是增加新能力的。也就是说,每个编程范式的目的都是设置限制。这些范式主要是为了告诉我们不能做什么,而不是可以做什么。

这三个编程范式分别限制了goto语句、函数指针和赋值语句的使用。那么除此之外,还有什么可以去除的吗

这些编程范式的历史知识与软件架构有关系吗?当然有,而且关系相当密切。譬如说,多态是我们跨越架构边界的手段,函数式编程是我们规范和限制数据存放位置与访问权限的手段,结构化编程则是各模块的算法实现基础。这和软件架构的三大关注重点不谋而合:功能性、组件独立性以及数据管理。


原文的意思非常明白,Structured programming,OO,FP都是在约束我们作为程序员的行为,给我们立下基本法,免得我们瞎搞。

Neal Ford: 自然界的物理定律是个好东西啊,可以限制土木工程师,让他们无法乱搞。我们软件世界里缺的就是这个。

Robert C. Martin: 老弟所言甚是。缺少自然的馈赠,我们就搞人为的规约。诸多编程范式都已经尝试过做这件事了。天不立法人立法。

张信哲表示同意

张信哲:你俩说的对,自由是个好东西,但是不能《过火》,免得过犹不及。

Markdown

工程师之荣光 演绎 归纳

人类99%的机率都在使用归纳法,只有1%的机率使用演绎法因为演绎法需要消耗认知能量,所以默认使用归纳法。

土木工程师要仰仗演绎推理,这是理工科之荣光。

此乃吾辈偏执之骄傲。

怎奈沦落为软件工程师,我们要重度依赖归纳才能勉强维持生活这样子。

代码有问题?跑一跑tests,改一改。还不行?再改改,再跑跑。仍然不行?那我可要debug了!!!

反复利用run tests和debug的方式来观察现象,收集现象,总结现象,反推原理。

这是软件工程师在脑力不断失败的绝望中不断降低自己身段的慷慨悲歌。

福兮?祸兮?

测试要先写 别着急跑 debug不到万不得已最好别用

为了保护我们宝贵的智商与荣光,在此作出如下建议。

测试写完,然后去写实现代码的时候要尽力去争取一遍过。

在试图用run test去验证刚刚写完的实现代码之前,别着急,可以先回头把代码捋一遍,用肉脑跑一遍先。

不到万不得已,别用debug。因为一旦你开始debug,就是认输了,认怂了。承认无法通过肉脑来演绎推理明白了。

不要过于骄纵,不要过量服用软件世界给我们的自由,多给自己一些人为设置的限制,向土木工程师看齐。

没想到吧?前面写的神神叨叨,最后来到了如此接地气的建议上。

别误会 不是那个意思

不是宣扬big upfront design。

不是反对short feedback loop。

不是反对跑测试,只是建议别着急跑,不要太过依赖跑测试,优先用肉脑,然后才跑测试。

不是否认debug的价值,只是希望大家尽力放慢做出要去debug的决定的速度,让子弹飞一会,给肉脑机会,让它跑一会。

不是否认归纳的价值,只是建议演绎与归纳不可偏废,不要过度依赖归纳而冷落演绎。


谨以此文与诸君共勉。For the serious hardcore programmers。

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

| 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月份由于去客户现场的原因,中断掉了。数据就放在这儿,不分析了。

其他

这一年脾气见好,不错。