<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>qinsicheng (秦晨)</title>
    <link>https://ruby-china.org/qinsicheng</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>show history for selection lines in Zed</title>
      <description>&lt;p&gt;相信使用过 JetBrains 产品的用户，对内置的 Git 操作都会觉得很方便。其中很重要的一个功能：选中某几行代码，鼠标右键，使用 &lt;code&gt;show history for selection&lt;/code&gt;。就能展示出这几行代码的 git 提交历史记录，这个功能真的太香了。&lt;/p&gt;

&lt;p&gt;这个功能在很多编辑器上都不支持，我目前在使用 Zed 编辑器，最开始对 Git 操作支持很少，我都是使用 &lt;code&gt;lazygit&lt;/code&gt; 来操作，但上面提到的功能无法实现。所以我就尝试编写脚本去实现这个功能。本质上就是使用 git 原生命令行操作：&lt;code&gt;git log -L &amp;lt;range:file&amp;gt;&lt;/code&gt;  。将内容输出到一个临时文件中，然后通过 zed 编辑器打开它，同时使用 &lt;code&gt;diff&lt;/code&gt; 语法展示高亮信息。&lt;/p&gt;
&lt;h3 id="效果展示："&gt;效果展示：&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/c2c295e9-bedd-4833-b06f-9b6f2d0b7579.gif" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="Fish代码"&gt;Fish 代码&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function git_line_history --description "Show git history for specific lines in a file"
    # Validate argument count
    if test (count $argv) -ne 3
        echo "Usage: git_line_history &amp;lt;file_path&amp;gt; &amp;lt;start_line&amp;gt; &amp;lt;end_line&amp;gt;"
        return 1
    end

    set -l file_path $argv[1]
    set -l start_line $argv[2]
    set -l end_line $argv[3]

    # Verify the file exists
    if not test -f "$file_path"
        echo "Error: file '$file_path' does not exist"
        return 1
    end

    # Verify we are inside a git repository
    if not git rev-parse --is-inside-work-tree &amp;gt;/dev/null 2&amp;gt;&amp;amp;1
        echo "Error: current directory is not inside a git repository"
        return 1
    end

    # Create temporary files
    set -l temp_file (mktemp -t git_history.XXXXXXXXXX.diff)
    set -l raw_log_file (mktemp -t git_history.XXXXXXXXXX.raw)

    # Build the line range argument for git log -L
    set -l line_range "$start_line,$end_line:$file_path"

    # Fetch the line history
    git log --date=format:'%Y-%m-%d %H:%M:%S' -L $line_range &amp;gt;$raw_log_file 2&amp;gt;&amp;amp;1

    if test $status -ne 0
        echo "Error: failed to execute git log"
        if test -s $raw_log_file
            echo "Details:"
            cat $raw_log_file
        end
        rm -f $raw_log_file $temp_file
        return 1
    end

    # Process output: filter diff metadata and add separators between commits
    awk '
    BEGIN { in_commit = 0; has_content = 0; }
    /^commit / {
        if (in_commit &amp;amp;&amp;amp; has_content) print "\n--------------------------------------------------\n"
        in_commit = 1
        has_content = 1
        print
        next
    }
    /^diff --git|^index |^--- a\/|^\+\+\+ b\/|^@@ .* @@/ { next }
    {
        if (in_commit) print
        has_content = 1
    }
    ' $raw_log_file &amp;gt;$temp_file

    rm -f $raw_log_file

    # Check if the result is empty
    if not test -s $temp_file
        echo "Note: no matching commit history found"
        rm -f $temp_file
        return 0
    end

    # Choose an editor: prefer zeditor (Arch Linux), fall back to subl, then $EDITOR
    set -l editor_cmd
    if command -q zeditor
        set editor_cmd zeditor
    else if command -q zed
        set editor_cmd zed
    else if command -q subl
        set editor_cmd subl
    else if set -q EDITOR
        set editor_cmd $EDITOR
    else
        echo "Error: no suitable editor found"
        echo "Temporary file location: $temp_file"
        return 1
    end

    $editor_cmd "$temp_file"

    # Clean up old temporary files from previous runs
    function cleanup_git_history --on-event fish_exit
        # Clean up old temporary files from previous runs
        set -l tmpdir (dirname (mktemp -u))
        rm -f $tmpdir/git_history.*.diff 2&amp;gt;/dev/null
        rm -f $tmpdir/git_history.*.raw 2&amp;gt;/dev/null
    end

    return 0
end
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function zed_git_line_history --description "Zed editor wrapper for git_line_history"
    # Extract line range from Zed environment variables
    set -l start_line $ZED_ROW
    set -l selection "$ZED_SELECTED_TEXT"
    set -l line_count (printf '%s\n' "$selection" | wc -l | string trim)
    set -l end_line (math $start_line + $line_count - 1)
    set -l file_path "$ZED_FILE"

    git_line_history "$file_path" $start_line $end_line
end
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="Zed 配置"&gt;Zed 配置&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;task&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"git_line_history"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zed_git_line_history"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"ZED_ROW"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$ZED_ROW"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"ZED_SELECTED_TEXT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$ZED_SELECTED_TEXT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"ZED_FILE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$ZED_FILE"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"hide"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"always"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"allow_concurrent_runs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"use_new_terminal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;keymap&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vim_mode == normal || vim_mode == visual"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bindings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"g h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"task::Spawn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"task_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"git_line_history"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Thu, 23 Apr 2026 22:20:47 +0800</pubDate>
      <link>https://ruby-china.org/topics/44558</link>
      <guid>https://ruby-china.org/topics/44558</guid>
    </item>
    <item>
      <title>西安垚海 有大佬在里面吗？求问怎么样</title>
      <description>&lt;p&gt;想了解里面公司内文化，氛围。以及是否靠谱&lt;/p&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Mon, 20 Apr 2026 21:37:54 +0800</pubDate>
      <link>https://ruby-china.org/topics/44556</link>
      <guid>https://ruby-china.org/topics/44556</guid>
    </item>
    <item>
      <title>利用 Delegated Types 与 Self Joins 设计高扩展性系统</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;前言&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;如何设计一套类似&lt;code&gt;Confluence&lt;/code&gt;、&lt;code&gt;语雀&lt;/code&gt;、&lt;code&gt;Notion&lt;/code&gt;这类 CMS 系统，在一个容器内，里面含有大量的不同形式的内容，同时多种内容又有很多类似的操作：移动、复制、删除、支持评论等。一个文档下可以写文本，也可以再次建立子文档，文本评论也可以建立子评论。一篇文档能够清晰的查看操作历史、变化内容。同时一个容器内可能还会新增一些不同的内容，又同时也要支持上述的操作。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.37signals.com/the-rails-delegated-type-pattern/" rel="nofollow" target="_blank" title=""&gt;https://dev.37signals.com/the-rails-delegated-type-pattern/&lt;/a&gt; 文章中探讨了 37Signals 团队如何设计系统架构，保证高维护性和高扩展性。其核心概念就是 Recording、Recordable 和 Event。本质上，他们利用了&lt;code&gt;Delegated Types + Self Joins&lt;/code&gt;模式设计架构，来最大程度上支持高扩展性和可维护性。&lt;/p&gt;

&lt;p&gt;参考资料：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://guides.rubyonrails.org/association_basics.html#delegated-types" rel="nofollow" target="_blank" title=""&gt;Active Record Associations — Ruby on Rails Guides&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/DelegatedType.html" rel="nofollow" target="_blank" title=""&gt;https://api.rubyonrails.org/classes/ActiveRecord/DelegatedType.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://guides.rubyonrails.org/association_basics.html#delegated-types" rel="nofollow" target="_blank" title=""&gt;Active Record Associations — Ruby on Rails Guides&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;以下几种模式需要清楚：&lt;/p&gt;
&lt;h3 id="基础模型关联"&gt;基础模型关联&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/v8.1.1/classes/ActiveRecord/Associations/ClassMethods.html#method-i-belongs_to" rel="nofollow" target="_blank" title=""&gt;belongs_to&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/v8.1.1/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_one" rel="nofollow" target="_blank" title=""&gt;has_one&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/v8.1.1/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many" rel="nofollow" target="_blank" title=""&gt;has_many&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/v8.1.1/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many" rel="nofollow" target="_blank" title=""&gt;has_many :through&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/v8.1.1/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_one" rel="nofollow" target="_blank" title=""&gt;has_one :through&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/v8.1.1/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_and_belongs_to_many" rel="nofollow" target="_blank" title=""&gt;has_and_belongs_to_many&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最常见的关联关系，相当于每个独立的表，通过一个索引关联 ID 来关联其他模型&lt;/p&gt;
&lt;h3 id="高级模型关联"&gt;高级模型关联&lt;/h3&gt;&lt;h4 id="Polymorphic Associations"&gt;Polymorphic Associations&lt;/h4&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Picture&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:imageable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;polymorphic: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Employee&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:pictures&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: :imageable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:pictures&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: :imageable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里相当于图片资源，可能属于是用户的，也可能属于是产品的。未来可能还有其他地方也使用图片。那这里在图片模型中就会定义两个字段：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;imageable_id&lt;/li&gt;
&lt;li&gt;imageable_type&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;通过 rails 的关联模型，就能直接扩展和关联想要的数据，在复用历史模型的情况下，支持可拓展性&lt;/p&gt;
&lt;h4 id="Self Joins"&gt;Self Joins&lt;/h4&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Employee&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="c1"&gt;# an employee can have many subordinates.&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:subordinates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Employee"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="s2"&gt;"leader_id"&lt;/span&gt;

  &lt;span class="c1"&gt;# an employee can have one leader.&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:leader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Employee"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;optional: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一个领导有很多员工，但领导本身也是一个员工，也可能有上级领导。统一存在一个表中，通过一个&lt;code&gt;leader_id&lt;/code&gt;进行关联。&lt;/p&gt;

&lt;p&gt;这本质上利用了树结构进行灵活的扩展，通过自连接替换掉了普通的模型关联。对于多层级但又是同一类型的实体，这种模式很方便。&lt;/p&gt;
&lt;h4 id="Single Table Inheritance 单表继承"&gt;Single Table Inheritance 单表继承&lt;/h4&gt;
&lt;p&gt;允许多个模型存储在单个数据库表中。当你拥有不同类型实体，它们共享共同属性和行为，但又有特定行为时，这非常有用。&lt;/p&gt;

&lt;p&gt;例如，假设我们有 &lt;code&gt;Car&lt;/code&gt; 、 &lt;code&gt;Motorcycle&lt;/code&gt; 和 &lt;code&gt;Bicycle&lt;/code&gt; 模型。这些模型将共享诸如 &lt;code&gt;color&lt;/code&gt; 和 &lt;code&gt;price&lt;/code&gt; 之类的字段，但每个模型都有独特的行为。它们也各自拥有自己的控制器。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Vehicle&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Car&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Vehicle&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Motorcycle&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Vehicle&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所有模型使用一张表（vehicle），通过一个&lt;code&gt;type&lt;/code&gt;字段来区分不同的类型。这样可以避免重复建表，又保留了扩展性。不同的类型，做各自特别的处理。&lt;/p&gt;

&lt;p&gt;不过 STI 有个问题：随着业务复杂化，多个类型需要存储特定的数据，这些数据对有的类型有用，对有的类型无用，会导致原本的一张表中字段越来越多，很多字段对某些类型来说就是&lt;code&gt;NULL&lt;/code&gt;值。&lt;/p&gt;
&lt;h4 id="Delegated Types  委托类型"&gt;Delegated Types  委托类型&lt;/h4&gt;
&lt;p&gt;这正是为了解决 STI 的问题而出现的：&lt;/p&gt;

&lt;p&gt;可能一开始使用单表继承很方便，但随着业务的复杂化，可能多个类型需要存储特定的数据，这些特定的数据，对有的类型有用，对有的类型无用，会导致原本的一张表中，字段越来越多，而又不是每个类型，都要使用的。那就可以把核心公共字段放在原始表上，然后创建各个类型具体字段到具体的表中。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Schema: entries[ id, entryable_type, entryable_id, created_at, updated_at, created_by, updated_by ]&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;delegated_type&lt;/span&gt; &lt;span class="ss"&gt;:entryable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;types: &lt;/span&gt;&lt;span class="sx"&gt;%w[ Message Comment ]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Entryable&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="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="ss"&gt;:entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: :entryable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&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;# Schema: messages[ id, subject, body ]&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Entryable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Schema: comments[ id, content ]&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Comment&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Entryable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;数据创建时使用：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt; &lt;span class="ss"&gt;entryable: &lt;/span&gt;&lt;span class="no"&gt;Message&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="ss"&gt;subject: &lt;/span&gt;&lt;span class="s2"&gt;"hello!"&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;比如我希望文本搜索 Entry 中的内容时，可以将命令委托到特定的类上&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;delegated_type&lt;/span&gt; &lt;span class="ss"&gt;:entryable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;types: &lt;/span&gt;&lt;span class="sx"&gt;%w[ Message Comment ]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;
  &lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="ss"&gt;:searchable_content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: :leafable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Entryable&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;searchable_content&lt;/span&gt;
    &lt;span class="n"&gt;body&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;class&lt;/span&gt; &lt;span class="nc"&gt;Comment&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Entryable&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;searchable_content&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="从业务场景看设计选择"&gt;从业务场景看设计选择&lt;/h3&gt;
&lt;p&gt;通过一个实际的业务场景来理解这些设计模式的选择。&lt;/p&gt;

