2015年8月29日 星期六

什麼是專業?

29-122628-64fa9554-5fa8-42b8-ac5f-ec76b211199e

前幾天,看了一篇文章,看完之後我立即在FB上分享,原因不僅僅是因為文章中描述的雖然以設計界為主體,但根本120%符合台灣軟體專案外包的現況。我本來想節錄其中讓我很感慨的幾句話,但我回頭再看一次文章,發現我不用節錄什麼,我推薦讀者整篇重頭到尾讀一遍就對了。

接著,我想說說我的感受。

最近十年,我剛好身兼文中所說的幾種工作,資訊軟體開發的教育訓練、雜誌專欄文章撰寫、書籍出版、同時也在業界接案、甚至擔任顧問,台灣軟體專案發包的低價、浮濫…等問題,跟文中說的完全一樣,我們非常明白這件事情。(因此看完之後,我沉思良久)

但如果說,我們在接案時跟其他人一樣,必須被迫用較低的價格來接案,這只說明了一件事情,就是『客戶覺得我們不夠專業』,我們不夠優秀到讓客戶願意用比別人更高的價格,來聘請我們為他服務。簡單的說,就是如此而已。

你說市場行情,沒錯,市場行情是一回事,但對於追求專業的我們來說,我們總是期許(要求)自己所做的每一項工作,都有著比他人高的品質和效率,這意味著,我們期許自己比其他同業要來的專業,果真如此,那在價格上應該要比一般"行情"要來的高一些才對。

如果客戶不願意用比市場行情高的價格,來聘請我們,那顯然該檢討的不是客戶,而是我們自己。我們是否曾在客戶面前彰顯了自己的能力? 我們是否讓客戶感受到十足的值得信任? 客戶和我們溝通的過程中,是否就能夠體會到所謂的專業? 是否與我們對談本身,對客戶來說就已經是一種收穫?

如果不是,那表示我們還必須努力。

當然,容或有一些客戶對專業沒興趣,只希望用最低的價格拿到堪用的成品,但這本來也就不是我們想要爭取的對象。

最近十年,軟體或網站開發比起過去容易非常多,隨便一個學生或是上過職訓的SOHO,都能夠拿起開發工具寫code(沒什麼好怨的,我們自己都帶過這樣的教育訓練課程,也都在台上跟學員說,你看,你看,就這樣,很簡單吧…)。

但,如果只是把程式碼寫出來,讓網站可以動,那顯然無法彰顯你的專業。

在這個業界待了五年、十年、甚至二十年,和剛畢業的學生有何差別? 當破壞行情的份子在業界用極低的價格承接專案的同時,我們能否清楚地跟客戶說明,為何我們值得比較高的報價? 軟體開發或網站設計為何不僅僅只是寫代碼? 一個優秀的系統還包含了哪些要素? (穩定性? 延展性? 安全性?) 好的專案管理該如何凸顯透明度? 為何我們可以每兩周給客戶一個Demo? 為何我們歡迎且能夠掌握需求變更? 我們如何控管 bug的生命週期,好讓軟體產品達到最好的品質管理? …

凡此種種,都不是剛從職訓單位結訓兩三年內可以掌握可以做到的,把程式寫出來是一回事,做出對客戶有用的網站或軟體,是另一回事。更不用說後續的維護、更新、以及改版或技術升級了。

專業,不一定是用最新的技術,也並非是把天花亂墜的術語天天掛在嘴邊…專業是與客戶合作,為客戶找到最適合的實施方式,讓客戶喜歡我們為他所做出的產品,更覺得與我們合作的過程不僅能夠學習,且充滿樂趣。

後記:
這篇文章寫的是軟體公司,但想想,技術人員的職場不也是如此? 什麼是專業? (絕對不只是天天學最新的技術而已,這剛畢業的年輕人也可以…) 和老闆之間的關係如何處理? 總是會碰到只出得起香蕉的老闆,而你又該如何回應? 在老闆與同事面前如何凸顯你的專業? 這也是技術人員該深思的問題。

2015年8月12日 星期三

讓團隊溝通? 比你想像的容易…

前一陣子到某家公司進行教育訓練,談的是專案管理與ALM。席間有位學員問到:『Scrum鼓勵溝通,但要怎樣才能夠讓成員凝聚出團隊氣氛,讓大家真的進行溝通呢?』

我很能理解這個問題,因為卓越的工程師普遍喜歡自己一個人埋頭寫code,對解決技術問題都有自己的一套想法,但要習慣性地跟其他人一起合作,有時似乎還挺困難。這位提問者是公司的主管,他嘗試過讓這些團隊成員到外面上溝通的課程、訓練團隊成員溝通的技巧,甚至把溝通這件事情當作績效指標,結果…不出你的預料,完全沒效。團隊成員好像更不喜歡講話了。

回答這個學員的問題前,我先跟這位學員說了一個我最近在一本書上(關鍵18分鐘)看到的一段故事。

書籍的作者有次造訪一座部落平原,搭乘著老舊的火車,乘車過程中,他看到有一頭雄偉的獅子 · 蹲伏在山丘頂的岩石上,看得清楚極了。
作者問列車上的管理員:『這麼巧,竟然能夠遇到獅子出來 · 這樣算是相當幸運的吧?』
『那頭獅子一向都在那裡,牠總是坐在岩石上。』對方這麼回答。
『真的? 你們用什麼辦法 · 讓牠一直待在那裡???』作者問。
管理員笑而不答。

學員們疑惑的同時,我接著說了書上的另一個故事:
『曾經有一位主管向HR顧問抱怨他們公司的接待人員不夠熱情,看到有人走進服務櫃台卻沒有表現出友善的態度。顧問實際上瞭解了狀況之後,發現根本不需要讓接待人員接受任何額外的教育訓練,也不需要複雜的績效制度,只需要把接待檯前面像是郵局櫃台一樣的那個售票窗口的玻璃移除,同時降低櫃檯的高度,服務人員的態度立即有了明顯的改善。』

真的有用? 是的,因為人們非常容易受到環境的引導和暗示。

接著,我把我們某個專案團隊的照片開給他看,並且問:有沒有發現,和一般的辦公室比起來,位子和位子之間少了什麼?SNAGHTML9cb5a72

沒錯,少了隔板。

如果你一邊鼓勵團隊溝通,一邊卻讓每一個團隊成員都坐在像是城堡一樣高度的Office Partition座位中,那溝通是不容易發生的。反之,把隔板拿掉,讓team member面對面,眼神可以隨時看到對方,如此一來,團隊成員之間的交流會自然而然的形成,不需要額外的教育訓練與獎勵制度,不需要導入績效管理。事實上,最有效的方式,就只是一個環境的改變而已。

同樣的,在實施敏捷開發的許許多多做法當中,最不需要的就是導入KPI之類的績效制度或SOP,而是透過環境來引導團隊成員,往你所需要的方向前進。

帶領人,和管理事情,是不一樣的。事情能夠管理(Manage),但人只能領導(Lead)。把人當作工具一樣管理,是工業革命時期的管理模式,不是一個適合現今這樣需要創意的腦力密集產業的有效作法。

至於那頭獅子,原因很簡單,因為那個岩石是控溫的,天氣冷的時候,園方就讓岩石溫暖一點,大熱天時,就讓岩石冰涼一點,獅子,自然就待在岩石上了…

2015年8月3日 星期一

[開發Office Add-Ins]常用API - getSelectedDataAsync(6)

談完setSelectedDataAsync之後,我們來看看getSelectedDataAsync這個API,有填入當然就有取得,getSelectedDataAsync的功能非常簡單,就是取得用戶選取範圍的資料,我們看底下的程式碼:

function getTextData() {
    //調用API填入文字
    Office.context.document.getSelectedDataAsync(Office.CoercionType.Text,
    //call back
    function (asyncResult) {
        var error = asyncResult.error;
        //如果失敗
        if (asyncResult.status == "failed") {
            //顯示失敗訊息
            app.showNotification(error.name + ": " + error.message);
        }
        else {
            //顯示成功訊息
            app.showNotification('成功取得:' + asyncResult.value);
        }
    });
}

執行的結果如下:

image

你會發現,我們透過程式碼中的『asyncResult.value』取得選取區域的文字,由於我們傳入的取回type是Office.CoercionType.Text,因此取得的結果也以純文字方式呈現。

讀者可以試試看,如果把Office.CoercionType.Text改為Office.CoercionType.Html,你回發現傳回的asyncResult.value就變成HTML格式的內容了。

如果是Excel的內容呢?

我們看底下這段程式碼:

function getTableData() {
    //調用API填入文字
    Office.context.document.getSelectedDataAsync(Office.CoercionType.Matrix,
    //call back
    function (asyncResult) {
        var error = asyncResult.error;
        //如果失敗
        if (asyncResult.status == "failed") {
            //顯示失敗訊息
            app.showNotification(error.name + ": " + error.message);
        }
        else {
            //顯示成功訊息
            app.showNotification('成功取得:' + asyncResult.value[1][2]);
        }
    });
}

由於我們傳入參數『Office.CoercionType.Matrix』以Matrix格式取得選取區域的資訊,因此,當執行上面這段程式時,結果如下:

image

當選取區域是上圖畫面中的A1-C3,則抓取到的Matrix[1][2],其內容就是『3』(矩陣起始編號為0)。

透過簡單的API,我們不僅可以寫入,也可以讀取Office文件中的資料了。

2015年8月1日 星期六

[開發Office Add-Ins]常用API - setSelectedDataAsync(5)

先前我們談到,我們在開發Office Add-Ins(特別是Task Pane與Content Add-in)時,大部分的API都從『Office.context.document…』而來,而這個從Office開頭的API,來源就是我們在html頁面上所引入的Office.js,請注意,Office.js並非一個檔案,而是一整組 office.js ,這是一大組 javaScript files的集合,如果你透過Visual Studio開發的話,可以看到專案目錄下有許多檔案:
其中的細節我們後面有機會再討論,先來看看我們常用的兩個API。getSelectedDataAsync與setSelectedDataAsync這兩個方法。
設定(填入)選取區域
我們先看setSelectedDataAsync這個方法的定義:
範例:
Office.context.document.setSelectedDataAsync(data [, options], callback);
其中data參數的可用內容如下:
參數
內容
備註
data string (Office.CoercionType.Text) Excel、 Excel Online、 PowerPoint、 PowerPoint Online、 Word,以及只Word Online
  array的陣列 (Office.CoercionType.Matrix) Excel、 Word,以及只Word Online
  TableData (Office.CoercionType.Table) Access、 Excel、 Word、 僅限Word Online
  html Word,並只Word Online
  Office Open XML 僅限Word
基本上setSelectedDataAsync的功能就是將特定的文字或物件,填入當前用戶在文件當中的選取區域。 例如,底下這段很簡單的程式碼,可將特定文字填入文件當中: // 從目前文件選取範圍讀取資料,並且顯示通知
function FillText() {
    //調用API填入文字
    Office.context.document.setSelectedDataAsync("測試填入文字",
    //call back
    function (asyncResult) {
        var error = asyncResult.error;
        //如果失敗
        if (asyncResult.status == "failed") {
            //顯示失敗訊息
            app.showNotification(error.name + ": " + error.message);
        }
        else {
            //顯示成功訊息
            app.showNotification('成功!');
        }
    });
}
按下Button之後,執行結果如下: SNAGHTML10a16bd7 當然,可以填入的不僅僅是文字而已,試試看底下這段程式碼: function FillTable() {
    //調用API填入文字
    Office.context.document.setSelectedDataAsync([['張三', '李四', '王五'], ['A', 'B', 'C'], ['123', '456', '789']],
    //call back
    function (asyncResult) {
        var error = asyncResult.error;
        //如果失敗
        if (asyncResult.status == "failed") {
            //顯示失敗訊息
            app.showNotification(error.name + ": " + error.message);
        }
        else {
            //顯示成功訊息
            app.showNotification('成功!');
        }
    });
}
執行的結果如下: SNAGHTML10a139da 你會發現,setSelectedDataAsync除可以填入文字之外,如果你填入陣列類型的資料,在Word當中則會變成表格,在Excel當中自然就是直接變成儲存格內容了: image
非常簡單的API,是吧? 我們後面再來看我們用過多次的getSelectedDataAsync。










