LINUX.ORG.RU

Соблюдаете ли вы LSP?

 , , , ,


2

6

Из комментариев:

«Но следовать LSP или не следовать — дело договоренности внутри команды. Если команда следует, это одно, если не следует — это другое. Команда, в которой работаю, этому принципу не следует.»

Это очень забавный вопрос) Идут годы, а истории те же самые)

Саттер и Александреску говорят про LSP: «подкласс не должен требовать от вызывающего кода больше, чем базовый класс, и не должен предоставлять вызывающему коду меньше, чем базовый класс». Это те чуваки. который C++ и D делали :-) У Гослинга тоже где-то было про это, сейчас не нагуглю.

Посмотрим, как они мучались, когда не было дженериков.

Вот код на Java:

static void update(Object[] objs)
{
    objs[0] = new Object();
}

public static void main(String[] args) {
    String[] strs = new String[] { "hello", "world" };
    update(strs);
}

Или на C#:

static void update(object[] objs)
{
   objs[0] = new object();
}

string[] strs = new string[] { "hello", "world" };
update(strs);

При запуске он бросит исключение ArrayStoreException (Java) или ArrayTypeMismatchException (C#), поскольку Шарп не может записать экземпляр объекта внутрь массива из стрингов. Это — прямое нарушение LSP. string — это подтип object, но когда его попытались использовать в том же месте где object[], всё сломалось. Подстановочность не работает. Заметьте, что это ошибка времени выполнения, а не компиляции.

В чем проблема? Если следовать логике, то пришлось бы писать несколько одинаковых методов: `static void update(string[] objs)`, `static void update(int[] objs)` — и люто копипастить. Вроде бы, в 2018 году так всё ещё делают в некоторых отсталых языках вроде Golang, когда не опускаются до рефлексии и кодогенерации. В нормальных языках для этого есть дженерики. Но когда Java и C# создавались, дженериков в них не было ещё. Поэтому массивы сделали ковариантными по типу элемента. В смысле, теперь можно отправить string[] на вход методу, который принимает object[], и это скомпилируется и заработает вот так:

Java:

static void sort(Object[] objs)
{
    Arrays.sort(objs);
}

public static void main(String[] args) {
    String[] strs = new String[] { "hello", "world" };
    sort(strs);
}

C#:

static void Sort(object[] objs)
{
   // ...
}

string[] strs = new string[] { "hello", "world" };
Sort(strs);

Совершенно очевидно, что это жёсткий хак системы, сделанный от безысходности.

- Кто мы?
- Большие боссы, заказчики языка!
- Что мы хотим?
- Уменьшения копипасты.
- Когда мы это хотим?
- ПРЯМО СЕЙЧАС!!! //и наплевать на ваши задротские дженерики

ООП не предназначено для уменьшения копипасты, оно скорее запрограммировано на её увеличение. Уменьшить именно дублирование буковок можно только на уровне другого над-языка вроде шаблонного генератора.

Есть подозрение, что идти против дизайна языка - это удовольствие не для слабонервных. Когда у тебя в системе будут тысячи классов и типы вроде `Map<Obj,Map<Obj,Map<Obj,Map<Obj,Obj>>>>`, без соблюдения некоторого феншуя всё это быстро скатится в пучину ада.

Теперь, ежедневный опрос. Стоит ли соблюдать LSP? Приведите аргументы.

★★★★☆

Последнее исправление: stevejobs (всего исправлений: 1)

Не следую, догмы - зло. Для меня наследование = полиморфизм или соглашение фреймоврка. Если оно мне нужен, значит будет наследование. Если нет, значит будет что-то другое. А все эти теоретические изыскания это пустая потеря времени, как болтать про REST, вместо того, чтобы писать код.

Legioner ★★★★★
()
Ответ на: комментарий от peregrine

На хабре топик про другое. Там некий поциент обнаружил, что ООП три разных социальных группы считают разными вещами. Я там хотел написать коммент «неужели автор прочитал статью „полиморфизм“ на википедии?», но не стал, потому что автор не прочитал.

stevejobs ★★★★☆
() автор топика
Последнее исправление: stevejobs (всего исправлений: 1)

Стоит ли соблюдать LSP? Приведите аргументы.

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

derlafff ★★★★★
()

Теперь, ежедневный опрос. Стоит ли соблюдать LSP? Приведите аргументы.

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

Deleted
()

Просто в данном случае несколько перепутаны ковариантность и наследование типов.

Кресты в этом случае правильнее: vector<Base*> никак не связан с vector<Derived*>.

vzzo ★★★
()

Это — прямое нарушение LSP

Нет. Именно потому что это эффект рантайма. LSP не говорит, что должно происходить в рантайме, а только о том, что код должен быть синтаксически корректен. Т.е. что операция string[]= должна быть, а что она там делает, вычисляет фазу луны или кидает исключение - никого не волнует.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

LSP не говорит, что должно происходить в рантайме, а только о том, что код должен быть синтаксически корректен.

Вы совсем не понимаете о чем говорит LSP.

trex6 ★★★★★
()
Ответ на: комментарий от trex6

Вы совсем не понимаете о чем говорит LSP.

LSP говорит о том, что интерфейс типа и подтипа должен быть одинаков. В контексте жабки это означает что при любой подстановке типа на подтип (или наоборот) код должен компилироваться. И на этом всё.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

LSP говорит о том, что интерфейс типа и подтипа должен быть одинаков.

LSP говорит еще много о чем.

при любой подстановке типа на подтип (или наоборот) код должен компилироваться. И на этом всё.

Не всё.

tailgunner ★★★★★
()

Окей, напомни, тебе 18 лет, ты учишься на технической специальности Мухосранского Государственного, и проходишь курс «программирование и ООП»? Нахуй ты сюда это принес, извиняюсь за мой французский?

lovesan ★★
()
Ответ на: комментарий от lovesan

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

lovesan ★★
()
Ответ на: комментарий от no-such-file

если рантайм, автоматически сгенерированный языком, не определяется синтаксисом этого же языка, это говно а не язык, не?

stevejobs ★★★★☆
() автор топика
Ответ на: комментарий от stevejobs

Докторские по Java? Ты ебанулся или что? Ты вообще представляешь что там можно написать то? Особенно ну на реально докторскую, а не всякое как в РФ докторские пишут. Я просто угораю. Докторские по Java. А докторские по PHP у вас там не пишут, случаем?

lovesan ★★
()
Ответ на: комментарий от stevejobs

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

lovesan ★★
()
Ответ на: комментарий от lovesan

Я не хотел форсировать дискуссию, но раз уж всё равно тред не удался, то на: http://researcharchive.vuw.ac.nz/xmlui/handle/10063/3333

Докторская на полторы сотни страниц про ограничения в мире дженериков =) То есть, именно по той теме, которую мы должны бы обсуждать в этом треде

