ผู้ทะเยอทะยานควรโยนข้อยกเว้นถ้าวัตถุนั้นมีสถานะที่ไม่ถูกต้อง?


55

ฉันมักจะพบปัญหานี้โดยเฉพาะใน Java แม้ว่าฉันคิดว่ามันเป็นปัญหา OOP ทั่วไป นั่นคือการเพิ่มข้อยกเว้นพบปัญหาการออกแบบ

สมมติว่าฉันมีคลาสที่มีString nameฟิลด์และString surnameฟิลด์

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

public void String name;
public void String surname;

public String getCompleteName() {return name + " " + surname;}

public void displayCompleteNameOnInvoice() {
    String completeName = getCompleteName();
    //do something with it....
}

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

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

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

ดังนั้นความเป็นไปได้เท่านั้นดูเหมือนว่าจะเปลี่ยนความหมายของวิธีgetCompleteNameการcomposeCompleteNameที่แสดงให้เห็นมากขึ้นพฤติกรรม 'ใช้งาน' และกับความสามารถของการขว้างปาข้อยกเว้น

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


8
"ในกลุ่มผู้ใช้ทั่วไปไม่ควรโยนข้อยกเว้นหากไม่ได้ตั้งค่า - พวกเขาควรทำอะไร?
Rotem

2
@scriptin จากนั้นทำตามตรรกะนั้นdisplayCompleteNameOnInvoiceจะสามารถส่งข้อยกเว้นได้ถ้าgetCompleteNameส่งคืนnullใช่ไหม
Rotem

2
@Remem มันสามารถ คำถามเกี่ยวกับถ้ามันควร
scriptin

22
ในเรื่องนี้ผู้เขียนโปรแกรม Falsehoods เชื่อเกี่ยวกับชื่อเป็นคนอ่านที่ดีมากว่าทำไม 'ชื่อ' และ 'นามสกุล' จึงเป็นแนวคิดที่แคบมาก
Lars Viklund

9
หากวัตถุของคุณสามารถมีสถานะที่ไม่ถูกต้องคุณก็เมาแล้ว
user253751

คำตอบ:


151

อย่าอนุญาตให้ชั้นเรียนของคุณสร้างโดยไม่ต้องกำหนดชื่อ


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

71
นี่ไม่ใช่ความคิดเห็น หากเขาใช้คำแนะนำในคำตอบเขาจะไม่พบปัญหาอีกต่อไป มันคือคำตอบ
DeadMG

14
@AgostinoX: คุณควรทำให้คลาสของคุณลื่นไหลถ้าจำเป็นจริงๆ มิฉะนั้นพวกเขาควรจะไม่เปลี่ยนแปลงมากที่สุด
DeadMG

25
@gnat: คุณทำผลงานยอดเยี่ยมได้ที่นี่เพื่อตั้งคำถามเกี่ยวกับคุณภาพของคำถามทุกข้อและทุกคำตอบเกี่ยวกับ PSE แต่บางครั้งคุณก็เป็น IMHO เล็กน้อยเกินไป
Doc Brown

9
หากพวกเขาสามารถเลือกได้อย่างถูกต้องไม่มีเหตุผลที่เขาจะโยนในสถานที่แรก
DeadMG

75

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

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


7
So if you want fluidity, use something like the builder pattern- ความลื่นไหลหรือเปลี่ยนแปลงไม่ได้ (ยกเว้นว่าเป็นสิ่งที่คุณหมายถึงอย่างใด) หากต้องการสร้างวัตถุที่ไม่เปลี่ยนรูป แต่ต้องเลื่อนการตั้งค่าบางค่ารูปแบบที่จะปฏิบัติตามคือการตั้งค่าทีละตัวบนวัตถุตัวสร้างและสร้างแบบไม่เปลี่ยนรูปเมื่อเรารวบรวมค่าทั้งหมดที่ถูกต้องแล้ว รัฐ ...
Konrad Morawski

1
@ KonradMorawski: ฉันสับสน คุณกำลังชี้แจงหรือโต้แย้งคำสั่งของฉันหรือไม่? ;)
back2dos

3
ฉันพยายามเติมเต็ม ...
Konrad Morawski

40

ไม่มีวัตถุที่มีสถานะไม่ถูกต้อง

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

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

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

กลุ่มที่ทำมากกว่านั้น

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

แก่นแท้ของเรื่องนี้มาจาก:

ใน getters ทั่วไปไม่ควรโยนข้อยกเว้นหากไม่ได้ตั้งค่า

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

นอกจากนี้ยังมีสถานการณ์ที่คุณต้องใช้getFooวิธีการเช่นกรอบ JavaBeans และเมื่อคุณมีบางสิ่งบางอย่างจากการโทรELคาดหวังว่าจะได้รับถั่ว (เพื่อที่<% bar.foo %>จะเรียกgetFoo()ในชั้นเรียนจริง - การตั้งค่า 'ถั่วควรจะทำองค์ประกอบหรือควร ที่ถูกทิ้งให้ดู? 'เพราะคนหนึ่งสามารถเกิดขึ้นกับสถานการณ์ที่คนใดคนหนึ่งหรืออีกคนหนึ่งสามารถเป็นคำตอบที่ถูกต้องได้อย่างชัดเจน)

ตระหนักถึงความเป็นไปได้ที่ getter ที่กำหนดจะถูกระบุในอินเตอร์เฟสหรือเป็นส่วนหนึ่งของ public พับลิก API ที่เปิดเผยก่อนหน้านี้สำหรับคลาสที่ได้รับการ refactored (เวอร์ชันก่อนหน้านี้เพิ่งมีชื่อ 'completeName' ที่ถูกส่งคืน มันเป็นสองฟิลด์)

ในตอนท้ายของวัน...

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

