หลักการของความประหลาดใจน้อยที่สุด (POLA) และส่วนต่อประสาน


17

ไตรมาสที่ดีของศตวรรษที่ผ่านมาเมื่อฉันเรียนรู้ C ++ ฉันได้รับการสอนว่าอินเตอร์เฟสควรจะให้อภัยและเท่าที่เป็นไปได้ไม่สนใจเกี่ยวกับลำดับที่วิธีการนั้นถูกเรียกเนื่องจากผู้บริโภคอาจไม่สามารถเข้าถึงแหล่งข้อมูลหรือเอกสารแทน นี้.

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

ชัดเจนเหมือนโคลน

พิจารณาอินเทอร์เฟซด้วยวิธีการเหล่านี้ (สำหรับการสร้างไฟล์ข้อมูล):

OpenFile
SetHeaderString
WriteDataLine
SetTrailerString
CloseFile

ตอนนี้คุณสามารถแน่นอนเพียงไปถึงเหล่านี้ในการสั่งซื้อ แต่บอกว่าคุณไม่สนใจเกี่ยวกับชื่อไฟล์ (คิดว่าa.out) AddDataLineหรือสิ่งที่หัวและสตริงรถพ่วงถูกรวมคุณสามารถเพียงแค่โทร

ตัวอย่างที่มีความรุนแรงน้อยกว่าอาจมองข้ามส่วนหัวและส่วนท้าย

อาจมีอีกอย่างหนึ่งอาจตั้งค่าสตริงส่วนหัวและส่วนท้ายก่อนที่จะเปิดไฟล์

นี่เป็นหลักการของการออกแบบส่วนต่อประสานที่ได้รับการยอมรับหรือเป็นวิธี POLA ก่อนที่จะได้รับชื่อหรือไม่?

NB ไม่จมลงในส่วนย่อยของอินเทอร์เฟซนี้มันเป็นเพียงตัวอย่างเพื่อประโยชน์ของคำถามนี้


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

1
ที่เกี่ยวข้อง: programmers.stackexchange.com/questions/187457/…
Ben Cottrell

7
@KilianFoth: ฉันค่อนข้างมั่นใจว่า Wikipedia ผิดเกี่ยวกับเรื่องนี้ - POLA ไม่เพียง แต่เกี่ยวกับการออกแบบส่วนต่อประสานกับผู้ใช้คำว่า "หลักการของความประหลาดใจอย่างน้อย" (ซึ่งค่อนข้างเหมือนกัน) ยังใช้โดย Bob Martin สำหรับการออกแบบฟังก์ชั่น หนังสือ "รหัสสะอาด"
Doc Brown

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

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

คำตอบ:


25

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

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

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

พิจารณา API ทางเลือกที่การดำเนินการ IO แยกจากการดำเนินการข้อมูล และตำแหน่งที่ API นั้น 'เป็นเจ้าของ' การสั่งซื้อ:

ContentBuilder

SetHeader( ... )
AddLine( ... )
SetTrailer ( ... )

FileWriter

Open(filename) 
Write(content) throws InvalidContentException
Close()

ด้วยการแยกข้างต้นContentBuilderไม่จำเป็นต้อง "ทำ" สิ่งใดนอกจากการจัดเก็บบรรทัด / ส่วนหัว / ส่วนท้าย (อาจเป็นContentBuilder.Serialize()วิธีการที่ทราบลำดับ) โดยทำตามหลักการของแข็งอื่น ๆ มันไม่สำคัญว่าคุณจะตั้งหัวหรือรถพ่วงก่อนหรือหลังเส้นเพิ่มเพราะไม่มีอะไรในจะถูกเขียนไปยังแฟ้มจริงจนกว่าจะผ่านมันไป ContentBuilderFileWriter.Write

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

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


สิ่งที่ฉันกำลังมองหา - ขอบคุณ! จากบทความ ISP: "(ISP) ระบุว่าลูกค้าไม่ควรถูกบังคับให้ขึ้นอยู่กับวิธีการที่ไม่ได้ใช้"
Robbie Dee

5
นี่ไม่ใช่คำตอบที่ไม่ถูกต้องอย่างไรก็ตามตัวสร้างเนื้อหาอาจถูกนำไปใช้ในลักษณะที่คำสั่งของการโทรSetHeaderหรือAddLineเรื่องสำคัญ ในการกำจัดการพึ่งพาคำสั่งซื้อนี้ไม่ใช่ ISP หรือ SRP มันเป็นเพียง POLA
Doc Brown

เมื่อการสั่งซื้อมีความสำคัญคุณยังคงพึงพอใจ POLA โดยการกำหนดการดำเนินการดังกล่าวว่าการดำเนินการตามขั้นตอนในภายหลังจำเป็นต้องมีค่าส่งคืนจากขั้นตอนก่อนหน้า FileWriterอาจต้องใช้ค่าจากContentBuilderขั้นตอนสุดท้ายในWriteวิธีการเพื่อให้แน่ใจว่าเนื้อหาอินพุตทั้งหมดเสร็จสมบูรณ์โดยInvalidContentExceptionไม่จำเป็น
Dan Lyons

