l

2013年12月27日 星期五

例外處理實務做法(3):Designing for Recovery

Dec. 24 10:10~11:20

image

 

今天繼續介紹Rebecca J. Wirfs-Brock女士2006年在IEEE Software上發表的另外一篇文章《Designing for Recovery》所建議的例外處理作法,這裡可以下載作者提供的原文。

在〈例外處理實務做法(1):Toward Exception-Handling Best Practices〉,Teddy介紹了Rebecca 提出的9點例外處理實務做法。今天則是針對「如何讓自己的系統可以從錯誤中恢復正常」的角度,來探討例外處理策略。

 

1. 忽略請求

請注意這裡提到的做法是「忽略請求(ignore the request)」不是「忽略例外(ignore the exception)」。在某些特殊例外狀況,採取忽略請求的策略可以有助於系統恢復正常。例如,訂票系統突然在連續假日之前湧入大量的訂票請求,或是被駭客以「阻斷服務」攻擊。在這種情況之下,系統採取忽略請求的作法,雖然可能會降低系統服務的水準,但至少可以讓系統不要死當,有機會可以慢慢復原。

***

2. 承認失敗

承認失敗(admitting failure)就是所謂的fail fast,只要是發生例外就直接將例外往上傳,也不去管什麼系統狀態正不正確,可以不可重試(retry)的問題。基本上這種作法談不上什麼「為了復原而設計」,因為整個系統狀態可能根本就不對了,要復原變得相對困難,但也並非不可能。

在fail fast的策略之下還想要做到狀態回復,就必須要讓有足夠context的人來負責例外處理。例如,系統的controller呼叫一個信用卡授權元件,這個元件三不五時會當掉,導致整個系統停止運作。為了解決這個問題,controller可以再呼叫這個元件的時候,每次都重新啟動一個process或是JVM,這樣子就算是信用卡授權元件當機,也只會影響到自己的process/JVM,而不會讓整個系統都當掉。

***

3. 放棄

放棄(resign)基本上和fail fast一樣,就是承認失敗,丟出例外。唯一的不同就是放棄還需要保證系統狀態是正確的,所以要做一些cleanup的動作。Rebecca把fail fast的失敗稱為failure,而resign的失敗稱為definite failure,以資區別。

***

4. 聰明的重試

如果造成錯誤的狀態只是短暫的現象,則重試(retry)就有很大的機會可以讓系統繼續運作下去。網頁無法載入的時候大家第一個反應是什麼?一定是按下瀏覽器的「重新載入(reload)」按鈕,這就是一種手動的重試。

採用重試策略的時候,需要考慮最多重試幾次多久重試一次等問題,通常做這些決定並不容易。最常見的作法是:連續重試三次,如果都失敗就放棄,但這種做法並不一定都會成功。有個朋友告訴Teddy 一個實際案例,他們有一個銀行業的客戶向他們抱怨,他們的某監控系統每天早上的時候都處於斷線狀態。查了好久終於發現,原來是每天晚上銀行的主機在執行大量的批次處理工作,導致有一段時間CPU負載很高,對於監控系統的連線要求沒有回應。雖然系統具有連續重試三次的功能,但是由於在短時間內重試,CPU依舊處於高負載狀態,系統還是沒有回應,所以造成白天客戶上班的時候,發現監控系統處在離線的狀態。

由以上例子可知,聰明的重試需要了解應用程式的執行環境與運算特性,才能夠發揮最好的效果。

5. 拖客戶和使用者下水

有時候失敗、放棄、自動重試都不可行或是不被允許,這時候就必須向更加聰明的人類求救,讓客戶或是使用者來決定處理方式。例如,印表機卡紙,就算是系統自動重試N次還是沒用,需要使用者先排除卡紙的狀態。另外還有想是要存檔的時候空間不足,這時候就需要詢問使用者,是要自行清出空間之後然後重試,還是要將檔案改存到其他的空間裡面。

採用這種策略的時候,記得要提供使用者足夠明確的選項與說明資訊,否則使用者也不知道要如何選擇與協助,讓系統繼續運作下去。

***

