Stoned's Blog

A startup hacker.

新的一天

好久没有写东西了,写东西在我想做的事情里的优先级是极低的,低于上coursera的课程,低于技术,低于一切我想做的事情。但我又觉得它很重要,我总是安慰自己说,如果我有时间,如果我有心情,我就一定会写东西。但这样的事情从来不会主动发生。看了些陈天的知乎专栏,很羡慕他有这么多的积累。我和他有相同的作息时间,早上5点起床,但我却没有任何积淀。这很让我沮丧。有一天看到他的文章中提到他是在下午6点到8点这段时间用来写东西,我深受启发,于是决定6点到8点这段时间用来强迫自己写些东西。

我不知道这样的事情能够坚持多久,我从来就不是个勤奋的人,浮躁,缺乏耐心。这段时间对自己的认识发生了很大的变化,看到自己想做的事情正在一点点的进行着,充满惊喜,看到自己想到达的地方,一点点靠近,充满欣喜。我不知道我是否足够幸运到达目的地,或许这并不重要,重要的是我已迈入了新的一天。

今天就写这些,纪念这新的一天。

一次聊天服务器性能调优经历分享

导读:
这是我的一次性能调优经历的分享,我的本意不是分享我调优所获得的知识与经验,而是希望可以启发大家的思考。如天放所说,其实最有效完成一个目标的方式是用二分法,逐步找到问题的解决办法。但二分法的本质是趋利避害,是以目标为导向的方法,追求最短时间解决一个问题,追求最易解决问题的办法。二分法可能是高效地解决问题的好办法,但却不是学习的好方法。

“名人哲言”里有一句话,“上帝若為你關了一扇門,必然為你開另一扇門”,在这里,恰恰相反,“当你开了一扇门,你必然会关上另外一扇门”。当你用二分法解决了你的问题,并为此窃喜之时,或许被你关上的无数扇门了有你追求了许久的宝藏,这些宝藏一次次与你擦肩而过。从长远的角度来讲,二分法不见得是最高效的办法。

带着问题去学习,才是一个最高效的学习方式,在我们去完成我们的目标的时候,我们会碰到各种各样的问题,正是这些问题的存在,才让我们逐步减少因我们片段学习(我们的许多知识是通过google得到的)而产生的割裂性,逐渐完整化,系统化。而二分法在许多时候,会让我们规避掉这些问题,导致我们错失最好的学习机会。

在我的这次经验中,我获得了一些看起来很简单,但是却让我受益无穷的经验和知识宝藏,有了这些知识的夯实,我才能慢慢往更深的地方走去。或许可以这么说,二分法是一个解决问题的好办法,但不是一个提高知识深度的好办法。而提高你的知识深度,有时候,需要你违背二分法的原则,脑子缺根筋,在一条未知的路上反复探索,才能到达更深的地步。

理是这么个理,你需要有足够的智慧去区分什么时候需要用二分法,什么时候需要钻牛角尖。其实最简单的办法是看时间的要求,在时间紧张的时候,我们应该追求效率,在时间充裕的时候,我们应该追求深度,对问题穷追不舍。我觉得每个人都应该在快速的节奏下,停下来想一想,去深究一些问题,张弛有度才是一个健康的方式。这也是为什么我希望我们的开发节奏是一大一小穿插的方式进行的原因。(但看起来,大部分人都误用了小版本的用途,小版本最主要的目的是希望你能深究一些你碰到的难题,比如重构,比如UI效果,而现在大家却只用小版本来解决小bug…)

第二件,我想通过这次经历分享的是,面对未知原因的问题的时候,其实每个人都可以解决。用最笨的办法,用最小时候学过的做实验的办法。我们小时候做过许多化学实验,物理实验,老师通常会选一组参照物,最终通过与参照物对比的方式来得出我们的结论。所以我们碰到未知问题的时候,也同样的可以用这样的办法解决,但请注意最重要的一点,要保证参照物与实验对象只有唯一一点不同,这样结论才是正确的,否则实验没有任何意义。所以大胆假设,小心求证才显得如此重要。

有参照物的问题,每个人都有办法去解决,每个人都不要错过这样一个完美的学习的机会。

这次调优经历不见得是最高效,最好的方式,结果也不见得是最好的结果,但我想,希望我的这次抛砖引玉,可以给大家带来更多的思考。


ppt内容预览下载地址(可以用上一页,下一页翻页,这样就像ppt了):http://pan.baidu.com/s/1sjybu8X

2013 Summary

2013已经过完,站在2014的开头回望2013,记得清楚的事情已不太多。但我知道,2013发生的一些改变会让我铭记一辈子。

2013最重要的事情是戒了游戏重新思考了自己的生活状态(我的生活出了问题),并找到了解决的钥匙我的早起体验)。这种改变带来的最直观的好处就是,我终于做成了一些之前老是想做,却没有实施的事情,比如读一些书,比如写一些东西。除了表面的好处之外,其实最大的好处在于,深刻地体验到了自我把控能力的增强,生活和心理就好比进入了快车道,不会再像之前那样老是经历状态的起起伏伏。

