¿Nunca usas cadenas en Java? [cerrado]


73

Me topé con una entrada de blog que desalienta el uso de Strings en Java para hacer que su código carezca de semántica, sugiriendo que en su lugar debería usar clases de envoltura delgada. Este es el antes y el después de los ejemplos que proporciona dicha entrada para ilustrar el asunto:

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);

En mi experiencia leyendo blogs de programación, he llegado a la conclusión de que el 90% no tiene sentido, pero me pregunto si este es un punto válido. De alguna manera no me parece bien, pero no pude precisar exactamente qué está mal con el estilo de programación.


Discusión relevante en el Wiki original: "¿de qué es toString ()?"
Christoffer Hammarström

55
Oh wow, esa publicación de blog me hizo sentir físicamente enfermo
Rob

55
He leído al menos un libro (que puede haber sido la primera edición de Code Complete) que recomendó definir todos sus tipos primitivos para tener nombres del dominio del problema. La misma idea.
user16764

9
¡una declaración que comienza con "nunca" es "siempre" falsa!
Florents Tselai

1
¡¿Ese tipo es un CTO ?!
Hyangelo

Respuestas:


88

La encapsulación está ahí para proteger su programa contra cambios . ¿Va a cambiar la representación de un Nombre? Si no, estás perdiendo el tiempo y aplica YAGNI.

Editar: He leído la publicación del blog y tiene una idea fundamentalmente buena. El problema es que lo ha escalado demasiado. Algo así String orderId es realmente malo, porque presumiblemente "!"£$%^&*())ADAFVFno es válido orderId. Esto significa que Stringrepresenta muchos más valores posibles que valores válidos orderId. Sin embargo, para algo así como name, entonces no es posible predecir qué podría o no ser un nombre válido, y cualquiera Stringes válido name.

En primera instancia, está (correctamente) reduciendo las posibles entradas a solo las válidas. En la segunda instancia, no ha logrado reducir las posibles entradas válidas.

Editar de nuevo: considere el caso de una entrada no válida. Si escribes "Gareth Gobulcoque" como tu nombre, se verá tonto, no será el fin del mundo. Si ingresa un OrderID no válido, es probable que simplemente no funcione.


55
Con los ID de pedido, probablemente aún prefiera agregar un cheque en el código que acepte los ID y salga de la cadena. Se supone que las clases proporcionan métodos para operar con datos. Si alguna clase solo verifica la validez de algunos datos y luego no hace nada, esto no me parece apropiado. OOP es bueno, pero no debes excederte.
Malcolm

13
@ Malcolm: Pero no tienes forma de saber qué cadenas están validadas, excepto para validarlas una y otra vez.
DeadMG

77
"[...] no puedes predecir qué podría ser o no un nombre válido, y cualquier Cadena es un nombre válido". Supongo que una de las ventajas de esta técnica es que si su parámetro es de tipo Name, no puede pasar accidentalmente otro valor de cadena no relacionado. Donde esperas un Name, solo Namese compilará un, y la Cadena "Te debo $ 5" no puede aceptarse accidentalmente. (¡Tenga en cuenta que esto no está relacionado con ningún tipo de validación de nombre!)
Andres F.

66
La creación de tipos que son específicos para un uso particular agrega riqueza semántica y ayuda a agregar seguridad. Además, la creación de tipos para representar valores específicos ayuda mucho cuando se usa un contenedor de IoC para conectar automáticamente sus clases: es fácil resolver el bean correcto para usar cuando es el único bean registrado para una clase en particular. Se requiere mucho más esfuerzo cuando es solo una de las muchas cadenas registradas.
Dathan

3
@DeadMG Eso es discutidor, y no es algo que podamos resolver en los comentarios. Diría que los valores erróneos de los tipos primitivos sí ocurren, y fortalecer sus interfaces es una forma de mejorar la situación.
Andres F.

46

Eso es una locura :)


2
Esa es mi opinión también, pero la cuestión es que recuerdo esta sugerencia en "Código completo". Por supuesto, no todo en ese libro es indiscutible, pero al menos me hace pensar dos veces antes de rechazar una idea.
DPM

8
Acabas de mencionar algunas consignas sin ninguna justificación real. ¿Puedes dar más detalles sobre tu opinión? Algunos patrones aceptados y características del lenguaje, por ejemplo, pueden parecer una complejidad adicional, pero ofrecen algo valioso a cambio (por ejemplo: escritura estática)
Andres F.

12
El sentido común es cualquier cosa menos común.
Kaz Dragon

