Это правильное использование #define для облегчения ввода повторяющегося кода?


17

Есть ли мнение о том, является ли использование #define для определения полных строк кода для упрощения кодирования хорошей или плохой практикой программирования? Например, если бы мне нужно было напечатать несколько слов вместе, меня раздражало бы печатать

<< " " <<

Вставить пробел между словами в операторе cout. Я мог бы просто сделать

#define pSpace << " " <<

и введите

cout << word1 pSpace word2 << endl;

Для меня это не добавляет и не вычитает из ясности кода и делает ввод текста немного легче. Есть и другие случаи, когда я могу подумать о том, где печатать будет намного проще, обычно для отладки.

Есть мысли по этому поводу?

РЕДАКТИРОВАТЬ: Спасибо за все отличные ответы! Этот вопрос пришел ко мне после многократного набора текста, но я никогда не думал, что будут другие, менее запутанные макросы. Для тех, кто не хочет читать все ответы, лучшая альтернатива состоит в том, чтобы использовать макросы вашей IDE, чтобы уменьшить количество повторений.


74
Это понятно тебе, потому что ты это изобрел. Для всех остальных это просто запутано. Сначала это выглядит как синтаксическая ошибка. Когда он компилируется, я думаю, что за хрень, а потом обнаружу, что у вас есть макрос, который не во всех заглавных буквах. По моему мнению, это просто делает код ужасным для обслуживания, я бы определенно отверг это, если бы он пришел для проверки кода, и я не ожидаю, что вы найдете много людей, которые примут его. И вы сохраняете 3 персонажа !!!!!!!!!
Мартин Йорк,

11
Там, где вы не можете разумно выделить повторяемость с помощью функций или чего-либо еще, лучший подход состоит в том, чтобы узнать, что может сделать ваш редактор или IDE, чтобы помочь вам. Макросы текстового редактора или горячие клавиши «сниппет» могут избавить вас от необходимости набирать текст без ущерба для читабельности.
Steve314

2
Я делал это раньше (с большими блоками шаблонов), но моей практикой было написать код, затем запустить препроцессор и заменить исходный файл выходным сигналом препроцессора. Избавил меня от набора текста, избавил меня (и других) от хлопот по обслуживанию.
TMN

9
Вы сохранили 3 символа и обменяли их на запутанные утверждения. Очень хороший пример плохого макроса, imho: o)
MaR

7
Многие редакторы имеют расширенную функцию именно для этого сценария, она называется «Копирование и вставка»
Крис Берт-Браун

Ответы:


111

Написание кода легко. Читать код сложно.

Вы пишете код один раз. Он живет годами, люди читают его сто раз.

Оптимизируйте код для чтения, а не для записи.


11
Я согласен на 100%. (На самом деле, я собирался написать этот ответ сам.) Код написан один раз, но может быть прочитан десятки, сотни или даже тысячи раз, может быть, десятками, сотнями или даже тысячами разработчиков. Время, которое требуется для написания кода, совершенно не имеет значения, единственное, что имеет значение, - это время, чтобы прочитать и понять его.
ВОО

2
Препроцессор можно и нужно использовать для оптимизации кода для чтения и обслуживания.
SK-logic

2
И даже если вы просто читаете код в течение одного или двух лет: вы забудете эти вещи сами, делая другие промежуточные вещи.
Йоханнес

2
«Всегда пишите код, как будто парень, который в конечном итоге будет поддерживать ваш код, будет жестоким психопатом, который знает, где вы живете». - (Мартин Голдинг)
Дилан Яга

@Dylan - в противном случае, через несколько месяцев поддерживать этот код, он будет найти вас - (Me)
Steve314

28