或许你无法体会到,就是这一点点心理的变化,给我的带来的影响超乎想象,它几乎重塑了我的价值体系(虽然有些夸张,但我真觉得它对我的影响至关重要)。这些影响包括

  1. 让我更了解自己。当你的生活有一大堆想做,却从来不去做的事情的时候,你的生活仅仅处在应付那些你应该做的事情上面。做这些你应该做的事情并不会让你痛苦不堪,但它对你自我认知却意义不大。自我认知就好比拿你自己做实验,只有足够多的实验次数,足够好的实验方法,你的结论才能尽可能正确。而今年我的自我认知方面的提升显而易见,这种提升带来的好处是更明白自己想要什么,也让自己更加自信。
  2. 价值体系的重塑。我觉得价值观是一个很有意思的事情,它对应的另一个词是原则。价值体系重塑的过程就是原则建立的过程。在2013之前,我是没有明确的原则的(不是没有原则,而是很懵懂,不明白自己的原则到底是什么),这也是为什么我长时间经历困惑、状态起伏的原因。原则,更加通俗得讲,其实是在说,什么是第一重要的事情。2013,我对一些事情确立了我的原则(原则或许也是一个说出来就矫情的事情),明确和认识到确立原则的方法以及重要性。
  3. 好吧,想破脑袋也想不出第三点。

以上是2013最重要的事情,这也决定了2013会成为我难以忘怀的一年。以下是其他一些事情。

  1. 夫妻关系。2013我花了很多时间去思考怎样改善我们的关系,但这不像是自我认知那样,自己想明白了就可以解决一切。我真的很努力在思考这方面的问题,从一个非自我的视角,从一个(假装)上帝视角来看待我们的问题。但这从来都是持久战,没有一剂良药药到病除。2014,我还是有耐心去改善,你呢?
  2. 旅行。2013,去了广东(没去成香港,因为忘带了港澳通行证),去了日本,回了成都,去了泰国。总体感受是,南方确实是一个适合我的地方,有生活的感觉。国外感觉都还挺好的,日本安静,干净,友好,海鲜好吃。泰国大部分人友好,车让行人,米粉好吃,有些燥热,但我还挺喜欢的。回成都是为了和过去告别,确实如此,告别了过去,轻装上路。
  3. 工作。事实上我的很多改变跟我工作上的体验密不可分,毕竟工作其实才是占了你大部分时间的事情。我记不起我2013大概干了些什么了,我只记得我被他们从“祥旦”叫成了“大神”到“大祥神”再到“Tech Lead”,用现在的话来说,就是,我已经被他们玩得不行了,快玩坏了。所以2014,我只想做祥旦。其实细想一下,在工作上我还是做了一些有意义的事情的,是吧,天放?只是怕有些事情说出来矫情,不说了。

照例,还是要说些2014的期待,这样2015年来看的时候,要么当成个笑话来看,要么满心欣喜。
2014,女儿快上幼儿园了,花更多的时间去陪她。其它,不强求,但做到最好。

The First Thing of How We Protect Our Marriage

老婆,有些东西放在信里面说更为合适,因为言语永远带着当时的情绪,而信可以写在理智之上。希望你可以不带任何情绪的看完这封信,我也尽量不掺杂任何我对具体事件的看法。

我一直觉得夫妻关系是一件很奇妙的事情,而夫妻两人,就像是Pai和老虎之间的关系。这句话的意思是,夫妻两人非驯服与被驯服的关系,而是在一次次的冲突中找到最适合夫妻双方的平衡点。而最理想的平衡点,不是泾渭分明的三八线,而是有合适距离的缓冲区。我说多让你想想夫妻关系,是希望你可以从一个更高的角度来看待冲突和感情。因为再理智的人也无法避免一叶障目的过激情绪。

夫妻关系从人生的长度来看,它就像是一条纽带,系在白发苍苍的耋耄老人与少不更事的小情侣中间。所以从大的角度来讲,只要它还系着,没有断,那什么事情都是可以商量的。哪怕那些让人歇斯底里的冲突,只要想想这条纽带,或许,就能让你安静下来。而从当下的角度来看待夫妻关系,它或许就是一次次的争吵,一次次的面红耳赤与歇斯底里。每一次当下的争吵决定了夫妻关系这条纽带的或粗或细,或者缘尽于此。

从长远的角度来讲,我是不愿意我的人生充满了争吵,我希望我的人生都是幸福的瞬间。但争吵也并非都是有害的,有争吵是因为我们的观念发生了冲突,如果处理得当,争论能帮我们重塑我们的观念,构建属于自己人生的原则。而这一点,不仅让我们在夫妻关系上受益,也让我们自己在人生其它地方享受它带来的好处。争吵就像润滑剂,偶尔加点润滑剂,可以让夫妻关系更加融洽。但如果只剩下了润滑剂,其中酸甜苦乐唯有自己知道了。

