<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>chrishyman (Madao)</title>
    <link>https://ruby-china.org/chrishyman</link>
    <description>低性能打字机，划水小能手。 http://madao.me</description>
    <language>en-us</language>
    <item>
      <title>我听到有人在说：「?????:??」</title>
      <description>&lt;p&gt;如果有人问你在 Ruby 中执行 &lt;code&gt;?????:??&lt;/code&gt; 会得到什么，如果不尝试执行这个命令，你会觉得结果是什么？&lt;/p&gt;

&lt;p&gt;答案是你会得到返回值："?"，外加一个 warning。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;warning: string literal in condition
=&amp;gt; "?"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;事实上拆解来看懂你就会知道了：?? 在 Ruby 中其实等价于 "?", ?如果在一个单字面前他会把对应的符号变成字符串，官方文档描述如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;?a       #=&amp;gt; "a"
?abc     #=&amp;gt; SyntaxError
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https://ruby-doc.org/core-2.3.0/doc/syntax/literals_rdoc.html#top" rel="nofollow" target="_blank"&gt;https://ruby-doc.org/core-2.3.0/doc/syntax/literals_rdoc.html#top&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;心细的人可能已经发现了，刚刚我这个 &lt;code&gt;?????:??&lt;/code&gt; 其实就是一个三元表达式，也可以写作：&lt;code&gt;"?" ? "?" : "?"&lt;/code&gt;。那么 Ruby Parser 是怎么样知道他应该如何处理这个情况的呢？&lt;/p&gt;

&lt;p&gt;直接打开 Ruby 的源码，找到 parse.y 文件，Ruby Parser 的语法定义来自于 parse.y 这个文件。&lt;/p&gt;

&lt;p&gt;一般来说我们查找这个 parse 的分析顺序，第一个一定会是去看&lt;code&gt;parser_yylex&lt;/code&gt; 方法，因为这个函数做的是分词的动作。找到'?' 这个 case 你就能找到&lt;code&gt;parse_qmark&lt;/code&gt; 这个方法，从源码可以看出。
&lt;img src="https://l.ruby-china.com/photo/2020/7ade2d0d-f374-4681-b831-96f7da12d232.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;所以可以得知，&lt;code&gt;?&lt;/code&gt; 操作符支持单个字符串，也支持 ascii\utf8 以及一些制表符。
&lt;img src="https://l.ruby-china.com/photo/2020/cd4cdcac-45de-493e-b7e6-240533494e50.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;另外顺道一说的是，刚刚的警告 &lt;code&gt;string literal in condition&lt;/code&gt; 的代码发生处是在 &lt;code&gt;cond0&lt;/code&gt; 方法中，另外如果你是用正则作为三元运算符的判断也会发出类似的警告。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2020/0ef060fc-7acc-4976-af48-09af4a0a625d.png!large" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>chrishyman</author>
      <pubDate>Tue, 04 Feb 2020 14:59:25 +0800</pubDate>
      <link>https://ruby-china.org/topics/39481</link>
      <guid>https://ruby-china.org/topics/39481</guid>
    </item>
    <item>
      <title>文言文編程語言 现已支持 Ruby</title>
      <description>&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2019/52019374-ed4b-4c8a-91bf-2dabe0f1cf8d.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;大家可以用这个语言念诗了（大雾）。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/LingDong-/wenyan-lang" rel="nofollow" target="_blank"&gt;https://github.com/LingDong-/wenyan-lang&lt;/a&gt;&lt;/p&gt;</description>
      <author>chrishyman</author>
      <pubDate>Fri, 20 Dec 2019 13:19:41 +0800</pubDate>
      <link>https://ruby-china.org/topics/39359</link>
      <guid>https://ruby-china.org/topics/39359</guid>
    </item>
    <item>
      <title> [Ruby Summit 2018 话题分享] 模块化的 Rails，微服务以外的另一种选择</title>
      <description>&lt;p&gt;拖延症犯了今天才发，这是今年 Ruby Summit 2018 做的一个话题分享，随着主要的技术栈的转移（前端和 Rust），可能以后对于 Rails 架构的分享更多的就会是翻译相关的（TAT 我没有叛逃）。Slides &lt;a href="https://speakerdeck.com/madao/component-based-rails-application-mo-kuai-hua-de-rails-wei-fu-wu-yi-wai-de-ling-chong-xuan-ze" rel="nofollow" target="_blank" title=""&gt;在这里&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;主要分享的内容是 Component Based Rails Application，事实上也是一个 13 年就开始出现了的概念。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;最早相关的书籍是 Stephan Hagemann 的 &lt;a href="http://shageman.github.io/cbra.info/" rel="nofollow" target="_blank" title=""&gt;《CBRA》&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;现在这本书的新版本亚马逊有售：&lt;a href="https://www.amazon.com/Component-Based-Rails-Applications-Addison-Wesley-Professional/dp/0134774582/ref=sr_1_1?ie=UTF8&amp;amp;qid=1539791462&amp;amp;sr=8-1&amp;amp;keywords=Component-Based+Rails+Applications" rel="nofollow" target="_blank" title=""&gt;https://www.amazon.com/...&lt;/a&gt; （翻译本问了出版社那边应该是没希望了）.&lt;/li&gt;
&lt;li&gt;这次分享的实践点也参考了：&lt;a href="/T_Dnzt" class="user-mention" title="@T_Dnzt"&gt;&lt;i&gt;@&lt;/i&gt;T_Dnzt&lt;/a&gt; 的 &lt;a href="http://modular-rails.samurails.com/" rel="nofollow" target="_blank" title=""&gt;《Modular Rails Application》&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;另外，&lt;a href="https://www.joinroot.com/" rel="nofollow" target="_blank" title=""&gt;Root Car Insurance&lt;/a&gt; 这家公司的 Rails 团队最近发布的文章 &lt;a href="https://medium.com/@dan_manges/the-modular-monolith-rails-architecture-fb1023826fc4" rel="nofollow" target="_blank" title=""&gt;The Modular Monolith: Rails Architecture&lt;/a&gt; 也给了我非常多地启发，他们最新的文章 &lt;a href="https://medium.com/root-engineering/separating-data-and-code-in-rails-architecture-3a031e17706b" rel="nofollow" target="_blank" title=""&gt;Separating Data and Code in Rails Architecture&lt;/a&gt; 对于实现 stateless code 的概念也是让我眼前一亮。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最终关于我这次分享的 CBRA(Component Based Rails Application)，我最主要想要表达的点，就是可以「疼痛度最低」地简化一个巨大臃肿的单体应用（monolith），并且给未来的拆分做好准备。&lt;strong&gt;但另一方面，这次演说没说到的是：组件化的的 Rails 开发，不一定要从业务领域来起步的，他本质还是提供了你一种插件化开发一些特性的选择而已。&lt;/strong&gt;&lt;/p&gt;</description>
      <author>chrishyman</author>
      <pubDate>Thu, 18 Oct 2018 00:02:52 +0800</pubDate>
      <link>https://ruby-china.org/topics/37643</link>
      <guid>https://ruby-china.org/topics/37643</guid>
    </item>
    <item>
      <title>说说 Web 无障碍设计</title>
      <description>&lt;h2 id="何为无障碍？"&gt;何为无障碍？&lt;/h2&gt;
