티스토리 뷰

프로그래밍

프로그래밍 언어의 간단한 역사

이명헌의 경영과 투자 2020. 7. 2. 15:06

기계어, 어셈블리어, 절차적 언어, 객체 지향 언어

1999-5-8

 

* www.mackido.com  글을 저자 동의 하에 번역한 것입니다.

로 레벨 코드(Low Level Code)

컴퓨터는 아주 단순한 지시사항(이하,instruction)을 매우 빠르게 수행하는 기계입니다. 인스트럭션은 0과 1로 이루어져 있습니다. 이런 형태의 인스트럭션을 기계어(machine code)라 합니다. 그런 코드는 기계만 이해할 수 있기 때문입니다.

1과 0은 컴퓨터 내부의 전기 신호 중 하나로, 켜지면 1, 꺼지면 0입니다. 이 신호들은 0 또는 1 가운데 하나의 값을 갖는다고 해서 '이진 (binary)'이라 합니다. 그리고 그 값을 '비트(bit)'라 합니다. bit는 'binary digit(이진수)'의 약자입니다.

 

1940-1950년대의 컴퓨터는 사실상 프로그래밍 한다는 것이 불가능에 가까웠습니다. 그 물건들은 '배선 (wiring)'을 통해 제어되었죠. 기판 상에 있는 작은 전선을 조작해서 연결이 이루어진 곳은 1을, 연결이 없는 곳은 0을 나타내게 해놓은 것입니다. 전선 연결의 조합이 당시의 프로그래밍이었습니다. 이런 형태의 배선은 만드는 데 아주 시간이 오래 걸리고, 하기도 힘들 뿐 아니라, 뭔가 문제가 생겼을 때 에러를 찾아낸다는 것이 거의 불가능 했습니다. 어떤 문제가 생기면, 문자 그대로 전선 하나하나를 따라가며 어느 선에서 문제가 생겼는지를 찾아내야 했습니다. 문제는 전선이 수천, 수만 개가 얽혀 있다는 점입니다. 새로운 프로그램을 추가하는 것도 일일이 새로운 배선을 해주어야 하는 지루하기 짝이 없는 작업이었습니다.

 

http://www.columbia.edu , ENIAC 초기 프로그래머들

버그(bug)라는 단어가 이때 유래되었다는 전설도 있습니다. 당시의 거대한 컴퓨터 안에서 어슬렁 거리다가 장렬하게 전사한 진짜 '벌레'의 시체가 전선과 전선 사이에 또 다른 연결을 만들어서 에러를 내었다는 것입니다. 벌레 때문에 문제가 일어난 것 자체는 사실이었습니다. 하지만 '버그'라는 단어가 이로부터 유래되었다는 주장은 도시민들의 소박한 상상력의 소산입니다. '버그'라는 용어는 1940년대 훨씬 훨씬 이전부터 있어왔고, 컴퓨터가 태어나기 100 년 전부터 기계류에서 어떤 문제가 생긴 경우 보편적으로 사용되던 단어였습니다. 그 옛날 기계들도 벌레가 어슬렁거리다가 기계 내부로 추락한 경우 엉망이 되어버렸기 때문입니다. (특히 톱니바퀴가 많은 시계나 뮤직박스 같은) 컴퓨터 버그는 이런 오랜 용어 사용의 관행이 이어진 것일 뿐입니다.

 

조금 시간이 지나자 기계어는 직접 배선을 해주어야 하는 수준은 벗어나게 되었습니다. 대신 '메모리'내의 값(value)을 이용해서 프로그래밍을 하게 됩니다.(1950-60년대) 새로운 방식은 예전의 무식한 방식보다는 분명 향상된 것이었고, 빠른 프로그램 수정을 가능케 해주었지만 수많은 0과 1로 이루어진 패턴과 씨름하다 보면 많은 에러가 나는 것은 마찬가지였습니다. 사람들은 보다 쉽게 기억하기 위해 0과 1로 이루어진 비트 패턴 대신 어느 정도 이해가 되는 일종의 약어(mnemonics)를 사용하기 시작했습니다.

 

 

약어는 비트 패턴의 기능 설명으로 부터 만들어진 용어였습니다. 예를 들면 move, jump, branch같은 식입니다. 이들 명령어는 어셈블러(assembler)라 불리우는 프로그램에 의해 이진수 패턴 형태로 바뀝니다. 이렇게 축약어로 이루어진 언어를 어셈블리어(assembly language)라고 합니다. 어셈블러가 변환을 담당한 데서 유래된 이름입니다.

 

