#jika DEBUG vs Bersyarat ("DEBUG")

432

Mana yang lebih baik untuk digunakan, dan mengapa, pada proyek besar:

#if DEBUG
    public void SetPrivateValue(int value)
    { ... }
#endif

atau

[System.Diagnostics.Conditional("DEBUG")]
public void SetPrivateValue(int value)
{ ... }
Lucas B
sumber
18
Lihat blogs.msdn.com/b/ericlippert/archive/2009/09/10/… untuk beberapa pemikiran tentang pertanyaan ini.
Eric Lippert
2
Anda dapat menggunakan ini juga: if (Debugger.IsAttached) {...}
sofsntp
Catatan untuk pengembang Unity: DEBUG berarti di editor atau di build pengembangan. forum.unity.com/threads/…
KevinVictor

Jawaban:

578

Ini benar-benar tergantung pada apa yang Anda tuju:

  • #if DEBUG: Kode di sini bahkan tidak akan mencapai IL pada rilis.
  • [Conditional("DEBUG")]: Kode ini akan mencapai IL, namun panggilan ke metode ini akan dihilangkan kecuali DEBUG diatur saat pemanggil dikompilasi.

Secara pribadi saya menggunakan keduanya tergantung pada situasi:

Contoh Bersyarat ("DEBUG"): Saya menggunakan ini sehingga saya tidak harus kembali dan mengedit kode saya nanti selama rilis, tetapi selama debugging saya ingin memastikan saya tidak membuat kesalahan ketik. Fungsi ini memeriksa apakah saya mengetikkan nama properti dengan benar ketika mencoba menggunakannya dalam hal-hal INotifyPropertyChanged saya.

[Conditional("DEBUG")]
[DebuggerStepThrough]
protected void VerifyPropertyName(String propertyName)
{
    if (TypeDescriptor.GetProperties(this)[propertyName] == null)
        Debug.Fail(String.Format("Invalid property name. Type: {0}, Name: {1}",
            GetType(), propertyName));
}

Anda benar-benar tidak ingin membuat fungsi menggunakan #if DEBUGkecuali Anda bersedia untuk membungkus setiap panggilan ke fungsi itu dengan yang sama #if DEBUG:

#if DEBUG
    public void DoSomething() { }
#endif

    public void Foo()
    {
#if DEBUG
        DoSomething(); //This works, but looks FUGLY
#endif
    }

melawan:

[Conditional("DEBUG")]
public void DoSomething() { }

public void Foo()
{
    DoSomething(); //Code compiles and is cleaner, DoSomething always
                   //exists, however this is only called during DEBUG.
}

#Jika DEBUG contoh: Saya menggunakan ini ketika mencoba mengatur binding yang berbeda untuk komunikasi WCF.

#if DEBUG
        public const String ENDPOINT = "Localhost";
#else
        public const String ENDPOINT = "BasicHttpBinding";
#endif

Dalam contoh pertama, semua kode ada, tetapi hanya diabaikan kecuali DEBUG aktif. Dalam contoh kedua, const ENDPOINT diatur ke "Localhost" atau "BasicHttpBinding" tergantung pada apakah DEBUG diatur atau tidak.


Pembaruan: Saya memperbarui jawaban ini untuk memperjelas poin penting dan rumit. Jika Anda memilih untuk menggunakan ConditionalAttribute, ingatlah bahwa panggilan dihilangkan selama kompilasi, dan bukan runtime . Itu adalah:

MyLibrary.dll

[Conditional("DEBUG")]
public void A()
{
    Console.WriteLine("A");
    B();
}

[Conditional("DEBUG")]
public void B()
{
    Console.WriteLine("B");
}

Ketika perpustakaan dikompilasi dengan mode rilis (yaitu tidak ada simbol DEBUG), itu akan selamanya memiliki panggilan B()dari dalam A()dihilangkan, bahkan jika panggilan ke A()dimasukkan karena DEBUG didefinisikan dalam majelis panggilan.

saya
sumber
13
#Jika Debug untuk DoSomething tidak perlu memiliki semua pernyataan panggilan yang dikelilingi oleh #jika DEBUG. Anda dapat 1: cukup # jika MENGGUNAKAN bagian dalam DoSomething, atau, melakukan #else dengan definisi kosong DoSomething. Tetap saja komentar Anda membantu saya memahami perbedaannya, tetapi #jika DEBUG tidak harus seburuk yang Anda tunjukkan.
Apeiron
3
Jika Anda baru saja #BAB DEBUG konten, JIT masih dapat menyertakan panggilan ke fungsi ketika kode Anda berjalan dalam membangun non-debug. Menggunakan atribut Conditional berarti JIT tahu untuk bahkan tidak menghasilkan callsite ketika dalam membangun non-DEBUG.
Jeff Yates
2
@ Jeffeates: Saya tidak melihat apa yang Anda tulis berbeda dari apa yang saya jelaskan.
saya
1
@Apeiron jika Anda hanya memiliki konten fungsi di # jika debug maka panggilan fungsi masih ditambahkan ke tumpukan panggilan, sementara ini biasanya tidak terlalu penting, menambahkan deklarasi dan pemanggilan fungsi ke # jika berarti kompiler berperilaku sebagai jika fungsi tidak ada, maka metode saya adalah cara yang lebih "benar" untuk menggunakan #jika. meskipun kedua metode menghasilkan hasil yang tidak dapat dibedakan satu sama lain dalam penggunaan normal
MikeT
5
jika ada yang bertanya-tanya, IL = Bahasa Menengah - en.wikipedia.org/wiki/Common_Intermediate_Language
jbyrd
64

