- PVSM.RU - https://www.pvsm.ru -
Программисты много говорят про сложность решений. Мы можем часами размышлять о правильных шаблонах, красивых абстракциях и цепочках зависимостей. Однако, давайте поговорим открыто, всегда ли сложность обусловлена решаемой проблемой? Не оказываемся ли мы в плену наших стереотипов и убеждений?
День добрый. Меня зовут Никита Данилов, я ведущий .NET разработчик, при этом старательно интересуюсь как техническими нюансами .NET платформы, так и философскими аспектами ремесла программиста. Некоторое время назад я выступал на встрече SarDotNet [1] с докладом «Имитация сложности» [2] (Саратовское сообщество всероссийского DotNetRu [3]), вольной расшифровкой которого хочу поделиться.
Сегодня мы поговорим об имитации. Вынужден предупредить, всё что вы сейчас увидите и услышите – мысли на обдумывание, а не точные советы.
Программисты могут часами размышлять о правильных шаблонах и красивых абстракциях. Как сделать еще гибче, еще круче, еще сложнее. Как усложнить. Обычно мы двигаемся от простого к сложному.
Простое → Сложное
На доклад меня подвигло именно то, что мы мало говорим о простом. По крайней мере в моём окружении мало разговоров об этом. Существуют статьи восхваляющие простые решения, но таковых довольно мало и чаще мы говорим о сложном. Давайте сегодня пройдемся от сложного к простому, через таинственную имитацию сложности. Предлагаю всем вместе подумать, почему мы так мало говорим про простые решения и много концентрируемся на сложных.
Простое ← Имитация Сложности ← Сложное
Если говорить про сложное и сложность, то вроде и много всего написано, а всё равно я ощущаю нехватку материалов. Размышляя про код, модули или архитектуру, мы интуитивно понимаем что такое маленькое и большое. Можем примерно отличить нечто простое (как кирпич) и интуитивно понимаем когда творится хаос (с лапшой). Так или иначе, все эти термины связаны с понятием сложности.
Вопрос: Попробуйте подумать, повспоминать и ответить на следующие вопросы:
Обозначений сложности много, она многогранна, перечислим лишь наиболее известные:
К сожалению, зачастую люди находят информацию лишь про SLOC, где обнаруживают критику, что метрика устарела с развитием языков, и на этом изучение теории сложности программного обеспечения завершается. Различные метрики сложности программного обеспечения мы постараемся разобрать в следующей статье (надеюсь). Сейчас же не будем отвлекаться, запомним лишь факт их существования.
Словари определяют, что само слово «сложный» является антонимом к слову «простой» и имеет несколько устоявшихся значений:
Если пристально вглядеться, в нашем мире и правда много сложного. Как нам думается. Навскидку, выписал слова, которые крутились в голове – ГОСТ, финансы, LESS, физика, Docker, ООП, Шаблоны проектирования, Опционы, АЭС и т.д. (см. КДПВ).
Хотя зачастую мы сами явно не упрощаем. Именно в IT-индустрии многие могут знать не понаслышке про такие проекты, где собиралась огромная бригада аналитиков и менеджеров, приносились фреймворки и Blue-Green развертывание, event-driven микросервисы и реактивные микрофронтенды. И вот спустя полгода человек жонглирует 30 серверами пытаясь понять в каком месте оно не работает. А нужна была лишь система обработки заказов, с пиковой нагрузкой в 100 пользователей.
Несмотря на то, что в этом мире существуют явно сложные задачи, тем не менее есть особая категория решений, которые строятся сложнее чем они могли бы быть построены (например, пишется больше кода чем необходимо для решения проблемы).
На мой взгляд, для описания подобной ситуации подходит слово «имитация» имеющее древние корни:
Когда мы имитируем сложность, у нас её еще нет, но мы решили попробовать ей подражать. Это промежуточная серая область, когда разрабатываемое решение уже нельзя считать простым, однако и сложность пока выглядит сомнительно либо неуместно. Позвольте объяснить точнее, рассмотрим на условном примере.
Простое ← Имитация Сложности ← Сложное
Вам передают приложение, разработанное кем то иным, допустим REST API. Вы открываете и видите N проектов: контракты, ядро, данные, веб, фасады и т.д. Богато выглядит, думаете вы, наверняка оно решает нечто важное.
Открываете первый проект. Там 2 директории. 2 интерфейса на фасады. Некий интерфейс видимо указывающий на возможность валидации сущности. Хотя сущностей всего 2 и вроде и логично, что их можно валидировать.
Открываете фасады, думаете может там хитрость закопана. Там обнаружится всего два фасада, по одной реализации, без переключения с одной базы данных на другую или тому подобного. Закрадываются сомнения, а всё ли так богато на самом деле.
Постепенно вы добираетесь до проекта Web, думается там вся соль! Ан нет, там только один контроллер и стандартная конфигурация.
Пример несколько гиперболизирован, однако ситуация многим может быть знакома.
Вопрос: На ваш взгляд, обоснована ли здесь такая организация кода, архитектуры, проектов?
Универсальный ответ — «зависит», ведь это зависит от контекста, от поставленной задачи и чего хотели достичь. Возможно это всё будет ещё расширяться, возможно нет, но если вдруг будет, приложение готово к расширению. Разумеется, я выступаю за продуманную организацию кода, а с другой стороны, вроде всё это упаковывается в 2 проекта и несколько файлов, на первое время.
Рассмотрим другой пример.
Вы создаете новое приложение, допустим, REST API. Решив начать всё с контроллера, вы описали контроллер, описали сущности контракта.
И ощущаете, вроде маловато. Надо еще ведь модульные тесты написать — создаете аксессор (и/или: репозиторий, UnitOfWork, DB-контекст, ...).
Потом идете на кухню попить чайку, открываете книгу про шаблоны проектирования, посещаете конференцию где вам рассказывают еще 3 истории успеха внедрения DDD и CQRS, т.е. вы где-то что-то зачем-то узнаете. Не успеваете оглянуться, вжух – кембрийский взрыв. В приложении появляются останки всех известных слов для выражения зависимостей:
Контракты, фасады, прокси, процессоры и т.д. и т.п. Я знаком с приложениями где больше слов, а делают они пока очень мало чего.
Справедливости ради, спустя 2 года эти приложения научились делать значительно больше и превратились в серьезные системы. Вот только некоторые зависимости так никогда и не были использованы, а некоторые раздробились на множество новых.
Вопрос: Насколько оно обосновано?
Опять-таки, возможно, всё здесь и как надо, возможно усложнили код предполагая будущие задачи. Всё зависит от контекста и наших целей. В теории правильно выстроенная архитектура и не должна позволять сделать неправильно. Однако, надеюсь я сумел донести саму идею существования определенной грани, когда реализуемая сложность еще не нужна, где начинается имитация сложности — будущей или вымышленной.
Мне доводилось самому делать такие приложения как показал на предыдущих слайдах – сразу всё по полочкам, с горой терминов. Потом вернулся через 3 года и выкинул много лишнего, что так и не пригодилось.
Серая область имитации сложности довольно велика и относительна. Но чтобы её лучше понять надо сначала осознать простое и сравнить сложное с простым.
Простое ← Имитация Сложности ← Сложное
Если существует сложность (и она измеряема), то с другой стороны должно быть нечто противопоставляемое, т.е. простое. Довольно давно сформулирован принцип KISS [4], про него немало всего написано и рассказано. Предлагаю рассмотреть какие преимущества нам дает простое решение.
Во-первых, простое решение быстрее реализовать. Уточню, реализовать изначально, тему дальнейшей поддержки/развития мы лишь отчасти затронем чуть ниже.
Безусловно, выбор решения во многом зависит от решаемой проблемы и поставленной задачи.
Сани, машина, ракета — всё из этого позволяет добраться из пункта А в пункт Б.
Сани не сгодятся для полета в космос, зато их сделать быстрее чем космический корабль.
Если мы понимаем, что нам надо лишь съехать с горки, то быть может и куска линолеума хватит, а в этом случае даже сани будут являться имитацией сложности. Мы захотели на более вычурных санках съехать, хотя линолеум прекрасно справился бы, а могли сэкономить деньги и время.
Другой пример это когда вам необходимо доплыть до пункта Б.
Летом, чтобы доплыть до островка неподалеку, зачастую хватает ваших собственных рук и ног, в крайнем случае подручных средств. Смастерить плот и весло сложнее, чем проплыть немного самостоятельно, зато с плотом легче преодолеть большую дистанцию. Структура плота явно проще парусной яхты. Иногда вы арендуете уже готовую яхту с командой и задача поддержки сложности при этом перекладывается на другого.
Во-вторых, простое решение обеспечивает более надежную работу. Это заявление может выглядеть более спорным, а потому позвольте объяснить подробнее. Обратим наше внимание на названые ранее метрики сложности. Значение любой из них так или иначе прямо пропорционально числу «подвижных» частей нашего решения. Если мы входим в зону имитации сложности, мы начинаем добавлять код, вряд ли убавлять.
Вопрос: Какова вероятность ошибки или опечатки в строке кода? 1% или 0.1% или 0.01%?
Отчасти понадеемся на компилятор, он сможет обнаружить самые очевидные ошибки. Допустим мы уверены, что вероятность ошибки в строке кода крайне мала, пусть 0.01%. Перемножив вероятности ошибки на большом приложении с 10 000 строк кода, получаем 100%, что там есть ошибка. Что-то здесь не так...
Причем, исправление одной ошибки уменьшит вероятность следующей лишь незначительно, ведь эти события являются практически независимыми. Допустим мы пишем код идеально. Уменьшим вероятность еще на 2 знака, получим вероятность ошибки 1% на 10 000 строк кода. А потом вспоминаем, что у нас минимум 100 взаимосвязей или разных зависимостей между классами...
Продолжать можно бесконечно, хоть и к точности подсчета можно придраться, суть останется прежней: «Больше последовательных звеньев — ниже надежность». Сложность проверки превосходит количество строк кода в миллионы раз. Поэтому, чем меньше кода в вашем решении, тем выше вероятность надежной работы. Обычно.
В-третьих, простое решение понятнее для поддержки. Снова обратим наше внимание на метрики сложности, многие из которых означают – чем сложнее, тем больше у вас будет методов/классов/абстракций. Всевозможные шаблоны придуманы чтобы сокрыть сложность за некой абстракцией. Даже если они звучат лаконично, в реализации это может означать десятки классов, т.е. сложность зачастую привносит новые абстракции.
Абстракции это великолепный инструмент доступный человеческому разуму. Абстракции упрощают восприятие при моделировании, позволяют сокрыть сложность и абстрагироваться от деталей. Здесь мы спрятали за фасадом, здесь укрыли в репозиторий, и кажется сложности у нас совсем нет.
Если мы начинаем вводить абстракции заранее (а это почти всегда так), то что мы делаем? Имитируем будущую сложность. Проходит время и вы оказываетесь в роли знаменитой Даши. Где-то в глубине приложения что-то упало и вы начинаете копать.
Начинаем смотреть на наши зависимости приправленные любимым контейнером для внедрения.
Что-то там делается, вызывается менеджер с разными параметрами.
class Controller
{
Controller(IManager, ILogger, ISettings) { … }
Load()
{
…
var data = _manager.Load(...);
…
}
}
Идем дальше. Менеджер вроде тоже вполне прозрачный, зависимости какие-то, но параметры перекомпоновывает, поэтому надо разобраться как именно. Разобрались, идём дальше.
class Manager
{
Manager(IFacade, ILogger, IChecker, IWrapper) { … }
Load()
{
…
var data = _facade.Load(...);
…
}
}
Открываем фасад, там еще порция зависимостей. Перегруппировка параметров и вызов дальше аксессора.
class Facade
{
Facade(IAccessor, ILogger, IPermission, IPhone) { … }
Load()
{
…
var data = _accessor.Load(...);
…
}
}
Открываем аксессор, нет зависимостей – ура! Подождите ка...
class Accessor
{
Accessor() { }
Load(...)
{
NLog.Instance.Write(…);
var context = new DbContext("connectionString");
var data = context.Entities.ToList();
}
}
Видимо человек, кто писал аксессор на этом уровне, уже просто устал разбираться. Взял любимый логгер, указал хардкодом строку подключения для начала разработки, да так и оставил. Теоретически, архитектура не должна позволять делать такое. Только кто запретит человеку выстрелить себе в ногу? Возможно на код-ревью потом отловим. Возможно и не отловим, изменится строка подключения и всё рухнет.
Этот пример о том, что вот я стал уставать от обилия абстракций. Здесь десяток абстракций запомни, здесь десяток, и вроде недолго разбираться, но время тратится. Грустно, хочется задачу решать, а не заучивать плоды чьей то фантазии.
Итого, простота ведет к Скорости (уменьшению затрачиваемого времени), к Надежности, к Поддерживаемости. Казалось бы,
«Обалдеть, дайте две!» ©
Замечательно, давайте делать всё максимально просто, чего я вообще тут про имитацию распинаюсь. Почему мы не делаем всё Просто? Разумеется, указанные параметры здесь встают в позу знаменитой тройки: лебедь, рак и щука.
(источник [5], автор: Анна Тимакова, Город: Москва, возраст: 7 лет)
К сожалению, реально полноценно совместить лишь 2 аспекта, а 3-й обязательно будет страдать. Примерно как CAP теорема, или Тройственная ограниченность проекта, только про другое (очень уж людям нравится треугольники). Нам придется делать выбор, а выбор обусловлен целью и задачами, как мы помним. Решаемая задача каждый раз особая, где для одной допустимо простое решение, для другой придется усложнять. В одном случае топорное решение обеспечит страдания при поддержке, а в другом наоборот позволит быстрее переписать при необходимости.
Нередко встречаю аргументацию, мол бывает «сложное» решение реализовать даже «проще», зато с «простым» вы потом получите «сложные» проблемы. Однако, здесь легко можно попасть в лингвистическую и семантическую ловушку. Решение которое видится нам «простым», на самом деле может оказаться невероятно сложным с т.з. отдельных метрик. Лишь отчасти можно утверждать, что такое «сложное» решение «проще» подстраивать, т.е. обходиться компоновкой существующих деталей.
Попробуем подумать, зачем мы имитируем?
Причины могут быть разные, я не берусь сейчас разделять на «хорошие» или «плохие», но важно честно и рационально их оценивать.
Мне знакомы ситуации, когда люди привносили сложность т.к. им было стыдно показать простое решение. Они выходили на кухню, там все рассказывали про сложные шаблоны и рождалась мысль: «Как же я свои 2 класса покажу, это же всё не по книгам».
Часто можно услышать и другую универсальную причину — сделаем гибче, еще гибче, на будущее. Мы многое можем спрятать под общими словами, не желая признать собственное непонимание решаемой задачи.
Не призываю совсем избегать сложности, ведь она бывает оправдана, да и зачастую она оправдана. Всё-таки мы проблемы Бизнеса решаем, а не лямбдами балуемся.
Не призываю создавать только примитивные решения, ведь действительно с ними потом можно получить множество проблем при необходимости внести изменение. Если строить лишь телеги, то в космос не полететь. Хотя если строить лишь космические корабли, то и саночек не останется.
Многое в этом мире автоматизировано и успешно функционирует с использованием относительно простых решений. Признаю проблематичность подтверждения пруфами данного утверждения, оно основано скорее на личном общении. Можно вспомнить доклад Дилана Битти «Ctrl-Alt-Del: учимся любить легаси-код [6]», где Excel упоминается как самая популярная в истории платформа для разработки коммерческих продуктов, а рядом бродит призрак Visual Basic. Знаю небольшие магазины, где долгие годы люди успешно работают с примитивнейшим полу-консольным интерфейсом кассовых аппаратов.
Призываю быть честными по отношению к себе, к коллегам и решаемой задаче. Давайте стараться всегда начинать с упрощения самой постановки проблемы ещё до начала проектирования решения. Возможно, достаточно больших красных букв на странице, вместо механизма отката распределенных транзакций.
«Всё следует упрощать до тех пор, пока это возможно, но не более того» © Альберт Эйнштейн
Упростив постановку проблемы и предлагаемое решение, вы получите пространство для маневра.
Если вы осознаете необходимость восхитительно гибкого механизма — прекрасно, вы эксперт, дерзайте.
Если вы хотите именно здесь опробовать новый подход и он не принесёт страданий — замечательно, пробуйте.
Если же причин усложнять нет, а вас смущают лишь субъективные нерабочие факторы, разве это повод обеспечить трудности будущему себе?
Простое <—> Имитация Сложности <—> Сложное
Давайте чаще задумываться о простом, помня о преимуществах как сложных, так и простых решений. На мой взгляд, именно благодаря этому мы сможем свободно варьировать сложность в зависимости от реальных потребностей. Потом вам и другие скажут спасибо, и вы сами себе.
В ближайшем будущем я планирую подготовить статью с описанием метрик сложности и разбором их влияния на программное обеспечение (информационную систему). По-хорошему, стоило такую статью опубликовать первой, но заготовки под неё не было, зато вот имелся текст выступления с докладом о наболевшем.
Благодарю за внимание, до новых встреч.
Keep It Simple Stupid (KISS) [4].
Rich Hickey, Simple Made Easy [7].
Vlad Balin, Software: Managing the Complexity [8].
Дилан Битти, Ctrl-Alt-Del: учимся любить легаси-код [6].
Основы теории надежности, Расчет показателей надежности невосстанавливаемых нерезервированных систем [9].
DotNetRu [10] — группа независимых сообществ .NET разработчиков со всей России. Мы объединяем людей вокруг .NET платформы, чтобы способствовать обмену опытом и знания. Проводим регулярные встречи, чтобы делиться новостями и лучшими практиками в разработке программных продуктов.
Мы стараемся объединить всех .NET разработчиков России, стать тем метасообществом, где можно посмотреть видео всех докладов, почитать новости, поделиться своими знаниями или мнением на ту или иную тему. В 2018 году нас было 4 города [11], а через два года уже 13 [12]! Это настоящее объединение русскоговорящих .NET сообществ.
DotNetRu [3] ставит перед собой следующие цели:
Мы в телеграмме: новостной канал DotNetRu [14] и обсуждение насущных вопросов DotNetRuChat [15].
Если вы хотите организовать .NET сообщество в вашем городе, свяжитесь с нами [16]. У нас богатый опыт организации встреч, тренировки докладчиков, общения со спонсорами и мы всегда рады новым инициативам. Пишите по любым вопросам и предложениям. Мы сами перенаправим ваше послание нужному адресату. Присоединяйтесь, вместе — мы сила!
Автор: Никита Данилов
Источник [17]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/350657
Ссылки в тексте:
[1] SarDotNet: https://vk.com/sardotnet
[2] «Имитация сложности»: https://www.youtube.com/watch?v=z6yxXJxmst8
[3] DotNetRu: https://vk.com/dotnetru
[4] KISS: http://principles-wiki.net/principles:keep_it_simple_stupid
[5] источник: http://audiohrestomatiya.ru/competitions2/?page=219&now_sort=name_d&now_filter_name=name&now_filter_val=
[6] Ctrl-Alt-Del: учимся любить легаси-код: https://habr.com/ru/company/jugru/blog/436530/
[7] Rich Hickey, Simple Made Easy: https://www.infoq.com/presentations/Simple-Made-Easy/
[8] Vlad Balin, Software: Managing the Complexity: https://medium.com/@gaperton/software-managing-the-complexity-caff5c4964cf
[9] Основы теории надежности, Расчет показателей надежности невосстанавливаемых нерезервированных систем: http://stellus.rgotups.ru/exec/learning_materials/%D0%9A%D0%B0%D1%84%D0%B5%D0%B4%D1%80%D0%B0%20'%D0%96.%D0%B4.%20%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0,%20%D1%82%D0%B5%D0%BB%D0%B5%D0%BC-%D0%BA%D0%B0%20%D0%B8%20%D1%81%D0%B2%D1%8F%D0%B7%D1%8C'/%D0%9E%D0%A2%D0%9D%20(4%20%D0%BA%D1%83%D1%80%D1%81%20%D0%90%D0%A2%D0%A1)/%D1%83%D1%87.%20%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%B8%D0%B5%20%E2%84%961%20%5b%D0%B4%D0%BE%D0%BF.%5d%20(4%20%D0%BA%D1%83%D1%80%D1%81%20%D0%90%D0%A2%D0%A1)/%D1%83%D1%87.%20%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%D0%B8%D0%B5%20%E2%84%961%20%5b%D0%B4%D0%BE%D0%BF.%5d%20(4%20%D0%BA%D1%83%D1%80%D1%81%20%D0%90%D0%A2%D0%A1).pdf
[10] DotNetRu: https://dotnet.ru/communities
[11] В 2018 году нас было 4 города: https://habr.com/ru/company/jugru/blog/334476/
[12] через два года уже 13: https://habr.com/ru/company/jugru/blog/482428/
[13] качественную коллекцию видео-лекций: https://www.youtube.com/DotNetRu
[14] новостной канал DotNetRu: https://t.me/DotNetRu
[15] обсуждение насущных вопросов DotNetRuChat: https://t.me/DotNetRuChat
[16] свяжитесь с нами: mailto:hi@DotNet.Ru
[17] Источник: https://habr.com/ru/post/493782/?utm_source=habrahabr&utm_medium=rss&utm_campaign=493782
Нажмите здесь для печати.