프로그래밍 언어 활용

라이브러리 활용

출제기준 세세항목: 라이브러리 · 데이터 입출력 · 예외 처리 · 프로토타입

핵심 요약

라이브러리란 무엇일까요

라이브러리는 자주 쓰는 기능을 미리 만들어 묶어 둔 코드 모음입니다. 도서관에서 필요한 책을 빌려 읽듯, 개발자는 정렬이나 수학 계산, 파일 읽기 같은 기능을 직접 만들지 않고 라이브러리에서 가져다 씁니다. 이미 많은 사람이 써 보며 검증한 코드라 직접 만드는 것보다 빠르고 안전합니다.

라이브러리는 두 가지로 나눕니다. 표준 라이브러리는 언어를 설치하면 함께 들어 있어 따로 내려받지 않아도 되는 것입니다. C의 표준 입출력 함수, Java의 java.util, Python의 math와 random이 그 예입니다. 외부 라이브러리는 다른 개발자나 회사가 만들어 공개한 것으로, 따로 내려받아 프로젝트에 추가해야 합니다. Python의 NumPy나 Java의 JSON 처리 라이브러리가 그렇습니다.

비슷한 말도 정리해 둡니다. 모듈은 관련된 기능을 담은 파일 하나 정도의 단위이고, 패키지는 여러 모듈을 폴더처럼 묶은 것이며, 라이브러리는 이런 모듈과 패키지를 쓰기 좋게 모은 전체를 가리킵니다. 앞에서 봤듯이 라이브러리는 내 코드가 필요할 때 부르는 것이고, 프레임워크는 정해진 틀 안에서 내 코드를 불러 주는 것이라는 차이가 있습니다.

외부 라이브러리는 버전에 따라 동작이 달라질 수 있어 의존성 관리가 중요합니다. Java는 Maven(pom.xml)이나 Gradle(build.gradle), Python은 pip와 requirements.txt, JavaScript는 npm과 package.json에 필요한 라이브러리와 버전을 적어 두면, 다른 컴퓨터에서도 같은 버전을 한 번에 설치할 수 있습니다. 쓰기 전에 라이선스 조건과 알려진 보안 취약점도 확인해야 합니다.

C 표준 라이브러리: 헤더별로 기억하기

C에서는 #include <stdio.h>처럼 헤더 파일을 포함해야 그 안의 함수를 쓸 수 있습니다. stdio.h는 표준 입출력으로 printf, scanf, getchar, putchar, 그리고 파일을 다루는 fopen, fclose, fprintf, fgets 등이 있습니다. stdlib.h는 범용 유틸리티로 메모리를 할당하고 풀어 주는 malloc, calloc, free, 문자열을 숫자로 바꾸는 atoi, atof, 난수를 만드는 rand, srand, 정수 절댓값 abs, 프로그램을 끝내는 exit 등이 있습니다.

string.h는 문자열 함수입니다. strlen(s)는 널 문자를 빼고 센 길이, strcpy(a, b)는 b를 a에 복사, strcat(a, b)는 a 뒤에 b를 이어 붙이기, strcmp(a, b)는 두 문자열을 사전 순으로 비교합니다. strcmp는 같으면 0, a가 앞서면 음수, 뒤면 양수를 돌려줍니다. 같을 때 1이 아니라 0이라는 점이 자주 나오는 함정입니다. strcpy와 strcat은 받을 배열의 크기를 확인하지 않아 넘치면 메모리를 덮어쓰므로, 길이를 지정하는 strncpy, strncat이 더 안전합니다.

math.h는 수학 함수로 sqrt(제곱근), pow(거듭제곱), fabs(실수 절댓값), ceil(올림), floor(내림) 등이 있으며 대부분 double을 돌려줍니다. ctype.h는 문자 하나를 다루는 함수로 toupper, tolower(대소문자 바꾸기), isdigit(숫자인지), isalpha(영문자인지), isupper(대문자인지) 등이 있습니다. malloc은 할당에 실패하면 NULL을 돌려주므로 결과를 확인한 뒤 쓰고, 다 쓴 메모리는 free로 풀어 주어야 메모리 누수가 생기지 않습니다.

Java 표준 라이브러리: String과 컬렉션

