Domanda

Simile a I letterali hard-coding sono mai accettabili? , ma io sto pensando specificamente a " stringhe magiche " qui.

Su un grande progetto, abbiamo una tabella di opzioni di configurazione come queste:

Name         Value
----         -----
FOO_ENABLED  Y
BAR_ENABLED  N
...

(centinaia di loro).

La pratica comune è chiamare una funzione generica per testare un'opzione come questa:

if (config_options.value('FOO_ENABLED') == 'Y') ...

(Naturalmente, potrebbe essere necessario selezionare questa stessa opzione in molti punti del codice di sistema.)

Quando ho aggiunto una nuova opzione, stavo considerando di aggiungere una funzione per nascondere " stringa magica " in questo modo:

if (config_options.foo_enabled()) ...

Tuttavia, i colleghi hanno pensato che fossi andato in mare e hanno obiettato a farlo, preferendo il codice hard perché:

  • Questo è ciò che facciamo normalmente
  • Rende più facile vedere cosa succede durante il debug del codice

Il problema è che vedo il loro punto! Realisticamente, non rinomineremo mai le opzioni per nessun motivo, quindi l'unico vantaggio che posso pensare per la mia funzione è che il compilatore catturerebbe qualsiasi refuso come fo_enabled (), ma non "FO_ENABLED".

Cosa ne pensi? Ho perso altri vantaggi / svantaggi?

È stato utile?

Soluzione

if (config_options.isTrue('FOO_ENABLED')) {...
}

Limita il tuo assegno Y con codice fisso in un posto, anche se ciò significa scrivere una classe wrapper per la tua Mappa.

if (config_options.isFooEnabled()) {...
}

