<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>zchar (古灵)</title>
    <link>https://ruby-china.org/zchar</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>[成都 + 武汉] Tower 诚聘初级产品工程师 / 前端 + 后端 / 长期有效</title>
      <description>&lt;h3 id="概览"&gt;概览&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;公司：&lt;a href="https://tower.im" rel="nofollow" target="_blank" title=""&gt;Tower&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;地址：成都市天府二街领地。环球金融中心&lt;/li&gt;
&lt;li&gt;职位：初级后端工程师 + 初级前端工程师&lt;/li&gt;
&lt;li&gt;工作：协助开发 Tower 产品功能&lt;/li&gt;
&lt;li&gt;薪酬：10 - 14k&lt;/li&gt;
&lt;li&gt;简历投放：join(at)mycolorway.com&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="关于 Tower"&gt;关于 Tower&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://tower.im" rel="nofollow" target="_blank" title=""&gt;Tower&lt;/a&gt; 是彩程软件设计有限公司旗下的一款团队协作工具，从 2013 年发布至今，已经积累了 800 万的注册用户，是 Alexa 上国内排名第一的协作类软件。Tower 从 2017 年开始收费以来，已经连续两年保持 100% 的收入增长。&lt;/p&gt;

&lt;p&gt;为了更好的满足越来越多的用户的需求，我们希望招募一批年轻的工程师加入，来协助我们加速 Tower 的进化。&lt;/p&gt;
&lt;h3 id="职位要求"&gt;职位要求&lt;/h3&gt;
&lt;p&gt;初级前端工程师：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;一年以上的互联网产品开发经验；&lt;/li&gt;
&lt;li&gt;能够熟练使用 HTML/JS/CSS 实现自己的产品想法；&lt;/li&gt;
&lt;li&gt;对成长的热切渴望；&lt;/li&gt;
&lt;li&gt;使用过 Tower 加分。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;初级后端工程师：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;一年以上的互联网产品开发经验；&lt;/li&gt;
&lt;li&gt;能够熟练使用 Ruby/PHP/Python/Go 等至少一种后端语言实现自己的产品想法；&lt;/li&gt;
&lt;li&gt;对成长的热切渴望；&lt;/li&gt;
&lt;li&gt;使用过 Tower 加分。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="薪酬福利"&gt;薪酬福利&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;基础工资：10 - 14k&lt;/li&gt;
&lt;li&gt;五险一金&lt;/li&gt;
&lt;li&gt;补充商业险&lt;/li&gt;
&lt;li&gt;年终奖&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="能远程吗"&gt;能远程吗&lt;/h3&gt;
&lt;p&gt;虽然彩程是一支远程办公的团队，但为了初级工程师的成长速度，这次招募原则上不考虑除&lt;strong&gt;成都、武汉&lt;/strong&gt;两个地点以外的工程师。如果有优秀的异地的小伙伴想要加入，入职后需要在成都和大家一起工作 6 个月，公司提供临时住宿。&lt;/p&gt;
&lt;h3 id="联系方式"&gt;联系方式&lt;/h3&gt;
&lt;p&gt;如果对我们的职位感兴趣，欢迎发送简历至 join(at)mycolorway.com，期待你的加入~&lt;/p&gt;

&lt;p&gt;&lt;em&gt;附上成都办公室的环境&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2019/99a53ecb-28ce-4ee5-a75c-da352689c5e2.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2019/854dd9ba-0e3a-40cd-83d6-4fe2809c73ef.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2019/d75796d4-0021-4f25-b8f2-9c93cf25a267.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2019/68c41198-e584-41ff-b6c7-72ceabb31846.jpg!large" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>zchar</author>
      <pubDate>Thu, 21 Mar 2019 06:56:21 +0800</pubDate>
      <link>https://ruby-china.org/topics/38263</link>
      <guid>https://ruby-china.org/topics/38263</guid>
    </item>
    <item>
      <title>[远程] 彩程知人招募工程师 (15 - 30k)</title>
      <description>&lt;h2 id="关于我们"&gt;关于我们&lt;/h2&gt;
&lt;p&gt;彩程设计成立于 2008 年，从 2012 年开始远程工作，迄今为止已经 快 5 年。5 年时间里我们打造了国内用户量最大的团队协作工具 &lt;a href="https://tower.im" rel="nofollow" target="_blank" title=""&gt;Tower&lt;/a&gt;，以及目前看来国内对 HR 最友好的人力资源产品&lt;a href="https://zhiren.com" rel="nofollow" target="_blank" title=""&gt;知人&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;关于我们团队远程工作的方式和经验，可以参看这篇文章：&lt;a href="https://zhuanlan.zhihu.com/p/26031654?refer=mycolorway" rel="nofollow" target="_blank" title=""&gt;《Tower 团队 48 个月远程实践》&lt;/a&gt;，关于团队的更多介绍，可以参看去年发的这篇帖子：&lt;a href="https://ruby-china.org/topics/31960" title=""&gt;《彩程招募 Rails 工程师》&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id="关于「知人」"&gt;关于「知人」&lt;/h2&gt;
&lt;p&gt;2012 年开始，我们进入 SaaS 企业市场，Tower 是团队第一款尝试的作品，迄今为止以不大的团队规模实现了自给自足。2015 年我们觉得自己更加强壮了，于是开始打造新产品「知人」。&lt;/p&gt;

&lt;p&gt;选择人力资源方向切入是因为三点原因决定的：1. 市场规模；2. 竞品情况；3. 我们自己是否喜欢。&lt;/p&gt;

&lt;p&gt;我们如果参考一下美国的企业软件市场，有 100 多家 SaaS 企业超过百亿美金市值，在这个企业市场领域里，最大的三块蛋糕是被 CRM / ERP / HRM 切走的。在这三者中，CRM / ERP 在国内已经有相对成熟的软件产品，但是人力资源管理软件，在 2015 年进行搜索的时候，还没有找到任何这个时代应有的产品。最后，因为彩程这个团队最大的「爱好」就是用互联网产品解决一切低效率的问题，所以我们对「解放 HR MM」这件事情，有着极大的兴趣。基于这些答案，我们从 2015 年开始打造知人，至今已有两年多时间。&lt;/p&gt;

