학부 Java 스터디 회고 1

공병주(Chris)·2023년 1월 25일
0
post-thumbnail

2023년에 어떤 걸 할까… 고민하다가 2022년에 공부한 것들을 조금씩 나누면서 살면 좋겠다는 생각이 들어서 대학 과 후배들을 대상으로 멘토링 같은 스터디를 진행하였다. 스터디가 50%정도 진행된 현 시점에서 이전 3주의 기간을 돌아보려한다.

동기

스터디원(멘티)들의 성장 그 자체

과에서 웹 개발자를 하고 싶어하는 친구들이 있었는데, 어떻게 시작해야하는지 어떤 것을 중점적으로 해야할지 모르는 친구들이 많았다. 또, 실제로 웹 서버 개발을 해보고 해왔지만 OOP, Test, Clean Code에 대해 잘 모르는 친구들이 대부분이었다. 막말로 본인이 찾아서 하면 되지만 누군가 인도해주면 더 잘할 수 있을 것이라는 생각이 들었다. 나 역시도, 우테코 전의 3개월 정도 선배의 리딩하에 Java 공부를 진행했었고 우테코에서도 크루들과 코치님들 덕분에 많은 성장을 이뤘다고 생각한다. 물론 내가 공부에 쏟은 열정과 시간도 있겠지만, 함께 공부하는 사람들과 이끌어주는 누군가가 있었기에 지금만큼 올 수 있었다고 생각한다. 따라서, 과 후배들에게도 이런 환경을 마련해주면 좋을 것 같다는 생각이 들었다!

나의 또 다른 성장

우테코 시절에는 리뷰이, 크루로써 배움을 받는, 혹은 동등한 위치에서 배움을 나누면서 성장해왔다. 이번엔 반대로 가르침을 주는 위치에서 성장을 이룰 수 있을 것이라고 생각했다. 남에게 이해할 수 있는 정도로 설명을 할 수 있어야 온전한 내 지식이 된다고 생각한다. 이때까지 공부한 것들을 멘티들에게 설명을 하면서 온전한 내 지식들로 만들 수 있겠다고 생각했다. 또한, 코드 리뷰를 진행하면서 리뷰이들의 질문에 알지 못했던 것들을 메워나갈 수 있고 당연하게 생각했던 것들을 한번 더 의심하고 돌아볼 수 있는 기회가 될 것이라고 생각했다.

사실 우테코를 수료할 때 즈음에, 스터디에 대한 생각을 가지고 있었고 수료식 때 이를 포비에게 말했다. 포비의 그런 경험이 크리스에게 더 좋을 거예요. 라는 말씀이 아직도 기억이 난다. 그냥 계속 생각나서 적어두었다.

지원

그렇게 아래 스터디 모집 링크를 학부 학생들에게 배포하기 시작했다.

https://sulfuric-help-b59.notion.site/Java-b218542bd748446985924b5c09d71526

되게 급하게 쓴거라 내용은 빈약했다. 근거를 토대로 중요성을 더 어필했어야 했는데 라는 생각이 들었다. 그리고, 학생들이 지원을 안하면 어떡하지 ㅋㅋㅋ 라는 생각도 들었다.

다행스럽게, 6명의 학생들이 지원을 해주었다. 지원 과정에서 어느 정도의 학습에 대한 진정성과 Java의 진짜 기초 문법에 대한 이해를 보기 위해서 문자열 계산기를 미션으로 제출해달라고 요청했다.

아래 저장소에 스터디 지원시에 제출한 코드들이 있다. (합격한 4명의 코드만 동의를 얻고 저장소에 기록해두었다.)

https://github.com/GlobalMedia-BackEnd-Hallae/gmbs-apply

사실 코드만으로 6명 중 4명을 고르기가 상당히 어려웠다. 따라서, 먼저 스터디 기간 중에 장기 여행을 가는 1명은 합류시키지 않았다. 그리고 나머지 인원들과는 1대 1로 이야기를 나눠보고 조금 더 진정성이 있어보이는 4인을 스터디 인원으로 최종 결정했다.

진행 방식

미션 + 리뷰 방식

미션 진행과 미션에 대한 코드 리뷰 방식이 제일 효율적이라고 생각이 들었다.

또한, 미션마다 공부하고 집중해야 할 주제들을 할당해두었다.