По шарпам в точности такого же говна тоже жопой жуй, но мне лень искать - по этому добру уже ты спецом быть должен!

Прямо сейчас несколько друзей пишут тезисы по «языку Java» и «платформе Java» (не знаю на какие уровни, правда). Там ещё поле непаханное про теорию трансляции для конструкторов DSL на Java/C# и самооптимизирующихся AST и JIT-комплияторов, например

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

stevejobs ★★★★☆
() автор топика
Последнее исправление: stevejobs (всего исправлений: 4)
Ответ на: комментарий от no-such-file

А конкретно?

Пусть q(x) является свойством, верным относительно объектов x некоторого типа T. Тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T.

В частности, свойством является прохождение любого заданного теста. То есть если f(x) { update(x); return x[0] == «ok»; } возвращает истину для всех x типа T, то f(y) должно возвращать истину и для всех y типа S, который является подтипом типа T.

monk ★★★★★
()

Принцип LSP не выполняется для любой системы классов с рефлексией. Потому что свойство q(x) { x.class == «Base»; } будет выполняться для всех x класса Base и не будет выполняться для его подтипов.

monk ★★★★★
()

ключевые слова треда: covariance, contravariance, причём здесь ООП нипанятна.

system-root ★★★★★
()

Для C# вам скорее нужно вот это:

static void update<T>(T[] objs)
{
    objs[0] = default(T);
}

static void Main(string[] args)
{
    string[] strs = new string[] { "hello", "world" };
    update(strs);
}

sanwashere ★★
()

А вот LSP вступает в силу уже в примере с сортировкой, за счёт интерфейсов. Для C#:

static void Sort<T>(T[] objs) where T: IComparable
{
   // ...
}

string[] strs = new string[] { "hello", "world" };
Sort(strs);

sanwashere ★★
()
Ответ на: комментарий от monk

То есть если f(x) { update(x); return x[0] == «ok»; } возвращает истину для всех x типа T, то f(y) должно возвращать истину и для всех y типа S, который является подтипом типа T.

И дальше что?

Потому что свойство q(x) { x.class == «Base»; } будет выполняться для всех x класса Base и не будет выполняться для его подтипов.

Схерали вдруг? Определяем в subBase q(x) { x.class == «SubBase»; } и оно прекрасно выполняется для SubBase.

no-such-file ★★★★★
()
Ответ на: комментарий от stevejobs

рантайм, автоматически сгенерированный языком, не определяется синтаксисом этого же языка

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

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Схерали вдруг? Определяем в subBase q(x) { x.class == «SubBase»; }

Свойство q — это не метод класса, а внешний тест. По LSP, если тест проходит для любого объекта класса Base, то он обязан проходить и для любого объекта подтипа Base.

monk ★★★★★
()
Ответ на: комментарий от monk

Свойство q — это не метод класса, а внешний тест

Тогда он никакого отношения не имеет к LSP. В LSP говорится только о свойствах объектов x типа T, а не о каких-то внешних тестах. Т.е. q(x) это лисповый дженерик. Для жабки большинства других языков q(x) записывается как q.x или q.x()

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 2)
Ответ на: комментарий от no-such-file

LSP - это формализм теории типов, почитай документ выше. Там всё формально описано, включая имеющиеся ограничения

