среда, 10 февраля 2021 г.

[prog.c++] Куда не заглянешь, везде чего-нить да унюхаешь...

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

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

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

Приведу пару свежих примеров.


Случай номер раз. По мотивам вброса на RSDN-е (полагаю, что автор вброса в том числе и меня подразумевал под "знатоки раскошерного C++").

Есть недавно открытый исходный код майкрософтского движка ESE. Заглядываю чуть ли не в первый же файл и вижу распрекрасную функцию на 400 строк.

Ну а раз есть функция на 400 строк, то кучка известной субстанции там должна быть обязательно. Возможно и не одна.

И, действительно, далеко ходить не нужно. Вот прям классический пример говнокода оттуда:

if ( iRetry > 0 )
{
    const WCHAR*    rgpsz[ 5 ];
    DWORD           irgpsz      = 0;
    WCHAR           szAbsPath[ IFileSystemAPI::cchPathMax ];
    WCHAR           szOffset[ 64 ];
    WCHAR           szLength[ 64 ];
    WCHAR           szFailures[ 64 ];
    WCHAR           szElapsed[ 64 ];

    CallS( pfmp->Pfapi()->ErrPath( szAbsPath ) );
    OSStrCbFormatW( szOffset, sizeof( szOffset ), L"%I64i (0x%016I64x)", OffsetOfPgno( pgnoStart ), OffsetOfPgno( pgnoStart ) );
    OSStrCbFormatW( szLength, sizeof( szLength ), L"%u (0x%08x)", ( pgnoEnd - pgnoStart + 1 ) * g_cbPage, ( pgnoEnd - pgnoStart + 1 ) * g_cbPage );
    OSStrCbFormatW( szFailures, sizeof( szFailures ), L"%i", iRetry );
    OSStrCbFormatW( szElapsed, sizeof( szElapsed ), L"%g", ( TickOSTimeCurrent() - tickStart ) / 1000.0 );

    rgpsz[ irgpsz++ ]   = szAbsPath;
    rgpsz[ irgpsz++ ]   = szOffset;
    rgpsz[ irgpsz++ ]   = szLength;
    rgpsz[ irgpsz++ ]   = szFailures;
    rgpsz[ irgpsz++ ]   = szElapsed;

    UtilReportEvent(    eventError,
                        LOGGING_RECOVERY_CATEGORY,
                        TRANSIENT_READ_ERROR_DETECTED_ID,
                        irgpsz,
                        rgpsz,
                        0,
                        NULL,
                        m_pinst );
}

Я не могу отделаться от мысли, что к этому коду приложил руку человек, которого к программированию вообще нельзя было подпускать. Ну вот вообще. И здесь даже не сделать скидку на то, что автор сего фрагмента не очень хорошо знает C++. Проблема в ДНК, ибо по другому инициализацию rgpsz посредством инкрементирования вспомогательной переменной irgpsz объяснить не получается.

Да, код тупой и писать такой код -- это унылая рутина. Но ведь не сложно же написать так:

if ( iRetry > 0 )
{
    WCHAR           szAbsPath[ IFileSystemAPI::cchPathMax ];
    WCHAR           szOffset[ 64 ];
    WCHAR           szLength[ 64 ];
    WCHAR           szFailures[ 64 ];
    WCHAR           szElapsed[ 64 ];

    CallS( pfmp->Pfapi()->ErrPath( szAbsPath ) );
    OSStrCbFormatW( szOffset, sizeof( szOffset ), L"%I64i (0x%016I64x)", OffsetOfPgno( pgnoStart ), OffsetOfPgno( pgnoStart ) );
    OSStrCbFormatW( szLength, sizeof( szLength ), L"%u (0x%08x)", ( pgnoEnd - pgnoStart + 1 ) * g_cbPage, ( pgnoEnd - pgnoStart + 1 ) * g_cbPage );
    OSStrCbFormatW( szFailures, sizeof( szFailures ), L"%i", iRetry );
    OSStrCbFormatW( szElapsed, sizeof( szElapsed ), L"%g", ( TickOSTimeCurrent() - tickStart ) / 1000.0 );

    const WCHAR * rgpsz[] = { szAbsPath, szOffset, szLength, szFailures, szElapsed };

    UtilReportEvent(    eventError,
                        LOGGING_RECOVERY_CATEGORY,
                        TRANSIENT_READ_ERROR_DETECTED_ID,
                        sizeof(rgpsz) / sizeof(rgpsz[0]),
                        rgpsz,
                        0,
                        NULL,
                        m_pinst );
}