每一次争吵都会结束,结束之后必然会出现新的争吵,这是一定会发生的事情,这件事情本身就像是买彩票不中奖一样,是件高概率的事情。但但凡高概率的事件,只要能减少单次发生的概率或者减少发生的次数,那么它就能降低最终发生的概率,变成一个小概率事件。(参见【一定会发生和一定不会发生的事情】)

所以从减少争论发生的角度来讲,我们需要减少单次发生争吵的概率。而做到一点,我觉得有几点地方我们可以努力。

  1. 还原事实真相。很多时候我们发生激烈争论,是因为我们都认为自己是对的,对方的某一点明显是错的。我们就像两个拳击手,攻击对方的弱点,自信可以击倒对方。而这,就像是相互驯服。我们或许都没有错,只是缺乏智慧,缺乏足够的智慧从以上帝的视角来看待这些问题。我们一直期待有个上帝视角可以帮助还原事实真相,让我们看到全部,而不只是眼前的叶子。但,从来没有上帝,甚至没有其他人可以帮助我们自己,唯有我们自己可以还原。只有我们还原了事实真相,我们才能更清楚地看待事情。但这一点我们做得极度糟糕,每一回还原的过程就是争吵的一部分。
    还原真相是为了更好的得出一个正确的结论,当信息量不足的时候,所得出的结论只是在赌博。如37signals的共同创始人Jason Fried所言

      什么样的思维方式在大多数情况下是错误的呢?当你纠结于仅仅一种观点时,当你无法从不同的角度对问题进行宏观的观察时,你的想法往往就是错的。
    

    而这一点正是信息熵理论(可参见【数学之美】)的精髓,只有拥有了同一个事情更多的信息,你才越有可能得到一个满意的结果,否则,不管做了什么决定,都只是赌博而已。
    所以,这也是还原事实真相的意义所在,这也是为什么它排在第一位的原因。

  2. 为原则排好优先级。许多时候,我们冲突,是因为我们对于原则的优先级不同,而对方触犯了自己的原则,导致我们无休止地争吵。其实,我们(中国人)是最缺原则的一代(不是说我们没有原则,而是原则需要重塑,旧的一些原则不适合新的社会),至少对我来说是这样的,我经历着自己原则重塑的过程。其实要完成原则重塑的过程还挺困难的,因为只有无数次去验证你的原则,你才能确信自己设立原则的正确性。按照现代的理论,到处都是没有优先级的原则,那就是没有原则。基于这一点,我建议,我们可以整理好属于我们自己的原则优先级(最多三点)。
    我对原则的提议如下
    1. 第一原则)无论什么情况下,给予对方解释的机会。永远不能剥夺别人的话语权。
    2. 任何事情,任何指责,只在还原了事情真相之后,才去下结论和追究责任。
    3. 做到心平气和地还原真相。
  3. 把控自己的情绪。你我都是情绪把控的失败者,心中的火苗如同干燥的山林一样易燃,这是我们许多争吵的起源,只因言语冲突,怒从心头起。情绪控制是人生修行一辈子的难题,不敢奢求一蹴而就,只期望控制自己的情绪不违反上诉原则。

我们曾经说,要更加宽容,可以更加包容对方。但事实上我们根本无法做到这一点,所以也可能是因为宽容本身太过宽泛,不是真正意义上的处事准则。我希望以上所言,对你有一些启发意义。

Splitting a Fat Model Into Multiple Files

起因

Rails的应用内,总有一些超级复杂的model,比如User。而正因是胖model,瘦controller这样的指导思想,会导致一些model臃肿不堪,有些model甚至会超过1000行,而一旦超过1000行,一个类的代码就变得难以维护了。所以一段时间内,我们为了不让我们的model太臃肿,我们只能不往User里面写代码,而把一些代码写在其他的model里面。比如写成如下的方式

#exam.rb
class Exam < ActiveRecord::Base
    def add_user(user)
    end
end

但这样的方式Exam.add_user(user)明显没有user.join_exam(exam)直观。

#user.rb
class User < ActiveRecord::Base
    def join_exam(exam)
    end
end

所以为了兼顾直观与保持model可维护性更高,我查找了一些资料,尝试为我们的model瘦身。

解决思路

一个广为流传的办法是Use concerns to keep your models manageable,这是dhh(Creator of Ruby on Rails)大神推荐的方式,已被列入Rails4的规范之中了(Rails4重大设计决策:“胖”Model用ActiveSupport::Concern瘦身)。

但使用concern的根本目的不是为了解决单个model的肥胖问题,而是为了让一些model通用的代码片段存在于一个合适的位置,比如commentable,这样但凡有用到comment的model,只要include commentable就可以实现评论相关的功能,这样的实现方式既简单又直观。

如果为model瘦身,主要的问题是,如何分离你的业务逻辑,因为单纯的将代码发在几个小文件中是没有什么意义的(为什么没有意义?因为会增加你查找的难度和理解的难度),这也是为什么7 Patterns to Refactor Fat ActiveRecord Models

“Any application with an app/concerns directory is concerning.”

的一个重要原因。

