【问题标题】:Ruby YARD: documenting abstract methods implementationsRuby YARD:记录抽象方法的实现
【发布时间】:2013-10-15 10:39:13
【问题描述】:

我有一个典型的 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

我得到了什么:

  • 代码按预期工作 - 即在抽象类中调用 throws AbstractMethodException,在具体类中完成工作
  • 在 YARD 中,AbstractClass 明确定义为抽象,ConcreteClass 是正常的
  • 方法描述和返回类型在AbstractClass中很好
  • 据说方法会在AbstractClass中抛出AbstractMethodException
  • 方法完全没有描述,Object 的返回类型在ConcreteClass 中是通用的,根本没有注意到基类中存在抽象方法。

我期望得到什么:

  • 方法的描述和返回类型从AbstractClass 的信息继承(即复制)到ConcreteClass
  • 理想情况下,此方法在ConcreteClass 描述的“继承”或“实现”部分中指定,并带有从ConcreteClass#do_somethingAbstractMethod#do_something 的一些参考链接。

有可能吗?

【问题讨论】:

    标签: ruby abstract documentation-generation yard


    【解决方案1】:

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

    这里有一些关于 Ruby 中接口的典型想法的信息:What is java interface equivalent in Ruby?

    也就是说,我明白你想要做什么。如果您不想直接实现您的 AbstractClass,但您想定义可以在行为类似于 AbstractClass 规定的类中使用的方法(如Design by Contract),那么您可能想要使用模块。模块可以很好地保存您的代码DRY,但它们并不能完全解决与记录overridden methods 相关的问题。因此,在这一点上,我认为您可以重新考虑如何处理文档,或者至少以更 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
    

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

    您可能还想查看Ruby Contracts,因为它可能更接近您正在寻找的绝对强制方法接受并仅返回您想要的对象类型,但我不确定这是怎么做到的会和 YARD 一起玩。

    【讨论】:

    • 如果我想为其他开发人员提供文档,以便他们可以轻松了解他们的类(在其他语言中会遵循接口)必须实现什么?
    【解决方案2】:

    不理想,但您仍然可以使用(see ParentClass#method) 构造(记录在here)。不理想,因为您必须为每个覆盖方法手动键入。

    话虽如此,我不是 Yard 专家,但考虑到其特别可定制的架构,我会感到惊讶的是,仅通过在 Templates 部门 I 的某个地方扩展 Yard 就没有简单的方法来实现您需要的东西猜测。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-08
      • 2013-05-30
      • 2021-10-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多