Какие принципы разработки ПО, кроме SOLID, вы знаете?

«Какие принципы разработки ПО, кроме SOLID, вы знаете?» — вопрос из категории Архитектура, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Помимо SOLID, существуют другие ключевые принципы проектирования ПО:

  • DRY (Don't Repeat Yourself) — избегайте дублирования кода и логики. Повторяющуюся функциональность следует выносить в единый источник (функцию, класс, модуль).

    • Почему: Упрощает поддержку, изменения вносятся в одном месте, снижает вероятность ошибок.
    • Пример:

      // Нарушение DRY
      func calculateCircleArea(radius: Double) -> Double {
          return 3.14159 * radius * radius
      }
      func calculateSphereVolume(radius: Double) -> Double {
          return (4.0 / 3.0) * 3.14159 * radius * radius * radius
      }
      
      // Соблюдение DRY
      let pi = Double.pi
      func calculateCircleArea(radius: Double) -> Double {
          return pi * radius * radius
      }
      func calculateSphereVolume(radius: Double) -> Double {
          return (4.0 / 3.0) * pi * pow(radius, 3)
      }
  • KISS (Keep It Simple, Stupid) — стремитесь к максимальной простоте и понятности дизайна и кода.

    • Почему: Сложный код сложнее понимать, тестировать, поддерживать и в нем легче допустить ошибку.
  • YAGNI (You Aren't Gonna Need It) — не добавляйте функциональность, пока она явно не потребовалась.

    • Почему: Разработка "на будущее" тратит время, увеличивает сложность кодовой базы и часто приводит к ненужному коду, который никогда не используется.
  • Composition over Inheritance — отдавайте предпочтение композиции (включению объектов) перед наследованием классов.

    • Почему: Композиция обеспечивает большую гибкость, снижает связность и позволяет избежать проблем хрупкой базовой иерархии (Fragile Base Class).
  • Law of Demeter (Принцип наименьшего знания) — модуль должен взаимодействовать только с ближайшими для него объектами (друзьями), а не с их "друзьями друзей".

    • Почему: Уменьшает связность между компонентами, делая систему более модульной и устойчивой к изменениям.
    • Пример:

      // Нарушение: объект A знает о внутренней структуре объекта B.
      let value = a.b.c.doSomething()
      
      // Соблюдение: A вызывает метод B, а B управляет своими зависимостями.
      let value = a.getValueFromB()