Школа
Управление

Как написать аккуратный код? Часть первая: зацепление

6 фев 2020
👁 13437   🗩3
Управление

Как написать аккуратный код? Часть первая: зацепление

6 фев 2020
👁 13437   🗩3
Фёдор Борщёв
Программист, стартапер, ИТ‑консультант
Полезно
 15
15
Непонятно
  
Войдите в Бюросферу, чтобы голосовать

Сложнейший вопрос в объектно‑ориентированном программировании — как правильно структурировать новый код в приложении: положить в отдельный класс или просто дописать в конце существующего? Если долго складывать код неправильно — кодовая база деградирует. Чтобы правильно определять место для нового кода, нужно изучить несколько понятий — зацепление, связность и заменяемость. В этом совете поговорим о зацеплении.

Зацепление — это мера того, насколько класс в коде зависит от соседних классов. Чем больше класс зависит от соседей, тем выше зацепление. Чем зацепление ниже — тем легче читать и дорабатывать код.

Представим бэкенд для магазина, который продаёт электронные курсы. В коде у него есть класс «Заказ», у которого есть метод «отправить пользователю ссылку на курс». Метод использует электронную почту, но должен ли он работать с почтовым провайдером напрямую? Давайте попробуем:

from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail

class Order:
  def ship(self):
    message = Mail(to_emails=self.customer.email, subject='Вот ваш курс', content='Удачи в прослушивании')

    sendgrid = SendgridAPIClient(settings.SENDGRID_API_KEY)
    sendgrid.send(message)

Получился код, в котором класс «Заказ» намертво зацеплен с классами Mail и SendGridAPIClient из поставки Сендгрида. Если появятся другие классы, которые пишут письма пользователю — к примеру «Догонятель», который через три дня после покупки спрашивает у пользователя, как ему курс, то придётся туда копировать эту логику:

class Chaser:
  def chase(self):
    message = Mail(to_emails=self.customer.email, subject='Как вам курс?', content='Расскажите, что нам улучшить?')

    sendgrid = SendgridAPIClient(settings.SENDGRID_API_KEY)
    sendgrid.send(message)

Такой одинаковый код программисты называют «копипастой» — по имени операции copy‑paste , которая его порождает. Копипаста приводит к большому количеству ненужной работы. Допустим, Сендгрид стал хуже доставлять наши письма и, чтобы спасти ситуацию, мы решили перейти к другому почтовому провайдеру, скажем, к Мейлджету. АПИ Мейлджета несовместим с АПИ Сендгрида, и поэтому нам придётся искать в коде все места, которые зацеплены с реализацией Сендгрида и полностью переделывать их на Мейлджет — отдельно править и «Заказ», и «Догонятель». Если таких мест в коде 5 — придётся править все 5.

Теперь давайте отцепим «Заказ» и «Догонятель» от Сендгрида. Для этого сосредоточим логику отправки в одном месте — специальном классе «Почтальон»:

from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail
            
class Mailman:
  def __init__(self, customer):
    self.customer = customer

  def deliver(self, subject, message):
    message = Mail(to_emails=self.customer.email, subject=subject, content=message)

    sendgrid = SendgridAPIClient(settings.SENDGRID_API_KEY)
    sendgrid.send(message)

Теперь ни «Заказ», ни «Догонятель» ничего не знают о том, как мы отправляем почту. Их задача — просто попросить почтальона доставить письмо:

from app.mailmain import Mailman

class Order:
  def ship(self):
    mailman = Mailman(customer)
    mailman.deliver(subject='Вот ваш курс', content='Удачи в прослушивании')

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

P. S. Это был совет об управлении разработкой. Хотите больше знать о планировании спринтов, управлении продуктом или о настройке инфраструктуры? Присылайте вопросы.

Управление проектомВеб‑разработка
Полезно
 15
15
Непонятно
  
Войдите в Бюросферу, чтобы голосовать
Отправить
Поделиться
Поделиться
Запинить
Твитнуть

Комментарии

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

Но в момент разработки даже самые опытные программисты не могут достоверно предсказать, что будет переиспользоваться ещё где‑то, а что нет. Например, в примере выше можно заранее вычленить «Почтальона» в отдельный класс, а потом у бизнеса сменятся цели и «Догонятель» не будет написан никогда. А «Почтальон» и 50 других хорошо написанных, но избыточных классов станут тяжёлой ношей, замедляющей разработку, воруя время на практические задачи.

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

Логично, что есть тот, кто занимается отправкой писем. Изначально это был sendgrid, потом сделали свой класс. Я тоже сделал бы свой класс, но эмулировал АПИ sendgrid, чтобы потом автозаменой сменить имя класса во всех скриптах.

Но более странным я считаю наличие метода «отправить пользователю ссылку на курс» у класса «Заказ», который не занимается отправкой чего‑либо кому‑либо. В моём понимании «Заказ» внутри себя хранит: номер, дату, статус, покупатель, выбранный товар, доставку, оплату и другую полезную информацию. И его задача работать с тем, что внутри него, а не слать что‑то наружу.

Можно у класса Покупатель сделать метод «отправить письмо» или просто «отправить» и туда передать что‑то при вызове.

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

А можно в скобках указывать английские термины? Мне ни о чём не говорит «зацепление», «связность» и «заменяемость», но есть стойкое ощущение, что английский аналог знаком.

По теме: мне кажется, надо убирать дублирующийся код тогда, когда он появляется или когда есть чёткое видение проекта, в котором точно будут эти классы. Строить такую структуру заранее — избыточно. Она может никогда не понадобиться, а силы и время на её создание уже потрачены.

Цель рубрики — обсуждение вопросов дизайна, веб-разработки, переговоров, редактуры и управления.
Комментарии модерируются. Мы публикуем комментарии, которые добавляют к уже сказанному новые мысли и хорошие примеры.

Рекомендуем другие советы