Search

프로그래밍 패러다임

 패러다임

어떤 한 시대 사람들의 견해나 사고를 근본적으로 규정하고 있는 테두리로서의 인식의 체계, 또는 사물에 대한 이론적인 틀이나 체계를 의미하는 개념 - Wikipedia
패러다임이란 시대에 따라 사람들이 가지는 공통적인 인식 체계를 말합니다. 따라서, 시간과 환경이 변하면 패러다임도 따라 변하게 됩니다.
프로그래밍과 패러다임을 합친 프로그래밍 패러다임은 프로그램을 어떠한 관점으로 바라보고 설계해야 하는지 제시합니다. 어떤 프로그래밍 구조를 사용하고 언제 이 구조를 사용해야 하는지 결정해주는 역할을 하죠.
좋은 프로그램을 만들 수 있는 방법과 시각을 제공해주는 겁니다.
시간과 환경이 변하면 패러다임이 변하듯, 프로그래밍 패러다임이 바라보는 관점도 변하게 됩니다.
초기 컴퓨터 시대에는 컴퓨터가 적은 메모리를 가지고 있었습니다. 따라서, 제한된 프로그래밍 환경을 가지게 됩니다. 제한된 프로그래밍 환경에서 프로그램을 만들기 위해서는 최적화가 중요했습니다.
한 번에 취급하는 데이터를 분리해서 중복되는 데이터를 없애고, 메모리에 직접 접근해서 이를 조작하는거죠.
그렇기에 취급하는 데이터가 변경되면 프로그램도 함께 변경되어야만 했습니다.
점점 컴퓨터가 대중화되면서 소프트웨어의 수요가 급증했습니다.
소프트웨어의 수요가 급증하면서 프로그램이 대량 생산될 필요가 생겼습니다. 하지만, 컴퓨터는 취급 데이터가 변경되면 프로그램도 다 변경되어야 하는 상태였으며 매번 프로그램을 새로 작성해야 했습니다.
따라서, 사람들은 재사용에 관심을 가졌습니다.
매번 새로 만들지 않고 만들어 둔 걸 재사용할 수 없을까하고 생각한 거죠. 그러한 관점에서 나온 프로그래밍 패러다임이 바로, 객체 지향 프로그래밍(OOP) 입니다.
객체 지향 프로그래밍은 데이터를 객체라는 존재로 설계합니다. 객체 추상화, 상속 등의 특징을 가지고 데이터 변화와 재사용에 대응했습니다. 따라서, 객체 지향 프로그래밍(OOP)는 대부분의 주류 프로그래밍 언어가 지원하는 프로그래밍 패러다임이 되었습니다.
시간이 지나면서 CPU 한 개에 여러 개의 코어가 들어가고 하나의 PC에 여러 CPU가 장착되었습니다.
하나의 프로세스에서 돌아가던 프로그램도 여러 개의 스레드로 나뉘어서 여러 프로그램들이 동시에 돌아가게 되었습니다. ⓵하나의 반복된 작업을 병렬로 나눠서 처리해야 하고, ⓶여러 개의 작업을 동시에 수행해야 하며, ⓷각 작업이 끝날 때까지 기다리면서 멈춰도 안되고, ⓸각기 수행된 작업의 결과들이 모두 수집되어서 새로운 작업에 사용되어야 합니다.
즉, 패러다임이 동시성 관점으로 옮겨졌습니다.
물론, 재사용에 관심을 두고 있는 객체 지향 프로그래밍도 동시성 문제를 해결할 수 있기는 합니다. 세마포어를 사용하는 방법이죠. 하지만, 세마포어는 불편하고 많은 문제를 파생시킵니다.
따라서, 이를 해결할 수 있는 다른 방법이 필요했습니다.
동시성 문제를 해결하려면 한 번 만들어진 데이터를 변경하지 않고, 변경된 데이터가 필요하다면 새로운 데이터를 만들어내는 방식을 사용해야 합니다. 기존 데이터가 변경되지 않아서 같은 데이터를 동시에 사용하는 서로 다른 프로그램에서 서로 영향을 주지 않고 프로그램을 돌릴 수 있게 됩니다.
즉, side-effect가 사라집니다. side-effect가 사라지도록 프로그래밍하는 방법이 동시성을 해결해주는 방법인거죠.
이러한 관점이 함수형 프로그래밍(FP)와 닮았습니다.
함수형 프로그래밍은 함수의 실행이 외부에 영향을 끼치지 않도록 함수를 작성합니다. 외부에 영향을 끼치지 않는다는건 외부에 있는 데이터를 건드리지 않는다는 의미가 됩니다. 외부에 있는 데이터를 건드리지 않기 때문에 해당 데이터를 동시에 사용하는 프로그램들이 해당 데이터를 안전하게 접근해서 사용할 수 있게 됩니다.
프로그래밍 패러다임은 시간과 환경이 변하면서 함께 변화했습니다.
그리고 변화하는 패러다임을 지원하는 프로그래밍 언어들이 생겨났습니다. 서로 다른 프로그래밍 언어는 서로 다른 프로그래밍 패러다임을 지원하게 되었습니다. 한 언어가 여러 가지 프로그래밍 패러다임을 지원하기도 합니다.
제가 iOS 개발을 할 때 사용하는 Swift도 객체 지향, 함수형, 프로토콜 지향 프로그래밍을 지원하는 멀티 패러다임 언어입니다.
프로그래밍 패러다임이 각 시대마다 어떤 목적을 가지고 생겨났는지 알아봤습니다. 목적을 가진 프로그래밍 패러다임이 어떠한 특징을 가지고 있는지 알아보겠습니다.

 객체 지향 프로그래밍(OOP)