所以综合以上思路,最终实践得出了三个可以简化User model的办法

  • 为model抽象一些功能性concern,比如Authenticatable, Genderable。
  • 为model分离一些业务相关模块。比如user_post, 比如 user_event
  • 将特殊的功能模块抽象成class

实践

以下实践均以User model为例

为model抽象一些功能性concern

这样做符合concern本身的意图,比如将用户验证的相关操作和相关scope放在Authenticatable的module,这样User就拥有了可拔插的能力,如果想去掉相关的功能,只需要取消include就可以了。

所以,这部分的代码类似于

#app/models/concerns/authenticatable.rb
# -*- encoding : utf-8 -*-
module Authenticatable
  extend ActiveSupport::Concern
  included do
    def self.basic_auth(account, password)
    end
  end

  def has_password?
  end
end

为model分离一些业务相关模块

这样做是不太符合concern本身的意图的,但借用concern来表达我们的业务逻辑,也没有什么问题。但这样做最大的问题是,这部分的concern不应该和传统的concern混在一起,这样不同model的业务逻辑容易混乱(至少不好查找)。

所以,我们将这部分代码放在了app/models/concerns/user_concern/中,代码类似如下

#app/models/concerns/user_concern/post.rb
# -*- encoding : utf-8 -*-
module UserConcern
  module Post
    extend ActiveSupport::Concern

    included do
      has_many :posts, :dependent => :destroy
      has_many :other_posts, :dependent => :destroy
    end
  end
end

将特殊的功能模块抽象成class

这种办法最受7 Patterns to Refactor Fat ActiveRecord Models推崇,所以,可以借鉴一些这边文章提到的一些方法来抽象我们的代码。这部分代码类似于

#app/models/lib/edit_limit.rb
# -*- encoding : utf-8 -*-
class EditLimit
  NO_MODIFY_LIMIT_USERS_KEY = "no_modify_limit_users"

  MODIFY_LIMIT = 5
  MODIFY_LIMIT_KEY = "editable_times_%d"

  def initialize(user)
    @user = user
  end

  def self.no_modify_limit_users
    Rails.cache.fetch NO_MODIFY_LIMIT_USERS_KEY do
      Set.new
    end
  end

  def self.add_no_modify_limit_user(user)
    no_limit_user_ids = no_modify_limit_users

    no_limit_user_ids << user.id

    Rails.cache.write NO_MODIFY_LIMIT_USERS_KEY, no_limit_user_ids
    no_limit_user_ids
  end
end

最终,我们在user.rb的引用如下所示

#app/models/user.rb
class User < ActiveRecord::Base
  include Authenticatable
  include UserConcern::Post
end

总结

因为Rails本身的设计,对于一些不太复杂的逻辑与应用,实在无需煞费苦心去寻求瘦身与简化。因为符合Rails本身的设计思想在很大程度上就已经很好的分离了model,view,controller(MVC)三者的职责。而一些复杂的应用,在不考虑更好的可读性的前提下(比如将许多逻辑放置在关联表中),也不会形成太过肥胖的model。所以,只有一些人像我这样对可读性有些洁癖,又想让model保持简单的人,才需要类似的解决办法。

参考资料

一定会发生和一定不会发生的事情

谈起这个话题,原因是我的一次愚蠢的尝试。我曾经在一个游戏中用虚拟积分玩过这样的游戏,我先用1个积分去投注,如果输了,我就翻倍去投注,直到我输光为止。而我不相信我能连输十盘,我觉得那是一个不可能发生的事情。结果很明显,我为了赚一个积分,把我的所有积分都输光了。所以从这点出发,我觉得有必要去正视什么是一定会发生的事情,什么是一定不会发生的事情,两者的边界是什么?

一定会发生的事情

赌博

以我上述的例子为准,假设你的人品爆发,碰到赌场了绝对公平的游戏,你赢的概率有50%。所以我们可以计算一下你连输10(或11次,下方是11次的概率)盘,输掉内裤的概率有多大。

连输11次的概率=m¹º=0.0009765625 其中m=0.5

所以,很明显你连输11盘的概率很低,换言之,你赢的概率达到了99.90%,这个概率达到了千足金的含金量。可问题是,这够稳固吗?这就意味着,你玩1000次,你就要输掉一次,只为了赚1块钱。

ok,你可以说假设你每天玩一次,1000天才会输掉一次,就是说快3年你才输掉一次。这看起来还挺好的。常胜将军。但问题关键是,这是赌博,赌博最重要的一点是趋利,是贪婪,没有人会满足一天只赚一块钱,说不定你一天就会玩上100盘,不到十天,你就输光了你的所有资本。

我称上述的例子为一定会发生的事情,哪怕它的概率达到了99.9%。

一定不会发生的事情

飞机失事

众所周知,飞机失事的概率是很低的,但到底是一个什么量级的数据呢?

Accident statistics的统计,平均340万次飞行才会有一次至少一人死亡的记录,所以全身而退的概率就等于

1-1/3400000 = 0.99999970588

