분리배출 OX퀴즈 - 15일 프로젝트 챌린지 회고

·2021년 8월 17일
0

시작

조 배정, 기획

지난번 11일 메이킹챌린지의 경험이 좋아서, 다음 기수의 15일 프로젝트에 다시 한번 신청했고, 랜덤으로 2조에 배정됐다. 팀장과 팀명을 정하고, 프로젝트로 어떤 걸 만들어볼지 아이디어를 주고받았다. 지난번에 한번 해봤으므로 팀장은 굳이 맡지 않기로 했다.

무엇을 만들어볼까 기획회의를 했는데 기후위기로 환경에 대한 관심이 높았다. 이에 따라 분리수거 레벨 테스트를 포함한 환경 관련 웹사이트를 만들어보자는 이야기에 공감하는 분이 제일 많았고, 우선 분리수거(분리배출)에 대한 OX퀴즈를 먼저 만들어보는 걸로 가닥을 잡았다.

특히 분리수거할 때 헷갈리는 품목들에 대해서 어떻게 분리수거하는 방법이 맞는지 물어보는 OX퀴즈와 함께, 현재 환경오염이나 분리배출의 실태를 보여준다.

협업 환경

소스코드 공유를 어떻게 할까 고민했는데, 역시 git, github밖에 생각이 나지 않았다. git을 아무도 안 써보셨을 것 같은데, 그래도 일단 git을 써보기로 결의했다.
코딩을 계속 하려면 깃은 피해갈 수 없을 것이고, 쓸 수만 있다면 엄청난 축복이므로!

모두가 모일 수 있는 밤 11시에 급히 새로 합류하신 팀원분까지 모두 게더타운에 모였다. github계정을 모두 만들고 약식으로 git clone, git add, git commit, git push 명령어를 알려드렸다.
github계정을 만들고 터미널에서 명령어를 칠 수 있게 되어 드디어 git clone을 시작하는 데까지만 40~50분 정도가 걸렸다. 모두가 처음이므로 쉽지 않으리라 생각은 했지만, 역시 쉽지 않았다!

그래도 다들 간단한 깃의 기본 기능을 통해 어쨌거나 깃허브와 깃을 이용한 소스코드 공유가 가능해졌다! 내가 처음 깃을 접했을 때와 비교하면 엄청난 속도! 나도 아직 지난 프로젝트에서밖에 써보지 않은 branch 기능까지는 가지 않기로 했다.

최대한 서로 같은 파일은 수정하지 않도록, 각자가 작업하는 파일을 최대한 분리해서 혹여나 commit이 충돌 나도 수정사항이 날아가지 않도록 했다.

역할 배분

전체 페이지를 기획하고, 각 페이지에 대한 html, css, javascript로 이루어진 프론트 파일들과 서버사이드의 파이썬 파일로 작업을 나누었다. 각자 작업할 파일을 생성해보고, git commit해서 github에 push까지 해보면서 각자의 역할이 무엇인지에 대해 점검했다.(담당매니저님께서 참 열심히 챙겨주심)

OX 퀴즈 컨텐츠의 핵심인 문제와 답 아이디어를 가장 늦게 퇴근하는(ㅜㅜ) 팀원분께서 정리해주셨다. 10문제를 채우기 위해 나도 아이디어를 하나 냈다.

디자인하시는 팀원분들이 계셔서 메인페이지 이미지와 OX 이미지 등을 직접 제작해주셔서 너무 좋았다!

코딩(코드)

스파르타코딩클럽 웹개발종합반 강의의 기술스택을 따라갔다. 백엔드는 python flask, DB는 mongoDB를 이용했고, 간단한 javascript, jQuery와 부트스트랩을 활용했다.

OX 문제와 답을 입력, 수정하는 관리자 페이지

mongoDB에 ox quiz의 문제, 정답, 해설 등을 저장해두고 db에서 하나씩 불러오면 어떨까싶어서, 문제를 등록, 수정할 수 있는 간단한 관리자페이지를 만들었다.
(app.py에 미리 설정해둔 아이디, 비번으로 로그인하도록) 간단하게 input태그를 이용해 문제번호, 문제, 답을 입력하면 db에 insert되게 하고, 그 밑에 표로 현재 db에 있는 문제 정보들을 다 보여주게 했다.

