프로그래밍과 영어

류광

 

한국의 프로그래머들이 흔히 듣는 조언 가운데, ‘영어는 필수입니다’라는 것이 있다. 영어가 필수인 이유로 흔히 말하는 것은 외국 책이나 자료를 빨리 제대로 읽을 수 있어야 한다는 것이다. 그러나 프로그래머가 영어를 잘해야 하는 또 다른, 그리고 좀 더 중요한 이유는, 코딩 자체가 일종의 영작문이라고 할 수 있다는 점이다.

 

거의 대부분의 프로그래밍 언어들은 영어를 기반으로 하고 있다. 특히 고수준 언어로 올라갈수록 자연 언어로서의 영어와 가까와지는 경향이 보인다. 또한 설계가 잘 된 코드일수록 마치 영어로 쓰여진 문서를 읽는 느낌이 드는데, 이는 프로그래밍 방법론의 발전과도 맞닿아 있다. 절차적 언어 시절에는 프로그램 소스 코드라는 것을 컴퓨터를 위한 작업 지시서로 생각하는 경향이 있었으나, 소프트웨어의 설계 및 유지·보수에 대한 문제의식이 대두되고 객체지향적 패러다임이 대세를 이루면서 프로그램 소스 코드는 기본적으로 사람을 위한 것이며 프로그래밍 언어는 사람의 생각을 좀 더 직접적으로 나타낼 수 있어야 한다는 생각이 널리 퍼진 것 같다. 객체 지향이라는 것, 특히 ‘모든 것은 객체이다’라는 말은 결국 우리가 다뤄야 할 문제에 관련된 사물들과 개념들을 그대로 소스 코드로 옮길 수 있어야 한다는 말과 일맥상통한다는 점에서, 소스 코드가 곧 하나의 문서(일상정 의미에서의)이며 프로그래머는 곧 작가 또는 저술가라는 점을 항상 염두에 두어야 할 것이다.

 

앞에서도 말했듯이 프로그램 소스 코드는 컴퓨터를 위한 작업 지시서가 아니라 프로그래머 스스로가 컴퓨터를 통해서 무엇을 어떻게 하고자 하는지를 적어놓은 것이다. 따라서 코딩은 프로그래머 스스로를 위한 서술 과정이기도 하다. 그러나 불행하게도 우리는 우리의 한글로 우리의 생각을 적을 수가 없다. 물론 C# 같은 일부 언어에서는 변수나 클래스, 함수 이름 같은 식별자들에 한글, 엄밀히 말하면 유니코드의 ‘글자(letter)’에 속하는 범위의 한글 글자들을 사용할 수 있으나, 어순 자체는 여전히 프로그래밍 언어의 정의에 따르며, 그 정의라는 것은 대부분 영문법을 따른다. 애초에 한글 언어를 목표로 만들어진 프로그래밍 언어들도 있으나 대부분 기존 언어에서 흔히 보이는 핵심 키워드들을 한글화한 것일 뿐 우리말글의 어순을 따르지는 않는다. 차후에 우리말글의 어순을 따르며 우리말글을 사용하는 사람의 생각을 그대로 자연스럽게 표현할 수 있는 언어가 나오기를 바라나, 어쨌든 지금은 영어에 익숙해질 수밖에 없다.

 

이 글에서는 영어에 관련해서 프로그래머가 좀 더 나은 소스 코드를 작성하는 데 도움이 될만한 몇 가지 주제들을 이야기하겠다.

 

그 전에 한가지 강조하고 싶은 것이 있다. 많은 프로그래머들이 고교 과정때까지 배운 수학과 물리학을 전혀 활용하지 못하고 모든 것들을 다시 시작하는 모습을 많이 보는데 무척 안타까운 일이다. 예를 들어 수학 시간에 배운 다각형과 프로그래밍 책에 나오는 폴리곤을 다른 것으로 생각하는(이는 저자나 번역가의 잘못이 크다) 사람은 그 수학 시간만큼의 시간을 낭비하는 것이다. 그와 마찬가지로, 고교 과정까지 배운 영어만으로도 커다란 재산이며, 프로그래머의 코딩 실력에 커다란 뒷받침이 된다. 품사니 태, 법, 3형식 4형식 같은 것들을 자꾸 떠올려보면서 이 글을 읽었으면 한다.

 

0. 주석

 

흔히 하는 이야기로, 주석은 없어서도 안 되지만 필요 이상으로 많아서도 안된다. Martin Fowler의 책 Refactoring 을 보면, ‘주석을 쓸 필요가 있다고 느낀다면, 먼저 주석이 불필요해질 때까지 코드를 리팩토링해보라’라는 말이 나올 정도이다. 이는, 역시 흔히 하는 이야기로 ‘잘 쓴 코드는 그 자체가 주석이다’라는 말과 일맥상통하는 말이다. 예를 들어, Refactoring의 Extract Method 항목을 보면 이런 예가 나온다.

