繰り返しコードを入力しやすくするための#defineの適切な使用ですか?


17

コーディングを簡素化するために#defineを使用して完全なコード行を定義することは、プログラミングの実践として良いか悪いかについて意見はありますか?たとえば、大量の単語を一緒に印刷する必要がある場合、イライラするタイピングを取得します

<< " " <<

coutステートメントの単語間にスペースを挿入します。私はただできる

#define pSpace << " " <<

そしてタイプ

cout << word1 pSpace word2 << endl;

私にとっては、これはコードの明快さを加減せず、入力を少し簡単にすることもありません。通常デバッグのために、タイピングがはるかに簡単になると思う他のケースがあります。

これについて何か考えはありますか?

編集:すべての素晴らしい答えをありがとう!この質問は、繰り返しタイピングを何度も行った後に私に届いたものですが、使用する他の混乱の少ないマクロがあるとは思いもしませんでした。すべての答えを読みたくない人にとって、最良の代替案は、IDEのマクロを使用して、繰り返し入力を減らすことです。


74
あなたがそれを発明したので、あなたには明らかです。他のすべての人には難読化されただけです。最初は構文エラーのように見えます。それがコンパイルされるとき、私は一体何を考えてから、あなたがすべて大文字ではないマクロを持っていることを見つけます。私の意見では、これは単にコードを維持するのを恐ろしいものにします。コードレビューのために来た場合、私は間違いなくこれを拒否し、あなたがそれを受け入れる多くのものを見つけるとは思わないでしょう。そして、あなたは3文字を保存しています!!!!!!!!!
マーティンヨーク

11
関数などを使用して反復性を合理的に除外できない場合、より良いアプローチは、エディターまたはIDEができることを学ぶことです。テキストエディタマクロまたは「スニペット」ホットキーを使用すると、読みやすさ損なうことなく、入力を大幅に節約できます。
Steve314

2
以前にこれを(ボイラープレートの大きなチャンクで)実行しましたが、私の練習はコードを記述し、プリプロセッサを実行し、元のファイルをプリプロセッサの出力で置き換えることでした。タイピングを節約し、メンテナンスの手間を省きました。
TMN

9
あなたは3つのキャラクターを保存し、それらを混乱させるステートメントと交換しました。悪いマクロの非常に良い例、imho:o)
11

7
多くのエディターはまさにこのシナリオのための高度な機能を備えており、「コピーアンドペースト」と呼ばれています
クリスバートブラウン

回答:


111

コードの記述は簡単です。コードを読むのは難しいです。

一度コードを書きます。それは何年も生き、人々はそれを何百回も読みます。

書き込み用ではなく、読み取り用にコードを最適化します。


11
100%に同意します。(実際、私はこの答えを自分で書くつもりでした。)コードは一度書かれますが、数十、数百、または数千回、おそらく数十、数百、または数千の開発者によって読み取られる可能性があります。コードを書くのにかかる時間はまったく関係ありません。重要なことはそれを読んで理解する時間だけです。
-sbi

2
プリプロセッサは、読み取りおよび保守用のコードを最適化するために使用できます。
SKロジック

2
そして、たとえあなたが1年か2年でコードを読んでいるだけであっても、あなたはこれらのことを忘れてしまい、その間に他のことをします。
ヨハネス

2
「あなたのコードを保守することになった人が、あなたがどこに住んでいるかを知っている暴力的なサイコパスであるかのように常にコードします。」- (マーティン・ゴールディング)
ディランヤガー

@Dylan -それ以外の場合は、そのコードを維持し、数ヶ月後、彼はなりますあなたを見つける- (ミー)
Steve314

28

