Tutorial

Programming 101 – Writing Clean Code

By Arnon Puitrakul - 10 ธันวาคม 2017

Programming 101 – Writing Clean Code

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

Code Style

Wonder Movie

คิดซะว่า Code Style เป็นกฏละกัน ที่จะทำให้ Code ของเราอ่านได้โดย Programmer ทุกคน บนโลกนี้มี Code Style อยู่เยอะแยะไปหมด แต่ประเด็นสำคัญมันไม่ได้อยู่ที่ว่าเราหยิบอันไหนมาใช้ ประเด็นมันอยู่ที่เมื่อเราทำงานเป็นทีมเราต้องเข้าใจร่วมกันก่อนว่า เราจะใช้แบบไหน และทำให้ทุกคนเห็นว่า การนำมาใช้มันทำให้ชีวิตง่ายขึ้นยังไง มันก็จะช่วยให้ทุกคนนำมาใช้ได้เรื่อย ๆ เช่นส่วนตัวผมจะใช้ PSR ในการทำงานกัน จริง ๆ ไม่ได้เพราะมันมีอะไรหรอก แต่เพราะคนในทีมผมตกลงกันว่าเอาอันนี้แหละ ฮ่า ๆ แต่ไม่ต้องกลัวว่าการเราเขียนให้ตรงกับ Code Style จะทำให้เราเสียเวลาในการทำงานมากขึ้น นี่ปี 2017 แล้ว มี Tool ต่าง ๆ ช่วยเราได้มากมาย เช่น การใช้ Git Hook ที่จะ Styling Code ของเราตอนที่เรา Commit หรือจะเป็น Plugin ต่าง ๆ ที่เราสามารถหยิบมาใช้กับ Text Editor ของเราได้ เช่น Beautify ใน Atom ที่ช่วยให้เราจัด Code ให้ส่วนงามได้อย่างง่ายดาย นอกจากนั้นถ้าใครใช้ Modern IDE แล้วละก็ Feature การ Style Code ถือเป็นอะไรที่มาตราฐานมากที่จะมี ฉะนั้นการจัดการเรื่องของ Code Style เป็นอะไรที่ไม่ได้กินแรงซะเท่าไหร่ แค่ทุกคนตกลงปลงใจที่จะมาใช้ร่วมกันได้ก็จบแล้ว

Reduce Duplicated Code

connector1.init()
connector2.init()
connector3.init()
connector4.init()

จากที่เราเห็นคือ เราต้องเรียก init() ซ้ำกัน 4 ครั้ง แต่เราจะเห็นว่ามันอ่านแล้วมันรกมากแค่ไหน ลองคิดดูว่า ถ้าเราทำแบบนี้ทุกครั้ง Code ของเรามันจะออกมาหน้าตาแย่แน่ ๆ อ่านยากสุด ๆ ฉะนั้น ถ้าเราต้องทำอะไรซ้ำ ๆ หลาย ๆ ครั้ง ไม่ว่าเราจะรู้จำนวนที่มันต้องทำหรือไม่ก็ตาม การใช้ Loop มันจะช่วยได้ เช่น

connectors = [connector1, connector2, connector3, connector4]
foreach (connector in connectors)
{
    connector.init()
}

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

Naming Things

