¿Deberíamos definir tipos para todo?


141

Recientemente tuve un problema con la legibilidad de mi código.
Tenía una función que hizo una operación y devolvió una cadena que representa la ID de esta operación para referencia futura (un poco como OpenFile en Windows que devuelve un identificador). El usuario usaría esta ID más tarde para comenzar la operación y monitorear su finalización.

La identificación tenía que ser una cadena aleatoria debido a problemas de interoperabilidad. Esto creó un método que tenía una firma muy poco clara como esta:

public string CreateNewThing()

Esto hace que la intención del tipo de devolución no esté clara. Pensé envolver esta cadena en otro tipo que aclara su significado de esta manera:

public OperationIdentifier CreateNewThing()

El tipo contendrá solo la cadena y se usará siempre que se use esta cadena.

Es obvio que la ventaja de esta forma de operación es una mayor seguridad de tipo y una intención más clara, pero también crea mucho más código y código que no es muy idiomático. Por un lado, me gusta la seguridad adicional, pero también crea mucho desorden.

¿Crees que es una buena práctica incluir tipos simples en una clase por razones de seguridad?


44
Es posible que le interese leer sobre Tipos minúsculos. Jugué un poco con él y no lo recomendaría, pero es un enfoque divertido para pensar. darrenhobbs.com/2007/04/11/tiny-types
Jeroen Vannevel

40
Una vez que use algo como Haskell, verá cuánto ayuda el uso de tipos. Los tipos ayudan a garantizar un programa correcto.
Nate Symer

3
Incluso un lenguaje dinámico como Erlang se beneficia de un sistema de tipos, por eso tiene un sistema de tipos y una herramienta de verificación de tipos familiar para Haskellers a pesar de ser un lenguaje dinámico. Lenguajes como C / C ++ donde el tipo real de cualquier cosa es un número entero que estamos de acuerdo con el compilador para tratar de cierta manera, o Java donde casi tiene tipos si decide fingir que las clases son tipos presentan un problema de sobrecarga semántica en el idioma (¿es esto una "clase" o un "tipo"?) o ambigüedad de tipo (¿es esto una "URL", "Cadena" o "UPC"?). Al final, use cualquier herramienta que pueda para desambiguar.
zxq9

77
No estoy seguro de dónde viene la idea de que C ++ tiene una sobrecarga en los tipos. Con C ++ 11, ninguno de los fabricantes de compiladores vio un problema al dar a cada lambda su propio tipo único. Pero TBH realmente no puede esperar encontrar mucha información en un comentario sobre el "sistema de tipo C / C ++" como si ambos compartieran un sistema de tipo.
MSalters

2
@JDB: no, no lo hace. Ni siquiera similar.
Davor Ždralo

Respuestas:


152

Las primitivas, como stringo int, no tienen significado en un dominio comercial. Una consecuencia directa de esto es que puede usar una URL por error cuando se espera un ID de producto, o usar la cantidad cuando espera el precio .

Esta es también la razón por la cual el desafío Object Calisthenics presenta el primitivo envoltorio como una de sus reglas:

Regla 3: Envuelva todas las primitivas y cadenas

En el lenguaje Java, int es un objeto primitivo, no real, por lo que obedece a reglas diferentes que los objetos. Se usa con una sintaxis que no está orientada a objetos. Más importante aún, un int por sí solo es solo un escalar, por lo que no tiene sentido. Cuando un método toma un int como parámetro, el nombre del método debe hacer todo el trabajo de expresar la intención. Si el mismo método toma una Hora como parámetro, es mucho más fácil ver qué está pasando.

El mismo documento explica que hay un beneficio adicional:

Los objetos pequeños como Hour o Money también nos dan un lugar obvio para poner un comportamiento que de otro modo habría estado plagado de otras clases .

De hecho, cuando se usan primitivas, generalmente es extremadamente difícil rastrear la ubicación exacta del código relacionado con esos tipos, lo que a menudo conduce a una duplicación severa del código . Si hay Price: Moneyclase, es natural encontrar el rango comprobando dentro. Si, en cambio, se utiliza un int(peor, a double) para almacenar los precios de los productos, ¿quién debería validar el rango? ¿El producto? El descuento? ¿El carro?

