ขั้นตอนถัดไป

ความรู้เบื้องต้นเกี่ยวกับการเขียนโปรแกรมและ C++

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

เรียนรู้ตามตัวอย่างที่ 2

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

ตัวอย่างที่ 1: มีแนวทางปฏิบัติในการแตกย่อยมากขึ้น

ลองพิจารณาผลลัพธ์ที่ได้จากเกมง่ายๆ ต่อไปนี้

Welcome to Artillery.
You are in the middle of a war and being charged by thousands of enemies.
You have one cannon, which you can shoot at any angle.
You only have 10 cannonballs for this target..
Let's begin...

The enemy is 507 feet away!!!
What angle? 25<
You over shot by 445
What angle? 15
You over shot by 114
What angle? 10
You under shot by 82
What angle? 12
You under shot by 2
What angle? 12.01
You hit him!!!
It took you 4 shots.
You have killed 1 enemy.
I see another one, are you ready? (Y/N) n

You killed 1 of the enemy.

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

StartUp(); // This displays the introductory script.
killed = 0;
do {
  killed = Fire(); // Fire() contains the main loop of each round.
  cout << "I see another one, care to shoot again? (Y/N) " << endl;
  cin >> done;
} while (done != 'n');
cout << "You killed " << killed << " of the enemy." << endl;

ขั้นตอนของ Fire จะจัดการกับการเล่นเกม ในฟังก์ชันนั้น เราจะเรียกใช้โปรแกรมสร้างตัวเลขสุ่มเพื่อดูระยะห่างของศัตรู จากนั้นตั้งค่าการวนซ้ำเพื่อรับข้อมูลของผู้เล่นและคำนวณว่ายิงโดนศัตรูหรือไม่ เงื่อนไขของการป้องกันคือเราจะเข้าใกล้ศัตรูได้แค่ไหน

In case you are a little rusty on physics, here are the calculations:

Velocity = 200.0; // initial velocity of 200 ft/sec Gravity = 32.2; // gravity for distance calculation // in_angle is the angle the player has entered, converted to radians. time_in_air = (2.0 * Velocity * sin(in_angle)) / Gravity; distance = round((Velocity * cos(in_angle)) * time_in_air);

เนื่องจากมีการเรียก cos() และ sin() คุณจึงต้องใส่ math.h ด้วย ลองเขียนโปรแกรมนี้ขึ้นมา โปรแกรมนี้เป็นแนวทางปฏิบัติที่ดีในการจำแนกลักษณะของปัญหาและการทบทวน C++ พื้นฐานที่ดี อย่าลืมทำงานเพียงหนึ่งอย่างในแต่ละฟังก์ชัน นี่เป็นโปรแกรมที่ซับซ้อนที่สุดที่เราเขียนขึ้นจนถึงตอนนี้ ดังนั้นอาจใช้เวลาสักเล็กน้อยที่นี่คือวิธีแก้ปัญหาของเรา

ตัวอย่างที่ 2: ฝึกปฏิบัติโดยใช้ตัวชี้

มี 4 สิ่งที่ต้องจดจำเมื่อใช้ตัวชี้ ดังนี้
  1. ตัวชี้คือตัวแปรที่เก็บที่อยู่หน่วยความจำ ขณะที่โปรแกรมกำลังดำเนินอยู่ ระบบจะจัดเก็บตัวแปรทั้งหมดไว้ในหน่วยความจำ โดยแต่ละตัวแปรจะอยู่ในที่อยู่หรือตำแหน่งที่ไม่ซ้ำกันของตนเอง ตัวชี้เป็นตัวแปรประเภทพิเศษที่มีที่อยู่หน่วยความจำ ไม่ใช่ค่าข้อมูล เช่นเดียวกับการแก้ไขข้อมูลเมื่อมีการใช้ตัวแปรปกติ ค่าของที่อยู่ที่จัดเก็บไว้ในตัวชี้จะถูกแก้ไขตามตัวแปรตัวชี้ เช่น
    int *intptr; // Declare a pointer that holds the address
                 // of a memory location that can store an integer.
                 // Note the use of * to indicate this is a pointer variable.
    
    intptr = new int; // Allocate memory for the integer.
    *intptr = 5; // Store 5 in the memory address stored in intptr.
          
  2. โดยปกติเรามักจะบอกว่าตัวชี้ "ชี้ไปยัง" ตำแหน่งที่มันจัดเก็บอยู่ ("ตัวชี้") ดังนั้นในตัวอย่างข้างต้น intptr จะชี้ไปที่ Pointee 5.

    โปรดสังเกตการใช้โอเปอเรเตอร์ "ใหม่" เพื่อจัดสรรหน่วยความจำสำหรับตัวชี้จำนวนเต็ม นี่คือสิ่งที่เราต้องทำก่อนที่จะพยายามเข้าถึง Pointee

    int *ptr; // Declare integer pointer.
    ptr = new int; // Allocate some memory for the integer.
    *ptr = 5; // Dereference to initialize the pointee.
    *ptr = *ptr + 1; // We are dereferencing ptr in order
                     // to add one to the value stored
                     // at the ptr address.
          

    โอเปอเรเตอร์ * ใช้สำหรับยกเลิกการอ้างอิงใน C หนึ่งในข้อผิดพลาดที่พบบ่อยที่สุดที่โปรแกรม C/C++ ทำในการทำงานกับตัวชี้คือลืมเริ่มต้นใช้งาน Pointee ซึ่งบางครั้งอาจทำให้รันไทม์ขัดข้องเนื่องจากเรากำลังเข้าถึงตำแหน่งในหน่วยความจำที่มีข้อมูลที่ไม่รู้จัก หากเราพยายามแก้ไขข้อมูลนี้ อาจทำให้หน่วยความจำเสียหายเล็กน้อย ทำให้เป็นข้อบกพร่องที่ยากที่จะติดตามได้

  3. การกำหนดตัวชี้ระหว่างตัวชี้ 2 จุดจะทำให้ตัวชี้เหล่านั้นชี้ไปยังจุดเดียวกัน ดังนั้นงาน y = x ทำให้ y ชี้ไปที่จุดเดียวกันเป็น x การกำหนดตัวชี้ไม่แตะตัวชี้ เป็นเพียงการเปลี่ยนตัวชี้หนึ่งให้มีตำแหน่งเดียวกับอีกตัวชี้หนึ่ง หลังจากกำหนดตัวชี้ ตัวชี้ 2 อันจะ "แชร์" แต้ม 
  4. void main() {
     int* x; // Allocate the pointers x and y
     int* y; // (but not the pointees).
    
     x = new int; // Allocate an int pointee and set x to point to it.
    
     *x = 42; // Dereference x and store 42 in its pointee
    
     *y = 13; // CRASH -- y does not have a pointee yet
    
     y = x; // Pointer assignment sets y to point to x's pointee
    
     *y = 13; // Dereference y to store 13 in its (shared) pointee
    }
      

