l

2018年7月27日 星期五

阿嬤覺得你冷

July 27 10:09~11:11

螢幕截圖 2018-07-27 11.10.19

▲有一種趕,叫做司機覺得你趕


前言

有一種冷,叫做阿嬤覺得你冷。有一種敏捷,叫做長官腦中的敏捷。

***

故事一

去年年中有一位學員跟Teddy分享他們跑Scrum的經驗…

學員:我們跑Scrum已經一年了,我對團隊的表現很滿意。

Teddy:喔,怎麼說?

學員:因為我們團隊的產能很穩定,可預測性很高。

Teddy:你們是有經驗的Scrum團隊嗎?

學員:不是,我們一年前才剛開始接觸Scrum。

Teddy:所以你們新的Scrum團隊在這一年中的產能可預測性都很高?!

學員:對啊,這一年來,團隊的velocity都是50個story points,只有一個sprint稍微低一點42個story points。

Teddy:你們需求都沒改變嗎?

學員:有啊,這就是我覺得Scrum好的地方,我們團隊可以很敏捷的擁抱改變又維持固定的產出。

學員:有了穩定的產出我們做release plan就很簡單,照著計劃完成進度中的工作,沒什麼大意外,對老闆與客戶都很好交代。

Teddy:這樣啊,你說你們在跑什麼,waterfall嗎?

學員:不是啦,是Scrum!

友藏內心獨白:啊不就好棒棒。

***

後來經過側面了解,原來是他們公司大老闆在團隊跑Scrum之前曾經跟團隊精神喊話…

大老闆:我們團隊有五個人,一個sprint兩個禮拜,每個人每天做一點(1個story point)應該不過分吧?

從此之後大家的產出就變得很穩定了XD 。

***

故事二

幾年前有一位新創公司的老闆來上Teddy的Scrum課程,幾個月後在某個場合遇到他…

Teddy:上完課之後你們回去有開始試跑Scrum嗎?

老闆:有啊,而且成效很不錯。

Teddy:喔,怎麼說?

老闆:跑Scrum之後工程師都自願加班。

Teddy:啊,自願加班?!

老闆:對啊,以前專案時間是我估的,工程師抱怨說我估的時間太少,他們做不完,怪我逼他們加班。跑Scrum之後,時間都是團隊成員在sprint planning的時候估出來的。現在sprint結束之前user story做不完,團隊成員也沒話說,就會自願加班把沒做完的user story給做完。

Teddy:我上課沒這樣教吧?

老闆:是沒有,但是Scrum不是一種流程框架嗎,每個團隊可以依據不同狀況自行調整啊。我覺得我們現在這種調整很好。

友藏內心獨白:啊不就好棒棒。

***

結論

這1~2年「敏捷」在台灣突然有一種大爆發、大流行的趨勢,各種活動很多,有興趣的人也變多。

這種狀況,讓我想起電影達摩祖師傳中,達摩大師傳衣缽給慧可時對他說:「在我過世二百年後,衣缽傳至六祖,便不須再傳,因為在那個時候,禪宗法門已傳遍各地,不過,知道的人多而行道的人少,說理的人多而悟理的人少。」

不過達摩大師最後說:「你不可輕視執迷不悟的人,任何人在一念之差,都會棄惡從善。」這種毅力,應該只有得道高僧能做得到。

Teddy不是大師,只是工程師,離這個境界還差很遠。

***

友藏內心獨白:那個…那個誰,給我一根棍子先。

2018年7月6日 星期五

Clean Architecture(6):架構三原則三部曲—跨層原則

July 6 09:52~10:58

螢幕截圖 2018-07-06 10.58.15

▲直接跨層XD


前言

將軟體架構分層並且確認相依性嚴格遵守由外往內(由低層往高層)的這兩個原則之後,接下來會面臨到另一個問題:「物件跨層時該怎麼辦?」。

***

跨層範例

▼請參考下圖,AddHostController呼叫AddHost Use Case,它再呼叫Entity層的Host物件,並獲得一個Host物件。接著AddHost Use Case直接把Host物件往外傳給AddHostController。

螢幕截圖 2018-07-06 10.05.10


