[實作筆記] 重灌開發環境

1.Typescript 踩雷

問題

visual studio 預設會安裝 typescript 2.6
專案使用 typescript 2.3 , 因為暫時無法升級到 2.6 以上的版本
會導致專案無法編譯成功

解決步驟

  1. 在專案目錄執行 npm i 重新安裝相關 module
  2. compile 後發現 node_modules/@types 中有檔案無法成功編譯
  3. 移除 node_modules/@types 整個資料夾
  4. 重新 compile 後仍會無法成功
  5. 移除 C:\Program Files (x86)\Microsoft SDKs\TypeScript\2.6 (非必要,好像要看 vs 預設載入的版本為何?)

2.多語系 dll 衝突

  1. 清空bin資料夾
  2. 清空 c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\資料夾
  3. 重建前台專案

3.Chocolatey

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
choco install googlechrome -y
choco install dropbox -y
choco install evernote -y

choco install git -y
choco install nodejs -y
choco install putty -y
choco install visualstudiocode -y

choco install winmerge -y
choco install slack -y
choco install linqpad -y
choco install 7zip -y

choco install gitkraken -y
#choco install sourcetree -y

(fin)

[翻譯] 為什麼IBM/Google/Microsoft都要在今年增加對台投資?

原文

重點節錄

  1. Google 宣布要在台灣顧用 300 名員工並訓練 5000 名學生在人工智能領域
  2. 微軟也表明未來要在台灣發展 AI 業務,5 年內投資 3400 萬鎂並招聘 200 人
  3. IBM 也表示將在台灣擴展 AI/區塊鏈/雲計算等相關的研發中心,並增聘 100 人以上

美國的投資與台灣的人材

台灣的優點

  1. 人材容易訓練適應 AI 等新產業的工作
    • 通常大學畢業生都有基礎工程知識
    • 台灣的擁有製造業供應鏈(跨軟韌硬體)的人材
    • 便宜(Orz)
  2. 地理優勢(東北亞\東南亞\中國)
  3. 台灣目前政策
    • IoT(物聯網)與智能機器
    • 正在進行產業轉型(硬轉軟)
  4. 其它
    • 穩定且便宜的電力與其它基礎建設
    • 合理的成本結構

中國的競爭

  1. 人材更便宜
  2. 美中貿易戰的擔心
    • 資訊安全
    • 智慧財產

其它

(fin)

[學習筆記] Linux 語法學習筆記 二

上一篇我們學會一些基本 linux command,
接下來我們將介紹更多的命令並組合它們到 shell script.
讓 script 幫助我們完成一些事, 就像魔法一般, 開始囉.

補充指令

vim

開啟 vim 編輯器

echo

印出文字

$ echo “text”
text

印出變數 echo $*

印出 PID (Process ID) echo $$

set

設定變數

$ set good morning marsen

補充:使用 echo 印出變數,從 1 開始
$* 指所有變數

$ echo $1
good
$ echo $2
morning
$ echo $3
marsen
$ echo $*
good morning marsen

進階使用 backticks 執行 cat Command

$ cat > testfile
hello world
sh-4.4$ set cat testfile
sh-4.4$ echo $*
hello world

範例

Hello World

  1. 建立檔案

$ cat > helloworld.sh

  1. 編輯檔案

$ vim helloworld.sh

1
2
#say hello
echo "hello world"
  1. 執行檔案

$ sh helloworld.sh
hello world

變數 variable

  1. 大小寫有分
  2. 使用 read 讀取 input 到變數中
  3. 使用 $+變數名呼叫變數

sample:

1
2
3
4
# this is a shell sample
echo "who are you?"
read name
echo "Hi, $name nice to see you."

executed:

$ sh whoareyou.sh
who are you?
Mark
Hi, Mark nice to see you.

互動式重新命名檔案

sample:

1
2
3
4
5
# this is a shell sample
echo "keyin a filename"
read name
mv $1 $name
echo $name"

$ sh rename.sh file1
keyin a filename
newfile
newfile

