1. 소프트웨어 구조란?


이 책은 ‘어떻게 코딩할 것인가’보다는 ‘어떻게 구조를 잡을 것인가’를 다룬다
모든 프로그램, 심지어 main() 함수에 모두 작성된 코드조차 어떤 식으로든 구조가 있다
무엇이 좋은 구조를 만드는지를 살펴보자

1.1. 좋은 소프트웨어 구조란?


필자는 좋은 구조를 ‘뭔가를 고쳐야 할 때 그럴 줄 알았다는 듯이 코드가 준비되어 있는 것’이라고 생각한다
코드를 거의 건드리지 않고도 적당한 함수 몇 개만 호출하면 원하는 작업을 할 수 있는 그러한 구조를 말한다.
하지만 그러한 코드를 작성하는 것은 쉽지 않다는 것을 모두가 알고 있다.

‘구조’는 ‘변경’과 관련이 있다.
누군가는 코드를 고치고 새로운 기능을 추가해야 한다.

얼마나 코드를 쉽게 고칠 수 있느냐가 구조를 평가하는 척도가 된다.
만약 코드를 고칠 일이 전혀 없다면, 그 코드의 구조는 고민하지 않아도 된다.

1.2. 코드를 고치는 방법


기능 추가, 버그 수정 등의 이유로 코드를 고치려면 기존의 코드를 이해해야 한다.
모든 코드를 이해하는 건 아니더라도, 고치려는 코드와 관련된 부분은 머릿속에 집어넣어야 한다

대부분 이 과정을 대수롭지 않게 여기지만, 사실 프로그래밍에서 가장 시간이 오래 걸리는 작업이다.

데이터를 Disk -> RAM 으로 로딩하는 게 느리다고 하지만, 눈 -> 두뇌 로 로딩하는 것에 비할 바가 아니다.

머릿속의 코드에 대한 큰 그림을 그리고 나면 해결책을 금방 찾을 수 있다.
문제가 무엇인지, 어디를 고쳐야 할지를 이해하고 나면, 실제로 코딩하는 것은 별거 아니다.

코드를 추가했다고 해서, 코드 곳곳에 우리가 남겨놓은 똥(?)에 다른 사람이 걸리적거리게 하고 싶지 않을 것이다.
추가한 코드를 나머지 코드와 깔끔하게 통합하기 위해서는 코드를 정리하는 작업도 필요하다.

프로그래밍 흐름도

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


사람은 머릿속에 코드를 로딩하는 게 굉장히 느리고, 용량도 크지 않기 때문에,
한 번에 이해해야 하는 코드 양을 최대한 줄이기 위한 여러 방법이 나왔다.

코드의 커플링

필자는 디커플링을 다음과 같이 설명한다.
“양쪽 코드 중에서 한쪽이 없으면 코드를 이해할 수 없을 때 둘이 커플링 되어 있다고 본다”

다시 말해서, 두 코드를 디커플링 하면, 각각을 서로를 알지 못하더라도 따로 이해할 수 있게 된다.
그렇게 되면 머릿속에 집어넣는 코드의 양이 줄게 된다.

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

2. 비용?


디커플링이 그렇게 좋다면, 모든 코드를 서로 엮이지 않게 디커플링 하면 되지 않는가?
그렇게 된다면 수정사항이 있어도 메서드 한두 개만 건드리면 금방 끝날 것이다.

이러한 장점 때문에, 사람들은 ‘추상화’, ‘모듈화’, ‘디자인 패턴’에 열광한다.
모두가 구조화가 잘 이루어진 환경에서의 높은 생산성을 원한다.
여기서 말하는 높은 생산성이 얼마나 심오한 차이를 가져오는지는 아무리 강조해도 지나치지 않다.

하지만 당연히 이렇게 좋은 구조를 만드는 것은 쉽지가 않다.
좋은 구조를 만들고 유지하기 위해서는 많은 노력과 원칙이 필요하다.
코드를 조금 변경할 때마다 나머지 코드와 깔끔하게 통합될 수 있도록 노력해야 하고,
수천 번씩 코드를 고치면서도 구조를 유지하려면 노력을 엄청 쏟아부어야 한다.