上面這個例子有兩件事情需要討論:

  • 內層物件可否跨層往外傳:舉例來說,位於Entity層的Host物件可以直接跨層(跨過Use Case層)傳給AddHostController嗎?依據相依性原則,上述情況還是有遵守「外層相依於內層」的規定,只不過AddHostController跨過Use Case這層相依於Entity層的Host物件。Teddy並未在《Clean Architecture》書中看到可否「跨層相依」的描述,而網路上國外鄉民所實作的範例,有些直接跨層相依,有些則是會將Host物件在AddHost Use Case轉過一次再傳給AddHostController(請參考稍後討論的Use Case雙向介面)。
  • 外層如何將資料傳給內層:假設AddHostController從瀏覽器收到使用者所填寫的「新增Host表單」,並將其內容轉成一個JSON物件。如果AddHostController直接將這個JSON物件傳給AddHost Use Case,將會破壞相依性原則,因為如此一來AddHost Use Case就相依於屬於應用程式框架的JSON物件。

▼直接把JSON從Controller往Use Case傳遞違反了相依性原則(假設JSON函式庫是由網頁應用程式框架所提供)

螢幕截圖 2018-07-06 10.29.56

***

定義雙向介面

▼如下圖所示,《Clean Architecture》書中建議Use Case需要定義「雙向介面」,簡單來說就是定義Use Case的Input與Output介面與參數的資料型態。

螢幕截圖 2018-07-06 10.32.55

Use Case本身提供Input介面與資料型別實作,外層的Controller要將參數傳給Use Case時,必須負責把參數型別轉成Use Case所定義的Input介面。

Use Case的Output介面則由Presenter所實作(Presenter是Model-View-Presenter設計模式裡面的那個Presenter),呼叫Use Case時Controller會傳入Presenter物件,Use Case則將透過Presenter物件輸出資料。


▼看一個程式範例片段,HostController的getHosts函數呼叫FetchHosts Use Case得到所有host的列表,它首先產生HostListPresenter物件與FetchHostsInput物件,並將它們傳給FetchHosts Use Case。FetchHosts Use Case從FetchHostsInput物件獲得搜尋物件的參數資料,並將找到的資料傳給HostListPresenter。

螢幕截圖 2018-07-06 10.40.21


▼FetchHostsUseCase介面定義

螢幕截圖 2018-07-06 10.45.04


▼FetchHostsInput類別。請注意,如果嚴格依據《Clean Architecture》書中的圖示,應該先將FetchHostsInput定義成一個介面,然後再讓FetchHostsUseCase提供這個介面的實作。在此Teddy偷懶簡化這個步驟,直接將FetchHostsInput設計成一個具體類別。

螢幕截圖 2018-07-06 10.45.49

▼FetchHostsOutput介面

螢幕截圖 2018-07-06 10.48.20


▼HostListPresenter

螢幕截圖 2018-07-06 10.49.15


▼FetchHostsUseCase實做

螢幕截圖 2018-07-06 10.52.54

***

結論

《Clean Architecture》整本書雖然有多達34章,如果可以掌握分層、相依性與跨層這三個重點,在實作Clean Architecture時應該就沒什麼困難之處。

***

友藏內心獨白:還有一個Missing Chapter沒討論…

2018年7月5日 星期四

Clean Architecture(5):架構三原則二部曲—相依性原則

July 5 16:17~17:23

螢幕截圖 2018-07-05 17.21.14


前言

軟體架構依據「距離I/O遠近」分層之後,接下來遇到一個問題:「如何管理各層之間的相依性?」。在《Clean Architecture》書中,把這個規則稱為相依性原則(Dependency Rule)。

***

低層相依於高層

《Clean Architecture》書中所定義的相依性原則只有如下的短短一句話:

Source code dependencies must point only inward, toward higher-level policies. (程式碼相依性必須只能往內,指向更高層級的策略。)


▼如下圖所示(圖片來源在此),內圈(較高層級)的元件不能知道(相依)任何外層的元件。也就是說,外層的函數、類別、變數或任何有名字的軟體實體,都不能出現在內層的元件之中。相依性只有一個方向,就是由外往內,由低層往高層。

CleanArchitecture

***

煩人的實作細節

相依性原則本身非常簡單,但實務上在系統中要完全落實此原則有許多細節要注意。

▼例如下圖中位於Entities層(最高層)的Host類別,依據相依性原則它不能相依於任何位於此層以外的「任何東西」。為了將Host物件儲存於資料庫中,Teddy使用了javax.persistence package裡面的annotation(@Entity、@Table、@EmbeddedId、@Column等)。

