Go學習筆記(2): make a game

來到學習Go 的第二篇文章了,來學習一些控制流以及一些基礎東西吧!

條件式

在 Golang 寫條件式很簡單,就是寫個if 加個大括號就好

// 這裡請記得 "{" 和 if 要同一行,之前自由慣了常常被這件事情雷到!
if true {
    ......
} else if false {
    .....
}

// 或者你可以使用 if  更短的初始化陳述句
if count := 5; count>4 {
   fmt.Println("count is ",count)
}

另外有一個比較特別的是,if 區塊裡面的區域變數(使用到:=)是不能使用在if 外面的喔~

請讓我用以下程式碼來做進一步陳述

詳細可以去看 這裏:https://github.com/r567tw/go-practice/blob/master/HelloWorld/if.go

package main

import (
  "fmt"
)

// var x = 999 // go 裡面也可以宣告一個超越main範圍以外的超全域變數, 但不建議這麼做

func main() {
	fmt.Println("Hello World")
	x := 10

	if x > 0 {
		y := 10
		// x := 100 // 這個等同在裡面宣告新的位置x , 所以外面的x仍然是10
		x = 100 // 這個會污染外面宣告的x , 所以外面的x 會等於 100
		fmt.Printf("x = %d\n", x) // x =100
		fmt.Printf("%d in if statement\n", y) //10 in if statement
	}

	// fmt.Sprintf("%d out of if statement", y) // error
	fmt.Printf("x = %d out of if statement\n", x) // x = 100 out of if statement
}

值得再拿出來說嘴的是,x變數在外面的宣告,如果在if 陳述句範圍裡面使用單純的= ,在main 範圍內的x 是會被更動的,可是如果使用:= , main 裡面的x 不會被影響。

if number,err := strconv.ParseFloat("3.14",64); err != nil{
    log.Fatal(err)
}

fmt.Println(number) // <= 這裡會出現錯誤,因為number 屬於if 區塊裡面的範圍,go 語言的if 裡面變數不得共享

Switch 陳述句

switch rand.Intn(3)+1 {
    case 1 : ...
    case 2 : ...
    case 3 : ...
    default : ...
}

迴圈

話不多說, show you the code

for x:= 0; x<=6; x++ {
    ......
}

// 或者只是條件式得處理
x:=0
for x<=6 {
    x++
}

// 迴圈裡面也可以使用 continue 和 break ...

和 if 一樣,使用到:= 所宣告的變數是沒辦法使用在for 外面的。

function

package main

import (
	"fmt"
)

type bigger = func(int) bool // function 也可以作為型態的一種!

func main() {
	handleFn()

	anonymous := func() {
		fmt.Println("anonymous function")
	} // go 也支援匿名funciton
	anonymous()

	origin := []int{1, 2, 3, 4, 5}
	change := filter(origin, func(el int) bool {
		return el > 3
	})
	fmt.Println(change)
}

func handleFn() {
	fmt.Println("test function")
}

func filter(data []int, big bigger) []int {
	filtered := []int{}
	for _, element := range data {
		if big(element) {
			filtered = append(filtered, element)
		}
	}
	return filtered
}

Make A Game !

接下來,讓我們試著應用以上這些東西,來寫個簡單的猜數字遊戲吧!

package main

import (
	"bufio"
	"fmt"
	"os"
	"math/rand"
	"strconv"
	"strings"
	"time"
)

func main() {
	r := rand.New(rand.NewSource(time.Now().UnixNano())) // 這一行帶入現在的時間,好讓每一次遊戲隨機產生的數字都不一樣, 原來亂數的原理其實是有一個小技巧和規則的
	result := r.Intn(100) // 其實這裡可以隨機產生數字
	
	loop := true // 設定遊戲開始的條件
	for (loop) {
		fmt.Printf("Please Enter a number(1-100): ")
		reader := bufio.NewReader(os.Stdin) // 其實這裏就是類似python 的input 而已
		input, _ := reader.ReadString('\n')
		number,_ := strconv.Atoi(strings.TrimSpace(input)) // 一定要用trimspace, 否則 strconv轉出來的數字不一定取得出來
		switch {
			case (result < number):
				fmt.Printf("smaller than %d\n",number)
			case (result > number):
				fmt.Printf("bigger than %d\n",number)
			case (result == number):
				fmt.Println("Bingo")
				fmt.Printf("result is %d\n",result)
				loop = false
				break
			default:
				loop = true
		}
	}

	fmt.Println("Game is over!")
}

有興趣看程式碼的可以來這裡:https://github.com/r567tw/go-practice/blob/master/makeGame/main.go

小君曰:猜數字遊戲好像可以作為每個程式語言的入門磚,相對於前端的Todo list 呵呵

Go學習筆記(1): HelloWorld

在之前寫到今年的計劃當中,我就說到我想要學Go 語言。同時我自己也買了一本有關於Go 的書:深入淺出Go , 希望藉此督促自己有個比較完整性的學習……

以下是我讀這本書以及學習的心得與筆記,可能有點無聊,高手請跳過、不過如果有看到錯的也請不吝指正!

首先,你要先去安裝好Go…….

程式組成

Go 語言的組成通常有三個部分:
1. 套件子句(package main)
2. import 相關陳述句 (import "fmt")
3. 主要的程式碼 (func main(){......})

Hello World 程式!

讓我們先建立一個名為hello.go 的檔案… 然後在裡面寫這些東西…

package main
import "fmt"

func main(){
	fmt.Println("Hello World")
	// 這裡請務必使用 " 否則很容易跳出 invalid character literal (more than one character) 的問題
}

接下來讓我們對這個檔案做go run hello.go 就可以看到 Hello World 的字眼啦!

Go 的資料型態類別

  1. 字串:用雙引號所框起來的任意數量字元
  2. 符文(runes) : 用單引號所匡著的單一字元, ex. ‘A’ , ‘B’
  3. boolean (bool)
  4. numbers
    1. float32 , float64
    2. int8 ,int16 ,int32 ,int64
    3. uint
    4. uint8 , uint16 ,uint32 ,uint64
  5. byte (檔案專用)

tips: 可以透過 reflect 這個套件裡面的TypeOf 方法得知資料的型別

package main

import (
  "fmt"
  "reflect"
)

