Hits bn

2020년 2분기 회고

정신을 차리고 보니 벌써 2020년의 7월이 되었다.

그래서 다시 정신을 가다듬고, 2020년의 2분기는 얼마나 알차게 보냈는지 정리해보고자 한다.

개발 역량 강화

1. 일일커밋

일일커밋
* 글을 작성하는 시점(8월) 기준의 Github Contributions 이다.

올해의 첫 번째 목표이자 제일 큰 목표는 바로 일일커밋이다. 이전에는 일일커밋이 부질없다고 생각했는데, 그 생각을 고쳐먹었다. 무언가를 거창하게 하기보단, 작더라도 꾸준히 하는 것이 중요하다.

이런 생각은 지방대 개발 비전공자가 배달의민족 리드 개발자가 되기까지open in new window에 언급된 하루키 법칙에서 비롯되었다.

나는 관성의 영향을 크게 받는 사람이다. 그래서 일일커밋이라는 관성에 탑승하기로 했다. 강제로 꾸준히 공부하다 보면 뭐라도 되어있겠지 하며..

너무 피곤해서 못할뻔한 적도 있었지만, 머릿속으로 계속 '그래도 오늘 커밋을 해야 하는데...' 하며 몸을 일으켜 공부 했다. 이렇게까지 해야 하는 건가 싶을 때도 있었지만 어쨌든 결과적으론 나쁘지 않았다.

2분기에는 일일커밋의 양분으로

등을 이용했다.

학과 자료는 옛날부터 생각했던 거라서 그냥 시간 있을 때 조금씩 정리했고, TIL도 팀원들과 유명한 개발자들이 하고 있었기 때문에 나도 하고 싶다는 생각으로 시작했다.

그리고 사이드 프로젝트의 경우 학교에 다닐 때부터 해야지 해야지 하다가 결국 못했는데, 어떻게든 끝맺음을 짓고 싶어서 시작했다. 결과적으로 지금은 방치중이다.3분기에 꼭 배포까지 해보자!

2분기 커밋 정산

  • 2분기에 총 1537개의 커밋을 했다.
  • 4월462개의 커밋을 했다.
    4월
  • 5월641개의 커밋을 했다.
    5월
  • 6월434개의 커밋을 했다.
    6월
  • 한 달 평균 약 512개의 커밋을 했다.
  • 하루 평균 약 17개의 커밋을 했다.

확실히 3개월 동안 매우 많은 기록을공부를 했다. 적어도 내가 여태까지 살아왔던 다른 시간들과 비교하면 그렇다. 이 밀도를 최대 3년 정도 유지하는 것이 나의 최대 목표다.

그리고 이 목표를 얼마나 착실하게 이루어가느냐에 따라서 앞으로의 인생이 달라지겠지?

성공까진 아니더라도 나 자신에게 떳떳하게 살기 위한 한 걸음이리라.


2. Today I Learned

나의 자극제

입사 했을 때 이런 분들과 같이 일할 수 있음에 감사했다.

일일 커밋의 좋은 양분 중 하나가 Today I Learn이었다. 공부한 것들을 기록하는 단순한 행위지만 그 효과는 상당히 좋다고 생각한다.

기록하고 싶은 것들이 참 많은데, 내가 글을 작성할 때 공을 너무 많이 들여서 한 가지 주제로 무언가를 쓸 때 시간을 너무 많이 소모한다. 그렇다고 대충 쓰기는 또 싫다. 그래서 자연스럽게 글쓰기 자체를 멀리하게 된다.. 핑계도 가지가지

일단 사이드 프로젝트를 완료한 다음에 다시 꾸준히 쓰든가 해야지.. 대체 언제하겠다는걸까?

2분기는 월간 리뷰만 작성했다.

1~3월 리뷰는 기억이 가물 가물 해서 대충 작성했다. 사실 그렇게 특이한 일도 없었다.


3. Side Project

Github와 관련된 주제로 사이드 프로젝트를 진행하고 있다. 자신의 Github에 올린 Markdown 파일을 읽어올 수 있고, Webhooks를 이용하여 자동으로 포스트가 업데이트되도록 만들었다. (물론 수동으로 갱신할 수도 있다.)

일단 위에 언급한 기능들은 일찍이 다 만들었는데, 문제는 디자인도 구리고 퀄리티가 낮았다. 그래서 이것저것 서브 기능을 넣어야 하는데 그것마저 쉽지가 않다. 혼자서 사이드 프로젝트를 하는 게 이럴 때 버겁구나 느끼는 중이다.

