]> Sergey Matveev's repositories - stargrave-blog.git/commitdiff
Что ненавижу в Си
authorSergey Matveev <stargrave@stargrave.org>
Thu, 24 Dec 2020 09:51:21 +0000 (12:51 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Thu, 24 Dec 2020 09:51:21 +0000 (12:51 +0300)
1) Когда записывают if/for без фигурных скобок. Да, это 1-2 строки
экономит, но когда надо вставить ещё какую-нибудь команду, например
print для отладки, то начинаются пляски с добавлением. А ещё не всегда
замечаешь что фигурных скобок нет, вставляешь print, и только он после
if срабатывает, а штатная, следующая после него команда, уже находится
вне if.

2) В целом ненавижу как возвращается успех/не успех выполнения функи.
Такое впечатление, что половина людей/фунок считают int=0 успехом
выполнения, а половина int≠0, и ещё меньше нуля отдельная категория. И,
как правило, по названию функи не поймёшь что от неё ожидать. Вот если
cmp, то скорее всего 0 означает равенство. Вот только фиг -- есть и
исключения. Если функа называется is_equal, то точно стоит ожидать что
возвращает 1 при равенстве, чтобы можно было записать if(is_equal), но
это редкость встретить такие говорящие названия. Ну и лично я, когда
вижу, восклицательные знаки, то это ассоциируется с чем-то негативным,
отрицательным, хотя 100500 мест уже видел где if (!...) это проверка на
успешность выполнения. Плюс бесит отсутствие ЯВНЫХ указаний что
ожидается от проверки (==0, >-1, !=0, и т.д.) -- но это касается также и
кода для Python, где видел тьму ошибок совершаемых из-за этого. А ведь в
Unix return code = 0 это успех, хотя в Сях оно eval-ится в false.

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

Если какие-то функи возвращают NULL, то это значит что не успех. А в чём
ошибка? Или глобальные переменные смотри или в, указанную отдельным
аргументом, переменную с результатом смотри. Или >0 -- успех, ==0 --
такая-то ошибка, <0 -- другие такие-то ошибки.

После этого особо стал ценить задумку с error типом в Go. Вообще в Сях я
по сути пишу как на Go. Практически все функи возвращают структуру
которая как-бы является error-ом, внутри которого есть .code возможно
говорящий что ошибки не было. Все функи возвращают error этот, почти без
исключений. Это и возможность кучу дополнительной информации передать
сопутствующей ошибки. Код везде становится очень простым из серии:
err=MyFunc, if(HasErr(err)).

Ещё я всюду и везде делаю не просто указатели на массивы, но и рядом с
ними size_t размер данных. А в идеале вообще можно и нужно бы было
делать struct из указателя и размера, по сути делая недо-slice из Go.
Для входных read-only данных это const size_t, а для выходных это
size_t*, куда записывается кол-во данных записанных, или возвращается
ошибки и записывается сколько данных в dst нужно иметь, но не хватает.
Особенно видя OpenSSL код, я ужасаюсь кучей потенциальных проблем
которые может вызвать всё это отсутствие указания явных размеров и как
нужно доверять разработчику. Собственно, понимаю что с таким кодом
ненавидеть Си -- благое дело и здоровая реакция.

Отдельная боль это конечно очистка данных при выходе из функи, при
ошибках. Если в Go хотя бы есть garbage collector, то в Си нужно не
забывать освобождать память. В Go хотя бы есть defer или, как минимум,
анонимные функции. В Сях нужно очень аккуратным быть. Я делаю
int needCleanup = 0 переменную, а дальше с каждым действием требующим
"очистки"/освобождения, её инкрементирую (if (malloced == NULL);
needCleanup++). А при очистке декрементирую (free(...); needCleanup--) и
вставляю assert(needCleanup == 0) перед каждым return-ом. Костыль,
недоверие к самому же себе, но этот подход не раз уже окупился у меня на
практике.


No differences found