Non usare mai le stringhe in Java? [chiuso]


73

Mi sono imbattuto in un post di blog che scoraggia l'uso di Strings in Java per rendere il codice privo di semantica, suggerendo che dovresti usare invece le classi thin wrapper. Questi sono gli esempi prima e dopo che detta voce fornisce per illustrare la questione:

public void bookTicket(
  String name,
  String firstName,
  String film,
  int count,
  String cinema);

public void bookTicket(
  Name name,
  FirstName firstName,
  Film film,
  Count count,
  Cinema cinema);

Nella mia esperienza di lettura di blog di programmazione sono giunto alla conclusione che il 90% è una sciocchezza, ma mi chiedo se questo sia un punto valido. In qualche modo non mi sembra giusto, ma non sono riuscito a individuare esattamente ciò che non va con lo stile della programmazione.


Discussione pertinente sul Wiki originale: "che cos'è quello toString () di?"
Christoffer Hammarström,

5
Oh wow, quel post sul blog mi ha fatto sentire fisicamente malato
Rob,

5
Ho letto almeno un libro (che potrebbe essere stata la prima edizione di Code Complete) che mi ha consigliato di scrivere tutti i tipi primitivi per avere nomi dal dominio problematico. Stessa idea
user16764

9
un'istruzione che inizia con "never" è "sempre" false!
Florents Tselai,

1
Quel ragazzo è un CTO ?!
Hyangelo,

Risposte:


88

L'incapsulamento è lì per proteggere il tuo programma dal cambiamento . La rappresentazione di un nome cambierà? In caso contrario, stai perdendo tempo e si applica YAGNI.

Modifica: ho letto il post sul blog e ha una buona idea. Il problema è che si è ridimensionato troppo. Qualcosa del genere String orderId è davvero male, perché presumibilmente "!"£$%^&*())ADAFVFnon è un valido orderId. Ciò significa che Stringrappresenta molti più valori possibili di quelli disponibili orderId. Tuttavia, per qualcosa come un name, quindi non è possibile prevedere quale potrebbe essere o non essere un nome valido e nessuno Stringè valido name.

In primo luogo, stai (correttamente) riducendo i possibili input a solo quelli validi. Nel secondo caso, non hai ottenuto alcun restringimento di possibili input validi.

Modifica di nuovo: considera il caso di input non valido. Se scrivi "Gareth Gobulcoque" come tuo nome, sembrerà sciocco, non sarà la fine del mondo. Se si inserisce un OrderID non valido, è probabile che semplicemente non funzionerà.


5
Con gli ID ordine probabilmente preferirei ancora aggiungere un segno di spunta nel codice accettando gli ID e lasciando la stringa. Le classi dovrebbero fornire metodi per operare con i dati. Se una classe verifica solo la validità di alcuni dati e quindi non fa nulla, questo non mi sembra corretto. OOP è buono, ma non dovresti esagerare.
Malcolm,

13
@Malcolm: Ma allora non hai modo di sapere quali stringhe sono convalidate se non per convalidarle ancora e ancora.
DeadMG

7
"[...] non è possibile prevedere quale potrebbe essere o meno un nome valido e ogni stringa è un nome valido." Immagino che un punto di forza di questa tecnica sia che se il tuo parametro è di tipo Name, non puoi passare accidentalmente un altro valore String non correlato. Laddove ci si aspetta un Name, Nameverrà compilato solo un test e la stringa "Ti devo $ 5" non può essere accidentalmente accettata. (Nota che questo non è correlato a nessun tipo di validazione del nome!)
Andres F.

6
La creazione di tipi specifici per un uso particolare aggiunge ricchezza semantica e aiuta ad aggiungere sicurezza. Inoltre, la creazione di tipi per rappresentare valori specifici aiuta molto quando si utilizza un contenitore IoC per autorowire le classi - facile risolvere il bean giusto da usare quando è l'unico bean registrato per una classe particolare. Sono necessari molti più sforzi quando è solo una delle tante stringhe registrate.
Dathan,