관리자페이지 - templates/admin.html

templates/admin.html

{% with messages = get_flashed_messages() %}
{% if messages %}
<script>
    alert("{{messages[-1]}}")
</script>
{% endif %}
{% endwith %}


<!DOCTYPE html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
        integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
        integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
        crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
        integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
        crossorigin="anonymous"></script>

    <title>OX퀴즈 관리자</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Poor+Story&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="../static/css/member.css">

    <script>
        $(document).ready(function () {
            showQuizList();
        });

        /**
         * @brief 새로운 퀴즈 또는 퀴즈의 변경사항을 등록하기 위해 DB로 보냅니다.(ajax)
         * @param {}
         * @returns {}
         * @author 김진회
         */
        function saveQuiz() {
            if (confirm("정말 등록하시겠습니까? 동일 퀴즈번호일 경우 기존 데이터가 삭제(덮어쓰기)됩니다.")) {
                let quizno = $('#quizno').val()
                let category = $('#category').val()
                let content = $('#content').val()
                let answer = $('#answer').val()
                let description = $('#description').val()
                $.ajax({
                    dataType: "json",
                    type: "POST",
                    url: "/admin_quiz",
                    data: {
                        "quizno": quizno,
                        "category": category,
                        "content": content,
                        "answer": answer,
                        "description": description
                    },
                    success: function (response) {
                        alert(response['msg']);
                        window.location.reload()
                    }
                })
            }
        }

        /**
         * @brief 현재 DB등록된 퀴즈목록을 가져와 보여줍니다.(ajax)
         * @param {}
         * @returns {}
         * @author 김진회
         */
        function showQuizList() {
            $.ajax({
                type: "GET",
                url: "/admin_quiz",
                data: {},
                success: function (response) {
                    // console.log(response)
                    const quizdata = response['quiz']
                    for (let i = 0; i < quizdata.length; i++) {

                        let quizno = quizdata[i]['no']
                        let category = quizdata[i]['category']
                        let content = quizdata[i]['content']
                        let answer = quizdata[i]['answer']
                        let description = quizdata[i]['description']

                        const temp_html = `
                                <tr>
                                    <td>${quizno}</td>
                                    <td>${category}</td>
                                    <td>${content}</td>
                                    <td>${answer}</td>
                                    <td>${description}</td>
                                </tr>
                            `
                        $("#quizTable").append(temp_html)
                    }
                }
            })
        }
    </script>
</head>

<body class="login_body">
    <div class="admin_div">
        <button type="button" class="btn btn-light" onclick="location.href='/' "
            style="height: 33px; font-size: 13px">원래 홈페이지 메인으로 가기
        </button>
        {%if userid%}
        <span style="color: black; font-size:0.9rem">관리자 {{userid}}님 <br>환영합니다</span> <br>
        <button type="button" class="btn btn-light" onclick="location.href='/logout' "
            style="height: 33px; font-size: 13px">로그아웃
        </button>



        <div class="input-group mb-3">
            <div class="input-group-prepend">
                <label class="input-group-text">퀴즈번호</label>
            </div>
            <select class="custom-select" id="quizno">
                <option selected>선택</option>
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="3">3</option>
                <option value="4">4</option>
                <option value="5">5</option>
                <option value="6">6</option>
                <option value="7">7</option>
                <option value="8">8</option>
                <option value="9">9</option>
                <option value="10">10</option>
            </select>
        </div>
        <div class="input-group mb-3">
            <div class="input-group-prepend">
                <span class="input-group-text" id="basic-addon1">카테고리</span>
            </div>
            <input type="text" class="form-control" placeholder="카테고리" aria-label="name" aria-describedby="basic-addon1"
                id="category">
        </div>

        <div class="input-group mb-3">
            <div class="input-group-prepend">
                <span class="input-group-text" id="basic-addon1">퀴즈내용</span>
            </div>
            <input type="text" class="form-control" placeholder="퀴즈내용을 입력해주세요" aria-label="name"
                aria-describedby="basic-addon1" id="content">
        </div>
        <div class="input-group mb-3">
            <div class="input-group-prepend">
                <label class="input-group-text">정답</label>
            </div>
            <select class="custom-select" id="answer">
                <option selected>선택</option>
                <option value="O">O</option>
                <option value="X">X</option>
            </select>
        </div>
        <div class="input-group mb-3">
            <div class="input-group-prepend">
                <span class="input-group-text" id="basic-addon1">해설내용</span>
            </div>
            <input type="text" class="form-control" placeholder="정답을 설명할 해설 내용을 입력해주세요" aria-label="description"
                aria-describedby="basic-addon1" id="description">
        </div>
        <div class="wrap_btn">
            <button onclick="saveQuiz()" type="submit" class="btn btn-primary">입력(수정)하기</button>
        </div>
        <table class="type07">
            <thead>
                <tr>
                    <th>번호</th>
                    <th>카테고리</th>
                    <th>퀴즈</th>
                    <th>정답</th>
                    <th>설명</th>
                </tr>
            </thead>
            <tbody id="quizTable">

            </tbody>
        </table>
    </div>

    {%else%}
    <form method="post" action="/admin">
        <ul>

            <li><input type="text" placeholder="아이디" name="userid"></li>

            <li><input type="password" placeholder="비밀번호" name="userPW"></li>

            <button type="submit" class="btn_login">로그인</button>
        </ul>
    </form>
    {%endif%}


    </div>
