การเขียน Javascript ที่ดี

พอดีไปเจอมาเลยกะมาแบ่งปันครับหลายๆคนก็เขียน javascript มาเยอะแล้วแต่อาจจะหลงลืมไปบ้างในการเขียนทีดี เขียนให้ทำงานได้นั้นดี แต่จะดีกว่าครับหากเขียนให้เป็น style ที่ทำงานเร็วขึ้นและเป็นระเบียบมากขึ้นครับ

Writing Maintainable Code

javascript การเขียนโค้ดที่ดี

การแก้ไข Bug เป็นค่าใช้จ่ายที่ราคาแพง และราคาของการจัดการจะเพิ่มขึ้นเรื่อยๆ โดยเฉพาะ Bug ที่ไปเจอตอนที่ขึ้น production ไปแล้ว ( เหมือนเวลาปล่อยเว็บขึ้นจริงแล้วไปเจอตอนโชว์ของให้ชาวบ้านดูนั่นแหละ ) แต่มันจะดีกว่าไหมถ้าเราสามารถจัดการมันได้ในทันทีเมื่อคุณเจอมัน แต่การจะทำอย่างนี้ได้ต่อเมื่อคุณทำงานอยู่กับงานนี้ แล้วถ้าวันหนึ่งคุณต้องย้ายไปจัดการงานอย่างอื่นล่ะ ? และลืมไปหมดแล้วสำหรับโค้ดของงานเก่า ( ผมเจอบ่อยมาก ! ) ในการกลับมาแก้ไขงานเก่าของเราหรือของคนอื่นต้องประกอบไปด้วยสองอย่างหลักๆคือ

  • เวลาสำหรับการเรียนรู้และทำความเข้าใจปัญหา
  • เวลาสำหรับการเข้าใจโค้ดเพื่อจะแก้ไขปัญหา

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

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

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

  • Bug ที่ไม่ได้อยู่ในการครอบคลุม ( เช่นคิดว่าจะส่งค่าเท่านี้เพราะตอนที่เราสร้างมีแค่นี้แต่พอไปเชื่อมต่อแล้วมีส่งค่าที่ไม่ได้คิดถึง หรือมีค่าที่ไม่ได้กำหนดไว้ส่งมาทำเกิด Bug )
  • การเพิ่ม Feature ใหม่ๆของโปรแกรมครับ ( เช่น ตอนนี้ทำระบบ Login ไม่มี Facebook แต่หลังๆเจ้านายต้องการแล้วพอเราทำเสร็จดันมีค่าบางอย่างที่ไม่ได้ตั้งใจให้มีเกิดขึ้นทำนองนี้ครับ )
  • โปรแกรมต้องไปทำงานใน environment ใหม่ๆ ( เช่น ตอนแรกรันอยู่ใน linux แต่ต้องย้ายไปทำงานบน window server หรือต้องทำงานใน Browser ใหม่ๆได้ )
  • โค้ดเปลี่ยนวัตถุประสงค์ใหม่ๆ ( ตอนแรกกะว่าจะทำงานได้แค่สองค่า ตอนต้องทำงานรับได้ 3 ค่าก็ต้องมาเปลี่ยนแปลงโค้ดกันใหม่ ซึ่งอาจทำให้เกิด Bug ได้ )
  • โค้ดต้องเขียนใหม่หมดหรือต้องเปลี่ยนไปใช้สถาปัตยกรรมใหม่ๆ ( เช่น mysql -> mongoDB ) หรือแม้แต่การเปลี่ยนไปใช้ภาษาใหม่ๆ ( PHP -> node.js )

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

Maintainable code คือสิ่งเปล่านี้

  • อ่านรู้เรื่อง
  • มั่นคงสอดคล้องกัน
  • ค้นหาได้ง่าย
  • เขียนเหมือนเป็นคนๆเดียวกันเขียน เพราะควรจะเขียนให้เหมือนๆกันทุกคน
  • มันคือเอกสารในตัว

Minimizing Globals

Javascript จะมีขอบเขตของตัวแปรใน function ของตัวเอง และแน่นอนนอกเหนือ function เราจะมีตัวแปรที่ไปได้ทุก function นั่นคือ “global” ตรงหัวข้อนี้เขาพยายามบอกว่า อย่าใช้เยอะ ! เพราะว่าการใช้ global variable นั่นอาจจะทำให้โปรแกรมของเราหยุดทำงานได้ทันที

