ในการพัฒนาโปรแกรมเนี่ยเรามักจะมีการตั้ง coding standard ซึ่งก็จะมีส่วนของรูปแบบโค๊ด ไม่ว่าจะเป็นการย่อหน้า การเว้นวรรค การตัดบรรทัด และอื่น ๆ

ปัญหาก็คือ คนเขียนโค๊ดก็เป็นคน และด้วยความที่เป็นคน คนเขียนโค๊ดก็มักจะไม่สามารถรักษาฟอร์แมทของโค๊ดได้ตลอดเวลา และด้วยกฎที่กำหนดอย่างหลวม ๆ ทำให้คนเขียนโค๊ดสามารถที่จะเลือกว่าจะให้กฎข้อไหน และละเลยกฎข้อไหนไป บางโปรเจคก็ถึงขึ้นว่าไม่สามารถรักษาฟอร์แมทให้เหมือนกันได้ทั้งโปรเจค

ปัญหาอีกข้อคือ ฟอร์แมทของโค๊ดนั้นก็เหมือนกับตัวโค๊ด ตรงที่มีการพัฒนาตลอดเวลา ถ้าเรามีการเปลี่ยนแปลงโค๊ดให้เข้ากับฟอร์แมทใหม่ก็ต้องมีคนไปแก้โค๊ดเก่า ถ้าเป็นโปรเจคที่มีไฟล์เยอะมาก ๆ รับรองได้ว่าไม่มีใครทำแน่ ๆ ทั้งเสียเวลาทั้งน่าเบื่อ

ด้วยปัญหาหลาย ๆ อย่างของการจัดรูปแบบด้วยมือก็เลยมีคนทำโปรแกรมที่คอยจัดฟอร์แมทของไฟล์ให้ โปรแกรมประเภทนี้บางคนก็จะเรียกว่า code format หรือบางคนก็จะเรียกว่า code beautifier ซึ่งใน IDE แพง ๆ ส่วนใหญ่ก็จะมีฟีเจอร์นี้อยู่ในตัวครับ

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

สมมติว่าผมมีโค๊ดนี้นะครับ

        #include <iostream>
    using namespace std;

    struct Point{ int x, int y};
enum class Direction {Left, Right, Forward, Backward};

void PrintDirection(const Direction &dir){
  switch(dir){
    case Direction.Left: cout<<"Turn Left"<<endl; break;
    case Direction.Right: cout<<"Turn Right"<<endl; break;
    case Direction.Forward: cout<<"Move Forward"<<endl; break;
    case Direction.Barkward: cout<<"Move Backward"<<endl; break;
    default: throw;
  }
}

int main(int, char **) 
{
    PrintDirection(Direction.Left);

    Point p{50,100};

  for(int i = 0; i< 100; i++) {p.x ++; p.y--;}

  return 0;
}

ดูเละ ๆ แบบนี้แหละ 555 ผมสามารถใช้คำสั่ง clang test.pp เพื่อให้มันฟอร์แมทโค๊ดสวย ๆ โดยเมื่อเราสั่งมันจะพิมพ์ออกมาบนคอนโซลครับ

#include <iostream>
using namespace std;

struct Point {
  int x, int y
};
enum class Direction { Left, Right, Forward, Backward };

void PrintDirection(const Direction &dir) {
  switch (dir) {
  case Direction.Left:
    cout << "Turn Left" << endl;
    break;
  case Direction.Right:
    cout << "Turn Right" << endl;
    break;
  case Direction.Forward:
    cout << "Move Forward" << endl;
    break;
  case Direction.Barkward:
    cout << "Move Backward" << endl;
    break;
  default:
    throw;
  }
}

int main(int, char **) {
  PrintDirection(Direction.Left);

  Point p{50, 100};

  for (int i = 0; i < 100; i++) {
    p.x++;
    p.y--;
  }

  return 0;
}

ดูดีขึ้นไหมครับ ทั้งนี้เราสามารถเพิ่มพารามิเตอร์ -i เข้าไปเพื่อให้มันบันทึกลงไปในไฟล์เลยได้ด้วยครับ

ทีนี้ฟอร์แมทของโค๊ดที่ได้เนี่ยมันจะเป็นตามมาตรฐานของโครงการ llvm แต่เราสามารถเลือกที่จะใช้มาตรฐานอื่น ๆ ได้ (ตัวโปรแกรมมีมา 5 รูปแบบครับ) และเราสามารถสร้างรูปแบบของเราเองได้ด้วยอีกเช่นกัน ตัวอย่างของล่างนี้เป็นฟอร์แมทของ WebKit นะครับ

#include <iostream>
using namespace std;

struct Point {
    int x, int y
};
enum class Direction { Left,
    Right,
    Forward,
    Backward };

void PrintDirection(const Direction& dir)
{
    switch (dir) {
    case Direction.Left:
        cout << "Turn Left" << endl;
        break;
    case Direction.Right:
        cout << "Turn Right" << endl;
        break;
    case Direction.Forward:
        cout << "Move Forward" << endl;
        break;
    case Direction.Barkward:
        cout << "Move Backward" << endl;
        break;
    default:
        throw;
    }
}

