Đây có phải là cách sử dụng thích hợp của #define để giúp việc nhập mã lặp lại dễ dàng hơn không?


17

Có bất kỳ quan điểm nào về việc sử dụng #define để xác định các dòng mã đầy đủ để đơn giản hóa mã hóa là thực hành lập trình tốt hay xấu? Ví dụ: nếu tôi cần in một loạt các từ với nhau, tôi sẽ cảm thấy khó chịu khi gõ

<< " " <<

Để chèn khoảng trắng giữa các từ trong câu lệnh cout. Tôi chỉ có thể làm

#define pSpace << " " <<

và gõ

cout << word1 pSpace word2 << endl;

Đối với tôi điều này không thêm hoặc bớt đi sự rõ ràng của mã và làm cho việc gõ dễ dàng hơn một chút. Có những trường hợp khác mà tôi có thể nghĩ về việc gõ sẽ dễ dàng hơn nhiều, thường là để gỡ lỗi.

Bất kỳ suy nghĩ về điều này?

EDIT: Cảm ơn tất cả các câu trả lời tuyệt vời! Câu hỏi này chỉ đến với tôi sau khi thực hiện nhiều lần gõ lặp đi lặp lại, nhưng tôi không bao giờ nghĩ rằng sẽ có các macro khác, ít gây nhầm lẫn hơn để sử dụng. Đối với những người không muốn đọc tất cả các câu trả lời, cách thay thế tốt nhất là sử dụng các macro của IDE của bạn để giảm việc gõ lặp đi lặp lại.


74
Nó rõ ràng cho bạn bởi vì bạn đã phát minh ra nó. Đối với mọi người khác, nó chỉ bị xáo trộn. Lúc đầu, nó trông giống như một lỗi cú pháp. Khi nó biên dịch tôi sẽ nghĩ cái quái gì và sau đó thấy rằng bạn có một macro không có trong tất cả các mũ. Theo ý kiến ​​của tôi, điều này chỉ làm cho mã trở nên khủng khiếp để duy trì, tôi chắc chắn sẽ từ chối điều này nếu nó được xem xét mã và tôi không hy vọng bạn sẽ tìm thấy nhiều mã sẽ chấp nhận nó. Và bạn đang lưu 3 ký tự !!!!!!!!!
Martin York

11
Trường hợp bạn không thể xác định một cách hợp lý sự lặp đi lặp lại bằng cách sử dụng các hàm hoặc bất cứ điều gì, cách tiếp cận tốt hơn là tìm hiểu những gì trình soạn thảo hoặc IDE của bạn có thể làm để giúp bạn. Các macro trình soạn thảo văn bản hoặc các phím nóng "đoạn trích" có thể giúp bạn không phải gõ quá nhiều mà không làm hỏng khả năng đọc.
Steve314

2
Tôi đã làm điều này trước đây (với các phần lớn hơn của bản tóm tắt), nhưng thực tế của tôi là viết mã, sau đó chạy bộ tiền xử lý và thay thế tệp gốc bằng đầu ra của bộ tiền xử lý. Tiết kiệm cho tôi gõ, tránh cho tôi (và những người khác) rắc rối bảo trì.
TMN

9
Bạn đã lưu 3 ký tự và giao dịch chúng cho các tuyên bố khó hiểu. Ví dụ rất hay về macro xấu,
imho

7
Nhiều biên tập viên có một tính năng nâng cao cho chính xác kịch bản này, nó được gọi là "Sao chép và dán"
Chris Burt-Brown

Câu trả lời:


111

Viết mã rất dễ. Đọc mã là khó.

Bạn viết mã một lần. Nó sống trong nhiều năm, mọi người đọc nó hàng trăm lần.

Tối ưu hóa mã để đọc, không phải để viết.


11
Tôi đồng ý 100%. (Trên thực tế, tôi sẽ tự viết câu trả lời này.) Mã được viết một lần, nhưng có thể được đọc hàng chục, hàng trăm, thậm chí hàng nghìn lần, có thể hàng chục, hàng trăm hoặc thậm chí bởi hàng ngàn nhà phát triển. Thời gian để viết mã là hoàn toàn không liên quan, điều duy nhất được tính là thời gian để đọc và hiểu nó.
sbi

