Ruby项目中封装含两个辅助方法的功能的地道写法咨询
最佳实践写法
你列出的两种写法各有适用场景但都存在缺陷,推荐根据实际使用场景二选一:
场景1:仅作为独立工具模块调用,不需要被其他类include
用class << self包裹所有方法,私有规则可以正常生效,是大型Ruby项目中工具模块的最常用写法:
module MathUtils module NarcissisticNumbers class << self # 判断输入数字是否为自恋数 # @param number [Integer] 待校验的正整数 # @return [Boolean] 是自恋数返回true,否则返回false def narcissistic?(number) digits = digits(number) exp = digits.length checksum(digits, exp) == number end private def digits(number) number.to_s.chars.map(&:to_i) end def checksum(digits, exp) digits.map { |d| d ** exp }.sum end end end end # 调用方式 MathUtils::NarcissisticNumbers.narcissistic?(153) # => true
这种写法的优势:
- 命名空间完全隔离,不会污染全局作用域
- 辅助方法
digits、checksum是真正的私有方法,外部无法直接调用,完全符合你公开接口的要求 - 结构清晰,所有同业务逻辑的方法都放在同一个模块下,方便维护
场景2:后续需要把方法混入(include)到其他类中使用
用module_function声明公开方法,这种写法既支持直接调用,也支持混入后作为实例方法使用:
module MathUtils module NarcissisticNumbers def narcissistic?(number) digits = digits(number) exp = digits.length checksum(digits, exp) == number end # 声明为模块函数,支持直接调用和混入调用 module_function :narcissistic? private def digits(number) number.to_s.chars.map(&:to_i) end def checksum(digits, exp) digits.map { |d| d ** exp }.sum end end end # 直接调用 MathUtils::NarcissisticNumbers.narcissistic?(153) # => true # 混入调用 class NumberValidator include MathUtils::NarcissisticNumbers def validate(number) narcissistic?(number) end end
你写的两种写法的问题
- 第一种
def self.xxx的写法:Ruby默认的private关键字只对实例方法生效,对模块单例方法无效,你后面写的def self.digits仍然是公开方法,外部可以直接调用,达不到隐藏内部实现的目的。 - 第二种不加
module_function的写法:narcissistic?是模块实例方法,不能直接通过模块名调用,只能include到其他类之后使用,不符合独立工具方法的使用需求。
额外优化建议
- 用
sum代替reduce(:+):Ruby 2.4及以上版本原生支持Enumerable#sum,性能和可读性都更好 - 增加参数校验:生产环境可以给公开方法加参数校验,比如判断输入是否为正整数,避免非法输入抛出无意义的错误
- 补全注释:大型项目里给公开API加参数、返回值、用法注释,降低团队协作的沟通成本
- 命名空间拆分:不要直接写
module MathUtils::NarcissisticNumbers,分两层定义模块,避免顶层MathUtils未定义的报错
内容的提问来源于stack exchange,提问作者Peter Slotko
相关产品推荐
相关产品推荐

