> For the complete documentation index, see [llms.txt](https://eda-1.gitbook.io/lgwt/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://eda-1.gitbook.io/lgwt/osnovy-testirovaniya/refactoring-checklist.md).

# Контрольный список для рефакторинга

Рефакторинг — это навык, который, будучи достаточно отработанным, в большинстве случаев довольно легко становится второй натурой.

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

## Рефакторинг против других видов деятельности

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

Именно поэтому это 3-й шаг цикла TDD. Как только вы добавили поведение и тест для его поддержки, рефакторинг должен быть действием, которое не требует изменения вашего тестового кода. **Вы занимаетесь чем-то другим**, если "рефакторите" код и при этом вынуждены менять тесты.

Многие очень полезные рефакторинги просты в освоении и легко выполняются (ваша IDE практически полностью автоматизирует многие из них), но со временем они оказывают огромное влияние на качество нашей системы.

### Другие виды деятельности, например "большой" дизайн

> Итак, я не меняю "реальное" поведение, но должен изменить свои тесты? Что это?

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

* Менять поведение
* Менять сигнатуры методов

...поскольку ваши тесты связаны с этими двумя вещами, но вы можете:

* Вводить приватные методы, поля и даже новые типы и интерфейсы
* Изменять внутренности публичных методов

Что, если вы хотите изменить сигнатуру метода?

```go
func (b BirthdayGreeter) WishHappyBirthday(age int, firstname, lastname string, email Email) {
	// some fascinating emailing code
}
```

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

```go
func (b BirthdayGreeter) WishHappyBirthday(person Person)
```

Что ж, вы сейчас **проектируете** и должны действовать осторожно. Если вы не сделаете это дисциплинированно, вы можете испортить свой код, стоящие за ним тесты *и*, вероятно, то, что от него зависит — помните, `WishHappyBirthday` используют не только ваши тесты. Надеемся, что его использует и "реальный" код!

**Вы всё равно должны иметь возможность управлять этим изменением, сначала написав тест**. Можно спорить, является ли это изменением "поведения", но вы хотите, чтобы ваш метод вёл себя по-другому.

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

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

Вероятно, у вас будет несколько тестов, вызывающих `WishHappyBirthday`; в таких сценариях я бы предложил закомментировать все тесты, кроме одного, выполнить изменение, а затем проработать остальные тесты по своему усмотрению.

### Большой дизайн

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

### За деревьями леса не видать

> [Если кто-то не может **увидеть за деревьями лес** (в британском английском: "can't see the wood for the trees", в американском английском: "can't see the forest for the trees"), то он слишком сильно погружен в детали чего-либо и поэтому не замечает главного в целом.](https://www.collinsdictionary.com/dictionary/english/cant-see-the-wood-for-the-trees)

Говорить о "больших" проблемах дизайна легче, когда **лежащий в основе код хорошо факторизован**. Если вы и ваши коллеги вынуждены тратить значительное количество времени на мысленное осмысление беспорядка в коде каждый раз, когда открывают файл, какой у вас шанс подумать о дизайне кода?

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

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

## Начальный мысленный чек-лист

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

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

### Инлайнинг переменных

Если вы создаете переменную только для того, чтобы передать её другому методу/функции:

```go
url := baseURL + "/user/" + id
res, err := client.Get(url)
```

Рассмотрите возможность инлайнинга (`command+option+n`), *если только* имя переменной не добавляет значимого смысла.

```go
res, err := client.Get(baseURL + "/user/" + id)
```

Не будьте *слишком* умными в инлайнинге; цель не в том, чтобы иметь ноль переменных, а в том, чтобы избежать нелепых однострочников, которые никто не может прочитать. Если вы можете добавить осмысленное имя значению, возможно, лучше оставить его как есть.

### Устранение дублирования значений с помощью извлечения переменных

"Не повторяйся" (DRY). Используете одно и то же значение несколько раз в функции? Рассмотрите возможность извлечения и присвоения его переменной с осмысленным именем (`command+option+v`).

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

### Устранение дублирования в целом

Концепция [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) в наши дни имеет плохую репутацию, с некоторым на то основанием. DRY — это одна из тех концепций, которую *слишком* легко понять на поверхностном уровне, а затем неправильно применить.

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

Так что да, DRY может быть неправильно применен, но крайняя противоположность — отказ от любого DRY — также плоха. Повторяющийся код добавляет "шума" и увеличивает затраты на поддержку. Отказ от объединения связанных концепций или значений в одну сущность из-за страха неправильного использования DRY вызывает *другие* проблемы.

Поэтому, вместо того чтобы быть экстремистом с любой стороны — "нужно всё DRY" или "DRY это плохо" — задействуйте свой мозг и подумайте о коде, который вы видите перед собой. Что повторяется? Нужно ли это? Выглядит ли список параметров разумно, если вы инкапсулируете какой-то повторяющийся код в метод? Кажется ли он самодокументирующим и четко инкапсулирует ли "идею"?

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

Если сделать какой-то код DRY кажется сложным, вы, вероятно, усложняете вещи; рассмотрите возможность остановиться.

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

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

### Извлечение "магических" значений.

> [Уникальные значения с необъясненным смыслом или множественными вхождениями, которые могли бы (предпочтительно) быть заменены именованными константами](https://en.wikipedia.org/wiki/Magic_number_\(programming\))

Используйте извлечение переменной (`command+option+v`) или константы (`command+option+c`), чтобы придать смысл "магическим" значениям. Это можно рассматривать как инверсию рефакторинга инлайнинга. Я часто "переключаю" код между инлайнингом и извлечением, чтобы помочь себе судить, что, по моему мнению, читается лучше.

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

```go
func main() {
	api1Client := http.Client{
		Timeout: 1 * time.Second,
	}
	api2Client := http.Client{
		Timeout: 1 * time.Second,
	}
	api3Client := http.Client{
		Timeout: 1 * time.Second,
	}
	//etc
}
```

Мы настраиваем несколько HTTP-клиентов для нашего приложения. Здесь есть несколько *магических значений*, и мы могли бы устранить дублирование `Timeout`, извлекая переменную и давая ей осмысленное имя.

![Скриншот извлечения переменной](https://i.imgur.com/4sgUG7L.png)

Теперь код выглядит так:

```go
func main() {
	timeout := 1 * time.Second
	api1Client := http.Client{
		Timeout: timeout,
	}
	api2Client := http.Client{
		Timeout: timeout,
	}
	api3Client := http.Client{
		Timeout: timeout,
	}
	// etc..
}
```

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

Если вы хорошо владеете своей IDE, вы можете выполнить рефакторинг *инлайнинга*, чтобы клиенты снова имели отдельные значения `Timeout`.

### Сделайте публичные методы/функции легко читаемыми

Есть ли в вашем коде излишне длинные публичные методы или функции?

Инкапсулируйте шаги в приватные методы/функции с помощью рефакторинга извлечения метода (`command+option+m`).

Приведённый ниже код содержит некоторую скучную, отвлекающую "церемонию" по созданию JSON-строки и превращению её в `io.Reader`, чтобы мы могли отправить её `POST`-запросом.

```go
func (ws *WidgetService) CreateWidget(name string) error {
	url := ws.baseURL + "/widgets"
	payload := []byte(`{"name": "` + name + `"}`)

	req, err := http.NewRequest(
		http.MethodPost,
		url,
		bytes.NewBuffer(payload),
	)
	//todo: handle codes, err etc
}
```

Сначала используйте рефакторинг инлайнинга переменной (`command+option+n`), чтобы поместить `payload` в буфер.

```go
func (ws *WidgetService) CreateWidget(name string) error {
	url := ws.baseURL + "/widgets"
	req, err := http.NewRequest(
		http.MethodPost,
		url,
		bytes.NewBuffer([]byte(`{"name": "`+name+`"}`)),
	)
	// etc
}
```

Теперь мы можем извлечь создание JSON-нагрузки в функцию, используя рефакторинг извлечения метода (`command+option+m`), чтобы убрать "шум" из метода.

```go
func (ws *WidgetService) CreateWidget(name string) error {
	url := ws.baseURL + "/widgets"
	req, err := http.NewRequest(
		http.MethodPost,
		url,
		createWidgetPayload(name),
	)
	// etc
}
```

Публичные методы и функции должны описывать *что* они делают, а не *как* они это делают.

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

\-- Мартин Фаулер

Это помогает вам лучше понять общий дизайн, а затем позволяет задавать вопросы об обязанностях:

> Почему этот метод делает X? Разве это не должно находиться в Y?

> Почему этот метод выполняет так много задач? Можем ли мы объединить это где-то ещё?

Приватные функции и методы великолепны; они позволяют обернуть нерелевантные "как" в "что".

#### Но теперь я не знаю, как это работает!

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

> Вы научились эффективно навигировать по кодовым базам, используя свои инструменты?

Совершенно намеренно, как *автор* `CreateWidget`, я не хочу, чтобы создание конкретной строки было существенным элементом в описании метода. В 99% случаев это отвлекающий, нерелевантный "шум" для читателя.

Однако, если кому-то *действительно* интересно, вы нажимаете `command+b` (или что бы ни было у вас "перейти к символу") на `createWidgetPayload`... и читаете. Нажимаете `command+left-arrow`, чтобы вернуться назад.

### Перемещение создания значений во время конструирования.

Методам часто приходится создавать значения и использовать их, например, `url` в нашем методе `CreateWidget` из предыдущего примера.

```go
type WidgetService struct {
	baseURL string
	client  *http.Client
}

func NewWidgetService(baseURL string) *WidgetService {
	client := http.Client{
		Timeout: 10 * time.Second,
	}
	return &WidgetService{baseURL: baseURL, client: &client}
}

func (ws *WidgetService) CreateWidget(name string) error {
	url := ws.baseURL + "/widgets"
	req, err := http.NewRequest(
		http.MethodPost,
		url,
		createWidgetPayload(name),
	)
	// etc
}
```

Методика рефакторинга, которую можно применить здесь, заключается в том, что если создаётся значение **которое не зависит от аргументов метода**, то вместо этого вы можете создать *поле* в вашем типе и вычислить его в вашей функции-конструкторе.

```go
type WidgetService struct {
	client          *http.Client
	createWidgetURL string
}

func NewWidgetService(baseURL string) *WidgetService {
	client := http.Client{
		Timeout: 10 * time.Second,
	}
	return &WidgetService{
		createWidgetURL: baseURL + "/widgets",
		client:          &client,
	}
}

func (ws *WidgetService) CreateWidget(name string) error {
	req, err := http.NewRequest(
		http.MethodPost,
		ws.createWidgetURL,
		createWidgetPayload(name),
	)
	// etc
}
```

Перемещая их во время конструирования, вы можете упростить свои методы.

#### Сравнение и сопоставление `CreateWidget`

Начиная с:

```go
func (ws *WidgetService) CreateWidget(name string) error {
	url := ws.baseURL + "/widgets"
	payload := []byte(`{"name": "` + name + `"}`)
	req, err := http.NewRequest(
		http.MethodPost,
		url,
		bytes.NewBuffer(payload),
	)
	// etc
}

```

С помощью нескольких базовых рефакторингов, выполненных почти полностью с использованием автоматизированных инструментов, мы получили:

```go
func (ws *WidgetService) CreateWidget(name string) error {
	req, err := http.NewRequest(
		http.MethodPost,
		ws.createWidgetURL,
		createWidgetPayload(name),
	)
	// etc
}
```

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

### Попытайтесь удалить комментарии.

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

\-- Мартин Фаулер

И снова, рефакторинг извлечения метода может стать вашим другом.

## Исключения из правил

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

Простым примером может быть переименование публичного символа (например, метода, типа или функции) с помощью `shift+F6`. Это, конечно, изменит производственный и тестовый код.

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

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

## Используйте свои инструменты, чтобы практиковаться в рефакторинге.

* Вы должны запускать свои юнит-тесты каждый раз, когда делаете одно из этих небольших изменений. Мы инвестируем время в то, чтобы наш код был тестируем юнит-тестами, и цикл обратной связи в несколько миллисекунд является одним из значительных преимуществ; используйте его!
* Опирайтесь на систему контроля версий. Вы не должны стесняться пробовать новые идеи. Если вы довольны, фиксируйте изменения; если нет, откатывайте. Это должно быть удобно и легко, и не быть большой проблемой.
* Чем лучше вы используете свои юнит-тесты и систему контроля версий, тем легче *практиковаться* в рефакторинге. Как только вы освоите эту дисциплину, **ваши навыки проектирования быстро возрастут**, потому что у вас будет надежный и эффективный цикл обратной связи и страховочная сетка.
* Слишком часто в моей карьере я слышал, как разработчики жаловались на отсутствие времени на рефакторинг; к сожалению, очевидно, что это занимает у них так много времени, потому что они не делают это дисциплинированно — и они недостаточно практиковались.
* Хотя ввод текста никогда не является узким местом, вы должны уметь использовать любой редактор/IDE, который вы используете, для безопасного и быстрого рефакторинга. Например, если ваш инструмент не позволяет извлекать переменные одним нажатием клавиши, вы будете делать это реже, потому что это более трудоемко и рискованно.

## Не просите разрешения на рефакторинг

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

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

У Мартина Фаулера есть еще одна фантастическая цитата для нас.

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

## Заключение

Это не исчерпывающий список, а только начало. Прочтите книгу Мартина Фаулера "Рефакторинг" (2-е изд.), чтобы стать профессионалом.

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

Мы всегда должны стремиться оставлять код в *образцовом* состоянии.

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


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://eda-1.gitbook.io/lgwt/osnovy-testirovaniya/refactoring-checklist.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
