回顾:文件解析器
本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 58 课,共 107 课。
挑战
简单让我们构建一个配置文件解析器,把本章介绍的所有错误处理技巧结合起来。你的解析器将使用自定义错误类型、哨兵错误、错误包装,以及通过 defer 正确执行清理操作,来处理多种失败情况。
你将把代码分成三个文件:
errors.go:为解析器定义错误基础结构。创建两个哨兵错误:
ErrEmptyContent,错误消息为"file content is empty"ErrInvalidFormat,错误消息为"invalid configuration format"
创建自定义错误类型
ParseError,包含Line(int)和Message(string)字段。它的Error()方法应返回:parse error at line [Line]: [Message]parser.go:构建配置解析器,并分层处理错误。创建一个
ConfigParser结构体,其中包含一个parsed布尔字段,用于记录解析是否成功完成。实现以下方法:
NewConfigParser() *ConfigParser:创建一个新的解析器,并将parsed设为falseParseLine(lineNum int, line string) error:验证单行内容。如果该行不包含=字符,则返回一个*ParseError,其中包含行号和错误消息"missing key-value separator"。否则返回nil。Parse(content string) error:主要的解析方法,需要:- 如果内容为空,则返回
ErrEmptyContent - 如果内容中不包含至少一个换行符,则返回
ErrInvalidFormat - 按换行符拆分内容,并对每一行调用
ParseLine(行号从 1 开始) - 如果
ParseLine返回错误,则使用"parsing configuration: %w"包装该错误并返回 - 如果解析成功,则将
parsed设为true并返回nil
- 如果内容为空,则返回
Status() string:如果parsed为 true,则返回"ready",否则返回"not parsed"
main.go:读取配置内容,并演示如何全面检查错误。读取两行输入,将它们用换行符连接起来,组成配置内容。使用
defer确保无论成功还是失败,最后都会打印解析器的状态。尝试解析后,同时使用
errors.Is()和errors.As()检查错误,并根据错误原因提供具体反馈。
输入内容如下:
- 第 1 行:配置内容的第一行
- 第 2 行:配置内容的第二行
请使用以下具体消息处理错误:
- 如果
errors.Is()找到ErrEmptyContent:打印Error: No configuration provided - 如果
errors.Is()找到ErrInvalidFormat:打印Error: Configuration must have multiple lines - 如果
errors.As()找到*ParseError:打印完整的包装错误,然后在新的一行打印Fix line [Line]: [Message] - 如果解析成功:打印
Configuration parsed successfully
处理完结果(成功或错误)后,延迟打印的状态应显示在新的一行:Parser status: [status]
例如,输入 host=localhost 和 port=8080 时,输出应为:
Configuration parsed successfully
Parser status: ready输入 host=localhost 和 invalid line 时,输出应为:
parsing configuration: parse error at line 2: missing key-value separator
Fix line 2: missing key-value separator
Parser status: not parsed亲手试试
package main
import (
"bufio"
"errors"
"fmt"
"os"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// 读取配置的第一行
line1, _ := reader.ReadString('\n')
line1 = line1[:len(line1)-1] // 移除末尾的换行符
// 读取配置的第二行
line2, _ := reader.ReadString('\n')
if len(line2) > 0 && line2[len(line2)-1] == '\n' {
line2 = line2[:len(line2)-1]
}
// 用换行符连接这两行,形成配置内容
content := line1 + "\n" + line2
// 创建一个新的解析器
parser := NewConfigParser()
// TODO:使用 defer,确保最后输出解析器的状态
// 格式:"Parser status: [status]"
// TODO:尝试解析内容
// TODO:使用 errors.Is() 和 errors.As() 处理错误
// 如果 errors.Is() 找到 ErrEmptyContent:输出 "Error: No configuration provided"
// 如果 errors.Is() 找到 ErrInvalidFormat:输出 "Error: Configuration must have multiple lines"
// 如果 errors.As() 找到 *ParseError:输出完整的包装错误,
// 然后在新的一行打印 "Fix line [Line]: [Message]"
// - 如果解析成功:打印 "Configuration parsed successfully"
_ = errors.Is // 提示:用 errors.Is 处理哨兵错误
_ = errors.As // 提示:用 errors.As 处理自定义错误类型
_ = fmt.Println
}
面向对象编程中的所有课
自己练习: Go 在线编译器