螢幕截圖 2018-07-05 16.34.14

雖然這些annotation是 Java Persistence API(JPA)標準的一部分,但它們的實作是由Hibernate所提供。嚴格來說,上面的程式違反了《Clean Architecture》的相依性原則。

如果不用annotation,也可以用XML檔案來註記,把對hibernate-jpa-2.1-api的相依性從原始碼中移除。但廣義的來講,雖然在原始碼中Host不直接相依於hibernate-jpa-2.1-api,但透過XML設定檔案還是間接的相依於hibernate-jpa-2.1-api,Teddy認為兩者效用實際上是差不多,只是把「顯性相依」轉成「隱性相依」,並沒有好到哪裡去,甚至更糟糕。

***

要完全把Host對hibernate-jpa-2.1-api的相依性拿掉,一個常見的作法是定義另一組針對持久化(Persistence )所需的物件,例如針對原本的Host物件的持久化需求另外定義一個HostEntity類別,如此一來原本Host類別中JPA annotation就可以移到HostEntity之中,然後再透過Hibernate把HostEntity直接存到資料庫。

但這種做法又衍生另其他問題,首先,必須另外撰寫物件負責把Host轉成HostEntity,以及把HostEntity轉成Host。這種物件稱之為Mapper

其次,HostEntity要擺在哪一層也是需要考慮一下,很顯然不會擺在Entities這一層。因為Repository通常被Use Case所使用,所以放在Use Cases層似乎很合理:當要儲存Host時,Use Case先產生或是獲得一個Host物件,然後呼叫Mapper把Host轉成HostEntity,在透過Repository將HostEntity存到資料庫。反之,要讀取Host時,Use Case透過Repository從資料庫讀取HostEntity,再把它轉成Host。

但是這麼做其實還是違反了相依性原則,因為Use Case相依於外部的hibernate-jpa-2.1-api。忙了大半天其實是白忙一場XD。

***

另一種作法是,不透過任何JPA annotation來簡化物件永久化的問題,只定義Repository介面,然後透過main元件注入Repository介面的實作。Repository介面的實作程式碼位於Interface Adapter層,也就是《Clean Architecture》架構圖裡面的Gateways。它再透過相依性注入的方式,獲得不同持久化框架的實作,如此一來便符合相依性原則。

但是,原本只要貼一貼annotation就可以把透過持久化框架(例如Hibernate)達到持久化的目的,現在為了嚴格遵守相依性原則,程式碼變得麻煩好多。這就是在實作上必須注意與取捨的地方。

***

結論

滿足相依性原則可以獲得兩大好處,首先,因為高層的物件不再相依於框架等低層的物件,所以測試碼可以單獨測試,大幅簡化測試的工作。

其次,系統的核心功能位於Entities與Use Cases這兩層(蛋黃區與蛋白區),要更換I/O或是應用程式框架「理論上」變得簡單很多。例如,如果要將Web-Based UI換成Android App,只需異動最外面兩層(Framework & Drivers與Interface Adapters),便可直接銜接原本的Use Cases與Entities。

***

友藏內心獨白:跟有潔癖的人住在一起家裡肯定很乾淨,但你可能會被對方煩死。

2018年7月4日 星期三

Clean Architecture(4):架構三原則首部曲—分層原則

July 3 14:38~15:58

螢幕截圖 2018-07-03 16.08.32


前言

《Clean Architecture》這本書一共有34章,全部讀完也是有點累。今天Teddy幫鄉民們整理一下重點,總地來講Teddy認為Clean Architecture有三個最重要的原則,將分三集逐一介紹這三個原則。今天先談「分層原則」。

***

階層式架構

軟體架構就是軟體的形狀(shape),而形狀是由多個元件及其互動所構成。這些元件通常會依據其功能加以分類,以便於管理、擴充與維護。


