본문 바로가기

후기/강의 후기

[3주 차 회고] 코드스쿼드 2022 프리코스 (feat. 코드스쿼드)

728x90

- 총 4주 과정 (22.10.30 ~ 22.11.25)

- 100% 온라인 진행

- 수강료 20만 원

3주 차 학습일지 (22.11.13  ~ 11.19)


-  상속과 다형성 / 미션 #1 (백화점 멤버십 프로그램, 커피 만들기 프로그램 <데코레이터 패턴>)

-  3주 차 알고리즘 문제

-  추상 클래스와 인터페이스 / 미션 #2 (게임 플레이어 레벨 문제, 성적에 산출 프로그램 2)

-  추가미션 #3-1문제  &   #3-2문제

 

 

3주 차는 미뤄둔 '알고리즘 문제'를 푸는 것으로 시작했다. 특히 2주 차 알고리즘 문제론 프로그래머스 문제들이 출제됐는데, 한 개를 제외하곤 과거 모두 풀어봤던 거였다. 인형 뽑기 알고리즘 문제였는데 그냥 봐선 복잡해 보여 걱정을 했지만 침착하게 문제를 뜯어보니 Stack과 2차 배열만 알면 무난하게 풀 수 있었다. 이외의 문제들도 다시 한번 더 풀어보면서 메서드를 분리하는 리팩터링 연습을 했다. 

 

 

첫 번째 미션은 백화점 멤버십 프로그램과 커피 만들기 프로그램을 만드는 것이다. 두 미션 모두 상속을 이용해야 했다. '백화점 멤버십 프로그램'은 과거 박은종 멘토의 수업을 들었던 적이 있는데 그때 풀어봤던 거라 수월했다. 특히 캡슐화에 신경을 썼는데 이후 멘토링 시간에 공개 피드백을 받으면서 칭찬을 받았다. 고객 객체를 생성할 때 생성자의 매개변수로 정보를 받는 즉시 필요한 메서드가 수행되어 필요한 변수에 저장되도록 만들었는데 이 부분은 내가 봐도 만족하는 코드여서 뿌듯함이 샘솟았다.

 

public class Customer {
    private String customerName;
    private Member membership;
    private int totalShoppingAmount;
    private int pointScore;
    private int totalParkingFee;

    public Customer(String customerName, Member membership, int shoppingAmount, int stayingTime) {
        this.customerName = customerName;
        this.membership = membership;
        totalShoppingAmount = calculateTotalShoppingAmount(shoppingAmount);
        pointScore = calculatePoint(shoppingAmount);
        totalParkingFee = calculateTotalParkingFee(stayingTime);
    }

    private int calculateTotalShoppingAmount(int shoppingAmount) {
        return membership.applyDiscountRate(shoppingAmount);
    }

    private int calculatePoint(int shoppingAmount) {
        return membership.applyPointEarningRage(shoppingAmount);
    }

    private int calculateTotalParkingFee(int stayingTime) {
        return membership.calculateParkingFee(stayingTime);
    }

    public String showCustomerInfo() {
        return String.format("%s님의 지불 금액은 %d원이고, 적립 포인트는 %d점 입니다.\n" +
                "주차 요금은 %d원 입니다.\n", customerName, totalShoppingAmount, pointScore, totalParkingFee);
    }

    public void callCounselor() {
        membership.assignCounselor(customerName);
    }
}

 

그리고 스스로 고민하는 과정을 겪으니 한층 성장하는 것 같았다.

'커피 만들기 프로그램'은 '데코레이터 패턴' 태그가 큰 힌트로 작용했다. 데코레이터 패턴이 무엇인지 몰라 유튜브에 검색해서 찾아봤는데 너무 설명이 잘 되어 있는 영상을 발견했다. 한번 쓱 보니 이해가 되면서 해당 미션에도 적용할 수 있을 것 같았다. 실제로 똑같이 작성하니깐 잘 동작됐다. 데코레이터 패턴의 작동 원리는 아직 이해가 안 되지만 어떤 상황에서, 왜 사용하는지를 배웠다. 이 패턴도 많이 사용하면 점차 익숙해지겠지.

첫 번째 미션에서 자신감 상승 + 새로운 지식 덕분에 기분 좋게 마무리 지을 수 있었다.

 

 