Yah, perlu dicatat bahwa itu tidak berarti sama sekali.

Jika simbol DEBUG tidak didefinisikan, maka dalam kasus pertama SetPrivateValueitu sendiri tidak akan dipanggil ... sedangkan dalam kasus kedua itu akan ada, tetapi setiap penelepon yang dikompilasi tanpa simbol DEBUG akan membuat panggilan tersebut dihilangkan.

Jika kode dan semua peneleponnya berada dalam rakitan yang sama perbedaan ini kurang penting - tetapi itu berarti bahwa dalam kasus pertama Anda juga perlu memiliki #if DEBUGsekitar kode panggilan juga.

Secara pribadi saya akan merekomendasikan pendekatan kedua - tetapi Anda harus menjaga perbedaan di antara mereka jelas di kepala Anda.

Jon Skeet
sumber
5
+1 untuk kode panggilan harus memiliki # jika pernyataan juga. Yang berarti akan ada banyak pernyataan #jika ...
Lucas B
Sementara opsi kedua (atribut Bersyarat) lebih bagus dan lebih bersih dalam beberapa kasus, mungkin diperlukan untuk mengkomunikasikan fakta bahwa pemanggilan metode akan dihapus dari perakitan selama kompilasi (misalnya, dengan konvensi penamaan).
asam lisergik
45

Saya yakin banyak yang akan tidak setuju dengan saya, tetapi setelah menghabiskan waktu sebagai tukang bangunan terus-menerus mendengar "Tapi itu bekerja pada mesin saya!", Saya mengambil pendirian bahwa Anda sebaiknya tidak pernah menggunakan keduanya. Jika Anda benar-benar membutuhkan sesuatu untuk pengujian dan debugging, cari cara untuk membuat testabilitas itu terpisah dari kode produksi yang sebenarnya.

Abstraksi skenario dengan mengejek dalam unit test, buat satu versi dari hal-hal untuk satu skenario yang ingin Anda uji, tetapi jangan masukkan tes untuk debug ke dalam kode untuk binari yang Anda uji dan tulis untuk rilis produksi. Tes debug ini hanya menyembunyikan kemungkinan bug dari devs sehingga tidak ditemukan hingga nanti dalam proses.

Jimmy Hoffa
sumber
4
Saya sangat setuju dengan Anda, Jimmy. Jika Anda menggunakan DI dan mengejek untuk pengujian Anda, mengapa Anda perlu #if debugatau konstruksi serupa dalam kode Anda?
Richard Ev
@RichardEv Mungkin ada cara yang lebih baik untuk menangani ini, tetapi saya saat ini menggunakannya untuk memungkinkan saya memainkan bagian dari pengguna yang berbeda melalui string kueri. Saya tidak ingin ini dalam produksi tetapi saya ingin itu untuk debugging sehingga saya dapat mengontrol alur kerja yang dilalui tanpa harus membuat banyak pengguna dan masuk ke kedua akun untuk berjalan melalui alur. Meskipun ini adalah pertama kalinya saya benar-benar harus menggunakannya.
Tony
4
Daripada hanya untuk pengujian, kita sering melakukan hal-hal seperti mengatur email penerima default untuk diri kita sendiri, dalam membangun debug, menggunakan #if DEBUGsehingga kita tidak secara tidak sengaja melakukan spam kepada orang lain saat menguji sistem yang harus mengirimkan email sebagai bagian dari proses. Kadang-kadang ini adalah alat yang tepat untuk pekerjaan itu :)
Hilang Coding
6
Saya biasanya akan setuju dengan Anda, tetapi jika Anda berada dalam situasi di mana kinerja adalah yang terpenting maka Anda tidak ingin mengacaukan kode dengan pencatatan dan pengguna pengguna asing, tetapi saya setuju 100% bahwa mereka tidak boleh digunakan untuk mengubah perilaku mendasar
MikeT
5
-1 Tidak ada yang salah dengan menggunakan salah satu dari ini. Unit klaim pengujian dan DI entah bagaimana menggantikan build yang diaktifkan untuk debug suatu produk adalah naif.
Ted Bigham
15

Yang ini bisa bermanfaat juga:

