3기 김태영
2시간 전•

10월 30일 일지

10월 30일

오늘 공부에 투자한 순수한 시간: 3시간 30분

학습한 개념 : SOLID 원칙 - 단일 책임, 개방-폐쇄, 리스코프 치환, 인터페이스 분리, 의존성 역전

----------------------------------------------------------------------------------------------------------

<핵심 개념1 : 단일 책임 원칙 (SRP)>

1. 왜 이런 이름이 붙혀졌는가?

-> Single Responsibility Principle은 하나의 모듈이 하나의 책임을 맡도록 하는 원칙이다. 여기서 책임은 코드를 변경하게 만드는 이유이고, 같은 업무 기준으로 변경을 요구하는 주체와 연결된다.

2. 왜 이 원칙이 생겼는가?

-> 서로 다른 요구를 한곳에서 처리하면 한쪽의 변경이 다른 쪽의 동작까지 영향을 준다. 따로 바뀌어야 할 부분이 얽혀 수정 범위가 넓어지는 것이다. 이러한 변경의 전파를 줄이고 각 부분의 역할을 분명하게 하기 위해 생긴 원칙이다.

3. 어떤 문제와 불편함을 해결하는가?

-> 어떤 요구로 코드를 수정하는지에 따라 책임을 구분하면 변경할 위치와 확인할 범위를 파악하기 쉬워진다. 관련된 규칙이 모여 있어 어떤 일을 처리하는 부분인지 이해하기 좋다. 다른 업무와의 연결도 줄어 수정과 테스트에 드는 부담을 낮출 수 있다.

4. 이 원칙이 없으면 어떤 문제와 불편함이 생기는가?

-> 하나의 모듈에 서로 다른 변경 이유가 섞이면 작은 수정에도 여러 기능의 동작을 함께 살펴봐야 한다. 공통으로 사용하는 처리 안에 특정 업무의 요구가 들어가면 그 처리를 사용하는 다른 부분에도 변화가 전달된다. 코드가 늘어날수록 어떤 요구가 어디에 연결되어 있는지 파악하기 어려워지고, 각 기능을 따로 이해하거나 수정하는 데도 더 많은 시간이 필요해진다.

5. 어떤 핵심 아이디어로 해결했는가?

-> 함께 바뀌는 코드는 모으고 서로 다른 이유로 바뀌는 코드는 나누는 것이다. 여기서 기준은 같은 업무 기준을 가진 사람이나 집단의 요구이다. 따라서 하나의 책임을 수행하는 데 필요한 여러 메서드는 같은 모듈에 둘 수 있다.

공통 처리는 공통의 기준을 유지하고, 각 업무에만 필요한 처리는 해당 책임 안에 두어 변경의 범위를 정한다. 이렇게 경계를 나누면 한쪽의 변경이 다른 쪽으로 번질 가능성을 줄일 수 있다.

다만 지나치게 나누면 하나의 흐름을 이해하기 위해 여러 파일을 따라가야 하므로, 실제로 함께 바뀌는 관계를 보고 적절한 크기로 묶어야 한다.

----------------------------------------------------------------------------------------------------------

<핵심 개념2 : 개방-폐쇄 원칙 (OCP)>

1. 왜 이런 이름이 붙혀졌는가?

-> Open-Closed Principle은 확장에는 열려 있고 수정에는 닫혀 있도록 한다는 의미이다. 새로운 동작을 추가할 수 있으면서 기존 기능을 사용하는 코드의 변경은 줄이는 방향으로 구조를 잡는 원칙이다.

2. 왜 이 원칙이 생겼는가?

-> 요구가 추가될 때마다 기존 처리에 조건을 끼워 넣으면 이미 동작하던 코드를 반복해서 수정한다. 같은 구분 기준이 흩어져 있으면 관련된 곳을 전부 찾아야 한다. 이러한 반복과 누락을 줄이며 기능을 확장하기 위해 생긴 원칙이다.