Finalmente, un tercer beneficio no mencionado en el documento es la capacidad de cambiar relativamente fácilmente el tipo subyacente. Si hoy ProductIdtiene mi shorttipo subyacente y luego necesito usarlo inten su lugar, es probable que el código que cambie no abarque toda la base de código.

El inconveniente, y el mismo argumento se aplica a todas las reglas del ejercicio de Calistenia de Objetos, es que si rápidamente se vuelve demasiado abrumador para crear una clase para todo . Si Productcontiene ProductPricequé hereda de PositivePricequé hereda de quién a Pricesu vez hereda Money, esto no es una arquitectura limpia, sino más bien un desastre completo donde, para encontrar una sola cosa, un mantenedor debe abrir algunas docenas de archivos cada vez.

Otro punto a considerar es el costo (en términos de líneas de código) de crear clases adicionales. Si los envoltorios son inmutables (como deberían ser, por lo general), significa que, si tomamos C #, debe tener, al menos en el envoltorio:

  • El captador de propiedades,
  • Su campo de apoyo,
  • Un constructor que asigna el valor al campo de respaldo,
  • Una costumbre ToString(),
  • Comentarios de documentación XML (que forman muchas líneas),
  • A Equalsy una GetHashCodeanulación (también una gran cantidad de LOC).

y eventualmente, cuando sea relevante:

  • Un atributo DebuggerDisplay ,
  • Una anulación de ==y !=operadores,
  • Eventualmente, una sobrecarga del operador de conversión implícita para convertir sin problemas hacia y desde el tipo encapsulado,
  • Contratos de código (incluido el invariante, que es un método bastante largo, con sus tres atributos),
  • Varios convertidores que se utilizarán durante la serialización XML, la serialización JSON o el almacenamiento / carga de un valor a / desde una base de datos.

Cien LOC para un envoltorio simple lo hacen bastante prohibitivo, por lo que también puede estar completamente seguro de la rentabilidad a largo plazo de dicho envoltorio. La noción de alcance explicada por Thomas Junk es particularmente relevante aquí. Escribir cien LOC para representar un ProductIdusado en toda su base de código parece bastante útil. Escribir una clase de este tamaño para un fragmento de código que forma tres líneas dentro de un solo método es mucho más cuestionable.

Conclusión:

  • Envuelva primitivas en clases que tengan un significado en un dominio comercial de la aplicación cuando (1) ayuda a reducir errores, (2) reduce el riesgo de duplicación de código o (3) ayuda a cambiar el tipo subyacente más adelante.

  • No envuelva automáticamente cada primitivo que encuentre en su código: hay muchos casos en los que usar stringo intestá perfectamente bien.

En la práctica, public string CreateNewThing()devolver una instancia de ThingIdclase en lugar de stringpodría ayudar, pero también puede:

  • Devuelve una instancia de Id<string>clase, que es un objeto de tipo genérico que indica que el tipo subyacente es una cadena. Tiene el beneficio de la legibilidad, sin el inconveniente de tener que mantener muchos tipos.

  • Devuelve una instancia de Thingclase. Si el usuario solo necesita la ID, esto se puede hacer fácilmente con:

    var thing = this.CreateNewThing();
    var id = thing.Id;
    

33
Creo que el poder de cambiar libremente el tipo subyacente es importante aquí. Es una cadena hoy, pero quizás un GUID o largo mañana. La creación de la envoltura de tipos oculta esa información y genera menos cambios en el futuro. ++ Buena respuesta aquí.
RubberDuck

44
En el caso del dinero, asegúrese de que la moneda esté incluida en el objeto Dinero, o que todo su programa use el mismo (que luego debe documentarse).
Paŭlo Ebermann

