[Wecode] 기업협업 후기

mokyoungg·2020년 7월 21일
2

WECODE 후기

목록 보기
3/3

1. 기업협업(프로젝트) 소개

  • 주제 : 회사의 새로운 광고 상품의 프로토타입 제작
  • 인원 : 3명(Front 1명 Back 2명)
  • 기간 : 06.22 - 07.16
  • 기술 스택(Front) : React / Styled-Components

2차 프로젝트가 끝나고 바로 기업협업에 참여하였다.
기업협업은 위코드의 마지막 커리큘럼으로, 1달이라는 기간 동안
협업하는 회사의 인턴으로 개발자 업무를 경험할 수 있다.

나는 미디어업종의 회사에 들어가게 되어 그 회사가 준비하는 새로운 광고 상품의 프로토타입을 개발하는 일을 맡았다. 광고 상품은 AI 기술을 활용하여 사용자가 선택한 이미지(의류)에 대한 유사 상품을 찾아주는 상품이었다. 프로토타입으로 활용될지의 여부는 잘 모르겠으나 아무튼 책임감을 가져야했고 기간 동안 100% 만족은 아니지만 어느 정도 완성시켰다.

2. 내가 맡은 부분

  • 모든 페이지 제작(회원가입, 로그인, 메인, 디테일)
  • 주요 기능 : 필터(제품, 성별, 가격) / 반응형

해당 기업에 참여하는 프론트앤드가 나 혼자라서 처음부터 끝까지 제작해볼 수 있었다.
매우 큰 기회이자 값진 경험이었다. 일은 많았지만 덕분에 더 배울 수 있었고 어디가 부족한지, 무엇을 공부해야하는지에 대해 생각할 수 있었다.

3. 잘한 점 + 아쉬운 점 + 해결/개선 방법

잘한 점 : 일단 완성한 점, 라이브러리를 처음 써본 것
아쉬운 점 : 코드 완성도가 떨어지는 점(필터, 반응형), 요구한 부분을 100% 수행하지 못한 점
해결/개선 방법 : 공부, 공부, 공부, 작업, 작업, 작업

완성

회사에서 결과물에 대한 기준이 명확하여 초반부터 새로 기획을 하거나 디자인을 해야하는 필요가 적었다. 덕분에 거의 모든 기간을 개발에만 집중할 수 있었고 프로토타입을 완성하였다.

라이브러리

프로젝트 기간 동안 써보고 싶었지만 하지 못 했던 라이브러리를 사용해 볼 수 있었다.
프로젝트 기간에도 시도는 해보았지만 실패하였다.
이번에는 기간이 충분하여 천천히 라이브러리의 문서를 읽고 이것저것 시도해보며 라이브러리를 결과물에 넣을 수 있었다. 라이브러리에 대한 기존에 가지고 있던 거부감이라든가 어려움이 조금은 사라진 것 같다.


코드완성도, 가독성

코드 완성도가 떨어진다. 어떻게든 화면에 나타나게 하려고. 또는 기존의 계획과는 달리 추가되는 사항이 있다보니. 기존의 코드에 새로운 코드(불필요하거나 줄일 수 있는)가 덕지덕지 붙었다.
그렇다고 기능이 완벽하게 작동하는 것도 아니다. 에러가 발생하여 코드를 수정하려고 하면 무척 고생할 것 같다. 계획을 잘 세워야 한다. 줄일 수 있으면 줄여야 한다. 코드를 깨끗하게 써야한다.


리덕스 / 웹 / 백앤드 공부

공부해야한다. 프로젝트 후기에도 말했지만 공부하고 연습해야 한다. 이전과 다른 점은 무엇을 공부해야할지에 대해 스스로 느꼈다는 점이다. 나는 리덕스를 공부해야 한다.
리덕스에 사용 이유에 대한 글을 읽을 때, 보통 리덕스의 장점으로 state의 값을 한 곳에서 관리한다는 점을 이야기하는데 머리로만 이해를 하고 있었다. 아마 편할 것이다. 라고.
그러나 이번에 경험했다. 리덕스는 필요하다. state 값이 난장판이 되었다. 조부모에서 부모로, 부모에서 자식으로 넘어가는 state가 많다보니 어느 순간 state가 무엇을 위해 것인지, 넘어가는 state에 필요한 것은 무엇인지 등등 대환장 파티가 열렸다.
그래서 리덕스. 반드시 리덕스를 공부해야한다.

