2011-01-12 14 views
16

Estoy buscando un consejo de mejores prácticas de lo que entra en la función principal de un programa que utiliza C++. Actualmente creo que dos enfoques son posibles. (Aunque los "márgenes" de esos enfoques pueden ser arbitrariamente cercanos)¿Qué entra en la función principal?

1: Escriba una clase "Master" que recibe los parámetros pasados ​​a la función principal y maneja el programa completo en ese "Master" - clase (por supuesto, también hace uso de otras clases). Por lo tanto, la función principal se reduciría a un mínimo de líneas.

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

2: ¡Escriba el programa "completo" en la función principal haciendo uso de objetos definidos por el usuario, por supuesto! Sin embargo, también hay funciones globales involucradas y la función principal puede ser algo grande.

Estoy buscando algunas pautas generales sobre cómo escribir la función principal de un programa en C++. Me encontré con este problema al tratar de escribir algunas pruebas de unidad para el primer enfoque, lo cual es un poco difícil ya que la mayoría de los métodos son privados.

+0

Normalmente se escribe argc en lugar de args. argc siendo un recuento del número de argumentos – Will

Respuesta

23

¿Por qué tendrías una clase magistral? ¿Cuál es su área de responsabilidad solo?

Las clases "maestra" o de "aplicación" tienden a convertirse en grandes manchas que hacen demasiadas cosas diferentes. En definitiva, ¿cuál es el punto? ¿Qué te compró?

¿Por qué no utilizar la función principal para proporcionar esta funcionalidad principal? Defina el ciclo de vida de la aplicación de alto nivel en main. Toma algunos argumentos de línea de comandos y los analiza (preferiblemente delegando esto a otra función o clase), y luego llama a alguna funcionalidad de configuración, y luego puede ingresar algún tipo de bucle principal, antes de hacer una limpieza. Con todo, esto podría darle una función principal de tal vez 10-15 líneas, pero probablemente no más que eso. Debe delegar a otras clases y funciones tanto como sea posible. así que main se mantiene corto y dulce, pero todavía tiene un propósito.

Poner este tipo de flujo de super alto nivel en main significa que es fácil de encontrar, porque main es su punto de partida de todos modos. Es donde vas a empezar a buscar si quieres entender el código. Así que ponga en él lo que un lector desea saber al tratar de entender el código.

Claro, podría tomar todo eso y ponerlo en una "clase principal", y no habría ganado absolutamente nada, aparte de satisfacer a todos los ludditas de Java que sienten que "todo debe estar en una clase" ".

+1

+1 si solo para el análisis de argumentos. Si uno desea escribir un código "modular", entonces uno debe desacoplar el análisis sintáctico (una interfaz entre otras) del código real, y usar una estructura apropiada para reunir todas esas "configuraciones". Tenga en cuenta que el análisis de argumentos también podría abarcar el uso de variables de entorno, ya que la mayoría de las veces se utilizan para configurar valores predeterminados. Entonces es la configuración trivial/loop hasta la vista de alto nivel done/cleanup. –

+0

"En definitiva, ¿cuál es el punto?" Aunque no se muestra aquí, puede proporcionar inyección de dependencia. En lugar de tener un código que escribe en 'std :: cout', por ejemplo, la clase' Master' ** asocia-con-a ** la corriente 'standardOutput', un 'std :: ostream'. La función 'main()' crea un objeto 'Master' con' std :: cout' para esa asociación, pero las pruebas unitarias pueden usar 'std :: ostringstream'. Luego es fácil probar los requisitos de comportamiento de la prueba, como "si la línea de comandos es defectuosa, el programa debe escribir un mensaje de error en su flujo de salida de eror estándar". – Raedwald

+0

@Raedwald: ¿por qué necesitas una clase magistral para eso? ¿Por qué no 'main' simplemente crea el' std :: ostream' apropiado y luego lo pasa a todas las clases y funciones que delegue? No necesita la clase maestra para eso. – jalf

2

Su primer enfoque es muy común, aunque la clase tiende a llamarse 'Aplicación' (o al menos contener la palabra 'Aplicación') así que hágalo.

3

Haría el análisis de los argumentos del proceso en la rutina principal, luego crearía una instancia de clase pasando más parámetros legibles que argc y argv.

6

Usted está describiendo dos enfoques "extremos", ninguno de los cuales me parece correcto. Ni tener un solo God Class, ni tener una única función de Dios es la forma correcta de implementar cualquier programa no trivial destinado para uso real.

Puede ser bien tener una sola llamada a MasterClass dentro de su main() (aunque yo preferiría funcionalidad de partición mejor, por ejemplo, hacer cualquier procesamiento específico de línea de comandos en main(), para desacoplar MasterClass de los detalles de los parámetros de línea de comandos). Sin embargo, si esa clase es difícil de probar, es una señal de un problema de diseño, y generalmente la solución es delegar parte o la totalidad de su funcionalidad comprobable a otras clases donde se puede probar fácilmente en unidades separadas, a través de interfaces públicas. .

Su segundo enfoque puede volver a ser problemático para la prueba unitaria, por lo que debe esforzarse por extract methods (y preferiblemente move them into a separate class) para realizar pruebas de unidades de grano fino.

El punto ideal en el que desea estar es, por lo tanto, entre los dos extremos, restringido por el requisito de hacer que su código sea comprobable.

Vale la pena pensar en cómo estructurar sus programas en general, no solo en el contexto de main(). La idea básica es dividir en "trozos" (clases y métodos) que son

  • lo suficientemente pequeño como para ser entendido, probado y de fácil mantenimiento,
  • lógicamente cohesiva.
+1

No estoy de acuerdo. Tiene que haber un punto de entrada, ya sea una función de Dios o una clase de Dios, tiene que haber al menos uno. – Puppy

+2

@DeadMG, por supuesto. Pero citando el OP: * "escribir un examen unitario para el primer acercamiento [...] es un poco difícil ya que la mayoría de los métodos son privados". Esto lo interpreto para que su clase 'Maestro' sea una Clase de Dios, no delegando nada a ninguna parte. –

+0

Oh, veo lo que quieres decir allí. Sí, estoy completamente de acuerdo. – Puppy

0

Normalmente, realizo la acción de configuración específica de la plataforma necesaria y luego la delego a un objeto. Hay pocas ventajas de un objeto sobre una función, pero los objetos pueden heredar de las interfaces, lo que es bueno si se tiene, por ejemplo, bibliotecas independientes de la plataforma que utilizan implementación de interfaz para devoluciones de llamadas.

2

En primer lugar: rara vez uso C++, pero supongo que esto no es realmente un problema específico del idioma.
Bueno, supongo que esto se reduce al gusto, aparte de algunos problemas de practicidad. Personalmente tiendo a usar el diseño # 1, pero no coloque las rutinas de análisis de línea de comandos en MasterClass. Para mí, eso claramente pertenece al principal. La MasterClass debe obtener los argumentos analizados (enteros, FileStreams, lo que sea).

2

Por lo general llama a una función principal (un poco con la misma firma) en el espacio de nombres de mi solicitud:

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

Y entonces por lo general utiliza mi real principal (el que está en el espacio de nombres) para inicializar sabia aplicación cosas :

  • conjunto locales en entrada/salida/error estándar)

  • de análisis sintáctico Applica parámetros ción

  • crear una instancia de objeto de mi clase principal, si está presente (el equivalente a algo así como QApplication)

  • llamada de la función principal, si está presente (el equivalente a algo así como QApplication::run)

y generalmente prefieren agregar un bloque trycatch allí para imprimir más información de depuración en caso de falla.


Todo esto es muy subjetivo, sin embargo; es parte de tu estilo de codificación.

+0

No veo lo que esto te da. Excepto los programadores de mantenimiento confusos. :) –

+0

@John Dibling: ¿por qué? – peoro

+1

Porque está intentando hacer algo Java-ish en C++. Este tipo de arquitectura, donde simplemente delgas 'main()' a una función libre dentro de un espacio de nombres, no * te * da nada, y es inesperado. ¿Qué hace el espacio de nombre 'main' para usted que no lo hace el espacio de nombre? –

0

El ejemplo que acaba de presentar mueve la función principal dentro de un espacio de nombres. No veo la ventaja aquí. Un enfoque de medio camino con una ligera tendencia hacia el modelo de Master Class funciona para mí. El objeto Master Class normalmente sería enorme y mejor creado en el montón. Tengo la función principal de crear el objeto y manejar cualquier error que pueda ocurrir aquí.

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

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

Esto también tiene la ventaja de que cualquier tratamiento que no tiene nada que ver con la lógica real del programa, como por ejemplo, la lectura del medio ambiente, etc., no tiene que ser puesto en la Masterclass. Pueden ir en main(). Un buen ejemplo es una tarea de complemento de servidor para el sistema Lotus Domino. Aquí la tarea solo debería ejecutarse cuando la tarea del planificador de Domino le transfiera el control. Aquí el principal será, probablemente, ver cómo el siguiente:

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 
} 

Toda la lógica para interactuar con el programador es así en principal y el resto del programa no tiene que manejar cualquiera de ella.

0

Si una excepción no tiene un controlador, no se especifica si se invocan destructores de objetos locales antes del std::terminate.

Si desea que el comportamiento sea predecible, entonces main es un buen lugar para tener un manejador de excepción superior, con algún tipo de informe.

Por lo general, eso es todo lo que puse en main, que de otro modo sólo llama a un cppMain ... ;-)

Saludos & HTH.,

+1

Otro truco es que, en lugar de tener objetos en alcance global, puede tener punteros a alcance global, y luego construye los objetos dentro de main (y así controla el orden de construcción) y configura los punteros para señalar a los locales y luego llama a la función principal "real". De ahí la construcción determinista y la destrucción y orden de los mismos :) –

0

prefiero una COI (Inversión de Control) acercamiento a mi programas.

Por lo tanto, mi "principal" utiliza los argumentos del comando para determinar las "opciones" y la "configuración" del programa. Por opciones me refiero a interpretar ciertas banderas pasadas en la línea de comando y por configuración me refiero a la carga de archivos de configuración.

El objeto que utiliza para cargar los archivos de configuración tiene un comando para "ejecutar", pero lo que se ejecuta (en todo caso) también depende de los argumentos de la línea de comandos.

Cuestiones relacionadas