Tekrarlanan kod yazmayı kolaylaştırmak için #define kullanımı uygun mudur?


17

#Define komutunun kodlamayı basitleştirmek için tam kod satırlarını tanımlamak için kullanmanın iyi veya kötü programlama uygulaması olup olmadığı konusunda herhangi bir görüş var mı? Örneğin, bir grup kelimeyi birlikte yazdırmam gerekirse, yazarak rahatsız olurdum

<< " " <<

Bir cout deyiminde kelimeler arasına boşluk eklemek için. Sadece yapabilirdim

#define pSpace << " " <<

ve yazın

cout << word1 pSpace word2 << endl;

Bana göre bu kodun netliğini arttırmaz ya da çıkarmaz ve yazmayı biraz kolaylaştırır. Genellikle hata ayıklama için yazmanın nerede daha kolay olacağını düşünebileceğim başka durumlar da var.

Bunun hakkında bir fikrin var mı?

EDIT: Tüm harika cevaplar için teşekkürler! Bu soru çok fazla tekrarlı yazım yaptıktan sonra bana geldi, ancak kullanmak için daha az kafa karıştırıcı başka makrolar olacağını hiç düşünmemiştim. Tüm cevapları okumak istemeyenler için en iyi alternatif, yinelenen yazmayı azaltmak için IDE'nizin makrolarını kullanmaktır.


74
Sizin için açık çünkü icat ettiniz. Diğer herkese sadece gizlendi. İlk başta bir sözdizimi hatası gibi görünüyor. Derlediğinde ne halt olduğunu düşünürdüm ve sonra tüm kapaklarda olmayan bir makronun olduğunu bulurdum. Bence bu sadece kodu korumak için korkunç yapar kod incelemesi için geldi ve ben bunu kabul ediyorum birçok bulacaksınız sanmıyorum kesinlikle bu reddetmek istiyorum. Ve 3 karakter kazanıyorsun !!!!!!!!!
Martin York

11
İşlevleri veya herhangi bir şeyi kullanarak tekrarlanabilirliği makul bir şekilde belirleyemediğiniz durumlarda, daha iyi yaklaşım, düzenleyicinizin veya IDE'nizin size yardımcı olmak için neler yapabileceğini öğrenmektir. Metin düzenleyici makrolar veya "snippet" kısayol tuşları, okunabilirliğe zarar vermeden çok fazla şey yazmanızı engelleyebilir .
Steve314

2
Bunu daha önce yaptım (daha büyük kazan plakası ile), ancak benim pratiğim kodu yazmak, sonra önişlemciyi çalıştırmak ve orijinal dosyayı önişlemci çıkışı ile değiştirmekti. Yazarak kurtardım, beni (ve diğerlerini) bakım güçlüğünden kurtardı.
TMN

9
3 karakter kaydettiniz ve kafa karıştırıcı ifadeler için işlem yaptınız. Kötü makroya çok iyi bir örnek,
imho

7
Birçok editör tam olarak bu senaryo için gelişmiş bir özelliğe sahiptir, buna "Kopyala ve Yapıştır" denir
Chris Burt-Brown

Yanıtlar:


111

Kod yazmak kolaydır. Kodu okumak zor.

Kodu bir kez yazarsınız. Yıllarca yaşıyor, insanlar yüz defa okuyor.

Kodu yazmak için değil, okumak için optimize edin.


11
% 100 katılıyorum. (Aslında, bu yanıtı kendim yazacaktım.) Kod bir kez yazılmıştır, ancak düzinelerce, yüzlerce hatta binlerce kez, belki onlarca, yüzlerce, hatta binlerce geliştirici tarafından okunabilir. Kod yazma süresi tamamen önemsizdir, önemli olan tek şey kodu okuma ve anlama zamanıdır.
sbi

2
Önişlemci, kodu okuma ve bakım için optimize etmek için kullanılabilir ve kullanılmalıdır.
SK-logic

2
Ve sadece bir veya iki yıl içinde kodu okuyor olsanız bile: Aralarında başka şeyler yaparken bunları kendiniz unutacaksınız.
johannes

2
"Her zaman kodunuzu koruyan adam, nerede yaşadığınızı bilen şiddetli bir psikopat olacak gibi kodlayın." - (Martin Golding)
Dylan Yaga

@Dylan - Birkaç ay bu kodu muhafaza sonra aksi takdirde, o olacak bulabileceğimi - (Me)
Steve314

28

