การกำหนดตัวแปรเพื่อตั้งชื่ออาร์กิวเมนต์วิธีเป็นการปฏิบัติที่ดีหรือไม่?


73

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

var preventUndo = true;
doSomething(preventUndo);

รุ่นที่สั้นกว่าของสิ่งนี้คือ

doSomething(true);

แต่เมื่อฉันกลับมาที่รหัสฉันมักจะสงสัยว่าtrueหมายถึงอะไร มีแบบแผนสำหรับปริศนาประเภทนี้หรือไม่?


5
คุณใช้ IDE บางชนิดหรือไม่ ส่วนใหญ่จะมีวิธีการเรียงลำดับของการระบุสิ่งที่พารามิเตอร์ที่มีต่อฟังก์ชั่นที่คุณกำลังเรียกซึ่งค่อนข้างโมฆะต้องทำเช่นนี้
Matthew Scharley

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

11
หากภาษานั้นรองรับคุณสามารถใช้การแจงนับเช่นdoSomething( Undo.PREVENT )
James P.

4
Undo = { PREVENT = true, DONT_PREVENT = false }แต่คุณสามารถกำหนด แต่ใน JavaScript, การประชุมคือการทำเช่นนั้น: แล้วfunction myFunction( mandatoryArg1, mandatoryArg2, otherArgs ) { /*...*/ } myFunction( 1, 2, { option1: true, option2: false } )
xavierm02

6
มันขึ้นอยู่กับภาษาอย่างแน่นอน ยกตัวอย่างเช่นถ้าเป็นงูหลามฉันขอแนะนำให้ใช้เพียงอาร์กิวเมนต์คำหลักเช่นdoSomething(preventUndo=True)
Joel Cornett

คำตอบ:


117

การอธิบายตัวแปร

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

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

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

editButton.Enabled = (_grid.SelectedRow != null && ((Person)_grid.SelectedRow).Status == PersonStatus.Active);

เมื่อเทียบกับอีกต่อไปเล็กน้อย แต่เนื้อหาชัดเจนขึ้น:

bool personIsSelected = (_grid.SelectedRow != null);
bool selectedPersonIsEditable = (personIsSelected && ((Person)_grid.SelectedRow).Status == PersonStatus.Active)
editButton.Enabled = (personIsSelected && selectedPersonIsEditable);

พารามิเตอร์บูลีน

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

ParseFolder(true, false);

คุณต้องค้นหาความหมายของพารามิเตอร์เหล่านั้น ถ้าพวกเขา enums มันจะชัดเจนมากขึ้น:

ParseFolder(ParseBehaviour.Recursive, CompatibilityOption.Strict);

แก้ไข:

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


28
ยกเว้นว่าคุณใช้พารามิเตอร์ที่มีชื่อ (อาร์กิวเมนต์ที่มีชื่อใน. NET Framework) ซึ่งในกรณีนี้doSomething(preventUndo: true);จะชัดเจนเพียงพอ นอกจากนี้การสร้าง enum สำหรับบูลีนทุกตัวใน API ของคุณหรือไม่ อย่างจริงจัง?
Arseni Mourzenko

12
@MainMa: หากภาษาที่คุณใช้นั้นไม่เย็นพอที่จะรองรับการโต้เถียงที่มีชื่อการใช้ enum เป็นตัวเลือกที่ดี นี่ไม่ได้หมายความว่าคุณควรแนะนำ enums อย่างเป็นระบบทุกที่
Giorgio

18
@MainMa - เพื่อตอบคำถามนั้นประเด็นก็คือคุณไม่ควรมีบูลีนใน API ของคุณ (อย่างน้อยก็เป็นสาธารณะของคุณ) คุณจะต้องเปลี่ยนเป็นไวยากรณ์อาร์กิวเมนต์ที่ตั้งชื่อซึ่งไม่ได้เป็นสำนวน (ในขณะที่เขียน) และในกรณีใด ๆ พารามิเตอร์บูลีนมักจะบ่งบอกว่าวิธีการของคุณทำสองสิ่งและคุณอนุญาตให้ผู้โทร เลือก. แต่ประเด็นของคำตอบคือไม่มีอะไรผิดปกติกับการใช้ตัวแปรอธิบายในโค้ดของคุณ มันเป็นแนวปฏิบัติที่ดีมาก
Daniel B

