เจ้านายของฉันขอให้ฉันหยุดเขียนฟังก์ชั่นเล็ก ๆ และทำทุกอย่างในลูปเดียวกัน


209

ฉันอ่านหนังสือชื่อClean Codeของ Robert C. Martin แล้ว ในหนังสือเล่มนี้ฉันได้เห็นวิธีการมากมายในการทำความสะอาดโค้ดเช่นการเขียนฟังก์ชั่นเล็ก ๆ การเลือกชื่ออย่างระมัดระวัง ฯลฯ ดูเหมือนว่าหนังสือที่น่าสนใจที่สุดเกี่ยวกับรหัสที่ฉันอ่าน อย่างไรก็ตามวันนี้เจ้านายของฉันไม่ชอบวิธีที่ฉันเขียนโค้ดหลังจากอ่านหนังสือเล่มนี้

ข้อโต้แย้งของเขาคือ

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

สิ่งนี้ขัดต่อทุกสิ่งที่ฉันอ่าน ปกติคุณเขียนรหัสได้อย่างไร หนึ่งวงใหญ่หลักไม่มีฟังก์ชั่นเล็ก ๆ ?

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

ตัวอย่างหนึ่งคือ:

// The way I would write it
if (isApplicationInProduction(headers)) {
  phoneNumber = headers.resourceId;
} else {
  phoneNumber = DEV_PHONE_NUMBER;
}

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

// The way he would write it
// Take the right resourceId if application is in production
phoneNumber = headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;

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

ฉันต้องการคำแนะนำวิธี / การปฏิบัติที่ดีกว่าในการเขียนโค้ดสะอาด


17
เพียงเพื่อให้คุณรู้ว่าจอห์น Carmack อาจจะเห็นด้วยกับเจ้านายของคุณ
walen

4
phoneNumber = headers.resourceId?: DEV_PHONE_NUMBER;
Joshua

10
ตรวจสอบว่าคุณต้องการทำงานในสถานที่ที่ฝ่ายบริหารบอกคุณว่าจะทำงานอย่างไรแทนที่จะต้องแก้ไขอะไร
Konstantin Petrukhnov

8
@rjmunro ถ้าคุณไม่ชอบงานของคุณจริงๆโปรดจำไว้ว่ามีนักพัฒนาน้อยกว่างาน ดังนั้นหากต้องการอ้างอิง Martin Fowler: "ถ้าคุณไม่สามารถเปลี่ยนองค์กรของคุณได้ให้เปลี่ยนองค์กรของคุณ!" และผู้บังคับบัญชาบอกให้ฉันทราบถึงวิธีการกำหนดรหัสเป็นสิ่งที่ฉันแนะนำคุณควรเปลี่ยน
Niels van Reijmersdal

10
ไม่เคยมีisApplicationInProduction()ฟังก์ชั่น! คุณต้องมีการทดสอบและการทดสอบนั้นไร้ประโยชน์หากรหัสของคุณทำงานแตกต่างจากเวลาที่ใช้งานจริง มันเหมือนกับว่าตั้งใจที่จะมีรหัสที่ยังไม่ได้ทดสอบ / ไม่ได้เปิดในการผลิต: มันไม่สมเหตุสมผล
Ronan Paixão

คำตอบ:


215

ยกตัวอย่างรหัสก่อน คุณชอบ:

if (isApplicationInProduction(headers)) {
  phoneNumber = headers.resourceId;
} else {
  phoneNumber = DEV_PHONE_NUMBER;
}

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

และเจ้านายของคุณจะเขียนมันเป็น:

// Take the right resourceId if application is in production
phoneNumber = headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;

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

ฉันขอแนะนำรหัสที่ดีที่สุดระหว่างสอง:

phoneNumber = isApplicationInProduction(headers) ? headers.resourceId : DEV_PHONE_NUMBER;

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

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

เกี่ยวกับมุมมองของหัวหน้าในการออกแบบรหัส:

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

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

ใส่ทุกอย่างไว้ในลูปหลักหลักแม้ว่าลูปหลักจะมีมากกว่า 300 บรรทัดการอ่านเร็วขึ้น

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

เขียนฟังก์ชั่นขนาดเล็กเฉพาะในกรณีที่คุณต้องทำซ้ำรหัส

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

