In cosa consiste "Soft Coding"?

87

In questo articolo di Alex Papadimoulis, puoi vedere questo snippet:

private void attachSupplementalDocuments()
{
  if (stateCode == "AZ" || stateCode == "TX") {

    //SR008-04X/I are always required in these states
    attachDocument("SR008-04X");
    attachDocument("SR008-04XI");
  }

  if (ledgerAmnt >= 500000) {
    //Ledger of 500K or more requires AUTHLDG-1A
    attachDocument("AUTHLDG-1A");
  }

  if (coInsuredCount >= 5  && orgStatusCode != "CORP") {
    //Non-CORP orgs with 5 or more co-ins require AUTHCNS-1A
    attachDocument("AUTHCNS-1A");
  }
}

Davvero non capisco questo articolo.

Cito:

If every business rule constant was stored in some configuration file, life would be much [more (sic)] difficult for everyone maintaining the software: there’d be a lot of code files that shared one, big file (or, the converse, a whole lot of tiny configuration files); deploying changes to the business rules require not new code, but manually changing the configuration files; and debugging is that much more difficult.

Questo è un argomento contro il numero intero costante "500000" in un file di configurazione, o "AUTHCNS-1A" e altre costanti di stringa.

Come può essere una cattiva pratica?

In questo snippet, "500000" non è un numero. Ad esempio, non è lo stesso di:

int doubleMe(int a) { return a * 2;}

dove 2, è un numero che non deve essere astratto. Il suo uso è ovvio e non rappresenta qualcosa che può essere riutilizzato in seguito.

Al contrario, "500000" non è semplicemente un numero. È un valore significativo, che rappresenta l'idea di un punto di rottura nella funzionalità. Questo numero potrebbe essere utilizzato in più di un posto, ma non è il numero che stai usando; è l'idea del limite / limite, al di sotto della quale si applica una regola e al di sopra della quale un'altra.

Come ci si riferisce da un file di configurazione, o anche a #define , const o qualunque sia la tua lingua, peggio che includerne il valore? Se in seguito il programma, o qualche altro programmatore, richiede anche quel limite, in modo che il software faccia un'altra scelta, sei fregato (perché quando cambia, nulla ti garantisce che cambierà in entrambi File). Questo è chiaramente peggio per il debug.

Inoltre, se domani il governo chiedesse "Dal 5/3/2050, è necessario aggiungere AUTHLDG-122B invece di AUTHLDG-1A", questa costante di stringa non è una costante di stringa semplice. È uno che rappresenta un'idea; è solo il valore corrente di quell'idea (che è "la cosa che aggiungi se il libro mastro è superiore a 500k").

Lasciatemi chiarire. Non sto dicendo che l'articolo è sbagliato; Io proprio non capisco; forse non è spiegato troppo bene (almeno per il mio pensiero).

Capisco che la sostituzione di ogni possibile valore letterale o numerico stringa con una costante, definizione o variabile di configurazione, non solo non sia necessaria, ma comporti un eccesso di complessità, ma questo particolare esempio non sembra rientrare in questa categoria. Come fai a sapere che non ne avrai più bisogno in seguito? O qualcun altro, per quello?

    
posta K. Gkinis 08.04.2016 - 11:14
fonte

8 risposte

100

L'autore mette in guardia contro l'astrazione prematura.

La riga if (ledgerAmt > 500000) è simile al tipo di regola aziendale che ci si aspetterebbe di vedere per sistemi aziendali complessi di grandi dimensioni i cui requisiti sono incredibilmente complessi ma precisi e ben documentati.

In genere questi tipi di requisiti sono casi eccezionali / marginali piuttosto che una logica utile riutilizzabile. Tali requisiti sono generalmente di proprietà e gestiti da analisti aziendali e esperti in materia, piuttosto che dagli ingegneri

(Si noti che la "proprietà" dei requisiti degli Analisti / esperti aziendali in questi casi si verifica in genere laddove gli sviluppatori che lavorano in campi specialistici non dispongono di competenze sufficienti sul dominio, anche se mi aspetterei comunque una comunicazione / cooperazione completa tra gli sviluppatori e gli esperti di dominio per la protezione da requisiti ambigui o mal scritti).

