บริบท
ฉันเพิ่งได้รับความสนใจในการผลิตรหัสที่จัดรูปแบบที่ดีขึ้น และโดยดีกว่าฉันหมายถึง "การปฏิบัติตามกฎเกณฑ์ที่รับรองโดยผู้คนมากพอที่จะถือว่าเป็นแนวปฏิบัติที่ดี" (เนื่องจากจะไม่มีวิธีที่ดีที่สุดในการเขียนโค้ดแน่นอน)
ทุกวันนี้ฉันส่วนใหญ่เป็นรหัสใน Ruby ดังนั้นฉันจึงเริ่มใช้ linter (Rubocop) เพื่อให้ข้อมูลบางอย่างเกี่ยวกับ "คุณภาพ" ของรหัสของฉัน ("คุณภาพ" นี้ถูกกำหนดโดยชุมชนruby-style- project -guide-guide )
โปรดทราบว่าผมจะใช้ "คุณภาพ" ในขณะที่ "คุณภาพของการจัดรูปแบบว่า" ไม่มากเกี่ยวกับประสิทธิภาพของรหัสแม้ว่าในบางกรณีที่มีประสิทธิภาพรหัสจริงจะได้รับผลกระทบโดยวิธีการรหัสที่เขียน
อย่างไรก็ตามทำทุกสิ่งที่ฉันรู้ (หรืออย่างน้อยก็จำได้) บางสิ่ง:
- บางภาษา (ที่โดดเด่นที่สุดคือ Python, Ruby และเช่นนั้น) อนุญาตให้สร้างโค้ดแบบหนึ่งบรรทัดที่ยอดเยี่ยม
- การปฏิบัติตามหลักเกณฑ์บางประการสำหรับรหัสของคุณอาจทำให้รหัสสั้นลงและชัดเจนมาก
- กระนั้นก็ตามการปฏิบัติตามแนวทางเหล่านี้อย่างเคร่งครัดเกินไปอาจทำให้รหัสชัดเจนน้อยลง / อ่านง่าย
- รหัสสามารถปฏิบัติตามหลักเกณฑ์บางอย่างเกือบจะสมบูรณ์แบบและยังคงมีคุณภาพไม่ดี
- ส่วนใหญ่การอ่านรหัสเป็นเรื่องส่วนตัว (เช่นเดียวกับใน "สิ่งที่ฉันเห็นชัดเจนว่าอาจคลุมเครือกับนักพัฒนาซอฟต์แวร์")
นี่เป็นเพียงการสังเกตไม่ใช่กฎที่แน่นอน คุณจะทราบได้ว่าการอ่านโค้ดและแนวทางต่อไปนี้อาจดูเหมือนไม่เกี่ยวข้องกัน ณ จุดนี้ แต่ในที่นี้แนวทางเป็นวิธีที่จะ จำกัด จำนวนวิธีในการเขียนโค้ดหนึ่งอันใหม่
ตอนนี้ตัวอย่างบางส่วนเพื่อทำให้ทุกอย่างชัดเจนยิ่งขึ้น
ตัวอย่าง
มาดูกันดีกว่า: เรามีแอพพลิเคชั่นที่มีUser
รูปแบบ "" ผู้ใช้มีตัวเลือกfirstname
และsurname
และที่email
อยู่บังคับ
ผมอยากจะเขียนวิธีการ " name
" ซึ่งจะกลับมาแล้วชื่อ ( firstname + surname
) ของผู้ใช้ถ้าอย่างน้อยเขาfirstname
หรือsurname
เป็นปัจจุบันหรือมันemail
เป็นค่าทางเลือกหากไม่ได้
ฉันต้องการให้วิธีนี้ใช้use_email
พารามิเตอร์" " เป็นบูลีน (อนุญาตให้ใช้อีเมลผู้ใช้เป็นค่าทางเลือก) use_email
พารามิเตอร์ " " นี้ควรเป็นค่าเริ่มต้น (หากไม่ผ่าน) เป็น " true
"
วิธีที่ง่ายที่สุดในการเขียนใน Ruby คือ:
def name(use_email = true)
# If firstname and surname are both blank (empty string or undefined)
# and we can use the email...
if (firstname.blank? && surname.blank?) && use_email
# ... then, return the email
return email
else
# ... else, concatenate the firstname and surname...
name = "#{firstname} #{surname}"
# ... and return the result striped from leading and trailing spaces
return name.strip
end
end
รหัสนี้เป็นวิธีที่ง่ายและเข้าใจง่ายที่สุด แม้แต่คนที่ไม่ "พูด" รูบี
ทีนี้ลองทำให้สั้นลง:
def name(use_email = true)
# 'if' condition is used as a guard clause instead of a conditional block
return email if (firstname.blank? && surname.blank?) && use_email
# Use of 'return' makes 'else' useless anyway
name = "#{firstname} #{surname}"
return name.strip
end
สั้นกว่านี้ยังเข้าใจง่ายถ้าไม่ง่ายกว่า (ส่วนคำสั่งป้องกันอ่านง่ายกว่าบล็อกแบบมีเงื่อนไข) ประโยค Guard ยังทำให้สอดคล้องกับแนวทางที่ฉันใช้มากกว่าดังนั้น win-win ที่นี่ เรายังลดระดับการเยื้อง
ทีนี้ลองใช้เวทมนต์ Ruby เพื่อทำให้มันสั้นลง:
def name(use_email = true)
return email if (firstname.blank? && surname.blank?) && use_email
# Ruby can return the last called value, making 'return' useless
# and we can apply strip directly to our string, no need to store it
"#{firstname} #{surname}".strip
end
แม้สั้นลงและทำตามแนวทางอย่างสมบูรณ์แบบ ... แต่ชัดเจนน้อยกว่าเนื่องจากการขาดคำสั่งส่งคืนทำให้สับสนเล็กน้อยสำหรับผู้ที่ไม่คุ้นเคยกับการปฏิบัตินี้
ที่นี่เราสามารถเริ่มถามคำถาม: คุ้มหรือไม่ เราควรพูดว่า "ไม่ทำให้อ่านได้และเพิ่มreturn
" "" (การรู้ว่าสิ่งนี้จะไม่เคารพหลักเกณฑ์) หรือเราควรจะพูดว่า "ไม่เป็นไรเป็นวิธีเรียนรู้ภาษาที่น่ากลัว"
หากเราใช้ตัวเลือก B ดังนั้นทำไมไม่ทำให้สั้นลง:
def name(use_email = true)
(email if (firstname.blank? && surname.blank?) && use_email) || "#{firstname} #{surname}".strip
end
นี่คือหนึ่งซับ! แน่นอนว่ามันสั้นกว่า ... ที่นี่เราใช้ประโยชน์จากความจริงที่ว่า Ruby จะคืนค่าหรือสิ่งอื่นขึ้นอยู่กับว่ามีการกำหนดหนึ่ง (ตั้งแต่อีเมลจะถูกกำหนดภายใต้เงื่อนไขเดียวกันเหมือนก่อน)
นอกจากนี้เรายังสามารถเขียนมัน:
def name(use_email = true)
(email if [firstname, surname].all?(&:blank?) && use_email) || "#{firstname} #{surname}".strip
end
มันสั้นไม่ยากที่จะอ่าน (ฉันหมายถึงเราทุกคนได้เห็นสิ่งที่น่าเกลียดหนึ่งซับสามารถมีลักษณะ), ดีทับทิมมันเป็นไปตามแนวทางที่ฉันใช้ ... แต่ยังเทียบกับวิธีแรกในการเขียน มันอ่านและเข้าใจได้ง่ายกว่ามาก เราสามารถยืนยันได้ว่าบรรทัดนี้ยาวเกินไป (มากกว่า 80 ตัวอักษร)
คำถาม
ตัวอย่างของรหัสสามารถแสดงให้เห็นว่าการเลือกระหว่างรหัส "ขนาดเต็ม" และหลาย ๆ รุ่นที่ลดลง (จนถึงหนึ่งซับที่มีชื่อเสียง) อาจเป็นเรื่องยากเนื่องจากอย่างที่เราเห็นว่าหนึ่งซับไม่น่ากลัว แต่ ยังไม่มีอะไรจะเอาชนะรหัส "ขนาดเต็ม" ในแง่ของการอ่าน ...
ดังนั้นนี่คือคำถามจริง: หยุดที่ไหน สั้นเมื่อใดจะสั้นพอ จะทราบได้อย่างไรว่าเมื่อรหัสกลายเป็น "สั้นเกินไป" และอ่านได้น้อยลง (โปรดจำไว้ว่ามันเป็นอัตวิสัย) และยิ่งไปกว่า: วิธีการเขียนโค้ดให้สอดคล้องกันเสมอและหลีกเลี่ยงการผสม one-liners กับ chunks "full-size" เมื่อฉันรู้สึกเหมือนมัน?
TL; DR
คำถามหลักที่นี่คือเมื่อมันมาถึงการเลือกระหว่าง "ยาว แต่ชัดเจนอ่านง่ายและเข้าใจของรหัส" และ "พลังสั้นสั้น แต่ยากที่จะอ่าน / เข้าใจหนึ่งซับ" รู้ว่าทั้งสองอยู่ด้านบนและ ด้านล่างของสเกลและไม่ใช่ทั้งสองตัวเลือกเท่านั้น: จะกำหนดได้อย่างไรว่าขอบเขตระหว่าง "ชัดเจนเพียงพอ" และ "ไม่ชัดเจนเท่าที่ควร"
คำถามหลักไม่ใช่คำถามคลาสสิก "One-liners vs. readability: อันไหนดีกว่ากัน?" แต่ "จะหาสมดุลระหว่างทั้งสองได้อย่างไร"
แก้ไข 1
ความคิดเห็นในตัวอย่างโค้ดมีไว้เพื่อ "เพิกเฉย" พวกเขาอยู่ที่นี่เพื่ออธิบายสิ่งที่เกิดขึ้น แต่ไม่ควรนำมาพิจารณาเมื่อประเมินความสามารถในการอ่านของรหัส
return
คำหลักเพิ่ม ตัวละครทั้งเจ็ดนั้นเพิ่มความชัดเจนในสายตาของฉัน
[firstname,surname,!use_email].all?(&:blank?) ? email : "#{firstname} #{surname}".strip
... เพราะfalse.blank?
ผลตอบแทนจริงและผู้ประกอบการที่ช่วยคุณประหยัดสองสามตัวละคร ... ¯ \ _ (ツ) _ / ¯
return
คำสำคัญที่ควรเพิ่มคืออะไร! จะให้ข้อมูลใด ๆ มันเป็นความยุ่งเหยิงที่บริสุทธิ์