그래서 만족할 때 까지 리팩토링을 하거나, 기술 스택을 최대한 많이 공부하는 등의 목표를 가지고 진행 중이다. 벌써 리팩토링만 몇 번 한 것인지...

첫 번째 성과, Open API 습득

사이드 프로젝트를 하면서 얻은 첫 번째 성과는 Open API를 사용하는 방법을 완전히 터득한 것이다. 이전에는 API를 연동할 때 문서를 봐도 모르겠고, 다른 사람들이 작성한 글들을 봐도 감이 오질 않았는데 정말 어떤 API를 사용하더라도 큰 문제가 없을 정도로 이해한 상태다.

사실 사이드 프로젝트 덕분이라기보단 입사 직후에 진행했던 파일럿 프로젝트의 영향이 더 큰 것 같다.

두 번째 성과, NestJS 습득

두 번째 성과로 NestJS에 대한 사용법 습득이다.

재학중에 NestJSopen in new window가 뭔가 좋아보여서 공부했었는데, 너무 어려워서 포기했다. 그런데 이번에 무심코 다시 적용을 시도했고, 결과적으로 좋은 선택이 되었다.

NestJS 소개

  • NestJS는 Google에서 만든 Server Side Framework이다.
  • Java의 Spring과 매우 비슷한 방식으로 작동한다.
  • DI(Dependency Injection) 방식으로 구성한다.
  • DDD 형태의 프로젝트 구조를 권장한다.
  • Typescript를 사용한다.
  • express 기반이다.

NestJS의 철학

NestJS는 Angular의 영향을 받아 다음과 같은 철학 기반을 만들어졌습니다.

  • 고도의 테스트 지원
  • 효율적인 확장
  • 느슨한 결합
  • 유지 관리가 용이​​한 애플리케이션

이건 객체지향 공부의 영향이 컸다. 국내 자료를 아무리 찾아봐도 NestJS에 대한 내용은 거의 볼 수 없었다. 대부분 해외에서 사용하고 있는데, 영어에 너무 취약하다 보니.. 이해가 너무 어려웠다.

하지만 Spring을 많이 사용해봤고 Spring에 사용된 기본적은 디자인패턴이나 철학을 이해하고 있다면, NestJS를 이해하는 데 큰 무리가 없는 것 같다.

그래서 사이드 프로젝트의 제일 큰 수확은 사실 NestJS의 사용 숙지가 아닐까 싶다.

세 번째 성과, SSR

진짜 SSR 때문에 한 동안 고생을 너무 많이 했다. Vue에서 제공하는 가이드라인이 너무 빈약하고, API 문서도 잘못 되었거나 반영되지 않은 것들이 많았다.

각설하고 문제점과 약간의 해결과정을 나열해보자면,

첫 번째 문제: SSR과 CSR을 같이 하기 위한 가이드라인이 없다.

Vue의 공식문서open in new window, 각종 커뮤니티 사이트, 기술 블로그 등을 폼하여 눈씻고 찾아봐도 SSR과 CSR을 같이 사용하는 방법에 대한 가이드라인은 없었다. 있다면 누가 좀 알려주길..

어쨌든 SSR에 CSR을 연동하기 위해선 다음과 같은 과정이 필요하다.

  • CSR의 Template에 SSR의 Template을 합쳐야 한다.
  • CSR 코드를 번들링(빌드) 한다. 이 때 Template도 Bundling 코드에 포함된다.
  • CSR에서 Build된 Template를 SSR에서 사용한다.

이와 관련 내용도 추후에 상세하게 정리해서 올릴 예정이다.

두 번째 문제: window와 document를 사용하는 코드들

SSR은 CSR의 코드를 Server에서 실행하여 HTML 코드를 만들고 바로 렌더링한다. 이 때 발생하는 문제가 window와 document는 Server Side에서 사용할 수 없다는 것이다.

그래서 직접 window와 document를 만들어주거나 Render와 관련된 코드에는 window와 document를 사용하지 않는 것이다. 그런데 이게 말이 쉽지 직접 해보면 욕나온다.

어쨌든 어떤 방법이 제일 좋을까 고민하다가 찾아난 해결책이 JSDOM을 사용하는 것이다.

