Чи порушує конструктор, який підтверджує свої аргументи, SRP?


66

Я намагаюся максимально дотримуватися Принципу єдиної відповідальності (СРП) і звик до певної моделі (для СРП щодо методів), сильно покладаючись на делегатів. Мені хотілося б знати, чи є такий підхід обгрунтованим чи є якісь серйозні проблеми з ним.

Наприклад, щоб перевірити вхід до конструктора, я міг би ввести наступний метод ( Streamвхід випадковий, може бути що завгодно)

private void CheckInput(Stream stream)
{
    if(stream == null)
    {
        throw new ArgumentNullException();
    }

    if(!stream.CanWrite)
    {
        throw new ArgumentException();
    }
}

Цей метод (певно) робить більше ніж одне

  • Перевірте входи
  • Киньте різні винятки

Тому, щоб дотримуватися SRP, я змінив логіку на

private void CheckInput(Stream stream, 
                        params (Predicate<Stream> predicate, Action action)[] inputCheckers)
{
    foreach(var inputChecker in inputCheckers)
    {
        if(inputChecker.predicate(stream))
        {
            inputChecker.action();
        }
    }
}

Що нібито робить лише одне (чи це?): Перевірте вхідні дані. Для фактичної перевірки входів та викидів винятків я ввів такі методи

bool StreamIsNull(Stream s)
{
    return s == null;
}

bool StreamIsReadonly(Stream s)
{
    return !s.CanWrite;
}

void Throw<TException>() where TException : Exception, new()
{
    throw new TException();
}

і може дзвонити, CheckInputяк

CheckInput(stream,
    (this.StreamIsNull, this.Throw<ArgumentNullException>),
    (this.StreamIsReadonly, this.Throw<ArgumentException>))

Це взагалі краще, ніж перший варіант, або я ввожу необмежену складність? Чи є ще спосіб вдосконалити цю схему, якщо вона взагалі є життєздатною?


26
Я можу стверджувати, що CheckInputвсе ще робиться кілька речей: це ітерація над масивом, і виклик функції предиката та виклик функції дії. Хіба це не є порушенням СРП?
Барт ван Іґен Шенау

8
Так, я це намагався зробити.
Барт ван Інген Шенау

135
важливо пам’ятати, що це єдиний принцип відповідальності ; не єдиний принцип дії . Вона несе одну відповідальність: переконатися, що потік визначений та записаний.
Девід Арно

40
Майте на увазі, що суть цих принципів програмного забезпечення полягає в тому, щоб зробити код більш читабельним і доступним для обслуговування. Ваш оригінальний чек-вхід читати та підтримувати набагато простіше, ніж ваш реконструйований варіант. Насправді, якби я коли-небудь стикався з вашим остаточним методом CheckInput у кодовій базі, я б це все ломав і переписав, щоб він відповідав тому, що у вас був.
17 з 26

17
Ці "принципи" практично марні, оскільки ви можете просто визначити "єдину відповідальність" будь-яким способом, яким ви хочете йти вперед, якою б не була ваша первісна ідея. Але якщо ви спробуєте їх жорстко застосувати, я думаю, ви закінчите з таким кодом, який, чесно кажучи, важко зрозуміти.
Кейсі

Відповіді:


151

SRP - це, мабуть, найбільш неправильно зрозумілий програмний принцип.

Програмна програма побудована з модулів, які побудовані з модулів, які побудовані з ...

Внизу, одна функція, така як, CheckInputбуде містити лише крихітний біт логіки, але, як ви рухаєтеся вгору, кожен наступний модуль інкапсулює все більше логіки, і це нормально .