3. 어떤 문제와 불편함을 해결하는가?

-> 확장되는 부분과 이를 사용하는 부분의 연결을 느슨하게 만들어 기존 코드가 받는 영향을 줄인다. 사용하는 쪽이 세부 처리 방법을 알아야 할 필요가 줄고, 추가할 동작을 해당 구현에 모을 수 있다. 검증한 흐름을 유지하며 기능을 붙이므로 확장의 부담도 낮아진다.

4. 이 원칙이 없으면 어떤 문제와 불편함이 생기는가?

-> 새로운 종류가 생길 때마다 호출하는 쪽에서 이를 구분하고 처리하면 종류가 늘수록 조건과 수정 지점도 많아진다. 일부만 수정하면 같은 대상을 처리하는 코드끼리 서로 다른 결과를 낼 수 있다. 또한 기능을 추가하는 과정에서 기존 동작을 바꿀 가능성이 커져 관련된 코드를 넓게 확인해야 한다. 결국 기능을 늘리는 일과 기존 기능을 유지하는 일이 서로 얽히게 된다.

5. 어떤 핵심 아이디어로 해결했는가?

-> 유지할 공통 약속과 달라질 구체적인 처리를 분리하는 것이 핵심이다. 추상 타입에는 사용하는 쪽이 알아야 할 기능을 정의하고, 구현체에는 그 기능을 실제로 수행하는 방법을 둔다.

호출하는 쪽은 약속된 기능을 요청하고 실행 시에는 연결된 객체의 구현이 동작한다. 새로운 구현도 같은 약속을 지키면 기존 호출 흐름에 연결할 수 있다.

즉, 무엇을 제공하는지를 공통으로 정하고 어떻게 제공하는지는 각 구현에 맡기는 것이다. 이 구조는 약속이 유지되는 범위에서 확장의 효과를 얻으므로, 실제로 달라질 가능성이 있는 부분을 먼저 파악하고 그 부분에 맞는 안정적인 약속을 정해야 한다.

----------------------------------------------------------------------------------------------------------

<핵심 개념3 : 리스코프 치환 원칙 (LSP)>

1. 왜 이런 이름이 붙혀졌는가?

-> Liskov는 바버라 리스코프의 이름이고 Substitution은 대신 넣는다는 뜻이다. 상위 타입을 사용하는 자리에 하위 타입을 넣어도 약속된 동작이 유지되어야 한다는 원칙이다.

2. 왜 이 원칙이 생겼는가?

-> 타입 관계를 만족해도 실제 결과는 사용하는 쪽의 기대와 달라질 수 있다. 이름, 인자, 반환 타입을 맞추는 것과 약속된 기능을 수행하는 것 사이에 차이가 있기 때문이다. 실제 동작을 기준으로 교체 가능성을 판단하기 위해 생긴 원칙이다.

3. 어떤 문제와 불편함을 해결하는가?

-> 사용하는 쪽이 상위 타입의 약속만 보고 동작할 수 있게 한다. 교체할 때마다 내부를 확인하고 특정 구현만을 위한 처리를 추가하는 부담을 줄인다. 구현체들이 같은 행동 기준을 지키면 호출부는 구체적인 종류에 덜 의존하면서 입력과 결과를 일관되게 다룰 수 있다.

4. 이 원칙이 없으면 어떤 문제와 불편함이 생기는가?

-> 형식만 맞추고 동작의 약속을 지키지 않으면 컴파일을 통과해도 실행 중에 실패하거나 잘못된 결과를 낼 수 있다. 기존에 처리하던 입력을 새 구현이 받아들이지 않거나 약속한 결과를 충분히 제공하지 못하면 사용하는 쪽의 전제가 무너진다. 호출부에서 구현체별로 보완하면 교체할 때마다 수정이 늘어나 공통 타입으로 확장하려던 구조를 유지하기 어려워진다.

