반복되는 코드를 쉽게 입력 할 수 있도록 #define을 적절하게 사용합니까?


17

코딩 단순화를 위해 전체 코드 줄을 정의하기 위해 #define을 사용하는 것이 좋은지 나쁜 프로그래밍 관행인지에 대한 견해가 있습니까? 예를 들어, 여러 단어를 함께 인쇄해야한다면 입력에 짜증이납니다

<< " " <<

cout 문에서 단어 사이에 공백을 삽입합니다. 그냥 할 수있어

#define pSpace << " " <<

그리고 유형

cout << word1 pSpace word2 << endl;

나에게 이것은 코드의 명확성을 더하거나 빼지 않으며 입력을 약간 더 쉽게 만듭니다. 일반적으로 디버깅을 위해 타이핑이 훨씬 쉬운 위치를 생각할 수있는 다른 경우가 있습니다.

이것에 대한 생각?

편집 : 모든 위대한 답변을 주셔서 감사합니다! 이 질문은 반복적 인 타이핑을 많이 한 후에 나에게 왔지만 사용하기에 혼란스럽지 않은 다른 매크로가 있다고 생각하지 않았습니다. 모든 답변을 읽고 싶지 않은 사람들에게는 IDE의 매크로를 사용하여 반복적 인 타이핑을 줄이는 것이 가장 좋습니다.


74
당신이 그것을 발명했기 때문에 당신에게 분명합니다. 다른 사람들에게 그것은 단지 난독 화되었습니다. 처음에는 구문 오류처럼 보입니다. 그것이 컴파일 될 때 도대체 무슨 생각을하고 모든 대문자가 아닌 매크로가 있음을 알 수 있습니다. 내 의견으로는 이것은 코드를 유지하기가 끔찍한 일이라고 생각합니다. 코드 검토를 위해 온다면 이것을 거부하고 그것을 받아 들일 많은 사람들을 찾을 것이라고는 기대하지 않습니다. 그리고 당신은 3 개의 문자를 절약하고 있습니다 !!!!!!!!!
Martin York

11
함수 등을 사용하여 반복성을 합리적으로 고려할 수없는 경우 더 나은 방법은 편집기 나 IDE가 도움을 줄 수있는 방법을 배우는 것입니다. 텍스트 편집기 매크로 또는 "스 니펫"핫키를 사용하면 가독성 손상시키지 않으면 서 입력 하지 않아도 됩니다 .
Steve314

2
나는 이것을 (보일러 덩어리의 큰 덩어리로) 전에 했었지만, 연습은 코드를 작성한 다음 전처리기를 실행하고 원본 파일을 전 처리기 출력으로 교체하는 것이 었습니다. 타이핑을 구하고 유지 보수 번거 로움을 덜어주었습니다.
TMN

9
당신은 3 개의 문자를 저장하고 혼란스러운 말로 교환했습니다. 나쁜 매크로의 아주 좋은 예, imho : o)
MaR

7
대부분의 편집자는 정확히이 시나리오의 고급 기능은, 그것은 "복사 및 붙여 넣기"이라고이
크리스 버트 - 브라운

답변:


111

코드 작성이 쉽습니다. 코드 읽기가 어렵습니다.

코드를 한 번 작성하십시오. 그것은 몇 년 동안 살고 사람들은 백 번 읽습니다.

쓰기 용이 아닌 읽기 용 코드를 최적화하십시오.


11
100 % 동의합니다. (실제로이 답변을 직접 작성하려고했습니다.) 코드는 한 번 작성되었지만 수십, 수백 또는 수천 번, 수십, 수백 또는 수천 명의 개발자가 읽을 수 있습니다. 코드를 작성하는 데 걸리는 시간은 전적으로 관련이 없으며 계산하는 유일한 시간은 코드를 읽고 이해하는 시간입니다.
sbi

2
전처리기를 사용하여 읽고 유지하기위한 코드를 최적화 할 수 있습니다.
SK-logic

2
1 ~ 2 년 안에 코드를 읽는 것만으로도 중간에 다른 일을하면서 이러한 일을 스스로 잊어 버리게됩니다.
johannes

2
"항상 코드를 관리하는 사람이 당신이 사는 곳을 아는 폭력적인 정신병자 인 것처럼 항상 코드를 작성하십시오." -(Martin Golding)
Dylan Yaga

