2016年8月29日 星期一

什麼是多型 (polymorphism)

所謂的"多型",指的是 runtime 時期執行的內容
在C#當中,有下列四種多型的意義,其中最常提及的就是OO的繼承式多型:
1). 繼承式多型 (inclusion)
在design time,可以用父類別型態容納子類別的物件,在 run time 進行函數呼叫時會呼叫到子類別的函數,重寫 (override) 父類方法

2). 泛型
List<T> 中的 T 就是參數型別,依據參數的型別決定實作的內容

3). 運算子多載 (overloading)
C#可以多載運算子 (ex: 可以重寫 == 的行為)

4). 強制同型 (coercions)
自動將型別轉換,ex: 在 run time 時 int 轉成double



"多型"與"動態繫結(Dynamic Binding)"是相關的, dynamic binding可利用(virtual function)來達。

1). 晚期繫結也稱之動態繫結(dynamic binding)。
簡單的說,就是物件的行為並不是在編譯時期 (compileir-time,就已經決定了。而是在程式執行時期於(晚期)(run-time)才動態地決定 的。如何動態地決定。就看物件當時的狀態(state)而定,物件封裝了所有可能的狀態處理 方法,並且根據外邊送來的訊息做出適當的反應。這也就是晚期連結的意義,這是物件導向 一個很重要的精神。

2).繫結
是將程式中所使用到的各名稱(包括程式名稱及變數名稱),分配到適當的記憶體位置。
其中在編譯過程中即完成連結的稱為靜態繫結(Static Binding),又稱為早期繫結(Early Binding)。如果是在程式執行過程中才完成繫結的,則稱為動態繫結(Dynamic Binding),又稱為延後繫結(Late Binding)。

2016年8月28日 星期日

專業主義

大概在今年6月底收到MTD team的需求,希望開發一個Tool,把所有machine test reports 做一個資料分析,彙整成一份報告(csv格式)。

以下是一開始從MTD team收到的資訊:

  • 目前大約有200台machines, 都放置在 ~\machine_data 資料夾裡面
  • 每個machine有60個test report要分析
  • 每個test report 依據test name,存放在~\mXXXX\test_results\YS@prepship\Test
  • 每個test report的xml格式都很類似


時程估算方面,依據之前工程師用matlab開發MAS的經驗,一個test report大約一個小時,所以粗估60個test reports大約要7天。

然後,我就開始做了...災難也開始了...

首先,根據MTD team的說法,由於我先前已經做了一個parsing tool,所以"只要修改一下"原本的parsing tool,便可以符合他們的需求。
這個認知整慘我了...
因為 input 與 output已經不同了,我幾乎是重寫整個parsing tool,這部分大概花4天,絕對不是"只要修改一下"

再來,MTD team給我的資訊只有部分正確,詳述如下:
  1. 目前大約有200台machines, 都放置在 ~\machine_data 資料夾裡面
    • 錯! 200台machines的資料放置在 ~\machine_data 與\\172.xxx.xxx.xxx\d 資料夾裡面
    •  ~\machine_data 不需要 Id/Password ,但是\\172.xxx.xxx.xxx\d 需要Id/Password才可連線,這部分大概花了1天
  2. 每個machine有60個test report要分析
    • 錯! 60個test report是200/250的machine,後來又新增加了12個350的test reports
    • 這12個新增加的test reports是全新格式,這部分大概花2天
  3. 每個test report 依據test name,存放在~\mXXXX\test_results\YS@prepship\Test
    • 錯!~\mXXXX\test_results\YS@prepship\Test 這個資料夾結構是人工後續手動輸入,因此有很多錯誤,造成test report 找不到
    • 因此,只好加入log,針對找不到的test report手動把資料夾結構調整成正確
    • 手動把資料夾結構調整再加上log機制,就花了3天
  4. 每個test report的xml格式都很類似
    • 錯!xml schema只要有一點點不同就必須"特別處理" (special handle)。幾乎每個test report都要個別處理,共通性的test report少之又少...
    • "特別處理"意味著需要額外的effort,大概花了6天
另外,最後彙整產出的csv檔案還需要加上:

  • 顯示machine type (從CM_Options.xml讀取)
  • 顯示單位(從HTML而來)
  • test report (xml) 與CS 習慣閱讀的HTML,KPI顯示的字串不同,必須一個一個檢查

這些零零總總又多花了3天...

總結上述天數是19天,是原本粗估(7天)的2.7倍...
在開始專案之前,我應該要審慎評估的,這才是專業!



2016年5月8日 星期日

使用16進位(hexadecimals)的時機與好處


寫程式的時候,有各種理由使用 16 進位。
其中一個理由是提醒工程師 - 我們要處理bits(用電腦世界的觀點),而不是處理數字(用人類世界的觀點)。使用 16 進位代表我們要處理相對低階的問題 (例如:記憶體資料的型式是必須被考慮)。

