Чи погана практика використовувати повернення всередині методу void?


92

Уявіть такий код:

void DoThis()
{
    if (!isValid) return;

    DoThat();
}

void DoThat() {
    Console.WriteLine("DoThat()");
}

Чи нормально використовувати повернення всередині методу void? Чи передбачено якесь покарання за продуктивність? Або було б краще написати такий код:

void DoThis()
{
    if (isValid)
    {
        DoThat();
    }
}
c#  return  void 

1
А як щодо: void DoThis () {if (isValid) DoThat (); }
Dscoduc

30
уявіть собі код? Чому? Це саме там! :-D
STW

Це гарне запитання, я завжди вважаю, що це хороша практика - використовувати return; для виходу з методу або функції. Особливо в методі видобутку даних LINQ, що має кілька результатів IQueryable <T>, і всі вони залежать один від одного. Якщо один з них не має результату, попереджайте і виходьте.
Cheung

Відповіді:


173

Повернення методу void непогано, це звичайна практика інвертування ifоператорів для зменшення вкладеності .

А менша кількість вкладеності у ваші методи покращує читабельність та ремонтопридатність коду.

Насправді, якщо у вас є метод void без будь-якого оператора return, компілятор завжди генерує інструкцію ret в кінці його.


33

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

Розглянемо:

void MyFunc(object obj)
{
    if (obj != null)
    {
        obj.DoSomething();
    }
}

проти:

void MyFunc(object obj)
{
    if (obj == null)
        return;

    obj.DoSomething();
}

А тепер, уявіть, інший програміст додає рядок: obj.DoSomethingElse ();

void MyFunc(object obj)
{
    if (obj != null)
    {
        obj.DoSomething();
    }

    obj.DoSomethingElse();
}

void MyFunc(object obj)
{
    if (obj == null)
        return;

    obj.DoSomething();
    obj.DoSomethingElse();
}

Очевидно, що це спрощений випадок, але програміст додав до програми збій у першому (вкладеному коді) екземплярі. У другому прикладі (ранній вихід із захисниками), коли ви пройдете повз сторожу, ваш код захищений від ненавмисного використання нульового посилання.

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


Так, але з іншого боку, кілька шарів вкладеності, за їх умов, роблять код ще більш схильним до помилок, логіку важче відстежувати і - що більш важливо - важче налагоджувати. Плоскі функції - це менше зло, ІМО.
Скрім

18
Я сперечаюся на користь скороченого гніздування! :-)
Джейсон Вільямс

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

18

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

Також перевірте відповіді на подібне запитання: Чи слід використовувати оператор return / continue замість if-else?


8

Це непогана практика (з усіх зазначених причин). Однак, чим більше повернень методу, тим більша ймовірність його поділу на менші логічні методи.


8

Перший приклад - використання інструкції гвардії. З Вікіпедії :

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

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

Тож загалом хотілося б такого:

void DoThis()
{
  if (guard1) return;
  if (guard2) return;
  ...
  if (guardN) return;

  DoThat();
}

Я думаю, що це набагато читабельніше:

void DoThis()
{
  if (guard1 && guard2 && guard3)
  {
    DoThat();
  }
}

3

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


Рассел, я не погоджуюся з вашою думкою, але ви не повинні були проголосувати за неї. +1, щоб вирівняти. До речі, я вважаю, що логічний тест і повернення в одному рядку, за яким слідує порожній рядок, є чітким свідченням того, що відбувається. наприклад, перший приклад Родріго.
Paul Sasik

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

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

2

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


0

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

Завжди

if( foo ){
    return;
}

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


2
читабельний - це суб’єктивно. imho, все, що додається до непотрібного коду, робить його менш читабельним ... (мені доводиться більше читати, а потім мені цікаво, чому це там, і витрачаю час, намагаючись переконатися, що я чогось не пропускаю) ... але це мій суб'єктивна думка
Чарльз Бретана

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

2
Шовковистий, будь ласка, натисніть Enter перед вашим {. Це співпадає {з вашими }в одному стовпці, що значно покращує читабельність (набагато простіше знайти відповідні фігурні дужки).
Імажист,

1
@Imagist Я залишу це на власні переваги; і це зроблено так, як я віддаю перевагу :)
Полудень Шовковий

1
Якщо кожна фігурна дужка поєднується з відкритою фігурною дужкою, яка розміщена на одному рівні відступу , тоді візуально розрізнити, які ifоператори потребують фігурних фігурних дужок, буде легко, а отже, ifконтроль операторів одного висловлення буде безпечним. Відсунення відкритої фігурної дужки назад до лінії із ifзбереженням лінії вертикального простору на кожному мульти-операторі if, але вимагатиме використання непотрібної в іншому випадку лінії фігурної дужки.
supercat