3
@DeadMG Questo è polemico, e non qualcosa che possiamo risolvere nei commenti. Direi che succede valori errati di tipi primitivi e rafforzare le interfacce è un modo per migliorare la situazione.
Andres F.

46

È semplicemente pazzo :)


2
Anche questa è la mia opinione, ma il fatto è che ricordo questo suggerimento in "Codice completo". Naturalmente non tutto è indiscutibile in quel libro, ma almeno mi fa pensare due volte prima di rifiutare un'idea.
DPM,

8
Hai appena citato alcuni slogan senza alcuna vera giustificazione. Puoi approfondire la tua opinione? Alcuni schemi e caratteristiche del linguaggio accettati, ad esempio, possono sembrare una complessità aggiunta, ma offrono qualcosa di prezioso in cambio (ad esempio: digitazione statica)
Andres F.

12
Il buon senso è tutt'altro che comune.
Kaz Dragon,

1
+1, a questo aggiungerei che quando una lingua supporta parametri nominati e l'IDE è buono, come nel caso di C # e VS2010, non si deve compensare la mancanza di funzionalità nella lingua con i modelli folli . Non è necessario per la classe denominata X e una classe denominata Y, se si può var p = new Point(x: 10, y:20);anche scrivere , non è che Cinema sia così diverso da una stringa. Comprenderei se si trattasse di quantità fisiche, come pressione, temperatura, energia, dove le unità differiscono e alcune non possono essere negative. L'autore del blog deve provare funzionale.
Giobbe

1
+1 per "Non sovraccaricare!"
Ivan,

23

Sono d'accordo con l'autore, principalmente. Se esiste un comportamento peculiare in un campo, come la convalida di un ID ordine, allora creerei una classe per rappresentare quel tipo. Il suo secondo punto è ancora più importante: se hai una serie di campi che rappresentano un concetto come un indirizzo, crea una classe per quel concetto. Se stai programmando in Java, stai pagando un prezzo elevato per la digitazione statica. Puoi anche ottenere tutto il valore che puoi.


2
Il commentatore può commentare?
Kevin Cline, il

Qual è il prezzo elevato che stiamo pagando per la digitazione statica?
Richard Tingle,

@RichardTingle: il tempo per ricompilare il codice e riavviare la JVM per ogni modifica del codice. Il tempo perso quando si effettua una modifica compatibile con l'origine ma incompatibile con il binario e le cose che devono essere ricompilate non lo sono e si ottiene "MissingMethodException".
Kevin Cline,

Non ho mai avuto problemi con le applicazioni java (cosa che con la compilazione continua di solito è già compilata quando ho iniziato a correre) ma è un punto giusto con le GUERRA per il web per le quali la ridistribuzione sembra un po 'più lenta
Richard Tingle il

16

Non farlo; complicherà le cose e tu non ne avrai bisogno

... è la risposta che avrei scritto qui 2 anni fa. Ora, però, non ne sono così sicuro; in effetti, negli ultimi mesi ho iniziato a migrare il vecchio codice in questo formato, non perché non ho niente di meglio da fare, ma perché ne avevo davvero bisogno per implementare nuove funzionalità o modificare quelle esistenti. Capisco le avversioni automatiche che altri qui vedono quel codice, ma penso che questo sia qualcosa che merita una riflessione seria.


Benefici

Il principale vantaggio è la possibilità di modificare ed estendere il codice. Se usi

class Point {
    int x,y;
    // other point operations
}

invece di passare solo un paio di numeri interi - cosa che, sfortunatamente, molte interfacce fanno - allora diventa molto più facile aggiungere successivamente un'altra dimensione. Oppure modifica il tipo in double. Se lo usi List<Author> authorso List<Person> authorsinvece List<String> authorsin un secondo momento diventa molto più semplice aggiungere ulteriori informazioni a ciò che rappresenta un autore. Scrivere in questo modo mi sembra di affermare l'ovvio, ma in pratica sono stato colpevole di usare le stringhe in questo modo molte volte me stesso, specialmente nei casi in cui all'inizio non era ovvio, quindi avrei bisogno di più di una stringa.

