Support us

Будущее разработки ПО — за более абстрактными системами

Оставить комментарий
Будущее разработки ПО — за более абстрактными системами

Практически все системы во всех областях эволюционируют во всё более и более абстрактные и сложные. Вероятнее всего, программные продукты не являются исключением, пишет основатель Targetprocess Михаил Дубаков, анализируя абстракцию в разных дисциплинах и её влияние на будущее разработки ПО.

Читать далее

Всем известная картина. Фото: Micha Theiner, The Independent.

Внимание. В этой статье смешаны живопись, поэзия, математика, визуализация данных, физика, программирование и прочие дисциплины, плавно перетекающие в проблематику разработки ПО. Поэтому слабонервным лучше не читать.

Начнём с определений.

Абстрагирование — это процесс выделения общих правил и концепций из примеров и методов.

Абстракция — это результат процесса, то есть концепция, которая соединяет в себе все подчинённые концепции.

В процессе анализа я хочу получить ответы на следующие вопросы:

  1. Как изменялся уровень абстракции в различных областях с развитием человечества?
  2. Какая связь абстракции со сложностью и мощностью системы?
  3. Чем это нам всем помогает или мешает в разработке программ?

Если вам интересны только выводы — пройдите вниз. А начнём мы издалека.

Живопись

Люди стали изображать разные вещи довольно давно. Особым спросом пользовались сюжеты охоты, рыбалки и групп людей с палками. Постепенно живопись развивалась и становилась все более техничной. Появлялись разные жанры, о которых мы и поговорим.

Реализм

В этом жанре художник рисует мир таким, каким он его видит. Все очень конкретно. Есть выдуманные сюжеты, но в целом довольно ясно, что художник изобразил и что он хочет сказать.

На картине Перова все довольно конкретно. Три охотника ведут увлекательную беседу. Тема беседы нам неизвестна, но можно предположить, что охотник слева описывает захватывающую схватку с медведем/кабаном/тигром, чему центральный охотник не очень-то верит.

Василий Перов. Охотники на привале. 1871.

Всё понятно. Никакой абстрактности.

Импрессионизм

Реальный мир пропускается через сознание художника и смешивается с его личными ощущениями и впечатлениями. Уже не так просто понять, что изображено на картине.

Клод Моне. Впечатление. Восходящее солнце. 1872.

Размытые силуэты гавани, странное сочетание цветов, неясность. В то время критики довольно едко высказывались о картинах импрессионистов. Например, так: «Обои, и те смотрелись бы более законченно, чем это “Впечатление”!» Сейчас импрессионистов любят все.

Абстракционизм

Абстрактное искусство пытается вообще уйти от прямой передачи действительности. Сознание художника и его видение мира выходит на первый план.

Пит Мондриан. Композиция с синим, красным и желтым. 1921.

С одной стороны, мы видим просто прямоугольники разных цветов и размеров. С другой стороны, пространство интерпретаций картины огромно. Кто-то увидит в ней витражи католического собора, кто-то казнь Карла I, а кто-то неровность линии слева. Здесь высокий уровень абстракции увеличивает мощность системы.

Абстракционизм нравится далеко не всем. Многие люди его не понимают.

Поэзия

В поэзии происходили аналогичные процессы. По мере развития цивилизации появлялись все более и более абстрактные жанры.

Начнем с какого-нибудь эпоса. Кусочек из «Старшей Эдды».

Ночь была в доме,

норны явились

судьбу предрекать

властителю юному;

судили, что он

будет прославлен,

лучшим из конунгов

прозван будет.

Так нить судьбы

пряли усердно,

что содрогались

в Бралунде стены;

нить золотую

свили и к небу —

к палатам луны —

её привязали.

Всё довольно конкретно и понятно. Родился знатный парень, чему поспособствовали норны. Больше в целом говорить тут не о чем.

Импрессионизм

Федерико Гарсия Лорка обычно писал не столь прямолинейные стихи. Вот, скажем, стихотворение «Перекресток».

Восточный ветер.

Фонарь и дождь.

И прямо в сердце

нож.

Улица —

дрожь

натянутого

провода,

дрожь

огромного овода.

Со всех сторон,

куда ни пойдешь,

прямо в сердце —

нож.

1921

Трактовка уже не так однозначна. Есть какое-то ощущение тревоги на городской улице и #безысходность. Появляются интересные сравнения и образы. Тут нет чёткого описания, а скорее просто настроение.

