ความสามารถในการอ่านและการบำรุงรักษากรณีพิเศษของการเขียนการเรียกฟังก์ชันที่ซ้อนกัน


57

รูปแบบการเข้ารหัสของฉันสำหรับการเรียกใช้ฟังก์ชันที่ซ้อนกันมีดังต่อไปนี้:

var result_h1 = H1(b1);
var result_h2 = H2(b2);
var result_g1 = G1(result_h1, result_h2);
var result_g2 = G2(c1);
var a = F(result_g1, result_g2);

ฉันเพิ่งเปลี่ยนเป็นแผนกที่ใช้รูปแบบการเข้ารหัสต่อไปนี้เป็นอย่างมาก:

var a = F(G1(H1(b1), H2(b2)), G2(c1));

ผลของวิธีการเข้ารหัสของฉันคือในกรณีที่ฟังก์ชั่นการขัดข้อง Visual Studio สามารถเปิดการถ่ายโอนข้อมูลที่สอดคล้องกันและระบุบรรทัดที่ปัญหาเกิดขึ้น (ฉันกังวลเป็นพิเศษเกี่ยวกับการละเมิดการเข้าถึง)

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

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

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

ฉันไม่รู้ว่ามันเกี่ยวข้องหรือไม่ แต่เรากำลังทำงานใน C ++ (STL) / C #


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

9
โปรดทราบว่าหากตัวอย่างนี้จะถูกนำไปใช้กับ C ++ (ตามที่คุณพูดถึงสิ่งนี้ถูกใช้ในโครงการของคุณ) นี่ไม่ใช่แค่คำถามของรูปแบบเท่านั้นเนื่องจากลำดับของการประเมินผลHXและGXการร้องขออาจเปลี่ยนแปลงในหนึ่งซับ ลำดับของการประเมินผลของฟังก์ชั่นการโต้แย้งไม่ได้ระบุ หากคุณมีเหตุผลบางอย่างขึ้นอยู่กับคำสั่งของผลข้างเคียง (อย่างรู้เท่าทันหรือไม่ทราบ) ในการเรียกร้อง "การปรับโครงสร้างสไตล์" นี้อาจทำให้เกิดผลมากกว่าการอ่าน / การบำรุงรักษา
dfri

4
เป็นชื่อตัวแปรresult_g1ที่คุณใช้จริง ๆ หรือค่านี้เป็นตัวแทนของบางสิ่งบางอย่างที่มีชื่อที่สมเหตุสมผล percentageIncreasePerSecondเช่น นั่นจะเป็นการทดสอบของฉันในการตัดสินใจระหว่างสองคน
Richard Tingle

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

4
@ t3chb0t คุณมีอิสระที่จะลงคะแนน แต่คุณชอบ แต่โปรดระวังในการกระตุ้นให้เกิดคำถามที่ดีมีประโยชน์และเป็นประโยชน์ในหัวข้อนี้ในเว็บไซต์นี้ (และทำให้คนที่ไม่ดีแย่) ว่าจุดประสงค์ของการลงคะแนนคำถามขึ้นหรือลง เพื่อระบุว่าคำถามนั้นมีประโยชน์และชัดเจนหรือไม่ดังนั้นการลงคะแนนด้วยเหตุผลอื่นเช่นการใช้การลงคะแนนเป็นวิธีการวิจารณ์ในโค้ดตัวอย่างที่โพสต์เพื่อช่วยบริบทของคำถามโดยทั่วไปจะไม่มีประโยชน์ในการรักษาคุณภาพของเว็บไซต์ : softwareengineering.stackexchange.com/help/privileges/vote-down
Ben Cottrell

คำตอบ:


111

หากคุณรู้สึกว่าถูกบังคับให้ต้องขยายสายการบินหนึ่งสาย

 a = F(G1(H1(b1), H2(b2)), G2(c1));

ฉันจะไม่โทษคุณ ไม่เพียงอ่านยากเท่านั้นมันยากที่จะแก้ไขข้อผิดพลาด

