CSM Day2-1 Planning

雖然實施部分的 Scrum 是可能的,但結果並不是 Scrum。Scrum,
只有在完整的時候才會存在,也才能有效的成為其他技巧、方法論、和實務發揮的運作舞臺。
— Scrum Guide 結語**

Planning (with 2~4 hours timebox)

Planning 第一階段

  • 說明要作什麼(What)。
  • 對 Item 估點。
    • 基於 Velocity
    • 基於 Commitment

估計的方法

有 Size (大、中、小) 或點數(Story Point、Planning Poker)等方法,
實務上都使用點數較多,原因是多個項目的加總,在比較時點數較有感覺。

參考下例:
Sprint 1 有 4 個 Item,Size 是分別是大、大、中、小。點數分別是 13、13、5、2。
Sprint 2 有 5 個 Item,Size 是分別是大、中、中、中、小。點數分別是 13、8、8、5、1。

Sprint Size 點數
Sprint 1 2 大、1 中、1 小 13+13+5+2 = 33
Sprint 2 1 大、3 中、1 小 13+8+8+5+1 = 35

Velocity-Driven 或 Commitment-Driven 哪個比較好呢?
大多數的 Scrum Master 恐怕會跟你說團隊決定。
兩者的差異點在哪裡呢 ? Scrum 是立基於經驗導向的流程控制理論
Velocity 有數據佐証,較為客觀;但是 Team members 與 Backlog Item 其實都是會變動的(理想上當然是穩定更好),
如果 PO 與 Team 的互信足夠, Commitment 其實也可以符合經驗導向,比如說:「 我的 Develop Team 平均每個 Sprint 裡面可以交付 90% 的 Item。」
換句話說還是團隊決定,但是彈性是很大的,或許 Develop Team 有信心時使用 Commitment ,而團隊缺乏信心時或許可以用過往的 Velocity 作為標準。

Planning 第二階段

  • 組織 Task 以達到溝通與完成 Items 的目的
  • 接受來自第一階段的 Backlog Items
    • 要相信「Velocity」
    • 不要馬上分派 Task
      • 會形成 Silo ,被分派者會提早將專注力從 Item 移到 Task 上
      • 保持專注在 Item 上
  • 對 Task 估時。
    • 估時請包含假勤/會議/教育訓練…etc
    • 別忘了 Backlog Refactoring 的時間
    • 在 Time box 裡估完成估計的 Task
    • Maintain → 用經驗法則抓一點 Buffer 吧
    • 用小時去估時 ex:1~8,如果超過 8 小時稍後再作進一步的工作拆解
    • Task 如果仍有模糊不清的部份,可以先估一個較大的時數,再進一步拆解
    • 隨著 Sprint 進行,隨時調整 Task (增/刪/修)

Sprint Backlog常常會長這樣

  • 這是用來估計而非報告,不要混為一談;Scrum 在乎的是結果,而非時間。

  • 只管相對大小,ex: Task A 與 Task B,由 Alice 與 Bob 領走可能會如下呈現

    • Task A Alice 領走估時 8 hours,Task B Bob 領走估時 4 hours
    • Task A Bob 領走估時 2 hours,Task B Alice 領走估時 4 hours
    • 其實不用追究原因,或是是否相同,這因人而異,不求準確
      • 我自已的理解是
        • 而是某人估了高時數時,能不能有人來支援他?
        • 能不能透估時發現 Team Member 的能力差異,並引入其它方法提升 Member 能力 (ex: Pair Programming、Sharing、讀書會…)
        • 承上一點,注意佔用 Sprint 的時間,並回頭檢核是否有效;沒有比較的改善,只是自我滿足。

Sprint Backlog
Tasks to turn product backlog into working product functionality.
Tasks are estimated in hours , usually 1-8 .
Team with more than 8 hours are broken down later .
Team members sign up for tasks , they aren’t assigned .
Estimated work remaining is updated daily .
Any team member can add, delete or change the Sprint Backlog work for the Sprint emerges
If work unclear, define a Sprint Backlog with a larger amount of time break it down later .
Update work remaining as more is known, as items are worked

Scrum Master 的 Check List

  • Product Backlog Item 排序
    • 上一個 Sprint 未完成的 Item
    • Review Meeting 的反饋所產生的 Action Item
    • Acceptance Criteria 是否已經定義清楚了?
      • 需視 Scrum Team 的流程定義
      • User Story 的 Descriptions & Acceptance criteria 希望是 PO 與團隊一起共同完成。
        • 實務上蠻花時間的,寫出來的效果也不見得很好。(*也許是我們的操作方法不對)
      • User story 是用來溝通的,不是用來寫的!
    • 是否有人請假?
      • 可以提前確認
      • 有人請假要評估是否吃得下?
      • 吃不吃得下由團隊評估

PO 在 Sprint 中一直塞東西是不是一種不信任團隊的壞味道?
Team 毫無反應,就只是個接 Task 的 Team 是不是也是一種不信任?

OKR in Scrum

我對 OKR 與 Sprint Goal 的認知。
不難理解為什麼 RD 會覺得干我屁事,又沒加薪也沒獎金,
看了兩本書、用口說說就能讓人激勵人心太不且實際了

