반면교사
요즘 모종의 코드 — 부모가 버려버린(?) — 을 보고있는데 참 보고 배울게 많다(?).
서버 로직에서 네트웍과 로직 부분을 분리하라
이 둘이 같이 붙어있으면 사실 상 리팩터링하는 수 밖에 없다. 둘을 따로 테스트 할 수도 없고, 그냥 지뢰밭이 된다. 고치는건 살얼음길…
프로토콜 메시지의 형태(C++의 struct/class)가 그대로 서버 로직과 같이 움직이면 이건 어쩌자는거냐. 즉, 프로토콜 메시지 형태를 바꾸는 일 == 전체 서버의 재컴파일 및 로직 부분을 리뷰해야하는 사태가 생기는 코드를 보고 있자니 느는건 한숨이오 Orz
게다가 패킷을 생성하고 보내는 코드가 한 곳(class or function)에 응집되어있지 않고, 수많은 곳에서 조용히 패킷을 만들고 보내는 일을 하고 있다. 싸우자!
그래도 제일 무서운 건 이것. 대체 왜 프로토콜 메시지를 위한 구조체들을 DB 쪽 래퍼에서 알고 있고, 이걸 일일이 바인드(SQL Bind)해서 쓰고있더라?
불필요한 메모리 할당을 피하자
이 코드베이스에선 대부분의 네트웍 전송을 위한 준비 단계에서,
- 동적으로 객체를 위한 메모리를 할당받고
- 객체에 다른 (보낼) 객체를 저장하고
- 이를 다시 실제로 보낼 컨테이너 객체 류에 삽입
한다는 것, 보내는 곳에서도 이 일을 하는데 더 이상한건(…) 이걸 결국엔 다 지워 -_-;; 버린다.
소멸자에서 버그가 있는 경우도 있고 Orz
C++ 의 강점 중의 하나는 메모리 할당 공간을 프로그래머가 제어할 수 있다는 점일 터인데; 저런 용도라면 아주 자연스러운 공간이 있질 않은가 -_-;; 스택이라고(…).
불필요한 캐싱을 하지 말자
대부분의 객체 관리자가,
- 객체
- 객체의 네트웍 전송 형태(Packet 을 보내기 위한 버퍼 형태로)
두 가지를 다 가지고 있다. 게다가 내부 업데이트에 객체가 아니라 객체의 네트웍 전송형태를 주고받기도 하고 (덕분에 애써 de-serialize한 객체를 다시 serialize 했다 다시 deserialize 해가면서 쓴다)
개인적으론 메모리 소모량이 적은 서버 프로그램이 전반적인 반응성도 좋다고 본다. 메인 메모리가 빨라봐야 L1/L2 캐쉬에 비하면야.
일단 캐슁하기 위해 불필요한 연산이 들어가고 / 이게 바뀔 수도 있으니 (특히 분산 서버에서) consistency도 맞춰줘야하고 / 그리고 로컬 객체를 패킷으로 만들어 보내기 위한 연산은 대부분 CPU-local 한 연산이라 CPU를 늘릴 수 있는 가능성만 있으면 우선순위가 아주 낮은 최적화 작업이다.
Compiler wraning을 줄이자
서버 쪽 코드만 주로 보는데, 이쪽을 풀 빌드하면 현재 3948개의 compiler-warning을 보게된다. 요 몇 일 사이에 이걸 최대한 줄이려고 삽질 중이긴한데, 이걸 줄이다보니 실제로 의미있는 경고 메시지가 수 개 발견된다.
warning이 좀 뜨면 그걸 해결해놔야지 대체 개발을 어찌하려고 이 모양이었는지.
사실 이 원인 중에 하나는 매크로를 지나칠 정도로 사용했다는게 문제인 것 같지만; inline 함수로 처리하거나 멤버 함수 하나 추가하면 될 일을 / 상속 계통의 최상 위에 함수를 만들면 될 일을, 타입과, 객체를 일일이 지정하는 매크로로 해놓으니 코드 보기가 뭣 같더라.
정말 이러지 말아야지 싶은 코드를 한가득 보고 있다. “개발 과정"에 관한 것만으론 필생의 적을 만난 것 같은 기분?