728x90

4장 클래스와 인터페이스

이펙티브 자바를 TDD, Clean Code 과정을 수강하면서 들으니 이해가 잘되면서 잘 읽힌다❗

클래스와 멤버의 접근 권한을 최소화

ㅋㅋㅋㅋㅋㅋㅋㅋ 어제 Clean Code 과정에서 뼈저리게 겪었다...

랜덤값 부터 시작해서 어설프게 설계된 컴포넌트와 잘 설계된 컴포넌트의 차이는 클래스 내부 데이터를 각각 다른 외부 컴포넌트로부터 얼마나 추상화 하였는가가 결정 짓는다.

잘 설계되었다면 구현부, API를 깔끔하게 분리할 수 있다.
MVC 패턴만 사용했어 분리하는게 조금 어려웠다. 그런데 진도 나가면서 점점 발전하는것 같다. 😁

이 챕터에서는 정보 은닉(캡슐화) 의 장점에 대해 설명하고 있다.

  • 시스템 개발 속도를 높인다.
    • 여러 컴포넌트를 병렬로 개발 가능하기 때문
  • 시스템 관리 비용을 낮춘다.
    • 다른 컴포넌트로 교체하는 부담이 적어진다.
  • 성능을 높여주진 않지만, 성능 최적화에는 도움을 준다.
    • 다른 컴포넌트에 영향을 주지 않고 해당 컴포넌트만 최적화할 수 있음.
  • 소프트웨어 재사용성을 높인다.
  • 큰 시스템을 제작하는 난이도를 갖춰준다.

접근 제어자(private, public, protected)

접근 제어자를 잘 써야한다.
사실 이제껏 private, public 두개만 사용했지 protected는 활용 빈도가 지극히 낮았던게 나의 코드였다.

여기서 말하는 원칙을 준수하려면 모든 클래스와 멤버의 접근성을 가능한 좁혀야 한다.

접근 범위가 좁은 순서

  • private : 멤버를 선언한 가장 바깥의 클래스에서만 접근 가능
  • package-private: 멤버가 소속된 패키지 안의 모든 클래스에서 접근할 수 있다.
    • 접근 제한자를 명시하지 않았을 때 적용되는 접근 수준(interface는 public)
  • protected: package-private의 접근 범위를 포함하며 상속받는 하위 클래스에서도 접근할 수 있다.
  • public: 그냥 다 접근 가능

근데 나는 여기중에서 package-private를 쓰지않고 도메인을 더 분리하여 일급 컬렉션을 사용하면서 호출하는식으로 관리하는 것이 더 좋을 것 같다는 생각.

결국 public을 많이 사용하면 할수록 점점 코드가 뚱뚱해진다.

🤷‍♂️ 그렇다는건 또 분리를 해야한다는 것 ❗❗❗

접근 제어자 범위를 늘릴 때,
테스트를 하기 위해서 접근 제어자의 범위를 늘려주는 것은 하나의 방법이 될 수 있겠지만
인터페이스를 사용해서 값을 할당하는 strategy 패턴을 고려하는게 좋다고 생각한다.
이것도 뼈저리게 느낀 이번챕터..

public 클래스의 인스턴스 필드는 되도록 public이 아니어야 한다.
public 가변 필드를 갖는 클래스라면 스레드 안전하지 않다. 그 클래스는 이 필드를 통제할 통솔력이 없다. 밖에서도 참조가 가능하기 때문에 불변을 보장할 수도 없다.
상당히 좋지 않은 방식... 그래서 DTO를 사용할 때 다 private로 기본 가져가는것이 이유중 하나일 수 있다.

이 책에서는 상수용public static final 외에는 public 필드를 가져서 안된다고 하는데 내가 생각할때는 이 마저도 남용할 수 있는 우려가 있기 때문에 private로 바꿔서 해당 클래스에서만 사용하게 해야 좋은 것 같다. 사용 빈도가 많다면 Enum을 만들어서 상수를 관리하자.

public 클래스에서는 public 필드 말고 접근제어자 메소드를 사용

이것이 dto에서 쓰는 기본 원칙이다.

class Class {
    public int x;
    public int y;
}

이러면

class Main {
    public static void main(String[] args) {
        Class class = new Class();
        class.x = 1;
    }
}

데이터에 직접 위의 코드처럼 할당이 가능해서 캡슐화의 이점을 사용할 수 없다.
그래서 필드를 private 변경하고 get, set 메서드를 사용한다.

class Class {
    private int x;
    private int y;

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }
}

이렇게 말이다.

