Сегодня я написал свой первый итератор на C#. Woohoo.
Интересно, что у него есть побочные эффекты. Мой итератор отфильтровывает недопустимые файлы из каталога и возвращает последовательность действительных файлов для обработки. Когда он встречает инкрустированный файл, он перемещает его в другой каталог.
Я пробовал реализовать его как запрос LINQ, но мне действительно не нравится тот факт, что предикат для предложения where имеет побочные эффекты. Определенный запах.
Я мог бы реализовать это явно, перебирая все файлы и обрабатывая по очереди хорошее или плохое, но это не очень элегантно. Лучшее решение - разбить его на два списка (хороший и плохой) и обработать каждый по очереди.
Но потом вспомнил итераторы. И теперь у меня есть итератор, который возвращает действительные файлы и обрабатывает (перемещает) недопустимые.
Итак, мой вопрос таков: плохая идея, чтобы итератор имел такие побочные эффекты, как этот? Не скрываю ли я слишком много функциональности в итераторе?





Я бы сказал, что в целом - плохая идея иметь побочные эффекты в итераторе, но это не полное «нет-нет». Если у вас есть побочные эффекты, вызывающим абонентам будет сложно / невозможно работать чисто функционально. Это может быть или не быть проблемой в зависимости от вашего варианта использования.
Я предлагаю вам иметь два способа получить итератор - один с побочным эффектом (который, по-видимому, в основном является оптимизацией), а другой - нет (медленнее, но проще рассуждать). Это может быть просто передача флага методу или два метода с разными именами.
Итераторы, которые логически являются перечислениями коллекций, не должны иметь побочных эффектов, нет. В частности, они не будут идемпотентными при перезапуске с помощью метода IEnumerator.Reset ().
Однако тот факт, что итераторы фактически являются своего рода сопрограммами, они могут быть полезны для реализации некоторых вещей, которые неудобно реализовать другими способами, например шаги в асинхронном рабочем процессе.
Обратите внимание: IEnumerator.Reset () очень редко реализуется, а никогда реализуется в блоке итератора.
Конечно, но тот факт, что он является частью API, указывает на то, что это логическое ожидание, что перечисление стабильно, а перезапуск идемпотентен.
Тот факт, что это часть API, был в основном ошибкой :) Но да, я думаю, разумно ожидать, что по крайней мере итераторы самый будут идемпотентными и повторяемыми. Конечно, так будет не всегда - например, получение результатов запроса LINQ из веб-службы может каждый раз давать разные результаты.
(Продолжение) Но можно надеяться, что причина для получения разных результатов будет «естественной», а не «потому что вы звонили мне в прошлый раз».
Итераторы с побочными эффектами - ПЛОХО, мкай? :)
Если у вас есть последовательность, содержащая все файлы, у вас может быть что-то в стиле посетитель, которое посещает все элементы и вызывает функцию для каждого случая. Дискриминация посетителя может быть либо предикатом, который вы можете указать, либо внутренне присущим посетителю.
Итак, я не говорю на C#, но что-то вроде этого псевдокода:
good_handler = new FileHandler() {
handle(File f) { print "Yay!"; }
}
bad_handler = new FileHandler() {
handle(File f) { print "Nay!"; }
}
files = YourFileSequence();
visitor = new Visitor(good_handler, bad_handler);
visitor.visit(files);
Мое практическое правило: если я повторяю коллекцию, нет. Но в Python цикл for часто идиоматически используется для выполнения кода определенное количество раз, и в этом случае у меня нет проблем с его использованием с побочными эффектами.
Спасибо, ребята - и черт возьми! быстрые ответы!
Я должен согласиться, что побочные эффекты в итераторе - плохая идея. Тот факт, что мне пришлось спросить, указывает на запах. Надо было прислушаться к своему паучьему чутью.
Я думаю, что основная причина, по которой я спросил, заключалась в том, что мой побочный эффект был достаточно изолирован от основной задачи и так аккуратно инкапсулирован внутри итератора. Однако это по-прежнему скрытый функционал, что не очень приятно.
Кроме того, я думаю, что я объединил идею посетителя с итератором, что тоже не очень хорошая идея.
С тех пор я изменил свою реализацию, чтобы создать две последовательности из моей исходной последовательности всех файлов - одну хорошую, одну плохую. Теперь я могу обрабатывать их более очевидным и интуитивным образом. Ура.
Итак, я до сих пор не использовал итератор в реальном мире. Ну что ж.
Спасибо! Мэтт
Я бы сказал, что побочные эффекты - это плохая идея, но они не вредны. Если у вас есть побочные эффекты, вы, по сути, выполняете две операции. Лучше разделить эти операции на две функции, чтобы код было легче поддерживать, и вы могли выполнять их по отдельности.
В этом случае вы перемещаете плохие файлы из папки и что-то еще в хорошие файлы. Разделение этих операций позволяет перемещать плохие файлы, не выбирая хорошие, или позволяет работать с хорошими файлами (например, подсчитывать их), не перемещая плохие. Ваш код также будет более разделен на части, поэтому при необходимости будет проще оптимизировать одну из этих операций.
Я думаю, что на самом деле есть более серьезная проблема, чем тот факт, что ваш итератор имеет скрытые побочные эффекты. То есть: вы меняете членство в коллекции, которую он перебирает. Даже если бы у побочных эффектов не было неприятного запаха кода, вам следует быть осторожным. Есть способы реализовать это, которые могут показаться разумными (например, кэшировать список файлов и повторно использовать его при сбросе итератора), которые ломаются, если вы удаляете что-то из коллекции.
Я не буду комментировать общий случай, но в вашем случае я думаю, что это опасный. Хорошим показателем качества интерфейса является то, насколько легко использовать его правильно и насколько сложно использовать его неправильно.
При применении этой метрики ваш дизайн получает довольно низкие баллы, потому что невероятно легко использовать неправильно: просто повторите его дважды.
Я бы пошел дальше, чем Джон, и сказал бы: даже не предлагайте вариант. Это может быть полезно, но цена возможного неправильного использования может быть слишком высокой. С другой стороны, можно утверждать, что, если пользователь намеренно делает выбор, он должен иметь дело с последствиями.
Еще одна проблема заключается в том, что метод может быть «неправильно использован» в том смысле, что вызывающий может попытаться использовать его для перемещения файлов, не будучи действительно заинтересованным в возвращаемых результатах.
Если вызывающая программа никогда не перебирает результаты, то (предполагаемый) побочный эффект не вызывается из-за ленивого выполнения итераторов. Могут быть даже сценарии, в которых пользователь выполняет итерацию только по части коллекции, поэтому побочный эффект выполняется для некоторых элементов, но не для всех.
Вопрос обсуждается в этом посте: http://codequota.com/archive/2012/02/13/iterator-blocks-and-side-effects.aspx
@sharptooth - вы собираетесь получить значок таксономии или что?