2012-07-10 4 views
16

Nuestro profesor de C++ mencionó que usar el resultado de operador-> como entrada en otro operador-> se considera estilo incorrecto.¿Por qué foo-> bar-> foobar se considera mal estilo? ¿Y cómo evitar sin agregar código?

Así que en lugar de escribir:

return edge->terminal->outgoing_edges[0]; 

él preferiría:

Node* terminal = edge->terminal; 
return terminal->outgoing_edges[0]; 
  1. Por qué es esto considerado mal estilo?
  2. ¿Cómo podría reestructurar mi programa para evitar el "mal estilo" pero también evitar la línea adicional de código que se crea según la sugerencia anterior?
+15

Nunca había escuchado eso. El único beneficio que puedo ver es que si obtiene un error de un puntero nulo, puede decir a partir del número de línea cuál fue la desreferencia que fue, y es marginalmente más fácil examinar el puntero intermedio en un depurador. Pero si existiera el peligro de una desreferencia nula, debería verificarlo explícitamente antes de la segunda línea. No, no puedo ver una buena razón. – Rup

+1

Es más fácil depurar una cosa. Es más simple probar si el terminal es nulo. –

+0

Tal vez porque eso ayudará cuando tenga un puntero nulo sin referencias y esté tratando de averiguar cuál.De esta forma, cuando comprenda la línea de error (de un depurador, por ejemplo) sabrá qué puntero es) – Mohammad

Respuesta

12

Debe preguntarle a su profesor por qué lo considera un mal estilo. Yo no. Sin embargo, consideraría su omisión de la const en la declaración de terminal como un mal estilo.

Para un solo fragmento como ese, probablemente no sea un mal estilo. Sin embargo considere esto:

void somefunc(Edge *edge) 
{ 
    if (edge->terminal->outgoing_edges.size() > 5) 
    { 
     edge->terminal->outgoing_edges.rezize(10); 
     edge->terminal->counter = 5; 
    } 
    ++edge->terminal->another_value; 
} 

Esto está comenzando a conseguir difícil de manejar - que es difícil de leer, es difícil escribir (he hecho unos 10 errores ortográficos al escribir esto). Y requiere mucha evaluación del operador-> en esas 2 clases. Está bien si el operador es trivial, pero si el operador hace algo emocionante, terminará haciendo mucho trabajo.

Así que hay 2 posibles respuestas:

  1. Maintanability
  2. eficiencia

Y un solo fragmento de esa manera, no se puede evitar que la línea adicional. En algo como lo anterior, habría resultado en menos tipeo.

+6

Con este código, definitivamente veo el beneficio al declarar el puntero primero porque se usa varias veces en el mismo bloque de código. Como regla general, si 'uso' algo más de una vez en un bloque de código, lo declaro como una variable, y esto cae bajo esa regla de oro. Parece que en mi ejemplo simple, probablemente no sea un mal estilo. Pero si el código fuera más complejo, lo sería. – HorseloverFat

+2

Typo? "edge-> terminal-counter" –

+1

¡Descanso mi caso! (Aunque veo que ha sido editado para corregirlo) –

20

Existen varios motivos.

Law of Demeter da una razón estructural (tenga en cuenta que su código de profesores C++ todavía viola esto!). En su ejemplo, edge tiene que saber aproximadamente terminal y outgoing_edges. Eso lo hace muy unido.

Como alternativa

class Edge { 
private: 
    Node* terminal; 
public: 
    Edges* outgoing_edges() { 
     return terminal->outgoing_edges; 
    } 
} 

Ahora puede cambiar la implementación de outgoing_edges en un solo lugar sin tener que cambiar todas partes. Para ser honesto, realmente no compro esto en el caso de una estructura de datos como un gráfico (está estrechamente acoplado, los bordes y los nodos no pueden escapar entre sí). Esto sería una abstracción excesiva en mi libro.

También existe el problema de eliminación de referencias nulas, en la expresión a->b->c, ¿qué ocurre si b es nulo?

+2