以下2個例子說明 16 進位使用時機:
[範例1]
 使用 bit shift (x << y) 比使用"乘法"更為清楚:
 1 << 10 = KB, 1 << 20 = MB, 1 << 30 = GB
 建立一個 16 KB 的array: var buffer = new byte[16 << 10]

[範例2]
 在 enum 當中, 項目是從 0 開始,而且依序加 1:
 使用 16 進位可以避免'負數'產生
 使用 16 進位可以使用bitwise AND (&) and OR (|) operators

 相關連結:
Why are flag enums usually defined with hexadecimal values: stackoverflow
使用16進位的好處是?? ptt

2016年5月4日 星期三

Implementation Patterns - Kent Beck (Chapter 6 State (狀態))


根據書上的建議,狀態的管理應該:
  1. 把相似的狀態放一起
  2. 把不同的狀態分離
要判斷兩個狀態(或是變數)的相似性,可以用下列方式:
  1. 他們在同一個 method 被用到
  2. 他們出現和消滅的時間相同
就以 MaterdiskAutoTool 的設計來當作例子看看設計...

[Case 1]:
MasterdiskAutoTool 會讀取 CM_Options.xml,來作為建立 Steps 的判斷。設計如下:


可以發現,Form 直接讀取 CM_Options.xml,然後再把 CM_Options.xml 的內容用參數的方式傳遞給其他的class (紅色框框的地方, 以及每個 Step)。
這種設計有下列缺點:
1.) 參數的傳遞路徑太長了,參數也太多了
2.) 這些參數透過Step的constructor傳進去變成step的全域變數,然後被Step裡面的 method 所使用,造成 method 裡面會有'全域變數'與'區域變數'交雜的情況,不容易閱讀
3.) 本質來說,Step 物件本身並不需要擁有這些參數,只有 Step 的 method 需要這些參數。'全域變數'與'區域變數'在此 method 的生命週期不一樣 (違反狀態管理原則)
4.) 因為Step 擁有CM_Options.xml的參數,當Step要增減這些參數時,必須修改很多地方  (:ex: constructor, 全域變數...)
5.) 不容易寫unit test (因為要準備太多參數當作input)

[Case 2]:
比較好的做法,是把讀取CM_Options.xml 的內容交給某個CMOptions class 處理 (橘色框框),然後物件向CMOptions class 要內容。
也就是說,CMOptions class只會出現在Step裡面的 method,Step 不會有CM_Options.xml 的內容變成全域變數 (生命週期一樣)。

修改後的設計如下:


這種設計可以避免上列的缺點,而且還有下列優點:
1.) CMOptions class只只需要出現在Step裡面的 method,Step不會有CM_Options.xml 的內容是全域變數 (因此 method 內的變數生命週期一樣)
2.) 一旦 CM_Options.xml 有變化,則不需要改變 MasterdiskAutoTool 的架構

[Case 3]:
由上面Case 2可以發現,幾乎所有Steps都必須使用 CMOptions class,造成關係太複雜。
可以再進一步改善一下: 讓 BaseStep 使用 CMOptions class,這樣每個Steps 就可以擁有 CMOptions class,免除每個Step自己使用CMOptions class。






Event 的意義

一個物件可以有三種東西:
  1. Property (ex: public string Name = "Peter";)
  2. Method (ex: public void Convert() {...} )
  3. Event (ex: public event EventHandler Click;)
一個物件擁有event, 它便可以通知其他物件"某件事情已經發生了"!
其中,一個 event 可以有多個 handlers 。 利用 event - handler 語法,我們可以建立一個 Notification system 。[註1] [註2]

若是要利用 event - handler 設計一個 Notification 機制,必須先確定下列3件事:
1. 誰有event,並且如何raise這個event
2. 誰handle這個event (也就是誰實作 callback function)
3. 兩者如何連結

以下用 Button 與 Form 做個例子:
1. Button 有 Clicked event,並且 user 透過 UI 來 raise 這個 event
2. Form 會 handle這個 Clicked event
3. Form 的 Constructor 實作把 button.Clicked 與 callback function 作連結 ( += )



再舉個 Queue 與 Producer 的例子:
當 Queue 有空位的時候,Queue物件 '通知' Producer物件進行 enqueue。(Queue 發出Event 給 Producer)
仿照 Button 與 Form 的設計如下:



1. Queue 有 NotifyToEnqueu 的 event,並且實作 OnNotifyToEnqueue() 讓 user (client code) 來raise 這個event,如下:

        public event EventHandler NotifyToEnqueue;

        protected virtual void OnNotifyToEnqueue()
        {
            if (NotifyToEnqueue != null) // null means no subscribe
            {
                NotifyToEnqueue(this, EventArgs.Empty); // raise event
                
                // Or, you can write like:
                // NotifyToEnqueue.Invoke(thisEventArgs.Empty);
            }
        }

2. Producer 會 handle 這個 NotifyToEnqueu event,如下:

        private void Queue_NotifyToEnqueue(object sender, EventArgs e)
        {
            m_MyQueue.Enqueue("aaa");
        }

