Антипаттерны и запахи
Неправильная или неполная реализация некоторых шаблонов проектирования или приёмов, а также неправильная иерархия сущностей могут нарушить LSP.
Непредсказуемое изменение поведения
Допустим, мы делаем клон Медиума, где авторы будут публиковать статьи. Статья может находиться в разных состояниях, от которых зависит, что с ней можно делать. Например, удалённую статью нельзя удалить, а опубликованную — снова опубликовать.
Для подобной задачи подходит шаблон Состояние — он позволяет менять поведение объектов в зависимости от их внутреннего состояния. Если он реализован правильно и полно, то LSP он не нарушит. Однако реализация в примере ниже нарушает.
Допустим, статья описывается базовым классом Article:
enum ArticleStatus {
  Draft
  Published
  Deleted
}
class Article {
  status: ArticleStatus
  constructor() {/*...*/}
  edit() {/*...*/}
  delete() {/*...*/}
  restore() {/*...*/}
  unpublish() {/*...*/}
  publish(): void {
    this.status = ArticleStatus.Published
  }
}
Если опубликованная статья при попытке публикации выбрасывает исключение, которое не было описано в базовом классе, то мы нарушаем LSP:
class Published extends Article {
  constructor() {
    super({ status: ArticleStatus.Published })
  }
  publish(): void {
    // Упс!
    throw new Error('article is already published')
  }
}
Чтобы реализация шаблона не нарушала LSP, нам необходимо описать в базовом классе возможность выбросить исключение. Для этого мы введём метод, который будет определять, можно ли статью публиковать:
class ArticleException extends Error {/*...*/}
class Article {
  // ...
  protected canPublish(): boolean {
    return this.status === ArticleStatus.Draft
  }
  publish(): void {
    if (!this.canPublish()) throw new ArticleException()
    // ...
  }
}
Сейчас переопределение метода publish для опубликованной статьи не будет усиливать предусловия, поэтому это не нарушит LSP:
class ArticlePublishedException extends ArticleException {/*...*/}
class Published extends Article {
  // ...
  publish(): void {
    // `ArticlePublishedException` наследуется от `ArticleException`,
    // указанного в классе `Article`, поэтому здесь нарушения нет:
    throw new ArticlePublishedException()
  }
}
Вопросы
Интерфейс, которому нельзя доверять
Более тонкое нарушение LSP — это «пустая» реализация интерфейса.
Если опереться на пример выше, то пустой реализацией было бы описание метода Publish для опубликованной статьи таким образом:
class Published extends Article {
  // ...
  publish(): void {
    return
  }
}
Вроде всё хорошо: метод описывает правильное поведение, усиления предусловия нет. Но если посмотреть на ситуацию в терминах контрактного программирования, то метод publish должен менять состояние статьи с Draft на Published, чего не будет происходить:
class Article {
  // ...
  publish(): void {
    // Проверяем, что состояние позволяет публиковать статью
    // именно эта проверка в `Published` вызовет ошибку:
    this.contract.require(this.canPublish() === true)
    // ...
    // Проверяем, что состояние поменялось на `Published`:
    this.contract.ensure(this.status === ArticleStatus.Published)
  }
}
«Пустая» реализация интерфейса также нарушает принцип разделения интерфейса.