Tuesday, January 24, 2012

奮鬥吧!系統工程師(4)誰都能辦到?專案管理 閱讀心得

這兩天看了小說"奮鬥吧!系統工程師"第四集,這次的主題是專案管理。
拜過去的成功經驗所賜,主角工兵接下PM(Project Manager)的工作,為了制定計畫,以及協調來自不同公司的專案成員而奔走。由於是沒有經驗的菜鳥,主角不知如何對應不受指揮的專案成員,就在焦頭爛額時獲得了正妹高人指點:如何駕馭對立的專案成員。

小說中提到專案成員可分成三類
Server (服務者):此類型成員將PM是為客戶,可以接受內容粗略的委託,並自行做出適當判斷以進行作業。
Tool (工具):此類型成員以作業員自居,會順從PM的指示,但並不會背負專案成敗的責任。PM需要追蹤此類成員的進度。
Client (客戶):此類型成員將PM是為服務提供者,必須替自己排除影響工作的障礙,並且同時提供專案進度和支援。

看到這一段令我十分驚奇,因為這管理的道理和<僕人:修道院的領導啟示錄>所說的不謀而合。僕人中說:領導始於服務,領導就是服務。PM做為專案的領導人,也就要"服務"專案成員,為成員整頓環境(比如說解決失戀問題XD),以及發生問題時的支援。

將成員分類對應到我現在的團隊中也很有趣。我的前輩是Server型的成員,不需多做指示變會自動完成工作,甚至還會反過來指示PM接下來的計畫:) 而我則偏向Tool,只專心在被交付的工作。

最後引用書中角色藤崎的話:專案管理是甚麼呢?就是可以讓大家愉快進行作業的工作。

Saturday, January 14, 2012

[推薦] 奮鬥吧! 系統工程師

[此文適合:資訊系學生,想進入資訊業的朋友,已經在資訊業工作的朋友]

有次同事聚餐的時候,我的大長官語重心長得跟我說了:「雖然我們是軟體業,但是最難的地方不在於技術,而是在於人啊。」我才剛踏入業界一年,很難體會他的感想,只覺得工作就是看能力,努力一定會有結果。最近看到一部輕小說 "奮鬥吧! 系統工程師",把軟體業的黑暗真實面描寫得相當清楚。
為了不讓氣氛太沉悶,先放張美美的封面 (圖片來自台灣角川)。





「千萬不要當上SE。」
研究室的教授這麽對我說。
「我站在這裏授課,不是爲了要讓你們過勞死。」
    -- 摘自第一集附錄

還沒被嚇跑的朋友請往下看:)

故事主角櫻坂工兵非科班出身,只是因為畢業找不到工作,被拐騙到駿河系統公司,開始了SE(system engineer)的生活。故事的架構是主角一邊學習,一邊把妹,然後順利完成工作...的樣子。雖然是照著輕小說的歡樂公式,但是本書對於工作的描寫卻是寫實到靠北邊走。好比說主角的主修是行銷,但是卻搖身一變成為工程師,這個設定其實反映了軟體業的入門門檻低,像dot com泡沫時期,阿貓阿狗都可以進入科技公司工作。
不過也不是說非科班出身就不能打出一片天,但隨著故事進行,主角利用美色協調能力讓團隊順利運作,並隨著經驗增加,不斷完成各種管理工作。我個人的感覺是,除了軟體公司裡面幾乎沒有正妹工程師以外,其他的部分都很寫實,好比說顧客任性的改動程序,老闆總是接下超過負荷的工作等等。如果主角換成女生,其他的角色保持男性的話,應該就毫無違和感了!事實上我大學時有個美人學姊,畢業後就到某知名外商當PM,也過得相當不錯。在一片缺乏社交能力的阿宅中,有交際手腕就容易顯得突出。想到這裡,我才發覺當年因為自己不擅和別人相處,還以為資訊工作可以只和電腦打交道,實在是太天真了。想要在這個業界混,還是不能不加強人際管理的能力啊。


發覺寫到這裡還沒有介紹多少內容,但是我懶得打書摘:p 所以放上前五集的標題,看得懂的人想會會心一笑吧

