มันสมเหตุสมผลหรือไม่ที่จะสร้างบล็อกเพื่อลดขอบเขตของตัวแปร?


38

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

//Some code
....

KeyManagerFactory keyManager = KeyManagerFactory.getInstance("SunX509");
Keystore keyStore = KeyStore.getInstance("JKS");

{
    char[] password = getPassword();
    keyStore.load(new FileInputStream(keyStoreLocation), password);
    keyManager.init(keyStore, password);
}

...
//Some more code

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

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

มีกรณีที่ใช้บล็อกเพียงเพื่อลดขอบเขตตัวแปรที่เหมาะสมหรือไม่?


13
@gnat .... อะไรนะ ฉันไม่รู้ด้วยซ้ำว่าจะแก้ไขคำถามของฉันอย่างไรให้คล้ายกับเป้าหมายของกลุ่มเป้าหมายนั้น ส่วนใดที่ทำให้คุณเชื่อว่าคำถามเหล่านี้เหมือนกัน
ลอร์ด Farquaad

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

4
มีคำถามที่คล้ายกันสองสามข้อที่ถามสิ่งนี้สำหรับภาษาซีพลัสพลัส: เป็นวิธีปฏิบัติที่ไม่ถูกต้องหรือไม่ในการสร้างบล็อกรหัส และโครงสร้างรหัสด้วยวงเล็บปีกกา อาจเป็นหนึ่งในนั้นมีคำตอบสำหรับคำถามนี้แม้ว่า C ++ และ Java จะแตกต่างกันมากในส่วนนี้
amon

2
มีความเป็นไปได้ที่ซ้ำกันของการสร้างบล็อคของรหัส
Panzercrisis

4
@gnat ทำไมคำถามนั้นถึงเปิดอยู่ มันเป็นหนึ่งในสิ่งที่กว้างที่สุดที่ฉันเคยเห็น มันเป็นความพยายามในคำถามที่ยอมรับหรือไม่
jpmc26

คำตอบ:


34

ขั้นแรกให้พูดกับกลไกพื้นฐาน:

ในขอบเขต C ++ == อายุการใช้งาน b / c destructors ถูกเรียกเมื่อออกจากขอบเขต นอกจากนี้ความแตกต่างที่สำคัญใน C / C ++ เราสามารถประกาศวัตถุท้องถิ่น ในรันไทม์สำหรับ C / C ++ โดยทั่วไปคอมไพเลอร์จะจัดสรรสแต็กเฟรมสำหรับวิธีการที่มีขนาดใหญ่ที่สุดเท่าที่อาจจำเป็นล่วงหน้าแทนที่จะจัดสรรพื้นที่สแต็กมากขึ้นในการเข้าสู่แต่ละขอบเขต (ที่ประกาศตัวแปร) ดังนั้นคอมไพเลอร์กำลังยุบหรือแบนขอบเขต

คอมไพเลอร์ C / C ++ อาจนำพื้นที่เก็บข้อมูลสแต็กกลับมาใช้ใหม่สำหรับคนในท้องถิ่นที่ไม่ขัดแย้งกับอายุการใช้งาน (โดยทั่วไปจะใช้การวิเคราะห์รหัสจริงเพื่อพิจารณาว่าเป็นขอบเขตมากกว่า b / c ที่แม่นยำกว่าขอบเขต!)

ฉันพูดถึงไวยากรณ์ของ Java C / C ++ b / c นั่นคือการจัดฟันแบบหยิกและการกำหนดขอบเขตอย่างน้อยก็ในส่วนที่ได้มาจากตระกูลนั้น และเนื่องจาก C ++ เกิดขึ้นในความคิดเห็นของคำถาม

ในทางตรงกันข้ามมันเป็นไปไม่ได้ที่จะมีวัตถุในท้องถิ่นใน Java: วัตถุทั้งหมดเป็นวัตถุกองและท้องถิ่น / formals ทั้งหมดเป็นตัวแปรอ้างอิงหรือประเภทดั้งเดิม (btw เดียวกันเป็นจริงสำหรับสถิตศาสตร์)

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

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

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

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

ยังไม่มีอะไรผิดปกติกับการใช้มันเพื่อ deconflict ชื่อ

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

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


คำตอบที่น่าสนใจมาก (+1)! อย่างไรก็ตามหนึ่งส่วนประกอบของ C ++ หากรหัสผ่านเป็นสตริงในบล็อกวัตถุสตริงจะจัดสรรพื้นที่ในร้านค้าฟรีสำหรับส่วนขนาดตัวแปร เมื่อออกจาก Bloc สตริงจะถูกทำลายส่งผลให้เกิดการจัดสรรคืนของส่วนตัวแปร แต่เนื่องจากมันไม่ได้อยู่ในสแต็คจึงไม่มีสิ่งใดรับประกันได้ว่ามันจะถูกเขียนทับในไม่ช้า ดังนั้นควรจัดการความลับให้ดีขึ้นโดยเขียนทับตัวละครแต่ละตัวก่อนที่สตริงจะถูกทำลาย ;-)
Christophe