อย่าเขียนฟังก์ชันที่มีชื่อของความคิดเห็นใส่รหัสบรรทัดที่ซับซ้อนของคุณ (3-4 บรรทัด) ด้วยความคิดเห็นด้านบน เช่นนี้คุณสามารถแก้ไขรหัสที่ล้มเหลวได้โดยตรง

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

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

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


26
Fucntions ขนาดเล็กผ่านการทดสอบแบบง่าย
Mawg

13
Quoth @ ExpertBeginner1 :“ ฉันเบื่อที่จะเห็นวิธีการเล็ก ๆ มากมายในรหัสของเราดังนั้นจากที่นี่ไปข้างหน้ามีขั้นต่ำ 15 LOC สำหรับทุกวิธี”
Greg Bacon

34
"ความคิดเห็นมีข้อบกพร่องพื้นฐาน: พวกเขาไม่ได้รวบรวม / ตีความและดังนั้นจึงไม่สามารถทดสอบหน่วย" เล่นผู้สนับสนุนของปีศาจที่นี่นี่เป็นความจริงเช่นกันถ้าคุณแทนที่ "ความคิดเห็น" ด้วย "ชื่อฟังก์ชั่น"
mattecapu

11
@mattecapu ฉันจะใช้การสนับสนุนของคุณและเพิ่มมันเป็นสองเท่าให้กับคุณ ผู้พัฒนาขยะเก่า ๆ สามารถวาฟเฟิลต่อในความคิดเห็นที่พยายามจะอธิบายว่าส่วนใดของโค้ด อธิบายโดยย่อว่าส่วนของโค้ดที่มีชื่อฟังก์ชั่นที่ดีนั้นต้องใช้ผู้สื่อสารที่มีทักษะ นักพัฒนาที่ดีที่สุดคือนักสื่อสารที่มีทักษะเนื่องจากการเขียนโค้ดนั้นเกี่ยวข้องกับการสื่อสารกับผู้พัฒนารายอื่นเป็นหลักและคอมไพเลอร์เป็นข้อกังวลรอง Ergo ผู้พัฒนาที่ดีจะใช้ฟังก์ชั่นที่มีชื่อดีและไม่มีความคิดเห็น ผู้พัฒนาที่ยากจนจะซ่อนทักษะที่ไม่ดีของตนไว้เบื้องหลังข้ออ้างในการใช้ความคิดเห็น
David Arno

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

223

มีปัญหาอื่น ๆ

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

phoneNumber = DEV_PHONE_NUMBER_WHICH_CAUSED_PROBLEMS_FOR_CUSTOMERS;

หรือ

phoneNumber = DEV_PHONE_NUMBER_FROM_OTHER_COUNTRY;

คุณต้องการที่จะเพิ่มสาขามากขึ้น?

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

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

ฉีดพึ่งพา

นี่คือลักษณะที่โค้ดของคุณควรมีลักษณะ:

phoneNumber = headers.resourceId;

ไม่มีการแตกแขนงที่นี่เพราะตรรกะที่นี่ไม่มีการแตกแขนง โปรแกรมควรดึงหมายเลขโทรศัพท์จากส่วนหัว ระยะเวลา

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

function foo(headers){
    phoneNumber = headers.resourceId;
}

// Creating the test case
foo({resourceId: DEV_PHONE_NUMBER_FROM_OTHER_COUNTRY});

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


11
ฉันคิดว่าจะตอบคำถามนี้ด้วยคำตอบของตัวเอง แต่รู้สึกว่ามันนานพอแล้ว ดังนั้น +1 ให้กับคุณสำหรับการทำเช่นนั้น :)
David Arno

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

56
@DavidArno บางทีคุณควรจะแบ่งคำตอบออกเป็นคำตอบเล็ก ๆ ;)
krillgar

2
@ user949300 ใช้บิตหรือจะไม่ฉลาด;)
อยากรู้อยากเห็น dannii

1
@curiousdannii ใช่สังเกตเห็นว่าสายเกินไปที่จะแก้ไข ...
user949300

59

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

  1. ไม่มีสิ่งเช่น "รหัสการทำเอกสารด้วยตนเอง" ทำไม? เพราะการเรียกร้องในทัศนะที่สมบูรณ์
  2. ความคิดเห็นที่ไม่เคยล้มเหลว อะไรคือความล้มเหลวคือรหัสที่ไม่สามารถเข้าใจได้เลยหากไม่มีความคิดเห็น
  3. 300 บรรทัดอย่างต่อเนื่องของรหัสในบล็อกรหัสหนึ่งคือฝันร้ายการบำรุงรักษาและมีแนวโน้มที่จะเกิดข้อผิดพลาดสูง บล็อกดังกล่าวบ่งบอกอย่างยิ่งถึงการออกแบบและการวางแผนที่ไม่ดี

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