55
@Blrfl: si bien hay casos válidos en los que es necesaria una herencia profunda, generalmente veo dicha herencia en un código sobrediseñado escrito sin prestar atención a la legibilidad, como resultado de una actitud compulsiva de pago por LOC. De ahí el carácter negativo de esta parte de mi respuesta.
Arseni Mourzenko

3
@ ARTs: Ese es el argumento de "condenemos la herramienta porque algunas personas la usan de manera incorrecta".
Blrfl

66
@MainMa Un ejemplo de dónde el significado podría haber evitado un error costoso: en.wikipedia.org/wiki/Mars_Climate_Orbiter#Cause_of_failure
cbojar

60

Usaría el alcance como regla general: cuanto más estrecho sea el alcance de generar y consumir values, menos probable es que tenga que crear un objeto que represente este valor.

Digamos que tiene el siguiente pseudocódigo

id = generateProcessId();
doFancyOtherstuff();
job.do(id);

entonces el alcance es muy limitado y no tendría sentido convertirlo en un tipo. Pero supongamos que genera este valor en una capa y lo pasa a otra capa (o incluso a otro objeto), entonces tendría mucho sentido crear un tipo para eso.


14
Un muy buen punto. Los tipos explícitos son una herramienta importante para comunicar la intención , lo cual es especialmente importante a través de los límites de objetos y servicios.
Avner Shahar-Kashtan

+1 aunque si aún no ha refactorizado todo el código doFancyOtherstuff()en una subrutina, puede pensar que la job.do(id)referencia no es lo suficientemente local como para mantenerla como una cadena simple.
Mark Hurd

Esto es totalmente cierto! Es el caso cuando tienes un método privado donde sabes que el mundo exterior nunca usará el método. Después de eso, puede pensar un poco más cuando la clase está protegida y luego cuando la clase es pública pero no forma parte de una API pública. Alcance del alcance alcance y mucho arte para colocar la línea en la buena posición entre tipos muy primitivos y muy personalizados.
Samuel

34

Los sistemas de tipo estático tienen que ver con la prevención del uso incorrecto de los datos.

Hay ejemplos obvios de tipos que hacen esto:

  • no puedes obtener el mes de un UUID
  • no puedes multiplicar dos cadenas.

Hay ejemplos más sutiles.

  • no puedes pagar por algo usando la longitud del escritorio
  • no puede realizar una solicitud HTTP utilizando el nombre de alguien como URL.

Podemos sentir la tentación de usar doubletanto por precio como por duración, o usar stringtanto para un nombre como para una URL. Pero hacer eso subvierte nuestro increíble sistema de tipos y permite que estos abusos pasen las comprobaciones estáticas del lenguaje.

Confundir libra segundos con Newton segundos puede tener malos resultados en tiempo de ejecución .


Esto es particularmente un problema con las cadenas. Con frecuencia se convierten en el "tipo de datos universal".

Estamos acostumbrados principalmente a las interfaces de texto con las computadoras, y a menudo ampliamos estas interfaces humanas (UI) a las interfaces de programación (API). Pensamos en 34.25 como los personajes 34.25 . Pensamos en una fecha como los personajes 05-03-2015 . Pensamos en un UUID como los caracteres 75e945ee-f1e9-11e4-b9b2-1697f925ec7b .

Pero este modelo mental daña la abstracción API.

Las palabras o el idioma, como están escritos o hablados, no parecen jugar ningún papel en mi mecanismo de pensamiento.

Albert Einstein

Del mismo modo, las representaciones textuales no deberían desempeñar ningún papel en el diseño de tipos y API. Cuidado con el string! (y otros tipos "primitivos" demasiado genéricos)


Los tipos comunican "qué operaciones tienen sentido".

Por ejemplo, una vez trabajé en un cliente para una API REST HTTP. REST, hecho correctamente, utiliza entidades hipermedia, que tienen hipervínculos que apuntan a entidades relacionadas. En este cliente, no solo se escribieron las entidades (por ejemplo, Usuario, Cuenta, Suscripción), también se escribieron los enlaces a esas entidades (UserLink, AccountLink, SubscriptionLink). Los enlaces eran poco más que envoltorios Uri, pero los tipos separados hacían imposible intentar usar un AccountLink para buscar un Usuario. Si todo hubiera sido sencillo Uri, o incluso peor, stringestos errores solo se encontrarían en tiempo de ejecución.

