클린 코더스 - OOP

dev_Shawn·2022년 5월 13일
0

CleanCode

목록 보기
1/1

해당 내용은 백명선님의 강의 '클린 코더스'를 정리한 내용입니다.
이 포스팅의 모든 내용과 자료의 출처는 유튜브강의 자료입니다.

Intro

돌아가는 코드(기계가 읽는 코드)는 아무나 짤 수 있다.
하지만 사람이 이해할 수 있는 코드는 교육되고 훈련된 소프트 엔지니어만 짤 수 있다.
by. Martin Fowler

우리가 짜는 코드는 사람이 이해할 수 있는 코드인가? 기계만 알아보는 코드인가?

현실은 바쁘니까 TDD, Refactoring, Code Review 없이 개발
이렇게 개발한 프로젝트는 버그를 수정하며 또 다른 버그가 발생한다.

다짜고짜 개발부터 시작하면 처음엔 빨라 보일 수 있지만, 고민하고 설계하며 시작한 프로젝트에 비해 더욱 많은 시간을 버그 수정에 소요하게 된다.

개인적으로도 이런 경험을 한적이 있습니다.
과거에 이렇게 급하게 프로젝트를 수행한 적이 있었어요.
시연용이라 대충 굴러가게만 만들었던 프로젝트였는데 시간에 쫓기다보니 결국 해당 프로젝트가 프로덕트가 되어 기능을 추가하며 수정해갔죠. 워낙 급했던터라 짬을 내어 리팩토링을 할 여력도 없었습니다..
개발을 하던 당시에도 하나를 수정하면 여러 부분을 고쳐야했고, 버그를 수정하며 다른 버그들이 발생하여 힘들었던 기억이 있습니다.
그저 돌아가기만 하면 그만인 코드를 짜는 것이 추후에 얼마나 큰 비용을 지불해야 하는지 몸소 깨달은 순간이었습니다.

Why Clean Code

SW는 한번 작성되면 최소 10번 이상 읽힌다.
그래서 대충 돌아가게 작성하면 안되고 읽기 편하도록 작성해야한다.

대다수의 개발자는 어떤 업무를 할까?
매번 새로운 코드를 작성하며 개발하는 개발자보다 이미 작성된 코드를 읽고 수정하고 기능을 추가하는 유지보수성 업무를 하는 개발자가 더 많다.
그렇다면 개발을 하는데에 있어 더 중요한 것은 무엇일까?
돌아가는 코드를 빨리 만드는 것보다 잘 읽을 수 있는 코드를 만드는 것이 더욱 중요!!

Why OOP

절차지향

알아야 할 것이 적어 초기 진입이 쉽기 때문에 주로 절차 지향적으로 코드를 짠다.
하지만 모든 프로시저(함수)가 데이터를 공유하기 때문에 데이터에 변경이 생기면 관련된 모든 로직을 하나하나 찾아 수정해주어야 한다.

즉, 유지보수가 쉽지 않다.

객체지향

데이터와 데이터를 사용하는 기능(함수)을 객체에 Wrapping해두고 외부 노출을 최소화한다.(Encapsulation)
데이터의 변경이 해당 객체에만 영향을 미치고 외부에 영향을 미치지 않기 때문에 해당 객체만 수정하면 된다.
응집도가 높고 결합도가 낮다고 표현한다.

즉, 유지보수가 쉽다.

객체지향적으로 분석하고 설계하기가 쉽지 않다.
이게 어렵다면 절차지향적으로 분석 및 설계를 하고 구현하며 OOP로 Refactoring을 하면 된다.
Refactoring을 하지 않고 절차지향적인 구현에서 끝나게 된다면 유지보수가 매우 어려워진다.

Object / Role / Responsibility

객체를 볼 때 데이터로 보지 말고 기능으로 봐야한다.

  • 클래스의 이름은 '어떻게'가 아닌 '무엇'으로 정의해야한다.
    Ex> ArticleService(X) WriteArticleService(O), JsonRequestParser(X) RequestParser(O)

  • Role은 관련된 Responsibility의 집합
    Ex> Role : 시스템 사용자(사용자, 비회원 사용자, 운영자 등)
    Responsibility : 역할을 만족시키기 위해 시스템에서 제공되는 기능(행위)

  • 객체는 역할(Role)을 갖는다.

객체지향 설계 과정

요구사항 분석 등 일련의 선행과정이 다 되었을 때 클래스는 어떻게 나눌 것인가?

기능, 역할로 눈다.
이 결과를 도출하기 위해 ERD, 클래스 다이어그램등의 과정을 거친다.

FlowController에서 필요한 기능을 메서드를 선언하며 로직을 구현한 것이 아닌, 기능에 따라 FileReader, Encrypter, FileWriter 클래스로 분리시켰다.

