Аккуратный код
Зацепление
Аккуратный код
Зацепление
Сложнейший вопрос в объектно‑ориентированном программировании — как правильно структурировать новый код в приложении: положить в отдельный класс или просто дописать в конце существующего? Если долго складывать код неправильно — кодовая база деградирует. Чтобы правильно определять место для нового кода, нужно изучить несколько понятий — зацепление, связность и заменяемость. В этом совете поговорим о зацеплении.
Зацепление — это мера того, насколько класс в коде зависит от соседних классов. Чем больше класс зависит от соседей, тем выше зацепление. Чем зацепление ниже — тем легче читать и дорабатывать код.
Представим бэкенд для магазина, который продаёт электронные курсы. В коде у него есть класс «Заказ», у которого есть метод «отправить пользователю ссылку на курс». Метод использует электронную почту, но должен ли он работать с почтовым провайдером напрямую? Давайте попробуем:
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. Это был совет об управлении разработкой. Хотите больше знать о планировании спринтов, управлении продуктом или о настройке инфраструктуры? Присылайте вопросы.