Attualmente sto cercando di refactificare un elenco di stringhe che è intrecciato in tutto il mio codice perché ho bisogno di ulteriori informazioni lì e sento il dolore: \

Oltre a ciò, concordo con l'autore del blog sul fatto che contiene più informazioni semantiche , facilitando la comprensione da parte del lettore. Mentre ai parametri vengono spesso dati nomi significativi e si ottiene una linea dedicata di documentazione, questo non è spesso il caso di campi o locali.

Il vantaggio finale è la sicurezza del tipo , per ovvi motivi, ma ai miei occhi è una cosa minore qui.

svantaggi

Ci vuole più tempo per scrivere . Scrivere una piccola classe è semplice e veloce ma non facile, soprattutto se hai bisogno di molte di queste classi. Se ti ritrovi a fermarti ogni 3 minuti per scrivere una nuova classe di wrapper, può anche essere un vero danno per la tua concentrazione. Mi piacerebbe pensare, tuttavia, che questo stato di sforzo di solito avverrà solo nella prima fase della scrittura di qualsiasi pezzo di codice; Di solito riesco a farmi un'idea abbastanza veloce di quali entità dovranno essere coinvolte.

Può coinvolgere molti setter (o costruzioni) e getter ridondanti . L'autore del blog fornisce l'esempio davvero brutto di new Point(x(10), y(10))invece di new Point(10, 10), e vorrei aggiungere che un utilizzo potrebbe anche comportare cose come Math.max(p.x.get(), p.y.get())invece di Math.max(p.x, p.y). E il codice lungo è spesso considerato più difficile da leggere, e giustamente. Ma in tutta onestà, ho la sensazione che un sacco di codice muova gli oggetti, e solo alcuni metodi lo creano, e ancora meno hanno bisogno di accedere ai suoi dettagli grintosi (che comunque non è OOPy).

Discutibile

Direi che questo è discutibile o meno con la leggibilità del codice. Sì, più informazioni semantiche, ma codice più lungo. Sì, è più facile capire il ruolo di ciascun locale, ma è più difficile capire cosa si può fare con esso a meno che non si vada a leggere la sua documentazione.


Come con la maggior parte delle altre scuole di pensiero di programmazione, penso che non sia salutare portare questo estremo. Non mi vedo mai separare le coordinate xey per essere di un tipo diverso. Non penso Countsia necessario quando intdovrebbe bastare. Non mi piace l' unsigned intuso in C - sebbene teoricamente buono, semplicemente non ti dà abbastanza informazioni e proibisce di estendere il tuo codice in seguito per supportare quel magico -1. A volte hai bisogno di semplicità.

Penso che il post sul blog sia un po 'estremo. Ma nel complesso, ho imparato dall'esperienza dolorosa che l'idea di base è fatta dalle cose giuste.

Ho una profonda avversione per il codice troppo ingegnerizzato. Lo faccio davvero. Ma usato bene, non credo che questa ingegneria eccessiva.


5

Anche se è un po 'eccessivo, penso spesso che la maggior parte delle cose che ho visto sia invece sottosegnata.

Non è solo "Sicurezza", Una delle cose davvero belle di Java è che ti aiuta molto a ricordare / capire esattamente ciò che ogni chiamata a un metodo di libreria richiede / si aspetta.

La peggiore (di gran lunga) libreria Java con cui ho lavorato è stata scritta da qualcuno che amava molto Smalltalk e ha modellato la libreria GUI per renderla più simile a smalltalk - il problema è che ogni metodo ha preso lo stesso oggetto base, ma in realtà non PUOI UTILIZZARE tutto ciò su cui l'oggetto base poteva essere lanciato, quindi eri di nuovo indovinato cosa passare ai metodi e non sapevi se avessi fallito fino al runtime (Qualcosa che dovevo affrontare ogni singola volta in cui lavorava in C).

