LY Corporation은 높은 개발 생산성을 유지하기 위해 코드 품질 및 개발 문화 개선에 힘쓰고 있습니다. 이를 위해 다양한 노력을 하고 있는데요. 그중 하나가 Review Committee 활동입니다.
Review Committee에서는 머지된 코드를 다시 리뷰해 리뷰어와 작성자에게 피드백을 주고, 리뷰하면서 얻은 지식과 인사이트를 Weekly Report라는 이름으로 매주 공유하고 있습니다. 이 Weekly Report 중 일반적으로 널리 적용할 수 있는 주제를 골라 블로그에 코드 품질 개선 기법 시리즈를 연재하고 있습니다.
이번에 블로그로 공유할 Weekly Report의 제목은 '차일드 록(child lock)'입니다.
차일드 록
메시지의 데이터 모델로 MessageData라는 추상 클래스가 있다고 가정해 봅시다. 메시지에는 여러 유형이 있으며, 각 유형은 MessageData의 자식 클래스를 정의하는 방식으로 표현합니다.
여기서 MessageData의 자식 클래스 T의 목록 List<T>를 화면에 표시하기 위해 다음과 같이 추상 클래스 MessageListPresenter<T : MessageData>를 정의했다고 가정하겠습니다. 단, 메시지 표시 로직은 메시지 유형에 따라 달라지므로 MessageListPresenter.bind에는 목록 표시 로직을 구현하지 않았습니다. 부모 클래스의 bind에서는 헤더와 푸터(footer)만 표시하고, 목록 표시는 자식 클래스에서 오버라이딩해서 구현할 것으로 기대한 코드입니다(Kotlin에서 open은 오버라이딩이 가능하다는 의미의 수정자입니다).
abstract class MessageListPresenter<T : MessageData>( ... ) { private val headerView: ... = ... private val footerView: ... = ... open fun bind(messageList: List<T>) { updateHeader(messageList.size) updateFooter(messageList) // 여기서는 `messageList` 표시가 구현되어 있지 않다. // 이 함수를 오버라이딩해서 `messageList` 표시를 구현한다. } private fun updateHeader(messageCount: Int) { headerView.text = ... headerView.... } private fun updateFooter(messageList: List<T>) { footerView.... } }다음 SomeSpecificMessageListPresenter는 MessageListPresenter를 구현한 예시입니다. SomeSpecificMessageData를 타입 파라미터로 지정하고, 해당 클래스 고유의 목록 표시 로직을 bind에 정의했습니다.
class SomeSpecificMessageListPresenter( ... ) : MessageListPresenter<SomeSpecificMessageData>( ... ) { override fun bind(messageList: List<SomeSpecificMessageData>) { super.bind(messageList) ... // `messageList` 표시 } }이 코드에 문제가 있을까요?
자식 클래스가 장난을 못 치도록 하기
super를 명시적으로 호출해야 한다는 것은 대부분 오버라이딩 가능한 함수의 범위가 너무 넓다는 것을 의미합니다(예외에 대해서는 뒤에서 설명하겠습니다). 위 코드는 오버라이딩할 수 있는 함수의 범위가 너무 넓기 때문에 다음과 같은 구현 실수가 발생하기 쉽습니다.
- super 호출 누락: 모든 자식 클래스는 헤더와 푸터를 업데이트하려면 super.bind를 호출해야 합니다. 만약 super.bind 호출을 누락한 경우 헤더와 푸터가 업데이트되지 않는 버그가 발생하며, 이때 특별히 에러