Восемь месяцев назад я начал писать приложение на Electron. Чтобы осилить эту задачу, мне пришлось разработать три отдельных суб-приложения, которые запускаются в разных средах. Ниже я расскажу о том, что вынес для себя по ходу дела.
Binder в исходном виде
Прежде чем углубляться в подробности, начну с того, что приведу некоторые общие сведения, необходимые для понимания ситуации. В начале 2019 года я стал искать, где бы пройти интернатуру, как того требовала программа co-op для моей степени. К апрелю я разослал несколько десятков резюме, причем каждое было подогнано под конкретного работодателя, и в общей сложности получил ровно ноль ответов. Можете себе представить, как я на это отреагировал – у меня опустились руки, казалось, что я ни на что не гожусь и не стою вообще никакой работы. Но вместо того, чтобы дать этим чувствам одержать верх, я решил доказать самому себе, что кое-что все-таки знаю и на какие-то должности могу претендовать. В конечном итоге, я выяснил, что на деле знаю меньше, чем предполагал.
Я стал пересматривать свои проекты в поиск такого, который можно было бы превратить в нечто масштабное и сложное, что могло бы меня подстегнуть. Наконец я остановил свой выбор на Binder – на тот момент простом веб-приложении, которое позволяло управлять файлами с Onedrive, Google Drive и Dropbox в одно и то же время. Цель состояла в том, чтобы разработать бэкап-сервис, сопоставимый по функциональности с этой троицей, за вычетом возможности передавать файлы другим пользователям.
Наверное, я вообще не довел бы проект до конца, если бы с самого начала не поставил перед собой несколько бредовые цели. Первой вехой, которую я для себя определил, было создание рабочей альфа-версии ко дню моего рождения, который я праздную в середине июля. Я приступил к работе немедленно. К началу мая я создал клон Binder и стал разбирать его на части, выбрасывая многие функции, которые теперь стали мне не нужны. В общем, я взял совершенно приличное приложение и превратил его в неказистый кусок кода где-то на уровне туториала.
Я остановился на том, что буду писать четыре отдельных приложения. Первое – это клиент, который будет нативно работать на компьютере пользователя. Второе – API с бэкендом, оно будет облегчать рассылку безопасных запросов. Третье – облачный сервис, отвечающий за целостность всех данных, которые на нем хранятся. И наконец, «маркетинговая» веб-страница, потому что без переливающегося всеми красками сайта продукту никак не обойтись.
Ну хорошо, хорошо, признаю: все, что я сказал до сих пор, вообще не имеет никакого отношения ни к Electron, ни уж тем более разработке крупномасштабных приложений. Но мне нужно было прояснить контекст, чтобы вы понимали, откуда я брал свои знания. Вот пять основных уроков, которые я вынес для себя:
№1 Обязательно используйте традиционные структуры для фронтенда и бэкенда, потому что межпроцессное взаимодействие – дурацкая штука
Я чаще, чем хотелось бы, психовал из-за того, насколько примитивно межпроцессное взаимодействие реализовано в Electron (и сейчас еще раз психану). Да, межпроцессное взаимодействие вообще не рассчитано на то, чтобы проводить абстракцию данных в духе Javascript, с представлением функций как объектов и всяким таким прочим. Но насколько это облегчило бы жизнь! А так вместо того, чтобы сделать минималистичное клиентской приложение, в то время как основной массив кода размещался бы в главном процессе, я был вынужден четко разделять, что у меня будет взаимодействовать с пользователем, а что нет. Критерий, по которому я определял, что пойдет в главный процесс, а что – в рендер-процесс, формулировался как простой вопрос: обслуживает ли этот код что-нибудь? Размер фрагмента при этом значения не имел. Если он обслуживал другие куски кода, то отправлялся в главный процесс. Единственным исключением стал endpoint платежной системы Stripe – из соображений безопасности мне хотелось, чтобы он располагался как можно ближе к пользователю.
№2 Заставить данные сохранять целостность очень и очень сложно
Пока не начал работать над Binder, я не понимал, насколько тяжело добиться, чтобы все данные, поступающие от произвольного и внушительного числа пользователей, всегда оставались корректными и доступными. И просто-то обеспечивать для них безопасное хранение – задача не из легких, а теперь вы еще хотите, чтобы я это все валидировал и следил, чтобы не было противоречий с другим данными, при этом вообще не имея представления, что это за данные?!
Я, конечно, немного тут преувеличиваю, но суть проблемы при этом не искажается. Валидация должна по мере возможности происходить на самых ранних этапах, а затем повторяться (в более скромных масштабах) по мере продвижения потока данных. Сохранять согласованность становится проще, если при каждом изменении данных применять транзакционную модель. Сказать по правде, вместе с собственно данными появляется еще и масса метаданных, управляться с которыми не намного проще. Всегда казалось отличной идеей написать функцию, которая бы прочитывала пользовательские данные, а потом осуществляла проверки на целостность. Но после долгих размышлений я пришел к выводу, что тайные входы ничем не отличаются от парадных – вся разница в том, кто ими пользуется.
№3 Интерфейсы — как туфли, которые шьются на заказ
Отличный способ сделать красивый, хорошо работающий интерфейс – смотреть на него как на пару туфель индивидуального пошива (или сшитый по фигуре костюм, неважно). Первый вопрос, который я себе задал, когда взялся за дизайн для Binder: кто будет смотреть на интерфейс приложения? Заметьте: я не говорил про то, кто будет пользоваться интерфейсом. Это должно идти вторым вопросом, потому что завязано все именно на внешнем виде. В прошлом я вел немало проектов и могу сказать вам с полной уверенностью: люди плевать хотели на ваше приложение, если оно не выглядит как положено.
Я начинал с того, что делал небольшие наброски в записной книжке (шариковой ручкой, потому что у меня есть привычка постоянно сомневаться в своих идеях). На первых набросках акцент делался на общей структуре интерфейса, а уже потом, рисуя дальше, я в подробностях раскраивал, как должна выглядеть каждая из «страниц». На мой взгляд, не так важна представленная информация, как ее удобочитаемость.
№4 Много отказоустойчивости не бывает
Не знаю, как вас, а меня от мысли, что API даст сбой уже после того, как я принял платеж, холодный пот прошибает. Начиная работать над платежной системой, я сказал себе: «Теперь ты не можешь себе позволить пропустить ни одной ошибки». Я позаботился о том, чтобы внедрить механизмы защиты на всей протяженности процесса: от момента, когда API получает запрос на покупку пакета услуг, и до того, как Stripe передает информацию о проведенном платеже системе уведомлений о событиях и пакет активируется. Такого рода паранойя, безусловно, замедляет процесс разработки, но я ни о чем не жалею. Зато я всегда располагаю полной информацией о том, когда поступил платеж, каково его назначение и какой статус у действий, которые должны за ним последовать.
№5 Не идеально? Не страшно
Я перфекционист, и мои попытки создать нечто сногсшибательное нередко выходят из-под контроля и обращаются бесконечными придирками к каждой строчке кода и сомнениями, имеют ли они право на существование. Мне приходится раз за разом выдерживать битвы с собой по поводу того, что же все-таки важнее – эффективность или читабельность. В первые несколько месяцев я еще не прозрел и не понимал, что многое из того, на что я трачу силы, на деле не имеет никакой практической пользы – смысл ведь в том, чтобы сделать продукт, который можно использовать со спокойной душой, а не задел на какую-нибудь престижную премию. Забавно, что мой пятый урок отчасти идет вразрез с четвертым, но это даже хорошо. Четвертый урок напоминает мне об осторожности и вниманию к пользовательским ожиданиям, а пятый задает границы, чтобы я не застрял, бесконечно совершенствуя одну функцию.
Вы прочитали мою статью до конца и, возможно, подметили (ну, или нет), что Binder еще не совсем готов к эксплуатации. На момент написания статьи я только-только выпустил первую публичную версию (бета 4). Я не особенно настроен превращать Binder в полноценный продукт, однако все-таки разработал его так, чтобы он функционировал как нормальное приложение – вдруг у меня взыграют амбиции. На переливающуюся всеми красками веб-страницу можно полюбоваться здесь.
Все, что я счел безопасным для предоставления в открытый доступ, выложено на странице проекта на GitHub. Там же я буду размещать и обновления (и там же можно скачать клиент, чтобы он сам себя обновлял). А, ну и вот статистика, которую я составил по четырем суб-приложениям, чтобы польстить своему самолюбию:
binder-local (клиент на Electron)
binder-web (веб-страница с красивостями)
В оставшихся двух я не стал подсчитывать строки кода автоматическими средствами из соображений безопасности.
binder-api
binder-mongo (сервис для проверки целостности)
Общее число строк кода: 30,015
Binder в исходном виде
Контекст
Прежде чем углубляться в подробности, начну с того, что приведу некоторые общие сведения, необходимые для понимания ситуации. В начале 2019 года я стал искать, где бы пройти интернатуру, как того требовала программа co-op для моей степени. К апрелю я разослал несколько десятков резюме, причем каждое было подогнано под конкретного работодателя, и в общей сложности получил ровно ноль ответов. Можете себе представить, как я на это отреагировал – у меня опустились руки, казалось, что я ни на что не гожусь и не стою вообще никакой работы. Но вместо того, чтобы дать этим чувствам одержать верх, я решил доказать самому себе, что кое-что все-таки знаю и на какие-то должности могу претендовать. В конечном итоге, я выяснил, что на деле знаю меньше, чем предполагал.
Я стал пересматривать свои проекты в поиск такого, который можно было бы превратить в нечто масштабное и сложное, что могло бы меня подстегнуть. Наконец я остановил свой выбор на Binder – на тот момент простом веб-приложении, которое позволяло управлять файлами с Onedrive, Google Drive и Dropbox в одно и то же время. Цель состояла в том, чтобы разработать бэкап-сервис, сопоставимый по функциональности с этой троицей, за вычетом возможности передавать файлы другим пользователям.
Начало пути
Наверное, я вообще не довел бы проект до конца, если бы с самого начала не поставил перед собой несколько бредовые цели. Первой вехой, которую я для себя определил, было создание рабочей альфа-версии ко дню моего рождения, который я праздную в середине июля. Я приступил к работе немедленно. К началу мая я создал клон Binder и стал разбирать его на части, выбрасывая многие функции, которые теперь стали мне не нужны. В общем, я взял совершенно приличное приложение и превратил его в неказистый кусок кода где-то на уровне туториала.
Я остановился на том, что буду писать четыре отдельных приложения. Первое – это клиент, который будет нативно работать на компьютере пользователя. Второе – API с бэкендом, оно будет облегчать рассылку безопасных запросов. Третье – облачный сервис, отвечающий за целостность всех данных, которые на нем хранятся. И наконец, «маркетинговая» веб-страница, потому что без переливающегося всеми красками сайта продукту никак не обойтись.
А теперь начинается самое интересное
Ну хорошо, хорошо, признаю: все, что я сказал до сих пор, вообще не имеет никакого отношения ни к Electron, ни уж тем более разработке крупномасштабных приложений. Но мне нужно было прояснить контекст, чтобы вы понимали, откуда я брал свои знания. Вот пять основных уроков, которые я вынес для себя:
№1 Обязательно используйте традиционные структуры для фронтенда и бэкенда, потому что межпроцессное взаимодействие – дурацкая штука
Я чаще, чем хотелось бы, психовал из-за того, насколько примитивно межпроцессное взаимодействие реализовано в Electron (и сейчас еще раз психану). Да, межпроцессное взаимодействие вообще не рассчитано на то, чтобы проводить абстракцию данных в духе Javascript, с представлением функций как объектов и всяким таким прочим. Но насколько это облегчило бы жизнь! А так вместо того, чтобы сделать минималистичное клиентской приложение, в то время как основной массив кода размещался бы в главном процессе, я был вынужден четко разделять, что у меня будет взаимодействовать с пользователем, а что нет. Критерий, по которому я определял, что пойдет в главный процесс, а что – в рендер-процесс, формулировался как простой вопрос: обслуживает ли этот код что-нибудь? Размер фрагмента при этом значения не имел. Если он обслуживал другие куски кода, то отправлялся в главный процесс. Единственным исключением стал endpoint платежной системы Stripe – из соображений безопасности мне хотелось, чтобы он располагался как можно ближе к пользователю.
№2 Заставить данные сохранять целостность очень и очень сложно
Пока не начал работать над Binder, я не понимал, насколько тяжело добиться, чтобы все данные, поступающие от произвольного и внушительного числа пользователей, всегда оставались корректными и доступными. И просто-то обеспечивать для них безопасное хранение – задача не из легких, а теперь вы еще хотите, чтобы я это все валидировал и следил, чтобы не было противоречий с другим данными, при этом вообще не имея представления, что это за данные?!
Я, конечно, немного тут преувеличиваю, но суть проблемы при этом не искажается. Валидация должна по мере возможности происходить на самых ранних этапах, а затем повторяться (в более скромных масштабах) по мере продвижения потока данных. Сохранять согласованность становится проще, если при каждом изменении данных применять транзакционную модель. Сказать по правде, вместе с собственно данными появляется еще и масса метаданных, управляться с которыми не намного проще. Всегда казалось отличной идеей написать функцию, которая бы прочитывала пользовательские данные, а потом осуществляла проверки на целостность. Но после долгих размышлений я пришел к выводу, что тайные входы ничем не отличаются от парадных – вся разница в том, кто ими пользуется.
№3 Интерфейсы — как туфли, которые шьются на заказ
Отличный способ сделать красивый, хорошо работающий интерфейс – смотреть на него как на пару туфель индивидуального пошива (или сшитый по фигуре костюм, неважно). Первый вопрос, который я себе задал, когда взялся за дизайн для Binder: кто будет смотреть на интерфейс приложения? Заметьте: я не говорил про то, кто будет пользоваться интерфейсом. Это должно идти вторым вопросом, потому что завязано все именно на внешнем виде. В прошлом я вел немало проектов и могу сказать вам с полной уверенностью: люди плевать хотели на ваше приложение, если оно не выглядит как положено.
Я начинал с того, что делал небольшие наброски в записной книжке (шариковой ручкой, потому что у меня есть привычка постоянно сомневаться в своих идеях). На первых набросках акцент делался на общей структуре интерфейса, а уже потом, рисуя дальше, я в подробностях раскраивал, как должна выглядеть каждая из «страниц». На мой взгляд, не так важна представленная информация, как ее удобочитаемость.
№4 Много отказоустойчивости не бывает
Не знаю, как вас, а меня от мысли, что API даст сбой уже после того, как я принял платеж, холодный пот прошибает. Начиная работать над платежной системой, я сказал себе: «Теперь ты не можешь себе позволить пропустить ни одной ошибки». Я позаботился о том, чтобы внедрить механизмы защиты на всей протяженности процесса: от момента, когда API получает запрос на покупку пакета услуг, и до того, как Stripe передает информацию о проведенном платеже системе уведомлений о событиях и пакет активируется. Такого рода паранойя, безусловно, замедляет процесс разработки, но я ни о чем не жалею. Зато я всегда располагаю полной информацией о том, когда поступил платеж, каково его назначение и какой статус у действий, которые должны за ним последовать.
№5 Не идеально? Не страшно
Я перфекционист, и мои попытки создать нечто сногсшибательное нередко выходят из-под контроля и обращаются бесконечными придирками к каждой строчке кода и сомнениями, имеют ли они право на существование. Мне приходится раз за разом выдерживать битвы с собой по поводу того, что же все-таки важнее – эффективность или читабельность. В первые несколько месяцев я еще не прозрел и не понимал, что многое из того, на что я трачу силы, на деле не имеет никакой практической пользы – смысл ведь в том, чтобы сделать продукт, который можно использовать со спокойной душой, а не задел на какую-нибудь престижную премию. Забавно, что мой пятый урок отчасти идет вразрез с четвертым, но это даже хорошо. Четвертый урок напоминает мне об осторожности и вниманию к пользовательским ожиданиям, а пятый задает границы, чтобы я не застрял, бесконечно совершенствуя одну функцию.
Прежде чем мы попрощаемся
Вы прочитали мою статью до конца и, возможно, подметили (ну, или нет), что Binder еще не совсем готов к эксплуатации. На момент написания статьи я только-только выпустил первую публичную версию (бета 4). Я не особенно настроен превращать Binder в полноценный продукт, однако все-таки разработал его так, чтобы он функционировал как нормальное приложение – вдруг у меня взыграют амбиции. На переливающуюся всеми красками веб-страницу можно полюбоваться здесь.
Все, что я счел безопасным для предоставления в открытый доступ, выложено на странице проекта на GitHub. Там же я буду размещать и обновления (и там же можно скачать клиент, чтобы он сам себя обновлял). А, ну и вот статистика, которую я составил по четырем суб-приложениям, чтобы польстить своему самолюбию:
binder-local (клиент на Electron)
binder-web (веб-страница с красивостями)
В оставшихся двух я не стал подсчитывать строки кода автоматическими средствами из соображений безопасности.
binder-api
- JavaScript: 21 файл, 4117 строк
- Прочие файлы: ~ 150 строк
binder-mongo (сервис для проверки целостности)
- JavaScript: 16 файлов, 2374 строк
- Прочие файлы: ~ 140 строк
Общее число строк кода: 30,015