Şahsen, nefret ediyorum. İnsanları bu teknikten caydırmamın birkaç nedeni var:

  1. Derleme zamanında gerçek kod değişiklikleriniz önemli olabilir. Bir sonraki adam gelir ve hatta #define veya fonksiyon çağrısında bir kapanış ayracı içerir. Belirli bir kod noktasında yazılanlar, ön işlemden sonra orada olacaklardan çok uzaktır.

  2. Okunamaz. Sizin için açık olabilir .. şimdilik .. sadece bu tanım ise. Bir alışkanlık haline gelirse, yakında düzinelerce # tanımla sonuçlanacak ve kendinizi takip etmeye başlamayacaksınız. Ama en kötüsü, hiç kimse word1 pSpace word2tam olarak ne anlama geldiğini anlayamayacak (#define bakmadan).

  3. Harici araçlar için sorun olabilir. Bir şekilde bir kapanış köşeli ayraç içeren ancak açılış köşeli ayraç içermeyen bir #define ile sonuçlandığını varsayalım. Her şey iyi çalışabilir, ancak editörler ve diğer araçlar function(withSomeCoolDefine;oldukça tuhaf bir şey görebilir (yani, hataları ve ne olduğunu rapor edeceklerdir). (Benzer örnek: bir tanım içindeki bir işlev çağrısı - analiz araçlarınız bu çağrıyı bulabilecek mi?)

  4. Bakım zorlaşır. Bakımın beraberinde getirdiği olağan sorunlara ek olarak bunları tanımlayabilirsiniz. Yukarıdaki noktaya ek olarak, yeniden düzenleme için alet desteği de olumsuz etkilenebilir.


4
Sonunda, Doxygen'te onları ele almaya çalışmanın zorlukları yüzünden kendimi neredeyse tüm makrolardan yasakladım. Birkaç yıl geçti ve Doxygen'i kullanıp kullanmadığımdan bağımsız olarak okunabilirliğin biraz iyileştiğini düşünüyorum.
Steve314

16

Bu konudaki ana düşüncem, kod yazarken kural olarak asla "yazmayı kolaylaştır" ı kullanmam.

Kod yazarken temel kuralım, kolayca okunabilir yapmaktır. Bunun arkasındaki mantık, kodun yazıldığından daha fazla bir büyüklük sırası okunmasıdır. Bu nedenle, zaman kaybetmek dikkatle yazma, düzenli, doğru aslında daha çok okuma yapma ve çok daha hızlı anlamakta yatırım ortaya koydu.

Bu nedenle, kullandığınız #define, alternatif <<ve diğer şeylerin olağan yolunu kırıyor . En az sürpriz kuralını çiğniyor ve IMHO iyi bir şey değil.


1
+1: "Kod, yazıldığından daha fazla bir büyüklük sırası okunur" !!!!
Giorgio

14

Bu soru, makroları nasıl kötü kullanabileceğinize dair açık bir örnek vermektedir. Diğer örnekleri görmek (ve eğlenmek) için bu soruya bakın .

Bunu söyledikten sonra, makroların iyi bir şekilde dahil edildiğini düşündüğüm gerçek dünyaya örnekler vereceğim.

İlk örnek , birim test çerçevesi olan CppUnit'te görünür . Diğer herhangi bir standart test çerçevesi gibi, bir test sınıfı oluşturursunuz ve testin bir parçası olarak hangi yöntemlerin çalıştırılacağını bir şekilde belirtmeniz gerekir.

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

Gördüğünüz gibi, sınıfın ilk öğesi olarak bir makro bloğu vardır. Yeni bir yöntem eklediysem testSubtraction, test çalışmasına dahil etmek için ne yapmanız gerektiği açıktır.

Bu makro bloklar şöyle bir şeye genişler:

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

Hangisini okumayı ve sürdürmeyi tercih edersiniz?

Başka bir örnek, işlevleri iletilerle eşleştirdiğiniz Microsoft MFC çerçevesinde yer almaktadır:

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

Peki, "İyi Makrolar" ı korkunç kötü türden ayıran şeyler nelerdir?

  • Başka bir şekilde basitleştirilemeyen bir görevi yerine getirirler. İki öğe arasında bir maksimum belirlemek için bir makro yazmak yanlıştır, çünkü bunu bir şablon yöntemi kullanarak elde edebilirsiniz. Ancak, C ++ dilinin zarifçe işlemediği bazı karmaşık görevler (örneğin, mesaj kodlarını üye işlevleriyle eşleme) vardır.

  • Son derece katı ve resmi bir kullanımları vardır. Bu örneklerin her ikisinde de makro bloklar makroları başlatarak ve bitirerek bildirilir ve aradaki makrolar sadece bu blokların içinde görünür. Normal C ++ 'nız var, kısaca bir makro bloğu ile özür dileriz ve sonra tekrar normale dönersiniz. "Kötü makrolar" örneklerinde, makrolar kod boyunca dağılmıştır ve bahtsız okuyucunun C ++ kurallarının ne zaman uygulandığını ve uygulanmadığını bilmesinin bir yolu yoktur.