&lt;p&gt;好的网站设计是具有人文气息的，不仅仅是视觉的美观，加载渲染速度的快慢。而是使尽可能多的人能够使用 Web 站点，即便他们有或多或少的缺陷。而「无障碍设计」，就是现代 Web 开发中一个值得关注的分支，来让我们开发易访问的内容的网站。它主要体现在以下几点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;让视觉障碍者可以通过「屏幕阅读器」（Screenreaders）得到友善的听觉体验。&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;h2 id="何为 WAI-ARIA?"&gt;何为 WAI-ARIA?&lt;/h2&gt;
&lt;p&gt;关于 WAI-ARIA (Web Accessibility Initiative's Accessible Rich Internet Applications 也就是：Web 无障碍倡议 - 无障碍富互联网应用) 具体可以看我翻译的：&lt;a href="https://developer.mozilla.org/zh-CN/docs/learn/Accessibility/WAI-ARIA_basics" rel="nofollow" target="_blank" title=""&gt;WAI-ARIA basics&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id="WAI-ARIA 核心内容有哪些？"&gt;WAI-ARIA 核心内容有哪些？&lt;/h2&gt;&lt;h3 id="1.Signpost and Landmark (路牌与地标)"&gt;1.Signpost and Landmark (路牌与地标)&lt;/h3&gt;
&lt;p&gt;WAI-ARIA 给浏览器增加了 role 属性，这允许我们给站点中的元素增加我们想要的语义属性。ARIA 的 角色 属性值可以作为地标 (Landmark) 来复制 HTML5 元素的语义化（例如 nav tag）。甚至超越 HTML5 的语义，给不同的功能块提供路标（Signpost），例如 search, tabgroup, tab, listbox 等等。&lt;/p&gt;
&lt;h4 id="用法举例："&gt;用法举例：&lt;/h4&gt;&lt;h5 id="如何用role"&gt;如何用 role&lt;/h5&gt;
&lt;p&gt;打开地址： &lt;a href="https://mdn.github.io/learning-area/accessibility/aria/website-aria-roles/" rel="nofollow" target="_blank"&gt;https://mdn.github.io/learning-area/accessibility/aria/website-aria-roles/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;然后使用 VoiceOver 绑定键（你可以在 VoiceOver Utility 中设置） + U 访问，然后使用光标或者键盘来选择菜单选项。由于是结构化的内容，你通过键盘上下就能听到对应的标题内容来跳转需要的内容。
你就能看到：&lt;img src="https://user-gold-cdn.xitu.io/2018/8/12/1652e1d37e8672f4?w=729&amp;amp;h=532&amp;amp;f=png&amp;amp;s=7149" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;每一块都很分明，做的不好的，Landmarks 直接没有，或者一个页面一个 banner 三个 navigation。&lt;/p&gt;
&lt;h5 id="2.aria-label"&gt;2.aria-label&lt;/h5&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;input type="search" name="q" placeholder="Search query" aria-label="Search through site content"&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样当你聚焦的时候就会 VoiceOver 会念出'Search through site content'。&lt;/p&gt;
&lt;h5 id="3.aria-labelby"&gt;3.aria-labelby&lt;/h5&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;input type="search" name="q" placeholder="Search query" aria-labelby="searchLabel"&amp;gt;
&amp;lt;span id='searchLabel'&amp;gt;Search through site content&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2.动态内容更新"&gt;2.动态内容更新&lt;/h3&gt;
&lt;p&gt;内容动态更新是对于无障碍是一个很麻烦的事情，因为早年的网站并没有这个困扰，所以 WAI-ARIA 增加一个新的概念：实时区域（live region）。用属性 aria-live 便可以触发实时区域（role="alert" 也可以），它有四个值：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;off: 默认值，更新不会提醒。&lt;/li&gt;
&lt;li&gt;polite:  只有用户空闲的情况下提醒。&lt;/li&gt;
&lt;li&gt;assertive: 尽快提醒。&lt;/li&gt;
&lt;li&gt;rude: 会以打断用户操作的方式直接提醒。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;通常来说 assertive 设置足以让你的更新在显示时按时序读出，因此，如果改变多次，那么屏幕阅读器也只会念出最后一个改变。&lt;/p&gt;

&lt;p&gt;那么就会有一个问题，如果不断增加子元素，那就会不断的出现提示，增加的子元素的内容。&lt;/p&gt;

&lt;p&gt;通过添加 aria-atomic="true" 属性告诉屏幕阅读器去读取整个元素的内容作为一个整体来判断是否提示已修改，&amp;nbsp;而不是里头某个子元素增加删除了。例子如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;section aria-live="assertive" aria-atomic="true"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上内容大略的说说了几个重点，至于具体全面的 aria-* 属性/状态，请看 &lt;a href="https://www.w3.org/TR/wai-aria-1.1/#state_prop_def" rel="nofollow" target="_blank" title=""&gt;Definitions of States and Properties (all aria-* attributes) &lt;/a&gt;，关于角色，请看&lt;a href="https://www.w3.org/TR/wai-aria-1.1/#role_definitions" rel="nofollow" target="_blank" title=""&gt;Definition of Roles&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="3.优化键盘的无障碍操作"&gt;3.优化键盘的无障碍操作&lt;/h3&gt;
&lt;p&gt;Tab 和确认键是 HTML 默认实现的无障碍操作，通过对 tabindex 的合理使用（并不是到处乱用），可以让 webpage 的操作体验变得更好，另一方面组合 aria-setsize 和 aria-posinset 能最大化给屏幕阅读器提供信息。
&lt;a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Global_attributes/tabindex" rel="nofollow" target="_blank" title=""&gt;tabindex 文档&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="4.非语义控件的可访问性"&gt;4.非语义控件的可访问性&lt;/h3&gt;
&lt;p&gt;这是现代复杂 Web 应用的最大问题，为了实现复杂需求会大量使用非语义元素来实现一个控件，对于屏幕阅读器而言是灾难，对于使用者而言页面等于不可用。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;首先还是离不开角色，如果你想用 a 标签做一个 button。可以，加上&lt;code&gt;role="button"&lt;/code&gt; 即可。&lt;/li&gt;
&lt;li&gt;熟悉表单验证用的常规状态属性 aria-required/aria-disabled/aria-selected 等属性。&lt;/li&gt;
&lt;li&gt;另外理解刚刚说过的 aria-setsize 和 aria-posinset 能最大化给屏幕阅读器提供信息。&lt;/li&gt;
&lt;li&gt;aria-hidden="true"&amp;nbsp;对屏幕阅读器进行一定程度的隐藏。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="If You Can't Measure It, You Can't Improve It"&gt;If You Can't Measure It, You Can't Improve It&lt;/h2&gt;
&lt;p&gt;彼得•德鲁克（Peter F. Drucker）说过这么一句话，那么我们介绍两款相关的软件用来测量网站对于无障碍的实现程度。&lt;/p&gt;
&lt;h3 id="The A11y Machine"&gt;The A11y Machine&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -g the-a11y-machine

a11ym --standards WCAG2AA,HTML https://developer.mozilla.org/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后打开当前目录下的 &lt;code&gt;./a11ym_output/index.html&lt;/code&gt; ，你可以看到它对于整体的分析结果。然后 note codes 指向的会是每一个 Web 内容无障碍指南（WCAG）的每一个实现细则，你可以就像完成一个个代码风格错误一样逐个根据情况修复它。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://user-gold-cdn.xitu.io/2018/8/12/1652e1d2eac0bad1?w=2006&amp;amp;h=1504&amp;amp;f=jpeg&amp;amp;s=318818" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="Accessibility Developer Tools"&gt;Accessibility Developer Tools&lt;/h3&gt;
&lt;p&gt;这是一款由谷歌可访问性团队开发的谷歌 &lt;a href="https://chrome.google.com/webstore/detail/accessibility-developer-t/fpkknkljclfencbdbgkenhalefipecmb?hl=en" rel="nofollow" target="_blank" title=""&gt;Chrome Extension&lt;/a&gt;。
然后操作如图：
&lt;img src="https://user-gold-cdn.xitu.io/2018/8/12/1652e1d2eae68acb?w=1438&amp;amp;h=1420&amp;amp;f=jpeg&amp;amp;s=182393" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;顺带一说，Google 的 Audits 功能是基于 &lt;a href="https://github.com/GoogleChrome/lighthouse" rel="nofollow" target="_blank" title=""&gt;Lighthouse&lt;/a&gt; 的，关于它可以再写一整篇文章，按下不表。&lt;/p&gt;

&lt;p&gt;然后得到的结果会是这样的：
&lt;img src="https://user-gold-cdn.xitu.io/2018/8/12/1652e1d2e9cf6c67?w=1444&amp;amp;h=1288&amp;amp;f=jpeg&amp;amp;s=278378" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;他的评价体系比起 a11ym 不可不谓温和。但是根据它给出的信息，我们还是可以进行对应的优化。&lt;/p&gt;

&lt;p&gt;比起发达国家，国内的互联网技术的发展日新月异，但是从大部分的主流网站可以看得出，除开大厂，大家对于无障碍的支持是不够的。但我觉得这仅仅是出于不了解，如果不得而知，那么就无从做起，所以我们更多地提及无障碍技术，推广它，便能让中文互联网世界的无障碍更进一步。&lt;/p&gt;

&lt;p&gt;所以像 Leonie Watson 说的：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It doesn't have to be perfect, Just a little bit better that yesterday.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src="https://user-gold-cdn.xitu.io/2018/8/12/1652e1d2eab7d23c?w=1034&amp;amp;h=1026&amp;amp;f=jpeg&amp;amp;s=176206" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;推荐阅读：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/Translations/WCAG20-zh" rel="nofollow" target="_blank" title=""&gt;Web 内容无障碍指南 (WCAG) 2.0
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/zh-CN/docs/Learn/Accessibility/WAI-ARIA_basics" rel="nofollow" target="_blank" title=""&gt;Web 无障碍倡议 - 无障碍富互联网应用 基础&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="http://madao.me/wai-aria/" rel="nofollow" target="_blank" title=""&gt;博客原文&lt;/a&gt;&lt;/p&gt;</description>
      <author>chrishyman</author>
      <pubDate>Mon, 13 Aug 2018 10:35:51 +0800</pubDate>
      <link>https://ruby-china.org/topics/37318</link>
      <guid>https://ruby-china.org/topics/37318</guid>
    </item>
    <item>
      <title>[译] 再见微服务，从 100 多个问题儿童到一个超级明星</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;本文翻译自 Alexandra Noonan 的 &lt;a href="https://segment.com/blog/goodbye-microservices/" rel="nofollow" target="_blank" title=""&gt;Goodbye Microservices: From 100s of problem children to 1 superstar&lt;/a&gt;。&lt;br&gt;
本文的内容是描述 &lt;a href="http://segment.com" rel="nofollow" target="_blank" title=""&gt;Segment&lt;/a&gt; 的架构如何从「单体应用」 -&amp;gt;  「微服务」 -&amp;gt; 「140+ 微服务」 -&amp;gt; 「单体应用」的一个历程。&lt;br&gt;
翻译比较粗糙，如有疏漏，请不吝指教。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;注：下文说的&lt;code&gt;目的地&lt;/code&gt;就是对应的不同的数据平台（例如 Google Analytics，Optimizely）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;除非你生活在石器时代，不然你一定知道「微服务」是当世最流行的架构。我们&lt;a href="https://segment.com/" rel="nofollow" target="_blank" title=""&gt;Segment&lt;/a&gt;早在 2015 年就开始实践这一架构。这让我们在一些方面上吃了不少甜头，但很快我们发现：在其他场景，他时不时让我们吃了苦头。&lt;/p&gt;

&lt;p&gt;简而言之，微服务是一个面向服务的软件架构，每一个服务端的程序都是朝着一个单一目标构建的（每一个服务会占据较小的面积）。微服务的主要宣传点在于：模块化优化，减少测试负担，更好的功能组成，环境独立，而且开发团队是自治的（因为每一个服务的内部逻辑是自洽且独立的）。而另一头的单体应用：「巨大无比且难以测试，而且服务只能作为一个整理来伸缩（如果你要提高某一个服务的性能，只能把服务器整体提高）」&lt;/p&gt;

&lt;p&gt;2017 早期我们陷入了僵局，复杂的微服务树让我们的开发效率骤减，并且每一个开发小组都发现自己每次实现都会陷入巨大的复杂之中，此时，我们的缺陷率也迅速上升。&lt;/p&gt;

&lt;p&gt;最终，我们不得不用三个全职工程师来维护每一个微服务系统的正常运行。这次我们意识到改变必须发生了，本文会讲述我们如何后退一步，让团队需要和产品需求完全一致的方法。&lt;/p&gt;
&lt;h2 id="为什么微服务可行曾经可行？"&gt;为什么微服务&lt;del&gt;可行&lt;/del&gt;曾经可行？&lt;/h2&gt;
&lt;p&gt;Segment 的客户数据基础设施吸收每秒成百上千个事件，将每一个伙伴服务的 API 请求结果一个个返回给对应的服务端的「目的地」。而「目的地」有上百种类别，例如 Google Analytics，Optimizely，或者是一些自定义的 webhook。&lt;/p&gt;

&lt;p&gt;几年前，当产品初步发布，当时架构很简单。仅仅是一个接收事件并且转发的消息队列。在这个情况下，事件是由 Web 或移动应用程序生成的 JSON 对象，例子如下：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;identify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;traits&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alex Noonan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;anoonan@segment.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;company&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Segment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Software Engineer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;userId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;97980cfea0067&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;事件是从队列中消耗的，客户的设置会决定这个事件将会发送到哪个目的地。这个事件被纷纷发送到每个目的地的 API，这很有用，开发人员只需要将他们的事件发送到一个特定的目的地——也就是 Segment 的 API，而不是你自己实现几十个项目集成。&lt;/p&gt;

&lt;p&gt;如果一个请求失败了，有时候我们会稍后重试这个事件。一些失败的重试是安全的，但有些则不。可重试的错误可能会对事件目的地不造成改变，例如：50x 错误，速率限制，请求超时等。不可重试的错误一般是这个请求我们确定永远都不会被目的地接受的。例如：请求包含无效的认证亦或是缺少必要的字段。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://ws3.sinaimg.cn/large/006tKfTcly1fte9b42k0mj31b00ocn51.jpg" title="" alt="asset_QhV9GV3"&gt;&lt;/p&gt;

&lt;p&gt;此时，一个简单的队列包含了新的事件请求以及若干个重试请求，彼此之间事件的目的地纵横交错，会导致的结果显而易见：&lt;a href="https://zh.wikipedia.org/wiki/%E9%98%9F%E5%A4%B4%E9%98%BB%E5%A1%9E" rel="nofollow" target="_blank" title=""&gt;队头阻塞&lt;/a&gt;。意味着在这个特定的场景下，如果一个目的地变慢了或者挂掉了，重试请求将会充斥这个队列，从而整个请求队列会被拖慢。&lt;/p&gt;

&lt;p&gt;想象下我们有一个 &lt;code&gt;目的地 X&lt;/code&gt; 遇到一个临时问题导致每一个请求都会超时。这不仅会产生大量尚未到达&lt;code&gt;目的地 X&lt;/code&gt;的请求，而且每一个失败的事件将会被送往重试的队列。即便我们的系统会根据负载进行弹性伸缩，但是请求队列深度突然间的增长会超过我们伸缩的能力，结果就是新的时间推送会延迟。发送时间到每一个目的地的时间将会增加因为目的地 X 有一个短暂的停止服务（因为临时问题）。客户依赖于我们的实时性，所以我们无法承受任何程度上的缓慢。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://ws1.sinaimg.cn/large/006tKfTcly1fte9bonruij31ay0o647d.jpg" title="" alt="asset_joMYeSNt"&gt;&lt;/p&gt;

&lt;p&gt;为了解决这个队头阻塞问题，我们团队给每一个目的地都分开实现了一个队列，这种新架构由一个额外的路由器进程组成，该进程接收入站事件并将事件的副本分发给每个选定的目标。现在如果一个目的地有超时问题，那么也仅仅是这个队列会进入阻塞而不会影响整体。这种「微服务风格」的架构分离把目的地彼此分开，当一个目的地老出问题，这种设计就显得很关键了。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://ws2.sinaimg.cn/large/006tKfTcly1fte9bwuu2mj31au0peqc6.jpg" title="" alt="asset_ElKl4CZv"&gt;&lt;/p&gt;
&lt;h2 id="个人Repo 的例子"&gt;个人 Repo 的例子&lt;/h2&gt;
&lt;p&gt;每一个目的地的 API 的请求格式都不同，需要自定义的代码去转换事件来匹配格式。一个简单的例子：还是&lt;code&gt;目的地X&lt;/code&gt;，有一个更新生日的接口，作为请求内容的格式字段为 &lt;code&gt;dob&lt;/code&gt; ，API 会对你要求字段为 &lt;code&gt;birthday&lt;/code&gt;，那么转换代码就会如下：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;traits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="nx"&gt;traits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;segmentEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;birthday&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;许多现代的目的地终点都用了 Segment 的请求格式，所以转换会很简单。但是，这些转换也可能会十分复杂，取决于目的地 API 的结构。&lt;/p&gt;

&lt;p&gt;起初，目的地分成几个拆分的服务的时候，所有的代码都会在一个 repo 里。一个巨大的挫折点就是一个测试的失败常常会导致整个项目测试无法跑通。我们可能会为此付出大量的时间只是为了让他像之前一样正常运行通过测试。为了解决这个问题，我们把每一个服务都拆分成一个单独的 repo，所有的目的地的测试错误都只会影响自己，这个过渡十分自然。&lt;/p&gt;

&lt;p&gt;拆分出来的 repo 来隔离开每一个目的地会让测试的实现变得更容易，这种隔离允许开发团队快速开发以及维护每一个目的地。&lt;/p&gt;
&lt;h2 id="伸缩微服务和Repo 们"&gt;伸缩微服务和 Repo 们&lt;/h2&gt;
&lt;p&gt;随着时间的偏移，我们加了 50 多个新的目的地，这意味着有 50 个新的 repo。为了减轻开发和维护这些 codebase 的负担，我们创建一个共享的代码库来做实现一些通用的转换和功能，例如 HTTP 请求的处理，不同目的地之间代码实现更具有一致性。&lt;/p&gt;

&lt;p&gt;例如：如果我们要一个事件中用户的名字，&lt;code&gt;event.name()&lt;/code&gt; 可以是任何一个目的地里头的调用。共享的类库会去尝试判断 event 里的 name 或者 Name 属性，如果没有，他会去查 first name，那么就回去查找 first_name 和 FirstName，往下推：last name 也会做这样的事情。然后吧 first name 和 last name 组合成 full name.&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Identify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;traits.name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&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;当我们实现了自动伸缩的实现，每个服务都具有所需 CPU 和内存资源的明显混合，这让我们的自动伸缩配置与其说是科学的，不如说更具有艺术性（其实就是蒙的）。&lt;/p&gt;

&lt;p&gt;目的地的数量极速增长，团队以每个月三个（目的地）的速度增长着，这意味着更多的 repo，更多的队列，更多的服务。我们的微服务架构的运维成本也是线性地增长着。因此，我们决定退后一步，重新考虑整个流程。&lt;/p&gt;
&lt;h2 id="深挖微服务以及队列"&gt;深挖微服务以及队列&lt;/h2&gt;
&lt;p&gt;这时列表上第一件事就是如何巩固当前超过 140 个服务到一个服务中，管理所有服务的带来的各种成本成了团队巨大的技术债务。运维工程师几乎无眠，因为随时出现的流量峰值必须让工程师随时上线处理。&lt;/p&gt;

&lt;p&gt;尽管如此，当时把项目变成单一服务的架构是一个巨大的挑战。要让每一个目的地拥有一个分离的队列，每一个 worker 进程需要检查检查每一队列是否运行，这种给目的地服务增加一层复杂的实现让我们感到了不适。这是我们「离心机」的主要灵感来源，「离心机」将替换我们所有的个体队列，并负责将事件发送到一个单体服务。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;译者注： 「离心机」其实就是 Segment 制作的一个事件分发系统。 &lt;a href="https://segment.com/blog/introducing-centrifuge/" rel="nofollow" target="_blank" title=""&gt;相关地址&lt;/a&gt;
&lt;img src="https://ws3.sinaimg.cn/large/006tNc79ly1fteyg0gv1kj31bw0d2gq7.jpg" title="" alt="asset_a0ViVzT6"&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="搬到一个单体Repo"&gt;搬到一个单体 Repo&lt;/h2&gt;
&lt;p&gt;所以我们开始把所有的目的地代码合并到了一个 repo，这意味着所有的依赖和测试都在一个单一的 repo 里头了，我们知道我们要面对的，会是一团糟。&lt;/p&gt;

&lt;p&gt;120 个依赖，我们都提交了一个特定的版本让每一个目的地都兼容。当我们搬完了目的地，我们开始检查每一个对应的代码是否都是用的最新的依赖。我们保证每一个目的地在最新的依赖版本下，都能正确运行。&lt;/p&gt;

&lt;p&gt;这些改变中，我们再也不用跟踪依赖的版本了。所有目的地都使用同一版本，这显著地减小了 codebase 的代码复杂度。维护目的地变得快捷而且风险也变小了。&lt;/p&gt;

&lt;p&gt;另一方面我们也需要测试能简单快速地运行起来，之前我们得出的结论之一就是：「不去修改共享库文件主要的阻碍就是得把测试都跑一次。」&lt;/p&gt;

&lt;p&gt;幸运的是，目的地测试都有着相似的架构。他们都有基础的单元测试来验证我们的自定义转换逻辑是否正确，而且也能验证 HTTP 的返回是否符合我们的期望值。&lt;/p&gt;

&lt;p&gt;回想起我们的出新是分离每一个目的地的 codebase 到各自的 repo 并且分离各自测试的问题。尽管如此，现在看来这个想法是一个虚假的优势。HTTP 请求的发送仍然以某种频率失败着。因为目的地分离到各自的 repo，所以大家也没有动力去处理这类失败的请求。这也让我们走进了某种令人沮丧的恶性循环。本应只需几个小时的小改动常常要花上我们几天甚至一周的时间。&lt;/p&gt;
&lt;h2 id="构建一个弹性测试套件"&gt;构建一个弹性测试套件&lt;/h2&gt;
&lt;p&gt;给目的地发送的 HTTP 请求失败是我们主要的失败测试原因，过期凭证等无关的问题不应该使测试失败。我们从中也发现一些目的地的请求会比其他目的地慢不少。一些目的地的测试得花上 5 分钟才能跑完，我们的测试套件要花上一小时时间才能全部跑完。&lt;/p&gt;

&lt;p&gt;为了解决这个问题，我们制作了一个「Traffic Recorder」，「Traffic Recorder」是一个基于 yakbak 实现的工具，用于记录并且保存一些请求。无论何时一个测试在他第一次跑的时候，对应的请求都会被保存到一个文件里。后来的测试跑的时候，就会复用里头的返回结果。同时这个请求结果也会进入 repo，以便在测试中也是一致的。这样一来，我们的测试就不再依赖于网络 HTTP 请求，为了接下来的单一 repo 铺好了路。&lt;/p&gt;

&lt;p&gt;记得第一次整合「Traffic Recorder」后，我们尝试跑一个整体的测试，完成 140+ 目的地的项目整体测试只需几毫秒。这在过去，一个目的地的测试就得花上几分钟，这快得像魔术一般。&lt;/p&gt;
&lt;h2 id="为何单体应用可行"&gt;为何单体应用可行&lt;/h2&gt;
&lt;p&gt;只要每个目的地都被整合到一个 repo，那么他就能作为一个单一的服务运行。所有目的地都在一个服务中，开发团队的效率显著提高。我们不因为修改了共享库而部署 140+ 个服务，一个工程师可以一分钟内重新完成部署。&lt;/p&gt;

&lt;p&gt;速度是肉眼可见地被提升了，在我们的微服务架构时期，我们做了 32 个共享库的优化。再变成单体之后我们做了 46 个，过去 6 个月的优化甚至多过 2016 年整年。&lt;/p&gt;

&lt;p&gt;这个改变也让我们的运维工程师大为受益，每一个目的地都在一个服务中，我们可以很好进行服务的伸缩。巨大的进程池也能轻松地吸收峰值流量，所以我们也不用为小的服务突然出现的流量担惊受怕了。&lt;/p&gt;
&lt;h2 id="坏处"&gt;坏处&lt;/h2&gt;
&lt;p&gt;尽管改变成单体应用给我们带来巨大的好处，尽管如此，以下是坏处：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;故障隔离很难，所有东西都在一个单体应用运行的时候，如果一个目的地的 bug 导致了服务的崩溃，那么这个目的地会让所有的其他的目的地一起崩溃（因为是一个服务）。我们有全面的自动化测试，但是测试只能帮你一部分。我们现在在研究一种更加鲁棒的方法，来让一个服务的崩溃不会影响整个单体应用。&lt;/li&gt;
&lt;li&gt;内存缓存的效果变低效了。之前一个服务对应一个目的地，我们的低流量目的地只有少量的进程，这意味着他的内存缓存可以让很多的数据都在热缓存中。现在缓存都分散给了 3000+ 个进程所以缓存命中率大大降低。最后，我们也只能在运维优化的前提下接受了这一结果。&lt;/li&gt;
&lt;li&gt;更新共享库代码的版本可能会让几个目的地崩溃。当把项目整合的到一起的时候，我们解决过之前的依赖问题，这意味着每个目的地都能用最新版本的共享库代码。但是接下来的共享库代码更新意味着我们可能还需要修改一些目的地的代码。在我们看来这个还是值得的，因为自动化测试环节的优化，我们可以更快的发现新的依赖版本的问题。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="结论"&gt;结论&lt;/h2&gt;
&lt;p&gt;我们起初的微服务架构是符合当时的情况的，也解决了当时的性能问题还有目的地之间孤立实现。尽管如此，我们没有准备好服务激增的改变准备。当需要批量更新时，我们缺乏适当的工具来测试和部署微服务。结果就是，我们的研发效率因此出现了滑坡。&lt;/p&gt;

&lt;p&gt;转向单体结构使我们能够摆脱运维问题，同时显着提高开发人员的工作效率。我们并没有轻易地进行这种转变，直到确信它能够发挥作用。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;我们需要靠谱的测试套件来让所有东西都放到一个 repo。没有它，我们可能最终还是又把它拆分出去。频繁的失败测试在过去损害了我们的生产力，我们不希望再次发生这种情况。&lt;/li&gt;
&lt;li&gt;我们接受一些单体架构的固有的坏处而且确保我们能最后得到一个好的结果。我们对这个牺牲是感到满意的。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在单体应用和微服务之间做决定的时候，有些不同的因素是我们考虑的。在我们基础设施的某些部分，微服务运行得很好。但我们的服务器端，这种架构也是真实地伤害了生产力和性能的完美示例。但到头来，我们最终的解决方案是单体应用。&lt;/p&gt;

&lt;p&gt;&lt;a href="http://madao.me/goodbye-microservices/" rel="nofollow" target="_blank" title=""&gt;博客地址&lt;/a&gt;&lt;/p&gt;</description>
      <author>chrishyman</author>
      <pubDate>Thu, 19 Jul 2018 10:24:46 +0800</pubDate>
      <link>https://ruby-china.org/topics/37189</link>
      <guid>https://ruby-china.org/topics/37189</guid>
    </item>
    <item>
      <title>安利一个活动 Ludum dare，顺便记一下这次参加 GameJam 的经历</title>
      <description>&lt;p&gt;我的 LD 41 刚刚结束啦！这里给大家分享一下这一次开发竞赛的记录。&lt;/p&gt;

&lt;p&gt;先介绍下 LD，也就是 Ludum Dare, Ludum Dare 是一个经常性以推进个人游戏开发为目的的开发竞赛。参与者需要在 48 小时内针对所设定的主题创作游戏。Ludum Dare 是 Geoff Howland 一手建立的，第一次举办在 2002 年 4 月。&lt;/p&gt;

&lt;p&gt;我参加的这一届已经是第 41 届了，早上 9 点，LD 这边就统一公布主题。&lt;/p&gt;

&lt;p&gt;这次的主题是&lt;strong&gt;Combine 2 Incompatible Genres&lt;/strong&gt;（合并两种不相容的【游戏】类型）。&lt;/p&gt;

&lt;p&gt;在我毫无头绪的时候，已经有好事者做了这个东西 &lt;a href="https://boltkey.cz/ld41roller/" rel="nofollow" target="_blank"&gt;https://boltkey.cz/ld41roller/&lt;/a&gt;。一个随机的两个主题组合生成器。生了几次我看到了一个心水的主题。
&lt;img src="https://ws4.sinaimg.cn/large/006tKfTcly1fqnu8mjap2j30hi0agdgf.jpg" title="" alt="Screen Shot 2018-04-21 at 17.40.20"&gt;&lt;/p&gt;

&lt;p&gt;可编程的游戏+roguelike，主题定了，接下来就是磨素材和故事大纲了。#还剩下 1 天 23 小时。&lt;/p&gt;

&lt;p&gt;我参加的类型是 compo ,LD 有两种参赛类型，一种是 Compo，要求单人作战，48 小时，最终要提交源代码。
另一种是 Jam 允许团队参赛、不提供代码，并将时间延长至 72 小时。&lt;/p&gt;

&lt;p&gt;时间还早，就去做家务做做饭，顺便构思下游戏的思路以及要准备的素材了。#还剩下 1 天 20 小时。&lt;/p&gt;

&lt;p&gt;开始憋素材，素材的绘制都是用的 Aseprite。
&lt;img src="https://ws4.sinaimg.cn/large/006tKfTcly1fqnu92vax8j31kw0ivq4l.jpg" title="" alt="Screen Shot 2018-04-22 at 15.00.30"&gt;&lt;/p&gt;

&lt;p&gt;中间做饭，打打游戏，磨磨蹭蹭，晚饭后开始实现游戏逻辑。（还剩下 36 小时&lt;/p&gt;

&lt;p&gt;这次使用的开发框架是 Phaser，由于之前从未用过，花了一些时间看了文档。（还剩下 35 小时&lt;/p&gt;

&lt;p&gt;把需要实现的类都写一下，然后就去玩游戏了。（还剩下 34 小时&lt;/p&gt;

&lt;p&gt;&lt;img src="https://ws2.sinaimg.cn/large/006tKfTcly1fqnuaoeuioj30op03fjre.jpg" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;睡觉。&lt;/p&gt;

&lt;p&gt;第二天开始热火朝天地实现大部分的内容，开始构思道具和玩什么梗（尽管由于实现的太糙看不太出来。&lt;/p&gt;

&lt;p&gt;实现各个类的内容，调整地图。（还有 12 小时&lt;/p&gt;

&lt;p&gt;想到第二天还得 9 点去洗牙，所以预估好 8 点提交游戏。于是乎 10 点就睡觉，周一早上 5 点起来再战。（还有 10 小时&lt;/p&gt;

&lt;p&gt;&lt;img src="https://ws1.sinaimg.cn/large/006tKfTcly1fqnu9f35r1j31b00uytiw.jpg" title="" alt="Screen Shot 2018-04-23 at 08.09.01"&gt;&lt;/p&gt;

&lt;p&gt;早上 8 点，这款可编程的死循环模拟器就做完了。提交 LD，出门去看牙医。&lt;/p&gt;

&lt;p&gt;最后贴上我这个充满 BUG 的可编程死循环 roguelike 模拟器的地址： &lt;a href="https://ldjam.com/events/ludum-dare/41/need-for-sleep" rel="nofollow" target="_blank"&gt;https://ldjam.com/events/ludum-dare/41/need-for-sleep&lt;/a&gt;&lt;/p&gt;</description>
      <author>chrishyman</author>
      <pubDate>Tue, 24 Apr 2018 16:49:00 +0800</pubDate>
      <link>https://ruby-china.org/topics/35523</link>
      <guid>https://ruby-china.org/topics/35523</guid>
    </item>
    <item>
      <title>如何教会小明实现 Rails 的 Validation</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;本主题来源于公司内部每周分享，我们正在招聘 Ruby 工程师和架构师欢迎查看我们的招聘信息 &lt;a href="https://ruby-china.org/topics/35085" rel="nofollow" target="_blank"&gt;https://ruby-china.org/topics/35085&lt;/a&gt;
  或直接投递简历到 &lt;strong&gt;likai@uniqueway.com&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;前言：直接写一份教程实现未免生涩，所以我尝试用 8bit 漫画的方式写一个教程，渣画技，试试水。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;图多不看党可以直接看 &lt;a href="https://github.com/Madao-3/HowToCreateValidation" rel="nofollow" target="_blank" title=""&gt;github 地址&lt;/a&gt;
&lt;img src="https://l.ruby-china.com/photo/2018/d699b2b2-f58a-4e26-8325-142269e02c46.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/2018/4b1e6e29-3d63-478e-b8a7-155b034fc61b.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/e299392c-45ed-4d9e-a12d-f44450083da1.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/ca089ba1-5be4-4de8-a458-5a035291c135.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;数学家波利亚说过：「观察未知量！并尽量想出一道你所熟悉的具有相同未知量或者相似题目未知量的题目。」&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我们先要尝试归纳出我们需要的或者说已知的内容——「一个 ActiveModel Validator 能实现什么和做到什么？」&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;可以通过 validates 类方法来验证需要验证的字段&lt;/li&gt;
&lt;li&gt;具有 Valid? 方法，可以返回布尔值&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/73e57220-cbf9-43dc-95f0-df63cebd5ad1.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/2018/e366161d-f8e2-429b-a276-7258661fe313.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/2018/75bbea53-3ca6-44ac-85b1-d62f6bdbbbd9.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/2018/2f32e4bc-fa58-4fae-bff3-5600075b1b80.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/2018/bd73aba2-d1b6-4d5a-a94a-69df10d8dec8.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/2018/fb346a69-d580-4780-97fd-97af6de768b5.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/2018/c2756b13-0a77-475c-9ca5-f7c8d04b0a4c.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/2018/ac622d47-8426-4fa0-8d1a-c96c51bcd317.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/2018/8123c73d-13c8-4537-a4e4-2b22f8abe51f.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/2018/60202aac-fbbf-4bfe-94e0-df495ebb64e8.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/2018/40bfb60c-3dc0-44db-9f69-2318523a1bdb.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/2018/6f7a932c-7ca2-4745-ab34-e836360b304a.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2018/c9228ba4-be50-4507-8192-3c49529e8ee8.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="课后笔记"&gt;课后笔记&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;这里我们实现了一个最基本的 Validation。&lt;/li&gt;
&lt;li&gt;事实上 Rails 的 Validation 更加强大，vaild? 方法执行后会更新执行对象的 errors 变量；options 另外还有类似 on: :create 这类的限制选项；除开 valid? 还有 invalid? 和 validate! （该方法会抛出异常）方法。&lt;/li&gt;
&lt;li&gt;Rails 的 Validation 支持 custom Validator (其实就是少了 validates_with 的实现)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="练习题："&gt;练习题：&lt;/h3&gt;
&lt;p&gt;如何实现 Custom Validator 呢？&lt;/p&gt;</description>
      <author>chrishyman</author>
      <pubDate>Tue, 20 Mar 2018 17:44:35 +0800</pubDate>
      <link>https://ruby-china.org/topics/35273</link>
      <guid>https://ruby-china.org/topics/35273</guid>
    </item>
    <item>
      <title>五分钟玩一下 Headless Chrome</title>
      <description>&lt;p&gt;是什么让前端工程师狂欢，让 PhantomJS 维护者宣告失业，是大企业的良心泯灭，还是 chrome 59 之后的一个新坑？&lt;/p&gt;

&lt;p&gt;—— 让我们走进 Headless Chrome。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;一、浅尝辄止&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;让我们先感受下这个东西：&lt;/p&gt;

&lt;p&gt;Chrome 的应用程序路径 &lt;code&gt;/Contents/MacOS/Google\ Chrome --headless --remote-debugging-port=8888 http://baidu.com/&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;remote-debugging-port 是你的绑定端口号&lt;/li&gt;
&lt;li&gt;后面这个 baidu 是你要直接生成解析的地址（另外你还有很多体位可以玩弄，例如 --dump-dom【解析 dome】， --print-to-pdf【生成 pdf】， --screenshot【截图】, --remote-debugging-port 和上述功能不兼容，记得去掉。 ）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;二、试&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;首先我们需要安装 puppeteer (Chrome 提供的一个 Headless API 封装库，安装前请确保自己 Node 的版本在 7.10 以上)&lt;/p&gt;

&lt;p&gt;puppeteer 的安装体积会比较大，因为他会下载一个 chrome 内核进来。&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;puppeteer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://google.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就是一个创建截图的例子。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;三、悟&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;官方背景加上非常勤的 API Update，以后服务端解析确实是没 PhantomJS 啥事了。而且 Headless 的 API 支持大量的 Event 操作，以后前端测试的未来就是它了。&lt;/p&gt;</description>
      <author>chrishyman</author>
      <pubDate>Thu, 17 Aug 2017 11:44:19 +0800</pubDate>
      <link>https://ruby-china.org/topics/33858</link>
      <guid>https://ruby-china.org/topics/33858</guid>
    </item>
    <item>
      <title>每个 Ruby 开发者都需要知道的 5 个 Pry 技巧</title>
      <description>&lt;p&gt;觉得有点意思，就从&lt;a href="https://blog.cognitohq.com/five-pry-features-every-ruby-developer-should-know/" rel="nofollow" target="_blank" title=""&gt;5 Pry Features Every Ruby Developer Should Know&lt;/a&gt; 搬运过来了。&lt;/p&gt;

&lt;p&gt;大部分时候，我们都把 Pry 作为一个断点工具来对 Ruby 代码进行调试。但它不仅仅是一个断点工具，他同时也能给我们带来极好的代码交互。接下来我们从两个层面来说说来说说 5 个你需要知道 pry 技巧。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;探索可用方法&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pry 提供了一个 &lt;code&gt;ls&lt;/code&gt; 方法，让我们可以获取当前 scope 里所有可用的变量和方法，如果觉得出现的内容太多了，是因为他会从多个层级得到对应的方法和变量。&lt;/p&gt;

&lt;p&gt;于是我们可以&lt;code&gt;ls --locals&lt;/code&gt; 这样就可以只获得当前 scope 的变量和方法。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;丢掉文档&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pry 让命名空间下的搜索变得极其容易，例如我们要找一个 Nokogiri 处理 xpaths 的函数，我们只需：
&lt;code&gt;find-method xpath Nokogiri&lt;/code&gt;，结果如下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt; &lt;span class="n"&gt;xpath&lt;/span&gt; &lt;span class="no"&gt;Nokogiri&lt;/span&gt;
&lt;span class="no"&gt;Nokogiri&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CSS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;xpath_for&lt;/span&gt;
&lt;span class="no"&gt;Nokogiri&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CSS&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Node&lt;/span&gt;
&lt;span class="no"&gt;Nokogiri&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CSS&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Node&lt;/span&gt;&lt;span class="c1"&gt;#to_xpath&lt;/span&gt;
&lt;span class="no"&gt;Nokogiri&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CSS&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Parser&lt;/span&gt;
&lt;span class="o"&gt;....&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果我们需要下列函数具体的信息，我们只需 &lt;code&gt;stat Nokogiri::CSS.xpath_for&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;stat&lt;/span&gt; &lt;span class="no"&gt;Nokogiri&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CSS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;xpath_for&lt;/span&gt;
&lt;span class="no"&gt;Method&lt;/span&gt; &lt;span class="no"&gt;Information&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;--&lt;/span&gt;
&lt;span class="no"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;xpath_for&lt;/span&gt;
&lt;span class="no"&gt;Alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;None&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="no"&gt;Owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#&amp;lt;Class:Nokogiri::CSS&amp;gt;&lt;/span&gt;
&lt;span class="no"&gt;Visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kp"&gt;public&lt;/span&gt;
&lt;span class="no"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Bound&lt;/span&gt;
&lt;span class="no"&gt;Arity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="no"&gt;Method&lt;/span&gt; &lt;span class="no"&gt;Signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;xpath_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sc"&gt;?)&lt;/span&gt;
&lt;span class="no"&gt;Source&lt;/span&gt; &lt;span class="no"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/.rvm/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;nokogiri&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.7&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;nokogiri&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所有内容一览无余，但还不够，我们还能 &lt;code&gt;show-source Nokogiri::CSS.xpath_for&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="no"&gt;Nokogiri&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CSS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;xpath_for&lt;/span&gt;
&lt;span class="no"&gt;From&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/.rvm/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;nokogiri&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.7&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;nokogiri&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;

&lt;span class="no"&gt;Owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#&amp;lt;Class:Nokogiri::CSS&amp;gt;&lt;/span&gt;
&lt;span class="no"&gt;Visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kp"&gt;public&lt;/span&gt;
&lt;span class="no"&gt;Number&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="ss"&gt;lines: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;xpath_for&lt;/span&gt; &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="no"&gt;Parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:ns&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;xpath_for&lt;/span&gt; &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你还是要看文档，Pry 连 &lt;code&gt;show-doc&lt;/code&gt; 也提供了。&lt;/p&gt;

&lt;p&gt;这些技巧能更好地让我们每日的开发中得到更多的信息，更好地 Debug 代码。一会就去试一下吧！&lt;/p&gt;</description>
      <author>chrishyman</author>
      <pubDate>Fri, 26 May 2017 12:36:28 +0800</pubDate>
      <link>https://ruby-china.org/topics/33076</link>
      <guid>https://ruby-china.org/topics/33076</guid>
    </item>
    <item>
      <title>Rails 缓存，你应该知道的几件事</title>
      <description>&lt;h3 id="Page Caching"&gt;Page Caching&lt;/h3&gt;
&lt;p&gt;首先 Page Caching 在 Rails 4 已经被移除作为 Gem &lt;code&gt;actionpack-page_caching&lt;/code&gt; 存在了。
因为 DHH 在后来要推行他的 &lt;code&gt;key-based cache&lt;/code&gt; 实践，事实证明这样的做法更灵活更好。&lt;/p&gt;

&lt;p&gt;回到 &lt;code&gt;actionpack-page_caching&lt;/code&gt; 的讨论中&lt;/p&gt;
&lt;h4 id="1. 原理："&gt;1. 原理：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;把对应需要缓存的 action 内容，在 public 文件夹中缓存一份地址对应的 html 文件，让 nginx 直接访问这个 html 文件，那 Rails Application 就不会参与任何的处理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="2. 核心代码(有删减)："&gt;2. 核心代码 (有删减)：&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/rails/actionpack-page_caching/blob/master/lib/action_controller/" rel="nofollow" target="_blank" title=""&gt;步兵地址&lt;/a&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# action_controller/page_caching.rb&lt;/span&gt;
&lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:include&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Caching&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Pages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# action_controller/caching/pages.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ActionController&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Caching&lt;/span&gt;
    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Pages&lt;/span&gt;
      &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;
      &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ClassMethods&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;caches_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract_options!&lt;/span&gt;
          &lt;span class="n"&gt;after_filter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gzip_level&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# controller instance method&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cache_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gzip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Zlib&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BEST_COMPRESSION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Mime&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;LOOKUP&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type_symbol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
    &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;".&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;type_symbol&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gzip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="3. 解说："&gt;3. 解说：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;首先 &lt;code&gt;action_controller/page_caching.rb&lt;/code&gt;  让所有的 Controller 都 include  &lt;code&gt;ActionController::Caching::Pages&lt;/code&gt; 这个 Module。
然后在 caches_pages 被声明的 action 就会走一个 after_filter 执行 cache_page 这一个实例方法
最后得到 response.body，写进 public 里头的对应目录文件里，下次访问这个路由 Nginx 会直接访问这个文件，应用服务器将不参与任何处理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="4. 优缺点"&gt;4. 优缺点&lt;/h4&gt;
&lt;p&gt;优点：
快，适合变化频率低的数据。&lt;/p&gt;

&lt;p&gt;缺点：
缓存失效的策略只有一个：手动 expire_page。所以 2012 年就被 DHH 逐出 Rails 4 的核心代码。&lt;/p&gt;
&lt;h3 id="Action Caching"&gt;Action Caching&lt;/h3&gt;
&lt;p&gt;Action Caching 也在 Rails 4 中移除作为 Gem &lt;code&gt;actionpack-action_caching&lt;/code&gt; 存在了。&lt;/p&gt;
&lt;h4 id="1. 原理："&gt;1. 原理：&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;乍一看以为 Action Caching 对比 Page Caching 是新瓶装旧药，但是两者的缓存上的策略和思路是不同的。Action Caching 实现了一个 around_action 的方法，由于是走方法的，所以可以提供「unless」的保存规则。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="2. 核心代码(有删减)："&gt;2. 核心代码 (有删减)：&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/rails/actionpack-action_caching/tree/master/lib/action_controller" rel="nofollow" target="_blank" title=""&gt;步兵地址&lt;/a&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ClassMethods&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;caches_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;cache_configured?&lt;/span&gt;
    &lt;span class="c1"&gt;# cache_options 来源有删减&lt;/span&gt;
    &lt;span class="n"&gt;around_action&lt;/span&gt; &lt;span class="no"&gt;ActionCacheFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache_options&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;filter_options&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="kp"&gt;protected&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ActionCacheFilter&lt;/span&gt;
  &lt;span class="c1"&gt;# before_around 将会执行的部分&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;around&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cache_layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;expand_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@cache_layout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;path_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;expand_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@cache_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cache_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ActionCachePath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path_options&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_fragment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@store_options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;
      &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_has_layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;cache_layout&lt;/span&gt;
      &lt;span class="k"&gt;yield&lt;/span&gt;
      &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_has_layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_save_fragment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@store_options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;render_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;cache_layout&lt;/span&gt;
    &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;
    &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;content_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Mime&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cache_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="ss"&gt;:html&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="3. 解说："&gt;3. 解说：&lt;/h4&gt;
&lt;p&gt;用 around_action 来实现是否进入 action 原有的逻辑代码还是直接访问缓存文件，这一点算是 Page Caching 的强化版本。&lt;/p&gt;

&lt;p&gt;如果有缓存，将会直接从 read_fragment 中读取内容，然后直接作为 response_body 返回，如果不，就执行 yield 中代码（本身 action 的代码），然后_save_fragment。&lt;/p&gt;
&lt;h4 id="4. 优缺点"&gt;4. 优缺点&lt;/h4&gt;
&lt;p&gt;和 Page Caching 不同，他最终的缓存访问这个行为会走进 Rails App 的 action 里，所以这个是缺点（不是直接访问文件），也是优点（不会无脑访问文件）。&lt;/p&gt;

&lt;p&gt;但是这种在 action 层面上的缓存还是太不自由了，基本上是同时被逐出 Rails 4。&lt;/p&gt;
&lt;h3 id="Fragment Caching"&gt;Fragment Caching&lt;/h3&gt;
&lt;p&gt;前面两位被弃用的主要原因，就是现在 view 方面的需求太复杂了，一步到位的缓存是不存在的。&lt;/p&gt;

&lt;p&gt;于是我们就有了在 view 层面上碎片化的 Key-base 缓存方案：Fragment Caching。&lt;/p&gt;
&lt;h4 id="1. 原理："&gt;1. 原理：&lt;/h4&gt;
&lt;p&gt;在 View 中用的 cache 方法来自 ActionView::Helpers::CacheHelper，例子如下：
如果我们在 View 中写下如下代码&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% cache &lt;/span&gt;&lt;span class="vi"&gt;@article&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="sx"&gt;%&amp;gt;
  &amp;lt;%= @article.title %&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= @article.content %&amp;gt;
&amp;lt;% end %&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;_ 我们会往我们的缓存抽象层（cache_store）保存如下地址的 key，内容当然是我们的渲染内容。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;articles&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;20160829091941000000000&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;b45efaa1f600a3b9dffe061fb38ad68&lt;/span&gt;
     &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;类名&lt;/span&gt;     &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="n"&gt;的&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;updated_at&lt;/span&gt;  &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="no"&gt;ActionView&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Digestor&lt;/span&gt; &lt;span class="n"&gt;的digest&lt;/span&gt; &lt;span class="n"&gt;方法生成的特征码&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;值得一说的是，这个虽然是 ActionView 的方法，但是 read_fragment 和 write_fragment 都是 ActionController::Base 提供的。&lt;/p&gt;
&lt;h4 id="2. 核心代码(有删减)："&gt;2. 核心代码 (有删减)：&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/rails/rails/blob/4dbb7f0758ca5e3132a590d05e86793853674d44/actionview/lib/action_view/helpers/cache_helper.rb" rel="nofollow" target="_blank" title=""&gt;步兵地址&lt;/a&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# 略了若干判断，以及TextHelper 的 safe_concat 方法&lt;/span&gt;
  &lt;span class="c1"&gt;# cache_fragment_name(name, options) 就是我们的存储key 来源的方法 TL;DR&lt;/span&gt;
  &lt;span class="n"&gt;fragment_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache_fragment_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fragment_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;read_fragment_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;write_fragment_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_fragment_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_fragment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;write_fragment_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output_buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt;
  &lt;span class="n"&gt;output_safe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output_buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html_safe?&lt;/span&gt;
  &lt;span class="n"&gt;fragment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output_buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;..-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;output_safe&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;output_buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output_buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_fragment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关于 ActionController 如何读写保存碎片的部分，&lt;a href="https://github.com/rails/rails/blob/191ddf26379670b477bd63bccf8debbe16d20ed9/actionpack/lib/action_controller/caching/fragments.rb" rel="nofollow" target="_blank" title=""&gt;步兵地址摸我&lt;/a&gt;。&lt;/p&gt;
&lt;h4 id="3. 解说："&gt;3. 解说：&lt;/h4&gt;
&lt;p&gt;事实上从 Key 的内容我们就可以知道，这个以 action + model base attribute + 自定义字符串的玩法，对于现在的 Rails 项目来说再好不过了。
更加碎片自定义的信息，更高而且准确的缓存命中，但是坏处非常非常明显，下一部分说。&lt;/p&gt;
&lt;h4 id="4. 优缺点："&gt;4. 优缺点：&lt;/h4&gt;
&lt;p&gt;缓存从一开始就带着开发者对于它可能会发生的「不失效」有着巨大的忧虑。用了 Fragment Caching，在我们不增加其他代码的前提下，意味着我们把缓存的是否失效留给了 updated_at 这个时间戳。在最简单原始的场景下，这倒是万能解决方案。&lt;/p&gt;

&lt;p&gt;——直到我们用上了「俄罗斯套娃」缓存，那我们接下来就不得不可能在父子层级的 model 之间加恼人的 touch，然后继续期望缓存会在适当的时候乖乖失效。&lt;/p&gt;
&lt;h3 id="SQL Caching"&gt;SQL Caching&lt;/h3&gt;&lt;h5 id="施工中, 不日更新"&gt;施工中，不日更新&lt;/h5&gt;</description>
      <author>chrishyman</author>
      <pubDate>Sun, 12 Mar 2017 17:29:58 +0800</pubDate>
      <link>https://ruby-china.org/topics/32511</link>
      <guid>https://ruby-china.org/topics/32511</guid>
    </item>
    <item>
      <title>用图片和 Ruby 来说 LCS 问题</title>
      <description>&lt;p&gt;当我们在一个 10000 多行的文件里头修改了 20 行代码，然后我们输入一行 &lt;code&gt;git diff&lt;/code&gt; 去和某一个分支比较，到底用了什么方法来保证不会因为这一个 diff 让电脑卡死呢？&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;因为大部分的 diff，本质上就是以行为单位的 LCS。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Longest Common Subsequences（最长公共子序列）&lt;/p&gt;

&lt;p&gt;在这里我们只讨论序列长度确定的情况下的解法。&lt;/p&gt;

&lt;p&gt;LSD 问题例子：有 OPABIZCA 和 OPIABZCD 则 OPABZ 为这两组字符串的最长公共子串。&lt;/p&gt;

&lt;p&gt;PS: 连续字符串不是必要条件，只要字符出现顺序相同即可。&lt;/p&gt;

&lt;p&gt;如果我们的解法是暴力求解，然后和另一个字符串做匹配，复杂度就会是 O(KL^2)&lt;/p&gt;

&lt;p&gt;如果我们构建矩阵来存储动态规划过程中子问题的解，则复杂度就会大大优化。&lt;/p&gt;

&lt;p&gt;话不多说，上代码来解说。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"d"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;#在这一步已知结果应该是 bdcbd 让我们往下反推&lt;/span&gt;

&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;char&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;)]}&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;LCS_length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="c1"&gt;# 如果相等，把c 数组的i,j 位置设置为斜上方的值 + 1&lt;/span&gt;
        &lt;span class="c1"&gt;# b 数组的对应位置为 0&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="c1"&gt;# 如果不相等而且前一个数更大，那么说明前面正在维护一个子串(有匹配的部分)&lt;/span&gt;
          &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="c1"&gt;# 如果没有，说明x字符串在这个坐标前部分没有匹配的必要&lt;/span&gt;
          &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;Print_LCS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="no"&gt;Print_LCS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"%c "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Print_LCS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="no"&gt;Print_LCS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;



