¿Cuál es la característica del lenguaje más abusada?

Estado mutable

Ahora, el estado mutable tiene muchos usos geniales. Es útil para el rendimiento, y a veces realmente queremos modelar cosas mutables.

¡Pero el estado mutable conlleva algunos altos costos!

Es inherentemente no local . Una línea de código en algún lugar alejado de su programa puede cambiar el significado de la línea que está viendo ahora. El estado mutable agrega dependencias ocultas y omnipresentes en todo el código.

En términos más generales, el estado mutable es extremadamente difícil de razonar y usar correctamente. (Entraré en detalles más adelante). ¡Y sin embargo, los programadores confían en él en todas partes !

(Abuso

Hay algunas cosas para las que el estado mutable no es muy adecuado y, sin embargo, todavía se usa ampliamente. Los que me molestan particularmente son: el flujo de control y la organización del programa .

Para el flujo de control, a menudo es simplemente innecesario. Podría tener un bucle for que modifique una variable i en el cuerpo, o podría tener uno donde solo exista dentro del cuerpo y sea una nueva variable cada vez. Si aún prefiere el primero, considere este ejemplo de Python:

  x = []
 para i en rango (1,10):
   x + = [lambda: i]

¿Qué crees que devolverá x[3]() ?

¿Por qué tener la complejidad adicional en nuestros bucles y demás, a menos que sea realmente necesario para el rendimiento? (O para los casos raros donde es más simple, por supuesto).

También realmente no me gusta usar el estado para la organización del programa. Este es un poco más nebuloso, pero para mí en realidad es más importante. A menudo, todo el programa está organizado en torno a entidades particulares que tienen un estado interno y se comunican compartiendo más estado. Creo que este es un estado de eventos realmente pobre (je). Se remonta a las dependencias implícitas de las que hablé anteriormente, excepto que ahora las personas están haciendo las dependencias a propósito. Pero todavía están implícitos. Y aún son más difíciles de seguir. Y no creo que te ganen mucho al pasar cosas como argumentos de función. Siempre puede seguir una cadena de argumentos de función hasta su origen; Esto es mucho más difícil con dependencias realizadas con estado mutable.

Ahora, a veces el estado mutable es realmente bueno en estas cosas. A menudo esto sucede en circunstancias relativamente restringidas, como compartir una cola de comunicación o algo así. Pero estas son la excepción, no la regla. La mayoría de los usos del estado mutable son innecesarios .

Sistemas reactivos
El estado mutable también es un mal modelo para el tiempo . Y sin embargo, esa es la forma más común en que los programadores modelan el tiempo en sus programas. Con el estado mutable, el tiempo es totalmente implícito. Solo puede acceder a los valores de las cosas en el momento actual , y el tiempo es un ciudadano de segunda clase, en el mejor de los casos . Usando el estado mutable, no puede escribir código hablando sobre el tiempo directamente , por lo que se ve obligado a usar algunos proxies y abstracciones desafortunadas como eventos y controladores de eventos.

Los eventos, a su vez, son realmente malos para hablar de causalidad o relaciones temporales como “hasta” o “después”. El código para cosas como “evento x después del evento y” es bastante complicado. A menudo, la lógica de la interfaz de usuario es como una máquina de estado a lo largo del tiempo, y codificar esta máquina de estado con estado y eventos mutables es, en el mejor de los casos, incómodo. El proyecto Arrowlets de UMD tiene algunos buenos ejemplos de dónde esto es incómodo. Por ejemplo, considere cómo codificaría la siguiente lógica para arrastrar y soltar utilizando estado y eventos mutables:

(Para más detalles, eche un vistazo a sus diapositivas de presentación).

Los sistemas que se comportan con el tiempo de esta forma se denominan sistemas reactivos . Preferiría usar un modelo de programación que reconozca su naturaleza reactiva y haga que el comportamiento a lo largo del tiempo sea explícito que simplemente fingirlo con un estado mutable. La versión de estado mutable es más difícil de pensar, más fácil de estropear, menos elegante, menos extensible y, a menudo, aún más detallada. Es solo peor .

Es por eso que soy tan defensor de la Programación Reactiva Funcional (FRP).

Razonamiento irrazonable

No refleja la realidad de la CPU . “¿Qué?”, ​​Puede preguntar con incredulidad: “La CPU funciona cambiando las variables y la memoria; este es obviamente un estado mutable”. Bueno, no estarías completamente equivocado. Pero las CPU modernas … realmente no funcionan así. Por un lado, cualquier modelo razonable de estado mutable linealiza su código. ¡Pero las CPU ya no son lineales y no lo han sido durante bastante tiempo! Hay ejecución fuera de orden, instrucciones vectoriales, canalización, todo tipo de cachés, otro tipo de paralelismo a nivel de instrucciones … Las CPU ponen mucho esfuerzo en hacer que todo esto parezca secuencial y hacen un trabajo razonable, pero aún hay Una gigantesca brecha de abstracción.

El compilador también está en este juego: trata de reorganizar las instrucciones tanto como sea posible para un mejor rendimiento: bloqueo de registros, uso inteligente de caché … lo que sea. El estado mutable implícito realmente obstaculiza el optimizador cuando intenta estas cosas. ¿Recuerdas todas esas dependencias ocultas que mencioné? El compilador necesita reificarlos a todos en dependencias reales y asegurarse de que todo lo que hace los conserva a todos. El estado mutable es una forma indirecta de especificar estas dependencias que, curiosamente, dificulta el trabajo tanto del compilador como del programador. Después de todo, ¡es muy difícil para el programador hacer análisis de dependencia en su cabeza!

Y luego llegamos a un paralelismo de grano grueso como múltiples hilos y todo el castillo de naipes se derrumba. Tomemos Java: tiene un modelo de memoria relativamente limpio. ¿Qué puede pasar cuando tienes múltiples hilos? La respuesta pesimista es: cualquier posible entrelazado de los dos hilos. Ja! Yo deseo. En realidad, múltiples hilos rompen el supuesto secuencial . Si bien, mediante un esfuerzo heroico, el sistema ejecuta cada secuencia “como si”, puede observar la ejecución fuera de orden en otras secuencias. Esto se complica aún más por múltiples cachés y coherencia de caché. Absolutamente aterrador.
El modelo de memoria Java .

Entonces, en su lugar, escribimos código que usa bloqueos de una manera demasiado conservadora. ¿Y se supone que esto está “cerca del metal”? Solo piense en lo fundamentalmente derrochador que es un candado: estamos tirando tantos cálculos potenciales … Todo para preservar la ilusión de la ejecución secuencial.

En última instancia, la mayor parte de esto se reduce a una sola idea: generalmente queremos modelar dependencias , y el estado mutable es una forma pobre e indirecta de hacerlo.

Soluciones

Entonces, ¿cómo resolvería esto? ¿Eliminando por completo el estado mutable? ¡No! Prefiero el enfoque de Haskell: hacer que el estado mutable sea explícito , controlable y no el estado predeterminado de las cosas (je).

Todavía quiero un estado mutable ya que, como mencioné anteriormente, puede ser útil. Pero quiero restringirlo a los lugares donde tiene más sentido. Si lo necesito para el rendimiento o quiero modelar algo intrínsecamente con estado, entonces podría ser una buena opción. ¡Y eso está bien! Todavía debería ser una opción.

Pero no lo quiero en todas partes. Y cuando lo uso, quiero que quede claro: el estado mutable generalmente debe ser una parte explícita de una API, lo que evita que surjan dependencias completamente implícitas.

He estado programando así por un tiempo, y realmente me hace la vida más fácil. Todavía uso el estado ocasionalmente, pero no con mucha frecuencia. Y ahora la mayor parte de mi código es sorprendentemente fácil de modificar y refactorizar: tuve un proyecto que, con el tiempo, en realidad se acortó a medida que agregué características solo porque hacer que el código fuera más fácil era muy fácil. También es mucho más fácil para mí saltar a mi antiguo código Haskell que un código JavaScript similarmente antiguo. Básicamente, es genial .

Que la mayoría de los idiomas ignoran los espacios en blanco.

En otras palabras, sangría.

La capacidad de escribir funciones / métodos / procedimientos que son demasiado largos / complejos para ser comprendidos.

No estoy diciendo que las unidades de código largas o complejas deban prohibirse, o que permitirlas sea un error de diseño del lenguaje. Solo digo que el abuso más común es la falta de refactorización de unidades de código largas o complejas en unidades más pequeñas que se puedan entender más fácilmente.

Comentarios, tanto por la falta de ellos como, en otras manos, por la verbosidad de los mismos.

Editar: Ralph Winters (en los comentarios) agregó, con toda razón, comentarios que están mal porque no se han modificado cuando cambia el código.

Funciones estáticas Un sorprendente número de buenas compañías casi no hace diseño orientado a objetos; simplemente construyen un mar de funciones estáticas con un modelo de objetos que todo depende de todo.

Métodos de conveniencia sin otro propósito que ocultar una cadena de llamadas profunda

En lenguajes OOP como Java, herencia. Cuando los estudiantes aprenden sobre las subclases, a menudo comienzan a usarlo como su herramienta principal para la reutilización de códigos y a construir estas grandes jerarquías de herencia complejas.

More Interesting

¿Es posible cambiar dominios de prueba a desarrollo?

¿Qué programa debe tener cada programador en su conjunto de habilidades?

¿Es posible entrenar una red neuronal para convertir imágenes de rostros humanos (selfies) en dibujos animados de estilo moderno?

¿Cómo actualizan los desarrolladores las aplicaciones descentralizadas en blockchain?

¿Cuál es tu opinión sobre tener un vehículo autónomo? ¡Ford ha anunciado un ambicioso plan para entregar un vehículo de gran volumen y totalmente autónomo para 2021!

¿Cómo controlan los gerentes de proyecto el alcance de los proyectos de desarrollo de software?

¿Las leyes o regulaciones federales o internacionales prohíben que un ingeniero de calidad de software almacene pruebas automatizadas en el mismo repositorio de origen que la aplicación que se está desarrollando, y se recomienda?

¿Qué lenguaje de programación debería uno estudiar en la novena clase para convertirse en ingeniero de software?

¿Se puede demandar a una empresa por desarrollar software malo?

¿Deben las Startups realmente preocuparse por producir código reutilizable y bien mantenible o sería mejor para ellos hacerlo funcionar y luego preocuparse por la refactorización? ¿Qué piensas y por qué?

¿Qué es el software de microordenador y cuáles son algunos ejemplos?

¿Por qué los ingenieros de software pierden regularmente las fechas de lanzamiento del producto?

¿Hasta qué punto en el proceso de aprendizaje de ruby ​​un codificador principiante comenzará a trabajar con cosas que se parecen a la web moderna (usuarios, perfiles, mensajes, publicaciones), en oposición a las variables y cadenas iniciales en las ventanas de terminal negras?

¿Cómo comienzas a construir software desde cero? ¿Qué idioma uso? Si quiero que el programa pueda integrarse con otros, ¿qué debo considerar? ¿Dónde puedo aprender todo lo básico?

¿Por qué es necesario mantener actualizado su software de seguridad?