บทที่ 1 อธิบายว่าการพัฒนาซอฟต์แวร์เป็นกระบวนการแบบวนซ้ำและเพิ่มทีละน้อย ระบบซอฟต์แวร์ขนาดใหญ่พัฒนาผ่านขั้นตอนวิวัฒนาการหลายขั้นตอน ซึ่งแต่ละขั้นตอนจะเพิ่มความสามารถใหม่และปรับปรุงโมดูลที่มีอยู่ ซึ่งหมายความว่าการออกแบบของระบบมีการพัฒนาอยู่ตลอดเวลา เป็นไปไม่ได้ที่จะออกแบบระบบให้ถูกต้องตั้งแต่เริ่มต้น การออกแบบของระบบที่เติบโตเต็มที่นั้นถูกกำหนดโดยการเปลี่ยนแปลงที่เกิดขึ้นระหว่างวิวัฒนาการของระบบมากกว่าแนวคิดเริ่มต้นใดๆ บทก่อนหน้านี้ได้อธิบายวิธีลดความซับซ้อนระหว่างการออกแบบและพัฒนาเริ่มแรก บทนี้จะกล่าวถึงวิธีป้องกันไม่ให้ความซับซ้อนแทรกซึมเข้ามาเมื่อระบบมีวิวัฒนาการ
16.1 Stay strategic (การคิดเชิงกลยุทธ์)
บทที่ 3 ได้แนะนำความแตกต่างระหว่าง tactical programming และ strategic programming: ในการเขียนโปรแกรมแบบ tactical เป้าหมายหลักคือการทำให้บางอย่างทำงานได้อย่างรวดเร็ว แม้ว่าจะทำให้เกิดความซับซ้อนเพิ่มขึ้นก็ตาม ในขณะที่การเขียนโปรแกรมแบบ strategic เป้าหมายที่สำคัญที่สุดคือการสร้างการออกแบบระบบที่ยอดเยี่ยม แนวทาง tactical นำไปสู่การออกแบบระบบที่ยุ่งเหยิงอย่างรวดเร็ว ถ้าคุณต้องการให้ระบบดูแลรักษาและปรับปรุงได้ง่าย การ "ทำงานได้" ถือเป็นมาตรฐานที่ไม่สูงพอ คุณต้องให้ความสำคัญกับการออกแบบและคิดเชิงกลยุทธ์ แนวคิดนี้ยังใช้ได้เมื่อคุณกำลังแก้ไขโค้ดที่มีอยู่ด้วย
แต่น่าเสียดายที่เมื่อนักพัฒนาต้องเข้าไปแก้ไขโค้ดที่มีอยู่ ไม่ว่าจะเป็นการแก้ไขบั๊กหรือเพิ่มฟีเจอร์ใหม่ พวกเขามักจะไม่ได้คิดเชิงกลยุทธ์ ความคิดทั่วไปคือ "อะไรคือการเปลี่ยนแปลงที่เล็กที่สุดที่ฉันสามารถทำได้เพื่อให้ได้สิ่งที่ต้องการ?" บางครั้งนักพัฒนาให้เหตุผลว่าพวกเขาไม่ถนัดกับโค้ดที่กำลังแก้ไข พวกเขากังวลว่าการเปลี่ยนแปลงที่ใหญ่ขึ้นจะเพิ่มความเสี่ยงในการทำให้เกิดบั๊กใหม่ อย่างไรก็ตาม สิ่งนี้ส่งผลให้เกิด tactical programming การเปลี่ยนแปลงเล็กๆ น้อยๆ แต่ละครั้งจะเพิ่ม special cases, dependencies, หรือความซับซ้อนในรูปแบบอื่นๆ ผลลัพธ์คือการออกแบบระบบแย่ลงไปอีก และปัญหาก็สะสมตามแต่ละขั้นตอนของวิวัฒนาการของระบบ
ถ้าคุณต้องการรักษาการออกแบบที่สะอาดสำหรับระบบ คุณต้องใช้แนวทางเชิงกลยุทธ์เมื่อแก้ไขโค้ดที่มีอยู่ ตามหลักการแล้ว เมื่อคุณทำการเปลี่ยนแปลงแต่ละอย่างเสร็จสิ้น ระบบควรมีโครงสร้างแบบเดียวกับที่คุณจะออกแบบตั้งแต่แรกถ้าคุณคิดถึงการเปลี่ยนแปลงนั้นไว้ล่วงหน้า เพื่อให้บรรลุเป้าหมายนี้ คุณต้องต่อต้านสิ่งล่อใจที่จะแก้ไขแบบเร่งด่วน ให้คิดแทนว่าการออกแบบระบบปัจจุบันยังดีที่สุดอยู่หรือไม่ เมื่อพิจารณาถึงการเปลี่ยนแปลงที่ต้องการ ถ้าไม่ใช่ ก็ให้ refactor ระบบเพื่อให้คุณได้การออกแบบที่ดีที่สุดเท่าที่จะเป็นไปได้ ด้วยแนวทางนี้ การออกแบบระบบจะดีขึ้นทุกครั้งที่มีการแก้ไข
นี่คือตัวอย่างของแนวคิดการลงทุน (investment mindset) ที่กล่าวถึงในหน้า 15: ถ้าคุณลงทุนเพิ่มเวลาเล็กน้อยในการ refactor และปรับปรุงการออกแบบระบบ คุณจะได้ระบบที่สะอาดขึ้น ซึ่งจะช่วยเร่งการพัฒนา และคุณจะได้รับผลตอบแทนจากความพยายามที่ลงทุนในการ refactor กลับคืนมา แม้ว่าการเปลี่ยนแปลงเฉพาะของคุณจะไม่จำเป็นต้อง refactor คุณก็ควรมองหาจุดบกพร่องในการออกแบบที่คุณสามารถแก้ไขได้ในขณะที่กำลังอยู่กับโค้ด เมื่อใดก็ตามที่คุณแก้ไขโค้ด ให้พยายามหาวิธีปรับปรุงการออกแบบระบบอย่างน้อยเล็กน้อยในกระบวนการนั้น ถ้าคุณไม่ได้ทำให้การออกแบบดีขึ้น คุณกำลังทำให้มันแย่ลง
ดังที่ได้กล่าวไว้ใน บทที่ 3 แนวคิดการลงทุนบางครั้งขัดแย้งกับความเป็นจริงของการพัฒนาซอฟต์แวร์เชิงพาณิชย์ ถ้าการ refactor ระบบ "อย่างถูกต้อง" ใช้เวลาสามเดือน แต่การแก้ไขแบบเร็วๆ สกปรกๆ ใช้เวลาแค่สองชั่วโมง คุณอาจจำต้องใช้วิธีที่รวดเร็วและสกปรก โดยเฉพาะอย่างยิ่งถ้าคุณต้องทำงานให้ทันกำหนดส่ง หรือถ้าการ refactor ระบบจะสร้างความไม่เข้ากันที่ส่งผลต่อคนและทีมอื่นๆ จำนวนมาก การ refactor ก็อาจไม่สามารถทำได้ในทางปฏิบัติ
อย่างไรก็ตาม คุณควรต่อต้านการประนีประนอมเหล่านี้เท่าที่จะทำได้ ถามตัวเองว่า "นี่คือสิ่งที่ดีที่สุดที่ฉันสามารถทำได้เพื่อสร้างการออกแบบระบบที่สะอาด ภายใต้ข้อจำกัดที่มีอยู่หรือไม่?" บางทีอาจมีแนวทางอื่นที่เกือบจะสะอาดเท่ากับการ refactor สามเดือน แต่ทำได้ภายในสองสามวัน? หรือถ้าคุณไม่สามารถทำการ refactor ขนาดใหญ่ได้ในตอนนี้ ให้ขอให้หัวหน้าของคุณจัดสรรเวลาให้คุณกลับมาทำหลังจากกำหนดส่งปัจจุบัน ทุกองค์กรพัฒนาควรวางแผนที่จะใช้เวลาเพียงเล็กน้อยของความพยายามทั้งหมดในการทำความสะอาดและ refactor; งานนี้จะคุ้มค่าในระยะยาว
16.2 Maintaining comments: keep the comments near the code (การดูแลคอมเมนต์: วางคอมเมนต์ไว้ใกล้กับโค้ด)
เมื่อคุณเปลี่ยนแปลงโค้ดที่มีอยู่ มีโอกาสสูงที่การเปลี่ยนแปลงจะทำให้คอมเมนต์ที่มีอยู่บางส่วนใช้ไม่ได้อีกต่อไป เป็นเรื่องง่ายที่จะลืมอัปเดตคอมเมนต์เมื่อคุณแก้ไขโค้ด ซึ่งส่งผลให้คอมเมนต์ไม่ถูกต้องอีกต่อไป คอมเมนต์ที่ไม่ถูกต้องทำให้ผู้อ่านหงุดหงิด และถ้ามีจำนวนมากเกินไป ผู้อ่านจะเริ่มไม่เชื่อถือคอมเมนต์ทั้งหมด โชคดีที่ด้วยวินัยเล็กน้อยและกฎชี้นำสองสามข้อ ก็สามารถรักษาคอมเมนต์ให้ทันสมัยได้โดยไม่ต้องใช้ความพยายามมากมาย ส่วนนี้และส่วนต่อไปจะเสนอเทคนิคเฉพาะบางอย่าง
วิธีที่ดีที่สุดที่จะมั่นใจได้ว่าคอมเมนต์จะถูกอัปเดตคือการวางคอมเมนต์ไว้ใกล้กับโค้ดที่มันอธิบาย เพื่อที่นักพัฒนาจะได้เห็นคอมเมนต์เมื่อพวกเขาเปลี่ยนแปลงโค้ด ยิ่งคอมเมนต์อยู่ห่างจากโค้ดที่เกี่ยวข้องมากเท่าไหร่ โอกาสที่คอมเมนต์จะถูกอัปเดตอย่างถูกต้องก็ยิ่งน้อยลงเท่านั้น ตัวอย่างเช่น ตำแหน่งที่ดีที่สุดสำหรับ interface comment ของ method อยู่ในไฟล์โค้ด ถัดจาก body ของ method การเปลี่ยนแปลงใดๆ กับ method จะเกี่ยวข้องกับโค้ดนี้ ดังนั้นนักพัฒนามีแนวโน้มที่จะเห็น interface comments และอัปเดตถ้าจำเป็น
อีกทางเลือกหนึ่งสำหรับภาษาเช่น C และ C++ ที่มีไฟล์โค้ดและ header file แยกจากกัน คือการวาง interface comments ไว้ถัดจาก declaration ของ method ในไฟล์ .h อย่างไรก็ตาม นี่เป็นระยะทางที่ห่างจากโค้ดมาก นักพัฒนาจะไม่เห็นคอมเมนต์เหล่านั้นเมื่อแก้ไข body ของ method และต้องทำงานเพิ่มเติมในการเปิดไฟล์อื่นและหา interface comments เพื่ออัปเดต บางคนอาจแย้งว่า interface comments ควรอยู่ใน header file เพื่อให้ผู้ใช้สามารถเรียนรู้วิธีใช้งาน abstraction โดยไม่ต้องดูไฟล์โค้ด อย่างไรก็ตาม ผู้ใช้ไม่ควรต้องอ่านทั้งไฟล์โค้ดหรือ header file; พวกเขาควรได้รับข้อมูลจาก documentation ที่รวบรวมโดยเครื่องมืออย่าง Doxygen หรือ Javadoc นอกจากนี้ IDE หลายตัวจะดึงและแสดง documentation ให้ผู้ใช้ เช่น การแสดง documentation ของ method เมื่อพิมพ์ชื่อ method ด้วยเครื่องมือเหล่านี้ เอกสารควรอยู่ในตำแหน่งที่สะดวกที่สุดสำหรับนักพัฒนาที่ทำงานกับโค้ด
เมื่อเขียน implementation comments อย่าใส่คอมเมนต์ทั้งหมดของ method ไว้ที่ด้านบนของ method ให้กระจายคอมเมนต์ออกไป โดยดันแต่ละคอมเมนต์ลงไปที่ขอบเขตที่แคบที่สุดที่ครอบคลุมโค้ดทั้งหมดที่คอมเมนต์นั้นอ้างถึง ตัวอย่างเช่น ถ้า method มีสามขั้นตอนหลัก อย่าเขียนคอมเมนต์เดียวที่ด้านบนของ method ที่อธิบายทุกรายละเอียดของทั้งสามขั้นตอน ให้เขียนคอมเมนต์แยกต่างหากสำหรับแต่ละขั้นตอนและวางคอมเมนต์นั้นไว้เหนือบรรทัดแรกของโค้ดในขั้นตอนนั้น ในทางกลับกัน การมีคอมเมนต์ที่ด้านบนของ implementation ของ method ที่อธิบายกลยุทธ์โดยรวมก็มีประโยชน์เช่นกัน:
// We proceed in three phases:
// Phase 1: Find feasible candidates
// Phase 2: Assign each candidate a score
// Phase 3: Choose the best, and remove it
รายละเอียดเพิ่มเติมสามารถบันทึกไว้เหนือโค้ดของแต่ละขั้นตอนได้
โดยทั่วไป ยิ่งคอมเมนต์อยู่ห่างจากโค้ดที่มันอธิบายมากเท่าไหร่ คอมเมนต์ก็ควรจะเป็นนามธรรมมากขึ้นเท่านั้น (ซึ่งช่วยลดโอกาสที่คอมเมนต์จะถูกทำให้ใช้ไม่ได้โดยการเปลี่ยนแปลงโค้ด)
16.3 Comments belong in the code, not the commit log (คอมเมนต์ต้องอยู่ในโค้ด ไม่ใช่ใน commit log)
ข้อผิดพลาดที่พบบ่อยเมื่อแก้ไขโค้ดคือการใส่ข้อมูลรายละเอียดเกี่ยวกับการเปลี่ยนแปลงไว้ใน commit message ของ source code repository แต่ไม่ได้บันทึกไว้ในโค้ด แม้ว่า commit messages จะสามารถเรียกดูได้ในอนาคตโดยการสแกน log ของ repository แต่นักพัฒนาที่ต้องการข้อมูลนั้นไม่น่าจะคิดที่จะสแกน repository log ถึงแม้พวกเขาจะสแกน log ก็จะเป็นการน่าเบื่อในการหา log message ที่ถูกต้อง
เมื่อเขียน commit message ให้ถามตัวเองว่านักพัฒนาจะต้องใช้ข้อมูลนั้นในอนาคตหรือไม่ ถ้าใช่ ก็ควรบันทึกข้อมูลนี้ไว้ในโค้ด ตัวอย่างคือ commit message ที่อธิบายปัญหาที่ซับซ้อนซึ่งเป็นสาเหตุให้มีการเปลี่ยนแปลงโค้ด ถ้าสิ่งนี้ไม่ได้ถูกบันทึกในโค้ด นักพัฒนาที่เข้ามาทีหลังอาจยกเลิกการเปลี่ยนแปลงนั้นโดยไม่รู้ว่าพวกเขากำลังสร้างบั๊กขึ้นมาใหม่ ถ้าคุณต้องการรวมสำเนาข้อมูลนี้ใน commit message ด้วย ก็ไม่เป็นไร แต่สิ่งที่สำคัญที่สุดคือการใส่ไว้ในโค้ด นี่แสดงให้เห็นถึงหลักการของการวาง documentation ในตำแหน่งที่นักพัฒนามีแนวโน้มจะเห็นมากที่สุด; commit log ไม่ค่อยเป็นตำแหน่งนั้น
16.4 Maintaining comments: avoid duplication (การดูแลคอมเมนต์: หลีกเลี่ยงการทำซ้ำ)
เทคนิคที่สองสำหรับการรักษาคอมเมนต์ให้ทันสมัยคือการหลีกเลี่ยงการทำซ้ำ ถ้า documentation ถูกทำซ้ำ นักพัฒนาจะค้นหาและอัปเดตสำเนาที่เกี่ยวข้องทั้งหมดได้ยากขึ้น ให้ลองบันทึกการตัดสินใจในการออกแบบแต่ละครั้งเพียงครั้งเดียวแทน ถ้ามีหลายตำแหน่งในโค้ดที่ได้รับผลกระทบจากการตัดสินใจใดโดยเฉพาะ อย่าทำ documentation ซ้ำในแต่ละจุดเหล่านั้น ให้หาตำแหน่งเดียวที่ชัดเจนที่สุดในการวาง documentation ตัวอย่างเช่น สมมติว่ามีพฤติกรรมที่ซับซ้อนเกี่ยวข้อง กับตัวแปรหนึ่ง ซึ่งส่งผลต่อหลายตำแหน่งที่ตัวแปรนั้นถูกใช้งาน คุณสามารถบันทึกพฤติกรรมนั้นในคอมเมนต์ถัดจาก declaration ของตัวแปร นี่เป็นตำแหน่งธรรมชาติที่นักพัฒนามักจะตรวจสอบถ้าพวกเขามีปัญหาในการทำความเข้าใจโค้ดที่ใช้ตัวแปรนั้น
ถ้าไม่มีตำแหน่ง "ที่ชัดเจน" เพียงตำแหน่งเดียวสำหรับวาง documentation ชิ้นใดโดยเฉพาะที่นักพัฒนาจะหาเจอ ให้สร้างไฟล์ designNotes ตามที่อธิบายไว้ใน ส่วนที่ 13.7 หรือเลือกตำแหน่งที่ดีที่สุดจากตำแหน่งที่มีอยู่และวาง documentation ไว้ที่นั่น นอกจากนี้ ให้เพิ่มคอมเมนต์สั้นๆ ในตำแหน่งอื่นๆ ที่อ้างอิงถึงตำแหน่งกลาง: "ดูคอมเมนต์ใน xyz สำหรับคำอธิบายของโค้ดด้านล่าง" ถ้าการอ้างอิงนั้นล้าสมัยเนื่องจาก master comment ถูกย้ายหรือถูกลบ ความไม่สอดคล้องนี้จะชัดเจนในตัวเองเพราะนักพัฒนาจะไม่พบคอมเมนต์ในตำแหน่งที่ระบุ; พวกเขาสามารถใช้ revision control history เพื่อค้นหาว่าเกิดอะไรขึ้นกับคอมเมนต์นั้นและอัปเดตการอ้างอิง ในทางตรงกันข้าม ถ้า documentation ถูกทำซ้ำและบางสำเนาไม่ได้ถูกอัปเดต ก็จะไม่มีสัญญาณใดๆ บอกนักพัฒนาว่าพวกเขากำลังใช้ข้อมูลที่ล้าสมัย
อย่าเขียน documentation การตัดสินใจออกแบบของโมดูลหนึ่งซ้ำในอีกโมดูลหนึ่ง ตัวอย่างเช่น อย่าใส่คอมเมนต์ก่อนการเรียก method ที่อธิบายสิ่งที่เกิดขึ้นใน method ที่ถูกเรียก ถ้าผู้อ่านต้องการทราบ พวกเขาควรดูที่ interface comments ของ method นั้น เครื่องมือพัฒนาที่ดีมักจะให้ข้อมูลนี้โดยอัตโนมัติ เช่น การแสดง interface comments ของ method เมื่อคุณเลือกชื่อ method หรือวางเมาส์เหนือมัน พยายามทำให้นักพัฒนาสามารถค้นหา documentation ที่เหมาะสมได้ง่าย แต่อย่าทำโดยการทำ documentation ซ้ำ
ถ้าข้อมูลถูกบันทึกไว้ที่อื่นนอกโปรแกรมของคุณแล้ว อย่าทำ documentation ซ้ำภายในโปรแกรม; แค่ reference ไปยัง documentation ภายนอก ตัวอย่างเช่น ถ้าคุณเขียน class ที่ implement HTTP protocol ก็ไม่จำเป็นต้องอธิบาย HTTP protocol ภายในโค้ดของคุณ มีแหล่งข้อมูลมากมายบนเว็บสำหรับ documentation นี้; แค่เพิ่มคอมเมนต์สั้นๆ ในโค้ดของคุณพร้อม URL ของแหล่งใดแหล่งหนึ่ง อีกตัวอย่างคือฟีเจอร์ที่ถูกบันทึกไว้ในคู่มือผู้ใช้แล้ว สมมติว่าคุณกำลังเขียนโปรแกรมที่ implement ชุดของคำสั่ง โดยมี method หนึ่งรับผิดชอบในการ implement แต่ละคำสั่ง ถ้ามีคู่มือผู้ใช้ที่อธิบายคำสั่งเหล่านั้น ก็ไม่จำเป็นต้องทำข้อมูลนี้ซ้ำในโค้ด ให้ใส่หมายเหตุสั้นๆ เช่นต่อไปนี้ใน interface comment ของแต่ละ command method แทน:
// Implements the Foo command; see the user manual for details.
สิ่งสำคัญคือผู้อ่านสามารถค้นหา documentation ทั้งหมดที่จำเป็นเพื่อทำความเข้าใจโค้ดของคุณได้อย่างง่ายดาย แต่นั่นไม่ได้หมายความว่าคุณต้องเขียน documentation ทั้งหมดนั้นด้วยตัวเอง
16.5 Maintaining comments: check the diffs (การดูแลคอมเมนต์: ตรวจสอบ diffs)
วิธีที่ดีวิธีหนึ่งในการทำให้แน่ใจว่า documentation ยังคงทันสมัยคือการสละเวลาไม่กี่นาทีก่อนที่จะ commit การเปลี่ยนแปลงเข้าสู่ revision control system เพื่อสแกนดูการเปลี่ยนแปลงทั้งหมดใน commit นั้น; ตรวจสอบให้แน่ใจว่าแต่ละเปลี่ยนแปลงได้ถูกสะท้อนใน documentation อย่างถูกต้อง การสแกนก่อน commit นี้ยังช่วยตรวจจับปัญหาอื่นๆ ได้อีก เช่น การทิ้ง debugging code ไว้ในระบบโดยไม่ตั้งใจ หรือการลืมแก้ไข TODO items
16.6 Higher-level comments are easier to maintain (คอมเมนต์ระดับสูงดูแลรักษาง่ายกว่า)
ข้อคิดสุดท้ายเกี่ยวกับการดูแล documentation: คอมเมนต์จะดูแลรักษาง่ายกว่าถ้ามันเป็นระดับสูงและเป็นนามธรรมมากกว่าโค้ด คอมเมนต์เหล่านี้ไม่ได้สะท้อนรายละเอียดของโค้ด ดังนั้นมันจะไม่ได้รับผลกระทบจากการเปลี่ยนแปลงโค้ดเล็กน้อย; มีเพียงการเปลี่ยนแปลงในพฤติกรรมโดยรวมเท่านั้นที่จะส่งผลต่อคอมเมนต์เหล่านี้ แน่นอน ดังที่ได้กล่าวไว้ใน บทที่ 13 คอมเมนต์บางประเภทจำเป็นต้องมีรายละเอียดและแม่นยำ แต่โดยทั่วไป คอมเมนต์ที่มีประโยชน์ที่สุด (ที่ไม่ใช่แค่การพูดซ้ำโค้ด) ก็เป็นคอมเมนต์ที่ดูแลรักษาง่ายที่สุดเช่นกัน