1. C# / Говнокод #15635

    +136

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    private Action<Action, Action<Exception>> ToCallbackAction(Action a)
            {
                return (sc, ec) =>
                {
                    try
                    {
                        a();
                        sc();
                    }
                    catch (Exception e)
                    {
                        ec(e);
                    }
                };
            }

    толково или говно? Мнения разделились

    Запостил: taburetka, 01 Апреля 2014

    Комментарии (49) RSS

    • Тут нужен Y-комбинатор!
      Ответить
    • Делает вырасывание ошибки бессмысленной операцией, которую лучше заменить на условный переход. Если бы а() что-нибудь возвращал, еще можно было бы пожать плечами и пропустить, а так - просто лишние телодвижения.
      Назначение ошибок, ну, по крайней мере изначально, было в том, чтобы получить какой-то контроль над процессом выполнения програмы, ну а тут он не нужен...
      Ответить
      • А я так понял, что он вернет делегат с действием и ошибкой которая в нем произошла.
        Сама идея интересна.
        Ответить
        • Чем жеж интересна? Ошибка дает уникальную возможность вернуться на несколько этапов по стэку вызовов, а тут эту уникальную возможность проигнорировали, превратив в обычный условный переход. Текста писать примерно столько же получается. В функциях высшего порядка такой конструкцией особенно не попользуешься, потому что ей нельзя задать вменяемый тип возврата. Вобщем, хз, я бы не смог придумать такой ситуации, где бы это было предпочтительно обычному try-catch.
          Ответить
          • В дебаггере что ли вернутся по стеку вызовов? оО
            тем более throw тут нигде нет, стек вызовов в ошибке остался.
            Я вижу тут очень интересный делегат.
            Хотя применения ему найти трудно.
            Ответить
            • Нет, смысл ошибки в том, чтобы вернуться по стеку вызовов, т.как нет возможности вернуть из функции результат нужного типа. А тут результат вообще игнорируется. Что мешало просто вернуть результат?
              Ответить
              • делегат action инкапсулирует методы который возвращают void.
                Там нет никакого результата.
                Ответить
                • Ну так это как раз и не нужно. Ошибка - это когда функция не может вернуть объект нужного типа. А возвращать пустоту и выбрасывать ошибку - купить билет и пойти пешком.
                  Ответить
                  • А что тогда такая функция должна возвращать? Бул? Код ошибки, как в сишке? Или возвращать само исключение вместо его вброса? Глупости какие-то выдумываете, ей-богу...

                    Все-таки ошибка (исключительная ситуация) это не когда функция не может вернуть объект нужного типа, а всего лишь когда она не справляется со своими обязанностями (нарушены какие-то предусловия, или вызываемая ей функция выбросила ошибку или что-то еще).
                    Ответить
                    • О, я ща кому-то на больную мозоль наступлю. А вот Бенжамин Пирс описывает ситуацию с ошибками именно как функцию возвращающую номинальный тип объединенный с типом ошибки. И в этом, если задуматься, правда.
                      Не нужно спрашивать: "а что возвращать" - такого ворпоса не должно быть изначально. А зачем нужно было функцию писать, если она ничего не возвращает? Тип ошибки - это часть типа функции, но тип "ничего" объединенный с типом "ошибка" - это маразм.
                      Просто у ошибки есть специфический побочный эффект - размотка стека ради которого, и только ради него имеет смысл пользоваться ошибками. Если отматывать стек не нужно - то нефиг и ошибку выбрасывать (другими словами, ситуацию можно разрулить на месте).
                      Ответить
                      • > как функцию возвращающую номинальный тип объединенный с типом ошибки
                        Ну да, и я с ним согласен. Но в c# это обернуто в синтаксический сахар, называемый исключениями. И переть в обход системы где-то возвращая исключения самому, а где-то юзая встроенный механизм - какой-то изврат.

                        > А зачем нужно было функцию писать, если она ничего не возвращает?
                        Ну не на хаскеле же пишем :) Ради побочных эффектов, вестимо.

                        > Тип ошибки - это часть типа функции, но тип "ничего" объединенный с типом "ошибка" - это маразм.
                        Это не маразм. Маразм - это "тип" void, не имеющий ни одного значения. Был бы он типом, принимающим единственное значение void (так называемый unit type) - вы бы до пары (void, Exception) уже бы никогда не докопались ;))

                        > Просто у ошибки есть специфический побочный эффект - размотка стека ради которого, и только ради него имеет смысл пользоваться ошибками.
                        Т.е. если я пишу функцию ради побочного эффекта, то я должен ПОЙМАТЬ ошибку и ВЕРНУТЬ ее как результат? Ну очень удобно... Это как раз таки убьет всю пользу от исключений.
                        Ответить
                        • Ну а какая реализация может быть у а()?
                          void OpenFile(string name) { ... throw new Exception(); }

                          Это ж идиотизм. Хотя, вобщем-то, да, распространенный. Т.е. вместо того, чтобы открыть файл и вернуть на него ссылку мы потом по косвенным признакам догадываемся о том, что файл все-таки открылся, а возвращаемое значение игнорим.
                          Ответить
                          • Возьмем другой пример - void print(string text). Он возвращает ничего (если бы void был unit type с единственным значением void - то тут даже формально не докопаться), если все ок, и возвращает ошибку (выбрасывает ее в терминах c#), если сфейлился. Что ему еще надо возвращать при успехе кроме самого факта успешности?

                            Так что проблема совсем не в том, что функции могут вернуть ничего.
                            Ответить
                            • Хз. мне бы вполне нормально было, если бы такие функции сообщали о своем успехе / неудаче через логические значения, или перечисления какие-нибудь. На мой взгляд это легитимная реификация (и в том же Баше, или Прологе так и есть).
                              Ответить
                              • > через логические значения
                                Ну дык здесь уже на уровне языка и рантайма есть неявное логическое значение... Это тот самый флаг-селектор в объединении возвращаемого типа и типа ошибки. Функция вернула управление с помощью return - true, вернула управление с помощью throw - false.

                                Можно подумать, что кому-то хочется обрабатывать возвращаемое значение каждого print'а по-отдельности...

                                Собственно в этом и основное удобство исключений - за счет "магии" компилятора и рантайма человеку не надо вставлять проверку успеха\неуспеха после вызова каждой функции. Флаг успеха\неудачи сделан неявным (на самом деле там более хитрые механизмы, но это неважно, поведение один хрен точно такое же, как-будто каждая функция возвращает Either<ReturnType, ExceptionType>) и неудачи автоматом возвращаются на уровень выше, если их не хотят обрабатывать в текущей функции.
                                Ответить
                                • Но return true не задействует ненужного в данном случае механизма манипуляции стеком.
                                  Ну так а где в примере обработка множества ошибок? Ее ж там нет. Более того, ее и не сделаешь особо полезной, т.как полиморфизьм в try-catch не особо работает.
                                  Ответить
                                  • > Ну так а где в примере обработка множества ошибок?
                                    В котором примере?

                                    > return true не задействует ненужного в данном случае механизма манипуляции стеком
                                    Зато он добавляет кучу бесполезных проверок в success path. А это - преждевременная пессимизация ;)

                                    В том же g++ исключения вообще не добавляют стоимости к успешной ветке. Там за них платят только при возникшей ошибке, что, имхо, вполне логично.

                                    > полиморфизьм в try-catch не особо работает
                                    А в return true он вообще не работает. Вон я же уже постил ГК о жабьем bool mkdir(), в котором вообще не отличить "каталог уже есть" и "нет прав". Если надо отличить две ошибки - придется изобретать коды возврата. Но зачем, если в язык уже встроен более-менее адекватный механизм возврата ошибок?
                                    Ответить
                                    • > В каком примере?
                                      В том, который мы сейчас обсуждаем... который запостил автор этого говнокода.

                                      И вообще, код в примере не решает эти задачи, он просто вместо if-else использует try-catch, но не пользуется ни одной из возможностей, которые try-catch предоставляет. Что там и как конкретно сгенерирует компилятор, да еще и в другом языке... по-моему тут на столько не важно, т.как мы уже получили оверхед на создание объекта инкапсулирующего операцию + замыкание в этом объекте. Накладные расходы на это все сто раз перекроют (скорее всего остуствующую) оптимизацию глобального отлова ошибок вместо ифа.
                                      Ответить
                                      • > В том, который мы сейчас обсуждаем... который запостил автор этого говнокода.

                                        Код в примере решает задачу о том, как из существующего (подчеркну это слово) action'а сделать функцию, пригодную для использования посреди асинхронной лапши аля continuation passing style.

                                        Скорее всего рядом с описанной в топике функцией есть еще и другая, которая умеет передавать возвращаемое значение:
                                        try {
                                            R res = a();
                                            sc(res);
                                        } catch (Exception e) {
                                            ec(e);
                                        }
                                        А вот нужен ли в этом проекте CPS - это уже совсем другой вопрос для совсем другого треда. Да и без доступа к самому проекту на этот вопрос, наверное, и не ответить...
                                        Ответить
                                      • > он просто вместо if-else использует try-catch, но не пользуется ни одной из возможностей, которые try-catch предоставляет
                                        А кто сказал, что сам a() ими не пользуется? Может быть a() как раз и использует неявный форвардинг ошибок из одной из вызываемых им функций? Или в нем 100500 вызовов функций, которые возвращают результат (или бросают исключение, которое сам a() обработать не может)? Мы же код этого a() не видим. Да и не увидим, т.к. там может быть абсолютно что угодно, лишь бы оно имело подходящую сигнатуру.

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

                                        Может быть автор вообще какую-то стандартную функцию из библиотеки c# хочет туда передать в качестве экшена...
                                        Ответить
                                        • Ну если мы будем придумывать а что там могло быть рядом, так у автора скорее возникнут проблемы с генериками / полиморфизмом в его замечатэльном классе, т.как в catch генерики особо не засунеш. Ошибок может быть много разных, но вот макросов в С# нет написать нужное количество кетчей на разные типы ошибок (а с возвращаемым значением можно было бы разрулить).
                                          Вобщем, если как попытка поставить заплатку на уже существующий дурацкий код, то хз. Как инструмент для постройки идеального общества - не подходит.
                                          Ответить
                                          • > Вобщем, если как попытка поставить заплатку на уже существующий дурацкий код, то хз.
                                            Ну имхо это именно костыль для вписывания обычных функций в CPS лапшу... К которой у меня крайне негативное отношение (кроме случаев, когда этот CPS является результатом какого-то автоматического преобразования, который не надо писать/читать человеку).

                                            > Как инструмент для постройки идеального общества - не подходит.
                                            А это никто и не предлагал ;)
                                            Ответить
                                  • А return true удобно только в тех местах, где return false - не исключительная ситуация.

                                    Вот например file.exists():
                                    true - да, такой файл есть
                                    false - нет, такого файла нету
                                    исключения - нет прав на чтение, сбой ФС и т.п. есть ли файл - мы не знаем
                                    Ответить
                                    • Зачем вообще нужны эти дурацкие fileexists и тп? Атомарности ведь нет. Я после нашего с вами минувшего разговора всерьез задумался над этим. Правильнее ведь в try..except, с проверкой кодов возврата, не?
                                      Ответить
                                      • > Зачем вообще нужны эти дурацкие fileexists и тп?
                                        Хе ;)

                                        Ну например юзеру показать что-нибудь перед началом операции.

                                        А непосредственно перед открытием файла их лепить глупо, согласен.
                                        Ответить
                                        • Ну а вообще? Ведь между проверкой и выполнением какого-то действия файл может быть удален. Иное дело, проверять, чем является файл - файлом, или директорией.
                                          Ответить
                                          • > Ведь между проверкой и выполнением какого-то действия файл может быть удален.
                                            Может. Но ситуации разные бывают.

                                            Вот например какая-то операция занимает часа три. И где-то в конце этой операции надо прочитать некий файл. Показать ошибку через 3 часа будет вполне корректно, но не особо приятно для юзера.

                                            Поэтому можно перед стартом операции убедиться в том, что все файлы на месте, и если чего-то не хватает - сказать юзеру об этом сразу. Если он что-то удалит потом - ну ССЗБ, получит ошибку через 3 часа ;)
                                            Ответить
                      • Ну т.е. в c# (и многих подобных языках) тип возвращаемого значения пишется явно, а тип ошибки подразумевается и захардкожен разработчиками компилятора (Exception, или что там в шарпе, не важно). Для возврата успешного значения используется конструкция return значение, а для возврата ошибки - throw ошибка. При этом механизмы, встроенные в рантайм умеют форвардить ошибку выше, до тех пор пока ее не поймают специальной конструкцией catch. Если не вдаваться в подробности реализации этого catch'а, а смотреть с точки зрения модели Пирса - компилятор к каждому вызову функции добавляет код, проверяющий наличие ошибки. Этот код проверяет, будем ли мы ее обрабатывать, и если нет - просто возвращает ее из текущей функции. И все эти throw/catch это всего лишь синтаксический сахар, позволяющий работать со второй половинкой результата, а return и то, что шарписты считают возвращаемым значением функции - сахар, позволяющий работать с первой половинкой результата.

                        Т.е. правильный способ возврата ошибки в таком языке - это именно throw. Если я напишу Exception в возвращаемом типе и верну ошибку через return - я получу полное говно - функцию, которая возвращает... тип ошибки объединенный с типом ошибки.
                        Ответить
                  • Если функция вернет исключение, а не выбросит его - это исключение придется проверять. А об этом во-первых можно забыть и молча просрать ошибку (ССЗБ конечно, сишники как-то живут с этим и не забывают). А во-вторых это усложнит обработчики исключений - где-то придется писать if'ы, где-то try/catch.

                    А если ошибку всегда выбрасывать, даже в функциях, возвращающих void которые юзаются только ради их побочного эффекта - получаем какое-никакое единообразие.
                    Ответить
        • > Сама идея интересна.
          Блин, в шарпике же есть замечательный механизм под названием async/await, которым все время любят хвастаться шарпеи... Так какого хрена выдумывать асинхронную лапшу с коллбеками аля нода жс, если есть возможно поюзать кошерный псевдосинхронный стиль?
          Ответить
          • Не в шарпике а в .net 4.5. (в 4ом вроде нет такого)
            И спорить особо об этой функции смысла нет.
            Тут я думаю попытка супер-пупер повторного использования кода.
            Ответить
            • > в 4ом вроде нет такого
              Если бы не попытка M$ побыстрее закопать XP за счет неработоспособности на ней нового софта - все бы уже юзали фреймворк 4.5. Какой смысл оставаться на старых?
              Ответить
              • сам подумай.
                Ответить
                • Любовь M$ к забиванию на обратную совместимость фреймворков? Не хватает денег на новую анальную игрушку визуалку? Других причин пока не вижу.
                  Ответить
          • Ога. Только вот большенство прог пишутся под .NET 3.5 , так что гуй тебе, а не асинки авейтные и TPL

            Некрофил не смог молчать
            Ответить
            • А с какой они версии? 4.5? А на свинье его никак? А под моно?
              Ответить
              • Я подвиндовый парень. Ебал я в жопу ваше моно
                Ответить
                • Но моно есть и на винде. Теоретически. А Балмер ебал вас, подвиндовых.
                  Ответить
                  • Лол что? Зачем моно на винде? Ты сошел с ума?

                    А вас ебал.... Мммм.... Стивен Хокинг!
                    Ответить
                    • > Зачем моно на винде?
                      Чтобы писать кроссплатформенные проги с GTK#. Ну и чтобы не платить за вижуалку.

                      Ваш кэп.
                      Ответить
                      • Наху... зачем? Кто будет писать на винде проги под линукса? Красноглазых на винду не заманиишь.

                        Хотите везде исполняемый код - пишите WCF и тонкий клиент под платформу.
                        Ответить
                      • >Ну и чтобы не платить за вижуалку.
                        А на сисярпе вне вижуалки хотя бы теоретически можно платить?
                        Ответить
                      • Компилятор и платформа бесплатные - хочешь - пили свое IDE, хочешь - юзай SharpDev - бесплатное говно.
                        Ответить
                        • >бесплатное говно.
                          Точно

                          > теоретически можно писать
                          Поправил
                          Ответить
                    • Хокинг ебать может только мысленно, а вот Балмер вам четко разъяснил, что ништяки - только под свежую винду. А питон вроде как только недавно 2к дропнул.
                      Ответить
                      • Вот и еби своего Балмера.

                        Балмер! Балмер! Балмер!

                        Причем тут змий?
                        Ответить
                • Давай в жопу ебаться.
                  Ответить

    Добавить комментарий