포스트

[SideProject] 어째서 코드를 깨끗하게 만들어야할까

프로그램의 소스 코드는 한 번에 완벽하게 작성할 수 없습니다.
팀 프로젝트에서는 한 팀원이 작성한 코드를 다른 팀원이 읽을 수 있고
개인 프로젝트에서도 기능을 추가하거나 수정하다 보면 이전 코드를 읽을 때가 있습니다.
여기서 이전 코드를 이해하는데 시간이 소모될 수 있고
최악의 경우, 매번 같은 코드 이해하고자 처음부터 다시 읽어야 될 수 있습니다.
심지어는 코드 작성자 조차 과거 자신이 작성한 코드를 이해하지 못한다면
프로젝트 진행에 있어 심각한 문제를 초래할 수 있습니다.


더러운 소스 코드


깨끗한 코드가 “읽으면서 짐작했던 기능을 각 루틴이 그대로 수행하는 코드” 라면
반대인 “읽으면서 짐작했던 기능과 다르게 수행하는 코드” 는 최악의 코드라고 할 수 있습니다.
이 외에도 일관성 이 없으며, 가독성 이 낮은 코드들도 최악의 코드라고 할 수 있습니다.

제가 작성한 최악의 코드 중 하나는 main.cpp 코드가 700 줄 코드입니다.
프로그램은 실행되지만 프로그래머의 역할 중 하나인 유지 보수 를 망각한 코드입니다.

700줄 짜리 코드의 문제점은 여러 가지가 존재하지만 가장 심각한 문제점은 다음과 같습니다.

  • 함수, 전역 변수, 지역 변수 등 모든 것에 일정한 명명 규칙 이 없다.
  • 객체 지향 언어(C++)를 사용했음에도 전역 함수와 전역 변수를 빈번하게 사용한다.
  • 특정 함수에 역할이 너무 많다.

대부분의 문제점은 결국 깨끗하지 않은 코드 가 원인입니다.
따라서 이 문제점을 해결하고자 리팩토링을 진행하게 되었습니다.


깨끗해지고 있는 코드


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#pragma once

#include "NetworkSettings.h"
#include "GameMaker.h"

int main() {
	NetworkSettings* network_settings = new NetworkSettings(ULONG(INADDR_ANY), USHORT(PORT_NUM));

	GameMaker* game_maker = new GameMaker();
	array<Client, 3>* clients = game_maker->get_clients();

	SOCKET* listen_socket = network_settings->get_listen_socket();
	ClientAccepter client_accepter(listen_socket);
	while (false == client_accepter.accept_all_client(clients)) {
		network_settings->reset_listen_socket();
	}

	game_maker->run_game();
	game_maker->cleanup_game();

	network_settings->close_listen_socket();
	return 0;
}

위 소스 코드는 리팩토링을 진행 중인 main.cpp 코드 입니다.
이 코드가 완벽하고 깨끗한 코드라 장담할 수 없지만 이전 코드와 비교한다면
기능을 유지하면서 700 줄에서 23 줄로 줄이고 가독성 을 최대한 높이고자 하였습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 서버 소스 코드
class PacketSender {
public:
	PacketSender(array<Client, 3>* member);

	virtual void send_packet(Client& client, void* packet);
	virtual void sync_send_packet(void* packet);

	array<Client, 3>* clients;
};

class StageUpdatePacket : public PacketSender {
public:
	StageUpdatePacket(array<Client, 3>* member);

	void sync_send_packet(void* packet) override;
};

class ClientMovePacket : public PacketSender {
public:
	ClientMovePacket(array<Client, 3>* member);

	void sync_send_packet(void* next_position) override;
};

FWE(Fireboy Watergirl and Earthman) 프로젝트는 단일 서버와 3개의 클라이언트가
소켓으로 통신해 즐길 수 있는 게임입니다.
따라서 게임에 사용될 여러 패킷들이 존재하며 종류에 따라 수행하는 동작도 다릅니다.

모든 동작을 한 함수에서 수행할 경우 함수 외부에서 보았을 때, 동작을 예상하기 어렵습니다.
따라서 저는 클래스의 상속과 가상 함수로 예상되는 코드 를 작성하고자 하였습니다.

서버의 소스 코드 이므로 PacketSender 는 서버에서 패킷을 송신하는 클래스이고
이 클래스의 자식 클래스들은 모두 서버에서 클라이언트로 송신하는 클래스들 입니다.
코드에서 ClientMovePacket 클래스는 클라이언트(유저)가 움직였을 때 사용되는 클래스이며
sync_send_packet 메서드는 모든 클라이언트와 동기화(sync) 하는 동작을 합니다.


앞으로 해야할 일


로버트 C.마틴의 책 CleanCode에서 유지 보수는 결코 끝나지 않는다. 라는 말처럼
지금 작성한 코드가 나중에도 보기 좋다는 보장은 어디에도 없습니다.

앞으로도 꾸준하게 코드를 유지 보수하면서 깨끗한 코드에 근접하고자 합니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.