ทำไม?

  1. มันหนาแน่น
  2. ผู้ดีบักบางคนจะเน้นเฉพาะเรื่องทั้งหมดในคราวเดียว
  3. ไม่มีชื่อที่สื่อความหมาย

หากคุณขยายด้วยผลลัพธ์ระดับกลางคุณจะได้รับ

 var result_h1 = H1(b1);
 var result_h2 = H2(b2);
 var result_g1 = G1(result_h1, result_h2);
 var result_g2 = G2(c1);
 var a = F(result_g1, result_g2);

และยังอ่านยาก ทำไม? แก้ปัญหาสองข้อและแนะนำข้อที่สี่:

  1. มันหนาแน่น
  2. ผู้ดีบักบางคนจะเน้นเฉพาะเรื่องทั้งหมดในคราวเดียว
  3. ไม่มีชื่อที่สื่อความหมาย
  4. มันรกด้วยชื่อที่ไม่สื่อความหมาย

หากคุณขยายด้วยชื่อที่เพิ่มความหมายใหม่ดีความหมายดียิ่งขึ้น! ชื่อที่ดีช่วยให้ฉันเข้าใจ

 var temperature = H1(b1);
 var humidity = H2(b2);
 var precipitation = G1(temperature, humidity);
 var dewPoint = G2(c1);
 var forecast = F(precipitation, dewPoint);

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

หากคุณทำมันด้วยชื่อที่ไม่มีความหมายเช่นresult_thisและresult_thatเพราะคุณไม่สามารถนึกถึงชื่อที่ดีได้ฉันก็อยากให้คุณช่วยถ่วงชื่อที่ไม่มีความหมายและขยายมันโดยใช้ช่องว่างเก่า ๆ ที่ดี:

int a = 
    F(
        G1(
            H1(b1), 
            H2(b2)
        ), 
        G2(c1)
    )
;

มันสามารถอ่านได้ถ้าไม่มากไปกว่าที่มีชื่อผลลัพธ์ที่ไม่มีความหมาย (ไม่ใช่ว่าชื่อฟังก์ชันเหล่านี้ยอดเยี่ยม)

  1. มันหนาแน่น
  2. ผู้ดีบักบางคนจะเน้นเฉพาะเรื่องทั้งหมดในคราวเดียว
  3. ไม่มีชื่อที่สื่อความหมาย
  4. มันรกด้วยชื่อที่ไม่สื่อความหมาย

เมื่อคุณไม่สามารถนึกถึงชื่อที่ดีได้มันก็เป็นอย่างดี

ด้วยเหตุผลบางประการที่นักดีบักชอบบรรทัดใหม่ดังนั้นคุณควรพบว่าการดีบั๊กนี้ไม่ยาก:

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

หากยังไม่พอให้จินตนาการG2()ถูกเรียกมากกว่าหนึ่งแห่งแล้วสิ่งนี้ก็เกิดขึ้น:

Exception in thread "main" java.lang.NullPointerException
    at composition.Example.G2(Example.java:34)
    at composition.Example.main(Example.java:18)

ฉันคิดว่ามันดีที่การG2()โทรแต่ละครั้งจะอยู่ในสายของตัวเองสไตล์นี้จะนำคุณไปยังสายที่ละเมิดหลักโดยตรง

ดังนั้นโปรดอย่าใช้ปัญหา 1 และ 2 เป็นข้ออ้างที่จะทำให้เรามีปัญหา 4 ใช้ชื่อที่ดีเมื่อคุณนึกถึงพวกเขา หลีกเลี่ยงชื่อที่ไม่มีความหมายเมื่อคุณทำไม่ได้

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

var user = db.t_ST_User.Where(_user => string.Compare(domain,  
_user.domainName.Trim(), StringComparison.OrdinalIgnoreCase) == 0)
.Where(_user => string.Compare(samAccountName, _user.samAccountName.Trim(), 
StringComparison.OrdinalIgnoreCase) == 0).Where(_user => _user.deleted == false)
.FirstOrDefault();

