[Clean Code][TIL] 의미 있는 이름
이 포스트는 로버트 C.마틴의 Clean Code 속 2장(22p ~ 38p) 내용에 대한 후기입니다.
“여느 코드 개선 노력과 마찬가지로 이름 역시 나름대로 바꿨다가는 누군가의 질책을 받을지 모른다.
그렇다고 코드를 개선하려는 노력을 중단해서는 안 된다.” (2장 38p)
이름을 잘 짓는 규칙
로버트는 이름을 잘 짓는 규칙으로 15개의 규칙을 제시 했습니다.
- 그릇된 정보를 피하라
- 의미 있게 구분하라
- 발음하기 쉬운 이름을 사용하라
- 검색하기 쉬운 이름을 사용하라
- 인코딩을 피하라
- 자신의 기억력을 자랑하지 마라
- 클래스 이름
- 메서드 이름
- 기발한 이름은 피하라
- 한 개념에 한 단어를 사용하라
- 말장난을 하지 마라
- 해법 영역에서 가져온 이름을 사용하라
- 문제 영역에서 가져온 이름을 사용하라
- 의미 있는 맥락을 추가하라
- 불필요한 맥락을 없애라
위의 내용 중에서 “6. 자신의 기억력을 자랑하지 마라”, “클래스 이름, 메서드 이름”, “한 개념에 한 단어를 사용해라”
4가지 개념이 특히 중요하다고 생각합니다.
프로그램은 대부분 함께 만들기 때문에 작성한 코드도 여러명이 읽습니다.
만약 명료하지 않은 이름 을 사용해 작성자만 기억하는 코드가 된다면
작성할 때는 편할 수 있지만 다른 팀원에게 직접 설명해줘야 하는 문제가 발생할 수 있습니다.
클래스와 객체 모두 특정 기능의 집합이기 때문에 집합을 표현할 수 있는 명사 혹은 명사구가 적합합니다.
한 가지의 기능만 수행하는 메서드는 기능에 대한 동사나 동사구가 적합합니다.
클래스 내부에 있는 private 변수를 수정할 때,
변수에 접근하기 위한 public 메서드 이름을 store, set 등 여러가지를 고려할 수 있습니다.
이때, 각 상황에 맞춰 A 메서드는 store, B 메서드는 set 으로 사용하는 것이 아닌
저장 역할을 담당하는 메서드의 접두어는 set으로 통일 하는 것이 일관성 있는 코드를 작성할 수 있습니다.
의미 있는 이름 사용해보기
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
enum TYPE { ACCEPT, RECV, SEND, LOGOUT };
enum SESSION_STATE { ST_FREE, ST_ALLOC };
enum PLAYER_STATE { ST_HOME, ST_NOTREADY, ST_READY, ST_STAGE };
class OVER_EXP {
public:
WSAOVERLAPPED _over;
TYPE c_type;
WSABUF _wsabuf;
char _send_buf[BUF_SIZE];
OVER_EXP();
OVER_EXP(char* packet);
~OVER_EXP();
};
class SESSION {
public:
SESSION();
~SESSION();
bool recycle_session();
SESSION& operator=(const SESSION& ref);
void set_socket(SOCKET new_socket);
int get_prev_size();
void set_prev_size(int in);
void do_recv();
void do_send(void* packet);
void disconnect();
private:
OVER_EXP _recv_over;
SOCKET _socket;
int _prev_size;
};
위의 코드는 개선하기 전 코드입니다.
이 코드를 읽어보면 중복되는 이름이 있거나 명확하지 않은 이름, 사용 해선 안되는 이름도 존재합니다.
처음 코드를 읽는 사람이 보았을 때, 가장 큰 문제점들은 다음과 같습니다.
- OVER_EXP 클래스의 역할 파악 불가능
- 변수명 첫 글자에 under bar( _ ) 를 사용
- set_prev_size, get_prev_size 함수의 역할 파악 불가능
먼저 OVER_EXP 클래스의 역할은 WSAOVERLAPPED 객체에 기능을 추가해서 사용하기 위한 클래스입니다.
클래스 내부 변수들은 현재 클라이언트 상태를 저장하는 c_type, 소켓 통신에서 데이터를 저장할 _wsabuf,
그리고 소켓 통신에서 데이터를 수신할 때 사용하는 임시 저장 변수 _send_buf 가 있습니다.
마지막으로 ~prev_size 함수는 소켓 통신 중 잘려서 수신 받은 데이터가 존재할 때,
해당 데이터의 크기를 정수형으로 저장하고 가져오는 함수입니다.
따라서 이 문제점을 해결하기 위해 우선 다음과 같이 작명 규칙을 만들었습니다.
Type | Rule |
---|---|
Class, Function | CamelCase |
Variable, Class Field | snake_case |
enum Field | KEBAB_CASE |
그리고 변수명 첫 글자에 사용한 under bar는 C++ 에서 시스템 변수로 자주 사용되기 때문에
완전히 제거하기로 결정했습니다.
마지막으로 함수의 이름은 동사로 시작하고 이름이 길어지더라도 역할을 명확하게 표현할 수 있도록 수정했습니다.
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 소켓 타입임을 명시
enum SOCKET_TYPE { ACCEPT, RECV, SEND, LOGOUT };
enum SESSION_TYPE { FREE, ALLOC };
// 접두사 제거
enum PLAYER_STATE { HOME, NOT_READY, READY, STAGE };
// 카멜 표시법으로 클래스 이름 변경
class OverlappedExpansion {
public:
OverlappedExpansion();
OverlappedExpansion(char* packet);
~OverlappedExpansion();
private:
public:
// 자세한 이름 사용
WSAOVERLAPPED overlapped;
SOCKET_TYPE socket_type;
WSABUF wsa_buffer;
char packet_buffer[BUF_SIZE];
private:
};
// 카멜 표시법으로 클래스 이름 변경
class Session : public OverlappedExpansion {
public:
Session();
~Session();
Session& operator=(const Session& ref);
void set_socket(SOCKET new_socket);
// 동사 + 명사 형태로 함수 이름 변경
void recv_packet();
void send_packet(void* packet);
// 자세한 이름 사용
int get_prev_remain_packet_size();
void set_curr_remain_packet_size(int in);
void disconnect();
bool recycle_session();
private:
public:
private:
SOCKET socket;
// 자세한 이름 사용
int remain_packet_size;
};
작명 규칙에 의해 개선된 코드는 이름들이 길어졌지만 개선되기 전 코드와 비교해서
클래스, 함수, 변수 등의 역할이 명확하게 이해되었습니다.
이번 작업 덕분에 매번 명확하지 않은 역할을 파악하고자
Visual Studio 에서 F12 버튼으로 이동하는 수고를 줄일 수 있게 되었습니다.
기존 프로젝트들에서 나쁜 코드들을 수정하고
앞으로 참여할 프로젝트들에서는 직접 설명할 필요 없이
읽기만 해도 루틴이 이해가 되는 코드를 작성하기 위해 Clean Code를 활용해 보겠습니다.