프로그램의 어디를 디커플링 해야할지,
나중에 쉽게 변경하기 위해서는 어디를 확장성 있게 설계할지도 정해야 한다.

추상화 계층을 추가하거나, 확장 가능성을 제공하는 것은, 그러한 유연성이 나중에 필요할 거라고 예측했기 때문이다.
이를 위해 코드를 추가하고, 복잡성을 늘리다 보면 개발, 디버깅, 유지보수에 시간이 더 걸리게 된다.
예측이 들어맞아 해당 코드를 수정하게 된다면 보상받을 수 있지만, 그렇지 않다면 작업해야 할 코드가 늘어났다는 점에서 안 만드느니만 못하다.
이런 확성성에 심취하게 되면 코드에 ‘인터페이스’, ‘추상화’, ‘플러그인’, ‘추상 클래스’, ‘가상 메서드’, 등 확장 포인트가 덕지덕지 붙으면서 구조가 막 나가게 된다.

이론대로라면, 디커플링이 코드를 고치기 전에 이해해야 하는 코드의 양을 줄여줘야 하지만,
오히려 추상화 계층 그 자체가 머릿속 용량을 꽉 채우게 된다.

Siren’s Song by Manelle Oliphant

이렇게 코드 그 자체에 둘러싸이다 보면, 우리가 무엇을 하고 있는지를 잊기 쉽다.
우리는 코드에 집중할 게 아니라, 게임을 만들기 위해 기능과 콘텐츠를 만들고 있음을 잊으면 안 된다.

3. 성능과 속도


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

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

인터페이스를 사용하는 이유는, 지금 있는 클래스뿐만 아니라
같은 인터페이스를 구현하는 어떠한 클래스가 와도 코드가 문제없이 동작하게 만들기 위해서이다.
당장은 서로 통신하는 부분이 둘밖에 없더라도 나중에 셋, 넷으로 쉽게 늘리고 싶기 때문이다.

하지만 성능은 전부 가정에 기반하며, 최적화 기법은 구체적인 제한을 선호한다.

  • ID가 256개 이하일 거라고 확신할 수 있다면?
    -> ID를 1byte로 압축할 수 있다.
  • 한 자료형의 한 메서드만 호출한다고 확신할 수 있다면?
    -> 가상 함수 없이 정적으로 바인딩된 함수를 바로 호출하거나 인라인으로 작성할 수 있다.
  • 모든 개체가 같은 클래스라면?
    -> 배열에 전부 깔끔하게 집어넣을 수 있다. (데이터 지역성)

프로그램을 유연하게 만들면 성능상 비용이 발생한다.
반대로 코드를 최적화하면 유연성이 떨어진다.

필자의 경험상으로는
재미있는 게임을 최적화 하는 것최적화된 게임을 재미있게 만드는 것보다 횔씬 쉽다.

처음에는 코드를 유연하게 가져가다가, 기획이 확실해지면 추상 계층을 제거해 나가며 성능을 높이는 방법도 있다.

4. 나쁜 코드의 장점


상황에 맞는 코딩 스타일이 따로 있다.
엉성한 코드도 나름의 가치는 있다.

구조화가 잘된 코드를 작성하려면 많이 고민해야 한다.
좋은 구조를 개발 기간 내내 유지하려면 엄청난 시간과 노력이 필요하다.
“야영객이 야영장을 처음 왔을 때보다 더 깨끗하게 해 놓고 떠나는 것” 처럼 코드를 다뤄야 하기 때문이다. (Clean Code의 ‘보이스카우트 규칙’)

오랫동안 + 같은 코드로 계속 작업해야 한다면 이렇게 하는 게 좋다.
하지만 게임을 기획하기 위해서는 실험과 탐구를 많이 해야 한다.
특히 초반에는 나중에 버릴 코드를 작성하는 일이 비일비재하다.

단순히 어떤 기획을 확인하기 위한 코드의 구조를
멋지게 만들려다가는 실제로 화면에 띄워서 피드백을 얻기까지 시간만 더 오래 걸릴 뿐이다.
그 기획이 별로라는 것이 판명 나면, 당신이 작성한 우아한 코드와 쏟았던 시간이 그대로 버려진다.