นี่คือการติดตามของโค้ดนี้

1. จัดสรร 2 ตัวชี้ x และ y การจัดสรรตัวชี้ไม่จัดสรร Pointee ใดๆ
2. จัดสรร Pointee และกำหนด x ให้ชี้ไปยังปลายทาง
3. ยกเลิกการอ้างอิง x เพื่อเก็บ 42 ไว้ใน Pointee นี่คือตัวอย่างพื้นฐานของการดำเนินการยกเลิกการอ้างอิง เริ่มต้นที่ x ตามลูกศรด้านบนเพื่อเข้าถึง Pointee
4. ลองยกเลิกการอ้างอิง y เพื่อจัดเก็บ 13 ใน Pointee ข้อขัดข้องนี้ขัดข้องเนื่องจาก y ไม่มีผู้ได้รับแต้ม ไม่เคยมีการให้สิทธิ์
5. กําหนด y = x เพื่อให้ y ชี้ไปที่ Pointee ของ x ตอนนี้ x และ y ชี้ไปที่ จุดเดียวกัน นั่นคือ "แชร์"
6. ลองยกเลิกการอ้างอิง y เพื่อจัดเก็บ 13 ใน Pointee แต่คราวนี้ได้ผล เนื่องจากงานก่อนหน้านี้ได้มอบแต้มให้คุณ

ดังที่คุณเห็น รูปภาพมีประโยชน์อย่างมากต่อการทำความเข้าใจการใช้งานตัวชี้ นี่คืออีกตัวอย่างหนึ่ง

int my_int = 46; // Declare a normal integer variable.
                 // Set it to equal 46.

// Declare a pointer and make it point to the variable my_int
// by using the address-of operator.
int *my_pointer = &my_int;

cout << my_int << endl; // Displays 46.

*my_pointer = 107; // Derefence and modify the variable.

cout << my_int << endl; // Displays 107.
cout << *my_pointer << endl; // Also 107.

โปรดสังเกตในตัวอย่างนี้ว่าเราไม่เคยจัดสรรหน่วยความจำด้วยโอเปอเรเตอร์ "ใหม่" เราประกาศตัวแปรจำนวนเต็มปกติและดัดแปลงผ่านตัวชี้

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

int *ptr1; // Declare a pointer to int.
ptr1 = new int; // Reserve storage and point to it.

float *ptr2 = new float; // Do it all in one statement.

delete ptr1; // Free the storage.
delete ptr2;

ในตัวอย่างสุดท้ายนี้ เราจะแสดงวิธีใช้ตัวชี้เพื่อส่งค่าโดยการอ้างอิงไปยังฟังก์ชัน นี่คือวิธีที่เราแก้ไขค่าของตัวแปรภายในฟังก์ชัน

// Passing parameters by reference.
#include <iostream>
using namespace std;

void Duplicate(int& a, int& b, int& c) {
  a *= 2;
  b *= 2;
  c *= 2;
}

int main() {
  int x = 1, y = 3, z = 7;
  Duplicate(x, y, z);
  // The following outputs: x=2, y=6, z=14.
  cout << "x="<< x << ", y="<< y << ", z="<< z;
  return 0;
}

หากเราปล่อยเครื่องหมาย & ไว้นอกอาร์กิวเมนต์ในคำจำกัดความฟังก์ชันที่ซ้ำกัน เราจะส่งตัวแปร "ตามค่า" กล่าวคือจะมีการทำสำเนาค่าของตัวแปร การเปลี่ยนแปลงที่ทำกับตัวแปรในฟังก์ชันจะแก้ไขสำเนา แต่จะไม่แก้ไขตัวแปรเดิม

เมื่อมีการส่งต่อตัวแปรผ่านการอ้างอิง เราจะไม่ส่งสำเนาของค่าให้ทางเราจะส่งที่อยู่ของตัวแปรไปยังฟังก์ชัน การแก้ไขที่เราทํากับตัวแปรภายในจะเป็นการแก้ไขตัวแปรเดิมที่ส่งมา

หากคุณเป็นโปรแกรมเมอร์ระดับ C นี่แหละคือสิ่งใหม่ เราสามารถทำในลักษณะเดียวกันใน C โดยประกาศ Duplicate() เป็น Duplicate(int *x) ซึ่งในกรณีนี้ x คือตัวชี้ไปยัง int จากนั้นเรียกใช้ Duplicate() ด้วยอาร์กิวเมนต์ &x (address-of x) และใช้การยกเลิกการอ้างอิง x ภายใน Duplicate() (ดูด้านล่าง) แต่ C++ ช่วยให้ส่งค่าไปยังฟังก์ชันด้วยการอ้างอิงที่ง่ายขึ้น แม้ว่าวิธีเดิมของ "C" จะยังใช้ได้อยู่

void Duplicate(int *a, int *b, int *c) {
  *a *= 2;
  *b *= 2;
  *c *= 2;
}

int main() {
  int x = 1, y = 3, z = 7;
  Duplicate(&x, &y, &z);
  // The following outputs: x=2, y=6, z=14.
  cout << "x=" << x << ", y=" << y << ", z=" << z;
  return 0;
}

โปรดทราบว่าการอ้างอิง C++ จะทำให้เราไม่จําเป็นต้องส่งที่อยู่ของตัวแปร หรือไม่จำเป็นต้องเลิกการอ้างอิงตัวแปรภายในฟังก์ชันที่เรียก

โปรแกรมต่อไปนี้จะให้ผลลัพธ์อะไรบ้าง วาดภาพความทรงจำเพื่อค้นหาคำตอบ

void DoIt(int &foo, int goo);

int main() {
  int *foo, *goo;
  foo = new int;
  *foo = 1;
  goo = new int;
  *goo = 3;
  *foo = *goo + 3;
  foo = goo;
  *goo = 5;
  *foo = *goo + *foo;
  DoIt(*foo, *goo);
  cout << (*foo) << endl;
}

void DoIt(int &foo, int goo) {
  foo = goo + 3;
  goo = foo + 4;
  foo = goo + 3;
  goo = foo;
} 

เรียกใช้โปรแกรมเพื่อดูว่าคุณมีคำตอบที่ถูกต้องหรือไม่

ตัวอย่างที่ 3: การส่งต่อค่าโดยการอ้างอิง

