#if DEBUG vs. Conditional (“ DEBUG”)


432

สิ่งที่ดีกว่าใช้และทำไมในโครงการขนาดใหญ่:

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

หรือ

[System.Diagnostics.Conditional("DEBUG")]
public void SetPrivateValue(int value)
{ ... }

18
ดูblogs.msdn.com/b/ericlippert/archive/2009/09/10/…สำหรับความคิดบางอย่างสำหรับคำถามนี้
Eric Lippert

2
คุณสามารถใช้สิ่งนี้ได้เช่นกัน: if (Debugger.IsAttached) {... }
sofsntp

หมายเหตุสำหรับนักพัฒนา Unity: DEBUG หมายถึงในตัวแก้ไขหรือในการพัฒนาสร้าง forum.unity.com/threads/…
KevinVictor

คำตอบ:


578

มันขึ้นอยู่กับว่าคุณกำลังทำอะไร:

  • #if DEBUG: รหัสในที่นี่จะไม่ถึง IL ที่วางจำหน่าย
  • [Conditional("DEBUG")]: รหัสนี้จะไปถึง IL อย่างไรก็ตามการโทรไปยังวิธีการจะถูกละเว้นยกเว้น DEBUG จะถูกตั้งค่าเมื่อมีการคอมไพล์ผู้โทร

ส่วนตัวฉันใช้ทั้งสองขึ้นอยู่กับสถานการณ์:

ตัวอย่างแบบมีเงื่อนไข ("DEBUG"):ฉันใช้สิ่งนี้เพื่อที่ฉันจะได้ไม่ต้องกลับไปแก้ไขรหัสในภายหลังในระหว่างการวางจำหน่าย แต่ในระหว่างการดีบักฉันต้องการให้แน่ใจว่าฉันไม่ได้พิมพ์ผิด ฟังก์ชันนี้ตรวจสอบว่าฉันพิมพ์ชื่อคุณสมบัติอย่างถูกต้องเมื่อพยายามใช้ในสิ่งที่ INotifyPropertyChanged ของฉัน

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

คุณไม่ต้องการสร้างฟังก์ชั่นที่ใช้#if DEBUGเว้นแต่คุณจะยินดีที่จะตัดการเรียกทุกครั้งไปยังฟังก์ชันนั้นด้วยสิ่งเดียวกัน#if DEBUG:

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

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

เมื่อเทียบกับ:

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

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

#if ตัวอย่าง DEBUG:ฉันใช้สิ่งนี้เมื่อพยายามตั้งค่าการเชื่อมต่าง ๆ สำหรับการสื่อสาร WCF

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

ในตัวอย่างแรกรหัสทั้งหมดมีอยู่ แต่จะถูกละเว้นยกเว้นว่า DEBUG เปิดอยู่ ในตัวอย่างที่สอง const ENDPOINT ถูกตั้งค่าเป็น "Localhost" หรือ "BasicHttpBinding" ขึ้นอยู่กับว่า DEBUG ถูกตั้งค่าหรือไม่


อัปเดต: ฉันกำลังอัปเดตคำตอบนี้เพื่อชี้แจงจุดที่สำคัญและยุ่งยาก หากคุณเลือกที่จะใช้ConditionalAttributeโปรดจำไว้ว่าการโทรถูกละเว้นระหว่างการคอมไพล์ไม่ใช่การใช้งานจริง นั่นคือ:

MyLibrary.dll

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

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

เมื่อไลบรารีถูกคอมไพล์กับโหมดการปล่อย (นั่นคือไม่มีสัญลักษณ์ DEBUG) มันจะมีการเรียกB()จากภายในตลอดไปA()แม้ว่าจะมีการเรียกไปยังA()เนื่องจาก DEBUG ถูกกำหนดไว้ในแอสเซมบลีการโทร