6. 向「高人」求援

當前面幾種策略都試過,依舊無法讓系統恢復正軌,這時候就必須要向外求援(appealing to a higher authority)。例如,找IT部門的系統管理員來幫忙,或是將產品送回給廠商維修。

向外求救之前,系統最好能夠提供足夠的除錯訊息,幫助這些「高人」縮短修復的時間。

***

友藏內心獨白:6個策略護一生。

2013年12月26日 星期四

例外處理實務做法(2下):Unhandled Exception、Unchecked Client Problem、Exception Wrapping、Safety Net

Dec. 23 18:15~19:10

-2013-12-23-11.28.22_thumb2

圖片來源在此

今天要介紹《Java Idioms: Exception Handling》這篇文章中剩下的四個pattern:Unhandled ExceptionUnchecked Client ProblemException WrappingSafety Net

***

Unhandled Exception

在實做函數功能的時候,你遇到了Checked Server Problem(也就是checked exception),但是此時你只想要實作正常邏輯,並不想要處理例外行為。這時候,不可以用一個空的catch block捕捉例外並忽略它,而是先定義一個屬於RuntimeException的UnhandledException,將Checked Server Problem串接在UnhandledException身上,然後丟出這個UnhandledException代表目前該例外還沒有被處理。

這個pattern的做法,和Teddy之前在〈敏捷式例外處理設計 (3):我到底哪裡做錯之 ignored checked exception〉文章中介紹過的「Replace Ignored Checked Exception with Unchecked Exception」這個例外處理重構方法所講的是一樣的原理。

***

Unchecked Client Problem

一個函數的執行需要依賴它的輸入參數,如果輸入參數錯誤,則函數的執行也會產生錯誤。在這種情況下,讓Throwing Server丟出一個RuntimeException的子類別,用來代表輸入參數錯誤的問題。在Java語言中,IllegalArgumentException、IllegalStateException都是這個pattern的例子。

Teddy在這裡要補充說明一下,這篇文章用Checked Server ProblemUnchecked Client Problem分別代表一種使用checked與unchecked exception的時機。但實際上checked與unchecked exception在Java語言裡面扮演的角色,Teddy認為應該從component fault與design fault的角度來來解釋,這樣子涵蓋面比較完整。請參考〈Fault、Error、Failure、Exception〉的說明。

***

Exception Wrapping

若一個函數宣告它會丟出Homogeneous Exception,則將原本發生的例外串接在Homogeneous Exception身上(把一個例外串接到另外一個例外裡面,這個動作叫做wrapping,打包的意思)。讓客戶端的程式捕捉到Homogeneous Exception的時候,可以透過被打包的例外物件,知道例外發生的根本原因。

事實上,Exception Wrapping是一種很常見的拋出例外技巧,像是Unhandled ExceptionTunneling Exception也都會套用這個技巧。

***

Safety Net

如果例外沒有被捕捉,最後會導致程式不預期的終止。因此,安裝一個預設的例外處理程序,把它當成一個安全網,用來捕捉Throwable例外。這個安全網可以安裝在application、thread、或是thread group身上。

這個pattern和Teddy之前介紹過的「Avoid unexpected termination with big outer try block」 這個 重構方法基本上是一樣的,請參考〈敏捷式例外處理設計 (6):我到底哪裡做錯之 unprotected main program〉。

 

***

分三次把《Java Idioms: Exception Handling》這篇文章所介紹的11個例外處理模式都介紹完畢,以Teddy自己的經驗,這11個模式所提出的建議,都是很實用的例外處理實務做法。雖然作者的出發點是以Java語言為目標,但很多模式也同樣使用於只支援unchecked exception的語言,例如C#)。像是Expressive Exception Interface、Homogeneous Exception、Exception Hierarchy、Smart Exception、Tunneling Exception、Safety Net這幾個pattern,使用C#或其他非Java語言的鄉民們可以驗證一下,是否也可以一體適用。

***

友藏內心獨白:一魚三吃。

2013年12月25日 星期三

例外處理實務做法(2中)Exception Hierarchy、Tunneling Exception

Dec. 23 15:37~17:07

