서버에서 받은 아이템 정보를 게임에 넣는데 코드가 자꾸 길어졌다. JsonCpp 로 읽어 ShopItem 에 옮기는 부분이다.
Table of contents
Open Table of contents
두 구조가 달랐다
서버가 주는 JSON 은 층이 있었고 ShopItem 은 평평했다. 한 값을 꺼내는 데 네 단계를 지나야 했다.
item.typeCode = codeOf(root["items"][i]["info"]["type"]["code"].asCString());
중간에 하나라도 없으면 asCString() 에서 죽는다. 그래서 꺼내기 전에 isMember 를 넣었다. 확인할 자리가 층마다 있어서 코드가 더 길어졌다.
확인을 한 자리에 모았다
경로를 점으로 이어 받는 pick 을 하나 뒀다.
static std::string pick(const Json::Value& v, const char* path, const char* def = "") {
std::vector<std::string> keys = split(path, '.');
Json::Value cur = v;
for (size_t i = 0; i < keys.size(); ++i) {
if (!cur.isMember(keys[i])) {
CCLOG("json: no path %s", path);
return def;
}
cur = cur[keys[i]];
}
return cur.asString();
}
std::string 과 std::vector 를 쓰려면 Application.mk 에 APP_STL := gnustl_static 이 있어야 한다. NDK 의 기본 런타임에는 STL 이 거의 없다.
부르는 쪽이 한 줄이 됐고 중간에 없어도 안 죽는다. 다만 조용히 기본값이 들어가는 것은 그것대로 문제다. 없는 경로를 만나면 CCLOG 에 남기게 했다. 개발 중에 서버 구조가 바뀌면 logcat 으로 바로 보인다.
어느 쪽에 맞출지 정했다
변환을 편하게 하는 것과 구조를 맞추는 것은 다른 이야기였다. 서버 JSON 에 맞추면 변환이 없어진다. 대신 게임 코드가 매번 Json::Value 를 따라가야 한다. ShopItem 에 맞추려면 서버 쪽에 변경을 요청해야 한다.
서버는 다른 곳에서도 쓰고 있어 못 바꾼다. 게임 안에서 Json::Value 를 들고 다니는 것도 느리다. 그래서 가운데에 옮기는 자리를 하나 뒀다. JSON 구조가 바뀌어도 거기만 고친다.
필수와 선택, 그리고 받은 것
pick 이 전부 기본값을 주면 이름 없는 아이템이 생긴다. 없으면 버릴 것과 기본값으로 둘 것을 나눴다.
out.id = node.get("id", 0).asInt();
if (out.id == 0) return false; // 없으면 버린다
out.typeCode = codeOf(pick(node, "info.type.code", "ETC").c_str()); // 없어도 된다
이름을 복사하는 부분도 고쳤다. char name[32] 인데 서버가 그보다 긴 이름을 주면 넘친다. strcpy 를 snprintf 로 바꿨다. 잘리기는 해도 끝에 \0 이 들어가고 넘치지 않는다. 보내는 쪽이 지켜 줄 것이라고 보면 안 된다.
받은 것을 개발 빌드에서 그대로 찍는 것도 넣었다. Json::StyledWriter 로 원문을 남긴다. 옮기고 나면 원래 받은 것이 사라져서 값이 이상할 때 무엇을 받았는지 볼 수가 없었다.
내가 잘못 옮긴 것인지 받은 것이 원래 그런지가 이걸로 갈렸다. 이 구분이 없으면 서버 쪽에 문의할지 내 코드를 볼지부터 정하지 못한다.
정리
- 두 쪽 구조가 다르면 옮기는 코드가 길어지고 층마다 확인이 붙는다
- 경로로 꺼내는 함수를 하나 두면 확인이 한 자리에 모인다
- 없으면 기본값을 주되 로그에 남긴다
- 조용히 넘어가면 구조가 바뀐 것을 모른다
- 양쪽을 못 바꾸면 가운데에 옮기는 자리를 둔다
- 없으면 버릴 것과 기본값으로 둘 것을 나눈다
- 받는 쪽 크기가 정해져 있으면
snprintf로 넣는다.strcpy는 넘친다 - 받은 것을 남겨 두면 누가 잘못한 것인지 갈린다