วิธีการเลือกระหว่างบอกไม่ถามและแยกคำสั่ง?


25

หลักการบอกว่าอย่าถามว่า:

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

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

ตัวอย่างง่ายๆของ "Tell, Don't Ask"คือ

Widget w = ...;
if (w.getParent() != null) {
  Panel parent = w.getParent();
  parent.remove(w);
}

และเวอร์ชัน Tell คือ ...

Widget w = ...;
w.removeFromParent();

แต่ถ้าฉันต้องรู้ผลลัพธ์จากวิธี removeFromParent ปฏิกิริยาแรกของฉันคือเพียงแค่เปลี่ยน removeFromParent เพื่อส่งกลับค่าบูลีนเพื่อระบุว่าพาเรนต์ถูกลบหรือไม่

แต่ฉันเจอรูปแบบการแยกคำสั่งซึ่งบอกว่าไม่ต้องทำ

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

สองคนนี้ขัดแย้งกันจริงๆและฉันจะเลือกระหว่างคนทั้งสองได้อย่างไร ฉันจะไปกับ Pragmatic Programmer หรือ Bertrand Meyer ได้ไหม?


1
คุณจะทำอะไรกับบูลีน
David

1
ดูเหมือนว่าคุณกำลังดำน้ำในรูปแบบการเขียนโค้ดมากเกินไปโดยไม่สร้างความสมดุลในการใช้งาน
Izkata

บูลีนอีกครั้ง ... นี่คือตัวอย่างที่ง่ายต่อการย้อนกลับของหมู แต่คล้ายกับการดำเนินการเขียนด้านล่างเป้าหมายคือเพื่อให้มันเป็นสถานะของการดำเนินการ
Dakotah North 9'11

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

1
Command Query Separation เป็นหลักการไม่ใช่รูปแบบ รูปแบบเป็นสิ่งที่คุณสามารถใช้เพื่อแก้ปัญหา หลักการคือสิ่งที่คุณทำตามเพื่อไม่สร้างปัญหา
candied_orange

คำตอบ:


25

จริงๆแล้วปัญหาตัวอย่างของคุณแสดงให้เห็นถึงการขาดการสลายตัว

ลองเปลี่ยนกันนิดหน่อย:

Book b = ...;
if (b.getShelf() != null) 
    b.getShelf().remove(b);

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

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

หากต้องการกลับมาที่ตัวอย่างหนังสือ - ป้อนบรรณารักษ์:

Librarian l = ...;
Book b = ...;
l.findShelf(b).remove(b);

โปรดเข้าใจว่าเราไม่ได้ถามอีกต่อไปในความรู้สึกของบอกไม่ได้ถาม

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

ด้วยการเปิดตัวบรรณารักษ์เราได้จำลองความสัมพันธ์นี้แยกจากกันเพื่อให้เกิดการแยกความกังวล


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

1
@scarridge: ที่บรรทัดล่างบอกไม่ต้องถามเป็นข้อพิสูจน์ของการแยกความกังวลที่เหมาะสม หากคุณคิดเกี่ยวกับมันฉันจะตอบคำถาม อย่างน้อยฉันก็คิดอย่างนั้น;)
back2dos

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

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

13

หากคุณต้องการทราบผลลัพธ์คุณต้องทำ นั่นคือความต้องการของคุณ

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

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

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

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

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


1
หนึ่งอาจโต้แย้งว่าในตัวอย่างของคุณหากการดำเนินการ "เขียน" ล้มเหลวข้อยกเว้นควรถูกโยนดังนั้นจึงไม่จำเป็นต้องมีวิธีการ "รับสถานะ"
David

@David: จากนั้นกลับไปที่ตัวอย่างของ OP ซึ่งจะกลับมาจริงถ้ามีการเปลี่ยนแปลงเกิดขึ้นจริงเท็จถ้ามันไม่ได้ ฉันสงสัยว่าคุณต้องการส่งข้อยกเว้นที่นั่น
Robert Harvey

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

@ David: ฉันได้แก้ไขคำตอบเพื่อชี้แจง
Robert Harvey

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

2

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

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

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

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


2

สิ่งที่คุณอธิบายคือ "ข้อยกเว้น" ที่รู้จักกันในหลักการแยกคำสั่ง

ในบทความนี้Martin Fowlerอธิบายว่าเขาจะคุกคามข้อยกเว้นเช่นนี้ได้อย่างไร

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

ในตัวอย่างของคุณฉันจะพิจารณาข้อยกเว้นเดียวกัน


0

การแยกคำสั่งการค้นหานั้นเข้าใจผิดได้ง่ายอย่างน่าอัศจรรย์

ถ้าฉันบอกคุณในระบบของฉันมีคำสั่งที่ส่งคืนค่าจากแบบสอบถามและคุณพูดว่า "ฮา! คุณกำลังละเมิด!" คุณกำลังกระโดดปืน

ไม่นั่นไม่ใช่สิ่งต้องห้าม

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

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

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

อินเทอร์เฟซที่คล่องแคล่วและ "ละเมิด" ของ iDSL นี้แสดงถึงการตอบโต้ตลอดเวลา มีพลังงานจำนวนมากที่ถูกเพิกเฉยถ้าคุณทำปฏิกิริยามากกว่า

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

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

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