2022년 정리5

조용휘·2023년 1월 22일
0

2022

목록 보기
5/10

학습 내용

Git에서 star를 많이 받은 코드들을 살펴보자.
MVC패턴
예외처리 고찰 (tistory.com)
Model
rule
사용자가 편집하길 원하는 모든 데이터를 가지고 있어야 한다.
뷰나 컨트롤러에 대해 어떠한 정보도 알지 말아야 한다.
변경이 일어나면, 변경 통지에 대한 처리방법을 구현해야 한다.
View
모델이 가지고 있는 정보를 따로 저장해서는 안된다.
모델이나 컨트롤러와 같이 다른 구성요소를 몰라야 한다.
변경이 일어나면 변경 통지에 대한 처리방법을 구현해야 한다.
Controller
모델이나 뷰에 대해서 알고 있어야 한다.
모델이나 뷰의 변경을 모니터링 해야 한다.
비즈니스 로직 / 도메인 로직
비즈니스 로직, 도메인 로직이 도대체 뭐지? (velog.io)
도메인, 비즈니스 : 소프트웨어가 풀고자 하는 현실세상의 문제
‘이 코드가 현실 문제, 즉 비즈니스에 대한 의사결정을 하고 있는가?
도메인 로직은 현실 문제에 대한 의사결정을 하는 코드이다. 나머지 코드는 그 결정을 위한 입력값을 만들어주거나, 그 결정의 결과물을 해석하고 보여주고 전파하는 코드이다.
만약 어떤 코드가 명확하게 도메인 로직인지 아닌지 애매하다면, 해당 코드가 하는 일을 쪼개야 한다는 신호다. 도메인 로직이 분명한 부분과 서비스 로직이 섞여 있을 수도 있다.
소프트웨어 설계의 근본 원칙, 관심사의 분리 (velog.io)
객체 지향도, MVC 패턴도 결국에는 코드를 어떤 단위로 나누고 구조화할지에 대한 고민의 결과물이라는 걸 알았을 때, 되게 흥미로웠다.
글쓰기의 구조는 독자의 관점에서 글이 잘 읽히도록 고민하는 것이고, 소프트웨어의 아키텍처는 다른 프로그래머의 관점에서 코드가 잘 읽히도록 고민하는 것이다.
응집도는 높을수록, 결합도는 낮을수록 좋은 코드이다.
공과 사를 구분해야 일을 잘한다.
공 : 인터페이스 / 사 : 구현 → 캡슐화
A class should have one, and only one, reason to change →변경할 이유를 기준으로 객체를 분리해라.
인터페이스를 별도의 추상 클래스나, 혹은 프로토콜로 정의
하는 것이다. (= 의존성 역전 원칙)
비유하자면 충전기가 USB-C라는 규격만 따르면, 자기가 충전하는 기기가 스마트폰이든 태블릿이든 알바 아니게 되는 상황을 만들 수 있다.
인터페이스의 관심사는 메시지를 보내는 쪽, '클라이언트'다. → 인터페이스에서 관심사를 분리할 때는 클라이언트가 필요한 인터페이스만 쓸 수 있도록 분리하자.
결론 : 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI 로직은 제외한다.
→ 핵심 로직을 구현하는 코드와 UI담당 로직을 분리해서 구현하자.
마크다운 문법
제목 > 부제목 > … : # 붙여가면서 작성. 제목은 # 1개, 부제목은 #2개 등..
#은 총 6개까지 지원한다.

: Block Quote를 표시한다.

..할때마다 inner block이 생성됨.
This is a first BQ
hi
hello
순서가 있는 목록은 숫자와 점을 사용하여 표현한다.
어차피 무조건 내림차순으로 정렬된다.
순서가 없는 목록은 *, +, -로 표현한다.
혼합해서 사용도 가능
텝 혹은 4칸 공백을 만나면 변환되기 시작하여 다음 들여쓰기(4칸)가 쓰이지 않은 단어 전까지 변환한다.
시작줄은 윗줄과 한줄 떨어뜨려야 한다. 인식이 잘못될수도 있다.
코드 블럭은 두 가지로 표현 가능하다.

{code}

public class BootSpringBootApplication {
  public static void main(String[] args) {
    System.out.println("Hello, Honeymon");
  }

}

public class BootSpringBootApplication {
public static void main(String[] args) {
System.out.println("Hello, Honeymon");
}
}
b. “ " 사용 깃헙에서는 코드블럭코드("") 시작점에 사용하는 언어를 선언하여 문법강조(Syntax highlighting)이 가능하다.