比如說:作功德,最後反而會讓第一線人當成幹話,幹話聽多了失去信任就更糟糕了。

Team

適當的領取 Item,過多的 Item 進去,只會在 Sprint 結束時,有一堆半成品。
而半成品在面對(市場)變化,失去產品的彈性。

軟體(代碼)的優化與重構只作一半,算不算是半成品?
軟體隨著架構或相依的套件升級或補丁,在 Sprint 期間通常會有必要升級或是改變寫法。
最後就變成下圖,擁有各種版本的 Code Base。

version

這會拖慢開發速度,讓新進成員難以理解,也難以用 Code Review 或自動化 Tool 解決,
目前比較理想的解法是 Pair-Programming 與時時重構。

可是,PO 願意花費這個時間去調整嗎?
可以用 Veloctiy 變低當作參考嗎?Veloctiy 變低的原因有很多種。
又或者由 Team 自行決定?
Team 要能自組織要多久時間呢 ?「不是拿掉主管,團隊就能自組織」;
如果是跨 Team 共同開發的產品又該如何?
Arch 的調整需要更其它或上層的團隊允可(這也會占用不少時間)
把產品拆掉變成多個服務?讓團隊成為服務 Owner ?
拆成服務又需要多久時間呢?
組織架構調整?

當對 PO 來說,這是沒有商業價值的事。,實務上該怎麼作?或該怎麼量化呢?
註記:或許這只是個過度理想化的偽議題,畢竟「凡存在,必合理」。

好像有點懂了,又有點不懂…

(fin)

[活動筆記] CSM Day1

課程簡介

燃盡圖的壞味道

如下,請忽略文字
上圖使用 Item 作為 y 軸單位 ,下圖使用 Task 作為 y 軸單位,
上圖直到最後一天,才瞬間完成所有 Item ,我稱之為跳崖式
下圖是在 Sprint 的過程中,Task 增加了,有可能是亂入也有可能是建立 Task 時不準確,我稱之為登山式。

燃盡圖的壞味道

上圖的例子還算是樂觀的,畢竟最後 Sprint 都完成了,實務上更多是 Sprint Failed 。

我在 C 公司時是使用 Task , N 公司是使用 Item ,在 N 公司與 Scrum Master 討論都建議使用 Item 繪製,
最主要立論都在於 Item 才有價值,而 Task 只細部的工作項而已。但對於我的角色而言(Team Member),
能看見 Task 起伏很重要,往上爬待表 Task 變多了,可能的原因有插件、未預期的項目等…,可以作。

一個大原則, PO 看 Item,Team 看 Task。

Product Backlog 使用 Item 沒有問題,因為是 PO 在看的。
Sprint Backlog 可以兩種都用,Task 燃盡圖可以看見 Task 的增加,或許使用 Kanban 就足夠了。

重點是,看見後要作什麼 ?

觀注點的不同

PO 觀注 WHY & WHAT ;Team 觀注 WHAT & HOW

如上圖右下,在 Scrum 的角色中,PO 觀注 WHY & WHAT ; Team 觀注 WHAT & HOW ; 而 Scrum Master 觀注的點

上圖中間的部份,則是我個人猜想,世界上大部份事情都可以被拆成 3+1;
恰巧這個 Context 符合我的猜想,就是市場、產品、錢,那個 1 我沒想到就順手先畫了下來。
PO 觀注產品,目的要儘快的丟到市場去作測試,而達到盈利(錢)的目標。所以他要思考市場反應背後的原因(WHY),
設想策略,提供產品(WHAT),最後才會 Break Down 成一個個 Product Log 再進到 Scrum 的循環之中,
當團隊成功的交付了可發佈的產品,PO 才有了武器去市場作戰,才有機會取得回饋。

  • Time
    • PO 觀注 Sprint
    • Team 觀注 Daily
  • Scope
    • PO 觀注 Item
    • Team 觀注 Task
  • Cost
    • PO 觀注 Teams
    • Team 觀注 members

專案管理的三個維度,Time、Cost 與 Scope

Done 、Undone & Debt

*建議先看影片(中文字幕支援原始影片)。

如果你有杰出的工程團隊、最好的開發工具、運用正確的工程實踐(TDD、Code Review、Pair Programming etc…)、、
對 Business Domain 與 Tech Domain 有深入理解 ,開發時不受干擾,並且擁有足夠的資源

太理想了嗎 ?

  • 在現實的考量中如何堅持開發者的良好實踐?
  • 在缺乏實踐的環境如何導入?
  • 在缺乏經驗的情況下如何學習 ?
  • 在焦油坑中如何實踐 ?
  • 你有說「不」的勇氣嗎?
style=info

回過頭來,如何定義完成,在 Scrum 的框架中對完成保留了很大的空間,PO 與 Team 的角度可能不儘相同,
所以由團隊來定義,模糊的空間很大。

同樣的定義用來指導開發團隊,讓團隊知道在短衝計劃會議中可以選擇多少產品待辦事項。
每一個短衝的目的是交付潛在可發佈的功能增量,而這些功能符合 Scrum 團隊目前對 「完成」之定義。

由 PO 主導的 DoD 會由商務與 PO 個人績效導向(如果有的話),而工程上的重要實踐往往會被犧牲,
一方面是團隊成員對專業的堅持度不夠,一方面或許整體公司的開發文化就是如此。

