이미지 출처:educba.com
프로그래밍에서 소프트웨어 개발은 여러 단계로 나뉘며, 그중 가장 중요한 두 가지 개념이 컴파일 타임(Compile Time)과 런타임(Runtime)이다. 이 두 개념은 프로그램의 실행과 오류 처리, 성능 최적화에 깊은 관련이 있다.
고급 언어로 작성된 프로그램을 소스 프로그램 또는 소스 코드라고 한다. 컴퓨터는 소스 프로그램을 실행할 수 없기 때문에, 소스 프로그램을 실행하기 위해서는 기계 코드로 번역되어야 한다. 이 번역은 인터프리터 또는 컴파일러라고 불리는 또 다른 프로그래밍 도구를 사용하여 수행할 수 있다. 컴파일러는 전체 소스 코드를 기계 코드 파일로 번역하며, 그 기계 코드 파일이 실행된다.
📖Daniel Liang, Introduction to JAVA Programming, p.8
우리가 고급 언어로 작성된 소스 코드를 입력할 때, 이는 처음에는 쓸모가 없다. 이 코드를 CPU에서 실행할 수 있는 '전자적 작용'의 연속으로 변환해야 한다. 이 첫 번째 단계가 바로 컴파일이다. 즉, 컴파일 타임 에러는 이 컴파일 단계에서 발생하며, 런타임 에러는 프로그램이 실행되는 이후에 발생한다.
소스 코드가 오류 없이 컴파일되었다고 해서 프로그램이 오류 없이 실행될 것이라는 보장은 없다. 런타임 오류는 프로그램의 라이프사이클에서 '실행' 단계 이후에 발생하는 반면, 컴파일 시간 오류는 프로그램이 실행되기 전, 즉 준비(레디) 단계에서 발생한다.
소스 코드는 배의 청사진과 같다. 이는 배가 어떻게 만들어져야 하는지를 정의한다.
청사진을 조선소에 넘기고, 그들이 배를 만드는 과정에서 결함을 발견하면, 배가 도크를 떠나 물에 떠오르기 전에 즉시 작업을 중지하고 이를 보고할 것이다. 이는 컴파일 타임 오류이다. 배가 실제로 떠오르거나 엔진을 사용하기 전에도 오류가 발견되었기 때문이다.
내 코드가 컴파일이 되면, 이는 배가 완성된 것과 같다. 만들어졌고 이제 항해할 준비가 된 것이다. 이 코드를 실행하면, 이는 배가 항해를 시작하는 것과 같다. 승객들이 탑승하고, 엔진이 가동되며, 선체가 물 위에 떠 있는 상태, 즉 이게 런타임이다. 만약 배에 치명적인 결함이 있어서 첫 항해에서 침몰하게 된다면(또는 추가적인 문제로 인해 나중에 침몰할 수 있다면), 이는 런타임 오류가 발생한 것이다.
소프트웨어 개발 과정은 다음과 같은 주요 단계를 거친다.
이러한 각 단계는 프로그램의 성능, 안정성, 유지 보수성에 중요한 영향을 미친다.
컴파일 타임은 프로그램의 소스 코드가 기계어로 변환되는 시점을 의미한다. 이 과정은 프로그램이 실행되기 전에 발생하며, 컴파일러가 소스 코드를 분석하여 구문 및 의미 오류를 감지하고, 타입 체크를 수행하는 단계이다. 컴파일 타임에 발생하는 오류는 프로그램이 실행되기 전에 해결해야 하며, 이를 통해 실행 중 발생할 수 있는 오류를 최소화할 수 있다.
컴파일러는 프로그래머가 작성한 소스 코드를 컴퓨터가 이해할 수 있는 기계어로 번역하는 도구이다. 컴파일러는 다음과 같은 주요 작업을 수행한다.
string my_value = Console.ReadLine();
int i = my_value;
문자열 값을 정수형 변수에 할당할 수 없으므로, 컴파일러는 이 코드에 문제가 있다는 것을 컴파일 타임에 확실히 알고 있다.
즉, 컴파일 타임은 개발자가 코드를 컴파일하는 시간대이다.
컴파일 타임은 프로그램의 안정성과 성능을 결정하는 중요한 시점이다. 컴파일러가 소스 코드를 분석하여 오류를 감지하고, 이를 통해 프로그램이 안전하게 실행될 수 있도록 보장한다. 컴파일 시간에 모든 오류를 해결하면, 프로그램이 런타임에 예상치 못한 동작을 하거나, 충돌할 가능성이 줄어든다.
컴파일러는 이러한 입력을 처리하여 프로그램의 실행 파일을 생성하고, 컴파일 중 발생한 모든 오류를 보고한다.
컴파일 타임에는 다음과 같은 오류가 발생할 수 있다.
int x = 10 // 세미콜론 누락으로 인한 구문 오류
Console.WriteLine(x);
int y = "hello"; // 타입 불일치로 인한 의미 오류
int z;
z = "text"; // 타입 오류 발생
컴파일 타임에 수행되는 최적화 작업은 프로그램의 실행 속도를 크게 향상시킬 수 있다. 컴파일러는 다음과 같은 최적화 작업을 수행할 수 있다.
int a = 2 + 3; // 컴파일러는 2 + 3을 미리 계산하여 a = 5로 변환
if (false) {
Console.WriteLine("이 코드는 절대 실행되지 않음"); // 제거될 코드
}
for (int i = 0; i < 1000; i++) {
int result = i * 2; // 불필요한 연산을 줄이기 위해 루프 외부로 이동 가능
}
컴파일 타임이 성공적으로 완료되었다는 것은 프로그램이 소스 코드에서 실행 가능한 기계어 코드로 변환되었음을 의미한다. 이 과정에서 컴파일러는 여러 가지 중요한 작업을 수행하며, 그 결과 다음과 같은 사후 조건이 만족되어야 한다.
.exe
파일, Linux에서는 실행 가능한 바이너리 파일이 생성된다..pdb
파일을 포함하여 디버깅 가능성을 높인다.이 모든 사후 조건이 충족되었을 때 비로소 컴파일 타임이 성공적으로 완료되었다고 할 수 있으며, 프로그램은 다음 단계인 실행을 위해 준비된 상태가 된다.
컴파일링은 소스 코드를 기계어 코드로 변환하는 과정이다. 컴파일된 프로그램은 독립 실행 파일로 실행되며, 실행 속도가 빠르다. 컴파일러는 소스 코드를 한 번에 분석하고 번역하여, 이후 실행 시 추가적인 번역이 필요 없다.
인터프리팅은 소스 코드를 실행하는 동시에 번역하는 과정이다. 인터프리터는 소스 코드를 한 줄씩 읽어 실행하며, 코드를 즉시 실행한다. 인터프리팅된 프로그램은 일반적으로 컴파일된 프로그램보다 실행 속도가 느리지만, 코드 수정이 쉽고 디버깅이 용이하다.
컴파일링과 인터프리팅의 주요 차이점은 다음과 같다.
Java와 같은 언어는 컴파일과 인터프리팅의 하이브리드 접근을 사용한다. Java 프로그램은 먼저 바이트코드로 컴파일된 후, 이 바이트코드는 자바 가상 머신(JVM)에서 인터프리팅되거나 즉시 실행된다. 이를 통해 컴파일된 코드의 성능과 인터프리팅 코드의 유연성을 모두 제공할 수 있다.
런타임은 프로그램이 실제로 실행되는 단계로, 프로그램의 논리적 흐름이 실행 환경에서 수행된다. 런타임 동안 프로그램은 사용자가 입력한 데이터를 처리하고, 결과를 출력하며, 다양한 시스템 자원(파일, 메모리, 네트워크 등)을 활용하여 작업을 수행한다.
❗️ 입력과 출력은 전적으로 프로그래머에게 달려있다. 파일들, 화면에 뜨는 창들, 네트워크 패킷들, 프린터로 보내는 작업들, 모든 것이 포함될 수 있다. 프로그램이 미사일을 발사하는 경우, 그것은 출력이며, 오직 런타임에서만 발생한다.
런타임은 프로그램이 실제로 동작하는 단계이기 때문에, 프로그램의 성능과 안정성에 직접적인 영향을 미친다. 이 단계에서 발생하는 오류는 사용자 경험에 큰 영향을 미치며, 프로그램이 정상적으로 작동하지 않을 경우 심각한 문제로 이어질 수 있다.
string my_value = Console.ReadLine();
int i = int.Parse(my_value);
ReadLine()
이 반환하는 문자열에 따라 결과가 달라진다. 어떤 값은 정수로 변환될 수 있다. 이는 오직 런타임에서만 확인될 수 있다.
즉, 런타임은 사용자가 소프트웨어를 실행하는 시간대이다.
런타임 오류는 프로그램이 실행되는 동안 발생하는 오류로, 프로그램이 예상한 대로 동작하지 않거나, 비정상적으로 종료되는 원인이 된다. 런타임 오류는 컴파일러에 의해 사전에 감지되지 않기 때문에, 프로그램이 실제로 실행되는 환경에서만 발생한다. 이러한 오류를 이해하고 적절히 처리하는 것은 프로그램의 안정성과 사용자 경험에 중요한 영향을 미친다.
int a = 10;
int b = 0;
int result = a / b; // 0으로 나누는 오류 발생
ArithmeticException
과 같은 특정 예외로 처리된다.null
로 설정된 포인터 또는 참조를 역참조하려고 할 때 발생하는 오류이다. 이는 존재하지 않는 메모리 주소를 참조하려는 시도로 인해 프로그램이 중단될 수 있다.string str = null;
Console.WriteLine(str.Length); // 널 포인터 오류 발생
NullReferenceException
과 같은 예외를 발생시키며, 대부분의 경우 프로그램이 비정상적으로 종료된다.int[] largeArray = new int[int.MaxValue]; // 메모리 부족 오류 발생
OutOfMemoryException
을 발생시키고, 메모리 부족으로 인해 작업을 계속할 수 없게 된다.int[] array = new int[5];
int value = array[10]; // 배열 범위를 벗어난 접근 시도
IndexOutOfRangeException
이 발생하며, 프로그램은 비정상적으로 종료될 수 있다.string str = "abc";
int number = int.Parse(str); // 형식 변환 오류 발생
FormatException
이 발생하며, 변환이 실패한다.string path = "non_existent_file.txt";
var content = File.ReadAllText(path); // 파일 접근 실패로 인한 오류 발생
FileNotFoundException
이나 IOException
이 발생하며, 자원 접근이 실패한다.void RecursiveFunction() {
RecursiveFunction(); // 무한 재귀 호출로 인해 스택 오버플로우 발생
}
StackOverflowException
이 발생하며, 프로그램이 중단된다.lock (resource1) {
lock (resource2) {
// 데드락 발생 가능성
}
}
런타임 오류를 처리하는 주요 방법은 예외 처리(Exception Handling)이다. 예외 처리는 프로그램이 런타임 오류를 감지하고, 이를 적절히 처리하여 비정상 종료를 방지할 수 있도록 돕는다.
try {
int a = 10;
int b = 0;
int result = a / b; // 예외 발생 가능성
} catch (DivideByZeroException e) {
Console.WriteLine("오류: 0으로 나눌 수 없습니다.");
}
finally
블록은 예외 발생 여부와 관계없이 항상 실행되는 코드를 포함한다. 주로 자원 해제나 정리 작업을 수행하는 데 사용된다.try {
// 파일 열기 및 처리
} catch (Exception e) {
Console.WriteLine("오류 발생: " + e.Message);
} finally {
// 파일 닫기 등 정리 작업
}
global exception handler
에서 처리할 수 있다.public class InvalidTransactionException : Exception {
public InvalidTransactionException(string message) : base(message) { }
}
void ProcessTransaction(double amount) {
if (amount < 0) {
throw new InvalidTransactionException("거래 금액은 음수일 수 없습니다.");
}
}
런타임에서의 성능 최적화는 프로그램이 사용자에게 더 나은 경험을 제공할 수 있도록 돕는다. 런타임 최적화는 주로 다음과 같은 방식으로 이루어진다.
런타임이 성공적으로 완료되었다는 것은 프로그램이 정상적으로 실행되어 모든 작업을 완료하고, 예상한 대로 종료되었음을 의미한다. 런타임 동안 발생할 수 있는 다양한 상황을 고려하여, 프로그램이 종료될 때 다음과 같은 사후 조건이 충족되어야 한다.
0
으로 반환하며, 이는 모든 작업이 성공적으로 완료되었음을 나타낸다.이러한 사후 조건들이 충족되었다면, 런타임이 성공적으로 완료되었다고 할 수 있다. 이는 프로그램이 예상대로 동작했으며, 사용자가 프로그램을 사용할 때 예상치 못한 오류나 문제를 경험하지 않았음을 나타낸다.
자바스크립트는 대표적인 인터프리팅 언어로, 코드가 실행되는 런타임 환경에서 즉시 해석되고 실행된다. 이는 웹 개발에서 자바스크립트가 널리 사용되는 이유 중 하나로, 코드 수정 후 즉시 결과를 확인할 수 있는 장점이 있다.
자바스크립트는 런타임에서 다양한 작업을 수행할 수 있는 유연성을 제공한다. 자바스크립트에서는 변수의 타입이 런타임에 결정되며, 이를 통해 동적인 프로그램 동작이 가능하다. 그러나 이러한 유연성은 런타임 오류가 발생할 가능성을 높이며, 개발자는 철저한 테스트와 디버깅을 통해 이를 관리해야 한다.
전통적으로 자바스크립트는 컴파일 타임 개념이 약했지만, 최근에는 Babel과 같은 트랜스파일러(Transpiler)를 통해 자바스크립트 코드를 다른 버전의 자바스크립트나 타 언어로 변환하는 방식이 도입되었다. 이를 통해 자바스크립트에서도 컴파일 타임에 코드를 미리 검사하고 최적화할 수 있는 기회를 제공하게 되었다.