SRP не в тому, щоб робити одну атомну дію. Йдеться про єдину відповідальність, навіть якщо ця відповідальність вимагає численних дій ... і, зрештою, йдеться про технічне обслуговування та перевірку :

  • це сприяє інкапсуляції (уникаючи об'єктів Бога),
  • це сприяє відокремленню проблем (уникаючи рятування змін через усю базу коду),
  • це допомагає передбачуваності, звужуючи сферу обов'язків.

Те, що CheckInputреалізується за допомогою двох перевірок та викликає два різних винятки, певною мірою не має значення .

CheckInputнесе відповідальність: гарантує, що вхід відповідає вимогам. Так, є декілька вимог, але це не означає, що є численні обов'язки. Так, ви можете розділити чеки, але як би це допомогло? У якийсь момент чеки повинні бути перераховані певним чином.

Порівняємо:

Constructor(Stream stream) {
    CheckInput(stream);
    // ...
}

проти:

Constructor(Stream stream) {
    CheckInput(stream,
        (this.StreamIsNull, this.Throw<ArgumentNullException>),
        (this.StreamIsReadonly, this.Throw<ArgumentException>));
    // ...
}

Тепер CheckInputробить менше ... але його абонент робить більше!

Ви перенесли список вимог з місця CheckInput, де вони інкапсульовані, до місця, Constructorде вони видно.

Це гарна зміна? Це залежить:

  • Якщо CheckInputйого називають лише там: це дискусійно, з одного боку він робить вимоги видимими, з іншого - це захаращує код;
  • Якщо вас CheckInputвикликають кілька разів з однаковими вимогами , це порушує DRY і у вас є проблема інкапсуляції.

Важливо усвідомити, що одна відповідальність може означати велику роботу. "Мозок" автомобіля, що керує самостійно, несе єдину відповідальність:

Керування автомобілем до місця призначення.

Це одна відповідальність, але вимагає узгодження тонни датчиків і акторів, прийняття багато рішень і навіть має, можливо, суперечливі вимоги 1 ...

... проте, все це капсульовано. Тож клієнту все одно.

1 безпека пасажирів, безпека інших людей, дотримання правил, ...


2
Я думаю, що ви використовуєте слово "інкапсуляція" та його похідні заплутано. Крім цього, чудова відповідь!
Фабіо Турати

4
Я погоджуюся з вашою відповіддю, але міркування мозку, що управляє автомобілем, часто спокушають людей зламати SRP. Як ви сказали, це модулі, виготовлені з модулів, виготовлених з модулів. Ви можете визначити призначення цілої системи, але ця система повинна бути розбита. Ви можете зламати практично будь-яку проблему.
Сава Б.

13
@SavaB. Звичайно, але принцип залишається тим самим. Модуль повинен мати єдину відповідальність, хоча і більшого обсягу, ніж його складові.
Матьє М.

3
@ user949300 Гаразд, як щодо просто "водіння". Дійсно, "водіння" - це відповідальність, "безпечно" та "законно" - це вимоги щодо того, як воно виконує цю відповідальність. І ми часто перераховуємо вимоги, заявляючи про відповідальність.
Брайан Маккутчон

1
"SRP - це, мабуть, найбільш неправильно зрозумілий програмний принцип". Як свідчить ця відповідь :)
Майкл

41

