Costanti in Java: pattern e anti-pattern

1. Introduzione

In questo articolo, impareremo come usare le costanti in Java con particolare attenzione ai modelli e agli anti-pattern comuni.

Inizieremo con alcune convenzioni di base per la definizione delle costanti. Da lì, passeremo agli anti-pattern comuni prima di terminare con uno sguardo ai pattern comuni.

2. Nozioni di base

Una costante è una variabile il cui valore non cambierà dopo essere stata definita.

Diamo un'occhiata alle basi per la definizione di una costante:

private static final int OUR_CONSTANT = 1;

Alcuni dei modelli che esamineremo affronteranno la decisione del modificatore di accesso pubblico o privato . Rendiamo le nostre costanti statiche e finali e diamo loro un tipo appropriato, che sia una primitiva Java, una classe o un'enumerazione . Il nome dovrebbe essere tutte lettere maiuscole con le parole separate da trattini bassi , a volte noto come caso serpente urlante. Infine, forniamo il valore stesso.

3. Anti-pattern

Per prima cosa, iniziamo imparando cosa non fare. Diamo un'occhiata a un paio di anti-pattern comuni che potremmo incontrare quando lavoriamo con costanti Java.

3.1. Numeri magici

I numeri magici sono letterali numerici in un blocco di codice:

