프로그래밍 언어는 나눠지는 몇 가지 기준이 있고, 각 언어는 구분된 카테고리중 하나 또는 여러 범주에 속한다.

 

1. 컴파일 언어 vs 인터프리터 언어

1.1. 컴파일언어

1.1.1. 컴파일?

코드를 실행하기 전 기계어나 다른 코드로 먼저 번역하는 것

==> 사람이 이해할 수 있는 언어를 컴퓨터가 이해할 수 있는 언어(기계어)로 바꿔주는것

 

 

Java : 자바 인터프리터, 컴파일러가 생성한 바이트코드를 해석하고 실행한다

Javac : 자바소스코드를 바이트 코드로 컴파일러
javap : 역어셈블러, 컴파일된 클래스 파일을 원래의 소스로 변환한다

 

1.1.2. 컴파일러?

고급언어로 쓰인 프로그램을 즉시 실행될 수 있는 형태의 프로그램으로 바꾸어 주는 번역프로그램

 

compile 주된 기능
- 문법 체크(compile error)
   1. 문법오류 
   2. 실행 중 오류

- 논리 오류 : 작동은 하는데 개발자 의도대로 작동안함

- 물리 오류  :  출력시 메시지를 볼 수 있는가 

1.1.3. 디컴파일

컴퓨터가 이해할 수 있는 언어를 컴퓨터가 이해할 수 있는 언어(기계어)로 바꿔주는것

 

컴파일 - 디컴파일 과정
.java를 컴파일러해서 클래스나오고 인터프리터해서 해석후 보여주고 클래스를 역어셈블러 해서 
Hello.java(원시파일, src) >>> compiler >>> Hello.class(byte code, bin) >>> Interpreter >>> 실행

 

1.1.4. 정적타입

// variable 변수에 문자열 입력
string variable = 'Hello'

// 같은 variable 변수에 숫자 입력
variable = 1234 // 에러

적을때부터 이미 빨간줄이 나와있다. 그래도 무시하고 실행해보면 에러가 발생한다.

자료형이 고정되어있어 프로그램의 모든 자료형이 컴파일중에 결정되어 컴파일러가 자료형을 먼저 검증할 수 있다.

 

 

1.1.5. 장점

- 컴파일 중 내용에 이상있는 부분을 발견할 수 있어 문법상 잘못된 내용을 사전에 방지할 수 있다.

- 빌드과정이 번거롭지만 컴파일 과정 중 오류를 미리 발견해 배포 후 문제를 어느정도 방지할 수 있다.

 

 


1.2. 인터프리터언어

컴파일 과정을 미리 거치지않고 바로 실행한다. 프로그램을 실행하면 인터프리터는 소스코드를 한 줄 한줄 읽으며 기계어로 번역해서 컴퓨터에 명령을 내린다.

1.2.1. ​동적타입

// variable 변수에 문자열 입력
let variable = "hello";
console.log(variable);

// 같은 variable 변수에 숫자 입력
variable = 1234;
console.log(variable);

자료형이 고정되어있지 않고 실행과 동시에 통역을 한다. 프로그램 실행시 런타임에 1번에는 문자열에 맞는 자료형, 2번에는 숫자에 맞는 자료형을 할당해 주기 때문에 런타임 오류가 발생하지 않는다.

1.2.2. 장점

프로그래밍, 배포, 오류수정이 수월하지만 한 문장 한 문장 통역을 거쳐야 하기 때문에 프로그램 실행 속도가 컴파일 언어에 비해 느릴 수 있다.

프로그래밍 과정 중 오류를 놓칠 수 있다. 다만 이 문제는 IDE의 발달로 많이 줄어들었다.

 

 

 

 

 

 

 


2. 절차지향 언어 vs 객체지향 언어

2.1. 절차지향 프로그래밍

- 물이 위에서 아래로 흐르는 것 처럼 소스코드를 위에서부터 차례대로 읽는 방법

- 소스코드를 순차적으로 실행하기 때문에 소스 코드의 순서가 굉장히 중요하며, 프로그램 전체가 유기적으로 연결되어 있다.

- 대표적인 언어로 C 가 있다.

2.1.1. 장점

- 컴퓨터의 처리구조와 비슷해 실행속도가 빠르다.

2.1.2. 단점

- 모든 구성요소가 유기적으로 연결되어 있어 사소한 문제 하나로도 시스템 전체가 돌아가지 않는다.

  ex) 콘 반죽이 완성되지 않으면 콘 아이스크림을 만들 수 없다.