Цитуючи дядька Боба про SRP ( https://8thlight.com/blog/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html ):

Принцип єдиної відповідальності (SRP) зазначає, що кожен програмний модуль повинен мати одну і лише одну причину для зміни.

... Цей принцип стосується людей.

... Коли ви пишете програмний модуль, ви хочете переконатися, що коли вимагаються зміни, ці зміни можуть походити лише від однієї людини, а точніше - однієї групи з щільно поєднаною людиною, яка представляє одну вузько визначену ділову функцію.

... Це причина, що ми не ставимо SQL в JSP. Це причина, що ми не генеруємо HTML в модулях, які обчислюють результати. Це причина того, що правила бізнесу не повинні знати схему бази даних. Це причина, яку ми розділяємо.

Він пояснює, що програмні модулі повинні вирішувати проблеми конкретних зацікавлених сторін. Тому, відповідаючи на ваше запитання:

Це взагалі краще, ніж перший варіант, або я ввожу необмежену складність? Чи є ще спосіб вдосконалити цю схему, якщо вона взагалі є життєздатною?

IMO, ви дивитесь лише на один метод, коли ви повинні дивитись на більш високий рівень (рівень класу в цьому випадку). Можливо, ми повинні ознайомитись із тим, чим зараз займається ваш клас (і для цього потрібно більше пояснень щодо вашого сценарію). Поки що ваш клас все ще робить те саме. Наприклад, якщо завтра є запит на зміну щодо певної перевірки (наприклад: "зараз потік може бути нульовим"), тоді вам все одно потрібно перейти до цього класу та змінити матеріали в ньому.


4
Найкраща відповідь. Щоб уточнити, що стосується ОП, якщо охоронні чеки надходять від двох різних зацікавлених сторін / відділів, то їх checkInputs()слід розділити, скажімо, на checkMarketingInputs()та checkRegulatoryInputs(). Інакше чудово поєднувати їх все в один метод.
user949300

36

Ні, про цю зміну НРП не повідомляє.

Запитайте себе, чому у вашій шасі немає перевірки на те, що "переданий об'єкт - це потік" . Відповідь очевидна: мова заважає абоненту скласти програму, яка передається в потоці.

Система типу C # недостатня для задоволення ваших потреб; ваші чеки реалізують примусове застосування інваріантів, які не можуть бути виражені в типовій системі сьогодні . Якби було способом сказати, що метод займає нерегульований потік для запису, ви б це написали, але цього немає, тому ви зробили наступне найкраще: ви застосували обмеження типу під час виконання. Сподіваємось, ви також це задокументували, так що розробникам, які використовують ваш метод, не доведеться його порушувати, не випробовувати свої тестові справи, а потім усунути проблему.

Нанесення типів на метод не є порушенням Принципу єдиної відповідальності; не є методом, що забезпечує виконання своїх передумов і не підтверджує свої постумови.


1
Крім того, залишити створений об’єкт у дійсному стані - це одна відповідальність, яку конструктор принципово завжди несе. Якщо він, як ви вже згадували, вимагає додаткових перевірок, які час виконання та / або компілятор не може забезпечити, то насправді немає способу його обійти.
SBI

23

Не всі обов'язки створюються рівними.

введіть тут опис зображення

введіть тут опис зображення

Ось два ящики. Вони обоє несуть одну відповідальність. У кожного з них є імена, які дозволяють вам знати, що належить до них. Один - ящик із срібла. Інший - ящик для сміття.

То яка різниця? Ящик срібного посуду дає зрозуміти, що до нього не належить. Однак ящик для сміття приймає все, що підходить. Виймання ложок із ящика срібного посуду здається дуже неправильним. Та все ж мені важко думати про все, що було б пропущено, якби його зняли з шухляди. Правда полягає в тому, що ви можете стверджувати, що будь-яка відповідальність є одна, але, на вашу думку, є більш цілеспрямована одна відповідальність?

Об'єкт, що несе єдину відповідальність, не означає, що тут може статися лише одне. Обов’язки можуть гніздитися. Але ці обов'язки повинні вносити сенс, вони не повинні дивувати вас, коли ви знайдете їх тут, і вам слід їх сумувати, якщо їх не було.

Тож коли пропонуєте

CheckInput(Stream stream);

Я не переживаю, що це і перевірка внесків, і викидання виключень. Мені б занепокоєно, якби це було і перевірка вводу, і збереження вводу. Це жахливий сюрприз. Одного я б не пропустив, якби його не було.


21

Коли ви зав'язуєте себе у вузлах і пишете дивний код, щоб відповідати Важливому принципу програмного забезпечення, зазвичай ви неправильно зрозуміли принцип (хоча іноді принцип неправильний). Як вказує відмінна відповідь Матьє, весь сенс СРП залежить від визначення поняття "відповідальність".

Досвідчені програмісти бачать ці принципи і пов'язують їх із спогадами про код, який ми зкрутили; менш досвідчені програмісти бачать їх і, можливо, взагалі нічого не стосуються. Це абстракція, що плаває в просторі, вся усмішка і жодна кішка. Так вони здогадуються, а це зазвичай йде погано. Перш ніж ви розробили сенс програмування для коней, різниця між дивним надскладним кодом та звичайним кодом зовсім не очевидна.

Це не релігійна заповідь, якій ви повинні виконувати незалежно від особистих наслідків. Це скоріше правило, що означає формалізувати один елемент почуття коня програмування і допомогти вам зберегти свій код максимально простим і зрозумілим. Якщо це має протилежний ефект, ви праві шукати зовнішній вклад.

У програмуванні ви не можете бути набагато гнівнішими, ніж намагаючись вивести значення ідентифікатора з перших принципів, просто поглянувши на нього, і це стосується ідентифікаторів, письмово про програмування стільки ж, скільки ідентифікаторів у фактичному коді.


14

Роль CheckInput

По-перше, дозвольте мені викласти очевидне там, CheckInput це робить одне, навіть якщо це перевірка різних аспектів. Зрештою, він перевіряє вхід . Можна стверджувати, що це не одне, якщо ви маєте справу з названими методами DoSomething, але я думаю, що можна впевнено припустити, що перевірка введення не надто розпливчаста.

Додавання цього шаблону до предикатів може бути корисним, якщо ви не хочете, щоб логіка перевірки введення була розміщена у вашому класі, але ця модель виглядає досить багатослівною для того, що ви намагаєтесь досягти. Це може бути набагато прямішим просто пройти інтерфейс IStreamValidatorз одним методом, isValid(Stream)якщо це те, що ви хочете отримати. Будь-який клас, який реалізує, IStreamValidatorможе використовувати предикати на зразок StreamIsNullабо StreamIsReadonlyза бажанням, але повертаючись до центральної точки, це є досить смішною зміною в інтересах збереження принципу єдиної відповідальності.

Перевірка обгрунтованості

Я вважаю, що всім нам дозволено "перевірити обгрунтованість", щоб переконатися, що ви принаймні маєте справу з потоком, який не є нульовим і записуваним, і ця основна перевірка якось не робить ваш клас валідатором потоків. Зауважте, що більш досконалі чеки найкраще залишити за межами вашого класу, але саме тут намальована лінія. Після того, як вам потрібно почати змінювати стан свого потоку, читаючи з нього або присвячуючи ресурси на валідацію, ви почали проводити офіційну перевірку свого потоку, і це те, що слід вивести в його власний клас.

Висновок

Думаю, що якщо ви застосовуєте шаблон, щоб краще організувати аспект свого класу, це заслуговує на те, щоб бути у своєму класі. Оскільки модель не підходить, вам слід також поставити під сумнів, чи справді вона належить до власного класу в першу чергу. Думаю, що якщо ви вважаєте, що перевірка потоку, можливо, буде змінена в майбутньому, і особливо, якщо ви вважаєте, що ця перевірка може навіть мати динамічний характер, тоді описаний вами шаблон є хорошою ідеєю, навіть якщо це може бути бути спочатку тривіальним. В іншому випадку не потрібно довільно робити вашу програму більш складною. Давайте називати лопату лопатою. Перевірка - це одне, але перевірка нульового введення не є валідацією, і тому, я думаю, ви можете безпечно тримати його у своєму класі, не порушуючи принципу єдиної відповідальності.


4

Принцип чітко не зазначає, що фрагмент коду повинен "робити лише одну річ".

"Відповідальність" в СРП слід розуміти на рівні вимог. Відповідальність коду полягає у задоволенні вимог бізнесу. SRP порушується, якщо об'єкт задовольняє більш ніж одним незалежним вимогам бізнесу . Під незалежністю це означає, що одна вимога може змінитися, поки інша вимога залишиться на місці.

Цілком можливо, що введено нову вимогу бізнесу, що означає, що цей конкретний об'єкт не повинен перевіряти на читабельність, тоді як інша бізнес-вимога все ще вимагає, щоб об'єкт перевірив, чи читається? Ні, оскільки вимоги бізнесу не визначають деталі впровадження на цьому рівні.

Фактичним прикладом порушення SRP буде такий код:

var message = "Your package will arrive before " + DateTime.Now.AddDays(14);

Цей код дуже простий, але все ж можливо, що текст буде змінюватися незалежно від очікуваної дати доставки, оскільки їх вирішують різні частини бізнесу.


Різний клас практично для кожної вимоги звучить як непристойний кошмар.
whatsisname

@whatsisname: Тоді, можливо, SRP не для вас. Жоден принцип дизайну не застосовується для всіх видів та розмірів проектів. (Але майте на увазі, що ми говоримо лише про незалежні вимоги (тобто можуть змінюватися незалежно), а не про будь-які вимоги, з тих пір це буде просто залежати від того, наскільки вони будуть визначені дрібнозернисто.)
JacquesB

Я думаю, що більше, ніж СРП вимагає елемента ситуаційного судження, який складно описати в одній прискіпливій фразі.
whatsisname

@whatsisname: Я повністю згоден.
ЖакБ

+1 для SRP порушується, якщо об'єкт задовольняє більш ніж одним незалежним вимогам бізнесу. За незалежністю це означає, що одна вимога може змінитися, тоді як інша вимога залишиться в силі
Juzer Ali

3

Мені подобається точка з відповіді @ EricLippert :

Запитайте себе, чому у вашій шасі немає перевірки на предмет, переданий в потоці . Відповідь очевидна: мова заважає абоненту скласти програму, яка передається в потоці.

Система типу C # недостатня для задоволення ваших потреб; ваші чеки реалізують примусове застосування інваріантів, які не можуть бути виражені в типовій системі сьогодні . Якби було способом сказати, що метод займає нерегульований потік для запису, ви б це написали, але цього немає, тому ви зробили наступне найкраще: ви застосували обмеження типу під час виконання. Сподіваємось, ви також це задокументували, так що розробникам, які використовують ваш метод, не доведеться його порушувати, не випробовувати свої тестові справи, а потім виправити проблему.

Право EricLippert має право на те, що це проблема для типової системи. А оскільки ви хочете використовувати принцип єдиної відповідальності (SRP), то вам в основному потрібна система типу, щоб відповідати за цю роботу.

Насправді можна сортувати це в C #. Ми можемо зловити літерали nullпід час компіляції, а потім нелітеральні null- під час виконання. Це не так добре, як повна перевірка часу компіляції, але це суворе вдосконалення, ніж ніколи не потрапляти під час компіляції.

Отже, ви знаєте, як є C # Nullable<T>? Давайте повернемо це і зробимо NonNullable<T>:

public struct NonNullable<T> where T : class
{
    public T Value { get; private set; }
    public NonNullable(T value)
    {
        if (value == null) { throw new NullArgumentException(); }
        this.Value = value;
    }
    //  Ease-of-use:
    public static implicit operator T(NonNullable<T> value) { return value.Value; }
    public static implicit operator NonNullable<T>(T value) { return new NonNullable<T>(value); }

    //  Hack-ish overloads that prevent null-literals from being implicitly converted into NonNullable<T>'s.
    public static implicit operator NonNullable<T>(Tuple<T> value) { return new NonNullable<T>(value.Item1); }
    public static implicit operator NonNullable<T>(Tuple<T, T> value) { return new NonNullable<T>(value.Item1); }
}

Тепер замість того, щоб писати

public void Foo(Stream stream)
{
  if (stream == null) { throw new NullArgumentException(); }

  // ...method code...
}

, просто напишіть:

public void Foo(NonNullable<Stream> stream)
{
  // ...method code...
}

Тоді є три випадки використання:

  1. Користувачі дзвінки Foo()з ненульовим значенням Stream:

    Stream stream = new Stream();
    Foo(stream);

    Це бажаний випадок використання, і він працює з або без NonNullable<>.

  2. Користувач дзвінки Foo()з нулем Stream:

    Stream stream = null;
    Foo(stream);

    Це помилка виклику. Тут NonNullable<>допомагає повідомити користувача, що вони цього не повинні робити, але насправді це не зупиняє їх. Так чи інакше, це призводить до запуску часу NullArgumentException.

  3. Користувач дзвонить за Foo()допомогою null:

    Foo(null);

    nullне конвертується неявно в a NonNullable<>, тому користувач отримує помилку в IDE перед запуском. Це делегування перевірки нуля в систему типів, як би радила SRP.

Ви можете розширити цей метод і для того, щоб стверджувати інші речі щодо своїх аргументів. Наприклад, оскільки вам потрібен потік, який можна записати, ви можете визначити, struct WriteableStream<T> where T:Streamщо перевіряє nullі stream.CanWriteв конструкторі, і в ньому. Це все ще буде перевірка типу виконання, але:

  1. Він прикрашає тип WriteableStreamкласифікатором, сигналізуючи про потребу абонентів.

  2. Він перевіряє в одному місці в коді, тому вам не доведеться повторювати чек і throw InvalidArgumentExceptionкожен раз.

  3. Він краще відповідає SRP, переносячи обов'язки перевірки типу на типову систему (як поширюється загальними декораторами).


3

Ваш підхід наразі є процедурним. Ви розбиваєте Streamоб'єкт і перевіряєте його зовні. Не робіть цього - це порушує інкапсуляцію. Нехай Streamвідповідальність за її власну перевірку. Ми не можемо прагнути застосувати SRP, поки не отримаємо кілька класів для його застосування.

Ось Streamщо виконує дію, лише якщо вона проходить перевірку:

class Stream
{
    public void someAction()
    {
        if(!stream.canWrite)
        {
            throw new ArgumentException();
        }

        System.out.println("My action");
    }
}

Але зараз ми порушуємо SRP! "Клас повинен мати лише одну причину для зміни." У нас є суміш 1) перевірки та 2) фактичної логіки. У нас є дві причини, які можуть знадобитися змінити.