Это и компактнее, и понятнее. И, что гораздо важнее, такой вариант тупо надежнее.


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

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

В реализации класса Event_queue_element_for_exec обнаруживается потенциально неприятный момент (объявление класса здесь, реализация здесь):

bool
Event_queue_element_for_exec::init(const LEX_CSTRING &db, const LEX_CSTRING &n)
{
  if (!(dbname.str= my_strndup(key_memory_Event_queue_element_for_exec_names,
                               db.str, dbname.length= db.length, MYF(MY_WME))))
    return TRUE;
  if (!(name.str= my_strndup(key_memory_Event_queue_element_for_exec_names,
                             n.str, name.length= n.length, MYF(MY_WME))))
  {
    my_free(const_cast<char*>(dbname.str)); // (1)
    return TRUE;
  }
  return FALSE;
}

Обращаем внимание на точку (1). Здесь память, выделенная под dbname.str, освобождается. Но значение dbname.str не зануляется, оно остается прежним. Т.е. dbname.str указывает на уже освобожденный блок памяти.

Далее смотрим на деструктор:

Event_queue_element_for_exec::~Event_queue_element_for_exec()
{
  my_free(const_cast<char*>(dbname.str)); // (2)
  my_free(const_cast<char*>(name.str));
}

Там тупо вызываются my_free.

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

Но я сильно сомневаюсь, что my_free в MariaDB может обработать ситуацию с double free: т.е. вряд ли my_free может понять, что ей подсунули указатель, который уже был освобожден.

Так что код класса Event_queue_element_for_exec производит впечатление, что его писали лишь под успешный сценарий использования: создали экземпляр Event_queue_element_for_exec, вызвали для него init, этот init нормально отработал, затем спокойно отработает и деструктор.

Но вот в ситуации, когда init сможет успешно выделить память под dbname.str, но обломается с name.str, может случиться double free. Т.е. выделенная под dbname.str память сперва будет освобождена в init в точке (1). А затем ее еще раз попробуют освободить в деструкторе в точке (2).

Повторюсь, не уверен есть ли здесь реальная проблема или же я просто не знаю каких-то особенностей работы my_free в MariaDB (соответствующий issue я завел, посмотрим будет ли какая-нибудь реакция). Но вообще код Event_queue_element_for_exec изрядно попахивает, т.к. кроме обозначенного уже момента мне еще не нравится и отсутствие обнуления dbname.str и name.str в конструкторе (а LEX_CSTRING -- это просто typedef для чисто сишной структуры, у которой нет ни конструктора, ни инициализаторов для поля str). Так что проблемы с Event_queue_element_for_exec возможны даже если просто создать экземпляр Event_queue_element_for_exec и не вызывать для него init.


Какая из всего этого мораль?

А никакой.

Данный пост можно воспринимать просто как крик души.

Чем старше становишься, тем больше устаешь от бахвальства и самодовольства программистов при том, что откровенного говнокода эти самые программисты производят все больше и больше. Ну вот буквально, куда не посмотришь, везде чем-то да попахивает. Такое ощущение, что качество нахер никому не упало. И уже давно.

И да, для меня это немного личное.

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

Но когда смотришь по сторонам и регулярно видишь вокруг одно говно, то невольно задумываешься а не пора ли послать все это куда подальше?

Ну вот реально, читаешь в Интернетах разговоры программистов о том, как и где устраиваться на хорошие зарплаты, как и зачем менять работу каждые 2-3 года, как прокачивать свои софт-скилы дабы успешнее продвигаться по карьерной лестнице... И задаешься вопросом: "А кому-нибудь из вас, криворуких балаболов, есть дело до качества производимого вами кода?"

Вопрос, конечно же, риторический.

И да, я злой.

21 комментарий:

MaxF комментирует...
Этот комментарий был удален автором.
MaxF комментирует...

Добрый день, Евгений.

Касаемо случая 1, очень кажется, что это был бездумный copy-paste из какого-то другого подобного проекта, где требовалось динамически добавлять от 1 до 5 элементов в зависимости от какого-либо условия.

Что касается понтов на форумах, хорошо сказал один мой приятель (сам неплохой Oracle DBA): "все кругом понтуются и надувают щеки, а как глянешь реальный код или DB - все как у всех".

