더 나아가기 · 22
흔한 실수와 오류 메시지
오류 메시지 읽는 법과, 어느 언어에서나 나오는 실수들: 한 칸 차이, = 과 ==, 정수 나눗셈, null, 범위 밖 번호.
어느 언어에서나 나오는 실수가 있어요.
오류 메시지를 읽는 법과 함께 알아 둬요.
아래 예제는 번호가 범위 안인지, 나누는 수가 0인지 먼저 확인한 뒤에 써요.
def get(a, i): if 0 <= i < len(a): # a[-1]도 막아요(Python에서는 마지막 값) return a[i] return None def safe_div(x, y): if y == 0: return None return x // y n = int(input())a = list(map(int, input().split()))input()for i in map(int, input().split()): v = get(a, i) if v is None: # if not v: 로 쓰면 0도 걸려요 print(f"a[{i}]: out of range") else: print(f"a[{i}] = {v}")x, y = map(int, input().split())q = safe_div(x, y)print(f"{x} / {y}: cannot divide" if q is None else f"{x} / {y} = {q}")print(f"average {sum(a) / n:.1f}")const tok = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);let p = 0;const n = tok[p++];const a = tok.slice(p, (p += n));const q = tok[p++];const idx = tok.slice(p, (p += q));const [x, y] = tok.slice(p, p + 2); // a[7]은 오류 없이 undefined라서 직접 검사해요const get = (arr, i) => (i >= 0 && i < arr.length ? arr[i] : null);const safeDiv = (u, v) => (v === 0 ? null : Math.trunc(u / v)); // u / 0은 Infinity for (const i of idx) { const v = get(a, i); console.log(v === null ? `a[${i}]: out of range` : `a[${i}] = ${v}`);}const d = safeDiv(x, y);console.log(d === null ? `${x} / ${y}: cannot divide` : `${x} / ${y} = ${d}`);let sum = 0;for (let i = 0; i < n; i++) sum += a[i]; // <= n 이면 undefined를 더해 NaNconsole.log(`average ${(sum / n).toFixed(1)}`);import { readFileSync } from "node:fs";const tok: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);let p = 0;const n = tok[p++];const a = tok.slice(p, (p += n));const q = tok[p++];const idx = tok.slice(p, (p += q));const [x, y] = tok.slice(p, p + 2); // a[7]은 오류 없이 undefined라서 직접 검사해요const get = (arr: number[], i: number): number | null => (i >= 0 && i < arr.length ? arr[i] : null);const safeDiv = (u: number, v: number): number | null => (v === 0 ? null : Math.trunc(u / v)); for (const i of idx) { const v = get(a, i); console.log(v === null ? `a[${i}]: out of range` : `a[${i}] = ${v}`);}const d = safeDiv(x, y);console.log(d === null ? `${x} / ${y}: cannot divide` : `${x} / ${y} = ${d}`);let sum = 0;for (let i = 0; i < n; i++) sum += a[i]; // <= n 이면 undefined를 더해 NaNconsole.log(`average ${(sum / n).toFixed(1)}`);import java.util.Scanner; public class Main { // int에는 null을 못 담아서 Integer로 돌려줘요 static Integer get(int[] a, int i) { if (i >= 0 && i < a.length) return a[i]; // 안 막으면 ArrayIndexOutOfBoundsException return null; } static Integer safeDiv(int x, int y) { if (y == 0) return null; // 안 막으면 ArithmeticException return x / y; } public static void main(String[] args) { Scanner in = new Scanner(System.in); int n = in.nextInt(); int[] a = new int[n]; int sum = 0; for (int i = 0; i < n; i++) { // i <= n 이면 한 칸 넘어가요 a[i] = in.nextInt(); sum += a[i]; } int q = in.nextInt(); for (int t = 0; t < q; t++) { int i = in.nextInt(); Integer v = get(a, i); System.out.println(v == null ? "a[" + i + "]: out of range" : "a[" + i + "] = " + v); } int x = in.nextInt(), y = in.nextInt(); Integer d = safeDiv(x, y); System.out.println(x + " / " + y + (d == null ? ": cannot divide" : " = " + d)); System.out.println(String.format("average %.1f", (double) sum / n)); // sum / n은 2 }}using System;using System.Linq; class Program{ // int?는 '수 또는 null'이에요 static int? Get(int[] a, int i) => i >= 0 && i < a.Length ? a[i] : null; // 안 막으면 IndexOutOfRangeException static int? SafeDiv(int x, int y) => y == 0 ? null : x / y; // 안 막으면 DivideByZeroException static void Main() { int n = int.Parse(Console.ReadLine()); int[] a = Console.ReadLine().Split(' ').Select(int.Parse).ToArray(); Console.ReadLine(); foreach (int i in Console.ReadLine().Split(' ').Select(int.Parse)) { int? v = Get(a, i); Console.WriteLine(v is null ? $"a[{i}]: out of range" : $"a[{i}] = {v}"); } int[] xy = Console.ReadLine().Split(' ').Select(int.Parse).ToArray(); int? d = SafeDiv(xy[0], xy[1]); Console.WriteLine($"{xy[0]} / {xy[1]}" + (d is null ? ": cannot divide" : $" = {d}")); Console.WriteLine($"average {((double)a.Sum() / n).ToString("F1")}"); // a.Sum() / n은 2 }}#include <cstdio>#include <iostream>#include <optional>#include <vector> // optional<int>는 '수 또는 없음'이에요std::optional<int> get(const std::vector<int>& a, int i) { if (i < 0 || i >= (int)a.size()) return std::nullopt; // a[i]는 검사 안 함, a.at(i)는 예외 return a[i];} std::optional<int> safeDiv(int x, int y) { if (y == 0) return std::nullopt; // 0으로 나누면 정의되지 않은 동작 return x / y;} int main() { int n, q, x, y; std::cin >> n; std::vector<int> a(n); long long sum = 0; // = 0 을 빼면 쓰레기 값에서 시작해요 for (int& v : a) std::cin >> v, sum += v; std::cin >> q; for (int t = 0; t < q; t++) { int i; std::cin >> i; if (auto v = get(a, i)) std::cout << "a[" << i << "] = " << *v << '\n'; else std::cout << "a[" << i << "]: out of range\n"; } std::cin >> x >> y; if (auto d = safeDiv(x, y)) std::cout << x << " / " << y << " = " << *d << '\n'; else std::cout << x << " / " << y << ": cannot divide\n"; std::printf("average %.1f\n", (double)sum / n); // sum / n은 2}#include <stdbool.h>#include <stdio.h> /* C에는 null 정수가 없어서 성공 여부(bool)와 값(*out)을 따로 돌려줘요 */bool get(const int *a, int n, int i, int *out) { if (i < 0 || i >= n) return false; /* 안 막으면 정의되지 않은 동작 */ *out = a[i]; return true;} int main(void) { int n, q, x, y, v; int a[100]; int sum = 0; /* = 0 을 빼면 쓰레기 값에서 시작해요 */ scanf("%d", &n); if (n < 1 || n > 100) return 1; /* 배열 크기를 넘지 않게 */ for (int i = 0; i < n; i++) { /* i <= n 이면 한 칸 넘어가요 */ scanf("%d", &a[i]); sum += a[i]; } scanf("%d", &q); for (int t = 0; t < q; t++) { int i; scanf("%d", &i); if (get(a, n, i, &v)) printf("a[%d] = %d\n", i, v); else printf("a[%d]: out of range\n", i); } scanf("%d %d", &x, &y); if (y == 0) printf("%d / %d: cannot divide\n", x, y); /* 0으로 나누면 정의되지 않은 동작 */ else printf("%d / %d = %d\n", x, y, x / y); printf("average %.1f\n", (double)sum / n); /* sum / n은 2 */ return 0;}get(a, i){ 만약 (i >= 0 && i < a.개수) { 돌려주기 a[i]; } 돌려주기 빈값;} n = 콘솔.줄읽기().수로변환();a = 콘솔.수들읽기();q = 콘솔.줄읽기().수로변환();각각 (i 에서 콘솔.수들읽기()){ v = get(a, i); 만약 (v == 빈값) { 콘솔.쓰기("a[" + i + "]: out of range"); } 아니면 { 콘솔.쓰기("a[" + i + "] = " + v); }}xy = 콘솔.수들읽기();만약 (xy[1] == 0){ 콘솔.쓰기(xy[0] + " / " + xy[1] + ": cannot divide");}아니면{ 콘솔.쓰기(xy[0] + " / " + xy[1] + " = " + 수학.몫(xy[0], xy[1]));}합 = 0;반복 (k = 0; k < a.개수; k++){ 합 += a[k];}콘솔.쓰기("average " + 글자.소수자리맞추기(합 / n, 1));5 3 1 4 1 5 3 2 7 -1 7 0
a[2] = 4 a[7]: out of range a[-1]: out of range 7 / 0: cannot divide average 2.8
핵심
- 오류 메시지에서 오류 이름, 이유, 줄 번호를 찾아요.
Python은 맨 아래 줄부터 읽어요. - 컴파일하는 언어(C·C++·Java·C#)는 오류가 여러 개여도 첫 번째 오류부터 고쳐요.
뒤의 것은 첫 오류 때문에 따라 나온 경우가 많아요. - 번호는 0부터
n - 1까지예요.
반복은i < n이어야 해요.i <= n은 한 칸을 넘어가는 한 칸 차이(off-by-one)예요. =는 넣기,==는 비교예요.- 나누기 전에 0이 아닌지, 쓰기 전에 빈 값(null)이 아닌지 먼저 물어봐요.
- 변수는 만들 때 처음 값을 줘요(
int sum = 0;).
언어별로 쓰는 법
| Python | 마지막 줄에 IndexError: list index out of range처럼 이름과 이유, 그 위에 줄 번호.0으로 나누면 ZeroDivisionError, None으로 계산하면 TypeError, 없는 이름은 NameError.a[-1]은 오류가 아니라 마지막 값이고, if not v:는 0도 걸러요(v is None으로 물어요) |
|---|---|
| JavaScript | 범위 밖 번호는 오류 없이 undefined, 7 / 0은 Infinity.빈 값은 null과 undefined인데 undefined + 1은 NaN, null + 1은 1로 조용히 틀려요.비교는 ===("1" == 1이 참이에요) |
| TypeScript | JavaScript와 같아요 |
| Java | 첫 줄 ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 5, 아래 at Main.main(Main.java:12)가 줄 번호.0으로 나누면 ArithmeticException, null은 NullPointerException(int에는 못 담아서 Integer).처음 값이 없으면 "might not have been initialized" 컴파일 오류 |
| C# | 범위 밖은 IndexOutOfRangeException, 0으로 나누면 DivideByZeroException.빈 값은 int?.처음 값이 없으면 컴파일 오류 CS0165 |
| C++ | a[i]는 범위를 검사하지 않아 정의되지 않은 동작, a.at(i)는 std::out_of_range 예외.정수를 0으로 나누기도 정의되지 않은 동작(흔히 죽어요). 빈 값은 std::optional.int sum;은 쓰레기 값으로 시작해요 |
| C | 배열 범위 검사가 없어 정의되지 않은 동작: 엉뚱한 값, 다른 변수 덮기, 죽기. 0으로 나누기와 처음 값 없는 지역 변수는 C++와 같아요 |
| Craykon | 범위 밖은 "목록에는 0~4번만 있어요"로 멈춰요.7 / 0은 Infinity, 수학.몫(7, 0)은 "0으로 나눌 수 없어요".빈값 + 1은 "빈값으로는 계산할 수 없어요"(문자열과 이으면 빈값이라고 보여요), 없는 이름은 "아직 만들지 않은 이름이에요" |
자주 하는 실수
i <= n으로 한 칸 넘어갔어요C · C++ · Java · C# · JavaScript · TypeScript
int sum = 0;
for (int i = 0; i <= n; i++)
sum += a[i];→ Java·C#은 범위 밖 예외, JavaScript는 undefined를 더해 NaN, C는 조용히 쓰레기 값을 더해요
i < n으로 써요.
== 대신 =를 썼어요C · C++ · JavaScript
if (x = 5) {
puts("다섯");
}→ 오류 없이 x에 5가 들어가고 늘 참이에요(gcc -Wall은 경고해 줘요)
비교는 ==.
Java·C#은 컴파일 오류, Python은 SyntaxError(3.10부터 "Maybe you meant '=='"), 크레이콘은 문법 오류로 막아 줘요.
평균이 정수로 나왔어요Java · C# · C · C++
int sum = 7, n = 2;
double avg = sum / n;→ avg는 3.0이에요. 7 / 2가 정수 나눗셈이라 소수가 먼저 버려져요
한쪽을 실수로 바꿔요: (double)sum / n.
언어마다 다른 점
오류로 멈추는 건 오히려 고마운 일이에요.
정말 무서운 건 C·C++의 범위 밖 접근이나 JavaScript의
오류로 멈추는 건 오히려 고마운 일이에요.
정말 무서운 건 C·C++의 범위 밖 접근이나 JavaScript의
undefined처럼 오류 없이 틀린 값이 나오는 경우예요.