Basic Programming กับระบบต่อสู้ใน Final Fantasy 12

final_fantasy_xii_box_art

Final Fantasy XII (12) นั้นมีระบบการต่อสู้ที่แตกต่างจากภาคอื่นๆอย่างสิ้นเชิง โดยส่วนตัวผมเพิ่งได้เล่นจริงๆจังๆจนจบก็ตอนที่ Remastered ลงบน PS4 นี่แหละครับในชื่อ Final Fantasy XII : The Zodiac Age ซึ่งแน่นอนวันนี้เราจะมาพูดถึงระบบต่อสู้ของเกมนี้กัน

Gambits เป็นชื่อของระบบต่อสู้ในเกม Final Fantasy 12 ซึ่งถ้าใครเป็น Programmer มาจะค่อนข้างคุ้นเคยมากเลยครับ เพราะนี่มันเป็น Basic Programming ชัดๆ

qhdvcpy0poow2saaenrr

จากรูปข้างบนนั้นระบบ Gambits นั้นจะเป็นการตั้ง programให้ตัวละครทำ action ต่างๆ ตามเงื่อนไข เช่น ถ้าศัตรูบินอยู่ให้โจมตี  ถ้าทีมเราพลังต่ำกว่า  30% ให้ใช้เวทย์มนต์ฟื้นหลัง ถ้าทีมเราติดพิษให้ใช้เวทย์แก้ผิด เป็นต้น หรือเราเรียกอีกอย่างนึงว่าการตั้งค่า AI (หรือ Bot?)

โดยที่ระบบนี้จะทำงานตามเงื่อนไขจาก บนลงล่าง หมายความว่าถ้าเหตุการณ์เข้าเงื่อนไขแรกก็ทำ action นั้นเลยโดยที่จะไม่ทำเงื่อนไข้ด้านล่างต่อ ซึ่งถือว่าเป็นการจบหนึ่งเทิร์นของตัวละครนั้นๆ

ที่นี้ถ้าเราลองเลือก 3 ตัวแรกมาลองแปลงเป็น Code ง่ายๆกัน มันก็น่าจะเป็นประมาณนี้ครับ

Screen Shot 2560-12-10 at 3.32.40 PM.png

 

if( enemy.IsFlying() ){
 me.Attack(enemy);
}
else if( team.IsHPbelow(30) ){
 let m = team.getIsHPbelow(30);
 me.Curaja(m);
}
else if(team.IsHPbelow(50) ){
 let m = team.getIsHPbelow(50);
 me.Curaga(m);
}

ซึ่งตีความลำดับของ Script ได้ว่า

  1. ถ้าศัตรูบินได้ ตีมันก่อนเลย
  2. ถ้าทีมเรามีคนพลังชีวิตต่ำกว่า 30% ให้ใช้เวทย์ Curaja
  3. ถ้าทีมเรามีคนพลังชีวิตต่ำกว่า 50% ให้ใช้เวทย์ Curaga (ซึ่งเป็นเวทย์ที่ฟื้นพลังชีวิตได้สูงกว่า Curaja)

ก็จะเห็นได้ว่าการเล่นเกม(บางเกม) ก็อาจจะช่วยให้เรารู้จัก Programming ได้นิดหน่อยนะครับสำหรับคนที่ไม่ได้มาสายนี้ แต่สำหรับคนที่เป็น Programmer/Developer ก็ยิ่งทำให้เข้าใจระบบของเกมได้ง่ายขึ้นทำให้เล่นได้ง่ายและสนุกไปกับมันมากขึ้น

 

เรื่องของตัว D (Dependency Inversion) ในหลักการ SOLID สำหรับคนเขียน OOP

Screen Shot 2560-08-27 at 7.02.07 PM

มาถึงตอนสุดท้ายของ SOLID กันแล้วนะครับ ตัวสุดท้ายตัว  D ซึ่งมาจาก Dependency Inversion Principle (DIP) โดยที่ส่วนตัวผมเองนั้นคิดว่าหลักการข้อนี้ค่อนข้างมีประโยชน์และน่าจะเป็นที่รู้จักกันเยอะระดับนึงอยู่แล้วในโลกของการพัฒนา Software ช่วงนี้

DIP นั้นกล่าวว่า

Class ที่มีการเรียกใช้  Class อื่นๆนั้นไม่ควรที่จะมีการอ้างอิงถึงตัว Object ของ Class ที่ถูกเรียกตรงๆ แต่ควรที่จะเรียกผ่าน Interface หรือ Pointer ของ Base Class

เป้าหมายที่สำคัญของหลักการนี้เพื่อทำให้ Code นั้นมีการผูกติดกันให้น้อยที่สุดกับสิ่งที่เป็น Dependency ของ Class เรา

คำว่า Dependency ในกรณีนี้ก็คือ เช่น เรามี Class SmartPhone แล้วมี Data Member เป็น Object ที่สร้างจาก Class BluetoothDevice เช่น  Code ตัวอย่างนี้

class BluetoothDevice {
 public connect(){ /* do low level network tasks */}
 public scan(){}
} 

class SmartPhone {
 private bt : BluetoothDevice; // มีการถือ object BluetoothDevice ใน Class
 public init(){
   this.bt = new BluetoothDevice(); // มีการสร้าง Object ใน Class
 }
 public connectBluetooth(){
   this.bt.connect();
 }
}

จะเห็นว่า Code ข้างต้นนี้ออกแบบได้ถูกหลัก Single Responsibility แล้วเพราะเราทำการแยก Class ของ SmartPhone และ BluetoothDevice ออกจากกันตามหน้าที่ 🙂 ตามรูปด้านล่าง

Screen Shot 2560-08-28 at 11.10.51 PM

Refactor

แต่ถึงอย่างนั้นด้วย DIP เราจะทำให้ Class นี้มี Design ที่ดีขึ้นไปอีกโดยที่เราจะใช้ Interface เป็นตัวกลางในการ Reference เช่นรูปด้านล่างนี้

Screen Shot 2560-08-28 at 11.12.12 PM

ดังนั้น Code ที่ทำการ Refactor โดยการ Inversion แล้วก็จะมีหน้าตาแบบนี้แทนครับ

 interface IBluetooth {
   connect();
   scan();
 }
 class BluetoothDevice implements IBluetooth { // class นี้ทำการ implements interface
   public connect(){ 
     /* do low level network tasks */
     console.log('bluetooth connect');
   }
   public scan(){}
 } 
 class SmartPhone {
   private bt : IBluetooth; // อ้างถึง Bluetooth ผ่าน Interface ไม่ใช่ Object
   public init(bt : IBluetooth){ // มีการรับ Object ที่มี Interface IBluetooth เป็น Parameter
     this.bt = bt;
   }
   public connectBluetooth(){
     this.bt.connect();
   }
 }

