Visual Basic · 기본
Visual Basic과 .NET 입문
프로시저 - Sub 와 Function
매개변수 ByVal·ByRef, Optional·ParamArray, 반환값, 오버로드
개발자KR · 원고 갱신
이 장에서 배우는 것
앞 장에서 조건과 반복을 이용해 비품의 대여 가능 여부를 판단하고 같은 작업을 여러 번 실행했다. 이제 대여 처리, 이용 금액 계산, 상태 출력처럼 역할이 분명한 작업을 이름 붙여 나눈다. 이렇게 나눈 작업을 프로시저(procedure)라고 한다. 프로시저를 사용하면 같은 계산을 여러 곳에 다시 쓰지 않고, 필요한 곳에서 이름으로 실행할 수 있다.
이 장에서는 작은 사무실의 비품 관리 프로그램을 여러 프로시저로 구성한다. 화면에 상태를 표시하는 작업과 값을 계산하는 작업을 구분하고, 호출한 쪽의 변수를 바꾸어야 하는 경우도 살펴본다. 모든 예제는 Option Strict On을 사용하는 Visual Basic 콘솔 앱을 기준으로 한다.
- Sub와 Function의 차이를 알고 작업의 목적에 맞게 선택한다.
- ByVal과 ByRef가 호출한 쪽의 변수에 미치는 영향을 구분한다.
- Optional과 ParamArray로 호출할 때 전달하는 값의 수를 조절한다.
- 반환값을 조건식과 계산식에 사용한다.
- 같은 이름의 프로시저를 매개변수 구성에 따라 오버로드한다.
문제 상황
사무실에는 대여 가능한 노트북이 세 대 있다. 담당자는 두 대를 빌려주고, 나중에 한 대를 돌려받는다. 프로그램은 처리할 때마다 남은 수량을 보여 주어야 한다. 이용 일수에 따른 금액도 계산하고, 여러 비품의 이용 금액을 합쳐야 한다.
이 작업을 모두 Main에 쓰면 수량 확인과 화면 출력, 금액 계산이 한곳에 섞인다. 다른 비품의 대여를 추가할 때 비슷한 조건문을 복사하기도 쉽다. 복사한 코드 중 한 곳에서만 수량 확인 조건을 고치면 비품마다 다른 규칙이 적용될 수 있다.
작업을 나누려면 먼저 각 작업의 입구와 출구를 정해야 한다. 대여 처리는 현재 수량과 요청 수량을 받아 성공 여부를 알려 준다. 성공하면 현재 수량도 줄인다. 금액 계산은 일수와 일일 금액을 받아 계산 결과를 돌려준다. 상태 출력은 비품 이름과 수량을 받아 화면에 표시한다. 이런 약속이 코드에 드러나면 Main은 전체 처리 순서를 보여 주는 장소가 된다.
이번 프로그램은 키보드 입력을 받지 않는다. 정해진 값을 사용하므로 실행할 때마다 같은 결과가 나온다. 대여 실패도 정상적인 결과로 취급한다. 수량이 부족하면 프로그램을 중단하는 대신 실패를 반환하고 다음 작업으로 넘어간다.
Sub와 Function으로 작업 나누기
Sub는 값을 돌려주지 않는 프로시저다. 화면 출력처럼 작업 자체가 목적일 때 사용한다. Function은 작업을 수행한 뒤 값을 돌려주는 프로시저다. 금액 계산이나 성공 여부 판단처럼 호출한 쪽에서 결과를 사용해야 할 때 알맞다.
프로시저를 실행하도록 요청하는 일을 호출(call)이라고 한다. 선언에 적힌 입력 항목은 매개변수(parameter)이고, 호출할 때 그 항목에 전달하는 실제 값이나 변수는 인수(argument)다. 다음 선언의 days는 매개변수이고, CalculateFee(3)의 3은 인수다.
Function CalculateFee(ByVal days As Integer) As Integer
Return days * 1000
End Function
Function 이름 뒤의 괄호에는 입력 항목을 적는다. 괄호 뒤의 As Integer는 반환하는 값의 데이터 형식을 뜻한다. Return은 뒤에 적힌 식의 값을 호출한 쪽에 돌려주고 해당 Function의 실행을 끝낸다. Return 아래에 문장을 더 적더라도 그 경로에서는 실행되지 않는다.
반환값은 변수에 저장하거나 다른 식에서 바로 사용할 수 있다. 여기서는 정수 금액을 사용하므로 반환 형식을 Integer로 정했다. 실제 통화 계산에 필요한 형식을 모두 논의하려는 예제는 아니다.
Dim fee As Integer = CalculateFee(3)
Console.WriteLine(fee)
Sub 선언에는 반환 형식을 적지 않는다. Sub도 입력을 받을 수 있으며, 실행을 마치면 호출 다음 문장으로 돌아온다. 입력이 없다면 선언과 호출 모두 빈 괄호를 사용한다.
Sub PrintAvailable(ByVal itemName As String,
ByVal count As Integer)
Console.WriteLine($"{itemName}: {count}대")
End Sub
이 Sub는 PrintAvailable("노트북", 3)처럼 호출한다. Console.WriteLine 안의 달러 기호로 시작하는 문자열은 중괄호 안의 값을 문자열에 끼워 넣는다. 프로시저의 반환값과 화면 출력은 서로 다르다. 숫자를 화면에 출력했다고 해서 그 숫자가 호출한 쪽의 계산식에 전달되는 것은 아니다.
| 구분 | 선언의 끝 | 호출한 쪽에서 받는 값 | 비품 관리 예 |
|---|---|---|---|
| Sub | End Sub | 없음 | 남은 수량 표시 |
| Function | End Function | 선언한 형식의 값 | 금액 또는 성공 여부 |
프로시저 안에서 선언한 변수는 그 프로시저 안에서 사용한다. 예를 들어 합계를 계산하는 Function 안의 total은 Main에서 직접 읽을 수 없다. Main에서 합계가 필요하다면 Function이 Return으로 돌려준 값을 받아야 한다. 이처럼 사용할 수 있는 코드의 범위를 변수의 범위라고 한다.
ByVal과 ByRef로 값 전달하기
매개변수에는 데이터 형식뿐 아니라 전달 방식도 적을 수 있다. ByVal은 전달받은 값을 매개변수에 넣어 사용한다. 이 장에서 다루는 Integer 매개변수에 새 값을 대입해도 호출한 쪽의 Integer 변수는 바뀌지 않는다. ByRef는 호출한 쪽 변수에 접근할 수 있게 전달하므로 매개변수에 대입한 결과가 그 변수에도 반영된다.
Visual Basic에서 전달 방식을 생략하면 기본은 ByVal이다. 여기서는 뜻을 읽기 쉽게 모든 매개변수에 전달 방식을 적는다. 단, ParamArray는 별도의 선언 규칙을 사용한다.
Sub PreviewBorrow(ByVal count As Integer)
count -= 1
Console.WriteLine($"미리보기 수량: {count}대")
End Sub
Main의 available이 3일 때 PreviewBorrow(available)을 호출하면 Sub 안의 count는 2가 된다. 그러나 Main의 available은 여전히 3이다. 위 코드는 수량을 실제로 줄이는 작업이 아니라 줄였을 때의 모습을 보여 주는 작업이다.
반대로 실제 대여 처리에서는 호출한 쪽의 수량을 바꾸어야 한다. 다음 Function은 남은 수량을 ByRef로 받고 요청 수량은 ByVal로 받는다. 성공 여부는 Boolean으로 반환한다. 한 번의 호출에서 수량 변경과 성공 여부 전달이 함께 일어난다.
Function TryBorrow(ByRef available As Integer,
ByVal requested As Integer) As Boolean
If requested <= 0 OrElse requested > available Then
Return False
End If
available -= requested
Return True
End Function
요청 수량이 0 이하이거나 남은 수량보다 많으면 False를 반환한다. 이 경로에서는 수량을 변경하지 않는다. OrElse는 왼쪽 조건이 참이면 오른쪽 조건을 평가하지 않고 전체 조건을 참으로 정하는 연산자다. 유효한 요청일 때만 수량을 빼고 True를 반환한다.
ByRef를 사용한다고 항상 변수가 바뀌는 것은 아니다. 프로시저 안에서 실제로 대입을 실행해야 바뀐다. 위 Function은 대여에 성공하는 경로에서만 수량을 바꾼다. 호출한 쪽에서는 If TryBorrow(available, 2) Then처럼 반환값으로 분기할 수 있다.
ByRef 매개변수에는 변경 결과를 받을 변수를 전달하는 것이 명확하다. 특히 Option Strict On에서는 이 예제처럼 매개변수와 인수 변수의 형식을 Integer로 맞춘다. 변환이 필요한 식이나 숫자 자체를 전달해서 호출한 쪽의 상태가 바뀌기를 기대하지 않는다.
또한 ByRef에 전달하는 변수 하나를 추가 괄호로 감싸면 값으로 전달하도록 강제할 수 있다. 예를 들어 TryBorrow((available), 2)는 Function 내부의 변경이 원래 available에 반영되지 않는다. 호출 전체에 필요한 괄호와 인수 하나를 감싼 추가 괄호를 구분해야 한다.
여기서 설명한 변경 여부는 Integer 변수에 값을 대입하는 경우를 기준으로 한다. ByVal을 모든 데이터에 대한 깊은 복사로 이해하면 안 된다. 객체를 다룰 때에는 변수에 새 값을 대입하는 것과 그 변수가 가리키는 대상의 내용을 바꾸는 것을 따로 살펴야 한다.
Optional, ParamArray, 오버로드
Optional로 기본값 제공하기
Optional은 호출할 때 생략할 수 있는 매개변수를 선언한다. 선언에는 기본값이 필요하다. 기본값은 실행 중에 호출한 쪽의 상태를 읽어 계산하는 값이 아니라 선언에 사용할 수 있는 상수 값이어야 한다. 비품 상태를 출력할 때 제목을 생략하면 "현재"를 쓰도록 만들 수 있다.
Sub PrintStatus(ByVal itemName As String,
ByVal count As Integer,
Optional ByVal label As String = "현재")
Console.WriteLine($"{label} {itemName}: {count}대")
End Sub
PrintStatus("노트북", 3)은 "현재"를 사용하고, PrintStatus("노트북", 3, "점검")은 "점검"을 사용한다. 선택 매개변수 뒤에 일반 필수 매개변수를 둘 수는 없다. 여러 Optional 매개변수를 선언한다면 뒤에 오는 매개변수도 Optional이어야 한다.
매개변수 이름을 적어 전달하는 방식도 있다. PrintStatus("노트북", 3, label:="점검")처럼 쓰면 마지막 문자열이 무엇을 뜻하는지 호출만 읽어도 알 수 있다. 이 장의 완성 코드에서는 위치에 따라 인수를 전달한다.
ParamArray로 여러 값 받기
ParamArray는 같은 형식의 값을 개수가 달라도 받을 수 있게 한다. 이용 금액 둘을 합치거나 셋을 합칠 때 각각 다른 프로시저를 만들지 않아도 된다. ParamArray 매개변수는 마지막에 하나만 둘 수 있고, ByVal이나 ByRef를 함께 적지 않는다. 같은 프로시저 선언에 Optional과 ParamArray를 함께 사용할 수 없다.
Function SumFees(ParamArray fees() As Integer) As Integer
Dim total As Integer = 0
For Each fee As Integer In fees
total += fee
Next
Return total
End Function
fees()의 괄호는 여러 Integer 값을 담는 배열 형식임을 나타낸다. 배열은 같은 형식의 값을 묶어 다루는 구조이며 다음 장에서 자세히 배운다. 지금은 전달한 금액들이 fees에 모이고, For Each가 그 값을 하나씩 fee에 넣어 반복한다는 점만 이해하면 된다.
SumFees(1200, 800, 500)은 2500을 반환한다. SumFees()처럼 금액을 하나도 전달하지 않아도 호출할 수 있다. 그때 반복문은 실행되지 않으며 초기값 0이 반환된다. ParamArray 앞에 필수 매개변수가 있다면 그 필수 인수까지 생략할 수 있는 것은 아니다.
오버로드로 같은 이름의 입력 형태 나누기
오버로드(overload)는 같은 이름의 프로시저를 매개변수의 수나 형식이 다르게 선언하는 것이다. 이용 금액 계산에서는 일수만 받는 형태와 일수 및 일일 금액을 받는 형태를 만들 수 있다. 컴파일러는 호출에 전달된 인수들을 보고 사용할 선언을 선택한다.
Function CalculateFee(ByVal days As Integer) As Integer
Return CalculateFee(days, 1000)
End Function
Function CalculateFee(ByVal days As Integer,
ByVal dailyRate As Integer) As Integer
Return days * dailyRate
End Function
CalculateFee(3)은 첫 번째 선언을 사용한다. 그 안에서 CalculateFee(days, 1000)을 호출하면 인수가 두 개이므로 두 번째 선언을 사용한다. 같은 이름을 호출하지만 입력 형태가 다르므로 자신을 끝없이 다시 호출하는 구조가 아니다. 기본 일일 금액을 정하는 일과 실제 곱셈을 하는 일이 구분된다.
반환 형식만 다르게 해서 오버로드할 수는 없다. 매개변수 이름만 바꾸거나 ByVal을 ByRef로 바꾸는 것만으로도 별개의 오버로드를 만들 수 없다. 이름이 같은 프로시저를 늘리기 전에 호출에 쓰일 입력 형태가 명확히 구분되는지 확인한다.
선택 매개변수와 오버로드를 함께 설계하면 어떤 선언을 호출하는지 읽기 어려워질 수 있다. 이 예제는 상태 출력에 Optional을 사용하고 금액 계산에는 인수 개수가 다른 두 선언을 사용해 각 기능의 목적을 분명히 한다.
완성 코드
새 Visual Basic 콘솔 프로젝트의 Program.vb 내용을 다음 코드로 바꾼다. 프로시저는 모두 Module Program 안에 두되 Main 안에 중첩해서 선언하지 않는다. Main이 먼저 나오고 그 아래에 호출할 프로시저가 있어도 된다. 선언을 위에서부터 실행하는 것이 아니라 Main의 문장 순서에 따라 호출이 이루어진다.
Option Strict On
Option Explicit On
Imports System
Module Program
Sub Main()
Dim itemName As String = "노트북"
Dim available As Integer = 3
PrintStatus(itemName, available)
PreviewBorrow(available)
PrintStatus(itemName, available, "미리보기 후")
If TryBorrow(available, 2) Then
Console.WriteLine("대여 성공: 2대")
Else
Console.WriteLine("대여 실패: 2대")
End If
PrintStatus(itemName, available, "대여 후")
If TryBorrow(available, 2) Then
Console.WriteLine("추가 대여 성공: 2대")
Else
Console.WriteLine("추가 대여 실패: 수량 부족")
End If
PrintStatus(itemName, available, "추가 요청 후")
ReturnItems(available, 1)
PrintStatus(itemName, available, "반납 후")
Dim basicFee As Integer = CalculateFee(3)
Dim customFee As Integer = CalculateFee(3, 1500)
Dim totalFee As Integer = SumFees(1200, 800, 500)
Console.WriteLine($"기본 이용 금액: {basicFee}원")
Console.WriteLine($"지정 이용 금액: {customFee}원")
Console.WriteLine($"합계: {totalFee}원")
Console.WriteLine($"금액 없는 합계: {SumFees()}원")
End Sub
Sub PrintStatus(ByVal itemName As String,
ByVal count As Integer,
Optional ByVal label As String = "현재")
Console.WriteLine($"{label} {itemName}: {count}대")
End Sub
Sub PreviewBorrow(ByVal count As Integer)
count -= 1
Console.WriteLine($"미리보기 수량: {count}대")
End Sub
Function TryBorrow(ByRef available As Integer,
ByVal requested As Integer) As Boolean
If requested <= 0 OrElse requested > available Then
Return False
End If
available -= requested
Return True
End Function
Sub ReturnItems(ByRef available As Integer,
ByVal returned As Integer)
If returned <= 0 Then
Return
End If
available += returned
End Sub
Function CalculateFee(ByVal days As Integer) As Integer
Return CalculateFee(days, 1000)
End Function
Function CalculateFee(ByVal days As Integer,
ByVal dailyRate As Integer) As Integer
Return days * dailyRate
End Function
Function SumFees(ParamArray fees() As Integer) As Integer
Dim total As Integer = 0
For Each fee As Integer In fees
total += fee
Next
Return total
End Function
End Module
이 프로그램은 수량 변경과 매개변수 전달을 관찰하기 위한 작은 모형이다. 전체 보유 수량이나 대여자별 기록을 저장하지 않으므로 ReturnItems는 반납 수량이 실제 대여 기록과 일치하는지 확인하지 않는다. CalculateFee도 음수 입력 검사는 하지 않는다. 완성 코드의 호출에서는 유효한 정수만 사용한다.
줄별 해설
Option Strict On은 형식이 맞지 않는 값을 암시적으로 좁혀 변환하는 등의 사용을 제한한다. Option Explicit On은 변수를 선언한 뒤 사용하도록 요구한다. Imports System은 Console처럼 System에 속한 이름을 짧게 쓸 수 있게 한다.
Module Program은 이 파일의 프로시저들을 담는다. Sub Main부터 실제 실행이 시작된다. itemName은 출력에 쓸 비품 이름이고 available은 호출 사이에 유지할 대여 가능 수량이다. 수량을 Main에 두었으므로 상태가 언제 바뀌는지 호출 순서로 확인할 수 있다.
첫 PrintStatus 호출은 label 인수를 생략한다. 따라서 "현재"가 출력에 사용된다. PreviewBorrow 호출은 available의 값 3을 count에 전달한다. count에서 1을 빼고 2를 표시하지만, 다음 PrintStatus는 여전히 Main의 값 3을 표시한다.
첫 TryBorrow 호출은 available을 ByRef로 전달한다. 요청한 2가 남은 3보다 작으므로 수량은 1이 되고 True가 반환된다. If는 그 반환값을 조건으로 사용하여 성공 문장을 출력한다. 이처럼 Function 호출 자체가 Boolean 조건식이 될 수 있다.
두 번째 TryBorrow 호출에서는 남은 1보다 요청 2가 크다. Function은 수량을 빼기 전에 False를 반환한다. Else의 실패 문장이 출력되고 다음 상태 출력에서도 수량은 1이다. 실패 경로에서 상태를 유지한다는 약속을 실행 결과로 확인할 수 있다.
ReturnItems는 available에 반납 수량 1을 더한다. ByRef이므로 Main의 수량도 2가 된다. 이 Sub의 Return에는 값이 없다. 반납 수량이 유효하지 않을 때 작업을 일찍 끝내는 용도다. 반환값이 필요 없는 Sub에서도 Return으로 실행을 종료할 수 있다.
basicFee에는 일수 하나를 받는 CalculateFee의 반환값이 들어간다. 이 선언은 기본 일일 금액 1000을 넣어 다른 선언을 호출한다. customFee는 처음부터 인수 둘을 전달해 3과 1500을 곱한 결과를 받는다. 두 변수의 값은 각각 3000과 4500이다.
totalFee에는 SumFees의 반환값이 들어간다. For Each는 전달된 금액 1200, 800, 500을 순서대로 더한다. total은 호출할 때마다 새로 0으로 시작하므로 앞선 호출의 합계가 다음 호출에 남지 않는다. 마지막 SumFees() 호출은 0을 반환한다.
각 End Sub와 End Function은 해당 선언의 끝을 나타낸다. Main이 끝나면 아래의 프로시저 본문을 차례로 실행하는 것이 아니다. 이 프로그램에는 기다리는 입력이나 계속 도는 반복문이 없으므로 마지막 출력 뒤에 실행이 종료된다.
실행 결과
macOS 또는 Linux에서 .NET 10 SDK가 준비되어 있다면 다음 명령으로 프로젝트를 만든다. 명령 실행 후 생성된 Program.vb를 완성 코드로 교체한다.
dotnet new console -lang VB -f net10.0 -o OfficeLoans
cd OfficeLoans
그다음 프로젝트 폴더에서 다음 명령을 실행한다.
dotnet run
프로그램이 출력하는 내용은 다음과 같다. 미리보기 뒤에는 3대가 유지되고, 성공한 대여 뒤에만 수량이 줄어든다.
현재 노트북: 3대
미리보기 수량: 2대
미리보기 후 노트북: 3대
대여 성공: 2대
대여 후 노트북: 1대
추가 대여 실패: 수량 부족
추가 요청 후 노트북: 1대
반납 후 노트북: 2대
기본 이용 금액: 3000원
지정 이용 금액: 4500원
합계: 2500원
금액 없는 합계: 0원
실무에서 자주 틀리는 것
아래 코드는 각각의 문제를 보여 주는 독립적인 조각이다. 같은 이름의 선언을 완성 코드에 그대로 추가하지 말고, 틀린 선언과 고친 선언을 비교한다.
수량을 바꾸면서 ByVal을 사용하는 경우
다음 Sub는 컴파일되지만 호출한 쪽 수량을 바꾸지 못한다. available에서 1을 빼려는 목적이라면 전달 방식이 맞지 않는다.
Sub BorrowOne(ByVal count As Integer)
count -= 1
End Sub
호출한 쪽 변수에 변경을 반영하도록 ByRef로 고친다. BorrowOne(available)을 호출하면 available이 1 줄어든다. 다만 이 조각에는 수량 부족 검사가 없으므로 실제 대여 처리에는 완성 코드의 검사를 유지한다.
Sub BorrowOne(ByRef count As Integer)
count -= 1
End Sub
Function에서 출력만 하고 반환하지 않는 경우
다음 코드는 계산 결과를 화면에 표시하지만 호출한 쪽에는 그 결과를 반환하지 않는다. 반환값이 없는 경로에 대한 컴파일러 경고도 발생한다. 화면 출력은 Return을 대신하지 않는다.
Function MakeFee(ByVal days As Integer) As Integer
Console.WriteLine(days * 1000)
End Function
계산 결과를 Return으로 전달하고, 출력할지는 호출한 쪽에서 정한다. 조건에 따라 경로가 나뉘는 Function에서도 필요한 모든 경로가 값을 반환하도록 작성한다.
Function MakeFee(ByVal days As Integer) As Integer
Return days * 1000
End Function
Optional을 필수 매개변수 앞에 두는 경우
선택 매개변수 뒤에 필수 매개변수를 두면 선언할 수 없다. 호출할 때 생략할 수 있는 항목을 뒤쪽에 배치한다.
Sub ShowCount(Optional ByVal label As String = "현재",
ByVal count As Integer)
Console.WriteLine($"{label}: {count}")
End Sub
count를 먼저 받고 label을 뒤에 두도록 고친다. ShowCount(2)는 기본 제목을 사용하고 ShowCount(2, "반납 후")는 지정한 제목을 사용한다.
Sub ShowCount(ByVal count As Integer,
Optional ByVal label As String = "현재")
Console.WriteLine($"{label}: {count}")
End Sub
반환 형식만 바꾸어 오버로드하는 경우
다음 두 선언은 입력 형태가 같아서 함께 선언할 수 없다. 결과를 Integer 변수에 넣을지 String 변수에 넣을지는 이 두 선언을 구별하는 기준이 되지 않는다.
Function FeeValue(ByVal days As Integer) As Integer
Return days * 1000
End Function
Function FeeValue(ByVal days As Integer) As String
Return $"{days * 1000}원"
End Function
작업 목적이 다르므로 이름을 나눈다. 계산 결과를 구하는 선언과 표시용 문자열을 만드는 선언이 호출에서도 구분된다.
Function FeeValue(ByVal days As Integer) As Integer
Return days * 1000
End Function
Function FeeText(ByVal days As Integer) As String
Return $"{FeeValue(days)}원"
End Function
한눈에 보기
| 기능 | 쓰는 목적 | 확인할 규칙 |
|---|---|---|
| Sub | 출력이나 상태 변경 수행 | 값을 반환하지 않는다. |
| Function | 계산 결과나 성공 여부 전달 | 반환 형식과 Return 값을 맞춘다. |
| ByVal | 입력값을 받아 사용 | Integer 매개변수의 대입은 원래 변수를 바꾸지 않는다. |
| ByRef | 호출한 쪽 변수 변경 | 어느 경로에서 대입하는지 확인한다. |
| Optional | 생략 가능한 입력 제공 | 기본값을 지정하고 필수 매개변수 뒤에 둔다. |
| ParamArray | 같은 형식의 여러 입력 처리 | 마지막에 하나만 두고 Optional과 함께 선언하지 않는다. |
| 오버로드 | 같은 작업의 입력 형태 구분 | 매개변수 수나 형식을 다르게 한다. |
작업을 나눌 때는 이름부터 정하기보다 무엇을 받고 무엇을 돌려줄지 먼저 적어 본다. 호출한 쪽의 변수를 바꾸어야 하는지도 함께 정한다. 이 세 가지가 분명하면 선언과 호출의 관계를 읽기가 쉬워진다.
연습 문제
- 완성 코드의 첫 TryBorrow 요청 수량을 2에서 4로 바꾼다. 두 대여 요청의 성공 여부와 반납 후 수량을 예상한다. 다른 호출은 그대로 둔다.
- 비품 이름을 받아 안내 문장을 출력하는 PrintReturnNotice를 작성한다. 두 번째 매개변수는 Optional ByVal returned As Integer = 1로 선언한다. PrintReturnNotice("프로젝터")가 "프로젝터 반납 안내: 1대"를 출력하게 한다.
- 각 비품의 대여 일수를 받아 가장 큰 값을 반환하는 MaxDays를 ParamArray로 작성한다. 모든 입력은 0 이상의 Integer라고 가정한다. 입력이 없으면 0을 반환하고, MaxDays(2, 5, 3)은 5를 반환하게 한다.
- 반납 처리를 TryReturnItems라는 Function으로 바꾼다. 첫 매개변수는 ByRef available As Integer, 두 번째는 ByVal returned As Integer로 둔다. 반납 수량이 0 이하이면 수량을 유지하고 False를 반환하며, 그 외에는 수량을 더하고 True를 반환하게 한다.
정답과 해설
1. 수량 부족 경로 확인
첫 요청은 남은 3대보다 많은 4대를 요구하므로 실패한다. 수량은 3으로 유지된다. 두 번째 요청은 2대이므로 성공하고 수량은 1이 된다. 이후 한 대를 반납하므로 마지막 수량은 2다. 두 요청의 성공 여부가 바뀌어도 마지막 수량은 원래 예제와 같다.
코드에서 요청 숫자만 4로 바꾸면 첫 성공·실패 안내 문자열에는 여전히 "2대"가 남는다. 출력도 올바르게 표현하려면 첫 If 안의 두 안내 문자열을 "4대"로 바꾼다. 계산에 사용하는 값과 그 값을 설명하는 문장이 서로 어긋나지 않는지 함께 살펴야 한다.
2. 기본 반납 수량을 가진 안내
Sub PrintReturnNotice(ByVal itemName As String,
Optional ByVal returned As Integer = 1)
Console.WriteLine($"{itemName} 반납 안내: {returned}대")
End Sub
필수 입력인 비품 이름을 앞에 두고 선택 입력을 뒤에 둔다. PrintReturnNotice("프로젝터")는 기본값 1을 사용한다. PrintReturnNotice("프로젝터", 2)는 2대를 표시한다. 이 Sub는 안내만 출력하며 실제 수량을 변경하지 않는다.
3. 여러 일수 중 가장 큰 값 반환
Function MaxDays(ParamArray values() As Integer) As Integer
Dim largest As Integer = 0
For Each value As Integer In values
If value > largest Then
largest = value
End If
Next
Return largest
End Function
largest는 지금까지 확인한 값 중 가장 큰 값이다. 더 큰 값을 만나면 교체한다. 입력이 없으면 초기값 0이 그대로 반환된다. 초기값을 0으로 둘 수 있는 이유는 문제에서 입력을 0 이상의 정수로 제한했기 때문이다. 음수도 허용하는 요구사항이라면 초기값과 입력이 없는 경우의 규칙을 다시 정해야 한다.
4. 반납 성공 여부 반환
Function TryReturnItems(ByRef available As Integer,
ByVal returned As Integer) As Boolean
If returned <= 0 Then
Return False
End If
available += returned
Return True
End Function
유효성 검사를 먼저 하고 수량 변경을 뒤에 둔다. 실패 경로에서는 available에 대입하지 않으므로 원래 수량이 유지된다. Main에서는 기존 ReturnItems 호출을 다음 코드로 교체할 수 있다.
If TryReturnItems(available, 1) Then
Console.WriteLine("반납 성공: 1대")
Else
Console.WriteLine("반납 실패: 수량 확인 필요")
End If
이렇게 바꾸면 기존 출력에 반납 성공 안내가 한 줄 추가된다. ByRef는 상태 변경을 전달하고 Boolean 반환값은 처리 결과를 전달한다. 두 통로의 역할을 구분하면 호출한 쪽에서도 상태와 안내를 일관되게 처리할 수 있다.
READER FEEDBACK
질문·의견
내용에 관한 질문이나 더 나은 설명을 위한 의견을 남겨 주세요. 오탈자는 위의 제보 양식이 더 빨리 반영됩니다. 이 댓글은 원래 게시글과 같은 자리에 쌓입니다.
댓글 0
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.