пятница, 16 декабря 2011 г.

[prog] Ввязался в спор о краткости имен идентификаторов

Вот в этот: Наименования переменных.

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

Когда я слышу разговоры о том, что идентификаторы (имена констант или переменных) должны быть короткими, я инстинктивно считаю, что люди предлагают вместо:

const size_t expected_bytes = calculate_expected_message_size();
const size_t bytes_read = load_data_from_stream( expected_bytes );
if( bytes_read != expected_bytes )
   ...;
binary_buffer_ptr_t whole_message = extract_raw_message();
binary_buffer_ptr_t message_payload =
   check_and_remove_protocol_headers( whole_message );

писать так:

const size_t e = calculate_expected_message_size();
const size_t r = load_data_from_stream( e );
if( r != e )
   ...;
binary_buffer_ptr_t m = extract_raw_message();
binary_buffer_ptr_t p = check_and_remove_protocol_headers( m );

Чего лично я не приемлю и за что “бью по рукам”. Ничего не поделать, учился у хороших учителей, давно, когда в образовании использовался Паскаль и книжки Вирта. А место, даже на дискетах, под исходные тексты экономить уже было не принято. Кстати, мониторы тогда были еще алфавитно-цифровые, с разрешением 80x25 знаков, так что текста на экране было намного меньше, чем сейчас. И IDE с автодополнением, подсказками и нафигацией по коду не было. Зато хороший стиль кода очень ценился.

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

Допустим, нам нужно сформировать SQL-ный select, где количество столбцов определяется внешними параметрами. Как это может выглядеть в императивном стиле с переменными? Как-то вот так:

std::string select_statement = "select id";
if( need_creation_time )
   select_statement += ", ctime";
if( need_modificaton_time )
   select_statement += ", mtime";
...
select_statement += " from my_table where ...";

Переменная одна, название ей дано длинное. А вот если мы напишем что-то подобное в функциональном стиле с иммутабельными сущностями, то получится что-то вроде:

s1 = "select id";
s2 = if( need_creation_time ) s1 + ", ctime" else s1;
s3 = if( need_modificaton_time ) s2 + ", mtime" else s2;
...
select_statement = sN + " from my_table where ...";

Т.е. если давать сущностям s1…sN какие-то вменяемые названия (вроде statement_with_opt_ctime, statement_with_opt_mtime и т.д.), то элементарно задолбешься выдумывать промежуточные названия. Да и смысла большого они нести не будут. Хотя, по моему мнению, обилие сущностей вида s1, s2 и т.д., не есть хорошо. Страшно далеко ФП от народа железа ;)

В общем, в программировании есть две неразрешимые проблемы – предсказание сроков и выбор названий для идентификаторов. Только временами кому-то начинает казаться, что они нашли серебряную пулю решение какой-то из них. Забывая четко обозначить тот сильно ограниченный контекст, в котором их частные решения работают.

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

stream << range.m_left
   << range.m_right
   << range.m_client
   << range.m_priority;

на код:

// Если диапазон оказался слишком большим, то нужно его сузить
// принудительно передвинув правую границу.
id_t right = range.m_right;
if( range.m_right > range.m_left + package_size )
   right = range.m_left + package_size;
stream << range.m_left
   << right
   << range.m_client
   << range.m_priority;

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

// Если диапазон оказался слишком большим, то нужно его сузить
// принудительно передвинув правую границу.
id_t right = range.m_right;
if( range.m_right > range.m_left + package_size )
   right = range.m_left + package_size;
stream << range.m_left
   << range.m_right
   << range.m_client
   << range.m_priority;

Понятное дело, что ошибка обнаружилась далеко не сразу и случайно. А уж если бы вместо range, right, m_left, m_right использовались бы имена вида R, L, R1, R2 и т.д., то я бы только функциональщикам и пожелал бы сопровождать такой код.

PPS. Еще ссылки:
- заметка в блоге lionet на какое-то исследование понятности написанного в разном стиле кода;
- очень интересная реакция на это исследование в блоге sleepy_drago;
- скептическая точка зрения Бертранда Мейера на околопрограммистские исследования (на английском).

Отправить комментарий