5

Eğer tekrar tekrar yazmak için yorucu bulduğunuz kod snippet'lerini eklemek için favori IDE / metin düzenleyicisini ayarlarsanız, elbette daha iyi olacaktır. Ve daha iyi karşılaştırma için "kibar" terimdir. Aslında, önişleme editörün makrolarını yenerken benzer bir durumu düşünemiyorum. Eh, bir tane olabilir - bazı gizemli ve mutsuz nedenlerle kodlama için sürekli olarak farklı araçlar kullanırsanız. Ama bu bir gerekçe değil :)

Ayrıca, metin önişleminin çok daha okunamaz ve karmaşık bir şey yapabileceği daha karmaşık senaryolar için daha iyi bir çözüm olabilir (parametrelenmiş girdiyi düşünün).


2
+1. Gerçekten: editör işi sizin için yapsın. Örneğin kısaltması yaparsanız her iki dünyanın da en iyisini elde edersiniz << " " <<.
unperson325680

-1 "Ayrıca, metin önişleminin çok daha okunamaz ve karmaşık bir şey yapabileceği daha karmaşık senaryolar için daha iyi bir çözüm olabilir (parametrelenmiş girdiyi düşünün)" - Eğer bu karmaşıksa, bunun için bir yöntem yapın, o zaman bile, bunun için bir yöntem. eg Bu kötü son zamanlarda kodda buldum ..... #define printError (x) {koyar (x); dönüş x}
mattnz

@mattnz, Ben döngü yapıları, eğer / else yapıları, karşılaştırıcı oluşturma ve benzeri için şablonlar demek istiyorum - bu tür şeyler. IDE'lerde bu tür parametreli girişler, yalnızca birkaç satır kod yazmanıza yardımcı olmakla kalmaz, aynı zamanda parametrelerle hızlı bir şekilde yineleme yapmanıza yardımcı olur. Hiç kimse yöntemlerle rekabet etmeye çalışmaz. yöntemi yöntem)))
shabunc

4

Diğerleri bunu neden yapmamanız gerektiğini zaten açıkladı. Örneğinizin bir makro ile uygulanmasını hak etmediği açıktır. Ancak, okunabilirlik için makroları kullanmanız gereken çok çeşitli durumlar vardır .

Böyle bir tekniğin akıllıca bir uygulamasının meşhur bir örneği Clang projesidir: .defdosyaların orada nasıl kullanıldığını görün. Makrolarla ve #includetür bildirimlerine açılacak benzer şeylerden oluşan bir koleksiyon için tek, bazen tamamen beyan edici bir tanım sağlayabilirsiniz,case uygun olan yerlerde ifadelere, varsayılan başlatıcılara vb. . Bu, sürdürülebilirliği önemli ölçüde artırır: Örneğin, caseyeni bir yer eklediğiniz her yerde ifadeler enum.

Bu nedenle, diğer güçlü araçlarda olduğu gibi, C ön işlemcisini dikkatli kullanmanız gerekir. Programlama sanatında "bunu asla kullanmamalısınız" veya "her zaman bunu kullanmak zorundasınız" gibi genel bir kural yoktur. Tüm kurallar, kurallardan başka bir şey değildir.


3

Böyle # tanımları kullanmak asla uygun değildir. Sizin durumunuzda bunu yapabilirsiniz:

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

Hayır.

Kodda kullanılması amaçlanan makrolar için, uygunluğu test etmek için iyi bir kılavuz, genişlemesini parantez (ifadeler için) veya parantezlerle (kod için) çevrelemek ve hala derlenip derlenmeyeceğine bakmaktır:

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