public class BootSpringBootApplication {
  public static void main(String[] args) {
    System.out.println("Hello, Honeymon");
  }
}

public class BootSpringBootApplication {
public static void main(String[] args) {
System.out.println("Hello, Honeymon");
}
}
수평선(페이지 나누기 용도)
“* * *”
“***”
“- - -”
링크
참조링크
link keyword

// code
Link: Google

외부링크
사용문법: Title
적용예: Google
자동연결

  • 외부링크: http://example.com/
  • 이메일링크: address@example.com
    강조
    single asterisks
    single underscores
    double asterisks
    double underscores
    cancelline
    줄바꿈을 위해서는 3칸이상 띄어쓰기를 하면 줄이 바뀐다.
    ‘java 상수’ 구현 방법
    [JAVA] Constant Interface : 상수를 정의한 인터페이스 :: 개발 공부 (tistory.com)
    상수에 대해 인터페이스로 사용하는 것은 anti 패턴으로 취급한다.
    → import static 구문 사용을 권장한다.
    → static final 타입이며 대문자 사용.
    무언가 상수가 갖는 의미를 표현하고 싶을 때 사용.
    public static final int FIRST_ELEMENT_OF_ANSWER = answer[0];
    Import & Import static
    Static의 사용은 결국 클래스에 대한 인스턴스의 생성없이 메소드를 사용할 수 있다는 것을 말한다.
    import static을 사용하면 클래스명 없이 소속 메소드를 사용할 수 있다. 하지만 만약 같은 클래스 내에 동일한 이름의 메소드가 있다면 클래스 자신의 메소드가 우선한다.
    클래스 내의 모든 “정적” 메소드를 import 하려면 *를 사용하는데, 권장되는 방식은 아니다.
    → static 메소드만 가져오는 거였구나!
    → import static 하려는 field와 method는 “모두 static으로 정의”되어 있어야 한다.
    패키지, 모듈, 라이브러리
    패키지 : 클래스들의 모음집. ‘폴더/디렉토리’ 개념. package {패키지, 즉 해당 소스가 속한 폴더 이름}; 식으로 선언한다.
    패키지를 통해 라이브러리끼리의 구분이 가능.
    하위 패키지를 포함하는 중첩 구조도 가능.
    패키지를 사용하는 이유는 클래스명의 고유성을 보장하기 위함.
    서로 다른 용도의 라이브러리를 도입할 때 이름이 충돌하는 클래스들이 있을 수 있는데 이럴 때 패키지 기능을 사용하여 클래스의 이름이 충돌하는 것을 방지함.
    다른 패키지의 클래스에서 사용될 클래스의 이름 앞에 public을 사용해야 import해서 사용 가능. 다른 패키지의 클래스를 사용할 때 그 클래스의 이름 앞에 package 이름을 붙여 이 클래스가 어느 패키지에 속해있는지를 알려주어야 한다.
    접근 제어자에 따른 패키지 적용 가능 범위
    멤버 및 생성자
    private : 같은 클래스에서만 사용가능
    default : 같은 패키지에서만 사용가능
    protected : 같은 패키지에 속한 클래스와 다른 패키지에 속한 서브클래스에서만 사용가능
    public : 모든 클래스에서 사용 (단, 클래스가 public이 아닌 경우 같은 패키지에서만)
    클래스 및 인터페이스
    default : 같은 패키지에서만 사용
    public : 다른 패키지에서도 사용
    라이브러리
    공통으로 사용될 수 있는 특정한 기능들을 모듈화한 것.
    모듈
    외부에서 재사용할 수 있는 패키지들을 묶은 것. 패키지의 상위 개념.
    Enum class
    https://techblog.woowahan.com/2527/
    ‘Enum으로 추출한다’라고 표현.
    Lombok?
    어노테이션을 통해 각종 필수 메소드들을 간단히 만들어주는 기능 모음집.
    나중에 적용해보고 싶다. 깔끔한 코드 구현에 도움이 됨.
    인풋으로 주어지는 두 값의 연관 관계를 코드로 표현할 수 없기 때문에, 어떤 한 인풋이 다른 인풋에 관련된 기능을 강제할 수 있는 수단이 없다.
    입력값을 제한할 수 있다.
    같은 타입의 값이면 전부 받아들이는 현상을 방지할 수 있다. 지정된 값만 받을 수 있도록 하는 검증코드가 필요하게 된다.
    그룹별 기능을 추가할 때 용이하다.
    Enum의 default 생성자는 private로 되어 있으며 public으로 변경하는 경우 컴파일 에러가 발생한다.
    Enum 클래스를 구현하는 경우 상수 값과 같이 유일하게 하나의 인스턴스가 생성되어 사용된다. → Singleton 형태로 어플리케이션 전체에서 사용된다.
    enum 클래스는 내부적으로 java.lang.enum 클래스를 상속받는다. 따라서 다중 상속이 안되므로 enum은 다른 클래스 상속이 불가능하다.
    Enum의 내부 API
    Enum의 toString() 메소드는 상수의 이름을 리턴하도록 구현되어 있다.
    public enum Rank {
    THREE(3, 4_000) // Enum 인스턴스1, type : 당연히 Rank
    FOUR(4, 10_000) // Enum 인스턴스2
    Five(5, 30_000) // Enum 인스턴스3
    ...
    }