또한, PR을 제출하면서 본인이 중점적으로 고민했던 부분과 어려웠던 점을 작성하도록 하였다.

세션

미션의 핵심 주제들을 제시하면서, 주제들에 대한 기본 개념들에 대해서 설명해주는 간단한 세션 시간을 두었다.

라이브 코딩을 통해 핵심 주제들에 대해 설명하고 질문에 대한 답변을 진행했다.

토론과 공유의 시간

1주일에 1회씩 온/오프라인으로 만나서, 미션을 하면서 들었던 고민들, 어려웠던 점, 공부했던 것들을 나눌 수 있도록 했다. 또한, 스터디의 목적에 공부한 것을 다른 사람들과 나누는 경험이 있었기에 중요한 시간으로 생각했다.

소통 및 코드리뷰

SlackGitHub PR을 통해 진행했다.

미션

모든 미션은 아래와 같이 우아한테크코스 프리코스에서 진행했던 미션들을 기반으로 진행하였다.

⭕️ 1주차 : 숫자 야구 미션

  • Clean Code
  • Unit Test

⭕️ 3-4주차 : 자동차 경주 미션

  • OOP 기본
  • MVC 패턴
  • TDD (Test-Driven Development)
  • Value Object

⭕️ 5-6주차 : 로또 미션

  • enum
  • Test하기 좋은 구조 만들기
  • 일급컬렉션

⭕️ 7-8주차 : 블랙잭 미션

  • 상속 - 인터페이스와 추상클래스

첫번째 미션 : ⚽️ 숫자 야구 미션 ⚽️

미션 저장소

https://github.com/GlobalMedia-BackEnd-Hallae/java-baseball

제출된 PR

https://github.com/GlobalMedia-BackEnd-Hallae/java-baseball/pull/2

https://github.com/GlobalMedia-BackEnd-Hallae/java-baseball/pull/3

https://github.com/GlobalMedia-BackEnd-Hallae/java-baseball/pull/4

https://github.com/GlobalMedia-BackEnd-Hallae/java-baseball/pull/5

핵심 주제

내가 처음 코드를 작성했을 때 Clean Code에 대해 무지했고 코드를 지저분하게 작성했다. 따라서, 첫 미션은 가장 기본으로 생각되는 Clean Code에 집중을 하려고 했다. 또한, 처음부터 Test 코드를 작성하도록 하기 위해서 Unit Test를 왜 작성해야 하는지에 대한 설명을 하고 Unit Test를 작성하도록 했다.

두 개의 주제에 대해 세션을 가졌다. 거의 1시간 정도를 떠들었는데, 목이 많이 아팠던 기억이 난다.

아래 가장 기억에 남는 코드를 첨부해두었다. 이 친구는 알고리즘을 엄청나게 많이 풀었었는데, 뭔가 코드에서 알고리즘스러운 향기가 났다.

위 코드로 PR을 올리면서 아래와 같은 질문을 했다.

이번주에 했던 스터디 마지막 부분에 test에 대해서 설명을 많이 해주셨는데 test라는 것에 대해 아직 잘 모르는 거 같아서 당장은 제 코드에 적용한다면 어떻게 적용할 수 있을 지 궁금합니다.

하나의 클래스에 입출력 로직, 비즈니스 로직을 다 집어넣었기 때문에 테스트를 할 수 없는 구조가 만들어졌다.

// 첫번째로 제출한 코드
package gmbs;

import java.util.Random;
import java.util.Scanner;

public class Application {
    /*
    함수 종류
    -------- 반복 --------
    1. 1부터 9까지 서로 다른 수로 이루어진 3개의 수를 랜덤으로 만들어주는 함수
    -------- 반복 --------
    2. 사용자에게 3가지 수를 입력 받는 함수
    3. 입력 받은 수와 랜덤수를 비교해 결과를 출력해주는 함수
    4. 게임이 끝난 경우 재시작/종료 여부를 입력 받아 1번 부터 반복할 지의 여부를 결정하는 함수
     */

    private static final String EXPRESSION_DELIMETER = "";
    public static final int NUMBERLENGTH = 3;
    public static final int NOT_EXIST = -1;
    public static final int BALL = 0;
    public static final int STRIKE = 1;

    static int rightInput = 0;
    static int recursion = 1;
    static int answer = 0;
    static String randomNumber;
    static String userNumber;
    static int ballCount;
    static int strikeCount;

