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


106

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

ฉันได้ปฏิบัติตามแนวทางปฏิบัติบางข้อที่แนะนำในหนังสือ "Clean Code" ของ Robert Martin โดยเฉพาะอย่างยิ่งวิธีการที่ใช้กับซอฟต์แวร์ประเภทที่ฉันทำงานด้วยและแนวทางที่เหมาะสมกับฉัน (ฉันไม่ปฏิบัติตามแนวคิด) .

ผลข้างเคียงหนึ่งที่ฉันสังเกตเห็นคือรหัส "สะอาด" ที่ฉันเขียนนั้นเป็นรหัสมากกว่าที่ฉันไม่ได้ปฏิบัติตาม แนวทางปฏิบัติเฉพาะที่นำไปสู่สิ่งนี้คือ:

  • Encapsulating conditionals

ดังนั้นแทนที่จะ

if(contact.email != null && contact.emails.contains('@')

ฉันสามารถเขียนวิธีการขนาดเล็กเช่นนี้

private Boolean isEmailValid(String email){...}
  • การแทนที่ความคิดเห็นแบบอินไลน์ด้วยวิธีส่วนตัวอื่น ๆ เพื่อให้ชื่อวิธีการอธิบายตัวเองมากกว่าที่จะมีความคิดเห็นแบบอินไลน์ด้านบนของมัน
  • คลาสควรมีเหตุผลเดียวเท่านั้นที่จะเปลี่ยน

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

ฉันทราบว่าการฝึกฝนใด ๆ ที่รุนแรงอาจเป็นอันตรายได้

คำถามที่เป็นรูปธรรมที่ฉันกำลังมองหาคำตอบคือ:

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

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

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

นี่เป็นข้อกังวลเฉพาะ ... คลาสเหล่านั้นอาจเป็นคลาสเดี่ยวที่ยังคงได้รับ "สิ่งเดียว" โดยไม่ต้องใช้วิธีการเล็ก ๆ มากมาย มันอาจเป็นชั้นเดียวที่อาจจะมี 3 หรือ 4 วิธีและความเห็นบางอย่าง


98
หากองค์กรของคุณใช้LOC เพียงอย่างเดียวเป็นตัวชี้วัดสำหรับฐานรหัสของคุณดังนั้นการระบุรหัสที่ชัดเจนว่าไม่มีความหวังที่จะเริ่มต้น
Kilian Foth

24
หากการบำรุงรักษาเป็นเป้าหมายของคุณ LOC ไม่ใช่ตัวชี้วัดที่ดีที่สุดในการตัดสิน - เป็นหนึ่งในนั้น แต่ก็มีอีกมากมายที่ต้องพิจารณามากกว่าเพียงแค่ทำให้มันสั้น
Zibbobz

29
ไม่ใช่คำตอบ แต่เป็นจุดที่ต้องทำ: มีชุมชนย่อยทั้งหมดเกี่ยวกับการเขียนโค้ดที่มีเส้น / สัญลักษณ์น้อยที่สุด codegolf.stackexchange.comเราสามารถโต้แย้งได้ว่าคำตอบส่วนใหญ่นั้นไม่สามารถอ่านได้อย่างที่ควรจะเป็น
Antitheos

14
เรียนรู้เหตุผลเบื้องหลังการปฏิบัติที่ดีที่สุดทุกข้อ การทำตามกฎโดยไม่มีเหตุผลคือลัทธิขนส่งสินค้า กฎทุกข้อมีเหตุผลของมันเอง
Gherman

9
และการใช้ตัวอย่างของคุณบางครั้งการผลักสิ่งต่าง ๆ ออกไปจะทำให้คุณคิดว่า: "อาจจะมีฟังก์ชั่นห้องสมุดที่สามารถทำได้" ตัวอย่างเช่นในการตรวจสอบที่อยู่อีเมลคุณสามารถสร้างSystem.Net.Mail.MailAddressซึ่งจะตรวจสอบความถูกต้องสำหรับคุณ จากนั้นคุณสามารถ (หวังว่า) จะเชื่อถือผู้แต่งไลบรารีนั้นเพื่อทำให้ถูกต้อง นี่หมายความว่า codebase ของคุณจะมี abstractions มากขึ้นและลดขนาดลง
Gregory Currie

คำตอบ:


130

... เราเป็นทีมเล็ก ๆ ที่สนับสนุนฐานรหัสที่ค่อนข้างใหญ่และไม่มีเอกสาร (ที่เราสืบทอด) ดังนั้นนักพัฒนา / ผู้จัดการบางคนเห็นคุณค่าในการเขียนโค้ดน้อยลงเพื่อให้สิ่งต่าง ๆ เสร็จสิ้นเพื่อให้เรามีรหัสน้อยกว่าในการรักษา

ชาวบ้านเหล่านี้ระบุสิ่งที่ถูกต้อง: พวกเขาต้องการให้รหัสนั้นง่ายต่อการบำรุงรักษา ที่พวกเขาทำผิดพลาดแม้ว่าจะสมมติว่ามีรหัสน้อยลงจะง่ายต่อการบำรุงรักษา

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

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


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

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

29
@JackAidley ต้องเปลี่ยนการทดสอบพร้อมกับรหัสอาจทำให้มีลักษณะการทำงานมากขึ้น แต่ถ้ามีใครสนใจข้อผิดพลาดที่หายากที่การเปลี่ยนแปลงของรหัสที่ยังไม่ทดลองจะแนะนำและมักจะไม่พบจนกว่าจะมีการจัดส่ง . หลังเพียงเสนอภาพลวงตาของการทำงานน้อยลง
David Arno

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

22
@JackAidley คุณสามารถปรับโครงสร้างได้มากมายโดยไม่ต้องเปลี่ยน API หรือส่วนต่อประสานใด ๆ หมายความว่าคุณสามารถคลั่งไคล้ในขณะที่แก้ไขโค้ดโดยไม่ต้องเปลี่ยนบรรทัดเดียวในหน่วยหรือการทดสอบการทำงาน นั่นคือถ้าการทดสอบของคุณไม่ทดสอบการใช้งานเฉพาะ
Eric Duminil

155

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

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

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


10
คำตอบที่ดีกว่านั้นคือ“ การทดสอบหน่วยแก้ปัญหาทั้งหมดของคุณ”
Dirk Boer

13
คำตอบนี้ตรงประเด็นสำคัญที่ลุงบ็อบและเพื่อน ๆ มักจะพลาด - การปรับโครงสร้างด้วยวิธีเล็ก ๆ จะช่วยได้ก็ต่อเมื่อคุณไม่ต้องไปอ่านวิธีเล็ก ๆ ทั้งหมดเพื่อหาว่าโค้ดของคุณทำอะไรอยู่ การสร้างคลาสแยกต่างหากเพื่อตรวจสอบความถูกต้องของที่อยู่อีเมล ดึงรหัสiterations < _maxIterationsลงในวิธีการที่เรียกว่าShouldContinueToIterateเป็นคนโง่
BJ Myers

4
@DavidArno: "เป็นประโยชน์"! = "แก้ปัญหาทั้งหมดของคุณ"
Christian Hackl

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

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

34

Bill Gates มีชื่อเสียงมาจากการพูดว่า "การวัดความคืบหน้าการเขียนโปรแกรมโดยสายของรหัสเป็นเหมือนการวัดความคืบหน้าการสร้างอากาศยานด้วยน้ำหนัก"

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

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

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

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


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

21
@jos ในทีม OP เฉพาะกำลังทำงานด้วยดูเหมือนว่า LOC ที่น้อยลงจะถือว่า 'ดีกว่า' ประเด็นที่บิลเกตส์กำลังทำอยู่ก็คือ LOC ไม่เกี่ยวข้องกับความก้าวหน้าในทางใดทางหนึ่งที่มีความหมายเช่นเดียวกับน้ำหนักการก่อสร้างอากาศยานนั้นไม่เกี่ยวข้องกับความก้าวหน้าในลักษณะที่มีความหมาย เครื่องบินที่กำลังก่อสร้างอาจมีน้ำหนัก 95% ของน้ำหนักสุดท้ายอย่างรวดเร็ว แต่มันจะเป็นเปลือกเปล่าที่ไม่มีระบบควบคุมมันไม่สมบูรณ์ 95% ในซอฟต์แวร์หากโปรแกรมมีโค้ดขนาด 100k นั่นไม่ได้หมายความว่าแต่ละ 1,000 บรรทัดจะมีฟังก์ชั่น 1%
Mr.Mindor

7
การตรวจสอบความก้าวหน้าเป็นงานที่ยากใช่มั้ย ผู้จัดการแย่
jos

@jos: ในรหัสด้วยจะดีกว่าถ้ามีบรรทัดน้อยกว่าสำหรับฟังก์ชันการทำงานเดียวกันหากสิ่งอื่นเท่ากัน
RemcoGerlich

@jos อ่านอย่างระมัดระวัง เกตส์ไม่ได้พูดอะไรเลยว่าน้ำหนักนั้นเป็นตัววัดที่สำคัญสำหรับเครื่องบินหรือไม่ เขาบอกว่าน้ำหนักเป็นมาตรการที่น่ากลัวสำหรับความก้าวหน้าในการสร้างเครื่องบิน ท้ายที่สุดโดยการวัดนั้นทันทีที่คุณได้โยนตัวถังทั้งหมดลงบนพื้นคุณก็ทำเสร็จแล้วโดยทั่วไปเพราะมันคิดเป็น 9x% ของน้ำหนักของระนาบทั้งหมด
Voo

23

ดังนั้นนักพัฒนา / ผู้จัดการบางคนเห็นคุณค่าในการเขียนโค้ดให้น้อยลงเพื่อให้สิ่งต่าง ๆ เสร็จสิ้น

นี่เป็นเรื่องของการมองไม่เห็นเป้าหมายที่แท้จริง

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

ส่วนที่เหลือของคำตอบคือตัวอย่างว่าโค้ดที่สะอาดสามารถนำไปสู่การเพิ่มเวลาได้อย่างไร


เข้าสู่ระบบ

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

แต่เวลาส่วนใหญ่จะจมลงไปในการตรวจสอบปัญหาและข้อบกพร่องและค้นหาสิ่งที่ผิดพลาด

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

สำหรับแอปพลิเคชัน B ซึ่งสมมติว่าการบันทึกสมบูรณ์แบบนักพัฒนาจะทำการตรวจสอบบันทึกสามารถระบุองค์ประกอบที่ผิดพลาดได้ทันทีและตอนนี้รู้ว่าจะต้องดูที่ไหน

สิ่งนี้สามารถบันทึกได้เป็นนาทีชั่วโมงหรือวัน ขึ้นอยู่กับขนาดและความซับซ้อนของ codebase


การถดถอย

ใช้แอปพลิเคชั่น A ซึ่งไม่เป็นมิตรกับสิ่งแวดล้อมเลย
ใช้แอปพลิเคชั่น B ซึ่งเป็น DRY แต่สุดท้ายก็จำเป็นต้องมีบรรทัดมากขึ้นเนื่องจากมี abstractions เพิ่มเติม

คำขอเปลี่ยนแปลงจะถูกยื่นซึ่งต้องมีการเปลี่ยนแปลงตรรกะ

สำหรับแอปพลิเคชัน B นักพัฒนาจะเปลี่ยนตรรกะ (ไม่ซ้ำกันแชร์) ตามคำขอเปลี่ยนแปลง

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

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

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


ความสามารถในการแลกเปลี่ยนของนักพัฒนา

นักพัฒนาแอปพลิเคชันที่สร้างขึ้น A. รหัสไม่สะอาดหรือไม่สามารถอ่านได้ แต่มันทำงานเหมือนมีเสน่ห์และทำงานในการผลิต น่าประหลาดใจที่ไม่มีเอกสารเช่นกัน

ผู้พัฒนา A ขาดงานไปหนึ่งเดือนเนื่องจากวันหยุด มีการยื่นคำขอเปลี่ยนแปลงฉุกเฉิน ไม่สามารถรออีกสามสัปดาห์ก่อนที่ Dev A จะกลับมา

นักพัฒนา B ต้องดำเนินการเปลี่ยนแปลงนี้ ตอนนี้เขาต้องการที่จะอ่าน codebase ทั้งหมดเข้าใจว่าทุกอย่างทำงานทำไมมันทำงานและสิ่งที่มันพยายามที่จะสำเร็จ สิ่งนี้ใช้เวลานาน แต่สมมติว่าเขาสามารถทำได้ในเวลาสามสัปดาห์

ในเวลาเดียวกันแอปพลิเคชัน B (ซึ่ง dev dev สร้างขึ้น) มีเหตุฉุกเฉิน Dev B ถูกครอบครอง แต่ Dev C นั้นมีให้แม้ว่าเขาจะไม่รู้จัก codebase ก็ตาม พวกเราทำอะไร?

  • ถ้าเราทำงาน B ต่อไปและให้ C ทำงานกับ B เราก็มีนักพัฒนาสองคนที่ไม่รู้ว่าพวกเขากำลังทำอะไร
  • หากเราดึง B ออกจาก A และให้เขาทำ B และตอนนี้เราวาง C ไว้บน A ผลงานทั้งหมดของนักพัฒนา B (หรือส่วนสำคัญ) อาจสิ้นสุดลงด้วยการยกเลิก นี่อาจเป็นจำนวนวัน / สัปดาห์ของความพยายามที่สูญเปล่า

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


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

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

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

คำอธิบาย goto ของฉันคือถามผู้บริหารว่าพวกเขาต้องการอะไร: แอปพลิเคชันที่มี codebase 100KLOC ซึ่งสามารถพัฒนาได้ในสามเดือนหรือ 50KLOC codebase ที่สามารถพัฒนาได้ในหกเดือน

พวกเขาเห็นได้ชัดว่าจะเลือกเวลาในการพัฒนาที่สั้นลงเพราะการจัดการไม่สนใจเกี่ยวกับ KLOC ผู้จัดการที่มุ่งเน้นไปที่ KLOC นั้นเป็น micromanaging ในขณะที่ไม่รู้ข้อมูลเกี่ยวกับสิ่งที่พวกเขาพยายามจัดการ


23

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

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

ฉันไม่ได้บอกว่าคุณไม่ควรแยกเงื่อนไขออกมา แต่คุณควรพิจารณาอย่างรอบคอบหากคุณต้องการ

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

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

ฉันว่าถ้าสงสัยให้เลือกรหัสที่ง่ายที่สุดและตรงไปตรงมาที่สุดเสมอ


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

7
คำตอบที่ดีที่สุดได้อย่างง่ายดายที่นี่ โดยเฉพาะอย่างยิ่งการสังเกต (ในย่อหน้าที่สาม) ว่าความซับซ้อนไม่ได้เป็นเพียงคุณสมบัติของรหัสทั้งหมดโดยรวม แต่มีบางสิ่งที่มีอยู่และแตกต่างกันในหลายระดับที่เป็นนามธรรมพร้อมกัน
Christian Hackl

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

เรื่อง"... เพราะตอนนี้คุณมีฟังก์ชั่นที่สามารถมองเห็นได้จากจุดต่าง ๆ ในโปรแกรม" : ในPascalมันเป็นไปได้ที่จะมีฟังก์ชั่นในท้องถิ่น - "... แต่ละขั้นตอนหรือฟังก์ชั่นสามารถมีการประกาศของตนเอง ประเภทตัวแปรและขั้นตอนและฟังก์ชันอื่น ๆ , ... "
Peter Mortensen

2
@PeterMortensen: มันเป็นไปได้ใน C # และ JavaScript และนั่นก็ยอดเยี่ยม! แต่ประเด็นยังคงอยู่ฟังก์ชั่นแม้กระทั่งฟังก์ชั่นในท้องถิ่นสามารถมองเห็นได้ในขอบเขตที่ใหญ่กว่าชิ้นส่วนรหัสแบบอินไลน์
JacquesB

9

ฉันจะชี้ให้เห็นว่าไม่มีอะไรผิดปกติกับเรื่องนี้:

if(contact.email != null && contact.email.contains('@')

อย่างน้อยก็สมมติว่ามันใช้ครั้งเดียว

ฉันอาจมีปัญหากับเรื่องนี้ได้อย่างง่ายดายมาก:

private Boolean isEmailValid(String email){
   return email != null && email.contains('@');
}

บางสิ่งที่ฉันจะดู:

  1. ทำไมถึงเป็นส่วนตัว ดูเหมือนว่าต้นขั้วที่มีประโยชน์ มันมีประโยชน์เพียงพอที่จะเป็นวิธีการส่วนตัวและไม่มีโอกาสที่จะถูกใช้อย่างแพร่หลายมากขึ้น?
  2. ฉันจะไม่ชื่อวิธีการ IsValidEmail ส่วนตัวอาจContainsAtSignหรือLooksVaguelyLikeEmailAddressเพราะมันไม่เกือบจะไม่มีการตรวจสอบจริงซึ่งอาจจะเป็นสิ่งที่ดีอาจจะไม่ใช่สิ่งที่เป็น exected
  3. มันถูกใช้มากกว่าหนึ่งครั้งหรือไม่?

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

ในทางกลับกันฉันได้เห็นวิธีการทำสิ่งนี้

if (contact.email != null && contact.email.contains('@')) { ... }
else if (contact.email != null && contact.email.contains('@') && contact.email.contains("@mydomain.com")) { //headquarters email }
else if (contact.email != null && contact.email.contains('@') && (contact.email.contains("@news.mydomain.com") || contact.email.contains("@design.mydomain.com") ) { //internal contract teams }

ตัวอย่างนี้เห็นได้ชัดว่าไม่ใช่ DRY

หรือแม้แต่ประโยคสุดท้ายที่สามารถยกตัวอย่างได้อีก:

if (contact.email != null && contact.email.contains('@') && (contact.email.contains("@news.mydomain.com") || contact.email.contains("@design.mydomain.com") )

เป้าหมายควรทำให้โค้ดอ่านง่ายขึ้น:

if (LooksSortaLikeAnEmail(contact.Email)) { ... }
else if (LooksLikeFromHeadquarters(contact.Email)) { ... }
else if (LooksLikeInternalEmail(contact.Email)) { ... }

สถานการณ์อื่น:

คุณอาจมีวิธีการเช่น:

public void SaveContact(Contact contact){
   if (contact.email != null && contact.email.contains('@'))
   {
       contacts.Add(contact);
       contacts.Save();
   }
}

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

แต่เมื่อมีคนถาม "ทำไมบันทึก '@' เพราะไม่ถูกต้อง!" และคุณตัดสินใจที่จะเพิ่มการตรวจสอบความถูกต้องจริงของการเรียงลำดับบางอย่างแล้วแยกมันออก!

คุณจะดีใจที่คุณทำเมื่อคุณต้องการบัญชีสำหรับบัญชีอีเมลที่สองของประธานาธิบดีPr3 $ sid3nt @ h0m3! @ mydomain.comและตัดสินใจที่จะออกไปลองและสนับสนุน RFC 2822

เมื่ออ่านได้:

// If there is an email property and it contains an @ sign then process
if (contact.email != null && contact.email.contains('@'))

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

// The UI passes '@' by default, the DBA's made this column non-nullable but 
// marketing is currently more concerned with other fields and '@' default is OK
if (contact.email != null && contact.email.contains('@'))

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

โดยสรุป: อย่าวัดสิ่งเหล่านี้ มุ่งเน้นไปที่หลักการที่ข้อความถูกสร้างขึ้น (DRY, SOLID, KISS)

// A valid class that does nothing
public class Nothing 
{

}

3
Whether the comments above an if statement or inside a tiny method is to me, pedantic.นี่คือ "ฟางที่หักหลังอูฐ" ปัญหา คุณพูดถูกเรื่องนี้ไม่ยากเลยที่จะอ่านออกไป แต่ถ้าคุณมีวิธีการใหญ่ (เช่นนำเข้าขนาดใหญ่) ซึ่งมีหลายสิบของการประเมินผลขนาดเล็กเหล่านี้มีเหล่านี้ห่อหุ้มในชื่อวิธีอ่าน ( IsUserActive, GetAverageIncome, MustBeDeleted, ... ) จะกลายเป็นการปรับปรุงที่เห็นได้ชัดในการอ่านรหัส ปัญหาที่เกิดขึ้นกับตัวอย่างคือมันสังเกตเห็นเพียงฟางเส้นเดียวไม่ใช่มัดทั้งหมดที่แบ่งหลังอูฐ
Flater

@Flater และฉันหวังว่านี่คือจิตวิญญาณที่ผู้อ่านจะได้รับจากสิ่งนั้น
AthomSfere

1
"encapsulation" นี้เป็นรูปแบบการต่อต้านและคำตอบนี้แสดงให้เห็นจริง เรากลับมาอ่านโค้ดเพื่อจุดประสงค์ในการดีบั๊กและเพื่อขยายโค้ด ในทั้งสองกรณีการทำความเข้าใจกับสิ่งที่รหัสจริงทำมีความสำคัญ การเริ่มต้นการบล็อกรหัสif (contact.email != null && contact.email.contains('@'))คือบั๊กซี หาก if เป็นเท็จไม่มีสิ่งอื่นใดถ้าบรรทัดสามารถเป็นจริงได้ สิ่งนี้ไม่สามารถมองเห็นได้ในLooksSortaLikeAnEmailบล็อก ฟังก์ชั่นที่มีโค้ดหนึ่งบรรทัดนั้นไม่ได้ดีไปกว่าคอมเม้นท์ที่อธิบายถึงการทำงานของบรรทัด
Quirk

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

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

6

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

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

ทางเลือกหนึ่งเมื่อไม่คุ้มที่จะสร้างฟังก์ชั่นใหม่ทั้งหมดคือการใช้ตัวแปรระดับกลาง:

boolean isEmailValid = (contact.email != null && contact.emails.contains('@');

if (isEmailValid) {
...

ซึ่งจะช่วยให้รหัสนั้นง่ายต่อการติดตามโดยไม่ต้องกระโดดไปรอบ ๆ ไฟล์มากนัก

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


บางคนมีความกังวลเกี่ยวกับรหัสบรรทัดเพิ่มเติมใกล้กับเงื่อนไข (ฉันไม่ได้เลย) แต่อาจตอบคำถามของคุณได้
Peter Mortensen

5

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

บรรทัดของโค้ดในฐานะตัวชี้วัดนั้นไม่มีความหมาย คำถามสำคัญในวิศวกรรมซอฟต์แวร์ไม่ใช่:

  • คุณมีรหัสมากเกินไปหรือไม่
  • คุณมีรหัสน้อยเกินไปหรือไม่

คำถามที่สำคัญคือ:

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

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

แน่นอนว่าฉันอาจใช้การดำเนินการบูลีนแบบยาวแทนวิธีการแบบยูทิลิตี้ แต่ฉันควรทำอย่างไร

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


3

ในระดับหนึ่งพวกเขาพูดถูก - โค้ดน้อยดีกว่า อีกคำตอบที่ยกมา Gate ฉันชอบ:

“ หากการดีบักเป็นกระบวนการลบข้อบกพร่องของซอฟต์แวร์การเขียนโปรแกรมจะต้องเป็นขั้นตอนการใส่พวกเขาเข้าไป” - Edsger Dijkstra

“ เมื่อทำการดีบั๊กมือใหม่จะใส่รหัสที่ถูกต้อง ผู้เชี่ยวชาญลบรหัสที่มีข้อบกพร่อง” - Richard Pattis

ส่วนประกอบที่ถูกที่สุดเร็วที่สุดและน่าเชื่อถือที่สุดคือส่วนประกอบที่ไม่มี - กอร์ดอนเบลล์

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

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

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

เป้าหมายสุดท้ายคือการมีโค้ดที่อ่านได้ แต่ไม่มีความพิเศษที่เกินความจำเป็น หลักการทั้งสองไม่ควรกระทำต่อกัน

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


2

มีจำนวนมากของภูมิปัญญาในคำตอบที่มีอยู่ แต่ฉันต้องการที่จะเพิ่มในอีกหนึ่งปัจจัยที่: ภาษา

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

ตัวอย่างเช่นใน Java อาจใช้เวลา 50 บรรทัดในการเขียนคลาสใหม่ที่มีคุณสมบัติสามอย่างแต่ละบรรทัดมี getter และ setter และตัวสร้างอย่างน้อยหนึ่งตัว - ในขณะที่คุณสามารถทำสิ่งเดียวกันได้อย่างสมบูรณ์ใน Kotlin * หรือ Scala (ประหยัดมากยิ่งขึ้นถ้าคุณยังต้องการที่เหมาะสมequals(), hashCode()และtoString()วิธีการ.)

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

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

ดังนั้น 'ต้นทุน' ในการทำสิ่งที่ถูกต้องขึ้นอยู่กับภาษา บางทีสัญญาณหนึ่งของภาษาที่ดีคือสิ่งที่ไม่ทำให้คุณเลือกระหว่างทำสิ่งที่ดีและทำอย่างง่าย ๆ !

(* นี่ไม่ใช่สถานที่สำหรับเสียบ แต่ Kotlin คุ้มค่ากับการดู IMHO)


1

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

นอกจากนี้ยังจัดการกับความรับผิดชอบบางอย่างของอีเมลซึ่งควรจะเป็นคลาสของตัวเอง


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

private void LargeMethod() {
    //A lot of code which modifies a lot of values. You do all sorts of tricks here.
    //Code.
    //Code..
    //Code...

    //Email validation code becoming very difficult to test as it will be difficult to ensure 
    //that you have the right data till you reach here in the method
    ValidateEmail();

    //Another whole lot of code that modifies all sorts of values.
    //Extra work to preserve the result of ValidateEmail() for your asserts later.
}

ในทางตรงกันข้ามหากคุณมีชั้นเรียนอีเมลแยกต่างหากพร้อมวิธีการตรวจสอบความถูกต้องของอีเมลจากนั้นให้ทำการทดสอบรหัสตรวจสอบความถูกต้องของหน่วยคุณเพียงแค่โทรหาEmail.Validation()ข้อมูลทดสอบของคุณเพียงครั้งเดียว


เนื้อหาโบนัส: MFeather พูดคุยเกี่ยวกับความสามารถในการทดสอบและการออกแบบที่ดี


1

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

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

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

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


1

คุณได้ระบุการแลกเปลี่ยนที่ถูกต้อง

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

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

อัลกอริทึมโลภสามารถล้มเหลวในกรณีเหล่านี้

ความซับซ้อนทำให้รหัสในความหมายบางอย่างน้อยอ่านง่ายมากกว่ามาก. ในงานก่อนหน้านี้ฉันจัดการกับ HTTP API ซึ่งมีโครงสร้างอย่างรอบคอบและแม่นยำเป็นหลายเลเยอร์ทุกปลายทางจะถูกระบุโดยคอนโทรลเลอร์ซึ่งตรวจสอบรูปร่างของข้อความที่เข้ามาแล้วมอบให้ผู้จัดการ "business-logic-layer" ซึ่งต่อมาได้ร้องขอบางส่วนของ "ชั้นข้อมูล" ซึ่งรับผิดชอบในการสร้างแบบสอบถามหลายรายการไปยังชั้น "วัตถุการเข้าถึงข้อมูล" ซึ่งรับผิดชอบในการสร้างผู้แทน SQL หลายรายซึ่งจะตอบคำถามของคุณได้จริง สิ่งแรกที่ฉันสามารถพูดเกี่ยวกับมันคืออะไรบางอย่างเช่น 90% ของรหัสที่ได้คือการคัดลอกและวางสำเร็จรูปในคำอื่น ๆ มันเป็นไม่มี ops ดังนั้นในหลายกรณีการอ่านข้อความที่ได้รับนั้น "ง่าย" มากเพราะ "โอ้ผู้จัดการรายนี้ส่งต่อคำขอไปยังวัตถุการเข้าถึงข้อมูล"จำนวนมากของบริบทการเปลี่ยนและการหาไฟล์และพยายามที่จะติดตามข้อมูลที่คุณไม่ควรได้รับการติดตาม "นี้เรียกว่า X ที่ชั้นนี้มันจะกลายเป็นเรียกว่า X' ที่ชั้นอื่น ๆ นี้แล้วมันจะเรียกว่า X '' ในอื่น ๆ ชั้นอื่น ๆ "

ฉันคิดว่าเมื่อฉันออกไป CRUD API ง่าย ๆ นี้อยู่ที่ขั้นตอนที่ถ้าคุณพิมพ์ที่ 30 บรรทัดต่อหน้ามันจะใช้เวลา 10-20 ตำราห้าร้อยหน้าบนชั้นวาง: มันเป็นสารานุกรมทั้งหมดของการทำซ้ำ รหัส. ในแง่ของความซับซ้อนที่สำคัญฉันไม่แน่ใจว่ามีแม้แต่ครึ่งหนึ่งของตำราเรียนของความซับซ้อนที่จำเป็นในนั้น เราอาจมีไดอะแกรมฐานข้อมูล 5-6 รายการเพื่อจัดการ การเปลี่ยนแปลงเล็กน้อยคือการทำแมมมอ ธ การเรียนรู้มันเป็นการทำแมมมอ ธ การเพิ่มฟังก์ชั่นใหม่จะต้องเจ็บปวดมากจนเรามีไฟล์เทมเพลตสำเร็จรูปที่เราจะใช้เพื่อเพิ่มฟังก์ชั่นใหม่

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

วิธีปฏิบัติที่ดีที่สุด: ใช้ DRY และความยาวเพื่อโทรออก

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

สิ่งที่ผมอยากจะแนะนำให้อยู่บนพื้นฐานของการพูดคุยโดยแจ็ค Diederich ที่เรียกว่าหยุดการเรียนการสอนการเขียน (youtube.com) เขาให้คะแนนที่ยอดเยี่ยมหลายอย่างในการพูดคุย: ตัวอย่างเช่นคุณสามารถรู้ได้ว่าคลาสเป็นเพียงฟังก์ชั่นเมื่อมันมีเพียงสองวิธีสาธารณะและหนึ่งในนั้นคือตัวสร้าง / initializer แต่ในกรณีหนึ่งเขากำลังพูดถึงว่าห้องสมุดสมมุติฐานที่เขามีการแทนที่สตริงสำหรับการพูดคุยเป็น "มัฟฟิน" ประกาศคลาส "มัฟฟินแฮช" ซึ่งเป็นคลาสย่อยของบิวอินdictชนิดที่ไพ ธ อนมี การนำไปใช้นั้นว่างเปล่าอย่างเต็มที่ - มีคนคิดว่า "เราอาจต้องเพิ่มฟังก์ชันการทำงานที่กำหนดเองลงในพจนานุกรมของ Python ในภายหลังเราจะแนะนำสิ่งที่เป็นนามธรรมในตอนนี้"

และการตอบโต้ที่ท้าทายของเขาก็คือ "เราสามารถทำสิ่งนั้นได้ในภายหลังหากเราต้องการ"

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

หากเราปฏิบัติตามคำแนะนำนั้นอย่างจริงจังเราต้องระบุว่าเมื่อใด“ ภายหลัง” อาจเป็นสิ่งที่ชัดเจนที่สุดคือการสร้างขีด จำกัด บนความยาวของสิ่งต่าง ๆ ด้วยเหตุผลด้านสไตล์ และฉันคิดว่าคำแนะนำที่ดีที่สุดที่เหลือคือการใช้ DRY อย่าทำซ้ำตัวคุณเองด้วยฮิวริสติกเหล่านี้เกี่ยวกับความยาวบรรทัดเพื่อปรับปรุงรูในหลักการ SOLID ขึ้นอยู่กับการวิเคราะห์พฤติกรรมของ 30 บรรทัดเป็น "หน้า" ของข้อความและการเปรียบเทียบกับร้อยแก้ว

  1. สร้างการตรวจสอบอีกครั้งในฟังก์ชั่น / วิธีเมื่อคุณต้องการคัดลอกมัน เหมือนมีเหตุผลที่ถูกต้องเป็นครั้งคราวในการคัดลอกวาง แต่คุณควรรู้สึกสกปรกเกี่ยวกับมัน ผู้แต่งที่แท้จริงไม่ได้ทำให้คุณอ่านประโยคยาวขนาดใหญ่ 50 ครั้งตลอดการบรรยายจนกว่าพวกเขาจะพยายามเน้นธีม
  2. ฟังก์ชัน / เมธอดควรเป็น "ย่อหน้า" ฟังก์ชั่นส่วนใหญ่ควรมีความยาวประมาณครึ่งหน้าหรือโค้ด 1-15 บรรทัดและอาจมีเพียง 10% ของฟังก์ชั่นของคุณที่ควรได้รับอนุญาตให้อยู่ในช่วงหน้าหนึ่งและครึ่ง, 45 บรรทัดหรือมากกว่านั้น เมื่อคุณอยู่ที่ 120+ บรรทัดของรหัสและความคิดเห็นที่สิ่งที่จะต้องแบ่งออกเป็นส่วน
  3. ไฟล์ควรเป็น "ตอน" ไฟล์ส่วนใหญ่ควรมีความยาว 12 หน้าหรือน้อยกว่าดังนั้น 360 บรรทัดของรหัสและความคิดเห็น ไฟล์ของคุณอาจมีเพียง 10% เท่านั้นที่ควรได้รับอนุญาตให้มีความยาวได้ถึง 50 หน้าหรือโค้ดและความคิดเห็น 1,500 บรรทัด
  4. ตามหลักแล้วโค้ดส่วนใหญ่ของคุณควรเยื้องกับฟังก์ชันพื้นฐานหรือลึกลงไปหนึ่งระดับ ขึ้นอยู่กับฮิวริสติกเกี่ยวกับทรีซอร์สของ Linux หากคุณเชื่อในเรื่องนี้เพียง 10% ของรหัสของคุณควรมีการเยื้อง 2 ระดับขึ้นไปภายใน baseline และน้อยกว่า 5% เยื้อง 3 ระดับขึ้นไป นี่หมายถึงโดยเฉพาะอย่างยิ่งสิ่งที่จำเป็นต้อง "ห่อ" ความกังวลอื่น ๆ เช่นการจัดการข้อผิดพลาดในการลอง / จับขนาดใหญ่ควรถูกดึงออกมาจากตรรกะที่แท้จริง

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

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