2
Bộ tiền xử lý có thể và nên được sử dụng để tối ưu hóa mã để đọc và duy trì.
SK-logic

2
Và ngay cả khi bạn chỉ đọc mã trong một hoặc hai năm: Bạn sẽ tự quên những thứ này trong khi làm những việc khác ở giữa.
johannes

2
"Luôn luôn mã hóa như thể anh chàng cuối cùng duy trì mã của bạn sẽ là một kẻ tâm thần bạo lực, người biết bạn sống ở đâu." - (Martin Golding)
Dylan Yaga

@Dylan - nếu không, sau vài tháng duy trì mã đó, anh ta sẽ tìm thấy bạn - (Tôi)
Steve314

28

Cá nhân, tôi ghê tởm nó. Có một số lý do tại sao tôi không khuyến khích mọi người từ kỹ thuật này:

  1. Tại thời điểm biên dịch, các thay đổi mã thực tế của bạn có thể là đáng kể. Anh chàng tiếp theo xuất hiện và thậm chí bao gồm một khung đóng trong #define của anh ta hoặc một lệnh gọi hàm. Những gì được viết tại một điểm mã nhất định khác xa với những gì sẽ có sau khi xử lý trước.

  2. Nó là không thể đọc được. Nó có thể rõ ràng với bạn .. bây giờ .. nếu đó chỉ là một định nghĩa. Nếu nó trở thành thói quen, bạn sẽ sớm kết thúc với hàng tá #defines và sẽ bắt đầu tự mình theo dõi. Nhưng tệ nhất trong tất cả, không ai khác có thể hiểu word1 pSpace word2chính xác nghĩa là gì (mà không cần tra cứu #define).

  3. Nó có thể trở thành một vấn đề cho các công cụ bên ngoài. Giả sử bạn bằng cách nào đó kết thúc với #define bao gồm khung đóng, nhưng không có khung mở. Mọi thứ có thể hoạt động tốt, nhưng các biên tập viên và các công cụ khác có thể thấy một cái gì đó function(withSomeCoolDefine;khá kỳ dị (nghĩa là họ sẽ báo cáo lỗi và không có gì). (Ví dụ tương tự: một lệnh gọi hàm bên trong một định nghĩa - các công cụ phân tích của bạn có thể tìm thấy cuộc gọi này không?)

  4. Bảo trì trở nên khó khăn hơn nhiều. Bạn có tất cả những định nghĩa bên cạnh các vấn đề thông thường mà bảo trì mang theo. Ngoài điểm trên, công cụ hỗ trợ tái cấu trúc cũng có thể bị ảnh hưởng tiêu cực.


4
Cuối cùng tôi đã cấm bản thân mình trong hầu hết các macro vì những rắc rối khi cố gắng xử lý chúng ngay trong Doxygen. Đã vài năm rồi, và nhìn chung tôi nghĩ khả năng đọc được cải thiện khá nhiều - bất kể tôi có sử dụng Doxygen hay không.
Steve314

16

Suy nghĩ chính của tôi về điều này, là tôi không bao giờ sử dụng "làm cho việc gõ dễ dàng hơn" như một quy tắc khi viết mã.

Quy tắc chính của tôi khi viết mã là làm cho nó dễ đọc. Lý do đằng sau điều này chỉ đơn giản là mã được đọc một thứ tự cường độ nhiều lần hơn nó được viết. Như vậy, thời gian bạn mất việc viết nó một cách cẩn thận, có trật tự, được đặt ra một cách chính xác trên thực tế đã được đầu tư để làm cho việc đọc thêm và hiểu nhanh hơn nhiều.

Như vậy, #define bạn sử dụng chỉ đơn giản là phá vỡ cách thông thường của xen kẽ <<các công cụ khác . Nó phá vỡ quy tắc ít bất ngờ nhất và không phải là một điều tốt.