这个比例就相当于,如果象梁朝伟一样,每天飞到巴黎去喂个鸽子,第二天飞回来(每天一次飞机)。那么9315年才能出现一次(至少一个人死亡)的飞机失事。

所以这个比例在我们看来是几乎不会出现的概率,我们暂时可以称之为一定不会发生的事情。

UUID(Universally Unique Identifier)

UUID是计算机上普遍使用的用于唯一标识的标识符。它一般是根据mac地址+时间戳+随机数生成的。

UUID的标准型式包含32个16进位数字,以连字号分为五段,形式为8-4-4-4-12的32个字符。示例:
550e8400-e29b-41d4-a716-446655440000

所以假设UUID是全随机的,那么它的概率是

16的32次方 = 3.4 x 10的38次方

所以,也就是说随机两次,重复一次的概率=1/ 3.4*10的38次方

那随机UUID的重复机率是多少呢?

随机产生的UUID(例如说由java.util.UUID类型产生的)的128个比特中,有122个比特是随机产生,4个比特在此版本('Randomly generated UUID')被使用,还有2个在其变体('Leach-Salz')中被使用。利用生日悖论,可计算出两笔UUID拥有相同值的机率约为   
p(n)\approx 1-e^{-n^2/{2 \cdot x}}
以下是以x=2122计算出n笔UUID后产生碰撞的机率:
n   机率
68,719,476,736 = 2的36次方   0.0000000000000004 (4 x 10-16)
2,199,023,255,552 = 2的41方    0.0000000000004 (4 x 10-13)
70,368,744,177,664 = 2的46方  0.0000000004 (4 x 10-10)
与被陨石击中的机率比较的话,已知一个人每年被陨石击中的机率估计为170亿分之1[1],也就是说机率大约是0.00000000006 (6 x 10-11),等同于在一年内置立数十兆笔UUID并发生一次重复。换句话说,每秒产生10亿笔UUID,100年后只产生一次重复的机率是50%。如果地球上每个人都各有6亿笔UUID,发生一次重复的机率是50%。
产生重复UUID并造成错误的情况非常低,是故大可不必考虑此问题。
机率也与乱数产生器的质量有关。若要避免重复机率提高,必须要使用奠基于密码学上的假乱数产生器来生成值才行。

所以,UUID的重复可以认为是一定不可能发生的事情。而这个概率相比飞机失事的概率来说,对我们来说,那真的是非常非常放心了。如果飞机失事的概率达到UUID重复的概率,那就没有空难了。

两者的边界

当我们去评估哪一些属于一定会发生,哪些是一定不会发生的时候,有两个指标。一个是单次发生的概率,第二个是你重复的次数。而我们去评估的时候,通常会以相对夸张的次数来评估最终发生的概率。

以飞机失事的概率来计算,假设一个人的寿命是90年,每天坐一班飞机。那么他在有生之年失事的概率<1%。虽然没有绝对安全,但相对更为复杂的生存环境,我觉得这已经是一个完美的概率了。

所以我觉得,飞机失事的概率为分界(1%的最终概率),远大于最终死亡概率的,那么它就可以认为是一定会发生的事情,而远小于最终死亡概率的,那么可以列为安全概率。而在这概率附近的,看你个人自己的看法啦。

参考资料

我们真正关心的事情

如果你是位新员工,你可能会有一些困惑,我们没有代码规范,却要求你代码应该这么写,而不应该那么写,相同的,我们会建议你有些事情应该这样做,而不是那么做。你不知道到底哪些事情才是最重要的,在不知道优先级的情况下,你可能会犯一些错误,你会让你感到很沮丧。所以,从一个更严谨的角度,我应该把我们真正在乎的事情全都告诉你。 我们真正在乎的事情是:

  • 工作态度
  • 效率
  • 创造性

工作态度

认真严谨的工作态度

这件事情,我在之前提过,你可以参考an advise to the new staff,这是最最重要的事情,这是我们接下来要说的所有事情的前提。

我们没有程序员,只有工程师

希望你可以理解这句听上去有点绕口的话。程序员和工程师,在我的理解,两者最大的区别,前者只工作在代码开发阶段,而后者参与一件事情的全部(请以广义的工程师来理解,非片面的IT工程师)。而这点区别决定了,在这里,你不是对你的leader负责,而是对整个产品负责。所以需要要求你:

  • 希望你以用户的眼光来看待问题。不管是API接口还是App开发,请以用户的视角来看待问题。不要犯类似于给用户的接口却不关心排序这么低级的错误。
  • 在你的角度,尽你所能推进事情的进展。请记住你不是一个程序员,你可以推动相关人员在这件事情上的进度,(比如推动设计师完成急需的东西)。请不要置身事外,等待事情的进展。
  • 写代码之前先想清楚,先写spec梳理整个功能的开发以及后续。

在职责之外,也有属于你的权利

  • 你可以强烈坚持你的意见。(但在申诉无效之后,请服从团队的决定)
  • 关于对于产品的建议,你的建议会比其他人的更受重视。

效率

效率是团队竞争力的根基。围绕效率,有一系列我们推崇的工作方式

