우테코 8기 프리코스 3주차를 진행하면서, 좋은 코드 구조는 무엇인지 굉장히 궁금해지게 되었다.
코드 리뷰를 하다보니, 객체 구분이 덜 되었지만 오히려 가독성은 좋은 코드, 객체 구분은 확실하지만 오히려 가독성은 안 좋은 코드를 보면서 객체 구분은 무엇을 위한 것일까 라는 생각이 들게 되었다.
유지 보수성, 확장성을 위해 확실하게 단일책임원칙을 지키는 것은 좋겠지만, 과해지면서 오히려 정신 없어지는 코드가 되기도 하는 것 같다.
우선 그동안 무턱대고 내가 채택해서 사용했던 MVC 패턴에 대해서 생각해보는 시간을 가지려고 한다.
1. MVC 패턴이란?
MVC 는 Model(모델), View(뷰), Controller(컨트롤러)의 약자이다.
하나의 애플리케이션을 이 세 가지 역할로 나누어 개발하는 방식을 말한다.
| Model 👩🍳 | 데이터와 비즈니스 로직을 담당한다. 앱이 '무엇을' 하는지에 대한 모든 규칙과 데이터를 가지고 있다. |
| View 🍽️ | 사용자에게 보이는 화면, 즉 UI(유저 인터페이스)를 담당한다. 사용자에게 데이터를 보여주고, 사용자의 입력을 받는 '껍데기'이다. |
| Controller 🤵 | 모델과 뷰 사이를 이어주는 '중간 관리자'이다. 사용자의 입력(요청)을 뷰로부터 받아, 이를 모델에게 전달하여 처리하도록 지시한다. 그리고 모델이 처리한 결과를 다시 뷰에게 전달하여 사용자에게 보여주게 한다. |

