오늘부터 숙련 공부하는 기간이긴 하지만 어쨌든 어제부터 진행했고 아직 강의를 20%도 나가지 못했는데 과제를 먼저 보니 이번에 구조적으로 고민할 부분을 잘 찾아야겠다는 게 느껴졌다.
강의에서 배운 내용을 토대로 TODO List 서버를 만드는게 과제인데 얼추 이유를 알던 App Native때와 달리 이유를 모르고 과제 때문에 정확히 모르는 데 붙여 쓰는 불상사가 일어날까 조금 무섭기도 하다.
아마 모르는 걸 억지로 쓰기 시작하면 과거 행보를 다시 밟는 미숙한 개발자가 될 것 같으니 이번엔 부족하더라도 챗봇, 팀, 튜터님 모두에게 도움을 받아가며 그 이유를 정리해나가야 할 것 같다.
기능 구현까지는 어찌저찌 할 수 있겠지만 이번 TODO List 과제의 목표 추가 달성 목표를 설정해보기로 했다.
- ERD, Diagram등도 설정해서 설계부터 차근차근 접근하기
- Delete는 진짜 제거가 아니라 Status로 제거 관리하기
- created_at, update_at, deleted_at 등 Date 처리 잘 하기
- Docs 작성 잘 하기
- 로그 잘 남기기
- User / Admin 구분해보기
- 혹시 된다면 Production / Test 환경 분리하기
- 비로그인 계정이라 치고 아이디 / 비밀번호를 받아 관리하는데 암호화 잘 하기?
- Scheme 명시하고 실무에서도 범용적으로 그렇게 쓰는지 확인하기
- Validation 잘하기
- Status code, Message 관리 잘하기
- Query parameter, Path variable 관리 잘하기
resource를 식별하고 싶으면 Path Variable을 사용하고
정렬이나 필터링을 한다면 Query Parameter를 사용하는 것이 Best Practice- v1 으로 관리 시작하기
내가 앱에서 써야 한다면?
이라는 생각으로 접근하기- retool 은 써봐도 좋고 타임리프는 금지다
- Pagination, N + 1 Query, Auth
일단 발제만 보고는 이 정도고 아마 강의가 한참 남았으니 들으면서 내용을 많이 추가하게 되지 않을까 싶다. 그런데 이미 너무 많은 거 아닌가? 강의도 다 안봤는데 약 10일간 만들 거 치고는 내용이 많은 것 같다. 그냥 희망 사항인 걸로 해야겠다.
과제가 꽤 분량이 많아 보여서 가늠이 안되가지고 강의를 최대한 빨리 수강하고 실전을 곁들여서 공부하기로 했다. 그래서 오늘은 강의를 좀 빨리 뺐다.
강의를 보며 느낀 건데 앱도 구조를 처음부터 설계 잘해야 편하긴 했지만 Web application은 Refactoring 하기 조금 더 빡실 것 같다. 다만 앱 구조가 상대적으로 쉬운건지 못짜서 코드 복잡도가 적었다고 봐야하는지는 불확실하긴 하다.
fun solution(cards1: Array<String>, cards2: Array<String>, goal: Array<String>): String {
var answer = ""
val cards1List = cards1.toMutableList()
val cards2List = cards2.toMutableList()
val goalList = goal.toMutableList()
var breakFlag = false
while (goalList.isNotEmpty()) {
val goalKeyword = goalList.removeFirst()
if (cards1List.getOrNull(0) == goalKeyword) {
cards1List.removeAt(0)
} else if (cards2List.getOrNull(0) == goalKeyword) {
cards2List.removeAt(0)
} else {
breakFlag = true
break
}
}
answer = if (breakFlag) "No" else "Yes"
return answer
}
처음에 문제를 읽고 cards1, cards2를 교차로 묶어서 처리하면 되는 줄 알고 flatMapIndexed
를 써서 묶었는데 문제를 잘못 읽은걸 뒤늦게서야 깨달았다.
요지는 순서대로
사용해야한다는 점 같아 첫 번째 값만 지속적으로 비교하는 알고리즘을 떠올렸고 순회 중단 시점도 명확하게 순서대로 구분해도 문장을 이룰 수 없을 시 탈출하는 것이니 "No"를 반환하고 goalList가 비었다면 "Yes" 인 걸로 작성해서 제출했다.
그런데 테스트 케이스중 한 개가 실패가 뜨길래 대체 뭘까 싶어서 고민하며 문제를 읽다가
goal
의 길이 ≤cards1
의 길이 +cards2
의 길이
이걸 보고 탈출 시점에 goal의 길이로 체크하는 건 명시적이지 않을 수 있겠다 싶어서 breakFlag
를 만들어서 순회 탈출 유무를 체크했더니 무사히 통과할 수 있었다.
안되는 케이스가 정확히 어떤 건지는 파악 못했지만 길이에 따른 반례가 생길 수 있는건 다른 사람들의 질문을 보고 알 수 있었다.
그리고 다른 풀이를 보다가 아차차 싶었던 부분이 추가적으로 breakFlag
를 쓸게 아니라 else 에서 그냥 return "No"
를 하는게 더 깔끔했을텐데 싶어서 조금 아쉬움이 남는다.
추가로 가장 깔끔한 다른 풀이를 가져왔다.
fun solution2(cards1: Array<String>, cards2: Array<String>, goal: Array<String>): String {
var idx1 = 0
var idx2 = 0
goal.forEach {
if (idx1 < cards1.size && it == cards1[idx1]) idx1++
else if (idx2 < cards2.size && it == cards2[idx2]) idx2++
else return "No"
}
return "Yes"
}
이걸 보니 내가 왜 처음에 goal에 removeFirst를 해야하니 while 순회를 선택했는지 잘 모르겠다.
그리고 List가 제거되는 시간 복잡도도 고려하지 않아도 되는 idx를 두개를 같이 돌리는 방식을 선택했고 removeFirst로 goal의 키워드를 확인할 필요도 없었는데 내 풀이는 문제 풀이 자체는 맞는 방향으로 접근한 것 같지만 코드 퀄리티로는 너무 부족한 풀이가 됐다.
이번엔 시간이 좀 걸려서 그대로 제출한 느낌도 있는데 다음에는 제출 잘 되는 것 같으면 줄여볼 방법도 같이 고민을 해야겠다.