Uso del controllo di tipo statico per la protezione da errori di business

13

Sono un grande fan del controllo di tipo statico. Ti impedisce di commettere errori stupidi come questo:

// java code
Adult a = new Adult();
a.setAge("Roger"); //static type checker would complain
a.setName(42); //and here too

Ma non ti impedisce di commettere errori stupidi come questo:

Adult a = new Adult();
// obviously you've mixed up these fields, but type checker won't complain
a.setAge(150); // nobody's ever lived this old
a.setWeight(42); // a 42lb adult would have serious health issues

Il problema si presenta quando si utilizza lo stesso tipo per rappresentare ovviamente diversi tipi di informazioni. Stavo pensando che una buona soluzione sarebbe estendere la classe Integer , solo per evitare errori di logica aziendale, ma non aggiungere funzionalità. Ad esempio:

class Age extends Integer{};
class Pounds extends Integer{};

class Adult{
    ...
    public void setAge(Age age){..}
    public void setWeight(Pounds pounds){...}
}

Adult a = new Adult();
a.setAge(new Age(42));
a.setWeight(new Pounds(150));

Questa è considerata una buona pratica? O ci sono problemi ingegneristici imprevisti lungo la strada con un design così restrittivo?

    
posta J-bob 02.07.2018 - 23:06
fonte

3 risposte

12

Stai essenzialmente chiedendo un sistema di unità (no, non test di unità, "unità" come in "unità fisica", come metri, volt ecc.).

Nel tuo codice Age rappresenta il tempo e Pounds rappresenta la massa. Questo porta a cose come conversione delle unità, unità di base, precisione, ecc.

Ci sono stati / sono tentativi di ottenere una cosa del genere in Java, ad esempio:

Le ultime due sembrano vivere in questa cosa github: link

C ++ ha unità tramite Boost

LabView viene fornito con un gruppo di unità .

Ci sono altri esempi in altre lingue. (le modifiche sono benvenute)

Is this considered good practice?

Come puoi vedere sopra, più è probabile che venga usata una lingua per gestire i valori con le unità, più nativamente supporta le unità. LabView viene spesso utilizzato per interagire con i dispositivi di misurazione. Come tale ha senso avere una tale caratteristica nella lingua e usarla sarebbe sicuramente considerata una buona pratica.

Ma in qualsiasi linguaggio di alto livello di carattere generale, in cui la domanda è bassa per una tale quantità di rigore, è probabilmente inaspettato.

Or are there unforeseen engineering problems down the road with such a restrictive design?

La mia ipotesi potrebbe essere: prestazioni / memoria. Se gestisci molti valori, il sovraccarico di un oggetto per valore potrebbe diventare un problema. Ma come sempre: L'ottimizzazione prematura è la radice di tutti i mali .

Penso che il "problema" più grande sia quello di far abituare la gente, dato che l'unità è solitamente definita implicitamente in questo modo:

class Adult
{
    ...
    public void setAge(int ageInYears){..}

Le persone saranno confuse quando devono passare un oggetto come valore per qualcosa che potrebbe apparentemente essere descritto con un semplice int , quando non hanno familiarità con i sistemi di unità.

    
risposta data 03.07.2018 - 00:31
fonte
6

Al contrario della risposta di null, definire un tipo per una "unità" può essere utile se un numero intero non è sufficiente per descrivere la misurazione. Ad esempio, il peso è spesso misurato in più unità all'interno dello stesso sistema di misurazione. Pensa "sterline" e "once" o "chilogrammi" e "grammi".

Se hai bisogno di un livello di misurazione più granulare, la definizione di un tipo per l'unità è vantaggiosa:

public struct Weight {
    private int pounds;
    private int ounces;

    public Weight(int pounds, int ounces) {
        // Value range checks go here
        // Throw exception if ounces is greater than 16?
    }

    // Getters go here
}

Per qualcosa come "età" raccomando di calcolarlo in fase di esecuzione in base alla data di nascita della persona:

public class Adult {
    private Date birthDate;

    public Interval getCurrentAge() {
        return calculateAge(Date.now());
    }

    public Interval calculateAge(Date date) {
        // Return interval between birthDate and date
    }
}
    
risposta data 03.07.2018 - 15:41
fonte
2

Ciò che sembra cercare è noto come tipi di tag . Sono un modo per dire "questo è un numero intero che rappresenta l'età" mentre "questo è anche un numero intero ma rappresenta il peso" e "non puoi assegnarne uno all'altro". Nota, questo va oltre le unità fisiche come metri o chilogrammi: potrei avere nel mio programma "altezze della gente" e "distanze tra i punti su una mappa", entrambi misurati in metri, ma non compatibili tra loro da quando si assegnava uno a l'altro non ha senso dal punto di vista della logica aziendale.

Alcune lingue, come Scala supportano i tipi con tag in modo abbastanza semplice (vedi il link sopra). In altri, puoi creare le tue classi wrapper, ma questo è meno conveniente.

Convalida, ad es. verificare che l'altezza di una persona sia "ragionevole" è un altro problema. Puoi inserire tale codice nella tua classe Adult (costruttore o setter) o nelle classi type / wrapper codificate. In un certo senso, classi built-in come URL o UUID soddisfano tale ruolo (tra gli altri, ad esempio fornendo metodi di utilità).

Se l'utilizzo di tipi di tag o classi wrapper contribuirà effettivamente a migliorare il codice, dipenderà da diversi fattori. Se i tuoi oggetti sono semplici e hanno pochi campi, il rischio di assegnarli erroneamente è basso e il codice aggiuntivo necessario per utilizzare i tipi di tag potrebbe non valerne la pena. In sistemi complessi con strutture complesse e molti campi (specialmente se molti di essi condividono lo stesso tipo primitivo), potrebbe essere utile.

Nel codice che scrivo, spesso creo classi wrapper se passo le mappe. Tipi come Map<String, String> sono molto opachi da soli, quindi avvolgerli in classi con nomi significativi come NameToAddress aiuta molto. Ovviamente, con i tipi di tag, potresti scrivere Map<Name, Address> e non è necessario il wrapper per l'intera mappa.

Tuttavia, per tipi semplici come Stringhe o Interi, ho trovato che le classi wrapper (in Java) sono troppo fastidiose. La regolare logica di business non era poi così male, ma sono sorti diversi problemi con la serializzazione di questi tipi su JSON, il loro mapping su oggetti DB, ecc. È possibile scrivere mapper e hook per tutti i framework di grandi dimensioni (es. Jackson e Spring Data), ma il lavoro extra e la manutenzione relativi a questo codice compenseranno tutto ciò che si ottiene usando questi wrapper. Ovviamente, YMMV e in un altro sistema, la bilancia potrebbe essere diversa.

    
risposta data 04.07.2018 - 21:00
fonte

Leggi altre domande sui tag