15 분 소요

드디어 프리코스 1주차 메일이 왔다.

스크린샷 2022-10-26 오후 3 47 41

앞으로 4주동안 ‘과정’ 중심으로 소감을 작성해보자!

1. 환경 세팅

java 버전 확인

우선 java 11이 맞는지 버전부터 확인하였다.

$ java -version

java 버전이 8 이어서 11로 변경했다.

# 설치되어 있는 Java Virtual Machines 목록을 확인
$ /usr/libexec/java_home -V

# JAVA_HOME 의 경로를 JDK 1.8으로
$ vi ~/.bash_profile
# export JAVA_HOME=$(/usr/libexec/java_home -v 11.0)

$ source ~/.bash_profile 

$ vi ~/.zshrc
# export JAVA_HOME=$(/usr/libexec/java_home -v 11.0)

$ source ~/.zshrc

/usr/libexec/java_home -v 11.0 커맨드를 실행하면 아래와 같이 설치되어 있는 경로가 나온다.
스크린샷 2022-10-26 오후 3 44 46

vi를 이용해 JAVA_HOME의 경로를 바꿔준 뒤 java -vsersion 커맨드로 확인해보자.
스크린샷 2022-10-26 오후 3 45 31

인텔리제이 java 버전 변경

이 글을 참고했다.

formatter & checkstyle 적용

이 글을 참고했다.
(이 곳에서 naver-intellij-formatter.xml을 다운로드 했으며, 이 곳에서 naver-checkstyle-rules.xml을 다운로드 했다.)

2. 1주차 과제 체크리스트

구현

✅ JDK 11 버전인가?
✅ 기능을 구현하기 전에 docs/PROBLEM.md에 구현할 기능 목록 정리하였는가?
✅ 정리한 기능 단위로 커밋하고 있는가?
✅ 함수가 한 가지 일만 하도록 작성하였는가?

테스트

✅ 모든 기능을 구현하였는가?
✅ 일어날 수 있는 예외의 상황을 생각해보았는가?

제출

✅ 모든 테스트가 이상없이 통과하는가?
✅ 올바르게 PR을 보냈는가?
✅ 지원 사이트에 제출하였는가?

3. 문제 별 풀이 과정

문제는 이 곳에서 확인할 수 있다.

문제 1

1번 문제는 리스트 형태로 주어지는 포비와 크롱의 페이지를 주어진 요구사항대로 점수를 계산한 뒤, 포비의 점수와 크롱의 점수를 비교하는 문제였다.
스크린샷 2022-11-03 오전 11 10 23

✏️ 요구 사항 정리

  1. 포비(크롱)의 왼쪽 페이지 번호의 각 자리 숫자를 모두 더하거나, 모두 곱해 더 큰 수를 구한다.
  2. 포비(크롱)의 오른쪽 페이지 번호의 각 자리 숫자를 모두 더하거나, 모두 곱해 더 큰 수를 구한다.
  3. 1 ~ 2번 과정에서 가장 큰 수를 포비(크롱)의 점수로 한다.
  4. 포비와 크롱의 점수를 비교해 가장 높은 사람이 게임의 승자가 된다.
  5. 승부 결과에 따라 반환하는 결과값은 2,1,0 중 하나다. (단, 예외발생 시 -1)

⛔️ 제한 사항

  • pobi와 crong의 길이는 2이다.
  • pobi와 crong에는 [왼쪽 페이지 번호, 오른쪽 페이지 번호]가 순서대로 들어있다.

✅ 기능 목록

- 예외 처리
    - [x] 페이지 리스트의 요소가 2개가 아닌 경우 예외
    - [x] 페이지 리스트의 요소가 연속된 수가 아닌 경우 예외
    - [x] 왼쪽 페이지가 홀수이고, 오른쪽 페이지가 짝수가 아닌 경우 예외
    - [x] 페이지 범위가 1 ~ 400 이 아닌 경우 예외


