Software Design

🧓 Uncle Bob - Clean Code

🤔 Clean Code ในมุมของปรมาจารย์เขาคิดยังไงบ้างน๊า

บทความนี้เป็นการสรุปเนื้อหาจากวีดีโอของลุงบ๊อบ หรือ Robert C. Martin มหาเทพในวงการคอมพิวเตอร์ ที่ลุงแกมาพูดถึงการทำ Clean Code หรือแปลเป็นไทยแบบกำปั้นทุบดินว่า การทำโค้ดให้สะอาด กัน เพื่อให้เห็นถึงความสำคัญและประโยชน์ของการทำ Clean Code นั่นเองครัช โดยเนื้อหาถูกแบ่งออกเป็น 6 วีดีโอ ดังนั้นแมวน้ำก็เขียนแตกออกมาเป็น 6 บทความเช่นกันนะขอรับ

แนะนำให้อ่าน บทความนี้เป็นส่วนหนึ่งของบทคอร์ส 👶 Clean Code หากเพื่อนๆแมวน้ำสนใจศึกษาเรียนรู้ว่าการทำ Clean Code ว่ามันคืออะไร? มีอะไรบ้าง? บลาๆ ก็สามารถกดที่ชื่อบทความสีฟ้าๆเข้าไปอ่านได้เลยครัช

🤔 โค้ดดีไม่ดีดูไง ?

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

โค้ดที่เขียนวันนั้น มีแต่ผมกับพระเจ้าเท่านั้นที่เข้าใจ 😎 ... แต่ตอนนี้ เหลือแค่พระองค์เท่านั้นละ 😐

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

🤓 โลกเราอยู่ได้เพราะซอฟต์แวร์

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

การเขียนโค้ดโง่ๆเพียงบรรทัดเดียว อาจทำให้ธุรกิจใหญ่ๆพังได้ในข้ามคืน

คนเขียนซอฟต์แวร์คือคนที่ถูกเรียกว่าเป็น มืออาชีพด้านซอฟต์แวร์ ดังนั้นคนที่จะเป็น Software Professional ตัวจริงจะต้องไม่พลาดเรื่องเหล่านี้ แล้วเราจะทำมันได้ยังไง? ลุงบ๊อบแกเลยตอบว่า

🧓 เราต้องมีมาตรฐานในการทำงานไง และหนึ่งในมาตรฐานที่ว่าคือ การทำ Clean Code งุยจ๊ะ

🤔 Clean Code คือไย ?

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

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

🤨 Clean Code ทำไงบ้าง ?

💖 Intent

โค้ดที่เขียนจะต้องแสดงเจตนาของมันชัดเจน ว่ามันถูกสร้างขึ้นมาทำไม? เช่น ลองอ่านโค้ดแบบแย่ๆด้านล่างดู แล้วลองเดาดิ๊ว่า จริงๆมันต้องการทำอะไร?

https://www.youtube.com/watch?v=7EmboKQH8lM&t=2409s

จะเห็นว่าโค้ดที่แย่ๆ มันจะทำให้เราเสียเวลาอ่านเยอะม๊วก แทบจะต้องไล่ตามอ่านทีละบรรทัด เพื่อแกะว่าไอ้คนเขียนมันพยายามจะทำอะไร และบางทีก็อ่านทั้งหมดก็ยังต้องกลับมาอ่านซ้ำเพราะไม่เข้าใจ ... แล้วลองเทียบกับโค้ดที่ถูก Clean แล้วดูบ้างดิ๊

https://www.youtube.com/watch?v=7EmboKQH8lM&t=2965s

จะเห็นว่าโค้ดที่ถูก Clean แล้ว ต่อให้เราไม่รู้เรื่องมาก่อนเลยว่ามันคืออะไร แต่พออ่านเราก็จะรู้ว่า method นี้จะส่ง html กลับไป และจะเติมข้อมูลอะไรซักอย่างให้ถ้าเป็นหน้าสำหรับ Test หรือจริงๆ แค่อ่านชื่อ method เราก็จะรู้เจตนาของมัน ว่ามันเป็นการสร้างหน้าเพจสำหรับเอาไปแสดงผลซักอย่างนั่นเอง

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

จากที่ว่ามาเลยมีข้อแนะนำการเขียน method แบบง่ายๆคือ

โค้ดทั้งหมดในเมธอดควรแสดงให้เห็นภายใน 1 หน้าจอได้ทั้งหมด เกินกว่านั้นถือว่าแย่

💡 Extract method

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