</body>

</html>

관리자페이지 - app.py

app.py

from flask import Flask, render_template, jsonify, request, flash, session, redirect
from pymongo import MongoClient

app = Flask(__name__)
# Set mongo db
conn = MongoClient()
db = conn.good42


@app.route('/admin', methods=['GET', 'POST'])
def member_login():
    ids = ['진회','형준','은정','나현']
    password = 'xxx'
    if request.method == 'GET':
        return render_template('admin.html')
    elif request.method == 'POST':
        userid = request.form.get("userid", type=str)
        pw = request.form.get("userPW", type=str)

        if userid == "":
            flash("아이디를 입력하세요")
            return render_template('admin.html')
        elif pw == "":
            flash("비밀번호를 입력하세요")
            return render_template('admin.html')
        else:
            if userid not in ids:
                flash("아이디가 존재하지 않습니다.")
                return render_template('admin.html')
            elif pw == password:
                session["logged_in"] = userid
                return render_template('admin.html', userid = userid)
            else:
                flash("비밀번호가 틀렸습니다.")
                return render_template('admin.html')

## 퀴즈를 DB에 넣을 때 받는 api
@app.route('/admin_quiz', methods=['GET', 'POST'])
def admin_quiz():
    if request.method == 'GET':
        quiz = list(db.quiz.find({}, {'_id': False}))
        return jsonify({'msg': '성공', 'quiz': quiz})
    elif request.method == 'POST':
        no = request.form['quizno']
        category = request.form['category']
        content = request.form['content']
        answer = request.form['answer']
        description = request.form['description']
        # 이미 DB에 있는 퀴즈번호로 요청이 들어오면 기존의 해당 번호 퀴즈를 삭제하고 재등록
        check_cnt = db.quiz.find({"no": no}).count()
        if check_cnt > 0:
            db.quiz.delete_one({"no": no})
        doc = {
            "no": no,
            "category": category,
            "content": content,
            "answer": answer,
            "description": description,
        }
        db.quiz.insert_one(doc)
        return jsonify({'msg': '퀴즈 등록(수정) 완료!'})
    else:
        return None

## 로그아웃
@app.route("/logout", methods=["GET"])
def logout():
    session.pop('logged_in',None)
    return redirect('/admin')

퀴즈페이지 templates/quiz.html

퀴즈를 순서대로 불러오기

관리자페이지를 통해 mongoDB에 넣은 퀴즈 정보를 서버측과의 ajax 통신을 통해 퀴즈번호 순서대로 불러와서 한번에 하나씩 보여줄 수 있도록 jQuery, javascript 작업을 시작했다.