13
Debug #if สำหรับ DoSomething ไม่จำเป็นต้องมีคำสั่งการโทรทั้งหมดล้อมรอบด้วย #if DEBUG คุณสามารถ 1: เพียงแค่ # ถ้าแก้ปัญหาด้านในของ DoSomething หรือทำ #else ด้วยคำจำกัดความว่างเปล่าของ DoSomething ความคิดเห็นของคุณยังช่วยให้ฉันเข้าใจความแตกต่าง แต่ # ถ้า DEBUG ไม่จำเป็นต้องน่าเกลียดอย่างที่คุณได้แสดง
Apeiron

3
หากคุณเพียงแค่ #if แก้ปัญหาเนื้อหา JIT ยังอาจรวมการเรียกไปยังฟังก์ชันเมื่อรหัสของคุณทำงานในบิลด์ที่ไม่ใช่การดีบัก การใช้แอททริบิวต์แบบมีเงื่อนไขหมายถึง JIT ที่ไม่รู้จะเอาท์พุทไซต์แม้แต่ในบิวด์ที่ไม่ใช่ DEBUG
Jeff Yates

2
@JeffYates: ฉันไม่เห็นว่าสิ่งที่คุณเขียนนั้นแตกต่างจากสิ่งที่ฉันอธิบาย
ของฉัน

1
@Apeiron หากคุณมีเพียงเนื้อหาของฟังก์ชั่นในการแก้ไขข้อผิดพลาด #if ดังนั้นการเรียกใช้ฟังก์ชั่นยังคงถูกเพิ่มเข้าไปใน call stack ขณะนี้มักจะไม่สำคัญมากการเพิ่มการประกาศและการเรียกใช้ฟังก์ชัน #if หมายถึงคอมไพเลอร์ หากฟังก์ชั่นไม่มีอยู่ดังนั้นวิธีการของฉันเป็นวิธีที่ "ถูกต้อง" ในการใช้ #if แม้ว่าทั้งสองวิธีจะให้ผลลัพธ์ที่ไม่สามารถแยกออกจากกันในการใช้งานปกติ
MikeT

5
ถ้ามีใครสงสัย IL = ภาษาระดับกลาง - en.wikipedia.org/wiki/Common_Intermediate_Language
jbyrd

64

มันก็น่าสังเกตว่าพวกเขาไม่ได้หมายถึงสิ่งเดียวกัน

หากสัญลักษณ์ DEBUG ไม่ได้ถูกกำหนดไว้ในกรณีแรกSetPrivateValueตัวเองจะไม่ถูกเรียก ... ในกรณีที่สองมันจะมีอยู่ แต่ผู้โทรใด ๆที่ถูกคอมไพล์โดยไม่มีสัญลักษณ์ DEBUG จะถูกตัดการโทรเหล่านั้น

ถ้ารหัสและทุกสายที่มีในตัวเดียวกันการชุมนุมแตกต่างนี้เป็นน้อยที่สำคัญ - แต่มันหมายความว่าในกรณีแรกที่คุณยังจะต้องมี#if DEBUGรอบการเรียกรหัสเป็นอย่างดี

โดยส่วนตัวผมขอแนะนำวิธีที่สอง - แต่คุณต้องรักษาความแตกต่างระหว่างพวกเขาในหัวของคุณ


5
+1 สำหรับรหัสการโทรจะต้องมีคำสั่ง #if ด้วย ซึ่งหมายความว่าจะมีการแพร่กระจายของ #if งบ ...
ลูคัสบี

ในขณะที่ตัวเลือกที่สอง (แอตทริบิวต์แบบมีเงื่อนไข) นั้นดีกว่าและสะอาดกว่าในบางกรณีมันอาจจำเป็นต้องสื่อสารความจริงที่ว่าการเรียกเมธอดจะถูกถอดจากชุดประกอบระหว่างการคอมไพล์ (เช่นแบบแผนการตั้งชื่อเป็นต้น)
กรด lysergic

45

ฉันแน่ใจว่ามากมายจะไม่เห็นด้วยกับฉัน แต่มีการใช้เวลาเป็นคนสร้างได้ยินตลอดเวลา "แต่มันทำงานบนเครื่องของฉัน!" ฉันใช้จุดยืนที่คุณไม่ควรใช้อย่างใดอย่างหนึ่ง หากคุณต้องการบางสิ่งบางอย่างสำหรับการทดสอบและการดีบักหาวิธีทำให้การทดสอบนั้นแยกจากรหัสการผลิตจริง