效率、专注——番茄工作法

专注能力是一种宝贵的资源,你只有在专注的时候,你才能用一些方法来提高你的效率。番茄工作法简易工具下载)可以帮助你集中注意力,提高效率。这是我们实践过的极为有效的一种办法,使用番茄工作法的还有一个好处是可以尽快帮你从糟糕的状态里走出来。

异步工作方式

异步工作方式保障了你好不容易积累起来的注意力不会被轻易打断。请参考be asynchronous

不要过度设计,用迭代的方法去做事情

实现最小可用原型,然后用迭代的办法去完善它。这是所有敏捷开发最重要的一点。而这一点,也体现在我们工作中的方方面面。它帮助我们训练自己如何在缺乏资源的情况下,完成一件最小可用的模型,同时,也帮我们学会去抽象和简化一件事情,而抽象是程序设计最重要的能力之一。

TDD(测试驱动开发)

没错,把TDD放在这里是因为它能帮我们提高效率。TDD可以帮我们

  • 检查代码逻辑,很难被测试的代码就是一个强烈的可以被重构的信号。
  • 减少人工检查的时间。
  • 提高交付的正确率,节约大家的时间。

重构

重构最重要的一点是可以嗅得到坏代码的味道,而坏代码是所有bug的温床,是阅读和理解代码的拦路虎。适当的及时的重构一些不合理的代码可以帮助减少bug,节约其他人理解代码的时间。

而之所以需要频繁的重构,是因为我们采用迭代的方法来编写我们的代码,而在迭代初期,适当的简化和抽象逻辑是为了更快地完成我们的最小可用模型。但我们在修改和完善我们的代码的时候,我们必须要及时的把一些不再合理的代码改写掉,使之更健壮。

创造性

创造性是一个老生常谈的话题,也是一个可遇而不可求的东西。这看起来是一个非常虚和无法落实的话题。每个个体的创造性是一个概率问题,但一个团队的创造性却是一个竞争力问题。而这一点是发展过程中,不可避免的。

所以,为了提高自己和团队的创造性,希望大家可以找到hacker的感觉。hacker不是指入侵和破坏,而是一种无边界的思维方式。希望大家有这方面的体验和训练。

其实,这篇东西里讲的东西非常之多,非常之大,每个单独的东西拿出来都是可以单独出书的。之所以简要罗列于此,是希望大家可以明白和认同什么才是我们真正关注的东西。

参考资料

BE ASYNCHRONOUS

上次的团队分享中,跟大家分享了github的异步的工作方式。把大家的建议总结一下,并发起一个倡议。希望大家可以遵守并形成我们自己的异步的工作方式。

异步的工作方式的优点不言而喻,缩短等待(block)的时间,不会打扰其他人的工作。而这两点归根结底,会影响团队的工作效率,而效率,是一个团队,尤其是创业团队的命根子。

所以要想做到异步的工作方式,我们需要做到:

  1. 保障自己的成果输出质量。希望每个人都可以严格要求自己,对自己的输出成果负责。这其中包括
    • 服务端提供给API接口的正确性保障
    • App客户端提供给大家测试版本的基本正确性(减少明显错误的地方)
    • 设计输出给开发的切图的完整性与正确性
  2. 规范交付成果

    以API接口为例,在API接口可测试的同时,需要告诉App开发工程师,接口的url以及接口参数和调用说明。如果非绝对必要,请以异步的方式交付这些东西,比如email或者QQ的方式告诉相关的人员。

  3. 建立安静的工作氛围(强制大家以异步的方式来沟通)

    因为我们的工作环境是一个大开间,这样设计的原因是为了体现大家的平等,没有人应该有办公室,所有人包括天放在内都是平等的(虽然以后可能会改变,但改变不是为了体现特权,而是特殊原因,比如不应该被频繁打扰)。但大开间对工作效率是有害的(我在hackernews上见过类似的控诉),因为会有一些有意无意的打断和干扰,我相信这也是为什么天放设立效率室的原因。所以,为了减少大开间带来的弊端,我希望可以做到:

    • 安静的沟通方式,QQ为主,小声的讨论。如需要讨论激烈的话题,请到会议室讨论。(抱歉,在这一点上我做得非常不好,请大家监督,下不为例)
    • 不打扰带耳机的同事。如果你不希望被打扰,请带上耳机。同样的,你也不应该打扰带耳机的同事,哪怕他就坐在你旁边(你可以用QQ告知)。当然,真正紧急的事情除外。(但你要有能力区分哪些是真正紧急的事情)
    • 有些需要面对面沟通的事情,尽量放在scrum会议以及中午和晚上吃饭的时间附近。

