Soy relativamente nuevo en programación y muchas de las mejores prácticas de codificación que estoy leyendo efectivamente indican que hay muy pocas buenas razones para usar una variable global (o que el mejor código no tiene globales).
He hecho todo lo posible para tener esto en cuenta, al escribir software para hacer una interfaz Arduino con una tarjeta SD, hablar con una computadora y ejecutar un controlador de motor.
Actualmente tengo 46 globales para aproximadamente 1100 líneas de "nivel de principiante", código (ninguna línea tiene más de una acción). ¿Es esta una buena relación o debería considerar reducirla más? Además, ¿qué prácticas puedo emplear para reducir aún más el número de globales?
Lo pregunto aquí porque me interesan específicamente las mejores prácticas para la codificación de productos Arduino en lugar de la programación de computadoras en general.
fuente
Respuestas:
No son malvados per se, pero tienden a ser utilizados en exceso cuando no hay una buena razón para usarlos.
Hay muchas ocasiones en que las variables globales son ventajosas. Sobre todo porque programar un Arduino es, bajo el capó, muy diferente a programar una PC.
El mayor beneficio para las variables globales es la asignación estática. Especialmente con variables grandes y complejas, como instancias de clase. La asignación dinámica (el uso de
new
etc.) está mal vista debido a la falta de recursos.Además, no obtiene un solo árbol de llamadas como lo hace en un programa C normal (
main()
función única que llama a otras funciones); en cambio, obtiene efectivamente dos árboles separados (setup()
funciones de llamada, luegoloop()
funciones de llamada), lo que significa que a veces las variables globales son única forma de lograr su objetivo (es decir, si desea usarlo en ambossetup()
yloop()
).Entonces no, no son malvados, y en un Arduino tienen más y mejores usos que en una PC.
fuente
loop()
(o en múltiples funciones llamadasloop()
)? ¿Sería mejor configurarlos de una manera diferente que definirlos al principio?static
si los necesitara para retener su valor a través de las iteraciones) y pasarlos a través de los parámetros de la función en la cadena de llamadas.void foo(int &var) { var = 4; }
yfoo(n);
-n
ahora es 4.Es muy difícil dar una respuesta definitiva sin ver su código real.
Las variables globales no son malas, y a menudo tienen sentido en un entorno incrustado en el que normalmente se accede mucho al hardware. Tiene solo cuatro UARTS, solo un puerto I2C, etc. Por lo tanto, tiene sentido usar globales para variables vinculadas a recursos de hardware específicos. Y de hecho, la biblioteca central Arduino hace eso:
Serial
,Serial1
, etc, son variables globales. Además, una variable que representa el estado global del programa suele ser global.No se trata de los números. La pregunta correcta que debe hacerse es, para cada uno de estos globales, si tiene sentido tenerlo en el ámbito global.
Aún así, 46 global me parece un poco alto. Si algunos de estos tienen valores constantes, califíquelos como
const
: el compilador generalmente optimizará su almacenamiento. Si alguna de esas variables solo se usa dentro de una sola función, conviértala en local. Si desea que su valor persista entre llamadas a la función, califíquelo comostatic
. También podría reducir el número de globales "visibles" al agrupar variables dentro de una clase y tener una instancia global de esta clase. Pero solo hazlo cuando tenga sentido armar las cosas. Hacer una granGlobalStuff
clase en aras de tener solo una variable global no ayudará a aclarar su código.fuente
static
si tengo una variable que se usa solo en una función, pero obtengo un nuevo valor cada vez que se llama a una función (comovar=millis()
) ¿debería hacer esostatic
?static
es solo para cuando necesita mantener el valor. Si lo primero que hace en la función es establecer el valor de la variable a la hora actual, no es necesario mantener el valor anterior. Todo lo que hace la estática en esa situación es desperdiciar memoria.El principal problema con las variables globales es el mantenimiento del código. Al leer una línea de código, es fácil encontrar la declaración de variables pasadas como parámetro o declaradas localmente. No es tan fácil encontrar la declaración de variables globales (a menudo requiere e IDE).
Cuando tiene muchas variables globales (40 ya es mucho), se hace difícil tener un nombre explícito que no sea demasiado largo. Usar el espacio de nombres es una forma de aclarar el papel de las variables globales.
Una forma pobre de imitar espacios de nombres en C es:
En el procesador Intel o Arm, el acceso a las variables globales es más lento que otras variables. Probablemente sea lo contrario en arduino.
fuente
Aunque no los usaría al programar para una PC, para Arduino tienen algunos beneficios. La mayoría si ya se ha dicho:
Además, en algunos casos, especialmente en términos de rendimiento, puede ser bueno usar variables globales, en lugar de crear elementos cuando sea necesario:
fuente
Como con todo (excepto los gotos que son verdaderamente malvados) los globales tienen su lugar.
por ejemplo, si tiene un puerto serie de depuración o un archivo de registro en el que necesita poder escribir desde cualquier lugar, a menudo tiene sentido hacerlo global. Del mismo modo, si tiene alguna información crítica sobre el estado del sistema, hacerla global es a menudo la solución más fácil. No tiene sentido tener un valor que debe pasar a todas las funciones del programa.
Como otros han dicho, 46 parece mucho para solo un poco más de 1000 líneas de código, pero sin conocer los detalles de lo que está haciendo, es difícil decir si ha terminado de usarlos o no.
Sin embargo, para cada global, hágase algunas preguntas importantes:
¿Es el nombre claro y específico para que no intente usar el mismo nombre en otro lugar accidentalmente? Si no cambia el nombre.
¿Esto necesita cambiar alguna vez? Si no es así, considere hacerlo una constante.
¿Es necesario que esto sea visible en todas partes o es solo global para que el valor se mantenga entre las llamadas a funciones? Considere hacerlo local a la función y use la palabra clave static.
¿Las cosas realmente irán mal si esto se cambia por un código cuando no estoy teniendo cuidado? por ejemplo, si tiene dos variables relacionadas, por ejemplo, nombre y número de identificación, que son globales (consulte la nota anterior sobre el uso de global cuando necesita información en casi todas partes), entonces, si una de ellas cambia sin que ocurran otras cosas desagradables. Sí, podrías tener cuidado, pero a veces es bueno imponer un poco de cuidado. p. ej. colóquelos en un archivo .c diferente y luego defina funciones que establezcan ambos al mismo tiempo y le permitan leerlos. Luego solo incluye las funciones en el archivo de encabezado asociado, de esa manera el resto del código solo puede acceder a las variables a través de las funciones definidas y, por lo tanto, no puede estropear las cosas.
- Actualización - Acabo de darme cuenta de que había preguntado sobre las mejores prácticas específicas de Arduino en lugar de la codificación general y esta es más una respuesta de codificación general. Pero, sinceramente, no hay mucha diferencia, las buenas prácticas son buenas prácticas. La estructura
startup()
yloop()
de Arduino significa que tienes que usar globals un poco más que otras plataformas en algunas situaciones, pero eso realmente no cambia mucho, siempre terminas buscando lo mejor que puedes hacer dentro de las limitaciones de la plataforma sin importar lo que pase. la plataforma esfuente
goto
s y es romper los bucles anidados, es mucho más limpio y fácil de entender que las alternativas.goto
son malvados, mira el código de Linux. Podría decirse que son tan malvados como lostry...catch
bloques cuando se usan como tales.¿Son malvados? Tal vez. El problema con los globales es que se puede acceder a ellos y modificarlos en cualquier momento mediante cualquier función o código que se ejecute, sin restricciones. Esto puede conducir a situaciones que son, digamos, difíciles de rastrear y explicar. Por lo tanto, es deseable minimizar la cantidad de glóbulos, si es posible volver a poner la cantidad a cero.
¿Se pueden evitar? Casi siempre si. El problema con Arduino es que te obligan a adoptar este enfoque de dos funciones en el que te asumen a ti
setup()
y a tiloop()
. En este caso particular, no tiene acceso al alcance de la función de llamada de estas dos funciones (probablementemain()
). Si lo hubiera hecho, podría deshacerse de todos los globales y utilizar a los locales en su lugar.Imagen de lo siguiente:
Esto es probablemente más o menos como se ve la función principal de un programa Arduino. Entonces, las variables que necesita tanto en
setup()
laloop()
función como en la función se declararían preferiblemente dentro del alcance de lamain()
función en lugar del alcance global. Luego podrían hacerse accesibles a las otras dos funciones pasándolas como argumentos (utilizando punteros si es necesario).Por ejemplo:
Tenga en cuenta que en este caso también necesita cambiar la firma de ambas funciones.
Como esto podría no ser factible ni deseable, realmente veo solo una forma de eliminar la mayoría de los globales de un programa Arduino sin modificar la estructura del programa forzado.
Si no recuerdo mal, puede usar C ++ perfectamente mientras programa para Arduino, en lugar de C. Si aún no está familiarizado (todavía) con OOP (Programación Orientada a Objetos) o C ++, puede tomar un tiempo acostumbrarse y algunos leyendo.
Mi propuesta sería crear una clase de Programa y crear una única instancia global de esta clase. Una clase debe considerarse el modelo para los objetos.
Considere el siguiente programa de ejemplo:
Voilà, nos hemos librado de casi todos los globales. Las funciones en las que comenzaría a agregar la lógica de su aplicación serían las funciones
Program::setup()
yProgram::loop()
. Estas funciones tienen acceso a las variables miembro específicas de la instanciamyFirstSampleVariable
ymySecondSampleVariable
mientras que las funciones tradicionalessetup()
yloop()
no tienen acceso ya que estas variables se han marcado como privadas de clase. Este concepto se llama encapsulación de datos u ocultación de datos.Enseñarle OOP y / o C ++ está un poco fuera del alcance de la respuesta a esta pregunta, así que me detendré aquí.
Para resumir: se deben evitar los globals y casi siempre es posible reducir drásticamente la cantidad de globals. También cuando estás programando para Arduino.
Lo más importante es que espero que mi respuesta sea algo útil para usted :)
fuente
Program::instance().setup()
lugar de hacerloglobalProgram.setup()
. Poner variables globales relacionadas en una clase / estructura / espacio de nombres puede ser beneficioso, especialmente si solo son necesarias para un par de funciones relacionadas, pero el patrón singleton no agrega nada. En otras palabras,static Program p;
tiene almacenamiento global ystatic Program& instance()
tiene acceso global, lo que equivale a lo simpleProgram globalProgram;
.Las variables globales nunca son malas . Una regla general en contra de ellos es solo una muleta para permitirte sobrevivir el tiempo suficiente para obtener la experiencia para tomar mejores decisiones.
Lo que es una variable global es una suposición inherente de que solo hay una cosa (no importa si estamos hablando de una matriz o mapa global que podría contener varias cosas, que aún contiene la suposición de que solo hay una lista o mapeo, y no múltiples independientes).
Entonces, antes de hacer uso de un global, desea preguntarse: ¿es concebible que alguna vez quiera usar más de una de estas cosas? Si resulta ser cierto en el futuro, tendrá que modificar el código para no globalizar esa cosa, y probablemente descubrirá en el camino que otras partes de su código dependen de esa suposición de singularidad, por lo que Tendré que arreglarlos también, y el proceso se vuelve tedioso y propenso a errores. Se enseña "No usar globals" porque generalmente es un costo bastante pequeño evitar los globals desde el principio, y evita la posibilidad de tener que pagar un costo elevado más adelante.
Pero los supuestos simplificadores que permiten los globales también hacen que su código sea más pequeño, más rápido y use menos memoria, ya que no tiene que pasar por alto la noción de qué cosa está usando, no tiene que hacer indirección, no tiene que considere la posibilidad de que lo deseado tal vez no exista, etc. En el embebido, es más probable que se vea limitado por el tamaño del código y / o el tiempo y / o la memoria de la CPU que en una PC, por lo que estos ahorros pueden ser importantes. Y muchas aplicaciones integradas también tienen más rigidez en los requisitos: usted sabe que su chip solo tiene uno de ciertos periféricos, el usuario no puede simplemente enchufar otro en un puerto USB o algo así.
Otra razón común para querer más de uno de algo que parece único es la prueba: probar la interacción entre dos componentes es más fácil cuando puede pasar una instancia de prueba de algún componente a una función, mientras que tratar de modificar el comportamiento de un componente global es Una propuesta más complicada. Pero las pruebas en el mundo integrado tienden a ser muy diferentes a las de otros lugares, por lo que esto puede no aplicarse a usted. Hasta donde yo sé, Arduino no tiene cultura de prueba alguna.
Así que adelante y use globals cuando parezca que valen la pena. El código policial no vendrá a buscarte. Solo sé que la elección incorrecta podría llevarte a mucho más trabajo en el futuro, así que si no estás seguro ...
fuente
nada es inherentemente malo, incluidas las variables globales. Lo caracterizaría como un "mal necesario": puede hacer que su vida sea mucho más fácil, pero debe abordarse con precaución.
use funciones de contenedor para acceder a sus variables globales. así que al menos lo estás manejando desde la perspectiva del alcance.
fuente