23 декабря 2015 в 19:34

От STUPID кода к SOLID коду перевод

На прошлой неделе я выступил с докладом об Объектно-ориентированном программировании в Мишлене, в компании, где я работаю. Я рассказывал о написании более эффективного кода, от STUPID коду SOLID коду! STUPID, а также SOLID являются акронимами, и рассматривались довольно много в течение длительного времени. Однако эти мнемоники не всегда известны, таким образом, имеет смысл распространить эту информацию.

image

Далее я познакомлю вас с принципами STUPID и SOLID. Следует иметь в виду, что это принципы, а не законы. Однако, рассматривая их в качестве законов было бы хорошо для тех, кто хочет усовершенствоваться в написании кода.

STUPID код, серьезно?


Это может задеть Ваше самолюбие, но вы уже, вероятно, написали много STUPID кода. Я тоже. Но, что это значит?

  • Синглтон
  • Сильная Связанность/Tight Coupling
  • Невозможность тестирования
  • Преждевременная оптимизация
  • Не дескриптивное присвоение имени
  • Дублирование кода

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

Синглтон


Шаблон «одиночка» — вероятно, самый известный шаблон разработки, а также и самый недооцененный. Вы знаете о синдроме «одиночки»? Это — когда вы думаете, что шаблон «синглтон» — наиболее подходящий шаблон для текущего варианта использования, который у вас есть. Другими словами, вы используете его везде. Это определенно не круто.

Одиночные элементы противоречивы, и их часто считают ошибочными шаблонами. Вы должны избегать их. Фактически, использование синглтона не проблема, а признак проблемы. Вот две причины почему:

  • Программы, использующие глобальное состояние очень сложно протестировать;
  • Программы, которые зависят от глобального состояния, скрывают свои зависимости.


Но должны ли вы действительно избегать их все время? Я сказал бы да, потому что часто можно заменить использование синглтона чем-то лучшим. Избежание всего статического очень важно, чтобы не допустить сильной связанности.

Сильная связанность


Сильная связанность — это обобщение проблемы синглтона. Одним словом, вы должны уменьшить связь между своими модулями. Связанность — это мера того, насколько связаны подпрограммы или модули.

Если внесение изменения в одном модуле в вашем приложении требует, чтобы вы изменили другой модуль, тогда связанность существует. Например, вы инстанцируете объекты в классе своего конструктора вместо передачи экземпляров как параметров. Это плохо, потому что он не допускает дальнейших изменений, такие как замена экземпляра экземпляром подкласса, объектом-mock или что бы то ни было.

Сильно связанные модули трудно повторно использовать, и также сложно тестировать.

Невозможность тестирования


По моему мнению, тестирование не должно быть трудным! Нет, правда. Каждый раз, когда вы не пишете unit тесты, потому что у вас нет времени, реальная проблема заключается в том, что ваш код не так уж эффективен, но это — другая история.

В большинстве случаев невозможность тестирования вызвана сильной связанностью.

Преждевременная оптимизация


Дональд Эрвин Кнут сказал: «преждевременная оптимизация — корень всех зол. Только одни затраты, и никакой пользы». Фактически, оптимизированные системы гораздо сложнее, чем просто написание цикла или использование преинкремента вместо постинкремента. В конечном итоге, вы останетесь с нечитабельным кодом. Именно поэтому Преждевременную Оптимизацию часто считают ошибочной.

Мой друг часто говорит, что есть два правила для оптимизации приложения:

  • Не делайте этого;
  • (только для профессионалов!) пока не делайте этого.


Не дескриптивное присвоение имени


Это должно быть, очевидно, но все же нужно это сказать: назовите свои классы, методы, атрибуты и переменные должным образом. Ох, и не сокращайте их! И да, вы пишете код для людей, не для машин. Они не понимают то, что вы пишете, так или иначе. Компьютеры понимают только 0 и 1. Языки программирования предназначены для людей.

Дублирование кода


Дублированный код неэффективен, таким образом, не повторяйтесь, и также делайте его короче и проще. Пишите код только один раз!

Теперь, когда я объяснил, что собой являет код STUPID, вы можете подумать, что ваш код является кодом STUPID. Это (еще) не имеет значения. Не расстраивайтесь, сохраняйте спокойствие и вместо этого напишите SOLID код!

SOLID спешит на помощь


SOLID — термин, описывающий набор принципов разработки для эффективного кода, который был изобретен Робертом К. Мартином, также известным как Uncle Bob.

