Mentira 2: ¿El código debe diseñarse en torno a un modelo del mundo? [cerrado]

23

Recientemente leí la publicación del blog Three Big Lies y me está costando mucho justificar la segunda mentira, que se cita aquí:

(MENTIRA # 2) EL CÓDIGO DEBE DISEÑARSE EN UN MODELO DEL MUNDO

No hay ningún valor en que el código sea algún tipo de modelo o mapa de un mundo imaginario. No sé por qué este es tan atractivo para algunos programadores, pero es extremadamente popular. Si hay un cohete en el juego, ten la seguridad de que hay una clase "Rocket" (suponiendo que el código es C ++) que contiene datos para exactamente un cohete y hace cosas de cohetes. Sin tener en cuenta en absoluto qué transformación de datos realmente se está haciendo, o para el diseño de los datos. O para el caso, sin la comprensión básica de que donde hay una cosa, probablemente hay más de una.

Aunque hay muchas penalizaciones de rendimiento para este tipo de diseño, la más importante es que no escala. En absoluto. Cien cohetes cuesta cien veces más que un cohete. ¡Y es extremadamente probable que cueste incluso más que eso! Incluso para un no programador, eso no debería tener ningún sentido. Economía de escala. Si tiene más de algo, debería ser más barato, no más caro. Y la forma de hacerlo es diseñar los datos correctamente y agrupar las cosas por transformaciones similares.

Aquí están mis problemas con esta mentira en particular.

  1. Es valioso que el código sea un modelo / mapa de un mundo imaginario ya que modelar el mundo imaginario ayuda (al menos a mí, personalmente) a visualizar y organizar el código.

  2. Tener una clase "Rocket" es, para mí, una opción perfectamente válida para una clase. Tal vez los "Cohetes" podrían desglosarse en tipos de Cohetes como AGM-114 Hellfire, etc., que contendrían la fuerza de la carga útil, la velocidad máxima, el radio de giro máximo, el tipo de objetivo, etc., pero aún así cada cohete disparado necesitaría una posición y una velocidad

  3. Por supuesto, tener 100 cohetes cuesta más de 1 cohete. Si hay 100 cohetes en la pantalla, debe haber 100 cálculos diferentes para actualizar su posición. El segundo párrafo parece que afirma que si hay 100 cohetes, ¿debería costar menos de 100 cálculos actualizar el estado?

Mi problema aquí es que el autor presenta un modelo de programación "defectuoso" pero no presenta una forma de "corregirlo". Quizás estoy tropezando con la analogía de la clase Rocket, pero realmente me gustaría entender el razonamiento detrás de esta mentira. Cual es la alternativa?

thndrwrks
fuente
99
@gnat: Esta pregunta está directamente dentro de la provincia del diseño de software, por lo que me inclino a darle un margen de maniobra.
Robert Harvey
12
Esa publicación de blog está bastante mal escrita y no defiende y respalda sus afirmaciones demasiado bien. No lo pensaría mucho.
whatsisname
21
Quien escribió esa cita es un idiota con poca comprensión de los conceptos de OO o cómo se implementan en el software. Primero, no estamos mapeando a un mundo imaginario, estamos mapeando al mundo real. Y si tiene 100 cohetes, solo el estado de los cohetes adicionales utiliza recursos adicionales, no el modelo o el comportamiento. Parece tener ideas diferentes al respecto y sugiere solucionar un problema que no existe. "Agrupar cosas similares" como una optimización puede tener sentido a veces, pero es totalmente independiente del uso de clases o no. Si quieres aprender, mantente alejado de este charlatán.
Martin Maat
3
Teniendo en cuenta que el autor no se molestó en escribir más de 5 párrafos en total explicando las "3 grandes mentiras", probablemente haya pasado más tiempo pensando en el artículo que él. Si él no se molesta en hacer un esfuerzo, tú tampoco deberías hacerlo.
Caleb
99
Creo que lo que quiere decir es: ¿realmente necesita 100 "objetos de cohetes", probablemente asignados dinámicamente con métodos virtuales también, en lugar de una lista de posiciones, una lista de velocidades, etc. (tener una lista de todas las posiciones y un lista de velocidades significa que puede usar instrucciones vectoriales para agregar la velocidad a la posición en cada actualización de tick en lugar de escribir un bucle ingenuo a través de una lista de objetos)
Random832

Respuestas:

63

En primer lugar, echemos un vistazo a algún contexto: este es un diseñador de juegos que escribe en un blog cuyo tema es obtener la última caída de rendimiento de una CPU Cell BE. En otras palabras: se trata de la programación de juegos de consola, más específicamente, la programación de juegos de consola para PlayStation 3.

Ahora, los programadores de juegos son un grupo curioso, los programadores de juegos de consola aún más, y el Cell BE es una CPU bastante extraña. (¡Hay una razón por la que Sony eligió un diseño más convencional para PlayStation 4!)

Entonces, tenemos que mirar esas declaraciones dentro de este contexto.

También hay algunas simplificaciones en esa publicación de blog. En particular, esta Mentira # 2 está mal presentada.

Yo diría que todo lo que abstrae del mundo real es un modelo en algún sentido. Y dado que el software no es real, sino virtual, siempre es una abstracción y, por lo tanto, siempre un modelo. ¡Pero! Un modelo no tiene que tener un mapeo limpio 1: 1 en el mundo real. Eso es, después de todo, lo que lo convierte en un modelo en primer lugar.

Entonces, en cierto sentido, el autor está claramente equivocado: el software es un modelo. Período. En otro sentido, tiene razón: ese modelo en realidad no tiene que parecerse en absoluto al mundo real.

Daré un ejemplo que ya di en otras respuestas a lo largo de los años, el (in) famoso ejemplo de Introducción a la cuenta bancaria OO 101. Así es como se ve una cuenta bancaria en casi todas las clases de OO:

class Account {
  var balance: Decimal
  def transfer(amount: Decimal, target: Account) = {
    balance -= amount
    target.balance += amount
  }
}

Por lo tanto: el balancees de datos , y transferes una operación .

¡Pero! Así es como se ve una cuenta bancaria en casi todos los programas bancarios:

class TransactionSlip {
  val transfer(amount: Decimal, from: Account, to: Account)
}

class Account {
  def balance = 
    TransactionLog.filter(t => t.to == this).map(_.amount).sum - 
    TransactionLog.filter(t => t.from == this).map(_.amount).sum
}

Entonces, ahora transferson datos y balancees una operación (un pliegue a la izquierda sobre el registro de transacciones). (También notarán que TransactionSlipes inmutable, balancees una función pura, TransactionLogpuede ser una estructura de datos "casi" inmutable de solo agregar ... Estoy seguro de que muchos de ustedes detectaron los flagrantes errores de concurrencia en la primera implementación, que ahora desaparecen mágicamente .)

Tenga en cuenta que ambos son modelos. Ambos son igualmente válidos. Ambos son correctos. Ambos modelan lo mismo. Y, sin embargo, son exactamente duales entre sí: todo lo que son datos en un modelo es una operación en el otro modelo, y todo lo que es una operación en un modelo son datos en el otro modelo.

Entonces, la pregunta no es si modelas el "mundo real" en tu código, sino cómo lo modelas.

Como resultado, el segundo modelo es también cómo funciona la banca en el mundo real. Como ya he insinuado anteriormente, este segundo modelo es sobre todo inmutable y puro, e inmune a los errores de concurrencia, lo cual es realmente muy importante si tenemos en cuenta que hubo un tiempo no hace mucho tiempo, en donde TransactionSlips eran reales tiras de papel que fueron enviados alrededor a través de caballos y carruajes.

Sin embargo, el hecho de que este segundo modelo realmente coincida tanto con el funcionamiento de la banca en el mundo real como con el funcionamiento del software de la banca en el mundo real, no lo hace automáticamente más "correcto". Porque, en realidad, el primer modelo ("incorrecto") se aproxima bastante a cómo los clientes bancarios ven su banco. Para ellos , transferes una operación (tienen que completar un formulario) y balancees un dato en la parte inferior de su estado de cuenta.

Por lo tanto, puede ser cierto que en el código del motor del juego principal de un juego de disparos de PS3 de alto rendimiento, no habrá un Rockettipo, pero aún así, habrá algunos modelos del mundo, incluso si el modelo parece extraño a alguien que no sea experto en el dominio de la programación del motor de física de juegos de consola.

Jörg W Mittag
fuente
1
¿No significa esto que un buen código modela el mundo real y que, de hecho, es solo un malentendido del mundo real lo que causa un mal modelo y, por lo tanto, un mal código?
yitzih
14
Prefiero expresarlo como "a veces, el mundo real no es lo que crees que es" o "lo que es 'el mundo real' depende del contexto". (Nuevamente, para el propietario de una cuenta bancaria, los datos en la parte inferior de su estado de cuenta son muy reales, mientras que para un empleado del banco es efímero). Creo que el estado de cuenta en la publicación del blog es principalmente porque el autor no comprende ese "modelado el mundo real "no significa" tomar una fotografía y hacer de todo lo que ves allí una clase ".
Jörg W Mittag
El front-end para su aplicación de banca en línea probablemente tratará balancecomo datos y transacciones como más datos, y las transferencias como operaciones, porque eso es lo que ve el usuario, aunque el back-end puede tratarlo de manera diferente.
user253751
@yitzih: cada modelo es una abstracción y simplificación, por lo que podría acusar a cada modelo de ser incorrecto, pero eso no es constructivo. Cada modelo tiene que cumplir un propósito y debe ser lo suficientemente bueno para eso, no desperdiciar recursos para cosas innecesarias. Para el software de un gobierno, un humano puede ser alguien que puede participar en las elecciones, tiene que pagar impuestos o puede casarse con otro humano, para nuestro software CRM, un humano es alguien que está asociado con pedidos y tiene una dirección de entrega (y ninguno modela cómo (s) come) ...
Holger
2
Si el humano sabe algo acerca de la banca , encontrará el segundo más fácil, y dado que las técnicas bancarias que conoce fueron inventadas para que la banca funcione, pueden hacer que el software bancario funcione. No porque el segundo modelo sea "más parecido al mundo real", sino porque describe un banco mejor. ¡El primer modelo podría ser una representación igualmente precisa de un banco disfuncional del mundo real! Adivina qué: si quieres un buen software bancario, entonces los programadores deben aprender cómo hacer bien la banca, aunque solo sea a partir de los documentos de requisitos.
Steve Jessop
19

No estoy de acuerdo con cada "mentira" que propone.

TL; DR El autor de este artículo intentaba ser controvertido para hacer su artículo más interesante, pero las llamadas "mentiras" son aceptadas por los desarrolladores de software por buenas razones.

Mentira # 1: Big O es importante para fines de escala. A nadie le importa si una aplicación pequeña tarda más tiempo, que es lo único que importa, las constantes de tiempo les importa que cuando dupliquen el tamaño de entrada no multipliquen el tiempo de ejecución por un factor de 10.

Mentira n. ° 2: los programas de modelado posteriores al mundo real le permiten a un programador mirar su código 3 años después para comprender fácilmente lo que está haciendo. El código debe ser mantenible o debería pasar horas tratando de entender lo que el programa está tratando de hacer. Otra respuesta sugirió que puede tener clases más genéricas como LaunchPady MassiveDeviceMover. Estas no son necesariamente malas clases, pero aún así necesitarías la Rocketclase. ¿Cómo se supone que alguien sepa qué MassiveDeviceMoverhace o qué mueve? ¿Está moviendo montañas, naves espaciales o planetas? Esto básicamente significa que agregar clases como MassiveDeviceMoverhace que su programa sea menos eficiente (pero posiblemente mucho más legible y comprensible).

Además, el costo del tiempo del desarrollador comenzó a exceder el costo del hardware hace mucho tiempo. Es una idea horrible comenzar tratando de diseñar con optimización al frente de sus pensamientos. Lo programa de manera fácil y comprensible y luego modifica su programa después de descubrir qué partes de sus programas están tardando mucho en ejecutarse. No olvide: el 20% del programa está utilizando el 80% del tiempo de ejecución.

Mentira # 3 - El código es extremadamente importante. El código bien escrito (y modular) permite la reutilización (ahorrando innumerables horas hombre). También le permite filtrar y reconocer datos incorrectos para que puedan manejarse. Los datos son maravillosos, pero sin el código sería imposible analizarlos y obtener información útil de ellos.

yitzih
fuente
55
Creo que simpatizo más con el # 3. En 30 años de programación, la gran mayoría de los errores, problemas de rendimiento y otros problemas que he visto se resolvieron solucionando la representación de datos. Si los datos son correctos, el código prácticamente se escribe solo.
Lee Daniel Crocker
66
El verdadero problema con el n. ° 3 es que compara las manzanas con las naranjas, no es que el código sea más importante que los datos o viceversa.
Doc Brown
3
Los datos de entrada están fuera de su alcance, pero la forma de representar los datos en su software está completamente dentro de ellos. Puede que esté llamando a esa parte de "codificación", pero creo que no lo es: por ejemplo, a menudo es independiente del lenguaje y a menudo se realiza antes de que comience la codificación. Sin embargo, estoy de acuerdo en que ese código que limpia los datos de entrada feos a menudo es algo bueno; pero no puede hacerlo hasta que tenga una definición de "limpio".
Lee Daniel Crocker
3
No creo que la mentira # 3 sea una mentira, en realidad. Fred Brooks ya escribió hace décadas: "Muéstrame tus diagramas de flujo y oculta tus tablas, y seguiré desconcertado. Muéstrame tus tablas, y generalmente no necesitaré tus diagramas de flujo; serán obvios". (Hoy en día, probablemente hablaríamos de "algoritmos" y "tipos de datos" o "esquemas"). Por lo tanto, la importancia de los datos ha sido bien conocida durante mucho tiempo.
Jörg W Mittag
1
@djechlin Mi punto no era que los datos no son importantes o que el código es más importante. Simplemente esos datos no son más importantes que el código. Ambos son muy importantes y dependen mucho el uno del otro.
yitzih
6

En un sistema de comercio electrónico, no se trata con "cohetes" a nivel de clase, sino con "productos". Por lo tanto, depende de lo que intente lograr y del nivel deseado de abstracción.

En un juego, se podría argumentar que los cohetes son simplemente uno de los muchos tipos de "objetos en movimiento". Se les aplica la misma física que a todos los demás objetos en movimiento. Por lo menos, "cohete" va a heredar de una clase base más general de "objeto en movimiento".

En cualquier caso, el autor del pasaje que citó parece haber exagerado un poco su caso. Los cohetes aún pueden tener características únicas, como "cantidad de combustible restante" y "empuje", y no necesita cien clases para representar esto para cien cohetes, solo necesita uno. La creación de objetos tiene un costo bastante bajo en la mayoría de los lenguajes de programación decentes, por lo que si necesita rastrear cosas similares a cohetes, la idea de que no debería hacer objetos Rocket porque podría ser demasiado costoso no tiene mucho sentido.

Robert Harvey
fuente
5

El problema con el mundo real es toda esa maldita física. Separamos las cosas en objetos físicos en el mundo real porque son más fáciles de mover que los átomos individuales, o una escoria gigante fundida de algo que podría ser un cohete.

Del mismo modo, el mundo real proporciona una serie de características útiles en las que confiamos. Es realmente fácil hacer excepciones Penguin: "todas las aves vuelan, excepto ...". Y es realmente fácil etiquetar las cosas como cohetes, es decir, si llamo a ese pingüino cohete y lo enciendo ... simplemente no funciona.

Entonces, cómo separamos las cosas en el mundo real conceptualmente funciona bajo esas restricciones. Cuando hacemos cosas en código, debemos separar las cosas para que funcionen bien bajo esas restricciones, que son decididamente diferentes.

Cual es la alternativa?

Piensa en las redes. No modelamos puertos, cables y enrutadores en código. En cambio, abstraemos la comunicación de red en conexiones y protocolos. Hacemos eso porque es una abstracción útil independientemente de la implementación en el mundo real. Y pone restricciones útiles (por ejemplo: no puede comunicarse hasta que se abra la conexión) que solo importan en el código .

Entonces, sí, a veces el código de modelado después del mundo real funciona, pero eso es una coincidencia . Cuando la gente habla de POO, los objetos no son objetos del mundo real. Que las escuelas y los tutoriales digan lo contrario es una tragedia de décadas.

Telastyn
fuente
1
+1: Los protocolos son en gran medida una abstracción del "mundo real". Incluso en el mundo de hoy, los oficiales de protocolo son algunas de las personas más importantes del personal para una visita de estado, por ejemplo. ¿Quién va primero a la alfombra roja en la reunión del G8, Obama o Putin? ¿Se abrazan o se dan la mano? ¿Cómo saludo a un árabe contra un indio? Y así. Tenemos muchas "cosas" en el "mundo real" que no son "cosas" en el "mundo físico". Modelar el mundo real no significa modelar el mundo físico. Incluso si no hay un Rockettipo en el código de este tipo, estoy dispuesto a apostar que todavía hay algún modelo de ...
Jörg W Mittag
... el mundo real, incluso si no corresponde a nada "físico" (en el sentido de "tocable"). Sin embargo, no me sorprendería demasiado encontrar objetos "físicos" reales (en el sentido de "cosas que un físico podría reconocer") allí, como cuaterniones, tensores, campos, etc. que, por supuesto, también son " cosas del mundo real "y" modelos del mundo real ".
Jörg W Mittag
Alan Kay imaginó el Dynabook como una computadora que se les daría a los niños al nacer y que se convertiría en una extensión de su cerebro. El propósito del patrón MVC, entonces, sería que la Vista y el Controlador cubrieran la brecha entre el cerebro y el Modelo para soportar la Metáfora de Manipulación Directa, es decir, la ilusión de que la computadora es solo una extensión del cerebro, y que uno puede manipular directamente los objetos modelo con los pensamientos de uno. Y a eso nos referimos cuando decimos que el Modelo de Dominio modela el "mundo real". Debe implementar las abstracciones en nuestros cerebros.
Jörg W Mittag
Y cuando pienso en un motor de física de juegos de consola, entonces probablemente no piense en cohetes, y por lo tanto no debería haber un modelo de cohete en mi código. Pero probablemente estoy pensando en otros "pensamientos del mundo real", y debería haber modelos de esos en el código.
Jörg W Mittag
2

La alternativa es modelar las cosas que interesan a sus programas. Incluso si su programa trata con cohetes, es posible que no necesite tener una entidad llamada a Rocket. Por ejemplo, puede tener una LaunchPadentidad y una LaunchScheduleentidad y una MassiveDeviceMoverentidad. El hecho de que todo esto ayude al lanzamiento de cohetes no significa que usted mismo esté manejando cohetes.

BobDalgleish
fuente
0

Mi problema aquí es que el autor presenta un modelo de programación "defectuoso" pero no presenta una forma de "corregirlo". Quizás estoy tropezando con la analogía de la clase Rocket, pero realmente me gustaría entender el razonamiento detrás de esta mentira. Cual es la alternativa?

Este es el verdadero problema, pero te daré mi opinión como desarrollador, tal vez eso ayude.

Primero, no llamaría mentiras a ninguno de ellos, como conceptos erróneos comunes. Llamarlo mentira es solo una exageración.

Uno tiene razón, de alguna manera. No pasaré mucho tiempo en esto, porque no es parte de la pregunta. Pero en esencia él tiene razón. Podría repetir esto como "Lo que funciona en un laboratorio puede no funcionar en la vida real". Muchas veces los desarrolladores se adhieren a un diseño que funciona en un "laboratorio" pero falla en aplicaciones del mundo real.

Tres suena un poco jabonoso para mí, pero esencialmente está en lo correcto nuevamente. Pero esto podría reescribirse para "escribir código según sus necesidades, no intente ajustar las necesidades a su código".

Dos De nuevo, aquí está en lo correcto. He visto a los desarrolladores pasar semanas o más desarrollando una clase de "cohete" cuando una clase simple de "vehículo" funcionaría, o una clase aún más simple, "móvil". Si todo lo que necesita hacer su cohete es moverse del lado izquierdo de la pantalla a la derecha y emitir un sonido, entonces puede usar la misma clase que utilizó para automóviles, trenes, barcos y moscas. El argumento 100 debería costar menos de 1 * 100 parece estar en tiempo invertido en el desarrollo, y no tanto en costos de cálculo. Aunque apegarse a un menor número de clases generales para reutilizarlas es "más barato" que muchas clases específicas que no pueden reutilizarse. Esto probablemente podría reescribirse como "las clases generales son mejores que las clases específicas,

En esencia, todo el artículo podría reescribirse, con menos palabras de moda y, en el mejor de los casos, solo sería un párrafo. Dicho esto, es una publicación de blog centrada en un área estrecha de programación. He realizado una programación incrustada, y estoy de acuerdo, con la idea general detrás de estas declaraciones, aunque hay bastante "pelusa" a su alrededor para que sea adecuada para una presentación en GDC.

Una última nota, el artículo fue escrito en 2008 (lo mejor que pude ver). Las cosas cambian rápidamente. Las declaraciones son ciertas hoy en día, pero los sistemas integrados son mucho más comunes hoy que en aquel entonces, y los patrones de desarrollo cambian. Quizás incluso en respuesta a este artículo / charla.

coteyr
fuente
-1

Me parece interesante que estas mentiras se centren en preocupaciones académicas: la plataforma, la eficiencia del uso de la memoria y los datos. Pero ignora por completo el elemento humano.

El software se trata de satisfacer las necesidades de las personas. Por lo general, esto se cuantifica en términos comerciales: hay clientes que quieren algo y patrocinadores que están dispuestos a pagar para que esto suceda. Si el software se está escribiendo de una manera para satisfacer las necesidades de ambos lados de la ecuación, entonces es un buen software, si no, es un mal software.

Si la plataforma no es importante para el cliente, entonces la plataforma no es importante. Si la eficiencia de la memoria no es importante para el cliente, entonces no es importante. Si los datos no son importantes para el cliente, entonces los datos no son importantes. Si el código funciona, pero no se puede leer o mantener, y el cliente quiere cambios rápidos y confiables a un precio decente, entonces el código mal escrito es algo malo. Si el código funciona, pero no se puede leer o mantener, y al cliente no le importa o está dispuesto a pagar por refactores costosos, entonces un código mal escrito es algo bueno.

La gran mentira es que todo menos el elemento humano importa. ¿Por qué son importantes los datos? Porque hay algún cliente o parte interesada que lo necesita. Esa es la "gran verdad".

Price Jones
fuente
44
Desafortunadamente, los clientes quieren un código rápido y sucio que sea fácil de leer y mantener, barato, sin esfuerzos de prueba y sin errores.
Gonen I
@ user889742 ¡Ja! Cierto. Usted ha declarado, precisamente, los arquitectos de problemas de ingeniería han estado tratando de resolver por todo el tiempo y lo que hace la industria un espacio tan interesante para trabajar.
Precio Jones
Ignora el elemento humano porque es un desarrollador de juegos y la era de mantenimiento de un juego es relativamente corta, aunque hoy más larga que en 2008. Los parches del día 1 parecen ser la norma en los juegos hoy en día. En 2008, los parches para juegos todavía eran relativamente raros.
RubberDuck
-1

En mi humilde opinión, si el código está "diseñado en torno a un modelo del mundo", es más fácil de entender, tanto para los diseñadores y desarrolladores, como para los encargados del mantenimiento. Pero creo que no soy solo yo, y no solo software. De Wikipedia: El modelado científico es una actividad científica, cuyo objetivo es hacer que una parte o característica particular del mundo sea más fácil de entender, definir, cuantificar, visualizar o simular haciendo referencia al conocimiento existente y generalmente aceptado comúnmente

Gonen I
fuente