prism

2014-12-24

New job

新的工作,前幾天剛任職滿一個月

前幾個月心中浮出念頭,想換工作,就像馬雲說的,心委屈了,給的不到位

直到最後真的不想再承受的是,我真的由衷的喜歡寫程式,學新的東西,解決問題

後端用 Python, Node.js , 或是 Golang 去刻,也可以接受被叫去支援寫 Android
, iOS 的應用程式,可是 PHP Team 的工程師離職後,找不到人,找我去接專案,心中真的很圈圈叉叉
好歹我也是掛在 Python 的開發 Team 下面,應該在怎麼凹,也凹不到我吧!

喜歡寫程式,愛 Linux ,也尊重 Php 的工程師,寫過1, 2 年

可是無奈現在看到程式裡有很多的錢字號,真的要很多錢的工作職務,才吞的下去,尤其是人員的離職
就亂塞專案,心裡真的很不舒服,就是這樣的感覺,淹沒我工作的樂趣,及下班後,陪伴小孩,家人的幸福感,

開始認真的再看看有沒有適合的工作機會,這裡分享中年程式宅,換工作的心得,由幾個點來看找工作
這件事

最好的工作,一定是朋友介紹的,不過阿宅的朋友一般不多,所以自己拿捏一下

看工作說明及前置作業

1. 一般的說明,寫的很制式的,就不用投了,一間公司,連找人才這麼重要的事,都不在意了,相信也不太會重視你

2. 專業名詞,幾乎全部出現了,薪資待遇卻沒有空間,這投的話,只能說,自己的邏輯判斷都有問題了怎麼寫程式

3. 說明裡提到,克苦耐勞,新人尤佳,一般台灣的徵才有很多淺規則,要克苦耐勞的一定只有更苦,新人尤佳的
其實就是要應屆畢業的同學,說到歡迎抬青椒成的,就是只接受台青椒成 (外商不適用,真正的外商比較沒有這些規則)

4. 一般台灣企業,或是假外商的話,條件裡的 nice to have ,不用懷疑,就是一定要會,而且要很厲害

5. 新創公司,一般除了是自己認識的人外,一定要選成立一年以上的公司,一間連員工年終都沒發過的公司,你信任嗎?

6. 一般新創公司,徵才的文案寫的很漂亮,有一個陷阱是薪水的範圍給的很大,這種通常只是吸引你投履歷,一般給不起上限
(除非你有學長姊,或是認識的朋友在裡面任職,不過通常是好缺的話,不用寫到文案,履歷已經很多份,正送到人資主管手上了)

7. 大公司,有好學歷,比較有好機會,公司經營者越是學術界出身的越在意學歷,自然你學歷是強項,就很有機會出線,如果你是專業強,
學歷像我一樣,私立大學還非本科系,通常外商或是,專業強的老闆,會比較重視你

8. 一個工作一直在徵人,可能掛個一年以上,我是沒投過這樣的職務,你可以問看看,在告訴大家

9. 公司的經營者,名聲不好,或是人品不好,這個其實很主觀,就像選舉一樣,反正自己的選擇,我是不會考慮

10. 另外就是 Head hunter 及 Linkedin 提供的機會,一般會比一般人力銀行的條件好一些

進入面試過程,其實這是很主觀的事情,只能說,千奇百怪,什麼人都有
有些人,希望你的智力破表,人格正常,專業超強,充滿創造力及想像力,又要好管理,聽了是不是很矛盾 :-)

反正就是做自己,假如你找的是一份你本來就喜歡做的事,有問題就問,不能接受的就不要接受,

不必像一些面試法則說的那樣,溫文儒雅,有禮貌,懂規矩,少問問題,我們求得是一份快樂的工作,
工作開心,自然很期待每天上班,不是奴才的缺,每天痛苦的工作
如果想知道,新的工作環境,是不是責任制,或是一般的下班時間,就直接問,會因為這樣就在意的主管,
一般他也不是在意你的專業能力,而是在意對你的控制能力,也許教你面試的書,會說你失去了一的工作機會
其實是你又浪費了寶貴的時間在錯的地方


1. 一般面試官,會問的,有工作經驗,答的出來,知道多少就說多少,謙虛,不要唬爛,不要自以為是