เพราะว่าตัวแปรแบบ global เมื่อเราประกาศมันจะไปอยู่ใน object ของ window เรียกว่าอยู่เหนือสุดเลยจึงสามารถเรียกได้ที่ทุกเวลาเหมือนจะสะดวกแต่ไม่เลยเพราะมันอาจจะทำให้เกิดปัญหากับโปรแกรมอื่นๆดังนี้

  • third-party Javascript library คือเราไปเอาโค้ด login FB มาแต่ดันใช้ตัวแปรเดียวกันแบบบังเอิญอาจทำให้โปรแกรมหยุดทำงาน
  • Script จาก advertising partner หากเว็บเรามีโฆษณาด้วยบางคนตัวแปรของเราอาจจะไปชนกับ script โฆษณานั่นหมายถึงทำให้รายได้เราหดหาย
  • Script tracking and analytic พวก script สำหรับการตรวจสอบเช่น โค้ดของ google analytic หรือของยี่ห้ออื่นๆ ซึ่งถ้าทำให้หยุดทำงานเราจะวิเคราะห์อะไรไม่ได้เลย
  • Widget, badge and button โค้ดส่วนอื่นๆที่เราอาจจะตั้งชื่อตัวแปรโดยบังเอิญทำให้หยุดทำงานครับ

และเวลาที่เราสร้าง function อะไรมาแล้วไม่ประกาศตัวแปร Javascript จะถือว่าตัวแปรนั้นเป็น global เองเลยเช่น

function sum(x, y) { 
   // antipattern: implied global 
   result = x + y; 
   return result;
}

ตัวแปร result จะเป็น global โดยทันที ซึ่งนั่นอาจจะเป็นหายนะในภายหลังแล้วจะแก้ไขอย่างไรแน่ะหรอง่ายนิดเดียวครับพี่น้องแค่ประกาศ “var” ไปว่าให้ตัวแปรนี้อยู่แค่ใน function sum เท่านั้นนะก็จะได้แหละ

function sum(x, y) {
   var result = x + y;
   return result;
}

และบางคนเคยประกาศแบบนี้ครับ

// antipattern, do not use 
function foo() {
   var a = b = 0;
   // ...
}

ถ้าหากประกาศแบบนี้ตัวแปร b จะเป็น global โดยทันทีเพราะอะไรหรอ ? เพราะว่ามันคำนวนจาก ขวามาซ้าย แล้วทำให้เกิดการคำนวนแบบนี้ครับ คอมพิวเตอร์จะคำนวนว่า b = 0 ก่อนซึ่งเราไม่ได้ประกาศ b เมื่อไม่ได้ประกาศก็เข้าเงื่อนไขที่บอกไว้ก่อนหน้านี้ว่ามันจะทำการเข้าใจว่า b คือ global โดยทันทีครับผม หากไม่อยากจะสร้างตัวแปร global แบบไม่ได้ตั้งใจก็ให้ประกาศอย่างนี้ซะก่อน

function foo() { 
   var a, b;
   // ... a = b = 0; // both local
}

จากตัวอย่างที่กล่าวมาคงจะเห็นแล้วว่าการตั้งชื่อตัวแปรแบบ global ไม่ใช่ว่าไม่ให้ใช้ถ้าจะใช้ก็ควรเป็นชื่อที่ไม่เหมือนใครเลยจะดีมาก และใช้มันให้น้อยที่สุดเท่าที่จะเป็นได้ครับ

Side Effects When Forgetting var

นอกจากเรื่องการประกาศตัวแปรแบบ global โดยตั้งใจและไม่ตั้งใจยังมีข้อแตกต่างอีกนิดด้วยครับคือ

  • ตัวแปรที่เราตั้งใจประกาศเป็นแบบ global นั้นจะ ไม่สามารถลบได้!!!
  • ตัวแปรที่ไม่ได้ตั้งใจประกาศเช่น ไม่ใส่ var ใน function จะสามารถลบได้ครับ

เอาล่ะเรามาดูตัวอย่างกันเลยครับ

// define three globals 
var global_var = 1; 
global_novar = 2; // antipattern 
(function () {
   global_fromfunc = 3; // antipattern 
}());

// attempt to delete 
delete global_var; // false 
delete global_novar; // true 
delete global_fromfunc; // true

// test the deletion 
typeof global_var; // "number" 
typeof global_novar; // "undefined" 
typeof global_fromfunc; // "undefined"

จากโค้ดข้างบนนั้นตัวแปร global_var ถูกประกาศแบบตั้งใจ และสองตัวที่เหลือนั้นเป็นการประกาศแบบอาจจะไม่ได้ตั้งใจให้เป็นตัวแปร global ซึ่งผลก็อย่างที่เห็นครับตัวแปร global_var ไม่สามารถลบออกได้

Access to the Global Object

ทุกๆ browser นั้นการเข้าถึงตัวแปร global สามารถเข้าถึงใน property ของตัวแปร window อีกทีแต่ในบางกรณีเราสามารถเข้าถึงโดยไม่ต้องทำการ hardcode ในตัวแปร window โดยการทำอย่างนี้ครับ

var global = (function () { 
   return this;
}());