Un altro problema: se si passano attorno a stringhe, ints, raccolte e matrici senza oggetti, tutto ciò che si ha sono sfere di dati senza significato. Ciò sembra naturale quando si pensa in termini di librerie che "alcune app" useranno, ma quando si progetta un'intera app è molto più utile assegnare significato (codice) a tutti i dati nel luogo in cui i dati sono definiti e pensare solo in termini di come questi oggetti di alto livello interagiscono. Se stai passando intorno alle primitive anziché agli oggetti, allora, per definizione, stai modificando i dati in un posto diverso da dove sono definiti (Questo è anche il motivo per cui non mi piacciono davvero Setter e Getter - stesso concetto, stai operando su dati che non sono tuoi).

Infine, se definisci oggetti separati per tutto, hai sempre un ottimo posto per convalidare tutto, ad esempio se crei un oggetto per il codice postale e successivamente scopri che devi assicurarti che il codice postale includa sempre l'estensione a 4 cifre che hai un posto perfetto per dirlo.

In realtà non è una cattiva idea. Pensandoci non sono nemmeno sicuro che direi che è stato progettato in modo eccessivo, è semplicemente più facile lavorare in quasi tutti i modi - l'unica eccezione è la proliferazione di piccole classi ma le classi Java sono così leggere e facili scrivere che questo non è nemmeno un costo (possono anche essere generati).

Sarei davvero interessato a vedere un progetto java ben scritto che in realtà avesse definito troppe classi (dove ciò rendeva più difficile la programmazione), sto iniziando a pensare che non fosse possibile avere troppe classi.


3

Penso che tu debba guardare questo concetto da un altro punto di partenza. Dai uno sguardo dal punto di vista di un progettista di database: i tipi passati nel primo esempio non definiscono i tuoi parametri in un modo unico, per non parlare di un modo utile.

public void bookTicket(
  String name,
  String firstName,
  String film,
  int count,
  String cinema);

Sono necessari due parametri per specificare l'utente effettivo che sta prenotando i biglietti, potresti avere due film diversi con nomi identici (ad esempio remake), potresti avere lo stesso film con nomi diversi (ad esempio traduzioni). Una certa catena di cinema potrebbe avere filiali diverse, quindi come lo affronterai in una stringa e in modo coerente (ad esempio stai usando $chain ($city)o $chain in $cityo anche qualcos'altro e come ti assicurerai che questo sia utilizzato in modo coerente. Il peggio in realtà è specificare il tuo utente per mezzo di due parametri, il fatto che siano forniti sia un nome che un cognome non garantisce un cliente valido (e non puoi distinguere due John Doesecondi).

La risposta a questo è dichiarare i tipi, ma questi raramente saranno involucri sottili come mostrerò sopra. Molto probabilmente, funzioneranno come archivio di dati o saranno accoppiati a un qualche tipo di database. Quindi un Cinemaoggetto avrà probabilmente un nome, una posizione, ... e in questo modo ti libererai di tali ambiguità. Se sono involucri sottili, lo sono per coincidenza.

Quindi, IMHO il post sul blog sta solo dicendo "assicurati di passare i tipi corretti", il suo autore ha appena fatto la scelta troppo vincolata per scegliere in particolare i tipi di dati di base (che è il messaggio sbagliato).

L'alternativa proposta è migliore:

public void bookTicket(
  Name name,
  FirstName firstName,
  Film film,
  Count count,
  Cinema cinema);

D'altra parte, penso che il post del blog vada troppo lontano con il confezionamento di tutto. Countè troppo generico, potrei contare mele o arance con quello, aggiungerli e avere ancora una situazione nelle mie mani in cui il sistema di tipi mi permette di fare operazioni senza senso. Ovviamente puoi applicare la stessa logica del blog e definire tipi CountOfOrangesecc., Ma anche questo è semplicemente stupido.

Per quello che vale, scrivo davvero qualcosa del genere

public Ticket bookTicket(
  Person patron,
  Film film,
  int numberOfTickets,
  Cinema cinema);

Per farla breve: non dovresti passare variabili senza senso; le uniche volte in cui si specifica un oggetto con un valore che non determina l'oggetto reale, è quando si esegue una query (ad es. public Collection<Film> findFilmsWithTitle(String title)) o quando si mette insieme una prova di concetto. Mantieni pulito il tuo sistema di tipi, quindi non utilizzare un tipo troppo generale (ad esempio un film rappresentato da a String) o troppo restrittivo / specifico / inventato (ad esempio Countinvece di int). Utilizzare un tipo che definisce l'oggetto in modo univoco e inequivocabile quando possibile e fattibile.

