Есть ли причина использовать goto в современном .NET-коде? - PullRequest
31 голосов
/ 30 марта 2010

Я только что нашел этот код в отражателе в базовых библиотеках .NET ...

    if (this._PasswordStrengthRegularExpression != null)
    {
        this._PasswordStrengthRegularExpression = this._PasswordStrengthRegularExpression.Trim();
        if (this._PasswordStrengthRegularExpression.Length == 0)
        {
            goto Label_016C;
        }
        try
        {
            new Regex(this._PasswordStrengthRegularExpression);
            goto Label_016C;
        }
        catch (ArgumentException exception)
        {
            throw new ProviderException(exception.Message, exception);
        }
    }
    this._PasswordStrengthRegularExpression = string.Empty;
Label_016C:
    ... //Other stuff

Я слышал все о том, что "ты не должен использовать goto из-за страха изгнания в ад на вечность". Я всегда относился к кодировщикам MS с большим уважением, и хотя я, возможно, не соглашался со всеми их решениями, я всегда уважал их рассуждения.

Итак, есть ли веская причина для такого кода, который я пропускаю? Был ли этот фрагмент кода собран неумелым разработчиком? или .NET отражатель возвращает неточный код?

Я надеюсь, что - это хорошая причина, и я просто слепо скучаю по ней.

Спасибо за всеобщее мнение

Ответы [ 19 ]

2 голосов
/ 30 марта 2010

В дополнение ко всем этим хорошим действительным вещам, когда вы смотрите на разобранный код, имейте в виду, что разработчики МОГУТ использовать обфускатор на этих сборках. Один из методов запутывания - добавление случайных переходов к IL

.
2 голосов
/ 30 марта 2010

Посмотрите на диаграмму состояний. Если вы считаете, что лучше всего использовать структуру кода, которая наиболее точно и четко выражает ваше намерение, то каждый из этих переходов состояний должен быть закодирован как переход.

Это имеет тенденцию ломаться в реальном мире, все же. Первая проблема заключается в том, что нам часто приходится останавливать машину, выходить из другого кода и позже возобновлять работу машины. Это означает, что каждый из этих переходов, как правило, является изменением переменной состояния, используемой для определения правильного состояния в коммутаторе. / заявление о ситуации. Это действительно просто способ скрыть и задержать переход - запись в переменную состояния на самом деле мало чем отличается от записи в регистр счетчика программ. Это просто способ реализовать «Перейти туда - но не сейчас, а позже».

Однако есть случаи, когда goto хорошо работает для выражения того, что происходит в какой-то модели состояния - я бы предположил, что примером будет одна из тех диагностических блок-схем, которые иногда используют врачи. Если вы реализуете одну из них как программу, не используя gotos для переходов, то на самом деле вы просто усложняете себе жизнь, шифруя намерения вашего кода.

Просто самые распространенные случаи вряд ли будут написаны от руки. Я написал генераторы кода, которые генерируют операторы goto для переходов в различных типах модели состояния (обработка решений, регулярный анализ грамматики и т. Д.), Но я не помню, когда в последний раз я использовал goto в рукописном коде.

1 голос
/ 30 марта 2010

Как показали другие, код, который вы видите в отражателе, обязательно является кодом, написанным в Framework. Компилятор и оптимизаторы могут изменять код так, чтобы он функционировал аналогичным образом, если только он не изменяет фактическую работу, выполняемую кодом. Следует также отметить, что компилятор реализует все ветви и циклы как goto (ветви в IL или переходы в сборке.) Когда запускается режим выпуска и компилятор пытается оптимизировать код до простейшей формы, которая функционально совпадает с вашей источник.

У меня есть пример по различным методам зацикливания, которые все компилируются на 100% один и тот же IL при компиляции для релиза. См. Другой ответ

