[實作筆記] ASP.Net Core Logger

要知道的事

  • 這是個人的學習記錄
  • 可能對你沒幫助
  • 網路上資訊很多
  • 希望對你有幫助

概念

Asp.Net Core 的 Life Cycle 由 Program.csmain 方法開始(是的,就如同其它一般的程式),
WebHostBuilder 中的 ConfigureLogging 可以提供彈性讓你設定屬於你的 LoggerProvider,
不論是微軟提供、知名的第三方套件或是你手工自已刻一個,大致你的程式碼會如下

1
2
3
4
5
6
7
8
9
10
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureLogging(logging=>
{
logging.ClearProviders();
logging.AddEventLog();
logging.AddFile("D:\\Temp\\Log.txt");
logging.AddConsole();
})
.UseStartup<Startup>()

而在 Controller 或其它 Module 間,你只要透過建構子注入 logger 實體就可以實現 log 的功能

1
2
3
4
public HomeController(ILogger<HomeController> logger)
{
this._logger = logger;
}

預設的行為

如果你沒有呼叫 ConfigureLogging 預設的行為如下述.

The default project template calls CreateDefaultBuilder, which adds the following logging providers:

  • Console
  • Debug
  • EventSource (starting in ASP.NET Core 2.2)
1
2
3
4
5
6
7
8
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();

Find the Logs

Console

範例說明:在建構子中注入 ILogger 實體,運行網站後連到 Home\Index 頁面,並觀察 Console

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class HomeController : Controller
{
private readonly ILogger _logger;

/// <summary>
/// Initializes a new instance of the <see cref="HomeController" /> class.
/// </summary>
public HomeController(ILogger<HomeController> logger)
{
this._logger = logger;
}

public IActionResult Index()
{
this._logger.Log(LogLevel.Information,"HomeController Information");
this._logger.Log(LogLevel.Critical,"HomeController Critical");
this._logger.Log(LogLevel.Debug,"HomeController Debug");
this._logger.Log(LogLevel.Error,"HomeController Error");
this._logger.Log(LogLevel.None,"HomeController None");
this._logger.Log(LogLevel.Trace,"HomeController Trace");
this._logger.Log(LogLevel.Warning,"HomeController Warning");
return View();
}
}

結果如下,可以發現 LogLevel.NoneLogLevel.TraceLogLevel.Warning 並未出現在 Console 資訊當中

1
2
3
4
5
6
7
8
info: Marsen.NetCore.Site.Controllers.HomeController[0]
HomeController Information
crit: Marsen.NetCore.Site.Controllers.HomeController[0]
HomeController Critical
dbug: Marsen.NetCore.Site.Controllers.HomeController[0]
HomeController Debug
fail: Marsen.NetCore.Site.Controllers.HomeController[0]
HomeController Error

LogLevel說明了 None 的意義就是不記錄任何訊息,

Enum Level Description
Trace 0 Logs that contain the most detailed messages. These messages may contain sensitive application data. These messages are disabled by default and should never be enabled in a production environment.
Debug 1 Logs that are used for interactive investigation during development. These logs should primarily contain information useful for debugging and have no long-term value.
Information 2 Logs that track the general flow of the application. These logs should have long-term value.
Warning 3 Logs that highlight an abnormal or unexpected event in the application flow, but do not otherwise cause the application execution to stop.
Error 4 Logs that highlight when the current flow of execution is stopped due to a failure. These should indicate a failure in the current activity, not an application-wide failure.
Critical 5 Logs that describe an unrecoverable application or system crash, or a catastrophic failure that requires immediate attention.
None 6 Not used for writing log messages. Specifies that a logging category should not write any messages.

Log 的作用範圍會受 appsettings.json 影響,
另外要注意 appsettings.json 的載入順序.

1
2
3
4
5
6
"Logging": {
"LogLevel": {
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
}

Debug

如同 Console 的行為一般,可以在 Visual Studio 的輸出(Output)>偵錯(Debug)視窗中,查詢到記錄。

EventSource

如同官方文件所說,我下載了 PerfView
如下圖作了設定,
PerfView
不過我並沒有取得記錄,
PerfView Log

錯誤訊息如下
EventSource Microsoft-Extensions-Logging: Object reference not set to an instance of an object
暫時不打算深追查,
ETW 可以記錄的 Memory 、Disc IO 、CPU 等資訊,
其實與我想要的應用程式記錄有所差異,稍稍記錄一下以後也許用得到。
如果有人能留言給我一些方向,也是非常歡迎。

自訂 Filelog 與 EventLog

調整一下程式

1
2
3
4
5
6
7
8
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureLogging(logging=>
{
logging.AddEventLog();
logging.AddFile("D:\\Temp\\Log.txt");
})
.UseStartup<Startup>()

這裡我使用 Microsoft.Extensions.Logging.EventLog 處理 EventLog 可以在 Event View 中看見記錄;
而 file log 我使用 Serilog.Extensions.Logging.File , 特別要注意以下兩點

  • Nuget 使用的版本為 2.0.0 以上版本,目前仍然不是穩定版本
  • AddFile 傳入的是記錄檔的完整 Path 而非目錄

自訂 Elmah

Elmah 在 Net 算是一個蠻方便的工具,有提供簡易介面、可以選擇用 File 或是 Database 方式作 Logging,
更重要是小弟我用了 4 年,順手就研究一下。

設定相當簡單, 在 Startup.csConfigureServices 加入

1
2
3
4
5
6
services.AddElmah<XmlFileErrorLog>(options =>            
{
//options.CheckPermissionAction = context => context.User.Identity.IsAuthenticated;
//options.Path = @"elmah";
options.LogPath = "D:\\Temp\\elmah";
})

Configure 加入

1
app.UseElmah();

要注意是使用 XmlFileErrorLog 時,要設定的 options 是 LogPath 而非 Path, 其實使用 File 只能說是開發環境的暫時處置,真正的 Prodction 應該將 Log 放到專門的 Database 或是 Cloud Service 之中,
在這裡可以看見 Elmah 的行為與 Net Core 的行為並不一致,Log 與錯誤記錄本來就不該混為一談。
我想我要調整一下我的想法了,不過關於 Log 暫時就到此為止。

參考

(fin)

[閱讀筆記] 重構---改善既有程式的設計,第一章

為什麼

知道自已不知道

上次在公司內部開始進行 Coding Dojo,在 FizzBuzz 的 Kata 嚐到了甜頭
但是下一道題目「Bowling」,卻卡住了。
我們有測試,也通過測試,但是卻寸步難行,
在重構上我們非常的弱,這裡的重構不是指一次性的全面翻掉,
而是逐步的、可靠的前進,
我想習得這樣的技能,因為現場的代碼腐敗的更加嚴重,
如果連 Kata 產生的代碼都不能優化,
那想對產品指手劃腳只不過是說幹話。

閱讀經典:重構—改善即有的程式設計

這是一本來自 Martin Fowler 的經典書籍,新版已經出了,而且是以 JavaScript 作為範例語言。
不過我手頭上借到的是以 Java 作為範例的板本。

CH1 重構,第一個案例

第一個問題就是我找不到書中說的「線上範例」,
即使找到我也沒有 Java 的開發環境,所以心一橫就開始了改寫成 C# 的計劃
這部份比我想像中的簡單很多,兩個語言是相同類似的,
第1章,第一個案例

接下來只要照著書上一步一步作就會…覺得越來越沒 fu …
為什麼 ???

其實 Martin 大叔在書中有提到「為即將修改的程式建立可靠的測試…畢竟是人,可能會犯錯。所以我需要可靠的測試」。
沒fu的原因就是我沒加測試,即使重構了,我也不知道好壞。
沒有反饋是很糟糕的一件事。

CH1 重構,第一個案例,加上測試

那麼要怎麼加測試呢 ?
書上的案例我分析了一下,其實重構的目標只是一個單純的方法
會針對不同的情境回傳不同的字串。

簡單的說,我只要讓測試覆蓋這個方法就可以開始重構了,
我選擇dotCover來檢驗我的覆蓋率。
選擇的原因很簡單,因為我有買ReSharper, 如果有更好用更便宜的工具也請介紹給我。

OS:課金真的能解決很多人生問題啊(茶)…

最後的結果,我開了一個分支包含了100%的測試覆蓋率,
這樣就可以開開心心重構了,相信我有測試真得很有感覺。

重構的技法請自行看書,我只稍微作個記錄,有興趣可以 fork 回去玩。

  • Extract Method
  • Move Method
  • Replace Temp with Query
  • Replace Type Code with State/Strategy Pattern
  • Replace Conditional with Polymorphism
  • Self Encapsulate Field

在重構的過程中我儘可能讓步驟小(Baby Step),看我的commit歷程即可知道,但是最好可以自已作作看。
另外有一些心法,也稍作個記錄

  • 把一坨爛 Code 抽到獨立的方法之中
  • 如果一個類別方法並沒有使用到該類別的資訊
    • 考慮職責,是不是要讓它搬家
    • 提醒自已這是個壞味道
  • 拆分職責時,有個方法相依兩個不同的類別的資訊,那應該將方法放在哪裡呢?(這裡花了點時間理解)
    • 將方法放在未來可能變化較大的類別之中
    • 相依的資訊作為方法參數傳進來
    • 這樣未來有異動就被縮限在這個類別裡面。
  • 暫存變數常常會帶來問題(壞味道)
    • 儘可能的把它消除
    • 要考慮效能的問題(書上後面會說。)
  • 保持小步調、頻繁測試
    • 使用中繼方法可以縮小重構步調(特別是對public的方法)
    • 讓新的 return 值插在舊的 return 之前
    • 測試 ok 就可以刪掉舊 code (有時刪不掉也還是可以運作的)
    • 善用變異測試
  • UML 可以幫助對程式重構前後的理解
  • Java 與 C# 對繼承的處理是不同的

後記

第一章的範例完成後的結果大致如下
100%!!!
很帥氣的100%啊,這樣的 code 測試覆蓋率 100 % 全綠燈,
而且完成了重構,根本是現場不可能出現的完全體程式碼!!! 代碼的部份我會放在最後的參考區塊。

我有沒有可能讓它更好?或是找出他的缺陷呢?
下一步,我有沒有可能讓它更好?或是找出他的缺陷呢?

這個時候我想起了變異測試
還沒有實作過,來玩看看好了。

首先要選擇測試工具,這裡使用了Stryker Mutator, 但是注意只能用在 .Net Core 的版本
照著官網安裝完成後執行

1
>dotnet stryker

跑下去竟然真的找到有存活的變異
存活的變異
這兩個變異存活的原因是類似的,

1
2
3
4
double result = 2;
if (daysRented > 2)
result += (daysRented - 2) * 1.5;
return result;

變異點發生在 daysRented > 2 的判斷式之中,
現有的測試在變異發生(daysRented >= 2)時,無法提出警訊,也就是測試上的不足。
不過依現有的邏輯,不論是進入 if 進行了加0運算,或是直接回傳 result,
都是等價的(回傳 2 ),目前還沒有想法怎麼強化我的測試,
希望有先進願意不嗇指點,實務上跟本沒在跑變異測試。

後記2

回歸一下我們當初 Kata 的目的:

  • 學習 Pair ,並透過 Pair 彼此學習
  • 學習 TDD ,並透過 TDD 學習重構
  • 學習 Vim,並提昇開發速度

事情沒有那麼簡單,比如說學習 Vim 的過程中,
我們的目的是增進開發速度,但是一開始反而會變慢,
一定要刻意的練習才能習得,
你必須擁有以下的能力。

  • 打字速度,網路上很多資源,我是使用Ratatype作練習
    • 能盲打
    • 指法要正確(特別在特殊符號)
    • 快速切換中英(建議加入英文輸入法用 win + space 之切換過去)
  • 英文能力。命名是開發很重要的一課,英文不好看不懂寫得差,命名自然不會好。
  • 熟悉工具,特別是你的IDE與外掛
    • Visual Studio
    • Resharper
    • OzCode
    • more ..
  • Vim
    • Vim Basic 基本功(v、c、i、s、j、k、g、h、l….)
    • VimRc 要學會配置自已的 VimRC,這裡不僅要刻意練習,還要刻意試錯找到自已最順的模式

彼此學習方面需要相當的軟技能,
溝通、尊重、謙虛…;這些一生的功課我就不贅言了。
Pair Programming 一半是 Pair 一半是 Progrmming;
而在進入 Progrmming 之前請搞懂你要作什麼

同樣的在 TDD 的過程之中,我們沒有事先理好需求,
沒有想好作好需求分析,隨便選了測試案例就開始進行,
如果好好分析,是可以歸納出其中的邏輯,
甚至是理出 test case 的順序。

重要的是過程,但是我們太在乎結果,以致程式快速的腐敗。
甚至到了難以修改的狀態,僅管有測試保護,卻無法重構。

這是很好的一課,特別在這裡記錄一下。

參考

(fin)

[實作筆記]使用 Windows PowerShell 批次上傳 AWS S3

目標

相關服務要雲端化,要將站台、資料等…遷移至 AWS,
這個案例需要將大量在 File Server 上的檔案(報表/單據/報告書等…)上傳至 S3
因為檔案數量相當的大,所以開發一個簡單的指令來執行。

Step 1 下載並安裝適用於 Windows PowerShell 的 AWS 工具

只需要安裝必要的程式

設定 aws config

打開 terminal 執行以下語法

1
> aws configure

依指示設定 Access key IDSecret access key, 這個資料需要具備一定的權限才能取得,如果權限不足請向你的 AWS 服務管理員申請。

撰寫 PowerShell 與執行

1
2
3
4
5
6
7
8
9
10
11
12
$bucketName = "********************-your_bucket_name"
$path = "Your\s3\path\"

Get-ChildItem -Filter *.pdf |
ForEach-Object -Begin{$i=0} {
$i++;
$key = $path+$_ ;
## 進度顯示
Write-Host $key "($i/1000)" -ForegroundColor Green ;
## 上傳 S3
Write-S3Object -BucketName $bucketName -File $_.FullName -Key $key ;
}

(fin)

[實作筆記] Coding Dojo 第一個 Kata FizzBuzz

前情提要

記錄一下 Kata 的思路。

實例化需求

1
2
3
4
5
6
7
1 is 1
2 is 2
3 is Fizz
4 is 4
5 is Buzz
6 is Fizz
15 is FizzBuzz

雖然可以把上面的案例濃縮到 4 種,
整除 3 是 Fizz、
整除 5 是 Buzz、
整除 3 又整除 5 是 FizzBuzz ,
不符合上述條件的都是原數字。

有沒有必要寫這麼多測試呢?
比如說 1、2、4 的測試是不是重複了?
日前 91 大有過類似的討論,

1
2
3
4
第一個 test case 挑最簡單的,讓你可以從紅燈變綠燈。驅動出你需要的產品代碼。  
接下來後面的幾個,都可以只是拿來「確認」是否滿足你期望的情境,
也就是你寫新的測試案例,你期望他就是綠燈了,然後驗證是否符合你的期望。
目的是「驗證」,不是「驅動」

測試的不是只有「驅動開發」而已。
而好的程式碼,也不能只依靠測試。

第一個測試案例,1 回傳 1

我一開始就寫成這樣,所以後面的 2、4 案例也都會是綠燈。

1
2
3
4
5
6
7
public class FizzBuzz
{
public string GetResult(int number)
{
return number.ToString();
}
}

考慮另一種情況,也許有的人第一個測試案例會寫成這樣

1
2
3
4
5
6
7
public class FizzBuzz
{
public string GetResult(int number)
{
return "1";
}
}

這時候就有可能需要靠 2、4 的測試案例來驅動程式碼的改變。
實際上並沒有,第一種寫法對我來說就夠 Baby Step 了。

第二個測試案例,3 回傳 Fizz

1
2
3
4
5
6
7
8
9
public string GetResult(int number)
{
if (number % 3 == 0)
{
return "Fizz";
}

return number.ToString();
}

相信這是很好理解的,雖然我的案例是從 1、2、3 而來,
但是在我的腦海中已經思考好了這個程式碼的「餘數規則」,

所有測試案例

實作出一個「餘數規則」後,程式碼應該很容易隨著測試案例變成下面這個樣子,
用一堆 if 檢查「餘數」然後回傳指定的「字串」,就是我們的「規則」。
這個時候的複雜度是 4 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class FizzBuzz
{
public string GetResult(int number)
{
if (number % 5 == 0 & number % 3 == 0)
{
return "FizzBuzz";
}

if (number % 5 == 0)
{
return "Buzz";
}

if (number % 3 == 0)
{
return "Fizz";
}

return number.ToString();
}
}

重構

我儘量還原當初的想法,並記錄下來,
有許多值得改善的地方,換個順序重構起來就會更明快。

重構餘數檢查

這一步真的非常的的小,我想大多數的人甚至會跳過這步驟的重構,
我只是把餘數檢查抽成私有方法,可以透過 Resharp 快速重構。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class FizzBuzz
{
public string GetResult(int number)
{
if (IsDivisibleBy15(number))
{
return "FizzBuzz";
}

if (IsDivisibleBy5(number))
{
return "Buzz";
}

if (IsDivisibleBy3(number))
{
return "Fizz";
}

return number.ToString();
}

private bool IsDivisibleBy15(int number)
{
return IsDivisibleBy3(number) && IsDivisibleBy5(number);
}

private bool IsDivisibleBy5(int number)
{
return number % 5 == 0;
}

private bool IsDivisibleBy3(int number)
{
return number % 3 == 0;
}
}

抽出 result 變數作為回傳值

這裡我是作了一個舖墊,主要是我看到了 FizzBuzz 的字串重複出現在 FizzBuzz, 我預計下一階段要讓 FizzBuzz 是透過組合產生,而不是寫死在程式之中。
特別要注意的事是,我為了產生 result 變數,必須在最後多作一次空字串的檢查,
這個時候的複雜度會達到 5 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class FizzBuzz
{
public string GetResult(int number)
{
string result = string.Empty;
if (IsDivisibleBy15(number))
{
return "FizzBuzz";
}

if (IsDivisibleBy5(number))
{
result = "Buzz";
}

if (IsDivisibleBy3(number))
{
result = "Fizz";
}

if (string.IsNullOrEmpty(result))
{
return number.ToString();
}

return result;
}

private bool IsDivisibleBy15(int number)
{
return IsDivisibleBy3(number) && IsDivisibleBy5(number);
}

private bool IsDivisibleBy5(int number)
{
return number % 5 == 0;
}

private bool IsDivisibleBy3(int number)
{
return number % 3 == 0;
}
}

組合 result 值

這個階段 ‘Fizz’ 與 ‘Buzz’ 在程式中只會出現一次,
15 的餘數檢查也被移除了,這時的複雜度是 4 ,
可惜的是我沒有意識到第三個 if 的明顯不同,
如果我能提早重構成 result = number.ToString();
後面的重構也許會更簡潔一點。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class FizzBuzz
{
public string GetResult(int number)
{
string result = string.Empty;
if (IsDivisibleBy3(number))
{
result += "Fizz";
}

if (IsDivisibleBy5(number))
{
result += "Buzz";
}

if (string.IsNullOrEmpty(result))
{
return number.ToString();
}

return result;
}

private bool IsDivisibleBy5(int number)
{
return number % 5 == 0;
}

private bool IsDivisibleBy3(int number)
{
return number % 3 == 0;
}
}

實作 FizzRule Class

這是繼 FizzBuzz 後產生的第二個 Class,
算有指標意義,這裡原本的目的是想要消除 if, 但無法一步到位,先試著把 Fizz 與 Buzz 的邏輯作分離,
一樣我只聚焦在 Fizz 與 Buzz 身上,
而忽略了 其它 的邏輯判斷,寫成了三元判斷除了變成一行外其實沒有其他好處。

1
2
3
4
5
6
7
public class FizzRule
{
public bool Check(int number)
{
return number % 3 == 0;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class FizzBuzz
{
public string GetResult(int number)
{
string result = string.Empty;
var fizzRule = new FizzRule();
if (fizzRule.Check(number))
{
result += "Fizz";
}

if (IsDivisibleBy5(number))
{
result += "Buzz";
}

return string.IsNullOrEmpty(result) ? number.ToString() : result;
}

private bool IsDivisibleBy5(int number)
{
return number % 5 == 0;
}
}

實作 BuzzRule Class

一樣把 Buzz 的邏輯搬到新的 Class 中,
這裡故意用相同的方法名,是為了下一步要抽介面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public string GetResult(int number)
{
string result = string.Empty;
var fizzRule = new FizzRule();
if (fizzRule.Check(number))
{
result += "Fizz";
}

var buzzRule = new BuzzRule();
if (buzzRule.Check(number))
{
result += "Buzz";
}

return string.IsNullOrEmpty(result) ? number.ToString() : result;
}

介面 IRule

終於抽出了介面,自已為聰明的把關鍵字抽離到了介面之中,
卻沒有考慮到真正的邏輯是組合 result 的行為仍然相依在 FizzBuzz Class

1
2
3
4
5
public interface IRule
{
string Word { get; }
bool Check(int number);
}
1
2
3
4
5
6
7
8
9
public class FizzRule : IRule
{
public string Word => "Fizz";

public bool Check(int number)
{
return number % 3 == 0;
}
}
1
2
3
4
5
6
7
8
9
public class BuzzRule : IRule
{
public string Word => "Buzz";

public bool Check(int number)
{
return number % 5 == 0;
}
}

IRule List

準備好了 IRule ,就是要讓 FizzBuzzFizzRule 以及 BuzzRule 解耦的階段了,
這步我踩得有小,可以更直接一點重構,
一樣的問題,我仍然沒有意識最後一個if(?:)其實也是一種 IRule, 也沒有意識到 result+=XXXreturn YYY?number.ToString() : result; 其實應該是屬於 IRule 的一部份
這時的複雜度仍然是 4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class FizzBuzz
{
private List<IRule> _rules = new List<IRule> {new FizzRule(), new BuzzRule()};

public string GetResult(int number)
{
string result = string.Empty;
var fizzRule = new FizzRule();
if (fizzRule.Check(number))
{
result += fizzRule.Word;
}

var buzzRule = new BuzzRule();
if (buzzRule.Check(number))
{
result += buzzRule.Word;
}

return string.IsNullOrEmpty(result) ? number.ToString() : result;
}
}

foreach List

自以為帥氣的完成重構,而且用 foreach 消除了重複的 if
實際上複雜度完全沒有下降。
關鍵的 result += rule.Word;
return string.IsNullOrEmpty(result) ? number.ToString() : result;
我繼續忽視它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class FizzBuzz
{
private List<IRule> _rules = new List<IRule> {new FizzRule(), new BuzzRule()};

public string GetResult(int number)
{
string result = string.Empty;
foreach (var rule in _rules)
{
if (rule.Check(number))
{
result += rule.Word;
}
}

return string.IsNullOrEmpty(result) ? number.ToString() : result;
}
}

重構二、面對問題

消除 foreach

參考 Martin 大叔的作法,把 foreach 變成 pipelines
光是這個作法就讓我的複雜度從 4 下降到 2 了,
此時,result += rule.Word;
return string.IsNullOrEmpty(result) ? number.ToString() : result;
就顯得相當奇怪,第一個邏輯我認為應該放進實作IRule的類別之中,
而第二個邏輯應該是一個未被實作的 Rule 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class FizzBuzz
{
private readonly List<IRule> _rules = new List<IRule>
{
new FizzRule(),
new BuzzRule(),
};

public string GetResult(int number)
{
string result = string.Empty;
var myRules = _rules;
myRules
.Where(r => r.Check(number))
.ToList()
.ForEach(n => result += n.Word);
return string.IsNullOrEmpty(result) ? number.ToString() : result;
}
}

實作 Apply

終於將result += rule.Word;的邏輯從 FizzBuzz 抽離到 IRule 之中,
再由各自的 Rule 實作,這個時候就會覺得 IRule.CheckIRule.Word 有點累贅,
基於 SOLID 原則,這部份邏輯甚至不該被揭露在 FizzBuzz之中。

1
2
3
4
5
6
public interface IRule
{
string Word { get; }
bool Check(int number);
string Apply(string input);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class FizzBuzz
{
private readonly List<IRule> _rules = new List<IRule>
{
new FizzRule(),
new BuzzRule(),
};

public string GetResult(int number)
{
string result = string.Empty;
_rules
.Where(r => r.Check(number))
.ToList()
.ForEach(n => result = n.Apply(result));
return string.IsNullOrEmpty(result) ? number.ToString() : result;
}
}

NormalRule

終於加上 NormalRule Class 了,裡面只有一個方法 Apply, 這裡是為了將來的介面準備,我想讓 NormalRule 成為 IRule 的一部份,
不過可以看到的問題是,方法簽章並不一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class FizzBuzz
{
private readonly List<IRule> _rules = new List<IRule>
{
new FizzRule(),
new BuzzRule(),
};

public string GetResult(int number)
{
string result = string.Empty;
_rules
.Where(r => r.Check(number))
.ToList()
.ForEach(n => result = n.Apply(result));
var normalRule = new NormalRule();
return normalRule.Apply(number, result);
}
}

修改 IRule.Apply

在我的認知中,對 Production Code 修改介面是件危險的事,
這在 Kata 是可行的,但是在實際的 Production 恐怕就不夠 Baby Step 了,
我或許應該創造一個 IRuleV2 之類的介面,而不是直接修改 IRule

首先編譯會不過,這會趨動我去修改 FizzRuleBuzzRule
另外,這個時間點 IRule.CheckIRule.Word 作為 public 的資訊就顯得相當多餘了。
所以我會進一步將這些資訊從 IRule 介面中拿掉,
這也會使得 FizzBuzz Class 產生 Error,趁這個時候把 .Where().ToList() 一併拿掉,
但是要記得將 IRule.CheckIRule.Word 包含至 IRule.Apply 之中。

1
2
3
4
public interface IRule
{
string Apply(int number, string input);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class FizzRule : IRule
{
private string Word => "Fizz";

private bool Check(int number)
{
return number % 3 == 0;
}

public string Apply(int number, string input)
{
return Check(number) ? input += this.Word : input;
}
}

NormalRule 與 IRule

這裡讓 NormalRule 實作 IRule 介面,
實際上在上面幾步已經完成了,IRule 反而比較像一個標籤掛在 NormalRule 上,
如此一來,就能夠在 FizzBuzz 裡面透過 List<IRule> 統整所有的規則。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class FizzBuzz
{
private readonly List<IRule> _rules = new List<IRule>
{
new FizzRule(),
new BuzzRule(),
new NormalRule()
};

public string GetResult(int number)
{
string result = string.Empty;
_rules
.ForEach(n => result = n.Apply(number, result));
return result;
}
}
1
2
3
4
public interface IRule
{
string Apply(int number, string input);
}

收尾

作到這裡大概把我想作的東西都作掉了,
if 散落在各個 Rules 裡面,
如果是 Production Code 我想我會使用 NameSpace 與專案資料夾再作進一步的整理吧。
最後把 FizzRuleBuzzRuleCheckWord 拿掉只是一點潔癖。

1
2
3
4
5
6
7
public class FizzRule : IRule
{
public string Apply(int number, string input)
{
return number % 3 == 0 ? input += "Fizz" : input;
}
}

結語

過程中一直考慮著想要拿掉所有if,或是套用職責鏈(Chain of Responsibility Pattern)的 Pattern,
現在想想都有點走歪了方向,一再忽視責職的歸屬而讓後面的重構有點吃力,
不過透過 TDD 仍然讓程式碼重構到了一定的程度。
如果重來一次的話,我會選擇提早分離職責,
不過當中的取捨可能需要練習更多的 KATA 吧。

有人說這麼重構,會不會有點 Over Design 了,
我想說的是,反正是練習嘛,刻意練習到過頭也只是剛好而已,
如果不在練習時下點苦功,在戰場上用得出來嗎?
至少我的天賦而言,我應該是用不出來的。

後記 1. 20190207

Aggregate

文章貼出後,同事的回饋,可以使用 Aggregate 取代 Foreach, 程式碼可以更加精鍊。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class FizzBuzz
{
private readonly List<IRule> _rules = new List<IRule>
{
new FizzRule(),
new BuzzRule(),
new NormalRule()
};

public string GetResult(int number)
{
return _rules.Aggregate(string.Empty, (r, n) => n.Apply(number, r));
}
}

參數優化

把 r、n 這類較沒意義的命名改成 input 與 rule,
單純是為了讓 Aggreate 的可讀性較高一些。
接下來這個異動的幅度較大,實務上我不會這樣作,
Apply 的方法簽章順序與 Aggreate 一樣把 input String 放在最前面,
真的真的非常沒有必要,因為會異動到介面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class FizzBuzz
{
private readonly List<IRule> _rules = new List<IRule>
{
new FizzRule(),
new BuzzRule(),
new NormalRule()
};

public string GetResult(int number)
{
return _rules.Aggregate(string.Empty, (input, rule) => rule.Apply(input, number));
}
}

參考

[N社筆記] 在公司小規模玩 Coding Dojo

前情提要

在上 91 大的(熱血 Coding Dojo 第一梯次)的課程中,
我一直在思索一件事,為什麼我作不到呢?

我什麼時候才能成角啊

我不希望只是上了課之後,「喔喔喔 我好興奮啊…」就結束了,
所以馬上找了一起上課的兩個同事開了個簡單的 Retro
我只有一個問題,
「如果你覺得這堂課有效的話,我們要作什麼 才能帶來改變呢 ?」

開起小型 Dojo

我們簡單的作了一個決定,我們要在公司內部建立一個 Dojo
並且簡單的訂定了三個目標

  1. 學習 Pair ,並透過 Pair 彼此學習
  2. 學習 TDD ,並透過 TDD 學習重構
  3. 學習 Vim,並提昇開發速度

這三個東西都是在課堂上學習到的,幸運得是我們當中有兩位已經會基本的 Vim 指令,
當然也各有各的問題,ex: 不熟悉英打、沒有 Reshaper 、找不到共同的時間等…

總而言之,我們就這樣開始了。

第一個 Kata

第一個 Kata 是很重要的,Coding Dojo提供我們一些選擇,
Tennis Game 與 Pocker Hand 都是很好的題目,
但是新手上路,我們選了一個簡單的 Fizz Buzz 當作練習題目。
Fizz Buzz 的需求分析是很簡單的,

  1. 傳入 1 數,可以被 3 整除,回傳 Fizz
  2. 傳入 1 數,可以被 5 整除,回傳 Buzz
  3. 傳入 1 數,可以被 3 整除,且可以被 5 整除,回傳 FizzBuzz
  4. 不符合上述條件回傳原數值

Sample Case:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1 → 1
2 → 2
3 → Fizz
4 → 4
5 → Buzz
6 → Fizz
7 → 7
8 → 8
9 → Fizz
10 → Buzz
11 → 11
12 → Fizz
13 → 13
14 → 14
15 → FizzBuzz

怎麼開始?

首先是時間,專案很忙碌,不是所有的人都有空,
我要怎麼擠出時間 ?

利用 Timebox 與 Don’t Break The chain

每天都練習 KATA,每次 30 分鐘;
每天約定一個時間 30 分鐘,30 分鐘到就強制結束,
不論是中午的 30 分鐘,或是下班前的 30 分鐘,
找一個時間只要有兩個人的時間 OK 就進行 。
輪流當 Dirver 與 Navigator ,時間設定 5 分鐘,
一樣時間到就換手。

1
2
3
4
5
嚴格執行 Timebox 真的是一件很折磨人的事情,  
特別是有一些火花的討論出現時,
但是隨著每次的 Timebox 的結束,
看到的成果比上一次多時,
那種進步感是相同明顯的。

很幸運的,一直到過農曆年我們都沒有中斷,
設定 Timebox 也確實有讓參與的人提昇,
包含「重構的技巧」、「打字」、「TDD」與「Vim」,
也有人分享工具,或是小技巧,
這達到我們彼此學習的目的。

回顧一下作得好的部份

  1. 雖然很困難,有線上的問題要處理,有工作項目,有一堆會議,但是我們仍然沒有中斷過一天。
  2. 完成了兩個 Kata ,經典的 FizzBuzz 還有很類似的題目 FooBarQix。
  3. 確實有從彼此身上學到東西,比如說在 Vim 裡面超好用的 ci 或是 AceJump 小工具。
  4. 人數控制的很好,押在 3 到 5 人,也讓大家都能參與到。
  5. 最重要的一點,看到了很多「真實」

作得不好的部份

  1. 環境有一些前期門檻在,ex: Reshaper、已配置過的 VimRC
  2. Timebox 有時候仍會超過,主因是討論太熱情了。
  3. 整體成員都算有經驗的開發者,但是對重構仍然很弱,對壞味道麻痺(不在意重複等…)
  4. TDD 有時候沒辦法作到 Baby Step ,雖然重構是成功的,但過程有點跳躍;或是重構完才發現測試壞一大片,需要偵錯去修正。
  5. 雖然有記錄一些問題,但是常常沒有去求解答

真實的部份

  1. 我們常常看到重複視而不見,特別是只有兩、三次的重複
    • 這題成員的看法比較貼近大量重複再重構…我仍然有點持疑
  2. 因為時間壓力,我們會把問題留給後人;我們不會去理解前人的設計
    • 時間壓力可以換成任何理由
  3. Navigator 如果沒有思考 Dirver 會開 Auto

我們開發者真的太有自信,在 Kata 過程出現的問題,
如果放到開發現場的規模,基本上就是災難,
而實際上也是每天發生。

在時限的壓力下,你不能作出多完美的設計,
但是你可以透過測試作出足夠好的設計,
測試的保護可以幫助你提早發現

下一步是 ?

一直以來有那麼多讀書會,上了那麼多課,
也有外訓內訓,結果卻是不了了之,
要怎麼証明理論有效 ?
要怎麼導入開發現場 ?

我們的成果

寫在後面

我不是本科系畢業,但在軟體開發上也有 7、8 年的經驗,
軟體的經典書籍也看過不少,
不論是免費的社群活動或是上萬元的課程,
相關的課也上了不少。

學習單一語言的特性或是設計模式、物件導向到工程上的 Code Review、Pair Programming、TDD 諸如此類等…
不能說沒有幫助,但總覺得現實與書上的理想好遠啊…
我以為我懂了,其實我根本不懂。

技術不斷推陳出新,追不勝追,
常常工作完後研究或是練習到半夜,
但是沒什麼效果,或在實務上根本用不到
真得很有事倍功半的無力感。

在工作上的產品或專案永遠都有緊急又重要的事要作,
人家的艾森豪矩陣有四個象限,你永遠只有一個象限,
生活上各種的壓力與瑣事,變成了種惡性循環,
像個泥沼一般,無法提昇所以下沉,下沉了更無法提昇。

這個小小實驗,就像是整個開發現場的濃縮版,
現場很多問題不是沒有解答,
但是常常一句「理論上是,但是…」,然後就沒有然後了。
我們太習慣短解,太過自信,讓不好的事情重複發生,
然後覺得自已很忙碌,很有價值感,
但是沒有任何改變。

這個實驗我會繼續下去,期待能有好的結果,並帶來一些變化。

參考

(fin)

[上課筆記] 熱血 Coding Dojo 第一梯次

知道的事

活動連結 - 熱血 Coding Dojo - 第一梯次(活動已結束)
講師 : Joey Chen
範例語言 : C#

上課隨筆

觀念

  • 不要寫多餘的 Product Code
  • 害怕別人看你寫Code是一道門檻
  • 程式腐壞的速度遠比想像中的快 (大概給 3 個人寫過就開始爛了)
  • Pair 時不要一開始就寫 Code,先建立共識
  • 當你碰到薪資天花板就只能轉成管理者 (然後是彼得效應)
  • 國際化的產品能提升自已的視野與能力
  • 英文不夠好是一個門檻,特別在命名的時候
  • 不要臉也是一種技能
  • 先考慮正確性與可維護性 再考慮效能
  • 你以為你在重構 但是代碼變得更難維護
  • 看人家怎麼做?學怎麼想?
  • 即時重構是很重要的,太晚重構會來不及 (Side Effect會大到你無法克服心魔)
  • ATDD 與 TDD 的軟體工序與心魔
  • 你已經具備 knowledge 但是缺乏 Couching 與實務訓練
  • 代碼會反應開發當時的思緒 (不要在精神狀態不好的時候開發)
  • Poker Hand 91 大約2小時完成 (思考怎麼錄製與課後練習中…)
  • 睡前練習可以增強肌肉記憶

實作技巧

  • 紅燈時不要重構
  • .if .var (C# in Visual Studio)
  • 第一個test case 不要有判斷式
  • r n . (Vim in Visual Studio)
  • 紅燈→綠燈→重構→綠燈 ; 要學會節奏與時機
  • F8 跳錯誤
  • 一個變數活很久 最後可能被複寫 會容易產生side effects
  • zcc (Vim in Visual Studio)

壞味道

  • 重構的壞味道,使用私有變數而非方法
  • if else if 簽章抽象相同是個壞味道(或是一個可以重構的 Pattern)
  • Q:請回饋這堂課好的地方 A:T社的HR

寫在最後

最近覺得寫程式真的是一種造業,創造就業機會。

與善人居,如入芝蘭之室,久而不聞其香,即與之化矣;
與不善人居,如入鮑魚之肆,久而不聞其臭。

雖很想推熱血 Coding Dojo - 第二梯次,不過大概已經完售了。

(fin)

[實作筆記]在 Windows Subsystem for Linux 執行 docker

前情提要

最近開始學 Docker, 這個神器大概在 C 社時期就有聽過了,
不過一直沒有機會在實務上接觸,雖然有自已摸一點,但就只有皮毛而已,
在 A 社有機會由同事開課,並提供 EC2 作實驗,就趁這個機會作一點深入的學習。

一開始有一個很天真的想法,我想在 Windows 內建的 Linux 子系統 Ubantu 安裝 Docker, 沒想到最後是脫褲子放屁,不過過程蠻有趣的,稍微記錄一下。

操作步驟

  1. Windows 10 安裝 Ubantu

  2. 在 Ubantu 安裝 Docker

    更新 apt-get

    1
    sudo apt-get update

    允許 apt-get 透過 https

    1
    2
    3
    4
    5
    sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common

    加入 Docker 官方 GPG KEY

    1
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

    然後可以驗証一下

    1
    sudo apt-key fingerprint 0EBFCD88

    加入 Docker 的 apt-repository

    1
    2
    3
    4
    $ sudo add-apt-repository \
    "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
    $(lsb_release -cs) \
    stable"

    再次更新 apt-get

    1
    sudo apt-get update

    安裝 Docker CE

    1
    sudo apt-get install docker-ce
  3. Run Docker

1
sudo docker container run hello-world

我都會失敗如下

1
2
docker: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?.
See 'docker run --help'.

如何解決

  1. Windows 上要安裝 Docker for Windows

  2. 勾選 Docker for Windows → Setting → Expose deamon on tcp://localhost:2375 Without TLS
    Docker

  3. 回到 Ubantu , 執行以下命令

指定 Docker Host 在路徑

1
docker -H localhost:2375 images

如果不想每次指定的話…請參考以下命令

1
export DOCKER_HOST=localhost:2375

執行結果

學到的事

參考

(fin)

[實作筆記]用 Restclient GenCode

REST Client 是一套 Visual Studio Code 的套件。
可以讓你不離開編輯器(Visual Studio Code)的情況下,發送一些 Request。
使用方法可以參考最後的聯結,本文僅介紹 Code Gen 的功能。

作法

Step 1

取得 Request 連結資訊,這個部份可以直接從瀏覽器取得

大致上如下:

1
2
GET https://***.****.com.my/Invoice/DownloadMyInvoicePdf?shopId=44&tradesOrderGroupCode=MG180929L00002
Cookie: ai_user=zazJ/|2018-04-11T10:01:21.161Z; _ga=GA1.3.1260482009.1526374223; __zlcmid=p9hkxHsXzC5Ka5; _ym_uid=15450340861049673384; _ym_d=1545034086; _gid=GA1.3.233068902.1545616884; _fbp=fb.2.1545992230107.2040829768; .AspNet.ApplicationCookie=1ly--ntvPSpcJIIDvk2PBFfuyy744Wvwx4ezT0c0BEl1t4Vw3ahOOMwSczzBezkE0dIPWBwQt12KPN8IrFj8eV2ZgfO6HYKuiw7cUNgS37Gr1FOH28o-l5EZuOYGd4uuqRduaBBPbZrJop5nUso1oPS4fOs-mFO0I17QWHtfB_BI2Lzd6rQQRl6_IevI_EPbsh0EDahqvR4wvF2QFMH2ycECSzR4pgEmm3hcQRJ9COWoc-DtZjxxa11yfghghmReLbe1cYtx1G7ST9zakc_qGmnMV0IyZoRFqEmHrzEb1b8fDO35UkxsiP2_mzGY-Oy4e3fV12Q0N7eGVblEkJYZkrtXADP8h9iGToPwAUI5rbnX2o32Z0_4zbg7x_GSF_HWsW22SWlkRCAZEKhvhEB9Qk56JPSRSJwqmpGDzm8a807-6lRP-JPOo_F43eYLgztH6k6imlUseUyDyYBTwJeIgF5gdyLKMUSivKs-SOivyPofDiLMf0HItB9IgzRg-M94FjZROtOWeGXOYW4wqBMyABTelYgjfjgNtoW05SN7npnSYKG1JZeRwrT5KgaRXJy7KHkguyN8vhoYTjSX0cG4VaOrJqgGY-jsQov1lJvZTok_YAkiDRbOhDu3ebdJdi313sGSkUjjkW73Fn9ztmpexLN3OxfmhUlDu0MHhEqgLJMg8koLWBJjpmVC0YdNPCXNofGgNECkiWCZHTZw8u9jnLhtP686CG_JTQFE33YTdh3mJOp7KwFdFvQefEX5Jb2vUXmfFXMBP3MhD5civiPg4Q0WLU4; ai_session=XYYeY|1545989192862|1545993911682

Step 2

Ctrl+P,輸入 >,找到 Rest Client: Generate Code Snippet
選擇你的語言。

參考

(fin)

CSM 之後 Scrum 總結

  • 什麼是 Scrum ?
  • Scrum 的理想 ?
  • Scrum 怎麼導入 ?
  • 現實的殘酷
  • 仍然存在的疑惑 ?

什麼是 Scrum

「什麼是 Scrum?」,在這之前我們先問一下「為什麼要 Scrum ?」
「Scrum 之前我們怎麼進行專案的?」、「Scrum 之前我們怎麼管理團隊的?」
層層的組織架構、層層的管理人員、層層規劃設計開發…

本來都好好的,為什麼現在不 work 了?

時代正在改變,軟體、網路、行動裝置等…各種技術的成熟,使得整個社會,
不論工業、商業已經往下一個階段邁進,
這是一個VUCA(多變(Volatile)、不確定(Uncertain)、複雜(Complex) 與混沌不明(Ambiguous))的時代,
新的科技帶來新的生活習慣與消費行為,同時機會也再此產生,
不論是巨象還是螞蟻都必須想辦法求生存,而快速適應與改變的能力,成為這個時代必備的能力。

不論是 Agile 或是 Scrum 都是要讓你適應變化,並且活下去。

而 Scrum 是一個框架,三個大原則,透明(Transparency)、檢驗(Inspection)、調適性(Adaptation)
簡單易學,卻難以精通,這句話說得明白一點,就是很容易畫虎不成反類犬。
而 Scrum Guide 用了一句簡單帶過,
「Scrum’s roles, events, artifacts, and rules are immutable and although
implementing only parts of Scrum is possible, the result is not Scrum.
Scrum exists only in its entirety and functions well as a container
for other techniques, methodologies, and practices.」

常見的「Scrum 自助餐」其實不是 Scrum 。

透明(Transparency)

透明的目的是什麼?避免殼倉?曝露風險?取得共識?

CMS 課程帶給我一個很重要的學習,是個很簡單的老故事就說過的道理。
「盲人摸象」,大家應該都聽過這個故事吧,
有的人摸到了扇子(耳朵)、有人摸到牆壁(身體)、有人摸到柱子(腿)、有人摸到水管(鼻子)…
如果大象是你的產品、你的商業模式、你的生存武器,
那麼展現「雅量」就不是一件很好的事了,
當你的 DBA 說了扇子、RD 說了牆壁,PO 說了柱子而 QA 說了水管,
那麼這些的加總就是你的大象(產品)了嗎 ?

盲人的大象

「所有人說都是對,排列組合也是對的,但結果是錯的。」, 怎麼解決這個問題?

  1. 讓盲人看見
  2. 讓盲人摸得到別人的區塊

透明的目的是為了看見目標、取得共識, 還記得多少浪費生命的會議,有多少會前會?有多少會後討論嗎?
又或是大家都說好,作出來卻是半成品、廢品嗎 ?

取得共識 是困難的,Scurm 提供的機制,
讓回饋在不同的活動(會議)中由不同的角色中產生,
但是如果成員不願意講,這些會議就一點意義也沒有了。

要如何讓成員願意發言?
信任、信賴、安全感,但是這需要時間去打造這樣的文化與環境。
事實上不會喊完口號,成員就彼此互相信任,生產力大爆發。
如何打造一個這樣的環境?是管理職真正的責任所在。
但這種無法立竿見影的工作,喊口號的很多,作的人很少。

檢驗(Inspection)與調適性(Adaptation)

透明在檢驗與調適性之前,是有重要的意義的。
也只有透明,才能讓事實擺在眼前,
不是基於事實的檢驗與修正,只是自慰而已。

無奈的是這個世界是如此的複雜,你幾乎不可能真的看見大象(全貌),
曾經有個創業的朋友說了一個「敏捷無用論」,

沒錯,他說得對。
Agile 與 Scrum 從來不是銀子彈 ,如果要迷信「敏捷」不如迷信「沒有銀彈」吧。
你必需依賴你的情境(Context)來決定使用什麼方法,
在他的情境當中,他們要作得項目已經很明確了,而資金是不足的,所以每分每秒對他們都非常重要,
如果要照表操課這些會議,恐怕會佔據大部份的時間。
但是這些會議都有其背後的意義與目的,如果團隊能與商業目標緊密結合,
甚至是一體同心(比如說:你就是老闆又是開發人員),那麼梳理需求是不是能快速的在幾分鐘內完成 ?
如果你們每個人每時每刻都在一起工作,分享彼此的工作內容,那麼需不需要「每天」開個例會同步資訊呢 ?

反過來說,他說得也不對。
在每個會議與每個會議的產出物的背後都有一個目的與意義,
如果不能讓梳理需求、衝刺開發、展現結果與收取回饋時時發生,
那麼這些會議是最好的機會,更重要的事是 Scrum 給團隊自主權,決定進行的方式,
如果有進行這些活動,卻沒有帶來相對的效益,這背後是不是對 Scrum 沒有深刻理解所造成的呢 ?

這種都對都錯的「盲人摸象」時時發生,
重點是怎麼作「取捨」、怎麼作「選擇」。
也是「Adaptation」的意義,你要欠一些債,爭取提早上市的時間?
你要將一個團隊當作棄子,為了作出一個 POC?
這都是選擇,你是有意識的選擇,還是無意識的呢?

如果你是屬於身不由已的那方,被選擇得對象,那麼你要作出什麼選擇?
讓自已的生存機會提高呢?

Scrum 的理想

Scrum 本身僅僅是個框架,它給了整個團隊非常彈性的空間,
但是仍然有著一些限制。

角色的定位

Scrum Master 要作什麼 ?

Scrum Master 的這個角色,不存在一般的組織架構之中,
這個導致 Scrum Master 的養成非常不易,
偏偏「Master」這個名詞,使得人們對 Scrum Master 有不切實際的期待與依賴
「Scrum Master」本身是個教練型的角色,需要旁觀者的客觀心態,
需要觀察記錄個人、團隊乃至於整間公司文化,
(「記錄與側錄」是重要的,這能讓你從另一個旁觀者角度看事情,不要只有一個視角。)
同時又需要各種方法去教導、引導團隊,這包含「對 Scurm 的理解與實踐」與「工程實踐」
有太多 Scurm Master 常常會犯的錯如下:

  • 淪為安排會議的助理角色
  • 過度將焦點放在 Scurm Master 本身
  • 照表操課的帶四大會議,而不是觀注在團隊的成長與過程
  • 缺乏工程實踐的能力與經驗,導致在引入實踐時淪為空話
  • 身兼不同的角色,造成角色錯亂
  • 無法存活導致被炒,即使有冒出新芽的改變也隨之消失
  • 只顧著存活而無法帶來實踐改變,純粹變成招覽「人材」用的蜜糖(HR:我們有屎逛,很潮喔;進去後才發現是小瀑布,真的很潮)

PM 可以轉型 PO 嗎 ?

簡單的說,當然可以。
你想改變的是你的 Title 還是你的作事方法 ?
理想的 PO 應該更觀注在產品上面,我們希望你會產品有想法、有願景甚至有策略,
如果沒有策略,只是想作一些嚐試也是可以的。

第二點,排序,「我全都要」是不負責任的說法,是大頭症而且偏離事實的中二病
如果團隊也承諾「我全都給」,就要小心整個組織是不是落入「國王的新衣」,集體自我欺騙的困境了
Walking Skeleton 是一個非常好用的手法,在探索出你的商業策略之後,
要找出關鍵的支柱,儘可能快速的推出你的 MVP ,
實際上如果你要你的產品有 Value,你是很難在一個衝刺中完成一個可發佈的產品的
(Demo或概念介紹影片是有可能的,如果你也走記者會趨動開發的話…)。

我們理想上不要有半成品,或是讓半成品儘可能的少,存在時間儘可能的短,
我還蠻推崇 User Story Mapping 在排序上的作法,
「二分法」少了它就不行的功能、基礎建設就作,其它就不作。
讓 Walking Skeleton 儘早的串通,拿掉所有不必要的功能,儘可能的輕薄。
端看你的 DoD,要達成 End to End 的 Walking Skeleton。
但是有時候即使你拿掉所有非必要的東西,也需要多個衝刺才能完成 Walking Skeleton,
當你完成之後,你的每個開發就可以在這基礎上進行增量開發,而且每次都可以作端到端的完整測試,
持續的開發增量,直到滿足 PO 對 MVP 定義,才有 Release 的可能。
相同的手法在團隊拆解 Task 也是可行的。

MVP遠比你的想得大的多

歸納一下重點:

  • 想清楚產品現在最重要的策略
  • 由策略 Break Down 出 Product Backlog Items (User Story)
  • 只有作與不作的優先權
  • 只觀注要作 PBI 與 DoD
  • 找到 Walking Skeleton,在一開始儘可能的薄(這個手法適用 PBI 與 Task)
  • 事實上 MVP 仍然很大,大到你不一定能在一個衝刺中完成
  • 問兩個問題,什麼讓我們變慢?什麼能讓我們更快?
  • 增量不等於 Release
  • PO 要觀注產品的價值,與市場的變化
  • 不要 Management 不要 Dispatch,只要排序並約定最上面的最先完成
  • 進入衝刺後就不要異動排序
  • 保留插件泳道可以帶來一些彈性,但是也是有代價的,請確保放入泳道的 Task 比衝刺的 PBI 更有價值

導入

這幾年 Scurm 或是 Agile 在台灣各地相當盛行,
這是個好事,但同時也意味著各種妖魔鬼怪的出現,
認証單位的出現,品質低下的 WorkShop 出現,
同溫層自 high 喊口號的出現,這些都是一些壞味道,
敏捷是很科學的作法, Scurm 的經驗主義也是有其理論基礎。

在這之上如果變成直銷或是宗教化的宣傳,
我個人認是非常不好的,甚至會成為導入的反動力,
我常常在想,滿牆的便利貼與宗教崇拜這樣不是與彊屍道長沒有兩異嗎 ?

我的建議的導入方法

  • 先觀察找到瓶頸或 Key Man
  • 爭取組織關鍵人物的支持,如果有老闆的支持更好
  • 帶來變化與作法,更勝於帶來新名詞
  • 一直講一直講,通常講到對方不耐煩才只是開始

現實的殘酷 與 仍然存在的疑惑 ?

最後,記錄一些現在遇到不解的問題

團隊的不穩定

不論是個人的生涯規劃,或是內部的組織調動,
都會讓團隊的成員組成變得不穩定,而基於經驗主義的 Scurm,
如果每個衝刺的團隊組成都不一致,會不會帶來太多的變數 ?
甚至團隊成員是抱著打工的心態,而 Scrum Master 也當作這些成員是別人家的孩子,
而不關心其成長。這樣總體的團隊能成長嗎 ?

過多的變數

承上題,在實驗的過程中,我們會希望儘可能的控制變因,
但是實務上,包含人員、工作內容(可能過度偏向某個 Skill Set)、假期、Deadline與項目的複雜度/範圍等…
都會帶來很大的變因,這樣的估點是否仍然有意義 ?

規模化

理想的 Scrum 想達到有效溝通,建議人數要在 5~9 人,實際上整個組織一定會遠超這個人數,
雖然現在有 Less 等 solution,但是我仍沒有理解與體會到其帶來的價值,
因此仍然有所疑惑。

跨職能與自組織

本身產品的覆蓋範圍就很大,團隊人數限制在 5~9 之時,
要完成 End to End 變得有些不切實際與困難。
一方面現有的產品欠了相當大的技術債,而團隊欠了相當大的學習債, 團隊成員在開發上已經有點捉襟見肘,還要跨領域的學習,又要深入鑽研某項技能。

如何讓團隊認知到清還債務能讓我們變快,並且在工程實踐上達到要求也是需要時間,
而成員確在職涯規劃上,2~3年就換個工作,而花了時間培養的戰力瞬間飛滅。

更甚是組織內部寧可用便宜的人材,換掉好不容易養成的人才。
對此我深感無力,跨職能與自組織最後也是淪為口號。

全貌

以上,我仍在觀察、記錄…
並且尋找機會,「Change Your Company」。
做正確的事,然後等著被炒

(fin)