SQL이 나를 불편하게 만든다.
Source: Dev.to
번역하려는 텍스트를 제공해 주시면, 해당 내용을 한국어로 번역해 드리겠습니다. (소스 링크는 그대로 유지됩니다.)
Introduction
제가 실제로 (이론이 아닌) 이해하고 있는 바에 따르면, 객체‑지향 프로그래밍은 전통적인 함수형 패러다임에 대한 단순한 대안이 아니라 오히려 한 단계 앞선 느낌을 줍니다. 저는 이를 상태 관리를 포함한 함수형 프로그래밍이라고 생각합니다. 실제로 클래스는 프로그램 실행 시간 동안 지속되는 일종의 메모리를 가진 함수처럼 동작하므로, 현실 세계의 논리와 개념을 모델링하는 더 나은 상태ful 추상화를 만들 수 있습니다.
React의 함수형 컴포넌트를 사용해 보았을 때, 저는 직관적으로 웹 페이지를 트리 구조—작은 컴포넌트들로 구성된 부모 컴포넌트, 최종적으로 화면에 표시되는 루트 컴포넌트—로 생각했습니다. 재사용 가능한 컴포넌트는 OOP에서 인터페이스나 추상 클래스와 같은 추상화와 유사합니다. 이러한 컴포넌트가 의미를 갖기 위해서는 데이터를 보유해야 하고, 데이터가 변하면 UI가 업데이트되어야 합니다. 이때 데이터를 보유하고 변화에 반응하는 컴포넌트는 클래스의 “stateful abstraction” 해석과 비슷하게 느껴집니다.
이 때문에 UI를 Object Tree 로 생각하는 것이 많은 의미가 있었습니다. 전통적인 모바일 UI 프레임워크도 이 아이디어를 강화합니다: Java/Kotlin을 사용하는 Android와 Swift/Objective‑C를 사용하는 iOS는 UI를 객체 계층 구조로 모델링합니다. 그렇다면 왜 React는 이 모델을 포기하고 함수형 컴포넌트를 채택했을까요? 상태 관리를 거의 프레임워크에 맡기고 개발자에게는 명시적인 제어를 크게 줄였습니다.
답은 함수와 객체의 차이가 아니라 명령형 vs 선언형 프로그래밍에 관한 것입니다. 선언형 UI는 주어진 상태에 대해 어떤 인터페이스가 보여져야 하는지를 기술하고, 프레임워크가 어떻게 구현할지를 처리합니다. SwiftUI와 Jetpack Compose 같은 최신 모바일 UI 프레임워크도 같은 원리를 따릅니다.
UI 프레임워크: 명령형 vs 선언형
전통적인 UI Kit (명령형)
class CounterViewController: UIViewController {
var count = 0
let label = UILabel()
let button = UIButton(type: .system)
override func viewDidLoad() {
super.viewDidLoad()
label.text = "Count: 0"
button.setTitle("Increment", for: .normal)
button.addTarget(self, action: #selector(increment), for: .touchUpInside)
}
@objc func increment() {
count += 1
label.text = "Count: \(count)"
}
}
현대 SwiftUI (선언형)
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") {
count += 1
}
}
}
}
React (선언형)
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
SwiftUI와 React의 유사점
| SwiftUI | React |
|---|---|
@State var count | const [count, setCount] = useState(0) |
count += 1 | setCount(count + 1) |
body | return (…) |
View (구조체) | Function component |
| 본문 재계산 | 함수 재렌더링 |
이러한 유사점은 두 프레임워크 모두 UI 출력을 선언하고, 상태가 변할 때 프레임워크가 재평가를 자동으로 처리하도록 해 줍니다.
Imperative vs. Declarative Programming
- Imperative UI: UI를 어떻게 단계별로 구축하고 수정할지 설명합니다(예: 클래식 UIKit, 구 Android). 직접 제어가 가능하지만 보일러플레이트가 늘어나고 복잡한 상태 관리와 수동 업데이트가 필요해집니다.
- Declarative UI: 주어진 상태에 대해 UI가 어떻게 보여야 하는지 설명합니다. 프레임워크가 해당 상태를 달성하기 위한 필요한 단계를 결정하므로, 코드가 더 간단하고 가독성이 높으며 상태 변화에 자동으로 반응합니다.
선언형 패러다임에 대한 개인적 불편함
I experience a certain discomfort with this paradigm, similar to my unease with SQL and my preference for PyTorch over TensorFlow. The trade‑off is between explicit control and ease of implementation. Declarative abstractions make code less verbose and more elegant—TensorFlow’s model.fit() is a perfect example—but they also obscure when and how often code runs. Bugs become tied to state transitions rather than specific lines, making them harder to locate.
Declarative programming asks us to stop thinking like engineers issuing commands and start thinking like designers describing constraints. As a beginner in software, I don’t claim that functional components or declarative programming are inherently bad. They clearly work and are probably better suited for building systems at scale. For many developers, that is a price worth paying. For me, however, it remains an uncomfortable trade‑off. I can work within declarative systems and understand their value, but I still miss the loss of explicit control.