ฉบับที่ 3

รู้จัก Composition over Inheritance หลักการออกแบบคลาสให้ยืดหยุ่น

บทความ
Nuttavut Thongjor
แหล่งที่มา

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

เฟรมเวิร์กหลายตัวมักนิยมให้โปรแกรมเมอร์สร้าง model ด้วยการสืบทอดจากคลาสที่ทำหน้าที่เข้าถึงฐานข้อมูล เช่น เราต้องการสร้างหนังสือจำเป็นต้องสืบทอดจาก ActiveRecord::Base ใน Ruby on Rails

Ruby
1class Book < ActiveRecord::Base
2end

จากนั้นเราจึงเข้าถึงฐานข้อมูลที่นิยามการทำงานไว้ในคลาสของ ActiveRecord::Base ผ่าน Book ได้

Ruby
1# ตัวอย่างการใช้งาน
2# สร้าง book ขึ้นมาโดยมี title เป็น BabelCoder: The first book!
3book = Book.create(title: 'BabelCoder: The first book!')
4# อัพเดท title ก็ได้
5book.update(title: 'Foo-Bar')
6# เข้าถึงข้อมูลใน record นี้ก็ได้
7book.title

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

ในบทความจะแสดงให้เห็นปัญหาต่าง ๆ ของการออกแบบที่ผิด และนำเสนอวิธีที่ดีกว่าด้วยหลักการของ Composition over Inheritance ครับ

สร้างเว็บให้มีประสิทธิภาพ ด้วย 12 บัญญัติจาก Twelve Factor App

บทความ
Nuttavut Thongjor
แหล่งที่มา

Twelve Factor App เป็นหลักการที่เหมาะกับการพัฒนาแอพพลิเคชันใดๆ โดยเฉพาะอย่างยิ่งแอพพลิเคชันที่มีการให้บริการผ่านอินเตอร์เน็ต นิยามโดย Adam Wiggins ผู้ร่วมก่อตั้งของ Heroku, บริการกลุ่มเมฆชื่อดังที่แม้แต่เด็กอนุบาลหมีน้อยยังรู้จัก ประกอบด้วยกฎเหล็กทั้งสิ้น 12 ประการคือ

  1. Codebase: จัดเก็บแอพพลิเคชันในหนึ่ง Codebase เท่านั้น ส่วนจะ Deploy กี่ครั้ง ถามใจเธอดู~
  2. Dependencies: ระบุ package และเลขเวอร์ชันที่ใช้งานในแอพอย่างชัดเจนใน Codebase
  3. Config: เก็บ config ต่างๆใน Environment Variables
  4. Backing services: Backing services ต้องพร้อมสำหรับการถูกแทนที่ โดยที่โค้ดของเรายังเหมือนเดิม
  5. Build, release, run: แบ่งการ Deploy ออกเป็น Build, Release และ Run Stages อย่างชัดเจน
  6. Processes: อย่าจัดเก็บข้อมูลบนโปรเซส แต่จงใช้บริการ Backing Services แทน
  7. Port binding: แอพพลิเคชันของเราจะเป็นบริการที่ต้องให้บริการผ่าน Port เท่านั้น
  8. Concurrency: ขยายระบบด้วยการใช้ Process Model
  9. Disposability: แอพพลิเคชันเรา Startup Time ต้องไว และมีการจัดการข้อมูลให้เรียบร้อยเมื่อมีการ Shutdown
  10. Dev/prod parity: ทำสภาพแวดล้อมของ Development, Staging และ Production ให้เหมือนกันมากที่สุด
  11. Logs: ทำ Logs ให้เป็น Event Streams
  12. Admin processes: เตรียมคำสั่งสำหรับงานแอดมินเพื่อสร้างโปรเซสที่ใช้ Release เดียวกับแอพพลิเคชันเรา

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

CSS Grid Layout คืออะไร? รู้จักมาตรฐานการออกแบบเลย์เอาท์ใน 2 มิติกันเถอะ!

บทความ
Nuttavut Thongjor
แหล่งที่มา

Flexbox เป็นมาตรฐานการจัดเลย์เอาท์ในมิติเดียว จึงเกิดมาตรฐานสำหรับการจัดเลย์เอ้าท์ในสองมิติคือ CSS Grid Layout

Grid Items

