2. Agile Design

최정훈·2024년 9월 26일

들어가는 말


Agile개발에서 개발팀은 반복 작업을 할 때마다 시스템 설계를 개선해서 현재 시스템에 최대한 적합하도록 만든다. 그들은 미래에 필요할 기능을 찾는데 너무 많은 시간을 소비하지도 않고, 미래에 필요할 기능을 지금 구현하는데 많은 노력을 하지도 않는다. 이들은 단지 시스템의 현재를 가장 좋게 만드는 것에 초점을 맞춘다.

1. What is Agile Design?


소프트웨어 프로젝트의 설계는 추상적인 개념이다. 이는 프로그램 전반적인 모양과 구조와 관련이 있으며, 각 모듈 클래스, 메서드의 세부적인 모양과 구조와도 관련이 있다. 이는 여러가지 방법으로 구현될 수 있지만, 결국 최종적인 구현은 코드이다. 결국, 소스코드가 곧 설계이다.

운이 좋게 처음 프로젝트 시작에서 명쾌하게 시스템이 구현해야 하는 것을 파악했고, 첫 출시에 그것을 잘 표현했다 치더라도 진화하고 변화하지 않는다면 Design Smell이 나기 시작한다.

🚨불량한 디자인의 증상들

  1. Rigidity(경직성) - 변경이 다른 부분들을 많이 변경시키기 때문에, 디자인을 변경하기 어려움
  2. Fragility(취약성) - 변경으로 인해서 변화와 상관없는 부분의 디자인이 손상됨
  3. Immobility(부동성) - 디자인을 재사용하기 어려움
  4. Viscosity(점도) - 올바른 일을 하기 어려움. 디자인을 유지하기 어려움
  5. Needless Complexity(불필요한 복잡성) - 과도한 디자인
  6. Needless Repetition(불필요한 반복) - 단일 추상화로 통합할 수 있는 반복구조가 포함되어있음
  7. Opacity(불투명성) - 읽고 이해하기 힘들다. 의도를 잘 전달하지 못한다.

이러한 증상들이 보이기 시작하면 소프트웨어의 설계에서 악취가 난다는 표현을 사용한다.(Design Smells)

agile하지 않은 환경에서는 요구사항이 초기에 예상하지 못한 방식으로 변경되기 때문에 설계가 저하된다. 그렇기 때문에 이러한 변경은 신속하게 이루어져야 한다. 하지만, 변경은 기존의 디자인 철학에 익숙하지 않은 완전히 새로운 사람이 할 수도 있다. 결국, 변경이 이루어지긴 하지만, 원래의 디자인을 위반하는 셈이다. 이러한 위반들이 점점 쌓이고 쌓여서 Design Smell을 유발한다.

반면에, agile한 개발팀은 변경을 통해서 성장한다. 그들은 시스템 설계를 가능한 깔끔하고 단순하게 유지하고 많은 테스트로 이를 뒷받침한다. 이는 디자인을 변경이 용이하고 유연하도록 만든다. 개발팀은 이러한 유연성을 활용하여 설계를 반복이 끝날 때마다 요구사항에 가장 적합한 설계를 갖춘 시스템으로 끝나도록 한다.

⇒ 결국 Agile Design이란 소프트웨어의 구조와 가독성을 개선하기 위해 원칙, 패턴, 관행을 지속적으로 적용하는 것이다. 이는 시스템 디자인을 언제나 가능한 한 단순하고 깔끔하게 유지하려는 헌신이다.

2. SRP: Single-Responsibility Principle


하나의 클래스는 한가지의 기능만 가지고 있어야한다.

클래스는 하나의 이유만으로 변경되어야 한다.

각각의 책임은 변화의 축이다. 요구사항이 변경되면 이는 계층 간 책임의 변경을 통해서 나타난다.

만약 클래스가 두가지 이상의 책임을 가지고 있다면, 변경되어야 할 이유도 두가지인 것이다. 이러한 커플링은 변경 시 예상치 못한 방식으로 파손되는 fragile design으로 이어진다.

하지만, 모든 경우에 있어서 SRP를 적용하는 것은 아니다. 변화가 없는데 이를 적용하는 것은 Needless Complexity를 초래할 뿐이다.

이는 가장 간단하지만 가장 구현하기 어려운 원칙 중 하나이다. 각 책임을 찾아서 분리하는 것이 바로 소프트웨어 설계의 가장 큰 비중을 차지한다.

3. OCP: Open-Close Principle


소프트웨어 개체는 확장에 대해서는 열려있고, 수정에 대해서는 닫혀있어야한다.

추가에 대해서 개방적이고, 변경에 대해서 폐쇠적이여야한다.