&lt;p&gt;这两年时间里，知人给我最大的感受就是，挑战不断。我记得姜军同学在 Ruby China 上回复过这么一篇帖子：&lt;a href="https://ruby-china.org/topics/32456" title=""&gt;《什么是大型「Rails」项目》&lt;/a&gt;：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/832e96b3-6041-4827-b51c-9906b72b9468.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;用了差不多两年时间，知人的代码量达到了 6.9w 行的规模。那么到今天（2017.05.31）接近三个月时间，代码总量已经突破 9w 行了：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/e7c59483-285d-48e0-8427-bf01f3a53816.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;代码量说明业务的复杂程度，比如作为一个人力资源工具，我们必须在系统里支持基本的入职、转正、调整、离职等人事流程，涉及到流程就会有审批的概念，在知人里的审批模块是长这样的：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/7faf1827-bbda-4346-b16d-2fc8e135a729.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/fe60dd7c-30ac-4c26-bb8d-baf5f7330c5e.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/1fa40826-852b-439a-abd9-e1198fbc5a6f.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;每个审批流程的表单可以自定义，审批流程可以按照不同的表单字段设置流转分支，不同的流转分支可以设定不同的审批人。&lt;/p&gt;

&lt;p&gt;再举个例子，比如服务的很多公司都可能有类似排班的需求，于是在知人里，我们提供了一个非常灵活的排班工具供 HR 使用：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/ab5522d7-768c-4945-aed4-97a56b5fd26d.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这个在线排班表被一个知人客户用来安排 150 多个门店 4000 多个员工的班次。&lt;/p&gt;

&lt;p&gt;说到 HR 的日常琐事，我们最开始做知人的「野心」就是让 HR 每个月的工资计算可以彻底自动化，每个月要发工资之前，只需要点个按钮，嗖的一声工资就能给你自动计算出来。理想是很美好的，现实情况是，每个公司都有自己的工资的计算方式，这个公司可能午餐补贴是一种计算方法，换个公司可能又是另外一回事了。我们虽然做了通用的配置界面，但是为了最大的灵活性，后台的每张工资表的每个科目字段，都是可以用这样的公式编辑器来进行配置的：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/e911b42f-e52b-4745-9439-b82b6dbefeab.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这些都是知人接近 10w 行代码的冰山一角，我们最大的挑战不仅在于业务逻辑的复杂，还在于怎么把复杂的业务逻辑简化成对用户友好的设计，以及怎么把友好的设计通过足够优雅的代码实现。&lt;/p&gt;

&lt;p&gt;毋庸讳言这是一个蛮大的挑战，我们也还远远没有做到优秀，如果你对打造这样的产品感兴趣，也很看好这个市场的未来，欢迎作为早期成员加入我们，非常非常非常期待收获更多优秀的创业伙伴。&lt;/p&gt;
&lt;h2 id="招聘岗位"&gt;招聘岗位&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;后端工程师&lt;/strong&gt;：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;非常熟悉 Ruby/Ruby on Rails 的使用&lt;/li&gt;
&lt;li&gt;能独立完成一个中等复杂度的 Web 项目&lt;/li&gt;
&lt;li&gt;能熟练的运用常用的 gem 来解决问题&lt;/li&gt;
&lt;li&gt;熟练的使用 Git&lt;/li&gt;
&lt;li&gt;有良好的代码习惯，对代码的简洁和美有追求&lt;/li&gt;
&lt;li&gt;对面向对象有比较深的理解，并且能运用到实际的工作中&lt;/li&gt;
&lt;li&gt;熟练的运用测试框架来保证高质量的代码&lt;/li&gt;
&lt;li&gt;能熟练的使用类 Unix 系统&lt;/li&gt;
&lt;li&gt;对敏捷开发的实践有一定认识，比如 TDD, CI, Pair 等&lt;/li&gt;
&lt;li&gt;对产品有 sense，理解并钻研业务背后的逻辑&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;前端工程师&lt;/strong&gt;：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;2 年以上的前端架构经验，能为产品制定合适的前端架构与规范&lt;/li&gt;
&lt;li&gt;有丰富的前端模块化的经验，维护过模块化的前端 UI 组件&lt;/li&gt;
&lt;li&gt;有一定的设计功底，对好的产品设计、视觉设计、交互设计有自己的见解&lt;/li&gt;
&lt;li&gt;有 Ruby On Rails 开发经验&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="福利待遇"&gt;福利待遇&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;远程办公，以及远程通信补贴&lt;/li&gt;
&lt;li&gt;15 - 30k&lt;/li&gt;
&lt;li&gt;五险一金&lt;/li&gt;
&lt;li&gt;每年例行体检&lt;/li&gt;
&lt;li&gt;正式员工一份额外的商业保险&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;简历请发到：join@mycolorway.com&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;如果邮件没有及时回复，可能是被当做垃圾邮件误杀了，可以加微信「invader」直接联系&lt;/strong&gt;&lt;/p&gt;</description>
      <author>zchar</author>
      <pubDate>Wed, 31 May 2017 11:51:24 +0800</pubDate>
      <link>https://ruby-china.org/topics/33101</link>
      <guid>https://ruby-china.org/topics/33101</guid>
    </item>
    <item>
      <title>[远程] 彩程设计再招小伙伴</title>
      <description>&lt;p&gt;不好意思，我又来发一下招聘贴。&lt;/p&gt;

&lt;p&gt;官方的说明大家直接看这个帖子就行了：&lt;a href="http://v2ex.com/t/192966" rel="nofollow" target="_blank"&gt;http://v2ex.com/t/192966&lt;/a&gt;，包括招聘的岗位、薪酬范围以及如何投递简历。&lt;/p&gt;

&lt;p&gt;基于曾经的找人经验，我们在最近写了一篇文档，在小伙伴加入前会发给他，告诉他我们团队的使命和目标，以便帮助他做出更好的决策，我想干脆就贴在帖子里面吧。&lt;/p&gt;