ฉันเกลียดการดูเสียงดังแม้ไม่จำเป็นต้องห่อคำ นี่คือรูปลักษณ์ภายใต้สไตล์นี้:

var user = db
    .t_ST_User
    .Where(
        _user => string.Compare(
            domain, 
            _user.domainName.Trim(), 
            StringComparison.OrdinalIgnoreCase
        ) == 0
    )
    .Where(
        _user => string.Compare(
            samAccountName, 
            _user.samAccountName.Trim(), 
            StringComparison.OrdinalIgnoreCase
        ) == 0
    )
    .Where(_user => _user.deleted == false)
    .FirstOrDefault()
;

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


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

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

4
รุ่นที่ขยายดูน่าเกลียด มี ช่องว่างมากเกินไปที่นั่นซึ่งจะช่วยลดผลกระทบที่มีต่อสิ่งมีชีวิต phasizedwithit, ความหมายของมัน
Mateen Ulhaq

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

3
ต่างจาก @MateenUlhaq ฉันอยู่ในรั้วเกี่ยวกับช่องว่างในตัวอย่างนี้ด้วยชื่อฟังก์ชันดังกล่าว แต่ด้วยชื่อฟังก์ชันจริง (ซึ่งมีความยาวมากกว่าสองตัวอักษรใช่มั้ย) อาจเป็นสิ่งที่ฉันต้องการ
การแข่งขัน Lightness กับโมนิก้า

50

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

ฉันไม่เห็นด้วยอย่างยิ่งกับสิ่งนี้ เพียงดูตัวอย่างรหัสสองอันของคุณเรียกสิ่งนี้ว่าไม่ถูกต้อง:

var a = F(G1(H1(b1), H2(b2)), G2(c1));

ได้ยินที่จะอ่าน "การอ่าน" ไม่ได้หมายถึงความหนาแน่นของข้อมูล มันหมายถึง "ง่ายต่อการอ่านเข้าใจและบำรุงรักษา"

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

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

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


37
ในขณะที่ฉันเห็นด้วยว่าF(G1(H1(b1), H2(b2)), G2(c1))อ่านยาก แต่เรื่องนี้ไม่เกี่ยวกับการยัดเยียดให้แน่นเกินไป (ไม่แน่ใจว่าคุณตั้งใจจะพูดแบบนั้น แต่มันอาจตีความได้ด้วยวิธีนี้) การซ้อนฟังก์ชันสามหรือสี่บรรทัดในหนึ่งบรรทัดสามารถอ่านได้อย่างสมบูรณ์แบบโดยเฉพาะอย่างยิ่งหากฟังก์ชันบางตัวเป็นตัวดำเนินการมัดแบบง่าย มันเป็นชื่อที่ไม่มีคำอธิบายที่เป็นปัญหาที่นี่ แต่ปัญหานั้นยิ่งแย่กว่าในรุ่นหลายบรรทัดซึ่งยังมีชื่อที่ไม่มีคำอธิบายเพิ่มเติม การเพิ่มเฉพาะสำเร็จรูปสำเร็จรูปแทบจะไม่ช่วยให้สามารถอ่านได้
leftaroundabout

23
@leftaroundabout: สำหรับฉันแล้วความยากลำบากคือมันไม่ชัดเจนว่าG1จะรับพารามิเตอร์ 3 ตัวหรือ 2 เท่านั้นและG2เป็นอีกพารามิเตอร์Fหนึ่ง ฉันต้องเหล่และนับวงเล็บ
Matthieu M.

4
@MatthieuM นี่อาจเป็นปัญหาถึงแม้ว่าหากฟังก์ชั่นนั้นเป็นที่รู้จักกันดีก็มักจะเห็นได้ชัดว่าต้องใช้จำนวนอาร์กิวเมนต์เท่าใด ดังที่ฉันได้กล่าวไปแล้วสำหรับฟังก์ชั่นมัดมันชัดเจนทันทีว่าพวกเขาใช้สองข้อโต้แย้ง (นอกจากนี้ไวยากรณ์ที่ใช้วงเล็บ - ทูเปิลภาษาส่วนใหญ่ใช้ทำให้ปัญหานี้แย่ลงในภาษาที่ชอบการแก้ปัญหาโดยชัดแจ้ง: F (G1 (H1 b1) (H2 b2)) (G2 c1).)
leftaroundabout

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