@Dylan - 몇 달이 그 코드를 유지 한 후, 그렇지 않으면, 그 것이다 당신을 찾을 - (나)
Steve314

28

개인적으로, 나는 그것을 혐오합니다. 사람들에게이 기술을 권장하지 않는 데는 여러 가지 이유가 있습니다.

  1. 컴파일 타임에 실제 코드 변경이 중요 할 수 있습니다. 다음 사람이 와서 #define 또는 함수 호출에 닫는 대괄호를 포함합니다. 특정 코드 지점에서 작성된 내용은 사전 처리 후있을 내용과는 거리가 멀습니다.

  2. 읽을 수 없습니다. 이게 하나의 정의라면 지금은 분명 할 것입니다. 습관이되면 곧 수십 개의 #define으로 끝나고 자신을 추적하기 시작합니다. 그러나 무엇보다도 최악의 word1 pSpace word2경우, #define을 조회하지 않고는 정확히 무엇을 의미 하는지 이해할 수있는 사람이 없습니다 .

  3. 외부 도구에 문제가 될 수 있습니다. 어떻게 든 닫는 괄호는 포함하지만 여는 괄호는 포함하지 않는 #define으로 끝납니다. 모든 것이 잘 작동 할 수 있지만 편집자와 다른 도구는 function(withSomeCoolDefine;다소 특이한 것으로 보일 수 있습니다 (즉, 오류를보고하고 그렇지 않은 경우). (유사한 예 : 정의 내부의 함수 호출-분석 도구가이 호출을 찾을 수 있습니까?)

  4. 유지 보수가 훨씬 어려워집니다. 유지 보수로 인해 발생하는 일반적인 문제점 외에 모든 정의가 있습니다. 위의 사항 외에도 리팩토링을위한 툴 지원에도 부정적인 영향을 줄 수 있습니다.


4
나는 마침내 Doxygen에서 바로 처리하려고하는 번거 로움으로 거의 모든 매크로에서 자신을 금지했습니다. 지금은 몇 년이 지났고, Doxygen을 사용하는지 여부에 관계없이 전반적으로 가독성이 상당히 향상되었다고 생각합니다.
Steve314

16

이것에 대한 나의 주된 생각은, 코드를 작성할 때 "타이핑을 더 쉽게"하는 규칙을 결코 사용하지 않는다는 것입니다.

코드를 작성할 때의 주요 규칙은 쉽게 읽을 수 있도록하는 것입니다. 이것의 근거는 단순히 코드가 작성된 것보다 몇 배나 더 많이 읽힌다는 것입니다. 따라서, 당신 이주의 깊고, 질서 정연하게 정확하게 기록하는 것을 잃어버린 시간 은 실제로 더 많은 독서를하고 훨씬 더 빨리 이해하는 데 투자됩니다.

따라서 사용하는 #define은 일반적인 대체 <<기타 방법을 중단합니다 . 그것은 최소한 놀람의 규칙을 어 기고 IMHO가 좋은 것이 아닙니다.


1
+1 : "코드를 작성하는 횟수만큼 코드를 읽는다"!!!!
Giorgio

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) {puts (x); return x}
mattnz

@mattnz, 루프 구조, if / else 구조, 비교기 작성 용 템플릿 등을 의미했습니다. IDE에서 이러한 종류의 매개 변수화 된 입력을 사용하면 몇 줄의 코드를 빠르게 입력 할 수있을뿐만 아니라 매개 변수를 빠르게 반복 할 수 있습니다. 아무도 방법과 경쟁하려고하지 않습니다. method is method)))
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;

Andrew Shepherd의 답변과 같은 선언에 사용 된 매크로는 주변 컨텍스트를 화나게하지 않는 한 느슨한 규칙 세트를 사용하여 벗어날 수 있습니다 (예 : public및 사이 전환 private).


1

순수한 "C"프로그램에서하는 것이 합리적으로 유효한 일입니다.

C ++ 프로그램에서는 불필요하고 혼동됩니다.

C ++에서 반복적 인 코드 입력을 피하는 방법에는 여러 가지가 있습니다. IDE에서 제공하는 기능을 사용하는 것만으로도 단순한 " %s/ pspace /<< " " <</g" 로도 많은 타이핑을 절약하고 표준으로 읽을 수있는 코드를 생성 할 수 있습니다. 이것을 구현하기 위해 개인 메소드를 정의하거나 더 복잡한 경우 C ++ 템플릿이 더 깨끗하고 간단합니다.


