Идиомы в программировании

Один единственный бит или идиомы против определений

Я видел множество определений похожих на такие:

#define bit_0 1
#define bit_1 2
#define bit_2 4
и так далее...

И вроде бы очевидно, вот вам слова, каждое из которых обозначает число, в котором выставлен один бит. Всё хорошо, только число, в котором установлен один единственный бит записывается в си стандартным образом (как в прочем и во многих других языках это делает аналогично):

1 << bit

И с первого взгляда, особенно если это взглад человека только начавшего изучать язык программирования, запись bit_14 смотрится куда понятнее, чем (1 << 14), да ещё и короче в записи на целых 3 символа (или на 1, если убрать пробелы, но я их оставил, чтобы подразнить читателя на едкое замечание).

Аргумент работает только потому, что определения приближают вид программы к разговорному языку, однако его несостоятельность в том, что опладение языком состоит не в том, чтобы быстро с него переводить на родной (или другой хорошо знакомый), а научиться думать в нём. Перестать анализировать каждый символ, и научиться воспринимать целиком конструкции языка. И тогда запись в стандартных конструкциях языка будет восприниматься как эталон, а подобные переопределения — как диалектизмы.

Есть простая и очевидная запись для установки, сброса и инвертирования одного бита в переменной:

var |= 1 << bit_to_set;
var &= ~(1 << bit_ro_clear);
var ^= 1 << bit_to_invert;

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

#define bit_set(var, bit) ((var) |= 1<<(bit))

Или даже:

#define bit_0_set(x) ((var) |= 1)

Это по сути тоже диалектизм, который к тому же захламляет программу новыми определениями и ставит её в зависимость от дополнительных заголовков.

Цикл со счётчиком

Очевидной идиомой в си для цикла со счётчиком является всем известный

for(init; limit; advance)

Эта запись во многом эквивалентна записи

init;
while(limit) {
    ...
    advance;
}

Единственная разница с точки зрения машины в том, что применение continue внутри цикла while пропускает часть advance. а внутри for — нет. Но речь здесь не про машинный фактор, а про человеческий. Использование for служит сигналом того, цикл — со счётчиком, а while — любой другой. И этого стоит придерживаться, пока нет серьёзных (очень серьёзных) аргументов против.

Также не следует прятать изменение счётчика внутрь тела, как бы сладко не смотрелись пре- и постмодификации.

Почему идиомы не приживаются?

Ответ на этот вопрос требует скорее квалификации психолога или социолога, нежели программиста. Тем не менее можно выделить некоторые характерные моменты.

Весьма характерной причиной для многих (даже весьма опытных) программистов является стремление приблизить текст программы к языку общения, как правило к английскому, реже к родному. Это выражается в том, что программист делает определения многих идиом под именами на естественном языке (примеры приведены как раз в «Одном единственном бите...»). Фактически это превращает язык программирования в некий суржик, где все непонятные или трудно запоминаемые выражения заменены таковыми из другого языка. Проблема такого суржикостроения заключается в том, что для его понимания требуется уже знание не одного языка, а целых двух

Реже встречаются попытки перемешать один язык программирования с другим. Комический пример языкового суржика не раз гулял по Сети. Вот его отрывок:

#define begin {
#define end }

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

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

Краткий словарь идиом си

Произвольная начальная часть идиом опущена в целях упорядочивания по алфавиту. В тексте определения ссылка на переменную часть принимается по умолчанию при отсутствии прямого указания, либо даётся словом определённый. Так в определении идиомы * sizeof(type) в выражении массив из определённого числа элементов подразумевается, что размер стоит перед знаком звёздочки (n * sizeof…), а в идиоме |= mask в словах инверсия битов переменной подразумевается, что переменная стоит перед знаком операции: (var |=…).

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

&

&= ~mask
Сброс битов, установленных в mask.
&= ~(1<<n)
Сброс бита с номером n.
* sizeof(type)
Размер массива из определённого числа элементов либо смещение (ptrdiff_t), между элементами с некоторой разницей индексов.
/ sizeof(type)
Количество элементов в массиве определённого размера.
^= mask
Инверсия битов переменной, установленных в mask.
^= 1<<n
Инверсия бита с номером n.
|= mask
Установка битов переменной, установленных в mask.
|= 1<<n
Установка бита с номером n.
~0
Число со всеми битами, установленными в 1.

1...9

1 << n
Бит с номером n.

A

assert(!"Message")
Указывает, что данный участок кода не должен выполняться, при выполнении выводит сообщение !"Message" и прерывает ряботу программы.
assert(cond || !"Message")
Дополняет условие сообщением на человеческом языке.

С

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

F

for(init; limit; advance)
Цикл со счётчиком.
for(;;)
Бесконечный цикл.

I

if(cond) return errorcode;
if(cond) {…; return errorcode;}
Если стоит не в конце функции, означает аварийное завершение по условию cond с выдачей кода ошибки (может быть 0 / NULL, в случае возврата указателя).

R

return (errno = errorcode), failresult
Возврат с ошибкой: errorcode сохраняется в errno, failresult — возвращаемое значение, сигнализирующее об ошибке.

S

sizeof(array)/sizeof(*array)
Количество элементов в массиве array''..(если массив определён с этим количеством). Следует отметить, что аргумент должен быть именно массивом, не сведённым к указателю (то есть проверить аргумент функции не удастся).
cifra/idiomy_v_programmirovanii.txt · Последние изменения: 2012/01/31 18:36 — vovanium
За исключением случаев, когда указано иное, содержимое этой вики предоставляется на условиях следующей лицензии: CC Attribution 3.0 Unported