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

30

Иногда я сталкиваюсь с методами, в которых разработчик решил вернуть что-то, что не критично для функции. Я имею в виду, что, глядя на код, он, очевидно, работает так же хорошо, как voidи, после некоторого момента я спрашиваю: «Почему?» Это звучит знакомо?

Иногда я соглашусь с тем, что чаще всего лучше вернуть что-то вроде boolили int, а не просто сделать void. Я не уверен, хотя, в целом, о плюсах и минусах.

В зависимости от ситуации, возвращение intможет сообщить вызывающей стороне количество строк или объектов, на которые воздействует метод (например, 5 записей, сохраненных в MSSQL). Если такой метод, как «InsertSomething», возвращает логическое значение, я могу иметь метод, предназначенный для возврата в trueслучае успеха, иначе false. Абонент может выбрать, действовать или нет на эту информацию.

С другой стороны,

  • Может ли это привести к менее ясной цели вызова метода? Плохое кодирование часто заставляет меня перепроверить содержание метода. Если он что-то возвращает, он говорит вам, что метод относится к типу, который вы должны сделать с возвращенным результатом.
  • Другая проблема будет, если реализация метода вам неизвестна, что разработчик решил вернуть, который не является критически важным для функции? Конечно, вы можете это прокомментировать.
  • Возвращаемое значение должно быть обработано, когда обработка может быть закончена на закрывающей скобке метода.
  • Что происходит под капотом? Получил ли вызванный метод falseиз-за сгенерированной ошибки? Или он вернул false из-за оцененного результата?

Каков ваш опыт с этим? Как бы вы поступили с этим?

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

Ответы:

32

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

Например, void InsertRow()метод может вызвать исключение, если уже существует строка с таким же ключом. Вопрос в том, разумно ли предположить, что вызывающая сторона гарантирует, что их строка уникальна перед вызовом InsertRow? Если ответ отрицательный, то я бы также предоставил, bool TryInsertRow()который возвращает false, если строка уже существует. В других случаях, таких как ошибки подключения к базе данных, TryInsertRow может по-прежнему выдавать исключение, предполагая, что поддержание подключения к базе данных является обязанностью вызывающей стороны.

Bubblewrap
источник
4
+1 за то, что я сделал различие между ожидаемыми и неожиданными сбоями - мой собственный ответ недостаточно подчеркивал это.
Петер Тёрёк
Абсолютно +1 за попытку изобрести принцип для методов TryXX, который, по-видимому, упрощает поддержку как метода try, так и метода main, а также облегчает понимание их цели.
Независимо
+1 Хорошо сказано. Ключ к лучшему ответу на этот вопрос заключается в том, что иногда вы хотите дать вызывающей стороне возможность утвердительного метода, который вызовет исключение. В этих случаях, однако, исключение не должно быть перехвачено и обработано ложным методом. Дело в том, что звонящий становится ответственным. С другой стороны, парадигма Try (или Monad) должна перехватывать и обрабатывать исключения, а затем либо передавать обратно bool, либо описательный Enum, если требуется больше подробностей. Обратите внимание, что вызывающий ожидает, что Try / Monad НИКОГДА не сгенерирует исключение. Это как точка входа.
Суамер
Таким образом, поскольку ожидается, что исключение никогда не произойдет из Try / Monad, но bool недостаточно описателен, чтобы сообщить вызывающей стороне, что соединение db не удалось, или запрос http имеет одно из многих возвращаемых значений, которые необходимы для действия по-другому, вы можете использовать Enum вместо bool для своего Try / Monad.
Суамер
Если у вас есть TryInsertRow - возвращающий bool - если, скажем, в базе данных имеется более одного уникального ограничения - как вы точно знаете, в чем заключалась ошибка, - и сообщите об этом пользователю? Следует ли использовать более богатый тип возврата, содержащий сообщение об ошибке / код и успех bool?
niico
28

ИМХО возвращение «кода состояния» происходит с исторических времен, до того, как исключения стали обычным явлением в языках среднего и более высокого уровня, таких как C #. В настоящее время лучше создать исключение, если какая-то непредвиденная ошибка помешала успешному выполнению вашего метода. Таким образом, эта ошибка не останется незамеченной, и вызывающая сторона может справиться с ней соответствующим образом. Таким образом, в этих случаях вполне нормально, чтобы метод ничего не возвращал (т.е. void) вместо логического флага состояния или целочисленного кода состояния. (Конечно, следует документировать, какие исключения метод может генерировать и когда.)

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

Между ними существует серая область, когда кто-то может решить вернуть некоторую «дополнительную» информацию из метода, если это считается полезным для его вызывающих. Как и ваш пример с количеством затронутых строк во вставке. Или для метода, чтобы поместить элемент в ассоциативную коллекцию, может быть полезно вернуть предыдущее значение, связанное с этим ключом, если оно было. Такое использование может быть идентифицировано путем тщательного анализа (известных и ожидаемых) сценариев использования API.