สรุปสถานการณ์ที่มีการจำลองในการทดสอบหน่วยให้ทำสิ่งหนึ่งอย่างสำหรับสถานการณ์ที่คุณต้องการทดสอบ แต่ไม่ได้ทำการทดสอบเพื่อดีบักลงในรหัสสำหรับไบนารีที่คุณทดสอบและเขียนเพื่อการผลิต การทดสอบการแก้ไขข้อบกพร่องเหล่านี้เพียงซ่อนข้อบกพร่องที่เป็นไปได้จาก devs ดังนั้นพวกเขาจะไม่พบจนกระทั่งในภายหลังกระบวนการ


4
ฉันเห็นด้วยกับคุณ Jimmy หากคุณใช้ DI และการเยาะเย้ยสำหรับการทดสอบของคุณทำไมคุณจะต้อง#if debugหรือโครงสร้างที่คล้ายกันในรหัสของคุณ?
Richard Ev

@RichardEv อาจเป็นวิธีที่ดีกว่าในการจัดการกับสิ่งนี้ แต่ขณะนี้ฉันกำลังใช้เพื่ออนุญาตให้ตัวเองเล่นเป็นส่วนหนึ่งของผู้ใช้ที่แตกต่างกันผ่านสตริงการสืบค้น ฉันไม่ต้องการสิ่งนี้ในการผลิต แต่ฉันต้องการมันเพื่อการดีบั๊กเพื่อให้ฉันสามารถควบคุมเวิร์กโฟลว์ที่ก้าวผ่านโดยไม่ต้องสร้างผู้ใช้หลายคนและลงชื่อเข้าใช้บัญชีทั้งสองเพื่อผ่านขั้นตอนนี้ แม้ว่านี่จะเป็นครั้งแรกที่ฉันต้องใช้จริง
Tony

4
แทนที่จะทำการทดสอบเรามักจะทำสิ่งต่าง ๆ เช่นตั้งค่าอีเมลผู้รับเริ่มต้นให้กับตัวเองในการตรวจแก้จุดบกพร่องสร้างโดยใช้#if DEBUGเพื่อไม่ให้ผู้อื่นสแปมโดยไม่ตั้งใจขณะทดสอบระบบที่ต้องส่งอีเมลเป็นส่วนหนึ่งของกระบวนการ บางครั้งเหล่านี้เป็นเครื่องมือที่เหมาะสมสำหรับงาน :)
Gone Coding

6
โดยทั่วไปฉันจะเห็นด้วยกับคุณ แต่ถ้าคุณอยู่ในสถานการณ์ที่ประสิทธิภาพเป็นสิ่งสำคัญคุณไม่ต้องการถ่วงรหัสด้วยการบันทึกภายนอกและผลผู้ใช้ แต่ฉันทำ 100% ยอมรับว่าไม่ควรใช้เพื่อแก้ไข พฤติกรรมพื้นฐาน
MikeT

5
-1 ไม่มีอะไรผิดปกติเมื่อใช้อย่างใดอย่างหนึ่งเหล่านี้ การอ้างสิทธิ์การทดสอบหน่วยและ DI แทนการสร้างการเปิดใช้งานการดีบักของผลิตภัณฑ์จะไร้เดียงสา
Ted Bigham

15

อันนี้มีประโยชน์เช่นกัน:

if (Debugger.IsAttached)
{
...
}

1
โดยส่วนตัวแล้วฉันไม่เห็นว่าสิ่งนี้มีประโยชน์อย่างไรเมื่อเปรียบเทียบกับอีกสองทางเลือก สิ่งนี้รับประกันว่าบล็อกทั้งหมดจะถูกคอมไพล์และDebugger.IsAttachedจะต้องถูกเรียกใช้ที่รันไทม์แม้ในรีลีส builds
ใจ

9