void printOwing(double amount) 
{
  printBanner();

  /* print details */ 
  System.out.println("name:" + _name);
  System.out.println("amount" + amount);
}

===>

void printOwing(double amount) 
{
  printBanner();

  printDetails(amount);
}

void printDetails(double amount) {
  System.out.println("name:" + _name);
  System.out.println("amount" + amount);
}

이 예는 코드의 블럭에 주석을 붙여서 설명을 하는 대신, 블럭이 하는 일을 직접 알 수 있는 이름의 메서드를 추가함으로써 주석 자체가 필요없게 만든 것이다. 그러나, 주석에 관련된 이러한 지침들과 조언들은 대부분 프로그래머가 영어에 익숙하다는 전제를 깔고 있다. 아주 극단적인 예로, 다음 두 코드 조각들을 비교해보자.

1.

void printOwing(double amount) 
{
  printBanner();

  printDetails(amount);
}

2.

/* 고객의 미지불 정보를 출력. a는 미지불 총액 */ 
void print1(double a) 
{
  /* 배너를 출력한다 */ 
    print2();

  /* 미지불 정보를 출력한다. */ 
  print3(a);
}

영어에 익숙한 사람이라면 당연히 1번 예가 더 낫다고 생각할 것이며, 주석을 달 필요를 전혀 못느낄 것이다. 그러나 만일 owing이나 banner, amount 같은 단어를 모르는 사람이라면 1번은 주석이 전혀 없는 난해한 코드, 2번은 친절한 주석이 달린 알기 쉬운 코드라고 생각할 수 있다.

이 이야기의 교훈은 간단하다. 깔끔한 코드를 원한다면 영어에 더 익숙해져야 하며, 그게 싫으면 주석이라도 많이 집어넣으라는 것. 물론 선택은 여러분의 몫이다.

 

1. 객체 지향 언어의 표기법과 영어

 

객체 지향 언어들의 표기 방식에서 영어보다는 한글 어순에 가까운 용법들이 보인다는 점을 지적하는 사람들이 있다. 예를 들어 어떤 문을 연다고 하자. 절차적, 즉 함수와 자료구조 중심의 프로그래밍 언어라면,

 

open(theDoor);

 

이런 형태로 표기하게 될 것이다. 이는 영어의 명령문 어순(서술어-목적어)과 거의 일치한다.

 

Open the door.

 

반면 객체 지향적 프로그래밍 언어라면,

 

theDoor.open();

 

이는 차라리 한글 어순에 더 맞는 듯 보인다.

 

그 문(the door)을 열어라(open).

 

그러나 이는 단지 우연일 뿐인 것 같다. theDoor.open()을 일상 영어로 표시하자면 다음에 가까울 것이다.

 

Door, open yourself!

 

(yourself는 그냥 붙인 것이 아니다. 실제로, c++에서는 open()이 호출될 때 암묵적으로 this 포인터가 전달된다. this가 바로 yourself에 해당한다).

 

절차에서 객체로 오면서, 바뀐 것은 어순이 아니라 명령의 대상일 뿐이다. 즉 open(theDoor)는 컴퓨터 자체에게 문을 열라고 명령하는 것이라면, theDoor.open()은 ‘문’이라는 객체에게 명령을 내리는 것(객체 지향 세계의 용어로 말한다면 객체에 메시지를 전달)이다.

 

어쨌거나 중요한 것은, 객체 지향 언어를 만드는 사람들은 스스로 자각했든 그렇지 않든 여전히 영어를 염두에 두면서 언어를 설계했다는 점 – 객체 지향에서도 영어는 여전히, 아니 더욱더 중요하다.

 

2. 명사

 

영문법의 명사는 프로그램 소스 코드의 변수나 상수, 클래스 등의 이름에 해당한다. 명사는 행위의 주체(주어) 또는 행위의 대상(목적어)으로 쓰이곤 한다.

 

코딩 스타일 표준에 따라서는 변수 이름을 소문자로 시작할 수도 있고 대문자로 시작할 수도 있다. 물론 접두어가 붙는 경우 접두어는 소문자로 쓰는 것이 일반적인 관례이나, 변수의 의미를 알려주는 주된 단어 자체는 대문자로 하는 것이 바람직할 것 같다.

 

