Как структурировать сложные проекты в C?

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

Я всегда использовал парадигму OO в Java и PHP, и теперь, когда я хочу учиться C, я боюсь, что я мог бы неправильно структурировать свои приложения. Я не понимаю, на каких руководящих принципах следует соблюдать модульность, развязку и сухость с процедурным языком.

Есть ли у вас какие-либо показания? Я не смог найти какую-либо платформу приложений для C, даже если я не использую фреймворки. Я всегда находил хорошие идеи, просматривая их код.

Ответ 1

Ключ - это модульность. Это проще в проектировании, внедрении, компиляции и обслуживании.

  • Определите модули в своем приложении, например классы в приложении OO.
  • Отдельный интерфейс и реализация для каждого модуля, вставляемый в интерфейс только то, что требуется другим модулям. Помните, что в C нет пространства имен, поэтому вам нужно сделать все в своих интерфейсах уникальным (например, с префиксом).
  • Скрыть глобальные переменные в реализации и использовать функции доступа для чтения/записи.
  • Не думайте с точки зрения наследования, а с точки зрения композиции. Как правило, не пытайтесь имитировать С++ в C, это было бы очень трудно читать и поддерживать.

Если у вас есть время для обучения, посмотрите, как структурировано приложение Ada, с его обязательным package (интерфейсом модуля) и package body (реализация модуля).

Это для кодирования.

Для поддержания (помните, что код один раз, но вы поддерживаете несколько раз) я предлагаю задокументировать ваш код; Doxygen - отличный выбор для меня. Я предлагаю также создать сильную систему регрессионных тестов, которая позволяет вам реорганизовать.

Ответ 2

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

Одной из основ надежного проектирования системы является инкапсуляция реализации за интерфейсом. FILE* и функции, которые с ним работают (fopen(), fread() и т.д.), являются хорошим примером того, как инкапсуляция может применяться в C для установления интерфейсов. (Конечно, поскольку C не имеет спецификаторов доступа, вы не можете обеспечить, чтобы никто не заглядывал внутрь struct FILE, но только мазохист сделал бы это.)

При необходимости полиморфное поведение может быть выполнено в C, используя таблицы указателей функций. Да, синтаксис уродлив, но эффект такой же, как и виртуальные функции:

struct IAnimal {
    int (*eat)(int food);
    int (*sleep)(int secs);
};

/* "Subclass"/"implement" IAnimal, relying on C guaranteed equivalence
 * of memory layouts */
struct Cat {
    struct IAnimal _base;
    int (*meow)(void);
};

int cat_eat(int food) { ... }
int cat_sleep(int secs) { ... }
int cat_meow(void) { ... }

/* "Constructor" */
struct Cat* CreateACat(void) {
    struct Cat* x = (Cat*) malloc(sizeof (struct Cat));
    x->_base.eat = cat_eat;
    x->_base.sleep = cat_sleep;
    x->meow = cat_meow;
}

struct IAnimal* pa = CreateACat();
pa->eat(42);                       /* Calls cat_eat() */

((struct Cat*) pa)->meow();        /* "Downcast" */

Ответ 3

Все хорошие ответы.

Я бы добавил только "минимизировать структуру данных". Это может быть даже проще в C, потому что если С++ является "C с классами", OOP пытается побудить вас принять каждое существительное/глагол в вашей голове и превратить его в класс/метод. Это может быть очень расточительным.

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

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

Таким образом, вы получаете огромное количество кода, который хорошо читается и просто тратит 90% времени на управление объектами.

Все это делается во имя "хорошей практики программирования" и "эффективности".

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

Ответ 4

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

Ответ 5

Если вы знаете, как структурировать свой код на Java или С++, вы можете следовать тем же принципам с помощью кода C. Единственное различие заключается в том, что у вас нет компилятора на вашей стороне, и вам нужно сделать все, что нужно, вручную.

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

Вы не можете иметь классы с C, но вы можете легко реализовать "Абстрактные типы данных". Вы создаете файлы .C и .H для каждого абстрактного типа данных. Если вы предпочитаете, у вас могут быть два файла заголовка, один открытый и один закрытый. Идея состоит в том, что все структуры, константы и функции, которые необходимо экспортировать, попадают в общий заголовочный файл.

Ваши инструменты также очень важны. Полезным инструментом для C является lint, который может помочь вам найти неприятные запахи в вашем коде. Другим инструментом, который вы можете использовать, является Doxygen, который может помочь вам создать документацию.

Ответ 6

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

Чтобы упростить сложное приложение, я использую Разделить и покорить.

Ответ 7

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

Трюк, который я использовал, чтобы помочь инкапсулировать методы "private" в C, - это не включать их прототипы в файл ".h".

Ответ 8

Я бы предложил прочитать учебник на C/С++ в качестве первого шага. Например, C Primer Plus является хорошей ссылкой. Просматривая примеры, вы дадите идею и идею о том, как сопоставить свой Java-код с более понятным языком, например C.

Ответ 9

Я бы посоветовал вам проверить код любого популярного проекта с открытым исходным кодом C, например... hmm... Linux kernel или Git; и посмотреть, как они его организуют.