x
 
Фёдор Борщёв
30 апреля 2020
Советы почтой каждую неделю
Пожалуйста, получите наше письмо, чтобы подтвердить свой адрес:
Вы подписаны на «Советы за неделю»:

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


Аккуратный код

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

Аккуратный код

Решим задачу «в лоб», рас­ши­рив класс заказа:

from app.mailman import Mailman
from app.integrations import Zoom
​
class ZoomOrder(Order):
  def ship(self, zoom_webinar_id):
    self.register_in_zoom_room(zoom_course_id)
​
  def register_in_zoom_room(self, zoom_webibar_id):
    zoom_client = new Zoom(customer=self.customer, webinar_id=zoom_webinar_id)
    link = zoom_client.register()
    self.save_link_to_db(link)
​
    mailman = Mailman(customer)
    mailman.deliver(subject='ссылка на веби­нар', content=f'Вот: {link}')

Новому классу нужно знать, на какой веби­нар реги­стри­ро­вать поль­зо­ва­теля, и мы не при­ду­мали для этого ничего лучше, чем доба­вить обя­за­тель­ный пара­метр в метод ship(). Полу­ча­ется, что ZoomOrder не сов­ме­стим с обыч­ным Order, а зна­чит, во всех местах, кото­рые фор­ми­руют заказ, при­дётся делать при­мерно так:

order = Order(customer)
​
if type == 'zoom':
  order.ship(zoom_webinar_id=request.data['ZoomWebinarId'])
else:
  order.ship()

Такой код при­дётся искать и пере­пи­сы­вать вся­кий раз, когда у нас будет добав­ляться новый вид това­ров. Это и есть нару­ше­ние прин­ципа заменяемости:

Код, кото­рый исполь­зует базо­вый класс, дол­жен иметь воз­мож­ность исполь­зо­вать любой его под­класс без изме­не­ний

Давайте сде­лаем новый сфо­ку­си­ро­ван­ный базо­вый класс «Товар» с двумя спе­ци­фич­ными наслед­ни­ками — «Лек­цией» и «Веби­на­ром». Пусть каж­дый наслед­ник несёт в себе всю биз­нес‑логику, кото­рая вклю­ча­ется, когда его купили:

class Item:
  """Абстракт­ный товар"""
  def __init__(self, request):
    self.request = request
​
  def ship(self):
    """Что слу­ча­ется после про­дажи"""
    pass
​
class Lecture(Item):
  def ship(self):
    mailman = Mailman(self.request.customer)
    mailman.deliver(subject='Вот ваша лек­ция', content='Удачи в про­слу­ши­ва­нии')
​
class ZoomWebinar(Item):
  def ship(self):
    zoom_client = new Zoom(customer=self.request.customer, webinar_id=self.request.data['zoom_webinar_id'])
    ...
    mailman.deliver(subject='ссылка на веби­нар', content=f'Вот: {link}')

Оба класса реа­ли­зуют стан­дарт­ный интер­фейс, поэтому код для запи­сей и веби­на­ров выгля­дит одинаково:

item = Lecture(request)  # отпра­вить запись лек­ции
item.ship()
​
item = ZoomWebinar(request)  # заре­ги­стри­ро­вать на веби­нар
item.ship()

Когда каж­дый товар сам знает, что делать после про­дажи, класс заказа ста­но­вится более сфо­ку­си­ро­ван­ным. Его задача теперь — опре­де­лить, что поло­жили в кор­зину, и офор­мить продажу:

class Order:
  def __init__(self, request):
    self.request = request
​
  @property
  def item(self):
    if 'zoom_webinar_id' in self.request:
      return ZoomWebinar(request)
​
    return Lecture(request)
​
  def ship(self):
    self.item.ship()

У нас полу­чи­лась устой­чи­вая к изме­не­ниям система, в кото­рую легко можно доба­вить любую дру­гую веби­нар­ную пло­щадку или даже целый класс това­ров, к при­меру доставку книг или под­писку на плат­ную рассылку.

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

Поделиться
Отправить

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

Мы публикуем комментарии, которые добавляют к уже сказанному новые мысли и хорошие примеры. Мы ожидаем, что такие комментарии составят около 20% от общего числа.

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

Вот такой веб 2.0.

Как быть разработчикам, которые хотят получать больше денег, но не хотят разбираться в бизнесе? Что делать, если меня, технического директора, потихоньку отстраняют от дел? Есть СТО, он классный, но при этом редко выходит в свет. Насколько, на твой взгляд, это может быть важно или полезно компании в целом? Как быть, если всё моё время уходит на разработку всё новых и новых фич? 1




Недавно всплыло

1 Что должен знать дизайнер об интерлиньяжах, полях, отступах, кеглях, выравниваниях и модульных сетках? 9 Как разделать папайю 2 Что такое профессиональная этика? 5