8
ฉันพบว่ารหัสที่ง่ายต่อการตรวจแก้จุดบกพร่องโดยทั่วไปคือรหัสที่ไม่ต้องการแก้ไขข้อบกพร่อง
Rob K

25

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

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

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

ตอนนี้คุณมีโค้ดที่อ่านได้แล้วบั๊กส่วนใหญ่จะเห็นได้ชัดและอย่างน้อยคนอื่น ๆ ก็ยากที่จะซ่อนตัวคุณได้


17

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

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

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


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

double d = sqrt(square(x1 - x0) + square(y1 - y0));

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

double dx = x1 - x0;
double dy = y1 - y0;
double dxSquare = square(dx);
double dySquare = square(dy);
double dSquare = dxSquare + dySquare;
double d = sqrt(dSquare);

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

var a = F(G1(H1(b1), H2(b2)), G2(c1));

ดูเหมือนซับซ้อนมากเกินไปกับผมแม้ว่าจะดำเนินการอย่างใดอย่างหนึ่งน้อยกว่าการคำนวณระยะทาง แน่นอนว่าเป็นผลโดยตรงจากผมไม่ทราบอะไรเกี่ยวกับF(), G1(), G2(), หรือH1() H2()ฉันอาจตัดสินใจแตกต่างกันถ้าฉันรู้เพิ่มเติมเกี่ยวกับพวกเขา แต่นั่นเป็นปัญหาอย่างแม่นยำ: ความซับซ้อนที่เหมาะสมของคำสั่งนั้นขึ้นอยู่กับบริบทและการดำเนินการที่เกี่ยวข้อง และคุณในฐานะโปรแกรมเมอร์คือผู้ที่จะต้องพิจารณาบริบทนี้และตัดสินใจว่าจะรวมอะไรไว้ในคำสั่งเดียว หากคุณสนใจเกี่ยวกับความสามารถในการอ่านคุณไม่สามารถถ่ายความรับผิดชอบนี้ให้กับกฎแบบคงที่บางตัวได้


14

@ Dominique ฉันคิดว่าในการวิเคราะห์คำถามของคุณคุณทำผิดที่ "อ่าน" และ "การบำรุงรักษา" เป็นสองสิ่งที่แยกจากกัน

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

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

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

ฉันจะบอกว่าประเด็นต่อไปนี้มีผลต่อการตัดสิน:

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

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

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

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


3
+1 ถึง "เป็นไปได้ไหมที่จะมีรหัสที่สามารถบำรุงรักษาได้ แต่อ่านไม่ได้" นั่นเป็นความคิดแรกของฉันเช่นกัน
RonJohn

4

ทั้งสองเป็นสิ่งที่ไม่ดี พิจารณาความคิดเห็น

// Calculating torque according to Newton/Dominique, 4th ed. pg 235
var a = F(G1(H1(b1), H2(b2)), G2(c1));

หรือฟังก์ชั่นเฉพาะมากกว่าฟังก์ชั่นทั่วไป:

var a = Torque_NewtonDominique(b1,b2,c1);

เมื่อตัดสินใจว่าผลลัพธ์ใดที่จะสะกดออกมาโปรดคำนึงถึงต้นทุน (คัดลอกเทียบกับการอ้างอิง l-value vs r-value) ความสามารถในการอ่านและความเสี่ยงแต่ละรายการสำหรับแต่ละคำสั่ง

ตัวอย่างเช่นไม่มีค่าเพิ่มจากการย้ายการแปลงหน่วย / ประเภทอย่างง่ายไปยังบรรทัดของตนเองเนื่องจากสิ่งเหล่านี้อ่านง่ายและไม่น่าจะล้มเหลวอย่างมาก:

var radians = ExtractAngle(c1.Normalize())
var a = Torque(b1.ToNewton(),b2.ToMeters(),radians);

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


