[Из песочницы] Рефакторинга много не бывает

Привет, Хабр! Представляю вашему вниманию перевод статьи «Refactoring — oops, I«ve been doing it backwards» автора Джастина Фуллера (Justin Fuller).

_pscew0p77obdpwkceeojkldvlw.jpeg


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

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

Так почему рефакторинг не оправдал моих надежд?

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

В конце концов, все получилось, но был ли мой код более сложным, чем нужно? Думаю, что да.

Главенство принципов над целью


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

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

Помните мое размышление в самом начале? Это напомнило мне мантру: «Заставь это работать, сделай это правильно, сделай это быстро». Я понял, что не следовал этому порядку. Я делал все правильно, быстро, а потом делал так, чтобы это работало!

Заставьте это работать


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

То же самое может случиться и с кодом.

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

jiybxzkbplklp6tcndto86yds_e.jpeg


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

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

Имейте причину для рефакторинга


Наличие SOLID-кода не является причиной. Наличие функционального или чистого кода также не является причиной.

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

Почему мы инвертируем зависимости? Для того, чтобы бизнес-логика могла использоваться несколькими реализациями.

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

«Считается, что лучше всего использовать инверсию зависимостей» — это не причина. «Хороший код — расширяемый код» — это тоже не причина. Что если у меня есть только пара неизменяемых зависимостей? Мне все еще нужна инверсия зависимостей? Пока что нет. Что если ничего не нужно для расширения моего кода, и я вообще не планирую этого делать? Должен ли я усложнять мой код, чтобы просто отметить этот флажок? Нет!

Посмотрите на следующий пример.

// not extensible
function getUser() {
  return {
    name: 'Justin',
    email: 'justinfuller@email.com'
  }
}
// Extensible

class User {
  constructor(options = {}) {
    this.userData = options
  }
  get() {
    return this.userData
  }

  set(key, value) {

    this.userData[key] = value
  }
}
        // not extensible

function getUser() {

  return {
    name: 'Justin',
    email: 'justinfuller@email.com'
  }
}
// Extensible

class User {
  constructor(options = {}) {
    this.userData = options
  }
  get() {
   return this.userData
 }
  set(key, value) {
   this.userData[key] = value
  }
}


Что вы предпочитаете? Что обычно пишете в первую очередь? Конечно, класс User гораздо более расширяем, потому что он может обрабатывать не только имя и электронную почту. Он также может быть расширен дочерним классом, может быть SuperUser, который будет иметь гораздо больше методов, но все еще использует классические методы get() и set().

Тем не менее класс User может быть излишним, и ваш код может стать гораздо сложнее, чем это когда-нибудь может понадобиться.

Мой совет — придерживайтесь простейшего шаблона.

Порядок сложности


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

Обычно я помещаю что-то в объект вместо простой константной переменной. Этот список держит меня под контролем и предохраняет от преждевременного рефакторинга.

Баланс


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

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

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

В следующий раз, когда захотите провести рефакторинг своего кода, подумайте, а стоит ли?

© Habrahabr.ru