0

Я збираюся не погодитися з усіма вашими, молодими шпигунами, щодо цього.

Використання повернення в середині методу, порожнього чи іншого, є дуже поганою практикою з причин, які були досить чітко сформульовані майже сорок років тому покійним Едджером В. Дейкстра, починаючи з добре відомого "Заяви ГОТО, що вважається шкідливою" ", і продовження у" Структурованому програмуванні "Даля, Дейкстри та Хоара.

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

"Заява GOTO вважається шкідливою" і "Структуроване програмування" започаткували революцію "Структурне програмування" 1970-х років. Ці дві частини є причинами, які ми маємо сьогодні - тоді - інакше, в той час як - робити, та іншими явними конструкціями управління сьогодні, і чому оператори GOTO мовами високого рівня є у списку зникаючих видів. (Моя особиста думка полягає в тому, що вони повинні бути у списку вимерлих видів.)

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

Варто також згадати, що Ніклаус Вірт змінив семантику висловлювання RETURN в Oberon-07, останній версії мови програмування Oberon, зробивши це кінцевим фрагментом декларації типової процедури (тобто функції), а не виконуваний оператор у тілі функції. Його пояснення змін зазначило, що він зробив це саме тому, що попередня форма БУЛА порушенням принципу структурованого програмування з одним виходом.


2
@ Джон: ми перебрали розпорядження Dykstra про багаторазові повернення приблизно в той час, коли отримали Паскаля (більшість із нас, так чи інакше).
Джон Сондерс,

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

@nairdaen: У цьому кварталі досі існують суперечки щодо винятків. Моя настанова така: якщо система, що розробляється, ПОВИННА усунути проблему, яка спричинила вихідний винятковий стан, і я не проти роздратувати хлопця, якому доведеться писати цей код, я викину виняток. Тоді мене кричать на зустрічі, бо хлопець не потрудився вловити виняток, і додаток зірвався під час тестування, і я пояснюю, ЧОМУ він повинен усунути проблему, і все знову влаштовується.
John R. Strohm,

Існує велика різниця між заявами охоронців та gotos. Зло гото в тому, що вони можуть стрибати куди завгодно, тому може бути дуже заплутано розгадати і запам’ятати. Заяви Guard - це прямо протилежне - вони забезпечують вхід до методу, після чого ви знаєте, що працюєте в "безпечному" середовищі, зменшуючи кількість речей, які ви повинні враховувати під час написання решти коду (наприклад, "Я знаю, що цей вказівник ніколи не буде нульовим, тому мені не потрібно обробляти цю справу в коді").
Джейсон Вільямс,

@ Джейсон: Початкове питання стосувалося не конкретно заяв охорони, а випадкових випадків повернення в середині методу. У наведеному прикладі видно, що це охорона. Ключова проблема полягає в тому, що на сайті повернення ви хочете мати можливість міркувати про те, що метод робив чи не робив, а випадкові повернення ускладнюють це, ТОЧНО з тих самих причин, що випадкові GOTO ускладнюють це. Див .: Дейкстра, "Заява GOTO вважається шкідливою". Що стосується синтаксису, cdmckay дав, в іншій відповіді, свій улюблений синтаксис для охоронців; Я не згоден з його думкою про те, яка форма є більш читабельною.
John R. Strohm

0

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

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

Приклад

// guards point you to the core intent
void Remove(RayCastResult rayHit){

  if(rayHit== RayCastResult.Empty)
    return
    ;
  rayHit.Collider.Parent.Remove();
}

// no guards needed: function split into multiple cases
int WonOrLostMoney(int flaw)=>
  flaw==0 ? 100 :
  flaw<10 ? 30 :
  flaw<20 ? 0 :
  -20
;

-3

Викиньте виняток замість того, щоб нічого не повертати, коли об’єкт нульовий тощо.

Ваш метод очікує, що об'єкт не має значення null, і це не так, тому вам слід викинути виняток і дозволити абоненту обробляти це.

Але дострокове повернення - це не погана практика, інакше.


1
Відповідь не відповідає на питання. Питання - метод порожнечі, тому нічого не повертається. Крім того, методи не мають параметрів. Я розумію, що не повертати значення null, якщо тип повернення є об’єктом, але це не стосується цього питання.
Люк Хаммер,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.