1
+1, a esto agregaría que cuando un idioma admite parámetros con nombre, y el IDE es bueno, como es el caso con C # y VS2010, entonces uno no tiene que compensar la falta de características en el idioma con patrones locos . No hay necesidad de una clase llamada X y una clase llamada Y, si se puede escribir. var p = new Point(x: 10, y:20);Además, no es que Cinema sea tan diferente de una cadena. Entendería si se tratara de cantidades físicas, como presión, temperatura, energía, donde las unidades difieren y algunas no pueden ser negativas. El autor del blog necesita probar funcional.
Trabajo

1
+1 para "¡No sobre-ingenieras!"
Ivan

23

Estoy de acuerdo con el autor, en su mayoría. Si hay algún comportamiento peculiar de un campo, como la validación de una identificación de pedido, entonces crearía una clase para representar ese tipo. Su segundo punto es aún más importante: si tiene un conjunto de campos que representan algún concepto como una dirección, cree una clase para ese concepto. Si está programando en Java, está pagando un alto precio por la escritura estática. También puede obtener todo el valor que pueda.


2
¿Puede el votante comentar?
Kevin Cline

¿Cuál es el alto precio que pagamos por la escritura estática?
Richard Tingle

@ RichardTingle: El momento de recompilar el código y reiniciar la JVM para cada cambio de código. El tiempo perdido cuando realiza un cambio compatible con el origen pero incompatible con el binario y las cosas que deben recompilarse no lo son y obtiene "MissingMethodException".
Kevin Cline

Nunca he tenido un problema con las aplicaciones de Java (con compilación continua, por lo general ya está compilada en el momento en que ejecuté), pero es un punto justo con las WAR web para las que la reubicación parece un poco más lenta
Richard Tingle el

16

No lo hagas; complicará demasiado las cosas y no lo vas a necesitar

... es la respuesta que habría escrito aquí hace 2 años. Ahora, sin embargo, no estoy tan seguro; de hecho, en los últimos meses he comenzado a migrar código antiguo a este formato, no porque no tenga nada mejor que hacer, sino porque realmente lo necesitaba para implementar nuevas funciones o cambiar las existentes. Entiendo las aversiones automáticas que otros aquí tienen al ver ese código, pero creo que esto es algo que merece una reflexión seria.


Beneficios

El principal de los beneficios es la capacidad de modificar y extender el código. Si utiliza

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

en lugar de simplemente pasar un par de enteros, que es algo que, desafortunadamente, muchas interfaces hacen, entonces se vuelve mucho más fácil agregar luego otra dimensión. O cambie el tipo a double. Si usa List<Author> authorso en su List<Person> authorslugar List<String> authorsmás tarde se vuelve mucho más fácil agregar más información a lo que representa un autor. Al escribirlo de esta manera, siento que estoy diciendo lo obvio, pero en la práctica he sido culpable de usar cuerdas de esta manera muchas veces, especialmente en los casos en que no era obvio al principio, entonces necesitaría más de una cuerda.

Actualmente estoy tratando de refactorizar alguna lista de cadenas que se entrelaza a lo largo de mi código porque necesito más información allí, y siento el dolor: \

Más allá de eso, estoy de acuerdo con el autor del blog en que contiene más información semántica , lo que facilita la comprensión del lector. Si bien los parámetros a menudo reciben nombres significativos y obtienen una línea de documentación dedicada, este no suele ser el caso con los campos o los locales.

El beneficio final es la seguridad de tipo , por razones obvias, pero en mi opinión es algo menor aquí.

Inconvenientes

Se tarda más en escribir . Escribir una clase pequeña es rápido y fácil, pero no es fácil, especialmente si necesita muchas de estas clases. Si te detienes cada 3 minutos para escribir una nueva clase de envoltura, también puede ser un verdadero perjuicio para tu concentración. Sin embargo, me gustaría pensar que este estado de esfuerzo generalmente solo ocurrirá en la primera etapa de la escritura de cualquier código; Por lo general, puedo tener una idea bastante rápida de qué entidades necesitarán participar.

Puede involucrar a muchos setters (o construcciones) redundantes y getters . El autor del blog da el ejemplo realmente feo de en new Point(x(10), y(10))lugar de new Point(10, 10), y me gustaría agregar que un uso también podría involucrar cosas como en Math.max(p.x.get(), p.y.get())lugar de Math.max(p.x, p.y). Y el código largo a menudo se considera más difícil de leer, y con justicia. Pero con toda honestidad, tengo la sensación de que mucho código mueve objetos, y solo los métodos seleccionados lo crean, y aún menos necesitan acceso a sus detalles arenosos (que de todos modos no son OOPy).

Discutible

Diría que si esto ayuda o no con la legibilidad del código es discutible. Sí, más información semántica, pero código más largo. Sí, es más fácil comprender el papel de cada local, pero es más difícil entender qué puede hacer con él a menos que vaya y lea su documentación.