&lt;p&gt;------------------------------------------------------------------ &lt;em&gt;我是分割线&lt;/em&gt; ------------------------------------------------------------------&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;普鲁斯特是个法国作家。彻底的失败者，情事不断还是个同性恋。花 20 年写了一本没几个人看的小说。但他也许是莎士比亚之后最伟大的作家。晚年回首人生，他发现那些难熬的日子才是一生中最好的时光，因为那些日子造就了他。而快活的日子全是浪费时间，没有任何收获。你想一觉醒来就到 18 岁，觉得这样可以跳过高中时期的痛苦。但高中是你一生中最重要的苦难时光，你不可能经历比这更好的苦难了 - 《阳光小美女》&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;欢迎你加入彩程设计，一支永远在路上的团队。这份文档会让你更好的理解这个团队的价值观，以及我们做事情的方式。如果你理解并且认同这些文字里传递的意义，那么你会在这个团队里获得无穷的财富，否则，我们可能还没有缘分。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;1. 我们的使命&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;截至我写下这篇文档的今天，彩程在互联网上几乎人尽皆知的产品 Tower 距离 2012 年 12 月正式发布已经过去两年半，Tower 带给我们一共「敏感信息」块人民币的收入，而目前我们这个不到 20 人的团队一个月的开销是「敏感信息」万人民币左右。&lt;/p&gt;

&lt;p&gt;从 2012 年开始，中国企业市场进入活跃期，在 Tower 推出前后，我们周围出现了「直接写出来不太好哇 :D」，而这些 SaaS 企业软件，都消耗着风险投资人的钱，无一例外的离盈利差距甚远。&lt;/p&gt;

&lt;p&gt;而在另一块大陆美国，一个叫做 Slack 的团队协作工具，在发布一年后，携 50 万日活跃用户，将以 25 亿美金的估值，获得 B 轮融资，在此之前，2014 年 11 月，他们刚刚融资 1.2 亿美金。Slack 只是美国众多企业软件中的新宠儿，按照&lt;a href="http://startupclass.club/steps/12" rel="nofollow" target="_blank" title=""&gt;这个家伙的说法&lt;/a&gt;，在美国，这是一个 3 万亿美金的市场。&lt;/p&gt;

&lt;p&gt;在 2012 年成都市五洲花园的民宅里，我们这群创业的同伴达成了一致，美国的昨天，就是中国的明天，我们的抱负与自负驱使我们放弃一个「普通人的生活」选择了创业，因此我们期待成为一个拓荒者，期待在中国的明天掘到企业市场的黄金，满足我们的人生抱负，这就是我们的使命。&lt;/p&gt;

&lt;p&gt;我们希望你在加入的时候，相信这个使命，这是我们能熬过创业痛苦时光的唯一原因。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;2. 如何达成我们的使命&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;无论你是工程师、产品经理、设计师、销售还是客服，人，永远都是达成团队使命的根本。在任何时候，我们希望我们的伙伴能够做到以下两点：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.1 超出预期&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;我们无论是在写一行代码，还是在做一次设计，或者是在回复客户的一封邮件，我们都要首先问自己一个问题，我们如何超出「用户」的预期？&lt;/p&gt;

&lt;p&gt;也许你正在用代码开发一个功能，你的产品经理告诉你这个功能对用户的价值是什么，你要确保你已经非常了解了产品经理的这个需求的价值，当你甚至能够提出更有价值的建议的时候，你就超出了产品经理的预期；接着你可能会收到一份来自设计师的视觉设计稿，你要确保你仔细思考了这份设计图里的每一个信息元素，以及这些信息元素是否满足需求，如果你发现这个交互设计可以有更好的实现方式，并且主动联系视觉设计师，跟他商量是否可以更进一步的设计提升的时候，你就超出了视觉设计师的预期；当你的视觉设计师根据你的建议做出了一版更好、难度更高的设计之后，你用同样迅速的时间，以非常高的质量把成果送达客户面前，你就超出了客户的预期。&lt;/p&gt;

&lt;p&gt;也许你正在回复一封客户邮件，客户在抱怨一个功能上的问题，你要确保你自己非常明白客户的这个问题究竟是什么，在必要的情况下，想方设法的和用户取得直接的联系，告诉他我们非常抱歉，获取到他的系统的详细情况，浏览器的版本，手机的型号，并且把详细的问题重现步骤反馈给工程师，当你甚至能够用录屏的方式把问题重现给工程师的时候，你就超出了工程师的预期；接着你应该关注这条任务的进展，当工程师完成了这个问题的修复，你第一时间主动联系客户告诉他这个消息，你就超出了客户的预期；在一个月结束的时候，如果你能整理出一份客户抱怨最多的问题的列表，把它交给产品经理，你就超出了产品经理的预期。&lt;/p&gt;

&lt;p&gt;超出预期，其实是不断对自我的突破，就算你的创业之旅最终以失败告终，但你会在这个过程中，成为那个你真正想成为的自己。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.2 少说，多做&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;我们需要反对的声音，我们需要灵光乍现的好点子，创业团队一片和谐或者寂静不语都不是好现象，甚至按照&lt;a href="http://item.jd.com/11645500.html" rel="nofollow" target="_blank" title=""&gt;这个家伙的观点&lt;/a&gt;，在一个创业公司，我们永远都是相爱相杀的。但是除了提出无穷无尽的「好点子」或者「我不满意」之外，我们更需要的是解决方案，如果你只有观点，没有行为，那么你应该把话先放在肚子里，我们非常讨厌只说不做的家伙。开玩笑和讲段子例外。&lt;/p&gt;

&lt;p&gt;具体点来讲，我们希望在每日动态里能够看到你非常高效的推动分配给你的任务：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/c132a6e4609adbbaf96f144ee7ea6b6c.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;我们希望你每天结束的时候，身上不背债务：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/ac08f5868e404771466a2c147819b5d0.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;但是，也别把债务堆积太多到以后。这是一个满负荷的成员的最佳状态：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/6c95e3981bf5624bbed21babf9fda71a.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;另外，我们希望你随时保持在线，Tower、微信、手机、邮件、Telegram、Zoom，我们使用这些沟通工具来消弭团队成员之间的距离和时差，让我们随时保持联系，5 分钟内就能够找到你。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;3. 其它参考&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;顺着下面这些链接，你可以找到日常工作时候的一些参考或准则，这些文档会持续补充完善，请多多回顾：&lt;/p&gt;

