Почему ярлыки, такие как x + = y, считаются хорошей практикой?

Я понятия не имею, что они на самом деле называются, но я вижу их все время. Реализация Python выглядит примерно так:

x += 5 как сокращенное обозначение для x = x + 5.

Но почему это считается хорошей практикой? Я столкнулся с этим почти в каждой книге или учебнике по программированию, которое я прочитал для Python, C, R так далее и т. д. Я понимаю, что это удобно, экономя три нажатия клавиш, включая пробелы. Но они всегда кажутся мне меня, когда я читаю код и, по крайней мере, думаю, меньше читабельны, а не больше.

Я пропустил какую-то ясную и очевидную причину, по которой они используются повсюду?

293 голоса | спросил 2 revs, 2 users 75%
Fomite
1 Jam1000000amThu, 01 Jan 1970 03:00:00 +030070 1970, 03:00:00

16 ответов


582

Это не сокращение.

Символ += появился в языке C в 1970-х годах и - с идеей C «умного ассемблера» соответствует явно другой машинной инструкции и режиму адресации:

Такие вещи, как «i=i+1», "i+=1" и "++i", хотя при абстрактном уровень дают тот же эффект, соответствуют низкому уровню другому способу работы процессора.

В частности, эти три выражения, предполагая, что переменная i находится в адресе памяти, хранящемся в регистре CPU (назовем его D), подумайте об этом как «указатель» to int "), а ALU процессора принимает параметр и возвращает результат в« накопителе » "(назовем это A - подумайте об этом как int).

С этими ограничениями (очень распространенными во всех микропроцессорах того периода), перевод, скорее всего, будет

;i = i+1;
MOV A,(D); //Move in A the content of the memory whose address is in D
ADD A, 1;  //The addition of an inlined constant
MOV (D) A; //Move the result back to i (this is the '=' of the expression)

;i+=1;
ADD (D),1; //Add an inlined constant to a memory address stored value

;++i;
INC (D); //Just "tick" a memory located counter

Первый способ сделать это disoptimal, но он более общий при работе с переменными вместо константы (ADD A, B или ADD A, (D+x)) или при переводе более сложных выражений (все они сворачиваются в режиме низкого приоритета в стеке, вызывают высокий приоритет, поп и повторяют до тех пор, пока все аргументы не будут устранены).

Второй вариант более типичен для «конечного автомата»: мы больше не «оцениваем выражение», а «управляем значением»: мы все еще используем ALU, но избегаем перемещения значений вокруг результата, позволяющего заменить параметр , Эта инструкция не может использоваться там, где требуется более сложное выражение: i = 3*i + i-2 нельзя использовать на месте, так как i требуется больше раз.

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

С современными компиляторами (см. теперь C), позволяя оптимизировать компилятор, соответствие может быть заменено на основе удобства, но в семантике все еще существует концептуальная разница.

x += 5 означает

  • Найдите место, идентифицированное x
  • Добавить 5 к нему

Но x = x + 5 означает:

  • Оцените x + 5
    • Найдите место, идентифицированное x
    • Скопировать x в аккумулятор
    • Добавить 5 в аккумулятор
  • Сохранить результат в x
    • Найдите место, идентифицированное x
    • Скопируйте накопитель на него

Конечно, оптимизация может

  • если «find x» не имеет побочных эффектов, два «находок» могут быть сделаны один раз (и x станет адресом, сохраненным в регистре указателей)
  • две копии могут быть удалены, если ADD применяется к &x вместо этого к аккумулятору

, что делает оптимизированный код совпадающим с x += 5.

Но это можно сделать, только если «поиск х» не имеет побочных эффектов, иначе

*(x()) = *(x()) + 5;

и

*(x()) += 5;

являются семантически разными, так как побочные эффекты x() (допускающие x()) - это функция, делающая странные вещи и возвращающая int*) будет производиться дважды или один раз.

Эквивалентность между x = x + y и x += y, следовательно, обусловлена ​​конкретным случаем, когда += и = применяются к прямому l-значению.

