(It's All About Complexity)
การเขียนซอฟต์แวร์คอมพิวเตอร์เป็นหนึ่งในกิจกรรมสร้างสรรค์ที่บริสุทธิ์ที่สุดในประวัติศาสตร์ของมนุษยชาติ โปรแกรมเมอร์ไม่ถูกผูกพันด้วยข้อจำกัดในทางปฏิบัติ เช่น กฎของฟิสิกส์ เราสามารถสร้างโลกเสมือนที่น่าตื่นเต้นได้ โดยมีพฤติกรรมที่ไม่สามารถมีอยู่ในโลกจริงได้เลย การเขียนโปรแกรมไม่ต้องการทักษะเชิงร่างกายที่ยอดเยี่ยมหรือความประสานสัมพันธ์ เช่น ในบัลเล่ต์หรือบาสเกตบอล สิ่งเดียวที่การเขียนโปรแกรมต้องการคือใจที่สร้างสรรค์และความสามารถในการจัดระเบียบความคิดของคุณ หากคุณสามารถจินตนาการถึงระบบหนึ่งได้ คุณอาจจะสามารถนำมันไปใช้ในโปรแกรมคอมพิวเตอร์ได้
ซึ่งหมายความว่าข้อจำกัดที่ยิ่งใหญ่ที่สุดในการเขียนซอฟต์แวร์คือความสามารถของเราในการเข้าใจระบบที่เราสร้างขึ้น เมื่อโปรแกรมพัฒนาตัวและได้รับฟีเจอร์เพิ่มเติม มันจะกลายเป็นที่ซับซ้อน โดยมีการพึ่งพาแบบละเอียดอ่อนระหว่างส่วนประกอบต่างๆ เมื่อเวลาผ่านไป ความซับซ้อนจะเพิ่มขึ้นไปเรื่อยๆ และจะยิ่งยากขึ้นเรื่อยๆ สำหรับโปรแกรมเมอร์ในการจำปัจจัยที่เกี่ยวข้องทั้งหมดไว้ในใจขณะที่พวกเขากำลังปรับเปลี่ยนระบบ สิ่งนี้ทำให้การพัฒนาช้าลง และนำไปสู่บั๊กซ์ ซึ่งทำให้การพัฒนาช้าลงอีกและเพิ่มต้นทุนของมัน ความซับซ้อนเพิ่มขึ้นอย่างหลีกเลี่ยงไม่ได้ตลอดอายุการใช้งานของโปรแกรมใดๆ ยิ่งโปรแกรมใหญ่ขึ้น และยิ่งมีคนทำงานกับมันมากขึ้น ความยากลำบากในการจัดการความซับซ้อนก็ยิ่งมากขึ้น
เครื่องมือพัฒนาที่ดีสามารถช่วยเราจัดการความซับซ้อนได้ และมีเครื่องมือที่ยอดเยี่ยมมากมายที่ถูกสร้างสรรค์ตลอดหลายทศวรรษที่ผ่านมา แต่มีข้อจำกัดว่าเราสามารถทำได้ด้วยเครื่องมือเพียงอย่างเดียว หากเราต้องการทำให้การเขียนซอฟต์แวร์ง่ายขึ้น เพื่อให้เราสามารถสร้างระบบที่ทรงพลังมากขึ้นในราคาที่ถูกลง เราต้องหาวิธีในการทำให้ซอฟต์แวร์ง่ายขึ้น ความซับซ้อนจะยังคงเพิ่มขึ้นตลอดเวลา แม้จะใช้ความพยายามอย่างเต็มที่ของเรา แต่การออกแบบที่ง่ายขึ้นช่วยให้เราสามารถสร้างระบบที่ใหญ่ขึ้นและทรงพลังมากขึ้นได้ก่อนที่ความซับซ้อนจะกลายเป็นอุปสรรค์
มีวิธีการต่างๆ สองประการทั่วไปในการต่อสู้กับความซับซ้อน ทั้งสองอย่างจะถูก กล่าวถึงในหนังสือเล่มนี้ วิธีการแรกคือการกำจัดความซับซ้อนโดยทำให้โค้ดง่ายขึ้นและชัดเจนขึ้น ตัวอย่างเช่น ความซับซ้อนสามารถลดลงได้โดยการกำจัดกรณีพิเศษหรือใช้ตัวระบุตัวตนในลักษณะที่สอดคล้องกัน
วิธีการที่สองในการจัดการกับความซับซ้อนคือการห่อหุ้มมัน เพื่อให้โปรแกรมเมอร์สามารถทำงานกับระบบได้โดยไม่ต้องสัมผัสกับความซับซ้อนทั้งหมดของระบบในครั้งเดียว วิธีการนี้เรียกว่า modular design ในการออกแบบแบบ modular ระบบซอฟต์แวร์จะถูกแบ่งออกเป็น modules เช่น classes ในภาษา object-oriented Modules นี้ได้รับการออกแบบให้เป็นอิสระจากกันในระดับหนึ่ง เพื่อให้โปรแกรมเมอร์สามารถทำงานกับ module หนึ่งได้โดยไม่ต้องเข้าใจรายละเอียดของ modules อื่นๆ
เนื่องจากซอฟต์แวร์มีความหยุ่นตัวได้มากมาย การออกแบบซอฟต์แวร์เป็นกระบวนการต่อเนื่องที่ครอบคลุมวงจรชีวิตทั้งหมดของระบบซอฟต์แวร์ นี่ทำให้การออกแบบซอฟต์แวร์แตกต่างจากการออกแบบระบบทางกายภาพ เช่น อาคาร เรือ หรือสะพาน อย่างไรก็ตาม การออกแบบซอฟต์แวร์ไม่ได้ถูกมองในแบบนี้ตลอดเวลา สำหรับส่วนใหญ่ของประวัติศาสตร์การเขียนโปรแกรม การออกแบบเน้นที่จุดเริ่มต้นของโครงการ เช่นเดียวกับในสาขาวิศวกรรมอื่นๆ ส่วนตรงกลางของวิธีการนี้เรียกว่า waterfall model ซึ่งโครงการจะถูกแบ่งออกเป็นเฟสที่แตกต่างกัน เช่น definition ของ requirements, design, coding, testing และ maintenance ในแบบ waterfall model เฟสแต่ละเฟสจะเสร็จสิ้นก่อนที่เฟสถัดไปจะเริ่มต้น ในกรณีหลายๆ กรณี คนแตกต่างกันจะรับผิดชอบแต่ละเฟส ระบบทั้งหมดจะถูกออกแบบในครั้งเดียว ในช่วง design phase ดีไซน์จะถูก freeze ไว้ที่ท้ายของเฟสนี้ และบทบาทของเฟสต่อๆ ไปคือการนำดีไซน์นั้นมาใช้และนำมันมาอุดตัน
น่าเสียดายที่ waterfall model นั้นไม่ค่อยได้ผลดีสำหรับซอฟต์แวร์ ระบบซอฟต์แวร์มีความซับซ้อนมากกว่าระบบทางกายภาพ จึงไม่สามารถจินตนาการถึงการออกแบบสำหรับระบบซอฟต์แวร์ขนาดใหญ่ได้อย่างดีพอที่จะเข้าใจความหมายและผลกระทบทั้งหมดของมันก่อนสร้างสิ่งใดๆ เป็นผลให้ การออกแบบเริ่มต้นจะมีปัญหามากมาย ปัญหาต่างๆ เหล่านี้จะปรากฏออกมาจนกว่า implementation จะดำเนินการได้อย่างดี อย่างไรก็ตาม waterfall model ไม่ได้ถูกสร้างให้รองรับการเปลี่ยนแปลงการออกแบบที่สำคัญในจุดนี้ (ตัวอย่างเช่น designer อาจได้ย้ายไปยัง projects อื่นๆ แล้ว) เพราะฉะนั้น developers จึงพยายามปะเก็นปัญหาต่างๆ เหล่านั้นโดยไม่เปลี่ยนแปลงการออกแบบโดยรวม สิ่งนี้นำไปสู่การระเบิดของความซับซ้อน
เพราะ issues เหล่านี้ โปรเจ็กต์พัฒนาซอฟต์แวร์ส่วนใหญ่ในปัจจุบันใช้วิธีการเพิ่มเติมแบบค่อยๆ ไป เช่น agile development ซึ่งการออกแบบเริ่มต้นเน้นที่ชุดเล็กของฟังก์ชันโดยรวม ชุดย่อยนี้ได้รับการออกแบบ ใช้งาน และประเมินผล ปัญหาต่างๆ ของการออกแบบเริ่มต้นจะถูกค้นพบและแก้ไข จากนั้นฟีเจอร์เพิ่มเติมอีกไม่กี่อย่างจะได้รับการออกแบบ ใช้งาน และประเมินผล การวนซ้ำแต่ละครั้งจะสัมผัสปัญหาต่างๆ ของการออกแบบที่มีอยู่ ซึ่งจะถูกแก้ไขก่อนที่ชุด features ถัดไปจะได้ ออกแบบ โดยการกระจายการออกแบบในลักษณะนี้ ปัญหาต่างๆ ของการออกแบบเริ่มต้นสามารถถูกแก้ไขได้ในขณะที่ระบบยังคงเล็ก features ที่มาภายหลังจะได้รับประโยชน์จากประสบการณ์ที่ได้รับระหว่าง implementation ของ features ก่อนหน้า ดังนั้น พวกเขาจึงมีปัญหาน้อยลง
วิธีการเพิ่มเติมแบบค่อยๆ ไปได้ผลลัพธ์ที่ดีสำหรับซอฟต์แวร์เพราะว่า ซอฟต์แวร์มีความหยุ่นตัวได้เพียงพอที่จะอนุญาตให้มีการเปลี่ยนแปลงการออกแบบที่สำคัญในระหว่าง implementation ตรงกันข้าม การเปลี่ยนแปลงการออกแบบที่สำคัญนั้นยากมากขึ้นสำหรับระบบทางกายภาพ ตัวอย่างเช่น มันจะไม่สะดวกที่จะเปลี่ยนแปลงจำนวนของ towers ที่ support สะพานในตรงกลาง construction
Incremental development หมายความว่า การออกแบบซอฟต์แวร์ไม่เคยสิ้นสุด การออกแบบเกิดขึ้นต่อเนื่องตลอดอายุการใช้งานของระบบ developers ควรคิดเรื่องการออกแบบอยู่ตลอดเวลา Incremental development ยังหมายความถึง continuous redesign การออกแบบเริ่มต้นสำหรับระบบหรือ component นั้นแทบจะไม่ใช่อันที่ดีที่สุดเลย ประสบการณ์จะแสดงให้เห็นอย่างหลีกเลี่ยงไม่ได้ว่ามีวิธีการที่ดีกว่าในการทำสิ่งต่างๆ ในฐานะ software developer คุณควรจะค้นหาโอกาสต่างๆ ในการปรับปรุงการออกแบบของระบบที่คุณกำลังทำงานด้วยอยู่ตลอดเวลา และคุณควรจะวางแผนในการใช้เวลาบางส่วนของคุณในการปรับปรุงการออกแบบ
หากว่า software developers ควรคิดเกี่ยวกับปัญหาการออกแบบตลอดเวลา และการลดความซับซ้อนเป็นองค์ประกอบที่สำคัญที่สุดของการออกแบบซอฟต์แวร์ แล้ว software developers ก็ควรคิดเกี่ยวกับความซับซ้อนตลอดเวลา หนังสือเล่มนี้เป็นเกี่ยวกับวิธีการใช้ความซับซ้อนเพื่อกำหนดทิศทางของการออกแบบซอฟต์แวร์ตลอดอายุการใช้งานของมัน
หนังสือเล่มนี้มีเป้าหมายโดยรวมสองประการ เป้าหมายแรกคือการอธิบายลักษณะของความซับซ้อนของซอฟต์แวร์ "complexity" หมายความว่าอะไร ทำไมมันจึงมีความสำคัญ และคุณสามารถรับรู้ได้อย่างไรเมื่อโปรแกรมหนึ่งมีความซับซ้อนที่ไม่จำเป็น เป้าหมายที่สอง และยากกว่า ของหนังสือคือการนำเสนอเทคนิคต่างๆ ที่คุณสามารถใช้ได้ในระหว่างกระบวนการพัฒนาซอฟต์แวร์เพื่อลดความซับซ้อน น่าเสียดายที่ไม่มี simple recipe ที่จะรับประกันการออกแบบซอฟต์แวร์ที่ยอดเยี่ยม แทนที่จะเป็นเช่นนั้น ผมจะนำเสนอ collection ของ concepts ที่สูงกว่าซึ่งอยู่ชายแดนของ philosophical เช่น "classes should be deep" หรือ "define errors out of existence" Concepts เหล่านี้อาจจะไม่สามารถระบุการออกแบบที่ดีที่สุดได้ในทันที แต่คุณสามารถใช้พวกเขาเพื่อเปรียบเทียบ design alternatives และกำหนดทิศทางการสำรวจของคุณในการออกแบบ
1.1 How to use this book (วิธีการใช้หนังสือเล่มนี้)
แนวคิดการออกแบบจำนวนมากที่อธิบายไว้ที่นี่นั้นค่อนข้างนามธรรม ดังนั้นมันอาจจะเป็นเรื่องยากที่จะชื่นชมมันโดยไม่ได้ดูโค้ดจริงๆ มันเป็นความท้าทายในการค้นหาตัวอย่างที่มีขนาดเล็กพอที่จะรวมอยู่ในหนังสือ แต่ใหญ่พอที่จะแสดงให้เห็น ปัญหาต่างๆ ของระบบจริงๆ (หากคุณพบตัวอย่างที่ดี โปรดส่งมันให้ผม) เพราะฉะนั้น หนังสือเล่มนี้อาจจะไม่เพียงพอสำหรับคุณที่จะเรียนรู้วิธีการใช้ principles ต่างๆ
วิธีที่ดีที่สุดในการใช้หนังสือเล่มนี้คือการใช้ร่วมกับ code reviews หากเมื่อคุณอ่านโค้ดของคนอื่นๆ คิดว่าข้อมูลนั้นสอดคล้องกับ concepts ที่อธิบายไว้ที่นี่หรือไม่ และสิ่งนั้นเกี่ยวข้องกับความซับซ้อนของโค้ดอย่างไร เป็นเรื่องง่ายกว่ามากที่จะเห็นปัญหาการออกแบบในโค้ดของคนอื่นๆ มากกว่าโค้ดของคุณเอง คุณสามารถใช้ red flags ที่อธิบายไว้ที่นี่เพื่อระบุปัญหาต่างๆ และแนะนำการปรับปรุง การ review โค้ดจะนำคุณไปเจอ design approaches และ programming techniques ใหม่ๆ ด้วย
วิธีหนึ่งในวิธีที่ดีที่สุดในการปรับปรุง design skills ของคุณคือการเรียนรู้ที่จะรับรู้ red flags สัญญาณต่างๆ ที่แสดงว่า piece of code นั้นน่าจะซับซ้อนมากกว่าที่จำเป็น ในระหว่างบทหลายๆ บทของหนังสือ ผมจะชี้ให้เห็น red flags ที่เสนอปัญหาต่างๆ ที่เกี่ยวข้องกับ design issue หลัก สำคัญที่สุดนั้นอยู่ใน summary ในตอนท้ายของหนังสือ คุณสามารถใช้มันเหล่านี้เมื่อคุณกำลังเขียนโค้ด เมื่อคุณเห็น red flag ให้หยุดและค้นหา alternate design ที่กำจัดปัญหา เมื่อคุณลองใช้วิธีการนี้เป็นครั้งแรก คุณอาจจะต้องลองใช้ design alternatives หลายๆ อันก่อนที่คุณจะพบอันที่กำจัดปัญหา อย่าถูกท้อใจ ยิ่งคุณลองใช้ alternatives มากขึ้นก่อนแก้ไขปัญหา คุณก็จะเรียนรู้ได้มากขึ้น เมื่อเวลาผ่านไป คุณจะพบว่าโค้ดของคุณมี red flags น้อยลงเรื่อยๆ และ designs ของคุณก็สะอาดขึ้นเรื่อยๆ ประสบการณ์ของคุณจะแสดงให้เห็นด้วยว่ามี red flags อื่นๆ ที่คุณสามารถใช้เพื่อระบุปัญหาการออกแบบ (ผมจะดีใจที่ได้ยินเกี่ยวกับสิ่งเหล่านี้)
เมื่อนำแนวคิดจากหนังสือเล่มนี้มาใช้ มันเป็นเรื่องสำคัญที่จะใช้ความพิจารณาพอประมาณและเหตุผล ทุก rule ก็มี exceptions และ principle ทุกประการก็มีขีดจำกัด หากคุณนำ design idea ใดๆ ไปถึงระดับสูงสุด คุณจะเสียไปในสถานที่ที่เลวร้าย ดีไซน์ที่สวยงามนั้นสะท้อนความสมดุลระหว่าง competing ideas และ approaches บทหลายๆ บทมี sections ที่เรียกว่า "Taking it too far" ซึ่งอธิบายวิธีการรับรู้เมื่อคุณกำลังทำสิ่งที่ดี
ตัวอย่างเกือบทั้งหมดในหนังสือเล่มนี้อยู่ใน Java หรือ C++ และการสนทนาส่วนใหญ่อยู่ในแง่ของการออกแบบ classes ในภาษา object-oriented อย่างไรก็ตาม แนวคิดต่างๆ เหล่านี้ใช้ได้กับ domains อื่นๆ ด้วย เกือบทั้งหมดของแนวคิดที่เกี่ยวข้องกับ methods ยังสามารถนำไปใช้ได้กับ functions ในภาษาที่ไม่มี object-oriented features เช่น C แนวคิดการออกแบบก็ใช้ได้กับ modules อื่นๆ นอกจาก classes ด้วย เช่น subsystems หรือ network services
ด้วยพื้นฐานนี้ มาว่าเราจะอภิปรายในรายละเอียดเพิ่มเติมในเรื่องที่ทำให้เกิดความซับซ้อน และวิธีการทำให้ระบบซอฟต์แวร์ง่ายขึ้น