profile image

L o a d i n g . . .

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

 

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

컴파일언어

컴파일?

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

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

 

 

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

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

 

컴파일러?

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

 

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

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

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

디컴파일

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

 

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

 

정적타입

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

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

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

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

 

 

장점

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

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

 

 


인터프리터언어

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


​동적타입

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

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

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

장점

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

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

 

 

 

 

 

 

 


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

절차지향 프로그래밍

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

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

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

장점

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

단점

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

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

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

 

 

 

객체지향 프로그래밍

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

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

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

 

특징

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

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

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

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

 

 

장점

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

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

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

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

 

단점

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

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

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

 


함수형 프로그래밍

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

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

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

 

함수형 프로그래밍?

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

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

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

 

 

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

 

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

함수?

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

 

변수?

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

 

 

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

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

 

함수형 프로그래밍 원칙

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

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

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

 

 

예시

자바스크립트에서 사용하는 대표적인 함수형 프로그래밍 함수는 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

 

 

 

 

반응형
복사했습니다!