Чтобы перейти к Python, он унаследовал синтаксис от C, но поскольку нет перевода и оптимизации перед выполнением интерпретируемых языков, все это не обязательно так тесно связано (так как есть один меньше шагов синтаксического анализа). Однако интерпретатор может ссылаться на разные процедуры выполнения для трех типов выражений, используя различные машинные коды в зависимости от того, как формируется выражение и в контексте оценки.


Кому нравится больше деталей ...

Каждый процессор имеет ALU (арифметико-логический блок), который по своей сути представляет собой комбинаторную сеть, входы и выходы которой «подключаются» к регистрам и /или памяти в зависимости от кода операции инструкции.

Двоичные операции обычно реализуются как «модификатор накопительного регистра с введенным« где-то », где где-то может быть  - внутри самого потока команд (типичный дляманифест: ADD A 5)  - внутри другого реестра (типичного для вычисления выражения с временными: например, ADD A B)  - внутри памяти, по адресу, заданному регистром (типичный для извлечения данных, например: ADD A (H)) - H, в этом случае работает как указатель разыменования.

С этим псевдокодом x += 5 есть

ADD (X) 5

, а x = x+5 -

MOVE A (X)
ADD A 5
MOVE (X) A

То есть, x + 5 дает временное, которое позже назначается. x += 5 работает непосредственно на x.

Фактическая реализация зависит от реального набора команд процессора: Если код кода ADD (.) c отсутствует, первый код становится вторым: no way.

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

ответил Bart van Ingen Schenau 10 Jpm1000000pmMon, 10 Jan 2011 18:10:42 +030011 2011, 18:10:42
277

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

x = x + 5 вызывает интеллектуальную обработку «take x», добавляет к ней пять, а затем присваивает это новое значение «x»

x += 5 можно рассматривать как «увеличить x на 5»

Итак, это не просто сокращение, оно фактически описывает функциональность гораздо более прямо. При чтении сквозных кодов кода это гораздо легче понять.

ответил Bart van Ingen Schenau 10 Jpm1000000pmMon, 10 Jan 2011 18:10:42 +030011 2011, 18:10:42
46

По крайней мере, в Python , x += y и x = x + y может делать совершенно разные вещи.

Например, если мы делаем

a = []
b = a

, затем a += [3] приведет к a == b == [3], а a = a + [3] приведет к a == [3] и b == []. То есть += изменяет объект на месте (ну, может быть, вы можете определить метод __iadd__, чтобы делать практически все, что вам нравится), а = создает новый объект и привязывает к нему переменную.

Это очень важно, когда вы выполняете численную работу с NumPy , так как вы часто получаете несколько ссылок на разные части массив, и важно убедиться, что вы не произвольно не модифицируете часть массива, где есть другие ссылки, или ненужные копии массивов (что может быть очень дорого).

ответил Bart van Ingen Schenau 10 Jpm1000000pmMon, 10 Jan 2011 18:10:42 +030011 2011, 18:10:42
41

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

Когда кто-то пишет x += y, вы знаете, что x увеличивается на y, а не на более сложную операцию (как лучший практика, обычно я бы не смешивал более сложные операции и эти сокращения синтаксиса). Это имеет наибольший смысл при увеличении на 1.

ответил Bart van Ingen Schenau 10 Jpm1000000pmMon, 10 Jan 2011 18:10:42 +030011 2011, 18:10:42
39

Чтобы поставить точку @ Pubby немного понятнее, рассмотрите someObj.foo.bar.func(x, y, z).baz += 5

Без оператора += существует два способа:

  1. someObj.foo.bar.func(x, y, z).baz = someObj.foo.bar.func(x, y, z).baz + 5. Это не только ужасно избыточно и долго, но и медленнее. Поэтому нужно было бы
  2. Использовать временную переменную: tmp := someObj.foo.bar.func(x, y, z); tmp.baz = tmp.bar + 5. Это нормально, но для простоты много шума. Это действительно очень близко к тому, что происходит во время выполнения, но утомительно писать, и просто используя += переносит работу на компилятор /интерпретатор.

Преимущество += и других таких операторов неоспоримо, но привыкание к ним - это только вопрос времени.

ответил Bart van Ingen Schenau 10 Jpm1000000pmMon, 10 Jan 2011 18:10:42 +030011 2011, 18:10:42
21

