Non usare mai le stringhe in Java? [chiuso]

72

Mi sono imbattuto in un post di blog scoraggiando l'uso di Strings in Java per far sì che il codice manchi di semantica, suggerendo invece di utilizzare invece classi di wrapper sottili. Questo è l'esempio precedente e successivo che la voce fornita 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 dei blog di programmazione sono giunto alla conclusione che il 90% non ha senso, 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 di programmazione.

    
posta DPM 25.01.2012 - 23:04
fonte

10 risposte

87

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

Modifica: ho letto il post del blog e ha un'idea fondamentalmente buona. Il problema è che l'ha ridimensionato troppo. Qualcosa come String orderId è veramente pessimo, perché presumibilmente "!"£$%^&*())ADAFVF non è un orderId valido. Ciò significa che String rappresenta molti più valori possibili di quanti ce ne siano validi orderId s. Tuttavia, per qualcosa di simile a name , non puoi quindi prevedere quale potrebbe essere o non essere un nome valido, e qualsiasi String è un name valido.

In prima istanza, stai riducendo (correttamente) i possibili input a quelli validi. In seconda istanza, non hai ottenuto alcun restringimento di possibili input validi.

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

    
risposta data 25.01.2012 - 23:10
fonte
46

È semplicemente pazzesco:)

risposta data 25.01.2012 - 23:09
fonte
23

Sono d'accordo con l'autore, soprattutto. Se c'è un comportamento qualsiasi peculiare in un campo, come la convalida di un ID ordine, creo una classe per rappresentare quel tipo. Il secondo punto è ancora più importante: se si dispone di un insieme di campi che rappresentano un concetto come un indirizzo, creare una classe per tale concetto. Se si sta programmando in Java, si paga un prezzo elevato per la digitazione statica. Puoi anche ottenere tutto il valore che puoi.

    
risposta data 26.01.2012 - 00:28
fonte
16

Non farlo; sarà molto complicato e tu non ne avresti 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 avessi nulla di meglio da fare, ma perché ne avevo davvero bisogno per implementare nuove funzionalità o modificare quelle esistenti. Capisco le avversioni automatiche che gli altri hanno visto in quel codice, ma penso che questo sia qualcosa che merita un pensiero serio.

vantaggi

Il vantaggio principale è 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 interi in giro - che è qualcosa che, purtroppo, fanno molte interfacce - allora diventa molto più facile aggiungere in seguito un'altra dimensione. Oppure modifica il tipo in double . Se utilizzi List<Author> authors o List<Person> authors anziché List<String> authors , in seguito sarà molto più semplice aggiungere ulteriori informazioni a ciò che rappresenta un autore. Scrivendolo in questo modo mi sento come se stessi affermando 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, avrei avuto bisogno di più di una stringa.

Attualmente sto cercando di ridefinire qualche lista di stringhe che è intrecciata nel mio codice perché ho bisogno di più informazioni lì, e sento il dolore: \

Oltre a ciò, sono d'accordo con l'autore del blog che contiene più informazioni semantiche , rendendo più facile la comprensione da parte del lettore. Mentre ai parametri vengono spesso dati nomi significativi e una linea di documentazione dedicata, spesso non è il caso dei campi o della gente del posto.

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

Inconvenienti

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

Può coinvolgere molti setter (o costruzioni) ridondanti e getter . L'autore del blog fornisce l'esempio veramente 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 lotto di codice si muova attorno agli oggetti, e solo i metodi di selezione lo creano, e ancora meno bisogno di accedere ai suoi dettagli grintosi (che comunque non è OOPy).

Discutibile

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

Come per la maggior parte delle altre scuole di pensiero di programmazione, penso che non sia salutare portare questo estremo all'estremo. Non mi vedo mai separare le coordinate xey per essere ognuna di un tipo diverso. Non penso che Count sia necessario quando un int dovrebbe essere sufficiente. Non mi piace l'utilizzo di unsigned int in C - mentre teoricamente buono, 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 appreso dall'esperienza dolorosa che l'idea alla base è fatta dalle cose giuste.

Ho una profonda avversione al codice troppo ingegnerizzato. Lo so davvero. Ma usato correttamente, non penso che sia un eccesso di ingegneria.

    
risposta data 26.01.2012 - 22:30
fonte
5

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

Non è solo "sicurezza", una delle cose veramente belle di Java è che ti aiuta molto a ricordare / capire solo quello che ogni chiamata di metodo di una determinata libreria ha bisogno / si aspetta.

La WORST (di gran lunga) libreria Java con cui ho lavorato è stata scritta da qualcuno che era molto affezionato a Smalltalk e ha modellato la libreria GUI dopo di esso per renderlo più simile a smalltalk - il problema è che ogni metodo ha preso lo stesso oggetto di base, ma in realtà non poteva UTILIZZARE tutto ciò su cui poteva essere lanciato l'oggetto base, quindi tornavi a indovinare cosa passare in metodi e non sapendo se avevi fallito fino al runtime (Qualcosa che dovevo affrontare ogni singolo momento in cui stavo lavorando in C).

