[แปล] สรุปการเขียน Javascript ที่ดี ตอนจบ

javascript การเขียนโค้ดที่ดีมาถึงตอนที่ 2 กันแล้วครับเราไปดูกันต่อเลย หากว่าใครยังไม่เคยอ่านตอนที่ 1 แนะนำไปทางนี้เลยจ้า [แปล] สรุปการเขียน Javascript ที่ดี ตอนที่ 1 บทความนี้อยากให้เพื่อนๆได้แชร์กันนะครับหากผมมีการแปลอะไรที่ผิดพลาดก็รบกวนแนะนำกันได้ที่ comment นะครับผมเอาล่ะ ! ไปดูกันต่อเลย

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 : //net.tutsplus.com/tutorials/javascript-ajax/the-essentials-of-writing-high-quality-javascript/

 

 

ฝากข้อคิดเห็น

This site uses Akismet to reduce spam. Learn how your comment data is processed.