ประกาศอย่างนี้ใน function ของคุณก็จะสามารถเข้าถึงตัวแปร window ได้ทันที โดยเราไม่ต้อง hardcode ตัว window ครับแต่ผมดูแล้วว่ามันอาจจะไม่สะดวกก็ได้ยังไงก็ลองเอาไปใช้กันดู

Single var Pattern

ใช้การประกาศครั้งเดียวสำหรับหลายๆตัวแปร สิ่งที่จะได้รับจากการทำอย่างนี้คือ

  • มีจุดสำหรับสังเกตุว่ามีตัวแปรไหนชื่ออะไรบ้าง ไม่ต้องประกาศหลายทีดูที่เดียวส่วนใหญ่แล้วจะประกาศบรรทัดแรกสุดไม่ก็ตอนเริ่มต้นของ function ครับ
  • ป้องกันการถูกเรียกใช้ก่อนที่จะประกาศทำให้ไม่เกิด error ครับผม
  • ช่วยให้เราจดจำได้ว่าประกาศอะไรไปแล้วบ้างชื่ออะไร จะได้ลดการใช้ global ด้วย
  • ทำให้โค้ดสั้นลงนิดนึง

ตัวอย่างการใช้นะครับ

function func() { 
   var a = 1,
       b = 2, 
       sum = a + b, 
       myobject = {}, 
       i, 
       j;
   // function body...
}

การทำอย่างนี้เป็นตัวอย่างที่ดี เพราะการประกาศพร้อมๆกับใส่ค่าเริ่มต้นให้แก่ตัวแปรจะลดการเกิด error undifined ของตัวแปรลงครับและช่วยให้โค้ดอ่านง่ายขึ้นอีกด้วย ถึงแม้ว่าจะกลับมาอ่านทีหลังก็ยังทำให้เราเข้าใจว่าตัวแปรไหนรับค่าอะไร และที่สำคัญเราสามารถยัดค่า DOM ( Document Object Model ) และ element ต่างๆเข้าไปยังตัวแปรได้อีกด้วยครับ ตัวอย่าง

function updateElement() { 
   var el = document.getElementById("result"),
       style = el.style; 
   // do something with el and style...
}

Hoisting: A Problem with Scattered vars

มันปัญหาบางอย่างในการประกาศชื่อตัวแปรซ้ำกันใน function กับ global โดยเราไม่ได้ตั้งใจและทำให้ตัว Javascript นั้นทำงานผิดพลาดตัวอย่างครับ

// antipattern 
myname = "global"; // global variable 
function func() {
    alert(myname); // "undefined" 
    var myname = "local"; 
    alert(myname); // "local"
} 
func();

หลายๆคนที่เขียนจะเข้าใจว่าการ alert ครั้งที่ 1 ค่ามันต้องเป็น global สิทำไมเป็น undefined นั่นก็เพราะว่าตัวแปร local ใน function นั้นชื่อเหมือนกับตัวแปร global โดยตัว Javascript มันจะเข้าใจว่าตัวแปร myname ที่อยู่ใน function ยังไม่ถูกประกาศ เหมือนเราเรียกใช้งานก่อนอ่ะครับ จึงไม่มีค่าพอหาค่าไม่ได้จึงกลายเป็น undefined นั่นเอง จริงๆทางแก้ไขก็คืออาจจะไม่ใช้ชื่อที่เหมือนกับตัว global หรืออีกทางก็คือประกาศ var ไว้แต่แรกๆเลยก็ช่วยลดการผิดพลาดแต่แรกได้ครับ เพราะไม่ว่าคุณจะประกาศตัวแปร myname ไว้ตรงไหนของ function มันก็จะเข้าใจว่าขอบเขตคืออยู่ใน function เท่านั้นครับ

for Loops

การวนซ้ำทำซ้ำที่หลายๆคนต้องใช้กับ array ซึ่งหลายๆคนก็เขียนประมาณนี้

// sub-optimal loop 
for (var i = 0; i < myarray.length; i++) {
   // do something with myarray[i]
}

ปัญหาสำหรับการเขียนโค้ดแบบนี้คือ ทุกๆครั้งของการวน 1 รอบมันจะทำการถามค่าจาก array ทุกครั้ง ซึ่งมันจะทำให้โค้ดทำงานได้ ช้า โดยเฉพาะอย่างยิ่งถ้าหากตัวแปร myarray ไม่ใช่ array แต่เป็น HTMLCollection object แล้วเจ้า HTMLCollection object มันคืออะไรหว่า ?

  • document.getElementsByName()
  • document.getElementsByClassName()
  • document.getElementsByTagName()

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

for (var i = 0, max = myarray.length; i < max; i++) {
   // do something with myarray[i] 
}