Это правда, что он короче и проще, и это правда, что он, вероятно, был вдохновлен основным языком ассемблера, но причина, по которой это best practice , состоит в том, что он предотвращает целый класс ошибок, и это упрощает просмотр кода и уверен, что он делает.

С

RidiculouslyComplexName += 1;

Поскольку есть только одно имя переменной, вы уверены, что делает оператор.

С помощью RidiculouslyComplexName = RidiculosulyComplexName + 1;

Всегда есть сомнения, что обе стороны точно такие же. Вы видели ошибку? Это становится еще хуже, когда присутствуют индексы и квалификаторы.

ответил Bart van Ingen Schenau 10 Jpm1000000pmMon, 10 Jan 2011 18:10:42 +030011 2011, 18:10:42
14

В то время как обозначение + = идиоматично и короче, это не то, почему его легче читать. Самая важная часть кода чтения - это сопоставление синтаксиса с смыслом, поэтому, чем ближе синтаксис соответствует мыслительным процессам программиста, тем более читаемым он будет (это также является причиной того, что шаблонный код плохой: он не является частью мысли процесс, но все же необходимо сделать функцию кода). В этом случае мысль представляет собой «переменную приращения x на 5», а не «пусть x - значение x плюс 5».

Существуют и другие случаи, когда более короткая нотация является плохой для удобочитаемости, например, когда вы используете тернарный оператор, где утверждение if было бы более подходящим.

ответил Bart van Ingen Schenau 10 Jpm1000000pmMon, 10 Jan 2011 18:10:42 +030011 2011, 18:10:42
11

Для некоторого понимания того, почему эти операторы находятся на языках C-стиля, есть этот отрывок из K & R 1st Edition (1978), 34 года назад:

  

В отличие от краткости, операторы присваивания имеют преимущество   что они лучше соответствуют тому, как люди думают. Мы говорим: «добавьте 2 к   i "или" increment i на 2 ", а не" взять i, добавить 2, а затем вернуть результат обратно   в i ". Таким образом, i += 2. Кроме того, для сложного выражения типа

yyval[yypv[p3+p4] + yypv[p1+p2]] += 2
     

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

Я думаю, что из этого отрывка видно, что Брайан Керниган и Деннис Ритчи (K & R) полагал, что сложные операторы присваивания помогли с читаемостью кода.

Прошло много времени с тех пор, как K & R написал это, и многие из «лучших практик» о том, как люди должны писать код, изменились или развились с тех пор. Но этот вопрос для программистов.stackexchange - это первый случай, когда я могу вспомнить кого-то, высказывающего жалобу на читаемость сложных назначений, поэтому я задаюсь вопросом, не найдет ли многие программисты проблемы? Опять же, когда я набираю этот вопрос, у него есть 95 upvotes, так что, возможно, люди находят, что они сотрясаются при чтении кода.

ответил Bart van Ingen Schenau 10 Jpm1000000pmMon, 10 Jan 2011 18:10:42 +030011 2011, 18:10:42
8

Помимо читаемости, они фактически выполняют разные вещи: += не нужно дважды оценивать свой левый операнд.

Например, expr = expr + 5 будет evalaute expr дважды (при условии, что expr нечисто).

ответил Bart van Ingen Schenau 10 Jpm1000000pmMon, 10 Jan 2011 18:10:42 +030011 2011, 18:10:42
6

Это краткий.

Это намного короче. Он включает меньше операторов. Он имеет меньшую площадь поверхности и меньше возможностей для путаницы.

Он использует более конкретный оператор.

Это надуманный пример, и я не уверен, реализуют ли это реальные компиляторы. x + = y фактически использует один аргумент и один оператор и модифицирует x на месте. x = x + y может иметь промежуточное представление x = z, где z - x + y. Последний использует два оператора, добавление и назначение и временную переменную. Единственный оператор делает очень понятным, что сторона значения не может быть чем-то другим, кроме y и не нуждается в интерпретации. И теоретически может быть какой-то причудливый CPU, у которого есть оператор с плюсом, который работает быстрее, чем оператор плюс и оператор присваивания последовательно.

ответил Bart van Ingen Schenau 10 Jpm1000000pmMon, 10 Jan 2011 18:10:42 +030011 2011, 18:10:42
5

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

  MyVeryVeryVeryVeryVeryLongName += 1;

