배너 이미지

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

최종 수정일 : (11개월 전)

팀 프로젝트

엘리스 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로 돌아가 보겠습니다.
행복한 연말 되시고, 저는 프로젝트 끝내느라 바쁠 크리스마스 부디 제 몫까지 즐겨주시길…


profile

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

주의 : 비밀 댓글 사용 시 수정 기능을 이용할 수 있는 시간이 지나면 작성자도 내용 확인이 불가능합니다.