modifica : un riepilogo ancora più breve. Per piccole applicazioni (ad es. Prova di concetti): perché preoccuparsi di un design complicato? Basta usare Stringo inte andare avanti con esso.

Per applicazioni di grandi dimensioni: è davvero così probabile che tu abbia molte classi che consistono in un singolo campo con un tipo di dati di base? Se hai classi di questo tipo, hai solo oggetti "normali", niente di speciale.

Sento l'idea di incapsulare stringhe, ... è solo un design incompleto: eccessivamente complicato per piccole applicazioni, non abbastanza completo per applicazioni di grandi dimensioni.


Vedo il tuo punto, ma direi che stai assumendo più di quanto l'autore stia affermando. Per quanto ne sappiamo, il suo modello ha solo una stringa per un mecenate, una stringa per un film e così via. Quell'ipotetica funzionalità extra è davvero il punto cruciale del problema, quindi o era molto "distratto" per ometterlo quando si occupava del suo caso o pensa che dovremmo fornire più potere semantico solo perché. Ancora una volta, per quanto ne sappiamo, è quest'ultimo che aveva in mente.
DPM,

@Jubbat: anzi, presumo più di quanto afferma l'autore. Ma il mio punto è che o hai una semplice applicazione, nel qual caso entrambi i casi sono eccessivamente complicati. Per quella scala, la manutenibilità non è un problema e la distinzione semantica impedisce la velocità di codifica. Se, d'altra parte, la tua applicazione è grande, vale la pena definire correttamente i tuoi tipi (ma è improbabile che siano semplici wrapper). IMHO, i suoi esempi non sono convincenti o presentano importanti difetti di progettazione oltre al punto che sta cercando di evidenziare.
Egon,

2

Per me farlo è come usare le regioni in C #. Generalmente se ritieni che ciò sia necessario per rendere leggibile il tuo codice, allora hai problemi più grandi su cui dovresti passare il tempo.


2
+1 per implicare il trattamento dei sintomi piuttosto che la causa.
Giobbe

2

Direi che è davvero una buona idea in una lingua con una caratteristica tipicamente tipizzata fortemente tipizzata.

In Java non hai questo, quindi la creazione di una classe completamente nuova per queste cose significa che il costo probabilmente supera il vantaggio. Puoi anche ottenere l'80% del vantaggio facendo attenzione alla denominazione delle variabili / dei parametri.


0

Sarebbe bello, SE String (end Integer, e ... parlando solo di String) non era definitivo, quindi queste classi potrebbero ESSERE una stringa (limitata) con significato e potrebbero comunque essere inviate a qualche oggetto indipendente che sa come gestire il tipo di base (senza conversare avanti e indietro).

E "buoni" ne aumentano quando ci sono ad es. restrizioni su tutti i nomi.

Ma quando si crea un'app (non una libreria), è sempre possibile modificarla. Quindi prefare di iniziare senza di essa.


0

Per fare un esempio: nel nostro sistema attualmente sviluppato, ci sono molte entità diverse che possono essere identificate da più tipi di identificatori (a causa dell'uso di sistemi esterni), a volte anche lo stesso tipo di entità. Tutti gli identificatori sono stringhe, quindi se qualcuno confonde quale tipo di ID debba essere passato come parametro, non viene mostrato alcun errore di tempo di compilazione, ma il programma esploderà in fase di esecuzione. Questo succede abbastanza spesso. Quindi devo dire che lo scopo principale di questo principio non è quello di proteggere dal cambiamento (anche se serve anche a quello), ma di proteggerci dagli insetti. E comunque, se qualcuno progetta un'API, deve riflettere i concetti del dominio, quindi è concettualmente utile definire classi specifiche del dominio: tutto dipende dal fatto che ci sia ordine nella mente degli sviluppatori,


@downvoter: puoi per favore dare una spiegazione?
thSoft
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.