int main(int, char**)
{
    PrintDirection(Direction.Left);

    Point p{ 50, 100 };

    for (int i = 0; i < 100; i++) {
        p.x++;
        p.y--;
    }

    return 0;
}

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

ส่วนตัวผมก็สนับสนุนให้มีการตั้ง commit trigger ให้ฟอร์แมทโค๊ดก่อนที่จะบันทึกทุกครั้งเหมือนกัน แต่ส่วนตัวยังไม่ได้ทำครับ ตอนนี้ใช้วิธีใช้ Text Editor (Atom) ไปเรียก clang-format ก่อนบันทึกไฟล์แทน ก็พอทดแทนกันได้ในระดับหนึ่งครับ

ทั้งนี้เรื่องหนึ่งที่ต้องบอกคือเจ้า clang-format เนี่ยมันไม่ใช่คอมไพล์เลอร์ มันไม่จับโค๊ดที่ผิดนะครับ อย่างโค๊ดข้างบนเองเอาจริง ๆ ก็คอมไพล์ไม่ผ่านนะขอบอก

พอดีช่วงนี้ศึกษา Web Application มากขึ้นกว่าแต่ก่อน ก็เลยได้เห็นเทรนด์นึงที่ในระยะหลังเกิดขึ้นอยู่ประมาณนึง ก็คือการเอา Database ไปเก็บ Configuration เอาซะเลย ด้วยความที่ว่าคนที่เขียน Enterprise Application ย่อมคุ้นเคยกับ SQL อยู่แล้ว (แต่สารภาพตรง ๆ ผมเกลียด SQL แบบสุด ๆ ไปเลย) แล้วก็มีข้อดีของ Database หลาย ๆ อย่าง เช่นเรื่องของ Consistency และอื่น ๆ

เอาจริง ๆ ผมค่อนข้างคัดค้านแนวความคิดนี้นะครับ ผมคิดว่าการเอา Database ไปเก็บ Configuration เป็นแนวความคิดที่แย่เอามาก ๆ แต่พอมาคิดหาเหตุผลก็ ... อืม ... ตอนแรกก็ช็อตไปเหมือนกันว่าจะตอบว่าอะไรดี 555

