[ING][정리] C와 C++ 게임 코드로 알아보는 코딩의 기술

Yeonjoon Kim·2021년 2월 23일
0
post-thumbnail

History
210223 ~ 1.4 assert 활용
210301 ~ 1.5 제어문
TODO: 210302 ~ 1.6 함수

1장. 읽기 좋은 코드를 작성하는 기술

1. 읽기 좋은 코드란?

읽기 좋은 코드 "의도를 명확하게 전달할 수 있는 코드"
다양한 기술을 사용한다 해도 의도를 제대로 전달할 수 없다면 의미가 없다.

읽기 좋은 코드를 작성하는 요령을 간단하게 설명하면 '복잡한 코드 문제를 작게 나누고, 읽기 좋은 이름을 붙여 정리하자' 이다.

2. 변수와 상수

의미가 명확한 변수 이름 붙이기

  • 예외적으로 배열의 인덱스 등은 짧은 이름이 좋다
  • 참조되는 범위가 넓은 변수, 수명이 긴 변수는 신중하게 이름 정할 것
for (int i=0; i < 4; ++i) {
  for( intj=0; j< 4; ++j) {
    matrix[i][j] = 0;
  }
}

매직 넘버에 이름 붙이기

  • 열거형(enum) 등을 사용해서 매직 넘버에 이름 붙이자.
enum State {
  STATE_WALK,
  STATE_JUMP,
  STATE_ATTACK
}

3. 조건식과 계산식

설명 전용 변수를 사용, 계산식 또는 조건식을 함수화

  • const keyword 를 이용해 설명 전용 변수를 지역변수로 선언
  • 설명 전용 변수로 대응했던 것들을 함수화하면 재사용 및 단위테스트 가능
const bool isJump = y > 0.0f;
const bool isDamage = state == STATE_DAMAGE;
const bool isDash = (speed >= 10.0f) && !isJump && !isDamage;

bool isJump() {
	return y > 0.0f;
}

bool isDamage() {
	return state == STATE_DAMAGE;
}

bool isDash() {
	if(speed < 10.0f) return false;
    if(isJump()) return false;
    if(isDamage()) return false;
    return true;
}


if(isDash or isDash()) {
  ...
}

하나의 의미를 나타내는 코드라면 비록 한 줄이라고 해도 함수화해야 하는 후보가 된다.

4. assert 활용

  • 사전 또는 사후 조건의 검사
  • assert( condition == true ) 체크, 배열의 인덱스 범위 등...
  • switch default 절에 assert 를 추가하기도 함.
  • 일반적으로 Debug 모드에서만 동작하고, Release 모드에서는 모두 무효화
  • 문자열 상수가 0번지 이외에 저장되는 것을 이용해 assert에 주석을 달 수 있음. ex> assert(!"부정확한 상태");
void display_week(int week) {
  static const char* name[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
  assert(0 <= week && week <= 6 && "정해지지 않은 요일");
  std::cout << name[week];
}

5. 제어문

코드를 복잡하게 만드는 문제의 근원은 if 조건문 또는 for 반복문 등의 제어문 이다.

if 조건문

  • 하한값, 상한값 체크 단순화 (STL 이용)
int clamp(int x, int low, int high) {
  assert(low<=high);
  return std::min(std::max(x, low), high);
}

x = clamp(x, 0, 10);
  • 랩 어라운드의 함수화 (like circular queue)
if(x >= 10) x = 0;
x = x % 10;
  • 조기 리턴 활용
  • 중복된 조건식 통합
  • 중복된 복합 조건 통합
if(state == STATE_FULL && wait_timer > WAIT_TIME) fall();
if(state == STATE_MOVE && wait_timer > WAIT_TIME) move();

if(wait_timer > WAIT_TIME) {
  if(state == STATE_FULL) fall();
  if(state == STATE_MOVE) move();
}
  • 배열을 활용한 if 조건문 제거
    - 배열, map, set 등 자료구조를 이용해 불필요한 if 조건문들을 단순화 한다.
  • 결정표를 사용한 if 조건문 제거
    - ex>가위바위보 게임

초보자는 조건이라는 말을 들으면 일단 if 조건문이 필요하다고 먼저 생각하기 쉽다.
하지만 코드의 보수성을 높이려면 if 조건문을 사용하지 않고 해결할 방법을 생각하는 것이 중요하다.

  • null 객체 도입
    - null 객체란 null 포인터를 대신하는 더미 객체를 의미한다. (객체 존재유무 확인 필요 x)
//플레이어가 존재하는지 확인
if(player != nullptr) {
  //존재한다면 이동
  player->move();
}
...
if(player != nullptr) {
  player->draw();
}

class Actor {
  ...
  virtual void move() = 0;
  virtual void draw() = 0;
};

class NullActor : public Actor {
  ...
  virtual void move() override {}
  virtual void draw() ovrride {}
}

player = new NullActor();
player->move();
player->draw();

for 반복문

for 반복문의 기본 원칙은 '1개의 작업만 반복' 이다.

  • STL함수를 활용해서 반복문을 단순화하자.
//for_each
std::for_each(actors.begin(), actors.end(), [](Actor* actor) { actor->draw(); });

//find_if
auto player = std::find_if(actors.begin(), actors.end() 
  [](Actor* actor) { return Actor->id() == PLAYER;});
if(player != actors.end()) {
  attack(*player);
}

//min_element, max_element
//all_of
//count_if
//accumulate
...etc
  • 검색 반복문 분리
//반복
for(iter i = actors.begin(); i != actors.end(); ++i) {
  //검색
  if((*i)->distance(position) <= 5) {
    attack(*i);
    break;
  }
}

//검색부분 함수화
auto target = findTarget(position, 5);
if(target != nullptr) {
  attack(target);
}
  • 반복문 내부의 불필요한 조건 분리
    - 외부로 뺄 수 있는 조건문은 빼준다.

  • 반복문 분할

    코드를 짧게 만들기 위해 1개의 반복문에 여러 개의 처리를 집어넣으면 코드가 복잡해져 버린다.
    단순한 for 반복문으로 나누고 STL 알고리즘으로 치환해주자.

  • 람다식을 활용한 반복문 일반화

switch 조건문의 감축과 단순화

  • case 내부는 가독성을 위해 분기에만 집중하자.
  • default 에는 assert를 넣어서 단순 실수를 방지하자.
  • switch 조건문의 대부분은 다형성을 사용한 분기로 변경할 수 있다 -> State pattern

6. 함수

profile
write down the problem, think very hard, write down the answer.

0개의 댓글