這些事項是不是該落入 Task 之中 呢?

  • Retro 後產生的 Action Item
  • 教育訓練
style=info

UnDone 的工作項目有很多,這意味著半成品(浪費)。
Undone 的項目可是五花八門包羅萬像的,技術債、文件製作、修復錯誤等…,只要不符合完成的定義,是不是都算 Undone 呢 ?
但是有的時候的 Sprint Goal 是需要欠下一些債的,這中間是一個取捨與權衡的,所以記得保持透明;
你或許需要一個 Undone Sprint 來消弭這些 Undone Task。

"Undone Work"

注意 Undone 是會影響開發速率的,在 Scrum 之中開發速率應該是個關鍵指標
不論你用 KPI、MBO 或 OKR 都應該觀注這個指標。
而速率是由經驗法則測得。當然有時為了快速交付,有些暫解是在所難免,要小心LeBlanc’s Law
這些債務除了團隊反應之外,更可以透過觀察速率指標,評估適當的時機消除與改善。

在 Time、Cost 與 Scope 都被鎖死的情況下,結果往往是犧牲了品質。
舉例說明: 這個東西 11 月底要,不能 Delay ,交由你們作(不準增加開發成本),功能我全都要。

品質的犧牲我竟然相對覺得還好…更可怕的是,「管理者」為了滿足那個鐵三角,而作出一些虛妄狡詐之事。
不僅僅是犧牲了品質,更給了高層錯誤訊息,甚至形成惡性循環,讓領導者作出錯誤判斷,更進一步的損耗品質。
最後成本提高、時間拉長與交付範圍不得不縮小,這不僅僅是鐵三角崩潰,連透明度與互信與也會失去。

學習債 & Cross Learning

Scrum Team 的成員組成有個前提,團隊具備完成端到端的開發的所有技能。

  • 開發團隊 3~9 人
  • 開發團隊是跨職能的,團隊擁有產出產品增量所需要的所有技能。

如果產品所需的 Skill Set 很大,巴士因子就容易過低;
比如說一個七人的團隊,要製作一個包含 Web、App(包含 iOS 、 Android )的產品。
人力與能力配置假設如下,而且 DoD 的定義為所有載具的交付,那麼這個團隊的風險極高 ; 實際的經驗會變成 Mini Water Fall。
這也意味著每當工作移轉到某個職能的負責人身上,當下就形成瓶頸。

Alice Bob Carol Dave Eve Frank Grace
UI/UX Front-End Back-End iOS Android DBA Tester

讓我們換個情境來說明,同樣的七個人,如果每個具備的 Skill Set 彼此能夠互相援助,巴士因子就提高了,而工作分配會更容易些,
較不易形成個人瓶頸(當然還是有可能,比如說這個 Sprint 的 Items 都落在 DBA 身上之類 …),很明顯這樣的團隊組成更能適應變化。

Alice Bob Carol Dave Eve Frank Grace
UI/UX Front-End Back-End iOS Android DBA Tester
Tester Back-End Front-End Android iOS Front-End Back-End
UI/UX iOS Tester Back-End Android

你不用樣樣精通,但是你最好是練山型。

上面的團隊很理想,在人力市場上卻很難得到你想要的牌,所以才需要訓練,但是要花時間。
但在開花結果之前會發生什麼事呢 ?
人員可能會離職、團隊會異動、團隊中有成員認為專精勝於通才(強調一下,這沒有對錯,只是價值觀的不同)

(以上為 Day 1, 不保証有 Day 2)

心得小結

  • Scrum 跟生產力沒有關係
  • PO 與 Team 的觀注點是不同的
  • Scrum Master 更觀注過程
  • Scrum Master 要有意識的不作為
  • 專任的 Scrum Master 能有更好的發揮,但在初期你也需要被培訓,並在實作中學習,你不見得會擁有那樣的環境。
  • 更多時候是文化與組織架構的問題,你需要透過系統思考來審視
  • Cross-Learning:Scrum 對團隊的要求更高,但本身的設計也會促使團隊成長。
  • 維持團隊的穩定,良好的工程實踐,維持交付速率,隨著經驗成長。
  • Change Your Company。
  • 開發人員的陽光、空氣、水;陽光的心態、空間(權限與能力)、薪水。

同場加映

(fin)

[閱讀筆記] 誰搬走我的乳酪? 堅持與PIVOT

故事簡述

一個大的迷宮裡面有許多乳酪,
四個角色「哈哈」、「猶豫」、「好鼻鼠」、「飛腿鼠」
「哈哈」是主角,它們在迷宮裡找乳酪,
每個人喜歡的乳酪都不相同,一開始他們找到了一個大乳酪站,
但是隨著時間過去,乳酪消失了,
「好鼻鼠」、「飛腿鼠」很快的離開尋找新的乳酪,
「哈哈」與「猶豫」在原處停留了一陣子,
最後「哈哈」終於啟程去尋找乳酪的心路歷程…

心得

「哈哈」是主角,
「猶豫」代表過去的自已,
「好鼻鼠」、「飛腿鼠」分別代表兩種成功的人,
一種是有敏銳的嗅覺,能快速找到「乳酪」所在的方向
另一種是行動派,能快速嘗試迷宮的各個走道。

