3

我发现 Ruby 中的某些类,尤其是UserRuby on Rails 中的类,有变得非常庞大的趋势。这本身几乎是可管理的,但如此大类的测试文件也变得庞大。

为了尝试区分这种复杂性,我想从几个子类组成用户类。

class User < ApplicationRecord
  include User::AuthMixin
  include User::EmailsMixin
  include User::SettingsMixin
end

从技术上讲,这是可行的,但这意味着 Mixins 最终会引用 User 对象上的一堆属性,而这些属性实际上并不知道。以这个 EmailsMixin 为例,它划分了每个用户多封电子邮件的所有逻辑:

module User::EmailsMixin

  def email
    primary_email.try :value
  end

  def update_email value
    email = emails.build value: value
    self.primary_email = email
  end
  ...
end

这行得通,但它闻起来并不好...

虽然这一切都有效,但感觉就像是难闻的代码。为什么要User::EmailsMixin引用它不知道的primary_email?这将是 mixins 的本质——它们都引用父对象(在这种情况下是用户)上的属性。

不仅如此,现在我正在使用 Sorbet Typechecking 它没有通过类型检查。Sorbet 准确地指出了这个错误并指出Method primary_email does not exist on User::EmailsMixin。这当然是真的。

所以我有三个问题:

  1. 这是划分代码的合理范例吗?请注意,这不是可以移动到服务对象中的代码,它直接与用户对象上的属性相关(如上所述)
  2. 如果这是一个合理的范例,是否有办法在每个 mixin 中引用父对象?有没有办法说“这个模块实际上是用户对象的一部分,稍后将被包含/合并”
  3. 如果这不是将大量类分解为更小的类的合理方法,那么我应该如何考虑这样做(重新迭代,服务对象不会在这里剪切它,因为代码需要访问父属性)
4

1 回答 1

1

如果不查看您的所有代码并根据您的用例进行相应的重构,就无法给出具体的答案。

在我看来,应该非常谨慎地使用 mixins(或 Rails 问题)。它们没有做任何事情来减少代码的表面积(公共接口仍然是相同的“逻辑大小”),它们只是用来隐藏东西并使东西更难找到。这也是深度继承变得笨拙的原因——一个类的“逻辑大小”是它本身加上它的所有超类。将代码移动到另一个文件并不能改变这一点。

在不确切知道您的代码在做什么的情况下,我将提出一个通常适用的盲目建议:将持久性与域逻辑分开。例如,我认为拥有#email#update_email留在其中很好User,因为它们处理持久性,并且让它们靠近模型很好 b/c 接口 /w 数据库是模型的责任。

但是,如果您正在寻找一种#validate_email方法的位置,那么模型和模型都不UserEmail放置它的好地方。

划分应用程序的方法总是不止一种,正如您所注意到的,纯粹按职责(电子邮件、身份验证、设置)划分意味着您没有数据访问/持久性划分,即“一切都需要访问到User属性”。

如果除以数据,您会发现这些模块不需要访问User. Auth可能只需要loginhashed_passSettings可能只需要一个哈希。

随着逻辑移出User,您需要将数据传递出去以处理完成后检索结果——这是您要维护的组件之间的数据接口。如果它开始变得复杂,也许数据接口本身应该是一个类/数据类型。如果您开始需要传递太多参数,请考虑组件之间的边界是否在正确的位置。

一个警告——许多 Rails 库没有做出这种区分,经常建议你include/extend你的模型访问它们的功能,所以如果你想使用它们,你必须找到一种方法来使用它们。

于 2020-07-27T18:37:08.543 回答