การประเมินการลัดวงจรมันเป็นการปฏิบัติที่ไม่ดีเหรอ?


88

สิ่งที่ฉันรู้จักมาระยะหนึ่ง แต่ไม่เคยพิจารณามาก่อนคือภาษาส่วนใหญ่มีความเป็นไปได้ที่จะให้ความสำคัญกับผู้ประกอบการในคำสั่ง if ตามคำสั่งของพวกเขา ฉันมักจะใช้สิ่งนี้เป็นวิธีในการป้องกันข้อยกเว้นอ้างอิงเป็นโมฆะเช่น:

if (smartphone != null && smartphone.GetSignal() > 50)
{
   // Do stuff
}

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

สิ่งนี้มีประโยชน์ในสถานการณ์อื่น ๆ เช่นการตรวจสอบว่าดัชนีอยู่ในขอบเขตของอาเรย์และเทคนิคดังกล่าวสามารถทำได้ในหลายภาษาตามความรู้ของฉัน: Java, C #, C ++, Python และ Matlab

คำถามของฉันคือ: รหัสประเภทนี้เป็นตัวแทนของการปฏิบัติที่ไม่ดีหรือไม่? การปฏิบัติที่ไม่ดีนี้เกิดขึ้นจากปัญหาทางเทคนิคที่ซ่อนอยู่หรือไม่ (เช่นนี้อาจส่งผลให้เกิดข้อผิดพลาด) หรือนำไปสู่ปัญหาความสามารถในการอ่านสำหรับโปรแกรมเมอร์อื่น ๆ หรือไม่? มันอาจสร้างความสับสนได้ไหม


69
ระยะทางเทคนิคคือการประเมินผลการลัดวงจร นี่เป็นเทคนิคการติดตั้งที่เป็นที่รู้จักและมาตรฐานของภาษารับประกันได้ว่าการใช้มันเป็นสิ่งที่ดี (หากไม่เป็นเช่นนั้นมันไม่ใช่ภาษา IMO)
Kilian Foth

3
ภาษาส่วนใหญ่ให้คุณเลือกพฤติกรรมที่คุณต้องการ ใน C # ผู้ปฏิบัติ||งานลัดวงจรตัวดำเนินการ|(ไปป์เดียว) ไม่ได้ ( &&และ&ทำงานเหมือนกัน) เนื่องจากตัวดำเนินการลัดวงจรถูกใช้บ่อยกว่ามากฉันแนะนำให้ทำเครื่องหมายการใช้งานของคนที่ไม่ลัดวงจรพร้อมกับข้อคิดเห็นเพื่ออธิบายว่าทำไมคุณถึงต้องการพฤติกรรมของพวกเขา
linac

14
@linac ตอนนี้วางบางอย่างที่ด้านขวามือของ&หรือ|เพื่อให้แน่ใจว่าผลข้างเคียงที่เกิดขึ้นคือสิ่งที่ฉันพูดแน่นอนการปฏิบัติที่ไม่ดี: มันจะไม่เสียค่าใช้จ่ายใด ๆ ที่คุณจะใส่วิธีการโทรในบรรทัดของตัวเอง
Jon Hanna

14
@linac: ใน C และ C ++ &&เป็นไฟฟ้าลัดวงจรและ&ไม่ใช่ แต่เป็นตัวดำเนินการที่แตกต่างกันมาก &&เป็นตรรกะ "และ"; &คือ bitwise "และ" เป็นไปได้ที่x && yจะเป็นจริงในขณะที่x & yเป็นเท็จ
Keith Thompson

3
ตัวอย่างที่เฉพาะเจาะจงของคุณอาจเป็นการฝึกฝนที่ไม่ดีด้วยเหตุผลอื่น: ถ้าเป็นโมฆะ มันสมเหตุสมผลที่จะเป็นโมฆะที่นี่หรือไม่? ทำไมมันถึงเป็นโมฆะ? ส่วนที่เหลือของฟังก์ชั่นนอกบล็อกนี้จะทำงานหากเป็นโมฆะสิ่งทั้งหมดควรทำอะไรหรือโปรแกรมของคุณควรหยุด การใส่กระดาษ "ถ้าเป็นโมฆะ" ในทุกการเข้าถึง (อาจเป็นเพราะข้อยกเว้นการอ้างอิงเป็นโมฆะที่คุณต้องรีบกำจัด) หมายความว่าคุณสามารถเข้าสู่ส่วนที่เหลือของโปรแกรมได้ค่อนข้างไกลโดยไม่สังเกตเห็นว่า ขึ้น และในที่สุดเมื่อคุณจนได้รับการบิตของรหัสที่ไม่ได้
Random832