- [x] 페이지 번호 점수 계산
    - 왼쪽 페이지의 각 자리수를 더한다.
    - 왼쪽 페이지의 각 자리수를 곱한다.
    - 오른쪽 페이지의 각 자리수를 더한다.
    - 오른쪽 페이지의 각 자리수를 곱한다.
    - 위에서 나온 4개의 값 중 최댓값을 리턴한다.


- [x] 포비와 크롱의 페이지 점수를 비교해 리턴값 세팅
    - (포비 점수 == 크롱 접수) -> return 0
    - (포비 점수 > 크롱 접수) -> return 1
    - (포비 점수 < 크롱 접수) -> return 2

🛠 리팩토링

  • 제한사항들을 체크하는 메서드의 분리
    - checkRestrictions 메서드 안에서 특정 제한사항을 체크하는 메서드 호출
  • early return 구현
  • 매직 넘버의 상수화
public static final int DRAW = 0;
public static final int POBI_WON = 1;
public static final int CRONG_WON = 2;

public static final int LEFT_PAGE = 0;
public static final int RIGHT_PAGE = 1;

public static final int FIRST_PAGE = 1;
public static final int LAST_PAGE = 400;

public static final int EXCEPTION = -1;

😲 회고

처음에는 포비 혹은 크롱의 page를 한 자리수로 나눠 리스트에 저장했다.
그리고 그 리스트를 순회하며 각 요소의 합과 곱을 구했는데,
그럴 필요 없이 while (page > 0) 동안 page를 10으로 나눈 몫과 나머지를 이용하여 깔끔하게 합과 곱을 구할 수 있었다.
그리고 매번 비교를 통해 합과 곱을 갱신하여 최댓값을 구하면 되는 문제였다.

문제 2

스크린샷 2022-11-03 오전 11 10 37

✏️ 요구 사항 정리

  1. cryptogram 에서 연속하는 중복 문자들을 삭제한다.
  2. 결과값을 반환한다.

⛔️ 제한 사항

  • cryptogram은 길이가 1 이상 1000 이하인 문자열이다.
  • cryptogram은 알파벳 소문자로만 이루어져 있다.

✅ 기능 목록

- 예외 처리
    - [x] cryptogram 의 길이가 1 ~ 1000 이 아닌 경우 예외
    - [x] cryptogram 이 알파벳 소문자로만 이루어지지 않은 경우 예외


- [x] 연속하는 중복 문자를 삭제
    - cryptogram 를 순회하며 한 글자씩 스택에 넣는다.
    - 현재 스택의 top 과 넣을 글자가 같다면 top 을 pop 한다.
        - 이때 top 의 값으로 중복 문자를 세팅한다.
    - 이어서 순회하며 한 글자씩 스택에 넣는다.
        - 이전에 세팅한 중복 문자인 경우, 스택에 넣지 않는다.
        - 중복 문자가 아닌 경우, 스택에 넣고 중복 문자를 빈 문자로 초기화한다.
    - 세 개 이상의 문자가 연속되더라도 정상적으로 삭제되도록 한다.

- [x] 스택을 스트링으로 변환하여 리턴

🛠 리팩토링

  • 매직 넘버의 상수화
public static final String ERROR_MESSAGE = "제한 사항을 위배했습니다.";
public static final int EXCEPTION = -1;

😲 회고

제일 처음 생각났던 방법은 cryptogram을 캐릭터 배열인 charArr로 변환한 뒤, charArr를 순회하며 charArr[i]charArr[i-1]이 같을 때, 두 원소를 remove 하는 것이었다.
그러나 이 방법은 연속되는 두 개의 문자만 삭제할 수 있고, 세 개 이상의 문자는 삭제할 수 없었다.
기능 요구사항에는 중복 문자가 두 개라는 사항은 없었기에 세 개 이상의 문자도 삭제할 수 있는 방법을 생각했다.

그리고 생각한게 스택 자료구조를 이용하는 것이었다.
cryptogram을 한 문자식 순회하면서 두 개 연속 중복 문자가 있을 때 그 문자를 duplicateCharacter 라는 변수에 담아놓고 pop 한다. 그리고 duplicateCharacter 가 아닐 때만 스택에 push 하는 것이다.
이런식으로 구현하니 세 개 이상의 중복 문자도 삭제할 수 있었다.

