เราควรกำจัดตัวแปรท้องถิ่นถ้าทำได้หรือไม่?


95

ตัวอย่างเช่นเพื่อให้ซีพียูยังคงอยู่ใน Android ฉันสามารถใช้รหัสเช่นนี้:

PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "abc");
wakeLock.acquire();

แต่ฉันคิดว่าตัวแปรท้องถิ่นpowerManagerและwakeLockสามารถกำจัดได้:

((PowerManager)getSystemService(POWER_SERVICE))
    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag")
    .acquire();

ฉากที่คล้ายกันจะปรากฏในมุมมองการแจ้งเตือนของ iOS เช่นจาก

UIAlertView *alert = [[UIAlertView alloc]
    initWithTitle:@"my title"
    message:@"my message"
    delegate:nil
    cancelButtonTitle:@"ok"
    otherButtonTitles:nil];
[alert show];

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    [alertView release];
}

ไปที่:

[[[UIAlertView alloc]
    initWithTitle:@"my title"
    message:@"my message"
    delegate:nil
    cancelButtonTitle:@"ok"
    otherButtonTitles:nil] show];

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    [alertView release];
}

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


95
ไม่จำเป็น. บางครั้งมันทำให้โค้ดชัดเจนขึ้นในการใช้ตัวแปรแบบครั้งเดียวและในภาษาการเขียนโปรแกรมที่เหมาะสมส่วนใหญ่มีค่าใช้จ่ายในการรันไทม์น้อยมาก (ถ้ามี) สำหรับตัวแปรเพิ่มเติมเหล่านั้น
Robert Harvey

75
นอกจากนี้ยังทำให้การก้าวผ่านรหัสด้วยตัวแก้จุดบกพร่องยากขึ้น และคุณต้องแน่ใจด้วย (ขึ้นอยู่กับภาษา) การแสดงออกครั้งแรกไม่ใช่ NULL หรือข้อผิดพลาด
GrandmasterB

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

48
ลองดู ตอนนี้เสียบดีบักเกอร์และลองดูสถานะของ PowerManager หรือ WakeLock ตระหนักถึงความผิดพลาดของคุณ ฉันเคยคิดเหมือนกัน ("มีอะไรกับคนในท้องถิ่นเหล่านี้?") จนกว่าฉันจะต้องใช้เวลาส่วนใหญ่ในการตรวจสอบรหัส
Faerindel

42
แม้ในตัวอย่างของคุณเวอร์ชัน "ตัดออก" ของคุณมีแถบเลื่อนและไม่พอดีกับหน้าจอของฉันทั้งหมดซึ่งทำให้อ่านยากขึ้นมาก
Erik

คำตอบ:


235

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

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

1หากคุณมีโค้ดแทรกแซงจำนวนมากระหว่างการประกาศและการใช้ตัวแปรโลคัลมันอาจไปที่สแต็ก แต่เป็นรายละเอียดเล็กน้อย


2
คุณไม่สามารถรู้ได้ว่ามีรหัสจำนวนมากหรือไม่เครื่องมือเพิ่มประสิทธิภาพอาจแทรกบางฟังก์ชั่น ...
Matthieu M.

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

15
@Walfrat ตัวแปรท้องถิ่นได้รับการเริ่มต้นหลายครั้ง? อุ๊ยตาย ไม่มีใครแนะนำให้ชาวบ้านกลับมาใช้ใหม่ :)
Luaan

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

4
ถาม: เราควรกำจัดตัวแปรท้องถิ่นถ้าทำได้หรือไม่? A: ไม่ได้
simon

94

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

Debuggability

บ่อยครั้งที่นักพัฒนาใช้เวลาและความพยายามในการดีบักมากกว่าเขียนโค้ด