if (number == 3.14159265359) { // ... }

Sono difficili da capire per gli altri sviluppatori. Inoltre, se utilizziamo un numero in tutto il codice, è difficile gestire la modifica del valore. Dovremmo invece definire il numero come una costante.

3.2. Una grande classe di costanti globali

Quando iniziamo un progetto, potrebbe sembrare naturale creare una classe denominata Constants o Utils con l'intenzione di definire tutte le costanti per l'applicazione lì. Per progetti più piccoli, questo potrebbe essere ok, ma consideriamo un paio di motivi per cui questa non è una soluzione ideale.

Innanzitutto, immaginiamo di avere cento o più costanti tutte nella nostra classe di costanti. Se la classe non viene mantenuta, sia per tenere il passo con la documentazione sia per refactoring occasionalmente delle costanti in raggruppamenti logici, diventerà piuttosto illeggibile. Potremmo persino ritrovarci con costanti duplicate con nomi leggermente diversi. È probabile che questo approccio ci dia problemi di leggibilità e manutenibilità in tutto tranne che nei progetti più piccoli.

Oltre alla logistica del mantenimento della stessa classe Costanti , stiamo anche invitando altri problemi di manutenibilità incoraggiando troppa interdipendenza con questa classe di costanti globali e varie altre parti della nostra applicazione.

Da un punto di vista più tecnico, il compilatore Java inserisce il valore della costante nelle variabili di riferimento nelle classi in cui le usiamo . Quindi, se modifichiamo una delle nostre costanti nella nostra classe di costanti e ricompiliamo solo quella classe e non la classe di riferimento, possiamo ottenere valori costanti incoerenti.

3.3. The Constant Interface Anti-Pattern

Il modello di interfaccia costante è quando definiamo un'interfaccia che contiene tutte le costanti per determinate funzionalità e quindi abbiamo le classi che necessitano di tali funzionalità per implementare l'interfaccia.

Definiamo un'interfaccia costante per una calcolatrice:

public interface CalculatorConstants { double PI = 3.14159265359; double UPPER_LIMIT = 0x1.fffffffffffffP+1023; enum Operation {ADD, SUBTRACT, MULTIPLY, DIVIDE}; }

Successivamente, implementeremo la nostra interfaccia CalculatorConstants :

public class GeometryCalculator implements CalculatorConstants { public double operateOnTwoNumbers(double numberOne, double numberTwo, Operation operation) { // Code to do an operation } }

Il primo argomento contro l'utilizzo di un'interfaccia costante è che va contro lo scopo di un'interfaccia. Abbiamo lo scopo di utilizzare le interfacce per creare un contratto per il comportamento che forniranno le nostre classi di implementazione. Quando creiamo un'interfaccia piena di costanti, non definiamo alcun comportamento.

In secondo luogo, l'utilizzo di un'interfaccia costante ci apre a problemi di runtime causati dall'ombreggiatura del campo. Diamo un'occhiata a come ciò potrebbe accadere definendo una costante UPPER_LIMIT all'interno della nostra classe GeometryCalculator :

public static final double UPPER_LIMIT = 100000000000000000000.0;

Una volta definita quella costante nella nostra classe GeometryCalculator , nascondiamo il valore nell'interfaccia CalculatorConstants per la nostra classe. Potremmo quindi ottenere risultati inaspettati.

Un altro argomento contro questo anti-pattern è che causa l'inquinamento dello spazio dei nomi. Le nostre CalculatorConstants saranno ora nello spazio dei nomi per una qualsiasi delle nostre classi che implementano l'interfaccia, nonché qualsiasi loro sottoclasse.

4. Modelli

In precedenza, abbiamo esaminato la forma appropriata per la definizione delle costanti. Diamo un'occhiata ad alcune altre buone pratiche per la definizione delle costanti all'interno delle nostre applicazioni.

4.1. Buone pratiche generali

Se le costanti sono logicamente correlate a una classe, possiamo semplicemente definirle lì. Se vediamo un insieme di costanti come membri di un tipo enumerato, possiamo usare un enum per definirli.

Definiamo alcune costanti in una classe Calculator :

public class Calculator { public static final double PI = 3.14159265359; private static final double UPPER_LIMIT = 0x1.fffffffffffffP+1023; public enum Operation { ADD, SUBTRACT, DIVIDE, MULTIPLY } public double operateOnTwoNumbers(double numberOne, double numberTwo, Operation operation) { if (numberOne > UPPER_LIMIT) { throw new IllegalArgumentException("'numberOne' is too large"); } if (numberTwo > UPPER_LIMIT) { throw new IllegalArgumentException("'numberTwo' is too large"); } double answer = 0; switch(operation) { case ADD: answer = numberOne + numberTwo; break; case SUBTRACT: answer = numberOne - numberTwo; break; case DIVIDE: answer = numberOne / numberTwo; break; case MULTIPLY: answer = numberOne * numberTwo; break; } return answer; } }

Nel nostro esempio, abbiamo definito una costante per UPPER_LIMIT che stiamo pianificando di utilizzare solo nella classe Calculator , quindi l'abbiamo impostata su private . Vogliamo che altre classi siano in grado di usare PI e Operation enum, quindi le abbiamo impostate su public .

Consideriamo alcuni dei vantaggi dell'utilizzo di un enum per Operation . Il primo vantaggio è che limita i valori possibili. Immagina che il nostro metodo prenda una stringa per il valore dell'operazione con l'aspettativa che venga fornita una delle quattro stringhe costanti. Possiamo facilmente prevedere uno scenario in cui uno sviluppatore che chiama il metodo invia il proprio valore di stringa. Con l' enum , i valori sono limitati a quelli che definiamo. Possiamo anche vedere che le enumerazioni sono particolarmente adatte per l'uso nelle istruzioni switch .

4.2. Costanti Classe

Now that we've looked at some general good practices, let's consider the case when a constants class might be a good idea. Let's imagine our application contains a package of classes that need to do various kinds of mathematical calculations. In this case, it probably makes sense for us to define a constants class in that package for constants that we'll use in our calculations classes.

Let's create a MathConstants class:

public final class MathConstants { public static final double PI = 3.14159265359; static final double GOLDEN_RATIO = 1.6180; static final double GRAVITATIONAL_ACCELERATION = 9.8; static final double EULERS_NUMBER = 2.7182818284590452353602874713527; public enum Operation { ADD, SUBTRACT, DIVIDE, MULTIPLY } private MathConstants() { } }

The first thing we should notice is that our class is final to prevent it from being extended. Additionally, we've defined a private constructor so it can't be instantiated. Finally, we can see that we've applied the other good practices we discussed earlier in the article. Our constant PI is public because we anticipate needing to access it outside of our package. The other constants we've left as package-private, so we can access them within our package. We've made all of our constants static and final and named them in a screaming snake case. The operations are a specific set of values, so we've used an enum per definirli.

Possiamo vedere che la nostra specifica classe di costanti a livello di pacchetto è diversa da una grande classe di costanti globali perché è localizzata nel nostro pacchetto e contiene costanti rilevanti per le classi di quel pacchetto.

5. conclusione

In questo articolo, abbiamo considerato i pro ei contro di alcuni dei pattern e degli anti-pattern più popolari visti quando si usano le costanti in Java. Abbiamo iniziato con alcune regole di formattazione di base, prima di trattare gli anti-pattern. Dopo aver appreso un paio di anti-pattern comuni, abbiamo esaminato i pattern che spesso vediamo applicati alle costanti.

Come sempre il codice è disponibile su GitHub.