1
@ErikEidt ฉันพยายามดึงความสนใจไปที่ปัญหาการพูดของ "C / C ++" ราวกับว่ามันเป็น "สิ่ง" จริง ๆ แต่ไม่: "ในรันไทม์สำหรับ C / C ++ คอมไพเลอร์จะจัดสรรเฟรมสแต็กโดยทั่วไป สำหรับวิธีการที่มีขนาดใหญ่เท่าที่ต้องการ "แต่ C ไม่มีวิธีการทั้งหมด มันมีฟังก์ชั่น สิ่งเหล่านี้แตกต่างกันโดยเฉพาะใน C ++
tchrist

7
" ในทางตรงกันข้ามมันเป็นไปไม่ได้ที่จะมีวัตถุท้องถิ่นใน Java: วัตถุทั้งหมดเป็นวัตถุกองและท้องถิ่น / formals ทั้งหมดเป็นตัวแปรอ้างอิงหรือประเภทดั้งเดิม (btw เดียวกันเป็นจริงสำหรับสถิตยศาสตร์) " - โปรดทราบว่านี่คือ ไม่เป็นความจริงทั้งหมดโดยเฉพาะอย่างยิ่งไม่ใช่สำหรับตัวแปรโลคอลเมธอด การวิเคราะห์ Escapeสามารถจัดสรรวัตถุให้กับกองซ้อน
Boris the Spider

1
ที่สุดแล้วคอมไพเลอร์สามารถนำสล็อตตัวแปรท้องถิ่นกลับมาใช้ใหม่ได้ แต่ (ต่างจาก C / C ++) เฉพาะในกรณีที่ประเภทเดียวกัน ” ผิดปกติ ในระดับ bytecode ตัวแปรท้องถิ่นสามารถกำหนดและกำหนดใหม่ได้ตามที่คุณต้องการข้อ จำกัด เพียงอย่างเดียวคือการใช้งานในภายหลังนั้นเข้ากันได้กับประเภทของรายการที่กำหนดล่าสุด การนำช่องตัวแปรท้องถิ่นเป็นบรรทัดฐานเช่นกับ{ int x = 42; String s="blah"; } { long y = 0; }ที่longตัวแปรจะนำมาใช้ช่องของทั้งสองตัวแปรxและsกับทุกคอมไพเลอร์ที่ใช้กันทั่วไป ( javacและecj)
Holger

1
@ Boris the Spider: นั่นเป็นคำพูดที่พูดซ้ำบ่อย ๆ ซึ่งไม่ตรงกับสิ่งที่เกิดขึ้นจริง การวิเคราะห์การหลบหนีอาจเปิดใช้งาน Scalarization ซึ่งเป็นการสลายตัวของเขตข้อมูลของวัตถุเป็นตัวแปรซึ่งจะถูกปรับให้เหมาะสมต่อไป บางส่วนอาจถูกกำจัดตามที่ไม่ได้ใช้งานส่วนอื่น ๆ อาจถูกพับด้วยตัวแปรต้นทางที่ใช้ในการเริ่มต้นส่วนอื่น ๆ ถูกแมปกับการลงทะเบียน CPU ผลลัพธ์ไม่ค่อยตรงกับสิ่งที่ผู้คนอาจจินตนาการเมื่อพูดว่า "จัดสรรให้กับกองซ้อน"
Holger

27

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


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

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

