이제 C언어 등 프로그래밍을 시작하시는 분들께 제 작은 조언을 하나 드릴까 싶어 글 남겨요. 일단 여러분들의 호기심과 열정에 격려의 박수 보내드립니다. 짝짝짝. 그럼 약간의 시간을 들여 삽질성 코드에 대한 조언을 좀 드리도록 할께요. 저는 평소에 글을 좀 무미건조하게 쓰는지라 그다지 재미가 없을지 모르겠어요.
<삽질성 코드?>
코딩을 열심히 했습니다. 엄청나게 많은 코드량에 약간 뿌듯합니다. 그러나 똻!!! 버그가 쩝니다!! 뭐가 잘못되었는지 몰라 들여다보고 들여다보고 들여다보지만, 알 수가 없습니다. 도움을 청하기 위해 글을 씁니다. 코딩 창의 코드를 쓱~ 긁어서 쓱~ 갖다 붙입니다. 답변이 오겠지 오매불망 기다립니다.
여러분들에게는 훌륭해 보이는 코드일 수도 있습니다. 그렇지만 코드를 복사해 붙이기 전, 여러분들은 여러분들이 보는 코드가 남들에게는 어떻게 보이는 지 좀 더 고민할 필요가 있습니다.
초보가 흔히 저지르는 실수가, 이런 "삽질성 코드"의 범람입니다. 제가 아주 싫어하는 코드이인데, 보통 이런 스타일로 작성되는 코드들입니다.
...
printf(" 프로그램을 시작합니다.\n");
...
...
printf("blah blah"); scanf("blah blah", &a);
printf("blah blah"); scanf("blah blah", &b);
...
printf("1번 메뉴: "); ... blah blah
printf("2번 메뉴: "); ... blah blah
printf("종료는 q: "); ... blah blah
printf("메뉴를 선택: "); ... blah blah
그래서 프로그램을 실행시키면 무슨 80~90년대 PC 통신 스타일의 뭔가가 만들어지죠. 묻고 싶습니다. 간단한 사칙연산 계산기 로직 하나 만드는데 뭐가 그렇게 장황한가요? 왜 이런 printf의 홍수가 일어나나요? 혹시 콘솔 윈도우에 나오는 그러한 출력물들이 신기해서 그러시는 건가요? 이렇게 코드를 작성하시는 태도는 매우 좋지 않습니다. goto 만큼이나 해롭다고 생각합니다. 혹시 이렇게 프로그램을 작성하시는 분들, 이제 부터는 그렇게 짜지 마세요.
<삽질성 프로그램이 좋지 않은 이유>
1. 당신의 시간이 무의미하게 소모된다.
printf와 scanf, 그리고 쓸데없는 "메뉴" 구성 때문에 불필요하게 시간이 듭니다. 시간 아까워요.
2. 프로그램의 핵심 로직이 가려집니다.
프로그램이 뭘 하는지 잘 드러나지 않습니다. 그렇게 작성된 코드를 "논술"에 비교한다면 핵심 주제는 한 두줄인데 쓸데없는 미사여구만 10페이지, 혹은 종이에 낙서한 겁니다. 초보 분들, 그리고 실수를 해 버리면 도대체 어디서 잘못된 건지 파악하기도 어렵죠. 왜일까요? 실제 핵심적인 역할을 하는 부분과, 쓸데없이 출력하는 부분, 쓸데없이 입력을 받는 부분, 또 무의미한 메뉴 구성들이 서로 한데 얽혀 풍성한 스파게티가 될 겁니다.
<왜 이런 삽질을 하는가?>
그럼 왜 이런 코드를 짜게 될까요? 조금 엄하지만 나열해 보도록 하죠.
1. 코드에 뭔가 출력되는 것이 신기해서.
당신이 정말 초보라는 증거군요. 그럼 코드는 실습 정도로만 짜고, 책을 더 보고 전반적인 사항을 보다 익히기를 바랍니다. 많이 짠다고 무조건 많이 느는 건 아니에요.
2. 자신이 없어서.
자신이 짠 코드 흐름도 불안해서 이렇게 하는 거라면, 너무 불안감이 심한 겁니다. 물론 프로그래밍에 익숙한 선배들도 중요한 부분에는 printf 등을 삽입해서 코드가 생각대로 돌아가는지 확인하는 코드를 넣습니다. 그렇지만 이것도 중간 결과를 간략하게 출력하는 로그 정도로 끝내야 됩니다. 불필요하게 번잡한 메시지를 넣을 필요가 있을까요? 중간 결과만 명료하게 알아볼 수 있을 정도로 출력하면 됩니다.
* 번잡한 예: scanf( ... , &a); printf("변수 a에 입력된 값은 %d 입니다... 블라블라, ..", a, ...);
* 간단한 예: scanf( ...., &a); printf("a = %d\n", a); // 정말 확인을 위해 쓰긴 하지만, 애초에 이런 코드를 넣지 마라.
그리고 디버깅을 꼭 배우세요.
3. 뭔가 있어 보이게 만들고 싶어서.
뭔가 메뉴 1, 2, 3, a, b, c, q 등을 이용해서 메뉴도 만들고 하면 있어 보이니까요.
그치만요... 그거 진짜 90년대 PC 통신이잖아요. 아님 자판기에요? 솔직히 말해 남들 눈에는 졸라 구려 보여요.
<그럼 어떻게 짜라는 거지?>
1. 제발 함수를 써서 기능을 분할하세요.
총합을 구하고 싶으면 총합을 수행하는 함수를 만들고, 평균을 구하고 싶으면 평균을 구하는 함수를 만들어 구현하세요. 함수의 입력은 딱 그 역할만 하는 것들만 넣구요. 함수 어떻게 짜는 지 모른다구요? 그럼 그런 삽질 코딩으로 보내는 시간에 함수를 공부하는 것이 어떨까요.
메인 함수에 모든 기능이 몽땅 때려박혀 있는거, 재앙입니다. 실습으로 한걸음씩 따라하는 거라든가, 함수를 들어가기 전에 아주 기초 단계일때만 이해 가능한 겁니다. 여러분들의 프로그램 어딘가에서 에러가 납니다. 함수로 잘 기능을 분할했다면 그 에러난 함수만 교정하면 됩니다. 그러나 메인함수에 모든 걸 짰다는 말은, 뒤죽박죽이자 같은 코드가 여러번 반복되었다는 말입니다.
2. 입력은 파일로 받으세요.
input.txt 등의 파일을 만들어서 거기에 한 줄에 하나씩 케이스를 입력 받아 결과를 출력하도록 하세요. 프로그래밍 경시 대회 등에서 자주 쓰이는 스타일입니다. 표준 입력으로 받더라도 불필요하게 입력을 요구하는 print문을 넣지 마세요.
만일 여러분들이 프로그램의 입력으로 정수 두 개를 받고 싶다면,
int x, y;
if ( 2 == scanf ("%d %d ", &x, &y) ) {
....
}
정도만 해도 충분합니다. 이외에 정수 두개를 입력하라는 둥의 입력 메시지는 굳이 출력할 필요가 없습니다. 입출력 스타일을 이렇게 딱 정하는 것도 깔끔한 프로그램을 짜는 데 있어 매우 중요합니다. 그리고 파일을 표준 입력으로 받으려면 컴파일된 파일에, 예를 들어 a.exe 가 만들어졌다면,
a.exe < input.txt
이렇게 해 주면 됩니다. 진짜 파일로 입력을 받으려면 이렇게 하면 되겠죠?
FILE *fp = fopen("input.txt", "r");
if (fp) {
fscanf("%d %d ", &x, &y);
fclose(fp);
}
// 여기서도 굳이 파일이 열리지 않았다는 에러 메시지가 필요할까? 이 부분은 선택에 맡긴다.
이렇게 입력값을 따로 주는 이유가 있습니다. 매번 컴파일, 실행할 때마다 여러분들은 1, 2, 3, 4 직접 숫자를 입력하지 않아도 됩니다. 그거 일일이 입력하는 것도 시간이 걸리죠. 혹시 두 정수 x, y들을 코드 안엔 박아두었나요? 값을 바꿀 때마다 일일이 재컴파일 하겠죠. 그런 프로그램은 절대 좋은 게 아닙니다. 100번이든 1000번이든 한 번 짜고 컴파일한 뒤 문제를 발견할 때까지 계속 테스트할 수 있도록 만들어야죠.
파일을 입력을 받는 법을 모른다면, 반드시 익히셔서 쓸 수 있도록 만드세요.
3. 입력 포맷과 마찬가지로 출력 포맷도 간단하게 만듭니다.
두 개의 정수를 받아서 하나의 정수를 출력한다든지, 이렇게 단촐하게 결과를 찍는 게 좋습니다. 왜 주저리주저리 값을 출력해야 할까요? 왜 사람의 제어 메뉴가 굳이 들어가야 할까요? 원래
사람의 손을 줄이고 기계의 힘을 빌어 단순한 많은 일을 자동화하기 위해 하는 것이 컴퓨터 프로그래밍의 원칙입니다. 이런 삽질성 코드들은 그러한 원칙을 무시하는 겁니다. 그러니 좋지 않죠.
문자열이 들어간다 하더라도 그냥 "The answer is %d\n" 이 정도로. 이것만으로도 프로그램의 동작을 확인은 충분히 할 수 있습니다.
<예: 평균 출력>
표준 입력으로부터 N개의 정수를 받아 합과 평균을 출력하는 프로그램을 작성할 겁니다. 입력 포맷은 이렇게 할 겁니다.
5 1 2 3 4 5
첫번째 5는 정수의 개수 N입니다. 자신 다음에 정수 5개가 입력된다는 뜻이에요. input.txt 파일을 메모장으로 열어서 적어 줍니다.
여기서 입력 포맷은 여러분이 어떤 식으로든 여러분이 입력하기 편하도록 구성하면 됩니다.
총합과 평균은 각각 실수(double)이며 하나의 공백으로 구분합니다. 소수점 2자리까지만 표현하도록 하죠.
#include
double get_total(int n)
{
double tot = 0.0;
int i, x;
for(i = 0; i < n; ++i) {
scanf("%d ", &x);
tot += (double)x;
}
return tot;
}
double get_average(double total, int n)
{
double average = 0.0;
if (n) {
average = total / (double)n;
}
return average;
}
int main()
{
int n;
double average, total;
scanf("%d ", &n);
total = get_total(n);
average = get_average(total, n);
printf("%.2lf %.2lf\n", total, average);
return 0;
}
이것이 컴파일 되어서 average.exe 가 되었다면
average.exe < input.txt
15.00 3.00
이렇게 하면 됩니다. 비주얼 스튜디오를 쓴다면 프로젝트 속성을 열어 "Debugging -> Command Arguments"에 간 다음 "< input.txt"를 넣어 주면 되죠.
아주 간결하게 입/출력이 나옵니다. 게다가 입력값은 input.txt 만 바꿔주면 결과는 알아서 바뀝니다. 다시 여러번 컴파일할 필요도 없죠.
그리고 여러분들이 합과 평균을 구해 출력했다는 의도까지 함수를 통해 명확히 보입니다. 만일 합에서 에러가 나면, get_total() 함수만 보면 되고, 평균 부분에서 에러가 나면 get_average함수만 보면 됩니다.
물론 합과 평균을 구하는 정도의 매우 간단한 코드니까, 모든 걸 main에서 아주 간단하게 처리해도 상관 없습니다. 그러나 코드가 조금이라도 복잡해진다면 반드시 제가 드리는 조언을 지키셔야 버그를 줄일 수 있어요. 말이 많이 길어졌습니다. 그냥 여기 질문 올리시는 분들께 꼭 드리고 싶은 말씀이라 적어 보았습니다.