&lt;p&gt;假设我们有一个报价单系统，报价单下包含多种服务，这些服务大致类似，但又有各自不同的订阅内容。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;公共信息&lt;/strong&gt;：订阅时长、原价格、实际价格、创建人、创建时间、更新人、更新时间、价格版本&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;特定信息&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A 服务需要：订阅数量&lt;/li&gt;
&lt;li&gt;B 服务需要：订阅类型&lt;/li&gt;
&lt;li&gt;C 服务可能还需要其他信息...&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="传统设计"&gt;传统设计&lt;/h4&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 报价单表&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Quotation&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="c1"&gt;# 关联A服务&lt;/span&gt;
  &lt;span class="ss"&gt;has_many: :project_service&lt;/span&gt;
  &lt;span class="c1"&gt;# 关联B服务&lt;/span&gt;
  &lt;span class="ss"&gt;has_many: :bid_info&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# A服务 存储：订阅服务多久，原价格，实际价格，创建人，创建时间，更新人，更新时间，价格版本，订阅数量&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProjectService&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:quotation&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# B服务 存储：订阅服务多久，原价格，实际价格，创建人，创建时间，更新人，更新时间，价格版本，订阅类型&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BidInfo&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:quotation&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到每个服务核心内容是一致的，但是又有一些各自的信息。假如说我再加一个新的服务，就需要再修改&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 报价单表&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Quotation&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="c1"&gt;# 关联A服务&lt;/span&gt;
  &lt;span class="ss"&gt;has_many: :project_service&lt;/span&gt;
  &lt;span class="c1"&gt;# 关联B服务&lt;/span&gt;
  &lt;span class="ss"&gt;has_many: :bid_info&lt;/span&gt;
  &lt;span class="c1"&gt;# 关联C服务&lt;/span&gt;
  &lt;span class="ss"&gt;has_many: :market_service&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# C服务&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MarketService&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:quotation&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在遇到两个场景：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;我想查看一个报价单下有哪些订阅服务，并分页查看，怎么写？&lt;/li&gt;
&lt;li&gt;我想统计报价单下某些类别的金额，怎么写？ &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;统计可能稍好一些，大不了就是多查几个表。但分页获取怎么做呢？&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;按照委托模式设计的话，可以这样：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 报价单表&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Quotation&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="ss"&gt;has_many: :quotion_service&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 服务表&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Service&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;delegated_type&lt;/span&gt; &lt;span class="ss"&gt;:serviceable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;types: &lt;/span&gt;&lt;span class="sx"&gt;%w[ ProjectService BidInfo ]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Serviceable&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="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="ss"&gt;:service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: :serviceable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&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;class&lt;/span&gt; &lt;span class="nc"&gt;ProjectService&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Serviceable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BidInfo&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Serviceable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个方案虽然多了一张表，关系也更复杂，但带来的好处很明显：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;所有服务的统计、分页、批处理都能直接从&lt;code&gt;services&lt;/code&gt;表获取&lt;/li&gt;
&lt;li&gt;新增服务类型只需要添加新的 Recordable 类型，不需要修改报价单模型&lt;/li&gt;
&lt;li&gt;统一的管理接口，便于维护&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="WriteBook 源码查看"&gt;WriteBook 源码查看&lt;/h3&gt;
&lt;p&gt;源码：&lt;a href="https://once.com/writebook" rel="nofollow" target="_blank"&gt;https://once.com/writebook&lt;/a&gt; &lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Book&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:leaves&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Leaf&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Editable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Positionable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Searchable&lt;/span&gt;

  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;delegated_type&lt;/span&gt; &lt;span class="ss"&gt;:leafable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;types: &lt;/span&gt;&lt;span class="sx"&gt;%w[ Page Section Picture ]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;
  &lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="ss"&gt;:searchable_content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: :leafable&lt;/span&gt;

  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;:with_leafables&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="n"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:leafable&lt;/span&gt;&lt;span class="p"&gt;)&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;class&lt;/span&gt; &lt;span class="nc"&gt;Page&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Leafable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Section&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Leafable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Picture&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Leafable&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的设计很清晰：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Book&lt;/code&gt;是容器&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Leaf&lt;/code&gt;是通用记录层（Recording）&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Page&lt;/code&gt;、&lt;code&gt;Section&lt;/code&gt;、&lt;code&gt;Picture&lt;/code&gt;是具体内容（Recordable）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="37Signals的核心设计思想"&gt;37Signals 的核心设计思想&lt;/h3&gt;
&lt;p&gt;回到 37Signals 文章提出的概念&lt;/p&gt;
&lt;h4 id="Recording（记录外壳）"&gt;Recording（记录外壳）&lt;/h4&gt;
&lt;p&gt;存储所有内容的公共属性：ID、创建时间、创建者、位置信息等。相当于给所有内容穿了一个统一的外套。&lt;/p&gt;
&lt;h4 id="Recordable（记录内核）"&gt;Recordable（记录内核）&lt;/h4&gt;
&lt;p&gt;存储特定类型的具体数据。文档有文档的数据结构，评论有评论的数据结构。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;关键设计&lt;/strong&gt;：Recordable 是不可变的。用户操作修改时，不是修改原来的 Recordable，而是拷贝并创建一条新的数据，同时创建一个 Event。&lt;/p&gt;
&lt;h4 id="Event（事件）"&gt;Event（事件）&lt;/h4&gt;
&lt;p&gt;记录操作历史，关联新旧版本 ID。历史版本查看实际上就是查看一系列 Event，回滚到特定版本只需要把相应的 Recordable 挂载到 Recording 上。&lt;/p&gt;

&lt;p&gt;在 CMS 系统中往往有文档历史更新记录的查看功能，所以在设计上，Recordale 是不能修改的。如果用户操作修改，则拷贝并创建一条新的数据，和一个 Event。Event 关联两个版本 ID，Recordable 挂载到新的 Recording 上，历史版本查看，实际上就是一个个事件，如果要回滚文档到某个具体的版本，只需要把 Recordable 挂载到特定的 Recording 上。&lt;/p&gt;
&lt;h4 id="树形结构设计：Self Joins的应用"&gt;树形结构设计：Self Joins 的应用&lt;/h4&gt;
&lt;p&gt;在 CMS 系统中，内容往往需要支持树形结构：文档可以包含子文档，评论可以有子评论。&lt;/p&gt;

&lt;p&gt;37Signals 文章中还提到一个值得学习的点：&lt;strong&gt;通过自连接来替换关联关系&lt;/strong&gt;。实际上就是 Recordable 本身有一个&lt;code&gt;parent_id&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;用 Confluence 举例，假设有这样的文档结构：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- A文档
  - B文档
    - C文档
      - 1评论
      - 2评论
    - D文件列表
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看起来就像一个目录结构，文档、评论、文件列表可以自由地挂载到任意位置。其他服务也可以挂载。通过&lt;code&gt;parent_id&lt;/code&gt;就可以做到：被挂载的服务无需知道以后会有什么挂载在自己下面，只要遵守规范，就能获得一致性处理。&lt;/p&gt;

&lt;p&gt;这种设计的优雅之处在于：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Delegated Types&lt;/strong&gt;负责处理内容的水平扩展（不同类型的内容）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self Joins&lt;/strong&gt;负责处理内容的垂直组织（树形结构）&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="总结"&gt;总结&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Delegated Types + Self Joins&lt;/code&gt;这种设计模式为构建复杂 CMS 系统提供了一个优雅的解决方案：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Delegated Types&lt;/strong&gt;解决了“多种内容类型统一管理”的问题&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self Joins&lt;/strong&gt;解决了“无限层级嵌套结构”的问题&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;不可变 Recordable + Event&lt;/strong&gt;解决了“完整版本历史”的问题&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这种架构的核心优势在于：&lt;strong&gt;将内容类型的变化与内容结构的变化解耦&lt;/strong&gt;。当需要新增内容类型时，只需实现新的 Recordable；当需要调整组织结构时，只需调整&lt;code&gt;parent_id&lt;/code&gt;关系。两者互不干扰，系统维护性和扩展性都得到了保证。&lt;/p&gt;

&lt;p&gt;对于需要处理复杂内容关系、支持未来扩展的现代应用系统，这一模式提供了一个经过实践验证的优秀设计范式。&lt;/p&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Thu, 15 Jan 2026 22:17:13 +0800</pubDate>
      <link>https://ruby-china.org/topics/44450</link>
      <guid>https://ruby-china.org/topics/44450</guid>
    </item>
    <item>
      <title>kamal 加速</title>
      <description>&lt;p&gt;国内墙的原因，导致在使用 kamal 部署到服务器上慢的要死，最近折腾了一下，在不适用代理情况下，第一次部署在两分钟内，之后更新就在三十秒左右。&lt;/p&gt;
&lt;h2 id="举例项目"&gt;举例项目&lt;/h2&gt;
&lt;p&gt;脚手架生成的最基本项目&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ruby&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-v&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;ruby&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.4&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025-09-16&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;revision&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;dbd&lt;/span&gt;&lt;span class="mi"&gt;83256&lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;+PRISM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;x&lt;/span&gt;&lt;span class="mi"&gt;86&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="err"&gt;-linux&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;rails&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-v&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="err"&gt;Rails&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;8.1&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;rails&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;demo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--css&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;tailwind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;postgresql&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="dockerfile syntax  卡住"&gt;dockerfile syntax  卡住&lt;/h2&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;syntax=docker.m.daocloud.io/docker/dockerfile:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;check=error=&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实际测试下来比较稳定，偶尔会卡住&lt;/p&gt;
&lt;h2 id="docker login 卡住"&gt;docker login 卡住&lt;/h2&gt;
&lt;p&gt;dockerHub 登录一直卡住&lt;/p&gt;

&lt;p&gt;我换成腾讯云 docker 个人仓库，我看免费有十个，个人完全够用了，需要提前绑定密码，账号就是腾讯云用户 Id&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;keep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;container&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;images.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;registry:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Alternatives:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;hub.docker.com&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;registry.digitalocean.com&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ghcr.io&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;server:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ccr.ccs.tencentyun.com"&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Needed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;authenticated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;registries.&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;username:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxxxxx"&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Always&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;access&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;rather&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;than&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;real&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;possible.&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;password:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;KAMAL_REGISTRY_PASSWORD&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="docker pull 卡主"&gt;docker pull 卡主&lt;/h2&gt;
&lt;p&gt;镜像名使用&lt;a href="https://1ms.run/" rel="nofollow" target="_blank" title=""&gt;毫秒镜像 - 告别蜗牛速度！国内专线拉取镜像！&lt;/a&gt;&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;docker.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;ms.run/ruby:$RUBY_VERSION-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;base&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="debain 卡主"&gt;debain 卡主&lt;/h2&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;换清华源&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'s|deb.debian.org|mirrors.tuna.tsinghua.edu.cn|g'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/etc/apt/sources.list.d/debian.sources&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;sed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'s|security.debian.org|mirrors.tuna.tsinghua.edu.cn|g'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/etc/apt/sources.list.d/debian.sources&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;sed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'s|http://|https://|g'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/etc/apt/sources.list.d/debian.sources&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="bundle install 卡住"&gt;bundle install 卡住&lt;/h2&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;gem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sources&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;https://gems.ruby-china.com/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--remove&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;https://rubygems.org&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外把 gemfile 里的 source 也改了&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://gems.ruby-china.com/"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="其他小问题"&gt;其他小问题&lt;/h2&gt;&lt;h3 id="healthy check"&gt;healthy check&lt;/h3&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;检查一下是否正常&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"up"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rails/health#show"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;as:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:rails_health_check&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="数据库登录错误"&gt;数据库登录错误&lt;/h3&gt;
&lt;p&gt;我是先部署了一个 demo 服务，看成功了后。想着把我实际服务部署上去，结果数据库一直显示没有对应配置用户名，因为我在 deploy.yml 中数据库配置的一样，而之前部署的时候数据库应该是已经配置好了，我就把之前的那个实例删掉，重新部署，看正常了&lt;/p&gt;
&lt;h3 id="kamal-proxy 显示已经有一个服务了"&gt;kamal-proxy 显示已经有一个服务了&lt;/h3&gt;
&lt;p&gt;还是一样的，之前已经配置了一个服务，现在再配一个新的，又是一样的配置信息。参考：&lt;a href="https://github.com/basecamp/kamal/issues/1147" rel="nofollow" target="_blank" title=""&gt;https://github.com/basecamp/kamal/issues/1147&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;感觉 kamal2 已经很方便了，相比版本 1。个人开发者感觉可以很方便部署服务，建议没用过的，先看官网 DHH 演示，基本无坑，除了墙。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dockerfile 最终版&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# syntax=docker.m.daocloud.io/docker/dockerfile:1&lt;/span&gt;
&lt;span class="c"&gt;# check=error=true&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; RUBY_VERSION=3.4.6&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;docker.1ms.run/ruby:$RUBY_VERSION-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /rails&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s|deb.debian.org|mirrors.tuna.tsinghua.edu.cn|g'&lt;/span&gt; /etc/apt/sources.list.d/debian.sources &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s|security.debian.org|mirrors.tuna.tsinghua.edu.cn|g'&lt;/span&gt; /etc/apt/sources.list.d/debian.sources &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s|http://|https://|g'&lt;/span&gt; /etc/apt/sources.list.d/debian.sources

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="nt"&gt;-qq&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; curl libjemalloc2 libvips postgresql-client &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /usr/lib/&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="nt"&gt;-linux-gnu&lt;/span&gt;/libjemalloc.so.2 /usr/local/lib/libjemalloc.so &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists /var/cache/apt/archives

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; RAILS_ENV="production" \&lt;/span&gt;
  BUNDLE_DEPLOYMENT="1" \
  BUNDLE_PATH="/usr/local/bundle" \
  BUNDLE_WITHOUT="development" \
  LD_PRELOAD="/usr/local/lib/libjemalloc.so"

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;gem sources &lt;span class="nt"&gt;--add&lt;/span&gt; https://gems.ruby-china.com/ &lt;span class="nt"&gt;--remove&lt;/span&gt; https://rubygems.org 

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="nt"&gt;-qq&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; build-essential git libpq-dev libyaml-dev pkg-config &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists /var/cache/apt/archives

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Gemfile Gemfile.lock vendor ./&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;bundle &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; ~/.bundle/ &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUNDLE_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/ruby/&lt;span class="k"&gt;*&lt;/span&gt;/cache &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUNDLE_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/ruby/&lt;span class="k"&gt;*&lt;/span&gt;/bundler/gems/&lt;span class="k"&gt;*&lt;/span&gt;/.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="c"&gt;# -j 1 disable parallel compilation to avoid a QEMU bug: https://github.com/rails/bootsnap/issues/495&lt;/span&gt;
  bundle exec bootsnap precompile -j 1 --gemfile

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;bootsnap precompile &lt;span class="nt"&gt;-j&lt;/span&gt; 1 app/ lib/

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nv"&gt;SECRET_KEY_BASE_DUMMY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 ./bin/rails assets:precompile