추가적으로, 현업 개발자분과 이야기할 수 있는 기회가 있었는데 웹 개발에 필요한 개념과 용어에 대한 이해가 매우매우매우 부족하다는 것을 알게 되었다. 웹에 대한 공부를 해야하고
또! 개발은 협업이고 커뮤니케이션이니, 백앤드 개발자와 소통하기 위해서라도 백앤드를 조금이나마 공부해야한다. 할게 산더미다.

4. 기록하고 싶은 코드

4-1. state

  const [data, setData] = useState([]);
  const [category, setCate] = useState([]);
  const [img_url, setImgUrl] = useState("");
  const [img_file, setImgFile] = useState("");
  const [boxview, setBox] = useState(false);
  const [preview, setPre] = useState("");
  const [checkk, setCheck] = useState([]);
  const [strTonum, setNS] = useState([]);
  const [coordi, setCoor] = useState([]);
  const [tempD, setTemp] = useState([]);
  const [checked, setChecked] = useState(false);
  const [value, setValue] = useState(``);
  const [load, setLoad] = useState(true);
  const [res, setRes] = useState([]);
  const [ren, setRen] = useState(true);
  const [price, setPrice] = useState([]);
  const [tempP, setTempP] = useState([]);
  const [minP, setMin] = useState();
  const [maxP, setMax] = useState();
  const [gender, setGen] = useState([]);
  const [checkG, setGch] = useState([]);
  const [strToAl, setGA] = useState([]);
  const [filterOn, setOn] = useState(false);

사실 아직 현업 개발자의 코드를 제대로 본적이 없어서 이 정도가 많은지 적당한지 잘 모르겠다.
아무튼 이게 최상위 컴포넌트에 있는 state 값이다. 이게 자식으로, 또 자식으로 넘어가다보니 헷갈리기 시작했다.(코드 수정할 때) 그리고 클린코드, 코드리펙터링을 앞으로 공부할 예정이지만 무엇보다 state의 값의 이름을 잘 지어야한다.

4-2 fetch

 useEffect(() => {
    //loading indicator 부분
    setTimeout(() => {
      setLoad(false);
    }, 3000);

    // 데이터를 받는 부분
    fetch(`${URL}`)
      .then((res) => res.json())
      .then((res) => {
        // boxview 의 여부에 따라 받는 데이터가 다름(최초 페이지와 검색 결과 페이지)
        if (boxview === false) {
          //가격 필터를 위한 세팅
          const price = res.product.map((data) => data.price);
          const max = price.reduce(function (pre, cur) {
            return pre > cur ? pre : cur;
          });
          const min = price.reduce(function (pre, cur) {
            return pre > cur ? cur : pre;
          });

          // 받은 데이터를 state 값으로 저장
          setData(res.product);
          setCate(categoryFilter(res.product));
          setMin(min);
          setMax(max);
          setPrice([min, max]);
          setGen(genderFilter(res.product));

          // checkk 의 값을 통해 state 값의 변화를 주고 새로 render 한다.
          // checkk 는 카테고리 필터 부분(outer, top 등)에 따라 필터
          if (checkk.length > 0) {
            const checkedData = lastFilter(res.product, strTonum);
            setData(checkedData);

            //가격에 따라 필터가 되도록
            if (tempP.legnth > 0) {
              const priceData = checkedData.filter(
                (data) => tempP[0] <= data.price && data.price <= tempP[1]
              );
              setData(priceData);
              setPrice(tempP[0], tempP[1]);

              // gender 체크에 따라 필터가 되도록
              if (checkG.length > 0) {
                const genderData = genLastFilter(priceData, strToAl);
                setData(genderData);
              }
            }

            // 가격에 따라 필터가 되도록(가격 필터만)
          } else if (tempP.length > 0) {
            const priceData = res.product.filter(
              (data) => tempP[0] <= data.price && data.price <= tempP[1]
            );
            setData(priceData);
            setPrice([tempP[0], tempP[1]]);

            // gender 에 따라 필터가 되도록(gender 필터만)
          } else if (checkG.length > 0) {
            const genderData = genLastFilter(res.product, strToAl);
            setData(genderData);
          }

          // 검색했을 때(url 또는 파일) 데이터 받는 부분
        } else if (boxview === true) {
          const price = tempD.map((data) => data.price);
          const max = price.reduce(function (pre, cur) {
            return pre > cur ? pre : cur;
          });
          const min = price.reduce(function (pre, cur) {
            return pre > cur ? cur : pre;
          });
          setData(tempD);
          setMin(min);
          setMax(max);
          setPrice([min, max]);
          if (checkk.length > 0) {
            const checkedData = lastFilter(tempD, strTonum);
            setData(checkedData);
            if (tempP.length > 0) {
              const priceData = checkedData.filter(
                (data) => tempP[0] <= data.price && data.price <= tempP[1]
              );
              setData(priceData);
              setPrice([tempP[0], tempP[1]]);
              if (checkG.length > 0) {
                const genderData = genLastFilter(priceData, strToAl);
                setData(genderData);
              }
            }
          } else if (tempP.length > 0) {
            const priceData = tempD.filter(
              (data) => tempP[0] <= data.price && data.price <= tempP[1]
            );
            setData(priceData);
            setPrice([tempP[0], tempP[1]]);
          } else if (checkG.length > 0) {
            const genderData = genLastFilter(tempD, strToAl);
            setData(genderData);
          }
        }
      });
  }, [checkk, boxview, ren, checkG]);

