엘리스 sw트랙 팀 프로젝트 중간 회고

개발

엘리스 sw트랙 팀 프로젝트 중간 회고
최종 수정일:

팀 프로젝트

엘리스 SW 트랙에는 2번의 팀 프로젝트가 있습니다.
그중 7주간 학습한 Vanilla JS와 Node.js를 활용해 2주가 조금 안 되는 기간 웹 서비스를 제작하는 첫 번째 팀 프로젝트를 진행 중입니다.
저번 주 화요일(14일)부터 시작해 이번 주 토요일(25일)까지 코드를 완성하고, 다음 주 월요일(27일)에 발표가 이뤄집니다. 일정을 보시면 아시겠지만, 프로젝트를 진행하기엔 아주 촉박한 시간이고, 덕분에 전 여가와 잠 등을 잃은 시간을 보내고 있습니다. 온종일 앉아있었더니 목, 어깨, 척추 등이 뽑힐 것 같네요.

사실 이런 거 쓰고 있을 시간도 없긴 한데, 며칠 동안 씨름했더니 골이 아파서 식히기도 할 겸 포스팅 시작해봤습니다.

이걸 잡담 카테고리에 넣어야 할지, 제작일지 카테고리에 넣어야 할지 고민하다 코드도 있는 김에 제작일지로 넣었습니다.
나름 웹 서비스 제작일지기도 한데다, 코드까지 있으니 충분히 제작일지로 볼 수 있지 않을까...싶네요.

어쩌다 팀장

낯도 가리고, 언변이 그리 훌륭하지도 않거니와, 통솔력이 있는 것도 아닌데 어쩌다 보니 팀장이 되었습니다.
내향적이고 소심한 사람도 외향적이게 되는 버튼 같은 게 있다고 생각하는데, 코딩이라는 분야, 팀이 구성됐을 때 다들 어색해서 일이 더디게 진행되는 상황이 제 버튼이 아닌가 싶네요.

기본이 내향적인 사람이다 보니 뭐 한 번 할 때마다 외향성 한 두어 달 치 가불받아 쓰는데, 알고리즘 스터디에서도 팀장을 맡고 있다 보니 지금 엘리스 트랙 들으면서 한 3년 치는 가불로 다 써버린 것 같습니다.
끝나면 어디 산에 들어가거나 성향이 조금 바뀌어 있거나 둘 중 하나지 않을까 싶네요. 😅

상황을 확인하고, 일을 분배하는 일도 해야 하는데, 여러 쓰레드에 작업 분배해서 최적화한다고 괜히 일 어중간하게 분배하면 오히려 쓰레드 하나가 혹사할 때보다 효율이 떨어지는 것처럼 일을 분배해서 해본 적 없는 사람이 이런 역할을 차지하고 있으니 당장 저도 혼자 할 때보다 효율이 떨어지고, 팀원 분들도 그러실 것 같아 걱정이 많습니다.
팀원 분들이 역량을 펼치는 길을 제가 막고 있나 걱정도 되어, 일을 크게 보는 시야를 가져야겠단 다짐도 많이 하는 요즘입니다.

코드 리뷰

엘리스 sw 트랙에 지원한 계기가

정돈데, 드디어 1번을 이뤘습니다.
코치님이 직접 제가 작성한 코드를 확인하시며 좋은 점, 개선할 수 있는 점, 고쳐야 할 점 등을 짚어주시니 코드를 개선해갈 방향이 명확히 보이는 것 같아 아주 만족스럽습니다.

돔 생성 팩토리 함수

바닐라로 웹 앱 만들기를 즐겨오면서도, createElement 등을 사용해 돔을 조작하는 건 꽤 귀찮은 일이었습니다.
관련 자료들을 찾아보며 팩토리 함수 관련 예제를 몇 번 보고도 '편할 거면 React를 쓰는 게 나을 것이고, 바닐라에선 이런 방식으로 작업하는 게 맞을 것'이란 - 아집이란 것이 다 그렇겠지만 - 돌이켜보면 왜 그랬는지 모르겠는 아집에 사로잡혀 있었습니다.

코치님께서 제가 사용해오던 방식은 유지 보수가 어렵다 말씀하시며, Jason Miller: Preact: Into the void 0 | JSConf EU 2017이란 동영상을 참고해 팩토리 함수를 만들어보라 조언해주셨고, 덕분에 아집에서 벗어나 팩토리 함수를 작성해봤습니다.

export default function el(nodeName, attributes, ...children) {
    const node =
        nodeName === "fragment"
            ? document.createDocumentFragment()
            : document.createElement(nodeName);
 
    Object.entries(attributes).forEach(([key, value]) => {
        if (key === "events") {
            Object.entries(value).forEach(([type, listener]) => {
                node.addEventListener(type, listener);
            });
 
            return;
        }
 
        if (key in node) {
            try {
                node[key] = value;
            } catch (err) {
                node.setAttribute(key, value);
            }
        } else {
            node.setAttribute(key, value);
        }
    });
 
    children.forEach((childNode) => {
        if (typeof childNode === "string") {
            node.appendChild(document.createTextNode(childNode));
        } else {
            node.appendChild(childNode);
        }
    });
 
    return node;
}