&lt;/code&gt;&lt;/pre&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Wed, 03 Dec 2025 21:04:50 +0800</pubDate>
      <link>https://ruby-china.org/topics/44405</link>
      <guid>https://ruby-china.org/topics/44405</guid>
    </item>
    <item>
      <title>西安还有在使用 Ruby 的公司吗？</title>
      <description>&lt;p&gt;这两年整体环境都不太好，去成都参加活动的那次，感觉在用的公司也少，不过还是有一些，西安感觉都没有了。&lt;img title=":weary:" alt="😩" src="https://twemoji.ruby-china.com/2/svg/1f629.svg" class="twemoji"&gt; &lt;img title=":weary:" alt="😩" src="https://twemoji.ruby-china.com/2/svg/1f629.svg" class="twemoji"&gt; &lt;img title=":weary:" alt="😩" src="https://twemoji.ruby-china.com/2/svg/1f629.svg" class="twemoji"&gt;&lt;/p&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Wed, 22 Oct 2025 20:44:12 +0800</pubDate>
      <link>https://ruby-china.org/topics/44349</link>
      <guid>https://ruby-china.org/topics/44349</guid>
    </item>
    <item>
      <title>omarchy 体验分享</title>
      <description>&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/d0715ac7-0a2f-4a9b-93e0-88eee57708e2.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/3dad2c4a-8679-4e2c-8932-996304b81f3d.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/6058bf57-8584-4f49-97fb-3c6086f36612.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/cbccbb58-9396-4d24-95a8-fe4e96bbdfe7.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/5e31053b-cd4d-4fc5-9163-f5ff9695d495.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="是什么？"&gt;是什么？&lt;/h2&gt;
&lt;p&gt;地址：&lt;a href="https://omarchy.org/" rel="nofollow" target="_blank"&gt;https://omarchy.org/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An opinionated Arch + Hyprland Setup by DHH&lt;/p&gt;

&lt;p&gt;一个由 DHH 开源分享的一套初始化工具，基于 Arch Linux + Hyperland 平铺式桌面管理框架&lt;/p&gt;

&lt;p&gt;更通俗的讲：开发环境不在使用 Windows，Mac，而是基于 Linux 系统&lt;/p&gt;

&lt;p&gt;我们一想到 Linux 就能联想到黑乎乎啥也没有的命令行，或者是丑的不行的 Ubuntu 桌面。不过现在已经是 2025 年了，Linux 社区也在不断的发展，并有许多有趣新奇的玩意儿，但我们都知道 Linux 中很多东西都是自己配置的，你需要自己一步步添加或调整一些内容，而不像 window，mac 很多东西都是开箱即用的。而 Omarchy 就是一套快速设置的脚本，让你从安装系统到实际使用 Rails 开发，仅仅只需要五分钟时间。&lt;/p&gt;
&lt;h2 id="怎么安装？"&gt;怎么安装？&lt;/h2&gt;&lt;h3 id="使用虚拟机"&gt;使用虚拟机&lt;/h3&gt;
&lt;p&gt;B 站有博主分享，我自己试了一下，安装好后没法启动，不知道什么没配好还是，后放弃&lt;/p&gt;
&lt;h3 id="本机装系统"&gt;本机装系统&lt;/h3&gt;
&lt;p&gt;我最开始尝试在 Mac M2 上安装双系统，其中教程可以在 Omarchy 的 GitHub 上查看，但是不建议，因为 M2 架构的问题，很多组件是不支持的。虚拟机上也有很多坑，总之不建议&lt;/p&gt;

&lt;p&gt;然后我在 ThinkPad X1 carbon gen9 上尝试安装，整体还是比较顺利的，不过还是有一些点需要注意：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;使用一个 U 盘，烧制 Omarchy 提供的 ISO 文件，作为启动盘&lt;/li&gt;
&lt;li&gt;Windows 启动时按 F12，然后进入执行系统安装&lt;/li&gt;
&lt;li&gt;这时候会直接安装 arch linux，然后才安装 omarchy 和下载各种依赖的包，arch linux 好了后，就可以先退出了，因为国内墙的问题很多东西到后面也会报错，所以可以提前推出，去做一些调整，然后再继续安装

&lt;ol&gt;
&lt;li&gt;修改 pacman 的源，iso 镜像中源配置文件中本身有中国的地址，但是你需要手动将那部分放到文件的开头&lt;/li&gt;
&lt;li&gt;安装 clash，因为后面要去 GitHub 中拉取项目，所以这里最好用 VPN，如果你能修改 host 访问那也行，我是不清楚怎么搞。启动代理后记得自己先验证一下是否能访问到&lt;/li&gt;
&lt;li&gt;在 ~/.auto... 执行这个脚本会继续安装
之后网络情况好的话，应该就比较顺利安装了。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;还有一点：我最开始想双系统运行，但是我执行 Omarchy 初始化任务时，发现只能选择整个磁盘，没办法安装好了后我的 window 启动不了了。Omarchy GitHub Discussion 中有讨论双系统怎么操作。可以自行尝试&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="使用体验"&gt;使用体验&lt;/h2&gt;
&lt;p&gt;一进去系统，让我又重新体验到了第一次用 Vim 的感觉，怎么啥也没有啊，都不知道点哪里启动程序，哪里关闭程序，不过好在官方文档里面介绍的很详细，各种快捷键绑定来启动程序。&lt;/p&gt;

&lt;p&gt;从这里的初体验也能体现出，这一套初始化设置对键盘优先爱好者很友善，也就是说尽可能通过键盘去控制行为，减少鼠标操作。DHH 也入坑 Nvim+LazyVim 了，所以一套下来各种快捷键，如果玩的好的，效率会很高吧。但是对于初次接触的使用者而言，痛苦面具带上，需要慢慢适应一下。我之前一直使用 Mac，所以命令行工具之类的常用，所以很快能习惯，但是对于 Window 用户，怕是很难上手。&lt;/p&gt;

&lt;p&gt;Hyperland 大家应该之前都没听说过，我也是第一次接触，平铺式桌面？？？就是说窗口不会层层堆叠，而是有序的将这个屏幕拆分成块儿，这样能很直观的进行管理，不清楚的，可以看 B 站或者 DHH 的宣传视频&lt;/p&gt;

&lt;p&gt;然后担心是否我们常用工具不能下载的问题，常见的编辑器我看都有，能用。微信的话我也下载能用。其他的暂时没测试。&lt;/p&gt;

&lt;p&gt;安装前一直听说 Arch Linux 的滚挂问题，目前还没遇到，系统操作本身还是很丝滑的。就是很多快捷键记不住，也可以配置成自己常用的键。日常大部分操作我看都没问题，主要是操作习惯的问题。&lt;/p&gt;

&lt;p&gt;总的来说研究这玩意儿，还是挺花时间的，确实有些东西需要自己配，比如输入法。建议自己想尝试的最好自己想清楚了，自己愿意投多少时间精力。我当时是被花里胡哨的介绍整上头了，如果想试了，也最好用虚拟机尝试，熟悉一下 Arch linux 之类的东西&lt;/p&gt;
&lt;h2 id="注意事项："&gt;注意事项：&lt;/h2&gt;
&lt;p&gt;不是所有电脑对 Arch Linux 都支持很好，最好去社区看看：&lt;a href="https://bbs.archlinux.org/viewtopic.php?id=22816" rel="nofollow" target="_blank"&gt;https://bbs.archlinux.org/viewtopic.php?id=22816&lt;/a&gt;  ，你的电脑使用 Arch 是否有兼容问题&lt;/p&gt;

&lt;p&gt;虚拟机安装，和双系统安装都可以去 GitHub 的 Discussion 中看看，很多人分享经验&lt;/p&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Sun, 14 Sep 2025 19:55:23 +0800</pubDate>
      <link>https://ruby-china.org/topics/44313</link>
      <guid>https://ruby-china.org/topics/44313</guid>
    </item>
    <item>
      <title>Rails 层级设计演化（笔记）</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;相关资料&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.37signals.com/vanilla-rails-is-plenty/" rel="nofollow" target="_blank" title=""&gt;37signal - 层级设计&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.37signals.com/good-concerns/" rel="nofollow" target="_blank" title=""&gt;37signal - concern 设计&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://xfyuan.github.io/2022/11/vanilla-rails-is-plenty" rel="nofollow" target="_blank" title=""&gt;层级设计翻译&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.37signals.com/good-concerns/" rel="nofollow" target="_blank" title=""&gt;concern 设计翻译&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.bilibili.com/video/BV1F94y1s7kx/?spm_id_from=333.337.search-card.all.click" rel="nofollow" target="_blank" title=""&gt;gitlab-ddd 应用&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.bilibili.com/video/BV19z411B7Qy/?spm_id_from=333.1007.top_right_bar_window_history.content.click" rel="nofollow" target="_blank" title=""&gt;面向资源编程&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="最初的样子"&gt;最初的样子&lt;/h2&gt;
&lt;p&gt;我们都知道 rails 初始化项目，在层级拆分上只有两层&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;controller&lt;/li&gt;
&lt;li&gt;model&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/GengCen-Qin/pic/main/image-20250709225003138.png" alt="image-20250709225003138" style=""&gt;&lt;/p&gt;

&lt;p&gt;controller 负责接收请求，model 负责实现存储，那业务逻辑在哪里实现？&lt;/p&gt;

&lt;p&gt;可能为了 model 的干净，将业务逻辑写到 controller 中。也有可能为了代码复用，将业务逻辑写在 model 中。但不管选择哪种方式，如果不进行谨慎的管理，随着业务的不断迭代，你的 controller 或者 model 也会逐渐的臃肿，变得难以维护。自然而然的大家决定抽离出业务层，比如：Service，Command 等等&lt;/p&gt;
&lt;h2 id="引入Service/Command"&gt;引入 Service/Command&lt;/h2&gt;
&lt;p&gt;比如我有一个报价单模型，我可能有类似的结构&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# controller&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QuotationController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Command&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QuotationCommand&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&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;list&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;modify&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;delete&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;# model&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Quotation&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所有关于报价单的所有操作我都封装到 QuotationCommand 中，通过 Controller 调用 Command，最终调用 Model&lt;/p&gt;

&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/GengCen-Qin/pic/main/image-20250709225052038.png" title="" alt="image-20250709225052038"&gt;&lt;/p&gt;

&lt;p&gt;可以看到我将增删改查的复杂业务逻辑统放入到 Command 中，假如我这时候再来一个新的需求比如我要导出一部分数据，我就简单的称为&lt;code&gt;export&lt;/code&gt;吧，根据惯性我会将这个功能也实现到 QuotationCommand 中。一切自然而然，但是很明显我的这个类早已违反了&lt;strong&gt;单一功能原则（Single responsibility principle）&lt;/strong&gt;,直到有一天这个巨无霸的 Command 也变得臃肿和难以维护，随便一个修改都可能是牵一发而动全身的。没办法我们再按照功能拆分一下吧。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/GengCen-Qin/pic/main/image-20250709225127210.png" title="" alt="image-20250709225127210"&gt;&lt;/p&gt;

&lt;p&gt;看起来很漂亮，每个 Command 只负责自己的业务，保证职责单一，如果再出现新的业务只需要构建新的 Command 即可。&lt;/p&gt;
&lt;h2 id="管理Concern"&gt;管理 Concern&lt;/h2&gt;
&lt;p&gt;我们目前的方案有什么问题呢？&lt;/p&gt;

&lt;p&gt;比如&lt;code&gt;ExportCommand&lt;/code&gt;导出功能是在列表查询的基础上做的，比如我有一个后台管理页面，它拥有一个列表搜索页，通过传递不同的筛选项，查询出不同的数据，使用&lt;code&gt;ListCommand&lt;/code&gt;。现在我希望能将查询的数据导成 Excel，也就是&lt;code&gt;ExportCommand&lt;/code&gt;负责的内容，毫无疑问我查询的功能已经有了，我只需要在&lt;code&gt;ExportCommand&lt;/code&gt;中使用&lt;code&gt;ListCommand&lt;/code&gt;即可，&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 紧耦合的Command示例&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ListCommand&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&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;call&lt;/span&gt;
    &lt;span class="c1"&gt;# 复杂的查询逻辑&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;role: &lt;/span&gt;&lt;span class="vi"&gt;@params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:role&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:role&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:q&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:q&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;created_at: :desc&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;class&lt;/span&gt; &lt;span class="nc"&gt;ExportCommand&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&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;call&lt;/span&gt;
    &lt;span class="c1"&gt;# 直接依赖ListCommand的实现&lt;/span&gt;
    &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ListCommand&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="vi"&gt;@params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;

    &lt;span class="c1"&gt;# 导出逻辑&lt;/span&gt;
    &lt;span class="no"&gt;CSV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&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;csv&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;csv&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sx"&gt;%w[id name email]&lt;/span&gt;
      &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;]&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;那这就牵扯到一个问题：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Command 之前是否允许相互调用？&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;如果相互允许调用，势必引发耦合性，也就是&lt;code&gt;ListCommand&lt;/code&gt;的修改势必会影响到&lt;code&gt;ExportCommand&lt;/code&gt;, 或者说因为 ExportCommand 需要一些其他内容，反而去调整&lt;code&gt;ListCommand&lt;/code&gt;的行为。&lt;/p&gt;

&lt;p&gt;更好的方式应该将 Command 共用的业务逻辑，封装到一个独立的 Poro(Plain Old Ruby Object) 中，或者将业务封装到 Model 中，通过 Concern 来组织。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 将查询逻辑提取到独立模块&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Search&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserSearch&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&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;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@scope&lt;/span&gt;
      &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filter_by_role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:role&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;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:role&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filter_by_keyword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:q&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;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:q&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;created_at: :desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;filter_by_role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;role: &lt;/span&gt;&lt;span class="n"&gt;role&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;filter_by_keyword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 假设有search方法&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;# 解耦后的Command&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ListCommand&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&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;call&lt;/span&gt;
    &lt;span class="no"&gt;Search&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UserSearch&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="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@params&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;class&lt;/span&gt; &lt;span class="nc"&gt;ExportCommand&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&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;call&lt;/span&gt;
    &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Search&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UserSearch&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="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="no"&gt;CSV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&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;csv&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;csv&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sx"&gt;%w[id name email]&lt;/span&gt;
      &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;]&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;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;过度抽离 Command 本身可能会有什么问题？&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我们引用一些评论&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Now, the more common mistake is to give up too easily on fitting the behavior into an appropriate object, gradually slipping towards procedural programming.*&lt;/p&gt;