5. 어떤 핵심 아이디어로 해결했는가?

-> 하위 타입이 상위 타입의 행동에 관한 약속을 유지하도록 하는 것이다. 기존에 허용한 입력을 계속 받아들이고, 보장한 결과를 유지하며, 항상 성립해야 하는 상태의 규칙도 지켜야 한다.

이를 사전조건을 강화하지 않고 사후조건을 약화하지 않으며 불변식을 유지한다고 표현한다. 처리 방법은 달라도 약속된 동작은 성립해야 한다. 구현이 약속을 벗어났다면 구현을 고치고, 함께 묶은 대상이 같은 약속을 수행하기 어렵다면 타입의 경계도 다시 살펴야 한다.

OCP가 새 구현을 연결할 구조를 다룬다면 LSP는 그 구현을 연결했을 때 실제 동작까지 호환되는지를 확인하는 기준이라고 이해할 수 있다.

----------------------------------------------------------------------------------------------------------

<핵심 개념4 : 인터페이스 분리 원칙 (ISP)>

1. 왜 이런 이름이 붙혀졌는가?

-> Interface Segregation Principle은 인터페이스를 사용하는 쪽의 필요에 맞게 분리한다는 의미이다. 여러 기능을 모아둔 큰 약속을 역할에 맞게 나누어 필요한 기능에만 의존하도록 하는 원칙이다.

2. 왜 이 원칙이 생겼는가?

-> 서로 다른 필요를 하나의 인터페이스로 묶으면 일부 기능만 쓰는 코드도 관련 없는 기능까지 포함한 타입에 의존한다. 구현체도 제공할 수 있는 범위를 넘어선 기능을 요구받을 수 있다. 이러한 불필요한 연결과 구현 부담을 줄이기 위해 생긴 원칙이다.

3. 어떤 문제와 불편함을 해결하는가?

-> 여기서 클라이언트는 약속된 메서드를 호출하는 쪽이고 구현체는 그 요청을 실제로 처리하는 쪽이다. 인터페이스에 메서드의 이름, 인자, 반환 타입을 선언하면 구현체가 동작할 내용을 채운다. 필요한 범위의 약속만 사용하면 역할을 이해하기 쉽고 관련 없는 변경에 연결될 여지도 줄어든다.

4. 이 원칙이 없으면 어떤 문제와 불편함이 생기는가?

-> 큰 인터페이스에 여러 역할이 섞이면 일부 기능의 변경으로 같은 약속을 구현한 객체들을 함께 수정할 수 있다. 제공하기 어려운 기능까지 구현하도록 요구받으면 빈 처리나 예상하지 못한 실패가 들어가기도 한다. 그러면 호출하는 쪽에서 어떤 기능을 실제로 사용할 수 있는지 따로 확인해야 한다. 테스트에서도 필요한 동작 외에 여러 메서드를 채워야 해서 준비할 내용이 늘어난다.

5. 어떤 핵심 아이디어로 해결했는가?

-> 인터페이스를 호출하는 쪽이 어떤 기능을 함께 필요로 하는지 보고 약속의 범위를 나누는 것이다. 서로 다른 목적으로 사용되는 기능은 분리하고 함께 쓰이는 기능은 모아둔다.

하나의 구현체가 여러 인터페이스를 구현할 수 있고, 클라이언트는 그중 자신에게 필요한 역할을 통해 객체를 사용한다. 구현이 한 객체에 모여 있어도 클라이언트에는 필요한 약속만 보여줄 수 있다.

즉, 메서드의 수보다 사용하는 쪽과 기능 사이의 관계가 분리의 기준이다. 잘게 나눌수록 관리할 대상도 늘어나므로 실제 사용 관계에 맞게 경계를 정해야 한다.

----------------------------------------------------------------------------------------------------------

<핵심 개념5 : 의존성 역전 원칙 (DIP)>

