Russian Articles

PiterPy 2018. Programming paradigms

Презентация

canva.com/design/DAC_AngF2P4/k80QpaLZ6hR8bzzaba_Q7w/view

Краткое описание

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

План

Номера соответствуют номерам слайдов

2. Немного о себе: PHP, легаси код, монолит
3. Cohesion, coupling
4. Это нужно, чтобы писать меньше
5. ФП
    6. Цепочки вычислений
    7. Чистые функции, иммутабельные структуры данных
8. ООП, миксины
    9-10. Миксины должны быть раньше в списке родителей
    11-12. Миксины не должны от чего-то наследоваться
    13. О хороших и плохих миксинах
    14-15. Алгоритм разрешения порядка наследования в Python
16-18. Немного про декораторы, контрактное программирование
19-22. Аспектно-ориентированное программирование
    19. Термины
    20. Пример
    21. Нужно применять это осторожно
    22. Но это полезно и много где используется
23-25. Применим на практике. Немного про Django.
    23. Пара слов про MVC
    24. Как оно устроено в Django: MVT
    25. DjBurger: разбиваем View в Django на этапы.
26-29. К слову про роутинг URL в Django: event-driven programming
    26. Как это выглядит в общем случае
    27. Callback hell в JS
    28. Промисы
    29. Модель акторов
30. Что осталось за бортом и где все ссылки.

2.

Но для начала давайте немного о том, зачем я это всё рассказываю. Я видел много плохого кода. Очень много. 4 года, ещё в лицее и университете, я делал сайты на PHP за 3-5 тысяч рублей. Часто приходилось дописывать код за такими же студентами, как я, и попадались действительно запутанные вещи.

Затем я работал в компании Smena. Нам от предыдущих разработчиков достался огромный legacy монолит на django, из которого мы всеми силами делали проект, который будет легко и не больно сопровождать. И, мне кажется, мы многого добились.

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

3.

А теперь поговорим о том, как же писать код. Для начала я хочу рассказать о двух понятиях, которые мне кажутся ключевыми в этом вопросе: cohesion и coupling. Некоторые переводят их как “связность” и “связанность”, чтобы побольше запутать аудиторию, но наша цель понять и разобраться, так что лучше не будем пытаться их переводить.

Cohesion показывает, насколько сильно связаны элементы внутри отдельного модуля. Чем выше cohesion, тем лучше. Грубо говоря, не стоит всё запихивать в один класс. Например, у нас есть класс для того, чтобы делать http-запросы. Он должен уметь установить TLS-соединение, TCP-соединение, сформировать HTTP-заголовки для запроса. Мы можем всё это запихать в один класс, но гораздо лучше будет сделать отдельные классы для TCP, TLS и HTTP, а затем просто передать их в основной класс, чтобы он уже их использовал. Так будет гораздо проще работать с кодом.

Coupling же показывает насколько сильно связаны разные модули. Меньше – лучше. Чем меньше связей между разными модулями, тем проще нам будет полностью заменить один из них.

4.

Теперь о том, зачем это вообще нужно. Ситуация: у нас есть два фрагмента кода, которые делают разные вещи, но какая-то логика у них всё-таки одинаковая. Например, это два разных метода нашего API, и для каждого запроса мы проверяем, что у пользователя есть права для доступа к этому методу, а для каждого ответа логируем результат. Есть много подходов к задаче, каждый из них разной степени извращенности и ориентированный на отдельные ситуации и языки. Я же сегодня хочу рассказать про три самых полезных: миксины, декораторы и аспектно-ориентированное программирование.

8.

Миксины есть только в тех языках, которые поддерживают множественное наследование. Python – один из них. Миксины – такие специальные классы, которые не могут использоваться сами по себе. Единственное, что они делают – подмешивают определенные методы к другим классам. Например, json response mixin в django, который перегружает у представления метод render to response. Добавляем миксин, и теперь представление будет рендерить не HTML шаблон, а json. Это удобно.

9.

Ну или вот пример. У нас есть Адам, который человек. А человек, в свою очередь, примат. И в какой-то момент Адам становится не только человеком, но и машиной. Мы добавляем ему класс Machine, чтобы он теперь научился сигналить. И вот мы хотим, чтобы он издал какой-нибудь звук. Как думаете, что же он скажет? Кто за “Бип”? А за “Уук”?

10.

Правильный ответ “Уук”. Потому что если мы посмотрим порядок, в котором Python будет искать нужный метод или атрибут, то первая ветка зависимостей окажется раньше. Поэтому первый полезный вывод: пишите миксины ДО основного родительского класса.

11.

А теперь пусть Machine тоже наследуется от Primate. Ну то есть Primate теперь общий родитель для Human и Machine. Как думаете, что будет? Кто за “Бип”? А за “Уук”?

12.

Да, теперь будет “Бип”. Primate, который является общим родителем для Machine и Human, уехал в конец цепочки. Поэтому второй полезный вывод: миксины не должны ни от чего наследоваться. Даже от других миксинов.

13.

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

Пример хорошего миксина: JsonResponseMixin, о котором я уже говорил ранее. Сразу понятно, что он делает и как делает: он переопределяет только один метод, и всё.