1. 兩週內即可上手?SE入門
2. 從基礎學習?運用建構
3. 絕不失敗?提案活動
4. 誰都能辦到?專案管理
5. Step by Step? Customer Engineer (中文譯名未定)

Sunday, January 1, 2012

親愛的軟體工程師,你平常到底在做什麼?

又到了歲末年初的時候,三五好友聚會的時候總是會談到:最近的工作如何呀?誰誰誰受不了要跳槽了之類的。剛開始到業界工作時,我常跟人說我的工作不有趣,好像只是為了賺錢而已,所以萌生的辭職的想法。但是聽聽朋友的意見之後,發覺我其實還蠻幸福的,因為:
  • 休假多,而且想放假就能放假,不會有專案緊急被叫回去加班的情況。這點很重要,我聽說不少公司的假很多,但是看得到吃不到。
  • 每天平均開會時間約一小時,相當有效率。
  • 薪水有外商的水準。
  • 主管的技術底子強,不會有外行領導內行的情況。

以上是從外部來評量工作的好壞,但是我實際上在做的事究竟是怎樣呢?有那麼無趣嗎?我試著回想看看自己的工作情況,並看看自己有什麼成長。

Who:和誰一起工作
工作一年後,發覺關鍵果然是人,如果人對了,可以聊天打屁之外,還能夠相互刺激學習。如同一般軟體團隊,我們有
產品經理:收集客戶反應,然後決定產品的功能。
專案經理:scrum master。不曉得scrum是什麼?請收看Teddy Chen的部落格。此人並不需深入瞭解技術,他的工作追蹤專案進度。
架構師:一人,負責決定技術決策。
開發者:我和另外五人。主要工作是依據需求設計程式。
支援工程師:平時不會看到他們,不過當產品上市後,站在第一線面對顧客的意見抱怨的就是他們 :) 所以我們需要寫FAQ給他們用。最近從支援工程師那邊來的抱怨是:FAQ寫太多啦,特別是安裝系統的步驟太繁瑣。

What:做了哪些事
我嘗試用簡短幾句話來說我做了什麼。我們團隊的產品是開發針對VMware虛擬化環境的分析工具。為什麼要做這個產品呢?因為VMware是個包山包海的大怪物,裡面什麼東西都有,即便我接觸快一年了,對裡面各種虛擬硬體仍是不完全瞭解,更別說新近的VMware管理員了。
1. 收集事實:用Perl來擷取VMware環境的所有資料,像記憶體用量,CPU用量,錯誤紀錄,使用者的操作歷史等等等。這些東西都會送到我們後端的專用資料庫中。
2. 說故事:有了事實,我們要找出合適的呈現方法。好比說用折線圖呈現各種資源的用量,讓使用者能快速的瞭解他的虛擬環境。由於我們的前端是Web,所以我們常用到Javascript,將來更會多採用HTML5。
3. 回答問題:雖說預防勝於治療,但是一般管理者平時不一定會去用我們的分析工具,只有在火燒屁股時才會趕緊問:到底什麼東西壞掉了?所以我們事先收集各種錯誤的發生原因,並找出偵測方法,好在問題發生時能快速鎖定。

學到什麼
Script language:我們工作用到Perl和Python。特別是Perl,因為這是VMware官方的SDK之一。在這之前我怎麼都搞不懂Perl裡面的%&$@奇怪符號。說真的script langulage開發起來比C快很多,程式也簡短很多。唯一的遺憾是你沒辦法吹噓說自己寫了上萬行程式碼:)
VMware:這是個龐大的怪物!我好像進了大觀園一樣,改天要整理自己的心得分享一下。
Splunk:強大的搜尋引擎,有著類似SQL但又獨特的指令。

回顧完這一年的工作經驗後,發覺好像也沒哪麼無趣嘛。新的一年如果還在這家公司的話,我想要學習HTML5和jQuery,一方面可以強化我們的使用者介面,一方面可以玩一些資料視覺化。

各位看官,你們的工作是否也能說成一個有趣的故事呢?

Monday, December 26, 2011

以 Python 做測試驅動開發 (Test-driven development) 練習 -- 測試一個多邊形是否近似於四角形 (二)

回顧一下前篇文章中提到測試驅動開發(TDD)的三步驟:

1. 寫好程式的介面,只要能夠通過編譯即可
2. 增加測試
3. 執行測試,如果有失敗的測試的話就修改程式,然後回到步驟2

回到我們目前的IsQuadrilateral函數,目前的實作非常簡單,只是檢查傳入的多邊形是否有四個頂點,若是的話就傳回真。現在要來處理其他狀況:長的像四角形的五角形(或更多角)。解法如前篇所說,我們先取多邊形的第一個點做為p0,然後找出最遠的點p1,接著找離p1最遠的點p0。我們把這件工作交給FindFarthestPoint,此函數接受一個點p和一個集合S,傳回在集合S中離p最遠的點。這時我們依照TDD的精神,加上個測試先。testFindFarthestPoint利用我們之前定義好的square,測試離左下角最遠的點是否為右上角。

# 要放到 class TestSequenceFunctions 裡面
def testFindFarthestPoint(self):
    self.assertTrue( numpy.array_equal(FindFarthestPoint(numpy.array([0,0]), self.square)[0], numpy.array([1,1])) )
    self.assertTrue( numpy.array_equal(FindFarthestPoint(numpy.array([0,2]), self.square)[0], numpy.array([1,0])) )

def FindFarthestPoint(p, polygon):
    x = polygon[0,]-p[0]
    y = polygon[1,]-p[1]
    idx = numpy.argmax(x**2 + y**2)
    return polygon[:,idx], idx

好了,雖然繞了些路,終於萬事俱備,可以來寫判斷三角形的函數了。由於我們已經把多邊形切成兩半,各自是兩群連續的點的集合。我們可以想像從p1p2的連線,如果途中有一個凸出的點,p1到p2就會是一個三角形(或者更多角)。我使用CountMiddleVertex來計算一群連續的點之中有幾個凸出的點。這會是一個遞迴函數,先找到最突出的點p3,然後用p3切割線段,然後分別呼叫CountMiddleVertex繼續計算。

# 要放到 class TestSequenceFunctions 裡面
def testCountVertex(self):
    self.assertEqual(CountMiddleVertex(self.square2),   1)
    self.assertEqual(CountMiddleVertex(self.triangle),  1)
    self.assertEqual(CountMiddleVertex(self.square),    2)
    self.assertEqual(CountMiddleVertex(self.pentagon1), 3)
    self.assertEqual(CountMiddleVertex(self.pentagon2), 3)
    self.assertEqual(CountMiddleVertex(self.hexagon),   4)

def CountMiddleVertex(pointset, threshold=0.03):
    pointnum = pointset.shape[1]
    if pointnum <=2:
        return 0
    p1 = pointset[:, 0]
    p2 = pointset[:, -1]
    dist2 = (p1[0]-p2[0])*(p1[0]-p2[0]) + (p1[1]-p2[1])*(p1[1]-p2[1]) 
    triarea = []
    for p in range(pointset.shape[1]):
        triarea.append(PolygonArea(numpy.column_stack((p1,p2,pointset[:,p]))))
    # 如果最突出的點超過門檻值
    if max(triarea)*1.0/dist2 > threshold:
        midpoint = triarea.index(max(triarea))
        vertexnum = 1 + CountMiddleVertex(pointset[:, 0:midpoint+1])
            + CountMiddleVertex(pointset[:, midpoint:], maxnum-vertexnum);
    return 0

CountMiddleVertex測試完成後,我們就利用它來判定三角形吧。更新後的IsQuadrilateral以及完整程式如下。為了讓CountMiddleVertex執行快一些,我加了一個參數maxnum,讓它可以早點結束。

import numpy
import unittest

def PolygonArea(polygon):
    x,y = numpy.column_stack((polygon, numpy.array([polygon[0,0],polygon[1,0]])))
    return abs((numpy.dot(x[0:-1],y[1:]) - numpy.dot(x[1:],y[0:-1])))/2.0 
    
def FindFarthestPoint(p, polygon):
    x = polygon[0,]-p[0]
    y = polygon[1,]-p[1]
    idx = numpy.argmax(x**2 + y**2)
    return polygon[:,idx], idx