или

  MyVeryVeryVeryVeryVeryLongName =  MyVeryVeryVeryVeryVeryLongName + 1;
ответил Bart van Ingen Schenau 10 Jpm1000000pmMon, 10 Jan 2011 18:10:42 +030011 2011, 18:10:42
5

Это хорошая идиома. Будет ли это быстрее или нет, зависит от языка. В C это быстрее, потому что это переводит на инструкцию для увеличения переменной с правой стороны. Современные языки, включая Python, Ruby, C, C ++ и Java, поддерживают синтаксис op =. Он компактный, и вы привыкаете к нему быстро. Поскольку вы увидите это в коде других народов (OPC), вы можете привыкнуть к нему и использовать его. Вот что происходит на нескольких других языках.

В Python ввод текста x += 5 по-прежнему вызывает создание целочисленного объекта 1 (хотя он может быть извлечен из пула) и сирота целочисленного объекта, содержащего 5.

В Java это приводит к появлению негласного приведения. Попробуйте ввести

int x = 4;
x = x + 5.2  // This causes a compiler error
x += 5.2     // This is not an error; an implicit cast is done.
ответил Bart van Ingen Schenau 10 Jpm1000000pmMon, 10 Jan 2011 18:10:42 +030011 2011, 18:10:42
4

Операторы, такие как +=, очень полезны, когда вы используете переменную как аккумулятор , т. е. общее количество:

x += 2;
x += 5;
x -= 3;

Легче читать, чем:

x = x + 2;
x = x + 5;
x = x - 3;

В первом случае, концептуально, вы изменяете значение в x. Во втором случае вы вычисляете новое значение и каждый раз назначаете его x. И хотя вы, вероятно, никогда не будете писать код, который является настолько простым, идея остается прежней ... основное внимание уделяется тому, что вы делаете с существующим значением, а не создаете новое значение.

ответил Bart van Ingen Schenau 10 Jpm1000000pmMon, 10 Jan 2011 18:10:42 +030011 2011, 18:10:42
1

Рассмотрим это

(some_object[index])->some_other_object[more] += 5

D0 вы действительно хотите написать

(some_object[index])->some_other_object[more] = (some_object[index])->some_other_object[more] + 5
ответил Bart van Ingen Schenau 10 Jpm1000000pmMon, 10 Jan 2011 18:10:42 +030011 2011, 18:10:42
1

Другие ответы нацелены на более распространенные случаи, но есть и другая причина: на некоторых языках программирования он может быть перегружен; например Scala .


Урок Малой Скалы:

var j = 5 #Creates a variable
j += 4    #Compiles

val i = 5 #Creates a constant
i += 4    #Doesn’t compile

Если класс определяет только оператор +, x+=y действительно является ярлыком x=x+y.

Если класс перегружает +=, они не являются:

var a = ""
a += "This works. a now points to a new String."

val b = ""
b += "This doesn’t compile, as b cannot be reassigned."

val c = StringBuffer() #implements +=
c += "This works, as StringBuffer implements “+=(c: String)”."

Кроме того, операторы + и += - это два отдельных оператора (и не только эти: + a, ++ a, a ++, a + ba + = b разные операторы); в языках, где имеется перегрузка операторов, это может создать интересные ситуации. Как описано выше - если вы перегрузите оператор +, чтобы выполнить добавление, имейте в виду, что необходимо также перегрузить +=.

ответил Bart van Ingen Schenau 10 Jpm1000000pmMon, 10 Jan 2011 18:10:42 +030011 2011, 18:10:42
1

Скажите это один раз и только один раз: в x = x + 1, я говорю «x» дважды.

Но никогда не пишите: «a = b + = 1» или нам придется убить 10 котят, 27 мышей, собаку и хомяка.


Вы никогда не должны изменять значение переменной, так как это упрощает подтверждение правильности кода - см. функциональное программирование. Однако, если вы это сделаете, тогда лучше не говорить вещи только один раз.

ответил Bart van Ingen Schenau 10 Jpm1000000pmMon, 10 Jan 2011 18:10:42 +030011 2011, 18:10:42

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132