JavaScript จะใส่ semicolon (;
) หรือไม่ใส่ก็ได้ ไม่ทำให้โค้ดดีขึ้นหรือแย่ลง แทบไม่มีปัญหาอะไรแล้ว ขึ้นอยู่ความชอบส่วนตัวมากกว่า
จากโพลในกลุ่ม สมาคมโปรแกรมเมอร์ไทย:
ปกติเขียน Javascript ใส่ semicolon กันไหมครับ
และเพราะอะไรถึง ใส่/ไม่ใส่ ครับ
Table of contents
Automatic semicolon insertion ในภาษา JavaScript
ในภาษา JavaScript มีระบบ ASI (Automatic semicolon insertion) ซึ่งทำให้เวลาเขียนโค้ด JavaScript จะใส่ ;
หรือไม่ใส่ก็ได้
จึงเป็นคำถามที่ว่า ‘ตกลงควรใส่หรือไม่ควรใส่?’ ซึ่งเป็นประเด็นที่ถกเถียงกันอย่างหนัก และหลายๆ คนก็มาโหวตและให้เหตุผลกัน
บางเหตุผลก็สมเหตุสมผลดี แต่บางเหตุผลก็ไม่ถูกต้องครับ
คำตอบจริงๆ คือ จะใส่หรือไม่ใส่ก็ได้ครับ เพราะมันแทบไม่ได้ทำให้โค้ดดีขึ้นหรือแย่ลงเลย ขึ้นอยู่กับความชอบส่วนบุคคลและความเคยชินซะมากกว่า ไม่ต่างอะไรกับถามว่า ‘ควรซื้อจักรยานสีอะไร’
ส่วนตัวผมไม่อะไรมากเรื่อง ;
จะใส่หรือไม่ใส่ก็ได้
(ปกติผมจะไม่ใส่ แต่ถ้าผมไปทำงานในโปรเจกต์ที่เขาใส่ ;
กัน ผมก็จะใส่ด้วย ไม่มีปัญหาอะไร)
แต่ที่มาเขียนโพสต์นี้เพราะคิดว่า เรื่องยิบย่อยแบบนี้ ควรจะเลิกเถียงกันไปตั้งนานแล้ว เอาเวลาไปทำอย่างอื่นกันดีกว่า~
เหตุผลที่ไม่ถูกต้อง
ถ้าอ้างด้วยเหตุผลพวกนี้ เถียงยังไงก็ไม่จบครับ
“ควรใส่ ;
เพราะเสี่ยงเขียนโค้ดผิดน้อยกว่า”
เหตุผลนี้ใช้ไม่ได้ เพราะไม่ว่าจะใส่ ;
หรือไม่ใส่ ก็มีความเสี่ยงที่จะเขียนโค้ดแล้วพลาดได้ทั้งสองแบบ
นอกจากนี้ เครื่องมือปัจจุบันสามารถช่วยป้องกันการเขียนโค้ดพลาดได้ในทั้งสองกรณีอีกด้วย
คนที่ใส่ ;
เพราะคิดว่าไม่ควรพึ่งระบบ ASI (Automatic semicolon insertion) ต้องระวังเคสนี้ครับ:
var useSemicolons = function ()
{
var numberOfBugs = 0;
return
{
bugs: numberOfBugs
};
};
เคสนี้ คนที่ใส่ ;
จนชินอาจจะอ่านโค้ดข้างบนแล้วคิดว่า ฟังก์ชันนี้ควรจะให้ผลลัพธ์เป็น { bugs: 0 }
แต่มันกลับคืนค่าเป็น undefined
ออกมาแทนครับ
ฟังก์ชันนี้พังเพราะว่าคำสั่ง return
มันอยู่ด้วยตัวมันเองได้
ทำให้ JavaScript อ่านโค้ดเป็นแบบนี้:
var useSemicolons = function () {
var numberOfBugs = 0;
return;
{
bugs:
numberOfBugs;
}
};
ซึ่งหากปกติไม่ได้ใส่ ;
อยู่แล้ว ก็จะไม่เจอปัญหานี้ เพราะจะเห็นได้ทันทีว่าเราเขียนคำสั่ง return
เปล่าๆ
“ไม่ควรใส่ ;
เพราะเสี่ยงเขียนโค้ดผิดน้อยกว่า”
คนที่ไม่ใส่ ;
เพราะคิดว่าไม่จำเป็น จะต้องระวังเคสนี้ครับ:
var n = 10
(a || b).x = n
เคสนี้จะพัง เพราะบรรทัดที่สองขึ้นต้นด้วย (
จึงสามารถเอาไปต่อกับบรรทัดแรกได้
JavaScript เลยรวมสองบรรทัดเข้าด้วยกัน
ทำให้โค้ดข้างบนกลายเป็นแบบนี้:
var n = 10(a || b).x = n;
ซึ่งถ้าหากใส่ ;
เป็นประจำ ก็จะไม่น่าเจอปัญหานี้ครับ
จะเห็นได้ว่า ไม่ว่าใส่หรือไม่ใส่ ก็เสี่ยงทั้งคู่ — ซึ่งปัจจุบัน ESLint เครื่องมือที่ช่วยตรวจโค้ด JavaScript (ใช้กับ TypeScript ก็ได้นะ) ก็สามารถป้องกันเราจากสองเคสนี้ได้ครับ
- เคสแรกป้องกันได้ด้วยกฏ no-unexpected-multiline
- เคสสองป้องกันได้ด้วยกฏ semi และ no-unreachable
“ควรใส่ เพราะ Tools บางตัว (เช่นพวก Minifier) จะทำงานผิดพลาดถ้าไม่ใส่”
เหตุผลนี้อาจจะจริงในหลายปีก่อน แต่เดี๋ยวนี้ไม่จริงแล้วครับ
ภาษา JavaScript เป็นภาษาตามมาตรฐานของ ECMA-262 ซึ่งกำหนดวิธีอ่านโค้ดไว้อย่างชัดเจนตั้งแต่เวอร์ชั่นแรก (1997) แล้วครับ
โดย Tools ทุกตัวที่อ่านโค้ด JavaScript ตามมาตรฐาน จะสามารถอ่านโค้ดที่ไม่ใส่ ;
ได้อย่างไม่มีปัญหาเลยครับ
ต่อให้ย้อนกลับไปก่อนที่ภาษา JavaScript จะกลายเป็นมาตรฐาน กฏของการใส่ ;
อัตโนมัติ ก็ถูกกำหนดไว้ชัดเจนตั้งแต่แรกๆ แล้วครับ
ขนาด IE5 ยังอ่านได้อย่างไม่มีปัญหาเลย
ตอนนี้ Tools เกือบทุกตัวในที่คนมักใช้กัน ก็สามารถอ่านได้อย่างไม่มีปัญหาแล้วครับ (ส่วนตัวไหนที่อ่านไม่ได้ ถือว่าไม่ได้มาตรฐานและไม่ควรใช้ครับ)
“ควรใส่เพื่อให้มันถูกต้องตามหลักภาษา”
อย่างที่กล่าวไปในหัวข้อก่อนหน้า — ภาษา JavaScript มีกฏของการใส่ ;
ที่ชัดเจน ถ้าทำความเข้าใจมันก็จะรู้ว่า JavaScript จะขึ้นประโยคใหม่ตรงไหน
โค้ดที่ไม่ใส่ ;
จึงไม่ผิดกฏแต่อย่างใด
“ควรใส่ จะได้รู้ว่าแต่ละคำสั่งจบตรงไหน” “ควรใส่/ไม่ควรใส่ จะได้อ่านง่าย”
คำสั่งส่วนมากก็จบที่ท้ายบรรทัดอยู่แล้ว การใส่หรือไม่ใส่ ;
จึงแทบไม่มีผลต่อความอ่านง่ายเลย:
try {
var start = now()
return f.apply(this, arguments)
} finally {
var finish = now()
var stat = stats[title] || (stats[title] = new Stat(title))
stat.push(finish - start)
}
นอกจากนี้ภาษาใหม่ๆ เช่น Kotlin และ Swift ก็ไม่ต้องใส่ ;
และไม่เคยเจอใครบ่นว่าภาษาพวกนี้มันอ่านยากเพราะไม่มี ;
เลย
แต่การจัดเรียงบรรทัดต่างหากที่มีผลต่อความอ่านง่าย:
try { var start = now(); return f.apply(this, arguments); } finally
{ var finish = now(); var stat = stats[title] ||
(stats[title] = new Stat(title));stat.push(finish - start); }
“ควรใส่ จะได้เป็นระเบียบ” “ไม่ควรใส่ เพราะไม่ใส่แล้วสวยกว่า”
คำว่าสวยหรือเป็นระเบียบ ขึ้นอยู่กับคนดู ดังสำนวนที่ว่า “Beauty is in the eye of the beholder” ครับ ไม่ต่างอะไรกับบางเพลงที่ผมชอบ แต่คนอื่นไม่ชอบ
ดังนั้นเราชอบอะไรหรือมองว่าอะไรสวยหรือเป็นระเบียบกว่า ก็ไม่ได้แปลว่าคนอื่นควรจะเห็นตรงกับเราครับ
เหตุผลที่ใช้ได้
จะเห็นว่าการใส่ ;
หรือไม่ใส่ ;
แทบไม่ช่วยให้โค้ดดีขึ้นหรือแย่ลงเลย
ดังนั้นจึงไม่ควรแนะนำว่าควรใส่หรือไม่ควรใส่ด้วยเหตุผลว่าจะทำให้โค้ดดีขึ้นหรือแย่ลง
ส่วนเหตุผลพวกนี้ โอเคครับ:
- ใส่ เพราะปกติเขียนโค้ดตามกฏของ Airbnb
- ใส่ เพราะปกติเขียนโค้ดตามกฏของ Google
- ไม่ใส่ เพราะปกติใช้ JavaScript Standard Style
- ใส่ เพราะปกติเขียน PHP / Java / C++ / … ด้วย จะได้เหมือนๆ กัน
- ไม่ใส่ เพราะปกติเขียน Ruby / Python / Go / … ด้วย จะได้เหมือนๆ กัน
- ใส่ เพราะชินกับภาษา PHP / Java / C++ / …
- ไม่ใส่ เพราะเกะกะลูกตา
- ถ้าเกิดทำเว็บด้วย PHP ก็ควรใส่ โค้ดจะได้ดูเหมือนๆ กัน
- ถ้าเกิดทำเว็บด้วย Python ก็ไม่ควรใส่ โค้ดจะได้ดูเหมือนๆ กัน
- ใส่/ไม่ใส่ เพราะโปรเจกต์ที่ทำงานเขาใส่/ไม่ใส่กัน
- ใส่ เพราะชอบใส่ / ไม่ใส่ เพราะไม่ชอบใส่
- ไม่ใส่ เพราะขี้เกียจใส่
- จะใส่หรือไม่ใส่ก็ได้ แต่ถ้าใส่ก็ควรใส่ไปเลย หรือถ้าไม่ใส่ก็ไม่ควรใส่ไปเลย ให้มันเหมือนๆ กันทั้งโปรเจกต์
- (ส่วนตัวของผมเหตุผลนี้) ไม่ใส่ เพราะถ้าเกิดใส่แล้วเวลาลืมจะโดน ESLint เตือน บางทีเขียนโค้ดยังไม่จบบรรทัดมันก็เตือนแล้วมันรำคาญ แต่ถ้าเกิดไม่ใส่ผมไม่ค่อยเจอปัญหานี้ เพราะปกติไม่ลืมไม่ใส่