불변 필드를 노출하게 되면 원하는 데이터가 정형화되어 나올 수 있는 환경이 파괴되기 때문에 접근제어자를 고민하면서 구현하자❗

728x90

'Java' 카테고리의 다른 글

[Java] 데이터타입, 변수, 배열  (0) 2022.08.06
JVM  (0) 2022.08.06
TDD Clean Code with Java 12기 3주차  (0) 2022.08.06
TDD Clean Code with Java 12기 2주차 피드백  (0) 2022.08.06
728x90

토이 프로젝트 관련

JPA 스터디를 하면서 배운 내용을 토대로 하여 Spring Data JPA를 더 유연하게 사용하기 위해 토이프로젝트를 진행했다.

이슈사항이 될 수도 있겠다 싶어서 삽질 아닌 삽질을 하다가 알게된 사실이다.

사실, 계속 궁금했다. 🤔

코드로 보면 아래와 같은 궁금증이었다.

@Test
void findById() {
    memberRepository.save(member);
    Optional<Member> result = memberRepository.findById(1L);

    result.ifPresent(m -> {
        assertThat(m.getName()).isEqualTo("홍길동");
    });
}
@Test
void findAll() {
    memberRepository.save(member);
    List<Member> list = memberRepository.findAll();
    assertThat(list.size()).isEqualTo(0);
}

이렇게 두 메서드 이다.

결과부터 보자면

findById

image

findAll

findAll

결과가 다르다. 아무 sql을 출력해주지 않는다.

이유는 findAll을 하면 해당 테이블에 있는 모든걸 가져와야 하니까 update를 해야한다.

이 트랜잭션 밖에서 무슨일이 벌어져서 해당 테이블에 무슨 변화가 있었는지 모른다.

그래서 실행 전에 update도 발생한다❗

현재 트랜잭션 안에서 벌어진 일도 반영하고 select를 해야 현재 가장 최신 상태의 데이터를 가져오는거니까 동기화를 하는 느낌? 이라고 생각하면 될것같다.

근데 findById는 한건에 대한 데이터고, 그 한 건이 현재 트랜잭션 안에서 변경중인 데이터이다. 그래서 해당 변경 사항은 이미 영속성 컨텍스트가 관리하는 중이었기 때문에 findAll처럼 updateselect가 발생할 필요없이, 영속성 컨텍스트 안에서 캐싱하고 있던 객체를 찾아주기 때문에 updateselect 모두 발생하지 않은것 처럼 보인다. 근데 해당 코드가 테스트가 아니라 일반 애플리케이션 코드였으면 트랜잭션 끝나고 update가 발생한다.

이게 왜 발생한다고 말을 할 수 있냐면 실제로 한 메서드 내의 트랜잭션에서 save를 빼고 다른 트랜잭션에서 save를 해주고 findById만 조회하면 또 잘나오게 된다.

JPA를 사용함에 있어서 트랜잭션 정말 중요하다는걸 또 한번 깨닫는다❗

데이터를 원하는 조건에 맞게 추출하게 로직을 구현하는건 중요하지만 그 전에 트랜잭션 관리가 최우선이다. 앞으로 더 참고해서 개발해보도록 하자 😁

728x90
728x90

재귀 알고리즘

개념을 먼저 익힌 후에 백준 알고리즘을 푸는식으로 해야겠다. 맨땅에 헤딩식으로 하면 나는 잘 이해가 안되는 것 같다. 천천히 하더라도 깊이있게 해보자❗

재귀란?

어떤 사건이 자기 자신을 포함하고 다시 자기 자신을 사용하여 정의될 때 재귀적이라고 한다.

바로 예제로 가보도록 하자

팩토리얼 구하기

재귀의 예시로 팩토리얼이 있다.
n!(팩토리얼) 은 n * n-1 * n-2 * ... 이다.
이것을 그대로 코드로 표현하면 다음과 같다.

import java.util.Scanner;

public class Factorial {
    static int factorial(int n) {
        if(n > 0)
            return n * factorial(n - 1);
        else
            return 1;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        System.out.println(n + "! = " + factorial(n));
    }
}

동작형식을 자세하게 보면 factorial(int n)은 n이 0보다 크면 n * factorial(n - 1) 를 호출하고 아니면 1을 반환한다.

풀어서 써보자.

예를 들어 n값에 3을 넣었다고 가정한다.

  • n = 3
    • 3 * factorial(2);
    • 2 * factorial(1);
    • 1 * factorial(0);
    • return 1

이 순서로 들어가게 될텐데,

맨 마지막의 1부터 1 * 2 * 3 이 되는 것이다.
호출은 n = 3부터 시작했으니 정렬이 되면 3 * 2 * 1이 되게 된다.