func main() {
	fmt.Println(reflect.TypeOf("Hello World")) //string
	fmt.Println(reflect.TypeOf(true)) // bool
}

宣告變數

  • var q int
  • var q int = 4
  • p,q = 4,5 (居然有像python 一樣的多重賦值!)
  • p :=4 (快速寫法, 連型別都不用!)

命名規則

  1. 開頭必須是字母
  2. 如果開頭字母是大寫,表示他是可以被匯出的

陣列

在Go 裡面要宣告陣列,請用以下的code 得形式

// 第一種
var todos [2]string
todos[0]= "learning go !"
todos[1]= "use go to write an app"
// 第二種
var grades [3]int = [3]int{90,98,93}

// 第三種
heights := [3]int{90,98,93}

// go 裡面的foreach , 陣列/map資料型態都適用!
for index,note := range notes{
    fmt.Println(index,note)
}

切片

這是我在深入淺出Go 這本書裡面的某一個章節,他裡面寫道Go 宣告切片就像是宣告陣列變數, 只是不需要指定大小!for example:

var mySlice []int
mySlice = make([]int,7) // 設定七個數字的切片
fmt.Printf("%v",mySlice) //[0 0 0 0 0 0 0]
// 增加
newSlice := append(mySlice, 5,9) // 回傳新的, 增加完的切片
fmt.Printf("%v",newSlice) //[0 0 0 0 0 0 0 5 9]

錯誤處理

說真的,最近在學著Go 都覺得他的語言調性和其他語言差很多,像是if/for 的區域範圍變數無法用在if/for 後面(但之前的宣告可以使用)、陣列的宣告是很獨樹一幟的他也不像傳統程式語言那種try…catch 的敘述,而是你要分成你自己去處理或者直接error 中斷給你看這樣

像是這樣, 你必須用參數去接下可能會error 的地方,然後用if 去判斷, 控制壞掉之後的流程這樣。詳細你可以參考此連結:https://michaelchen.tech/golang-programming/error-handling/

或者你也可以使用 panic 這個關鍵字 或 用 recover 這個關鍵字讓他從panic 的狀態中恢復, 另外也筆記一下 defer 可以添加到任何地方,用來暫緩該調用直到目前程式結束

詳細可以參考此網址:https://openhome.cc/Gossip/Go/DeferPanicRecover.html

 file, err := os.Open("file.txt")         
 if err != nil {                         
     ....         
 }  
defer file.Close()                                    

Maps

有點像是python 裡的 dictionary , 或者 php 的 association array……

var myMap map[string]float64 // 宣告出以字串型態為index, float64型態的值
myMap = make(map[string]float64)

// 又或者可以做更簡單的宣告
myMap := make(map[string]float64)


// 如何把東西放進去...
myMap["Jimmy"] = 12.4
myMap["Bob"] = 15.3

// 如果已經知道要建立怎麼樣的map
myMap := map[string]float64{"Jimmy":12.4 , "Bob":15.3}

// 另外如果取得一個沒有被指派的index 會根據型態而回傳不同的值
// 數字:0
// 字串:""
// * 如果沒有make 它則會是一個nil 的 map, 而nil 的 map 無法被指派值
// 可以視情況給予第二個參數,好讓map 可以判斷是否有這個index
var value string
var exist bool
value , exist = myMap["Andy"] // return 0, false

// 移除
myMap["Jim"] = 999
delete(myMap,"Jim")

結構 struct

var myStruct struct {
    name string
    grade int
}
myStruct.name = "Jimmy"
myStruct.grade = 10

// 自訂型別, 前面使用type 這個關鍵字
type myStruct struct {
    name string
    grade int
}

// 自訂型別也能加入method
func (m myStruct) hello() string {
    return "hello"
}
// 自訂型別也可以加入getter /setter 封裝裡面的資料結構
func (m *myStruct) SetName(name string){
   m.name = name
}

func (m *myStruct) Name() string {
   return m.name
}


// 透過指標存取結構
func applyDiscount(s *subscriber){
    s.rate = 4.99
}

func main() {
    var s subscriber
    applyDiscount(&s)
}

結構裡面可以有另外一個結構,而Go另外還支援了匿名結構欄位,不用特別設定名稱也可以直接帶進去結構裡

type Employee struct {
    Name string
    Salary float64
    Address
}

type Address struct {
     ......
}

介面 interface

在 Go 裡面也有介面的概念,定義某些特定得值與某些特定的行為。

type myInterface interface {
   methodOne()
   methodTwo(float64)
   method() string
}

測試

在Go 裡面,我們可以使用testing 這個套件,首先是要讓我們在同樣的套件底下建立一個_test 結尾的Go 檔案,話不多說,show you some code !

func TestFunction(t *testing.T){
    ......(略)
    if ...(略)
    t.Errorf("......")
}

function programming

在Go 裡面, function 本身也能夠被視為變數, 型別處理,像是

// 此範例來自深入淺出Go p.439
func callFunction(passedFunction func()){
  passedFunction()
}

func callTwice(passedFunction func()){
  passedFunction()
  passedFunction()
}

func callWithArguments(passedFunction func(string, bool)){
  passedFunction("this sentence is",false)
}

func printReturnValue(passedFunction func() string){
  fmt.Println(passedfunction())
}

func functionA(){
  fmt.Println("function called")
}

func functionB() string{
    fmt.Println("function called")
    return "Returning from function"
}

func functionC(a string , b bool){
    fmt.Println("function called")
    fmt.Println(a,b)
}

func sayHi(){
  fmt.Println("Hi")
}

func main(){
    var myFunction = func()
    myFunction = sayHi
    myFunction()

    callFunction(functionA)
    callTwice(functionA)
    callWithArguments(functionC)
    printReturnValue(functionB)
}

相關指令

  • go build :編譯成二進位
  • go run :編譯及執行
  • go fmt :格式化原始碼
  • go version :go 的版本
小君曰:來學Go 啦, go !

談Python: Lambda, Map , Filter 及 Reduce

從大二開始研究怎麼當一名駭客之後,開始接觸Python, 出社會後也常常用Python 開發我個人的工具包,可說是這幾年來個人 Python 的功力大漲!

最近在邊寫邊查資料的過程當中,認識到Python的 Lambda,Map和Reduce