3. Producer 的 Constructor 實作把 queue.NotifyToEnqueue 與 Queue_NotifyToEnqueue 作連結 (+= ), 如下:

public Producer(MyQueue queue)
        {
            m_MyQueue = queue;
            m_MyQueue.NotifyToEnqueue += Queue_NotifyToEnqueue; // subscribe
        }

所以,當呼叫 OnNotifyToEnqueue() 的時候, Producer物件就會進行 enqueue

[註1]
event 這個物件(ex: NotifyToEnqueue)的 invocations list 可用來儲存多種方法 (ex: Queue_NotifyToEnqueue方法)
要使用這些方法,只要呼叫 Invoke() 即可。 ex: NotifyToEnqueue.Invoke();

[註2]
An Event declaration adds a layer of abstraction and protection on the delegate instance.
This protection prevents clients of the delegate from resetting the delegate and its invocation list
and only allows adding or removing targets from the invocation list.

2016年5月1日 星期日

PlatformTarget 不同而造成的 Build warning

[Problem description]
在 Build C# 的過程當中,當某個 Project A 有 reference 到 Project B,如果這兩個 Projects 的 PlatformTarget 不同,則 MS-Build 結果有可能出現下列 Warning 訊息: (此 warning 不會出現在 VS-Build )

"warning MSB3270: There was a mismatch between the processor architecture of the project being build "MSIL" and the processor architecture of the reference "[internal C# dll]", "x86"."


[Root cause]
這是因為 兩個 Projects 的 Debug platform (或是 Release platform) 不相同所造成。如下圖所示:
MasterDiskAutoTool.Test.csproj 是一個 Unit test project,它會 reference 到 AutoTool.csproj。
然而,MasterDiskAutoTool.Test.csproj 的 release build platform 為 Any CPU,與 AutoTool.csproj 的 release build platform 的 x86 不相同。
因此,MS-Build 的結果有可能產生 Warning



[Solution]
把 MasterDiskAutoTool.Test.csproj 的 release build platform 改為 x86 (與 AutoTool.csproj 一樣),然後 MS-Build 就沒有Warning 了。
如下圖所示:




**PS: 另外一個因為Platforms 不同所造成的執行時期錯誤,請參閱 這裡


相關連結:
VS compile warning - “mismatch between processor architecture” stack overflow

2016年4月28日 星期四

Functional requirement & Non-Functional requirement

要寫好一份'軟體需求規格文件' (SRS) 不容易。 '使用者需求'是它的Input,'軟體設計文件' 是它的Output。
[User Requirements] --> [SRS] --> [SW Design (High Level Design + Detailed Level Design)]

什麼樣的SRS才是好的? 下列四點是個不錯的判斷依據:
1. 提供回饋給user。最好的回饋方式就是讓User看到! 比如UI、Output files, Tools
2. 用軟體的觀點來看問題,把問題分解成幾個SW components。What (SW components, Tools) should be enhanced/changed/updated/removed/created...
3. SRS是SW design 的Input。SRS 只需定義'規格' (What),它是一份合約。實作方式則定義在SW Design (How)。
4. SRS 必須用'外顯'、'可量測' 的角度來撰寫,好讓Tester判斷正確性。

看了許多的 SRS, 節錄出下列幾個重要方向:
Functional requirement: 定義系統要做的事 (What the system does)
Business Rules
External Interfaces (ex: UI, output files...)
Audit Tracking (ex: Logging)
Transaction corrections, adjustments and cancellations
Administrative functions
Authentication
Authorization levels
Certification Requirements
Reporting Requirements
Historical Data
Legal or Regulatory Requirements

Non-functional requirements: 定義系統的能力 (What the system's capability - the power to do it)
Reliability - 可以依據既定規格而持續運行的能力
Serviceability
Availability
Performance – for example Response Time, Throughput, Utilization, Static Volumetric
Scalability -  To handle a growing amount of work (系統處理工作量的成長)
Capacity - 系統可以產出(或提供服務)的最大量
Recoverability
Maintainability: 維持原有的形式,萬一發生錯誤也可以回復到原有的形式
Security
Regulatory
Manageability
Environmental
Data Integrity
Usability
Interoperability


Non-functional 影響層面有:
  • Reliability: Improvement in number of hits [hits/ syst yr] (Hit 次數)
  • Serviceability: Improvement on Maintenance and in MTTR (diagnostics, access, repair and/or recovery) repairing of system [hrs] (維修, 回復 時數)
  • Availability: Improvement in hours of downtime both USD (Unscheduled) as well as SD (Scheduled) [hrs/syst yr] (Downtime 時數)
  • Manufacturability: Improvement in CT (Cycle Time) or A-time in production [hrs/ syst]
  • Safety: Improvement of probability of incidents occurring
  • Usability: Functional improvement of machine for operator
  • Machine Performance: Non-functional improvement of customer requirements



2016年4月18日 星期一

The Benefits of Isolating Applications (隔離應用程式的優點)

本文先說明應用程式'隔離'(isolation)的概念, 接著再說明.NET 實作'隔離'的方式。

[應用程式'隔離'的概念]
在過去,Process boundaries 被用來達到 app 隔離的效果。App 必須被隔離是因為'記憶體位址' (memory address) 是 process 相關 (process-relative)。也就是說,這個 process 使用的 memory address 不能被另一個 process 使用。
因為 memory address 不能共用,因此,兩個 processes 之間也不能直接互相呼叫,也就是不能直接存取程式碼 (必須使用 proxies, 例如 Inter-process communication)。

而現在,managed code 在被執行之前,必須通過'權限驗證'(permission verification)才能被執行 (除非  administrator 已經授權 skip此驗證 )。權限驗證主要是驗證下列兩件事情:
  1. 程式碼是否可能嘗試存取無效的memory address
  2. 程式碼是否可能無法正常運行
如果上述的驗證通過了,此 managed code 稱為 型別安全 (type-safe) [** 注意: 這裡指的 type safe,指的是memory type safety (參閱 這裡),而不是廣義的type safe定義 (例如 integer 與 string 的型別)]。這種'權限驗證'提供了 CLR 具有與過去Process boundaries 相同的app 隔離效果,而且效率更好!

上述的說明,概念上解釋了隔離的重要性。接下來,說明.NET 如何實作隔離

[.NET實作'隔離'的方式]
在.NET中,使用 AppDomain 可以讓 CLR 提供 app 的隔離效果。CLR 允許單一個process裡面執行多個AppDomains,也就是說,單一個processe可以執行多個 apps,如此便增加了 server 端的 scalability。

AppDomain 具有下列優點:
  1). 在某一應用程式中的錯誤不會影響其他應用程式。由於 type-safe 程式碼不會導致記憶體錯誤,所以使用 AppDomain 可確保在某一Domain中執行的程式碼不會影響處理序(process)裡面的其他應用程式。

  2). 可以停止個別應用程式而不需停止整個處理序(process)。使用 AppDomain 可以卸載在單一個 app 中執行的程式碼。[** 注意: 這裡指的是 "卸載一個完整的AppDomain",而不是卸載一個DLL) (卸載AppDomain的方法, 參閱 這裡 ]

  3). 可以防止不同 AppDomain 中對於物件的呼叫。在不同定義域之間傳遞的物件必須用'複製方式傳遞'或由 'Proxy' 存取。
    3-1). 如果物件是複製的,那麼對該物件的呼叫就是區域呼叫。也就是說,呼叫端(caller)和被參考的物件是在同一個AppDomain。
    3-2). 如果物件是透過 Proxy 存取,那麼對物件的呼叫便是遠端呼叫(call remotely)。在這種情況下,呼叫端(caller)和被參考的物件(referenced object)是在不同的應用程式定義域中。跨定義域呼叫(Cross-domain call)是使用與兩個處理序(process)或兩部電腦之間呼叫相同的'遠端呼叫基礎結構'(remote call infrastructure)。因此,所參考之物件的中繼資料 (Metadata) 必須在這兩個AppDomain 中都能使用,才能讓該方法呼叫被 JIT 適當地編譯。如果caller 無權存取所呼叫之物件的中繼資料(Metadata),則編譯可能會失敗,並擲回型別 System.IO.FileNotFound 的例外狀況。

