Коли і чому слід використовувати void (замість, наприклад, bool / int)


30

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

Іноді я б погодився, що найчастіше краще повернути щось на кшталт boolабо int, а точніше просто зробити void. Я не впевнений, хоча у великій картині про плюси і мінуси.

Залежно від ситуації, повернення значка intможе дати абоненту знати кількість рядків або об'єктів, на які впливає метод (наприклад, 5 записів, збережених у MSSQL). Якщо такий метод, як "InsertSomething", повертає булевий, я можу мати метод, призначений для повернення у trueвипадку успіху false. Абонент може вибирати чи не діяти на цій інформації.

З іншої сторони,

  • Чи може це призвести до менш чіткої мети виклику методу? Погане кодування часто змушує мене двічі перевірити вміст методу. Якщо він щось повертає, він говорить вам, що метод такого типу, що вам потрібно щось робити з повернутим результатом.
  • Інша проблема, якщо впровадження методу вам невідоме, що розробник вирішив повернути, що не є критично важливим? Звичайно, ви можете прокоментувати це.
  • Повертане значення повинно бути оброблене, коли обробка могла закінчитися в кінцевій дузі методу.
  • Що відбувається під кришкою? Чи отримав викликаний метод falseчерез помилку, яку викинули? Або він повернув помилку через оцінений результат?

Який у вас досвід з цим? Як би ви діяли з цього приводу?


1
Функція, яка повертає недійсність, технічно зовсім не є функцією. Це просто метод / процедура. Повернення, voidпринаймні, дозволяє розробникові знати, що повернене значення методу є несуттєвим; вона робить дію, а не обчислює значення.
KChaloux

Відповіді:


32

У разі повернення значення bool для вказівки на успіх або невдачу методу, я віддаю перевагу Tryпарадигмі -prefix, яка використовується в різних методах .NET.

Наприклад, void InsertRow()метод може кинути виняток, якщо вже існує рядок з тим самим ключем. Питання в тому, чи обґрунтовано припускати, що абонент гарантує, що їх рядок є унікальним перед викликом InsertRow? Якщо відповідь "ні", то я б також вказав, bool TryInsertRow()що повертає помилкове значення, якщо рядок вже існує. В інших випадках, таких як помилки підключення db, TryInsertRow все-таки може викинути виняток, припускаючи, що підтримка підключення db є відповідальністю абонента.


4
+1 за те, щоб розрізняти справу з очікуваними та несподіваними невдачами - моя власна відповідь недостатньо наголосила на цьому.
Péter Török

Абсолютно +1 для спроби винайти принцип TryXX-методів, які, як правило, полегшують і метод спробу, і основний метод, простіший в обслуговуванні та простіше зрозуміти їх призначення.
Незалежна

+1 Добре сказано. Ключ до найкращої відповіді на це питання полягає в тому, що іноді ви хочете дати абоненту можливість настирливого методу, який призведе до виключення. Однак у цих випадках виняток не повинен бути викритим і підробленим методом асертивного методу. Справа в тому, що абонент несе відповідальність. З іншого боку, спроба-парадигма (або Монада) повинна вловлювати винятки та обробляти винятки, а потім або передавати назад bool, або описовий Енум, якщо потрібно більше деталей. Зауважте, що абонент очікує, що Try / Monad ніколи не кине виняток. Це як точка входу.
Suamere

Отже, оскільки очікується, що виняток ніколи не виникає з Try / Monad, але bool не є достатньо описовим, щоб повідомити абонента про те, що з'єднання db не вдалося, або запит http має одне з багатьох значень повернення, необхідних для дії по-різному, ви можете використовувати Enum замість bool для своєї Try / Monad.
Суамер

Якщо у вас є TryInsertRow - повернення bool - якщо, скажімо, в базі даних є більше одного унікального обмеження - як ви точно знаєте, в чому помилка - і повідомте про це користувачеві? Чи повинен використовуватися більш багатий тип повернення, що містить повідомлення про помилку / код & bool успіху?
niico

28

IMHO, що повертає "код статусу", походить від історичних часів, перш ніж винятки стали звичним явищем для мов середнього та вищого рівня, таких як C #. Сьогодні краще викинути виняток, якщо якась несподівана помилка завадила досягти успіху вашому методу. Таким чином, ви впевнені, що помилка не залишиться непоміченою, і абонент може впоратися з нею як слід. Тож у цих випадках цілком чудово для методу повернути нічого (тобто void) замість булевого прапора статусу або цілого коду статусу. (Звичайно, слід задокументувати, які винятки метод може кинути і коли.)

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

Між ними є сіра зона, коли можна вирішити повернути деяку "зайву" інформацію з методу, якщо вона вважатиметься корисною для своїх абонентів. Як і ваш приклад про кількість постраждалих рядків у вставці. Або для способу введення елемента в асоціативну колекцію може бути корисним повернути попереднє значення, пов’язане з цим ключем, якщо воно було. Такі способи використання можна визначити, ретельно проаналізувавши (відомі та очікувані) сценарії використання API.