변수를 대문자로 시작하는 것이 좋은 첫 번째 이유는, 변수를 일종의 고유 명사로 볼 수 있기 때문이다. 알다시피 영어에서 고유 명사는 항상 대문자로 시작한다. 두 번째로, 특히 객체 지향적 언어에서는 문장 제일 처음에 나오는 단어가 변수(객체)인 경우가 많다. 절차적 언어에서는 배정문이나 기타 제어문이 아닌 나머지는 모두 함수 호출이며, 따라서 항상 동사가 문장 처음에 나오는 셈이 된다. 그러나 객체 지향 언어에서는 어떠한 동작을 수행할 때 우선 객체 변수가 나오고 그 다음에 그 객체의 메서드가 나오는 식이며, 따라서 문장 처음에 변수 즉 명사가 나오게 된다. 역시 알다시피 영어에서 문장 첫 단어는 대문자로 쓴다.

 

3. 관사

 

변수 이름에는 접두어가 붙기도 한다. C 중심 API에서는 흔히 변수의 자료형을 의미하는 접두어를 붙이곤 했으나, 엄격한 형검사를 수행하는 C++ 같은 언어에서는 굳이 변수의 이름에 자료형 정보를 추가할 필요는 없다고 생각한다. 굳이 접두어를 붙여야 한다면 자료형보다는 변수의 범위에 대한 정보를 제공하는 접두어가 더 유용할 것이다.

 

영어 문장에 좀 더 가까운 코드를 만들기 위해서라면, 변수의 범위를 ‘관사’로 표현하는 것이 바람직할 것이다. 예를 들어 함수의 지역 변수나 매개 변수에는 a(an)를, 전역 변수나 멤버 변수에는 the를 붙이는 식이다. 이는 일상 영어에서 a와 the의 일반적인 의미를 반영한 것이다. 예를 들어 an apple은 불특정한 하나의 사과를 뜻하나, the apple은 해당 맥락에서 이미 존재하고 있던 또는 화자들이 익히 알고 있는 특정한 사과를 뜻한다. 지역 변수는 해당 범위에 진입할 때 초기화되고 범위를 벗어나면서 사라진다는 점에서 a(an)를 붙이는 것이 의미가 있으며, 전역 변수는 전역 아래의 개별 범위들에서도 이미 존재하며 언제라도 접근할 수 있다는 점에서 the를 붙이는 것이 의미가 있다.

int getCurrentDistanceFromOrigin(int aX, aY)
{
  return getDistanceBetween( 
    Position(theOriginX, theOriginY), Position(aX, aY) 
  );
}

4. 형용사

명사에는 형용사가 붙기도 한다. 형용사는 명사에 대한 추가적인 정보를 제공한다. 영어의 경우 형용사는 명사 앞에 붙는다. 예를 들어 a red apple. 그렇다면 프로그래밍에서 형용사는 어떻게 표현될까? 형용사는 명사에 대한 추가적인 정보를 제공하기 위한 것이라는 점을 생각하면, 형용사는 객체의 상태를 의미하는 멤버 변수 또는 속성에 해당한다고 할 수 있다. 예를 들어 a red apple을 프로그래밍적으로 표현한다면,

class Apple
{
private:
  Color theColor;
public:
  void setColor(Color aColor) { theColor = aColor; }
};

...

Apple aApple;
aApple.setColor( Color("Red") );

이는 결국, 뭔가를 코드로 표현하고자 할 때 어떤 형용사를 사용하려고 한다면, 그 형용사 하나만 추가할 수는 없다는 뜻이 된다. 형용사를 사용하려고 하면, 사용하고자 하는 형용사들의 범주를 의미하는 ‘명사’를 정하고 그 범주를 명사에 속하는 하나의 멤버로 만들어야 한다는 뜻이 된다. 위의 예에서 red라는 형용사를 사용하기 위해서는 color라는 일반화된 ‘명사’를 생각해 낼 수 있어야 한다 – 사실 이는 객체 지향적 모델링의 기본이라 할 수 있다. 주어진 대상에서 중요한 명사들을 뽑아내는 것은 가장 기본적인 객체 식별 방법이기 때문이다. 5. 동사

영문법의 동사는 어떠한 행동, 행위를 의미하는 품사이다. 프로그래밍에서 동사는 어떠한 함수나 메서드로 표현된다. 또한 연산자들 역시 동사로 볼 수 있다. 코딩 스타일의 명명 규칙을 이야기한다면, 동사에 해당하는 함수 이름은 소문자로 시작하는 것이 바람직할 것이다. 물론 이는 변수를 대문자로 쓰는 것과 반대의 이유이다.

 

사실 함수 이름이 동사만으로 구성되는 경우는 적다. 보통 함수 이름은 하나의 동사구이며, 동사구는 동사에 부사나 명사(목적어나 보어 역할)들이 붙어서 만들어진다. 함수 이름에 대해서는 잠시 후에 좀 더 자세히 이야기하겠다.

 

6. 부사

 

부사는 동사에 추가적인 정보를 제공한다. move fast!는 움직이긴 움직이되 빨리 움직이라는 뜻이다. 명사-형용사의 관계와 마찬가지로, 부사를 프로그래밍에서 표현하려면 역시 새로운 명사가 필요하게 된다. 예를 들어 move fast라면 speed라는 명사가 필요하며, 이 때 speed는 객체의 한 멤버 또는 함수의 매개 변수가 될 것이다.

void move(int aSpeed)
{
...
}
...

move(MOVING_SPEED_FAST);

또는, 
class Ship 
{
...
  int theSpeed;
...
  void setSpeed(int aSpeed) { .... }
...
};

aShip.setSpeed(MOVING_SPEED_FAST); 
aShip.move();

 

특정 부사가 자주 붙는다면 함수 이름 자체에 그것을 반영할 수도 있다.

void Ship::moveFast()
{
  setSpeed(MOVING_SPEED_FAST); 
  move();
}

7. 변수 이름

대소문자 구성이 밑줄의 포함 여부 같은 코딩 표준 차원의 문제를 제외할 때, 변수의 이름을 짓는 것은 매우 간단한 일이다. 변수 이름을 지을 때는, 프로그램이 나타내고자 하는, 또는 시스템에서 스스로 존재하며 활동하는 물체나 대상, 간단히 말해서 ‘객체’를 가장 잘 나타내는 단어들을 고르기만 하면 되기 때문이다. 이를 다른 말로 하면, 변수의 이름을 짓기가 힘들다면(적절한 영어 단어를 고르는 차원의 문제가 아니라), 프로그램이 나타내고자 하는 대상에 대한 이해가 불완전하지는 않은 지 의심해 봐야 한다.

 

객체 지향 언어의 경우, 변수 이름은 곧 객체의 이름이 될 것이며, 객체의 이름은 당연히 클래스의 이름을 따르게 된다. 예를 들어 Apple의 한 인스턴스가 aRottenPear일 수는 없을 것이다. 이를 달리 말하면, 변수의 이름을 지으려면 클래스의 이름을 정해야 하고, 클래스의 이름을 정하려면 프로그램이 나타내고자 하는 대상이 어떤 요소들로 구성되어 있는 지를 알아야 한다.

 

문장의 관점에서 볼 때 변수는 주어나 목적어로 쓰이며, 따라서 변수 이름은 명사 또는 명사구의 형태가 된다. 명사구의 경우 형용사 또는 형용사로 쓰이는 동사구와 명사가 결합된 형태이다. 앞에서 예를 든 aRottenPear는 Pear 앞에 Rotten이라는 형용사가 붙은 것이다. 또는 aNormalizedVector 처럼 수동태의 동사가 붙을 수도 있는데, 물론 이 때 수동태의 동사는 형용사와 동일한 기능을 한다. 이러한 수식어가 붙은 변수들은 컴파일 시점에서 특정한 상태 또는 속성을 이미 알고 있는 객체를 가리킬 때 쓰인다. 예를 들어서 aRedApple이라는 변수의 Color 속성을 Red로 설정하는 것은 당연한 일이다.

Apple aRedApple;
aRedApple.setColor(“Red");

 

반면, 변수가 가리키는 객체의 속성을 구체적으로 알 수 없는 경우라면(즉 실행 시점에서 결정된다면) 클래스 이름을 거의 그대로 가져다 사용하는 것이 일반적이다. 아래의 anApple이 그러한 예이다.

bool IsAppleRed(Apple& anApple)
{
  return ( anApple.getColor() == "Red" );
}

 

단수 복수의 문제

하나의 객체나 하나의 값을 가리키는 변수는 당연히 단수 형태가 되어야 할 것이며, 배열이나 컬렉션이라면 복수가 되어야 할 것이다. 물론 여기에는 이론의 여지가 있다. 코드 문장 안에서 배열은 배열 그 자체로 쓰이기보다는 배열의 특정한 하나의 원소로 쓰이는 경우도 많다. 예를 들면:

 

CostOfAnAppleBox = Apples[i].Cost * Box.Size + Box.Cost;

 

이 때 Apples는 사과들, 즉 복수이지만 정작 코드에서 쓰이는 것은 i 번째의 사과 하나이다. 한 가지 우회책은 코드를 나누는 것이다.

 

Apple anApple = Apples[i]; ?CostOfAnAppleBox = anApple.Cost * Box.Size + Box.Cost;

 

물론 이러한 것들은 취향의 문제일 것이다(특히 성능 상의 효율과 관련해서).

 

8. 함수 이름

 

목적어

 

함수 이름은 동사 또는 동사구의 형태이다. 동사구에 대한 부분은 부사를 이야기할 때 잠깐 언급했다. 그 외에 동사의 목적어나 보어가 함수 이름에 포함될 수도 있다. 예를 들면 updateMatrix() 등. 그러나 만일 이러한 함수가 행렬을 매개변수로 받는다면, 그리고 인수로 제공된 변수의 이름이 제대로 붙어 있다면, 문장은 예를들어

 

upadate(currentModelViewMatrix);

 

형태가 될 것이므로 함수 이름에 굳이 Matrix라는 목적어를 붙일 필요가 없을 것이다.
법, 태, 인칭, 시제

 

영어의 동사는 태, 인칭, 시제에 따라 변형된다. 일반적으로는 동사의 기본형, 즉 능동태/1인칭/현재형을 쓰나, 3인칭을 쓰는 경우도 있다.

 

태, 인칭, 시제를 생각할 때 먼저 생각할 것은 ‘법(화법, mood)’이다. 하나의 코드 문장은 명령문으로 볼 수도 있고 평서문으로 볼 수도 있다. 앞에서 나온 Door.Open()의 예에서는 ‘문아 너를 열어라’라는 명령문으로 생각했다. 명령문의 경우 동사는 기본형이 쓰인다. 그러나 Door.Open()을 평서문으로, 즉 ‘문을 연다’ 또는 ‘문은 자신을 연다’로 해석하는 것도 얼마든지 가능하며, 특히 이 글의 전반적인 전제인 ‘소스 코드는 개발자 자신을 위한 서술이다’라는 것을 생각하면 평서문으로 보는 것이 더 합당하다.

 

코드 문장을 평서문으로 본다면, 객체들은 3인칭이므로 함수의 동사도 3인칭의 형태를 가지는 게 영문법에 좀 더 맞는 것 같기도 하지만, 이정도는 그냥 일상 언어와 프로그래밍 언어의 차이에 따르는 것으로 간주해 버려도 될 것이다.

 

어떠한 ‘여부’를 돌려주는 동사의 경우에는 be 동사나 have 동사 또는 기타 조동사들이 쓰이는데, 이런 경우에는 3인칭을 쓰는 것이 더 자연스럽다. 예를 들면 isFinished()나 hasUpdated() 등등. 이런 경우는 실제의 의미를 가진 동사는 수동태 또는 과거형이 된다(Finished, Updated).

 

get, have 같은 사역동사가 앞에 붙는 경우 주된 동사는 수동태가 되겠지만, 함수 이름에 사역동사는 쓰지 않는 것이 좋다고 생각한다. 예를 들어서 법선을 계산하는 함수의 경우 void normalize(Vector& aVector)면 충분하며, 굳이 void getNormalized(Vector& aVector)나 void haveNormalized(Vector& aVector)라고 할 필요는 없을 것이다. 이후에 좀 더 자세히 이야기하겠지만, getNormalized()는 정규화된 벡터를 ‘돌려주는’ 의미로 쓰일 때 더 합당하다. 즉 Vector getNormalized(Vector &aVector) 형태가 되어야 한다. 9. 구문적 정확성과 의미론적 정확성

 

(갑자기 곁가지이지만 초안이니까요 뭐-.- 나중에 좀 더 고민이 성숙되어서 ‘문장론’을 쓸 수 있게 된다면 이 절은 문장론의 일부가 될 것입니다.)

 

문장이 구문적으로 옳다는 말은 간단히 말하면 문법에 맞는다는 이야기이다. 이를 적격성(well-formedness)라고도 한다. 의미론적으로 옳다는 것은 말이 된다는 뜻이다. 이를 유효성(validity)라고도 한다. 예를 들어서 “Time flies like an arrow.”는 구문적으로나 의미론적으로나 문제가 없지만, “Thyme flies like an arrow.”은 구문적으로 옳으나 의미론적으로는 좀 이상하다(thyme은 향신료-.-). 올바른 문장은 구문적으로도 옳아야 하고 의미론적으로도 옳아야 한다. 대부분의 프로그래밍 언어들은 의미론적으로 옳으려면 구문적으로도 옳아야 한다. 즉 적격성은 유효성의 전제 조건이다. 반면 자연 언어는 그렇지 않은 경우도 있다. 예를 들어 ‘날으는 비행기’는 구문적으로 옳지 않지만(‘나는 비행기’가 옳다) 뜻은 통하기 때문이다.

 

컴파일 언어로 된 프로그램 문장의 구문적 정확성은 컴파일 시점에서 판단된다. 반면 의미론적 정확성은 실행 시점에서 판단된다. 구문적으로 정확하지 않은 문장은 컴파일 오류(소위 신택스 에러)를 내며, 이는 잡아내기 쉽다. 의미론적으로 정확하지 않은 문장은 버그 또는 논리적 오류가 되며, 컴파일 오류에 비해 잡아내기 어렵다.

 

오류를 피하고 디버깅을 줄일 수 있으려면 구문적 정확성이 의미론적 정확성을 최대한 보장해 줄 수 있어야 한다. C++처럼 형식(type)에 엄격한 언어는 그러한 보장이 비교적 쉽다. 이는 C의 printf()와 C++의 cout 을 비교하면 쉽게 이해할 수 있다.

 

표준 C 라이브러리의 함수 printf()의 원형은 다음과 같다.

 

int printf(const char* format, …) ;

 

첫 번째 매개변수만 형이 정해져 있고 그 다음부터는 가변인수들이다. 이는 구문적으로 옳지만 의미론적으로는 옳지 않은 문장에 대한 어떠한 보호도 없다는 뜻이다. 예를 들어:

 

printf(“결과: %s”, s);

 

이러한 문장은 s가 널 종료 문자열일 때에만 안전하다. 이 문장 자체는 구문적으로 옳지만 s가 널 종료 문자열이 아니면 의미론적으로 옳지 않게 된다. 반면 cout은 구문적 정확성이 의미론적 정확성을 (어느정도)보장한다.

 

cout << “결과:” << s;

 

만일 s가 int라면 s의 값이 문자열로서 출력된다. 널 종료 문자열이라면 s에 담긴 문자열 자체가 출력된다. s가 어떤 구조체나 클래스의 객체라면, 그 클래스가 << 연산자를 지원한다면 그에 맞게 출력이 되고 그렇지 않다면 컴파일 오류가 생긴다. 즉 의미론적으로 옳지 않은 문장이 구문적으로 거부되는 것이다. 변수, 함수 이름을 제대로 붙이고 영어 문장을 염두에 두면서 코드를 만드는 것은 구문적으로 옳은 코드가 의미론적으로도 옳은 코드가 되게 하는 데 일정한 도움이 될 것이다. (!!이 부분에서 좀 더 구체적인 사례가 필요!!)

 

10. 어휘

 

어휘는 간단히 말하면 단어, 숙어들의 집합이다. 프로그램의 어휘는 곧 프로그램에 쓰인 식별자들(클래스, 변수, 상수, 함수 등등의 이름들)의 집합에 해당한다. 프로그래머는 어휘가 풍부해야 하나 자신의 어휘력을 자랑할 필요는 없다. 어휘가 풍부하다는 것은 해당 언어의 키워드들이나 표준 라이브러리의 함수, 클래스들을 잘 알고 있다는 뜻이다. 예를 들어서 for만 알고 while을 모르는 사람이나 printf만 알고 cout은 모르는 사람보다는 둘 다 아는 사람이 더 나은 코드를 만들 수 있을 것이다.

 

반면 어휘가 풍부하다고 해서 한 프로그램에서 필요 이상으로 많은 종류의 단어들을 집어 넣을 필요는 없다. 예를 들어 서로 다른 자료형들에 대해 동등한 일을 하는 함수라면 같은 이름을 붙이고 매개변수 목록에만 차이를 두어서 중복시키면 된다.

 

일상 언어와 마찬가지로, 프로그래밍 어휘는 무한하다. 이는 대부분의 프로그래밍 언어들이 변수라는 개념을 지원하며, 또한 사용자 정의형이나 사용자 정의 함수를 지원하기 때문이다. 역시 일상 언어로 쓰여진 책이나 문서에서처럼, 특정한 한 프로그램 안에서의 어휘는 유한하다. 대부분의 프로그래밍 언어들에서 식별자들은 이미 소스 코드가 작성될 때 고정되기 때문이다. 대체로 프로그래밍에서는 (비유하자면)간결체, 건조체 같은 문체들이 선호되므로, 좋은 프로그램은 ‘풍부한 어휘 중에서 꼭 필요한 낱말들만으로 만들어진’ 프로그램이라고 할 수 있다.

 

11. 함수에 자주 쓰이는 동사들

 

함수들은 쓰임새에 따라 크게

 

1. 뭔가를 돌려주는 함수 2. 객체의 상태를 변경하는 함수 3. 외부 시스템에 대해 뭔가를 수행하는 함수

 

로 나눌 수 있다.

 

예를 들어 ?GetDC()는 1번, ?SetDC()는 2번, ?DrawText()는 3번에 해당한다고 할 수 있다. 물론 하나의 함수가 세가지 종류 중 둘 이상에 해당하는 경우도 있으며, 그런 경우 가독성을 위해서는 함수 이름에 둘 이상의 동사들이 들어가거나(예: ?SetCursorPositionAndDrawText(x, y, str) 등), 아니면 개별적인 함수들로 분리해야 할 것이다(?SetPosition(x, y) ; ?DrawText(); )

 

1. 뭔가를 돌려주는 함수에 주로 쓰이는 동사들(이하 돌려주는 동사)

 

가장 대표적인 동사는 get이다. 그런데 왜 return이 아닌가? 한 가지 이유는, return은 실행 흐름의 제어에 관련된 뜻으로 쓰이는 경우가 많기 때문이다. 또 다른 이유는 주어를 무엇으로 볼 것인가의 문제이다. 주어가 객체 자체라면 return이 더 자연스럽겠지만, 등호 오른쪽(rhs)의 변수를 주어로 본다면 get이 더 자연스럽다..

 

예:

 

Result = Vector1.returnSize(); // Vector1이 자신의 크기를 Result에게 ‘돌려준다’.

 

Result = Vector1.getSize(); // Result가 Vector1의 크기를 ‘얻는다’.

 

* get – 가장 흔히 쓰이며 의미하는 범위도 가장 넓다. ?GetDC(), getInstance(), getPosition() 등등. get은 아래에 나열된 다른 모든 돌려주는 동사들을 포괄한다. 그러나, get의 용도를 최대한 한정시킨다면 주로 ‘이미 계산되어 있는 객체의 속성 또는 시스템의 특정 상태를 돌려주며 상태, 시스템을 변경하거나 어떠한 복잡한 처리를 하지 않는’ 함수에 쓰는 것이 좋다고 본다. * retrieve – get에 비해 이것은 반환할 결과를 찾기 위한 처리(주로 검색)가 일어남을 암시한다. 필요하다면 이 동사는 search 같은 실제 수행 동사와 get으로 나눌 수 있다.

 

예:

 

aCheapestApple = anAppleBox.retrieveApple(CHEAPEST);

 

=>

 

anAppleBox.search(CHEAPEST); aCheapestApple = anAppleBox.getSearchResult();

 

* acquire – 제한된 자원을 독점적으로 확보한다는 뜻이 있다. 예를 들어 주어진 윈도우 또는 시스템 전체에 DC가 하나이며 ?GetDC()로 얻은 DC를 ?ReleaseDC()로 풀기 전까지는 혼자만 사용해야 하는 상황이라고 하자. 그런 경우라면 ?GetDC() 보다는 ?AcquireDC()가 더 적합하다. 반환값은 확보한 객체 자체나 포인터, 핸들 등이다. 반대말은 release나 unacquire(get에 대해 release를 사용한다면 acquire에 대해서는 unacquire를 사용해서 구분해 주는 것이 좋을 것임).

 

예:

 

AudioManager ?AudioMgr = new ?DxAudioManagerImpl(); … ?AudioDevice* anAudioDevice = ?AudioMgr.acquireDevice(); … ?AudioMgr.unacquireDevice(anAudioDevice);

 

* fetch – 돌려준 값을 담고 있는 컨테이너의 내부 상태가 변함을 의미한다. 주어진 컨테이너 안의 어떤 값을 가져오면 현재 값의 위치를 가리키는 포인터가 다음 값으로 이동하게 되는 경우에 fetch가 쓰인다. 데이터베이스 API에서 흔히 볼 수 있다. 스택의 pop과도 비슷하다. 또한 파일 시스템의 많은 함수들이 이러한 fetch 식으로 작동한다. 이는 moveNext와 get으로 분리될 수 있다.

 

예:

 

UnitList.reset(); pUnit1 = ?UnitList.fetch(); pUnit2 = ?UnitList.fetch(); => ?UnitList.moveFirst(); pUnit1 = ?UnitList.getUnit(); ?UnitList.moveNext(); pUnit2 = ?UnitList.getUnit();

 

* be 동사와 조동사 – 이들은 어떠한 상태 또는 여부를 뜻하는 부울 값을 돌려줄 때 주로 쓰인다.

 

예: while ( isDone ) { if ( currentUnit.hasWeapon() ) …. ….

 

}

 

2. 객체의 상태를 변경하는 함수에 주로 쓰이는 동사들

 

객체 또는 현재 범위의 어떤 값을 변경하는 함수들에 가장 흔히 쓰이는 동사는 set이다. 이런 동사들은 반환값이 없거나 또는 함수의 성공 여부를 알려주는 반환값을 가진다.

 

또는 변경되기 전의 상태나 변경된 후의 상태를 돌려주기도 하는데(Win32 API의 ?SelectObject() 등) 코딩의 양을 줄이는 데에는 좋지만 가독성 면에서는 혼란의 여지가 있다.

 

상태를 변경하는 함수에 쓰이는 동사들의 종류는 돌려주는 동사보다 훨씬 더 많다. 이는 상태를 변경하는 함수의 동사는 그 상태가 어떤 종류의 값인 지, 그리고 그 상태를 변경하는 데 어떤 작업이 필요한 지에 따라 얼마든지 다양해질 수 있기 때문이다.

 

따라서 사실 ‘주로 쓰이는’이라고 할만한 동사는 set 정도이다.

 

* set – 가장 흔하게 쓰인다. 설명이 필요없겠지만 굳이 부연 설명을 하자면, get의 반대물이라는 차원에서, 상태의 변경에 복잡한 처리가 일어나지 않는 비교적 단순한 코드로 된 함수에 적합하다.

 

3. 외부 시스템에 뭔가를 수행하는 함수에 주로 쓰이는 동사들

 

2번과 마찬가지로, 수행하는 작업이나 외부 시스템에 따라 다양한 동사들이 쓰이므로 주로 쓰이는 동사들이라고 할만한 것은 없다. 다만 게임의 경우 외부 시스템이 주로 그래픽, 오디오, 입력 장치들이므로 굳이 고른다면 몇 가지를 고를 수 있을 것이다.

 

그래픽 시스템

 

display – 실제의 시각적 변화를 일으킨다는 의미가 있다(버퍼 스왑 등 실제 비디오 메모리 갱신의 차원에서). 또는 아래의 동사들을 포괄하는 고차원 함수에도 쓰인다(특정 그래픽 API에 국한되지 않는…)

 

render – display와 비슷하나 내부 버퍼 메모리의 갱신만 의미할 수도 있다. 즉 render 후 상황에 따라 display가 수행되는 등. render부터는 특정 그래픽 API에 국한된 함수에 가까와진다. 목적어를 가질 수 있다(renderScene, renderBackground 등)

 

draw – 개별 데이터나 개별 개체를 그린다는 의미로 많이 쓰인다. Model.draw()나 drawText() 등. render 안에서 일어나는 경우가 많다.

 

put – 주로 2D에서 점이나 스프라이트를 ‘찍는다’는 의미로 쓰인다. 실제의 시각적 변화보다는 내부 메모리에서의 변화일 가능성이 크다. 3D에서는 별로 쓰이지 않음. 목적어를 가지는 경우가 많다(putPixel, putSprite 등)

 

flush – 그래픽이나 장면 데이터를 스트림으로 간주할 때 쓰인다. 지금까지 명령 버퍼 또는 프레임 버퍼에 쌓여 있는 데이터를 실제 장치로 밀어 내보낸다는 의미를 가진다.

 

오디오 시스템

 

play – 말 그대로 재생. 대상은 출력 버퍼보다는 음악, 음성 데이터를 감싸고 있는 좀 더 고차원의 객체일 가능성이 많다. 무엇을 재생하는가에 따라 목적어를 가질 수 있다(playSFX, playMusic 등).

 

perform – play보다 좀 더 고수준. ‘연주’를 뜻한다. 따라서 이 동사의 대상은 음성 데이터 차원보다 높은 연주 정보를 담는 객체가 될 것이다.

 

output – play 보다는 저수준. 바이트 차원의 파형 정보를 출력 버퍼로 내보내는 경우.

 

입력 장치

 

입력 장치의 특성 상 입력 장치에 대해 뭔가를 수행하는 경우는 별로 없고 입력 장치로부터 정보를 얻는 것이 대부분이므로 앞에서 말한 돌려주는 동사들이 주로 쓰인다. 예외는 진동 기능(force-feedback)일 것이다.

 

4. 애매한 동사들 – 돌려줄 것인가 말 것인가

 

예를 들어서 기본 대미지와 타격 가능성으로 실제 대미지를 계산하는 calculateDamage(?BaseDamage, ?HitProbability)라는 함수가 있다면, 계산된 실제 대미지는 어디로 가야할까? 두 가지 가능성이 있다.

 

1) aTotalDamage = calculateDamage(?BaseDamage, ?HitProbability); 2) aDamageCalculator.calculateDamage(?BaseDamage, ?HitProbability); aTotalDamage = aDamageCalculator.getDamage();

 

//또는 이런 식으로도 쓰일 것이다 player.?UpdateHP( aDamageCalculator.getDamage() );

 

이 예가 말하는 것은, 둘 중 어느 것을 택할 것인가는 대미지를 계산하기 위한 객체가 따로 필요할 것인가의 문제라는 점이다. 좀 비약하자면, 이는 ‘스타일의 문제는 객체 지향적 설계의 문제이기도 하다’를 의미하게 된다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다