성공하는 프로그래머들의 9가지 코딩 습관

개발 노트 2008. 3. 23. 21:41 posted by 무병장수권력자


출처 : 코리아 인터넷 닷컴

좋은 소스 코드가 좋은 프로그램을 만든다

작동하는 결과만 좋으면 소스 코드는 어떻게 만들어지든 상관이 없다고 생각하는 사람들이 있다. 절대로 그렇지 않다. 프로그램은 한 번 만들고 끝나는 법이 없다. 계속되는 버그 수정, 기능 확장 등 계속적인 관리가 필요하다. 그래서 소스 코드는 최대한 알아보기 쉽고 관리하기 쉽게 만들어져야 한다. 이 것은 1차로 프로그래머를 위한 것이지만 결국 프로그램의 성능에도 영향을 미칠 수밖에 없다. 관리하기 어려운 소스 코드에서 좋은 프로그램이 나오는 것은 불가능하기 때문이다.

이 글을 통해 좋은 프로그래밍 습관을 배워보자. 이 글을 제대로 이해하려면 최소한 하나의 프로그래밍 언어는 어느 정도 알고 있어야한다. ASP,PHP,Perl,Java-script 등의 스크립트 언어도 상관없다. HTML도 일종의 언어이기 때문에 몇 가지를 빼고는 적용할 수 있을 것이다. 이 글에서 사용한 예제는 모두 C로 작성되었지만, C언어를 모른다고 해서 걱정할 필요는 없다. 여기서 중요한 것은 "원리"이지 "특정 언어"가 아니다.



문장을 확실히 끝낸다 - 세미콜론(;)


프로그래머가 가장 흔히 저지르는 실수는 한 문장을 끝내고 세미콜론(;)을 빼 먹는 것이다. 이렇게 해서 생긴 오류는 때로 감쪽같이 숨어서 프로그래머를 당황하게 만든다. 코드 작성시 각 문장이 세미콜론으로 제대로 끝났는지 항상 확인하라. 비록 모든 언어가 문장 끝에 세미콜론을 요구하지는 않는다 하더라도 말이다. 여기 세미콜론을 잊어버린 예제가 있다:

int main(void)
{
    /* 세미콜론이 없음. 오류 발생*/
    printf("Hello World!\n")
    return(0);
}


놀랄 정도로 많은 사람들이 이런 실수를 한다. 몇 줄 안되는 프로그램에서는 그럭저럭 괜찮지만…길이가 1000 줄 이상이 되는 코드를 작성한다고 하면 문제는 다르다. 어디선가 세미콜론을 빼 먹었다면 찾아내는 일이 쉽지 않을 것이다. 일반 문장 끝에 항상 마침표(.)를 쓰듯이 코드 한 줄이 끝나면 항상 세미콜론을 써라.

세미콜론에 관한 다른 실수는 그것을 엉뚱한 곳에 찍어서 생긴다. 경험 있는 프로그래머들은 다음의 예제를 보고 웃음을 터트릴 테지만, 필자는 실제로 이런 일을 많이 보았다. 자신의 프로그램이 왜 작동하지 않는지 이상하게 여기며 email을 보내는 사람들이 많은데, 거의 대부분 세미콜론 때문이다. 다음 예를 보자:

/* main() 뒤에 세미콜론 필요 없음 */
int main(int argc, char *argv[]);
{
    printf("Hello World");
    return(0);
}


함수나 메소드, 제어문 블록을 시작할 때 세미콜론을 찍으면 안 된다.



빈 칸과 들여쓰기를 적절히 활용한다.


C언어에서 빈 칸은 무시된다. 따라서 빈 칸없이 모두 붙여 쓴 코드를 만들 수는 있지만 결국 아무도 알아보지 못하는 "어려운" 코드가 되고 만다. 다음 예를 보자:

if(x==0) {a=b=c=d=MAX; x++;}

이렇게 코드를 작성하면 공간을 절약할지 모르지만 다른 사람은 물론 작성자 자신도 알아보지 못하는 코드가 된다. 읽기 쉽게 구성된 코드를 작성하라!

if(x == 0)
{
    a = b = c = d = MAX;
    x++;
}


얼마나 보기 편해졌는가? 빈 칸이 적절히 있어야 코드가 읽기 쉽다. 컴퓨터는 빈 칸을 상관하지 않지만, 사람에게는 꼭 필요하다. 들여쓰기 역시 마찬가지다.



중괄호와 블록방식을 통일한다.


프로그래머마다 중괄호({,})와 블록을 쓰는 방법이 다르기 때문에 다른 프로그래머가 코드를 넘겨 받아 작업할 때 혼란이 생긴다.

예를 들면, 이런 식으로 쓰는 것이다:

int main()
{
    int x = 1;
    int y = 10;
    while(x < y )
{
       
printf("Value of x is %d\n", x);
        x++;
    }
}


어떤 프로그래머들은 다음과 같이 중괄호를 사용한다:

int main()
{
    int x = 1;
    int y = 10;
    while(x < y )
   
{
       
printf("Value of x is %d\n", x);
        x++;
    }
}


모두가 동의하지는 않겠지만, 필자는 2번째 방식을 더 좋아한다. 블록의 시작과 끝이 명확하기 때문이다. 어떤 한 방식을 모든 프로그래머에게 강요할 수는 없지만 하나의 프로그램 안에서는 한 방식으로 통일해야 한다. 그리고 다른 프로그래머의 코드를 볼 때는 다른 방식에 대한 가능성을 언제나 염두에 두고 보아야 한다.



if문을 남용하지 않는다.


어떤 사람들은 "if" 문 사용하기를 너무 좋아해서 다음과 같은 코드를 작성하기도 한다:

if(a == 0)
{
    a++;
    return(a);
}

if(a == 1)
{
    a += 5;
    return(a);
}

if(a == 2)
{
    a += 10;
    return(a);
}

if(a == 3)
{
    a += 20;
    return(a);
}

if(a == 4)
    exit(1);


이것보다 더 나은 방법이 있을까? else if를 쓰는 것? 아니다. 대안은 "switch-case"문을 사용하여 간결한 코드를 얻는 것이다:

switch(a)
{
    case 0: a++;
        return(a);

    case 1: a += 5;
        return(a);

    case 2: a += 10;
        return(a);

    case 3: a += 20;
        return(a);

    default: exit(1);
}


"a"와 일치하는 값이 없으면 자동적으로 default에 정의된 작업을 한다. 위의 예에서는 프로그램이 끝나게 설정되어 있다.



블록 안에 블록(nesting)을 남용하지 않난다.


사람들이 블록 안의 블록(nesting)을 얼마나 남용하는지 알 게 되면 놀랄 것이다. 세 개 이상의 중첩된 블록을 갖는 것은 좋지 않다. 알아보기 힘들기 때문이다. 다음 예제를 보자.

int a = 10;
int b = 20;
int c = 30;
int d = 40;

if(a == 10)
{
    a = a + d;
    if(b == 20)
    {
        b = b + a;
        if(c != b)
        {
            c = c + 1;
            if(d > (a + b))
                printf("Made it all the way to the bottom!\n");
         }
    }
}


어느 정도는 과장되어 있지만, 많은 사람들이 "진짜" 이렇게 하고 있다. 그럼 어떻게 고칠 수 있을까? 한 가지 방법은, 함수로 ご㈃?것이다:

void next(int a, int b, int c, int d)
{
    if(c != b)
    {
        c = c + 1;
        if(d > (a + b))
            printf("Made it all the way to the bottom!\n");
    }
}

int main()
{
    int a = 10;
    int b = 20;
    int c = 30;
    int d = 40;

    if(a == 10)
    {
        a = a + d;
        if(b == 20)
        {
            b = b + a;
            next(a, b, c, d);
        }
    }
    return(0);
}


이렇게 하려면 일거리가 약간 늘겠지만, 코드는 구조화되고 읽기는 휠씬 쉬워진다. 함수를 좀 더 잘 만들면 다른 함수에서도 다시 사용할 수 있다. 코드 전체를 이해하기 쉽게 만들려면 더 많은 코드를 만들어야 하는 경우가 자주 있다. 그러나 한 번만 그렇게 하면 오래도록 편리하다.


주석을 열심히 제대로 단다.


코드에 주석을 자주 달아라. 사람들이 이해하기 어려운 문장이나 변수에 항상 주석을 달아라. 한달 후엔 당신 자신도 그 주석의 도움이 필요할 것이다:

int x = 100;
int y = 1000;

if(x < y)
    a = 0;
else
    a = 1;


이 프로그램이 무엇을 하고 있는지 알 수 있겠는가? 변수 x, y와 a가 무엇을 뜻하는 지 모른다면 알기 어렵다. 이 프로그램에 주석을 달아 보자:

/*
* 이익과 손실을 체크하는 프로그램:
*/

int x = 100;
/* x 는 책을 팔아 얻은 돈의 총 량 */
int y = 1000
/* y 는 책을 만드는 데 든 비용 */
int a;
/* 이익인지 손실인지 확인 */

/* x가 y보다 작은지 체크: */
if(x < y)
/* 손실. 숫자 1이 손실을 나타내기로 함 */
    a = 1;
else
/* 이익. 숫자 0가 이익을 나타내기로 함 */
    a = 0;


이렇게 주석을 달면 C 언어를 모르는 사람도 이 프로그램을 쉽게 알 수 있다. 각 문장이 무엇을 수행하는지, 각 변수들이 무엇을 나타내는지 알 수 있다. 주석을 다는 것은 좋은 습관이다. 하지만 그렇다고 "무작정" 주석을 달라는 것은 아니다. 어떤 부분을 이해하기 쉽도록 하자는 것이지 "모든 부분에" "장황한 이야기를 늘어놓자"는게 아니다.

int profit = 1;
/* 이익은 1과 같다 */
int loss = 0;
/* 손실은 0과 같다 */

/* 만일 이익이 1과 같다면: */
if(profit == 1)
    /* "이익이 났다!" 라고 프린트한다*/
    printf("We made a profit!\n");
/* 아니면: */
else
    /* "손실이 났다!" 라고 프린트한다*/
    printf("We made a loss!\n");


이렇게 주석을 다는 것은 시간 낭비이다. 당연한 것을 "다시" 설명하고 있다. 변수를 선언하거나 변수에 값을 넣을 때가 주석이 가장 필요한 때이다. 각 변수가 무엇을 나타내는지, 각 변수에 무슨 일이 일어나는지, 프로그램이 무엇을 하는 것인지, 어떻게 그 일을 하는지 적어둘 필요가 있다. 각 함수와 메소드, 프로시저, 에러 코드가 무엇을 하는지도 주석을 달아 놓을 필요가 있다.


이름을 제대로 짖는다.


변수, 함수, 메소드, 프로시저, 유니언, 구조체(structure), 클래스의 이름만 보아도 무엇을 하는 것인지 금방 알 수 있도록 이름을 지어라. 변수 이름을 "x", "y", "z"로 정하는 습관은 좋지 않다는 것이다. 필자도 위에서 "x", "y", "z"로 변수 이름을 쓰지 않았냐고 항의할지도 모르겠다. 그러나 필자가 한 글자 변수를 썼던 이유는 어떤 프로그램에 속하지 않은 "단순" 예제였기 때문이다. 필자가 실제 프로그램을 작성할 때는 당연히 의미 있는 이름을 사용한다. 자, 다음의 코드를 살펴 보자:

void x(int a, int b)
{
    int z;
    z = a + b;
    printf("z is %d\n", z);
}


여기서 우리는 프로시저 x가 무슨 일을 하는지는 알 수 있지만, 무엇을 의미하는지는 모른다. 또한 a, b, z가 무언지도 모른다. 이제 약간 수정해 보자:

void sum_of_ages(int jacks_age, int jills_age)
{
    int total_age;
    total_age = jacks_age + jills_age;
    print("total_age is %d\n", total_age);
}


주석은 하나도 없지만 이름만 보면 무엇을 하려는지 쉽게 알 수 있다. 이름을 잘 지으면 주석을 달지 않아도 된다.


준비한 만큼만 넣는다. - 버퍼(buffer)확인하기


미리 정해놓은 배열이나 변수의 크기를 항상 확인해야 한다. 잘못하면 저장되는 데이터가 뒤섞이거나 심하면 시스템에 문제를 일으킬 수 있다. 사용자로부터 입력을 받는 C 프로그램을 보자:

char city[10];
/* 도시 이름을 담는 배열 */

printf("Enter a city name: ");
scanf("%s", city);
printf("City is %s\n", city);


여기서는 도시 이름을 영어 아홉 글자로 가정하고 있다(열번째 글자는 null문자’\0’이다). 사용자가 아홉 글자 이상의 도시 이름을 입력하면 어떤 일이 일어날까? 아무 일도 없거나, 프로그램이 실패하거나, 또는 버퍼 안에 있는 데이터를 겹쳐 쓸 수도 있다. 어떤 경우든, 위험을 무릅쓰는 무모한 일을 하지 말아라. 입력되는 문자의 길이를 점검하도록 하자:

char city[10];
/* 도시 이름을 담는 배열 */

printf("Enter a city name: ");
fgets(city, sizeof(city), stdin);
printf("City is %s\n", city);


수정된 코드에서는 사용자가 버퍼 크기보다 큰 문자열을 입력한다면, 버퍼가 담을 수 있는 만큼만 받아 들이고 나머지는 잘라 버린다.



사용자를 절대로 믿지 않는다.


이것은 중요한 법칙이다. 당신의 프로그램을 이용할 사람들을 믿지 말라. 사용자들이 당신이 원하는 방식대로만 사용할 것이라는 안일한 생각을 버려야 한다. 오히려 프로그램의 문제점을 찾기에 열심인 사람들로 생각하고 철저히 대비해야 한다. 예를 들면, 위에서는 도시 이름을 더 길게 입력할 사용자에 대비하여 문자열의 길이를 확인했다.

C언어처럼 데이터 타입(Type)을 명시하도록 요구하는 언어를 사용할 때는 사용자 입력으로 들어 오는 데이터 타입과 프로그램에서 사용하는 데이터 타입을 확인해야만 한다. 그렇지 않으면, 문제가 생길 수 있다. 디버거(Debugger)가 있다면 프로그램이 실행될 때 어떤 일이 일어나는지, 예를 들면 변수들에게 어떤 일이 일어나는지 모니터하라. 에러 체크하는 데에는 많은 시간이 들지만, 그만큼 더 좋은 프로그램을 만들 수 있다.



작은 시간을 써서 큰 시간을 얻는다.


위에 열거한 습관은 다른 프로그래머들 보다 오히려 당신 자신을 위한 것이다. 소스 코드 타이핑을 시작하기 전에 조금 만 더 준비(분석,설계)하고 시작하자. 조금 번거로워도 제대로 알아 볼 소스 코드를 만드는데 시간을 아끼지 말자. 시간이 없다는 핑계로 이런 습관을 게을리 하면 나중에 결국 더 많은 시간이 없어지게 될 것이다.

'개발 노트' 카테고리의 다른 글

Ruby on Rails 개발 환경 구축  (0) 2008.03.27
Internet Explorer 7 검색 공급자 주소  (0) 2008.03.24
Unix/Linux Redirection  (0) 2008.03.23
int atoi(char* p) 함수를 구현하라.  (0) 2008.03.23
Linux에서 DNS Server 설정하기  (0) 2008.03.23