¿Cómo introduce una variable el estado?


11

Estaba leyendo los "Estándares de codificación C ++" y esta línea estaba allí:

Las variables introducen el estado, y debería tener que lidiar con el menor estado posible, con vidas tan cortas como sea posible.

¿Nada de lo que muta finalmente manipula el estado? ¿Qué debe tener que lidiar con el menor estado posible ?

En un lenguaje impuro como C ++, ¿no es realmente la administración del estado lo que está haciendo? ¿Y cuáles son otras formas de lidiar con el menor estado posible además de limitar la vida útil variable?

Respuestas:


16

¿Ninguna cosa mutable realmente manipula el estado?

Si.

¿Y qué significa "deberías tener que lidiar con un pequeño estado"?

Significa que menos estado es mejor que más estado. Más estado tiende a introducir más complejidad.

En un lenguaje impuro como C ++, ¿no es realmente la administración del estado lo que está haciendo?

Si.

¿Cuáles son otras formas de "lidiar con un pequeño estado" además de limitar la vida útil variable?

Minimiza el número de variables. Aísle el código que manipula algún estado en una unidad autónoma para que otras secciones del código puedan ignorarlo.


9

¿Ninguna cosa mutable realmente manipula el estado?

Si. En C ++, las únicas cosas mutables son las constvariables (no ).

¿Y qué significa "deberías tener que lidiar con un pequeño estado"?

Cuanto menos estado tenga un programa, más fácil será comprender lo que hace. Por lo tanto, no debe introducir un estado que no sea necesario, y no debe mantenerlo una vez que ya no lo necesite.

En un lenguaje impuro como C ++, ¿no es realmente la administración del estado lo que está haciendo?

En un lenguaje multi-paradigmático como C ++, a menudo hay una opción entre un enfoque funcional "puro" o un enfoque impulsado por el estado, o algún tipo de híbrido. Históricamente, el soporte de lenguaje para la programación funcional ha sido bastante débil en comparación con algunos lenguajes, pero está mejorando.

¿Cuáles son otras formas de "lidiar con un pequeño estado" además de limitar la vida útil variable?

Restrinja el alcance y la vida útil para reducir el acoplamiento entre objetos; favorezca las variables locales en lugar de las globales y los miembros de objetos privados en lugar de públicos.


5

estado significa que algo se está almacenando en algún lugar para que pueda consultarlo más adelante.

Al crear una variable, se crea espacio para que almacene algunos datos. Estos datos son el estado de su programa.

Lo usas para hacer cosas, alterarlo, calcular con él, etc.

Esto es estado , mientras que las cosas que haces no son estado.

En un lenguaje funcional, se trata principalmente de funciones y funciones de paso como si fueran objetos. Aunque estas funciones no tienen estado, y al pasar la función, no introduce ningún estado (además de tal vez dentro de la función misma).

En C ++ puede crear objetos de función , que son structo classtipos que se han operator()()sobrecargado. Estos objetos de función pueden tener estado local, aunque esto no se comparte necesariamente entre otros códigos en su programa. Los functores (es decir, los objetos de función) son muy fáciles de pasar. Esto es lo más cercano que puede imitar un paradigma funcional en C ++. (HASTA DONDE SE)

Tener poco o ningún estado significa que puede optimizar fácilmente su programa para ejecución paralela, porque no hay nada que se pueda compartir entre subprocesos o CPU, por lo que no se puede crear una contención, y nada que tenga que proteger contra las carreras de datos, etc.


2

Otros han proporcionado buenas respuestas a las primeras 3 preguntas.

¿Y cuáles son otras formas de "lidiar con el menor estado posible" además de limitar la vida útil variable?

La respuesta clave a la pregunta # 1 es sí, cualquier cosa que muta eventualmente impacta el estado. La clave es no mutar las cosas. Tipos inmutables, que usan un estilo de programación funcional en el que el resultado de una función se pasa directamente a la otra y no se almacena, pasando mensajes o eventos directamente en lugar de almacenar el estado, calculando valores en lugar de almacenarlos y actualizarlos ...

De lo contrario, queda limitado el impacto del estado; ya sea a través de la visibilidad o de por vida.


1

¿Y qué significa "deberías tener que lidiar con un pequeño estado"?

Esto significa que sus clases deben ser lo más pequeñas posible, representando de manera óptima una sola abstracción. Si coloca 10 variables en su clase, lo más probable es que esté haciendo algo mal, y debería ver cómo refactorizar su clase.


1

Para comprender cómo funciona un programa, debe comprender sus cambios de estado. Cuanto menos estado tenga y más local sea el código que lo usa, más fácil será.

Si alguna vez trabajó con un programa que tenía una gran cantidad de variables globales, lo entendería implícitamente.


1

El estado es simplemente datos almacenados. Cada variable es realmente una especie de estado, pero usualmente usamos "estado" para referirnos a datos que son persistentes entre operaciones. Como un ejemplo simple y sin sentido, puede tener una clase que almacena internamente una inty tiene increment()y decrement()funciones miembro. Aquí, el valor interno es estado porque persiste durante la vida de la instancia de esta clase. En otras palabras, el valor es el estado del objeto.