จะเห็นว่ามีเพิ่ม max = myarray.length เข้ามารับค่าตัวเลขสำหรับการวนครับทำให้ไม่ต้องไปถามทุกครั้งตัวเลขก็คงที่เสมอ งั้นก็มีคำถามอีกว่าหากต้องการตัวเลขที่มันมีการเปลี่ยนแปลงตลอดเวลาล่ะทำอย่างไร เช่นอยากรู้ว่า user สร้าง input มาทั้งหมดกี่ตัวแหละ ถ้าเป็นกรณีอย่างนี้ตัวแปร max ของเราจะไม่ใช่ค่าคงที่ ( constant ) ให้เราทำฟังก์ชั่นอย่างนี้ครับ

function looper() { 
   var i = 0,
        max, 
        myarray = [];
   // ...
   for (i = 0, max = myarray.length; i < max; i++) {
      // do something with myarray[i]
   }
}

จะสังเกตุว่าเราไม่ได้ประกาศ var ภายในลูปอีกต่อไปทำให้เราสามารถปรับเปลี่ยนค่าได้ หากเราทำการเปลี่ยนแปลงค่าก็อาจจะโยนค่าอะไรเข้าไปใน function looper แล้วกำหนดค่าใหม่ให้แก่ max ก็ทำได้ครับ การทำ function อย่างนี้ดีตรงที่สามารถย้าย function ไปไหนก็ได้โดยเราจะไม่ลืมค่า i และ max เพราะจะมีติดไปด้วยตลอด

ต่อมาที่เราจะทำการ optimize ตัว i++ เนี้ยแหละคือจุดเล็กๆที่หลายๆคนพลาดครับโดยการปรับแบบใหม่เราจะทำให้ได้ประโยชน์คือ

  • ลดตัวแปรได้อีก 1 ตัวนั่นคือ ไม่ใช่ตัวแปร max อีกต่อไป
  • การเปรียบเทียบค่ากลับไปที่ 0 นั้นเร็วกว่า

เราไปดูกันว่าทำอย่างไรครับ

var myarray = [],
    i = myarray.length; 
while (i--) {
   // do something with myarray[i]
}

โดยเราใช้ตัวแปร i มารับค่า max แล้วทำการวิ่งถอยกลับไปที่ 0 จะเร็วกว่าแต่อย่าลืมว่ามันเหมือนเอาค่า array มานับจากตัวมี index มากสุดกลับไปที่น้อยสุดนะครับ แต่ก็สามารถใช้ฟังก์ชั่นในการจัดเรียงทีหลังได้หากมีการปรับเปลี่ยนแล้ว

for-in Loops

for-in ลูปมักจะใช้กับการวนใน object ซึ่งเราจะเรียกการวนแบบนี้ว่า enumeration ในเชิงเทคนิคแล้วคุณสามารถใช้ฟังก์ชั่นนี้กับ array ก็ได้เพราะ javascript ก็มอง array คือ object อยู่ดีแต่ไม่แนะนำ มันอาจจะนำมาซึ่งข้อผิดพลาดได้ อาจจะงงยกตัวอย่าง ถ้า array ตัวนั้นมี augment เป็น function อยู่ในตัวมัน นอกจากนี้มันยังไม่สามารถยืนยันได้ชัวร์ๆเลยว่าจะเอาค่าแต่ละตัวออกมาได้จริงๆถ้าหากเราใช้ for-in

ฉะนั้นเราจึงควรจะใช้ for loop สำหรับ array ธรรมดา และ for-in loop สำหรับ object

ในการวนลูปของ object ที่เรากำลังพูดถึงนี้มี method หนึ่งที่เรียกว่าเป็นพระเอกเลยคือ hasOwnProperty() ซึ่งมันจะตรวจจับว่าตอนกำลังวนหาค่าข้างใน object นั้นมีตัวไหนไม่ใช่ค่าของ object ตัวนั้นหรือเปล่าพูดแล้วอาจจะงงไปดูตัวอย่างกันเลยดีกว่า

// the object 
var man = {
   hands: 2, 
   legs: 2, 
   heads: 1
};

// somewhere else in the code 
// a method was added to all objects 
if (typeof Object.prototype.clone === "undefined") {
   Object.prototype.clone = function () {};
}

จากตัวอย่างที่กำหนด เรากำหนด object man ชื่อมาโดยมีค่าข้างในเป็น hands, legs, heads อย่างที่เห็นแล้วบรรทัดต่อมาเนี้ยมีการเติม method clone ให้กับทุก object ย้ำนะครับ ทุก object นั่นหมายถึงตัว object ที่ชื่อ man ของเราด้วย ซึ่งหมายความว่าปัจจุบันตัว object man ของเราเป็นอย่างนี้ครับ

// the object 
var man = {
   hands: 2, 
   legs: 2, 
   heads: 1,
   clone: function() {}
};