def CountMiddleVertex(pointset, maxnum=10, threshold=0.03):
    pointnum = pointset.shape[1]
    if pointnum <=2:
        return 0
    p1 = pointset[:, 0]
    p2 = pointset[:, -1]
    dist2 = (p1[0]-p2[0])*(p1[0]-p2[0]) + (p1[1]-p2[1])*(p1[1]-p2[1]) 
    triarea = []
    for p in range(pointset.shape[1]):
        triarea.append(PolygonArea(numpy.column_stack((p1,p2,pointset[:,p]))))
    if max(triarea)*1.0/dist2 > threshold:
        midpoint = triarea.index(max(triarea))
        vertexnum = 1 + CountMiddleVertex(pointset[:, 0:midpoint+1]);
        if vertexnum >= maxnum:
            return maxnum
        return vertexnum + CountMiddleVertex(pointset[:, midpoint:], maxnum-vertexnum);
    return 0

def IsQuadrilateral(polygon):
    x,y = polygon
    
    if len(x) < 4 or len(x)!=len(y):
        return False
   
    p1, p1idx = FindFarthestPoint(polygon[:,0], polygon)
    p2, p2idx = FindFarthestPoint(p1, polygon)
    
    if p1idx > p2idx:
        p1idx,p2idx = p2idx,p1idx
        p1,p2 = p2,p1
    
    if CountMiddleVertex(polygon[:, p1idx:p2idx+1], 2)==1 and \
        CountMiddleVertex(numpy.column_stack((polygon[:, p2idx:], polygon[:, :p1idx+1])), 2)==1:
        return True
    else:
        return False
    
class TestSequenceFunctions(unittest.TestCase):
    def setUp(self):
        self.triangle   = numpy.array( [ (0,10,0),(0,0,10)  ] )
        self.square     = numpy.array( [ (0,1,1,0),(0,0,1,1)  ] )
        self.square2    = numpy.array( [ (0,0.5,1,0),(0,-0.01,0,1)  ] )
        self.pentagon1  = numpy.array( [ (0,1,1,0,-1),(0,0,1,1,0.5)  ] )
        self.pentagon2  = numpy.array( [ (0,1,1,0,-0.01),(0,0,1,1,0.5)  ] )
        self.pentagon3  = numpy.array( [ (0,1,1,0,-0.21),(0,0,1,1,0.5)  ] )
        self.hexagon    = numpy.array( [ (1,1,0,-1,-1,0),(1,-1,-2,-1,1,2)  ] )
        
    def testFindFarthestPoint(self):
        self.assertTrue( numpy.array_equal(FindFarthestPoint(numpy.array([0,0]), self.square)[0], numpy.array([1,1])) )
        self.assertTrue( numpy.array_equal(FindFarthestPoint(numpy.array([0,2]), self.square)[0], numpy.array([1,0])) )
        
    def testCountVertex(self):
        self.assertEqual(CountMiddleVertex(self.square2),   1)
        self.assertEqual(CountMiddleVertex(self.triangle),  1)
        self.assertEqual(CountMiddleVertex(self.square),    2)
        self.assertEqual(CountMiddleVertex(self.pentagon1), 3)
        self.assertEqual(CountMiddleVertex(self.pentagon2), 3)
        self.assertEqual(CountMiddleVertex(self.hexagon),   4)
    
    def testIsQuadrilateral(self):
        self.assertEqual(IsQuadrilateral(self.triangle),   False)
        self.assertEqual(IsQuadrilateral(self.square),     True)
        self.assertEqual(IsQuadrilateral(self.pentagon1),  False)
        self.assertEqual(IsQuadrilateral(self.pentagon2),  True)
        self.assertEqual(IsQuadrilateral(self.pentagon3),  False)
        self.assertEqual(IsQuadrilateral(self.hexagon),    False)

if __name__ == '__main__':
    unittest.main()

Sunday, December 18, 2011

以 Python 做測試驅動開發 (Test-driven development) 練習 -- 測試一個多邊形是否近似於四角形 (一)

這次用Python來練習驅動程式開發。
TDD簡單的說只有三步
1. 寫好程式的介面,只要能夠通過編譯即可
2. 增加測試
3. 執行測試,如果有失敗的測試的話就修改程式,然後回到步驟2