어셈블리어와 기계어는 대단히 상세하게 컴퓨터가 취할 동작을 규정합니다. 각각의 인스트럭션은 그야말로 단순한 작업을 지시하는 것입니다. 몇 개의 스위치 상태를 바꾼다든지 전기적으로만 의미가 있는 미묘한 작업을 수행합니다. 그러므로 어떤 문제점을 해결하기 위해 프로그램을 모델링하는 데 곧바로 사용되기에는 무리가 있었습니다. 어셈블리어로 이루어지는 프로그래밍은 몇몇 프로그램에 국한되어 이루어지게 됩니다.

 

어셈블리어가 얼마나 세세하게 규정하고 있는지는 예를 들어보면 될 것입니다. 누군가에게 '먹어라'는 요청을 한다면 어셈블리어로 이렇게 표현됩니다.

 

'당신 오른 팔에 붙은 오른 손을 뻗쳐서 그 오른 손에 붙어있는 손가락을 이용해서 포크를 잡아라. 그리고 음식물을 자르거나 파내어서 앞에서 말한 포크 위에 얹어라. 입을 벌리고 오른 팔을 구부려서 입 쪽으로 가져가라. 입을 향한 방향으로 음식물이 입에 넣어질 때까지 오른 팔을 계속 움직여라. 음식물이 도착할 시점엔 포크 움직이는 것을 멈춰라.(포크가 당신 머리 뒤를 관통하지 않도록) 포크 둘레로 입을 닫고(너무 세게 말고) 포크를 빼내라. 위 과정을 접시가 비워질 때까지 반복하라.'

 

이것도 그다지 상세하게 묘사한 것은 아니지만, 어셈블리어로 프로그래밍한다는 것이 어떤 것인지 대략 짐작할 수 있으리라 생각합니다.

 

그런 식으로 10-20년 정도 컴퓨터를 다루다보니 대부분의 프로그래머들은 단어 의미에 대단히 철저지게 되었습니다. (너무 과하게 철저해졌습니다.) 이 점을 확인해 보고 싶으면 참을성 많은 제 아내에게 물어보세요. 내가 텔리비젼을 가리고 있어서 아내가 'move'라고 말한 경우 나는 문자 그대로 살짝 움직이고 맙니다. 정확하게 텔리비젼으로부터 비키라고 얘기하지 않았기 때문이죠. 또는 내 주변에 있는 사람들에게 물어봐도 됩니다. 간단한 질문을 한 뒤 몇 페이지에 이르는 대답을 들은 사람을 어렵쟎게 찾을 수 있을 것입니다. 

하이 레벨 코드(High Level Code)

어셈블리어는 '로-레벨' 언어로 알려지게 되었습니다. 프로그래머가 컴퓨터와 매우 하위 수준에서 얘기를 나누기 때문입니다. (실제 컴퓨터가 이해하는 언어를 말하고 있는 것과 마찬가지입니다.) 어셈블리어의 까다로운 점들을 개선하고 프로그래머의 생산성을 높이기 위해 보다 '상위'에 존재하는 언어가 1960-1970년대에 만들어져 나오게 나옵니다.

 

https://www.researchgate.net, 포트란 코드

 

포트란(Fortran; FORmular TRANslator)이 초기에 나온 하이 레벨 언어의 대표입니다. 기본적으로 하이 레벨 언어는 프로그래머가 지나치게 세세한 인스트럭션을 내리지 않아도 되도록 디자인 되었습니다. 프로그래머는 보다 더 생산적이 될 수 있었고, 보다 규모가 큰 복잡한 프로그램을 용이하게 만들어 낼 수 있었습니다. 하지만 모든 코드는 정확하게 프로그래머의 조직화 정도만큼만 체계적이었습니다. 그리고 대부분의 프로그래머는 그다지 조직적으로 사고하고 있지는 않았습니다. 프로그래밍도 일종의 새로운 기술인데 아직 충분한 트레이닝을 할 시간이 없었습니다. 몇몇 코드들은 상당히 우아하게 잘 만들어졌지만, 나머지는 유치원생이 피카소 그림을 흉내낸 것과 비슷했습니다.

 

