/ TDD, ARCHITECTURE, CLEANCODERS

아키텍처와 클린코드 그리고 TDD (1)

객체지향에 대해 정리하고, 전체적인 맥락을 짚어본다.

이 포스팅에 나오는 내용 대부분은 백명석님의 유튜브영상에서 발췌된 내용입니다. 개인적인 학습, 리마인드를 위해 기록한 내용이며 자세한 내용은 해당 유튜브 강좌를 보시길 추천드립니다.

백명석님의 클린코더스 강의 다시보기

우리의 목표

  • 잘 동작하는 코드
  • 읽을 수 있는 코드
  • OOP/SOLID

잊지 말아야 할 것.

  • 기계가 이해하고 동작하는 코드는 누구나 작성할 수 있다. 사람이 읽을 수 있는 코드는 숙력된 개발자만 가능하다. -마틴파울러- 동작하는 코드와 읽을 수 있는 코드의 차이는 아주크다.
  • 우선동작하는 코드를 만들고, 출시후 아름답게 만든다.는 불가능하다. 나중은 없다. 혹여나 그렇게 만들어진 코드를 정리하는 작업을 한다고 했을때 리팩토링이 아니고 리스트럭처링이 되며, 새로운 버그를 만들어 낸다. 또한 겁나기 때문에 묻어두게 된다.

Why Clean Code?

  • 코드는 최소 10번이상 읽힌다. 우린 항상 새로운 코드를 만들지 않는다. 대부분 수정하고 추가하고 확장한다.
  • 기계가 이해할 수 있는 코드는 바보도 작성 할 수 있다(크흡.)

Why OOP?

  • 절차지향적인 코드는 모든 프로시저가 데이터를 공유한다. 프로시저 하나가 변경되면 모든 프로시저가 영향을 받고 함께 수정되어야한다.
  • 객체지향적인 코드는 외부에 노출된 인터페이스만 변경 되지 않는다면, 데이터를 조작하는 코드의 변경은 외부에 영향을 미치지않는다. 프로시저를 실행하는데 필요한 만큼 데이터만 가진다. 대부분의 사람들은 절차적으로 사고한다. 나 역시 절차지향적이다. 때문에 훈련이 필요하다. 네이티브 본도 있긴하다. 총을 가진 누군가 내 코드를 유지보수 할 예정이라 생각하라 심지어 그는 미치광이다.

객체

  • WriteArticleService 는 맞지만 ArticleWriteService는 틀렸다.
  • Class 이름은 무엇으로 정의해야한다. RequestParser(O) JsonRequestParser(X) 나중에 Json을 Xml로 바꾼다면? 이름만 바꿔서 되지 않는다. 이름만 바꿔도 참조중인 코드에 변경사항이 생긴다. 또한 클래스 이름을 잘못 지으면 동작까지 달라진다. RequestParser를 구현하고, Reader의 구현을 Json을 참조하여 구현체를 만들면 ReqestParser를 참조하던 클래스에 변경사항 없이 개발 가능하다.

    무엇으로 정의하라, 어떻게로 정의하지 말고.

  • 역할은 관련된 책임의 직합이다.
  • 객체는 역할을 가진다.

객체지향 설계 과정

  1. 내부에서 필요한 데이터 선별
  2. 객체간 메시지 흐름을 연결

절차적인 설계

FlowController{
fileRead()
encrypt()
fileWrite()
}

객체지향적인 설계

- FlowController
  ᆫ FileReader(read:byte[])
  ᆫ Encrypter(bytes:byte[]):byte[]
  ᆫ FileWriter(bytes:byte[])

기능을 제공한 객체로 분리(선별)하고, 객체간 메시지 흐름을 연결한다. 머리로 하지말고 손으로 하라.

Encapsulation

내부적으로 어떻게 구현했는지 감춰 내부의 변경(데이터,코드)이 클라이언트의 변경이 되지않도록 하여 코드 변경에 따는 비용 최소화를 목표로 한다. 변경된 내용은 모두 비용이다.

Tell, Don’t ASK

데이터를 요청해서 변경하고 저장하지 말고, 무슨 기능을 실행하라. 아래의 코드를 보면 expire 조건이 변경되면 모든 코드를 찾아서 변경해야한다.

if(member.getExpiredDate().getTime()<System.currentTimeMillis){...   // (Bad)
if(member.isExpired()){...                                           // (Good) 

Command Vs Query

Command(Tell) 객체 내부의 상태를 변경 원칙은 데이터반환을 하지 않지만, 편의를 위해 어떤 결과를 반환 할 수 있다. Query(Ask) 객체의 상태에 대한 정보를 제공. 하지만 값의 변경은 해선 안된다. 메서드명을 본뒤 코드를 열어보고 당황할 만한 일을 해선 안된다.

Polymorphism

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

ZetMotocycle zm = new ZetMotocycle(); `Zet` `Motocycle`의 모습
Motocycle mc = new Motocycle(); `Motocycle`의 모습
ZetEngine ze = new ZetEngine(); `ZetEngine`의 모습

그런데 아래처럼 쓸수 있게 된다. 이게 핵심이다.

Motocycle mc = new ZetMotocycle();
ZetEngine mc = new ZetMotocycle(); 

상속은 두가지가 있다.

  • 구현 상속 : 슈퍼타입의 구현을 재사용. 슈퍼타입에서 작성했던 코드를 재사용 할 수 있다. 의존성이 높아진다.
  • 인터페이스 상속 : 타입 정의만 상속, 상속은 객체에게 다형성을 제공. 인터페이스를 참조하는 코드를 재사용 할 수 있다.

추상화와 개발자의 습성

  • 개발자들은 습성상 상세한 구현에 빠지다 보면 상위 수준의 설계를 놓치기 쉬운데, 추상화를 통해 상위 수준에서 설계를 하는데 도움을 얻을 수 있다.
구현 추상화
디렉토리에서 파일을 읽어와 메모리에 저장하고 로그 수집
한줄 한줄 정규표현식으로 파싱하고 로그 분석
그 결과를 DB에 저장하고 결과 저장

내가 테스트할려는 코드들에게 모두 Mocking을 만들기 쉬워진다. 서비스 로케이터 패턴 VS 의존성 주입(DI) 에 대해 고민해보자.

상속을 통한 재사용

서브클래스는 수퍼클래스의 기능을 재사용. 추가적인 기능을 제공하끼 쉽다.
변경이 유연함 측면에서 치명적 단점.

  • 수퍼클래스의 변경이 다수의 서브 클래스에 영향을 미침
  • 유사한 기능의 확장에서 클래스의 개수가 불필요하게 증가 할 수 있다.
    • 2개 이상의 수퍼클래스의 기능이 필요한 경우 다중상속 불가. 1개를 상속받고 1개는 내가 구현?!X
  • 상속자체를 잘 못 사용 할 수 있다.

Composition(delegation)

class Calculator(private val strategy: PriceStrategy) {
  calculate() {
    this.strategy.apply(price)
  }
}

PriceStrategy은 얼마든지 다양한 형태의 구현체로 변경가능하고, 이 구현체의 변경은 Calculator 클래스에 영향을 끼치지 않는다. 인터페이스에 의한 의존성을 주입받음으로써 처음 Unit Test를 할 때 가장 까다로운 부분인 Mock을 생성하기 쉽다. 이는 TDD에 기본이 된다.

inheritance vs implementation -> implementation!

Search

Get more post