Del mismo modo, en su situación, tiene datos que se utilizan para un solo propósito: identificar un Operation. No debería usarse para otra cosa, y no deberíamos tratar de identificar Operations con cadenas aleatorias que hemos inventado. Crear una clase separada agrega legibilidad y seguridad a su código.


Por supuesto, todas las cosas buenas se pueden usar en exceso. Considerar

  1. cuánta claridad agrega a su código

  2. con qué frecuencia se usa

Si se usa con frecuencia un "tipo" de datos (en sentido abstracto), para propósitos distintos y entre interfaces de código, es un muy buen candidato para ser una clase separada, a expensas de la verbosidad.


21
"las cadenas a menudo se convierten en el tipo de datos 'universal'", ese es el origen del apodo irónico de "lenguajes tipeados".
MSalters

@MSalters, ja, ja, muy agradable.
Paul Draper

44
Y, por supuesto, ¿qué 05-03-2015 significa cuando se interpreta como una cita ?
un CVn

10

¿Crees que es una buena práctica incluir tipos simples en una clase por razones de seguridad?

A veces.

Este es uno de esos casos en los que necesita sopesar los problemas que pueden surgir al usar un en stringlugar de uno más concreto OperationIdentifier. ¿Cuáles son su gravedad? ¿Cuál es su probabilidad?

Entonces debe considerar el costo de usar el otro tipo. ¿Qué tan doloroso es usar? ¿Cuánto trabajo es hacer?

En algunos casos, se ahorrará tiempo y esfuerzo al tener un buen tipo concreto para trabajar. En otros, no valdrá la pena.

En general, creo que este tipo de cosas debería hacerse más de lo que es hoy. Si tiene una entidad que significa algo en su dominio, es bueno tener eso como su propio tipo, ya que es más probable que esa entidad cambie / crezca con el negocio.


10

Generalmente estoy de acuerdo en que muchas veces debes crear un tipo para primitivas y cadenas, pero debido a que las respuestas anteriores recomiendan crear un tipo en la mayoría de los casos, enumeraré algunas razones por las cuales / cuándo no:

  • Actuación. Debo referirme a los idiomas reales aquí. En C #, si un ID de cliente es corto y lo ajusta en una clase, acaba de crear una gran cantidad de sobrecarga: memoria, ya que ahora es de 8 bytes en un sistema de 64 bits y la velocidad desde ahora está asignada en el montón.
  • Cuando haces suposiciones sobre el tipo. Si un ID de cliente es corto y tiene alguna lógica que lo empaqueta de alguna manera, generalmente ya hace suposiciones sobre el tipo. En todos esos lugares ahora tienes que romper la abstracción. Si es un lugar, no es gran cosa, si está por todas partes, es posible que la mitad del tiempo estés usando el primitivo.
  • Porque no todos los idiomas tienen typedef. Y para los idiomas que no lo hacen y el código que ya está escrito, hacer un cambio así podría ser una gran tarea que incluso podría introducir errores (pero todos tienen un conjunto de pruebas con buena cobertura, ¿verdad?).
  • En algunos casos reduce la legibilidad. ¿Cómo imprimo esto? ¿Cómo valido esto? ¿Debería estar buscando nulo? Son todas las preguntas que requieren profundizar en la definición del tipo.

3
Seguramente si el tipo de contenedor es una estructura en lugar de una clase, entonces un short(por ejemplo) tiene el mismo costo envuelto o no. (?)
MathematicalOrchid

1
¿No sería un buen diseño para un tipo encapsular su propia validación? ¿O para crear un tipo de ayuda para validarlo?
Nick Udell

