สิ่งที่ควรเป็นตำแหน่งของคนตัดไม้ในรายการพารามิเตอร์ [ปิด]


12

ในรหัสของฉันฉันฉีดคนตัดไม้ไปยังหลายชั้นของฉันผ่านรายการพารามิเตอร์ของตัวสร้างของพวกเขา

ฉันสังเกตเห็นว่าฉันใส่มันแบบสุ่ม: บางครั้งมันเป็นรายการแรกในบางครั้งสุดท้ายและบางครั้งในระหว่าง

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

คำตอบ:


31

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

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

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

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


11
+! ให้คนตัดไม้ออกจาก Parms นวกรรมิก; ฉันชอบคนตัดไม้แบบคงที่ดังนั้นฉันรู้ว่าทุกคนใช้สิ่งเดียวกัน
Steven A. Lowe

1
@ StevenA.Lowe โปรดทราบว่าการใช้ตัวบันทึกแบบคงที่อาจทำให้เกิดปัญหาอื่น ๆ ในบรรทัดถ้าคุณไม่ระมัดระวัง (เช่นคำสั่งเริ่มต้นคงที่ล้มเหลวใน C ++) ฉันยอมรับว่าการมีคนตัดไม้เป็นองค์กรที่สามารถเข้าถึงได้ทั่วโลกนั้นมีเสน่ห์ แต่ควรได้รับการประเมินอย่างรอบคอบว่าการออกแบบนั้นเหมาะสมกับสถาปัตยกรรมโดยรวมหรือไม่
ComicSansMS

@ComsSansMS: แน่นอนรวมถึงปัญหาเธรดและอื่น ๆ เพียงแค่การตั้งค่าส่วนตัว - "ง่ายที่สุด แต่ไม่ง่าย";)
Steven A. Lowe

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

@Slothario: นั่นคือความงามของคนตัดไม้แบบคงที่ ไม่จำเป็นต้องฉีดทุกชนิด
Robert Harvey

7

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

foo(mandatory);
foo(mandatory, optional);
foo(mandatory, optional, evenMoreOptional);

ในภาษาที่ใช้งานได้การย้อนกลับมีประโยชน์มากขึ้น - ยิ่งคุณเลือกค่าเริ่มต้นมากเท่าใดก็ยิ่งมีโอกาสมากขึ้นเท่านั้น สิ่งนี้ทำให้ง่ายต่อการชำนาญฟังก์ชั่นโดยเพียงแค่ใช้การขัดแย้งกับมัน:

addThreeToList = map (+3)

อย่างไรก็ตามตามที่กล่าวไว้ในคำตอบอื่น ๆ คุณอาจไม่ต้องการส่งผ่านตัวบันทึกในรายการอาร์กิวเมนต์ของทุกคลาสในระบบอย่างชัดเจน


3

คุณสามารถใช้เวลามากเกินไปในการแก้ไขปัญหานี้ได้

สำหรับภาษาที่มีการใช้งานการบันทึกแบบ canonical เพียงสร้างอินสแตนซ์ตัวบันทึกแบบ canonical โดยตรงในทุกชั้นเรียน

สำหรับภาษาที่ไม่มีการใช้งานแบบบัญญัติให้ลองค้นหาโครงร่างซุ้มการบันทึกและดำเนินการ slf4jเป็นตัวเลือกที่ดีใน Java

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

เมื่อลายเซ็นของฟังก์ชั่นมีบริการอ้างอิงหนึ่งหรือสองบริการเช่นเดียวกับข้อโต้แย้ง "ของจริง" บางอย่างฉันจะทำการอ้างอิงล่าสุด:

int calculateFooBarSum(int foo, int bar, IntegerSummationService svc)

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

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


ฉันรู้สึกอึดอัดใจที่จะใช้การฉีดคุณสมบัติเพื่อเรียก calculFooBarSum
ภู่

2

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

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

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


1

ฉันเห็นด้วยกับผู้ที่แนะนำว่าคนตัดไม้ควรเข้าถึงแบบคงที่แทนที่จะส่งผ่านไปยังคลาส แต่ถ้ามีเหตุผลที่ดีที่คุณต้องการที่จะผ่านมันใน (อินสแตนซ์ที่แตกต่างกันอาจจะต้องการเข้าสู่ระบบไปยังสถานที่ที่แตกต่างกันหรืออะไร) แล้วฉันขอแนะนำให้คุณไม่ผ่านโดยใช้สร้าง แต่โทรออกแยกต่างหากที่จะทำเช่นClass* C = new C(); C->SetLogger(logger);ค่อนข้าง กว่าClass* C = new C(logger);

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


1
ของหลักสูตรนี้มีข้อเสียเปรียบที่คนตัดไม้ไม่สามารถใช้ได้กับตัวสร้าง
Ben Voigt

1
ฉันชอบคำตอบนี้จริงๆ มันทำให้การบันทึกข้อกังวลด้านข้างของคลาสที่คุณต้องใส่ใจแยกต่างหากจากการใช้งาน มีโอกาสที่ถ้าคุณเพิ่มตัวบันทึกลงในตัวสร้างของคลาสจำนวนมากคุณอาจใช้การฉีดพึ่งพา ฉันไม่สามารถพูดได้ทุกภาษา แต่ฉันรู้ในภาษา C # การใช้งาน DI บางอย่างจะส่งไปยังคุณสมบัติโดยตรง(getter / setter)
jpmc26

@BenVoigt: นั่นเป็นความจริงและมันอาจเป็นเหตุผลที่นักฆ่าไม่ทำแบบนี้ แต่โดยปกติคุณสามารถทำการบันทึกที่คุณทำในตัวสร้างในการตอบสนองต่อการตั้งค่าตัวบันทึก
Jack Aidley

0

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

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

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


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

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

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

1
ฉันโต้เถียงกับการฉีดคุณสมบัติ ฉันไม่จำเป็นต้องสนับสนุนการใช้งานการฉีดในตัวสร้าง ฉันแค่บอกว่าในความคิดของฉันที่เป็นที่นิยมในการฉีดทรัพย์สิน
Dan Pantry

"เป็นไปได้ไหม" ยังใช่, IL ทอผ้าเป็นสิ่งที่มีอยู่และได้รับการกล่าวถึงในคำตอบด้านบน ... mono-project.com/docs/tools+libraries/libraries/Mono.Cecil
แดน Pantry

0

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

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