완성 배포 페이지
새로운 인턴십
9월 말부터 감사하게도 옥소폴리틱스 사의 인턴십 프로그램에 다시 참여하게 되었다. 4주과정이 끝나고 마지막 발표만을 앞둔 상태라 그동안의 프로젝트를 회고해보려 한다.
팀 컨벤션 정하기
우선 프로젝트에 앞서 팀 컨벤션과 소소한 규칙들을 정하였다. 별거 아닌듯 하지만 컨벤션과 규칙은 미리 구체화 시켜놓는 것이 나중에 훨씬 편하다는 것을 이전 프로젝트에서 경험했기 때문에 주제 선정 이전에 팀 컨벤션을 미리 정하였다.
미팅 룰
시간 엄수
- 사정 있을 시 (불참, 지각 등) 1시간 전에는 알리기
디스코드 미팅
- 매일 오전 10시 미팅
- 디스코드 항상 온라인 상태 유지
- 떠오르는 생각은 바로 디스코드에 정리
의견 교환
- 부정적 피드백 기분 나빠하지 말기…🥲
- 되도록 대안 제시 또는 근거 제시
- 긍정 의견 부정 의견 모두 제시
- 건강한 토의를 지향합시다
코드 컨벤션
- 기술 스택
- 언어
- reactJS
- JavaScript
- reactJS
- css
- styled component
- eslint
- standard
- airbnb
- prettier
- semi: true - statement 마지막에 세미콜론을 찍음
- printWidth: 120 - 선호되는 한 줄의 길이
- endOfLine: 'auto' - 파일의 마지막에는 EOL을 보장
- singleQuote: false- 홑따옴표가 아닌 쌍따옴표를 사용
- useTabs: false - 탭을 사용하지 않고 스페이스를 사용
- tabWidth: 2 - 탭을 할 경우 2 스페이스
- trailingComma: 'all' - 여러줄로 나뉘었을 때는 쉼표를 사용
- arrowParens: 'always' - 화살표 함수에서 괄호 사용 의무화
- 언어
- 전역상태관리
RTKRedux- Recoil
context
- 커밋 단위
- 커밋 하나의 의도와 의미만 가져야함
- 파일을 하나만 수정하더라도 두 개 이상의 의도가 있다면 하지 말아야 함. 버그 수정과 기능 추가를 동시에 하지 않아야 함
- 커밋은 기능, 타입 단위로 짧게 끊어서 커밋합니다. 한번에 작업한 내용이 많더라도 커밋은 여러번에 걸쳐 구분하여 커밋합니다. 그리고 커밋 타입에 따라 구분하여 커밋합니다. 예를 들어 리소스 추가는 리소스끼리 모아 커밋하고 버그 수정은 버그 수정만 따로 커밋하고 새로운 기능 추가는 추가된 기능만 따로 커밋합니다.
- 변수 네이밍 규칙컴포넌트: PascalCase
- 컴포넌트 내부의 변수: camelCase
- 컴포넌트 함수 형태: 화살표 함수 사용 const ${name} = () ⇒ {}
- export 컴포넌트의 경우 함수 표현식
- 주석 규칙
// 한 줄 주석일 때 /** * 여러줄 * 주석일 때 */
- 기능 추가 시 주석달기
-
type : subject body
- 타입
- Fix 수정
- Add 기능 추가
- Mod 코드 수정
- Rm 기능 삭제
- Refactor : 코드 리팩토링 (변수명 수정 등)
- Subject : 제목
- 소스코드를 보지 않고도 변경 사항이 무엇을 하는지 알 수 있도록 하기
- 타입
- 기본적인 틀
- 브랜치 이름 작성 규칙
- 새로운 브랜치를 만들 때, 브랜치 이름만 보고 어떤 목적으로 브랜치를 만들었는지 바로 알 수 있어야 함
- 커밋 메세지와 마찬가지로 몇 가지 카테고리를 만들어서 분류하는 것이 좋음
- new 새 기능 추가가 목적
- test 새 라이브러리, 배포, 환경,실험
- bug 버그 수정이 목적
- 이슈 트레킹
- Issue
- 칸반보드 기능 활용
주제 선정
새로운 프로젝트는 프론트엔드 4명의 팀으로 시작하였다. 아쉽게도 백앤드 인원은 참여하지 않아 프론트엔드 4명으로 구성된 팀이 되었다. 프론트엔드로만 구성된 팀이었기에 오픈 API를 활용할 수 있는 프로젝트에 집중하였고 처음 주제는 '각 지방 의회 예산결의안을 분석하여 만든 공무원 추천 맛집 지도'였다.
API에 의존하는 프로젝트는 좋지 않다는 깨달음
해당 아이디어는 몇년전 중앙일보에서 진행한 '의슐랭 가이드' 프로젝트에서 착안한 아이디어였다. 각 지방의회에서는 일정 주기마다 사용한 예산의 사용처를 정리하여 예산결의안을 게시한다. '의슐랭 가이드' 프로젝트는 이 결의안들을 모두 분석하여 해당 지역의 공무원들이 자주 방문하는 가게를 보여주는 프로젝트였다. 우리는 이것을 API로 받아 더 동적으로 재밌게 보여줄 수 있는 프로젝트를 기획했다.
하지만 문제는 예상치 못한 곳에 있었다. 공공데이터 포털에서 REST API 형태로 제공되는줄로 알고 있었던 예산 결의안이 알고보니 엑셀이나 심지어는 PDF의 형태로만 제공해주는 곳도 있었다.
첫번째로 계획했던 프로젝트는 지나치게 API에 의존적인 프로젝트였기에 최초에 계획했던 API를 사용할 수 없게되자 프로젝트 전체가 무산되고 말았다.
만약 API를 받을 수 있었더라도 프로젝트 중간에 API가 닫혔더라면 프로젝트 중간에 전체가 엎어질뻔한 위험이 있었다. 한 가지 API에만 의존하는 프로젝트는 피해야한다는 큰 교훈을 얻을 수 있었다.
새로운 프로젝트 'Laws Cloud'
'의슐랭' 프로젝트가 무산되고 우리는 새로운 프로젝트를 구상해야했다. 4주간의 인턴십 과정 중 1주일을 다른 프로젝트에 허비했기 때문에 짧은 시간 안에 구현할 수 있는 아이디어가 필요했다. 치열한 회의 끝에 나온 아이디어는 국회에서 제공하는 법률 발의안 API를 사용하는 'Laws Cloud'였다.
Laws Cloud?
입법부인 국회에서는 수시로 새로운 법률을 만들기 위한 초석인 '법률안'을 발의한다. 국회에서는 오래전부터 해당 발의 법률안을 수신할 수 있는 API를 제공해왔다. 대한민국 국회에서 정식으로 제공하는 API이기 때문에 안정적일 것이라 판단하였다.
원래 발의안을 뜻하는 영단어는 'Bill'이지만 한국에서는 생소한 단어이기 때문에 뜻은 조금 다르지만 더 직관적인 'Law'를 사용하기로 하였다.
최초의 아이디어는 매년 발의된 법률안의 제목을 명사 단위로 나누어 WordCloud를 만드는 것이었다. 이후에 추가된 기능은 다음과 같다. 실제 구현된 기능은 Bold 처리하였다.
최소 기능( minimum )
- 최근 3대 의회에서 발의안 법률발의안의 제목에 포함된 단어들로 워드 클라우드를 보여준다.
- 클라우드의 단어 클릭시 관련된 발의안들을 모아서 보여줌
발전 기능( advanced )
- 법률발의안의 제목에 포함된 단어들로 워드 클라우드를 보여준다.
- 클라우드의 단어 클릭시 관련된 발의안들을 모아서 보여줌
- 발의안에 대한 반응 좋아요 댓글 ⇒ 익명 로그인 기능 필요
- 새로고침갱신
- 발의안에 대한 반응 좋아요 댓글 ⇒ 익명 로그인 기능 필요
- 역대 국회의원(20대, 19대 …) 법안 발의 비교
- 현재랑 그전 기수 워드 클라우드 비교
- 연도별 법안 워드클라우드 비교
- 관심있는 키워드로 검색
Nightmare
- 가장 반응이 많았던 발의안을 보여주기
- 정당별 발의안 페이지 - 검색 - 관련 발의안
- 정당별 테이블 표 - 의원 검색
- 정당별 발의안 개수 원형 그래프(?) 등 간단한 시각화
- 정당별 테이블 표 - 의원 검색
- 지역구 지도 만들어서 지역구 의원별 발의한 법안 갯수 시각화
Prototype
구현할 기능들을 토대로 Figma를 이용해 프로토타입을 제작하였다.
최소단위 Components로 나누기
Figma로 프로토타입을 제작한 후에는 최소 단위로 Components를 쪼개었다. 나는 이중에서 워드클라우드(WordCloud) 페이지와 관련된 컴포넌트 제작을 담당하였다. 다들 프로젝트 경험이 처음이라 최대한 작게 컴포넌트를 나눴다고 생각했지만 결국 제작하다보니 컴포넌트는 더 늘어났다.
컴포넌트 모음
- 좋아요(하트) 토글 버튼 <LikeButton>
- 댓글
- 댓글 아이디 입력창 <ReplyId>
- 댓글 비밀번호 입력창 <ReplyPassword>
- 댓글 작성창 <InputReply>
- 댓글창 전체보기 <TotalReplyVeiw>
- 작성한 댓글 <Replys>
- 댓글 수정 버튼 <EditReplyButton>
- 댓글 삭제 버튼 <DeleteCommentButton>
- 댓글 정보가 담긴 db 컬렉션
- 조회수 표시 <TotalViews>
- 댓글수 표시 <TotalComments>
- 발의안 주요 내용 <BillsContents>
- 상세검색 (돋보기) 버튼 <SearchButton>
- 발의안 목록 모음 <BillsList>
- 발의안 목록 디자인 <Bills>
- 페이지네이션 구현 모달 <PageNationSession>
- 목록표 엑셀버튼 <ExcelFilterButton>
- <Title />
- props
- 기능
- Home(워드클라우드 페이지)으로 이동
- 연도구분 <YearSelectSection>
- 의회 기수 선택 <AgeSelect>
- 검색창 <SearchBar>
- 워드클라우드 렌더링 컴포넌트 <WordCloud>
- props
- age - 발의 국회 대수
- year - 발의 연도
- elemId - 워드클라우드를 그릴 상위요소(div)의 id 값
- props
- 최근 달린 댓글 <RecentComments>
- props
- bills
- 구성
- <RecentCommentsView>
- <CommentArea>
- Comment - 댓글
- Info - 발의안 작성자 정보 (ID, 작성 시간)
- <BillName>
- 아이콘
- 발의안 제목
- <CommentArea>
- <RecentCommentsView>
- props
컴포넌트를 최대한 작게 나누고 각자 만들 부분을 정하자 일은 일사천리로 진행되었다. 워드클라우드를 제작할 때에 데이터를 명사 단위로 쪼개는 Tokenize 작업이 필요했다. 이를 위해서 konlpy 라이브러리를 사용하였다.
당연하게도 형태소 분해 작업은 어플리케이션 위에서 진행되어야 했으나, nodeJS나 Django 등의 경험이 부족하여 서버 안에서 데이터를 처리하는 것에 어려움이 있었다. 새로 공부하기에는 인턴십 기간이 부족하여 차후 과제로 미루어 두었다.
서버 단에서 데이터를 처리하는 대신 이번 프로젝트에서는 konlpy 라이브러리와 파이썬을 이용하여 발의안 제목들을 모두 명사단위로 쪼갠 다음 빈도수 순으로 나열하여 로컬 JSON 파일로 저장하였다. 발의안의 특성상 계속 등장하는 다음 단어들은 제외하였다.
"기본법", "개정", "관", "한", "관리", "관리법", "관련", "등", "등에", "안", "법", "법률", "법안", "제한", "조치", "지원", "진흥", "특례", "특례법", "특별", "특별법"
재사용성 높이기
이번 프로젝트는 특별히 어려운 기능을 구현하는 것이 없었기 때문에 최대한 팀프로젝트에 필요한 요소들을 많이 접목해보기로 하였다. 우선 첫번째로 중요하게 생각한 것은 '재사용성'을 높이는 것이었다.
위에서 보듯 '워드클라우드 페이지'와 '워드클라우드 비교 페이지'는 동일한 WordCloud 컴포넌트를 사용할 수 있다. 비교페이지는 단지 워드클라우드 컴포넌트를 크기를 줄여서 여러 개 보여주면 되는 페이지이다. 이에 나는 워드클라우드에 필요한 것들을 묶은 <WordCloudSection> 컴포넌트를 만들었고 여기에 size라는 속성값을 더해주었다.
//WordCloudSection.jsx
...
return (
<div className="wordcloud-section">
<WordCloud age={age} year={year} setSearch={setSearch} size={size} elemId={elemId} />
<AgeSelect
setAgeData={setAgeData}
age={age}
setAge={setAge}
setYear={setYear}
agesAndYears={agesAndYears}
size={size}
/>
<YearSelect
year={year}
setYear={setYear}
firstYear={agesAndYears[age].firstYear}
yearsNumber={agesAndYears[age].yearsNum}
size={size}
/>
</div>
);
box-shadow 등의 속성은 비교페이지에서만 필요했기 때문에 나중에 CSS를 입힐 수 있도록 div 태그에 클래스명을 주었다. 각 컴포넌트(YearSelect, WordCloud, AgeSelect)에서는 size 값에 따라 margin, width값 등을 조절하도록 styled-components의 props 기능을 활용하였다.
// YearSelect.jsx
//...
return (
<StyledWrapper onClick={handleClick} size={size}>
{years.map((elemYear) => (
<button
type="button"
value={elemYear}
id={"year-button"}
className={year === elemYear ? "year-button selectedYear" : "year-button"}
key={`${elemYear}-button`}>
{elemYear}
</button>
))}
</StyledWrapper>
);
// StyledYearSelect.js
export const StyledWrapper = styled.div`
display: flex;
width: ${({size}) => (size / 100) * 300}px;
margin: 20px auto;
align-items: center;
justify-content: center;
button {
align-items: center;
background: none;
border: none;
font-weight: 500;
font-size: ${({size}) => (size / 100) * 24}px;
font-style: normal;
color: #c6c6c6;
transition: all 0.2s ease-in-out;
}
.selectedYear {
color: black;
font-weight: 600;
}
`;
이렇게 재사용 가능하게 옵션값을 만들어서, 컴포넌트에 size 값을 지정해주는 것만으로 편리하게 wordcloud를 사용할 수 있게 되었다.
// ComparedWordCloudsSection.jsx
function ComparedWordCloudsSection({setSearch, compareNumber = 3, cloudSize}) {
const compareNum = new Array(compareNumber).fill(1).map((elem, i) => elem + i);
return (
<section className="compared-wordclouds">
{compareNum.map((num) => (
<WordCloudSection setSearch={setSearch} size={cloudSize} elemId={`wordcloud${num}`} key={`wordcloud-${num}`} />
))}
</section>
);
}
// WordCloudPage.jsx
//...
return (
<StyledWrapper>
<WordCloudSection setSearch={setSearch} size={100} />
<button type="button" className="compare-button" onClick={() => navigate("/compare")}>
비교해보기
</button>
</StyledWrapper>
);