คำตอบ:


125

ไม่นี่ไม่ใช่การฝึกฝนที่ไม่ดี การใช้การลัดวงจรของเงื่อนไขเป็นเทคนิคที่มีประโยชน์และได้รับการยอมรับอย่างกว้างขวางตราบใดที่คุณใช้ภาษาที่รับประกันพฤติกรรมนี้ (ซึ่งรวมถึงภาษาสมัยใหม่ส่วนใหญ่ด้วย)

ตัวอย่างรหัสของคุณค่อนข้างชัดเจนและแน่นอนว่าเป็นวิธีที่ดีที่สุดในการเขียน ทางเลือกอื่น (เช่นifข้อความซ้อน) จะมีความซับซ้อนมากขึ้นและยากที่จะเข้าใจ

คำเตือนสองสามข้อ:

ใช้สามัญสำนึกเกี่ยวกับความซับซ้อนและความสามารถในการอ่าน

ต่อไปนี้ไม่ใช่ความคิดที่ดี:

if ((text_string != null && replace(text_string,"$")) || (text_string = get_new_string()) != null)

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

หากคุณต้องการจัดการข้อผิดพลาดมักจะมีวิธีที่ดีกว่า

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

หลีกเลี่ยงการตรวจสอบซ้ำในสภาพเดียวกัน

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

นี่ไม่ได้เกี่ยวกับการลัดวงจรโดยเฉพาะ มันเป็นปัญหาทั่วไปของรหัสซ้ำ ๆ อย่างไรก็ตามมันเป็นสิ่งที่ควรค่าแก่การกล่าวถึงเนื่องจากเป็นเรื่องปกติที่จะเห็นโค้ดที่มีคำสั่งซ้ำหลายรายการเช่นคำสั่งตัวอย่างของคุณ


12
ควรสังเกตว่าทำซ้ำหากบล็อกการตรวจสอบว่าอินสแตนซ์เป็นโมฆะในการประเมินผลการลัดวงจรควรจะเขียนใหม่เพื่อดำเนินการตรวจสอบนั้นเพียงครั้งเดียว
Neil

1
@ Neil จุดดี; ฉันอัพเดตคำตอบแล้ว

2
สิ่งเดียวที่ฉันคิดว่าเพิ่มคือการจดบันทึกทางเลือกอื่นในการจัดการซึ่งเป็นที่ต้องการจริง ๆ ถ้าคุณกำลังจะตรวจสอบบางสิ่งในสภาพที่แตกต่างกัน: ตรวจสอบเพื่อดูว่าบางสิ่งบางอย่างเป็นโมฆะใน if () จากนั้น return / throw - ไม่เช่นนั้นก็ไปต่อ สิ่งนี้จะช่วยลดการซ้อนและมักจะทำให้รหัสชัดเจนขึ้นและสั้นลงในขณะที่ "นี่ไม่ควรเป็นโมฆะและถ้าเป็นเช่นนั้นฉันก็ออกจากที่นี่" - วางไว้ให้เร็วที่สุดในรหัสอย่างรอบคอบที่สุดเท่าที่จะทำได้ ยอดเยี่ยมทุกเวลาที่คุณเขียนโค้ดบางอย่างที่ตรวจสอบเงื่อนไขเฉพาะมากกว่าในที่เดียวในวิธีการ
BrianH

1
@ dan1111 ฉันจะสรุปตัวอย่างที่คุณให้ไว้ว่า "เงื่อนไขไม่ควรมีผลข้างเคียง (เช่นการกำหนดตัวแปรด้านบน) การประเมินการลัดวงจรไม่เปลี่ยนแปลงสิ่งนี้และไม่ควรใช้เป็นทางลัดสำหรับด้านข้าง - เอฟเฟ็กต์ที่อาจมีอยู่ในร่างกายของ if เพื่อให้เป็นตัวแทนของ if / then
AaronLS

