ตัวแปรชั่วคราวเทียบกับข้อกำหนดความยาวบรรทัด


10

ฉันได้อ่านRefactoringของ Martin Fowler แล้ว โดยทั่วไปถือว่ายอดเยี่ยม แต่คำแนะนำอย่างหนึ่งของ Fowler ดูเหมือนจะก่อให้เกิดปัญหาเล็กน้อย

ฟาวเลอร์แนะนำให้คุณแทนที่ตัวแปรชั่วคราวด้วยเคียวรีดังนั้นแทนที่จะเป็น:

double getPrice() {
    final int basePrice = _quantity * _itemPrice;
    final double discountFactor;
    if (basePrice > 1000) discountFactor = 0.95;
    else discountFactor = 0.98;
    return basePrice * discountFactor;
}

คุณดึงออกมาเป็นวิธีการช่วยเหลือ:

double basePrice() {
    return _quantity * _itemPrice;
}

double getPrice() {
    final double discountFactor;
    if (basePrice() > 1000) discountFactor = 0.95;
    else discountFactor = 0.98;
    return basePrice() * discountFactor;
}

โดยทั่วไปฉันเห็นด้วยยกเว้นเหตุผลหนึ่งที่ฉันใช้ตัวแปรชั่วคราวคือเมื่อบรรทัดยาวเกินไป ตัวอย่างเช่น:

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

ถ้าฉันลอง inline มันเส้นจะยาวกว่า 80 ตัวอักษร

อีกทางหนึ่งฉันจบด้วย chain of code ซึ่งตัวมันเองไม่ได้อ่านง่ายกว่านี้มาก:

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

มีกลยุทธ์อะไรบ้างในการปรับยอดทั้งสอง


10
80 ตัวอักษรเป็น 1 ใน 3 ของจอภาพของฉัน คุณแน่ใจหรือว่าการผสานกับสายอักขระ 80 รายการยังคงคุ้มค่าสำหรับคุณ
jk

10
ใช่ดูตัวอย่างprogrammers.stackexchange.com/questions/604/…
Kevin Burke

ตัวอย่าง$hostและคุณ$uriเป็นประเภทที่มีการวางแผนแม้ว่า - โฮสต์จะถูกอ่านจากการตั้งค่าหรืออินพุตอื่น ๆ ฉันต้องการให้พวกเขาอยู่ในบรรทัดเดียวกันแม้ว่ามันจะพันหรือหลุดจากขอบ
Izkata

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

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

คำตอบ:


16

วิธีการ
1. มีข้อจำกัดความยาวของบรรทัดเพื่อให้คุณเห็น + เข้าใจโค้ดเพิ่มเติม พวกเขายังคงใช้ได้
2. เน้นการตัดสินในช่วงการประชุมคนตาบอด
3. หลีกเลี่ยงตัวแปรชั่วคราวจนกว่าจะปรับประสิทธิภาพให้เหมาะสม
4. หลีกเลี่ยงการใช้การเยื้องลึกสำหรับการจัดตำแหน่งในงบหลายบรรทัด
5. แบ่งข้อความยาวเป็นหลายบรรทัดตามขอบเขตความคิด :

// prefer this
var distance = Math.Sqrt(
    Math.Pow(point2.GetX() - point1.GetX(), 2) + // x's
    Math.Pow(point2.GetY() - point1.GetY(), 2)   // y's
);

// over this
var distance = Math.Sqrt(Math.Pow(point2.GetX() -
    point1.GetX(), 2) + Math.Pow(point2.GetY() -
    point1.GetY(), 2)); // not even sure if I typed that correctly.

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

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

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

บางตัวอย่าง
1. PEP 8 Style Guide for python (ไม่ใช่ตัวอย่างที่สวยที่สุด)
2. Paul M Jones บน Guide Style Pear (ตรงกลางของอาร์กิวเมนต์ถนน)
3. ความยาวบรรทัดของออราเคิล + ระเบียบการห่อ (มี stratagems ที่มีประโยชน์สำหรับเก็บ 80 ตัวอักษร)
4. MDN Java Practices (เน้นการตัดสินของโปรแกรมเมอร์เกี่ยวกับการประชุม)


1
อีกส่วนหนึ่งของปัญหาคือตัวแปรชั่วคราวมักจะอยู่ได้นานกว่าค่าของมัน ไม่ใช่ปัญหาในบล็อคขอบเขตขนาดเล็ก แต่ในปัญหาที่ใหญ่กว่าใช่ปัญหาใหญ่
Ross Patterson

8
หากคุณกังวลเกี่ยวกับการเปลี่ยนแปลงชั่วคราวให้วาง const ไว้บนนั้น
Thomas Eding

3

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

(โปรดแก้ไขฉันถ้าฉันผิด)


3

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

ให้ฉันดูตัวอย่างที่คุณโพสต์

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

การสังเกตของฉันคือการเรียก twilio api ทั้งหมดจะเริ่มต้นด้วย "https://api.twilio.com/2010-04-1/" และดังนั้นจึงมีฟังก์ชั่นการใช้ซ้ำได้อย่างชัดเจน:

$uri = twilioURL("Accounts/$accountSid/Usage/Records/AllTime")

ที่จริงแล้วฉันคิดว่าเหตุผลเดียวในการสร้าง URL คือการขอดังนั้นฉันจะทำ:

$response = TwilioApi::makeRequest("Accounts/$accountSid/Usage/Records/AllTime")