Ми можемо вирішити це за допомогою перевіряючих декораторів . По-перше, нам потрібно перетворити наш Streamінтерфейс і реалізувати це як конкретний клас.

interface Stream
{
    void someAction();
}

class DefaultStream implements Stream
{
    @Override
    public void someAction()
    {
        System.out.println("My action");
    }
}

Тепер ми можемо написати декоратор, який загортає Stream, виконує перевірку та відкладає задану Streamдля фактичної логіки дії.

class WritableStream implements Stream
{
    private final Stream stream;

    public WritableStream(final Stream stream)
    {
        this.stream = stream;
    }

    @Override
    public void someAction()
    {
        if(!stream.canWrite)
        {
            throw new ArgumentException();
        }
        stream.someAction();
    }
}

Тепер ми можемо складати ці будь-які способи, які нам подобаються:

final Stream myStream = new WritableStream(
    new DefaultStream()
);

Хочете додаткової перевірки? Додати ще одного декоратора.


1

Робота класу - це надання послуги, що відповідає договору . Клас завжди має контракт: набір вимог щодо його використання та обіцянки, які він висуває щодо його стану та результатів, за умови, що вимоги будуть виконані. Цей договір може бути явним через документацію та / або твердження, або неявний, але він завжди існує.

Частина контракту вашого класу полягає в тому, що абонент надає конструктору деякі аргументи, які не повинні бути недійсними. Виконання договору є обов'язком класу, тому перевірка того, що абонент виконав свою частину договору, може легко вважатися в межах відповідальності класу.