🧓 ให้เราทำการแตกเป็นเมธอดย่อยๆ (Extract method) ไปเรื่อยๆ แล้วทำการจัดกลุ่มมันดีๆ เผลอๆเราก็จะได้ class ใหม่ที่เป็นตัวระบบจริงๆโดยไม่รู้ตัวก็ได้ โดยหัวใจหลักของการแตกเมธอดย่อยคือการทำ (SRP) Single Responsibility Principle นั่นเอง

สีเหลืองคือ method ที่เราทำการ extract แล้ว จัดกลุ่มจนได้คลาสใหม่

แนะนำให้อ่าน สำหรับคนที่สนใจอยากรู้จักว่า (SRP) Single Responsibility Principle คืออะไรก็ให้กดที่ชื่อมันได้เลย ซึ่งมันเป็นหนึ่งในหลักการออกแบบขั้นพื้นฐานที่โปรแกรมเมอร์ควรจะต้องรู้ แต่ถ้าใครสนใจอยากดูหลักการออกแบบพื้นฐานทั้งหมดก็สามารถไปอ่านได้จากลิงค์ตัวนี้ครัช 👦 SOLID Design Principles

🏭 Method

🍰 Arguments

เมธอดที่ดีที่สุดคือไม่ต้องมี argument เลย เพราะคนใช้มีหน้าที่แค่เรียกใช้ก็พอ แต่ถ้าจะต้องมี ไม่ควรมีเกิน 1~3 ตัว โดยลุงบ๊อบแกให้เหตุผลว่า

🧓 ของที่ส่งไปให้กับเมธอด มันคือของที่มีความเกี่ยวข้องกัน ดังนั้นถ้าเรามีสิ่งที่เกี่ยวข้องกันเกิน 3 อย่างนั่นหมายความว่า มันควรจะอยู่ในรูปแบบของคลาสแล้ว ดังนั้นให้ส่งมาทั้งคลาสเลย (แมวน้ำเสริมให้ว่าทำเป็น model แล้วส่งไปกะดั๊ย)

🚩 Flag argument

เวลาออกแบบเมธอดนั้น ไม่ควรจะต้องรับ Flag argument เข้ามา หรือพูดให้เข้าใจง่ายๆคือ อย่าให้เมธอดต้องรับ Boolean เข้ามานั่นเอง โดยลุงบ๊อบให้เหตุผลว่า

🧓 ถ้าเราส่ง boolean เข้าไปใน method นั่นหมายความว่าเราจะต้องมี if อยู่ข้างใน ดังนั้นทำไมไม่แยกเป็นเมธอด 2 ตัวที่ทำงานแยกกันเลยล่ะ? (ไม่ผิดกฎ SRP ด้วย) หรือลองจิตนาการดูเวลาเรียกใช้เมธอดแล้วเจอแบบนี้ SimpleMethod(5,3,7, true) คำถามคือ true คืออะไร? คนอ่านก็จะ งง และต้องเสียเวลาไปไล่ดูการทำงานข้างในเมธอดนั้นๆอีกที ถึงจะรู้ว่าเมื่อไหร่ควรส่ง true เมื่อไหร่จะส่ง false

👻 Output argument

เวลาออกแบบเมธอดนั้นไม่ควรมีการรับ argument ที่เอาไว้สำหรับ return ค่ากลับนอกเหนือจาก return type ของเมธอดเอง โดยลุงบ๊อบให้เหตุผลว่า

🧓 สิ่งที่ส่งเข้าไปเป็น argument ของเมธอดมันจะเป็นสิ่งที่สอดคล้องกันทั้งหมดอยู่แล้ว แต่ output argument จะเป็นสิ่งเดียวที่ไม่สอดคล้องกัน ดังนั้นเมื่อเราอ่านโค้ดเราจะ งง เพราะเราจะพบของที่มันต่างจากพวก แล้วเราต้องเสียเวลาไปไล่ตามหาว่าเจ้าตัวนี้คืออะไรอีกด้วย เช่น Print(line1, line2, line3, banaSakabra) แค่อ่านปุ๊ปเราก็จะเอ๋อแดร๊กว่า banaSakabra คือไรฟระ? เราจะเป็นต้องไปสนใจมันป่ะ? โค้ดมันถูกหรือยังฟระ? หรือต่อให้เราไม่สนใจแต่มันก็จะคาใจอยู่ดี ทำให้เราไม่สามารถ focus ต่อได้นั่นเอง

🔀 Switch case

ไม่ควรใช้ switch case keyword โดยลุงบ๊อบให้เหตุผลว่า

🧓 สาเหตุหลักๆคือ

  1. Switch statements มันรับผิดชอบมากกว่า 1 อย่าง (ละเมิดกฎ SRP) ดังนั้นของหลายๆอย่างจะไปผูกติดกับมัน

  2. ปรกติ switch statements ถ้าใช้กับ object เรานิยมใช้กับของที่เป็น hierarchy ดังนั้นเวลาที่ต้องแก้ hierarchy เหล่านั้นมันก็จะต้องไปไล่แก้แต่ละ case ที่อยู่ใน switch เหล่านั้น

  3. ในบาง case มันอาจจะไปเรียกใช้งาน module อื่น ทำให้มันเกิด Dependency กับไฟล์ที่ใช้ switch ได้ง่ายมาก ทำให้พวก .jar, .dll ของเราขาดความสามารถในการ delopy ได้แบบอิสระ เพราะทุกครั้งที่ dependency ที่เราเรียกใช้มีการอัพเดทเกิดขึ้น เราจะต้อง build ใหม่ deploy ใหม่ทุกครั้งนั่นเอง

🧓 จากเหตุผลที่ให้ไปทั้งหมด สามารถแก้ได้โดยการทำ Polymorphysm อยู่แล้ว ดังนั้นเราก็สามารถแยกแต่ละ case ให้ไปอยู่ในคลาสลูกที่เหมาะสมได้ ดังนั้นเวลาที่พฤติกรรมเปลี่ยน เราก็แค่แก้ไขคลาสลูกที่ดูแลพฤติกรรมตัวนั้นก็พอ หรืออย่างมากก็เพิ่มคลาสลูกตัวใหม่เท่านั้น ซึ่งไม่ทำให้โค้ดเดิมถูกแก้เลย ซึ่งเข้ากับหลัก (OCP) Open/Closed Principle อีกด้วย

แนะนำให้อ่าน (OCP) Open/Closed Principle ก็เป็นหนึ่งในหลักการออกแบบพื้นฐานที่เหล่าโปรแกรมเมอร์ควรจะต้องรู้ ถ้าสนใจก็กดที่ชื่อมันไปอ่านได้เบยขอรับ

🤕 Side effects

💣 Output argument

สิ่งที่เป็น output จาก method ไม่ควรมีผลข้างเคียง (side effect) เช่น ผลลัพท์ที่ได้จากการอ่านไฟล์ ควรจะต้องนำมาใช้งานได้เลย ไม่ใช่จะใช้งานได้เฉพาะภายในช่วงที่ยังเปิด Stream อยู่เท่านั้น โดยลุงบ๊อบให้เหตุผลว่า

🧓 เวลาที่เราเรียกเมธอดเพื่อเอาผลลัพท์มันออกมาใช้ เราอยากจะเอาไปใช้เมื่อไหร่ก็ได้ ไม่จำเป็นต้องเป็นเดี๋ยวนั้น และตอนจะใช้มันควรจะต้องใช้ได้เสมอ ไม่ใช่เรียกได้บ้างไม่ได้บ้าง เพราะไม่อย่างนั้น developer จะ งง ว่าเกิดอะไรขึ้น และหลายๆคนน่าจะเคยเจอว่า เราต้องเสียเวลาตั้งหลายวันเพื่อแก้ bug แค่สลับบรรทัดกัน 2 บรรทัด ก็ทำงานได้โดยไม่รู้เหตุผลชิมิ นั่นแหละปัญหาจาก output argument

ดังนั้นวิธีการแก้ปัญหา output argument ที่มี side effect นั่นคือการ จัดการ side effect ให้หมดภายในเมธอดนั้นๆก่อน ค่อยส่งออกมันเป็น output ให้คนเรียกใช้สามารถใช้งานได้อย่างสบายใจ

⚔️ Command & Query separation

โดยปรกติการทำงานต่างๆเราสามารถแยกมันออกเป็น 2 เรื่องคือ

  1. Command - เป็นการสั่งให้มันทำงานอะไรซักอย่าง แล้วมันทำให้ state ของระบบเปลี่ยน

  2. Query - เป็นการสั่งให้มันไปเอาอะไรซักอย่างกลับมาให้ และ ต้องไม่เปลี่ยน state ของระบบเลย

ดังนั้นทุกครั้งที่เราเขียนเมธอดเราก็ควรที่จะยึดตามหลัก Command & Query นี้ด้วยคือ

  • เมธอดที่ไม่มี return type (void method) - ตรงกับ Command ซึ่งเมื่อเราเรียกใช้ เราจะคาดหวังว่ามันต้องไปทำอะไรซักอย่างกับระบบ ซึ่งจะทำให้ state ของระบบเปลี่ยนแน่ๆ เพราะเราสั่งให้มันทำงาน แต่ไม่ต้องการอะไรกลับมา

  • เมธอดที่มี return type - ตรงกับ Query เพราะมันไปเอาอะไรซักอย่างกลับมาให้เรา ดังนั้นเมื่อเราเรียกใช้งาน เราจะคาดหวังว่า มันต้องไม่เกิดอะไรขึ้นกับระบบเรา เพราะมันแค่ไปเอาของกลับมาให้เราดูเท่านั้น

โดยลุงบ๊อบแกให้เหตุผลว่า

🧓 Command & Query เป็นสิ่งที่ใช้กันบ่อยและรู้จักกันกว้างขวางมาก ดังนั้นถ้าทำให้ของที่เป็นลักษณะเดียวกันให้เหมือนกัน จะทำให้ developer จำง่ายขึ้น ทำงานง่ายขึ้น และคาดหวังได้ว่าเมื่อไหร่จะเกิด state change และเมื่อไหร่ไม่เกิดนั่นเอง

🚨 Exception over Error Code

เวลาที่เกิดข้อผิดพลาดขึ้นให้ส่ง exception ไปเลย อย่าไปส่งเป็น Error Code โดยลุงบ๊อบให้เหตุผลว่า

🧓 ถ้าเราไม่ throw exception ไปเลย นั่นหมายความว่า เราต้องเขียนโค้ดเพิ่มขึ้นเพื่อระบุ Error Code ที่มันจะเกิดขึ้น ดังนั้นงานจะงอกเยอะขึ้น และเราต้องไปคอยจัดการกับ Error Code แต่ละแบบว่ามันเกิดแบบนี้ขึ้นเพราะอะไรได้ยากขึ้นอีกด้วย แทนที่จะเห็นเลยว่าจะเกิดเรื่องอะไร บรรทัดไหน

ตัวอย่างการส่ง Error Code จะเห็นว่าเราต้องมาคอยตั้งเงื่อนไขต่างๆอีกว่าเมื่อไหร่ จะส่ง Error code อะไรกลับไป

📝 DRY

(DRY) Don't Repeat Yourself หรือพูดง่ายๆคือ อย่าไปทำของเดิมๆซ้ำๆ โดยปรกติเรามักจะพูดถึงการเขียนโค้ดแบบเดียวกันเด๊ะๆ หลายๆจุด ซึ่งโดยปรกติ developer มักจะ copy โค้ดส่วนนั้นไปวางในที่ต่างๆที่มันมีการทำงานเหมือนๆกันนั่นเอง โดยลุงบ๊อบให้เหตุผลว่า

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

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

🙌 ส่งท้ายบท

🧓 เราไม่มีวิธีพิสูจน์ว่าซอฟต์แวร์มันทำงานถูกต้องหรือเปล่า แต่เราสามารถทดสอบมันได้ ดังนั้นเราทำได้แค่ทดสอบมัน จนเราหาข้อผิดพลาดไม่เจอเท่านั้น แต่ก็ไม่ได้หมายความว่ามันทำงานถูก 100% แค่เรายังไม่เจอข้อผิดพลาดตัวอื่นๆเท่านั้นเอง

We can't proof our software correct, We demonstrate that it not incorrect by surrounding it with tests.

ของบางอย่างแมวน้ำก็ไม่ได้ลงรายละเอียดให้นะ เพราะมันถูกเขียนไว้ใน 👶 Clean Code ตัวหลักอยู่แล้ว ดังนั้นถ้าสนใจก็ไปกดอ่านเอาเองเด้อ

บทความนี้ยังไม่จบอย่างที่บอกว่ามันจะแบ่งเป็น 6 ส่วน นี่เป็นแค่ตอนที่ 1 เท่านั้นเอง ดังนั้นตามอ่านกันยาวๆต่อได้จากบทความหลัก หรือไม่อยากพลาดก็ติดตามได้จาก Facebook: Mr.Saladpuk ฮั๊ฟ

แนะนำให้อ่าน ในบทความถัดๆไปเราจะเจอหลักในการออกแบบเยอะม๊วก ดังนั้นแมวน้ำแนะนำให้รู้จักหลักในการออกแบบพื้นฐาน 5 ตัว โดยเพื่อนๆสามารถไปอ่านได้จากลิงค์นี้ครัช 👦 SOLID Design Principles

🎥 วีดีโอลุงบ๊อบ