svr комментирует...

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

Который состоит в том, что вы критикуете тот код, который видите. И, что не удивительно, не можете критиковать код, который не видите. А в любом коде (я уверен что и в вашем тоже) будут какие-то недочеты. И вот вы натыкаетесь на них, и думаете "что за дебил был тот автор". Но возможно, что автор не дебил, а наоборот, гений, который продумал и учел 1000 различных сценариев работы функции. Но так как он это учел, то вы этого просто не видите, и ваши глаза цепляются за другие недостатки данного кода.

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

eao197 комментирует...

@Unknown

Тут есть два момента.

1. Мой код лежит в открытом доступе. Если кто-то укажет на фрагменты, которые можно отнести к говнокоду, то я только спасибо скажу.

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

Например, такими маркерами являются функции большого размера.
Или, если говорить про C++, голые указатели и ручной вызов функций типа free.

svr комментирует...

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

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

eao197 комментирует...

@Unknown

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

Это как в обычном языке. У одного человека есть что сказать, у другого нет. И разница между этими вещами практически не компенсируется (по крайней мере на длительном отрезке времени) уровнем владения языком (красноречием, риторикой, использованием аналогий/гипербол и пр. выразительными средствами).

Но вот, допустим, есть человеку что сказать. Сделать он может это по разному. Можно очень косноязычно, скучно, затянуто, путанно. Да и просто с большим количеством орфографических ошибок.

Но можно выразить ту же самую мысль и по другому. Тщательно выбирая выражения, не углубляясь в ненужные детали, стройно выстраивая подачу материала, используя богатый словарный запас и т.д.

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

Что касается первого фрагмента, то a) в нем лишняя сущность, без которой можно и нужно обойтись и b) он ненадежен, т.к. никак не защищен от того, что в rgpsz попытаются добавить еще один указатель не увеличив предварительно размерность rgpsz. Т.е. мало того, что ввели лишную сущность, так еще и никак не синхронизировали эту сущность с размером rgpsz.

И это при том, что есть гораздо более прямое и гораздо более надежное решение, которое напрашивается вот прямо сразу. Для которого вообще нужен совсем небольшой опыт использования что чистого C, что C++.

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

svr комментирует...

Все равно не понятно акцентирование именно на коде, а не на решении целиком.
Да и с языком таже ситуация. Мне например довольно тяжело выражать свои мысли. Слова просто убегают от меня, когда я пытаюсь что-то выразить.

>> Т.е., грубо говоря, это как если бы кто-то начал писать "Войну и мир" не владея русским языком в минимальной степени.
Хм, но если бы ктото начал писать "Войну и мир", не владея в совершенстве языком, то это была бы уже не Война и мир? К томуже куча людей пишут книги, не владея русским языком (а владея английским, например). Так что эта аналогия как раз непонятна. Точнее, как раз понятно, что вы хотите сосредоточиться на форме, а не на содержании

svr комментирует...

И вроде Толстой даже правил пунктуации не знал, за него запятые проставляла секретарша. Или это не Толстой был, не помню...

eao197 комментирует...

> Все равно не понятно акцентирование именно на коде, а не на решении целиком.

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

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

> Мне например довольно тяжело выражать свои мысли. Слова просто убегают от меня, когда я пытаюсь что-то выразить.

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

Вот, например, вы в двух предложениях подряд использовали слова "выражать". Если бы за вами кто-то опытный тексты вычитывал и показывал бы на такие повторы, то вы бы быстро начали бы сами такие вещи замечать, а затем бы у вас синонимы начали бы подбираться автоматически.

Но это требует постоянных занятий.

> Хм, но если бы ктото начал писать "Войну и мир", не владея в совершенстве языком, то это была бы уже не Война и мир?

Нет. "Война и мир" уникальна не только заложенными в нее идеями, но и формой воплощения. Скажем, описания батальных сцен там чуть ли не самые лучшие в мировой литературе вообще. Что стало возможно благодаря кропотливой работе самого Толстого.

> И вроде Толстой даже правил пунктуации не знал, за него запятые проставляла секретарша. Или это не Толстой был, не помню...

Я не Толстой, конечно, но если бы не автоматическая проверка орфографии, то мои тексты постоянно бы выглядели как говно.

svr комментирует...

Ну вот например такой абстрактный пример
int divideBy(int a, int b) {
return a / b;
}
Что можно сказать плохого про такую функцию? Разве только то, что здесь не проверяется деление на 0. Ок, а если такая функция?
int divideBy(int a, int b) {
if (b == 0) {
throw divide_by_zero_exception();
}
if (a == std::numeric_limits::min_value() && b == -1) {
throw integer_owerflow_exception();
} else {
return a / b;
}
}
Смотрите, автор этой функции учел, что исключительная ситуация возникает не только при делении на 0, но и при делении на -1 тоже, о чем вы скорее всего не подумали. Однако к такому коду можно накопать намного больше претензий. К именам исключений например. Да и вообще, почему тут используются исключения, когда сегодня более моден подход с возвратом Result-обертки? Но даже если переписать эту функцию на возврат значения, то к ней все равно можно будет придраться, например, почему первый и второй if не соединены else? И наоборот, почему последний return лежит под else, когда может просто лежать ниже? И т.п.

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

eao197 комментирует...

@Unknown

> И это лишь простой, абстрактный пример.

Проблема в том, что вы рассматриваете именно что "простой, абстрактный пример". Да еще в полном отрыве от контекста его использования.

Тогда как я указываю на реальные проблемы в реальном коде из реальных проектов.

svr комментирует...

Ну вот я например посмотрел первую функцию из вашего примера. И что вижу
1) Повсеместное использование Assert-ов. В некоторых местах Assert-ов больше чем собственно кода. Я считаю это за отличный знак, сам так стараюсь писать и то не всегда получается.
2) Использование const там где это можно, тоже хороший знак
3) По поводу конкретно вашего фрагмента, там почти в самом конце функции есть похожий кусок кода
https://github.com/microsoft/Extensible-Storage-Engine/blob/0d5475fe4af15d5253699e840a24d5ac66ba4062/dev/ese/src/ese/backup.cxx#L490
Также заполняется массив через переменную isz, а потом этот массив ДОзаполняется в функции, из-за чего необходимо знать количество уже положенных туда элементов. Почему такойже подход был сделан в начале функции, я не знаю, возможно это просто копи-паста, а может для единообразия

Конечно в коде есть и недостатки, вроде несогласованного стиля разных участков кода (возможно код писался в разное время), использование goto во вроде бы C++ коде, и тп. Но в целом, мельком проглядев код, ничего плохого не увидел.

eao197 комментирует...

> 1) Повсеместное использование Assert-ов. В некоторых местах Assert-ов больше чем собственно кода. Я считаю это за отличный знак, сам так стараюсь писать и то не всегда получается.

assert-ы -- это частично и хреново работающие костыли. Но если кому-то помогают, то радибоха. Для меня это вообще не показатель ничего.

> 3) По поводу конкретно вашего фрагмента, там почти в самом конце функции есть похожий кусок кода

Там такое же говно. Переменная isz не нужна. А ее проверка в assert-е выглядит издевкой. Т.к. подобный assert, если уж и вставлять, то после каждого инкремента isz. А так получается, что если размер rgszT подобран неправильно, то мы сперва затрем что-то на стеке, а потом сделаем assert. Ну или не сделаем, если компилируемся сразу в Release.

svr комментирует...

> assert-ы -- это частично и хреново работающие костыли
А в чем костыли? В том, что оно отключается на релизе? Да, я тоже предпочитаю не отключать assert-ы на релизе (правда и называются они не assert-ы), но опять же мы имеет возможность это обсудить потому что они используются. Если бы они не использовались, мы бы это не обсуждали

> Переменная isz не нужна
А как без нее написать? Мне даже что-то в голову не приходит сходу

eao197 комментирует...

> В том, что оно отключается на релизе?

Если это assert-ы, то они отключаются в релизе. Иначе это не assert-ы.

Если в релизе сохраняются какие-то элементы defensive programming, то тут уже нужно смотреть на то, что это за элементы и как они влияют на работу программы. Начиная от накладных расходов и завершая способами информирования о нарушениях контрактов/инвариантов.

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

> А как без нее написать? Мне даже что-то в голову не приходит сходу

Вроде бы точно так же, как я показывал в посте.

svr комментирует...

> Если же речь все-таки про assert-ы, то мало того, что они отключаются в релизе, так еще и в debug-е они могут влиять на время выполнения кусков кода, что может быть важно.
В дебаге много что влияет на скорость выполнения программы. Я считаю, что код с проверками (assert-ы, не assert-ы) намного лучше кода без проверок. Если у вас есть к этому какие-то возражения, приведите