2
순수한 C에서하는 것은 합리적으로 유효한 일이 아닙니다. 매크로 매개 변수에만 의존하는 단일 값 또는 완전한 독립 식, 예. 후자의 경우 함수가 더 나은 선택 일 수 있습니다. 예제에서와 같이 반 구운 구조는 결코 아닙니다.
보안

@ secure-주어진 예의 경우 좋은 생각이 아니라는 데 동의합니다. 그러나 템플릿이 부족한 경우 C에서 "#DEFINE"매크로를 올바르게 사용합니다.
James Anderson

1

C ++에서 이것은 연산자 오버로딩으로 해결할 수 있습니다. 또는 다양한 함수처럼 간단한 것조차도 :

lineWithSpaces(word1, word2, word3, ..., wordn)간단하고 pSpaces다시 입력을 저장 합니다.

따라서 귀하의 경우에는 큰 일이 아닌 것처럼 보일 수 있지만 더 간단하고 강력한 솔루션이 있습니다.

일반적으로 난독 화를 도입하지 않고 매크로를 사용하는 것이 상당히 짧은 경우는 거의 없으며 실제 언어 기능 (매크로는 단순한 문자열 대체 일 뿐임)을 사용하여 충분히 짧은 솔루션이 있습니다.


0

코딩 단순화를 위해 전체 코드 줄을 정의하기 위해 #define을 사용하는 것이 좋은지 나쁜 프로그래밍 관행인지에 대한 견해가 있습니까?

예, 매우 나쁩니다. 나는 사람들이 이것을하는 것을 보았습니다 :

#define R return

입력을 저장하기 위해 (당신이 달성하려는 것).

이러한 코드는 같은 장소에 속하는 .


-1

매크로는 사악 하므로 실제로 필요할 때만 사용해야합니다. 매크로가 적용 가능한 경우가 몇 가지 있습니다 (주로 디버깅). 그러나 대부분의 경우 C ++에서는 인라인 함수를 대신 사용할 수 있습니다 .


2
프로그래밍 기술 에는 본질적으로 악한 것이 없습니다 . 하고있는 일을 아는 한 가능한 모든 도구와 방법을 사용할 수 있습니다. 악명 높은 goto모든 가능한 매크로 시스템 등에 적용됩니다 .
SK-logic

1
이것이 바로이 경우 악의 정의입니다. "대부분은 피해야하지만 항상 피해야하는 것은 아닙니다." 그것은 이 가리키는 링크에 설명되어 있습니다.
sakisk

2
"사악한", "잠재적으로 유해한"또는 "의심스러운"브랜드로 만드는 것은 역효과를 낳습니다. 나는 "나쁜 연습"과 "코드 냄새"라는 개념이 마음에 들지 않습니다. 각각의 모든 개발자는 모든 특정 경우에 해를 입히는 것을 결정해야합니다. 라벨링은 유해한 관행입니다 . 사람들은 다른 라벨이 이미 붙어 있으면 더 이상 생각하지 않는 경향이 있습니다.
SK-logic

-2

아니요, 입력 을 저장하기 위해 매크로를 사용할 수 없습니다 .

그러나 코드의 변경되지 않는 부분을 변경하는 부분과 분리하고 중복성을 줄이기 위해 그것들을 사용해야 할 수도 있습니다. 후자의 경우 대안을 생각하고 더 나은 도구가 작동하지 않는 경우에만 매크로를 선택해야합니다. (실제로 매크로는 줄 끝 부분에 있으므로 마지막 수단을 의미합니다 ...)

타이핑을 줄이려면 대부분의 편집기에는 매크로, 지능형 코드 스 니펫이 있습니다.


"아니요, 매크로를 사용 하여 입력 내용 을 저장할 수 없습니다 ." -잘 했어 여기서 주문을 듣지 않겠다! 선언 / 정의 / map switch// etc에 필요한 객체가 있다면 매크로를 사용하여 미쳐가는 것을 피하고 가독성을 높일 수 있습니다. 매크로를 사용하여 키워드 입력, 제어 흐름 등을 저장하는 것은 어리석은 일입니다. 그러나 유효한 컨텍스트에서 키 입력을 저장하는 데 아무도 사용할 수 없다고 말하는 것은 똑같은 일입니다.
underscore_d
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.