스택을 떠올리지 못했다면 어려운 문제였을 것이다.

문제 3

3번은 이미 잘 알고있는 369 게임의 룰과 같았으며, 특정 수(number)까지 손뼉을 몇 번 치는지 계산하는 문제였다.
스크린샷 2022-11-03 오전 11 10 49

✏️ 요구 사항 정리

  1. 1부터 number까지 3, 6, 9의 게임을 진행한다.
  2. 이때 손뼉을 총 몇 번 쳐야 하는지 반환한다.

⛔️ 제한 사항

  • number는 1 이상 10,000 이하인 자연수이다.

✅ 기능 목록

- 예외 처리
    - [x] number 의 범위가 1 ~ 10000 이 아닌 경우 예외


- [x] 손뼉을 치는 횟수 계산
    - 3, 6, 9 가 들어가면 손뼉을 친다.
        - 33, 36 등은 손뼉을 두 번 친다.

🛠 리팩토링

  • int 타입의 변수를 string으로 변환한 뒤, 리스트에 담지 말고 charAt()을 이용해 한 글자씩 char로 변환한다.

😲 회고

처음에는 contains() 메서드를 이용해 문자열에 3 또는 6 또는 9가 들어있을 때 cnt++ 하도록 구현하였는데, 테스트에 실패하였다.
그 이유는, 예를 들어 36 이라는 수에서 카운트를 2회 올려야 하는데, 1회만 올리게 되어있었기 때문이었다.

그래서 int 형태로 주어지는 numberstring으로 변환한 뒤, string 을 또 charList로 변환했다.
그리고 charList를 순회하며 3,6,9 각각에 박수를 치도록 구현했다.
테스트 코드는 통과했지만, 조금 더 리팩토링 할 수 있을 것 같았다.

방법을 생각하던 중, 스트링을 한 문자씩 순회하려면 리스트 형태로 변환해야하는 줄 알았는데, 변환하지 않고 문자열 형태에서도 순회가 가능하다는 사실을 알았다.
그렇다면 charList는 필요 없었고, intstring으로 변환한 뒤, 그 스트링을 한 글자씩 순회하며 char 형태로 변환한다.
그리고 해당 char가 3,6,9라면 각각에 박수를 치도록 구현했다.

문제 4

4번은 string 형태의 word가 주어질 때, 그 문자열에 포함된 각각의 문자를 주어진 조건에 맞게 변환하는 문제였다.
스크린샷 2022-11-03 오전 11 10 59

✏️ 요구 사항 정리

  1. 주어진 문자열을 사전을 알파벳 역순으로 배치했을 때 매칭되는 값으로 변환한다.

⛔️ 제한 사항

  • word는 길이가 1 이상 1,000 이하인 문자열이다.
  • 알파벳 외의 문자는 변환하지 않는다.
  • 알파벳 대문자는 알파벳 대문자로, 알파벳 소문자는 알파벳 소문자로 변환한다.

✅ 기능 목록

- 예외 처리
    - [x] word 의 길이가 1 이상 1,000 이하가 아닌 경우 예외


- [x] 주어진 문자열을 청개구리 사전대로 변환
    - 주어진 word 를 아래 청개구리 사전을 참고해 변환한다.
        - 예를 들어, a는 z로, B는 Y로 변환한다.
    - 알파벳 외의 문자는 변환하지 않는다.
    - 알파벳 대문자는 알파벳 대문자로, 알파벳 소문자는 알파벳 소문자로 변환한다.

🛠 리팩토링

  • 문자를 치환하기 위한 로직의 표현법 변경

변경 전

