프로그램의 흐름을 제어해 봅시다.
todo : 존댓말로 바꾸기
음...... 흐름 제어(flow control). 여기서 지금까지 배웠던 모든 것을 한꺼번에 써먹어 볼 수 있다. 이번 장이 지난 메서드 장보다 더 쉽고 짧지만, 이 장에서 배운 내용은 프로그래밍의 새로운 세계를 열어줄 것이다. 이 장을 배우고 나면 진짜로 컴퓨터와 이런 저런 내용을 주고 받는 프로그램(interactive program)을 짤 수 있게 된다. 지금까지는 우리가 키보드에 입력한 내용만 가지고 컴퓨터가 결과물을 보여주었다. 하지만 이제 컴퓨터가 실제로 새로운 것을 할 수 있게 된다. 하지만 그 전에 프로그램 속의 객체들을 비교하는 법을 배워야한다. 그래서 해야 하는 것은...
두 값을 비교할 때, 어떤 메서드를 사용할까요?
이 부분은 빨리 끝내고 훨씬 멋진 내용이 기다리고 있는 다음 부분, 분기(Branching) 부분으로 넘어가자. 그래서, 하나의 객체가 다른 객체보다 큰지 작은지를 파악하기 위해서는 > 나 < 메서드를 사용하면 된다.
puts 1 > 2 puts 1 < 2 #실행결과 false true
쉽군. 같은 방식으로 우리는 >= 과 <= 메서드를 사용해서 객체들이 서로 크거나 같은지 또는 작거나 같은지도 알 수 있다.
puts 5 >= 5 puts 5 <= 4 #실행결과 true false
마지막으로, 두 객체가 같은지 다른지를 == 과 != 를 통해서 확인할 수 있다. ==는 "둘이 같은가?" 라는 뜻이고, !=는 "둘이 다른 가?"라는 뜻이다. 여기서 = 과 == 를 혼동하지 않아야 한다. = 는 변수가 특정 객체를 가리키도록 하며(할당), == 는 두 객체가 같은지를 묻는다. "이 두 객체가 같아?"라고 묻는다는 뜻이다.
puts 1 == 1 puts 2 != 1 #실행결과 true true
물론 문자열도 역시 비교할 수 있다. 문자열을 비교할 때 컴퓨터는 문자를 사전에 있는 순서(알파벳 순서)대로 비교한다. cat는 dog 보다 앞에 온다. 그래서 아래처럼 비교할 수 있다.
puts 'cat' < 'dog' #실행결과 true
그런데 여기에 작은 함정이 하나 있다. 컴퓨터는 대문자를 소문자보다 앞에 오는 것으로 파악하기 때문이다. (컴퓨터에서 쓰는 글꼴들도 이런 방식으로 저장된다. 그러니까 모든 대문자를 먼저 저장하고 그 다음에 소문자를 저장한다) 이렇기 때문에 'Zoo'가 'ant'보다 앞에 오는 단어가 된다. 결국 두 단어를 비교하면서 실제 사전에서 어떤 단어가 먼저 나오는지를 알고 싶다면, 비교하는 단어를 모두 소문자나 대문자로 바꾸고 비교해야 한다.
분기(Branching)로 넘어가기 전에 짚고 넘어갈 것 하나 더. 비교 메서드는 'true'와 'false'라는 문자열을 되돌려주는 것이 아니다. true 와 false라는 특별한 객체를 되돌려 주는 것이다. (물론 true.to_s 는 'true'라는 문자열을 돌려주며, 이는 puts 가 'true'라는 문자열을 출력해 주는 이유이다) true 와 false 는 프로그래밍을 하면서 계속 쓰이게 되는데... 음냐..
분기시키기(Branching)
분기는 매우 간단하지만 강력한 개념이다. 사실 너무 간단해서 굳이 설명할 필요도 없다. 직접 예제를 보면 된다.
puts 'Hello, what\'s your name?' name = gets.chomp puts 'Hello, ' + name + '.' if name == 'Chris' puts 'What a lovely name!' end #실행결과 Hello, what's your name? Chris Hello, Chris. What a lovely name!
그렇지만 만약 다른 이름을 입력한다면
#실행결과 Hello, what's your name? Chewbacca Hello, Chewbacca.
이것이 분기다. 만약 if 다음에 오는 내용이 참(true)라면, 프로그램은 if와 end 사이에 있는 코드를 실행한다. 만약 if 다음에 오는 내용이 거짓(false)이라면, 실행하지 않는다. 아주 명확하고 간단하다.
if 와 end 사이에 있는 코드에는 들여쓰기를 하였는데 이는 분기(branch)를 한 눈에 알아 볼 수 있게 하기 위해서이다. 어떤 프로그램 언어를 사용하든 대부분 모든 프로그래머들은 이렇게 들여쓰기를 한다. 이 간단한 예제에서는 별로 도움이 되지 않는 것처럼 보일 수 있지만 코드가 복잡할 때는 큰 도움이 된다.
가끔씩, if 다음에 오는 조건(표현식, expression)이 참(true)일 때와 거짓(false)일 때 서로 다른 코드이 실행되기를 바란다. 이럴때 else를 쓴다.
puts 'I am a fortune-teller. Tell me your name:' name = gets.chomp if name == 'Chris' puts 'I see great things in your future.' else puts 'Your future is... Oh my! Look at the time!' puts 'I really have to go, sorry!' end #실행결과 I am a fortune-teller. Tell me your name: Chris I see great things in your future.
이제 다른 이름을 사용해 보자.
#실행결과 I am a fortune-teller. Tell me your name: Ringo Your future is... Oh my! Look at the time! I really have to go, sorry!
분기(Branching)는 코드의 흐름 위에서 갈림길을 만난 것과 비슷하다. 누군가의 이름이 'Chris'(name == 'Chris')인 길로 갈까? 아니면 다른 길로 갈까?
그리고 마치 나무에서 가지(branch)가 뻗어나가는 것처럼, 하나의 분기(branch) 속에서 또 다른 분기(branch)가 있을 수 있다. (역자주: 이 부분은 branching의 역어를 정하고 꼭 다시 수정해야겠군요)
puts 'Hello, and welcome to 7th grade English.' puts 'My name is Mrs. Gabbard. And your name is...?' name = gets.chomp if name == name.capitalize puts 'Please take a seat, ' + name + '.' else puts name + '? You mean ' + name.capitalize + ', right?' puts 'Don\'t you even know how to spell your name??' reply = gets.chomp if reply.downcase == 'yes' puts 'Hmmph! Well, sit down!' else puts 'GET OUT!!' end end 알았어요.. 첫글자를 대문자로 적어주죠 뭐.. #실행결과 Hello, and welcome to 7th grade English. My name is Mrs. Gabbard. And your name is...? Chris Please take a seat, Chris.
가끔은 이 모든 if와 else, end들이 어디에 있는지 찾기 힘들 때가 있다. 그래서 나는 if 를 적을 때 end를 함께 적어 준다. 때문에 위의 프로그램을 짤 때 프로그램의 처음 모습은 이러했다.
puts 'Hello, and welcome to 7th grade English.' puts 'My name is Mrs. Gabbard. And your name is...?' name = gets.chomp if name == name.capitalize else end # 그리고 이 사이에 주석을 적어 넣는다. 주석은 컴퓨터가 읽지 않는 코드이다. puts 'Hello, and welcome to 7th grade English.' puts 'My name is Mrs. Gabbard. And your name is...?' name = gets.chomp if name == name.capitalize # She's civil. else # She gets mad. end
# 뒤에 오는 모든 내용은 주석으로 처리된다.(물론 문자열 안에 있지 않다면 말이다). 이렇게 주석을 달고 난 다음에, 주석을 실제 코드로 바꾼다. 어떤 사람들은 주석을 남겨 놓는 걸 좋아한다. 내 개인적인 생각으로는, 잘 짜여진 코드는 주석이 없이도 스스로 코드의 의미를 드러낼 수 있다고 생각한다. 나도 주석을 많이 사용했었는데, 점점 ruby 언어를 잘 쓸 수 있게 되면서 주석을 적게 쓰게 되었다. 많은 경우 주석이 걸리적 거린다고 느낀다. 이건 개인의 선택의 문제이고, 프로그램을 하면서 점점 발전하는 자신만의 스타일을 갖게 될 것이다. 아무튼, 그래서 내 코드의 다음 모습은 이렇다.
puts 'Hello, and welcome to 7th grade English.' puts 'My name is Mrs. Gabbard. And your name is...?' name = gets.chomp if name == name.capitalize puts 'Please take a seat, ' + name + '.' else puts name + '? You mean ' + name.capitalize + ', right?' puts 'Don\'t you even know how to spell your name??' reply = gets.chomp if reply.downcase == 'yes' else end end
다시 말씀드리지만, 여기에서 if, else, end는 한번에 적어주었습니다. 이런 방법은 코드를 작성하며서 "내가 어디에 있는지(무얼하고 있는지)"를 잘 알 수 있게 해준다. 또한 작은 한 부분에만 집중할 수 있게 해주기 때문에 프로그래밍을 더 쉽게 느껴지게 한다. 작은 한 부분이란 단순히 if와 end 사이를 채우는 것 같은 일이다. 이런 방법의 또다른 장점은 어떤 단계에서든 컴퓨터가 프로그램을 이해할 수 있다는 점이다. 위에서 내가 보여주었던 비완성 단계의 프로그램들도 모두 오류 없이 작동한다. 완성본은 아니지만, 분명 작동하는 프로그램이다. 덕분에 프로그램을 작성하면서 테스트를 할 수 있고, 지금까지 잘 하고 있는지, 어디를 더 작업해야 하는지 잘 알 수 있게 해준다. 그리고 모든 테스트를 통과했을 때, 프로그램이 완성되었다는 것을 알 수 있게 된다.
이 방법은 분기(branching)으로 프로그램을 짜는데 도움이 될 뿐 아니라, 다른 흐름 제어(flow control)을 작성하는데도 도움이 될 것이다.
순환문에 대해 알아봅시다.
우리는 종종 컴퓨터에게 같은 작업을 계속 시키고 싶을 때가 있다. 사실 이런 반복작업이야 말고 컴퓨터가 정말 잘하는 것이다. 컴퓨터에게 같은 것을 반복하게 만들려면, 그 반복 작업을 언제 멈춰야 할지를 말해줘야 한다. 컴퓨터는 절대 지루해하지 않기 때문에 멈추라고 말해주지 않으면 절대 멈추지 않는다. 이런 일이 벌어지지 않게 하기 위해서는 주어진 작업을 특정 조건이 true일 때만 반복하도록 말해줘야 한다. 이건 if가 작동하는 방법과 매우 비슷하다.
command = '' while command != 'bye' puts command command = gets.chomp end puts 'Come again soon!' #실행결과 Hello? Hello? Hi! Hi! Very nice to meet you. Very nice to meet you. Oh... how sweet! Oh... how sweet! bye Come again soon!
이것이 loop이다. (출력된 결과물의 시작에 빈 줄이 들어 있는 것을 눈치 챈 사람이 있을 것이다. 이 빈 줄은 첫 번째 gets 전에 있는 첫 번째 puts 때문에 생겼다. 이 첫 줄을 없애도록 프로그램을 짜려면 어떻게 해야 할까? 테스트 해 보자! 바꾼 프로그램이 첫 번째 빈 줄을 제외한 채로 위에 있는 프로그램과 완전히 똑같이 작동하는가?)
여러분들도 예상할 수 있듯이 loop를 사용하면 재미있는 일을 많이 할 수 있다. 하지만 작은 실수를 범하면 문제를 불러 일으키기도 한다. 프로그램이 무한 루프에 빠져 버리면 어쩌겠는가? 만약 무한 루프에 빠졌다고 생각되면 Ctrl(컨트롤) 키와 C 키를 함께 누르자.
loop를 가지고 놀기 전에 우리 작업을 좀 더 쉽게 해 줄 수 있는 몇 가지 것들을 먼저 배워보자.
로직에 대해 생각해 볼까요.
우리가 처음 작성했던 분기(branching) 프로그램을 다시 살펴 보자. 아내가 집에 들어와서 이 프로그램을 실행해 보았는데, 프로그램이 아내한테 "정말 멋진 이름이군요"라고 말해주지 않았다면? 난 아내의 기분을 상하게 하고 싶지 않으니 (또는 방에서 쫓겨나서 거실에서 자고 싶은 생각이 없으니), 프로그램을 다시 작성해야겠다.
puts 'Hello, what\'s your name?' name = gets.chomp puts 'Hello, ' + name + '.' if name == 'Chris' puts 'What a lovely name!' else if name == 'Katy' puts 'What a lovely name!' end end #실행결과 Hello, what's your name? Katy Hello, Katy. What a lovely name!
자, 잘 작동한다... 하지만 이건 잘 짜여진 프로그램이 아니다. 왜냐고? 내가 프로그램을 배우면서 배운 최고의 법칙은 DRY 법칙(Don't Repeat Yourself, 반복하지 마라)이다. 난 이 법칙이 왜 그렇게 중요한지에 대해 작은 책도 하나 쓸 수 있다. 이 프로그램의 경우, puts 'What a lovely name!' 를 반복했다. 이게 왜 중요할까? 그러니까, 내가 이 줄을 다시 쓰면서 맞춤법을 틀렸다면? 내가 두 줄 모두의 'lovely'를 'beautiful'로 바꾸고 싶다면? 기억하겠지만, 나는 게으르다. 기본적으로 내 프로그램이 'Chris'나 'Katy'를 받았을 때 같은 일을 하기를 원한다면, 프로그램은 실제로 같은 일을 해야 한다.
puts 'Hello, what\'s your name?' name = gets.chomp puts 'Hello, ' + name + '.' if (name == 'Chris' or name == 'Katy') puts 'What a lovely name!' end #실행결과 Hello, what's your name? Katy Hello, Katy. What a lovely name!
훨씬 나아졌다. 이렇게 작동하게 만들기 위해서 나는 or 를 사용했다. or 이외의 또 다른 논리연산자로는 and 와 not 이 있다. 이런 논리연산자를 사용할 때는 괄호를 같이 쓰는 것이 좋다. 이것들이 어떻게 작동하는지 살펴보자.
iAmChris = true iAmPurple = false iLikeFood = true iEatRocks = false puts (iAmChris and iLikeFood) puts (iLikeFood and iEatRocks) puts (iAmPurple and iLikeFood) puts (iAmPurple and iEatRocks) puts puts (iAmChris or iLikeFood) puts (iLikeFood or iEatRocks) puts (iAmPurple or iLikeFood) puts (iAmPurple or iEatRocks) puts puts (not iAmPurple) puts (not iAmChris ) #실행결과 true false false false true true true false true false
논리연산자들 중에서 살짝 헷갈리는 건 or 이다. 영어에서 or (한국어의 '또는') 는 "이것 또는(or) 저것, 하지만 둘 다는 아님"을 뜻한다. 예를 들어, 엄마가 "디저트로 파이 또는 케이크를 먹어도 된다"라고 말했면, 둘 다 먹으면 안 된다는 뜻이다. 하지만 컴퓨터에서는 or(또는) 가 "이것 또는 저것, 또는 둘 다"를 뜻한다. (다른 말로 하자면, "적어도 하나는 true"가 된다.) 이것 때문에 컴퓨터가 엄마보다 더 재미있다.
프로그램 만들어보기
"벽에 맥주가 99병이 있어요..." 사랑받는 곡을 (번역 마저 안 된듯~ 약간 보완하기~)
"난 알아요."
좋아하는 음악의 가사를 보여주는 프로그램을 작성해 보세요. 왜 있잖아요, 소풍갈 때 꼭 나오는 노래요.
'귀먹은 할머니' 프로그램을 작성하세요.
여러분이 할머니에게 말할때마다(뭘 입력하건 간에) "응? 크게말해, 손주녀석아!"라고 대답하죠. 소리칠 때까지는(모두 대문자로 입력할때) 그렇죠. 당신이 소리치면 알아들으시고 '아니, 1938년부터가 아니야!"라고 응답하시죠. 프로그램을 믿음직스럽게 만들기 위해 할머니가 매번 다른 연도를 말씀하도록 하세요. 아마도 1930년에서 1950년 중 무작위로 연도를 정하세요. (이 부분은 선택이고 메서드 장 끝에 나오는 난수 발생기를 읽었다면 쉬울 거에요.) 여러분이 'BYE'라고 소리치기 전까지 대화가 끝나지 않게 하세요.
힌트: chomp를 까먹지 말아요.! 'BYE'가 엔터랑 있는 건 'BYE'만 있는 것과는 다르죠.
힌트 2: 반복해서 생기는 부분이 어디인지 생각하세요. 반복되는 부분은 루프문 안에 있어야만 해요.
귀먹은 할머니 프로그램을 확장해보세요.
만약 할머니가 여러분이 가기를 원하지 않는다면? 여러분이 'BYE'라고 소리쳤는데 할머니가 못들은 척해요. 앞서 작성한 프로그램을 변경해서 연속으로 세번 'BYE'를 외쳐야 한다고 해보세요. 프로그램을 테스트해보세요. 'BYE'를 세번 외쳤지만 연속적으로 안했다면 계속 할머니랑 대화를 해야 되요.
윤년
시작하는 연도와 끝나는 연도를 물어보는 프로그램을 작성하세요. 그리고 사이 기간에 있는 모든 윤년을 출력하도록 하세요.(만약 해당연도가 윤년이라면 시작, 끝연도를 포함해서요.) 윤년은 1984년이나 2004년처럼 4로 나눠져요. 그렇지만 100으로 나눠지는 연도는 윤년이 아니에요. 1800년이나 1900년처럼요. 1600년이나 2000년처럼 400으로 나눠지면 윤년이에요.(다소 혼란스러울 거에요. 그렇지만 겨울 중반에는 7월이 있다만큼은 아니죠.)
6장을 마무리하며
여기까지 끝냈다면 잠시 쉬어요! 여러분은 이미 많은 내용을 배웠어요. 축하해요! 컴퓨터가 할 일을 알려줄 수 있는 것이 많다는 점이 놀랍죠? 진도를 더 나가면 여러분은 어떤 것이던 프로그램으로 작성할 수 있어요. 농담이 아니에요! 루프와 분기를 몰라서 할 수 없었지만 지금 할 수 있는 내용을 보세요. 지금부터 새로운 종류의 객체를 배워봐요. 다른 객체를 목록으로 유지하는 객체를 배열(Array)이라고 해요.