ด้วยตัวแปรท้องถิ่นคุณสามารถ:

  • จุดพักบนบรรทัดที่ถูกกำหนด (พร้อมกับการตกแต่งจุดพักอื่น ๆ ทั้งหมดเช่นจุดพักแบบมีเงื่อนไข ฯลฯ ... )

  • ตรวจสอบ / เฝ้าดู / พิมพ์ / เปลี่ยนแปลงค่าของตัวแปรโลคัล

  • จับประเด็นที่เกิดขึ้นเนื่องจากการปลดเปลื้อง

  • มีการติดตามสแต็กที่ชัดเจน (บรรทัด XYZ มีหนึ่งการดำเนินการแทน 10)

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

ดังนั้นให้ทำตาม maxim ที่น่าอับอาย (เขียนโค้ดในแบบที่คุณต้องการราวกับว่านักพัฒนาคนต่อไปจะดูแลมันหลังจากตัวคุณเองคือคนบ้าที่บ้าคลั่งที่รู้ว่าคุณอยู่ที่ไหน) และ err ด้านdebuggability ง่ายขึ้นหมายถึงใช้ตัวแปรท้องถิ่น .


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

3
การก้าวผ่านโค้ดก็ยากกว่าเช่นกัน หากมีการเรียก () -> call () -> call () -> call () เป็นการยากที่จะก้าวเข้าสู่การโทรด้วยวิธีที่สาม ง่ายกว่ามากถ้ามีตัวแปรท้องถิ่นสี่ตัว
gnasher729

3
@ FrankPuffer - เราต้องจัดการกับโลกแห่งความจริง ผู้ที่ดีบั๊กไม่ทำตามที่คุณเสนอ
DVK

2
@DVK: อันที่จริงมี debuggers มากกว่าที่ฉันคิดว่าให้คุณตรวจสอบหรือดูการแสดงออกใด ๆ MS Visual Studio (ตั้งแต่รุ่น 2013) มีคุณสมบัตินี้สำหรับ C ++ และ C # Eclipse มีไว้สำหรับ Java ในความคิดเห็นอื่น Faerindel พูดถึง IE11 สำหรับ JS
Frank Puffer

4
@DVK: ใช่ debuggers โลกแห่งความจริงไม่ได้ทำสิ่งที่ฉันเสนอ แต่นั่นไม่เกี่ยวข้องกับส่วนแรกของความคิดเห็นของฉัน ฉันแค่คิดว่ามันบ้าไปแล้วที่จะคิดว่าคนที่จะรักษารหัสของฉันไว้ส่วนใหญ่จะมีปฏิสัมพันธ์กับมันโดยใช้ดีบักเกอร์ ตัวแก้จุดบกพร่องนั้นยอดเยี่ยมสำหรับจุดประสงค์บางอย่าง แต่หากพวกเขาได้รับเครื่องมือหลักในการวิเคราะห์รหัสฉันจะบอกว่ามีบางอย่างผิดปกติอย่างยิ่งและฉันพยายามแก้ไขมันแทนที่จะทำให้รหัสนั้น debuggable ได้มากขึ้น
Frank Puffer

47

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

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


1
ที่มีส่วนเกี่ยวข้องกับการกำหนดรูปแบบเหมือนกับการใช้ตัวแปร คำถามได้ถูกแก้ไขแล้ว
Jodrell

29

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

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

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


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

15

อาจจะ.

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

การเน้นไวยากรณ์ที่มีให้โดยเครื่องมือแก้ไขโค้ดอาจช่วยลดข้อกังวลดังกล่าวได้ในระดับหนึ่ง

สำหรับสายตาของฉันนี่คือการประนีประนอมที่ดีกว่า:

PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "abc").acquire();

ดังนั้นการรักษาตัวแปรท้องถิ่นหนึ่งและทิ้งอีกหนึ่งตัวแปร ใน C # ฉันจะใช้คำหลัก var ในบรรทัดแรกเนื่องจาก typecast ให้ข้อมูลประเภทแล้ว


13

แม้ว่าฉันจะยอมรับความถูกต้องของคำตอบที่นิยมตัวแปรในท้องถิ่น แต่ฉันจะเล่นเป็นทนายของปีศาจและนำเสนอมุมมองที่แตก

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

ในกรณีของคุณปัญหาหลักคือการเยื้องและไม่ขาดตัวแปร คุณสามารถ (และควร) จัดรูปแบบโค้ดดังนี้:

