แนวปฏิบัติที่ดีที่สุด - การห่อถ้ารอบ ๆ การเรียกใช้ฟังก์ชัน vs การเพิ่มการออกก่อนหน้าถ้าการป้องกันในการทำงาน


9

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

ฉันไม่ได้ถามว่าอะไรคือวิธีที่ดีที่สุดเมื่ออยู่ในฟังก์ชั่นฉันกำลังถามว่าฉันควรออกจากเร็วหรือควรจะไม่เรียกฟังก์ชั่นนี้

Wrap ถ้าเรียกฟังก์ชันรอบ


if (shouldThisRun) {
  runFunction();
}

มีฟังก์ชั่นif ( guard )

runFunction() {
  if (!shouldThisRun) return;
}

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


นี่เป็นตัวอย่าง

ถ้าฉันมีฟังก์ชั่น updateStatus () ที่จะอัพเดทสถานะของบางสิ่ง ฉันต้องการเฉพาะสถานะที่อัปเดตหากมีการเปลี่ยนแปลงสถานะ ฉันรู้ว่าสถานที่ในรหัสของฉันที่สถานะมีศักยภาพในการเปลี่ยนแปลงและฉันรู้ว่าสถานที่อื่น ๆ ที่มีการเปลี่ยนแปลงท้าทาย

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



3
@gnat ไม่คำถามนั้นเป็นหลัก 'สิ่งที่เป็นไวยากรณ์ที่ต้องการในการออกก่อน' ในขณะที่ของฉันคือ 'ฉันควรออกก่อนหรือฉันไม่ควรเรียกฟังก์ชั่น'
Matthew Mullin

4
คุณไม่สามารถไว้วางใจนักพัฒนาซอฟต์แวร์แม้แต่ตัวคุณเองในการตรวจสอบเงื่อนไขล่วงหน้าของฟังก์ชันได้อย่างถูกต้องทุกที่ที่เรียกว่า ด้วยเหตุนี้ฉันขอแนะนำให้ฟังก์ชันตรวจสอบเงื่อนไขที่จำเป็นภายในถ้ามีความสามารถในการทำเช่นนั้น
TZHX

"ฉันต้องการเฉพาะสถานะที่อัปเดตหากสถานะเปลี่ยนไป" - คุณต้องการสถานะที่อัปเดต (= เปลี่ยน) หากสถานะเดิมเปลี่ยนไปหรือไม่ ฟังดูค่อนข้างกลม คุณช่วยอธิบายให้ชัดเจนว่าคุณหมายถึงอะไรดังนั้นฉันสามารถเพิ่มตัวอย่างที่มีความหมายในคำตอบของฉันได้
Doc Brown

@DocBrown ให้เช่นบอกว่าฉันต้องการให้คุณสมบัติสถานะวัตถุที่แตกต่างกันสองในการซิงค์ เมื่อใดก็ตามที่วัตถุเปลี่ยนไปฉันเรียก syncStatuses () - แต่สิ่งนี้อาจถูกเรียกใช้สำหรับการเปลี่ยนแปลงฟิลด์ที่แตกต่างกันมากมาย (ไม่เพียง แต่ฟิลด์สถานะ)
Matthew Mullin

คำตอบ:


15

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

Guard clause ในฟังก์ชั่น (return ก่อนหน้า):
เพื่อป้องกันการถูกเรียกด้วยพารามิเตอร์ที่ไม่ถูกต้อง

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

ตรรกะเกี่ยวกับว่าจะเรียกใช้ฟังก์ชั่นทั้งหมดอยู่ที่ระดับที่สูงขึ้นของนามธรรมแม้เพียงแค่ มันควรจะมีอยู่นอกฟังก์ชั่นของตัวเอง ในขณะที่ DocBrown พูดว่าคุณสามารถมีฟังก์ชั่นระดับกลางที่ดำเนินการตรวจสอบนี้เพื่อลดความซับซ้อนของรหัส

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

ด้วยการรักษาแยกเหล่านี้คุณมั่นใจได้ว่ารหัสของคุณจะง่ายต่อการเขียนอ่านและบำรุงรักษา


คำตอบที่ยอดเยี่ยม ฉันชอบความจริงที่ทำให้ฉันคิดวิธีนี้ได้ชัดเจน ถ้าควรอยู่ข้างนอกเนื่องจากเป็นส่วนหนึ่งของกระบวนการตัดสินใจว่าควรจะเรียกใช้ฟังก์ชันนี้ตั้งแต่แรกหรือไม่ และโดยเนื้อแท้ไม่มีส่วนเกี่ยวข้องกับฟังก์ชั่นนั้น ๆ รู้สึกแปลกที่จะทำเครื่องหมายคำตอบความคิดเห็นว่าถูกต้อง แต่ฉันจะดูอีกครั้งในอีกไม่กี่ชั่วโมงและทำเช่นนั้น
Matthew Mullin

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

7

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

bool tryRunFunction(...)
{
    bool shouldThisRun = /* some logic using data not available inside "runFunction"*/;
    if (shouldThisRun)
        runFunction();
    return shouldThisRun;
}

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

หมายเหตุว่าบางครั้งคุณจะต้องฟังก์ชั่นเช่นการตรวจเช็คตามแบบบูรณาการเฉพาะเพื่อให้คุณสามารถบูรณาการการตรวจสอบลงtryRunFunction runFunctionหรือคุณไม่จำเป็นต้องนำเช็คไปใช้ที่ใดก็ได้ในโปรแกรมอีกครั้งซึ่งในกรณีนี้คุณสามารถปล่อยให้มันอยู่ในฟังก์ชันการโทรได้

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


1
ต้องยอมรับว่า "tryXXX ()" สำนวนที่ดูเหมือนจะออกไปเล็กน้อยและมันไม่เหมาะสมที่นี่ คุณไม่ได้พยายามทำสิ่งที่คาดว่าจะเกิดข้อผิดพลาด คุณกำลังอัพเดทหากสกปรก
user949300

@ user949300: runFunctionการเลือกชื่อที่ดีหรือการตั้งชื่อโครงการขึ้นอยู่กับกรณีการใช้งานจริงในชื่อที่ทำงานจริงไม่บางชื่อที่วางแผนไว้เช่น ฟังก์ชั่นเช่นอาจจะมาพร้อมกับฟังก์ชั่นอื่นเช่นupdateStatus() updateIfStatusHasChanged()แต่นี่เป็นกรณีที่ขึ้นอยู่กับ 100% ไม่มีวิธีแก้ปัญหา "one-size-fits-all" นี้ดังนั้นใช่ฉันเห็นด้วยสำนวน "ลอง" ไม่ได้เป็นตัวเลือกที่ดีเสมอไป
Doc Brown

ไม่มีสิ่งที่ชื่อ "dryRun" ใช่ไหม มากขึ้นหรือน้อยลงมาเป็นการดำเนินการปกติโดยไม่มีผลข้างเคียง วิธีปิดการใช้งานผลข้างเคียงเป็นอีกเรื่องหนึ่ง
Laiv

3

สำหรับผู้ที่ตัดสินใจว่าจะรันหรือไม่คำตอบคือจากGRASPใครคือ "ผู้เชี่ยวชาญด้านข้อมูล" ที่รู้

เมื่อคุณตัดสินใจแล้วให้เปลี่ยนชื่อฟังก์ชั่นเพื่อความชัดเจน

สิ่งนี้ถ้าฟังก์ชั่นตัดสินใจ:

 ensureUpdated()
 updateIfDirty()

หรือถ้าผู้โทรควรตัดสินใจ:

 writeStatus()

2

ฉันต้องการขยายคำตอบของ @ Baldrickk

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

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

ดังนั้นไซต์ที่โทรสามารถข้ามการเรียกไปได้updateStatus()หากรู้ว่าภายในขอบเขตของโดเมนไม่มีสิ่งใดเปลี่ยนแปลงไป นั่นเป็นสถานการณ์ที่โทรควรถูกล้อมรอบด้วยที่เหมาะสมifสร้าง

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

ดังนั้นคำถามคือ:

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

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


2

มีคำอธิบายที่ดีมากมาย แต่ฉันต้องการดูวิธีที่ผิดปกติ: สมมติว่าคุณใช้วิธีนี้:

if (shouldThisRun) {
   runFunction();
}

runFunction() {
   if (!shouldThisRun) return;
}

และคุณต้องเรียกใช้ฟังก์ชันอื่นในrunFunctionวิธีดังนี้:

runFunction() {
   if (!shouldThisRun) return;
   someOtherfunction();
}

คุณจะทำอะไร? คุณคัดลอกการตรวจสอบทั้งหมดจากบนลงล่างหรือไม่

someOtherfunction() {
   if (!shouldThisRun) return;
}

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

public someFunction() {
   if (shouldThisRun) {
      runFunction();
   }
}

private runFunction() {
 // do your business.
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.