¿Por qué usar prefijos en variables miembro en clases de C ++? [c++]


Answers

Estoy a favor de los prefijos bien hechos .

Creo que la notación húngara (del Sistema) es responsable de la mayor parte del "mal rap" que obtienen los prefijos.

Esta notación es en gran parte inútil en lenguajes fuertemente tipados, por ejemplo, en C ++ "lpsz" para decirle que su cadena es un puntero largo a una cadena terminada en nulo, cuando: la arquitectura segmentada es historia antigua, las cadenas C ++ son por convención comunes punteros a nul-terminados ¡matrices de caracteres, y no es realmente tan difícil saber que "customerName" es una cadena!

Sin embargo, sí uso prefijos para especificar el uso de una variable (esencialmente "Aplicaciones húngaras", aunque prefiero evitar el término húngaro debido a que tiene una asociación mala e injusta con el sistema húngaro), y esto es muy útil para ahorrar tiempo y enfoque de reducción de errores .

Yo suelo:

  • m para miembros
  • c para constantes / readonlys
  • p para el puntero (y pp para el puntero al puntero)
  • v para volátil
  • s para estática
  • i para índices e iteradores
  • e para eventos

Cuando deseo hacer que el tipo sea claro, uso los sufijos estándar (por ejemplo, List, ComboBox, etc.).

Esto hace que el programador sea consciente del uso de la variable cada vez que la ve / usa. Podría decirse que el caso más importante es "p" para el puntero (porque el uso cambia de var a var-> y tiene que ser mucho más cuidadoso con los punteros - NULL, aritmética del puntero, etc.), pero todos los demás son muy útiles.

Por ejemplo, puede usar el mismo nombre de variable de varias maneras en una sola función: (aquí un ejemplo de C ++, pero se aplica por igual a muchos idiomas)

MyClass::MyClass(int numItems)
{
    mNumItems = numItems;
    for (int iItem = 0; iItem < mNumItems; iItem++)
    {
        Item *pItem = new Item();
        itemList[iItem] = pItem;
    }
}

Puedes ver aquí:

  • Sin confusión entre miembro y parámetro
  • Sin confusión entre índice / iterador y elementos
  • Uso de un conjunto de variables claramente relacionadas (lista de elementos, puntero e índice) que evitan las muchas trampas de nombres genéricos (vagos) como "recuento", "índice".
  • Los prefijos reducen el tipeo (más corto y funciona mejor con autocompletar) que alternativas como "itemIndex" y "itemPtr"

Otro gran punto de los iteradores "iName" es que nunca indexo una matriz con el índice incorrecto, y si copio un bucle dentro de otro bucle, no es necesario refactorizar una de las variables de índice de bucle.

Compare este ejemplo irrealmente simple:

for (int i = 0; i < 100; i++)
    for (int j = 0; j < 5; j++)
        list[i].score += other[j].score;

(que es difícil de leer y que a menudo conduce al uso de "i" donde se pretendía "j")

con:

for (int iCompany = 0; iCompany < numCompanies; iCompany++)
    for (int iUser = 0; iUser < numUsers; iUser++)
       companyList[iCompany].score += userList[iUser].score;

(que es mucho más legible y elimina toda confusión sobre la indexación. Con el autocompletado en IDEs modernos, esto también es rápido y fácil de escribir)

El siguiente beneficio es que los fragmentos de código no requieren ningún contexto para ser comprendido. Puedo copiar dos líneas de código en un correo electrónico o documento, y cualquiera que lea ese fragmento puede diferenciar entre todos los miembros, constantes, punteros, índices, etc. No tengo que agregar "oh, y tenga cuidado porque 'datos' es un puntero a un puntero ", porque se llama 'ppData'.

Y por la misma razón, no tengo que mover los ojos fuera de una línea de código para poder entenderlo. No tengo que buscar el código para encontrar si 'data' es un local, parámetro, miembro o constante. No tengo que mover la mano hacia el mouse para pasar el puntero sobre 'datos' y esperar a que aparezca una información sobre herramientas (que a veces nunca aparece). Por lo tanto, los programadores pueden leer y comprender el código significativamente más rápido, porque no pierden el tiempo buscando hacia arriba o hacia abajo o esperando.

(Si no crees que pierdes el tiempo buscando las cosas que salen, encuentra un código que escribiste hace un año y que no has visto desde entonces. Abre el archivo y salta a la mitad sin leerlo. Mira cómo Hasta ahora puede leer desde este punto antes de no saber si algo es miembro, parámetro o local. Ahora salte a otra ubicación aleatoria ... Esto es lo que hacemos todos durante todo el día cuando estamos solos caminando a través del código de otra persona o tratando de entender cómo llamar a su función)

