Является ли приемлемой практикой исправление базовых классов Ruby, таких как Fixnum?

17

Я все еще очень новичок в Ruby (читаю через Pickaxe и трачу большую часть своего времени в irb ), и теперь, когда я знаю, что можно поменять классы в Ruby, мне интересно, когда это приемлемо для этого , в частности, допустимо ли исправление базовых классов Ruby. Например: я ответил на другой вопрос Ruby здесь , где плакат хотел узнать, как вычесть часы из DateTime . Поскольку класс DateTime , похоже, не обеспечивает эту функциональность, я отправил ответ, который исправляет классы DateTime и Fixnum в качестве возможного решения. Это код, который я представил:

require 'date'

# A placeholder class for holding a set number of hours.
# Used so we can know when to change the behavior
# of DateTime#-() by recognizing when hours are explicitly passed in.

class Hours
   attr_reader :value

   def initialize(value)
      @value = value
   end
end

# Patch the #-() method to handle subtracting hours
# in addition to what it normally does

class DateTime

   alias old_subtract -

   def -(x) 
      case x
        when Hours; return DateTime.new(year, month, day, hour-x.value, min, sec)
        else;       return self.old_subtract(x)
      end
   end

end

# Add an #hours attribute to Fixnum that returns an Hours object. 
# This is for syntactic sugar, allowing you to write "someDate - 4.hours" for example

class Fixnum
   def hours
      Hours.new(self)
   end
end

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

five_hours_ago = DateTime.now - 5.hours

Что, кажется, довольно приятно смотреть и легко понять; однако я не уверен, что неплохо было бы возиться с функциональностью оператора DateTime - .

Единственными альтернативами, которые я могу придумать для этой ситуации, будет следующее:

1. Просто создайте новый объект DateTime «на лету», вычисляя новое значение часа в вызове new

new_date = DateTime.new(old_date.year, old_date.year, old_date.month, old_date.year.day, old_date.hour - hours_to_subtract, date.min, date.sec)


2. Напишите метод утилиты, который принимает DateTime и количество часов для вычитания из него

В принципе, просто обертка вокруг метода (1):

def subtract_hours(date, hours)
  return DateTime.new(date.year, date.month, date.day, date.hour - hours, date.min, date.sec)
end


3. Добавьте новый метод в DateTime вместо изменения существующего поведения #-()

Возможно, новый метод DateTime#less , который может работать вместе с патчем Fixnum#hours , чтобы разрешить синтаксис следующим образом:

date.less(5.hours)

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

Что-то не так с моим подходом, или я должен использовать одну из трех альтернатив (или другую, о которой я не думал), чтобы сделать это? Я чувствую, что исправление становится моим новым «молотом» для проблем в Ruby, поэтому я хотел бы получить некоторую информацию о том, что я делаю «Ruby way» или нет.

    
задан Mike Spross 27.10.2008 в 02:01
источник

4 ответа

18

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

Это действительно зависит от среды, в которой вы кодируете. Если это персональный проект - обязательно, патч для вашего сердца! Проблемы возникают, когда вы работаете над большой кодовой базой в течение длительного периода времени с большой группой программистов. В организации, в которой я работаю, у которой есть кодовые базы Ruby более 100KLOC и двадцати или около того разработчиков, мы начали очень сильно расправляться с переделкой обезьян, потому что мы видели, что это привело к поцарапанию головы, слишком часто. На данный момент мы почти полностью терпим его для временного исправления стороннего кода, который либо еще не включил, либо не включит наши исходные исправления.

    
ответ дан Avdi 27.10.2008 в 03:03
источник
6

Лично я считаю приемлемым добавлять методы к базовым классам, но неприемлемо модифицировать реализацию существующих методов.

    
ответ дан Dónal 27.10.2008 в 02:06
источник
  • Во-вторых, это мнение. –  Gordon Wilson 27.10.2008 в 02:33
  • Проблема заключается в том, что добавление одного человека является конфликтом другого человека. Я не могу сказать, сколько раз я видел (немного отличающуюся) реализацию String # / (). –  Avdi 27.10.2008 в 03:05
  • Если все думают, что могут добавлять методы к базовому классу, тогда вы получаете конфликты, поскольку люди делают это параллельно. Возможно, это не имеет значения, когда вы контролируете весь источник, но когда вы не контролируете библиотеки, все становится странно. –  brian d foy 27.10.2008 в 17:12
5

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

class MyDateTime < DateTime
  alias...
  def...

Но, очевидно, теперь вы получаете только новое поведение, если объявляете объекты своего нового класса.

    
ответ дан glenn mcdonald 27.10.2008 в 02:56
источник
  • +1 Это было достаточно очевидно для меня, чтобы не думать об этом ;-) –  Mike Spross 27.10.2008 в 02:58
  • Не забудьте делегацию / украшение: avdi.org/devblog/2008/04/17/... –  Avdi 27.10.2008 в 03:04
0

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

    
ответ дан dicroce 27.10.2008 в 02:14
источник
  • Я на самом деле не планировал подталкивать свои патчи к сообществу Ruby в целом. Я предполагал, что это будет локальное изменение. –  Mike Spross 27.10.2008 в 03:00