โปรแกรมเมอร์เป็นอะไรที่มีปัญหากับการตั้งชื่อตลอด ตั้งแต่สิ่งของ ชื่อโปรแกรม ยันชื่อตัวแปร หลาย ๆ ครั้งที่ผมอ่าน Code แล้วเจอชื่อตัวแปรสุด Classic เช่น A หรือ B เองก็เถอะ ก็อยากจะเอาอะไรฝาดหัวคนเขียนหน่อย ๆ เหตุผลเป็นเพราะ แค่ A กับ B มันไม่ได้บ่งบอกสักนิดเลยว่าตัวแปรนี้มันคืออะไร เอามาทำอะไร หรือพูดอีกนัยก็คือ เราไม่เข้าใจจุดประสงค์ของมัน ยกเว้นว่าเราจะเขียนโปรแกรมให้มันคำนวณสูตรอะไรสักอย่างแล้วมันมีตัวแปรชื่อ A หรือ B อยู่มันก็เข้าใจได้ไง การตั้งชื่อของหลาย ๆ อย่างมันก็มีหลายวิธีบางคนใช้ตั้งมันยาว ๆ เลย เอาให้อ่านแล้วรู้เลยว่าสิ่งนี้คืออะไร (แทบจะเขียน Abstract ลงไปแล้วมั่ง) ก็ยาวเกินต้องมาเลื่อนอ่านดูอีก ปวดหัวไปสิ ! กับอีกเขาพวกใจเด็ดสุด ๆ เล่นเขียนตัวย่อลงไปเลยแจ้ ทีนี้แหละ กลับมาอ่านทีนี่ งง ไปตาม ๆ กัันว่าที่ย่อมานี่หมายความว่าอะไร บางทีคนเขียนเองยังลืมเลย วิธีง่าย ๆ ที่ผมมักจะใช้ในการตั้งชื่อคือการใช้ Context ของสิ่งที่เราต้องการตั้งชื่อมาใช้ เพื่อให้มันสามารถบอกได้จากชื่อว่าตัวแปรนี้มันคืออะไร เก็บอะไรอยู่ เช่นผมมี ตัวแปรที่เก็บ Class ของ User อยู่ผมก็จะตั้งชื่อว่า userInstance เพื่อให้เป็นการรู้ว่าของชิ้นนี้เก็บข้อมูลของ User คนนึงอยู่นะ กับอีกจุดที่ผมมักจะใช้คือการเติม s ลงไปในชื่อว่า สิ่ง ๆ นี้มันประกอบตัวของหลายชิ้นเช่น userInstances ที่เป็นการบอกว่าตัวแปรนี้เก็บ User อยู่หลาย ๆ อัน ทำให้เวลาเราเอาไปเข้า Loop มันก็จะง่ายหน่อย ๆ เพราะเราไม่ต้องมานั่งหาชื่อให้มันอีก มันก็จะเขียนง่ายขึ้นดังตัวอย่างข้างล่างนี้

foreach (userInstance in userInstances) {
     #do sth in the code
}

จะเห็นได้ว่ามันทำให้เราไม่ต้องคิดชื่อใหม่แค่เอาตัว s ออก จริง ๆ วิธีนี้ก็ไม่รู้เหมือนกันว่ามันถูกมั้ย แต่เอาจริงคือผมก็ติดมาสัก 2-3 ปีละ ก็เขียนแล้วก็อ่านง่ายดีถ้าเราค่อนข้างจู้จี้กับไวยกรณ์ทางภาษาหน่อย ๆ ถ้าไม่เติม s เวลาเป็นพหุพจน์มันจะขัดใจหน่อย ๆ เข้าใจว่าการคิดชื่อมันอาจจะใช้เวลาเยอะหน่อย แต่ถ้าตอนนั้นคิดไม่ออกก็ตั้งชื่ออะไรไปก่อนก็ได้เช่น whatever หรือจะเป็นคำว่า fuck ถ้าเราหัวร้อนมาก ๆ แต่สุดท้ายแล้วอย่าลืมแก้มห้มันดี ๆ หน่อยตอนหลังเอาได้ (ก่อนจะ Commit จะดีมาก !) ในปัจจุบันมี Feature การเปลี่ยนชื่อแบบอัตโนมัติมักจะมาใน IDE หลาย ๆ ตัวแล้วละ ไม่ต้องเป็นห่วงว่าจะตามแก้ไม่หมด ถามว่าตั้งชื่อมั่ว ๆ มันก็ไม่มีผลกับโปรแกรมหรอก แต่มันทำให้เวลาเราหรือคนอื่นมาอ่านก็จะสามารถอ่านได้ง่ายขึ้น ลดอาการหัวร้อนได้เป็นอย่างดี

Data Structure & Data Coupling

หลาย ๆ ครั้งที่เราเขียนโปรแกรมกัน เราก็ค่อยคิดกันเรื่องของการจัดการข้อมูลในโปรแกรมเราเท่าไหร่ ลองมาดูตัวอย่างข้างล่างนี้กัน