2. 程式題,就盡量寫,不會寫,也還好,就說一下怎麼解,或是自己的了解,依您自己開發多年的經驗,你的成果應該都不是在紙上寫出來的吧!
一般程式題,我覺得是要過濾一些完全沒有概念的人用的,真的會用很艱深的程式問題或是演算法,來考倒你,我想這樣公司需要的是很會考試的同學
一般網路上,找的到的,或是書本有教的,查的到的,有概念,需要驗正及實作的時候,再專研即可,真正的工作,會有不同的挑戰,不見得書上找的到,
有時候,是真的花比較多時間在看文件,及實作

3. 一般回答完面試官所有問題後,會給你提問,如果你真的是會不好意思,我也是會有一點,我至少問
a. 公司一般作息時間,是不是責任制 ( 一般會要你加班工作的主管,聽到你這樣問,也會打你槍,不過是好事)
b. 公司有沒有不同於勞基法以外特別的規定
c. 問面試官,現在任職的公司服務,最令他覺得驕傲,自豪的點,還有最不舒服的點 (說不出來,或是吱吱嗚嗚,自己都說不出為什麼要做下去,又怎麼能要你加入,大概就可以打槍這一間公司不用再往下談了)
d. 公司成家的工程師的比例,及一間公司工程師的比例 (一般,日夜都忙的工作,沒有時間交女友,或是生活只有工作,沒有一點其他的生活,又有誰要跟他共組家庭)


最後階段,公司覺得你是不可多得的人才,談薪資及 package

1. 如果是要離開台灣,記得要依照,實際國家水準,不要用台灣的角度看

2. 千萬不要相信人力銀行上面的數據,用你簡單的數學,如果,不吃不喝,買不起房子的薪水,哪就不要做了,出國吧

3. 至少相信自己的專業,在 odesk 上面可以拿的到的時薪,稍微還原一下,就可以推出一個正常的無國界工作者的基本價值,不然就開始接案人生吧 ;-)

4. 在自己真正有實力的基礎下(不是自我感覺良好),至少,你覺得爽,覺得夠,不然又會墜入心理的糾結 (最好可以跟學長姊,或是同業的朋友打聽一下,不要看人力銀行的數據)

以上自己的心得分享,希望對您有幫助

2014-12-23

golang routers

覺得簡單易懂的好文章

可以讓你用 golang 寫 api 有一個簡單的開始

http://nicolasmerouze.com/guide-routers-golang/

另外也推薦他的 gist ,再把 main 的部份,拔出來,做一個 NewRouter 應該更好測試,再加幾個函式庫,就是一個 micro framework 了

作者這一系列的文章 http://nicolasmerouze.com/build-web-framework-golang/

package main
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"time"
"github.com/gorilla/context"
"github.com/julienschmidt/httprouter"
"github.com/justinas/alice"
)
func recoverHandler(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic: %+v", err)
http.Error(w, http.StatusText(500), 500)
}
}()
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func loggingHandler(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
t1 := time.Now()
next.ServeHTTP(w, r)
t2 := time.Now()
log.Printf("[%s] %q %v\n", r.Method, r.URL.String(), t2.Sub(t1))
}
return http.HandlerFunc(fn)
}
func aboutHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "You are on the about page.")
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome!")
}
type appContext struct {
db *sql.DB
}
func (c *appContext) authHandler(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
authToken := r.Header.Get("Authorization")
user, err := map[string]interface{}{}, errors.New("test")
// user, err := getUser(c.db, authToken)
log.Println(authToken)
if err != nil {
http.Error(w, http.StatusText(401), 401)
return
}
context.Set(r, "user", user)
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func (c *appContext) adminHandler(w http.ResponseWriter, r *http.Request) {
user := context.Get(r, "user")
// Maybe other operations on the database
json.NewEncoder(w).Encode(user)
}
func (c *appContext) teaHandler(w http.ResponseWriter, r *http.Request) {
params := context.Get(r, "params").(httprouter.Params)
log.Println(params.ByName("id"))
// tea := getTea(c.db, params.ByName("id"))
json.NewEncoder(w).Encode(nil)
}
// We could also put *httprouter.Router in a field to not get access to the original methods (GET, POST, etc. in uppercase)
type router struct {
*httprouter.Router
}
func (r *router) Get(path string, handler http.Handler) {
r.GET(path, wrapHandler(handler))
}
func NewRouter() *router {
return &router{httprouter.New()}
}
func wrapHandler(h http.Handler) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
context.Set(r, "params", ps)
h.ServeHTTP(w, r)
}
}
func main() {
// db := sql.Open("postgres", "...")
appC := appContext{nil}
commonHandlers := alice.New(context.ClearHandler, loggingHandler, recoverHandler)
router := NewRouter()
router.Get("/admin", commonHandlers.Append(appC.authHandler).ThenFunc(appC.adminHandler))
router.Get("/about", commonHandlers.ThenFunc(aboutHandler))
router.Get("/", commonHandlers.ThenFunc(indexHandler))
router.Get("/teas/:id", commonHandlers.ThenFunc(appC.teaHandler))
http.ListenAndServe(":8080", router)
}
view raw main.go hosted with ❤ by GitHub


