Ruby YARD:记录抽象方法实现

Gre*_*Cat 8 ruby documentation-generation abstract yard

我有一个典型的OO模式:一个基本抽象类(定义抽象方法)和几个以类特定方式实现这些抽象方法的类.

我习惯在抽象方法中只编写一次文档,然后它自动传播到几个具体的类(至少它在Javadoc,Scaladoc,Doxygen中以下面的方式工作),即我不需要重复相同的描述在所有具体课程中.

但是,我无法找到如何在YARD中进行此类传播.我试过,例如:

# Some description of abstract class.
# @abstract
class AbstractClass
  # Some method description.
  # @return [Symbol] some return description
  # @abstract
  def do_something
    raise AbstractMethodException.new
  end
end

class ConcreteClass < AbstractClass
  def do_something
    puts "Real implementation here"
    return :foo
  end
end
Run Code Online (Sandbox Code Playgroud)

我得到了什么:

  • 代码按预期工作 - 即throws AbstractMethodException在抽象类中调用,在具体类中完成
  • 在YARD中,AbstractClass明确定义为抽象,ConcreteClass是正常的
  • 方法描述和返回类型都很好 AbstractClass
  • 方法是说扔AbstractMethodExceptionAbstractClass
  • 方法根本没有描述和泛型Object返回类型ConcreteClass,没有一个通知表明基类中存在抽象方法.

我期望获得:

  • 方法的描述和返回类型ConcreteClass从info at 继承(即复制)到AbstractClass
  • 理想地,该方法在"继承"或"实行"的部分中指定ConcreteClass的描述中,与来自一些参考链接ConcreteClass#do_somethingAbstractMethod#do_something.

有可能这样做吗?

小智 4

我认为问题归结为你想要做什么。看起来您正尝试在 Ruby 中实现一个接口,如果您来自 Java 或 .NET,这很有意义,但实际上并不是 Ruby 开发人员的工作方式。

以下是有关 Ruby 中接口的典型思想的一些信息:Ruby 中的 java 接口等价物是什么?

也就是说,我明白你想做什么。如果您不希望直接实现 AbstractClass,但希望定义可在行为类似于 AbstractClass 规定的类中使用的方法(如按契约设计),那么您可能需要使用模块。模块可以很好地保持代码干燥,但它们并不能完全解决与记录重写方法相关的问题。所以,在这一点上,我认为您可以重新考虑如何处理文档,或者至少以更 Ruby 的方式处理它。

Ruby 中的继承实际上(一般来说根据我自己的经验)仅出于以下几个原因使用:

  • 可重用的代码和属性
  • 默认行为
  • 专业化

显然还有其他边缘情况,但老实说,这就是 Ruby 中继承的用途。这并不意味着您正在做的事情不起作用或违反了某些规则,它只是在 Ruby(或大多数动态类型语言)中并不常见。这种非典型行为可能就是 YARD(和其他 Ruby 文档生成器)不符合您期望的原因。也就是说,从代码的角度来看,创建一个仅定义子类中必须存在的方法的抽象类实际上并没有什么好处。未定义的方法无论如何都会导致抛出 NoMethodError 异常,并且您可以以编程方式检查对象是否会响应来自调用该方法的任何对象的方法调用(或与此相关的任何消息),使用(或其他#respond_to?(:some_method)反射工具来获取元数据)东西)。这一切都源于 Ruby 对Duck Typing的使用。

对于纯文档,为什么要记录您实际上并不使用的方法?您不应该真正关心通过调用方法发送或接收的对象的类,而只关心这些对象响应什么。因此,如果它在这里没有增加任何实际价值,那么首先不要创建你的 AbstractClass。如果它包含您实际上将直接调用而无需重写的方法,则创建一个模块,在那里记录它们,然后运行$ yardoc --embed-mixins以包含在混合模块中定义的方法(及其描述)。否则,记录您实际实现的方法,因为每个实现都应该不同(否则为什么要重新实现它)。

这是我想要做的与你正在做的类似的事情:

# An awesome Module chock-full of reusable code
module Stuff
  # A powerful method for doing things with stuff, mostly turning stuff into a Symbol
  def do_stuff(thing)
    if thing.kind_of?(String)
      return thing.to_sym
    else
      return thing.to_s.to_sym
    end
  end
end

# Some description of the class
class ConcreteClass
  include Stuff

  # real (and only implementation)
  def do_something
    puts "Real implementation here"
    return :foo
  end
end

an_instance = ConcreteClass.new
an_instance.do_somthing       # => :foo
# > Real implementation here
an_instance.do_stuff("bar")   # => :bar
Run Code Online (Sandbox Code Playgroud)

运行 YARD(使用 --embed-mixins)将包括从 Stuff 模块混合的方法(及其描述),现在您知道包括 Stuff 模块的任何对象都将具有您期望的方法。

您可能还想看看Ruby Contracts,因为它可能更接近您正在寻找的绝对强制方法接受并仅返回您想要的对象类型,但我不确定这将如何与 YARD 一起使用。