El prefijo 'm' también evita la notación "this->" fea y vergonzosa, y la inconsistencia que garantiza (incluso si tiene cuidado, generalmente terminará con una mezcla de 'this-> data' y 'datos' en la misma clase, porque nada exige una ortografía consistente del nombre).

La notación "this" tiene la intención de resolver la ambigüedad , pero ¿por qué alguien escribiría código deliberadamente ambiguo? La ambigüedad dará lugar a un error tarde o temprano. Y en algunos idiomas, "esto" no se puede usar para miembros estáticos, por lo que debe introducir "casos especiales" en su estilo de codificación. Prefiero tener una sola regla de codificación simple que se aplique en todas partes: explícita, no ambigua y consistente.

El último gran beneficio es con Intellisense y autocompletado. Intente utilizar Intellisense en Windows Forms para encontrar un evento: tiene que desplazarse a través de cientos de misteriosos métodos de clase base que nunca necesitará llamar para encontrar los eventos. Pero si cada evento tuviera un prefijo "e", se incluirían automáticamente en un grupo debajo de "e". Por lo tanto, el prefijo funciona para agrupar los miembros, constelaciones, eventos, etc. en la lista intellisense, lo que hace que sea mucho más rápido y fácil encontrar los nombres que desee. (Por lo general, un método puede tener entre 20 y 50 valores (locals, params, members, consts, events) que son accesibles en su alcance. Pero después de tipear el prefijo (quiero usar un índice ahora, entonces escribo 'i. .. '), se me presentan solo 2-5 opciones de autocompletar. La gente' de tipo extra 'atribuye a los prefijos y nombres significativos reduce drásticamente el espacio de búsqueda y acelera de forma considerable la velocidad de desarrollo)

Soy un programador perezoso, y la convención anterior me ahorra mucho trabajo. Puedo codificar más rápido y cometo muchos menos errores porque sé cómo se deben usar todas las variables.

Argumentos en contra

Entonces, ¿cuáles son los contras? Los argumentos típicos contra los prefijos son:

  • "Los esquemas de prefijo son malos / malvados" . Estoy de acuerdo en que "m_lpsz" y su tipo están mal pensados ​​y son totalmente inútiles. Es por eso que le aconsejo utilizar una notación bien diseñada diseñada para respaldar sus requisitos, en lugar de copiar algo que no sea apropiado para su contexto. (Use la herramienta correcta para el trabajo).

  • "Si cambio el uso de algo, debo cambiarle el nombre" . Sí, por supuesto que sí, de eso se trata la refactorización, y de por qué los IDE tienen herramientas de refactorización para hacer este trabajo de forma rápida y sin complicaciones. Incluso sin prefijos, cambiar el uso de una variable casi seguro significa que su nombre debe ser cambiado.

  • "Los prefijos me confunden" . Al igual que todas las herramientas hasta que aprenda a usarlo. Una vez que tu cerebro se haya acostumbrado a los patrones de nombres, filtrará la información automáticamente y no te importará que los prefijos ya estén allí. Pero debe usar un esquema como este sólidamente durante una semana o dos antes de que realmente se vuelva "fluido". Y es entonces cuando mucha gente mira el código antiguo y comienza a preguntarse cómo se las arreglaron sin un buen esquema de prefijos.

  • "Solo puedo mirar el código para resolver esto" . Sí, pero no necesita perder el tiempo buscando en otro lugar del código o recordando cada pequeño detalle cuando la respuesta está en el lugar en el que su ojo ya está enfocado.

  • (Parte de) esa información se puede encontrar simplemente esperando a que aparezca una información sobre herramientas en mi variable . Sí. Cuando se admite, para algunos tipos de prefijo, cuando el código se compila limpiamente, después de una espera, puede leer una descripción y encontrar la información que el prefijo habría transmitido al instante. Siento que el prefijo es un enfoque más simple, más confiable y más eficiente.

  • "Es más tipeo" . De Verdad? Un personaje completo más? ¿O lo es? Con las herramientas de autocompletado de IDE, a menudo reducirá el tipeo, ya que cada carácter de prefijo reduce significativamente el espacio de búsqueda. Presiona "e" y los tres eventos en tu clase aparecerán en intellisense. Presione "c" y las cinco constantes se enumeran.

  • "Puedo usar this-> lugar de m " . Bueno, sí, puedes. ¡Pero eso es solo un prefijo mucho más feo y más detallado! Solo que conlleva un riesgo mucho mayor (especialmente en equipos) porque para el compilador es opcional , y por lo tanto su uso es frecuentemente inconsistente. m por otro lado es breve, claro, explícito y no opcional, por lo que es mucho más difícil cometer errores al usarlo.

