¿Dónde cargar y almacenar configuraciones desde un archivo?

9

Creo que esta pregunta debería aplicarse a la mayoría de los programas que cargan configuraciones desde un archivo. Mi pregunta es desde el punto de vista de la programación, y realmente es cómo lidiar con la carga de configuraciones desde un archivo en términos de diferentes clases y accesibilidad. Por ejemplo:

  • Si un programa tuviera un settings.iniarchivo simple , ¿su contenido debería cargarse en un load()método de una clase, o quizás en el constructor?
  • ¿Deberían almacenarse los valores en public staticvariables, o debería haber staticmétodos para obtener y establecer propiedades?
  • ¿Qué debería suceder en caso de que el archivo no exista o no sea legible? ¿Cómo le harías saber al resto del programa que no puede obtener esas propiedades?
  • etc.

Espero preguntar esto en el lugar correcto aquí. Quería hacer la pregunta lo más agnóstica del lenguaje posible, pero me estoy centrando principalmente en lenguajes que tienen cosas como la herencia, especialmente Java y C # .NET.

Andy
fuente
1
Para .NET, es mejor usar App.config y la clase System.Configuration.ConfigurationManager para obtener la configuración
Gibson
@ Gibson Recién estoy comenzando en .NET, ¿podría vincularme a algún buen tutorial para esa clase?
Andy
Stackoverflow tiene muchas respuestas. La búsqueda rápida revela: stackoverflow.com/questions/114527/… y stackoverflow.com/questions/13043530/… También habrá mucha información sobre MSDN, comenzando aquí msdn.microsoft.com/en-us/library/ms228063(v = vs.100) .aspx y msdn.microsoft.com/en-us/library/ms184658(v=vs.110).aspx
Gibson
@ Gibson Gracias por esos enlaces. Te serán muy útiles.
Andy

Respuestas:

8

Esta es realmente una pregunta realmente importante y a menudo se hace mal, ya que no se le da suficiente importancia a pesar de que es una parte central de casi todas las aplicaciones. Aquí están mis pautas:

Su clase de configuración, que contiene todas las configuraciones, debería ser simplemente un tipo de datos antiguo, struct / class:

class Config {
    int prop1;
    float prop2;
    SubConfig subConfig;
}

No debería necesitar métodos y no debería implicar herencia (a menos que sea la única opción que tiene en su idioma para implementar un campo variante; consulte el siguiente párrafo). Puede y debe usar composición para agrupar las configuraciones en clases de configuración específicas más pequeñas (por ejemplo, subConfig arriba). Si lo hace de esta manera, será ideal pasar las pruebas unitarias y la aplicación en general, ya que tendrá dependencias mínimas.

Es probable que necesite usar tipos de variantes, en caso de que las configuraciones para diferentes configuraciones tengan una estructura heterogénea. Se acepta que necesitará colocar una conversión dinámica en algún momento cuando lea el valor para convertirlo a la clase de (sub) configuración correcta, y sin duda esto dependerá de otra configuración.

No debe ser flojo al escribir en todas las configuraciones como campos simplemente haciendo esto:

class Config {
    Dictionary<string, string> values;
};

Esto es tentador, ya que significa que puede escribir una clase de serialización generalizada que no necesita saber con qué campos se trata, pero está mal y explicaré por qué en un momento.

La serialización de la configuración se realiza en una clase completamente separada. Cualquiera sea la API o biblioteca que use para hacer esto, el cuerpo de su función de serialización debe contener entradas que básicamente equivalen a ser un mapa desde la ruta / clave en el archivo hasta el campo en el objeto. Algunos lenguajes proporcionan una buena introspección y pueden hacer esto de forma inmediata, otros tendrá que escribir explícitamente el mapeo, pero la clave es que solo debe escribir el mapeo una vez. Por ejemplo, considere este extracto que adapté de la documentación del analizador de opciones del programa c ++ boost:

struct Config {
   int opt;
} conf;
po::options_description desc("Allowed options");
desc.add_options()
    ("optimization", po::value<int>(&conf.opt)->default_value(10);

Tenga en cuenta que la última línea básicamente dice "optimización" asigna a Config :: opt y también que hay una declaración del tipo que espera. Desea que la lectura de la configuración falle si el tipo no es el esperado, si el parámetro en el archivo no es realmente flotante o int, o no existe. Es decir, debe producirse un error al leer el archivo porque el problema está en el formato / validación del archivo y debe arrojar un código de excepción / retorno e informar el problema exacto. No debe retrasar esto para más adelante en el programa. Es por eso que no debería tener la tentación de atrapar todas las Conf de estilo Diccionario como se mencionó anteriormente, lo que no fallará cuando se lea el archivo, ya que la conversión se retrasa hasta que se necesita el valor.

Debe hacer que la clase Config sea de solo lectura de alguna manera: establezca el contenido de la clase una vez cuando la cree e inicialice desde el archivo. Si necesita tener configuraciones dinámicas en su aplicación que cambien, así como constantes que no lo hacen, debe tener una clase separada para manejar las dinámicas en lugar de tratar de permitir que los bits de su clase de configuración no sean de solo lectura .

Idealmente, lee en el archivo en un lugar de su programa, es decir, solo tiene una instancia de " ConfigReader". Sin embargo, si está luchando para que la instancia de Config pase a donde la necesita, es mejor tener un segundo ConfigReader que introducir una configuración global (que supongo que es lo que el OP quiere decir con "static "), Lo que me lleva a mi siguiente punto:

Evita la canción de sirena seductora del singleton: "Te ahorraré tener que pasar esa clase de clase, todos tus constructores serán encantadores y limpios. Continúa, será muy fácil". La verdad es que con una arquitectura comprobable bien diseñada, difícilmente necesitará pasar la clase Config o partes de ella a través de tantas clases de su aplicación. Lo que encontrará, en su clase de nivel superior, su función main () o lo que sea, desentrañará la conf en valores individuales, que proporcionará a sus clases de componentes como argumentos que luego volverá a armar (dependencia manual inyección). Una configuración única / global / estática hará que las pruebas unitarias de su aplicación sean mucho más difíciles de implementar y comprender, por ejemplo, confundirá a los nuevos desarrolladores con su equipo que no sabrán que tienen que configurar el estado global para probar cosas.

Si su idioma admite propiedades, debe usarlas para este propósito. La razón es que significa que será muy fácil agregar configuraciones de configuración 'derivadas' que dependen de una o más configuraciones. p.ej

int Prop1 { get; }
int Prop2 { get; }
int Prop3 { get { return Prop1*Prop2; }

Si su idioma no admite de forma nativa el idioma de propiedad, puede tener una solución alternativa para lograr el mismo efecto, o simplemente cree una clase de contenedor que proporcione la configuración de bonificación. Si de lo contrario no puede conferir el beneficio de las propiedades, de lo contrario es una pérdida de tiempo escribir manualmente y usar captadores / establecedores simplemente con el propósito de complacer a algún dios. Estarás mejor con un campo viejo y llano.

Es posible que necesite un sistema para fusionar y tomar múltiples configuraciones de diferentes lugares en orden de precedencia. Ese orden de precedencia debe estar bien definido y entendido por todos los desarrolladores / usuarios, por ejemplo, considere el registro de Windows HKEY_CURRENT_USER / HKEY_LOCAL_MACHINE. Debe hacer este estilo funcional para que pueda mantener sus configuraciones de solo lectura, es decir:

final_conf = merge(user_conf, machine_conf)

más bien que:

conf.update(user_conf)

Finalmente, debería agregar eso, por supuesto, si el marco / lenguaje elegido proporciona sus propios mecanismos de configuración incorporados y conocidos, debería considerar los beneficios de usar eso en lugar de crear el suyo.

Entonces. Hay muchos aspectos a tener en cuenta: acéptelo bien y afectará profundamente la arquitectura de su aplicación, reduciendo errores, haciendo que las cosas sean fácilmente comprobables y obligándolo a usar un buen diseño en otros lugares.

Benedicto
fuente
+1 Gracias por la respuesta. Anteriormente, cuando he almacenado la configuración del usuario, acabo de leer un archivo como un archivo .inipara que sea legible para los humanos, pero ¿sugieres que debería serializar una clase con las variables?
Andy
.ini es fácil de analizar, y también hay muchas API que incluyen .ini como posible formato. Le sugiero que use tales API para ayudarlo a hacer el trabajo duro del análisis textual, pero que el resultado final debería ser que haya inicializado una clase POD. Uso el término serializar en el sentido general de copiar o leer en los campos de una clase desde algún formato, no la definición más específica de serializar una clase directamente a / desde binario (como se entiende típicamente para decir java.io.Serializable )
Benedicto
Ahora entiendo un poco mejor. Entonces, ¿básicamente está diciendo que tiene una clase con todos los campos / configuraciones y tiene otra para establecer los valores en esa clase desde el archivo? Entonces, ¿cómo debo obtener los valores de la primera clase en otras clases?
Andy
Caso por caso. Es posible que algunas de sus clases de nivel superior solo necesiten una referencia a toda la instancia de Config que se les haya pasado si dependen de tantos parámetros. Es posible que otras clases solo necesiten uno o dos parámetros, pero no es necesario acoplarlas al objeto Config (lo que hace que sea aún más fácil de probar), simplemente pase los pocos parámetros a través de su aplicación. Como mencioné en mi respuesta, si construye con una arquitectura orientable / DI comprobable, obtener los valores donde los necesita generalmente no será difícil. Utilice el acceso global bajo su propio riesgo.
Benedicto
Entonces, ¿cómo sugeriría que el objeto de configuración se pase a las clases si no es necesario involucrar la herencia? ¿A través de un constructor? Entonces, en el método principal del programa, ¿se inicia la clase de lector que almacena valores en la clase Config, luego el método principal pasa el objeto Config o variables individuales a otras clases?
Andy
4

En general (en mi opinión), es mejor dejar que la aplicación se ocupe de cómo se almacena la configuración y pasarla a sus módulos. Esto permite flexibilidad en cómo se guardan las configuraciones para que pueda apuntar a archivos o servicios web o bases de datos o ...

Además, pone la carga de "lo que sucede cuando las cosas fallan" en la aplicación, quién sabe mejor qué significa esa falla.

Y hace que sea mucho más fácil realizar pruebas unitarias cuando solo puede pasar un objeto de configuración en lugar de tener que tocar el sistema de archivos o tratar los problemas de concurrencia introducidos por el acceso estático.

Telastyn
fuente
Gracias por su respuesta, pero no lo entiendo del todo. Estoy hablando de cuando escribo la aplicación, por lo que no hay nada que lidiar con la configuración y, por lo tanto, como programador, sé lo que significa una falla.
Andy
@andy: claro, pero si los módulos acceden a la configuración directamente, no saben en qué contexto están trabajando, por lo que no pueden determinar cómo manejar los problemas de configuración. Si la aplicación está cargando, puede resolver cualquier problema, ya que conoce el contexto. Algunas aplicaciones pueden querer
cambiar
Solo para aclarar aquí, por módulos, ¿se refiere a clases o extensiones reales de un programa? Porque estoy preguntando dónde es mejor almacenar la configuración dentro del código del programa principal y cómo permitir que otras clases accedan a la configuración. Entonces quiero cargar un archivo de configuración y luego almacenar la configuración en algún lugar / de alguna manera para no tener que seguir refiriéndome al archivo.
Andy
0

Si está utilizando .NET para programar las clases, tiene diferentes opciones como Recursos, web.config o incluso un archivo personalizado.

Si usa Resources o web.config, los datos se almacenan realmente en un archivo XML, pero la carga es más rápida.

Recuperar los datos de estos archivos y almacenarlos en otra ubicación será como un doble uso de la memoria, ya que estos se cargan en la memoria de forma predeterminada.

Para cualquier otro archivo o lenguaje de programación, la respuesta anterior de Benedict funcionará.

Kiran
fuente
Gracias por su respuesta y perdón por la lenta respuesta. Usar los recursos sería una buena opción, pero me acabo de dar cuenta de que probablemente no sea la mejor opción para mí, ya que planeo desarrollar el mismo programa en otros idiomas, por lo que prefiero tener un archivo XML o JSON, pero leerlo en mi propia clase para que el mismo archivo pueda leerse en otros lenguajes de programación.
Andy