((PowerManager)getSystemService(POWER_SERVICE))
        .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MyWakelockTag")
        .acquire();

เปรียบเทียบกับรุ่นที่มีตัวแปรท้องถิ่น:

PowerManager powerManager=(PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"abc");
wakeLock.acquire();

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

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

สามความคิดเห็นเพิ่มเติม:

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

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

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


เกี่ยวกับความคิดเห็นเพิ่มเติมของคุณ: 1. สิ่งนี้ไม่น่าเป็นปัญหาหากคุณทำตามอนุสัญญาว่าตัวแปรท้องถิ่นถูกประกาศอย่างใกล้ชิดมากที่สุดเท่าที่จะเป็นไปได้ว่ามันถูกใช้ไปที่ไหนและคุณกำลังทำให้วิธีการของคุณยาวพอสมควร 2. ไม่ได้อยู่ในภาษาการเขียนโปรแกรมที่มีการอนุมานประเภท 3. โค้ดที่เขียนได้ดีมักไม่จำเป็นต้องมีตัวดีบัก
Robert Harvey

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

1
ฉันคิดว่าคำถามสันนิษฐานว่าตัวแปรท้องถิ่นมีหน้าที่ซ้ำซ้อนดังนั้นจึงจำเป็นต้องใช้อินเทอร์เฟซชนิดนั้นสำหรับเคส เราอาจจะประสบความสำเร็จในการลบคนในท้องถิ่นถ้ามันเป็นอย่างอื่นผ่านหลาย contortions แต่ฉันคิดว่าเรากำลังมองกรณีที่ง่าย
rghome

1
ฉันพบว่าตัวแปรของคุณแย่ลงกว่าเดิมสำหรับการอ่านได้ ฉันอาจได้รับ VB.net มากเกินไป แต่ก่อนอื่นเมื่อฉันเห็นตัวอย่างของคุณคือ "คำสั่ง WITH ที่หายไปไหนฉันไม่ได้ลบโดยบังเอิญหรือไม่" ฉันต้องดูสองครั้งว่าเกิดอะไรขึ้นและมันก็ไม่ดีเมื่อฉันต้องทบทวนรหัสอีกหลายเดือนต่อมา
Tonny

1
@Tonny สำหรับฉันมันสามารถอ่านได้มากขึ้น: ชาวบ้านไม่ได้เพิ่มอะไรที่ไม่ชัดเจนจากบริบท ฉันมีหัว Java ของฉันดังนั้นจึงไม่มีwithคำสั่ง มันจะเป็นการจัดรูปแบบปกติใน Java
rghome

6

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

ในแรงจูงใจของการปรับสภาพหลังเหล่านี้ฟาวเลอร์เขียน :

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

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

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


1
คำตอบที่ดี รหัสที่ดีที่สุดที่ฉันเขียน (และได้เห็นจากผู้อื่น) มักจะมีวิธีการเล็ก ๆ (2, 3 บรรทัด) ที่สามารถใช้แทนตัวแปรชั่วคราว ที่เกี่ยวข้องคือการโต้เถียงวิธีนี้ใช้วิธีส่วนตัวเหม็น .... คิดออกและฉันมักจะพบมันจริง
Stijn de Witt

temps ในคำถามนี้อ้างถึงฟังก์ชั่นอยู่แล้วดังนั้นคำตอบนี้ไม่เกี่ยวข้องกับคำถาม OPs
user949300

@ user949300 ฉันไม่คิดว่ามันไม่เกี่ยวข้อง ใช่อุณหภูมิในตัวอย่างเฉพาะของ OP คือค่าส่งคืนจากฟังก์ชันหรือวิธีการ แต่ OP มีความชัดเจนมากซึ่งเป็นเพียงตัวอย่างสถานการณ์ คำถามจริงคือคำถามทั่วไป "เราควรกำจัดตัวแปรชั่วคราวเมื่อเราทำได้?"
Jonathan Hartley