Ідея, що клас реалізує контракт, пов'язана з Бертраном Мейєром , дизайнером Ейфелевої мови програмування та ідеєю дизайну за контрактом . Ейфелева мова робить специфікацію та перевірку контрактної частини мови.


0

Як було зазначено в інших відповідях, SRP часто не розуміється. Справа не в тому, щоб мати атомний код, який виконує лише одну функцію. Йдеться про те, щоб переконатися, що ваші об'єкти та методи роблять лише одне, а те, що одна справа робиться лише в одному місці.

Розглянемо поганий приклад у псевдокоді.

class Math
    private int a;
    private int b;
    def constructor(int x, int y) 
        if(x != null)
          a = x
        else if(x < 0)
          a = abs(x)
        else if (x == -1)
          throw "Some Silly Error"
        else
          a = 0
        end
        if(y != null)
           b = y
        else if(y < 0)
           b = abs(y)
        else if(y == -1)
           throw "Some Silly Error"
        else
         b = 0
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

У нашому досить абсурдному прикладі "відповідальність" конструктора Math # полягає в тому, щоб зробити математичний об'єкт корисним. Це роблять, спочатку дезінфікуючи вхід, потім впевнившись, що значення не -1.

Це дійсно SRP, оскільки конструктор робить лише одне. Це підготовка об'єкта Math. Однак це не дуже ретельно. Це порушує DRY.

