在 Ruby 中使用 2 个助手构建函数的惯用方法

Idiomatic ways to structure a function with 2 helpers in Ruby

提问人:Peter Slotko 提问时间:7/15/2021 更新时间:7/16/2021 访问量:79

问:

我为代码大战上的自恋数字 kata 编写了一个解决方案。

在编写了一个函数后,我提取了两个辅助函数,以便将行数保持在最多 5 行(Sandi Metz 的开发人员规则)。

这导致了 3 个功能:

def digits(number)
  number
    .to_s
    .chars
    .map(&:to_i)
end

def checksum(digits, exp)
  digits
    .map { |d| d**exp }
    .reduce(:+)
end

def narcissistic?(number)
  digits = digits(number)
  exp = digits.length
  checksum = checksum(digits, exp)
  checksum == number
end

现在,我想假装这段代码应该添加到一个更大的实际项目中。我的问题是,在 Ruby 中应该如何惯用地做到这一点。

一般来说,我有两个要求:

  1. 代码应该以某种方式命名(考虑到一个真实世界的项目)。
  2. 应该清楚的是,公共 API 函数 - 处于较高级别,而其他两个函数则处于较低抽象级别narcissistic?digitschecksum

到目前为止,我的推理是:这段代码实际上并不需要 OOP。但是在 Ruby 中,将某些内容放入命名空间的唯一方法是创建一个 或 .ClassModule

也许,一个会是更好的选择?不过,我不确定我是否应该选择:Module

module MathUtils::NarcissisticNumbers
  def self.narcissistic?(number)
    ...
  end

  private
  ...
end

module MathUtils::NarcissisticNumbers
  def narcissistic?(number)
    ...
  end

  private
  ...
end

你会如何把这段代码引入到一个 Ruby 项目中?如果您知道最佳实践解决方案,请告诉我!:)

任何其他指示也将不胜感激。

Ruby 模块 命名空间 结构

评论


答:

0赞 Lam Phan 7/16/2021 #1

在我看来,这取决于您的方法的目的,请考虑 2 个方法名称:narcissistic

  1. narcissistic?(number):这让我觉得有一个外部班级负责检查输入的数字是否自恋。

  2. narcissistic?:这让我想到了班级本身能够检查它是否自恋。

因此,在情况 1 中,假设您有一个包含该模块的类,如果该模块不支持类方法,则只有类代码检查的实例,那么方法名称应该属于上述情况 2。CodeMathUtils::NarcissisticNumberscan_donarcissistic

另一方面,如果模块支持类方法,那么方法名称应该属于情况 1,但是,假设你有一个类需要检查它的值,如果你使用它会让其他人感到困惑(至少他们需要知道是什么),但如果你使用它是完全有意义的,其他人会立即明白这是一种检查数字方法。MoneynarcissisticCode.narcissistic?(money.value)CodeMathUtils::NarcissisticNumbers.narcissistic?(money.value)

我建议你让是一个并创建另一个模块MathUtils::NarcissisticNumbersmodule_functionnarcissistic?

module MathUtils::NarcissisticNumbers
  module_function
  def is_narcissistic?(number)
  end
end

module Narcissistic
 def narcissistic?
   MathUtils::NarcissisticNumbers.is_narcissistic?(self.value)
 end
end

class Code
 include Narcissistic
end

class Money
 include Narcissistic
end

code = Code.new(...)
code.narcissistic?

# for those classes that only check narcissistic? internally
# then you can include MathUtils::NarcissisticNumbers
# since is_narcissistic?(number) become a private method

class FormatNumber
 include MathUtils::NarcissisticNumbers
 def format(number)
   if is_narcissistic?(number)
    # ...
   else
    # ...
   end
 end
end

# you can use MathUtils::NarcissisticNumbers wherever you want (as helper)
# on other classes that not include Narcissistic, including views , ...
<% if MathUtils::NarcissisticNumbers.is_narcissistic?(input) %>
0赞 Christian Bruckmayer 7/16/2021 #2

我同意林已经写过的大多数事情。但是,我会先提取一个您在模块中使用的类。类使处理数据变得更加容易(并且遵循建议,您的方法应最大为 5LOC)。

class MathUtils::NarcissisticNumber
  def initialize(number)
    @number = number
  end

  def valid?
    checksum == number
  end

  private
  
  attr_reader :number

  def checksum
    digits.map { |d| d**exponent }.reduce(:+)
  end

  def digits
    @digits ||= number.to_s.chars.map(&:to_i)
  end

  def exponent
    @exponent ||= digits.length
  end
end

通过使用一个类,我们能够删除所有方法参数和临时变量。现在,我们可以在 Liam 建议的帮助程序模块中使用此类。

module MathUtils::NarcissisticNumbers
  def narcistic?(number)
    NarcissisticNumber.new(number).valid?
  end
end