(Я не могу найти его сейчас, но Эрик Липперт опубликовал заметку о том, как компилятор C # обрабатывает код. Один из замечаний, которые он сделал, это то, как все циклы заменяются на goto.)

Как говорится, у меня нет проблем с goto. Если есть лучшая петлевая структура, используйте ее. Но иногда вам нужно что-то немного больше, чем то, из чего вы можете выжать, foreach, while, do / while, но вы не хотели, чтобы дополнительный беспорядок и боль возникали из-за вызовов методов (зачем тратить 5 с лишним строк, чтобы преобразовать вложенное в рекурсивные методы.)

1 голос
/ 30 марта 2010

Относительно этого пункта:

Итак, есть ли веская причина для кода как то, что мне не хватает? Это было Извлечение кода просто собрал дерьмовый разработчик? или является отражателем .NET возвращать неточный код?

Я не согласен с предпосылкой, что это единственные три возможности.

Может быть, это правда, как и многие другие предположили, что это просто не точное отражение реального исходного кода в библиотеке. Несмотря на это, мы все были виновны (ну, в любом случае, у меня ) в написании кода "грязным путем" с целью:

  1. Быстрое внедрение функции
  2. Быстрое исправление ошибки
  3. Выжимает небольшой прирост производительности (иногда с оправданием, иногда не так сильно)
  4. Какая-то другая причина, которая имела смысл в то время

Это не делает кого-то "дерьмовым разработчиком". Большинство руководств, таких как «ты не должен использовать goto», в основном введены для защиты разработчиков от самих себя; их не следует рассматривать как ключ к различению хороших и плохих разработчиков.

В качестве аналогии рассмотрим простое правило, которому многие из нас учат в начальной школе по английскому языку: никогда не заканчивайте предложение предлогом. Это не настоящее правило ; Это руководство, которое не дает людям говорить что-то вроде: "Где машина?" Важно понимать этот факт; как только вы начнете относиться к нему как к действительному правилу, а не к руководству, вы обнаружите, что «исправляете» людей к совершенно хорошим предложениям типа «Чего вы боитесь?»

Имея это в виду, я бы с осторожностью относился к любому разработчику, который называл другого разработчика "дерьмовым", потому что он использовал goto.

Я определенно не пытаюсь защитить goto, по сути - просто утверждаю, что его использование никоим образом не указывает на некомпетентность.

0 голосов
/ 30 марта 2010

goto идеально подходит для очистки, по крайней мере, в таких языках, как C, где оно несколько имитирует понятие исключений. Я уверен, что .NET имеет лучшие способы обработки подобных вещей, поэтому goto просто устарел и подвержен ошибкам.

0 голосов
/ 02 июля 2010

Возможно, это не лучший пример, но он показывает случай, когда goto может быть очень удобным.

private IDynamic ToExponential(Engine engine, Args args)
{
    var x = engine.Context.ThisBinding.ToNumberPrimitive().Value;

    if (double.IsNaN(x))
    {
        return new StringPrimitive("NaN");
    }

    var s = "";

    if (x < 0)
    {
        s = "-";
        x = -x;
    }

    if (double.IsPositiveInfinity(x))
    {
        return new StringPrimitive(s + "Infinity");
    }

    var f = args[0].ToNumberPrimitive().Value;
    if (f < 0D || f > 20D)
    {
        throw new Exception("RangeError");
    }

    var m = "";
    var c = "";
    var d = "";
    var e = 0D;
    var n = 0D;

    if (x == 0D)
    {
        f = 0D;
        m = m.PadLeft((int)(f + 1D), '0');
        e = 0;
    }
    else
    {
        if (!args[0].IsUndefined) // fractionDigits is supplied
        {
            var lower = (int)Math.Pow(10, f);
            var upper = (int)Math.Pow(10, f + 1D);
            var min = 0 - 0.0001;
            var max = 0 + 0.0001; 

            for (int i = lower; i < upper; i++)
            {
                for (int j = (int)f;; --j)
                {
                    var result = i * Math.Pow(10, j - f) - x;
                    if (result > min && result < max)
                    {
                        n = i;
                        e = j;
                        goto Complete;
                    }
                    if (result <= 0)
                    {
                        break;
                    }
                }

                for (int j = (int)f + 1; ; j++)
                {
                    var result = i * Math.Pow(10, j - f) - x;
                    if (result > min && result < max)
                    {
                        n = i;
                        e = j;
                        goto Complete;
                    }
                    if (result >= 0)
                    {
                        break;
                    }
                }
            }
        }
        else
        {
            var min = x - 0.0001;
            var max = x + 0.0001; 

            // Scan for f where f >= 0
            for (int i = 0;; i++)
            {
                // 10 ^ f <= n < 10 ^ (f + 1)
                var lower = (int)Math.Pow(10, i);
                var upper = (int)Math.Pow(10, i + 1D);
                for (int j = lower; j < upper; j++)
                {
                    // n is not divisible by 10
                    if (j % 10 == 0)
                    {
                        continue;
                    }

                    // n must have f + 1 digits
                    var digits = 0;
                    var state = j;
                    while (state > 0)
                    {
                        state /= 10;
                        digits++;
                    }
                    if (digits != i + 1)
                    {
                        continue;
                    }

                    // Scan for e in both directions
                    for (int k = (int)i; ; --k)
                    {
                        var result = j * Math.Pow(10, k - i);
                        if (result > min && result < max)
                        {
                            f = i;
                            n = j;
                            e = k;
                            goto Complete;
                        }
                        if (result <= i)
                        {
                            break;
                        }
                    }
                    for (int k = (int)i + 1; ; k++)
                    {
                        var result = i * Math.Pow(10, k - i);
                        if (result > min && result < max)
                        {
                            f = i;
                            n = j;
                            e = k;
                            goto Complete;
                        }
                        if (result >= i)
                        {
                            break;
                        }
                    }
                }
            }
        }

    Complete:

        m = n.ToString("G");
    }

    if (f != 0D)
    {
        m = m[0] + "." + m.Substring(1);
    }

    if (e == 0D)
    {
        c = "+";
        d = "0";
    }
    else
    {
        if (e > 0D)
        {
            c = "+";
        }
        else
        {
            c = "-";
            e = -e;
        }
        d = e.ToString("G");
    }

    m = m + "e" + c + d;
    return new StringPrimitive(s + m);
}
0 голосов
/ 30 марта 2010

Существует один допустимый случай - когда вы пытаетесь смоделировать рекурсивный вызов процедуры и выполнить возврат в нерекурсивном коде, или делаете что-то подобное (такое требование также встречается в интерпретаторе Prolog).Но в целом, если вы не занимаетесь чем-то, требующим микрооптимизации, например, шахматной программой или переводчиком языка, гораздо лучше просто использовать обычный стек процедур и использовать вызовы функций / процедур.

0 голосов
/ 30 марта 2010

Мне не нравится этот код.

Я бы предпочел хранить Regex в элементе и проверять его при настройке, избегая всей необходимости логики при чтении.

0 голосов
/ 30 марта 2010

Я никогда не кодировал GO TO, когда писал Фортран.

Мне никогда не приходилось им пользоваться. Я не понимаю, почему какой-либо современный язык требует от пользователя такой вещи. Я бы однозначно сказал «нет».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...