Wednesday, July 17, 2013

聊聊Service吧!

聊聊Service吧!

我們在一個大型系統裡面,常常會用到許多Service,這些Service通常來講會以Singleton的形式存在(或者他的變形,比方說在Spring裡面就是@Autowire)。這些系統用的Service通常會有一個共通的界面,比方說,這些Service都是CRUD服務的話,大概會長這樣…

interface IService {
    String getServiceName();
    String getServiceID();
}

interface ICRUDService<Target> extends IService {
    long create(Target t) throw ServiceException;
    T read(long uuid) throw ServiceException;
    long update(Target t) throw ServiceException;
    bool delete(long uuid) throw ServiceException;
}

淺顯易懂沒有任何問題。假設我們有一個Service叫做NameCard service,我們可能就會這樣實作…

public class NameCardService implements ICRUDService<NameCard> {
    // ICRUDService implementation is here
    …
    …
    …

    //NameCardService only public method
    public void sort();
    public List<NameCard> getDuplicated();
}

當我們需要取用這個服務的時候,有很多種不同的做法。最普通的Singleton風格就是

NameCardService service = NameCardService.getInst();

而Spring風格則會是

@Autowire
NameCardService service;

而Android風格則會是

NameCardService service = (NameCardService)this.getSystemService(NAME_CARD_SERVICE);

前兩者其實都沒什麼型別安全上的問題,第一個做法會有Scaling上的問題,我們必須要在getInst()裡面檢查太多東西(別忘了,getInst()是static,沒辦法被包進interface -- 這點顯然obj-c是好一點),比方說有沒有權限拿取啊,該做哪些logging阿....balabalaba,這些通通都是duplicated code -- 因為他們沒辦法被寫入interface。第二種作法則是綁死在Spring,而且我想應該不用我說,大家也知道Spring新增一個服務並沒有那麼簡單直觀。

所以Android選擇了3,但是很明顯地,這邊存在一個無法在編譯期檢查出來的型別問題,也就是說,你可能無法阻止別人寫成這樣

NameCardService service = (NameCardService)this.getSystemService(TELEPHONY_SERVICE);

因為它getSystemService傳回來的是一個類似IService的東西,這等於要強迫使用者使用「正確的Context」「正確的Service Handle」去轉型成「正確的Service」

以一個寫Framework的人來講,這真是惡夢 - 誰曉得Framework user這些工程師會不會拿一些莫名其妙的錯用來當作bug開給你?

//以下當然會執行期爆掉
((NameCardService)this.getSystemService(TELEPHONY_SERVICE)).sort();

//我必須對某些喜歡這種懶人寫法的人致敬,這些人專注於寫出讓人(包含自己)看不懂的code的努力,實在是令人太欽佩了。

重點在於Java來講,用任何一個Native型別去當作Handle都是一種頗令人困惑而且不智的行為。什麼是Handle?其實就相當於map裡面的key,用這個key去取得value(在這裡就是指Service)。有興趣的人可以看看Android的getSystemService(...)裡面傳入的參數是什麼?結論會很讓你噴飯,他傳入的Handle索引居然是一個字串…不過這有他的道理,他傳入的字串是一組class的qualified name,這可以讓他很輕易地「無中生有」出一組service -- 我們不在這裡討論這種規格外的做法。另外,傳入string還有一個非常糟糕的用途--它可以把要傳給service的參數包在一部分的String傳進去,很方便,很好用,我們也可以想像這是維護上的多大的災難。

有沒有什麼方法至少可以逼使用者不用轉型,甚至在編輯器裡面就可以輕鬆地幫你指出你拿到的是什麼Service而不會出錯呢?比方說我們能不能在編譯時期就檢查出下面這寫法對不對

//拿出來就是NameCardService,不需要轉型
BaseCRUDService.getService(???????????).sort();
//拿出來就是Telephony Service,不需要轉型
BaseCRUDService.getService(???????????).getTeleponyStatus();

甚至Eclipse就可以幫你檢查出來了!這乾五可能!?

廢話,當然可以,不然我寫這篇幹嘛 XD

這秘密在於我們Handle的選擇。String?這顯然不對,long?這當然也不行,特殊規格的class?我沒想到要怎麼用這種方法,也許誰來發明一下

我們Handler選用的是Java Generic可以直接支援的Class<?>

老實講我一直覺得Java Generic雖然靈活度不比C++的Template,不過Generic裡面的"?"實在是一個很無賴的東西,它可以做出很多平常根本想當想不到的方法。

首先,我們為了讓所有的CRUD Service都會「註冊自己」,讓自己可以被CURDService找到,所以我們必須要有一個「所有的CRUDService都會跑得到的地方」--我們當然第一個想到的就是constructor,所以我們要讓所有的CRUD Service從implements ICRUDService改成extend BaseCRUDService