&lt;p&gt;现在，更常见的错误是过于轻易地放弃将行为适配到适当的对象中，逐渐滑向过程编程。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Don’t lean too heavily toward modeling a domain concept as a Service. Do so only if the circumstances fit. If we aren’t careful, we might start to treat Services as our modeling “silver bullet.” Using Services overzealously will usually result in the negative consequences of creating an Anemic Domain Model, where all the domain logic resides in Services rather than mostly spread across Entities and Value Objects.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;不要过于倾向于将领域概念建模为一个 Service。仅仅在情况适合时才这样做。如果我们不仔细地话，就可能会开始将 Services 视为建模的“银弹”。过度使用 Services 通常会导致创建贫血领域模型的负面后果，其中所有领域逻辑都驻留在 Services 中，而不是主要分布在实体和值对象中。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;也就是说，如果我们如果不假思索的将所有的行为一股脑的塞进 Service 层级中，而不考虑内聚性，我们会渐渐走向面向过程编程，并且会导致模型&lt;strong&gt;贫血&lt;/strong&gt;，即为携带数据的空壳。&lt;/p&gt;

&lt;p&gt;所以我们再看看该怎么组织 Model？&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;假设我们有一个&lt;code&gt;User&lt;/code&gt;模型，随着业务发展，它包含了用户认证、资料管理、权限检查、统计报表等多种功能，导致代码量过大。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/user.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="c1"&gt;# 验证相关&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;uniqueness: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;length: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;minimum: &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# 认证相关&lt;/span&gt;
  &lt;span class="n"&gt;has_secure_password&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:sessions&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_auth_token&lt;/span&gt;
    &lt;span class="c1"&gt;# 生成认证token的逻辑&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# 资料管理相关&lt;/span&gt;
  &lt;span class="n"&gt;before_save&lt;/span&gt; &lt;span class="ss"&gt;:format_name&lt;/span&gt;
  &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="ss"&gt;:profile&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;full_name&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="si"&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;last_name&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="kp"&gt;private&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;format_name&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;first_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capitalize&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;last_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capitalize&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# 权限相关&lt;/span&gt;
  &lt;span class="no"&gt;ROLES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%w[admin moderator user guest]&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;admin?&lt;/span&gt;
    &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'admin'&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;can_edit?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;admin?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# 统计相关&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active_users_count&lt;/span&gt;
    &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;last_active_at: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;week&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&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;activity_score&lt;/span&gt;
    &lt;span class="c1"&gt;# 计算用户活跃度分数&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# ... 更多方法 ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;里面混乱的组织着各种方法，假如我想找到某个功能，我需要一个个的看，哪些对我是有用的。这也称为&lt;code&gt;Fat Model&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;但如果我们能用 Concern 进行合理化的拆分聚合，效果就会完全不同&lt;/p&gt;
&lt;h3 id="拆分的目录结构："&gt;拆分的目录结构：&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
  models/
    concerns/
      user/
        authenticatable.rb
        profileable.rb
        roleable.rb
        reportable.rb
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="拆分后Concern模块"&gt;拆分后 Concern 模块&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/concerns/user/authenticatable.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;User::Authenticatable&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="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;uniqueness: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;length: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;minimum: &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;has_secure_password&lt;/span&gt;
    &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:sessions&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;generate_auth_token&lt;/span&gt;
    &lt;span class="c1"&gt;# 生成认证token的逻辑&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/concerns/user/profileable.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;User::Profileable&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="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;before_save&lt;/span&gt; &lt;span class="ss"&gt;:format_name&lt;/span&gt;
    &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="ss"&gt;:profile&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;full_name&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="si"&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;last_name&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="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;format_name&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;first_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capitalize&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;last_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capitalize&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/concerns/user/roleable.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;User::Roleable&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="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;ROLES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%w[admin moderator user guest]&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;admin?&lt;/span&gt;
    &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'admin'&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;can_edit?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;admin?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/concerns/user/reportable.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;User::Reportable&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="n"&gt;class_methods&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;active_users_count&lt;/span&gt;
      &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;last_active_at: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;week&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&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;activity_score&lt;/span&gt;
    &lt;span class="c1"&gt;# 计算用户活跃度分数&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="拆分后的User模型"&gt;拆分后的 User 模型&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/user.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Authenticatable&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Profileable&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Roleable&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Reportable&lt;/span&gt;

  &lt;span class="c1"&gt;# 这里只保留模型特有的或无法分类的方法&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样拆分后，可能都不用构建特定的 Command，模型本身维护拆分良好的功能，来让 Controller 直接进行调用&lt;/p&gt;

&lt;p&gt;但是这也要求团队内的成员遵守统一的规范和要求，并且具有良好的抽象化思想，这一点很难，最难的可能是如何给你的模块命名🤣，因为它直接反映了该模块儿的内涵。&lt;/p&gt;
&lt;h2 id="面向资源思考"&gt;面向资源思考&lt;/h2&gt;
&lt;p&gt;如何管理 Controller 或者说是路由，通过面向资源 (resource) 的方式。这是有些有趣的观点值得思考&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;引用&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jeromedalbert.com/how-dhh-organizes-his-rails-controllers/" rel="nofollow" target="_blank" title=""&gt;dhh 如何管理 Controller&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.bilibili.com/video/BV19z411B7Qy" rel="nofollow" target="_blank" title=""&gt;Reconsider REST by 陈金洲&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="为什么需要它"&gt;为什么需要它&lt;/h3&gt;
&lt;p&gt;传统的 Rails Controller 设计容易陷入"动作膨胀"的陷阱：&lt;/p&gt;

&lt;p&gt;最初 Controller 只有默认的七个标准方法（index, show, new, edit, create, update, destory），对应代表的含义也很清晰明确，针对某一资源的查询，详情，新建，编辑，删除操作。但是随着业务的不断膨胀，Controller 内的非标准方法会越来越多，对于一个资源的操作也渐渐不再清晰。&lt;/p&gt;

&lt;p&gt;比如说我有个一个 Topic(Model)，最初我只需要使用 CRUD 操作，七个标准方法能满足我的要求，后面我需要新加一个功能：&lt;code&gt;喜欢（favorite）/取消喜欢（unfavorite）&lt;/code&gt;，我可能直接这么写&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 传统方式 - 动作膨胀&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TopicsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="c1"&gt;# 标准CRUD动作&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&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;show&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="c1"&gt;# 自定义动作越来越多&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;favorite&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;unfavorite&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;publish&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;archive&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;recommend&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="c1"&gt;# ... 随着业务增长，这里会越来越长&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种设计会导致：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Controller 变得臃肿，难以维护&lt;/li&gt;
&lt;li&gt;动作之间缺乏明确的组织原则&lt;/li&gt;
&lt;li&gt;路由变得复杂且不一致&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="如何用资源思维重构？"&gt;如何用资源思维重构？&lt;/h3&gt;&lt;h4 id="重构前 - 传统方式"&gt;重构前 - 传统方式&lt;/h4&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# routes.rb&lt;/span&gt;
&lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:topics&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ss"&gt;:favorite&lt;/span&gt;    &lt;span class="c1"&gt;# /topics/:id/favorite&lt;/span&gt;
    &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ss"&gt;:unfavorite&lt;/span&gt; &lt;span class="c1"&gt;# /topics/:id/unfavorite&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;# controllers/topics_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;favorite&lt;/span&gt;
  &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;favorites&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;topic: &lt;/span&gt;&lt;span class="vi"&gt;@topic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="vi"&gt;@topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;notice: &lt;/span&gt;&lt;span class="s2"&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;unfavorite&lt;/span&gt;
  &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;favorites&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;topic: &lt;/span&gt;&lt;span class="vi"&gt;@topic&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;destroy_all&lt;/span&gt;
  &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="vi"&gt;@topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;notice: &lt;/span&gt;&lt;span class="s2"&gt;"已取消收藏"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="重构后 - 资源方式"&gt;重构后 - 资源方式&lt;/h4&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# routes.rb&lt;/span&gt;
&lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:topics&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="ss"&gt;:favorite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:create&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:destroy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# 单数资源&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# controllers/favorites_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FavoritesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:set_topic&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;favorites&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;topic: &lt;/span&gt;&lt;span class="vi"&gt;@topic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="vi"&gt;@topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;notice: &lt;/span&gt;&lt;span class="s2"&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;destroy&lt;/span&gt;
    &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;favorites&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;topic: &lt;/span&gt;&lt;span class="vi"&gt;@topic&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;destroy_all&lt;/span&gt;
    &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="vi"&gt;@topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;notice: &lt;/span&gt;&lt;span class="s2"&gt;"已取消收藏"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_topic&lt;/span&gt;
    &lt;span class="vi"&gt;@topic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Topic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:topic_id&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;那如果按照资源的方式思考，是不是可以理解为&lt;/p&gt;

&lt;p&gt;&lt;code&gt;create favorite&lt;/code&gt;  请求 喜欢 这个行为 =&amp;gt; 创建 喜欢 这个资源&lt;/p&gt;

&lt;p&gt;&lt;code&gt;destory favorite&lt;/code&gt; 请求 取消喜欢 这个行为 =&amp;gt; 删除 喜欢 这个资源&lt;/p&gt;

&lt;p&gt;两个 Controller 都保持了简单和整洁。需要注意这里的 Favorite 可能是一个数据库存储的表，也可能只是一个业务逻辑模型。&lt;/p&gt;

&lt;p&gt;其实这个思维更打动我的是，在 Model 设计上我们可以利用类似的想法，Model 并不代表一定要和数据库交互，我们可以通过 ActiveModel 来直接使用 Rails 提供的便利，比如校验，回调等行为。通过这种方式将抽象复杂的业务逻辑，思考为一个个小的资源对象，并相互发送信息。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;简略总结几点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;鼓励构建&lt;code&gt;rich domain model&lt;/code&gt;，不过为了避免&lt;code&gt;Fat&lt;/code&gt;

&lt;ol&gt;
&lt;li&gt;使用 Concern 来组织 Model 的代码&lt;/li&gt;
&lt;li&gt;将复杂功能委托到额外的对象系统（PORO）&lt;/li&gt;
&lt;li&gt;ActiveRecord 和 Poro 视为一整套领域模型，而至于是否需要持久化，业务逻辑消费者并不在意&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;对于复杂的行为，可构建 Service/Command，但是需要谨慎和良好的设计（比较有趣的是：&lt;code&gt;37signal&lt;/code&gt;很激进，觉得 Service 层都是没有必要的。）&lt;/li&gt;
&lt;li&gt;利用面向资源的方式进行思考和建模&lt;/li&gt;
&lt;/ol&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Sun, 13 Jul 2025 11:03:35 +0800</pubDate>
      <link>https://ruby-china.org/topics/44220</link>
      <guid>https://ruby-china.org/topics/44220</guid>
    </item>
    <item>
      <title>Lazyapi 通过终端管理你的 API，尽可能减少双手离开键盘的次数😂</title>
      <description>&lt;h2 id="Lazyapi"&gt;Lazyapi&lt;/h2&gt;&lt;h2 id="是什么？"&gt;是什么？&lt;/h2&gt;
&lt;p&gt;通过终端操作的 API 管理工具，该项目受到&lt;a href="https://github.com/jesseduffield/lazygit" rel="nofollow" target="_blank" title=""&gt;lazygit&lt;/a&gt;的鼓舞与启发，在某些简单的 API 对接和调用的场景上，希望能简单，快速，直观的处理 API，包含：对 API 的增删改查，请求，历史请求记录等操作。&lt;/p&gt;
&lt;h2 id="示例"&gt;示例&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/df0542f3-fe70-4173-9520-5a7bd5bcc285.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/27306c1a-f1e7-4771-9ed4-f4742b17dbde.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/b2a48846-c810-4330-bb0c-90d9b939a3df.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="使用"&gt;使用&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/GengCen-Qin/lazyapi" rel="nofollow" target="_blank" title=""&gt;Github&lt;/a&gt; 下载项目到本地，并执行 &lt;code&gt;go build&lt;/code&gt; 生成可执行文件&lt;/p&gt;

&lt;p&gt;对于数据的存储与读取，通过 Sqlite，Mac 用户会自动尝试在 &lt;code&gt;~/Library/Application Support/lazyapi&lt;/code&gt; 下生成存储文件&lt;/p&gt;

&lt;p&gt;在操作上，你可以选择先创建一个 API，然后对其进行请求发送，目前仅支持 GET/POST 请求，请求参数要求为 json 格式，如果是 POST 请求会放到 body 中，如果是 Get 请求，会拼接在 url 后面，后续会增加更细致的功能。&lt;/p&gt;