public static void main(String[] args) {
System.out.println(Rank.Five.toString());
}
// 결과 : FIVE
values()
Enum 클래스가 가지고 있는 모든 상수 값을 배열의 형태로 리턴. 단순히 String 타입으로 반환하는 것이 아니라 ‘인스턴스’ 형태로 반환한다. 즉, Enum 클래스가 가지고 있는 모든 인스턴스를 배열에 담아 반환하는 것.
Rank[] values = Rank.values();
for(int i = 0; i < values.length; i++) {
System.out.println(values[i]);
}
// 결과 : THREE, FOUR, FIVE
valueOf()
String을 파라미터로 받으며 입력값(string)과 일치하는 상수 인스턴스가 존재하면 그 ‘인스턴스’를 반환한다.
ordinal()
Enum 클래스 내부에 있는 상수들(인스턴스들)의 Index를 리턴하는 메소드. 0부터 상수의 수 -1 까지 이다.
사용 예시
public enum Winner {
WINNER("승리", Arrays.asList("kyle", "pobi", "hello", "world"))
LOSER("패배", Arrays.asList("hodol", "dunddoung", "rutgo"))

private final String winner;
private final List<String> list;

Winner(String winner, List<String> list) {
	this.winner = winner;
	this.list = list;
}

}
상수 선언시 람다식을 활용할 수 있다.
예외
throws는 ‘던진다’는 개념으로 알아서 잘 와닿지 않았는데, ‘뱉어낸다’ 라고 이해하니 잘 와닿았다. 그래서 해당 메소드를 호출한 클래스나 메소드에 예외 처리를 요구하는 것이다. 또한, runtimeException이 아니면 컴파일러가 오류를 잡지 않으므로 그 외 예외를 처리할때는 더욱 신중하게 해야 한다.
함수형 프로그래밍
[프로그래밍] 함수형 프로그래밍(Functional Programming) 이란? - MangKyu's Diary (tistory.com)
프로그래밍 패러다임
명령형 프로그래밍 : 무엇(What)을 할 것인지 나타내기보다 어떻게(How) 할 건지 설명
절차지향 프로그래밍 : 수행 순서대로 처리
객채지향 프로그래밍 : 객체들의 집합으로 상호작용을 표현
선언형 프로그래밍 : 어떻게(How) 할 건지 나타내기보다 무엇(What)을 할 건지 설명
함수형 프로그래밍 : 순수 함수를 조합하고 소프트웨어를 만드는 방법
명령형과 함수형에서 사용하는 함수는 부수효과의 유무에 따라 차이가 있다.
Stream은 상태를 바꾸는 지역 변수 자체를 없앰으로써 부수효과를 제거하였다.
또한, 함수형 프로그래밍의 관점에 따라 Stream의 파라미터로 함수가 전달될 수 있다.
함수를 변수로 선언하는 것도 가능하며 이 함수를 반환하는 것도 가능하다.
@Test
void customFunction() {
Function<String, String> function = word -> word.toUpperCase();
assertThat(function.apply("text")).isEqualTo("TEXT");
람다식, 함수형 인터페이스
[Java] 람다식(Lambda Expression)과 함수형 인터페이스(Functional Interface) - (2/5) - MangKyu's Diary (tistory.com)
람다식
: 함수를 하나의 식으로 표현한 것 → 메소드의 이름이 필요 없음 : ‘익명 함수’의 한 종류
익명함수는 모두 일급 객체이다. (일급 객체 : 함수형 프로그래밍 관련 개념)
일급 객체인 함수는 변수처럼 사용 가능, 매개 변수로 전달이 가능하다는 등의 특징이 있다.
람다식으로 선언된 함수는 1급 객체이기 때문에 Stream API의 매개변수로 전달이 가능해진다.
람다식의 특징
람다식 내에서 사용되는 지역변수는 final이 붙지 않아도 상수로 간주된다.
일급 객체의 특성을 갖기 위한 것으로 보임
람다식으로 선언된 변수명은 다른 변수명과 중복될 수 없다.
함수형 인터페이스
: JAVA에서는 객체지향 언어이기 때문에 순수 함수(람다식)와 일반 함수를 다르게 취급한다. 따라서 이를 구분하기 위해 ‘함수형 인터페이스’가 등장했다.
함수형 인터페이스란 함수를 1급 객체처럼 다룰 수 있게 해주는 어노테이션으로, 인터페이스에 선언하여 단 하나의 추상 메소드만을 갖도록 제한하는 역할을 한다.
JAVA의 람다식이 함수형 인터페이스를 반환하기 때문에 이를 사용한다.
@FunctionalInterface
interface MyLambdaFunction {
int max(int a, int b);
}

public class Lambda {
public static void main(String[] args) {
MyLambdaFunction lambdaFunction = (int a, int b) -> a > b ? a : b;
System.out.println(lambdaFunction.max(3,5));
}
} // 결과 : 5
Java에서 제공하는 함수형 인터페이스(자주 사용되는 것들을 정의한 4가지 FI)
Supplier
매개변수 없이 반환값만을 갖는다. T get()을 추상 메소드로 갖고 있다.
@FunctionalInterface
public interface Supllier {
T get();
}

Supplier supplier = () -> "Hello world!";
System.out.println(supplier.get());
// 결과 : Hello world!
Consumer
객체 T를 전달받으며, 반환값은 없다. void accept(T t)를 추상메소드로 갖는다.
@FunctionalInterface
public interface Consumer {
void accept(T t);
default Consumer andThen(Consumer<? super T> after) {
objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); }
}
}
Function<T, R> , Predicate 도 있다.
<인터페이스(interface)의 내부 규칙>
아스피린 개발자 :: [Java] 인터페이스 (interface)의 기본 문법과 꼭 지켜야 하는 규칙 (tistory.com)
인터페이스는 호출 규칙을 정의하는 것이기 때문에 추상 메서드만 선언 가능하다.
인터페이스는 인스턴스 변수를 가질 수 없다.
즉 인터페이스에 선언된 모든 메서드는 public이고 abstract이다. 또한 인터페이스에 선언된 모든 필드(영역)는 public이고 static이며 final이다.
Stream.API
[Java] Stream API에 대한 이해 - (1/5) - MangKyu's Diary (tistory.com)
[Java] Stream API의 활용 및 사용법 - 기초 (3/5) - MangKyu's Diary (tistory.com)
[Java] Stream API의 고급 활용 및 사용 시의 주의할 점 - (4/5) - MangKyu's Diary (tistory.com)
데이터를 추상화하고, 처리하는데 자주 사용되는 함수들을 정의해두었다. 데이터의 추상화덕에 데이터 종류에 상관 없이 같은 방식으로 데이터를 처리할 수 있으므로 재사용성이 높다.
특징
원본의 데이터를 변경하지 않는다. 주어진 데이터에 .stream 을 사용함으로서 별도의 stream을 생성하는 것.
따라서 일회용이다. 만약 사용 후 닫힌 stream에 대해 재사용을 하면 IllegalStateException이 발생하게 된다.
내부 반복으로 작업을 처리한다. 덕분에 간결한 코드의 작성이 가능하다.
nameStream.forEach(System.out::println);
연산 종류
총 3단계로 나눈다.
생성하기
stream 객체 생성 : 배열, 컬렉션, 임의의 수, 파일 등 대부분 stream 생성 가능.
재사용이 불가능하므로 닫히면 다시 생성해야 한다.
Collection 타입 : .stream()
배열 타입 : Stream.of(~) or Arrays.stream(~)
원시 타입 : {Type}Stream / ex) IntStream, DoubleStream 등
가공하기
연산 결과를 다시 stream으로 반환하기 때문에 메소드체이닝이 가능하다.
Filter (정제)
조건에 맞는 데이터만을 정제하여 더 작은 컬렉션을 만들어내는 연산. 인자로 함수형 인터페이스 Predicate를 받고 있기 때문에 boolean을 반환하는 람다식을 작성하여 filter 함수를 구현할 수 있다.
Stream stream =
list.stream()
.filter(name -> name.contains("a"));
Map (데이터 변환)
기존 Stream 요소들을 변환하여 새로운 Stream을 형성하는 연산. 인자로 함수형 인터페이스 function을 받고 있다. map함수의 람다식은 메소드 참조를 이용해 변경이 가능하다.
Stream stream =
names.stream()
.map(s -> s.toUpperCase());
결과만들기
가공된 데이터로부터 원하는 결과를 만들기 위한 최종 연산.
stream의 요소들을 소모하면서 연산이 수행되기 때문에 1번만 처리 가능.
Stream 연산들은 매개변수로 함수형 인터페이스를 받도록 되어있다. 따라서 이와 람다식을 알아야 한다.
Method Reference : 반복되는 작업에 대해 :: 로 표현
TestCode : TDD, BDD
BDD : Dehavior Driven Development
행동 중심 개발을 하면 좋겠다는 생각에서 시작한 스타일
→ 행동을 기반하여 TDD를 수행하자는 공통의 이해.
→ TDD를 수행하려는 어떠한 행동과 기능을 개발자가 더 이해하기 쉽게하는 것이 목적.
따라서 모든 테스트 문장은 Given, When, Then으로 나눠서 작성할 수 있어야 한다.
Given
테스트를 위해 주어진 상태
테스트 대상에게 주어진 조건
테스트가 동작하기 위해 주어진 환경
When
테스트 대상에게 가해진 어떤 상태
테스트 대상에게 주어진 어떠한 조건
테스트 대상의 상태를 변경시키기 위한 환경
Then
앞선 과정의 결과
즉, 어떤 상태에서 출발(Given)하여, 어떤 상태의 변화를 가했을 때(When) 기대하는 어떠한 상태(Then)가 되어야 한다.
public class Calculator{
public int plus(int a, int b) {
return a+b;
}

public class CalculatorTest{
Calculator calc = new Calculator();

@Test
void plus() {
	// given
	int a = 10;
	int b = 20;

	// when
	int result = calc.plus(a, b);

	// then
	assertEquals(result, a+b);
}

}
즉, BDD 자체는 TDD를 더 클린하게 사용하기 위한 방법이다.
BDD를 더욱 BDD스럽게 하기 위한 방법은 BDDMockito를 사용하는 것이다.
특정 상황(When)에 대한 가짜 결과를 만들어주는 것을 Stubbing이라고 한다. 즉, 가짜로 수행할 객체를 넣어주는 것이다.
BDDMockito를 이용하면 Mockito의 when()을 given()이라는 메서드로 더 정확한 의미를 전달할 수 있다.
given(someClass.method()).willReturen();

given(memberRepository.existByEmail(any(String.class)).willReturn(false);
Given() 메소드는 어떤 메소드가 실행되었을 때의 테스트를 위한 상황을 설정할 수 있다.
단위 테스트에서 중요한 것은 테스트하려는 대상의 고립이다.
테스트 대상에 연관된 다른 객체들은 관여하지 않도록 우리가 가짜 객체를 넣어줘야 한다.
→ Mockito의 mock()를 이용하였다.
given / willReturn을 @BeforeEach로 실행해서 가짜 객체에 의한 fore-specified answer를 미리 만든다. 그리고 그 값을 JUnit5 테스트 코드로 실행해서 확인하는 것이다.
<틈새 지식>
any() : 모든 값을 받았을 때의 행동 정의.
ex) any(String.class) : 스트링인 모든 객체가 가능하다.
eq() : 특정 값을 받았을 때의 행동 정의
해당 메서드를 수행했을 때 반환하는 값
행동을 반환할 때는 크게 3가지 방법이 존재한다.
willReturn()
특정 메소드가 실행됐을 때, 이에 대한 메소드의 반환을 willReturn에서 정의
will()
invocation(호출)을 통해서 새로운 객체를 반환하거나 아예 새로운 행동 반환 가능
willThrow()
정규식
“(^[0-9]*):정규식의시작)” ^ : 정규식의 시작 : 정규식의 끝
[0-9] : 숫자 0~9까지

  • : 0개 이상 있다.
profile
Progress Gradually, Never Stop

0개의 댓글