안녕하세요. 커뮤니케이션 앱 LINE의 모바일 클라이언트를 개발하고 있는 Ishikawa입니다.
저희 회사는 높은 개발 생산성을 유지하기 위해 코드 품질 및 개발 문화 개선에 힘쓰고 있습니다. 이를 위해 다양한 노력을 하고 있는데요. 그중 하나가 Review Committee 활동입니다.
Review Committee에서는 머지된 코드를 다시 리뷰해 리뷰어와 작성자에게 피드백을 주고, 리뷰하면서 얻은 지식과 인사이트를 Weekly Report라는 이름으로 매주 공유하고 있습니다. 이 Weekly Report 중 일반적으로 널리 적용할 수 있는 주제를 골라 블로그에 코드 품질 개선 기법 시리즈를 연재하고 있습니다.
이번에 블로그로 공유할 Weekly Report의 제목은 '새것을 들일 때 옛것도 다시 살피자'입니다.
새것을 들일 때 옛것도 다시 살피자
'사용자'를 나타내는 다음과 같은 모델 클래스가 있다고 가정해 봅시다.
class UserData(
val id: UserId,
val name: String,
val profileImageUri: String,
...
)
name
이라는 속성은 사용자의 '로그인 이름'을 나타냅니다. 로그인 이름을 다른 사용자에게 공개하고 싶지 않다는 요청 사항을 구현하기 위해 공개용 별칭인 '닉네임'을 등록할 수 있도록 사양을 변경한다고 가정하겠습니다.
구현 결과 UserData
를 다음과 같이 변경했다고 가정하겠습니다(닉네임을 등록하지 않을 경우 null
을 사용하는 것은 합의된 사항으로 문제 없다고 가정합니다).
class UserData(
val id: UserId,
val name: String,
val profileImageUri: String,
...
/**
* ...
* 만약 `nickname`이 명시적으로 `null`인 경우 닉네임이 등록되지 않았음을 의미한다.
*/
val nickname: String?
)
또한 ‘사용자 이름’을 표시하는 UI는 다음과 같이 구현했다고 가정하겠습니다.
userNameView.text = userData.nickname
?: userData.name
이 코드에 문제가 있나요?
새로운 것을 추가할 때 오래된 것을 바꾸기
새로운 속성이나 메서드를 추가할 때 기존 속성이나 메서드의 이름을 변경해야 하는 경우가 있습니다. 위 코드에서는 UserData.name
이라는 속성 이름을 변경해야 했는데요. 그 이유는 다음과 같습니다.
가령 로그인 이름은 표시하지 않고 닉네임만 표시하는 UI nicknameView
를 아래와 같이 구현했다고 가정해 보겠습니다. 아래 코드에는 '닉네임이 아닌 로그인 이름을 사용했다'는 버그가 있지만 언뜻 봐서는 이를 알아차리기 어렵습니다.
val nickname = userData.name
nicknameView.isVisible = nickname.isNotEmpty()
nicknameView.text = nickname
한눈에 버그를 발견하기 어려운 이유는 nickname
은 name
의 일종처럼 보이기 때문입니다. 실제 UserData.name
과 UserData.nickname
값 사이에는 포함 관계가 없지만, 속성 이름만 보면 포함 관계가 있는 것처럼 보입니다.
이러한 혼란을 피하려면 UserData.nickname
을 추가할 때 UserData.name
의 이름을 변경하는 게 좋습니다. 다음 코드에서는 name
이라는 속성 이름을 loginName
으로 변경했습니다.
class UserData(
val id: UserId,
/** ... */
val loginName: String,
/** ... */
val nickname: String?,
val profileImageUri: String,
...
) {
// Also, we may put property selection logic
/** ... */
val displayName: String
get() = nickName ?: loginName
}
이렇게 하면 loginName
과 nickname
의 차이를 명확하게 구분할 수 있습니다. 또한 해당 코드는 'displayName
에 우선 닉네임을 사용하고, 닉네임이 없다면 로그인 이름으로 대체(fallback)한다'는 것을 명시하고 있습니다.
유사한 사례
다양한 경우에 위와 비슷한 상황이 발생할 수 있습니다.
사례 1
- 기존 요소: 로컬에 저장된 데이터
fooData
- 새로운 요소: 네트워크를 통해 가져오는 데이터
fooRemoteData
- 이름 변경안:
fooData
->fooLocalData
사례 2
- 기존 요소: 생성 시점의 타임스탬프
timestamp
- 새로운 요소: 마지막 업데이트 타임스탬프
lastUpdateTimestamp
- 이름 변경안:
timestamp
->creationTimestamp
사례 3
- 기존 요소: 사용자 프로필에 추가할 수 있는 외부 링크
url
- 새로운 요소: 사용자 프로필 이미지 링크
profileImageUrl
- 이름 변경안:
url
->externalLinkUrl
한 줄 요약: 새로운 요소를 추가할 때 기존 코드의 이름을 변경하는 것을 고려한다.
키워드:
naming
,new element
,inclusive relation