1
Empacar o mezclar innecesariamente es un antipatrón. La información debe fluir de manera transparente a través del código de la aplicación, excepto cuando la transformación sea explícita y genuinamente necesaria . Los tipos son buenos, ya que generalmente sustituyen una pequeña cantidad de código bueno, ubicado en un solo lugar, por grandes cantidades de implementación deficiente repartidas por todo el sistema.
Thomas W

7

No, no debe definir tipos (clases) para "todo".

Pero, como dicen otras respuestas, a menudo es útil hacerlo. Debe desarrollar, conscientemente, si es posible, un sentimiento o sensación de demasiada fricción debido a la ausencia de un tipo o clase adecuada, mientras escribe, prueba y mantiene su código. Para mí, el inicio de demasiada fricción es cuando quiero consolidar múltiples valores primitivos como un solo valor o cuando necesito validar los valores (es decir, determinar cuál de todos los valores posibles del tipo primitivo corresponde a valores válidos de 'tipo implícito').

He encontrado consideraciones como lo que has planteado en tu pregunta para ser responsable de demasiado diseño en mi código. He desarrollado el hábito de evitar deliberadamente escribir más código del necesario. Esperemos que esté escribiendo buenas pruebas (automatizadas) para su código; si lo está, puede refactorizar fácilmente su código y agregar tipos o clases si hacerlo proporciona beneficios netos para el desarrollo y mantenimiento continuos de su código .

La respuesta de Telastyn y la respuesta de Thomas no deseado ambos hacen un muy buen punto sobre el contexto y el uso del código en cuestión. Si está utilizando un valor dentro de un solo bloque de código (por ejemplo, método, bucle, usingbloque), entonces está bien usar un tipo primitivo. Incluso está bien usar un tipo primitivo si está usando un conjunto de valores repetidamente y en muchos otros lugares. Pero cuanto más frecuente y ampliamente esté utilizando un conjunto de valores, y cuanto menos se corresponda con ese conjunto de valores a los valores representados por el tipo primitivo, más debería considerar encapsular los valores en una clase o tipo.


5

En la superficie, parece que todo lo que necesita hacer es identificar una operación.

Además, usted dice lo que se supone que debe hacer una operación:

para comenzar la operación [y] para monitorear su finalización

Lo dice de una manera como si esto fuera "simplemente cómo se usa la identificación", pero yo diría que estas son propiedades que describen una operación. Eso me parece una definición de tipo, incluso hay un patrón llamado "patrón de comando" que se ajusta muy bien. .

Dice

el patrón de comando [...] se utiliza para encapsular toda la información necesaria para realizar una acción o desencadenar un evento en un momento posterior .

Creo que esto es muy similar en comparación con lo que quieres hacer con tus operaciones. (Compare las frases que escribí en negrita en ambas citas) En lugar de devolver una cadena, devuelva un identificador para un Operationen sentido abstracto, un puntero a un objeto de esa clase en oop, por ejemplo.

Sobre el comentario

Sería wtf-y llamar a esta clase un comando y luego no tener la lógica dentro de ella.

No, no lo haría. Recuerde que los patrones son muy abstractos , tan abstractos de hecho que son algo meta. Es decir, a menudo abstraen la programación en sí y no un concepto del mundo real. El patrón de comando es una abstracción de una llamada de función. (o método) Como si alguien pulsara el botón de pausa justo después de pasar los valores de los parámetros y justo antes de la ejecución, para reanudarlo más tarde.

