음악, 삶, 개발
5. Errors 본문
I realized that from now on a large part of my life would be spent finding correcting my own mistakes.
- Maurice Wilkes, 1949
우리는 이 챕터에서, 프로그램의 정확성, 에러, 에러를 다루는법에 대해 이야기할것이다.
< 목차 >
5.1 Introduction
5.2 Sources of errors (에러의 근원)
5.3 Compile-time errors
5.3.1 Syntax errors
5.3.2 Type errors
5.3.3 Non-errors
5.4 Link-time errors
5.5 Run-time errors
5.5.1 The caller deals with errors
5.5.2 The callee deals with errors
5.5.3 Error reporting
5.6 Exceptions
5.6.1 Bad arguments
5.6.2 Range errors
5.6.3 Bad input
5.6.4 Narrowing errors
5.7 Logic errors
5.8 Estimation
5.9 Debugging
5.9.1 Practical debug advice
5.10 Pre and Post conditions
5.10.1 Post-conditions
5.11 Testing
5.1 Introduction
error 의 종류는 아래와 같다.
- Compile-time errors: compiler 가 발견되는 error. Syntax errors, Type errors 가 포함된다.
- Link-time errors : linker 에 의해 발견되는 error. .obj 를 결합하는 과정에서 발생한다.
- Run-time errors : 프로그램이 돌아가는동안 발견되는 error. 컴퓨터에의해 발견되거나, library 에 의해, 혹은 사용자 code 에 의발견될수있다.
- Logic errors : 원하지않는 결과가 나옴으로써 프로그래머에 의해 발견되는 error.
error 는 compile-time -> link-time -> run-time 순으로 확인된다.
언제나 우리는 스스로에게 물어봐야한다.
"나의 프로그램은 error 을 detect 하는가?"
항상 아래 사항을 고려하라. (번역하기 짜증나서 영어로 적겠음...)
- Should produce the desired results for all legal inputs
- Should give reasonable error messages for all illegal inputs
- Need not worry about misbehaving hardware
- Need not worry about misbehaving system software
- Is allowed to terminate after finding an error
위의 고려사항중, 사실 3,4,5 를 대비하는것은 매우 광범위하고 어려운일이다.
하지만 1, 2 는 프로그래머라면 필수적으로 언제나 고려되어야한다.
프로그램에서 error 의 발생은 자연스럽고 피할수없는 존재이다.
하지만, 기본적으로 아래 3가지 접근법을 유지해야한다.
- Organize software to minimize errors.
- Eliminate most of the errors we made through debugging and testing.
- Make sure the remaining errors are not serious.
5.2 Sources of errors
아래는 error 의 근원의 예이다.
항상 마음속에 염두해두고, error check-list 로 활용하도록한다.
- Poor specification (열악한 사양)
- Incomplete programs (미완성된 프로그램)
- Unexpected arguments (예상치못한 인자)
- Unexpected input (예상치못한 input)
- Unexpected state (예상치못한 상태) : data 가 잘못된 경우.
- Logical errors (논리 error) : 우리가 기대한 결과가 나오지않는 error
5.3 Compile-time errors
compiler 에 의해 발견되는 error 의 종류는 대부분 오타를 적었거나 문장의 미완성등과같은 사소한것들이다.
Syntax error 와 Type error 가 이에 포함된다.
5.3.1 Syntax errors
syntax error 의 예를 보자.
int area(int length, int width);
int s1 = area(7; // error : ) missing
int s2 = area(7) // error : ; missing
Int s3 = area(7); // error : Int is not a type
int s4 = area('7); // error : non-terminated character (' missing)
syntax error 는 대부분 우리의 어처구니 없는 실수이다. (내가 이렇게 적었다고?? 같은..)
syntax error 가 확인되었을때 compiler 가 알려주는 line 이 문제가없다면
전,후 line 을 확인해보도록 한다.
5.3.2 Type errors
type 의 miss-match 를 나타내는 error 이다.
예를 보자.
int area(int length, int width);
int x1 = area(7); // error : wrong number of arguments (area 의 인자로 2개가 와야하는데, 1개만 옴.)
int x2 = area("seven", 2); // error : 1st argument has a wrong type. (첫번째 인자가 string 이 아니라 int 로 들어와야함)
5.3.3 Non-errors
compiler 는 error 라고 말해주지않지만 error 스러운 것들이다.
예를 보자.
int area(int length, int width);
int x1 = area(10, -7) // OK : 하지만, width 가 - 가 될수있음?
int x2 = area(10.7, 9.3); // OK : 하지만, int 로 변환되서 area(10, 9) 로 호출됨.
char x6 = area(1000, 9999); // OK : 하지만 char 는 작기에, 결과값이 truncate 될것임.
위의 코드들은 모두, compiler 가 문제없이 compile 하지만 보다시피 문제들이 있다.
(compiler 에 따라, 경고(warn)를 해줄수도 안해줄수도있다)
따라서 "나의 코드는 compile 돼!" 가 "정상적으로 작동한다" 를 의미하지않는다는것을 기억해야한다.
5.4 Link-time errors
프로그램은 분리된 여러개의 compile된 파트(translation units)로 구성된다.
모든 함수는 똑같은 type 으로 모든 translation unit 에 declare 되어야한다.
또한 한 개의 프로그램에서 오직 단 한번 definie 되어야한다.
위의 법칙을 어길시, linker 는 error 를 줄것이다.
어떻게 link-time error 를 피하는지 배워본다.
아래는 전형적인 linker error 의 예이다.
int area(int length, int whide);
int main() {
int x = area(2, 3);
}
우리가 area() 를 다른 source file 어딘가에 define 해놓지않는한, linker 는 area() 의 definition 을
찾지못했다고 error 를 보낼것이다.
area() 의 definition 은 선언된 return type, argument type 가 완벽히 동일하게 정의되어야한다.
만약 동일 type 이 아닌 함수는 동일 함수가 아닌 "다른" 함수가 된다.
예를 들어.
int area(int x, int y);
double area(double x, double y);
int area(int x, int y, char unit);
선언되지않은 함수가 코드에 사용되었을때에는 compile-time error 가 발생한다.
정리하면 link 의 규칙은 아래와같다.
- 한 이름아래 함수의 정의(definition) 는 오직 한번만 이루어져야한다.
- 한 이름아래 함수의 선언(declaration) 은 여러번 할수있지만, type 은 완벽히 일치해야한다.
5.5 Run-time errors
우리의 프로그램이 compile-time error 나, link-time error 가 없다면, 실행(run) 될것이다.
이제부터가 괴로움의 시작인데, run-time error 가 가장 찾기 힘든 error 이다.
예를 보자.
int area(int length, int width) {
return length * width;
}
int framed_area(int x, int y) {
return area(x - 2, y - 2);
}
int main() {
int x = -1;
int y = 2;
int z = 4;
int area1 = area(x, y);
int area2 = framed_area(1, z);
int area3 = framed_area(y, z);
double ratio = double(area1) / area3; // 계산하면 area3 은 0 이 된다.
}
위의 코드같은 문제를 해결하기위해 2가지 대안이 있다.
- caller(호출하는 놈 : 여기서는 main()) 가 bad arguments 를 check 하게.
- callee (호출되는 놈 : 위에서는 area()) 가 bad arguments 를 check 하게.
5.5.1 The caller deals with errors
5.5.2 The callee deals with errors
(길어서 생략)
요약하면, callee 가 bad argument 를 check 해야한다!
영어로 풀어쓰면 아래와 같다.
function must check it's own arguments ( = the callee checks)
Max object 를 생각해봐도, 각 object 가 bad input 을 책임진다.
하지만... 불행히도 항상 이렇게만 callee checks 을 하는건 아니다. 아래와 같은 이유로 인해..
- 우리가 library 의 함수를 사용할때 함수의 정의를 수정할수가없다.
- 호출된 함수는 error 발생시 무엇을 해야할지 모른다. (대부분 library)
- 호출된 함수는 어디서 자신을 호출했는지 모른다. : error 발생시 무언가 잘못되었다고 말해주지만, 어디인지 알려주지않는다.
- 성능 (performance) : 작은 함수에서는, 배보다 배꼽이 더 클수가있다. error check 하는 코드가 더 길어질수도있는것.
그래서 어떻게 하라고?
그래도 callee checks 를 한다. (아주 특별한 이유가 있지않는한)
5.5.3 Error reporting
5.6 Exceptions
5.6.1 Bad arguments
5.6.2 Range errors
5.6.3 Bad input
5.6.4 Narrowing errors
위의 sub 챕터들은 exception 에 대한 내용이 대부분인데,
만약 추후에 내가 알아야할 상황이 왔을때 돌아와서 읽어보자.
Peter 말로는 거의 쓰이지않는다고한다.
5.7 Logic errors
logic error 는 가장 찾기 힘든 error 이다.
5.8 Estimation
생략.
5.9 Debugging
debugging 의 3요소
- 프로그램이 compile 되게 하라.
- 프로그램이 link 되게 하라.
- 프로그램이 예상대로 작동되게하라.
우리는 위의 과정을 무한 반복할것이다.
우리는 error 가 발생활 기회를 최소화하고, error 을 찾을 기회를 최대화해야한다.
5.9.1 Practical debug advice
언제나 코드의 첫줄을 작성하기 직전에, 우리가 어떻게 debugging 을 할지 생각하고 출발하라.
이 책에서 추천하는 대답은 "error() 와 exception 을 main() 에서 catch 하라" 이다.
아래는 debug 를 위한 필자의 조언이다.
조언1. Comment
comment 를 잘 작성하라. 이것은 많은 양의 comment 를 의미하는것이다.
code 에서 드러나지않은 부분을 comment 에서 깔끔하게 (clearly) 간략하게 (briefly) 작성하라.
아래는 comment 를 작성해야할 목록.
- 프로그램의 이름
- 프로그램의 목적
- 누가, 언제 이 code 를 적었는지
- version ex) 1.0.1
- 복잡한 코드가 의미하는 역할
- 디자인 아이디어
- 소스 code 가 어떻게 정렬되었는지.
- input 에 대한 가정
- code 의 어떤 부분이 여전히 부족하고, 어떤 경우가 여전히 handle 되고 있지않은지.
- 함수안에 argument 로써 우리가 기대하는값 : pre-condition (뒤에 나옴)
- 함수가 return 할때 우리가 기대하는 값 : post-condition (뒤에 나옴)
조언2. 의미있는 이름을 사용하라. (긴 이름을 쓰라는 말이 아니다)
조언3. code 의 일관성을 유지하라. (이 책에 나와있는 code 스타일은 꽤나 합리적인 시작점이다.)
조언4. code 를 작은 함수로 쪼개라. 대부분 함수는 매우 짧아야한다.
조언5. 복잡한 코드 연결(sequence)을 피해라.
중첩된 loop, 중첩 if, 복잡한 조건문등을 피해야한다.
가끔은, 어쩔수없이 위의 것들을 사용해야하지만,
항상 복잡한 코드는 bug 가 살수있는 최적의 장소임을 명심하라.
조언6. 가능하면 직접 만들지말고 library 를 사용하라.
대부분 library 는 충분히 검증, 테스트 된것들이고
우리가 우리가 만든 코드로 이런 검증을 시도하는것보다 훨씬 나은 선택이다.
----
아래는 compile-time 에서 나타날수있는 일반적인 error 목록이다.
(번역 하기귀찮아서 영어로. 항상 자문자답 해보길)
- Is every string literal terminated?
- Is every character literal terminiated?
- Is every block terminated?
- Is every set of parentheses matched?
- Is every name declared?
- Did you inlcude needed headers like #include <iostream>?
- Is every name declared before it's used?
- Did you spell all names correctly?
- Did you terminate each expression satement with a semicolon?
아래는 또다른 조언
- 우리는 보고싶은것만 보려한다. 우리가 적은것을 보기보다는 보고싶은것을 본다.
- 우리가 문제를 못보는 이유는, 실행되는 구간에 코드가 너무 많아서 이다.
- 현대 IDE 는 문장 단위로 one by one, "Step through" 를 지원한다. 이 기능을 활용하라.
- 너가 아무리 애를 써도, bug 가 안보인다면 아마 잘못된 장소에서 뒤지고있는 것이다.
debug 의 왕도는 없다. 하지만 우리의 코드는 언제나.
simple, logical, well-formatted.
5.10 Pre and Post conditions
좋은 습관은 우리가 함수를 작성할때 기대되는 argument 를 항상 comment 로 적어놓는것이다.
argument 의 요구사항을 우리는 pre-condition 이라고한다.
int myFunction(int a, int b, int c) {
// a,b,c 는 양수이며 (positive). a < b < c 이다.
if(!(0 < a && a < b && b <c)) {
error("bad arguments!");
}
// ...
}
pre-condition 이 true 일때 함수는 정상 작동을 하고,
pre-condition 이 false 일떄 어떻게 할지 우리는 고려해보아야한다.
2가지 방법이 있다.
- 무시한다.
- 확인하고, error 를 report 한다.
다행히도, compiler 는 이런 pre-condition 에 대해 대부분 compile-time 떄 알려준다.
하지만 간혹 compiler 가 알려주지않는 경우도 있으니, 언제나 pre-condition 을 comment 화 해놓도록하자.
pre-condition 코드를 작성할 필요가 없는 경우는 아래와 같다.
- 절대 bad argument 가 들어올 일이 없다.
- 나의 코드를 느리게 만들것이다.
- check 하기 너무 복잡하다.
우리가 pre-condition 을 작성하기힘든 경우가 꽤나 있지만,
언제나 기본은 작성을 해보고, 뺼수있는 이유가 있을시 빼는것이다.
pre-condition 을 작성하는 습관은 함수가 무엇을 하는지 우리가 생각하게끔하는 아주 큰 이득이 있다.
또한 design 적인 실수를 꽤나 만ㅀ이 줄여준다.
만약 간단 명료하게 pre-condition comment 를 작성할수없다면,
이것은 우리가 이 함수로 뭘 하고자하는지 스스로가 명확히 모르고있음을 의미한다.
5.10.1 Post-conditions
post-condition 은 우리가 return 할때의 요구사항이다.
이에 대한 comment 역시 작성해야한다.
pre-condition 과 post-condition 을 comment 로 작성한 예제를 보자.
(앞으로 나도 이렇게 하자)
int area(int length, int width) {
// caculate area of a rectangle.
// pre-conditions : length and width are positive
// post-condition : returns a positive value that is the area
if (length <= 0 || width <= 0) {
error("area() pre-condition");
}
int a = length * width;
if (a <= 0) {
error("area() post-condition");
}
return a;
}
아래와 같이 함수안에 늘 작성하도록 한다.
// 함수가 하는 일
// pre-condition : argument 요구사항
// post-condition : return 의 요구사항
5.11 Testing
인간이 일일히 노가다로 하지않고,
프로그래밍의 testing 을 도와줄수있는 tool 들이 있다.
Chapter 10 과 Chapter 26 에서 다룰것이다.
Review
- Name four major types of errors and briefly define each one.
- What kinds of errors can we ignore in student programs?
- What guarantees should every completed project offer?
- List three approaches we can take to eliminate errors in programs and produce acceptable software.
- Why do we hate debugging?
- What is a syntax error?
- What is a type error?
- What is a linker error?
- What is a logic error?
- List four potential sources of programs errors discussed in the text.
- How do you know if a result is plausible?
- caller function's run-time error vs called function's run-time error
- Why is using exceptions a better idea than returning an "error" value?
- How do you test if an input operation succeeded?
- Describe the process of how exceptions are thrown and caught.
- Why, with vector called v, is v[v.size()] a range error?
- Define pre-condition and post-condition.
- When would you not test a pre-condition?
- When would you not test a post-condition?
- What are the steps in debugging a program?
- Why does commenting help when debugging?
- How does testing differ from debugging?
Terms
- argument error
- assertion
- cath
- compile-time error
- container
- debugging
- error
- exception
- invariant
- link-time error
- logic error
- post-condition
- pre-condition
- range-error
- requirement
- run-time error
- syntax error
- testing
- throw
- type error