ระวังอย่าไปลงน้ำด้วย "ฟังก์ชั่นที่ตั้งชื่อไว้อย่างชัดเจน" งานประจำควรสรุปความคิดมากกว่า "แค่โค้ด" ฉันสนับสนุนข้อเสนอแนะของอมรphoneNumber = getPhoneNumber(headers)และเพิ่มที่getPhoneNumber()ควรทำแบบทดสอบ "สถานะการผลิต" ด้วยisApplicationInProduction()


25
มีความคิดเห็นที่ดีซึ่งไม่ใช่ความล้มเหลว อย่างไรก็ตามความคิดเห็นที่เกือบทุกคำรหัสที่พวกเขาควรจะอธิบายหรือเป็นเพียงบล็อกว่างเปล่าความคิดเห็นก่อนหน้าวิธีการ / class / etc เป็นความล้มเหลวอย่างแน่นอน
jpmc26

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

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

7
@ Timbo คุณหมายถึง "... อย่างน้อยหนึ่งรายการไม่ถูกต้อง" ;)
jpmc26

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

47

“ เอนทิตีต้องไม่ทวีคูณเกินความจำเป็น”

- มีดโกนของอ็อกคัม

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

หน่วยขนาดเล็ก (แฟ้ม, ฟังก์ชั่นการเรียน) เป็นความคิดที่ดี หน่วยขนาดเล็กเข้าใจง่ายเพราะมีสิ่งที่คุณต้องเข้าใจน้อยลงในคราวเดียว มนุษย์ปกติสามารถเล่นปาหี่ ~ 7 แนวคิดต่อครั้ง แต่ขนาดไม่ได้วัดเพียงในบรรทัดของรหัส ฉันสามารถเขียนโค้ดให้น้อยที่สุดเท่าที่จะทำได้โดย“ golfing” โค้ด (เลือกชื่อตัวแปรแบบสั้นโดยใช้ช็อตคัทที่“ ฉลาด” ทำการสแมชชิงโค้ดให้มากที่สุดเท่าที่เป็นไปได้ในบรรทัดเดียว) แต่ผลลัพธ์สุดท้ายนั้นไม่ง่าย การพยายามทำความเข้าใจโค้ดดังกล่าวนั้นเหมือนกับวิศวกรรมย้อนกลับมากกว่าการอ่าน

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

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

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

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

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

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

function bigFatFunction(...) {
  ...
  phoneNumber = getPhoneNumber(headers);
  ...
}

...

function getPhoneNumber(headers) {
  return headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;
}

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


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

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

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

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


ฉันเป็นคนทั่วไปฉันชอบคำตอบของคุณ ฉันทำ แต่จะมีปัญหากับการวัดความซับซ้อนของวัฏจักร mcabes จากสิ่งที่ฉันได้เห็นมันไม่ได้แสดงถึงความซับซ้อนที่แท้จริง
Robert Baron

27

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

อย่าฟังพวกเขา!

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

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


53
"อย่าฟังพวกเขา!" : เนื่องจากเจ้านายของเขาขอให้ OP แยกรหัสออกจากกัน OP อาจจะหลีกเลี่ยงคำแนะนำของคุณ แม้ว่าเจ้านายไม่เต็มใจที่จะเปลี่ยนนิสัยเก่าของเขา นอกจากนี้โปรดทราบว่าตามที่เน้นโดยคำตอบก่อนหน้านี้ทั้งรหัสของ OP และรหัสของเจ้านายของเขานั้นเขียนไม่ดีและคุณ (โดยเจตนาหรือไม่) ไม่ได้กล่าวถึงในคำตอบของคุณ
Arseni Mourzenko

10
@ ArseniMourzenko: ไม่ใช่ทุกคนที่เราจะต้องต่อหน้าเจ้านายของเขา ฉันหวังว่า OP จะแก่พอที่จะรู้ว่าเมื่อเขาต้องทำสิ่งที่ถูกต้องหรือเมื่อเขาต้องทำสิ่งที่เจ้านายของเขาพูด และใช่ฉันไม่ได้เข้าไปในรายละเอียดของตัวอย่างโดยเจตนามีคำตอบอื่น ๆ อยู่แล้วพอพูดถึงรายละเอียดเหล่านั้น
Doc Brown

