Чи правильно використовувати #define, щоб полегшити введення повторного коду?


17

Чи є думка про те, чи є використання #define для визначення повних рядків коду для спрощення кодування є хорошою чи поганою практикою програмування? Наприклад, якщо мені потрібно було друкувати купу слів разом, я би роздратований набравши текст

<< " " <<

Щоб вставити пробіл між словами в cout оператор. Я міг би просто зробити

#define pSpace << " " <<

і тип

cout << word1 pSpace word2 << endl;

Для мене це ні додає, ні віднімає ясності коду, а введення тексту трохи простіше. Є й інші випадки, які я можу придумати, де набрати текст буде набагато простіше, як правило, для налагодження.

Будь-які думки з цього приводу?

EDIT: Дякую за всі чудові відповіді! Це питання якраз прийшло до мене після багаторазового набору тексту, але я ніколи не думав, що будуть використовувати інші, менш заплутані макроси. Для тих, хто не хоче прочитати всі відповіді, найкращою альтернативою є використання макросів IDE для зменшення повторного введення тексту.


74
Це вам зрозуміло, тому що ви його вигадали. Для всіх інших це просто заплутано. Спочатку це виглядає як синтаксична помилка. Коли вона компілюється, я б подумала, що за чорт, а потім виявила, що у вас є макрос, який є не у всіх шапках. На мою думку, це просто робить код жахливим для твердження, я б напевно відкинув це, якби він прийшов для перегляду коду, і я не сподіваюся, що ви знайдете багато, хто би його прийняв. І ви економите 3 символи !!!!!!!!!
Мартін Йорк

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

2
Я робив це раніше (з більшими шматками котла), але моя практика полягала в тому, щоб написати код, потім запустити препроцесор і замінити вихідний файл на вихід препроцесора. Врятувало мене набравши текст, позбавило мене (та інших) клопоту з технічного обслуговування.
TMN

9
Ви зберегли 3 символи та торгували ними за заплутані твердження. Дуже хороший приклад поганої макро, имхо: о)
MaR

7
Багато редакторів мають вдосконалену функцію саме для цього сценарію, вона називається "Скопіювати та вставити"
Кріс Берт-Браун

Відповіді:


111

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

Ви пишете код один раз. Це живе роками, люди читають його сто разів.

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


11
Я згоден на 100%. (Насправді я збирався написати цю відповідь сам.) Код написаний один раз, але його можна прочитати десятки, сотні, а то й тисячі разів, можливо, десятки, сотні чи навіть тисячі розробників. Час, необхідний для написання коду, абсолютно не має значення, єдине, що враховує, - це час його прочитати та зрозуміти.
sbi

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

2
І навіть якщо ви просто читаєте код через один-два роки: Ви самі забудете ці речі, роблячи інші речі між ними.
johannes

2
"Завжди кодуйте так, ніби хлопець, який закінчує підтримувати ваш код, буде жорстоким психопатом, який знає, де ви живете". - (Мартін Голдінг)
Ділан Яга

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

28

Особисто я це ненавиджу. Існує ряд причин, чому я відштовхую людей від цієї методики:

  1. Під час компіляції фактичні зміни коду можуть бути суттєвими. Наступний хлопець приходить і навіть включає фіксатор, що закриває, у свій #define або виклик функції. Те, що написано в певному коді, далеко не те, що буде там після попередньої обробки.

  2. Це не читається. Вам може бути зрозуміло .. поки що .. якщо це лише одне визначення. Якщо це стане звичкою, незабаром ви закінчитеся з десятками #defines і почнете втрачати себе. Але найгірше, що ніхто більше не зможе зрозуміти, що word1 pSpace word2саме означає (не дивлячись на #define).

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

  4. Технічне обслуговування стає набагато складніше. У вас є всі ці функції, які визначаються, крім звичайних проблем, які підтримує технічне обслуговування. На додаток до вищезазначеного, на підтримку інструментів для рефакторингу також може негативно вплинути.


4
Нарешті я заборонив собі майже всі макроси через клопоти намагатися впоратися з ними прямо в 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, я маю на увазі конструкції циклу, якщо / else конструкції, шаблони для створення компараторів тощо - подібні речі. В IDE такий тип параметризованого введення допомагає вам не тільки швидко вводити кілька рядків коду, але і швидко повторювати через парами. Ніхто не намагається конкурувати з методами. метод - метод)))
shabunc

4

Інші вже пояснили, чому ви не повинні цього робити. Ваш приклад, очевидно, не заслуговує на реалізацію з макросом. Але, існує широкий спектр випадків, коли вам доводиться використовувати макроси заради читабельності.

Сумно відомий приклад розумного застосування такої методики - проект Clang : подивіться, як .defтам використовуються файли. За допомогою макросів #includeви можете надати єдине, іноді цілком декларативне визначення для колекції подібних речей, які будуть розгорнуті в декларації типів, caseзаяви, коли це доречно, ініціалізатори за замовчуванням тощо. Це значно збільшує ремонтопридатність: ви ніколи не забудете додати нові caseтвердження скрізь, коли ви додали нове enum, наприклад.

Отже, як і будь-який інший потужний інструмент, ви повинні ретельно використовувати C-препроцесор. У мистецтві програмування немає загальних правил, наприклад "ти ніколи не повинен цим користуватися" або "ти завжди повинен цим користуватися". Усі правила - це не що інше, як настанови.


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-логіка

1
Саме таке визначення зла в цьому випадку: "чогось слід уникати більшу частину часу, але не те, чого слід уникати весь час". Посилання пояснюється, що зло вказує.
сакіск

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

-2

Ні, вам не дозволяється використовувати макроси для збереження набору тексту .

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

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


"Ні, вам не дозволяється використовувати макроси для збереження набору тексту ." - Гарна робота, я не збираюся тут слухати ваше замовлення! Якщо у мене є купа об’єктів, які мені потрібно оголосити / визначити / на карті / switch/ тощо, ви можете зробити ставку, що я буду використовувати макроси, щоб уникнути божевілля - і збільшити читабельність. Використовувати макроси для збереження введення ключових слів, керування потоком тощо - нерозумно - але сказати, що ніхто не може використовувати їх для збереження натискань клавіш у дійсних контекстах - це рівнозначно.
підкреслити_12
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.