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

สมมติว่าคุณกำลังออกแบบ class ที่ใช้จัดการข้อความในไฟล์สำหรับ GUI text editor ขั้นตอนแรกคือการกำหนด interface ที่ class นี้จะ expose ให้กับส่วนอื่นๆ ของ editor แทนที่จะเลือกไอเดียแรกที่ผุดขึ้นมาในหัว ให้ลองพิจารณาหลายๆ ทางเลือกดู ทางเลือกแรกอาจเป็น line-oriented interface ที่มี operation สำหรับ insert, modify, และ delete ข้อความทั้งบรรทัด อีกทางเลือกคือ interface แบบ character-oriented ที่ insert และ delete ทีละตัวอักษร ทางเลือกที่สามคือ string-oriented interface ที่ทำงานกับช่วงของอักขระตามต้องการ ซึ่งอาจข้ามระหว่างบรรทัดก็ได้ คุณไม่จำเป็นต้องลงรายละเอียดทุกฟีเจอร์ของแต่ละทางเลือก แค่ sketch method ที่สำคัญๆ สักสองสามอันก็เพียงพอในตอนนี้

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

หลังจากที่คุณร่างแบบคร่าวๆ ของแต่ละทางเลือกแล้ว ให้ทำรายการข้อดีและข้อเสียของแต่ละแบบ สิ่งสำคัญที่สุดสำหรับ interface คือความสะดวกในการใช้งานสำหรับซอฟต์แวร์ระดับสูงกว่า ในตัวอย่างข้างต้น ทั้ง line-oriented interface และ character-oriented interface จะต้องทำงานเพิ่มเติมในซอฟต์แวร์ที่ใช้งาน text class line-oriented interface จะทำให้ซอฟต์แวร์ระดับสูงกว่าต้อง split และ join บรรทัดระหว่างการทำงานแบบ partial-line และ multi-line เช่น การตัดและวาง selection ส่วน character-oriented interface จะต้องใช้ loop เพื่อ implement operation ที่แก้ไขมากกว่าหนึ่งตัวอักษร นอกจากนี้ยังมีปัจจัยอื่นๆ ที่ควรพิจารณา:

  • ทางเลือกไหนมี interface ที่เรียบง่ายกว่ากัน? ในตัวอย่าง text class นั้น interface ทั้งหมดค่อนข้างเรียบง่าย
  • interface ไหนมีความเป็น general-purpose มากกว่ากัน?
  • interface ไหนทำให้ implementation มีประสิทธิภาพมากกว่ากัน? ในตัวอย่าง text class นั้นแนวทางแบบ character-oriented มีแนวโน้มที่จะช้ากว่าแบบอื่นอย่างมาก เพราะต้องเรียก text module ทีละตัวอักษร

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

บางครั้งไม่มีทางเลือกไหนน่าสนใจเลย เมื่อเกิดกรณีแบบนี้ ให้ลองดูว่าคุณจะคิดแผนอื่นๆ เพิ่มเติมได้ไหม ใช้ปัญหาที่คุณพบจากทางเลือกเดิมเป็นตัวผลักดันการออกแบบใหม่ ถ้าคุณกำลังออกแบบ text class และพิจารณาแค่แนวทางแบบ line-oriented กับ character-oriented คุณอาจสังเกตว่าแต่ละทางเลือกก็มีข้อเสีย เพราะมันบังคับให้ซอฟต์แวร์ระดับสูงกว่าต้องจัดการข้อความเพิ่มเติม นั่นคือ red flag: ถ้ามี text class แล้ว มันควรจะจัดการเรื่องการจัดการข้อความทั้งหมดเอง เพื่อกำจัดงานจัดการข้อความที่เพิ่มขึ้นมา text interface จะต้อง match กับการทำงานที่เกิดขึ้นในซอฟต์แวร์ระดับสูงกว่าให้ใกล้เคียงมากขึ้น ซึ่งการทำงานเหล่านั้นไม่ได้ตรงกับ single characters หรือ single lines เสมอไป แนวคิดนี้จะนำคุณไปสู่ range-oriented API สำหรับ text ที่ช่วยขจัดปัญหาจากการออกแบบก่อนหน้านี้

หลักการออกแบบสองครั้งสามารถนำไปใช้ได้ในหลายระดับของระบบ สำหรับโมดูล คุณสามารถใช้แนวทางนี้ในการเลือก interface ก่อน ดังที่อธิบายไว้ข้างต้น จากนั้นก็ใช้มันอีกครั้งเมื่อคุณออกแบบ implementation: สำหรับ text class คุณอาจพิจารณา implementation ต่างๆ เช่น linked list ของบรรทัด, fixed-size blocks ของตัวอักษร, หรือ "gap buffer" เป้าหมายสำหรับ implementation จะแตกต่างจาก interface: สำหรับ implementation สิ่งที่สำคัญที่สุดคือความเรียบง่ายและประสิทธิภาพ นอกจากนี้ยังมีประโยชน์ในการสำรวจหลายๆ การออกแบบในระดับที่สูงขึ้นของระบบ เช่น การเลือกฟีเจอร์สำหรับ user interface หรือการแบ่งระบบออกเป็นโมดูลหลักๆ ในแต่ละกรณี การระบุแนวทางที่ดีที่สุดจะง่ายกว่าถ้าคุณสามารถเปรียบเทียบทางเลือกสองสามแบบ

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

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

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

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