Apakah ini penggunaan yang tepat dari #define untuk mempermudah pengetikan kode yang diulang?

17

Apakah ada pandangan apakah menggunakan #define untuk mendefinisikan baris kode lengkap untuk menyederhanakan pengkodean adalah praktik pemrograman yang baik atau buruk? Misalnya, jika saya perlu mencetak banyak kata bersama, saya akan merasa kesal mengetik

<< " " <<

Untuk menyisipkan spasi antara kata dalam pernyataan cout. Saya hanya bisa melakukannya

#define pSpace << " " <<

dan ketik

cout << word1 pSpace word2 << endl;

Bagi saya ini tidak menambah atau mengurangi dari kejelasan kode dan membuat mengetik sedikit lebih mudah. Ada kasus lain yang bisa saya pikirkan di mana mengetik akan lebih mudah, biasanya untuk debugging.

Ada pemikiran tentang ini?

EDIT: Terima kasih atas semua jawaban hebatnya! Pertanyaan ini baru saja datang kepada saya setelah melakukan banyak pengetikan berulang, tetapi saya tidak pernah berpikir akan ada makro lain yang kurang membingungkan untuk digunakan. Bagi mereka yang tidak ingin membaca semua jawaban, alternatif terbaik adalah menggunakan makro IDE Anda untuk mengurangi pengetikan berulang.

gsingh2011
sumber
74
Ini jelas bagi Anda karena Anda menciptakannya. Bagi semua orang, itu hanya dikaburkan. Pada awalnya itu tampak seperti kesalahan sintaksis. Ketika mengkompilasi saya akan berpikir apa-apaan dan kemudian menemukan bahwa Anda memiliki makro yang tidak ada dalam semua topi. Menurut pendapat saya ini hanya membuat kode mengerikan untuk mempertahankan saya pasti akan menolak ini jika datang untuk meninjau kode dan saya tidak berharap Anda akan menemukan banyak yang akan menerimanya. Dan Anda menyimpan 3 karakter !!!!!!!!!
Martin York
11
Di mana Anda tidak dapat secara masuk akal mengulangi pengulangan menggunakan fungsi atau apa pun, pendekatan yang lebih baik adalah mempelajari apa yang dapat dilakukan editor atau IDE Anda untuk membantu Anda. Tombol-tombol makro editor teks atau "snippet" dapat menyelamatkan Anda dari mengetik begitu banyak tanpa merusak keterbacaan.
Steve314
2
Saya telah melakukan ini sebelumnya (dengan potongan boilerplate yang lebih besar), tetapi praktik saya adalah menulis kode, kemudian menjalankan preprocessor, dan mengganti file asli dengan output preprocessor. Menyelamatkan saya dari pengetikan, menyelamatkan saya (dan lainnya) dari kerumitan perawatan.
TMN
9
Anda menyimpan 3 karakter dan menukarnya dengan pernyataan yang membingungkan. Contoh makro buruk yang sangat bagus, imho: o)
MaR
7
Banyak editor memiliki fitur canggih untuk skenario ini, itu disebut "Copy and Paste"
Chris Burt-Brown

Jawaban:

111

Menulis kode itu mudah. Membaca kode itu sulit.

Anda menulis kode sekali. Itu hidup selama bertahun-tahun, orang membacanya seratus kali.

Optimalkan kode untuk membaca, bukan untuk menulis.

tammmer
sumber
11
Saya setuju 100%. (Sebenarnya, saya sendiri yang akan menulis jawaban ini.) Kode ditulis sekali, tetapi bisa dibaca puluhan, ratusan, atau bahkan ribuan kali, mungkin puluhan, ratusan, atau bahkan oleh ribuan pengembang. Waktu yang diperlukan untuk menulis kode sama sekali tidak relevan, satu-satunya hal yang diperhitungkan adalah waktu untuk membaca dan memahaminya.
sbi
2
Preprosesor dapat dan harus digunakan untuk mengoptimalkan kode untuk membaca dan memelihara.
SK-logic
2
Dan bahkan jika Anda hanya membaca kode dalam satu atau dua tahun: Anda akan melupakan hal-hal ini sendiri saat melakukan hal-hal lain di antaranya.
johannes
2
"Selalu kode seolah-olah orang yang akhirnya mempertahankan kode Anda akan menjadi psikopat kejam yang tahu di mana Anda tinggal." - (Martin Golding)
Dylan Yaga
@Dylan - jika tidak, setelah beberapa bulan mempertahankan kode itu, dia akan menemukan Anda - (Saya)
Steve314
28

