[인프런 워밍업 스터디] 추상과 객체 지향의 구체화
인프런 ‘Readable Code: 읽기 좋은 코드를 작성하는 사고법’을 수강한 후, 작성한 내용입니다.📌 1주차 기간 강의섹션 2. 추상코드를 잘 짠다는 것은?개발자라면 코드를 잘 짜기 위해 노력한다. ‘코드를 잘 짠다’라는 것은 무엇일까?코드를 잘 짠다는 것은 이해하기 쉬운 코드, 즉 ‘읽기 좋은 코드’라는 것에 동의하지 않는 사람은 없을 것이다.내가 짠 코드를 읽는 대상은 결국 나와 동료이다. 미래의 나와 동료를 위해 매 순간 읽기 좋은 코드를 작성하려고 노력해보자.클린 코드와 리팩토링의 가장 좋은 예시는 테스트 코드 생성 사이클로 볼 수 있다.리팩토링 대상/범위 확인기능 보장을 위한 테스트 코드리팩토링 & 테스트 코드로 검증클린 코드를 추구하는 이유그런데 클린 코드를 추구하는 이유는 뭘까?코드가 잘 읽힌다 ⇒ 이해가 잘 된다 ⇒ 유지보수하기 수월하다 ⇒ 시간과 자원이 절약된다!즉, 클린 코드는 우리의 시간과 자원을 절약해준다.클린 코드, 그리고 추상과 구체추상은 클린 코드를 관통하는 주제이다. 그렇다면 추상이란 뭘까?추상 : 중요한 정보는 가려내어 남기고, 덜 중요한 정보는 생략하여 버린다.추상의 반대편은 구체라고 볼 수 있다. 하나의 예시를 들어보자.질문A랑 주말에 뭐 했어?답변답변 1 : 식당에 예약해서 갔다왔어.답변 2 : 케치테이블을 통해 가고자 하는 식당에 들어가서 가고자하는 날짜와 시간을 선택하고 그때 식당에 들어가서 밥을 먹고왔어.답변 1과 답변 2는 같은 의미를 나타낸다. 그런데 표현은 다르다. 답변 2는 구체적인 사실이고 답변 1은 추상화된 문장이라고 할 수 있다.구체에서 추상으로 갈수록 정보는 함축되어 제거되고, 추상에서 구체로 갈수록 생략된 정보를 유추하고 재현한다.이를 개발자답게 컴퓨터 과학에 적용해보자.int는 4 byte이고, char는 1 byte이다. 같은 byte로 이루어지지만, 데이터를 어떻게 읽는지에 따라 달라진다. 즉, 데이터 개념에도 추상화가 존재하는 것이다!잠깐 프로그램의 정의에 대해 생각해보자. 프로그램은 다음과 같이 말할 수 있다.프로그램 = 데이터 + 코드위의 예시를 통해 데이터 개념에 추상화가 존재함을 알 수 있었다. 당연히 코드에도 추상화가 존재한다. 또한 데이터 + 코드에도 추상화가 존재한다.흔히 고수준/저수준 언어 라는 용어를 들어봤을 것이다. 고수준과 저수준이 나뉘는 이유는 추상화에 대한 수준을 나타내기 때문이다. 잊고 있을 수 있지만, 컴퓨터는 0과 1밖에 모른다!적절한 추상화는 복잡한 데이터와 복잡한 로직을 단순화하여 이해하기 쉽게 한다!만약 어느 도시에서 예약을 선택이라고 한다고 가정해보자. 그렇다면 답변 1은 다음과 같이 바뀔 것이다.식당을 선택해서 갔다왔어.이 말을 들으면 무슨 말인가 싶다. 말을 통해 유추하거나 재현하기 쉽지 않다. 단순히 말이 안된다고 생각할 수 있지만 이유에 대해 따져보자.추상화 과정에서 중요한 정보를 남기지 않았다.식당을 방문 날짜를 미리 정하는 예약이 아니라, 단순히 선택했다는 정보만 남김상대적으로 덜 중요한 정보를 남기고 중요한 정보를 삭제해석자가 동일하게 공유하는 Context가 없다.중요한 정보의 기준이나 도메인 영역 별 추상화 기준은 다를 수 있음해당 도시에 살았으면 이해 가능잘못된 추상화가 야기하는 사이드 이펙트는 상당히 크다!적절한 추상화란 해당 도메인의 문맥 안에서 정말 중요한 핵심 개념만 남겨서 표현하는 것이다.이름 짓기이름을 짓는다는 행위는, 추상적 사고를 기반으로 한다.단수와 복수 구분하기이름 줄이지 않기은어/방언 사용하지 않기좋은 코드를 보고 습득하기좋은 이름을 짓기 위해 노력하자!메서드 추상화메서드 이름으로 구체적인 내용을 추상화할 수 있어야 한다.서점에서 책을 샀다.서점에 가서 책을 고른다. 책을 계산대에 가져가 직원에게 건네주고 책 가격 금액을 전달하고 책을 얻었다.잘 쓰여진 코드라면, 한 메서드의 주제는 반드시 하나다!그런데 내용이 다음과 같다고 해보자.서점에서 책을 샀다.돈을 인출하고 가는길에 아이스크림을 사먹고, 책을 구매했다.추상화된 내용을 보고 구체적인 내용의 유추가 어렵다. 의미를 담을 수 있는 더 작은 단위로 쪼개야 한다.현금 인출아이스크림 사먹기서점에서 책 구입하기메서드 작성메서드 선언부반환타입메서드명파라미터메서드명추상화된 구체를 유추할 수 있는, 적절한 의미가 담긴 이름파라미터와 연결지어 더 풍부한 의미를 전달할 수도 있다.파라미터파라미터의 타입, 개수, 순서를 통해 의미를 전달파라미터는 외부 세계와 소통하는 창어떤 재료가 필요한지 알려주는 역할이다. 외부 세계한테 필요한 재료를 요구한다.반환타입메서드 시그니처에 납득이 가는, 적절한 타입의 반환값 돌려주기반환 타입이 boolean인데, 이게 이 메서드에서 무엇을 의미하는거지?void 대신 충분히 반환할 만한 값이 있는지 고민해보기반환값이 있다면 테스트도 용이해진다.코드의 줄 수가 많아서 추상화하는 것이 아니다! 같은 라인 수를 가지더라도 추상화할 수 있다.추상화 레벨메서드를 추출한다 ⇒ 외부 세계와 내부 세계를 나누고, 추상화 레벨이 나뉜다.하나의 세계 안에서는, 추상화 레벨이 동등해야 한다!게임 시작 멘트 출력게임 초기화게임 보드 보여주기게임 상태가 1이면게임 종료 메시지 출력1, 2, 3, 4를 쭉 읽다보면 4에서 멈칫하게 된다. 게임 상태 = 1이라는 것의 의미에 대해서 의문이 생기게 된다. 즉, 읽는 사람이 해석을 하게 만든다. 따라서 동등한 추상화 레벨을 맞춰야 한다.게임 시작 멘트 출력게임 초기화게임 보드 보여주기게임을 이겼다면,게임 종료 메시지 출력전보다 훨씬 읽기 수월하다.메서드로 추출한다는 것은 로직이 복잡하거나 의미를 부여할 수 있어서도 맞지만, 추상화 레벨을 동등하게 맞춤으로써 읽는 사람으로 하여금 자연스럽게 이해할 수 있게 한다.매직 넘버, 매직 스트링의미를 갖고 있으나, 상수로 추출되지 않은 숫자, 문자열 등상수 추출로 이름을 짓고 의미를 부여함으로써 가독성, 유지보수성 증가섹션 3. 논리, 사고의 흐름인지적 경제성코드를 작성할 때 인지적 경제성을 추구하도록 작성해보자!💡 인지적 경제성? - 최소의 인지적 노력으로 최대의 정보 제공 - 한 번에 한 가지 일 즉, 최소한의 인지만 가져가서 최대의 효율을 내보자라는 뜻!코드를 읽는 사람의 메모리를 효과적으로 쓸 수 있도록 해서 읽기 좋은 코드를 작성하자.Early Returnelse if ⇒ 이전의 if에 대해서 생각한다.else ⇒ 앞의 조건들을 모두 알아야 안다.즉, 앞 선 정보들을 모두 기억하고 있어야 함!Early Return을 이용하자!else가 사라짐앞의 코드를 신경 쓸 필요가 없다.사고의 depth 줄이기중첩 분기문, 중첩 반복문2중 for문 내에 if 문이 있다면?3 depth ⇒ 사고의 depth도 많을 것!메서드 추출을 통해 중첩 분기문/반복문을 없애자! 그렇다고 무조건 1 depth로 만들라는 것은 아니다.추상화를 통한 사고 과정의 depth를 줄이는 것이 중요2중 중첩 구조로 표현하는 것이 사고하는 데 더 도움이 된다면 그대로 놔두자.사용할 변수는 가깝게 선언하기사용할 변수는 가깝게 선언하기사용할 변수가 너무 멀리있다면 되돌아가도록 유도한다.공백 라인도 의미를 가진다!복잡한 로직의 의미 단위를 나누어 보여줌으로써 읽는 사람에게 추가적인 정보를 전달할 수 있다.우리나라 글도 문단이 있지 않은가!부정어를 대하는 자세!가 있으면 조건을 먼저 이해하고, 이에 반대되는 조건을 생각하게 된다.가독성이 떨어지고 비틀어서 생각하게 함읽으면서 바로 생각할 수 있게 조건을 작성하자.해피 케이스와 예외 처리예외가 발생할 가능성 낮추기검증이 필요한 부분 = 외부 세계와의 접점의도한 예외와 예상하지 못한 예외 구분사용자에게 보여줄 예외개발자가 보고 처리해야 할 예외항상 NPE가 일어나지 않는지 고려하자.메서드 설계 시 return null 자제Optional은 비싼 객체이고 반환 타입에 사용한다.orElse, orElseGet, orElseThrowe.printStackTrace는 실무에서는 안티패턴이다!섹션 4. 객체 지향 패러다임추상의 관점으로 바라보는 객체 지향절차 지향정해진 순서 차례대로 처리하는 프로그래밍객체 지향객체를 만들어서 객체들 간의 협력을 통해 이루어지는 프로그래밍함수형순수 함수를 정의Side Effect가 없는 함수A ⇒ 항상 A에 대한 정해진 결과가 나오는 것객체란?추상화된 데이터 + 코드객체간의 협력과 객체가 담당하는 책임객체 지향의 특징 ⇒ 캡슐화, 추상화, 상속, 다형성정말 이를 이해하고 적용하고 있는지 스스로 질문해보자!코드 레벨에서 잘 녹여서 쓸 수 있어야 한다.관심사의 분리유지보수가 원활해짐높은 응집도와 낮은 결합도같은 관심사는 응집도가 높아야 한다.각 관심사끼리는 결합도가 낮아야 한다.객체 설계객체로 추상화는..비공개 필드(데이터), 비공개 로직(코드)공개 메서드 선언부를 통해 외부 세계와 소통객체의 책임이 나뉨에 따라 객체 간 협력 발생!객체를 통해절차 지향에서 보이지 않았던 개념 가시화관심사가 한 군데로 모이기 때문에 유지보수성 증가객체를 사용하는 입장에서 보다 높은 추상화 레벨에서 도메인 로직을 다룰 수 있다!!주의사항1개의 관심사로 책임이 분리되었는지 스스로 질문해보자.객체 생성 시, 유효성 검증이 가능setter는 지양, 필요할 경우 메서드명에 의도를 드러내자.getter는 필요한 경우에만.필드 수는 적을 수록 좋다.불필요한 데이터를 굳이 가지고 있지 말자. ex) 주문의 총 가격도메인 지식은 만드는 것이 아니라 발견하는 것!SRP (단일 책임 원칙)하나의 클래스가 하나의 책임만 갖도록 설계해라!하나의 클래스는 한 가지의 변경 이유만 가진다.객체가 가진 공개 메서드, 필드, 상수 등은 해당 객체의 단일 책임에 의해서만 변경 되는지 질문하자.변경이유 = 책임관심사의 분리높은 응집도, 낮은 결합도응집도 = 클래스나 모듈 내 요소들이 서로 긴밀하게 연관되어 있는 정도결합도 = 두 개 이상의 객체가 협력할 때, 한 객체가 변경되었을 때 다른 객체가 영향받는 정도각 객체가 독립적인 책임을 가지도록 잘 쪼개진다.객체를 하나의 변경 지점, 하나의 책임으로만 가지도록 설계하자!책임을 발견하기 어려움경계선이 사람마다, 도메인마다 다를 수 있음설계한 객체가 하나의 책임만 가지고 있는지 끊임없이 질문책임을 보는 눈을 기르자!객체를 설계했을 때 책임이 잘 응집되었는가?, 단일 책임만을 가지고 있는가?OCP (개방-폐쇄 원칙)확장에는 열려 있고, 수정에는 닫혀 있어야 한다.기존 코드 변경 없이 기능 확장이 가능해야 함!추상화와 다형성을 활용하여 OCP를 지키자.새로운 요구사항이 추가되었을 때, 기존 코드가 과도하게 변화된다?OCP를 지키지 못하고 있는게 아닌지 생각하자.LSP (리스코프 치환 원칙)상속 구조에서, 부모 클래스의 인스턴스를 자식 클래스의 인스턴스로 치환할 수 있어야 한다.자식 클래스는 부모 클래스의 책임을 준수하고 행동을 변경하지 않아야 한다.부모가 일하는 곳에 자식이 가더라도 문제가 없어야 한다!LSP 위반상속 클래스를 사용할 때 오동작 발생ISP (인터페이스 분리 원칙)클라이언트는 자신이 사용하지 않는 인터페이스에 의존하면 안 된다.기능 단위로 인터페이스를 잘게 쪼개라!내가 구현하려는 인터페이스의 기능 명세에 사용하지 않는 기능이 있다.자신이 사용하지 않는 인터페이스에 의존하게 됨불필요한 의존성으로 인해 결합도가 높아진다.특정 기능의 변경이 여러 클래스에 영향을 미친다.DIP (의존성 역전 원칙)상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안 된다.둘 모두 추상화에 의존해야 한다.의존성의 순방향 : 고수준 모듈이 저수준 모듈을 참조하는 것의존성의 역방향 : 고수준, 저수준 모듈이 모두 추상화에 의존하는 것저수준 모듈이 변경되어도, 고수준 모듈에는 영향이 가지 않는다.기능을 추상화하여 고수준 모듈은 추상화된 스펙만 참조추상화를 중간에 두고 의존하도록 하자!저수준 모듈이 자유롭게 변경되도 고수준 모듈에는 영향 X💡스프링에서의 DI, IoC DI = Dependency Injection - 필요한 의존성을 직접 생성하는 것이 아니라, 외부에서 주입받는다. - 제 3자(스프링 컨텍스트)가 런타임 시점에 의존성을 주입해준다. IoC = Inversion of Control - 프로그램의 흐름을 프레임워크가 담당 - 빈 생성, 의존성 주입, 생명주기 관리를 스프링 컨테이너가 해준다. 섹션 5. 객체 지향 적용하기상속과 조합상속보다 조합을 사용하자.상속은 시멘트처럼 굳어지는 구조수정이 어렵다.부모와 자식의 결합도가 높다.자식이 부모에 대해서 다 알고 있어야 함부모 수정 ⇒ 자식이 다 영향을 받는다.자식은 부모에 있는 필드를 직접적으로 알고 있음조합과 인터페이스를 활용하는 것이 유연한 구조상속을 통한 코드의 중복 제거가 주는 이점보다, 중복이 생기더라도 유연한 구조 설계가 주는 이점이 더 크다.Value Object도메인의 어떤 개념을 추상화하여 표현한 값 객체값으로 취급하기 위해 불변성 동등성 유효성 검증 등을 보장해야 한다.불변성 : final 필드, setter 금지동등성 : 다른 인스턴스여도 내부 값이 같으면 같은 값 객체로 취급유효성 검증 : 객체가 생성되는 시점에 값에 대한 유효성 보장VO vs EntityEntity식별자 O식별자가 아닌 필드의 값이 달라도, 식별자가 같으면 동등한 객체로 취급!equals & hashcode도 식별자 필드를 통해 재정의VO식별자 X내부의 모든 값이 다 같아야 동등한 객체일급 컬렉션일급 시민다른 요소에게 사용 가능한 모든 연산을 지원하는 요소변수로 할당될 수 있다.파라미터로 전달될 수 있다.함수의 결과로 반환될 수 있다.일급 함수함수형 프로그래밍에서 함수는 일급 시민함수는 변수에 할당될 수 있고, 인자로 전달될 수 있고, 함수의 결과로 함수가 반환될 수도 있다.일급 컬렉션컬렉션을 포장하면서, 컬렉션만을 유일하게 필드로 가지는 객체컬렉션을 다른 객체와 동등한 레벨로 다루기 위함단 하나의 컬렉션 필드만을 가진다.컬렉션을 추상화하며 의미를 담고, 가공 로직의 보금자리가 생긴다.가공 로직에 대한 테스트도 작성 가능getter로 컬렉션을 반환할 일이 생긴다면, 외부 조작을 피하기 위해 새로운 컬렉션을 만들어 반환해야 한다.Enum의 특성과 활용enum은 상수의 집합상수와 관련된 로직을 담을 수 있음특정 도메인 개념에 대해 종류와 기능 명시변경이 잦은 개념은 DB로 하는게 나을 수도 있다.enum은 코드 ⇒ 배포를 해야만 변경할 수 있다.변경이 잦은 요소라면 DB로 관리하자.숨겨져 있는 도메인 개념 도출하기도메인 지식은 만드는 것이 아니라 발견하는 것객체 지향은 현실을 100% 반영하는 도구가 아니라, 흉내내는 것현실 세계에서 쉽게 인지하지 못하는 개념도 도출해서 사용해야 할 때가 있다.설계할 때는 근시적, 거시적 관점에서 최대한 미래를 예측하고, 시간이 지나 틀렸다는 것을 인지하면 언제든 ㄷ돌아올 수 있도록 코드를 만들어야 한다.완벽한 설계 X, 그 당시의 최선이 있을 뿐 📌 1주차 수강 후 느낀점강의 제목들을 보면, 사실 개발자라면 다 한번씩 들어봤을 내용이다. SOLID 원칙, 추상화, VO, Enum 등등 누군가 질문하면 그럴듯한 답변을 할 수 있는 주제다. 그런데 ‘이 내용들을 깊이 있게 이해하고 있을까?’ 라는 질문에는 아니었던 것 같다.이러한 개념들을 직접 코드 레벨에 적용하며 개선하는 과정에서 배울 점이 많았다. 내가 알고 있던 깊이는 상당히 얄팍했다. 가끔 헷갈리면 검색해서 ‘아 대충 이런내용이었지.’하고 금방 닫곤 했는데, 이번 기회에 직접 경험해보며 내 지식들을 구체화시킬 수 있었다. 개발자가 가볍게 여기고 넘어갈 수 있는 주제들에 대해 다시 생각하며, 기본기의 중요성을 알 수 있었다! 그리고, 기존에 프로젝트하며 고민했던 지점이 있었다. 추상화를 할 때 ‘같은 라인 수를 가져도 굳이 해아하나?’ 생각했었는데, 나한테 해답이 되는 1주차였던 것 같다!! 전부터 ‘한 번 들어야지!’했던 강의인데 워밍업 스터디를 계기로 수강하길 잘한 것 같다🙂 📌 1주차 미션Day 2 : 추상과 구체 예시 생각해보기뭔가 추상화를 실생활에 생각해본 적은 없었는데, 느낌이 새로웠다. 되게 개발스러운 단어라고만 생각했는데 추상은 우리의 삶에도 가까이 적용되는 단어라는 것을 느꼈다.농구에서 슛을 한다고 해보자. 이때 무릎을 구부리고, 팔꿈치를 올리고, 무릎을 다시 피고, 팔꿈치를 다시 피고, 손목을 피며 공을 던진다. 위 여러 과정들이 ‘슛’으로 추상화된다. 우리 삶의 추상화는 상당히 자주 쓰인다!Day 4 : [섹션 3. 논리, 사고의 흐름] 내용을 중심으로 리팩토링하기이전에는 코드를 리팩토링하면, 되게 느낌적으로 다가갔던 것 같다. ‘뭔가 이게 나은 것 같긴한데..’하며 접근하거나 단순히 코드 라인을 줄이는 등 이유와 근거가 빈약했다. 하지만 Day 4 미션을 통해 리팩토링의 이유와 근거를 통해 리팩토링을 진행해보았다.필요한 정보를 빼내 추상화부정어구 최소화케이스 확인위 과정을 통해 미션에서 주어진 코드를 읽기 좋게 개선하였다. 읽으면서 사고의 흐름이 멈칫하거나 꼬일 수 있던 코드를 바로 읽어나갈 수 있게 작성하려고 노력했다.강의를 들으면서 내가 납득됐던 내용에 대해 직접 리팩토링에 적용하여 ‘읽기 좋은 코드’를 만들어 나갈 수 있었다. 단순히 불필요한 작업을 없애는 것만이 리팩토링이 아니라, ‘읽기 좋은 코드’를 만드는 것도 중요한 리팩토링이라는 것을 깨달았다!