迷宮意謂著這個世界,乳酪讓人生成功與幸福的事物。
是的,乳酪是會改變的,就像是人生的目標也會改變
不論是人生的階段的不同,或是市場的變化或是單純心境的改變。
你的乳酪就是會改變。

我想起「牧羊少年奇幻之旅」的商人,他永遠不會去麥加(假的乳酪),反而期待著去麥加
才是他真正的乳酪。真實的生活上也是這樣,我們會訂定很多目標,
但是許多目標不是你真正熱情所在,反而訂目標這個行為會成為安慰自已的碎屑乳酪,
訂目標多容易不是嗎?比起實踐它。

最近在看另一本書「OKR 工作法」,裡面也有提到訂定目標(乳酪)的原則。

  1. 目標要明確方向且鼓舞人心
  2. 要有時間限制
  3. 由獨立團隊來執行目標

我只談第一點,但在工作上很少有目標讓自已能振奮起來的,
缺乏獎勵是其一,不合個人目標是其二,說一套作一套是其三。
當然我相信有的幸運兒會工作與個人目標契合的,
缺乏獎勵在台灣是常態,但我認為獎勵只是特效藥,
最糟糕的是說一套作一套,目前有點陷入這樣的困境了。

我的乳酪究竟是什麼呢 ?
不要停在沒有乳酪的地方,但是離開的時機很關鍵啊…
這個小寓言仍然是很樂觀的,完全沒有描述在尋找乳酪的路上餓死情境。
我的乳酪究竟是什麼呢 ?

哈哈的牆上筆記

「有了乳酪,你就會快樂」
「乳酪對你愈重要,你就愈想擁有它」
「如果你不改變自已,你就會被淘汰」
「如果你對未來無所謂畏懼的話,你會怎麼作?」
「雖然比較晚才開始,但也比從來都沒有開始要好得多了!」
「要常常嗅一嗅乳酪的氣味,如此你才會知道它何時開始變質!」
「往新的方向移動能幫助你找到新乳酪!」
「當你擺脫了自已的恐懼,你就會感到無比的暢快和舒適!」
「在還沒有享用乳酪前,先想像我正在享用那些乳酪,這會幫助我快點找到乳酪!」
「你愈找放棄舊乳酪,你就會愈快找到新乳酪!」
「進入迷宮裡尋找新的乳酪,比繼續停留在已經沒有乳酪的地方要安全得多了!」
「食古不化的想法,不會幫助你找到新的乳酪。」
「當你覺得你會發現並享受新的乳酪時,你就會改變你的路徑。」
「及早注意事情的小變化,就能幫助提早適應即將到來的大變化。」

變化是會發生的
預期改變的到來
觀察變化
迅速地適應變化
改變自已
享受自已的改變
隨時準備好迅速地適應改變並再度享用美味的乳酪

(fin)

[實作筆記] Hexo Update 與 npm 修復漏洞

好久沒有作 hexo 更新了,
本身這個 Blog 就是透過 hexo 與 Github 建置的 。
眨眼也過了兩年,文筆仍然很差;
說真的有很大的資訊焦慮,文章的內容也不夠充實,
很多觀點無法忠實的記錄下來,更無法連貫成線;
自已還要多方努力與學習。

如前面所說,這個 Blog 是透過 Github 作存放的空間,
雖然 Github 最近被 Microsoft 收購了;
不過它仍是開發人員最常使用的代碼托管服務與社群,
而且微軟近年轉型相同成功 –— 未來會怎麼樣我們可以觀察後再下定論。

Github 一直以來都有一個很棒的功能,
GitHub’s security alerts for vulnerable dependencies

它能夠對你的 Repo 提供安全警告,
如下圖,這是對我的個人 Blog 所使用的框架 Hexo 的相依性安全警告,
這封信說明了 lodash 這個套件的版本存在著高風險的資安問題,
建議升級套件以解決這個問題。
升級套件

解決方式

使用 npm update , 這個命令會更新專案的 package 到最新版本

1
npm update

可能會看到類似以下的結果,同時提示你可以使用 npm audit fix 更新有風險的 package 以修複這些漏洞

1
2
3
4
5
6
7
8
9
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: [email protected] (node_modules\nunjucks\node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for [email protected]: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: [email protected] (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for [email protected]: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

+ [email protected]
added 126 packages from 59 contributors, removed 80 packages, updated 21 packages, moved 14 packages and audited 3143 packages in 25.064s
found 68 vulnerabilities (21 low, 38 moderate, 9 high)
run `npm audit fix` to fix them, or `npm audit` for details

(fin)

[實作筆記]SQL Server 與 Linked Server 注意事項

原始 SQL

1
2
3
4
5
6
7
8
9
10
11
USE NationalDB
GO
BEGIN TRY
---Select Cross Linked Server
Select Name From LinkedServer.Shop.dbo.Member
---DO SOMETHING
END TRY
BEGIN CATCH
DECLARE @errorMessage NVARCHAR(MAX) = ERROR_MESSAGE();
RAISERROR (@errorMessage, 16, 1);
END CATCH
  1. 變數先行宣告
  2. 非必要不要宣告MAX
  3. 跨 DB 存取不使用四節式的查詢語法,改用 sp_executesql

修改後的語法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
USE NationalDB
GO
DECLARE @errorMessage NVARCHAR(4000) = ERROR_MESSAGE();
BEGIN TRY
---Select Cross Linked Server
EXEC LinkedServer.Shop.dbo.sp_executesql N'
SELECT @Name = Name
FROM dbo.Member
WHERE MemberId = @MemberId
AND Valid = 1',N'@MemberId bigint,@Name varchar(20) OUTPUT',@MemberId,@Name OUTPUT
---DO SOMETHING
END TRY
BEGIN CATCH
RAISERROR (@errorMessage, 16, 1);
END CATCH

(fin)

[踩雷筆記] Docker 啟動 MongoDB Server Error

錯誤訊息

1
2
3
4
5
6
> docker-compose up db
Starting marsen-mongodb ... error
ERROR: for marsen-mongodb Cannot start service db:
driver failed programming external connectivity on
endpoint marsen-mongodb (6f2be666a700187b7f30884478d7231cc210182e2650602e513c6627d0bc3e5a):
Error starting userland proxy: mkdir /port/tcp:0.0.0.0:27017:tcp:172.18.0.2:27017: input/output error

除錯

重啟 docker

(fin)

[閱讀筆記] 拿破崙不是敗在滑鐵盧戰役而是金融戰爭

  • 猶太教典

    • 財富是要塞,貧苦是廢墟
    • 金錢並非罪惡、並非詛咒,而是給予人的祝福
    • 有三件事情會傷人:煩惱、爭吵、空錢包。其中尤以空錢包最為傷人
  • 伊斯蘭人

    • 發明複式簿記
    • 損益表(銷售額與支出費用)
    • 資產負債表(資產與負債)
  • 內克爾

    • 公開透明引發法國大革命
  • 第一艘蒸汽船停駛

    • 既得利益者(運河公司與船東)的反對
  • 緬甸因英國殖民政策引發種族問題

    • 社會地位,英國人 > 中國/印度人 > 克倫族(少數民族) > 緬甸人
    • 獨立後,克倫族反而持續遭受迫害至今
  • 猶太人待過的金融重鎮

    • 西班牙
    • 荷蘭 阿姆斯特丹
    • 英國 倫敦
    • 美國 紐約
  • 一次大戰

    • 四打一的圍毆(英法俄美 vs 德)
    • 德國的優勢,與土耳其建鐵路,與奧匈帝國相同語言,若統一會成為歐陸強國
    • 美國產石油,英國產煤,能源革命導致需求轉向
  • 希特勒

    • 凡爾賽條約是個源頭
    • 希特勒被提名過諾貝爾獎
    • 一開始的「侵略」是「收復國土」
  • 二戰的好處

    • 亞非各國的獨立
  • 共產主義

    • 建立平等社會
    • 平均分配財富
    • 蘇聯未受大蕭條影響
    • 短期的極權,有效;長期的極權,腐敗
    • 蘇聯比其它國家更不平等
    • 「當下有事情該作,只要沒納入事前計劃中就不能作;而當下發現沒必要作的事情,只要已經在計劃裡就非作不可,所以完全無法發揮第一線人員的創意」
  • 凱因斯

    • 二戰對德不合理條款的警告
    • 對「廢除德匯兌保護條款」的警告
    • 美元本位的警告
  • 亡國模式

    • 富人有逃(節)稅特權
    • 國家對窮人課重稅
    • 貧富差距變大

(fin)

[KATA] 最大公因數與最小公倍數

簡介

數學系畢業後,在學寫程式雖然偶爾會用到一些數學特性來解題,
不過到了工作確不是那麼一回事,大多在處理商業流程,資料流 ;
然後在商業流程與資料流中穿梭,找蟲然後補丁 ;

這是最近在練習 Kata 的題目所用到的兩個國中數學,
「最大公因數」與「最小公倍數」,還用程式實踐了「輾轉相除法」,
真的是有點懷念啊 。

這是題外話,Rx(Reactive Functional Programing)感覺很有趣
最近參與 functional programing 的讀書會,
裡面似乎有比現在開發模式更優雅的解決問題方式。
不過門檻有點高,需要具備的抽象思考能力,是種非常燒腦的開發方式。

實作

1
2
3
4
5
6
7
8
//// GCD
static Func<int, int, int> GCD = (numA, numB) => {
return numB == 0 ? numA : GCD(numB, numA % numB);
};
//// LCM
static Func<int, int,int> LCM = (numA,numB) => {
return numA * numB / GCD(numA, numB);
} ;

參考

(fin)

i18n 與 l10n

I18n 國際化

1
2
3
| I | n | t | e | r | n | a | t | i | o | n | a | l | i | z | a | t | i | o | n |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |10 |11 |12 |13 |14 |15 |16 |17 |18 | |

Internationalization 因為字首I到字尾N18個英文字

L10n 在地化

1
2
3
| L | o | c | a | l | i | z | a | t | i | o | n |
|---|---|---|---|---|---|---|---|---|---|---|---|
| | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |10 | |

Localization 因為字首L到字尾N共10個英文字

國際化/在地化常見問題

  • 語言
    • 語系-區碼
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
  [
{"LangCultureName": "af-ZA", "DisplayName": "Afrikaans - South Africa"},
{"LangCultureName": "sq-AL", "DisplayName": "Albanian - Albania"},
{"LangCultureName": "ar-DZ", "DisplayName": "Arabic - Algeria"},
{"LangCultureName": "ar-BH", "DisplayName": "Arabic - Bahrain"},
{"LangCultureName": "ar-EG", "DisplayName": "Arabic - Egypt"},
{"LangCultureName": "ar-IQ", "DisplayName": "Arabic - Iraq"},
{"LangCultureName": "ar-JO", "DisplayName": "Arabic - Jordan"},
{"LangCultureName": "ar-KW", "DisplayName": "Arabic - Kuwait"},
{"LangCultureName": "ar-LB", "DisplayName": "Arabic - Lebanon"},
{"LangCultureName": "ar-LY", "DisplayName": "Arabic - Libya"},
{"LangCultureName": "ar-MA", "DisplayName": "Arabic - Morocco"},
{"LangCultureName": "ar-OM", "DisplayName": "Arabic - Oman"},
{"LangCultureName": "ar-QA", "DisplayName": "Arabic - Qatar"},
{"LangCultureName": "ar-SA", "DisplayName": "Arabic - Saudi Arabia"},
{"LangCultureName": "ar-SY", "DisplayName": "Arabic - Syria"},
{"LangCultureName": "ar-TN", "DisplayName": "Arabic - Tunisia"},
{"LangCultureName": "ar-AE", "DisplayName": "Arabic - United Arab Emirates"},
{"LangCultureName": "ar-YE", "DisplayName": "Arabic - Yemen"},
{"LangCultureName": "hy-AM", "DisplayName": "Armenian - Armenia"},
{"LangCultureName": "Cy-az-AZ", "DisplayName": "Azeri (Cyrillic) - Azerbaijan"},
{"LangCultureName": "Lt-az-AZ", "DisplayName": "Azeri (Latin) - Azerbaijan"},
{"LangCultureName": "eu-ES", "DisplayName": "Basque - Basque"},
{"LangCultureName": "be-BY", "DisplayName": "Belarusian - Belarus"},
{"LangCultureName": "bg-BG", "DisplayName": "Bulgarian - Bulgaria"},
{"LangCultureName": "ca-ES", "DisplayName": "Catalan - Catalan"},
{"LangCultureName": "zh-CN", "DisplayName": "Chinese - China"},
{"LangCultureName": "zh-HK", "DisplayName": "Chinese - Hong Kong SAR"},
{"LangCultureName": "zh-MO", "DisplayName": "Chinese - Macau SAR"},
{"LangCultureName": "zh-SG", "DisplayName": "Chinese - Singapore"},
{"LangCultureName": "zh-TW", "DisplayName": "Chinese - Taiwan"},
{"LangCultureName": "zh-CHS", "DisplayName": "Chinese (Simplified)"},
{"LangCultureName": "zh-CHT", "DisplayName": "Chinese (Traditional)"},
{"LangCultureName": "hr-HR", "DisplayName": "Croatian - Croatia"},
{"LangCultureName": "cs-CZ", "DisplayName": "Czech - Czech Republic"},
{"LangCultureName": "da-DK", "DisplayName": "Danish - Denmark"},
{"LangCultureName": "div-MV", "DisplayName": "Dhivehi - Maldives"},
{"LangCultureName": "nl-BE", "DisplayName": "Dutch - Belgium"},
{"LangCultureName": "nl-NL", "DisplayName": "Dutch - The Netherlands"},
{"LangCultureName": "en-AU", "DisplayName": "English - Australia"},
{"LangCultureName": "en-BZ", "DisplayName": "English - Belize"},
{"LangCultureName": "en-CA", "DisplayName": "English - Canada"},
{"LangCultureName": "en-CB", "DisplayName": "English - Caribbean"},
{"LangCultureName": "en-IE", "DisplayName": "English - Ireland"},
{"LangCultureName": "en-JM", "DisplayName": "English - Jamaica"},
{"LangCultureName": "en-NZ", "DisplayName": "English - New Zealand"},
{"LangCultureName": "en-PH", "DisplayName": "English - Philippines"},
{"LangCultureName": "en-ZA", "DisplayName": "English - South Africa"},
{"LangCultureName": "en-TT", "DisplayName": "English - Trinidad and Tobago"},
{"LangCultureName": "en-GB", "DisplayName": "English - United Kingdom"},
{"LangCultureName": "en-US", "DisplayName": "English - United States"},
{"LangCultureName": "en-ZW", "DisplayName": "English - Zimbabwe"},
{"LangCultureName": "et-EE", "DisplayName": "Estonian - Estonia"},
{"LangCultureName": "fo-FO", "DisplayName": "Faroese - Faroe Islands"},
{"LangCultureName": "fa-IR", "DisplayName": "Farsi - Iran"},
{"LangCultureName": "fi-FI", "DisplayName": "Finnish - Finland"},
{"LangCultureName": "fr-BE", "DisplayName": "French - Belgium"},
{"LangCultureName": "fr-CA", "DisplayName": "French - Canada"},
{"LangCultureName": "fr-FR", "DisplayName": "French - France"},
{"LangCultureName": "fr-LU", "DisplayName": "French - Luxembourg"},
{"LangCultureName": "fr-MC", "DisplayName": "French - Monaco"},
{"LangCultureName": "fr-CH", "DisplayName": "French - Switzerland"},
{"LangCultureName": "gl-ES", "DisplayName": "Galician - Galician"},
{"LangCultureName": "ka-GE", "DisplayName": "Georgian - Georgia"},
{"LangCultureName": "de-AT", "DisplayName": "German - Austria"},
{"LangCultureName": "de-DE", "DisplayName": "German - Germany"},
{"LangCultureName": "de-LI", "DisplayName": "German - Liechtenstein"},
{"LangCultureName": "de-LU", "DisplayName": "German - Luxembourg"},
{"LangCultureName": "de-CH", "DisplayName": "German - Switzerland"},
{"LangCultureName": "el-GR", "DisplayName": "Greek - Greece"},
{"LangCultureName": "gu-IN", "DisplayName": "Gujarati - India"},
{"LangCultureName": "he-IL", "DisplayName": "Hebrew - Israel"},
{"LangCultureName": "hi-IN", "DisplayName": "Hindi - India"},
{"LangCultureName": "hu-HU", "DisplayName": "Hungarian - Hungary"},
{"LangCultureName": "is-IS", "DisplayName": "Icelandic - Iceland"},
{"LangCultureName": "id-ID", "DisplayName": "Indonesian - Indonesia"},
{"LangCultureName": "it-IT", "DisplayName": "Italian - Italy"},
{"LangCultureName": "it-CH", "DisplayName": "Italian - Switzerland"},
{"LangCultureName": "ja-JP", "DisplayName": "Japanese - Japan"},
{"LangCultureName": "kn-IN", "DisplayName": "Kannada - India"},
{"LangCultureName": "kk-KZ", "DisplayName": "Kazakh - Kazakhstan"},
{"LangCultureName": "kok-IN", "DisplayName": "Konkani - India"},
{"LangCultureName": "ko-KR", "DisplayName": "Korean - Korea"},
{"LangCultureName": "ky-KZ", "DisplayName": "Kyrgyz - Kazakhstan"},
{"LangCultureName": "lv-LV", "DisplayName": "Latvian - Latvia"},
{"LangCultureName": "lt-LT", "DisplayName": "Lithuanian - Lithuania"},
{"LangCultureName": "mk-MK", "DisplayName": "Macedonian (FYROM)"},
{"LangCultureName": "ms-BN", "DisplayName": "Malay - Brunei"},
{"LangCultureName": "ms-MY", "DisplayName": "Malay - Malaysia"},
{"LangCultureName": "mr-IN", "DisplayName": "Marathi - India"},
{"LangCultureName": "mn-MN", "DisplayName": "Mongolian - Mongolia"},
{"LangCultureName": "nb-NO", "DisplayName": "Norwegian (Bokmål) - Norway"},
{"LangCultureName": "nn-NO", "DisplayName": "Norwegian (Nynorsk) - Norway"},
{"LangCultureName": "pl-PL", "DisplayName": "Polish - Poland"},
{"LangCultureName": "pt-BR", "DisplayName": "Portuguese - Brazil"},
{"LangCultureName": "pt-PT", "DisplayName": "Portuguese - Portugal"},
{"LangCultureName": "pa-IN", "DisplayName": "Punjabi - India"},
{"LangCultureName": "ro-RO", "DisplayName": "Romanian - Romania"},
{"LangCultureName": "ru-RU", "DisplayName": "Russian - Russia"},
{"LangCultureName": "sa-IN", "DisplayName": "Sanskrit - India"},
{"LangCultureName": "Cy-sr-SP", "DisplayName": "Serbian (Cyrillic) - Serbia"},
{"LangCultureName": "Lt-sr-SP", "DisplayName": "Serbian (Latin) - Serbia"},
{"LangCultureName": "sk-SK", "DisplayName": "Slovak - Slovakia"},
{"LangCultureName": "sl-SI", "DisplayName": "Slovenian - Slovenia"},
{"LangCultureName": "es-AR", "DisplayName": "Spanish - Argentina"},
{"LangCultureName": "es-BO", "DisplayName": "Spanish - Bolivia"},
{"LangCultureName": "es-CL", "DisplayName": "Spanish - Chile"},
{"LangCultureName": "es-CO", "DisplayName": "Spanish - Colombia"},
{"LangCultureName": "es-CR", "DisplayName": "Spanish - Costa Rica"},
{"LangCultureName": "es-DO", "DisplayName": "Spanish - Dominican Republic"},
{"LangCultureName": "es-EC", "DisplayName": "Spanish - Ecuador"},
{"LangCultureName": "es-SV", "DisplayName": "Spanish - El Salvador"},
{"LangCultureName": "es-GT", "DisplayName": "Spanish - Guatemala"},
{"LangCultureName": "es-HN", "DisplayName": "Spanish - Honduras"},
{"LangCultureName": "es-MX", "DisplayName": "Spanish - Mexico"},
{"LangCultureName": "es-NI", "DisplayName": "Spanish - Nicaragua"},
{"LangCultureName": "es-PA", "DisplayName": "Spanish - Panama"},
{"LangCultureName": "es-PY", "DisplayName": "Spanish - Paraguay"},
{"LangCultureName": "es-PE", "DisplayName": "Spanish - Peru"},
{"LangCultureName": "es-PR", "DisplayName": "Spanish - Puerto Rico"},
{"LangCultureName": "es-ES", "DisplayName": "Spanish - Spain"},
{"LangCultureName": "es-UY", "DisplayName": "Spanish - Uruguay"},
{"LangCultureName": "es-VE", "DisplayName": "Spanish - Venezuela"},
{"LangCultureName": "sw-KE", "DisplayName": "Swahili - Kenya"},
{"LangCultureName": "sv-FI", "DisplayName": "Swedish - Finland"},
{"LangCultureName": "sv-SE", "DisplayName": "Swedish - Sweden"},
{"LangCultureName": "syr-SY", "DisplayName": "Syriac - Syria"},
{"LangCultureName": "ta-IN", "DisplayName": "Tamil - India"},
{"LangCultureName": "tt-RU", "DisplayName": "Tatar - Russia"},
{"LangCultureName": "te-IN", "DisplayName": "Telugu - India"},
{"LangCultureName": "th-TH", "DisplayName": "Thai - Thailand"},
{"LangCultureName": "tr-TR", "DisplayName": "Turkish - Turkey"},
{"LangCultureName": "uk-UA", "DisplayName": "Ukrainian - Ukraine"},
{"LangCultureName": "ur-PK", "DisplayName": "Urdu - Pakistan"},
{"LangCultureName": "Cy-uz-UZ", "DisplayName": "Uzbek (Cyrillic) - Uzbekistan"},
{"LangCultureName": "Lt-uz-UZ", "DisplayName": "Uzbek (Latin) - Uzbekistan"},
{"LangCultureName": "vi-VN", "DisplayName": "Vietnamese - Vietnam"}
]
  • 單位
  • 時區
    • 跨時區
    • 日光節約時間
  • 其它
    • 地址
    • 姓名
    • 電話號碼

(fin)

[內部分享]Cache 使用 Files 與 Redis

Why Cache ?

  1. 保護稀缺資源(database/disk io/server)
  2. 提高反應速度,減少延遲(Latency)

Cache Point

  1. 資料即時性/如何除錯/如何更新
  2. Hit Rate
  3. High Availability(HA)
  4. 單位時間承載量

Our Cache

  1. CDN(Cloudfront)
  2. OutputCache
  3. Memory Cache
  4. Redis Cache
  5. File Cache

More Cache not in above

  1. Browser Cache
  2. Database Execution Plan Cache (執行計劃快取)
  3. Elastic Search

File Cache

  • pro
    • 簡單易用
  • con
    • 咬檔
    • 不會自動過期
    • 自已要處理 lock 機制

Redis Cache

  • pro
    • 會自動過期
    • 有提供 Queue 的功能
    • 高速(in memory)
  • con
    • 單緖(Single Thread)
    • HA 的機制費用高
    • Redis 承載量受限於記憶體大小
    • 存儲的東西過大,會導致存取速度下降

實踐問題:在大流量的站台,Cache 過期時如何避免大量 Request 同時更新同一份 Cache ?

Ans: 使用 Lock ,
不要使用.NET 的lock ;.NET Lock 的機制問題在於,Lock 會導致其它 Thread 排隊
當系統處於高流量的情境底下,會發生間歇性 Latency 提高,
而且難以追查問題,也難以重現(一般的壓測無法達到如此高量)。

使用共用的 Lock Key 作判斷標準:

  1. Lock Key 存在,回傳 Cache Data
  2. Lock Key 不存在,建立 Lock Key(包含 Server Id)
  3. 重新取得 Lock Key 如果
    • Server Id 與 Request 相同,更新 Cache Data 並回傳
    • Server Id 與 Request 不相同,直接回傳 Cache Data

[Cache Double check Lock Patter(https://docs.microsoft.com/en-us/previous-versions/office/developer/sharepoint-2010/ee558270(v%3Doffice.14)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static object _lock =  new object();

public void CacheData()
{
SPListItemCollection oListItems;
oListItems = (SPListItemCollection)Cache["ListItemCacheName"];
if(oListItems == null)
{
lock (_lock)
{
// Ensure that the data was not loaded by a concurrent thread
// while waiting for lock.
oListItems = (SPListItemCollection)Cache["ListItemCacheName"];
if (oListItems == null)
{
oListItems = DoQueryToReturnItems();
Cache.Add("ListItemCacheName", oListItems, ..);
}
}
}
}

Other

  1. 使用多執行緖時要小心處理 Exception,不然會導致主程序被中斷
    (File Cache 寫入真實案例,多緖的錯誤不佳會使得 IIS 無法處理錯誤,進而中斷程序(w3wp.exe))
  2. AWS S3 是走 Https Protocal
  3. Redis Delete/Backup 會 Lock Thread
  4. Redis Command Timeout 極短,所以要注意單一 Cache 資料量大小 (建議資料要小於 1k?)

參考

(fin)

Please enable JavaScript to view the LikeCoin. :P