ค่าใช้จ่ายในการผ่าน ARG: มีสองกฎของการเพิ่มประสิทธิภาพ 1) อย่า 2) (สำหรับผู้เชี่ยวชาญเท่านั้น) อย่าเลย
RubberDuck

1

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

ฉันจะทำให้การอ่านเป็นหนึ่งระหว่าง 80 และ 90 ของการบำรุงรักษา อีกร้อยละ 10-20 เป็นวิธีการแก้ไขให้เป็นไปตาม refactoring

ที่กล่าวว่าคุณผ่านตัวแปร 2 ตัวไปยังฟังก์ชันสุดท้าย (F) ได้อย่างมีประสิทธิภาพ ตัวแปร 2 ตัวนั้นถูกสร้างขึ้นโดยใช้ตัวแปรอื่น ๆ 3 ตัว คุณน่าจะดีกว่าถ้าผ่าน b1, b2 และ c1 ไปยัง F ถ้ามี F อยู่แล้วให้สร้าง D ที่ทำหน้าที่จัดองค์ประกอบของ F และส่งกลับผลลัพธ์ ณ จุดนี้มันเป็นเพียงเรื่องของการให้ชื่อ D ที่ดีและมันจะไม่สำคัญว่าคุณจะใช้รูปแบบใด

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

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


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

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

1

ไม่ว่าคุณจะอยู่ใน C # หรือ C ++ ตราบใดที่คุณอยู่ในบิลด์ debug โซลูชันที่เป็นไปได้ก็คือการห่อฟังก์ชัน

var a = F(G1(H1(b1), H2(b2)), G2(c1));

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

returnType F( params)
{
    returnType RealF( params);
}

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

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

นี่ไม่ใช่กระสุนเงิน แต่เป็นครึ่งทางที่ไม่เลว

ไม่ต้องพูดถึงว่ากลุ่มฟังก์ชั่นการตัดสามารถมีประโยชน์ต่อการอ่านโค้ดได้มากกว่า:

type CallingGBecauseFTheorem( T b1, C b2)
{
     return G1( H1( b1), H2( b2));
}

var a = F( CallingGBecauseFTheorem( b1,b2), G2( c1));

1

ในความคิดของฉันรหัสการจัดทำเอกสารด้วยตนเองดีกว่าสำหรับการบำรุงรักษาและการอ่านโดยไม่คำนึงถึงภาษา

ข้อความที่ให้ไว้ข้างต้นมีความหนาแน่นสูง แต่ "การจัดทำเอกสารด้วยตนเอง":

double d = sqrt(square(x1 - x0) + square(y1 - y0));

เมื่อแตกออกเป็นขั้นตอน (ง่ายขึ้นสำหรับการทดสอบแน่นอน) สูญเสียบริบททั้งหมดตามที่ระบุข้างต้น:

double dx = x1 - x0;
double dy = y1 - y0;
double dxSquare = square(dx);
double dySquare = square(dy);
double dSquare = dxSquare + dySquare;
double d = sqrt(dSquare);

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

แม้ว่า "ถ้า" บล็อกสามารถดีหรือไม่ดีในการจัดทำเอกสารด้วยตนเอง สิ่งนี้ไม่ดีเนื่องจากคุณไม่สามารถบังคับให้ 2 เงื่อนไขแรกเพื่อทดสอบข้อที่สามได้อย่างง่ายดาย ... ทั้งหมดไม่เกี่ยวข้องกัน:

if (Bill is the boss) && (i == 3) && (the carnival is next weekend)

อันนี้ทำให้รู้สึก "รวม" มากขึ้นและง่ายต่อการสร้างเงื่อนไขการทดสอบ:

if (iRowCount == 2) || (iRowCount == 50) || (iRowCount > 100)

และคำสั่งนี้เป็นเพียงสตริงสุ่มของตัวละครที่มองจากมุมมองการบันทึกเอกสารด้วยตนเอง:

var a = F(G1(H1(b1), H2(b2)), G2(c1));

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

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

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