2013-05-19 4 views
0

Я опытный программист Obj-C/Java, и я попал в Ruby. Очевидно, тот факт, что он настолько динамичен, замечателен (повторное открытие классов - это классно!), Но есть одна вещь, которая меня беспокоит/беспокоит, когда я начинаю писать код Ruby.Явное определение класса iVar в Ruby (ala Obj-C)

Мне было бы интересно узнать, что вы, например, Ruby-ers, явно задаете тип iVars в своих классах. Из того, что я вижу, вы можете установить iVar на любой объект, и рубин не будет жаловаться. Но если вы ожидаете, что определенный iVar будет иметь определенный тип, это может вызвать проблемы в строке. Например:

class MyString 
    def initialize(myString) 
    @myString = myString 
    end 

    def uppercase_my_string 
    @myString.upcase 
    end 
end 

st1 = MyString.new("a string!") 
st1.uppercase_my_string 

st2 = MyString.new(["a string"]) 
st2.uppercase_my_string 

Этот код будет бросать NoMethodError, так как, конечно, массив не имеет какого-либо метода upcase. К сожалению, он не говорит нам, где мы действительно ошибались (строка выше, при создании str2), поэтому нам не очень помогают при отладке (если str2 создается несколько модулей в некотором неприметном месте) Один естественный шаг может чтобы добавить некоторые проверки initialize следующим образом:

class MyString 
    def initialize(myString) 
    raise TypeError, "myString iVar is not a string!" unless myString.class == String 
    @myString = myString 
    end 
end 
...same code as before 

Отлично, теперь, если мы случайно создать новый MyString мы рассказали, как глупо мы были (и что еще более важно, что мы сказали, когда мы делаем это и не тогда, когда мы Моя следующая проблема заключается в том, когда мы решили использовать attr_accessors на iVar.

class MyString 
    attr_accessor :my_string 
    def initialize(my_string) 
    raise TypeError, "myString iVar is not a string!" unless my_string.class == String 
    @my_string = my_string 
    end 

    def uppercase_my_string 
    @my_string.upcase 
    end 
end 

st1 = MyString.new("a string!") 
st1.uppercase_my_string 

st2 = MyString.new("good, it's a string") 
st2.my_string = ["an array!"] 
st2.uppercase_my_string 

Используя установленный сеттер, мы можем быть очень проницательным и обходим проверку ошибок в initialize. Еще раз у этой проблемы возникает исключение в uppercase_my_string, а не когда мы случайно установили @my_string в массив.

Наконец, мы могли бы создать аксессуаров вручную и добавить проверку ошибок, но это огромная боль ... есть более быстрый и простой способ сделать это. Или я просто застенчивый и недостаточно динамичный?

Спасибо!


стороны: Я знаю, что в Obj-C вы все еще имеете те же проблемы во время выполнения, но обычно вы заметили ошибку компилятора говоря, вы присваиваете объект типа array переменного типа string (или что-то подобное), поэтому, по крайней мере, мы предупреждаем, где это происходит

ответ

4

На практике подобные проблемы типа на самом деле довольно редки. Если вы хотите быть параноиком (с они действительно вы, чтобы получить вас), вы можете отправить свой вход через to_s, чтобы убедиться, что у вас всегда есть строка:

def initialize(my_string) 
    @my_string = my_string.to_s 
end 

Тогда вы можете сказать MyString.new(6) и все будет работайте, как ожидалось. Конечно, вы можете сказать MyString.new([6, 11]) и получить ерунду.

Если вы действительно хотите, чтобы my_string был строкой, вы не будете явно проверять его class.Это может вызвать проблемы, если кто-то подклассы строки, так что вы хотели бы, по крайней мере использовать is_a?:

def initialize(myString) 
    raise TypeError, ... unless myString.is_a? String 
    @myString = myString 
end 

Там также метод to_str вы можете проверить:

def initialize(myString) 
    raise TypeError, ... unless myString.respond_to? :to_str 
    @myString = myString.to_str 
end 

Реализация этого метода будет (в некоторых круги) указывают, что ваша вещь является String-подобной, чтобы быть String. Я думаю, что вызов to_s был бы лучшей идеей, однако, это сделало бы вещи более похожими на людей в Ruby.

Насколько ваша проблема мутатор обеспокоен:

st2.my_string = ["an array!"] 

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

class MyString 
    attr_reader :my_string 
    def initialize(my_string) 
    self.my_string = my_string 
    end 

    def my_string=(s) 
    @my_string = s.to_s 
    end 

    def uppercase_my_string 
    @my_string.upcase 
    end 
end 

В принципе, вы не беспокойтесь о типах, что многое в Ruby, вы беспокоиться о том, что методы что-то отвечает. И, если вы хотите, чтобы что-то специально было String, вы делаете его строкой, вызывая универсальный метод to_s (что и будет делать строковая интерполяция, "#{x}").

+0

«Вы не беспокоитесь о типах, которые много в Ruby, вы беспокоитесь о том, какие методы что-то реагируют» - какие методы что-то реагирует на *, - это его тип. –

+0

Спасибо, я так и думал, но просто подумал, есть ли способ - похоже, нет, но, скорее всего, потому, что это не рубиноподобно. Хорошая точка в методе 'is_?', Я буду использовать это в будущем для проверки классов/членов классов. – Patrick

+0

В Ruby есть много DWIM (и, как и большинство вещей, это хорошо и плохо :) Вообще-то вы использовали бы более узкий 'response_to?' В пользу 'is_a?'; конечно, в реальном мире вы должны быть практичными и делать то, что работает. –

2

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

В программе, которая использует MyString класс, должен быть contract, такие, как соглашение о том, что объект отдается конструктор реализует метод upcase.

Испытания и документация также включены, чтобы запретить эти виды недостатков в программах. Но я думаю, что это может быть отдельная дискуссия.

+0

+1 для ссылки на определение контакта в Википедии Вы правы - модульные тесты и документация выглядят как способ (например, при создании lib, который будут использовать другие) – Patrick

+0

Я могу добавить это только при написании общего кода (например, драгоценный камень), некоторые проверки типов могут оказаться полезными для других, сталкивающихся с проблемами. Но всегда используйте response_to? потому что не имеет значения, какой класс предоставляется, он должен отвечать только конкретным требованиям. Если вы проверите класс, предоставленный класс должен спуститься с этого класса, и это может быть некорректно. –