객체 지향 프로그래밍을 대부분의 주류 프로그래밍 언어가 지원하기 때문에 우리는 객체 지향 프로그래밍에 익숙합니다. 우리는 매일 객체 지향적이게 프로그래밍을 하려고 노력합니다.
객체 지향 프로그래밍은 재사용에 목표를 두고 생긴 패러다임입니다.
그렇기 때문에, 객체 지향은 프로그램을 재사용할 수 있게 만들어주는 특징들로 구성되어 있습니다.
특징을 알아보기 전에 객체 지향이 뭔지부터 알아봅시다.
객체 지향은 우리가 살고 있는 실제 세계와 비슷하게 소프트웨어를 작성해보자는 프로그래밍 방법입니다. 실세계를 소프트웨어에 대입하는 방법이죠.
객체 지향에서는 현실에 존재하는 사물과 대상의 상태와 행동을 실체화 시킵니다. 그리고 대상의 상태와 행동을 하나의 덩어리로 묶어서 생각합니다. 그 묶음이 바로 객체입니다.
객체 안에는 대상의 상태와 행동이 묶여 있습니다. 우리는 객체 안에 있는 상태를 속성, 필드라고 하고, 행동을 메소드라고 합니다. 객체는 연관된 필드와 메소드의 묶음입니다. 우리는 필드에 객체의 상태를 저장하고 메소드를 통해서 특정 작업을 수행할 수 있도록 해줍니다.
현실 세계에 대입해서 예시를 들자면, 사람이라는 객체가 있다고 해봅시다. 사람이라는 객체 안에는 키, 혈액형, 몸무게 등의 상태가 있습니다. 그리고 걷는다, 뛴다, 잔다 라는 행동을 가지고 있습니다. 이 상태와 행동들은 사람이라는 객체 안에 묶여 있습니다. 그리고 사람은 키에 자신의 상태를 저장하고, 걷는다를 수행할 수 있습니다.
객체는 다른 객체들과 메세지를 주고 받으며 상호 작용합니다. 객체는 고유한 기능을 수행하면서 다른 객체와도 상호 작용을 할 수 있습니다. 상호 작용을 하는 방식인 메세지 전달은 바로, 함수 호출입니다.
프로그램 내부에는 다양한 기능을 하는 소프트웨어 객체가 존재하고 이러한 객체를 조합하여 원하는 기능을 구현합니다. 소프트웨어 모듈을 하나의 객체로 만들어서 보관하게 되면 다른 SW 개발 시에 프로그램 코드를 전혀 변경하지 않고 다시 사용할 수 있습니다. 즉, 소프트웨어 개발의 생산성이 향상됩니다.
또한, 객체 지향에서 모듈의 독립성을 유지하기 위해서 전역 변수를 사용하지 않기 때문에 모듈은 높은 독립성을 유지하게 됩니다. 즉, 유지 보수가 쉬워집니다.

캡슐화

캡슐화는 데이터와 데이터를 이용하는 함수를 캡슐 안에 두는 겁니다.
이전 코드들을 재사용하기 위한 체제가 필요했는데, 재사용을 하기 위해서는 코드 자체가 잘 정리되어 있어야 했습니다. 데이터와 함수를 하나의 묶음으로 정리하는 방법이 바로, 캡슐화 입니다.
데이터와 함수를 캡슐로 묶어 버리면 코드가 더 구조화되고, 함수가 데이터를 직접 액세스할 수 있기 때문에 인수를 취할 필요가 없어집니다. 데이터와 함수를 사용하기 더 편리해지는 겁니다.
캡슐화는 기본적으로 정보 은닉 개념이 포함되어 있습니다. 이왕 캡슐을 사용해서 데이터와 함수를 감싸는 김에 멤버 변수가 보이지 않게 감싸자는 겁니다.
캡슐화를 하면 표시할 속성과 숨길 속성을 선택할 수 있게 됩니다. 숨길 속성은 private 접근 제한자로 설정해두게 되는데, 이는 클래스 외부에서 해당 필드에 접근하고 수정하는 것을 불가능하게 만듭니다. 그리고 외부에서 조작할 수 있는 메소드는 public 접근 제한자로 설정해두어 클래스 외부에서 해당 메소드를 사용할 수 있게끔 해줍니다.
메소드는 public 접근 제한자를 사용해서 외부에서 접근할 수 있게끔 할 수 있지만, 변수는 숨겨야 합니다.
왜 멤버 변수가 보이지 않게 감싸야 할까요?
변수를 직접 접근을 허용하게 되면 중대한 실수를 발생시킬 가능성이 커집니다. 하지만, 실수가 실행 결과에 드러나지 않아서 결국 큰 문제가 되어 버립니다.
만약, 외부 모듈에서 객체의 변수에 직접 접근이 가능하게 되면 외부 모듈에서 변경이 일어났을 때 그에 대한 파급 효과로 현재 객체 내부의 메소드나 변수를 수정해야 될 수도 있습니다. 하지만, 외부에서 객체 내부를 알지 못하게 된다면 객체에서 제공한 메소드 외에는 외부 모듈이 건들 수 없기 때문에 파급 효과가 줄어들게 됩니다. public 하게 제공한 메소드에 대해서만 신경쓰면 되니깐요.
그렇게 되면, 나중에 실수가 발생해도 실수를 쉽게 발견할 수 있습니다. 우리는 변수에 접근을 하지 못하도록 클래스를 설계할 필요가 있습니다.
그렇다면, 멤버 변수에 접근할 방법이 없는건가요?
멤버 변수는 상수를 제외하고는 무조건 감추는 것이 좋습니다. 하지만, 외부에서 멤버 변수 값을 사용해야할 수도 있습니다. 외부 접근이 필요한 변수를 상수로 바꾸는 것도 이상한 방법입니다.
이럴 때는 변수를 안에 감추고 메소드를 만들어서 외부에서 사용하도록 허용하면 됩니다. 멤버 변수는 은닉되지만 값은 메소드를 통해서 접근할 수 있게 됩니다.
이렇게 클래스를 만드는 것이 좋은 클래스의 기본 조건입니다.
모든 클래스는 캡슐화를 만족시키기 위해서 노력해야 합니다. 캡슐화는 하나의 목적을 이루기 위해 관련 있는 모든 것을 하나의 캡슐(클래스)에 담아두는 것입니다. 캡슐화를 잘 시키면 프로그램이 간결해지지만 그렇지 않으면 프로그램이 복잡해져서 구현이 어려워집니다.
캡슐화를 위해서 관련된 모든 것들을 관련 클래스 내부로 다 넣으라는 것이 아닙니다. 중요한 건 크기가 아닌 내용입니다.

추상화

추상화는 불필요한 정보는 숨기고 중요한 정보만을 표현하는 겁니다.
불필요한 정보는 숨겨지고 중요한 정보만이 표시되기 때문에 프로그램이 간단해지는거죠. 추상화를 사용하지 않으면 객체들이 너무 복잡해지게 됩니다. 복잡한 객체는 사소한 것도 우리가 신경을 써야될 필요가 있게 됩니다.
차를 예로 들자면, 우리가 차를 운전할 때 인터페이스만을 활용해서 차량을 조종하게 됩니다. 엔진이 어떻게 작동하는지, 바퀴가 어떻게 핸들의 방향에 맞춰서 위치를 바꾸는지 같은 세부 정보는 우리가 몰라도 됩니다. 해당 정보를 몰라도 우리는 충분히 운전을 할 수 있습니다.
만약, 중간에 엔진 작동 방식이 바뀐다고 해서 우리가 운전을 하는 방식이 바뀌는건 아닙니다. 구현 세부 내용이 바뀌었더라도 인터페이스는 그대로 유지되기 때문입니다. 우리는 어제처럼 똑같이 운전을 하면 됩니다.

상속

상속은 재활용을 위한 문법이라고 하지만, 재활용이 목적이 아닙니다.
상속의 목적은 연관된 일련의 클래스들에 대한 공통적인 규약을 정의하는 겁니다.
연관된 클래스에 대한 공통적인 규약이 정의된 클래스에 메소드, 변수를 추가하여 새로운 클래스를 정의하는 겁니다. 새로운 클래스는 공통적인 규약 클래스의 변수, 메소드를 동일하게 가질 수 있게 됩니다.
그렇게 되면, 우리가 직접 작성할 필요없이 이미 존재하는 클래스의 필드와 메소드를 재사용하게 됩니다. 이를 통해서 중복되는 코드가 줄어들게 되고 공통 부분이 하나로 정리되어서 관리하기 쉽고 유지 보수, 변경이 쉬워지게 되는겁니다. 즉, 공통적인 규약을 정의하게 되면서 그에 대한 결과로 재사용도 가능해지게 된 겁니다.
어떤 클래스들이 상속 관계를 맺는 것이 좋을까요?
IS-A 관계가 성립할 때입니다. 한국말로 하면 ~는 ~이다. 관계가 성립할 때 입니다.
예를 들어서, 모바일폰과 스마트폰의 관계가 있습니다. 스마트폰은 모바일폰이 가지고 있는 통화, 문자 기능을 모두 가지고 있으면서, 스마트폰만의 추가적인 기능을 가집니다.
우리는 스마트폰은 모바일폰이다. 라고 말할 수 있습니다. 즉, IS-A 관계가 성립합니다.
상위 클래스에 정의된 메소드는 하위 클래스에서 다시 정의될 수 있습니다. 이를 메소드 오버라이딩(Method Overriding)이라고 합니다. 예를 들어서, 모바일폰이라는 클래스에는 message라는 메소드가 있습니다. 해당 메소드는 1~100자의 텍스트를 보낼 수 있도록 구현이 되어 있습니다. 이를 스마트폰이 상속받아서 message 라는 메소드를 오버라이딩하게 되면 텍스트뿐만 아니라 사진, 동영상을 첨부할 수 있도록 만드는 겁니다.
외관은 message 라는 모습 그대로지만 내부 구현이 완전히 바뀌는거죠. 그렇다고 해서 메소드 이름을 제외하고 모든 부분을 수정해서는 안됩니다. 메소드 이름뿐만 아니라 매개변수, 반환형도 모두 동일해야지만 동일한 메소드로 인식합니다.

다형성

다형성은 하나의 메세지에 대해 각각의 객체가 가지는 고유한 방법으로 응답하는 능력입니다.
객체들은 동일한 메소드명을 사용해서 같은 의미의 응답을 하게 되고 같은 클래스에 속한 인스턴스처럼 수행 가능하게 됩니다. 동일한 코드로 다양한 타입의 객체를 처리할 수 있는 방법이죠.
상속 부분에서 예를 든 message 라는 메소드가 바로 다형성의 예시입니다. 하나의 메세지, 즉 message 메소드에 대해서 각각의 객체가 다른 방법으로 응답하는거죠. 모바일폰은 텍스트로만, 스마트폰은 텍스트뿐만 아니라 사진, 동영상으로까지 응답할 수 있습니다.
다형성은 프로그램이 다른 장치에서도 동일하게 동작할 수 있도록 만들어 줍니다.
만약, 세상에 있는 모든 프린터가 Printable 이라는 인터페이스, 프로토콜을 따르고 있다면 해당 인터페이스, 프로토콜을 준수하는 프린터 드라이버들은 모두 Printable 이 원하는 print 라는 메소드를 가지고 있기 때문에 어떤 프린터에서나 프린트가 가능하게 만들어 줍니다. 해당 드라이버가 삼성 제품 드라이버인지, LG 제품 드라이버인지는 몰라도 됩니다. print 라는 메소드를 어떤 타입의 객체(드라이버)냐에 상관없이 실행할 수 있다는게 중요합니다.
다형성을 사용하게 되면 코드도 줄고, 체계적이고 안정적으로 코드를 작성할 수 있게 됩니다.

Object Oriented Programming

객체 지향 프로그래밍을 잘 사용하면 컴포넌트를 개별적이며 독립적으로 배포 가능하게 해줍니다. 특정 컴포넌트 소스가 변경되면 해당 코드가 포함된 컴포넌트만 다시 배포하면 됩니다. 배포 독립성이 생기는거죠. 배포가 독립적이면 서로 다른 팀에서 각 모듈을 독립적으로 개발할 수 있게 됩니다. 개발 독립성이 생깁니다.
객체 지향은 다형성을 이용하여 전체 시스템의 모든 소스 코드 의존성에 대한 절대적인 제어 권한을 획득할 수 있는 능력이 있습니다. 플러그인 아키텍쳐를 구성할 수 있게 됩니다. 고수준 정책을 포함하는 모듈은 저수준의 세부 사항을 포함하는 모듈에 대해 독립성을 보장 받을 수 있습니다. 독립적으로 개발하고 배포 가능하기 때문입니다.
객체 지향은 객체 지향이 가지고 있는 특징들을 통해서 해당 시대가 가지고 있던 재사용의 문제를 해결하는 패러다임이 될 수 있었습니다.

 함수형 프로그래밍(FP)

함수형 프로그래밍 패러다임은 사실 이전부터 존재했던 패러다임입니다.
하지만, 동시성 문제를 해결해줄 수 있다는 점에서 이제서야 각광을 받고 있는 패러다임입니다. 함수형 패러다임이 데이터를 동시에 사용하는 프로그램들이 데이터를 안전하게 접근해서 사용할 수 있도록 도와주기 때문이죠.
함수형 프로그래밍은 동시성 문제를 해결하기 위해서 생긴 것이 아닌데, 어떻게 동시성을 해결할 수 있는 특징들을 가질 수 있던걸가요?
일단, 함수형 프로그래밍 위에 있는 선언형 프로그래밍부터 알고 갑시다.
선언형 프로그래밍을 쉽게 설명하기 위해서 우리가 잘 아는 명령형 프로그래밍을 데리고 오겠습니다. 선언형 프로그래밍은 명령형 프로그래밍의 반대되는 개념입니다. 명령형 프로그래밍은 원하는 결과를 얻기 위해 필요한 지침에 따라서 코드를 작성하지만, 선언형 프로그래밍은 원하는 결과를 표현하기 위해 코드가 작성됩니다.
예를 들어서, 숫자 배열([1, 2, 3, 4, 5, 6])에서 홀수만 뽑아내는 기능을 코드로 작성한다고 해봅시다.
명령형 프로그래밍으로 해당 코드를 작성하면 이렇게 작성될 겁니다.
func findOdd(arr: [Int]) -> [Int] { var results: [Int] = [] arr.forEach { if $0 % 2 != 0 { result.append($0) } } return results }
Swift
복사
홀수 값을 얻기 위해 숫자 배열을 반복문 안에 넣어서 각각의 원소들이 홀수인지, 짝수인지 확인해보고 홀수라면 새로운 배열 안에 넣어서 해당 배열을 반환하는 식으로 코드를 작성했습니다.
현재 초록색으로 하이라이트되어 있는 부분이 바로 홀수 값이라는 원하는 결과를 얻기 위해 필요한 지침입니다. 요구 사항을 충족하기 위한 모든 단계를 하나씩 적고 있습니다.
이렇게 코드를 작성하게 된다면, 버그나 실수가 생기기 더 쉽습니다. 또한, 함수가 정확히 무엇을 하려고 하는지 한줄 한줄 정성드려서 자세히 읽어야 하죠.
반대로 같은 예시를 선언형 프로그래밍으로 작성해본다고 생각해볼게요.
func findOdd(arr: [Int]) -> [Int] { return arr.filter { $0 % 2 != 0 } }
Swift
복사
현재 배열에 홀수만 필터링해서 결과로 반환하는 식으로 코드를 작성했습니다.
명령형과 다르게 얻기 위한 단계를 작성하지 않았습니다. 그저 원하는 결과만 작성했습니다. 제가 원하는 결과는 배열에 홀수만 들어오는 겁니다. 즉, 홀수만을 필터링해서 가지고 있는 배열이죠.
코드로 배열에 적용할 filter 가 무엇인지 설명했을 뿐 “어떻게” 적용되어야 하는지는 적지 않았습니다. 그건 중요하지 않으니깐요.
선언형 프로그래밍에서는 원하는 결과값을 말하는 것이 어떻게 해낼 지 알아낸 것과 마찬가지 입니다.
물론, filter 라는 메소드 안에는 명령형으로 작성되어 있을 수 있습니다. 모든 선언형 코드는 명령형 코드 위에 쓰여지기 때문이죠.
함수형 프로그래밍 패러다임도 선언형처럼 “어떻게”가 아닌 “무엇”을 원하는지를 적습니다.
그에 더해서, 함수형 프로그래밍은 함수에 입력된 인수에 의존해서 출력값을 냅니다. 즉, f(x)f(x)는 항상 같은 값을 가집니다.
어떻게 항상 같은 값을 가질 수 있나요?
함수형 프로그래밍에서는 외부에서는 함수 안을 보거나 접근할 수 없고, 함수 안에서도 밖에 접근할 수 없습니다. 위에서 설명한대로 함수는 입력된 인수에만 의존하게 되죠.
따라서, 어떤 함수 f(x)f(x)에 2를 넣어서 5가 나온다고 하면, 항상 함수 f(x)f(x)는 2를 넣었을 때 5를 방출합니다.
함수형 프로그래밍은 항상 같은 값을 가지는 함수들을 적용하고 묶어서 프로그램을 구성해나갑니다.
이러한 함수를 순수 함수라고 합니다.
순수 함수는 외부의 상태값을 참조하지 않고, 외부 상태를 변경하지도 않죠. 따라서, 동일한 인자를 넣으면 항상 동일한 결과값을 반환할 수 있는 겁니다. 외부에 전혀 영향을 받지 않으니깐요.
그렇다면, 함수형 프로그래밍 중에 인자로 들어온 데이터를 변경해야 한다면 어떡해야 하나요?
인자로 들어온 데이터를 변경하는 것은 함수형 프로그래밍이 아닙니다. 함수형 프로그래밍은 함수 안에서 밖으로 접근할 수 없습니다. 대신, 새로운 객체를 만들어서 결과값으로 전달하게 됩니다.
새로운 객체를 만들게 되면 함수에 인자로 전달된 데이터의 상태를 변경하지 않음으로써 side-effect를 만들지 않아도 되기 때문입니다. 즉, 불변성이 유지됩니다.
이렇게 사용하면 멀티 스레딩 환경에서도 안정적으로 동작하게 됩니다.
어떻게 고정값만 쓰면서 소프트웨어를 만들 수 있죠?
아무런 상태 변화를 일으키지 않는다는 말이 아닙니다. 물론, 모든 걸 100% 함수형 기준에 맞추는 건 매우 어렵습니다. 하지만, 일정 작업을 side-effect 없이 안정적이고 예측 가능한 프로그램으로 짜는 것이 함수형 프로그래밍입니다.
불변 데이터를 사용하고 map, filter, reduce 같은 거 사용하면 함수형 프로그래밍인가요?
아닙니다. map, filter, reduce 같은 고차 함수는 프로그래밍 기법입니다. 함수형 프로그래밍이 아니더라도 사용할 수 있습니다.
함수형 프로그래밍의 큰 특징은 side-effect가 없다는 겁니다. 함수를 중심으로 side-effect가 없도록 프로그래밍하는 것이 중요합니다.
자꾸 side-effect, side-effect 하는데, 부작용이 없다는 게 무슨 뜻인가요?
함수형 프로그래밍은 “side-effect”에 의한 문제로부터 보다 자유롭습니다.
“side-effect”는 어떤 함수의 동작에 의해서 프로그램 내 특정 상태가 변경되는 상황입니다. 변경된 상태는 다른 동작에 영향을 미칩니다. 예를 들어서, 반복문(for, while문)은 반복문 내부에서 외부 변수를 가져와서 바꾸는 상황이 발생하기 때문에 부수 효과(side-effect)가 생깁니다. 반복문에서 사용하고 있는 변수값을 다른 함수가 가져가서 쓰려고 할 때 문제가 생기는거죠.
프로그램 내 특정 상태가 변경되면서 발생하는 문제로는 race condition, deadlock, concurrency 문제가 있습니다. 이런 문제를 수동적으로 막는 방식들이 존재합니다. 하지만, 구현이 어렵고 주의가 많이 필요하죠.
따라서, 함수형 프로그래밍은 애초에 변수의 값 변경이 완전 발생하지 않도록 하여 이런 오류를 방지합니다. 문제의 소지가 있는 일을 하지 않는 코딩 방식인겁니다.
그럼 함수형 프로그래밍으로만 코드를 구성할 수 있을까요?
UI 어플리케이션 프로그램을 만들 때는 많은 상태 변화가 일어납니다. 순수 함수 프로그래밍으로 프로그램을 구성하는데에는 무리가 있습니다.
이러한 문제를 해결하기 위해서 함수형 + 반응형 프로그래밍을 사용하거나 객체 지향에서 함수형 프로그래밍의 특징을 결합해서 조금 더 안정적인 프로그래밍이 가능해지게 됩니다.
함수형 프로그래밍으로 객체 지향 프로그래밍을 대체할 수 있나요?
객체 지향 프로그래밍을 함수형 프로그래밍으로 대체하는 건 불가합니다. 위에서 봤지만, 두 패러다임이 해결하고자하는 문제가 완전히 다릅니다. 객체 지향은 재사용을 위함이고, 함수형은 동시성을 위함이니깐요. 각 시대의 변화와 요구에 따라 기존의 문제를 해결하면서 제시된 것이기 때문에 각각 사용하는 용도가 다릅니다.
어느 하나가 다른 하나를 대체할 수 있는 것이 아니기 때문에, 원하는 목표에 따라서 적절한 것을 잘 골라서 사용하면 됩니다.
클린 아키텍쳐 책에서는 현명한 아키텍트라면 가능한 한 많은 처리를 불변 컴포넌트로 옮겨야 하고, 가변 컴포넌트에서는 가능한 한 많은 코드를 빼내야 한다고 합니다.
함수형 프로그래밍은 가변 컴포넌트로 인한 동시성 문제를 해결하기 위한 탁월한 방법입니다.
위에서 설명한 부분들 외에도 함수형 프로그래밍을 제대로 하기 위해서는 다양한 컨셉에 대한 이해가 필요합니다. 저도 아직 이해가 부족하기 때문에 이에 대한 공부가 추가로 이루어 진다면 새로운 포스팅으로 나타나겠습니다.

 참고 자료