@ DanLyons ฉันรู้สึกว่าค่อนข้างใกล้เคียงกับสถานการณ์ที่ผู้ถามพยายามหลีกเลี่ยง; ตำแหน่งที่ผู้ใช้ API จำเป็นต้องรู้หรือสนใจเกี่ยวกับการสั่งซื้อ ตามหลักแล้ว API ควรบังคับใช้คำสั่งไม่เช่นนั้นอาจขอให้ผู้ใช้ละเมิด DRY นั่นเป็นเหตุผลในการแยกออกContentBuilderและให้FileWriter.Writeความรู้เล็กน้อยนั้น ข้อยกเว้นจะมีความจำเป็นในกรณีที่มีสิ่งผิดปกติเกิดขึ้นกับเนื้อหา (เช่นส่วนหัวหายไป) การส่งคืนสามารถใช้งานได้ แต่ฉันไม่ใช่แฟนของการเปลี่ยนข้อยกเว้นเป็นรหัสส่งคืน
Ben Cottrell

แต่แน่นอนว่าควรเพิ่มหมายเหตุเพิ่มเติมเกี่ยวกับ DRY และสั่งซื้อคำตอบ
Ben Cottrell

12

สิ่งนี้ไม่เพียงเกี่ยวกับ POLA แต่ยังเกี่ยวกับการป้องกันสถานะที่ไม่ถูกต้องว่าเป็นแหล่งที่มาของข้อบกพร่อง

มาดูกันว่าเราสามารถให้ข้อ จำกัด บางประการกับตัวอย่างของคุณได้อย่างไรโดยไม่ต้องมีการนำไปใช้อย่างเป็นรูปธรรม:

ขั้นตอนแรก: ไม่อนุญาตให้เรียกสิ่งใด ๆ ก่อนที่จะเปิดไฟล์

CreateDataFileInterface
  + OpenFile(filename : string) : DataFileInterface

DataFileInterface
  + SetHeaderString(header : string) : void
  + WriteDataLine(data : string) : void
  + SetTrailerString(trailer : string) : void
  + Close() : void

ตอนนี้มันควรจะชัดเจนว่าCreateDataFileInterface.OpenFileจะต้องเรียกเพื่อดึงDataFileInterfaceอินสแตนซ์ที่สามารถเขียนข้อมูลจริง

ขั้นตอนที่สอง: ตรวจสอบว่าได้ตั้งค่าส่วนหัวและตัวอย่างเสมอ

CreateDataFileInterface
  + OpenFile(filename : string, header: string, trailer : string) : DataFileInterface

DataFileInterface
  + WriteDataLine(data : string) : void
  + Close() : void

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


ในการตอบกลับความคิดเห็น:

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

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

CreateDataFileInterface
  + OpenFile(filename : string, header : string) : IncompleteDataFileInterface

IncompleteDataFileInterface
  + WriteDataLine(data : string) : void
  + FinalizeWithTrailer(trailer : string) : CompleteDataFileInterface

CompleteDataFileInterface
  + Close()

ฉันจะไม่ทำมันสำหรับตัวอย่างนี้จริง ๆ แต่มันแสดงให้เห็นว่าจะส่งผ่านเทคนิคได้อย่างไร

โดยวิธีการที่ฉันคิดว่าวิธีการที่จริงจะต้องเรียกว่าในคำสั่งนี้เช่นการเขียนตามลำดับหลายบรรทัด หากไม่ต้องการสิ่งนี้ฉันจะชอบผู้สร้างเสมอตามที่ Ben Cottrelแนะนำ


1
อนิจจาคุณตกลงไปในกับดักที่ฉันเตือนคุณอย่างชัดเจนให้หลีกเลี่ยงตั้งแต่เริ่มแรก ไม่จำเป็นต้องใช้ชื่อไฟล์ - ทั้งส่วนหัวและส่วนท้าย แต่ชุดรูปแบบทั่วไปของการแยกอินเทอร์เฟซเป็นสิ่งที่ดีดังนั้น +1 :-) +1
Robbie Dee

โอ้ฉันเข้าใจคุณผิดฉันคิดว่านี่เป็นการอธิบายถึงเจตนาของผู้ใช้ไม่ใช่การนำไปปฏิบัติ
Fabian Schmengler

ฉันชอบแยกอินเทอร์เฟซ แต่ฉันมีแนวโน้มที่จะคิดว่าข้อเสนอแนะของคุณเกี่ยวกับการบังคับใช้ (เช่นWriteTrailerAndClose()) กำลังยืนยันว่ามีการละเมิด SRP (นี่คือสิ่งที่ฉันต่อสู้มาหลายครั้ง แต่ข้อเสนอแนะของคุณน่าจะเป็นตัวอย่างที่เป็นไปได้) คุณจะตอบอย่างไร
kmote

1
@kmote คำตอบยาวเกินไปสำหรับความคิดเห็นโปรดดูการอัปเดตของฉัน
Fabian Schmengler

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