8
@DocBrown เห็นด้วย 300 บรรทัดเป็นปัญหาสำหรับทั้งชั้นเรียน ฟังก์ชั่น 300 บรรทัดอนาจาร
JimmyJames

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

9
นอกจากนี้ฉันยังเห็นคำแนะนำของปราชญ์ลุงบ๊อบตีความผิดและใช้ในทางที่ผิดอย่างมากจนฉันสงสัยว่ามันจะเป็นประโยชน์กับทุกคนยกเว้นโปรแกรมเมอร์ที่มีประสบการณ์
Robert Harvey

23

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

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

วิธีการ "isApplicationInProduction" นั้นไม่มีจุดหมายเลย การเรียกจากเมธอด getPhonenumber ของคุณไม่ได้ทำให้การใช้งานชัดเจนขึ้นและทำให้ยากที่จะทราบว่าเกิดอะไรขึ้น

อย่าเขียนฟังก์ชั่นเล็ก ๆ ฟังก์ชั่นการเขียนที่มีวัตถุประสงค์ที่กำหนดไว้อย่างดีและตอบสนองวัตถุประสงค์ที่กำหนดไว้อย่างดี

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


1
"อย่าเขียนฟังก์ชั่นเล็ก ๆ เขียนฟังก์ชั่นที่มีจุดประสงค์ที่ชัดเจนและตอบสนองวัตถุประสงค์ที่กำหนดไว้อย่างดี" นั่นคือเกณฑ์ที่ถูกต้องสำหรับการแยกรหัส ถ้าทำงานไม่มากเกินไป (ชอบมากกว่าหนึ่ง) ที่แตกต่างกันฟังก์ชั่นแล้วแยกขึ้น หลักการความรับผิดชอบเดี่ยวคือหลักการชี้นำ
robert bristow-johnson

16

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

จำนวนบรรทัดไม่ใช่ตัวชี้วัดที่มีประโยชน์ในกรณีส่วนใหญ่

300 (หรือ 3,000 เส้น) ของรหัสต่อเนื่องล้วน ๆ (การตั้งค่าหรืออะไรทำนองนั้น) นั้นไม่ค่อยมีปัญหา (แต่อาจจะสร้างอัตโนมัติดีกว่าหรือเป็นตารางข้อมูลหรืออะไรก็ตาม) 100 ลูปซ้อนซ้อนที่มีจำนวนมากซับซ้อน เงื่อนไขการออกและคณิตศาสตร์อย่างที่คุณอาจพบในการกำจัดแบบเกาส์อินหรือการผกผันของเมทริกซ์หรืออาจจะมากเกินไปที่จะติดตามได้อย่างง่ายดาย

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

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

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

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


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

15

อย่าใส่ทุกอย่างไว้ในลูปใหญ่ แต่อย่าทำอย่างนี้บ่อยเกินไป:

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

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

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

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


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

14

ดูเหมือนว่าสิ่งที่คุณต้องการคือ:

phoneNumber = headers.resourceId || DEV_PHONE_NUMBER

สิ่งนี้ควรอธิบายได้ด้วยตนเองสำหรับทุกคนที่อ่าน: ตั้งค่าphoneNumberเป็นresourceIdหากมีหรือเป็นค่าเริ่มต้นDEV_PHONE_NUMBERหากไม่ใช่

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


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

14

ให้ฉันเป็นคนทื่อ: ฉันว่าสภาพแวดล้อมของคุณ (ภาษา / กรอบ / การออกแบบชั้นเรียน ฯลฯ ) ไม่เหมาะสำหรับรหัส "สะอาด" คุณกำลังผสมสิ่งที่เป็นไปได้ทั้งหมดในโค้ดสองสามบรรทัดที่ไม่ควรอยู่ใกล้กัน ฟังก์ชั่นเดียวในธุรกิจใดที่มีการรู้ว่านั่นresourceId==undefหมายความว่าคุณไม่ได้ใช้งานจริงคุณกำลังใช้หมายเลขโทรศัพท์เริ่มต้นในระบบที่ไม่ได้ใช้งานจริงหรือไม่ซึ่งระบบจะบันทึกหมายเลขทรัพยากรไว้ใน "ส่วนหัว" เป็นต้น ฉันถือว่าheadersเป็นส่วนหัว HTTP ดังนั้นคุณจึงตัดสินใจออกจากสภาพแวดล้อมที่คุณเป็นผู้ใช้