이렇게 해서 3! 의 답인 6을 얻게된다.

내가 재귀를 볼때 막힘없이 봐야 이해가 쏙 되는것 같은 느낌 😂

한번에 안읽히면 머릿속에서 무한루프를 도는느낌이다.

항상 끝부터 쭉 나간다음 생각해 보는것이 이해가 잘된다❗

재귀 안썼을 때

static int factorial(int n) {
        int count = 1;

        for(int i = n; i > 0; i--) {
            count *= i;
        }

        return count;
    }

유클리드 호제법

최대공약수를 재귀로 구할 수 있다.

두 정수를 직사각형 두 변의 길이라고 가정하면 최대 공약수를 구하는 문제는 다음과 같아질 수 있다.

직사각형을 정사각형으로 완전히 채우고, 만들 수 있는 정사각형의 가장 긴 변의 길이를 구하자

4와 22 의 최대 공약수를 구해보자고 가정하면

  1. 22 x 4에서 4를 한변으로 하는 정사각형으로 분할한다.
  2. 5개의 4 x 4 정사각형이 생기고 2 x 2 두개가 남는다.
  3. 이렇게 더이상 나눌 수 없는 2 x 2 정사각형이 생겼으므로 2가 최대 공약수이다.
public class Euclid {
    static int solution(int x, int y) {
        if(y == 0) {
            return x;
        }

        return solution(y, x % y);
    }

    public static void main(String[] args) {
        int x = 22;
        int y = 4;
        System.out.println(solution(x, y));
    }
}

단순하게 해석하면 0일때까지 계속 solution 자기 자신을 호출하면서 맨 마지막에 남은 x를 돌려주는 것이다.

재귀 안썼을 때

static int gcd2(int x, int y) {
        while (y != 0) {
            int temp = y;
            y = x % y;
            x = temp;
        }
        return x;
    }

배열 모든 요소의 최대 공약수

public class EucArray {

    static int euc(int x, int y) {
        while (y != 0) {
            int temp = y;
            y = x % y;
            x = temp;
        }
        return x;
    }

    static int eucArray(int[] arr, int index, int length) {
        if(length == 1) {
            return arr[index];
        }

        if(length == 2) {
            return euc(arr[index], arr[index + 1]);
        }

        return euc(arr[index], eucArray(arr, index + 1, length - 1));
    }

    public static void main(String[] args) {
        int[] arr = {3, 6, 8, 12, 15};
        System.out.println(eucArray(arr, 0, arr.length));
    }
}

막상 풀어보니까 이렇게 복잡한거는 재귀보다는 다른 방법이 나아보이는....

728x90

'CS' 카테고리의 다른 글

불 논리 회고  (0) 2022.08.07
HTTP  (0) 2022.08.07
불 논리 정리  (0) 2022.08.07
[알고리즘] 그리디  (0) 2022.08.04
728x90

포스팅이 늦었다. 3주차 미션인 사다리도 끝나게 되었다.

로또에서보다 난이도가 많이 올라간 느낌이었다.

리뷰어분이 빡세게 그리고 꼼꼼하게 해주신 덕분에 나 자체도 굉장히 성장한것 같다❗

아래는 깃허브 PR 목록이다.

사다리 1주차

사다리 2주차

사다리 3주차

사다리 4주차

테스트

전체적으로 테스트코드를 고민하다가 한번 로직에 손을 대면 저절로 도메인 위주로 구현을 하게 되었다.

테스트를 항상 생각하면서 그리고 테스트를 실행함으로 인해 로직을 구현해 나가야 하는것이 조금 부족했던 챕터였다.

그래서 중간에 리뷰를 받다가 너무 로직이 답답해 보였다.

읽고있던 이펙티브 자바를 접목시켜서 조금 더 나은 로직으로 개선했다.

로직개선으로 문제를 겪었었는데, 그 문제가 바로 절차지향으로 개발했기 때문에 문제였다.

모든 로직을 한군데에 구현해놓으니까 분리하기도 쉽지않고 어떻게 돌아가는지 명확하게 알 수도 없는 그런 로직이 완성되어 있었다.

이걸 일급 컬렉션으로 포장해주고, 모든 엔티티를 작게 유지한다 라는 조건을 생각하면서

개발하게 되니까 확실히 알기도 쉬워졌고, 유지보수성이 좋게 되었다.

무엇보다 인터페이스 그리고 람다에 대해 공부를 많이 해야겠다고 생각했다.

