# PiterPy 2018. Programming paradigms ## Презентация [canva.com/design/DAC_AngF2P4/k80QpaLZ6hR8bzzaba_Q7w/view](https://www.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. 3. Ну и `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. А теперь время вопросов!