При использовании файлов классов в Ruby вы размещаете операторы «требует» в верхней части файла или внутри определения класса?
Входят ли операторы Ruby «require» внутри или вне определения класса?
Ответы (7)
Технически это не имеет большого значения. require — это обычный вызов метода, и область, в которой он вызывается, не влияет на то, как он работает. Единственная разница в размещении заключается в том, что оно будет выполнено, когда будет оцениваться любой код, в который оно помещено.
С практической точки зрения, вы должны поместить их вверху, чтобы люди могли сразу увидеть зависимости файла. Это традиционное место для него.
require в начало файла, но технически это не повлияет на время выполнения. Теперь, я думаю, если бы вы использовали load, это была бы другая история.
- person Christian Juth; 18.03.2017
наверху.
require 'rubygems'
require 'fastercsv'
class MyClass
# Do stuff with FasterCSV
end
Я вижу возможную причину того, что require не помещается в начало файла: это дорого для загрузки и не всегда выполняется. Мне приходит в голову один случай, когда, например, код и его тесты находятся в одном файле, что мне нравится делать время от времени, в частности, для кода небольшой библиотеки. Затем я могу запустить файл из своего редактора, и тесты запустятся. В этом случае, когда файл required из другого места, я не хочу, чтобы test/unit загружался.
Что-то вроде этого:
def some_useful_library_function()
return 1
end
if __FILE__ == $0
require 'test/unit'
class TestUsefulThing < Test::Unit::TestCase
def test_it_returns_1
assert_equal 1, some_useful_library_function()
end
end
end
На самом деле не имеет значения, куда вы их поместите, но если вы поместите их внутри выражения class или module, то будет выглядеть так, как будто вы импортируете все, что находится в файле required, в пространство имен класса, то есть неверно: все попадает в глобальное пространство имен (или любое другое пространство имен, определенное в библиотеке).
Так что лучше поместите их вверху, чтобы избежать путаницы.
В верхней части файла большинство (но не все) языков обрабатывают импорт таким образом. Я нахожу, что так намного чище и проще обращаться с ними.
Я думаю, что на самом деле это имеет смысл только так... как будто вы в середине файла:
class Foo
def initialize(init_value)
@instance_var = init_value
# some 500 lines of code later....
end
end
class Bar
# oh look i need an import now!
require 'breakpoint'
как видите, отследить их будет очень сложно. Не говоря уже о том, что если вы захотите использовать импортированные функции ранее в своем коде, вам, вероятно, придется вернуться и включить их снова, потому что другой импорт будет специфичен для этого класса. Импорт одних и тех же файлов также создаст много накладных расходов во время выполнения.
Я чувствую, что оператор require принадлежит классу. Использование классов означает, что мы принимаем основной принцип ООП, а именно, объекты должны быть как можно более слабо связаны. Для меня это означает минимизацию внешних зависимостей. Если позже я перемещу класс в его собственный файл, я не хочу, чтобы он сломался, потому что я не отследил все необходимые операторы require, которые использует класс.
Дублирование операторов require в файле не создает проблем и упрощает рефакторинг, который неизбежно будет выполнен следующим программистом, унаследовавшим ваш код.
В большинстве ответов рекомендуется помещать операторы require в начало файла. Однако, когда требуются вложенные классы/модули, вы можете вместо этого рассмотреть возможность их размещения в классе (вверху). Взгляните на этот пример:
# foo.rb
require './foo/bar'
class Foo < Struct.new(:name, :description)
def bar
Bar.new(self)
end
end
# foo/bar.rb
class Foo
class Bar < Struct.new(:foo)
end
end
irb:
require './foo'
# ...
# TypeError (superclass mismatch for class Foo)
Это происходит потому, что Bar вложен в Foo, и его необходимо определить как таковой, вложив класс Bar внутрь Foo класс. Однако, поскольку Foo еще не определен, теперь он определяется вложенной структурой. После того, как Bar успешно потребовался, мы пытаемся определить класс Foo, который наследуется от другого класса. Это не удается из-за того, что Foo уже определен (вложенной структурой) и наследование может происходить только в исходном определении класса. Таким образом поднимая:
TypeError (несоответствие суперкласса для класса Foo)
Эту проблему можно решить, просто переместив оператор require внутрь класса Foo.