อันที่จริงแล้ว URL จำนวนมากเริ่มต้นด้วย "Accounts / $ accountSid" ดังนั้นฉันอาจจะดึงข้อมูลนั้นด้วย:

$response = TwilioApi::makeAccountRequest($accountSid, "Usage/Records/AllTime")

และถ้าเราทำให้ twilio api เป็นวัตถุที่มีหมายเลขบัญชีเราสามารถทำสิ่งต่อไปนี้:

$response = $twilio->makeAccountRequest("Usage/Records/AllTime")

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

ลองดูที่อื่น

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

ที่นี่ฉันคิดอย่างใดอย่างหนึ่ง:

$params = MustacheOptions::buildFromParams($bagcheck->getParams());

หรือ

$params = MustacheOptions::build($bagcheck->getFlatParams());

หรือ

$params = MustacheOptions::build(flatParams($backCheck));

ขึ้นอยู่กับว่าเป็นสำนวนที่ใช้ซ้ำได้มากขึ้น


1

อันที่จริงฉันไม่เห็นด้วยกับนายฟาวเลอร์ผู้มีชื่อเสียงในเรื่องนี้ในกรณีทั่วไป

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

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

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

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

double? _basePrice; //not sure if Java has C#'s "nullable" concept
double basePrice(bool forceCalc)
{
   if(forceCalc || !_basePrice.HasValue)
      return _basePrice = _quantity * _itemPrice;
   return _basePrice.Value;
}

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


1

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

ตัวอย่างเช่นตัวอย่างของคุณอ้างอิง:

double getPrice() {
    final double discountFactor;
    if (basePrice() > 1000) discountFactor = 0.95;      <--- first call
    else discountFactor = 0.98;
    return basePrice() * discountFactor;                <--- second call
}

ชัดเจน_quantityและ_itemPriceเป็นตัวแปรกลาง (หรืออย่างน้อยระดับคลาส) และดังนั้นจึงมีความเป็นไปได้สำหรับพวกเขาที่จะแก้ไขนอกgetPrice()

ดังนั้นจึงมีความเป็นไปได้ที่การโทรครั้งแรกไปbasePrice()ยังการคืนค่าที่แตกต่างจากการโทรครั้งที่สอง!

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


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

double getPrice()
{
    final double basePrice      = calculateBasePrice();
    final double discountFactor = calculateDiscount( basePrice );

    return basePrice * discountFactor;
}

การแบ่งพาร์ติชันออกจากระดับที่แน่นอนจะทำให้รหัสอ่านน้อยลง


+1 สำหรับการสร้างโค้ดให้อ่านน้อยลง การแบ่งพาร์ทิชันสามารถซ่อนปัญหาทางธุรกิจที่ซอร์สโค้ดพยายามแก้ไข อาจมีกรณีพิเศษที่มีการนำคูปองไปใช้ใน getPrice () แต่ถ้าซ่อนอยู่ลึกเข้าไปในสายของฟังก์ชันการโทรกฎธุรกิจก็จะถูกซ่อนเช่นกัน
Reactgular

0

หากคุณทำงานในภาษาที่มีพารามิเตอร์ที่ระบุชื่อ (ObjectiveC, Python, Ruby, ฯลฯ ), temp vars จะมีประโยชน์น้อยกว่า

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

เช่นเดียวกับคุณฉันใช้ตัวแปรชั่วคราวสำหรับการพิจารณาความชัดเจนและความยาวบรรทัด

ฉันเคยเห็นโปรแกรมเมอร์ทำต่อไปนี้ใน PHP มันน่าสนใจและยอดเยี่ยมสำหรับการดีบั๊ก แต่มันแปลกเล็กน้อย

$rs = DB::query( $query = "SELECT * FROM table" );
if (DEBUG) echo $query;
// do something with $rs

0

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

วิธีการใหม่นั้นสามารถนำมาใช้ในวิธีอื่น ๆ

    double basePrice = _quantity * _itemPrice;
    if (basePrice > 1000)
        return basePrice * 0.95;
    else
        return basePrice * 0.98;

           http://i.stack.imgur.com/mKbQM.gif

    if (basePrice() > 1000)
        return basePrice() * 0.95;
    else
        return basePrice() * 0.98;
...
double basePrice() {
    return _quantity * _itemPrice;
}

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

หากเป็นกรณีนี้เนื่องจากเนมสเปซฉันจะไม่กำหนดเมธอด uri สากล () หรือโฮสต์ () แต่ชื่อที่มีข้อมูลเพิ่มเติมเช่น twilio_host () หรือ archive_request_uri ()

จากนั้นสำหรับปัญหาความยาวบรรทัดฉันเห็นหลายตัวเลือก:

  • uri = archive_request_uri()สร้างตัวแปรท้องถิ่นเช่น

เหตุผล: ในวิธีการปัจจุบันคุณต้องการให้ URI เป็นวิธีที่กล่าวถึง คำจำกัดความของ URI นั้นยังคงเป็นตัวประกอบ

  • กำหนดวิธีการในท้องถิ่นเช่น uri() { return archive_request_uri() }

หากคุณใช้คำแนะนำของ Fowler บ่อยครั้งคุณจะรู้ว่าวิธี uri () เป็นรูปแบบเดียวกัน

ถ้าเลือกภาษาคุณต้องใช้วิธีการในตัวเองด้วย 'ตัวเอง' ฉันจะแนะนำวิธีแก้ปัญหาแรกเพื่อเพิ่มความชัดเจน (ใน Python ฉันจะกำหนดฟังก์ชัน uri ภายในวิธีการปัจจุบัน)

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