3
มันเป็นส่วนถาวรของภาษา C # ต้องการอะไรอีก "การยอมรับกระแสหลัก" แม้สำหรับคนที่ไม่ได้ตระหนักถึงโครงสร้าง (ซึ่งหมายถึง C # devs ของคุณยังไม่ได้อ่าน C # เอกสาร) ก็ชัดเจนว่ามันทำอะไร
Nate CK

3
นั่นคือเหตุผลที่ MS เผยแพร่บทสรุปที่ดีเหล่านั้นเกี่ยวกับสิ่งใหม่ทั้งหมดที่เพิ่มไว้ในแต่ละรุ่นฉันไม่คิดว่ามันจะสร้างงานจำนวนมากเพื่ออ่านหนึ่งในสองสามปีนี้ทุกmsdn.microsoft.com/en- เรา / ไลบรารี่ / bb383815% 28v = vs.100% 29.aspx
Nate CK

43

อย่าเขียนรหัสที่คุณไม่ต้องการ

หากคุณdoSomething(true)เข้าใจยากคุณควรเพิ่มความคิดเห็น:

// Do something and prevent the undo.
doSomething(true);

หรือหากภาษารองรับให้เพิ่มชื่อพารามิเตอร์:

doSomething(preventUndo: true);

มิฉะนั้นพึ่งพา IDE ของคุณเพื่อให้ลายเซ็นของวิธีการที่เรียกว่า:

ป้อนคำอธิบายรูปภาพที่นี่

เคสที่มีประโยชน์

การวางตัวแปรเพิ่มเติมมีประโยชน์:

  1. สำหรับวัตถุประสงค์ในการแก้ไขข้อบกพร่อง:

    var productId = this.Data.GetLastProductId();
    this.Data.AddToCart(productId);
    

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

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

    // Even with indentation, this is unreadable.
    var doSomething(
        isSomethingElse ? 0 : this.getAValue(),
        this.getAnotherOne() ?? this.default,
        (a + b + c + d + e) * f,
        this.hello ? this.world : (this.hello2 ? this.world2 : -1));
    
  3. หากการประเมินของพารามิเตอร์มีความซับซ้อนเกินไป ตัวอย่างของรหัสที่ฉันเคยเห็น:

    // Wouldn't it be easier to have several if/else's (maybe even in a separate method)?
    do(something ? (hello ? world : -1) : (programmers ? stackexchange : (com ? -1 : 0)));
    

ทำไมไม่ในกรณีอื่น ๆ ?

ทำไมคุณไม่ควรสร้างตัวแปรเพิ่มเติมในกรณีง่าย ๆ ?

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

  2. แต่เนื่องจากความเสี่ยงในการแยกชื่อคุณให้กับตัวแปรกับชื่อของพารามิเตอร์

    ตัวอย่าง:

    สมมติว่ารหัสเดิมคือ:

    void doSomething(bool preventUndo)
    {
        // Does something very interesting.
        undoHistory.removeLast();
    }
    
    // Later in code:
    var preventUndo = true;
    doSomething(preventUndo);
    

    หลังจากนั้นนักพัฒนาซอฟต์แวร์ที่ทำงานเกี่ยวกับการdoSomethingแจ้งเตือนเมื่อเร็ว ๆ นี้ประวัติการเลิกทำแนะนำวิธีการใหม่สองวิธี:

    undoHistory.clearAll() { ... }
    undoHistory.disable() { ... }
    

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

    doSomething(bool hideLastUndo) { ... }
    doSomething(bool removeAllUndo) { ... }
    doSomething(bool disableUndoFeature) { ... }
    

    ดังนั้นตอนนี้คุณมี:

    void doSomething(bool hideLastUndo)
    {
        // Does something very interesting.
        undoHistory.removeLast();
    }
    
    // Later in code, very error prone, while the signature of the method is clear:
    var preventUndo = true;
    doSomething(preventUndo);
    