於是,就讓我寫一篇文章記錄這一切吧 ^^

Lambda

此 Lambda 不是 AWS的Lambda ,我也不知道為什麼這裡要取和AWS的Lambda一樣的名稱哈哈,總之他是一種Python裡面的表示式,可以更加簡便、更加Function programming 的呈現程式碼,不多贅述,我最喜歡 Show me the code了!

// 通常我們定義Function是長這個樣子(順便我也想練習強型別,python也支援喔)
def demoX(x:int) -> int:
    return x + 10;
    
print(demoX(20)) //output-> 30

其實蠻落落長的,所以Python說,要有Lambda , 就有Lambda

demoX = lambda x: x+10

print(demoX(20))

好了,我附上參考網址結束這一切:https://openhome.cc/Gossip/Python/LambdaExpression.html

Map和Reduce

Map , FilterReduce 我想要放在一起說,如果常寫Function Programming的對這兩個單字一定不陌生,Laravel的Collection也有這些方法。沒錯,他們的用法其實和他們的單字意思很像呢!

// map(function_to_apply, list_of_inputs)
a = list(map(lambda a: a+2,[1,2,3]))
print(a) # [3,4,5]

map 其實就會迭代列表中的每一個項目,最後回傳出來的是一個map的物件,記得之後要用list才能把它印出來喔

a = list(filter(lambda a: a>2,[1,2,3,4,5,6]))
print(a) # [3, 4, 5, 6]

filter 其實和map很像,只是它是過濾項目,一樣他是個filter的物件,一樣需要list,所以你的function裡面請回傳出boolean,這我就不多說明啦~

from functools import reduce

a = reduce(lambda a,b: a+b,[1,2,3])
print(a) # 6

至於 reduce需要先 import 喔,只要有一個有兩個參數的function , 他就會迭代這清單的項目做出最後的結果

參考網址: https://book.pythontips.com/en/latest/map_filter.html

小君曰:Python也能Function Programming !

原來可以這麼寫(11): 每個人的心中都要有DevOps

最近工作比較沒有什麼新鮮事,所以開始在將手上專案有比較完整的文件化之後,開始逐步導入TDD ,寫一些Unit Test 以確保程式碼的品質。

但老實說:我還是不是太懂Laravel Feature 和 Unit 這兩個資料夾的區別?我知道Unit 是要做單元測試,是測試那個類別的行為,但我目前大多都在寫Feature Test… 如果有大大知道Unit 該在何時寫、什麼情境下要寫,歡迎不吝賜教!

研究Socket/API Gateway

因為要導入官網購物車購買,討論一連串的流程與討論,最後希望我Laravel 要開一個socket server,但研究的結果其實發現 因為我們專案的版本比較低,所以沒辦法使用Laravel-Websocket 套件,也就是在Laravel 裡面自己開socket server 那種,變成我們要另外用Node 的套件去建立… 然後… 又衍伸第二種方案,在AWS Api-gateway 架起websocket api ,然後他可以指定動作去call API…

這個方案很好玩… 不過在研究初期卡關在怎麼用Laravel 取出 connectid , 因為AWS example 是用Node 的Lambda 去串的,很簡單就取出connectid… 。我Laravel 怎麼print request 的body 還是 header ,顯然就是找不太到那個connectid …

後來實在有點卡太久了(大概一天),被同事叫我去寫AWS Support XD

人生第一次寫AWS Support 耶,然後AWS 團隊真的很用心很貼心,告訴我很詳細的步驟及方法、解決方案,原來,如果要讓Laravel 的 後端讀得到 connectid ,是要透過CLI 去設定啊… (目前API gateway 控制台還沒有支援這個),不然就是需要把integration type 改成 HTTP ,去寫 request template 這樣。

總之,這次經驗讓我更認識 API gateway 和 socket 啦!預計之後想要開一系列深入研究Laravel 的系列,想要寫broadcast 的部分,希望今年可以完成這個寫作計畫 ^^

網站可靠性工程工作導讀

我們每個禮拜都會有小組的例會,小組的成員都需要報告一下我們各自的狀況與專案進度,然而在今年我們組長要求大家輪流每個禮拜都要分享一下技術議題,不管他是前端還是後端皆可。

我個人覺得這是很好的一個文化,但最近也常常聽到同事在講對他們有一點小小的loading…。有點不知道耶,明明是很棒的事情為什麼反而有點造成不太好的效果?我們的專案真的有點多而且每個人負責很多啊,不太平均……

話說回來,大概這樣輪流每個人平均一個月講一次,所以這個月就帶來我讀網站可靠性工作工程的心得與收穫~

之前,也曾經參加這本書的導讀會,大家可以參考:網站可靠性工程工作手冊導讀會一遊

然後值得一講的是後面補充文章有一則:技術債觀念及實務
因為在查一些SRE 和 DevOps 相關文章時就不小心逛到,在這篇文章讀到:

另一方面,著有《Domain-Driven Design》一書的 Eric Evans ,對於處理舊有程式碼(legacy code),也曾提出過一個叫做「戰略設計(Strategic Design)」的觀念。Eric Evans 認為,在一個系統中,根本不可能所有的程式碼都維持夠高的品質,而且,有些情況下,有些程式碼也沒有必要一定非得是高品質。這是個相當務實的觀念,如果我們很現實的來計較在開發過程中每一份生產力的 C/P 值,那麼,維持一些程式碼的完美,需要付出的代價太高,但這些程式碼即使完美對整個系統的效用卻不大時,是否應該讓它們也被一視同仁地被維持高品質?

By 技術債觀念及實務

這帶給我寫程式上面莫大省思與體悟,原來,寫程式也像DevOps 一樣「不應該」去要求百分百可靠度(完美)

以前我都覺得自己要每一行寫得很棒很好,這是一種工程師對自我的要求… 反而導致每一次我寫功能之後就會每次檢討自己、總是花很多時間在想下一次怎麼可以寫更好,但有時候下一次也不一定像這一次可以要求寫得很好…

而DevOps 和剛剛讀到的技術債觀念及實務帶給我在寫程式上面的一個心態轉變,應該要對於不同的程式碼有不同的權重分配,有些可以選擇粗略地寫,只要功能有夠、排版讓人覺得算ok,這樣就好;而有些需要好好每次的重構每次的改善,讓程式碼可以越來越易讀!