階層式架構就是一種很常見的分類方法,常見的階層式架構長成下面這樣 (詳細說明可參考https://goo.gl/Tjq75M):

螢幕截圖 2018-07-03 14.55.57


既然稱為階層式架構,一定有上、下層之分。請問上圖階層式架構中,誰是上層、誰是下層?

一般直覺的看法:Presentation Layer畫在架構的最上方,所以它是最上層,而Database Layer在最下方,所以它是最下層。這種解讀有一個問題,因為在階層式架構中,越上層的人(元件)通常擁有越核心的商業邏輯(business logic)。如果Presentation Layer是最上層,那豈不是代表它擁有最核心的商業邏輯?這也不對啊,因為大家都知道,儘量不要在Presentation Layer放置商業邏輯,因為那將使系統擴充、維護與測試變得更加困難。

***

定義高層與低層

《Clean Architecture》書中關於分層有一個非常明確的定義:「離I/O(輸入、輸出)越遠的元件層級越高,離I/O越近的層級越低」。這其實也很容易理解,在公司中,櫃台小姐離I/O(接電話、接待訪客)最近,通常層級比較低。董事長、總經理離I/O很遠,層級最高(通常看高階主管的辦公室位置離大門遠近就可猜出層級高低XD)。

▼Port and Adapter不把階層式架構畫成常見的垂直形狀,而是畫成圖1中的六角型。

螢幕截圖 2018-03-15 17.39.22


▼《Clean Architecture》書中則是畫成同心圓(圖片來源在此)。

CleanArchitecture


▲上圖中,蛋黃區(Entities)是架構中層級最高的一層,而最外圈屬於I/O層,是層級最低的。

《Clean Architecture》書中有四個預設的階層,分別是:

  • Entities:也就是傳統物件導向分析與設計所說的domain model object。
  • Use Cases:Entities這一層存放著核心商業邏輯,也就是在這個領域中,不同應用程式都用得到的物件。而Use Cases則代表應用程式邏輯,也就是應用程式的功能。Use Cases扮演著controller的角色,呼叫Entities或是Repository物件提供應用程式對外的服務。
  • Interface Adapters:將外部資料與呼叫介面透過此層轉呼叫Use Cases,如此一來Use Cases就可以與I/O或是應用程式框架無關。
  • Frameworks and Drivers:此層包含了應用程式框架,例如如果Java程式使用了Spring Framework,則Spring Framework就位於這一層。資料庫,或常見的MVC Framework也都位於這一層。通常大家在這一層所寫的程式都只是為了把應用程式框架與內部的Interface Adapters或Use Cases串起來的膠水程式,鮮少有複雜的商業邏輯會位於這一層。

***

結論

依據「元件距離I/O遠近」分好層級之後,高、低層之間的界線就非常的清楚。層級分明之後,接下來衍生出另一個問題:每一層之間的相依性要怎麼管理?如果層與層之間的相依性沒有好好管理,僅僅是分層你的架構還是很可能會一團亂。

下一集「相依性原則」將說明這個問題,下次見。

***

友藏內心獨白:真正的高層都是坐在離I/O很近的大門口XD。

2018年7月3日 星期二

LG gram 15Z980-R.AAS9U1 (2018) Amazon 購買與使用心得(下)

July 3 09:39~11:45

螢幕截圖 2018-07-03 11.43.37

▲LG gram 15吋比MBP15吋略寬、長度略短


開箱

▼快遞送來時的箱子。

螢幕截圖 2018-07-02 22.59.18


▼打開後有點傻眼,白色的紙箱上居然有很多黑色的指紋。

螢幕截圖 2018-07-03 09.42.07


▼而且很明顯地筆電的封條已經被拆開,這種情況只有兩種可能:(1)拿到別人退貨的商品;(2)商家自行拆封安裝RAM與硬碟。不管是哪一種情況,至少也把盒子擦乾淨一點吧…Orz。

螢幕截圖 2018-07-03 09.44.00


▼打開盒子。

螢幕截圖 2018-07-03 09.47.30


▼內裝很單純:筆電、電源線、一條無三小路用的USB-A轉100M的網路線。這肯定是清庫存,都什麼年到了還配100M的有線網路,直接用WIFI還比較方便。有線網路好歹也給個1G吧。

螢幕截圖 2018-07-03 09.50.36


▼用單手就可以輕鬆打開上蓋。

螢幕截圖 2018-07-03 09.55.21


▼鍵盤的排列方式,沒有注音符號好像比較清爽一些。

螢幕截圖 2018-07-03 09.56.34


▼開機,第一次開機會執行Windows 10安裝。這是Teddy第一次買Windows 10筆電,之前都是在MBP中安裝Parallels跑Windows 10。

螢幕截圖 2018-07-03 09.57.49


▼安裝過程就不用看了,這是筆電內建的兩顆SSD硬碟。查了一下型號是海力士的M.2硬碟

螢幕截圖 2018-07-03 10.00.08

***

始用狀況—優點

這台筆電主是Teddy帶出門上課使用,平時在家裡還是用原本的MBP。筆電有兩顆512G SSD,C碟裝系統,D碟裝資料。Teddy把Dropbox設到D碟,目前空間分配還夠用。雖然內建的SSD速度並不是特別快,但Teddy也沒要用它做什麼偉大的事,暫時就先這樣。

把軟體都裝好後,還沒有機會帶它去上課。先說一下目前的使用感覺,先看優點:

  • 重量非常輕。這也是Teddy購買它的主要原因之一。
  • 續航力(理論上)應該很長。官方數據是16.5小時,打個五折至少也可以用的8小時。目前還沒機會帶去上課,過一陣子在報告實際使用續行狀況。
  • 開機速度很快,從按下開機扭到登入Windows約需20~25秒。從睡眠狀態按下電源鍵不用3秒就可喚醒(真的有在睡眠嗎?!)。
  • 電腦機身不會很燙,拔掉電源之後放在大腿上使用都OK。
  • LG 的軟體做得很好,重裝Windows之後只要下載LG Update Center軟體,就會自動幫你抓所有的驅動程式。
  • Windows 10的手勢操作,在切換虛擬桌面這一點和MacOS很類似(這可能是Teddy最常使用的手勢XD),所以原本在MBP上的「上課使用體驗」:一個桌面開PowerPoint秀簡報、一或多個桌面開IDE看程式碼、一個桌面開時鐘倒數計時,換到Windows 10筆電還是差不多。
  • 喇叭音效還不錯(沒有MBP好但也不錯了)。
  • 開啟一個130頁的PowerPoint只要二點多秒,播放投影片中嵌入的影片或外部連結影片也都很順暢。
  • 效能不是頂尖但尚可接受。LG gram開啟IntelliJ專業版約需16秒,MBP則只需10秒多一點點。LG gram的速度比MBP慢可能是SSD速度的關係,不過也還在可以接受的範圍。目前還沒有在LG gram寫程式的機會,日後有機會在分享coding的感覺XD。
  • 可重複使用type-c轉接器。原本給MBP使用Apple原廠的type-c轉VGA + type-a + type-c充電轉接頭,接在LG gram的Thunderbolt 3 type-c連接埠上可以投影。

螢幕截圖 2018-07-03 10.58.57


接上Teddy原本給MBP使用的CalDigit Thunderbolt 外接盒之後,可以支援雙外接螢幕顯示。


  • 支援Thunderbolt 3 type-c連接埠充電。在筆電送來的第一天,就被Ada給整台踢到桌下。螢幕背蓋刮了一條痕跡,非常纖細的充電器接頭也因此斷掉了…Orz。還好可以透過type-c連接埠充電,不然Teddy真的要哭哭了。

螢幕截圖 2018-07-03 11.02.00


▼當事貓在此,可能要教導Teddy世事無常,不可入相的道理,所以每天努力地搞破壞。

螢幕截圖 2018-07-03 11.04.45


  • 觸控螢幕功能可透過軟體關閉。觸控螢幕雖然方便,但有時候不小心碰到反而造成困擾。不想使用這個功能可以透過軟體關閉觸控螢幕。

***

始用狀況—可以更好

接下來談一下Teddy認為還可以更好的地方:

  • 觸控板小小一塊,雖然堪用但按下去「咖咖」的聲音和MBP的觸控板比起來差了一截。
  • 亮面觸控螢幕,稍微手碰一下就會在螢幕上留下痕跡。
  • 不知道是不是輕薄筆電的緣故,Teddy覺得LG gram的USB type-a與type-c連接埠在拔插時都很緊,要用點力。
  • 內建的SSD如果可以用料好一點,選用速度比較快的硬碟就更好了。
  • 內建的HDMI應該是 HDMI 1.4(規格書沒寫,Teddy也查不到),實際測試結果只可輸出4K 30Mz。

***

結論

Teddy沒有用過其他Windows 10的筆電,也許其他筆電也有相同的優點但Teddy無從得知。所以Teddy的評論只可視為從MBP轉移到LG gram的一種感受,同時具備輕薄與長效這一點目前看起來是無庸置疑的。

但也許等Teddy開始在LG gram上面寫程式,又會嫌它速度太慢也說不定XD。

***

友藏內心獨白:魚與熊掌何時才可兼得?!

LG gram 15Z980-R.AAS9U1 (2018) Amazon 購買與使用心得(上)

July 2 21:36~22:42

螢幕截圖 2018-07-02 22.07.23

緣起

Teddy目前使用的筆電是MacBook Pro (15 英寸,2016),購買至今約18個月,平常都是外接螢幕、鍵盤、滑鼠、觸控板當成桌機使用。雖然RAM只有16G有點不足,但整體速度也還算快。除了剛買的時候MacOS有點bug導致系統不穩以外,當作「桌機」使用大致都很滿意。但是,一旦攜帶出門,就有點傷腦筋。主要有以下幾點不方便之處:

  • 鍵盤:蝴蝶鍵盤真的非常、非常、非常難打。這一點真的很困擾,一台不好打字的筆電要怎麼長時間使用勒。
  • 什麼都需要轉接器:新一代的MBP全部連接埠都改成Thunderbolt 3 (type-c),Teddy帶筆電出門幾乎都是要去上課,因為不知道客戶端的投影機支援哪種介面,所以隨身攜帶type-c轉VGA,type-c轉HDMI,還有type-c轉type-a的轉接器。除了使用上不方便,還增加了外出包包的重量。
  • 重量:1.83公斤加上一大堆轉接器大概會超過2.2公斤。
  • 續航力:官方說可以使用10小時,實際是使用大概4點多小時。

今年比較常跑客戶端,剛好看到LG gram這款筆電重量輕且續航力長,於是就興起了買一台「外出型筆電」的念頭。這其間也研究了Acer、Asus以及其他國外品牌的筆電,不是螢幕太小(13~14吋),就是配備太爛(只有8GRAM還不可擴充,或是只有type-c gen1之類的),要不然就是學MBP只有type-c輸出,再不然就是為了減輕重量拔掉電池容量,好像都沒有LG gram來得合適,

***

準備購買

LG台灣已經多年沒有在台灣販賣筆電,今年不知怎麼的引進LG gram,原本打算在台灣購買保固與維修也比較有保障。無奈在台灣販售的LG gram都是「低規高價」(規格低,售價高)的版本,相較於Amazon上的售價,實在是買不下手。其實價格只是次要因素,主要是Teddy想買觸控螢幕Thunderbolt 3指紋辨識16G RAM1T SSD(512 GB X 2)的這個版本,只有在Amazon上販售,台灣最高等級只出了一款指紋辨識的版本,最後決定「賭人品」(從國外購賣在台灣就沒有保固,真的出問題要寄回美國,還挺麻煩的),直接從Amazon購賣寄到台灣。

***

購買與報關

▼在Amazon上找到可以直接寄送到台灣的買家,原本Teddy想拜託在美國出差的朋友帶回台灣,沒想到下單之後將寄送地址選美國,稅金居然5千多台幣。而直接送到台灣,運費加台灣的稅金也只要3千多,那還不如就直接寄回台灣吧。


▼直接寄到台灣的費用。

螢幕截圖 2018-07-03 09.26.22


▼寄到美國國內的費用

螢幕截圖 2018-07-03 09.25.56


6月22日早上11點下單,25日寄到香港,26日就寄到台灣。貨物快寄到台灣之前,快遞業者寄了封信給Teddy,要填妥委託報關資料,以及一份NCC自用切結書。因為筆電有無線訊號,所以需要NCC審核才可進口。如果是個人使用,可以申請免驗。把資料填好寄給報關業者它們就會幫忙報關,所以並不困難。麻煩的點在於,怎麼填寫工作頻率輸出功率。Teddy找了老半天,就是找不到。後來查到有人說工作頻率就填寫WIFI支援的標準即可,至於輸出功率可以查詢筆電使用哪個牌子的網卡,再依據這個資料去Google應該就可以找到。只要數據不要太誇張,一般都可以通過審查。

螢幕截圖 2018-07-02 22.15.33


▼如果購買的商品超過1千美元,還要到經濟部商檢局申請應施檢驗商品免驗申請

螢幕截圖 2018-07-02 22.23.28


資料都準備好後交給報關行,6/27號就寄到Teddy家。可惜當天Teddy在外面上課,請快遞業者6/28日下午再送來。從下訂到拿到筆電,前後6天,還算滿快的。

***

友藏內心獨白:希望人品不要太差。


參考資料: