Что входит в основную функцию? - PullRequest
20 голосов
/ 12 января 2011

Я ищу совет о том, что входит в основную функцию программы на c ++.В настоящее время я думаю, что возможны два подхода.(Хотя «поля» этих подходов могут быть произвольно близки друг к другу)

1: написать класс «Master», который получает параметры, переданные главной функции, и обрабатывает всю программу в этом «Master»-класс (Конечно, вы также используете другие классы).Поэтому основная функция будет сведена к минимуму строк.

#include "MasterClass.h"
int main(int args, char* argv[])
{
MasterClass MC(args, argv);
}

2: Напишите «завершенную» программу в основной функции, используя, конечно же, определенные пользователем объекты!Однако также задействованы глобальные функции, и основная функция может стать несколько большей.

Я ищу некоторые общие рекомендации о том, как написать основную функцию программы на c ++.Я столкнулся с этой проблемой, пытаясь написать некоторый модульный тест для первого подхода, который немного сложен, так как большинство методов являются закрытыми.

Ответы [ 10 ]

23 голосов
/ 12 января 2011

Зачем тебе один мастер-класс? Какова его единичная зона ответственности?

"Мастер" или "прикладные" классы, как правило, становятся большими каплями, которые делают слишком много разных вещей. В конечном счете, какой смысл? Что он тебе купил?

Почему бы не использовать функцию main для обеспечения этой main функциональности? Определите жизненный цикл приложения высокого уровня в main. Он принимает некоторые аргументы командной строки и анализирует их (предпочтительно, делегируя это другой функции или классу), а затем вызывает некоторую функциональность установки, а затем он может войти в какой-то основной цикл перед выполнением некоторой очистки. В общем, это может дать вам основную функцию из 10-15 строк, но, вероятно, не более того. Он должен делегировать другим классам и функциям как можно больше. поэтому main сам по себе короток и сладок, но все же имеет цель.

Помещение этого вида потока сверхвысокого уровня в main означает, что его легко найти, поскольку в любом случае main является вашей отправной точкой. Это то место, где вы начнете искать, если хотите понять код. Поэтому вставьте в него то, что читатель хочет знать, пытаясь понять код.

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

7 голосов
/ 12 января 2011

Вы описываете два «экстремальных» подхода, ни один из которых не подходит мне.Ни один класс Бога , ни единственная функция Бога не являются правильным способом реализации любой нетривиальной программы, предназначенной для реального использования.

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

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

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

Стоит подуматьо том, как структурировать ваши программы в целом, а не только в контексте main().Основная идея состоит в том, чтобы разбить его на «куски» (классы и методы), которые

  • достаточно малы, чтобы их можно было легко понять, протестировать и поддерживать,
  • логически связными.
3 голосов
/ 12 января 2011

Я бы сделал анализ аргументов процесса в основной подпрограмме, а затем создал бы экземпляр класса, передав более читаемые параметры, чем argc и argv.

2 голосов
/ 12 января 2011

Обычно я вызываю основную функцию (с той же сигнатурой) в пространстве имен моего приложения:

namespace current_application {
    int main( int argc, char **argv ) {
        // ...
    }
}
int main( int argc, char **argv ) {
    return current_application::main( argc, argv );
}

И затем я обычно использую свой фактический main (тот, что находится в пространстве имен), чтобы инициализировать приложения мудрых вещей:

  • установка локалей на стандартном вводе / выводе / ошибке)

  • анализ параметров приложения

  • создание экземпляра объекта моего основного класса, если он есть (эквивалент чего-то вроде QApplication)

  • вызов основной функции, если имеется (эквивалент чего-то вроде QApplication::run)

и обычно предпочитают добавить туда блок try catch, чтобы вывести больше отладочной информации в случае сбоя.


Однако все это очень субъективно; это часть вашего стиля кодирования.

2 голосов
/ 12 января 2011

Во-первых: я редко использую C ++, но я предполагаю, что это не является проблемой, специфичной для конкретного языка.
Что ж, я думаю, это сводится к некоторым проблемам практичности.Лично я склонен использовать макет # 1, но не помещаю процедуры синтаксического анализа командной строки в MasterClass .Для меня это явно относится к основному. MasterClass должен получить проанализированные аргументы (целые числа, FileStreams, что угодно).

2 голосов
/ 12 января 2011

Ваш первый подход очень распространен, хотя класс, как правило, называется «Приложение» (или, по крайней мере, содержит слово «Приложение»), поэтому сделайте это.

1 голос
/ 12 января 2011

Если исключение не имеет обработчика, то не определено, вызывается ли деструктор локальных объектов раньше std::terminate.

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

Обычно это все, что я помещаю в main, который в противном случае просто вызывает cppMain ...; -)

Приветствия & hth.,

0 голосов
/ 12 января 2011

Я предпочитаю подход IOC (Inversion of Control) к моим программам.

Поэтому мой «main» использует аргументы команды для определения «параметров» и «конфигурации» программы. Под опциями я подразумеваю интерпретацию определенных флагов, переданных в командной строке, а под конфигурацией я подразумеваю загрузку файлов конфигурации.

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

0 голосов
/ 12 января 2011

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

class MasterClass {
public:
static MasterClass* CreateInstance( int argc, char **argv );
    // ...
}

int main(int argc, char** argv)
{
    try
    {
         MasterClass mc = MC::CreateInstance(argc, argv);
    }
    catch(...)
    {
        // ...
    }
}

Это также имеет то преимущество, что любая обработка, которая не имеет ничего общего с реальной логикой программы, такой как чтение среды и т. Д., Не должна быть помещена в MasterClass. Они могут идти в main (). Хорошим примером является задача надстройки сервера для системы Lotus Domino. Здесь задача должна выполняться только тогда, когда ей передается управление планировщиком Domino. Здесь главное, вероятно, будет выглядеть так:

STATUS LNPUBLIC AddInMain(HMODULE hModule, int argc, char far *argv[])
{
     MasterClass mc = MC::CreateInstance(argc, argv);
     while(/* wait for scheduler to give control */)
     {
          STATUS s = mc->RunForTimeSlice();
          if (s != SUCCESS)
               break;
     }
     // clean up
}

Таким образом, вся логика взаимодействия с планировщиком в основном, и остальная часть программы не должна обрабатывать ее.

0 голосов
/ 12 января 2011

Обычно я выполняю необходимые действия по настройке платформы и затем делегирую объекту. Есть небольшое преимущество объекта над функцией, но объекты могут наследовать от интерфейсов, что хорошо, если у вас есть независимые от платформы библиотеки, которые используют реализацию интерфейса для обратных вызовов.

...