[Clean Code][TIL] 함수
이 포스트는 로버트 C.마틴의 Clean Code 속 3장(40p ~ 65p) 내용에 대한 후기입니다.
작은 함수에게 한 가지만 시키기
“함수는 한 가지를 해야한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다.” (3장 44p)
코드를 작성하면서 여러 함수가 추가되고 기능이 추가되면서
한 개의 함수가 비대해지는 경험은 프로그래머라면 한 번쯤 해보았을 것이라고 생각합니다.
비대해진 함수는 다른 프로그래머 뿐만 아니라 작성자도
함수의 기능을 한 번에 파악하기 어렵습니다.
따라서 함수는 한 가지만 잘 해야 합니다.
아래 함수는 여러 일을 한 곳에서 처리하는 나쁜 함수로
switch 문 안에서 변수 생성, 콘솔 출력, 소켓 통신 등 여러 일을 한 함수에서 처리합니다.
뿐만 아니라 함수의 줄 수도 200줄이 넘어가 한 화면에서 함수를 읽을 수 없습니다.
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
59
60
61
62
63
64
65
void process_packet(int c_uid, char* packet)
{
switch (packet[1]) {
case CS_LOGIN:
{
CS_LOGIN_PACK* token_pack = reinterpret_cast<CS_LOGIN_PACK*>(packet);
if (0 == strncmp(token_pack->Token, "dummy_client_", strlen("dummy_client_"))) {
if (Login_UDB(c_uid, nameBuffer)) {
clients[c_uid].send_self_info(
std::string("Login player name is ").append(clients[c_uid].get_name()).c_str()
);
}
else {
if (SetNew_UDB(c_uid, nameBuffer)) {
clients[c_uid].send_self_info(
std::string("New login player name is ").append(clients[c_uid].get_name()).c_str()
);
}
else {
clients[c_uid].send_fail("Failed to initialize player");
}
}
}
else if (0 == strncmp(token_pack->Token, "theEnd", strlen("theEnd"))) {
std::string nameBuffer = GetPlayerName(*clients[c_uid].get_token());
if ("TokenError" == nameBuffer || "Token Error" == nameBuffer || "Empty" == nameBuffer) {
clients[c_uid].send_fail("Token error");
}
else if (Login_UDB(c_uid, nameBuffer)) {
clients[c_uid].send_self_info(
std::string("Login player name is ").append(clients[c_uid].get_name()).c_str()
);
}
else {
if (SetNew_UDB(c_uid, nameBuffer)) {
clients[c_uid].send_self_info(
std::string("New login player name is ").append(clients[c_uid].get_name()).c_str()
);
}
else {
clients[c_uid].send_fail("Failed to initialize player");
}
}
}
else {
std::string tokenBuffer;
tokenBuffer.assign(token_pack->Token, (size_t)token_pack->Token_size);
clients[c_uid].get_token()->append(tokenBuffer);
}
}
break;
case CS_CHAT:
// ...
break;
case CS_QUEST_INVENTORY:
// ...
break;
case CS_SAVE_INVENTORY:
// ...
break;
// ...
}
}
이 함수를 한 가지만 잘 하도록 아래의 규칙을 지켜 수정해 보았습니다.
함수 만들기 규칙
- 한 가지 기능만 수행
- 추상화 수준은 하나로
- 서술적인 이름 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void PacketWorker::process_packet(int user_id, char* packet)
{
switch (packet[1]) {
case CS_LOGIN:
CS_LOGIN_PACK* token_packet = reinterpret_cast<CS_LOGIN_PACK*>(packet);
clients[user_id].check_exists_token(token_packet->Token);
break;
case CS_CHAT:
CS_CHAT_PACK* chatting_packet = reinterpret_cast<CS_CHAT_PACK*>(packet);
sync_new_chatting_all_client(user_id, chatting_packet->content);
break;
case CS_QUEST_INVENTORY:
clients[user_id].get_all_inventory_item();
break;
case CS_SAVE_INVENTORY:
CS_SAVE_INVENTORY_PACK* item_packet = reinterpret_cast<CS_SAVE_INVENTORY_PACK*>(packet);
clients[user_id].set_inventory_item(item_packet->_name, item_packet->_cnt);
break;
break;
// ...
}
위 규칙을 기반으로 수정한 덕분에
process_packet 함수가 비약적으로 줄어들었습니다.
switch 문의 일부 case 속 자료형 변환을 제외하면 함수만 호출하도록 수정하였고
호출하는 함수의 기능을 이름으로 파악할 수 있도록 서술적으로 작성했습니다.
인수 줄이기
함수가 받는 인수의 수가 많을수록 함수를 이해하는데 어려움이 증가합니다.
인수의 순서 혹은 자료형에 따라 함수가 어떻게 동작하는지 함수 정의를 직접 찾아 봐야합니다.
작은 함수를 위해 서술적으로 이름을 작성하였더라도 인수가 너무 많으면 이해하기 어렵습니다.
따라서 인수는 최소한으로 줄여야 합니다.
만약 2-3개의 인수가 반드시 필요한 경우
인수의 일부를 클래스 변수로 선언해 사용할 수 있습니다.
부수 효과 제거하기
부수 효과는 한 가지 기능만 하도록 작성한 함수에 대한 모순입니다.
부수 효과를 갖고 있는 함수는 시간적인 결합 이나 순서 종속성 를 초래합니다.
작은 함수를 만들기 위해 한 가지 일만 잘 한다면 부수 효과는 필요 없습니다.
마무리
함수는 예외 처리, switch, if 문 등 여러 작업을 수행할 수 있지만
반드시 지켜야 하는 것은 위의 여러 기능 중 한 가지만 수행해야 한다는 것입니다.
추상 팩토리(Abstract Factory)
48 페이지 목록 3-5 설명 중 추상 팩토리(Abstract Factory)라는 기술이 소개됩니다.
추상 팩토리는 디자인 패턴 중 하나로 객체의 추상화를 이용하여
여러 객체를 한 곳에서 생성하는 패턴이라는 것을 알게 되었습니다.
시간적인 결합(temporal coupling)
55페이지 첫 문단에 나오는 단어로 코드에서는 분리되어 있지만
시간 순서에 의해서 항상 함께 사용되는 것을 시간적 결합이 되었다고 합니다.
순서 종속성(order dependency)
55페이지 첫 문단에 나오는 단어로 분리되어 있는 객체이지만
객체 사용 순서에 따라 결과가 달라지는 상황을 의미합니다.
“처음부터 함수를 탁 짜내지 않는다. 그게 가능한 사람은 없으리라.” (3장 62p)