-2013-12-23-11.28.22_thumb2

圖片來源在此

 

在前一集介紹了Expressive Exception InterfaceThrowing ServerChecked Server ProblemHomogeneous ExceptionSmart Exception,今天繼續介紹《Java Idioms: Exception Handling》這篇文章中所提到的Exception Hierarchy與Tunneling Exception。

***

Exception Hierarchy

使用Homogeneous Exception的好處是客戶端程式可以用相同的方式來捕捉例外,在〈例外處理實務做法(2上)〉介紹Homogeneous Exception所提到的例子,無論是login()、withdraw()、deposit()發生例外,只要捕捉ATMOperationException即可。

login() throws ATMOperationException

withdraw() throws ATMOperationException

deposit() throws ATMOperationException

但是,Homogeneous Exception的優點也是它的缺點。萬一客戶端程式想要用不同的方式來處理login()、withdraw()、deposit()的例外該怎麼辦?

因此,建立例外類別繼承架構,讓更精確的例外類別繼承Homogeneous Exception。在介面宣告上函數會丟出Homogeneous Exception,在實作上則是丟出例外子類別。如此客戶端程式便可自由的選擇要捕捉Homogeneous Exception,或是其子類別。

 

螢幕快照 2013-12-23 下午3.59.51

 

Exception HierarchySmart Exception都是一種用來提供詳細錯誤資料的方法,前者透過繼承,後者則是透過包含一個錯誤資料列舉物件。Exception Hierarchy可以讓客戶端直接用不同的catch block來捕捉例外,省卻了Smart Exception只用一個catch block,但卻需要在catch block裡面用if條件式依據列舉物件的值來判斷錯誤原因的作法。

通常,如果錯誤原因不是很多,或是不同錯誤原因的處理方式各不相同,則可能會考慮採用Exception Hierarchy的方法。例如,Java的IOException就是一種Homogeneous Exception,而其子類別有:

ChangedCharSetException, CharacterCodingException, CharConversionException, ClosedChannelException, EOFException, FileLockInterruptionException, FileNotFoundException, FilerException, FileSystemException, HttpRetryException, IIOException, InterruptedByTimeoutException, InterruptedIOException, InvalidPropertiesFormatException, JMXProviderException, JMXServerErrorException, MalformedURLException, ObjectStreamException, ProtocolException, RemoteException, SaslException, SocketException, SSLException, SyncFailedException, UnknownHostException, UnknownServiceException, UnsupportedDataTypeException, UnsupportedEncodingException, UserPrincipalNotFoundException, UTFDataFormatException, ZipException

各自代表不同的錯誤狀況,處理的方式也都不相同,這時候套用Exception Hierarchy就比較合適。如果改用Smart Exception則每個catch (IOException e)的區塊都還要用if條件式來判斷詳細錯誤原因,很像傳統程序導向的寫法。

Java裡面有沒有Smart Exception?有,就是SQLException,她有一個public int getErrorCode()會傳回vendor-specific exception code(資料庫廠商所定義的錯誤代碼)。SQLException也是一個Homogeneous Exception,在處理資料庫的時候也會有很多種不同的錯誤原因,例如刪除的資料不存在、鍵值重複、表格不存在、資料表被鎖住、沒有操作權限等。造成這些錯誤的起因都可被歸類為「執行SQL指令所發生的錯誤」,只是產生錯誤的原因不同。再加上很多存取SQL資料庫的介面都是用error code來代表錯誤,因此用Smart Exception就比較合適。如果這裡要套用Exception Hierarchy,則有可能要產生數十到上百個例外物件,數量過多。

再補充說明,其實SQLException也有如下的子類別,和SQLException本身扮演Smart Exception所代表的錯誤狀況不同,以下這些例外,各用來表示與SQLException應採用不同處理方法的例外狀況,所以套用了Exception Hierarchy

BatchUpdateException, RowSetWarning, SerialException, SQLClientInfoException, SQLNonTransientException, SQLRecoverableException, SQLTransientException, SQLWarning, SyncFactoryException, SyncProviderException

***

Tunneling Exception