SOLID значит:

  • Принцип единственной обязанности
  • Принцип открытости/закрытости
  • Принцип подстановки Барбары Лисков
  • Принцип разделения интерфейса
  • Принцип инверсии зависимостей


Принцип единственной обязанности


Принцип единственной обязанности (Single-Responsibility Principle, SRP) гласит, что на каждый класс должна быть возложена одна-единственная обязанность. У класса должна быть только одна причина для изменения.

Просто, потому что вы можете добавить все, что хотите в свой класс, не означает, что вы должны это делать. Обязанности помогут вам разработать приложение лучше. Спросите себя, должна ли логика, которую вы представляете, находиться в этом классе или нет. Использование уровней в приложении очень помогает. Разделите большие классы на меньшие и избегайте “божественных” классов. Последнее, но не менее важное, напишите простые комментарии. Если начинаете писать комментарии такие как in this case, but if, except when, or, то вы делаете это неправильно.

Принцип открытости/закрытости


Принцип открытости/закрытости (Open/Closed Principle, OCP): Сущности (классы, модули, функции и т.п.) должны быть открытыми для расширения, но закрытыми для модификации.

Вы должны сделать все переменные экземпляра private по умолчанию. Пишите методы get и set только, когда они действительно будут вам нужны. Я уже ответил на этот вопрос в предыдущей статье, поскольку девятое правило Объектной Гимнастики связано с этим принципом.

Принцип подстановки Барбары Лисков


Принцип подстановки Лисков (Liskov Substitution Principle, LSP ): Должна быть возможность вместо базового типа подставить любой его подтип.
Давайте рассмотрим пример. Прямоугольник — плоская фигура с четырьмя прямыми углами. У него есть ширина (width) и высота (height).

Теперь, взгляните на следующий псевдо-код:
rect = new Rectangle();

rect.width  = 10;
rect.height = 20;

assert 10 == rect.width
assert 20 == rect.height

Мы просто устанавливаем ширину width и высоту height на экземпляре Rectangle, и затем мы подтверждаем, что оба свойства правильны. Пока все идет хорошо.

Теперь мы можем улучшить наше определение, сообщив, что прямоугольник с четырьмя сторонами одинаковой длины называют квадратом. Квадрат — это прямоугольник, таким образом, мы можем создать класс Square, который расширяет класс Rectangle, и заменить первую строку, представленную выше, нижней:
rect = new Square();

Согласно определению квадрата, его ширина равна его высоте. Вы можете определить проблему? Первое утверждение перестанет работать, потому что мы должны были изменить поведение методов set в классе Square, чтобы соответствовать определению. Это нарушение Принципа подстановки Барбары Лисков.

Принцип разделения интерфейса


Принцип разделения интерфейса (Interface Segregation Principle или ISP): много специализированных интерфейсов лучше, чем один универсальный. Другими словами, вам не придется реализовать методы, которые вы не используете. Осуществление ISP дает слабую связанность и сильную связность.

Когда речь идет о связанности, связность также часто упоминается. Сильная связность означает сохранять подобные и связанные элементы вместе. Объединение связности и связанности является ортогональной структурой.

Идея состоит в том, чтобы сохранить компоненты ориентированными, и попытаться минимизировать зависимости между ними.

Обратите внимание на то, что это подобно Принципу единственной обязанности. Интерфейс — контракт, который удовлетворяет потребности. Нормально иметь класс, который реализует различные интерфейсы, но будьте осторожны, не нарушайте SRP.

Принцип инверсии зависимостей


Принцип инверсии зависимостей (Dependency Inversion Principle или DIP) имеет два основных положения:

  • Абстракции не должны зависеть от деталей.
  • Детали должны зависеть от абстракций.


Этот принцип можно перефразировать, как использовать тот же уровень абстракции на заданном уровне. Интерфейсы должны зависеть от других интерфейсов. Не добавляйте конкретные классы к сигнатурам методов интерфейса. Однако используйте интерфейсы в своих методах класса.

Обратите внимание на то, что Принцип инверсии зависимостей не совпадает с Внедрением зависимостей. Внедрение зависимости это когда один объект знает о другом зависимом объекте. Иными словами, речь идет о том, как один объект получает зависимость. С другой стороны, Принцип внедрение зависимости заключается в уровне абстракции. Кроме того, контейнер внедрения зависимости — это способ для автоматического соединения классов. Это не означает, что вы делаете внедрение зависимости все же. Например, взгляните на Service Locator.

Также, вместо того, чтобы работать с классами, которые являются сильно связанными, используйте интерфейсы. Это называется программирование интерфейса. Он уменьшает зависимость от особенностей реализации и допускает повторное использование кода. Он также гарантирует, что Вы сможете заменить реализацию, не нарушая ожидания того интерфейса, согласно Принципу подстановки Барбары Лисков.