JSDOM

  • JSDOM은 말 그대로 가상의 window와 document를 만들어주는 것이다.
  • 가상의 존재여도 존재한다는 것 자체만으로도 그 가치가 있다.
import { Injectable } from '@nestjs/common'
import { join } from 'path'
import { BundleRenderer, createBundleRenderer } from 'vue-server-renderer'
import { DOMWindow, JSDOM } from 'jsdom'

const port = process.env.NODE_ENV === 'development' ? 3000 : 8080
const baseURL = `http://localhost:${port}`
const bundlePath = join(__dirname, '../../../resources/vue-ssr-server-bundle.json');
const htmlStr = `<!DOCTYPE html><html><head><title></title></head><body></body></html>`

@Injectable()
export class SSRService {

  public getRenderer (): BundleRenderer {
    try {
      return createBundleRenderer(bundlePath, {
        runInNewContext: false,
        template: (result, context) => `${result}${context.renderState()}${context.renderScripts()}`
      } as any)
    } catch (e) {
      console.log(e)
      throw 'Renderer Error'
    }
  }

  public getDom (contextURL: string): [ DOMWindow, Document ] {
    try {
      const url: string = `${baseURL}${contextURL}`
      const {window} = new JSDOM(htmlStr, {url})
      return [window, window.document]
    } catch (e) {
      console.log(e)
      throw 'JSDOM Error'
    }
  }
}

























 
 
 
 
 
 
 
 
 
 

세 번째 문제: 제대로된 Tutorial을 찾을 수 없다.

SSR의 가장 큰 문제점 중 하나가 바로 제대로된 튜토리얼이 없다는 것이다. github를 찾아봐도 구글링을 해봐도 이것만 보면 이해할 수 있다 싶은 튜토리얼은 존재하지 않았다.

그래서 내가 만들었다 --> Vue SSR Tutorialopen in new window

일단 설명은 없고 소스코드만 존재한다. 뭐.. 이해할 사람은 이해하겠지.

네 번째 성과, Mono Repo 적용

Client와 Server에 Typescript를 적용하면서 생긴 고민이 공통 타입을 잘 활용할 수 있는 방법이 없을까? 였다.

예를들어 Server에서 Github API를 이용하여 Repository 정보에 대한 타입을 GithubRepository로 정의했다. 그런데 이 타입은 Client에서도 필요하다.

그래서 처음엔 Client가 프로젝트의 코드상으로 Server에 접근할 수 있도록 만들어야 했다. 만들면서 계속 찜찜했다. Type이 Server에 종속되어있는게 맞을까? 라는 생각 때문이다.

그래서 Mono Repository에 대해 찾아봤고, 두 가지 방법이 존재했다.

권장하는 것은, 두 가지를 같이 사용하는 것이다.

필자 또한 두 가지 모두 사용하기로 결정했다.

먼저 폴더 구조를 다음과 같이 만들었다.

┌─ /
├─ package.json
├─ lerna.json
├─ front-end/
│  ├─ package.json
│  └─ src/
├─ back-end/
│  ├─ package.json
│  └─ src/
└─ domain/
   ├─ package.json
   └─ src/

그리고 각각의 package.json을 수정해야 한다.

/package.json

{
  "name": "DKU-Software-Engineering-Logging-Service",
  "private": true,
  "workspaces": [
    "front-end", // front-end 폴더
    "back-end", // back-end 폴더
    "domain" // front와 back이 공유하는 타입 혹은 로직
  ],
  "devDependencies": {
    "lerna": "^3.20.2" // learn 사용하기 
  },
  "scripts": {
    // 이 명령을 실행할 경우 front와 back의 dev 명령 실행
    "lerna:dev:stream": "lerna run lerna:dev --stream", // 직렬 실행(front->back)
    "lerna:dev:parallel": "lerna run lerna:dev --parallel" // 병렬 실행(front와 back 동시에)
  }
}

/lerna.json

{
  "packages": [ "back-end", "front-end" ], // 관리하는 repo 목록
  "npmClient": "yarn", // yarn 사용
  "version": "1.0.0" // 공통으로 관리하는 버전
}

그리고 front-end와 back-end의 package.json에 domain을 불러와야 한다.

/back-end/pacakge.json