&lt;p&gt;「加入以后就能看到...」&lt;/p&gt;</description>
      <author>zchar</author>
      <pubDate>Mon, 25 May 2015 17:06:20 +0800</pubDate>
      <link>https://ruby-china.org/topics/25735</link>
      <guid>https://ruby-china.org/topics/25735</guid>
    </item>
    <item>
      <title>说说 Rails 的套娃缓存机制</title>
      <description>&lt;p&gt;Rails 4.0 以后，开始推广一种称为「俄罗斯套娃」的缓存机制，这是一种使用 Fragment Caching（&lt;a href="http://guides.rubyonrails.org/caching_with_rails.html#fragment-caching" rel="nofollow" target="_blank"&gt;http://guides.rubyonrails.org/caching_with_rails.html#fragment-caching&lt;/a&gt;）技术的缓存机制，在数据库做完查询以后，如果记录没有变化，那么对应的页面不会被 Rails 重新渲染，而是直接从缓存里取出，拼装好以后，返回给客户。&lt;/p&gt;

&lt;p&gt;Tower 正是借鉴了这套缓存机制，给那些访问 Tower 的用户提供流畅的使用体验，今天跟大家分享一下我们的经验，和一些需要注意的坑。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. 套娃是怎么套的&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;拿 Tower 的项目详情页为例，我们可以把这个页面明显的分成一个个的 Section，比如「讨论区」、「任务清单区」、「文件区」、「文档区」、「日历事件区」，我们可以把每一个列表区域设置为一个独立的缓存，这样，如果列表的数据没有更新的话，在渲染项目详情页的时候，就可以直接从缓存里读取之前生成好的数据。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/d907c092628717a9d0f4b09d2f9bb296.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;那么，所谓的套娃在哪儿呢？实际上，除了可以把上面的 Section 放到缓存里，我们也可以把整个项目详情页放入缓存，这样，如果某一个项目里的数据没有任何更新，访问这个详情页就可以直接读取详情页的缓存，连下面的 Section 缓存都不用碰了。&lt;/p&gt;

&lt;p&gt;同样，对于某一个列表缓存，比如「讨论区」，我们可以看到里面会放三条讨论 item，这里其实每一个 item 也可以对应放入一个独立的缓存，这样如果只有其中一条 item 有更新的话，其它两条数据是不会被重新渲染，而是直接从缓存区读取的。这样分拆下去，我们大概可以把整个项目详情页的缓存弄成下面这个模样：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/6904b25ef55b5fe6e6abbdba820e79b3.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这样不就一层一层的套起来了么？&lt;/p&gt;

&lt;p&gt;接下来我们看看，如果这个页面的某一条数据，比如「任务 A-1」的内容被改变了，会发生什么。&lt;/p&gt;

&lt;p&gt;首先，这条任务自己对应的 L4 Todo Item 缓存失效了，所以在拼装外面的 L3 级「任务清单 A」缓存的时候，会从缓存里获取任务 A-2、A-3 的缓存，速度嗖嗖快，快到可以忽略不计，然后对任务 A-1 重新渲染一次，放入缓存，这样「任务清单 A」通过直接从缓存里读取两条任务（A-2 和 A-3），以及渲染一条新的（A-1）生成了整个 L3 Todolist Item 的页面片段。剩下的「任务清单 B」和「任务清单 C」，都没有变化，因此由在生成「任务清单」Section 缓存的时候，直接拼装即可。&lt;/p&gt;

&lt;p&gt;其它几个 Section 片段因为和任务没有任何关系，所有缓存都不会过期，因此这几个 Section 的页面片段都是直接从缓存里捞出来，同样嗖嗖快。&lt;/p&gt;

&lt;p&gt;最后，整个项目详情页把这几个 Section 拼装起来，返回给客户。从上面的过程可以看出，只有「任务 A-1」这个片段的页面被重新渲染了。&lt;/p&gt;

&lt;p&gt;所以，这种套娃式的缓存，能够保证页面缓存利用率的最大化，任何数据的更新，只会导致某一个片段的缓存失效，这样在组装完整页面的时候，由于大量的页面片段都是直接从缓存里读取，所以页面生成的时间开销就很小。&lt;/p&gt;

&lt;p&gt;那么，套娃是如何在缓存中存取页面片段的呢？主要是靠一个叫做 cache_key 的东西来决定的。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. cache_key&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;我们在页面上可以使用一个叫做 cache 的方法，把一坨 HTML 代码片段放在一个 Fragment Cache 里，以项目详情页为例，我们的代码可能是这个样子的：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/c75211adff814b0aa8f4cd796da7a08e.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;可以看到，在整个页面的最外面，有个最大的「套娃」：&lt;code&gt;&amp;lt;% cache @project %&amp;gt;&lt;/code&gt;，这个 cache 使用 &lt;code&gt;@project&lt;/code&gt; 作为方法参数，在 cache 方法内部，会把这个对象进行一番处理，最后生成一个字符串，大概是这个样子：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;views/projects/1-20140906112338&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;这就是所谓的 cache_key，Rails 会使用这串字符串作为 key，对应的页面片段作为内容，存储进缓存系统里。每次渲染页面的时候，Rails 会根据 cache 里的元素计算出对应的 cache_key，然后拿着这个 cache_key 到缓存里去找对应的内容，如果有，则直接从缓存里取出，如果没有，则渲染 cache 里的 HTML 代码片段，并且把内容存储进缓存里。&lt;/p&gt;

&lt;p&gt;对于一个具体的 Model 对象，cache_key 的生成机制简单来说，就是：对象对应的模型名称/对象数据库 ID-对象的最后更新时间。&lt;/p&gt;

