프로그래밍 리마인드

정동현·2022년 8월 22일

[ 프로그래밍 패러다임(Programming Paradigm) ]

프로그래밍 패러다임은 프로그래머에게 프로그래밍의 관점을 갖게 하고 코드를 어떻게 작성할 지 결정하는 역할을 한다.
새로운 프로그래밍 패러다임을 통해서는 새로운 방식으로 생각하는 법을 배우게 되고, 이를 바탕으로 코드를 작성하게 된다.

최근의 프로그래밍 패러다임은 크게 아래와 같이 구분할 수 있다.

  • 명령형 프로그래밍: 무엇(What)을 할 것인지 나타내기보다 어떻게(How) 할 건지를 설명하는 방식
    • 절차지향 프로그래밍: 수행되어야 할 순차적인 처리 과정을 포함하는 방식 (C, C++)
    • 객체지향 프로그래밍: 객체들의 집합으로 프로그램의 상호작용을 표현 (C++, Java, C#)
  • 선언형 프로그래밍: 어떻게 할건지(How)를 나타내기보다 무엇(What)을 할 건지를 설명하는 방식
    • 함수형 프로그래밍: 순수 함수를 조합하고 소프트웨어를 만드는 방식 (클로저, 하스켈, 리스프)



절차지향 프로그래밍(Procedural Programming)

물이 위에서 아래로 흐르듯 순차적인 처리가 중요시되며 프로그램 전체가 유기적으로 연결되도록 하는 프로그래밍 기법이다.(Top Down 방식)
절차지향 프로그래밍에서 사용하고 있는 언어로 대표적인 언어의 예로 C언어가 있다. (Objective-C)

  • 장점

    컴퓨터의 작업 처리 방식과 유사하기 때문에 객체 지향에 비해 처리 속도가 빠름.

  • 단점

    유지보수가 어려움.
    실행 순서가 정해져 있어서 순서가 바뀌면 동일한 결과를 보장하기 어려움.
    프로그램 분석과 디버깅이 어려움.



객체지향 프로그래밍(Object-Oriented Programming)

큰 문제를 작게 쪼개는 방식이 아니라 작은 문제들을 해결할 수 있는 객체들을 만든 뒤, 이 객체들을 조합해서 큰 문제를 해결하는 프로그래밍 기법이다.(Bottom UP 방식)
절차지향과는 다르게 데이터와 순차를 하나로 묶어서 생각하게 되는데, 이는 컴퓨터의 부품들을 하나씩 사서 컴퓨터를 조립하는 과정과 비슷하다 볼수 있다.

  • 의의

    Object Oriented programming(이하 "OOP")의 기본 아이디어는 단순하고 직관적이다.

객체는 데이터와 기능을 논리적으로 묶어놓은것으로 만약 자동차가 객체라면 그 "데이터"에는 제조사, 모델, 차량번호 등이 있을 것 이며, 그 "기능"으로는 가속, 변속, 헤드라이트 등이 있다.

  • 용어

    객체(Object)는 클래스의 인스턴스(Instance). 상위 클래스의 속성을 가지고 있으면서 개별적인 "데이터"와 행위(Method) 또한 가지고 있다.

클래스(Class)는 어떤 자동차처럼 추상적이고 범용적인것, 인스턴스(Instance)는 특정 자동차처럼 구체적이고 한정적인것, 위에서 설명한 "기능"에 해당하는것을 메소드라고 볼수 있다.

클래스에 속하지만 특정 인스턴스에 묶이지 않는 기능을 클래스 메소드라고 한다.(가속은 자동차에도있고, 열차에도있고, 비행기에도 있다)

인스턴스(Instance)라는 용어는 "객체(object)"와 유사하지만 의미상으로 "객체"는 좀더 일반적인 의미인 반면에 "인스턴스"라고 표현하면 "현재 생성된 바로 그 객체"라는 인스턴트(instant)한 뉘앙스로 좀 더 짙게 표현할 수 있다.

  • 장점

    코드를 재사용 할 수 있어 편리함.
    독립된 객체를 사용하여 문제가 되는 기능만 수정해서 사용하면 되기에 유지 및 보수가 용이.
    프로그램의 분석과 디버깅이 절차지향에 비해 쉬움.

  • 단점

    절차지향에 비해 처리되는 속도가 느림.
    모든 객체의 기능에 대한 이해를 해야하기에 설계하는데 시간이 오래걸림.

  • 특징

    • 강한 응집력
      프로그램의 한 요소가 특정 목적을 위해 밀접한 기능들이 모여 구현되어 있고 지나치게 많은 일을 하지 않으면 응집력이 높아짐
      (응집력이 낮아지면 여러가지 이유로 변경되는 코드가 많아 질 수 있음)
    • 약한 결합력
      코드간의 의존성이 낮아 코드에 자율성(독립적임)이 있음
      (결합도가 높아지면 유지 및 보수가 힘들어짐)

객체란 물리적인 존재, 추상적인 존재중 자신의 속성과 동작을 가지고 있는 것.
여기에 좀 더 추가를 하자면 물리적으로 존재하거나 추상적으로 생각할 수 있는 것 중에서 자신의 속성과 기능을 가지고 있고 다른것과 식별 가능한 것.



함수형 프로그래밍

WWDC19에서 iOS의 UIKit과 MacOS의 AppKit을 대체할 새로운 UI 개발 프레임워크인 SwiftUI가 등장하였다.
애플은 7개나 되는 세션을 SwiftUI와 관련된 내용으로 채운 만큼 WWDC19에서 가장 주요한 내용 중 하나로 포지셔닝하였다.

SwiftUI

가장 최신의 언어 중 하나인 Swift가 최신의 프로그래밍 패러다임을 잘 담고 있듯이 SwiftUI 또한 애플의 뛰어난 개발자들이 여러 의미 있는 소프트웨어적 개념들을 반영한 결과물이다. 그렇기 때문에 SwiftUI의 형태와 구성 하나하나를 음미해보는 것은 최신의 프로그래밍 패러다임의 흐름을 파악할 수 있는 좋은 방법이기도 하다.

이러한 의미에서 SwiftUI에 녹아있는 주요 프로그래밍 패러다임에 대해 정리해 보았다. 프로그래밍 패러다임은 특정 언어나 프레임워크에 국한된 개념이 아니라 컴퓨터 프로그래밍이라는 공통적으로 사고 방식이다. 따라서 단순히 UIKit을 대체하는 개념으로써의 SwiftUI에 익숙해지는 것보다 훨씬 더 SwiftUI에 대한 이해를 높일 수 있을 것이다. 그리고 SwiftUI기반의 새로운 iOS 프로그래밍 아키텍처를 논의하는데에도 탄탄한 바탕이 될 것이다.

선언형 프로그래밍

SwiftUI의 가장 기본이 되는 패러다임은 선언형 프로그래밍(Declarative Programming)이다. 애플도 여러 세션에서 SwiftUI가 선언형임을 반복적으로 강조 하는만큼 이 개념에 대해서는 충분히 이해할 필요가 있다.

선언형 프로그래밍은 명령형 프로그래밍(Imperative Programming)과 대비되는 개념으로 프로그램이 수행해야 하는 동작을 순차적으로 나열하는 것이 아니라 결과적으로 구현하고자 하는 것을 명세하는 형태로 작성하는 프로그래밍 방법이다. 좀 더 직관적인 이해를 위해 아래 예제를 참고해보자.

  • 명령형으로 아보카도 토스트 만들기
    1. 재료를 준비한다 : 아보카도, 빵, 아몬드 버터, 소금, 레드 페퍼
    2. 도구를 준비한다 : 토스터, 접시, 버터 나이프
    3. 빵을 살짝 토스터에 데운다
    4. 접시에 빵을 올려놓는다
    5. 아몬드 버터를 빵위에 얇게 펴바른다
    6. 아보카도를 긴 방향으로 반으로 자른다
    7. 아보카도 씨를 제거한다 …
      = 선언형으로 아보카도 토스트 만들기 (아몬드 버터, 소금, 레드 페퍼를 넣은 아보카도 토스트)

이 때 선언형 프로그래밍은 여러 절차들을 단순히 하나로 묶어내 서브 루틴으로 만든 것과는 다르다는 점에 주의하여야 한다.
구현이 아닌 명세의 형태를 가져야 한다.

물론 튜링 머신의 구조를 갖는 컴퓨터 자체가 명령형으로 동작하기 때문에 모든 프로그램을 선언형으로만 작성하는 것은 불가하다.
따라서 선언형 프로그래밍을 위해서는 복잡한 명령형 코드가 추상화된 프레임워크/라이브러리가 필요하다.

SwiftUI 또한 Function Builder를 통해 간결한 선언형의 외형을 가지도록 추상화 되어 있으면서 실제 UI요소들을 그리는 동작은 프레임워크 내부의 명령형 코드에 의해 이루어진다. 이렇게 플랫폼마다 조금씩 달라질 수 있는 명령형 코드들이 선언형으로 추상화 된 덕분에 iOS, macOS, watchOS, tvOS 모두에서 사용가능한 코드 개발을 가능하게 해준다. 이는 플랫폼의 통합을 추구하는 애플이 SwiftUI를 선보인 중요한 하나의 목적이라고 판단된다.

함수형 프로그래밍

SwiftUI가 외형적으로는 선언형의 특징을 가지고 있다면 내부적인 아키텍처는 함수형 프로그래밍(Functional Programming)의 원칙을 따른다.
함수형 프로그래밍을 설명하는데는 다양한 개념들이 따라오는데 그 중 가장 핵심이 되는 단 하나의 사고 방식을 이야기 하자면 함수를 순수 함수(Pure Function)라는 제한적인 개념으로만 사용한다는 것이다.

순수 함수는 동일한 Input에 대해서는 동일한 Output을 만들어 내며 외부로부터 영향을 주거나 받는 부수 효과(Side-Effet)가 없는 함수이다.
이것은 어떤 단일 기능 구현에 있어서는 상당히 큰 제약으로 작용하지만 전체 아키텍처 구성을 단순화 하는데 있어서는 강력한 이점을 가져다 준다.

 struct OrderCell: View {
    var order: CompletedOrder
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text(order.summary)
                Text(order.purchaseDate)
                    .font(.subheadline)
                    .forgroundColor(.secondary)
            }
            Spacer()
            if order.includeSalt {
                SaltIcon()
            }
        }
    }
}

