过去24小时你有一条支付小米6最新消息1小时前未处理,我要删除群聊天的时候,出现了这,是什么意思啊?求解

迷思迷思我的博客及微信公众号里的精华内容都会放在这里。关注专栏陈天 的文章{&debug&:false,&apiRoot&:&&,&paySDK&:&https:\u002F\\u002Fapi\u002Fjs&,&wechatConfigAPI&:&\u002Fapi\u002Fwechat\u002Fjssdkconfig&,&name&:&production&,&instance&:&column&,&tokens&:{&X-XSRF-TOKEN&:null,&X-UDID&:null,&Authorization&:&oauth c3cef7c66aa9e6a1e3160e20&}}{&database&:{&Post&:{&&:{&title&:&Let it crash: 因为误解,所以瞎说&,&author&:&tchen&,&content&:&\u003Cp\u003E今天我知乎的时间线上反复出现了一个流毒甚广的帖子:「\u003Ca href=\&https:\u002F\\u002Fquestion\u002F\& data-editable=\&true\& data-title=\&应该如何理解Erlang的“就让它崩溃”思想? - 编程 - 知乎\&\u003E应该如何理解Erlang的“就让它崩溃”思想? - 编程 - 知乎\u003C\u002Fa\u003E」,十几个不懂装懂的回答,赞竟然都不少。\u003C\u002Fp\u003E\u003Cp\u003E严格意义上来说,我之于 erlang,也是个半吊子,到目前为止,还没有写过真正的在生产环境中使用的 erlang 代码。不过我倒是写了两千行在生产环境中使用的 elixir 代码,还有几千行将要被应用在生产环境中,所以自认为对 elixir 算是略懂一二。由于在 VM 层面和语言核心层面,elixir 和 erlang 一脉相承,所以我也对 let it crash 也算略懂。今天,咱们就谈谈 let it crash。\u003C\u002Fp\u003E\u003Cp\u003E什么是 let it crash?\u003C\u002Fp\u003E\u003Cp\u003E在 erlang 里,let it crash 是指程序员不必过分担心未知的错误,而进行面面俱到的 defensive coding。相反,当这种错误来临时,任由错误所处的上下文 —— 一般是某个 process —— 崩溃退出。当 process 退出后,它会将这种状态汇报给 monitor process,由其决定如何来进一步处理这个错误。\u003C\u002Fp\u003E\u003Cp\u003E举一个不那么恰当的例子:你写了一个 gen_server 处理支付的状态机,其中要用到第三方的支付服务。第三方的支付服务有几个返回状态:要么超时(timeout),要么支付成功(suceed)或者失败(fail),有一天,第三方支付服务出问题了,返回一个本不该返回的状态:busy。你的 server 不知道如何处理这种状态,于是整个 process 便崩溃退出,接着,这个 process 的 monitor process,一般是个 supervisor process,获得通知,然后根据预定义的策略,重启了这个 server process。整个过程发生在极短的时间内(微秒以内),以至于外界根本感知不到服务发生过崩溃。\u003C\u002Fp\u003E\u003Cp\u003ELet it crash 是 erlang 的专利么?\u003C\u002Fp\u003E\u003Cp\u003E不好意思,这个只能打脸某些想当然的回答了,是的,确定一定以及肯定。这个思想也许不是 erlang 最先提出的,但只有 erlang VM 真正让程序员可以放心地 let it crash。其他语言和框架,对不起,没有这个能力。Java VM 的 akka 是对此最接近的模仿,然而,由于在这个场景下 JVM 天然的劣势(毕竟不是围绕着 let it crash 设计的),akka 只是接近。\u003C\u002Fp\u003E\u003Cp\u003E不少人错误地认为 let it crash 是让程序崩溃,然后靠 systemd \u002F supervisord \u002F monit 这样的工具去做崩溃后的恢复 —— 这是对 let it crash 思想的一种亵渎。我看到有的答案信心满满地瞎扯,你看我的项目多进程 + 共享内存 \u002F memcached 也能 let it crash。真是一派胡言!你这么任性地 let it crash 试试,你老板不把你开了才怪。进程级别的监控和重启往往是数百毫秒甚至秒级才能完成的事情,在高并发场景下,一秒的宕机就可能意味着成百上千次的请求被耽误而得不到处理。耽误请求这还算是小事,黑客试上一些输入的 pattern,发现你的服务用特定的输入就能延迟响应的时间(因为 crash 重启了),那么 DOS \u002F DDOS 简单了,每秒钟给你发上千个特定的请求,让你的服务不停地重启,啥正事也别干了。\u003C\u002Fp\u003E\u003Cp\u003E为什么实现 let it crash 很困难?\u003C\u002Fp\u003E\u003Cp\u003Eerlang VM 为了支持 let it crash,即便够不上呕心沥血,也对得起殚精竭虑四个字了。我们看如果你要让代码可以不做 defensive coding 系统还能很好地工作(不 crash),要做哪些事情:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cp\u003E错误必须隔离 —— 一处处理过程中的错误不会影响和传播到其他地方,并且这个受到影响的上下文,越小越好\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E错误必须有人处理 —— 之前是程序员通过 defensive coding 处理,现在要靠一套合适的系统来处理\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E错误恢复的速度必须够快 —— 你隔离了,处理了,但处理的速度像前面说的那样,很慢,也没有实际的意义\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E我们挨个看。\u003C\u002Fp\u003E\u003Cp\u003E错误隔离\u003C\u002Fp\u003E\u003Cp\u003Eerlang VM 提供了以下手段 共同 完成了错误隔离:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cp\u003E使用 actor model,每个代码运行的上下文都是一个单独的 process。process 是一个非常细粒度的运行时,可以把它想象成是组成一个系统的细胞。在数种并发模型(threading,CSP,actor model)中,actor model 是隔离性最好的并发模型。关于 actor model 和其他 concurrency model,见我的文章:\u003Ca href=\&http:\u002F\u002Fmp.\u002Fs?__biz=MzA3NDM0ODQwMw==&mid=&idx=1&sn=b4b3a8fe51eb3580f1dd&scene=21#wechat_redirect\& data-editable=\&true\& data-title=\&Concurrency\&\u003EConcurrency\u003C\u002Fa\u003E。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E引入 immutable data。数据无法改变,也就截断了被多个上下文共享的可能性,因此,process A 读取的数据坏了,不会波及到 process B(我们先把 ets table 和 database 放在一边不谈),错误隔离进一步得到保证。这一点很重要,像某个答案提及的多进程 + 共享内存 \u002F memcached,如果共享的数据坏了,你 let it crash 一百万回,它还是读着坏数据,不 defensive coding 根本解决不了问题。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E这二者缺一不可。有个答案说重点是 immutable data,光靠 immutable data,出现问题,crash 一个线程,甚至一个进程,也受不了。\u003C\u002Fp\u003E\u003Cp\u003E错误处理\u003C\u002Fp\u003E\u003Cp\u003E不要天真地以为 let it crash 就不需要处理错误了。只不过,错误的处理有「人」帮你抗了。\u003C\u002Fp\u003E\u003Cp\u003E在让「别人」帮你扛下这个错误之前,你先得有机制把这个错误通知出去。\u003C\u002Fp\u003E\u003Cp\u003E在 erlang 里,这是通过 link \u002F monitor 来完成的。简言之,一个 process 在启动时,和一个已有的 process link 起来,这样,其中的一方 crash 了,另一方得到通知,然后进行必要的处理。\u003C\u002Fp\u003E\u003Cp\u003Eerlang 里的 OTP framework 进一步将这个模式封装成 supervisor —— 也就是有些 process 它不干别的,只管理别的 process —— 这跟你公司里的 people manager 一个样,所以被形象地称为 supervisor。因为 people manager 不用写代码,所以他不会出错,更不会因为好奇而去写些代码抢月饼,所以也不会被 terminate,他只管一件事:有人被 terminate 了,赶紧再招一个新人补上这个坑。啊,好像跑偏了。supervisor process 不干具体的活,活都让 worker process 干了,worker process 干得多,所以犯错的几率也高,有些情况没处理好 biu 的一下挂了,supervisor 得到通知,马上新启动一个 worker process 顶上去。\u003C\u002Fp\u003E\u003Cp\u003E所以 let it crash 不是不处理错误,只不过它处理错误的方式是通过监控发现崩溃的 process,然后按照一定的策略处理之。\u003C\u002Fp\u003E\u003Cp\u003E处理的策略有好几种:one_for_one。一个人挂了,再招一个顶上来;one_for_all,一个人挂了,我对整个 team 都不放心,把整个 team 都 lay off,然后全数招新人找回来补齐空位;还有一种是 rest_for_one,一个 team 有五个人,一二三四五,三号员工挂了,我把四号五号也 lay off,然后再招三个回来。\u003C\u002Fp\u003E\u003Cp\u003E通过这样的组织方式,我们可以把系统组织成严格的层级关系,形成一个 supervision tree,这个 tree 很像一家公司的组织结构图。若干 worker 由 manager 带着,若干 manager 由 director 管理着,若干 director 再汇报给 VP,VP 上面是 EVP,每个 EVP 负责一个 BU,并悉数听命于一个叫 CEO 的家伙。底下的人来人往并不影响公司的生死存亡(小小 engineer 走了都没人感知的到),即便是一个 BU 出了大问题,把 BU 砍了,EVP 走人,下面悉数散伙,资源重新分配,新的 BU 又被组建起来,对公司来说,影响大些,但也并非致命。\u003C\u002Fp\u003E\u003Cp\u003E所以 erlang 做的系统,如果组织得当,是很难 crash 的。前面说的错误隔离,加上这里的树状结构的错误处理方式,让错误的影响最小化。\u003C\u002Fp\u003E\u003Cp\u003E错误恢复\u003C\u002Fp\u003E\u003Cp\u003Eprocess 的重启是要花时间的。对 erlang 来说,这个时间足够小以至于你大部分时间可以不在意。一个不保存任何状态的 erlang process,启动后只有几百字节的 memory footprint,一段执行函数,和在 scheduler 里注册的 process 的信息(以便于调度)。在 erlang 下,spawn 一个 process 跟执行一个稍稍复杂的函数的速度几乎是一个量级的,所以你才有机会 let it crash —— 因为 restart 瞬间完成。\u003C\u002Fp\u003E\u003Cp\u003E此外,对于无状态(stateless)的 process,restart 的代价几乎为零;更多的时候,process 是有状态的,重启意味着状态的重新初始化(从持久化存储中调出状态),有时候还可能丢失一部分未持久化的数据。\u003C\u002Fp\u003E\u003Cp\u003E举个例子。你有一个用户服务。每个活跃用户你用一个 process 来追踪处理其实时状态(游戏经常这么干),在特定的时间间隔内将状态持久化(或者做 oplog)。如果某个用户的 process 挂了,重启这个用户的 process 涉及到重新读取用户的状态,对于未持久化的数据,则有可能丢失。\u003C\u002Fp\u003E\u003Cp\u003E我们前文说过,erlang 整个语言和 VM 都围绕着 let it crash 设计,所以也考虑着这种场景:它有 ets 这样的 in-memory store 来保存数据,使得 process crash 也不至于丢失实时的数据。你可以使用一个 process 来「管理」 ets table(设置这个 process 为 heir),当 worker 启动时,把 table 移交给 worker,当 worker crash 时,table 的 ownership 被还回给这个 process。\u003C\u002Fp\u003E\u003Cp\u003E你看,let it crash 粗狂的外表下还有这样精致的细节!这是 erlang \u002F OTP 整个体系漫长的发展过程中逐渐完善出来的;是将 let it crash 真正作为一种思想后,在各种问题,各种需求的催化下,衍生出来的功能。akka 没有 ets,也没有类似的权限管理的能力,所以上文我说 akka 只是接近。\u003C\u002Fp\u003E\u003Cp\u003Eets table 的 heir 和 give away 机制保证了数据访问者的唯一性(同样也是为了隔离问题)。这是 memcached \u002F redis 等方案无法媲美的。在 redis 里,设计再精妙的隔离也架不住软件上的一个 bug,写了不该写的 key,导致别人访问了坏掉的数据,进而让整个系统 crash。\u003C\u002Fp\u003E\u003Cp\u003Eheir \u002F give away 看着眼熟?对!rust 的 memory 的 lifecycle 里的 ownership \u002F borrow 机制和它有异曲同工之妙。\u003C\u002Fp\u003E\u003Cp\u003E不要滥用 let it crash\u003C\u002Fp\u003E\u003Cp\u003E尽管 let it crash 有诸多好处,我们也不该毫无节制地使用它。孔子他老人家说:\u003Cstrong\u003E过犹不及\u003C\u002Fstrong\u003E。满汉全席天天吃也会恶心,再好的思想,不该用的时候还用,也会「樯橹灰飞烟灭」。\u003C\u002Fp\u003E\u003Cp\u003E为啥?let it crash 的目标是 fault tolerance,auto healing,是系统能够自我治愈,新陈代谢。let it crash 只是实现这个目标的手段。\u003C\u002Fp\u003E\u003Cp\u003E此外,我们要清醒地认识到,尽管代价很低,let it crash 是有代价的 —— 你任由 2 million 的 process crash 然后重启看看?\u003C\u002Fp\u003E\u003Cp\u003E那么,哪些情况是不该使用 let it crash 的?\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cp\u003E\u003Cstrong\u003E来自外界的输入不符合你的预期\u003C\u002Fstrong\u003E。软件系统需要把外部世界和内部世界隔离。这个隔离可以通过数据的转换,清洗来完成,而不是一言不合就 crash。你想想看,写个 API,当调用者使用了错误的参数,你是返回 400 bad request 并辅以提示信息好呢,还是 crash 掉返回 500 internal error 好呢?\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E\u003Cstrong\u003E预知的错误\u003C\u002Fstrong\u003E。函数该使用 guard 不用,pattern match 该提供一个 match all 不提供,读文件不先判断文件是否存在就去读,这些你写代码就应当考虑清楚的事情不去做,反而美其名曰 let it crash,会气煞 Joe Armstrong 他老人家滴。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E\u003Cstrong\u003E不可恢复的上下文尽量不要 let it crash\u003C\u002Fstrong\u003E。典型的不可恢复的上下文有 tcp connection。如果不是严重的问题,你莫名 crash 掉一个正在处理当前 connection 的 process,这个 connection 后面的用户可能就永远不会来了。你想想,你会使用一个在你通话的时候随时自动挂线的语音聊天软件么?\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E&,&updated&:new Date(&T06:05:45.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:43,&likeCount&:287,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T14:05:45+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\\u002Fv2-36b7ad32c1cb793cd070c676d7658230_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:43,&likesCount&:287},&&:{&title&:&北美互联网哀鸿遍野 - 号称99.99%可用性的S3挂了&,&author&:&tchen&,&content&:&\u003Ch2\u003E事件回顾\u003C\u002Fh2\u003E\u003Cp\u003E美西太平洋时间早上 10 点(北京时间凌晨 2 点),AWS S3 开始出现异常。很多创业公司的技术人员发现他们的服务无法正常上传或者下载文件。有人在 hacker news 上问:Is S3 down? 然后迅速得到大伙的确认。\u003C\u002Fp\u003E\u003Cp\u003E然而,AWS 自己的 status page (https:\u002F\u002Fstatus.) 却后知后觉,放眼望去,一片让人喜滋滋的绿油油。就在大伙儿以为自己神经过敏,一切都是虚妄的猜测时,AWS 的工程师惊悚地发现,由于这个页面依赖于 S3,所以它实际上也挂了,于是赶紧放了个 banner 上去说明状况,然后在 twitter 上昭告天下绿油油是假象:\u003C\u002Fp\u003E\u003Cimg src=\&v2-46c0f55b73fcab6c8067.jpg\& data-rawwidth=\&1180\& data-rawheight=\&336\&\u003E\u003Cp\u003E11:35am,经过一番努力,这个页面总算显示正常的状态了:\u003C\u002Fp\u003E\u003Cimg src=\&v2-8a94dfe8af031d.jpg\& data-rawwidth=\&1802\& data-rawheight=\&1786\&\u003E\u003Cbr\u003E\u003Cp\u003E可以看到,重灾区是 North Virginia 的 S3。由于 S3 不工作,那些高度依赖 S3 的服务,比如 Elastic Map Reduce(需要 S3 存储中间过程和结果),以及去年 re:invent 刚发布的 Athena(查询的数据要放在 S3 上),也完全挂掉。依赖 S3 不那么重的服务,状态也不是太好。\u003C\u002Fp\u003E\u003Cp\u003ES3 是 AWS 最早发布的云服务,simple storage service,解决存储的问题。存储是任何互联网服务的基石 —— 只要有大的数据对象,无论是图片,视频还是文本,我们都需要一个合适的存储方案保存它们。在没有云的日子里,这些内容要么存储在无比昂贵的 SAN (Storage Area Network) 中,要么存储在大量 PC 服务器的磁盘阵列中,通过一些特殊的文件系统,如 HDFS 来访问。为了维护这些数据的持久性和可用性,互联网公司需要在这样的基础设施上花费巨大的人力物力,无法集中所有的工程能力处理业务。当 S3 和类似 S3 的服务诞生后,对于很多初创的互联网公司,简直是久旱逢甘霖,99.99999% 的持久性(durability),和 99.99% 的可用性(availability)爽的不能再爽,于是纷纷把自个的存储架构布在了 S3 上。时至今日,使用 S3 的网站,已经多达 148, 213 个(数据来自 techrunch)。\u003C\u002Fp\u003E\u003Cp\u003E所以,当今早 S3(主要是 North Virginia)宕机时,整个北美的互联网呈现一片哀魂遍野的景象。\u003C\u002Fp\u003E\u003Cp\u003ESlack 无法上传文件,进度条永远在走:\u003C\u002Fp\u003E\u003Cimg src=\&v2-d8eb326d7bc273c9e88d4702cf1afdb6.jpg\& data-rawwidth=\&1518\& data-rawheight=\&410\&\u003E\u003Cbr\u003E\u003Cp\u003ETrello 表示老子都被收购了,休息,休息一会也无妨:\u003C\u002Fp\u003E\u003Cimg src=\&v2-126fe7ebfdd84f5ae7651.jpg\& data-rawwidth=\&1370\& data-rawheight=\&982\&\u003E\u003Cbr\u003E\u003Cp\u003E收购了 Trello 的 Atlassian 也不遑多让,文案好一本正经扑克脸(我都怀疑他们的工程师发现问题了么):\u003C\u002Fp\u003E\u003Cimg src=\&v2-10fddf75f136.jpg\& data-rawwidth=\&1702\& data-rawheight=\&1058\&\u003E\u003Cbr\u003E\u003Cbr\u003E\u003Cp\u003E最近 VC 的宠儿 giffy,表面上一切正常(CDN 扛起了 gif 的下载),但如果你要上传 gif,对不起,偶们也不知道发生了神马事情:\u003C\u002Fp\u003E\u003Cimg src=\&v2-f4ea0e61562b88eff30c9dbe911dfa48.jpg\& data-rawwidth=\&2192\& data-rawheight=\&1290\&\u003E\u003Cbr\u003E\u003Cp\u003E至于高冷的 quora,干脆连个暖心的页面都不给,直接说,老子不玩了:\u003C\u002Fp\u003E\u003Cimg src=\&v2-46bd78d13fba82c9ed6b3501bba22992.jpg\& data-rawwidth=\&1148\& data-rawheight=\&472\&\u003E\u003Cbr\u003E\u003Cp\u003E。。。\u003C\u002Fp\u003E\u003Cp\u003E照理来说像 quora 这样的服务,面向用户阅读的部分本不该高度依赖 S3,要挂也不该全站挂,顶多是挂用户撰写答案的部分,不知道为何死的这么彻底。\u003C\u002Fp\u003E\u003Cp\u003E我们看看当问题出现时,一个普通的 S3 GET 返回什么:\u003C\u002Fp\u003E\u003Cimg src=\&v2-bb1dafa9def6c65b02bd008.jpg\& data-rawwidth=\&1366\& data-rawheight=\&332\&\u003E\u003Cbr\u003E\u003Cp\u003EAWS 赤果果地告诉你,Internal Error 了。从 error handling 的角度,我们在写代码的时候都应该捕捉这个异常,然后做合适的错误处理。很遗憾的是,S3 这样的服务是如此基础,就像互联网的水和电一样,大家默认为它永远不会出错。因此,好多工程师干脆不做错误处理,像 slack 那样,任由进度条一直傻乎乎地跑;或者,让错误一路冒泡,直到把整个服务挂掉了事,像 quora \u002F trello 那样。这样对用户都不太友好。\u003C\u002Fp\u003E\u003Cp\u003EMurphy 定律告诉我们,凡事可能发生,就将要发生。所以比较好的处理方法是,捕捉到异常,让错误只局限在特定的页面,如:atlassian \u002F giffy。或者,有个 plan B 应对突发事件。\u003C\u002Fp\u003E\u003Ch2\u003E使用 S3 的用户如何自救?\u003C\u002Fh2\u003E\u003Cp\u003E类似的事情发生在任何公司上都是不幸的,尤其是给客户以 SLA 保障的 SAAS 公司。大家能做得就是:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cp\u003E当云服务商的宕机发生时,尽量控制它影响面。像 trello 这样连 landing page 都一并挂掉实在不可取,起码 S3 影响不到的页面,如 landing page,用户注册 \u002F 登录页面,应该还保持正常服务;而像 quora 这样的服务,其实是可以准备一个静态化的镜像,一旦出问题,起码让读者可以无障碍地阅读。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E尽可能地把动态内容缓存起来,甚至静态化。redis cache,nginx cache,HA proxy,CDN 都是把内容缓存甚至静态化的一些手段。尽管多级缓存维护起来是个麻烦,但当底层服务出现问题时,它们就是难得的战略缓冲区。cache 为你争取到的半个小时到几个小时几乎是续命的灵芝,它能帮你撑过最艰难的时刻(这次 S3 宕机前后大概 4 小时,最严重的时候是 11点到1点),相对从容地寻找解决方案,紧急发布新的页面,或者迁移服务,把损失降到最低。否则,只能像这次事件中的诸多公司一样,听天由命,双手合十祈祷 aws 的工程师给力些解决问题。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E使用 simian army 在平日里操练系统的容错性。这个适合大一些的,工程团队有余力的公司。netflix 重度使用 aws,却在历次 aws 的宕机中毫发无损,是因为他们之前也深深地被云的「不稳定性」刺痛过。他们的 chaos monkey(之后发展为 simian army)服务,会随时随地模拟各种宕机情况,扰乱生产环境。比如说对于此次事件的演练,你可以配置 simian army 去扰乱 S3:simianarmy.chaos.fails3.enabled = true。这样,这群讨厌的猴子就会在你不知情的情况下随机把你的服务器的 \u002Fetc\u002Fhosts 改掉,让所有的 S3 API 不可用。这样你就可以体验平时很难遇到的 S3 不可访问的场景,进而找到相应的对策(注意:请在 staging 环境下谨慎尝试,否则老板把你开了不要赖程序君)。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E如果 AWS 真的发生大规模宕机,而你又没有采取任何措施,天也不一定就塌下来了。此时此刻,你的投资人,你的客户,你的合作伙伴也许都忙着解决他们各自的宕机问题呢,hacker news 上(https:\u002F\\u002Fitem?id=)有个笑话这么说:\u003C\u002Fp\u003E\u003Cblockquote\u003E\u003Cp\u003EWhy do we host on AWS?\u003C\u002Fp\u003E\u003Cp\u003EBecause if it goes down then our customers are so busy worried about themselves being down that they don't even notice that we're down!\u003C\u002Fp\u003E\u003C\u002Fblockquote\u003E\u003Cp\u003E看,这就是 CIO \u002F CTO 们的狡黠之处(自建的出了问题都得自己擦屁股)。\u003C\u002Fp\u003E\u003Ch2\u003E如何利用这样的宕机机会?\u003C\u002Fh2\u003E\u003Cp\u003EGoogle 的工程师忙不迭地过来补刀加教育用户:\u003C\u002Fp\u003E\u003Cimg src=\&v2-bf0cb653e0fdefd2fc7fa4ff64b2dfc2.jpg\& data-rawwidth=\&3118\& data-rawheight=\&424\&\u003E\u003Cbr\u003E\u003Cp\u003E你看,这个社会就是这么群狼环饲。你别说不努力了,你努力着,但只要摔上一跤,就有猛兽过来蹭肉吃。对于甲方来说,狼越多选择越多,开心都来不及;作为乙方,则欲哭无泪。这次事故,我们作为乙方,看看热闹。但要知道,每家公司,甚至每个人,都在不同的上下文中扮演不同的角色,一会是甲方,一会是乙方。看热闹娃哈哈时,不要忘了有一天自己也可能遇到相同的境地,被自己的客户放在火上烤。\u003C\u002Fp\u003E\u003Cp\u003E什么?你问 Tubi TV 宕没宕机?虽然我们有我们操蛋的烦恼,但是托 CDN 的福,在过去的几个小时,我们好好的。\u003C\u002Fp\u003E&,&updated&:new Date(&T05:25:10.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:29,&likeCount&:417,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:true,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T13:25:10+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\\u002Fv2-0dd4c1ebfb955cff1ecb7_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:29,&likesCount&:417},&&:{&title&:&来自硅谷的互联网企业是一个好的选择么?&,&author&:&tchen&,&content&:&\u003Cp\u003E过去的一周多每天戴着耳机,对邮箱里看得上眼的简历电话筛选。作为一个「内向」的,不那么喜欢电话沟通的人,我从未这么集中地打过电话 —— 我的舌肌和咬肌得到了前所未有地锻炼,仿佛这辈子要煲的电话粥,说的情话,都得在短短几日里完成。\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E很多招聘者对待面试是一个居高临下的态度,仿佛一份工作机会是一份给予,是施舍,是「有个工作就不错了你还那么矫情究竟想怎样爱来来不来滚」(这是我在公司所在的众创空间亲耳听到的某个团队的对话)。我(以及我呆过的主要公司:tubitv,途客圈,Juniper)对此都持完全相反的态度。公司和面试者的地位是平等的,更准确地说,面试者的地位要高一些。公司追求人才,应该像小伙子追求心仪的姑娘一样,低眉顺目,孜孜以求。这一点,在第一个电话,第一封 on-site interview 的邮件,第一次接触中应该体现出来。也许我做的还算不错,前两天电话时,一个面试者羞涩地问我:请问您是 HR 么?\u003C\u002Fp\u003E\u003Cp\u003E我一下子愣了。就我做的微不足道的工作而言,世界上还有比这更美妙的认可么?\u003C\u002Fp\u003E\u003Cp\u003E言归正传。对于我们,Tubi TV,很多面试者很关心的一个问题,在面试中通过各种显露的,或者隐晦的问题,反复在确认一件事:一家来自硅谷的互联网创业公司,会是我的一个好的选择么?\u003C\u002Fp\u003E\u003Cp\u003E今天我来回答这个问题。尽管「屁股决定舌头」,我还是会尽量不偏不倚。\u003C\u002Fp\u003E\u003Cp\u003E硅谷公司有什么优势?\u003C\u002Fp\u003E\u003Cp\u003E在如今互联网行业一日千里的背景下,传统外企从之前高大上的金领行业跌入了凡间,无论是成长机会,工资福利都纷纷给国内的互联网企业让位,更有甚者,由于全球经济的低迷和中国企业,新兴企业全方位地抢班夺权,很多外企,如 adobe,yahoo,hp,IBM,oracle 纷纷在退守,甚至让出半壁江山。而互联网外企,由于不可描述的原因,无法在国内展开业务,因此也鲜有研发业务。微软(包括 linkedIn),amazon,hulu,还有尚在残喘的 google 中国,是为数不多的在国内建立研发中心的知名互联网外企。\u003C\u002Fp\u003E\u003Cp\u003E大多数互联网外企传承硅谷的企业文化:宽松灵活的工作方式,浓厚的工程师文化,人文关怀(提倡 work-life balance),结果导向等等。很多外企还提供一些隐性的,并未放入员工手册中的福利:锻炼英语的机会,开拓视野的机会,去硅谷或者西雅图出差的机会,以及,很多人都关心的,肉身翻墙的机会。这些,我来挨个撸一撸。\u003C\u002Fp\u003E\u003Cp\u003E工程师文化\u003C\u002Fp\u003E\u003Cp\u003E先说工程师文化。硅谷公司的工程师文化氛围之浓,全球无人出其右。这体现在几个方面:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cp\u003E开放。公司的核心信息和指标基本上全员都可以获取到(至少在初创阶段)。在 Tubi TV,我们所有的 dashboard,包括 DAU,session,retention,用户数,广告机会,当前的CPM 等等指标都有详细的图表,并且每个员工想看的时候都能看到实时的结果。为了让大家有更多的关于公司状态的感觉(sense),我们还在就餐区的大电视上滚动显示当下的核心指标。开放意味着信任,这也是我之前文章里(\u003Ca href=\&http:\u002F\u002Fmp.\u002Fs?__biz=MzA3NDM0ODQwMw==&mid=&idx=1&sn=e33def1e9bcdb2fc46c2cf68dab187ce&chksm=edd3738fed2bee4051a68eec4dba278b2f61e92711f0&scene=21#wechat_redirect\& data-editable=\&true\& data-title=\&新的冒险\&\u003E新的冒险\u003C\u002Fa\u003E)所说的 trust over control。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E结果导向。一切工作以结果论英雄。当数据人人都唾手可得时,各种方案的优劣是可以通过数据来证明的。如果数据证明老板是错的,那么就可以推翻老板的方案。这是所谓的 data over authority。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E为员工提供持续成长的机会。在 coursera \u002F udemy 上上一门课,平时去参加个会,购买技术书籍材料,只要合乎情理,没问题,公司报销;更有甚者,你去大学进修,如果修到了学分,公司也能报销(一般大公司会有 $5000 每人每年的上限,足够去 stanford 修一门课程了)。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E全公司上下对工程师的尊敬。拿 Tubi TV 来说,我们的商业模型是帮助好莱坞的影视工作室变现他们的长尾内容:我们通过为用户提供可以免费观看的影视剧(这些剧我们零成本从好莱坞签下),在用户观看的过程中通过插播广告来变现,和好莱坞的工作室们共享收益。这样一个业务,我们的核心竞争力在于我们和好莱坞建立起来的长期合作关系,而非我们的技术或者产品;我们变现的能力有赖于销售拉广告客户的水平(至少在初期)。在我加入之前,我们的技术烂的一塌糊涂,但公司商业上很成功,收入不少。创始人并没有因此仅仅把技术当成是实现商业目标的一个工具,一种手段,而是坚信公司的上限取决于我们的技术能力,不遗余力在各种场合向大家灌输技术的重要性,工程师的重要性。在公司自由现金流紧张,不得不砍掉招人名额(不是砍人)时,最为昂贵的技术职位却总是被努力保留下来,技术团队的招聘基本上是不间断的。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E人文关怀\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E相对于国内公司追求高速发展,对员工不遗余力地使用,硅谷的公司对人的关怀是值得称道的。这里面最重要的思想是:work-life balance,也就是所谓的工作生活的平衡。\u003C\u002Fp\u003E\u003Cp\u003E二十岁的时候,我们并太不追求工作生活的平衡。除了偶尔的把妹撩汉外,工作可以是生活的全部。\u003C\u002Fp\u003E\u003Cp\u003E二十五岁到三十岁,这会成为一个不得不考虑的问题。大多数人会在这个阶段成家,所谓成家立业,基本是先有家后有业(有个理论是:male marriage wage premium)。一旦有了家庭,就有了牵挂,有了责任,需要挤出很多时间照顾家庭。\u003C\u002Fp\u003E\u003Cp\u003E三十岁后,有了孩子,添了更多的快乐,也会有更多的烦恼。孩子是需要父母陪伴的,对她们而言,即便物质上富足,没有陪伴的童年(少年)是不幸的。\u003C\u002Fp\u003E\u003Cp\u003E然而,对于绝大多数工程师而言,三十岁还难言财富自由。即便财富自由,也很难「人身自由」。国内互联网公司倡导的996,生生地把工作和生活对立起来。我有一对已不算年轻的工程师夫妇朋友,工作了快十年,两人一年收入加起来一百多万,算是同龄人中的佼佼者,但为此付出的惨痛代价是他们每天见面的时间多在半夜十一二点;周末,总凑不上时间,一起手牵着手看场电影都是奢望。在这种工作压力下,如果没有女方付出牺牲,甚至连要孩子都几乎是不可能的事情。有次大家见面,聊到这个沉重的话题,我嘴贱说了句:我怎么觉得你们这生活,像是凑合着对食。。。对方一时语塞。\u003C\u002Fp\u003E\u003Cp\u003E硅谷的企业不提倡这样的工作方式,它有几个特点:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cp\u003E灵活的工作时间。没有上下班打卡,也没有996,更不会要求你成为奋斗者。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E可以在需要的时候「在家办公」。家里有点事,或者天气糟糕,或者得了点小病,不用浪费宝贵的年假,跟老板同事说一声,在家办公即可。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E几乎无障碍的休假方式。普通员工如有度假计划,提前通知老板就好,做好项目的交接,即可飞到鸟不拉屎,手机没有信号,网络无法覆盖的地方浪。一两周,两三周,老板都不太会有异议。有时候,思乡病犯了,假却不够了(比如想回家一个月,却只剩两周假),可以先有多少休多少,再 work from hometown 个两周,飞回家乡,病治好了,再回来好好干活。在 Tubi TV,我告诉北京的同事,过年前后可以 work from hometown,省得和恐怖的春运对对撞。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E这其实是对人最大的关怀和信任。这样的制度在不被滥用的情况下,会大大提高工作的满意度。\u003C\u002Fp\u003E\u003Cp\u003E隐性福利\u003C\u002Fp\u003E\u003Cp\u003E有些福利,是看不见摸不着,但是对职业生涯很有帮助的。比如说英文。在外企里面,由于有一个良好的英文环境,不出半年,一个工程师的英文的听说读写的能力会得到飞跃提升。我们知道,工程师这个职业,少不了和英文打交道 —— 很多第一手的资料(在线文档,图书)都是英文,第一手的问答信息也以英文为主。我们笑话有些工程师是 stackoverflow driven development(见我的文章:\u003Ca href=\&http:\u002F\u002Fmp.\u002Fs?__biz=MzA3NDM0ODQwMw==&mid=&idx=1&sn=cf5bd19b7eb979cc5f997d&scene=21#wechat_redirect\& data-editable=\&true\& data-title=\&你要避免的软件开发模式\&\u003E你要避免的软件开发模式\u003C\u002Fa\u003E),殊不知,能够 stackoverflow driven,也是一种英文能力的体现。更何况,对于一些复杂的问题,如果书面英文尚可的话,可以在 google groups 上和语言\u002F框架的创建者探讨。我曾经和 golang team 的工程师讨论过编译时 bss 段的问题,也跟 elixir 的 creator 问过很 silly 的问题,他们都耐心地跟我解答。如果英文不好,这些讨论将无从展开。当你身处在一个工作的书面语言是英文,跨团队开会用英语讨论,那么,英文的提升会非常大。\u003C\u002Fp\u003E\u003Cp\u003E另一个福利是人脉圈子。互联网的圈子很小,对于工程师来说,参加几次活动,跳几次槽整个行业的主要人脉可能都已经囊括了,之后基本上就是慢慢发展。加入外企,实际上帮自己拓展出了原本触及不到的人脉,尤其是海外的人脉。在 Juniper,如果没有我老板 Shalang 和 Gilbert 的帮忙,我无法成功肉身翻墙;如果没有 haofei 的推荐,我也无从转换身份,加入 Tubi TV 这样的创业公司,提早达成当年出国的主要目标。很多工程师想肉身翻墙,很多海外的公司有意延揽国内的人才,无奈受到圈子的限制,互相之间信息不通,彼此只能望洋兴叹。\u003C\u002Fp\u003E\u003Cp\u003E还有眼界的提升。我不是说在国内的互联网公司就得不到眼界的提升 —— 这和圈子一样,发展到一定程度就开始固化。而加入一家来自硅谷的公司有助于打破这种藩篱。通过在工作中的历练和国外同事的交流,可以更新自己的技能栈,know
what you don't know。另外,潜在的出差机会还能让你在硅谷落地的几周内,有机会参加参加当地的技术 meetup,或者各种 startup 活动,甚至 stanford 的公开课(要在 event 页面提前注册),来进一步开拓视野。我之前在 Juniper 时,经常到 Sunnyvale 出差,每次都把这样的日程安排的满满的,生怕浪费了来之不易的机会。我参加过 siri 创始人的讲座,看过 500 startup 组织的 demo day,也和莘莘学子们挤在Wharton 和 Stanford 的阶梯教室里旁听过它们的公开课。这非常非常有助于从视野上提升自己,发现更大的世界。\u003C\u002Fp\u003E\u003Cp\u003E至于肉身翻墙的机会,我只能说,如果你有这样一个目标,做好了上面的功夫,翻墙成功只是时间和机会的问题。\u003C\u002Fp\u003E\u003Cp\u003E这些隐性的福利,你要说值几个钱,似乎很难衡量,但当你需要的时候,它是无价的。\u003C\u002Fp\u003E\u003Cp\u003E硅谷公司存在的问题\u003C\u002Fp\u003E\u003Cp\u003E说了这么多好处,也说说问题。一块硬币的两面,你都要看到。\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cp\u003E工资和福利并不是最好的。国内某些令人发指的土豪团队,也许是受了《奋斗》的影响,会时不时搞点年终奖整一麻袋现金,或者全员发特斯拉的大福利。不那么土豪的团队,也会整上好几个月甚至十多个月的工资作为年终奖。这在硅谷公司几乎不可能。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E个人的发展未必是最佳轨迹。国内的互联网,尤其是移动互联网,发展一日千里。如果跟对了好的互联网公司,个人能力和资历的增值是指数的增长。如果你看过『龙珠』,知道「精神和时间的房子」,那么,国内和国外的互联网发展速度就很像「精神和时间的房子」和外界的速度。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E对于崇尚奋斗的年轻人,硅谷的工作氛围更像是养老。去年我跟一个朋友聊过产品的开发速度,他们和我们差不多大小的团队,粗略估计,开发速度是我们的好几倍。他经常翻墙看我们的 app,见面第一句话,你们 iphone app 怎么三个月过去了,只做了 xxx,yyy 等几个小功能。我无言而对。硅谷相对宽松的生存环境,让创业公司「朝生暮死」的压力不像国内没那么大,整个社会的氛围又对工作生活的平衡很注重,所以有时会让人受不了这种「悠闲」。Paul Graham 说过 (似乎是他吧):人所承受的痛苦是恒定的。有的人愿意把痛苦集中在一个较短的时间内(奋斗),有的人愿意把痛苦均匀放在一生。这都没有错。996的「受害者」也许某天会感谢这段拼搏的日子;而宽松氛围的受益者可能某天会后悔当年为何不再努力一些。问题叠着问题,编织成了日子;选择就着选择,打造出我们的生活。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E&,&updated&:new Date(&T05:44:34.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:10,&likeCount&:60,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T13:44:34+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\\u002Fv2-96dae63103b12dcf7aea196cdd290a51_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:10,&likesCount&:60},&&:{&title&:&是时候想想该怎么删代码了&,&author&:&tchen&,&content&:&\u003Cp\u003E武林外传里秀才怼上姬无命,来了一段关于「我是谁」的精彩逼问。\u003C\u002Fp\u003E\u003Cblockquote\u003E\u003Cp\u003E我是谁?我生从何来,死往何处,我为何要出现在这个世界上?我的出现对这个世界来说意味着什么,是世界选择了我还是我选择了世界?!我和宇宙之间有必然的联系吗?宇宙是否有尽头,时间是否有长短,过去的时间在那里消失,未来的时间又在何处停止,我在这一刻提出的问题还是还是你刚才听到的问题吗?\u003C\u002Fp\u003E\u003C\u002Fblockquote\u003E\u003Cp\u003E我们平时很少问自己这样愚蠢的问题。很多事情,我们是如此地习以为常,以至于非但自己看不到这样的问题,当别人问道时,反而纯纯地回一句:doesn't look like anything to me。\u003C\u002Fp\u003E\u003Cp\u003E在很早前的一篇文章 \u003Ca href=\&http:\u002F\u002Fmp.\u002Fs?__biz=MzA3NDM0ODQwMw==&mid=&idx=1&sn=61a7adbeb49a105b9a22bae41614d07f&chksm=8704aaa9b00d392a7a858ba93c4dafba4dbb29f005a3&scene=21#wechat_redirect\& data-editable=\&true\& data-title=\&技术债:the good, the bad, and the tao\&\u003E技术债:the good, the bad, and the tao\u003C\u002Fa\u003E 里我问了几个问题:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cp\u003E我们为什么要做 code review?可以不做 code review 么?不做会有很严重的问题么?\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E在一个 team 里面,我们为什么要用同一种语言写代码?为什么程序员不能随意用自己想用的语言?\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E上面的问题,别说问了,想一想都是政治不正确。我们写代码遵循的很多流程,归根到底,都是为了软件能有更好的可维护性(maintainability) —— 毕竟,一段代码写下去,未来很久很久的时间,它们都是公司的资产:我们需要根据市场的需要,为其添加新功能,我们需要修改发现的 bug 和潜在的问题,我们还不得不不断提升其效率和能力,以便调和人民群众日益增长的需求和落后的生产力之间的矛盾 —— 这可是软件公司初级阶段的主要矛盾(高级阶段的都搞 AI 了)。所以,我们必须保证软件的可维护性 —— 而代码审核,同事间互相备份,使用同种语言开发都是服务于这个目的而存在的手段而已。那么问题来了:\u003C\u002Fp\u003E\u003Cp\u003E为什么我们要维护代码?为什么不能重写代码?或者说,为什么我们要通过修改代码来维护代码,而不是通过删除和重写代码来达到维护代码的目的?\u003C\u002Fp\u003E\u003Cp\u003E我们之所以不能,或者不愿这么做有很多原因:\u003C\u002Fp\u003E\u003Col\u003E\u003Cli\u003E\u003Cp\u003E添加一个新的功能同时保证不引入问题已经让人头大了 —— 要修改十几几十个文件,新增代码即便只有几百上千行,但涉及的代码成千上万行,都重写,写的过来么?风险太大\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E改动不算太大,没有必要重写啊\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E疯了,就算这么做是对的,老板肯定不会让我这么干\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E重写我自己的代码也就算了,重写别人的 —— 比我牛的我没底气;比我怂的我不忍心\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E...\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Fol\u003E\u003Cp\u003E这里面,除了第二个原因成立,其他其实都不太成立。第一个是架构的问题;三四是文化或者说认知的问题。架构问题如果深究,也是一种认知的问题。\u003C\u002Fp\u003E\u003Cp\u003E因为我们认定了我们所写的代码,都不是可以随意抛弃(throwable)的代码,而是需要维护(maintainable)的代码;我们从不把代码看做阻碍我们前进的负债,而是沉淀下来的宝贵的资产。所以我们愿意日复一日地堆上千万行的代码,从不考虑有计划地删除。\u003C\u002Fp\u003E\u003Cp\u003E同样的认知问题也发生在 imperative \u002F functional programming 上。变量之所以被称之变量,是因为它能够被赋值,被修改。很多程序员无法想象如果变量只能被绑定,无法被修改,日子该如何过 —— 因为他们甚至不知道该如何处理最基础的控制流程:循环。你告诉他 high ordered function,tail recursion,他会一脸懵逼:it doesn't look like anything to me。\u003C\u002Fp\u003E\u003Cp\u003E你也许会说 functional programming 和代码可随意抛弃是两种完全不同的认知,前者理性且有学术界的支撑,后者疯狂,近乎无稽之谈。让我们先抛开这个争论,假设确实有个疯子要我们设计一种架构,在这个架构下,我们引入的任何新的代码,都能够在未来的某个时刻被完全扔掉重写,而不会影响系统的其他功能。 我们该怎么做?\u003C\u002Fp\u003E\u003Cp\u003E意大利面条式的架构肯定不行。别说把某个功能摘出来扔掉不影响功能了,光摘出来可能就已经让人竭尽全力了。\u003C\u002Fp\u003E\u003Cp\u003E所以我们必须要模块化。每个模块各司其职,上帝的归上帝,凯撒的归凯撒。这样我们即便把凯撒抛弃了,上帝也不会活不下去。\u003C\u002Fp\u003E\u003Cp\u003E光有模块化也是不足够的。我们还得考虑分层。原子核和电子组成原子,若干原子组成分子,若干分子组成细胞,若干细胞组成组织,若干组织组成器官,若干器官组成生物体。每个层次各司其职,细胞不会过问原子中的电子是如何变态跃迁的。层次化能够保证在变化的系统中能有不变的,稳定的部分。在 ISO\u002FOSI 结构中,物理链路层的变化被传输层屏蔽,而传输层的变化又被应用层掩盖地妥妥帖帖。程序员在发送一个 GET \u002F 请求时,并不关心这个请求是经过 IPv4 还是 IPv6 传输的,更不需要迷失在 RJ45,fiber 这些多种多样的接口形态中。\u003C\u002Fp\u003E\u003Cp\u003E分层还是不足够的,因为它虽然解开了纵向的耦合而忽视了横向的耦合。举个最简单的例子 —— 现在几乎所有的 web app 都拥抱 MVC,但几乎所有的经典 MVC framework 里做出来的 web app 都让你陷入横向耦合的陷阱,rails,django,phoenix,无一不是。如果让你设计一个博客系统,你会自然而然地从 Model 起,设计 Blog \u002F Post \u002F Comment \u002F User 等基本的 model。然而,Post 跟 Blog 本可以无关, Blog 只是一组满足特定条件的 Post 的一个容器而已;Comment 也和 Post 无关,满足特定条件的 Comments 聚合起来,恰巧构成了 Post 的某个属性。因此,当你开始使用 rails generate model 的那一刻起,你的代码已经注定了有很强的横向耦合,难以将某个 Model 删除重写。有一天,当你发现 Post 需要用一个和 User 完全不同的 data store 存储时,你会发现,这几乎成了不可能完成的任务 —— 除非将整个系统重写。\u003C\u002Fp\u003E\u003Cp\u003E所以我们还需要将功能和功能解耦,也就是服务化,通过接口或者说协议来约束双方的行为。拿刚才的博客系统来说,Post 应该对 User 而言完全是一个黑盒,User 无法触及 Post 的内部状态(使用什么存储方式),只能通过约定好的接口来获取 Post 的信息。这样的话,Post 和 User 可以在同一个数据库中,也可以在不同的数据库中。\u003C\u002Fp\u003E\u003Cp\u003E服务化能够部分地让我们扔掉某个服务的代码完全重写,只要保证接口不变,就不会影响系统的其他功能。但它还不完全是最终的答案。我们从一个问题出发,走了这么远,已经可以心满意足了。Stephen Covey 劝诫我们:begin with the end in mind。如果我们在开始写代码的时候能够考虑到日后能被更加容易地删除,那么我们为此设计时会更加深思熟虑。我们会发现,写一段能够容易删除和重写的代码要比写一段容易维护的代码要难上很多。\u003C\u002Fp\u003E\u003Cp\u003E川普在他的百日新政中,提到了这么一条:for every new federal regulation, two existing regulations must be eliminated. 且不管这有没有实现的可能,它都是很大胆和有远见卓识的想法。社会的效率之所以越来越低效,是跟法规(行政命令)只增不删或者增加的速度大于删除的速度,导致其臃肿繁复有极大的关系;软件系统之所以越来越庞大不堪,难以维护,也是跟我们只是不断添加和修改代码,代码的增加速度大于删除的速度有关,偏巧,增加的又多以业务逻辑为主 —— 要知道,很多数年前的业务逻辑相关的代码,可能已经和当下的商业环境大不相同,留着徒增烦恼,却不带来太多的价值。\u003C\u002Fp\u003E\u003Cp\u003E对于这样的问题,生物界给出来的答案是新陈代谢。所谓新陈代解,就是生物体与外界环境之间的物质和能量交换以及生物体内物质和能量的自我更新过程。新陈代谢包括合成代谢(同化作用)和分解代谢(异化作用)。\u003C\u002Fp\u003E\u003Cp\u003E同化作用是指生物体把从外界环境中获取的营养物质转变成自身的组成物质,并且储存能量的变化过程 —— 通过同化作用,程序员,软件系统中的叶绿素,用灵巧的双手把自己大脑中苦心孤诣钻研出来的成果转化成代码,汇入系统中;异化作用是指生物体能够把自身的一部分组成物质加以分解,释放出其中的能量,并且把分解的终产物排出体外的变化过程 —— 对程序员来说,就是对系统去芜存菁,定期不定期地删除代码中的渣滓,重写那些光是维护着就已经让人竭尽全力的代码。。。然而,我们却很少启动这个异化过程。\u003C\u002Fp\u003E\u003Cp\u003E写这篇颇有争议(且尚未深思熟虑)的短文,并非想说代码维护不重要,重构不重要,只有重写才是王道,而是想抛出一个问题:有没有可能,我们在架构之初,就考虑到这个代码有可能成为一种负债,因此在设计上考虑到如何能轻松地将其删除或者替换?\u003C\u002Fp\u003E&,&updated&:new Date(&T05:46:27.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:3,&likeCount&:39,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T13:46:27+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\\u002Fv2-201be345b63d4f7c7926745_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:3,&likesCount&:39},&&:{&title&:&Phoenix 1.3,迈向正确的道路&,&author&:&tchen&,&content&:&\u003Cp\u003E距离 1.2 发布已经有一年多,而 exlirconf 2016 McCord 宣布 1.3 的特性也已过去半年,phoenix 1.3 依旧犹抱琵琶半遮面,迟迟不肯现身。几天前,1.3 RC.0 悄然发布,我们终于可以一睹她的芳容。\u003C\u002Fp\u003E\u003Cp\u003E引子\u003C\u002Fp\u003E\u003Cp\u003E因为程序人生的读者大多不是 elixir \u002F phoenix 的用户,所以在这里小小普及一下。elixir 是在 erlang VM 上发布的一门语法类似 ruby,能力完全继承 erlang,并支持 metaprogramming 的函数式编程语言。erlang VM 下以 actor model(请自行 wiki 之)为基础的 concurrency model,加之 pattern matching 的强大能力,辅以 metaprogramming 的上帝视角,让 elixir 充满独特的魅力。而 phoenix,是 elixir 语言下的一个脱胎于 rails 的框架,可以帮助我们快速打造 webapp。\u003C\u002Fp\u003E\u003Cp\u003Ephoenix 相对于 rails,根本性的颠覆有二:\u003C\u002Fp\u003E\u003Cp\u003E1) 基于 erlang VM 的 concurrency 能力。你看不见的很多地方,都使用大量的 process,或者 process pool 来提升并发能力。很多时候,phoenix 的 performance 是 rails 的 5-10 倍。phoenix 的作者 McCord 做了一个实验,在单机上成功实现了 2M websocket connection。\u003C\u002Fp\u003E\u003Cp\u003E2) realtime web。phoenix 大大简化了开发高性能 realtime app 的难度,通过抽象出 channel,让 join \u002F leave \u002F broadcast \u002F presence 这些事情处理起来非常简单 —— 这让一个普通的工程师也可能写出非常 scalable 的 realtime app,比如一个支撑百万级用户的聊天软件。\u003C\u002Fp\u003E\u003Cp\u003E以下是一篇文章 (https:\\u002Fblog\u002Fposts\u002Fwebsocket-shootout) 做的评测,测试方法是每个 websocket 收到 message 后 broadcast 给所有其他 websocket,完成后发送状态给 sender。在 95th percentile response time & 500ms 的情况下,看能支持多少 websocket:\u003C\u002Fp\u003E\u003Cimg src=\&v2-99c1be0aabe984d652c36.jpg\& data-rawwidth=\&1196\& data-rawheight=\&596\&\u003E\u003Cp\u003E我们可以看到,phoenix 的能力(大概 24,000 active websockets),落后于 C++ \u002F clojure,和 go 并驾齐驱。\u003C\u002Fp\u003E\u003Cp\u003E不过这个评测是非常不公平的 —— 在这里,phoenix 和 rails 都是 full-fledged framework,而其他都是直接使用语言的 websocket 库。这种对比就好像做网络性能测试,拿 UDP 和 TCP 对比,然后得出 UDP performance 要远好于 TCP 一样滑稽。考虑到 phoenix 在 websocket 基础上抽象出了 channel,每个 websocket connection,都是一对 process(一个处理网络层,一个处理 channel 层),并且从 connection 到 dispatch,都完整的走了 framework 的整个流程,达到这样一个效率还是相当惊人的。\u003C\u002Fp\u003E\u003Cp\u003E目录结构的变迁\u003C\u002Fp\u003E\u003Cp\u003E回到正题。phoenix 既然脱胎于 rails,一颦一笑都在模仿先祖。model,controller,view,template 一个都不少,scaffolding 出来的目录结构都异曲同工。这带来很多问题。其中最重要的,也是最根本的问题是:我们究竟在做一个包含了 web interface 的系统,还是在做一个以 web 为中心的 app?\u003C\u002Fp\u003E\u003Cp\u003E这是每个 web app 在成长过程中不得不面临的问题。我在 \u003Ca href=\&http:\u002F\u002Fmp.\u002Fs?__biz=MzA3NDM0ODQwMw==&mid=&idx=1&sn=6d309bb5501a3fafae2eac&chksm=8704aaa5bfbb1488cbfc50a008b250a27bc489392&scene=21#wechat_redirect\& data-editable=\&true\& data-title=\&rails, django, phoenix,你们错了\&\u003Erails, django, phoenix,你们错了\u003C\u002Fa\u003E 一文中提到:\u003C\u002Fp\u003E\u003Cblockquote\u003E\u003Cp\u003E说句不太好听的话,rails 等 framework 很容易引导人们走向一个 web 前端为中心的歧路。这里所说的「前端」,是指后端的前端。我们应该根据需求,先把业务模型构建出来,各个服务构建妥当后,再使用 rails 等打造前端。我们可能需要一个面向用户的前端,可能还要面向管理员的前端,每个独立的服务可能也需要它们各自的管理前端,我们还要有统计分析的前端,用户行为分析的前端等等。这些所有的前端基本都没有所谓的 model,因为数据的存储在各个服务中解决了。\u003C\u002Fp\u003E\u003C\u002Fblockquote\u003E\u003Cp\u003E我们看 phoenix 1.2 的目录结构:\u003C\u002Fp\u003E\u003Cimg src=\&v2-dc3aa43af319c65f31285.jpg\& data-rawwidth=\&2584\& data-rawheight=\&1898\&\u003E\u003Cp\u003E这是典型的以 web 为中心的处理方法。你的数据模型,你的各种业务逻辑,似乎就是奔着一个 web interface 去的,虽然能很快搭建出一个 app,但从长远发展来看,有诸多问题。当然我们随着系统的发展,把业务逻辑和数据模型抽取出来,放在 lib 下,甚至,用 elixir \u002F erlang 惯有的方式,将它们包装成一个个独立的 app,然而,scaffolding 出来的目录结构还是会深深地影响和制约着你的代码结构。起初,你会往 web\u002Fmodels 里塞 data model,往 web\u002Fcontrollers 里塞各种逻辑,慢慢地,你的代码就会变成这样的状态:处理业务的逻辑和处理 web 的逻辑揉在了一起,不同 model 间的逻辑揉在了一起,由此 controller 要了解很多 model 的细节,才能处理得当:\u003C\u002Fp\u003E\u003Cimg src=\&v2-60cbbcb80c3.jpg\& data-rawwidth=\&2520\& data-rawheight=\&1944\&\u003E\u003Cp\u003E在这样的代码里,\u003Cb\u003E我们看不清系统各部分的边界在哪里\u003C\u002Fb\u003E。新的代码的插入是那样的顺理成章,以至于一切良好的设计都随着边界的模糊而变得混乱不堪。理想的状态是这样:\u003C\u002Fp\u003E\u003Cimg src=\&v2-d23ffe288d2ea2c8190d1f.jpg\& data-rawwidth=\&2558\& data-rawheight=\&900\&\u003E\u003Cp\u003E业务和 web 分开,Blog 看上去更像是一个 service,一个 web controller 并不需要关心细节(只要知道接口)的 service。\u003C\u002Fp\u003E\u003Cp\u003E从上面的目录结构中演化出这样的代码并非易事 —— 新的代码放哪,目录如何设置,怎么命名,都是学问。Conway's laow告诉我们:\u003C\u002Fp\u003E\u003Cblockquote\u003E\u003Cp\u003Eorganizations which design systems ... are constrained to produce designs which are copies of the communication structures of these organizations\u003C\u002Fp\u003E\u003C\u002Fblockquote\u003E\u003Cp\u003E换句话说,一个公司的技术架构和设计受到该公司的组织架构的影响。同样的,Tyr's law 告诉我们:\u003C\u002Fp\u003E\u003Cblockquote\u003E\u003Cp\u003E\u003Cb\u003E一个系统的软件架构和设计和这个系统的目录结构非常相关\u003C\u002Fb\u003E。\u003C\u002Fp\u003E\u003C\u002Fblockquote\u003E\u003Cp\u003E在 phoenix 1.3 中,最大的变化就是目录结构的变化。我们欣喜地看到,models 不再隶属于 web,甚至,models 都不单独存在了,而 web,只是作为 app 的一个附庸而存在。由此,web 层被狠狠地削薄了,我们做一个系统不再是从 model 出发,在 controller 里构建逻辑,然后在 view 中呈现;相反,我们开始考虑如何打造 service,如何提供 internal 的 API,然后在这些 API 的基础上,提供 web interface。\u003C\u002Fp\u003E\u003Cimg src=\&v2-e197edba6b6ba791.jpg\& data-rawwidth=\&2584\& data-rawheight=\&1936\&\u003E\u003Cp\u003E由此,我们可以打造逻辑更为清晰的系统:\u003C\u002Fp\u003E\u003Cimg src=\&v2-e4ede621eb438c0ea62d.jpg\& data-rawwidth=\&2396\& data-rawheight=\&1892\&\u003E\u003Cp\u003E这样的目录结构,一眼望去,我们就大概知道系统提供什么样的服务,各个服务的边界在哪里:\u003C\u002Fp\u003E\u003Cimg src=\&v2-5c3db6b99893cfc603d285f.jpg\& data-rawwidth=\&2490\& data-rawheight=\&1540\&\u003E\u003Cp\u003E对 unbrella project 的支持\u003C\u002Fp\u003E\u003Cp\u003E在 elixir 中,umbrella project 是我的最爱。我不但喜欢把服务通过目录来划分势力范围,更钟情将它们构造成不同的 app 来进一步在运行时界定它们的边界。application 是 erlang VM 里一个非常重要的概念,这在其他 VM,其他语言中都不曾出现。一个 erlang VM,你可以将其看做是一个操作系统,这个操作系统里运行着很多各司其职的 application,每个 application 管理着它们各自的 process。在 rails 里,logger 是一个模块,db connector 是一个模块,它们运行在当前代码所在的上下文中。而 elixir \u002F erlang 中,logger 是一个 app,db connector 是一个 app,当你要记录日志时,实际上是发一个 message 给 logger app,请它来处理 log,log 的最终写入是一个完全不同的上下文。这种在运行时把系统划分成不同 app 来管理的方式,我非常非常喜欢。它让系统的管理变得简单,边界清晰,解耦变得容易,系统的脉络一路了然。\u003C\u002Fp\u003E\u003Cp\u003E在 phoenix 1.2 之前的版本,我使用 phoenix 的一个方式是先创建一个 umbrella project,然后在里面再创建只有 controller 和 view 的 phoenix app,这有些别扭;phoenix 1.3 中,我们终于可以直接使用 phoenix 来创建 umbrella project 了:\u003C\u002Fp\u003E\u003Cimg src=\&v2-c844e5c7a0dc4374accfea.jpg\& data-rawwidth=\&2282\& data-rawheight=\&1924\&\u003E\u003Cp\u003E这让我在 \u003Ca href=\&http:\u002F\u002Fmp.\u002Fs?__biz=MzA3NDM0ODQwMw==&mid=&idx=1&sn=6d309bb5501a3fafae2eac&chksm=8704aaa5bfbb1488cbfc50a008b250a27bc489392&scene=21#wechat_redirect\& data-editable=\&true\& data-title=\&rails, django, phoenix,你们错了\& class=\&\&\u003Erails, django, phoenix,你们错了\u003C\u002Fa\u003E 一文中提到的例子,从结构上打造起来方便很多:\u003C\u002Fp\u003E\u003Cimg src=\&v2-2e7c4c0c016.jpg\& data-rawwidth=\&960\& data-rawheight=\&720\&\u003E\u003Cp\u003E以上种种,解耦经验丰富的工程师也许不屑一顾;但它的确为经验不那么丰富的工程师,从结构上指出了一条明路,尤其是很多直接从 rails 转 phoenix,对 elixir \u002F erlang VM 还 一知半解的工程师。而 \u003Cb\u003E从结构上给出正确的方向,往往是 framework 的最大贡献\u003C\u002Fb\u003E。很欣喜,phoenix 1.3 终于迈出了这一步。\u003C\u002Fp\u003E\u003Cp\u003E当然,这样的步子迈起来很痛,容易扯着蛋。基于 phoenix 的很多优秀的第三方库,一下子变得都不好用起来。写起代码,很难直接使用已有的架构在 phoenix 1.2 之上的 lib,于是掣肘丛生,只能踯躅前行 —— 而且,在可预见的几个月内,这状况不太会有太多的改变。然而这种痛,是一个架构逐渐成熟 —— 走出全盘借鉴别人的路子,结合语言的特性,形成自己独特思路的必经之路。\u003C\u002Fp\u003E\u003Cp\u003E有意思的是,我第一个大规模使用的框架,django,也是在 1.2 到 1.3 的升级中,完成了 function based view 到 class based view 的蜕变。莫非,这就是天道轮回?\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E(本文的代码和大部分截图出自:\u003Ca href=\&https:\u002F\\u002Fwatch?v=tMO28ar0lW8\& data-editable=\&true\& data-title=\& 的页面\&\u003Ehttps:\u002F\\u002Fwatch?v=tMO28ar0lW8\u003C\u002Fa\u003E。McCord 大神亲自揭秘 phoenix 1.3 的更新。这个视频非常值得观看)\u003C\u002Fp\u003E&,&updated&:new Date(&T05:59:40.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:9,&likeCount&:60,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:true,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T13:59:40+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\\u002Fv2-23dddefafbf42_r.png&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:9,&likesCount&:60},&&:{&title&:&当我参加培训的时候,我在学什么?&,&author&:&tchen&,&content&:&\u003Cp\u003E在旧金山举行的 erlang\u002Felixir 2017 大会上周结束。这次,我并未参加 —— 权衡再三,我选择了这周的 complete OTP 培训,毕竟大会的视频 youtube 上找得见,可以慢慢补,培训错过了就没了。\u003C\u002Fp\u003E\u003Cp\u003E参加一次技术培训,代价往往不菲,像这样一个四天的培训,价格是两千多刀,你很难说出它有多值 —— 培训的主题有一半都是我已经了解或掌握的内容,在过去的一两个月,我还给我的 team 培训过;另一半,其实给我空出来四天的时间,我自己看书或者读 erlang 的文档,获取到的知识也未必比参加培训少,那么,花这样的大价钱参加的意义是什么?\u003C\u002Fp\u003E\u003Cp\u003E自己先想想看。\u003C\u002Fp\u003E\u003Cp\u003E寻找合适的,志同道合的工程师?No。通过参加培训达到这样的目的,既不规模,也不经济,还不如去相关的 meetup 上勾搭呢。\u003C\u002Fp\u003E\u003Cp\u003E跟讲师套磁,建立关系?hmmm... 你觉得四天能套出什么结果?大家都是实诚的程序员,讲究以德胡人,活好的自然互相倾慕(我跟讲师约了五月份他来湾区有机会喝个咖啡聊聊);活不好,培训完就是路人。\u003C\u002Fp\u003E\u003Cp\u003E朝着这些方向想,too simple, sometimes naive。\u003C\u002Fp\u003E\u003Cp\u003E其实,花钱参加培训的终极意义是(敲黑板):你购买了一次 附带培训的咨询服务。\u003C\u002Fp\u003E\u003Cp\u003E(此处应该有黑人问号脸)\u003C\u002Fp\u003E\u003Cp\u003E就拿我参加的 complete OTP 培训来说吧。讲师 Francesco 是 Erlang Solutions 的 founder。Erlang Solutions 是一个咨询公司,很多著名的开源项目(比如:RabbitMQ,Riak,MongooseIM)来源于他们,他们还是 erlang\u002Felixir 大会的发起人。考虑到这样的 profile,你去 Erlang Solution 买个四天的咨询服务试试看?不定给你派个什么水平的咨询师来,而且起价没个几万肯定下不来。两千?做梦去吧。\u003C\u002Fp\u003E\u003Cp\u003E可惜似乎没人懂这个理。也幸好没人懂这个理,这样一门课程,冷清到全世界的 erlang\u002Felixir 程序员(虽然基数很小)加起来,包括我也就只有九个人参加。而时时刻刻都在提问的,只有我和一个 apple 的工程师(是的,apple 内部有项目在用 elixir 做,yeah,erlang\u002Felixir 程序员可以嗨森一下,然后该干嘛干嘛去了)。而提问的方向聚焦在平时工作中遇到的问题的,和公司业务相关的,基本就是我。\u003C\u002Fp\u003E\u003Cp\u003E这个 complete OTP 的课程内容不算特别紧凑,其中,做一个 ebank 项目的练习时间,快占了一半。这个宝贵的问各种二逼问题的时间,你造么 —— 绝大多数工程师都在寂静无声地吭哧吭哧写代码,偶尔遇到问题了问问 Francesco 这怎么回事,那里为什么编不过去?\u003C\u002Fp\u003E\u003Cp\u003E这就好比你守着扁鹊,不让他给你问诊,却让他给你剪指甲。\u003C\u002Fp\u003E\u003Cp\u003E或者,说个程序员熟知的段子:妹子对序员说你要是能让论坛的人都吵起来,我今晚就归你。序员在论坛里说:PHP 是世界上最好的语言。。。\u003C\u002Fp\u003E\u003Cp\u003E暴殄天物啊。\u003C\u002Fp\u003E\u003Cp\u003Eexercise 没有价值么?非也。这个培训使用的 exercise 非常经典,从 gen_server,gen_fsm,gen_event,一路练习到 supervisor,application,以及使用 sasl 深入了解 release 的过程,如果谁能把整个 exercise 独立完成,我觉得他\u002F她就能击败 80% 的 elixir 程序员,真可以在简历上号称一下掌握 OTP 了。\u003C\u002Fp\u003E\u003Cp\u003E很有价值,但实现这个价值的时机不对。\u003C\u002Fp\u003E\u003Cp\u003E全班同学仿佛只有我预先(或者之后)把 exercise 做完,而在 exercise 的时候,问课程中各种没有来得及问的问题,以及工作中踩到的各种坑。\u003C\u002Fp\u003E\u003Cp\u003E你看我都问了哪些很 \&silly\& 的问题:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cp\u003E为什么说 start_link 是个同步的过程?\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003Eapplication 为什么会起两个 process,再启动 supervisor?(其实这个问题我在书中看到答案,只是为了引出更多的问题来确保我理解对了)\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003Egroup_leader 的意义何在?(我觉得我懂了,但我不知道我是不是真的懂了)\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E使用 global process 是不是个好的做法(这次我干脆无耻地打开我工作中写的 auto compiler 的代码跟他探讨)\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E我这样这样这样用 ansible provision vm.args 和 nodes.config,究竟对不对?有没有更好的做法?\u003Cbr\u003E\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E等等。在 Francesco 的推荐下,我甚至还读了一些 OTP 的源码,和他探讨源码里的细节。\u003C\u002Fp\u003E\u003Cp\u003E这才是有效地利用这样的「附带培训的咨询服务」。\u003C\u002Fp\u003E\u003Cp\u003E如果你觉得我说的对,那么接下来我们看看如何从一次培训中收到最大的收益?\u003C\u002Fp\u003E\u003Cp\u003E选择合适的讲师\u003C\u002Fp\u003E\u003Cp\u003E如果抱着上述所说的目的,那么,培训的内容其实是次要的。既然把它当做是「咨询」,那么,我们需要选择合适的「咨询师」。\u003C\u002Fp\u003E\u003Cp\u003E前面说了,这次的讲师是 Francesco Cesarini,我们前面介绍过他是 Erlang Solutions 的 founder(这个身份暂且放在一边不提),他的其他身份(吸引我的身份)是:Designing for Scalability with Erlang\u002FOTP 一书的作者,erlang\u002FOTP R1 的开发人员。\u003C\u002Fp\u003E\u003Cp\u003E也就是说,可能除了 erlang 的几个 founder 外,如果在全球范围内海选十个有资格讲 OTP 的人,他必定是其中之一,而且排位靠前。\u003C\u002Fp\u003E\u003Cp\u003E我知道大部分读者不知道 erlang\u002FOTP,大概也搞不清 Francesco 和 San Francisco 有什么区别。我们换个角度说 ——\u003C\u002Fp\u003E\u003Cp\u003E如果你做微信服务,你平日烧香供着的姓张的舅舅开个小班培训,谈谈如何做微信下的服务,票价一万;而程序君也同时做几乎相同的培训,吐血白菜价一百,你参加哪个?\u003C\u002Fp\u003E\u003Cp\u003E搞清楚了这道选择器后,我们再出一道题:Rich Hickey 有个 clojure 的 培训,或者 Guido van Rossum 讲 dive into Python,面向中级水平的程序员,而作为 clojure 或者 python 的高手高手高高手,你参不参加这个培训?\u003C\u002Fp\u003E\u003Cp\u003E做足功课\u003C\u002Fp\u003E\u003Cp\u003E培训上可能讲到的内容,是不是先自己过一遍,把所有自己没搞明白的问题整理出来,在培训的过程中随时发问呢?\u003C\u002Fp\u003E\u003Cp\u003E工作中,我们在一个方向上工作久了,业务熟悉了,就会成为所谓的「专家」,如果周围的人在你的领域都远不如你,便会自以为是。是不是可以趁着这样的机会把自己工作中遇到的问题,不懂的,似懂非懂的,以为自己懂的,以为自己对的,都拎出来跟讲师辩一下呢?\u003C\u002Fp\u003E\u003Cp\u003E用这样的机会跨越平台期\u003C\u002Fp\u003E\u003Cp\u003E程序员估计都知道一万小时理论 —— 足够长时间(一万是个约数)在某个领域的刻意训练(deliberate training)能够让你成为专家。我们据此坚信,24小时学会 C++ 是错误的,肤浅的;相反,只要功夫深,就能学精 C++。\u003C\u002Fp\u003E\u003Cp\u003E也似乎不太对。\u003C\u002Fp\u003E\u003Cp\u003E因为我遇到太多干了十几年的平庸程序员了。他们似乎困在一个无论怎么努力也很难跨越的平台期 —— 这是一万小时理论里的禁飞区。\u003C\u002Fp\u003E\u003Cp\u003E这次培训,同学们的 erlang\u002Felixir 的工作经验都远高于我 —— 我 elixir 三个月,三千行代码经验,erlang 零工作经验。在做 exercise 前,我都搞不清楚写代码的时候什么时候该用分号,什么时候该用逗号。\u003C\u002Fp\u003E\u003Cp\u003E但四天下来,我觉得我写 erlang 代码的水平(虽然还是比较慢,还会漏句号)已经并不比有好几年 erlang 经验的同学差多少了。\u003C\u002Fp\u003E\u003Cp\u003E这是为啥?\u003C\u002Fp\u003E\u003Cp\u003E我想,很多人误解了 deliberate training 的含义了 —— 朝着目标头悬梁锥刺股是对的,但要知道该如何跨越平台期而进阶。我最近每周六晚都带着小丫头溜冰。我知道她跟我溜得再努力,每次一刻都不停歇一个小时滑一百圈也不可能成为下一个关颖姗。我的上限就决定了她的上限;可是,她要是有幸跟着关颖姗滑上一年,接受指点,同样的努力程度,往大了不敢说,一年后制霸 Cupertino 同年龄段应该不成问题。\u003C\u002Fp\u003E\u003Cp\u003E所以 deliberate training 很重要的组成部分是 feedback loop。对,我又要灌 build - measure - learn 的鸡汤了。你光是机械地 build build build,没有 measure,哪来 learn 呢?measure 很重要。更重要的是,怎么 measure,谁来 measure?用什么 criteria 来 measure?这很有讲究。\u003C\u002Fp\u003E\u003Cp\u003E跟着一个高手学几天,即便不能功力大涨,但起码知道自己应该突破的脉门何在。我们不是都读过令狐冲和风清扬在思过崖上的故事么?\u003C\u002Fp\u003E\u003Cp\u003E善用你的老师。老师不光是有血有肉的,由碳基构成的那些位,还有无色无味的,从 0 和 1 演化而成的源代码们。\u003C\u002Fp\u003E\u003Cp\u003ELinus 说:read the F**KING code!\u003C\u002Fp\u003E\u003Cp\u003E我们读一本优秀的书,就像是和作者在进行心与心的交流;我们读一段美妙的源代码,如同长辈和晚辈间的薪火相传。这世上,也许只有写作和编码,能够像无崖子渡让内力给虚竹那样,并不太费力就能完成知识和经验的传承。\u003C\u002Fp\u003E\u003Cp\u003E我问的好些问题,Francesco 建议我去 xxx 目录下读源码。比如 application master,比如 gen_server,我就着源码,在纸上画着流程,嘴里喃喃自语道:bravo!\u003C\u002Fp\u003E\u003Cimg src=\&v2-f9adb8d6a273b8578cba.jpg\& data-rawwidth=\&1024\& data-rawheight=\&1365\&\u003E\u003Cp\u003E(app master 和 x process)\u003C\u002Fp\u003E\u003Cimg src=\&v2-c4f9ce521fec1b09f3f58b3f2ddac530.jpg\& data-rawwidth=\&1024\& data-rawheight=\&1365\&\u003E\u003Cp\u003E(gen server)\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E读到不通处,再和他探讨。\u003C\u002Fp\u003E\u003Cp\u003E你看,傻小子郭靖,不就在七公手下,这么成长的么?\u003C\u002Fp\u003E\u003Cp\u003E就写这么多。此致,敬礼。\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cbr\u003E\u003Cp\u003EHead fake 1: 你以为这文章是写给你的?不不不,是写给我的同事的。 \u003C\u002Fp\u003E\u003Cp\u003EHead fake 2: 你以为这文章是在告诉你看清培训的意义,以及如何选择培训?不不不,请看思考题。\u003C\u002Fp\u003E\u003Cp\u003E思考题:赵丹阳 08 年 211 万美金拍得和巴菲特共进午餐的机会。他是真的为了和巴老聊天套磁寻觅投资机会么?\u003C\u002Fp\u003E&,&updated&:new Date(&T07:29:44.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:14,&likeCount&:148,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T15:29:44+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\\u002Fv2-1b9cf3f8d6ae6e00e0fbbe4c1d4c579f_r.png&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:14,&likesCount&:148},&&:{&title&:&为什么我们要阅读源码?&,&author&:&tchen&,&content&:&\u003Cp\u003E程序员每天都和代码打交道。经过数年的基础教育和职业培训,大部分程序员都会「写」代码,或者至少会抄代码和改代码。但是,会读代码的并不在多数,会读代码又真正读懂一些大项目的源码的,少之又少。这种怪状,真要追究起来,怪不得程序员这个群体本身 —— 它是两个原因造成的:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cp\u003E我们所有的教育和培训都在强调怎么写代码,并没有教大家如何读代码\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E大多数工作场景都是一个萝卜一个坑,我们只需要了解一个系统的局部便能开展工作,读不相干的代码,似乎没用\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E我常常把写代码和写作进行类比 —— 二者有很多相通之处;但从培养写代码和写作的过程来看,二者又有很多不同。我们的写作能力,是建立在大量基础阅读的基础上的,是除了学习语法和文法知识外,从小学开始,经年累月,通过阅读各种不同层次的名家的作品,再加上各种各样的写作训练,累积出来的;而我们的写代码的能力,在了解和掌握了语法\u002F文法之后(学习和抄写 example 代码也算语法\u002F文法学习的一部分),跳过了大量阅读名家作品的过程,直接 biu 地一下就自动养成了:学会基础的语法和试验了若干 example 后,我们就火箭般蹿到了自己写代码打怪赞经验的阶段。这样略过大量阅读代码的阶段有三个害处:\u003C\u002Fp\u003E\u003Col\u003E\u003Cli\u003E\u003Cp\u003E写代码的基础是不牢靠的,打怪升级的过程也是最慢的。道理很简单 —— 前辈们踩过的坑,总结的经验教训,你都不得不亲自用最慢的法子一点点试着踩一遍。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E很容易养成 stackoverflow driven 的写代码习惯 —— 遇到不知如何写的代码,从网上找现成的答案,找个高票的复制粘贴改吧改吧,凑活着完成功能再说。写代码的过程中遇到问题,开启调试模式,要么设置无数断点一步步跟踪,要么到处打印信息试图为满是窟窿的代码打上补丁,导致整个写代码的过程是一部调代码的血泪史。(见我的文章:\u003Ca href=\&http:\u002F\u002Fmp.\u002Fs?__biz=MzA3NDM0ODQwMw==&mid=&idx=1&sn=cf5bd19b7eb979cc5f997d&scene=21#wechat_redirect\& data-editable=\&true\& data-title=\&你要避免的软件开发模式\&\u003E你要避免的软件开发模式\u003C\u002Fa\u003E)\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E你周围最强的那个工程师的开发水平的上限就是你的上限。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Fol\u003E\u003Cp\u003E我们再回到读书进行类比。\u003C\u002Fp\u003E\u003Cp\u003E从小学到高中,就语文而言,12年时光,单单课本我们要读十二册,数百篇文章。如果每篇文章平均一千字,那么我们读了数十万文字。这些文字,我们是精读过的(有些甚至要求全文背诵)。每篇文章我们需要总结中心思想,段落大意,归纳出论点论证论据或者时间任务地点起因经过结果,会分析长句难句,会学习起承转结,并反复训练基础的遣词造句能力,并最终模仿那些文章写出自己的文章。在这个过程中,我们学会了赋、比、兴,我们掌握了三段论,我们知道了如何用更优雅地方式表达自己的思想。更重要的是,这些阅读训练让我们在我们在脱离学校的基础教育后,可以自己独立完成一本书的阅读。我们不再依赖老师或者参考书为我们给出段落大意,中心思想,我们知道如何粗读,细读甚至类比阅读一本书,我们能把书中的精髓浓缩成思维导图,也大段大段摘录书中精彩的句子,段落或者篇章。\u003C\u002Fp\u003E\u003Cp\u003E这十几年的时光,算上各种课外阅读,世界名著,古典文学,金庸古龙,修正玄幻,一个大学毕业的二十几岁的青年人,阅读量应该不下几百万字。而稍稍涉猎广些的读者,上千万字的阅读累积是常有的事。有了这些累积,你才能在迎面走来一位妙龄女子,想到的是肌肤胜雪,明眸善睐,桃腮带笑,齿如含贝,气若幽兰,美艳不可方物,一笑倾城,再笑倾国,沉鱼落雁,闭月羞花这些词句,而非不知如何表达,只能吞吞口水,在肚子里闷上一句:「我擦,美女啊」。\u003C\u002Fp\u003E\u003Cp\u003E这是读书的第一大功用:累积素材(information)。你是否写文章时,经过一番搜肠刮肚,也不知该如何描述某事某物?同样的,写代码时,有没有毫无头绪,不知从何写起的时候?或者有了些的思路,双手却在键盘上迟滞,不知所措?这些现象,大多是缺乏累积所致。\u003C\u002Fp\u003E\u003Cp\u003E读书的第二大功用:是开拓思路。有时候,一段文字,甚至一个句子,在你意料之外扑面而来,让你有种醍醐灌顶的感觉。比如『围城』里,赵辛楣和方鸿渐鸿初次见面,钱老描述赵的傲慢无礼,是这么写的:「傲兀地把他从头到脚看一下,好像鸿渐是本一览而尽的大字幼稚园读本」。初读围城的时候,我关注点是其故事性,将这样的句子轻易放了过去,几年前再读时,才发觉它的精妙:竟能如此简单地以物喻人,就把整个场景复原到如同发生在我的面前一样活灵活现。随后,我自己的文字里也模仿着,有时甚至刻意地如此这般使用比喻来增强画面感。前些日子偶尔再读到这句,因我有了作为一个成人,给女儿读幼稚园读本的经验,不由得莞尔一笑,旋即明白了一个道路:精妙的不是比喻本身,而是对生活的细微观察。\u003C\u002Fp\u003E\u003Cp\u003E我在边学 elixir 边做 policy engine(见:\u003Ca href=\&http:\u002F\u002Fmp.\u002Fs?__biz=MzA3NDM0ODQwMw==&mid=&idx=1&sn=aee8fa02ebd0d9b6d289&chksm=8704aaf3bb29ed954aff792917daea06e1619afc0de392c6eefd206&scene=21#wechat_redirect\& data-editable=\&true\& data-title=\&Policy Engine 的前世今生\&\u003EPolicy Engine 的前世今生\u003C\u002Fa\u003E)的过程中,除了官方的文档和零星的博文外,可读的内容少得可怜,我要解决的一些问题,论坛里也没人能给我较好的思路。于是我转而读了部分 iex 的代码,了解了 elixir 代码编译的方式,最终完成了一个 auto compiler 的 app —— 它能接受一些 API 请求,对预先配置的属于其他 app 下的源码可以在 cluster 里的有且仅有一个 node 上进行编译,编译完成后在整个 cluster 的所有 node 里重新加载;在做 API 的过程中,我读了 plug(elixir 下官方的 connection adapter 实现)里面的主要逻辑,尤其是精读了 Plug.Router 的代码,搞明白了为何 Phoenix 的 router 敢宣称在 route match 阶段,其 performance 就甩同行好几个数量级。于是我做 API 时,对如何在 match \u002F dispatch 前后如何做些动作实现 middleware,甚至 hook 进 before send,有了更清晰的思路,在写代码时,也更加明白如何写出类似的 composable adapter。\u003C\u002Fp\u003E\u003Cp\u003E累积素材是基础,被启发出来的思路将这些素材}

我要回帖

更多关于 英拉最新消息2小时前 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信