&lt;p&gt;某些临时请求上，可以直接通过快捷键&lt;code&gt;g&lt;/code&gt;快速生成一个 Get 请求，&lt;code&gt;p&lt;/code&gt;快速生成一个 Post 请求，当回车后会直接发送请求&lt;/p&gt;
&lt;h2 id="注意"&gt;注意&lt;/h2&gt;
&lt;p&gt;由于底层使用的&lt;a href="https://github.com/jroimartin/gocui" rel="nofollow" target="_blank" title=""&gt;gocui&lt;/a&gt;对中文支持的不好，所以当中文输入处理上会有问题，目前仅 fork 了一个版本，解决了输入和展示的问题，但是在光标移动和删除上，仍有问题，需要操作两次才行，例如删除时，删除键按两次，因为底层存储时中文字长占了两位。&lt;/p&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Sun, 30 Mar 2025 17:14:32 +0800</pubDate>
      <link>https://ruby-china.org/topics/44112</link>
      <guid>https://ruby-china.org/topics/44112</guid>
    </item>
    <item>
      <title>在 Zed 编辑器中使用 Telescope 风格进行查询定位文件</title>
      <description>&lt;p&gt;尝试过&lt;code&gt;nvim&lt;/code&gt;的人应该都对 telescope 这种文件查询风格很喜欢，当在输入时能自动进行重新查询，并通过上下键进行翻阅&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/c9925485-f41d-4047-b830-df7d6011c061.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;我目前在使用另一款编辑器 Zed，它目前内置的查询并无法做到类似的操作。但通过内置的 Terminal+fzf+bat+rg 也能做到类似的效果&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/8bebe536-17bd-4da9-8569-f554773e9533.gif" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;git 链接：&lt;a href="https://l.ruby-china.com/photo/qinsicheng/8bebe536-17bd-4da9-8569-f554773e9533.gif" rel="nofollow" target="_blank"&gt;https://l.ruby-china.com/photo/qinsicheng/8bebe536-17bd-4da9-8569-f554773e9533.gif&lt;/a&gt;   帖子里看没有打开成功&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;fish function&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;function &lt;/span&gt;search_and_edit
    &lt;span class="c"&gt;# 使用rg进行文件搜索，并使用fzf进行预览&lt;/span&gt;
    &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; selected_file &lt;span class="o"&gt;(&lt;/span&gt;rg &lt;span class="nt"&gt;--column&lt;/span&gt; &lt;span class="nt"&gt;--hidden&lt;/span&gt; &lt;span class="nt"&gt;--line-number&lt;/span&gt; &lt;span class="nt"&gt;--no-heading&lt;/span&gt; &lt;span class="nt"&gt;--color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;always &lt;span class="nt"&gt;--smart-case&lt;/span&gt; &lt;span class="nt"&gt;--glob&lt;/span&gt; &lt;span class="s1"&gt;'!**/.git/'&lt;/span&gt; &lt;span class="nt"&gt;--glob&lt;/span&gt; &lt;span class="s1"&gt;'!**/node_modules/'&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; | fzf &lt;span class="nt"&gt;--ansi&lt;/span&gt; &lt;span class="nt"&gt;--delimiter&lt;/span&gt; : &lt;span class="nt"&gt;--preview&lt;/span&gt; &lt;span class="s1"&gt;'bat --style=numbers,changes,header --color=always --highlight-line {2} {1}'&lt;/span&gt; &lt;span class="nt"&gt;--preview-window&lt;/span&gt; &lt;span class="s1"&gt;'up:60%:+{2}+3/3'&lt;/span&gt; &lt;span class="nt"&gt;--layout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;reverse&lt;span class="o"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;# 如果选择了文件，提取文件路径、行号和列号&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$selected_file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; file &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$selected_file&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;':'&lt;/span&gt; &lt;span class="nt"&gt;-f1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; line &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$selected_file&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;':'&lt;/span&gt; &lt;span class="nt"&gt;-f2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; col &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$selected_file&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;':'&lt;/span&gt; &lt;span class="nt"&gt;-f3&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;# 使用Zed编辑器打开文件&lt;/span&gt;
        zed &lt;span class="nv"&gt;$file&lt;/span&gt;:&lt;span class="nv"&gt;$line&lt;/span&gt;:&lt;span class="nv"&gt;$col&lt;/span&gt;
    end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Zed keymap&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Editor &amp;amp;&amp;amp; vim_mode == normal &amp;amp;&amp;amp; !VimWaiting &amp;amp;&amp;amp; !menu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bindings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"space f"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"workspace::SendKeystrokes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;在页面上打开Terminal，执行&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;search_and_edit(我定义的脚本)，执行结束后关闭&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;": new center terminal enter search_and_edit space &amp;amp;&amp;amp; space exit enter"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样通过快捷键就能快速打开进行查询，同时可以看到这种方式主要依赖于 Terminal，所以应该能很容易改到其他编辑器中。&lt;/p&gt;

&lt;p&gt;另外让人很兴奋的一点，通过这种方式也能将&lt;code&gt;lazygit&lt;/code&gt;通过相同的方式进行使用，因为现在 zed 的 git 工具并不完善。&lt;/p&gt;

&lt;p&gt;参考帖子：&lt;a href="https://github.com/zed-industries/zed/issues/8279" rel="nofollow" target="_blank"&gt;https://github.com/zed-industries/zed/issues/8279&lt;/a&gt;&lt;/p&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Sat, 23 Nov 2024 13:37:58 +0800</pubDate>
      <link>https://ruby-china.org/topics/43965</link>
      <guid>https://ruby-china.org/topics/43965</guid>
    </item>
    <item>
      <title>window 使用 docker 开发 ruby 有坑吗？</title>
      <description>&lt;p&gt;最近想换个新笔记本电脑，内存怎么着也得 16g，感觉苹果最基本的都是 9 千多，但是 window 能便宜小一半，不知道作为 Ruby 开发有没有坑，使用 docker 跑一个容器来运行。&lt;/p&gt;

&lt;p&gt;然后就是有没有推荐的产品&lt;/p&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Tue, 10 Sep 2024 09:02:13 +0800</pubDate>
      <link>https://ruby-china.org/topics/43882</link>
      <guid>https://ruby-china.org/topics/43882</guid>
    </item>
    <item>
      <title>如何在文本编辑器中快速找到变量定义位置</title>
      <description>&lt;p&gt;之前一直使用 Webstorm 开发前端项目，因为它的常量定义查询真的太方便了。最近许可证要过期了，就在想如何在：&lt;code&gt;Zed，Vscode，ST&lt;/code&gt; 这种文本编辑器中去做到类似效果。&lt;/p&gt;

&lt;p&gt;比如我目前开发的 Vue 项目，感觉提供的 LSP 和文本编辑器自带的定义查询并不能很好的支持&lt;/p&gt;

&lt;p&gt;比如查询：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;code&gt;this.$CONS.ORDER.MODIFY_TASK.MODIFY_TYPE&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;this.info('app_users_lists.validation', 'blocks.base_info.attributes')&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;目前我在使用正则表达式去查询，效果是有的，但感觉表达式复杂不好写。想看看各位大佬是怎么解决类似问题的。&lt;/p&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Sat, 10 Aug 2024 15:16:04 +0800</pubDate>
      <link>https://ruby-china.org/topics/43842</link>
      <guid>https://ruby-china.org/topics/43842</guid>
    </item>
    <item>
      <title>SublimeText Web 开发在 2024</title>
      <description>&lt;p&gt;随着 LSP 和 AI 发展的越来越完善，SublimeText 也可通过各类插件享受到新的福利，最近尝试使用 ST4(SublimeText4) 作为主开发工具，体验上还是很爽的，所以想给想尝试的朋友一些推荐。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;为什么不用 Vscode，Jetbrains 等主流 IDE 工具？&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;​ 老实讲我感觉 ST 概念比较简单，很快，按钮少😂，但是功能上 IDE 肯定更全面，对新人更友善。每个人有自己在意的点，如果喜欢原先的开发工具也完全 OK，目前我通过插件将 ST 改成了一个简略版 IDE，对一些基础操作：&lt;code&gt;git,sql,terminal,代码补全,定义/引用查找等功能&lt;/code&gt;已经集成的不错了，觉得 ST 是大家选择开发工具中的一个可选项。&lt;/p&gt;

&lt;p&gt;​ &lt;a href="https://zed.dev/" rel="nofollow" target="_blank" title=""&gt;Zed&lt;/a&gt;也很不错，不过还不太完善，所以感觉还等一段时间，很期待。&lt;/p&gt;
&lt;h2 id="Plugins"&gt;Plugins&lt;/h2&gt;
&lt;p&gt;插件统一通过&lt;code&gt;Package Control&lt;/code&gt;下载即可&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;主题&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;视觉效果看着舒服是很重要的一个点，我目前尝试了：&lt;code&gt;ayn&lt;/code&gt;,&lt;code&gt;Material&lt;/code&gt;插件，感觉都不错，不过可能有些小细节，希望能自定义一下，比如注释代码的颜色，侧边栏字体大小，滚动块儿样式等等。&lt;/p&gt;

&lt;p&gt;可以通过&lt;code&gt;PackageResourceViewer&lt;/code&gt;插件，快速定位到插件样式定义文件，然后去修改&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Git&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;Git 插件&lt;/li&gt;
&lt;li&gt;GitGutter 插件&lt;/li&gt;
&lt;li&gt;Sublime Merge 软件&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;通过&lt;code&gt;Git,GitGutter&lt;/code&gt;可以很方便在写代码时操作 Git，不过有时候遇到代码冲突，分支管理等操作还是不太方便，通过 Sublime Merge 可以无缝衔接增强。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;命令行&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;Terminal &lt;/li&gt;
&lt;li&gt;Terminus&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;可以通过绑定快捷键，快速打开命令行，但是我发现 ST 中的 Panel 默认只能打开一个 Shell。通过快捷键可以解决这个问题：&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"keys"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"alt+1"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"toggle_terminus_panel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"panel_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"first-panel"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"keys"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"alt+2"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"toggle_terminus_panel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"panel_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"second-panel"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就可以通过快捷键，切换到不同的 shell 中了。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;SQLTools 插件&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;操作 SQL 的瑞士军刀，真的超级好用，提供 SQL 编写字段，表等补全功能，不过由于项目中没有 sql 文件，每次创建删除又很麻烦，需要注意 ST 原生的宏记录，对于创建新文件页面无效，我通过&lt;code&gt;Multicommand&lt;/code&gt;插件，来编写宏：&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;query_file.sublime-macro&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cmd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"new_file"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"untitled.sql"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"syntax"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SQL"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"flags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"window"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;文件操作&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;AdvancedNewFile 快速创建文件&lt;/li&gt;
&lt;li&gt;Quick File Creator 快速指定目录创建文件&lt;/li&gt;
&lt;li&gt;SyncedSideBar ST 在文件切换时，侧边文件导航栏并不会定位到当前文件，通过该插件可以自动定位&lt;/li&gt;
&lt;li&gt;Local History 记录本地代码修改记录&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;AI 代码补全&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://codeium.com/" rel="nofollow" target="_blank" title=""&gt;Codeium&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;配置简单，响应快速，智商还算在线，官网上说比 Copilot 强，个人免费。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;LSP&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;     "LSP"&lt;/li&gt;
&lt;li&gt;     "LSP-css"&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;     "LSP-eslint",&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;     "LSP-json",&lt;/li&gt;
&lt;li&gt;     "LSP-typescript",&lt;/li&gt;
&lt;li&gt;     "LSP-volar"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;目前写 Ruby 用的 LSP 是&lt;code&gt;solargraph&lt;/code&gt;,可以看文档自己去配置就好，&lt;a href="https://lsp.sublimetext.io/language_servers/" rel="nofollow" target="_blank" title=""&gt;sublime-lsp&lt;/a&gt;。再说一遍 LSP 真香！！！&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;配置同步&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;Sync Settings&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;就是说在笔记本上 ST 的配置，想同步到台式机上，就可以通过该插件将配置同步到：gist.github.com &lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;这一套下来，就基本上差不多了，像前端开发也会用&lt;a href="https://packagecontrol.io/packages/Emmet" rel="nofollow" target="_blank" title=""&gt;&lt;strong&gt;Emmet&lt;/strong&gt;&lt;/a&gt;插件，不过最近发现在写 Vue 时，常量的定义查找，标签组件的定义查找，都没用，只能通过文件名搜索查看，感觉还是不太方便，目前仍在找解决方案。&lt;/p&gt;

&lt;p&gt;可以看出 ST 仍有一些不便之处，所以就得视情况而定去选择工具。为了某些优势，能容纳其他的问题。&lt;/p&gt;

&lt;p&gt;最近想学着写写插件，前辈们有推荐的学习方式吗。&lt;/p&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Sun, 16 Jun 2024 15:47:26 +0800</pubDate>
      <link>https://ruby-china.org/topics/43747</link>
      <guid>https://ruby-china.org/topics/43747</guid>
    </item>
    <item>
      <title>编辑器的发展趋势？</title>
      <description>&lt;p&gt;之前一直使用 &lt;strong&gt;Jetbrain&lt;/strong&gt; 系列的产品进行开发，操作都很舒服。当仍有一些问题让人无奈&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;占内存，比如开发 Java 时，本地跑几个服务内存就快没了，直接卡死&lt;/li&gt;
&lt;li&gt;启动速度慢&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;然后就使用 Vscode，但是发现在打开一些特别大的文件时，还是不太行&lt;/p&gt;

&lt;p&gt;最后使用 SublimeText，速度惊人&lt;/p&gt;

&lt;p&gt;Sublime Text 给我的感觉是简单纯粹，但看到插件库维护的越来越少，都转到 Vscode 时还是觉得有些伤感&lt;img title=":anguished:" alt="😧" src="https://twemoji.ruby-china.com/2/svg/1f627.svg" class="twemoji"&gt; 。Vscode 似乎现在是最受欢迎的了。&lt;/p&gt;

&lt;p&gt;感觉汇总出四个方向：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;简单，快速&lt;/li&gt;
&lt;li&gt;插件化，简单或复杂由自己说的算&lt;/li&gt;
&lt;li&gt;巨无霸式，一条龙服务&lt;/li&gt;
&lt;li&gt;LSP &lt;img title=":heart_eyes:" alt="😍" src="https://twemoji.ruby-china.com/2/svg/1f60d.svg" class="twemoji"&gt; &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;以前看黑客与画家时，作者提到一百年后的编程语言的设想，这很酷，但写代码还是得有编辑器吧，那未来的编辑器是什么样子啊？难道直接对 AI 说需求，生成机器码&lt;img title=":joy:" alt="😂" src="https://twemoji.ruby-china.com/2/svg/1f602.svg" class="twemoji"&gt; &lt;/p&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Fri, 08 Mar 2024 18:34:17 +0800</pubDate>
      <link>https://ruby-china.org/topics/43620</link>
      <guid>https://ruby-china.org/topics/43620</guid>
    </item>
    <item>
      <title>编程语言学习与人文语言学习是否有共同之处</title>
      <description>&lt;p&gt;突然在想学习英语，德语和学习 Go，Lua 有什么差别，同样是学习一门不同的语言？&lt;/p&gt;

&lt;p&gt;但新学一门编程语言明显速度要快的多，我能想到的答案是，我们在不断的使用它，类似的如果我们只通过书本学习编程语言那大概率也无法学会。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;当我们敲击代码，并执行时，如果有显著语法错误，就会立刻得到反馈，如果逻辑错误，则在运行时得到反馈。（相当于找个外国人免费聊天，且帮我纠正错误，想到了&lt;a href="https://www.grammarly.com/" rel="nofollow" target="_blank" title=""&gt;grammarly&lt;/a&gt;）这个工具&lt;/li&gt;
&lt;li&gt;当遇到不懂的内容，我们通过 Google，Github，StackOverFlow，CSDN 等工具查看相应的文章，获得一个更广泛和具体的概念。（而当我不懂一个单词的意思时，我往往只会查看中文翻译和英文翻译）&lt;/li&gt;
&lt;li&gt;人文语言中有大量的关键词，每个事物对象都有自己的名字，而不是由我们去命名，当然我们可以起别名😁(起外号不好),我们大量的时间都在记住这些名称上，我记着我学习 Java 的第一课是写一个输出 Hello Word 时。我很痛苦的要记住每个单词是怎么拼写的😭   简写是一个好办法：K8s = Kubernetes，LOL = Laugh Out Loud
&lt;code&gt;java
public class HelloWorld {
public static void main(String[] args) {
    System.out.println("Hello World");
}
}
&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;朋友对我说，想快速学一门编程语言，比如 Rust，就去做一个会话管理工具。至少做个有趣的东西出来，整个过程便不会太枯燥，那学习英语的话我要用它做什么呢？写英文博客？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;大学毕业后除了看国外文章，就没咋学了，感觉词汇量还是原来那些，每次都是坚持没超过一个月&lt;img title=":joy:" alt="😂" src="https://twemoji.ruby-china.com/2/svg/1f602.svg" class="twemoji"&gt; &lt;/p&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Sun, 24 Dec 2023 20:57:45 +0800</pubDate>
      <link>https://ruby-china.org/topics/43524</link>
      <guid>https://ruby-china.org/topics/43524</guid>
    </item>
    <item>
      <title>面向对象的演化过程和解决问题</title>
      <description>&lt;h2 id="面向对象的演化过程和解决问题"&gt;面向对象的演化过程和解决问题&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;前言：&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;相信大家对面向对象这个概念已经很熟悉了，如果你是使用 Java 入门编程的，一定会被大量灌输：&lt;code&gt;类，继承，多态&lt;/code&gt;的概念，但我们真的理解它们吗？一切就好像自然而然，写代码不就应该是这样吗？但事实上这些概念，只是我们脑海中的一个毫无意义的符号，当我们写一个类时，我们只是按照被教导的方式编写。好比现在最多的 Web 开发，业务是用户的增删改查，我们想到用户会有：name,age,gender 属性。于是我们创建一个实体：&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;gender&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后按部就班的写出 Mapper(或 Dao) 层，Service 层，Controller 层，这就是 MVC 结构，它是如此的好用，让我们编写简单业务时十分顺手。但是当业务逐渐复杂时，我们在 Service，Controller 层中添加了大量的外部依赖，各种依赖注入，大量重复的代码堆砌在各个角落，一个变量被各个函数引用修改，当出现问题时，我们不得不到各个函数中查看，到底哪里出了问题，面向对象中的概念已抛到脑后。而且很糟的一点是，好像除了 Web 开发，想写出其他程序，便不知道从何写起了。我们的脑袋还被框架禁锢住了。&lt;/p&gt;

&lt;p&gt;渐渐地，我们知道了设计模式这玩意儿，听说有了它，便能化腐朽为神奇，不过打开书发现，基本上都是从面向对象讲起，为什么还是它？因为它是一切的基础，面向对象就是为了解决以往混乱编程所演化的思想。所以本文会列出面向对象的演化过程，各个阶段遇到的问题，已经是如何慢慢改进的。&lt;/p&gt;

&lt;p&gt;最早我使用 Python 写爬虫程序，那里只有一个 py 文件，没有&amp;lt;类，继承，多态&amp;gt;。只有自上而下的变量，条件，循环，网页请求，正则匹配，然后输出到控制台。但问题也能被很好的解决了。 &lt;/p&gt;
&lt;h2 id="机器语言"&gt;机器语言&lt;/h2&gt;
&lt;p&gt;计算机只可以解释用二进制数编写的机器语言。并且计算机不对机器语言检查，而是飞快的执行。二十世纪四十年代，还没有现在这么多通俗易懂的编程语言。程序员必须用机器语言编写，我们看看个简单的例子，这里只是简单的算术计算。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;A10010
8B160210
01D0
A10410
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这玩意儿没人愿意看和写吧，致敬前辈们。😭&lt;/p&gt;
&lt;h2 id="汇编语言"&gt;汇编语言&lt;/h2&gt;
&lt;p&gt;为了改善机器语言的编码方式，汇编语言出现了，它将无含义的机器语言用人类易懂的符号表达，我们再改写上面的代码&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;MOV AX, X
MOV DX, Y
ADD AX,DX
MOV Z, AX
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这玩意儿大概猜一下，能看出 MOV 是信息传递，ADD 是加法运算吧，通过这种方式，编码方式变得轻松了许多，但逐个指定计算机的执行命令也很麻烦。&lt;/p&gt;
&lt;h2 id="高级语言"&gt;高级语言&lt;/h2&gt;
&lt;p&gt;随后的高级语言采用人类更容易理解的方式进行，改写上面的代码&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;Z&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;X+Y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到程序的形式和数学计算公式非常相似，即使没有编程经验的人也能理解。&lt;/p&gt;
&lt;h2 id="结构化编程"&gt;结构化编程&lt;/h2&gt;
&lt;p&gt;随着高级语言的出现，编程的效率和质量都在不断上升，再加上计算机的飞速发展和普及，人们对提高编程效率的需求从未止步，也就提出了各种新的思想和编程语言。&lt;/p&gt;

&lt;p&gt;其中最受关注的是：&lt;strong&gt;结构化编程&lt;/strong&gt;，核心思想是：&lt;code&gt;为了编写正确运转的程序，采用简单易懂的结果是非常重要的。&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;而具体方式：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;废除 GOTO 语句&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在 Java 中我们都没听过 GOTO 语句，不过在 C 语音中还有，这是因为当年内存容量，硬件性能都很差，推崇哪怕减少一字节，或者一步也好，代码顺序执行混乱，导致维护性很差。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;使用循序，选择，重复来表达逻辑&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;循序就是按顺序执行代码&lt;/p&gt;

&lt;p&gt;选择就是条件判断，如 if,case&lt;/p&gt;

&lt;p&gt;重复就是重复执行某个命令，如 for，while&lt;/p&gt;

&lt;p&gt;这三种结构被称为&lt;strong&gt;三种基本结构&lt;/strong&gt;，我们也可以看到现在的编程语言都遵循这样的结构。&lt;/p&gt;
&lt;h3 id="提高子程序的独立性，强化维护性"&gt;提高子程序的独立性，强化维护性&lt;/h3&gt;
&lt;p&gt;我们都知道，如果一段代码经常重复出现，我们可以将它抽离为一个函数，进行统一调用，这样在未来修改时，也很方便。&lt;/p&gt;

&lt;p&gt;而在当时为了强化程序的可维护性，还有另一种方法，就是提高子程序 (函数) 的独立性。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;那该如何提高函数的独立性呢？&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;减少主程序与子程序共享信息，而多个子程序中共享的信息就是全局变量。所以就要尽可能减少全局变量。&lt;/p&gt;

&lt;p&gt;为什么这么做呢？你想想这个变量谁的都能访问修改，出了问题我找谁啊！现在有了 IDE 开发工具，我们还算能方便点儿，但在以前的时候，编译先等着，获取执行结果再等着，再去成百上千个代码中查找问题就更费时间了。&lt;/p&gt;

&lt;p&gt;为了解决这样的问题，人们设计了两种结构：局部变量，按值传递。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;局部变量：在函数中使用的变量，函数执行开始创建，结束时消失。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;按值传递：在传参时，不直接使用调用端引用的变量，而是复制值进行传递，这样即使函数内部进行了修改，也不会影响外界的变量。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这两种结构能将全局变量的使用控制到最小范围，减少子程序共享的信息，&lt;strong&gt;以此提高函数的独立性&lt;/strong&gt;。&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;在结构化语言中最有名的就是 C 语言，它完全支持结构化编程的功能，并且能高效的操作内存，不过另一个特征就是：函数库，编程需要的功能不仅是语言规范提供，还有函数库，比如输出字符串 printf。这样就能不用修改编译器，也能增加新的功能&lt;/p&gt;
&lt;h3 id="进化方向演化为重视可维护性和可重用性"&gt;进化方向演化为重视可维护性和可重用性&lt;/h3&gt;
&lt;p&gt;我们来一总结编程语言的进化历史吧&lt;/p&gt;

&lt;p&gt;分为两条线：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;从机器语言-&amp;gt;汇编语言-&amp;gt;高级语言，编程语言发展的&lt;strong&gt;更符合人类语言的方式&lt;/strong&gt;来描述计算机执行操作，代表作：FORTRAN，COBOL。但遗憾的是仍无法满足需求&lt;/li&gt;
&lt;li&gt;接下来的结构化语言就需要改变方向，即&lt;strong&gt;提高可维护性&lt;/strong&gt;，无 GOTO 编程和提高子程序独立性都是为了程序便于理解和修改。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;程序的寿命也越来越长，老的代码要被一直维护，那能看的懂，并能进行修改，就很重要了。&lt;/p&gt;

&lt;hr&gt;
&lt;h3 id="没有解决的问题"&gt;没有解决的问题&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;全局变量问题&lt;/li&gt;
&lt;li&gt;可重用性差问题&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;全局变量问题是指：虽然引入了局部变量和按值传递，但当我们要将计算结构保存下来时，局部变量在函数结束时消失，所以要将结果保存到函数外部，也就又变成全局变量了。&lt;/p&gt;

&lt;p&gt;可重用性差是指：虽然使用函数库来重用，但随着需求越来越大，仅靠函数是不够的。&lt;/p&gt;
&lt;h2 id="面向对象编程"&gt;面向对象编程&lt;/h2&gt;&lt;h3 id="OOP的三种结构"&gt;OOP 的三种结构&lt;/h3&gt;
&lt;p&gt;这三种结构为：类 (封装)，多态，继承。也被称为 OOP 的三要素。&lt;/p&gt;

&lt;p&gt;而在前面的结构化语言中我们没有解决的问题，就可以被这三种结构解决&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OOP 具有不使用全局变量的结构&lt;/li&gt;
&lt;li&gt;OOP 具有除公用子程序之外的可重用结构&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;需要注意虽然现在大部分语言都属于 OOP，但根据编程语言的设计不同，语法差异也会不同，比如：Java 和 Ruby，Java 中没有全局变量的概念，而 Ruby 中仍有保留，而且两个语言的差异也很大。&lt;/p&gt;
&lt;h3 id="类"&gt;类&lt;/h3&gt;
&lt;p&gt;类的功能总结为：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;汇总&lt;/strong&gt;子程序和变量&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;隐藏&lt;/strong&gt;类内部的子程序和变量&lt;/li&gt;
&lt;li&gt;一个类可以&lt;strong&gt;创建多个实例&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;汇总&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我们举个文件访问的例子，代码：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;readChar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;openFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;pathName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;省略&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;closeFile&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;&lt;span class="err"&gt;省略&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;&lt;span class="err"&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;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TextFileReader&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;readChar&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;pathName&lt;/span&gt;&lt;span class="o"&gt;)&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="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;close&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="kt"&gt;char&lt;/span&gt; &lt;span class="nf"&gt;read&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="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到这里将子程序和全局变量都汇总到一个类里了，好像没什么区别，但是汇总本身就是有意义的，就好比将散乱的东西分门别类，这样当再去寻找时，就有个范围。而且通过类的汇总，我们可以将子函数的名称变得更简洁，并且避免了命名冲突的问题。当别的类也有：open, close, read 方法时，双方并不会影响。
&lt;img src="https://l.ruby-china.com/photo/qinsicheng/b06f3716-5fdb-4556-8d5e-aa6da94bd408.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;隐藏&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;比如上面的代码，我们只希望类内部的方法能访问类中实例变量，外部不让动，这样就能解决全局变量的问题了&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TextFileReader&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{};&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{};&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{};&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;someMethod&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{};&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过访问修饰符来控制变量与子函数的访问权限，可以选择公开或者隐藏。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;创建多个实例&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;通过 C 语言也可以实现上面的汇总和隐藏功能，而创建多个实例则很难实现这种结构。比如上面的文件访问例子，如果只访问一个文件没有问题，那如果访问多个文件，并比较区别该怎么操作？ &lt;/p&gt;