ซึ่งการประกาศอย่างนี้มันดีที่ว่า object ทุกตัวอาจจะต้องมี Method นี้ในการทำอะไรบ้างอย่าง แต่เวลาที่เราวนลูปนั้นค่าของ close จะถูกนับว่าเป็นหนึ่งในค่าที่ถูกวนออกมาด้วย เพราะฉะนั้นเราต้องการเพียงค่าของ hands, legs, heads แต่ไม่ต้องการให้มันวน clone ออกมาจึงใช้คำสั่งในการเช็คว่าค่า property ตัวนี้เป็นของ object นั้นๆที่กำลังวนอยู่หรือเปล่า ไปดูโค้ดกันครับ

// 1. 
// for-in loop 
for (var i in man) {
   if (man.hasOwnProperty(i)) { // filter
      console.log(i, ":", man[i]);
   }
} 
/* result in the console 
hands : 2 
legs : 2 
heads : 1 
*/
// 2. 
// antipattern: 
// for-in loop without checking hasOwnProperty() 
for (var i in man) {
   console.log(i, ":", man[i]);
} 
/* 
result in the console 
hands : 2 
legs : 2 
heads : 1 
clone: function() 
*/

เห็นไหมครับถ้าเราวนไม่ดูตาม้าตาเรือนั้นเราจะได้ค่า clone ติดมาด้วยซึ่งเราไม่ต้องการต่อไปเราจะ optimize ได้อีกนิดในการเขียนอะไรที่ยาวๆสำหรับการเช็คค่า hasOwnProperty ได้ดังนี้

var i, hasOwn = Object.prototype.hasOwnProperty;
for (i in man) {
    if (hasOwn.call(man, i)) { // filter
        console.log(i, ":", man[i]);
    }
}

และเราสามารถทำให้อ่านง่าย ( readable ) ได้อีกโดยการเอามาอยู่บรรทัดเดียวกันอย่างนี้ครับ

// Warning: doesn't pass JSLint 
var i, hasOwn = Object.prototype.hasOwnProperty;
for (i in man) if (hasOwn.call(man, i)) { // filter
    console.log(i, ":", man[i]);
}

(Not) Augmenting Built-in Prototypes

อย่างที่เห็นว่าเราสามารถสร้างหรือแอดค่าใหม่ๆ ไม่ว่าจะเป็น function หรือ object ลงใน object อีกทีเช่นเดียวกับการแอด method clone เข้าไปใน object man ซึ่งดูแล้วมีประโยชน์มากแต่บางครั้ง … มันเกินพอดี

เพราะการที่เราใช้มันจนเกินความจำเป็นไม่ว่าเราจะใส่อะไรเข้าไปใน constructor ( เวลาเรียกใช้แล้วมีค่ารอไว้ก่อน ) เช่น Object(), Array(), or Function() มันทำให้การดูแลโค้ดภายหลังลำบาก ทำไม ? เพราะว่ามันทำให้โค้ดของเราคาดเดายาก ถ้าหากมีเพื่อนและคนในทีมเราทำงานต่อจากเรา เขาอาจจะคาดหวังว่าตัวเขาจะใส่เพิ่มพวก Method ของเขาเเพื่อให้สอดคล้องกับโค้ดของเราและไม่ได้ต้องการการเพิ่มหรือแอดอะไรบางอย่างจากเรา เช่น เพื่อนเราอาจจะมาเขียนฟังก์ชั่น clone ใส่มาในภายหลังที่ทำงานต่อจากเรา แต่เรากลับหวังดีทำไปก่อนไม่ได้ตกลงกันไว้อย่างนี้เป็นต้น หรือหากตกลงกันไว้แล้วก็ไม่ควรทำเกินขอบเขตของโค้ดเราครับ

นอกจากนี้การที่เราใส่ค่า property เข้าไปใน object มันอาจจะไปแสดงผลตอนเพื่อนเราเอาไปวนลูปหาค่า ถ้าหากเพื่อนเราไม่รู้วิธีใช้ hasOwnProperty() มาก่อนเขาจะเกิดความสับสนว่าอะไรฟ่ะ ตูวนแล้วค่านี้มาไง

เพราะฉะนั้นมันจะดีที่สุดถ้าคุณไม่สร้างอะไรแปลกๆเข้าไปใน prototype เราเรียกว่า built-in prototype แต่ ! คุณสามารถมีข้อยกเว้นได้ก็ต่อเมื่อเงื่อนไขดังนี้

  • ถ้าหาก ECMAScript versions ( คล้ายๆพวกองค์กรในการกำหนดมาตราฐานว่าจะใช้ function อะไรเข้าไปในตัวหลักของ javascript พวกฟังก์ชั่นพื้นฐานนั่นแหละ )  จะทำ method ใหม่ๆที่จะ build-in เข้าไปในในตัว javascript คุณสร้างฟังก์ชั่นแบบนั้นรอได้เลย
  • คุณทำการเช็ดแล้วว่าถ้าหากตัว property หรือ method ที่คุณต้องการใช้มันหาไม่เจอ ซึ่งอาจจะมีการใช้แล้วในบางส่วนของโค้ดของคุณหรือมันเป็นส่วนหนึ่งของ JavaScript engine ที่ตัว Browser ของคุณ support ก็เขียนได้เลย
  • คุณมีการตกลงกับทีมแล้วว่าจะสร้าง built-in ตัวนี้ๆนั้นๆนะ แล้วสามารถเรียกใช้ได้ตกลงกับทีมให้ดีก็โอเคเขียนเลย