/**
* @brief 현재 순서에 맞게 퀴즈를 세팅합니다.(ajax)
* @param {int} no 이번에 세팅해야할 퀴즈 번호
* @returns {}
* @author 김진회
* @date 21.08.23
*/
function setQuiz(no = 1) {
  $.ajax({
    dataType: "json",
    type: "POST",
    url: "/quiz",
    data: {
      "seq": no,
    },
    success: function (response) {
      console.log(response.quiz);
      quiz = response.quiz['content']
      correctAnswer = response.quiz['answer']
      description = response.quiz['description']
      quizno = response.quiz['no']
      temp_quiz = `${quiz}`
      $('#quiz').html(temp_quiz);
    }
  })
  seq++;
}

한 문제를 풀 때마다 숫자가 1씩 증가해서, 그 다음 문제를 불러올 수 있도록 해야하니까, seq라는 변수를 선언해두고 한번 문제 불러올 때마다 1씩 증가하도록 했다.

답을 선택했을 때의 이벤트


        /**
         * @brief 답을 골랐을 때 맞았는지 틀렸는지와 설명을 띄우고, 점수 합산을 위해 이 정보를 저장합니다.
         * @param {string} selectedAnswer O를 골랐는지 X를 골랐는지에 대한 값
         * @returns {}
         * @author 김진회
         * @date 21.08.24
         */
        function selectAnswer(selectedAnswer){
            if(seq == 10){
                $('#btn-next').html("결과보러가기!");
            }
            if(selectedAnswer === correctAnswer){
                countCorrect++;
                // 모달창에 정답입니다! 문구와 description을 넣어 띄우기
                temp_check = `정답입니다! <br/>`;
                $('#description').html(temp_check);
                modalOn()
            } else {
                // 모달창에 아쉽네요! 문구와 함께 descrirption을 넣어 띄우기
                temp_check = `아쉽지만 오답이에요! <br/>`;
                $('#description').html(temp_check);
                modalOn()
            }

        }

다음 문제로 가기

        /**
         * @brief 다음 문제 버튼 작동 => 다음 문제 세팅 또는 로컬스토리지에 맞춘 개수 저장하고 결과페이지로 보내기
         * @param {}
         * @returns {}
         * @author 김진회
         * @date 21.08.24
         */
        function next(){
            if (seq > 10) {
                // 10번 문제까지 풀었으면 seq가 11이 되었을 것, 그러면 몇 문제 맞췄는지를 저장하고 result페이지로 보내기
                localStorage.setItem('countCorrect',countCorrect);
                location.href = '/result';
            } else {
                setQuiz(seq);
                const modal = document.getElementById("modal")
                modal.style.display = "none"

            }
        }

모달창 띄우기


        /**
         * @brief O 또는 X 클릭시 모달창 띄우는 함수
         * @param {}
         * @returns {}
         * @author 김진회
         * @date 21.08.24
         */
        function modalOn(){
            const modal = document.getElementById("modal")
            // const btnModal = document.getElementById("btn-modal")
            // btnModal.addEventListener("click", e => {
                modal.style.display = "flex"
            // })
            const temp_des = `${description}`
            $('#description').append(temp_des);
        }

퀴즈 맞춘 개수는 로컬스토리지에 저장해서 result페이지에서 불러와서 쓰기로 함

CSS 문제


퀴즈페이지의 모달창이 뜰 때 모바일에서 상하스크롤이 생기면 아래쪽까지 모달창의 배경레이어가 덮이지 않아서 답을 수정할 수 있는 문제가 있었다.
스크롤이 생기더라도 다 덮이게 하는 방법을 생각하다, 결국에는 퀴즈의 글씨크기를 확 줄이고 너무 크게 나와 좌우가 잘리던 상단이미지의 크기도 PC일 때보다 작게 나오도록 만들어서 모바일에서 상하스크롤이 생기지 않도록 만들었다.

모바일일 때는 카테고리를 제목 한 칸 아래에서 보여주고 싶어서 아래와 같이 코딩했다.
javascript로 접속한 디바이스의 종류를 인식해서 PC가 아니면(모바일이면) br태그를 추가하게.
이 방법의 단점은, 개발환경에서 테스트할 때 개발자도구로 width값을 줄이거나 모바일버전으로 보이게 해도 테스트가 안돼서 테스트하려면 서버에 진짜 올려서 진짜 내 폰으로 접속해보는 방법밖에(정말 그 방법뿐인가!?) 없다는 점이다.

