Saladpuk.com
🏆 เนื้อหาหลัก
Search
K
Comment on page
👶

Clean Code

เคยทำความสะอาดโค้ดกันบ้างหรือเปล่า ?

😢 ปัญหา

เคยอ่านโค้ดแล้วกร่นด่าในใจกันไหม "นี่มันโค้ดบ้าอะไร?", "ทำไมเขียนแบบนี้ฟร๊า?", "จะเริ่มจากตรงไหนดี?" บลาๆ ซึ่งถ้าเกิดเหตุการณ์แบบนี้ขึ้นนั่นแสดงว่า เราจะทำงานได้ช้าลง เพราะต้องเสียเวลานั่งทำความเข้าใจเจ้าโค้ดพวกนั้น และเลวร้ายที่สุดคือการที่พบว่า ลบโค้ดพวกนั้นทิ้งแล้วเขียนใหม่ดีกว่า นั่นแสดงว่าทีมต้องเสียเวลาไปทำงานเดิมซ้ำสอง
โค้ด !@#$%^&* พวกนั้นเราเรียกมันว่า Bad Code ซึ่งเราควรจะเปลี่ยนมาเขียน Good Code กัน

❓ ทำไมต้องแก้ Bad Code ?

ถ้าในโปรเจคมี Bad Code อยู่เยอะๆ คนอื่นๆในทีมก็จะเห็น Bad Code เหล่านั้น เมื่อเห็นเรื่อยๆเห็นบ่อยๆ มันก็มีโอกาสสูงที่ทีมจะคิดว่านั่นคือเรื่องธรรมดา เลยทำให้ทีมบ่มนิสัยผลิตแต่โค้ดที่กากๆออกมา ทำให้โปรเจคนี้และโปรเจคต่อๆไปเราก็จะเจอแต่โค้ดที่ทำให้ทีมทำงานได้ช้าลงตลอดไป
สมมุติว่าเราต้องเพิ่มความสามารถใหม่ให้โค้ดหรือต้องไปแก้ bug กับงานที่มี Bad Code เยอะๆ จะได้อารมณ์ประมาณภาพด้านล่างนี้
เส้นสีเขียว - คือเราต้องเดินไปไหนบ้างถึงจะไปเจอจุดที่ต้องไปเขียนโค้ดจริงๆ โซนสีส้ม - คือจุดที่ต้องเข้าไปเขียนโค้ด
ภาพจาก ronjeffries.com
จะเห็นว่า Bad Code ต้องเสียเวลาไปหาก่อนว่าจุดที่เราจะทำงานอยู่ตรงไหน และต้องเสียเวลาทำความเข้าใจก่อนว่ามันต้องแก้ตรงไหนบ้าง ซึ่งถ้ามั่วมากๆก็อาจจะทำให้ไปแก้ผิดจุดได้
คราวนี้มาดูงานที่มีแต่ Good Code ที่ทุกอย่างเป็นระเบียบเรียบร้อยกันบ้าง
ภาพจาก ronjeffries.com
จะเห็นว่า Good Code ทำให้เราเข้าถึงจุดของงานได้เร็ว และ ลดโอกาสเกิดข้อผิดพลาดลง

🤔 ดูยังไงว่าโค้ดเราดีหรือยัง ?

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

😄 วิธีแก้ปัญหา

ก็อย่าเขียน Bad Code แต่แรกดิ !! (ไม่บอกก็รู้เฟร้ย 🤣)
การเขียน Good Code นั้นเป็นเรื่องของความเอาใจใส่ในคุณภาพของโค้ด ซึ่งการเขียนโค้ดมันก็เหมือนกับคุณเขียนหนังสือซักเล่มนั่นแหละ ถ้าคุณเขียนแล้วคนอื่นเอาไปอ่านแล้วไม่เข้าใจ มันก็หมายถึงคุณเขียนได้ไม่ดี #โค้ดก็เช่นกัน
งั้นเดี๋ยวเราไปดูมาตรฐานสากลของ Good Code แล้วเทียบกับ Bad Code กันเรย

🔥 การเขียนเงื่อนไข

  • อย่าเขียนเงื่อนไขที่ให้คนอ่านต้องแปลความหมาย เพราะมันจะเสียเวลาทำความเข้าใจ และอาจะเข้าใจผิดได้
ลองดูโค้ดด้านล่างแล้วทำความเข้าใจดูครับว่ามันจะสื่อว่าอะไร ? แล้วลองเปรียบเทียบ Bad Code กับ Good Code ดู
Bad Code
if(Math.Pow(banaCa.Extana + 3 / 1.2, 2) >= Policy && (FullName.StartWith("LUNA") || LastName == "Covan" ))
{
// เข้าผับได้
}
else
{
// เข้าผับไม่ได้
}
Good Code
var canEnterThePub = Math.Pow(banaCa.Extana + 3 / 1.2, 2) >= Policy && (FullName.StartWith("LUNA") || LastName == "Covan" );
if(canEnterThePub)
{
// เข้าผับได้
}
else
{
// เข้าผับไม่ได้
}
ใจความของตัวอย่างคือ เราเข้าใจเงื่อนไขได้เร็วขนาดไหน * Bad Code เราต้องไล่ดูทีละเงื่อนไขทีละตัวใน if เพื่อจะได้รู้ว่ามันกำลังตรวจเรื่องอะไร * Good Code เราไม่ต้องไล่ดูเงื่อนไขเลย เพราะอ่านใน if ปุ๊ปเราก็เข้าใจได้ทันที