差不多是以上几点。此外有一些技巧可以减少你被阻塞(block)的机会,以及在被阻塞(block)住之后,可以继续进行下去的办法。

  • 花一些时间在跟你的工作有交集的地方,比如App开发组,需要花一些时间在服务端的开发上,和设计的工作方式上,这样可以减少你被block的机会,以API接口为例,如果你真不清楚某个接口的参数,你可以直接去后台代码中查看,这样可以节省很多时间。

  • 培养处理block的能力。还是以App开发组为例,要有能力在没有接口和切图的前提下,去完成App的逻辑。这样在碰到block的地方,可以轻易跳过。

  • 两件事情并行的能力。虽然原则上你要一件件的事情去做,优质地完成一件事情之后,再去做另外一件事情。这样可以减少事情烂尾的风险。但真被block住的事情,希望你能有快速切换到另外一件事情的能力。这就要求你有良好的开发习惯。

    • 合适的commit粒度
    • 每件事情一个branch的开发原则
    • 了解和理解团队和你的优先级

希望和大家一起享受异步的工作方式。

参考资料

An Advise to the New Staff

hi,新来的同学,欢迎加入我们的团队。

我们是一个甚少约束的公司,你可以自由选择自己的工作时间,自由选择自己的工作方式,在保证效率的前提下,你几乎可做任何尝试,比如站着办公,比如去不受打扰的房间一个人待着。(虽然除了我和天放,几乎没有人这样做过,但实际上我希望大家这么做,希望每个人都可以找到属于的自己的高效率的钥匙),甚至可以在家办公(你可以尝试,我尝试过,但效果不佳)。

我们在编程风格上和团队交流上都没有强制的建议。(比如所有成果必须文档化,在做之前必须写设计文档之类的)。但我们建议你可以遵从语言本身的编码规范,这样可以减少犯错的机会和降低交流的成本。

在团队的建设过程中,我们也逐渐形成了我们自己的一套工作流程。比如如何是用git来管理我们的代码,如何用git flow来管理我们的代码提交流程。(如果你不是很熟悉这些,没有关系,我们会认真跟你讲明白,帮你尽快适应)。还有比如用trello来管理和追踪我们的工作进度和工作安排。

希望你可以尽快适应上述一切(那只是一部分),那是属于我们团队的特质(traits)。你在适应的过程中,可能会遇到一些困难,习惯上的,新语言上的。希望困难不会让你感到无所适从,但不管如何,请谨记下面最重要的一点。

养成认真、严谨的工作态度,对自己的工作成果负责。

要做到上述要求,你需要做到:

  1. 在提交代码之前,认真检查自己的代码,没有任何多余无关的东西。
  2. 认真测试自己的输出成果,只有经过本地测试,内部测试之后,所有的东西才能发布在生产(production)环境。
  3. 希望你可以养成写单元测试的习惯,这样可以减少你犯错的机会。
  4. 不要重复犯低级错误。

总而言之,就是对自己的成果负责,不要让其他人去发现你的低级的错误。请记住,这是最最重要的一点,如果你多次犯了这样的错误,我们对你会是零容忍的态度。(原则的事情需要说在前面,希望这样的事情永远不会发生)至于其他,语言,习惯上的,你可以慢慢适应。

最后,希望你可以在这里找到快乐。(Be Happy here).

Support Emoji in Rails 3.2.14

本文主要参考一片日文的文章Rails 3.2でiOS5の絵文字を扱う,实践并修改完成。让你的应用可以支持emoji,需要达到以下几点要求

  1. 数据库支持emoji(utf8mb4)
  2. rails的sql adapter可以支持以utf8mb4的方式访问数据库
  3. 传输过程中,utf8mb4的信息不会丢失

让MySQL数据库支持emoji(utf8mb4)的存储

MySQL 5.5.3以上已经支持了utf8mb4的字符集,所以如果你的MySQL是5.5.3以上,只需要在my.conf文件中按照如下的配置就可以支持utf8mb4的字符集了。

[client]
default-character-set = utf8mb4

[mysqld]
collation-server = utf8mb4_unicode_ci
character-set-server = utf8mb4

在修改完my.conf配置之后,重启mysql,检查字符集是否已经更改,除了character_set_systemcharacter_set_filesystem之外,其他的字符集都需要变成utf8mb4类型。

mysql> show variables like 'char%';
+--------------------------+------------------------------------------------------+
| Variable_name            | Value                                                |
+--------------------------+------------------------------------------------------+
| character_set_client     | utf8mb4                                              |
| character_set_connection | utf8mb4                                              |
| character_set_database   | utf8mb4                                              |
| character_set_filesystem | binary                                               |
| character_set_results    | utf8mb4                                              |
| character_set_server     | utf8mb4                                              |
| character_set_system     | utf8                                                 |
| character_sets_dir       | /usr/local/Cellar/mysql/5.5.10/share/mysql/charsets/ |
+--------------------------+------------------------------------------------------+

为了支持之前用utf8创建的表同样支持emoji,你还需要将之前的表修改未utf8mb4字符集

alter table posts convert to character set utf8mb4 collate utf8mb4_unicode_ci;

rails支持emoji(utf8mb4)

rails对utf8bm4的支持主要通过sql adapter实现,mysql2的0.3.13版本已经支持了utf8mb4 encoding。主要在配置(database.yml)的时候加上以下配置即可

charset: utf8mb4
encoding: utf8mb4
collation: utf8mb4_unicode_ci