Un altro problema: se si passano stringhe, int, 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 "qualche app" utilizzerà, ma quando si progetta un'intera app è molto più utile assegnare un significato (codice) a tutti i dati nel luogo in cui i dati sono definiti e pensare solo in termini di interazione tra questi oggetti di alto livello. Se passi intorno ai primitivi invece degli oggetti, per definizione, stai modificando i dati in un luogo diverso da quello in cui è definito (questo è anche il motivo per cui davvero non mi piacciono i setter ei getter - lo stesso concetto, tu stai operando su dati che non sono i tuoi).

Infine, se si definiscono oggetti separati per tutto ciò si ha sempre un ottimo posto per convalidare tutto - ad esempio se si crea un oggetto per il codice di avviamento postale e successivamente si scopre che è necessario assicurarsi che il codice postale includa sempre l'estensione di 4 cifre hai un posto perfetto per metterlo.

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

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

    
risposta data 26.01.2012 - 02:11
fonte
3

Penso che devi guardare questo concetto da un altro punto di partenza. Dai un'occhiata alla prospettiva di un designer di database: i tipi passati nel primo esempio non definiscono i tuoi parametri in modo univoco, 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 reale 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 sale cinematografiche potrebbe avere diverse filiali, quindi come hai intenzione di affrontarle in una stringa e in modo coerente (ad esempio stai usando $chain ($city) o $chain in $city o anche qualcos'altro e come stai andando per assicurarci che questo sia usato in modo coerente. Il peggio in realtà è specificare il tuo cliente per mezzo di due parametri, il fatto che sia un nome e un cognome siano forniti non garantisce un cliente valido (e non puoi distinguere due John Doe s) .

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

Quindi, IMHO il post del blog sta solo dicendo "assicurati di passare i tipi corretti", il suo autore ha appena fatto la scelta eccessivamente 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 sul blog vada troppo lontano con il wrapping di tutto. Count è troppo generico, potrei contare mele o arance con questo, aggiungerle e avere ancora una situazione nelle mie mani dove il sistema di tipi mi permette di fare operazioni senza senso. Ovviamente puoi applicare la stessa logica del blog e definire i tipi CountOfOranges ecc., Ma anche questo è semplicemente sciocco.

Per quello che vale, in realtà scriverei qualcosa come

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

Per farla breve: non si devono passare variabili insensate; le uniche volte in cui specifichi effettivamente un oggetto con un valore che non determina l'oggetto reale, è quando esegui una query (ad esempio public Collection<Film> findFilmsWithTitle(String title) ) o quando crei un proof-of-concept. Mantieni pulito il tuo sistema di tipi, quindi non utilizzare un tipo troppo generico (ad esempio un film rappresentato da String ) o troppo restrittivo / specifico / artificioso (ad esempio Count anziché int ). Usa un tipo che definisca il tuo oggetto in modo univoco e non ambiguo quando possibile e praticabile.

modifica : un riepilogo ancora più breve. Per le piccole applicazioni (ad es. Prova dei concetti): perché preoccuparsi di un design complicato? Usa solo String o int e vai avanti con esso.

Per le applicazioni di grandi dimensioni: è davvero probabile che tu abbia molte classi costituite da un singolo campo con un tipo di dati di base? Se hai poco di queste classi, hai solo oggetti "normali", niente di speciale da fare lì.

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

    
risposta data 28.01.2012 - 17:46
fonte
2

Per me fare questo è come usare le regioni in C #. In genere, se ritieni che ciò sia necessario per rendere leggibile il tuo codice, hai maggiori problemi su cui dovresti passare il tuo tempo.

    
risposta data 25.01.2012 - 23:21
fonte
2

Direi che è una buona idea in una lingua con una caratteristica simile a typedef strongmente tipizzata.

In Java non ce l'hai, quindi creare una nuova classe per queste cose significa che il costo probabilmente supera il vantaggio. Puoi anche ottenere l'80% del vantaggio prestando attenzione alla tua variabile / denominazione dei parametri.

    
risposta data 26.01.2012 - 09:12
fonte
0

Sarebbe bello, IF String (end Integer, e ... parlando solo di String) non era definitivo, quindi queste classi potrebbero essere alcune stringhe (ristrette) con significato e potrebbero ancora essere inviate a qualche oggetto indipendente che lo conosca come gestire il tipo di base (senza conversare avanti e indietro).

E "goodnes" aumentano quando ci sono eg. restrizioni su tutti i nomi.

Ma quando si crea qualche app (non una libreria), è sempre possibile rifattarla. Quindi mi prefio di iniziare senza di esso.

    
risposta data 26.01.2012 - 16:38
fonte
0

Per fare un esempio: nel nostro sistema attualmente sviluppato, ci sono molte diverse entità che possono essere identificate da più tipi di identificatori (a causa dell'uso di sistemi esterni), a volte persino lo stesso tipo di entità. Tutti gli identificatori sono stringhe, quindi se qualcuno mescola il tipo di id che deve essere passato come parametro, non viene mostrato alcun errore di compilazione, ma il programma esploderà in fase di runtime. 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 questo), ma di proteggerci dagli errori. 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, e questo può essere molto facilitato dal sistema di tipi!

    
risposta data 20.04.2012 - 14:20
fonte

Leggi altre domande sui tag