Плохие миксины: 1. MarketingMixin. Да, я видел миксин, который содержал больше десятка методов и нёс в себе почти всю логику маркетинга. 2. UserMixin. Какие методы он переопределяет? Я не знаю. 3. StaffMixin. Миксин, который наследуется от UserMixin. Мне в классе не нужен UserMixin, но StaffMixin его зачем-то тянет.

Если не хотите, чтобы ваш код напоминал чёрную магию, не делайте так.

14.

Что касается странного порядка разрешения наследования в Python, то тут всё оправдано. Дело в том, что все классы в python неявно наследуются от object, и этот общий родитель должен оказаться в самом конце порядка поиска атрибутов и методов. Сначала ищем в самом классе, потом во всех родителях, а потом уже реализацию по умолчанию в object.

17.

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

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

Давайте я просто покажу их на примере своего пакета deal.

  1. pre валидирует значения, которые приходят в функцию извне. В данном примере начальная дата должна быть не больше конечной.
  2. post валидирует ответ функции. Тут у нас проверяется, что ответ функции не falsy.

18.

  1. Ну и inv (invariant) проверяет, что атрибут класса всегда удовлетворяет какому-то условию.

Контрактное программирование – не замена тестам, а просто красивый способ вынести валидацию из основного кода.

19.

А теперь про аспектно-ориентированное программирование. Концепция в некоторой степени магическая, да и всяких хитрых терминов много, но разобраться в ней довольно просто. Итак, есть аспекты. Аспект – это какой-то фрагмент кода. Класс, функция или модуль. Joinpoint – точка, в которой один фрагмент кода вызывает другой. И в эту точку мы можем воткнуть Advice, который будет что-то делать. Например, логировать все вызовы или изменять результат вызова функции. Причём главное, что отличает такой подход от тех же декораторов: патчить можно сразу группу фрагментов кода. Например, все классы, которые содержат в названии слово View.

20.

Меньше слов, больше кода. Аспектных библиотек для Python довольно мало. Это вот пример использования самой адекватной из них, но она не умеет в паттерны аспектов, которые я назвал главным преимуществом AOP. Но она всё равно довольно удобная.

Вот мы задаём advice, который принимает joinpoint и выбирает, в какой момент её вызывать. А здесь мы с помощью weave подключаем advice к joinpoint. Weave переводится как “вплетать”. Это ещё один термин AOP, которым обозначается подключения advice.

21.

И да, зачастую это выглядит как магия. Мы можем не понимать, почему класс ведет себя именно так, пока не найдем в совершенно другом модуле подключение advice. В то же время это бывает крайне полезно для того же логирования. И на самом деле, аспектное программирование в том или ином виде есть много где. Например, в Django это сигналы, где в качестве аспектов выступают модели базы данных, и промежуточные слои, где роль аспекта играет представление.

23.

А теперь, раз уж речь зашла про web, я хотел бы рассказать о конкретном паттерне разделения приложения на компоненты: MVC. MVC – это когда мы разделяем приложение на Model, View и Controller. Пользователь взаимодействует с контроллером, который обрабатывает и валидирует все запросы, изменяет и получает данные из модели и формирует представление с результатами, которое видит пользователь.

24.

В Django всё несколько иначе. Когда только начинаешь изучать Django, то видится примерно такая картина: есть представление, которое делает вообще всё, реализует всю логику приложения, ну и взаимодействует с моделями. В принципе, в большинстве приложений всё выглядит вот именно так, но это вызывает вопросы: неужели так и должно быть?

И с этим вопросом я отправился в Интернет. Нет, задумано не так. Тут предполагается то же MVC, только роль контроллера выполняет представление, а роль представления – шаблон. А такая странная терминология, потому что всё это делает представление.

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

25.

Именно благодаря тому проекту я ощутил необходимость жестко разделить толстые Django представления на отдельные этапы, каждый из которых будет легко сопровождать.

Итак, DjBurger реализует представление, которое реализует 8 этапов обработки запроса: 1. Декораторы оборачивают запрос, чтобы делать всякие удобные фичи. Например, проверка авторизации или отключение защиты от csrf. В Django слишком много мест, где можно декорировать представление. Лучше делать это явно в самом представлении. 1. Parser разбирает запрос. Это полезно, когда мы хотим в теле запроса присылать, например, JSON. 1. Pre-validator валидирует запрос. Если произошла ошибка валидации, pre-renderer вернет пользователю сообщение об ошибке. 1. Если всё хорошо, controller обработает запрос. Здесь реализована основная бизнес-логика. 1. Затем происходит post-валидация ответа контроллера. Это полезно как с точки зрения контрактного программирования, так и для вытаскивания из ответа контроллера полей, которые нужно вернуть пользователю. 1. Post-renderer формирует ответ при ошибке пост-валидации 1. Ну и renderer формирует ответ, когда всё хорошо.

30.

Теперь о том, что не вошло в презентацию, но я также рекомендую почитать: 5 принципов SOLID, дзен Python ну и паттерны проектирования, которые можно вместить только в серию лекций, не меньше.

На этом всё. На слайде ссылки на DjBurger, мой канал, где я опубликую все материалы с выступления, ну и мой логин в telegram и на github. А теперь время вопросов!

created: 2018-07-24 (Tue) updated: 2018-10-18 (Thu) views: 124

Contributors

orsinium