個人的には嫌いです。私がこのテクニックをやめさせる理由はいくつかあります。

  1. コンパイル時に、実際のコードの変更が重要になる場合があります。次の男が登場し、#defineまたは関数呼び出しに閉じ括弧を含めます。コードの特定のポイントで書かれたものは、前処理後にそこにあるものとはほど遠いです。

  2. 読めません。それはあなたにとって明らかかもしれません..今のところ..それがこの一つの定義であるならば。それが習慣になると、すぐに数十の#definesになり、自分自身を失い始めます。しかし最悪word1 pSpace word2なのは、(#defineを検索せずに)正確に何を意味するのかを他の誰も理解できないことです。

  3. 外部ツールにとって問題になる可能性があります。どういうわけか、終了ブラケットを含む#defineになりますが、開始ブラケットはありません。すべてがうまくいくかもしれませんが、編集者や他のツールはfunction(withSomeCoolDefine;かなり奇妙なもののように見えるかもしれません(つまり、エラーなどを報告します)。(同様の例:定義内の関数呼び出し-分析ツールはこの呼び出しを見つけることができますか?)

  4. メンテナンスがずっと難しくなります。メンテナンスがもたらす通常の問題に加えて、これらすべてを定義します。上記の点に加えて、リファクタリングのツールサポートも同様に悪影響を受ける可能性があります。


4
Doxygenでマクロを処理しようとする面倒なため、私は最終的にほとんどすべてのマクロから自分自身を禁止しました。Doxygenを使用しているかどうかに関係なく、全体的に読みやすさがかなり向上したと思います。
Steve314

16

これについての私の主な考えは、コードを書くとき、規則として「タイピングを容易にする」ことは決してしないということです。

コードを書くときの私の主なルールは、コードを読みやすくすることです。この背後にある理論的根拠は、コードが書き込まれるよりも桁違いに多く読み取られるということです。このように、注意深く、整然と、正しくレイアウトされた文章を書くのを失う時間は、実際には、さらに読み、より速く理解することに費やされます。

そのため、使用する#defineは単にalternated <<other-stuffの通常の方法を壊します。それは最も驚きのルールを破り、私見は良いことではありません。


1
+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( )

それでは、「良いマクロ」と恐ろしい悪の種類を区別するものは何ですか?

  • 他の方法では単純化できないタスクを実行します。テンプレートメソッドを使用して同じことを実現できるため、2つの要素間の最大値を決定するマクロを記述するのは間違っています。しかし、いくつかの複雑なタスク(たとえば、メッセージコードのメンバー関数へのマッピング)があり、C ++言語ではうまく処理されません。

  • それらは非常に厳密で正式な使用法を持っています。これらの例の両方で、マクロブロックはマクロの開始と終了によってアナウンスされ、中間のマクロはこれらのブロック内にのみ表示されます。通常のC ++があり、簡単にマクロのブロックで言い訳をした後、再び通常に戻ります。「邪悪なマクロ」の例では、マクロはコード全体に散らばっており、不運な読者には、C ++ルールがいつ適用されるか、いつ適用されないかを知る方法がありません。


5

お気に入りのIDE /テキストエディタを調整して、何度も何度も再入力するのが面倒なコードスニペットを挿入する場合は、ぜひお勧めします。そして、比較のための「丁寧な」用語がより良いです。実際、前処理がエディターのマクロに勝るとき、私は同様のケースについて考えることができません。さて、1つかもしれません-いくつかの神秘的で不幸な理由により、コーディングのために異なるツールセットを常に使用している場合。しかし、それは正当化ではありません:)

また、テキストの前処理がより読みにくく複雑なことを行うことができる場合(パラメーター化された入力について考えてください)、より複雑なシナリオのためのより良いソリューションになります。


2
+1。確かに、編集者に任せてください。たとえば、の省略形を作成すると、両方の長所が得られます<< " " <<
unperson325680

-1の「テキストの前処理がより読みにくく複雑なことを行うことができる場合、より複雑なシナリオのより良いソリューションになる可能性があります(パラメータ化された入力について考えてください)」-複雑な場合それのための方法。たとえば、最近コードで見つけたこの悪..... #define printError(x){puts(x); return x}
mattnz