CSS Grid Layout ช่วยให้งานออกแบบเว็บง่ายขึ้นด้วยการแบ่งพื้นที่ออกเป็นส่วนๆ พื้นที่หน้าจอจะถูกแบ่งได้ก็ต่อเมื่อมีเส้นตัดผ่านจากบนลงล่างและจากซ้ายไปขวาเกิดเป็นลักษณะตารางที่เรียกว่า Grid หากเปรียบเลย์เอาท์ของเราเป็นกล่อง เราจะเรียกกล่องใบนี้ว่าเป็น Grid Container ที่มีสิ่งของข้างในเป็น Grid Item วางตัวไหลไปตามการจัดวางแบบ Grid ของเรา แต่ละเส้นที่ตัดผ่านพื้นที่ต่างๆภายในกล่องของเราจะเรียกว่า Grid Line โดยพื้นที่ที่เกิดจากการตัดกันของ Grid Column กับ Grid Row เรียกว่า Grid Cell

อาศัยนิยามข้างต้นเราสามารถออกแบบหน้าจอเช่นนี้ได้ด้วยการกำหนดค่าต่าง ๆ ของ Grid ดังนี้

HTML
1.container {
2 /*
3 ประกาศให้เป็น Grid Container
4 display ของเรานอกเหนือจาก grid แล้ว
5 ยังสามารถใช้ inline-grid และ subgrid ได้ด้วย
6 เราจละที่จะพูดถึงในบทความนี้
7 */
8 display: grid;
9 /*
10 โดย Grid ของเราแบ่งออกเป็น 3 แถว
11 แถวที่1: สูง 50px
12 แถวที่2: สูง 200px
13 แถวที่3: สูง 50px
14 */
15 grid-template-rows: 50px 200px 50px;
16 /*
17 ให้ Grid ของเราแบ่งออกเป็น 2 คอลัมภ์
18 คอลัมภ์ 1: กว้าง 200px
19 คอลัมภ์ 2: กว้าง 500px
20 */
21 grid-template-columns: 200px 500px;
22 color: #FFF;
23 font-weight: bold;
24 text-align: center;
25}
26
27.container > * {
28 padding: 10px;
29}
30
31.header {
32 background: #8CC152;
33}
34
35.main {
36 background: #F6BB42;
37}
38
39.sidebar {
40 background: #E9573F;
41}
42
43.footer {
44 background: #D770AD;
45}

Grid

คำอธิบายฉบับยาวและตัวอย่างเพิ่มเติมสามารถอ่านได้จากบทความฮะ

พื้นฐาน ES2015 (ES6) สำหรับการเขียน JavaScript สมัยใหม่

บทความ
Nuttavut Thongjor
แหล่งที่มา

ประวัติศาสตร์อันยาวนานของ JavaScript ได้รับการปฏิรูปครั้งใหญ่อีกครั้งใน ES2015 หรือ ES6 รวมหลายฟีเจอร์ที่ช่วยให้นักพัฒนามีความสุขมากขึ้น ได้แก่

จาก var สู่ let และ const

ES2015 ได้เพิ่ม let และ const ที่เป็น block-scoped หมายถึงประกาศตัวแปรอยู่ในบลอคไหนก็จะอยู่แค่ในบลอคนั้น ไม่เสนอหน้าดีดตัวเองออกไปใกล้ฟังก์ชันแบบที่ var ทำ

ES2015 Module

JavaScript ไม่เคยจัดการโมดูลได้ด้วยตัวเองมาก่อนต้องทำผ่านไลบรารี่อย่าง CommonJS หรือ AMD การมาของ ES2015 มาพร้อมกับการสนับสนุนการทำงานกับโมดูลในตัว ตอนนี้คุณสามารถใช้ ES2015 เพื่อ import/export ของจากไฟล์หนึ่งไปอีกไฟล์หนึ่งได้แล้ว

Destructuring

Destructuring เป็นฟีเจอร์สำหรับการดึงส่วนของข้อมูลออกมาทำให้เราเขียนโค๊ดได้ง่ายขึ้น

