Содержание
Представьте, что вы работаете над сайтом, и ваши пользователи постоянно сообщают, что у них проблемы с навигацией. Скорее всего вам нужно будет реорганизовать код, чтобы исправить эту ошибку.
Другой пример: вы работаете над игрой, которая постоянно дает сбой и вылетает. В этом случае рефакторинг кода тоже устранит ошибки, повысит производительность и предотвратит дальнейшие сбои.
Рефакторинг — это метод разработки программного обеспечения, который включает улучшение дизайна, структуры и качества существующего кода без изменения его внешнего поведения.
Рефакторинг — это очень важная практика в процессе разработки программного обеспечения, потому что она гарантирует, что код останется удобным для сопровождения, масштабируемым и гибким даже с течением времени.
Эта практика применина к практически всем языкам программирования и средам разработки, включая Java, Python и Ruby on Rails.
Цель рефакторинга — сделать код более понятным, модифицировать и поддерживать его с течением времени.
Эта цель достигается за счет устранения сверхсложности кода, улучшения его общего дизайна, повышения удобочитаемости и выразительности. Рефакторинг также помогает обнаружить избыточный или повторяющийся код. Удаление такого кода повышает производительность и удобство сопровождения программного обеспечения.
Рефакторинг можно делать разными методами. Вот несколько базовых примеров:
Также можно оптимизировать код с использованием современных парадигм программирования и шаблонов проектирования, чтобы улучшить его общую структуру.
Рефакторинг может занимать много времени, но оно того стоит. В будущем вы, наоборот, снизите затраты на обслуживание. Также эта практика повысит производительность труда разработчиков: оптимизированный код будет гораздо проще обновлять и развивать.
В целом, рефакторинг дает гарантию, что код останется поддерживаемым, масштабируемым и гибким. Это существенно упрощает его адаптацию к изменяющимся потребностям бизнеса и техническим требованиям.
Так как рефакторинг — это очень важный процесс в разработке программного обеспечения, нужно строго следовать правилам его проведения. Если этого не сделать, могут появиться серьезные ошибки, которые не только помешают масштабируемости и гибкости кода, но и вызовут критические баги после его исполнения.
Основные правила рефакторинга перечислены ниже:
Только следуя этим правилам, рефакторинг можно проводить безопасно и эффективно. Если правило по каким-то причинам приходится нарушить, лучше не проводить рефакторинг вообще и оставить код, как есть.
Чтобы лучше понять, как применять вышеописанные правила, давайте рассмотрим их на примерах.
Предположим, в вашем коде есть функция, вычисляющая сумму двух чисел. После ее рефакторинга, результат вычисления (то есть то, что код выведет пользователю) должен остаться прежним:
// до рефакторинга function sum(a, b) { return a + b; } // после рефакторинга function sum(a, b) { const result = a + b; return result; }
Тестирование помогает проверить, что код по-прежнему работает так, как задумано, даже после внесения изменений. Но самое главное, что тестирование должно быть тщательным и широким.
Например, у вас есть функция, которая сортирует массив чисел. После рефакторинга будет недостаточно проверить ее работу только на одном наборе данных. Вместо этого, вы должны будете протестировать разные массивы (включая положительные и отрицательные числа, нули и т.д.), как до, так и после рефакторинга.
// до рефакторинга function sortArray(array) { return array.sort(); } // после рефакторинга function sortArray(array) { const sortedArray = [...array].sort((a, b) => a - b); return sortedArray; }
Допустим, вам нужно реорганизовать сложную функцию, выполняющую несколько операций. Вы можете разбить ее на более мелкие и более управляемые функции. При этом создание каждой новой функции считается новым шагом рефакторинга.
// до рефакторинга function complexFunction(a, b, c, d) { // perform operation 1 // perform operation 2 // perform operation 3 // ... return result; } // после рефакторинга function operation1(a, b) { // perform operation 1 return result1; } function operation2(c, d) { // perform operation 2 return result2; } function operation3(result1, result2) { // perform operation 3 return result3; } function complexFunction(a, b, c, d) { const result1 = operation1(a, b); const result2 = operation2(c, d); const result3 = operation3(result1, result2); return result3; }
Например, у вас есть функция, которая вычисляет площадь прямоугольника. Она включает:
calculateRectangleArea
.width
, height
и area
.// до рефакторинга function calculateArea(x, y) { return x * y; } // после рефакторинга function calculateRectangleArea(width, height) { const area = width * height; return area; }
Дубли кода обычно появляются, если одно и то же действие выполняется несколько раз. Иногда можно переписать код так, чтобы действие выполнялось только один раз — но не всегда.
Во всех других случаях вы можете просто вынести повторяющийся код в отдельную функцию:
// до рефакторинга function calculateTotalPrice(quantity, price) { const totalPrice = quantity * price; const discount = 0.1; const discountedPrice = (totalPrice * discount) + totalPrice; console.log(discountedPrice); } // после рефакторинга function calculateDiscountedPrice(totalPrice, discount) { const discountedPrice = totalPrice * (1 - discount); return discountedPrice; }
Обычно рефакторинг проводят по одной (или нескольких сразу) из следующих причин:
Вышеперечисленные причины важны, но не всегда можно понять, актуальны ли они для вашего проекта здесь и сейчас. Поэтому, если сомневаетесь, нужен ли вам рефакторинг кода, лучше пройтись по этому чек-листу.
Рефакторинг нужен:
Рефакторинг приносит результаты, потому что улучшает качество, удобство сопровождения и производительность вашего кода без изменения его внешнего поведения.
Это как редактирование готового текста: вы не пишете ничего с нуля, а думаете, как улучшить имеющийся материал.
Чем больше вы смотрите на уже написанную функцию и ищете возможности ее упростить, тем лучше вы сами понимаете, как именно работает код. В результате вы получаете не только фактически работающую программу, но четкую программную структуру.
Это, своего рода, самопроверка — сможете ли вы кратко в комментариях объяснить, почему нужен именно этот кусок кода?
Допустим, вы работаете над веб-приложением с формой авторизации. У формы есть одна-единственная функция проверки. Функция следит за тем, чтобы поля имени пользователя и пароля не были пустыми:
def validate_login(username, password): if not username: return "Username is required" if not password: return "Password is required" return None
Но приложение развивается и требований к нему становится все больше. Так, теперь имя пользователя должно содержать только действительный адрес электронной почты, а пароль содержать ровно восемь символов.
Каждое из новых требований вы записываете все в той же одной-единственной функции, из-за чего она разрастается и становится все более сложной.
В какой-то момент вы понимаете, что не только не можете объяснить коллеге, что именно делает эта функция, но и сами теряетесь, где заканчивается одна проверка и начинается другая. Пора делать рефакторинг.
Логичное решение — вынести каждую из проверок в отдельную функцию с соответствующим названием и целью:
def validate_username(username): if not username: return "Username is required" if not re.match(r"[^@]+@[^@]+\.[^@]+", username): return "Username must be a valid email address" return None def validate_password(password): if not password: return "Password is required" if len(password) < 8: return "Password must be at least 8 characters long" if not any(c.isupper() for c in password): return "Password must contain at least one uppercase letter" if not any(c.isdigit() for c in password): return "Password must contain at least one digit" return None def validate_login(username, password): errors = [] username_error = validate_username(username) if username_error: errors.append(username_error) password_error = validate_password(password) if password_error: errors.append(password_error) return errors or None
Теперь у каждого правила проверки есть своя функция: читать такой код гораздо удобнее. Кроме того, мы добавили функцию validate_login
, которая вызывает каждую функцию проверки и возвращает список ошибок.
Если посмотреть на ситуацию со стороны пользователя — ничего не изменилось. Как бы ни была записана функция проверки, на выходе она даст ошибку, если вы как пользователь введете недействительный адрес электронной почты или пароль в четыре символа. Но вы как программист (и ваши коллеги) больше не будете путаться в том, где какая проверка.
Более того, если в будущем вам потребуется добавить новые правила проверки, вы можете просто создать новую функцию и добавить ее к функции validate_login
.
При всех преимуществах рефакторинга, бывают ситуации, когда трудозатраты на него не имеют смысла. Вот несколько примеров, когда рефакторинг не нужен:
Худшее, что вы можете сделать — это начать делать рефакторинг, не понимая зачем. Так вы просто потратите деньги и время зря.
Рефакторинг и проектирование (дизайн-код) — тесно связанные понятия в разработке программного обеспечения, но с некоторым ключевыми отличиями.
Рефакторинг — это процесс улучшения дизайна существующего кода без изменения его внешнего поведения.
Проектирование — это процесс создания структуры и организации кода с нуля.
Из-за этого основного отличия, рефакторинг проводят после проектирования. Другими словами, результат проектирования — это код, который используется для последующего рефакторинга. Но это не означает, что на этом все заканчивается.
Рефакторинг — это непрерывный процесс, который должен выполняться постоянно на протяжении всего цикла разработки, а не только как разовое мероприятие.
Возвращаясь к сравнению, проектирование выполняется только в начале цикла разработки. Оно включает создание плана программной системы, структуру, функциональность и поведение. Если проектирование сделано хорошо, в процессе рефакторинга не будет много изменений.
Но даже при хорошем дизайн-коде могут возникать моменты, когда необходим рефакторинг. Обычно это происходит по мере того, как код развивается и изменяется, а дизайн перестает быть оптимальным. Все потому, что в начале цикла разработки невозможно предусмотреть все. А рефакторинг как раз может помочь привести код в соответствии с исходным видением.
Таким образом, проектирование и рефакторинг — два важнейших аспекта разработки ПО. Дизайн закладывает основу программной системы, а рефакторинг помогает улучшить ее существующий код.
При правильном выполнении рефакторинг может повысить производительность за счет удаления ненужного кода, упрощения и оптимизации алгоритмов. Но если рефакторинг выполнен неправильно, это может негативно сказаться на производительности.
Тем не менее, бывают случаи, когда рефакторинг вообще не влиять не производительность. Давайте разберемся, как это работает:
НО:
Рефакторинг должен проводиться с упором на улучшение качества кода. И хотя производительность имеет значение, она не должна быть основной целью рефакторинга.
Мы уже достаточно часто упоминали это в материале, чтобы сделать однозначный вывод: тестирование — неотъемлемая часть процесса рефакторинга. Это гарантия того, что рефакторинг не внесет в код новых ошибок и что после этого процесса код по-прежнему будет работать так, как нужно.
Разберем подробнее, когда и как проводят тесты.
Тестирование перед началом процесса рефакторинга помогает установить базовый уровень того, как код работает в настоящее время. Кроме того, так можно обнаружить ошибки или проблемы, которые нужно решить перед стартом рефакторинга.
Пример:
Допустим, у вас есть функция на Python, которая изменяет размер изображения с помощью библиотеки Pillow
:
from PIL import Image def resize_image(input_path, output_path, max_size): with Image.open(input_path) as img: width, height = img.size if width > max_size or height > max_size: ratio = min(max_size / width, max_size / height) new_size = (int(width * ratio), int(height * ratio)) img = img.resize(new_size) img.save(output_path)
Чтобы протестировать эту функцию, вы можете написать простой скрипт, который (1) создает файл изображения нужного размера, (2) вызывает функцию resize_image
для уменьшения размера изображения, а затем (3) проверяет правильность размеров выходного файла:
import os import tempfile def test_resize_image(): input_file = tempfile.NamedTemporaryFile(suffix='.jpg') output_file = tempfile.NamedTemporaryFile(suffix='.jpg') img = Image.new('RGB', (1000, 1000)) img.save(input_file.name) resize_image(input_file.name, output_file.name, 500) assert os.path.exists(output_file.name) with Image.open(output_file.name) as img: width, height = img.size assert width <= 500 and height <= 500
В этом примере функция test_resize_image
создает временный входной файл с использованием модуля tempfile
с размером 1000×1000 пикселей. Затем он вызывает функцию resize_image
, чтобы изменить размер изображения до максимального размера 500 пикселей, и проверяет, что выходной файл существует и имеет размеры, меньшие или равные 500×500 пикселей.
Протестировав функцию resize_image
перед рефакторингом, вы можете установить базовые показатели того, как она работает в настоящее время, и убедиться, что любые изменения, которые вы вносите в процессе рефакторинга, не приводят к новым ошибкам или регрессиям.
Регулярные проверки в процессе рефакторинга помогают обнаружить любые проблемы и ошибки на ранней стадии и устранить их.
После завершения процесса рефакторинга код необходимо снова протестировать, чтобы убедиться, что он по-прежнему работает должным образом.
Если регулярное тестирование проводилось достаточно тщательно, проверка после рефакторинга не должна обнаружить проблемы.
Автоматизированное тестирование может быть особенно полезным во время рефакторинга, потому что оно помогает проводить частое и всестороннее тестирование с минимальными усилиями. Ее включают на всех этапах проверок: до, после и в процессе рефакторинга.
Хотя у рефакторинга много преимуществ, он также может привести к возникновению определенных проблем:
Чтобы проблем не было, нужно подходить к рефакторингу осторожно и методично, имея четкий план и цели.
Тестирование и документирование должны быть главным приоритетом на протяжении всего процесса, а изменения нужно вносить постепенно и с прицелом на удобство сопровождения и удобочитаемость.
В заключение поделимся с вами некоторыми эффективными методами рефакторинга:
Это лишь некоторые из многих методов, которые можно использовать для рефакторинга кода. Конкретные используемые методы будут зависеть от конкретных целей процесса рефакторинга и характеристик кода.
Прокси (proxy), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…
Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…
Повсеместное распространение смартфонов привело к огромному спросу на мобильные игры и приложения. Миллиарды пользователей гаджетов…
В перечне популярных чат-ботов с искусственным интеллектом Google Bard (Gemini) еще не пользуется такой популярностью…
Скрипт (англ. — сценарий), — это небольшая программа, как правило, для веб-интерфейса, выполняющая определенную задачу.…
Дедлайн (от англ. deadline — «крайний срок») — это конечная дата стачи проекта или задачи…