    public static int checkInput() {
        if (userNumber.length() != NUMBERLENGTH) {
            throw new IllegalStateException("3개의 수를 입력해주세요.");
        }

        if ((userNumber.charAt(0) == userNumber.charAt(1)) || (userNumber.charAt(0) == userNumber.charAt(2)) || (userNumber.charAt(1) == userNumber.charAt(2))) {
            throw new IllegalStateException("서로 다른 임의의 수 3개를 입력해주세요.");
        }

        return rightInput = 1;
    }

    public static void inputNumber() {
        while(rightInput == 0) {
            System.out.print("숫자를 입력해주세요 : ");
            Scanner scanner = new Scanner(System.in);
            userNumber = scanner.nextLine();
            checkInput();
        }
        rightInput = 0;
    }

    public static String checkOverlap(String createdNumber, int index) {
        if (index == 0) {
            return createdNumber;
        }

        if (createdNumber.charAt(index - 1) == createdNumber.charAt(index)) {
            return createdNumber.substring(0, index);
        }

        return createdNumber;
    }

    public static String createRandomNumber() {
        Random random = new Random();
        String createdNumber = EXPRESSION_DELIMETER;

        while (createdNumber.length() < NUMBERLENGTH) {
            createdNumber += Integer.toString((random.nextInt(8) + 1));
            createdNumber = checkOverlap(createdNumber, createdNumber.length() - 1);
        }

        return createdNumber;
    }

    // 중복 수정 필요
    public static int compareNumber(int index) {
        if ((randomNumber.charAt(0) == userNumber.charAt(index)) && index == 0) {
            return STRIKE;
        }

        if ((randomNumber.charAt(0) == userNumber.charAt(index))) {
            return BALL;
        }

        if (randomNumber.charAt(1) == userNumber.charAt(index) && index == 1){
            return STRIKE;
        }

        if ((randomNumber.charAt(1) == userNumber.charAt(index))) {
            return BALL;
        }

        if (randomNumber.charAt(2) == userNumber.charAt(index) && index == 2){
            return STRIKE;
        }

        if ((randomNumber.charAt(2) == userNumber.charAt(index))) {
            return BALL;
        }

        return NOT_EXIST;
    }

    public static void applyResult(int result) {
        if (result == 0) {
            ballCount++;
        }

        if (result == 1) {
            strikeCount++;
        }
    }

    public static void checkResult() {
        ballCount = 0;
        strikeCount = 0;
        int resultOfIndex;

        for (int i = 0; i < 3; i++) {
            applyResult(compareNumber(i));
        }
    }

    public static void output() {
        if (ballCount == 0 && strikeCount == 0) {
            System.out.println("낫싱");
        }

        else if (ballCount == 1 && strikeCount == 0) {
            System.out.println("1볼");
        }

        else if (ballCount == 2 && strikeCount == 0) {
            System.out.println("2볼");
        }

        else if (ballCount == 3 && strikeCount == 0) {
            System.out.println("3볼");
        }

        else if (ballCount == 0 && strikeCount == 1) {
            System.out.println("1스트라이크");
        }

        else if (ballCount == 0 && strikeCount == 2) {
            System.out.println("2스트라이크");
        }

        else if (ballCount == 0 && strikeCount == 3) {
            System.out.println("3스트라이크");
            System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 종료");
            answer = 1;
        }

        else if (ballCount == 1 && strikeCount == 1) {
            System.out.println("1볼 1스트라이크");
        }

        else if (ballCount == 2 && strikeCount == 1) {
            System.out.println("2볼 1스트라이크");
        }
    }

    public static void numberBaseBall() {
        while(answer == 0) {
            inputNumber();
            checkResult();
            output();
        }
    }

    public static void returnGame() {
        System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.");
        Scanner scanner = new Scanner(System.in);
        recursion = scanner.nextInt();
        answer = 0;
        rightInput = 0;
    }

    public static void main(String[] args) {
        // TODO : 기능 구현
        while(recursion == 1) {
            randomNumber = createRandomNumber();
            numberBaseBall();
            returnGame();
        }
    }
}

테스트의 중요성과 테스트를 하는 방식에 대해서는 설명을 했지만, 테스트를 하기 위해서 어떤 구조를 가져가야 하는지에 대한 설명이 부족했다. 따라서, 테스트를 하나도 하지 않고 미션을 제출하는 결과를 초래했다. 급하게 따로 연락을 해서, 설명을 덧붙혔다. 아래와 같은 방법을 제시해주었다.

  • 하나의 클래스에 모든 로직을 두지 마라
  • 입출력 로직과 비즈니스 로직을 분리해라
  • 기능 단위로 최대한 클래스를 쪼개고 여러 클래스의 메서드 호출을 통해서 프로그램을 동작하게 해보아라

생각보다 리뷰할 것이 많았다. 접근제어자도 public으로 다 열려있고, 네이밍들은 뜻을 파악하기 힘들었다.

https://naver.github.io/hackday-conventions-java java 컨벤션을 제공해줬음에도 코드가 제각각이었다.

기본적으로 세션에서 설명한 명확한 네이밍, 상수 분리 등을 제외하고 아래와 같은 공통 피드백을 주었다.

숫자야구 미션 공통 피드백

1. 접근 제어의 범위를 최소화하자.

불필요하게 public으로 되어 있는 접근제어자들을 다 private으로 바꾸자.

테스트를 하기 위해서 메서드를 public으로 만드는 것도 절대 안된다.

2. 불필요한 객체 생성을 없애자.

public class Again {
    public int returnGame() {
        System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.");
        Scanner scanner = new Scanner(System.in);
        return scanner.nextInt();
    }
}

returnGame 메서드를 호출할 때 마다, Scanner 객체가 생성된다.

public class Again {

    private final Scanner scanner = new Scanner(System.in);

    public int returnGame() {
        System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.");
        return scanner.nextInt();
    }
}

위처럼 변경하면, returnGame이 100번 호출되어도 Scanner 객체는 한번만 생성된다.

3. 적절한 예외를 사용하자.

/**
 * Thrown to indicate that a method has been passed an illegal or
 * inappropriate argument.
 *
 * @author  unascribed
 * @since   1.0
 */
public
class IllegalArgumentException extends RuntimeException {
    /**
     * Constructs an <code>IllegalArgumentException</code> with no
     * detail message.
     */
    public IllegalArgumentException() {
        super();
    }

어떤 예외가 어떤 상황에 쓰이는지 찾아보자. 특정 클래스에 커서를 두고 Cmd + B (Mac), Ctrl + B (Windows)를 누르면 해당 클래스의 선언부로 이동할 수 있다. 들어가서 설명을 읽어보자. 꼭!

4. 반복적인 행위를 줄이자

예시)

Input 클래스에서 사용자 입력을 String으로 받고 split으로 배열로 만들고 검증을 한다. 그리고 나중에 계산하는 과정에서 사용자 입력 String을 split으로 자른다.

Input에서 split으로 잘랐으면 Input에서 배열을 반환하고 사용자 입력에 대한 split을 또 할 필요 없도록 하자

5. Constant 클래스에 모든 상수를 정의하지 말자.

한 클래스에서만 사용되는 상수면 해당 클래스에 정의하자. 상수 클래스가 분리되어 있으면, 상수값이 뭔지 찾기 위해서 계속 클래스 이동을 해야한다.

또한, 나중에 다시 설명하겠지만 객체지향의 개념에 어긋난다. 특정 클래스가 기능을 수행하기 위해 필요한 값은 특정 클래스가 보관하고 관리해야한다.

진짜 모든 것에서 통용적으로 사용될 수 있는 것만 사용하자.

6. 문자의 유효성 검증은 정규표현식을 이용해보자.

https://hbase.tistory.com/160

7. 상수와 변수 사이에는 개행을 하자

public class UserInput {
    private static final String REPLAY_VALUE = "1";
    private static final String QUIT_VALUE = "2";

    private final Scanner scan = new Scanner(System.in);
    private final InputValidator validator = new InputValidator();
    private final Display display = new Display();
}
public class UserInput {
    private static final String REPLAY_VALUE = "1";
    private static final String QUIT_VALUE = "2";

    private final Scanner scan = new Scanner(System.in);
    private final InputValidator validator = new InputValidator();
    private final Display display = new Display();
}

스터디원들의 느낀점

  • 기능 구현은 어렵지 않았는데, 조건들을 지키는게 힘들었다
  • java가 어색했다.
  • 코드를 깔끔하게 치는게 어려웠다
  • 기능을 쪼개서 독립적으로 클래스를 만드는게 어색했다
  • 클래스를 쪼개면서 테스트를 해야하는데, 개발할 때 테스트코드를 작성하는 부분이 어려웠다

확실히 depth 2, 메서드 라인 15 등의 조건들이 처음이라 어색하게 느낀 것 같다. 또한, 테스트를 위해서 기능을 클래스로 쪼개는 과정이 있었는데, 보통 한 클래스에 모든 로직을 다 넣어왔기 때문에 이역시도 어색하게 느낀 것으로 보인다.

테스트 코드 역시 처음 작성하는 것이기 때문에, 어려워했다. assertJ와 JUnit과 같은 라이브러리를 처음 사용해 본 것도 이유일 것이다.

숫자야구미션을 통해 내가 느낀점

스터디원들이 테스트코드의 필요성에 대해서 이론적으로 이해한 것 같다. 사실, 근거 없이 Test Code 작성을 시키는 것이 아니라 테스트 코드를 작성해야 하는 이유에 대해 이해시켜야한다고 생각했다. 사실 이보다 규모있는 어플리케이션을 개발한다면 이론적인 이해를 넘어서 필요성을 경험으로 느낄텐데.. 라는 생각이 들었다. 테스트 코드를 작성하면서 개발하는 습관을 들이면 언젠간 느끼겠지~ 싶었다.

Clean Code에 대한 개념이 조금은 잡힌 것 같다. 아직 명확하진 않지만, 네이밍을 잘 하려는 노력이 조금씩 보였다.

이제 다음 미션이면 OOP에 대한 개념을 약간 맛보게 해야하는데, 어느 정도의 수준을 고민했다.

과거에 함께 공부했던 동기(최지환)가 1명의 리뷰를 맡아서 해줘서 미션당 3명의 리뷰를 진행한다. 그런데, 생각보다 시간이 많이 든다.. 참여한 인원들이 믿고 시간을 할애하면서 참여를 한 것이기 때문에 더 책임감이 느껴져서 더 시간이 많이 드는 것 같다. 뭐 어쩌겠냐 내가 벌인 일이다. 하루에 유튜브 좀 덜보면 되지


두번째 미션 : 🚗 자동차 경주 🚗

미션 저장소

https://github.com/GlobalMedia-BackEnd-Hallae/java-racingcar

제출된 PR

https://github.com/GlobalMedia-BackEnd-Hallae/java-racingcar/pull/1

https://github.com/GlobalMedia-BackEnd-Hallae/java-racingcar/pull/2

https://github.com/GlobalMedia-BackEnd-Hallae/java-racingcar/pull/3

https://github.com/GlobalMedia-BackEnd-Hallae/java-racingcar/pull/4

핵심 주제

  • OOP 첫걸음 내딛기
  • MVC 패턴
  • Java Collection
  • Value Object

OOP에 대한 개념 입문이 목표였다. 사실 나는 객체지향이란 것이 처음에는 상당히 추상적으로 다가온다고 생각한다. 따라서, 하나의 객체가 하나의 기능만 수행하도록 하자. 객체가 기능을 수행하기 위해 필요한 값은 객체 내부에서 관리하도록 하자, 객체 지향 생활체조 등의 몇가지 팁(?)만을 줬다. 또한, 처음부터 OOP의 많은 개념들을 알려주기 보다는 미션을 하고 코드를 쳐가면서, OOP 서적을 읽으면서 본인만의 객체지향 개념을 잡아가는 것이 좋다고 생각했다.

기억에 남는 질문

잘 돌아가는 코드인데, 테스트를 위해서 구조를 바꾸는 것이 맞나?

구현을 할 때, 테스트에 대한 생각을 해서 테스트를 위한 구현이 되는 것 같다.

나의 답변

테스트를 위해서 설계를 바꾸는 것이 아니라 좋은 설계는 테스트하기도 좋은 것이다. 만약, 해당 코드가 테스트하기가 어렵다면 적절하지 않거나, 강한 의존이 존재하거나 변경에 유연하지 못하는 등의 문제가 있는 것이다.

사용자 입력이 강하게 결합되어 있는 객체에 대한 테스트로 이를 증명했다. 테스트를 위해서 사용자 입력 부분과의 느슨한 결합을 맺게하는 것이 아니라, 느슨한 결합을 맺고 나니 테스트 하기가 좋은 구조라는 것을 말이다.

VO를 불변으로 사용하면 좋은 것으로 공부했는데, DTO도 불변으로 사용하면 되지 않나?

나의 답변

본인이 사용하고 싶으면 사용해라. 하지만, VO와 DTO의 정의에 대해서 다시 생각해보면 좋을 것 같다. 그리고, DTO는 setter를 정의할 수 있다고 해도, DTO의 값을 바꿀 일이 거의 없다. 값을 생성자로 담아서 전달하는 이상의 행위는 해본 적이 없다.

또한, 모든 객체를 불변으로 가져갈 수 있을까? 에 대한 고민을 해보기 바란다. 객체를 생성하는 것도 비용이다. 어디까지 불변으로 가져갈지에 대한 본인의 기준이 필요할 것 같다.

테스트 코드의 양이 구현양보다 많은 것 같다.

나의 답변

초기에 개발을 해나갈 때는 테스트 코드 때문에 개발이 늦어질 수가 있다. 지금 그렇게 느낄 것이다. 하지만, 어플리케이션의 덩치가 커지고 추후에 리팩토링, 유지보수를 하면서 테스트 코드의 필요성을 더더욱 느낄 것이다.

예시를 들자면, A - Z를 개발하고 B는 A를 참조하고, C는 B를 참조하고 D는 C를 참조하는 방식이 Z까지 이어지는 구조라고 생각해보자. Z까지 개발을 했다. 그런데, 개발을 끝내고 나니 버그가 발생했다. 어디서 문제가 발생했는지 빠르게 찾을 수 있을까? 단위 테스트가 있다면 문제가 어디서 발생했는지 빠르게 찾을 수 있을 뿐더러, 개발 하면서 내가 개발을 잘 해나가고 있다는 검증을 받을 수 있다.

MVC에서 비즈니스 로직을 Model로 최대한 넣는다고 공부했는데, 사용자 입력도 프로그램에서 중요한 비즈니스 로직 아닌가?

나의 답변

개인적인 생각이지만, 사용자 입력은 중요하지만 여기서 말하는 비즈니스 로직에는 해당하지 않는다. 비즈니스 로직이란 사용자가 원하는 기능을 제공하기 위한 로직이다. 사용자는 어떤 값과 함께 요청을 하기도 한다. 해당 값과 함께 혹은 값 없이 어떤 기능을 프로그램에 요청을 했을 때, 규칙, 정책 등에 따라 어떤 값 혹은 예외를 반환해줄지에 대한 로직 그리고 그 과정이 비즈니스 로직이라고 생각한다. 사용자 입력은 그냥 사용자 입력 그 자체이다.

공통 피드백

DisplayName을 명확하고 구체적으로 작성하자

테스트 코드는 프로덕션을 검증하기 위한 목적도 있지만, 객체가 하는 일을 보여주는 명세의 역할도 한다.
따라서, DisplayName을 명확하고 구체적으로 작성하면 객체의 내부 구현을 보지 않고 테스트 코드만 보더라도 어떤 책임을 지녔는지 파악할 수 있다.

경계값을 테스트하자

특정 객체를 1 ~ 5 사이의 자연수로 생성할 수 있다고 가정해보자. 옳지 않은 범위로 객체를 생성할시에 예외가 발생하는 테스트를 작성한다고 했을 때, 경계값은 0과 6이다. 만약 테스트케이스를 7로 잡았다고 가정해보자. 내부 구현을 1 ~ 5이어야 하는데, 1 ~ 6이 되었다고 하면 7이라는 숫자로는 테스트가 통과할 것이고 확실하게 검증할 수 없다. 또한, 명세의 측면에서도 0과 6을 테스트 값으로 설정한다면, 해당 객체는 1 ~ 5 범위만을 허용한다고 바로 파악할 수 있다.

객체는 데이터를 담는 일 그 이상을 할 수 있다. 해야 한다.

public class Car {

    private final String name;
    private int position = 0;

    public Car(String name) {
        this.name = name;
    }
}

위와 같은 객체를 값을 담는 저장소라고 생각하지 말자. 객체는 자신이 가진 값으로 사용자에게 필요한 기능을 적극적으로 제공하고 협력하는 객체이다. 외부에서 Car의 값을 getter로 얻은 후 작업하지 말고, Car의 name과 position으로 필요한 행위가 있다면 Car의 메서드로 요청하자.

의미있고 명확한 네이밍을 하도록 더 노력하자