최초 render가 되었을 때 실행되는 코드다. 백앤드에서 데이터를 받고 그 데이터를 가공하여 화면에 보여주는 코드다. 심지어 이미지를 보내고 받는 부분은 최초 reder, fetch의 api와는 다른 api에서 데이터를 받는데 그것도 여기에 작성하였다. 뭔가에 홀린 듯 코드를 작성하였다.
지금 생각해보면 이럴 필요가 없던 것 같다. componentDidMount 가 아닌 ComponentDidUpdate로 접근하여 state 값이 변할 때 render가 되게 했으면 이렇게 길게 안 써도 되었을 것 같다. 이 코드는 실패다. 필터를 위해 if문에 if문이 계속 붙는다. 이럴 필요가 없을 것이다. 지저분하다.

데이터와 카테고리, 필터

//받는 데이터의 형태
{img_uri: ###, product_label1:U, product_label2: F, product_label3 : 13}
// lable1: 성별, lable2: 제품카테고리(옷,신발,가방 등), label3: 상세카테고리(아우터/바지 등)

// 받은 데이터의 label2 와 label3의 값을 통해 문자로 바꿈
  const categoryFilter = (data) => {
    let result = [];

    const clothing_rule = {
      10: "Everything",
      11: "Outer",
      12: "Top",
      13: "Dress",
      14: "Jumpsuit",
      15: "Suit",
      16: "Swimsuit",
      17: "Pants",
      18: "Skirts",
    };

    const footwear_rule = {
      10: "Everything",
      11: "Shoes",
      12: "Sneakers",
    };

    const bag_rule = {
      10: "Everything",
      11: "Handbag",
      12: "Backpack",
      13: "Travelbag",
    };

    const jewelry_rule = {
      10: "Everything",
      11: "Necklace",
      12: "Earring",
      13: "Bracelet",
    };

    const accessory_rule = {
      10: "Everything",
      11: "Eyewear",
      12: "Belt",
      13: "Scarves",
      14: "Gloves",
      15: "Hats",
      16: "Watch",
      17: "NeckTie",
    };

    const anything_rule = {
      10: "Anything but Clothing",
    };
    const impossible_rule = {
      10: "분류불가",
    };

    data.map((data) => {
      if (data.product_label2 === "C") {
        result.push(clothing_rule[data.product_label3]);
      } else if (data.product_label2 === "F") {
        result.push(footwear_rule[data.product_label3]);
      } else if (data.product_label2 === "B") {
        result.push(bag_rule[data.product_label3]);
      } else if (data.product_label2 === "J") {
        result.push(jewelry_rule[data.product_labe3]);
      } else if (data.product_lable2 === "A") {
        result.push(accessory_rule[data.product_label3]);
      } else if (data.product_label2 === "Z") {
        result.push(anything_rule[data.product_label3]);
      } else if (data.product_labe2 === "T") {
        result.push(impossible_rule[data.product_lable13]);
      } else if (data.product_label1 === "U") {
      }
    });
    return Array.from(new Set(result));
  };

  // 카테고리의 문자를 숫자 형식으로 바꿔 줌
  const stringToNum = (aaa) => {
    let result = [];
    const rule = {
      Everything: { product_label2: "C", product_label3: "10" },
      Outer: { product_label2: "C", product_label3: "11" },
      Top: { product_label2: "C", product_label3: "12" },
      Dress: { product_label2: "C", product_label3: "13" },
      Jumpsuit: { product_label2: "C", product_label3: "14" },
      Suit: { product_label2: "C", product_label3: "15" },
      Swimsuit: { product_label2: "C", product_label3: "16" },
      Pants: { product_label2: "C", product_label3: "17" },
      Skirts: { product_label2: "C", product_label3: "18" },
      Shoes: { product_label2: "F", product_label3: "11" },
      Sneakers: { product_label2: "F", product_label3: "12" },
      Handbag: { product_label2: "B", product_label3: "11" },
      Backpack: { product_label2: "B", product_label3: "12" },
      Travelbag: { product_label2: "B", product_label3: "13" },
      Necklace: { product_label2: "J", product_label3: "11" },
      Earring: { product_label2: "J", product_label3: "12" },
      Bracelet: { product_label2: "J", product_label3: "13" },
      Eyewear: { product_label2: "A", product_label3: "11" },
      Belt: { product_label2: "A", product_label3: "12" },
      Scarves: { product_label2: "A", product_label3: "13" },
      Gloves: { product_label2: "A", product_label3: "14" },
      Hats: { product_label2: "A", product_label3: "15" },
      Watch: { product_label2: "A", product_label3: "16" },
      NeckTie: { product_label2: "A", product_label3: "17" },
      "Anything but Clothing": { product_label2: "Z", product_label3: "10" },
      분류불가: { product_label2: "T", product_label3: "17" },
    };

    aaa.map((item) => {
      result.push(rule[item]);
    });
    return result;
  };
  

필터에 필요한 카테고리, 아우터나 팬츠 등을 하드코딩하지 않고 데이터에 따라 변화시키고 싶었다. 이를 위해 UF13 과 같이 들어오는 데이터를 가공하여 outer, pants 와 같은 형태로 만들었다. 문제는 이후 필터를 할 때는 이러한 이름이 아닌 UF13과 같은 형식의 데이터가 필요했다는 점이다. 처음부터 데이터를 이런식으로 가공을 하지 않았다면.. 나중에 필터를 할 때, 최초의 데이터 형태와 같은 꼴이 필요하다는 것을 알았다면 이런 식으로 코드를 작성하지는 않았을 것이다.
데이터가 들어오는 것을 어떻게 글자로 보여줄까 만 고민했다.
이게 해결되니 그럼 필터는 어떻게 해야하지를 고민했고. 역시 경험이 부족하다. 더 심각한 것은 심지어 성별 부분 코드는 똑같은 형태로 따로 작성했다는 점이다.

파일을 이미지로 미리 보여주기

 const setFile = (e) => {
    let reader = new FileReader();
    let file = e.target.files[0];
    //setImgFile(file);
    reader.onloadend = () => {
      const base64 = reader.result;
      if (base64) {
        setPre(base64.toString()); //
      }
    };
    if (file) {
      reader.readAsDataURL(file);
      setImgFile(file);
      fileSearch(file);
    }
  };

이미지를 검색하면 검색과 유사한 제품을 보여주는 것은 물론 이용자가 무엇을 검색했는지 볼 수 있게 만들어야 했다. img url로 검색하면 img 태그의 속성으로 넣으면 쉽게 해결되는 문제지만, 이게 파일을 업로드했을 땐 심각해졌다. 검색하여 답을 찾아 해결은 했지만 사실 저 코드엔 모르는게 너무 많다. 공부할 게 많다는 것이다.

map을 위해 다른 데이터를 같은 이름으로 넘겨주자

//부모
 <Section>
          <Bar>
            <FilterName>CATEGORY</FilterName>
            <ArrowBtn
              idx="1"
              curIdx={curIdx}
              arrowrotate={arrowrotate}
              handleRotate={handleRotate}
            />
          </Bar>
          <Select idx="1" curIdx={curIdx} arrowrotate={arrowrotate}>
            {category.map((category, index) => {
              return <FilterBox check={check} category={category} />;
            })}
          </Select>
        </Section>
        <Section>
          <Bar>
            <FilterName>Gender</FilterName>
            <ArrowBtn
              idx="2"
              curIdx={curIdx}
              arrowrotate={arrowrotate}
              handleRotate={handleRotate}
            />
          </Bar>
          <Select idx="2" curIdx={curIdx} arrowrotate={arrowrotate}>
            {gender.map((gender, index) => {
              return <FilterBox category={gender} check={genderCheck} />;
            })}
          </Select>
        </Section>
 
 //자식(map으로 반복되는 컴포넌트)
 const FilterBox = ({ check, category }) => {
  return (
    <ItemContainer>
      <ItemInner>
        <CheckBox type="checkbox" onClick={() => check(category)} />
        <Label>{category}</Label>
      </ItemInner>
    </ItemContainer>
  );
};

해당 코드에서 map이 돌아가는 FilterBox 컴포넌트는 생김새는 같으나 다른 정보를 가지고 있어야한다. 자식컴포넌트에 category라는 같은 이름으로 넘기지만 가지고 있는 데이터는 다르다.
그러나 자식 컴포넌트는 그 정보가 무엇이든 상관없이 일단 map으로 반복한다.


쓰고 싶은 코드는 많지만 이 글은 후기이기 때문에 다른 글에서 코드를 더 쓸 예정이다.

5. 기업협업 느낀 점

위코드의 커리큘럼으로 기업협업에 나갔다. 기업 협업은 4~5개 정도의 회사가 참여해주었다.
기업협업을 나가는 회사마다 개발문화가 다르고 현재 진행되는 일도 다르기 때문에 같은 커리큘럼이라고 해도 얻어가는 부분이 다르다.

프론트앤드 기준으로, 다른 분들은 리액트 네이티브나 타입스크립트를 배우는 경우도 있었다. 나의 경우엔 지금까지 배운 리액트와 스타일 컴포넌트로 하나의 결과물을 만들어보는 경험 할 수 있었다. 처음엔 리액트 네이티브나 타입스크립트를 공부해보지 못 한다는 것에 좀 불안하고 불평도 있었지만 생각해보니 지금 하는 것도 잘 못하는데 이 기술에 대해 더 능숙해질 수 있는 좋은 기회라 생각되었다. 무엇보다 프론트앤드가 1명이라 나 스스로, 처음부터 끝까지 만들어 볼 수 있는 굉장히 좋은 기회였다.

그리고 현업의 회사 관계자 분들의 눈높이에 대해 고민할 수 있는 경험이었다. 지금까지 동기들끼리 이 정도만 보이면, 이 정도 기능만 하면, 이라고 생각했지만 회사의 관계자 분들은 내가 지금까지 생각했던 것보다 두 걸음 정도는 더 앞선(진행속도나 완성도에서) 결과물을 원하였다.
인턴이라 큰 압박은 주시지 않았지만 어느 정도의 압박을 느낄 수 있었고. 회사 관계자를 넘어 이용자가 나의 결과물을 사용했을 때 어떤 경험을 하게 될까 고민할 수 있는 시간이었다.

개발이라는 것이 참 쉽지가 않구나
하지만 극복한다.

profile
생경하다.

0개의 댓글