ถ้าตรงตามเงื่อนไข 3 อย่างด้านบน คุณสามารถสร้าง property หรือ function เข้ากับตัว prototype ได้เลยครับโดยสามารถทำตาม pattern นี้ได้

if (typeof Object.protoype.myMethod !== "function") { 
   Object.protoype.myMethod = function () {
      // implementation...
   };
}

ปล. การแอดอย่างนี้คือการแอดเข้าไปทุก object หมายถึงเป็นส่วนหนึ่งของ javascript เลยนะครับ

switch Pattern

ว่ากันเรื่อง switch ต่อครับเราสามารถทำให้มันอ่านง่ายและดูเป็นระเบียบเรามาดูกันเลยดีกว่า

var inspect_me = 0, 
    result = '';
switch (inspect_me) { 
case 0:
   result = "zero";
   break; 
case 1:
   result = "one";
   break; 
default:
   result = "unknown";
}

การเขียนอย่างนี้ดียังไง มันดีอย่างนี้ครับ

  • มีการจัดตำแหน่งของ case และ switch ตามกฎการ indentation
  • indent ( กด tab ) ในส่วนโค้ดข้างใน case
  • จบด้วย break;
  • หลีกเลี่ยงการไม่ใช้ break แต่ถ้าหากคุณมั่นใจว่ามีการพูดคุยกับทีมแล้วหรือการเขียนโค้ดของเราอธิบายได้ดีแล้วเพราะว่า หากใครมาอ่านโค้ดของคุณจะงงเพราะมันจะดูคล้ายๆกับการเขียนให้ error
  • เขียน default ทุกครั้งจะได้รู้ว่ามีอันนี้ที่ไม่ตรงกับ case ที่เราคาดหวัง

Avoiding Implied Typecasting

Javascript จะทำการเปลี่ยน type ของตัวแปรให้อัตโนมัติเมื่อมีการ compare ใน if เช่น ถ้าหากเราต้องการเปรียบเทียบในเงื่อนไขว่าเป็น false การใส่การเปรียบเทียบเหล่านี้ให้ผลเป็น false เหมือนกันเช่น false == 0 หรือ “” == 0 return true

การป้องกันการสับสนเวลาเปรียบเทียบให้เป็นอย่างที่เราต้องการให้ใช้ === หรือ !== สำหรับการเปรียบเทียบค่า not เพื่อเช็คว่ามันเหมือนกันทั้งค่าและ type ตัวอย่าง

var zero = 0; 
if (zero === false) {
   // not executing because zero is 0, not false
}

// antipattern 
if (zero == false) {
   // this block is executed...
}

มีหลายๆที่บอกว่ามันเกินไปที่จะมานั่งเปรียบเทียบค่าแบบทั้งค่าและ type เพราะการใช้แค่ == มันเพียงพอแล้ว ยกตัวอย่างเช่น หากคุณใช้ type ที่คุณรู้อยู่แล้วว่ามันจะ return เป็น string ฉะนั้นมันไม่มีเหตุผลที่ต้องใช้ strict equality ( === ) แต่อย่างไรก็ตามเว็บตรวจการเขียนโค้ด Js หรือเรียกว่า ( JSLint ) จะทำการบอกว่าต้องใช้ === เพราะว่าไม่รู้ว่าการที่เราใช้ == มันจะตั้งใจหรือไม่ตั้งใจ

Avoiding eval()

ถ้าหากคุณลองสังเกตุการใช้ evel() ในโค้ดของคุณจำไว้เลยว่ามีคำกล่าวที่ว่า “eval() is evil.” เพราะไอ้เจ้าฟังก์ชั่นมันจะทำการรันคำสั่ง Javascript แม้ว่าเป็นเพียงแค่ประโยค string ธรรมดาซึ่งหมายถึงหายนะที่จะตามมาหากมีการใช้ eval() อย่างไม่ระวังและก็ไม่มีความจำเป็นต้องใช้เลยในหลายๆกรณี

ตัวอย่างเช่นหากต้องการรันคำสั่งในพวกฟังก์ชั่นอย่าง setTimeout() ให้เราทำอย่างนี้ครับ

// antipatterns 
setTimeout("myFunc()", 1000); 
setTimeout("myFunc(1, 2, 3)", 1000);

// preferred 
setTimeout(myFunc, 1000); 
setTimeout(function () {
   myFunc(1, 2, 3); 
}, 1000);