การแยกชิ้นส่วนออกเป็นส่วน ๆ นั้นจะไม่ช่วยอะไรคุณได้มากนักกับปัญหาพื้นฐาน

คำค้นหาที่จะค้นหา:

  • decoupling
  • การติดต่อกัน
  • ฉีดพึ่งพา

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

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

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


1
ฉันเห็นด้วย 100% ว่ามีองค์ประกอบโดยปริยายที่นี่ซึ่งควรแยกออกจากกัน แต่หากปราศจากความรู้เพิ่มเติมเกี่ยวกับภาษา / กรอบงานมันเป็นการยากที่จะรู้ว่าวิธีการ OO เหมาะสมหรือไม่ Decoupling และหลักการความรับผิดชอบเดี่ยวมีความสำคัญในภาษาใด ๆ จากการทำงานที่บริสุทธิ์ (เช่น Haskell) ถึงความจำเป็นบริสุทธิ์ (เช่น C. ) ขั้นตอนแรกของฉัน - ถ้าเจ้านายอนุญาตให้มัน - จะเปลี่ยนฟังก์ชั่นหลักเป็นฟังก์ชั่นมอบหมายงาน ( เช่นโครงร่างหรือสารบัญ) ซึ่งจะอ่านในรูปแบบที่เปิดเผย (อธิบายนโยบายไม่ใช่อัลกอริธึม) และนำงานไปทำหน้าที่อื่น ๆ
David Leppik

จาวาสคริปต์คือต้นแบบกับฟังก์ชั่นชั้นหนึ่ง มันคือ OO โดยเนื้อแท้ แต่ไม่ใช่ในแง่ของความคลาสสิคดังนั้นสมมติฐานของคุณอาจไม่ถูกต้อง ชั่วโมงในการรับชม Crockford vids บน YouTube ...
Kevin_Kinsey

13

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


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

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

6

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

ฉันจะไม่บอกว่าคุณควรใช้ไตรภาคอย่างแน่นอนบางคนที่ฉันเคยทำงานด้วยชอบนานกว่าif () {...} else {...}นั้นเล็กน้อยมันเป็นตัวเลือกส่วนบุคคล ฉันมักจะชอบ "หนึ่งบรรทัดทำสิ่งหนึ่งวิธี" แต่โดยทั่วไปฉันจะยึดติดกับสิ่งที่ codebase ปกติใช้

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

// NOTE "resourceId" not present in dev build, use test data
let isProduction = 'resourceId' in headers;
let phoneNumber = isProduction ? headers.resourceId : DEV_PHONE_NUMBER;

ฉันอยากจะบอกว่าถ้า codebase ขยายไปถึง 300 บรรทัดฟังก์ชั่นก็จะต้องมีการแบ่ง แต่ฉันขอแนะนำให้ใช้จังหวะที่กว้างขึ้นเล็กน้อย


5

ตัวอย่างรหัสที่คุณให้เจ้านายของคุณถูกต้อง ในกรณีนี้จะมีเส้นใสที่ชัดเจนกว่า

โดยทั่วไปแล้วการแบ่งตรรกะที่ซับซ้อนออกเป็นชิ้นเล็ก ๆ นั้นดีกว่าสำหรับความชัดเจนการบำรุงรักษาโค้ดและความเป็นไปได้ที่คลาสย่อยจะมีพฤติกรรมที่แตกต่างกัน (แม้ว่าจะเล็กน้อยเท่านั้น)

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


1
"function overhead": ขึ้นอยู่กับคอมไพเลอร์ "obscuration": OP ไม่ได้ระบุว่าเป็นวิธีเดียวหรือดีที่สุดในการตรวจสอบคุณสมบัตินั้น คุณไม่สามารถรู้ได้อย่างแน่นอน "ตรรกะปาเก็ตตี้ที่ซับซ้อน": ที่ไหน "ศักยภาพของฟังก์ชั่นที่ตายแล้ว": การวิเคราะห์รหัสที่ตายแล้วเป็นผลไม้แขวนลอยต่ำและเครื่องมือในการพัฒนาที่ขาดความสมบูรณ์
Rhymoid