1
+1: "Mã được đọc một thứ tự cường độ nhiều lần hơn nó được viết" !!!!
Giorgio

14

Câu hỏi này đưa ra một ví dụ rõ ràng về cách bạn có thể sử dụng macro một cách tồi tệ. Để xem các ví dụ khác (và được giải trí) xem câu hỏi này .

Phải nói rằng, tôi sẽ đưa ra các ví dụ trong thế giới thực về những gì tôi cho là kết hợp tốt các macro.

Ví dụ đầu tiên xuất hiện trong CppUnit , là khung kiểm tra đơn vị. Giống như bất kỳ khung thử nghiệm tiêu chuẩn nào khác, bạn tạo một lớp thử nghiệm và sau đó bạn phải bằng cách nào đó chỉ định phương thức nào sẽ được chạy như một phần của thử nghiệm.

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

Như bạn có thể thấy, lớp có một khối macro là phần tử đầu tiên. Nếu tôi đã thêm một phương thức mới testSubtraction, rõ ràng bạn cần phải làm gì để đưa nó vào trong quá trình chạy thử.

Các khối macro này mở rộng ra một cái gì đó như thế này:

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

Mà bạn thích đọc và duy trì?

Một ví dụ khác là trong khung Microsoft MFC, nơi bạn ánh xạ các chức năng thành các thông báo:

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

Vậy, những điều phân biệt "Macros tốt" với loại ác quỷ khủng khiếp là gì?

  • Họ thực hiện một nhiệm vụ không thể đơn giản hóa bằng bất kỳ cách nào khác. Viết một macro để xác định tối đa giữa hai yếu tố là sai, bởi vì bạn có thể đạt được điều tương tự bằng cách sử dụng một phương thức mẫu. Nhưng có một số tác vụ phức tạp (ví dụ: ánh xạ mã thông báo đến các hàm thành viên) mà ngôn ngữ C ++ không xử lý một cách tao nhã.

  • Họ có một cách sử dụng cực kỳ nghiêm ngặt, chính thức. Trong cả hai ví dụ này, các khối macro được thông báo bằng cách bắt đầu và kết thúc macro và phần giữa các macro sẽ chỉ xuất hiện bên trong các khối này. Bạn có C ++ bình thường, bạn xin phép một thời gian ngắn với một khối macro và sau đó bạn trở lại bình thường. Trong các ví dụ "macro ác", các macro nằm rải rác trong mã và người đọc không may không có cách nào biết khi nào các quy tắc C ++ được áp dụng và khi nào thì không.


5

Bằng mọi cách, sẽ tốt hơn nếu bạn điều chỉnh trình soạn thảo IDE / văn bản yêu thích của bạn để chèn đoạn mã bạn thấy mệt mỏi để gõ lại nhiều lần. Và tốt hơn là thuật ngữ "lịch sự" để so sánh. Trên thực tế, tôi không thể nghĩ về bất kỳ trường hợp tương tự nào khi tiền xử lý đánh bại các macro của trình soạn thảo. Chà, có thể là một - khi bởi một số lý do bí ẩn và không vui, bạn liên tục sử dụng các bộ công cụ khác nhau để mã hóa. Nhưng nó không phải là một lời biện minh :)

Nó cũng có thể là một giải pháp tốt hơn cho các kịch bản phức tạp hơn, khi tiền xử lý văn bản có thể làm những điều khó đọc và phức tạp hơn nhiều (hãy nghĩ về đầu vào tham số).


2
+1. Thật vậy: hãy để biên tập viên làm việc cho bạn. Bạn sẽ có được điều tốt nhất của cả hai thế giới nếu ví dụ bạn viết tắt của << " " <<.
unperson325680

-1 cho "Nó cũng có thể là một giải pháp tốt hơn cho các kịch bản phức tạp hơn, khi tiền xử lý văn bản có thể làm điều khó đọc và phức tạp hơn nhiều (hãy nghĩ về đầu vào tham số)" - Nếu nó phức tạp, hãy tạo một phương thức cho nó, thậm chí sau đó, thực hiện một phương pháp cho nó. ví dụ như ác này thời gian gần đây tôi thấy trong mã ..... #define printError (x) {puts (x); lợi nhuận x}
mattnz