&lt;span class="no"&gt;LCS_length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;构建完之后得到两个矩阵 c,b
&lt;img width="200" style="float:left;" src="https://l.ruby-china.com/photo/2017/80ca50c8359f18015c7a4441cf437a57.gif!large"&gt;
&lt;img width="200" style="float:left;" src="https://l.ruby-china.com/photo/2017/4c483ec74a392c6eba12ee214d0b5c7c.gif!large"&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 最后执行递归函数从后往前比较矩阵来获得我们需要的字符串&lt;/span&gt;
&lt;span class="no"&gt;Print_LCS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;本文的无码版 &lt;a href="https://gist.github.com/Madao-3/caa6bc78d7876fd1917b69b3b0aad007" rel="nofollow" target="_blank"&gt;https://gist.github.com/Madao-3/caa6bc78d7876fd1917b69b3b0aad007&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;推荐一个 gem 包 &lt;a href="https://github.com/halostatue/diff-lcs" rel="nofollow" target="_blank"&gt;https://github.com/halostatue/diff-lcs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;JGit 的 Diff 的实现代码 &lt;a href="https://github.com/eclipse/jgit/blob/ebfd62433a58d23af221adfdffed56d9274f4268/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java" rel="nofollow" target="_blank"&gt;https://github.com/eclipse/jgit/blob/ebfd62433a58d23af221adfdffed56d9274f4268/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;思考题：事实上，React 最值得称道的优点之一，就是他的 Virtual DOM Diff 优化，请问：「它做了哪些优化？」&lt;/p&gt;</description>
      <author>chrishyman</author>
      <pubDate>Tue, 07 Mar 2017 09:10:43 +0800</pubDate>
      <link>https://ruby-china.org/topics/32463</link>
      <guid>https://ruby-china.org/topics/32463</guid>
    </item>
    <item>
      <title>讨论一下，大家是如何存储时间范围的？</title>
      <description>&lt;p&gt;例如一月四号到五月一号。&lt;/p&gt;

&lt;p&gt;这样的存储有两个字段存储以外的方法吗？&lt;/p&gt;</description>
      <author>chrishyman</author>
      <pubDate>Thu, 18 Sep 2014 14:31:51 +0800</pubDate>
      <link>https://ruby-china.org/topics/21586</link>
      <guid>https://ruby-china.org/topics/21586</guid>
    </item>
    <item>
      <title>如何得到某个一个最多的多对多的子类？</title>
      <description>&lt;p&gt;标题说的太模糊了，举个例子，我有一个 Post Model  里头有一个 Author Model，关系是多对多的。&lt;/p&gt;

&lt;p&gt;如果我要做一个排列，来排列出创作最多 Post 的 Author。有什么比较好的方法？&lt;/p&gt;</description>
      <author>chrishyman</author>
      <pubDate>Mon, 11 Aug 2014 00:35:32 +0800</pubDate>
      <link>https://ruby-china.org/topics/20946</link>
      <guid>https://ruby-china.org/topics/20946</guid>
    </item>
  </channel>
</rss>
