Когда и почему вы должны использовать 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 может по-прежнему выдавать исключение, предполагая, что поддержание подключения к базе данных является обязанностью вызывающей стороны.


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 возможных возвращаемых значения.


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

1

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


1
Цепочка методов может сделать наследование громоздким и сложным. Я не буду делать цепочку методов, если у вас нет веских причин делать это, кроме как не желать давать вашим методам «пустоту».
KallDrexx

Правда. Но наследование может быть громоздким и сложным в использовании.
Уайетт Барнетт

Глядя на неизвестный код, который возвращает то, что (упомянул весь объект), уделяет большое внимание, просто проверьте поток внутри и снаружи методов.
Независимый

Я не думаю, что вы попали в Rubyкакой-то момент? Вот что познакомило меня с методом цепочки.
KChaloux

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