let filter = "win16|win32|win64|macintel|mac|"; // PC일 경우 가능한 값
//모바일에서 접속했을 경우 <br/>을 추가해 카테고리 를 아래로 내리기
if (navigator.platform) {
    if (filter.indexOf(navigator.platform.toLowerCase()) < 0) {
        /* 모바일에서 접속하셨습니다 */
        $('#cate-br').html("<br />");
        // alert("모바일");
    } else {
        $('#cate-br').html("&nbsp;");
    }
}

quiz페이지의 html파일에 있는 javascript코드가 너무 길어져서, static/js 폴더에 별도 파일로 분리했다.

문제별 정답률 추가

OX퀴즈의 문제별 정답률을 실시간(!?)으로 계산해서 보여주는 코드

마지막날, 각 문제마다 문제의 정답률이 나오게 하면 어떨까? 하는 생각을 했다. '카테고리'(답이 스포일러되는 문제가 있었다) 대신 각 문제별 사람들이 풀었던 정답률이 나오게 하자는 의견에 좋다고 해주셔서 그렇게 변경하였다. 아래는 구현 과정과 코드.


/**
 * @brief 정답을 골랐을 때 맞았는지 틀렸는지와 설명을 띄우고, 점수 합산을 위해 이 정보를 저장합니다.
 * @param {string} selectedAnswer O를 골랐는지 X를 골랐는지에 대한 값
 * @returns {}
 * @author 김진회
 * @date 21.08.24
 */
function selectAnswer(selectedAnswer) {
    //답을 골랐을 때 마지막 문제라면 버튼이름을 '다음 문제로'에서 결과보러가기로 변경하기
    let thisQuizNo = seq - 1;
    if (thisQuizNo > 9) {
        $('#btn-next').html("결과보러가기!");
    }

    //고른 답이 정답일 때(if)와 오답일 때(else)
    if (selectedAnswer === correctAnswer) {        
        //이 문제의 정답을 맞췄다고 서버에 전송
        $.ajax({
            dataType: "json",
            type: "POST",
            url: "/saveone",
            data: {
                "quizno": thisQuizNo,
                "device": device,
                "correctOrNot": 1
            },
            success: function (response) {
            }
        })
        countCorrect++;
        // 모달창에 정답입니다! 문구와 정답 설명을 넣어 모달창 띄우기
        temp_check = `정답입니다! <br/><br>`;
        $('#description').html(temp_check);
        modalOn()
    } else {
        //이 문제의 정답을 틀렸다고 서버에 전송
        $.ajax({
            dataType: "json",
            type: "POST",
            url: "/saveone",
            data: {
                "quizno": thisQuizNo,
                "device": device,
                "correctOrNot": 0
            },
            success: function (response) {
            }
        })
        // 모달창에 아쉽네요! 문구와 함께 답 설명을 넣어 모달창 띄우기
        temp_check = `아쉽지만 오답이에요! <br/><br>`;
        $('#description').html(temp_check);
        modalOn()
    }
}

위처럼 문제의 정답을 골랐을 때 답이 맞았는지 틀렸는지와 해설을 보여주기 위해 만들었던 selectAnswer 함수 내에 정답을 골랐는지, 오답을 골랐는지에 따라 서로 다른 값을 ajax로 서버측에 전송하도록 코드를 추가하였다.

from flask import Flask, render_template, jsonify, request, flash, session, redirect
from pymongo import MongoClient
import datetime

app = Flask(__name__)
conn = MongoClient()
db = conn.good42

# 문제별 정답률: 퀴즈 하나를 풀 때마다 그 시도에서 해당 문제의 정답을 맞혔는지 여부를 DB에 저장하기
# author 김진회
# date 21.08.29
@app.route('/saveone', methods=['POST'])
def saveone():
    quizno = request.form['quizno']
    device = request.form['device']
    correct = request.form['correctOrNot']
    now = datetime.datetime.now()
    doc = {
        "quizno": quizno,
        "device": device,
        "correct": correct,
        "time": now,
    }
    db.quizanswers.insert_one(doc)
    return jsonify({'msg': '등록 완료!'})