1. 왜 이런 이름이 붙혀졌는가?

-> Dependency Inversion Principle은 의존 방향을 뒤집는 원칙이다. 상위의 핵심 규칙이 하위의 구체적인 처리 방식에 맞추던 구조에서, 하위가 상위에 필요한 추상적인 약속을 구현하도록 방향을 바꾼다는 뜻이다.

2. 왜 이 원칙이 생겼는가?

-> 핵심 규칙이 구체적인 처리 방식에 직접 연결되면 세부 기술이 달라질 때 규칙을 처리하는 코드까지 영향을 받는다. 수행할 일은 같아도 수단이 바뀌어서 함께 수정하는 것이다. 핵심 규칙을 이러한 변경으로부터 보호하기 위해 생긴 원칙이다.

3. 어떤 문제와 불편함을 해결하는가?

-> 상위는 무엇을 해야 하는지에 관한 핵심 정책을, 하위는 구체적인 처리 방법을 맡는다. 양쪽이 공통 약속에 의존하면 하위를 바꿔도 상위 규칙을 유지하기 쉬워진다. 같은 약속의 대체 객체를 사용할 수 있어 각 부분을 따로 개발하고 테스트하는 부담도 줄어든다.

4. 이 원칙이 없으면 어떤 문제와 불편함이 생기는가?

-> 상위가 구체적인 구현을 직접 선택하고 생성하면 그 구현을 사용하는 일과 어떤 구현을 쓸지 결정하는 일이 함께 묶인다. 다른 구현으로 교체하려면 상위 코드도 수정해야 하고, 규칙을 확인할 때 하위의 실행 환경까지 준비해야 할 수 있다. 각 부분을 분리해서 재사용하기 어려워지고 구체적인 구현이 준비되는 순서에도 영향을 받게 된다.

5. 어떤 핵심 아이디어로 해결했는가?

-> 상위에서 필요한 기능을 추상적인 약속으로 정하고 상위와 하위 모두 그 약속에 의존하도록 한다. 상위는 약속을 통해 요청하고 하위는 약속에 맞게 처리한다.

이때 추상에도 특정 기술의 세부 처리 방식이 들어가지 않도록 상위의 필요를 기준으로 설계해야 한다. 실제 객체의 선택과 생성은 바깥의 조립 지점에서 맡고, 사용하는 쪽은 전달받은 객체로 일을 수행하게 할 수 있다.

이렇게 밖에서 객체를 제공하는 방법이 의존성 주입이고, DIP는 양쪽이 추상에 의존하도록 하는 설계 원칙이다. 실행할 때의 호출은 상위에서 하위로 이어지지만 코드의 의존 대상은 양쪽 모두 공통 약속을 향하도록 바뀌는 것이다.

----------------------------------------------------------------------------------------------------------

키워드

ㄴ꼬리질문1 서로 다른 주인의 코드가 한 지붕 아래에서 같은 헬퍼를 공유한다는게 무슨 말인지 간단한 코드 예시로 보여줘

ㄴ꼬리질문2 해결 코드 예시에서 average()를 같이 사용하고 있는데 이러면 안되는거 아니야?

ㄴ꼬리질문3 그냥 값만 공유 하는거인거야? 이 값을 통해서 뭘 할건지는 상관없는거고?

ㄴ꼬리질문4 class ReportFormatter {

String reportText(Student s) {

s.scores.removeLast(); // ❌ 원본을 건드림

return '평균: ${s.average()}점';

}

}

이 코드가 원본을 어떻게 망가탸렸는제 설명해조ㅓ

ㄴ꼬리질문5 오직 하나의 액터 에게만 책임을 져야 한다라고 했는데, 액터는 코드 변경을 요구하는 사람이나 집단이라고 했어

이 상황을 실제 쉬운 코드로 예시를 들어서 설명해줘

ㄴ꼬리질문6 결국 하나의 클래스는 하나의 요구에만 응답해야 한다는 거야?

ㄴ꼬리질문7 먼저 클래스에 하나의 약속 또는 규격만 정해두고, 이를 Impl을 통해서 구현해나가는 것이 원본은 그대로 두고 자식 클래스를 만들어 붙이는 과정인거야?

ㄴ꼬리질문8 추상 타입이라는게 뭐야? 이 타입이 구현체를 만들때 지켜야 하는 약속 인거야?

ㄴ꼬리질문9 여기서 추상화라는 말이 나왔는데 내가 아는 추상화는 서로의 의존도를 낮추는 것 즉, 서로에 대해 몰라도 되게 설계하는 것으로 알고 있어

내가 알고있는 추상화라는 개념이랑 이 OCP 원칙에서 말하는 추상화 개념이랑 많이 다른거야?

ㄴ꼬리질문10 알아도 되는 것만 남기고 나머지는 떼어내는 방법이 추상 타입을 통해 알아야 하는 약속을 정의해서

이 약속만 지키면되고 나머지는 이 약속을 지키는 선에서 내가 원하는 대로 구현을 할 수 있다는거야?

ㄴ꼬리질문11 리스코프 원칙은 OCP를 구현할때, 추상화 즉, 약속을 잘 설정해야 한다는 원칙인거야?

ㄴ꼬리질문12 타입 체크는 다 통과하는데 프로그램이 틀리게 동작한다는게 무슨 말인지 설명해줘

ㄴ꼬리질문13 인터페이스 메서드가 뭐야

ㄴ꼬리질문14 클라이언트는 자신이 쓰지 않는 메서드에 의존하도록 강요받으면 안된다는 말의 의미가 불필요한 인터페

이스 메서드로 인해 구현체가 불필요한 메서드를 사용하게 하면 안된다는 말이랑 같은거지?

ㄴ꼬리질문15 구현체가 사용하는 쪽 아니야?

ㄴ꼬리질문16 구현체가 메서드를 만든다고 했는데 그럼 인터페이스 메서드는 뭐야?

ㄴ꼬리질문17 추상에 의존해야 한다는게 무슨 의미야?

ㄴ꼬리질문18 둘 다 추상에 의존해야 하고 더 들어가서 추상에 의존하는 상위모듈은 하위 모듈에 의존하면 안된다는 말인거지?

ㄴ꼬리질문19 안에서 만들지 말고 밖에서 받아라는 말은

만들어진 것을 밖에서 받아서 쓰라는 말인거야?

...

AI 대화 내역 링크:

클로드: https://claude.ai/share/aae5216e-e1d1-4653-a4cf-fb7eba157ada

오늘 잘한 점: 원칙의 이름을 읽고 넘어가기보다 실제 코드에서 어떤 부분을 나누고, 누가 약속을 정하고, 누가 구현하고 호출하는지까지 이어서 확인했다. 특히 코드를 예시로 보면서 각각의 원칙들이 지켜지는 경우와 어겨지는 경우를 구분해서 이해했던것 같다.

오늘 부족했던 점: 실제 내가 본 코드들은 아주 간단한 코드들이었지만 조금 더 복잡한 코드에 내가 공부한 원칙들을 작 적용하기 위해서는 연습과 공부가 조금 더 필요할 것 같다는 생각이 들었다.

오늘 총평: 오늘 배운 다섯 원칙은 변경이 생겼을 때 관련된 부분만 고칠 수 있도록 구조를 잡는다는 방향으로 연결된다. SRP로 변경 이유에 따라 경계를 나누고, OCP로 약속을 통해 기능을 추가하며, LSP로 그 구현이 약속한 동작까지 지키는지 확인한다.

ISP는 사용하는 쪽에 필요한 약속의 범위를 정하고, DIP는 핵심 규칙과 세부 기술이 그 약속에 의존하도록 한는 것으로 이해를 했다.

1
0

여기에 파일을 드롭하세요