Тож давайте ще один пропуск

class Math
    private int a;
    private int b;
    def constructor(int x, int y)
        cleanX(x)
        cleanY(y)
    end
    def cleanX(int x)
        if(x != null)
          a = x
        else if(x < 0)
          a = abs(x)
        else if (x == -1)
          throw "Some Silly Error"
        else
          a = 0
        end
   end
   def cleanY(int y)
        if(y != null)
           b = y
        else if(y < 0)
           b = abs(y)
        else if(y == -1)
           throw "Some Silly Error"
        else
         b = 0
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

У цьому пропуску ми трохи покращили DRY, але у нас все ще є способи йти із DRY. SRP з іншого боку, здається, трохи відключений. Зараз у нас є дві функції з однаковою роботою. І CleanX, і CleanY санірують введення.

Давайте давайте ще одне

class Math
    private int a;
    private int b;
    def constructor(int x, int y)
        a = clean(x)
        b = clean(y)
    end
    def clean(int i)
        if(i != null)
          return i
        else if(i < 0)
          return abs(i)
        else if (i == -1)
          throw "Some Silly Error"
        else
          return 0
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

Тепер нарешті було краще про DRY, і SRP, схоже, згоден. У нас є лише одне місце, яке виконує "санітарну" роботу.

Код теоретично більш доцільний і кращий, але коли ми хочемо виправити помилку та посилити код, нам це потрібно зробити лише в одному місці.