其它

  1. 額外的 vim 問題排解 E348: No string under cursor 表示未輸入 i 進入 Insert mode

    • ESC + : , 輸入 w filename (以 filename 保存)
    • ESC + : , 輸入 wq (存儲並離開 vim)
    • ESC + : , 輸入 q! (不存儲並離開 vim)
  2. 「**`**」 Backquote 或 backticks

參考

  1. Unix Terminal Online
  2. 離開 Vim 的100 種方法
  3. 鳥哥的 Linux 私房菜—認識與學習 BASH

(fin)

[學習筆記] 允許 IAM User 存取 AWS Billing Console

前情提要

設定了 IAM User Account 也給予了 Administrator 的權限,
不過仍然看不到 Billing 的頁面資訊 .

Billing

這帶來了很大的不方便, 因為如果要看 Billing 的資訊就要切換到 Root Account
而建立 Administrator IAM Account 的用意本來就是要儘可能不使用 Root Account 作登入.
檢查了權限,明明就有設定 Read Billing 但是仍然看不到.

解決方法

實際上要進入 Billing Console 其實要有兩個步驟

  1. 權限要設定,更多細節可以參考這篇文章(2014)
  2. 要透過 Root Account 在 Account Settings 頁面設定, 允許 IAM user 存取 Billing Console

Root Account

參考

(fin)

[學習筆記] Linux 語法學習筆記 一

參考

學習筆記

Clear

清除目前 terminal 畫面

Cal

產生當下的月曆

$ cal

March 2018
Su Mo Tu We Th Fr Sa

         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

$ cal 2 1985

February 1985
Su Mo Tu We Th Fr Sa

            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

Date

顯示日期與時間

$ date

Sat Mar 10 19:01:37 UTC 2018

$ date  ‘+ %y-%m-%d %n %H:%M:%S:%N’

18-03-10
 19:06:24:126172657

pwd

目前所在的檔案路徑

Touch

建立檔案

mkdir

建立資料夾

cat

寫檔案 cat > filename

ctrl + d 可以離開編輯

讀檔案 cat < filename

合併檔案 cat file1 file2 > merged_file

$ cat > file1
this is file1
$ cat file1
this is file1
$ cat > file2
this is file2
$ cat file2
this is file2
$ cat file1 file2 > merged_file
$ cat merged_file
this is file1
this is file2

mv

重新命名檔案

mv origin_name new_name

rm

刪除檔案或資料夾

$ rm file_name

$ rm -r folder_name/

rmdir

刪除資料夾

$ rmdir folder_name/

cp

複製檔案

$ cp oldfile other_folder/newfile

ln

$ touch one
$ cat < one
$ ln one two
$ ls
one two
$ cat > one
this is one
$ cat < one
this is one
$ cat < two
this is one

hard link 會產生實體檔案,soft link 只是指標的轉向.
如果使用 soft link,當刪除原始檔案時,link 檔案將無法開啟.

檔案權限概觀

三種權限

  • read / 讀 / 100 => 4
  • write / 寫 / 010 => 2
  • execute / 執行 / 001 => 1

每個權限都有一個代號,
read 表示可讀權限, 意味著可以開啟檔案與看見內容,
代號為 4,二進位表示為 100
write 表示可以複寫其內容,
代號為 2,二進位表示為 010,
execute 代表可執行,適用可執行檔或 shell script,
代號為 1,二進位表示為 001.
三種權限都有的話,權限為(111=>7)

三種身份

  • owner 開啟的帳號
  • owner group 開啟的帳號所屬的群組
  • other group 其它的群組

新增一個檔案的時候,
預設只有讀寫,沒有執行的權限 (100|010=110=>6)

指令 umask 的設定值以三個八進位的數字“nnn”代表。
第一個設定數字給使用者自己(owner user),
第二個則是設定給用使用者所屬的群體(group),
第三個給不屬於同群體的其它使用者(other)。
每一位數字的設定值都是三項不同權限的數值加總,
read 權限數值為 4;write 權限數值為 2;execute 權限數值為 1。
結合了前三者的權限數值,單一的數字可設定的範圍是 0 ~ 7;
整體的可設定範圍是 000 ~ 777。
— 鳥哥的 Linux 私房菜

ls

列出資料夾中的所有檔案

ls foldername

列出指定的資料夾中所有的檔案

ls -l

列出資料夾中的所有檔案與其權限資訊

ls 最常被使用到的功能還是那個 -l 的選項,為此,很多 distribution 在預設的情況中, 已經將 ll (L 的小寫) 設定成為 ls -l 的意思了!其實,那個功能是 Bash shellalias 功能呢
— 鳥哥的 Linux 私房菜

chmod

修改檔案權限

sh-4.4$ ls -l
total 4
-rw-r–r– 1 33581 33581 978 Mar 12 17:30 README.txt
-rw-r–r– 1 33581 33581 0 Mar 12 17:32 test
sh-4.4$ chmod 777 test
sh-4.4$ ls -l
total 4
-rw-r–r– 1 33581 33581 978 Mar 12 17:30 README.txt
-rwxrwxrwx 1 33581 33581 0 Mar 12 17:32 test
sh-4.4$ chmod 444 test
sh-4.4$ ls -l
total 4
-rw-r–r– 1 33581 33581 978 Mar 12 17:30 README.txt
-r–r–r– 1 33581 33581 0 Mar 12 17:32 test

uname

顯示系統相關的資訊

$ uname -a                                                            
Linux e955582759de 3.10.0-514.26.2.el7.x86_64 #1 SMP Tue Jul 4 15:04:05 UTC 
2017 x86_64 x86_64 x86_64 GNU/Linux

選項與參數:
-a :所有系統相關的資訊,包括底下的資料都會被列出來;
-s :系統核心名稱
-r :核心的版本
-m :本系統的硬體名稱,例如 i686 或 x86_64 等;
-p :CPU 的類型,與 -m 類似,只是顯示的是 CPU 的類型!
-i :硬體的平台 (ix86)
— 鳥哥的 Linux 私房菜

file

查詢檔案基本資料(類型)

file *

jazzy: ASCII text
mark: empty
marsen: directory

wc

顯示檔案資訊

行數 字數 字元數 檔名

$ wc jazzy

3 10 39 jazzy

wc -l filename

顯示檔案行數資訊

wc -w filename

顯示檔案字數資訊

wc -c filename

顯示檔案字元數資訊

sort

印出排序過後的結果(遞增)

$ sort
owls
pigs
dogs
cats

cats
dogs
owls
pigs

sort filename

印出檔案內排序過後的結果(遞增)

cut

切割資料

參數:
-d 分割字元
-f index (從 1 開始)

範例

cat > filenames
Name-Sport-Age
Roger-Tennis-30
Nadal-Tennis-25
Tiger-Golf-37
Michael-Baseball-49

$ cut -d”-“ -f 1,3 filenames
Name-Age
Roger-30
Nadal-25
Tiger-37
Michael-49

dd

資料處理、拷貝、備份、轉碼;更多

$ cat > infile
this is the input file
$ cat infile
this is the input file

$ dd if=infile of=outfile conv=ucase
0+1 records in
0+1 records out
23 bytes copied, 6.6972e-05 s, 343 kB/s
$ cat outfile
THIS IS THE INPUT FILE

man

查詢其它指令用法

$ man ls

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
LS(1)	User Commands  LS(1)
NAME ls - list directory contents
SYNOPSIS ls [OPTION]... [FILE]...

DESCRIPTION
List information about the FILEs (the current directory by default).
Sort entries alphabetically if none of
-cftuvSUX nor --sort is specified.

Mandatory arguments to long options are mandatory for short options too.
-a, --all
do not ignore entries starting with .

-A, --almost-all
do not list implied . and ..

--author
with -l, print the author of each file

-b, --escape
print C-style escapes for nongraphic characters
Manual page ls(1) line 1 (press h for help or q to quit)

h看更多訊息

$ man ls
h

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
                   SUMMARY OF LESS COMMANDS

Commands marked with * may be preceded by a number, N.
Notes in parentheses indicate the behavior if N is given.

h H Display this help.
q :q Q :Q ZZ Exit.
---------------------------------------------------------------------------

MOVING

e ^E j ^N CR * Forward one line (or N lines).
y ^Y k ^K ^P * Backward one line (or N lines).
f ^F ^V SPACE * Forward one window (or N lines).
b ^B ESC-v * Backward one window (or N lines).
z * Forward one window (and set window to N).
w * Backward one window (and set window to N).
ESC-SPACE * Forward one window, but don't stop at end-of-file.
d ^D * Forward one half-window (and set half-window to N).
u ^U * Backward one half-window (and set half-window to N).
ESC-) RightArrow * Left one half screen width (or N positions).
ESC-( LeftArrow * Right one half screen width (or N positions).
F Forward forever; like "tail -f".
r ^R ^L Repaint screen.
HELP -- Press RETURN for more, or q when done

q退出查詢畫面

輸出用#組成的大形文字

實測未出現,上網查了一下 banner 好像有蠻多不同的類型可以安裝?

compress

壓縮檔案

zcat

讀取壓縮檔案

uncompress

解壓縮檔案

compress 已經退流行了。為了支援 windows 常見的 zip,其實 Linux 也早就有 zip 指令了! gzip 是由 GNU 計畫所開發出來的壓縮指令,該指令已經取代了 compress 。
— 鳥哥的 Linux 私房菜

小結

以上是一些基本的 Linux Command ,
下一篇,我們會建立.sh 檔,將 Linux Command 依照指定的順序執行
並使用 sh 命令執行
用以完成一些更進階的工作.

(more..)

[學習筆記] AWS EC2 學習筆記 AWS CLI 與 Login

安裝 AWS CLI

配置 AWS CLI

EC2 開機

EC2 開機

  • 直接開機跳過網路設定(也還沒有辦法設)
  • 第 5 步驟設定 TAG ,對找尋 ec2 的 instance 很有幫助

設定 Putty

連線機器

連線機器

ex:

1
ssh -i /path/my-key-pair.pem [email protected]

預設連線帳戶

For Amazon Linux, the user name is ec2-user.
For Centos, the user name is centos.
For Debian, the user name is admin or root.
For Fedora, the user name is ec2-user.
For RHEL, the user name is ec2-user or root.
For SUSE, the user name is ec2-user or root.
For Ubuntu, the user name is ubuntu or root.
Otherwise, if ec2-user and root don’t work, check with your AMI provider.

windows 好像是 Administrator ? 求補充

Docker

安裝 Docker

1
sudo yum install docker

啟動 Docker 服務,並讓它隨系統啟動自動載入

1
2
sudo service docker start
sudo chkconfig docker on

雷包

  • 重啟機器的話 public dns 會改變.(意味連線的命令參數會變)
  • 注意使用的 AIM, 不同的 Linux OS 會有不同的套件執行命令
  • ubuntu apt-get
  • CentOS yum

參考

(fin)

[讀書會] 單元測試的藝術 - 導讀、序與第一章

要知道的事

  1. 這是單元測試的藝術的閱讀筆記
  2. 筆記的意思就是不一定會有心得
  3. 這篇主要是導讀

譯者序

  • TDD , Test First to Think First
  • 什麼是好的單元測試?
  • 單元測試三支柱:可信任 可讀性 可維護
  • 綠色安全區域
  • 實務上導入的指引

入門建議

  • 了解如何隔離相依(Part II)
  • Stub 與 Mock 的差異,熟練隔離框架(NSubstitute)
  • 如何撰寫優秀的單元測試(Part III)

進階建議

  • 如何撰寫優秀的單元測試(Part III)
  • 如何在組織中導入單元測試(Part IV)
  • 針對遺留代碼的重構與測試,以及可測試性設計(Part IV)

避免

  1. 測試不穩定
  2. 過度指定
  3. 一次不只測一件事
  4. 測試程式重複過多
  5. 可讀性差

關於本書

前言

有寫測試, 也不保証專案成功,
一個失敗的單元測試案例,
作者歸納原因如下,

  • 脆弱的測試(Prod 改一點,測試就錯一大片)
  • 不易維護
  • 測試間相護依賴
  • 可讀性差

作者推薦的框架

學習路線圖

  • Part I 基礎知識
  • Part II 測試框架
  • Part III 最佳實踐
  • Part IV 組識導入/遺留代碼/設計

目錄

  1. 入門
    • 什麼是優秀的單元測試
    • 單元測試與整合測試的分別
    • 第一個單元測試
  2. 核心技術
    • Stub
    • IoC(DI)
    • 值、狀態與互動
    • 測試框架
    • 事件
    • 深入了解測試框架
  3. 測試程式碼
    • 自動化
    • 綠色安全區域
    • 可信任/可維護/可讀性
  4. 設計與流程
    • 組織導入
    • 遺留代碼
    • 設計與可測試性

定義單元測試

什麼是優秀的單元測試

  1. 自動化, 可重複執行
  2. 容易實現*
  3. 到第二天還有存在的意義(非臨時性的,ex:hotfix)
  4. 任何都可以一鍵執行
  5. 執行速度快
  6. 結果一致
  7. 可以完全控制(不與外部相依)
  8. 獨立於其他測試
  9. 失敗時,錯誤應該是明確的

整合測試

  1. 整合測試相依於真實物件
  2. 整合測試的結果不穩定
  3. 整合測試與單元測試應該被分開(見 ch7.2.2)
  4. 整合測試執行時間長
  5. 依據現實狀況無法完全控制
  6. 缺點: 一次測試的東西太多

理解測試趨動開發

  1. TDD 不保證產品會成功
  2. 步驟
    1. 寫一個失敗的測試
    2. 寫一個符合測試預期的產品程式碼,以通過測試
    3. 重構

TDD 的核心技能

  1. 可維護、可讀、可靠(這本書的目的)
  2. 寫出可維護、可讀、可靠的測試不等於 TDD,至於如何寫優秀的 TDD,作者推薦閱讀〈Test-Driven Development:by Example〉
  3. 就算執行 TDD,也不保証能設計一個完善的系統,作者推薦閱讀Growing Object-Oriented Software, Guided by Tests無瑕的程式碼

簡單說就是:

  • 寫好測試
  • 測試先行(TDD)
  • 設計

作者認為這是三種技能, 同時學習三種技能門檻會相當的高, 最後導致放棄.

小結

  • 優秀的測試就是
    • 自動化
    • 容易撰寫
    • 執行快速
    • 任何人都可以執行,並得到相同結果

揪錯

印刷錯誤

本書資源

  1. Samples
  2. The Art Of Unit Testing
  3. Videos

(fin)

[活動筆記] 變異測試 - 一種改進測試和代碼的 「新」 方法

應該知道的事

  • 範例使用 Java
  • 這場活動使用人肉找尋變異
  • 實務上應使用工具
  • 但是不能完全相信工具
  • 活動聯結
  • 講師是 Odd-e 的姚若舟
  • 簡報 preview 版

什麼是變異

前言

想像一下產品(Production)就是你的身體,
我們可以透過健康檢查(Unit Test);
檢查你的身體有沒有異狀 ?

但是檢查真的可靠嗎 ?
比如說一般的流感的快篩只有 50~60%的準確率,
我們的測試也無法達到 100%準確率(這裡不是指覆蓋率喔).
如何抓到測試抓不到的漏網之魚就是變異測試的目的.

我們透過讓 Production 產生變異(Mutation)
來確認我們的 Unit Test 是否可靠.

題外話,當大流行的時候會跳過快篩節省醫療資源,
因為可能有一半(50%)的患者都是流感,
而快篩準確率也只有 50%,加上時間及醫材成本,
不如直接開克流感能有效抑止疫情

變異測試(Mutation Testing)

變異後導致測試失敗?
yes , good
應該要失敗,表示你的測試有覆蓋到這個變異

no , test not covered
這表示你的測試並未

測試不一定能補捉變異

比如說 邊際值 或是 隱含的互動;
測試覆蓋率 100%也不一定能補捉變異
要麼少了 test case,
要麼多了無意義的代碼
看看以下例子
ex:

1
2
3
4
5
6
foo(x,y)
{
//// logic here
sideeffct();
return z;
}

反思一下, 測試過了代碼就沒問題 ?
不能捉到變異的測試,
有發揮它的功能嗎 ?
一般來說如果透過 TDD 進行軟體開發,
我們的測試應該是會恰巧符合一項 Test Case
而如果是先寫代碼再寫測試,
將很難通過變異測試(容易產生多餘的代碼)

找到變異的幾個方向

  • 邊界條件(< => <=)
  • 反向條件(< => >)
  • 移除條件(永真/永偽)
  • 數學
  • 遞增/遞減
  • 常量
  • 返回值
  • 移除代碼

先寫代碼再寫測試有問題是很正常的

Kata-PokerHands 範例

原碼(使用 java)

變異實例

有問題 ,反向測試案例不足

1
2
3
4
5
6
7
private List<Integer> getPairCardRanks(List<Integer> cardRanks) {
List<Integer> result = new ArrayList<>();
for (int index = 0; index < CARD_COUNT - 1; index++)
if (isTwoNeighborCardRanksEquals(index, cardRanks))
result.add(cardRanks.get(index));
return result;
}

有問題 , -1 但是預期中的行為

1
2
3
4
5
6
protected   Integer   getThreeOfAKindCardRank(List<Integer\> cardRanks) {
for (int index = 0; index < CARD_COUNT - 2; index++)
if (isThreeNeighborCardRanksEquals(index, cardRanks))
return cardRanks.get(index);
throw new IllegalStateException();
}

其它

  1. 沒有 TDD 沒有單元測試,別跑變異測試
  2. 至少要有行級別的覆蓋率(line coverage)
  3. 分支覆蓋(Branch Coverage)好一點 仍不夠
  4. 在需求不變的情況下,再作變異測試
  5. 以變異測試的角度來說,覆蓋率 100%是木有用的(testing coverage is useless)
  6. 發現變異怎麼辦
    • 報告(記錄)
    • 重現
    • 評估
    • 修改 或 補測試
  7. 依靠工具不要相信工具,上一步的評估
    Ex: mock 物件會取代互動實際的行為,導致變異測試失敗

Tools

參與者心得

  1. 變異測試 (Mutation Test) —  一種提高測試和代碼質量的 ”新”  方法速記

  2. Test - 變異(Mutation)測試之你的測試到底是寫爽的,還是有效的?

心得

  1. 佩服當天就能寫出文章的人
  2. 變異測試是好上加好的測試
  3. Odd-e 的講師真的很粉棒, 雖然不致到毀三觀 不過眼界大開

參考

(fin)

[活動筆記] 蝦皮購物新加坡研發團隊技術分享會

應該不用知道的事

  1. 雖然是「技術分享會」實際上在徵才
  2. 不過還是有半場的技術分享
  3. 91app 至少去了 10 個人(含前員工)
  4. 這篇文章對你應該沒有幫助

有關蝦皮

  • 屬於Sea 集團的一部份
  • 東南亞多國服務(新加坡、泰國、馬來西亞、印度、台灣、越南…)
  • 63e Request / Day
  • 8G IO / Mins

選擇

  • Native App / Web / Hybrid / RN ?
  • Cloud / Self machine ?
  • Php/ Nodejs / RoR / Django ?
  • Apache / Nginx ?
  • C ++ / Java / GoLang
  • Memcached / Redis ?
  • SPA / MPA ?
  • Mesos / Kubernetes ?

Qiz & Ans

1

用戶下單的時候, 先收錢還是先扣庫存?
扣掉最後一件庫存後, 收錢失敗怎麼辦?
你已經把「賣完」訊息發給了賣家, 怎麼辦?

2

計算金額用整數還是浮點數?(浮點數不準)

3

Android 一共有幾種螢幕的 DPI ?
Android WebView 和 Chrome 的 Webkit 有何不同 ?

4

Web Service 花最多時間在處理什麼 ?
如何壓搾最高的吞吐量 ?
IO, USE async

5

什麼樣的情境適合增加伺服器數量來增進效能?
stateless
那有狀態怎麼辦 ?

6

load balancer 效能到達瓶頸怎麼辦 ?
IP

7

一天 25TB 的 Log 數量,怎麼不會查到天荒地老

8

Cache & 超賣問題
什麼時候要清 Cache ?

9

Database Master 與 Slave 哪個壓力大 ?(Slave)
增加 index 的代價為何 ?(Space)
Table 多大要 shard ?
Database 多大要分庫 ?
分庫如何作 transaction ?

實踐

  1. Prototype 簡單 Production 困難 (邊際效應/熵)
  2. 可靠:言出必行,作不到也要早點說(知難行易)
  3. Redis 的資料超過 64G 就無法用 bgsave 有效存檔
  4. 在 Production 千萬別用 Redis 的 key 指令
  5. 衡量的基準(benchmark)為何?
  6. 不要對邏輯下 command(不要寫前因後果)
    • Don’t command How
    • Command Why
    • Collect your dots first
    • Connecting the dots

持久發展的研發團隊

  • knowledge
  • 保持開放
  • 尊重事實
  • 信任
  • 可靠
  • 找到根本原因(root cause)
  • 分析 修復 記錄
  • Docs
    • connection docs
    • collection docs

測試

  • 白箱測試
  • 黑箱測試

其它

  • Hypergraph
  • Tech Stack
  • Roles
    • countries PM
    • function PM
  • Scrum 是跑給老闆看的(!!?)
  • 馬來西亞不用小豬 ICON(各地風俗民情不同)

參考

(fin)

[翻譯] C# 的常見錯誤

出處

http://www.dotnetcurry.com/csharp/1417/csharp-common-mistakes

線上工具

https://dotnetfiddle.net

引言

C#是個好棒棒的言語,但是它仍會有超乎你想像的行為,
而且就算你是有經驗的開發者,你也要看一看這篇文章.
這篇文章不講幹話,還會給你代碼喔

C# Quiz

Null Value

Null 很危險啦, 你別在 Null 身上調用方法
(譯注:在公司的維運人員應該還蠻常見這個錯誤的 一 ω 一)

We are all aware that null values can be dangerous, if not handled properly.
Dereferencing a null-valued variable (i.e. calling a method on it or accessing one of its properties)
will result in a NullReferenceException, as demonstrated with the following sample code:

1
2
object nullValue = null;
bool areNullValuesEqual = nullValue.Equals(null);

就安全的角度,好像我們要不停的檢查 reference type 是不是 null ,
雖然這件事常常發生,好像也很難說成是非預期的行為了…
(譯注:又有種中槍的感覺)

To be on the safer side, we should always make sure that reference type values are not null before dereferencing them.
Failing to do so could result in an unhandled exception in a specific edge case.
Although such a mistake occasionally happens to everyone, we could hardly call it unexpected behavior.

看看這個代碼, null 值在 runtime 的時候不會有 type 的

1
2
string nullString = (string)null;
bool isStringType = nullString is string;

No, null 值在 runtime 的時候不會有 type 的
No, null 值在 runtime 的時候不會有 type 的
No, null 值在 runtime 的時候不會有 type 的
很重要所以說三次,
當然你也別想呼叫 GetType() 方法

The correct answer is No.

A null value has no type at runtime.

In a way, this also affects reflection.
Of course, you can’t call GetType() on a null value because a NullReferenceException would get thrown:

1
2
object nullValue = null;
Type nullType = nullValue.GetType();

純量呢?

1
2
3
int intValue = 5;
Nullable<int> nullableIntValue = 5;
bool areTypesEqual = intValue.GetType() == nullableIntValue.GetType();

那我們可不可能用反射(reflection)區分 nullable 跟 non-nullable 的值?
答案是不可能, 看看後面的代碼

Is it possible to distinguish between a nullable and a non-nullable value type using reflection?

The answer is No.

The same type will be returned for both variables in the above code: System.Int32.
This does not mean that reflection has no representation for Nullable, though.

1
2
3
Type intType = typeof(int);
Type nullableIntType = typeof(Nullable<int>);
bool areTypesEqual = intType == nullableIntType;

上面兩段程式在 runtime 拿到的 type 很不一樣喔,
一個是System.Int32一個是 System.Nullable'1\[System.Int32\]

當 null 遇上多載方法 (Handling Null values in Overloaded methods)

1
2
3
4
5
6
7
8
9
private string OverloadedMethod(object arg)
{
return "object parameter";
}

private string OverloadedMethod(string arg)
{
return "string parameter";
}

上面有兩個OverloadedMethod
猜猜看,傳入 null 時會呼叫哪一個方法?

1
var result = OverloadedMethod(null);

有人會猜編譯失敗嗎?
MAGIC ! 竟然可以編譯成功, 而回傳的值是 “string parameter” ,
一般來說,在編譯時期會作型別檢查,相同簽章的方法參數可以被轉型成另一個型別時,是可以編譯成功的喔.
而有明確型別的方法將被優先調用(譯注:求這段.Net Framework 的原碼來看一下,知道的人請告訴我)

如果要指定 null 參數呼叫的多載方法就要對 null 轉型唷,可以參考下面的方法.

1
var result = OverloadedMethod((object)null);

算術運算 (Arithmetic Operations)

好像很少用位移運算吼?
回憶一下 左移移 右移移

1
var shifted = 0b1 << 1; // = 0b10
1
var shifted = 0b1 >> 1; // = 0b0

bits 跑到底並不會重頭開始喔,一直移位到爆掉就變 0 了.
(這裡會用 32 是因為 int 是 32bit 的數值,你可以試試放超過 32 的數值到 for loop 裡會發生什麼事)

The bits don’t wrap around when they reach the end.
That’s why the result of the second expression is 0.
The same would happen if we shifted the bit far enough to the left (32 bits because integer is a 32-bit number):

1
2
3
4
5
var shifted = 0b1;
for (int i = 0; i < 32; i++)
{
shifted = shifted << 1;
}

The result would again be 0.

那我們是不是可以一次移 32 bit,讓它一次變成 0 呢?
靠北啊 竟然不行捏, 你只會拿到 1,
這跟運算子(operator)基本運算有關,在作位元運算的時候,
會拿第一個運算數除以第二個運算數後取餘數,
這導致我們只會拿 32 % 32 的結果 , 也就是 1 啦
(譯注:這段其實我不是很確定,如果錯誤請糾正)

However, the bit shifting operators have a second operand.
Instead of shifting to the left by 1 bit 32 times, we can shift left by 32 bits and get the same result.

1
var shifted = 0b1 << 32;

Right? Wrong.

The result of this expression will be 1. Why?

Because that’s how the operator is defined. Before applying the operation,
the second operand will be normalized to the bit length of the first operand with the modulo operation,
i.e. by calculating the remainder of dividing the second operand by the bit length of the first operand.

The first operand in the example we just saw was a 32-bit number, hence: 32 % 32 = 0.
Our number will be shifted left by 0 bits. That’s not the same as shifting it left by 1 bit 32 times.

好棒棒 你竟然可以看到這裡,
那我們繼續討論 & (and) 跟 | (or) 運算子吧,
這兩個運算子跟一般的運算子有點不一樣

  • 通常只要看運算子的第一個運算數就能得知結果
  • 在有掛 [Flag] attribute 的列舉它們好好用(看一下範例)
1
2
3
4
5
6
7
8
[Flags]
private enum Colors
{
None = 0b0,
Red = 0b1,
Green = 0b10,
Blue = 0b100
}
1
2
Colors color = Colors.Red | Colors.Green;
bool isRed = (color & Colors.Red) == Colors.Red;

上面這個刮號可不能省略喔, 因為(&)運算符的優先順序低於(==)運算符,
不過這段程式沒有刮號的話連編譯都不會過,真是好加在
另外在 .NET framework 4.0 之後的版本提供更棒的方法去檢查 flags

1
bool isRed = color.HasFlag(Colors.Red);

Math.Round()

猜一下這個值會是多少?

1
var rounded = Math.Round(1.5);

猜 2 的就答對了, 下一題
猜一下這個值會是多少?

1
var rounded = Math.Round(2.5);

還是 2 ,
因為預設會取最接近的偶數

No. The result will be 2 again. By default,
the midpoint value will be rounded to the nearest even value.
You could provide the second argument to the method to request such behavior explicitly:

1
var rounded = Math.Round(2.5, MidpointRounding.ToEven);

這個行為可以透過MidpointRounding參數改變

1
var rounded = Math.Round(2.5, MidpointRounding.AwayFromZero);

另外要小心浮點數的精度問題,
以下的例子結果會是 1,( 因為 float 的 0.1 實際上小於 0.1 一 ω 一 )
這提醒我們在處理精確數值時,應轉換成整數處理.
(譯注:使用 dotnet fiddle 時並不會有這個問題, 在 windows 環境下測試的確會有問題)

1
2
3
var value = 1.4f;

var rounded = Math.Round(value + 0.1f);

類別初始化

最佳實踐建我我們應該避免在建構子初始化類別,
特別是靜態建構子.
在初始化一個類別的順序如下

  1. 靜態欄位
  2. 靜態建構子
  3. 實體欄位
  4. 實體建構子

看看這個例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static class Config
{
public static bool ThrowException { get; set; } = true;
}

public class FailingClass
{
static FailingClass()
{
if (Config.ThrowException)
{
throw new InvalidOperationException();
}
}
}

當我們嘗試實例化 FailingClass 時,你會得到 Exception;
值得注意的事,你拿到的會是TypeInitializationException
而並不是InvalidOperationException,

那麼我們是不是可以試著透過 try catch 補捉錯誤,
並修改靜態屬性,重新實體化 class 呢?
答案是不行

一個靜態建構值,如果它拋出一個異常,
那麼無論何時你想創建一個實例或以任何其他方式訪問這個類,
這個異常都會被重新拋出.

1
2
3
4
5
6
7
try
{
var failedInstance = new FailingClass();
}
catch (TypeInitializationException) { }
Config.ThrowException = false;
var instance = new FailingClass();

這個類別在程序重啟前是不能再被使用了(會拋出錯誤),
這在 C# 是個非常糟糕的實踐,
千萬別這樣設計你的類別.

The static constructor for a class is only called once.
If it throws an exception, then this exception will be rethrown
whenever you want to create an instance or access the class in any other way.

The class becomes effectively unusable until the process (or the application domain) is restarted.
Yes, having even a minuscule chance that the
static constructor will throw an exception, is a very bad idea.

繼承與類別初始化

繼承的類別初始化執行順序更加複雜,看看下面的例子

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
public class BaseClass
{
public BaseClass()
{
VirtualMethod(1);
}

public virtual int VirtualMethod(int dividend)
{
return dividend / 1;
}
}

public class DerivedClass : BaseClass
{
int divisor;
public DerivedClass()
{
divisor = 1;
}

public override int VirtualMethod(int dividend)
{
return base.VirtualMethod(dividend / divisor);
}
}

當我們初始化 DerivedClass

1
var instance = new DerivedClass();

你會得到一個除 0 的錯誤 DivideByZeroException
這與執行順序有關

  1. 呼叫 BaseClass 建構子
  2. 執行 DerivedClass VirtualMethod (overrid BaseClass)
  3. divisor 未賦值拋出 DivideByZeroException

多形 Polymorphism

這個例子只是要說明多形的概念與應用,
你可以透過轉形呼叫基底類別的方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var instance = new DerivedClass();
var result = instance.Method(); // -> Method in DerivedClass
result = ((BaseClass)instance).Method(); // -> Method in BaseClass
// The correct answer is: by using the new modifier.

public class BaseClass
{
public virtual string Method()
{
return "Method in BaseClass ";
}
}

public class DerivedClass : BaseClass
{
public new string Method()
{
return "Method in DerivedClass";
}
}

It’s typically used to hide the interface methods from the consumers of the class implementing it,
unless they cast the instance to that interface.
But it works just as well if we want to have two different implementations of a method inside a single class.
It’s difficult to think of a good reason for doing it, though.

另外一個例子是明確實作介面方法,
如果你的類別已經有同名的方法的話.
雖然沒有什麼好理由建議你這樣作.
(譯注:實務上我有在遇到歷史共業這樣作過…)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var instance = new DerivedClass();
var result = instance.Method(); // -> Method in DerivedClass
result = ((IInterface)instance).Method(); // -> Method belonging to IInterface
It’s explicit interface implementation.

public interface IInterface
{
string Method();
}

public class DerivedClass : IInterface
{
public string Method()
{
return "Method in DerivedClass";
}

string IInterface.Method()
{
return "Method belonging to IInterface";
}
}

迭代器 Iterators

小心 Iterators 的陷阱
看看以下代碼:

1
2
3
4
5
6
7
8
private IEnumerable<int> GetEnumerable(StringBuilder log)
{
using (var context = new Context(log))
{
return Enumerable.Range(1, 5);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Context : IDisposable
{
private readonly StringBuilder log;

public Context(StringBuilder log)
{
this.log = log;
this.log.AppendLine("Context created");
}

public void Dispose()
{
this.log.AppendLine("Context disposed");
}
}

假設我們 foreach 呼叫 GetEnumerable 方法,
你預期 Context 類別會有什麼樣的行為?
我們會印出以下的 output 嗎?

Context created
1
2
3
4
5
Context disposed

1
2
3
4
5
var log = new StringBuilder();
foreach (var number in GetEnumerable(log))
{
log.AppendLine($"{number}");
}

不是的,
實際上印出的是

Context created
Context disposed
1
2
3
4
5

這點很重要,
因為實務上你很有可能 using dbconnetion 之類的物件,
那麼你在取得真正的資料之前,
你的連線就已經中斷了

This means that in our real world database example, the code would fail –
the connection would be closed before the values could be read from the database.

看看以下的修正

1
2
3
4
5
6
7
8
9
10
private IEnumerable<int> GetEnumerable(StringBuilder log)
{
using (var context = new Context(log))
{
foreach (var i in Enumerable.Range(1, 5))
{
yield return i;
}
}
}

譯注:看到這裡對 yield return 的使用情境才比較有感啊…

如果你不太熟yield return,其實它只是個語法糖,允許增量執行,
參考以下範例,或許能更容易理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private IEnumerable<int> GetCustomEnumerable(StringBuilder log)
{
log.AppendLine("before 1");
yield return 1;
log.AppendLine("before 2");
yield return 2;
log.AppendLine("before 3");
yield return 3;
log.AppendLine("before 4");
yield return 4;
log.AppendLine("before 5");
yield return 5;
log.AppendLine("before end");
}
1
2
3
4
5
6
7
var log = new StringBuilder();
log.AppendLine("before enumeration");
foreach (var number in GetCustomEnumerable(log))
{
log.AppendLine($"{number}");
}
log.AppendLine("after enumeration");

before enumeration
before 1
1
before 2
2
before 3
3
before 4
4
before 5
5
before end
after enumeration

值得注意的事, 如果你在 loop 當中重複執行以上的代碼,
那麼 Iterators 也會重複執行

1
2
3
4
5
6
7
8
9
10
var log = new StringBuilder();
var enumerable = GetCustomEnumerable(log);
for (int i = 1; i <= 2; i++)
{
log.AppendLine($"enumeration #{i}");
foreach (var number in enumerable)
{
log.AppendLine($"{number}");
}
}

輸出如下,可以明顯看到 GetCustomEnumerable 方法,
實際上被隱含的執行了兩次,
這在 Code Review 的階段也是難以被察覺的.

enumeration #1
before 1
1
before 2
2
before 3
3
before 4
4
before 5
5
before end
enumeration #2
before 1
1
before 2
2
before 3
3
before 4
4
before 5
5
before end

比較好的作法是將 IEnumerable ToList(),
如果你真的需要對 IEnumerable 的結果作 loop 的操作

1
2
3
4
5
6
7
8
9
10
var log = new StringBuilder();
var enumerable = GetCustomEnumerable(log).ToList();
for (int i = 1; i <= 2; i++)
{
log.AppendLine($"enumeration #{i}");
foreach (var number in enumerable)
{
log.AppendLine($"{number}");
}
}

輸出結果

before 1
before 2
before 3
before 4
before 5
before end
enumeration #1
1
2
3
4
5
enumeration #2
1
2
3
4
5

譯者小結

如果真的能夠預期所有的行為的開發人員,
真的是好棒棒,
對我來說 static class constructor 的行為是超乎預期的,
然後對 yield return 的使用場景更有感覺了.
本來預計農曆年就可以完成的翻譯,
竟然也拖了這麼久,看來我英文還是不行啊.

希望對大家有幫助,也請多多看原文 :)

(fin)

Please enable JavaScript to view the LikeCoin. :P