有時候一個函數不能宣告自己要丟出checked exception,例如你要實做別人定義的callback函數或是command pattern,如果原本的callback或是command沒有宣告會丟出任何checked exception,你的實作也就無法丟出任何的checked exception。

因此,定義一個繼承自RuntimeException的TunnelingException,丟出這個TunnelingException,並且把原本想要丟出的checked exception串接到TunnelingException身上。

TunnelingException是一種用在callback函數或是command pattern的例外宣告技巧,並非一定都是以RuntimeException子類別出現。例如,Java用來處理XML的ContentHandler介面,裡面有好幾個函數都宣告會丟出SAXException(一個checked exception),這個SAXException也是扮演TunnelingException的角色。也就是說,不管實作上可能會遭遇什麼例外,都要透過這個TunnelingException來傳遞。

還有沒有其他例子,有Java的AutoCloseable介面就只有一個函數:void close() throws Exception,這個Exception也是扮演著TunnelingException的角色。

***

還剩四個pattern,下集繼續。

***

友藏內心獨白:這兩個pattern都很實用。

2013年12月24日 星期二

例外處理實務做法(2上):Expressive Exception Interface、Throwing Server、 Checked Server Problem、Homogeneous Exception、Smart Exception

Dec. 23 10:18~12:03

螢幕快照 2013-12-23 上午11.28.22

圖片來源在此

 

今天介紹Arno Haase在《Java Idioms: Exception Handling》這篇文章中所介紹的幾個針對Java語言的例外處理實務做法,文章可在此下載。Haase的文章採用pattern的格式來撰寫,Teddy將以簡短的說明來解釋這些pattern的用意。

***

Expressive Exception Interface

例外的產生,造成丟出例外的人,以及接收例外的人之間的隱性耦合(hidden coupling)。寫過程式的鄉民們應該都知道,耦合關係如果處理不好,會造成程式難以理解、修改、擴充。由於例外地傳遞具有「非區域性(non-local)」的特性,也就是會沿著call chain的路徑一直往上傳遞,如果沒有妥善的規劃,很可能在執行期間造成不預期的錯誤狀況。

因此,將例外視為介面的一部分,不僅應該宣告在函數(function、method)之上,還應該讓使用者知道,整個類別或是package會產生那些例外。並且在設計軟體架構的時候,將例外處理的問題列入考慮,訂定系統面的例外處理策略。

***

Throwing Server

一個函數的執行發生了問題,但是這個問題無法在函數內部被解決,該如何處理?

用例外來通知客戶端程式,該函數的執行失效。如果執行失效的問題是由客戶端所產生,例如傳入不正確的參數,使用Unchecked Client Problem,否則使用Checked Server Problem。

***

Checked Server Problem

使用checked exception來表示一個Throwing Server(一個函數或是軟體元件)的執行結果為失效(failure)。其失效原因為該Throwing Server內部實做遇到錯誤狀況,而非因為無效的輸入所引起。

***

Homogeneous Exception

如果每一個函數都套用了Checked Server Problem這個模式,那麼一個函數的介面可能會有很多不同的checked exception,加總起來某一個類別的全部函數所丟出的例外種類將會非常的驚人。

因此,產生一個新的例外類別,用這個例外類別來代表同一個類別所有函數可能會丟出的例外。這種例外類別稱之為同質性例外(homogeneous exception)。例如,一個ATM類別的login()、withdraw()、deposit()函數可能會遇到IOException、SQLException:

login() throws IOException, SQLException

withdraw() throws IOException, SQLException

deposit() throws IOException, SQLException

鄉民民可能會想,上述作法違反了昨天〈例外處理實務做法(1)〉所建議的第4點「將低階層的例外轉成高階層所理解的例外」,直接暴露了實做細節的例外。因此,改成:

login() throws loginException

withdraw() throws WithdrawException

deposit() throws DepositException

修改後的版本雖然符合了「將低階層的例外轉成高階層所理解的例外」這條建議,但是卻可能產生過多例外類別,違反了「避免宣告很多例外類別」這條原則。如果鄉民們並不希望一個ATM類別的不同函數各自丟出不一樣的例外,則可套用Homogeneous Exception模式來解決這個問題:

login() throws ATMOperationException

withdraw() throws ATMOperationException

deposit() throws ATMOperationException

然後將將產生裡外的原因串接到ATMOperationException身上,以便接收到例外的人可以有足夠的脈絡資源可以進一步地進行處理。

***

Smart Exception

客戶端程式捕捉到例外之後,有時候需要依據例外發生原因而設計不同的例外處理方式。例如當你遇到LoginException這個例外,如果是帳號不存在,你會問使用者是否要新增帳號。如果是密碼錯誤,你會顯示重新登入或是寄發密碼的選項。要如何讓捕捉到例外的人可以進一步依據例外發生的原因,來設計不同的處理方式?

設計一個列舉類別(enumeration class)來代表錯誤發生的原因。在例外類別之中包含將這個例舉類別,使得例外物件變得「聰明」,讓客戶端程式可以直接透過以下方式來依據不同錯誤原因執行不同的處理方式:

螢幕快照 2013-12-23 上午11.47.25

圖片來源在此

***

最後總結一下今天介紹的幾個例外處理pattern:

  • Expressive Exception Interface:和Teddy之前介紹過的〈為什麼例外處理那麼難(2):設計觀點〉要強調的內容一致。和昨天介紹的「盡可能在接近問題發生處來處理例外」與「將例外處理責任指派給可以做決定的物件」這兩個例外處理實務做法相關。
  • Throwing Server:和「僅使用例外來發出緊急事件」相關。
  • Checked Server Problem:這是Java對於checked exception使用的另一種表示方法,類似Teddy之前介紹過的〈Fault、Error、Failure、Exception〉,用checked exception來代表某個函數執行結果為failure。
  • Homogeneous Exception:和昨天介紹的「避免宣告很多例外類別」、
    「將低階層的例外轉成高階層所理解的例外」、「用哪裡出了問題幫例外命名,而不是用誰丟出了例外來命名」有點相關,都是用來告訴鄉民們如何設計例外類別。。
  • Smart Exception:可以視為「伴隨著例外物件提供足夠的脈絡資訊」以及〈例外處理的四種Context(1):Exception Context〉的一種實作範例,讓例外物件夾帶更多的脈絡資訊,以方便捕捉到例外的人設計例外處理策略。

這篇文章的內容有點多,剩下的pattern下集繼續介紹。

***

友藏內心獨白:Homogeneous Exception還蠻常用的。

2013年12月23日 星期一

例外處理實務做法(1):Toward Exception-Handling Best Practices

Dec. 22 12:43~15:03

image

 

寫了幾十篇例外處理的文章,分別介紹了例外處理重要名詞、物件導向語言的例外處理機制、Java checked exception的用意與實際應用上的問題、try-catch-finally責任分配、例外處理為什麼這麼困難、例外處理的強健度等級、例外處理壞味道、例外處理重構等。鄉民們可能以為例外處理設計的議題大概都談得差不多了,沒什麼戲可唱。如果這樣想就太單純了,有關例外處理的議題,還有很多東西可談。接下來花一點時間來談談例外處理實務做法(exception handling practice)

不同的人,不同的派別,對於例外處理,有著不同的建議。今天先介紹Rebecca J. Wirfs-Brock女士2006年發表在IEEE Software上面的一篇文章《Toward Exception-Handling Best Practices and Patterns》中所建議的9點作法,這裡可以下載作者提供的原文。

 

1. 不要處理程式設計錯誤

設計錯誤(coding error或programming error),例如除數為0、null point exception、index out-of-bound,這些都是所謂的design fault(請參考〈Fault、Error、Failure、Exception〉對於fault的解釋),或是一般人認知的程式bug。程式有bug就應該「把程式碼拿出來修正」,而不是用例外處理的方式來解決。例外處理要解決的是component fault,如果要用例外處理來解決design fault,則這種作法已經將例外處理提升到「fault tolerance programming(容錯設計)」的等級,所花費的時間與成本將大幅增加。

 

2. 避免宣告很多例外類別