public function transfer (String sourceAccId, String sourceOwnerName, String sourceOwnerSurname, String destinationAccId, String destinationOwnerName, String destinationOwnerSurname, double amount, String currency)
{
   # do sth
}

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

public function transfer (Account source, Account destination, Money amount)
{
    # do sth
}

เราจะเห็นมันว่า เราโยนตัวแปรเป็น Parameter น้อยกว่าเดิม ทำให้อ่านได้ง่ายขึ้น ยิ่งไปกว่านั้นถ้าเราต้องการอะไรที่มากกว่า เลขบัญชี และชื่อ-สกุล แล้วละก็เป็นอะไรที่ง่าย ๆ มาก ๆ แค่เราเข้าไปแก้ใน Class Account เพิ่มเข้าไปก็ได้แล้ว ทำให้ Code ของเรา Focus ไปที่ Logic ของโปรแกรมได้มากขึ้น อีกจุดหนึ่งที่จะเห็นได้ กับคนที่เรียน Data Structure มาแล้วคือการเลือกใช้ Data Structure ที่เข้ากับงานนั้น ๆ เรื่องนี้ส่งผลต่อ Performance อย่างรุนแรงมาก โดยเฉพาะโปรแกรมที่ต้องเข้าถึงข้อมูลบ่อย ๆ เช่น

function getUserInfoFromCitiId (User [] users, String citiId)
{
    foreach (user in users) {
       if (user.citiId == citiId) return return user
    }
    return null
}

จากตัวอย่างด้านบนเป็น Function ในการหา User จากเลขบัตรประชาชน ถ้าเป็นอย่างตัวอย่างข้างบนถ้าเราต้องการหา User สักคนจากเลขบัตรประชาชน เราจะเสียทั้งหมด O(n) ในการหา 1 User ลองดู Code ด้านล่างนี้

function getUserInfoFromCitiId (User [] users, Hashmap idMapper, String citiId)
{
    return users[idMapper.get(citiId)]
}

จาก Function ด้านบน ผมหยิบ Hashmap ที่ Map ระหว่าง เลขบัตรประชาชนกับ Index ใน Array เวลาเราอยากได้คนไหน เราก็สามารถป้อนลงไปใน Hashmap แล้วก็จะได้ Index มาเข้าถึงข้อมูลได้ตรง ๆ เลย โดยที่ไม่ต้องไล่วิ่งหาทีละอัน เห็นมั้ยว่า Code มันสั้นลงไปอีก แต่ไม่ใช่เท่านั้นเวลาในการหาก็น้อยลงอีกด้วยเพราะเวลาในการหาลดลงจาก O(n) เหลือแค่ O(1) เท่านั้น จะเห็นว่ามันต่างราวฟ้ากับเหวเลย นั่นก็หมายความว่าจากที่เราต้องให้มันวิ่งหาทุก Record ในทุกการค้นหาก็ไม่จำเป็นอีกต่อไป เราสามารถเข้าถึงข้อมูลตรง ๆ ได้เลย จริง ๆ มันต้องแยกออกจากกันเป็น 2 เรื่องแต่ก็เอาเถอะ จาก 2 เรื่องนี้ทำให้เราเห็นความสำคัญของการแยกข้อมูลว่า อะไรมันควรติดกัน อะไรมันควรแยกกัน อย่างที่เราได้เห็นจากในตัวอย่างของการโอนเงิน และ การเลือกใช้ Data Structure ที่ดีก็ทำให้เราสามารถทำ Performance Tuning ได้อีกระดับหนึ่งเลยจริง ๆ เคสที่ยกมาในเรื่องของการใช้ Hashmap จัดว่าเป็น Classic Case ในการยกตัวอย่างเลยก็ว่าได้ แนะนำว่าให้ลองไปหาอ่านเพิ่มดูได้ มันมีอะไรทำนองนี้อยู่เยอะมาก ๆ

Higher Level