따라서 기획 확인에 필요한 기능만 간신히 돌아가도록
코드를 작성하는 프로토타이핑 기법은 아주 적절한 프로그래밍 실천법이다.

하지만 이러한 코드를 작성할 때, 나중에 버릴 코드는 확실히 버릴 수 있게 해야 한다.
“프로토타입을 보니까 좋네, 몇 시간만 더 써서 정리하면 출시용으로 구현되지?” 라는 요청을 들어올 수도 있다.

5. 균형 잡기


우리에게는 목표가 있다.

  1. 프로젝트 개발 기간 동안 코드를 쉽게 이해할 수 있도록 구조를 깔끔하게 만들고 싶다.
  2. 실행 성능을 최적화하고 싶다.
  3. 지금 개발 중인 기능을 최대한 빠르게 구현하고 싶다.

아쉽게도 모든 목표를 동시에 달성할 수는 없다.
대신 주어진 상황에 맞게 좀 더 비중을 두고 작업할 뿐이다.

이러한 목표들은 어느정도 서로 상반된다.
좋은 구조를 만들기 위해서는 많은 시간과 노력이 필요하고 어느 정도의 성능 저하도 감수해야 한다.
성능을 최적화하기 위해서는 제한된 상황을 만들게 되므로 유연성과도 거리가 멀어진다.
기능을 빠르게 만들기 위해서 코드베이스에 꼼수와 버그가 있는 코드를 추가하게 되는 것 또한 좋은 구조와는 멀어진다.

어느 분야든 일생을 바쳐야 달인이 될 수 있는 일이라면,
서로 얽혀있는 제약 사항이 있는 법이다.

체스같이 각각의 말들이 서로 완벽하게 균형이 맞는 게임은 절대로 마스터할 수 없다
반면 허접한 게임은 필승 전략 하나만 있어도 이길 수 있기 때문에 금방 지겨워지고 질린다

6. 단순함


좋은 구조, 성능 최적화, 개발 속도의 서로 얽혀있는 제약 사항을 완화하는 방법이 있다고 한다면 그것은 ‘단순함’이다.
필자는 코드를 최대한 ‘간결하게’, ‘문제를 직접 해결하는 방향’ 으로 짜려고 노력한다.

코드를 단순하게 유지한다면, 코드를 이해하기 위해 머릿속에 넣어야 하는 코드의 양도 줄어든다.

코드가 단순하다고 해서 짜는 데 걸리는 시간도 적은 건 아니다.
전체 코드가 줄어들기 때문에, 걸리는 시간도 줄어드는 것이 아닌가 착각할 수 있다.

Nicholas Burroughs

파스칼은 “시간이 없어서 편지를 짧게 쓰지 못했습니다”라고 쓴 적이 있다.
생택쥐페리는 “완벽함이란 보탤 것이 없을 때가 아니라 뺄 것이 없을 때 성취된다”고 했다.

프로그램 개발 상황에서의 문제들은 대부분 우아하지 않고, 수많은 유스케이스로 뒤덮여 있다.
조건 A일 때는 B를 해야 하지만, C일 때는 D를 해야 하는 식이다.
즉 수많은 상황을 지원해야 한다.

가장 간단한 해결책으로는 모든 유스케이스를 코드로 옮기는 것이다.
if() ~ else if() ~ else () 와 같이 조건문을 마구 늘어놓는 것이다.
초보 개발자들이 흔히 이렇게 한다.

이러한 코드는 전혀 우아하지 않고, 조금만 다른 유스케이스가 등장해도 동작하지 않는다.
우아한 해결책이란 ‘일반적’이고 ‘적은 로직으로 많은 유스케이스를 처리할 수 있는’ 코드는 뜻한다.

유스케이스로부터 숨어있는 규칙을 찾기란 쉽지 않지만, 찾아낸다면 굉장한 성취감을 느낄 수 있다.

7. 마치며


마지막으로 몇 가지 조언하겠다.

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

중요한 단어 및 내용


단어 : 추상화, 디커플링
내용 : 작업에 들어가기 전에 알아야 할 지식의 양을 줄이는 것은 좋은 소프트웨어 구조의 핵심이다.

카테고리:

업데이트:

댓글남기기