Почему API должен возвращать "void"?

При написании API или объекта многократного использования существует ли какая-либо техническая причина, почему все вызовы методов, возвращающие 'void', должны не просто возвращать 'this' (* это в С++)?

Например, используя класс строк, мы можем делать такие вещи:

string input= ...;
string.Join(input.TrimStart().TrimEnd().Split("|"), "-");

но мы не можем этого сделать:

string.Join(input.TrimStart().TrimEnd().Split("|").Reverse(), "-");

.. потому что Array.Reverse() возвращает void.

Есть много других примеров, когда API имеет много операций, возвращающих пустоты, поэтому код заканчивается следующим образом:

api.Method1();
api.Method2();
api.Method3();

.. но было бы вполне возможно написать:

api.Method1().Method2().Method3()

.. если дизайнер API допустил это.

Есть ли техническая причина для следования этому маршруту? Или это просто вещь стиля, указывающая на изменчивость/новый объект?

(x-ref Стилистический вопрос о возвращении пустоты)


Эпилог

Я принял ответ Luvieere, поскольку я думаю, что это лучшее представляет намерение/дизайн, но, похоже, есть популярные примеры API, которые отличаются от этого:

В С++ cout << setprecision(..) << number << setwidth(..) << othernumber;, похоже, изменен объект cout, чтобы изменить следующую вставленную датум.

В .NET, Stack.Pop() и Queue.Dequeue() оба возвращают элемент, но также изменяют коллекцию.

Подходит для ChrisW и других для получения подробной информации о фактических затратах на производительность.

Ответ 1

Методы, которые возвращают пустое состояние более четко, что у них есть побочные эффекты. Предполагается, что те, которые возвращают измененный результат, не имеют побочных эффектов, включая изменение исходного ввода. Создание метода return void подразумевает, что он изменяет свой вход или какое-либо другое внутреннее состояние API.

Ответ 2

Если вы получили Reverse() return a string, то пользователю API API было бы не очевидно, вернет ли он новую строку или то же самое, обратное на месте.

string my_string = "hello";
string your_string = my_string.reverse(); // is my_string reversed or not?

Вот почему, например, в Python list.sort() возвращает None; он отличает сортировку по месту от sorted(my_list).

Ответ 3

Есть ли техническая причина для следования этому маршруту?

Один из принципов разработки на С++ - "не платите за функции, которые вы не используете"; возврат this будет иметь небольшое (незначительное) ограничение производительности, для функции, которую многие люди (I, для одного) не будут склонны использовать.

Ответ 4

Технический директор, упомянутый многими другими (что void подчеркивает тот факт, что функция имеет побочный эффект), называется Command-Query Separation.

Хотя есть плюсы и минусы этого принципа, например (субъективно) более четкое намерение и более сжатый API, наиболее важная часть должна быть последовательной.

Ответ 5

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

Если я вижу функцию, которая возвращает void, я знаю, что тип возврата не важен. Независимо от того, что делает функция, она не возвращает ничего для меня для работы.

Если функция возвращает что-то не void, я должен остановиться и задаться вопросом, почему. Что это за объект, который может быть возвращен? Почему он возвращается? Могу ли я предположить, что this всегда возвращается или будет иногда иметь значение null? Или совсем другой объект? И так далее.

В стороннем API я бы предпочел, чтобы такие вопросы просто не возникали.

Если функции не нужно ничего возвращать, она ничего не должна возвращать.

Ответ 6

Если вы намереваетесь, чтобы ваш API вызывался из F #, do return void, если вы не уверены, что этот конкретный вызов метода будет соединен с другим почти каждый раз, когда он будет использоваться.

Если вы не хотите, чтобы ваш API был прост в использовании из F #, вы можете здесь остановиться.

F # является более строгим, чем С# в определенных областях - он хочет, чтобы вы были в явной форме о том, вызываете ли вы метод, чтобы получить значение или просто для его побочных эффектов. В результате вызов метода для его побочных эффектов, когда этот метод также возвращает значение, становится неудобным, поскольку возвращаемое значение должно быть явно проигнорировано, чтобы избежать ошибок компилятора. Это делает "беглые интерфейсы" несколько неудобными для использования с F #, который имеет свой собственный превосходный синтаксис для объединения серии вызовов.

Например, предположим, что у нас есть система протоколирования с методом Log, которая возвращает this, чтобы обеспечить некоторую привязку метода:

let add x y =
    Logger.Log(String.Format("Adding {0} and {1}", x, y)) // #1
    x + y                                                 // #2

В F #, поскольку строка # 1 - это вызов метода, который возвращает значение, и мы ничего не делаем с этим значением, считается, что функция add принимает два значения и возвращает этот экземпляр Logger. Однако строка # 2 не только возвращает значение, оно появляется после того, что F # считает оператором return для функции, эффективно создавая два "возвратных" оператора. Это вызовет ошибку компилятора, и нам нужно явно игнорировать возвращаемое значение метода Log, чтобы избежать этой ошибки, так что наш метод add имеет только один оператор return.

let add x y =
    Logger.Log(String.Format("Adding {0} and {1}", x, y)) |> ignore
    x + y

Как вы могли догадаться, многие вызовы "Fluent API", которые в основном касаются побочных эффектов, становятся несколько разочаровывающими в использовании большого количества операторов ignore по всему месту.

Вы можете, конечно, иметь лучшее из обоих миров, и, пожалуйста, как разработчики С#, так и F #, предоставляя как свободный API, так и модуль F # для работы с вашим кодом. Но если вы этого не сделаете, и вы намерены использовать свой API для общественного потребления, пожалуйста подумайте дважды, прежде чем возвращать this из каждого отдельного метода.

Ответ 7

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