La Ley de Demeter ciertamente prohíbe las cadenas de '->', pero también prohíbe la forma preferida de este profesor 'Node * terminal = borde-> terminal; '. Sin embargo, esta es probablemente una razón mejor que la razón del prof. –

+1

¿este ejemplo sigue funcionando si cambio el 'Terminal' a un nodo * según mi ejemplo de código? (He editado). Sin embargo, no veo por qué está menos acoplado ... ¿'Edge' todavía no 'sabe' sobre Node * y outgoing_edges [] en este ejemplo? – HorseloverFat

+0

Creo que la Ley de Demeter se aplica principalmente a la capacidad de prueba. Para gráficos de objetos más complejos, cuando se prueba (por ejemplo) borde, sería algo molesto simular un objeto 'terminal' complejo para devolver un objeto 'outgoing_edges' burlado. Sería más fácil simular 'getOutgoingEdges()'. –

3

Los dos fragmentos de código que ha mostrado son (por supuesto) semánticamente idénticos. Cuando se trata de cuestiones de estilo, sugiérales simplemente seguir lo que tu profesor quiere.

Ligeramente mejor que el segundo fragmento de código sería:

Node* terminal = (edge ? edge->terminal : NULL); 
return (terminal ? terminal->outgoing_edges[0] : NULL); 

Estoy asumiendo que outgoing_edges es una matriz; si no, debes verificar que sea NULO o que esté vacío también.

6

Bueno, no puedo estar seguro de lo que tu profesor quiso decir, pero tengo algunas ideas.

En primer lugar, el código como este puede ser "no tan obvia":

someObjectPointer->foo()->bar()->foobar() 

Usted realmente no puede decir lo que es el resultado de foo(), y por lo tanto no se puede decir realmente en ¿Cómo se llama el bar()? ¿Es ese el mismo objeto? O tal vez es un objeto temporal que se está creando? O tal vez es algo más?

Otra cosa es si tiene que repetir el código. Consideremos un ejemplo como este:

complexObject.somePart.someSubObject.sections[i].doSomething(); 
complexObject.somePart.someSubObject.sections[i].doSomethingElse(); 
complexObject.somePart.someSubObject.sections[i].doSomethingAgain(); 
complexObject.somePart.someSubObject.sections[i].andAgain(); 

compararlo con tales:

section * sectionPtr = complexObject.somePart.someSubObject.sections[i]; 
sectionPtr->doSomething(); 
sectionPtr->doSomethingElse(); 
sectionPtr->doSomethingAgain(); 
sectionPtr->andAgain(); 

se puede ver cómo el segundo ejemplo no sólo es más corto, pero más fácil de leer.

veces la cadena de funciones es más fácil, ya que no tiene que molestarse con lo que vuelven:

resultClass res = foo()->bar()->foobar() 

vs

classA * a = foo(); 
classB * b = a->bar(); 
classC * c = b->foobar(); 

Otra cosa es la depuración. Es muy difícil depurar cadenas largas de llamadas a funciones, porque simplemente no puede entender cuál de ellas causa el error. En este caso, rompiendo la cadena ayuda mucho, por no hablar de que puede hacer algunas comprobaciones adicionales también, como

classA * a = foo(); 
classB * b; 
classC * c; 
if(a) 
    b = a->bar(); 
if(b) 
    c = b->foobar(); 

tanto, para resumir, no se puede decir que es una "mala" o "buena "estilo en general. Tienes que considerar tus circunstancias primero.

1

Ninguno de los dos es necesariamente mejor en su forma actual. Sin embargo, si agregara un cheque para null en su segundo ejemplo, entonces esto tendría sentido.

if (edge != NULL) 
{ 
    Node* terminal = edge->terminal; 
    if (terminal != NULL) return terminal->outgoing_edges[0]; 
} 

Aunque incluso que es todavía mal porque ¿quién puede decir que outgoing_edges está poblada o asignados correctamente. Una mejor opción sería llamar a una función llamada outgoingEdges() que hace todo ese buen error comprobando para usted y nunca le causa comportamiento indefinido.

Cuestiones relacionadas