Лично я ненавижу это. Есть ряд причин, почему я отговариваю людей от этой техники:

  1. Во время компиляции ваши реальные изменения кода могут быть значительными. Следующий парень приходит и даже включает закрывающую скобку в своем #define или вызове функции. То, что написано в определенной точке кода, далеко от того, что будет после предварительной обработки.

  2. Это нечитаемо Это может быть ясно для вас ... пока ... если это только одно определение. Если это станет привычкой, вы скоро получите десятки #defines и начнете терять след. Но, что хуже всего, никто не сможет понять, что word1 pSpace word2именно означает (не глядя на #define).

  3. Это может стать проблемой для внешних инструментов. Скажем, вы каким-то образом получили #define, который включает закрывающую скобку, но не открывающую скобку. Все может работать хорошо, но редакторы и другие инструменты могут видеть что-то вроде function(withSomeCoolDefine;довольно своеобразного (то есть, они сообщат об ошибках и еще много чего). (Подобный пример: вызов функции внутри определения - смогут ли ваши инструменты анализа найти этот вызов?)

  4. Обслуживание становится намного сложнее. У вас есть все те, которые определяют в дополнение к обычным проблемам, которые сопровождает обслуживание. В дополнение к вышеупомянутому пункту, поддержка инструментов для рефакторинга также может быть затронута отрицательно.


4
Я наконец запретил себе почти все макросы из-за проблем с обработкой их прямо в Doxygen. Прошло уже довольно много лет, и в целом, я думаю, читаемость значительно улучшилась - независимо от того, использую ли я Doxygen или нет.
Steve314

16

Моя основная мысль заключается в том, что при написании кода я никогда не использую «упростить типизацию».

Мое основное правило при написании кода - сделать его легко читаемым. Это объясняется тем, что код читается на порядок больше, чем написано. Таким образом, время, которое вы теряете, написав его тщательно, упорядоченно, правильно выложено, фактически потрачено на дальнейшее чтение и понимание намного быстрее.

Таким образом, #define, который вы используете, просто нарушает обычный способ чередования <<и прочего . Это нарушает правило наименьшего удивления, и это не ИМХО хорошая вещь.


1
+1: «Код читается на порядок больше, чем написано» !!!!
Джорджио

14

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

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

Первый пример появляется в CppUnit , который является структурой модульного тестирования. Как и любая другая стандартная среда тестирования, вы создаете тестовый класс, а затем вам необходимо каким-то образом указать, какие методы следует запускать как часть теста.

#include <cppunit/extensions/HelperMacros.h>

class ComplexNumberTest : public CppUnit::TestFixture  
{
    CPPUNIT_TEST_SUITE( ComplexNumberTest );
    CPPUNIT_TEST( testEquality );
    CPPUNIT_TEST( testAddition );
    CPPUNIT_TEST_SUITE_END();

 private:
     Complex *m_10_1, *m_1_1, *m_11_2;
 public:
     void setUp();
     void tearDown();
     void testEquality();
     void testAddition();
}

Как видите, у класса есть блок макросов в качестве первого элемента. Если бы я добавил новый методtestSubtraction , очевидно, что вам нужно сделать, чтобы включить его в тестовый прогон.

Эти макроблоки расширяются до чего-то вроде этого:

public: 
  static CppUnit::Test *suite()
  {
    CppUnit::TestSuite *suiteOfTests = new CppUnit::TestSuite( "ComplexNumberTest" );
    suiteOfTests->addTest( new CppUnit::TestCaller<ComplexNumberTest>( 
                                   "testEquality", 
                                   &ComplexNumberTest::testEquality ) );
    suiteOfTests->addTest( new CppUnit::TestCaller<ComplexNumberTest>(
                                   "testAddition",
                                   &ComplexNumberTest::testAddition ) );
    return suiteOfTests;
  }

Что бы вы предпочли читать и поддерживать?

Другой пример - инфраструктура Microsoft MFC, где вы отображаете функции на сообщения:

BEGIN_MESSAGE_MAP( CMyWnd, CMyParentWndClass )
    ON_MESSAGE( WM_MYMESSAGE, OnMyMessage )
    ON_COMMAND_RANGE(ID_FILE_MENUITEM1, ID_FILE_MENUITEM3, OnFileMenuItems)
    // ... Possibly more entries to handle additional messages
END_MESSAGE_MAP( )

