Применим ли принцип единой ответственности к функциям?

17

По словам Роберта К. Мартина, SRP заявляет, что:

Никогда не должно быть более одной причины для изменения класса .

Однако в своей книге « Чистый код» , глава 3: Функции, он показывает следующий блок кода:

    public Money calculatePay(Employee e) throws InvalidEmployeeType {
        switch (e.type) {
            case COMMISSIONED:
                return calculateCommissionedPay(e);
            case HOURLY:
                return calculateHourlyPay(e);
            case SALARIED:
                return calculateSalariedPay(e);
            default:
                throw new InvalidEmployeeType(e.type);
        }
    }

И тогда говорится:

Есть несколько проблем с этой функцией. Во-первых, он большой, и когда добавляются новые типы сотрудников, он будет расти. Во-вторых, это очень четко делает больше, чем одно. В-третьих, он нарушает принцип единой ответственности (SRP), потому что есть несколько причин для его изменения . [Акцент мой]

Во-первых, я думал, что SRP был определен для классов, но оказалось, что он также применим к функциям. Во-вторых, как эта функция имеет более чем одну причину для изменения ? Я могу видеть только его изменение из-за изменения на сотрудника.

Энрике
источник
5
Это похоже на случай учебника для полиморфизма.
wchargin
Это очень интересная тема. Есть ли шанс добавить следующее решение этой проблемы? Я хотел бы, чтобы каждый из них вставлял функцию Calculay в каждый класс сотрудников, но это было бы плохо, потому что теперь каждый класс сотрудников может быть изменен из-за: 1. Расчет оплаты. 2. добавление дополнительных свойств в класс и т. Д.
Став Альфи

Ответы:

13

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

Итак, в вашем примере calculatePayметод нужно будет менять всякий раз, когда требуются новые типы сотрудников. Поскольку один тип сотрудника может не иметь ничего общего с другим, это будет нарушением принципа, если вы будете держать их вместе, так как изменение затронет различные группы пользователей (или участников варианта использования) в системе.

Теперь о том, применим ли этот принцип к функциям: даже если у вас есть нарушение только в одном методе, вы все равно меняете класс по нескольким причинам, поэтому это все еще является нарушением SRP.

MichelHenrich
источник
1
Я пытался посмотреть связанное видео на YouTube, но после 10 минут без упоминания о группировке по сценариям использования я сдался. Первые 6 минут все бродят по энтропии, без видимой причины. Если бы вы дали место в видео, где он начинает это обсуждать, это было бы полезно.
Майкл Шоу,
@MichaelShaw Попробуйте смотреть с 10:40. Дядя Боб упоминает, что код «изменится по разным причинам, из-за разных людей». Я думаю, что это может быть то, на что Мишель Генрих пытается указать нам.
Энрике
Закончил просмотр всего 50-минутного видео на YouTube, большинство из которых было не о том, что предполагалось уточнить. В 16:00 я заметил, что он уже ушел от этой темы, и он никогда не вернулся к ней. «Объяснение» по сути сводится к следующему: «ответственность» в ПСП не означает, что это означает «разные причины изменений», что на самом деле означает «изменения по просьбе разных людей», что на самом деле означает «изменения на запрос разных ролей, которые люди играют ». «Объяснение» ничего не проясняет, оно заменяет одно смутное слово другим.
Майкл Шоу
2
@MichaelShaw, как в цитате из книги, если вам нужно ввести разные типы сотрудников, вы должны изменить класс Employee. Разные роли могут отвечать за оплату сотрудникам разных типов, поэтому в этом случае класс Employee необходимо изменить для более чем одной роли, что приводит к нарушению SRP.
Мишель Генрих
1
@MichaelShaw да, вы правы - SRP зависит от того, как организована организация. Именно поэтому я добавляю «может» или «может» ко всем моим комментариям :). Однако даже в тех случаях, когда код не может нарушать SRP, он, безусловно, нарушает OCP.
Мишель Генрих
3

На странице 176, Глава 12: Появление, в разделе, озаглавленном « Минимальные классы и методы», в книге приведено несколько исправлений:

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

и

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

Очевидно, он говорит о догматизме в следовании SRP, чтобы сломать совершенно прекрасные невинные маленькие методы как calculatePay()выше.

Майк Накис
источник
3

Когда мистер Мартин применяет SRP к функции, он неявно расширяет свое определение SRP. Поскольку SRP является ОО-специфической формулировкой общего принципа, и, так как это хорошая идея применительно к функциям, я не вижу проблемы с этим (хотя было бы неплохо, если бы он явно включил ее в определение).

Я также не вижу более одной причины для изменения, и я не верю, что полезно думать о ПСП в терминах «обязанностей» или «причин для изменения». По сути, к чему стремится SRP, это то, что программные объекты (функции, классы и т. Д.) Должны выполнять одну задачу и делать это хорошо.

Если вы посмотрите на мое определение, оно не менее расплывчато, чем обычная формулировка SRP. Проблема с обычными определениями SRP заключается не в том, что они слишком расплывчаты, а в том, что они пытаются быть слишком конкретными в отношении чего-то, что по существу расплывчато.

Если вы посмотрите на то calculatePay, что делает, он явно делает одну вещь: отправка на основе типа. Поскольку в Java есть встроенные способы выполнения диспетчеризации на основе типов, calculatePayона неэлегатична и не идиоматична, поэтому ее следует переписать, но не по указанным причинам.

Майкл Шоу
источник
-2

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

Утверждение «причина для изменения» иногда немного вводит в заблуждение, но если вы измените calculateSalariedPayили calculateHourlyPayили Employee.typeваше перечисление, вам придется изменить этот метод.

В вашем примере функция:

  • проверяет тип работника
  • вызывает другую функцию, которая вычисляет деньги в соответствии с типом

По моему мнению, это не является прямым нарушением SRP, так как случаи переключения и вызовы не могут быть записаны короче, если вы думаете о Employee, и методы уже существуют. В любом случае, это явное нарушение принципа открытого-закрытого (OCP), так как вы должны добавлять операторы «case», если добавляете типы сотрудников, так что это плохая реализация: рефакторинг.

Мы не знаем, как рассчитывать «деньги», но самый простой способ - иметь Employeeинтерфейс и некоторые конкретные реализации с getMoneyметодами. В этом случае вся функция не нужна.

Если его сложнее вычислить, можно использовать шаблон посетителя, который также не является 100% -ным SRP, но это больше OCP, чем случай коммутатора.

крестцовой
источник
2
Не уверен, как вы можете перечислить 2 вещи, которые делает функция, но скажите, что это не нарушение SRP.
Джефф
@JeffO: Это не две вещи, это две части одной вещи: вызов соответствующей функции в зависимости от типа.
Майкл Шоу