จาก Code ด้านบนนี้จะเห็นว่า Class SmartPhone นั้นไม่ได้มีการอ้างอิงไปถึง Object ของ Class BluetoothDevice ตรงๆแล้วแต่จะใช้ Interface แทนครับ ทำให้การที่จะเรียกใช้ Method ที่ถูกต้องของ BluetoothDevice นั้นจะต้องมีการสร้าง Instance ของมันให้ถูก

.

.

Code ของฝั่งที่จะสร้าง Object หรือ Instance จาก Class SmartPhone จึงต้องเปลี่ยนไปเป็นเช่นนี้

let sp = new SmartPhone();
sp.init(new BluetoothDevice()); // ต้องสร้าง Object ของ BluetoothDevice ส่งไปให้
sp.connectBluetooth();

ซึ่งการทำการส่ง Object ที่เป็น Dependency ของอีก Class นึงเข้าไปแบบนี้คือการทำสิ่งที่เรียกว่า Dependency Injection ( DI ) นั่นเอง

.

Testable Code

ข้อดีที่เราจะได้รับจากการทำ DIP ก็คือการทำให้ Code ของเรานั้น Unit Test ได้ง่ายขึ้น

ลองคิดดูครับว่าถ้า Class BluetoothDevice ตัวจริงนั้นต้องไปต่อ Network จริงๆ แล้วเราจะเขียน Unit Test ของ Class SmartPhone ได้ยังไง

.

การทำ DIP นั้นจะทำให้เราสามารถ Inject Mock Dependency ลงไปได้เช่นดังตัวอย่างในรูปนี้

Screen Shot 2560-08-28 at 11.27.36 PM.png

class MockBluetoothDevice implements IBluetooth {
 public connect(){
   console.log('just mock connect');
 }
 public scan(){
   console.log('just mock scan');
 }
}

Code ตอนเขียน UniTest

let sptest = new SmartPhone();
sptest.init(new MockBluetoothDevice()); // ส่งตัวปลอม(Mock) ของ BluetoothDevice ไป
sptest.connectBluetooth(); // ไม่ต้องไปต่อ Network จริงๆ

ก็จะเห็นว่า DIP นั้นช่วยทำให้ Code เราเขียน Test ได้ง่ายขึ้น แต่ทั้งนี้ทั้งนั้นทุกวันนี้เราจะเห็นว่ามี Framework ที่ช่วยทำ Dependency Injection มากมายเลยนะครับ แต่ผมอยากให้ทุกคนทราบกันว่านี่คือเบื้องหลังของมันรวมถึงที่มาที่ไปด้วย

เอาล่ะ มาถึงตรงนี้เป็นอันว่าจบหลักสูตร SOLID กันแล้วนะครับ สำหรับคนที่คิดว่ามันเยอะไปสำหรับ 5 หลักนี้ ผมก็คงแนะนำให้ทบทวน 2 ตัว คือ S กับ D ละกันครับ 🙂 และหวังว่าซีรี่ย์นี่จะเป็นประโยชน์กับหลายๆคนนะครับ

SOLID The Series:

 

 

เรื่องของตัว I (Interface segregation) ในหลักการ SOLID สำหรับคนเขียน OOP

Screen Shot 2560-08-23 at 12.13.53 AM.png

มาถึงตอนที่ 4 ของซีรีย์ SOLID กันแล้วนะครับ กับตัว I ซึ่งตัวนี้ย่อมาจาก Interface segregation Principle (ISP)  ที่จะทำให้ Code ที่เขียนด้วยภาษา OOP นั้นดูดีมากขึ้นไปอีกขั้น 😉

ISP นั้นกล่าวถึงหลักการที่ว่า

Class ที่มา Implement เพื่อใช้งาน Interface นั้นๆไม่ควรที่จะต้องมา Implement Method ต่างๆให้ครบโดยที่อาจจะไม่ได้ใช้

ซึ่งถ้าตีความง่ายๆที่สุดเลยก็คือ เราควรอย่าไปกลัวกับการที่จะต้องสร้าง Interface ใหม่ เพราะโดนส่วนใหญ่ Developer อาจจะชอบเพิ่ม Method ใน Interface เดิม ซึ่งมันดูปลอดภัยมากกว่านั่นเอง

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

เริ่มแรกเลย

ผมมี Interface ชื่อ IPhone (ไม่ใช่ Appple iPhone นะครับ :D) ซึ่งมีด้วยกันทั้งหมด 6 Methods

interface IPhone {
        call();
        ring();
        installApp();
        addContact();
        connectWifi();
        connectMobile();
    }

ที่นีผมก็ลองสร้าง Class ชื่อ SmartPhone ที่จะมา Implement ตัว Interface IPhone ดูครับ

