아직 Git 특강에서 머리가 뜨거워질 수도 있는 Branch는 배우지 않았지만 우리 조는 웹 개발에 대한 기본은 충분하다고 생각해서 랜딩 페이지를 기본 와이어프레임과 기본적인 기능 명세서만 두고 각자의 스타일로 살을 덧붙여서 완성해보는 걸 목표하기로 했다.
실무였다면 어차피 같은 랜딩페이지를 각자 파서 개발한다는 게 정말 비효율적이겠지만 branch를 나누고 한 파일에서 작업 후 각자 붙인 살을 main으로 merge하다 보면 어려울 수는 있어도 flow를 이해하기엔 꽤 도움이 될 것 같아 내가 제안한 사항이다.
이게 너무 어렵거나 너무 flow적으로 의미없을까봐 걱정도 되지만 이번 미니프로젝트는 웹 개발을 터무니 없이 못하는 나 스스로도 공부를 해야하는 만큼 나만의 커리큘럼을 채워나간다는 생각으로 임하고 있으니 어떻게든 유의미한 결과를 남기고자 노력하고 있다.
랜딩에서 구현할 기능은 정말 단순하게 지난 번에 짰던 자기소개 홈페이지로 연결하기, 팀의 목표/약속을 추가하는 팀의 약속칸과 방명록을 남길 수 있는 방명록 기능이다.
다만 여기서 웹 종합반에서 배운 Firebase의 Firestore를 채용하기로 했는데 너무 중구난방으로 개발하면 갈피를 잡기 어려울 수 있으니 최소한의 스키마를 준비하기로 했고 이를 ERD Cloud로 공유했다.
갑자기 스키마를 들이밀면 머리만 뜨거워질 수 있고 애초에 Firestore는 NoSQL이기 때문에 이 관계도를 보고 Field를 통일하며 문서를 작성해보자는 취지였다.
그런데 지금 보니 고작 Field 4개 명세하자고 억지로 ERD Cloud를 쓴 건가? 생각도 든다. 살짝 부끄럽지만 공유는 해야겠다.
모두가 각자의 branch 에서 개발할 건데 Firebase 프로젝트도 4개일 필요는 없으니 내가 대표로 파서 편집자로 초대 후 하나의 Firestore에서 Collection을 테스트용으로 구분해 테스트하기로 했다.
최초 양식은 각자의 이름을 따서 (Ex: jaewon) 콜렉션을 파고 문서 ID를 objective, guest_book 으로 나누고 안에 Field를 채워서 팀원분들께 이렇게 진행하면 될 것 같다고 자신만만하게 공유를 진행했다. 시간이 지나고 팀원분의 질문을 받고 나서야 Firestore를 개판으로 이해하고 진행했구나를 깨달아서 부랴부랴 수정했다.
얼핏 보고 Collection 을 하나의 DB, 문서 ID를 Table, 문서 안의 Field를 Column-row로 이해했었는데 사실 Collection이 테이블이고 문서 ID는 row의 pk고 Field는 그냥 데이터일 뿐이었다.
그렇기에 objective와 guest_book으로 짠 문서는 사실 무의미했고 팀원분들께 빠르게 정정하고 jaewon-objective, jaewon-guestbook 의 형식으로 테스트하고 최종 랜딩에선 objective, guestbook 두 개의 collection 으로 통합하기로 했다.
NoSQL 마저 너무 SQL에 빗대어 표현하려던 게 큰 실수였다.
iOS에선 Cocoapod, Android에선 Gradle로 편하게 관리하던 의존성을 Javascript에선 어떻게 관리해야할까?
사실 나는 이 생각을 놓치고 있었고 오늘 팀원 분께 질문을 받고 나서야 고민을 하기 시작했다.
Firebase module을 불러올 때 버전도 명시되어 있기에 레거시 코드를 따오거나 구버전 문서를 읽거나 하면 4명의 버전이 틀릴 수도 있는데 협업 시작 단계에서 미리 정하지 못해 각자 관리하게 될 뻔 했다.
우선은 부랴부랴 Firebase 가이드에서 제공하는 최신 버전의 CDN을 공유해서 사용하기로 했는데 일단 Node 기반으로 개발하는건 아니기도 하고 바닐라 JS다 보니 npm 옵션은 사용하지 않고 있었다.
CDN은 URL로 명시를 하니 더욱 조심해야할 것 같아서 이걸 고민하다가 원래 secret key 관리에만 사용하려던 config.js에 Firebase import도 전부 진행해서 모듈화해 script에서 다시 불러들여와 사용하는 방법을 골랐고 썩 나쁜 방법은 아닌 걸로 확인했다.
다만 이 과정에서도 CORS 라는 문제가 생겨서 시간을 크게 소비하고 말았다.
아예 처음 보는 건 아니지만 직접 맞닥뜨린건 이번이 처음인 것 같다. 여태까지 로컬 파일 기준으로 코딩을 진행하던 나는 CORS의 벽 앞에서 Chrome extension이나 설정 변경등으로 넘어가보려고 했으나 결국엔 local http server를 열어서 디버깅하는 법을 골랐다.
CORS같은 정책은 사실 보안적으로는 우릴 안전하게 지켜주는 거니 불만이 있기보단 이걸 정확히 이해하는게 좋을 것 같아 최대한 이해하려고 노력해봤다. 파일 시스템상에서 진행한 코딩이 CORS 정책에 부딪히는 이유는 크게 두가지였는데 조사해보기론 아래와 같다.:
- type='module'로 불러올 때 더 예민해지는 보안
모듈 스크립트는 리소스를 동적으로 계속 불러올 수 있기 때문에 취약할 경우 지속적인 위협을 받게될 수 있다. 자바스크립트 에러를 더 꼼꼼히 체크하는 strict mode로 처리된다는 얘기가 있다.- 파일 시스템 보호를 위한 조치
웹 페이지에서 거꾸로 로컬 파일 시스템에 접근하는 취약점 방지를 위해 파일 시스템에서 요청을 보낼 때 출처가 null로 처리되게끔 설계가 되어있기 때문에 CORS 정책에 걸리게 된다.
서버에 대한 이해도가 너무나도 낮은 나는 '그거 배우려고 신청했는데' 라고 징징대며 최대한 쉬운 방법을 골랐고 npm으로 http-server를 설치해서 localhost를 열게 되었다. (설치 후 node가 너무 구버전이라 이슈가 생긴건 덤이다.)
그렇게 CORS는 어떻게든 회피했나 싶었는데 module 불러오는 path를 잘못 설정해서 수정했는데 수정해도 어째 같은 에러가 수도 없이 반복이 됐다.
커맨드에 뜨는 서버 로그는 이미 바꾼 경로를 예전 경로로만 인식하고 크롬 자체를 껐다 켜도 똑같아서 이게 대체 무슨 일인가 싶었는데 캐싱 문제였다. Cmd + Shift + R 한번 누르고 해결된걸 보니 속이 다 아팠다.
크롬이 캐싱을 어떻게 진행하는지 정확히는 모르지만 나도 처음엔 혹시 아예 파일을 잘못 가리키고 있거나 할까봐 html이나 style을 건드린 건 정상 반영돼서 캐싱 문제라는 의심이 늦어졌는데 다음부턴 의심의 길을 넓게 열어놔야할 것 같다.
어쨌든 결과적으로 Firebase Key와 Module을 완전 분리해 사용하는 것에 성공했고 시간을 워낙 많이 뺏겨 알고리즘 구현 부분은 내일 마저 작성하기로 했다.
한국중학교에 다니는 학생들은 각자 정수 번호를 갖고 있습니다. 이 학교 학생 3명의 정수 번호를 더했을 때 0이 되면 3명의 학생은 삼총사라고 합니다. 예를 들어, 5명의 학생이 있고, 각각의 정수 번호가 순서대로 -2, 3, 0, 2, -5일 때, 첫 번째, 세 번째, 네 번째 학생의 정수 번호를 더하면 0이므로 세 학생은 삼총사입니다. 또한, 두 번째, 네 번째, 다섯 번째 학생의 정수 번호를 더해도 0이므로 세 학생도 삼총사입니다. 따라서 이 경우 한국중학교에서는 두 가지 방법으로 삼총사를 만들 수 있습니다.
fun solution(number: IntArray): Int {
var answer: Int = 0
var max = number.count() - 1
for (i in 0..max) {
for (j in i + 1..max) {
for (k in j + 1..max) {
if (number[i] + number[j] + number[k] == 0) answer += 1
}
}
}
return answer
}
범위를 보자마자 삼중 포문이 떠오르긴 했지만 정녕 그것밖에 해답이 없나 고민하다가 반복 횟수를 더 이상 줄일 획기적인 방법이 떠오르지 않아서 작성한 결과 퍼포먼스는 생각보다 나쁘지 않아 다행이었다.
다른 제출을 보며 비교해보니 반복을 세 번해서 최적화 하는게 최선인 것 같긴한데 이번에도 디테일을 놓친 부분이 있어 가져왔다.
if (number.count() - i < 3) break
첫 번째 for 문에서 guard clause로 반복문을 빠르게 빠져나가는 걸 고려하지 못했다. 어차피 연산에 필요한 숫자는 세 개인 만큼 guard clause를 적용해서 최적화해야 했는데 간단한 걸 놓친게 아쉬움이 남았다.