@mattnz, tôi có nghĩa là các cấu trúc vòng lặp, nếu / khác xây dựng, các mẫu để tạo bộ so sánh, v.v. - loại công cụ đó. Trong các loại IDE, kiểu nhập tham số như vậy giúp bạn không chỉ gõ nhanh vài dòng mã mà còn lặp lại nhanh chóng thông qua các thông số. Không ai cố gắng cạnh tranh với các phương pháp. phương thức là phương thức)))
shabunc

4

Những người khác đã giải thích lý do tại sao bạn không nên làm điều đó. Ví dụ của bạn rõ ràng không xứng đáng được thực hiện với một macro. Nhưng, có một loạt các trường hợp bạn phải sử dụng macro để dễ đọc.

Một ví dụ nổi tiếng về một ứng dụng khôn ngoan của một kỹ thuật như vậy là dự án Clang : xem cách .defcác tệp được sử dụng ở đó. Với các macro và #includebạn có thể cung cấp một định nghĩa khai báo đôi khi hoàn toàn cho một tập hợp những thứ tương tự sẽ không được kiểm soát thành các khai báo kiểu, các casecâu lệnh bất cứ khi nào thích hợp, các trình khởi tạo mặc định, v.v. Nó làm tăng đáng kể khả năng duy trì casebáo cáo ở khắp mọi nơi khi bạn đã thêm một enumví dụ mới.

Vì vậy, như với bất kỳ công cụ mạnh mẽ nào khác, bạn phải sử dụng C tiền xử lý một cách cẩn thận. Không có quy tắc chung trong nghệ thuật lập trình, như "bạn không bao giờ nên sử dụng cái này" hoặc "bạn luôn phải sử dụng cái đó". Tất cả các quy tắc không có gì ngoài hướng dẫn.


3

Không bao giờ thích hợp để sử dụng #defines như thế. Trong trường hợp của bạn, bạn có thể làm điều này:

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

Không.

Đối với các macro dự định được sử dụng trong mã, một hướng dẫn tốt để kiểm tra sự phù hợp là bao quanh phần mở rộng của nó bằng dấu ngoặc đơn (cho biểu thức) hoặc dấu ngoặc (cho mã) và xem liệu nó có còn biên dịch không:

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

Các macro được sử dụng trong các khai báo (như ví dụ trong câu trả lời của Andrew Shepherd) có thể thoát khỏi một bộ quy tắc lỏng lẻo miễn là chúng không làm đảo lộn bối cảnh xung quanh (ví dụ: chuyển đổi giữa publicprivate).


1

Đó là một điều hợp lý để làm trong một chương trình "C" thuần túy.

Nó là không cần thiết và khó hiểu trong một chương trình C ++.

Có nhiều cách để tránh việc gõ mã lặp đi lặp lại trong C ++. Từ việc sử dụng các tiện ích được cung cấp bởi IDE của bạn (ngay cả với vi "" đơn giản %s/ pspace /<< " " <</gsẽ tiết kiệm được nhiều thao tác gõ và vẫn tạo ra mã có thể đọc được tiêu chuẩn). Bạn có thể định nghĩa một phương thức riêng để thực hiện điều này hoặc đối với các trường hợp phức tạp hơn, mẫu C ++ sẽ sạch hơn và đơn giản hơn.


2
Không, đó không phải là một điều hợp lý để làm trong C. Các giá trị đơn lẻ hoặc các biểu thức độc lập hoàn toàn chỉ dựa vào các tham số macro, vâng, và sau này, một hàm thậm chí có thể là một lựa chọn tốt hơn. Một cấu trúc nửa nướng như trong ví dụ, không có cách nào.
Bảo mật

@secure - Tôi đồng ý rằng nó không phải là một ý tưởng tốt trong trường hợp ví dụ được đưa ra. Nhưng với các mẫu thiếu, v.v., có các cách sử dụng hợp lệ cho các macro "#DEFINE" trong C.
James Anderson

