Допустимо ли исправлять базовые классы Ruby, такие как Fixnum?


Я все еще очень новичок в Ruby (читаю кирку и провожу большую часть своего времени в 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 S -.

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

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, поэтому я хотел бы получить некоторую обратную связь о том, делаю ли я вещи "рубиновым способом" или нет.
4 17

4 ответа:

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

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

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

Самый безопасный способ-это определить свой собственный класс, который наследует от встроенного, а затем добавить свой новый материал в новый класс.

class MyDateTime < DateTime
  alias...
  def...
Но очевидно, что теперь вы получите новое поведение только в том случае, если объявите объекты вашего нового класса.

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