แล้ว enums ล่ะ?

บางคนแนะนำให้ใช้ enums ในขณะที่มันแก้ปัญหาได้ทันทีมันจะสร้างปัญหาที่ใหญ่กว่า ลองดู:

enum undoPrevention
{
    keepInHistory,
    prevent,
}

void doSomething(undoPrevention preventUndo)
{
    doTheJob();
    if (preventUndo == undoPrevention.prevent)
    {
        this.undoHistory.discardLastEntry();
    }
}

doSomething(undoPrevention.prevent);

ปัญหาที่เกิดขึ้นกับ:

  1. มันเป็นรหัสมากเกินไป if (preventUndo == undoPrevention.prevent)? อย่างจริงจัง?! ฉันไม่ต้องการที่จะเขียนเช่นifนี้ทุกครั้ง

  2. การเพิ่มองค์ประกอบให้กับ enum นั้นดึงดูดมากในภายหลังถ้าฉันใช้ enum เดียวกันที่อื่น ถ้าฉันแก้ไขมันเช่นนี้

    enum undoPrevention
    {
        keepInHistory,
        prevent,
        keepButDisable, // Keeps the entry in the history, but makes it disabled.
    }
    

    จะเกิดอะไรขึ้นตอนนี้ doSomethingวิธีการจะทำงานตามที่คาดไว้หรือไม่ เพื่อป้องกันสิ่งนี้มันจะต้องเขียนวิธีนี้ตั้งแต่ต้น:

    void doSomething(undoPrevention preventUndo)
    {
        if (![undoPrevention.keepInHistory, undoPrevention.prevent].contains(preventUndo))
        {
            throw new ArgumentException('preventUndo');
        }
    
        doTheJob();
        if (preventUndo == undoPrevention.prevent)
        {
            this.undoHistory.discardLastEntry();
        }
    }
    

    ตัวแปรที่ใช้บูลีนเริ่มดูดีมาก!

    void doSomething(bool preventUndo)
    {
        doTheJob();
        if (preventUndo)
        {
            this.undoHistory.discardLastEntry();
        }
    }
    

3
"มันเป็นรหัสที่มากเกินไปถ้า (preventUndo == undoPrevention.prevent) อย่างจริงจัง?! ฉันไม่ต้องการเขียน ifs ดังกล่าวทุกครั้ง": แล้วใช้คำสั่งสวิตช์เก่าที่ดีอย่างไร นอกจากนี้หากคุณใช้บูลีนคุณจะต้องมีคำสั่ง if การวิจารณ์ของคุณในประเด็นนี้ดูเหมือนจะเป็นเรื่องจริงสำหรับฉัน
Giorgio

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

2
@superM: ฉันไม่คิดว่ามันยากที่จะเปลี่ยนชื่อของตัวแปรชั่วคราวที่ใช้สำหรับการโทรเท่านั้น หากมีการเปลี่ยนชื่อ enum จะดียิ่งขึ้นเพราะรหัสการโทรจะไม่ทำงานอีกต่อไปและส่งข้อผิดพลาดไปที่ใบหน้าของคุณ หากการทำงานของฟังก์ชั่นมีการเปลี่ยนแปลงอย่างรุนแรงโดยการขยาย enum แล้วคุณจะต้องกลับไปที่การโทรทั้งหมดเพื่อความถูกต้องต่อไปอย่างไรก็ตามไม่ว่าคุณจะเขียนโปรแกรมด้วยความหวัง ไม่แม้แต่จะคิดเกี่ยวกับการเปลี่ยนค่าเช่นกวนpreventUndoไปallowUndoในลายเซ็น ...
รักษาความปลอดภัย