- 실행 순서가 정해져 있어 소스코드의 순서과 바뀌면 결과가 달라질 수 있다.

 

 

 

2.2. 객체지향 프로그래밍

- 코드 작성시 구성 요소를 객체 라는 단위로 묶어 조합으로 프로그램을 만든다.

- 프로그램을 객체로 만들고 객체끼리 서로 상호작용을 할 수 있어 재사용성이 좋다.

- 컴퓨터 조립 시 CPU, RAM, BOARD, SSD, HDD, VGA, POWER, CASE 등 여러 부품을 조합하고 연결해 기능을 만드는 것을 생각하면 된다.

 

2.3. 특징

- 캡슐화(Encapsulation): 데이터와 코드의 형태를 외부로부터 알 수없게 하고, 데이터의 구조와 역할, 기능을 하나의 캡슐형태로 만드는 방법이다.

- 상속(Inheritance): 상위 클래스의 모든걸 하위 클래스가 모두 이어 받는것. 즉, 부모가 자식에게 유전자를 물려주듯이 부모의 특징을 자식에게 모두 물려준다.

- 다형성(Polymorphism): 상속과 연관이 있는 개념으로 한 객체가 다른 여러형태(객체)로 재구성 되는 것을 말한다. 쉽게 말해 한부모의 밑에서 태어난 자식(쌍둥이 포함)이 똑같지 않다는 것을 생각하면 된다. 자바의 오버로드(Overload) 또는 오버라이드(Override)이 다형성의 대표적인 예라 할 수있고, 이 것을 구현하는걸 오버로딩(Overloading)오버라이딩(Overriding) 이라고 한다.

- 추상화(abstraction)
객체의 공통적인 속성과 기능을 추출하여 정의하는것을 말한다. 실제로 존재하는 객체들을 프로그램으로 만들기 위한 공통적인 특성을 파악해 필요없는 특성을 제거하는 과정이다.

 

 

2.3.1. 장점

- 재사용성: 상속을 통해 프로그래밍하여 코드의 재사용을 높일 수 있다. 

- 생산성 향상: 잘 설계된 클래스로 독립적인 객체를 사용해 개발의 생산성을 향상시킬 수 있다. 

- 자연적인 모델링: 일상생활의 구조가 객체에 자연스럽게 녹아들어 있기 때문에 생각하고 있는 것을 그대로 자연스럽게 구현할 수 있다. (설계도, 부품 등)

- 경제적인 유지보수:  하나의 객체가 고장나더라도 해당 객체만 수리하거나 교체할 수 있다. 프로그램 수정 시 추가, 수정을 하더라도 캡슐화를 통해 주변 영향을 적게할 수 있다. 유지보수가 쉬워 경제적이라할 수 있다.

 

2.3.2. 단점

- 모든 객체의 역할과 기능을 이해해야 하기 때문에 설계에 많은 시간이 걸린다.

- 처리속도가 절차지향 보다 느리다.

- 다중상속이 가능한 C++같은 경우 난이도가 매우 높다.

 


3. 함수형 프로그래밍

절차지향 언어와 객체지향 언어가 명령형 프로그래밍의 모습을 띈다면, 함수형 프로그래밍은 선언적 프로그래밍에 가깝다고 생각하면된다.

명령형 프로그래밍: 무엇(What)을 할 것인지 나타내기보다 어떻게(How) 할 건지를 설명하는 방식

선언형 프로그래밍: 어떻게 할건지(How)를 나타내기보다 무엇(What)을 할 건지를 설명하는 방식

 

3.1. 함수형 프로그래밍?

- 함수형 프로그래밍은 객체지향 언어와 상반되는 개념은 아니다. 많은 언어가 객체지향 프로그래밍과 함수형 프로그래밍 기능을 모두 제공하며, 한 프로그램에 두 방식을 모두 적용할 수 있다.

- 변수 사용을 최소화 함으로써 스파게티 코드의 오류를 줄이는 프로그래밍이며 작은 문제를 해결하기위한 함수를 작성하여 가독성을 높이고 유지보수를 용이하게 해준다.

- 외부에 상태값을 두지않고 내부에서 연쇄적으로 기능을 사용해 결과를 연산하므로 멀티스레딩이 많이 이루어지는 환경에서 유용하게 사용할 수 있다.

 

 