Secara pribadi, saya benci itu. Ada sejumlah alasan mengapa saya tidak menyarankan orang dari teknik ini:

  1. Pada waktu kompilasi, perubahan kode Anda yang sebenarnya mungkin signifikan. Orang berikutnya datang dan bahkan menyertakan braket penutup di #define atau panggilan fungsi. Apa yang ditulis pada titik kode tertentu jauh dari apa yang akan ada di sana setelah pra-pemrosesan.

  2. Itu tidak bisa dibaca. Mungkin jelas bagi Anda .. untuk saat ini .. jika hanya definisi yang satu ini. Jika itu menjadi kebiasaan, Anda akan segera berakhir dengan puluhan #define dan mulai kehilangan jejak. Tapi yang terburuk, tidak ada orang lain yang bisa mengerti apa word1 pSpace word2sebenarnya arti (tanpa melihat #define).

  3. Mungkin menjadi masalah bagi alat eksternal. Katakanlah Anda entah bagaimana berakhir dengan #define yang mencakup braket penutup, tetapi tidak ada braket pembuka. Semuanya mungkin berfungsi dengan baik, tetapi editor dan alat lain mungkin melihat sesuatu yang function(withSomeCoolDefine;agak aneh (yaitu, mereka akan melaporkan kesalahan dan yang lainnya). (Contoh serupa: panggilan fungsi di dalam define - apakah alat analisis Anda dapat menemukan panggilan ini?)

  4. Perawatan menjadi lebih sulit. Anda memiliki semua yang didefinisikan sebagai tambahan untuk masalah yang biasa dibawa oleh pemeliharaan itu. Selain dengan poin di atas, dukungan alat untuk refactoring juga dapat terpengaruh secara negatif.

jujur
sumber
4
Saya akhirnya melarang diri saya dari hampir semua makro karena kerepotan mencoba menanganinya di Doxygen. Sudah beberapa tahun sekarang, dan secara keseluruhan saya pikir keterbacaan meningkat sedikit - terlepas dari apakah saya menggunakan Doxygen atau tidak.
Steve314
16

Pikiran utama saya tentang ini, adalah bahwa saya tidak pernah menggunakan "mempermudah pengetikan" sebagai aturan saat menulis kode.

Aturan utama saya saat menulis kode adalah membuatnya mudah dibaca. Alasan di balik ini hanyalah bahwa kode dibaca urutan besarnya lebih banyak dari yang tertulis. Dengan demikian, waktu Anda kehilangan menulisnya dengan hati-hati, tertib, ditata dengan benar sebenarnya diinvestasikan untuk membuat bacaan lebih lanjut, dan memahami lebih cepat.

Dengan demikian, #define yang Anda gunakan cukup merusak cara berganti-ganti <<dan yang lainnya . Ini melanggar aturan yang paling tidak mengejutkan, dan bukan IMHO hal yang baik.

Didier Trosset
sumber
1
+1: "Kode dibaca urutan besarnya lebih banyak dari yang tertulis" !!!!
Giorgio
14

Pertanyaan ini memberikan contoh yang jelas tentang bagaimana Anda dapat menggunakan makro dengan buruk. Untuk melihat contoh lain (dan dihibur) lihat pertanyaan ini .

Karena itu, saya akan memberikan contoh dunia nyata dari apa yang saya anggap sebagai penggabungan makro yang baik.

Contoh pertama muncul di CppUnit , yang merupakan kerangka pengujian unit. Seperti halnya kerangka kerja pengujian standar lainnya, Anda membuat kelas pengujian dan kemudian Anda harus menentukan metode apa saja yang harus dijalankan sebagai bagian dari pengujian.

#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();
}

Seperti yang Anda lihat, kelas memiliki blok makro sebagai elemen pertama. Jika saya menambahkan metode baru testSubtraction, sudah jelas apa yang perlu Anda lakukan untuk memasukkannya dalam uji coba.

Blok makro ini berkembang menjadi seperti ini:

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

Mana yang Anda sukai untuk dibaca dan dipelihara?

Contoh lain adalah dalam kerangka kerja Microsoft MFC, di mana Anda memetakan fungsi untuk pesan:

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

Jadi, apa saja hal yang membedakan "Makro Baik" dari jenis jahat yang mengerikan?

  • Mereka melakukan tugas yang tidak dapat disederhanakan dengan cara lain. Menulis makro untuk menentukan maksimum antara dua elemen adalah salah, karena Anda dapat mencapai hal yang sama menggunakan metode templat. Tetapi ada beberapa tugas kompleks (misalnya, memetakan kode pesan ke fungsi anggota) yang bahasa C ++ tidak ditangani dengan elegan.

  • Mereka memiliki penggunaan formal yang sangat ketat. Dalam kedua contoh ini blok makro diumumkan dengan memulai dan mengakhiri makro, dan makro antar hanya akan pernah muncul di dalam blok ini. Anda memiliki C ++ normal, Anda permisi sebentar dengan makro, dan kemudian Anda kembali normal lagi. Dalam contoh "makro jahat", makro tersebar di seluruh kode dan pembaca yang malang tidak memiliki cara untuk mengetahui kapan aturan C ++ berlaku dan kapan mereka tidak.

Andrew Shepherd
sumber
5

Dengan segala cara, akan lebih baik jika Anda menyetel editor teks / IDE favorit Anda untuk menyisipkan cuplikan kode yang Anda rasa membosankan untuk mengetik ulang berulang kali. Dan lebih baik istilah "sopan" untuk perbandingan. Sebenarnya, saya tidak bisa memikirkan kasus serupa ketika preprocessing beats macro editor. Yah, mungkin satu - ketika dengan beberapa alasan misterius dan tidak bahagia Anda terus-menerus menggunakan berbagai alat untuk pengkodean. Tapi itu bukan pembenaran :)

Ini juga bisa menjadi solusi yang lebih baik untuk skenario yang lebih kompleks, ketika preprocessing teks dapat melakukan hal yang jauh lebih tidak dapat dibaca dan rumit (pikirkan tentang input parametrized).

shabunc
sumber
2
+1. Memang: biarkan editor melakukan pekerjaan untuk Anda. Anda akan mendapatkan yang terbaik dari kedua dunia jika misalnya Anda membuat singkatan dari << " " <<.
unperson325680
-1 untuk "Ini juga bisa menjadi solusi yang lebih baik untuk skenario yang lebih kompleks, ketika preprocessing teks dapat melakukan hal yang jauh lebih tidak dapat dibaca dan rumit (pikirkan tentang input parametrized)" - Jika itu kompleks, buat metode untuk itu, bahkan kemudian, buat metode untuk itu. mis. Kejahatan ini baru-baru ini saya temukan dalam kode ..... #define printError (x) {puts (x); return x}
mattnz
@ mattnz, saya maksudkan konstruksi lingkaran, jika / selain itu konstruksi, template untuk pembuatan komparator dan sebagainya - hal semacam itu. Dalam IDE semacam input parametrized membantu Anda tidak hanya mengetik beberapa baris kode dengan cepat, tetapi juga untuk beralih dengan cepat melalui params. Tidak ada yang mencoba bersaing dengan metode. metode adalah metode)))
shabunc
4

Yang lain sudah menjelaskan mengapa Anda tidak harus melakukannya. Contoh Anda jelas tidak layak diimplementasikan dengan makro. Tapi, ada banyak kasus di mana Anda harus menggunakan makro demi keterbacaan.

Contoh terkenal dari aplikasi bijak dari teknik semacam itu adalah proyek Dentang : lihat bagaimana .deffile digunakan di sana. Dengan makro dan #includeAnda dapat memberikan definisi tunggal, kadang-kadang seluruhnya deklaratif untuk koleksi hal serupa yang akan dibuka menjadi tipe deklarasi, casepernyataan di mana pun sesuai, penginisialisasi default, dll. Ini meningkatkan pemeliharaan secara signifikan: Anda tidak akan pernah lupa menambahkan baru casepernyataan di mana-mana saat Anda menambahkan yang baru enum, misalnya.

Jadi, seperti halnya alat canggih lainnya, Anda harus menggunakan preprosesor C dengan hati-hati. Tidak ada aturan umum dalam seni pemrograman, seperti "Anda tidak boleh menggunakan ini" atau "Anda harus selalu menggunakannya". Semua aturan hanyalah pedoman.

Logika SK
sumber
3

Tidak pernah tepat untuk menggunakan #define seperti itu. Dalam kasus Anda, Anda bisa melakukan ini:

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;
}
Mendesis
sumber
2

Tidak.

Untuk makro yang dimaksudkan untuk digunakan dalam kode, pedoman yang baik untuk menguji kelayakan adalah mengelilingi ekspansi dengan tanda kurung (untuk ekspresi) atau kurung kurawal (untuk kode) dan lihat apakah masih akan dikompilasi:

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

Makro yang digunakan dalam deklarasi (seperti contoh dalam jawaban Andrew Shepherd) dapat lolos dengan seperangkat aturan yang lebih longgar selama mereka tidak mengganggu konteks sekitarnya (misalnya beralih antara publicdan private).

Blrfl
sumber
1

Ini hal yang wajar untuk dilakukan dalam program "C" murni.

Tidak perlu dan membingungkan dalam program C ++.

Ada banyak cara untuk menghindari pengetikan kode berulang-ulang di C ++. Dari menggunakan fasilitas yang disediakan oleh IDE Anda (meskipun dengan " %s/ pspace /<< " " <</g" sederhana, akan menghemat sebanyak mengetik dan masih menghasilkan kode yang dapat dibaca standar). Anda bisa mendefinisikan metode pribadi untuk mengimplementasikan ini atau untuk kasus yang lebih kompleks, template C ++ akan menjadi lebih bersih dan sederhana.

James Anderson
sumber
2
Tidak, itu bukan hal yang wajar untuk dilakukan dalam nilai C. murni atau ekspresi independen lengkap yang hanya bergantung pada parameter makro, ya, dan untuk yang terakhir fungsi bahkan mungkin menjadi pilihan yang lebih baik. Seperti konstruksi setengah matang seperti dalam contoh, tidak mungkin.
Aman
@Secure - Saya setuju bahwa ini bukan ide yang baik dalam kasus contoh yang diberikan. Namun mengingat kekurangan template dll. Ada kegunaan yang valid untuk makro "#DEFINE" di C.
James Anderson
1

Dalam C ++ ini dapat diatasi dengan overloading operator. Atau bahkan sesuatu yang sederhana seperti fungsi variadik:

lineWithSpaces(word1, word2, word3, ..., wordn)keduanya sederhana dan menghemat waktu Anda mengetik pSpaceslagi.

Jadi, sementara dalam kasus Anda mungkin tidak tampak seperti masalah besar, ada solusi yang lebih sederhana dan lebih kuat.

Secara umum, ada beberapa kasus di mana menggunakan makro secara signifikan lebih pendek tanpa menimbulkan kebingungan, dan sebagian besar ada solusi yang cukup singkat menggunakan fitur bahasa aktual (makro lebih dari sekadar penggantian string).

back2dos
sumber
0

Apakah ada pandangan apakah menggunakan #define untuk mendefinisikan baris kode lengkap untuk menyederhanakan pengkodean adalah praktik pemrograman yang baik atau buruk?

Ya, itu sangat buruk. Saya bahkan melihat orang melakukan ini:

#define R return

untuk menyimpan pengetikan (apa yang ingin Anda capai).

Kode semacam itu hanya dimiliki di tempat-tempat seperti ini .

BЈовић
sumber
-1

Makro itu jahat dan harus digunakan hanya ketika Anda benar-benar harus. Ada beberapa kasus di mana makro berlaku (terutama debugging). Tetapi dalam C ++ dalam banyak kasus Anda dapat menggunakan fungsi inline sebagai gantinya.

sakisk
sumber
2
Tidak ada yang secara intrinsik jahat dalam teknik pemrograman apa pun. Semua alat dan metode yang mungkin dapat digunakan, selama Anda tahu apa yang Anda lakukan. Ini berlaku untuk yang terkenal goto, untuk semua sistem makro yang mungkin, dll.
SK-logic
1
Itulah definisi kejahatan dalam kasus ini: "sesuatu yang harus Anda hindari sebagian besar waktu, tetapi bukan sesuatu yang harus Anda hindari sepanjang waktu". Dijelaskan di tautan yang ditunjukkan oleh kejahatan .
sakisk
2
Saya percaya bahwa itu kontraproduktif dengan merek apa pun sebagai "jahat", "berpotensi berbahaya" atau bahkan "mencurigakan". Saya tidak suka gagasan "praktik buruk" dan "bau kode". Setiap dan setiap pengembang harus memutuskan dalam setiap kasus tertentu, praktik mana yang berbahaya. Pelabelan adalah praktik yang berbahaya - orang cenderung tidak berpikir lebih jauh jika sesuatu sudah diberi label oleh yang lain.
SK-logic
-2

Tidak, Anda tidak diizinkan menggunakan makro untuk menyimpan pengetikan .

Namun Anda diizinkan, bahkan diharuskan menggunakannya untuk memisahkan bagian kode yang tidak berubah dari yang diubah, dan mengurangi redundansi. Untuk yang terakhir Anda harus memikirkan alternatif dan memilih makro hanya jika alat yang lebih baik tidak berfungsi. (Untuk praktik makro cukup di akhir baris, jadi memiliki makro berarti pilihan terakhir ...)

Untuk mengurangi mengetik, sebagian besar editor memiliki makro, bahkan cuplikan kode yang cerdas.

Balog Pal
sumber
"Tidak, kamu tidak diizinkan menggunakan makro untuk menyimpan pengetikan ." - Kerja bagus, saya tidak akan mendengarkan pesanan Anda di sini! Jika saya memiliki setumpuk objek yang harus saya deklarasikan / define / map / switch/ etc, Anda bisa bertaruh saya akan menggunakan makro untuk menghindari menjadi gila - dan meningkatkan keterbacaan. Menggunakan makro untuk menyimpan pengetikan pada kata kunci, kontrol aliran, dan sejenisnya itu bodoh - tetapi untuk mengatakan tidak ada yang bisa menggunakannya untuk menyimpan penekanan tombol dalam konteks yang valid juga sama.
underscore_d