Как фронтендеры изобретали чистые функции и TDD

и что я украл в свой бекенд

Максим, выздоравливай!

История меня

Дима Богер

Python Backend Lead, Cindicator

  • Организатор PiterPy Meetup
  • Тимлид
  • Иногда преподаю


Люблю 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) ???

Почему нельзя писать только чистые функции?

Постоянно нужно делать грязные вещи

Test Driven Development

(кто-то вообще этим пользуется?)

TDD

А иногда и так

История компонентного подхода

(у строителей пользовательских интерфейсов)

Эволюция

Блоки вёрстки

Сначала мы писали страницу модулями — блоками вёрстки, которую кусочно генерировали на сервере

Переиспользование

Потом мы поняли, что блоки одни и те же, и вынесли их в компоненты

 

Которые зависели только от данных

Данные для блоков

Если такие блоки зависят только от данных, то можно придумывать разную магию:

  • способы передачи этих данных;
  • хранилища;
  • способы синхронизации;

А в чём проблема?

(данные есть компоненты есть)

Побочные эффекты!

Vue.js компоненты

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, хранилище, мутирование)

Visual TDD и CDD

визуальное тестирование и разработка через компоненты

TDD

Visual TDD и связь с заказчиком

Задача — источник состояний

Пишем заглушку компонента

Пишем "визуальный тест"

Вторая итерация

Пишем реализацию

Повторяем

Storybook

и другие 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 🌚

Изучайте новое

не будьте занудами

Спасибо, Олег

Вопросы?

  • Меня зовут Дима
  • Я рассказывал про
    • свойства математических функций
    • свойства чистых функций
    • TDD
    • глупые и умные компоненты
    • Visual TDD
    • Storybook
    • Снепшот-тесты в питоне
    • Чистое-грязное в питоне
    • gitmoji

Использованные материалы

Использованные материалы

Дополнительные материалы

Made with Slides.com