인벤토리 화면을 맡았다. 서버에서 받은 아이템 목록을 Cocos2d-x 쪽 구조체에 담아 화면에 뿌린다. 사용할 때 그 값을 다시 서버로 보낸다.
구매가 계속 실패했고 서버는 그런 아이템이 없다고 답했다.
Table of contents
Open Table of contents
증상 — 화면을 맞추려다 통신이 깨졌다
받은 목록을 저장할 때 이렇게 했다.
std::string code = json["item_code"].asString();
// 앞뒤 공백을 없애고 대문자로 맞춘다
code = trim(code);
std::transform(code.begin(), code.end(), code.begin(), ::toupper);
item.code = code;
wp_001 처럼 뒤에 공백이 붙은 값이 섞여 있어서 정리한 것이었다. 화면에 찍을 때 줄이 어긋나 보였기 때문이다.
문제는 이 값을 그대로 서버에 돌려준다는 것이다.
보낸 값: WP_001
서버 기대: wp_001
서버는 iBatis 로 조회한 그 문자열을 키로 다시 찾는다. 자기가 준 것과 정확히 같아야 한다. toupper 로 대소문자를 바꾼 순간 다른 값이 됐다.
하나씩 빼 보며 좁혔다
전부 실패하는 것이 아니라 일부만 실패했다. 원래 소문자였거나 공백이 없던 값은 그냥 됐다. 그래서 서버 쪽을 먼저 의심했다.
들어온 값과 나가는 값을 같이 찍어 봤다.
CCLOG("recv=[%s] send=[%s]", item.raw["item_code"].asCString(), code.c_str());
recv=[wp_001 ] send=[WP_001]
한 줄로 드러났다. CCLOG 에 recv 와 send 를 나란히 놓지 않았으면 서버 쪽을 계속 의심했을 것이다. 값이 어디서 바뀌는지 모를 때는 들어온 지점과 나가는 지점을 같이 찍는 것이 빨랐다.
다듬는 코드를 한 줄씩 지우고 ndk-build 로 다시 올려 봤다. 공백 제거만 빼니 절반이 됐고 대문자 변환까지 빼니 전부 됐다.
JNI 로 넘어가는 경로도 함께 봤다. Java 쪽에서 String.trim() 이 한 번 더 걸려 있어 두 군데에서 값이 바뀌고 있었다.
표시용과 전송용을 나눴다
고친 방향은 원본을 손대지 않는 것이었다.
item.code = json["item_code"].asString(); // 원본 그대로
item.display = trim(item.code); // 화면용
화면에는 display 를 쓰고 서버로는 code 를 보낸다. 정리가 필요했던 것은 맞다. 다만 정리한 값이 원본을 덮으면 안 됐다. Java 쪽 String.trim() 도 없앴다. 두 군데에서 고치면 어디서 바뀌었는지 찾기 어렵다.
같은 문제가 다른 데서도 있었다. 응답에서 아는 필드만 골라 구조체에 넣고 나머지는 버리고 있었다.
struct Item {
std::string code;
std::string name;
int price;
};
서버가 나중에 필드를 하나 더 보내기 시작하면 그건 버려진다. 그 필드를 같이 돌려줘야 하는 규칙이 생기면 클라이언트를 고쳐야 한다. 그래서 Item 에 원본 응답을 통째로 들고 있게 했다.
struct Item {
std::string code;
std::string name;
int price;
Json::Value raw; // 받은 것 전부
};
돌려보낼 때는 raw 에서 필요한 것을 꺼낸다. 내가 모르는 필드도 손실 없이 왕복한다.
가격에서도 같은 일이 있었다. 서버가 "price": "12000" 을 문자열로 주는데 정수로 읽고 정수로 보냈다.
받은 값: "012000"
보낸 값: 12000
어떤 값은 앞에 0이 붙어 있었고 그게 사라졌다. 형식과 값이 둘 다 달라진 것이다. 숫자로 계산할 일이 있으면 따로 변환해서 쓰고 돌려보낼 때는 받은 문자열을 쓴다.
판단 기준 — 손대도 되는 값과 아닌 값
세 부류로 나눠 봤다.
| 값 | 손대도 되나 |
|---|---|
| 화면에 보이기만 하는 것 | 자유롭게 |
| 서버로 돌아가는 것 | 손대지 않음 |
| 계산에 쓰는 것 | 사본을 만들어 계산 |
이렇게 나누고 나니 어디를 고쳐야 할지 찾기 쉬웠다. 돌아가는 값에 변형이 들어간 자리를 찾으면 된다.
정리
- 받은 값을 담는 자리에서 다듬으면 원본이 사라진다
- 일부만 실패하는 형태로 나타난다 — 원래 다듬을 필요가 없던 값은 그냥 된다
- 값이 어디서 바뀌는지 모르면 들어온 값과 나가는 값을 같이 찍는다
- 다듬는 코드를 한 줄씩 빼고
ndk-build로 올리면 어느 것인지 갈린다 JNI경로에서Java쪽String.trim()이 한 번 더 고치고 있었다- 표시용
display와 전송용code를 필드로 나눴다 - 아는 필드만 남기면 모르는 필드가 왕복에서 사라진다.
raw를 통째로 들고 있는다 - 문자열로 온 숫자를 정수로 바꿔 돌려보내면 앞의 0 같은 것이 사라진다
- 값을 세 부류로 나눈다 — 표시만 하는 것, 돌아가는 것, 계산에 쓰는 것