在做完上述工作之后你的rails其实已经支持了utf8mb4的存储与读取,但为了与之前的代码兼容,你还需要做下面的事情

处理MySQL index 767个字节的限制

因为mysql对index是有767个字节的限制的,在我们默认使用utf8编码的时候,如果我们定义如下的结构和index,是不会有问题的

add_column :users, :name, :string
add_index :users, :name

因为utf8最多占3个字节,而rails中创建string类型的字段时,默认用的是varchar(255)这样的数据库结构,所以255*3=765,没有超出767的限制。但我们把字符集改成了utf8mb4,所以一个字最多可以占用4个字节,那255*4就会超出767的字符限制了,所以我们没有办法使用上述的方式来创建字段和index了。

所以我们应该限定name的长度

add_column :users, :name, :string, :limit => 100
add_index :users, :name

但对于之前的系统,如果我们全部修改string类型的index,会是一个很大的工作量(主要是在正式服务器上的耗费时间会很长),所以我们可以有个折中的方案,只修改index的长度限制。

add_index :entries, [:user_id, :url],
  unique: true, length: { url: (191-4) }

所以你需要找遍所有的migration文件,通通加上长度的限制,这样才能避免一个新同学可以通过rake db:migrate

除此之外,你还需要修改rails系统的schema_migrations_table表的结构,打开activerecord(bundle open activerecord),修改lib/active_record/connection_adapters/abstract/schema_statements.rb的419行,换成下面的语句。

schema_migrations_table.column :version, :string, :null => false, :limit => 15

或者你可以像我一样,打个补丁。将以下内容写入config/initializers/active_record_schema_migrations_version.rb文件

require 'active_support/core_ext/array/wrap'
require 'active_support/deprecation/reporting'

module ActiveRecord
  module ConnectionAdapters # :nodoc:
    module SchemaStatements
            # Should not be called normally, but this operation is non-destructive.
      # The migrations module handles this automatically.
      def initialize_schema_migrations_table
        sm_table = ActiveRecord::Migrator.schema_migrations_table_name

        unless table_exists?(sm_table)
          create_table(sm_table, :id => false) do |schema_migrations_table|
            schema_migrations_table.column :version, :string, :null => false, :limit => 15
          end
          add_index sm_table, :version, :unique => true,
            :name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"

          # Backwards-compatibility: if we find schema_info, assume we've
          # migrated up to that point:
          si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix

          if table_exists?(si_table)
            ActiveSupport::Deprecation.warn "Usage of the schema table `#{si_table}` is deprecated. Please switch to using `schema_migrations` table"

            old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i
            assume_migrated_upto_version(old_version)
            drop_table(si_table)
          end
        end
      end
    end
  end
end

修改JSON gem保障utf8mb4信息不丢失

Emoji and Rails JSON output issue文章描述,在未对json库做补丁之前,JSON会丢失掉一些信息。(我测试的结果是,未打补丁之前,确实emoji无法正确显示)

>> JSON({:a => "\360\237\230\204"}.to_json)
=> {"a"=>"\357\230\204"}

所以,针对这个问题的解决方案就是,把以下内容写入config/initializers/active_support_encoding.rb文件

module ActiveSupport::JSON::Encoding
    class << self
        def escape(string)
            if string.respond_to?(:force_encoding)
                string = string.encode(::Encoding::UTF_8, :undef => :replace).force_encoding(::Encoding::BINARY)
            end
            json = string.gsub(escape_regex) { |s| ESCAPED_CHARS[s] }
            json = %("#{json}")
            json.force_encoding(::Encoding::UTF_8) if json.respond_to?(:force_encoding)
            json
        end
    end
end

这样做完之后,你就可以支持emoji在json格式中的传输了。

Rails 3.2でiOS5の絵文字を扱う还给了另外一个解决办法,

module ActiveSupport
    module JSON
        module Encoding
          class << self
            def escape_with_json_gem(string)
              ::JSON.generate([string])[1..-2]
            end
            alias_method_chain :escape, :json_gem
          end
        end
    end
end

但据我测试,如果在不使用resque的时候,但在使用resque的时候,就会出现死循环的问题了。导致resque无法启动。如果你也使用了resque,你可以在escape_with_json_gem里面加入puts信息,来验证我说的话。很有可能是resque对JSON库的generate做了一些改动(我看了resque的代码,没有发现相关的细节,但resque确实动过JSON gem),导致escape与generate循环调用了。因为这个原因,我们使用这种办法来做JSON的补丁。

添加测试保证对utf8mb4的支持

为了确保我们上述的工作已经很好地支持了emoji,你最好可以在你的测试中加入类似的测试用例,来确保这一点。

  test "post emoji comment create" do
    post = FactoryGirl.create(:post)
    post :create, :format => :json, :comment => {:content => "😔"}
    assert_response_result(true)
    #测试json是否正确
    assert_equal "😔", @response_json["content"]
    #测试数据库是否正确保存与读取
    assert_equal "😔", Comments.first.content
  end

参考资料