Как структурировать сложные проекты в 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; и посмотреть, как они его организуют.