if (Debugger.IsAttached)
{
...
}
sofsntp
sumber
1
Secara pribadi, saya tidak melihat bagaimana ini bisa bermanfaat dibandingkan dengan 2 alternatif lainnya. Ini menjamin bahwa seluruh blok dikompilasi, dan Debugger.IsAttachedharus dipanggil pada saat runtime bahkan dalam rilis rilis.
Jai
9

Dengan contoh pertama, SetPrivateValuetidak akan ada di build jika DEBUGtidak ditentukan, dengan contoh kedua, panggilan ke SetPrivateValuetidak akan ada di build jika DEBUGtidak ditentukan.

Dengan contoh pertama, Anda juga harus membungkus semua panggilan SetPrivateValuedengan #if DEBUG.

Dengan contoh kedua, panggilan ke SetPrivateValueakan dihilangkan, tetapi perlu diketahui bahwa SetPrivateValueitu sendiri masih akan dikompilasi. Ini berguna jika Anda sedang membangun perpustakaan, jadi aplikasi yang mereferensikan perpustakaan Anda masih dapat menggunakan fungsi Anda (jika kondisinya terpenuhi).

Jika Anda ingin menghilangkan panggilan dan menghemat ruang callee, Anda bisa menggunakan kombinasi kedua teknik ini:

[System.Diagnostics.Conditional("DEBUG")]
public void SetPrivateValue(int value){
    #if DEBUG
    // method body here
    #endif
}
P Ayah
sumber
@P Daddy: Wrapping #if DEBUGsekitarConditional("DEBUG") tidak menghapus panggilan ke fungsi itu, itu hanya menghapus fungsi dari IL alltogether, jadi Anda masih memiliki panggilan ke fungsi yang tidak ada (kesalahan kompilasi).
saya
1
Jika seseorang tidak ingin kode ada dalam rilis, haruskah seseorang membungkus tubuh metode dalam "#jika DEBUG", mungkin dengan tulisan rintisan "#else" (dengan lemparan atau nilai pengembalian dummy), dan gunakan atribut untuk menyarankan bahwa penelepon tidak peduli dengan panggilan itu? Itu tampaknya yang terbaik dari kedua dunia.
supercat
@myermian, @supercat: Ya, Anda berdua benar. Kesalahanku. Saya akan mengedit sesuai saran supercat.
P Daddy
5

Mari kita anggap kode Anda juga memiliki #elsepernyataan yang mendefinisikan fungsi rintisan nol, membahas salah satu poin Jon Skeet. Ada perbedaan penting kedua di antara keduanya.

Misalkan fungsi #if DEBUGatau Conditionalada dalam DLL yang dirujuk oleh proyek utama Anda yang dapat dieksekusi. Dengan menggunakan #if, evaluasi kondisional akan dilakukan sehubungan dengan pengaturan kompilasi perpustakaan. Menggunakan Conditionalatribut, evaluasi kondisi akan dilakukan sehubungan dengan pengaturan kompilasi dari penyerang.

Kennet Belenky
sumber
2

Saya memiliki ekstensi SOAP WebService untuk mencatat lalu lintas jaringan menggunakan kustom [TraceExtension]. Saya menggunakan ini hanya untuk membangun Debug dan menghilangkan dari rilis membangun. Gunakan #if DEBUGuntuk membungkus [TraceExtension]atribut sehingga menghapusnya dari rilis Rilis .

#if DEBUG
[TraceExtension]
#endif
[System.Web.Service.Protocols.SoapDocumentMethodAttribute( ... )]
[ more attributes ...]
public DatabaseResponse[] GetDatabaseResponse( ...) 
{
    object[] results = this.Invoke("GetDatabaseResponse",new object[] {
          ... parmeters}};
}

#if DEBUG
[TraceExtension]
#endif
public System.IAsyncResult BeginGetDatabaseResponse(...)

#if DEBUG
[TraceExtension]
#endif
public DatabaseResponse[] EndGetDatabaseResponse(...)
Steven J. Hathaway
sumber
0

Biasanya Anda akan membutuhkannya di Program.cs di mana Anda ingin memutuskan untuk menjalankan Debug pada kode Non-Debug dan itu terlalu banyak di Windows Services. Jadi saya membuat isdebugMode bidang readonly dan mengatur nilainya dalam konstruktor statis seperti yang ditunjukkan di bawah ini.

static class Program
{

    #region Private variable
    static readonly bool IsDebugMode = false;
    #endregion Private variable

    #region Constrcutors
    static Program()
    {
 #if DEBUG
        IsDebugMode = true;
 #endif
    }
    #endregion

    #region Main

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    static void Main(string[] args)
    {

        if (IsDebugMode)
        {
            MyService myService = new MyService(args);
            myService.OnDebug();             
        }
        else
        {
            ServiceBase[] services = new ServiceBase[] { new MyService (args) };
            services.Run(args);
        }
    }

    #endregion Main        
}
Yashwant Shukla
sumber