공부/디자인 패턴

[게임 프로그래밍 패턴]1. 구조, 성능, 게임

개발의 피 2023. 5. 2. 22:29

1.1. 소프트웨어 구조란?

'어떻게 구조를 잡을 것인가' 

모든 프로그램, 코드는 어떤 식으로든 구조가 있다. 좋은 구조와 나쁜 구조를 어떻게 구별할 수 있을까?

 

- 좋은 소프트웨어 구조란?

'구조는 변경과 관련이 있다' -> 코드 설계를 평가하는 척도 : 얼마나 쉽게 변경할 수 있느냐

 

- 코드를 고치는 방법

기능 추가, 버그 수정, 그 외 무슨 이유에서든지 간에 코드를 고쳐야 한다면, 먼저 기존 코드를 이해해야 한다. 전체 프로그램을 다 이해해야 하는 건 아니지만, 고치려는 코드와 관련된 부분은 머릿속에 집어넣어야 한다. 대부분 이 과정을 대수롭지 않게 여기지만, 사실 프로그래밍에서 가장 오래 걸리는 부분. 

- 프로그래밍 흐름도 

 

- 디커플링은 어떻게 도움이 되는가?

양쪽 코드 중에서 한쪽이 없으면 코드를 이해할 수 없을 때 둘이 커플링되어 있다고 본다. 두 코드를 디커플링하면, 각각을 따로 이해할 수 있게 된다. 작업에 관련된 코드가 두 코드 중에서 하나에만 연관되어 있다면, 한쪽 코드만 머리에 집어넣으면 된다. 

디커플링 : 어느 한 코드를 변경했을 때 다른코드를 변경하지 않아도 된다. 작업을 위해서는 코드 어딘가를 고쳐야 하지만, 커플링이 적은 코드일수록 변경이 나머지 게임 코드에 미치는 영향이 적다.

 

소프트웨어 구조의 핵심 목표 : 작업에 들어가기 전에 알아야 할 지식의 양을 줄이는 것.

 

1.2. 비용은? (-> 확장성은 세이렌의 노래가 될 수도 있다)

좋은 구조(추상화, 모듈화, 디자인 패턴, 소프트웨어 구조)는 생산성을 크게 높여준다. 이게 얼마나 심오한 차이를 가져오는지는 아무리 강조해도 지나치지 x

좋은 구조를 만들고 유지하려면 많은 노력과 원칙이 필요하다. 기능을 하나 추가하거나 코드를 조금 변경할 때마다 나머지 코드와 깔끔하게 통합될 수 있도록 노력해야 한다. 코드 구조를 잘 잡고, 개발 주기마다 수천 번씩 코드를 고치면서도 구조를 유지하려면 노력을 엄청 쏟아야 한다. 

어려워지는 부분 : 추상화 계층을 추가하거나 확장 가능성을 제공하는 것은, 그런 유연성이 나중에 필요할 거라고 예측했기 때문. 이를 위해 코드를 추가하고, 복잡성을 늘리다 보면 개발, 디버깅, 유지보수에 시간이 더 걸리게 됨. 예측이 맞아서 해당 코드를 수정할 일이 생긴다면 지금 들인 노력을 보상 받을 수 있지만, 미래를 예측하기란 어렵다. 만들어놓은 모듈을 써먹지 못한다면, 작업해야 할 코드가 늘어났다는 점에서 안 만드니만 못하다. 

이런 확장성에 심취하게 되면 코드에 인터페이스, 추상화, 플러그인 시스템, 추상 클래스, 수많은 가상 메서드, 온갖 확장 포인트가 덕지덕지 붙으면서 구조가 막 나가게 된다. 보조 코드가 늘어나면 실제 작업 코드를 찾는 게 오래 걸리게 된다. 이렇게 코드 그 그 자체에 둘러싸이다 보면, 게임을 출시해야 한다는 사실을 잊기 쉽다. 

 

1.3. 성능과 속도

소프트웨어 구조와 추상화가 게임 성능을 저하시킨다는 비판도 존재. 코드를 유연하게 만드는 많은 패턴이 가상 함수, 인터페이스, 포인터, 메시지 같은 메커니즘에 의존하는데, 다들 어느 정도 런타임 비용을 요구.

(반례 : C++ 템플릿! 템플릿 메타프로그래밍은 런타임 낭비 없는 인터페이스 추상화 방법을 제공) 

 

많은 소프트웨어 구조는 코드를 더 유연하고 쉽게 변경할 수 있게 만들기 위해 존재 = 프로그램에서 가정을 줄인다

하지만 성능은 전부 가정에 기반. 최적화 기법은 구체적인 제한을 선호 (유연성이 나쁘다는 것 결코 x)

 

쉬운 답은 없다. 프로토타이핑을 빠르게 하기 위해서 프로그램을 유연하게 만들면 성능상 비용이 발생. 반대로 코드를 최적화하면 유연성이 떨어짐. 경험상으로는 재미있는 게임을 최적화하는 것이 최적화된 게임을 재미있게 만드는 것보다 훨씬 쉬움. 처음에는 코드를 유연하게 유지하다가 기획이 확실해진 다음에 추상 계층을 제거해 성능을 높이는 타협안도 존재. 

 

1.4. 나쁜 코드의 장점

프로토타이핑 기법 : 기획 확인에 필요한 기능만 간신히 돌아가도록 대강 코드를 작성 ('버릴 코드'는, 나중에 확실히 버릴 수 있게 해야 한다.)

vs 구조화가 잘된 코드 : 오랫동안 같은 코드로 계속 작업할 경우

 

1.5. 균형 잡기

1. 프로젝트 개발 기간 동안 코드를 쉽게 이해할 수 있도록 구조를 깔끔하게 만들고 싶다.

2. 실행 성능을 최적화하고 싶다.

3. 지금 개발 중인 기능을 최대한 빠르게 구현하고 싶다.

세 목표는 서로 어느 정도 상반됨. 좋은 구조는 장기적으로 생산성을 높여주지만, 구조를 좋게 유지하려면 코드를 변경할 때마다 노력을 더 들여야 한다. 

셋 다 각각 장단점이 있기 때문에 트레이드오프 외에는 명쾌한 정답 x. 

 

하지만 이런 어려움은 신나는 일이다! 어느 분야든지 일생을 바쳐야 달인이 될 수 있는 일이라면, 서로 얽혀 있는 제약사항이 있는 법. 쉬운 방법이 있다면 다들 그것만 하고 있을 것. 1주일 안에 숙달할 수 있는 분야라면 금방 지겨워지기 마련. '삽질'로 굉장한 경력을 쌓을 수 있다.

 

1.6. 단순함

이런 제약을 완화할 방법 : 단순함

최대한 간결하게, 문제를 직접 해결하는 방향으로 짠 코드 -> 읽어보면 의도를 바로 알 수 있고, 그 외의 다른 해결 방법은 떠오르지 않는 법

자료구조와 알고리즘을 (순서대로) 먼저 잡아놓고, 여기서부터 다른 방법을 찾아나가기. 코드를 단순하게 유지하면 전체 코드를 줄일 수 있다. 

코드가 단순하다고 해서 짜는 데 걸리는 시간도 적은 건 x. 해결책 : 코드를 덧붙이는 게 아니라 필요 없는 코드를 최대한 빼는 것. 

 

초보 개발자 : 모든 유스케이스를 하나하나 코드로 옮김(생각나는 대로 조건문을 마구 늘어놓음) <- 전혀 우아x, 예상과 조금만 다른 입력이 들어와도 전혀 동작하지 x

우아한 해결책 : 일반적인(적은 로직으로도 많은 유스케이스를 정확하게 처리할 수 있는) 코드

 

1.7. 마치며

<조언>

  • 추상화와 디커플링을 잘 활용하면 코드를 점차 쉽고 빠르게 만들 수 있다. 하지만, 지금 고민 중인 코드에 유연함이 필요하다는 확신이 없다면 추상화와 디커플링을 적용하느라고 시간 낭비하지 말자.
  • 개발 내내 성능을 고민하고, 최적화에 맞게 설계해야 한다. 하지만 가정을 코드에 박아 넣어야 하는 저수준의 핵심 최적화는 가능하면 늦게 하라.
  • 게임 기획 내용을 확인해볼 수 있도록 빠르게 개발하되, 너무 서두르느라 코드를 엉망으로 만들지 말자. 결국 그 코드로 작업해야 하는 건 우리다.
  • 나중에 버릴 코드를 잘 만들겠다고 시간 낭비하지 말자. 록 스타들이 호텔 방을 어지르는 이유는 다음 날 계산하고 나가면 그만이라는 것을 알기 때문이다.
  • 무엇보다, 뭔가 재미있는 걸 만들고 싶다면 먼저 만드는 데에서 재미를 느껴보라.