Idealmente, el estado que define una clase debe ser lo más pequeño posible con una redundancia mínima. Esto ayuda a su clase a cumplir con el principio de responsabilidad única , mejora la encapsulación y reduce la complejidad. El estado de un objeto debe estar completamente encapsulado por la interfaz a ese objeto. Esto significa que el resultado de cualquier operación en ese objeto será predecible dada la semántica del objeto. Puede mejorar aún más la encapsulación minimizando el número de funciones que tienen acceso al estado .

Esta es una de las principales razones para evitar el estado global. El estado global puede introducir una dependencia para un objeto sin que la interfaz lo exprese, ocultando este estado al usuario del objeto. Invocar una operación en un objeto con una dependencia global puede tener resultados variables e impredecibles.


1

¿Nada de lo que muta finalmente manipula el estado?

Sí, pero si está detrás de una función miembro de una clase pequeña que es la única entidad en todo el sistema que puede manipular su estado privado, entonces ese estado tiene un alcance muy limitado.

¿Qué debe tener que lidiar con el menor estado posible?

Desde el punto de vista de la variable: tan pocas líneas de código deberían poder acceder a ella como sea posible. Limite el alcance de la variable al mínimo.

Desde el punto de vista de la línea de código: se debe poder acceder a la menor cantidad de variables posible desde esa línea de código. Reducir el número de variables que la línea de código puede , posiblemente, el acceso (no importa incluso que mucho si se hace acceder a ella, lo único que importa es si es posible ).

Las variables globales son muy malas porque tienen un alcance máximo. Incluso si se accede desde 2 líneas de código en una base de código, desde la línea de POV del código, siempre se puede acceder a una variable global. Desde el punto de vista de la variable, se puede acceder a una variable global con enlace externo a cada línea de código en toda la base de código (o cada línea de código que incluya el encabezado de todos modos). A pesar de que solo se tiene acceso a través de 2 líneas de código, si la variable global es visible a 400,000 líneas de código, su lista inmediata de sospechosos cuando encuentre que se configuró en un estado no válido tendrá 400,000 entradas (quizás se reduzca rápidamente a 2 entradas con herramientas, pero sin embargo, la lista inmediata tendrá 400,000 sospechosos y ese no es un punto de partida alentador).

Asimismo, es probable que incluso si una variable global comienza a modificarse solo por 2 líneas de código en toda la base de código, la desafortunada tendencia de las bases de código a evolucionar hacia atrás tenderá a aumentar drásticamente ese número, simplemente porque puede aumentar tantas Los desarrolladores, frenéticos por cumplir con los plazos, ven esta variable global y se dan cuenta de que pueden tomar atajos a través de ella.

En un lenguaje impuro como C ++, ¿no es realmente la administración del estado lo que está haciendo?

En gran medida, sí, a menos que esté usando C ++ de una manera muy exótica que lo haga lidiar con estructuras de datos inmutables personalizadas y programación funcional pura en todo momento: a menudo también es la fuente de la mayoría de los errores cuando la administración de estado se vuelve compleja y la complejidad es a menudo una función de la visibilidad / exposición de ese estado.

¿Y cuáles son otras formas de lidiar con el menor estado posible además de limitar la vida útil variable?

Todos estos están en el ámbito de limitar el alcance de una variable, pero hay muchas maneras de hacer esto:

  • Evite variables globales sin procesar como la peste. Incluso alguna función global setter / getter tonta reduce drásticamente la visibilidad de esa variable, y al menos permite alguna forma de mantener invariantes (por ejemplo: si nunca se debe permitir que la variable global sea un valor negativo, el setter puede mantener esa invariante). Por supuesto, incluso un diseño setter / getter además de lo que de otra manera sería una variable global es un diseño bastante pobre, mi punto es que todavía es mucho mejor.
  • Haga sus clases más pequeñas cuando sea posible. Una clase con cientos de funciones miembro, 20 variables miembro y 30,000 líneas de código que lo implementarían tendría variables privadas más bien "globales", ya que todas esas variables serían accesibles para sus funciones miembro que consisten en 30k líneas de código. Se podría decir que la "complejidad del estado" en ese caso, al descontar las variables locales en cada función miembro, es 30,000*20=600,000. Si hubiera 10 variables globales accesibles además de eso, entonces la complejidad del estado podría ser similar 30,000*(20+10)=900,000. Una "complejidad de estado" saludable (mi tipo personal de métrica inventada) debería estar en los miles o menos para las clases, no en decenas de miles, y definitivamente no en cientos de miles. Para funciones gratuitas, digamos cientos o menos antes de comenzar a tener serios dolores de cabeza en el mantenimiento.
  • En la misma línea que anteriormente, no implemente algo como una función miembro o una función amiga que de otra manera puede ser no miembro, no amigo usando solo la interfaz pública de la clase. Dichas funciones no pueden acceder a las variables privadas de la clase y, por lo tanto, reducen el potencial de error al reducir el alcance de esas variables privadas.
  • Evite declarar variables mucho antes de que realmente se necesiten en una función (es decir, evite el estilo C heredado que declara todas las variables en la parte superior de una función, incluso si solo se necesitan muchas líneas a continuación). Si de todos modos usa este estilo, al menos procure funciones más cortas.