แล้วที่น่ากลัวคือตัวฟังก์ชั่น eval() นั้นอาจจะสร้างตัวแปรแบบ global โดยเราไม่รู้ตัวก็ได้เพราะในคำสั่งที่มีบางครั้งอาจจะไม่ได้ประกาศด้วย var อย่างที่เรารู้กันมันทำให้ตัว javascript สร้างตัวแปรแบบ global โดยปริยายนั่นเองหลีกเลี่ยงเป็นดีที่สุดครับ

Number Conversions with parseInt()

ด้วยฟังก์ชั่น parseInt เราสามารถเปลี่ยนจากตัวแปรแบบ string มาเป็น int ได้ เราควรจะใส่ค่า parameter ตัวที่สองด้วยนั่นคือ หน่วยฐานเลขครับ เพราะบางทีมันอาจจะกลับเลขฐานเป็นฐานอื่นได้ถ้าหากมี 0 อยู่ข้างหน้ายกตัวอย่างเช่น

var month = "06",
    year = "09";
month = parseInt(month, 10);
year = parseInt(year, 10);

ตัว year มันอาจจะเป็นเลขฐานแปดได้

Coding Conventions

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

หลายๆครั้งในการประชุมหาข้อตกลงว่า ข้อตกลงในการเขียนโค้ดต้องมีอะไร บ้างอย่างไร ไม่ว่าจะเป็นเรื่องเล็กๆอย่าง indentation ควรจะเป็น tab or space ถ้าหากคุณเป็นหนึ่งในคนที่เสนอข้อตกลงในบริษัทหรือทีมควรจะเตรียมตัวที่จะเปิดรับมุมมองอื่นๆ และต้องมั่นใจกับความคิดเห็นต่างๆด้วย เพราะมันสำคัญมากๆจริง

Indentation

การ indent ทำให้อ่านง่ายนักพัฒนาบางคนเสนอการ indent แบบ tab เพราะว่าทุกๆคนสามารถแสดงผล tab ได้และกำหนดได้ด้วยว่าจะเป็น tab ครั้งละกี่ space บางคนก็เสนอว่าควรจะเป็น space ไปเลยโดยเฉพาะมี space = 4 ซึ่งเป็นสากลแต่มันไม่สำคัญหรอกถ้าหากว่าทีมมีข้อตกลงการเขียนโค้ดอย่างไรและยังคงทำตามกันอยู่

แล้วคุณควรจะ indent อะไรบ้าง ? กฎง่ายๆคือ ทุกอย่างที่อยู่ในวงเล็บปีกกา “{}” พวกใน function ก็ใช่ loop ด้วย if , switch และ object property ต่างๆใน object ดูตัวอย่างกัน

function outer(a, b) {
    var c = 1,
        d = 2,
        inner;
    if (a > b) {
        inner = function () {
            return {
                r: c - d
            };
        };
    } else {
        inner = function () {
            return {
                r: c + d
            };
        };
    }
    return inner;
}

Curly Braces

คือหมายปีกกาถูกใช้ตลอดแม้แต่ตอนที่มีทางเลือกก็ใช้อยู่ดีในเชิงเทคนิคถ้าคุณมีแค่ 1 statement ใน if หรือ for ปีกกาอาจจะไม่ต้องใช้ก็ได้แต่คุณควรจะใช้ตลอดไม่ว่าจะใส่ได้หรือไม่ใส่ก็ได้มันทำให้โค้ดดูเข้าใจและง่ายต่อการ update

ลองคิดดูคุณมี for loop กับอีก 1 statement คุณสามารถไม่ใส่ปีกกาก็ได้และมันก็ไม่ได้ทำให้ผิด syntax เช่น

// bad practice 
for (var i = 0; i < 10; i += 1)
   alert(i);

แต่หลังจากนั้นคุณต้องกลับมาแก้ไขโค้ดเป็น …

// bad practice 
for (var i = 0; i < 10; i += 1)
   alert(i); 
   alert(i + " is " + (i % 2 ? "odd" : "even"));

การ alert ของคำสั่งที่บรรทัดที่สองนั้นอยู่นอกการวนลูป เพราะฉะนั้นใส่วงเล็บปีกกาเถอะครับพี่ขอ

// better 
for (var i = 0; i < 10; i += 1) {
   alert(i);
}

และในทำนองเดียวกันกับการใช้ if else

// bad 
if (true)
   alert(1); 
else
   alert(2);

// better 
if (true) {
   alert(1); 
} else {
   alert(2);
}

Opening Brace Location

เราควรจะใส่ปีกกาให้เท่ากับบรรทัดของเงื่อนไขหรือว่าลงมาอีก 1 บรรทัดกันแน่ ?

จริงๆคือใส่แบบไหนก็ได้แต่มีบางกรณีที่น้อยๆมากๆจำเป็นต้องยกเว้นในคือกรณีของการไม่ใส่ semicolon แล้วตัว javascript ดันใส่ให้เองแบบ auto เช่น