유명한 책인 클린 코드(Clean Code)의 저자 Robert C.Martin은 함수형 프로그래밍을 대입문이 없는 프로그래밍이라고 정의하였다.

 

Functional Programming is programming without assignment satements
- Rober C.Martin -

3.1.1. 함수?

특정한 기능을 하는 소스 코드를 따로 빼서 묶어놓은 것이다. 함수를 사용해 코드를 기능별로 구분하여 코드 분석이 편리해지고 같은 코드를 여러번 사용하는 코드 중복을 줄일 수 있다.

 

3.1.2. 변수?

변수란 프로그램에서 개발자가 메인 메모리 공간에 올려놓은 값이다. 값이 대입되면 바뀔 수 없는 상수와 달리 변수는 언제든 값을 변경할 수 있다. 적절히 사용하면 프로그램에 유용하지만 프로그램이 복잡해지고 여러 스레드가 돌아갈 경우 변수는 오류의 원인으로 작용할 가능성이 커진다. 

 

 

3.1.3. 여러스레드가 왜 오류의 원인이 되죠?

스레드는 같은 메모리 공간을 공유하기 때문에 설계가 꼼꼼하지 않을 경우 한 스레드가 변수를 사용하는 동안 다른 스레드가 그 변수를 바꿔버리는 문제가 발생할 수 있기 때문이다.

 

3.2. 함수형 프로그래밍 원칙

- 입출력이 순수해야 한다. (순수함수) => 하나 이상의 인자를 받고, 받은 인자만으로 처리하여 반드시 결과물 리턴을 해야한다.

- 부작용(부산물)이 없어야한다.

- 함수와 데이터를 중점으로 생각한다.

 

 

3.2.1. 예시

자바스크립트에서 사용하는 대표적인 함수형 프로그래밍 함수는 map, filter, reduce가 있다.

 

const arr = [1, 2, 3, 4, 5];
const map = arr.map(function (x) {
  return x * 2;
});

console.log(map);

처음에 배열 arr을 넣어(입력), 결과 map을 얻었다.(출력) 

arr도 사용이 됐으나 값은 변하지 않았고 map이라는 결과에도 아무런 부작용도 낳지않은 이 함수를 순수함수라 부른다.

 

 

 

 

const arr2 = [1, 2, 3, 4, 5];
const condition = function (x) {
  return x % 2 === 0;
};
const ex = function (array) {
  return array.filter(condition);
};

console.log(ex(arr2)); //2,4

그렇다면 위의 ex 함수는 순수함수일까? 얼핏 보면 맞는것 같지만 condition변수에 의해 아니게된다. condition이 인자로 받지않는 변수를 사용했기 때문이다.

 

위 함수를 함수형으로 변환하면

const arr2 = [1, 2, 3, 4, 5];
const condition = function (x) {
  return x % 2 === 0;
};
const ex = function (array, condition) {
  return array.filter(condition);
};

console.log(ex(arr2, condition)); //2,4

condition 변수 또한 인자로 받는다. 이렇게 되면 쉽게 에러를 추적할 수 있다. 인자가 문제였거나, 함수 내부가 문제였거나 둘중 하나로 추측할 수 있기 때문이다.

 

 

이번엔 반복문을 함수형으로 처리해보자

let sum = 0;
for (let index = 0; index <= 10; index++) {
  sum += index;
}
console.log(sum); //55

가장 보통의 1부터 10까지 더하는 반복문 작업이다. 

이를 함수형 프로그래밍으로 바꾸면

 

function add(sum, count) {
  sum += count;
  if (count > 0) {
    return add(sum, count - 1);
  } else {
    return sum;
  }
}

console.log(add(0, 10)); //55

이런 형태를 띄게 된다. 

함수안에 함수를 호출하여 얼핏볼때는 복잡해 보이지만 이렇게 표현하면 코드의 재사용성이 매우 높아진다는 장점이 있다.

 

이 함수를 좀더 함수형 프로그래밍 다운 방법을 사용하려면 reduce를 쓰면된다.

const arr3 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const reduceArr = arr3.reduce((prev, cur) => {
  return prev + cur;
});

console.log(reduceArr);

 

참고:

- 혼자 공부하는 얄팍한 코딩지식(고현민 저) 한빛미디어

- https://www.zerocho.com/category/JavaScript/post/576cafb45eb04d4c1aa35078

 

 

 

 

반응형
복사했습니다!