ด้วยตัวอย่างแรกSetPrivateValueจะไม่มีอยู่ในบิลด์หากDEBUGไม่ได้กำหนดไว้พร้อมกับตัวอย่างที่สองการเรียกร้องให้SetPrivateValueไม่มีอยู่ในบิลด์หากDEBUGไม่ได้กำหนดไว้

ด้วยตัวอย่างแรกคุณจะต้องตัดสายSetPrivateValueด้วย#if DEBUGเช่นกัน

ด้วยตัวอย่างที่สองการโทรที่SetPrivateValueจะถูกตัดออกไป แต่ต้องระวังว่าSetPrivateValueตัวเองจะยังคงถูกคอมไพล์ สิ่งนี้มีประโยชน์หากคุณกำลังสร้างห้องสมุดดังนั้นแอปพลิเคชันที่อ้างอิงห้องสมุดของคุณยังสามารถใช้ฟังก์ชันของคุณได้ (หากตรงตามเงื่อนไข)

หากคุณไม่ต้องการใช้สายและประหยัดพื้นที่ของผู้รับสายคุณสามารถใช้เทคนิคทั้งสองนี้ร่วมกัน:

[System.Diagnostics.Conditional("DEBUG")]
public void SetPrivateValue(int value){
    #if DEBUG
    // method body here
    #endif
}

@P Daddy: การ#if DEBUGล้อมรอบConditional("DEBUG")ไม่ได้ลบการเรียกไปยังฟังก์ชันนั้นเพียงแค่ลบฟังก์ชั่นออกจาก IL ทั้งหมดดังนั้นคุณยังคงมีการเรียกไปยังฟังก์ชันที่ไม่มีอยู่ (ข้อผิดพลาดในการรวบรวม)
ของฉัน

1
หากไม่มีใครต้องการรหัสที่มีอยู่ในรุ่นหนึ่งควรห่อร่างกายวิธีการใน "#if DEBUG" อาจเป็นด้วย "#else" ต้นขั้ว (ด้วยค่าส่งกลับหรือโยนจำลอง) และใช้คุณลักษณะเพื่อแนะนำ ผู้โทรที่ไม่รบกวนการโทร? นั่นจะเป็นสิ่งที่ดีที่สุดของทั้งสองโลก
supercat

@myermian, @supercat: ใช่คุณพูดถูก ความผิดพลาดของฉัน. ฉันจะแก้ไขตามคำแนะนำของ supercat
Paddy

5

สมมติว่าโค้ดของคุณมี#elseข้อความที่กำหนดฟังก์ชั่น null stub โดยระบุหนึ่งในจุดของ Jon Skeet มีความแตกต่างที่สำคัญที่สองระหว่างทั้งสอง

สมมติว่าฟังก์ชัน#if DEBUGหรือConditionalมีอยู่ใน DLL ซึ่งอ้างอิงโดยปฏิบัติการหลักของโครงการของคุณ การใช้การ#ifประเมินผลตามเงื่อนไขจะดำเนินการโดยคำนึงถึงการตั้งค่าการรวบรวมของห้องสมุด การใช้Conditionalแอ็ตทริบิวต์การประเมินเงื่อนไขจะดำเนินการตามการตั้งค่าการรวบรวมของผู้เรียกใช้


2

ฉันมีนามสกุล SOAP [TraceExtension]เว็บเซอร์เพื่อเข้าสู่ระบบเครือข่ายการจราจรโดยใช้กำหนดเอง ฉันใช้สิ่งนี้เฉพาะกับDebug builds และละเว้นจากRelease builds ใช้การ#if DEBUGเพื่อล้อมรอบ[TraceExtension]แอ็ตทริบิวต์เพื่อลบออกจากRelease builds

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

0

โดยปกติแล้วคุณจะต้องใช้มันใน Program.cs ที่คุณต้องการเรียกใช้ Debug บนรหัสที่ไม่ใช่ Debug และส่วนใหญ่ใน Windows Services ดังนั้นฉันจึงสร้าง IsDebugMode แบบอ่านอย่างเดียวและตั้งค่าในตัวสร้างสแตติกตามที่แสดงด้านล่าง

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        
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.