Question

Una gran cantidad de código C ++ utiliza convenciones sintácticas para marcar las variables de miembro. Ejemplos comunes incluyen

  • m_ memberName para miembros públicos (donde se usan miembros públicos)
  • _ memberName para miembros privados o todos los miembros

Otros intentan hacer cumplir el uso de este miembro cuando se usa una variable miembro.

En mi experiencia, la mayoría de las bases de códigos más grandes fallan en la aplicación de tales reglas consistentemente.

En otros idiomas, estas convenciones están mucho menos extendidas. Lo veo solo ocasionalmente en código Java o C #. Creo que nunca lo he visto en el código de Ruby o Python. Por lo tanto, parece que hay una tendencia en los lenguajes más modernos de no usar marcado especial para las variables miembro.

¿Es esta convención todavía útil hoy en C ++ o es simplemente un anacronismo? Especialmente ya que se usa de forma tan inconsistente en las bibliotecas. ¿No han demostrado los otros idiomas que uno puede prescindir de los prefijos de los miembros?




Prefiero los guiones bajos de postfix, como los siguientes:

class Foo
{
   private:
      int bar_;

   public:
      int bar() { return bar_; }
};



No puedo decir qué tan amplio es, pero hablando personalmente, siempre (y siempre) he puesto como prefijo mis variables de miembro con 'm'. P.ej:

class Person {
   .... 
   private:
       std::string mName;
};

Es la única forma de prefijo que uso (soy notación muy anti-húngara) pero me ha sido muy útil a lo largo de los años. Como comentario adicional, generalmente detesto el uso de caracteres de subrayado en los nombres (o en cualquier otro lugar), pero hago una excepción para los nombres de macro del preprocesador, ya que generalmente están en mayúsculas.




No creo que una sintaxis tenga valor real sobre otra. Todo se reduce, como mencionaste, a la uniformidad en todos los archivos fuente.

El único punto donde encuentro tales reglas interesantes es cuando necesito 2 cosas nombradas idénticamente, por ejemplo:

void myFunc(int index){
  this->index = index;
}

void myFunc(int index){
  m_index = index;
}

Lo uso para diferenciar los dos. Además, cuando envuelvo las llamadas, como desde Windows Dll, RecvPacket (...) desde el Dll podría estar envuelto en RecvPacket (...) en mi código. En estas ocasiones en particular, el uso de un prefijo como "_" puede hacer que los dos se vean iguales, fácil de identificar cuál es cuál, pero diferente para el compilador




La razón principal para un prefijo miembro es distinguir entre una función miembro local y una variable miembro con el mismo nombre. Esto es útil si usa getters con el nombre de la cosa.

Considerar:

class person
{
public:
    person(const std::string& full_name)
        : full_name_(full_name)
    {}

    const std::string& full_name() const { return full_name_; }
private:
    std::string full_name_;
};

La variable miembro no se podría llamar nombre completo en este caso. Necesita cambiar el nombre de la función miembro a get_full_name () o decorar la variable miembro de alguna manera.




Otros intentan hacer cumplir el uso de este miembro> siempre que se usa una variable miembro

Eso es usualmente porque no hay prefijo . El compilador necesita suficiente información para resolver la variable en cuestión, ya sea un nombre único debido al prefijo o mediante la palabra clave this .

Entonces, sí, creo que los prefijos aún son útiles. Yo, por mi parte, preferiría escribir '_' para acceder a un miembro en lugar de 'this->'.




Cuando tiene un método grande o bloques de código, es conveniente saber de inmediato si usa una variable local o un miembro. ¡es para evitar errores y para una mejor claridad!




Como otros ya lo han dicho, la importancia es ser coloquial (adaptar los estilos y convenciones de los nombres a la base del código en el que está escribiendo) y ser coherente.

Durante años he trabajado en una gran base de código que usa tanto la convención "this->" como el uso de una notación de subrayado de postfijo para las variables miembro. A lo largo de los años, también trabajé en proyectos más pequeños, algunos de los cuales no tenían ningún tipo de convención para nombrar variables de miembros, y otros que tenían diferentes convenciones para nombrar variables de miembros. De esos proyectos más pequeños, siempre he encontrado aquellos que carecían de convenciones para ser los más difíciles de saltar rápidamente y comprender.