Lo siguiente es considerar la opresión, pero la motivación detrás de esto debería ser cierta para cualquier paradigma. Me gusta dar algunas razones por las cuales colocar la lógica en el comando puede considerarse algo malo.

  1. Si tuviera toda esa lógica en el comando, estaría hinchado y desordenado. Estaría pensando "oh, bueno, toda esta funcionalidad debería estar en clases separadas". Se podría refactorizar la lógica de la orden.
  2. Si tuviera toda esa lógica en el comando, sería difícil de probar y obstaculizar la prueba. "Mierda, ¿por qué tengo que usar este comando sin sentido? Solo quiero probar si esta función escupe 1 o no, no quiero llamarlo más tarde, ¡quiero probarlo ahora!"
  3. Si tuviera toda esa lógica en el comando, no tendría mucho sentido informar cuando haya terminado. Si piensa que una función se ejecutará sincrónicamente de manera predeterminada, no tiene sentido recibir una notificación cuando termine de ejecutarse. Tal vez esté comenzando otro hilo porque su operación es en realidad renderizar avatar al formato de cine (puede necesitar un hilo adicional en raspberry pi), tal vez tenga que buscar algunos datos de un servidor, ... lo que sea, si hay una razón para informando cuando haya terminado, podría deberse a que hay cierta asincronía (¿es una palabra?) Creo que ejecutar un hilo o ponerse en contacto con un servidor es una lógica que no debería estar en su comando. Esto es algo así como un meta argumento y quizás controvertido. Si crees que no tiene sentido, dímelo en los comentarios.

Para recapitular: el patrón de comando le permite ajustar la funcionalidad en un objeto, para ser ejecutado más tarde. En aras de la modularidad (la funcionalidad existe independientemente de que se ejecute a través de un comando o no), la capacidad de prueba (la funcionalidad debe ser comprobable sin comando) y todas esas otras palabras de moda que esencialmente expresan la demanda de escribir un buen código, no pondría la lógica real en el comando.


Debido a que los patrones son abstractos, puede ser difícil encontrar buenas metáforas del mundo real. Aquí hay un intento:

"Hola abuela, ¿podrías presionar el botón de grabación en el televisor a las 12 para no perderme a los Simpson en el canal 1?"

Mi abuela no sabe qué sucede técnicamente cuando presiona ese botón de grabación. La lógica está en otra parte (en la televisión). Y eso es algo bueno. La funcionalidad está encapsulada y la información está oculta del comando, es un usuario de la API, no necesariamente parte de la lógica ... oh cielos, estoy balbuceando palabras de moda nuevamente, mejor termino esta edición ahora.


Tienes razón, no lo he pensado así. Pero el patrón de comando no se ajusta al 100% porque la lógica de la operación está encapsulada en otra clase y no puedo ponerla dentro del objeto que es el Comando. Sería wtf-y llamar a esta clase un comando y luego no tener la lógica dentro de ella.
Ziv

Esto tiene mucho sentido. Considere devolver una Operación, no un OperationIdentifier. Esto es similar al C # TPL donde devuelve una Tarea o Tarea <T>. @Ziv: Usted dice "la lógica de la operación está encapsulada en otra clase". ¿Pero eso realmente significa que no puedes proporcionarlo también desde aquí?
Moby Disk

@Ziv ruego diferir. Eche un vistazo a las razones que agregué a mi respuesta y vea si tienen algún sentido para usted. =)
nulo

5

Idea detrás de envolver tipos primitivos,

  • Para establecer un lenguaje específico de dominio
  • Limite los errores cometidos por los usuarios al pasar valores incorrectos

Obviamente, sería muy difícil y no práctico hacerlo en todas partes, pero es importante ajustar los tipos donde sea necesario,

Por ejemplo, si tiene la clase Order,

Order
{
   long OrderId { get; set; }
   string InvoiceNumber { get; set; }
   string Description { get; set; }
   double Amount { get; set; }
   string CurrencyCode { get; set; }
}

Las propiedades importantes para buscar un pedido son principalmente OrderId y InvoiceNumber. Y Amount y CurrencyCode están estrechamente relacionados, donde si alguien cambia el CurrencyCode sin cambiar el Importe, el Pedido ya no puede considerarse válido.

Por lo tanto, solo el ajuste de OrderId, InvoiceNumber y la introducción de un compuesto para la moneda tiene sentido en este escenario y probablemente la descripción de ajuste no tiene sentido. Por lo tanto, el resultado preferido podría ser,

    Order
    {
       OrderIdType OrderId { get; set; }
       InvoiceNumberType InvoiceNumber { get; set; }
       string Description { get; set; }
       Currency Amount { get; set; }
    }