Java는 import java.util.ArrayList;처럼 패키지 이름과 클래스 이름으로 가져오고, import java.util.*;로 패키지 전체를 가져올 수도 있습니다. String, Math, System, Integer 같은 기본 클래스가 든 java.lang 패키지는 import 없이 자동으로 쓸 수 있습니다. 자주 쓰는 패키지는 컬렉션과 Scanner가 든 java.util, 입출력용 java.io입니다.

String의 메서드는 코드 추적 문제에 단골로 나옵니다. length()는 길이, charAt(i)는 i번째 문자, substring(시작, 끝)은 시작부터 끝 바로 앞까지(끝 인덱스는 포함하지 않음), indexOf(x)는 처음 나오는 위치(없으면 -1), equals()는 내용 비교, toUpperCase()는 대문자로 바꾼 새 문자열, replace(a, b)는 a를 b로 바꾼 새 문자열, split(구분자)은 잘라 낸 배열을 돌려줍니다. String은 한 번 만들면 바뀌지 않는(불변) 객체라서 s.toUpperCase();만 쓰고 결과를 다시 담지 않으면 s는 그대로입니다. 내용을 자주 바꿀 때는 append, insert, reverse를 가진 StringBuilder를 씁니다.

컬렉션에서는 ArrayList와 HashMap이 가장 많이 쓰입니다. ArrayList는 크기가 늘어나는 배열로 add(값), add(위치, 값), get(i), set(i, 값), remove, size()를 씁니다. remove(2)처럼 int를 넘기면 인덱스 2의 원소를 지우고, remove(Integer.valueOf(2))처럼 객체를 넘기면 값이 2인 원소를 지운다는 차이가 중요합니다. HashMap은 키와 값의 쌍을 담으며 put(키, 값), get(키)(없는 키면 null), getOrDefault(키, 기본값), containsKey(키), keySet()을 씁니다. HashSet은 중복을 허용하지 않는 집합입니다.

Math 클래스도 알아 둡니다. Math.max, Math.min, Math.abs, Math.pow(결과는 double), Math.sqrt가 있고, Math.random()은 0.0 이상 1.0 미만의 실수를 돌려줍니다. Math.round는 0.5를 더한 뒤 내림하는 방식이라 Math.round(2.5)는 3, Math.round(-2.5)는 -2입니다. 문자열을 정수로 바꿀 때는 Integer.parseInt("12"), 정수를 문자열로 바꿀 때는 String.valueOf(12)를 씁니다. 배열은 Arrays.sort로 정렬하고 Arrays.toString으로 내용을 확인합니다.

Python 모듈과 내장 함수

Python은 import math로 모듈 전체를 가져와 math.sqrt(16)처럼 쓰거나, from math import sqrt로 필요한 것만 가져와 sqrt(16)처럼 바로 쓰고, import numpy as np처럼 별명을 붙일 수도 있습니다. 자주 쓰는 표준 모듈로 math(수학), random(난수), datetime(날짜와 시간), os(운영체제 기능), sys(인터프리터 정보), collections(Counter 같은 특수 자료형)가 있습니다. math.sqrt(16)은 정수 4가 아니라 실수 4.0을 돌려줍니다.

import 없이 쓰는 내장 함수도 많습니다. len, range, print, input, int, str, sum, max, min, abs, sorted, enumerate, zip, map, filter 등입니다. sorted(a)는 정렬된 새 리스트를 돌려주고 a는 그대로 두지만, a.sort()는 a 자체를 정렬하고 None을 돌려줍니다. round는 정확히 절반일 때 짝수 쪽으로 반올림해 round(2.5)는 2, round(3.5)는 4입니다. zip은 여러 묶음을 짝지어 주며 가장 짧은 쪽에 맞춰 끝납니다.

문자열 메서드로는 split, join, replace, find, index, upper, lower, strip, count가 있습니다. find는 찾는 글자가 없으면 -1을, index는 ValueError를 냅니다. split()처럼 인자 없이 부르면 공백 여러 개를 하나로 보고 빈 조각을 버리지만, split(',')처럼 구분자를 주면 쉼표 사이의 빈 문자열도 원소로 남깁니다. replace(a, b, 1)의 세 번째 인자는 바꿀 최대 횟수입니다. ','.join(['a', 'b'])는 'a,b'처럼 리스트를 구분자로 이어 붙이며, 원소가 모두 문자열이어야 합니다.

