รหัสเช่นนี้เป็น "ซากรถไฟ" (เป็นการละเมิด Law of Demeter) หรือไม่


23

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

service.setLocation(this.configuration.getLocation().toString());

ในกรณีserviceนี้เป็นตัวแปรอินสแตนซ์ของชนิดที่รู้จักประกาศภายในวิธีการ this.configurationมาจากการถูกส่งผ่านไปในการสร้างคลาสและเป็นตัวอย่างของชั้นเรียนใช้อินเตอร์เฟซที่เฉพาะเจาะจง (ซึ่งเอกสารสาธารณะgetLocation()วิธีการ) ดังนั้นชนิดการคืนค่าของนิพจน์this.configuration.getLocation()จึงเป็นที่รู้จัก โดยเฉพาะในกรณีนี้ก็คือjava.net.URLในขณะที่ต้องการservice.setLocation() Stringเนื่องจากทั้งสองประเภทเป็นสตริงและ URL เข้ากันไม่ได้โดยตรงจึงจำเป็นต้องมีการแปลงบางประเภทเพื่อให้พอดีกับหมุดสี่เหลี่ยมในรูกลม

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

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

ถ้าผมจะใช้วิธีการURLToString()ซึ่งก็เรียกtoString()บนURLวัตถุ (เช่นที่ส่งกลับโดยgetLocation()) ผ่านไปเป็นพารามิเตอร์และผลตอบแทนผลที่ฉันสามารถห่อgetLocation()โทรในนั้นเพื่อให้บรรลุผลตรงเดียวกัน ได้อย่างมีประสิทธิภาพฉันจะย้ายการแปลงออกไปหนึ่งก้าว มันจะทำให้เป็นที่ยอมรับหรือไม่? ( ดูเหมือนว่าสำหรับฉันอย่างสังหรณ์ใจว่ามันไม่ควรสร้างความแตกต่างในทางใดทางหนึ่งเนื่องจากสิ่งที่ทำคือเคลื่อนย้ายสิ่งต่าง ๆ ไปรอบ ๆ อย่างไรก็ตามตามจดหมายของกฎหมายของ Demeter ตามที่อ้างถึงมันจะเป็นที่ยอมรับตั้งแต่ฉัน จะทำงานโดยตรงบนพารามิเตอร์ของฟังก์ชัน)

มันจะสร้างความแตกต่างหรือไม่ถ้าสิ่งนี้เกี่ยวกับสิ่งที่แปลกใหม่กว่าการโทรtoString()แบบมาตรฐาน

เมื่อตอบรับโปรดจำไว้ว่าการเปลี่ยนแปลงพฤติกรรมหรือ API ของประเภทที่serviceตัวแปรนั้นไม่สามารถนำไปใช้ได้จริง นอกจากนี้เพื่อเหตุผลของการโต้แย้งสมมติว่าการเปลี่ยนประเภทการส่งคืนของgetLocation()ยังทำไม่ได้

คำตอบ:


34

ปัญหานี่คือsetLocationลายเซ็นของ มันพิมพ์ stringly

เพื่ออธิบาย: ทำไมมันจะคาดหวังString? Stringหมายถึงชนิดของข้อมูลที่เป็นข้อความใดมันอาจเป็นอะไรก็ได้ยกเว้นที่ตั้งที่ถูกต้อง

ที่จริงแล้วสิ่งนี้ทำให้เกิดคำถาม: สถานที่คืออะไร? จะทำอย่างไรฉันรู้โดยไม่ได้มองเป็นรหัสของคุณหรือไม่ ถ้ามันเป็นURLมากกว่าที่ฉันจะรู้มากขึ้นเกี่ยวกับสิ่งที่วิธีนี้คาดหวัง บางทีมันอาจจะทำให้รู้สึกมากขึ้นเพื่อให้เป็นชั้นเอง
Locationตกลงฉันไม่รู้ตอนแรกว่ามันคืออะไร แต่ในบางจุด (อาจจะก่อนที่this.configuration.getLocation()ฉันจะเขียนฉันจะใช้เวลาสักครู่เพื่อหาว่ามันคือวิธีการนี้จะกลับมา)
ได้รับในทั้งสองกรณีฉันต้องมองหาที่อื่นที่จะเข้าใจสิ่งที่คาดหวัง อย่างไรก็ตามในกรณีหลังถ้าฉันเข้าใจสิ่งที่Locationเป็นฉันสามารถใช้ API ของคุณในกรณีก่อนถ้าฉันเข้าใจสิ่งที่String(ซึ่งคาดว่าจะ) ฉันยังไม่ทราบว่า API ของคุณคาดหวังอะไร

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

นอกจากนี้คุณควรพิจารณาว่านี่คือ Java ที่คุณกำลังพูดถึงซึ่งมีคุณสมบัติน้อยมากจากการออกแบบ นั่นคือสิ่งที่บังคับให้คุณต้องโทรหาจริงtoStringในตอนท้าย
ถ้าคุณใช้ C # ตัวอย่างซึ่งเป็นพิมพ์แบบคงที่แล้วคุณจริงจะสามารถที่จะละเว้นการโทรว่าด้วยการกำหนดพฤติกรรมการหล่อโดยปริยาย
ในภาษาที่พิมพ์แบบไดนามิกเช่น Objective-C คุณไม่ต้องการการแปลงจริงๆเพราะตราบใดที่ค่าทำงานเหมือนสตริงทุกคนก็มีความสุข

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

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


ฉันมักจะเห็นด้วยกับคุณ แต่เขาบอกไปแล้วว่าเขาไม่สามารถแก้ไขบริการ API ได้
jhocking