private static List<Character> conversionWord(String word) {
		List<Character> letters = new ArrayList<>();
		char letter;
    int askiiSequence;
		for (int i = 0; i < word.length(); i++) {
			letter = word.charAt(i);
			if (letter > LOWER_SRC && letter < LOWER_DEST) {
        askiiSequence = letter - 'a'; // 앞에서부터 몇 번째인지
				letter = (char)('z' - askiiSequence); // 뒤에서부터 몇 번째인지
			}
			if (letter > UPPER_SRC && letter < UPPER_DEST) {
				askiiSequence = letter - 'A'; // 앞에서부터 몇 번째인지
				letter = (char)('Z' - askiiSequence); // 뒤에서부터 몇 번째인지
			}
			letters.add(letter);
		}
		return letters;
	}

변경 후

private static List<Character> conversionWord(String word) {
		List<Character> letters = new ArrayList<>();
		char letter;
		for (int i = 0; i < word.length(); i++) {
			letter = word.charAt(i);
			if (letter > LOWER_SRC && letter < LOWER_DEST) {
				letter = (char)('a' + 'z' - letter); // 한 줄로 정리
			}
			if (letter > UPPER_SRC && letter < UPPER_DEST) {
				letter = (char)('A' + 'Z' - letter); // 한 줄로 정리
			}
			letters.add(letter);
		}
		return letters;
	}

😲 회고

이 글

보자마자 아스키 코드가 생각난 문제였고, 아스키 코드를 사용하여 어렵지 않게 구현할 수 있었다.
이 때, 문자열을 순회하기 위해서는 3번에서와 마찬가지로 리스트 형태로 변환하지 않고 문자열 상태 그대로 순회하였다.

문자열을 순회하기 전에, for 문을 이용해 word 문자열을 순회하며 charArr에 한 글자씩 초기화했었는데, 나중에 생각해보니 굳이 charArr를 초기화하지 않고 빈 Array에 변환된 문자를 대입하면 될 것 같아서 초기화하는 코드는 제거했다.

이렇게 Array를 이용해 코드를 작성하고 테스트 코드까지 통과했지만, Array가 아닌 ArrayList를 이용할 수도 있을 것 같았다.
Array를 사용할지 ArrayList를 사용할지 판단하기 위해 검색해보았다.

둘의 가장 큰 차이점은 길이를 조정할 수 있는가? 없는가? 이다.
Array의 경우는 고정 길이로, 정해진 길이의 배열을 모두 채우면, 새로운 데이터를 추가하고 싶을 경우 새로운 배열을 만들어주어야 한다.
반면, ArrayList는 가변 길이이며 개발자에게 조금 더 편한 배열이라고 생각할 수 있다.
다만, 편리함의 대가로 Array보다 살짝 느리기 때문에 Array로 충분히 처리 가능하다면 Array를 활용하는 것이 좋다고 한다.
따라서 ArrayList로 변경하지 않았다.

문제 5

5번은 int 형태로 money가 주어질 때, 해당 돈을 오만 원권부터 일원 동전까지 각 몇 개로 변환되는지 계산하는 문제였다.
스크린샷 2022-11-03 오전 11 11 13

✏️ 요구 사항 정리

  1. 주어진 금액이 [오만 원권, 만 원권, 오천 원권, 천 원권, 오백원 동전, 백원 동전, 오십원 동전, 십원 동전, 일원 동전] 각 몇 개로 변환되는지 반환한다.

⛔️ 제한 사항

  • money는 1 이상 1,000,000 이하인 자연수이다.

✅ 기능 목록

- 예외 처리
    - [x] money 가 1 이상 1,000,000 이하인 자연수가 아닌 경우 예외


- [x] money 가 오만 원권, 만 원권, 오천 원권, 천 원권, 오백원 동전, 백원 동전, 오십원 동전, 십원 동전, 일원 동전 각 몇 개로 변환되는지 계산
    - 금액이 큰 순서대로 리스트/배열에 담아 반환한다.

😲 회고

처음에는 조건문을 이용해 money가 오만원 이상일 때, 만원 이상일 때, 등등.. 금액별로 나누어야 하나 생각했다.
하지만 나중에 생각해보니 money를 오만원, 만원, 천원 등으로 나누었을 때의 몫을 사용하면 되기 때문에 money를 금액별로 나누지 않아도 되었다.

