От переводчика:
Немало копий сломано в спорах о том, когда уместнее KISS, а когда DRY, когда лучше как можно быстрее и проще решить задачу любыми средствами, а когда стоит создавать красивые и универсальные абстракции. Натан Марц, автор популярного фреймворка Storm, используемого в Твиттере, предлагает свой вариант. Чтобы не создавать тонны бесполезного кода ради абстрактной универсальности и в то же время не позволять системе превращаться в кашу из костылей, он использует «разработку через страдание» (suffering oriented programming).
Однажды меня спросили: «Как ты решился пойти на такой страшный риск — писать Storm одновременно с запуском стартапа?» (Storm — фреймворк для распределённых вычислений в реальном времени). Да, пожалуй, со стороны создание такого крупного проекта для стартапа кажется крайне рискованным. Тем не менее, с моей точки зрения это вообще не было рискованным делом. Трудным, но не рискованным.
Я использую стиль разработки, который сильно уменьшает степень риска таких больших проектов, как Storm. Я называю этот стиль «разработкой через страдание». В двух словах: не занимайтесь реализацией технологий, от отсутствия которых вы не испытываете страданий. Этот совет применим как к большим, архитектурным решениям, так и к маленьким повседневным задачам. Разработка через страдание существенно уменьшает риск, гарантируя, что вы всегда работаете над чем-то важным, и что вы хорошо разобрались в предметной области, прежде чем вложить в решение много сил.
Я придумал такую мантру разработки: «Сначала сделай, чтобы было. Затем — чтобы было красиво. Затем — чтобы было быстро».
Сталкиваясь с новой предметной областью, нельзя с самого начала пытаться создать «общее» или «расширяемое» решение. Вы просто не понимаете проблему настолько хорошо, чтобы предсказать, что вам понадобится в будущем. Вы обобщаете там, где в этом нет необходимости, добавляя сложности и теряя время.
Гораздо лучше решать все задачи «в лоб», возможно с костылями и грязными хаками, заботясь лишь о сегодняшнем дне. Это позволяет быстро получить результат и не терять время зря. И в процессе этого вы узнаёте всё больше и больше о хитросплетениях предметной области.
Фаза «Сделай, чтобы было» для Storm длилась около года. Методом проб и ошибок мы создавали систему обработки потоков сообщений с очередями и процессами. Мы научились использовать механизм подтверждений для гарантированной обработки данных. Мы научились масштабировать вычисления в реальном времени на кластерах очередей и процессов. Мы узнали, что в разных случаях лучше по-разному разбивать поток сообщений, иногда случайно, а иногда по хэшу, чтобы одни и те же сущности обрабатывались одним и тем же процессом.
Мы даже не осознавали, что находимся посреди фазы «Сделай, чтобы было». Мы просто писали наш продукт. Тем не менее, боль, которую нам причиняли очереди и процессы, очень быстро стала острой. Масштабирование было ужасной морокой, а надёжность была очень далека от нужного уровня. Стало очевидно, что наша парадигма очередей и процессов была неправильной и использовала не тот уровень абстракции. Большая часть нашего кода занималась маршрутизацией и сериализацией сообщений, а не бизнес-логикой.
В то же время, в процессе разработки мы обнаружили новые задачи в предметной области. Нам нужна была функция, которая вычисляла охват URL в Твиттере, то есть количество уникальных пользователей, которые увидели этот URL. Это сложная задача, которая может потребовать сотен обращений к БД и миллионов операций. Первоначальная реализация, работавшая на одной машине, могла потратить больше минуты на обработку одного URL. Стало ясно, что нужно распараллеливать вычисления в распределённой среде, чтобы работать быстро.
Одна из ключевых идей, легших в основу Storm, состояла в том, что «проблема охвата»
и проблема «обработки потока сообщений» могут быть объединены в одну простую абстракцию.
«Разведка боем» в предметной области позволяет построить её карту. Со временем приходит всё более глубокое понимание нюансов и реальных вариантов использования. Это понимание может привести к созданию красивого решения взамен существующих «костылей», которое утолит страдания и сделает возможным создание новых функций или систем, к которым раньше нельзя было и подступиться.
Ключ к нахождению красивой системы — определить самый простой набор абстракций, достаточный для решения всех конкретных задач, с которыми вы имели дело. Пытаться предсказать гипотетические случаи, с которым вы сами не сталкивались — ошибка, приводящая к овер-инжинирингу. Вообще, чем крупнее система, тем более глубоко надо понимать предметную область и тем более разносторонним должен быть набор вариантов использования. Иначе вы можете подвергнуться эффекту второй системы.
Именно в фазе «Сделай, чтобы было красиво» можно дать волю своим навыкам абстрагирования и проектирования, чтобы выделить набор гармонично сочетающихся простых абстракций. Это похоже на аппроксимацию набора точек на графике (ваши сценарии использования) максимально простой математической функцией (ваш набор абстракций).
Чем больше на графике точек, тем больше шансы, что вы найдёте оптимальную кривую. Если точек слишком мало, велик риск того, что ваша кривая будет или слишком плохо описывать реальные данные, или её формула будет слишком сложной. А это — ненужные потери времени и сил.
Очень важное условие красивого решения — помнить о производительности и требованиях к ресурсам. Знания, необходимые для этого, появляются именно во время первой фазы.
Работая над Storm, я выделил маленький набор ключевых абстракций: потоки (streams), изливы (spouts), задвижки (bolts) и топологии (topologies). Я смог разработать новый алгоритм гарантированной обработки данных, который избавил от промежуточных брокеров сообщений — той части системы, которая причиняла больше всего страданий. То, что две такие разные на первый взгляд проблемы, как проблема охвата и обработка потока сообщений решались так элегантно одним способом, говорило о том, что я нащупал нечто выдающееся.
Я поискал ещё несколько сценариев использования, чтобы проверить дизайн. Я расспрашивал знакомых программистов и написал в Твиттере, что я работаю над новой системой реального времени и интересуюсь реальными сценариями использования. Было много интересных обсуждений, я получил много ценной информации и убедился, что моя идея работает.
После того, как вам удалось создать красивое решение, можно приступать к профилированию и оптимизации.
Фаза «Чтобы было быстро» не затрагивает глубоких архитектурных проблем с производительностью. О них надо было узнать во время первой фазы, и разобраться с ними во время второй. Сейчас речь идёт о микро-оптимизациях и «вылизывании» кода. Во время первых двух фаз надо уменьшать асимптотическую сложность алгоритмов, во время третьей — уменьшать константы, влияющие на скорость.
Разработка через cтрадание — непрерывный процесс. Красивая и эффективная система открывает новые возможности и ставит новые задачи, а значит снова придётся «делать, чтобы было» уже в новых областях, и изменять дизайн на основе полученной информации, чтобы соответствовать новым точкам на графике.
Storm прошёл через множество таких итераций. Когда мы начали его использовать, обнаружилась потребность генерировать несколько независимых потоков из одного компонента. Оказалось, что если добавить специальный тип потока «direct stream», Storm сможет обрабатывать пакеты записей как единое целое. Недавно я разработал «транзакционные топологии», которые позволяют гарантировать строго однократную обработку сообщений в практически произвольных вычислениях.
Метод проб и ощибок в предметной области, которую вы не слишком хорошо понимаете, по определению приводит к каше в коде. Поэтому самая важная характеристика разработки через страдание — постоянная концентрация на рефакторинге. Это жизненно важно, чтобы не позволить случайной сложности наводнить код.
В разработке через страдание сценарии использования — это всё. Они ценятся на вес золота. А единственный способ их раздобыть — начать писать код, и неважно насколько он будет красивым в первое время.
Все программисты проходят через несколько этапов развития. Сначала мы заставляем программу хоть как-то работать. Код лишён какой-бы то ни было структуры и полон копипасты. Со временем мы понимаем преимущества более структурированного подхода, осваиваем инкапсуляцию и всё более абстрактные и обобщённые конструкции. А затем мы становимся одержимыми стремлением писать как можно более общий и расширяемый код, перестраховываясь на будущее.
Методология разработки через страдание отбрасывает попытки предсказать проблемы, с которыми вы пока не столкнулись. Она признаёт, что обобщения без глубокого понимания предметной области ведут к чрезмерной сложности и потерянным усилиям. Архитектура всегда должна подчиняться реальным, а не выдуманным требованиям.
Немало копий сломано в спорах о том, когда уместнее KISS, а когда DRY, когда лучше как можно быстрее и проще решить задачу любыми средствами, а когда стоит создавать красивые и универсальные абстракции. Натан Марц, автор популярного фреймворка Storm, используемого в Твиттере, предлагает свой вариант. Чтобы не создавать тонны бесполезного кода ради абстрактной универсальности и в то же время не позволять системе превращаться в кашу из костылей, он использует «разработку через страдание» (suffering oriented programming).
Однажды меня спросили: «Как ты решился пойти на такой страшный риск — писать Storm одновременно с запуском стартапа?» (Storm — фреймворк для распределённых вычислений в реальном времени). Да, пожалуй, со стороны создание такого крупного проекта для стартапа кажется крайне рискованным. Тем не менее, с моей точки зрения это вообще не было рискованным делом. Трудным, но не рискованным.
Я использую стиль разработки, который сильно уменьшает степень риска таких больших проектов, как Storm. Я называю этот стиль «разработкой через страдание». В двух словах: не занимайтесь реализацией технологий, от отсутствия которых вы не испытываете страданий. Этот совет применим как к большим, архитектурным решениям, так и к маленьким повседневным задачам. Разработка через страдание существенно уменьшает риск, гарантируя, что вы всегда работаете над чем-то важным, и что вы хорошо разобрались в предметной области, прежде чем вложить в решение много сил.
Я придумал такую мантру разработки: «Сначала сделай, чтобы было. Затем — чтобы было красиво. Затем — чтобы было быстро».
Сделай, чтобы было
Сталкиваясь с новой предметной областью, нельзя с самого начала пытаться создать «общее» или «расширяемое» решение. Вы просто не понимаете проблему настолько хорошо, чтобы предсказать, что вам понадобится в будущем. Вы обобщаете там, где в этом нет необходимости, добавляя сложности и теряя время.
Гораздо лучше решать все задачи «в лоб», возможно с костылями и грязными хаками, заботясь лишь о сегодняшнем дне. Это позволяет быстро получить результат и не терять время зря. И в процессе этого вы узнаёте всё больше и больше о хитросплетениях предметной области.
Фаза «Сделай, чтобы было» для Storm длилась около года. Методом проб и ошибок мы создавали систему обработки потоков сообщений с очередями и процессами. Мы научились использовать механизм подтверждений для гарантированной обработки данных. Мы научились масштабировать вычисления в реальном времени на кластерах очередей и процессов. Мы узнали, что в разных случаях лучше по-разному разбивать поток сообщений, иногда случайно, а иногда по хэшу, чтобы одни и те же сущности обрабатывались одним и тем же процессом.
Мы даже не осознавали, что находимся посреди фазы «Сделай, чтобы было». Мы просто писали наш продукт. Тем не менее, боль, которую нам причиняли очереди и процессы, очень быстро стала острой. Масштабирование было ужасной морокой, а надёжность была очень далека от нужного уровня. Стало очевидно, что наша парадигма очередей и процессов была неправильной и использовала не тот уровень абстракции. Большая часть нашего кода занималась маршрутизацией и сериализацией сообщений, а не бизнес-логикой.
В то же время, в процессе разработки мы обнаружили новые задачи в предметной области. Нам нужна была функция, которая вычисляла охват URL в Твиттере, то есть количество уникальных пользователей, которые увидели этот URL. Это сложная задача, которая может потребовать сотен обращений к БД и миллионов операций. Первоначальная реализация, работавшая на одной машине, могла потратить больше минуты на обработку одного URL. Стало ясно, что нужно распараллеливать вычисления в распределённой среде, чтобы работать быстро.
Одна из ключевых идей, легших в основу Storm, состояла в том, что «проблема охвата»
и проблема «обработки потока сообщений» могут быть объединены в одну простую абстракцию.
Сделай, чтобы было красиво
«Разведка боем» в предметной области позволяет построить её карту. Со временем приходит всё более глубокое понимание нюансов и реальных вариантов использования. Это понимание может привести к созданию красивого решения взамен существующих «костылей», которое утолит страдания и сделает возможным создание новых функций или систем, к которым раньше нельзя было и подступиться.
Ключ к нахождению красивой системы — определить самый простой набор абстракций, достаточный для решения всех конкретных задач, с которыми вы имели дело. Пытаться предсказать гипотетические случаи, с которым вы сами не сталкивались — ошибка, приводящая к овер-инжинирингу. Вообще, чем крупнее система, тем более глубоко надо понимать предметную область и тем более разносторонним должен быть набор вариантов использования. Иначе вы можете подвергнуться эффекту второй системы.
Именно в фазе «Сделай, чтобы было красиво» можно дать волю своим навыкам абстрагирования и проектирования, чтобы выделить набор гармонично сочетающихся простых абстракций. Это похоже на аппроксимацию набора точек на графике (ваши сценарии использования) максимально простой математической функцией (ваш набор абстракций).
Чем больше на графике точек, тем больше шансы, что вы найдёте оптимальную кривую. Если точек слишком мало, велик риск того, что ваша кривая будет или слишком плохо описывать реальные данные, или её формула будет слишком сложной. А это — ненужные потери времени и сил.
Очень важное условие красивого решения — помнить о производительности и требованиях к ресурсам. Знания, необходимые для этого, появляются именно во время первой фазы.
Работая над Storm, я выделил маленький набор ключевых абстракций: потоки (streams), изливы (spouts), задвижки (bolts) и топологии (topologies). Я смог разработать новый алгоритм гарантированной обработки данных, который избавил от промежуточных брокеров сообщений — той части системы, которая причиняла больше всего страданий. То, что две такие разные на первый взгляд проблемы, как проблема охвата и обработка потока сообщений решались так элегантно одним способом, говорило о том, что я нащупал нечто выдающееся.
Я поискал ещё несколько сценариев использования, чтобы проверить дизайн. Я расспрашивал знакомых программистов и написал в Твиттере, что я работаю над новой системой реального времени и интересуюсь реальными сценариями использования. Было много интересных обсуждений, я получил много ценной информации и убедился, что моя идея работает.
Сделай, чтобы было быстро
После того, как вам удалось создать красивое решение, можно приступать к профилированию и оптимизации.
Фаза «Чтобы было быстро» не затрагивает глубоких архитектурных проблем с производительностью. О них надо было узнать во время первой фазы, и разобраться с ними во время второй. Сейчас речь идёт о микро-оптимизациях и «вылизывании» кода. Во время первых двух фаз надо уменьшать асимптотическую сложность алгоритмов, во время третьей — уменьшать константы, влияющие на скорость.
«Сполоснуть и повторить»
Разработка через cтрадание — непрерывный процесс. Красивая и эффективная система открывает новые возможности и ставит новые задачи, а значит снова придётся «делать, чтобы было» уже в новых областях, и изменять дизайн на основе полученной информации, чтобы соответствовать новым точкам на графике.
Storm прошёл через множество таких итераций. Когда мы начали его использовать, обнаружилась потребность генерировать несколько независимых потоков из одного компонента. Оказалось, что если добавить специальный тип потока «direct stream», Storm сможет обрабатывать пакеты записей как единое целое. Недавно я разработал «транзакционные топологии», которые позволяют гарантировать строго однократную обработку сообщений в практически произвольных вычислениях.
Метод проб и ощибок в предметной области, которую вы не слишком хорошо понимаете, по определению приводит к каше в коде. Поэтому самая важная характеристика разработки через страдание — постоянная концентрация на рефакторинге. Это жизненно важно, чтобы не позволить случайной сложности наводнить код.
Вывод
В разработке через страдание сценарии использования — это всё. Они ценятся на вес золота. А единственный способ их раздобыть — начать писать код, и неважно насколько он будет красивым в первое время.
Все программисты проходят через несколько этапов развития. Сначала мы заставляем программу хоть как-то работать. Код лишён какой-бы то ни было структуры и полон копипасты. Со временем мы понимаем преимущества более структурированного подхода, осваиваем инкапсуляцию и всё более абстрактные и обобщённые конструкции. А затем мы становимся одержимыми стремлением писать как можно более общий и расширяемый код, перестраховываясь на будущее.
Методология разработки через страдание отбрасывает попытки предсказать проблемы, с которыми вы пока не столкнулись. Она признаёт, что обобщения без глубокого понимания предметной области ведут к чрезмерной сложности и потерянным усилиям. Архитектура всегда должна подчиняться реальным, а не выдуманным требованиям.