Quando si gestiscono sistemi i cui requisiti sono ricchi di casi limite e di logica altamente complessa, di solito non c'è modo di astrarre utilmente quella logica o renderla più manutenibile; i tentativi di provare a costruire astrazioni possono facilmente ritorcersi contro il fuoco - non solo con conseguente perdita di tempo, ma anche con conseguente minor numero di codice gestibile.

How is referring to it from a config file, or even a #define, const or whatever your language provides, worse than including its value? If later on the program, or some other programmer, also requires that borderline, so that the software makes another choice, you're screwed (because when it changes, nothing guarantees you that it will change in both files). That's clearly worse for debugging.

Questo tipo di codice tende a essere protetto dal fatto che il codice stesso ha probabilmente una mappatura uno-a-uno ai requisiti; Ad esempio, quando uno sviluppatore conosce che la cifra 500000 appare due volte nei requisiti, lo sviluppatore sa anche che appare due volte nel codice.

Considera l'altro (altrettanto probabile) scenario in cui 500000 appare in più punti nel documento dei requisiti, ma gli Esperti sulla materia decidono di cambiarne solo uno; c'è un rischio ancora peggiore che qualcuno che cambia il valore di const potrebbe non rendersi conto che 500000 è usato per significare cose diverse - così lo sviluppatore lo cambia nell'unico e solo posto lo trova nel codice, e finisce per rompere qualcosa che non si rendevano conto di aver cambiato.

Questo scenario accade molto su un software legale / finanziario su misura (ad es. logica di quotazione assicurativa) - le persone che scrivono tali documenti non sono ingegneri e non hanno problemi a copiare + incollare interi pezzi della specifica, modificando alcune parole / numeri, ma lasciando la maggior parte lo stesso.

