Статьи

Улучшите запах вашего кода с помощью микрорефакторинга

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

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

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

Обратите внимание, что здесь есть две части:

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

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

В любом случае, все сводится к выявлению проблемного паттерна и последующей систематической процедуре его устранения.

Проблемные шаблоны известны как «запахи кода».

Запах [A] кода — это поверхностное указание, которое обычно соответствует более глубокой проблеме в системе. — Мартин Фаулер

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

Если вы хотите лучше справиться с рефакторингом, применение процесса на микроуровне может облегчить начало работы с ним в более крупных и хаотичных системах.

Посмотрите на следующий фрагмент кода:

sum = 0
numbers.each do |number|
  sum += number
end
sum

Сделайте быстрое суждение об этом.

Хорошо плохо. Чистый грязный. Простой / комплекс. Читаемые / загадочными.

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

Вот еще один:

 anagrams = []
candidates.each do |candidate|
  if anagram_of?(subject, candidate)
    anagrams << candidate
  end
end
anagrams

Проверьте свои интуитивные чувства по этому поводу. Сравните это с предыдущим примером.

Лучше хуже. То же / разные.

Вот еще один:

 mutations = 0
(0...strand1.length).each do |i|
  if strand1[i] != strand2[i]
    mutations += 1
  end
end
mutations

И еще один:

 oldest = ""
highest = 0
kids.each do |kid|
  if kid.age > highest
    oldest = kid.name
    highest = age
  end
end
oldest

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

Самое очевидное сходство, пожалуй, в том, что это все петли. Кроме того, каждый цикл — это each Что еще более важно, каждый из приведенных выше примеров кода демонстрирует один или два специфических запаха микрокода.

  • Цикл с временной переменной.
  • Цикл с вложенным условным выражением.

Исправление для обоих этих запахов кода заключается в выборе более подходящего перечислимого метода.

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

Вот микрорефакторинг для соответствия микро-запахам, показанным в приведенных выше примерах.

Первый цикл имеет только временную переменную, а не вложенную условную. Это можно исправить с помощью inject

 reduce

Второй цикл имеет как временную переменную, так и вложенную условную. Мы фильтруем список, и # before
sum = 0
numbers.each do |number|
sum += number
end
sum

# after
numbers.inject(:+)
select В этом случае, поскольку мы фильтруем, чтобы сохранить, а не отбросить, rejectselect

 keep_if

Третий пример также демонстрирует оба микро-запахов. В этом случае мы считаем различия:

 # before
anagrams = []
candidates.each do |candidate|
  if anagram_of?(subject, candidate)
    anagrams << candidate
  end
end
anagrams

# after
candidates.select do |candidate|
  anagram_of?(subject, candidate)
end

В последнем примере кода есть тройной удар: две временные переменные и вложенное условное выражение. Так как мы ранжируемся по некоторому атрибуту, возможно, будет работать # before
mutations = 0
(0...strand1.length).each do |i|
if strand1[i] != strand2[i]
mutations += 1
end
end
mutations

# after
(0...strand1.length).count {|i| strand1[i] != strand2[i]}

 sort_byсамый # before
oldest = ""
highest = 0
kids.each do |kid|
  if kid.age > highest
    oldest = kid.name
    highest = age
  end
end
oldest

# after
kids.sort_by {|kid| kid.age}.last.name

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

 # even more after
kids.max_by {|kid| kid.age}.name

В реальном мире цикл не всегда будет each Это не всегда будет особенно многословно. Иногда условие будет кратким: постфикс, ifunless

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

 # before
words.inject([]) do |censured, word|
  censured << word if word.size == 4
  censured
end

# after
words.select {|word| word.size == 4}

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

В отличие от классических запахов кода, описанных Мартином Фаулером в его книге « Рефакторинг» , запахи микрокода вряд ли будут признаком более глубокой проблемы в системе. Запах микрокода затронет только несколько строк кода в его непосредственной близости.

Это все еще стоит делать, и не только потому, что вы начинаете чувствовать, насколько мало драмы в хорошем рефакторинге.

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

И если вы можете обнаружить это, есть большой шанс, что вы можете это исправить.