เขียน Code ให้ Clean ขึ้น ด้วยเทคนิค Clean Code จากลุง Robert C. Martin

Wattanachai Prakobdee
LINE Developers Thailand
6 min readJul 10, 2018

Programmers หลายคนคงเคยได้ยินคำพูด code ไม่ดีแล้วยังไง? ถ้ามันยัง works ทำไมต้องไปแก้ไขมันด้วย ? หรืออาจจะเคยได้ยินคำถาม เช่น เอาไว้แก้ไขมันในภายหลังได้ไหม ตอนนี้เรารีบทำตาม business? ซึ่ง “ในภายหลัง” นั้น ในทางปฏิบัติแล้วมักจะหมายถึง “ไม่มีวันได้ทำ”

โดยปกติแล้วคำโต้แย้งตอนเราขอเวลาเพื่อทำ refactoring code คือ ทำไมเราต้องใช้เวลามากขึ้น ใช้ cost มากขึ้น แถมการ delivery งานยังล้าช้าออกไปอีก ซึ่งทางฝั่ง programmers เราเองรู้ดีอยู่แล้วว่า การละเลย refactoring มันมีหนี้เกิดขึ้นเสมอ ซึ่งหนี้ที่เกิดขึ้นนี้เราเรียกกันว่า Technical Debt เราตระหนักอยู่แล้วว่า หนี้ที่เกิดขึ้นนี้จะต้องจ่ายคืนแน่นอนไม่ช้าก็เร็ว ผลที่ตามมาของ Technical Debt ก็มีความหลากหลาย เช่น new feature ออกได้ช้าลง มี costs การเรียนรู้ที่เยอะขึ้น เวลามีคนใหม่ๆเข้ามาร่วมในทีมหรือใน project แถมการ estimate งานทำได้ยากและไม่แม่นยำ หรืออาจเกิดปัญหาอื่นๆที่ส่งผลร้ายแรงมากกว่านั้น อาจส่งผลเสียได้ถึงระดับองค์กรเลยก็เป็นไปได้ แต่มันไม่จำเป็นต้องเป็นอย่างนั้นเสมอไป

ในการป้องการ technical debt และการเสียเวลาในการ refactoring code นั้นมีแนวทางที่ประกอบไปด้วยหลากหลายส่วน เช่น

  • การทำ code style ให้สอดคล้องกัน
  • การบัญญัติ best practices
  • การคิดถึง security อยู่เสมอ
  • เขียน code ที่ testable
  • เขียน code ให้ readable
  • เขียน code ให้ clean

เมื่อเรารู้ความร้ายแรงของ Technical Debt และวิธีที่ช่วยป้องกันมันแล้ว บทความนี้ เราจะมาเรียนรู้ practice ในการเขียน code ให้ clean ซึ่งเป็น Technics ในรูปแบบสั้นๆจากบางส่วนของหนังสือ Clean Code: A Handbook of Agile Software Craftsmanship โดย Robert C. Martin หรือที่เราเรียกกันสั้นๆว่าลุง BOB

อะไรคือ Bad code ? อาจจะฟังดู over มากเกินไป bad code สามารถทำให้บริษัทพินาศได้ ? การเขียน bad code อาจมาจากหลากหลายเหตุผล แต่อย่าเชื่อว่าเราจะกลับมาแก้ไขทีหลังหรือจะกลับมาทำให้มันดีขึ้นในภายหลัง หลายครั้งที่ programmers เราเคยอ้างเหตุผล โดยบอกทำๆไปไปก่อน มีเวลาค่อยกลับมาแก้ให้มันดีขึ้นทีหลัง แต่ว่าเราก็ไม่เคยทำมัน อย่างที่หลายคนคงเคยได้ยินประโยค classic นี้

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

แล้วอะไรคือสาเหตุที่ทำให้ good code ไปเป็น bad code ได้ มีคำอธิบายและข้อแก้ตัวมากมาย เช่น การเปลี่ยนแปลงของ requirements บ้าง เวลาในการทำน้อยเกินไปบ้าง หรือสาเหตุอื่นๆ อีกมากมาย แต่ไม่ว่าจะสาเหตุอะไรก็ตาม programmer เราจำเป็นต้องหยุดกล่าวโทษทุกๆคนและทุกๆอย่าง ยกเว้นให้โทษ programmer เอง

programmer รู้สึกกดดันเมื่อต้องพบกับ deadlines แต่ programmer ที่มีความเป็นมืออาชีพจะรู้ได้ว่า นี้คือความไม่ถูกต้อง วิธีเดียวที่จะทำได้ตาม deadline และดำเนินการต่อไปในภายภาคหน้าได้อย่างรวดเร็ว คือ การคิดและเขียน code ให้ clean มากสุดเท่าที่จะเป็นไปได้ตลอดเวลา แต่คำถามคือ เราจะเขียน code ให้ clean ได้อย่างไร?

การเขียน clean code ก็เหมือนกับการวาดภาพ ซึ่งคำตอบของคำถามที่ว่า เราจะเขียน clean code ได้อย่างไรค่อนข้างคลุมเครือ เราทุกคนเหมือนจะรู้ว่าภาพที่เราวาดนั้นมันดีหรือมันห่วยขนาดไหน แต่การแยกหรือรู้ว่าภาพที่ดีกับภาพที่ไม่ดีได้นั้น ไม่ได้หมายความว่าเรารู้ว่าจะวาดมันให้เป็นภาพที่ดีได้อย่างไร การเขียน code ก็เหมือนกับการวาดภาพ การที่เรารู้จักอันไหน clean code อันไหน bad code ไม่ได้หมายความว่าเราจะรู้ว่าวิธีในการเขียน code ให้ clean ได้

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

Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin เป็นหนังสือที่สอนเกี่ยวกับ best practices ต่างๆในการ code ที่ดี ที่จะทำให้เรารู้สึกอับอายกับ coding skills ของเรา วันนี้เราจะมารู้จัก practices ต่างๆที่จะช่วยให้เราเขียน code ให้ง่ายต่อการทำความเข้าใจและง่ายในการแก้ไข

ในส่วนนี้จะพูดถึงกฏง่ายๆที่จะช่วยให้เราตั้งชื่อที่ดีเวลาที่เขียน code

  • Use Intention-Revealing Names

ชื่อของ variable, function, หรือ class ควรจะบ่งบอกว่า ทำไมมันถึงมีอยู่ บอกสิ่งที่มันทำและวิธีการใช้งาน โดยลุง BOB เขายังบอกด้วยว่า ถ้าชื่อ variable, function, หรือ class ใดๆที่ต้องการ comment แล้ว นั้นคือชื่อที่ไม่ดี เพราะมันไม่ได้บ่งบอกถึงเจตนาของสิ่งที่มันทำ

  • Avoid Disinformation

Programmers ต้องหลีกเลี่ยงการใช้ชื่อที่คลุมเครือใน code และหลีกเลี่ยงการใช้ชื่อที่ควรหลีกเลี่ยง เช่น ชื่อ hp, aix, และ sco เนื่องจากเป็นชื่อของระบบปฏิบัติการ Unix หรืออีกตัวอย่างหนึ่งที่ควรเลี่ยง คือ ตั้งชื่อ กลุ่มของ accounts ว่า accountList ซึ่งจริงๆแล้วไม่ควรทำ เว้นแต่ว่ามันจะเป็น List จริงๆ

  • Make Meaningful Distinctions

สร้างความแตกต่างอย่างมีความหมาย! programmers ต้องสามารถตั้งชื่อ code ให้เป็นเอกเทศ เพื่อให้ทำงานกับ compiler หรือ interpreter ได้ เช่น วิธีการในการตั้งชื่อ variable ชื่อ klass เพราะว่าชื่อ class นั้นถูก reserved โดย compiler แต่ในการเลี่ยงนั้นก็ต้องระวังการตั้งชื่อที่เป็น number-series naming เช่น a1, a2, .. aN เพราะว่ามันไม่มีความหมายในตัวมันเอง

  • Use Pronounceable Name

อย่าตั้งชื่อที่ไม่สามารถอ่านออกเสียงได้ เช่น ตัวแปรชื่อ genymdhms ซึ่งย่อมาจาก generation year, month, day, hour, minute, และ second แต่ genymdhms มันไม่สามารถออกเสียงได้ เพราะฉะนั้น ควรหลีกเลี่ยงการตั้งชื่อในลักษณะนี้ด้วย

  • Use Searchable Names

บางครั้งการใช้ตัวอักษรเพียงตัวเดียวในการตั้งชื่อ อาจก่อให้เกิดปัญหา เนื่องจากมันยุ่งยากในการค้นหาโดย searching ฉะนั้นควรหลีกเลี่ยงด้วย

  • Avoid Encodings

การ encoding ชื่อต่างๆด้วยการระบุ type ด้วย เช่น String variable ตั้งชื่อว่า phoneString โดยเป็นการตั้งชื่อให้มีคำว่า String ร่วมด้วย เป็นการตั้งชื่อที่ไม่จำเป็น แถมยังสร้างความน่ารำคาญอีก

  • Interfaces and Implementations

การใช้ตัวอักษร I นำหน้า interface เช่น IShapeFactory เป็นสิ่งที่ควรเลี่ยง ถ้าสามารถทำได้

  • Class Names

ชื่อของ Classes และ Objects ควรเป็น “คำนาม” เช่น Customer, Book หรือ Student หลีกเลี่ยงกลุ่มคำ เช่น Manager, Processor และควรหลีกเลี่ยงการใช้คำ “กริยา” ในการตั้งชื่อด้วย

  • Method Names

ตั้งชื่อ Method ด้วยคำ “กริยา” เช่น postPayment, deletePage, หรือ save

  • Don’t Be Cute

อย่าตั้งชื่อให้มันดูหลักแหลมดูฉลาดมากเกินไป ควรตั้งชื่อให้มีความชัดเจน ไม่ใช่ตั้งชื่อตามความสนุกสนานหรือเพราะว่ามันดูเท่

  • Pick One Word per Concept

มันจะน่าสับสนขนาดไหน ถ้าใช้คำว่า fetch, retrieve, และ get กับ methods ที่เหมือนกันแต่อยู่คนละ classes ควรเลือกใช้แบบใดแบบหนึ่งเท่านั้น

function เป็นกระบวนการหรือคำสั่งใน computer program ซึ่งสำคัญมากๆ เทคนิคต่อไปนี้จะช่วยให้เราเขียน function ได้ clean มากยิ่งขึ้น

  • Small

— ความยาวของ code ต่อ line ไม่ควรยาวเกิน 150 characters (ส่วนตัวผมใช้ 120 characters ซึ่งเป็น default ของ IntelliJ)

— Function ไม่ควรยาวเกิน 20 บรรทัด

  • Do One Thing

Function ต้องทำงานอย่างเดียวเท่านั้น ไม่ควรทำหลายอย่างใน function เดียวกัน

  • Use Descriptive Names

ตั้งชื่อให้สื่อความหมายกับการทำงานของ function ด้วย โดยใช้ชื่อที่ชัดเจนให้สื่อถึงการทำงานสิ่งเดียวสิ่งนั้นของมันเลย

  • Function Arguments

Function Arguments ไม่ควรเยอะเกิน 3 arguments และอย่าใช้ flag arguments เช่น booleans เพราะมันบอกเป็นนัยว่า function นั้นๆกำลังทำงานมากกว่าหนึ่งอย่าง ถ้า arguments มีจำนวนมาก ให้พิจารณาการ wrap arguments เหล่านั้นโดยใช้ class แล้วส่ง class นั้นเป็น argument แทน

  • Have No Side Effects

ตรวจสอบให้แน่ใจว่า function ต้องไม่มี side effects เรื่อง side effects สำคัญเป็นอย่างมาก โดยเฉพาะอย่างยิ่งการเขียน codeในรูปแบบ functional programming

  • Command Query Separation

Function ควรจะดำเนินการ เช่น set result หรือ return result ต้องเป็นอย่างใดอย่างหนึ่ง ไม่ใช่ทำทั้งสองอย่าง

  • Don’t Repeat Yourself

หลีกเลี่ยงการทำ duplicate code!

ถึง comments อาจถือเป็น bad code แต่จงตระหนักอยู่ตลอดเวลาว่า code ของเราควรอธิบายการทำงานของมันด้วยตัวมันเอง ไม่ใช่เข้าใจได้ด้วย comments การตั้งชื่อที่ดี สามารถป้องกัน comments ได้ แต่สำหรับ Legal comments แล้ว จำเป็นต้องเขียน comment ด้วย ซึ่ง legal comment ในที่นี้ หมายถึง copyright และ licenses

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

ควรตั้ง formatting rules ของทีม โดย formatting rules นี้ อาจจะพิจารณาการใช้ tools ต่างๆมาช่วยในการทำงาน เพื่อให้การทำงานสะดวกและง่ายขึ้น เช่น maven checkstyle หรือใช้ความสามารถของ IDE ต่างๆ เช่น IntelliJ Code Formatter (Option + Cmd +L) แต่ทั้งนี้ทั้งนั้นแต่ละบริษัทหรือแต่ละ team อาจจะมีความแตกต่างกันเล็กน้อยใน formatting rules แต่ที่สำคัญที่สุด ทีมต้องตกลงและเข้าใจตรงกันใน formatting rules ที่จะใช้งานร่วมกันด้วย

Objects and Data Structures เป็นเรื่องซับซ้อน ดังนั้นเราควรให้ความเอาใจใส่มันเป็นอย่างดี

  • Data Abstraction

ควรซ่อนส่วนของการ implementation หรือซ่อน data ไว้หลัง abstractions และเผยเฉพาะ function ที่ทำงานกับข้อมูลนั้นๆแทนที่เปิดเผยข้อมูลทั้งหมดออกมา

  • Data/Object Anti-Symmetry

เราต้องเข้าใจความแตกต่างระหว่าง objects และ data structures โดย Objects ซ่อน data ไว้หลัง abstractions layer และแสดง function ที่ทำงานกับ data นั้นๆ ส่วน Data Structures แสดงข้อมูลของตนเพื่อความสะดวกและไม่มี meaningful function เหมือน objects

เทคนิคและข้อพิจารณาในการเขียน handles errors นั้นเป็นเรื่องสำคัญ โดยเฉพาะ errors ที่เกิดจากปัจจัยภายนอก program เราควรพิจารณาเทคนิคในการจัดการ exceptions ให้เหมาะสม

  • Use Exceptions Rather Than Return Codes

ใช้ความสามารถของภาษาที่เราเขียนให้เกิดประโยชน์ ถ้าภาษารองรับการใช้งาน exceptions ให้ทำการ ใช้ exceptions ในการจัดการกับ errors แทนที่ return error codes

  • Write Your Try-Catch-Finally Statement First

Code ใน try blocks เหมือนกับ transactions เนื่องจาก catch block สามารถดักจับ program ใน state ที่สอดคล้องกัน และใช้ finally Block ในการ clean up resources ในกรณีที่มีการใช้งาน resources connections

  • Use Unchecked Exceptions

ใช้ Unchecked Exceptions ทำหน้าที่เป็นข้อบกพร่องของ program เช่น บางครั้ง มีการส่งค่าที่ไม่ถูกต้องให้ Method ของเรา ควรใช้ Unchecked Exceptions ในการจัดการกับเงื่อนไขประเภทนี้ และ Unchecked Exceptions ยังเป็น subclasses ของ RuntimeException ซึ่งโดยปกติแล้ว Unchecked runtime exceptions ทำหน้าที่เกี่ยวกับจัดการเงื่อนไขและการสะท้อนข้อผิดพลาดนั้นๆ

  • Don’t Return Null

อย่าทำการ return Null ให้ทำการ re-throwing exception หรือ return object พิเศษที่เราสร้างขึ้นมาในการจัดการกับ exception แทน การที่เรา return null จะทำให้เราไม่รู้สาเหตุของ error เลย

Note : สำหรับ Java developers ที่สนใจอ่านเพิ่มเติมเกี่ยวกับการ Handle Exceptions ใน Java สามารถดูเพิ่มเติมได้ที่ 5 เทคนิคง่ายๆสำหรับการ Handle Exceptions ใน Java

Agile และ Test Driven Development (TDD) ได้จุดประกายเหล่า programmers จำนวนมากในการเริ่มที่จะเขียน unit tests แต่ก็มี programmers จำนวนที่ไม่น้อยที่ยังไม่เข้าใจความสำคัญในการเขียน unit tests และไม่เข้าใจจุดมุ่งหมายของการเขียน unit tests ที่ดี

  • The Three Laws of TDD

— ไม่เขียน production code จนกว่าจะเขียน test ทดสอบความผิดพลาดเสียก่อน

— ไม่เขียน unit test นอกเหนือจาก case ที่เพียงพอและคลุม fail case แล้ว ไม่งั้นเราจะเขียน test โดยฟุ่มเฟือยและเกิด over thinking

— ไม่เขียน production code เพิ่ม นอกจากมันจะผ่าน failing test ที่มีอยู่ในปัจจุบัน

  • Clean Tests

ปัจจัยที่ช่วยให้ tests ของเรา clean นั้น มีอยู่ด้วยกัน 3 แบบ คือ

1)Readability
2)Readability
3)Readability

โดยลักษณะของ readable code นั้น ต้องมีความชัดเจนและมีความเรียบง่าย

  • Five Rules of Clean Tests

— เร็ว
— เป็นอิสระ
— ทำซ้ำได้
— มีการ validate ด้วยตัวเอง
— ใช้เวลาที่เหมาะสม

  • Classes Should Be Small!

กฏอย่างแรก class ควรที่จะมีขนาดไม่ใหญ่จนเกินไป ซึ่ง small เป็นกฏหลักเวลาที่เรา design class

  • Encapsulation

variables และ utility functions ใน class ควรเป็น private หรือเป็น protected ในกรณีที่ต้องการใช้เพื่อการ test ใน package เดียวกัน

  • The Single Responsibility Principle

Single Responsibility Principle (SRP) คือ หนึ่ง class หรือ module ทำหน้าที่เพียงหนึ่งอย่าง ซึ่งหนึ่งอย่างนั้นควรมีเหตุผลเพียงอย่างเดียวเท่านั้นที่ class หรือ module นั้นๆจะถูกแก้ไข

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

--

--

Wattanachai Prakobdee
LINE Developers Thailand

Software Engineer at LINE Thailand | Learning is a journey, Let's learn together.