프로그램이 뭘 하는지 어떻게 해야하는지 생각하는 Out-In방식이 아니라

In-Out방식으로 최소한의 객체에서부터 출발하는 생각을 지속적으로 해야한다.

접근방식

처음에 내가 이 사다리를 놓고 접근한 방식은 다리와 세로 기둥을 같이 넣어서 구현하려고 했던게 문제였다.

그러니까 사다리 라는 큰 틀만 놓고 일차원적으로 생각한 결과가 이렇게 된 것이다.

여기가 갈아 엎은 부분

그래서 결국 생각해낸 것은 이라는 객체가 결국 이동해서 결과를 내주는 것인데

점부터 시작해서 왼쪽 오른쪽을 판단하게끔 로직을 구현하니까 점점 조금씩 큰 컬렉션으로 나가지면서 그에 대한 테스트도 조금씩 늘릴 수가 있게 되었다.

요구사항

참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)
pobi,honux,crong,jk

실행 결과를 입력하세요. (결과는 쉼표(,)로 구분하세요)
꽝,5000,꽝,3000

최대 사다리 높이는 몇 개인가요?
5

사다리 결과

pobi  honux crong   jk
    |-----|     |-----|
    |     |-----|     |
    |-----|     |     |
    |     |-----|     |
    |-----|     |-----|
꽝    5000  꽝    3000

결과를 보고 싶은 사람은?
pobi

실행 결과
꽝

결과를 보고 싶은 사람은?
all

실행 결과
pobi : 꽝
honux : 3000
crong : 꽝
jk : 5000

조건

자바 8의 스트림과 람다를 적용해 프로그래밍한다.

  • 규칙
    • 모든 엔티티를 작게 유지한다.
    • 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.

위 요구사항에 따라 4명의 사람을 위한 5개 높이 사다리를 만들 경우, 프로그램을 실행한 결과는 다음과 같다.

728x90

'Java' 카테고리의 다른 글

JVM  (0) 2022.08.06
Effective Java 4장 요약  (0) 2022.08.06
TDD Clean Code with Java 12기 2주차 피드백  (0) 2022.08.06
TDD Clean Code with Java 12기 2주차  (0) 2022.08.05
728x90

테스트 코드

자동차 경주에 대한 라이브 피드백 시간인데
느낀점이 있어서 포스팅하게 되었다.

테스트 코드 비교할 경우

우리는 항상 getter, setter 메소드를 많이 써왔다.
그래서 나도 습관처럼 객체를 생성하고 비교를 할 경우에 아래와 같이 코드를 작성했었다.

@Test
void create() {
    Position actual = new Position(5);
    assertThat(actual.getPosition).isEqualTo(5);
}

이런식으로 get 메소드를 사용해서 값을 비교를 했는데
이 방식은 잘못되었다기 보다는 get을 사용하지 않고
객체와 객체를 비교하는 방법을 사용하는 것이 오히려 객체지향적 측면에서 좋을 것 같다.

그렇게 Position 클래스에 equals()hashCode() 를 오버라이드 해주고 객체끼리 비교하게끔 만들어준다.

public class Position {
    private Position position = new Position(0);

    public Position(int position) {
        if (position < 0) {
            throw new IllegalArgumentException("음수는 위치 값이 될 수 없음");
        }
        this.position = position;
    }
}
@Test
void create() {
    Position actual = new Position(5);
    assertThat(actual).isEqualTo(new Position(5));
}

이렇게 객체 두개가 같은지를 구현하면 테스트가 성공하게 된다.

이런식으로 어떤 객체를 생성했을때 객체끼리 비교하는 습관을 들이도록 해보자.

문자열과 원자값을 포장해서 쓰는게 객체지향에서 하기 굉장히 좋은 것이니까 지금부터라도 습관 들이자~😄

이제서야 이펙티브 자바 1장이 이해가 되는것 같다. 경험해봐야 이해가 잘되는 이 기분이 좋다.

일급 컬렉션

필드변수를 하나만 두고 사용하는 클래스이다.

일급 콜렉션을 사용하면 계속해서 객체에게 메세지를 보내서 get메소드 대신 객체에게 위임해서 데이터를 조작하게끔 만들어야 한다.

하나씩 객체들을 포장해서 관리하면 테스트 로직을 짜기에 되게 수월하게 작성할 수 있다.

객체에 메시지를 보내라 = 객체가 하게 만들어라 = 객체에게 위임해라

클래스 역할은 작게할수록, 그리고 클래스를 잘게 쪼갠다면? TDD는 쉬워진다

항상 이것들을 생각하면서 코드를 작성하도록 하자 👍텍스트