1
ตกลงฉันขายแล้ว ** พยายามเปลี่ยนคะแนนของฉัน แต่บ่อยครั้งเมื่อฉันไปเกินขอบเขตที่ จำกัด ของคำถาม OP ฉันได้รับการ dinged ดังนั้นอาจไม่แน่นอน ... :-) เอ้อไม่สามารถเปลี่ยนการลงคะแนนของฉันจนกว่าคุณจะแก้ไขอะไรก็ตาม ...
user949300

@ user949300 Holy cow! คนที่เปลี่ยนใจเมื่อพิจารณาประเด็นอย่างรอบคอบ! คุณครับเป็นสิ่งที่หายากและฉันปิดฝาของฉันกับคุณ
Jonathan Hartley

3

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

acquireWakeLock(POWER_SERVICE, PARTIAL, "abc");

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


2

ลองพิจารณากฎของ Demeterที่นี่ ในบทความ Wikipedia ที่ LoD มีการระบุไว้ดังต่อไปนี้:

กฎของ Demeter สำหรับฟังก์ชั่นกำหนดให้เมธอด m ของวัตถุ O อาจเรียกใช้เมธอดของวัตถุประเภทต่อไปนี้เท่านั้น: [2]

  1. โอตัวเอง
  2. พารามิเตอร์ของ m
  3. วัตถุใด ๆ ที่สร้าง / อินสแตนซ์ภายใน m
  4. วัตถุองค์ประกอบโดยตรงของ O
  5. ตัวแปรโกลบอลซึ่งสามารถเข้าถึงได้โดย O ในขอบเขต m

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

((PowerManager)getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MyWakelockTag").acquire();

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

PowerManager powerManager=(PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock=powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"abc");
wakeLock.acquire();

แสดงให้เห็นอย่างชัดเจนว่าคุณกำลังพยายามทำอะไร - คุณได้รับวัตถุ PowerManager รับวัตถุ WakeLock จาก PowerManager แล้วรับ WakeLock กฎต่อไปนี้ # 3 ด้านล่างของ LoD - คุณกำลังทำให้วัตถุเหล่านี้เป็นอินสแตนซ์ภายในรหัสของคุณและสามารถใช้มันเพื่อทำสิ่งที่คุณต้องการ

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

ขอให้โชคดี


3
ฉันสนใจว่าไวยากรณ์ของของไหลเข้ากับคำตอบนี้ได้อย่างไร ด้วยการจัดรูปแบบที่ถูกต้องไวยากรณ์ของเหลวสามารถอ่านได้สูง
Gusdor

12
นั่นไม่ใช่สิ่งที่ "กฎหมายของ Demeter" หมายถึง กฎหมายของ Demeter ไม่ใช่แบบฝึกหัดการนับจุด มันเป็นหลักหมายถึง "พูดคุยกับเพื่อนของคุณทันที"
Robert Harvey

5
การเข้าถึงวิธีการและสมาชิกเดียวกันผ่านตัวแปรกลางยังคงเป็นการละเมิดกฎหมายของ Demeter อย่างรุนแรง คุณเพิ่งบดบังมันไม่ได้แก้ไข
Jonathan Hartley

2
ในแง่ของ "กฎหมายของ Demeter" ทั้งสองรุ่นมีความเท่าเทียมกัน "ไม่ดี"
Hulk

@Gusdor เห็นด้วยนี่เป็นความคิดเห็นเพิ่มเติมเกี่ยวกับสไตล์ในตัวอย่างคำถาม คำถามได้ถูกแก้ไขแล้ว
Jodrell

2

สิ่งหนึ่งที่ควรทราบคือรหัสนั้นอ่านบ่อยๆเพื่อ "ความลึก" ที่แตกต่างกัน รหัสนี้:

PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "abc");
wakeLock.acquire();