{
  "name": "back-end",
  "version": "1.0.0",
  "description": "Dankook University Developer Logging Service",
  "author": "junil hwang",
  "license": "MIT",
  "scripts": {
    "lerna:dev": "cross-env NODE_ENV=development nest start --watch", // root의 npm script에서 실행
    /* 생략 */
  },
  "dependencies": { /* 생략 */},
  "devDependencies": {
    "domain": "^1.0.0", // domain package를 불러와야 사용할 수 있다.
    /* 생략 */
  },
  "jest": { /* 생략 */ }
}






 




 





/front-end/package.json

{
  "name": "front-end",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "lerna:dev": "vue-cli-service serve",
    /* 생략 */
  },
  "dependencies": { /* 생략 */ },
  "devDependencies": {
    "domain": "^1.0.0",
    /* 생략 */
  },
  "browserslist": [ /* 생략 */ ],
  "jest": { /* 생략 */ }
}





 




 





결과적으로 다음과 같이 사용 가능했다.

/front-end/src/services/GithubService.ts

import $http from 'axios'
import { GithubProfile, GithubRepository, GithubContent, GithubTrees, GithubBlob, ContentVO, GithubHook } from 'domain/src'
import { responseProcessor } from '@/helper'

const baseURI = '/api/github'

export default Object.freeze({

  async getRepo ({ login }: GithubProfile): Promise<GithubRepository[]> {
    return await responseProcessor<GithubRepository[]>($http.get(`${baseURI}/repo/${login}`))
  },

  async getContent (params: ContentVO): Promise<GithubContent> {
    return await responseProcessor<GithubContent>($http.get(`${baseURI}/content`, { params }))
  },

  async getTrees (params: ContentVO): Promise<GithubTrees> {
    return await responseProcessor<GithubTrees>($http.get(`${baseURI}/trees`, { params }))
  },

  async getBlob (params: ContentVO): Promise<GithubBlob> {
    return await responseProcessor<GithubBlob>($http.get(`${baseURI}/blob`, { params }))
  },

  async getHook (): Promise<GithubHook[]> {
    return await responseProcessor<GithubHook[]>($http.get(`${baseURI}/hook`))
  },

  async addHook (repo: string): Promise<GithubHook[]> {
    return await responseProcessor<GithubHook[]>($http.post(`${baseURI}/hook`, { repo }))
  },

  async removeHook (idx: number): Promise<GithubHook[]> {
    return await responseProcessor<GithubHook[]>($http.delete(`${baseURI}/hook/${idx}`))
  }

})

 



































/back-end/src/api/githbu/github.service.ts

import { Inject, Injectable } from '@nestjs/common'
import { GithubRepository, GithubContent, GithubResponseToken, GithubProfile, GithubTrees, GithubBlob } from 'domain/src'
import { GithubAdapter } from './github.adapter'

@Injectable()
export class GithubService {

  constructor(@Inject('GithubAdapter') private readonly githubAdapter: GithubAdapter) {}

  public async getRepo (user: string): Promise<Array<GithubRepository>> {
    try {
      return await this.githubAdapter.getRepo(user)
    } catch (e) {
      console.log('githubService.getRepo', e)
      throw e
    }
  }

  public async getContent (params: { [k: string]: string }): Promise<GithubContent> {
    try {
      return await this.githubAdapter.getContent(params)
    } catch (e) {
      console.log('githubService.getContent', e)
      throw e
    }
  }

  public async getToken (code: string): Promise<GithubResponseToken> {
    try {
      return await this.githubAdapter.getToken(code)
    } catch (e) {
      console.log('githubService.getToken', e)
      throw e
    }
  }

  public async getProfile (token: string): Promise<GithubProfile> {
    try {
      return await this.githubAdapter.getProfile(token)
    } catch (e) {
      console.log('githubService.getProfile', e)
      throw e
    }
  }

  public async getTrees (params: { [k: string]: string }): Promise<GithubTrees> {
    try {
      return await this.githubAdapter.getTrees(params)
    } catch (e) {
      console.log('githubService.getTrees', e)
      throw e
    }
  }

  public async getBlob (params: { [k: string]: string }): Promise<GithubBlob> {
    try {
      return await this.githubAdapter.getBlob(params)
    } catch (e) {
      console.log('githubService.getBlob', e)
      throw e
    }
  }

}

 































































여기까지는 사이드 프로젝트를 통해서 얻은 성과였고, 이제 사이드 프로젝트에 꼭 적용해야 하는 것들을 나열해보자.

