Рефакторинг с динамически типизированным языком - PullRequest
9 голосов
/ 12 августа 2010

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

Ruby, PHP и Javascript являются довольно популярными языками в наши дни, и у них есть много людей, которые защищают их и утверждают, что динамическая типизация не сдерживает разработчика. Я новичок в этих языках и хотел бы начать использовать их для более крупных проектов, но вот базовый сценарий рефакторинга, который все время возникает на работе (работа == C #), и мне интересно, какой подход будет в Ruby - я выбрал Ruby, потому что это ОО.

Хорошо, я использую Ruby и создаю объект Customer. У него есть методы для загрузки / сохранения / удаления из базы данных. Это хорошо, и люди используют это. Я добавляю больше методов для других вещей, и люди используют это больше. Я добавляю метод для расчета истории заказов на основе некоторых параметров. К настоящему времени этот класс используется во всей системе. Затем однажды я решаю изменить параметры метода GetOrderHistory. Итак, я:

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

Но что теперь? У меня есть десятки / сотни / кто знает, сколько других мест в системе нужно изменить. Как бы я поступил с таким динамическим ОО-языком, как Ruby или Javascript?

От макушки головы, не зная Руби очень хорошо, я могу придумать два глупых ответа:

  1. 100% код покрытия. Я тестирую все приложение, и каждый раз, когда оно ломается, я вижу, что это тот метод, и исправляю его
  2. Найти и заменить. Я использую текстовый поиск, чтобы найти этот метод. Но у меня могут быть другие объекты с такими же именами методов.

Так есть ли хороший ответ на это? Кажется, IDE будет трудно. Если бы у меня был код, такой как

c = Customer.new

он мог бы понять это, но что, если это

c= SomeFunctionThatProbablyReturnsACustomerButMightReturnOtherThings()

Так какой подход вы бы выбрали в этом случае для специалистов по Ruby?

Ответы [ 2 ]

3 голосов
/ 12 августа 2010

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

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

Если c не Клиент, то по крайней мере я бы ожидал, что будет действовать как один. В средах разработки можно было бы искать утку, которая более гибка, чем проверка экземпляра определенного класса.

Некоторые IDE (по крайней мере, Rubymine) также обращают внимание на соглашения. Например, в приложениях Rails Rubymine переходит в файл схемы и добавляет свойства модели в базу данных в качестве методов. Он также распознает ассоциации (has_many, own_to и т. Д.) И динамически добавляет соответствующие методы, которые Rails генерирует под капотом.

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

1 голос
/ 12 августа 2010

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

Пример:

def my_method(options = {})
  if options[:name]
    ...
  end
end

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

Другие варианты могут включать переопределение метода в подклассе для выполнения желаемой функциональности.

Или как насчет ...

def get_order_history(required_param, options = [])

  @required_param = required_param

  if options[:do_something_else]
    result = other_method(options[:do_something_else])
  else
    result = ...
  end

  result      

end

def other_method(something_else)
  ...
end
...