Каков правильный способ инициализации константы в Ruby?

У меня есть простой класс, который определяет некоторые константы, например:

module Foo
  class Bar
    BAZ = "bof"
    ...

Все щенки и радуги, пока я не скажу Рейку, чтобы запустить все мои тесты Test::Unit. Когда это произойдет, я получаю предупреждения:

bar.rb:3: warning: already initialized constant BAZ

Моя привычка заключалась в том, чтобы избежать этих предупреждений, сделав условную инициализацию условной, например:

...
BAZ = "bof" unless const_defined? :BAZ
...

Это, похоже, решает проблему, но это немного утомительно, и я никогда не видел, чтобы кто-то еще инициализировал константы таким образом. Это заставляет меня думать, что я могу делать это неправильно. Есть ли лучший способ инициализировать константы, которые не будут генерировать предупреждения?

Обновить. Посредством немного более подробной информации о том, как я использую эти константы, скажем, я определил класс Token, который имеет константы для всех символов, которые являются частью синтаксис некоторого искусственного языка. У меня также есть класс Scanner, который читает поток символов, генерируя экземпляр Token для каждого из них.

module Foo
  class Token
    LPAREN = "("
    RPAREN = ")"
    ...
  end

  class Scanner
    def next_token
      case read_char()
        when Token::LPAREN: # Generate a new LPAREN token
        ...

То есть, проверяя, какой тип токена должен быть сгенерирован для данного символа, я хочу использовать константы, определенные в Token.

Обновить 2: Ответ Jörg показал, что проблема, вероятно, заключалась в том, как я конструировал пути в своих операторах require, а не в как я инициализировал или использовал константы. Я переписал свои операторы require, чтобы устранить любое создание ручного пути, например:

# File: $PROJECT_ROOT/lib/foo.rb; trying to load $PROJECT_ROOT/lib/foo/bar.rb
require File.expand_path(File.dirname(__FILE__)) + "foo/bar"

теперь записывается, чтобы полагаться на $LOAD_PATH:

# File: $PROJECT_ROOT/lib/foo.rb; trying to load $PROJECT_ROOT/lib/foo/bar.rb
require 'lib/foo/bar'

Я удалил условные проверки из своих постоянных инструкций инициализации, а rake теперь запускает модульные тесты, не бросая никаких предупреждений.

Ответ 1

Единственный способ, которым это может случиться, - это когда bar.rb равно require d несколько раз. Что не должно происходить, поскольку require не загружает файлы, которые уже были загружены один раз.

Однако он использует только путь, который вы передаете ему, чтобы определить, был ли файл уже загружен, по крайней мере, в Ruby 1.8:

require 'bar'   # => true, file was loaded

require 'bar'   # => false, file had already been loaded

require './bar' # => true, OOPS, I DID IT AGAIN
# bar.rb:3: warning: already initialized constant BAZ

Итак, вы правы: это вполне может быть признаком того, что с вашим управлением зависимостями что-то не так.

Типичные предупреждающие знаки

  • вручную создавать пути к файлам вместо того, чтобы просто полагаться на $LOAD_PATH

    require "File.expand_path('../lib/bar', File.dirname(__FILE__))"
    
  • манипулирование $LOAD_PATH везде, за исключением, может быть, главной точки входа в вашу библиотеку:

    path = File.expand_path(File.dirname(__FILE__))
    $LOAD_PATH << path unless $LOAD_PATH.include?(path)
    

В целом, моя философия заключается в том, что не моя работа как библиотека писателя, чтобы выяснить, как поместить мою библиотеку в $LOAD_PATH. Это работа системного администратора. Если sysadmin использует RubyGems для установки моей библиотеки, тогда RubyGems позаботится об этом, иначе любая другая система управления пакетами, которую он использует, должна позаботиться об этом, и если он использует setup.rb, то он будет установлен в site_ruby, который уже находится на $LOAD_PATH.

Ответ 2

Там хорошее обсуждение этого в комментариях к этот пост. Я думаю, что следующий выглядит так:

Это раздражало меня, когда я первый подошел к Руби. Это был остаток мой статический тип "промывания мозгов". Три вещи смягчают это реальная проблема. 1. Предупреждение. Вы может утверждать, что это должно быть исключение, но на самом деле, как бы это было разные в случае, когда некоторые другие программист молча поймал исключение? Который подводит меня к числу 2) Не работайте с идиотами. Только дебилы молча отключайте исключения и продолжайте и только дебилы изменяют постоянные значения используется таким образом. Что привело меня к 3) Никто из нас не придурок, так что вы с удовольствием узнайте, что это анекдотически со мной никогда не случалось. Это реально потому что константы выделяются в Ruby во всяком случае, что с их высотой, и как часто вы, вероятно, константа как L-значение в вашем коде?

Чтобы ответить на ваш вопрос, я бы ничего не сделал, чтобы избавиться от предупреждения. Просто возьмите предупреждение как раз это, предупреждение и двигайтесь дальше.