JavaScript
1let person = {
2 age: 24,
3 gender: 'male',
4 name: {
5 firstName: 'firstName',
6 lastName: 'lastName'
7 }
8}
9
10// ถ้าเราต้องการค่าเหล่านี้ออกจากอ็อบเจ็กต์ ต้องมาประกาศตัวแปรแบบนี้
11let age = person.age
12let genger = person.gender
13let name = person.name
14let firstName = name.firstName
15let lastName = name.lastName
16
17// หากใช้ Destructuring จะเหลือแค่นี้
18let { age, gender, name } = person
19let { firstName, lastName } = name
20
21// แต่ในความเป็นจริงแล้ว name เป็นเพียงแค่ทางผ่าน
22// เราไม่ต้องการมัน เราต้องการแค่ firstName และ lastName
23// จึงใช้ Destructuring ซ้อนเข้าไปอีกครั้ง
24// เพียงเท่านี้ตัวแปร name ก็จะไม่เกิดขึ้นมาให้รำคาญใจ
25let { age, gender, name: { firstName, lastName } } = person

รู้จักกับ Spread Operator

Spread Operator หรือผมขอเรียกมันง่ายๆว่าเครื่องหมายแตกตัวแล้วกัน เป็นจุดไข่ปลาสามจุด (…) ที่เอาไปวางหน้าอาร์เรย์หรืออ็อบเจ็กต์แล้วมีผลทำให้เครื่องหมายที่ครอบมันอยู่หลุดออก ดูตัวอย่างกันเลย

JavaScript
1let obj1 = { a: 1, b: 2 }
2let obj2 = { c: 3, d: 4 }
3console.log({ ...obj1, ...obj2 }) // {"a":1,"b":2,"c":3,"d":4}
4
5let arr1 = [1, 2, 3]
6let arr2 = [4, 5, 6]
7console.log([...arr1, ...arr2]) // [1,2,3,4,5,6]

สารพัดวิธีแบบใหม่ในการจัดการกับฟังก์ชัน

ES2015 มาพร้อมกับความสามารถในการจัดการฟังก์ชันที่มากขึ้นดังนี้

  • Arrow Functions
  • Default Values
  • Named Parameters
  • Rest Parameters

และยังมีส่วนอื่น ๆ อีกมาก เพื่อน ๆ สามารถอ่านเพิ่มเติมได้ตามลิงก์ของบทความครับ

ข้อแตกต่างของ bind, apply และ call ใน JavaScript กับการใช้งาน

บทความ
Nuttavut Thongjor
แหล่งที่มา

เราทราบกันดีว่า JavaScript มีคีย์เวิร์ดชื่อ this ที่สร้างความลำบากใจเมื่อใช้งานเพราะ this ตัวนี้มีความหมายขึ้นอยู่กับคนเรียกใช้ กล่าวคือ this จะชี้ไปที่ผู้เรียกหรือผู้อ้างถึงฟังก์ชัน พิจารณาตัวอย่างต่อไปนี้ครับ

JavaScript
1const obj = {
2 firstName: 'Nuttavut',
3 lastName: 'Thongjor',
4 getFullName() {
5 // firstName และ lastName มาจาก obj
6 return `${this.firstName} ${this.lastName}`
7 }
8}
9
10console.log(obj.getFullName()) // Nuttavut Thongjor

ในตัวอย่างนี้ดูตรงไปตรงมามากครับ obj เป็นผู้เรียกใช้ฟังก์ชัน getFullName ดังนั้น this ในที่นี้จึงหมายถึงตัว obj เอง ทำให้เมื่อเรากล่าวถึง this.firstName จึงหมายถึง firstName ของ obj

แล้วถ้าเป็นตัวอย่างนี้หละ?

JavaScript
1const print = (fn) => {
2 // เรียกใช้งาน getFullName ที่ส่งเข้ามา
3 console.log(fn())
4}
5
6const obj = {
7 firstName: 'Nuttavut',
8 lastName: 'Thongjor',
9 getFullName() {
10 return `${this.firstName} ${this.lastName}`
11 }
12}
13
14print(obj.getFullName) // Cannot set property 'firstName' of undefined

เราสร้างฟังก์ชันชื่อ print ขึ้นมาเพื่อรับฟังก์ชั่นเข้ามาอีกทีในชื่อของ fn จากนั้นจึงทำการเรียกฟังก์ชันที่ส่งเข้ามา พบว่าการเรียกฟังก์ชันนี้ไม่ได้อ้างอิงจากอ็อบเจ็กต์อื่นใดเหมือนกับตัวอย่างบน ตัวอย่างบนเราเรียก getFullName ผ่าน obj แต่ตัวอย่างนี้เราเรียก fn ที่ส่งเข้ามาลอยๆ จึงมีผลทำให้ this ว่างเปล่า