總之,這是我當天報告用的ppt ,希望能幫助到大家~

小君曰:每個工程師的心中都應該要有DevOps !

Redis筆記

最近在玩Redis, 記錄起來才不會忘!

利用Docker 迅速起一個redis container

你可以去官網按照他的說明下載redis, 但我這裡選擇用docker 來起一個簡單的redis 服務首先我的dokcer-compose.yml 是這樣寫:

# ...(略)
### Redis Container #######################################

    redis:
        image: redis:alpine
        ports:
            - "6379:6379"
# ...(略)

然後使用 docker-compose up -d 就可以起一個簡單的redis server, 你可以使用Another Redis Desktop Manager 去連線它看看狀況

指令與相關說明

簡單來說,redis 就是一個key-value 的 in-memory 資料庫。
首先,我們需要進去一下redis cli…只要`docker exec -it {你docker-composer.yml 所在的資料夾名稱}_redis_1 sh`就可以進入redis container 裡面,之後在container 裡面執行`redis-cli`即可

GET/SET

最簡單你一定要學到的redis 指令就是:SETGET!

127.0.0.1:6379> SET name Jimmy
OK
127.0.0.1:6379> GET name
"Jimmy"

MGET/MSET

每次只能設定一個那怎麼行?你可以一次設定多個

127.0.0.1:6379> MSET first "Hello" second "World"
OK
127.0.0.1:6379> MGET second first
1) "World"
2) "Hello"

EXPIRE

既然是一個key-value 的 in-memory 資料庫,他就不像一般Relational database 一樣,只能永久保存資料,你可以設定這個key 的保留期間

127.0.0.1:6379> SET session "HelloWorld"
OK
127.0.0.1:6379> EXPIRE session 10
(integer) 1
127.0.0.1:6379> GET session
"HelloWorld"
127.0.0.1:6379> GET session
(nil)

EXIST / DEL

你也可以判斷這個key 存在不存在, 以及刪除那個key

127.0.0.1:6379> EXISTS name
(integer) 1
127.0.0.1:6379> DEL name
(integer) 1
127.0.0.1:6379> EXISTS name
(integer) 0

INCR / DECR

redis 還自帶遞增和遞減的指令。

127.0.0.1:6379> SET counter 100
OK
127.0.0.1:6379> INCR counter
(integer) 101
127.0.0.1:6379> GET counter
"101"
127.0.0.1:6379> DECR counter
(integer) 100
127.0.0.1:6379> GET counter
"100"

INCRBY / DECRBY

每次只能遞增和遞減一個那怎麼可行?

127.0.0.1:6379> SET counter 100
OK
127.0.0.1:6379> INCRBY counter 100
(integer) 200
127.0.0.1:6379> GET counter
"200"
127.0.0.1:6379> DECRBY counter 100
(integer) 100
127.0.0.1:6379> GET counter
"100"

keys *

這個指令可以讓你看到目前設定在redis 裡面所有的key

127.0.0.1:6379> keys *
1) "first"
2) "counter"
3) "second"

Redis 的資料型態

redis 支援以下型態,上面已經充分示範出string 這個簡單的型態。接下來展示其他沒有在上面示範過的型態!

  1. string(字串)
  2. hash(雜湊)
  3. list(串列)
  4. set(群集)
  5. sorted set(有序群集)

Hash

就像HMSET 的說明一樣:HMSET key field value [field value ...]以底下的說明來說,就像產生了這樣的資料型態:{id: 45, name: Jimmy}

127.0.0.1:6379> HMSET user id 45 name "Jimmy"
OK
127.0.0.1:6379> HGET user id
"45"
127.0.0.1:6379> HGET user name
"Jimmy"
127.0.0.1:6379> HGETALL user
1) "id"
2) "45"
3) "name"
4) "Jimmy"

List

和javascript 的想法很像,有push 和pop , L代表左邊, R代表右邊啊那個如果要列出清單可以用LRANGE指令

127.0.0.1:6379> LPUSH mylist 10 "Hello"
(integer) 2
127.0.0.1:6379> LRANGE mylist 0 1
1) "Hello"
2) "10"
127.0.0.1:6379> RPUSH mylist "World" 20
(integer) 4
127.0.0.1:6379> LRANGE mylist 0 3
1) "Hello"
2) "10"
3) "World"
4) "20"
127.0.0.1:6379> LPOP mylist
"Hello"
127.0.0.1:6379> LRANGE mylist 0 3
1) "10"
2) "World"
3) "20"
127.0.0.1:6379> RPOP mylist
"20"
127.0.0.1:6379> LRANGE mylist 0 3
1) "10"
2) "World"

Set & Sorted Set

1. set 沒有排序得權重,而且不可以增加重複的值

127.0.0.1:6379> SADD myset 1 2 3 4 5 
(integer) 5
127.0.0.1:6379> SMEMBERS myset
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
127.0.0.1:6379> SISMEMBER myset 5
(integer) 1
127.0.0.1:6379> SISMEMBER myset 50
(integer) 0
127.0.0.1:6379> SADD myset 1 
(integer) 0
127.0.0.1:6379> SADD myset 10
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "10"

2. sorted set 有排序得權重

127.0.0.1:6379> ZADD mysortedset 1 Jimmy
(integer) 1
127.0.0.1:6379> ZADD mysortedset 0 Jim
(integer) 1
127.0.0.1:6379> ZRANGE mysortedset 0 1
1) "Jim"
2) "Jimmy"
127.0.0.1:6379> ZRANK mysortedset Jimmy
(integer) 1

同場加映:pub/sub

laravel broadcast 如果你的driver 設定的是 redis , 你會看到文件會寫道使用redis 的pub/sub 來實現,但這個其實很難透過GUT 去看,首先,你需要開兩個redis-cli

A Redis cli

127.0.0.1:6379> SUBSCRIBE mychannel
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "mychannel"
3) (integer) 1

B Redis Cli

在這裡你可以用PUBLISH 指令

127.0.0.1:6379> PUBLISH mychannel "HelloWorld"
(integer) 1

這時你回到A 來看,奇蹟發生了!底下多了message, mychannel, HelloWorld !