&lt;p&gt;这里我们能够很容易分析出，一个缓存判断最后是否过期，其实很大程度上只和数据最后更新时间有关，因为在系统里，数据对象对应的模型名称是不变的，对象在数据库里的 ID 一般也是不变的，唯一可能变化的就是最后更新时间。Rails 在创建模型数据表的时候，一般会创建两个默认的 datetime 类型字段，一个是 created_at，一个是 updated_at，而后者正是用来生成 cache_key 的最后更新时间。而且这个时间一般来说不需要我们手动更新，我们都知道如果对一个模型对象调用 save 方法，Rails 会自动帮我们更新这个 updated_at 字段，这样，如果我修改了项目名称，项目的 updated_at 会发生变化，自然的，页面上项目对应的 cache_key 也就会发生变化，因此我们的 &lt;code&gt;&amp;lt;%cache @project %&amp;gt;&lt;/code&gt; 也就自动过期了。&lt;/p&gt;

&lt;p&gt;继续项目详情页里的示例代码，接下来我们看看第二级套娃：各个 Section 缓存。&lt;/p&gt;

&lt;p&gt;拿这个 &lt;code&gt;&amp;lt;% cache @top3_topics.max(&amp;amp;:updated_at) %&amp;gt;&lt;/code&gt; 为例，它比 &lt;code&gt;&amp;lt;% cache @project %&amp;gt;&lt;/code&gt; 稍微复杂了点。我们首先应该知道的是，&lt;code&gt;@top3_topics&lt;/code&gt; 存储的是对应项目里最新创建的三条讨论，这里比较奇怪的是，我们为什么要用一个 &lt;code&gt;max(&amp;amp;:updated_at)&lt;/code&gt; 方法呢？&lt;/p&gt;

&lt;p&gt;如果我们直接把 &lt;code&gt;@top3_topics&lt;/code&gt; 对象作为 cache 的参数 &lt;code&gt;&amp;lt;% cache @top3_topics %&amp;gt;&lt;/code&gt;，得到的 cache_key 实际上会是这样的形式：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;views/topics/3-20140906112338/topics/2-20140906102338/topics/3-20140906092338&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;看的出来，是每个对象的 cache_key 的组合，我们并不太希望 cache_key 变得这么复杂，特别是当列表元素超过 3 个，比如说有 20 条记录的时候，所以最简单的办法，是取这组数据里最新一个被更新的数据的 updated_at 时间戳，这样生成的 cache_key 就是下面的样子了：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;views/20140906112338&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;但是注意，这里有一个问题，就是假如 &lt;code&gt;@top3_topics&lt;/code&gt; 一条数据都没有，会出现什么情况？比如我新建的项目，里面理所当然的一条讨论都没有，这个时候，实际上 cache 的是一个空的 relation，对这个空对象调用 &lt;code&gt;max(&amp;amp;:updated)&lt;/code&gt; 方法，返回的值永远都是 nil，所以实际上我们是对 nil 进行 cache，不幸的是，所有 nil 的 cache_key 都一模一样，导致这样的缓存片根本不可用，你不知道究竟是对什么数据进行的缓存。另外，加入任务清单 Section 和讨论 Section 最后更新的那条数据的 updated_at 时间戳恰好一样，也会造成两个缓存片混淆的问题。&lt;/p&gt;

&lt;p&gt;而解决这个问题的方法很简单，就是给 cache 参数里增加一个特定的字符串标识，比如把 &lt;code&gt;&amp;lt;% cache @top3_topics.max(&amp;amp;:updated_at) %&amp;gt;&lt;/code&gt; 改成 &lt;code&gt;&amp;lt;% cache [:topics, @top3_topics.max(&amp;amp;:updated_at)] %&amp;gt;&lt;/code&gt;，这样一来，如果 &lt;code&gt;@top3_topics&lt;/code&gt; 里一条数据都没有，生成的 cache_key 是这样的：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;views/topics/20140906112338&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;带上了「topics」自己的标识，这样就能和其它 nil 类型的缓存区分开了。修改后的项目详情页代码片段如下：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/ced50caf6985bcb37705769d040a59d3.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Touch!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;我们回过头来再看看套娃缓存的读取机制，访问项目详情页的时候，首先读取最外层的大套娃 &lt;code&gt;&amp;lt;% cache @project %&amp;gt;&lt;/code&gt; ，如果这个缓存片对应的 cache_key 在缓存里能找到，则直接取出来并且返回，如果缓存过期，则读取第二级套娃 — 几个列表 Section 缓存，这些缓存根据列表里最新一条数据的更新时间生成 cache_key，如果最新一条数据的更新时间没有变化，则缓存不过期，直接取出来供页面拼装用，如果缓存过期，则继续读取各自的第三级套娃。&lt;/p&gt;

&lt;p&gt;等等，这里有个问题，如果我改变了一条任务的内容，也就是作废了任务 partial 自己的缓存，但是包裹任务的任务清单，以及包裹任务清单的项目都没有变化，这样当页面加载的时候，读取到的第一个大套娃 -- &lt;code&gt;&amp;lt;% cache @project %&amp;gt;&lt;/code&gt; 都没有更新，会直接返回被缓存了的整个项目详情页，所以根本不会走到渲染更新的任务 partial 那里去。对于这个问题的解决方案，是 Rails 模型层的 touch 机制。&lt;/p&gt;

&lt;p&gt;简单的说，我们需要让里面的子套娃在数据更新了以后，touch 一下处在外面的套娃，告诉它，嘿，我更新了，你也得更新才行。我们直接看看这个代码片段：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/31c3f850e8dfcb89eac60351095b9566.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;在这里，我们使用 Rails model 的 belongs_to 来声明模型的从属关系，比如一个 Todo 属于一个 Todolist，一个 Todolist 属于一个 Project，而在 belongs_to 后面，我们还传入了一个 touch: true 的参数，这样，当一条 Todo 更新的时候，会自动更新它对应的 Todolist 对象的 updated_at 字段，然后又因为 Todolist 和 Project 之间也有 touch 机制，所以对应 Project 对象的 updated_at 字段也会被更新。放到我们的套娃缓存片里面看的话，就是当一条任务更新以后，「包裹」它的任务清单的缓存片也会被更新，因为对应的 Todolist 对象的 updated_at 时间改变了，而「包裹」这个任务清单的任务清单列表 Section 的缓存片也会失效，因为 &lt;code&gt;@todolists.max(:updated_at)&lt;/code&gt; 改变了，接着是「包裹」列表 Section 的项目缓存片过期，因为 &lt;code&gt;@project&lt;/code&gt; 对应的 updated_at 也被更新了。&lt;/p&gt;