처음에는 money를 나눌 50000, 10000, …, 1 까지의 단위는 unit 이라는 리스트에 담아, 리스트를 순회하며 money를 나눴다.

문제 6

지원자들의 닉네임 중 같은 글자가 연속으로 포함되는 닉네임을 작성한 지원자의 이메일 목록을 반환하는 문제로, 조금은 복잡했던 문제였다.
스크린샷 2022-11-03 오전 11 11 23

✏️ 요구 사항 정리

  1. 지원자들의 닉네임을 검토하여 사용이 제한되는 닉네임을 작성한 지원자의 이메일 목록을 반환한다.
  2. 사용지 제한되는 닉네임은 다음과 같다.
    2-1. 연속된 두 글자 이상의 문자가 공통으로 포함되어 있는 경우

예시

  • “제이”엠
  • “제이”슨
  • 워니
  • 엠”제이”
  • 이제엠

여기서 사용이 제한되는 닉네임은, “제이”라는 연속되는 문자가 공통으로 포함되어 있는 “제이엠, 제이슨, 엠제이” 이다.

⛔️ 제한 사항

  • 두 글자 이상의 문자가 연속적으로 순서에 맞추어 포함되어 있는 경우 중복으로 간주한다.
  • 크루는 1명 이상 10,000명 이하이다.
  • 이메일은 이메일 형식에 부합하며, 전체 길이는 11자 이상 20자 미만이다.
  • 신청할 수 있는 이메일은 email.com 도메인으로만 제한한다.
  • 닉네임은 한글만 가능하고 전체 길이는 1자 이상 20자 미만이다.
  • result는 이메일에 해당하는 부분의 문자열을 오름차순으로 정렬하고 중복은 제거한다.

✅ 기능 목록

- 예외 처리
    - [x] 크루의 수가 1명 이상 10,000명 이하가 아닌 경우 예외
    - [x] 이메일의 전체 길이가 11자 이상 20자 미만이 아닌 경우 예외
    - [x] 닉네임의 전체 길이가 1자 이상 20자 미만이 아닌 경우 예외


- [x] 사용이 불가능한 닉네임 찾기
    - 사용이 불가능한 닉네임이란, 다른 크루의 닉네임과 연속적으로 동일한 문자열이 포함된 닉네임이다.
        - 모든 지원자의 닉네임을 두 글자씩 쪼갠다.
        - 두 글자씩 쪼갠 닉네임들 중 중복 문자열 찾는다.
            - 이때 발견한 문자열이 포함된 넥네임은 사용이 불가능하다.


- [x] 사용이 불가능한 닉네임을 작성한 지원자의 이메일 목록 반환
    - 이메일에 해당하는 부분의 문자열을 오름차순으로 정렬하고 중복은 제거한다.

😲 회고

지원자들의 닉네임 중 같은 글자가 연속으로 포함되는 닉네임을 작성한 지원자의 이메일 목록을 반환하는 문제로, 조금은 복잡했던 문제였다.

문제를 읽고 key(닉네임)와 value(이메일)를 다루기 위해 HashMap을 이용해야겠다는 생각까지는 바로 들었는데, HashMap에 값을 초기화 한 뒤에 로직을 생각하기 어려웠다.

처음에는 splitNicknamesIntoTwoDigit 리스트를 구현하기 위해 2차원 배열을 활용할까 생각했었지만,
String[][] splitNicknamesIntoTwoDigit = new String[forms.size][20] 으로 할당받으니 수많은 null 값이 들어가서 리스트에 담아야 겠다고 생각했다.

  1. 닉네임을 두 글자씩 쪼개어 splitNicknamesIntoTwoDigit 리스트에 담는다.
  2. splitNicknamesIntoTwoDigit 리스트에서 중복을 찾아 duplicateNicknames set에 담는다.
    2-1. 이 때 중복은 indexOf()lastIndexOf() 메서드를 이용한다.
  3. duplicateNicknames를 순회하며 해당 중복 스트링이 포함되어 있는 닉네임을 찾는다.
  4. 3번에서 찾은 닉네임을 작성한 이메일을 리스트에 담아 반환한다.
    4-1. duplicateNicknames 의 요소가 포함되어 있는 key(닉네임)가 있다면, 그 value(이메일)를 answer 리스트에 추가한다.