127.0.0.1:6379> SUBSCRIBE mychannel
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "mychannel"
3) (integer) 1
1) "message"
2) "mychannel"
3) "HelloWorld"

有趣吧?

小君曰:redis 一日遊

Python Paramiko 筆記

在以前公司工作的時候,有點忘了是遇到什麼情境,總之我就看到python 有這樣的一個套件庫:Paramiko

話不多說,我們就給大家來看文件吧:http://www.paramiko.org/

然後就結束這一回合(阿不是!

他是一個和SSH 有關的套件庫,是可以使用python 直接在遠端給他執行程式起來… 啊寫文章的同時我就想到了!之前我們好像是要做那個資料庫備份什麼的, 然後有發現說有時server 會不夠空間backup , 所以後來我就用這個套件透過本機去連結遠端執行 df -h 的指令,以方便告訴我到底有沒有足夠的空間這樣…. 不然每次連線打指令實在很麻煩…

然後 , 我最喜歡的是: show you the code !

import paramiko

paramiko.util.log_to_file('paramilo.log')
key = paramiko.RSAKey.from_private_key_file("pem path...")

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname='......',username='user',pkey=key)

stdin, stdout, stderr = ssh.exec_command('df -h')

result = open('log.txt','wb')
result.write(stdout.read())
result.close()

ssh.close()
小君曰:我到底寫了什麼...?

原來可以這麼寫(10):同步與非同步

來到第十篇原來這麼寫啦,看來這個系列真的常駐我這個部落格真的很久呢!

要冷靜啊!

然後這次真的是史上我接過任務最難的一波,有一天還差點情緒崩潰在工作現場爆哭… 真的覺得自己很丟臉很誇張……

不過事後想想,那是因為我自己對自己的要求也實在太高了,也一直過度自責、苛責自己的規劃上有很大的問題….. 真的很辛苦各位我的同事。總之,這次的經驗告訴我:要努力試著讓情緒歸情緒、工作歸工作哭完,問題還是在那裡,我們一定要努力地解決問題。工程師的存在正是為此啊。

我自己最喜歡得形容詞就是忠心!忠心於工作、忠心於自己的技術能力、也忠心於自己的信仰。我想藉著上面的事件也再度應證與難怪自己為什麼會有那種過度反應了吧

結論是:calm down ! 挽起袖子來解決問題

小說的匯入任務

這個任務為什麼對我來說蠻困難的,我覺得技術問題是一回事,其次我自己也檢討是不是太晚將問題丟出來了?我的個性常常是獨立做事,說真的還蠻就事論事得、原生工程師性格。而我通常認為我自己不是那個第一個遇到同樣問題的人,所以總是自己想辦法處理、想辦法解決…. 像是Laravel 的開發與專案、API維護上,我其實就非常游刃有餘、自由自在。(當然溝通上面的gap 與問題是需要慢慢的與團隊磨合與自己努力調整的),DevOps 的精神就是逐步改善麻!

但我卻忽略有時候其實是有時間上的問題,在過年前要匯入這麼多的小說,一共12000多章節,剛好我台東人在過年期間卻要請長長的年假,我才驚訝發現:我hold 不住了。看來,下次也要注意時間,好讓PM與SA 能夠發覺與注意到我的狀況,能hold 住專案

自動匯入方案的產生

不過還好啦,謝謝同事、夥伴們的體諒與幫助。在禮拜五怒給他加班到十一點的時候把這個自動化方案寫出來(但當然啦,這個我覺得也需要事先給PM測試,所以同時我也預備自己的手動匯入程式方案…但等等分享我遇到的問題與啟發)

手動匯入的些許失敗經驗與啟發

但說真的,小說匯入其實這次第二波了。上一次真的我自己沒有準備好… 可是這次我吸取第一次的失敗經驗,重新調整流程、設計。於是這次在匯入資料上面就非常的順利,還記得第一次營運單位有兩天的時間都無法到後台修改資料…但這次一個下午就搞定了。

事實上,我只是把匯入分成兩個階段進行,第一階段是把資料放進去資料庫(就是這個步驟才會不建議營運單位修改資料,以免我們的id 亂跳…),而第二階段是去別人小說的網站把音擋下載下來上傳到我們server上的指定位置,驅動我們同事寫好的自建音檔模組。於是完成了這整個匯入流程。

而第二階段的處理原本我用python 寫 request.get(url) 這樣去下載,寫檔案後上傳的“同步方案”,一支處理就要10秒多…. 然後12000多就… 超級慢的啦!

中間還遇到工作電腦爆掉的問題… 真的是很衰… 第一次遇到…. 但所幸謝謝我們家的MIS幫我工作電腦換好一個power 這樣,於是只有一點點時間是不知道怎麼辦而已。

隔天早上,發現我那支居然跑到一半不跑了… 還好我之前在設計上有納入可中斷性,就算中斷了重新執行也可以從還沒處理的部分繼續接著處理… 但就像剛剛說的一個一個上傳真的很慢啊…. 於是開始研究 python 的 非同步方案版本…

import aiohttp
import aiofiles
import asyncio
import time
import os


#定義協程(coroutine)
async def main(links):

    async with aiohttp.ClientSession() as session:
        tasks = [
            asyncio.create_task(fetch(data['url'], data['episodeId'], session)) for data in links
        ]  # 建立任務清單
        await asyncio.gather(*tasks)  # 打包任務清單及執行


#定義協程(coroutine)
async def fetch(link, id, session):
    async with session.get(link) as response:  #非同步發送請求
        if response.status == 200:
            f = await aiofiles.open('/tmp/file.mp3', mode='wb')
            await f.write(await resp.read())
            await f.close()
        try:
            # s3 upload
            print("{} upload success")
        except Exception as e:
            print("{} upload error")

        os.remove('tmp/file.mp3')



start_time = time.time()  #開始執行時間
loop = asyncio.get_event_loop()  #建立事件迴圈(Event Loop)

#  episodes = ...

loop.run_until_complete(main(episodes))  #執行協程(coroutine)
print("花費:" + str(time.time() - start_time) + "秒")

於是,我在家中研究寫了這樣一個模擬出來的程式碼… 然後執行下卻發現… 靠 檔案因為非同步的關係所以就產生Permission error 的錯誤,在修正後變成以下這個版本…(但不是全部程式碼,僅是示意)