На первой же странице даётся самое финальное свойство (к которому идёт весь текст и заканчивается на последней странице):

Let q(x) be a property provable about object x of type T. Then q(y) should be true for objects y of type S where S is a subtype of T"

Здесь и дальше речь идёт именно о «propery provable about», а не «property of»

В разделе model of computation идёт описание модели, в которой тип задаётся через тройку (объекты, значения, методы), где методы бывают одного из трёх типов: мутаторы, обсерверы и продюсеры. Заметь, что «проверяемые ограничения» не являются частю модели вычислений

Практическая запись типа - имя, инварианты, ограничения, история и методы. Методы - имя-сигнатура и тройка requires+modifies+ensures.

В смысле доказательства «property provable about» интересны инварианты и ограничения,

инварианты (в отдельной секции) задаются как предикаты, которые _любым_ образом задают легальные значения (или являются конъюнкцией других инвариантов из этого типа и универсального множества значений). Если ни один инвариант явно не задан, то используется инвариант по умолчанию, равный true.

ограничения (тоже есть отдельная глава). Там важно history rule, которое говорит что для любого мутатора i, если он срабатывает (выполняются пре и пост условия), то и в подклассе они тоже срабатывают. Это одна из фишек введенная именно в LSP. Но так как подтип может объявить свои мутаторы, к этому правилу добавляется дополнение: подтип не должен иметь таких мутаторов, которые вели бы себя по-другому, чем в надтипе. На основе этого выводится понятия ограничений, которые говорят что для всей области определения должно выполняться некое утверждение, причём способ задания этого утверждения не описан - он может быть любым, и уж точно он не обязан является одним из методов

дальше на «figure 4» даётся определение того, что есть подтип, и нам тут важно в пункте 2 «constraint rule»: наличие ограничения на подтипе означает наличие того же ограничения на надтипе

в методах, тройка requires-modifies-ensures задаёт проверки на типы, относительно modifies свойств, к которым будут применяться описанная выше схема, и это смысл человекочитаемой фразы «about x» в фразе «provable about x»

таким образом, мы можем придумать любые «тесты» («важные для системы свойства») относительно которых будет проверяться поведение типа и подтипа - главное, чтобы они казались нам почему-то важными

как раз то, о чём говорит monk

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

stevejobs ★★★★☆
() автор топика
Последнее исправление: stevejobs (всего исправлений: 1)
Ответ на: комментарий от no-such-file

В LSP говорится только о свойствах объектов x типа T, а не о каких-то внешних тестах.

Свойство и есть внешний тест. Вот смотри канонический пример нарушения принципа: https://x-twig.ru/blog/liskov-substitution-principle/

Т.е. q(x) это лисповый дженерик.

Нет. q(x) это лисповый предикат

monk ★★★★★
()
Ответ на: комментарий от monk

сознательно не пользоваться рефлексией. Сознательно не делать мутабельных объектов (сеттеры в класах проксировать в конструкторы, создающие объект с одним изменённым полем), писать чистые функции (включая IO), и так далее

достаточно ли ты просветлён, чтобы следовать Дао, программируя на Йаве?

stevejobs ★★★★☆
() автор топика

Саттер и Александреску
Это те чуваки. который C++ и D делали
Посмотрим, как они мучались, когда не было дженериков.
Вот код на Java:

Как обычно, логика живет своей жизнью, а Стиви — своей.

Кстати, в C++ и D не дженерики, там шаблоны.

eao197 ★★★★★
()
Ответ на: комментарий от stevejobs

Сознательно не делать мутабельных объектов (сеттеры в класах проксировать в конструкторы, создающие объект с одним изменённым полем), писать чистые функции (включая IO), и так далее

...

достаточно ли ты просветлён, чтобы следовать Дао, программируя на Йаве?

Вот именно поэтому я пишу на Haskell и Racket.

monk ★★★★★
()

Госпади, кто-то в 2018 веке до сих пор пользуется нетипизироваными коллекциями?

Deleted
()
Ответ на: комментарий от Deleted

Госпади, кто-то в 2018 веке до сих пор пользуется нетипизироваными коллекциями?

И код для работы с List<Base> не может работать с List<Derived>. А если делать шаблон, то нет гарантии, что в списке наследники Base. Есть только утиная типизация по именам используемых методов и операций. И крайне загадочные сообщения об ошибках, показывающие потроха шаблона, если утиная типизация не подошла.

monk ★★★★★
()
Ответ на: комментарий от monk

И код для работы с List<Base> не может работать с List<Derived>.

это говнокод же. К чему его ставить в пример? Да, он есть, но это не то, на что стоит ориентироваться

Deleted
()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от monk

Всем Хаскель хорош, но вот отсутствие IDE и программирования с помощью автодополнения, копипасты со Stackoverflow и отладчика удручают даже больше странного синтаксиса

stevejobs ★★★★☆
() автор топика
Последнее исправление: stevejobs (всего исправлений: 1)
Ответ на: комментарий от stevejobs

отсутствие IDE и программирования с помощью автодополнения, копипасты со Stackoverflow

Быдлокодеропроблемы.

bodqhrohro_promo
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.