결과값을 반환할 때, 이메일을 오름차순으로 정렬해야하는 규칙을 못 보고 한참을 고민했다.
문제부터 풀게 아니라 요구 사항을 꼼꼼히 읽는 습관을 길러야겠다.
기능 목록을 먼저 정의하고 개발을 시작하는 것에 대한 중요성을 다시 한번 깨달았다.

7번

7번은 친구 추천 규칙에 따라 친구 추천 점수를 계산해 점수가 가장 높은 5명을 리턴하는 문제로, 가장 복잡한 문제였다.
스크린샷 2022-11-03 오전 11 11 36

✏️ 요구 사항 정리

  1. 미스터코의 친구 추천 규칙에 따라 점수가 가장 높은 순으로 정렬하여 최대 5명을 반환한다. 친구 추천 규칙은 아래와 같다.
    1-1. 사용자와 함께 아는 친구의 수 = 10점
    1-2. 사용자의 타임 라인에 방문한 횟수 = 1점
  2. 이때 추천 점수가 0점인 경우 추천하지 않으며, 추천 점수가 같은 경우는 이름순으로 정렬한다.

⛔️ 제한 사항

  • user는 길이가 1 이상 30 이하인 문자열이다.
  • friends는 길이가 1 이상 10,000 이하인 리스트/배열이다.
  • friends의 각 원소는 길이가 2인 리스트/배열로 [아이디 A, 아이디 B] 순으로 들어있다.
    - A와 B는 친구라는 의미이다.
    - 아이디는 길이가 1 이상 30 이하인 문자열이다.
  • visitors는 길이가 0 이상 10,000 이하인 리스트/배열이다.
  • 사용자 아이디는 알파벳 소문자로만 이루어져 있다.
  • 동일한 친구 관계가 중복해서 주어지지 않는다.
  • 추천할 친구가 없는 경우는 주어지지 않는다.

✅ 기능 목록

- 예외 처리
    - [x] user 의 길이가 1 이상 30 이하가 아닌 경우 예외
    - [x] friends 의 길이 1 이상 10,000 이하가 아닌 경우 예외
    - [x] 사용자 아이디의 길이가 1 이상 30 이하가 아닌 경우 예외
    - [x] visitors 의 길이가 0 이상 10,000 이하가 아닌 경우 예외
    - [x] 사용자 아이디가 알파벳 소문자로만 이루어져 있지 않은 경우 예외


- [x] 각 친구들의 추천 점수 계산
    - 이미 친구인 사람들을 초기화한다.
        - 이미 친구인 사람들은 추천 점수를 계산할 필요가 없다.
    - 추천 점수를 계산해야하는 친구들을 초기화한다.
    - 친구 추천 규칙에 따라 추천 점수를 계산한다.
        - 사용자와 함께 아는 친구 수 * 10점
        - 사용자의 타임 라인에 방문한 횟수 * 1점


- [x] 정렬
    - 정렬 하기 전, 추천 점수가 0점인 사람은 제외한다.
    - 점수가 가장 높은 순으로 정렬한다.
    - 추천 점수가 같은 경우는 이름 순으로 정렬한다.


- [x] 추천 친구 반환
    - 최대 5명까지만 반환한다.

😲 회고

문제를 처음 봤을 때는 잘 이해가 되지 않아 여러번 읽고서야 이해할 수 있었다.

제일 먼저, user와 아직 친구가 아닌 사람들을 notYetFriendWithUser 에 담았다.
왜냐하면 user와 이미 친구인 사람들은 추천 친구에서 제외하기 때문에, 추천 점수를 계산할 필요가 없기 때문이다.
(친구들의 이름은 friends 와 visitors 에 있는데, 중복을 제외하기 위해서 set 자료구조를 이용하였다.)