abstract class BaseCRUDService<T> implements ICRUDService<T> {
    BaseCRUDService() {
    //現在我們有一個地方可以讓所有的CRUD Service都跑到了
    }
}

public class NameCardService extends BaseCRUDService<NameCard> {
…
…
…
}

這邊有一份實作,可以先給大家參考一下,出來差不多就是我剛剛講的結果。所有的Service只要繼承BaseCRUDService,就可以用BaseCRUDService.getService(Class<? extends BaseCRUDService> clz);拿出正確的Service,而且可以直接編譯期判斷出該型別,並且可以用編譯器的自動完成去呼叫該Service獨有的method。

import java.util.HashMap;

public abstract class BaseCRUDService<MarshelBean extends ICRUDBean> implements ICRUDService<MarshelBean> {

    protected BaseCRUDService() {
        registerSelf(this);
    }

    @SuppressWarnings({ "unchecked" })
    private <T extends ICRUDService<?>> void registerSelf(T service) {
        serviceMap.put( (Class<? extends ICRUDService<?>>) service.getClass(), service);
    }

    /**
     * For thread safety, it can be ConcurrentHashMap...as long as I don't think it is needed.
     * Since it is single thread write one time only, HashMap can be good enough in any case.
     */
    static HashMap<Class<? extends ICRUDService<?>>, ICRUDService<?>> serviceMap = new HashMap<Class<? extends ICRUDService<?>>, ICRUDService<?>>();

    @SuppressWarnings("unchecked")
    static public <T extends ICRUDService<?> > T getService(Class<T> targetService) {
        return (T) serviceMap.get(targetService);
    }

    public abstract <T extends ICRUDService<?>, F extends ICRUDBean> T handle(F targetBean);
}

這份實作神奇的地方在於,只要任何服務繼承它,那它就可以被「型別安全的」取出來。這點很重要,請跟著我重複念兩次。另外,這份實作是取自於我自己寫的一個PP通訊界面,所有CRUD都會至少有一個Marshel(管理)的對象,這個被管理的對象一定會繼承自IBean,如果你不知道他是幹嘛的,請完全不用理他。

class PeopleRecordService extends BaseCRUDService<PeopleRecord&gt {
…
…
…

    void people_record_only_function();
}

class CarRecordService extends  BaseCRUDService<CarRecord&gt {
…
…
…
    void car_record_only_function();
}

好,我們把這個東西setup了,該試試看他的威力了。

//PASS!
BaseCRUDService.getService(PeopleRecordService.class).people_record_only_function();

//Compile Time error!
BaseCRUDService.getService(PeopleRecordService.class).car_record_only_function();

//或著試試看下面這邊自動完成會幫你秀出什麼東西?
BaseCRUDService.getService(CarRecordService.class).;

That's it~ 沒有強制轉型,沒有奇怪的Context問題,更沒有duplicated static method,世界真是美好...

Wednesday, January 09, 2013

Singleton的愛恨情仇

今天剛好在某討論版看到正在討論的Singleton。這東西其實我個人是挺皺眉頭的,除非非常有必要,而且為數甚少的兩個前提下,不然這東西我是把它當作全域變數來看。某些地方來講,這比全域變數還糟糕... 不過通常在受控的場合下,這東西其實也是還有一定的可用性。 有人建議乾脆用Context pattern,不過我個人是覺得Context Pattern...某些方面來講其實不見得比Singleton好,甚至用錯的場合比Singleton還糟糕 :/ -- (stub, to be continue)

Monday, November 05, 2012

Java System Call problem -- in Linux

我們在Java裡面要做execution external command的時候通常會用到Runtime.getRuntime().exec("<你要執行的外部指令>"); 這個算是比較常見的做法,但是他有一些隱含的問題

Process p = Runtime.getRuntime().exec("ps -al");
BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream()));
//然後讀取InputStream做分析…

這是通解,不過這有個很大的問題。事實上, Runtime.getRuntime().exec在Linux實作上似乎是採取fork()的方式。眾所皆知的是,fork的實作方法是duplicate parent PID的資源。這在小規模下其實不成問題,問題在於…當java memory佔用量很大的時候…

比方說,以我們這個case,2G。所以每call一次system call都會造成系統嚴重occupied。

目前我們是採用jni的方式,把system call轉移到c的部分去做。因為它是一個獨立的pid,所以不會因此而產生巨大的paging(這拷貝動作的paging實在很驚人)。這算是在java底下system call的小小(?)陷阱...

Sunday, May 27, 2012

Bohemian like you

昨天聽演奏會聽到一首老歌,叫做"Bohemian",馬上就讓我想到這首歌了 =P 這首歌我記得最早最早以前我剛聽到的時候,把它看成了"Behemoth like you"「如你一般的末日巨獸(或者貝希摩斯,如果你是個FF迷的話)」