คำตอบนั้นเน้นไปที่ข้อดีมากกว่าฉันเพียง แต่ต้องการชี้ให้เห็นถึงข้อเสียเช่นกัน การเรียกฟังก์ชันเช่นผลรวม (a, b) มักจะมีราคาแพงกว่า "a + b" เสมอ (เว้นแต่ฟังก์ชันจะถูกคอมไพล์โดยคอมไพเลอร์) ส่วนที่เหลือของข้อเสียแสดงให้เห็นว่ามีความซับซ้อนเกินความสามารถนำไปสู่ชุดของปัญหา รหัสที่ไม่ถูกต้องคือรหัสที่ไม่ดีและเพียงเพราะมีขนาดเล็กลง (หรือเก็บไว้ในวงวน 300 บรรทัด) ไม่ได้หมายความว่าจะกลืนได้ง่ายขึ้น
Phil M

2

ฉันสามารถคิดอย่างน้อยสองข้อโต้แย้งในการทำงานยาว:

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

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

นอกจากนี้ยังมีข้อโต้แย้งเกี่ยวกับฟังก์ชั่นที่ยาวนาน - หน่วยทดสอบความจำ ใช้t̶h̶e̶̶f̶o̶r̶c̶e̶ประสบการณ์ของคุณเมื่อเลือกระหว่างหนึ่งและอื่น ๆ

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


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


ความเห็นเกี่ยวกับคำตอบของ David Arno :

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

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

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

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

ใส่ทุกอย่างไว้ในลูปหลักหลักแม้ว่าลูปหลักจะมีมากกว่า 300 บรรทัดการอ่านเร็วขึ้น

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

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

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

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

(*) อย่างน้อยฉันก็ซื่อสัตย์กับมัน ;-)

ฉันคิดว่าทั้งสองแนวทางมีจุดแข็งและจุดอ่อน

เขียนฟังก์ชั่นขนาดเล็กเฉพาะในกรณีที่คุณต้องทำซ้ำรหัส

ฉันไม่เห็นด้วย. ตามตัวอย่างรหัสของคุณฟังก์ชั่นขนาดเล็กที่มีชื่อดีจะช่วยเพิ่มความสามารถในการอ่านรหัสและควรใช้เมื่อใดก็ตามที่ [เช่น] คุณไม่สนใจ "วิธีการ" เพียงฟังก์ชัน "อะไร" ของชิ้นส่วน

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

ที่กล่าวมานี้เป็นส่วนหนึ่งของมุมมองของเจ้านายฉันอาจไม่เห็นด้วยมากที่สุด

อย่าเขียนฟังก์ชันที่มีชื่อของความคิดเห็นใส่รหัสบรรทัดที่ซับซ้อนของคุณ (3-4 บรรทัด) ด้วยความคิดเห็นด้านบน เช่นนี้คุณสามารถแก้ไขรหัสที่ล้มเหลวได้โดยตรง

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

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


-1

ในความคิดของฉันรหัสที่ถูกต้องสำหรับฟังก์ชั่นที่คุณต้องการคือ:

phoneNumber = headers.resourceId || DEV_PHONE_NUMBER;

หรือถ้าคุณต้องการที่จะแยกมันเป็นฟังก์ชั่นอาจจะเป็นสิ่งที่ชอบ:

phoneNumber = getPhoneNumber(headers);

function getPhoneNumber(headers) {
  return headers.resourceId || DEV_PHONE_NUMBER
}

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

function isApplicationInProduction() {
  process.env.NODE_ENV === 'production';
}

จากนั้นคุณสามารถรับหมายเลขโทรศัพท์ด้วย:

phoneNumber = isApplicationInProduction() ? headers.resourceId : DEV_PHONE_NUMBER;

-2

เพียงแค่แสดงความคิดเห็นเกี่ยวกับสองสัญลักษณ์แสดงหัวข้อย่อย

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

ผู้แก้ไขหลายคน (เช่น IntelliJ) จะช่วยให้คุณข้ามไปยังฟังก์ชั่น / ชั้นเรียนเพียงแค่กด Ctrl-Clicking การใช้งาน นอกจากนี้คุณมักไม่จำเป็นต้องรู้รายละเอียดการใช้งานของฟังก์ชั่นในการอ่านรหัสจึงทำให้การอ่านรหัสเร็วขึ้น

ฉันขอแนะนำให้คุณบอกเจ้านายของคุณ เขาจะชอบการสนับสนุนของคุณและเห็นว่ามันเป็นความเป็นผู้นำ เพียงแค่สุภาพ

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