[開發Office Add-Ins] Task Pane Add-Ins專案內容(4)

不管你是透過NAPA 或是Visual Studio來開發Office Add-Ins,建構出的Task Pane Add-Ins專案,在App Body的部分,結構都是底下這樣:

clip_image002

我們前面曾經提過,進入點Home.html是從manifest檔案那邊設定的,你當然可以改變這個設定。

整個App都是透過HTML與JavaScript撰寫,我們先來看Home.html部分。

Home.html

我們開啟Home.html,你會發現頁面結構如下:

clip_image004

你會發現主畫面<Body>標記裡面被兩個<div>區分,分別是content-header,以及content-main。(上圖)

content-header的部分是標題,呈現出來向是底下的『歡迎』部分:

clip_image006

而content-main則是上圖除了藍色的歡迎區塊以外的其他內容。一般來說我們把App主程式放在content-main當中。

前面說過,這個Home.html是app的進入點,是在Manifest當中設置的,當然,你也可以在專案中新增一個或多個HTML Page,我會建議你直接在Visual Studio當中複製Home.html即可:

clip_image008當然,複製完後你要修改一下檔名(例如Page2.html)。接著,我們來修改一下Home.html的內容:

clip_image010

我們加上了一行程式碼:

<button onclick="javascript: location.href = 'Page2.html';">跳到Page2</button>

你會發現,主頁面上多了一個Button,並且,點選之後會跳到另一個Page2.html:

clip_image002[6]

這沒什麼,就是網頁的基本行為,對於常寫JavaScript的開發人員來,就是家常便飯一般。

不過,從這邊你就看的出來,我們的Task Pane Add-Ins,基本上就是一個Html page,而上面可以運行JavaScript,必要時我們可以撰寫多個html,透過JavaScript在多個頁面中切換。

Home.js

緊接著,我們來看看Home.js這個檔案,從Home.html的頁面上我們發現,這是該Page所引入的唯一一個直屬javascript file(另外兩個是office.js與App.js)。

該檔案內容如下:

clip_image004[7]

沒了,就這樣,簡單到不行。

其中第一個部分,是在Document Ready時,做一個hook按鈕『get-data-from-selection』的Click事件的動作:

當按鈕『get-data-from-selection』被按下,則getDataFromSelection會被觸發。

而getDataFromSelection的內容是:

留意getSelectedDataAsync這個Method,它是由Office.context.document.而來的,依照慣例,微軟掛上『Async』字尾的Method,都是非同步的API,因此一定會有一個call back函式,上面例子中,就是後面的這一段function:

function (result) {
    if (result.status === Office.AsyncResultStatus.Succeeded) {
        app.showNotification('選取的文字為:', '"' + result.value + '"');
    } else {
        app.showNotification('錯誤:', result.error.message);
    }
}

getSelectedDataAsync這個Method,是取得當前文件上的被選取區段,取得之後,就調用上面這個function,將取得的物件以result這個名稱傳入,傳入後,可由result.status判斷剛才的動作是否成功,如果成功,則把取得的用戶選取區域的內容(result.value)顯示出來:

app.showNotification('選取的文字為:', '"' + result.value + '"')

程式碼就這樣而已,很單純。

上面的『app.showNotification』,是在add-in的下方呈現出一個訊息顯示區塊,有這區塊可用,是app.js的功勞:

clip_image002[8]

1-1-1 App.js

如果我們開啟App.js,不難發現其中的內容,就是幫我們建構一塊訊息顯示區域,並提供API讓我們可以藉以調用,顯示出訊息:

clip_image004[9]

上面這一段code中的app.initialize,則是在home.js當中被叫用的:

clip_image006[6]

到這邊為止,您應該已經很瞭解整個Task Pane Add-Ins的架構了,後面,我們要來看幾個常見的API。