Итак, что же отличает «Хорошие Макросы» от ужасного злого?

  • Они выполняют задачу, которую нельзя упростить другим способом. Написание макроса для определения максимума между двумя элементами является неправильным, потому что вы можете добиться того же, используя метод шаблона. Но есть некоторые сложные задачи (например, отображение кодов сообщений на функции-члены), которые язык C ++ просто не выполняет элегантно.

  • Они имеют чрезвычайно строгое формальное использование. В обоих этих примерах макроблоки объявляются с помощью начального и конечного макросов, а промежуточные макросы будут появляться только внутри этих блоков. У вас нормальный C ++, вы на короткое время извиняете себя блоком макросов, а затем снова возвращаетесь в нормальное состояние. В примерах «злых макросов» макросы разбросаны по всему коду, и незадачливый читатель не может узнать, когда применяются правила C ++, а когда - нет.


5

Во что бы то ни стало, будет лучше, если вы настроите свой любимый IDE / текстовый редактор для вставки фрагментов кода, который вам кажется утомительным повторять снова и снова. А лучше это «вежливый» термин для сравнения. На самом деле, я не могу думать ни о каком подобном случае, когда предварительная обработка бьет макросы редактора. Ну, может быть один - когда по каким-то таинственным и несчастным причинам вы постоянно используете различный набор инструментов для кодирования. Но это не оправдание :)

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


2
+1. Действительно: пусть редактор сделает всю работу за вас. Вы получите лучшее из обоих миров, если, например, сделаете аббревиатуру << " " <<.
unperson325680

-1 для «Это также может быть лучшим решением для более сложных сценариев, когда предварительная обработка текста может сделать что-то гораздо более нечитаемым и сложным (подумайте о параметризованном вводе)» - Если это так сложно, создайте метод для этого, даже тогда, сделайте метод для этого. Например, это зло, которое я недавно обнаружил в коде ..... #define printError (x) {put (x); return x}
mattnz

@mattnz, я имел в виду циклические конструкции, конструкции if / else, шаблоны для создания компаратора и так далее - все в таком духе. В IDE такой вид параметризованного ввода помогает вам не только быстро набирать несколько строк кода, но и быстро перебирать параметры. Никто не пытается конкурировать с методами. метод есть метод)))
шабун

4

Другие уже объяснили, почему вы не должны этого делать. Ваш пример, очевидно, не заслуживает реализации с помощью макроса. Но есть широкий спектр случаев, когда у вас есть использовать макросы для удобства чтения.

Известный пример мудрого применения такой техники - проект Clang : посмотрите, как .defтам используются файлы. С помощью макросов #includeвы можете предоставить одно, иногда полностью декларативное определение для коллекции похожих вещей, которые будут развернуты в объявления типов, caseоператоры, где это уместно, инициализаторы по умолчанию и т. Д. Это значительно повышает удобство сопровождения: вы никогда не забудете добавить новыеcase заявления везде, когда вы добавили новыйenum , например.

Так что, как и с любым другим мощным инструментом, вы должны осторожно использовать препроцессор Си. В искусстве программирования нет общих правил, таких как «вы никогда не должны использовать это» или «вы всегда должны использовать это». Все правила не что иное, как руководящие принципы.


3

Никогда не стоит так использовать #defines. В вашем случае вы можете сделать это:

class MyCout 
{
public:
  MyCout (ostream &out) : m_out (out), m_space_pending (false)
  {
  }

  template <class T>
  MyCout &operator << (T &value)
  { 
    if (m_space_pending)
      m_out << " ";

    m_out << value;
    m_space_pending = false;
    return *this;
  }

  MyCout &operator << (const char *value)
  {
    if (m_space_pending)
      m_out << " ";

    m_out << value;
    m_space_pending = true;
    return *this;
  }

  MyCout &operator << (char *value) { return operator << (static_cast <const char *> (value)); }
  MyCout &operator << (ostream& (*fn)(ostream&)) { m_out << fn; return *this; }

private:
  ostream
    &m_out;

  bool
    m_space_pending;
};

int main (int argc, char *argv [])
{
  MyCout
    space_separated (cout);

  space_separated << "Hello" << "World" << endl;
}

2

Нет.

Для макросов, предназначенных для использования в коде, хорошее руководство для проверки соответствия - заключить его расширение в круглые скобки (для выражений) или фигурные скобки (для кода) и посмотреть, будет ли оно все еще компилироваться:

