Se comprendo correttamente il tuo problema (devi gestire un gran numero di regole la cui logica dipende da un gran numero di proprietà dell'oggetto), potrebbe avere senso estrarre la logica in un formato alternativo (un "file delle regole") e utilizzare un generatore di codice per generare automaticamente il codice di invio e verifica.
Per quanto riguarda i formati alternativi, fai riferimento a Excel e è una possibilità. Personalmente, preferirei YAML, JSON o XML, penso. Fondamentalmente, devi solo scegliere un formato basato su testo che sia e leggibile da umani e facilmente analizzabile in uno script di generazione del codice.
Riguardo alla generazione del codice, la mia preferenza personale per gli ultimi anni è stata quella di utilizzare l'utilità Cog di Ned Batchelder. È uno script Python che elabora piccoli frammenti di codice Python incorporato inline all'interno delle tue fonti. Se suona bizzarro , credimi, è assolutamente in grado di cambiare la vita una volta che ti senti a tuo agio con l'approccio.
L'unico esempio che ho a portata di mano non corrisponde esattamente al tuo caso d'uso, ma qui c'è uno snippet di un'intestazione C ++ 'cogged' ( Datum.h
) che contiene bit di generazione di codice Python all'interno di un 'magic comment':
class Datum
{
public:
Datum(
v1_0::acme::SocketCanStub& stub,
std::string const& name,
uint8_t offset,
uint8_t length,
bool bigEndian
)
: stub_(stub)
, name_(name)
, offset_(offset)
, length_(length)
, bigEndian_(bigEndian)
{ }
virtual ~Datum() = default;
void Process(struct can_frame const& msg);
virtual void Update(uint64_t rawValue) = 0;
boost::any const& Value() const { return value_; }
protected:
v1_0::acme::SocketCanStub& stub_;
std::string name_;
uint8_t offset_; // <-- in bits
uint8_t length_; // <-- in bits
bool bigEndian_;
boost::any value_;
};
/* [[[cog
import socketcan.generator
signals = socketcan.generator.parse_can_defs()
for s in signals:
cog.outl('class {}_Datum : public Datum'.format(s.name))
cog.outl('{')
cog.outl(' public:')
cog.outl(' {}_Datum('.format(s.name))
cog.outl(' v1_0::acme::SocketCanStub& stub,')
cog.outl(' std::string const& name,')
cog.outl(' uint8_t offset,')
cog.outl(' uint8_t length,')
cog.outl(' uint8_t bigEndian,')
cog.outl(' uint64_t initval')
cog.outl(' )')
cog.outl(' : Datum(stub, name, offset, length, bigEndian)')
cog.outl(' {')
cog.outl(' this->Update(initval);')
cog.outl(' }')
cog.outl()
cog.outl(' void Update(uint64_t rawValue) override;')
cog.outl('};')
cog.outl()
]]] */
Quando esegui cog.py
su questo file usando:
$ cog.py -r Datum.h
viene eseguito il codice nel commento magico e il suo output viene inserito inline nel file, subito dopo il commento. L'output è simile a questo:
/* [[[cog
import socketcan.generator
signals = socketcan.generator.parse_can_defs()
for s in signals:
cog.outl('class {}_Datum : public Datum'.format(s.name))
cog.outl('{')
cog.outl(' public:')
cog.outl(' {}_Datum('.format(s.name))
cog.outl(' v1_0::acme::SocketCanStub& stub,')
cog.outl(' std::string const& name,')
cog.outl(' uint8_t offset,')
cog.outl(' uint8_t length,')
cog.outl(' uint8_t bigEndian,')
cog.outl(' uint64_t initval')
cog.outl(' )')
cog.outl(' : Datum(stub, name, offset, length, bigEndian)')
cog.outl(' {')
cog.outl(' this->Update(initval);')
cog.outl(' }')
cog.outl()
cog.outl(' void Update(uint64_t rawValue) override;')
cog.outl('};')
cog.outl()
]]] */
class VEH_SPEED_Datum : public Datum
{
public:
VEH_SPEED_Datum(
v1_0::acme::SocketCanStub& stub,
std::string const& name,
uint8_t offset,
uint8_t length,
uint8_t bigEndian,
uint64_t initval
)
: Datum(stub, name, offset, length, bigEndian)
{
this->Update(initval);
}
void Update(uint64_t rawValue) override;
};
class PRND_STAT_Datum : public Datum
{
public:
PRND_STAT_Datum(
v1_0::acme::SocketCanStub& stub,
std::string const& name,
uint8_t offset,
uint8_t length,
uint8_t bigEndian,
uint64_t initval
)
: Datum(stub, name, offset, length, bigEndian)
{
this->Update(initval);
}
void Update(uint64_t rawValue) override;
};
class TurnIndLvr_Stat_Datum : public Datum
{
public:
TurnIndLvr_Stat_Datum(
v1_0::acme::SocketCanStub& stub,
std::string const& name,
uint8_t offset,
uint8_t length,
uint8_t bigEndian,
uint64_t initval
)
: Datum(stub, name, offset, length, bigEndian)
{
this->Update(initval);
}
void Update(uint64_t rawValue) override;
};
// [[[end]]]
L'obiettivo è generare una piccola sottoclasse di Datum
per ogni segnale CAN 'interessante' definito in un ish di grandi dimensioni (1,4 MB) kcd il file . È un po 'difficile da spiegare, ma ... ecco il file socketcan/generator.py
completo a cui si fa riferimento nel commento Cog:
#!/usr/bin/env python
import bz2
from collections import namedtuple
SIGNALS = {
'TurnIndLvr_Stat': (
'v1_0::acme::SocketCan::TurnSignalState', # <-- Value type
'getTurnSignalState', # <-- Name of getter method
'0' # <-- Initial (raw) value
),
'VEH_SPEED': (
'float',
'getVehicleSpeed',
'0'
),
'PRND_STAT': (
'v1_0::acme::SocketCan::PrndlState',
'getPrndlState',
'0'
),
}
Signal = namedtuple(
'Signal',
'name id length offset endianess type getter initval'
)
def parse_can_defs():
import os.path
from lxml import etree
kcdfile = os.path.join(os.path.dirname(__file__), 'can-db.kcd.bz2')
with bz2.BZ2File(kcdfile) as f:
tree = etree.parse(f)
signals = []
ns = dict(kcd="http://kayak.2codeornot2code.org/1.0")
for name in SIGNALS.keys():
elem = tree.xpath("//kcd:Signal[@name='{}']".format(name), namespaces=ns)[0]
s = Signal(
name,
int(elem.getparent().attrib['id'], 16),
elem.attrib['length'],
elem.attrib['offset'],
elem.attrib.get('endianess', 'little'),
SIGNALS[name][0],
SIGNALS[name][1],
SIGNALS[name][2],
)
signals.append(s)
return signals
if __name__ == '__main__':
import sys
sys.exit(main())
La "sintassi alternativa" in questo caso è la pura struttura dati Python SIGNALS
, che definisce il sottoinsieme di segnali CAN dal file kcd
a cui l'applicazione si preoccupa. Lo script analizza il file kcd
e scorre i nomi dei segnali, popolando i campi id
, length
e offset
per ciascuno. Lo script di generazione del codice inserito in Datum.h
utilizza i dati restituiti per scrivere il codice. Se il mio capo mi informa che un altro segnale CAN è diventato improvvisamente 'interessante', aggiungo semplicemente un'altra voce al dizionario SIGNALS
in socketcan/generator.py
e rieseguo cog.py
.
Potrebbero esserci altri modi idiomatici per farlo in C # (io principalmente codice in C ++ e Python), ma l'idea di base di dividere la logica in un file separato con cui è più facile lavorare e generare codice da quella rappresentazione alternativa , può essere utile. Il mio esempio è piuttosto complesso e ho dovuto tralasciare alcuni dettagli importanti, ma l'approccio generale è fondamentalmente semplice : trovare un modo per trasformare la codifica manuale, soggetta a errori, di logica insidiosa in dati automatizzati, -drive process.