// warning: unexpected return value 
function func() {
   return
  // unreachable code follows
   {
      name : "Batman"
   }
}

อย่างนี้มันจะ return เองโดยทันทีไปไม่ถึงบรรทัดของ object ที่มี name เป็น property อยู่ครับเราจึงต้องเขียนแบบนี้

function func() {
   return {
      name : "Batman"
   };
}

White Space

การเว้นวรรคในการโค้ดที่ดีทำตามนี้ครับ

  • หลัง semicolon เช่น for (var i = 0; i < 10; i += 1) {…}
  • หลังการประกาศค่าแบบ multiple
  • หลัง commas ( , ) เช่น var a = [1, 2, 3];
  • ก่อนปีกกาถ้าหากเป็นบรรทัดเดียวกัน function () { }
  • หลังคำว่า function

และสุดท้ายหลังพวก operation ทั้งหลายเช่น +, -, *, =, <, == เป็นต้น

Naming Conventions

ข้อตกลงในการตั้งชื่อ ควรเลือกชื่อที่ความสอดคล้องระหว่างตัวแปรและฟังก์ชั่นที่คนทั่วไปอ่านแล้วเข้าใจ เช่น ฟังก์ชั่นเกี่ยวกับวันเวลาตัวแปรก็ควรจะเป็นเกี่ยวกับเวลาเช่น min,sec,hour ทำนองเนี้ยอ่ะครับ

Capitalizing Constructors

Javascript ไม่มี class แต่ดันมี constructor function โดยจะถูกใช้เมื่อมีคำสั่งคำว่า new

var adam = new Person();

เพราะว่าตัว constructor ยังคงเป็น function หากคุณต้องการหามันล่ะ ? คงไม่ง่ายใช่ไหมมันมีข้อแนะนำว่าให้ตัวชื่อ function constructor เป็นตัวอักษรตัวใหญ่ตัวแรกก่อน และใช้ตัวเล็กสำหรับพวก function หรือ method ธรรมดาเพื่อจะได้แยกออก

function MyConstructor() {...} 
function myFunction() {...}

Separating Words

มีสองแบบที่คุ้นเคยอย่างแรกคือ camel คือใช้ชื่อตัวแปรหรือ function สลับตัวเล็กใหญ่เหมือนหลังอูฐอีกแบบคือ snake ใช้ underscore สำหรับแบ่งคำ

ตัวอย่างของ camel เช่น myDate, myFunction

ตัวอย่างของ snake เช่น my_date, old_company_name

Other Naming Patterns

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

var PI = 3.14, MAX_WIDTH = 800;

และยังมีการใช้ underscore สำหรับบอกว่าตัวแปรหรือ method ไหนเป็น private ด้วยเช่น

var person = {
    getName: function () {
        return this._getFirst() + ' ' + this._getLast();
    },

    _getFirst: function () {
        // ...
    },
    _getLast: function () {
        // ...
    }
};

Writing Comments

การเขียน comment สำหรับโค้ดให้เขียนให้ตัวเองเข้าใจเลยว่ามันทำงานแบบไหนอย่างไร เขียนแบบว่าคนไม่เคยรู้เรื่องเลยมาอ่านแล้วเข้าใจว่ามันทำอะไร ทำงานแบบไหน บ่อยครั้งที่เราต้องมานั่งทำความเข้าใจว่าโค้ดชุดนี้ทำงานแบบไหนอย่างไร มันทำให้เราสูญเสียเวลามากๆ

แต่ก็ไม่ใช่ว่าจะ comment ทุกบรรทัดทุกตัวแปร นั่นก็เวอร์ไปแต่ควรจะมีเอกสารสำหรับ function ใส่อะไรเข้าไปได้อะไรออกมา ให้คิดว่าการ comment คือข้อแนะนำสำหรับคนที่มาอ่านโค้ด เพราะคนที่มาอ่านนั้นต้องการความเข้าใจว่าโค้ดเราทำอะไร ไม่ใช่ว่าต้องการรู้ว่ามีตัวแปรอะไรแต่ละ function ชื่ออะไร เพื่อความรวดเร็วสำหรับคนอื่นเช่น ส่วนตรงนี้เกี่ยวกับเรื่อง edit หากเราไม่ได้จะแก้ไขเกี่ยวกับเรื่อง edit จะได้ข้ามๆไปเลย

Credit

source : http://net.tutsplus.com/tutorials/javascript-ajax/the-essentials-of-writing-high-quality-javascript/

Loading

เป็นโปรแกรมเมอร์ที่ตามหาคุณค่าของชีวิตและความฝันในวัยเด็ก ชอบเล่นเกม เรียนรู้ทุกอย่าง ชอบเจอคนใหม่ๆ งานสังคมทุกชนิด ออกกำลังกายในวันว่าง อ่านหนังสือ มีเว็บรีวิวหนังสือด้วย www.readraide.in.th