// These don't compile:

#define pSpace (<< " " <<)
cout << word1 pSpace word2 << endl;

#define space(x) (" " << (x))
cout << word1 << space(word2) << endl;

// These do:

#define FOO_FACTOR (38)
x = y * FOO_FACTOR;

#define foo() (cout << "Foo" << endl)
foo();

#define die(c) { if ((c)) { exit(1); } }
die(foo > 8);

#define space(x) (" " + string((x)))
cout << "foo" << space("bar") << endl;

Макросы, используемые в объявлениях (как пример в ответе Эндрю Шепарда), могут избавиться от более слабого набора правил, если они не нарушают окружающий контекст (например, переключение между publicи private).


1

Это вполне допустимая вещь в чистой программе на «С».

Это не нужно и запутанно в программе на C ++.

Есть много способов избежать повторяющейся типизации кода в C ++. От использования средств, предоставляемых вашей IDE (даже с vi простое " %s/ pspace /<< " " <</g" сэкономит столько же времени при наборе текста и все равно даст стандартный читаемый код). Вы можете определить частные методы для реализации этого или для более сложных случаев шаблон C ++ будет чище и проще.


2
Нет, в чистом C. это недопустимо делать в одиночку. Отдельные значения или полные независимые выражения, которые полагаются только на макропараметры, да, и для последнего функция может быть даже лучшим выбором. Такой полуобжаренной конструкции, как в примере, нет.
Безопасный

@secure - я согласен, что в данном примере это не очень хорошая идея. Но, учитывая отсутствие шаблонов и т. Д., Существует допустимое использование макросов "#DEFINE" в C.
Джеймс Андерсон,

1

В C ++ это можно решить с помощью перегрузки операторов. Или даже что-то простое, как функция с переменным числом:

lineWithSpaces(word1, word2, word3, ..., wordn) это просто и экономит ваше печатание pSpaces снова и снова.

Так что, хотя в вашем случае это может показаться не таким уж большим делом, есть решение, которое проще и надежнее.

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


0

Есть ли мнение о том, является ли использование #define для определения полных строк кода для упрощения кодирования хорошей или плохой практикой программирования?

Да, это очень плохо. Я даже видел людей, делающих это:

#define R return

сохранить набор текста (что вы пытаетесь достичь).

Такой код принадлежит только в таких местах, как это .


-1

Макросы - это зло, и их следует использовать только тогда, когда это действительно необходимо. Есть несколько случаев, когда применимы макросы (в основном отладка). Но в C ++ в большинстве случаев вы можете использовать встроенные функции.


2
В любой технике программирования нет ничего злого. Можно использовать все возможные инструменты и методы, если вы знаете, что делаете. Это относится к печально известной goto, ко всем возможным макросистемам и т. Д.
SK-logic

1
Это именно то определение зла в данном случае: «то, чего вам следует избегать большую часть времени, но не то, чего вам следует избегать все время». По ссылке объясняется, что зло указывает.
Сакиск

2
Я считаю, что это контрпродуктивно - маркировать что-либо как «злое», «потенциально вредное» или даже «подозрительное». Мне не нравятся понятия «плохая практика» и «кодовый запах». Каждый разработчик должен решить в каждом конкретном случае, какая практика вредна. Маркировка - это вредная практика - люди, как правило, больше не думают, если другие уже что-то пометили.
SK-logic

-2

Нет, вы не можете использовать макросы для сохранения ввода .

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

Для сокращения набора текста большинство редакторов имеют макросы, даже интеллектуальные фрагменты кода.


«Нет, вам не разрешено использовать макросы для сохранения ввода ». - Хорошая работа, я не собираюсь слушать ваш заказ здесь! Если у меня есть куча объектов, которые мне нужно объявить / определить / карту / switch/ и т. Д., Вы можете поспорить, что я собираюсь использовать макросы, чтобы избежать сумасшествия и повысить читабельность. Использование макросов для сохранения ввода по ключевым словам, управления потоком данных и т. П. Глупо, но говорить, что никто не может использовать их для сохранения нажатий клавиш в допустимых контекстах, в равной степени глупо.
underscore_d
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.