พยายามที่จะยึดติดกับความบริสุทธิ์ของความหมายของการทะเยอทะยานที่private T foo; T getFoo() { return foo; }จะทำให้คุณมีปัญหาในบางจุด บางครั้งรหัสก็ไม่พอดีกับโมเดลนั้นและความพยายามที่จะทำให้มันเป็นไปอย่างที่ไม่สมเหตุสมผล ... และในที่สุดก็ทำให้การออกแบบยากขึ้น

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


2
เห็นได้ชัดว่าตัวอย่างของฉันเป็นเพียงข้อแก้ตัวในการปรับปรุงรูปแบบการเข้ารหัสโดยให้เหตุผลกับมันไม่ใช่ปัญหาการบล็อก แต่ฉันค่อนข้างงุนงงกับความสำคัญที่คุณและคนอื่น ๆ ดูเหมือนจะมอบให้กับผู้สร้างในทางทฤษฎีพวกเขารักษาสัญญาของพวกเขา ในทางปฏิบัติมันจะกลายเป็นเรื่องยุ่งยากในการจัดการกับมรดกไม่สามารถใช้งานได้เมื่อคุณดึงส่วนต่อประสานจากชั้นเรียนที่ไม่ได้รับการรับรองจาก javabeans และกรอบงานอื่น ๆ เช่นฤดูใบไม้ผลิถ้าฉันไม่ผิด ภายใต้แนวคิดของ 'คลาส' จะมีชีวิตอยู่ด้วยวัตถุหลากหลายประเภท วัตถุค่าอาจมีตัวตรวจสอบความถูกต้อง แต่นี่เป็นเพียงประเภทเดียว
AgostinoX

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

2
ไม่สมบูรณ์ไม่ได้แปลว่าไม่ถูกต้อง ใบแจ้งหนี้ที่ไม่มีใบเสร็จอาจอยู่ในระหว่างดำเนินการและ API สาธารณะที่นำเสนอจะแสดงให้เห็นว่าเป็นสถานะที่ถูกต้อง หากsurnameหรือnameเป็นโมฆะเป็นสถานะที่ถูกต้องสำหรับวัตถุแล้วจัดการมันตาม แต่ถ้ามันไม่ได้เป็นสถานะที่ถูกต้อง - ทำให้วิธีการภายในชั้นเรียนที่จะโยนข้อยกเว้นก็ควรจะป้องกันไม่ให้อยู่ในสถานะนั้นจากจุดที่มันถูกเริ่มต้น คุณสามารถผ่านวัตถุที่ไม่สมบูรณ์ แต่การผ่านวัตถุที่ไม่ถูกต้องเป็นปัญหา หากนามสกุลว่างเป็นพิเศษพยายามป้องกันไว้ก่อนหน้าในภายหลัง

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

5
"ทำสิ่งที่ง่ายที่สุดในการให้เหตุผล" นั่น นั่น
เบ็น

5

ฉันไม่ใช่คนชวา แต่ดูเหมือนว่าจะเป็นไปตามข้อ จำกัด ทั้งที่คุณนำเสนอ

getCompleteNameไม่ส่งข้อยกเว้นหากชื่อไม่ได้กำหนดค่าเริ่มต้นและdisplayCompleteNameOnInvoiceไม่

public String getCompleteName()
{
    if (name == null || surname == null)
    {
        return null;
    }
    return name + " " + surname;
}

public void displayCompleteNameOnInvoice() 
{
    String completeName = getCompleteName();
    if (completeName == null)
    {
        //throw an exception.
    }
    //do something with it....
}

หากต้องการขยายว่าทำไมนี่จึงเป็นโซลูชันที่ถูกต้อง: วิธีนี้ผู้โทรgetCompleteName()ไม่จำเป็นต้องสนใจว่าทรัพย์สินถูกจัดเก็บจริงหรือถูกคำนวณทันที
Bart van Ingen Schenau

18
หากต้องการขยายว่าทำไมการแก้ปัญหานี้เป็นบ้าบ้าคุณต้องตรวจสอบความว่างเปล่าในทุกการโทรเดียวgetCompleteNameซึ่งเป็นที่น่าเกลียด
DeadMG

ไม่ @DeadMG คุณต้องตรวจสอบในกรณีที่มีข้อผิดพลาดควรถูกส่งออกมาหาก getCompleteName ส่งคืนค่า null ซึ่งเป็นวิธีที่ควรจะเป็น วิธีนี้ถูกต้อง
Dawood ibn Kareem

1
@DeadMG ใช่ แต่เราไม่ทราบว่า OTHER ใช้อะไรgetCompleteName()ในชั้นเรียนที่เหลือหรือแม้แต่ในส่วนอื่น ๆ ของโปรแกรม ในบางกรณีการส่งคืนnullอาจเป็นสิ่งที่ต้องการ แต่ในขณะที่มีวิธีหนึ่งที่สเปค "โยนข้อยกเว้นในกรณีนี้" นั่นคือสิ่งที่เราควรเขียนโค้ด
Dawood ibn Kareem

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

4

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

คำตอบคือ:

ใช่มันควรจะเป็น

ตัวอย่างง่าย ๆ : FileStream.Lengthใน C # /. NETซึ่งจะพ่นNotSupportedExceptionหากไฟล์นั้นหาไม่ได้


ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
maple_shaft

1

คุณมีสิ่งที่ชื่อการตรวจสอบทั้งหมดคว่ำ

getCompleteName()ไม่จำเป็นต้องรู้ว่าฟังก์ชั่นใดจะใช้ประโยชน์ ดังนั้นการไม่มีการnameโทรเมื่อdisplayCompleteNameOnInvoice()ไม่มีgetCompleteName()ความรับผิดชอบ

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

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