In questi scenari, il modo migliore per gestire i requisiti di copia-incolla è scrivere il codice copia-incolla e rendere il codice simile ai requisiti (incluso l'hard-coding di tutti i dati) possibile.

La realtà di tali requisiti è che di solito non rimangono copia e incolla a lungo, ei valori a volte cambiano regolarmente, ma spesso non cambiano in tandem, quindi cercando di razionalizzare o astrarre quei requisiti fuori o li semplifica in qualsiasi modo finisce per creare più di un mal di testa manutenzione che solo tradurre i requisiti letteralmente in codice.

    
risposta data 08.04.2016 - 11:58
fonte
44

L'articolo ha un buon punto. Come può essere una cattiva pratica estrarre costanti in un file di configurazione? Può essere una cattiva pratica se complica inutilmente il codice. Avere un valore direttamente nel codice è molto più semplice che doverlo leggere da un file di configurazione e il codice scritto è facile da seguire.

In addition, tomorrow, the government goes "From 5/3/2050, you need to add AUTHLDG-122B instead of AUTHLDG-1A".

Sì, quindi cambi il codice. Il punto dell'articolo è che non è più complicato modificare il codice rispetto alla modifica di un file di configurazione.

L'approccio descritto nell'articolo non si ridimensiona se ottieni una logica più complessa, ma il punto è che devi fare un giudizio, e talvolta la soluzione più semplice è semplicemente la migliore.

How do you know that you will not need it later on? Or someone else for that matter?

Questo è il punto del principio YAGNI. Non progettare per un futuro sconosciuto che potrebbe rivelarsi completamente diverso, design per il presente. Hai ragione che se il valore 500000 è usato in diversi punti del programma, dovrebbe ovviamente essere estratto in una costante. Ma questo non è il caso nel codice in questione.

Softcoding è in realtà una questione di separazione delle preoccupazioni . Le informazioni softcode che sai potrebbero cambiare indipendentemente dalla logica dell'applicazione principale. Non indicherai mai una stringa di connessione su un database, perché sai che potrebbe cambiare indipendentemente dalla logica dell'applicazione e dovrai differenziarla per diversi ambienti. In un'app web ci piace separare la logica di business dai template html e dai fogli di stile, perché potrebbero cambiare indipendentemente e persino essere cambiati da persone diverse.

Ma nel caso dell'esempio di codice, le stringhe e i numeri hardcoded sono parte integrante della logica dell'applicazione. È ipotizzabile che un file possa cambiare il suo nome a causa di alcuni cambiamenti di policy fuori dal tuo controllo, ma è altrettanto plausibile che sia necessario aggiungere un nuovo controllo if-branch per una condizione diversa. In questo caso, l'estrazione dei nomi e dei numeri dei file interrompe la coesione.

    
risposta data 08.04.2016 - 12:27
fonte
26

L'articolo continua a parlare di "Enterprise Rule Engine" che sono probabilmente un esempio migliore di ciò contro cui sta discutendo.

La logica è che puoi generalizzare al punto in cui la tua configurazione diventa così complicata da contenere il proprio linguaggio di programmazione.

Ad esempio, il codice di stato per documentare la mappatura nell'esempio potrebbe essere spostato in un file di configurazione. Ma avresti bisogno di esprimere una relazione complessa.

<statecode id="AZ">
    <document id="SR008-04X"/>
    <document id="SR008-04XI"/>
</statecode>

Forse inseriresti anche la quantità di ledger?

<statecode id="ALL">
    <document id="AUTHLDG-1A" rule="ledgerAmt >= 50000"/>
</statecode>

Presto scoprirai che stai programmando in una nuova lingua che hai inventato e salvando quel codice in file di configurazione che non hanno controllo di origine o di modifica.

Va notato che questo articolo è del 2007, quando questo genere di cose era un approccio comune.

Al giorno d'oggi probabilmente risolveremo il problema con iniezione di dipendenza (DI). Ad esempio, avresti un 'hard coded'

InvoiceRules_America2007 : InvoiceRules

che sostituiresti con un hard coded o più configurabile

InvoiceRules_America2008 : InvoiceRules

quando la legge o i requisiti aziendali sono cambiati.

    
risposta data 08.04.2016 - 13:26
fonte
17

On the contrary, "500000" is not simply a number. It's a significant value, one that represents the idea of a breakpoint in functionality. This number could be used in more than one places, but it's not the number that you're using, it's the idea of the limit/borderline, below which one rule applies, and above which another.

E ciò si esprime avendo (e potrei sostenere che anche il commento è ridondante):

 if (ledgerAmnt >= 500000) {
    //Ledger of 500K or more requires AUTHLDG-1A
    attachDocument("AUTHLDG-1A");
  }

Questo è solo ripetere ciò che sta facendo il codice:

LEDGER_AMOUNT_REQUIRING_AUTHLDG1A=500000
if (ledgerAmnt >= LEDGER_AMOUNT_REQUIRING_AUTHLDG1A) {
    //Ledger of 500K or more requires AUTHLDG-1A
    attachDocument("AUTHLDG-1A");
}

Si noti che l'autore presume che il significato di 500000 sia legato a questa regola; non è un valore che è o è probabile che possa essere riutilizzato altrove:

The one and only business rule change that this preceding Soft Coding could ever account for is a change in the ledger amount that required a form AUTHLDG-1A. Any other business rule change would require even more work – configuration, documentation, code, etc

Il punto principale dell'articolo, a mio avviso, è che a volte un numero è solo un numero: non ha altro significato se non quello che viene trasmesso nel codice e non è probabile che venga usato altrove. Pertanto, riassumere goffamente ciò che il codice sta facendo (ora) in un nome di variabile solo per evitare valori codificati è una ripetizione inutile nella migliore delle ipotesi.

    
risposta data 08.04.2016 - 12:22
fonte
8

Le altre risposte sono corrette e premurose. Ma ecco la mia risposta breve e dolce.

  Rule/value          |      At Runtime, rule/value…
  appears in code:    |   …Is fixed          …Changes
----------------------|------------------------------------
                      |                 |
  Once                |   Hard-code     |   Externalize
                      |                 |   (soft-code)
                      |                 |
                      |------------------------------------
                      |                 |
  More than once      |   Soft-code     |   Externalize
                      |   (internal)    |   (soft-code)
                      |                 |
                      |------------------------------------

Se le regole e i valori speciali sono visualizzati in un punto del codice e non cambiano durante il runtime, quindi hard-code come mostrato nella domanda.

Se le regole oi valori speciali compaiono in più di una posizione nel codice e non cambiano durante il runtime, quindi codice soft. Il soft-coding di una regola potrebbe definire una classe / metodo specifico o utilizzare il pattern Builder . Per i valori, la codifica soft può significare definire una singola costante o enum per il valore da utilizzare nel codice.

Se le regole oi valori speciali possono cambiare durante il runtime, devi esternalizzarli. È fatto comunemente aggiornando i valori in un database. Oppure aggiornare i valori nella memoria manualmente da un utente che inserisce i dati. Viene anche effettuato memorizzando i valori in un file di testo (XML, JSON, testo normale, qualsiasi cosa) che viene scansionato ripetutamente per modificare la data-ora di modifica del file.

    
risposta data 08.04.2016 - 21:57
fonte
7

Questa è la trappola in cui ci imbattiamo quando usiamo un problema del giocattolo e quindi pongono solo le soluzioni strawman , quando cerchiamo di illustrare un vero problema.

Nell'esempio fornito, non fa differenza se i valori indicati sono hardcoded come valori inline o definiti come consts.

È il codice circostante che renderebbe l'esempio un orrore di manutenzione e codifica. Se è nessun codice circostante, lo snippet va bene, almeno in un ambiente di refactoring costante. In un ambiente in cui il refactoring tende a non accadere, i manutentori di quel codice sono già morti, per ragioni che presto diventeranno evidenti.

Vedi, se c'è un codice che lo circonda, allora le cose brutte accadono chiaramente.

La prima cosa negativa è che il valore 50000 viene usato per un altro valore da qualche parte, ad esempio, l'importo di mastro su cui cambia l'aliquota fiscale in alcuni stati ... quindi quando il cambiamento accade, il manutentore non ha modo di sapere quando trova queste due istanze di 50000 nel codice, indipendentemente dal fatto che si riferiscano allo stesso 50k o 50k interamente indipendenti. E dovresti cercare anche 49999 e 50001, nel caso qualcuno li usasse anche come costanti? Questa non è una chiamata a plonk quelle variabili in un file di configurazione di un servizio separato: ma anche l'hardcoding in linea è chiaramente sbagliato. Invece, dovrebbero essere costanti, definite e con scope all'interno della classe o del file in cui vengono utilizzate. Se le due istanze di 50k usano la stessa costante, allora probabilmente rappresentano la stessa restrizione legislativa; se no, probabilmente no; e in entrambi i casi, avranno un nome, che sarà meno opaco di un numero in linea.

I nomi file vengono passati a una funzione - attachDocument () - che accetta nomi di file di base come stringa, senza percorso o estensione. I nomi file sono essenzialmente chiavi esterne a qualche filesystem, o database, o ovunque attachDocument () ottiene i file da. Ma le stringhe non ti dicono nulla - quanti file ci sono? Che tipi di file sono? Come fai a sapere, quando si apre un nuovo mercato, se è necessario aggiornare questa funzione? A quali tipi di cose possono essere attaccati? Il manutentore viene lasciato completamente al buio e tutto ciò che possiede è una stringa, che può apparire più volte nel codice e significa cose diverse ogni volta che appare. In un posto, "SR008-04X" è un codice cheat. In un altro, è un comando per ordinare quattro razzi booster SR008. Qui, è un nome di file? Sono collegati? Qualcuno ha appena cambiato quella funzione per menzionare un altro file, "CLIENT". Quindi a te, povero maintainer, è stato detto che il file "CLIENT" deve essere rinominato in "CLIENTE". Ma la stringa "CLIENT" appare 937 volte nel codice ... dove inizi a guardare?

Il problema del giocattolo è che i valori sono tutti inusuali e possono essere ragionevolmente garantiti come unici nel codice. Non "1" o "10" ma "50.000". Non "client" o "report" ma "SR008-04X".

Il strawman è che l'unico altro modo per affrontare il problema delle costanti impenetrabilmente opache è di metterle in risalto nel file di configurazione di qualche servizio non correlato.

Insieme, puoi usare questi due errori per dimostrare qualsiasi argomento vero.

    
risposta data 10.04.2016 - 01:15
fonte
2

Ci sono diversi problemi in questo.

Un problema riguarda la creazione di un motore di regole per rendere tutte le regole facilmente configurabili al di fuori del programma stesso. La risposta in casi simili a questo è più spesso no. Le regole cambieranno in strani modi che sono difficili da prevedere, il che significa che il motore delle regole deve essere esteso ogni volta che c'è un cambiamento.

Un altro problema è come gestire queste regole e le loro modifiche nel controllo della versione. La soluzione migliore qui è dividere le regole in una classe per ogni regola.

Ciò consente a ciascuna regola di avere una propria validità, alcune regole cambiano ogni anno, alcune modifiche che si verificano quando viene rilasciato un permesso o viene emessa una fattura. La regola stessa contenente il controllo per la versione che deve applicare.

Anche se la costante è privata, non può essere utilizzata in modo errato da nessun'altra parte nel codice.

Poi hai un elenco di tutte le regole e applica l'elenco.

Un ulteriore problema è come gestire le costanti. 500000 potrebbe sembrare poco appariscente, ma bisogna fare molta attenzione per assicurarsi che venga convertito correttamente. Se viene applicata un'aritmetica in virgola mobile, potrebbe essere convertita in 500.000.00001, pertanto un confronto con 500.000.00000 potrebbe non riuscire. O ancora peggio, 500000 funziona sempre come previsto, ma in qualche modo 565000 fallisce quando viene convertito. Assicurati che la conversione sia esplicita e fatta da te non dalla supposizione del compilatore. Spesso questo viene fatto convertendolo in alcuni BigInteger o BigDecimal prima che venga utilizzato.

    
risposta data 10.04.2016 - 20:01
fonte
2

Anche se non è menzionato direttamente nella domanda, mi piacerebbe notare che ciò che è importante non è seppellire la logica di business nel codice.

Il codice, come nell'esempio precedente, che codifica i requisiti aziendali specificati esternamente dovrebbe realmente vivere in una parte distinta dell'albero dei sorgenti, forse chiamato businesslogic o qualcosa di simile, e bisogna prestare attenzione a assicurati che solo codifichi i requisiti aziendali in modo semplice, leggibile e conciso possibile, con un minimo di testo standard e con commenti chiari e informativi.

Dovrebbe non essere mescolato con il codice "infrastruttura" che implementa la funzionalità necessaria per eseguire la logica di business, come, ad esempio, l'implementazione di% co_de metodo% nell'esempio o ad es UI, log o codice di database in generale. Mentre il one modo per rafforzare questa separazione è quello di "codice soft" tutta la logica aziendale in un file di configurazione, questo è lontano dall'unico (o il migliore) metodo.

Tale codice di logica aziendale dovrebbe anche essere scritto in modo abbastanza chiaro che, se lo mostrassi a un esperto di dominio aziendale senza capacità di codifica, sarebbe in grado di dare un senso a questo. Per lo meno, se e quando i requisiti aziendali cambiano, il codice che li codifica dovrebbe essere abbastanza chiaro che anche un nuovo programmatore senza familiarità con il codebase dovrebbe essere in grado di localizzare, rivedere e aggiornare facilmente la logica di business, supponendo che non è richiesta alcuna funzionalità qualitativamente nuova.

Idealmente, tale codice sarebbe anche scritto in un linguaggio specifico del dominio per imporre la separazione tra la logica aziendale e l'infrastruttura sottostante, ma potrebbe essere inutilmente complicato per una semplice app interna. Detto questo, se sei per esempio vendere il software a più client che richiedono ciascuno il proprio set personalizzato di regole aziendali, un semplice linguaggio di scripting specifico del dominio (ad esempio, ad esempio basato su Lua sandbox ) potrebbe essere proprio la cosa giusta.

    
risposta data 11.04.2016 - 00:08
fonte

Leggi altre domande sui tag