class SmartPhone implements IPhone {
 public call(){ /* do something /* }
 public ring(){/* do something /* }
 public installApp(){/* do something /* }
 public addContact(){/* do something /* }
 public connectWifi(){/* do something /* }
 public connectMobile(){/* do something /* }
}

เนื่องจากว่า SmartPhone นั้นมีความสามารถครบก็ดูโอเคดีครับที่เราจะออกแบบ Interface IPhone แบบนี้

แต่ถ้าผมมีอีก Class ชื่อ FeaturePhone ที่ต้อง Implement IPhone แต่ไม่สามารถ install App หรือ ต่อ wifi ได้ล่ะ ด้วยมันก็จะเป็นแบบนี้

class FeaturePhone implements IPhone {
 public call(){}
 public ring(){}
 public installApp(){
     throw new Error('Feature Phone does not support apps..');
 }
 public addContact(){}
 public connectWifi(){
     throw new Error('Feature Phone does not support using Wifi..');
 }
 public connectMobile(){}
 }

และถ้าเรามีอีก Class ชื่อ BasicTablet ที่มันทำอะไรไม่ค่อยได้นอกว่า install App กับต่อ wifi  มันก็จะเป็นแบบนี้

 BasicTablet implements IPhone {
 public call(){
    throw new Error('Basic Tablet does not support call..');
 }
 public ring(){
    throw new Error('Basic Tablet does not support ring..');
 }
 public installApp(){}
 public addContact(){
    throw new Error('Basic Tablet does not support contacts..');
 }
 public connectWifi(){}
 public connectMobile(){
    throw new Error('Basic Tablet does not support using mobile..');
 }
}

ซึ่งจากตัวอย่างทั้ง 2 Class นั้นก็จะเห็นได้ว่า การออกแบบ Interface แบบนี้ดูแล้วมันค่อนข้างกว้างไป

.

.

เริ่มการ Refactor

แน่นอนว่า Interface IPhone ที่สร้างไว้ตั้งแต่ต้นนั้นมันกว้างไป เทคนิคที่จะใช้ครั้งนี้คือ Extract Interface ซึ่งก็คือทำการแบบ Interface IPhone ออกมาเป็น Interface ย่อยๆตามกลุ่มของ Method ที่เรามี

ซึ่งถ้าเราสังเกตุดูจาก Interface IPhone นั้นจะมี Method อยู่ 3 กลุ่มคือ

  1. โทร พื้นฐาน
  2. การใช้งาน App
  3. การเชื่อมต่อ

ดังนั้นหลักจากที่ทำการ Extract Method แล้วจะได้ผลแบบนี้ครับ

interface IPhone {
  call();
  ring();
  addContact();
}

interface ISmartFeatures {
  installApp();
}

interface IMobileConnect {
  connectMobile();
  connectWifi();
}

ที่นี้ Class SmartPhone ของเราก็ต้องแก้ Code ให้มาเป็นแบบนี้แทน

class SmartPhone implements IPhone, ISmartFeatures, IMobileConnect {

  public call(){}
  public ring(){}
  public installApp(){}
  public addContact(){}
  public connectWifi(){}
  public connectMobile(){}

}

ส่วน Class FeaturePhone ก็จะเป็นแบบนี้ ซึ่งก็ยังต้องมีการ handle Method ที่ไม่ Support อยู่แต่ว่าก็จะน้อยลงกว่าตอนแรก

class FeaturePhone implements IPhone, IMobileConnect {

  public call(){}
  public ring(){}
  public addContact(){}
  public connectMobile(){}
  public connectWifi() {
    throw new Error('Feature Phone does not support using Wifi..');
  }

}

ส่วน Class BasicTablet นั้นก็จะคล้ายๆกับ FeaturePhone ก็คือ

class BasicTablet implements ISmartFeatures, IMobileConnect {

  public installApp(){}
  public connectWifi(){}
  public addContact(){}
  public connectMobile(){}
  public connectMobile(){ 
    throw new Error('Basic Tablet does not support using mobile..'); 
  }

}

สรุป

เพื่อนๆก็จะเห็นว่าการทำ Interface Segregation นั้นจะทำให้เราสามารถออกแบบ Interface ได้ดียิ่งขึ้นและไม่เป็นการทำให้ Client ของ Interface นั้นต้องมาเขียน  Code  เพิ่มโดยที่ไม่ได้ Handle อะไรเลย รวมถึงทำให้ Class และ Interface ของเราดูแลได้ง่ายขึ้นและเจาะจงการการทำงานแต่ละเรื่องมากขึ้น

SOLID The Series:

เรื่องของตัว L (Liskov Substitution) ในหลักการ SOLID สำหรับคนเขียน OOP

Screen Shot 2560-08-23 at 12.09.41 AM.png

มาถึงหลักการข้อ 3 ของ SOLID กันแล้วนะครับ สำหรับตัว L ซึ่งย่อมากจาก Liskov Substitution Principle (LSP)  ซึ่งกล่าวด้วยความ Abstact อีกแล้วว่า

Function ที่ใช้ reference หรือ Object ของ คลาสแม่ (Base Class) นั้นควรจะต้องสามารถใช้ Object ของคลาสลูกมันได้โดยไม่ต้องรู้รายละเอียดหรือข้อจำกัดของมัน

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

…เอาล่ะ เราเป็น Developer เราก็มาลองดู Code กันดีกว่าครับ

*เช่นเดิม Code เขียนด้วย TypeScript

ก่อนอื่นเลยผมมี Class Phone ซึ่งมี  2 Methods คือ call() กับ installApp()

    class Phone {
        public call(){
            console.log('Make call...');
        }
        public installApp(){}
    }

ผมก็ได้ทำการเพิ่ม Class มาอีก 2 Classes คือ SmartPhone กับ FeaturePhone โดยการสืบทอดมาจาก Phone

แต่บังเอิญว่า FeaturePhone ดัน install App ไม่ได้เลยต้องทำการ Throw Exception ออกไป

    class SmartPhone extends Phone {
        public installApp(){
            console.log('SmartPhone install the app...');
        }
    }

    class FeaturePhone extends Phone {
        public installApp(){
            throw new Error('Feature Phone does not support install app...');
        }
    }

ถ้าผมที่ Code ฝั่งที่เรียกใช้ Phone จะเป็นแบบนี้ครับ

let phone = new Phone();
phone.installApp(); // ก็  Work ดี

let smPhone = new SmartPhone();
smPhone.installApp(); // ก็ Work ดี

let ftPhone = new FeaturePhone();
ftPhone.installApp(); // พัง เกิด Exception

ซึ่งจะเห็นได้ว่า ผมต้องมาแก้เป็นแบบนี้ เป็นกรณีพิเศษ เช่นต้องใช้ try/catch

try {
    ftPhone.installApp();
}

และนี่แหละคือการผิดหลักการออกแบบที่ดีของหลักการ LSP เพราะเราไม่สามารถเรียกใช้ Class ลูก (FeaturePhone) ได้แบบที่เหมือนเรียก Class แม่ (Phone) ซึ่งการที่เราจะรู้ได้ว่ามัน Throw Exception นั้นก็ต้องไปดู  Code ที่ถูก Implement ใน Class นั้น ซึ่งถ้าเป็น  Code ที่อยู่ใน Libary หรือพัฒนาโดยคนอื่นล่ะ มันก็คงจะดูไม่ได้ง่ายๆใช่มั้ยครับ

การจะแก้ไขปัญหานี้ที่ถูกต้องตามหลัก OO คือต้อง Refactor โดยใช้เทคนิคชื่อ Push Down Method ครับ (ก็คือย้าย Method ที่ไม่จำเป็นใน Class แม่ลงไปที่ Class ลูก)
ฉะนั้นผมจะได้ Class ใหม่แบบนี้แทน

    class Phone {
        public call(){
            console.log('Phone install the app...');
        }
    }
    
    class SmartPhone extends Phone {
        public installApp(){
            console.log('SmartPhone install the app...');
        }
    }

    class FeaturePhone extends Phone {
        // additional FeaturePhone feature e.g. multi sim cards
    }

สิ่งที่เปลี่ยนไปคือ

  1. Method installApp() ถูกย้ายลงมาที่ SmartPhone
  2. FeaturePhone จะไม่มี Method installApp() ให้เรียกแล้ว
  3. ทำให้แก้ปัญหาที่ Code ที่ใช้เรียก ftPhone.installApp() แล้วต้องใช้ try/catch ไปได้! 🙂

SOLID The Series:

 

เรื่องของตัว O (Open-Closed) ในหลักการ SOLID สำหรับคนเขียน OOP

Screen Shot 2560-08-23 at 12.08.07 AM.png

หลังจากที่พูดถึง S (หรือ Single Responsibility) ไปแล้วหลักการต่อมาก็คือ O ซึ่งย่อมาจาก  Open-Closed Principle ซึ่งเป็นหลักการที่ 2 ของ SOLID ที่มีใจความสำคัญแบบ Abstractๆ ว่า

เราควรออกแบบระบบให้ง่ายต่อการเพิ่มเติมอะไรใหม่ๆ โดยที่ไม่ต้องแก้ไขระบบเก่า (ถ้าไม่จำเป็น)

Open-Closed Principle (OCP) จะกล่าวถึงการออกแบบ Class หรือทั้งระบบให้ยืดหยุ่นมากขึ้น โดยที่เราใช้ความสามารถของ OOP มาช่วย โดยยกตัวอย่างเช่น ถ้าเราต้องเพิ่ม Feature ใหม่ให้ Class เรา จุดที่ควรจะแก้ไข Code นั้นควรจะเป็นจุดที่เหมาะสมและไม่ควรกระทบกับสิ่งที่ไม่ควรจะแก้ไข

อธิบายเพิ่มก็ยัง งง อีก…งั้นเรามาดูตัวอย่างกันดีกว่าครับ

เช่นเดิม ผมมี Class SmartPhone ซึ่งมี Constructor ที่มีการ Set ค่าต่างๆให้กับ Object ที่กำลังถูกสร้างตามชื่อ Model เช่น S10 หรือ 8s

*Code ตัวอย่างเป็น TypeScript

    
class SmartPhone {
    private phoneModel : string;
    private phoneFullName : string;
    private screenWidth : number;
    private screenHeigth : number;
    constructor(model : string){
       if(model ==='S10'){
          this.phoneModel ='S10';
          this.screenWidth =5;
          this.screenHeigth =10;
          this.phoneFullName ='Samsung Galaxy S10';
        }
        else if(model ==='8s'){
          this.phoneModel ='8s';
          this.screenWidth =4;
          this.screenHeigth =3;
          this.phoneFullName ='Apple iPhone 8s';
        }
    }
}

จาก Code นี้ถ้าผมทำการเพิ่ม Model ใหม่สิ่งที่ผมต้องทำคือผมต้องผมแก้ Contructor ของ Class SmartPhone โดยการเพิ่ม else if  แล้วทำการเพิ่ม Code ของ Model Nexus 9 เข้าไป

constructor(model : string){
       if(model ==='S10'){
          this.phoneModel ='S10';
          this.screenWidth =5;
          this.screenHeigth =10;
          this.phoneFullName ='Samsung Galaxy S10';
        }
        else if(model ==='8s'){
          this.phoneModel ='8s';
          this.screenWidth =4;
          this.screenHeigth =3;
          this.phoneFullName ='Apple iPhone 8s';
        }
        else if(model ==='Nexus 9'){
          this.phoneModel ='Nx9';
          this.screenWidth =5;
          this.screenHeigth =5;
          this.phoneFullName ='Google Nexus 9';
        }
}
       
การทำแบบนี้ไม่ผิดครับ แต่เนื่องจากเราเขียนภาษา OO และต้องการทำให้ Design ของเราเป็นไปตามหลักการ OCP ดังนั้นเราต้องทำการ Refactor Code กันครับ
ลองคิดดูก่อนนะครับว่าจะ Refactor กันยังไงดี…
.
.
.
เอาล่ะมาเริ่มกัน
เทคนิคที่เราจะใช้ในการเพิ่ม Code Quality ครั้งนี้คือการใช้ Inherithance หรือการสืบทอดนั่นเอง ดังนั้นเรามาสร้างเป็น 3 Classes โดยที่ SmartPhone จะเป็น Base Class
   class SmartPhone { // Base Class
        protected phoneModel : string;
        protected phoneFullName : string;
        protected screenWidth : number;
        protected screenHeigth : number;
        constructor(){
            this.init();
        }
        protected init(){};
    }

    class S10Phone extends SmartPhone{// Inherited จาก Base Class
        protected init(){
            this.phoneModel = 'S10';
            this.screenWidth = 5;
            this.screenHeigth = 10;
            this.phoneFullName = 'Samsung Galasy S10';
        } 
    }

    class i8sPhone extends SmartPhone{// Inherited จาก Base Class
        protected init(){
            this.phoneModel = '8s';
            this.screenWidth = 4;
            this.screenHeigth = 3;
            this.phoneFullName = 'Apple iPhone 8s';
        } 
    }

    class Nx9Phone extends SmartPhone{ // Inherited จาก Base Class
        protected init(){
              this.phoneModel ='Nx9';
              this.screenWidth =5;
              this.screenHeigth =5;
              this.phoneFullName ='Google Nexus 9';
        }
    }

ซึ่งการทำแบบนี้จะเห็นว่าถ้าเราต้องการเพิ่ม model ใหม่ๆเข้าไปเราจะไม่ต้องมาแก้ไข้ base class เลยครับแค่ทำการ Inheritance กับสร้าง Class ใหม่แทน ซึ่งนี่ก็คือหลักการของ OCP ครับ 🙂

SOLID The Series:

เรื่องของตัว S (Single Responsibility) ในหลักการ SOLID

Screen Shot 2560-08-23 at 12.04.58 AM.png

หลังจากที่ผมได้เกริ่นเกี่ยวกับหลักการ SOLID ไปแล้วนั้น วันนี้เราจะมาลองดูกันกับตัวแรกของเรา ซึ่งก็คือตัว S ซึ่งย่อมากจาก Single Responsibility หรือแปลว่า หลักการทำหน้าที่เดียวพอ ซึ่งผมจะขอใช้ตัวอย่างด้วย TypeScript ครับ

Single Responsibility Principle (SRP) นั้นกล่าวถึงการออกแบบ Class ในภาษา Object Oriented เพื่อที่จะทำให้ Class นั้นดูแลและจัดการได้สะดวกมากขึ้น

เริ่มต้น ผมมี Class ตัวอย่างที่ชื่อว่า SmartPhone

class SmartPhone {
   // properties
   private apps : Array<string>;
   private contacts : Array<string>;
   private phoneNumber : string;
   private phoneModel : string;
   private screenWidth : number;
   private screenHeigth : number;

   // methods
   public call( telNum : number){
   }
   public ring(){
   }
   public installApp(appName : string){
      this.apps.push(appName);
   }
   public launchApp(appName : string){
      let app = this.apps.filter(element => element === appName);
   }
   public addContact(contact : string){
      this.contacts.push(contact);
   }
   public getScreenPixel() : number {
      return this.screenWidth * this.screenHeigth;
   }
   public getModel() : string {
      return this.phoneModel;
   }
}

ซึ่งจาก Code ตัวอย่างนี้จะเห็นได้ว่า Class SmartPhone นี้ก็ออกแบบมาตามหลัก OO ระดับหนึ่ง ซึ่งถ้าเรามาดูรายละเอียดดีๆนั้นจะเห็นว่ามันมี 3 ส่วนด้วยกัน

  1. การจัดการ Apps
  2. การจัดการ Contact
  3. ข้อมูลของตัว SmartPhone เอง

แต่จะเห็นว่า Code นี้ยังไม่ได้ใช้หลักการของ SRP เลย ดังนั้นสิ่งที่เราต้องทำคือ เราจะมาลองเปลี่ยน Code ให้สอดคล้องกับหลักการนี้ ขั้นตอนนี้เราเรียกว่า การ Refactor 

สิ่งที่ต้องทำก็คือจะใช้เทคนิคการ  Refactor ที่ชื่อว่า Extract Class ก็คือเป็นการแยก Code ในส่วนของ Properties และ Methods ที่น่าจะรวมกันเป็น Class ใหม่ได้ออกมา

ดังนั้นเราเราจะได้ Class ใหม่มาเพิ่มอีก 3 Class คือ Apps และ Contacts

  1. Class Apps จะเอาการเก็บข้อมูล Array และ Methods การ install กับ launch มาไว้ใน Class นี้
class Apps {
   private apps : Array<string>;
   public install(appName : string){
      this.apps.push(appName);
   }
   public launch(appName : string){
      let app = this.apps.filter(element => element === appName);
   }
}

2. Class Contacts จะเอาไว้จัดการเกี่ยวกับ Contact List

class Contacts {
   private contacts : Array<string>;
   public add(contact : string){
      this.contacts.push(contact);
   }
}

3. Class PhoneInfo เราจะเอามาเก็บข้อมูลทั่วไปเช่น เบอร์โทร ชื่อรุ่น ขนาดหน้าจอ

class PhoneInfo {
   private phoneNumber : string;
   private phoneModel : string;
   private screenWidth : number;
   private screenHeigth : number;
   constructor(model : string){
      this.phoneModel = model;
      this.screenWidth =5;
      this.screenHeigth =10;
   }

   public getModel() : string {
      return this.phoneModel;
   }

   public getScreenPixel() : number {
      return this.screenWidth *this.screenHeigth;
   }
}

ซึ่งเมื่อมาถึงตรงนี้แล้วเราก็ต้องทำการแก้ Class หลักของเราก็คือ Class SmartPhone ให้มาเรียกใช้งานทั้ง 3 Classes นี้

// Smart Phone V2
class SmartPhone {
   private apps : Apps; // เรียกใช้ Class Apps
   private info : PhoneInfo; // เรียกใช้ Class PhoneInfo
   private contacts : Contacts; // เรียกใช้ Class Contacts

   constructor(){
      this.apps =new Apps();
      this.info =new PhoneInfo('S10');
      this.contacts =new Contacts();
   }

   public call( telNum : number){
   }

   public ring(){
   }

   public installApp(appName : string){
       this.apps.install(appName); // เรียกใช้ Method ของ Class Apps
   }

   public launchApp(appName : string){
      this.apps.launch(appName);// เรียกใช้ Method ของ Class Apps
   }

   public addContact(contact : string){
      this.contacts.add(contact);// เรียกใช้ Method ของ Class Contacts
   }

   public getScreenPixel() : number {
      return this.info.getScreenPixel();// เรียกใช้ Method ของ Class PhoneInfo
   }

   public getModel() : string {
       return this.info.getModel();// เรียกใช้ Method ของ Class PhoneInfo
   }
}

หลังจากทำการ Refactor เสร็จก็จะเห็นได้ว่า Class SmartPhone Verion 2 นั้นจะเป็นระเบียบมากขึ้น ไม่ต้องมาเก็บตัวแปรที่เป็น String หรือ Array ที่ไม่เกี่ยวข้องอะไรกับตัว Class นี้เองเลย แต่จะย้ายไปเก็บใน Class ย่อยๆแทนเพื่อที่จะให้ Code นั้นเหมาะสมกับ Class แต่ละ Class จริงๆ

เท่านี้เพื่อนๆก็จบหลักสูตร Single Responsibility Principle กันแล้วครับ หวังว่าจะเป็นประโยชน์กับทุกคน แล้วเจอกันตอนหน้าด้วยหลักการตัว O 🙂

หลักการ SOLID สำหรับคนที่เขียน OOP

SOLID เป็นหลักการ 5 ข้อที่จะช่วยให้ Developer ที่เขียนภาษา Object Oriented เช่นพวก C#, Java, C++) นั้นสามารถเขียน Code ได้ดูเป็นระบบระเบียบมากขึ้น รวมถึงสามารถที่จะดูแลและแก้ไขได้ง่ายและสุดท้ายทำให้พวกเราดูเป็น Profressional กันด้วย 🙂

ถึงแม้ว่าหลักการของ SOLID นั้นมีมาก็หลาย 10 ปีแล้วแต่ผมคิดว่าสำหรับนักพัฒนาที่เขียนภาษา OO ก็เป็นสิ่งที่ยังควรรู้ รวมถึงคนที่เขียนภาษาอื่นๆเช่นพวก Script หรือ Dynamic languages ก็อาจจะเอาไปประยุกต์ใช้ได้เหมือนกัน

เอาล่ะเรามาดูกันว่าหลักการ 5 ข้อนั้นย่ามาจากอะไรบ้าง

S – Single Responsibility Principle

กล่าวถึง Class หนึ่งๆนั้นควรจะมีแค่หน้าที่ ความรับผิดชอบเดียวเท่านั้น

O – Open Closed Principle

กล่าวถึง ระบบของ Software นั้นควรง่ายและเป็นระบบเปิดต่อการต่อยอดโดยที่แก้ไขระบบหลักให้น้อยที่สุด

L – Liskov substitution Principle

กล่าวถึง Object ของ Class ที่ระบบได้เรียกใช้นั้นควรจะสามารถเรียกใช้ Class ลูกๆได้โดยที่ไม่ต้องแก้ไขระบบเลย (ข้อนี้อาจจะเข้าใจยากนิดนึงนะครับ อาจจะต้องลองดูตัวอย่าง)

I – Interface segregation Principle

กล่าวถึงการแบ่งย่อย Interface ให้เป็นสัดสวนตามหน้าที่ของมัน ดีกว่ามี Interface เดียวที่มีหลายๆ Methods

D – Dependency Inversion Principle

กล่างถึงการที่ Class ต่างๆติดต่อกับอีก Class อื่นๆนั้น ควรจะติดต่อกันผ่าน Interface มากกว่า Object ของ Class นั้นๆตรงๆ

 

 

มาถึงตรงนี้เพื่อนๆที่ยังไม่รู้จัก SOLID ก็อาจจะยังไม่เข้าใจอยู่ดี ซึ่งผมคิดว่าบทความครั้งต่อๆไปนั้นจะลองมาเน้นขยายความกันในแต่ละเรื่องให้นะครับ

ref : https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)

WebAssembly เมื่อการพัฒนาเว็บเข้าสู่อีกโลกนึง

ในยุคที่มองไปทางไหนก็มีแต่การพัฒนา Software ด้วย Web Technology ไปหมดทุกทางและยิ่งเป็น Modern Web ที่มี JavaScript เป็นหัวใจสำคัญแล้วนั้น หลายๆปัญหาทางด้าน Performance ก็จะไปติดคอขวดอยู่ที่ Layer ของ JavaScript Engine ดังนั้นทางออกหนึ่งที่ทาง Community นั้นได้มองเห็นก็คือการต่อตรงกับ OS ไปเลยแทนที่จะต้องผ่านการแปล JavaScript ซึ่งนี่ก็คือหนึ่งในที่มาของ WebAssembly

WebAssembly คือการนำ Low Level Code ที่เขียนด้วย C/C++ (และอาจจะมีภาษาอื่นๆในอนาคตเพราะตอนนี้ก็มีภาษา Rust ที่ใช้ได้ด้วยเหมือนกัน) มาทำการรันภายใต้ Web Browser เพื่อให้ได้ Performance  สูงที่สุดรวมทั้งยังสามารถติดต่อกับ JavaScript ได้ด้วย ซึ่งหมายความว่ามันไม่ได้เป็นภาษาใหม่ที่จะมาแทน JavaScript แต่มันคือสิ่งที่จะช่วยยกระดับการพัฒนาเวปมากกว่าครับ และที่สำคัญโปรเจคนี้พัฒนาด้วยเจ้าใหญ่แห่งวงการทั้งนั้น เช่น Mozilla Microsoft Google Apple

เนื่องจากว่า WebAssembly นั้นค่อนข้าง Low Level มาก(แค่มีคำว่า Assembly ก็แลดูน่ากลัวแล้ว) ดังนั้นนักพัฒนาที่สนใจในเรื่องนี้ก็จำเป็นต้องรู้จักสิ่งเหล่านี้ก่อนครับ

  • Source Code แน่นอนว่าคือไฟล์ที่พวกเราเขียน Code ลงไปโดยจะแบ่งเป็น 2 ส่วนคือฝั่ง C/C++ กับฝั่ง JavaScript ครับ
  • Compiler เป็นตัวแปลงภาษา C/C++ ไปเป็น Bytes Code หรือ Machine Code ซึ่งภาษาที่ต้องใช้ Compiler นั้นจะเป็นภาษาที่มี Performance สูงกว่าภาษาพวก Script เพราะไม่ต้องแปลง Code ตอน เวลา Runtime
  • Bytes Code เป็นภาษาระดับกลางที่ทาง WebAssembly Runtime นั้นเข้าใจและทำงานติดต่อกับ OS ตรงๆ
  • Interoperability เป็นการทำงานเชื่อมกันระหว่างระบบหนึ่งกับอีกระบบหนึ่งเช่น การเรียกให้ Module ที่เขียนด้วย C++ ทำงานด้วย Code ของภาษา JavaScript หรือด้านสลับกัน

หลักการพัฒนาเว็บด้วย WebAssembly ก็คือ

  1. Developer เขียนโปรแกรมด้วย C/C++
  2. Developer ทำการ Compile C/C++ ที่เขียนไว้ให้เป็น WebAssembly Bytes Code (ไฟล์ .wasm) เพื่อให้พร้อมที่จะรันได้ใน Browser
  3. Developer ต้องเขียน JavaScript ให้ทำการโหลด .wasm ไฟล์มาไว้ใน Memory เพื่อให้ WebAssembly Runtime ที่อยู่บน Browser นั้นทำการแปลง Bytes Code ให้เป็น JavaScript Object
  4. Developer เรียกใช้งาน function ที่เขียนด้วย C/C++ ผ่าน JavaScript

ทีนี้เรามาลองดูแบบ Step by Step กัน

ก่อนอื่นเลยผมมี Code ที่เขียนด้วย C ง่ายๆคือ function การบวกเลข ช่ือไฟล์ demo.c

Screen Shot 2560-03-05 at 7.04.29 PM.png

สิ่งที่ต้องทำขั้นต่อไปคือ Compile Code ชุดนี้ให้เป็น .wasm ซึ่งเครื่องมือที่ใช้ค่อนข้างซับซ้อนสำหรับมือใหม่(อย่างผม) ซึ่งใช้ emcc (Emscriptenซึ่งเป็น Open Source โปรเจคซึ่งติดตั้งจาก ที่นี่

$emcc demo.c -Os -s WASM=1 -s SIDE_MODULE=1 -o democ.wasm

ด้วย command ข้างบนนี้เราก็จะได้ democ.wasm ซึ่งเป็นไฟล์ Bytes Code ที่ใช้งานได้กับ Browser ที่สนับสนุนแล้ว เช่น Google Chrome 57+ ขึ้นไป

หน้าตาของไฟล์ .wasm ก็เป็นแบบนี้ครับ!! อ่านไม่ออกแน่นอน เพราะเป็น Bytes Code 🙂

Screen Shot 2560-03-05 at 7.03.33 PM.png

ถัดมาเราก็มาเขียน JavaScript ให้การโหลดไฟล์ democ.wasm ซึ่งผมใช้ fetch API ครับและหลังจากนั้นเป็นการเรียกใช้งาน function

Screen Shot 2560-03-05 at 7.06.57 PM.png

จาก Code นี้ในฝั่งของ JavaScript นั้นผมขอแบ่งออกเป็น 4 ส่วนครับ

  1. โหลดไฟล์ .wasm ด้วย fetch API
  2. ทำการประกาศ imports Object เพื่อให้ตัว WenAssembly ทำงานได้ถูกต้อง เช่นการกำหนดขนาดของ  Memory
  3. เป็นการสร้าง Module Object จาก .wasm
  4. เป็น Code ที่ใช้เรียก function “add” ที่เขียนด้วยภาษา C (ในการ Compile นั้นทาง Compiler ได้เติม _ เพื่อเป็น prefix ให้กับชื่อ function ทำให้เราต้องเรียก function นี้ด้วย _add แทน)

ทีนี้ลองมารันดูครับด้วย localhost:8080/demo.html แต่ก่อนอื่นเราต้องมี Browser ที่รองรับก่อน

เตรียม Browser ให้พร้อม

ผมเลือกใช้ Google Chrome Canary Build (ซึ่งเป็น Google Chrome เวอร์ชั่นทดสอบ) ซึ่งตอนนี้เป็นเวอร์ชั่น 59 ครับ เราต้องเปิด flag สำหรับ WebAssembly ด้วยเพราะตอนนี้ยังเป็นแค่ฟีเจอร์สำหรับการทดสอบอยู่ด้วย

chrome://flags/#enable-webassembly

Screen Shot 2560-03-05 at 8.34.54 PM.png

ผลของการทำงานก็จะเห็นได้ว่า function _add นั้นทำการบวกค่าได้อย่างถูกต้อง

Screen Shot 2560-03-05 at 7.11.48 PM.png

สรุป

ก่อนอื่นต้องบอกว่า WebAssembly นั้นเป็นของที่ยังถือว่าใหม่และยังไม่นิ่งเพราะถึงแม้ว่าจะเปิดตัวมาตั้งแต่ปี 2015 แต่จนถึงวันนี้ก็ยังอยู่ในสถานะที่กำลังออกแบบกันอยู่ ซึ่งจากการที่ผมได้ลองอ่านและเล่นๆดูก็พบว่าบทความบนอินเตอร์เน็ตนั้นน้อยมาก กว่าผมจะพอปะติดปะต่อภาพรวมของการทำงานของมันจนมาถึงลองเขียน Code ตัวอย่างดูก็ใช้เวลาอยู่พักนึง แต่ผมว่าเมื่อถึงเวลาที่มันออกสู่สาธราณะเป็น Official Release ฟีเจอร์นี้ก็น่าจะถูกจับตามองอีกครั้งกับการใช้งานหนักๆ เช่น เกมหรืองานคำนวณ รวมทั้งงานที่เน้นกราฟฟิคหนักๆ

TypeScript มาเพิ่มคุณภาพของ Code ด้วย TsLint

Screen Shot 2560-02-04 at 10.23.56 AM.png

Linter นั้นคือเครื่องมือที่ช่วยให้ตรวจสอบลักษณะการเขียน Code ว่าเหมาะสมกับ Best Practices ที่ทาง Community ต่างๆแนะนำหรือไม่ ซึ่งจุดประสงค์หลักเพื่อช่วยให้ Source Code ของทั้งโปรเจคนั้นมีความ

  • อ่านง่าย และไปในทิศทางเดียวกัน
  • ดูแล แก้ไขง่าย
  • แก้ปัญหา Error แบบพื้นฐาน

โดยสำหรับ TypeScript นั้นgเครื่องมือนี้มีชื่อว่า TsLint

 การติดตั้งก็ใช้ npm เจ้าเก่า

$ npm install tslint typescript

จากนั้นก็ทำการสร้าง tslint.json ซึ่งจะเป็นไฟล์หลักที่ใช้ในการ Config ค่าต่างๆของ TsLint

$tslint –init

ลองเปิดดูไฟล์ tslint.json เราก็จะเห็นว่าทางค่าปกติที่ติดตั้งมานั้นมีกฏอะไรบ้าง ซึ่งใน version 4.4.2 ที่ผมใช้นั้นมันซัพพอร์ท JavaScript ด้วย

Screen Shot 2560-02-04 at 9.33.30 AM.png

กับอีกส่วนนึงคือ Node สำหรับ TypeScript ที่ใช้ชื่อ attribute ว่า rules เฉยๆ

Screen Shot 2560-02-04 at 9.36.27 AM.png

ทดสอบกับ Code ตัวอย่าง

ที่นี้มาลองเล่นกันกับ Code ตัวอย่างแบบง่ายๆกับ main.ts

// main.ts
class HERO{
 private name = 'HERO'
 public attack()
 {
 console.log(`${this.name} : do attack`);
 }
}

var player = new HERO();
player.attack();

หลังจาก compiled .ts ออกมาเป็น .js ก็จะออกมาเป็น

// main.js
var HERO = (function () {
 function HERO() {
 this.name = 'HERO';
 }
 HERO.prototype.attack = function () {
 console.log(this.name + " : do attack");
 };
 return HERO;
}());
var player = new HERO();
player.attack();

ลองรันดูก็ได้ผลออกมาตามนี้ครับ ซึ่งก็ถูกต้องดีหมายความว่า Code เรารันได้ดี 🙂

Screen Shot 2560-02-04 at 9.50.44 AM.png

ทีนี้ลองมารัน TsLint เพื่อดูว่า Code ของเรา 10 บรรทัดเนี่ยมันเขียนมาโอเคมั้ยกัน

$ tslint -c tslint.json *.ts -t stylish

ผลออกมากโดนไป 5 เรื่องครับ tslint จะรายงานมาว่าเราเขียนไม่ดียังไง ที่บรรทัดไหน ตัวอักษรที่เท่าไหร่

screen-shot-2560-02-04-at-9-54-26-am

เรื่องที่เราโดนรายงานครั้งนี้คือ

  1. ใช้ var แทนที่จะเป็น let หรือ const เพราะในโลกของ TypeScript เราแนะนำกันแบบนี้
  2. ลืมเว้นวรรค!!
  3. ใส่วงเล็บปีกกาเปิดผิดที่!@!#
  4. ตัวแปร String เราดันใช้ single quote ” แทนที่จะเป็น dobule quotes “” (ตามที่กำหนดไว้ใน tslint.json)
  5. ลืมเครื่องหมาย semi colon

จะเห็นว่าสิ่งที่มันรายงานผลมานั้นก็ดูดีมีประโยชน์ครับ(มั้ย?)

ที่นี้เรามาดู Code หลังจากแก้ไขไปแล้วดีกว่าเนอะ

Screen Shot 2560-02-04 at 10.15.16 AM.png

จากรูปด้านซ้ายและขวาที่เหมือนเล่นเกมจับผิด จะเห็นได้ว่าการทำ Linter นั้นช่วยให้ Code ของเรานั้นดูเป็นระเบียบมากขึ้น ยิ่งในกรณีที่ในโปรเจคมี Developer หลายๆคน อาจะใช้ Linter นั้นจะช่วยให้ Code ที่ถูกเขียนมานั้นเป็นไปในแนวทางเดียวกันมากขึ้น

การทำให้ Software นั้น Maintain ได้ง่ายและมีคุณภาพ เป็นเกียรติ (Duty) ของ Developer นะครับ 🙂

node-ffi เรียก functions ใน dll ด้วย JavaScript กันตรงๆ

จากความเดิมตอนที่แล้วผมเคยเขียนเรื่องการสร้าง Node.js Module ด้วย C++ มาทีนึงแล้ว แต่ถึงอย่างนั้นถ้าเรา
– มี C++ Library อยู่แล้วล่ะ
– หรืออยากทำให้ Node สามารถเรียกใช้งานพวก System API โดยที่ไม่อยากศึกษาการเขียน Node Module แบบ Native
ผมก็ไปเจอว่ามี Community ที่ได้พัฒนา Framework มาตัวนึงซึ่งวันนี้เราจะมาพูดถึงกัน นั่นก็คือ Node-ffi ซึ่งย่อมาจาก Foreign Function Interface

Node-ffi นั้นเป็น Module ที่ทำหน้าที่เป็นตัวกลางในการทำการติดต่อระหว่าง JavaScript Code ไปที่ C++ Library โดยที่เราไม่ต้องเขียน Native Module เอง ประมาณรูป Diagram ด้านล่างครับ

ffi.png

เรามาลองดูการใช้งานกันดีกว่าครับ

ก่อนอื่นเรามี C++ Library ที่เป็น DLL ที่ต้องการจะเรียก ซึ่งผมลองเขียนมาแบบง่ายๆคือ dll ที่มี function ในการ ดึงค่า Version ของ OS

extern "C" DLLIMPORT int getVersion()
{
 DWORD dwVer = ::GetVersion();
 DWORD majorVer = (DWORD)(LOBYTE(LOWORD(dwVer)));
 return majorVer;
};

ซึ่ง API ::GetVersion นั้นเป็นของ Windows SDK ครับ ซึ่งสำหรับฝั่ง C++ ผมได้ทำการ  Compile ออกมาเป็น dll ชื่อ dev.dll

ทีนี้มาลองดูส่วนที่สำคัญของเราก็คือฝั่ง JavaScript

var ffi = require('ffi');

var devModule = ffi.Library('dev', {
 'getVersion': [ 'int', [] ]
});
console.log('Windows version : ' + devModule.getVersion());

จาก Code นี้ก็มี 3 ส่วนหลักๆครับ

  1. require module ‘ffi’
  2. ประกาศการทำ function binding
    ffi.Library('dev', {
      'getVersion': [ 'int', [] ]

    2.1 ทำการ binding กับไฟล์ library ชื่อ dev.dll
    2.2 ทำการ binding กับ function ชื่อ getVersion โดยมี ‘int’ เป็น Type ของ Return Value
    2.3 Function getVersion นี้ไม่มี parameter เลยใส่เป็น []

  3. เรียกใช้ ffi ด้วย  devModule.getVersion()

ผลของการรันก็เป็นดังนี้ครับคือได้ค่า ’10’ มาจาก getVersion

ffi_run.png

นอกจากนี้เรายังสามารถประกาศการทำ binding ทีละหลายๆ function ก็ได้เหมือนกันครับ(ขออนุญาติไม่โชว์ Code ฝั่ง C++ ที่เขียนเพิ่มเพื่อดึงค่า Minor,Build Version) เช่น

var devModule = ffi.Library('dev', {
 'getVersion': [ 'int', [] ] ,
 'getMinorVersion' : [ 'int', [] ],
 'getBuildVersion' : [ 'int', [] ]
});
console.log('Windows version : ' + devModule.getVersion());
console.log('Minor version : ' + devModule.getMinorVersion());
console.log('Build version : ' + devModule.getBuildVersion());

ผลการรันก็ดูดีครับและตรงกับ System Info จริงๆนะ 🙂

ffi_run2.png

สรุป : node-ffi น่าจะเป็นอีกหนึ่งทางเลือกสำหรับคนที่เขียน Node.Js ด้วย Context บางอย่างที่ต้องติดต่อกับ System หรือ OS ระดับ Low Level (เหมือนผม..) โดยที่เรานั้นไม่ต้องมาการเรียนรู้การเขียน Node Module แบบ Native ที่อาจจะมีความซับซ้อนกว่าทั้งในแง่การทำ Mapping พวก Data Types รวมถึงการมานั่งศึกษา V8 API  ซึ่งผมคิดว่า node-ffi อาจจะมีผลกระทบกับ Performance ในการ call function บ้าง แต่ผมก็ยังไม่ได้ลองวัด ทั้งนี้ทั้งนั้นใครมีข้อมูลเพิ่มก็แชร์กันได้ครับ