ง่ายต่อการ "เรียด" มันเป็นงบ 3 PowerManagerครั้งแรกที่เรามาด้วย WakeLockจากนั้นเราก็มาด้วย แล้วเรา ฉันเห็นได้อย่างง่ายดายจริง ๆ เพียงแค่ดูจุดเริ่มต้นของแต่ละบรรทัด การกำหนดตัวแปรอย่างง่ายนั้นง่ายต่อการจดจำเพียงบางส่วนว่า "Type varName = ... " และเพียงแค่อ่านสกิลเหนือ "... " ในทำนองเดียวกันคำแถลงสุดท้ายนั้นเห็นได้ชัดว่าไม่ใช่รูปแบบของการมอบหมาย แต่มันเกี่ยวข้องกับชื่อเพียงสองชื่อดังนั้น "ส่วนสำคัญหลัก" จึงปรากฏขึ้นทันที บ่อยครั้งที่ฉันต้องรู้ว่าฉันแค่พยายามตอบว่า "รหัสนี้ทำอะไร?" ในระดับสูงacquirewakeLock

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

((PowerManager)getSystemService(POWER_SERVICE))
    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag")
    .acquire();

ตอนนี้มันเป็นคำสั่งเดียวทั้งหมด โครงสร้างระดับบนสุดไม่ใช่เรื่องง่ายที่จะอ่าน ในเวอร์ชั่นดั้งเดิมของ OP โดยไม่มีการแพร่กระจายและการเยื้องเพื่อสื่อสารโครงสร้างใด ๆ ที่มองเห็นได้ฉันจะต้องนับวงเล็บเพื่อถอดรหัสมันเป็นลำดับ 3 ขั้นตอน หากนิพจน์หลายส่วนบางส่วนซ้อนกันภายในแทนที่จะจัดเรียงเป็นสายโซ่ของการเรียกเมธอดมันก็ยังคงออกมาดูคล้ายกับเรื่องนี้ดังนั้นฉันจึงต้องระมัดระวังเกี่ยวกับการไว้วางใจโดยไม่ต้องนับวงเล็บ และถ้าฉันเชื่อมั่นในการเยื้องและแค่อ่านล่วงหน้าไปยังสิ่งสุดท้ายในฐานะที่เป็นจุดสันนิษฐานของสิ่งนี้.acquire()บอกฉันเกี่ยวกับตัวเอง?

บางครั้งนั่นอาจเป็นสิ่งที่คุณต้องการ ถ้าฉันใช้การแปลงครึ่งทางของคุณและเขียนว่า:

WakeLock wakeLock =
     ((PowerManeger)getSystemService(POWER_SERVICE))
    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLockTage");
wakeLock.acquire();

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

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


ถึงฉันแม้ว่าฉันจะไม่มีความรู้เกี่ยวกับ API แต่ก็เป็นที่ชัดเจนมากว่าโค้ดทำอะไรถึงแม้ว่าจะไม่มีตัวแปรก็ตาม getSystemService(POWER_SERVICE)รับตัวจัดการพลังงาน .newWakeLockได้รับการล็อคปลุก .acquireซื้อมัน ตกลงฉันไม่รู้ทุกประเภท แต่ฉันสามารถหามันได้ใน IDE ถ้าฉันต้องการ
rghome

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

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

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

2

นี้ก็เป็น antipattern ที่มีชื่อของตัวเอง: ซากรถไฟ มีหลายเหตุผลที่ควรหลีกเลี่ยงการเปลี่ยนใหม่:

  • อ่านยาก
  • ยากต่อการดีบัก (ทั้งเพื่อดูตัวแปรและตรวจจับตำแหน่งข้อยกเว้น)
  • การละเมิดกฎหมาย Demeter (LoD)

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

ยังจำได้ว่าการอ้างอิงถึงวัตถุนั้นราคาถูกจริงๆ


Ehm ไม่ใช่รหัสที่แก้ไขซึ่งกำหนดวิธีการผูกมัดหรือไม่ แก้ไขอ่านบทความที่เชื่อมโยงดังนั้นฉันจึงเห็นความแตกต่างในขณะนี้ ขอขอบคุณบทความดี ๆ
Stijn de Witt

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

ฉันเดาว่าตรงข้ามกับ "Train Wreck" คือ "Local Line" มันเป็นรถไฟที่อยู่ระหว่างสองเมืองใหญ่ที่จอดอยู่ที่หมู่บ้านทุกประเทศในระหว่างนั้นในกรณีที่มีคนต้องการขึ้นหรือลงแม้ว่าวันส่วนใหญ่จะไม่มีใครทำ
rghome

