เป็นสไตล์ที่ไม่ดีที่จะตรวจสอบสภาพซ้ำซ้อนหรือไม่?


10

ฉันมักจะไปที่ตำแหน่งในรหัสของฉันที่ฉันพบว่าตัวเองกำลังตรวจสอบเงื่อนไขที่เฉพาะเจาะจงซ้ำแล้วซ้ำอีก

ฉันต้องการให้คุณตัวอย่างเล็ก ๆ : สมมติว่ามีไฟล์ข้อความที่มีบรรทัดที่ขึ้นต้นด้วย "a", บรรทัดที่ขึ้นต้นด้วย "b" และบรรทัดอื่น ๆ และจริง ๆ แล้วฉันต้องการทำงานกับสองบรรทัดแรกเท่านั้น รหัสของฉันจะมีลักษณะเช่นนี้ (ใช้ python แต่อ่านมันเป็นรหัสเทียม):

# ...
clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if (line.startsWith("a")):
        # do stuff
    elif (line.startsWith("b")):
        # magic
    else:
        # this else is redundant, I already made sure there is no else-case
        # by using clear_lines()
# ...

คุณสามารถจินตนาการได้ว่าฉันจะไม่ตรวจสอบสภาพนี้ที่นี่ แต่อาจอยู่ในฟังก์ชั่นอื่น ๆ และอื่น ๆ

คุณคิดว่ามันเป็นเสียงรบกวนหรือเพิ่มมูลค่าให้กับรหัสของฉันหรือไม่?


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

กรณี 'else' ของคุณคือรหัสที่ใช้ไม่ได้ / เข้าถึงไม่ได้ ตรวจสอบว่าไม่มีข้อกำหนดทั่วทั้งระบบที่ห้ามสิ่งนี้
NWS

@NWS: คุณกำลังบอกว่าฉันควรจะทำอย่างอื่น? ขอโทษฉันไม่เข้าใจคุณอย่างสมบูรณ์
marktani

2
ไม่เกี่ยวข้องกับคำถามโดยเฉพาะ - แต่ฉันจะทำให้ 'ยืนยัน' เป็นค่าคงที่ - ซึ่งจะต้องมีคลาส "Line" ใหม่ (อาจมีคลาสที่ได้รับสำหรับ A & B) แทนที่จะปฏิบัติตามบรรทัดเป็นสตริงและบอกพวกเขาว่า พวกเขาเป็นตัวแทนจากภายนอก ฉันยินดีที่จะอธิบายรายละเอียดเกี่ยวกับเรื่องนี้ที่CodeReview
MattDavey

คุณหมายถึงelif (line.startsWith("b"))อะไร โดยวิธีการที่คุณอาจลบวงเล็บ surrouding ในเงื่อนไขพวกเขาจะไม่เป็นสำนวนใน Python
tokland

คำตอบ:


14

นี่คือการปฏิบัติร่วมกัน excedingly และวิธีการในการจัดการกับมันจะผ่านลำดับที่สูงกว่าฟิลเตอร์

โดยพื้นฐานแล้วคุณจะส่งผ่านฟังก์ชั่นไปยังวิธีการกรองพร้อมกับลิสต์ / ลำดับที่คุณต้องการกรองและลิสต์ / ลำดับที่ได้จะมีเพียงองค์ประกอบที่คุณต้องการ

ฉันไม่คุ้นเคยกับไวยากรณ์ของไพ ธ อน (แม้ว่ามันจะมีฟังก์ชั่นดังที่เห็นในลิงค์ด้านบน) แต่ใน c # / f # ดูเหมือนว่า:

ค#:

var linesWithAB = lines.Where(l => l.StartsWith("a") || l.StartsWith("b"));
foreach (var line in linesWithAB)
{
    /* line is guaranteed to ONLY start with a or b */
}

f # (สันนิษฐานว่านับไม่ได้มิฉะนั้นจะใช้ List.filter):

let linesWithAB = lines
    |> Seq.filter (fun l -> l.StartsWith("a") || l.StartsWith("b"))

for line in linesWithAB do
    /* line is guaranteed to ONLY start with a or b */

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


3
(line for line in lines if line.startswith("a") or line.startswith("b"))เป็นบันทึกไวยากรณ์หลามนี้จะมีการแสดงออกของเครื่องกำเนิดไฟฟ้า:
Latty

1
+1 สำหรับการชี้ให้เห็นว่าการดำเนินการที่จำเป็น (ไม่จำเป็น) clear_linesเป็นความคิดที่ไม่ดีจริงๆ ใน Python คุณอาจจะใช้เครื่องกำเนิดไฟฟ้าเพื่อหลีกเลี่ยงการโหลดไฟล์ทั้งหมดในหน่วยความจำ
tokland