4). 程式碼的行為範圍(behavior scope)是由該應用程式決定。換言之,AppDomain會提供 app 版本原則、它所存取的遠端組件的位置,以及有關哪裡可以載入組件的資訊。



相關連結:
The Benefits of Isolating Applications: MSDN

2016年4月17日 星期日

Regex Tester (Regular Expressions 線上debug pattern)

發現一個不錯的網站, 可以直接在線上 debug 與 test Regular Expressions的 pattern,而且它還有解釋 pattern 的每個意義:

  regular expressions 101



先關連結:
Regular Expression的學習筆記

什麼是 AppDomain (Application domains, 應用程式定義域)

OS 和 CLR 提供了某種 '隔離' (isolatation) 的概念來確保 process 和 process 之間不會相互影響。Application domains 就是這種 '隔離' (isolatation) 的概念。有了AppDomain,CLR便可以提供組件的安全性 (security)、可靠性 (reliability) 、 版本控制(versioning) 以及 卸載組件(unloading assemblies) 。

AppDomain 是由runtime host 所建立。Runtime host 啟動 CLR之後, 然後再 開始執行 app 。也就是說,Runtime host 會為這個 app 建立一個AppDomain

有關什麼是 AppDomain的詳細說明, 請參閱 這裡


相關連結:
Application Domains : MSDN

2016年4月13日 星期三

NUnit gets wrong configuration file (NUnit test 讀不到 app.config)

[Problem description]
要讀取應用程式的app.config,若使用下列的 API,在 NUnit 環境下可能會讀取到錯誤的 config:

if (File.Exists(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile))
{
    // do something
}