> Вроде бы точно так же, как я показывал в посте.
Я все равно не понимаю как. В посте у вас ответ на другой вопрос

eao197 комментирует...

> В дебаге много что влияет на скорость выполнения программы.

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

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

> Я считаю, что код с проверками (assert-ы, не assert-ы) намного лучше кода без проверок.

Проверки должны быть частью логики. Ограничения и допущения же лучше выражать теми или иными формами контрактов. Скажем шаблон not_null в качестве типа параметра лучше, чем голый указатель с assert-ом внутри функции.

> В посте у вас ответ на другой вопрос

В посте показано как переписать первый фрагмент. Аналогичным образом и здесь можно переписать, т.к. isz нужно только для вызова UtilReportEvent. Так что можно написать что-то вроде:

const WCHAR * rgszT[] = {g_rgfmp[ifmp].WszDatabaseName(), szSeconds, szErr, ...};

А в UtilReportEvent передать _countof(rgszT).

svr комментирует...

> Так что можно написать что-то вроде:
Но тогда в массиве rgszT будет меньше элементов, чем требуется по условию

> Скажем шаблон not_null в качестве типа параметра лучше, чем голый указатель с assert-ом внутри функции.
А еще лучше вообще ссылку или std::reverence_wrapper.
Да, иногда лучше выразить что-то типами, но без завтипов слишком далеко не уедешь, и какие-то проверки все равно придется прописывать руками.

> Подход же, когда мы сперва все пускаем в дебаге, а лишь затем уже начинаем собирать в release является, по-моему, ущербным в принципе.
Ну это опять же разные подходы к разработке. Гдето применим один, где-то другой.
В серверной разработке лучше разрабатывать и отлаживать в релизе, так мы получаем синхронность поведения. Но в случае например клиентский приложений, преимуществ от разработки в релизе нету, так как не понятно для кого мы стараемся, разрабатывая в релизе (так как когда приложение ушло к пользователю, получить от него feedback уже практически невозможно, хотя в последнее время средства для получения этого фидбека развиваются лучше (таже телеметрия например, которую все не любят), поэтому ни о какой синхронности речи идти не может)

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

eao197 комментирует...

> Но тогда в массиве rgszT будет меньше элементов, чем требуется по условию

По какому условию?

> Плюс опять же, я еще раз подчеркиваю, что не настаиваю, что для проверок должны использоваться именно ассерты.

Поэтому я и говорю, что наличие или отсутствие assert-ов не говорит ни о чем. Так что наличие в коде assert-ов не есть показатель качества кода.

svr комментирует...

> По какому условию?
Посмотрите, как объявлен массив.
const WCHAR * rgszT[16];
В нем должно быть 16 элементов. Видимо, остальные элементы дозаполняются в функции

> Поэтому я и говорю, что наличие или отсутствие assert-ов не говорит ни о чем
А мне например говорит. Говорит о том, что разработчик продумывает граничные случаи алгоритмов, пользуется в некотором смысле "документацией", которая поясняет следующим читателям, что ожидать от куска кода и т.п. Плюс конечно огромная помощь при отладке кода, когда что-то пошло не так (а оно обязательно пойдет) То есть по ассертам я вижу, что автор действительно подумал над своим кодом, а не просто набросал и свалил.

Хотя я например также вижу, что не всех assert-ов достаточно, плюс возможно сами assert-ы не самая хорошая идея. И по этой причине авторитет автора падает в моих глазах. И получается та самая парадоксальная ситуация, когда автор, отлично продумав свой код, вырыл тем самым себе яму и упал в моих глазах (а уж в ваших тем более) только потому что забыл/не подумал проставить 1-2 лишние проверки, отсутствие которых к томуже обязательно скажется при дебаге кода, за что такого "забывчивого" автора будут сердечно благодорить все коллеги.

eao197 комментирует...

> В нем должно быть 16 элементов. Видимо, остальные элементы дозаполняются в функции

Нет. Эти 16 элементов -- это просто выделение "с запасом".

> Говорит о том, что разработчик продумывает граничные случаи алгоритмов

Как раз об этом ничего и не говорит. Автор рассчитывает поймать нарушение в граничном случае в debug-е, но при этом пусть в release будет жопа, там ведь ничего-то не так пойти не может.