두 번째 미션의 하이라이트는 바로 '성적 산출 프로그램'이다. 지난주 성적 산출 프로그램을 구현하면서 굉장히 많은 시간을 소비했음에도 결국 실패했기 때문이다. 당시 해당 성적 산출 프로그램을 업그레이드하는 미션이 있을 거라고 예고했었는데 바로 이번에 등장했다. 파일 입출력부터 파싱까지 다양한 어려움이 있었지만 무엇보다 클래스 간의 관계 설정이 최고 난이도였다.

 

처음에는 학생(Student) 클래스를 만들고, 멤버 변수로 성적(int), 과목(String)을 받아 처리하도록 생각했다. 그러나 이렇게 하면 과목에 필요한 기능(메서드)을 사용할 수 없는 한계가 있었고, 그래서 아예 과목(Subject) 클래스를 만들어 학생(Student) 클래스의 멤버 변수로 넣었다.

 

public class Student {
    private int studentId;
    private String studentName;
    private List<Score> scoreList;
    private Subject major;
    private List<Subject> testScoreList;
}

 

그렇게 하니 "만약에 한 학생이 다양한 과목(Subject)을 듣는다면?"와 같이 또 고민이 생겼고, 꼬리에 꼬리를 물어가며 자문자답이 이어졌다. 그러다가 멘토링 시간에 박은종 멘토가 점수(Score) 클래스를 따로 만들어 관리하는 것을 권유했다. 오잉? 도무지 이해가 안 됐다. 점수 클래스를 따로 만든다면 학생 클래스, 과목 클래스와 어떻게 관계를 맺어야 하지? 한 학생의 과목 점수를 추출하고 싶을 땐 어떻게 접근해야 하지? 머리가 터질 듯 복잡했다. 결국 첫 번째 시도에선 끝을 내지 못했다.

 

다시 돌아온 미션 앞에 긴장감이 맴돌았다. 지난번처럼 결과물을 만들지 못하고 헤매면 어쩌지 걱정이 앞섰다. 심기일전하는 마음으로 아예 처음부터 코드를 다시 작성했다. 일단 Student, Score, Major 클래스를 따로 만들어 어떻게 관계를 맺게 할 것인지를 고민했다. 결국 Student 클래스에 Score와 Major 클래스를 멤버 변수로 둬서 해당 학생의 객체 내에 저장된 정보들을 이용하는 방식으로 만들었다.

 

public class Student {
    private String studentName;
    private String studentId;
    private Major major;
    private Score score;

    public Student(String studentName, String studentId, String majorName, int... scores) {
        this.studentName = studentName;
        this.studentId = studentId;
        getMajorInstance(majorName);
        getScoreInstance(scores);
    }
}

 

이렇게 하는 게 객체지향적으로 작성하는 건가? 내면에 끊임없는 의구심이 생겼다. 그래도 논리적으로 말이 되는 것 같아 뿌듯하면서도 한편 비효율적인 코드라는 의혹이 사라지지 않았다. 그래도 최선을 다했다. 조건에 맞는 인스턴스를 생성해서 그 인스턴스의 메서드를 사용할 수 있도록 구현했다. 여기서 궁금했다. if 문의 반복을 막고 확장성을 위해 인터페이스를 구현하는 것인데, 조건에 맞게 각기 다른 객체를 생성하려면 결국 if 문을 무한정 추가해야 하고, 이는 확장성에 취약해 보였다.

 

// 단적인 예로 아래 코드는 if 문에 의존하기 때문에 전공이 늘면 if 문도 늘어나야 한다
private void getMajorInstance(String majorName) {
    if (majorName.equals("국어국문학과")) {
        major = new KoreanAndLiteratureMajor(majorName);
        return;
    }
    if (majorName.equals("컴퓨터공학과")) {
        major = new ComputerEngineeringMajor(majorName);
    }
}

// 아래 코드도 동일하게 if 문에 의존한다. 코드가 너무 지저분하다
public String getGrade(String subjectName, String compulsorySubject) {
    int score = getScore(subjectName);
    if (compulsorySubject.equals(subjectName)) {
        gradeEvaluation = new MajorEvalution();
    } else if (subjectName.equals(String.valueOf(SubjectName.방송댄스))) {
        gradeEvaluation = new PassOrFailEvaluation();
    } else {
        gradeEvaluation = new BasicEvaluation();
    }
    return gradeEvaluation.getGrade(score);
}

 