&lt;p&gt;这里我们不需要再改成数组，来轮询访问，通过创建多个实例就可以解决这种问题。不过按照我们以前访问函数和变量的方式，当有多个实例时，该怎么区分哪个实例变量才是要处理的对象，用 OOP 的方式就得改为：&lt;code&gt;实例名称 . 方法名(参数)&lt;/code&gt;，代码如下：&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;TextFileReader&lt;/span&gt; &lt;span class="n"&gt;reader1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextFileReader&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;TextFileReader&lt;/span&gt; &lt;span class="n"&gt;reader2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextFileReader&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;reader1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"a.txt"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;reader2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"b.txt"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;ch1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;ch2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;reader1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;reader2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里我们创建了两个实例，并分别处理不同的文件，而每个方法都会访问当前实例内的变量。这样在类定义的一端，就不用担心多个实例同时运行的情况了。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;实例变量是限定访问范围的全局变量&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;实例变量的特性：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;能被隐藏，其他类的方法无法访问到&lt;/li&gt;
&lt;li&gt;实例创建后一直保存到内存中，直到不再需要&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这样就既解决了全局变量都能访问，又解决了局部变量信息无法妥善保存的问题&lt;/p&gt;
&lt;h3 id="多态"&gt;多态&lt;/h3&gt;
&lt;p&gt;多态是创建公用主程序的结构，与公用子程序相对应，公用子程序将被调用端的逻辑汇总为一个逻辑，而多态则相反，统一了调用端的逻辑&lt;/p&gt;

&lt;p&gt;说的简单点儿：我创建了一个公共函数 (公共子程序)，不管是谁调用这个函数，函数内不变。而多态是，不管被调用的东西内部怎么变，我都不用修改调用的代码。&lt;/p&gt;

&lt;p&gt;我们用代码来说明一下：&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TextReader&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{};&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{};&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{};&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们创建一个公共的接口，或者说是规范，当后面读取不同东西时，统一走这里的逻辑就行&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NetworkReader&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;TextReader&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 网络文件读取&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{};&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{};&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{};&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TextFileReader&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;TextReader&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 网络文件读取&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{};&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{};&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{};&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里我们定义了网络文件读取和文件文件读取，当用户最初是使用网络文件读取时：&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;TextReader&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NetworkReader&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当想再换成文本文件读取时：&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;TextReader&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextFileReader&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// 只有这里修改了，其他地方完全一样！！！&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样当需求变化时，只需要简单的调整，就能实现另外一套完全不同的操作&lt;/p&gt;
&lt;h3 id="继承"&gt;继承&lt;/h3&gt;
&lt;p&gt;继承将类的共同部分汇总到其他类中的结构。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/69c15954-45f4-4d28-b040-10da5aa6ab8a.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;需要注意声明继承也就声明使用多态，因此在声明继承的子类中，为了统一方法调用，继承的方法参数和返回值类型必须与超类一致。&lt;/p&gt;

