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

Experiments.Kotlin.DumpConf

 Experiments.Kotlin.DumpConf

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