접근제어자를 신경쓰자

리팩토링을 하다보면 사용하지 않는 메서드가 생긴다. 잘 확인하자

운영적 개선 사항

첫번째 미션은 일주일 기간이라는 짧은 기간이었기에 운영적인 아쉬움이 보이지 않았다. 하지만, 이번 미션부터 아쉬운 점이 보이기 시작했다.

1. 강제성

원래 스터디에 강제성을 최대한 배제하려고 했었다. 하지만, 자동차 미션을 진행해보니 스터디원들의 성장과 스터디의 효율을 위해서 어느 정도의 강제성을 부여해야겠다는 생각이 들었다.

1-1. 리뷰 요청

자동차 경주 미션에 10일의 시간, 최소 3번의 리뷰를 할당해두었다. 미션 시작일로부터 4일..이 지난 후에야 첫 PR이 올라오기 시작했다. 첫 PR이 늦어지니까, 바로 리뷰를 줘도 미션이 마무리 될 때 상당히 다급하게 마무리 되는 느낌이다. 사실 아직, 미션들이 완벽하게 정리 되지 않았다.

따라서, 다음 미션부터는 1, 2, 3차 리뷰 요청에 대한 기한을 정해두기로 했다.

1-2. 토론과 공유 활성화

토론과 공유를 활발하게 진행할지 알았는데, 생각보다 그렇지 않았다. 이 시간을 거의 QnA 시간으로 보냈다.

일단, 적어도 1주일에 1개씩은 공부한 내용을 문서화해서 공유하도록 강제했다. 이전에 내가 코드리뷰를 받았을 때, 스스로 학습을 확장시키기가 쉽지 않았다. 리뷰어들이 던져주는 키워드들을 공부하곤 했는데 되게 그 시절에 되게 좋았던 학습의 길이었다. 따라서, 정리하거나 공부하면 좋은 키워드와 내용들을 리뷰를 통해 제공해주려고 한다. 그리고 각자 공부한 내용들을 공부하고 공유하도록 하려고 한다!

2. 진도(?)

2-1. TDD

문자열계산기와 자동차경주 미션을 끝내고, 이제 로또 미션으로 들어간다. 로또 미션에서 TDD 방식으로 개발하게 하려고 했는데, 힘들 것 같다는 생각이 든다. 아직, 단위테스트를 명확하게 짜는 연습이 되지 않아서 TDD 방식으로 당장 개발하기 힘들 것이라고 판단했다. 스터디를 끝내기 전에 TDD 방식으로 개발하는 것을 도입하려고 했는데, 단위테스트를 잘 짜는 것으로 마무리를 해야할지도 모르겠다.

2-2. 마지막 미션

문자열 계산기와 자동차 경주 미션이 끝났는데, 뭔가 아직 해당 미션들이 정리되지 않은 느낌이다. 마지막 미션으로 블랙잭 미션을 두었는데, 미션이 난이도가 있기에 스터디원들이 어려워할 수도 있겠다는 생각이 들었다. 차라리, 마지막 미션의 기간에 이전까지의 미션들을 쭉 한번 리팩토링 하는 과정을 가져보면 어떨까 하는 생각이 든다. 했던 걸 다시 해야해서 싫증이 나려나..

개인적인 느낀점

생각보다 시간이 많이 든다. 사실 할게 많다.. 올해 5월에는 졸업작품전시회를 위한 어플리케이션을 하나 만들어야한다. 졸업작품에 이전 프로젝트에서 아쉬웠던 점이나 못해봤던 것들을 적용하기 위해서 개인적으로 공부할 것도 많다. 여기에 일을 쏟으면서 내가 진짜 일을 벌이긴 벌였구나 라는 생각이 든다.

그런데, 신기한건 그런 생각을 하면서도 **후회하는 감정을 정말 느끼지 않았다.** 내가 공부한 것들을 전달하고, 코드 리뷰를 하고 리뷰를 통해 개선되는 스터디원들의 코드를 보면 재밌다. 스터디 모집 전으로 돌아가도 당연히 스터디를 진행할 것이다.

어떻게 하면 스터디를 연 목적을 더 잘 달성할 수 있을까?

4개의 댓글

comment-user-thumbnail
2023년 1월 25일

와 멋싯네요 크리스

1개의 답글
comment-user-thumbnail
2023년 1월 25일

므찌다 병주

1개의 답글