1

Trong C ++, điều này có thể được giải quyết với quá tải toán tử. Hoặc thậm chí một cái gì đó đơn giản như một hàm matrixdic:

lineWithSpaces(word1, word2, word3, ..., wordn)vừa đơn giản vừa giúp bạn tiết kiệm gõ pSpaceslại nhiều lần.

Vì vậy, trong trường hợp của bạn có vẻ như không phải là vấn đề lớn, có một giải pháp đơn giản và mạnh mẽ hơn.

Nói chung, có một vài trường hợp sử dụng macro ngắn hơn đáng kể mà không giới thiệu obfuscation và chủ yếu là một giải pháp đủ ngắn sử dụng các tính năng ngôn ngữ thực tế (macro chỉ là một sự thay thế chuỗi đơn thuần).


0

Có bất kỳ quan điểm nào về việc sử dụng #define để xác định các dòng mã đầy đủ để đơn giản hóa mã hóa là thực hành lập trình tốt hay xấu?

Vâng, nó rất tệ. Tôi thậm chí đã thấy những người làm điều này:

#define R return

để lưu gõ (những gì bạn đang cố gắng để đạt được).

Mã như vậy chỉ thuộc về những nơi như thế này .


-1

Macro là xấu xa và chỉ nên được sử dụng khi bạn thực sự phải làm. Có một vài trường hợp áp dụng macro (chủ yếu là gỡ lỗi). Nhưng trong C ++ trong hầu hết các trường hợp, bạn có thể sử dụng các hàm nội tuyến thay thế.


2
Không có gì xấu về bản chất trong bất kỳ kỹ thuật lập trình. Tất cả các công cụ và phương pháp có thể có thể được sử dụng, miễn là bạn biết bạn đang làm gì. Nó áp dụng cho khét tiếng goto, cho tất cả các hệ thống vĩ mô có thể, v.v.
SK-logic

1
Đó chính xác là định nghĩa của cái ác trong trường hợp này: "thứ gì đó bạn nên tránh hầu hết thời gian, nhưng không phải là thứ bạn nên tránh mọi lúc". Nó được giải thích trên liên kết rằng cái ác đang chỉ.
sakisk

2
Tôi tin rằng nó phản tác dụng đối với bất kỳ thương hiệu nào là "xấu xa", "có thể gây hại" hoặc thậm chí là "đáng ngờ". Tôi không thích khái niệm "thực hành xấu" và "mùi mã". Mỗi và mọi nhà phát triển phải quyết định trong mọi trường hợp cụ thể, thực hành nào có hại. Dán nhãn là một thực hành có hại - mọi người có xu hướng không nghĩ gì thêm nếu một cái gì đó đã được dán nhãn bởi những người khác.
SK-logic

-2

Không, bạn không được phép sử dụng macro để lưu .

Tuy nhiên, bạn được phép, thậm chí được yêu cầu sử dụng chúng để tách phần không thay đổi của mã khỏi thay đổi mã và giảm sự dư thừa. Sau này, bạn phải nghĩ đến các lựa chọn thay thế và chỉ chọn macro nếu các công cụ tốt hơn không hoạt động. (Đối với macro thực hành khá ở cuối dòng, do đó, nó có nghĩa là biện pháp cuối cùng ...)

Để giảm việc gõ hầu hết các trình soạn thảo đều có macro, thậm chí các đoạn mã thông minh.


"Không, bạn không được phép sử dụng macro để lưu ." - Làm tốt lắm, tôi sẽ không nghe lệnh của bạn ở đây! Nếu tôi có một đống đối tượng mà tôi cần khai báo / xác định / ánh xạ / switch/ etc, bạn có thể đặt cược rằng tôi sẽ sử dụng macro để tránh phát điên - và tăng khả năng đọc. Sử dụng macro để lưu gõ từ khóa, kiểm soát luồng và tương tự là ngu ngốc - nhưng để nói rằng không ai có thể sử dụng chúng để lưu tổ hợp phím trong ngữ cảnh hợp lệ cũng tương tự như vậy.
gạch dưới
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.