Más allá de las variables: efectos secundarios

Muchas de estas pautas que enumeré anteriormente abordan el acceso directo al estado (variables) sin procesar y mutable. Sin embargo, en una base de código suficientemente compleja, limitar el alcance de las variables sin procesar no será suficiente para razonar fácilmente sobre la corrección.

Podría tener, por ejemplo, una estructura de datos central, detrás de una interfaz abstracta totalmente SÓLIDA, totalmente capaz de mantener perfectamente invariantes, y aún así terminar sufriendo mucho debido a la amplia exposición de este estado central. Un ejemplo de estado central que no es necesariamente accesible a nivel mundial sino que simplemente es ampliamente accesible es el gráfico de escena central de un motor de juego o la estructura de datos de la capa central de Photoshop.

En tales casos, la idea de "estado" va más allá de las variables en bruto, y solo a las estructuras de datos y cosas por el estilo. Asimismo, ayuda a reducir su alcance (reducir el número de líneas que pueden llamar a funciones que las mutan indirectamente).

ingrese la descripción de la imagen aquí

Tenga en cuenta cómo marqué deliberadamente incluso la interfaz como roja aquí, ya que desde el nivel arquitectónico amplio y alejado, el acceso a esa interfaz sigue siendo un estado mutante, aunque indirectamente. La clase puede mantener invariantes como resultado de la interfaz, pero eso solo va tan lejos en términos de nuestra capacidad de razonar sobre la corrección.

En este caso, la estructura de datos central está detrás de una interfaz abstracta que puede no ser accesible globalmente. Simplemente se puede inyectar y luego mutar indirectamente (a través de funciones miembro) de una gran cantidad de funciones en su base de código compleja.

En tal caso, incluso si la estructura de datos mantiene perfectamente sus propios invariantes, pueden ocurrir cosas extrañas en un nivel más amplio (por ejemplo: un reproductor de audio puede mantener todo tipo de invariantes como ese, el nivel de volumen nunca sale del rango de 0% a 100%, pero eso no lo protege del usuario que presiona el botón de reproducción y que tiene un clip de audio aleatorio que no sea el que cargó más recientemente, comienza a reproducirse como un evento, lo que hace que la lista de reproducción se reorganice de manera válida, pero comportamiento indeseado y fallido desde la perspectiva del usuario).

La forma de protegerse en estos escenarios complejos es "bloquear" los lugares en la base de código que pueden invocar funciones que finalmente causan efectos secundarios externos, incluso desde este tipo de visión más amplia del sistema que va más allá del estado bruto y más allá de las interfaces.

ingrese la descripción de la imagen aquí

Por extraño que parezca, puede ver que no se está accediendo a ningún "estado" (mostrado en rojo, y esto no significa "variable en bruto", solo significa un "objeto" y posiblemente incluso detrás de una interfaz abstracta). . Cada función tiene acceso a un estado local al que también puede acceder un actualizador central, y el estado central solo es accesible para el actualizador central (por lo que ya no es central sino más bien de naturaleza local).

Esto es solo para bases de código realmente complejas, como un juego que abarca 10 millones de líneas de código, pero puede ser de gran ayuda para razonar sobre la corrección de su software y descubrir que sus cambios producen resultados predecibles, cuando limita significativamente el número de lugares que pueden mutar estados críticos en los que gira toda la arquitectura para funcionar correctamente.

Más allá de las variables sin procesar están los efectos secundarios externos, y los efectos secundarios externos son una fuente de error, incluso si se limitan a un puñado de funciones miembro. Si una gran cantidad de funciones puede llamar directamente a esas pocas funciones miembro, entonces hay una gran cantidad de funciones en el sistema que pueden causar indirectamente efectos secundarios externos, y eso aumenta la complejidad. Si solo hay un lugar en la base de código que tiene acceso a esas funciones miembro, y esa ruta de ejecución no se desencadena por eventos esporádicos en todo el lugar, sino que se ejecuta de una manera muy controlada y predecible, entonces reduce la complejidad.

Complejidad del estado

Incluso la complejidad del estado es un factor bastante importante a tener en cuenta. Una estructura simple, ampliamente accesible detrás de una interfaz abstracta, no es tan difícil de estropear.

Una estructura de datos de gráfico compleja que representa la representación lógica central de una arquitectura compleja es bastante fácil de estropear, y de una manera que ni siquiera viola las invariantes del gráfico. Un gráfico es muchas veces más complejo que una estructura simple, por lo que se vuelve aún más crucial en tal caso reducir la complejidad percibida de la base de código para reducir al mínimo absoluto el número de lugares que tienen acceso a dicha estructura gráfica. y donde ese tipo de estrategia de "actualización central" que se invierte en un paradigma de extracción para evitar empujones esporádicos y directos a la estructura de datos gráficos de todo el lugar realmente puede dar sus frutos.

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.