포스트

[Clean Code][TIL] 형식 맞추기

이 포스트는 로버트 C.마틴의 Clean Code 속 5장(96p ~ 116p) 내용에 대한 후기입니다.


“좋은 소프트웨어 시스템은 읽기 쉬운 문서로 이뤄진다.” (5장 114p)


형식을 맞추는 목적

코드를 작성하면서 돌아가는 코드 뿐만 아니라 코드 작성 형식까지 고려해야할까요?
형식의 필요성을 확인하기 위해 내용이 같지만 형식이 다른 2개의 코드를 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 줄내림이 없는 코드
class PacketWorker
{
public:
	PacketWorker() { };
	PacketWorker(PlayerManager* player_manager, PartiesManager* parties_manager): overlapped(nullptr), players(player_manager), parties(parties_manager){ }
	~PacketWorker() { }
	void accept_new_client();
	bool check_exists_overlapped(OverlappedExpansion* overlapped);
	void disconnect(int user_id);
	int get_new_client_ticket();
	void process_packet(int, char*);
	void send_log(std::string log);
	void set_new_client_ticket(int player_ticket);
	void sync_new_chatting_all_client(int user_id, char* packet);
	void recv_new_message(OverlappedExpansion* exoverlapped);
	void run_packet_worker_threads(HANDLE h_iocp);
	void worker_thread(HANDLE h_iocp);
private:
	HANDLE iocp_handle;
	DWORD num_bytes;
	ULONG_PTR key;
	WSAOVERLAPPED* overlapped;
	BOOL GQCS_result;
	OverlappedExpansion accepter_overlapped;
	HANDLE handle_iocp;
	SOCKET server_socket;
  SOCKET client_accept_socket;
	PlayerManager* players;
	PartiesManager* parties;
};


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 줄내림이 있는 코드
class PacketWorker
{
public:
	PacketWorker() { };
	PacketWorker(PlayerManager* player_manager, PartiesManager* parties_manager)
		: overlapped(nullptr)
		, players(player_manager)
		, parties(parties_manager)
	{ }
	~PacketWorker() { }

	void send_log(std::string log);

	void run_packet_worker_threads(HANDLE h_iocp);
	void worker_thread(HANDLE h_iocp);

	void accept_new_client();
	void recv_new_message(OverlappedExpansion* exoverlapped);

	int get_new_client_ticket();
	void set_new_client_ticket(int player_ticket);

	void process_packet(int, char*);

	bool check_exists_overlapped(OverlappedExpansion* overlapped);
	void sync_new_chatting_all_client(int user_id, char* packet);
	void disconnect(int user_id);

private:
	HANDLE iocp_handle;
	DWORD num_bytes;
	ULONG_PTR key;
	WSAOVERLAPPED* overlapped;

	BOOL GQCS_result;

	OverlappedExpansion accepter_overlapped;
	HANDLE handle_iocp;
	SOCKET server_socket, client_accept_socket;
	
	PlayerManager* players;
	PartiesManager* parties;
};


2개의 코드에서 차이점이 보이시나요?
첫 번째 코드는 한 줄에 한 개의 메서드 혹은 필드를 작성했고
두 번째 코드는 범위와 기능에 따라 줄내림을 넣었습니다.

코드 밀집도

관련 있는 기능이나 종속성이 있는 것들을 한 곳에 묶어두면 세로 밀집도 가 증가하여
특정 메서드 혹은 필드의 역할을 파악에 도움이 될 수 있습니다.
그리고 줄내림이 적절히 들어가면 행들이 묶음으로 보여서 서로 관련 있음을 추측할 수 있습니다.

이때, 행의 순서는 호출하는 함수는 앞에 배치하고 호출되는 메서드를 아래쪽에 배치함으로서
소스 코드를 읽을 때, 고차원에서 저차원으로 자연스럽게 읽을 수 있습니다.

세로 밀집도가 묶음으로 기능을 파악하게 도와줬다면 가로 밀집도는 한 행 을 읽는데 도움됩니다.
가로 길이는 코드의 내용이 충분히 서술적일 때, 120자 이내가 이상적입니다.

120자 안에서는 여러 글자가 들어가겠지만 공백을 사용해 가독성을 높일 수 있습니다.
예를들어 수식을 읽을 때, 공백의 유무에 따른 가독성을 보겠습니다.

1
2
3
4
5
// 1번 수식
return (-b + sqrt(determinant) / (2*a));

// 2번 수식
return (-b+sqrt(determinant)/(2*a));


공백이 들어가면 수식에서 연산자의 위치를 한 번에 알 수 있어 가독성이 높아집니다.
반면, 공백이 없으면 가독성이 매우 떨어진 것을 확인할 수 있습니다.

들여쓰기 무시하기

코드를 작성하다 보면 매우 짧은 if문, while문 혹은 짧은 함수를 한 줄로 작성하고 싶은 유혹이 있습니다.

1
2
void set_number(int in_number) { this.number = in_number; }
int get_number() { return number; }


그러나 위의 코드처럼 일부만 한 줄로 작성되어 있는 것은
작성자가 아닌 다른 사람이 코드를 읽을 때, 일관성이 떨어져 코드에 대한 신뢰성이 떨어질 수 있습니다.
따라서 가능하면 이전 코드들과 같이 들여쓰기를 일관되게 작성하는 것이 좋습니다.

1
2
3
4
5
6
7
void set_number(int in_number) { 
  this.number = in_number; 
}

int get_number() { 
  return number; 
}


훌륭한 형식

가장 잘 맞춰신 코드 형식은 아주 좋은 신문 기사 처럼
최상단에 이름만 보고도 올바른 기능을 수행하고 있는지 판단할 수 있고
첫 부분에는 고차원의 개념과 알고리즘 설명이 포함하고
아래로 내려갈수록 의도를 세세하게 묘사한 뒤
마지막에는 가장 저차원 함수와 세부 내용을 작성하는 것이 가장 훌륭한 형식으로 이루어져 있습니다.

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