첫 번째 과제, Vue Composition API 사용

두 번째 과제, MongoDB 사용

세 번째 과제, AWS 배포

  • 아직까지 AWS를 제대로 사용해본적이 없다.
  • AWS 공부만 해도 한참 걸릴 것 같다.

네 번째 과제, Jenkins로 배포 자동화

  • 회사에서 Jenkins를 이용하여 배포하는 중이다.
  • 개인적으로 Jenkins 배포 환경을 구축해보고 싶다.

그리고 이건 꼭 적용할 필요는 없지만 한 번 해보고 싶은 것들이다.

Optional 01: GraphQL 사용

  • 이건 참 애매하다.
  • 그냥 개인적으로 공부해도 나쁘지 않을 것 같다.

Optional 02: Docker Container와 kubernetes 사용

  • 사실.. 이것 까지 가능할지 의문이다.
  • 일단 가능한 만큼 해보고 싶다.

4. 객체지향 개발방법론

코드스피츠 86기open in new window와 인프런에서 백기선 님이 강의하신 스프링 프레임워크 입문open in new window, 그리고 여름나라 겨울이야기open in new window 블로그의 주인장님이 집필한 스프링 입문을 위한 자바 객체 지향의 원리와 이해open in new window 라는 책을 읽고 객체지향에 대해 어느 정도 깨우칠 수 있었다.

여태까지 내가 작성했던 코드는 말 그대로 쓰레기였다는 것을 알 수 있게 해준 강의들과 책이었다.

그리고 이러한 개념들을 숙지하지 않은 상태에서 프레임워크를 공부한다는 것은 어불성설이라는 생각이 들었다. 요즘 누군가가 나에게 어떤 프레임워크를 공부하면 좋겠냐는 말에 이처럼 대답한다.

XXX라는 프레임워크를 공부하고 싶습니다. 무엇부터 해야 좋을까요?

  1. 객체지향 개발 방법론을 익혀라.
  2. 디자인패턴을 익혀라.
  3. MVVM을 익혀라.
  4. IoC나 DI에 대해 이해하라.
  5. 그리고 프레임워크 문서를 보아라.

객체지향의 궁극적인 목적은 바로 IoC(제어역전) 를 제공하는 것이다.

그리고 보통 프레임워크 수준에서 IoC를 제공한다. 따라서 IoC를 이해하지 못한다면 프레임워크를 사용하고 있다고 해도, 제대로 사용하는 경우는 드물다.

이러한 것들을 이해하고 있는 상태라면 어떤 프레임워크를 사용하여도 기본 이상은 할 수 있다.

객체지향 개발론을 공부하면서 아쉬웠던 점은, 왜 학부 과정에서 이러한 것들을 설명해주는 교수님이 없었을까 하는 점이다. 이렇게 중요한 개념을 왜 이제야 알았을까? 참 의문이다.


5. 단국대학교 알고리즘 스터디

여자친구의 취업 준비를 도우면서 꼴 보기도 싫던 알고리즘 공부를 같이 하게 되었다. 오랜만에 빌어먹어도 시원찮을 알고리즘을 공부하니까 나름 오기도 생기고, 커밋의 양분이 되기도 했고 코딩테스트를 응시하는 것도 재밌었다.

그래서 여자친구와 같이 다음과 같은 코딩테스트에 응시했다.

  • 카카오 인턴 코딩테스트
  • 카카오 프런트엔드 개발자 경력직 코딩테스트
  • 프로그래머스 프런트엔드 개발자 코딩테스트
  • 프로그래머스 백엔드 개발자 코딩테스트
  • 프로그래머스 여름방학 인턴 코딩테스트
  • 우아한테크코스 코딩 테스트
  • 이스트소프트 코딩 테스트

풀이하면서 느낀것은, 그냥 내 수준은 정해져 있는 것 같았다. 하긴 뭐.. 공부를 제대로 했어야 달라지지

그리고 코딜리티와 프로그래머스를 통해서 준비했는데, 다음과 같은 특징을 가지고 있다.

프로그래머스

  • 한글로 되어 있다.
  • UX/UI가 좋다.
  • 테스트케이스에 대한 설명이 너무 빈약하다.
  • 피드백이 없다.
  • 왜 틀렸는지 안 알려준다.
  • 질의응답 게시판이 있다.