Заключение


Как вы, наверное, заметили, избежание сильной связанности здесь является ключевым элементом. Здесь находиться большое количество кода, и если вы начинаете, сосредоточив внимание на исправление этого в одиночку, то вы сразу начнете писать лучший код.

Вот вам совет. Существует много принципов в разработке программного обеспечения. Даже если вы не понимаете все эти принципы, всегда думайте перед написанием кода. Не торопитесь, и постарайтесь понять то, что вы не понимаете.

Честно говоря, написание SOLID кода не так уж и сложное занятие.

TL;DR

STUPID — это акроним, который описывает неудачный опыт в Ориентированном Объектном Программировании:

  • Синглтон
  • Сильная Связанность
  • Невозможность тестирования
  • Преждевременная оптимизация
  • Не дескриптивное присвоение имени
  • Дублирование кода


SOLID — это акроним пяти основных принципов объектно-ориентированного программирования и дизайна и проектирования, чтобы исправить STUPID код:

  • Принцип единственной обязанности
  • Принцип открытости/закрытости
  • Принцип подстановки Барбары Лисков
  • Принцип разделения интерфейса
  • Принцип инверсии зависимостей


Золотое правило: Включи мозги!
Автор оригинала: William Durand
Dima @yarmolchuk
карма
3,0
рейтинг 0,0
iOS разработчик
Самое читаемое Разработка

Комментарии (8)

  • +1
    Принцип открытости/закрытости не объяснен вообще. Использование private это инкапсуляция. И каким образом инкапусляция делает класс открытым для расширения вообще не ясно.
  • +4
    Давайте рассмотрим пример. Прямоугольник — плоская фигура с четырьмя прямыми углами. У него есть ширина (width) и высота (height)

    Как-то раз я задумался над этим примером, после того, как прочитал его бурное обсуждение в комментах к этой статье.

    Начнем с того, что двумя числами можно в общем случае задать только попарные длины отрезков, но неверно, что 4 отрезка всегда образуют прямоугольник. Если мы договорились, что у нас всегда из них получается прямоугольник, мы уже задали некоторые начальные предусловия. В такой реализации у квадрата более сильные предусловия — добавляется условие, что длины всех отрезков должны быть равны. В реализации, когда задаются длины сторон и можно изменять стороны после создания, наследование использовать нельзя.

    Кстати, обычно как-то обходится стороной то, что если задать прямоугольнику одинаковую ширину и высоту, то фактически он будет квадратом, но в программе он останется прямоугольником. Если бы у нас была возможность изменить тип объекта после изменения параметров, то наследовать было бы можно. Присвоили значения width = 10, height = 10 — тип rect изменился с Rectangle на Square. Насколько это хорошо для разработки — это отдельный вопрос.

    А почему мы решили, что у прямоугольника можно просто так изменять стороны? Если мы его поменяем, то это уже будет другая фигура — может прямоугольник, может квадрат. Если размеры сторон задаются только при создании, то нет особых проблем, чтобы унаследовать квадрат от прямоугольника, если это зачем-нибудь требуется. Хотя, возможно, тут я не прав.

    А почему мы решили, что прямоугольник можно задавать 2 сторонами? Прямоугольник на плоскости не задается 2 сторонами, он задается 4 точками. Как и квадрат. Кроме того, это математический объект, и мы можем его только задать. Если мы его поменяем, то значит мы задали другую фигуру — может квадрат, может ромб, может просто какой-то четырехугольник. С этой точки зрения, прямоугольник и квадрат должны иметь конструкторы с одинаковым числом параметров — координаты 4 точек. При создании должно появляться исключение, если точки не образуют соответствующую фигуру. В этом случае с наследованием проблем нет.
  • 0
    Автор конечно молодец, но принципы понял не совсем правильно. В прицепе Лесков описал просто полиморфизм. Принцип открытости/закрытости вообще не о том.
    Остальное написано таким образом, что если бы не знал эти принципы, никогда бы их не понял.
    • 0
      • 0
        «В прицепе Лесков описал просто полиморфизм.» Я имел ввиду что автор «Дима» в своей главе про принцип Лесков, описал неправильно этот самый принцип.
        • +1
          Я имел ввиду, что автор Лесков писал про очарованного странника, а автор Лисков — про полиморфизм :)
          • –4
            Произношение фамилий — это спорная тема.
            • +1
              Ничего спорного в конкретно этой фамилии нет.

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