서버측에서는 위와 같이 ajax로 전송받은 값을 받아 현재시각 정보와 함께 mongodb에 저장하도록 했다.

그리고 퀴즈를 세팅할 때, 이렇게 저장되었던 정보들을 불러와서 서버측에서 정답률을 계산하도록 했다. 이렇게 계산된 해당 문제의 정답률을 문제, 정답, 해설 정보와 함께 응답으로 보내주도록 했다.

## 퀴즈페이지 HTML 화면 보여주기
## GET으로 들어오면 단순히 화면을 보여주고, POST로 들어오면 DB에서 다음 문제 정보를 전달하는 api
@app.route('/quiz', methods=['GET', 'POST'])
def quiz():
    if request.method == 'GET':
        return render_template('quiz.html')
    elif request.method == 'POST':
        seq = request.form['seq']
        quiz = db.quiz.find_one({'no': seq}, {'_id': False})
        #현재 문제의 정답률 계산하기
        answers = db.quizanswers.find({'quizno':seq}).count()
        corrects = db.quizanswers.find({'quizno':seq, 'correct':'1'}).count()
        rate = round(corrects / answers * 100, 2)
        return jsonify({'msg': '성공', 'quiz': quiz, 'rate': rate})

/**
 * @brief 현재 순서에 맞게 퀴즈를 세팅합니다.(ajax)
 * @param {int} no 이번에 세팅해야할 퀴즈 번호
 * @returns {}
 * @author 김진회
 * @date 21.08.23
 */
function setQuiz(no = 1) {
    $.ajax({
        dataType: "json",
        type: "POST",
        url: "/quiz",
        data: {
            "seq": no,
        },
        success: function (response) {
            quiz = response.quiz['content']
            correctAnswer = response.quiz['answer']
            description = response.quiz['description']
            quizno = response.quiz['no']
            correctRate = response.rate
            temp_quiz = `${quiz}`
            temp_rate = `정답률: ${correctRate}%`
            $('#quiz').html(temp_quiz);
            $('#correct-rate').html(temp_rate);
            numberOfQuiz(quizno);
        }
    })
    //현재 풀고 있는 문제 번호를 +1해서 저장
    seq++;
}

서버측에 다음 문제 정보를 ajax로 요청해서 받아오던 setQuiz 함수에 정답률 correctRate도 받아와서 적절한 곳에 html을 만들어서 넣어주도록 코딩했다.

회고

그때 하루하루 기록하느라 조각나 있던 글들을 하나로 합쳐보았다. 어느새 이 프로젝트를 한지도 반년이 지났다. 이전 프로젝트에 비하면 다들 생업이 너무 바쁘셔서 연락도 잘 안 되고 코딩할 시간도 많이 내실 수 없는 상황이라 어려움이 많았던 기억이 난다. 그래도 그렇게 야근하시고 늦게 집에 오시는 와중에도 어떻게든 참여하시려고 하시는 열의가 존경스러웠다.

때로는 뭔가를 도우려고 해도 아무것도 묻지 않으시니 도울 수가 없어 답답하기도 했다. 무얼 하고 계신지 알기도 어려웠다. 옆에서 모니터를 같이 보고 있는 것도 아닌데 아무 말씀도 안하고 계시니. 그 경험을 통해 반대로 나 역시 막히는 게 있으면 더 많이 얘기하고, 때로는 더 가볍게 자주 피드백을 해나가는 게 중요하겠다는 걸 알 수 있었다.

결과물

https://github.com/becho2/42good42





마지막 결과화면에서 레벨업을 누를 시에는 굉장한 분리수거 팁을 볼 수 있는 https://blisgo.com/ 사이트로 연결해드렸다.

profile
백엔드 개발자. 공동의 목표를 함께 이해한 상태에서 솔직하게 소통하며 일하는 게 가장 즐겁고 효율적이라고 믿는 사람.

0개의 댓글