跳转到主要内容
메인 컨텐츠로 가기

한국 developerWorks  >  리눅스  >

보다 나은 프로그래밍으로 가는 길: 3장

루프, 깨끗한 코드, 펄 이디엄

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.


난이도 : 초급

Teodor Zlatanov, 프로그래머, Gold Software Systems

2001 년 12 월 01 일

dW 시리즈를 통해 보다 나은 펄 프로그래밍을 위한 완벽한 가이드를 제공하고 있다. 세 번째 시리즈에서는 펄 루프 신택스, 조건문, 깨끗한 코드 작성 방법 등을 설명한다. 펄을 좀 더 효과적으로 적용할 수 있는 방법을 배울 수 있다.

매일매일의 프로그래밍이 항상 매력적인 일만은 아니다. 펄은 단순화된 루프 구조체, postfix if/unless branching 등을 이용하여 좋은 코드를 쉽게 작성할 수 있다. 이 글에서, 펄 코딩 작업에 도움이 되는 특별한 방법을 제시한다.

펄 루프(Perl loop)

펄은 표준의 절차적 루핑 구조체를 갖고 있다: while() loop, do-until() loop, for() loop. 이러한 것들은 프로그래머들에겐 일상적인 것이다. 게다가, 펄은 값의 리스트에 대한 반복을 허용하고 테스트 조건 범위에서 반복을 허용한다.

위의 모든 루프와 if/unless 구조체는 Perl 5.004 부터 EXPRESSION loop 또는 EXPRESSION if/unless의 형식으로 역 노테이션(inverted notation)을 허용한다. 펄 신택스에 대한 자세한 설명은 perldoc perlsyn 페이지를 참조하기 바란다. 문서를 잘 만들 수 없다면 피하는 것이 좋다. 어떤 사람들에게는 혼돈스러울 수 있다. 특히 C/C++/Java에 익숙한 사람들은 혼란을 겪을 것이다.

while() 루프는 실행하기에 앞서 조건을 체크한다. 다음 예제는 인풋을 불러오는 일반적인 방법이다:


Listing 1. 간단한 while() 루프

while (
)
{
 # do something with the current input here
}

위의 코드는 인풋이 없다면 0회 실행할 것이다. 또는 인풋 만큼 실행 할 것이다. 코드 안에 next/last 문장을 사용하는 것 이외에 반복(iteration)의 수를 제어할 방법은 없다.

do-until() 루프는 while() 루프와 비슷하다. 하지만 내용들은 적어도 한번 실행될 것이다. 조건문이 이미 수행되어진 어떤 것에 의존하는 것은 좀 더 적절하다. 예를 들어:


Listing 2. 간단한 do-until 루프

my $i;
do
{
 # put values in @list here...
 ...
 $i = grep(/pattern/, @list);
}
until($i > 0);

위 예제에서, $i는 grep이 실행될 때 까지 값을 갖지 않는다. 특별한 임시 값을 할당하여 사용하는 대신, do-until 루프를 사용한다면 자연스러운 흐름을 보이게 된다. "until" 대신, "while"이 사용될 수 있다.

마지막으로, for() 루프는 do-until()과 while() 루프의 인자와 반대되는 것으로서 세 개의 인자들을 취한다. for() 루프는 C 프로그래머들이 선호하는 것이다--for() 루프 작성은 매우 쉽고 모든 것이 한 라인에서 수행된다. 바디(body) 없이 for() 루프를 작성하는 예도 있었다. 하지만 이와 같이 응축된 코드는 실제 생산환경에서는 환영받지 못한다.

for()는 초기화 섹션과 테스트 섹션을 가지고 있다. 이에 상응하는 while()와 for() 루프는 다음과 같다:


Listing 3. 상응하는 for()와 while() 루프

for ($i=0; $i++; $i < 10)
{
 # do something 10 times
}
$i=0;
while ($i < 10)
{
 # do something 10 times
 $i++;
}

For 와 foreach

for() 루프의 풍부한 문화적 유산(?) 때문에, foreach()를 대신 사용하기를 권한다. 여러 가지 이유가 있다:

  • foreach()는 더욱 좋게 보인다.
  • foreach()는 명확하다. 반면 for() 는 초보 프로그래머를 혼돈에 빠지게 한다.
  • 인터프리터로서 둘 사이 어떤 차이도 없다.

for() 루프를 반드시 사용해야 겠다고 생각한다면 사용 이유에 대해 충분히 문서화하길 바란다. for() 는 3개의 인자 형식에서 특별히 사용할 수 있지만 초보 프로그래머는 피하는 것이 좋다.

위 예제에서, $i는 0에서 9까지 증분되었다. for() 루프는 확실히 좀 더 깨끗해 보인다. 하지만 펄은 C의 명예에 의존하는 것이 행복하지 않다. 펄은 for() 루프를 위해 더욱 향상된 신택스를 정의하고 이것을 "foreach"에 앨리어싱 한다. 펄의 스탠드얼론 언어 구조체로서 "for"를 사용할 수 있다.




위로


For와 foreach: 충분히 활용하기

앞서 언급한 "향상" 이란, foreach() 루프는 list인 인자를 취한다는 것이다. 이는 매우 유용하다. 왜냐하면 list는 펄의 기본적인 데이터 타입이고 이것을 사용해서 많은 일들을 수행할 수 있다. 예를 들어 2에서 40까지의 list는 펄에서 "2..40"로 작성된다. list의 모든 엘리먼트의 경우 foreach() 루프의 바디는 한 번 실행된다. while()과 do-until() 루프를 이용하여, "next"와 "last" 키워드는 루프를 통해서 프로그램을 변경할 수 있다. "next"와 "last"는 다음 엘리먼트로 건너 뛰고 루프를 종료한다. 예제를 보자:


Listing 4. foreach() loop

foreach (1..100)
{
 # do something 100 times, $_ will be set to the current number
 print "Now on iteration $_\n";
}
foreach (1..20,101..120)
{
 # do something 40 times, $_ will be set to the current number
}
foreach my $counter (0..1)
{
 # do something twice, $counter will be 0, then 1
}
foreach my $i (0..1000)
{
 next unless $i%5;                      # next if this number is a multiple of 5
 print "$i is not a multiple of 5...\n";
 next unless $i%7;                      # next if this number is a multiple of 7
 print "$i is not a multiple of 7...\n";
 next unless $i%12;                     # next is this number is a multiple of 12
 # $i is now not a multiple of 12, 5, or 7
 print "$i is a lucky, lucky number to have met you...\n";
}
# here we use the Perl map operator to create a list of 100 even numbers
# see chapter 5 for details on the map and grep operators
foreach (map { $_ *= 2 } 0..99)
{
 print "Even number: $_\n";




위로


If, else, elsif, unless, or

일반적으로, 논리적 조건과 부울 대수(Boolean algebra)를 이해하고 사용하는 것 만큼이나 프로그래머에게 중요한 몇 가지가 있다. 논리적 분기(logical branching) 없이 프로그램을 작성하는 것은 불가능하다. 펄의 논리적 연산자는 C와 매우 유사하다. 연산자 신택스에 대해서는 perldoc perlop 페이지를 참조하기 바란다.

하지만 여러가지가 다르다. 펄은 일반적으로 if 또는 else block으로서 수식을 사용하지 않는다. 그들은 수식이 아닌 블록(block)이 되어야 한다. 다시말해서:


Listing 5. if 또는 else block

if (something) dothis();                # does NOT work
if (something)                          # usually works great
{
 dothis();
}
dothis() if (something);                # my favorite, but see 3.1
                                        # (it should be documented)

unless() 문장은 매우 유용하다. 이것을 사용하라. if()의 중간을 작성하고 있다면, 이것이 체크되기 전에 조건은 역행되어야 하고 대신 unless()를 사용해야 한다. 일반적인 예외: 여러분은 루프가 복잡해지는 것을 제어하는 논리적 문장인 elsif() branch가 필요하다. 그렇지 않으면, 인버트 부울 문장과 관련된 문제를 겪게 될 것이다.

펄의 unless() branching 로직은 언뜻 보기에는 당황스러워 보인다. unless()의 개념은 펄에서 유래한 것은 아니다. 펄 초보자들은 unless()의 힘에 압도당하는 느낌을 받을 수 있다. unless() 문은 다른 생각의 방식이다. 다음을 비교해보자:


Listing 6. If unless,부울의 기초

if (!eof()) ...
unless(eof()) ...
if (!clear && !ready) ...
unless(clear || ready) ...

유일한 트릭은 논리적 문장을 전체 역행시키는 것이다-- 모든 개별 조건은 논리적으로 부정된다. 부울 연산자 역시 역행된다. 기초적인 부울 대수는 배우기 쉽다. (참고자료). 반면 로직 버그는 쉽지도 재미있지도 않다. 부울 대수의 기초를 배우려면 시간을 투자해야만 한다. 하지만 여러분의 코딩이 향상될 것이다.




위로


깨끗한 코딩방법

"-w" 플래그를 사용하여 펄 인터프리터에게 경고를 하도록 명령한다. "-w" 스위치를 인터프리터 호출 라인에 추가함으로서 이것을 수행할 수 있다. 몇몇 프로그래머는 경고가 단지 개발자를 위한 것이며 최종 생산물에는 경고를 실행시켜서는 안된다고 생각하는 것 같다.

프로그래밍에 있어서 마지막 생산물이란 것은 없다. Jack Cohen도 "안정된 것(stable)은 죽은 것이다" 라는 말을 한 바 있다.

둘째, 경고는 사용자에게 어떤 것이 잘못되어가고 있고 미리 어떤 액션이 취해져야 한다는 것을 나타내고 있다.

셋째, 개발 사이클 동안에만 경고를 사용하는 것은 실제 소방기구로 소방 훈련을 하고 불이 났을 경우 종이컵을 사용하여 불을 끼얹는 것과 같다. 제품 개발 사이클 중 임의의 시점에서 안전 망을 제거하는 것은 이치에 맞지 않는다.

"use strict" 컴파일러 프라그마는 (매뉴얼 페이지의 "perldoc strict"와 "perldoc perlmodlib" 참조) C 컴파일러의 "-pedantic" switch와 어느정도 비슷하다. "perldoc strict" 명령어는 프라그마에 대한 더 많은 것을 설명한다. "use strict" 프라그마에서 가장 중요한 것은 모든 변수들이 사용되기 전에 정의되어야 한다는 것이다. 다음 예제는 "use strict"가 피해야 할 것이다:


Listing 7. "use strict"에 의해 피해야 할 에러

$this_variable_name_is_too_long = 1;
while (
)
{
 $this_variabel_name_is_too_long++;
}

에러는 버그가 나타날 때 까지 전혀 알려지지 않는다. "use strict"를 사용하면 위의 것이 전혀 컴파일 되지 않는다. 대신 다음과 같은 것이 작성되어야 한다:


Listing 8. listing 7의 픽스버전

my $this_variable_name_is_too_long = 1;
while (
)
{
 $this_variable_name_is_too_long++;
}

변수 이름으로 15문자 이상을 타이핑 해야 한다면 여러분은 무엇인가를 잘 못하고 있는 것이다.

상수를 정의하기 위해 함수를 사용하지 말아라. 어떤 부분 펄에서는 그것이 필요했지만 이제는 "use constant" 프라그마로 수행된다. 다음 예제에서, 주석 스타일과 화살표 정렬을 주목해보자. 겉으로 보이는 코드만 보아도 프로그래머가 코드의 모든 라인을 공들여 작성했다는 것을 보여준다. 어떤 에디터는 자동적으로 이러한 종류의 정열을 자동적으로 수행 할 수 있다.


Listing 9. 상수를 정의하기 위한 "use constant"

use constant CHILDREN => 3;             # 3 children per process
use constant PI       => 3.14;          # 2 digits of precision
use constant MESSAGE  => "Hello";       # a message

함수가 호출되기 전에 함수를 프로토타이핑 해라. 함수를 프로토타이핑 하는 것은 쉽다. "perldoc perlsub" 매뉴얼 페이지에서는 프로토타입을 잘 설명하고 있다. 프로토타이핑은 프로그래머에 있어서 좋은 습관이다. 왜냐하면 예상할 수 있고 특별한 경우 컴파일러 에러를 피할 수 있다. 함수를 변경할 때 프로토타입을 변경하는 것은 성가신 일이 될 수 있지만 근본적인 문제는 함수가 시작부터 잘 정의되어 있지 않다는 점이다. 코딩하기 전에 계획을 세우고 계획을 세우기 전에 생각하라. 코딩 계획에 대해 10분 정도만 생각한다면 코딩과 디버깅에 드는 시간을 절약할 수 있다.

펄은 스트링과 넘버 사이에서 자동으로 변환된다. 이 기능을 없앨 필요는 없다. 하지만 초보 펄 프로그래머는 새롭고도 흥미진진한 버그를 만들고 말 것이다. Perl FAQ (참고자료), Programming Perl , Learning Perl, "perldoc perldata" 매뉴얼("Scalar values" section)등을 참조하라.




위로


실습

  1. 1 부터 100까지 카운트하는 루프를 작성한다. 그리고,
    • 모든 짝수들을 프린팅한다.
    • 모든 홀수들을 프린팅한다.
    • 1 또는 2 또는 7로 끝나는 모든 숫자를 프린팅한다.
  2. 100 에서 1로 카운트다운하는 루프를 작성한다.
  3. 표준 인풋을 읽고 "hello"로 시작하는 모든 라인을 프린트하는 프로그램을 작성한다. 힌트: "perldoc perlrun" 매뉴얼 페이지를 참조하라. 그리고 루프를 작성하지 말고 펄의 빌트인 switch를 사용해보라.
  4. scalar가 non-zero, palindrome또는 234.98로 정의되었는지를 체크 할 논리적 조건을 작성하라. 펄 인터프리터와 "use strict" 컴파일러 프라그마에 대해 "-w" 플래그 경고를 이용하여 테스트하라. 그리고 경고 없이도 테스트하라. 경고를 이해했는가?
  5. 정상적인 (non-postfix) 표기법으로 다음을 작성하라. 가능하면 단순화 시켜라:
    • print if $debug;
    • $i++ unless $i > 10;
    • unless ($j && !$i) { $j += $i while <> };
    • next while <>;



위로


참고자료




위로


필자소개

Teodor Zlatanov는 1999년에 보스턴 대학에서 컴퓨터 공학을 전공했다. 졸업 후 Perl, Java, C, C++를 사용하여 프로그램을 개발하였다.





위로


기사에 대한 평가

매우 불만족 (1)
불만족 (2)
보통 (3)
만족 (4)
매우 만족 (5)




위로



    IBM 소개개인정보 보호정책문의