2. MVC 패턴은 왜 사용할까?
가장 큰 이유는 관심사 분리이다.
쉽게 말해, '각자 자기 일만 잘하자'는 뜻이다.
- 장점 -
1. 유지보수가 쉬워진다.
만약 모든 코드가 한 파일에 뒤섞여 있다면, 버튼 색깔 하나 바꾸려다가 데이터베이스 로직을 건드리는 실수를 할 수 있다.
MVC 를 사용하면 UI 수정은 View만, 데이터 처리 방식 변경은 Model 만, 사용자 요청 처리 방식 변경은 Controller 만 수정하면 된다.
2. 협업이 편해지다.
프론트엔드(UI) 개발자는 View 에 집중하고, 백엔드(서버 로직) 개발자는 Model과 Controller에 집중할 수 있다. 서로의 작업 영역이 명확히 구분되어 동시에 작업하기 좋다.
3. 코드 재사용성이 높아진다.
하나의 Model을 가지고 웹 View(웹사이트)를 만들 수도 있고, 모바일 앱 View(iOS/Android)를 만들 수도 있다. 핵심 로직은 그대로 재사용하면서 겉모습만 바꿔 낄 수도 있다.
3. MVC 말고 더 좋은 패턴은 없을까?
'더 좋다'기 보다는 '다른 상황에 더 적합한' 패턴들이 있다. MVC는 훌륭하지만, 특히 현대적인 프론트엔드(UI) 개발에서는 몇 가지 단점(ex. 뷰와 컨트롤러가 너무 가깝게 연결되어 복잡해짐)이 부각되기도 했다.
그래서 MVC 에서 파생되거나 이를 보완하는 여러 패턴들이 등장했다.
- MVVM (Model-View-ViewModel) -
요즘 가장 많이 쓰이는 패턴 중 하나이다.
(특히 React, Vue, Angular 같은 모던 웹 프레임워크나 모바일 앱 개발에서)
- ViewModel (뷰모델)
'뷰(View)를 위한 모델'이라는 뜻이다. 컨트롤러의 역할 중 상당 부분을 가져온다.
- 핵심 (데이터 바인딩)
뷰모델의 데이터가 바뀌면 '데이터 바인딩'이라는 기술을 통해 뷰(화면)가 자동으로 업데이트된다.
반대로 뷰에서 사용자가 값을 입력하면 뷰모델의 데이터도 자동으로 바뀐다. MVC의 컨트롤러처럼 일일이 '이 데이터를 저기 보여줘'라고 명령할 필요가 줄어들어 코드가 매우 간결해진다.
- MVP (Model-View-Presenter) -
MVC와 비슷하지만, Presenter(프레젠터)가 뷰와 모델 사이를 더 적극적으로 중재한다. 컨트롤러보다 프레젠터가 뷰를 직접 제어하는 경향이 강하다.
4. 패턴 없이 좋은 구조를 짤 수 없을까?
결론부터 말하면, 작은 규모라면 가능하지만 프로젝트가 커지면 거의 불가능하다는 게 일반적이다.
| 작은 프로젝트 | 간단한 계산기나 토이 프로젝트를 만드는데 굳이 MVC, MVVM 과 같은 패턴을 도입하는 것은 배보다 배꼽이 더 큰 격일 수 있다. |
| 커지는 프로젝트 | 코드가 수천, 수만 줄이 넘어가기 시작하면, 개발자 본인도 자기가 짠 코드를 이해하기 어렵다. - 이 때 사람들은 자연스럽게 "아, UI 코드는 이쪽 파일에 모아두고, 데이터 처리는 저쪽 파일에 모아둬야겠다"라고 생각하게 된다. - 이것이 바로 '패턴'의 시작이다. |
디자인 패턴은 수십 년간 수많은 천재 개발자들이 '어떻게 하면 코드를 더 효율적이고 깔끔하게 관리할 수 있을까?'를 고민하며 쌓아 올린 경험의 집약체이자 개발자들 사이의 공통 언어이다.
따라서 패턴을 따르는 것은, 이미 검증된 좋은 구조를 빠르고 안전하게 적용하여 헤매는 수고를 더는 가장 현명한 방법일 수 있다.
하지만, 나는 초보 개발자라 나만의 방식을 만들고 싶기도 하다. 벌써부터 이러한 틀에 얽매여 코드를 만들고 싶지는 않다.
따라서 우테코 프리코스를 하는 동안, 같은 코드를 다른 방식으로 여러 번 작성해볼까 한다.
그동안은 고민하면서 쓰느라 한 과제를 작성하는데 시간이 꽤 들어갔는데 타임어택 방식을 사용해보면서 틀에 얽매이지 않는 코드 방식을 작성해볼까 한다.
5. TDD 이란?
Test-Driven Development 의 약자로, 한국어로는 '테스트 주도 개발'이라고 부른다.
이름 그대로, 실제 기능 코드를 작성하기 전에 테스트 코드를 먼저 작성하는 개발 방법론이다.
"기능도 없는데 어떻게 테스트를 먼저 만들지?"라고 생각할 수 있지만, TDD는 바로 그 역발상을 통해 더 견고하고 깨끗한 코드를 만드는 것을 목표로 한다.
지금의 나는 아직 해본 적 없는 방식이다. 아마 다음 과제나 최종 때 적용해보지 않을까? 생각한다.
TDD는 어떻게 진행될까?
TDD는 'Red-Green-Refactor' 라는 3가지 단계를 매우 짧은 주기로 반복하며 진행된다.
1. 🔴 Red (실패하는 테스트 작성)
- 새로 만들고 싶은 기능에 대한 기대(요구사항)를 테스트 코드로 먼저 작성한다.
- 예시: 'add(2, 3)을 호출하면 5가 반환되어야 한다'는 테스트를 만든다.
- 당연히 add 라는 함수가 아직 없거나, 제대로 동작하지 않기 때문에 이 테스트는 실패한다.
2. 🟢 Green (테스트 통과)
- 방금 작성한 '빨간불' 테스트를 통과시킬 수 있는 가장 단순하고 최소한의 실제 코드를 작성한다.
- 예시: add(a, b) { return a + b; }
- 이제 테스트를 다시 실행하면 성공한다. (초록불 🟢)
- 이 단계에서는 '일단 돌아가게' 만드는 것이 중요하다. 코드가 예쁘지 않아도 괜찮다.
3. 🔵 Refactor (코드 개선)
- 테스트가 '초록불'인 상태, 즉 기능이 정상적으로 동작함을 확인한 상태이다.
- 이 '안전망'을 믿고, 방금 작성한 코드를 리팩터링한다.
- 예시: 코드의 가독성을 높이거나, 중복을 개선하거나, 더 나은 구조로 개선한다.
- 코드를 수정한 후에도 테스트를 다시 돌려, 여전히 초록불이 유지되는지 확인한다.
(기능을 망가뜨리지 않았는지 확인)
이 Red ➔ Green ➔ Refactor 사이클을 아주 작은 단위로 끊임없이 반복하면서 전체 기능을 완성해 나가는 것이 TDD 이다.
TDD 를 왜 사용할까?
TDD를 실천하면 여러 가지 장점이 있다.
- 안정성 및 버그 감소
- 코드를 작성하는 동시에 '이 코드가 잘 동작한다'는 것을 증명하는 테스트를 함께 만든다.
- 나중에 다른 기능을 추가하거나 코드를 수정할 때, 기존 테스트를 돌려보기만 하면 새 코드가 기존 기능을 망가뜨렸는지(사이드 이펙트) 즉시 알 수 있다. - 더 나은 설계 유도
- 테스트하기 어려운 코드는 보통 잘못 설계된 코드일 확률이 높다.
(예: 하나의 함수가 너무 많은 일을 하거나, 다른 코드와 복잡하게 얽혀있음)
- TDD 를 하려면 코드를 테스트하기 쉬운 형태로 자연스럽게 쪼개게 된다. 이는 결합도는 낮추고 응집도는 높이는 좋은 설계로 이어진다. - 명확한 요구사항 및 살아있는 문서
- 테스트 코드는 그 자체로 이 기능은 이렇게 동작해야 한다는 명확한 요구 사항이자 명세서가 된다.
- 주석이나 별도 문서는 시간이 지나면 실제 코드와 달라지기 쉽지만, 테스트 코드는 실행 가능한 '살아있는 문서'이다. - 개발의 자신감 (심리적 안정감)
- 코드를 리팩터링하거나 새로운 기능을 추가할 때, "내가 뭘 잘못 건드려서 시스템 전체가 망가지면 어떡하지?"라는 두려움이 줄어든다. 테스트가 든든하게 뒤를 받쳐주기 때문이다.
나는 현재, 설계 ➔ 구현 ➔ 테스트 방식으로 코드를 짜왔다.
그러면 TDD 방식은 설계 ➔ 테스트 ➔ 구현 방식으로 진행되는 걸까?
그렇지 않다. 당연히 TDD 도 설계를 하긴 한다. 하지만 미리 모든 세부 설계를 하진 않는다.
'큰 그림 설계 ➔ 작은 기능 선택 ➔ TDD 사이클 시작' 방식으로 코드를 짠다.
TDD 의 핵심 철학은 진화하는 설계(Emergent Design)이다.
왜 이렇게 할까?
소프트웨어는 복잡해서 처음부터 모든 세부 사항을 완벽하게 설계하는 것은 거의 불가능하기 때문이다. TDD 는 '어차피 설계는 바뀐다'는 것을 인정하고, 테스트라는 '안전망'에서 '작은 단위'로 코드를 개선해나가며 최적의 설계를 찾아가는 과정이다.
코드로써 조금 더 자세히 살펴보겠다.
우테코 프리코스의 첫 과제였던 Calculator 클래스를 만든다고 가정해보자.
요구 사항: "두 숫자를 더하는 기능이 필요하다"
- 🔴 Red (실패하는 테스트 작성)
- 아직 Calculator 클래스도, add 메서드도 없다.
- 하지만 우리는 '이런 게 있었으면 좋겠다'고 상상하며 테스트 코드부터 작성한다.
// src/test/kotlin/CalculatorTest.kt (테스트 파일)
// JUnit5 임포트가 필요하다
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.*
class CalculatorTest {
@Test
fun `두 수를 더하면 합을 반환한다`() {
// 1. 상상: "Calculator라는 게 있으면 좋겠어."
val calc = Calculator()
// 2. 상상: "add라는 기능이 있어서, 2와 3을 넣으면..."
val result = calc.add(2, 3)
// 3. 기대: "결과는 5여야 해!"
assertEquals(5, result)
}
}
- 현재 상태: Calculator 클래스가 없으므로 val calc = Calculator() 에서 컴파일 에러가 발생한다.
이것이 첫 번째 'Red'이다.
2. 🟢 Green (테스트 통과)
- 이 Red 를 Green 으로 바꿀 최소한의 코드로 작성한다.
- Calculator 클래스를 만든다.
// src/main/kotlin/Calculator.kt
class Calculator {
fun add(a: Int, b: Int): Int {
// 일단 테스트만 통과하게 (아직은 통과 못함)
return 0
}
}
- 이제 코드는 컴파일 된다.
- 테스트를 실행하면,
assertEquals(5, result) -> result 는 0이므로 실패(Red)한다.
- add 함수가 실제 로직을 갖도록 수정한다.
// src/main/kotlin/Calculator.kt
class Calculator {
fun add(a: Int, b: Int): Int {
return a + b // 실제 로직 구현
}
}
- 다시 테스트를 진행한다.
- calc.add(2, 3)는 5를 반환한다.
- assertEquals(5, 5,) -> 성공
3. 🔵 Refactor (코드 개선)
- Green 상태이므로 코드를 청소한다.
- Kotlin 에서는 이 add 함수를 더 간결하게 '단일 표현식 함수(Single-Expression-Function)으로 바꿀 수 있다.
// src/main/kotlin/Calculator.kt (리팩터링)
class Calculator {
// 더 Kotlin스럽게 변경
fun add(a: Int, b: Int) = a + b
}
위와 같은 방식으로 사이클을 반복하면서 기능을 모두 구현하면 된다.
6. 마무리
TDD 를 적용해서 과제를 풀어보기도, 구조에 대해서 생각 없이 빠르게 구현하는데 목적을 둬서 과제를 풀어보기도 하는 식으로, 여러 방식으로 과제를 해나가면서 나만의 코드 구조 스타일을 만들도록 해봐야겠다.
'Dev Log' 카테고리의 다른 글
| UX 글쓰기 (feat. 애플) (1) | 2025.12.06 |
|---|---|
| [우테코 AN 8기] 프리코스 3주차 회고 | 로또 과제, 오픈미션이란? (0) | 2025.11.09 |
| [우테코 AN 8기] 프리코스 2주차 회고 | '자동차 경주' 과제 / 공통피드백 (0) | 2025.10.29 |
| [우테코 AN 8기] 프리코스 1주차 과제 회고 (0) | 2025.10.16 |
| 2025.09 | 유니티 부트캠프를 끝내고. (0) | 2025.09.20 |