@ AaronLS ฉันไม่เห็นด้วยอย่างยิ่งกับการกล่าวอ้างว่า "เงื่อนไขไม่ควรมีผลข้างเคียง" เงื่อนไขไม่ควรสับสนโดยไม่จำเป็น แต่ตราบใดที่มันเป็นรหัสที่ชัดเจนฉันก็สบายดีกับผลข้างเคียง (หรือมากกว่าหนึ่งผลข้างเคียง) ในสภาพ

26

สมมติว่าคุณกำลังใช้ภาษาแบบ C ที่ไม่มี&&และจำเป็นต้องทำโค้ดเทียบเท่าในคำถามของคุณ

รหัสของคุณจะเป็น:

if(smartphone != null)
{
  if(smartphone.GetSignal() > 50)
  {
    // Do stuff
  }
}

รูปแบบนี้จะเปิดขึ้นมาก

ตอนนี้คิดว่ารุ่น 2.0 &&เปิดตัวภาษาสมมุติของเรา คิดว่าเจ๋งแค่ไหนที่คุณคิด!

&&เป็นวิธีที่ได้รับการยอมรับและใช้สำนวนในการทำสิ่งที่ตัวอย่างด้านบนของฉันทำ มันไม่ใช่การฝึกฝนที่ไม่ดีที่จะใช้มันแน่นอนว่าเป็นการฝึกฝนที่ไม่ดีที่จะไม่ใช้มันในกรณีดังกล่าวข้างต้น: ผู้เขียนโค้ดที่มีประสบการณ์ในภาษาดังกล่าวจะสงสัยว่าelseสาเหตุหรือเหตุผลอื่นใดที่ไม่ทำสิ่งต่าง ๆ ตามปกติ

ภาษานั้นฉลาดเพราะรู้ว่าถ้าประโยคแรกเป็นเท็จถ้าไม่มีก็จะไม่มีการประเมินแม้แต่ประโยคที่สองดังนั้นจึงไม่มีข้อยกเว้นการอ้างอิงเป็นโมฆะ

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

ความแตกต่างระหว่างคุณเป็นคนฉลาดและภาษาเป็นคนฉลาดมีความสำคัญเพราะการฝึกฝนที่ดีและไม่ดีมักจะทำให้คุณฉลาดเท่าที่จำเป็น แต่ไม่ฉลาดอีกต่อไป

พิจารณา:

if(smartphone != null && smartphone.GetSignal() > ((range = (range != null ? range : GetRange()) != null && range.Valid ? range.MinSignal : 50)

rangeเรื่องนี้ต้องขยายรหัสของคุณเพื่อตรวจสอบ หากrangeเป็นโมฆะก็พยายามที่จะตั้งค่าโดยการโทรถึงGetRange()แม้ว่ามันอาจล้มเหลวดังนั้นrangeอาจยังคงเป็นโมฆะ ถ้าช่วงไม่ null หลังจากนี้และมันก็เป็นValidแล้วของMinSignalสถานที่ให้บริการจะใช้มิฉะนั้นเริ่มต้นของการ50ถูกนำมาใช้

สิ่งนี้ขึ้นอยู่กับ&&แต่การใส่ไว้ในหนึ่งบรรทัดอาจจะฉลาดเกินไป (ฉันไม่แน่ใจ 100% ว่าถูกต้องแล้วและฉันจะไม่ตรวจสอบอีกครั้งเพราะข้อเท็จจริงนั้นแสดงให้เห็นถึงจุดของฉัน)

แม้ว่าจะไม่ใช่&&ปัญหาที่นี่ แต่ความสามารถในการใช้จำนวนมากในการแสดงออกหนึ่ง (เป็นสิ่งที่ดี) เพิ่มความสามารถของเราในการเขียนการแสดงออกที่เข้าใจยากโดยไม่จำเป็น (เป็นสิ่งที่ไม่ดี)

นอกจากนี้:

if(smartphone != null && (smartphone.GetSignal() == 2 || smartphone.GetSignal() == 5 || smartphone.GetSignal() == 8 || smartPhone.GetSignal() == 34))
{
  // do something
}

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

if(smartphone != null)
{
  switch (smartphone.GetSignal())
  {
    case 2: case 5: case 8: case 34:
      // Do something
      break;
  }
}

ฉันได้รับทั้งความสามารถในการอ่านและประสิทธิภาพในการใช้งาน (การโทรหลายครั้งไปยังGetSignal()อาจไม่ได้รับการปรับให้เหมาะสม)

ที่นี่อีกครั้งปัญหาไม่มาก&&เท่ากับการใช้ค้อนนั้นและเห็นทุกอย่างเป็นเล็บ ไม่ใช้มันให้ฉันทำสิ่งที่ดีกว่าใช้มันเลย

กรณีสุดท้ายที่ย้ายออกจากแนวปฏิบัติที่ดีที่สุดคือ:

if(a && b)
{
  //do something
}

เปรียบเทียบกับ:

if(a & b)
{
  //do something
}

ข้อโต้แย้งคลาสสิกว่าทำไมเราถึงชอบหลังคือมีผลข้างเคียงบางอย่างในการประเมินbว่าเราต้องการไม่ว่าจะaเป็นจริงหรือไม่ ฉันไม่เห็นด้วยกับสิ่งนั้น: หากผลข้างเคียงนั้นมีความสำคัญดังนั้นให้เกิดขึ้นในบรรทัดของรหัสแยกต่างหาก

อย่างไรก็ตามในแง่ของประสิทธิภาพทั้งสองอย่างนั้นน่าจะดีกว่า ครั้งแรกที่เห็นได้ชัดจะรันโค้ดน้อย (ไม่ได้ประเมินbในเส้นทางรหัสทั้งหมดในหนึ่ง) bซึ่งสามารถช่วยเราสิ่งที่ปริมาณของเวลาที่ใช้ในการประเมิน

ที่แรกก็มีอีกสาขาหนึ่ง ลองพิจารณาว่าเราเขียนมันซ้ำในภาษา C-style สมมุติโดยไม่มี&&:

if(a)
{
  if(b)
  {
    // Do something
  }
}

พิเศษifนั้นถูกซ่อนอยู่ในการใช้งานของเรา&&แต่ก็ยังมีอยู่ และเป็นกรณีที่มีการคาดคะเนสาขาเกิดขึ้น

ด้วยเหตุนี้if(a & b)รหัสอาจมีประสิทธิภาพมากกว่าในบางกรณี

ที่นี่ฉันจะบอกว่าif(a && b)ยังคงเป็นวิธีปฏิบัติที่ดีที่สุดที่จะเริ่มต้นด้วย: ทั่วไปมากขึ้นเพียงคนเดียวที่ทำงานได้ในบางกรณี (ที่bจะผิดพลาดถ้าaเป็นเท็จ) และเร็วขึ้นบ่อยกว่าไม่ เป็นที่น่าสังเกตว่าif(a & b)การเพิ่มประสิทธิภาพนั้นมีประโยชน์ในบางกรณี


1
หากมีtryวิธีการที่ส่งคืนtrueในกรณีที่ประสบความสำเร็จคุณคิดif (TryThis || TryThat) { success } else { failure }อย่างไร มันเป็นสำนวนคอมมอนที่น้อยกว่า แต่ฉันไม่รู้ว่าจะทำสิ่งเดียวกันให้สำเร็จได้อย่างไรโดยไม่ต้องทำซ้ำโค้ดหรือเพิ่มแฟล็ก ในขณะที่หน่วยความจำที่จำเป็นสำหรับธงจะไม่ใช่ปัญหาจากมุมมองการวิเคราะห์เพิ่มธงได้อย่างมีประสิทธิภาพแยกออกเป็นสองรหัสจักรวาลคู่ขนาน - หนึ่งที่มีงบที่สามารถเข้าถึงได้เมื่อธงtrueและอื่น ๆ falseหนึ่งในงบสามารถเข้าถึงได้เมื่อมัน บางครั้งก็ดีจากมุมมองแบบแห้ง แต่น่ากลัว
supercat

5
if(TryThis || TryThat)ฉันเห็นอะไรผิดปกติกับ
Jon Hanna

3
คำตอบที่ดีครับ
บิชอป

FWIW โปรแกรมเมอร์ที่ยอดเยี่ยมรวมถึง Kernighan, Plauger & Pike จาก Bell Labs แนะนำเพื่อความชัดเจนในการใช้โครงสร้างการควบคุมแบบตื้นเช่นif {} else if {} else if {} else {}แทนที่จะเป็นโครงสร้างการควบคุมแบบซ้อนif { if {} else {} } else {}แม้ว่าจะหมายถึงการประเมินการแสดงออกที่เหมือนกันมากกว่าหนึ่งครั้ง Linus Torvalds พูดอะไรบางอย่างที่คล้ายกัน: "ถ้าคุณต้องการการเยื้องมากกว่า 3 ระดับคุณก็จะถูกเมา" ลองมาดูว่าเป็นการลงคะแนนให้กับผู้ประกอบการลัดวงจร
Sam Watkins

หากคำสั่ง (&&b); เป็นเรื่องง่ายที่จะเปลี่ยน ถ้า (&& b &&&&&&)) statement1; คำสั่งอื่น 2; เป็นความเจ็บปวดที่แท้จริง
gnasher729

10

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

open($handle, "<", "filename.txt") or die "Couldn't open file!";

เป็นสำนวน openส่งกลับบางสิ่งบางอย่างที่ไม่ใช่ศูนย์ในความสำเร็จ (เพื่อให้dieส่วนหลังจากorไม่เคยเกิดขึ้น) แต่ไม่ได้กำหนดในความล้มเหลวและจากนั้นการเรียกร้องให้dieเกิดขึ้น (นั่นคือวิธีของ Perl เพื่อยกข้อยกเว้น)

นั่นเป็นภาษาที่ทุกคนทำได้ดี


17
ผลงานที่ยิ่งใหญ่ที่สุดของ Perl เพื่อการออกแบบภาษาคือการให้หลายตัวอย่างของวิธีการไม่ได้ที่จะทำมัน
Jack Aidley

@ JackAidley: ฉันคิดถึง Perl's unlessและเงื่อนไข postfix ( send_mail($address) if defined $address) แม้ว่า
RemcoGerlich

6
@ JackAidley คุณสามารถให้ตัวชี้ว่าทำไม 'หรือตาย' ไม่ดี? มันเป็นเพียงวิธีการจัดการข้อยกเว้นที่ไร้เดียงสาหรือไม่?
bigstones

เป็นไปได้ที่จะทำสิ่งต่าง ๆ ที่น่ากลัวใน Perl แต่ฉันไม่เห็นปัญหากับตัวอย่างนี้ มันง่ายสั้นและชัดเจน - สิ่งที่คุณต้องการจากการจัดการข้อผิดพลาดในภาษาสคริปต์

1
@RemcoGerlich Ruby สืบทอดบางส่วนของสไตล์นั้น:send_mail(address) if address
58

6

ในขณะที่ฉันเห็นด้วยกับคำตอบของ dan1111มีกรณีที่สำคัญอย่างหนึ่งที่ไม่ครอบคลุม: กรณีที่smartphoneมีการใช้พร้อมกัน ในสถานการณ์นั้นรูปแบบประเภทนี้เป็นแหล่งที่รู้จักกันดีในการค้นหาบั๊ก

ปัญหาคือว่าการประเมินการลัดวงจรไม่ได้เป็นอะตอม เธรดของคุณสามารถตรวจสอบได้นั่นsmartphoneคือnullเธรดอื่นสามารถเข้ามาและทำให้เป็นโมฆะและจากนั้นเธรดของคุณจะพยายามGetSignal()และระเบิดทันที

แต่แน่นอนมันไม่ว่าเมื่อดาวจัดและระยะเวลาในกระทู้ของคุณเป็นเพียงขวา

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


3

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

ดังนั้นจึงมีความจำเป็นอย่างมากสำหรับการประเมินการลัดวงจรของนิพจน์เชิงตรรกะ มันไม่ได้อยู่ใน Pascal (ซึ่งไม่มีการประเมินการลัดวงจรที่ไม่รับประกัน) ซึ่งเป็นอาการปวดที่สำคัญ ฉันเห็นเป็นครั้งแรกใน Ada และ C

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


"เขียนใหม่เพื่อที่จะทำงานได้ดีโดยไม่มีการประเมินการลัดวงจรและกลับมาและบอกเราว่าคุณชอบมันอย่างไร" - ใช่สิ่งนี้นำความทรงจำกลับมาของ Pascal และไม่ฉันไม่ชอบมัน
โทมัส Padron-McCarthy

2

การพิจารณาอย่างใดอย่างหนึ่งซึ่งไม่ได้กล่าวถึงในคำตอบอื่น ๆ : บางครั้งการตรวจสอบเหล่านี้สามารถบอกใบ้ถึงการปรับโครงสร้างที่เป็นไปได้ให้กับรูปแบบการออกแบบNull Object ตัวอย่างเช่น:

if (currentUser && currentUser.isAdministrator()) 
  doSomething();

สามารถทำให้ง่ายขึ้นเพื่อเป็น:

if (currentUser.isAdministrator())
  doSomething ();

หากcurrentUserมีการตั้งค่าเริ่มต้นเป็น 'ผู้ใช้ที่ไม่ระบุชื่อ' หรือ 'ผู้ใช้ที่เป็นโมฆะ' ด้วยการใช้ทางเลือกหากผู้ใช้ไม่ได้เข้าสู่ระบบ

รหัสกลิ่นไม่เสมอแต่สิ่งที่ต้องพิจารณา


-4

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

กล่าวคือ

 if(quickLogicA && slowLogicB) {doSomething();}

เป็นสิ่งที่ดี แต่

if(logicA && doSomething()) {andAlsoDoSomethingElse();}

ไม่ดีและแน่นอนไม่มีใครจะเห็นด้วย!

if(doSomething() || somethingElseIfItFailed() && anotherThingIfEitherWorked() & andAlwaysThisThing())
{/* do nothing */}

เหตุผล:

รหัสของคุณมีข้อผิดพลาดหากทั้งสองฝ่ายได้รับการประเมินมากกว่าที่จะไม่ดำเนินการตามตรรกะ มันเป็นที่ถกเถียงกันอยู่ว่านี่ไม่ใช่ปัญหาตัวดำเนินการ 'และ' ถูกกำหนดใน c # ดังนั้นความหมายจึงชัดเจน อย่างไรก็ตามฉันขอยืนยันว่านี่ไม่เป็นความจริง

เหตุผล 1. ประวัติศาสตร์

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

แม้ว่าใน c # ข้อกำหนดทางภาษารับประกันบูลีน 'และ' โอเปอเรเตอร์จะลัดวงจร สิ่งนี้ไม่เป็นความจริงสำหรับทุกภาษาและคอมไพเลอร์

wikipeda แสดงรายการภาษาต่าง ๆ และใช้ในการลัดวงจรhttp://en.wikipedia.org/wiki/Short-circuit_evaluationในขณะที่คุณมีวิธีการมากมาย และปัญหาพิเศษอีกสองข้อที่ฉันไม่ได้เข้าไปที่นี่

โดยเฉพาะอย่างยิ่ง FORTRAN, Pascal และ Delphi มีธงคอมไพเลอร์ซึ่งสลับพฤติกรรมนี้

ดังนั้น: คุณอาจพบว่าโค้ดของคุณทำงานเมื่อคอมไพล์สำหรับรีลีส แต่ไม่ใช่สำหรับดีบั๊กหรือคอมไพเลอร์สำรองเป็นต้น

เหตุผลที่ 2. ความแตกต่างของภาษา

อย่างที่คุณเห็นจากวิกิพีเดียไม่ใช่ทุกภาษาใช้วิธีเดียวกันกับการลัดวงจรแม้ว่าพวกเขาจะระบุว่าโอเปอเรเตอร์บูลีนควรจัดการกับมันอย่างไร

โดยเฉพาะอย่างยิ่งตัวดำเนินการ VB.Net 'และ' ไม่ลัดวงจรและ TSQL มีลักษณะเฉพาะบางอย่าง

ระบุว่า 'โซลูชั่น' ที่ทันสมัยมีแนวโน้มที่จะรวมหลายภาษาเช่น javascript, regex, xpath ฯลฯ คุณควรคำนึงถึงเรื่องนี้เมื่อทำการใช้งานโค้ดของคุณชัดเจน

เหตุผลที่ 3. ความแตกต่างภายในภาษา

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

เหตุผลที่ 4. ตัวอย่างเฉพาะของคุณเป็นโมฆะตรวจสอบพารามิเตอร์ของฟังก์ชัน

นี่เป็นตัวอย่างที่ดีมาก มันเป็นสิ่งที่ทุกคนทำ แต่จริง ๆ แล้วมันเป็นการปฏิบัติที่ไม่ดีเมื่อคุณคิดถึงมัน

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

อย่างไรก็ตามมันล้มเหลวแนวปฏิบัติที่ดีที่สุดด้วยเหตุผลมากกว่าหนึ่งข้อ!

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

    if (smartphone == null)
    {
        //throw an exception
    }
    // perform functionality
  2. คุณกำลังเรียกฟังก์ชั่นจากภายในคำสั่งตามเงื่อนไข แม้ว่าชื่อฟังก์ชั่นของคุณจะบอกเป็นนัยว่ามันคำนวณเพียงค่า แต่สามารถซ่อนผลข้างเคียงได้ เช่น

    Smartphone.GetSignal() { this.signal++; return this.signal; }
    
    else if (smartphone.GetSignal() < 1)
    {
        // Do stuff 
    }
    else if (smartphone.GetSignal() < 2)
    {
        // Do stuff 
    }

    แนวทางปฏิบัติที่ดีที่สุดจะแนะนำ:

    var s = smartphone.GetSignal();
    if(s < 50)
    {
        //do something
    }

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

ฉันคิดว่าคุณจะพบว่านี่เป็นกรณีสำหรับประเพณีทั่วไปอื่น ๆ คุณต้องจำไว้ว่าเรายังมี:

เงื่อนไขแบบอินไลน์

var s = smartphone!=null ? smartphone.GetSignal() : 0;

และการรวมกันเป็นโมฆะ

var s = smartphoneConnectionStatus ?? "No Connection";

ซึ่งมีฟังก์ชั่นความยาวรหัสสั้นที่คล้ายกันและชัดเจนยิ่งขึ้น

ลิงค์จำนวนมากเพื่อสนับสนุนคะแนนทั้งหมดของฉัน:

https://stackoverflow.com/questions/1158614/what-is-the-best-practice-in-case-one-argument-is-null

การตรวจสอบพารามิเตอร์ Constructor ใน C # - แนวปฏิบัติที่ดีที่สุด

หนึ่งควรตรวจสอบโมฆะถ้าเขาไม่คาดหวังโมฆะ?

https://msdn.microsoft.com/en-us/library/seyhszts%28v=vs.110%29.aspx

https://stackoverflow.com/questions/1445867/why-would-a-language-not-use-short-circuit-evaluation

http://orcharddojo.net/orchard-resources/Library/DevelopmentGuidelines/BestPractices/CSharp

ตัวอย่างของธงคอมไพเลอร์ที่มีผลต่อการลัดวงจร:

Fortran: "sce | nosce โดยค่าเริ่มต้นคอมไพเลอร์ทำการประเมินการลัดวงจรในการแสดงออกทางตรรกะที่เลือกโดยใช้กฎ XL Fortran การระบุ sce ช่วยให้คอมไพเลอร์ใช้กฎที่ไม่ใช่ XL Fortran คอมไพเลอร์จะทำการประเมินลัดวงจรหากกฎปัจจุบันอนุญาต ค่าเริ่มต้นคือ nosce "

Pascal: "B - คำสั่งแฟล็กที่ควบคุมการประเมินการลัดวงจรดูลำดับของการประเมินผลการดำเนินการของตัวดำเนินการ dyadic สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการประเมินการลัดวงจรดูตัวเลือกคอมไพเลอร์ -sc สำหรับคอมไพเลอร์บรรทัดคำสั่งสำหรับข้อมูลเพิ่มเติม ( บันทึกไว้ในคู่มือผู้ใช้) "

Delphi: "Delphi รองรับการประเมินการลัดวงจรตามค่าเริ่มต้นสามารถปิดได้โดยใช้คำสั่งคอมไพเลอร์ {$ BOOLEVAL OFF}"


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