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

รหัสโปรแกรมต้องอ่านง่าย

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

     ตัวอย่างของสิ่งที่จะทำให้โปรแกรมอ่านยาก (1) แบบแผนการตั้งชื่อที่ไม่ดี: การใช้ชื่อตัวแปร ฟังก์ชัน หรือคลาสที่ไม่สื่อความหมายหรือกำกวมอาจทำให้เข้าใจวัตถุประสงค์และการทำงานของโปรแกรมได้ยาก (2) ขาดความคิดเห็น: การที่โปรแกรมไม่มีความคิดเห็นหรือเอกสารประกอบที่เพียงพออาจทำให้เข้าใจวัตถุประสงค์และการนำส่วนต่างๆ ไปใช้ได้ยาก (3) การจัดรูปแบบไม่สอดคล้องกัน: การเยื้อง การเว้นวรรค หรือการจัดรูปแบบที่ไม่สอดคล้องกันอาจทำให้โปรแกรมอ่านยากและทำตามได้ยาก โดยเฉพาะในโปรแกรมขนาดใหญ่ (4) การใช้ตัวย่อหรือคำย่อมากเกินไป: การใช้ตัวย่อหรือคำย่อมากเกินไปอาจทำให้ยากต่อการเข้าใจถ้าไม่รู้มาก่อน (5) โครงสร้างการควบคุมที่ซับซ้อน: โครงสร้างการควบคุมที่ซับซ้อนมากเกินไป เช่น การวนซ้ำซ้อนๆกัน คำสั่ง if-else หลายคำสั่งหรือซ้อนๆกัน อาจทำให้เข้าใจลำดับการทำงานและตรรกะของโปรแกรมได้ยาก (6) การทำให้เป็นโมดูลไม่ดี: การขาดการทำให้เป็นโมดูลอย่างเหมาะสมอาจทำให้ยากต่อการระบุและทำความเข้าใจความสัมพันธ์ระหว่างส่วนต่าง ๆ ของโปรแกรม (7) ความซ้ำซ้อน: การใช้ส่วนของโปรแกรมซ้ำซ้อนหรือการใช้คำสั่งซ้ำซ้อนอาจทำให้อ่านและเข้าใจโปรแกรมได้ยากขึ้น (8) การใช้การสืบทอด (inheritance) และความหลากหลาย (polymorphism) มากเกินไป: การใช้การสืบทอดและความหลากหลายมากเกินไปอาจทำให้โปรแกรมติดตามได้ยากขึ้นและอาจนำไปสู่ลำดับชั้นของคลาสที่สับสน (9) ขาดการจัดการข้อผิดพลาด: โปรแกรมที่ไม่มีการจัดการข้อผิดพลาดเพียงพออาจแก้ไขจุดบกพร่องและเข้าใจได้ยากเมื่อเกิดข้อผิดพลาด (10) ความซับซ้อนที่ไม่จำเป็น: โซลูชันที่ได้รับการออกแบบทางวิศวกรรมมากเกินไปอาจทำให้โปรแกรมอ่านและเข้าใจได้ยาก โดยเฉพาะอย่างยิ่งหากสามารถใช้ทางเลือกที่ง่ายกว่าได้

Static Analysis และ Linters 

     เมื่อเราเขียนโปรแกรมแล้วเราสามารถใช้เครื่องมือต่างๆในการตรวจสอบว่าโปรแกรมที่เราเขียนมีข้อผิดพลาดหรือไม่ เครื่องมือที่ใช้จะมีอยู่ 2 แบบคือ static analysis tool และ linter

     static analysis tools จะเป็นโปรแกรมที่ตรวจสอบรหัสโปรแกรมของเราโดยที่ไม่ได้เรียกให้โปรแกรมทำงาน โดยจะตรวจสอบทั้งสิ่งที่เป็นข้อผิดพลาดและสื่งที่อาจจะเป็นช่องโหว่ของโปรแกรม เช่น การเขียนโปรแกรมไปจากไวยากรณ์ที่กำหนด (systax error) รหัสโปรแกรมที่ไม่ถูกใช้ (unreachable code) ตัวแปรที่ไม่ถูกใช้ (unused variable) เงื่อนไขที่ทำให้เกิดการวนรอบไม่รู้จบ (deadlock and race condition) และโอกาสที่จะเกิดช่องโหว่อื่นๆ (potential security vulnerability) ลักษณะโปรแกรมแบบนี้ที่เราต้องใช้แน่ๆคือตัวคอมไพล์โปรแกรม (compiler) แต่ก็ยังมีโปรแกรมอื่นๆอีก เช่น SonarQube, Coverity, Checkstyle, PMD และ FindBugs เป็นต้น แต่ถึงแม้ว่าโปรแกรมเหล่านี้จะช่วยให้เราป้องกันข้อผิดพลาดได้ แต่ก็ไม่ได้หมายความว่ามันจะมาแทนที่การตรวจสอบรหัสโปรแกรมและการทดสอบ

     เนื่องจาก static analysis tool จะตรวจสอบโดยยึดจากไวยากรณ์ของภาษาเป็นหลัก ดังนั้รเราจึงต้องใช้ linter เพื่อตรวจสอบข้อผิดพลาดด้านจุดมุ่งหมายของโปรแกรม เช่น int a=5; int b=10; int sum = a*b; จากตัวอย่างจะเห็นว่าในเรื่องไวยากรณ์นั้นไม่มีข้อผิดพลาด แต่ในเรื่องของจุดมุ่งหมายซึ่งต้องการผลรวมแต่ดำเนินการด้วยการคูณ อันนี้คือผิดจุดมุ่งหมาย 

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

การใช้วิธีการอัตโนมัติ

     การใช้วิธีการแบบอัตโนมัติ (automation) เป็นสิ่งหนึ่งที่ทำให้กระบวนการพัฒนาโปรแกรมน่าเชื่อถือ ตัวอย่างเช่น จากภาพเป็นกระบวนการในการแก้ไขโปรแกรมซึ่งตามมาด้วยโหลดไลบรารี่ที่เกี่ยวข้องมาใช้งาน หากเกิดปัญหาก็จะกลับไปที่ขั้นตอนการแก้ไขโปรแกรมเพื่อแก้ไขให้ถูกต้อง หากไม่มีปัญหาก็จะไปต่อยังขั้นตอนการคอมไพล์ ขั้นตอนการทดสอบ และขั้นตอนการนำไปใช้งาน ซึ่งทุกๆขั้นตอนหากเกิดปัญหาก็จะกลับไปที่ขั้นตอนการแก้ไขโปรแกรมเพื่อแก้ไขให้ถูกต้อง จะเห็นว่ากระบวนการเป็นการทำซ้ำๆ และถ้าเรามองลงไปในวิธีการปฏิบัติจะเห็นว่าขั้นตอนต่างๆหากไม่ได้ใช้วิธีการแบบอัตโนมัติจะมีความเสี่ยงที่จะเกิดข้อผิดพลาดได้ง่าย และในการนำไปใช้งานหากพบข้อผิดพลาดเราก็ต้องสามารถย้อนกลับไปยังเวอร์ชั่นล่าสุดที่ใช้งานได้ ดังนั้นการใช้วิธีการแบบอัตโนมัติจะช่วยให้กระบวนการวนซ้ำได้ง่าย (repeatable) เชื่อถือได้ (reliable) และย้อนกลับได้ (revertible) ด้วยเครื่องมือในปัจจุบันช่วยให้เราใช้วิธีแบบอัตโนมัติได้ทุกขั้นตอนยกเว้นในขั้นตอนแรกคือการแก้ไขโปรแกรม เริ่มตั้งแต่การโหลดโปรแกรมและไลบรารี่โดยใช้ NPM, maven หรือ gradle การคอมไพล์โปรแกรมก็เป็นไปอย่างอัตโนมัติอยู่แล้ว การทดสอบก็สามารถใช้การทดสอบแบบอัตโนมัติ และการนำไปใช้งานยิ่งต้องการวิธีการแบบอัตโนมัติรวมถึงการย้อนกลับเวอร์ชั่นด้วย

ลักษณะโปรแกรมที่บำรุงรักษาได้ยาก

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

     ลักษณะของโปรแกรมที่ส่อว่าจะก่อปัญหาหรือบำรุงรักษาได้ยากในอนาคต (code smell) มีสาเหตุมาจากหลายอย่าง เช่น  (1) ส่วนย่อยของโปรแกรมมีขนาดใหญ่เกินไป (bloater) เช่น การมีเมธอดที่ยาวเกินไป คลาสที่ใหญ่เกินไป การมีพารามิเตอร์เยอะเกินไป ทำให้ยากต่อการทำความเข้าใจและยากต่อการแก้ไขปรับปรุง (2) การใช้แนวคิดเชิงวัตถุที่ไม่ถูกต้อง (OO abuser) เช่น การใช้การสืบทอดอย่างไม่ถูกต้องทำให้คลาสลูกไม่สามารถแทนที่คลาสแม่ได้ ซึ่งผิดไปจากหลักการ Liskov Substitution (3) ยากต่อการเปลี่ยนแปลง (change preventer) เช่น เมื่อมีการแก้ไขแล้วต้องตามไปแก้ไขในหลายๆส่วนของโปรแกรมให้สอดคล้องกัน (4) โปรแกรมซับซ้อนโดยไม่จำเป็น (dispensible) เช่น การใช้ส่วนของโปรแกรมเดียวกันซ้ำๆในหลายๆที่ (duplicated code) การมีส่วนของโปรแกรมที่ไม่ได้ถูกใช้ (dead code) หรือการเขียนโปรแกรมเผื่อไว้ก่อนทั้งที่ยังไม่ได้ใช้ (speculative generality) (5) การผูกพันที่ไม่จำเป็น (coupler) เช่น คลาสที่ทำงานโดยขึ้นอยู่กับพฤติกรรมหรือข้อมูลของคลาสอื่น หรือคลาสที่ทำหน้าที่เชื่อมระหว่างคลาสโดยที่ตัวเองไม่ได้ทำอะไร 

การปรับโครงสร้าง

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

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

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

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

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