&lt;p&gt;就是通过这么重重 touch 的机制，我们能确保子元素在更新以后，它的父容器的缓存也能过期，整个套娃机制才能正常运作。下面是整个 Tower 里面，各个模型层的 Touch 结构图：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/57525964a4626f3d0893e9720c51b98e.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. 那些踩过的坑&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;经过上面的介绍，大家应该已经明白了套娃的实际使用方式，看上去很完美不是么？但在我们的实际使用过程中，套娃缓存还是有一些坑需要注意的，这里跟大家分享一下。&lt;/p&gt;

&lt;p&gt;我们在开发过程中经常遇到的一个问题，是缓存模板里如果存在「父」元素的情况。我们把 Project 定义为 Todolist 的父元素，把 Todolist 定义为 Todo 的父元素，因为 touch 机制是自底向上的，从子 touch 到父，但是如果我们的模板是下面这个样子：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/ec471cf3ebe8c500674eb5849eb60c52.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;在任务清单模板里，我们需要显示一下项目的名称，也就是一个子元素的模板里，包含了父元素，这个时候如果缓存是 &lt;code&gt;&amp;lt;% cache [:todolists, @todolists.max(&amp;amp;:updated_at) %&amp;gt;&lt;/code&gt; 的话，当我们把项目名称修改了，这个缓存片是不会过期的，因此任务清单列表里的项目名也不会改变。&lt;/p&gt;

&lt;p&gt;解决这个问题的办法，一是修改任务清单的缓存的 cache_key，改成：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/cf040612ae5282c197ab23b0cb06dc77.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这样修改项目名称，就能导致缓存片过期，这也是一个普遍的手段，就是把缓存里面存在的所有模型对象统一纳入 cache_key 里面，但是这样存在一个问题，就是因为项目本身是经常被 touch 的，修改任务也会、创建评论也会，所以导致这个任务清单的缓存片会随时失效，缓存命中率降低，所以使用这种方法的时候要仔细考虑，引入父元素作为 cache_key 的一部分，是否会导致这个问题。&lt;/p&gt;

&lt;p&gt;另一个办法是，使用实际需要的模型字段来做缓存，比如上面的例子，我们实际上只是需要项目名称，所以可以把缓存改为：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/b6b64469241c5b0c6fdcd9301263c390.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这样只会在项目名称发生改变的时候，更新缓存片，这个方法可能性价比最高，不过如果一个缓存里出现多个模型字段的时候，就要写一串这样的 cache_key，和我们「只对一个具体资源缓存」的原则有些差距，所以一般来说，缓存的具体字段最好不要超过一个。&lt;/p&gt;

&lt;p&gt;还有一个处理方法是，在 HTML 结构上做调整，基于我们上面所说的「只对一个具体资源缓存」的原则，这里我们如果针对的是 &lt;a href="/todolists" class="user-mention" title="@todolists"&gt;&lt;i&gt;@&lt;/i&gt;todolists&lt;/a&gt; 做缓存，那么就应该把其它无关的资源从 HTML 结构里提取出来，比如放到一个外层的 hidden input 里面：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/0f2fecf3c60ce64aa92813a0b923fc2f.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这样可以通过 JS 读取这个属性，再重新注入到模板相应的元素里面。选择这种方案，需要提前根据设计做好规划，把那些需要提取出来的元素放在缓存以外。&lt;/p&gt;

&lt;p&gt;最后还有一个方法，就是不理会它。如果你相信任务清单不会长期不变，而项目名称不会经常变化的话，那么缓存里的项目名称不会随时都是最新版本，就是一个可以被接受的事实了，这需要在产品层面上考虑，我们建议如果遇到这样的问题，不妨先用这种最简单的方式处理，看看用户反馈再决定是否进行调整。&lt;/p&gt;

&lt;p&gt;——————————————— 我是分割线 —————————————————&lt;/p&gt;

&lt;p&gt;我们遇到的第二个问题比第一个问题更加让人头疼，这个问题发生在我们为 Tower 引入一个叫做「访客锁」的新功能的时候。在 Tower 里，用户被分为普通成员、管理员和访客三种，在一个项目里，有些资源比如一条任务清单，是可以设置对访客不可见的，这个在模型层处理起来很简单，只需要增加一个字段来标识一个资源是否是对访客不可见即可，但是一旦和 Fragment Caching 结合的时候，就有问题了。在引入访客锁功能之前，任务清单列表的 Cache 是这样的：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/b0f0b22f186d94b68a8aea9e8faa9ba8.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这里 &lt;a href="/todolists" class="user-mention" title="@todolists"&gt;&lt;i&gt;@&lt;/i&gt;todolists&lt;/a&gt; 是从项目里取出来的所有未完成的任务清单，然后使用 &lt;code&gt;max(&amp;amp;:updated_at)&lt;/code&gt; 时间戳来作为 cache_key，这样在一条任务清单更新以后，这个最后更新时间会变化，cache_key 也就变化了。但是在引入访客锁以后，这就会有潜在问题了。假如我们现在有如下图所示的三条任务清单：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/e30b349fe9a5a3eabf326c4871aa44c6.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;我们首先将「任务清单 B」加锁，然后再去修改一下「任务清单 A」的名称，这个时候整个清单列表的 &lt;code&gt;max(&amp;amp;:updated_at)&lt;/code&gt; 时间就是「任务清单 A」的 updated_at 时间，如果一个普通成员先打开项目详情页，根据这个更新时间，会缓存一个含有三条任务清单的页面，接着一个访客再打开同一个项目详情页，会出现什么情况呢？这个访客会看到三条同样的任务清单，「任务清单 B」加锁是无效的！这是因为对于访客来说，虽然在控制器里查询出来的任务清单只有 A 和 C 两条，但是对于这两条任务清单，最后更新的是 A 的 updated_at 时间戳，这个和能看到三条清单的普通成员以及管理员是一样的，因此他们的任务清单列表的 cache_key 是一样的，取出来的缓存片也一样。&lt;/p&gt;

&lt;p&gt;关于这个问题我们考虑了很久，最后发现只有两种解决方案，要么是彻底放弃对这种列表类型的片段做缓存，要么就是遍历列表里的所有子元素，把各自元素的 cache_key 组合起来再求一个 MD5 值，最后我们选择了后者，具体的做法是在有列表缓存需要的 Model 里，引入一个 Concern：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/236cf01594d3c56b4034dadf037a5222.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这样，在需要对列表进行缓存的时候，我们的写法就不再是 &lt;code&gt;&amp;lt;% cache [:todolists, @todolists.max(&amp;amp;:updated_at)] %&amp;gt;&lt;/code&gt;，而是这样：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/caed92f6b9fdc8ceebffca5363478ea5.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这种办法是目前我们能想到的最佳解决方案，不知道有没有更好的处理方式。&lt;/p&gt;

&lt;p&gt;绕过这个最大的坑以后，还剩下最后一个地方需要修改，就是我们最外层的那个套娃，我们使用的是 &lt;code&gt;&amp;lt;% cache @project %&amp;gt;&lt;/code&gt; 来对整个项目详情页做缓存的，但是因为引入了访客锁，所以访客看到的页面，和普通成员以及管理员看到的页面，是不一样的，如果都用 &lt;code&gt;@project&lt;/code&gt; 作为 cache_key，会导致和上面列表模式一样的问题，好在这个地方的解决方法比较简单，把缓存改成 &lt;code&gt;&amp;lt;% cache [@project, current_user.visitor?] %&amp;gt;&lt;/code&gt; 即可，只是对于同一个项目详情页，需要存储两份缓存了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. 小结&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;以上就是我们在 Tower 里使用套娃缓存的一些经验，除了 Fragment Caching 之外，我们也没有额外再使用 Page Caching 或者 Action Caching 之类的技术，37signals 在这篇 Blog 里（&lt;a href="https://signalvnoise.com/posts/3690-the-performance-impact-of-russian-doll-caching" rel="nofollow" target="_blank"&gt;https://signalvnoise.com/posts/3690-the-performance-impact-of-russian-doll-caching&lt;/a&gt;）统计过他们使用套娃后的缓存命中率，这个值是 67%，而 Tower 目前 8G 的 Memcache 的缓存命中率是 45%，相比之下还有差距，不过整站使用体验上，速度并不是一个显著的短板，如果能把 Fragment Cache 的细粒度继续做下去，应该会有更好的效果。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/a31042930f8a05525813fe119f31b005.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;综上，套娃缓存机制还是蛮适合于小团队用来加速自己的网站（实际上 Tower 的 Hybird 模式的移动客户端也是用这种方式来做加速的），只要在模板设计的时候，尽量按照资源做好规划，后面逐步增加套娃的数量和层级，由于只涉及到模板部分的更改，总体来说是一个性价比很高的方案。&lt;/p&gt;

&lt;p&gt;PS：特别感谢 &lt;a href="/hayeah" class="user-mention" title="@hayeah"&gt;&lt;i&gt;@&lt;/i&gt;hayeah&lt;/a&gt; 以及 &lt;a href="/quakewang" class="user-mention" title="@quakewang"&gt;&lt;i&gt;@&lt;/i&gt;quakewang&lt;/a&gt; ，很多技术问题经常骚扰二位 :D&lt;/p&gt;</description>
      <author>zchar</author>
      <pubDate>Thu, 11 Sep 2014 19:51:02 +0800</pubDate>
      <link>https://ruby-china.org/topics/21488</link>
      <guid>https://ruby-china.org/topics/21488</guid>
    </item>
    <item>
      <title>[成都][远程] 彩程「又」招暑期实习生啦~</title>
      <description>&lt;p&gt;去年暑期我们在全国范围内招募了 5 名实习生来彩程参与真实产品的打造，一年过去了，这些实习生里，有人继续拿着远超预期的报酬，在美丽如画的英国爱丁堡大学一边读研，一边向 Tower 的代码库里提交他的劳动成果，也有人成为了彩程的正式员工，和团队通力协作，参加了去年百度黑马大赛，阿里云开发者大赛，甚至做出了像 Simditor 这样优秀的开源作品。在这段时光里，彩程和这些小童鞋们都收获满满。&lt;/p&gt;

&lt;p&gt;一眨眼一年过去了，我们新一轮的暑期实习生计划又要开始了，如果你和我们同样认为人最重要的能力是学习能力，并且对你自己的学习能力充满信心的话，那就加入我们，一同打造被千万人使用的产品吧~&lt;/p&gt;
&lt;h2 id="我们提供"&gt;我们提供&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一个你愿意并且值得为此打造的产品；&lt;/li&gt;
&lt;li&gt;足够多的解决实际产品问题的机会，让你在实战中快速提高技术实力；&lt;/li&gt;
&lt;li&gt;可供选择的办公地点，你可以选择在学校或是在家里参与实习，当然，如果你选择到成都来，那么无需担心你的来回机票；&lt;/li&gt;
&lt;li&gt;每月 6k 以上的实习薪水，保证你能带着鼓囊囊的钱包回到校园。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="申请条件"&gt;申请条件&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;在读高校生，有完整且可支配的暑期时间；&lt;/li&gt;
&lt;li&gt;远超同龄人水准的开发能力或设计能力；&lt;/li&gt;
&lt;li&gt;远超同龄人水准的自我管理、责任心与沟通能力。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="申请方式"&gt;申请方式&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;个人简历（形式不限）；&lt;/li&gt;
&lt;li&gt;目前为止自己最满意的一份作品；&lt;/li&gt;
&lt;li&gt;谈谈你对 &lt;a href="http://tower.im/" rel="nofollow" target="_blank"&gt;http://tower.im/&lt;/a&gt; 的看法，技术、设计、产品角度均可。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;请将上述材料发送至 intern@mycolorway.com，并在邮件标题中标明「彩程 2014 暑期实习申请」。&lt;/p&gt;</description>
      <author>zchar</author>
      <pubDate>Mon, 19 May 2014 16:54:02 +0800</pubDate>
      <link>https://ruby-china.org/topics/19384</link>
      <guid>https://ruby-china.org/topics/19384</guid>
    </item>
    <item>
      <title>开源 Tower 的编辑器 Simditor</title>
      <description>&lt;p&gt;项目地址：   &lt;a href="https://github.com/mycolorway/simditor" rel="nofollow" target="_blank"&gt;https://github.com/mycolorway/simditor&lt;/a&gt;
DEMO 地址：&lt;a href="http://mycolorway.github.io/simditor/demo.html" rel="nofollow" target="_blank"&gt;http://mycolorway.github.io/simditor/demo.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;又一个编辑器？&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;从 2012 年第一版 Tower 上线以来，我们就在寻找一款最为合适的编辑器以供 Tower 的用户使用，我们最早使用 &lt;a href="http://xing.github.io/wysihtml5/" rel="nofollow" target="_blank"&gt;http://xing.github.io/wysihtml5/&lt;/a&gt; 作为 Tower 的编辑器，在前期 wysihtml5 很好的满足了我们的核心需求 -- 非常方便的添加附件，无论是直接粘贴剪切板里的内容还是拖动上传，wysihtml5 都很容易实现，不过随着 Tower 功能的增加，当我们引入 @ 成员、Markdown 功能、代码识别、自动短链等一系列功能以后，我们发现第三方开源编辑器的局限性也逐渐暴露出来，这些局限性让我们在很多用户体验和工程复杂性的岔路口上选择向工程复杂性妥协，于是我们思考了一下（实际上是纠结了很久），作为一款在线生产力工具，让用户能极其舒畅的生产内容是它的核心品质之一，所以我们决定自己开发一款编辑器，这就是 Simditor 产生的原因。&lt;/p&gt;

&lt;p&gt;经过不断地改进，我们已经在最近使用 simditor 替换了 Tower 里的默认编辑器，包括创建讨论、发表回复以及创建在线文档的编辑器，现在都使用自产的 Simditor 了。所以如果说这个开源编辑器和其它开源编辑器相比有什么不同，我觉得最大不同在于这款编辑器不是一个业余爱好的产物，而是每天被几万人使用的真实产品，我们会非常谨慎的评估究竟什么功能是实际用户最为需要的，而不会因为交互很酷炫，就把功能增加进编辑器里，导致代码膨胀，造成麻烦的后果。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simditor 认证功能&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;以下内容来自 Tower 里使用 Simditor 创建的一篇文档《在线笔记格式指南》，直接贴图了：
&lt;img src="//l.ruby-china.com/photo/2014/a48825877b38b3dcbc9f30c21e5bd840.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;功能扩展&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;除了上述格式指南中的功能，在 Tower 里的 Simditor 还具 @通知 这样的高级功能，这种类似的和本身产品的数据关联比较紧的插件，可以通过扩展 &lt;a href="https://github.com/mycolorway/simple-module" rel="nofollow" target="_blank"&gt;https://github.com/mycolorway/simple-module&lt;/a&gt; 这个 Module 来实现，我们也会在稍后以 @通知 功能为参考放出写插件的方法。&lt;/p&gt;

&lt;p&gt;有任何问题，欢迎 Email 给我们反馈：tower@mycolorway。当然，更加欢迎程序猿参与贡献，特别是各位 Tower 用户，不必等待，直接修改。&lt;/p&gt;</description>
      <author>zchar</author>
      <pubDate>Tue, 25 Mar 2014 12:44:35 +0800</pubDate>
      <link>https://ruby-china.org/topics/18152</link>
      <guid>https://ruby-china.org/topics/18152</guid>
    </item>
    <item>
      <title>[成都] 彩程设计 2013 暑期实习生招募</title>
      <description>&lt;p&gt;Hi~各位喜欢 Ruby 的童鞋们，我们想在今年暑假期间招募 5 名在校大学生，和彩程设计 (&lt;a href="http://mycolorway.com" rel="nofollow" target="_blank"&gt;http://mycolorway.com&lt;/a&gt;) 一起进行两个月的产品开发实践。如果各位童鞋觉得自己在学校的积累已够，不想在暑假无所事事而只能帮亲戚朋友攒攒机、重装操作系统的话，欢迎来参加我们的暑期实习，和我们一起开发真正被许多人使用的互联网产品 :)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;我们提供：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1、足够多的解决实际问题的机会，让你在实战中快速提高技术实力；
2、可供选择的实习地点，你可以选择在学校或者在家里，如果选择从外地来成都，则无需担心你的来回机票；
3、丰盛的实习期薪水，保证你能带着鼓囊囊的钱包回到校园。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;申请条件：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1、在读高校学生，有完整且可自由支配的暑期时间；
2、远超同龄人水准的互联网产品开发能力，或互联网产品设计能力；
3、远超同龄人水准的责任心，以及良好的团队沟通、协作能力。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;申请方式：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1、个人简历（形式不限）；
2、目前为止你自己最满意的一份作品，如果可以，请提供代码；
3、从技术或产品的角度，谈谈你对 &lt;a href="http://tower.im" rel="nofollow" target="_blank"&gt;http://tower.im&lt;/a&gt; 的看法。&lt;/p&gt;

&lt;p&gt;请将上述材料发送至 intern@mycolorway.com，并在邮件标题中标明“彩程 2013 暑期实习申请”。&lt;/p&gt;

&lt;p&gt;我们十分期待你的到来~&lt;/p&gt;</description>
      <author>zchar</author>
      <pubDate>Mon, 01 Apr 2013 20:23:45 +0800</pubDate>
      <link>https://ruby-china.org/topics/9907</link>
      <guid>https://ruby-china.org/topics/9907</guid>
    </item>
  </channel>
</rss>
