国外申请域名的网站,CC wordpress 攻击,wordpress实现图片幻灯展示效果,自己架设网站备案你知道有什么比改进错误处理的语法更好吗#xff1f;那就是根本不需要处理错误。 注意: 我不是说“删除你的错误处理”。我的建议是#xff0c;修改你的代码#xff0c;这样就不用处理错误了。 本节从 John Ousterhout 最近的著作“软件设计哲学”[9]中汲取灵感。该书的其中…你知道有什么比改进错误处理的语法更好吗那就是根本不需要处理错误。 注意: 我不是说“删除你的错误处理”。我的建议是修改你的代码这样就不用处理错误了。 本节从 John Ousterhout 最近的著作“软件设计哲学”[9]中汲取灵感。该书的其中一章是“定义不存在的错误”。我们将尝试将此建议应用于 Go 语言。
计算行数
让我们编写一个函数来计算文件中的行数。
func CountLines(r io.Reader) (int, error) {var (br bufio.NewReader(r)lines interr error)for {_, err br.ReadString(\n)linesif err ! nil {break}}if err ! io.EOF {return 0, err}return lines, nil
}由于我们遵循前面部分的建议CountLines 需要一个 io.Reader而不是一个 *File它的任务是调用者为我们想要计算的内容提供 io.Reader。
我们构造一个 bufio.Reader然后在一个循环中调用 ReadString 方法递增计数器直到我们到达文件的末尾然后我们返回读取的行数。
至少这是我们想要编写的代码但是这个函数由于需要错误处理而变得更加复杂。 例如有这样一个奇怪的结构:
_, err br.ReadString(\n)
lines
if err ! nil {break
}我们在检查错误之前增加了行数这样做看起来很奇怪。
我们必须以这种方式编写它的原因是如果在遇到换行符之前就读到文件结束则 ReadString 将返回错误。如果文件中没有换行符同样会出现这种情况。
为了解决这个问题我们重新排列逻辑增来加行数然后查看是否需要退出循环。 注意: 这个逻辑仍然不完美你能发现错误吗 但是我们还没有完成检查错误。当 ReadString 到达文件末尾时预期它会返回 io.EOF。ReadString 需要某种方式在没有什么可读时来停止。因此在我们将错误返回给 CountLine 的调用者之前我们需要检查错误是否是 io.EOF如果不是将其错误返回否则我们返回 nil 说一切正常。
我认为这是 Russ Cox 观察到错误处理可能会模糊函数操作的一个很好的例子。我们来看一个改进的版本。
func CountLines(r io.Reader) (int, error) {sc : bufio.NewScanner(r)lines : 0for sc.Scan() {lines}return lines, sc.Err()
}这个改进的版本从 bufio.Reader 切换到 bufio.Scanner。
在 bufio.Scanner 内部使用 bufio.Reader但它添加了一个很好的抽象层它有助于通过隐藏 CountLines 的操作来消除错误处理。 注意: bufio.Scanner 可以扫描任何模式但默认情况下它会查找换行符。 如果扫描程序匹配了一行文本并且没有遇到错误则 sc.Scan() 方法返回 true 。因此只有当扫描仪的缓冲区中有一行文本时才会调用 for 循环的主体。这意味着我们修改后的 CountLines 正确处理没有换行符的情况并且还处理文件为空的情况。
其次当 sc.Scan 在遇到错误时返回 false我们的 for 循环将在到达文件结尾或遇到错误时退出。bufio.Scanner 类型会记住遇到的第一个错误一旦我们使用 sc.Err() 方法退出循环我们就可以获取该错误。
最后 sc.Err() 负责处理 io.EOF 并在达到文件末尾时将其转换为 nil而不会遇到其他错误。 贴士: 当遇到难以忍受的错误处理时请尝试将某些操作提取到辅助程序类型中。 WriteResponse
我的第二个例子受到了 Errors are values 博客文章[10]的启发。
在本章前面我们已经看过处理打开、写入和关闭文件的示例。错误处理是存在的但是接收范围内的因为操作可以封装在诸如 ioutil.ReadFile 和 ioutil.WriteFile 之类的辅助程序中。但是在处理底层网络协议时有必要使用 I/O 原始的错误处理来直接构建响应这样就可能会变得重复。看一下构建 HTTP 响应的 HTTP 服务器的这个片段。
type Header struct {Key, Value string
}type Status struct {Code intReason string
}func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {_, err : fmt.Fprintf(w, HTTP/1.1 %d %s\r\n, st.Code, st.Reason)if err ! nil {return err}for _, h : range headers {_, err : fmt.Fprintf(w, %s: %s\r\n, h.Key, h.Value)if err ! nil {return err}}if _, err : fmt.Fprint(w, \r\n); err ! nil {return err}_, err io.Copy(w, body)return err
}首先我们使用 fmt.Fprintf 构造状态码并检查错误。 然后对于每个标题我们写入键值对每次都检查错误。 最后我们使用额外的 \r\n 终止标题部分检查错误之后将响应主体复制到客户端。 最后虽然我们不需要检查 io.Copy 中的错误但我们需要将 io.Copy 返回的两个返回值形式转换为 WriteResponse 的单个返回值。
这里很多重复性的工作。 我们可以通过引入一个包装器类型 errWriter 来使其更容易。
errWriter 实现 io.Writer 接口因此可用于包装现有的 io.Writer。 errWriter 写入传递给其底层 writer直到检测到错误。 从此时起它会丢弃任何写入并返回先前的错误。
type errWriter struct {io.Writererr error
}func (e *errWriter) Write(buf []byte) (int, error) {if e.err ! nil {return 0, e.err}var n intn, e.err e.Writer.Write(buf)return n, nil
}func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {ew : errWriter{Writer: w}fmt.Fprintf(ew, HTTP/1.1 %d %s\r\n, st.Code, st.Reason)for _, h : range headers {fmt.Fprintf(ew, %s: %s\r\n, h.Key, h.Value)}fmt.Fprint(ew, \r\n)io.Copy(ew, body)return ew.err
}将 errWriter 应用于 WriteResponse 可以显着提高代码的清晰度。 每个操作不再需要自己做错误检查。 通过检查 ew.err 字段将错误报告移动到函数末尾从而避免转换从 io.Copy 的两个返回值。