문제 상황
밀꼭 개인 어플리케이션에서 심각한 문제가 발생했습니다. UserNotification에서 사용자에게 Authroziation을 요청하는 코드가 주석처리 되었던 버그를 발견했습니다. 그리고 이를 해결하고 내부테스팅 이후에 앱을 배포하였습니다.
하지만 유저가 어플리케이션에서 crush가 난다고 하였습니다. 유저는 "프로필 화면 클릭시 앱이 강제종료된다" 라는 말을 덭붙였습니다. 일단 프로필 화면에서 왜 강제종료 되는지에 대해서 고민하였습니다.
자문 구하기(버그를 재현해보기)
이런 상황이 처음이라 처음에는 맨붕이 와서, 자문을 구해봤습니다. 공통된 의견으로 버그를 고치기 위해서는 "재현"이라는 키워드 였습니다. 이 버그가 어떤 상황에서 발생하는지만 알면, 버그를 수정할 수 있을 것 같았습니다.
버그가 일어나는 환경 정리
버그가 일어나는 상황은 정말 특이한 점이나, 다른 유저들은 버그가 일어난다고 보고하지 않는 분기였습니다. 그래서 정말 어떤식으로 접근해야할지가 고민이었습니다.
0. iphone 13 mini
1. 프로필 화면 접근시 필요한 정보를 업데이트 하던 도중 튕김
- 프로필 업데이트(Profile Image, Name 이 Local DB를 통해 업데이트)
- 캘린더 업데이트(History Date불러오기 Local DB를 통한 업데이트)
- 오늘 날짜의 챌린지 히스토리 업테이트 (오늘 날짜의 History Date불러오기 Local DB를 통한 업데이트)
프로덕트 중심의 개발자?(User휴대폰으로 디버깅)
버그를 도저히 재현할 수 없어서 고객과 직접 만났습니다. 고객은 지인이고 고객의 휴대폰을 디버깅 하였습니다. 디버깅을 통해서 캘린더업데이트에 버그가 발생한다는 사실을 깨닫고 캘린더 뷰에서 잘못된 API를 사용했는지 다시 확인했습니다.
결론적으로 API호출을 잘못된 방식으로 한 것을 깨달았습니다. 캘린더에 특정 날짜의 DateComponent에 이모티콘을 추가하는 로직이 있었는데, ViewModel이 업데이트 될 때 두번의 DateComponent 이모티콘 추가 로직 때문에 발생한 버그였습니다. DateComponent에 따라서 특정 날짜의 View를 보여주는 방법은 다음과 같이 두가지 입니다. 캘린더는 둘 중 한가지만 써야 하는데 둘을 중첩해서 써서 생긴 버그였습니다.
1. reloadDecorations
Reloads the decorations for the dates you provide, with an option to animate the decoration reload.
// Add a decoration to the specified date.
func add(decoration: UICalendarView.Decoration, on date: Date) {
// Get the calendar, year, month, and day date components for
// the specified date.
let dateComponents = Calendar.current.dateComponents(
[.calendar, .year, .month, .day ],
from: date
)
// Add the decoration to the decorations dictionary.
decorations[dateComponents.date] = decoration
// Reload the calendar view's decorations.
if let calendarView {
calendarView.reloadDecorations(
forDateComponents: [dateComponents],
animated: true
)
}
}
2. calendarView(_:decorationFor:)
Creates a calendar view decoration for the date represented by the date components you provide.
decorations[day.date] // 특정 날짜에 대한 DecorationsView
// Return a decoration (if any) for the specified day.
func calendarView(_ calendarView: UICalendarView, decorationFor dateComponents: DateComponents) -> UICalendarView.Decoration? {
// Get a copy of the date components that only contain
// the calendar, year, month, and day.
let day = DateComponents(
calendar: dateComponents.calendar,
year: dateComponents.year,
month: dateComponents.month,
day: dateComponents.day
)
// Return any decoration saved for that date.
return decorations[day.date]
}
버그 해결을 통한 느낀점
1. firebase crashlytics
버그를 일단 잡긴 잡았습니다. 하지만 앞으로 이런 일이 발생하지 않게 하려면 진지하게 firebase crashlytics에 대해서 고민하게 되었습니다. FireBase를 붙여서 어디서 버그가 발생하는지 알면 좋을 것 같아서 다음과 같이 고민하게 되었습니다.
2. 병렬 코드가 문제일까?
저는 병렬로 일어나는 코드들을 좋아합니다. 병렬로 작업을 처리하게 된다면 global Queue가 일하게 되고 이는 하나의 Queue(보통은 메인에서)보다 더 빠르게 해결되기 때문입니다. 하지만 이와 관련된 단점은 역시 디버깅이 어렵다 였습니다. 이번에도 이 버그를 발견하기 위해서 정말 많은 시간이 걸렸습니다. 모든 작업을 mainQueue에서 해도 됨에 불구하고 성능상의 이점을 위해서(얼마 차이 안나지만) 병렬처리를 했지만, 이것이 과연 옳은 것인지에 대한 고민을 깊게 하였습니다.
그리고 내린 결론은, "병렬 코드는 죄가 없다." 였습니다. 어떤 방식으로 디버깅을 하는지에 대한 루틴이나, 병렬 코드를 디버깅 하기 위한 노하우가 부족하다고 생각했습니다. 충분히 MainThread로 디버깅 할 수 있는 방법을 체득하는 것도(차후 비슷한 버그가 발생했을때 이를 방지하기 위해서) 방법이라 생각합니다.
3. 아키텍쳐 덕 보기(with Clean Architecture)
사실 아키텍쳐에서 덕을 본 케이스가 이번 버그 잡는 케이스 라고 생각합니다. Clean Architecture를 활용해서 각각의 Layer가 갖는 책임을 통해서 어디서 버그가 발생하는지 찾았습니다. (View로드 과정에서 버그가 발생하는 것을 찾음) 만약 아키텍쳐가 잘 구성되어지지 않았다면 디버깅이 훨 힘들었을 것으로 생각합니다.
** 끝으로 밀꼭 많은 관심 부탁드립니다. **
'프로젝트 > 밀꼭-당신을 위한 식사 타이머' 카테고리의 다른 글
[밀꼭] 사용자 피드백 반영하기 (Tuist + Carthage + RealmSwift 트러블 슈팅) (4) | 2024.11.01 |
---|---|
[MealGok] 어플리케이션의 종료 시나리오 작성 (0) | 2024.03.07 |