이후 객체간 메시지 흐름을 연결한다.

FlowController는 흐름을 제어 하고 기능은 기능별로 분리시킨 클래스에서 구현한다.
장점 : 유닛테스트하기 쉬워진다, 클래스를 인터페이스로 추상화를 하면 추후에 구현체를 교체하기도 용이하다. 비즈니스 로직에는 변경이 적어진다.
ex> FileReader를 DBReader나 NetworkReader 등으로 변경이 필요할 때 쉽다.

이 두 과정을 반복한다.

Encapsulation

내부적으로 어떻게 구현했는지를 감춰 내부의 변화가 외부에 영향을 주지 않아야 한다. - 객체 지향의 기본

StopWatch 예제

시나리오

  • 스탑워치 기능에서 최초에는 milli seconds만 지원
  • 추후 nano seconds 기능이 추가됨.

절차지향 방식

비즈니스 로직에서 ProceduralStopWatch 클래스의 퍼블릭 필드(프라이빗이어도 setter가 있다면 똑같음)에 직접 접근하여 데이터를 변경하는 절차지향 방식의 코드.

기존 기능인 milli seconds 뿐 아니라 새로운 기능 nano seconds가 추가된다면 새기능을 위한 새로운 필드와 메서드 생성 필요.

ProceduralStopWatch 클래스의 변경으로 인해 비즈니스 로직에서 ProceduralStopWatch 클래스 필드에 값을 넣는 부분과 값을 불러오는 부분 모두를 수정해주어야 한다.

==> 요구사항의 변경이 데이터 구조의 변경을 유발하고, 이 데이터를 사용하는 모든 코드 역시 수정되어야 한다.

객체지향 방식

비즈니스 로직에서 직접 StopWatch 클래스에 필드에 접근하거나 데이터를 변경하지 않고 스탑워치의 기능을 구현한 StopWatch 클래스의 메서드를 호출하여 클래스가 직접 필드의 데이터를 변경하도록 한다.
milli seconds를 반환하는 getElapsedTime 메서드는 반환 타입을 Time 클래스로 한번 감싸서 리턴.

nano seconds 기능이 새로 추가되었을 때 StopWatch 클래스에서는 변경할 내용이 없고 시간을 리턴하던 Time 클래스에서 nano seconds를 리턴하는 메서드만 하나를 추가로 생성

비즈니스 로직 역시 측정된 시간을 받는 부분에서만 Time 클래스에서 새로 생성한 메서드로 변경.

==> 요구사항 변경에 있어 절차지향에 비해 코드 수정의 영향이 적다.

Tell, Don't Ask

객체의 데이터를 직접 요청 및 변경하는 것이 아닌 데이터를 잘 알고 있는 객체에게 기능을 수행시킬 것.

  • 절차지향 방식
if(member.getExpiredDate().getTime() < System.currentTimeMillis) {
	...
    ...
}

ASK(객체에게 객체 내부 데이터를 물어봐서 ask)
객체의 필드 값을 가져와서 직접 비교하는 로직.
추후에 비교 조건이 변경된다면 해당 조건 로직을 모두 찾아서 고쳐야한다.

  • 객체지향 방식
if(member.isExpired()) {
	...
    ...
}

TELL(객체에게 기능을 명령해서 tell)
객체에게 기능을 수행시켜 답을 얻음.
추후에 조건이 변경된다면 member클래스에서 조건만 바꿔주면 된다.

Command vs Query

  • Command(Tell)
    객체 내부 상태를 변경하는 메서드
    편이를 위해 어떤 결과를 반환할 수 있다.

  • Query(Ask)
    객체의 상태에 대한 정보를 제공하는 메서드
    객체의 상태를 변경하지 않는다.

이 두메서드가 합쳐져 있으면 안된다.(CQS, Command Query Seperation)
또한 상태를 두개 이상 변경하는 것도 좋지 않다.

Polymorphism(다형성)

한 객체가 여러가지(poly) 모습/타입(morph)을 가질 수 있다.

sub 타입 객체는 super 타입 객체에 담길 수 있다.

다형성 구현 방법

  • Inherit(extends) - 클래스 상속
  • Implements - 인터페이스 상속

인터페이스는 왜 쓸까?
implements하면 구현해야할 메서드만 늘어나고, 클래스는 extends하면 super 클래스의 메서드를 호출할 수 있으니 이게 더 재사용성이 좋은게 아닐까?

=> super 클래스의 기능을 sub 클래스가 호출하여 기능을 사용하는 것은 추후 유지보수가 어렵다.

객체지향에서의 좋은 재사용은 인터페이스를 통한 재사용을 말한다.
비즈니스 로직과 크게 관련 없는 로직, 변경사항이 생길 수 있는 로직을 외부에 구현하고 이를 인터페이스를 통해 바라보게 하면, 추후에 구현체를 갈아끼우기 쉽다.

Spring에서 인터페이스를 써야하는 이유

  • 현재 구현체가 하나여도, 나중에 두개 이상이 되지 않으리란 법이 없다.
  • 테스트가 쉽다.
  • 부가적인 기능 추가가 쉽다(Decorator Design Pattern)
  • 비즈니스 로직에서 인터페이스를 사용하면 디테일한 구현체 변경(A구현체에서 B구현체로 변경 등)에도 비즈니스 로직은 영향을 받지 않는다.

==> 위 내용은 해당 강의가 끝날 때 쯤 더욱 명확해질 것이라고 하셨다.

Abstraction

상세한 구현에 빠지다보면 상위 수준의 설계를 놓치기 쉬운데, 추상화를 통해서 상위 수준에서의 설계를 하는데 도움을 얻을 수 있다.

객체 지향의 핵심은 의존성 관리를 통해 고수준의 로직(비즈니스 로직)을 저수준의 로직(고수준의 로직을 구현하기 위해 필요한 기능)으로부터 보호하는 것이다.

만약 최초에 FtpLogCollector를 구현한 후 비슷한 기능을 하는 DBLogCollector를 구현할 때에는 인터페이스를 떠올리고 추상화를 해야한다.

Abstraction을 갖는 인터페이스를 사용하면 구현체가 변경되어도 인터페이스를 사용한 로직은 바꿀 것이 없다.
이것이 좋은 재사용이다.

programming to interface

  • 클라이언트 코드가 항상 인터페이스에 대한 레퍼런스를 사용해야 한다는 의미
  • 클라이언트는 구현 변경에 대해서 영향을 받지 않는다.
  • 인터페이스 signature가 사용 가능한 모든 행위를 보여준다.
  • 추상화를 통해 유연함을 얻을 수 있다.
  • 이렇게 사용하는 이유
    • 런타임에 프로그램의 행위를 변경하기 위해
    • 유지보수 측면에서 보다 나은 프로그램을 작성하기 위해

concreate 클래스를 사용할 경우 FtpLogCollector를 FileLogCollector로 변경해주기 위해서는 생성자와 리턴타입을 모두 수정해주어야 한다.

인터페이스를 사용하면 리턴 타입은 인터페이스로 감싸지기 때문에 생성자만 변경해주면 된다.

팩토리를 활용하는 방법도 있다고 하는데, 이렇게 하면 어떤 구현체를 create하는지 알 수가 없을 것 같다는 생각이 듭니다.
강의를 듣는 지금 시점에서는 이해가 되지 않지만 추후에 알게된다면 다시 정리하겠습니다!

DI(Dependency Injection, 의존성 주입)을 통해 비즈니스 로직을 구현한다면 테스트도 용이한 장점이 있다.

상속을 통한 재사용

super 클래스를 상속받은 sub 클래스는 super 클래스의 필드와 메서드를 호출할 수 있고, sub 클래스에서 추가적인 기능을 생성하여 사용하면 쉽게 코드를 재사용할 수 있다는 장점이 있다.
하지만 아래와 같은 큰 단점이 있다.

변경에 유연하지 못하다

super 클래스의 변경이 다수의 sub 클래스에 영향을 미치고, sub 클래스를 사용하는 많은 클라이언트 코드에까지 영향을 미친다.

이를 피하기 위해 아래와 같은 안좋은 사례가 발생하기도 한다.

  • 변경이 필요한 부분만 해당 sub 클래스에서 비슷한 메서드를 새로 생성
  • 두개 이상의 super 클래스의 기능이 필요할 때 다중 상속이 불가하여 1개는 상속받고 나머지는 따로 구현

상속 자체를 잘못 사용할 수 있다.

ArrayList를 상속받은 경우, ArrayList의 remove 메서드를 다이렉트로 호출하게되면 비즈니스 로직의 불변율이 깨지는 상황이 생긴다.

ArrayList를 상속 받는 것이 아닌 필드로 주입하여 사용한다면 위 사례와 같은 부작용을 방지할 수 있다.

강의를 수강하고

'지금까지 내가 알던건 그냥 객체지향의 사전적 의미였구나'하는 생각 뿐이었습니다.
클래스의 필드에 직접 접근하여 데이터를 가공하는 것의 문제점, 인터페이스를 통한 추상화와 거기서 오는 구현체 변경 용이성의 이점 등에 대한 내용을 들으며 객체지향의 진정한 재사용과 유지보수상 이점이 무엇인지 알 수 있었습니다.

방대한 내용이었지만 앞으로도 주기적으로 강의를 듣고 정리하며 완전히 나의 지식으로 만들어야 겠다는 생각을 했습니다.

profile
안주는 술 마실 때나

0개의 댓글