OCP가 잘 적용되었다면, 추가적인 변경을 기존의 코드를 변경하는 것이 아니라, 새로운 코드를 추가하는 것으로 달성할 수 있다. 이를 잘 따르는 모듈에는 두 가지 주요한 속성이 있다.

  1. 확장에 열려있다.

    모듈의 동작을 확장할 수 있다는 의미이다. 요구사항이 변경되면 이를 충족하는 새로운 동작으로 모듈을 확장할 수 있다.

  2. 변경에 대해서는 닫혀있다.

    모듈의 동작을 확장해도, 모듈의 소스코드나 바이너리 코드는 변경되지 않는다.

이는 주로 추상화에 의해서 이루어질 수 있다. 하지만, 이러한 방법으로도 OCP를 완벽하게 지킬수는 없다. 분명 예외가 있는 경우가 존재한다. 그렇기 때문에 개발자는 가장 일어날 법한 변화에 대해서 예측해야하고, 이러한 변화로부터 소프트웨어를 보호하기 위해서 추상화를 구축해야한다. 하지만, 이러한 추상화에는 시간이 많이 걸릴 뿐더러, 예측을 적중시키기도 쉽지 않다. 또한 추상화는 소프트웨어 설계의 복잡성도 증가시킨다. 그렇기 때문에, 적절한 조사를 하고 개인의 경험을 활용하는 것이 중요하다.

4. LSP: Liskov Substitution Principle


하위타입은 기본타입으로 대체가 가능해야한다.

부모객체는 자식객체로 대체가 가능해야한다.

만약, S의 객체인 o1과 T의 객체인 o2가 있을 때, 프로그램에서 o1을 o2로 대체해도 동작이 달라지지 않는다면, S는 T의 하위유형이다.

LSP를 위반하면 종종 런타임 유형정보(RTTI)를 OCP를 심각하게 위배하는 방식으로 사용하는 결과는 낳는다(if-else문의 연속적인 체인). 이는 다르게 말하면, LSP를 위반하는 것은 잠재적으로 OCP를 위반할 가능성이 있다고 생각할 수 있다.

LSP는 OCP의 주요 구현요소 중 하나이다. 기본타입으로 표현된 모듈이 수정없이 확장될 수 있도록 하는 것은 하위타입의 대체성이다.

5. DIP: Dependancy-Inversion Principle


고수준의 모듈은 저수준의 모듈에 의존하면 안된다. 둘 다 추상화에 의존해야한다. 추상화는 세부사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야한다.

추상화에 의존해야지, 구체화에 의존하면 안된다.

전통적인 소프트웨어 개발 방법은 상위 수준의 모듈이 하위 수준 모듈에 의존하는 경향이 있다. 상위 수준 모델은 어플리케이션의 중요한 정책 결정과 비지니스 모델을 담고 있다. 하지만, 이러한 구조는 하위수준 모듈을 변경하면 상위 수준 모듈에 직접적인 영향을 미쳐 결과적으로 상위 수준 모듈도 변경되는 결과를 초래할 수 있다. 또한 이 상위 모듈을 다른 컨텍스트에서 재사용하는 것이 매우 어렵다.

각각의 상위 모듈은 서비스에 필요한 인터페이스를 정의하고, 하위 모듈은 이러한 인터페이스를 통해서 구현되어야한다. 하위 모듈은 상위 클래스에서 선언된 인터페이스에 의존하고, 상위 모듈은 더 이상 하위 모듈의 변경에 영향을 받지 않는다. 또한 다른 컨텍스트에서 유연하게 재사용이 가능하다.

6. ISP: Interface-Segergation Principle


뚱뚱한 인터페이스를 가진 클래스는 인터페이스가 응집력이 없는 클래스이다.

클라이언트는 자신이 사용하지 않는 매서드에 의존해서는 안된다.

클라이언트가 자신이 사용하지 않는 매서드에 의존하는 경우, 이들은 매서드에 의해서 변경된다. 이로 인해서 모든 클라이언트 간의 의도치 않은 결합이 발생한다. 이러한 일을 방지하기 위해서 인터페이스를 분리한다.

이는 뚱뚱한 인터페이스를 여러 개의 클라이언트별 인터페이스로 나눔으로써 해결할 수 있다. 각각의 클라이언트 별 인터페이스는 해당 클라이언트나 클라이언트 그룹이 호출하는 함수만 선언한다. 이를 통해서 클라이언트가 호출하지 않은 매서드에 대한 종속성이 끊어지고 클라이언트가 서로 독립적으로 될 수 있다.

profile
게임개발자(희망)의 공부일지

0개의 댓글