雖然使用此 API,在執行應用程式時可以正確回傳應用程式的config 檔案 (ex: <MyAppName>.exe.config),可是在 NUnit 環境下,使用此API 卻會回傳另外的 config 檔案!
例如:
  • 使用 VS2010 + Resharper (v6.1) 會回傳 ~\UnitTest\bin\Debug 目錄下的 UnitTest.dll.config 
  • 使用 VS2015 + Resharper (v10.0) 會回傳 %temp% 目錄下某個隨機子目錄的 config 檔案
這種不確定性會造成 unit test 不夠 robust, 而且上述兩種環境都無法正確讀取到<MyAppName>.exe.config。

[Root cause]
AppDomain.CurrentDomain 可以用來取得目前應用程式的 domain 。
  1. 對於應用程式來說, AppDomain.CurrentDomain 指的是 exe 所在的目錄,一般是位於~\bin\Debug 
  2. 對於 NUnit  來說,它沒有 exe 檔案,所以指的是 UnitTest.dll 所在的目錄: ~\UnitTest\bin\Debug
  3. 然而, 使用 VS2015 + Resharper (v10.0) 卻指向 %temp% ,這個應該是設定上的問題。
既然在使用NUnit 上透過 AppDomain.CurrentDomain 只能取得 NUnit 自己的 domain ,無法取得我們想要的應用程式 domain ,因此無法透過此API 取得 <MyAppName>.exe.config

[Solution]
有下列幾種 work around:
  1). 改用應用程式的 Settings.setting 取代 <MyAppName>.exe.config
  2). 移除 app.config 與 MyAppName 的直接相依性, 改用 dependency injection 來建立 app.config 與 MyAppName的關係,如此便很方便 Mock 這個 app.config 在NUnit的程式碼當中




相關連結:
app.config 與 Settings.setting 的差異: MSDN forums, StackOverflow, User settings - MSDN
什麼是 AppDomain
AppDomain.CurrentDomain 屬性
Application 與 AppDomain.CurrentDomain 的差異

2016年4月11日 星期一

IsHandleCreated, InvokeRequired 與 Invoke 的關係

要存取 Windows Forms Controls 就必須考慮 Thread-Safe的問題 (參閱: 這裡)。如果 InvokeRequired 是 True,就必須使用 Invoke method 來保證 Thread-Safe。

然而,要使用 Invoke method,必須確定到這個 Form 或是 Control 已經擁有 '視窗控制代碼' (window handle)。
在下列的情況下,Handle 才會存在:
  • Form/Control 已經實體化( instantiated )且初始化 (initialized, first shown)
  • Form 還沒被 closed (i.e., handle 還沒被 disposed)
** 要注意的是,使用InvokeRequired 判斷是否為 cross-thread 並不正確! 因為 InvokeRequired 在下列2種情況會回傳 False:
  1. 相同 thread (表示不需要 Invoke)
  2. Form/Control 沒有 handle (沒有 handle 就不可能 Invoke)
也就是說,InvokeRequired  只能判斷要不要 Invoke!

若要使用InvokeRequired 判斷 cross-thread,必須排除 handle 不存在所造成的影響。
比較好的做法是: 使用 InvokeRequired 之前,先判斷 IsHandleCreated 為True

[例如]:
private delegate void SetTextDelegate(string text, Color color, Font font);

private void SetText(string text, Color color, Font font)
{
            if (IsHandleCreated)
            {
                 // 已經排除沒有 handle 的情況
                 // 所以可用InvokeRequired 判斷是否為 cross-thread
                if (InvokeRequired)                
                {
                    SetTextCallback d = new SetTextCallback(SetText);
                    Invoke(d, new object[] { text, color, font });
                }
                else // use the RichTextBox control directly
                {
                    txtLogging.AppendText(text);
                    txtLogging.SelectionColor = color;
                    txtLogging.SelectionFont = font;
                 }
            }
}

相關連結:
Invoke or BeginInvoke cannot be called on a control until the window handle has been created


Cross-thread operation not valid on Windows Form Control

Windows Forms Controls 的存取不是安全執行緒(Thread-Safe)。 如果有兩個以上的執行緒在管理 Control 狀態,則可能會造成 Control 進入不一致的狀態, 也可能發生其他與執行緒相關的錯誤 - 例如競爭情況 (race conditions) 和死結 (deadlock)。 因此,必須確定 Control 存取是以Thread-Safe的方式執行。

最常發生的錯誤情況是,建立 Control 的執行緒 (i.e., Windows Form, creating thread) 與 存取Control 的執行緒 (i.e., 某個calling thread) 並不是同一個Thread,而造成 Cross-thread operation not valid 錯誤。此時,使用 Invoke method 可以解決這個問題。
(** 所謂的 Invoke method,其實只是把 delegate 與 input arguments 傳進去, 讓  Windows Form 去執行此 delegate。 參閱: Control.Invoke方法)