위의 코드 예제를 참고해보면 SwiftUI에서 뷰는 View 프로토콜을 따른다.
View 프로토콜은 body라는 프로퍼티를 가지고 있어야 한다는 조건을 가지는데 body는 연산 프로퍼티, 즉 함수이다.
뿐만 아니라 뷰는 클래스가 아닌 구조체이기 때문에 내부적인 상태 변경이 불가한 불변적(Immutable) 특성을 가진다.

이러한 구성은 뷰를 위 도식처럼 Input(order 프로퍼티)을 받아 body를 통해 Output으로 View를 출력하는 순수 함수의 형태로 만들어 준다.
그 결과 개발자는 뷰의 내외부 상태를 고려할 필요가 없으며 동일한 데이터에 대해서는 항상 동일한 뷰가 생성되는 것을 보장받는다.
다시 말해 데이터 변경이 발생했을 때 해당 데이터와 관련된 UI요소를 식별하여 업데이트 하는 것이 아니라 순수 함수를 통해 전체의 새로운 뷰를 생성하는 단일 흐름으로 단순화 된다.

이렇게 SwiftUI에서 뷰의 순수 함수적이고 불변적인 특성은 객체 지향적(Object-Oriented)인 UIKit과 결정적으로 차별화 되는 사항으로 SwiftUI를 SwiftUI답게 사용하기 위해 반드시 알고 있어야 하는 사항이다.

0개의 댓글