Есть область где подобное действия не только вызывали бы скептицизм, но могут преподносится как единственно верные – объектно-ориентированное программирование.
Начнем мы, как и положено, с самой слабой из парадигм «принципов SOLID», которые хорошо, что в граните не выбиваю. Приготовитесь много букв…
Дадим краткое определение: SOLID сокр. от англ. single responsibility, open-closed, Liskov substitution, interface segregation и dependency inversion. Т.е. достаточно небольшой набор принципов из всего многообразия, но проводящие четкие границы между тем что можно делать, что лучше не делать, а что надо точно переделывать в Вашем проекте.
Начнем.
Принцип подстановки Барбары Лисков
L, LSP, Liskov substitution principle
«объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы»
Один из самых слабых и не выполнимых принципов, предписывающий что мы можем заменить абстракцию на её подтип, при этом ничего не должно произойти. Мне даже сложно представить, что-то более далекое от реальности и не возможное выполнить уже из самого определения. (Очень интересно услышать бы определение «правильности выполнения программы», но Нам это и не потребуется).
Subtype Requirement: Let f(x) be a property provable about objects x of type T. Then f(y) should be true for objects y of type S where S is a subtype of T.
Оперирует не вообще программой, а свойством одного типа. Что конечно меньше, но сути-то не меняет. В ООП множество типов определяют работу программы.
Считать ли длительность выполнения подпадающим под необходимость следовать этому принципу. Да, время работы приложения является таким же контрактом (свойством), как и его входные параметры и результат выполнения, как его пред- и пост- условия, список возможных исключений. Часто на него не обращают внимание, как на трудного подростка, а вдруг ничего и не случиться. Хорошо если так и оказывается, но тогда и принцип тут ни причем – чистое везение, не очень-то и нужно было, не очень-то и хотелось.
Пойдем дальше.
Размер потребляемых ресурсов: памяти, процессорного времени, используемых ядер, необходимость или отсутствие доп. оборудования.
Иногда замена одной сущности, к примеру, способа логирования из файла в базу данных требует не тривиальных вещей: подключения дополнительных библиотек, изменения конфигурационных файлов (Предвижу спор, считать ли конфигурационный файл частью, подпадающей под этот принцип или нет).
Если конечно покопать, то конечно можно уменьшить требования: мол не для всех типов, не для всех свойств, да и не всегда. Но тогда и суть принципа очень размыта. Что-то из разряда «Вы можете переходить только на зелёный свет, но если сломался светофор … или в прямой видимости на расстоянии двух километров не видно машин … или пролетает единорог ^_^''»
Как проектировщики или разработчики Мы уже идем на риск (часто оправданный) нарушая Принцип подстановки Барбары Лисков когда оптимизируем или рефакторим код.
Данный принцип не выполним никогда, простая замена Int32 на Int64 уже делает его не выполнимым. Надеюсь обратная замена Int64 на Int32 не вызывает ни у кого сомнений его нарушения.
Фанатично придерживаясь его, Вы создается что-то уже «мертвое», поскольку оно не «дышит» при рождении и не когда не задышит после. Вы сами с завидным упорством будете отрезать всё «лишнее», чтобы не менять поведения нового. Однажды создав нечто, Вы обречены создавать тоже и в будущем. Поздравляю Вас, Вы создали труп и бережно следите за множеством рук в резинных перчатках.
Принцип открытости/закрытости
O, OCP, Open/closed principle
«программные сущности… должны быть открыты для расширения, но закрыты для модификации»
Если предыдущий можно как-то довести до возможности реального применения, то тут без вариантов. В пределе Вы не можете изменить свой код сразу после того как поднимите руки над клавиатурой.
Возможно Вас утешает что можно изменить код: до тестирования, до передачи в продакшен, до выпуска версии, до … (выберите несколько, или подчеркните нужное, или допишите своё). Не важно, у Вас всегда будет это «ДО». Слабое утешение в системах, жизнь которых не исчисляется часами и неделями, а длиться годами.
Вы когда-нибудь наблюдали чтобы в природе, что-то развивается «до»? Не вырастает дерево два метра ровно, всегда чуть выше, чуть ниже. И пока его не срубят будет расти, какая-то ветка засохнет, какая-то разовьётся. Единственно что не достижимо в реальном мире – постоянство.
Зачем далеко ходить: C => C++ => (Java =>) C# и Java => Scala => Kotlin попытки избежать эту проблему, а не решить. Вынужденные придерживаясь это принципа Вы рано или поздно сядете и перепишите всё, если, конечно, к тому времени Ваш проект уже не протух настолько, что его закопали.
Данный принцип ставит окончательный и жирный крест на Вашем проекте в самом его начале. Используя его Вы создаете Франкенштейна, в надежде что никто не заметит трупный запах.
Оставшиеся принципы можно оставить для следующей статьи (если конечно она последует)…
Вывод прозаичен. Современная разработка походит на создание в кунсткамерах тупиков и дружное их препарирование в процессе создания, внедрения и сопровождения проектов. Поэтому часто даже самого понятия «утилизации» (завершение, снятия с эксплуатации) нет во многих жизненных циклах методологий. Или им уделяется пара параграфов (Для примера посмотрите на библиотеки по хирургии, когда действительно стараются оставить в «живых»).
Зачем писать о том, что и так уже было создано и так тщательно скрывается, дезодорируется и препарируется. Когда каждому из участников действия, кому раньше, кому позже, становится ясно что «пора».
Надежда?
Но что делать, спросите Вы? (Если конечно дочитали и задумались). Создание «мутантов» тоже не выход. Если нечто изменяет своё поведение непредсказуемо, без надобности, то обычно оно либо умирает само, либо его удаляют.
Ответ достаточно прост – смотри вокруг, выползите из под груды принципов и методологий. Прислушивайтесь к советам, но думайте, когда время следовать им, а когда нарушать. Соблюдайте тот контракт, который нужно, но не более того. В конце концов ни многим интересно что делает ваш код, если он работает корректно и ожидаемо.
Если кто-то обошел вашу «фичу» лучше пусть он удалить этот костыль, ставший не нужным, чем Вы дальше будете с упорством продолжать поддерживать ставшее ошибочным решение. (Хорошая аналогия с пациентом, не желающим оставить ставшие не нужными костыли после снятия гипса, он же к ним привык. Удобно, а нога болит при ходьбе)
Если кто-то (клиент, менеджер, PM) требует остаться на месте, заведите для него версию и пусть препарирует, помогайте иногда ему (особенно если он платит). Но не стесняйтесь показывать более удачное решение. Дайте человеку шанс изменить решения.
Не уходите в бесконечный цикл. Нет смысла оправдываться отсутствием результата желанием создать вечное, это невозможно. Создайте малое, но нужное сейчас и «живое».
Остерегайтесь, когда в следующий раз Вы будите обучать, наставлять и требовать выполнение поставленных задач. Посмотрите не глядит ли Вам в глаза «безумец», который будет «препарировать труп» в полной уверенности что спасает «мир».
Не создавайте трупы. Развивайтесь или умрите.
Комментарии (85)
lair
22.08.2016 12:29+11LSP, как принцип, очень прост: наблюдаемое (и интересующее наблюдателя) поведение используемой сущности не должно зависеть от реализации. Естественно, что в каждом конкретном случае это принцип "замыкается" на конкретном интересующем нас наборе поведений/свойств (которые мы и включаем в контракт).
Таким образом, если рассматривать этот принцип без фанатизма, то он вполне выполним. Типичный пример: а давайте создадим
Stream
, в котором будетRead
иWrite
. Через полгода приходит кто-то и делает read-only-поток, в которомWrite
бросает исключение. Здравствуй, нарушение LSP (причем еще на фазе проектирования интерфейса). Как чинить? Или (более плохой вариант, я смотрю на тебя BCL!) заранее заложитьCanRead
/CanWrite
, поведение которых согласовать с методами, или же (вариант получше) сделать отдельноReadableStream
иWriteableStream
(но тогда нужен более мощный язык, который бы позволил сказать, что мы хотим на входе параметр, реализующий и то, и другое). Все, с точки зрения интересующего пользователя поведения LSP выполнен.
С другой стороны, естественно, есть множество классов в любом фреймворке, которые LSP нарушают — какие-то из-за плохого дизайна, какие-то от безысходности. Делает ли это сам принцип хуже? Да нет, он все так же остается, он все так же полезен при проектировании.
PS
Данный принцип не выполним никогда, простая замена
Int32
наInt64
уже делает его не выполнимымНу так одно и не подтип другого, вообще-то.
bitec
23.08.2016 17:09По поводу Stream примера — вроде еще третий путь возможен: вынести read и write методы в отдельные интерфейсы и сделать Stream расширяющим их? В этом случае LSP не ломается.
А вообще очень хорошо сказано, дополню только, что выполнение LSP зависит напрямую от опыта. На моей практике первые пять лет разработчики о нем в лучшем случае не думают, в худшем — попросту не знают. И работают отлично, создают кучу кода, считая себя опытными зубрами. Вот только в последствии всем их наследникам приходится разгребать недостатки LSP с помощью шлака наподобие
if (o instanceof SomeChild) { // do some implementation specific processing }
На моей практике именно парадигма «if-instanceof» чаще всего служит признаком сломанных LSP принципов. Если их придерживаться при проектировании/рефакторинге иерархий классов, то с абстракцией всегда можно работать напрямую без ветвлений.
Собственно продолжу мысль: даже многие опытные разработчики не ведают о LSP принципе. Но большая часть старших программистов все-таки начинает мыслить именно в этом русле — как только встает необходимость отнаследовать класс или реализовать интерфейс (наследование все-таки сложнее и чаще приводит к ошибкам) мысленно прогоняет наследник/реализацию через LSP принципы. Это как работа с массивами или ссылками — опытный программист всегда автоматически проверит индекс на попадание в диапазон или ссылку на предмет Null, тогда как начинающий непременно закодит алгоритм без нужных проверок и отхватит проблемы в продакшнеlair
23.08.2016 17:10По поводу Stream примера — вроде еще третий путь возможен: вынести read и write методы в отдельные интерфейсы и сделать Stream расширяющим их?
Это не третий, это как раз второй и есть — два раздельных интерфейса, каждый со своими возможностями. Но, повторюсь, когда вы захотите сказать "я хочу, чтобы мой метод принимал поток, умеющий читать и писать" — вам будет весело.
bitec
23.08.2016 17:13Сорри, маленько не понимаю — если мой метод принимает Stream, который расширяет WriteStream и ReadStream — значит, он умеет делать обе вещи и я могу им пользоваться легко? В случае, конечно, если реализация не бросает UnsupportedOperation в одном из методов, о чем Вы упомянули прежде.
lair
23.08.2016 17:15… а теперь вспомните, что у потоков еще есть третья операция —
Seek
. И надо вам, значит, поток, который умеет произвольный переход и чтение, а на запись вам наплевать...bitec
23.08.2016 17:23Ну это уже другая тема, комбинаторный взрыв называется, первый признак, что наследование желательно заменить другими паттернами типа Bridge или Decorator.
Просто применительно к первому примеру со стримом, который сделали readonly через бросание эксепшна и тем самым нарушили LSP — чинится легко для простого случая с двумя методами. В-целом, выдерживать LSP не так сложно, если знать некоторые простые правила, особенно по части обращения к внутренним полям суперкласса. Выдерживать LSP относительно реализаций интерфейса проще, если не ломать контракты его методов.lair
23.08.2016 17:30Ну это уже другая тема, комбинаторный взрыв называется
Комбинаторный взрыв — это следствие. А причина — это интерфейсы по возможностям и слабые языковые средства.
первый признак, что наследование желательно заменить другими паттернами типа Bridge или Decorator.
Во-первых, о наследовании речи не шло, только о реализации интерфейсов. А во-вторых, как вам здесь помогут эти паттерны?
AndreyRubankov
24.08.2016 10:05+1UnsupportedOperation — это первый признак нарушения LSP.
А так же, в сигнатуре метода не стоит объявлять конкретный класс, т.к. дальше этот метод не сможет быть переиспользован с другими стримами. Тут нужен композитный интерфейс, который наследуется от обоих, и все имплементации должны идти от композитного.
INC_R
23.08.2016 23:05Подумалось, что на шарпе этого добиться не так сложно с использованием дженерик-методов. Тогда не надо на каждую комбинацию требуемых интерфейсов создавать новый класс.
public interface IReadable { } public interface IWriteable { } public class ReadableWriteable : IReadable, IWriteable { } public void ReadWrite<T>(T stream) where T : IReadable, IWriteable { ... } void Call() { ReadWrite(new ReadableWriteable()); }
lair
23.08.2016 23:26Да, можно, и я так делал. Но есть проблема: если у вас метод дженерик не только для этого, но еще и по делу, и его параметр не выводился (т.е., было какое-нибудь
Read<T>
), то вам придется типы указывать явно. Что не ужасно, но раздражает.
Emiya
22.08.2016 12:45-6С другой стороны, естественно, есть множество классов в любом фреймворке, которые LSP нарушают — какие-то из-за плохого дизайна, какие-то от безысходности. Делает ли это сам принцип хуже? Да нет, он все так же остается, он все так же полезен при проектировании.
Ключевое слово полезен, но не выполним. Полезно знать, что не плохо было бы если бы…, но полезно знать, что и не выйдет даже если…
Данный принцип не выполним никогда, простая замена Int32 на Int64 уже делает его не выполнимым
Ну так одно и не подтип другого, вообще-то.
Как сказать, куда смотреть. В C# оба наследники (через костыли конечно), но Оbject.
К тому же что мешает реализовать в проекте более лучшее решение, работающее быстрее, точнее, чем работа с Int32. А затем, спустя время, «наследника». Будете использовать делегирование, наследование?lair
22.08.2016 12:57+2Ключевое слово полезен, но не выполним.
Почему невыполним-то?
Как сказать, куда смотреть. В C# оба наследники (через костыли конечно), но Оbject.
Но друг друга они не наследники, поэтому контракт, явно использующий одно, другое не ожидает. Все логично.
К тому же что мешает реализовать в проекте более лучшее решение, работающее быстрее, точнее, чем работа с Int32. А затем, спустя время, «наследника». Будете использовать делегирование, наследование?
Я вообще не понял, что вы спрашиваете. Ничто не мешает. Буду использовать то, что подходит под задачу.
Emiya
22.08.2016 13:20-5Почему невыполним-то?
Вы создаете решение, описываете поведение и контракты, явно или не явно. Часто не явно, поскольку ожидаете некоторого поведения от фрейморков, своих починных, своей группы.
Но как только это необходимо (требования ТЗ, изменения окружения, появления или уход сотрудника) Вы нарушаете его, помочу что выбора то у Вас и нет. В последствии, даже если, у Вас появится время (не занятое ни одним другим проектом) для изменений будете ли Вы приводить своё решение к этому принципу – НЕТ (оправдание: у нас нет времени, есть более значимые задачи). Электрической розетки без разницы зачем Вы в ней суете вилку (столовую), оно без лишних напоминаний напомнит закон Ома.
Можете Вы создать хорошее решение без – ДА, ВОЗМОЖНО. С ним – ДА, ВОЗМОЖНО.
Мешает ли Вам принцип – ИНОГДА.
Нужно ли ему следовать – ДА, КОГДА ОН НЕ МЕШАЕТ. НЕТ, КОГДА МЕШАЕТ.
Выполним ли принцип – НЕТ. Если Вас это утешит, можно написать ДА, КОГДА ОН НЕ МЕШАЕТ.lair
22.08.2016 13:24+3Выполним ли принцип – НЕТ.
Нигде в вашем комментарии нет обоснования этого утверждения.
Ну и да, все, что вы описали, можно применить к, без исключения, любому правилу разработки: как только "необходимо", оно нарушается.
В последствии, даже если, у Вас появится время (не занятое ни одним другим проектом) для изменений будете ли Вы приводить своё решение к этому принципу – НЕТ (оправдание: у нас нет времени, есть более значимые задачи)
Здесь вы ошибаетесь дважды. Во-первых, ваше утверждение противоречит само себе: если у меня есть время, не занятой ни одним проектом, оправдание "у нас нет времени" невозможно. Во-вторых, вы только что сказали, что технический долг невозможно отдать — что, очевидно, неправда.
Hokum
22.08.2016 13:01Ну так они оба наследники от Object, соответственно для правильного сравнения код должен работать не с Int32, а только с Object. Тогда можно свободно вместо Int32 использовать Int64 и прочие наследники от Object.
Emiya
22.08.2016 13:23Как ни странно, если я заменю в большей части проектов Int32 на Int64 вообще ничего не произойдет. Даже авто тесты не повалятся. Только принцип тут не причем.
Да получалось так что всё работает. Придерживались ли его при создании этого кода – не думаю.Hokum
22.08.2016 15:50+2Согласен, что при замене Int32 на Int64 может все работать нормально, можно даже заменить Int да Double и, в некоторых случаях, будет работать нормально. Я просто хотел указать, что пример не подходит для описания принципа. LSP говорит, что если вместо базового класса, будет передан наследник. А Int32 и Int64 это две параллельные иерархии наследования от Object.
yarosroman
23.08.2016 01:29Ничего они не наследники, Int значимый тип, а значит структура, а к типу object приводится путем упаковки. Да и в С# структуры не наследуются. Так, что Int32 и Int64 разные типы, как и int и double, один можно привести ко второму, а бругой без потерь нет.
Vilyx
22.08.2016 12:45+7Десятки лет разработка ПО велась без принципов и методологий, путём проб и ошибок наш брат дошёл до того, что мы имеем сейчас. А вы предлагаете вернуться в каменный век, где у каждого свои методологии и архитектуры? И делаете такое предложение на основе собственного непонимания принципов SOLID.
Liskov substitution principle говорит лишь о том, что функция, использующая базовый тип, должна иметь возможность использовать подтипы базового типа, не зная об этом. Если вы создали подтип, который имеет побочные эффекты выполнения, которых не должно быть при использовании базового класса, но на фактический результат работы это не влияет, т.е. конечный пользователь приложения всё так же доволен, то принцип выполнен.
Принцип открытости/закрытости говорит о том, что создав какое-то API мы должны дать возможность расширить функционал путём наследования или как-то ещё, но воспрепятствовать изменению поведения базового типа.Emiya
22.08.2016 12:56-6А вы предлагаете вернуться в каменный век, где у каждого свои методологии и архитектуры?
Где сказано, что нужно вернутся, Сказано сделайте шаг вперед.
Если Вы придерживаетесь принципов SOLID у Вас не выйдет ни чего путного, но если вышло, то Вы и не придерживались. Возможно вы смотрели на них, но нарушали везде где нужно.
Liskov substitution principle говорит лишь о том, что функция, использующая базовый тип, должна иметь возможность использовать подтипы базового типа, не зная об этом. Если вы создали подтип, который имеет побочные эффекты выполнения, которых не должно быть при использовании базового класса, но на фактический результат работы это не влияет, т.е. конечный пользователь приложения всё так же доволен, то принцип выполнен.
Решайтесь, Вы за или против.
Если пользователь доволен, код не поддерживает принцип, нужен ли принцип?
Нужен ли принцип, если пользователь не доволен?
Принцип открытости/закрытости говорит о том, что создав какое-то API мы должны дать возможность расширить функционал путём наследования или как-то ещё, но воспрепятствовать изменению поведения базового типа.
Ну да, руки подняли, код потеряли.
Vilyx
22.08.2016 13:04+2Я против глупости, а вы её тут проповедуете.
Emiya
22.08.2016 13:26-6Глупость?
Глупость — обучать разработчиков так, что ни фанатично придерживаются принципов. Не понимая зачем и для чего.
Глупость — когда они тратят уйму времени, на то чего не может быть никогда.
Глупость – когда он них требует того, что невозможно.AndreyRubankov
22.08.2016 15:00+1> Глупость — обучать разработчиков так, что ни фанатично придерживаются принципов. Не понимая зачем и для чего.
А ведь все просто — стоит лишь объяснить зачем нужен этот принцип! А ведь это элементарно! Этот принцип направлен в первую очередь на обратную совместимость.
Если вы пишите решение для конечного пользователя, который мышкой тыкает — вам на самом деле плевать на этот принцип, у вас скорее всего в любой момент времени есть 1 интерфейс и только 1 его имплементация.
В то же время, если вы разрабатываете продукт, который используют другие программисты (фреймворк какой-то), то в этом случае вы обязаны следовать этому принципу. Ну или вашим продуктом никто не будет пользоваться.
Следовать этому принципу не всегда просто. Но выглядеть это должно так — если метод ожидает на вход List, то никакая имплементация List не должна сломать работу этого метода. ArrayList, LinkedList, CopyOnWriteList — абсолютно все равно, ваш метод должен будет выполнится правильно (если не нарушается синхронизация, т.е. этот принцип про это ничего не говорит).
Но есть множество реализаций, которые не выполняют этот принцип, они удобные и классные, но часто порождают баги (ImmutableList, UnmodifiedList, SkippedList).
Выгода от этого принципа: Вы заменяете одну имплементацию на другую и получаете прирост производительности (LinkedList -> ArrayList, или в редких случаях ArrayList -> LinkedList), Заменяете ArrayList имплементацией из throw и получаете выигрыш по потреблению оперативной памяти.
eyeofhell
22.08.2016 12:48+3Если предыдущий можно как-то довести до возможности реального применения, то тут без вариантов. В пределе Вы не можете изменить свой код сразу после того как поднимите руки над клавиатурой.
Слабое описание, если честно. «Фыр-фыр-фыр, эмоции, фыр-фыр-фыр, еще эмоции». Можете более четко сформулировать, что вам не нравится в «open/closed principle»? Без аналогий, аллегорий, метафор, гипербол, всего вот этого?Emiya
22.08.2016 13:00Мне не нравиться что сложившая ситуация, когда решение после акцептирования остается «мертвым». Что часто решение сравнивают с конструкциями из стали и бетона, хотя требуют динамичности и пластичности.
eyeofhell
22.08.2016 13:05+2OCP не об этом. OCP о том, что желательно делать такие объекты, для модификации поведения которых не надо лезть им в код. А надо параметр передать. Или свойство установить. Или конфиг файл поправить, на худой конец. Отнаследоваться от него, если уж совсем припрет.
Emiya
22.08.2016 13:27-1Ну да, ну да. Делать такие объекты, которые не меняются ни когда.
eyeofhell
22.08.2016 13:51Замечательно. Ползем дальше:
Вынужденные придерживаясь это принципа Вы рано или поздно сядете и перепишите всё, если, конечно, к тому времени Ваш проект уже не протух настолько, что его закопали.
Предположим, я проектирую свои классы так, что не нужно лезть в их код, чтобы менять их поведение. Как из этого следует то, что «рано или поздно вы сядите и перепишете все» и «проект к тому времени протух»? Переписывание — это вообще естественно для разработки ПО, потому что сложность, изменяющиеся требования, колешек Миллера, вот это все. Переписывать придется в любом случае, если мы хотим что-то менять. Остается только «протух»? Это что за волшебное состояние проекта и как оно следует из OCP?Emiya
22.08.2016 15:09-2В том, что OCP дает вам надежду что этого не произойдет, поскольку вы будет придерживаться правил.
iCpu
22.08.2016 13:00+5Принцип подстановки Барбары Лисков
Собственно, никаким примером неосуществимости не подтверждено. Абстрактный класс, интерфейс, он накладывает контракт выполнения, но его наполнение зависит от языка. То, что время выполнения методов не указывается в C++\Java\C#, собственно, проблема (проблема ли?) языков, но не SOLID в целом, ведь сама концепция не запрещает накладывать и такие ограничения.
Конкретный пример, метод «выбросить мусор» требует любой объект, способный его содержать. Вы привыкли к контейнеру, но и мусоропровод, и урна, и мусоровоз, и даже чистое поле без признаков цивилизации на 200км в радиусе вам подойдёт. Вам, в принципе, не важно, куда выбросить, иначе это было бы вынесено в сигнатуру. (Этот коментарий не является призывом к загрязнению окружающей среды! Берегите природу!)
Что до отсутствие взаимозаменяемости предков-потомков аля Int32-Int64, она исходит не из самой концепции, а из совмещения в одном классе диаметрально разных функций: целого числа и битовой маски, к примеру, а так же из использования эмпирических констант в коде вместо использования именованных констант. Это не проблема SOLID, это проблема программиста, нарушающего SOLID.
Гораздо удачнее пример с потоками данных, в которых вам не важен его внутренний мир, достаточно, чтобы он был открыт и поддерживал нужную операцию.
Принцип открытости/закрытости
Приведён вами в неверной трактовке. Класс/объект, после того, как его внешний интерфейс определён, не имеет права потерять часть этого интерфейса. Иначе вызов потерянного метода приведёт к… к чему, собственно? Что должно произойти, если кто-то попытается получить доступ к потеряной части?Emiya
22.08.2016 13:07Принцип подстановки Барбары Лисков
Хорошо если Вы его знаете, лучше когда Вы знаете что его нарушаете, а еще лучше когда знаете зачем Вы это сделали.
В Вашем случае Вы замыкаете решение на заданные условия и придерживаетесь их до тех пор, пока они не проходят тестирование временем.
Что должно произойти, если кто-то попытается получить доступ к потеряной части?
Что произойдёт, когда вы возьмёте сломанный костыль – ни чего хорошего, Вы упадете. Но вместо того чтобы считать, что такое поведение для класса (программиста) не допустимо, подумайте вот над чем. Может решение стало лучше после декомпозиции, внедрения иного подхода. Но Вы по-прежнему требуете свой «костыль».
eyeofhell
22.08.2016 13:09Какая интерсная трактовка OCP. Откуда это, если не секрет?
iCpu
22.08.2016 13:27Из вики же! Откуда ещё? А что с ним, собственно, не так?
eyeofhell
22.08.2016 13:42Класс/объект, после того, как его внешний интерфейс определён, не имеет права потерять часть этого интерфейса.
Я никогда раньше не слышал такой трактовки OCP. В русской википедии написано:
- должны быть открыты для расширения, но закрыты для изменения
- могут позволять менять своё поведение без изменения их исходного кода
- новые или изменённые функции требуют создания нового класса
- существующий интерфейс должен быть закрыт для модификаций, а новые реализации должны, по меньшей мере, реализовывать этот интерфейс
Я правильно понял, что вы трактовали OCP как частный случай пункта №4, про интерфейс?iCpu
22.08.2016 13:58Да, в том числе. Разве я не прав в этом тезисе?
eyeofhell
22.08.2016 16:43Из того что я понимаю — это маленький кусочек, частный случай. Принцип-то о том, что не надо делать объекты, любой чих по отношению к которым требует лезть им в код. А про потерю части интерфейса, это больше похоже на труизм «не надо ломать код» :) К этому многие принципы можно свести.
iCpu
23.08.2016 04:24-1Из того, что дано на русской вики, можно сделать очень много разных выводов. Там, например, дано определение по Мейеру, полиморфное определение, и вольная трактовка без ссылок, которой нет на других языках. Все три вместе они не позволяют вывести общее определение кроме как с помощью отрицания.
Именно эту ситуацию я и описал в #comment_9764310 как клинический дегенератизм.
А теперь к определению. По нему нельзя выбрасывать код и нельзя выбрасывать части публичного интерфейса. При этом, переиспользование старого кода может быть через наследование или агрегацию, он может или не может быть перемещён в рамках класса\объекта, наследники должны или не должны реализовывать интерфейс предка и тп. А про то, насколько гибкими и параметризуемыми должны быть методы — про это в классических определениях нет ни слова. Быть может, потому, что параметризуем болжен быть класс?
В общем, вся каша из того, что наследники должны делать, напрямую зависит от того, какой из видов ООП вы используете. Общее только то, что наследники делать не должны — они не должны просратьполимерынаследие предков.
iCpu
22.08.2016 13:38-3Окей, нашел вторую трактовку, полностью обратную первой.
…
Должен сказать, ООП как концепция — прекрасная штука. Проблема в том, что её описывают клинические дегенираты. Я не могу найти другого определения для описания людей, использующих одни и те же термины для описания разных вещей. Критически разных. Диаметрально.
Можете назвать меня ситхом, клиническим дегениратом или просто заминусить в сраное говнище, но, ИМХО, пока не найдётся того Геркулеса, который разгребёт все эти Авгиевы конюшни, ничего путного в спорах получить не удасться. Потому что мы даже в терминах не имеем согласия.Emiya
22.08.2016 14:18-4Заклюют же.
ООП очень ограниченная концепция, как ФП (функциональное программирование).
К сожалению, одно ограничение приводит к следующему и так далее. И всё множество попыток втиснуть большее в меньшее, путём создания ограничений не добавляют ни чего.
Мы создаем тоже код, такой же плохо, но теперь больше.
Мы умеем его создавать, используя шаблоны T4, но лучше он не становится от этого. В нем просто меньше «глупых» ошибок.
Может даже огорчать, что множество «безумцев» так и будет защищать свои любимые, создаваемые годами, «костыли». Поднимать на щитах призывы к светлому будущему, но делать всё тот же страшный код.iCpu
22.08.2016 15:14Заклюют же.
И? Я должен теперь забиться в ужасе в угол? Пфффф! Пускай клюют, если им хочется.
ООП очень ограниченная концепция, как ФП (функциональное программирование).
Жизнь ограничена законами физики. И смертью, да. Мы все умрём. Что дальше, отказываться от жизни сейчас?
И всё множество попыток втиснуть большее в меньшее, путём создания ограничений не добавляют ни чего.
ООП, оно не про впихивание слонов в холодильники, оно про то же, про что был сериал Декстер (Кстати, стоит досмотреть последние 2 сезона?) — расчленение и правильную утилизацию ошмётков.
Может даже огорчать, что множество «безумцев» так и будет защищать свои любимые, создаваемые годами, «костыли». Поднимать на щитах призывы к светлому будущему, но делать всё тот же страшный код.
Да, вы правы, мы будем защищать наши любимые до боли в зубах костыли. Знаете, почему? Альтернативы хуже. ООП и ФП — две вершины представления систем, олицетворяющие подход практика и теоретика к задаче. Дальше идут только вариации.
Всё программирование в целом — это инструмент. Инструмент для перевода модели из образно-функционального описания в термины микропроцессорной архитектуры. Как и любой инструмент, его можно использовать неправильно. Быть может, вы именно этим и занимались?
З.Ы. У вас забавные проблемы с глаголами, и не только. Вы откуда будете родом?
Serg046
22.08.2016 13:45Поддерживаю, интересная трактовка. А можно где-то найти более полное описание мысли и желательно от автора мысли?
Antervis
22.08.2016 13:16+3во-первых, принцип Барбары Лискофф, как вы сами написали в статье:
«объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы»
Только лишь правильности. От того, что программа раньше писала в текстовый лог, а теперь начала писать в бд, правильность не изменилась. Изменилось поведение. Так, как надо было разработчику.
Open/Closed Principle — то же самое, на том же примере: если раньше был класс-логгер, пишущий лог в файл, и понадобился логгер, который будет писать в файл и в бд, можно просто расширить предыдущий класс новыми функциями и подставить его экземпляр вместо старого. При таком подходе проблемы могут быть только с классами, которые в силу конкретной реализации разработчиком не пригодны для расширения.
В двух словах, вся методология SOLID сводится к: «пиши код, состоящий из расширяемых и заменяемых элементов». Если следуя методологии SOLID у вас получаются не расширяемые и не заменяемые элементы, значит, вы что-то делаете не такEmiya
22.08.2016 13:34-3...(Очень интересно услышать бы определение «правильности выполнения программы»...).
От того, что программа раньше писала в текстовый лог, а теперь начала писать в бд, правильность не изменилась. Изменилось поведение. Так, как надо было разработчику.
Ну предположим раньше мы писали файл все было хорошо на столько, что даже ни одной обработки ошибок не было создано в процессе создания чуда.
Пишем в базу. Глотаем ошибки? Пишем их туда же в лог? Не обрабатываем?
Почему Вы считаете, что изменение поведения не меняет «правильность»?
При таком подходе проблемы могут быть только с классами, которые в силу конкретной реализации разработчиком не пригодны для расширения
Перефразирую. Проблемы возникнут как только разработчик не предвидел того что понадобилось на текущем этапе развития проекта.Antervis
22.08.2016 14:34Определение правильности (корректности) программы существует. См. гугл.
Пишем в базу. Глотаем ошибки? Пишем их туда же в лог? Не обрабатываем?
У потомка базового класса-логгера может быть новая зависимость — какой-нибудь класс-обработчик ошибок. От этого принципы SOLID не нарушатся, а программа не потеряет в корректности.
Почему Вы считаете, что изменение поведения не меняет «правильность»?
Потому что мне платят за то, чтобы программа после изменений работала правильно
Перефразирую. Проблемы возникнут как только разработчик не предвидел того что понадобилось на текущем этапе развития проекта.
профессионализм разработчика в том и заключается, чтобы изначально закладывать расширяемую архитектуру, дабы не пришлось городить костыли или рефакторить при каждой потребности в добавлении/изменении функционала.
Принципы SOLID сводятся к тому, чтобы программы состояли из легко заменяемых и/или расширяемых модулей без нарушения целостности и корректности программы. Если следуя принципам SOLID вы не добиваетесь подобного результата, значит, «вы просто не умеете их готовить»
alexkunin
22.08.2016 13:16Для буквоедов [...] Дословное утверждение звучало примерно как:
— не компилируется, простите.
По тексту, у меня тоже бывает такое настроение, когда внутренний перфекцинист орет и бегает кругами. Обычно это совпадает с какими-нибудь депрессняком, и тогда к вечеру у мнея на руках абстрактная фабрика шаблонов для производства кэширующих синглетонов для каждого базового типа, с адаптацией под первые 10 пунктов списка самых популярных процессоров. Тут главное — не коммитить, с утра жизнь налаживается.Emiya
22.08.2016 13:35— не компилируется, простите.
Меня там не было, я не записывал ^_^’’
Бывает, сон помогает +1
iCpu
22.08.2016 13:22Хорошо если Вы его знаете, лучше когда Вы знаете что его нарушаете, а еще лучше когда знаете зачем Вы это сделали.
В моём случае я залезаю в исходники известного проекта и препарирую его, вырезая все части, которые, с одной стороны, достаточно интересны в контексте моих задач, а с другой стороны, не слишком заумны. Иначе очень просто использовать инструмент не к месту.
В Вашем случае Вы замыкаете решение на заданные условия и придерживаетесь их до тех пор, пока они не проходят тестирование временем.
Что произойдёт, когда вы возьмёте сломанный костыль – ни чего хорошего, Вы упадете.
То есть сохранение сигнатуры, будь это интерфейс класса или алгоритма в ФП, это разумное требование?
Может решение стало лучше после декомпозиции, внедрения иного подхода. Но Вы по-прежнему требуете свой «костыль».
Может, оно и стало лучше, как я узнаю о существовании нового метода из старой программы?Emiya
22.08.2016 13:37Вы упадете.
У Вас не соберется проект, отвалятся тесты, позвонит клиент.iCpu
22.08.2016 13:50То есть… Я создам проблему из ниоткуда. Шикарно.
Само сабой, баги нужно лечить, от устаревших методов нужно отказываться, но поведение вашей программы, ваших классов должно быть предсказуемо. Если у вас изменился внешний вид класса, быть может, это уже не старый добрый SheriffWillie с его классическим shootHimWithColt, а JudgeDredd и его killHimWithFkngRocketLauncher, и на этой констатации факта оставить шерифа и его ранче в прошлой версии?Emiya
22.08.2016 14:05Вы уже сами дали хороший пример ответа.
Да создадите. Но создадите проблемы и придерживаясь принципа OCP.
Раньше или позже? Не будет завесить от принципа, будет завесить ровно от автора творения, его опыта и предсказанных изменений.
В сущности, если вы ошиблись, то вариантов у вас не много — признать это и исправить.
Как вы это сделаете? OCP вам не поможет, он вам просто это запрещает.
Может уже есть множество решений, которые эксплуатируют ваш недочет.
Можете поломать совместимость и «накажете» еще и других разработчиков, можете замазать штукатуркой и сделаете дополнительный метод.
Vadimyan
22.08.2016 13:25+2Только ситхи всё возводят в абсолют.
Вы предлагаете не поклоняться бездумно принципам SOLID и иметь критический взгляд на ООП, но не используете этот критический взгляд сами, трактуя принципы дословно без объектно-ориентированного мышления.
SOLID, в частности, ИМХО, рассказывает, как структурировать код таким образом, чтобы он вёл себя предсказуемо при модификации и переиспользовании, был тестируемым и, часто, простым для восприятия. OCP, например, позволит не сломать существующий код и избавит от мучительного регрессионного тестирования.
Статья могла бы стать полезной с реальными примерами неправильного применения и понимания SOLID.Emiya
22.08.2016 13:40-1Суть то в том, что даже придерживаясь их, тратя колоссальные ресурсы вы не получаете того чего желали.
например, позволит не сломать существующий код и избавит от мучительного регрессионного тестирования.
Классика жанра DoubleList.
Да, добавление двух элементов в список. Без задания на этапе проектирования пре- и пост- условия делают все ваши старания бесполезными.
Один залетевший дятел разрушит цивилизацию.
Sing
22.08.2016 13:33+2Сплошной максимализм.
Выходит, что код обязан работать только на машине программиста, а то вдруг вы поставите программу на слабый компьютер? Это же нарушение контракта на время выполнения.
И уж постарайтесь дописать класс за один заход, а то он не сможет быть изменён после подъёма рук от клавиатуры — так и останетесь с недописанным названием метода.Emiya
22.08.2016 13:52-3Ну поставите код на сервер, ну замените программиста, добавите еще полгода для верности. И что-нибудь изменится? Нет Вы просто введете еще пару тройку сущностей и усложните условия, возможные причины отказа и способы решения решения.
retran
22.08.2016 13:40+3Может стоить почитать не википедию, а оригинальные статьи (прекрасным языком, написанные, кстати):
http://reports-archive.adm.cs.cmu.edu/anon/1999/CMU-CS-99-156.pdf
Например, на 4-й странице:
«32-bit integers are not a subtype of 64-bit integers (because a user of 64-bit
integers would expect certain method calls to succeed that will fail when applied to 32-bit integers)».
А дальше есть ЧЕТКАЯ модель вычисления с ЧЕТКИМИ определениями понятий типа и субтипа и тех свойств для которых выполняется LSP.
Так что вы всего лишь не разобрались с областью применимости принципа.
UPD Ссылка на более раннюю статью — http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.12.819&rep=rep1&type=pdf
nApoBo3
22.08.2016 14:37Очень категоричный взгляд на SOLID. Во-первых SOLID, это набор принципов, а не законов. Зачем они нужны, чтобы не ходить по граблям. В зависимости от размера проекта отступление от данных принципов будет вам стоить определенных денег в будущем, они же не про написание кода, а про развитие и поддержку.
От данных принципов можно отступать когда это абсолютно необходимо. Но лучше их придерживаться, чтобы не получить не модифицируемого монстра в итоге.Emiya
22.08.2016 14:37-1Придерживаясь вы тоже тратите ресурсы и не только денег и прямо сейчас.
Полагаю, что выбор «неудачной» архитектуры никак не повлияет на выгоду от следования или не следования этим принципам. Т.е. следования принципам вам ничего не дает: вы не увидите, следуя им возможные в будущем ошибки, вы не сможете исправить положение. Они просто вас сдерживают.
Уверяю вам, будет вы ходить по тем же граблям, может быть только на цыпочках и медленно.michael_vostrikov
22.08.2016 19:19Нет. В начале разработки у вас есть выбор, в какую сторону пойти, налево или направо. Но если вы пойдете в неправильную сторону, вам потом придется поворачивать. То есть, исправлять и переделывать. Сумма двух сторон треугольника всегда больше длины одной стороны. Лучше заранее подумать и выбрать правильное направление, или хотя бы близкое к правильному. И принципы SOLID в этом помогают.
nApoBo3
23.08.2016 12:50Большинство крупных проектов состоит из взаимодействующих частей.
Чем больше проект, тем больше будет этих самых частей, иначе его будет невозможно сдать в обозримый промежуток времени и невозможно поддерживать.
Надеюсь это мы можем постулировать?
От сюда сразу несколько проблем:
1) Стабильность интерфейсов данных частей
2) Баланс между размером и сложностью данных частей.
3) Стабильность поведения данных частей.
SOLID позволяет в некоторой степени решить вторую и третью проблему, а так же от части определиться с размером нарезки.
Как вы предлагаете решать данные проблемы в общем случае, без велосипедостроения?
Akon32
22.08.2016 15:05Размер потребляемых ресурсов: памяти, процессорного времени, используемых ядер, необходимость или отсутствие доп. оборудования.
Если вы не смогли описать в интерфейсе эти параметры, это проблема конкретного случая. Опишите их в документации, если число ядер или время обработки — действительно часть интерфейса. Из невозможности полностью описать интерфейс средствами языка не следует вывод, что LSP неверен.
В пределе Вы не можете изменить свой код сразу после того как поднимите руки над клавиатурой.
Если вы лезете с клавиатурой прямо в продакшен, то да.
А на практике код можно менять как угодно, если вы в состоянии переписать все участки программы/инфраструктуры, на которые влияет изменение, так, чтобы клиенты не пострадали. OCP, если его применять, только минимизирует число изменений других частей.
В статье очень много максимализма, как уже говорилось выше. Эти принципы намного гибче, чем вам кажется. И SOLID действительно помогает поддерживать и развивать огромные системы.Emiya
22.08.2016 15:18-1Труп млекопитающего тоже может быть большим. И что странно, требует множества «приседаний» для поддержания в нем процессов жизнедеятельности.
Akon32
22.08.2016 15:37+1Ну так мир — он сложный.
А почему вы считаете трупами большие, но работающие, развивающиеся и приносящие деньги системы?Emiya
22.08.2016 15:57-1Кто же против. Парой встречаются исключения. Как в анекдоте «Главное для пациента своевременный уход врача».
Приведите пример системы, которая «развивалась» бы, приносила денег, без «врача» (хорошо если одно, а то порой целый консилиумы дежурят у койки).
fogone
22.08.2016 15:47+2Фанатично придерживаясь его, Вы создается что-то уже «мертвое», поскольку оно не «дышит» при рождении и не когда не задышит после.
Фанатичность любую идею делает «мертвой». Так что, если претензия именно к этому, то, думаю, вам стоит вместо написания статей, бить себя по голове, как предлагал проф. Преображенский.
ApeCoder
22.08.2016 16:46Не возникает ли у вас когда-нибудь сомнение типа "Может быть они все-таки имеют что-нибудь полезное ввиду? Не может же столько народу пропагандировать ерунду?"
Emiya
22.08.2016 16:58-1Конечно могут. Хотя…
Земля в центре вселенной.
Средние века, инквизиция.
Видимо и большинство может что-то делать иначе.ApeCoder
22.08.2016 23:21+1Ok
L — совершенно правильно. Будучи последовательно примененным LSP запрещает перекрытие реализации. Вы просто недодумали эту мысль и слишком категоричны, чтобы получить из него пользу, а не сходу отвергнуть
O — прочитайте статью в википедии. До конца. В каком смысле OCP соблюдается и соблюдался раньше. Вы просто в полемическое задоре придумали себе абсурдную трактовку и с ней спорите.
sentyaev
22.08.2016 16:54+2Такое ощущение, что вы считаете, что нельзя менять существующий код в процессе разработки.
И исходя из этого делаете вывод, что SOLID вам мешает.
Но ведь рефакторинг никто не отменял, TDD никто не отменял, да и много чего еще.
Я к тому, что SOLID не мешает вам, при добавлении/изменении функциональности, перелопатить хоть весь код в проекте.
SOLID он как раз о том, чем вы должны руководствоваться в процессе разработки, о чем думать, чтобы потом, когда вам нужно будет добавить одну маленькую фичу, не пришлось весь проект перелопачивать.
tangro
22.08.2016 17:21Такая статья в духе «нельзя написать идеально, давайте писать как-нибудь». Написать идеально действительно нельзя, но если к этому стремиться — то выйдет как-то «приемлемо». А если сразу сказать «к черту все методологии и паттерны» — то уж не сомневайтесь, результат будет хуже некуда.
Emiya
22.08.2016 17:36-2Не расширяете без надобности простое, до вас тут уже много мамонтов потопталось.
Статья о том, что, используя современные ООП методологии (в частности SOLID) вы создаете нечто, что не способно на самостоятельное развитие. Хорошо если это развитие вообще возможно, лучше если оно возможно без участия вас как «творца».
Да вы тешите себе мыслью, что проект пока «не смышлёный» на этапе прототипа. Да, вы тешите себе мыслью, что проект пока «не самостоятельный» на этапе внедрения. Но на этапе сопровождения вы не на минуту не спускаете глаз с проекта, а если и отвертитесь, то вам напомнят.
Допускаю что вами был изготовлен столь искусный механизм, способный работать автономно, который не требует вашего наблюдения. Но ответе сами себе, развивается ли он сам?lair
22.08.2016 18:04Оу, а вы знаете много (программных) систем, способных на самостоятельное развитие, и при этом не попадающих под расплывчатое определение "искусственный интеллект"?
Emiya
22.08.2016 18:41-2И? Вся статья об этом. Вы её прочти? Это хорошо =)
Мы используем принципы, чтобы создавать «мертвые» решения. И пропагандируем, что они позволят «развить мертвеца».
Мои познания ИИ скромнее, но там тоже не все так радует, пока получаются только системы «стимул — реакция».lair
22.08.2016 18:48Так вы много знаете таких систем, или нет?
(безотносительно того, использовался при их построении SOLID, или нет)
michael_vostrikov
22.08.2016 19:30+2лучше если оно возможно без участия вас как «творца»
Складывается впечатление, что вы, видимо, получили на поддержку чей-то проект с кучей фабрик и инверсией зависимостей, ниасилили, и поэтому написали эту статью.
sentyaev
22.08.2016 20:12+1Статья о том, что, используя современные ООП методологии (в частности SOLID) вы создаете нечто, что не способно на самостоятельное развитие.
А какие методологии или практики используете вы, чтобы создать нечто?
А как вам паттерны проектирования, используете?
retran
22.08.2016 21:27Я там чуть выше комментарий оставил со ссылками на оригинальные статьи.
Вы так и не посмотрели их судя по тому, что вы продолжаете писать?
netpilgrim
23.08.2016 12:40Хочется пожелать крепких нервов тиммейтам автора.
Кому интересна тема, вот относительно свежее обсуждение:
http://ru.stackoverflow.com/questions/551346/Когда-НЕ-нужно-использовать-solid
Scf
23.08.2016 12:50+1Слишком категорично. Не нужно ни слепо следовать SOLID, ни игнорировать его. Все эти методологии и принципы — это обобщающие чужой опыт советы, а не руководства к действию.
Новичкам, не наработавшим собственный опыт, имеет смысл им следовать. Опытным программистам все эти SOLID/TDD/паттерны/антипаттерны уже не нужны — они впитали их суть и применяют автоматически и по необходимости, не задумываясь о названиях.
ApeCoder
29.08.2016 12:42+1Опытным программистам это не нужно, если они работают в одиночку (ну или даже может и в этом случае нужно, когда они в сомнительных случаях выносят рассуждения на сознательный уровень).
Если опытный программист работает с другими программистами и обсуждает дизайн, ему вполне помогает послать собеседнику ссылку, вместо того, чтобы придумывать свои слова для объяснения почему это хорошо или плохо
G-M-A-X
25.08.2016 11:011. L. Тогда не стоит использовать наследование. :)
2. O. Если писать не публичную библиотеку, то можно не придерживаться этого принципа.
DrPass
Вы как-то сильно категорично к этому принципу относитесь. На самом деле речь идёт лишь о том, что логика класса-наследника от какого-то родительского класса не должна ломать работу тех методов, которые обращаются к методам родительского класса. И следование этому принципу, во-первых, необременительно, во-вторых, действительно полезно.
Emiya
Аккурат наоборот, и обременительна – Вы вынуждены придерживаться соглашения, и бесполезна – посмотрите хотя бы на «Закон Дырявых Абстракций» — Джоэл Сполски. Что-то не выходит каменный цветок даже когда очень стараются.
DrPass
Вы немного не так воспринимаете. Понятное дело, что далеко не всегда удаётся создать долгоживущий и универсальный контракт. Вы предлагаете нарушать контракт, если он не удовлетворяет условиям, а есть другой выход из ситуации — создавать новый контракт, с новым интерфейсом. И это не нарушает LSP. Я сейчас не говорю, что это серебряная пуля, и так нужно делать вообще всегда. Но зачастую это более правильный выход. Вы же сами наверняка знаете, поиск багов в коде по колено из-за нарушения контракта — он обычно намного более болезненный, чем разработка нового интерфейса под новое поведение.
Emiya
И я том же.
Кто-то создал труп, который нам не интересно препарировать. Мы создаем следующий.
Я рад за вас, за себя нет.
DrPass
А у вас есть какие-то другие варианты? Ну, кроме заведомо печального «сидеть и проверять/переделывать все написанные ранее обращения и тесты, если они есть». Оно взлетит, если это какой-то узкоспециализированный класс, который используется в небольшом количестве мест в коде. Но в этом случае вы можете и не заморачиваться с большинством принципов SOLID, оно вам особой выгоды не даст. Если же это какой-то часто используемый контейнер, модель и т.д., то тут вы, вполне вероятно, повеситесь раньше, чем приведёте код в порядок после нарушения LSP.