January 20th, 2021

Самый страшный саботаж в программировании

Программирование полезно тем, что оно часто имеет дело не с физическими объектами, а с данными. То есть если токарь запорет деталь, то надо будет с трудом нахреначивать материал сверху или всё переделывать с нуля, то программисту достаточно вытащить рабочую версию из системы управления версиями. При этом чуть менее, чем всегда, хранить все предыдущие версии дёшево (на порядки дешевле восстановления с нуля), и переключение между версиями не занимает много времени.

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

Отлично? Да более, чем отлично. Можно выпускать сложные, но всё ещё качественные программы (ха-ха, но это справедливо даже сейчас в 2020, когда чуть менее, чем каждая программа или сайт пользуется тем, что обновление криво добавленной фичи можно добавить через неделю, и ничего уже без такого режима как "релиз как унитаз" не работает), которые не тормозят у пользователя из-за множества проверок внутри, хотя эти проверки (что очень важно в случае невнёмательных программистов) работают на том же (до оптимизации) коде, который можно отправлять пользователю.

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

warning: comparison of unsigned expression >= 0 is always true


Как мерзко, ажтрисёт. Да, компилятор проверяет чуть больше, чем нужно, чтобы точно отловить ошибку. Но вот какой-то простой и наивный assert(i >= 0 && i < len); будет работать с начала своего написания до момента, когда какой-то человек заменит тип i с size_t на int. А компилятор всё время будет высирать и высирать это предупреждение про то, что i>>=0 по определению, что просто является временным явлением. После замены на более тихое с точки зрения компилятора assert(i < len); в будущем могут изменить как тип переменной, так и скопировать код туда, где i:int или изменить typedef size_t mylen_t, что изменит фиг знает в каком по размере множестве файлов с кодом. И всё.

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

Более того, assert(i >= 0 && i < len); - это унифицированный код, который формально подходит как знаковым переменным, так и беззнаковым. Если везде писать его, нельзя будет ошибиться. А если писать assert(i >= 0 && i < len); в одних местах, а assert(i < len); в других, то когда-то можно перепутать. Компилятор вынуждает программиста раздумывать над конкретными случаями и глючить вместо того, чтобы самому позаботиться о проверке и не приводить к ошибкам из-за невнимательности.

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