xam1816
хочу понять основы построения любой системы,для того чтобы продвигать и пользоваться знаниями о построении любых систем
Так все хотят так делать. То есть ты далеко не первый, кто об этом задумался, и даже далеко не сотый.
Так вот те, кто об этом думал раньше, они вот это всё придумали уже и изложили эти мысли в своих книгах. Поэтому на твоём месте я бы поискал эти книги, потому что они уже есть, а не пытался переизобретать идеи в роли такого гения, генерирующего тайные знания.
Хорошая система обладает пятью качествами (из книжки Буча по объектно-ориентированному анализу и объектно-ориентированному проектированию):
1) Иерархическая структура
2) Распределение базовых элементов по разным уровням
3) Разделение обязанностей по отдельным частям
4) Переиспользование структур и механизмов
5) Эволюция в виде цепочки стабильных состояний
Дальше примеры
1) Иерархическая структура
Что даёт иерархическая структура. Она даёт синергию и эмерджентность - появление у системы свойств, не присущих её элементам в отдельности; несводимость свойств системы к сумме свойств её компонентов.
Пример:
У нас есть полк солдат. Если перестрелять всех офицеров, этот полк превратится просто в стадо ничего не знающих и ничего не понимающих рядовых. Вроде по составу ничего сильно не изменилось, количество офицеров мало по сравнению с общей численностью, но при разрушении иерархической структуры структура стала плоской и перестала действовать как мощная единица. В то же время при грамотном командовании - при построении иерархии - это стадо образует какую-то мощь, появляющуюся из ниоткуда. Вот это “ниоткуда” и есть эмерджентность.
Соответственно, когда ты пишешь программу, не надо писать её плоской, её надо писать иерархической. Тогда она будет работать мощно.
2) Распределение базовых элементов по разным уровням
Это значит, что если система образует целое, то она должна состоять из каких-то отдельных повторяющихся элементов. Каждый из этих отдельных элементов сам по себе образует целое, поэтому он тоже должен состоять из отдельных повторяющихся элементов. Каждый из этих отдельных элементов сам по себе образует целое, поэтому он тоже должен состоять из отдельных повторяющихся элементов. И так оно идёт до самого низа, пока не дойдёт до неразложимых элементов (хотя таких нет, оно идёт бесконечно). Таким образом, самая верхняя система в качестве целого состоит как из базовых элементов уровня под ней, так и из базовых элементов подуровня уровня под ней, так и из базовых элементов подподуровня уровня под ней. И всё это происходит одновременно. То есть система состоит одновременно из разных базовых элементов, которые отделены друг от друга по уровням общности.
Пример:
У нас есть полк солдат. Полк состоит из батальонов. Батальоны состоят из рот. Роты состоят из взводов. Взводы состоят из отделений. Отделения состоят из солдат. Таким образом, полк состоит из батальонов, из рот, из взводов, из отделений и из солдат. И всё это происходит одновременно. Таким образом, полк может из одного своего батальона перекинуть какую-нибудь роту в другой свой батальон, и одновременно с этим полк может из одной своей роты перекинуть какой-нибудь взвод в другую свою роту, и одновременно с этим полк может из одного своего взвода перекинуть какое-нибудь отделение в другой свой взвод, и одновременно с этим полк может из одного своего отделения перекинуть какого-нибудь солдата в другое своё отделение. Вот это перекидывание элементов возможно благодаря тому, что они одинаковые. И при перекидывании этих элементов на разных уровнях у полка в целом ничего не меняется.
Соответственно, когда ты пишешь программу, нужно делать её такой, чтобы она поуровнево состояла из одинаковых элементов. У тебя будет идти один слой функций, под ним другой слой функций, под ним третий слой функций. И тогда, когда вдруг произошла ошибка в какой-то функции, ты просто берёшь и перекидываешь туда другую функцию. А программа при этом останется собой и ничего не заметит, просто ошибка исправится. Так вот эта функция может быть как где-то в самом низу иерархии, так и может быть где-то в самом верху иерархии. Соответственно, у тебя всегда есть доступ ко всем слоям программы.
3) Разделение обязанностей по отдельным частям
Это значит, что сами части системы должны быть как можно самостоятельнее и независимее от других частей. Для самостоятельности часть должна быть собранной и действовать в одном направлении, её не должно разрывать в разные стороны. Для независимости часть не должна пользоваться чужими силами, а должна действовать только своими силами.
Пример:
У нас есть танк. В нём сидит экипаж. И этот экипаж что делает, он хочет поразить другой танк. Один член экипажа управляет танком, чтобы подъехать к месту выстрела и правильно встать. Другой член экипажа следит за другим танком и сообщает расстояние и место, куда надо подъехать, наводит пушку. Третий член экипажа заряжает снаряд и ждёт указания, когда нужно выстрелить и зарядить новый снаряд. Они действуют в одном направлении. Наводчик не вылазит из танка и не идёт чинить гусеницу в это время, механик тоже никуда не уходит, и заряжающий тоже сидит на своём месте. Это самостоятельность танка при поражении другого танка - внутри танка всё действует так, что танк как целое сможет выполнить эту задачу сам. Второе. В танке всё есть для поражения другого танка. Ему не нужно ждать, когда ему привезут снаряды на машине, снаряды есть внутри. Ему не нужно ждать, когда приедет наводчик и подскажет, куда подъехать для выстрела, наводчик есть внутри. Ему не нужно ждать водителя, который придёт из кустов и поведёт танк к месту выстрела, водитель есть внутри. Это независимость от других машин, танков и солдат.
Соответственно, когда ты пишешь программу, ты делаешь в ней самостоятельные элементы и независимые от других элементов при этом. Поэтому в каждой функции свои переменные, а не глобальные переменные. Даже если ты думаешь “а чо они повторяются, они же одинаковые”, это не должно тебя волновать. Делать надо так, чтобы в каждой функции было всё своё. Поэтому же функция не должна представлять из себя винегрет, в котором происходит куча всяких действий, идущих в разные стороны. Все действия функции должны быть направлены на достижение одного её результата, а не нескольких. И действовать функция должна изолированно, как будто вокруг никаких других функций нет. Есть только данные, которые приходят ей на вход и есть только данные, которые выходят из неё на выходе.
4) Переиспользование структур и механизмов
Это относится к экономии ресурсов. В программных системах мы экономим время. В физических системах мы экономим как время, так и физические ресурсы. Допустим, нам нужна какая-то структура, мы её можем создать с нуля. Но мы не создаём её с нуля, а стараемся найти такую структуру, которая уже создана. То же самое касается механизма. Мы можем придумать какой-то новый механизм, но мы этого не делаем, а ищем уже существующий механизм и приспосабливаем его. Таким образом, в одной системе может использоваться одна структура для одной задачи и та же самая структура для совершенно другой задачи. Таким образом, в одной системе может использоваться один механизм для одной задачи и тот же самый механизм для совершенно другой задачи.
Пример:
По структуре. Если мы берём сапёрную лопатку, то она подходит для копания, для рубки деревьев, для замыкания электрической цепи. То есть структура у неё одна, но применима она и для строительных работ, и для каких-то минно-взрывных работ, и для каких-то боевых применений в окопах при рукопашном бое. То есть нам не нужно выдавать каждому солдату чемодан со всеми этими инструментами. Мы даём ему одну сапёрную лопатку и учим его применять её для решения разных задач. Тогда, имея одну лопатку, которую изготовить можно быстро, доставить можно быстро, отыскать где-то можно быстро, армия может сэкономить время и физические ресурсы и сам солдат может сэкономить своё время и выполнить задачу. Ему не нужно тратить время на поиск специального инструмента (а того может вообще не быть и время будет впустую потрачено), у него есть сапёрная лопатка, которой он умеет делать всё.
По механизму. Вот если мы сравним патрон для автомата и снаряд для танка, то они по механизму своей работы идентичны. То есть чтобы придумать станок, который сделает танковый снаряд, мы не должны напрягать инженеров, тратить на это их время, чтобы они придумали какой-то новый механизм. Нам достаточно просто повторить станок, изготавливающий патроны для автомата. Так мы переиспользовали уже придуманный механизм для произведения выстрела и ещё мы переиспользовали уже придуманный механизм изготовления таких компонентов для произведения выстрела. Нам не понадобилось придумывать, как делать выстрел, и нам не понадобилось придумывать, как изготавливать заряды. Всё это уже было готово и мы просто взяли готовое. Время мы сэкономили. Если подошли какие-то готовые станки, то и физические ресурсы мы тоже сэкономили, так как можем взять эти станки, пока они простаивают.
Соответственно, когда ты пишешь программу, ты должен обращаться к уже написанным программам и искать там идеи. Там есть уже какие-то готовые идеи по структурам, какие-то придуманные механизмы. Если у тебя есть какие-то проекты, они должны быть написаны так, чтобы из них что-нибудь можно было выдернуть и вставить в абсолютно другой проект. Поэтому когда ты изготавливаешь функцию, она должна быть максимально общей, хотя, на первый взгляд, это не является необходимым для данного конкретного проекта и ты будешь думать “а зачем мне напрягаться и делать её общей? мне же тут нужно только что-то сделать”. Потом, когда ты будешь делать другой проект, ты вспомнишь, что у тебя есть такая-то функция, в таком-то проекте, написанном столько-то лет назад, и она у тебя, к счастью, сделана в общем виде и подойдёт в текущий проект без какой-либо переделки - прямо через копипаст. Так ты сэкономишь время и за минуту сделаешь работы на день. Бывает и неделю экономишь, потому что копировать можно не только маленькие функции, но и целые компоненты, состоящие из множества модулей и библиотек функций. И всё это нужно делать в одном проекте, чтобы у тебя проект был компактным, но чтобы мог делать разные вещи через одни и те же средства. Тогда и изучение проекта будет занимать меньше времени.
5) Эволюция в виде цепочки стабильных состояний
Это означает, что систему нельзя делать сразу в конечном виде. Это не получится сделать, как бы там тебе не казалось обратное. Любая продвинутая система появлялась постепенно. Она проходила ряд хорошо отделённых друг от друга стабильных состояний, в которых она подолгу находилась. Она приходила в какое-то состояние, находилась в нём долго, потом постепенно начинала из него выходить, выходила всё больше и больше, в какой-то момент она начинала скатываться к новому состоянию и потом она приходила в новое состояние окончательно, в котором она опять надолго останавливалась. И так потом повторялось снова. Это неизбежный процесс формирования системы в каком-то виде. Его нельзя проскочить.
Пример:
В армию приходит новобранец. И кто-нибудь говорит “а давайте сделаем из него не генерала армии, а хотя бы командира роты”. Ну, не хватает им там людей. И его начинают учить, тетрадку там дают какую-то, чтобы он записывал. Потом его одевают в офицерскую форму, дают пистолет с кабурой и фуражку, ставят перед ротой и говорят всей роте “это теперь ваш командир”. Допустим, он хороший человек, он хотя бы не будет их там мучать, как-то будет поправляться, если будет видеть, что делает что-то неправильно, признавать свои ошибки. Но вот нужно будет покормить роту, а для этого надо будет почистить картошку. А он не знает, как это происходит, потому что он в тетрадке только это записывал, из него же командира роты делали. И он не накормит солдат. То есть это даже не боевая задача, а он уже не вывозит её. Он должен был сначала сам всё это проходить. Месяцами выполнять эти задачи, переходить из рядового в ефрейтора, потом ефрейтором выполнять задачи посложнее. И так он вырастает, постепенно переходя из одного состояния в другое и находясь в каждом отдельном состоянии подолгу, а не по одному дню и так далее. И только после этого он вырастет и станет командиром роты, который был во всех этих состояниях. Невозможно сделать его командиром роты, не проведя его через все необходимые промежуточне состояния в тех временных промежутках, которые для этого необходимы.
Соответственно, когда ты пишешь программу, ты не должен делать из неё сразу многофункциональный комбайн. У тебя это не получится, хотя тебе будет казаться, что “вот оно, да я щас напишу, да оно сразу взлетит”. Не взлетит оно. Прохождение промежуточных состояний у программы неизбежно. Когда она будет проходить свои состояния, это будет видно очень отчётливо, ты не спутаешь две соседние версии. В каждом состоянии она просуществует долгое время, ты не сможешь сказать “а всё, неделя прошла, можно её дальше писать”. И с каждой версией настоящей, а не той, которую ты пытаешься налепить искусственно, ты будешь видеть большой отрыв в мощности программы.
Поэтому ты пишешь сначала просто набросок. Потом он работает у тебя. Потом ты этот набросок дорабатываешь немного. Потом он работает у тебя. Потом ты этот набросок доработанный дорабатываешь ещё немного. Потом он работает. Потом ты всё это повторяешь. И потом уже ты увидишь, что программа сделала шаг. Вот там она точно была гусеницей, а вот там она точно была коконом, а вот там она точно стала бабочкой. Их не перепутаешь.
tags: system grady booch