user와 이미 친구인 사람들은 따로 alreadyFriendwithUser 라는 set을 이용해 담아두었다.
원래는 alreadyFriendwithUser 에 user의 이름은 제외하고 user와 친구인 사람의 이름을 넣으려고 했지만, user와 친구인 사람의 이름을 friend.get(0)을 통해 가져와야할지 friend.get(1)을 통해 가져와야할지 알 수 없었다.
따라서 우선은 alreadyFriendwithUser에 user를 포함한 friend 리스트의 모든 요소를 넣었고, user는 나중에 제거했다.

그리고 calcScore() 메서드를 작성했고, 각각의 친구 추천 규칙에 따라 notYetFriendWithUser 에 있는 친구들의 추천 점수를 계산하는 로직을 메서드로 분리했다.

마지막으로, sortFriends() 메서드를 정의해 반환값을 정렬했다.
추천 점수가 높은 순으로 정렬하고, 점수가 같은 경우에는 이름순으로 정렬해야 했기 때문에 compareTo() 메서드를 이용해 정렬 순서를 결정했다.

4. 회고

문제를 풀며

한 문제를 풀 수 있는 방법은 많겠지만, 역시 깔끔하고 간결한 코드를 작성하기 위해서는 다양한 자료구조를 알면 확실히 도움이 되는 것 같다.

2번 문제에서 처음에는 Array를 사용하여 복잡하게 구현했는데, 세 가지 이상의 문자가 중복될 때 삭제되지 않는 문제가 발생했다.
이를 Stack으로 구현하니 원하는 테스트도 성공할 수 있었고 코드도 훨씬 간결해졌다.
같은 문제를 풀기 위해 어떤 자료구조를 사용하느냐에 따라 코드의 질이 달라진다는 것을 다시 한번 느낄 수 있었다.

또한 중간중간 익숙하지 않은 java 문법에 대해 검색해가며 작성했는데, java 문법에 얼른 익숙해져야겠다고 생각했다.

코드를 작성하며

코드를 작성하며 신경쓴 부분은 아래와 같다.

  1. 최대한 기능별로 메서드를 나눌 것
  2. 메서드의 이름만 보고 해당 메서드의 역할을 알 수 있도록 의미있게 지을 것
  3. 각 메서드는 최대 20줄이 넘지 않도록 할 것
  4. 매직넘버를 상수화 할 것
  5. else를 사용하지 않을 것
  6. array(배열) 대신 List,Set,Map등의 java Collection을 사용할 것
  7. 주석은 꼭 필요한 경우에만 남길 것
  8. 예외 사항을 생각할 것

1번부터 8번까지의 모든 조건들을 평소에 신경쓰지 않았던터라 익숙치 않았다.
특히 메서드와 변수의 이름을 의미있게 짓기가 힘들었다.
기존에는 빠르게 코딩하기 위해서 str, arr 등 의미없는 이름을 지었다면, splitNicknamesIntoTwoDigit, notYetFriendWithUser 등으로 이름만 보아도 무슨 의미인지 알 수 있도록 네이밍을 하니
시간을 훨씬 오래 걸리더라도 다른 사람이 이해하기 훨씬 좋은 코드가 되었다.
이와 관련해 클린 코드에서 읽었던 내용 중, 의미없는 간결한 이름보다 의미있는 복잡한 이름이 더 낫다는 구절이 생각났다.

그리고 메서드 라인을 20줄을 넘지 않도록 하기 위해서 노력했는데, 이는 평소에 생각하던 것보다 훨씬 작은 단위였다.
역시 익숙치 않았기에 하나의 메서드가 하나의 기능만을 가지도록 최대한 작은 기능으로 나누기 위해 신경썼다.

또한 처음에는 주석을 통해 자세히 설명하는 것이 읽는 사람의 이해를 돕기 위한 방법이라 생각했기 때문에 주석을 많이 달았는데,
꼭 필요한 경우가 아니면 남기지 않는게 더욱 좋다는 사실에 놀랐다.
주석이 필요없을 만큼 네이밍에 신경쓰고 주석 없이 깔끔하게 두는 것이 좋은 코드라는 것을 깨달았다.