เขียนฟังก์ชันชื่อ accelerate() ที่ใช้ป้อนข้อมูลความเร็วของยานพาหนะและจำนวน ฟังก์ชันนี้จะเพิ่มความเร็วเพื่อเร่งความเร็วของยานพาหนะ พารามิเตอร์ความเร็วควรส่งผ่านค่าอ้างอิงและปริมาณตามค่า นี่คือวิธีแก้ปัญหาของเราที่นี่

ตัวอย่างที่ 4: คลาสและวัตถุ

ลองพิจารณาชั้นเรียนต่อไปนี้

// time.cpp, Maggie Johnson
// Description: A simple time class.

#include <iostream>
using namespace std;

class Time {
 private:
  int hours_;
  int minutes_;
  int seconds_;
 public:
  void set(int h, int m, int s) {hours_ = h; minutes_ = m; seconds_ = s; return;}
  void increment();
  void display();
};

void Time::increment() {
  seconds_++;
  minutes_ += seconds_/60;
  hours_ += minutes_/60;
  seconds_ %= 60;
  minutes_ %= 60;
  hours_ %= 24;
  return;
}

void Time::display() {
  cout << (hours_ % 12 ? hours_ % 12:12) << ':'
       << (minutes_ < 10 ? "0" :"") << minutes_ << ':'
       << (seconds_ < 10 ? "0" :"") << seconds_
       << (hours_ < 12 ? " AM" : " PM") << endl;
}

int main() {
  Time timer;
  timer.set(23,59,58);
  for (int i = 0; i < 5; i++) {
    timer.increment();
    timer.display();
    cout << endl;
  }
}

สังเกตว่าตัวแปรสมาชิกคลาสจะมีขีดล่างต่อท้าย ทั้งนี้เพื่อแยกความแตกต่างระหว่างตัวแปรภายในและตัวแปรคลาส

เพิ่มวิธีการลดในชั้นเรียนนี้ นี่คือวิธีแก้ปัญหาของเราที่นี่

ความมหัศจรรย์ของวิทยาศาสตร์: วิทยาการคอมพิวเตอร์

การออกกำลังกาย

เช่นเดียวกับในโมดูลแรกของหลักสูตรนี้ เราไม่มีโซลูชันสำหรับแบบฝึกหัดและโครงงาน

อย่าลืมว่าโปรแกรมนี้ดี...

... จะแยกออกเป็นฟังก์ชันต่างๆ อย่างมีตรรกะ โดยที่ฟังก์ชันหนึ่งจะทำงานเพียงรายการเดียวและมีเพียงงานเดียว

... มีโปรแกรมหลักที่จะให้ข้อมูลคร่าวๆ ว่าโปรแกรมจะทำอะไรบ้าง

... มีฟังก์ชันที่สื่อความหมาย ชื่อคงที่ และชื่อตัวแปร

... ใช้ค่าคงที่เพื่อหลีกเลี่ยงตัวเลข "มหัศจรรย์" ใดๆ ในโปรแกรม

... มีอินเทอร์เฟซผู้ใช้ที่ใช้งานง่าย

การออกกำลังกายอุ่นเครื่อง

  • แบบฝึกหัดที่ 1

    จำนวนเต็ม 36 มีคุณสมบัติที่แปลกใหม่ เป็นสี่เหลี่ยมจัตุรัสที่สมบูรณ์ และยังเป็นผลรวมของจำนวนเต็มตั้งแต่ 1 ถึง 8 ด้วย ตัวเลขถัดไปคือ 1225 ซึ่งเท่ากับ 352 และผลรวมของจํานวนเต็มตั้งแต่ 1 ถึง 49 หาจำนวนถัดไปที่เป็นกำลังสองสมบูรณ์และผลรวมของอนุกรม 1...n ตัวเลขถัดไปนี้อาจมากกว่า 32767 คุณอาจใช้ฟังก์ชันคลัง (หรือสูตรทางคณิตศาสตร์) ที่รู้จักเพื่อให้โปรแกรมทำงานได้เร็วขึ้น คุณอาจเขียนโปรแกรมนี้โดยใช้ลูปเพื่อระบุว่าจำนวนเป็นกำลังสองสมบูรณ์หรือผลรวมของอนุกรมได้ (หมายเหตุ: ทั้งนี้ขึ้นอยู่กับเครื่องและโปรแกรมที่คุณใช้ อาจใช้เวลาสักครู่จึงจะพบหมายเลขนี้)

  • แบบฝึกหัดที่ 2

    ร้านหนังสือมหาวิทยาลัยต้องการความช่วยเหลือในการประมาณธุรกิจสำหรับปีหน้า จากประสบการณ์พบว่าการขายหนังสือจะขึ้นอยู่กับว่าหนังสือเป็นสิ่งที่จำเป็นสำหรับหลักสูตรหรือเป็นเพียงไม่บังคับ และมีการใช้หนังสือนั้นในชั้นเรียนมาก่อนหรือไม่ ตำราเรียนเล่มใหม่ที่จำเป็นจะขายได้ 90% ของจำนวนผู้เข้าเรียนที่มีโอกาสลงทะเบียน แต่หากเคยใช้ในชั้นเรียนมาก่อน ก็จะซื้อได้เพียง 65% ในทำนองเดียวกัน ผู้ที่มีโอกาสลงทะเบียน 40% จะซื้อตำราเรียนเล่มใหม่ที่ไม่บังคับ แต่หากมีการใช้ในชั้นเรียนก่อนจะมีเพียง 20% เท่านั้นที่จะซื้อ (โปรดทราบว่าคำว่า "มือสอง" ในที่นี้ไม่ได้หมายถึงหนังสือมือสอง)

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

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

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

    ตัวอย่างเอาต์พุตมีดังนี้

    Please enter the book code: 1221
     single copy price: 69.95
     number on hand: 30
     prospective enrollment: 150
     1 for reqd/0 for optional: 1
     1 for new/0 for used: 0
    ***************************************************
    Book: 1221
    Price: $69.95
    Inventory: 30
    Enrollment: 150
    
    This book is required and used.
    ***************************************************
    Need to order: 67
    Total Cost: $4686.65
    ***************************************************
    
    Enter 1 to do another book, 0 to stop. 0
    ***************************************************
    Total for all orders: $4686.65
    Profit: $937.33
    ***************************************************

โครงการฐานข้อมูล

ในโปรเจ็กต์นี้ เราสร้างโปรแกรม C++ ที่มีฟังก์ชันการทำงานเต็มรูปแบบซึ่งนำแอปพลิเคชันฐานข้อมูลพื้นฐานไปใช้