我要寫的程式是輸入一個多邊形的頂點,檢查是否為近似四邊形。
這個問題可以很簡單,我們只需要檢查多邊形的頂點數目,
如果是四個點的話就是四邊形。
當然我希望這個程式的能力不只如此,不然就沒有練習的效果啦。
其實這個問題是從實際的問題演變過來的,實際上是應用在擴增實境中,
我們從攝影機抓取影像,然後找出影像中的正方形,做為一個參考點。
一個平片上的正方形從不同角度去看,會變成一個任意的四邊形,
我們從影像分割出許多多邊形,然後判斷像不像四邊形。然而由於影像處理必定會有誤差,
所以一個四邊形可能有一些凸出的部分,而會變成五角形或六角形。我們的程式須要能處理這種情況。
這也是採用TDD的理由,我們可以一步一步改進我們的程式。

開始囉
首先定義函數IsQuadrilateral,輸入為一個多邊形,輸出真假值。由於現在還沒有測試,
不假思索就可以得到一個最簡單的實作方法,一律傳回假就好。
def IsQuadrilateral(polygon):
  return False

接著是增加測試,利用Python的unittest模組,我們將測試程式包在TestSequenceFunctions物件中。

import unittest
import numpy
class TestSequenceFunctions(unittest.TestCase):
  def setUp(self):
    self.triangle   = numpy.array( [ (0,1,0),(0,0,1)  ] )
    self.square     = numpy.array( [ (0,1,1,0),(0,0,1,1)  ] )
  def testIsQuadrilateral(self):
    self.assertEqual(IsQuadrilateral(self.triangle),    False)
    self.assertEqual(IsQuadrilateral(self.square),      True)
if __name__ == '__main__':
    unittest.main()

setUp函數是用來建立測試用的資料,這邊我們建立了兩個二維陣列來表示三角形和正方形的頂點。
numpy是常用的數學函式庫,程式中的 [ (0,1,0),(0,0,1)  ] 表示一個二維陣列,第一列是三個頂點的x座標 0,1,0,
第二列三個頂點的y座標 (0,0,1)。square物件存放的是正方形,有四個頂點。

完成以後就是執行測試。結果在第二個測試失敗了,所以我們要回頭修改程式。第二版的程式如下:檢查傳入的多邊形是否含有四個頂點,
有的話就為真,否則為假。

def IsQuadrilateral(polygon):
    x,y = polygon
    if len(x) != 4 or len(x)!=len(y):
        return False
    return True

現在回頭再跑測試,就可以過關啦。當然我們不能停在這裡,要繼續加測試才行。

五角形1:
self.pentagon1 = numpy.array( [ (0,1,1,0,-1),(0,0,1,1,0.5)  ] )
五角形2:
self.pentagon2 = numpy.array( [ (0,1,1,0,-0.01),(0,0,1,1,0.5)  ] )
五角形3:
self.pentagon3 = numpy.array( [ (0,1,1,0,-0.21),(0,0,1,1,0.5)  ] )
此處我們用了三個五角形,長相如下:

其中第二個五角形其實是四角形,只是左邊有一點小突出而已,我們的新測試如下:

def testIsQuadrilateral(self):
    self.assertEqual(IsQuadrilateral(self.triangle),   False)
    self.assertEqual(IsQuadrilateral(self.square),     True)
    self.assertEqual(IsQuadrilateral(self.pentagon1),  False)
    self.assertEqual(IsQuadrilateral(self.pentagon2),  True)
    self.assertEqual(IsQuadrilateral(self.pentagon3),  False)

修改演算法,四邊形是兩個三角形所組成,所以我們先嘗試找到四邊形的兩個頂點。這裡我用了一個經驗法則,先任意選取polygon上的一個點p0,然後選取離p0最遠的點p1,接著選取離p1最遠的點p2,如此一來p1和p2就是近似四邊形上的兩個點。p1p2的連線把polygon分成兩半,我們接下來只需要判斷這兩半是否為三角形即可。請見下面的圖。

為了要找到離p0最遠的點,我需要再寫一個函數FindFarthestPoint,同時需要新的測試來驗證這新的函數。
今天就先寫到這裡吧。來回顧一下到目前為止我們的程式看起來如何:

import unittest
import numpy