對 Windows Forms Controls 進行Thread-Safe 存取的作法如下:
  1. 查詢 Controls 的 InvokeRequired 屬性。( InvokeRequired 用來 compares the thread ID )
  2. 如果 InvokeRequired 回傳 true ,不同 thread 要使用 Invoke;
  3. 不然的話,相同thread 則直接存取Control 。 
[例如]:
private delegate void SetTextDelegate(string text, Color color, Font font);

private void SetText(string text, Color color, Font font)
{
                 if (InvokeRequired)
                {
                    SetTextCallback d = new SetTextCallback(SetText);
                    Invoke(d, new object[] { text, color, font });
                }
                else // use the RichTextBox control directly
                {
                    txtLogging.AppendText(text);
                    txtLogging.SelectionColor = color;
                    txtLogging.SelectionFont = font;
                 }
}


利用VS 2015執行應用程式時,如果VS 2015引發 InvalidOperationException,並且會有訊息:"Control 'control name' accessed from a thread other than the thread it was created on",這就表示此 Control 正在被 none Thread-Safe 的行為存取, 此時必須用Invoke method 來解決。


See the following Q&A (from https://bbs.csdn.net/topics/390581976)
1. 為什麼更新某個 Textbox 的狀態, 不能夠直接寫 Textbox="123" 就好了?
  • 為了Thread-Safe 。微軟建議你在創建 Control 的那個 Thread 裡面去更新 Control 。如果只是 Textbox="123", CLR怎麼知道你是想做 Thread-Safe 的操作? 還是 none Thread-Safe 的操作呢?

 

2. 為什麼一定要搞個 delegate 要我們去寫的這麼複雜?
  • delegate 與 multi-thread 更新 Control 沒有什麼必然關係, 它只是指定了你更新 Control 的時候執行的具體動作

2016年4月9日 星期六

System.BadImageFormatException: Could not load file or assembly '...' ( Platform target 不一致)

[Problem description]
當某個 Project A (ex: unit test project) 使用某個 Project B,此時 compile 沒有問題,但是執行 Project A 卻發生下列錯誤訊息:

System.BadImageFormatException : Could not load file or assembly 'Project B, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. An attempt was made to load a program with an incorrect format.

[Root cause]
Project A 與 Project B 並不同為是 32-bit (或是 64-bit) 平台 (target platform)

[Solution]
編輯這兩個Projects 的 Platform target 同為 x86 (或是 64),如下:

2016年4月7日 星期四

Use Costura.Fody to bundle all assemblies into one ( 比 ILMerge 更好用 )

不論是 ILMerge 或是 LibZ,使用上都有一些限制 (參閱: 這裡)
在此,介紹一下 Costura.Fody ,它比 ILMerge或Libz更強大好用:

  • 可將 reflection, native DLLs, 或是3rd party 有使用 reflection 的應用程式整合成一個執行檔。
  • 可把 WPF 應用程式與其他 .Net 的DLL檔案 merge 成為單一執行檔。
  • 比 ILMerge 有更加的壓縮比率。大概是 ILMerge 壓縮出來執行檔的70%。


1).下載 Costura.Fody - 使用VS2015 的 NuGet Package Manager
  • 由 Reference 的 Manage NuGet Packages...下載 Costura.Fody (做法同 LibZ 的下載方式)

2).使用 Costura.Fody 來bundle 所有的 assemblies:
  • 下載之後,在這個project 底下有一個FordyWeavers.xml,編輯此 FodyWeavers.xml,把debug symbols 排除,如下:
          <?xml version="1.0" encoding="utf-8"?>
          <Weavers>
                <Costura IncludeDebugSymbols='false' />
          </Weavers>
  • 然後,在 VS2015 上面 Build 這個 project,<MyApp.exe> 檔案 size 會變大,變成一個單一執行檔。
       *** Costura.Fody 也可以直接用在 WPF app 上面!  夠簡單吧!

3).下載安裝Costura.Fody 之後,若要在VS2015設定 break point 來 debug 了,可以把 .csproj檔案修改如下:
  • <Import Project="~\..\..\Fody.targets" Condition=" '$(Configuration)' == 'Release' " />

相關連結:



2016年3月27日 星期日

Implementation Patterns - Kent Beck (Chapter 3 價值觀,原則, 模式)

第 3 章 - 程式設計理論

價值觀,原則,模式 這三種元素組成了一種穩定的開發方式:
  • 價值觀 - 提供了動機 (why)
  • 原則 - 實際行動 (what)
  • 模式 - 如何做 (how)


價值觀:
  1. 溝通 - 把程式寫成一個故事,讀起來像一本書一樣。
  2. 簡單 - 去掉多餘的複雜性,讓讀者看得懂。
  3. 靈活 - 只有真正發生變化的時候才需要靈活性 (不用想像明天或許會用得上的靈活)。

      重要性為 溝通>簡單>靈活

原則:
  1. 確保局部化影響 - 把組織程式碼的影響範圍縮到最小,程式碼就會有極佳的溝通效果。
  2. 消除重複 - 把程式拆成許多更小的部分:小方法, 小物件, 小 package 有助於發現並消除重複。
  3. 綑綁邏輯與資料 - 把邏輯和資料放在同一個方法,同一個物件,同一個 package,讓影響發生在局部。
  4. 建立對稱性 - 邏輯概念上的對稱。例如: Add () 與 Delete ()放在同一個物件。
  5. 使用宣告式表達 - 使用 Annotation (或者 attribute) 表達程式意圖。
  6. 確保相同變化率 - 物件中所有property 的 life time 應該一樣。較短 life time 的變數應該屬於某個方法。

模式:
  •       請參閱接下來的章節...


2016年3月25日 星期五

Get and change CD ROM drive letter (修改光碟機代號)

CD ROM 的 drive letter 記錄在 Windows Registry 裡面:
  • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\CD Burning\DriveIndex

若要使用 C# 修改CD ROM 的 drive letter, 必須透過 WMI 取得目前的drive letter, 再透過 WinAPI 改變drive letter。

1). 透過 WMI 取得目前的drive letter -

先加入 System.Management.dll 參考,在使用SelectQuery ManagementObjectSearcher ,如下:


SelectQuery queryCDROM = new SelectQuery("SELECT * FROM Win32_cdromdrive");
ManagementObjectSearcher searcherCDROM = new ManagementObjectSearcher(queryCDROM);
       
 foreach (ManagementObject cdromLetter in searcherCDROM.Get())
{
        string letter = cdromLetter["Drive"] + @"\";
        ChangeDriveLetter(letter); // sub-function to change drive letter, please see 2). 
}


2). 透過 WinAPI 改變drive letter - 
  • 先用DllImport把下列三個win api 載入進來:
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GetVolumeNameForVolumeMountPoint(string lpszVolumeMountPoint, [Out] StringBuilder lpszVolumeName, uint cchBufferLength);

[DllImport("kernel32.dll")]
static extern bool DeleteVolumeMountPoint(string lpszVolumeMountPoint);

[DllImport("kernel32.dll")]
static extern bool SetVolumeMountPoint(string lpszVolumeMountPoint, string lpszVolumeName);


  • 在ChangeDriveLetter() 方法中,使用上述的三個win api:
        private void ChangeDriveLetter(string driveLetter)
        {
            const int MAX_PATH = 260; 
            StringBuilder volume = new StringBuilder(MAX_PATH);
            if (!GetVolumeNameForVolumeMountPoint(driveLetter, volume, (uint) MAX_PATH))
            {
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }

            if (!DeleteVolumeMountPoint(driveLetter))
            {
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }

            if (!SetVolumeMountPoint(@"W:\", volume.ToString()))  // assume to change to W:\
            {
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
        }

** 上面列的程式碼,是先 delete 舊有的drive letter, 在 set 新的drive letter, 使用上要很小心,避免delete 非預期的drive letter。 


Use LibZ to distribute the WPF applications or libraries as single file ( 把WPF應用程式整合成一個執行檔 )

ILMerge的使用有下列限制:
  • 若.Net 應用程式有使用reflection, native DLLs, 或是3rd party 有使用 reflection, 則 ILMerge無法將應用程式整合成一個執行檔。
  • ILMerge 不能把 WPF 應用程式與其他 .Net assemblies 整合成為單一執行檔 (因為 WPF 把 .Net assemblies 編碼成 binary resources ,造成 ILMerge 不能修改這些 assemblies )。


 LibZ 可以解決ILMerge的限制:
  • 即使.Net 應用程式有使用reflection, native DLLs, 或是3rd party 有使用 reflection, 使用 LibZ 也可以把這些原件 embed 成為單一執行檔。(參閱: 這裡)
  • 根據上述,LibZ 可將WPF 應用程式與其他 .Net 的DLL檔案 embed 成為單一執行檔。


下載 LibZ - 使用VS2015 的 NuGet Package Manager (註 1)
  • 由 Reference 的 Manage NuGet Packages...下載 LibZ.Tool (註 2),如下:












  • 或是由Package Manager Console 輸入下載指令,如下:
 PM>Install-Package Libz.Tool 






















使用 LibZ 來embed 所有的DLL檔案:

  • 透過command-line (或是Post-build event command line) 輸入以下指令 - 
libz inject-dll --assembly <MyApp.exe> --include *.dll
  • 然後,<MyApp.exe> 檔案 size 會變大,就變成一個單一執行檔。 
[註 1]
NuGet Package Manager 是由 Microsoft 開發的一組Tools,整合在 VS2015 裡面。可以針對 VS的 Project 所需要的 Package (ex: 3rd party components, Resharper, Libz, Fody...) 進行下載,安裝/反安裝,升級,建構 。

[註 2]
NuGet Package Manager 下載的檔案會放在預設的packages folder 裡面。packages folder的位置 與 solution 位置相同,如下圖所示:


若要修改下載路徑,作法如下:
1). 在solution 相同位置建立一個 nuget.config 檔案
2). 編輯 nuget.config,如下圖所示:




2016年3月17日 星期四

Cast object to its actual type (把Object 轉成它的真正型別)

要把Object 轉成它的真正型別 (ex: GoodBaseStep type),可以有下列 5 種方式:

** 如果知道它的真正型別 -

               // explicit cast: if failed, throw 'InvalidCastException'
               var goodStep = (GoodBaseStep)obj;
               goodStep.HelloFunction();

               // as operator: if failed, return null
               var goodStep = obj as GoodBaseStep;
               goodStep.HelloFunction();



** 如果不知道它的真正型別 -

  • reflection
  • implementing a well-known interface
  • dynamic

例如:

             // reflection
             obj.GetType().GetMethod("HelloFunction").Invoke(obj, null);

             // interface
             IBaseStep step = (IBaseStep)obj;
             step.HelloFunction();

             // dynamic: represents an object whose operations will be resolved at runtime
             dynamic step= obj;
             step.HelloFunction();





2016年2月29日 星期一

Pull System v.s. Push System

Pull system 與 Push system 的差別在於 - 工作是如何分配給 developer 去做?

Pull system: (developer 自己拿工作)
所有工作根據 priority 被條列在 product backlog上。目前手邊沒事做的 developer 可以由 product backlog 上面把 high priority 的工作拿下來做 (pull the work out of the list)

Push system: (工作被分配給 developer)
主管建立一些工作,然後把工作分配給 developer 去做 (work is pushed to developers)


Story Point v.s. Velocity v.s. Focus Factor

Story point:
用來量測 story 複雜度的一種方式,是一種 story 的 size。每個 team 可以有自己的量測方式與單位 (days or hours)。已經定下來的 Story point (即使非常不精確) 也不應該隨著時間而改變。隨著每個 sprint 加入新的 story,這些新的 story 可以與之前舊的 story 做比較,而得到比較精確的 story point。

Velocity:
用來量測 team 可以完成多少 stories 的一種方式。每個 team 可以有自己的量測方式與尺度。如果 story point 的估算是具有一致性, velocity 便可以顯示出 team 的 performance 是加速還是減速。如果想要提高 velocity, 可以透過各種方式來改善 team 的 performance。


Focus factor:
用來量測這個 team 有多少時間比率花在 story 上面。Focus factor 的算法如下:
   Focus factor = Velocity / Capacity

比如說,team 的平均 Velocity = 41,team 的平均 capacity = 6 people * 12 days = 72 man-days
則 Focus factor = 41 / 72 = 0.57 ( 0.57 story point can be done per man-day)

一般 focus factor 會先抓 0.5,也就是一天 8 小時的工時,其實只有 4 小時會花在 story 上。
其它時間則花費在會議 (非story相關的會議),寫報告 (非story相關的報告),請假,等等

有了focus factor,便可以用來預測這個 team 在這個 sprint 可以 deliver 多少個 stories。
假設這個 team 在這個 sprint 可以有的 capacity = 6 people * 13 days = 78 days, 則這個 sprint 可以 deliver 的 story points 為:
   Story points (for this sprint)= 78 * 0.57 = 44 [story points]











2016年2月23日 星期二

Unit Test Runner failed to load test assembly

用 ReSharper 在執行 Unit Test (on VS 2010 ) 的時候,若發生"could not load nunit.framework",請嘗試將 nunit.framework 的 Copy Local 設為 True。如此 nunit.framework.dll 才會被 copy 到此unit test project 的 output folder,讓此 unit test project 可以 reference。

說明如下:
當一個 DLL 被加入至一個 project 當作 reference ,這個 DLL 預設的 Copy Local =True。
然而,若此 DLL 已經被註冊到 GAC ,則應該設此 DLL 的 Copy Local =False,否則有可能會 Build falied。
註1: 有關 Copy Local,請參閱:
The Copy Local property determines whether a reference is copied to the local bin path. At run time, a reference must exist in either the Global Assembly Cache (GAC) or the output path of the project. If this property is set to true, the reference is copied to the output path of the project at run time.

2016年2月15日 星期一

WPF v.s. WinForm

WPF 的優點:
  1. 使用 XAML 來編輯 UI, 並且可以清楚切開 UI 與 Business logic
  2. Databinding, 把 UI 與 Data 切開
  3. 利用硬體加速 UI 的呈現
WPF 的 project structure 如下:


其中:
  • MainWindow.xaml:  用來設計 UI
  • MainWindow.xaml.cs : 用來 handle 所有的events



WinForm 的優點:
  1. 歷史悠久, 成熟可靠
  2. 跟WPF比較起來, 需要寫的程式碼比較少
WinForm 的 project structure 如下:


其中:
  • Form1.cs:  用來 handle 所有的events
  • Form1.Designer.cs : 用來設計 UI

2016年2月13日 星期六

Everything MUST be transparent

敏捷式開發強調快速反應!
為了達到這樣的好處,"事事溝通"非常重要 - 任何事情都要溝通
360度溝通 - 對上級,對測試人員,對客戶,對使用者...都要溝通。