import aiohttp
import aiofiles
import asyncio
import time
import os


#定義協程(coroutine)
async def main(links):

    async with aiohttp.ClientSession() as session:
        tasks = [
            asyncio.create_task(fetch(data['url'], data['episodeId'], session)) for data in links
        ]  # 建立任務清單
        await asyncio.gather(*tasks)  # 打包任務清單及執行


#定義協程(coroutine)
async def fetch(link, id, session):
    await asyncio.sleep(10+int(random.random()*10))
    try:
        async with session.get(link) as response:
            if response.status == 200:
                FileHelper.upload_s3_audio_files(id, await response.read())
                print("{} upload success".format(id))
            else:
                print('status not 200')
                print("{} upload error".format(id))
    except Exception as e:
        print(e)
        print("{} upload error".format(id))



start_time = time.time()  #開始執行時間
loop = asyncio.get_event_loop()  #建立事件迴圈(Event Loop)

#  episodes = ...
loop.run_until_complete(main(episodes))  #執行協程(coroutine)

print("花費:" + str(time.time() - start_time) + "秒")

但還是無法解決所有的問題,有時還是會噴一堆錯誤,或者是Response payload is not completed , 因此… 還在想一想怎麼樣可以更好…

但比較好的是:因為這是第一次匯入,當然資料爆多。之後的更新就不會有那麼多了… 而確實使用非同步的寫法與方案在第二階段處理上快了很多…

此事件帶來我的啟發是:這麼大量的上傳下載處理還是要搞非同步啊

小君曰:看來,我需要好好理解非同步程式設計啊....

原來可以這麼寫(9):結果我變成python 工程師?

祝大家新年快樂。原來可以這麼寫這個系列終於來到第九篇!

說聲好消息,最近工作獲得肯定(撒花~)。只是不知道年終到底有多少…搞不好…其實很少…. 這個就題外話啦,在這個公開網路場合還是不宜多說XD

從資料庫匯出資料

最近接到一個需求,是要從資料庫匯出資料。其實這個東西並不是很難,寫寫SQL 語法就能搞定…但因為安全的因素我們的資料庫通常要透過SSH 跳板才能進去。可是他們匯出資料的需求是要by 一個顧客(客戶),你媽咧我難道一個一個SQL 撈出來然後再丟進Excel 嗎?

不!這絕對不是工程師的思維… 後來想想我在我第一份工作的時候開啟了一個side project : office: https://github.com/r567tw/office

那時候為什麼我要開啟這個專案呢?原因是,我當時負責重構一個網站系統,是用Laravel 重構原本native php 改寫的報名網站…. (這大概可以說是我工程師生涯其中一個直得常常拿出來說嘴的一個成就…但當然啦我之後在想覺得那時候我初出茅廬才維護一年多就改寫實在有點冒險…只怪我當時太年輕太衝動太不懂事了…. 裡面還是有一些遺珠之憾等級的小後悔~)

啊話說開了,總之那時候有要驗證台灣的身分證字號,還有生成台灣的身分證字號…這當然網路上可以有工具可以用啦,但你不覺得開瀏覽器->搜尋-> 點進網址 -> 可能還有點一些按鈕bla 的才能搞定自己的需求很麻煩嗎?

於是你看到office 裡面就有一個資料夾應運而生:id_card_number

隨著時間推移,裡面的工具也越來越多XD

這次我就用到使用ssh 連接到database 來幫我完成需求的工具:connectDBthoughSSH

import pymysql
import sshtunnel 
import dotenv
import os

dotenv.load_dotenv()

server = sshtunnel.SSHTunnelForwarder(
    ssh_address_or_host=(os.getenv('SSH_HOST'), 22), # 指定ssh登入的跳轉機的address
    ssh_username=os.getenv('SSH_USER'), # 跳轉機的使用者
    ssh_pkey=os.getenv('SSH_PEM_PATH'), # 跳轉機的密碼
    remote_bind_address=(os.getenv('DB_HOST'), 3306)
)

server.start()

db = pymysql.connect(
    host='127.0.0.1',
    port=server.local_bind_port,
    user=os.getenv('DB_USER'),
    passwd=os.getenv('DB_PASSWORD'),
    db=os.getenv('DB_DATABASE')
)

cur = db.cursor(pymysql.cursors.DictCursor)

cur.execute('select id,name from clients')

clients = cur.fetchall()

db.close()
server.close()

接下來,拿到sql 的資料之後就是python 和 excel 的問題了,撒花!

匯入第二波小說資料

因為目前時程與安排的緣故,目前匯入資料暫時由工程師處理,前面的應用到step function 就是其中一環的應用,但說真的,前面這一環的資料匯入,我開發幾個 laravel 的 api , 實在規劃的! 很!爛! 我愧對我作為工程師的身份啊…

經過漫長快一個禮拜的規劃… 我想到他馬的我為什麼要繼續用PHP完成我這個需求?用Python 呢?

再經過兩~三天的研究與開發, python 的版本終於應運而生!不過裡面資料先暫時用在sqlite 上面, 之後我想把它放在mysql 資料庫上面… 然後寫好專案裡面的readme和說明,不用我只要大家用這個程式就可以做使用,多自動化多方便啊… 這是我這個的最終目標啊!

嘗試做一些改變

目前,我是一名後端工程師,我的專案其實就只有一個。這一個專案負責官網、後台、ios/android app 的API, 同時也需要與交易的 Lambda 溝通,去年我甚至也主導單集銷售模式的Lambda 開發,而我在接手此專案的過程中曾發下宏願:

  1. 完整文件化
  2. 導入TDD,甚或是DDD , 強化程式碼品質、程式碼架構彈性

目前我自認自己將文件化的目標至少有70-80%,我很努力的研究swagger 怎麼寫,創造一個較為優良的swagger文件的環境,整理目前就我角度上專案的開發現況,導入代碼表的文件在每個需要代碼的swagger 文件。也在多次與app team/PM/官網/後台 有多次的溝通摩擦、gap , 雖然是有點加重了我個人的loading , 但至少在開發前做一次好好地確認、流程、導入mock api 的機制先在開發前作討論。

