오늘은 삽질해서 막히던 SZ-85번의 하위 이슈 중 하나를 팀원들의 도움으로 같이 해결했다. 세부 문제 상황은, 투두 리스트들 아래 텍스트 인풋을 받는 Input 컴포넌트를 누르면 텍스트 인풋 창이 활성화되지 않고 키보드가 활성화되었다가 바로 비활성화되는 문제였다.
팀원들과 웹엑스로 회의를 하면서 문제가 되는 컴포넌트(DailyTodos 안의 컴포넌트)를 KeyboardAvoidingView로 한번 더 감싸 보았더니 된다고 했다.
사실 어떤 원리로 문제가 해결되었는지는 모르겠다. 왜냐하면 이미 바깥 화면에서 KeyboardAvoidingView를 사용하고 있었고, 그래서 굳이 한번 더 감쌀 생각을 안 한 것이었기도 하기 때문이다. 또한 GPT한테 물어봤을 때는 컴포넌트(아마도 Input 컴포넌트)가 계속 텍스트창이 활성화될 때마다 다시 렌더링 되어서 텍스트 창 클릭 -> 활성화 -> 다시 렌더링됨 -> 초기 값인 비활성화...의 루프를 타는 것이라고 생각하였는데 결국 그도 아니었던 것이다.
이 부분은 KeyboardAvoidingView에 대해서도 더 알아봐야 알 수 있을 것 같고, 그래도 모호하다면 멘토님께 이 상황을 한번 공유드려봐도 어떤 실마리가 보일 것 같다. 이제 다음 하위 이슈들을 처리할 수 있을 것 같아 마음이 조금이나마 홀가분해졌다.
아직 SZ-85번 이슈의 완료를 위해선 몇 개의 하위 이슈들이 남아있지만, 이걸 완료해야 다른 팀원이 '드래그앤드롭 기능과 현재 컴포넌트를 연결하는 이슈' 및 'AI가 하위투두를 자동으로 생성해주는 이슈'를 처리할 수 있어서, 이걸 얼른 완료하는 게 중요하다. 그래야 나도 다음 급한 이슈인 'ECR, ECS를 통해 자동 배포하기'를 해볼 수 있을 것 같다. 암튼 파이팅이다.
오늘도 SZ-85번 이슈에서 삽질을 했다. 대부분은 상태관리에 관한 문제였다. 특히 프로젝트에서는 상태관리를 위해 zustand와 useContext API를 사용하는데, zustand 라이브러리를 통해서는 useTodoStore()와 useModalStore()라는 2가지의 store를 사용한 것이 복잡성의 원인이 되었다.
정확히는 selectedTodo라는 모달창에서 선택된 투두를 저장하기 위한 상태 변수가 useModalStore()에 선언되어 있었는데, 이것을 useTodoStore()에 선언된 줄 알고 useTodoStore에서 불러와서 제대로 모달창이 나타나지 않는 문제가 있었다.
또한 Jwt Parse error도 있었다. GPT에게 물어보니 토큰이 발급된 시간(iat)가 컴퓨터의 로컬 시간보다 빨라서 발생하는 에러로, 컴퓨터의 로컬 시간을 점검해 볼 것을 말해줬다. 나는 맥을 쓰고 있어서, 맥의 환경설정에 들어가서 서버가 애플의 시간 서버(time.apple.com)에서 제대로 시간을 받아오고 있는지를 확인했다. 그런데 이미 애플의 서버에서 제대로 현지 시각을 잘 받아오고 있어서, 오류의 원인은 알았지만 해결은 할 수 없었다... 현재 오류는 확률적으로 발생하는데, 만약 매번 또는 빈번하게 발생한다면 그때 더 자세히 알아봐야 할 것 같다.
투두 안에 하위 투두가 있고, 각 투두의 설정 아이콘을 눌러 모달창을 열 수 있고, 그 모달창을 통해 투두를 수정 및 삭제할 수 있는 기능을 개발 중이다. 원래 어제부터 만들었었는데, 시간을 많이 썼음에도 문제가 하나 터지고 겨우 해결하면 또 다음 게 생기고 이런 식이라서 아직도 막히고 있다.
오늘 배운 것은 상태 관리의 중요성이다. 투두 컴포넌트들은 크게 CategoryTodos > DailyTodos > DailyTodo > DailySubTodo의 계층 구조(사실 이 정도면 그냥 재귀 구조로 호출해도 될 것 같다...)로 되어 있는데, 문제는 TodoModal이라는 모달창 컴포넌트의 위치였다. 원래는 이 TodoModal이 DailyTodo와 DailySubTodo 컴포넌트에 있었는데, 그러다 보니 카테고리 값을 바꾸고 나서 투두를 클릭할 때 상태 관리가 제대로 되지 않아서 이전 카테고리의 투두 값을 모달에 띄운다는 문제가 있었다.
요즘은 개발을 할 때 GPT와 티키타카를 많이 하는데, 내가 문제 상황을 인식해서 그걸 풀어서 설명하면 GPT가 해결책을 제시해주고, 그럼 내가 그걸 따라해보거나 찾아보면서 이해하는 식으로 작업하고 있다. 모든 투두 컴포넌트 파일들을 첨부하고 GPT에게 위의 문제를 해결할 방법을 제시해달라고 하니, GPT는 기존의 CategoryTodos > DailyTodos > DailyTodo > TodoModal의 구조 대신 TodoModal을 CategoryTodo의 바로 아래, DailyTodos와 같이 배치했다. 그리고 그 대신 useContext나 zustand를 사용해서 상태 관리를 하는 식으로 코드를 제시해줬다.
그래서 이 방법을 사용해서 기존에 있었던 zustand로 투두 상태를 관리하는 useTodoStore() 대신, useModalStore()를 사용했다. 사실 상태 관리나 컴포넌트 작성에 정답은 없어서 useContext()를 사용할지 zustand를 사용할지 고민이었는데, 단순 변수와 set 함수(useState를 통해 쌍으로 생성되는 변수와 함수)만 있는 게 아니라면 zustand도 괜찮을 것 같다고 판단해서 zustand를 사용했다.
아직 모달창은 시작도 못 했고, CategoryTodos 컴포넌트 안에서도 해결해야 할 과제가 많이 남아있어 막막하지만, 근데 또 시간을 많이 썼는데 안 된 거라서 조금만 더 해보고 안 되면 일단 자고 내일 일찍 일어나서 조금이라도 더 해 봐야 할 것 같다. 늘 항상 막힘없이 개발이 잘 되는 게 아니고 막힐 때가 더 많은데, 오늘은 그런 날이었던 것 같다. 그래도 그런 날의 기록이라도 남기길 잘한 것 같다:)
프로그램을 진행하면서 새 맥북을 받았다! 너무 기뻤지만, 항상 사용하던 윈도우 노트북에서 새 맥북으로 바꾸려니 초기 설정부터 다시 해야 한다는 문제점이 있었다.
혹시라도 내가 다음에 또 노트북을 바꾸게 되거나, 비슷한 개발자들이 있다면 참고할 수 있도록 하기 위해서 간략하게나마 글을 적어본다.
✅설치할 프로그램들
1. brew
이 사이트에서 homebrew를 설치할 수 있다. 나는 homebrew는 mac에서 사용하는 일종의 패키지라고 알고 있고, 실제로 앞으로 설치할 많은 프로그램들(git, docker 등)이 brew install ___으로 간단히 설치가 가능하므로, 맥 사용자라면 homebrew 설치는 거의 필수적이라고 볼 수 있다.
필수 사항은 아니지만, 맥에서 기본으로 제공하는 터미널이 예쁘지 않다는 의견이 많아서 내 주변 맥 사용자들도 다 iterm을 사용하는 것 같아서 깔아봤다. 공식 사이트에서 설치할 수 있다.
5. python
지금 개발중인 프로젝트에서는 python, django를 사용하므로 파이썬도 깔아줬다. 공식 홈페이지에서 맞는 버전을 사용하면 된다.
6. java
지금 개발에서 사용하는 언어는 아니지만, 앱 개발을 하려면 android studio가 필요하고 android studio에서는 java를 필요로 하기 때문에 결론적으로 java도 필요했다. 주의할 점은, java를 설치하는 방법이 꽤나 여러가지라는 것이다. 그리고 android studio에서 빌드를 실행시키다 보면 jdk와 gradle의 버전이 맞지 않아서 나는 오류가 있다.
이 사이트에서는 여러 자바 버전을 설치할 수가 있어서 여러 블로그들을 찾아보다 이 사이트에서 특정 jdk 버전을 다운받아서 오류를 해결하였다.
자바가 잘 설치되었는지 확인하는 명령어는 다음과 같은데, 당연하게도 처음에 이 명령어를 입력하면 java, javac를 인식하지 못한다.
java -version
javac -version
그 이유는 java에 대해서 환경변수 등록을 해주지 않았기 때문이다.
맥에서는 루트 폴더 기준으로 .zshrc 라는 파일에 여러 환경변수 경로를 설정할 수 있는데, 이 안에 다음과 같이 입력해 주자.
참고로 맥에서 파일을 읽거나 쓰기 위해서는 vim 이나 nano 명령어를 사용한다. 그러니까 iterm의 초기 디렉토리에서 다음과 같이 입력하면 아마도 .zshrc 창이 열릴 거다.
vim .zshrc
여기서 -v 다음에 있는 17의 경우, 나의 경우는 jdk 17을 사용하고 싶어서 제한을 둔 것이다. 만약 다른 버전의 jdk를 명시적으로 사용하고 싶다면 17 대신 다른 숫자를 입력하면 된다.
우선 BFS에 필요한 큐를 구현하기 위해서 collections 라이브러리의 deque 자료구조를 사용했습니다.
일반 리스트로도 pop(0) 메소드를 통해 큐의 역할을 구현할 수는 있지만 이 경우 맨 앞의 원소를 제거할 때는 O(n) 시간이 걸린다는 단점이 있어서 deque() 라이브러리를 사용했습니다.
from collections import deque
def solution(maps):
m = len(maps)
n = len(maps[0])
dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]
def bfs(start_x, start_y, end_x, end_y):
# 거리 정보를 저장하는 배열. 시작점에서 닿을 수 없는 경우 -1의 값을 가짐
dist = [[-1] * n for _ in range(m)]
Q = deque([(start_x, start_y)])
dist[start_x][start_y] = 1
while Q:
cur_x, cur_y = Q.popleft()
for k in range(4):
nx = cur_x + dx[k]
ny = cur_y + dy[k]
# 배열 인덱스를 벗어나는 경우
if nx < 0 or nx >= m or ny < 0 or ny >= n:
continue
# 벽인 경우
elif maps[nx][ny] == 0:
continue
# 한 번도 방문하지 않은 경우만 큐에 추가
# 최단거리로 방문하려면 같은 블럭을 두 번 이상 방문하는 일은 없어야 한다.
if dist[nx][ny] == -1:
dist[nx][ny] = dist[cur_x][cur_y] + 1
Q.append((nx, ny))
return dist[end_x][end_y]
return bfs(0, 0, m-1, n-1)