Dove caricare e memorizzare le impostazioni da un file?


9

Penso che questa domanda dovrebbe applicarsi alla maggior parte dei programmi che caricano le impostazioni da un file. La mia domanda è dal punto di vista della programmazione ed è davvero come gestire il caricamento delle impostazioni da un file in termini di classi e accessibilità diverse. Per esempio:

  • Se un programma avesse un settings.inifile semplice , il suo contenuto dovrebbe essere caricato in un load()metodo di una classe, o forse il costruttore?
  • I valori dovrebbero essere memorizzati in public staticvariabili o dovrebbero esserci staticmetodi per ottenere e impostare le proprietà?
  • Cosa dovrebbe accadere nel caso in cui il file non esistesse o non fosse leggibile? Come faresti a sapere che il resto del programma non è in grado di ottenere quelle proprietà?
  • eccetera.

Spero di chiederlo nel posto giusto qui. Volevo rendere la domanda il più agnostica del linguaggio possibile, ma mi sto concentrando principalmente su linguaggi che hanno cose come l'ereditarietà, specialmente Java e C # .NET.


1
Per .NET, è meglio usare App.config e la classe System.Configuration.ConfigurationManager per ottenere le impostazioni
Gibson

@Gibson Ho appena iniziato in .NET, quindi potresti collegarmi a qualche buon tutorial per quella classe?
Andy,

Stackoverflow ha molte risposte su di esso. La ricerca rapida rivela: stackoverflow.com/questions/114527/… e stackoverflow.com/questions/13043530/… Ci saranno anche molte informazioni su MSDN, a partire da msdn.microsoft.com/en-us/library/ms228063(v = vs.100) .aspx e msdn.microsoft.com/en-us/library/ms184658(v=vs.110).aspx
Gibson

@Gibson Grazie per quei link. Saranno molto utili.
Andy,

Risposte:


8

Questa è in realtà una domanda molto importante e spesso viene fatta in modo errato in quanto non viene data abbastanza importanza anche se è una parte fondamentale di praticamente ogni applicazione. Ecco le mie linee guida:

La tua classe di configurazione, che contiene tutte le impostazioni dovrebbe essere solo un semplice vecchio tipo di dati, struct / class:

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

Non dovrebbe avere bisogno di metodi e non dovrebbe comportare l'ereditarietà (a meno che non sia l'unica scelta che hai nella tua lingua per implementare un campo variante - vedi paragrafo successivo). Può e deve usare la composizione per raggruppare le impostazioni in classi di configurazione specifiche più piccole (ad es. SubConfig sopra). Se lo fai in questo modo, sarà l'ideale passare in unit test e l'applicazione in generale poiché avrà dipendenze minime.

Probabilmente dovrai utilizzare tipi di varianti, nel caso in cui le configurazioni per configurazioni diverse siano eterogenee nella struttura. È accettato che dovrai inserire un cast dinamico ad un certo punto quando leggi il valore per lanciarlo nella classe di (sotto) configurazione corretta, e senza dubbio questo dipenderà da un'altra impostazione di configurazione.

Non dovresti essere pigro nel digitare tutte le impostazioni come campi semplicemente facendo questo:

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

Questo è allettante perché significa che puoi scrivere una classe di serializzazione generalizzata che non ha bisogno di sapere con quali campi tratta, ma è sbagliata e ti spiego perché tra un momento.

La serializzazione della configurazione viene eseguita in una classe completamente separata. Qualunque sia l'API o la libreria utilizzata per eseguire questa operazione, il corpo della funzione di serializzazione dovrebbe contenere voci che sostanzialmente equivalgono a essere una mappa dal percorso / chiave nel file al campo sull'oggetto. Alcune lingue forniscono una buona introspezione e possono farlo per te immediatamente, altre dovresti scrivere esplicitamente la mappatura, ma la cosa fondamentale è che dovresti scrivere la mappatura una sola volta. Ad esempio, considera questo estratto che ho adattato dalla documentazione del parser delle opzioni del programma boost c ++:

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

Nota che l'ultima riga in pratica dice che "ottimizzazione" è mappata su Config :: opt e che esiste anche una dichiarazione del tipo che ti aspetti. Volete che la lettura della configurazione fallisca se il tipo non è quello che vi aspettate, se il parametro nel file non è realmente un float o un int, o non esiste. Vale a dire che l'errore dovrebbe verificarsi quando si legge il file perché il problema riguarda il formato / convalida del file e si dovrebbe lanciare un codice di eccezione / ritorno e segnalare il problema esatto. Non dovresti ritardare questo a più tardi nel programma. Ecco perché non dovresti essere tentato di catturare tutte le Conf stile dizionario come menzionato sopra che non falliranno quando il file viene letto - poiché il cast viene ritardato fino a quando non è necessario il valore.

Dovresti rendere la classe Config di sola lettura in qualche modo - impostando il contenuto della classe una volta quando la crei e inizializzi dal file. Se hai bisogno di avere impostazioni dinamiche nella tua applicazione che cambiano, così come quelle costanti che non lo fanno, dovresti avere una classe separata per gestire quelle dinamiche piuttosto che cercare di permettere ai bit della tua classe di configurazione di non essere di sola lettura .

Idealmente, leggi il file in un punto del programma, ad esempio hai solo un'istanza di un " ConfigReader". Tuttavia, se stai lottando per far passare l'istanza di Config nel punto in cui ti serve, è meglio avere un secondo ConfigReader piuttosto che introdurre una configurazione globale (che suppongo sia ciò che l'OP intende con "statico" "), che mi porta al prossimo punto:

Evita la seducente canzone della sirena del singleton: "Ti risparmierò a passare quella classe in classe, tutti i tuoi costruttori saranno adorabili e puliti. Dai, sarà così facile." La verità è con un'architettura testabile ben progettata che difficilmente sarà necessario passare la classe Config, o parti di essa attraverso quelle classi dell'applicazione. Quello che troverai, nella tua classe di livello superiore, la tua funzione main () o qualsiasi altra cosa, spiegherai la conf in singoli valori, che fornirai alle tue classi di componenti come argomenti che poi rimetterai insieme (dipendenza manuale iniezione). Una configurazione singleton / globale / statica renderà molto più difficile l'implementazione e la comprensione dell'unità testare la tua applicazione, ad esempio confonderà i nuovi sviluppatori del tuo team che non sapranno che devono impostare lo stato globale per testare le cose.

Se la tua lingua supporta le proprietà, dovresti usarle per questo scopo. Il motivo è che sarà molto semplice aggiungere impostazioni di configurazione "derivate" che dipendono da una o più altre impostazioni. per esempio

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

Se la tua lingua non supporta in modo nativo il linguaggio delle proprietà, potrebbe avere una soluzione alternativa per ottenere lo stesso effetto, oppure puoi semplicemente creare una classe wrapper che fornisca le impostazioni del bonus. Se non puoi altrimenti conferire il vantaggio delle proprietà, è altrimenti una perdita di tempo scrivere manualmente e usare getter / setter semplicemente allo scopo di far piacere a qualche dio-dio. Starai meglio con un semplice vecchio campo.

Potrebbe essere necessario un sistema per unire e prendere più configurazioni da luoghi diversi in ordine di precedenza. Tale ordine di precedenza deve essere ben definito e compreso da tutti gli sviluppatori / utenti, ad esempio prendere in considerazione il registro di Windows HKEY_CURRENT_USER / HKEY_LOCAL_MACHINE. Dovresti fare questo stile funzionale in modo da poter mantenere le tue configurazioni di sola lettura cioè:

final_conf = merge(user_conf, machine_conf)

piuttosto che:

conf.update(user_conf)

Dovrei infine aggiungere che, naturalmente, se il framework / linguaggio scelto fornisce i propri meccanismi di configurazione incorporati e ben noti, è necessario considerare i vantaggi dell'utilizzo di questo invece di implementare il proprio.

Così. Molti aspetti da considerare: farlo bene e influenzerà profondamente l'architettura dell'applicazione, riducendo i bug, rendendo le cose facilmente testabili e costringendoti a utilizzare un buon design altrove.


+1 Grazie per la risposta. In precedenza, quando ho memorizzato le impostazioni dell'utente, ho appena letto un file come ad esempio in .inimodo che sia leggibile dall'uomo, ma stai suggerendo che dovrei serializzare una classe con le variabili in?
Andy,

.ini è facile da analizzare e ci sono anche molte API che includono .ini come possibile formato. Sto suggerendo di utilizzare tali API per aiutarti a fare il lavoro grugnito dell'analisi testuale, ma che il risultato finale dovrebbe essere che hai inizializzato una classe POD. Uso il termine serializzare nel senso generale di copiare o leggere nei campi di una classe da un certo formato, non la definizione più specifica di serializzare una classe direttamente da / verso binario (come si intende generalmente java.io. ).
Benedetto

Ora capisco un po 'meglio. Quindi, in sostanza, stai dicendo di avere una classe con tutti i campi / impostazioni e ne hai un'altra per impostare i valori in quella classe dal file? Quindi, come devo ottenere i valori dalla prima classe in altre classi?
Andy,

Caso per caso. Alcune delle tue classi di livello superiore potrebbero aver bisogno di un riferimento all'intera istanza di Config passata in esse se si basano su così tanti parametri. Altre classi hanno forse solo bisogno di uno o due parametri, ma non è necessario accoppiarli all'oggetto Config (rendendoli ancora più facili da testare), basta passare i pochi parametri verso il basso attraverso l'applicazione. Come accennato nella mia risposta, se costruisci con un'architettura testabile / orientata ai DI ottenere i valori dove ti servono non sarà generalmente difficile. Usa l'accesso globale a tuo rischio e pericolo.
Benedetto

Quindi, come suggeriresti che l'oggetto config venga passato alle classi se non è necessario coinvolgere l'ereditarietà? Attraverso un costruttore? Quindi, nel metodo principale del programma, viene avviata la classe reader che memorizza i valori nella classe Config, quindi il metodo principale passa l'oggetto Config o le singole variabili ad altre classi?
Andy,

4

In generale (a mio avviso), è meglio lasciare che l'applicazione gestisca il modo in cui è memorizzata la configurazione e passare la configurazione nei moduli. Ciò consente flessibilità nel modo in cui le impostazioni vengono salvate in modo da poter scegliere come target file o servizi Web o database o ...

Inoltre, pone l'onere di "cosa succede quando le cose falliscono" sull'applicazione, chissà cosa significa quel fallimento.

E semplifica notevolmente il test unitario quando puoi semplicemente passare un oggetto di configurazione invece di dover toccare il filesystem o affrontare i problemi di concorrenza introdotti dall'accesso statico.


Grazie per la tua risposta, ma non lo capisco del tutto. Sto parlando quando scrivo l'applicazione, quindi non c'è nulla da fare con la configurazione e quindi, come programmatore, so cosa significa un errore.
Andy,

@andy - certo, ma se i moduli accedono direttamente alla configurazione, non sanno in quale contesto stanno lavorando, quindi non sono in grado di determinare come gestire i problemi di configurazione. Se l'applicazione sta eseguendo il caricamento, può gestire qualsiasi problema, poiché conosce il contesto. Alcune app potrebbero voler eseguire il failover su un valore predefinito, altre vogliono interrompere, ecc.
Telastyn,

Giusto per chiarire qui, per moduli ti riferisci alle classi o estensioni effettive a un programma? Perché sto chiedendo dove archiviare le impostazioni all'interno del codice nel programma principale e come consentire ad altre classi di accedere alle impostazioni. Quindi voglio caricare un file di configurazione e quindi memorizzare l'impostazione da qualche parte / in qualche modo in modo da non dover continuare a fare riferimento al file.
Andy,

0

Se stai usando .NET per programmare le classi, hai diverse opzioni come Risorse, web.config o persino un file personalizzato.

Se usi Risorse o web.config, i dati vengono effettivamente memorizzati in file XML, ma il caricamento è più veloce.

Il recupero dei dati da questi file e la loro memorizzazione in un'altra posizione sarà come un doppio uso della memoria poiché questi vengono caricati in memoria per impostazione predefinita.

Per qualsiasi altro file o linguaggio di programmazione, la risposta di Benedict sopra funzionerà.


Grazie per la tua risposta e scusa per la risposta lenta. L'uso delle risorse sarebbe una buona opzione, ma mi sono appena reso conto che probabilmente non è l'opzione migliore per me visto che ho intenzione di sviluppare lo stesso programma in altre lingue, quindi preferirei avere un file XML o JSON ma leggilo nella mia classe in modo che lo stesso file possa essere letto in altri linguaggi di programmazione.
Andy,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.