Футуризм

Чем дальше, тем меньше однозначности. Возьмём раннего Маяковского и его стихотворение «А вы могли бы?»

Я сразу смазал карту будня,

плеснувши краску из стакана;

я показал на блюде студня

косые скулы океана.

На чешуе жестяной рыбы

прочел я зовы новых губ.

А вы

ноктюрн сыграть

могли бы

на флейте водосточных труб?

1913

И рассмотрим пару возможных трактовок данного стихотворения. Я приведу только небольшие отрывки:

«Карта будня», листок нашей жизни с повседневными, одинаковыми заботами, где уже кем-то заранее начерчены схемы дорог, расположение зданий, объектов. И Маяковский «сразу смазал» её, не залил, не испачкал, а именно смешал бесповоротно, не глядя на последствия «плеснувши» целый стакан краски. Изменил свою жизнь, резко, безоглядно, отрекаясь от старого, как человек, жаждущий революционного взрыва».

И другой вариант, который лично мне кажется гораздо более близким к действительности:

«Маяковский в ожидании хорошей погоды заказывает в рыбном ресторанчике заливное с вином, неуклюже опрокидывает последнее, и вот появляется стихотворение, о котором до сих пор идут непрекращающиеся споры.

Повышение уровня абстракции снова дает нам фейерверк трактовок и неистощимое поле для критики. Так что же он хотел этим сказать?»

Математика

Перейдём к более точным наукам. Математика сама по себе достаточно абстрактная штука, поэтому возьмём что-нибудь простое — числа.

Все началось с необходимости подсчитать количество нападающих волков и сравнить их с количество защищающихся людей, так что натуральные числа — это счётные числа: 1, 2, 3, 4 и так далее. Надо сказать, что число 3 само по себе достаточно абстрактное понятие. На самом деле, его не существует в природе. Есть 3 дерева или 3 барана, но числа 3 нет.

Далее умные индусы придумали число ноль. Долгое время 0 вообще в Европе не считали числом, а каким-то условным символом. Даже в 17 веке находились господа, которые ноль знать не хотели. Отрицательные числа ввели, чтобы было удобно записывать долги и решать некоторые уравнения. В итоге получили целые числа: -2, -1, 0, 1, 2

Всё бы хорошо, но иногда хочется что-то измерить. Например, длину отрезка. Для этого целых чисел будет недостаточно. На самом деле, если мой эталонный отрезок помещается в другом отрезке больше 1 но меньше 2 раз, то какая длина-то? На помощь приходят дроби. Делим один отрезок на другой и получаем дробь n/m. Такие числа называют рациональными.

Дальше сложно. Выяснилось, что есть числа, которые дробями не представишь. Например, число «пи» или квадратный корень из двух. Древним грекам данный факт выносил мозг, и они старательно не замечали таких чисел. Они называются иррациональными. Дать точное определение оказалось не так-то просто, вот Дедекинд смог.

Иррациональные числа гораздо сложнее представить, чем целые. В самом деле, что же такое Pi?

Но и это ещё не всё. Мы дошли до комплексных чисел, которые описываются в виде z = x + iy, где i — мнимая единица. К счастью, в наш просвещённый век комплексные числа учат в школе. Но, к несчастью, понимает их примерно 5% учеников.

Что мы имеем? Комплексные числа самые абстрактные и самые мощные. Из них можно вывести все другие числа. Кроме того, они наиболее сложны в понимании.

(Мы не будем трогать ещё более абстрактные кватернионы, октонионы и седенионы, потому что я их не знаю).

Физика

В физике просто море примеров повышения уровня абстракции. Возьмём самый простой  —  гравитацию. Сначала её просто наблюдали. Берёшь камень, отпускаешь его, и он почему-то падает на землю. Галилей вот шарики бросал. Это самый низкий уровень  —  наблюдение конкретного явления.

Потом Ньютон взял и всё обобщил. Он не только пришёл к выводу, что все тела притягиваются, а ещё и зависимость вывел: произведение масс делить на квадрат расстояния между телами. И внезапно стало возможным описывать движения планет, комет и прочих небесных тел. Взаимодействие распространяется мгновенно, все счастливы.