728x90

'Java' 카테고리의 다른 글

Effective Java 4장 요약  (0) 2022.08.06
TDD Clean Code with Java 12기 3주차  (0) 2022.08.06
TDD Clean Code with Java 12기 2주차  (0) 2022.08.05
[JPA] 객체 지향 쿼리 심화  (0) 2022.08.05
728x90

로또

2주차 미션은 로또 생성기였다.


Step1 - 문자열 덧셈 계산기


Step2 - 로또(자동)


Step3 - 로또(2등)


Step4 - 로또(수동)

프로그래밍 요구사항이 점점 추가되어 조금 더 제한적인 상황에서 조건문을 사용해야 한다.
주차가 늘어가면서 느끼는것이지만, 테스트 주도 개발을 하게 되니까 안하던 방법이라서 손에 익지는 않았다. 그런데 완성되는 테스트를 먼저 구현하다 보니까 오류가 나는 상황에 대해서 더 생각하고 코드를 구현할 수 있게 되는것 같다.

이 과정을 진행하면서 이펙티브 자바도 같이 읽고 있다. 정적 팩토리 메서드는 이제 꼭 쓰게 되는것 같다.😁 꼭 쓰는것은 또 아니라고 생각해야되는데 일단 무분별하게 생성자로 객체를 생성할 수는 없게 만들어 놨다.

클래스

클래스 부분에서 좀 많은 생각을 했었고 이번 과제에서는 if조건문을 추상 클래스와 추상 메소드를 활용해서 조건문을 처리한 로직이 있다.

아직 2주차 미션임에도 불구하고 예전 코드와 좀 많이 달라졌다는게 눈에 보인다.
단순 로직만 구현을 바꾸는 것도 좋겠지만 그 안의 복잡도도 고쳐가면서 코드를 구현해 나가야겠다.

강의를 정말 신청하길 잘했다는 생각이 들고 이 과정을 완주하는것이 목표니까 최선을 다 해보도록 해야겠다.

아래는 2주차 미션의 요구사항이다.

요구사항

기능 요구사항
로또 구입 금액을 입력하면 구입 금액에 해당하는 로또를 발급해야 한다.

로또 1장의 가격은 1000원이다.
구입금액을 입력해 주세요.
14000
14개를 구매했습니다.
[8, 21, 23, 41, 42, 43]
[3, 5, 11, 16, 32, 38]
[7, 11, 16, 35, 36, 44]
[1, 8, 11, 31, 41, 42]
[13, 14, 16, 38, 42, 45]
[7, 11, 30, 40, 42, 43]
[2, 13, 22, 32, 38, 45]
[23, 25, 33, 36, 39, 41]
[1, 3, 5, 14, 22, 45]
[5, 9, 38, 41, 43, 44]
[2, 8, 9, 18, 19, 21]
[13, 14, 18, 21, 23, 35]
[17, 21, 29, 37, 42, 45]
[3, 8, 27, 30, 35, 44]

지난 주 당첨 번호를 입력해 주세요.
1, 2, 3, 4, 5, 6

당첨 통계
---------
3개 일치 (5000원)- 1개
4개 일치 (50000원)- 0개
5개 일치 (1500000원)- 0개
6개 일치 (2000000000원)- 0개
총 수익률은 0.35입니다.(기준이 1이기 때문에 결과적으로 손해라는 의미임)

힌트

  • 로또 자동 생성은 Collections.shuffle() 메소드 활용한다.
  • Collections.sort() 메소드를 활용해 정렬 가능하다.
  • ArrayList의 contains() 메소드를 활용하면 어떤 값이 존재하는지 유무를 판단할 수 있다.

프로그래밍 요구사항

  • 모든 기능을 TDD로 구현해 단위 테스트가 존재해야 한다. 단, UI(System.out, System.in) 로직은 제외
  • 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
  • UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다.
  • indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다.
    • 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
    • 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다.
  • 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다.
    • 함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다.
  • 모든 로직에 단위 테스트를 구현한다. 단, UI(System.out, System.in) 로직은 제외
  • 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
  • UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다.
  • 자바 코드 컨벤션을 지키면서 프로그래밍한다.
    • else 예약어를 쓰지 않는다.
    • 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
      else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다.
728x90

'Java' 카테고리의 다른 글

TDD Clean Code with Java 12기 3주차  (0) 2022.08.06
TDD Clean Code with Java 12기 2주차 피드백  (0) 2022.08.06
[JPA] 객체 지향 쿼리 심화  (0) 2022.08.05
Effective Java 1장  (0) 2022.08.05

+ Recent posts