Potrebbe andare bene fino a quando non avrai 100 opzioni di configurazione e 100 metodi (quindi qui puoi dare un giudizio sulla crescita e le esigenze future dell'applicazione prima di decidere sulla tua implementazione). Altrimenti è meglio avere una classe di stringhe statiche per i nomi dei parametri.

if (config_options.isTrue(ConfigKeys.FOO_ENABLED)) {...
}

Altri suggerimenti

Se uso una stringa una volta nel codice, in genere non mi preoccupo di renderla una costante da qualche parte.

Se uso una stringa due volte nel codice, prenderò in considerazione rendendola una costante.

Se uso una stringa tre volte nel codice, quasi sicuramente la trasformerò in una costante.

Mi rendo conto che la domanda è vecchia, ma è emersa sul mio margine.

AFAIC, il problema qui non è stato identificato con precisione, né nella domanda, né nelle risposte. Dimentica "stringhe di codifica & Quot; o no, per un momento.

  1. Il database ha una tabella di riferimento, contenente config_options. Il PK è una stringa.

  2. Esistono due tipi di PK:

    • Identificatori significativi, che gli utenti (e gli sviluppatori) vedono e usano. Questi PK dovrebbero essere stabili, su cui si può fare affidamento.

    • colonne Id insignificanti che gli utenti non dovrebbero mai vedere, di cui gli sviluppatori devono essere consapevoli e che devono scrivere codice. Questi non possono essere fatti valere.

  3. È normale, normale, scrivere codice usando il valore assoluto di un PK significativo IF CustomerCode = "IBM" ... o IF CountryCode = "AUS" ecc.

    • il riferimento al valore assoluto di un PK insignificante non è accettabile (a causa dell'incremento automatico; i vuoti vengono modificati; i valori vengono sostituiti all'ingrosso). .
  4. La tabella di riferimento utilizza PK significativi. È inevitabile fare riferimento a quelle stringhe letterali nel codice. Nascondere il valore renderà più difficile la manutenzione; il codice non è più letterale; i tuoi colleghi hanno ragione. Inoltre c'è la funzione ridondante aggiuntiva che mastica i cicli. Se c'è un refuso nel letterale, lo scoprirai presto durante il test Dev, molto prima dell'UAT.

    • centinaia di funzioni per centinaia di letterali è assurdo. Se si implementa una funzione, quindi Normalizzare il codice e fornire un'unica funzione che può essere utilizzata per una qualsiasi delle centinaia di letterali. In tal caso, torniamo a un valore letterale nudo e la funzione può essere eliminata.

    • il punto è che il tentativo di nascondere il valore letterale non ha valore.
      .

  5. Non può essere interpretato come " hardcoding " ;, è qualcosa di molto diverso. Penso che sia qui il tuo problema, identificando questi costrutti come & Quot; hardcoded & Quot ;. Sta letteralmente riferendosi letteralmente a un PK significativo.

  6. Ora solo dal punto di vista di qualsiasi segmento di codice, se si utilizza lo stesso valore alcune volte, è possibile migliorare il codice acquisendo la stringa letterale in una variabile e quindi utilizzando la variabile nel resto del blocco di codice. Certamente non una funzione. Ma questo è un problema di efficienza e buone pratiche. Anche questo non cambia l'effetto IF CountryCode = @cc_aus

Nella mia esperienza, questo tipo di problema sta mascherando un problema più profondo: la mancata esecuzione della OOP effettiva e il rispetto del principio DRY.

In poche parole, cattura la decisione al momento dell'avvio con una definizione appropriata per ogni azione dentro le if istruzioni, quindi getta via sia il config_options sia i test di runtime.

Dettagli di seguito.

L'utilizzo di esempio era:

if (config_options.value('FOO_ENABLED') == 'Y') ...

che solleva l'ovvia domanda, " Cosa sta succedendo tra i puntini di sospensione? " ;, in particolare data la seguente dichiarazione:

  

(Naturalmente, potrebbe essere necessario selezionare questa stessa opzione in molti punti del codice di sistema.)

Supponiamo che ciascuno di questi config_option valori corrisponda realmente a un singolo concetto di dominio problematico (o strategia di implementazione).

Invece di farlo (ripetutamente, in vari punti del codice):

  1. Prendi una stringa (tag),
  2. Trova l'altra stringa (valore) corrispondente,
  3. Verifica quel valore come equivalente booleano
  4. In base a quel test, decidi se eseguire qualche azione.

Suggerisco di incapsulare il concetto di " azione configurabile " ;.

Prendiamo come esempio (ovviamente altrettanto ipotetico come FOO_ENABLED ... ;-) che il tuo codice deve funzionare in unità inglesi o unità metriche. Se METRIC_ENABLED è & Quot; true & Quot ;, converte i dati immessi dall'utente dalla metrica all'inglese per il calcolo interno e riconvertiteli prima di visualizzare i risultati.

Definisci un'interfaccia:

public interface MetricConverter {
    double toInches(double length);
    double toCentimeters(double length);
    double toPounds(double weight);
    double toKilograms(double weight);
}

che identifica in un'unica posizione tutto il comportamento associato al concetto di metricOption().

Quindi scrivi implementazioni concrete di tutti i modi in cui questi comportamenti devono essere realizzati:

public class NullConv implements MetricConverter {
    double toInches(double length) {return length;}
    double toCentimeters(double length) {return length;}
    double toPounds(double weight)  {return weight;}
    double toKilograms(double weight)  {return weight;}
}

e

// lame implementation, just for illustration!!!!
public class MetricConv implements MetricConverter {
    public static final double LBS_PER_KG = 2.2D;
    public static final double CM_PER_IN = 2.54D
    double toInches(double length) {return length * CM_PER_IN;}
    double toCentimeters(double length) {return length / CM_PER_IN;}
    double toPounds(double weight)  {return weight * LBS_PER_KG;}
    double toKilograms(double weight)  {return weight / LBS_PER_KG;}
}

Al momento dell'avvio, invece di caricare un gruppo di <=> valori, inizializzare una serie di azioni configurabili, come in:

MetricConverter converter = (metricOption()) ? new MetricConv() : new NullConv();

(dove l'espressione <=> sopra è uno stand-in per qualsiasi controllo una tantum che devi effettuare, incluso il valore di METRIC_ENABLED ;-)

Quindi, ovunque il codice avrebbe detto:

double length = getLengthFromGui();
if (config_options.value('METRIC_ENABLED') == 'Y') {
    length = length / 2.54D;
}
// do some computation to produce result
// ...
if (config_options.value('METRIC_ENABLED') == 'Y') {
    result = result * 2.54D;
}
displayResultingLengthOnGui(result);

riscrivilo come:

double length = converter.toInches(getLengthFromGui());
// do some computation to produce result
// ...
displayResultingLengthOnGui(converter.toCentimeters(result));

Poiché tutti i dettagli dell'implementazione relativi a quell'unico concetto sono ora impacchettati in modo pulito, tutta la manutenzione futura relativa a <=> può essere eseguita in un unico posto. Inoltre, il compromesso di run-time è una vittoria; il " overhead " invocare un metodo è banale se confrontato con il sovraccarico di recuperare un valore String da una mappa e di eseguire String # equivale.

Credo che i due motivi che hai citato, Possibile errore di ortografia nella stringa, che non possono essere rilevati fino al runtime e la possibilità (anche se snella) di un cambio di nome giustificherebbe la tua idea.

Inoltre puoi ottenere funzioni digitate, ora sembra che tu memorizzi solo booleani, e se avessi bisogno di memorizzare un int, una stringa ecc. Preferirei usare get_foo () con un tipo, piuttosto che get_string ( quot; FOO ") o get_int (" FOO ").

Dovrei davvero usare costanti e niente letterali codificati.

Puoi dire che non saranno cambiati, ma potresti non saperlo mai. Ed è meglio prendere l'abitudine. Per utilizzare le costanti simboliche.

Penso che ci siano due diversi problemi qui:

  • Nel progetto attuale, la convenzione di usare stringhe con codice fisso è già ben stabilita, quindi tutti gli sviluppatori che lavorano al progetto lo conoscono. Potrebbe essere una convenzione non ottimale per tutti i motivi che sono stati elencati, ma tutti quelli che hanno familiarità con il codice possono guardarlo e istintivamente sanno cosa dovrebbe fare il codice. Modifica del codice in modo che in alcune parti utilizzi il & Quot; new & Quot; la funzionalità renderà il codice leggermente più difficile da leggere (perché le persone dovranno pensare e ricordare ciò che fa la nuova convenzione) e quindi un po 'più difficile da mantenere. Ma immagino che il passaggio all'intero progetto alla nuova convenzione sarebbe potenzialmente costoso in modo proibitivo a meno che tu non possa scrivere rapidamente la conversione.
  • Su un nuovo progetto, le costanti simboliche sono il modo in cui IMO, per tutti i motivi elencati. Soprattutto perché tutto ciò che fa in modo che il compilatore rilevi errori in fase di compilazione che altrimenti verrebbero catturati da un essere umano in fase di esecuzione è una convenzione molto utile da stabilire.

Anch'io preferisco una classe di configurazione fortemente tipizzata se viene utilizzata in tutto il codice. Con metodi correttamente denominati non si perde leggibilità. Se è necessario eseguire conversioni da stringhe a un altro tipo di dati (decimale / float / int), non è necessario ripetere il codice che esegue la conversione in più posizioni e è possibile memorizzare nella cache il risultato in modo che la conversione abbia luogo una sola volta. Hai già ottenuto le basi di questo, quindi non credo che ci vorrebbe molto per abituarsi al nuovo modo di fare le cose.

Un'altra cosa da considerare è l'intento. Se fai parte di un progetto che richiede la localizzazione, le stringhe codificate possono essere ambigue. Considera quanto segue:

const string HELLO_WORLD = "Hello world!";
print(HELLO_WORLD);

L'intenzione del programmatore è chiara. L'uso di una costante implica che non è necessario localizzare questa stringa. Ora guarda questo esempio:

print("Hello world!");

Qui non ne siamo così sicuri. Il programmatore non voleva davvero che questa stringa fosse localizzata o il programmatore ha dimenticato la localizzazione mentre stava scrivendo questo codice?

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top