Utilizzo del controllo del tipo statico per proteggere da errori aziendali


13

Sono un grande fan del controllo statico dei tipi. Ti impedisce di fare 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 fare 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. Pensavo che una buona soluzione a ciò sarebbe stata l'estensione della Integerclasse, solo per prevenire errori di logica aziendale, ma non aggiungere funzionalità. Per 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 di ingegneria imprevisti lungo la strada con un design così restrittivo?


5
Non a.SetAge( new Age(150) )compilerai ancora?
John Wu,

1
Il tipo int di Java ha un intervallo fisso. Chiaramente ti piacerebbe avere numeri interi con intervalli personalizzati, ad esempio Integer <18, 110>. Stai cercando tipi di perfezionamento o tipi dipendenti, che offrono alcune lingue (non tradizionali).
Theodoros Chatzigiannakis,

3
Quello di cui stai parlando è un ottimo modo di fare design! Purtroppo, Java ha un sistema di tipi così primitivo che è difficile fare bene. E anche se provi a farlo, potrebbe causare effetti collaterali come prestazioni inferiori o minore produttività degli sviluppatori.
Euforico

@JohnWu sì, il tuo esempio verrà comunque compilato, ma questo è l'unico punto di errore per quella logica. Una volta dichiarato l' new Age(...)oggetto non è possibile assegnarlo erroneamente a una variabile di tipo Weightin nessun altro posto. Riduce il numero di luoghi in cui possono verificarsi errori.
J-bob,

Risposte:


12

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

Nel tuo codice Agerappresenta il tempo e Poundsrappresenta la massa. Questo porta a cose come la conversione di unità, unità di base, precisione, ecc.


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

I due successivi sembrano vivere in questa cosa github: https://github.com/unitsofmeasurement


C ++ ha unità tramite Boost


LabView viene fornito con un gruppo di unità .


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


Questa è considerata una buona pratica?

Come puoi vedere sopra, più è probabile che una lingua venga utilizzata 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 per uso generale, dove la domanda è bassa per tale rigore, è probabilmente inaspettato.

O ci sono problemi di ingegneria imprevisti lungo la strada con un design così restrittivo?

La mia ipotesi sarebbe: performance / memoria. Se hai a che fare con 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 abituare le persone ad esso, in quanto l'unità è generalmente implicitamente definita così:

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

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


"Le persone saranno confuse quando dovranno passare un oggetto come valore per qualcosa che apparentemente potrebbe essere descritto con un semplice int..." --- per il quale abbiamo qui il Principal of Least Surprise. Buona pesca.
Greg Burghardt,

1
Penso che gli intervalli personalizzati lo spostino dal regno di una libreria di unità e nei tipi dipendenti
jk.

6

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

Se è necessario un livello più granulare di misurazione, definire un tipo per l'unità è utile:

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à", consiglio 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
    }
}

2

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

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

La convalida, ad esempio verificare che l'altezza di una persona sia "ragionevole" è un altro problema. Puoi inserire tale codice nella tua Adultclasse (costruttore o setter) o all'interno delle classi di tipo / wrapper con tag. In un certo senso, classi integrate come URLoUUID svolgono tale ruolo (tra l'altro, ad esempio fornendo metodi di utilità).

Se l'utilizzo di tipi con 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 in modo errato è basso e il codice aggiuntivo necessario per utilizzare i tipi con tag potrebbe non valere la pena. In sistemi complessi con strutture complesse e molti campi (specialmente se molti di loro condividono lo stesso tipo primitivo), potrebbe effettivamente essere utile.

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

Tuttavia, per tipi semplici come String o Integer, ho trovato le classi wrapper (in Java) troppo fastidiose. La normale logica aziendale non era poi così male, ma sorgevano una serie di problemi con la serializzazione di questi tipi su JSON, la mappatura su oggetti DB, ecc. È possibile scrivere mapper e hook per tutti i grandi framework (ad esempio Jackson e Spring Data), ma il lavoro extra e la manutenzione relativi a questo codice compenseranno tutto ciò che guadagni dall'utilizzo di questi wrapper. Naturalmente, YMMV e in un altro sistema, il saldo potrebbe essere diverso.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.