我不知道,我這樣的做法,是否對於各方角度team 有好的成效、好的溝通效率?甚願可以。希望可以。畢竟API 的 DX 很差,那我這個後端工程師真的不用在繼續幹下去了!

接下來,希望完成資料庫的文件化,也希望程式的TDD, 測試能一一地補上。這是我接下來要努力的目標之一。

每次改變一點點,這也是DevOps 文化的其中一環麻。

小君曰:原來這次我是python工程師?

aws step function 筆記

最近工作用到一些工具,使用到AWS step function , 因此在這裡也筆記一下…

也在公司後端組例會分享了一下(以下就是我分享的PPT ):

其實我覺得我用的情境很簡單,只是用Map 的方式啟動lambda . 這個 lambda 就是我用來處理下載與上傳到s3指定位置… 說真的應用的情境真的很不多… 還有更多著墨的空間。

另外,自己同時也針對此寫了兩個版本,用SAM 和 用 CDK 的版本…

一、CDK 的版本

import * as cdk from '@aws-cdk/core';
import * as lambda from "@aws-cdk/aws-lambda"
import * as stepfunctions from "@aws-cdk/aws-stepfunctions"
import * as tasks from "@aws-cdk/aws-stepfunctions-tasks"
import * as logs from "@aws-cdk/aws-logs"
import * as s3 from "@aws-cdk/aws-s3"
import * as ec2 from "@aws-cdk/aws-ec2"
import * as dotenv from 'dotenv';

export class CdkLambdaStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 將裡面比較敏感的資訊用env 包起來, 注意後面的path 要正確
    dotenv.config({path:__dirname+'/../.env'})
    
    // 我要上傳音檔的S3 目標 arn:aws:s3:::test 為虛構(我忘了把這個也包env了哈哈)
    const bucket = s3.Bucket.fromBucketArn(this,"test","arn:aws:s3:::test")

    // 負責前面呼叫step function 的 lambda
    const downloadAudioLambda = new lambda.Function(this, "downloadAudioLambda", {
      runtime: lambda.Runtime.NODEJS_12_X,
      timeout: cdk.Duration.seconds(25),
      handler: "index.handler",
      code: lambda.Code.fromAsset("lambda/downloadAudioLambda")
    });

    bucket.grantPut(downloadAudioLambda)

    const downloadAudioJob = new tasks.LambdaInvoke(this,'Calllambda',{
      lambdaFunction: downloadAudioLambda,
      outputPath: "$.Payload"
    })

    const map = new stepfunctions.Map(this, 'ExampleMapState');
    map.iterator(downloadAudioJob);

    const logGroup = new logs.LogGroup(this, 'StepFunctionLogs')

    const stateMachine = new stepfunctions.StateMachine(this, 'StateMachine', {
        definition: map,
        logs: {
          destination: logGroup,
          level: stepfunctions.LogLevel.ERROR
        }
    });

    const testVpc = ec2.Vpc.fromLookup(this,"vpc-dev",{
      vpcId: process.env.VPCID
    });

    const processorLambda = new lambda.Function(this, "processorLambda", {
      runtime: lambda.Runtime.NODEJS_12_X,
      handler: "index.handler",
      timeout: cdk.Duration.seconds(25),
      code: lambda.Code.fromAsset("lambda/processor"),
      vpc: testVpc,
      environment: {
        ENDPOINT: process.env.ENDPOINT ?? 'localhost',
        DATABASE: process.env.DATABASE ?? 'db',
        DBUSERNAME: process.env.DBUSERNAME ?? 'root',
        PASSWORD: process.env.PASSWORD ?? 'password',
        NODE_ENV: process.env.NODE_ENV ?? 'test',
        statemachine_arn: stateMachine.stateMachineArn
      }
    });

    stateMachine.grantStartExecution(transferLambda)
  }
}

總之,上面我就是用CDK先創建我的lambda , 然後把那個要放到state machine 的建立”task”, 給予我另外一個lambda 有 startExecution 的權限…. 簡單完成!

二、SAM 的版本

總之,有些原因,我另外又學習怎麼用SAM製作 state machine XDD

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description:
  download audio file from huaxi to trigger audio transcoder
Resources:
  ProcessAudioFileStateMachine:
    Type: AWS::Serverless::StateMachine # More info about State Machine Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html
    Properties:
      DefinitionUri: statemachine/audioFile_processer.json
      DefinitionSubstitutions:
        DownloadAudioFunctionArn: !GetAtt DownloadAudioFunction.Arn
      Policies: # Find out more about SAM policy templates: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-policy-templates.html
        - LambdaInvokePolicy:
            FunctionName: !Ref DownloadAudioFunction

  DownloadAudioFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html
    Properties:
      FunctionName: downloadaudio
      CodeUri: functions/downloadaudio/
      Handler: index.handler
      Runtime: nodejs12.x
      Timeout: 20
      Policies:
        - S3ReadPolicy:
            BucketName: 'test'
        - S3WritePolicy:
            BucketName: 'test'

  ProcessorFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: processor
      Timeout: 20
      CodeUri: functions/processor/
      Handler: index.handler
      Runtime: nodejs12.x
      Environment:
        Variables:
          ENDPOINT: db_url
          DATABASE: dbname
          DBUSERNAME: dbusername
          PASSWORD:dbpassword
          NODE_ENV: test
          statemachine_arn: !Ref ProcessAudioFileStateMachine
      Policies:
        - StepFunctionsExecutionPolicy:
            StateMachineName: !GetAtt ProcessAudioFileStateMachine.Name

其實說真的CDK 和 SAM 沒有多大差別,只是CDK你可以用比較程式化的去做那個state machine language (就是sam 裡面要包的那個json 啦!),像我,實在懶得去構想那個json 怎麼寫(啊我就不是JSON工程師啊~),所以先用CDK 產生state machine , 然後上AWS控制台上面把那一串json 抓下來,放到我的sam 這裏… 整理一下,CDK detroy 一下,sam 的template.yaml 調整一下,一個下午搞定啦!(不過我好像忘了在sam 裡面宣吿log 去接state machine 啦 XDDD 之後再研究吧!)

小君曰:還有很多成長的空間

今年的回顧與未來規劃-2020

終於又準備來到新的一年,這一年過得真的很快,又到了回顧&立未來Flag的時候。讓我回顧去年寫的版本:今年的回顧與未來規劃

