การส่งบูลีนกลับมาเมื่อประสบความสำเร็จหรือล้มเหลวเป็นเรื่องที่ต้องกังวล


15

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

ฉันใช้ Python แต่คำถามไม่ได้เจาะจงเฉพาะภาษานั้น มีเพียงสองตัวเลือกที่ฉันนึกได้

  1. เพิ่มข้อยกเว้นถึงแม้ว่าสถานการณ์จะไม่ได้ยอดเยี่ยมและอย่าลืมจับข้อยกเว้นนั้นในทุกที่ที่มีการเรียกใช้ฟังก์ชัน
  2. คืนบูลีนขณะที่ฉันกำลังทำ

นี่คือจริงๆตัวอย่างง่ายๆที่แสดงให้เห็นว่าสิ่งที่ผมพูดถึง

import os

class DoSomething(object):

    def remove_file(self, filename):

        try:
            os.remove(filename)
        except OSError:
            return False

        return True

    def process_file(self, filename):

        do_something()

        if remove_file(filename):
            do_something_else()

แม้ว่ามันจะใช้งานได้ แต่ฉันไม่ชอบวิธีนี้ในการทำอะไรบางอย่างมัน "มีกลิ่น" และบางครั้งอาจทำให้เกิดการซ้อนกันหลายครั้ง แต่ฉันไม่สามารถคิดถึงวิธีที่ง่ายกว่านี้ได้

ฉันสามารถใช้ปรัชญา LBYL มากขึ้นและใช้os.path.exists(filename)ก่อนที่จะพยายามลบ แต่ก็ไม่รับประกันว่าไฟล์จะไม่ถูกล็อคในระหว่างนี้ (มันไม่น่าเป็นไปได้ แต่เป็นไปได้) และฉันยังต้องพิจารณาว่าการลบนั้นสำเร็จหรือไม่

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

คำตอบ:


11

คุณควรกลับมาbooleanเมื่อเมธอด / ฟังก์ชันมีประโยชน์ในการตัดสินใจเชิงตรรกะ

คุณควรใช้exceptionวิธีการ / ฟังก์ชั่นที่ไม่น่าจะถูกใช้ในการตัดสินใจเชิงตรรกะ

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

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

แก้ไข:

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


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

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

4

สัญชาตญาณของคุณเกี่ยวกับเรื่องนี้ถูกต้องมีวิธีที่ดีกว่าที่จะทำเช่นนี้: monads

Monads คืออะไร

Monads คือ (เพื่อแปลความหมาย Wikipedia) วิธีการผูกมัดการดำเนินงานร่วมกันในขณะที่ซ่อนกลไกการผูกมัด; ในกรณีของคุณกลไกการผูกมัดเป็นifs ที่ซ้อนกัน ซ่อนที่และรหัสของคุณจะมีกลิ่นหอมมากดีกว่า

มีพระสองสามองค์ที่จะทำเช่นนั้น ("อาจจะ" และ "ทั้งสอง") และโชคดีสำหรับคุณพวกเขาเป็นส่วนหนึ่งของห้องสมุดไพ ธ อนที่ดีจริงๆ!

สิ่งที่พวกเขาสามารถทำเพื่อรหัสของคุณ

นี่คือตัวอย่างการใช้ monad "Either" ("Failable" ในไลบรารีที่เชื่อมโยงกับ) ซึ่งฟังก์ชันสามารถส่งคืนสำเร็จหรือล้มเหลวขึ้นอยู่กับสิ่งที่เกิดขึ้น:

import os

class DoSomething(object):

    def remove_file(self, filename):
        try:
            os.remove(filename)
            return Success(None)
        except OSError:
            return Failure("There was an OS Error.")

    @do(Failable)
    def process_file(self, filename):
        do_something()
        yield remove_file(filename)
        do_something_else()
        mreturn(Success("All ok."))

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

    def action_that_might_fail_and_returns_something(self):
        # get some random value between 0 and 1 here
        if value < 0.5:
            return Success(value)
        else:
            return Failure("Bad value! Bad! Go to your room!")

    @do(Failable)
    def process_file(self, filename):
        do_something()
        yield remove_file(filename)
        yield action_that_might_fail(somearg)
        yield another_action_that_might_fail(someotherarg)
        some_val = yield action_that_might_fail_and_returns_something()
        yield something_that_used_the_return_value(some_val)
        do_something_else()
        mreturn(Success("All ok."))

ที่แต่ละyields ในprocess_fileฟังก์ชั่นถ้าการเรียกฟังก์ชั่นส่งกลับความล้มเหลวแล้วprocess_fileฟังก์ชั่นจะออกจากที่จุดนั้นคืนค่าความล้มเหลวจากฟังก์ชั่นที่ล้มเหลวแทนการดำเนินการต่อผ่านส่วนที่เหลือและกลับSuccess("All ok.")

ตอนนี้ลองนึกภาพทำข้างต้นกับifs ซ้อนกัน! (คุณจะจัดการกับค่าส่งคืนได้อย่างไร!)

ข้อสรุป

Monads นั้นดี :)


หมายเหตุ:

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

IIRC มีการพิมพ์ผิดในสคริปต์ lib บนหน้าเชื่อมโยงถึงแม้ว่าฉันจะลืมว่ามันอยู่ที่ไหน ATM ฉันจะอัปเดตถ้าฉันจำได้ ฉันกระจายเวอร์ชันของฉันกับหน้าและพบว่า: def failable_monad_examle():-> def failable_monad_example():- pในexampleนั้นหายไป

เพื่อให้ได้ผลลัพธ์ของฟังก์ชั่นการตกแต่งที่พร้อมใช้งาน (เช่นprocess_file) คุณต้องจับผลลัพธ์ในvariableและทำvariable.valueเพื่อให้ได้รับ


2

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

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


0

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

ในรหัสของคุณคุณต้องตรวจสอบก่อนว่ามีไฟล์อยู่หรือไม่ removeFileถ้ามันไม่โทร ถ้าไม่เช่นนั้นทำสิ่งอื่น ๆ

ในกรณีนี้คุณยังอาจต้องการที่removeFileจะโยนข้อยกเว้นหากไม่สามารถลบไฟล์ได้ด้วยเหตุผลอื่นเช่นสิทธิ์

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


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

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