ผมคิดว่าประเด็นการเก็บ Configuration ใน Database มีปัญหาอยู่หลายอย่างนะ

  1. ความชัดเจน คือ ผู้ที่จะเข้าไปแก้ไข Configuration จะต้องเป็นคนที่มีความรู้ในตัว Implementation ของตัวที่อ่าน Configuration พอสมควร เพราะว่าไอ้การหาว่า คอนฟิกอะไร เก็บค่าอะไร ค่าเดิมคืออะไร หรืออะไรทำนองนี้จะต้องกระทำผ่าน Query (ซึ่งอาจจะเป็นได้ทั้ง SQL Query หรือ MapReduce Query หรืออะไรก็ว่าไป) ในขณะที่ถ้าเป็น Flat File เนี่ยเราสามารถประยุกต์เอาพวก markup file เช่น xml, json และอื่น ๆ มาใช้ได้ ทำให้มีความชัดเจนมากกว่า (แต่ทั้งนี้ก็ต้องมีความเข้าใจในระดับนึงเช่นกันแต่อาจจะต่ำกว่า)

  2. การดูแลรักษา เท่าที่ทราบ DBMS แทบจะทุกเจ้าบนโลกนี้ทำงานกับ Binary File และไม่ใช่แค่ไฟล์เดียวด้วย (บางทีก็เพียบเลย) ดังนั้นลืมเรื่องการเก็บ DB เอาไว้ใน Version Control ไปได้เลย เพราะว่า Version Control เนี่ยจะทำงานได้ดีกับไฟล์ที่เป็น Text File ซึ่งการใช้ Binary File นั้นทำให้เสียฟังก์ชันหลักอันหนึ่งของ Version Control นั่นคือการ Merge Version

    เช่น สมมติว่า ผมต้องการจะแก้คอนฟิกทั้งหมดที่มีการเปลี่ยนแปลงในช่วงเดือนมีนาคมทั้งหมด (ซึ่งจริง ๆ ก็มีอยู่แค่ 2-3 อัน) การใช้ Flat File กับ VCS นั้นสิ่งที่ต้องทำคือ เปิด Version History เลือกเวอร์ชันที่ต้องการเอาออก แล้วสั่ง remove change มันจะทำการเอาการเปลี่ยนแปลงในช่วงที่เราเลือกออกไปทั้งหมด (แล้วก็ Commit)

    ในขณะที่ถ้าเป็น DB สิ่งที่คุณต้องทำคือเขียน Query เข้าไปลบ/แก้ไข ซึ่งก็คือการ "ทำใหม่" นั่นแหละ เพราะว่าต้องทำการทบทวนความเปลี่ยนแปลงทั้งหมดในช่วงเวลานั้น แล้วก็เขียน update query เข้าไปแก้ นี่ไม่ใช่การเอาออกแล้วครับ นี่มันคือการสร้างของใหม่เข้าไปทับของเก่าต่างหาก

    อ้อ จะว่าไป ชื่อเล่นของ VCS คือ SCM (Software Configuration Manager) ครับ ดังนั้นมันจึงเก็บ Configuration Item ทั้งหมด (ซึ่งปรกติจะรวมเฉพาะโค๊ดและคอนฟิกไฟล์) น่ะครับ

    เหตุผลส่วนตัวของผมอีกอย่างคือหลาย ๆ ครั้งผมต้องทำงานกับโค๊ดหลาย ๆ เวอร์ชัน การใช้ DB เนี่ยการสลับคอนฟิกระหว่างเวอร์ชัน (อันนี้สำคัญนะครับ) จะทำได้ยากลำบากมาก เพราะมันคือการลบคอนฟิกเก่าแล้วโหลดตัวใหม่เข้าไป ... แค่อ่านก็ปวดหัวละครับ ในขณะที่ถ้าเป็น Flat File เนี่ยก็แค่สลับไฟล์ที่โหลดก็จเสร็จละ

    ทั้งนี้เราสามารถที่จะดูแล config ในรูปของ text file เดี่ยว ๆ เพื่อใช้ใน DB ได้เหมือนกันครับ ก็คือการสร้าง Master Create Query ขนาดยักษ์ที่ทำงานโดยการ Drop Table ทิ้งแล้วสร้างข้อมูลใหม่ตั้งแต่ 0 แต่ผมว่าสุดท้ายแล้วมันก็เยอะเกินไปอยู่ดี และโอกาสพลาดสูงกว่าด้วย เพราะว่าในไฟล์จะมีทั้งส่วนของคำสั่งและส่วนของข้อมูล ในขณะที่ถ้าเป็น flat file ธรรมดามันมีแต่ข้อมูลอย่างเดียวครับ

  3. ทรัพยากร คือ DBMS เนี่ยจริง ๆ แล้วมันเป็นซอฟต์ที่ค่อนข้างกินแรมน่ะครับ (ผมไม่ได้พูดถึงพวก Oracle นะ) ในขณะที่ flat file เนี่ยใช้พื้นที่เท่ากับขนาดของไฟล์เท่านั้นแหละ โดยส่วนตัวผมว่าการใช้ DB เนี่ยเป็นการขี่ช้างจับตั๊กแตนครับ เพราะตัวมันเองออกแบบมาเพื่อเก็บข้อมูลจำนวนมาก ๆ แต่เราเอามาเก็บคอนฟิกไฟล์ซึ่ง...อาจจะมีแค่ 1 record เท่านั้น ระบบที่ออกแบบมาสำหรับข้อมูลขนาดใหญ่มักจะไม่เหมาะกับงานเล็ก ๆ น่ะครับ

ทั้งนี้ถ้าเราใช้แต่ flat file เพียว ๆ เลยเราจะเจอปัญหาด้านการจัดการแทน เพราะถ้าเรามี server ที่ต้อง deploy เยอะ ๆ เนี่ยการไล่ตามอัพเดต config ในแต่ละเครื่องมันก็เป็นงานใหญ่อยู่นะ ผมคิดว่าเราสามารถที่จะสร้าง micro service สำหรับงานด้าน configuration โดยเฉพาะได้ โดยอาจจะเป็น REST Web Service สักตัวนึงที่มีหน้าที่คืนค่า Configuration ต่าง ๆ แทนที่จะให้ตัว Web App ไปโหลดมาจากตัวไฟล์โดยตรง (ก็อาจจะมี Network Performance Penalty อยู่นะแต่คิดว่าไม่ต่างกับ DB หรอกครับ)

อีกอย่างหนึ่งที่สามารถทำได้คือการใช้ VCS Trigger ก็คือเราสามารถสร้าง Trigger ให้มีการสั่งให้ Service ต่าง ๆ ทำการอัพเดต Configuration หลังจากการ Check-in ตัวไฟล์คอนฟิก ซึ่งก็ไม่ได้เป็นเรื่องยากอะไรเลยและโอกาสพลาดต่ำกว่าด้วย (อะไรที่มีมนุษย์มาเกี่ยวข้องเนี่ยพลาดได้หมดละครับ) Configuration จะถูกอ่านจากตัว VCS โดยตรง ทำให้ปัญหาพวก Consistency ระหว่าง Service นั้นหมดไปนั่นเอง

เอาจริง ๆ เลยผมว่า ... แนวคิดนี้เกิดจากพวกบ้า Database ที่เอะอะอะไรก็ใช้ Database (คนกลุ่มนี้มีจริงครับและเยอะด้วย ... เยอะในหลายความหมายนะ) ถ้ามองกันตามจริงผมว่า DB มีไว้เก็บข้อมูล และไม่ควรเก็บอะไรมากกว่านั้นครับ