@mattnz、私はループ構造、if / else構造、コンパレータ作成用のテンプレートなどを意味してきました。IDEでは、このような種類のパラメータ化された入力は、数行のコードをすばやく入力するだけでなく、paramsをすばやく繰り返すことにも役立ちます。誰もメソッドと競合しようとしません。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が提供する機能を使用することにより(viを使用する場合でも、単純な " %s/ pspace /<< " " <</g"を入力するだけで、標準の読み取り可能なコードが生成されます)。これを実装するプライベートメソッドを定義することもできますし、より複雑なケースではC ++テンプレートがよりクリーンでシンプルになります。


2
いいえ、純粋なCで行うのは合理的に有効なことではありません。単一の値またはマクロパラメーターのみに依存する完全な独立式、はい。この例のような中途半端な構造はありません。
セキュア

@secure-与えられた例の場合、それは良い考えではないことに同意します。しかし、与えられた不足テンプレートなどC.で「#DEFINE」マクロの有効な用途がある
ジェームズ・アンダーソンが

1

C ++では、これは演算子のオーバーロードで解決できます。または、変数関数のような単純なものでも:

lineWithSpaces(word1, word2, word3, ..., wordn)シンプルであり、何pSpaces度も何度も入力する手間が省けます。

あなたの場合、それは大したことではないように思えるかもしれませんが、よりシンプルで堅牢なソリューションがあります。

一般に、難読化を導入せずにマクロの使用が大幅に短くなるケースはほとんどなく、実際の言語機能を使用した十分に短いソリューションがあります(マクロは単なる文字列置換です)。


0

コーディングを簡素化するために#defineを使用して完全なコード行を定義することは、プログラミングの実践として良いか悪いかについて意見はありますか?

はい、それは非常に悪いです。私はこれをしている人々を見ました:

#define R return

タイピング(達成しようとしていること)を節約します。

このようなコードは、このような場所にのみ属します。


-1

マクロは悪であり、本当に必要な場合にのみ使用する必要があります。マクロが適用される場合がいくつかあります(主にデバッグ)。しかし、ほとんどの場合、C ++では、代わりにインライン関数を使用できます。


2
プログラミング技術には本質的に悪はありません。自分が何をしているのかを知っている限り、すべての可能なツールと方法を使用できます。悪名高いgoto、すべての可能なマクロシステムなどに適用されます。
SKロジック

1
これはまさにこの場合の悪の定義です。「ほとんどの時間を避けるべきですが、常に避けるべきではないもの」。リンク上でが指していると説明されています。
サキスク

2
私は、「悪」、「潜在的に有害」、さらには「疑わしい」とブランド設定するのは逆効果だと考えています。私は「悪い習慣」や「コードの匂い」という概念が好きではありません。すべての開発者は、特定のケースごとに決定する必要がありますが、どのプラクティスが有害ですか。ラベル付けは有害な慣行です -他の人によって既にラベルが付けられている場合、人々はそれ以上考えることはありません。
SKロジック

-2

いいえ、マクロを使用して入力を保存することはできません。

ただし、コードの変更されない部分を変更された部分から分離し、冗長性を減らすためにそれらを使用することさえ許可されています。後者の場合、より優れたツールが機能しない場合にのみ、選択肢を考えてマクロを選択する必要があります。(練習用マクロは行の最後にあるため、最後の手段を意味します...)

入力を減らすために、ほとんどのエディターにはマクロがあり、インテリジェントなコードスニペットもあります。


「いいえ、マクロを使用して入力を保存することはできません。」-よくできました。ここでは注文を聞きません。宣言/定義/マップ/ switch/ etc を宣言する必要があるオブジェクトの山がある場合、マクロを使用して非常識になることを避け、読みやすさを向上させることができます。マクロを使用してキーワードの入力やフローの制御などを保存するのは愚かなことですが、有効なコンテキストでキーストロークを保存するためにマクロを使用することはできません。
underscore_d
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.