Entonces no hay razón para envolver todo, sino cosas que realmente importan.


4

Opinión impopular:

¡Usualmente no deberías definir un nuevo tipo!

La definición de un nuevo tipo para envolver una clase primitiva o básica a veces se denomina definición de un pseudotipo. IBM describe esto como una mala práctica aquí (se centran específicamente en el mal uso de genéricos en este caso).

Los pseudo tipos hacen que la funcionalidad de la biblioteca común sea inútil.

Las funciones matemáticas de Java pueden funcionar con todas las primitivas numéricas. Pero si define un nuevo Porcentaje de clase (envolviendo un doble que puede estar en el rango 0 ~ 1), todas estas funciones son inútiles y deben envolverse en clases que (aún peor) necesitan saber sobre la representación interna de la clase de Porcentaje .

Los pseudo tipos se vuelven virales

Al hacer varias bibliotecas, a menudo encontrará que estos pseudotipos se vuelven virales. Si usa la clase de Porcentaje mencionada anteriormente en una biblioteca, deberá convertirla en el límite de la biblioteca (perdiendo toda la seguridad / significado / lógica / otra razón que tuvo para crear ese tipo) o tendrá que hacer estas clases accesible a la otra biblioteca también. Infectar la nueva biblioteca con sus tipos donde un simple doble podría haber sido suficiente.

Quitar mensaje

Siempre y cuando el tipo que ajuste no necesite mucha lógica de negocios, le aconsejaría que no lo ajuste en una pseudo clase. Solo debe ajustar una clase si existen restricciones comerciales serias. En otros casos, nombrar sus variables correctamente debería ser muy útil para transmitir el significado.

Un ejemplo:

A uintpuede representar perfectamente un UserId, podemos seguir usando los operadores integrados de Java para uints (como ==) y no necesitamos lógica de negocios para proteger el 'estado interno' del ID de usuario.


De acuerdo. toda la filosofía de programación está bien, pero en la práctica también debes hacer que funcione
Ewan

Si usted ha visto ningún anuncios de préstamos a los más pobres en el Reino Unido, se encuentra que Porcentaje para envolver un doble en el intervalo 0..1 no funciona ...
gnasher729

1
En C / C ++ / Objective C puede usar un typedef, que le da un nuevo nombre para un tipo primitivo existente. Por otro lado, su código debería funcionar sin importar el tipo subyacente, por ejemplo, si cambio OrderId de uint32_t a uint64_t (porque necesito procesar más de 4 mil millones de pedidos).
gnasher729

No te despreciaré por tu valentía, pero los seudotipos y los tipos definidos en la respuesta superior son diferentes. Esos son tipos que son específicos del negocio. Los psuedotipos son tipos que aún son genéricos.
0fnt

@ gnasher729: C ++ 11 también tiene literales definidos por el usuario que hacen que el proceso de ajuste sea ​​muy agradable para el usuario: Distancia d = 36.0_mi + 42.0_km; . msdn.microsoft.com/en-us/library/dn919277.aspx
damix911

3

Al igual que con todos los consejos, saber cuándo aplicar las reglas es la habilidad. Si crea sus propios tipos en un lenguaje basado en tipos, obtiene la verificación de tipos. Por lo general, ese será un buen plan.

NamingConvention es clave para la legibilidad. Los dos combinados pueden transmitir la intención claramente.
Pero /// sigue siendo útil.

Entonces sí, diría que crean muchos tipos propios cuando su vida útil está más allá de un límite de clase. Considere también el uso de ambos Structy Class, no siempre de la clase.


2
Que ///significa
Gabe

1
/// es el comentario de la documentación en C # que permite que intellisense muestre la documentación de la firma en el punto del método de llamada. Considere la mejor manera de documentar el código. Puede cultivarse con herramientas y ofrecer un comportamiento de inteligencia de alcance. msdn.microsoft.com/en-us/library/tkxs89c5%28v=vs.71%29.aspx
phil soady
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.