К сожалению, постепенно обнаружились некоторые явления, которые не укладывались в стройную и красивую теорию Ньютона. Например, смещение перигелия Меркурия (крайне любопытная история попыток объяснения этого явления). Казалось бы, у фотонов нет массы, так что свет не должен отклоняться. Но отклоняется. Для устранения этого и других более крупных противоречий потребовалась общая теория относительности. Она постулировала максимальную скорость распространения взаимодействия (скорость света) и связь пространства-времени с массой. Свет на самом деле и летит себе по прямой, но вот пространство вблизи солнца искривляется.

Общая теория относительности выводит объяснение гравитации на новый уровень абстракции и совершенно недоступна для понимания подавляющему большинству населения земного шара. Также она включает в себя теорию Ньютона как просто частный случай. И, конечно же, она гораздо более мощная, так как способна объяснить и предсказать существование гораздо большего количества явлений.

Функциональное программирование

Быстро пробежимся по верхам, не заглядывая в теорию категорий.

Самым примитивным уровнем является присваивание. Например var n = 1. Какой-то абстрактный символ n у нас будет равен 1. Все довольно просто.

Далее, появляются функции f(x) -> y. Принимаем на вход значение какого-то типа, отдаём другое значение какого-то типа. Скажем, эта функция добавляет единицу:

function addOne(a) {return a+1}

С использованием функций уже можно нормально программировать, дробить решение на подпрограммы и использовать их в разных местах.

Повышаем уровень абстракции и добавляем функции высшего порядка. Тут мы уже можем принимать другие функции и возвращать функции f(g(x)) -> h(x). Тут уже мощность системы повышается и мы можем делать крутые штуки, замечать, что некоторые функции похожи и писать более абстрактные функции.

Скажем, умножение каждого элемента списка на 2 и конвертация списка чисел в список строк  —  это всё операция map над списком, просто с разными параметрами. Мы один раз пишем такую функцию (абстрагируясь от того, что именно происходит с элементом списка), а потом уже функции высших порядков помогают нам с её помощью делать разные операции.

Мы создаём много таких абстрактных операций над списками (map, filter, group, skip, take, etc.), а потом замечаем, что на самом деле все они могут быть реализованы через ещё более абстрактную функцию reduce/fold.

А потом мы видим, что эту операцию reduce можно применять не только к спискам, но и к чему угодно, что имеет какую-то структуру (например, к дереву). Тут уже вступает в игру класс типов Foldable, на котором определены методы вроде fold. Если какой-то объект инстанциирует этот класс типов, определяя для себя реализацию fold, то такой объект сразу можно использовать с операциями map/filter/etc., которые были ранее определены через reduce.

Повышение уровня абстракции снова дает нам всё более и более мощные решения.

Визуализация данных

Люди пытались что-то показать красиво довольно давно. Но раньше данных было маловато, так что обходились в основном картами. А потом данные начали потихоньку появляться, и умные господа из разных стран стали придумывать диаграммы. Шотландец Уильям Плейфэр отличился особенно, придумав целых три.

Цены на хлеб, общий оборот, экспорт и размер долга на одном графике! Умное использование одной оси Y с пояснением, что цена на хлеб измеряется в фартингах, долг — в десятках миллионов, а оборот и экспорт — в сотнях миллионах. Интересно изучать корреляции. William Playfair. 1824.

Со временем типов графиков придумали всё больше и больше, выделили общие паттерны и все унифицировали до библиотеки графиков, которая знакома любому пользователю Excel.

Excel. Выбираем график. Вообще stacked bar charts обычно плохи. Особенно трёхмерные.

Кажется, на этом всё? Конечно же, нет! Жак Бертен совершил небольшой подвиг, написав великолепную книгу Semiology of Graphic. Он обобщил принципы построения визуалиций данных и открыл дорогу к совершенно новому уровню абстракции, который не только включает в себя все существующие известные диаграммы, но и позволяет легко придумывать новые.

Идеи Бертена развил Лиланд Вилкинсон, создав грамматику графики (grammar of graphics). Если вкратце, то каждая диаграмма состоит из следующих элементов:

  • Data
  • Algebra
  • Scales
  • Statistics
  • Geometry
  • Coordinates
  • Aesthetics

И мы можем комбинировать их для получения практически любых диаграмм. Например, фасетные диаграммы, которые не умеет строить практически ни одна js-библиотека (кроме taucharts и plot.ly), описываются крайне просто:

Фасетная диаграмма.