Bildirimlerde kullanılan makrolar (Andrew Shepherd'ın cevabındaki örnek gibi), çevreleyen bağlamı bozmadıkları sürece daha gevşek bir kurallar kümesinden kurtulabilirler (örn . publicVe arasında geçiş yapmak private).


1

Saf bir "C" programında yapılacak oldukça geçerli bir şey.

C ++ programında gereksiz ve kafa karıştırıcıdır.

C ++ 'da kodun tekrar tekrar yazılmasını önlemenin birçok yolu vardır. IDE'niz tarafından sağlanan kolaylıkları kullanarak (vi ile bile basit bir " %s/ pspace /<< " " <</g" yazmaktan daha fazla tasarruf sağlar ve yine de standart okunabilir kod üretir). Bunu uygulamak için özel bir yöntem tanımlayabilirsiniz veya daha karmaşık durumlarda C ++ şablonu daha temiz ve daha basit olacaktır.


2
Hayır, saf C'de yapılması makul bir şey değildir. Sadece makro parametrelerine dayanan tek değerler veya tam bağımsız ifadeler, evet ve ikincisi için bir fonksiyon daha iyi bir seçim olabilir. Örnekte olduğu gibi yarım pişmiş bir yapı, hiçbir şekilde.
Güvenli

@secure - Verilen örnekte bunun iyi bir fikir olmadığını kabul ediyorum. Ancak eksik şablonlar vb. Verildiğinde, C'deki "#DEFINE" makroları için geçerli kullanımlar vardır
James Anderson

1

C ++ 'da bu, operatör aşırı yüklenmesi ile çözülebilir. Veya değişken bir işlev kadar basit bir şey bile:

lineWithSpaces(word1, word2, word3, ..., wordn)hem basittir hem de pSpacestekrar tekrar yazmanızı sağlar .

Durumunuz büyük bir şey gibi görünmese de, daha basit ve daha sağlam bir çözüm var.

Genel olarak, bir makro kullanmanın gizleme getirmeden önemli ölçüde daha kısa olduğu birkaç durum vardır ve çoğunlukla gerçek dil özelliklerini kullanan (makrolar daha çok dize ikamesi olan) yeterince kısa bir çözüm vardır.


0

#Define komutunun kodlamayı basitleştirmek için tam kod satırlarını tanımlamak için kullanmanın iyi veya kötü programlama uygulaması olup olmadığı konusunda herhangi bir görüş var mı?

Evet, çok kötü. İnsanların bunu yaptığını bile gördüm:

#define R return

yazmayı kaydetmek için (elde etmeye çalıştığınız şey).

Böyle kodu yalnızca gibi yerlerde aittir bu .


-1

Makrolar kötüdür ve sadece gerçekten ihtiyacınız olduğunda kullanılmalıdır. Makroların geçerli olduğu birkaç durum vardır (çoğunlukla hata ayıklama). Ancak çoğu durumda C ++ 'da satır içi işlevleri kullanabilirsiniz .


2
Orada hiçbir şey herhangi bir programlama tekniği özünde kötülük. Ne yaptığınızı bildiğiniz sürece tüm olası araçlar ve yöntemler kullanılabilir. Bu rezil goto, tüm olası makro sistemleri, vb. İçin geçerlidir .
SK-logic

1
Bu durumda kötülüğün tanımı budur: "çoğu zaman kaçınmanız gereken bir şey, ama her zaman kaçınmanız gereken bir şey değil". Kötülüğün işaret ettiği bağlantıda açıklanmıştır .
sakisk

2
Herhangi bir şeyi "kötü", "potansiyel olarak zararlı" ve hatta "şüpheli" olarak markalamanın verimsiz olduğuna inanıyorum. Ben "kötü uygulama" ve "kod kokusu" kavramını sevmiyorum. Her geliştirici, her uygulamada hangi uygulamanın zararlı olduğuna karar vermek zorundadır. Etiketleme zararlı bir uygulamadır - insanlar başkaları tarafından zaten bir şey etiketlenmişse daha fazla düşünmeme eğilimindedir.
SK-mantığı

-2

Hayır, yazmayı kaydetmek için makro kullanmanıza izin verilmiyor .

Bununla birlikte, kodun değişmeyen kısmını değişen olanlardan ayırmak ve yedekliliği azaltmak için bunları kullanmanıza izin verilir. İkincisi için alternatifler düşünmeli ve sadece daha iyi araçlar işe yaramazsa makro seçmelisiniz. (Pratik için makro satırın sonunda, bu yüzden son çare ima ...)

Yazmayı azaltmak için çoğu editörde makrolar, hatta akıllı kod parçacıkları bulunur.


"Hayır, yazmayı kaydetmek için makro kullanmanıza izin verilmiyor ." - İyi iş, burada siparişinizi dinlemeyeceğim! Eğer tanımlamam / tanımlamam / harita / switch/ vb. Anahtar kelimeleri, kontrol akışını ve benzerlerini yazmak için makroları kullanmak aptaldır - ancak hiç kimsenin bunları geçerli bağlamlarda tuş vuruşlarını kaydetmek için kullanamayacağını söylemek eşit derecede asindir.
underscore_d
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.