1
@ jhocking: เขาไม่ได้บอกว่าเขาทำไม่ได้ เขาบอกว่ามันเป็นไปไม่ได้ ฉันไม่เห็นด้วย. การพยายามใช้แนวปฏิบัติที่ดีที่สุดกับรหัสที่ใช้แก้ไขข้อบกพร่องในการออกแบบ API นั้นเหมาะสมจริงๆหากการแก้ไขปัญหาดังกล่าวเป็นเพียงตัวเลือกที่เป็นไปได้เท่านั้น อย่างไรก็ตามการตั้งค่า API ตรงนี้เป็นตัวเลือกที่ดีที่สุด การลบข้อบกพร่องมักจะดีกว่าที่จะหลีกเลี่ยง
back2dos

ดี 1 อยู่แล้วสำหรับ "น้อยกว่าโทรจริงเพียงแค่สร้างเสียงรบกวนจากความต้องการของ Java สำหรับชัดแจ้ง"
jhocking

1
ฉันจะให้ +1 นี้แม้ว่าเพียงแค่สำหรับ "พิมพ์อย่างเข้มงวด" ในรหัสของฉันฉันพยายามส่งประเภทที่แสดงความหมาย / เจตนาให้มากที่สุด แต่เมื่อทำงานกับไลบรารี่และ API ของบุคคลที่สามบางครั้งคุณอาจติดอยู่กับสิ่งที่ผู้เขียน API นั้นตัดสินใจ และ IIRC ที่ตั้งอาจเป็น "ข้อมูลที่เป็นข้อความใด ๆ ก็ได้" แต่สำหรับการติดตั้งที่ฉันใช้งานอยู่ตำแหน่งที่ไม่ใช่ URL นั้นไม่มีความหมายโดยสิ้นเชิง
CVn

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

21

กฎหมายของ Demeter เป็นแนวทางการออกแบบไม่ใช่กฎหมายที่จะต้องปฏิบัติตามอย่างเคร่งครัด

ถ้าคุณรู้สึกว่าคลาสของคุณ decoupled เพียงพอที่จะไม่มีอะไรผิดปกติกับบรรทัดthis.configuration.getLocation()โดยเฉพาะอย่างยิ่งถ้าอย่างที่คุณพูดมันเป็นไปไม่ได้ที่จะเปลี่ยนส่วนอื่น ๆ ของ API

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


1
เนื่องจากthis.configurationเป็นตัวแปรอินสแตนซ์ของชนิดของอินเทอร์เฟซที่รู้จักการเรียกใช้วิธีการที่กำหนดโดยอินเทอร์เฟซนั้นดูดีแม้ตามการตีความที่เข้มงวด ใช่ฉันรู้ว่ามันเป็นแนวทางเช่น KISS, SOLID, YAGNI และอื่น ๆ มีน้อยมากหาก "กฎหมาย" ใด ๆ (ในแง่กฎหมาย) ในการพัฒนาซอฟต์แวร์ทั่วไป
CVn

4
+1 สำหรับการใช้งานจริง ;-)
Treb

1
ฉันไม่คิดว่ากฏหมายของ Dementer ควรนำไปใช้ในกรณีเช่นนี้ - การกำหนดค่าเป็นคอนเทนเนอร์ ใน API ทุกครั้งที่ฉันทำงานด้วยการดูในตู้คอนเทนเนอร์เป็นเรื่องปกติและมีพฤติกรรมที่คาดหวัง
Loren Pechtel

2

สิ่งเดียวที่ฉันนึกได้คือไม่เขียนโค้ดแบบนี้จะเกิดอะไรขึ้นถ้าthis.configuration.getLocation()คืน null? ขึ้นอยู่กับรหัสรอบของคุณและกลุ่มเป้าหมายโดยใช้รหัสนี้ แต่ตามที่มาร์โกพูด - กฎหมายของ Demeter เป็นกฎง่ายๆ - ดีที่จะปฏิบัติตาม แต่อย่าทำลายหลังของคุณโดยไม่จำเป็น


ถ้าthis.configuration.getLocation()จะคืนค่า null เราก็ (a) ส่วนใหญ่จะไม่ได้ไปไกลขนาดนั้นหรือ (ข) มีบางอย่างที่เกิดความหายนะเกิดขึ้นในระหว่างนั้นซึ่งในกรณีนี้ฉันต้องการให้รหัสล้มเหลว ดังนั้นในขณะนี้เป็นจุดที่ถูกต้องโดยทั่วไปในกรณีนี้มันค่อนข้างปลอดภัยที่จะบอกว่ามันใช้ไม่ได้ นอกจากนี้การล้อมรอบทั้งหมดนี้และอีกมากมายเป็นตัวจัดการข้อยกเว้นที่ออกแบบมาเพื่อจัดการกับความล้มเหลวที่ไม่คาดคิด
CVn

2

การปฏิบัติตามกฎหมายของ Demeter อย่างเคร่งครัดจะหมายความว่าคุณควรใช้วิธีในวัตถุการกำหนดค่าเช่น:

function getLocationAsString() {
  return getLocation().toString();
}

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


1
นั่นคือสิ่งเดียวกันที่แนะนำที่นี่: c2.com/cgi/wiki?TrainWreck "สร้างวิธีการที่แสดงถึงพฤติกรรมที่ต้องการและบอกลูกค้าว่าจะทำอย่างไรต่อจากนี้" หลักการบอกอย่าถาม "หลักการ
heltonbiker
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.