原來某人體內的怪獸已經那麼大了(遠目)

我非常喜歡這首歌清淡平淡的味道。Why can't I just simply like you?





Bohemian like you
Dandy Warhols

You got a great car. 
Yeah, what's wrong with it today? 
I used to have one too, 
Maybe I'll come and have a look. 
I really love your hairdo, yeah. 
I'm glad you like mine too, 
See we're looking pretty cool. 
Getcha! 

So what do you do? 
Oh yeah, I wait tables too. 
No I haven't heard your band 
Cause you guys are pretty new. 
But if you dig on Vegan food. 
Well come over to my work 
I'll have them cook you something that you'll really love. 

Cause I like you, 
Yeah I like you. 
And I'm feeling so Bohemian like you, 
Yeah I like you, 
Yeah I like you, 
And I feel wahoo, wahoo, wahoo! 

Wait. Who's that guy just hanging at your pad? 
He's lookin' kinda bummed. 
Yeah you broke up that's too bad. 
I guess it's fair if he always pays the rent 
And he doesn't get all bent 
About sleepin' on the couch when I'm there. 

Cause I like you, 
Yeah I like you. 
And I'm feeling so Bohemian like you. 
Yeah I like you. 
Yeah I like you 
And I feel wahoo, wahoo, wahoo! 

I'm getting wise 
And I feel so bohemian like you. 
It's you that I want so please, 
Just a casual, casual easy thing. 
Is it? It is for me 

And I like you 
Yeah I like you 
And I like you, I like you, I like you, 
Yeah I like you. 
And I feel wahoo, wahoo, wahoo!

Sunday, May 20, 2012

Epicurean


其實我對很多美食主義者有意見。或者說,自稱是美食主義者的人有意見。

我尤其難以理解為什麼某些自稱美食主義者的會說「因為吃過了XXX店的OOO,所以其他的OOO我再也難以下嚥了」,這種跟許多壓根不懂咖啡的人硬說「市面上大多數咖啡簡直是黑苦的液體,根本不能喝」在自稱是咖啡鑑賞大師的人一樣等級。

先不談這些老兄大姐到底懂不懂咖啡,舌頭夠不夠天賦靈敏。事實上,假設真的是一個好的美食主義者,擁有如此有天賦的味蕾,那為什麼僅能嚐得出別人的缺點,卻無法分辨出它口中「不入流的貨色」有哪些特點,有哪些是別人沒有的味道呢?我不否認,你要我說屎有什麼別人沒有的香氣,我真的說不出口,不過要是有人覺得世界上如此多的廚坊(請注意,還沒夠格到稱為廚房),都只能作出這些人口中的屎,只有最高級的米其林餐廳做出來的鵝肝醬,這些人才能描述他有多香氣澎湃多美味...

我覺得這些人還是不要自稱美食家比較好,這種人我一般稱之為滿口屎味。

咖啡也是。我曾經喝過初學者手沖出過熱焦氣滿口的單品,也喝過真正的高手火候十足,賽風下水剛剛好時間的醇品。每滴咖啡,都有一定的特殊香氣,都有其他人做不出來的神奇口感。

噢我還不敢自稱咖啡達人,只是味覺靈敏了點。

Epicurean還有一個意思,就是享樂主義者。有篇很有趣的文章,我曾經分享給大家過。看看,是不是跟美食主義果然是該共用同一個單字呢? :)

同學,你其實可以不用周遊世界

可以不用去追求別人描述的所有人性解脫,再回來說台北空氣多汙濁,生活多繁忙,多世俗。當連路邊小店的特殊風味都沒辦法品味,當連附近的小山小水都沒有仔細觀察,那,你有什麼自信那麼可以自稱一個Epicurean? 還是只是一種自以為Epicurean的自High以及自以為的超脫感?

Tuesday, May 15, 2012

Roxette - She doesn't live here anymore

http://www.youtube.com/watch?v=LNcEQOVumYA


She Doesn't Live Here Anymore

Lyrics:Per Gessle Music:Per Gessle&Mats M.P.Persson

We grew up together, we've been here forever.
Barefoot in the summer, cold in stormy weather.
She taught me all there is, like magic and love,
lots of forgotten words.

But now, she doesn't live here anymore.
She's off and she's gone, she doesn't live here anymore.
I'm sorry that's all there is to it,
I'm sorry that's all.

She dressed just like a painter,
a female street Picasso and I,
I will remember the heaven in her laughter.

And the scent of her sweet perfume
when we made love under an August moon

But now, she doesn't live here anymore.
She's off and she's gone, she doesn't live here anymore.
I'm sorry that's all there is to it,
I'm sorry that's all.