&lt;p&gt;这里可以引申出：&lt;strong&gt;里氏替换原则：派生类（子类）对象可以在程序中代替其基类（超类）对象。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;通过这一原则，才可以实现对扩展开放，多修改关闭，当子类对父类方法进行扩展时，保证程序的兼容性，降低代码出错的可能。&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;上面我们介绍了 OOP 的三大要素：类，多态，继承。不过例如 Java，Ruby，Python 等比较新的编程语言提供了更先进的结构，典型的有：包，异常，垃圾回收。设计这种结构就是为了促进重用，减少错误等。我们简单介绍一下&lt;/p&gt;
&lt;h3 id="包"&gt;包&lt;/h3&gt;
&lt;p&gt;说到了具有汇总功能的类结构，而包是进一步对类的汇总的结构。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/464242d8-3f35-4486-adce-3387d8b4d4ab.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;不同于类，包不能定义方法和变量，但通过这种结构，也就是类似文件系统中的目录，我们可以更加轻松的组织管理。我们假设如果没有包，所有的类文件都放到根目录，一方面是命名冲突，一方面是要在大量的类中，找到具体那个，也很麻烦。&lt;/p&gt;
&lt;h3 id="异常"&gt;异常&lt;/h3&gt;
&lt;p&gt;在我们代码运行时，总会有各种问题出现，数组越界，空指针等等。&lt;/p&gt;

&lt;p&gt;在没有异常时，我们可以通过返回码来表示系统运行的不同状态，比如返回 200 表示正常，返回 -200 表示数组越界，返回 -300 表示空指针，但这有一个问题，调用端需要根据不同的状态处理不同的情况，大量的 if-else，异常处理混杂在代码中间。而且当调用端无法处理某种情况时，就必须将异常值再返回到上层，同样的异常处理在上层再写一遍。&lt;/p&gt;

&lt;p&gt;问题：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;必须在应用程序中执行所有的错误码判断&lt;/li&gt;
&lt;li&gt;错误码的判断逻辑在多个子程序中连锁&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 对应处理&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 对应处理&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 对应处理&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 对应处理&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 正常逻辑&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&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;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// 正常逻辑&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NullError&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 执行&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OutOfIndexError&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 执行&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有时在发生错误时不执行特殊处理，而是将错误传递给上位方法，这种情况只需要在方法中声明异常，而不必编写错误处理，就解决了第二个问题。&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;NullError&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="nc"&gt;OutOfIndexError&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 正常逻辑&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;需要注意 Java 这种静态类型语言会发生编译错误提醒，而 Ruby 这种动态语言则是在运行时错误提醒。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="垃圾回收"&gt;垃圾回收&lt;/h3&gt;
&lt;p&gt;在说到创建多个实例时，只谈到了如何创建，但没有说如何删除，每当创建一个实例，就会为之分配内存，如果不能及时清理就会导致 OOM(内存溢出)&lt;/p&gt;

&lt;p&gt;在 C 和 C++ 等之前的语言，需要显示的删除不需要的内存区域，但是就怕误删操作，或者忘了删了，需要一种机制能自动化管理，&lt;/p&gt;

&lt;p&gt;在Java，C#等很多OOP中采用由系统自动进行删除实例的处理结构，也就是垃圾回收。这里有很多策略，比如引用计数法，标记清除法，标记整理法，推荐去看：深入理解Java虚拟机&lt;/p&gt;
&lt;h3 id="OOP与设计模式"&gt;OOP 与设计模式&lt;/h3&gt;
&lt;p&gt;从历史上来说，先利用 OOP 创建可重用架构群，然后提取可重用构建群中的设计思想，形成设计模式，最后再利用设计模式，进一步创建可重用架构群。&lt;/p&gt;
&lt;h2 id="设计模式原则"&gt;设计模式原则&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/287696f0-9df8-43b2-86e8-9ea663f66235.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;原则是一种抽象的概念，最初接触时很难深入理解，所以往往大家会着重于学习具体的设计模式，看代码是如何组织的。但当多观察几种设计模式后，就会发现一些通用点和策略&lt;/p&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;p&gt;等等，而这些就是设计模式原则，我们这里侧重于从 OOP 的发展历程角度来看待这些设计模式原则，看思想上是否是一致的。&lt;/p&gt;
&lt;h3 id="单一职责原则"&gt;单一职责原则&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：就一个类而言，应该仅有一个引起它变化的原因&lt;/p&gt;

&lt;p&gt;我们之前提到 OOP 结构解决的一个问题就是可重用性结构。&lt;/p&gt;

&lt;p&gt;而 OOP 提供的类结构在设计时，就是为了将不同职责的变量和函数进行划分，这样在进行维护时，只需要找到特定的类即可。如果一个类承担的职责过多，这些职责就会耦合，当职责发证变化时，其他职责也会发生影响，这就导致的设计的脆弱性。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/5b4782cf-c55a-4f4d-90a1-96e9f9bfb3a5.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;就好像赤壁之战中，铁索练船，一个船着火了，其他的船也被影响。&lt;/p&gt;
&lt;h3 id="开放-封闭原则"&gt;开放 - 封闭原则&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定义：&lt;/strong&gt; 软件实体 (类，模块，函数等) 应该可以扩展，但是不可修改。&lt;/p&gt;

&lt;p&gt;有一句经典名言：拥抱变化。随着业务需求的变化，我们不得不对代码进行调整，但如果直接上手修改原有代码，在没有完整测试的情况下，很容易引入 Bug。&lt;/p&gt;

&lt;p&gt;而 OOP 提供的多态，继承结构就是为了解决这一问题，通过实现接口类或继承重写某些方法。这样在不修改原有功能的前提下，扩展新的功能。而通过多态，让客户端察觉不到发生的变化。&lt;/p&gt;
&lt;h3 id="依赖倒转原则"&gt;依赖倒转原则&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定义：&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;高层模块不应该依赖底层模块，二者都应该依赖抽象&lt;/li&gt;
&lt;li&gt;抽象不应该依赖细节，细节应该依赖抽象&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;我们观察一下 SpringBoot 在写 MVC 结构时，Service 层是有一个接口层和实现层，Mapper 层是有一个接口层和实现层。为什么需要这层接口呢？&lt;/p&gt;

&lt;p&gt;这里一方面的原因是 AOP 所需的动态代理，而另一方面是 SpringBoot 这样设计是基于依赖反转原则的，当 Controller 层调用 Service 层时，指向的是 Service 接口，但具体的实现是通过 ServiceImpl 来做。这样双方都不需要知道具体的实现，Service 层调用 Mapper 层也是同理的。假如我们原先是使用 Mysql 数据库，现在使用 Oracle 数据库，那我们只需要新增一个 MapperImpleByOracle 实现类即可。通过 Spring 容器的依赖注入，将代码修改范围降到最低。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/7b3dde53-7452-4ffb-9bfe-80bf93bcaf42.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="里氏替换原则"&gt;里氏替换原则&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定义：&lt;/strong&gt;子类型必须能替换他们的父类型&lt;/p&gt;

&lt;p&gt;只有当子类可以替换掉父类，软件单位的功能不受到影响时，父类才能真正被复用，而子类也能在父类的基础上增加新的行为，而这一点才使得开放 - 封闭原则称为可能。&lt;/p&gt;

&lt;p&gt;而里氏替换原则是在 OOP 多态的基础上提出的，是对多态的一种规范。&lt;/p&gt;
&lt;h3 id="迪米特法则(最少知识原则)"&gt;迪米特法则 (最少知识原则)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定义：&lt;/strong&gt;如果两个类不必彼此直接通信，那么这两个类就不应该发生直接的相互作用，如果其中一个类需要调用另一个类的某个一个方法的话，可以通过第三者转发调用。&lt;/p&gt;

&lt;p&gt;OOP 中提供的类结构一个特点就是隐藏 (封装)，每个类都应当尽量降低成员的访问权限。&lt;/p&gt;

&lt;p&gt;这里举一个例子：租客，房东，中介。当去掉中介这层当然没问题，但房东与租客直接沟通的话，成本大，且一旦一方失信或者违约，再去调整双方都要花费更多的时间，而通过引入中介，双方只需要和中介对接，如果出现问题，由中介进行调整，双方的损失也就降低了。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/2100e107-8400-4c95-aa9c-01f09810e791.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;通过面相对象的演化过程，我们能看到面向对象不是全新的概念，而是以之前技术为基础，对缺点进行补充，其中引入了新的结构来解决问题。&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;本文学习自：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://book.douban.com/subject/36085072/" rel="nofollow" target="_blank" title=""&gt;面向对象是怎样工作的（第 3 版）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://book.douban.com/subject/36116620/" rel="nofollow" target="_blank" title=""&gt;大话设计模式&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Sat, 23 Dec 2023 15:02:55 +0800</pubDate>
      <link>https://ruby-china.org/topics/43521</link>
      <guid>https://ruby-china.org/topics/43521</guid>
    </item>
    <item>
      <title>如何在 application.js 中使用 Rails 的配置变量信息</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;背景描述&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我在使用 Rails7 开发，其中通过&lt;code&gt;config/settings.yml&lt;/code&gt;来做一些配置，通过&lt;code&gt;gem: 'config'&lt;/code&gt;管理，现在希望在 js 文件中，也能获取使用这部分内容，但发现 .js.erb 好像不再支持，这里该怎么操作，还是说通过 js 来读取配置文件？&lt;/p&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Sat, 04 Nov 2023 18:23:42 +0800</pubDate>
      <link>https://ruby-china.org/topics/43453</link>
      <guid>https://ruby-china.org/topics/43453</guid>
    </item>
    <item>
      <title>菜鸡的单词卡片</title>
      <description>&lt;p&gt;地址：&lt;a href="https://github.com/GengCen-Qin/my_word_card" rel="nofollow" target="_blank" title=""&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;线上 demo(加载有点儿慢)：&lt;a href="http://121.37.186.74:4000/" rel="nofollow" target="_blank"&gt;http://121.37.186.74:4000/&lt;/a&gt;    Ai 生成部分禁用了&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;项目介绍&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;功能：英译词典，相比中文解释，英译解释会费脑一点儿，但你可以通过描述来学习一个单词，而不是一个中文符号来记忆。数据爬虫于&lt;a href="https://www.ldoceonline.com/" rel="nofollow" target="_blank" title=""&gt;朗文&lt;/a&gt;
但会更简洁，清晰一些。只提供必要的信息：拼写 (点击跳转到朗文，查看更详细信息)，音标 (点击可直接播放声音，没必要整个喇叭)，解释，例句。并通过 openAi，将你搜索过的单词，生成一个段落文章，来学习如何在实际中应用这些词汇，可以选择再次生成，或者退出。熟悉的单词也可通过 ✔️ 删除。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;项目待修正&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;使用 stream 来流式输出内容，现在这个太慢了&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;项目使用 Ruby On Rails 全栈做的，参考了我之前翻译的教程，不过模型简化的多，只有一个，更多的功夫用在样式实现和 Stimulus 使用，我感觉操作看着还是挺丝滑的&lt;img title=":sweat_smile:" alt="😅" src="https://twemoji.ruby-china.com/2/svg/1f605.svg" class="twemoji"&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;页面展示&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/b8cb8d69-df9c-4489-a612-b1cb84341829.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/qinsicheng/e711db12-8359-4b31-8a3d-8d5bee41f378.png!large" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Sun, 01 Oct 2023 19:00:27 +0800</pubDate>
      <link>https://ruby-china.org/topics/43363</link>
      <guid>https://ruby-china.org/topics/43363</guid>
    </item>
    <item>
      <title>无法监听到 turbo:render 事件</title>
      <description>&lt;p&gt;这是我的 Turbo Stream 操作：&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;turbo_stream.prepend&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;words&lt;/span&gt;&lt;span class="err"&gt;",&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;word&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我希望通过监听&lt;code&gt;turbo:render&lt;/code&gt;事件，在此从 DOM 来获取新加入的 &lt;a href="/word" class="user-mention" title="@word"&gt;&lt;i&gt;@&lt;/i&gt;word&lt;/a&gt; 渲染的节点，并对其样式进行修改。但是发现监听不到该事件，但是页面上已经成功显示了新加的数据&lt;/p&gt;

&lt;p&gt;我目前可以监听到的事件有：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;turbo:load&lt;/li&gt;
&lt;li&gt;turbo:before-cache&lt;/li&gt;
&lt;li&gt;before-stream-render&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;有什么办法能在 Turbo Stream 返回数据并修改 dom 后，进行某些操作？ &lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/9af23420-cc79-4775-8825-dd19d079990f.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/5cde8a3b-b470-40ef-b60d-702a3294ad62.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;我在 app/javascript/application.js 中写的：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;turbo:render&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;turbo:render&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;/code&gt;&lt;/pre&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Sun, 24 Sep 2023 14:07:49 +0800</pubDate>
      <link>https://ruby-china.org/topics/43352</link>
      <guid>https://ruby-china.org/topics/43352</guid>
    </item>
    <item>
      <title>如何进行测试驱动开发</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;前言&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;​最近在读《匠艺整洁之道 - 程序员的职业修养》这本书，作者鲍勃大叔开篇就用了大量的示例来讨论与演示为什么需要和如何操作测试驱动开发。我是之前写过测试，但从不知道测试如何驱动开发，大部分情况下也只是先写生产代码，写好后再测试，看看是否能调通，再修改代码中隐藏的问题。&lt;/p&gt;

&lt;p&gt;而测试驱动开发会扭转这种思维方式，是先写测试，再写对应的生产代码。这一开始让人会觉得很奇怪，感觉并且很麻烦，但当你尝试一下，就会发现这事儿挺有趣，且神奇。&lt;/p&gt;

&lt;p&gt;温馨提示：&lt;a href="https://learning.oreilly.com/videos/clean-craftsmanship-disciplines/9780137676385/9780137676385-CCC1_clean_craftsmanship_stack/" rel="nofollow" target="_blank" title=""&gt;Clean Craftsmanship: Disciplines, Standards, and Ethics (Companion Videos)&lt;/a&gt;，这个是该书示例操作的视频说明，&lt;strong&gt;非常非常非常有用！！！&lt;/strong&gt; 第一篇是讲述如何通过测试驱动开发来写一个栈结构。我严重怀疑大叔是不是一边写代码一边喝啤酒。整个过程伴随大叔魔性的笑声和宛如魔法般的操作，就好像他在不停调戏编译器。你会通过这个视频感受到如何从零开始，进行测试驱动开发。
​
下面将讲讲一些基础知识。&lt;/p&gt;
&lt;h2 id="TDD纪律"&gt;TDD 纪律&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;创建测试集，方便重构，并且其可信程度达到系统可部署的水平，也就是说测试集通过，系统就可以部署。