Петер Тёрёк
источник
4
+1 для исключений по коду статуса. Исключением (плохой каламбур) является хорошо используемый паттерн TryXX.
Энди Лоури
я там с тобой. Тсф и не заглушай ошибок! Вероятно, тем не менее, серая зона. Там всегда может быть полезно вернуть что-то. Затронутые строки, последний вставленный идентификатор, PID запуска программного обеспечения, но все же я думаю, что легче думать «черт возьми, я возвращаю это» при разработке. Затем, год спустя, при расширении, «что? Что это значит« someThing = RunProcess (x) », что произойдет, если он не сможет работать?»
Независимо
+1 дополнительная информация - вот почему я кодирую такие методы на языках высокого уровня, таких как C #. Это позволяет легко использовать дополнительную информацию, когда вам это нужно.
ashes999
2
-1 Вау, ваши собственные слова настолько противоречивы и неверны. Вы никогда не должны использовать исключения для потока управления или связи. Они бесконечно дороже, чем условные. Когда наступает время «до того, как исключения стали обычным явлением»? ... Вы имеете в виду, что блоки try / catch стали обычным явлением? Исключения были распространены с незапамятных времен. «Брось исключение, если какая-то неожиданная ошибка ...» Как вы действуете на что-то неожиданное? Почему вы поймали исключение, а затем перебросили его? Это так дорого. Почему вы говорите «ошибка»? Ошибки и исключения это разные вещи.
Суамер
1
И кто скажет, что брошенное исключение не останется незамеченным? Ничто не заставляет кого-либо входить в блок catch, почему бы не войти в блок «then»? Почему «документ, какие исключения метод может генерировать и когда»? Как насчет того, чтобы написать самодокументированный код, а метод просто возвращает перечисление с ожидаемыми конечными состояниями этого метода? Если возможное исключение можно избежать путем проверки состояния, они должны быть перехвачены и обработаны без выброса.
Суамере
14

Ответ Петра охватывает исключения хорошо. Другое соображение здесь включают Command-Query Separation

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

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

Энди Лоури
источник
1
-1 неправильно и неправильно. Во-первых, весь смысл CQS заключается в Q. Вызывающий должен иметь возможность получать вычисления или внешние вызовы через какую-либо службу с отслеживанием состояния, не опасаясь изменения состояния этой службы. Кроме того, хотя CQS является полезным принципом для свободного следования, он строго соблюдается только в определенных проектах. И наконец, даже в строгом CQS ничего не говорит о том, что команда не может вернуть значение, она говорит, что она не должна вычислять или вызывать внешние вычисления и возвращать данные . Либо C, либо Q могут свободно возвращать свое конечное состояние, если это полезно для вызывающей стороны.
Суамер
Да, но количество строк, затронутых Командой, значительно упрощает создание новых команд из старых. Источник: годы и годы опыта.
Джошуа
@Joshua: Аналогично, «добавить новую запись и вернуть идентификатор (для некоторых структур, номер строки), который был сгенерирован во время добавления», можно использовать в целях, которые иначе не могут быть достигнуты эффективно.
суперкат
Вам не нужно возвращать затронутые строки. Команда должна знать, сколько будет затронуто. Если это что-то кроме #, то оно должно откатиться и выдать исключение, поскольку что-то странное только что произошло. Таким образом, это информация, которую вызывающий абонент на самом деле не волнует (если только пользователю не нужно знать истинное число #, и вы не можете узнать из приведенных данных). Есть еще серые области, такие как идентификаторы, сгенерированные БД, стеки / очереди, где получение данных меняет их состояние, но мне нравится создавать «CommandQuery» в этих сценариях, поэтому никто не пытается сделать что-то «странное».
Даниэль Лоренц
3

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

refro
источник
+1 Это не отвечает на вопрос, но это определенно правильная информация. При принятии решения решение должно основываться на необходимости. Если вызывающие не находят возвращаемые данные полезными, это не должно быть сделано. И никто не находит смысла возвращать 100% времени для «проверки будущего». Конечно, если "будущее" похоже ... через пару дней, и для него существует задача, это другая история, смеется.
Суамер
1

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

Уайетт Барнетт
источник
1
Цепочка методов может сделать наследование громоздким и сложным. Я не буду делать цепочку методов, если у вас нет веских причин делать это, кроме как не желать давать вашим методам «пустоту».
KallDrexx
Правда. Но наследование может быть громоздким и сложным в использовании.
Уайетт Барнетт
Глядя на неизвестный код, который возвращает то, что (упомянул весь объект), уделяет большое внимание, просто проверьте поток внутри и снаружи методов.
Независимый
Я не думаю, что вы попали в Rubyкакой-то момент? Вот что познакомило меня с методом цепочки.
KChaloux
Одно из препятствий, которое у меня возникает с цепочкой методов, состоит в том, что она делает менее ясным, return Thing.Change1().Change2();ожидает ли оператор типа изменить Thingи вернуть ссылку на оригинал, или же он должен будет возвращать ссылку на новую вещь, которая отличается от оригинала, оставляя при этом оригинальный нетронутый.
суперкат