2012-01-10

"Pure C" Ненависти Псто.

Пишу обвертку вокруг одной библиотеки, написанной на "сишечке".
  • Пяток структур и полсотни функций в каждом хидере, документирована от силы четверть. Итого -- тысячи функций, которые вроде бы умеют все, но чтобы сделать что-то, на пол-шага отличающееся от примеров, приходится достаточно долго курить, а клиентский код получается весьма обширным. В результате -- за деревьями не видно леса.   
  • Отсутствие модификаторов доступа в С -- мне как клиенту библиотеки непонятно, какие данные я могу менять напрямую, а какие -- через функции библиотеки. Можно было бы спрятать приватные данные в pimpl, но автор до этого не додумался.
  • Pure C реализации вектора/списка/хешмапы. Через void*, по другому ведь никак. Типобезопасность? Нет, не слышали. Хорошо, если комментарий есть -- "в этом списке char*, в этом -- struct foo*. В динамических языках хоть ассерт можно поставить, в С -- либо все ок, либо расстрел памяти.
  • Вау, автор додумался до идеи конструктора/деструктора. Поэтому для каждой струкутры есть
    struct lib_foo * lib_foo_new(/*параметры*/); 
    void lib_foo_free(struct lib_foo*);
    
    даже если это
    struct lib_foobar {
      int fb_foo;
      int fb_bar;
    };
    
    Соответственно, все обьекты выделяются через malloc в хипе, в том числе и члены членов членов членов главной структуры. 
  • А до виртуального деструктора автор не додумался (Или решил, что это оверхед). Поэтому при оборачивании обьекта в умный указатель приходится постоянно передавать ему делитер.
  • Привычка добавлять сокращенное имя структуры как префикс поля (те самые fb_) в примере выше. В результате -- в клиентском коде будет куча копи-пасты вида
    lib_foo_list * fill_foo(...) {
      lib_foo_list * foos = lib_foo_list_new();
      fill(foos->foo_list, ...)
      return foos;
    }
    
    lib_bar_list * fill_bar(...) {
      libbar_list * bars = lib_bar_list_new();
      fill(bars->bar_list, ...)
      return bars;
    }
    
    lib_baz_list * fill_baz(...) {
      libbaz_list * bazs = lib_baz_list_new();
      fill(bazs->baz_list, ...);
      return bazs;
    }
    
    которую не засунуть в шаблон.
  • Попытка сэкономить на копировании приводит к странному контракту функций - если функция завершилась успешно -- она владеет переданными обьектами. Если с ошибкой - то обьектами владеет клиентский код. Отсюда: 
    1. В клиенской функции нужна секция обработки ошибок. 
    2. Это не типичная для "сишечки"
      clear_foo:
        lib_foo_free(foo);
      clear_bar:
        lib_bar_free(bar);
        
      return err; 
      
      а нечто позапутанней с дополнительными return-ами или goto-ми
  • Еще запутанней с функциями, принимающими char*. Одна функция копирует переданную строку, соседняя -- ее "овладевает". Комментариев обычно нет, смотри примеры или исходный код. И да, строки мы копируем через strdup, а длину меряем через strlen.  

Комментариев нет:

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