จะเกิดอะไรขึ้นเมื่อไฟล์อินพุตมีขนาดใหญ่กว่าหน่วยความจำที่มีอยู่
Blrfl

@Blrfl: ดีถ้าตัวสร้างคำมีความสอดคล้องกันระหว่าง c # / f # / python ดังนั้น @tokland และ @ Lattyware แปลเป็น c # / f # ผลผลิตและ / หรือผลผลิต! งบ เห็นได้ชัดขึ้นเล็กน้อยในตัวอย่าง f # ของฉันเพราะ Seq.filter สามารถใช้ได้กับคอลเลกชันของ IEnumerable <T> เท่านั้น แต่ตัวอย่างโค้ดทั้งสองจะใช้ได้ถ้าlinesเป็นคอลเลกชันที่สร้างขึ้น
Steven Evers

@mcwise: เมื่อคุณเริ่มต้นดูฟังก์ชั่นอื่น ๆ ที่ใช้งานได้ด้วยวิธีนี้มันเริ่มจะเซ็กซี่และแสดงออกได้อย่างไม่น่าเชื่อเพราะพวกมันสามารถถูกล่ามโซ่และประกอบเข้าด้วยกันได้ ดูskip, take, reduce( aggregateใน .NET) map( selectใน .NET) และมีมากขึ้น แต่ที่เริ่มต้นที่มั่นคงจริงๆ
Steven Evers

14

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

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

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

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


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

5

คุณสามารถยกข้อยกเว้นในelseกรณี วิธีนี้จะไม่ซ้ำซ้อน ข้อยกเว้นเกิดขึ้นกับสิ่งที่ไม่ควรเกิดขึ้น แต่มีการตรวจสอบอยู่

clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if (line.startsWith("a)):
        # do stuff
    if (line.startsWith("b")):
        # magic
    else:
        throw BadLineException
# ...

ฉันจะโต้แย้งหลังเป็นความคิดที่ไม่ดีเนื่องจากมันชัดเจนน้อยกว่า - หากคุณตัดสินใจที่จะเพิ่มในภายหลัง"c"อาจมีความชัดเจนน้อยลง
Latty

ข้อเสนอแนะครั้งแรกมีข้อดี ... ข้อที่สอง (ถือว่า "b") เป็นความคิดที่ไม่ดี
Andrew

@ Lattyware ฉันปรับปรุงคำตอบ ขอบคุณสำหรับความคิดเห็นของคุณ
Tulains Córdova

1
@ แอนดรูว์ฉันปรับปรุงคำตอบให้ดีขึ้น ขอบคุณสำหรับความคิดเห็นของคุณ
Tulains Córdova

3

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

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

ต้องขอบคุณฟังก์ชั่นที่ทำให้ลูกค้าใช้งานได้อย่างถูกต้องและลูกค้ามั่นใจว่าฟังก์ชั่นนี้ทำงานได้อย่างถูกต้อง

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

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

ส่วนที่ยากขึ้นคือการกำหนดหน้าที่ที่รับผิดชอบอะไรและจัดทำเอกสารบทบาทเหล่านี้อย่างเคร่งครัด


1

คุณไม่จำเป็นต้องใช้ clear_lines () ตอนเริ่มต้น หากบรรทัดนั้นไม่ใช่ "a" หรือ "b" เงื่อนไขก็จะไม่ทริกเกอร์ หากคุณต้องการกำจัดเส้นเหล่านั้นให้ทำอย่างอื่นเป็น clear_line () ในขณะที่คุณกำลังทำสองผ่านผ่านเอกสารของคุณ หากคุณข้าม clear_lines () ที่จุดเริ่มต้นและทำมันเป็นส่วนหนึ่งของวง foreach จากนั้นคุณลดเวลาในการประมวลผลของคุณในครึ่ง

มันไม่ใช่แค่สไตล์ที่ไม่ดี


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

0

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

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

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


0

... สมมติว่ามีไฟล์ข้อความที่มีบรรทัดที่ขึ้นต้นด้วย "a", บรรทัดที่ขึ้นต้นด้วย "b" และบรรทัดอื่น ๆ และจริง ๆ แล้วฉันต้องการทำงานกับสองบรรทัดแรกเท่านั้น รหัสของฉันจะมีลักษณะเช่นนี้ (ใช้ python แต่อ่านมันเป็นรหัสเทียม):

# ...
clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if ...

ฉันเกลียดสิ่งif...then...elseก่อสร้าง ฉันจะหลีกเลี่ยงปัญหาทั้งหมด:

process_lines_by_first_character (lines,  
                                  'a' => { |line| ... a code ... },
                                  'b' => { |line| ... b code ... } )
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.