- Методы борьбы со сложностью
- Сложность систем и способы «борьбы» с ней
- Классические способы борьбы со сложностью
- О чем это все в конце концов
- Закон сохранения сложности
- Количественная сложность
- Сложность восприятия
- Сложность изменения кода
- Алгоритмическая сложность
- Структурная сложность
- Сложность обучения
- Сложность понимания задачи
- Вместо заключения
Методы борьбы со сложностью
Обеспечение надежности — основной мотив разработки программных средств.
Рассмотрим теперь общие принципы обеспечения надежности ПС, что, как мы уже подчеркивали, является основным мотивом разработки ПС, задающим специфическую окраску всем технологическим процессам разработки ПС. В технике известны четыре подхода обеспечению надежности [3.11]:
· обеспечение устойчивости к ошибкам.
Целью подхода предупреждения ошибок — не допустить ошибок в готовых продуктах, в нашем случае — в ПС. Проведенное рассмотрение природы ошибок при разработке ПС позволяет для достижения этой цели сконцентрировать внимание на следующих вопросах:
· борьбе со сложностью,
· обеспечении точности перевода,
· преодоления барьера между пользователем и разработчиком,
· обеспечения контроля принимаемых решений.
Этот подход связан с организацией процессов разработки ПС, т.е. с технологией программирования. И хотя, как мы уже отмечали, гарантировать отсутствие ошибок в ПС невозможно, но в рамках этого подхода можно достигнуть приемлемого уровня надежности ПС.
Остальные три подхода связаны с организацией самих продуктов технологии, в нашем случае — программ. Они учитывают возможность ошибки в программах. Самообнаружение ошибки в программе означает, что программа содержит средства обнаружения отказа в процессе ее выполнения. Самоисправление ошибки в программе означает не только обнаружение отказа в процессе ее выполнения, но и исправление последствий этого отказа, для чего в программе должны иметься соответствующие средства. Обеспечение устойчивости программы к ошибкам означает, что в программе содержатся средства, позволяющие локализовать область влияния отказа программы, либо уменьшить его неприятные последствия, а иногда предотвратить катастрофические последствия отказа. Однако, эти подходы используются весьма редко (может быть, относительно чаще используется обеспечение устойчивости к ошибкам). Связано это, во-первых, с тем, что многие простые методы, используемые в технике в рамках этих подходов, неприменимы в программировании, например, дублирование отдельных блоков и устройств (выполнение двух копий одной и той же программы всегда будет приводить к одинаковому эффекту — правильному или неправильному). А, во-вторых, добавление в программу дополнительных средств приводит к ее усложнению (иногда — значительному), что в какой-то мере мешает методам предупреждения ошибок.
Мы уже обсуждали в лекции 2 сущность вопроса борьбы со сложностью при разработке ПС. Известны два общих метода борьбы со сложностью систем:
· обеспечения независимости компонент системы;
· использование в системах иерархических структур.
Обеспечение независимости компонент означает разбиение системы на такие части, между которыми должны остаться по возможности меньше связей. Одним из воплощений этого метода является модульное программирование. Использование иерархических структур позволяет локализовать связи между компонентами, допуская их лишь между компонентами, принадлежащими смежным уровням иерархии. Этот метод, по-существу, означает разбиение большой системы на подсистемы, образующих малую систему. Здесь существенно используется способность человека к абстрагированию.
Источник
Сложность систем и способы «борьбы» с ней
Все мы так или иначе проектируем и реализуем системы. Будь то программные комплексы, инфраструктурные или платформенные решения. И в рамках этой работы мы постоянно сталкиваемся с понятием «сложной системы». В рамках этой заметки я хочу поделиться своим видением на сложность систем и «борьбу» с ней.
Начнем с определения системы. Мне нравится определение данное в книге System Architecture. Strategy and Product Development for Complex Systems. Перевод звучит примерно так:
Система, это набор компонентов и их связей. Функциональность всей системы больше, чем сумма функциональностей отдельных ее составляющих.
Это очень важное определение. Оно говорит о том, что система должна генерировать «полезность». Если система не дает прироста «полезности», в сравнении с компонентами, ее составляющими, то, вероятно, такая система не очень нужна.
Следующий вопрос, который можно себе задать — а что же такое «сложная система». Можно много рассуждать на этот счет, но на мой взгляд сложной можно назвать систему, которую сложно оценить умом, с которой сложно работать, сложно понять, сложно держать в голове все взаимодействия, которые происходят в этой системе.
И тут для нас, как инженеров, важно иметь механизм, какой-то способ, позволяющий эту сложность измерить. В качестве базы для этого механизма ребята из MIT предлагают использовать широко известное «магическое число семь плюс минус два». На эту тему есть оригинальное исследование, а так же статьи на хабре и презентации TED. В двух словах, идея всех этих исследований состоит в том, что «рабочая память» человека может одновременно удерживать и работать с ограниченным числом различных объектов. Тут очень важно понятие «различных» объектов, поскольку мозг борется со сложностью группируя объекты. Например, связи между объектами одинакового вида или типа можно держать в голове как одну связь. Или, более наглядно — не надо представлять себе систему из кучи перемешанных шариков разных цветов. Достаточно просто сгруппировать их в голове, сказать, что есть, скажем, пять красных шариков, семь желтых и три синих. Это упрощает работу с системой, уменьшая количество объектов с пятнадцати до трех. Поэтому в контексте оценки сложности мы говорим именно о разных объектах, атомарных, которые невозможно сгруппировать.
В конечном итоге есть разные оценки емкости рабочей памяти. Кто-то говорит о четырех объектах, кто-то — о пяти, кто-то — о семи. В своих рассуждениях я буду придерживаться классического подхода — «семь плюс минус два».
Исходя из этих оценок можно сказать, что если с системой становится сложно работать, удерживать ее компоненты и связи в памяти, то, видимо, она превышает тот самый предел емкости в «семь плюс минус два». Это в свою очередь означает, что это самое «магической число семь» можно использовать как базовую оценку сложности системы. Я думаю, что следующее, пока, промежуточное определение, имеет право на жизнь:
Сложная система, это система состоящая из 7+-2 атомарных компонентов и их связей в различных соотношениях.
Классические способы борьбы со сложностью
Теперь давайте вкратце вспомним классические способы или инструменты борьбы со сложностью на этапе проектирования. Их немного: абстракция, декомпозиция, иерархия и иерархическая декомпозиция.
- Абстракция — способ, который позволяет выделить основную функцию системы или подсистемы и скрыть содержимое
- Декомпозиция — способ разбиения системы на блоки меньшего размера или составляющие
- Иерархия — способ разбиения системы на уровни, где уровни имеют определенное место в структуре и располагаются одни над другими
- Иерархическая декомпозиция — способ, объединяющий иерархию и декомпозицию
Все эти средства в конечном итоге призваны упростить отдельные подсистемы нашей системы таким образом, чтобы при работе с каждым отдельным блоком он «влезал в голову» целиком.
О чем это все в конце концов
Что же все эти вещи нам дают? Попросту говоря, идея состоит в том, чтобы, используя различные методы, преобразовать неструктурированный набор компонентов системы, к некоему структурному виду. При этом, памятуя о магической семерке, можно сказать, что каждый блок в декомпозиции не должен содержать больше чем семь плюс/минус два элемента. Иначе, при детальном рассмотрении такого блока, его будет сложно контролировать.
С другой стороны, если мы имеем систему с большим количеством блоков, разбитых на иерархические уровни, то количество таких уровней, желательно, не должно превышать семи (плюс/минус два). В качестве иллюстрации хочу привести слайд из Fundamentals of Systems Engineering. Как видно из слайда, сложность системы растет с ростом количества уровней декомпозиции.
Таким образом правильный процесс проектирования системы можно описать примерно следующим тезисом:
Не стройте сложные системы. Стройте системы с необходимым уровнем сложности.
Источник
Закон сохранения сложности
Автор: Игорь Ткачёв
The RSDN Group
Опубликовано: 06.12.2002
Исправлено: 07.08.2009
Версия текста: 2.0
Существует множество практик, принципов, паттернов и прочих страшных слов, которые мы используем в повседневной профессиональной деятельности и очень часто даже не задаём себе вопрос, зачем мы это делаем. Зачем это всё нужно, плохо это или хорошо, когда плохо и когда хорошо. Зачем нужны все эти принципы? На самом деле ответ до банального очевиден. Всё это в конце концов направлено на борьбу со сложностью разработки ПО. Теперь пришла очередь задать вопрос – а что же такое сложность и как знание того, что это такое, поможет нам лучше понять и использовать принципы, которые как раз и направлены на борьбу с ней?
С ответом на этот вопрос всё немного сложнее . Сложность – понятие многогранное, неоднозначное и местами противоречивое. Кратко сложность можно определить как меру усилий, требуемых для решения поставленной задачи. При работе с кодом мы чаще всего имеем дело со следующими видами сложности:
- количественная сложность – много букв;
- сложность восприятия кода;
- сложность изменения (гибкость) кода;
- алгоритмическая сложность или интеллектуальная сложность – речь идёт, грубо говоря, об умственных способностях того, кто решает поставленную задачу;
- структурная сложность — сложность архитектуры программы;
- сложность обучения или порог вхождения – минимально необходимый уровень знаний и умений для понимания конкретного способа решения задачи;
- сложность понимания поставленной задачи.
Дело усложняется ещё и тем, что все эти виды сложности взаимосвязаны и не имеют чёткой границы, да и сама сложность может быть как объективной, так и из разряда «назло маме отморожу уши». К счастью, уши нас не будут интересовать вовсе, а вот объективная сложность представляет определённый интерес. Как бороться с объективной сложностью? Главная проблема, на мой взгляд, заключается в том, что не существует способов абсолютного устранения объективной сложности. Сложность можно уменьшить, увеличить, поделить и преумножить, можно создавать её на пустом месте, особенно назло маме, но её нельзя устранить бесследно. Сложность можно трансформировать в другие виды, оптимизировать, перераспределить и, в конце концов, получить над ней контроль. Фактически, именно на это направлены все принципы разработки софта – на трансформацию сложности, на оптимизацию усилий, требуемых для решения поставленной задачи. Трансформируя сложность, мы получаем возможность лучше контролировать её и, как следствие, лучше контролировать разрабатываемый код. А это и есть наша главная цель – мы должны контролировать код, а не код нас .
Трансформирование сложности означает одну простую вещь – устраняя сложность в одном месте, мы всегда добавляем её где-то в другом. Если этого не происходит, то, скорее всего, мы просто пока чего-то не видим и не осознаём. Сложность никогда не уходит без следа, она трансформируется и сохраняется в виде других видов сложности. Давайте условно назовём это законом сохранения сложности .
ПРИМЕЧАНИЕ Во избежании недоразумений сразу хочется успокоить тех, кто слишком буквально воспринимает название статьи – автор вполне осознаёт, что он не открыл новый закон природы, более того, он понимает, что в строго научном смысле такого закона не существует, а выбор заголовка в большей степени обусловлен пристрастием автора к пафосным и провокативным названиям. Поэтому не стоит искать и не находить в статье формальные определения и доказательства. Такой цели автором не ставилось. Задача статьи – рассмотреть виды сложности, лучше понять их природу, неочевидные стороны и влияние на разрабатываемый код. Из закона сохранения сложности следует один простой вывод – не существует идеальных способов борьбы со сложностью. Нельзя применить какой-то принцип или паттерн и только уменьшить сложность. Мы всегда чем-то платим. Но теперь, зная это, мы можем довольно точно оценить соотношение платы и полученной выгоды и принять решение об адекватности применения того или иного решения в каждой конкретной ситуации. Рассмотрим каждый вид сложности отдельно. Количественная сложностьЭто когда задача требует написания большого количества кода. Если этот код в своей массе является однотипным, то с такой сложностью можно бороться с помощью практик повторного использования кода. Иногда это работает хорошо, иногда не очень. Повторное использование уменьшает: количественную сложность, сложность восприятия кода, сложность изменения и алгоритмическую сложность кода, но увеличивает для использующего такой код сложность обучения, для пишущего – практически все виды сложности. Когда это плохо работает? Это плохо работает, когда мы пытаемся заранее выделить код, который по идее можно повторно использовать, но на практике его повторно использовать не получается. Дополнительную сложность в решаемую задачу мы внесли, а взамен ничего не получили. Ещё это плохо работает, когда у автора такого кода недостаточно опыта и/или мозгов. Не секрет, что написание библиотек и повторно используемых компонентов – это тоже скил, который нужно прокачивать. Если делать это без мозгов, то в лучшем случае таким кодом никто не будет пользоваться, а в худшем – усложнит и восприятие, и изменение везде, где такой код используется. С другой стороны, качественный набор повторно используемых компонентов (библиотек) может дать существенное уменьшение сложности. Возьмём, например, класс System.String. Этот класс прост в использовании и самодостаточен. Вся сложность работы со строками перенесена из нашей задачи в головы инженеров из Microsoft и сотни серверов, которые 24×7 тестируют работоспособность этого класса во всех возможных версиях и конфигурациях. Для нас это уменьшение сложности иногда почти бесплатно, но это не означает, что оно бесплатно совсем. Это касается фактически любых инструментов, которые мы используем для решения наших задач. Современные операционные системы, фреймворки, библиотеки, среды разработки и исполнения, языки программирования – развитие всего этого направлено на то, чтобы как можно больше сложности убрать из наших приложений и перенести её в эти инструменты. За это мы платим иногда деньгами, и всегда увеличением сложности обучения, но в отношении хороших инструментов оно того стоит. Сложность восприятияЭтот вид сложности в первую очередь относится к форме кода, а не к его содержанию. В принципе, здесь всё просто – открываем код в определённом месте и засекаем секундомером, сколько времени нам понадобилось на то, чтобы понять, что и как он делает. Сложность восприятия зависит от множества факторов. Оформление кода, следование соглашениям об именованиях, запутанность/ясность алгоритма, выразительность используемых средств, поддержка среды разработки, включая подсветку кода и навигацию, и многое другое. Также сложность восприятия имеет непосредственное отношение к алгоритмической сложности и порогу вхождения. Чтобы не путать этот вид сложности с другими, достаточно представить, что у вас отобрали любимую игрушку, как-то Visual Studio, IDEA или прочий Eclipse, и заставили править в NotePad чужой, плохо отформатированный, без намёка на какие-либо соглашения об именованиях код, отняв при этом все моноширинные шрифты и заменив половину табуляций пробелами. Представили? Жуть! Сложность изменения кодаПростота восприятия кода не означает простоту изменения кода. Конечно же, простой код легче менять, но главная характеристика сложности изменения кода – это его гибкость. Простой код не всегда гибкий, а гибкий – не всегда простой. И это лишний раз подтверждает вывод о том, что сложность нельзя только уменьшать, её можно трансформировать. Многие практики и паттерны направлены на увеличение гибкости кода и, как правило, они же увеличивают сложность восприятия кода и повышают порог вхождения. Возьмём любой паттерн из этой серии, например, IoC или Visitor. Некоторые реализации IoC вносят в код дополнительные сущности, делают алгоритм менее очевидным и усложняют восприятие кода, в частности, навигацию по нему. Visitor разбивает цельный алгоритм обработки иерархической структуры данных на много частей, что, в свою очередь, существенно увеличивает сложность восприятия кода. И так всегда. Убрали в одном месте, добавилось в другом. Стоит ли тогда вообще добиваться тотальной гибкости кода? Решать нужно в каждом конкретном случае отдельно. Иногда можно получить существенный выигрыш и усилить контроль над кодом, иногда можно привнести дополнительную сложность в код, ничего не получив взамен, и окончательно потерять контроль над ним. Главное – помнить, что в соответствии с выведенным нами законом сохранения сложности абсолютно каждый паттерн одновременно является и антипаттерном, и оценивать нужно не только положительные качества паттерна, но и обязательно учитывать отрицательный эффект . В некоторых сценариях использования может оказаться, что у самого замечательного паттерна интегральная оценка будет отрицательной. Алгоритмическая сложностьЕсли совсем обобщённо, то алгоритмическая или интеллектуальная сложность – это минимально необходимый уровень интеллекта для решения поставленной задачи. В частности, более конкретно можно говорить о способности удержать задачу в голове целиком. Часто эту сложность путают с обучением, с уровнем знаний, но это не одно и тоже. Собственно, занятие программированием уже предполагает наличие определённого количества мозгов, но не секрет, что для решения одной и той же задачи разным людям требуется разный уровень умственного напряжения. Но не это главное. На самом деле алгоритмов, непосильных для простых смертных, в жизни встречается не так много. Главное то, что алгоритмическая сложность легко увеличивается в разы путём несложных манипуляций с кодом . Достигается это обычно путём смешивания реализаций разных алгоритмов в одном месте. Допустим, у нас есть входной поток данных, выходной поток данных и алгоритм их обработки. Давайте теперь поиграем в абстрактных попугаев. Предположим, мы разбираем XML, делаем с ним несложные манипуляции и генерируем HTML. Алгоритм разбора входного потока весьма прост и имеет интеллектуальную сложность, скажем, 3 попугая, алгоритм обработки данных – 4 попугая, а алгоритм генерации HTML – 2 попугая. Допустим, смешивание всего этого в одном месте даст нам число, равное произведению 3 * 4 * 2 = 24. Предположим, что наш интеллектуальный потолок равен ста попугаям. Итого, мы легко справились с поставленной задачей без особого умственного напряжения. Теперь предположим, что мы пишем компилятор, и в одном месте мы производим парсинг текста, генерируем исполняемый код и проводим некоторые оптимизации. Эти алгоритмы очевидно сложнее приведенных в предыдущем примере. Оценим каждый из них, для простоты, в 10 попугаев. Умножаем и получаем 1000, т.е. цифру на порядок превышающую наши интеллектуальные возможности. Очевидно, что в жизни всё происходит немного по другим формулам и с другими коэффициентами, но суть должна быть понятна. Чем сложнее алгоритмы, и чем больше их намешано в одном месте, тем стремительнее растёт алгоритмическая сложность решаемой задачи и тем быстрее мы теряем над ней контроль. Рассмотрим пример с single responsibility principle (SRP).
|