리스트 메서드 append(x)는 x를 원소 하나로 끝에 붙이고, extend(x)는 x 안의 원소들을 하나씩 붙입니다. a.append([1, 2])는 길이가 1 늘고, a.extend([1, 2])는 2 늘어납니다. insert(i, x)는 i 위치에 넣기, pop()은 마지막 원소를 꺼내 돌려주기, pop(i)는 i 위치를 꺼내기, remove(x)는 값이 x인 첫 원소 지우기입니다. 딕셔너리의 get(키, 기본값)은 키가 없을 때 오류 대신 기본값(생략하면 None)을 돌려주고, keys(), values(), items()로 키·값·쌍을 꺼냅니다.

데이터 입출력: 화면과 파일

C의 printf는 서식 문자열 안의 서식 지정자 자리에 값을 채워 출력합니다. %d는 10진 정수, %f는 실수(기본으로 소수점 아래 6자리, 그래서 printf("%f", 3.5)는 3.500000), %c는 문자, %s는 문자열, %x는 16진수, %o는 8진수, %%는 % 기호 자체입니다. %5d는 다섯 칸에 오른쪽 정렬, %-5d는 왼쪽 정렬, %05d는 빈칸을 0으로 채우기, %.2f는 소수점 아래 둘째 자리까지(반올림)입니다. 줄을 바꾸려면 \n을 직접 넣어야 합니다.

scanf는 값을 넣을 변수의 주소를 받으므로 scanf("%d", &n)처럼 &를 붙입니다. 다만 문자 배열 이름은 이미 주소이므로 scanf("%s", name)처럼 &를 붙이지 않습니다. Java는 System.out.print(줄 바꿈 없음), println(줄 바꿈 있음), printf(서식 지정)로 출력하고, 입력은 Scanner의 nextInt(), nextLine() 등으로 받습니다. nextInt() 뒤에 nextLine()을 부르면 남아 있던 줄 바꿈 문자만 읽고 끝나는 함정이 있습니다.

Python의 print는 여러 값을 쉼표로 넘기면 사이에 공백 하나를 넣고, 끝에 줄 바꿈을 붙입니다. sep로 사이에 넣을 문자를, end로 끝에 붙일 문자를 바꿀 수 있어 print(1, 2, sep='-', end='!')는 1-2!를 출력하고 줄을 바꾸지 않습니다. input()은 입력을 항상 문자열로 돌려주므로 숫자로 쓰려면 int(input())처럼 바꿔야 합니다. 서식은 f-문자열로 f'{x:.2f}'처럼 지정합니다.

파일 입출력은 파일 열기 → 읽기·쓰기 → 닫기 순서입니다. C는 fopen(파일 이름, 모드)으로 열고 fclose로 닫으며, 모드는 "r"(읽기), "w"(쓰기, 기존 내용 지움), "a"(이어 쓰기)입니다. 파일을 열지 못하면 NULL을 돌려줍니다. Python은 with open('data.txt', 'r', encoding='utf-8') as f:처럼 with 문을 쓰면 블록이 끝날 때 파일이 자동으로 닫혀 편리합니다.

예외 처리: 문제가 생겨도 멈추지 않게

예외는 프로그램 실행 중에 생기는 예상 밖의 상황입니다. 0으로 나누기, 없는 파일 열기, 배열 범위 밖 접근 같은 것들이죠. 예외를 처리하지 않으면 프로그램이 그 자리에서 멈춥니다. 예외 처리는 이런 상황을 미리 예상해 두고, 생기면 대신 할 일을 정해 두는 것입니다.

Java는 try 블록에 예외가 날 수 있는 코드를 넣고, catch 블록에서 예외 종류별로 처리하며, finally 블록에는 예외가 나든 안 나든 꼭 해야 할 정리 작업(파일 닫기, 연결 끊기)을 넣습니다. try 안에서 예외가 나면 그 아래 줄은 실행하지 않고 바로 맞는 catch로 뜁니다. finally는 try나 catch 안에서 return을 해도 실행되며, finally 안에서 다시 return하면 앞의 반환값을 덮어쓰므로 피합니다. catch를 여러 개 쓸 때는 자식 예외를 부모 예외보다 먼저 적어야 합니다.