Codility

  • 영어로 되어 있다.
  • UX/UI가 망했다. 어떻게 이렇게 구릴 수 있지?
  • 피드백이 확실하다.
  • 왜 틀렸는지 알 수 있다.

그래서 두 사이트를 섞어서 공부하는게 적절한 것 같다. 커뮤니티를 보니 Leetcodeopen in new window를 통해서 많이 준비하는 것 같은데.. 아직 살펴보진 않았다.

각설하고, 여자친구가 혼자서 공부하기 힘들다며 갑자기 4월 말에 알고리즘 스터디open in new window를 만들었다. 스터디 구성원은 먼저 에브리타임을 통해서 모집하고, 알고 지내던 후배들 중 같이 하고 싶은 의향이 있는 사람들을 초대했다.

스터디는 다음과 같이 진행했다.

그리고 생각보다 잘 진행되고 있는 중이다.

진행한지 한 달이 조금 넘었을 때(5월 말) 800개의 커밋과 160개의 PR이 올라왔다.

5월 결산(1)5월 결산(2)

6월에는 기말고사가 있기 때문에 활동이 매우 활발하진 않았다. 그래도 하는 사람은 꾸준히 했다.

6월 결산

그리고 6월 중순까지 카톡으로 채팅방을 운영했는데, 스터디원의 제안으로 채팅방을 디스코드로 옮겼다. 디스코드로 옮기면서 디스코드의 Channel API, Bot, github webhooks 등을 이용하여 Github 알림 봇open in new window을 만들었다.

Github 알림 봇

  • Node.js
  • Typescript
  • NestJS: 초기에 Express를 사용하다가 어느정도 완성된 다음에 NestJS로 마이그레션 했다.
  • VueJS: CMS를 만드는데 필요함
  • Discord API
  • Discrod bot
  • Github Webhooks
  • NHN Toast Cloud

서버는 NHN의 Toast Cloud를 이용중이다. 추후에 AWS로 이전할 예정이다.

알림 봇을 만드는 것 자체는 간단했으나.. CMS를 만드는 것은 쉽지 않은 것 같다. 사실 CMS를 만들어야 싶기도 하다.

어쨌든 알림 봇을 마무리 한 후에 진행중이던 토이 프로젝트를 다시 시작할 예정이다. 대체 언제??


6. 코덕

1~3월에 일일커밋을 하긴 했으나, 깃허브 활동이 활발하진 않았다. 그런데 4월에 스터디를 만들고, 5~6월에 Java Clean Code TDD 8기를 수강하면서 깃허브 사용량이 폭발적으로 증가했다.

덕분에 꾸준히 코덕 상위권에 랭크될 수 있었다.

4월

코덕_1코덕_2

4월은 2위로 마무리했다.

이 때 1위와의 격차는 별짓을 다 해도 좁혀지지가 않았다. 무엇보다 하루 활동량에 대한 점수 제한이 있었기 때문에 꾸준히 활동을 하지 않으면 결국 따라잡을 수 없는 구조다. 즉, 성실성을 겨루는 서비스라고 할 수 있다.

아쉽지만 코덕에 랭크된 수 많은 사람들 중에 두 번째로 성실하게 공부한 것이라는 증거이기 때문에 일단 만족했다.

그리고 칼을 갈았다.

5월

코덕_3

5월은 정말 눈물 겹게 1등을 달성했다. 말 그대로 정말 힘들었다. 내가 무슨 부귀영화를 누리자고..

어쨌든 한 번 1등을 해놓은 후에는 마음이 편해졌다. 그래서 랭킹에 연연하진 않게 되었다.

코덕_4

내가 1등한 것 보다 더 기쁜 사실은, DKU-STUDY가 코덕에 등록된 모든 그룹 중 활동량이 압도적인 1위라는 것이다.

6월

코덕_5

6월엔 쉬엄쉬엄 해서 6위로 마무리했다. 일단 top10에 들었으니 만족!

7. Java Clean Code TDD 8기

7기에는 수강신청 시간을 잘못 알고 있어서 마감이 됐었다. 결과적으로 그 때 수강하지 않은건 코로나 때문에 잘 한 일이라고 생각한다.

8기 수강신청은 거의 1등으로 수강신청을 완료하지 않았나 싶다. 대학교 수강신청으로 단련된 매크로급 수강신청 기술을 활용했다.

현재 회사에서 java를 사용하고 있긴 하지만, java의 코어를 잘 모르기 때문에 이번 기회에 제대로 익혀두자 싶어서 수강신청을 했다. 결과는 죽을 맛 그 자체! 매우 만족스러웠다.

코드 리뷰의 범위는 객체지향 생활체조의 범위에서 진행된다.

객체지향 생활체조

  • 한 메서드에 오직 한 단계의 들여쓰기(indent)만 한다.
  • else 예약어를 쓰지 않는다.
  • 모든 원시값과 문자열을 포장한다.
  • 한 줄에 점을 하나만 찍는다.
  • 줄여쓰지 않는다(축약 금지).
  • 모든 엔티티를 작게 유지한다.
  • 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
  • 일급 콜렉션을 쓴다.
  • Getter/Setter Property를 쓰지 않는다.

위의 규칙들을 지키면서 프로그램을 만드는 것 자체가 굉장히 어렵고 고민이 많이 된다. 하지만 그만큼 코드 자체는 극한의 아름다움을 보이게 된다.

덕분에 Stream과 Enum 등을 포함하여 Java에서 사용하기 좋은 API와 설계 기술을 배울 수 있었다.

각설하고, 내가 과연 이 과정을 무사히 수료할 수 있을까 고민했는데,
운이 좋게 제일 먼저 수료할 수 있었다. 사실 노렸다

약간의 소감을 이야기 해보자면, 시작(Racingcar)과 끝(Bowling)이 무척 어려웠다.

시작할 때는 자바 자체에 익숙하지 않다 보니, 말 그대로 모든 것들이 생소했다. 일단 사내 프로젝트를 운영하면서 Stream을 어느 정도 사용할 줄 알았기 때문에 코드를 짧게 작성한다던가 if나 while, for 없이 작성하는 것은 어렵지 않았다.

그런데 일급 컬렉션이나 원시값 포장 같은 개념은 쉽게 이해되지 않아서 애먹었다. 이러한 문제점을 보완하기 위해서 실력이 뛰어난 다른 수강생분들이나 코드리뷰를 해주시는 리뷰어분들이 이 과정을 수행할 때 작성했던 코드를 보면서 많이 참고했다.

개인적으로 생각하는 나의 장점 중 하나가 주변에 있는 뛰어난 사람들에게 주눅 들기보단 그 사람들의 노하우나 사고방식, 가치관을 잘 받아들이는 점이다.

결과적으로 초반에 애먹은 덕분에 Lotto와 Ladder는 쉽게 통과할 수 있었다.

마지막 미션인 Bowling은 설계 자체가 정말 어려웠다. 어떻게 설계하지? 라는 고민을 일주일 내내 했던 것 같다. 그래서 step2를 진행할 때, 지웠다가 썼다를 반복하다 보니 무려 한 step에 45개의 커밋이 발생했다.

  • step1을 6월 9일에 완료했다
  • step2를 6월 16일에 완료했다
  • step3, step4는 각각 18일, 20일에 완료했다.

정말 step2에서 설계에 대한 고민을 일주일 내내 한 것이다. 덕분에 step3와 step2는거의 바로 끝낼 수 있었다.

어쨌든 결과적으로 해당 과정을 전체 인원 중 첫 번째로 수료할 수 있었다.

클린코드 수료

내가 실력이 좋아서라기보단, 하루도 쉬지 않고 꾸준히 했기 때문에 이런 성과를 낼 수 있었으리라 생각한다.

꾸준히 공부하는 습관을 지니자

5월에는 코덕 1등을 했고, 6월에는 클린코드 1등을 했다.

항상 한계점의 페이스를 유지하기보단, 한 번 한계점까지 도달한 후에 70% ~ 80% 정도를 유지하는 게 좋다고 생각한다. 그렇지 않으면 너무 빨리 지치기 때문이다.

나는 줌인터넷에 입사한 다음에 주변 자극을 적극적으로 수용하고 있다. 그중에 제일 인상 깊었던 것은 우리 팀을 거쳐 간 우아한형제들에서 근무하고 계신 이동욱 님의 인터뷰open in new window이다. 무언가를 거창하게 하기보단 그냥 매일 꾸준히 하는 것이다. 확실히 나에게는 이 사고방식과 가치관이 맞아떨어진다.

  • 근데 이동욱 님은 굉장히 많은 것을 하고 계신다. 인간이 아닌 듯

전체 과정을 수료한 다음에 미션별로 파편화된 코드를 한 저장소open in new window에 모아놨다. 사실 깃허브 잔디에 반영하고 싶었다.

java-clean-code-repository

모아놓고 보니 두 달 동안 약 600개의 commit이 발생했다. 내가 개발 공부를 이렇게 열심히 했던 적이 있었나 싶다. 사실 고등학교 3학년 여름방학 때 제일 열심히 했다.

개발 외

1. 수영

1월에 몸 상태가 정상이 아님을 인지하고 2월에 수영을 시작했다.
하지만 코로나 때문에 망했다. (빌어먹을 코로나)

어쨌든 2월에 2주동안 발차기를 배우다가 코로나의 여파로 재택근무를 했다. 덕분에 몸이 개운해지는 느낌 비스무리한 것만 받았다.

5월이 되자 마자 6개월치를 일시불로 등록했고, 덕분에 매일 매일 수영을 할 수 있게 되었다! 80만원쯤이야!

5월이 다 지날때 쯤 자유형을 제대로 할 수 있게 되었다. 자유형을 하다가 너무 힘들어서 그냥 누웠는데 이 때 몸에 힘을 빼고 물에 몸을 맡기면 저절로 뜨는 것을 깨달았다.

6월이 다 지났을 때 배영/자유형은 꽤 잘할 수 있게 되었다. 다만 오래 못한다.. 아직까진 저질 체력.. 어쨌든 평영까지 배웠는데, 아직 나는 개구리가 되려면 멀지 않았나 싶다.. 허허

그런데 6월 말부터 회사에서 Work+ 라는 제도를 만들어서 재택근무를 할 수 있도록 해줬다. 수영을 하려면 회사 앞으로 와야 하기 때문에.. 지금 굉장히 고민 중이다.

강습은 월/수/금인데, 월/목은 고정출근을 해야 하고 화/수/금은 선택적 재택근무를 할 수 있다. 그래서 주 2회 재택을 하고 강습 2회, 자유 수영 1회 정도로 생각 중이다.

어쨌든 돈을 오랫동안 많이많이 벌기 위해서라도 공부를 열심히 하기 위해서라도 운동을 착실히 해야지!

2. 자본에 대하여

우리 회사 사람들의 핫한 주제는 언제나 부동산과 주식이다. 사실 우리 회사 사람들뿐만 아니라 다른 회사 사람들 또한 마찬가지다.

어느 회사에 다니든 아무리 월급을 많이 받아도 월급쟁이다.

그리고 월급쟁이는 기적이 일어나지 않는 이상 일정 수준 이상의 부를 축적하기는 힘들다.

그래서 큰돈을 모으기 위해선 사업을 하거나 주식을 하거나 부동산을 하거나 셋 중 하나인데, 부동산을 하기 위해선 또 큰돈이 필요하고, 다시 큰돈을 모으기 위해선 주식이나 사업을 해야한다.

그런데 주식이나 사업은 부동산보다 위험부담이 훨씬 크다. 특히 주식/비트코인을 통해서 자본을 잃은 사람이 주변에 너무 많다

이렇게 해도, 저렇게 해도 평탄한 인생을 살아가기는 참 힘들구나 싶다.

어쨌든 나는 지금 당장 욕심을 부리기보단, 내적 역량을 축적하는 것이 옳다고 생각한다.

앞서 언급했지만, 최소 3년은 공부에 매진하자.

3. 오버워치

자바 클린코드 과정을 모두 수료한 직후에 갑자기 번아웃이 왔다. 번아웃이라기보단, 한동안 일상에서 큰 비중을 차지하고 있던 일이 사라지니까 그 시간을 어떻게 메꿔야 좋을지 몰라서 방황했다.

그래서 수료한 직후에 주말 내내 원없이 오버워치만 했다.

오버워치

약 2일만에 마스터를 찍었다. 오버워치만 맨날 했으면 좋겠다!!

오버워치.. 참 잘 만든 게임인데 유저가 다 망쳐가고 있어서 아쉽다. 빨리 오버워치2가 나왔으면 하는 바람!

Summary

  • Java Clean Code 1등으로 수료.
  • 코덕 1등 달성.
  • 단국대 알고리즘 스터디 운영.
  • 사이드 프로젝트 진행.
  • 수영 재밌다.
  • 오버워치 재밌다.
  • 돈 벌기 힘들다.
Last Updated: