무엇을 만드나
사람은 식으로 보는 것을 컴퓨터는 글자로 받습니다.
주어진 문제
「3x + 5 = 20」 같은 일차방정식을 입력받아 x의 값을 구해 알려 주는 프로그램을 만듭니다.
손으로 푸는 것은 어렵지 않습니다. 5를 오른쪽으로 넘기고 3으로 나누면 x는 5입니다. 어려운 곳은 계산이 아니라 그 앞입니다. 컴퓨터가 받은 것은 식이 아니라 「3x + 5 = 20」이라는 글자 열한 개입니다. 그러니 이 글자 안에서 3과 5와 20을 꺼내는 일부터 해야 합니다.
| 단계 | 여기서 답할 질문 |
|---|---|
| 1 요구 분석 | 어떤 모양의 식까지 받아 줄 것인가 |
| 2 설계 | 글자 한 줄에서 숫자 셋을 어떻게 떼어 낼 것인가 |
| 3 구현 | 떼어 낸 것을 수로 바꿔 어떻게 계산할 것인가 |
| 4 테스트 | 어떤 식을 넣으면 무너지는가 |
먼저 개발 문서 양식을 내려받으세요
읽기만 하지 말고 직접 채워 보세요. 단계마다 자기 답을 적어 두면 그 기록이 요구 사항 명세서이자 설계서가 됩니다. 답을 보기 전에 먼저 적어 봅니다.
요구 분석과 설계는 언어와 상관없습니다. 두 양식의 앞부분은 같고, 구현을 적는 자리만 언어에 맞춰 다릅니다. 수업에서 쓰는 언어에 맞는 것을 받으면 됩니다. 이 강의노트의 코드 예시는 파이썬으로 되어 있습니다.
1단계 · 무엇을 만들 것인가
「일차방정식을 푼다」는 말은 아직 너무 넓습니다.
일차방정식이라고 해도 생김새가 여럿입니다. 어디까지 받아 줄지 정하지 않으면 만들 수가 없습니다. 되물어 봅시다.
되물어야 할 것들
- 어떻게 띄어 쓰나요? 「3x+5=20」처럼 붙여 써도 되나요
- 계수가 없으면? 「x + 5 = 10」처럼 3이 안 붙어 있을 수 있습니다
- 계수가 0이면? 「0x + 3 = 3」은 x를 구할 수 있나요
- 답이 안 떨어지면? 소수점 몇 자리까지 보여 주나요
- x가 오른쪽에 있으면? 「20 = 3x + 5」도 받아 주나요
다섯 가지가 전부 프로그램의 생김새를 바꿉니다. 하나라도 안 정하고 시작하면 코드를 적다가 되돌아오게 됩니다.
정리한 요구 사항
- 식은 한 줄로, 토막 사이를 한 칸씩 띄워 입력받는다. 예 — 3x + 5 = 20
- 좌변은 x가 있는 항 하나와 숫자 하나, 우변은 숫자 하나로 한다.
- 가운데 기호는 + 또는 − 둘만 받는다.
- 계수가 생략된 x도 받아 준다. 이때 계수는 1로 본다.
- 계수가 0이면 일차방정식이 아니라고 알리고 끝낸다.
- 답은 소수점 둘째 자리까지 반올림하고, 그 값이 딱 떨어지면 정수로 내놓는다.
- x가 오른쪽에 있는 식은 이번 판에서 받지 않는다.
1번이 뒤 단계를 통째로 정합니다
「한 칸씩 띄운다」는 사소해 보이지만 가장 큰 결정입니다. 띄어 쓰기로 정하면 공백에서 자르기만 하면 토막이 나뉩니다. 붙여 쓰기까지 받기로 하면 글자를 하나씩 순회하며 숫자와 기호를 구별해야 해서 프로그램이 몇 배로 커집니다. 요구 사항 하나가 난이도를 정한다는 것을 여기서 봐 두세요.
요구 사항 7번은 「하지 않을 것」을 적은 자리입니다. 이것을 적어 두지 않으면 나중에 어떤 말이 나올까요?
2단계 · 글자에서 숫자를 꺼낸다
한 줄로 들어온 글자를 어떻게 토막 내야 할까요?
| 토막 | 무엇을 정하는가 |
|---|---|
| 입력 | 한 칸씩 띄어 쓴 식 한 줄 — 글자로 들어온다 |
| 처리 | 토막을 나눠 계수 · 기호 · 상수 · 우변을 뽑고, 이항해 나눈다 |
| 출력 | x의 값 하나 — 정수 또는 소수 둘째 자리 |
공백에서 자르면 다섯 토막이 된다
요구 사항 1번에서 한 칸씩 띄어 쓰기로 정했기 때문에 공백을 기준으로 자르는 것만으로 토막이 깔끔하게 나뉩니다.
| 인덱스 | 토막 | 무엇인가 |
|---|---|---|
| 0 | 3x | 계수와 미지수가 붙어 있다 — 여기서 3을 더 떼어 내야 한다 |
| 1 | + | 기호. 이항할 때 부호가 뒤집힌다 |
| 2 | 5 | 좌변의 상수 |
| 3 | = | 쓰지 않는다. 그래도 자리는 차지하므로 건너뛴다 |
| 4 | 20 | 우변의 값 |
인덱스 3을 쓰지 않는다고 해서 없는 것이 아닙니다. 자리는 그대로 있으므로 우변을 가져오려면 인덱스 2 다음이 아니라 인덱스 4를 봐야 합니다. 여기서 한 칸씩 밀려 엉뚱한 값을 가져오는 실수가 자주 납니다.
「3x」에서 3만 떼어 내기
인덱스 0 토막은 아직 계수와 x가 붙어 있는 글자입니다. 마지막 글자가 언제나 x이므로, 끝 한 글자를 잘라 내면 계수만 남습니다. 「3x」에서 끝을 자르면 「3」, 「12x」에서 자르면 「12」입니다.
「x」 하나만 있으면 아무것도 안 남는다
요구 사항 4번에서 계수가 생략된 x도 받기로 했습니다. 그런데 「x」에서 끝 한 글자를 자르면 남는 것이 없습니다. 빈 것을 수로 바꾸려 들면 거기서 프로그램이 멈춥니다. 그러니 설계에 「남은 것이 없으면 계수를 1로 본다」는 줄이 반드시 들어가야 합니다. 설계에서 못 보고 지나가면 구현 뒤에 실행 오류로 만나게 됩니다.
푸는 순서
- 식을 한 줄 입력받는다.
- 공백에서 잘라 다섯 토막으로 나눈다.
- 인덱스 0 토막의 끝 글자를 떼어 낸다.
- 떼어 낸 것이 비어 있으면 계수는 1, 아니면 그 값을 수로 바꾼다.
- 인덱스 1 토막을 기호로, 인덱스 2 토막을 상수로, 인덱스 4 토막을 우변 값으로 삼는다.
- 계수가 0이면 「일차방정식이 아니다」를 알리고 끝낸다.
- 기호가 +이면 x = (우변 − 상수) ÷ 계수
- 기호가 −이면 x = (우변 + 상수) ÷ 계수
- x를 둘째 자리까지 반올림한 뒤, 딱 떨어지면 정수로, 아니면 그대로 내놓는다.
6번이 7번 앞에 있는 이유
계수가 0인데 나누기부터 하면 0으로 나누게 되어 그 자리에서 멈춥니다. 막는 검사는 언제나 위험한 계산보다 앞에 둡니다.
이항하면 부호가 뒤집힌다
7번과 8번의 부호가 기호와 반대인 것에 유의합니다. +5는 넘기면 −5가 되고 −5는 넘기면 +5가 됩니다. 손으로 풀 때 자연스럽게 하던 일이라 코드에서 뒤집는 것을 잊기 쉽습니다.
「2x - 4 = 10」을 「푸는 순서」 아홉 줄에 넣어 손으로 따라가 보세요. 7번과 8번 중 어느 줄로 가고, x는 얼마가 나오나요?
3단계 · 파이썬으로 옮기기
먼저 되는 것부터 만들고, 무너지는 경우를 막습니다.
먼저 스스로 적어 본 뒤에 코드를 보세요. 설계의 아홉 줄 중 4번과 6번, 9번을 빼고 먼저 만들어 봅니다. 가장 단순한 식 하나만 되게 하는 것이 첫 목표입니다.
일차방정식.기본.py
# "3x + 5 = 20" 처럼 토큰 사이를 한 칸씩 띄운 식을 받는다
equation = input()
# 공백에서 잘라 다섯 조각으로 나눈다
parts = equation.split()
# parts[0]="3x" parts[1]="+" parts[2]="5" parts[3]="=" parts[4]="20"
a = float(parts[0][:-1]) # "3x"에서 끝의 x를 떼면 "3"
op = parts[1] # "+" 또는 "-"
b = float(parts[2])
c = float(parts[4]) # parts[3]의 "="은 쓰지 않는다
# b를 우변으로 넘기고 a로 나눈다
if op == '+':
x = (c - b) / a
else:
x = (c + b) / a
print(x)「3x + 5 = 20」은 이걸로 풀립니다
공백에서 자르니 다섯 토막이 되고, 인덱스 0의 끝 글자를 떼니 「3」이 남고, 기호가 +이니 (20 − 5) ÷ 3 = 5.0이 나옵니다. 여기까지 되면 절반은 온 것입니다. 나머지 절반은 이 코드가 무너지는 경우를 막는 일입니다.
기본 코드가 못 지키는 요구 사항 셋
- 4번(계수 생략) — 「x + 5 = 10」을 넣으면 끝 글자를 뗀 자리에 아무것도 안 남아 수로 바꾸다가 멈춥니다.
- 5번(계수 0) — 「0x + 3 = 3」을 넣으면 0으로 나누다가 멈춥니다.
- 6번(답의 모양) — 답이 5여도 5.0이라고 나옵니다.
앞의 둘은 도중에 멈추는 실행 오류, 마지막은 멈추지 않고 틀린 답을 내는 논리 오류입니다. 셋 다 실행 전의 문법 검사는 멀쩡히 통과합니다.
일차방정식.완성.py
equation = input()
parts = equation.split()
# 계수가 생략된 "x + 5 = 10"이면 앞이 빈 문자열이 된다
a_str = parts[0][:-1]
a = float(a_str) if a_str else 1.0
op = parts[1]
b = float(parts[2])
c = float(parts[4])
if a == 0:
# a가 0이면 x가 사라져 일차방정식이 아니다. 나누기도 할 수 없다
print("계수가 0이면 일차방정식이 아닙니다")
else:
if op == '+':
x = (c - b) / a
else:
x = (c + b) / a
# 먼저 둘째 자리까지 반올림한다. 실수 계산은 3이 2.9999999999999996처럼
# 아주 조금 어긋날 수 있으므로, 반올림한 뒤에 딱 떨어지는지 본다
x = round(x, 2)
if x == int(x):
print(int(x))
else:
print(x)한 줄로 줄인 조건
계수를 정하는 줄이 낯설 수 있습니다. 「비어 있지 않으면 그 값, 비어 있으면 1」을 한 줄로 적은 것입니다. 여러 줄로 풀어 쓰면 이렇게 됩니다.
# 여러 줄로 쓰면 이렇게 된다
if a_str: # 빈 문자열이 아니면
a = float(a_str)
else:
a = 1.0
# 한 줄로 줄이면 이렇게 된다
a = float(a_str) if a_str else 1.0둘은 똑같이 동작합니다. 짧다고 언제나 나은 것은 아니고, 읽는 사람이 한눈에 알아볼 수 있을 때만 줄이는 편이 낫습니다.
완성 코드에서 계수가 0인지 보는 줄을 아래로 옮기면 어떻게 될까요? 설계 6번을 7번 앞에 둔 이유를 떠올려 보세요.
4단계 · 넣어 보고 확인하기
요구 사항 일곱 줄을 하나씩 확인해 봅시다.
이 프로그램은 요구 사항마다 확인할 값이 정해져 있습니다. 테스트 표는 그것을 한 줄씩 옮겨 놓은 것입니다. 「나와야 할 결과」는 실행해 보기 전에 손으로 풀어 적었습니다.
| 넣은 값 | 나와야 할 결과 | 무엇을 보려는 값인가 |
|---|---|---|
| 3x + 5 = 20 | 5 | 가장 단순한 식. 답이 딱 떨어진다 |
| 3x - 5 = 10 | 5 | 빼기 기호 — 이항할 때 부호가 뒤집히는지 |
| x + 5 = 10 | 5 | 요구 사항 4번 — 계수가 생략된 경우 |
| 2x + 1 = 4 | 1.5 | 요구 사항 6번 — 답이 소수인 경우 |
| 4x + 2 = 3 | 0.25 | 소수 둘째 자리까지 나오는지 |
| 0x + 3 = 3 | 안내 문구 | 요구 사항 5번 — 멈추지 않고 알리는지 |
| 0.1x + 0.2 = 0.5 | 3 | 요구 사항 6번 — 실수 계산이 2.9999999999999996처럼 조금 어긋나도 정수로 나오는지 |
결과가 어긋났다면 여기부터 봅니다
- 빼기 식에서만 답이 틀린다 — 이항할 때 부호를 뒤집었는지 봅니다. 두 갈래의 + 와 −가 바뀌기 쉽습니다
- 우변을 수로 바꾸는 줄에서 멈춘다 — 우변을 인덱스 4가 아니라 인덱스 3 토막에서 가져오고 있는지 봅니다. 「=」는 수로 바꿀 수 없습니다
- 「x + 5 = 10」에서 멈춘다 — 비어 있을 때 1로 보는 줄이 빠졌습니다
- 답이 늘 5.0처럼 나온다 — 딱 떨어지는지 먼저 보는 줄이 빠졌습니다. 계산은 맞는데 모양이 틀린 것입니다
표를 다 통과해도
요구 사항에 없는 입력은 여전히 남아 있습니다. 「3x+5=20」처럼 붙여 쓰거나, 「20 = 3x + 5」처럼 뒤집어 쓰거나, 토막 수가 다른 식을 넣으면 멈춥니다. 이것들은 버그가 아닙니다 — 1번과 7번에서 받지 않기로 정한 것입니다. 받고 싶어지면 코드가 아니라 1단계부터 고쳐야 합니다.
표에 한 줄을 더 보태 보세요. 계수가 생략되고 동시에 기호가 빼기인 식 — 「x - 3 = 4」는 무엇이 나와야 할까요?
더 해 보기
1단계에서 밀어낸 것을 하나씩 데려와 봅시다.
이 절의 과제는 전부 요구 사항을 넓히는 일입니다. 코드부터 열지 말고 1단계로 돌아가 요구 사항을 고쳐 적는 것에서 시작하세요. 테스트 표에도 줄이 늘어야 합니다.
음수 계수를 받는다
「-3x + 5 = 20」처럼 계수가 음수인 식입니다. 끝 글자를 떼는 방식은 대부분 그대로 통합니다. 먼저 왜 그런지 생각해 보세요 — 앞에 붙은 부호는 건드리지 않으니까요. 다만 계수를 생략한 「-x + 5 = 10」은 끝 글자를 떼면 「-」만 남아 수로 바꾸다가 멈춥니다. 이 경우를 어떻게 막을지도 요구 사항에 적어 보세요.
답의 자리를 정한다
「3x + 1 = 3」의 답은 끝없이 이어지는 소수입니다. 둘째 자리에서 반올림하면 0.67이 되는데, 이 값을 다시 식에 넣으면 3이 나오지 않습니다. 반올림이 무엇을 버리는지 생각해 보세요.
붙여 쓴 식도 받는다
「3x+5=20」을 받으려면 공백에서 자르는 방법을 통째로 버려야 합니다. 글자를 하나씩 순회하며 숫자와 기호를 구별해야 합니다. 요구 사항 한 줄이 프로그램을 얼마나 키우는지 직접 겪어 보는 과제입니다.
잘못된 식을 알린다
토막이 다섯이 아니거나 기호가 + · −가 아니면 멈추지 말고 「식을 다시 입력하세요」라고 알리게 만들어 보세요. 테스트에서 일부러 이상한 값을 넣어 보는 까닭이 여기 있습니다.
네 과제 가운데 가장 손이 많이 갈 것은 어느 것일까요? 그렇게 생각한 이유를 요구 사항 1번과 이어서 말해 보세요.