Статьи

Рефакторинг для шаблона «Не спрашивай»

Шаблоны проектирования предоставляют нам рекомендации, которые помогут нам реализовать понятный и лаконичный поддерживаемый код. При реализации объектно-ориентированного дизайна типизация утилит и шаблон Tell Don’t Ask идут рука об руку для создания легко компоноваемого и обслуживаемого кода. Кроме того, функциональное программирование и общие методы интерфейса, такие как Monads, специально разработаны для Tell Tell Ask.

Здесь мы сосредоточимся на реализации легко читаемой, легко обновляемой базы кода с объектно-ориентированным дизайном, в частности, с использованием принципа Tell Not Ask.

Как дизайн кода может пойти не так

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

Например, я решил создать флеш-игру из командной строки для изучения переводов японских иероглифов. Так что все началось довольно просто — я сделал отображение японских символов доступным в файле YAML. Загрузил их в виде необработанного хэша, а затем продолжил ссылаться на это в логике игрового цикла. Поскольку это было достаточно просто, я продолжил абстрагировать игровую систему от MVC (Model / View / Controller) с шаблонами ERB для представления и добавил поддержку I18n для всех слов меню и отображения игры. В этот момент я очень гордился тем, чего достиг.

Но затем идея пришла мне в голову. Вводить японские иероглифы можно прямо с клавиатуры, так почему бы не добавить игру о наборе текста вместе с игрой перевода ? Казалось, что это легко сделать с текущей базой кода, просто добавив подробности о том, какие режимы игры доступны для файлов YAML. Поэтому я написал сопоставления символов для режимов перевода и ввода текста.

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

Когда я закончил и работал хорошо, я все еще гордился этим, но один урок показался мне полным нарушением паттерна «Не спрашивай». Каждый метод в нем был внешним методом запроса, запрашивающим текущее состояние или поведение. Эти методы относятся к этому классу и являются примером того, чего не следует делать:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def mapped_as
  case @mapping.first
  when :k
    @mapped_as.keys.first
  else
    @mapped_as.values.first
  end
end
 
def match? input, comp_bitz
  case comp_bitz.mapping
  when :k
    comp_bitz.value == comp_bitz.collection[input]
  when :v
    comp_bitz.value == input
  end
end
 
def choose_display key, value
  case @mapping.last
  when :k
    key
  when :v
    value
  end
end
 
def choose_expected key, value
  case @mapping.first
  when :k
    key
  when :v
    value
  end
end

Это плохой дизайн и приводит к коду, который трудно поддерживать. Это не печатание утки, и при этом это не Скажите Не спрашивают. Кодовая база зависела от этого для поведения во всем, и я знал, что мне придется реорганизовать это … так что год спустя я сделал именно это.

Рефакторинг сказать не спрашивай

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

Проблемы оказались не просто объектом запроса, от которого зависела система. Его основной проблемой было отсутствие надлежащей абстракции, что часто встречается при написании кода, который «любит необработанные типы». Импортируя флэш-карты и используя их непосредственно как хеш (необработанный тип) по всему приложению, все, что работало с ним, должно было иметь код, написанный специально для понимания хеш-вещей и значений, лежащих в основе структуры хеш-функции.

Когда вам не удается абстрагировать данные в репрезентативные объекты, вы зацикливаетесь на реализации методов для абстракции при каждом взаимодействии этого объекта, а не один раз с созданием объекта в качестве абстракции. Так что мне пришлось сделать рефакторинг с Индианой-Джонсом — мне пришлось перестроить его вместе с разработкой, управляемой тестами, а затем поменять код на месте, надеясь не вызывать ловушки в пещере.

Первым шагом для реализации и тестирования было создание абстракции для отдельных флеш-карт. В данном случае флэш-карта — это карта с японским символом на лицевой стороне и переводом на обороте. На карте не должно быть никакой другой ответственности, кроме как хранить эти две части информации.

Когда игра была впервые реализована только как переводческая игра, казалось, что вещи придерживаются Единого принципа ответственности (когда каждый объект класса и метод отвечают только за одну вещь), поскольку обрабатывается только одна вещь. Но внесение фундаментальных изменений в систему сделало очевидным, что текущий дизайн не следовал SRP, так как многие функции были необходимы для управления двойственным поведением.

Создание абстракции для флэш-карты сразу после загрузки данных из файла YAML с использованием только методов для двух ее частей данных следует SRP и является основным строительным блоком, из которого можно реализовать функцию Tell Don’t Ask с типами утки.

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

Продумывая дизайн, у нас теперь есть флеш-карты ; их коллекция будет набором карт . Оттуда мы можем взять набор карт и передать его в один из двух видов игрового режима : либо набор текста, либо перевод . Эти два типа игрового режима будут одной и той же игрой типа утки.

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

Внедрение Скажи Не спрашивай

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
class CardSet
  attr_reader :card_set
  def initialize(card_hash)
    @card_set = card_set_builder(card_hash)
  end
 
  def game(mode)
    case mode
    when :translate
      Modes::Translate.new(self)
    when :typing_practice
      Modes::TypingPractice.new(self)
    else
      raise "Invalid Game Mode!"
    end
  end
 
  # ...
end

Здесь у нас есть класс CardSet , который инициализирует свой набор карточек для хранения и не несет никакой другой ответственности. Метод game здесь — наш метод рассказа. Мы сообщаем ему, какой режим мы будем использовать, и он возвращает нам тип игры, в которую мы собираемся играть.

Оба Modes::Translate и Modes::TypingPractice подклассируются к классу Modes::Game и определяют (одинаковые) методы только там, где они различаются.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
class Translate < Game
  def match? input
    current.translation.any? {|value| value == input }
  end
 
  def mode
    :translate
  end
end
 
class TypingPractice < Game
  def match? input
    "#{current}" == input
  end
 
  def mode
    :typing_practice
  end
end</code>

Теперь, когда наша игра отправляет метод mode для отображения, у нас есть имя и match? Метод — это особая логика, которая отличает две игры. Это поведение относится только к игре и, следовательно, к которой относится эта логика. Он также обеспечивает безошибочный механизм вызова между обработками каждой игры, что является основной целью типов уток.

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

Шаблоны — это общее правило, помогающее вам, но в программировании всегда есть исключение. Подсчет очков не входит в обязанности нашего игрового типа и относится к сфере действия нашего пользовательского интерфейса. Также оценка была реализована с необработанными типами, такими как Boolean и integer, поэтому мы перестали играть роль утки в этой игровой механике. Эта область кода оказывает очень слабое влияние на будущие изменения в коде системы и является приемлемым и оптимальным временем для использования необработанных типов.

Резюме

Когда я закончил рефакторинг, я удалил около 600 строк кода и добавил 400. Кодовая база превратилась из трудной для понимания и поддержки в простую для чтения и легко обновляемую с новыми функциями. И это то, что Tell Not Ask предлагает вам, когда вы следуете этому принципу дизайна. Это значительно упрощает понимание кода и дает лучшую гарантию компоновки.

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

Наконец, вы можете использовать принцип Tell Don’t Ask, как показано в коде выше, сообщая поведение, а не запрашивая его, и вы можете использовать этот метод, чтобы обернуть себя в текущем объекте вашим типом утки и продолжить работу по пути кода.

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

Опубликовано на Java Code Geeks с разрешения Дэниела П. Кларка, партнера нашей программы JCG . См. Оригинальную статью здесь: Рефакторинг для шаблона «Не спрашивай»

Мнения, высказанные участниками Java Code Geeks, являются их собственными.