일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 코딩 테스트
- Java
- 검색 기능 확장
- 완주하지 못한 선수
- @configuration
- H-index
- 모던 자바 인 액션
- @Setter
- @Getter
- 기능개발
- 가장 큰 수
- 스택/큐
- @Data
- 쿠버네티스
- 해시
- 알고리즘
- 롬복 어노테이션
- 크론 표현식
- 영속 자료구조
- 다리를 지나는 트럭
- K번째수
- @EnableScheduling
- 정렬
- kubenetes
- 루씬 인 액션
- 스프링 스케쥴러
- 전화번호 목록
- 커링
- 프로그래머스
- 고차원 함수
- Today
- Total
Today I Learned
02 벽돌부터 시작하기: 프로그래밍 패러다임 본문
1954년경에 앨런 튜링은 사람이 식별할 수 있는 형태의 실질적인 프로그램을 실제 컴퓨터에서 코드로 작성했다. 이들 프로그램은 반복문, 분기문, 할당문, 서브루틴, 스택 등 우리에게 익숙한 구조를 사용했다.
1940년대 후반 어셈블러가 처음으로 등장했다. 이 '언어'의 등장으로 바이너리 코드로 프로그램을 작성해야 했던 프로그래머의 단조롭고 고된 일이 줄어들었다.
1951년 그레이스 호퍼는 컴파일러(complier)란 용어를 만들고, 최초의 컴파일러인 A0을 발명했다. 1953년 포트란이 발명되었고 코볼, PL/1, SNOBOL, C, 파스칼, C++, 자바 등 수 많은 프로그래밍 언어가 발명되었다.
프로그래밍 패러다임(paradigm)에도 혁신적인 변화가 몰아쳤다.
패러다임이란 프로그래밍을 하는 방법으로, 대체로 언어에 독립적이다.
패러다임은 어떤 프로그래밍 구조를 사용할지, 그리고 언제 이 구조를 사용해야 하는지 결정한다.
3장. 패러다임 개요
구조적 프로그래밍(structured programming)
1968년, 데이트스트라는 무분별한 점프(goto 문장)는 프로그램 구조에 해롭다는 사실을 제시했다. 그리고 이러한 점프들을 if/then/else와 do/while/until과 같이 더 익숙한 구조로 대체했다.
구조적 프로그래밍은 제어흐름의 직접적인 전환에 대해 규칙을 부과한다.
객체 지향 프로그래밍(object-oriented programming)
1966년, 올레 요한 달과 크리스텐 니가드는 함수 호출 스택 프레임을 힙으로 옮기면, 함수호출이 반환된 이후에도 함수에서 선언된 지역변수가 오랫동안 유지될 수 있음을 발견했다. 이러한 함수가 클래스의 생성자가 되었고, 지역 변수는 인스턴스 변수, 그리고 중첩 함수는 메서드가 되었다. 함수포인터를 특정 규칙에 따라 사용하는 과정을 통해 필연적으로 다형성이 등장하게 되었다.
객체 지향 프로그래밍은 제어흐름의 간접적인 전환에 대해 규칙을 부과한다.
함수형 프로그래밍(functional programming)
알론조 처치는 어떤 수학적 문제를 해결하는 과정에서 람다 계산법을 방명했는데, 함수형 프로그래밍은 이러한 연구결과에 직접적인 영향을 받아 만들어졌다. 람다 계산법의 기초가 되는 개념은 불변성(immutability)으로, 심볼(symbol)의 값이 변경되지 않는다는 개념이다. 이는 함수형 언에는 할당문이 전혀 없다는 뜻이기도 하다.
함수형 프로그래밍은 할당문에 대해 규칙을 부과한다.
각 패러다임은 프로그래머에게서 권한을 박탈한다. 어느 패러다임도 새로운 권한을 부여하지 않는다.
패러다임은 무엇을 해야할지를 말하기보다는 무엇을 해서는 안되는지를 말해준다.
우리는 아키텍처 경계를 넘나들기 위한 메커니즘으로 다형성을 이용한다.
함수형 프로그래밍을 이용하여 데이터의 위치와 접근 방법에 대해 규칙을 부과한다.
모듈의 기반 알고리즘으로 구조적 프로그래밍을 사용한다.
세가지 패러다임과 아키텍처의 세 가지 큰 관심사(함수, 컴포넌트 분리, 데이터 관리)가 어떻게 연관되는지에 주목하자.
4장. 구조적 프로그래밍
"프로그램이 올바르게 동작한다는 것을 어떻게 증명할 수 있을까?"
증명
구조적 프로그래밍은 모듈을 증명 가능한 더 작은 단위까지 재귀적으로 세분화하는 방식을 사용한다.
분기(selection)와 반복(iteration)이라는 단순한 제어구조와 순차 실행(sequntial execution)이 결합하였을 때 프로그램이 올바르다는 것을 수학적으로 증명할 수 있게 된다.
데이크스트라는 단순한 열거법을 통해 순차실행을, 분기를 통한 각 경로를 열거하는 방식으로 분기 구조를, 귀납법을 사용해서 반복 구조를 증명하였다.
해로운 성명서
goto 문장은 모듈을 재귀적으로 분해하는 과정을 방해하여, 분할 정복 접근법을 사용할 수 없게 한다.
이때문에 대다수의 현대적 언어에는 (아무런 제약없는) goto문장을 지원하지 않는다.
기능적 분해
구조적 프로그래밍을 통해 거대한 문제 기술서를 받더라도 각 기능들에 대한 저수준의 함수들로 분해할 수 있게 되었고, 이렇게 분해한 기능들은 구조적 프로그래밍의 제한된 제어구조를 이용하여 표현할 수 있게 되었다.
이를 토대로 구조적 분석이나 구조적 설계와 같은 기법이 만들어졌고, 이 기법들을 통해 대규모 시스템을 모듈과 컴토넌트로 나누고, 입증할 수 있는 아주 작은 기능들로 세분화할 수 있게 되었다.
엄밀한 증명은 없었다
하지만 대게의 프로그래머들이 세세한 기능 하나하나를 엄밀히 증명하는 고된 작업을 하지는 않기에, 프로그램의 수학적 증명이 이뤄지지는 않았다. 대신 반증을 통한 과학적 방법을 사용한다.
과학이 구출하다
비록 증명을 통해 프로그램이 올바르다는 것을 입증하지는 않지만, 테스트를 통해 세부 기능들이 거짓인지 증명하려고 시도한다. 거짓임을 증명하려는 테스트가 실패한다면, 목표에 부합할만큼은 충분히 참이라고 여기는 것이다.
결론
구조적 프로그램이 오늘날까지 가치있는 이유는 프로그래밍에서 반증 가능한 단위를 만들어 낼 수 있는 능력 떄문이다.
가장 작은 기능등에서부터 가장 큰 컴포넌트에 이르기까지 모든 수준에서 소프트웨어는 과학과 같고, 따라서 반증 가능성에 의해 주도된다.
소프트웨어 아키텍트는 모듈, 컴포넌트, 서비스가 쉽게 반증 가능하도록(테스트하기 쉽도록) 만들기 위해 노력해야 한다. 이를 위해 구조적 프로그래밍과 유사한 제한적 규칙들을 받아들여 활용해야한다.
5장. 객체 지향 프로그래밍
"객체지향이란 무엇인가?"
누군가는 "데이터와 함수의 조합"이라고 답하지만, 객체지향 이전에도 프로그래머는 데이터 구조를 함수에 전달해왔다.
또 누군가는 "실제 세계를 모델링하는 새로운 방법"이라고 답한다. 하지만 이 대답은 의도조차도 불분명하며, 그 정의가 너무 모호하다.
캡슐화와 상속, 다형성이라는 개념을 통해 객체지향을 설명하려고 시도하기도 한다. 이 개념들에 대해 살펴보자.
캡슐화
객체지향을 정의하는 요소로 캡슐화가 언급되는 이유는 데이터와 함수를 쉽고 효과적으로 캡슐화하는 방법을 객체지향 언어가 제공하기 때문이다.
캡슐화를 통해 내부 데이터는 은닉되고(private), 일부 함수만이 외부에 노출된다(public).
하지만 완벽한 캡슐화를 제공하던 C언어와 달리, C++에서는 헤더파일에 클래스의 멤버 변수를 선언해야했고 자바, C#에서는 헤더와 구현체를 분리하는 방식을 버림으로써 클래스 선언과 정의를 구분하는게 불가능해졌다.
실제로는 객체지향 언어들이 완벽한 캡슐화를 약화시켰으며, 객체지향이 강력한 캡슐화에 의존하지 않는다는 뜻이다.
상속
객체지향 언어가 더 나은 캡슐화를 제공하지는 못했지만, 상속만큼은 확실히 제공했다.
하지만 상속이란 단순히 어떤 변수화 함수를 하나의 유효범위로 묶어서 재정의하는 일에 불과하며, 객체지향 언어 이전에도 이러한 방식으로 손수 구현할 수 있었다.
객체지향 언어가 새로운 개념을 만들었다기보다는, 데이터 구조에 가면을 씌우는 일을 상당히 편리한 방식으로 제공했다고 볼수 있다.
다형성
객체지향 이전에도 함수에 대한 포인터를 직접 사용하여 다형성을 사용해왔다.
하지만 함수형 포인터는 위험하다. 포인터와 초기화와 함수 호출에 대한 관례를 지키지 않으면 버그가 발생하고 찾아내기도 어렵다.
객체지향 언어는 이러한 관례를 없애주며, 실수할 위험이 없다. 객체지향은 다형성에 대한 강력한 기능을 제공한다.
이러한 이유로 객체지향은 제어흐름을 간접적으로 전환하는 규칙을 부과한다고 결론지을 수 있다.
다형성이 가진 힘
플러그인 아키텍처는 입출력 장치의 독립성을 지원하기 위해 만들어졌으며, 등장 이후 거의 모든 운영체제에서 구현되었다. FILE에 정의된 다섯 가지 표준 함수를 구현한다면, 입출력 장치가 무엇이든 동일한 프로그램을 사용할 수 있게된다.
그럼에도 객체지향 이전까지 대다수의 프로그래머는 직접 작성하는 프로그램에서는 이러한 개념을 확장 적용하지 않았는데, 함수를 가리키는 포인터를 사용하면 위험을 수반하기 때문이었다.
의존성 역전
전형적인 호출 트리에서는 main 함수 → 고수준의 함수 → 중간 수준의 함수 →저수준의 함수 순으로 호출한다. 이러한 호출트리에서 소스 코드 의존성의 방향은 반드시 제어 흐름(flow of control)을 따르게 된다.
객체지향에서는 다형성을 통해 의존성 역전(dependency inversion)을 시킬 수 있다.
코드 사이에 인터페이스를 추가함으로써 소스 코드 의존성에 대한 방향을 결정할 수 있으며, 이를 통해 고수준의 정책을 포함하는 모듈은 저수준의 세부사항을 포함하는 모듈에 대해 독립성을 보장할 수 있다.
예를 들어, 업무 규칙, UI, 데이터베이스는 세 가지로 분리된 컴포넌트 또는 배포 가능한 단위로 컴파일 할 수 있으며, 각각의 컴포넌트는 서로 의존하지 않는다.
따라서 특정 컴포넌트의 소스 코드가 변경되면, 해당 코드가 포함된 컴포넌트만 다시 배포하면 되는 배포 독립성이 확보된다.
또한 시스템의 모듈을 독립적으로 배포할 수 있게 되면, 서로 다른 팀에서 각 모듈을 독립적으로 개발할 수 있다. 이것이 개발 독립성이다.
결론
객체 지향이란 다형성을 이용하여 전체 시스템의 모든 소스 코드 의존성에 대한 절대적인 제어권한을 획들할 수 있는 능력이다.
플러그인 아키텍처를 구성하여 고수준의 정책을 포함하는 모듈은 저수준의 세부사항을 포함하는 모듈에 대해 독립성을 보장할 수 있다.
저수준의 세부사항은 중요도가 낮은 플러그인 모듈로 만들 수 있고, 고수준의 정책을 포함하는 모듈과는 독립적으로 개발하고 배포할 수 있다.
6장. 함수형 프로그래밍
자바 프로그램은 가변 변수를 사용하는데, 가변 변수는 프로그램 실행 중에 상태가 변할 수 있다.
반면, 함수형 언어인 클로저에서는 변수가 한번 초기화되면 절대로 변하지 않는다.
불변성과 아키텍처
아키텍트는 왜 변수의 가변성을 염려하는가?
경합(race), 교착상태(deadlock), 동시 업데이트(concurrent update)문제는 모두 가변 변수로 인해 발생한다.
즉, 다수의 스레드와 프로세스를 사용하는 애플리케이션에서 마주치는 모든 문제는 가변 변수가 없다면 절대로 생기지 않는다.
하지만 저장 공간과 처리능력이 무한하지 않기에, 모든 값을 불변으로 만들수는 없다.
가변성의 분리
따라서 변수를 변경하는 컴포넌트와 변경하지 않는 컴포넌트를 분리해야 한다. 그리고 가변 변수를 보호하는 적절한 수단(ex. 트랜잭션 메모리)을 동원해 뒷받침해야한다.
이벤트 소싱
이벤트 소싱은 상태가 아닌 트랜잭션을 저장하자는 전략이다. 상태가 필요해지면 단순히 상태의 시작점부터 모든 트랜잭션을 처리한다.
어플리케이션은 CRUD가 아니라 그저 CR만 수행하며, 변경과 삭제가 발생하지 않으므로 동시성 업데이트 문제 또한 일어나지 않는다.
충분한 저장 공간과 처리 능력이 확보될수록 완전한 불변성(=완전한 함수형)으로 만들 수 있게 된다.
결론
- 구조적 프로그래밍은 제어흐름의 직접적인 전환에 부과되는 규율이다.
- 객체지향 프로그래밍은 제어 흐름의 간적적인 전환에 부과되는 규율이다.
- 함수형 프로그래밍은 변수 할당에 부과되는 규율이다.
각 패러다임은 우리가 코드를 작성하는 방식의 형태를 한정시킨다. 어떤 패러다임도 우리의 권한이나 능력에 무엇인가를 보태지는 않는다.
1946년 앨런 튜링이 전자식 컴퓨터에서 실행할 거의 최초의 코드를 작성할 때 사용한 규칙과 지금의 소프트웨어 규칙은 조금도 다르지 않다. 도구는 달라졌고 하드웨어도 변했지만, 소프트웨어의 핵심은 여전히 그대로다.
컴퓨터 프로그램은 순차(sequence), 분기(selection), 반복(iteration), 참조(indirection)로 구성된다.
그 이상도 이하도 아니다.
'클린 아키텍처' 카테고리의 다른 글
04 컴포넌트 (2) (0) | 2021.07.30 |
---|---|
04 컴포넌트 (1) (0) | 2021.07.23 |
03 설계 원칙 (2) (0) | 2021.07.16 |
03 설계 원칙 (1) (0) | 2021.07.09 |
01 소개 (0) | 2020.02.02 |