Вашему вниманию предлагается перевод статьи Рэймонда Чена из блога The Old New Thing, посвященной проблемам кода, полагающегося на порядок вычисления выражений — и всем тем, кто пишет foo(i++, a[i]);
В ответ я спрашиваю:
Разумеется, приложив некоторые усилия, можно научить к��мпилятор анализировать такой код и выдавать предупреждение вроде такого: "Результат этой операции может различаться в зависимости от порядка вычисления". Однако, в случае принятия подобного решения, вам придется иметь дело с другими проблемами.
Во-первых, будет большое количество ложных срабатываний. Допустим, вы напишете следующий код:
Этот код вызовет предупреждение: компилятор увидит, что метод calculate_tax не является константным (const), поэтому он обеспокоится тем, что метод может изменить переменную base_price — и в этом случае иметь значение будет то, считаете ли вы налог по оригинальной base_price базовой цене, или по уже измененной. Теперь, допустим, что вы знаете (и эти знания компилятору недоступны), что метод подсчета налога calculate_tax обновляет значение локальной переменной налог (tax) в объекте, но не изменяет базовую цену; итак, для вас это предупреждение будет ложной тревогой.
Как вы уже увидели сами, проблема здесь в том, что в случае добавления подобного предупреждения нас ждёт ужасно большое количество ложных срабатываний — в результате чего разработчики будут просто отключать это предупреждение.
Что ж, хорошо, сделаем шаг назад, и давайте будем предупреждать только о самых вопиющих случаях — только тогда, когда переменная изменяется и вычисляется в одном и том же выражении. "Предупреждение: Выражение полагается на порядок вычисления".
Возьмём супер-умного программиста Эксперта Джо: он знает, что его код безупречен, а компилятор — слабак. «Хорошо, да это же очевидно, что сначала делается инкремент переменной, затем она используется для вычисления индекса массива, а затем результат поиска в массиве сохраняется обратно в переменную. Здесь нет конфликта порядка вычисления. Тупой компилятор!». В результате супер-умный программист Эксперт Джо отключит это предупреждение, сочтя его бесполезным. Что ж, наш Эксперт Джо — это всё-таки безнадежный случай, и за него мы не беспокоимся.
Но возьмем другого программиста, Новичка Джо — на деле, он даже не поймёт сути этого предупреждения. «Ну ок, давайте посмотрим. Я компилировал эту функцию пять раз, и каждый раз я получал одинаковый результат. Результат выглядит для меня надежным. Похоже на то, что предупреждение было ложным». Таким образом, как раз те, кто должен был полу��ить пользу от этого предупреждения, не всегда обладает достаточными знаниями, чтобы понять его.
Конечно же, проходит некоторое время, и этот вопрос всплывает в рассылке вновь. Кто-нибудь обязательно спросит, почему выражение x ^= y ^= x ^= y не работает в C#, хотя работает в С++. Вот вам ещё одно доказательство того, что некоторые всё-таки пишут код, который полагается на несколько побочных эффектов сразу — и эти же люди искренне считают, что их код очевиден и гарантировано будет работать.
Ссылка на оригинал
Ссылка на обсуждение
Порядок вычисления выражений определяется конкретной реализацией, за исключением случаев, когда язык гарантирует определенный порядок вычислений. Если же в дополнение к результату вычисление выражения вызывает изменения в среде выполнения, то говорят, что данное выражение имеет побочные эффекты.В нашей внутренней рассылке про C# регулярно возникает дискуссионный вопрос, который касается корректной интерпретации подобных конструкций:
MSDN
a -= a *= a;
p[x++] = ++x;
В ответ я спрашиваю:
Да кто вообще пишет такой код с невозмутимым видом? Одно дело, когда такое пишешь, пытаясь победить в «Международном Конкурсе запутывания кода на Си» (IOCCC, International Obfuscated C Code Contest), или если хочешь написать головоломку — но в обоих случаях понимаешь, что ты занимаешься чем-то нестандартным. Что, реально есть кто-то, кто пишет a -= a *= a и p[x++] = ++x; и думает про себя «Чёрт возьми, да я пишу действительно классный код!»На что Эрик Липперт отвечает мне: «Да, такие люди определенно встречаются». В качестве примера он привел одну успешную книгу популярного автора, который свято верил в то, что чем короче код, тем быстрее он работает. Так вот, представьте себе — продажи этой книги составляют уже свыше 4 миллионов копий и продолжают расти. Автор этой книги постарался впихнуть в каждое выражение несколько побочных эффектов сразу, плотно усеяв их условными тернарными операторами; всё дело в том, что он искренне верил в то, что скорость выполнения программы пропорциональна количеству использованных в ней точек с запятыми — и что каждый раз, когда программист объявляет новую переменную, Бог убивает щеночка.
Разумеется, приложив некоторые усилия, можно научить к��мпилятор анализировать такой код и выдавать предупреждение вроде такого: "Результат этой операции может различаться в зависимости от порядка вычисления". Однако, в случае принятия подобного решения, вам придется иметь дело с другими проблемами.
Во-первых, будет большое количество ложных срабатываний. Допустим, вы напишете следующий код:
total_cost = p->base_price + p->calculate_tax();Этот код вызовет предупреждение: компилятор увидит, что метод calculate_tax не является константным (const), поэтому он обеспокоится тем, что метод может изменить переменную base_price — и в этом случае иметь значение будет то, считаете ли вы налог по оригинальной base_price базовой цене, или по уже измененной. Теперь, допустим, что вы знаете (и эти знания компилятору недоступны), что метод подсчета налога calculate_tax обновляет значение локальной переменной налог (tax) в объекте, но не изменяет базовую цену; итак, для вас это предупреждение будет ложной тревогой.
Как вы уже увидели сами, проблема здесь в том, что в случае добавления подобного предупреждения нас ждёт ужасно большое количество ложных срабатываний — в результате чего разработчики будут просто отключать это предупреждение.
Что ж, хорошо, сделаем шаг назад, и давайте будем предупреждать только о самых вопиющих случаях — только тогда, когда переменная изменяется и вычисляется в одном и том же выражении. "Предупреждение: Выражение полагается на порядок вычисления".
Возьмём супер-умного программиста Эксперта Джо: он знает, что его код безупречен, а компилятор — слабак. «Хорошо, да это же очевидно, что сначала делается инкремент переменной, затем она используется для вычисления индекса массива, а затем результат поиска в массиве сохраняется обратно в переменную. Здесь нет конфликта порядка вычисления. Тупой компилятор!». В результате супер-умный программист Эксперт Джо отключит это предупреждение, сочтя его бесполезным. Что ж, наш Эксперт Джо — это всё-таки безнадежный случай, и за него мы не беспокоимся.
Но возьмем другого программиста, Новичка Джо — на деле, он даже не поймёт сути этого предупреждения. «Ну ок, давайте посмотрим. Я компилировал эту функцию пять раз, и каждый раз я получал одинаковый результат. Результат выглядит для меня надежным. Похоже на то, что предупреждение было ложным». Таким образом, как раз те, кто должен был полу��ить пользу от этого предупреждения, не всегда обладает достаточными знаниями, чтобы понять его.
Конечно же, проходит некоторое время, и этот вопрос всплывает в рассылке вновь. Кто-нибудь обязательно спросит, почему выражение x ^= y ^= x ^= y не работает в C#, хотя работает в С++. Вот вам ещё одно доказательство того, что некоторые всё-таки пишут код, который полагается на несколько побочных эффектов сразу — и эти же люди искренне считают, что их код очевиден и гарантировано будет работать.
Ссылка на оригинал
Ссылка на обсуждение
