Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Experiments.Kotlin.DumpConf

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

 Experiments.Kotlin.DumpConf

Avatar for Aleksandr Tarasov

Aleksandr Tarasov

April 18, 2018
Tweet

More Decks by Aleksandr Tarasov

Other Decks in Programming

Transcript

  1. Мнение докладчика может не совпадать с официальной позицией его работодателя,

    начальника, коллег или других специалистов. 
 Все представленные в докладе сведения, примеры, выводы и другую информацию вы можете использовать на свой страх и риск. За все ваши действия ответственность несёте только вы сами. В докладе будет код! 3 Минздрав предупреждает
  2. 8 71 млн Посещают нас в месяц 8000 Серверов Одноклассники

    в цифрах 11 тыс Выполненных экспериментов
  3. • Аватарок в WebP нет • в браузере пользователей •

    в кэшах CDN-а • во внутренних кэшах 18 Нет, потому что
  4. • Аватарок в WebP нет • в браузере пользователей •

    в кэшах CDN-а • во внутренних кэшах • Нужно потратить • трафик на скачивание • ресурсы CPU и памяти для нарезки 19 Нет, потому что
  5. • Затем, что есть риски • Технические риски • Продуктовые

    риски • Запуск новых фич происходит поэтапно • Наполнение кэшей • А/B/…/Z-тестирование 24 Зачем нужны эксперименты?
  6. • Затем, что есть риски • Технические риски • Продуктовые

    риски • Запуск новых фич происходит поэтапно • Наполнение кэшей • А/B/…/Z-тестирование • Процесс запуска фичи называется экспериментом 25 Зачем нужны эксперименты?
  7. 30 Эксперимент в деталях Code Branches if / switch Config

    by partition Logs/ Monitoring observe by server by device
  8. 31 Эксперимент в деталях Code Branches if / switch Config

    by partition Logs/ Monitoring observe by server alerts by device
  9. 32 Убери за собой Code Branches if / switch Config

    by partition Logs/ Monitoring observe by server alerts by device
  10. 39 Казалось бы что здесь сложного? one partition //step#1 app.avatar.shouldUseWebP:

    0 //step#2 app.avatar.shouldUseWebP: 0-63 //step#3 app.avatar.shouldUseWebP: 0-255 several partitions all partitions
  11. • Релизный цикл • Нужно вспоминать, где и какие настройки

    нужно включить/прописать • Хранение экспериментов • Разные нотации обозначения настроек • Отдельный тип задач в Jira 43 Проблемы
  12. • Релизный цикл • Нужно вспоминать, где и какие настройки

    нужно включить/прописать • Хранение экспериментов • Разные нотации обозначения настроек • Отдельный тип задач в Jira • Запуск экспериментов • руками через UI • комментарий в Jira • сообщение в ТамТам 44 Проблемы
  13. 48 Схема запуска эксперимента посмотреть что там в jira посмотреть,

    а что в PMS написать в чатик написать комментарий
  14. 49 Схема запуска эксперимента посмотреть что там в jira посмотреть,

    а что в PMS написать в чатик написать комментарий внести изменения
  15. 50 Схема запуска эксперимента посмотреть что там в jira посмотреть,

    а что в PMS написать в чатик написать комментарий внести изменения посмотреть графики
  16. 51 Схема запуска эксперимента посмотреть что там в jira посмотреть,

    а что в PMS написать в чатик написать комментарий внести изменения посмотреть графики
  17. • Консистентность настроек между средами • Непонятно в какой стадии

    эксперимент • Забыли написать комментарий? 57 Проблемы #2
  18. • Консистентность настроек между средами • Непонятно в какой стадии

    эксперимент • Забыли написать комментарий? • Тяжело возвращаться после некоторого времени • Это как возвращаться к давно написанному коду 58 Проблемы #2
  19. • Описание эксперимента в виде некоторого DSL • Храним в

    git-е как код • Автоматизированный накат/откат настроек • Запускаем через CLI • Используем API системы хранения конфигурации 61 Чего бы хотелось?
  20. • Описание эксперимента в виде некоторого DSL • Храним в

    git-е как код • Автоматизированный накат/откат настроек • Запускаем через CLI • Используем API системы хранения конфигурации • Автоматизация сопутствующих действий • Постим комментарии в Jira, TamTam 62 Чего бы хотелось?
  21. 65 Схемка с автоматизацией эксперимента запустить скрипт проверка в PMS

    посмотреть графики комментарии в чат и jira изменения в PMS
  22. • Быстро делать прототипы по автоматизации • Три универсальных модуля

    • sh • uri • template 70 Ansible - почти идеальный кандидат
  23. • Быстро делать прототипы по автоматизации • Три универсальных модуля

    • sh • uri • template • Готовый DSL, механизмы запуска, ограничений и т.д. 71 Ansible - почти идеальный кандидат
  24. 72 Описание эксперимента --- - include: step1.yml tags: - step1

    - include: step2.yml tags: - step2 … - include: stepN.yml tags: - stepN
  25. 73 Описание эксперимента --- - include: step1.yml tags: - step1

    - include: step2.yml tags: - step2 … - include: stepN.yml tags: - stepN //step#1 app.avatar.shouldUseWebP: 0 //step#2 app.avatar.shouldUseWebP: 0-63 //step#3 app.avatar.shouldUseWebP: 0-255
  26. 74 Каждый шаг имеет своё описание --- - hosts: local

    gather_facts: no tasks: - name: Init include_role: name: pms vars: step_definition: "проинициализирую настройки" application_name: odnoklassniki-web properties: useWebP: name: "app.avatar.useWebP" value: "" anotherProperty: name: "app.avatar.anotherProperty" value: "" with_items: - "{{ host_common }}" loop_control: loop_var: host_name --- - include: step1.yml tags: - step1 - include: step2.yml tags: - step2 … - include: stepN.yml tags: - stepN
  27. • Есть описание в виде DSL • Храним в git-е

    как код • Можем запустить из командной строки 77 Итоги #1
  28. • Есть описание в виде DSL • Храним в git-е

    как код • Можем запустить из командной строки • Убрали рутинные действия 78 Итоги #1
  29. 81 Проблема копипаста. Step#1 --- - hosts: local gather_facts: no

    tasks: - name: Init include_role: name: pms vars: step_definition: "проинициализирую настройки" application_name: odnoklassniki-web properties: useWebP: name: "app.avatar.useWebP" value: "" anotherProperty: name: "app.avatar.anotherProperty" value: "" with_items: - "{{ host_common }}" loop_control: loop_var: host_name
  30. 82 Проблема копипаста. Step#2 --- - hosts: local gather_facts: no

    tasks: - name: Init include_role: name: pms vars: step_definition: "на одну партицию" application_name: odnoklassniki-web properties: useWebP: name: "app.avatar.useWebP" value: "63" anotherProperty: name: "app.avatar.anotherProperty" value: "63" with_items: - "{{ host_common }}" loop_control: loop_var: host_name
  31. 83 Проблема копипаста. Step#N --- - hosts: local gather_facts: no

    tasks: - name: Init include_role: name: pms vars: step_definition: "на всех" application_name: odnoklassniki-web properties: useWebP: name: "app.avatar.useWebP" value: "0-255" anotherProperty: name: "app.avatar.anotherProperty" value: "0-255" with_items: - "{{ host_common }}" loop_control: loop_var: host_name
  32. • Нужно писать модули • рекомендуется на питоне • я

    не знаю питон :) 85 Проблемы прототипа
  33. • Нужно писать модули • рекомендуется на питоне • я

    не знаю питон :) • Обобщённый DSL это хорошо для решения общих задач • частное решение позволяет писать компактнее и выразительнее 86 Проблемы прототипа
  34. • Нужно писать модули • рекомендуется на питоне • я

    не знаю питон :) • Обобщённый DSL это хорошо для решения общих задач • частное решение позволяет писать компактнее и выразительнее • Не Java • просто так не воспользоваться готовыми библиотеками • мы любим Java! 87 Проблемы прототипа
  35. 90 DSL Design. Список шагов эксперимента steps { empty("off") {}

    user("onebot") { bot(prop("botId")) } partition("partition") { part("0") } partition("quarter") { part("0-63") } partition("half") { part("0-63") part("64-127") } partition("all") { part("0-255") } }
  36. 91 Инициализация/Отключка/Откат в исходное состояние steps { empty("off") {} user("onebot")

    { bot(prop("botId")) } partition("partition") { part("0") } partition("quarter") { part("0-63") } partition("half") { part("0-63") part("64-127") } partition("all") { part("0-255") } }
  37. 92 Открываем на бота steps { empty("off") {} user("onebot") {

    bot(prop("botId")) } partition("partition") { part("0") } partition("quarter") { part("0-63") } partition("half") { part("0-63") part("64-127") } partition("all") { part("0-255") } }
  38. 93 Открытие по партициям steps { empty("off") {} user("onebot") {

    bot(prop("botId")) } partition("partition") { part("0") } partition("quarter") { part("0-63") } partition("half") { part("0-63") part("64-127") } partition("all") { part("0-255") } }
  39. 94 Ура! Теперь для всех steps { empty("off") {} user("onebot")

    { bot(prop("botId")) } partition("partition") { part("0") } partition("quarter") { part("0-63") } partition("half") { part("0-63") part("64-127") } partition("all") { part("0-255") } }
  40. 95 DSL Design. А что будем менять то? steps {

    empty("off") {} user("onebot") { bot(prop("botId")) } partition("partition") { part("0") } partition("quarter") { part("0-63") } partition("half") { part("0-63") part("64-127") } partition("all") { part("0-255") } }
  41. 96 Настройки. Отделяем мух от котлет experiment { steps {

    empty("off") {} user("onebot") { bot(prop("botId")) } partition("partition") { part("0") } partition("quarter") { part("0-63") } partition("half") { part("0-63") part("64-127") } partition("all") { part("0-255") } } properties("odnoklassniki-web", prop("hosts")) { property("app.avatar.shouldUseWebP") property("app.avatar.anotherProperty") } }
  42. 97 Step#1 experiment { steps { empty("off") {} user("onebot") {

    bot(prop("botId")) } partition("partition") { part("0") } partition("quarter") { part("0-63") } partition("half") { part("0-63") part("64-127") } partition("all") { part("0-255") } } properties("odnoklassniki-web", prop("hosts")) { property("app.avatar.shouldUseWebP") property("app.avatar.anotherProperty") } }
  43. 98 Step#N experiment { steps { empty("off") {} user("onebot") {

    bot(prop("botId")) } partition("partition") { part("0") } partition("quarter") { part("0-63") } partition("half") { part("0-63") part("64-127") } partition("all") { part("0-255") } } properties("odnoklassniki-web", prop("hosts")) { property("app.avatar.shouldUseWebP") property("app.avatar.anotherProperty") } }
  44. 99 Step#N experiment { steps { ... partition("quarter") { part("0-63")

    } ... } properties("odnoklassniki-web", prop("hosts")) { property("app.avatar.shouldUseWebP") property("app.avatar.anotherProperty") } } app.avatar.shouldUseWebP: 0-63 app.avatar.anotherProperty: 0-63
  45. 100 Компактно и понятно experiment { steps { empty("off") {}

    user("onebot") { bot(prop("botId")) } partition("partition") { part("0") } partition("quarter") { part("0-63") } partition("half") { part("0-63") part("64-127") } partition("all") { part("0-255") } } properties("odnoklassniki-web", prop("hosts")) { property("app.avatar.shouldUseWebP") property("app.avatar.anotherProperty") } }
  46. • Java - знаем, любим, но вербозно • кто-то пишет

    скриптинг на Java? 102 Что же выбрать для реализации?
  47. • Java - знаем, любим, но вербозно • кто-то пишет

    скриптинг на Java? • JVM - очень здорово • интероп с уже написанными библиотеками • знакомая платформа 103 Что же выбрать для реализации?
  48. • Groovy, JRuby, Javascript, … • синтаксический сахар • JSR223

    • Но мы хотим: • в идеале писать скрипты и их обработку на одном языке • иметь статическую типизацию, • проверки на этапе компиляции • близкую к идеальной поддержку в IDE 105 А что вообще есть то?
  49. • Kotlin потому что: • синтаксический сахар • JSR223 •

    компиляция • статическая типизация • хороший code completion • хотелось попробовать новое и интересное ;) 106 Почему Kotlin?
  50. 108 Как реализовать такой DSL? experiment { steps { empty("off")

    {} user("onebot") { bot(prop("botId")) } partition("partition") { part("0") } partition("quarter") { part("0-63") } partition("half") { part("0-63") part("64-127") } partition("all") { part("0-255") } } properties("odnoklassniki-web", prop("hosts")) { property("app.avatar.shouldUseWebP") property("app.avatar.anotherProperty") } }
  51. 110 Древовидная структура. Код @DslMarker annotation class ElementMarker @ElementMarker abstract

    class Element { val children = arrayListOf<Element>() fun <T : Element> make(element: T, init: T.() -> Unit): T { element.init() children.add(element) return element } }
  52. 111 Init function experiment { steps { // init function

    is called here partition("half"){ // init function is called here } } ... }
  53. 112 Init function @DslMarker annotation class ElementMarker @ElementMarker abstract class

    Element { val children = arrayListOf<Element>() fun <T : Element> make(element: T, init: T.() -> Unit): T { element.init() children.add(element) return element } }
  54. 114 Упрощаем восприятие experiment { steps { // call init

    here partition("half"){ } } ... } fun partition(name: String, init: PartitionStep.() -> Unit) = make(PartitionStep(name), init)
  55. 118 @DSLMarker @DslMarker annotation class ElementMarker @ElementMarker abstract class Element

    { val children = arrayListOf<Element>() fun <T : Element> make(element: T, init: T.() -> Unit): T { element.init() children.add(element) return element } }
  56. 119 С @DSLMarker experiment { steps { steps { }

    } ... } Шаги не могут быть вложенными
  57. 121 80% укладывается в схему experiment { steps { empty("off")

    {} user("onebot") { bot(prop("botId")) } partition("partition") { part("0") } partition("quarter") { part("0-63") } partition("half") { part("0-63") part("64-127") } partition("all") { part("0-255") } } properties("odnoklassniki-web", prop("hosts")) { property("app.avatar.shouldUseWebP") property("app.avatar.anotherProperty") } }
  58. 123 Засахарим немного experiment { steps { openByPartitionSteps() } properties("odnoklassniki-web",

    prop("hosts")) { property("app.avatar.shouldUseWebP") property("app.avatar.anotherProperty") } }
  59. 124 Но дадим немного свободы experiment { steps { openByPartitionSteps(withBot

    = false, withEmployees = false) } properties("odnoklassniki-web", prop("hosts")) { property("app.avatar.shouldUseWebP") property("app.avatar.anotherProperty") } }
  60. 125 Extension Function - это очень удобно fun Steps.openByPartitionSteps(withBot: Boolean

    = true, withEmployees: Boolean = true) { empty("off") {} if (withBot) { user("bot") { bot(prop("botId")) } } if (withEmployees) { user("employees") { bot(betaUsers().joinToString(",")) } } partition("partition") { part("64") } partition("quarter") { part("64-127") } partition("half") { part("0-127") } partition("on") { part("0-255") } }
  61. 126 Copy-Paste fun Steps.openByPartitionSteps(withBot: Boolean = true, withEmployees: Boolean =

    true) { empty("off") {} if (withBot) { user("bot") { bot(prop("botId")) } } if (withEmployees) { user("employees") { bot(betaUsers().joinToString(",")) } } partition("partition") { part("64") } partition("quarter") { part("64-127") } partition("half") { part("0-127") } partition("on") { part("0-255") } }
  62. 127 Ещё сколько-то там процентов experiment { steps { openByPartitionSteps()

    } properties("odnoklassniki-web", prop("hosts")) { property("app.avatar.shouldUseWebP") } }
  63. 132 Больше "измерений" для мобильной версии experiment { steps {

    openByPartitionSteps() } properties(consts.appMob, consts.commonMobHost) { deviceFeature("app.avatar.shouldUseWebP") { appsOnly = false iphone = "x.x.x" android = "x.x.x" deviceOS = mapOf("iOS" to "x.x.x") } } }
  64. 133 Аналогично для API и приложений experiment { steps {

    openByPartitionSteps() } properties(consts.appApi, consts.commonApiHost) { appaware("app.aware.avatar.shouldUseWebP") { default = true iphone = "x.x.x" android = "x.x.x" } } }
  65. 134 Кастомные свойства experiment { steps { custom("setup", "сделаю преднастройку

    эксперимента") { properties("odnoklassniki-web", prop("hosts")) { property("app.avatar.useWebP.quality", “100") } } openByPartitionSteps() } properties("odnoklassniki-web", prop("hosts")) { property("app.avatar.shouldUseWebP") property("app.avatar.anotherProperty") } }
  66. 135 Кастомные свойства experiment { steps { custom("setup", "сделаю преднастройку

    эксперимента") { properties("odnoklassniki-web", prop("hosts")) { property("app.avatar.useWebP.quality", "100") } } openByPartitionSteps() } properties("odnoklassniki-web", prop("hosts")) { property("app.avatar.shouldUseWebP") property("app.avatar.anotherProperty") } }
  67. 136 Это всё-таки язык программирования custom("42everywhere", "всем по 42") {

    properties("odnoklassniki-web", prop("hosts")) { calcProperty("app.complex.property", { PMS.property("odnoklassniki-web", "app.complex.property") .lines() .map { Converter.SET_STRING.convert(it) .minus("42").plus("42") .joinToString(",") } .joinToString("\n") }) } }
  68. 137 Шаг custom("42everywhere", "всем по 42") { properties("odnoklassniki-web", prop("hosts")) {

    calcProperty("app.complex.property", { PMS.property("odnoklassniki-web", "app.complex.property") .lines() .map { Converter.SET_STRING.convert(it) .minus("42").plus("42") .joinToString(",") } .joinToString("\n") }) } }
  69. 138 Набор изменяемых настроек custom("42everywhere", "всем по 42") { properties("odnoklassniki-web",

    prop("hosts")) { calcProperty("app.complex.property", { PMS.property("odnoklassniki-web", "app.complex.property") .lines() .map { Converter.SET_STRING.convert(it) .minus("42").plus("42") .joinToString(",") } .joinToString("\n") }) } }
  70. 139 Вычисляемая настройка custom("42everywhere", "всем по 42") { properties("odnoklassniki-web", prop("hosts"))

    { calcProperty("app.complex.property", { PMS.property("odnoklassniki-web", "app.complex.property") .lines() .map { Converter.SET_STRING.convert(it) .minus("42").plus("42") .joinToString(",") } .joinToString("\n") }) } }
  71. 140 Обрабатываем построчно custom("42everywhere", "всем по 42") { properties("odnoklassniki-web", prop("hosts"))

    { calcProperty("app.complex.property", { PMS.property("odnoklassniki-web", "app.complex.property") .lines() .map { Converter.SET_STRING.convert(it) .minus("42").plus("42") .joinToString(",") } .joinToString("\n") }) } }
  72. 141 Ну теперь всем по 42 custom("42everywhere", "всем по 42")

    { properties("odnoklassniki-web", prop("hosts")) { calcProperty("app.complex.property", { PMS.property("odnoklassniki-web", "app.complex.property") .lines() .map { Converter.SET_STRING.convert(it) .minus("42").plus("42") .joinToString(",") } .joinToString("\n") }) } }
  73. • Файл с расширением .kts • Имплицитные аргументы • args[0]

    • println(“hello!”) • Запускается через command line • kotlinc -script <script_name> -classpath <> • Запускается с помощью IDE 145 Kotlin Script
  74. 146 Kotlin Script. Ожидание import runner.* runner.run { experiment {

    steps { empty("off") {} ... partition("all") { part("0-255") } } properties("odnoklassniki-web", prop("hosts")) { property("app.avatar.shouldUseWebP") } } }
  75. • Нужно как-то подключить классы проекта • Нужно как-то подключить

    все зависимости проекта 148 Kotlin Script. CMD kotlinc -script <script_name> -classpath <>
  76. • Нужно как-то подключить классы проекта • Нужно как-то подключить

    все зависимости проекта • Gradle не умеет (по крайней мере из коробки) • IDEA не умеет* 149 Kotlin Script. CMD kotlinc -script <script_name> -classpath <>
  77. 151 Kotlin Script. IDEA java -Dfile.encoding=UTF-8 \ -classpath "kotlin-compiler.jar:kotlin-reflect.jar: kotlin-stdlib.jar:kotlin-script-runtime.jar"

    org.jetbrains.kotlin.cli.jvm.K2JVMCompiler -script ~/experiments/src/main/kotlin/one/exps/scripts/XPRM-10222.kts XPRM-10222.kts:1:8: error: unresolved reference: one import one.exps.*
  78. 152 Kotlin Script. IDEA java -Dfile.encoding=UTF-8 \ -classpath "kotlin-compiler.jar:kotlin-reflect.jar: kotlin-stdlib.jar:kotlin-script-runtime.jar"

    org.jetbrains.kotlin.cli.jvm.K2JVMCompiler -script ~/experiments/src/main/kotlin/one/exps/scripts/XPRM-10222.kts XPRM-10222.kts:1:8: error: unresolved reference: one import one.exps.*
  79. 153 https://github.com/andrewoma/kotlin-script #!/bin/sh exec kotlins -cp `mvncp <lib>` !# import

    runner.* runner.run { experiment { steps { empty("off") {} ... partition("all") { part("0-255") } } properties("odnoklassniki-web", prop("hosts")) { property("app.avatar.shouldUseWebP") } } }
  80. 154 https://github.com/andrewoma/kotlin-scripting-kickstarter #!/usr/bin/env kotlin-script.sh import runner.* fun main(args: Array<String>) {

    runner.run { experiment { steps { empty("off") {} ... partition("all") { part("0-255") } } properties("odnoklassniki-web", prop("hosts")) { property("app.avatar.shouldUseWebP") } } } }
  81. 155 https://github.com/holgerbrandl/kscript #!/usr/bin/env kscript //DEPS one:experiments:1.0.0 import runner.* runner.run {

    experiment { steps { empty("off") {} ... partition("all") { part("0-255") } } properties("odnoklassniki-web", prop("hosts")) { property("app.avatar.shouldUseWebP") } } }
  82. • Необходимо писать обвязки • Нельзя запустить из IDE (ну

    кроме как console) • Непонятно как подебажить скрипт, если очень нужно 158 Проблемы Kotlin Script
  83. • Необходимо писать обвязки • Нельзя запустить из IDE (ну

    кроме как console) • Непонятно как подебажить скрипт, если очень нужно • Сложно переиспользовать описание эксперимента • для обновления описания задачи в Jira 159 Проблемы Kotlin Script
  84. 160 https://github.com/aatarasoff/kotlin-script-starter //JSR223 //ConsoleExecutor.kt fun main(args : Array<String>) { ScriptEngineManager().getEngineByExtension("kts")!!

    .eval(getResource("${args[0]}.kts").readText()) } //build.gradle compile "org.jetbrains.kotlin:kotlin-compiler-embeddable:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-script-util:$kotlin_version" //META-INF/services/javax.script.ScriptEngineFactory org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory
  85. 161 https://github.com/aatarasoff/kotlin-script-starter //JSR223 //ConsoleExecutor.kt fun main(args : Array<String>) { ScriptEngineManager().getEngineByExtension("kts")!!

    .eval(getResource("${args[0]}.kts").readText()) } //build.gradle compile "org.jetbrains.kotlin:kotlin-compiler-embeddable:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-script-util:$kotlin_version" //META-INF/services/javax.script.ScriptEngineFactory org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory
  86. 162 https://github.com/aatarasoff/kotlin-script-starter //JSR223 //ConsoleExecutor.kt fun main(args : Array<String>) { ScriptEngineManager().getEngineByExtension("kts")!!

    .eval(getResource("${args[0]}.kts").readText()) } //build.gradle compile "org.jetbrains.kotlin:kotlin-compiler-embeddable:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-script-util:$kotlin_version" //META-INF/services/javax.script.ScriptEngineFactory org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory
  87. 175 Разделение ответственностей user model experiment { steps { openByPartitionSteps()

    } properties("odnoklassniki-web", prop("hosts")) { property("app.avatar.shouldUseWebP") property("app.avatar.anotherProperty") } }
  88. 177 “Плоская” модель исполнения execution model execution { steps {

    init { description='на никого' property { "applicationName": "odnoklassniki-web", "hostName": "host-odnoklassniki-web", "propertyName": "app.avatar.shouldUseWebP", "propertyValue": "" } property { ... } } ... all { ... } } definition='возможность отмечать друзей в настроении' }
  89. 178 Модель исполнения. Один шаг init { description='на никого' property

    { "applicationName": "odnoklassniki-web", "hostName": "host-odnoklassniki-web", "propertyName": "app.avatar.shouldUseWebP", "propertyValue": "" } property { "applicationName": "odnoklassniki-web", "hostName": "host-odnoklassniki-web", "propertyName": "app.avatar.anotherProperty", "propertyValue": "" } }
  90. 183 Сверка реального и ожидаемого состояния 1 2 3 S

    validation model app.avatar.shouldUseWebP: 0-63 app.avatar.anotherProperty: 0-63 expected state S
  91. 184 Кто-то вмешался в наш эксперимент! app.avatar.shouldUseWebP: 0-63 app.avatar.anotherProperty: 0-63

    expected state S app.avatar.shouldUseWebP: 0-127 app.avatar.anotherProperty: 0-63 real state S
  92. 189 Только финальные значения app.avatar.shouldUseWebP: 0-255 app.avatar.anotherProperty: 0-255 final state

    S *odnoklassniki-web@host-odnoklassniki-web* {Code} > app.avatar.shouldUseWebP: 0-255 > app.avatar.anotherProperty: 0-255 {Code}
  93. 192 Основная модель - дополнительные модели • Модель исполнения •

    Модель валидации • Модель планирования • создание задач в Jira • визуализация плана
  94. 193 Основная модель - дополнительные модели • Модель исполнения •

    Модель валидации • Модель планирования • создание задач в Jira • визуализация плана • ??? • любые хотелки
  95. • Experiments as Code • Удобный и компактный DSL •

    потому что знаем предметную область • компиляция, code completion 196 Итоги
  96. • Experiments as Code • Удобный и компактный DSL •

    потому что знаем предметную область • компиляция, code completion • Автоматизированный запуск • проверка корректности • уменьшение количества рутинных действий 197 Итоги
  97. • Experiments as Code • Удобный и компактный DSL •

    потому что знаем предметную область • компиляция, code completion • Автоматизированный запуск • проверка корректности • уменьшение количества рутинных действий • Удовольствие от программирования ;) 198 Итоги
  98. • Нужно уметь программировать • или хотя бы понимать инструментарий

    и основные принципы • Нужно поддерживать всё самим • постоянные доработки • баги 200 Trade-Offs
  99. • Нужно уметь программировать • или хотя бы понимать инструментарий

    и основные принципы • Нужно поддерживать всё самим • постоянные доработки • баги • Парадокс синтаксического сахара • баланс между компактностью и сложностью освоения 201 Trade-Offs
  100. • Нужно уметь программировать • или хотя бы понимать инструментарий

    и основные принципы • Нужно поддерживать всё самим • постоянные доработки • баги • Парадокс синтаксического сахара • баланс между компактностью и сложностью освоения • Инструменты не идеальны • а так хотелось 202 Trade-Offs
  101. 203 Мы ещё в начале пути ;) t MVP CLI

    Jira, TamTam Auto Launch Jira custom fields and charts Auto Feedback Charts Smart monitoring