เมื่อเป็นเช่นนี้เราจึงต้องใช้ bind apply หรือ call เพื่อบ่งบอกว่าค่า this คืออะไร โดยแต่ละตัวก็มีวิธีการใช้ที่แตกต่างกัน

สำหรับ bind เราสามารถส่งผ่านอ็อบเจ็กต์เข้าไปในฐานะที่บ่งบอกว่านี่คือค่า this ได้ เช่น

JavaScript
1const print = (fn) => {
2 console.log(fn())
3}
4
5const obj = {
6 firstName: 'Nuttavut',
7 lastName: 'Thongjor',
8 getFullName() {
9 console.log(this)
10 return `${this.firstName} ${this.lastName}`
11 }
12}
13
14// bind this เข้ากับ obj
15print(obj.getFullName.bind(obj))

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

JavaScript
1const obj = {
2 name: 'Nuttavut Thongjor',
3 sayHello(day) {
4 console.log(`Hi, ${this.name}. Today is ${day}.`)
5 }
6}
7
8const obj2 = {
9 name: 'Somkiat'
10}
11
12obj.sayHello('Mon') // Hi, Nuttavut Thongjor. Today is Mon.
13obj.sayHello.call(obj2, 'Wed') // Hi, Somkiat. Today is Wed.
14
15// ต้องส่งเป็นอาร์เรย์
16obj.sayHello.apply(obj2, ['Fri']) // Hi, Somkiat. Today is Fri.

ข้อมูลเพิ่มเติมอ่านได้จากบทความครับ

เข้าใจ Web Security: จัดเก็บ JWT ไว้ใน local storage หรือ cookies ดี?

บทความ
Nuttavut Thongjor
แหล่งที่มา

สำหรับการสร้าง API เรามักนิยมใช้ Stateless Token เช่น JWT ในการทำ Authentication (Token-based authentication) โดย token ประเภทนี้จะไม่มีการจัดเก็บในฝั่งเซิฟเวอร์ แต่ยังจำเป็นต้องจัดเก็บทางฝั่งไคลเอ็นต์ เพื่อใช้ส่งไปกับรีเควสให้ทางเซิฟเวอร์ทราบว่าเราเป็นใคร เมื่อ access token นั้นยังต้องจับเก็บทางฝั่งไคลเอ็นต์ (browser) คำถามจึงเกิดขึ้นว่าเราควรจัดเก็บไว้ใน local storage / session storage หรือ cookies ดีกว่ากัน?

บทความนี้ได้นำเสนอข้อดีข้อเสียของการจัดเก็บ token ไว้ใน web storage และ cookies ดังนี้

Web Storage เช่น local storage

Web Storage นั้นใช้ JavaScript ในการเข้าถึงดังนั้นถ้าผู้ไม่หวังดีสามารถเข้าถึงการใช้ JavaScript ได้ก็สามารถดูด token ของเราไปใช้งานได้ ดังนั้นการใช้ web storage จึงอาจเกิดการโจมตีแบบ Cross-Site Scripting Attacks ได้ หากเราต้องการใช้ web storage จริง ๆ ควรใช้ HTML Sanitizer แปลงโค้ดที่ผู้ไม่หวังดีแอบฝังในเว็บของเราให้หมดสิทธิ์ทำงานได้ก่อน และควรอนุญาตให้เฉพาะสคริปต์ที่เราเชื่อใจเท่านั้นทำงานได้ผ่านการตั้งค่าด้วย Content Security Policy

Cookies

การจัดเก็บ token ด้วยคุกกี้นั้นป้องกัน XSS ได้ด้วยการใช้คุกกี้ประเภท HttpOnly Cookies แต่ปัญหาของการใช้คุกกี้ยังอาจเจอการโจมตี้ด้วย Cross-Site Request Forgery โดยเราสามารถป้องกันได้ด้วยการทำ Double Submit Cookie หรือใช้ Same-Site Cookies ก็ได้เช่นกัน

รายละเอียดเพิ่มเติมอ่านได้จากบทความครับ