package main
import (
"encoding/json"
"log"
"net/http"
"reflect"
"time"
"github.com/gorilla/context"
"github.com/julienschmidt/httprouter"
"github.com/justinas/alice"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
// Repo
type Tea struct {
Id bson.ObjectId `json:"id,omitempty" bson:"_id,omitempty"`
Name string `json:"name"`
Category string `json:"category"`
}
type TeasCollection struct {
Data []Tea `json:"data"`
}
type TeaResource struct {
Data Tea `json:"data"`
}
type TeaRepo struct {
coll *mgo.Collection
}
func (r *TeaRepo) All() (TeasCollection, error) {
result := TeasCollection{[]Tea{}}
err := r.coll.Find(nil).All(&result.Data)
if err != nil {
return result, err
}
return result, nil
}
func (r *TeaRepo) Find(id string) (TeaResource, error) {
result := TeaResource{}
err := r.coll.FindId(bson.ObjectIdHex(id)).One(&result.Data)
if err != nil {
return result, err
}
return result, nil
}
func (r *TeaRepo) Create(tea *Tea) error {
id := bson.NewObjectId()
_, err := r.coll.UpsertId(id, tea)
if err != nil {
return err
}
tea.Id = id
return nil
}
func (r *TeaRepo) Update(tea *Tea) error {
err := r.coll.UpdateId(tea.Id, tea)
if err != nil {
return err
}
return nil
}
func (r *TeaRepo) Delete(id string) error {
err := r.coll.RemoveId(bson.ObjectIdHex(id))
if err != nil {
return err
}
return nil
}
// Errors
type Errors struct {
Errors []*Error `json:"errors"`
}
type Error struct {
Id string `json:"id"`
Status int `json:"status"`
Title string `json:"title"`
Detail string `json:"detail"`
}
func WriteError(w http.ResponseWriter, err *Error) {
w.Header().Set("Content-Type", "application/vnd.api+json")
w.WriteHeader(err.Status)
json.NewEncoder(w).Encode(Errors{[]*Error{err}})
}
var (
ErrBadRequest = &Error{"bad_request", 400, "Bad request", "Request body is not well-formed. It must be JSON."}
ErrNotAcceptable = &Error{"not_acceptable", 406, "Not Acceptable", "Accept header must be set to 'application/vnd.api+json'."}
ErrUnsupportedMediaType = &Error{"unsupported_media_type", 415, "Unsupported Media Type", "Content-Type header must be set to: 'application/vnd.api+json'."}
ErrInternalServer = &Error{"internal_server_error", 500, "Internal Server Error", "Something went wrong."}
)
// Middlewares
func recoverHandler(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic: %+v", err)
WriteError(w, ErrInternalServer)
}
}()
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func loggingHandler(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
t1 := time.Now()
next.ServeHTTP(w, r)
t2 := time.Now()
log.Printf("[%s] %q %v\n", r.Method, r.URL.String(), t2.Sub(t1))
}
return http.HandlerFunc(fn)
}
func acceptHandler(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Accept") != "application/vnd.api+json" {
WriteError(w, ErrNotAcceptable)
return
}
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func contentTypeHandler(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Content-Type") != "application/vnd.api+json" {
WriteError(w, ErrUnsupportedMediaType)
return
}
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func bodyHandler(v interface{}) func(http.Handler) http.Handler {
t := reflect.TypeOf(v)
m := func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
val := reflect.New(t).Interface()
err := json.NewDecoder(r.Body).Decode(val)
if err != nil {
WriteError(w, ErrBadRequest)
return
}
if next != nil {
context.Set(r, "body", val)
next.ServeHTTP(w, r)
}
}
return http.HandlerFunc(fn)
}
return m
}
// Main handlers
type appContext struct {
db *mgo.Database
}
func (c *appContext) teasHandler(w http.ResponseWriter, r *http.Request) {
repo := TeaRepo{c.db.C("teas")}
teas, err := repo.All()
if err != nil {
panic(err)
}
w.Header().Set("Content-Type", "application/vnd.api+json")
json.NewEncoder(w).Encode(teas)
}
func (c *appContext) teaHandler(w http.ResponseWriter, r *http.Request) {
params := context.Get(r, "params").(httprouter.Params)
repo := TeaRepo{c.db.C("teas")}
tea, err := repo.Find(params.ByName("id"))
if err != nil {
panic(err)
}
w.Header().Set("Content-Type", "application/vnd.api+json")
json.NewEncoder(w).Encode(tea)
}
func (c *appContext) createTeaHandler(w http.ResponseWriter, r *http.Request) {
body := context.Get(r, "body").(*TeaResource)
repo := TeaRepo{c.db.C("teas")}
err := repo.Create(&body.Data)
if err != nil {
panic(err)
}
w.Header().Set("Content-Type", "application/vnd.api+json")
w.WriteHeader(201)
json.NewEncoder(w).Encode(body)
}
func (c *appContext) updateTeaHandler(w http.ResponseWriter, r *http.Request) {
params := context.Get(r, "params").(httprouter.Params)
body := context.Get(r, "body").(*TeaResource)
body.Data.Id = bson.ObjectIdHex(params.ByName("id"))
repo := TeaRepo{c.db.C("teas")}
err := repo.Update(&body.Data)
if err != nil {
panic(err)
}
w.WriteHeader(204)
w.Write([]byte("\n"))
}
func (c *appContext) deleteTeaHandler(w http.ResponseWriter, r *http.Request) {
params := context.Get(r, "params").(httprouter.Params)
repo := TeaRepo{c.db.C("teas")}
err := repo.Delete(params.ByName("id"))
if err != nil {
panic(err)
}
w.WriteHeader(204)
w.Write([]byte("\n"))
}
// Router
type router struct {
*httprouter.Router
}
func (r *router) Get(path string, handler http.Handler) {
r.GET(path, wrapHandler(handler))
}
func (r *router) Post(path string, handler http.Handler) {
r.POST(path, wrapHandler(handler))
}
func (r *router) Put(path string, handler http.Handler) {
r.PUT(path, wrapHandler(handler))
}
func (r *router) Delete(path string, handler http.Handler) {
r.DELETE(path, wrapHandler(handler))
}
func NewRouter() *router {
return &router{httprouter.New()}
}
func wrapHandler(h http.Handler) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
context.Set(r, "params", ps)
h.ServeHTTP(w, r)
}
}
func main() {
session, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
defer session.Close()
session.SetMode(mgo.Monotonic, true)
appC := appContext{session.DB("test")}
commonHandlers := alice.New(context.ClearHandler, loggingHandler, recoverHandler, acceptHandler)
router := NewRouter()
router.Get("/teas/:id", commonHandlers.ThenFunc(appC.teaHandler))
router.Put("/teas/:id", commonHandlers.Append(contentTypeHandler, bodyHandler(TeaResource{})).ThenFunc(appC.updateTeaHandler))
router.Delete("/teas/:id", commonHandlers.ThenFunc(appC.deleteTeaHandler))
router.Get("/teas", commonHandlers.ThenFunc(appC.teasHandler))
router.Post("/teas", commonHandlers.Append(contentTypeHandler, bodyHandler(TeaResource{})).ThenFunc(appC.createTeaHandler))
http.ListenAndServe(":8080", router)
}
view raw main.go hosted with ❤ by GitHub



相關也推再看 alice 模組作者的文章,雖然 alice 很單純,單純到自己刻,可能也只有 5 行左右
不過看一下,作者的實作概念,挺不錯的


https://justinas.org/alice-painless-middleware-chaining-for-go/
https://github.com/justinas/alice
https://github.com/julienschmidt/httprouter
https://github.com/gorilla/context

2014-12-17