class Math
    private int a;
    private int b;
    def constructor(int x, int y)
        a = clean(x)
        b = clean(y)
    end
    def clean(int i)
        if(i == null)
          return 0
        else if (i == -1)
          throw "Some Silly Error"
        else
          return abs(i)
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

У більшості випадків реального світу об'єкти були б складнішими, і SRP застосовуватиметься через купу об'єктів. Наприклад, вік може належати Батькові, Матері, Сину, Дочці, тому замість того, щоб мати 4 класи, які визначають вік від дати народження, у вас є клас Особи, який робить це, і 4 класи успадковують це. Але я сподіваюся, що цей приклад допомагає пояснити. SRP - це не про атомні дії, а про те, щоб виконати "роботу".


-3

Говорячи про SRP, дядько Боб не любить скрізь нульові чеки. Взагалі ви, як команда, повинні уникати використання нульових параметрів для конструкторів, коли це можливо. Коли ви публікуєте код за межами команди, все може змінитися.

Посилення ненульованості параметрів конструктора без попереднього забезпечення згуртованості відповідного класу призводить до роздуття викличного коду, особливо тестів.

Якщо ви дійсно хочете виконувати такі договори, подумайте про використання Debug.Assertчи щось подібне, щоб зменшити безлад:

public AClassThatDefinitelyNeedsAWritableStream(Stream stream)
{
   Assert.That(stream.CanWrite, "Put crucial information here, and not inane bloat.");

   // Go on normal operation.
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.