Ruby YARD: документирование реализации абстрактных методов

У меня есть типичный объектно-ориентированный шаблон: один базовый абстрактный класс (который определяет абстрактные методы) и несколько классов, которые реализуют эти абстрактные методы специфичным для класса способом.

Я привык писать документацию только один раз в абстрактных методах, а потом она автоматически распространяется на несколько конкретных классов (по крайней мере, в 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
  • Говорят, что метод бросает AbstractMethodException в AbstractClass
  • Метод вообще не имеет описания и общий тип возврата Object в ConcreteClass, нет ни единого уведомления о том, что абстрактный метод существует в базовом классе.

Что я ожидаю получить:

  • Описание метода и тип возвращаемого значения наследуются (т.е. копируются) в ConcreteClass из информации в AbstractClass
  • В идеале этот метод указывается в разделе «унаследовано» или «реализовано» описания ConcreteClass с некоторой ссылкой от ConcreteClass#do_something до AbstractMethod#do_something.

Возможно ли это сделать?


person GreyCat    schedule 15.10.2013    source источник


Ответы (2)


Я думаю, что проблема сводится к тому, что вы пытаетесь сделать. Похоже, вы пытаетесь реализовать интерфейс на Ruby, что имеет смысл, если вы работаете с Java или .NET, но на самом деле это не то, как обычно работают разработчики Ruby.

Вот некоторая информация о типичных представлениях об интерфейсах в Ruby: Что такое интерфейс Java эквивалент в Ruby?

Тем не менее, я понимаю, что вы пытаетесь сделать. Если вы не хотите, чтобы ваш AbstractClass был реализован напрямую, но вы хотите определить методы, которые можно использовать в классе, который ведет себя так, как это предусмотрено в AbstractClass (как в Design by Contract), то вы, вероятно, захотите использовать модуль. Модули очень хорошо помогают сохранить ваш код DRY, но они не совсем решают вашу проблему. проблема, связанная с документированием переопределенных методов. Итак, на данный момент, я думаю, вы можете пересмотреть свой подход к документации или, по крайней мере, подойти к ней более рубиново.

Наследование в 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.

person jgnagy    schedule 23.10.2013
comment
Что, если я хочу предоставить документацию другим разработчикам, чтобы они могли легко узнать, что должны реализовать их классы (которые в других языках следуют интерфейсу)? - person Juliusz Gonera; 01.09.2016