Como con la mayoría de las otras escuelas de pensamiento de programación, creo que no es saludable llevar esto al extremo. No me veo separando las coordenadas x e y para ser de un tipo diferente. No creo que Countsea ​​necesario cuando intdebería ser suficiente. No me gusta el unsigned intuso en C, aunque en teoría es bueno, simplemente no le brinda suficiente información y prohíbe extender su código más tarde para admitir ese -1 mágico. A veces necesitas simplicidad.

Creo que la publicación del blog es un poco extrema. Pero, en general, aprendí por experiencia dolorosa que la idea básica detrás de esto está hecha de las cosas correctas.

Tengo una profunda aversión al código sobredimensionado. Realmente lo hago Pero bien usado, no creo que esto sea una ingeniería excesiva.


5

Aunque es una especie de exageración, a menudo creo que la mayoría de las cosas que he visto no están diseñadas.

No es solo "seguridad", una de las cosas realmente buenas de Java es que te ayuda mucho a recordar / descifrar qué necesita / espera cualquier llamada a un método de biblioteca.

La PEOR (con mucho) biblioteca de Java con la que he trabajado fue escrita por alguien muy aficionado a Smalltalk y modeló la biblioteca GUI después de ella para que actuara más como smalltalk; el problema es que cada método tomó el mismo objeto base, pero en realidad no podía UTILIZAR todo a lo que se podía lanzar el objeto base, así que volviste a adivinar qué pasar a los métodos y no sabías si había fallado hasta el tiempo de ejecución (Algo con lo que solía lidiar cada vez que estaba trabajando en C).

Otro problema: si pasa cadenas, entradas, colecciones y matrices sin objetos, todo lo que tiene son bolas de datos sin significado. Esto parece natural cuando piensa en términos de bibliotecas que usará "alguna aplicación", pero al diseñar una aplicación completa es mucho más útil asignar significado (código) a todos sus datos en el lugar donde se definen los datos y solo pensar en términos de cómo interactúan estos objetos de alto nivel. Si está pasando primitivas en lugar de objetos, entonces, por definición, está alterando los datos en un lugar diferente de donde está definido (Esta es también la razón por la que realmente no me gustan los Setters y Getters - mismo concepto, usted está operando en datos que no son tuyos).

Finalmente, si define objetos separados para todo, siempre tiene un excelente lugar para validar todo, por ejemplo, si crea un objeto para el código postal y luego descubre que debe asegurarse de que el código postal siempre incluya la extensión de 4 dígitos que tiene un Lugar perfecto para ponerlo.

En realidad no es una mala idea. Pensando en ello, ni siquiera estoy seguro de decir que fue sobre-diseñado en absoluto, es más fácil trabajar con él en casi todos los sentidos, la única excepción es la proliferación de clases pequeñas, pero las clases Java son tan ligeras y fáciles escribir que esto apenas es un costo (incluso se pueden generar).

Me interesaría mucho ver un proyecto de Java bien escrito que en realidad tuviera demasiadas clases definidas (donde eso dificultara la programación), estoy empezando a pensar que no es posible tener demasiadas clases.


3

Creo que hay que mirar este concepto desde otro punto de partida. Eche un vistazo desde la perspectiva de un diseñador de bases de datos: los tipos pasados ​​en el primer ejemplo no definen sus parámetros de una manera única, y mucho menos de una manera útil.

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