이렇게 수준이 떨어지는 프로그래머들이 만든 코드를 '스파게티 코드(spaghetti code)'라고 합니다. 스파게티 면발을 더듬어 가는 것처럼 프로그램 흐름이 미로처럼 얽혀 있었기 때문입니다. 여기저기로 이동하다가 얽혀버립니다. 하이 레벨 언어를 사용한 프로그래밍이 보편화되면서 규모가 크고 훨씬 복잡한 프로그램을 만들어낼 수 있었지만 그 복잡성 자체가 문제가 됩니다. 프로그램이 너무나 커지고 복잡해져서 유지 관리나 에러 수정이 상당히 곤란했습니다. 이것은 거대한 유지 관리비로 귀결되었습니다 그런 형태의 붕괴는 보다 덜 일어나고는 있지만 오늘날에도 드문 일은 아닙니다.

 

초창기 하이 레벨 언어의 문제점 중 하나는 매우 한정된 종류의 데이타 유형만을 다룰 수 있다는 점이었습니다. 프로그래머는 원시적인 데이타 타입만을 이용해서 모든 것을 구성해 보려 했습니다. 복잡한 형태의 데이타형(Data Type)을 작성하기 위해서는 원시적 데이타형으로 이루어진 배열(array)을 남용할 수밖에 없었습니다. 프로그램 여기저기서 배열을 만들었다 풀었다 해야 했고, 한 번 에러가 나면 곧 치명적인 결과로 이어졌습니다. 또한 goto 라는 명령어를 사용해서 프로그램 여기저기를 뛰어다니고 있기도 했는데, goto 명령어의 남발은 스파게티 코드가 더욱 기승을 부리는 데 기여하게 됩니다. 모두들 뭔가 개선책이 필요하다고 느끼기 시작했습니다.

절차적 코드와 데이타 구조체(Procedural Code and Data Structure)

스파게티 코드의 문제점을 해결하기 위해 절차적 코드와 데이타 구조체가 등장합니다. 처음에는 새로운 언어를 만듦으로써 스파게티 코드 문제를 해소할 수 있을 것이라고 생각했었습니다. 결과적으로 이는 실패했습니다. 스파게티 코드는 언어 자체의 문제점에서 비롯되었다기 보다는 90% 이상이 프로그래머의 자질 부족이나 부실한 디자인에서 비롯되었기 때문입니다. 하지만 새로운 언어가 도움이 된 면도 있기는 했습니다. 다소의 도움을 준 새로운 언어들은 자체 내에 데이타 구조체와 절차적 디자인을 갖추고 있었습니다. 절차적 언어들은 1970 년대에서 80 년대에 걸쳐 유행됩니다. 대표적 절차적 언어가 파스칼(Pascal)과 C 언어입니다.

 

프로시져럴 코드가 등장하기 전의 프로그램은 단지 길게 늘여뜨러 놓은 코드였습니다. 프로그래머는 나열된 코드 여기저기를 옮겨다니기는 했지만, 프로그램내에 각 파트를 구분지워주는 어떤 경계도 없었습니다. (예를 들자면 고대의 둘둘 말린 양피지에 기록된 문서를 떠올리면 됩니다.) 프로그래밍 중에 어느 한곳에서 다른 곳으로 이동하려면 긴 코드 전체를 뒤져야만 하는 고통스런 과정을 거쳐야 했습니다. 절차(Procedures)는 코드를 몇 개 단위로 구분지워주는 방법입니다. (오늘날의 책들이 각 챕터와 페이지로 구분되어 있는 것처럼.) 아무 계통 없이 쭉 써내려간 내용 속에서 헤매던 종래의 방식대신 프로그램을 잘 짜여진 챕터로 구분짓고, 챕터내 각 페이지로 보다 용이하게 찾아갈 수 있게 만든 것입니다.

기술적으로 이야기하자면, 절차적 언어가 등장하기 전에도 'procedure'가 있기는 했습니다. 하지만 절차를 표준화하고 보다 예측가능한 형태로 쓰일 수 있게 만들어서 보편화된 것은 절차적 언어 등장 이후입니다.

 

