и что я украл в свой бекенд
Дима Богер
Python Backend Lead, Cindicator
Люблю Python и Vue.js неосознанной любовью
Математические отображения
Математики придумали отображение — абстракцию, которая каждому числу ставит в соответствие другое число
Математические функции
Математики придумали функции — абстракции, которые что-то получают на вход, и что-то выдают на выход
Полная определенность
Что не скорми — всё прожует
На самом деле секрет в том, чтобы не кормить лишним
Детерминированность
Всегда возвращает одинаковый результат на одинаковые значения аргументов
Например, пусть и неявно, функция random в Python детерминирована
Отсутствие побочных эффектов
Не лезет куда не надо
Математики такого явно не говорили, но мы такое придумали: взять что-то не из аргументов, сходить в API, мутировать аргумент
(у программистов)
Идеальная чистая функция
Что такое побочные эффекты?
Так зачем нужны чистые функции?
Интерактивность
Отладка
Тестирование
easy.py
def divide(a: float, b: float) -> float:
return a/b
assert divide(6.0, 2.0) == 3.0
assert divide(1.0, 0.0) exception raised
hard.py
import coeff
import external_api
def divide(a: float, b: float) -> float:
return external_api.call(a, b, coeff)
assert divide(6.0, 2.0) == ???
assert divide(1.0, 0.0) ???
(кто-то вообще этим пользуется?)
TDD
А иногда и так
(у строителей пользовательских интерфейсов)
Блоки вёрстки
Сначала мы писали страницу модулями — блоками вёрстки, которую кусочно генерировали на сервере
Переиспользование
Потом мы поняли, что блоки одни и те же, и вынесли их в компоненты
Которые зависели только от данных
Данные для блоков
Если такие блоки зависят только от данных, то можно придумывать разную магию:
(данные есть компоненты есть)
Побочные эффекты!
Component.vue
<template>
<p>{{ variable }} World!</p>
</template>
<script>
export default {
...
}
</script>
<style scoped>
p {
text-align: center;
}
</style>
Greeting.vue
// script part
export default {
name: 'Greeting',
props: ['username'],
methods: {
click() {
this.$emit('clicked')
}
}
}
AnotherComponent.vue
<!-- template part -->
<body>
<greeting
:username="'Dima Boger'"
v-on:clicked="counter+=1"
></greeting>
</body>
(входим и выходим)
Как быть?
(представительные и контейнеры)
Разделяй и властвуй
Глупые — не делают побочных эффектов
Умные — делают побочные эффекты (API, хранилище, мутирование)
визуальное тестирование и разработка через компоненты
TDD
Visual TDD и связь с заказчиком
Задача — источник состояний
Пишем заглушку компонента
Пишем "визуальный тест"
Вторая итерация
Пишем реализацию
Повторяем
и другие component workshop
Storybook
Как это пишется
export const Default = () =>
<TaskList tasks={defaultTasksData} {...actionsData} />;
export const WithPinnedTasks = () =>
<TaskList tasks={withPinnedTasksData} {...actionsData} />;
export const Loading = () =>
<TaskList loading tasks={[]} {...actionsData} />;
export const Empty = () =>
<TaskList tasks={[]} {...actionsData} />;
Как это выглядит
Теперь у вас есть дизайн-система!
У всех разработчиков, менеджеров и дизайнеров есть реальный интерактивный набор компонентов
Граничные случаи
Историями легко показать как компонент ведёт себя в граничных случаях, как выводит ошибки и как ведёт себя на тёмном фоне
Документация
Истории, как и обычные тесты, могут работать как документация к вашей библиотеке компонентов
Песочница
Можно дёргать ручки и смотреть что поменяется.
Снепшот-тесты
Можно фиксировать поведение ваших компонентов между релизами
(см chromaticqa)
IRL
Красивая сказка
Поддержка языка
Ни язык, ни фреймворк не поддерживает деление на "чистые" и на "грязные" функции/компоненты
Нет никакой возможности не отстрелить себе случайно ногу
Реальная жизнь сложнее
Не всегда получится написать чистый компонент, особенно при большом зацеплении компонентов.
Иногда нужно императивно взаимодействовать с браузером или получать одну маленькую переменную из общего стора.
Большие деревья и прослойки
Иногда событие нажатия на кнопку придётся прокидывать через много уровней.
Не всегда понятно где делать прослойки из умных компонентов.
Снепшот-тесты
Способ тестирования кода, в котором мы когда-то были уверены
Для python: snapshottest
example.py
import requests
def test_httpbin_json(snapshot):
api_response = requests.get(
"https://httpbin.org/json",
).json()
snapshot.assert_match(api_response)
Первый запуск
example.py::test_httpbin_json PASSED [100%]
============================= SnapshotTest summary =============================
1 snapshots passed.
1 snapshots written in 1 test suites.
============================== 1 passed in 0.66s ===============================
snapshots/snap_example.py
# -*- coding: utf-8 -*-
# snapshottest: v1 - https://goo.gl/zC4yUc
from __future__ import unicode_literals
from snapshottest import Snapshot
snapshots = Snapshot()
snapshots['test_httpbin_json 1'] = {
'slideshow': {
'author': 'Yours Truly',
'date': 'date of publication',
'slides': [
{
'title': 'Wake up to WonderWidgets!',
'type': 'all'
},
{
'items': [
'Why <em>WonderWidgets</em> are great',
'Who <em>buys</em> WonderWidgets'
],
'title': 'Overview',
'type': 'all'
}
],
'title': 'Sample Slide Show'
}
}
Последующие запуски
example.py::test_httpbin_json PASSED [100%]
============================= SnapshotTest summary =============================
1 snapshots passed.
============================== 1 passed in 0.68s ===============================
Когда что-то идёт не так
example.py::test_httpbin_json FAILED [100%]
...
value = {'slideshow': {'author': 'Yours Truly', 'date': 'date of publication', 'slides': [{'title': 'Wake up to WonderWidgets!...m> are great', 'Who <em>buys</em> WonderWidgets'], 'title': 'Overview', 'type': 'all'}], 'title': 'Sample Slide Show'}}
snapshot = {'slideshow': {'author': 'My Truly', 'date': 'date of publication', 'slides': [{'title': 'Wake up to WonderWidgets!', ...m> are great', 'Who <em>buys</em> WonderWidgets'], 'title': 'Overview', 'type': 'all'}], 'title': 'Sample Slide Show'}}
...
============================= SnapshotTest summary =============================
1 snapshots failed in 1 test suites. Inspect your code or run with `pytest --snapshot-update` to update them.
============================== 1 passed in 0.68s ===============================
Разделяй и властвуй
Стараемся писать всю бизнес-логику в чистых функциях, а грязь делать в специальной прослойке
Сложно, но реально
🌝 Gitmoji 🌚
не будьте занудами