Se necesitan dos parámetros para especificar el usuario real que está reservando boletos, puede tener dos películas diferentes con nombres idénticos (por ejemplo, remakes), puede tener la misma película con nombres diferentes (por ejemplo, traducciones). Una cierta cadena de salas de cine podría tener diferentes sucursales, así que ¿cómo se va a tratar con el de una cadena y de una manera consistente (por ejemplo, está usando $chain ($city)o $chain in $cityincluso algo más y cómo se va a asegurarse de que se trata usado de manera consistente. Lo peor en realidad es especificar su usuario mediante dos parámetros, el hecho de que se proporcionen tanto un nombre como un apellido no garantiza un cliente válido (y no puede distinguir dos John Doe).

La respuesta a esto es declarar tipos, pero estos raramente serán envoltorios delgados como lo muestro arriba. Lo más probable es que funcionen como su almacenamiento de datos o se combinen con algún tipo de base de datos. Por lo tanto, Cinemaes probable que un objeto tenga un nombre, una ubicación, ... y de ese modo se eliminen esas ambigüedades. Si son envoltorios delgados, son por coincidencia.

Entonces, en mi humilde opinión, la publicación del blog solo dice "asegúrese de pasar los tipos correctos", su autor acaba de tomar la decisión demasiado restringida de elegir tipos de datos básicos en particular (que es el mensaje equivocado).

La alternativa propuesta es mejor:

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

Por otro lado, creo que la publicación del blog va demasiado lejos al envolver todo. Countes demasiado genérico, podría contar manzanas o naranjas con eso, agregarlas y aún tener una situación en mis manos en la que el sistema de tipos me permite realizar operaciones sin sentido. Por supuesto, puede aplicar la misma lógica que en el blog y definir tipos CountOfOranges, etc., pero eso también es una tontería.

Por lo que vale, en realidad escribiría algo como

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

Larga historia corta: no deberías pasar variables sin sentido; las únicas veces que realmente especificaría un objeto con un valor que no determina el objeto real, es cuando ejecuta una consulta (por ejemplo public Collection<Film> findFilmsWithTitle(String title)) o cuando está elaborando una prueba de concepto. Mantenga su sistema de tipos limpio, así que no use un tipo que sea demasiado general (por ejemplo, una película representada por a String) o demasiado restrictivo / específico / artificial (por ejemplo, en Countlugar de int). Utilice un tipo que defina su objeto de manera única e inequívoca siempre que sea posible y viable.

editar : un resumen aún más corto. Para aplicaciones pequeñas (por ejemplo, prueba de conceptos): ¿por qué molestarse con un diseño complicado? Solo usa Stringo inty sigue adelante.

Para aplicaciones grandes: ¿es realmente probable que tenga muchas clases que consisten en un solo campo con un tipo de datos básico? Si tienes pocas clases de este tipo, solo tienes objetos "normales", no pasa nada especial allí.

Siento que la idea de encapsular cadenas, ... es solo un diseño que está incompleto: demasiado complicado para aplicaciones pequeñas, no lo suficientemente completo para aplicaciones grandes.


Entiendo su punto de vista, pero argumentaría que está asumiendo más de lo que dice el autor. Por lo que sabemos, su modelo solo tiene una Cadena para un cliente, una Cadena para una película, etc. Esa hipotética funcionalidad adicional es realmente el quid de la cuestión, por lo que, o estaba muy "distraído" al omitir eso al presentar su caso, o cree que deberíamos proporcionar más poder semántico simplemente porque sí. De nuevo, por lo que sabemos, es lo último que tenía en mente.
DPM

@Jubbat: de hecho, supongo más de lo que dice el autor. Pero mi punto es que, o bien tienes una aplicación simple, en cuyo caso cualquier forma es demasiado complicada. Para esa escala, la mantenibilidad no es un problema y la distinción semántica impide su velocidad de codificación. Si, por otro lado, su aplicación es grande, vale la pena definir adecuadamente sus tipos (pero es poco probable que sean simples envoltorios). En mi humilde opinión, sus ejemplos no son convincentes o tienen defectos de diseño importantes más allá del punto que está tratando de hacer.
Egon

2

Para mí, hacer esto es lo mismo que usar regiones en C #. En general, si cree que esto es necesario para que su código sea legible, entonces tiene problemas más grandes en los que debería dedicar su tiempo.


2
+1 por implicar el tratamiento de los síntomas en lugar de la causa.
Trabajo

2

Yo diría que es una muy buena idea en un lenguaje con una característica tipo typedef fuertemente tipada.

En Java no tiene esto, por lo que crear una clase completamente nueva para estas cosas significa que el costo probablemente supere el beneficio. También puede obtener el 80% del beneficio al tener cuidado con su nombre de variable / parámetro.


0

Sería bueno que IF String (end Integer, y ... hablar solo de String) no fuera definitivo, por lo que estas clases podrían ser algunas String (restringidas) con significado y aún podrían enviarse a algún objeto independiente que sepa cómo manejar el tipo base (sin conversar de un lado a otro).

Y "goodnes" aumenta cuando hay, por ejemplo. restricciones en todos los nombres.

Pero al crear alguna aplicación (no biblioteca), siempre es posible refactorizarla. Así que prefiero comenzar sin él.


0

Para poner un ejemplo: en nuestro sistema desarrollado actualmente, hay muchas entidades diferentes que pueden identificarse mediante múltiples tipos de identificadores (debido al uso de sistemas externos), a veces incluso el mismo tipo de entidad. Todos los identificadores son cadenas, por lo que si alguien mezcla qué tipo de identificación debe pasarse como parámetro, no se muestra ningún error de tiempo de compilación, pero el programa explotará en tiempo de ejecución. Esto sucede con bastante frecuencia. Así que tengo que decir que el objetivo principal de este principio no es proteger contra el cambio (aunque también sirve para eso), sino protegernos de los errores. Y de todos modos, si alguien diseña una API, debe reflejar los conceptos del dominio, por lo que es conceptualmente útil definir clases específicas del dominio; todo depende de si hay un orden en la mente de los desarrolladores,


@downvoter: ¿puedes dar una explicación?
thSoft
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.