有哪些完成了?有哪些沒完成?又有哪些放棄了呢?

關於工作

在今年大約三月初的時候,找到了一份在聯合報系行動發展部擔任後端工程師的工作,很幸運的得到一個自己蠻滿意的offer ,真心感謝長官們的賞識。而在這份工作當中,我終於體會與經歷做產品的酸甜苦辣,商業模式的各種錯綜複雜與討論,學習上真的蠻多的。感謝我的同事、夥伴、組長總是不厭其煩的幫忙與指導,真心希望未來2021年,還希望你們繼續指教我啊! (媽的我好官腔喔好想揍飛自己…)

而在約莫11月底的時候,長官更是幫我申請到一筆獎金給我,人生第一次收到這間公司的獎金。說真的,可以在工作上得到肯定,就是棒啦!♥將榮耀歸給上帝 ♥

關於去年的目標

其實啊,今年有一項相當失敗,就是 ios 上面的學習,遲遲都沒有進度。。。。 看來只能展望明年了 嗚嗚嗚 😭

另外,明明今年計劃想學好golang 的計畫也只是簡單上一門簡單的線上課程:GetGoing: Introduction to Golang,而且,目前還正在上課中,還沒融會貫通啊!

最後,上架了之前在鐵人賽的文章到部落格上,也果斷放棄搬到siteground的想法,因為… $$

今年完成了什麼

  • 關於部落格
    • 後來研究了一下費用與成本的部分,甚至一度想要改放到AWS上,但後來想一想,覺得放在siteground 真的太貴了,想到之前網路上問過的問題:想請問從目前的虛擬主機搬到AWS的成本,裡面有個回答寫道:「若是計較成本優先的人, 不應該選擇公有雲….」,就決定果斷放棄搬主機的計畫啦,現在固定一年兩千多塊多省啊XDDD
    • 有沒有發現,我的部落格文章變多了,很順利的,我用python撰寫一些工具把鐵人賽的文章搬過來了啦ㄎㄎ
  • 技術學習
    • 對 Laravel 有更深入的了解:在撰寫上有試著更多的在操作與學習 Collection,同時也試著將強型別的概念導入我寫的程式碼當中、繼承前人寫的測試繼續擴大寫測試、同時也在努力學習、讓自己的程式碼可以更乾淨、更好看!同時在這份工作上也學習用到Job的東西,好興奮啊!!!同時閒暇之餘玩轉了一下GraphQL ,恩 有更多的認識了,大概只剩下看看有沒有機會將這個技術應用到正式專案了…
    • API : 我們的產品其實是ios/Android 的 APP,我負責這後面的API 以及管理後台的API , 我發現,API 其實真的是一門學問,於是在許多次進坑後(被前端App team 譙) ,我去天瓏買了一本 Web API 建構與設計,App 的環境比較特殊、不能用以過去我那種以Web角度設計的API,能盡量少給API就少給API ,另外剛剛我買的那本書也提到要重視DX (Developer Experience) ,看來我還是要想辦法讓App/官網/前端 team 開心一點啊….. 之後看這本書如果有什麼心得感想會記錄在這個部落格裡面啦!如果想要知道更多資訊可以參考此網址:https://tw.alphacamp.co/blog/2015-04-22-api-dx
    • 對Mysql 的深入:如果你看過我今年的原來可以這麼寫系列,你會發現後面很多篇幅都在談Mysql 的技巧,不得不說,這算是今年很大的收穫之一了!
    • Docker 能力 level up : 藉著線上Hahow的課程、以及工作上也在使用,對Docker的操作與學習又更深入了一些,甚至覺得可以基礎的放進履歷的那種程度了
    • More AWS/CDK : 公司用了很多AWS 服務,像是Fargate , ECR/ECS 等等,對AWS可以說更熟了!這間公司玩AWS根本到了出神入化了啊,和我前一間公司只是開開EC2 差好多喔,另外也認識到了CDK….
    • NodeJS :  這次在公司也接到了一個NodeJS的專案,是金流交易的系統Lambda ,我們的產品APP 會打Lambda 扣點數交換課程,在這上面學習到很多、同時更是我第一個正式上線的NodeJS專案啊!同時我自己也有在Udemy 上有關於NodeJS的課程:NodeJS – The Complete Guide (MVC, REST APIs, GraphQL, Deno) ,對 NodeJS 掌握度也更深更多了呢 讚讚讚

未來目標

這裡就是嘴砲的地方了,不過既然如此還是希望自己要付諸實踐!寫下來就知道要怎麼實踐了~

  • 關於部落格
    • 一週一文章的計畫,以及希望可以寫更多Laravel deeper的文章~
  • PHP/Python/JavaScript 深化
    • 不知道明年可不可以挑戰寫一個自製 PHP framework ? 學習Swoole ? 同時自己也在Hiskio 買了一包 Laravel 組合包課程 (不過事後想想有點後悔。。。)
    • 買了一本 Python非同步設計:使用Asyncio, 明年要拿出來好好給他看看!
    • 結合今年上的Nodejs課程,當然要寫個NodeJS的部落格當練手看看啦!
  •  新語言學習:2021年,我想要開始認真擴展自己的技能樹,挑了兩門語言學習
    • Golang : 其實2020年就打算學Golang ,但就沒什麼系統,也沒有很認真,這次想要用一本書的時間,購買了深入淺出Go,用專門一個月的時間,好好學習Golang
    • TypeScript : 第一,我想要讓自己習慣強型別,第二,AWS CDK 比較支援的就是這門語言, 當然啦, 在前陣子心情不好也買了本相關書籍,所以當然要給他看完😅
    • ios 學習:不行,我還記得我的人生清單中有一項是上架一個自己的ios app 😂 明年不可以在落掉這項目標了啦!年初就給他行動起來啦!
    • Kubernetes : 覺得自己應該要好好認識這到底是三小東西……
  • 被年底 AWS Dev Day 演講煞到(參加心得):想給它真的很認真參與技術社群
    • DDD TW (想藉此好好學習DDD)
    • PHP也有Day or Taipei.py
    • 希望可以找一個G0v的專案好好給他埋下去
小君曰:明年的目標與想做的事情也太多,到底能不能一一完成呢?讓我們繼續看下去!