Soy muy analista sobre la nomenclatura. Agonizaré por el nombre para atribuirlo a una clase o variable hasta el punto de que, si no puedo encontrar algo que siento que es "bueno", elegiré nombrarlo como algo sin sentido y dar un comentario que describa lo que realmente es es. De esa manera, al menos el nombre significa exactamente lo que pretendo significar, nada más y nada menos. Y, a menudo, después de usarlo por un tiempo, descubro lo que el nombre debería realmente ser y puede volver atrás y modificar o refactorizar adecuadamente.

Un último punto sobre el tema de un IDE haciendo el trabajo, eso es muy bueno y bueno, pero los IDE a menudo no están disponibles en entornos donde he realizado el trabajo más urgente. A veces, lo único disponible en ese punto es una copia de 'vi'. Además, he visto muchos casos donde la finalización del código IDE ha propagado la estupidez, como la ortografía incorrecta en los nombres. Por lo tanto, prefiero no tener que depender de una muleta IDE.




Nuestro proyecto siempre ha usado "its" como un prefijo para los datos de los miembros, y "the" como un prefijo para los parámetros, sin prefijo para los locales. Es un poco cursi, pero fue adoptado por los primeros desarrolladores de nuestro sistema porque lo vieron utilizado como una convención por algunas bibliotecas de fuentes comerciales que estábamos usando en ese momento (ya sea XVT o RogueWave, tal vez ambas). Entonces obtendrías algo como esto:

void
MyClass::SetName(const RWCString &theName)
{
   itsName = theName;
}

La gran razón que veo para determinar el alcance de los prefijos (y no de otros, odio la notación húngara) es que te impide meterse en problemas escribiendo código donde crees que te refieres a una variable, pero en realidad te estás refiriendo a otra variable con el mismo nombre definido en el alcance local. También evita el problema de encontrar nombres de variables para representar el mismo concepto, pero con diferentes ámbitos, como el ejemplo anterior. En ese caso, tendría que encontrar algún prefijo o nombre diferente para el parámetro "theName" de todos modos, ¿por qué no hacer una regla consistente que se aplique en todas partes?

Simplemente usar esto-> no es realmente lo suficientemente bueno, no estamos tan interesados ​​en reducir la ambigüedad como lo estamos en reducir los errores de codificación, y enmascarar los nombres con identificadores de ámbito local puede ser un problema. Por supuesto, algunos compiladores pueden tener la opción de generar advertencias para los casos en los que haya enmascarado el nombre en un alcance mayor, pero esas advertencias pueden ser una molestia si está trabajando con un gran conjunto de bibliotecas de terceros que han elegido nombres de variables no utilizadas que ocasionalmente colisionan con las suyas.

En cuanto al its / the itself - sinceramente me resulta más fácil escribir que caracteres de subrayado (como un mecanógrafo táctil, evito los subrayados siempre que sea posible - demasiado estiramiento de las filas de inicio), y me resulta más legible que un subrayado misterioso.




Es útil diferenciar entre las variables miembro y las variables locales debido a la administración de la memoria. En términos generales, las variables de miembro asignadas en el montón deben destruirse en el destructor, mientras que las variables locales asignadas en el montón deben destruirse dentro de ese alcance. La aplicación de una convención de nomenclatura a las variables miembro facilita la gestión correcta de la memoria.




Code Complete recomienda m_varname para las variables miembro.

Aunque nunca pensé que la notación m_ fuera útil, le daría peso a la opinión de McConnell al construir un estándar.




En Python, los guiones bajos principales se usan para emular a los miembros privados. Para más detalles, vea esta respuesta




Me gustan los nombres de variables para darles solo un significado a los valores que contienen y dejar cómo se declaran / implementan fuera del nombre. Quiero saber qué significa el valor, punto. Tal vez he hecho más que una cantidad promedio de refactorización, pero encuentro que incorporar cómo algo se implementa en el nombre hace que la refactorización sea más tediosa de lo que debe ser. Los prefijos que indican dónde o cómo se declaran los miembros del objeto son específicos de la implementación.

color = Red;

La mayoría de las veces, no me importa si Red es una enumeración, una estructura o lo que sea, y si la función es tan grande que no puedo recordar si el color se declaró localmente o si es miembro, probablemente sea hora de romper. la función en unidades lógicas más pequeñas.

Si su complejidad ciclomática es tan grande que no puede realizar un seguimiento de lo que está sucediendo en el código sin las pistas específicas de la implementación incluidas en los nombres de las cosas, lo más probable es que necesite reducir la complejidad de su función / método.

Sobre todo, solo uso 'esto' en constructores e inicializadores.