구조 자체는 꽤 간단합니다. nodeName, attributes와 자식 노드들을 인자로 받아 element를 반환하는 형태입니다.

하나 단점이 간단한 이벤트 리스너도 등록하기가 꽤 불편했으나, events란 객체를 추가해 이를 해결했습니다.

import { addClickEvent } from "../router";
import "../../css/SignInPage.css";
 
export default function SignInPage() {
    const container = document.createElement("div");
    const logoWrap = document.createElement("div");
    const logo = document.createElement("img");
    const signInWrap = document.createElement("div");
    const signInTitle = document.createElement("h2");
    const signInInfo = document.createElement("p");
    const signInButton = document.createElement("button");
    const googleIcon = document.createElement("img");
    const signInText = document.createElement("span");
 
    container.classList.add("sign-in");
 
    // Logo
    logoWrap.classList.add("sign-in__logo");
    logo.src = "/static/images/logo.svg";
    logoWrap.append(logo);
 
    // Sign In Container
    // Title
    signInWrap.classList.add("sign-in__container");
    signInTitle.classList.add("sign-in__title");
    signInInfo.classList.add("sign-in__info");
    signInTitle.innerText = "로그인";
    signInInfo.innerText =
        "구글로 간편하게 로그인 하고 맛식 저장, 공유 하세요!";
 
    // Button
    signInButton.classList.add("sign-in__button");
    googleIcon.src = "/static/images/google.svg";
    signInText.innerText = "Google 계정으로 로그인";
    signInButton.append(googleIcon, signInText);
    addClickEvent(signInButton, "/user");
 
    signInWrap.append(signInTitle, signInInfo, signInButton);
 
    // Append
    container.append(logoWrap, signInWrap);
 
    return container;
}

제가 사용해오던 방식은 이랬습니다.

import el from "../utils/dom";
import { addClickEvent } from "../router";
import "../../css/SignInPage.css";
 
export default function SignInPage() {
    const signInButton = el(
        "button",
        {
            className: "sign-in__button",
        },
        el("img", { src: "/static/images/google.svg" }),
        el("span", {}, "Google 계정으로 로그인"),
    );
 
    addClickEvent(signInButton, "/user");
 
    return el(
        "div",
        { className: "sign-in" },
        el(
            "div",
            { className: "sign-in__logo" },
            el("img", { src: "/static/images/logo.svg" }),
        ),
        el(
            "div",
            { className: "sign-in__container" },
            el("h2", { className: "sign-in__title" }, "로그인"),
            el(
                "p",
                { className: "sign-in__info" },
                "구글로 간편하게 로그인 하고 맛식 저장, 공유 하세요!",
            ),
            signInButton,
        ),
    );
}

비약적으로 향상된 가독성이 보이시나요…
전 눈물이 흐를 뻔한 걸 얼마 안 나온 크리스마스에 찾아오실 산타할아버지를 생각하며 가까스로 참았습니다. 🥺

외에도 다양한 말씀을 많이 해주셨고, '좋은 코드란 무엇일까', '더 유지 보수하기 쉽게 작성하는 방법은 없을까' 등 다양한 생각할 거리를 얻는 계기가 되었습니다.
제가 프로그래밍에서 손을 떼기 전까진 놓지 못할, 놓지 않아야할 고민거리기도 한 것 같습니다. 주어진 기간 안에 프로젝트를 끝내는 게 더 급한 목표니 프로젝트부터 끝내두고 조금 더 깊이 고민해보며 이 고민을 이어갈 수 있을 것 같네요.
팀 프로젝트가 끝나면 위 고민과 코드 리뷰를 통해 얻은 지식을 바탕으로 지금까지 해왔던 사이드 프로젝트들 모조리 뜯어고칠 생각 하니 벌써 걱정과 기쁨이 동시에 몰려오는데, 이 감정을 도무지 어떻게 설명해야 할지...😅

나아가 저도 실력자가 되면 제가 리뷰를 받으며 느낀 이 벅찬 감정을 누군가에게 느끼게 해주고 싶다는 다짐도 했습니다.
물론 일단 실력자부터 돼야겠지만요.

마지막까지

이만 줄이고 이 글을 작성하는 와중에도 가까워지는 데드라인 내에 팀 프로젝트를 잘 마무리하기 위해 vsc로 돌아가 보겠습니다.
행복한 연말 되시고, 저는 프로젝트 끝내느라 바쁠 크리스마스 부디 제 몫까지 즐겨주시길…

Report an issue