Вы можете поверить, что рефакторинг — это ужасная сложность. Часы — нет, дни — были потрачены на то, чтобы расшифровать методы из тысячи строк. Растягивающийся клубок вызовов и обратных вызовов, которые, кажется, производят рабочий код только случайно.
Иногда рефакторинг — это все эти вещи, но чаще всего рефакторинг заключается в обнаружении знакомых шаблонов и последующем методическом реагировании на них. Издалека большие рефакторинги могут показаться пугающими. Невозможно. В конце концов, они состоят из множества крошечных изменений, и каждое изменение является рациональным. Объяснимый.
Рефакторинг — это не столько методы с тысячами строк. Речь идет о распознавании крошечного шаблона в этом методе из тысячи строк и последующем применении пошагового рецепта для изменения этой мелочи. Он делает это снова и снова.
Обратите внимание, что здесь есть две части:
- Признание фрагмента кода как признака, который, как известно, проблематичен.
- Применение изменений, которые известны, чтобы исправить эту категорию проблем.
Это не жесткий, непогрешимый процесс. Есть некоторые догадки. Иногда вы распознаете проблему, но она встроена в другие проблемы, о которых вам трудно сформулировать, и вам может быть трудно ее решить. Возможно, вы не сможете изолировать его достаточно хорошо, чтобы это исправить. Или, может быть, вы чувствуете проблему, но не можете точно определить, в чем проблема. Возможно, есть несколько известных решений проблемы, и вы не уверены, какой из них будет работать, и попробуйте два или три различных подхода, прежде чем вы найдете тот, который подходит для вас.
В любом случае, все сводится к выявлению проблемного паттерна и последующей систематической процедуре его устранения.
Проблемные шаблоны известны как «запахи кода».
Запах [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
В этом случае, поскольку мы фильтруем, чтобы сохранить, а не отбросить, reject
select
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_by
max_by
# even more after
kids.max_by {|kid| kid.age}.name
В реальном мире цикл не всегда будет each
Это не всегда будет особенно многословно. Иногда условие будет кратким: постфикс, if
unless
Однако после того, как вы определили запах с такой точностью, проблему очень просто диагностировать и исправить практически в любом контексте.
# before
words.inject([]) do |censured, word|
censured << word if word.size == 4
censured
end
# after
words.select {|word| word.size == 4}
Иногда вы не сможете избавиться от этого — не может быть идеального перечислимого метода. Это природа запахов кода. Они обычно указывают на то, что есть лучший способ решить что-то, но не всегда.
В отличие от классических запахов кода, описанных Мартином Фаулером в его книге « Рефакторинг» , запахи микрокода вряд ли будут признаком более глубокой проблемы в системе. Запах микрокода затронет только несколько строк кода в его непосредственной близости.
Это все еще стоит делать, и не только потому, что вы начинаете чувствовать, насколько мало драмы в хорошем рефакторинге.
Совокупный эффект уменьшения трения, удаления шаблона и улучшения читабельности небольшими шагами заключается в том, что большие запахи кода поднимаются на поверхность и становятся легче обнаруживаемыми.
И если вы можете обнаружить это, есть большой шанс, что вы можете это исправить.