[閱讀筆記] 執行力的修練

與成功有約的四個實踐原則

四個實踐原則

紀律一、鎖定極重要目標

80 % 用來維持改進日常工作,
20% 的時間與心力投入能帶來影響的目標,
尋找與使命有更直接關連的目標。

法則

  1. 不能聚焦顧於兩個以上的極重要目標
  2. 選擇能幫助打贏戰爭(高層次)的戰役(低層次)
  3. 高階領導人可以否決,但不能命令(賦予所有權與參與感)
  4. 設定「在何時之前從 X 到 Y」的終點線

問題

  1. 在我們營運的其它部份保持目前績效的前提下,哪一部份的改變能帶來最大的影響 ?
  2. 為了在這場戰爭中取勝,至少一定要打贏哪些戰役 ?

反思

影響地圖 ?
OKR

紀律二、從領先指標下手

領先指標的特徵

  1. 具有預測作用
  2. 團隊有能力影響它
  3. 領先指標可能是違反直覺的

找到槓桿(領先指標)的支點
領先指標

紀律三、設置醒目計分板

  • 優秀的團隊隨時知道自已是屬於領先還是落後。
  • 制定一個屬於「隊員」的計分板
    • 必須簡單
    • 必須顯而易見
    • 同時揭露領先與落後指標
    • 知道正在贏或輸
  • 賽局本身比計分板重要
    • 求「勝」而非求「不敗」

反思

  • 看板
  • Burn Down/Up Chart
  • 電子看板的便利,實體看版的可視性

紀律四、落實當責

為了把績效得分往前推進而向整個團隊作出承諾,並以有紀律的方式貫徹執行。

  • 《爛工作的三個跡象》
    1. 無人聞問
    2. 可有可無
    3. 無從評量

原則

  1. 每周的同一時間開會
  2. 別讓日常事項進入會議

議程

  1. 報告:報告上次作出的承諾
  2. 檢討:從成功和失敗中學習
  3. 計畫:釐清並作出新承諾

問題

  • 我可以作什麼會對領先指標有幫助 ?

反思

  • Scrum 四大會議的立會與 Retrospective

執行

極重要目標構想 目前的成果(從 X) 期望的成果(到 Y) 期限(在何時之前) 排序
領先指標構想 如何評量 排序

領先指標 Check List

  • 是否從團隊成員及其他人蒐集了大量意見與建議 ?
  • 是否有預測作用 ?
  • 團隊能不能影響它 ?
  • 它是不是可以被測量 ?
  • 蒐集他的價值是否高於成本 ?
  • 每個指標是否包含動詞 ?
  • 是否可以量化 ?

計分版主題

  1. 趨勢線
  2. 速度計
  3. 長條圖
  4. 紅綠燈
團隊極重要目標 落後指標
領先指標 1 圖表
領先指標 2 圖表

(fin)

[閱讀筆記] 學徒模式--優秀軟體開發者的養成之路

送書啦

前情提要

最近公司來了幾個來自 Build School 的學員,
作為暑期工讀生,開發一些 Side Project ,
雖然工作很忙,不過我倒是對帶新人還蠻有信心的。

這些小朋友問了一些問題,讓我想到自已剛畢業和剛從資策會出來的那時候。
有的問題很大,大到可能要一輩子都無法解決。
有的問題很小,小到聽了會笑,但對他們來說可能像是眼裡的一粒沙。

我在學習的初期,最困難的問題是不知道怎麼問問題
而過了一陣子後,我發現一個更糟的狀況,我不知道我有問題
而這本書大致上解決了我的上述的兩個問題。

為何選擇這本書

非常好讀,你不需要從頭讀到尾。
首先是每個篇幅非常的短,大概只要 5~10 分鐘就能讀完。
再來是除了第一章外,每一大章都會有一個地圖,
讓你能看見全貌。

如何讀這本書

而每一篇都包含情境、問題、解決方案與參照,
情境與問題就像是這本書的 GPS ,
讓你知道你落在這本書的哪個點上 。
解決方案是通篇的主體,但是對我來說後面的行動更為重要,
我開始練習 Kata、寫 Blog、尋找導師大多是因這本書而開始的。

最後,都會附上相關章節的參照。
這非常有幫助,原因是在這張地圖上,
你可能會同時落在幾個點上。
那麼地圖與參照就會非常有幫助,你可以查找地圖發現有關連的章節,
也可以直接透過參照找到其它章節。

會不會是雞湯文

會不會變成雞湯,其實取決於你自已。
書上的行動,你作了嗎 ?
如果不行動,就當作喝雞湯吧。

這本書的缺點

這本書強調的是如何成為一個工匠,
如何成為一個達人。
我知道的現實是,很多工程人員到了一定的程度就會轉換職務
如果你的目標是成為管理者,業務等…職務
我想這本書在幫助上可能會少一半。
畢竟這不是一本教你管理、銷售或領導的書。

後記

現實上的工匠真的很少,這不是條發大財的路。
走起來也很辛苦,不過這是個革命的時代,
各行各業都在與軟體產業發生衝突與融合,
如果能有更多優秀的工程師,我們就不需要工程師了。

鍚將會扮演它的角色,直到這個世界不再需要鍚為止,
然後鍚就會變成金子。

El Alquimista

期許自已和看到這篇文章的你都能成為更好的工程師。

(fin)

[閱讀筆記] 第八個習慣 --- 從成功到卓越

找到自已內在的聲音

第八個習慣

  • small things listen to your head , big things listen to your heart
  • 孟加拉微形貸款的反思

    讓 99% 的人生活更好,並賺取收入成為 1 % 富者

  • Most ailing organizations have developed a functional blindness to their own defects. They are not suffering because they cannot resolve their problems, but because they cannot see their problems. — John Gardner → 看見全貌 — 李智樺(Rudy Lee)
  • 願景和心聲是慢慢演化出來的…感覺到人類的某種需求,而且他們的良知的回應試圖滿足這些需求就會出現願景
  • 個人領導(發揮影響力)是一種選擇,是一種自已必須去贏得的自由——只有當你贏得之後,領導才會變成一種選擇。
  • 3+1 的世界(Marsen Lin)
Column Core First Second Third
全人思維模式 心靈 心智 身體 情感
四種才能 SQ 精神 IQ 智力 PQ 身體 EQ 情感
人類四項基本需求 發揮影響力 學習 生活
四種才能的最高表現 良知 願景 自律 熱情
四種才能的反模式 自我 受害者心理 放縱 淪為社會的鏡子
將人物化的管理 老闆 規定 效率 控制
領導的四項職責 以身作則 探索方向 整合體系 充份授權
四大管理措施 文化 策略 結構 執行
完整工作中的全人 服務 計畫 執行 評估
  • 現況

報酬: 不公 / 一般 / 很好
尊重: 有 / 無
自主性: 有 / 無
意義: 有 / 無
愛 : 有 / 無

  • 通往卓越的地圖 vs 選擇地圖

    通往卓越的地圖

    選擇地圖

  • 目的和手段是不可分的,目的實際存在於手段之中。

  • 用於實現目的手段與目的同樣重要 — 康德

  • 只要目的合理,用什麼樣的手段都是合理的。— 馬基維利

  • 在刺激和回應之間有一段空間,我們有自由和能力選擇自已的回應

    聰明是一種天賦,而善良是一種選擇。

  • 所有事物都是經過兩次創造,一次是心智上的創造,一次是實際的創造

問題

  • 我在家庭、社區、工作單位裡感受到什麼需求 ?
  • 我是否擁有可以滿足這個需求的天賦才能 ?
  • 滿足這個需求的機會是否能激起我的熱情 ?
  • 我的良知是否在激勵我積極投入、採取行動 ?

激勵他人尋找內在的聲音

  • 領導是一種如何充份授權的藝術。
  • 對人充份授權,對物進行管理與控制
  • 工業時代的管理,老闆/規定/效率/控制

七、以身作則

  • 不論什麼時候,如果你認為問題在別人身上,那麼這個想法本身就有問題。
  • 主動,是自我授權的一種形式
  • 有的時候,在某些文化中,得到原諒比得到批准來得容易
  • 7 種自我授權
    1. 坐等吩附
    2. 詢問
    3. 提出建議
    4. 我想做
    5. 執行並立刻報告
    6. 執行後定期報告
    7. 只管去做
  • 做一個燈塔,不要當法管; 作一個榜樣,不要當批評家。

八、成為表率

  • 七個習慣

    • 主動積極
    • 以終為始
    • 要事第一
    • 雙贏思維
    • 知彼解已
    • 統合綜效
    • 不斷更新
  • 有系統的個人計劃

    • 「確定任務與價值」
    • 願景和價值觀
      1. 每日計劃
      2. 每周計劃
      3. 設定目標
      4. 確定任務和價值

九、贏得信任

  • 儲蓄

    1. 先努力理解他人
    2. 信守承諾
    3. 誠實、開誠布公
    4. 和善、有禮
    5. 雙贏或無交易的思維方式
    6. 明確的期望
    7. 對不在場者保持忠誠
    8. 道歉
    9. 接受回饋意見
    10. 寬恕
  • 支出

    1. 先求被他人理解
    2. 破壞承諾
    3. 圓滑的操縱
    4. 冷漠、無禮
    5. 我贏你輸或我輸你贏的思維方式
    6. 破壞期望
    7. 不忠誠、欺騙
    8. 驕傲、自負、傲慢
    9. 不接受回饋意見
    10. 心懷怨恨
  • 必要的犧牲

    1. 不耐煩、自我中心
    2. 情緒、感情、時間
    3. 自我中心、傲慢、控制
    4. 自我、時間、感覺、成見、偏見
    5. 勝者為王、競爭意識
    6. 蜻蜓點水式的交流
    7. 某些社會認可、祕而不宣
    8. 自我、傲慢、驕傲、時間
    9. 自我、傲慢、驕傲、起反作用的溝通
    10. 驕傲、自我中心
  • 內在原則

    1. 相互了解
    2. 完整/執行
    3. 遠見/價值觀、統一/執行、相互了解
    4. 遠見/價值觀、完整/執行
    5. 相互尊重/互惠
    6. 相互尊重/互惠、相互了解、創造性合作、更新
    7. 遠見/價值觀、完整/執行
    8. 遠見/價值觀、完整/執行
    9. 相互了解
    10. 遠見/價值觀、統一/執行
  • 真正的付出,必須是對方願意接受的東西

  • 說謊是將問題推給未來,誠實則是將問題留給過去

  • 成年人只是大孩子而已

  • 你如何看待我的角色與目標 ? 你如何看待自已的角色與目標 ?

  • 我們自已選擇什麼樣的回應方式,是決定生活的關鍵因素

  • 信任和愛一樣

    • 愛是一個動詞,而愛情是愛的結果
    • 讓自已的情感服從價值
    • 信任需要冒險、磨練, 沒有風險是最大的冒險
  • 個人層次

    • 態度積極的好榜樣。
    • 不要抱怨、比較、批評、競爭和爭鬥。
    • 作一個燈塔式的人物,而不要作法官式的人,作一個榜樣而不要作批評家
    • 無力改變所有的事情(別人)時,改變自已
  • 人們需感覺自已被欣賞,需要感覺到自已所從事的工作是有價值的

十 融合心聲

  • 雙贏思維只要有一方願意改變就可以了

    改變得從願意改變的那個人開始 — 改變提問,改變人生

  • 傾聽的連續性

    1. 忽略
    2. 假裝在聽
    3. 選擇性傾聽
    4. 專心傾聽
    5. 以同理心傾聽 (以對方的框架思考)
  • 解決方案

    1. 你輸我贏/你贏我輸 [交易]
    2. 妥協 [交易]
    3. 相互了解(沒有共識) [轉變]
    4. 你輸我贏/你贏我輸(增加了解與關心) [轉變]
    5. 妥協(在關係上取得統合綜效) [轉變]
    6. 在問題和關係上取得統合綜效 [轉變]
  • 人們會看到同樣的事實,但是以個人的經驗加上不同的解釋,創造意義並產生行為

  • 一但你有了身分上的權利,你會認定只有一個答案是對的,自我愈強烈,愈固執,愈僵化。

  • 沉默是必要的,問一個問題「說出自已的真正想法,會比沉默好嗎 ?」

  • 兩個步驟

    1. 問「是否願意尋求一種比雙方的方法更好的解決方案 ?」
    2. 覆述對方的意見直到對方認可,確定你真正理解他的意思。
  • 第三選擇不一定是第三個方案,而是心態上真正的理解對方,而非妥協

  • 領導是一種選擇,而非一種職位

  • 與人和文化有關時,快就是慢,慢就是快

十一 同心協力

問題

  1. 你清楚了解組織的目標嗎 ?
  2. 你對組織忠誠奉獻嗎 ?

四個現實問題

  1. 市場狀況
  2. 核心競爭力
  3. 利益關系人的期望與所需
  4. 價值觀

刺蝟概念

  1. 你真正的專長是什麼 ?
  2. 你對什麼事充滿熱情 ?
  3. 人們會對什麼掏腰包 ?
  4. 你的良知期望怎麼作 ?

十二 整合體系

問題

  • 你是怎麼想的 ?
  • 成長過程中,你最喜歡做什麼 ? 有什麼優秀的表現 ?

    持續整合不只是工程的事,而是整個組織與公司的事。

十三 充分授權

  • 透過目標與責任以達到預期結果。
  • 若以授權的名義放任員工,自律也無法實現。
  • The Doctrine of Stubborn refusal — 出於良知
  • 服務型的領導人的問題
    • 工作進度如何 ? 依據看板
    • 你正在學習什麼 ?
    • 你的目標是什麼 ?
    • 我能幫你作什麼 ?
    • 作為一個幫助者,我表現得如何 ?
  • 選擇的自由與能力
    • 領導
      • 意義 : 創造性刺激
      • 愛 : 真心誠意的保證
      • 職責: 愉快的合作
    • 管理
      • 獎勵 : 自願服從
      • 恐懼 : 願意服從
      • 憤怒 : 反抗或退出
  • 為不同意見保留一席之地,建立回饋機制讓他理解別人的感受。

問題

  • 誰喜歡現在的方式 ?
  • 誰可以作 ? 誰有能力作 ? 誰有責任作 ?
  • 真正的考驗是改變能不能持久 ? → 回顧與重溫約定。(類似 Retrospective)
  • 你的建議是什麼 ? 你們的意見是什麼 ?
  • 知而不行,是為不知
  • 你所知道的東西 還不足以讓你提出合適的問題

參考

(fin)

[學習筆記] 重構使用 Introduce Variable for Substring

要知道的事

  • 這是個人的學習記錄
  • 可能對你沒幫助
  • 不知不知最可怕。快不起來,因為你不覺得慢
  • 希望對你有幫助
  • 使用 Visual Studio & ReSharper

問題

把原本的程式
string url = “http://localhost:5000/api/v3.3/refund/PayPal/SF188964T"

變成下面這樣

string domain = “http://localhost:5000";
string code = “SF188964T”;
string payType = “PayPal”;
string url = $”{domain}/api/refund/{payType}/{code}”;

你會怎麼作 ?

我原本的作法,一個字一個挖出來,命名變數,再填回去。

怎麼作可以更快 ?

透過 Resharper 的 Refactor > Introduce Variable > Introduce Variable for Substring 即可快速重構。

參考 91 的影片

我實測原本的開發時間約為2~3分鐘,善用工具約可以在 30 ~ 45 s 完成。
效能提昇 400 % !!!!!(聽起來就很威)

當然結合了 Vim 與 Visual Studio 的工具才達到這樣的速度,強烈推薦以下課程。

我只推薦好東西

【極速開發+】 202002 第九梯次 台北

課程優點

  1. 學心法也學作法,不是只有理論的打高空
  2. 認識同好 ,來上課的同學都是願意精進自已的人,所以身上有很多寶可以挖
  3. 認識 91 ,遇到 91 盡量挖就對了,偷到一招半式都完勝 10 年在那邊處理 NullReferenceException
  4. 學習怎麼學習

其它本周學習事項

  1. 可以讓中斷點停留在 lambda express 中
  2. MSTest 請用 DescriptionAttribute 加入測試描述
  3. .Net MVC Model Binding 若欄位為空字串預設為轉為 Null

本周待確認事項

  1. 如何對 HttpClient 測試 ?
  2. 如何用 FluentValidation 作測試 ?

(fin)

[實作筆記] Stripe 串接

Agenda

  • Stripe 簡介
  • 註冊
  • 付款

Stripe 簡介

Stripe 是一家提供讓個人或公司在網際網路上接受付款服務的科技公司。
Stripe 提供在網上接受付款所需的技術、避免信用卡詐騙技術及銀行基礎設施

– 引述自 Wiki

Our mission is to increase
the GDP of the internet

– 引述自 Stripe 官網

業務範圍大多為歐美,亞洲方面支援香港、新加坡與日本等國…

註冊

只需要透過信箱即可註冊
註冊後需要到信箱收取確認信以開通帳戶,
如果真的要在線上使用需要提供公司相關的資訊,
但以開發者而言,此時的 Stripe 已經提供一組測試 api 供你使用,
隨後即可以登入後台操作。
請在 Dashboard 的左邊側欄>開發者>API 密鑰,取得 Secret key
在後面呼叫 API 中都會使用這組 Secret Key 請特別留意。

付款

這裡只介紹 Stripe 信用卡的付款方法,
並根據 Stripe 文件整理一些資訊給大家。

如下圖,這是一個標準的 Stripe 結帳流程,
stripe overview

主要的兩個步驟在 Create SourceCreate Charge
這裡會透過呼叫 Stripe API 以完成付款流程。

下面會介紹幾種信用卡的付款方式, 僅供參考,實際作業請以最新的 Stripe 文件為準。
過程中如有呼叫 API 都會用 curl 帶過,
Secret Key 一律以 sk 表示, Public Key 以 pk 表示
Stripe 有提供多種語言的範例或是提供 SDK 或 Libary, 請親自去看它們的文件囉。

使用 Checkout Session

Step 1. 建立 Session

1
2
3
4
5
6
7
8
9
10
11
curl https://api.stripe.com/v1/checkout/sessions \
-u sk_test_dAa6L6BL4gZDuscgJcl3an8K00aJL2yIaW: \
-d payment_method_types[]=card \
-d line_items[][name]=T-shirt \
-d line_items[][description]="Comfortable cotton t-shirt" \
-d line_items[][images][]="https://example.com/t-shirt.png" \
-d line_items[][amount]=500 \
-d line_items[][currency]=hkd \
-d line_items[][quantity]=1 \
-d success_url="https://example.com/success" \
-d cancel_url="https://example.com/cancel"

Step 2. 建立 CheckOut 頁面

Step 1 會取得一組 session_id ,請填入頁面中的{session_id}
pk 請填入 public key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script src="https://js.stripe.com/v3/"></script>
<script>
var stripe = Stripe("pk");
stripe
.redirectToCheckout({
// Make the id field from the Checkout Session creation API response
// available to this file, so you can provide it as parameter here
// instead of the {{CHECKOUT_SESSION_ID}} placeholder.
sessionId: "{session_id}",
})
.then(function (result) {
// If `redirectToCheckout` fails due to a browser or network
// error, display the localized error message to your customer
// using `result.error.message`.
});
</script>

Step 3. 載入頁面

調整你的付款流程,引導消費者到 Step 2. 的頁面,
會自動轉導到 Stripe 的標準頁,並且出現填寫信用卡的資訊,
消費者需要手動輸入卡號後,確認付款。
如果確認會引導至 Step 1 的 success_url
消費者取消的話會引導至 Step 1 的 cancel_url

說明

這是標準的第三方串接步驟,可以發現在 Step 3 的時候,
消費者會被帶離你原本的站台到 Stripe 的付款頁面,
這樣的好處是你不需要經手敏感的資料,像是信用卡卡號,
但是有時候,轉導到外部頁面會讓消費者不安進而中斷結帳,
那我們可以參考其它的作法。

使用 Source

一般來說,Source 是 Stripe 最常用的付款方式,
但在歐洲相關規定調整後,信用卡不再建議使用這個 API。
可以參考官方文件的說明

Card Payments with Sources
Use Sources to accept card payments from around the world.

Use of this API is no longer recommended. We recommend adopting the Payment Intents API.
This new integration lets you benefit from Dynamic 3D Secure and helps you prepare for
Strong Customer Authentication regulation in Europe.

不過理論上您的客戶中沒有歐洲人的話,還是可以呼叫這個 API ,
作法如下:

Step 1. Create Source 並指定 Type 為 Card

1
2
3
4
5
6
7
8
9
curl https://api.stripe.com/v1/sources
-u sk
-d type=card
-d currency=hkd
-d owner[email]="[email protected]"
-d card[number]={{cardNumber}}
-d card[exp_month]=12
-d card[exp_year]=2020
-d card[cvc]=123

Step 2. Charge With Source

Step 1 可以取得 source id,利用 source id 呼叫 charge API 付款

1
2
3
4
5
6
7
8
curl https://api.stripe.com/v1/charges
-u sk
-d source={{source id}}
-d amount=411
-d currency=hkd
-d description="Charge for [email protected]"
-d metadata[a]="b"
-d metadata[b]=123

使用 Token

不過 Sorce 信用卡在官方文件上不再被建議使用,
我們可以看看另一個類似的方法 Token

Step 1. Create Token 並傳入卡號

1
2
3
4
5
6
curl https://api.stripe.com/v1/tokens
-u sk
-d card[number]={{cardNumber}}
-d card[exp_month]=12
-d card[exp_year]=2020
-d card[cvc]=123

Step 2. Charge With Token

Step 1 可以取得 token id,利用 source id 呼叫 charge API 付款

1
2
3
4
5
6
7
8
curl https://api.stripe.com/v1/charges
-u sk
-d source={{token id}}
-d amount=412
-d currency=hkd
-d description="Charge for [email protected]"
-d metadata[a]="c"
-d metadata[b]=663

使用 Payment Intent

終於來到 Payment Intent 了,
實際上這是目前 Stripe 最推薦的信用卡支付方式,
呼叫的作法也很類似於 Source 與 Token,
沒什麼特別考量的話,建議使用這個付款方式。

Step 1. Create Payment Method 並傳入卡號

1
2
3
4
5
6
7
8
curl https://api.stripe.com/v1/payment_methods
-u sk
-X POST
-d type=card
-d card[number]={{cardNumber}}
-d card[exp_month]=12
-d card[exp_year]=2020
-d card[cvc]=123

Step 2. Create Payment Intent With Payment Method

Step 1 可以取得 payment method id,
利用 payment method id 呼叫 payment intents API 付款

1
2
3
4
5
6
7
8
curl https://api.stripe.com/v1/payment_intents
-u sk
-d payment_method={{payment method id}}
-d amount=555
-d currency=hkd
-d confirmation_method=manual
-d confirm=true

小結

想快速成立訂單請用 Session 的作法,
想要避免轉換率下降,請使用 Payment Intents。

參考

(fin)

[實作筆記] 讓 SonarQube 檢查你的代碼

前情提要

  1. SonarQube 是一個開源的代碼品質(Quality)管理系統
  2. 我目前的公司 N 社是自架 SonarQube Server 再與 CI 結合
  3. 能夠透過工具讓代碼品質提昇,我訂定的目標如下
    • 免費
    • 能夠與 CI 結合,持續檢查代碼品質
    • 與 Side Project 結合

應該要知道的事

  1. 掃瞄環境為 Windows
  2. 掃瞄專案為 .Net Core 2.2 版
  3. 使用 PowerShell 執行 Command
  4. 也許不需要知道

記錄

  1. 申請SonarCloud帳號,我直接使用 Github

    • 需要允許 SonarCloud 存取 Github 的專案 Repo
    • 建立一組 Token, 用來作身份驗証,可以重複使用請勿外流
    • 如果要刪除 Token 請至 My Account > Security 找到並 Revoke
      建立 token
      建立 token2
    • 下載 SonarQube 執行檔,請選擇你的語言
      執行命令
  2. 執行掃瞄前的準備作業

    • 設定 Path (實務上我沒有設定)
    • 切換到專案目錄底下
  3. 啟動掃瞄,以 .Net Core 為例

1
> dotnet "{path of sonar scanner}\SonarScanner.MSBuild.dll" begin /k:"{project name}" /o:{group name} /d:sonar.host.url="https://sonarcloud.io" /d:sonar.login="{your token}"

輸出結果

1
2
3
4
Using the .NET Core version of the Scanner for MSBuild
Pre-processing started.
中略...
16:40:31.855 Pre-processing succeeded.
  1. 建置專案
1
dotnet build

輸出結果

1
2
3
4
Microsoft (R) Build Engine version 15.9.20+g88f5fadfbe for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.
中略...
Build succeeded.
  1. 上傳結果
1
> dotnet "{path of sonar scanner}\SonarScanner.MSBuild.dll" end /d:sonar.login="{your token}"

輸出結果

1
2
3
4
5
6
7
8
9
10
11
12
SonarScanner for MSBuild 4.6.1
Using the .NET Core version of the Scanner for MSBuild
Post-processing started.
中略...
INFO: Analysis total time: 21.446 s
INFO: ------------------------------------------------------------------------
INFO: EXECUTION SUCCESS
INFO: ------------------------------------------------------------------------
INFO: Total time: 2:27.021s
INFO: Final Memory: 24M/72M
INFO: ------------------------------------------------------------------------
The SonarQube Scanner has finished

最後到 SonarCloud 的網站上就可以看到報告結果,
下一步就是將這整段流程結合 CI ,官網推薦是使用 Travis CI,
也有相同的文件與資源,我會試試看或是使用 Jenkins,
如果有機會能更進一步,我想結合 OpenShift ,
讓部署的過程中結合代碼品質檢查。

結果上傳

參考

(fin)

[實作筆記] Unit Testing With TypeScript

前情提要

  • 難得有機會寫前端的東西
  • 其實只有寫 JavaScript
  • 我要用 TypeScript 寫
  • 合理的軟體工序 TDD
  • 所以我要寫測試
  • node 版本 v8.11.1
  • npm 版本 5.6.0
  • TypeScript 版本 3.3.3333

Context & User Stories

來自前端的需求,在一個日曆工具要加入對可選日期判斷的邏輯。
原始需求如下,g、h、i、j 可以透過修改日曆元件選項調整,
而細微的日期與時間判斷需要撰寫新的方法作判斷。

原始需求

a. 每週一過中午 12 點不能選週二及以前的日期
b. 每週二過中午 12 點不能選週三及以前的日期
c. 每週三過中午 12 點不能選週四及以前的日期
d. 每週四過中午 12 點不能選週五及以前的日期
e. 每週五過中午 12 點不能選隔週一及以前的日期
f. 每週日都不能選
g. 90 天以後的日期不能選
h. 需指導我們如何讓特定日期不能選,以因應遇到國定假日的狀況
i. 預設為選擇最近一個可以使用的日期
j. 改成中文

測試環境準備

使用 Mocha 與 Chai

1
npm i -D chai mocha nyc ts-node typescript

安裝 TypeScript

安裝至專案

1
>$ npm i -D typescript ts-node

安裝至全域

1
>$ npm i -g typescript

建立專案 tsconfig.json

1
>$ tsc --init

安裝 MochaJs

1
npm i -D mocha @types/mocha

安裝 Chai

1
npm i -D Chai @types/chai

設定測試

package.json

1
2
3
"scripts":{
"test": "mocha -r ts-node/register tests/**/*.test.ts",
}

寫測試

1
2
3
4
5
6
7
8
9
import { expect } from 'chai';
import Calculator from '../src/calculate';

describe('calculate', function() {
it('add', function() {
let result = Calculator.Sum(5, 2);
expect(result).equal(7);
});
});

實作代碼

1
2
3
4
export default class calculator {
static Sum(a: number, b: number): number {

}

執行測試

1
npm t

修正代碼

1
2
3
4
5
export default class calculator {
static Sum(a: number, b: number): number {
let c = a + b;
eturn c;
}

測試結果

1
2
3
4
5
calculate
√ add


1 passing (61ms)

測試覆蓋率

// TODO

安裝 nyc

1
npm i -D nyc

設定 scripts

1
2
3
"scripts":{
"testCover": "nyc -r lcov -e .ts -x \"*.test.ts\" mocha -r ts-node/register tests/**/*.test.ts && nyc report",
}

執行

1
> npm run testCover

執行結果

1
2
3
4
5
6
7
8
9
10
11
12
略過測試部份
------------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
------------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 98.63 | 100 | |
src | 100 | 100 | 87.5 | 100 | |
Calculator.ts | 100 | 100 | 100 | 100 | |
beforeShowDay.ts | 100 | 100 | 80 | 100 | |
tests | 100 | 100 | 100 | 100 | |
beforeShowDay.test.ts | 100 | 100 | 100 | 100 | |
calculator.test.ts | 100 | 100 | 100 | 100 | |
------------------------|----------|----------|----------|----------|-------------------|

完整 Test Cases

今天是 2019/3/30 號星期六 12:05
√ 日曆上 2019/4/02 星期二 出貨 可以選
√ 日曆上 2019/4/02 星期二 設定為國定假日, 出貨 不可以選

今天是 2019/3/24 號星期日 12:05
√ 日曆上 2019/3/25 星期一 出貨 不可以選
√ 日曆上 2019/3/26 星期二 出貨 可以選

今天是 2019/3/23 號星期六 12:05
√ 日曆上 2019/3/25 星期一 出貨 不可以選
√ 日曆上 2019/3/26 星期二 出貨 可以選

今天是 2019/3/22 號星期五 23:05
√ 日曆上 2019/3/25 星期一 出貨 不可以選
√ 日曆上 2019/3/26 星期二 出貨 可以選

今天是 2019/3/21 號星期四 01:30
√ 日曆上 2019/3/21 星期四 出貨 不可以選
√ 日曆上 2019/3/22 星期五 出貨 可以選

今天是 2019/3/22 號星期五 23:05
√ 日曆上 2019/3/25 星期一 出貨 不可以選
√ 日曆上 2019/3/26 星期二 出貨 可以選

今天是 2019/3/19 號星期二 23:05
√ 日曆上 2019/3/21 星期四 出貨 可以選

今天是 2019/3/22 號星期五 12:59
√ 日曆上 2019/3/22 星期五 出貨 不能選,因為現在時間超過 12 點
√ 日曆上 2019/3/25 星期一 出貨 不能選,因為現在時間超過 12 點
√ 日曆上 2019/3/26 星期二 出貨 可以選
√ 日曆上 2019/4/1 星期一 出貨 可以選
√ 日曆上 2019/3/23 星期六 出貨 不能選,因為現在時間超過 12 點

今天是 2019/3/21 號星期四 23:59
√ 日曆上 2019/3/21 星期四 出貨 不能選,因為現在時間超過 12 點

今天是 2019/3/18 號星期一 12:00
√ 日曆上 2019/3/18 星期一 出貨 不能選,因為現在是 12 點
√ 日曆上 2019/3/19 星期二 出貨 不能選,因為現在是 12 點
√ 日曆上 2019/3/20 星期三 出貨 可以選
√ 日曆上 2019/3/21 星期四 出貨 可以選

今天是 2019/3/18 號星期一 10:00
√ 日曆上 2019/3/24 星期日 出貨;不能選,因為週日都不能選
√ 日曆上 2019/3/18 星期一 出貨 不可以選,因為當天不能選
√ 日曆上 2019/3/19 星期二 出貨 可以選
√ 日曆上 2019/3/20 星期三 出貨 可以選

心得

能堅持「工序」是專業人士的表現,
我相信這是一種實務上能保持品質與速度的作法。
特別是越大規模越複雜的專案,
避免掉進焦油坑的方法就是一開始就別踩下去。

寫測試案例也是一種技能,在這次的 Case 中,
E2E 測試是相對困難的,受限於時間與日期,
單元測試可以控制時間反而成了絕妙的工具。

我一開始寫的測試並不優良,涵蓋的情境不夠(註:這裡並非指程式碼的函蓋率),
但是與 PO 反覆確認之後,調試出的情境終於滿足了需求,
看來我還要增進一下寫測試案例的能力。

最後,下次再試試用 Jest 寫寫看。

參考

(fin)

[實作筆記] ASP.NET 專案部署地雷-消失的靜態檔

應該知道的事

  • 這個是 Debug 的筆記
  • 用的是 .Net Framework 4.6 不是 .Net Core
  • 對你可能沒有幫助

問題

方案裡面有三個 Web 專案 Web1 、Web2 、Web3,
因開發某功能需要加入一個文字靜態檔 Iamfile.txt
在部署的時候卻無法將檔案部署至網站根目錄。

誤解

對檔案按右鍵 > 屬性 > 複製的輸出目錄 > 下拉選取一律複製。

複製的輸出目錄

很可惜,這個設定的調整會讓這個檔案在建置的時候輸出到指定資料夾中( Ex: \bin ),
這個操作會影響 csproj 如下:

1
2
3
<None Include="Iamfile.txt">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

正解

注意 Tag 的名稱為 None,需要調整為 Content
目前不確定如何用 IDE 操作。

1
<Content Include="Iamfile.txt" />

補充

找到你的 MSBuild 版本

1
cd C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin 

執行建置

1
λ MSBuild.exe D:\Projects\IsASolution.sln /p:Configuration=QA;DeployOnBuild=true;PublishProfile=Mall.QA.pubxml;MvcBuildViews=false;AutoVersion=True

執行後輸出的位置需要看你的部署檔 *.pubxml 如下範例
可以在 D:\Archives\QA\IsASolution 找到我的輸出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<!--
此檔案是由您 Web 專案的發行/封裝處理程序所使用。您可以編輯此 MSBuild 檔案,
以自訂此處理程序的行為。若要深入了解,請造訪 http://go.microsoft.com/fwlink/?LinkID=208121。
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WebPublishMethod>FileSystem</WebPublishMethod>
<LastUsedBuildConfiguration>QA</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish />
<ExcludeApp_Data>False</ExcludeApp_Data>
<publishUrl>D:\Archives\QA\IsASolution</publishUrl>
<DeleteExistingFiles>True</DeleteExistingFiles>
<PrecompileBeforePublish>True</PrecompileBeforePublish>
<EnableUpdateable>True</EnableUpdateable>
<DebugSymbols>False</DebugSymbols>
<WDPMergeOption>DonotMerge</WDPMergeOption>
</PropertyGroup>
</Project

(fin)

[實作筆記] 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)視窗中,查詢到記錄。

Console

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)

Please enable JavaScript to view the LikeCoin. :P