重回美國心得 – 金融業

離開美國五年再回來,金融業變化還真大啊。

GE Capital 的零售銀行被分割出來,變成 Synchrony Bank , GE 要把主力放在投資銀行的事業上。

Intuit 要把 30 年前開發,到目前都還是業界獨佔的 Quicken 賣掉,不再繼續開發,要把主力放在中小企業用的 Quickbook 跟 SaaS 的 Mint.com 上面。

FinTech 開了許多家,Personal Capital, Betterment WealthFront, Acorns ,雖然說整體的產品仍是比不上直接買 Vanguard Target Retirement Fund 的投資報酬率高,但是方便及充滿資訊的 UI ,讓許多年輕一代逃過收取 2% 手續費的傳統理專的魔掌。

兩年前就有聽過這些東西,不過直到回到美國,把自己的帳戶再調整一下,才發現變化還真大。

智慧電表

美國的電力公司真誇張,五年內進化到了讓使用者知道每小時的用電量,然後已經可以按照不同時段來收電費。

pg&e

反觀台灣,台電領了經濟部一堆補助,說是要在 2015 年完成安裝智慧電表到百萬戶,結果只有安裝一萬戶,然後就用成本不符不繼續執行計劃,反正國營企業,全民買單就好

http://news.ltn.com.tw/news/focus/paper/880231

關帳戶

啊,我的修養還是不夠,沒想到在台灣關個銀行戶頭,都要叫經理出來,還不能解決 Orz….

我有一間自己的公司,因為要長期出國,要把公司跟公司戶關掉,打去銀行問要帶什麼文件。

結果今天去,才跟我說缺公司登記文件。

回家,下午再來,又跟我說,要關甲存要連未用完的支票都帶來。

然後我就爆氣了,要行員幫我寫一樣樣下,到底關帳號要那些文件才可以關….

然後又被台灣的金融制度上了一堂課,原來關甲存帳戶,過去未用完的支票全部都要帶來,要不然一張要罰 240元跟簽切結書。


話說我三年前,人在台灣,還透過 email & skype out 開了 Wells Fargo 的戶頭,後來要關時,也是兩封 email 就關掉了,不用負責人親自到、不用把 debit card 寄回去、不用把支票簿寄回去


我可以理解,為什麼中華民國會要求支票要還回去,因為過去台灣芭樂票盛行,而中華民國又是個 nanny state ,什麼事都要政府負責,所以連個支付工具都要上層層的鎖。

在美國,個人支票就是沒什麼保障,要保障,請去買銀行本票 cashier check 。我當年要賣車時,就是跟買主約在銀行裡面,請他當場買 cashier check 給我,我再存進戶頭,而他把車子跟車籍資料帶走,當下就結清交易。

至於為什麼要用 cashier check 還有一個原因,因為在美國支票存進戶頭,帳戶上暫時會顯示這一筆進帳,但是還是要三到五天才能結清,所以既使存進去還是可能會跳票。

而當下結清,是商業運作上很重要的,因為當下就可以把風險減到零。反而在台灣,沒有這種安全、有紀錄、一手交錢一手交貨的運作的機制。

在台灣買車、買結婚戒指,還是要先到銀行轉帳給賣主的個人戶頭,才能領貨。非常沒保障。


最後,講到影響最大的,其實是台灣商業跟生活的雙軌制太嚴重。

我在美國只有生活七年,個人的金融交易方式,就是公司的金融交易方式,並沒有什麼太大的差易,都可以用經驗、常理判斷。

而在台灣,我是一個商學院畢業的學生,我自認對台灣社會運行的方式在同儕間算是不錯的了,念過會計,幫忙 “手工” 過帳過,上週才把我的公司法、商事法課本給丟掉,但是在過去幾年間,被台灣的會計及金融制度還是撞上許多的問題。

無法把生活經驗推演到商業上的運作,我想是台灣在培養商業人材上,很大的問題。

遺失停車代幣的解決方案

停車把代幣給搞丟,結果意外的上了兩課。

原來停車場收費機,往四個角點兩下,可以叫出隱藏的控制面板,輸入密碼後,繳當日最高停車費及罰金後,會掉一個不一樣顏色的代幣出來,可以讓你直接出場。

第二課是,我拿到的代幣壞了(運氣真好),停車場管理員,雖然下班了,還是可以透過手機,直接看監視器跟操作停車桿,直接放我出場。

這次我運氣不錯,兩年前,代幣被嘟嘟房的機器吃掉,繳完錢不退我代幣,打過去是 0800 中心接的,一點忙都幫不上,還好機器過了五分鐘後,良心發現,退給我。

不知道這系統是誰做的,把例外處理做的這麼好,前前後後才擔誤我十分鐘,我還以為要等收費員從家裡趕來,還是說隔天早上才能取車。

Type Inference 在實務上碰上的問題

最近在讀 Implementing Domain-Driven Design 這本書,學習一下別人
是怎麼整理好整個大型軟的架構,並且重新檢視一下我們公司的程式碼的問題。

在 DDD 的概念中,一個模組 Domain / Bounded Context 應該是一個獨立的觀念,一個大型程式,應該是由許多 Bounded Context 組合而成,整個程式的架構應該僅量保持這些的純淨性,Bounded Context 的互用,要把上下行的關係定義清楚,程式碼間不應該在一個區塊中引入多個 Bounded Context

在整實作上,如何合檢視一個 class 是否有妥善的處理引用入的 Domain ,最簡單也最好用的方式就是看檔頭 import 的地方

import com.codahale.metrics.Meter

import org.apache.commons.lang.StringUtils
import org.jboss.netty.handler.timeout.TimeoutException
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.yunglinho.common.JsonSerializer

import com.yunglinho.app.core.model.Location
import com.yunglinho.app.feature.SocialProfile
import com.yunglinho.app.service.LocationService

import scala.concurrent.Await
import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import scala.concurrent.duration._

過去我排 import 的慣用法是按 有授權疑慮 的順序去排

  • com.* 的在最上面
  • org, net 排第二
  • 接著是排自己公司的東西,按 common, base, app 的順序去排,common 跟 based 是放 utilities & framework , app 就是放一個專案的東西了
  • 最後scala & java 的東西

這樣排的好處是, code review 一眼看過去,就可以知道用了那些版權有疑慮的東西,而且 import 時,不會把 java & scala 內建的東西蓋過去,同時也知道這支程式碼用了多少其它的東西

然而,這個機制,在 Scala’s Type Inference 的影響下,變的很不可靠。

Type Inference 是初學 Scala 時,覺得很方便的一個功能,能夠在寫程式時,少打很多的字,例如:

   // 在沒有 Type Inference 下, statement 的左右兩邊都要宣告型態
   val location: Location = new Location("Taipei")

   // 左邊的這邊其實是可以省下的
   val location = new Location("Taipei")

   // 左右兩邊都沒有 *Location* 的存在,但是卻是被引入在這邊了 
   val location = locationService.lookup("Taipei")

這看似方便的功能,卻有著非常深遠且負面的影響,在上面的例子中 Location 這類別,雖然在 import 時沒有出現過,但是卻是在 Reviewer 意料之外中,被帶入到了這程式碼中。

當然,在上面的例子中,Location & LocationService 是一套的觀念,所以沒有真的引入意料之外的 Domain 進來,但是如果今天程式碼寫的不好,是有個 ApplicationContextHolder.getCalendarService 的話,那就不一樣了。

在一行行 code review 時,有可能會抓到這問題,但是如果你沒有空一行行的看呢?或者是你是在 refactor 數千行程式碼時,那要怎樣一行行的去看呢?

所以,設計程式語言真的是很困難,一個看似方便的功能,卻在意料之外的地方造成困擾,讓我最近有點想回去改用 Java 。當然 Scala 還是個很好的語言,能夠幫我增加兩三倍的生產力,不過,卻會讓團隊工作時造成困擾。

Metrics

歡迎各位來到首屆的 Java Community Conference ,我是何永琳,目前於飛向科技擔任工頭,所謂的工頭,就是做
大家做的事跟做大家不喜歡做的事,什麼是大家不喜歡做的事呢?那就是管上線機器的大大小小問題。

在我們開始之前,先岔題一下,大家都是工程師,(放someone on the internet is wrong)應該都有些大大小小的睡
眠問題,過去我一直以為我一天有睡到七小時,一直到去年穿戴式配備的興起,我買了個 Jawbone 來戴(放 Jawbone Up的圖片),
才知道我一天只睡六小時左右,中間的睡眠品質不佳。有了 Jawbone Up 天天計錄我的生活起居狀況,我才了解到自己真
正的狀況,才能著手改善問題。

上面的故事,就是今天講題的主軸 測量 / metrics

在軟體開發的過程中,程式的編寫,其實只在軟體生命週期的很小一塊,在經過數個禮拜至數週的開發及測試後,軟體就
被佈屬到線上環境中,在過去,可能是放在公司的機房中或是資料中心,現在則是被放到雲端平台之上,總之,你細心開
發的軟體,將要被真實世界開始摧殘。

在一個軟體的生命週期中,他可能會碰上大量的用戶擁入,也可能跑了數十天沒有重開,也有可能在臨晨三點碰上資料庫
備份,當然也有可能每兩個禮拜碰上其它整個的 REST API 不穩的狀況,在他跑了一兩年後,有可能被換到新的硬體上。

問題在於,你有沒有辦法知道這些狀況會不會出現,如果出現了,你能不能知道發生了什麼事;而且在這些狀況下,你的
應用程式會出什麼怪手?是全面性的慢下來、還是不可預期的某些 Thread 才慢些來,或者是,你的應用程式可以輕鬆
應付這些狀況,不用你擔心。

如果上線的系統不能應付這些狀況,你要做出什麼相對應的調整呢??

Metrics By Codahale

Metrics 一套是由 Codahale 在 2011 公布出來的 Opensource 專案,自此之後,因為他的 API 設計精良又容
易跟其它的外部程式整合,所以開始被業界所採用。

http://codahale.com/codeconf-2011-04-09-metrics-metrics-everywhere.pdf

為什麼我們需要測量的原因

手動調整

Iterative tuning -> 建立假設 -> 重建問題 -> 提出對應 -> 修理 -> 驗證

談台灣年輕人低薪問題

台灣這幾年商界、政界、很喜歡對年輕人指指點點,說只要努力就可以脫離 22K ,年輕人收入不好的問題根源是在年輕人上,所以在這邊寫一下我的看法。

我在美國念書畢業一年後,在 26 歲時,底薪收入就有八萬美金,後來我又幹了四年,回台灣又幹了四年,但我的收入連八年前的一半都沒有,所以說,是我能力退步了嗎?不對啊,我現在寫程式碼比以前快不知道多少倍,還帶一個團隊,怎麼可以說我退步了呢?

一個人所能領的薪水,是相對於他所能創造的產值,而一個人能創造的產值,則是基於他所在的團隊以及社會之上,我過去還是一兩年經驗的新人能領高薪,是因為我所處在的團隊、社會可以創造高價值,因此我所做的邊邊角角的工作,依附在團隊上仍是有價值的,公司就我當下及未來可能的貢獻,給我高的報酬。

所以說穿了,年輕人能領的薪水,很大的決定因素是在於這個社會能夠如何運用他們,這個社會的掌權者、這個社會的中監份子,能為年輕人創造怎樣的舞台,如果掌權者創造的社會有價值,能夠利用年輕人創造更多的價值,那麼年輕人自然能夠領高薪。

如過這個社會的掌權者,只在乎金錢遊戲,忽視產業,那麼年輕人沒有舞台,自然只能領低薪

TDD

總算是把 TDD 的戰文看完了,看完後我對 DHH 的評估仍是沒變。

DHH 在 Rework 中自述到,他偏好帶領一個三十人的團隊自在的工作,而不要把組織擴大到上百上千人把自己搞的焦頭濫額。 DHH 在組織架構跟應用範圍的偏好偏食,讓我在讀 DHH 文章時都會先考慮過他的出發點跟局限性。

以程式設計師的角色來說, DHH 的精兵政策當然看起來很爽,我自己也做過類似的選擇,捨棄 Java 而就 Scala ,追求更高的個人產值。

然而若是以一個架構師的角度來說,我的目標是成為像 Martin Fowler 的角色,相較於只有 36 個員工的 37 signals ,ThoughtWorks 是個有超過 2500 個員工的專業代工廠,在 ThoughtWorks 掛頭牌的 Martin Fowler 在技術的選用上,自然的考量要更全面,顧慮到不同應用領域的開發需求、以及不同程度的程式設計師該怎麼管理。

DHH 對 TDD 的批評我多數同意,尤其是 Testing / DI 對 Design 的入侵也是一直困擾我的問題,但是我是把 Testing 當成一個需求來看,所以是多一個需求造成的額外成本在困擾我,而不是降低可讀性等問題在困擾我。
在目前看不到較好的替代品之前,我還是會繼續用 TDD (w/o test first) 來做設計上的工法。

我目前對程式的想法是從資料做起,我做的系統,多數就是把輸入的資料,透過一串串的轉換,Map, Filter, Count, Sum, Aggregation 後,變成最後要的樣子,在A -> B -> C -> D 中間,可能會有一些中介型態的出現。

在我的設計中就會變成,在每一次的轉換的過程中,讓輸入輸出是可以被測試的,在設計階段就預先考量到要怎麼測試這些轉換。

至於會不會做 Unit Test, Test First, 我是看有多少時間做多少事的,但至少模組內的一串流程的測試是會去做的。

另外我會把 DAO & Data Transformer(Service) 分開來,資料轉換的運算,是可以不用靠 DB 來的資料直接做測試,這樣跑起來才會快。

許多的商業邏輯,我會在 DAO -> Service 上再多架一層 Controller ,這個 Controller 就會很醜,因為只有三(四)層,所以一個 Controller 可能換串個十來個 Service ,但是醜的就是在這裡面而以。

測試的時候,至少是可以分層把底層容易測的都測好是穩定不容易出錯的,到時後上線有問題時,就可以知道是在 Controller 把東西串起來時出錯了。

沒有 Test First 沒有 Unit Test ,但是會做 regression test, integration test 會在設計時預先留下容易稽核的點。這是不是 TDD??

原點科技介紹既2011年終回顧

原點科技成立於2010年十月,在過去的一年多間,本公司就為Location Based Service & Mobile開發及學習了許多技術。

Json WebService API with Jersey

身為SaaS的提供者,我們需要提供許多不同的WebService API給我們的客戶,在轉換內部的Java Object到外部的Json Object,我們選用的是Jersey這套 JAX-RS 標準的實作,以及Jackson這套 Json library。

這部份的成果,我們曾在2010年11月於 TWJUG 發表過,相關的投影片請見,另外關於Jackson的使用,我們也發表了幾篇部落格文章講解,怎麼在Scala上使用Jackson怎麼樣處理多型使用Jackson的小眉角,以及怎麼在Android上使用Jackson

對於 Jersey & Jackson 這一套 Json WebService framework,我們使用的經驗是很滿意,也大量的使用在我們的內外部系統間。

Search with Lucene

在 Lucene 之上,我們建立了一套,不同於Solr的 WebService 實作,透過我們自有的Search API,能夠對我們的Json文件庫做全文檢索及條件比對,並且可做翻頁、及選取部份欄位等運算。

Location Based(Spatial) Search

當我們開始做 LBS 時,一開始我們是始用 lucene-spatial 這套官方的函式庫來做 LBS ,但是,很快的,我們就了解到,用 Map Tile 來做 LBS 的問題,Map Tile的做法是,把地圖割成不同大小的區塊然後編號,然後,把這區塊內所有的座標,都標上一樣的 tag 值,例如 t16m2345 – t(tile_size)m(tile_id)。那麼,在做搜尋時,我們只要把現在的座標位址的tile id算出來,在去找有相同tile id的點就好。

這種做法的問題是,找出來的點,並沒有依距離排序;當 Lucene 對選出來的文件做 scoring 時,因為同一個區塊內的點都具有一樣的tile_id,所以,他們的分數都是一樣的,並不會依距離的不同,而有不同的分數。

另外,我們也發現,多數的搜尋技術,在處理地理位址時,只處理地點的資料,例如餐廳、百貨公司等小地理區塊等,但是這些地點搜詢技術,並不適合用在處理大區塊範圍的資料,如行政區及學區等。

如多數外國網站用的坐標轉行政區的GeoNames,他在搜尋某個座標目前所屬的行政區時,是計算座標點與附近行政區中心的距離來猜測,但是行政區往往不是正圓型的,行政區的劃分多是依自然環境(如河川)來切割的,所以,既始某個座標離行政區甲較近,但是,在實務上則是被劃分到乙行政區去的;類似的例子還有學區等

另一類無法用點去描述的,是線型的資料,如登山步道、腳踏車道,當我們登山時,我們不一定要從起點開始爬,而是可以從中間點開始加入,因此,當我們在找附近所有的登山步道時,該是計算所有線型資料與目前座標點的最短距離,而非是算起點與目前座標點的距離。

因此,就這兩個問題,我們實作了對地理區塊搜尋的功能,讓用戶能對點(Point)、線(LineString),多邊型(Polygon)等資料,做搜尋,並對尋結果依距離來排序。

就這功能,我們發表了搜詢座標所在行政區台北水災地圖

Search Analytics

講到搜尋,當然也不得不提用戶行為分析(Analytics),我們在我們的Search API中,內建了記錄使用者行為的功能,當一個使用者,打開程式送出第一個搜尋、翻到第二頁、點選了第三個連結、再做另一個搜尋、選了第一個結果、離開,這一整串的行為,都會被我們的後端自動記錄下來。

透過我們自己開發的 Mobile Analytics 技術,我們可以就這些使用者行為,提供底下的分析報告

  • Client Profiles
    • number of clients by device family
    • number of clients by device.
    • number of active clients by device family.
    • number of active clients by hour/day/week/month
    • the location of clients.
    • the location of active clients by hour/day/week/month.
    • average usage frequency(how often clients use your app per week)
  • Visit Trends
    • number of request of the hour/day/week/month
    • unique visits of the hour/day/week/month
    • average length of visits.
    • new vs return clients on the hour/day/week/month.
  • Search Usage
    • top search keywords of the hour/day/week/month.
    • top search keywords by location/country
    • the location of search usage of the hour/day/week
    • average search usage per visit.
    • top-exit search.
  • Content Usage:
    • popular contents.
    • popular result document ids.
    • top-exit result.

大量運算

在過去的一年多間,我們花了很多的心力在學習雲端相關技術,評估什麼技術適合我們,什麼不適合我們,像在做Analytics時,我們選用了MySQL而不是熱門的Hadoop

因為我們有群組軟體的需求,需要知道,在Cluster中,那幾台機器是負責處理 Domain X 的資料;我們嘗試了使用 jgroups 及 zookeper 來實作 service registry/discovery 的功能,這經驗,也在CloudTW發表過,投影片在此

為了在我們的 indexing 中使用 Producer and Consumer Pattern & durable message box,我們也投入了許多時間去學習Akka, Actor Model, Amazon SQS, ActiveMQ, Apache Camel,Akka的成果,我們也在 COSCUP 發表過Video on YouTube

社群服務