문제를 풀기 위해 단순히 로직을 생각하는 것 이상으로 좋은 코드를 작성하는 방법에 대해 정말 많은 것을 배울 수 있었다.

이번 과제를 진행하며 스스로 아쉬웠던 점이 몇 가지 있다.
리드미를 잘 읽었다고 생각했는데, 놓친 부분이 있었다는 것이다.
리드미에 기재되어 있는 “기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다.” 라는 코멘트를 놓쳤다.

해당 부분을 뒤늦게 읽은 탓에 기능을 구현한 뒤에 기능 목록을 만들었고, 초반에 기능 단위가 아닌 문제 단위로 커밋을 진행한 점이 아쉬웠다.
비록 기능을 다 개발하고 난 뒤였지만 기능 목록을 작성해 놓친 기능이 없는지 체크하였고, 후반 부에는 기능 단위로 커밋을 진행했다.

깃을 사용하며

깃에는 익숙하다고 생각했지만 그렇지 않았다.
fork & clone 하여 브랜치를 생성하는 것까지는 어려움이 없었으나, 평소 커밋 메시지를 열심히 적지 않고 있었다는 것을 깨달았다.

커밋 메시지를 작성할 때, 핵심 동사를 먼저 작성해야 보기 편하다는 사실을 알았으나,
한글로 작성하다보니 나도모르게 동사를 마지막에 위치시켜 커밋한 메시지들이 있어 통일감이 사라진게 아쉬웠다.
2주차 과제부터는 동사를 맨 앞에 자연스럽게 위치시키기 위해 영어를 사용하는 것에 대해 고려해보려고 한다.

그리고 비슷한 맥락에서, 커밋 메시지를 작성하며 같은 의미의 용어를 통일하지 못한 점이 아쉬웠다.
예를 들면 코드에 변경 사항이 생겼을 때 “~~ 수정” 으로 커밋할 때도 있었고, “~~ 변경” 으로 커밋할 때도 있었다.
평소 커밋 메시지에 통일감을 고려하지 않았기 때문이고 앞으로는 신경써야 겠다고 생각했다.

그리고 1주차 과제에서는 커밋 컨벤션에 대한 언급이 없어 body와 footer는 작성하지 않았는데, 2주차 과제부터는 body와 footer까지 작성하며 익숙해지려 노력할 것이다.

body와 footer에 대해서는 이 글을 참고하자.

코드 리뷰를 진행하며

같이 프로젝트를 진행하고 있는 팀원도 우테코를 신청했다는 사실을 알게되어 함께 코드리뷰를 진행했다.
줌을 이용해 실시간으로 자신의 코드를 설명하고 피드백을 받는 형식으로 진행했다.
코드리뷰를 받아본 경험은 있지만 해본 경험은 없었기 때문에 어떤 방식으로 진행해야할지 막막하고 긴장되었다.
하지만 막상 시작하니 어떻게 리팩토링 하면 좋을지, 어떤 이유에서 테스트가 실패했는지 이야기를 나누다보니 시간이 훌쩍 지나있었다.

코드에 관한 것 뿐만 아니라 잡담도 나눴는데 여러모로 도움이 정말 많이 되었다.
이전까지 코드리뷰를 할 때에는, 깃에 있는 코드리뷰 기능을 이용해 텍스트로만 의견을 나눴는데, 직접 대화를 통해 의견을 나누니 의사소통도 훨씬 수월했고 재미있었다.
같은 관심사를 가진 사람들과 활발하게 이야기 나누고 싶다는 생각이 더욱 간절히 들었던 시간이었다.

(추가)

1주차 공통 피드백

스크린샷 2022-11-03 오후 12 17 00
스크린샷 2022-11-03 오후 12 14 20
스크린샷 2022-11-03 오후 12 14 37



💛 개인 공부 기록용 블로그입니다. 👻

맨 위로 이동하기

태그:

카테고리:

업데이트: