구조적 프로그래밍

구조적 프로그래밍(structured programming)은 구조화 프로그래밍으로도 불리며 프로그래밍 패러다임의 일종인 절차적 프로그래밍의 하위 개념으로 볼 수 있다. GOTO문을 없애거나 GOTO문에 대한 의존성을 줄여주는 것으로 가장 유명하다. 역사적으로 구조적 프로그램을 작성하기 위하여 몇가지 다른 구조화 기법과 방법론이 개발되어왔다. 가장 일반적인 3가지는 다음과 같다. 잭슨의 구조적 프로그래밍 : 자료구조를 프로그램 구조에 맞추는 것에 중점을 두었다. 데이크스트라의 구조적 프로그래밍 : 프로그램의 논리 구조는 제한된 몇 가지 방법만을 이용하여 비슷한 서브 프로그램들로 구성된다. 프로그램에 있는 각각의 구조와 그 사이의 관계를 이해하면 프로그램 전체를 이해해야 하는 수고를 덜 수 있어, SoC에 유리하다. 데이크스트라의 관점에서 파생된 관점 : 하위 프로그램의 시작점은 한 군데이지만 끝점은 여러 개일 수 있다. 대부분의 사람들이 구조적 프로그래밍이라고 할 때 첫 번째 것을 제외한 둘 중에 하나를 말하는 것이며, 이것이 여기서 말하고자 하는 것이다. 저수준의 관점에서 구조적 프로그램은 간단하고, 계층적인 프로그램 제어 구조로 구성된다. 이 제어 구조들은 하나의 구문으로 간주되며, 동시에 더 간단한 구문들을 결합시키는 방법이다. 더 간단한 구문들은 또 다른 제어 구조일 수도 있고, 할당문이나 프로시저 호출과 같은 기본 구문일 수도 있다. 에츠허르 데이크스트라가 확인한 3가지 형태의 구조는 순차, 선택, 반복이다. 순차(concatenation)는 구문 순서에 따라서 순서대로 수행된다는 것이다. 선택(selection)은 프로그램의 상태에 따라서 여러 구문들 중에서 하나를 수행하는 것이다. 주로 if..then..else..endif, switch, case와 같은 키워드로 표현한다. 반복(repetition)은 프로그램이 특정 상태에 도달할 때까지 구문을 반복하여 수행하거나, 집합체의 각각의 원소들에 대해 어떤 구문을 반복 수행하는 것이다. 보통 while, repeat, for, do..until 같은 키워드로 표현한다. 종종 반복 영역의 시작점을 하나로 하는 것이 추천되며, (원조 구조적 프로그래밍에서는 종료점도 하나로 해야한다고 추천하고,) 몇 가지 언어에서는 이것을 꼭 지켜야 하도록 하고 있다. 데이크스트라의 초창기 가드 명령어 언어같은 어떤 언어에서는 구조를 완전히 둘러싸는 if..fi와 같은 구문으로 구조의 단일성을 강조한다. C 같은 다른 언어들은 구조의 단일성을 강조하지 않는데, 잘못 이해하거나 잘못 수정할 수 있는 위험이 커지는 것은 아니다. 코드 작성자는 큰 조각의 코드를 이해하기 쉬운 크기의 작은 하부 프로그램(함수, 프로시저, 메서드, 블록, 등)으로 나누어야 한다. 일반적으로 프로그램은 전역 변수는 거의 사용하지 않아야 하고 대신에 하부 프로그램은 지역 변수를 사용하거나, 값이나 참조에 의한 인자를 받아야 한다. 이런 기법은 전체 프로그램을 한번에 이해하지 않고, 분리된 작은 코드 조각을 쉽게 이해하는 데 도움을 준다. 구조적 프로그래밍은 항상 그런 것은 아니지만 하향식 설계와 관련이 있다. 하향식 설계를 할 때, 설계자는 큰 규모의 프로그램을 더 작은 공정으로 나누어 구현하고, 각각 검사한 다음에 전체 프로그램으로 합친다. 모든 절차적 프로그래밍 언어에서 구조적 프로그래밍을 할 수 있다. 1970년쯤부터 구조적 프로그래밍이 인기있는 기법이 되었기 때문에, 대부분의 새로 나온 절차적 프로그래밍 언어들이 구조적 프로그래밍을 고취시키기 위한 특징을 추가하였고 구조화되지 않은 프로그래밍을 쉽게 하기 위한 특징들은 남겨둔 것들도 있었다. 잘 알려진 구조적 프로그래밍 언어에는 파스칼(Pascal)과 에이다(Ada)가 있다. 구조적 프로그램 정리는 구조적 프로그래밍의 이론적 기반이 되었다. 정리에 따르면, 프로그램을 결합하는 3가지 방법인 순차, 분기, 반복만으로 충분히 계산가능 함수를 표현할 수 있다. 이런 점은 구조적 프로그래밍 운동에서 나온 것은 아니지만, 이런 구조들은 중앙 처리 장치의 명령 주기뿐만 아니라 튜링 기계의 동작을 설명하는 데 충분하다. 따라서 이런 의미에서 프로세서는 항상 "구조적 프로그램"을 실행한다. 구조적 프로그램이 아닌 기억 장치의 다른 부분에서 읽는 명령을 수행해도 그러하다. 1966년 뵘(Böhm)과 야코피니(Jacopini)의 글을 데이크스트라가 인용하였기 때문에 구조적 프로그래밍의 최초의 이론적 기반이라고 하기도 한다. 구조적 프로그램 정리에 구조적 프로그램을 어떻게 작성하고 분석하는지에 대해서는 나와있지 않다. 이 내용들은 1960년대 후반과 1970년대 초반에 개발되었는데 주로 데이크스트라, 플로이드, 호, 그리즈가 많은 공헌을 했다. 구조적 프로그래밍의 선구적 실천가(얼리어답터)인 플로저는 구조적 프로그램 정리에 대한 그의 반응을 이렇게 설명했다.: 우리는 이 흥미로운 소식을 어셈블리 프로그래머들에게 알려주면서 마음을 돌려보려 하였지만, 이 덜된 어셈블리 프로그래머들은 비비꼬인 로직의 비트들을 만들어내면서 계속해서 '이런건 구조화가 안될껄?'이라고 말하고 있다. 뵘과 야코피니의 증명을 보여주어도, 우리가 구조적 코드를 성공적으로 계속해서 만들어서 보여주어도, 그들은 구조화된 프로그래밍 적응 준비를 하루도 앞당기지 않았다. 1967년, CACM에 데이크스트라의 "GOTO문의 해로움"(Go to statement considered harmful)라는 서한이 실렸다. 이 글에서 그는 뵘과 야코피니의 증명을 인용하면서, 고급언어에서 GOTO 명령을 제거하는 것이 코드의 질을 높일 수 있다고 했다. 이 글은 주로 구조적 프로그래밍 논쟁의 시작점으로 인용된다. 비록 플로저가 언급했듯이 다수의 프로그래머들이 이 정리에 익숙하지 않다고 해도, 이런 프로그래머들을 양성할 가치가 충분히 있을 정도로 몇 년간 소프트웨어 개발은 간결성, 품질, 개발 시간의 측면에서 향상되었다. 데이크스트라는 구조의 종류를 제한하는 것이 프로그래머가 생각하는 데 집중하는 것을 돕고, 관리 가능한 절차로 분석하여 프로그램의 유효성을 더 간단히 보장할 수 있다고 했다. 그는 1969년구조적 프로그래밍에 대한 글에서 이렇게 썼다.: 우리는 정확한 프로그램을 작성하는 프로그래머의 직분을 수행해야 할 뿐만 아니라, 그것의 정확성을 납득가능한 방법으로 증명하는 역할도 수행해야 한다. 위에서 한 언급은 프로그래머가 작성하는 모든 것은 유효하게 구조화되어야 한다는 것에 뜻 깊은 영향을 끼친다. (중략) 프로그램의 정확성뿐만 아니라 프로그램의 적응성과 관리성까지 내가 신경 쓰고 있다는 것이 더욱 명백해진다.1 도널드 커누스(Donald Knuth)는 프로그램이 입증가능성을 염두에 두고 작성되어야 한다는 원리는 받아들였으나 GOTO문을 없애는 것은 받아들이지 않았고 지금도 받아들이지 않는다. 1974년, 그의 논문, "GOTO문이 포함된 구조적 프로그래밍"에서 직접적인 분기를 하여 입증가능성을 희생시키지 않으면서도 더 간결하고 효율적인 코드를 작성할 수 있는 몇 가지 예제를 보였다. 카누스는 좀 더 완화된 구조 제한을 제안했다. 그것은 프로그램의 순서도를 그린다면 왼쪽에는 아래쪽으로 가는 가지(branches)만, 오른쪽에는 위쪽으로 가는 가지만 그려야하며 그 가지들이 서로 교차하지 않아야 한다는 것이다. 컴파일러와 그래프 이론에 정통해 있는 많은 사람들이 축소 가능한 흐름도(reducible flow graphs)만을 허용해야한다고 이 생각을 옹호했다. 구조적 프로그램 이론가들은 1970년대 IBM의 연구원 밀즈가 구조적 프로그래밍 이론에 대한 그의 해석을 뉴욕타임즈의 인덱싱 시스템 개발자들에게 적용한 일이 있은 후에 대부분이 합의를 봤다. 이 계획은 공학적으로 크게 성공하였다. 데이크스트라가 밀즈의 해석이 출판된 것들과 다르다며 비판하였지만, 다른 회사의 관리자들까지도 구조적 프로그래밍의 채택을 지원하기 위하여 밀즈의 해석을 인용했다. 1987년이 되어서도 여전히 컴퓨터 과학 간행물에서 구조적 프로그래밍에 대해 의문점이 제기되었다. 프랭크 루빈은 그 해에 "GOTO문의 해로움의 해로움"('Go to statement considered harmful' considered harmful)이라는 글을 썼다. 루빈은 물론이거니와 양보하라고 한 다른 필자들까지도 날카롭게 비판한 데이크스트라의 응답과 함께 수많은 반대 의견이 뒤따랐다. 20세기의 막바지에 이르자, 대부분의 컴퓨터 과학자들은 구조적 프로그래밍의 개념을 배우고 적용하는 것은 유용하다고 확신했다. 포트란, 코볼, 베이직과 같이 프로그래밍 구조가 원래 취약한 고급 프로그래밍 언어들은 이제 그런 구조를 가지고 있다. GOTO문 제멋대로 사용하는 것을 받아들이는 프로그래밍 교육자들은 찾기가 힘들어졌다. 프로그래머가 경험을 쌓을수록 엄격한 의미의 구조적 프로그래밍을 침해하는 어떤 부분이 있는지를 이해하기가 쉽다는 것을 알았고, 널리 퍼진 몇몇 프로그래밍 언어들은 직접적인 분기문을 제한하고 있으며 예외처리를 이런 상황에서 사용할 수 있게 하고 있다. 주요한 산업용 언어들은 자바와 같은 언어들을 제외하고는 프로시저 내에서의 직접 분기를 위하여 GOTO문을 여전히 유지하고 있다. 데이크스트라가 구조적 프로그래밍을 표준 교육과정에 편입시키는 데는 성공했지만 엄격한 조건을 고수하는 데는 성공하지 못하였다. 대부분의 경우에 하위프로그램에 여러 개의 시작점이 있는 것은 아니지만, 여러 개의 종료점을 가지는 경우는 있다. 주로 하위프로그램이 더 이상 할 일이 없거나 더 이상 계속하지 못하는 상황이 된 경우이다. 다음은 파일에서 자료를 읽어서 처리하는 간단한 프로시저의 전형적인 예이다: open file; while (reading not finished) { read some data; if (error) { stop the subprogram and inform rest of the program about the error; } } process read data; finish the subprogram; 5번째 줄에서 멈추고 알리는 것은 예외를 발생시키거나, 제 2의 리턴을 하거나, 레이블한 루프로 빠져나가거나, 심지어는 goto를 써도 할 수 있다. 프로시저가 2개의 종료점을 갖기 때문에 데이크스트라의 구조적 프로그래밍의 규칙에 어긋난다. 종료점을 하나로 하는 규칙을 지키려고 하면 복잡해진다. 에러 상황이 더 있다면, 청소 규칙이 서로 달라서, 오히려 goto문을 사용한 비구조적인 것보다 훨씬 읽거나 이해하기 어렵게 될 것이다. 반면에 그런 규칙을 따르지 않는 구조적 프로그래밍은 코드를 아주 깔끔하고 읽기 쉽게 할 것이다. 대부분의 언어는 구조적 프로그래밍에서의 여러 종료점을 지원한다. C는 continue, break, return과 같이 여러 가지 경로로 구조에서 빠져나가는 것을 허용하고, 더 새로운 언어들은 레이블한 루프(전자와 비슷하지만 제일 안쪽 루프 뿐만 아니라 그 이상도 빠져나갈 수 있게 해 준다)와 예외처리를 지원한다. 특히 구문분석기와 통신 규약 같은 프로그램들은 상태들이 있어서 기본 구조들로 줄이기가 쉽지 않다. 각각의 상태 변화를 분리하여 하위프로그램을 만들고 변수를 이용하여 활동중인 상태를 나타내면 가능하긴 하다. 하지만, 카누스를 포함한 일부 프로그래머들은 상태의 변화를 새로운 상태로 직접 분기하는 것을 더 좋아한다. 구조적 프로그래밍에 대한 논의는 많은 새로운 언어를 낳았으며, 기존의 언어에 구조적인 면이 추가되는 등 언어의 발전에 도움이 되었다. 그리고 이후에 나온 프로그래밍 패러다임들에도 영향을 끼쳤다. 구조적 프로그래밍은 프로그래머의 습관을 바꾸었다. 프로그램의 정확성을 증명하는 문제를 떠나서 데이크스트라가 그의 논문에서 말한 대로 시간에 따라 변하는 동적인 과정을 시각화하는 것은 인간에게 매우 어려운 일이다. 꼭 GOTO문만의 문제가 아니라 구조화된 흐름 제어문을 사용한다고 할지라도 너무 복잡하게 중첩되어 있거나 스코프의 길이가 너무 긴 코드를 작성한다거나 너무 긴 길이의 하위프로그램을 작성하는 일을 가급적 피하게 경향이 생겼다. 그리고 이런 습관은 다른 사람이 작성한 프로그래밍 코드를 쉽게 이해하는 데 도움을 준다. 데이크스트라가 쓴 "GOTO문의 해로움"이라는 논문은 이후 "...의 해로움"이라는 유행을 낳기도 하였다. 이는 컴퓨터 과학에서 과도하게 사용되는 어떤 것에 대한 것을 비판하는 데 많이 사용되었다. 비구조적 프로그래밍 (대조되는 개념) 프로그래밍 패러다임 순서도 (고급 순서 구조에 대한 더 자세한 내용이 있음) 구조적 예외 처리 최소 평가 (영어) 에츠허르 데이크스트라, Notes on Structured Programming, pg. 6 (영어) 뵘, C. 와 야코피니, G.: Flow diagrams, Turing machines and languages with only two formation rules, CACM 9(5), 1966. (영어) Notes on Structured Programming and Variation Analysis (Programming Wisdom Center)