ch7: fix code format

This commit is contained in:
chai2010 2016-01-21 10:22:10 +08:00
parent 2420954025
commit 0b5ec941ed
13 changed files with 208 additions and 186 deletions

View File

@ -8,6 +8,7 @@
``` go ``` go
package fmt package fmt
func Fprintf(w io.Writer, format string, args ...interface{}) (int, error) func Fprintf(w io.Writer, format string, args ...interface{}) (int, error)
func Printf(format string, args ...interface{}) (int, error) { func Printf(format string, args ...interface{}) (int, error) {
return Fprintf(os.Stdout, format, args...) return Fprintf(os.Stdout, format, args...)
@ -26,6 +27,7 @@ Fprintf的前綴F表示文件(File)也表明格式化輸出結果應該被寫入
``` go ``` go
package io package io
// Writer is the interface that wraps the basic Write method. // Writer is the interface that wraps the basic Write method.
type Writer interface { type Writer interface {
// Write writes len(p) bytes from p to the underlying data stream. // Write writes len(p) bytes from p to the underlying data stream.
@ -45,21 +47,23 @@ io.Writer類型定義了函數Fprintf和這個函數調用者之間的約定。
讓我們通過一個新的類型來進行校驗,下面\*ByteCounter類型里的Write方法僅僅在丟失寫向它的字節前統計它們的長度。(在這個+=賦值語句中讓len(p)的類型和\*c的類型匹配的轉換是必須的。) 讓我們通過一個新的類型來進行校驗,下面\*ByteCounter類型里的Write方法僅僅在丟失寫向它的字節前統計它們的長度。(在這個+=賦值語句中讓len(p)的類型和\*c的類型匹配的轉換是必須的。)
<u><i>gopl.io/ch7/bytecounter</i></u>
```go ```go
// gopl.io/ch7/bytecounter
type ByteCounter int type ByteCounter int
func (c *ByteCounter) Write(p []byte) (int, error) { func (c *ByteCounter) Write(p []byte) (int, error) {
*c += ByteCounter(len(p)) // convert int to ByteCounter *c += ByteCounter(len(p)) // convert int to ByteCounter
return len(p), nil return len(p), nil
} }
``` ```
因爲*ByteCounter滿足io.Writer的約定我們可以把它傳入Fprintf函數中Fprintf函數執行字符串格式化的過程不會去關註ByteCounter正確的纍加結果的長度。 因爲*ByteCounter滿足io.Writer的約定我們可以把它傳入Fprintf函數中Fprintf函數執行字符串格式化的過程不會去關註ByteCounter正確的纍加結果的長度。
```go ```go
var c ByteCounter var c ByteCounter
c.Write([]byte("hello")) c.Write([]byte("hello"))
fmt.Println(c) // "5", = len("hello") fmt.Println(c) // "5", = len("hello")
c = 0 // reset the counter c = 0 // reset the counter
var name = "Dolly" var name = "Dolly"
fmt.Fprintf(&c, "hello, %s", name) fmt.Fprintf(&c, "hello, %s", name)
fmt.Println(c) // "12", = len("hello, Dolly") fmt.Println(c) // "12", = len("hello, Dolly")
@ -69,11 +73,12 @@ fmt.Println(c) // "12", = len("hello, Dolly")
```go ```go
package fmt package fmt
// The String method is used to print values passed // The String method is used to print values passed
// as an operand to any format that accepts a string // as an operand to any format that accepts a string
// or to an unformatted printer such as Print. // or to an unformatted printer such as Print.
type Stringer interface { type Stringer interface {
String() string String() string
} }
``` ```

View File

@ -1,4 +1,5 @@
## 7.2. 接口類型 ## 7.2. 接口類型
接口類型具體描述了一繫列方法的集合,一個實現了這些方法的具體類型是這個接口類型的實例。 接口類型具體描述了一繫列方法的集合,一個實現了這些方法的具體類型是這個接口類型的實例。
io.Writer類型是用的最廣泛的接口之一因爲它提供了所有的類型寫入bytes的抽象包括文件類型內存緩衝區網絡鏈接HTTP客戶端壓縮工具哈希等等。io包中定義了很多其它有用的接口類型。Reader可以代表任意可以讀取bytes的類型Closer可以是任意可以關閉的值例如一個文件或是網絡鏈接。到現在你可能註意到了很多Go語言中單方法接口的命名習慣 io.Writer類型是用的最廣泛的接口之一因爲它提供了所有的類型寫入bytes的抽象包括文件類型內存緩衝區網絡鏈接HTTP客戶端壓縮工具哈希等等。io包中定義了很多其它有用的接口類型。Reader可以代表任意可以讀取bytes的類型Closer可以是任意可以關閉的值例如一個文件或是網絡鏈接。到現在你可能註意到了很多Go語言中單方法接口的命名習慣
@ -6,10 +7,10 @@ io.Writer類型是用的最廣泛的接口之一因爲它提供了所有的
```go ```go
package io package io
type Reader interface { type Reader interface {
Read(p []byte) (n int, err error) Read(p []byte) (n int, err error)
} }
type Closer interface { type Closer interface {
Close() error Close() error
} }
``` ```
@ -17,21 +18,21 @@ type Closer interface {
```go ```go
type ReadWriter interface { type ReadWriter interface {
Reader Reader
Writer Writer
} }
type ReadWriteCloser interface { type ReadWriteCloser interface {
Reader Reader
Writer Writer
Closer Closer
} }
``` ```
上面用到的語法和結構內嵌相似我們可以用這種方式以一個簡寫命名另一個接口而不用聲明它所有的方法。這種方式本稱爲接口內嵌。盡管略失簡潔我們可以像下面這樣不使用內嵌來聲明io.Writer接口。 上面用到的語法和結構內嵌相似我們可以用這種方式以一個簡寫命名另一個接口而不用聲明它所有的方法。這種方式本稱爲接口內嵌。盡管略失簡潔我們可以像下面這樣不使用內嵌來聲明io.Writer接口。
```go ```go
type ReadWriter interface { type ReadWriter interface {
Read(p []byte) (n int, err error) Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error) Write(p []byte) (n int, err error)
} }
``` ```
@ -39,8 +40,8 @@ type ReadWriter interface {
```go ```go
type ReadWriter interface { type ReadWriter interface {
Read(p []byte) (n int, err error) Read(p []byte) (n int, err error)
Writer Writer
} }
``` ```

View File

@ -1,4 +1,5 @@
## 7.3. 實現接口的條件 ## 7.3. 實現接口的條件
一個類型如果擁有一個接口需要的所有方法,那麽這個類型就實現了這個接口。例如,\*os.File類型實現了io.ReaderWriterCloser和ReadWriter接口。\*bytes.Buffer實現了ReaderWriter和ReadWriter這些接口但是它沒有實現Closer接口因爲它不具有Close方法。Go的程序員經常會簡要的把一個具體的類型描述成一個特定的接口類型。舉個例子\*bytes.Buffer是io.Writer\*os.Files是io.ReadWriter。 一個類型如果擁有一個接口需要的所有方法,那麽這個類型就實現了這個接口。例如,\*os.File類型實現了io.ReaderWriterCloser和ReadWriter接口。\*bytes.Buffer實現了ReaderWriter和ReadWriter這些接口但是它沒有實現Closer接口因爲它不具有Close方法。Go的程序員經常會簡要的把一個具體的類型描述成一個特定的接口類型。舉個例子\*bytes.Buffer是io.Writer\*os.Files是io.ReadWriter。
接口指定的規則非常簡單:表達一個類型屬於某個接口隻要這個類型實現這個接口。所以: 接口指定的規則非常簡單:表達一個類型屬於某個接口隻要這個類型實現這個接口。所以:
@ -112,29 +113,29 @@ Track
```go ```go
type Artifact interface { type Artifact interface {
Title() string Title() string
Creators() []string Creators() []string
Created() time.Time Created() time.Time
} }
``` ```
其它的一些特性隻對特定類型的文化産品才有。和文字排版特性相關的隻有books和magazines還有隻有movies和TV劇集和屏幕分辨率相關。 其它的一些特性隻對特定類型的文化産品才有。和文字排版特性相關的隻有books和magazines還有隻有movies和TV劇集和屏幕分辨率相關。
```go ```go
type Text interface { type Text interface {
Pages() int Pages() int
Words() int Words() int
PageSize() int PageSize() int
} }
type Audio interface { type Audio interface {
Stream() (io.ReadCloser, error) Stream() (io.ReadCloser, error)
RunningTime() time.Duration RunningTime() time.Duration
Format() string // e.g., "MP3", "WAV" Format() string // e.g., "MP3", "WAV"
} }
type Video interface { type Video interface {
Stream() (io.ReadCloser, error) Stream() (io.ReadCloser, error)
RunningTime() time.Duration RunningTime() time.Duration
Format() string // e.g., "MP4", "WMV" Format() string // e.g., "MP4", "WMV"
Resolution() (x, y int) Resolution() (x, y int)
} }
``` ```
@ -142,9 +143,9 @@ type Video interface {
```go ```go
type Streamer interface { type Streamer interface {
Stream() (io.ReadCloser, error) Stream() (io.ReadCloser, error)
RunningTime() time.Duration RunningTime() time.Duration
Format() string Format() string
} }
``` ```

View File

@ -2,8 +2,8 @@
在本章我們會學到另一個標準的接口類型flag.Value是怎麽幫助命令行標記定義新的符號的。思考下面這個會休眠特定時間的程序 在本章我們會學到另一個標準的接口類型flag.Value是怎麽幫助命令行標記定義新的符號的。思考下面這個會休眠特定時間的程序
<u></i>gopl.io/ch7/sleep</i></u>
```go ```go
// gopl.io/ch7/sleep
var period = flag.Duration("period", 1*time.Second, "sleep period") var period = flag.Duration("period", 1*time.Second, "sleep period")
func main() { func main() {
@ -51,8 +51,8 @@ String方法格式化標記的值用在命令行幫組消息中這樣每一
讓我們定義一個允許通過攝氏度或者華氏溫度變換的形式指定溫度的celsiusFlag類型。註意celsiusFlag內嵌了一個Celsius類型(§2.5)因此不用實現本身就已經有String方法了。爲了實現flag.Value我們隻需要定義Set方法 讓我們定義一個允許通過攝氏度或者華氏溫度變換的形式指定溫度的celsiusFlag類型。註意celsiusFlag內嵌了一個Celsius類型(§2.5)因此不用實現本身就已經有String方法了。爲了實現flag.Value我們隻需要定義Set方法
<u><i>gopl.io/ch7/tempconv</i></u>
```go ```go
// gopl.io/ch7/tempconv
// *celsiusFlag satisfies the flag.Value interface. // *celsiusFlag satisfies the flag.Value interface.
type celsiusFlag struct{ Celsius } type celsiusFlag struct{ Celsius }
@ -89,8 +89,8 @@ func CelsiusFlag(name string, value Celsius, usage string) *Celsius {
現在我們可以開始在我們的程序中使用新的標記: 現在我們可以開始在我們的程序中使用新的標記:
<u><i>gopl.io/ch7/tempflag</i></u>
```go ```go
// gopl.io/ch7/tempflag
var temp = tempconv.CelsiusFlag("temp", 20.0, "the temperature") var temp = tempconv.CelsiusFlag("temp", 20.0, "the temperature")
func main() { func main() {

View File

@ -8,22 +8,22 @@
const debug = true const debug = true
func main() { func main() {
var buf *bytes.Buffer var buf *bytes.Buffer
if debug { if debug {
buf = new(bytes.Buffer) // enable collection of output buf = new(bytes.Buffer) // enable collection of output
} }
f(buf) // NOTE: subtly incorrect! f(buf) // NOTE: subtly incorrect!
if debug { if debug {
// ...use buf... // ...use buf...
} }
} }
// If out is non-nil, output will be written to it. // If out is non-nil, output will be written to it.
func f(out io.Writer) { func f(out io.Writer) {
// ...do something... // ...do something...
if out != nil { if out != nil {
out.Write([]byte("done!\n")) out.Write([]byte("done!\n"))
} }
} }
``` ```
@ -31,7 +31,7 @@ func f(out io.Writer) {
```go ```go
if out != nil { if out != nil {
out.Write([]byte("done!\n")) // panic: nil pointer dereference out.Write([]byte("done!\n")) // panic: nil pointer dereference
} }
``` ```
@ -46,7 +46,7 @@ if out != nil {
```go ```go
var buf io.Writer var buf io.Writer
if debug { if debug {
buf = new(bytes.Buffer) // enable collection of output buf = new(bytes.Buffer) // enable collection of output
} }
f(buf) // OK f(buf) // OK
``` ```

View File

@ -29,6 +29,7 @@ func (p StringSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
```go ```go
sort.Sort(StringSlice(names)) sort.Sort(StringSlice(names))
``` ```
這個轉換得到一個相同長度容量和基於names數組的切片值併且這個切片值的類型有三個排序需要的方法。 這個轉換得到一個相同長度容量和基於names數組的切片值併且這個切片值的類型有三個排序需要的方法。
對字符串切片的排序是很常用的需要所以sort包提供了StringSlice類型也提供了Strings函數能讓上面這些調用簡化成sort.Strings(names)。 對字符串切片的排序是很常用的需要所以sort包提供了StringSlice類型也提供了Strings函數能讓上面這些調用簡化成sort.Strings(names)。
@ -39,8 +40,8 @@ sort.Sort(StringSlice(names))
下面的變量tracks包好了一個播放列表。One of the authors apologizes for the other authors musical tastes.每個元素都不是Track本身而是指向它的指針。盡管我們在下面的代碼中直接存儲Tracks也可以工作sort函數會交換很多對元素所以如果每個元素都是指針會更快而不是全部Track類型指針是一個機器字碼長度而Track類型可能是八個或更多。 下面的變量tracks包好了一個播放列表。One of the authors apologizes for the other authors musical tastes.每個元素都不是Track本身而是指向它的指針。盡管我們在下面的代碼中直接存儲Tracks也可以工作sort函數會交換很多對元素所以如果每個元素都是指針會更快而不是全部Track類型指針是一個機器字碼長度而Track類型可能是八個或更多。
<u><i>gopl.io/ch7/sorting</i></u>
```go ```go
//gopl.io/ch7/sorting
type Track struct { type Track struct {
Title string Title string
Artist string Artist string
@ -210,6 +211,7 @@ sort.Sort(sort.Reverse(sort.IntSlice(values)))
fmt.Println(values) // "[4 3 1 1]" fmt.Println(values) // "[4 3 1 1]"
fmt.Println(sort.IntsAreSorted(values)) // "false" fmt.Println(sort.IntsAreSorted(values)) // "false"
``` ```
爲了使用方便sort包爲[]int,[]string和[]float64的正常排序提供了特定版本的函數和類型。對於其他類型例如[]int64或者[]uint盡管路徑也很簡單還是依賴我們自己實現。 爲了使用方便sort包爲[]int,[]string和[]float64的正常排序提供了特定版本的函數和類型。對於其他類型例如[]int64或者[]uint盡管路徑也很簡單還是依賴我們自己實現。
**練習 7.8** 很多圖形界面提供了一個有狀態的多重排序表格插件主要的排序鍵是最近一次點擊過列頭的列第二個排序鍵是第二最近點擊過列頭的列等等。定義一個sort.Interface的實現用在這樣的表格中。比較這個實現方式和重複使用sort.Stable來排序的方式。 **練習 7.8** 很多圖形界面提供了一個有狀態的多重排序表格插件主要的排序鍵是最近一次點擊過列頭的列第二個排序鍵是第二最近點擊過列頭的列等等。定義一個sort.Interface的實現用在這樣的表格中。比較這個實現方式和重複使用sort.Stable來排序的方式。

View File

@ -2,8 +2,8 @@
在第一章中我們粗略的了解了怎麽用net/http包去實現網絡客戶端(§1.5)和服務器(§1.7)。在這個小節中我們會對那些基於http.Handler接口的服務器API做更進一步的學習 在第一章中我們粗略的了解了怎麽用net/http包去實現網絡客戶端(§1.5)和服務器(§1.7)。在這個小節中我們會對那些基於http.Handler接口的服務器API做更進一步的學習
<u><i>net/http</i></u>
```go ```go
net/http
package http package http
type Handler interface { type Handler interface {
@ -17,8 +17,8 @@ ListenAndServe函數需要一個例如“localhost:8000”的服務器地址
想象一個電子商務網站爲了銷售它的數據庫將它物品的價格映射成美元。下面這個程序可能是能想到的最簡單的實現了。它將庫存清單模型化爲一個命名爲database的map類型我們給這個類型一個ServeHttp方法這樣它可以滿足http.Handler接口。這個handler會遍歷整個map併輸出物品信息。 想象一個電子商務網站爲了銷售它的數據庫將它物品的價格映射成美元。下面這個程序可能是能想到的最簡單的實現了。它將庫存清單模型化爲一個命名爲database的map類型我們給這個類型一個ServeHttp方法這樣它可以滿足http.Handler接口。這個handler會遍歷整個map併輸出物品信息。
<u><i>gopl.io/ch7/http1</i></u>
```go ```go
gopl.io/ch7/http1
func main() { func main() {
db := database{"shoes": 50, "socks": 5} db := database{"shoes": 50, "socks": 5}
log.Fatal(http.ListenAndServe("localhost:8000", db)) log.Fatal(http.ListenAndServe("localhost:8000", db))
@ -55,8 +55,8 @@ socks: $5.00
目前爲止這個服務器不考慮URL隻能爲每個請求列出它全部的庫存清單。更眞實的服務器會定義多個不同的URL每一個都會觸發一個不同的行爲。讓我們使用/list來調用已經存在的這個行爲併且增加另一個/price調用表明單個貨品的價格像這樣/price?item=socks來指定一個請求參數。 目前爲止這個服務器不考慮URL隻能爲每個請求列出它全部的庫存清單。更眞實的服務器會定義多個不同的URL每一個都會觸發一個不同的行爲。讓我們使用/list來調用已經存在的這個行爲併且增加另一個/price調用表明單個貨品的價格像這樣/price?item=socks來指定一個請求參數。
<u><i>gopl.io/ch7/http2</i></u>
```go ```go
gopl.io/ch7/http2
func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
switch req.URL.Path { switch req.URL.Path {
case "/list": case "/list":
@ -114,8 +114,8 @@ no such page: /help
在下面的程序中我們創建一個ServeMux併且使用它將URL和相應處理/list和/price操作的handler聯繫起來這些操作邏輯都已經被分到不同的方法中。然後我門在調用ListenAndServe函數中使用ServeMux最爲主要的handler。 在下面的程序中我們創建一個ServeMux併且使用它將URL和相應處理/list和/price操作的handler聯繫起來這些操作邏輯都已經被分到不同的方法中。然後我門在調用ListenAndServe函數中使用ServeMux最爲主要的handler。
<u><i>gopl.io/ch7/http3</i></u>
```go ```go
gopl.io/ch7/http3
func main() { func main() {
db := database{"shoes": 50, "socks": 5} db := database{"shoes": 50, "socks": 5}
mux := http.NewServeMux() mux := http.NewServeMux()
@ -154,8 +154,8 @@ func(w http.ResponseWriter, req *http.Request)
語句http.HandlerFunc(db.list)是一個轉換而非一個函數調用因爲http.HandlerFunc是一個類型。它有如下的定義 語句http.HandlerFunc(db.list)是一個轉換而非一個函數調用因爲http.HandlerFunc是一個類型。它有如下的定義
<u><i>net/http</i></u>
```go ```go
net/http
package http package http
type HandlerFunc func(w ResponseWriter, r *Request) type HandlerFunc func(w ResponseWriter, r *Request)
@ -169,8 +169,8 @@ HandlerFunc顯示了在Go語言接口機製中一些不同尋常的特點。這
因爲handler通過這種方式註冊非常普遍ServeMux有一個方便的HandleFunc方法它幫我們簡化handler註冊代碼成這樣 因爲handler通過這種方式註冊非常普遍ServeMux有一個方便的HandleFunc方法它幫我們簡化handler註冊代碼成這樣
<u><i>gopl.io/ch7/http3a</i></u>
```go ```go
gopl.io/ch7/http3a
mux.HandleFunc("/list", db.list) mux.HandleFunc("/list", db.list)
mux.HandleFunc("/price", db.price) mux.HandleFunc("/price", db.price)
``` ```
@ -181,8 +181,8 @@ mux.HandleFunc("/price", db.price)
然後服務器的主函數可以簡化成: 然後服務器的主函數可以簡化成:
<u><i>gopl.io/ch7/http4</i></u>
```go ```go
gopl.io/ch7/http4
func main() { func main() {
db := database{"shoes": 50, "socks": 5} db := database{"shoes": 50, "socks": 5}
http.HandleFunc("/list", db.list) http.HandleFunc("/list", db.list)

View File

@ -34,7 +34,7 @@ package fmt
import "errors" import "errors"
func Errorf(format string, args ...interface{}) error { func Errorf(format string, args ...interface{}) error {
return errors.New(Sprintf(format, args...)) return errors.New(Sprintf(format, args...))
} }
``` ```

View File

@ -17,8 +17,8 @@ pow(x, 3) + pow(y, 3)
下面的五個具體類型表示了具體的表達式類型。Var類型表示對一個變量的引用。我們很快會知道爲什麽它可以被輸出。literal類型表示一個浮點型常量。unary和binary類型表示有一到兩個運算對象的運算符表達式這些操作數可以是任意的Expr類型。call類型表示對一個函數的調用我們限製它的fn字段隻能是powsin或者sqrt。 下面的五個具體類型表示了具體的表達式類型。Var類型表示對一個變量的引用。我們很快會知道爲什麽它可以被輸出。literal類型表示一個浮點型常量。unary和binary類型表示有一到兩個運算對象的運算符表達式這些操作數可以是任意的Expr類型。call類型表示對一個函數的調用我們限製它的fn字段隻能是powsin或者sqrt。
<u><i>gopl.io/ch7/eval</i></u>
```go ```go
gopl.io/ch7/eval
// A Var identifies a variable, e.g., x. // A Var identifies a variable, e.g., x.
type Var string type Var string
@ -254,8 +254,8 @@ Check方法的參數是一個Var類型的集合這個集合聚集從表達式
這個ParseAndCheck函數混合了解析和檢査步驟的過程 這個ParseAndCheck函數混合了解析和檢査步驟的過程
<u><i>gopl.io/ch7/surface</i></u>
```go ```go
gopl.io/ch7/surface
import "gopl.io/ch7/eval" import "gopl.io/ch7/eval"
func parseAndCheck(s string) (eval.Expr, error) { func parseAndCheck(s string) (eval.Expr, error) {

View File

@ -1,5 +1,7 @@
## 7.11. 基於類型斷言區别錯誤類型 ## 7.11. 基於類型斷言區别錯誤類型
思考在os包中文件操作返迴的錯誤集合。I/O可以因爲任何數量的原因失敗但是有三種經常的錯誤必須進行不同的處理文件已經存在對於創建操作找不到文件對於讀取操作和權限拒絶。os包中提供了這三個幫助函數來對給定的錯誤值表示的失敗進行分類 思考在os包中文件操作返迴的錯誤集合。I/O可以因爲任何數量的原因失敗但是有三種經常的錯誤必須進行不同的處理文件已經存在對於創建操作找不到文件對於讀取操作和權限拒絶。os包中提供了這三個幫助函數來對給定的錯誤值表示的失敗進行分類
```go ```go
package os package os
@ -7,31 +9,37 @@ func IsExist(err error) bool
func IsNotExist(err error) bool func IsNotExist(err error) bool
func IsPermission(err error) bool func IsPermission(err error) bool
``` ```
對這些判斷的一個缺乏經驗的實現可能會去檢査錯誤消息是否包含了特定的子字符串, 對這些判斷的一個缺乏經驗的實現可能會去檢査錯誤消息是否包含了特定的子字符串,
```go ```go
func IsNotExist(err error) bool { func IsNotExist(err error) bool {
// NOTE: not robust! // NOTE: not robust!
return strings.Contains(err.Error(), "file does not exist") return strings.Contains(err.Error(), "file does not exist")
} }
``` ```
但是處理I/O錯誤的邏輯可能一個和另一個平台非常的不同所以這種方案併不健壯併且對相同的失敗可能會報出各種不同的錯誤消息。在測試的過程中通過檢査錯誤消息的子字符串來保證特定的函數以期望的方式失敗是非常有用的但對於線上的代碼是不夠的。 但是處理I/O錯誤的邏輯可能一個和另一個平台非常的不同所以這種方案併不健壯併且對相同的失敗可能會報出各種不同的錯誤消息。在測試的過程中通過檢査錯誤消息的子字符串來保證特定的函數以期望的方式失敗是非常有用的但對於線上的代碼是不夠的。
一個更可靠的方式是使用一個專門的類型來描述結構化的錯誤。os包中定義了一個PathError類型來描述在文件路徑操作中涉及到的失敗像Open或者Delete操作,併且定義了一個叫LinkError的變體來描述涉及到兩個文件路徑的操作像Symlink和Rename。這下面是os.PathError 一個更可靠的方式是使用一個專門的類型來描述結構化的錯誤。os包中定義了一個PathError類型來描述在文件路徑操作中涉及到的失敗像Open或者Delete操作,併且定義了一個叫LinkError的變體來描述涉及到兩個文件路徑的操作像Symlink和Rename。這下面是os.PathError
```go ```go
package os package os
// PathError records an error and the operation and file path that caused it. // PathError records an error and the operation and file path that caused it.
type PathError struct { type PathError struct {
Op string Op string
Path string Path string
Err error Err error
} }
func (e *PathError) Error() string { func (e *PathError) Error() string {
return e.Op + " " + e.Path + ": " + e.Err.Error() return e.Op + " " + e.Path + ": " + e.Err.Error()
} }
``` ```
大多數調用方都不知道PathError併且通過調用錯誤本身的Error方法來統一處理所有的錯誤。盡管PathError的Error方法簡單地把這些字段連接起來生成錯誤消息PathError的結構保護了內部的錯誤組件。調用方需要使用類型斷言來檢測錯誤的具體類型以便將一種失敗和另一種區分開具體的類型比字符串可以提供更多的細節。 大多數調用方都不知道PathError併且通過調用錯誤本身的Error方法來統一處理所有的錯誤。盡管PathError的Error方法簡單地把這些字段連接起來生成錯誤消息PathError的結構保護了內部的錯誤組件。調用方需要使用類型斷言來檢測錯誤的具體類型以便將一種失敗和另一種區分開具體的類型比字符串可以提供更多的細節。
```go ```go
_, err := os.Open("/no/such/file") _, err := os.Open("/no/such/file")
fmt.Println(err) // "open /no/such/file: No such file or directory" fmt.Println(err) // "open /no/such/file: No such file or directory"
@ -39,11 +47,13 @@ fmt.Printf("%#v\n", err)
// Output: // Output:
// &os.PathError{Op:"open", Path:"/no/such/file", Err:0x2} // &os.PathError{Op:"open", Path:"/no/such/file", Err:0x2}
``` ```
這就是三個幫助函數是怎麽工作的。例如下面展示的IsNotExist它會報出是否一個錯誤和syscall.ENOENT(§7.8)或者和有名的錯誤os.ErrNotExist相等(可以在§5.4.2中找到io.EOF或者是一個*PathError它內部的錯誤是syscall.ENOENT和os.ErrNotExist其中之一。 這就是三個幫助函數是怎麽工作的。例如下面展示的IsNotExist它會報出是否一個錯誤和syscall.ENOENT(§7.8)或者和有名的錯誤os.ErrNotExist相等(可以在§5.4.2中找到io.EOF或者是一個*PathError它內部的錯誤是syscall.ENOENT和os.ErrNotExist其中之一。
```go ```go
import ( import (
"errors" "errors"
"syscall" "syscall"
) )
var ErrNotExist = errors.New("file does not exist") var ErrNotExist = errors.New("file does not exist")
@ -52,15 +62,18 @@ var ErrNotExist = errors.New("file does not exist")
// report that a file or directory does not exist. It is satisfied by // report that a file or directory does not exist. It is satisfied by
// ErrNotExist as well as some syscall errors. // ErrNotExist as well as some syscall errors.
func IsNotExist(err error) bool { func IsNotExist(err error) bool {
if pe, ok := err.(*PathError); ok { if pe, ok := err.(*PathError); ok {
err = pe.Err err = pe.Err
} }
return err == syscall.ENOENT || err == ErrNotExist return err == syscall.ENOENT || err == ErrNotExist
} }
``` ```
下面這里是它的實際使用: 下面這里是它的實際使用:
```go ```go
_, err := os.Open("/no/such/file") _, err := os.Open("/no/such/file")
fmt.Println(os.IsNotExist(err)) // "true" fmt.Println(os.IsNotExist(err)) // "true"
``` ```
如果錯誤消息結合成一個更大的字符串當然PathError的結構就不再爲人所知例如通過一個對fmt.Errorf函數的調用。區别錯誤通常必須在失敗操作後錯誤傳迴調用者前進行。 如果錯誤消息結合成一個更大的字符串當然PathError的結構就不再爲人所知例如通過一個對fmt.Errorf函數的調用。區别錯誤通常必須在失敗操作後錯誤傳迴調用者前進行。

View File

@ -4,13 +4,13 @@
```go ```go
func writeHeader(w io.Writer, contentType string) error { func writeHeader(w io.Writer, contentType string) error {
if _, err := w.Write([]byte("Content-Type: ")); err != nil { if _, err := w.Write([]byte("Content-Type: ")); err != nil {
return err return err
} }
if _, err := w.Write([]byte(contentType)); err != nil { if _, err := w.Write([]byte(contentType)); err != nil {
return err return err
} }
// ... // ...
} }
``` ```
@ -24,23 +24,23 @@ func writeHeader(w io.Writer, contentType string) error {
// writeString writes s to w. // writeString writes s to w.
// If w has a WriteString method, it is invoked instead of w.Write. // If w has a WriteString method, it is invoked instead of w.Write.
func writeString(w io.Writer, s string) (n int, err error) { func writeString(w io.Writer, s string) (n int, err error) {
type stringWriter interface { type stringWriter interface {
WriteString(string) (n int, err error) WriteString(string) (n int, err error)
} }
if sw, ok := w.(stringWriter); ok { if sw, ok := w.(stringWriter); ok {
return sw.WriteString(s) // avoid a copy return sw.WriteString(s) // avoid a copy
} }
return w.Write([]byte(s)) // allocate temporary copy return w.Write([]byte(s)) // allocate temporary copy
} }
func writeHeader(w io.Writer, contentType string) error { func writeHeader(w io.Writer, contentType string) error {
if _, err := writeString(w, "Content-Type: "); err != nil { if _, err := writeString(w, "Content-Type: "); err != nil {
return err return err
} }
if _, err := writeString(w, contentType); err != nil { if _, err := writeString(w, contentType); err != nil {
return err return err
} }
// ... // ...
} }
``` ```
@ -50,8 +50,8 @@ func writeHeader(w io.Writer, contentType string) error {
```go ```go
interface { interface {
io.Writer io.Writer
WriteString(s string) (n int, err error) WriteString(s string) (n int, err error)
} }
``` ```
@ -65,13 +65,13 @@ interface {
package fmt package fmt
func formatOneValue(x interface{}) string { func formatOneValue(x interface{}) string {
if err, ok := x.(error); ok { if err, ok := x.(error); ok {
return err.Error() return err.Error()
} }
if str, ok := x.(Stringer); ok { if str, ok := x.(Stringer); ok {
return str.String() return str.String()
} }
// ...all other types... // ...all other types...
} }
``` ```

View File

@ -12,10 +12,10 @@
import "database/sql" import "database/sql"
func listTracks(db sql.DB, artist string, minYear, maxYear int) { func listTracks(db sql.DB, artist string, minYear, maxYear int) {
result, err := db.Exec( result, err := db.Exec(
"SELECT * FROM tracks WHERE artist = ? AND ? <= year AND year <= ?", "SELECT * FROM tracks WHERE artist = ? AND ? <= year AND year <= ?",
artist, minYear, maxYear) artist, minYear, maxYear)
// ... // ...
} }
``` ```
@ -23,22 +23,22 @@ Exec方法使用SQL字面量替換在査詢字符串中的每個'?'SQL字面
```go ```go
func sqlQuote(x interface{}) string { func sqlQuote(x interface{}) string {
if x == nil { if x == nil {
return "NULL" return "NULL"
} else if _, ok := x.(int); ok { } else if _, ok := x.(int); ok {
return fmt.Sprintf("%d", x) return fmt.Sprintf("%d", x)
} else if _, ok := x.(uint); ok { } else if _, ok := x.(uint); ok {
return fmt.Sprintf("%d", x) return fmt.Sprintf("%d", x)
} else if b, ok := x.(bool); ok { } else if b, ok := x.(bool); ok {
if b { if b {
return "TRUE" return "TRUE"
} }
return "FALSE" return "FALSE"
} else if s, ok := x.(string); ok { } else if s, ok := x.(string); ok {
return sqlQuoteString(s) // (not shown) return sqlQuoteString(s) // (not shown)
} else { } else {
panic(fmt.Sprintf("unexpected type %T: %v", x, x)) panic(fmt.Sprintf("unexpected type %T: %v", x, x))
} }
} }
``` ```
@ -48,11 +48,11 @@ switch語句可以簡化if-else鏈如果這個if-else鏈對一連串值做相
```go ```go
switch x.(type) { switch x.(type) {
case nil: // ... case nil: // ...
case int, uint: // ... case int, uint: // ...
case bool: // ... case bool: // ...
case string: // ... case string: // ...
default: // ... default: // ...
} }
``` ```
@ -70,21 +70,21 @@ switch x := x.(type) { /* ... */ }
```go ```go
func sqlQuote(x interface{}) string { func sqlQuote(x interface{}) string {
switch x := x.(type) { switch x := x.(type) {
case nil: case nil:
return "NULL" return "NULL"
case int, uint: case int, uint:
return fmt.Sprintf("%d", x) // x has type interface{} here. return fmt.Sprintf("%d", x) // x has type interface{} here.
case bool: case bool:
if x { if x {
return "TRUE" return "TRUE"
} }
return "FALSE" return "FALSE"
case string: case string:
return sqlQuoteString(x) // (not shown) return sqlQuoteString(x) // (not shown)
default: default:
panic(fmt.Sprintf("unexpected type %T: %v", x, x)) panic(fmt.Sprintf("unexpected type %T: %v", x, x))
} }
} }
``` ```

View File

@ -4,17 +4,17 @@
這里顯示的是和這個API相關的部分 這里顯示的是和這個API相關的部分
<u><i>encoding/xml</i></u>
```go ```go
encoding/xml
package xml package xml
type Name struct { type Name struct {
Local string // e.g., "Title" or "id" Local string // e.g., "Title" or "id"
} }
type Attr struct { // e.g., name="value" type Attr struct { // e.g., name="value"
Name Name Name Name
Value string Value string
} }
// A Token includes StartElement, EndElement, CharData, // A Token includes StartElement, EndElement, CharData,
@ -37,55 +37,55 @@ func (*Decoder) Token() (Token, error) // returns next Token in sequence
下面的xmlselect程序獲取和打印在一個XML文檔樹中確定的元素下找到的文本。使用上面的API它可以在輸入上一次完成它的工作而從來不要具體化這個文檔樹。 下面的xmlselect程序獲取和打印在一個XML文檔樹中確定的元素下找到的文本。使用上面的API它可以在輸入上一次完成它的工作而從來不要具體化這個文檔樹。
<u><i>gopl.io/ch7/xmlselect</i></u>
```go ```go
gopl.io/ch7/xmlselect
// Xmlselect prints the text of selected elements of an XML document. // Xmlselect prints the text of selected elements of an XML document.
package main package main
import ( import (
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"io" "io"
"os" "os"
"strings" "strings"
) )
func main() { func main() {
dec := xml.NewDecoder(os.Stdin) dec := xml.NewDecoder(os.Stdin)
var stack []string // stack of element names var stack []string // stack of element names
for { for {
tok, err := dec.Token() tok, err := dec.Token()
if err == io.EOF { if err == io.EOF {
break break
} else if err != nil { } else if err != nil {
fmt.Fprintf(os.Stderr, "xmlselect: %v\n", err) fmt.Fprintf(os.Stderr, "xmlselect: %v\n", err)
os.Exit(1) os.Exit(1)
} }
switch tok := tok.(type) { switch tok := tok.(type) {
case xml.StartElement: case xml.StartElement:
stack = append(stack, tok.Name.Local) // push stack = append(stack, tok.Name.Local) // push
case xml.EndElement: case xml.EndElement:
stack = stack[:len(stack)-1] // pop stack = stack[:len(stack)-1] // pop
case xml.CharData: case xml.CharData:
if containsAll(stack, os.Args[1:]) { if containsAll(stack, os.Args[1:]) {
fmt.Printf("%s: %s\n", strings.Join(stack, " "), tok) fmt.Printf("%s: %s\n", strings.Join(stack, " "), tok)
} }
} }
} }
} }
// containsAll reports whether x contains the elements of y, in order. // containsAll reports whether x contains the elements of y, in order.
func containsAll(x, y []string) bool { func containsAll(x, y []string) bool {
for len(y) <= len(x) { for len(y) <= len(x) {
if len(y) == 0 { if len(y) == 0 {
return true return true
} }
if x[0] == y[0] { if x[0] == y[0] {
y = y[1:] y = y[1:]
} }
x = x[1:] x = x[1:]
} }
return false return false
} }
``` ```
@ -122,8 +122,8 @@ type Node interface{} // CharData or *Element
type CharData string type CharData string
type Element struct { type Element struct {
Type xml.Name Type xml.Name
Attr []xml.Attr Attr []xml.Attr
Children []Node Children []Node
} }
``` ```