&lt;ol&gt;
&lt;li&gt;我不知道有多少人像我一样，由于老系统没有测试，导致每次上线都很害怕，担心修改引入新的问题。&lt;/li&gt;
&lt;/ol&gt;
&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;/p&gt;
&lt;h2 id="TDD三法则"&gt;TDD 三法则&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;第一法则&lt;/strong&gt;：在编写因为缺少生产代码而必然会失败的测试之前，绝不编写生产代码
&lt;strong&gt;第二法则&lt;/strong&gt;：只写刚好导致失败或者通不过编译的测试，编写生产代码来解决失败的问题
&lt;strong&gt;第三法则&lt;/strong&gt;:  只写刚好能解决当前测试失败问题的生产代码。测试通过后，立即写其他测试代码
整个过程看起来好像是：&lt;/p&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;再写生产代码，编译成功&lt;/li&gt;
&lt;li&gt;再写测试代码，编译能成功，但断言失败&lt;/li&gt;
&lt;li&gt;写一两行生产代码，满足断言&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如此循环，贯穿始终&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;遵守上述法则后，有以下好处&lt;/p&gt;
&lt;/blockquote&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;将产出一套测试集，让你有信息部署系统&lt;/li&gt;
&lt;li&gt;将创建较少耦合的代码&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="简单示例"&gt;简单示例&lt;/h2&gt;&lt;h3 id="栈"&gt;栈&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;这里建议先看我上面提供的链接，作者是用 Java 写的，我这里用 Ruby(核心思想是一致的)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'minitest/autorun'&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StackTest&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Minitest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Test&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_nothing&lt;/span&gt;

  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 这里我们一开始写一个什么都不做的测试，然后保证这个测试通过，这至少能说明你的环境没问题&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;规则 1：先编写测试，逼着自己写将要写的代码&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;​ 我们知道我们需要一个栈&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# stack_test.rb&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'minitest/autorun'&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StackTest&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Minitest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Test&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_canCreateStack&lt;/span&gt;
    &lt;span class="n"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 这里的测试一定会失败，因为现在根本没有&lt;code&gt;Stack&lt;/code&gt;这个类结构，所以我们写两行生产代码来通过测试&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# stack.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 这次我们在&lt;code&gt;stack_test.rb&lt;/code&gt;引入一下，就可以通过测试了。&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;规则 2：让测试失败，让测试通过，清理代码&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;​ 这时候我们发现我们还没有断言行为呢，比如当刚创建一个栈时，这个栈应该是空的。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# stack_test.rb&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'minitest/autorun'&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'stack'&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StackTest&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Minitest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Test&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_nothing&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;test_canCreateStack&lt;/span&gt;
    &lt;span class="n"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty?&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="c1"&gt;# =&amp;gt; 报错：NoMethodError: undefined method `isEmpty?'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 我们迅速补上生产代码，来解决这个问题&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# stack.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;isEmpty?&lt;/span&gt;
    &lt;span class="kp"&gt;false&lt;/span&gt; 
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 这样改后，方法可以找到了，但断言失败了，这里我们是故意的，为什么这么做呢？第一法则：测试必须失败，为啥呢？因为当测试应该失败时，我们就能看到它失败，我们测试了自己的测试。当我们将上面的方法返回 true 时，就能测试另一半了。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# stack.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;isEmpty?&lt;/span&gt;
    &lt;span class="kp"&gt;true&lt;/span&gt; 
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 这时候执行测试，测试通过，万事大吉，你一定会骂这不是作弊吗，但等等，至少我们只用几秒就能测出该通过时通过，该失败时失败。&lt;/p&gt;

&lt;p&gt;​ 下一个要测的是，栈需要能 push 吧&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# stack_test.rb&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'minitest/autorun'&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'stack'&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StackTest&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Minitest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Test&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_canPush&lt;/span&gt;
    &lt;span class="n"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&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;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 这里会报错，因为找不到方法，我们就跟着改改生产代码&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# stack.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;isEmpty?&lt;/span&gt;
    &lt;span class="kp"&gt;true&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;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ele&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 执行测试，测试通过，但这里我们没有断言啊，那既然 push 了，栈就不应为空吧&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'minitest/autorun'&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'stack'&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StackTest&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Minitest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Test&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_canPush&lt;/span&gt;
    &lt;span class="n"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&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;refute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty?&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 执行测试，断言失败，因为我们返回的一直是 true，那改改代码吧&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt;
  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:empty&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="vi"&gt;@empty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&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;isEmpty?&lt;/span&gt;
    &lt;span class="n"&gt;empty&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;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ele&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@empty&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;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 我们抽离出一个实例变量来保存是否为空，并在 push 后直接暴力设置为 false。这时候测试又能通过了
我们发现没写一个测试方法，都要创建一个栈，太麻烦了，于是我们重构一下，使用&lt;code&gt;setup方法&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'minitest/autorun'&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'stack'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StackTest&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Minitest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Test&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;
    &lt;span class="vi"&gt;@stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&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;test_nothing&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;test_canCreateStack&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@stack.isEmpty&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;test_canPush&lt;/span&gt;
    &lt;span class="vi"&gt;@stack.push&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;refute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@stack.isEmpty&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 测试依然能通过，不过 canPush 这个测试名不太好，我们改改（test_操作_结果）&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;test_afterOnePush_isEmpty&lt;/span&gt;
    &lt;span class="vi"&gt;@stack.push&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;refute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@stack.isEmpty&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;​ 当然测试还是能通过&lt;/p&gt;

&lt;p&gt;​ 这时候我们测试，栈 push 一次，也能 pop 一次吧，并且这时候栈应该为空&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;test_afterOnePushAndOnePop_isEmpty&lt;/span&gt;
    &lt;span class="vi"&gt;@stack.push&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="vi"&gt;@stack.pop&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@stack.isEmpty&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;​ 测试失败，因为没有这个方法&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pop&lt;/span&gt;
    &lt;span class="vi"&gt;@empty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 现在测试又通过了，现在我们测试两次 push 之后，栈的尺寸应该是 2&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;test_afterTwoPushs_sizeIsTwo&lt;/span&gt;
    &lt;span class="vi"&gt;@stack.push&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="vi"&gt;@stack.push&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;assert_equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="vi"&gt;@stack.size&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;​ 测试失败，因为没有这个方法，我们修改生产代码&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt;
  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="ss"&gt;:size&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="vi"&gt;@empty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="vi"&gt;@size&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;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;isEmpty?&lt;/span&gt;
    &lt;span class="n"&gt;empty&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;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ele&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&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="vi"&gt;@empty&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;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pop&lt;/span&gt;
    &lt;span class="vi"&gt;@empty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 我们定义 size 实例变量来保存状态，并在每次 push 时，size+1，这样断言通过。为了测试完整，我们再加一个测试&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;test_afterOnePush_isNotEmpty&lt;/span&gt;
    &lt;span class="vi"&gt;@stack.push&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;refute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@stack.isEmpty&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="vi"&gt;@stack.size&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;​ 测试通过。回到第一法则，如果对空栈执行 pop 操作，应该会有个异常吧&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;test_poppingEmptyStack_raisesUnderflow&lt;/span&gt;
    &lt;span class="n"&gt;assert_raises&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="vi"&gt;@stack.pop&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 测试失败，我们还没定义这个异常类&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Underflow&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 断言失败，我们再修改生产代码&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'underflow'&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pop&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;Underflow&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isEmpty?&lt;/span&gt;
    &lt;span class="vi"&gt;@empty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 测试通过，再测试：当栈 push 一个数据时，也应该 pop 出相同的数据吧&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;test_afterPushingX_willPopX&lt;/span&gt;
    &lt;span class="vi"&gt;@stack.push&lt;/span&gt;&lt;span class="p"&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;assert_equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="vi"&gt;@stack.pop&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;​ 测试失败，调整生产代码，我们加一个实例变量保存信息&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'underflow'&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt;
  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="ss"&gt;:size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="ss"&gt;:element&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="vi"&gt;@empty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="vi"&gt;@size&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;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;isEmpty?&lt;/span&gt;
    &lt;span class="n"&gt;empty&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;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ele&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&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="vi"&gt;@empty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
    &lt;span class="vi"&gt;@element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ele&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;pop&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;Underflow&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isEmpty?&lt;/span&gt;
    &lt;span class="vi"&gt;@empty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="vi"&gt;@element&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 测试通过，已经写了这么多代码，你看的都崩溃了，觉得直接写一个栈不就完了嘛&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;规则 3：别挖金子&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;​ 在最开始尝试 TDD 时，你会急于解决较难或有趣的问题，你可能先写 FILO 行为，这个就是挖金子，我们有意避免测试与栈行为有关的东西，专注与周边行为。例如栈是否为空或栈大小。
为什么避免挖金子，因为如果过早挖金子，可能忽略周边所有细节。现在我们根据第一法子，编写 FILO 测试&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;test_afterPushingXAndY_willPopYThenX&lt;/span&gt;
    &lt;span class="vi"&gt;@stack.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@stack.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="vi"&gt;@stack.pop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="vi"&gt;@stack.pop&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;​ 测试失败，我们发现抛出了&lt;code&gt;Underflow&lt;/code&gt;,我们修改一下&lt;code&gt;isEmpty?&lt;/code&gt;方法&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'underflow'&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;isEmpty?&lt;/span&gt;
    &lt;span class="n"&gt;size&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;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 测试失败，&lt;code&gt;test_afterOnePushAndOnePop_isEmpty&lt;/code&gt;这个测试中报错，好吧，我们在 pop 时没有 size-1&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'underflow'&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pop&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;Underflow&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isEmpty?&lt;/span&gt;
    &lt;span class="vi"&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="vi"&gt;@empty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="vi"&gt;@element&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 就剩下最后一个断言失败了，FILO 行为，我们开始修改&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'underflow'&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt;
  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="ss"&gt;:size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="ss"&gt;:elements&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="vi"&gt;@empty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="vi"&gt;@size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="vi"&gt;@elements&lt;/span&gt; &lt;span class="o"&gt;=&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;isEmpty?&lt;/span&gt;
    &lt;span class="n"&gt;size&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;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ele&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&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="vi"&gt;@empty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
    &lt;span class="vi"&gt;@elements&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;ele&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;pop&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;Underflow&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isEmpty?&lt;/span&gt;
    &lt;span class="n"&gt;ele&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@elements&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&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="vi"&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="vi"&gt;@empty&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;ele&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​ 测试全部通过，我们已经完成了一个栈的基础行为啦，你会发现一个栈在我们不断测试中渐渐成型，虽然现在并不完善，但我们已经能感受到测试驱动开发的整个过程了。&lt;/p&gt;

&lt;p&gt;​ 也许你可以花点儿时间，利用测试驱动开发自己写一个队列结构。 &lt;/p&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Sun, 10 Sep 2023 16:56:11 +0800</pubDate>
      <link>https://ruby-china.org/topics/43323</link>
      <guid>https://ruby-china.org/topics/43323</guid>
    </item>
    <item>
      <title>类变量与实例变量中遇到的问题</title>
      <description>&lt;p&gt;当我想实现一个 (暂不考虑并发性) 单例模式的类时：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Singleton&lt;/span&gt;
  &lt;span class="nb"&gt;private_class_method&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="ss"&gt;:dup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="ss"&gt;:clone&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;
    &lt;span class="vc"&gt;@@single&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时候我再用两个类去继承&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Singleton&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Database&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Singleton&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;        &lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Config:0x0000000102a9eef0&amp;gt;&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="no"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;      &lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Config:0x0000000102a9eef0&amp;gt;&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal?&lt;/span&gt; &lt;span class="no"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;  &lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当我执行两个继承类各自的&lt;code&gt;.instance&lt;/code&gt;方法时，返回了同一个对象，这里我的理解是：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;当执行&lt;code&gt;Config.instance&lt;/code&gt;时，找的&lt;code&gt;Singleton.instance&lt;/code&gt;并首次初始化类变量&lt;code&gt;@@single&lt;/code&gt;，这里的类变量归属&lt;code&gt;Singleton&lt;/code&gt;，而&lt;code&gt;@@single&lt;/code&gt;是&lt;code&gt;Config&lt;/code&gt;的一个实例。&lt;/li&gt;
&lt;li&gt;当执行&lt;code&gt;Database.instance&lt;/code&gt;时，也执行到了&lt;code&gt;Singleton.instance&lt;/code&gt;，由于&lt;code&gt;@@single&lt;/code&gt;已经被赋值了，所以返回。&lt;/li&gt;
&lt;/ol&gt;

&lt;hr&gt;

&lt;p&gt;但这里我希望的是&lt;code&gt;Config.instance&lt;/code&gt;和&lt;code&gt;Database.instance&lt;/code&gt;各自返回的是各自的实例，所以将&lt;code&gt;@@single&lt;/code&gt;改为&lt;code&gt;@single&lt;/code&gt;,最终结果确实没问题：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Singleton&lt;/span&gt;
  &lt;span class="nb"&gt;private_class_method&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="ss"&gt;:dup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="ss"&gt;:clone&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;
    &lt;span class="vi"&gt;@single&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;new&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;class&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Singleton&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Database&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Singleton&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;          &lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Config:0x0000000100c8d5b0&amp;gt;&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="no"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;        &lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Database:0x0000000100c8cae8&amp;gt;&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal?&lt;/span&gt; &lt;span class="no"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;  &lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是我搞不明白，为什么变为实例变量&lt;code&gt;@single&lt;/code&gt;时，结果发生了变化，&lt;code&gt;@single&lt;/code&gt;是&lt;code&gt;Singleton&lt;/code&gt;这个对象的实例变量，而这个&lt;code&gt;Singleton&lt;/code&gt;这个类对象应该是唯一的，但结果是不同的？想请教一下&lt;/p&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Tue, 29 Aug 2023 21:58:53 +0800</pubDate>
      <link>https://ruby-china.org/topics/43294</link>
      <guid>https://ruby-china.org/topics/43294</guid>
    </item>
  </channel>
</rss>