4
@superM ที่ทำให้ความคิดเห็นตรงกันกับรหัสนั้นเป็นปัญหาอย่างมากเนื่องจากคุณไม่ได้รับความช่วยเหลือจากคอมไพเลอร์ในการค้นหาความไม่สอดคล้องกัน
Buhb

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

20

ไม่ควรเขียนเมธอดด้วยค่าสถานะบูลีน

ในการอ้างอิง Robert C. Martin กล่าวในหนังสือ Clean Code (ISBN-13 978-0-13-235088-4) ของเขาในหนังสือของเขา "การส่งบูลีนเข้าสู่ฟังก์ชั่นเป็นการปฏิบัติที่เลวร้ายอย่างแท้จริง"

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

DoSomething()
{...

DoSomethingUndoable()
{....

7
ตามตรรกะนั้นตัวสร้าง HotDog เดียวของฉันจะต้องใช้วิธีการที่แตกต่างกันประมาณ 16 วิธี: HotDogWithMustardRelishKrautOnion (), HotDogWithMustardNorelishKrautOnion () และอื่น ๆ หรืออาจเป็น HotDogWithOptions อย่างง่าย (ตัวเลือก int) ซึ่งฉันตีความตัวเลือกเป็นฟิลด์บิต แต่จากนั้นเรากลับไปที่วิธีการหนึ่งที่ทำสิ่งที่แตกต่างกันหลายอย่างที่ถูกกล่าวหา ดังนั้นถ้าฉันไปกับตัวเลือกแรกฉันต้องเขียนเงื่อนไขขนาดใหญ่เพื่อจัดเรียงคอนสตรัคเตอร์ที่จะเรียกตามสิ่งที่มิฉะนั้นจะเป็นพารามิเตอร์บูลีนหรือไม่? และถ้า Q ใช้ int, A นี้จะไม่ตอบคำถาม
Caleb

4
หลังจากอ่านคำตอบทั้งหมดและดูที่รหัสของฉันฉันจะได้ข้อสรุปว่านี่เป็นวิธีที่ถูกต้องในการทำ doSomethingUndoable()สามารถจับการเปลี่ยนแปลงแล้วโทรdoSomething()
methodofaction

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

2
@Caleb ในกรณีนี้ฉันจะทำฮอทด็อกและเพิ่มส่วนผสมทีละอย่างหรือในภาษาที่พิมพ์ฉันจะผ่านวัตถุ HotDogSettings ที่ฉันจะได้ตั้งค่าบูลีนทั้งหมด ฉันจะไม่มีธงจริง / เท็จ 15 อันที่จะเป็นฝันร้ายในการรักษา โปรดจำไว้ว่ารหัสนั้นเกี่ยวกับการบำรุงรักษาที่ไม่ได้เขียนเหมือนที่เป็น 80% ของค่าใช้จ่ายของแอปพลิเคชันคือ var hd = MakeHotDog (); hd.Add (Onion); ...
Nick B.

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

5

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

ปัญหาคือรหัสนั้นแน่นเกินไป ในความคิดของฉันการส่งผ่านบูลเพื่อควบคุมพฤติกรรมนั้นเป็นสัญญาณของ API ที่ไม่ดี หากคุณเป็นเจ้าของ API ให้เปลี่ยน สองวิธีdoSomething()และdoSomethingWithUndo()จะดีกว่า ถ้าคุณไม่ได้เป็นเจ้าของมันเขียนสองวิธีการห่อหุ้มตัวเองในรหัสคุณเป็นเจ้าของและมีหนึ่งของพวกเขาโทรและสายอื่นdoSomething(true)doSomething(false)


4

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

ตัวแปรควรตั้งชื่อตามบทบาทในขอบเขตปัจจุบัน ไม่ใช่ขอบเขตที่ส่ง


1
บทบาทของตัวแปรในขอบเขตปัจจุบันคือการบอกสิ่งที่มันถูกใช้ ฉันไม่เห็นว่าสิ่งนี้ขัดแย้งกับการใช้งานตัวแปรชั่วคราวของ OP อย่างไร
superM

4

สิ่งที่ฉันจะทำใน JavaScript คือฟังก์ชั่นรับวัตถุเป็นพารามิเตอร์เพียงอย่างเดียวดังนี้:

function doSomething(settings) {
    var preventUndo = settings.hasOwnProperty('preventUndo') ? settings.preventUndo : false;
    // Deal with preventUndo in the normal way.
}

จากนั้นโทรหาด้วย:

doSomething({preventUndo: true});

HA !!! เราทั้งคู่ทำdoSomething(x)วิธี! ฮ่า ๆ ... ฉันไม่ได้สังเกตเห็นโพสต์ของคุณจนถึงตอนนี้
blesh

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

1
เอกสารรหัสของคุณ นั่นคือสิ่งที่คนอื่นทำ มันทำให้การอ่านรหัสของคุณง่ายขึ้นการเขียนจะต้องเกิดขึ้นเพียงครั้งเดียว
ridecar2

1
@NickB การประชุมมีประสิทธิภาพ หากรหัสส่วนใหญ่ของคุณยอมรับวัตถุที่มีอาร์กิวเมนต์จะมีความชัดเจน
orip

4

ฉันชอบที่จะใช้ประโยชน์จากภาษาเพื่อช่วยให้ชัดเจน ตัวอย่างเช่นใน C # คุณสามารถระบุพารามิเตอร์ตามชื่อ:

 CallSomething(name: "So and so", age: 12, description: "A random person.");

ใน JavaScript ฉันมักจะต้องการใช้อ็อบเจกต์ options เป็นอาร์กิวเมนต์ด้วยเหตุผลนี้:

function doSomething(args) { /*...*/ }

doSomething({ name: 'So and so', age: 12, description: 'A random person.' });

นั่นเป็นเพียงความชอบของฉัน ฉันคิดว่ามันจะขึ้นอยู่กับวิธีการเรียกใช้และวิธีที่ IDE ช่วยให้นักพัฒนาเข้าใจลายเซ็น


1
นั่นคือ "ข้อเสีย" ของภาษาที่พิมพ์อย่างหลวม ๆ คุณไม่สามารถบังคับใช้ลายเซ็นได้จริง ๆ โดยไม่ผ่านการตรวจสอบ ฉันไม่รู้ว่าคุณสามารถเพิ่มชื่อแบบนั้นใน JS ได้
James P.

1
@JamesPoulson: คุณอาจจะรู้ว่าคุณสามารถทำได้ใน JS และก็ไม่รู้ตัว มันจบแล้วที่ JQuery: $.ajax({url:'', data:''})ฯลฯ ฯลฯ
blesh

จริง ดูเหมือนผิดปกติตั้งแต่แรกเห็น แต่ก็คล้ายกับวัตถุหลอกแบบอินไลน์ที่สามารถมีอยู่ในบางภาษา
James P.

3

ฉันพบว่านี่เป็นวิธีที่ง่ายและอ่านง่ายที่สุด:

enum Undo { ALLOW, PREVENT }

doSomething(Undo u) {
    if (Undo.ALLOW == u) {
        // save stuff to undo later.
    }
    // do your thing
}

doSomething(Undo.ALLOW);

MainMa ไม่ชอบสิ่งนั้น เราอาจต้องเห็นด้วยไม่เห็นด้วยหรือเราสามารถใช้วิธีแก้ปัญหาที่ Joshua Bloch เสนอ:

enum Undo {
    ALLOW {
        @Override
        public void createUndoBuffer() {
            // put undo code here
        }
    },
    PREVENT {
        @Override
        public void createUndoBuffer() {
            // do nothing
        }
    };

    public abstract void createUndoBuffer();
}

doSomething(Undo u) {
    u.createUndoBuffer();
    // do your thing
}

ตอนนี้ถ้าคุณเคยเพิ่ม Undo.LIMITED_UNDO โค้ดของคุณจะไม่รวบรวมจนกว่าคุณจะใช้เมธอด createUndoBuffer () และใน doSomething () ไม่มีถ้า (Undo.ALLOW == u) ฉันได้ทำทั้งสองวิธีและวิธีที่สองค่อนข้างหนาและยากที่จะเข้าใจเมื่อ Undo enum ขยายไปยังหน้าและหน้าของรหัส แต่มันทำให้คุณคิด ฉันมักจะยึดติดกับวิธีที่ง่ายกว่าเพื่อแทนที่บูลีนอย่างง่ายด้วยค่า enum 2 ค่าจนกว่าฉันจะมีเหตุผลที่จะเปลี่ยน เมื่อฉันเพิ่มค่าที่สามฉันใช้ IDE ของฉันกับ "Find-usages" และแก้ไขทุกอย่างให้เรียบร้อย


2

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

ฉันสามารถคิดถึงทางเลือกสองทางที่ไม่เกี่ยวข้องกับตัวแปรพิเศษ:

1. ใช้ความคิดเห็นเพิ่มเติม

doSomething(/* preventUndo */ true);

2. ใช้ enum สองค่าแทนบูลีน

enum Options
{
    PreventUndo,
    ...
}

...

doSomething(PreventUndo);

คุณสามารถใช้ทางเลือกที่ 2 หากภาษาของคุณรองรับ enums

แก้ไข

แน่นอนว่าการใช้อาร์กิวเมนต์ที่มีชื่อเป็นตัวเลือกด้วยหากภาษาของคุณรองรับ

เกี่ยวกับการเข้ารหัสพิเศษที่จำเป็นโดย enums ดูเหมือนจริง ๆ ฉันเล็กน้อย แทน

if (preventUndo)
{
    ...
}

คุณมี

if (undoOption == PreventUndo)
{
    ...
}

หรือ

switch (undoOption)
{
  case PreventUndo:
  ...
}

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


2

ฉันเห็นด้วยกับสิ่งที่ @GlenPeterson พูดว่า:

doSomething(Undo.ALLOW); // call using a self evident enum

แต่การ insteresting ก็จะเป็นสิ่งต่อไปนี้เพราะมันดูเหมือนว่าฉันมีเพียงสองตำแหน่ง (จริงหรือเท็จ)

//Two methods    

doSomething()  // this method does something but doesn't prevent undo

doSomethingPreventUndo() // this method does something and prevents undo

2
ความคิดที่ดี - ฉันไม่ได้คิดอย่างนั้น เมื่อคุณมีความซับซ้อนมากขึ้นเช่น doSomething (String, String, String, บูลีน, บูลีน, บูลีน) จากนั้นชื่อค่าคงที่เป็นวิธีแก้ปัญหาที่สมเหตุสมผลเท่านั้น จอชโบลชแนะนำโรงงานและวัตถุคอนสตรัคเตอร์เหมือนใน: MyThingMaker thingMaker = MyThingMaker.new (); thingMaker.setFirstString ( "สวัสดี"); thingMaker.setSecondString ( "มี"); ฯลฯ ... สิ่ง t = thingMaker.makeIt (); t.doSomething (); นั่นเป็นงานที่มากขึ้นดังนั้นมันจึงคุ้มค่ากว่า
GlenPeterson

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

@GlenPeterson โรงงานเป็นไปได้และเป็นไปได้ใน Javascript (กล่าวถึงโดย OP)
เจมส์พี

2

เมื่อคุณถามเกี่ยวกับจาวาสคริปต์เป็นหลักโดยใช้ coffeescript คุณสามารถมีอาร์กิวเมนต์ที่มีชื่อเช่นไวยากรณ์ได้ง่ายมาก:

# declare method, ={} declares a default value to prevent null reference errors
doSomething = ({preventUndo} = {}) ->
  if preventUndo
    undo = no
  else
    undo = yes

#call method
doSomething preventUndo: yes
#or 
doSomething(preventUndo: yes)

รวบรวมเพื่อ

var doSomething;

doSomething = function(_arg) {
  var preventUndo, undo;
  preventUndo = (_arg != null ? _arg : {}).preventUndo;
  if (preventUndo) {
    return undo = false;
  } else {
    return undo = true;
  }
};

doSomething({
  preventUndo: true
});

doSomething({
  preventUndo: true
});

ขอให้สนุกที่http://js2coffee.org/เพื่อลองความเป็นไปได้


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

1

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

var doSomethingOptions = new Array();

doSomethingOptions["PREVENTUNDO"] = true;
doSomethingOptions["TESTMODE"] = false;

doSomething( doSomethingOptions );

// ...

function doSomething( doSomethingOptions ){
    // Check for null here
    if( doSomethingOptions["PREVENTUNDO"] ) // ...
}

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

ความเป็นไปได้อีกอย่างก็คือการมีวัตถุและยังเป็นไปได้ใน Javascript ฉันไม่แน่ใจ 100% ว่านี่ถูกต้องหรือไม่ ดูรูปแบบเช่นโรงงานเพื่อเป็นแรงบันดาลใจเพิ่มเติม

function SomethingDoer( preventUndo ) {
    this.preventUndo = preventUndo;
    this.doSomething = function() {
            if( this.preventUndo ){
                    // ...
            }
    };
}

mySomethingDoer = new SomethingDoer(true).doSomething();

1
ฉันคิดว่าสิ่งนี้สามารถเขียนได้ดีขึ้นใน dot-สัญกรณ์ ( doSomethingOptions.PREVENTUNDO) แทนสัญกรณ์การเข้าถึงอาร์เรย์
Paŭlo Ebermann

1

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

ฉันคิดว่ามันมีประโยชน์ที่จะต้องทราบว่าปัญหานี้ส่วนใหญ่หันไปเมื่อภาษาการเขียนโปรแกรมรองรับอาร์กิวเมนต์ที่มีชื่อ / อาร์กิวเมนต์คำหลักเช่นใน C # 4 และ Python หรือที่ข้อโต้แย้งวิธีการ interleaved ในชื่อวิธีเช่นใน Smalltalk หรือ Objective-C

ตัวอย่าง:

// C# 4
foo.doSomething(preventUndo: true);
# Python
foo.doSomething(preventUndo=True)
// Objective-C
[foo doSomethingWith:bar shouldPreventUndo:YES];

0

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

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


ดังนั้นคุณจะแปลงฟังก์ชั่นด้วยอาร์กิวเมนต์บูลีนในสองฟังก์ชั่นซึ่งแตกต่างกันในส่วนเล็ก ๆ ของรหัสของพวกเขา (ที่ซึ่งต้นฉบับจะมีif(param) {...} else {...})
Paŭlo Ebermann

-1
int preventUndo = true;
doSomething(preventUndo);

คอมไพเลอร์ที่ดีสำหรับภาษา lavel ที่ต่ำเช่น C ++ จะเพิ่มประสิทธิภาพรหัสเครื่องของ 2 บรรทัดด้านล่างดังนี้:

doSomething(true); 

ดังนั้นมันไม่สำคัญกับประสิทธิภาพ แต่จะเพิ่มความสามารถในการอ่าน

นอกจากนี้ยังมีประโยชน์ในการใช้ตัวแปรในกรณีที่มันจะกลายเป็นตัวแปรในภายหลังและอาจยอมรับค่าอื่นเช่นกัน

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