예외 처리와 파일 입출력
이 장에서 배우는 것
앞 장에서 비품 목록을 조건에 맞게 골라 보았다. 그러나 프로그램이 끝나면 메모리에 있던 목록도 사라진다. 다음 실행에서도 대여 상태를 이어 가려면 목록을 파일에 저장하고 다시 읽어야 한다. 파일은 프로그램 밖에 있으므로 읽는 순간 없어지거나, 권한 때문에 열리지 않거나, 사람이 내용을 잘못 고칠 수 있다. 이번에는 이런 상황을 구분해 처리하면서 비품 목록을 저장하고 읽는다.
예외(exception)는 실행 중 작업을 정상적으로 이어 갈 수 없을 때 전달되는 오류 정보다. 예외 처리의 목적은 오류를 숨기는 데 있지 않다. 어떤 작업이 실패했는지 알리고, 열어 둔 자원을 정리하며, 잘못된 자료가 정상 자료처럼 사용되지 않게 하는 데 있다.
- Try…Catch…Finally로 정상 처리, 오류 처리, 마무리 작업을 나눈다.
- Using으로 파일을 읽고 쓰는 객체의 자원을 정리한다.
- File과 Path를 이용해 운영체제에 맞는 경로를 만들고 파일을 다룬다.
- 정해진 CSV 형식으로 비품 목록을 저장하고 검증하며 읽는다.
- 사용자 정의 예외로 잘못된 자료의 행 번호와 이유를 전달한다.
문제 상황
작은 사무실에서 노트북과 프로젝터를 빌려 준다고 하자. 담당자가 프로그램을 종료했다가 다시 실행했는데 대여 상태가 모두 초기화되면, 실제로 빌려 준 비품을 다시 대여 가능한 것으로 표시할 수 있다. 따라서 비품 번호, 이름, 대여 가능 여부를 파일에 남겨야 한다.
파일을 남기는 것만으로 문제가 해결되지는 않는다. 담당자가 저장 파일을 편집하면서 비품 번호에 문자를 넣을 수도 있다. 공유 폴더의 쓰기 권한이 바뀔 수도 있다. 마지막 행만 잘못되었는데 앞부분을 읽었다는 이유로 전체 목록을 정상이라고 판단하면, 일부 비품이 빠진 목록으로 업무를 이어 가게 된다.
이번 예제는 목록을 저장하고 읽은 뒤, 마지막에 일부러 잘못된 행을 추가한다. 다시 읽을 때 그 행을 발견하고 사용자 정의 예외로 알린다. 실행할 때마다 별도의 임시 폴더를 사용하므로 이전 실행의 파일 내용에 영향을 받지 않는다. 학습용 임시 파일은 마지막에 지운다. 실제 업무용 저장 파일을 지우는 설계와는 구별해야 한다.
실패를 구분하고 마무리하기
Try와 Catch의 역할
Try에는 실패할 수 있는 작업을 넣는다. 작업 중 예외가 발생하면 Try의 나머지 문장은 실행하지 않고, 그 예외를 처리할 수 있는 Catch로 이동한다. Catch 뒤에는 처리하려는 예외의 형식을 적는다. 파일이 없다는 오류와 자료 내용이 잘못되었다는 오류는 대응 방법이 다르므로 구분해 다루는 편이 낫다.
Catch가 여러 개라면 위에서 아래로 검사한다. 더 구체적인 형식을 먼저 적어야 한다. 예를 들어 FileNotFoundException은 IOException에서 파생된 형식이다. IOException을 먼저 받으면 파일이 없는 경우도 그곳에서 처리되므로, 뒤에 둔 구체적인 처리가 의미를 잃는다.
Catch는 오류가 난 줄로 돌아가 재시도하는 문장이 아니다. 예외를 처리한 뒤에는 해당 Try…Catch 구문의 다음 문장으로 진행한다. 다시 시도하려면 반복이나 별도 호출로 그 동작을 직접 작성해야 한다.
Finally와 Using은 무엇을 정리하는가
Finally에는 정상 완료 여부와 관계없이 해당 Try 구문을 떠날 때 실행할 마무리 작업을 적는다. 이번 프로그램에서는 임시 폴더를 지운다. 다만 프로세스 강제 종료나 전원 중단에서도 Finally가 실행된다고 가정해서는 안 된다.
파일을 읽고 쓰는 객체는 운영체제가 관리하는 파일 자원을 사용한다. 이런 자원은 사용이 끝났을 때 해제해야 한다. 자원을 해제하는 약속인 IDisposable을 구현한 객체는 Using으로 다룰 수 있다. Using 블록을 벗어나면 Dispose가 호출된다. 블록 안에서 예외가 발생해도 이 정리 절차가 수행된다.
StreamWriter는 문자를 파일에 쓰는 객체이고 StreamReader는 파일의 문자를 읽는 객체다. 쓰기 객체에는 아직 파일로 전달되지 않은 내용이 임시로 남아 있을 수 있다. Using이 끝나면 그런 내용을 내보내고 파일 자원을 닫는다. 따라서 저장 함수가 끝난 뒤 읽기 함수를 호출하면 쓰기 객체가 이미 정리된 상태다.
| 구문 | 맡은 일 | 이번 예제의 사용 |
|---|---|---|
| Try | 실패할 수 있는 작업 실행 | 폴더 생성과 저장·읽기 |
| Catch | 선택한 예외에 대응 | 잘못된 행의 이유 출력 |
| Finally | 구문을 떠날 때 마무리 | 임시 폴더 삭제 시도 |
| Using | 객체가 가진 자원 해제 | 읽기·쓰기 객체 닫기 |
경로와 CSV 형식 정하기
경로(path)는 파일이나 폴더의 위치를 나타내는 문자열이다. Path.Combine은 폴더 경로와 파일 이름을 결합한다. Windows에서 사용하는 역슬래시를 문자열에 직접 넣으면 macOS와 Linux에서는 의도한 경로가 되지 않을 수 있다. 이 예제는 Path.GetTempPath로 임시 폴더의 위치를 얻고, Path.Combine으로 하위 폴더와 파일 경로를 만든다.
File은 파일 전체를 읽거나 쓰기, 내용 추가, 삭제 같은 작업을 제공한다. 파일을 한 행씩 처리할 때는 StreamReader와 StreamWriter가 편리하다. 이번에는 저장과 읽기에 이 두 객체를 사용하고, 오류 실험을 위한 행 추가에 File.AppendAllText를 사용한다.
CSV는 쉼표로 값을 나누는 텍스트 형식이다. 일반적인 CSV에서는 값 안의 쉼표와 따옴표, 줄바꿈을 표현하기 위한 인용 규칙도 사용한다. 따라서 모든 CSV를 Split 하나로 읽을 수는 없다. 여기서는 학습할 범위를 분명히 하기 위해 이름에 쉼표, 큰따옴표, 줄바꿈을 허용하지 않는 제한된 형식을 정한다. 이 규칙을 검사하므로 단순한 쉼표 분리가 가능하다.
| 항목 | 규칙 | 예 |
|---|---|---|
| 첫 행 | 정해진 제목과 정확히 일치 | Id,Name,Available |
| 비품 번호 | 양의 Integer, 중복 금지 | 101 |
| 이름 | 빈 이름·쉼표·따옴표·줄바꿈 금지 | 노트북 |
| 대여 가능 여부 | True 또는 False와 정확히 일치 | True |
문자는 UTF-8로 저장한다. UTF-8은 한글을 포함한 여러 문자를 바이트로 표현하는 문자 인코딩이다. 저장 파일 앞에는 문자 인코딩을 알리는 표식인 BOM을 붙이지 않는다. 읽기 객체는 BOM이 있는 UTF-8 파일도 읽을 수 있도록 구성한다.
비품 번호는 사용자의 지역 설정에 영향을 받지 않는 InvariantCulture로 변환한다. 이 형식에서는 숫자 사이의 구분 기호나 앞뒤 공백을 허용하지 않는다. 파일 형식은 화면에 보기 좋게 표시하는 방식과 별도로 정해야 다른 환경에서도 같은 의미로 읽을 수 있다.
자료 오류를 표현하는 예외 만들기
파일을 열지 못한 문제는 .NET이 제공하는 예외로 알 수 있다. 반면 “비품 번호는 양수여야 한다”는 우리 프로그램이 정한 규칙이다. 이런 규칙 위반을 구별하려고 CsvDataException이라는 사용자 정의 예외를 만든다. Exception을 상속하고, 생성자에서 행 번호와 이유를 받아 Message에 넣는다.
읽기 함수는 오류 문장을 화면에 출력하지 않는다. 대신 예외를 발생시키고, 호출한 쪽에서 화면 표시를 결정한다. 이렇게 하면 같은 읽기 함수를 다른 프로그램에서도 이용하기 쉽다. Throw는 예외를 발생시키는 문장이다. 이번 함수에서는 잘못된 행을 발견하는 즉시 Throw로 읽기를 중단한다.
숫자 변환에는 Integer.TryParse를 쓴다. 변환할 수 없는 입력은 파일 검사에서 흔히 예상할 수 있으므로, 변환 성공 여부를 Boolean으로 받는다. 실패하면 그 사실을 파일의 행 번호와 연결하여 CsvDataException으로 전달한다. TryParse의 Try와 예외 처리 구문의 Try는 서로 다른 용도다.
함수가 만든 목록은 파일 전체를 검사한 뒤에만 반환한다. 중간에 잘못된 행이 나오면 목록을 반환하지 않는다. 따라서 호출한 쪽이 일부만 읽힌 목록을 성공 결과로 받지 않는다. 이 성질은 여러 비품의 상태를 한 번에 복원할 때 유용하다.
완성 코드
새 Visual Basic 콘솔 프로젝트의 Program.vb를 다음 내용으로 바꾼다. 클래스도 같은 파일에 두므로 추가 파일은 필요하지 않다. 입력을 받지 않으며, 정상적인 파일 생성·읽기·삭제 권한이 있는 환경에서는 다음 절의 출력과 일치한다.
Option Strict On
Option Explicit On
Option Infer On
Imports System
Imports System.Collections.Generic
Imports System.Globalization
Imports System.IO
Imports System.Text
Module Program
Private Const Header As String = "Id,Name,Available"
Sub Main()
Dim items As New List(Of Equipment) From {
New Equipment(101, "노트북", True),
New Equipment(102, "프로젝터", False)
}
Dim folder As String = Path.Combine(
Path.GetTempPath(), "vb-equipment-" & Guid.NewGuid().ToString("N"))
Dim filePath As String = Path.Combine(folder, "equipment.csv")
Try
Directory.CreateDirectory(folder)
SaveItems(filePath, items)
Console.WriteLine("저장 완료: 2개")
Dim loaded As List(Of Equipment) = LoadItems(filePath)
Console.WriteLine("읽기 완료: " & loaded.Count.ToString(
CultureInfo.InvariantCulture) & "개")
For Each item As Equipment In loaded
Dim state As String =
If(item.Available, "대여 가능", "대여 중")
Console.WriteLine(
item.Id.ToString(CultureInfo.InvariantCulture) &
" | " & item.Name & " | " & state)
Next
File.AppendAllText(
filePath, "번호오류,마우스,True" & Environment.NewLine,
New UTF8Encoding(False))
Try
LoadItems(filePath)
Catch ex As CsvDataException
Console.WriteLine("자료 오류: " & ex.Message)
End Try
Catch ex As CsvDataException
Console.WriteLine("자료 오류: " & ex.Message)
Catch ex As UnauthorizedAccessException
Console.WriteLine("파일 작업 실패: 접근 권한을 확인한다.")
Catch ex As IOException
Console.WriteLine("파일 작업 실패: 파일과 저장 공간을 확인한다.")
Finally
Try
If Directory.Exists(folder) Then
Directory.Delete(folder, True)
Console.WriteLine("임시 파일 정리 완료")
End If
Catch ex As UnauthorizedAccessException
Console.WriteLine("임시 파일 정리 실패: 접근 권한 문제")
Catch ex As IOException
Console.WriteLine("임시 파일 정리 실패: 입출력 문제")
End Try
End Try
End Sub
Private Function IsValidName(name As String) As Boolean
Return Not String.IsNullOrWhiteSpace(name) AndAlso
Not name.Contains(","c) AndAlso
Not name.Contains(""""c) AndAlso
Not name.Contains(ChrW(13)) AndAlso
Not name.Contains(ChrW(10))
End Function
Private Sub SaveItems(
filePath As String, items As List(Of Equipment))
Dim ids As New HashSet(Of Integer)()
For Each item As Equipment In items
If item.Id <= 0 OrElse Not IsValidName(item.Name) Then
Throw New ArgumentException("保存対象の値が不正です。")
End If
If Not ids.Add(item.Id) Then
Throw New ArgumentException("저장할 비품 번호가 중복된다.")
End If
Next
Using writer As New StreamWriter(
filePath, False, New UTF8Encoding(False))
writer.WriteLine(Header)
For Each item As Equipment In items
writer.WriteLine(
item.Id.ToString(CultureInfo.InvariantCulture) & "," &
item.Name & "," &
If(item.Available, "True", "False"))
Next
End Using
End Sub
Private Function LoadItems(
filePath As String) As List(Of Equipment)
Dim result As New List(Of Equipment)()
Dim ids As New HashSet(Of Integer)()
Using reader As New StreamReader(filePath, Encoding.UTF8, True)
If reader.ReadLine() <> Header Then
Throw New CsvDataException(1, "제목 행이 올바르지 않다.")
End If
Dim lineNumber As Integer = 1
Do
Dim line As String = reader.ReadLine()
If line Is Nothing Then Exit Do
lineNumber += 1
Dim fields As String() = line.Split(","c)
If fields.Length <> 3 Then
Throw New CsvDataException(
lineNumber, "열은 3개여야 한다.")
End If
Dim id As Integer
If Not Integer.TryParse(
fields(0), NumberStyles.None,
CultureInfo.InvariantCulture, id) OrElse id <= 0 Then
Throw New CsvDataException(
lineNumber, "비품 번호는 양수여야 한다.")
End If
If Not IsValidName(fields(1)) Then
Throw New CsvDataException(
lineNumber, "비품 이름이 올바르지 않다.")
End If
If fields(2) <> "True" AndAlso fields(2) <> "False" Then
Throw New CsvDataException(
lineNumber, "대여 가능 여부는 True 또는 False여야 한다.")
End If
If Not ids.Add(id) Then
Throw New CsvDataException(
lineNumber, "비품 번호가 중복된다.")
End If
result.Add(New Equipment(
id, fields(1), fields(2) = "True"))
Loop
End Using
Return result
End Function
End Module
Public Class Equipment
Public ReadOnly Property Id As Integer
Public ReadOnly Property Name As String
Public ReadOnly Property Available As Boolean
Public Sub New(id As Integer, name As String, available As Boolean)
Me.Id = id
Me.Name = name
Me.Available = available
End Sub
End Class
Public Class CsvDataException
Inherits Exception
Public ReadOnly Property LineNumber As Integer
Public Sub New(lineNumber As Integer, reason As String)
MyBase.New(
lineNumber.ToString(CultureInfo.InvariantCulture) &
"행: " & reason)
Me.LineNumber = lineNumber
End Sub
End Class
줄별 해설
설정과 Main의 준비 부분
Option Strict On은 암시적인 축소 변환 같은 실수를 컴파일 단계에서 막는다. Option Explicit On은 변수 선언을 요구한다. Option Infer On은 초기값으로 변수 형식을 추론할 수 있게 한다. Imports는 코드에서 사용할 형식의 이름을 짧게 적기 위한 선언이다.
Header는 저장과 읽기가 공유하는 제목 행이다. 같은 값을 두 함수에서 따로 적지 않아 형식 변경 시 어긋날 가능성을 줄인다. Main은 두 비품을 만들고, 임시 폴더 아래에 저장 경로를 만든다. Guid.NewGuid는 실행마다 다른 폴더 이름을 만드는 데 사용한다. 그 이름은 화면에 출력하지 않으므로 예상 출력에는 영향을 주지 않는다.
Directory.CreateDirectory는 폴더를 준비한다. SaveItems가 반환되면 저장 객체의 Using도 끝난 상태이므로 저장 완료를 출력한다. LoadItems의 반환값을 loaded에 넣고 목록 순서대로 출력한다. If 식은 Available 값에 따라 두 상태 문자열 중 하나를 고른다.
오류 실험과 마지막 정리
File.AppendAllText는 기존 파일 끝에 문장을 덧붙인다. 파일이 없으면 새 파일을 만들지만, 여기서는 앞서 저장한 파일이 존재한다. 저장 함수가 마지막 자료 뒤에 줄바꿈을 썼으므로 추가한 문장은 독립된 네 번째 행이 된다.
안쪽 Try는 의도적으로 손상시킨 파일을 다시 읽는 부분이다. 이때의 CsvDataException은 안쪽 Catch가 처리한다. 바깥 Catch는 최초 읽기에서 발생한 자료 오류와 파일 작업 실패를 처리한다. 권한 문제는 UnauthorizedAccessException으로, 다른 입출력 문제는 IOException으로 나누어 안내한다.
Finally 안에서도 삭제가 실패할 수 있다. 그래서 정리 작업 자체에 별도의 Try…Catch를 둔다. 삭제 실패를 처리하지 않으면 정리 중 새로 발생한 예외가 원래 작업의 결과를 가릴 수 있다. Directory.Delete의 True는 하위 내용까지 삭제한다는 뜻이다. 이 인수는 프로그램이 직접 만든 전용 임시 폴더에만 사용한다.
이름 검사와 저장 함수
IsValidName은 이름이 비어 있지 않고 제한한 문자를 포함하지 않는지 검사한다. AndAlso는 앞 조건이 False이면 뒤 조건을 평가하지 않는다. 따라서 name이 Nothing이면 첫 검사에서 멈추고 Contains를 호출하지 않는다. 큰따옴표 문자 리터럴은 따옴표를 겹쳐 적은 뒤 c를 붙여 표현한다.
SaveItems는 파일을 열기 전에 모든 자료를 검사한다. 잘못된 값 때문에 기존 파일을 먼저 비우는 일을 줄이기 위한 순서다. HashSet은 같은 값이 이미 들어 있는지 검사하기에 적합한 집합이다. Add는 새 번호를 넣으면 True, 이미 있으면 False를 반환하므로 중복 검사가 된다.
검사가 끝나면 StreamWriter를 만든다. 생성자의 False는 추가 쓰기가 아니라 덮어쓰기를 뜻한다. 먼저 제목 행을 쓰고 각 비품을 세 값으로 저장한다. Available은 True 또는 False라는 정해진 문자열로 변환한다. 메모리에 준비한 저장 자료 자체가 잘못되었을 때는 ArgumentException을 발생시킨다. 완성 예제의 두 비품은 이 검사를 통과한다.
읽기 함수와 예외 클래스
LoadItems는 임시 결과 목록과 번호 집합을 만든다. 제목 행이 다르면 첫 행 오류를 발생시킨다. 빈 파일도 ReadLine이 Nothing을 반환하므로 제목 검사에서 실패한다. 반복 안에서는 Nothing을 파일 끝으로 판단하지만, 빈 문자열은 자료 행으로 검사한다. 따라서 빈 행을 조용히 건너뛰지 않는다.
열 개수부터 확인한 다음 각 열에 접근한다. 비품 번호는 변환 성공 여부와 양수 조건을 함께 검사한다. 이름, 상태, 중복 검사가 모두 끝나야 result에 비품을 추가한다. Return result는 Using 바깥에 있으므로 반환 전에 읽기 자원이 정리된다.
Equipment의 읽기 전용 속성은 생성자에서 받은 값을 보관한다. CsvDataException은 MyBase.New로 부모인 Exception의 메시지를 설정한다. LineNumber 속성에도 번호를 남겨 두므로 호출한 쪽은 메시지 문자열을 다시 분석하지 않고 오류 위치를 알 수 있다.
실행 결과
.NET 10 SDK가 설치된 macOS 또는 Linux에서 프로젝트를 만든다. 생성된 Program.vb를 완성 코드로 교체한 다음 실행한다.
dotnet new console -lang VB -f net10.0 -o OfficeEquipment
cd OfficeEquipment
dotnet run
파일 작업이 성공한 실행에서 프로그램이 출력하는 내용은 다음과 같다. 첫 번째 행은 제목이고 두 비품이 그다음에 있으므로, 일부러 추가한 잘못된 자료는 네 번째 행으로 보고된다.
저장 완료: 2개
읽기 완료: 2개
101 | 노트북 | 대여 가능
102 | 프로젝터 | 대여 중
자료 오류: 4행: 비품 번호는 양수여야 한다.
임시 파일 정리 완료
오류 행을 추가하기 전의 파일 내용은 다음과 같다. 줄바꿈 문자는 실행하는 운영체제에 맞게 기록된다. 이 예제의 읽기 함수는 그 차이를 처리한다.
Id,Name,Available
101,노트북,True
102,프로젝터,False
실행 후 임시 폴더는 삭제된다. 실제 대여 상태를 보존하려면 별도로 정한 데이터 폴더를 사용하고 그 파일을 종료 때 삭제하지 않아야 한다. 저장 도중의 장치 오류까지 대비하려면 임시 파일에 먼저 저장하고 성공 후 교체하는 방식도 검토해야 한다. 현재 저장 함수는 덮어쓰는 중 실패하면 기존 내용을 온전히 보존한다고 보장하지 않는다.
실무에서 자주 틀리는 것
Catch에서 아무 일도 하지 않기
빈 Catch는 저장 실패를 감춘다. 호출한 쪽에서 성공 여부를 판단하기 어렵다. 다음 조각은 Main 안에서 사용할 수 있다.
틀린 코드다.
Try
SaveItems(filePath, items)
Catch ex As IOException
End Try
Console.WriteLine("저장 완료")
고친 코드다. 성공 메시지는 저장이 성공한 경로에만 둔다.
Try
SaveItems(filePath, items)
Console.WriteLine("저장 완료")
Catch ex As IOException
Console.WriteLine("저장 실패: 파일 상태를 확인한다.")
End Try
Close를 정상 경로에만 두기
쓰기 도중 예외가 발생하면 뒤의 Close에 도달하지 못한다. 파일 객체를 만드는 순간부터 Using으로 범위를 정한다.
틀린 코드다.
Dim writer As New StreamWriter(filePath)
writer.WriteLine(Header)
writer.Close()
고친 코드다.
Using writer As New StreamWriter(
filePath, False, New UTF8Encoding(False))
writer.WriteLine(Header)
End Using
열 개수보다 배열 접근을 먼저 하기
한 열만 있는 행에서 fields(1)에 접근하면 배열 범위 오류가 난다. 자료 형식 오류를 설명하려면 구조부터 검사해야 한다. 다음 조각은 읽기 반복 안에 들어간다.
틀린 코드다.
Dim fields As String() = line.Split(","c)
Dim name As String = fields(1)
고친 코드다.
Dim fields As String() = line.Split(","c)
If fields.Length <> 3 Then
Throw New CsvDataException(lineNumber, "열은 3개여야 한다.")
End If
Dim name As String = fields(1)
경로 구분 문자를 직접 붙이기
역슬래시가 모든 운영체제에서 폴더 구분 문자로 쓰이는 것은 아니다. 경로를 결합하는 의도를 Path.Combine으로 표현한다.
틀린 코드다.
Dim filePath As String = folder & "\equipment.csv"
고친 코드다.
Dim filePath As String = Path.Combine(folder, "equipment.csv")
한눈에 보기
| 확인할 것 | 사용할 도구 | 기억할 점 |
|---|---|---|
| 저장 위치 | Path.Combine | 경로 구분 문자를 직접 붙이지 않는다. |
| 파일 내용 쓰기 | StreamWriter와 Using | 추가 쓰기와 덮어쓰기를 구별한다. |
| 파일 내용 읽기 | StreamReader와 Using | 파일 끝과 빈 행을 구별한다. |
| 자료 구조와 값 | Split, TryParse, HashSet | 구조 검사 후 변환하고 중복을 확인한다. |
| 자료 규칙 위반 | 사용자 정의 예외 | 행 번호와 원인을 함께 전달한다. |
| 실패 후 마무리 | Catch와 Finally | 정리 작업에서도 오류가 날 수 있다. |
.NET의 동작을 더 확인하려면 Try…Catch…Finally 참조, Using 참조, Path.Combine 참조를 볼 수 있다. 이번 장의 CSV 규칙은 예제를 위해 정한 규칙이며, 다른 프로그램의 CSV를 그대로 읽을 수 있다는 뜻은 아니다.
연습 문제
- 오류 실험에서 추가하는 행을 “103,마우스,대기”로 바꾼다. 오류 행 번호와 메시지를 예상하고, 어떤 검사가 먼저 실패하는지 설명한다.
- 네 번째 행에 “101,마우스,True”를 추가한다. 중복 번호가 목록에 들어가기 전에 막히는 이유를 설명한다.
- 읽기에서 비품 이름 앞뒤의 공백도 금지하도록 검사 조건을 추가한다. “103, 마우스,True”가 거부되어야 하며 저장 검사에도 같은 규칙이 적용되어야 한다.
- 파일이 없는 경우에 “저장 파일이 없다.”를 출력하도록 바깥 Catch에 처리를 추가한다. IOException 처리와의 순서도 제시한다.
정답과 해설
-
네 번째 행에서 상태 검사에 실패한다. 열은 세 개이고 번호와 이름은 유효하지만 “대기”는 허용한 두 문자열에 포함되지 않는다. 출력되는 자료 오류 줄은 다음과 같다.
자료 오류: 4행: 대여 가능 여부는 True 또는 False여야 한다. -
네 번째 행에서 “비품 번호가 중복된다.”라는 오류가 발생한다. ids에는 두 번째 행을 읽을 때 이미 101이 들어 있다. ids.Add(101)이 False를 반환하여 예외가 발생하므로, 바로 뒤의 result.Add는 실행되지 않는다. 읽기 함수 전체가 실패하므로 부분 목록도 반환되지 않는다.
-
IsValidName의 첫 검사 다음에 name과 name.Trim의 일치 조건을 넣는다. Trim은 문자열 앞뒤의 공백을 제거한 새 문자열을 반환한다. 원본과 다르면 앞뒤에 공백이 있었다는 뜻이다.
Return Not String.IsNullOrWhiteSpace(name) AndAlso name = name.Trim() AndAlso Not name.Contains(","c) AndAlso Not name.Contains(""""c) AndAlso Not name.Contains(ChrW(13)) AndAlso Not name.Contains(ChrW(10))이 함수는 저장과 읽기에서 함께 사용하므로 양쪽 검사에 규칙이 반영된다. 공백을 자동으로 제거하지 않고 오류로 알리는 정책이다.
-
FileNotFoundException을 IOException보다 먼저 둔다. 기존 바깥 Catch 부분을 다음처럼 바꾸면 된다. 이 처리는 지정한 파일이 없는 경우를 대상으로 한다. 상위 폴더가 없는 경우에는 다른 입출력 예외가 발생할 수 있다.
Catch ex As CsvDataException Console.WriteLine("자료 오류: " & ex.Message) Catch ex As UnauthorizedAccessException Console.WriteLine("파일 작업 실패: 접근 권한을 확인한다.") Catch ex As FileNotFoundException Console.WriteLine("저장 파일이 없다.") Catch ex As IOException Console.WriteLine("파일 작업 실패: 파일과 저장 공간을 확인한다.")원래 예제는 읽기 전에 파일을 저장하므로 이 분기로 들어가지 않는다. 별도의 존재하지 않는 파일 경로로 LoadItems를 호출하면 해당 처리를 확인할 수 있다.