在我們專注於軟體開發的同時,原點科技也不忘回饋社會及社群,原點科技的創辦人加入多個社群,並多次主講議題,包括

  • TWJUG: Introduction to Tapestry
  • CloudTW: Groupware – JGroups and Zookeeper.
  • Coscup: Introduction to Actor Model and Akka
  • OpenData: 系列講座 #2 – 第一次爬資料就上手?! 碰壁!

在今年底,原點科技將協助Scala Taipei的運作

關於未來

在下一個年度,我們將會把心力放在推廣我們的產品Search Cloud之上,通過參展與競賽,增加我們的媒體知名度。另外一方面,我們也會開始接一些關於LBS, Analytics, Distributed System的外包案。

此外,我們也會把一部份的心力,放置在推廣 Scala 及 Akka 的企業應用,透過教育訓練、外包專案、雜誌文章及研討會的方式,讓台灣的軟體界對這兩者有更多的了解

New Feature: Support for Geometries.

A pair of latitude and longitude gives a quickstart for your location based application. However, not every single feature in a LBS application can be describe as a point. Let us take school district as an example. The School A may be closer to your house than School B is, but your house is belong to school district for school B. Another example is bike trails, bike trail is a line not a single point. This is why we introduce geometry support into SearchCloud.

In the GIS area, Geometry are used to describe shape of features. The geometry model in SearchCloud consists of single points, line strings, and polygons, multi-point, multi-line string, and multi-polygon collections, and geometry collections.

In the next section, we will walk through all the supported geometries and how to represent them.

Represent Geometry

There are few defeco standards for representing geometries.

  • WKT, well-known text, represents geometry in human readable text format
  • WKB, well-known text, represents geometry in machine readable binary format
  • GeoJSON represents geometry in json format.

In SearchCloud’s JSON API, we pick GeoJSON over WKT because it has better integration with other external services.

Supported Geometries

POINT consists of a coordinate pair in longitude, latitude order.

{ "type": "Point", "coordinates": [100.0, 0.0] }

LineString is made of an array of coordinate pairs.

{
  "type": "LineString",
  "coordinates": [ [100.0, 0.0], [101.0, 1.0] ]
}

Polygon is made of array of LineStrings. The first cicular linestring represent the exterior ring and the subsquent LineStrings represent the holes in the ring.

{
  "type": "Polygon",
  "coordinates": [
    [ [35, 10], [10, 20], [15, 40], [45, 45], [30, 10]]
  ]
}
{
  "type": "Polygon",
  "coordinates": [
    [ [35, 10], [10, 20], [15, 40], [45, 45], [30, 10]],
    [ [20, 30], [35, 35], [30, 20], [20, 30] ]
  ]
}

MultiPoint is made of an array of coordinate pairs.

{
  "type": "MultiPoint",
  "coordinates": [ [10, 40], [40, 30], [20, 20], [30, 10] ]
}

MultiLineString is made of an array of LineStrings.

{
  "type": "MultiLineString",
  "coordinates": [
    [ [10, 10], [20, 20], [30, 10] ],
    [ [40, 40], [30, 30], [40, 20], [30, 10] ]
  ]
}

MultiPolygon is made of an array of polygons.

{
  "type": "MultiPolygon",
  "coordinates": [
    [[[30 20], [10, 40], [35, 40], [30, 20]]],
    [[[15, 5], [40, 10], [10, 20], [ 5, 10], [15, 5]]]
  ]
}

GeometryCollection is made of an array of geometry objects described above.

{
  "type": "GeometryCollection",
  "geometries": [
    {
      "type": "Point",
      "coordinates": [100.0, 0.0]
    },
    {
      "type": "LineString",
      "coordinates": [ [100.0, 0.0], [101.0, 1.0] ]
    }
  ]
}

Define Geometry Field

A new GeometryFieldType is added to schema types.

{ "location"  : { "type": "geometry" }  }

Search for Geometry Field

The existing Location Query can be applied to geometry fields as well as location fields. The search results will be sorted by the distance between the requesting location and geometries described in the documents.