본문 바로가기
Study/디자인 패턴의 아름다움

[디자인 패턴의 아름다움] 1. 개요

by Nahwasa 2024. 2. 24.

스터디 메인 페이지

목차

    - ☆ 표시가 붙은 부분은 스터디 중 나온 얘기 혹은 제 개인적인 생각이나 제가 이해한대로 적어놓은 것으로, 책에 나오지 않는 내용입니다. 따라서 책에서 말하고자 하는 바와 다를 수 있습니다.

    - 모든 이미지의 출처는 디자인패턴의 아름다움(왕정 저) 책 입니다.

     

     


     

    CHAPTER 01. 개요

    • 이 책의 주된 목적은 고품질의 코드를 작성할 수 있도록 돕는 것

     

    1.1 코드 설계를 배우는 이유

    • 데이터 구조와 알고리즘이 효율적인 코드를 작성할 때 필요한 것이라면, 코드 설계에 대한 지식은 확장성과 가독성이 높아 유지 보수가 용이한 고품질의 코드를 작성할 때 필요하다.

     

    고품질의 코드 작성

    • '나쁜' 코드를 작성하고 싶은 엔지니어는 없을 것이지만, 의외로 많은 소프트웨어 엔지니어가 만족스럽지 못한 코드를 작성하곤 한다. -> 다른 이유들도 있지만, 그보다 어떤 코드가 고품질의 코드인지 알지 못하기 때문이다.
    • 이 경우, 오랫동안 코드를 작성했더라도 코딩 스킬은 향상되지 않아 처음 코딩을 시작할 때와 스킬이 별반 다르지 않다.
    • 높은 품질의 코드를 작성할 수 있도록 스킬을 향상하려면 -> 먼저 코드 설계에 대한 이론적인 지식을 갖춰야 한다.`

     

    복잡한 코드 개발 다루기

    • 소프트웨어를 개발할 때 만나는 어려움 두 가지
      • 매우 높은 수준의 기술이 필요한 경우 : 코드의 양이 반드시 많은 것은 아니지만, 자율 주행이나 비디오 인식 등 상대적으로 고도의 기술이 필요한 경우.
      • 복잡한 비즈니스를 갖춘 대규모 프로젝트 : 단순하지만 코드의 양이 매우 많은 물류 시스템, ERP 등.
    • 이 책에서는 후자의 경우(소프트웨어 개발의 복잡성)을 다루는 방법에 중점을 둠.
    • 대부분의 소프트웨어 엔지니어는 프레임워크를 사용하여 비즈니스 요구 사항에 따라 코드를 채우는 것이 일반적인 업무이다. 이 경우 대단한 코드 설계 능력을 요구하지는 않는다.
    • 대부분의 소프트웨어 엔지니어는 수천 줄의 코드를 유지 보수할 수 있지만, 코드가 수만에서 수십 수백만 줄로 늘어나면 소프트웨어의 복잡성은 기하급수적으로 증가한다. 이럴 때 코드 설계와 관련된 지식이 활용될 여지가 있다.
    • 지속적으로 아래 질문들과 같이 코드 설계를 의식하고 개발하면, 복잡한 코드 설계와 개발을 할 때도 점점 더 쉽게 문제를 해결해 나가게 된다.

     

    [ 질문들 ]

    • 계층화와 하위 모듈화 방법은 무엇인가?
    • 클래스를 어떻게 나누는 것이 좋은가?
    • 각 클래스에는 어떤 속성과 메서드가 있는가?
    • 클래스 간의 상호 작용을 설계하는 방법은 무엇인가?
      •   위 내용들과 관련해서 현재까지 가장 와닿는 표현을 해준 책은 '오브젝트'인 것 같다. 오브젝트에 이런 내용이 있다. "객체를 기능을 구현하기 위한 협력하는 공동체의 일원으로 생각해야 한다. 협력자로써의 객체가 어떤 상태와 행동을 가지는지 결정한 후, 공통된 특성을 가진 객체들을 타입으로 분류하고 타입을 기반으로 클래스를 구현해야 한다.". 오브젝트 책 내용도 정리했었는데, '[오브젝트] 책 내용 전체 정리' 에 있다.
    • 상속이나 연관을 사용하는 것이 옳은가?
    • 인터페이스나 추상 클래스를 사용하는 것이 옳은가?
    • 결합도가 높은 코드와 낮은 코드는 무엇인가?
      • ☆ 다른 녀석에 대한 정보를 많이 아는 경우가 결합도가 높다고 생각된다. 예를들어 영화 예약을 위한 클래스에서, 고객정보 클래스에서 예매한 영화 정보 클래스를 얻은 후, 그 영화 정보 클래스에서 요금을 얻고, 조건문으로 할인까지 해준다. 이러면 이미 영화 예약을 위해 '고객정보' 클래스도 알고, '영화정보' 클래스도 알고, 심지어 할인까지 하는 결합도 무지 높은 녀석이 된다. 
    • 디커플링을 달성하는 방법은 무엇인가?
    • 싱글턴 패턴이나 정적 메서드를 사용하는 것이 옳은가?
    • 객체를 생성할 때 팩터리 패턴을 사용하는 것이 옳은가?
      • 추상화에 의존하려 한다면 옳다고 본다. 추상화에 의존하는데 팩토리나 DI 같은거 없이 스스로 생성하면 의미 없어보인다.
    • 가독성을 유지하면서 확장성을 향상하기 위해 디자인 패턴을 도입하는 방법은 무엇인가?
    • ☆ 위 질문에 언제나 옳은 명확한 답은 없을 것 같다. 그래도 서로 얘기해보기에 좋은 주제들인 것 같다. 스터디 마지막에 처음으로 돌아와 서로 저 내용들에 대해 별 문제없이 토론이 가능하다면 이 스터디는 성공적이라 생각된다.
    • ☆ 이러한 요소들 중 일부를 생각해가며 TDD 스터디에서 라이브코딩을 했던 내용이 블로그에 있다.  'TDD, Mock, SOLID 얘기 - 도시 가스 요금 계산'에 있다.
    • ☆ 사실 위 모든 질문들이, "그 때 그 때 달라요" 라고 대답할 수도 있을 것 같다. 은총알은 없다. 트레이드 오프에서 어떤걸 선택하냐이지. 애초에 언제나 적용되는 정답이 있었으면 이런 책 필요없었겠지..

     

    프로그래머의 기본 능력

    • 소스코드를 읽을 때 종종 그것이 어떤 의미인지 이해하지 못하는 경우가 있다. -> 이해하기 위해 필요한 기본적인 기술적 소양과 능력이 부족하기 때문이다.
    • 우수한 오픈소스 프로젝트, 프레임워크 등은 코드의 확장성, 유연성, 유지 보수성을 보장하기 위해 더 많은 디자인 패턴과 설계 원칙이 코드에 사용된다. -> 이러한 디자인 패턴이나 설계 원칙을 이해하지 못한다면 설계 의도를 완전히 이해하지 못할 수 있고 코드를 이해하는 데에도 시간이 오래 걸릴 수 있다.
      • 싱글턴 모른채로 스프링 IoC 이해하기, 프록시 모른채로 스프링의 @Transactional 이해하기?! 어려울 것 같다.
    • 기초적인 기술의 깊은 이해가 없다면 최신형 스텔스기를 눈앞에 두고도 본질을 이해하지 못해 수박 겉핥기만 하게 될 뿐이다.

     

    경력 개발에 필요한 기술

    • 주니어 개발 엔지니어는 먼저 운영 프레임워크, 개발 도구, 프로그래밍 언어에 대해 배운 다음, 몇 가지 프로젝트를 통해 기술을 연습하면 기본적으로 일반적인 개발 작업에 투입될 수 있다.
    • 하지만 평생 주니어가 아닌 수석 엔지니어로 성장하고, 더 높은 성과와 발전을 달성하기 원한다면 기본적인 능력 향상과 기본적인 지식 습득에 꾸준히 신경을 써야 한다.
      • 자신이 고품질의 코드가 무엇인지, 고품질의 코드를 어떻게 작성해야 하는지 모른다면 어떻게 다른 사람을 이끌고 설득할 수 있겠는가?
    • 코드의 품질이 낮으면 버그가 빈번하게 발생하고, 버그의 원일을 찾기 어려우며, 팀 전체가 낮은 수준의 버그 수정과 잘못된 코드의 패치 같은 무의미한 작업을 계속 해야 한다.
      • 깨진 유리창 이론 : 깨진 유리창 하나를 방치해 두면 그 지점을 중심으로 범죄가 확산되기 시작한다는 이론
      • "나쁜 코드는 나쁜 코드를 유혹한다" (클린코드)
      • 이미 코드가 더럽다면, 유지보수 시 더럽고 편한 코드로 작성하고 싶어지는게 당연하다.
    • 잘 설계되고 유지 보수가 쉬운 시스템은 더 의미 있는 일을 할 수 있도록 성장한다.
      • ☆ 이것도 참 맞는말이다. 일단 현재 문제없이 잘 동작하고 있어야 뭐 테스트 커버리지를 최대한 올리던지, 네트워크 보안을 개선한다던지 등등 그런걸 할 수 있을 것 같다.

     

    1.2 코드 품질 평가 방법

    • 코드의 품질을 설명하기 위해 '좋은' 또는 '나쁜' 같은 수식어를 사용하는 것은 일반적이지만, 이 경우 충분한 답변이 되지 않는 경우가 많다.
    • 코드가 잘 작성되었는지 여부를 판단할 수 없다면 아무리 많은 코드를 작성해도 코딩 능력은 크게 향상되지 않을 것이다.
    • 이 책에서는 일반적으로 사용되는 7가지 중요한 평가 기준인 유지 보수성, 가독성, 확장성, 유연성, 간결성, 재서용성, 테스트 용이성에 대해 집중적으로 살펴본다.
    • ☆ 1.2 읽고 드는 생각은 7가지 기준조차 서로 상반되는게 있다는 점. 확장성이 올라가면 간결성은 줄어들 수 밖에 없음. "설계는 균형의 예술이다. 훌륭한 설계는 적절한 트레이드 오프의 산물이다." (오브젝트). 가 생각났음.

     

    유지 보수성

    • 유지 보수성이 높다 = 기존의 코드 설계를 손상시키거나 새로운 버그를 발생시키지 않고도 빠르게 코드를 수정하거나 추가할 수 있는 상태를 말한다.
    • 유지 보수가 쉽지 않다 = 코드를 변경하거나 새로 추가하면 새로운 버그가 발생할 위험이 크고 전체적인 기능이 완벽하게 동작할 때까지 시간이 걸린다`
    • 코드의 유지 보수성은 프로젝트의 코드 규모, 비즈니스의 복잡성, 기술의 복잡성, 문서의 포괄성, 팀원의 개발 수준 등 다양한 요인과 관련 있다.
    • 많은 요인들이 공통적으로 작용한 결과라 수치화하기 어렵다. 코드가 간결하고 가독성이 높으며 확장성이 높다면 유지보수도 쉬워진다. 더 자세하게는 명확하게 계층화되어 있으며, 높은 모듈성, 높은 응집도와 낮은 결합도를 가지고 구현보다 인터페이스 기반의 설계 원칙을 고수한다면 코드의 유지 보수가 쉽다.

     

    가독성

    • 코드를 읽은 횟수는 코드를 작성하고 실행하는 횟수보다 훨씬 더 많다. 코드에 대해 잘 숙지하지 못한다면 잘못된 판단으로 새로운 버그를 발생시킬 수 있다.
    • "컴퓨터가 이해할 수 있는 코드는 바보라도 작성할 수 있다. 훌륭한 프로그래머는 사람이 이해할 수 있는 코드를 작성한다." (마틴 파울러)
      • 깨끗한 코드는 읽으면서 놀랄 일이 없어야 한다. (클린코드)
    • 코드의 명명이 의미가 있는지, 주석이 자세히 기술되어 있는지, 함수 길이는 적절한지, 모듈 구분이 명확한지, 코드가 높은 응집도와 낮은 결합도를 가지는지 등을 모두 확인해야 가독성을 판단할 수 있다.

     

    확장성

    • 기존 코드를 약간 수정하는 것만으로도 혹은 전혀 수정하지 않고도 확장을 통해 새로운 기능을 추가하는 것을 말한다.
    • 즉, 코드를 작성할 때 새로운 기능을 추가할 수 있는 여지가 설계 당시부터 고려되어 있어 확장용 인터페이스가 이미 존재함을 의미한다.

     

    유연성

    • 코드가 확장과 재사용이 용이하고 사용성이 높을 경우 일반적으로 코드가 유연하다고 생각할 수 있다.

     

    간결성

    • KISS 원칙 : Keep It Simple, Stupid.
    • 단순한 코드와 명확한 논리는 종종 코드의 가독성이 높고, 유지 보수성이 높다.
    • 복잡한 디자인 패턴을 프로젝트에 도입해야만 자신의 기술 수준이 높다고 착각한다. 그러나 고수준의 프로그래머는 종종 간단한 방법으로 복잡한 문제를 해결하는 경우가 많다.

     

    재사용성

    • DRY 원칙 : Don't repeat yourself
    • 반복적인 코드 작성을 최소화하고 기존 코드를 재사용하는 것.
    • 중복 코드는 여러 책에서 죄악으로 취급한다!

     

    테스트 용이성

    • 코드의 테스트 용이성이 낮으면 단위 테스트를 작성하기 어렵다는 뜻이며, 기본적으로 코드의 설계에 문제가 있음을 보여준다.

     

     

    1.3 고품질 코드를 작성하는 방법

    • 고품질의 코드는 유지하기 쉽고, 읽기 쉽고, 확장하기 쉽고, 유연하고, 간결하고, 재사용 가능하고, 테스트가 가능한 코드
    • 코드 품질 평가 표준을 충족하는 고품질의 코드를 작성하려면 객체지향 설계 패러다임, 설계 원칙, 코딩 규칙, 리팩터링 기술, 디자인 패턴을 포함하여 일부 세련되고 구현 가능한 프로그래밍 방법론을 마스터해야 한다.
    • 이러한 프로그래밍 방법론을 마스터하는 궁극적인 목표는 고품질의 코드를 작성하는 것이다.
    • 이론적 지식을 축적하는 것 외에도 어느 정도의 의도적인 훈련이 필요하다. -> 이론 지식만 배우면 막상 문제가 발생했을 때 해당 이론을 떠올리지 못한다.
    • 또한 코드 품질에 대한 인식을 갖는 것도 매우 중요하다.

     

    객체지향

    • 캡슐화, 추상화, 상속, 다형성의 풍부한 특성으로 인해 객체지향 프로그래밍은 복잡한 설계 사상을 실현할 수 있으므로 다양한 설계 원칙과 디자인 패턴 코딩 구현의 기초가 된다.

     

    설계 원칙

    • 설계 원칙은 코드 설계에서 배운 몇 가지 교훈이다.
    • 설계 원칙은 추상적으로 보이고, 정의와 설명이 모호하며, 동일한 설계 원칙에 대해 사람마다 해석이 다르다는 설계 원칙의 특징이 있다. -> 따라서 단순히 정의만 암기하면 도움이 안된다.
    • SRP, OCP, LSP, ISP, DIP, KISS, YAGNI, DRY, LoD 등
    • 디자인패턴보다 더 일반적이다.
    • 아직 설계 원칙에 대해 이해가 잘 안될걸수도 있는데, 잠깐 생각해봐도 DRY를 위해 설계를 바꾸다보면 필연적으로 KISS와 YAGNI가 깨질 수 있을 것 같다. 결국 설계는 트레이드 오프인데, 이후 챕터들에서 이러한 부분들에 대해 이 책이 어떻게 풀어낼지 궁금하다.

     

    디자인 패턴

    • 디자인 패턴은 소프트웨어 개발에서 자주 접하게 되는 일부 설계 문제에 대해 요약된 술려선 또는 설계 사상을 모아둔 것이다.
    • 주요 목적은 디커플링을 통해 코드의 확장성을 향상시키는 것이다.
    • 추상화 측면에서 설계 원칙은 디자인 패턴보다 더 추상적이며, 디자인 패턴은 더 구체적이고 구현하기 쉽다.

     

    코딩 규칙

    • 코딩 규칙은 주로 가독성 문제를 해결한다.
    • 최소한 변수, 클래스, 함수, 코드에 대한 명명 규칙, 코드에 주석을 다는 범위와 같은 코딩 규칙에 능숙해야 한다.
    • 설계 원칙과 디자인 패턴에 비해 코딩 규칙은 이해하고 마스터하기 쉽다.

     

    리팩터링 기법

    • 소프트웨어가 계속 반복되어 업데이트되는 한 언제까지나 적용되는 만능의 설계는 없다.
    • 요구 사항의 변경과 지속적인 코드 업데이트로 인해 기본 설계에는 반드시 문제가 발생한다. -> 이러한 문제를 해결하기 위해 코드를 리팩터링해야 한다.
    • 코드 품질 저하를 방지하는 효과적인 수단이다. 객체지향 프로그래밍 패러다임, 설계 원칙, 디자인 패턴, 코딩 규칙에 대한 이론적 지식에 의존한다.
    • 보이스카우트 규칙 : 캠프장을 처음 왔을 때 보다 깨끗하게 하자. -> 한꺼번에 많은 시간과 노력을 들이지 말고, 볼 때 마다 작은 개선으로도 충분하다 (변수 이름, 긴함수 분할, 중복제거 등)

     

     

    1.4 과도한 설계를 피하는 방법

    • 코드를 설계하지 않는 것은 좋지 않지만, 과도하게 설계하는 것도 좋지 않다.
    • 과도한 설계는 나중에 요구 사항이 변하지 않을 수도 있기 때문에, 이런 시도는 코드의 복잡성만 높일 뿐이다.
    • 설계는 항상 트레이드 오프이다. 설계가 들어가면 대부분의 경우 코드의 복잡성은 높아질 수 밖에 없다. 적당한 수준을 팀원끼리 합의해야 할 것 같다. 결국 명분과 팀의 합의라 생각한다. 그리고 이런 내용을 서로 토론하며 얘기할 수 있으려면 팀 실력의 상위 평준화도 필요하다고 생각한다.
    • 1.4의 내용들을 보다보니 나도 어느정도 자제하려 노력하는 상황이긴 했지만, 그럼에도 상당히 뜨끔한 부분들이 많은 것 같다 ㅋㅋ

     

    코드 설계의 원래 의도는 코드 품질을 향상시키는 것이다.

    • 코드를 설계할 때 우리는 먼저 왜 이런 방식으로 설계하는지, 왜 이 디자인 패턴을 적용해야 하는지, 이것이 실제로 코드의 품질을 향상시킬 수 있는지, 코드 품질의 어떤 측면을 개선할 수 있는지를 생각해야 한다.
    • 위에 대해 명확하게 생각하기 어렵거나 주어진 이유가 터무니없다면 과도한 설계이다.

     

    코드 설계의 원칙은 앞에 문제가 있고, 뒤에 방안이 있다는 것이다.

    • 사용자의 니즈를 생각해 요구 사항에 맞는 기능을 개발해야지, 화려한 기능을 먼저 개발한 후 요구 사항을 끼워 맞추는 방식으로 개발해서는 안 된다.
    • 코드 설계도 마찬가지로 먼저 가독성이 낮고 확장성이 떨어지는 등의 코드의 페인 포인트(pain point; 제품 및 서비스를 이용할 때 불편을 느끼는 지점 또는 허들이 되는 지점)를 분석한 다음, 해당 포인트를 디자인 패턴과 설계 원칙을 사용하여 개선해야 한다.
      • 즉, 누군가가 설계의 목적을 물었을 때, 코드의 확장성 향상이나 OCP 충족과 같은 핑계를 대는 상황은 바람직하지 않다.
      • 어느 것이 올바른 것인지 알지 못한 채 각종 디자인 패턴을 적용해보고, 자신이 작성한 매우 복잡한 코드를 보고 자기 만족에 빠지게 되는데 이런 방식을 가장 피해야 한다.

     

    코드 설계의 응용 시나리오는 복잡한 코드에 적용되어야 한다.

    • 일부 디자인 패턴 서적에서는 몇 가지 간단한 예제를 제공한다. 이러한 예제는 제한된 공간에서 디자인 패턴의 원리와 구현을 설명하기 위한 것일 뿐 실질적인 의미는 없다. 이것이 많은 초보자가 디자인 패턴을 배우고 나서 과도한 설계를 하게 되는 주된 이유다.
    • 우리가 개발하는 코드가 복잡하지 않다면 복잡한 디자인 패턴을 도입할 필요가 없다.
    •   알고리즘이나 자료구조도 마찬가지다. 일반적으론 자바에서 String에 대한 '+' 연산으로 문자열을 합쳐도 별 문제 없다. 하지만 합칠 데이터가 많아지면 매우 큰 문제가 발생한다. 관련해서 '자바에서 문자열 합칠 때 '+' 연산을 쓰지 마세요! (StringBuilder, StringJoiner, String.join, StringBuffer)' 글을 적은 적 있다. 또, 내 경우에도 현업 프로젝트를 할 때는 좀 더 효율적인 방법을 알더라도 코드가 더 복잡해지고 크게 유의미하지 않다면 넣지 않도록 노력(?)하고 있다.

     

    지속적인 리팩터링은 과도한 설계를 효과적으로 방지할 수 있다.

    • 디자인 패턴을 적용하면 코드 확장성을 향상시킬 수 있지만 동시에 코드 가독성이 나빠질 수 있다.
    • 실현 가능성이 낮은 미래의 요구 사항을 위해 처음부터 디자인 패턴을 적용하기보다, 진짜 문제가 발생했을 때 이를 해결하기 위해 디자인 패턴을 사용하는 것을 고려하자.

     

    특정 시나리오 외의 코드 설계에 대해 이야기하지 않는다.

    • 코드 설계는 매우 주관적이다. 따라서 코드 설계의 품질을 판단하기가 어렵다. 정말 판단을 하고 싶다면 특정한 맥락에서 판단해봐야 한다. 특정 시나리오 없이 코드 설계가 합리적인지 아닌지를 이야기하는 것은 공허한 이야기다.
    • 예를들어 모바일 게임 프로젝트는 개발된 후 시장 반응이 좋지 않으면 즉시 사장된다. 또한 빠른 시일 내에 출시하여 선점하는게 성패를 좌우하기도 한다. 따라서 초기 코드 설계와 코드 품질에 그렇게 많은 시간을 할애하지 않는 경우가 많다.
    • 하지만 MMORPG 같은 대규모의 게임을 개발한다면 프로젝트를 뒤집는 데 드는 비용이 매우 높으므로, 이때는 코드 품질이 매우 중요해진다.
    • 저수준, 프레임워크 기반, 범용 코드를 개발하는 경우 코드의 품질이 더 중요해진다.
    • 장기간 유지 관리가 필요하지 않은 비즈니스 시스템이나 프로젝트를 개발하는 경우 코드 품질 요구 사항을 낮출 수도 있다.

    댓글