1

คำถามที่กล่าวมาคือ "เราควรกำจัดตัวแปรท้องถิ่นถ้าทำได้"?

ไม่คุณไม่ควรกำจัดเพียงเพราะคุณทำได้

คุณควรปฏิบัติตามแนวทางการเข้ารหัสในร้านของคุณ

สำหรับตัวแปรท้องถิ่นส่วนใหญ่ทำให้การอ่านและการดีบักง่ายขึ้น

ฉันชอบสิ่งที่คุณทำกับ PowerManager powerManagerผมชอบสิ่งที่คุณทำกับสำหรับฉันตัวเล็กของคลาสหมายถึงใช้เพียงครั้งเดียว

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

using(SQLconnection conn = new SQLconnnection())
{
    using(SQLcommand cmd = SQLconnnection.CreateCommand())
    {
    }
}

สิ่งนี้ไม่เกี่ยวข้องกับตัวแปรท้องถิ่น แต่เป็นการล้างทรัพยากร ... ฉันไม่เชี่ยวชาญใน C # แต่ฉันจะแปลกใจถ้าusing(new SQLConnection()) { /* ... */ }ไม่ถูกกฎหมายด้วย (ไม่ว่าจะเป็นประโยชน์หรือไม่ก็เป็นเรื่องที่แตกต่างกัน :)
Stijn de Witt

1

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

สิ่งที่คุณต้องการไม่ใช่ตัวแปรที่สามารถแก้ไขค่าได้ แต่เป็นค่าคงที่ชั่วคราว ดังนั้นให้แสดงรหัสนี้ในภาษาของคุณหากภาษาของคุณอนุญาต ใน Java คุณสามารถใช้finalและใน C ++ constคุณสามารถใช้ ในภาษาที่ใช้งานได้มากที่สุดนี่เป็นพฤติกรรมเริ่มต้น

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


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

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

1
ตัวอย่างเช่นสมมติว่าฉันต้องตรวจสอบค่าและถ้าเกิน 10 ไฟแจ้งเตือน getFoo()สมมติว่าค่านี้มาจากวิธีการ ถ้าฉันหลีกเลี่ยงการประกาศในท้องถิ่นฉันจะจบลงด้วยif(getFoo() > 10) alert(getFoo());แต่ getFoo () อาจส่งคืนค่าที่แตกต่างกันในการโทรที่แตกต่างกันสองสาย ฉันสามารถส่งการแจ้งเตือนด้วยค่าที่น้อยกว่าหรือเท่ากับ 10 ซึ่งทำให้เกิดความสับสนที่ดีที่สุดและจะกลับมาหาฉันเป็นข้อบกพร่อง สิ่งชนิดนี้มีความสำคัญมากกว่าพร้อมกัน การมอบหมายงานในพื้นที่คืออะตอม
JimmyJames

จุดที่ดีมาก อาจเป็นเหตุผลที่ฉันไม่ชอบคนในท้องถิ่นเหล่านี้ .... คุณจะทำอะไรกับPowerManagerอินสแตนซ์นั้นหลังจากเรียกวิธีนี้หรือไม่? ให้ฉันตรวจสอบส่วนที่เหลือของวิธีการ .... mmm ok ไม่ และWakeLockสิ่งนี้ คุณได้รับ ... คุณกำลังทำอะไรกับสิ่งนั้น (กำลังสแกนวิธีที่เหลืออีกครั้ง) ... mmm ไม่มีอะไรอีกแล้ว ... ตกลงทำไมพวกเขาถึงอยู่ที่นั่น? โอ้ใช่ควรจะอ่านได้มากขึ้น ... แต่ถ้าไม่มีพวกเขาฉันแน่ใจว่าคุณจะไม่ใช้พวกเขาอีกต่อไปดังนั้นฉันไม่ต้องอ่านวิธีการที่เหลือ
Stijn de Witt

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