4
+1 за винятки щодо коду статусу. Виняток (поганий каламбур) з цього - добре використаний шаблон TryXX.
Енді Лоурі

éter Я з тобою там. TcF і не замовчуйте помилки! Напевно, правда, сіра зона. Завжди може бути корисно повернути щось. Уражені рядки, останній вставлений ідентифікатор, ПІД запуску програмного забезпечення, але все ж я думаю, що легше думати "чорт так, я повертаю це" при розробці. Потім, через рік, коли розширюється, "що? Це робить" деякийThing = RunProcess (x) "що станеться, якщо він не може працювати?"
Незалежна

+1 додаткова інформація, чому я кодую такі методи в таких мовах вищого рівня, як C #. Це полегшує використання додаткової інформації, коли вона вам потрібна.
ashes999

2
-1 Вау, ваші власні слова такі суперечливі і неправильні. Ніколи не слід використовувати винятки для контрольного потоку чи зв'язку. Вони нескінченно дорожчі, ніж умовні. Коли настає час "до того, як винятки стали звичними"? ... ти маєш на увазі, перш ніж блоки "try / catch" стали звичними? Винятки поширені назавжди. "Викиньте виняток, якщо якась несподівана помилка ..." Як ви вживаєте заходів щодо чогось несподіваного? Чому б ви зловити виняток, а потім повторно кинути його? Це суто дорого. Чому ви говорите "помилка"? Помилки та винятки - це різні речі.
Суамер

1
І хто може сказати, що викинутий виняток не залишиться непоміченим? Ніщо нікого не змушує ввійти в блок ловлі, чому б не увійти в блок "тоді"? Чому "документувати, які винятки може кинути метод і коли"? Як щодо того, якщо ми пишемо код самодокументування, а метод просто повертає перерахунок із очікуваними кінцевими станами цього методу? Якщо можливий виняток можна уникнути, перевіривши стан, їх слід спіймати і обробляти, не кидаючи.
Суамер

14

Відповідь Петра добре висвітлює винятки. Інші питання тут стосуються розділення команд-запитів

Принцип CQS говорить, що метод повинен бути або командою, або запитом, а не обома. Команди ніколи не повинні повертати значення, лише змінювати стан, а запити повинні повертати тільки, а не змінювати стан. Це робить семантику дуже чіткою та допомагає зробити код більш читабельним та доступним для обслуговування.

Кілька випадків порушення принципу CQS - це гарна ідея. Зазвичай це стосується безпеки продуктивності або потоку.


1
-1 Неправильно і неправильно. По-перше, вся суть CQS полягає у питаннях Q. Абонент повинен мати можливість отримувати обчислення чи зовнішні дзвінки через якусь стан-службу, не боячись змінити стан цієї служби. Крім того, хоча CQS є корисним принципом для вільного дотримання, він лише суворо дотримується у конкретних проектах. Нарешті, навіть у строгому CQS нічого не говорить про те, що команда не може повернути значення, вона говорить, що вона не повинна обчислювати або викликати зовнішні обчислення та повертати дані . Або C, або Q можуть вільно повернути свій кінцевий стан, якщо це корисно для абонента.
Suamere

Так, але кількість рядків, на які впливає Command, значно спрощує створення нових команд із старих. Джерело: роки та роки досвіду.
Джошуа

@Joshua: Аналогічно, "додати новий запис і повернути ідентифікатор (для деяких структур номер рядка), що був сформований під час додавання", можна використовувати для цілей, яких інакше не можна досягти ефективно.
supercat

Вам не потрібно повертати порушені рядки. Команда повинна знати, скільки вплине. Якщо це що-небудь, окрім #, воно повинно відкинутись назад та викинути виняток, оскільки щось дивне трапилось. Таким чином, це інформація, яку абонент не дуже цікавить її (якщо користувачеві не потрібно знати справжній номер #, і ви не можете сказати з даних даних). Є ще сірі ділянки, такі як ідентифікатори, генеровані БД, стеки / черги, де отримання даних змінює свій стан, але мені подобається створювати "CommandQuery" у цих сценаріях, тому ніхто не намагається зробити щось "дивне".
Даніель Лоренц

3

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


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

1

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


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

Правда. Але успадкування може бути громіздким і важким у використанні.
Wyatt Barnett

Дивлячись на невідомий код, який повертає будь-який (згаданий цілий об'єкт) приділяє багато уваги, просто перевірте потоки всередині і зовні методами ..
Незалежне

Я не думаю, що ти потрапив у Rubyякийсь момент? Саме це і познайомило мене з ланцюжком методів.
KChaloux

Одним із завдань, які я маю з ланцюжком методів, є те, що він робить менш зрозумілим, чи return Thing.Change1().Change2();очікується, що таке твердження зміниться Thingта поверне посилання на оригінал, чи очікується повернення посилання на нову річ, яка відрізняється від оригіналу, залишаючи при цьому оригінальний недоторканий.
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.