@candied_orange ฉันกลัวว่าจะไม่ :( สนใจที่จะทำอย่างละเอียด?
doubleOrt

@doubleOrt ฉันหมายถึงขั้นตอนการ refactoring ระดับกลางเป็นจริงและจำเป็น แต่แน่นอนไม่จำเป็นต้องได้รับการประดิษฐานในแหล่งควบคุมที่ทุกคนสามารถดูได้ เพิ่งเสร็จสิ้นการปรับโครงสร้างใหม่ก่อนที่คุณจะเช็คอิน
candied_orange

@candied_orange โอ้คิดว่าคุณกำลังโต้เถียงและไม่ขยายการโต้แย้งของ RubberDuck
doubleOrt

17

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

มีบางกรณีที่ฉันพบว่ามีประโยชน์ - ในswitchคำสั่ง! บางครั้งคุณต้องการประกาศตัวแปรในcase:

switch (op) {
    case ADD:
        int result = a + b;
        System.out.printf("%s + %s = %s\n", a, b, result);
        break;
    case SUB:
        int result = a - b;
        System.out.printf("%s - %s = %s\n", a, b, result);
        break;
}

อย่างไรก็ตามสิ่งนี้จะล้มเหลว:

error: variable result is already defined in method main(String[])
            int result = a - b;

switch ข้อความแปลก ๆ - เป็นข้อความควบคุม แต่เนื่องจากการเข้าใจผิดพวกเขาไม่แนะนำขอบเขต

ดังนั้น - คุณสามารถใช้บล็อกเพื่อสร้างขอบเขตด้วยตัวคุณเอง:

switch (op) {
    case ADD: {
        int result = a + b;
        System.out.printf("%s + %s = %s\n", a, b, result);
    } break;
    case SUB: {
        int result = a - b;
        System.out.printf("%s - %s = %s\n", a, b, result);
    } break;
}

6
  • กรณีทั่วไป:

มีกรณีที่ใช้บล็อกเพียงเพื่อลดขอบเขตตัวแปรที่เหมาะสมหรือไม่?

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

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

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

มันเข้าท่า แต่มันหายากมาก

  • กรณีการใช้งานของคุณ:

นี่คือรหัสของคุณ:

KeyManagerFactory keyManager = KeyManagerFactory.getInstance("SunX509");
Keystore keyStore = KeyStore.getInstance("JKS");

{
    char[] password = getPassword();
    keyStore.load(new FileInputStream(keyStoreLocation), password);
    keyManager.init(keyStore, password);
}

คุณกำลังสร้างFileInputStreamแต่คุณไม่เคยปิดมัน

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

KeyManagerFactory keyManager = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
Keystore keyStore = KeyStore.getInstance("JKS");

try (InputStream fis = new FileInputStream(keyStoreLocation)) {
    char[] password = getPassword();
    keyStore.load(fis, password);
    keyManager.init(keyStore, password);
}

(มันมักจะดีกว่าที่จะใช้getDefaultAlgorithm()แทนSunX509โดยวิธี.)

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


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

2

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

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

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

ไม่ใช่คำแนะนำสำหรับวิธีการฟุ่มเฟือย

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

พยายามลดขอบเขตขั้นต่ำให้ดียิ่งขึ้น

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

ImageIO.write(new Robot("borg").createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize())), "png", new File(Db.getUserId(User.handle()).toString()));

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

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

ประโยชน์เชิงปฏิบัติของการลดขอบเขต

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


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

2

มีกรณีที่ใช้บล็อกเพียงเพื่อลดขอบเขตตัวแปรที่เหมาะสมหรือไม่?

ฉันคิดว่าแรงจูงใจมีเหตุผลเพียงพอที่จะแนะนำบล็อกใช่

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

John Carmark เขียนบันทึกเกี่ยวกับโค้ดอินไลน์ในปี 2550 บทสรุปสุดท้ายของเขา

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

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


1

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

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

{
    char[] password = getPassword();
    try{
        keyStore.load(new FileInputStream(keyStoreLocation), password);
        keyManager.init(keyStore, password);
    }finally{
        Arrays.fill(password, '\0');
    }
}

นี่คล้ายกับคำสั่งtry-with-resourcesจริงๆเพราะมันเป็นขอบเขตของทรัพยากรและทำการสรุปให้เสร็จสิ้นหลังจากที่คุณทำเสร็จแล้ว โปรดทราบอีกครั้งว่าฉันไม่ได้โต้เถียงกับการจัดการรหัสผ่านในลักษณะนี้เพียงว่าถ้าคุณตัดสินใจที่จะทำเช่นนั้นสไตล์นี้ก็สมเหตุสมผล

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

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

{
    MethodBuilder m_ToString = tb.DefineMethod("ToString", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final, typeofString, Type.EmptyTypes);
    var il = m_ToString.GetILGenerator();
    il.Emit(OpCodes.Ldstr, templateType.ToString()+":"+staticType.ToString());
    il.Emit(OpCodes.Ret);
    tb.DefineMethodOverride(m_ToString, m_Object_ToString);
}
{
    PropertyBuilder p_Class = tb.DefineProperty("Class", PropertyAttributes.None, typeofType, Type.EmptyTypes);
    MethodBuilder m_get_Class = tb.DefineMethod("get_Class", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final, typeofType, Type.EmptyTypes);
    var il = m_get_Class.GetILGenerator();
    il.Emit(OpCodes.Ldtoken, staticType);
    il.Emit(OpCodes.Call, m_Type_GetTypeFromHandle);
    il.Emit(OpCodes.Ret);
    p_Class.SetGetMethod(m_get_Class);
    tb.DefineMethodOverride(m_get_Class, m_IPattern_get_Class);
}

คุณสามารถยืนยันว่าฉันสามารถประกาศILGenerator il;ที่ด้านบนของวิธีการ แต่ฉันก็ไม่ต้องการที่จะใช้ตัวแปรสำหรับวัตถุที่แตกต่างกัน (kinda functional approach) ในกรณีนี้บล็อกทำให้ง่ายต่อการแยกงานที่จะดำเนินการทั้ง syntactically และสายตา นอกจากนี้ยังบอกว่าหลังจากบล็อกฉันเสร็จแล้วilและไม่มีอะไรควรเข้าถึง

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

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