# 主函數
def IsQuadrilateral(polygon):
    x,y = polygon
    if len(x) != 4 or len(x)!=len(y):
        return False
    return True

# 測試程式
class TestSequenceFunctions(unittest.TestCase):
    # 此函數在 testIsQuadrilateral 被執行前會被呼叫,以建立測試所需的物件
    # 想一想為何需要一個獨立的 setUp 函數呢?
    def setUp(self):
        self.triangle   = numpy.array( [ (0,10,0),(0,0,10)  ] )
        self.square     = numpy.array( [ (0,1,1,0),(0,0,1,1)  ] )
        self.pentagon1  = numpy.array( [ (0,1,1,0,-1),(0,0,1,1,0.5)  ] )
        self.pentagon2  = numpy.array( [ (0,1,1,0,-0.01),(0,0,1,1,0.5)  ] ) # 這個測試會失敗
        self.pentagon3  = numpy.array( [ (0,1,1,0,-0.21),(0,0,1,1,0.5)  ] )

    def testIsQuadrilateral(self):
      self.assertEqual(IsQuadrilateral(self.triangle),   False)
      self.assertEqual(IsQuadrilateral(self.square),     True)
      self.assertEqual(IsQuadrilateral(self.pentagon1),  False)
      self.assertEqual(IsQuadrilateral(self.pentagon2),  True)
      self.assertEqual(IsQuadrilateral(self.pentagon3),  False)

if __name__ == '__main__':
    unittest.main()  # 會呼叫TestSequenceFunctions裡所有的測試函數

Saturday, December 3, 2011

我的軟體生涯發展規劃

到業界工作進一年來,有幸遇到功力深厚又照護晚輩的前輩。由於師徒制在軟體業不盛行,一般的人不會花多餘時間指導新人,一來是沒有回報,二來是怕被超越。對我來說學到的東西中,是前輩指出我的兩個最大的缺點。

1. 應付了事。我向來是那種很會考試的人,對於大多數問題都能找到一個答案並且應付過去。但是久而久之會養成一種交差了事的態度,只求解決眼前的問題,沒有去思索整個問題的來龍去脈,並且透徹了解問題。

2. 獨善其身。所謂教學相長,我平時學到的東西很少與人分享,一方面是個性不主動親近人,另一方面是表達能力不強。


前輩給我的建議是先改進第二點,練習成為某個領域的大師,並且能幫別人速成。所謂速成就是指:比自己更快速學成。他指出唯有能幫別人速成,才表示你已經學的透徹。由於現在沒有學生,我先嘗試把自己學習的心得以教學的方式放在部落格上。

最後分享一篇文章做為結尾: 給學生與軟體業新進的十招

Sunday, November 27, 2011

家庭幫手機器人的需求

我是個很懶的人,在家裡時用過的東西常隨手就丟,完全違反"物歸原位"的整潔原則哈。所以想說如果能有個機器人幫我拿東西收東西多好。

情境:我所需要的東西放在一個儲物櫃中,我呼叫機器人幫我拿來,用完後幫我收好。
為了達成這個目標,這樣的機器人需要以下能力:

接收命令的能力。最好是用語音輸入,可以簡單的用單字即可,如"剪刀"

辨識物體的能力。視覺能力,能夠在儲物櫃中找到特定物體。
移動能力。可以來回於使用者和儲物櫃之間。
取得物體的能力。打開儲物櫃,取出物體,所以要有"手"。

以上幾項技術早已存在,語音輸入有Apple最近發表的Siri語音助理,特定物體辨識在電腦視覺領域已經有一定的成熟,移動能力早已成熟,最後是拿取物體的能力,這部分也不成問題。照這樣看來,我們在市面上應該能找到這樣的產品才對。所以離題一下,來搜尋看看有哪些公司已經發表家事機器人。

Willow Garage
主要產品: PR2, OpenCV, ROS (Robot Operating System)

iRobot
主要產品: Roomba (地上亂跑的吸塵器那隻), Scooba (這隻可以拖地!), Looj (清除房屋外沿的排水溝), Verro (清潔游泳池)

比照這兩家公司,可以看出Willow Garage走通用型機器人,而iRobot是專用型的。

結尾來對照一下現實中的機器人和想像中的機器人做家事吧~




摺衣服



摺襪子