일단 기능 구현이 우선이기 때문에 정답을 찾지 못하고 넘어갔다. 이럴 때 '멘토'의 피드백이 절실히 필요하다고 생각했다. 내 코드의 비효율을 지적하고 개선점방향을 알려주는 역할이 정말 중요하다는 사실을 깨닫는 순간이었다. 혼자서는 아무리 고민해도 뚜렷한 답을 찾기가 힘들기 때문이다. 일단 박은종 멘토에게 코드 리뷰를 요청해서 기다리는 중이다.

 

 

이런저런 우여곡절이 많았지만 기능 요구사항을 충족한 업그레이드 성적 산출 프로그램을 만들었다! 정말 뿌듯하더라. 이와 별개로 우리 팀원 중 한 명의 코드를 볼 수 있었는데 충격을 받았다. 바로 내가 구현하고 싶었지만 어떻게 할지 몰라 포기했던 방식으로 만든 것이다. 즉 학생(Student), 성적(Score), 과목(Major) 클래스를 따로 구현하고 각자 정보를 연결할 '공동 키'를 id로 가지게 한 것이다. 여기서 중요한 건 학생, 성적, 과목마다 각각의 인스턴스를 만든다는 점이다. 하지만 이들을 하나의 자료구조에 모으고 또 호출하는 공간은 어디로 설정해야 하나? 이 지점에서 명확히 이해되지 않았다. MVC 패턴으로 패키지와 클래스들을 구조화한 만두의 코드를 보고, MVC 패턴이 지금까지 고민해오던 문제를 해소시켜줄 열쇠일 수도 있겠다는 생각을 했다. 내 사고가 한층 확장되는 경험을 한 것이다.

 

 

클린 코드, 클래스 간의 구조 설계에 더 큰 관심을 가지게 된 한 주였다.

멘토와의 대화


클래스의 초기화 시점과 로딩 시점은 다르다. 처음 main 함수를 실행하면 main 함수를 가지고 있는 클래스가 로딩되고 초기화된다. 따라서 main 함수가 실행되지 전에 이미 해당 클래스를 거치는 과정이 존재한다. 

 

자바 7까지 static 전역 변수의 저장 위치는 Heap 영역으로 82MB 고정 크기로 JVM이 관리했다.  

   자바 8부터 Native Memory로 위치가 바뀌었다. 그 이유는 크기의 제한을 없애기 위해서다.

 

-  코드를 재사용한다면 상속을 사용해선 안된다. 포함(composition)의 has-a 관계를 사용해라.

 

-  포함 관계에선 다른 객체를 매개변수 또는 멤버 변수로 사용할 수 있다.

 

-  변수 또는 메서드를 호출할 때 중요한 건 해당 객체가 어떤 타입으로 선언되었는가 다.

 

-  오버 라이딩을 했을 때 하위 메서드가 호출되는 이유는 '가상 메서드'의 원리 때문이다.

새롭게 고민한 것


-  인터페이스의 디폴트 메서드 사용법  :  메서드 앞에 'default' 키워드를 붙인다.

 

-  데코레이터 패턴  :  기능 확장할 때 사용, 동적으로 기능을 추가할 수 있다. 즉 '진짜'를 두고 추가되는 형식이다.

    자바 I/O 구현이 대표적인 예시이며, 단점으론 순서에 민감할 수밖에 없어 사용자의 부주의에 취약하다.

 

-  템플릿 패턴

 

-  클래스 로더

 

-  컴포지션의 개념과 상속과의 차이

 

-  비즈니스 로직과 MVC 패턴

 

-  가상 메서드의 원리

느낀 점


이번 과정을 겪으며 '프로그래밍'의 매력을 알게 됐다. 코드로 세상의 모든 사물 또는 시스템을 구현할 수 있다는 사실이 흥미롭게 다가왔다. 한 번은 도박 게임이 주제인 만화책을 봤는데 과거와 다르게 이번에는 "어? 이 게임을 코드로 구현하면 재미있겠다!" 생각이 번뜩였다. 사물을 인식할 때 프로그래머의 시점으로 바라보라는 조언이 떠오르는 순간이었다. 이번 3주 차 미션은 어려웠지만 해냈고 그만큼 성취감이 크다. 내 부족함을 목격했을 때도 좌절감보다 오기가 생겼다. 굉장히 고무적인 3주 차였다.