🔥 คอมเมนต์

  • อย่าเขียนคอมเมนต์!! ถ้าเขียนคอมเมนต์แสดงว่าโค้ดคุณไม่ได้เข้าใจง่ายพอ คุณเลยต้องเขียนคอมเมนต์ทิ้งไว้ให้ชนรุ่นหลังได้เข้าใจ วิธีแก้คือสร้าง method ที่ทำให้เข้าใจได้ง่ายแทน
  • อย่าคอมเมนต์โค้ดที่เผื่อจะได้ใช้ เพราะคนอื่นไม่รู้สาเหตุหรอกว่าคุณคอมมเมนต์มันไว้ทำไม และเขาจะไม่กล้าลบมันแล้วมันจะกลายเป็นขยะค้างไว้แบบนั้น ลบมันทิ้งซะ เพราะยังไงก็อยู่ใน commit อยู่แล้ว
Bad Code
/// <summary>
/// เปลี่ยนแปลงที่อยู่ผู้ใช้ใหม่
/// </summary>
/// <param name="email">อีเมล์ผู้ใช้</param>
/// <param name="address">ข้อมูลที่อยู่ใหม่</param>
public void UpsertAddress(string email, Address address)
{
// ตรวจสอบหาข้อผิดพลาด
if(address == null)
{
throw new Exception("Address เป็น null");
}
// ค้นหาผู้ใช้และตรวจว่ามีผู้ใช้ในระบบหรือไม่
var user = Database.User.Get(it => it.Email == email);
if(user == null)
{
throw new Exception("หาผู้ใช้ไม่พบ");
}
else
{
address.UserId = user.Id;
}
// ดึงข้อมูลที่อยู่ผู้ใช้มาทำการแก้ไข
if(Database.Address.Get(it => it.UserId == user.Id) == null)
{
Database.Address.Insert(address);
}
else
{
Database.Address.Update(address);
}
// เก็บเอาไว้ก่อนเผื่อได้ใช้ในอนาคต
//if(user.Setting.SendNotificationWhenAddressChanged)
//{
// Email.Send(email, address, "คุณได้ทำการเปลี่ยนที่อยู่ใหม่");
//}
}
Good Code
public void UpsertAddress(string email, Address address)
{
var user = getUserByEmail(email);
updateAddress(user, address);
}
private UserProfile getUserByEmail(string email) ...
private void updateAddress(UserProfile user, Address address) ...
ลดการเสียเวลาในการอ่านโค้ด โดยการสร้าง method เพื่อให้คนอ่านเห็นเป็นภาษามนุษย์มากขึ้น ไม่ใช่ต้องไปไล่อ่านโค้ดเพื่อทำความเข้าใจว่าเจ้า 10 บรรทัดนี้คืออะไร
จากตัวอย่างจะสังเกตุได้ว่า ใน Good Code บรรทัด 3 มันแทนที่ 14-19 ใน Bad Code ได้หมดเลย (และอ่านเป็นภาษามนุษย์ด้วย) ใน Good Code บรรทัด 4 มันแทนที่ 25-33 ใน Bad Code ได้หมดเลย (และอ่านเป็นภาษามนุษย์ด้วย) ส่วน 8-12 และ 20-23 ใน Bad Code มันถูกย้ายไปจัดการใน updateAddress() เรียบร้อยแล้ว และ 35-39 ถ้าไม่ได้ใช้ก็ลบทิ้งซะอย่างเก็บมันไว้ คนอื่นจะไม่กล้ายุ่งกับโค้ดคุณ

🔥 การตั้งชื่อทั่วไป

  • เวลาจะตั้งชื่ออะไรก็แล้ว ต้องตั้งให้มันสื่อความหมาย อ่านแล้วเข้าใจว่ามันมีไว้เพื่ออะไร
  • ชื่อต้อง อ่านออกเสียงได้ ถ้ามันออกเสียงไม่ได้เวลาจะบอกคนอื่นว่ามันผิดที่ไหน คุณจะบอกเขาว่ายังไง?
  • หลีกเลี่ยงคำย่อ เพราะทุกคนย่อคำไม่เหมือนกัน เช่น Student คุณจะย่อว่าอะไร (std? โรคติดต่อทางเพศงั้นเหรอ?)
  • ชื่อควรตั้งชื่อตามพฤติกรรม เช่น ชื่อคลาสควรเป็นคำนาม, ชื่อ method ควรขึ้นด้วยคำกิริยา
  • อย่าตั้งชื่อยาว เพราะมันจะเสียเวลาทำความเข้าใจ
  • อย่าตั้งชื่อเป็นนิเสธ เพราะคนอ่านจะ งง (แค่คำว่านิเสธก็ งง แล้ว)
  • อย่างตั้งชื่อกำกวม เพราะมันจะสับสนว่าตัวไหนเป็นตัวไหนกันแน่
Bad Code
double m; // ตัวแปรตัวนี้ใช้ทำอะไร ?
string actpwd; // ไอ้นี่มันอ่านว่ายังไง? ย่อจากอะไร?
string generatedSaltValueForEncryptTheAccountPasswordForThisUserOnly; // เสียเวลาอ่านจุง
string data; // กำกวมเกิน สรุปมันคือ data เรื่องอะไร?
Good Code
double money; // อ่านแล้วรู้เลยว่าใช้เก็บเรื่องเงิน
string accountPassword; // อ่านแล้วรู้เลยว่าเก็บรหัสผ่านของผู้ใช้
string salt4Password; // อ่านแล้วรู้เลยว่าเก็บรหัสผ่าน
string userData; // อ่านแล้วรู้เลยว่าเก็บข้อมูลผู้ใช้
เกร็ดความรู้ คลีนโค้ดบางสำนักไม่แนะนำให้ตั้งชื่อแบบใส่ตัวเลขแทนคำอ่านนะ เช่น Good Code บรรทัดที่ 3 อาจจะเขียนเต็มๆไปเลยก็ได้ saltForPassword

🔥 การตั้งชื่อ Methods & Functions

กฎในการตั้งชื่อทั่วไปทั้งหมดก็ใช้กับการตั้งชื่อ Methods & Functions ด้วยนะจ๊ะ
  • ขึ้นต้นด้วยคำกิริยา เพราะคนเรียกใช้อ่านแล้วรู้เลยว่าใช้ทำอะไร (คนไทยอาจะไม่มีผลเท่าไหร่แต่ฝรั่งนี่มีสูงเลย)
  • อย่ารับ arguments เยอะ เพราะคนเรียกใช้ method อาจ งง ใส่ผิด และเวลาเพิ่ม/ลดละก็อวกแตก
  • ตั้งชื่อให้สื่อว่างานของมันคืออะไร ไม่งั้นคนเรียกใช้ method จะมีคำถาม
Bad Code
AccountNameChanger(string name) // ต้องอ่านทั้งหมดถึงจะเข้าใจ
CreateAccount(string uName, string pwd, ...) // Arguments จะเยอะไปไหน
ChangeAccountPasswordAndDeleteIfNotValid() // เอ็งจะเปลี่ยนรหัสผ่านหรือจะลบผู้ใช้กันแน่ ?
Good Code
ChangeAccountName(string name) // อ่านแล้วเข้าใจเลยเพราะเห็นคำกิริยาก่อนเพื่อน
CreateAccount(AccountInfo account) // คนเรียกไม่สับสน
ChangeAccountPassword() // อ่านแล้วรู้เลยว่าแค่เปลี่ยนรหัสผ่าน
DeleteAccountIfNoBirthDate() // อ่านแล้วรู้เลยว่าจะลบผู้ใช้กรณีไหน

🔥 Layout

  • ควรจัดวาง Layout ให้ตรงกับมาตรฐานของภาษาที่ใช้ เพราะโค้ดของทั้งทีมจะได้เหมือนกัน และเวลาทำงานกับ Git ก็จะไม่เกิด conflict ด้วย
Bad Code
if( conditionA ){
// Do something-1
}
else if(conditionB)
{
// Do something-2
}
else
// Do something-3
// สรุปเอ็งจะใช้ layout ของ { } และการเว้นวรรคแบบไหนกันแน่?
Good Code
if(conditionA)
{
// Do something-1
}
else if(conditionB)
{
// Do something-2
}
else
{
// Do something-3
}

🎯 บทสรุป

เรื่อง Clean Code มันเป็นเรื่องความเอาใจใส่ในผลงานของตัวเอง ซึ่งผลลัพท์ที่ได้มันจะทำให้เราไม่เสียเวลาหลงทางในเขาวงกต และมันไม่ได้มีแค่เท่านี้หรอก อันนี้เป็นเพียงน้ำจิ้มส่วนนึงเท่านั้น และแต่ละภาษาก็มีหลักในการทำ clean code ในรูปแบบของเขาอยู่เช่นกัน เลยขอดึงเฉพาะตัวที่เป็นกลางๆมาให้ดูก่อน ไว้มีโอกาสจะเอามาเพิ่มให้อ่านนะจุ๊
ทำทันที เรื่องการ Clean Code ห้ามปล่อยทิ้งไว้ เพราะ คุณไม่กลับมาแก้หรอก!! ดังนั้นจงจำไว้ "ทำ-ทัน-ที"
เกร็ดความรู้ มาตรฐานทั้งหมดที่ว่านี้ ในแต่ละภาษามันจะมี Coding Standard ของเขาเองอยู่นะ ลองไปหาอ่านได้ เช่น มาตรฐานของฝั่ง .NET สามารถอ่านได้จากลิงค์นี้ครับ Microsoft document