เวลาเราเขียนโปรแกรมบางครั้งเราก็ต้อง Focus กับอะไรที่เราต้องทำจริง ๆ ไม่ใช่ Focus กับทุกอย่าง ต้องระแวงว่านี่ทำงานยังไง นั่นทำงานยังไง ทั้งที่ไม่ใช่ประเด็นหลักของสิ่งที่เราต้องเขียนในตอนนั้น ตัวอย่างยอดฮิตคือ init()

public function init()
{
    readFile()
    loadToMemory()
    show()
}

อย่างการ Init โปรแกรมเรา เราไม่ได้จำเป็นว่าอะไรต้องทำยังไง แต่เราต้อง Focus ว่าเราควรทำอะไรบ้าง การเขียนแบบนี้มันทำให้เราสามารถอ่านแล้วมองภาพออกได้ง่ายกว่าว่า เราต้องทำอะไรบ้าง ส่วยรายละเอียดของแต่ละอย่าง เราก็เขียนแยกไปเป็นอีก Function นึงไป

แล้วอะไรบ้างละที่ควรแยก ?

จริง ๆ มันก็แล้วแต่เรา (ประเด็นคือ กำนหนดไว้แต่ต้น คุยกันไว้แต่ต้น แล้วทำให้เหมือนกัน) แต่ถ้าเอาที่ผมใช้ ก็อะไรที่คิดว่า ณ ตอนนั้นมันไม่ได้เป็นประเด็นหลักของ Function นั้น ๆ ผมก็จับแยกออกไป ทำยังไงก็ได้ให้มันออกมาแบบดู High Level ที่สุดเท่าที่จะทำได้ อีกจุดนึงที่เราไม่ค่อยได้นึกถึงกัน แต่เราจะรู้สึกประมาณว่า “แม่มเอ้ย !” ตอนที่มันเกิดขึ้นคือ ตอนที่จำนวน Code ในแต่ละ Function มันเยอะมาก ๆ บางทีเท่าที่เคยเจอมา Class นึงกดไปเกือบพันบรรทัด ทำให้จะหาอะไรมันหายากมาก เปิดไฟล์ทีก็เหมือนมีจังหวะหน่วงหน่อย ๆ อาการนี้แก้ได้ด้วยการจับแยกซะก็จบแล้ว

Commenting Your Code

การเติม Comment มันทำให้ Code ของเรานั้นอ่านง่ายขึ้น โดยเฉพาะพวกสายเสกที่มันชอบเสก Code เทพ ๆ มาจนมนุษย์อ่านได้ยากมาก แต่สิ่งที่คำคัญผมมักจะพูดอยู่เสมอคือ

Comment Why not What !

หมายความว่า เวลาเราจะ Comment ให้เราเขียนว่า ทำไม มากกว่า อะไร เพราะคำว่า อะไร เราก็อ่านออกแหละว่า บรรทัดนี้มันทำอะไร (เว้น แต่คนอ่านมันอ่านภาษาโปรแกรมไม่ออก) แต่ถามว่า ทำไม ต้องทำสิ อันนี้ถ้าแค่อ่านผ่าน ๆ มันก็ดูไม่ค่อยออกหรอกเอาจริง ๆ พยายาม Comment ให้เหมือนกับ Guideline มากกว่าว่าทำไม Code ตรงนี้มันมายังไง มันช่วยอะไรมากกว่า บางทีถ้าเราใช้ Library บางอย่างมันสามารถที่จะดูดเอา Comment ในรูปแบบที่กำหนดไว้ออกมาสร้างเป็น Document แบบอัตโนมัติได้ด้วยก็มี หรืออีกอย่างคือการใช้พวก TODO, FIX อะไรพวกนี้ก็ช่วยให้ Plugin บางตัวสามารถ Detect และทำออกมาเป็น List เพื่อเป็น Reminder ให้เราได้ว่าตรงนี้เราเว้นไว้ เพื่อเขียนต่อ หรือแปะไว้ให้มาแก้ก็ว่ากันไป ที่เจ๋งคือมันมี Plugin ตัวนึงมันสามารถเอา Comment พวก TODO, FIX อะไรพวกนี้ลงไปแจ้ง Error ใน Github ได้ด้วย โคตรเจ๋ง

สรุป

การเขียน Code ของเราให้ อ่านง่าย และ น่ารัก หรือบางทีเราอาจจะเรียกมันว่า Clean Code มันอาจจะไม่ได้มีผลกระทบต่อ Performance มาก แต่มันทำให้เราสามารถทำงานกับคนอื่นได้ง่ายขึ้น ลดเวลาในการอ่าน Code ลง ลดอาการหัวร้อนเวลาคนอื่นมาอ่านได้ด้วย ลดหัวร้อนได้ อารมณ์ดี บรรยากาศในการทำงานก็ดีขึ้น (โยงมั่วขนาดนี้ GAT เชี่อมโยงเต็มแน่นอน 99H) เอาเป็นว่าพยายามทำตัวให้ชินกับการเขียน Code ดี ๆ หัดบ่อย ๆ เดี๋ยวมันก็จะชินไปเอง กับใช้ Tool ที่เราใช้อยู่ให้เป็นประโยชน์ด้วยนะแจ๊ะ สวัสดี ~

Read Next...

จัดการข้อมูลบน Pandas ยังไงให้เร็ว 1000x ด้วย Vectorisation

จัดการข้อมูลบน Pandas ยังไงให้เร็ว 1000x ด้วย Vectorisation

เวลาเราทำงานกับข้อมูลอย่าง Pandas DataFrame หนึ่งในงานที่เราเขียนลงไปให้มันทำคือ การ Apply Function เข้าไป ถ้าข้อมูลมีขนาดเล็ก มันไม่มีปัญหาเท่าไหร่ แต่ถ้าข้อมูลของเราใหญ่ มันอีกเรื่องเลย ถ้าเราจะเขียนให้เร็วที่สุด เราจะทำได้โดยวิธีใดบ้าง วันนี้เรามาดูกัน...

ปั่นความเร็ว Python Script เกือบ 700 เท่าด้วย JIT บน Numba

ปั่นความเร็ว Python Script เกือบ 700 เท่าด้วย JIT บน Numba

Python เป็นภาษาที่เราใช้งานกันเยอะมาก ๆ เพราะความยืดหยุ่นของมัน แต่ปัญหาของมันก็เกิดจากข้อดีของมันนี่แหละ ทำให้เมื่อเราต้องการ Performance แต่ถ้าเราจะบอกว่า เราสามารถทำได้ดีทั้งคู่เลยละ จะเป็นยังไง เราขอแนะนำ Numba ที่ใช้งาน JIT บอกเลยว่า เร็วขึ้นแบบ 700 เท่าตอนที่ทดลองกันเลย...

Humanise the Number in Python with "Humanize"

Humanise the Number in Python with "Humanize"

หลายวันก่อน เราทำงานแล้วเราต้องการทำงานกับตัวเลขเพื่อให้มันอ่านได้ง่ายขึ้น จะมานั่งเขียนเองก็เสียเวลา เลยไปนั่งหา Library มาใช้ จนไปเจอ Humanize วันนี้เลยจะเอามาเล่าให้อ่านกันว่า มันทำอะไรได้ แล้วมันล่นเวลาการทำงานของเราได้ยังไง...

ทำไม 0.3 + 0.6 ถึงได้ 0.8999999 กับปัญหา Floating Point Approximation

ทำไม 0.3 + 0.6 ถึงได้ 0.8999999 กับปัญหา Floating Point Approximation

การทำงานกับตัวเลขทศนิยมบนคอมพิวเตอร์มันมีความลับซ่อนอยู่ เราอาจจะเคยเจอเคสที่ เอา 0.3 + 0.6 แล้วมันได้ 0.899 ซ้ำไปเรื่อย ๆ ไม่ได้ 0.9 เพราะคอมพิวเตอร์ไม่ได้มองระบบทศนิยมเหมือนกับคนนั่นเอง บางตัวมันไม่สามารถเก็บได้ เลยจำเป็นจะต้องประมาณเอา เราเลยเรียกว่า Floating Point Approximation...