Java 예외는 처리를 강제하느냐로 나뉩니다. IOException처럼 컴파일러가 처리를 강제하는 체크 예외는 try-catch로 잡거나 메서드에 throws로 '이 예외를 던질 수 있다'고 선언해야 합니다. RuntimeException을 상속한 언체크 예외는 강제하지 않으며, NullPointerException(null 객체 사용), ArithmeticException(정수를 0으로 나누기), ArrayIndexOutOfBoundsException(배열 범위 초과), NumberFormatException(숫자로 못 바꾸는 문자열), ClassCastException(잘못된 형 변환)이 대표적입니다. 예외를 직접 일으킬 때는 throw를 씁니다. throw는 '던지기', throws는 '던질 수 있다는 선언'입니다.

Python은 try, except, else, finally를 씁니다. else는 예외가 나지 않았을 때만 실행되고, finally는 항상 실행됩니다. 예외를 직접 일으킬 때는 raise를 씁니다. 자주 보는 예외는 ZeroDivisionError(0으로 나누기), IndexError(인덱스 범위 초과), KeyError(딕셔너리에 없는 키), ValueError(int('abc')처럼 값이 맞지 않음), TypeError('1' + 1처럼 자료형이 맞지 않음), FileNotFoundError입니다. C에는 이런 예외 처리 문법이 없어서 함수의 반환값이나 errno 같은 오류 번호를 확인하는 방식으로 처리합니다.

프로토타입: 함수의 얼굴을 먼저 알려 주기

C 컴파일러는 코드를 위에서 아래로 읽습니다. 그래서 main 함수 안에서 아래쪽에 정의된 함수를 부르면, 컴파일러는 그 함수가 무엇을 받고 무엇을 돌려주는지 아직 모릅니다. 이때 함수의 반환형, 이름, 매개변수의 자료형만 미리 적어 알려 주는 선언을 함수 프로토타입(함수 원형)이라고 합니다. int add(int a, int b);처럼 몸체 없이 세미콜론으로 끝나며, 매개변수 이름은 생략해 int add(int, int);라고 써도 됩니다.

프로토타입이 있으면 컴파일러가 함수를 부르는 곳마다 인자의 개수와 자료형이 맞는지 검사할 수 있습니다. 프로토타입 없이 정의보다 먼저 함수를 부르면 경고나 오류가 나거나, 자료형이 맞지 않아 엉뚱한 값이 전달될 수 있습니다.

라이브러리와 프로토타입은 짝을 이룹니다. 헤더 파일(.h)에는 함수의 프로토타입이 들어 있고, 실제 구현은 미리 컴파일된 라이브러리 파일에 들어 있습니다. #include <string.h>를 쓰면 strlen의 프로토타입을 가져와 컴파일러가 올바른 사용인지 확인하고, 실제 strlen의 코드는 링커가 실행 파일을 만들 때 라이브러리에서 연결해 줍니다. 그래서 프로토타입은 '사용 설명서', 라이브러리 파일은 '실제 기계'라고 비유할 수 있습니다.

실무에서는 이렇게 쓰여요

strcmp 결과를 잘못 읽은 비밀번호 확인

C로 만든 장비 관리 프로그램이 if (strcmp(input, saved))로 비밀번호를 확인해, 틀린 비밀번호일 때 오히려 통과시키고 있었습니다. strcmp는 같을 때 0을 돌려주기 때문이며, if (strcmp(input, saved) == 0)으로 고쳤습니다.

파일이 없어도 멈추지 않는 정산

매일 받는 지점별 매출 파일 중 하나가 빠지면 정산 프로그램 전체가 멈췄습니다. 파일 열기를 try-except로 감싸 FileNotFoundError가 나면 해당 지점만 '누락'으로 기록하고 나머지 지점은 계속 처리하도록 바꿨습니다.

라이브러리 버전 고정

개발 PC에서는 잘 되던 Python 데이터 분석 코드가 서버에서는 오류를 냈습니다. 서버에 더 새 버전의 라이브러리가 깔려 함수 이름이 바뀌었기 때문이었고, requirements.txt에 버전을 정확히 적어 두고 pip install -r requirements.txt로 같은 버전을 설치하게 했습니다.

총정리 목차로