โปรแกรมของเราจะช่วยให้เราจัดการฐานข้อมูลของผู้ประพันธ์เพลงและข้อมูลที่เกี่ยวข้องกับนักแต่งเพลงได้ ฟีเจอร์ต่างๆ ของโปรแกรม ได้แก่

  • ความสามารถในการเพิ่มผู้ประพันธ์เพลงใหม่
  • ความสามารถในการจัดอันดับผู้ประพันธ์เพลง (เช่น ระบุว่าเราชอบหรือไม่ชอบเพลงของผู้ประพันธ์มากแค่ไหน)
  • ความสามารถในการดูคอมโพสเซอร์ทั้งหมดในฐานข้อมูล
  • ความสามารถในการดูนักแต่งเพลงทั้งหมดตามอันดับ

"การสร้างการออกแบบซอฟต์แวร์มี 2 วิธี วิธีหนึ่งคือทำให้เรียบง่ายจนไม่มีข้อบกพร่อง ส่วนอีกวิธีคือทำให้ซับซ้อนจนไม่เห็นข้อบกพร่องอย่างชัดเจน วิธีแรกนั้นยากกว่ามาก" - C.A.R. Hoare

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

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

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

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

ในแผนภาพนี้ เราได้กำหนดออบเจ็กต์ 2 รายการที่อยู่ในคลาส Apple ออบเจ็กต์แต่ละรายการมีแอตทริบิวต์และการทำงานเหมือนกับคลาส แต่ออบเจ็กต์จะเป็นตัวกำหนดแอตทริบิวต์สำหรับแอปเปิ้ลบางประเภท นอกจากนี้ การดำเนินการ "ดิสเพลย์" จะแสดงแอตทริบิวต์สำหรับออบเจ็กต์ที่เฉพาะเจาะจงนั้นๆ เช่น "Green" และ "Sour"

การออกแบบ OO ประกอบด้วยชุดคลาส ข้อมูลที่เชื่อมโยงกับคลาสเหล่านี้ และชุดการดำเนินการที่คลาสทำได้ นอกจากนี้ เรายังต้องระบุวิธีที่ชั้นเรียนต่างๆ โต้ตอบด้วย การโต้ตอบนี้ทำได้โดยออบเจ็กต์ของคลาสที่เรียกใช้การดำเนินการของออบเจ็กต์ของคลาสอื่นๆ เช่น เราอาจมีคลาส AppleExporter ที่แสดงสีและรสชาติของอาร์เรย์ของออบเจ็กต์ Apple ด้วยการเรียกใช้เมธอด Display() ของออบเจ็กต์ Apple แต่ละรายการ

ต่อไปนี้คือขั้นตอนที่เราดำเนินการในการออกแบบ OO

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

สำหรับระบบขนาดใหญ่ ขั้นตอนเหล่านี้เกิดขึ้นซ้ำๆ ที่ระดับรายละเอียดต่างๆ

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

นอกจากนี้เรายังต้องมีคอลเล็กชันของออบเจ็กต์ Composer ด้วย สำหรับการดำเนินการนี้ เราจะกำหนดคลาสฐานข้อมูลที่จัดการระเบียนแต่ละรายการ ออบเจ็กต์ของคลาสนี้สามารถเพิ่มหรือเรียกออบเจ็กต์ Composer และแสดงแต่ละรายการโดยเรียกใช้การดำเนินการแสดงผลของออบเจ็กต์ Composer

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

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

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

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

คลาสอินเทอร์เฟซผู้ใช้จะใช้อินเทอร์เฟซที่ขับเคลื่อนด้วยเมนู พร้อมด้วยเครื่องจัดการที่เรียกใช้การดำเนินการในคลาสฐานข้อมูล

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

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

ใน C++ โดยทั่วไปเราจะทำโดยการกำหนดไฟล์ส่วนหัวสำหรับแต่ละคลาส คลาสคอมโพสเซอร์มีสมาชิกข้อมูลส่วนตัวสำหรับข้อมูลทั้งหมดที่เราต้องการเก็บไว้ในคอมโพสเซอร์ เราต้องการตัวเข้าถึง ("เมธอด "get") และการเปลี่ยนรูปแบบ (เมธอด "set") รวมถึงการดำเนินการหลักสำหรับคลาสด้วย

// composer.h, Maggie Johnson
// Description: The class for a Composer record.
// The default ranking is 10 which is the lowest possible.
// Notice we use const in C++ instead of #define.
const int kDefaultRanking = 10;

class Composer {
 public:
  // Constructor
  Composer();
  // Here is the destructor which has the same name as the class
  // and is preceded by ~. It is called when an object is destroyed
  // either by deletion, or when the object is on the stack and
  // the method ends.
  ~Composer();

  // Accessors and Mutators
  void set_first_name(string in_first_name);
  string first_name();
  void set_last_name(string in_last_name);
  string last_name();
  void set_composer_yob(int in_composer_yob);
  int composer_yob();
  void set_composer_genre(string in_composer_genre);
  string composer_genre();
  void set_ranking(int in_ranking);
  int ranking();
  void set_fact(string in_fact);
  string fact();

  // Methods
  // This method increases a composer's rank by increment.
  void Promote(int increment);
  // This method decreases a composer's rank by decrement.
  void Demote(int decrement);
  // This method displays all the attributes of a composer.
  void Display();

 private:
  string first_name_;
  string last_name_;
  int composer_yob_; // year of birth
  string composer_genre_; // baroque, classical, romantic, etc.
  string fact_;
  int ranking_;
};

คลาสฐานข้อมูลก็ไม่ซับซ้อนเช่นกัน

// database.h, Maggie Johnson
// Description: Class for a database of Composer records.
#include  <iostream>
#include "Composer.h"

// Our database holds 100 composers, and no more.
const int kMaxComposers = 100;

class Database {
 public:
  Database();
  ~Database();

  // Add a new composer using operations in the Composer class.
  // For convenience, we return a reference (pointer) to the new record.
  Composer& AddComposer(string in_first_name, string in_last_name,
                        string in_genre, int in_yob, string in_fact);
  // Search for a composer based on last name. Return a reference to the
  // found record.
  Composer& GetComposer(string in_last_name);
  // Display all composers in the database.
  void DisplayAll();
  // Sort database records by rank and then display all.
  void DisplayByRank();

 private:
  // Store the individual records in an array.
  Composer composers_[kMaxComposers];
  // Track the next slot in the array to place a new record.
  int next_slot_;
};

โปรดสังเกตวิธีที่เราห่อหุ้มข้อมูลเฉพาะของคอมโพสเซอร์ไว้ในคลาสที่แยกต่างหาก เราอาจวางโครงสร้างหรือคลาสในคลาสฐานข้อมูลเพื่อแสดงถึงระเบียน Composer แล้วเข้าถึงโดยตรง แต่นั่นก็คือ "การมีเหตุผลเป็นกลาง" กล่าวคือ เราไม่ได้สร้างโมเดลด้วยวัตถุมากเท่าที่ทำได้

คุณจะได้เห็นเมื่อเริ่มต้นติดตั้งใช้งานคลาส Composer และฐานข้อมูล การมีคลาส Composer แยกกันจะง่ายกว่ามาก โดยเฉพาะอย่างยิ่ง การดำเนินการแบบอะตอมแยกต่างหากในออบเจ็กต์ Composer จะช่วยให้การติดตั้งใช้งานเมธอด Display() ในคลาสฐานข้อมูลง่ายขึ้นอย่างมาก

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

คุณมักพิจารณาว่าคุณมีความมากเกินไปหรือน้อยเกินไปโดยการทำแผนภาพชั้นเรียนอย่างละเอียด ตามที่ได้กล่าวไว้ก่อนหน้านี้ สิ่งสำคัญคือการออกแบบชั้นเรียนก่อนที่จะเริ่มเขียนโค้ดและช่วยวิเคราะห์แนวทางของคุณได้ สัญกรณ์ทั่วไปที่ใช้สำหรับวัตถุประสงค์นี้คือ UML (Unified Modeling Language) เมื่อเรากำหนดคลาสสำหรับออบเจ็กต์ Composer และฐานข้อมูลแล้ว เราจึงต้องการอินเทอร์เฟซที่ช่วยให้ผู้ใช้โต้ตอบกับฐานข้อมูลได้ เมนูง่ายๆ จะช่วย ให้สิ่งต่อไปนี้

Composer Database
---------------------------------------------
1) Add a new composer
2) Retrieve a composer's data
3) Promote/demote a composer's rank
4) List all composers
5) List all composers by rank
0) Quit

เราสามารถนำอินเทอร์เฟซผู้ใช้ไปใช้เป็นคลาสหรือเป็นโปรแกรมขั้นตอน เนื้อหาบางอย่างในโปรแกรม C++ ไม่จำเป็นต้องเป็นชั้นเรียน อย่างไรก็ตาม หากการประมวลผลเป็นลำดับหรือตามงาน เช่น ในโปรแกรมเมนูนี้ คุณก็นำมาใช้เป็นขั้นตอนได้ การติดตั้งใช้งานโดยให้โค้ดในลักษณะที่ยังคงเป็น "ตัวยึดตำแหน่ง" อยู่เป็นสิ่งสำคัญ กล่าวคือ หากในบางครั้งเราต้องการสร้างอินเทอร์เฟซผู้ใช้แบบกราฟิก เราไม่ควรต้องทำการเปลี่ยนแปลงใดๆ ในระบบ ยกเว้นอินเทอร์เฟซผู้ใช้

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

// test_composer.cpp, Maggie Johnson
//
// This program tests the Composer class.

#include <iostream>
#include "Composer.h"
using namespace std;

int main()
{
  cout << endl << "Testing the Composer class." << endl << endl;

  Composer composer;

  composer.set_first_name("Ludwig van");
  composer.set_last_name("Beethoven");
  composer.set_composer_yob(1770);
  composer.set_composer_genre("Romantic");
  composer.set_fact("Beethoven was completely deaf during the latter part of "
    "his life - he never heard a performance of his 9th symphony.");
  composer.Promote(2);
  composer.Demote(1);
  composer.Display();
}

เราต้องการโปรแกรมทดสอบที่คล้ายกันสำหรับคลาสฐานข้อมูล

// test_database.cpp, Maggie Johnson
//
// Description: Test driver for a database of Composer records.
#include <iostream>
#include "Database.h"
using namespace std;

int main() {
  Database myDB;

  // Remember that AddComposer returns a reference to the new record.
  Composer& comp1 = myDB.AddComposer("Ludwig van", "Beethoven", "Romantic", 1770,
    "Beethoven was completely deaf during the latter part of his life - he never "
    "heard a performance of his 9th symphony.");
  comp1.Promote(7);

  Composer& comp2 = myDB.AddComposer("Johann Sebastian", "Bach", "Baroque", 1685,
    "Bach had 20 children, several of whom became famous musicians as well.");
  comp2.Promote(5);

  Composer& comp3 = myDB.AddComposer("Wolfgang Amadeus", "Mozart", "Classical", 1756,
    "Mozart feared for his life during his last year - there is some evidence "
    "that he was poisoned.");
  comp3.Promote(2);

  cout << endl << "all Composers: " << endl << endl;
  myDB.DisplayAll();
}

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

การออกแบบแอปพลิเคชันของเราเสร็จสมบูรณ์แล้ว ขั้นตอนถัดไปคือการใช้ไฟล์ .cpp สำหรับคลาสและอินเทอร์เฟซผู้ใช้หากต้องการเริ่มต้น ให้คัดลอก/วาง .h และทดสอบโค้ดไดรเวอร์ด้านบนลงในไฟล์ จากนั้นคอมไพล์ได้ใช้ ตัวขับทดสอบเพื่อทดสอบชั้นเรียนของคุณ จากนั้นใช้อินเทอร์เฟซต่อไปนี้

Composer Database
---------------------------------------------
1) Add a new composer
2) Retrieve a composer's data
3) Promote/demote a composer's rank
4) List all composers
5) List all composers by rank
0) Quit

ใช้เมธอดที่คุณกำหนดในคลาสฐานข้อมูลเพื่อติดตั้งใช้งานอินเทอร์เฟซผู้ใช้ ทำให้วิธีการของคุณปลอดภัย เช่น การจัดอันดับควรอยู่ในช่วง 1-10 เสมอ โปรดอย่าอนุญาตให้ใครเพิ่มคอมโพสเซอร์ 101 เว้นแต่คุณวางแผนที่จะเปลี่ยนโครงสร้างข้อมูลในคลาสฐานข้อมูล

โปรดทราบว่าโค้ดทั้งหมดต้องเป็นไปตามรูปแบบการเขียนโค้ดของเราซึ่งระบุไว้ที่นี้เพื่อความสะดวกของคุณ

  • ทุกโปรแกรมที่เราเขียนจะเริ่มต้นด้วยความคิดเห็นในส่วนหัว โดยระบุชื่อผู้เขียน ข้อมูลติดต่อ คำอธิบายสั้นๆ และการใช้งาน (หากเกี่ยวข้อง) ทุกฟังก์ชัน/เมธอดจะเริ่มต้นด้วยความคิดเห็นเกี่ยวกับการดำเนินการและการใช้งาน
  • เราจะเพิ่มความคิดเห็นแบบอธิบายโดยใช้ประโยคแบบเต็มในกรณีที่โค้ดไม่ได้บันทึกด้วยตัวเอง เช่น ในกรณีที่การประมวลผลซับซ้อน ไม่ชัดเจน น่าสนใจ หรือสำคัญ
  • ใช้ชื่อที่สื่อความหมายเสมอ: ตัวแปรคือคำที่มีตัวพิมพ์เล็กใหญ่และคั่นด้วย _ เช่น ตัวแปร my_variable ชื่อฟังก์ชัน/เมธอดใช้อักษรตัวพิมพ์ใหญ่เพื่อทำเครื่องหมายคำ เช่น MyExcitingFunction() ค่าคงที่จะขึ้นต้นด้วย "k" และใช้ตัวอักษรตัวพิมพ์ใหญ่เพื่อทำเครื่องหมายคำ เช่น kDaysInWeek
  • การเยื้องมีค่าพหุคูณของ 2 ระดับแรกคือเว้นวรรค 2 ช่อง หากต้องมีการเยื้องเพิ่มเติม เราจะใช้ 4 ช่อง 6 ช่อง ฯลฯ

ยินดีต้อนรับสู่โลกแห่งความจริง!

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

สร้างไฟล์

โดยปกติแล้ว กระบวนการสร้างโปรแกรมจะจัดการด้วยเครื่องมือบิลด์ ซึ่งคอมไพล์และลิงก์ไฟล์ที่จำเป็นตามลำดับที่ถูกต้อง บ่อยครั้งที่ไฟล์ C++ จะมีทรัพยากร Dependency เช่น ฟังก์ชันที่เรียกใช้ในโปรแกรมหนึ่งอยู่ในอีกโปรแกรมหนึ่ง หรือบางทีอาจต้องการไฟล์ส่วนหัวในไฟล์ .cpp หลายไฟล์ เครื่องมือสร้างตัวกำหนดลำดับการคอมไพล์ที่ถูกต้องจากทรัพยากร Dependency เหล่านี้ และจะคอมไพล์เฉพาะไฟล์ที่มีการเปลี่ยนแปลงตั้งแต่บิลด์ล่าสุดเท่านั้น ซึ่งช่วยประหยัดเวลาได้มากในระบบที่ประกอบด้วยไฟล์หลายร้อยหรือหลายพันไฟล์

เครื่องมือสร้างแบบโอเพนซอร์สที่เรียกว่า "Make" เป็นโปรแกรมที่นิยมใช้กันโดยทั่วไป หากต้องการข้อมูลเพิ่มเติม โปรดอ่านบทความนี้ ลองดูว่าคุณจะสร้างกราฟการใช้ทรัพยากร Dependency สำหรับแอปพลิเคชันฐานข้อมูล Composer แล้วแปลงเป็นไฟล์ Makefile ได้หรือไม่ที่นี่คือวิธีแก้ปัญหาของเรา

ระบบจัดการการกำหนดค่า

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

คู่มือด้านเทคนิคจะถูกจัดเก็บไว้ในคอมพิวเตอร์ที่ทั้งบ็อบและซูซานสามารถเข้าถึงได้ หากไม่มีเครื่องมือหรือกระบวนการ CM ก็อาจเกิดปัญหาได้ สถานการณ์หนึ่งที่เป็นไปได้คือคอมพิวเตอร์ที่จัดเก็บเอกสารดังกล่าวอาจตั้งค่าให้บ็อบและ Susan ทํางานในคู่มือพร้อมกันไม่ได้ ซึ่งจะทำให้เว็บไซต์ช้าลงอย่างมาก

และเกิดเหตุการณ์อันตรายขึ้นเมื่อคอมพิวเตอร์จัดเก็บข้อมูลอนุญาตให้ทั้ง Bob และ Susan เปิดเอกสารได้พร้อมกัน สิ่งที่จะเกิดขึ้นมีดังนี้

  1. บัญชาเปิดเอกสารในคอมพิวเตอร์และทำงานในส่วนนั้น
  2. สิตาเปิดเอกสารในคอมพิวเตอร์และทำงานในส่วนของเธอ
  3. อานนท์ทำการเปลี่ยนแปลงเสร็จแล้วและบันทึกเอกสารไว้ในคอมพิวเตอร์จัดเก็บข้อมูล
  4. สิตาทำการเปลี่ยนแปลงเสร็จสมบูรณ์และบันทึกเอกสารไว้ในพื้นที่เก็บข้อมูลในคอมพิวเตอร์

ภาพนี้แสดงปัญหาที่อาจเกิดขึ้นหากไม่มีการควบคุมในคู่มือด้านเทคนิคฉบับเดียว เมื่อ Susan บันทึกการเปลี่ยนแปลงไว้ เธอจะเขียนทับการเปลี่ยนแปลงที่บัญชาสร้างขึ้น

นี่เป็นประเภทของสถานการณ์ที่ระบบ CM ควบคุมได้ การใช้ระบบ CM ทำให้ทั้ง Bob และ Susan "ตรวจสอบ" สำเนาคู่มือทางเทคนิคของตนเองและดำเนินการแก้ไข เมื่อบัญชากลับไปตรวจสอบสิ่งที่เปลี่ยนแปลงอีกครั้ง ระบบจะรู้ว่า Susan มีสำเนาบัญชีของตนเองที่ชำระเงินแล้ว เมื่อ Susan ตรวจสอบข้อความของเธอ ระบบจะวิเคราะห์การเปลี่ยนแปลงที่ทั้งปวิชและสุชาทำ แล้วสร้างเวอร์ชันใหม่ที่ผสานการเปลี่ยนแปลงทั้ง 2 ชุดเข้าด้วยกัน

ระบบ CM มีฟีเจอร์มากมายนอกเหนือจากการจัดการการเปลี่ยนแปลงที่เกิดขึ้นพร้อมกันตามที่อธิบายไว้ข้างต้น หลายระบบจัดเก็บที่เก็บถาวรของเอกสารทุกเวอร์ชัน ตั้งแต่ครั้งแรกที่สร้างเอกสาร ในกรณีของคู่มือด้านเทคนิค ข้อมูลนี้จะมีประโยชน์มากเมื่อผู้ใช้มีคู่มือเวอร์ชันเก่าและถามคําถามเกี่ยวกับนักเขียนเทคโนโลยี ระบบ CM จะช่วยให้นักเขียนด้านเทคนิคเข้าถึงเวอร์ชันเก่าและเห็นสิ่งที่ผู้ใช้เห็น

ระบบ CM มีประโยชน์อย่างยิ่งในการควบคุมการเปลี่ยนแปลงซอฟต์แวร์ ระบบดังกล่าวเรียกว่าระบบการจัดการการกำหนดค่าซอฟต์แวร์ (SCM) หากคุณพิจารณาไฟล์ซอร์สโค้ดแต่ละไฟล์จำนวนมากในองค์กรวิศวกรรมซอฟต์แวร์ขนาดใหญ่ และมีวิศวกรจำนวนมากที่ต้องทำการเปลี่ยนแปลงไฟล์ ก็เห็นได้ชัดว่าระบบ SCM มีความสำคัญ

การจัดการการกำหนดค่าซอฟต์แวร์

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

ระบบ SCM ทั้งหมดมีฟีเจอร์ที่จำเป็นต่อไปนี้

  • การจัดการการเกิดขึ้นพร้อมกัน
  • การกำหนดเวอร์ชัน
  • การซิงค์ข้อมูล

มาดูรายละเอียดของแต่ละคุณลักษณะกัน

การจัดการการเกิดขึ้นพร้อมกัน

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

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

  1. Client1 เปิด bar.cpp
  2. Client2 เปิด bar.cpp
  3. Client1 เปลี่ยนไฟล์และบันทึกไฟล์
  4. Client2 เปลี่ยนไฟล์และบันทึกทับการเปลี่ยนแปลงของ Client1

แน่นอนว่าเราไม่อยากให้เหตุการณ์นี้เกิดขึ้น แม้ว่าเราจะควบคุมสถานการณ์โดยการให้วิศวกร 2 คนทำงานบนสำเนาคนละชุดกันแทนที่จะทำงานในชุดต้นแบบโดยตรง (ตามภาพด้านล่าง) สำเนาเหล่านั้นก็ต้องมีการปรับยอดในทางใดทางหนึ่ง ระบบ SCM ส่วนใหญ่จะจัดการกับปัญหานี้โดยอนุญาตให้วิศวกรหลายคนตรวจสอบไฟล์ ("ซิงค์" หรือ "อัปเดต") และทำการเปลี่ยนแปลงได้ตามต้องการ จากนั้นระบบ SCM จะเรียกใช้อัลกอริทึมเพื่อรวมการเปลี่ยนแปลงเมื่อไฟล์ได้รับการตรวจสอบอีกครั้ง ("ส่ง" หรือ "คอมมิต") ไปยังที่เก็บ

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

การกำหนดเวอร์ชัน

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

การซิงค์ข้อมูล

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

การโค่นล้ม

Subversion (SVN) เป็นระบบควบคุมเวอร์ชันแบบโอเพนซอร์ส ซึ่งมีฟีเจอร์ทั้งหมดที่อธิบายไว้ข้างต้น

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

เราจะใช้ SVN ตลอดทั้งหลักสูตรนี้เพื่อช่วยให้คุณคุ้นเคยกับการจัดการการกำหนดค่า ระบบดังกล่าวใช้กันมากในอุตสาหกรรม

ขั้นตอนแรกคือการติดตั้ง SVN ในระบบ คลิกที่นี่เพื่อดูวิธีการ ค้นหาระบบปฏิบัติการของคุณและดาวน์โหลดไบนารีที่เหมาะสม

คำศัพท์ SVN บางรายการ

  • การแก้ไข: การเปลี่ยนแปลงในไฟล์หรือชุดไฟล์ การแก้ไขเป็น "สแนปชอต" 1 รายการในโปรเจ็กต์ที่มีการเปลี่ยนแปลงอย่างต่อเนื่อง
  • ที่เก็บ: สำเนาหลักที่ SVN จัดเก็บประวัติการแก้ไขทั้งหมดของโปรเจ็กต์ แต่ละโปรเจ็กต์จะมีที่เก็บ 1 รายการ
  • สำเนาการทำงาน: สำเนาที่วิศวกรทำการเปลี่ยนแปลงในโครงการ โปรเจ็กต์หนึ่งๆ อาจมีสำเนาที่ใช้งานได้เป็นจำนวนมากซึ่งวิศวกรแต่ละรายเป็นเจ้าของ
  • เช็คเอาต์: หากต้องการขอสำเนาที่ใช้งานได้จากที่เก็บ สำเนาที่ใช้งานได้จะมีสถานะเท่ากับสถานะของโปรเจ็กต์เมื่อชำระเงิน
  • คอมมิต: เพื่อส่งการเปลี่ยนแปลงจากสำเนาที่ทำงานอยู่ไปยังที่เก็บส่วนกลาง หรือที่เรียกว่าเช็คอินหรือส่ง
  • อัปเดต: เพื่อนำการเปลี่ยนแปลงของผู้อื่นจากที่เก็บไปยังสำเนาที่ใช้งานได้ หรือเพื่อระบุว่าสำเนาที่ทำงานอยู่ของคุณมีการเปลี่ยนแปลงใดๆ ที่ไม่คอมมิตหรือไม่ ซึ่งจะเหมือนกับการซิงค์ตามที่อธิบายไว้ข้างต้น ดังนั้นการอัปเดต/ซิงค์จึงทำให้สำเนาที่ใช้งานอยู่ของคุณเป็นปัจจุบันด้วยสำเนาที่เก็บ
  • ความขัดแย้ง: สถานการณ์เมื่อวิศวกร 2 คนพยายามทำการเปลี่ยนแปลงในพื้นที่เดียวกันของไฟล์ SVN ระบุความขัดแย้ง แต่วิศวกรต้องแก้ไขปัญหาดังกล่าว
  • ข้อความบันทึก: ความคิดเห็นที่คุณแนบไว้กับการแก้ไขเมื่อคุณยืนยัน ซึ่งอธิบายการเปลี่ยนแปลงของคุณ บันทึกจะแสดงข้อมูลสรุปของสิ่งที่เกิดขึ้นในโปรเจ็กต์

หลังจากติดตั้ง SVN แล้ว เราจะทำตามคำสั่งพื้นฐานบางอย่าง สิ่งแรกที่ต้องทำคือการตั้งค่าที่เก็บในไดเรกทอรีที่ระบุ ต่อไปนี้เป็นคำสั่ง

$ svnadmin create /usr/local/svn/newrepos
$ svn import mytree file:///usr/local/svn/newrepos/project -m "Initial import"
Adding         mytree/foo.c
Adding         mytree/bar.c
Adding         mytree/subdir
Adding         mytree/subdir/foobar.h

Committed revision 1.

