Home 스마트 컨트랙트 개발 중 enum class 활용
Post
Cancel

스마트 컨트랙트 개발 중 enum class 활용

EOSIO 스마트 컨트랙트 개발 중 enum class 활용

회사에서 웹 서버 개발과 블록체인 스마트 컨트랙트 개발 업무를 하면서 다음과 같은 문제의 상황을 확인하였다.

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
using namespace std;

struct [[eosio::table]] person {
     name key;
     std::string first_name;
     std::string last_name;
     std::string street;
     std::string city;
     std::string state;

     uint64_t primary_key() const { return key.value;}
};

typedef eosio::multi_index<"people"_n, person> address_index;

ACTION findaddress(name user, string first_name, string last_name, string street, 
                  	string city, string state) {
    ....
        
    address_index addresses(_code, _code.value);
    auto iterator = addresses.find(user.value);
	check(iterator == addresses.end(), 
          "#ERROR - CODE : 100000, DESC : couldn't find a person.\n");
    
    ....
}

ACTION deladdress(name user) {
     ....
        
         
    address_index addresses(_code, _code.value);
    auto iterator = addresses.find(user.value);
	check(iterator == addresses.end(), 
          "#ERROR - CODE : 100000, DESC : couldn't find a person.\n");
    
    ....
}

예외처리를 하면서 반환하는 에러 코드와 에러에 대한 설명이 모두 string 형태로 들어가 있었다.

ACTION의 수가 적으면 상관이 없을 수 있지만, ACTION의 수가 많아지고 ACTION의 과정이 복잡해지면서 내부 함수가 많아지게 되면 프로그램의 여러 곳에서 예외처리를 해줘야 하는 상황이 발생할 수 있다.

다행히 EOS는 c++의 문법을 바탕으로 만들 수 있어 c++에서의 enum class를 활용해서 리팩토링을 적용해 보기로 했다.

1
2
3
4
5
enum class ErrorCode {
    CANNOT_FIND_PERSON = 100000,
    ALREADY_EXIST_PERSON = 100001,
    ....
}

이를 통해서 위에 소스코드가 다음과 같이 변경을 할 수 있었다.

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
using namespace std;

struct [[eosio::table]] person {
     name key;
     std::string first_name;
     std::string last_name;
     std::string street;
     std::string city;
     std::string state;

     uint64_t primary_key() const { return key.value;}
};

typedef eosio::multi_index<"people"_n, person> address_index;

ACTION findaddress(name user, string first_name, string last_name, string street, 
                  	string city, string state) {
    ....
        
    address_index addresses(_code, _code.value);
    auto iterator = addresses.find(user.value);
	check(iterator == addresses.end(), 
          "#ERROR - CODE : " + 
          to_string(static_cast<int>(ErrorCode::CANNOT_FIND_PERSON)) + 
          " DESC : couldn't find a person.\n");
    
    ....
}

ACTION deladdress(name user) {
     ....
        
         
    address_index addresses(_code, _code.value);
    auto iterator = addresses.find(user.value);
	check(iterator == addresses.end(), 
          "#ERROR - CODE : " + 
          to_string(static_cast<int>(ErrorCode::CANNOT_FIND_PERSON)) +
          " DESC : couldn't find a person.\n");
    
    ....
}

이를 통해서 다음과 같은 이점을 얻을 수 있었다.

  • 코드를 조금 더 가독성을 좋게 만들었다.
  • enum을 통해서 구현의 의도가 열거임을 알 수 있다.
  • 에러코드를 수정할 때 여러 곳에서 수정하는 것이 아닌 한 곳에서 수정하는 것이 가능해졌다.

하지만 뭔가 아쉽다. 에러코드와 에러에 따른 메시지가 맵핑이 되는 경우가 많고 메시지는 아직 수정할 때 직접 그 부분을 모두 찾아서 수정해야 하는 상황이다. 하지만 c++은 enum을 기본적으로 정수형을 열거하는데 사용하기 때문에 문자열을 열거하는데는 사용할 수 없었다.

그래서 다음과 같은 함수를 사용해서 string이 입력이 되도록 했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum class ErrorCode {
    CANNOT_FIND_PERSON = 100000,
    ALREADY_EXIST_PERSON = 100001,
    ....
}

string getErrorMsg(ErrorCode code) {
	switch(code) {
        case ErrorCode::CANNOT_FIND_PERSION:
            return "DESC : couldn't find a person.\n";
        case ErrorCode::ALREADY_EXIST_PERSION:
            return "DESC : person is already exist \n";
        ....
    }
}
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
using namespace std;

struct [[eosio::table]] person {
     name key;
     std::string first_name;
     std::string last_name;
     std::string street;
     std::string city;
     std::string state;

     uint64_t primary_key() const { return key.value;}
};

typedef eosio::multi_index<"people"_n, person> address_index;

ACTION findaddress(name user, string first_name, string last_name, string street, 
                  	string city, string state) {
    ....
        
    address_index addresses(_code, _code.value);
    auto iterator = addresses.find(user.value);
	check(iterator == addresses.end(), 
          "#ERROR - CODE : " + 
          to_string(static_cast<int>(ErrorCode::CANNOT_FIND_PERSON)) + 
          getErrorMsg(ErrorCode::CANNOT_FIND_PERSON));
    
    ....
}

ACTION deladdress(name user) {
     ....
        
         
    address_index addresses(_code, _code.value);
    auto iterator = addresses.find(user.value);
	check(iterator == addresses.end(), 
          "#ERROR - CODE : " + 
          to_string(static_cast<int>(ErrorCode::CANNOT_FIND_PERSON)) +
          getErrorMsg(ErrorCode::CANNOT_FIND_PERSON));
    
    ....
}

하지만 이 역시 C style에 가까운 방식이고 조금 더 c++ 스러운 방식이 있을 것으로 생각이 된다. EOSIO에서 모든 c++ 문법이 지원이 되는 것이 아니라 조금 더 방법을 찾고 적용을 해볼 수 있도록 해야겠다.

리팩토링(4)_method 정리_2

리팩토링(5)_객체 사이의 기능 이동