Почему foo-> bar-> foobar считается плохим стилем? И как обойтись без добавления кода? - программирование
Подтвердить что ты не робот

Почему foo-> bar-> foobar считается плохим стилем? И как обойтись без добавления кода?

Наш профессор С++ отметил, что использование результата оператора- > как ввода в другой оператор- > считается плохим.

Итак, вместо написания:

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

Он предпочел бы:

Node* terminal = edge->terminal;
return terminal->outgoing_edges[0];
  • Почему это считается плохой стиль?
  • Как я могу реструктурировать свою программу, чтобы избежать "плохого стиля", но также избежать дополнительной строки кода, созданной в соответствии с приведенным выше предложением?
4b9b3361

Ответ 1

Вы должны спросить своего профессора, почему он считает его плохим. Я не. Тем не менее, я бы счел, что его упущение const в объявлении терминала плохое.

Для одного такого фрагмента, это, вероятно, не плохой стиль. Однако рассмотрите это:

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;
}

Это начинает становиться громоздким - его трудно читать, его трудно написать (я сделал примерно 10 опечаток при наборе текста). И это требует большой оценки оператора- > на этих двух классах. Хорошо, если оператор тривиален, но если оператор делает что-то захватывающее, он будет в конечном итоге выполнять большую работу.

Итак, есть 2 возможных ответа:

  • Maintanability
  • Эффективность

И один такой сниппет, вы не можете избежать лишней строки. В чем-то подобном выше, это привело бы к уменьшению набора текста.

Ответ 2

Есть ряд причин.

Закон Деметры дает структурную причину (обратите внимание, что ваш С++-профессорский код по-прежнему нарушает это, хотя!). В вашем примере edge должен знать о terminal и outgoing_edges. Это делает его плотно связанным.

В качестве альтернативы

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

Теперь вы можете изменить реализацию outgoing_edges в одном месте, не меняя везде. Честно говоря, я действительно не покупаю это в случае структуры данных, такой как график (он тесно связан, ребра и узлы не могут убежать друг от друга). Это была чрезмерная абстракция в моей книге.

Там также проблема с нулевым разыменованием, в выражении a->b->c, что, если b равно null?

Ответ 3

Ну, я не могу быть уверен в том, что имел в виду ваш профессор, но у меня есть некоторые мысли.

Прежде всего, такой код может быть "не столь очевидным":

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

Вы не можете сказать, что является результатом foo(), и поэтому вы не можете сказать, что называется bar(). Это тот же объект? Или, может быть, это создаваемый темп-объект? Или, может быть, это что-то еще?

Другое дело, если вам нужно повторить свой код. Рассмотрим пример:

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

Сравните это с таким:

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

Вы можете видеть, как второй пример не только короче, но и легче читать.

Иногда цепочка функций проще, потому что вам не нужно беспокоиться о том, что они возвращают:

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

против

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

Еще одна вещь - отладка. Очень сложно отлаживать длинные цепочки вызовов функций, потому что вы просто не можете понять, какая из них вызывает ошибку. В этом случае разрыв цепи помогает много, не говоря уже о том, что вы можете делать некоторые дополнительные проверки, например

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

Итак, резюмируя, вы не можете сказать, что это вообще "плохой" или "хороший" стиль. Сначала вы должны рассмотреть свои обстоятельства.

Ответ 4

Два фрагмента кода, которые вы показали, (конечно) семантически идентичны. Когда дело доходит до вопросов стиля, предложите вам просто следовать тому, что хочет ваш профессор.

Чуть лучше, чем ваш второй фрагмент кода:

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

Я предполагаю, что outgoing_edges - это массив; если нет, вы должны проверить, что это тоже NULL или пусто.

Ответ 5

В их текущей форме не обязательно лучше. Однако, если бы вы добавили чек для null во второй пример, это имело бы смысл.

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

Хотя даже это все еще плохо, потому что кто скажет, что outgoing_edges правильно заполнен или выделен. Лучшая ставка будет заключаться в вызове функции с именем outgoingEdges(), которая делает все, что славная проверка ошибок для вас, и никогда не вызывает вас undefined поведение.