คำสั่ง import คัดลอกเนื้อหาของไดเรกทอรี mytree ไปยังโปรเจ็กต์ไดเรกทอรีในที่เก็บ เราดูไดเรกทอรีในที่เก็บด้วยคำสั่ง list ได้

$ svn list file:///usr/local/svn/newrepos/project
bar.c
foo.c
subdir/

การนำเข้าไม่สร้างสำเนาที่ใช้งานได้ คุณต้องใช้คำสั่ง svn checkout เพื่อดำเนินการดังกล่าว ซึ่งจะสร้างสำเนาโครงสร้างไดเรกทอรีซึ่งทำงานได้ มาเริ่มกันเลย

$ svn checkout file:///usr/local/svn/newrepos/project
A    foo.c
A    bar.c
A    subdir
A    subdir/foobar.h
…
Checked out revision 215.

เมื่อคุณมีสำเนาที่ใช้งานได้แล้ว คุณจะทำการเปลี่ยนแปลงไฟล์และไดเรกทอรีได้จากที่นั่น สำเนาที่ทำงานของคุณเหมือนกับคอลเล็กชันไฟล์และไดเรกทอรีอื่นๆ คุณสามารถเพิ่มหรือแก้ไข ย้ายไปรอบๆ หรือจะลบ สำเนาที่ใช้ได้ทั้งหมดก็ได้ โปรดทราบว่าหากคุณคัดลอกและย้ายไฟล์ในสำเนาที่ใช้งานได้ จะต้องใช้ svn copy และ svnmove แทนคำสั่งของระบบปฏิบัติการ หากต้องการเพิ่มไฟล์ใหม่ ให้ใช้ svn add และลบไฟล์โดยใช้ svn Delete หากต้องการแก้ไข เพียงแค่เปิดไฟล์ด้วยโปรแกรมแก้ไขแล้วแก้ไข

มีชื่อไดเรกทอรีมาตรฐานบางชื่อที่มักใช้กับ Subversion ไดเรกทอรี "Trunk" เป็นสายการพัฒนาหลักสำหรับโปรเจ็กต์ ไดเรกทอรี "Branches" จะเก็บเวอร์ชันสาขาที่คุณอาจใช้งานอยู่

$ svn list file:///usr/local/svn/repos
/trunk
/branches

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

A       subdir/new.h      # file is scheduled for addition
D       subdir/old.c        # file is scheduled for deletion
M       bar.c                  # the content in bar.c has local modifications

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

$ svn diff bar.c
Index: bar.c
===================================================================
--- bar.c	(revision 5)
+++ bar.c	(working copy)
## -1,18 +1,19 ##
+#include
+#include

 int main(void) {
-  int temp_var;
+ int new_var;
...

สุดท้าย หากต้องการอัปเดตสำเนาที่ใช้งานได้จากที่เก็บ ให้ใช้คำสั่ง svn อัปเดต

$ svn update
U  foo.c
U  bar.c
G  subdir/foobar.h
C  subdir/new.h
Updated to revision 2.

ความขัดแย้งอาจเกิดขึ้นที่จุดนี้ ในเอาต์พุตด้านบน ตัว "U" บ่งชี้ว่าไม่มีการเปลี่ยนแปลงใดๆ กับเวอร์ชันที่เก็บของไฟล์เหล่านี้ และทำการอัปเดตแล้ว ตัวอักษร "G" หมายความว่ามีการผสาน มีการเปลี่ยนแปลงเวอร์ชันของที่เก็บ แต่การเปลี่ยนแปลงไม่ขัดแย้งกับเวอร์ชันของคุณ ตัวอักษร "C" บ่งชี้ว่าเกิดข้อขัดแย้ง ซึ่งหมายความว่าการเปลี่ยนแปลงจากที่เก็บทับซ้อนกับการเปลี่ยนแปลงของคุณ และตอนนี้คุณต้องเลือกระหว่างการเปลี่ยนแปลงเหล่านั้น

สำหรับทุกไฟล์ที่มีความขัดแย้ง Subversion จะนำไฟล์ 3 ไฟล์ไปไว้ในสำเนาที่ใช้งานได้ ดังนี้

  • file.mine: นี่คือไฟล์ของคุณตามที่ปรากฏในสำเนาที่ใช้งานได้ ก่อนที่คุณจะอัปเดตสำเนาที่ใช้งานได้
  • file.rOLDREV: นี่คือไฟล์ที่คุณนำออกจากที่เก็บก่อนทำการเปลี่ยนแปลง
  • file.rNEWREV: ไฟล์นี้เป็นไฟล์เวอร์ชันปัจจุบันในที่เก็บ

คุณสามารถดำเนินการ 1 ใน 3 วิธีต่อไปนี้เพื่อแก้ไขความขัดแย้ง

  • โปรดตรวจสอบไฟล์และผสานด้วยตนเอง
  • คัดลอกไฟล์ชั่วคราวที่ SVN สร้างทับเวอร์ชันสำเนาที่ใช้งานได้
  • เรียกใช้ svn redirect เพื่อทิ้งการเปลี่ยนแปลงทั้งหมด

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

สิ่งสุดท้ายที่ต้องทำคือคอมมิตเวอร์ชันสุดท้ายของคุณไปยังที่เก็บ ซึ่งทำได้ด้วยคำสั่ง svn Command เมื่อยืนยันการเปลี่ยนแปลง คุณต้องระบุข้อความบันทึกที่อธิบายการเปลี่ยนแปลงของคุณ ข้อความบันทึกนี้จะแนบอยู่กับการแก้ไขที่คุณสร้าง

svn commit -m "Update files to include new headers."  

มีหลายสิ่งอีกมากมายให้เรียนรู้เกี่ยวกับ SVN และวิธีที่ SVN ช่วยสนับสนุนโปรเจ็กต์วิศวกรรมซอฟต์แวร์ขนาดใหญ่ มีแหล่งข้อมูลมากมายบนเว็บ เพียงทำการค้นหา "Subversion" ใน Google

สำหรับการฝึกฝน ให้สร้างที่เก็บสำหรับระบบฐานข้อมูล Composer แล้วนำเข้าไฟล์ทั้งหมดของคุณ จากนั้นตรวจสอบสำเนาที่ใช้งานได้และดำเนินการตามคำสั่งที่อธิบายไว้ข้างต้น

รายการอ้างอิง

หนังสือเวอร์ชันโค่นเวอร์ชันออนไลน์

บทความวิกิพีเดียเกี่ยวกับ SVN

เว็บไซต์เวอร์ชันย่อย

แอปพลิเคชัน: การศึกษาด้านกายวิภาคศาสตร์

ลองดู eSkeletons จากมหาวิทยาลัยเท็กซัส ออสติน