只有在你認為新的例外將會以不同的方式來被處理的情況下,才宣告(定義)新的例外類別,否則盡量沿用現有的例外。這一條建議是提醒開發人員,避免盲目定義過多例外類別,因為每一個例外類都會占用系統資源,而且可能會干擾其他人的開發工作。

想像一下,一個系統有很多名稱不同,但是處理方法卻幾乎一樣的例外類別,這可能會對開發人員造成不必要的干擾。

 

3. 用哪裡出了問題幫例外命名,而不是用誰丟出了例外來命名

「命名」一直是撰寫易讀、易懂程式的基本功夫,像Kent Beck在《Implementation Patterns》(中文版:Kent Beck的實做模式),Robert C. Martin在《Clean Code》(中文版:無瑕的程式碼)都提到命名的技巧。對於例外類別的命名,應該以「發生什麼問題」來命名。例如一個ATM類別的withdraw()函數,負責實做提款功能。如果存款餘額小於提款金額,則withdraw()的執行會發生錯誤而丟出一個例外。假設鄉民們要設計一個新的例外類別來代表提款錯誤狀況,可能有以下幾個候選名稱:

  1. ATMException
  2. WithdrawException
  3. NotEnoughMoneyException。

前兩者的名稱比較接近「誰丟出了例外」,而NotEnoughMoneyException則是以「哪裡出了問題、出了什麼問題」來命名。如果鄉民覺得NotEnoughMoneyException太過詳細,如果用這種觀點來宣告例外,可能會違反第2條「避免宣告很多例外類別」的原則,那麼也可以設計一個比較一般性的TransactionException來代表交易例外,然後把NotEnoughMoney當成TransactionException的錯誤內容。

 

4. 將低階層的例外轉成高階層所理解的例外

這個實務做法建議鄉民們不要將實作例外(implementation exception)直接跨層(layer)傳遞給上層的人,因為這樣一來收到例外的人會被底層的實作細節給綁住,造成不必要的相依性。假設鄉民們有一個儲存設定檔的函數saveProperty(),這個函數的第一版實作採用檔案來儲存設定檔,所以當存檔失敗之後會丟出IOException:

public void saveProperty() throws IOException

如果有一天實作方法改變,改成用資料庫來儲存設定資料,則saveProperty()儲存失敗之後就不會丟出IOException,而是改丟出SQLException:

public void saveProperty() throws SQLException

上述兩種作法,都是將底層的實作例外直接丟給上層的人,並不是好的做法。所以在這裡可以套用第3條實務做法的建議,改丟一個PropertyManipulationException。

 

5. 伴隨著例外物件提供足夠的脈絡資訊

Teddy在〈例外處理的四種Context(1):Exception Context〉介紹過,例外物件是提供例外處理設計資訊的第一個候選人,舉凡像是丟出例外的函數所接受的參數、錯誤訊息、錯誤可能的原因、其他任何有助於設計錯誤復原的資料,都應該要一併提供。

 

6. 盡可能在接近問題發生處來處理例外

俗話說:「遠水救不了近火」,例外處理通常具有區域性,距離問題發生點愈近的地方,因為擁有local context與object context(請參考〈例外處理的四種Context(2):Object Context與Local Context〉),越有可能提供具體的例外處理或是狀態恢復方法。距離問題點越遠的地方,通常只能提供很一般性的例外處理方法,例如顯示錯誤訊息,或是將錯誤訊息記錄在日誌檔中。

 

7. 將例外處理責任指派給可以做決定的物件

雖然在第6點提到「盡可能在接近問題發生處來處理例外」,但是有時候距離問題很近的人權限或是授權不足,無法做決定。因此這一條實務做法建議,將例外處理責任指派給可以做決定的物件。其實這就是Teddy在〈例外處理的四種Context(3):Application Context〉所提到的,從軟體架構的角度來分析,看哪個物件有足夠的資訊可以做決定,就把例外交給這個物件來處理。

 

8.僅使用例外來發出緊急事件

這的實務做法的意思是只用例外來表示不預期的狀況,不要用例外作為改變程式控制流程的方法。這個關念Teddy在〈找不到資料要傳回Null還是丟出Exception?〉也介紹過,到資料庫去找學生的資料,如果找不到要傳回null或是Exception?因為找不到學生資料是很正常的一件事,所以在這個例子裡面,就不需要設計一個StudentNotFoundException來代表這個狀況。

 

9.不要反覆重丟相同的例外

如果捕捉例外僅僅只是為了寫入日誌檔,然後再把這個例外原封不動的重複丟出(rethrow),則這種作法其實沒有意義,也會傷害程式的效能。只需要在程式的最外面統一用一個Big Out Try Block來處理所有未被捕捉的例外即可,不需要在整個call chain裡面重複捕抓、丟出相同的例外。請參考〈敏捷式例外處理設計 (6):我到底哪裡做錯之 unprotected main program〉。

***

友藏內心獨白:實務做法都是從基本原理衍生而來的。

2013年12月22日 星期日

2012緬甸考察之旅Day6-B達瑪楊基佛塔

Dec. 17 23:08~23:42

達瑪楊基佛塔(Dhammayan Gyi)外型好像一座金字塔,網路上查到的資料說,該佛塔是浦甘王朝第四代皇帝拿刺度(Narathu)所建立。這位皇帝非常殘忍,下令佛塔的磚塊之間,不能有大於一根針的縫隙,否則將工匠砍手示眾。據說建塔期間砍了3000多人的手臂,這也太誇張了一點吧。想到台北市的人行道和柏油路的施工品質…無言挑眉質疑

螢幕快照 2013-12-17 下午11.08.42螢幕快照 2013-12-17 下午11.08.57螢幕快照 2013-12-17 下午11.09.11螢幕快照 2013-12-17 下午11.09.38螢幕快照 2013-12-17 下午11.10.16螢幕快照 2013-12-17 下午11.10.05螢幕快照 2013-12-17 下午11.10.30螢幕快照 2013-12-17 下午11.10.47螢幕快照 2013-12-17 下午11.11.07螢幕快照 2013-12-17 下午11.11.44螢幕快照 2013-12-17 下午11.11.53螢幕快照 2013-12-17 下午11.12.14螢幕快照 2013-12-17 下午11.12.28螢幕快照 2013-12-17 下午11.12.58螢幕快照 2013-12-17 下午11.13.06螢幕快照 2013-12-17 下午11.13.20螢幕快照 2013-12-17 下午11.13.32螢幕快照 2013-12-17 下午11.13.58

 

下圖十字形凹槽的大石頭,據說這就是砍手台,真的假的?

螢幕快照 2013-12-17 下午11.23.34

 

達瑪楊基佛塔蓋得很壯觀,來這裡的遊客好像不是很多。逛了一下發現有階梯可以爬上二樓,爬上去之後往下看才發現真的好高。不趕時間的話可以在這裡休息一下,沉澱一下心情。

螢幕快照 2013-12-17 下午11.28.37螢幕快照 2013-12-17 下午11.25.27螢幕快照 2013-12-17 下午11.25.40螢幕快照 2013-12-17 下午11.26.11螢幕快照 2013-12-17 下午11.26.19螢幕快照 2013-12-17 下午11.26.28螢幕快照 2013-12-17 下午11.26.44螢幕快照 2013-12-17 下午11.27.17

 

賣紀念品的小販,每一個著名景點一定會遇到。

螢幕快照 2013-12-17 下午11.31.49

 

疑似小販的便當盒。

螢幕快照 2013-12-17 下午11.31.59

 

發現兩個藏在佛塔裡的便當盒,這個洞應該不是為了藏便當盒挖出來的吧。

螢幕快照 2013-12-17 下午11.32.06

 

看幾張全景圖。

螢幕快照 2013-12-17 下午11.35.15

螢幕快照 2013-12-17 下午11.35.26

螢幕快照 2013-12-17 下午11.35.40

 

看到一隻睡的好熟的小狗,和Teddy小時候養的狗長得好像啊,好懷念。

螢幕快照 2013-12-17 下午11.35.58

***

友藏內心獨白:古時候的針應該也是很細吧。

2013年12月21日 星期六

2012緬甸考察之旅Day6-A早餐、達比紐佛塔

Dec. 17 22:06~22:55

飯店早餐的地點和晚餐一樣,都在河邊的一個木地板平台上面。但是晚餐的時候燈光非常昏暗,拍出來的照片幾乎一片漆黑,所以就沒有放到部落格裏面。早餐就不一樣,早上起來視野非常清楚,坐在這樣的環境裡面吃早餐真的非常奢侈的享受啊。

早餐的食物很好吃而且很新鮮,各式麵包、果醬、水果、果汁、熱咖啡、牛奶、煎蛋,邊吃邊看著漂亮的伊洛瓦底江景,要不是還有很多景點要參觀,真是想拿本書待在這裡不走了。

螢幕快照 2013-12-17 下午10.10.11螢幕快照 2013-12-17 下午10.07.14螢幕快照 2013-12-17 下午10.07.43螢幕快照 2013-12-17 下午10.08.14螢幕快照 2013-12-17 下午10.08.25螢幕快照 2013-12-17 下午10.07.58螢幕快照 2013-12-17 下午10.07.34螢幕快照 2013-12-17 下午10.08.42螢幕快照 2013-12-17 下午10.12.29螢幕快照 2013-12-17 下午10.08.58螢幕快照 2013-12-17 下午10.09.28螢幕快照 2013-12-17 下午10.09.19螢幕快照 2013-12-17 下午10.09.38螢幕快照 2013-12-17 下午10.09.54

***

離開飯店之後今天還是租腳踏車,首先來到達比紐佛塔(That Byin Nyu Pahto),「達比紐」是「無所不知」的意思,已有八百多年的歷史。據說這裡是蒲甘最高的佛塔,看了網路上別人拍的照片的確是很高,但看自己拍的照片卻不覺得,看來拍照的技術還要多磨練挑眉質疑

 

騎往達比紐的路上。

螢幕快照 2013-12-17 下午10.35.55螢幕快照 2013-12-17 下午10.36.05螢幕快照 2013-12-17 下午10.36.12

 

來到達比紐佛塔,遠看(下圖第一張照片,這是後來離開達比紐佛塔之後從較遠處拍攝的照片)真的覺得很高。

螢幕快照 2013-12-17 下午10.46.35螢幕快照 2013-12-17 下午10.37.06螢幕快照 2013-12-17 下午10.37.20螢幕快照 2013-12-17 下午10.37.28螢幕快照 2013-12-17 下午10.37.43螢幕快照 2013-12-17 下午10.38.20螢幕快照 2013-12-17 下午10.39.02螢幕快照 2013-12-17 下午10.38.55螢幕快照 2013-12-17 下午10.38.28螢幕快照 2013-12-17 下午10.37.52螢幕快照 2013-12-17 下午10.38.48螢幕快照 2013-12-17 下午10.38.07

 

螢幕快照 2013-12-17 下午10.42.44螢幕快照 2013-12-17 下午10.42.50螢幕快照 2013-12-17 下午10.42.58

***

達比紐佛塔附近還有一個不知道叫什麼名字的佛塔(下圖第一張照片),可以爬上去,從高處往下看,景色完全不一樣。這些佛塔的階梯都很陡,爬的時候要手腳並用,爬上去的時候有點「小嚇了一下」不要告訴別人

螢幕快照 2013-12-17 下午10.48.33螢幕快照 2013-12-17 下午10.44.50螢幕快照 2013-12-17 下午10.45.13螢幕快照 2013-12-17 下午10.45.32螢幕快照 2013-12-17 下午10.45.39螢幕快照 2013-12-17 下午10.45.46螢幕快照 2013-12-17 下午10.45.54螢幕快照 2013-12-17 下午10.49.15螢幕快照 2013-12-17 下午10.49.23螢幕快照 2013-12-17 下午10.49.35螢幕快照 2013-12-17 下午10.49.42螢幕快照 2013-12-17 下午10.50.33

***

友藏內心獨白:好懷念的早餐啊。