Come faccio a gestire un insieme molto grande di regole e numeri magici nel mio programma?

21

Sono un po 'nuovo alla programmazione (sono un ingegnere meccanico per mestiere) e sto sviluppando un piccolo programma durante il mio tempo di fermo che genera una parte (solidworks) basata su input di varie persone provenienti da tutto lo stabilimento.

Basandomi su pochi input (6 per la precisione), ho bisogno di fare centinaia di chiamate API che possono richiedere fino a una dozzina di parametri ciascuna; tutto generato da un insieme di regole che ho raccolto dopo aver intervistato tutti quelli che gestiscono la parte. La sezione delle regole e dei parametri del mio codice è di 250 righe e in crescita.

Quindi, qual è il modo migliore per mantenere il mio codice leggibile e gestibile? Come faccio a compartimentare tutti i miei numeri magici, tutte le regole, gli algoritmi e le parti procedurali del codice? Come faccio a gestire un'API molto dettagliata e granulare?

Il mio obiettivo principale è essere in grado di consegnare a qualcuno la mia fonte e fargli capire cosa stavo facendo, senza il mio contributo.

    
posta user2785724 17.02.2014 - 23:15
fonte

7 risposte

26

In base a ciò che descrivi, probabilmente vorrai esplorare il meraviglioso mondo dei database. Sembra che molti dei numeri magici che descrivi - in particolare se siano dipendenti dalla parte - sono in realtà dati, non codice. Avrai molta più fortuna e troverai molto più semplice estendere l'applicazione a lungo termine, se puoi classificare il modo in cui i dati si riferiscono alle parti e definire una struttura di database per questo.

Tenete a mente che i "database" non significano necessariamente MySQL o MS-SQL. Il modo in cui memorizzi i dati dipende molto da come viene utilizzato il programma, da come lo stai scrivendo, ecc. Può significare un database di tipo SQL, oppure può semplicemente significare un file di testo formattato.

    
risposta data 17.02.2014 - 23:31
fonte
14

A meno che non si preveda di estendere questo a più parti, sarei riluttante ad aggiungere un database ancora. Avere un database significa una grande quantità di cose da imparare per te e più cose da installare per farlo funzionare per altre persone. L'aggiunta di un database incorporato mantiene l'eseguibile finale portatile, ma qualcuno con il tuo codice sorgente ora ha ancora una cosa per funzionare.

Penso che un elenco di costanti chiaramente denominate e funzioni di implementazione delle regole sarà di grande aiuto. Se dai tutto nomi naturali e ti concentri sulle tecniche di programmazione alfabetica dovresti essere in grado di creare un programma leggibile.

Idealmente ti ritroverai con il codice che dice:

LeftBearingHoleDepth = BearingWidth + HoleDepthTolerance;
if (not CheckPartWidth(LeftBearingHoleDepth, {other parameters})
    {whatever you need to adjust}

A seconda di quanto siano localizzate le costanti, sarei tentato di dichiararle nelle funzioni in cui sono utilizzate, laddove possibile. È molto utile girare:

SomeAPICall(10,324.5, 1, 0.02, 6857);

in

const NumberOfOilDrainHoles = 10
const OilDrainHoleSpacing = 324.5
{etc}
SomeAPICall(NumberOfOilDrainHoles, OilDrainHoleSpacing, {etc}

Questo ti dà un codice ampiamente auto-documentante e incoraggia anche chi modifica il codice a dare nomi altrettanto significativi a ciò che aggiunge. Iniziare localmente rende anche più facile gestire il numero totale di costanti che accumulerai. Diventa un po 'fastidioso se devi continuare a scorrere un lungo elenco di costanti per assicurarti che il valore sia quello desiderato.

Un consiglio per i nomi: metti la parola più importante a sinistra. Potrebbe non leggere abbastanza bene, ma rende le cose più facili da trovare. La maggior parte delle volte si sta osservando un sifone e ci si chiede il bullone, non si guarda un fulmine e ci si chiede dove si trova, quindi chiamalo SumpBoltThreadPitch non BoltThreadPitchSump. Quindi ordina l'elenco di costanti. Successivamente, per estrarre tutti i pitch del thread è possibile ottenere la lista in un editor di testo e utilizzare la funzione find oppure utilizzare uno strumento come grep per restituire solo le righe che contengono "ThreadPitch".

    
risposta data 18.02.2014 - 03:06
fonte
4

Penso che la tua domanda si riduce a: come strutturare un calcolo? Si noti che si desidera gestire "un insieme di regole", che sono codice e "un insieme di numeri magici", quali sono i dati (Puoi vederli come "dati incorporati nel tuo codice", ma sono comunque dati).

Inoltre, rendere il tuo codice "comprensibile agli altri" è infatti l'obiettivo generale di tutti i paradigmi di programmazione (vedi ad esempio " Implementazione Pattern "di Kent Beck, o" Clean Code "di Robert C. Martin per gli autori sul software chi dichiara il tuo stesso obiettivo, per qualsiasi programma).

Tutti i suggerimenti contenuti in questi libri si applicano alla tua domanda. Consentitemi di estrarre alcuni suggerimenti specifici per "numeri magici" e "serie di regole":

  1. Usa costanti e Enumerazioni con nome per sostituire i numeri magici

    Esempio di costanti :

    if (partWidth > 0.625) {
        // doSomeApiCall ...
    }
    return (partWidth - 0.625)
    

    dovrebbe essere sostituito con una costante denominata in modo che nessuna modifica successiva possa introdurre un errore di battitura e interrompere il codice, ad es. cambiando il primo 0.625 ma non il secondo.

    const double MAX_PART_WIDTH = 0.625;
    
    if (partWidth > MAX_PART_WIDTH) {
        // doSomeApiCall ...
    }
    return (partWidth - MAX_PART_WIDTH)
    

    Esempio di enumerazioni :

    Le enumerazioni possono aiutarti a mettere insieme i dati che appartengono insieme. Se stai usando Java, ricorda che le Enum sono oggetti; i loro elementi possono contenere dati, e puoi definire metodi che restituiscono tutti gli elementi, o controllare alcune proprietà. Qui un Enum viene utilizzato nella costruzione di un altro Enum:

    public enum EnginePart {
        CYLINDER (100, Materials.STEEL),
        FLYWHEEL (120, Materials.STEEL),
        CRANKSHAFT (200, Materials.CARBON);
    
        private final double maxTemperature;
        private final Materials composition;
        private EnginePart(double maxTemperature, Materials composition) {
            this.maxTemperature = maxTemperature;
            this.composition = composition;
        }
    }
    
    public enum Materials {
        STEEL,
        CARBON
    }
    

    Il vantaggio è che nessuno può definire erroneamente un EnginePart che è non in acciaio o carbonio e nessuno può introdurre un EnginePart chiamato "asdfasdf", come sarebbe se fosse una stringa che verrebbe controllata sul contenuto.

  2. Il modello di strategia e Il pattern metodo di fabbrica descrive come incapsulare" regole "e passarle a un altro oggetto che le utilizza (nel caso del pattern Factory, l'utilizzo sta creando qualcosa, nel caso del pattern Strategy, l'utilizzo è quello che vuoi).

    Esempio di modello di metodo di fabbrica :

    Immagina di avere due tipi di motori: uno in cui ciascuna parte ha da collegare al Compressore e una in cui ciascuna parte può essere liberamente collegata a qualsiasi altra parte. Adattato da Wikipedia

    public class EngineAssemblyLine {
        public EngineAssemblyLine() {
            EnginePart enginePart1 = makeEnginePart();
            EnginePart enginePart2 = makeEnginePart();
            enginePart1.connect(enginePart2);
            this.addEngine(engine1);
            this.addEngine(engine2);
        }
    
        protected Room makeEngine() {
            return new NormalEngine();
        }
    }
    

    E poi in un'altra classe:

    public class CompressedEngineAssemblyLine extends EngineAssemblyLine {
        @Override
        protected Room makeRoom() {
            return new CompressedEngine();
        }
    }
    

    La parte interessante è: ora il costruttore AssemblyLine è separato dal tipo di motore che sta gestendo. Forse i metodi addEngine chiamano un'API remota ...

    Esempio di modello di strategia :

    Lo schema della strategia descrive come introdurre una funzione in un oggetto per modificarne il comportamento. Immaginiamo che a volte vuoi lucidare una parte, a volte vuoi dipingerla e, per impostazione predefinita, vuoi rivederne la qualità. Questo è un esempio di Python, adattato da Overflow dello stack

    class PartWithStrategy:
    
        def __init__(self, func=None) :
            if func:
                self.execute = func
    
        def execute(self):
            # ... call API of quality review ...
            print "Part will be reviewed"
    
    
    def polish():
        # ... call API of polishing department ...
        print "Part will be polished"
    
    
    def paint():
        # ... call API of painting department ...
        print "Part will be painted"
    
    if __name__ == "__main__" :
        strat0 = PartWithStrategy()
        strat1 = PartWithStrategy(polish)
        strat2 = PartWithStrategy(paint)
    
        strat0.execute()  # output is "Part will be reviewed"
        strat1.execute()  # output is "Part will be polished"
        strat2.execute()  # output is "Part will be painted"
    

    Potresti espanderlo per conservare un elenco di azioni che vuoi eseguire e poi chiamarle a turno dal metodo execute . Forse questa generalizzazione potrebbe essere meglio descritta come schema Builder , ma hey, non vogliamo essere pignoli, facciamo ? :)

risposta data 18.02.2014 - 15:28
fonte
2

Potresti voler usare un motore di regole. Un motore di regole fornisce un DSL (Domain Specific Language) progettato per modellare i criteri necessari per un determinato risultato in modo comprensibile, come spiegato in questa domanda .

A seconda dell'implementazione del motore delle regole, le regole possono anche essere cambiate senza ricompilare il codice. E poiché le regole sono scritte in un proprio linguaggio semplice, possono essere modificate anche dagli utenti.

Se sei fortunato, c'è un motore di regole pronto all'uso per il linguaggio di programmazione che stai utilizzando.

Il rovescio della medaglia è che devi familiarizzare con un motore di regole che può essere difficile se sei un principiante di programmazione.

    
risposta data 18.02.2014 - 18:26
fonte
1

La mia soluzione a questo problema è molto diversa: livelli, impostazioni e LOP.

Prima avvolgi l'API in un livello. Trova sequenze di chiamate API utilizzate insieme e combinale nelle tue chiamate API. Alla fine non ci dovrebbero essere chiamate dirette all'API sottostante, solo chiamate ai propri wrapper. Le chiamate al wrapper dovrebbero iniziare a sembrare una mini lingua.

In secondo luogo, implementa un "gestore delle impostazioni". Questo è un modo per associare dinamicamente nomi e valori. Qualcosa come questo. Un altro mini linguaggio.

Baseplate.name="Base plate"
Baseplate.length=1032.5
Baseplate.width=587.3

Infine, implementa il tuo mini linguaggio in cui esprimere i disegni (questa è la programmazione orientata alla lingua). Questo linguaggio dovrebbe essere comprensibile agli ingegneri e ai progettisti che contribuiscono con le regole e le impostazioni. Il primo esempio di questo prodotto che viene in mente è Gnuplot, ma ce ne sono molti altri. Potresti usare Python, anche se personalmente non lo farei.

Comprendo che questo è un approccio complesso e potrebbe essere eccessivo per il tuo problema o richiedere abilità che devi ancora acquisire. È proprio come lo farei io.

    
risposta data 18.02.2014 - 15:39
fonte
0

Non sono sicuro di aver ottenuto correttamente la domanda, ma sembra che dovresti raggruppare le cose in alcune strutture. Di 'se stai usando C ++, puoi definire cose come:

struct SomeParametersClass
{
    int   p1;  // this is for that
    float p2;  // this is a different parameter
    ...
    SomeParametersClass() // constructor, assigns default values
    {
        p1 = 42; // the best value that some guy told me
        p2 = 3.14; // looks like a know value, but isn't
    {
};

struct SomeOtherParametersClass
{
    int   v1;  // this is for ...
    float v2;  // this is for ...
    ...
    SomeOtherParametersClass() // constructor, assigns default values
    {
        v1 = 24; // the best value 
        v2 = 1.23; // also the best value
    }
};

Puoi installarli all'inizio del programma:

int main()
{
    SomeParametersClass params1;
    SomeOtherParametersClass params2;
    ...

Quindi le tue chiamate API appariranno (supponendo che tu non possa cambiare la firma):

 SomeAPICall( params1.p1, params1.p2 );

Se puoi modificare la firma dell'API, puoi passare all'intera struttura:

 SomeAPICall( params1 );

Puoi anche raggruppare tutti i parametri in un wrapper più grande:

struct AllTheParameters
{
    SomeParametersClass      SPC;
    SomeOtherParametersClass SOPC;
};
    
risposta data 18.02.2014 - 14:40
fonte
0

Sono sorpreso che nessun altro abbia menzionato questo ...

Hai detto:

My main goal is to be able to hand someone my source and have them understand what I was doing, without my input.

Quindi lasciatemi dire questo, la maggior parte delle altre risposte sono sulla strada giusta. Sicuramente penso che i database possano aiutarti. Ma un'altra cosa che ti aiuterà è commentare, buoni nomi di variabili e corretta organizzazione / separazione delle preoccupazioni.

Tutte le altre risposte sono strongmente basate sulla tecnica, ma ignorano i fondamenti appresi dalla maggior parte dei programmatori. Dal momento che sei un meccanico di mestiere, la mia ipotesi è che non sei abituato a questo stile di documentazione.

Commentare e scegliere i nomi delle variabili buoni, succinti aiuta immensamente con la leggibilità. Quale è più facile da capire?

var x = y + z;

o

//Where bandwidth, which was previously defined is (1000 * Info Rate) / FEC Rate / Modulation * carrier spacing / 1000000
float endFrequency = centerFrequency + (1/2 bandwidth);

Questo è abbastanza indipendente dal linguaggio. Non importa quale piattaforma, IDE, lingua, ecc. Tu stia lavorando, la documentazione corretta è il modo più semplice e pulito per assicurarti che qualcuno possa capire il tuo codice.

Successivamente, è in procinto di gestire quei numeri magici e un sacco di dubbi, ma penso che il commento di GrandmasterB sia andato abbastanza bene.

    
risposta data 19.02.2014 - 14:11
fonte

Leggi altre domande sui tag