데이타 구조체(Data structure)는 복잡한 데이타 세트를 하나의 그룹 또는 구조로 묶어준 것입니다. 초창기에 했던 방식처럼 원시적인 데이타 형으로 이루어진 수백 개의 배열을 남발하는 대신 여러 개의 데이타형을 하나의 구조(structure)로 통합한 다음 어디서든 손쉽게 참조할 수 있게 했습니다. 비유를 하자면, 초창기 방식은 모든 돈 계산을 일 원 짜리로만 하고 있는 것이고 데이타 구조는 10 원, 100 원, 1000 원, 10000 원권을 사용하거나 정확한 액수를 기입한 수표를 이용하는 방식이라고 할 수 있습니다.

 

포트란 프로그래머는 어떤 언어를 쓰더라도 포트란식으로 프로그래밍 할 수 있다는 농담이 있습니다. 바꿔 말하면, 언어가 바뀌었다는 사실만으로 언어 사용 양태의 변화까지 유도할 수는 없습니다. 자질이 떨어지는 프로그래머는 새로운 언어로도 얼마든지 스파게티 코드를 만들어 낼 수 있었습니다. 예전의 길게 늘어 놓은 코드대신 조잡한 챕터와 난삽한 페이지로 구분된 것에 불과한 코드를 만들어낼 수 있었습니다.

 

반면, 훌륭한 프로그래머는 언어가 옛날 것이든 최신 것이든 항상 깔끔하고 잘 짜여진 코드를 작성해냈습니다. 불행히도 조금 '떨어지는' 프로그래머와 훌륭한 프로그래머 수의 비율은 항상 일정하게 유지되고 있는 것처럼 보입니다. 언어 자체의 발전이 어느정도 도움을 주기는 했지만 기대했던 만큼의 변화는 가져오지 못했습니다.

객체지향형 디자인과 프레임웍스(OOD and Frameworks)

컴퓨터 언어 디자이너들은 다시 한번 프로그래밍 생산성을 높일 수 있는 방법을 찾아 나섭니다. 이번 시도는 기존의 시도보다 훨씬 더 광범위한 의미를 가지는, 심지어 프로그램의 패러다임 전환마저 요구하는 것이었습니다. 종래에는 코드라는 말이 순차적으로 나열된 인스트럭션 모음을 묶어둔 절차를 가르키는 것이었지만, 이를 허물고 프로그램을 새롭게 재정의했습니다. 이런 새로운 프로그래밍 디자인을 OOP(Object Oriented Programming, 객체 지향 프로그래밍)를 이용한 OOD(0bject 0riented Design)라고 합니다. OOP에 사용되는 새로운 언어에는 ObjectPascal과 C++, Java 등이 있습니다.

 

객체 지향은 코드를 절차로 그룹짓는 것에서 한 걸음 더 나아가 절차를 또 다시 객체(object)로 그룹짓는 것입니다. 객체는 특정작업을 수행해주는 코드 뿐만 아니라 그 작업에서 다루는 모든 데이타도 포함합니다. 이러한 그룹짓기 개념을 인캡슐레이션(encapsulation)이라고 합니다. 코드에서 원하는 내용을 찾는 것이 훨씬 더 용이해졌습니다.

 

 

객체 지향 방식 이전에는 코드 여기저기를 자유로이 뛰어 다닐 수 있었습니다. 각 코드가 프로그램내의 모든 데이타를 마음대로 변경시킬 수 있었습니다. 객체지향 방식에서는 모든 코드를 그 코드가 다룰 데이타와 함께 묶어 객체로 구분해서 독립시켰습니다. 각 객체는 오직 자신의 데이타만을 바꿀 수 있고, 다른 객체의 데이타는 변경시킬 수 없습니다. 각 객체 내에서는 자유롭게 이동할 수 있지만 다른 객체로 건너 뛸 수 없습니다. 대신 각 객체는 보다 공식화된 방식으로만 상호작용을 할 수 있게 했습니다. 이런 객체간 커뮤니케이션 방식을 일컬어 메시징(messaging)이라 합니다. 위와 같은 방식은 버그가 발생한 경우에도 국소적으로 제한되고 쉽게 찾아낼 수 있다는 점에서 프로텍션(protection)이라고 불리우기도 합니다. 한 객체의 데이타에 문제가 생긴다면 그 객체 안의 코드만 살펴보면 됩니다. 객체 지향 방식은 획기적인 비용 절감을 가져옵니다.

 

OOD가 등장하기 전에는 다른 사람이 만든 코드를 이용한다는 것이 그리 흔한 일은 아니었습니다. 프로그래머는 아마도 공통적으로 운영체계를 이용하고 몇몇 라이브러리들을 사용하고 있었겠지만 각각의 애플리케이션 코드는 독자적으로 확실히 구분이 되어 있었습니다. 이런 방식은 매우 비효율적인 것으로, 비슷한 기능을 하는 코드가 이미 다른 프로그래머에 의해 완성되어 있음에도 불구하고 또 다시 손수 작성해야 하는 번거로움을 안겨주었기 때문입니다. 비유를 하자면 객체와 OOD는 코드 조립 생산라인에 표준화된 부품(component)을 도입하여, '산업혁명'을 가져온 것과 같습니다. 각 코드를 기능별로 객체화해서 완성해 두었기 때문에 표준화된 부품처럼 다른 애플리케이션도 손쉽게 이용할 수 있었습니다. 보다 많은 코드들이 재사용될 수 있다는 점은 생산성의 급증과 비용의 급감을 의미합니다.

 

게다가 OOD는 한 객체가 다른 객체의 많은 특징을(코드와 데이타 모두) 상속(inherit)할 수 있어서 새 객체 타입에서는 달라지는 부분만 코딩하면 되는 폴리몰피즘(polymorphism)이라는 획기적인 개념까지 갖추고 있었습니다. 버그가 생긴 경우에도 새 객체의 수정된 부분만 살펴보면 됩니다. 코드 재사용율은 더욱 높아집니다.

 

그러면 객체들의 모음은 무엇이라고 할까요? 특히 미리 패키쥐된 객체 모음을 일컫는 말은 무엇일까요? 답은 프레임웍(Framework)입니다. 프레임웍은 일종의 수퍼 라이브러리라고 할 수 있겠지만, 인캡슐레이션과 폴리몰피즘이 가능하기 때문에 훨씬 더 증강된 생산성과 파워를 갖고 있습니다. 운영체계의 부분부분도 프레임웍스로 미리 패키쥐화할 수 있습니다. 미리 작성된 많은 애플리케이션 쉘들은 완전한 애플리케이션에 필요한 기능만 추가할 수 있습니다. 이를 애플리케이션 프레임웍(Application Framework) 이라고 합니다. 그 외에도 프로그래머들의 생산성을 높여줄 많은 종류의 프레임웍스들이 나와있습니다.

 

중요한것은 보다 많은 코드가 누군가 다른 사람에 의해 작성될 수 있게 됨에 따라 더욱 훌륭한 코드들이 많이 만들어지게 되었고, 각 프로그래머는 더욱 적은 양의 코드만으로 더욱 훌륭한 결과를 만들 수 있게 되었다는 점입니다. 시간은 적게 걸리면서도 더욱 많은 일을 해낼 수 있게 된 것입니다. 이것이 바로 OOD가 가져다 준 변화이며, 프로그래머에게 프레임웍스는 하나의 축복이라고까지 일컬어지는 이유입니다. 프로그래머에게 혜택이 크면 클수록 그 결과는 모두 사용자에게 돌아갑니다.

 

((역자주)) 위 그림을 보면 바깥 껍질(회색) 안에 작은 삼각형, 사각형들이 모여있는 것이 보입니다. 작은 삼각형, 사각형이 객체 변수(Instance Variable)고, 바깥에 둘러싸고 있는 것이 객체 메쏘드(Instance Method) 입니다. 객체 변수를 그 객체내에서만 (또는 그 객체를 포함하는 패키지에서만) 접근할 수 있고 다른 객체에서는 접근하지 못하도록 감싸놓은 것을 인캡슐레이션(encapsulation)이라 합니다. 어떤 변수를 어디어디에서 접근 가능한지를 규정하는 부분을 억세스 모디파이어(access modifier)라고 하며 여기에는 private(같은 클래스에서만 접근가능), default(같은 패키지에서는 접근가능), protected(subclass 에서도 접근가능), public(완전공개) 등이 있습니다. 객체변수(instance variable)의 경우는 반드시 private형으로 규정되어 보호되어야 합니다. 즉, 인캡슐레이션되어야 합니다. 인스턴스 변수(instance variable)와 외부 객체의 상호 작용은 객체 메쏘드(instance method)를 통해서만 이뤄집니다.

반응형
댓글