But now, she doesn't live here anymore.
She's off and she's gone, she doesn't live here anymore
She's gone since long, so long, she doesn't live here anymore.
She took off and left home, she doesn't live here anymore.
I'm sorry that's all there is to it, I'm sorry that's all.
That's all.









我們曾經在一起成長,我們曾經決定永遠在這裡

夏天赤腳踩在沙灘上,冬天擁抱在暴風雪中

她教會了我一切,如魔幻般的愛情,無法用言語表達的感覺
但是,她再也不在這裡了
她就這樣像是消失在大氣一班,再也不在這裡了
我覺得很抱歉,真的,我非常的遺憾

她曾經穿得像個油漆工,跟我像是街頭藝人般的手拉著手
我永遠會記得她有如天堂般的笑容
我似乎想起了她熟悉的香氣 想起了我們在八月的滿月下纏綿

但是,她再也不在這裡了
她就如同大氣一樣的消失了,再也不存在於這裡
我感到很抱歉,我真的很遺憾。

她再也不在這裡了
她如同熄滅的燭火一樣消失在這裡
她已經好久,好久,再也沒有回到這裡
她就這樣捲起了衣衫,如同出遠門一樣,離開了這裡
我對她很抱歉,我很遺憾,就這樣吧。

Thursday, March 15, 2012

Java socket程式 -- 絕對避免println() / PrintStream!

我先簡要的說一下原因,internet protocol用來分隔的是CRLF(\r\n) 以及CRLFCRLF(後者用以區隔header跟content)。PrintWriter的println(以及所有stream family的println, 比方說PrintStream)所傳出的分隔都是依系統而變(也就是,很不幸,這個函數並不是跨平台)

珍惜生命 遠離println / printstream(全文完)

--------
基本上Client來說,看到這裡應該就夠了,下面是比較詳細的說明。換行(也就是println會幫你做的事情)會幫你插入一個換行符號,而這個換行符號在各個系統都不一樣,所以基本上各個系統輸出都不同。比方說Windows就是CRLF(難得反而windows是正統的) 而unix系的含OSX絕大多數都是CR。

這是java少數幾個無法跨平台的地方,你可以想像一下同樣一串碼

out.println("This is SPARTA!");

其實在Windows底下是輸出
This is SPARTA!\r\n
而Unix系卻是
This is SPARTA!\r

這個println如果是輸出到螢幕上的話,沒差,大家看起來都一樣
阿輸出到socket就有差了,後者readline會根本讀不到

那當然照慣例就要問了:那要怎麼辦?
簡單,out.print("This is SPARTA!\r\n");

當然,也別忘了out.flush();

請銘記在心,寫Java Socket程式絕對要遠離任何println/printstream,看到他請當瘟神一樣離開他

ok,接下來談談server對應的部分。事實上當你寫server的時候,很多時候你是沒得選擇的。根據我自己的經驗,會犯下這種錯誤的coder其實比率是相當高的,只是絕大多數都是在windows下作業跟跑server,有時候是一些3rd party,server來講你很難做些什麼。畢竟code不是你寫的,你也沒source code,你更沒辦法把寫client的人抓過來狗幹一頓(我個人是稱為「友善的爭論」XD)

既然改不了client,那我們只好改server,讓server吃得下這些錯誤的東西

在談讓server怎麼吃這些東西而不至於拉肚子以前,我先講個瘟腥小故事。

IE7以前的世界,事實上browser是非常「友善」的,友善到一些明明是錯的東西他照樣吃。比方說有<p>沒</p>,或者<body><p> ....... </body></p>照吃不誤。這樣其實大家寫code都很開心,問題是每個browser都用不一樣的方法去解譯它,所以造成了同一組code的效果不見得一樣。

這造成了html界的大混亂,也間接導致了嚴格規範的xhtml誕生。(不過這東西始終沒有來的及正式長大,就被html 5.0取代了)

請注意,我們現在做的事情就跟以前「友善的」server一樣,是不對的,但是這是一種沒辦法中的辦法

說穿了很簡單,我們改用一個字元一個字元的去讀它(沒buffer想必這會很耗力)。我們讀取到\r的時候,我們會打開一個flag, 然後將\r後面多寫一個\n,下一個如果是\n的時候我們就把flag關閉並且跳過這個\n,不是的話關閉flag繼續。

具體的code大約是這樣(我個人是用filterinputstream處理這種東西)

boolean LRFlag = false;
while(char input = in.read()) {
if(input == '\r') {
LRFlag = true;
out.write(input);
out.write('\n');
continue;
}

if(input == '\n' && flag == true) {
flag = false;
continue;
}

flag = false;
out.write(input);
}


繼承fliterinputstream在read裡面插入這一段(也有一種寫法是override skip(),方法大同小異,我是比較喜歡override skip()就是,不過這比較少為人知)

hope it helps