С помощью этой грамматики можно создать любую диаграмму. Однако это не бесплатно. Грамматика довольно сложна и требует времени на освоение. Кроме того, не так уж много инструментов хорошо её поддерживают. Из доступных, пожалуй, это только ggplot2, да и то не полностью.

Как видим, и в этой области решения становятся всё более общими и мощными.

Пользовательский интерфейс

Переходим в гораздо более молодые и менее изученные сферы. Можно ли обобщить интерфейс, сделать его более абстрактным?

Самый нижний уровень  —  это конкретное решение конкретной проблемы. Так как мне ближе системы управления проектами, возьмем несколько примеров из Targetprocess. Нужен нам, к примеру, список багов. Мы берём и делаем отдельный экран, который решает только эту задачу.

Список багов в Targetprocess 2.

Каждый экран в системе в чём-то уникален. Конечно, есть общие принципы. Но в целом это отдельные страницы со своим кодом.

Возникает вопрос, можно ли решить задачу создания списка чего угодно в корне? Можно ли создать такой универсальный список, на базе которого можно создавать нужные нам экраны? В целом это возможно. Нужно хорошо декомпозировать это представление данных и придумать идеальное решение для каждого компонента. Список состоит из колонок, действий, иерархии, страниц, фильтров, редактирования и так далее. Вообще, задача создать такой универсальный UI-компонент достаточно сложна, но выполнима. Её можно решить в рамках одного приложения, а вот ещё более общее решение часто оказывается недостаточно кастомизируемым под конкретные нужды. Когда-то мы пробовали использовать Sencha. Получилось так себе.

Со временем я пришел к мысли, что практически любое Enterprise-приложение можно собрать из конечного набора высокоуровневых UI-компонентов. В целом любое Enterprise-приложение визуализирует данные и позволяет работать с ними, желательно интерактивно. Набор UI-представлений данных конечен, и это хорошо. Пока я думаю, что он примерно такой:

  • List —  иерархичные списки, знакомые всем.
  • Board — группировка данных по двум осям. Kanban борды например.
  • Timeline  — данные во времени. Диаграмма Ганта или родмэпы.
  • Calendar  — обычный календарь по дням или месяцам, бывает удобно.
  • Network — визуализация зависимостей сущностей.
  • Single Entity View — карточка одной сущности.
  • Single Entity Edit/Add — добавление и редактирование одной сущности.
  • Report —  любые графические отчеты. Слово “любые” сразу делает этот компонент очень сложным.
  • Dashboard —  композиция разных представлений в одном месте для удобства быстрого доступа.

Весь набор высокоуровневых UI-компонент, на которых можно построить практически любое Enterprise-приложение.

Конечно же, есть и уникальные экраны со всякими настройками, административными штучками и так далее. Но всю работу с данными, а это основная часть приложения, можно реализовать с помощью десятка общих компонент. В идеальном мире, реализовав эти компоненты, мы можем быстро клепать системы управления проектами, CRM, Service Desk и прочие решения.

Я называю это динамическим UI. В Targetprocess 3 мы именно его и делаем. Например, у нас можно создать List, Board или Timeline любых сущностей.

Enterprise-приложения

Как развивались и будут развиваться Enterprise-приложения?

Разработка на заказ

Самый конкретный уровень  — это custom software development. В этом случае компания решает свои проблемы самым конкретным образом с помощью разработчика ПО. Плюсы очевидны — костюм, сшитый на заказ, частенько сидит лучше. Но если портной плохой, то уже не факт. Минусы тоже ясны — в целом это очень дорого и не быстро.

Развитие и поддержка таких решений тоже дело не дешёвое и не быстрое. Обычно в custom development не особенно заморачиваются с расширяемостью решений, больше упирая на скорость разработки. Так что через десяток лет система превращается в большой шарик навоза и никто не хочет в ней ничего трогать по понятной причине.

Продукты

Сейчас большинство ниш пытаются закрывать продуктами, которые можно немного подгонять под себя. К сожалению, с расширением и подгонкой существующих продуктов есть проблемы:

  1. В продукте обычно довольно много ограничений и получить подходящее решение даже с помощью кастомизации нельзя.
  2. Сложные кастомизации требуют вовлечения разработчиков, так что мы тоже получаем не самое дешёвое решение.

Получается, что частенько компании меняют продукты раз в несколько лет, всегда довольствуясь не идеальным решением, а скорее средненьким.

Какое же идеальное Enterprise приложение? Оно должно подстраиваться под любую компанию без вовлечения программистов.

Представьте себе, выбираете вы софт для управления проектами и вам говорят: «Для начала мы должны выяснить, как вы работаете. Потом мы помогаем вам установить и настроить систему именно так, как вам нужно. Там будут только нужные вам сущности, с нужными свойствами и правилами, в привычной вам терминологии, только полезные экраны и ничего лишнего».

Как вообще можно достичь такой нирваны?

Грамматика домена

Я думаю, нам нужно изобрести грамматику домена. Она может состоять примерно из таких вещей:

  • Entities  — можно создавать любые сущности, с любыми полями.
  • Relations  — сущности можно связывать между собой.
  • Metrics  — в системе можно вычислять любые метрики. Например, прогресс по проекту в разных компаниях считается очень по разному.
  • Rules — в системе можно определять любые правила. Например, закрыть задачу, если закрыты все баги и все подзадачи. Правила в разных командах очень специфичны.
  • Events — использование событий для для уведомлений внешних и внутренних систем, каналов коммуникаций (емейл, SMS) и так далее.
  • Actions — в системе можно определять действия над сущностями: объединение, изменение типа, удаление и тп.
  • Workflow — гибкая конфигурация состояний любых сущностей.
  • Permissions — настройка прав доступа к любым сущностям и полям.

Создав систему, где можно декларативно описывать бизнес-домен, и привязав её к динамическому UI (см. выше), можно получить практически идеальное решение для огромного количества enterprise-приложений. Даже создав такую систему в одном домене (например, управление проектами), мы получим уникальное решение на новом уровне абстракции и мощности, которое полностью изменит рынок.

Создание такой системы — крайне непростая задача, на которую не жалко потратить 10 лет жизни. Мне она кажется возможной и практически реализуемой.

В итоге у нас будет сложное ядро, но простые и быстрые решения.

Выводы

Для начала, занимательная объединяющая таблица по всем областям, которые мы рассмотрели. Слева у нас конкретные вещи, а справа — довольно абстрактные.

Сразу можно отметить, что колонка слева понятна любому школьнику. Центральная колонка понятна школьнику одарённому. А вот с правой колонкой всё гораздо сложнее, наслаждаться ей могут не более 5-10% населения Земли.

Настала пора синтеза!

Сложность

Давайте изобразим зависимость сложности восприятия от уровня абстракции. Я думаю, график будет такой (горизонтальные полоски —  это разбиение человечества на группы по степени способности воспринимать абстракции).

Экспоненциальное повышение сложности восприятия при повышении уровня абстракции. Высокоабстрактные концепции понятны малому числу людей.

Ясно, что повышение уровня абстракции усложняет понимание концепций и систем. Сначала повышение абстракции не сильно сказывается на сложности. На самом деле, натуральные и рациональные числа довольно понятны. Или, скажем, переменные и функции также. Но потом происходит резкий скачок и сложность возрастает экспоненциально. Функции высших порядков хорошо понимает не более 20% программистов. Общую теорию относительности вообще мало кто понимает. Высшие уровни абстракции доступны только гениям, а на верхнем уровне система превращается в магию и понятна только богам.

Для простых смертных пользователей абстрактные системы должны скрывать свою абстрактность и притворяться конкретными частными случаями.

Чтобы вникнуть в более-менее абстрактные понятия и концепции, нужно прилагать довольно приличные дополнительные усилия. Люди вообще не очень-то любят это делать, только если награда высока. Поэтому, если вы делаете бизнес-приложения, то нужно всё показывать очень конкретно и прятать абстрактные штуки до поры до времени. На эти грабли мы наступили в Targetprocess, когда выставили напоказ возможность создавать свой динамический UI. С одной стороны, мощная и крутая концепция. С другой — сложная и непонятная. По-хорошему, нужно показывать готовые решения, а ко всяким более высоким материям подводить пользователей за руку, постепенно, и не всех.

B2C-решения должны работать с нижними уровнями абстракции, потому что большинство людей просто не будет врубаться во что-то сложнее Instagram. B2B-решения чуть более свободны в этом плане, но всё же не нужно надеяться, что бизнес-пользователи все сплошь гении или находятся в top 20%.

Мощность

Повышение уровня абстракции увеличивает мощность системы. В самом деле, обычно более абстрактные концепции обладают большей предсказательной силой и позволяют делать более крутые вещи. Например, с помощью грамматики графики мы можем создавать почти любые диаграммы. Общая теория относительности предсказала гравитационное замедление времени и существование чёрных дыр.

Зависимость свойств программных систем от уровня абстракции.

Если применять всё это к разработке ПО, то абстрактные системы делать сложнее и дольше. Выбор правильного уровня абстракции для построения системы — критически важная задача. Высокий уровень — медленно, сложно и мощно, низкий уровень — быстро, просто и конкретно. Заметим, что почти практически все решения в custom software development работают на нижнем уровне.

Создание полезных и глубоких абстрактных понятий — искусство, доступное немногим.

Эволюция

Как мне кажется, практически все системы во всех областях эволюционируют во всё более и более абстрактные и сложные. Вероятнее всего, программные продукты не являются исключением. Как видно даже по нашей системе Targetprocess, запросы пользователей ведут к повышению уровня абстракции. Они хотят большей гибкости, большего количества настроек и адаптивного поведения. К сожалению, решения находятся на более высоких уровнях абстракции, отчего пользователи сами потом и страдают.

Однако, общий уровень интеллекта растёт и постепенно всё больше людей воспринимает всё более абстрактные концепции. Например, то, что сейчас кажется магией, лет через 40 будет доступно для понимания гениям. И большинство программистов, наконец, научатся правильно использовать функции высших порядков.

Изменение восприятия абстрактных концепций со временем.

Вообще привычная абстракция редко воспринимается как абстракция. Например, никто не считает число 3 абстрактным понятием, потому что оно стало тривиальным с течением времени. Скорее всего, то же самое произойдёт и с другими концепциями.

Изменение

У меня есть ещё один открытый тезис, который любопытно обсудить.

Абстрактные системы проще модифицировать и развивать.

Так ли это на самом деле? Как посмотреть. Если мы имеем бизнес-приложение, построенное на доменной грамматике, то само приложение менять будет легко. Однако, если потребуется расширение или модифицирование ядра, то это будет непросто.

Точка входа в более абстрактную систему, конечно, выше. Разработчику нужно потратить больше времени, чтобы понять, как она работает.

В целом для реализации таких систем нужно небольшое количество очень сильных программистов, тогда как для реализации конкретных решений нужно огромное количество средних программистов.

Итог

Будущее за более абстрактными системами. Вся история говорит нам об этом.

P.S. Спасибо Андрею Хмылову за ценные рекомендации по разделу «функциональное программирование». Ну, фактически это он его и переписал, потому что мои примеры были слишком абстрактными.

 

 

Текст впервые опубликован на Medium.

Место солидарности беларусского ИТ-комьюнити

Далучайся!

Читайте также
Российские разработчики переориентируются на Ближний Восток, Азию и Африку
Российские разработчики переориентируются на Ближний Восток, Азию и Африку
Российские разработчики переориентируются на Ближний Восток, Азию и Африку
3 комментария
Российским ИТ-гигантам запретят слишком много тратить на внутренние разработки
Российским ИТ-гигантам запретят слишком много тратить на внутренние разработки
Российским ИТ-гигантам запретят слишком много тратить на внутренние разработки
1 комментарий
Каких инструментов и сервисов лишились ИТ-специалисты в Беларуси. Список (обновляем)
Каких инструментов и сервисов лишились ИТ-специалисты в Беларуси. Список (обновляем)
Каких инструментов и сервисов лишились ИТ-специалисты в Беларуси. Список (обновляем)
Собираем в одном месте список платформ, сервисов и инструментов разработки, полностью или частично заблокированных в Беларуси.  Если вы хотите дополнить список или рассказать, как можно обойти ограничения, пишите в наш телеграм-бот или на почту [email protected].   Последнее обновление — 10:00 12 мая.
63 комментария
На GitHub теперь можно создавать репозитории «только для спонсоров»
На GitHub теперь можно создавать репозитории «только для спонсоров»
На GitHub теперь можно создавать репозитории «только для спонсоров»
3 комментария

Хотите сообщить важную новость? Пишите в Telegram-бот

Главные события и полезные ссылки в нашем Telegram-канале

Обсуждение
Комментируйте без ограничений

Релоцировались? Теперь вы можете комментировать без верификации аккаунта.

Комментариев пока нет.