膨脹、驕傲,程序員轉項目經理的原罪

目錄

  • 一、前言:謙卑與狂妄
  • 二、尊重:遠與近
  • 三、原罪:膨脹與驕傲

作者簡介:妖生。 坐標合肥,一個普通程序員。十年職業生涯,8年編程。經歷了許多事,也有點想法,現在將它們變成文字,書於紙上。如果能幫助到你,那將不勝榮幸。

膨脹、驕傲,程序員轉項目經理的原罪

一、前言:謙卑與狂妄

突然就想到這麼個主題,回想我在2016下、2017上的時候,確實好像有那麼一段時間,有點膨脹,感覺自己無所不能,又太過驕傲,表面謙虛、內心傲氣,似乎誰都不放在眼裡。

做管理的沒我技術好,做技術的沒我會管理。

現在想想,真是可笑啊。

由己及彼,會不會也有不少做了三五年程序員,做了小組長,突然成為管理者,有了團隊里的一些話語權,然後膨脹的呢?

那麼我這樣去總結下,能不能給一些正在經歷這個階段的程序員們,甚至非程序員們的小夥伴們,予以一點點啟發呢?

嗯,晚上想就這個話題寫篇長文,回憶下我當年的情景,復盤當年,說一說我一些錯誤的做法,和之後怎樣警醒與走出來的。

二、尊重:遠與近

2.1 近之則不遜

2015年,金三大潮席捲而來,全國的稅務系統都要升級,我也有幸參與了這樣的歷史性進程。

我代表安徽的大集中征管稅務系統承建商,與金三征管中標商中軟公司進行系統對接。趕赴深圳南海,在國家稅務總局的信息中心進行征管數據的遷移工作。

在深圳南海的時候,我帶領的小組負責為數據遷移工作作數據校驗、數據確認、遷移腳本編寫等工作。

在這樣的時刻,安徽基地突然打電話過來,也是當時的項目經理——老趙,打電話對我說,要我負責金三本地保留系統的改造工作。

我當時一頭霧水,滿心不爽,不知道為什麼這樣的工作就落到了我的頭上。並且人在深圳,有心無力。所以說話有點沖,說我不知道怎麼做,直接就懟了幾句。

老趙忽然說了一句:“如果是XX你會這樣跟他說嗎?”XX是我們的項目總監。

我腦子里突然有一道電閃了一下。

是啊,如果是XX這樣跟我說,給我安排這樣的任務。我會這樣去懟嗎?
我是不是就默默接受了?

我突然想起來,2014年,我與老趙坐在一塊,因為某件事,我引用了孔子的一句話:“女子與小人難養也。遠之則怨,近之則不遜”。

老趙讚賞地說,很有道理。

可是這樣的話,我為何說了,卻不記得,做不到,無法知行合一呢?

我對老趙,就是近之則不遜了。

我沉默了大概十秒鐘,隨後對他說:“你放心,我肯定完成任務。沒有什麼完不成的。”
在轉變態度與思路后,我對怎麼解決金三本地保留的改造有了切實可行的思路與方法。

我在電話中將我的想法與老趙溝通后,老趙對我表示了讚賞,“就是要有這種舍我其誰的氣勢。除了你,沒人能搞定。”

這通電話很長,也在我以後的職業生涯中,對我做事做人的方法進行了一次很大的改造。

但是在這個故事里我卻還是有一點沒學到,也是我最近在反思的一個問題。

我為什麼對於別人,總是吝嗇於讚賞?

老趙在與我的相處中,一直對我進行鼓勵、讚賞,讓我有了一次次面對問題的自信,得到了極大的成長。

遇到這樣一個半師半友的領導,何其之幸也。

這段往事我總結了兩個點:

一是與上級相處時可以開玩笑、插科打諢,但是在工作上應該把上級當做上級,給予應有的尊重。

二是在與下級相處時,應該不吝嗇表揚,尤其在現在85后、90后當家的年代,表揚會比批評更能激發他們的工作激情,賦予他們超出當前能力的創造力。

2.2 遠之則怨

在2016年的時候,老趙罹患重疾,在長時間病情反覆、無法工作的情況下,拿了公司的補償金,主動離職。

誰來接替這個項目經理的位置?

當時其實是有三個人選,一個是運維經理Y,一個是技術經理H,還有一個就是我這個開發經理。

在H主動退出的情況下,我與Y開始競爭這個職位。

怎麼說呢?我其實一開始並不care這個項目經理的職位。我只想安安靜靜地做開發,不願意去做什麼項目管理。因為有權利的同時,意味着麻煩。

如果是技術經理H,也就是我在电子稅務局的老搭檔坐了項目經理這個職位,我是不想去競爭的。

但是現在是他退出,我倆競爭,而令我萬分不爽的是,在一開始,項目總監X便帶了濃烈的主觀傾向,他是屬意運維經理來坐這個職位的,這樣的傾向我後來的判斷是八二。

我直接去找了總監X,說出了這樣的話:“他何德何能,憑什麼做項目經理,拿什麼來領導我?”

我後來才知道,就在這個競爭的過程中,X找了項目組運維、開發的骨幹同事們,一一諮詢了對於項目經理這個職位人選的意見。

或許結果令他大失所望,90%的人都對他屬意的人選投了反對票。

真的是我牛逼?我人緣好嗎?我當時真的是這麼覺得。

後來細想,恐怕並不是如此。開發投我可以理解,為什麼運維的同事也投我呢?

我想,是因為這位Y先生不得手下人心吧。在對待客戶、對待上級方面,這位運維負責人把自己的工作幹得很出色,客戶關係維繫的很好。

但是在對待下屬方面,我則聽到了好幾次關於Y的抱怨。

一位同事說某個工作是他來解決的,處理的很出色,結果Y卻跟客戶說是Y來解決的,讓他心生怨懟。

另一位同事說他在處理某個線上的緊急問題,一時沒解決,結果總監X將Y調了過來,看着他處理,後來事情解決了,而Y在這個過程中什麼都沒幹。導致這位同事覺得 總監X 是不是太信任 Y 了,反而對 Y 敬謝不敏。

這兩位同事在16年已經成長為項目組的骨幹員工,也在這次競爭事件中投了 Y 的反對票。

有時候,其實不是你成功,而是對手打敗了自己。用現在流行的話來說,叫 同行襯托 吧。

我舉這兩個例子,是覺得這裏面有兩個點需要去反思和總結的。

一是遠之則怨,在對待下屬時如果為了保持上級的威嚴,而拉開了距離,則導致互相不了解,很容易產生芥蒂。

二是攬功諉過是領導者的大忌,其實團隊做好了事情就是你的功勞,至於是不是你個人解決的有什麼關係呢?作為領導者,個人成功,團隊失敗,其實都是失敗者。

在這裏面還有一點是我想提出的,就是在職場工作中,如果競爭的機會,要敢於競爭,競爭說明了你的決心。

我當時找 總監X 的談話中,還說了這樣一段話:

“我覺得如果我來選,我選自己做項目經理。”
“相比他,我肯定會做的更好。”
“如果他來帶這個團隊,那不用多久,這個團隊就要人心散了。”

是不是很狂妄?然而真的就是我內心的想法。你要麼空降一個項目經理過來,要麼就選我。

有時候在與上級的溝通中,沒必要遮掩自己內心的想法。當然,如果你的上級只愛聽阿諛諂媚的話,便保留意見,默默前行吧。

幸而,我覺得我一路遇到的都是貴人。從第一家公司的保總,到神碼的斌哥、蔡總、曹工、老趙、總監X,我覺得無比幸運,總是能遇到眼界比你高、願意提點你的老闆、導師、同事與朋友。

那麼回過頭來,我對 總監X 說的這番話到底有多大的影響呢?我相信在八二的傾向比重中,得到了兩分吧。再加上同事們的支持,最終是我勝出,成了【代】項目經理。

至於為什麼是【代】,呵呵,那便是另外一個故事了。

三、原罪:膨脹與驕傲

3.1 驕傲:無所不能與一天十會

在成為【代】項目經理后,我開始了馬不停蹄的開會生涯。最多的時候,一天十會,感覺什麼都要我來拍板。

2017年,政府開始提出“互聯網+”的概念,這便開始了我們開發項目爆發的一年。

智能辦公、大數據、移動APP、數據質量、統一門戶、短信平台、外部交換等等。

從需求到架構,全都是我來主導,業務人才的極度缺乏,導致我陷入了局方的會議大海。

後來有一位從別的區調過來幫助我的資深業務專家說了一句:

從技術人升上來的管理者可能都有一個毛病,凡事都要親力親為。何不放手讓下面的人乾乾呢?說不定干出的效果會超過你想象。

但是當時的我,並沒有把這話聽進去。

在前幾天看到雷軍的一篇文章,說為什麼小米手機干不過華為,甚至遠遜於OV呢?一是待遇問題,導致招不到行業最頂尖的人才;二是雷軍凡事親力親為,哪裡的業務不行了,就自己出山擼起袖子干。

很有感觸,何其相似。雷軍也是干技術出身的啊。同樣做技術出身,還是馬化騰夠瀟灑。

當然,我自然是比不了大佬的。可是反思下,我真的是這麼無所不能嗎?

或許只是我所在的位置,導致我接收的信息足夠多,而無法被下面的同事所取代?

或許並非我的能力太強,只是我的眼界、層次還不夠,還沒有做好一個管理者的轉變。

一個好的管理者,應該是大音希聲、大象無形,讓團隊在沒有你的情況下,也能按照預定計劃穩步前行。

3.2 膨脹:程序員的原罪

在無所不能的這一段時間中,因為開發項目的過多,還有大數據這樣新興技術的項目,從公司層面協調來了一位技術總監,也是我們安徽項目組以前出去的一位同事。暫且叫他 L 吧。

因為以前共在一個項目組,也算是半熟不生。而我並沒有充分意識到怎樣去尊敬這樣一位技術達人、前輩。

因為在這個公司,實行的是項目負責制,項目經理掌握着一票否決權。而我,這樣一位剛剛上任,新鮮熱乎着的【代】項目經理,開始膨脹了。

在這位 L 前輩研究spark + strom 解決實時倉庫問題時,我總是時不時打斷了他,喊他去參與大數據的需求會議、去解決APP的流量高峰問題、去參與統一門戶的架構設計。

誠然這一切,都應是技術總負責人應該去參與負責的事情。但是我在請求 L 去解決的時候,未免有些不夠尊重。

有時候,你外在不經意的一些表現其實正是你內心想法的一種體現。而人與人的相處,是奇妙而玄幻的。這種看不見、摸不着的氣氛是真的會影響彼此的心情與態度。

更何況,L 更是將技術人簡單、直接的性格放大到極致。

在某次赴地市的會議上,我與L一起討論確定了辦公系統與外部系統的對接與集成方式后,便借口項目組有事先走了,讓 L 留下參與後續與另一家公司的對接。

這件事直接導致 L 的不滿爆發,在幾天後的一次微信群的工作安排上直接懟了我,將我拉黑。

當然,這樣的過程都是我後來回憶、反思得出的結論,而我當時是懵逼的。

為什麼?會有這麼激烈的回應?我一臉懵逼地請求項目總監X的幫助,才讓 L 重新恢復參與了後續的工作。

我後來想想,這一切好像是偶然發生的,但卻也是必然會產生的,如果不是 L ,可能事情不會這麼激烈。然而其他沒有拉黑我的下屬和同事們,是不是其實心裏也在怨懟我呢?

在升上了項目經理之後,我是不是也開始遠離初心,“遠之則怨”了呢?

特別是在並非管理序列的上升通道中,突然從技術崗轉為了管理崗,從單純的管事變成了管人,有了財政、生殺大權,怎樣消去產生的膨脹與浮躁感呢?

有時候能力與眼界是位置帶來的,但是位置帶來的能力不是天生就來的,而是慢慢培養、逐漸形成的。

我回憶、反思這段往事的時候,覺得我在坐上項目經理的時候,缺失了一名老趙這樣的導師,想起他說的一句話:“扶上馬,再送一程”。

在很多公司,其實包括我當時所在的這家公司,也都有項目經理的培養與彙報機制,但是很多時候都淪為了背景色、走過程,是不是哪怕再忙,也該有這麼一課呢?

當然,有可能也並沒有什麼鳥用。因為紙上得來終覺淺,絕知此事要躬行。

我從這件事後,開始反思,重新整理人事,縮減彙報對象與被彙報對象。分離主要事務、次要事務。有意培養各開發組長的全棧能力。

當然,這一切其實都是不夠的,甚至在2017下2018上的時候,我開始怠惰,徹底放權,走入了另一個極端。

而在環境的演變下與艱難的掙扎中,我在2018下半年選擇重新投入了技術崗,但是在兩年已經極少碰代碼的情況下,我能重新拾起,並引領項目組前行嗎?

這是另一個故事了,但是最後想說的是:

沒做到技術總監、架構師,以後還想做技術,別去碰項目經理這個職位,繁雜瑣事,耗費精力,徒增煩惱。

嗯,這又可以寫篇文章了,下一篇就叫《技術人,別做項目經理,有毒!》,哈哈哈。

歡迎加入我的知識星球,掃一掃下方二維碼,目前免費哦。

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

Akka-CQRS(13)- SSL/TLS for gRPC and HTTPS:自簽名證書產生和使用

  到現在,我們已經完成了POS平台和前端的網絡集成。不過,還是那句話:平台系統的網絡安全是至關重要的。前一篇博客里我們嘗試實現了gRPC ssl/tls網絡連接,但測試時用的證書如何產生始終沒有搞清楚。現在akka-http開發的ws同樣面臨HTTPS的設置和使用問題。所以,特別抽出這篇博文討論一下数字證書的問題。

在正式的生產環境里数字證書應該是由第三方公證機構CA簽發的,我們需要向CA提出申請。数字證書的申請、簽發和驗證流程如下:

1) 服務⽅ S 向第三⽅方機構CA提交公鑰、組織信息、個⼈信息(域名)等資料提出認證申請 (不需要提供私鑰) 2) CA 通過各種手段驗證申請者所提供信息的真實性,如組織是否存在、 企業是否合法,是否擁有域名的所有權等 3) 如信息審核通過,CA 會向申請者簽發認證文件-證書。 證書包含以下信息:申請者公鑰、申請者的組織信息和個⼈信息、簽發機構 CA 信息、有效時間、證書序列號等信息的明⽂,同時包含一個簽名的產⽣生算法:首先,使用散列函數計算出證書中公開明文信息的信息摘要,然後, 採用 CA 的私鑰對信息摘要進⾏加密,這個密⽂就是簽名了 4) 客戶端 C 向服務器 S 發出請求時,S 返回證書文件 5) 客戶端 C 讀取證書中的相關的明⽂信息,采⽤相同的散列函數計算得到信息摘要, 然後,利用對應 CA 的公鑰解密簽名數據,對比證書的信息摘要,如果一致,則可以確認證書的合法性,即公鑰合法 6) 客戶端 C 然後檢驗證書相關的域名信息、有效時間等信息 7) 客戶端 C 應內置信任 CA 的證書信息(包含公鑰),如果 CA 不被信任,則找不到對應 CA 的證書,證書也會被判定非法 8) 內置 CA 對應的證書稱為根證書,頒發者和使⽤者相同,用 CA ⾃⼰的私鑰簽名,即⾃簽名證書(此證書中的公鑰即為 CA 的公鑰,可以使用這個公鑰對證書的簽名進行校驗,⽆需另外⼀份證書)

服務器端在通信中建立SSL加密渠道過程如下:

1)客戶端 C 發送請求到服務器端 S 2) 服務器端 S 返回證書和公開密鑰到 C,公開密鑰作為證書的一部分傳送 3)客戶端 C 檢驗證書和公開密鑰的有效性,如果有效,則⽣成共享密鑰並使⽤公開密鑰加密發送到服務器端 S 4) 服務器端 S 使⽤私有密鑰解密數據,並用收到的共享密鑰加密數據,發送到客戶端 C 5) 客戶端 C 使⽤用共享密鑰解密數據 6) SSL 加密通信渠道建立 ...

應該說,需要在客戶端進行認證的應用場景不多。這種情況需要在客戶端存放数字證書。像支付寶和一些銀行客戶端一般都需要安裝證書。

好了,還是回到如何產生自簽名證書示範吧。下面是一個標準的用openssl命令產生自簽名證書流程:

在產生證書和密鑰的過程中所有系統提問回答要一致。我們先假設密碼統一為:123456

1、生成根證書私鑰: rootCA.key:  openssl genrsa -des3 -out rootCA.key 2048 

2、根證書申請 rootCA.csr:openssl req -new -key rootCA.key -out rootCA.csr

3、用申請rootCA.csr生成根證書 rootCA.crt:openssl x509 -req -days 365 -sha256 -extensions v3_ca -signkey rootCA.key -in rootCA.csr -out rootCA.crt

4、pem根證書 rootCA.pem:openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.pem

5、創建⼀個v3.ext⽂件,目的是產生X509 v3證書,主要目的是指定subjectAltName選項:

  authorityKeyIdentifier=keyid,issuer basicConstraints=CA:FALSE keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment subjectAltName = @alt_names [alt_names] DNS.1 = localhost IP.1 = "192.168.11.189" IP.5 = "192.168.0.189" IP.2 = "132.232.229.60" IP.3 = "118.24.165.225" IP.4 = "129.28.108.238"

注意subjectAltName,這些都是可以信任的域名或地址。

6、構建證書密鑰 server.key:openssl req -new -sha256 -nodes -out server.csr -newkey rsa:2048 -keyout server.key

7、用根證書rootCA產生自簽證書 server.crt:openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.crt -days 500 -sha256 -extfile v3.ext

上面這個過程需要不斷重複回答同樣的問題,很煩。可以用配置文件來一次性產生:

先構建一個ssl.cnf文件:

  [req]
  prompt = no
  default_bits = 4096
  default_md = sha256
  distinguished_name = dn
  x509_extensions = v3_req
  [dn]
  C=CN
  ST=GuangDong
  L=ShenZhen
  O=Bayakala
  OU=POS
  CN=www.bayakala.com
  emailAddress=admin@localhost
  [v3_req]
  keyUsage=keyEncipherment, dataEncipherment
  extendedKeyUsage=serverAuth
  subjectAltName=@alt_names
  [alt_names]
  DNS.1 = localhost
  IP.1 = "192.168.11.189"  
  IP.5 = "192.168.0.189"
  IP.2 = "132.232.229.60"
  IP.3 = "118.24.165.225"
  IP.4 = "129.28.108.238"

然後:openssl req -new -newkey rsa:2048 -sha1 -days 3650 -nodes -x509 -keyout server.key -out server.crt -config ssl.cnf

一個指令同時產生需要的server.crt,server.key。

除aubjectAltName外還要關注CN這個字段,它就是我們經常會遇到系統提問:你確定信任“域名”嗎?中這個域名,也就是對外界開放的一個使用了数字證書的域名。

把crt,key抄寫到main/resources目錄下,然後在gRPC服務器配置證書:

trait gRPCServer { val serverCrtFile = new File(getClass.getClassLoader.getResource("server.crt").getPath) val serverKeyFile = new File(getClass.getClassLoader.getResource("server.key").getPath) def runServer(service: ServerServiceDefinition): Unit = { val server = NettyServerBuilder .forPort(50051) .addService(service) .useTransportSecurity(serverCrtFile,serverKeyFile) .build .start // make sure our server is stopped when jvm is shut down
    Runtime.getRuntime.addShutdownHook(new Thread() { override def run(): Unit = { server.shutdown() server.awaitTermination() } }) } }

啟動gRPC服務,運作正常。在看看客戶端代碼:

    val clientCrtFile = new File(getClass.getClassLoader.getResource("server.crt").getPath)
 //或者   val clientCrtFile = new File(getClass.getClassLoader.getResource("rootCA.pem").getPath)

//這樣也行 val clientCrtFile: InputStream = getClass.getClassLoader.getResourceAsStream("rootCA.pem")

    val sslContextBuilder = GrpcSslContexts.forClient().trustManager(clientCrtFile)

    //build connection channel
    val channel = NettyChannelBuilder
      .forAddress("192.168.11.189",50051)
      .negotiationType(NegotiationType.TLS)
      .sslContext(sslContextBuilder.build())
//      .overrideAuthority("192.168.1.3")
      .build()

測試連接,gRPC SSL/TLS成功!

現在開始了解一下https證書的配置使用方法吧。看了一下akka-http關於server端HTTPS設置的例子,證書是嵌在HttpsConnectionContext類型裏面的。還有就是akka-http使用的https證書格式只支持pkcs12,所以需要把上面用openssl產生的自簽名證書server.crt轉成server.p12。這個轉換又需要先產生證書鏈certificate-chain chain.pem:

1)產生certificate-chain:  cat server.crt rootCA.crt > chain.pem

2) server.crt轉換成server.p12: openssl pkcs12 -export -name servercrt -in chain.pem -inkey server.key -out server.p12

https server 測試代碼:

//#imports
import java.io.InputStream import java.security.{ SecureRandom, KeyStore } import javax.net.ssl.{ SSLContext, TrustManagerFactory, KeyManagerFactory } import akka.actor.ActorSystem import akka.http.scaladsl.server.{ Route, Directives } import akka.http.scaladsl.{ ConnectionContext, HttpsConnectionContext, Http } import akka.stream.ActorMaterializer import akka.http.scaladsl.Http import akka.http.scaladsl.server.Directives._ //#imports


object HttpsDemo extends App { implicit val httpSys = ActorSystem("httpSystem") implicit val httpMat = ActorMaterializer() implicit val httpEC = httpSys.dispatcher val password: Array[Char] = "123456".toCharArray // do not store passwords in code, read them from somewhere safe!
 val ks: KeyStore = KeyStore.getInstance("PKCS12") val keystore: InputStream = getClass.getClassLoader.getResourceAsStream("server.p12") ks.load(keystore, password) val keyManagerFactory: KeyManagerFactory = KeyManagerFactory.getInstance("SunX509") keyManagerFactory.init(ks, password) val tmf: TrustManagerFactory = TrustManagerFactory.getInstance("SunX509") tmf.init(ks) val sslContext: SSLContext = SSLContext.getInstance("TLS") sslContext.init(keyManagerFactory.getKeyManagers, tmf.getTrustManagers, new SecureRandom) val https: HttpsConnectionContext = ConnectionContext.https(sslContext) val route = get { complete("Hello world!") } val (port, host) = (50081,"192.168.11.189") val bindingFuture = Http().bindAndHandle(route,host,port,connectionContext = https) println(s"Https Server running at $host $port. Press any key to exit ...") scala.io.StdIn.readLine() bindingFuture.flatMap(_.unbind()) .onComplete(_ => httpSys.terminate()) }

用safari連接https://192.168.11.189:50081/, 彈出窗口一堆廢話后還是成功連接上了。

 

【精選推薦文章】

自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

SpringBoot啟動流程分析(一):SpringApplication類初始化過程

SpringBoot系列文章簡介

SpringBoot源碼閱讀輔助篇:

  Spring IoC容器與應用上下文的設計與實現

SpringBoot啟動流程源碼分析:

  1. SpringBoot啟動流程分析(一):SpringApplication類初始化過程
  2. SpringBoot啟動流程分析(二):SpringApplication的run方法
  3. SpringBoot啟動流程分析(三):SpringApplication的run方法之prepareContext()方法
  4. SpringBoot啟動流程分析(四):IoC容器的初始化過程
  5. SpringBoot啟動流程分析(五):SpringBoot自動裝配原理實現
  6. SpringBoot啟動流程分析(六):IoC容器依賴注入

筆者註釋版Spring Framework與SpringBoot源碼git傳送門:請不要吝嗇小星星

  1. spring-framework-5.0.8.RELEASE
  2. SpringBoot-2.0.4.RELEASE

一、SpringApplication初始化過程

  1.1、SpringBoot項目的mian函數

  常規的這個主類如下圖所示,我們一般會這樣去寫。

 

  在這個類中需要關注的是

  • @SpringBootApplication
  • SpringApplication.run()

  關於 @SpringBootApplication 註解,在後面分析SpringBoot自動裝配的章節會展開去分析。

  本章節中我們需要關注的就是 SpringApplication.run() 方法。

  查看run()方法的實現,如下面代碼所示,我們發現其實其首先是創建了 SpringApplication 的實例,然後調用了 SpringApplication 的run()方法,那本章我們關注的就是 SpringApplication 創建實例的過程。

/**
     * Static helper that can be used to run a {@link SpringApplication} from the
     * specified sources using default settings and user supplied arguments.
     *
     * @param primarySources the primary sources to load
     * @param args           the application arguments (usually passed from a Java main method)
     * @return the running {@link ApplicationContext}
     */
    public static ConfigurableApplicationContext run(Class<?>[] primarySources,
                                                     String[] args) {
        return new SpringApplication(primarySources).run(args);
    }

   

  1.2、 SpringApplication() 構造方法

  繼續查看源碼, SpringApplication 實例化過程,首先是進入但參數的構造方法,最終回來到兩個參數的構造方法。

 1 public SpringApplication(Class<?>... primarySources) {
 2     this(null, primarySources);
 3 }
 4 
 5 @SuppressWarnings({"unchecked", "rawtypes"})
 6 public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
 7     this.resourceLoader = resourceLoader;
 8     Assert.notNull(primarySources, "PrimarySources must not be null");
 9     this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
10     //推斷應用類型,後面會根據類型初始化對應的環境。常用的一般都是servlet環境
11     this.webApplicationType = deduceWebApplicationType();//2.2.1
12     //初始化classpath下 META-INF/spring.factories中已配置的ApplicationContextInitializer
13     setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));//2.2.2
14     //初始化classpath下所有已配置的 ApplicationListener
15     setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//2.2.3
16     //根據調用棧,推斷出 main 方法的類名
17     this.mainApplicationClass = deduceMainApplicationClass();
18 }

 

   1.2.1、deduceWebApplicationType();該方法推斷應用的類型。 SERVLET REACTIVE NONE 

 1 //常量值
 2 private static final String[] WEB_ENVIRONMENT_CLASSES = {"javax.servlet.Servlet",
 3             "org.springframework.web.context.ConfigurableWebApplicationContext"};
 4 
 5 private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
 6         + "web.reactive.DispatcherHandler";
 7 
 8 private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
 9         + "web.servlet.DispatcherServlet";
10 
11 private static final String JERSEY_WEB_ENVIRONMENT_CLASS = "org.glassfish.jersey.server.ResourceConfig";
12 
13 /**
14  * 判斷 應用的類型
15  * NONE: 應用程序不是web應用,也不應該用web服務器去啟動
16  * SERVLET: 應用程序應作為基於servlet的web應用程序運行,並應啟動嵌入式servlet web(tomcat)服務器。
17  * REACTIVE: 應用程序應作為 reactive web應用程序運行,並應啟動嵌入式 reactive web服務器。
18  * @return
19  */
20 private WebApplicationType deduceWebApplicationType() {
21     //classpath下必須存在org.springframework.web.reactive.DispatcherHandler
22     if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
23             && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)
24             && !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null)) {
25         return WebApplicationType.REACTIVE;
26     }
27     for (String className : WEB_ENVIRONMENT_CLASSES) {
28         if (!ClassUtils.isPresent(className, null)) {
29             return WebApplicationType.NONE;
30         }
31     }
32     //classpath環境下存在javax.servlet.Servlet或者org.springframework.web.context.ConfigurableWebApplicationContext
33     return WebApplicationType.SERVLET;
34 }

  返回類型是WebApplicationType的枚舉類型, WebApplicationType 有三個枚舉,三個枚舉的解釋如其中註釋

  具體的判斷邏輯如下:

  • WebApplicationType.REACTIVE  classpath下存在org.springframework.web.reactive.DispatcherHandler

  • WebApplicationType.SERVLET classpath下存在javax.servlet.Servlet或者org.springframework.web.context.ConfigurableWebApplicationContext

  • WebApplicationType.NONE 不滿足以上條件。

  

  1.2.2、 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); 

  初始化classpath下 META-INF/spring.factories中已配置的ApplicationContextInitializer。

 1 private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
 2     return getSpringFactoriesInstances(type, new Class<?>[]{});
 3 }
 4 
 5 /**
 6  * 通過指定的classloader 從META-INF/spring.factories獲取指定的Spring的工廠實例
 7  * @param type
 8  * @param parameterTypes
 9  * @param args
10  * @param <T>
11  * @return
12  */
13 private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
14                                                       Class<?>[] parameterTypes, Object... args) {
15     ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
16     // Use names and ensure unique to protect against duplicates
17     //通過指定的classLoader從 META-INF/spring.factories 的資源文件中,
18     //讀取 key 為 type.getName() 的 value
19     Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
20     //創建Spring工廠實例
21     List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
22             classLoader, args, names);
23     //對Spring工廠實例排序(org.springframework.core.annotation.Order註解指定的順序)
24     AnnotationAwareOrderComparator.sort(instances);
25     return instances;
26 }

 

  看看 getSpringFactoriesInstances 都幹了什麼,看源碼,有一個方法很重要 loadFactoryNames() 這個方法很重要,這個方法是spring-core中提供的從META-INF/spring.factories中獲取指定的類(key)的同一入口方法。

在這裏,獲取的是key為 org.springframework.context.ApplicationContextInitializer 的類。

  debug看看都獲取到了哪些

 

  上面說了,是從classpath下 META-INF/spring.factories中獲取,我們驗證一下:

  發現在上圖所示的兩個工程中找到了debug中看到的6條結果。 ApplicationContextInitializer 是Spring框架的類, 這個類的主要目的就是在   ConfigurableApplicationContext 調用refresh()方法之前,回調這個類的initialize方法。通過  ConfigurableApplicationContext 的實例獲取容器的環境Environment,從而實現對配置文件的修改完善等工作。

  關於怎麼實現自定義的 ApplicationContextInitializer 請看我的另一篇專門介紹該類的博客。

 

  1.2.3、 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); 

  初始化classpath下 META-INF/spring.factories中已配置的 ApplicationListener。

   ApplicationListener 的加載過程和上面的 ApplicationContextInitializer 類的加載過程是一樣的。不多說了,至於 ApplicationListener 是spring的事件監聽器,典型的觀察者模式,通過 ApplicationEvent 類和 ApplicationListener 接口,可以實現對spring容器全生命周期的監聽,當然也可以自定義監聽事件。為了梳理springboot的啟動流程在這裏先不說這個了。後面有時間的話再介紹。

   關於ApplicationContextInitializer的詳細介紹請看<SpringBoot之ApplicationContextInitializer的理解和使用>

 二、總結

  關於 SpringApplication 類的構造過程,到這裏我們就梳理完了。縱觀 SpringApplication 類的實例化過程,我們可以看到,合理的利用該類,我們能在spring容器創建之前做一些預備工作,和定製化的需求。

比如,自定義SpringBoot的Banner,比如自定義事件監聽器,再比如在容器refresh之前通過自定義 ApplicationContextInitializer 修改配置一些配置或者獲取指定的bean都是可以的。。。

  下一節開始分析SpringBoot容器的構建過程,也就是那個大家多少都看過的run();方法。

   

  原創不易,轉載請註明出處。

  如有錯誤的地方還請留言指正。

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

如何從現在開始寫博客?

  • 《構建之法》讀書筆記四-關於寫博客的想法

  在前不久知名博主《純潔的微笑》在博客園分享了他寫博客的心得,獲得了讀者圈的廣泛共鳴,同時也引起了一些老生常談的爭議,這樣的討論在我們長沙.NET技術社區內部也經常發生。

1、寫博客的好處

  在《軟技能·代碼之外的生存指南》這本書中,作者提到作為軟件開發人員,最好的推銷自己的媒介就是博客,他認為每個在乎自己職業生涯的軟件開發人員都應該建立一個博客,他把他職業生涯的大部分成功都歸功於他的博客。

  在我之前寫過的一篇博客中,介紹了長沙.NET技術社區的優秀開發者,全棧工程師《ASP.NET企業級開發實戰》的作者鄒瓊俊老師的成長經歷,在他大學畢業後去找工作屢屢碰壁時,有幸加入了由長沙社區組織者周尹老師開設的學習班,並向老師請教如何才能提高自己的技能時,老師告訴他,你去寫博客,堅持寫博客,堅持五年,一定能獲得成功。果不其然,他筆耕不輟、堅持學習和記錄自己發現的問題,並成為了一位出版了兩本開發技能書籍的暢銷書作者。

  除了他之外,還有我在博客園結識的Java全棧工程師,《Web全棧開發進階之路》的作者,沉默王二,也是一位愛好寫博客的人,多年前他在蘇州的日企工作,偶爾會做一些筆記,但自從回到小城洛陽之後,就開始瘋狂的學習技術和寫博客,多年的堅持沒有白費,終於得以打造這本優秀的Java開發者們值得擁有的寶貴書籍。

  在技術社區有一次的閑談中,有人說起一個故事,說他以前有一次和他的同學一起去面試一份工作,然後面試官問你覺得你平時有什麼比較好的學習習慣么?他說基本上回家之後就是看看書,玩玩遊戲什麼的。而他的同學卻說:回家之後會看看書,逛逛博客園、寫寫博客。於是毫無疑問,他的同學得到了面試官的青睞並獲得了這份Offer。或許他的同學以前並沒有寫博客,但是相信得到這份Offer之後,他一定會開始寫博客、並培養自己寫博客的習慣的。

  在《軟技能》這本書裏面也說了,他去給軟件開發人員做演講,每當他問開發者有多少位開了博客並每周更新的請举手時,一百個開發者,頂多只有一位举手。他認為,堅持寫博客、持之以恆的撰寫優質內容,能讓你輕鬆在開發者中脫穎而出。

2、不要給自己太大的壓力、從小問題開始記錄

  許多人說曾經開過博客賬戶,甚至心血來潮使用wordpress\hexo等博客平台搭建過自己的博客網站,但是最終都無疾而終了,主要原因是不知道寫什麼內容,以及擔心自己寫的內容會被人吐槽說質量不好、自己忙於工作,根本沒有時間寫博客等。

  有時候開發者總是會給自己過大的壓力,其實這樣的壓力毫無必要,坦率而言,在大部分技術網站中活躍的優秀開發者,他們剛剛參加工作時,並非每個人都是學霸、一開始就能寫出優秀的博客,他們善於發現和記錄在工作中自己發現的一些點,並持之以恆,最終讓他們脫穎而出。

  每一位開發者本質上沒有那麼大的區別,並不是所有的開發者都有機會經歷大併發等互聯網的牛逼場景,但是我們總有機會發現或親歷一些只有自己才能看到的場景。在開發者這個包容心最強的社會群體中,只要你勇於寫下自己的博客,一定會獲得其他開發者的認同。(當然,有一些網絡噴子,請不要在意他們的說法)

  例如,我個人認為,我們可以這樣嘗試。

  1、不要擔心自己的文筆不夠好、不要過度在意別人的看法,只要寫博客,讓自己滿意,就是一個開端。

  2、學會記錄,形成素材。想到什麼,就記錄什麼,提前頭腦風暴出不同的想法,隨時更新在自己筆記中記錄的點子、並適當的進行擴展,就是一個非常不錯的話題。

  3、無需花費太多時間,每天花半個小時思考和總結問題,一周就有3個半小時,足夠寫出一篇千字以內的總結了。

  4、通過博客與其他人進行討論。如果遇到想不明白的問題,還可以通過博客的形式,分享出來,邀請大家進行討論,這樣的過程簡直不要太開心。

  在《構建之法》這本書中,將寫博客當成是開發者形成良好習慣的開始,並稱之為“做中學”,我們也可以在edu.cnblogs.com這個站點中,看到許多年輕的未來開發者們,他們通過博客園這個平台,將自己軟件工程學習過程中的問題、解決問題的方法和經驗分享出來,不同學校間還可以互相交流,形成了一個非常積極活躍的技術氛圍,我覺得這是一種令人愉悅的體驗。

  當然,我也很遺憾在我讀書的那些年沒有機會體驗這樣的機會,以至於走了一些彎路,到今天我的博客依然寫得比較少,所以這篇博客其實也是寫給自己的一種警醒,提醒自己應該堅持寫博客,不管年紀多大,只要今天開始堅持,堅持五年,總能取得不錯的成功,雖然不一定能像其他人一樣成功,但至少會比今天的自己成功。

3、寫博客的原則

  我曾經針對寫博客的問題,有幸請教過《構建之法》的作者鄒欣老師,我說我最近也寫了一些博客,但是總感覺都是一些毫無乾貨,讀起來感覺沒什麼意思,請問如何才能寫出有乾貨的內容呢?

  • 老師回答:說清楚一個具體問題,解決一個具體問題。

  這是一個充滿哲理的回答,讓我茅塞頓開。寫博客不是寫小說,不用長篇大論,不用引經據典,不用引用華麗的詞藻,只需講清楚一個問題即可。再簡單的問題,也是一個問題,每個人的理解都不一定相同,只需用鍵盤敲下你的理解,就可以成為一篇博客。

 

  在閱讀《浪潮之巔》第二卷的過程中,我看到了一樁關於博客的軼事,說甲骨文收購了Sun公司之後,甲骨文老闆 Ellison這樣吐槽:

  “Sun的工程師團隊是如此優秀,但是他們獲得的指引方向卻異常糟糕,這是導致他們無法成功的原因。花哨的博客並不能取代優秀的微處理器,也不能取代任何軟件,博客文章多頁無法帶來好的銷售業績。”

  Sun是一家曾經是一家優秀的互聯網公司,開創的許多領域時至今日依然讓開發者們收益,但是為什麼十年前卻突然死亡,最終賣身給Oracle?在《浪潮之巔》中有比較深入的闡述,而在Sun破產之前, Schwartz這位Sun的末代領袖,卻試圖通過博客來治理這麼大的公司,經常使用十餘種語言寫博客,實在是一位有意思的商界領袖。(還有那位喜好用推特治國的川普,也挺有意思的,嗯,川普和Schwartz應該是筆友。)

  ps:我是一位.NET開發者,在過去十年間,大概.NET是受Java衝擊最嚴重的開發技術吧,但是創造出Java如此優秀語言的Sun公司,究竟是什麼原因讓他被歷史的浪潮打翻的?真的只是因為CEO愛寫博客嗎?這是一個很有意思的故事,大家也可以去《浪潮之巔》中看看,正好《浪潮之巔》第四版新書也上市了,我已經買了一套了。哈哈。

【精選推薦文章】

自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

上周熱點回顧(6.17-6.23)

熱點隨筆:

· 升維打擊,設計之道(Artech)
· 藉助FreeHttp為任意移動端web網頁添加vConsole調試(lulianqi15)
· 你為什麼有那麼多時間寫博客?(純潔的微笑)
· 看完此文,媽媽還會擔心你docker入不了門?(sullivan06)
· 我和 HelloGitHub(削微寒)
· 簡歷上如果出現過於高大上的項目,反而過猶不及:再論如何通過項目引出技術(hsm_computer)
· 程序員與醫生(道友留步`)
· 一個理想主義的程序員(沉默王二)
· 高考完?入門級的開源項目帶你開啟編程之旅(削微寒)
· 短信驗證碼“最佳實踐”(GUOKUN)
· .NET CORE下最快比較兩個文件內容是否相同的方法(WAKU)
· 從CLR GC到CoreCLR GC看.NET Core對雲原生的支持(艾心❤)

熱點新聞:

· “地震波還有61秒到達”,08年籌建的技術,在這次四川地震中立功了
· 微軟,奪回王位
· 我們從未見過它的真面目,直到一群科學家拍了張照片
· 華為自研SSD揭秘:國內唯一殺入全球TOP10
· 無需固態電池,一條假魚靠“血液”續航36小時
· 5G來了,需要更換SIM卡嗎?
· 屠呦呦團隊放“大招”:“青蒿素抗藥性”等研究獲新進展
· 羅永浩再談收購蘋果:還需要一點時間
· 國產最先進X86處理器KX-6000發布:8核3.0GHz 力壓酷睿i5
· 美國欲剝奪華為在美專利權,涉及至少 3195 件專利
· 四川發生強震 成都提前61秒收到預警
· 聯想 ThinkPad P 系列筆記本預裝 Ubuntu 系統

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

[WPF自定義控件庫]排序、篩選以及高亮

1. 如何讓列表的內容更容易查找

假設有這麼一個列表(數據源在本地),由於內容太多,要查找到其中某個想要的數據會比較困難。要優化這個列表,無非就是排序、篩選和高亮。

改造過的結果如上。

2. 排序

在WPF中要實現數據排序的功能有很多種,例如用Linq,但這種場景的標準做法是使用CollectionViewSource。

CollectionViewSource是一種數據集合的代理類。它有兩個很重要的屬性:

  • Source 是數據源的集合;

  • View 是經過處理后的數據視圖。

看上去感覺是不是很像數據庫里的Table和View的關係?

在這個例子里使用CollectionViewSource排序的代碼如下:

private readonly CollectionViewSource _viewSource;

public HighlightSample()
{
    InitializeComponent();
    _viewSource = new CollectionViewSource
    {
        Source = Employee.AllExecutives
    };

    _viewSource.View.Culture = new System.Globalization.CultureInfo("zh-CN");
    _viewSource.View.SortDescriptions.Add(new SortDescription(nameof(Employee.FirstName), ListSortDirection.Ascending));
    EmployeeElement.ItemsSource = _viewSource.View;
}

這段代碼為CollectionViewSource的Source賦值后,把CollectionViewSource的View作為ListBox的數據源。其中SortDescriptions用於描述View的排序方式。如果包含中文,別忘記將Culture設置為zh-cn

至此排序的功能就實現了。文檔中還提到CollectionViewSource的其它信息:

您可以將集合視圖作為綁定源集合,可用於導航和显示集合中基於排序、 篩選和分組查詢,而無需操作基礎源集合本身的所有頂層。 如果Source實現INotifyCollectionChanged接口,所做的更改引起CollectionChanged事件傳播到View。

由於View不會更改Source,因此每個Source都可以有多個關聯的View。 使用View,可以通過不同方式显示相同數據。 例如,可能希望在頁面左側显示按優先級排序的任務,而在頁面右側显示按區域分組的任務。

3. 篩選

CollectionViewSource的View屬性類型為ICollectionView接口,它提供了Filter屬性用於實現數據的過濾。在這個例子里實現如下:

_viewSource.View.Filter = (obj) => (obj as Employee).DisplayName.ToLower().Contains(FilterElement.Text);

private void OnFilterTextChanged(object sender, TextChangedEventArgs e)
{
    if (_viewSource != null)
        _viewSource.View.Refresh();
}

這段代碼實現了當輸入框的文字改變時刷新View的功能。其中Refresh方法用於重新創建View,也就是刷新視圖。

ICollectionView還提供了一個DeferRefresh函數,這個函數用於進入延遲循環,該循環可用於將更改合併到視圖並延遲自動刷新,在需要多次操作並刷新數據量大的集合時可以用這個函數。

4. 高亮

<TextBox x:Name="FilterElement"
         TextChanged="OnFilterTextChanged"/>
<ListBox Name="EmployeeElement"
         Grid.Row="1"
         Height="200"
         Margin="0,8,0,0">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding DisplayName}"
                           kino:TextBlockService.HighlightText="{Binding ElementName=FilterElement,Path=Text}" />
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

UWP的高亮可以使用TextHighlighter這個類,實現起來很簡單。WPF中的高亮則是使用自定義的TextBlockService.HighlightText附加屬性聲明要高亮的文字,然後將TextBlock的Text替換為處理過的Inlines,使用方式如上。

private static void MarkHighlight(TextBlock target, string highlightText)
{
    var text = target.Text;
    target.Inlines.Clear();
    if (string.IsNullOrWhiteSpace(text))
        return;

    if (string.IsNullOrWhiteSpace(highlightText))
    {
        target.Inlines.Add(new Run { Text = text });
        return;
    }

    while (text.Length > 0)
    {
        var runText = string.Empty;
        var index = text.IndexOf(highlightText, StringComparison.InvariantCultureIgnoreCase);
        if (index > 0)
        {
            runText = text.Substring(0, index);
            target.Inlines.Add(new Run { Text = runText, Foreground = _noHighlightBrush });
        }
        else if (index == 0)
        {
            runText = text.Substring(0, highlightText.Length);
            target.Inlines.Add(new Run { Text = runText });
        }
        else if (index == -1)
        {
            runText = text;
            target.Inlines.Add(new Run { Text = runText, Foreground = _noHighlightBrush });
        }

        text = text.Substring(runText.Length);
    }
}

這是實現代碼。其實用Regex.Split代碼會好看很多,但懶得改了。
本來應該是高亮匹配的文字,但實際使用中發覺把未匹配的文字置灰更好看,就這樣實現了。

5. 結語

這篇文章介紹了使用CollectionViewSource實現的排序、篩選功能,以及使用附加屬性和Inlines實現高亮功能。

不過這樣實現的高亮功能有個問題:不能定義高亮(或者低亮)的顏色,不管在代碼中還是在XAML中。一種可行的方法是參考ToolTipService定義一大堆附加屬性,例如這樣:

<TextBox x:Name="FilterElement" 
         ToolTipService.ToolTip="Filter Text"
         ToolTipService.HorizontalOffset="10"
         ToolTipService.VerticalOffset="10"
         TextChanged="OnFilterTextChanged"/>

這種方式的缺點是這一大堆附加屬性會導致代碼變得很複雜,難以維護。ToolTipService還可以創建一個ToolTip類,把這個類設置為附加屬性的值:

<TextBox x:Name="FilterElement" 
         TextChanged="OnFilterTextChanged">
    <ToolTipService.ToolTip>
        <ToolTip Content="Filter Text"
                 HorizontalOffset="10" 
                 VerticalOffset="10"/>
    </ToolTipService.ToolTip>
</TextBox>

這種方式比較容易維護,但有人可能不明白ToolTipService.ToolTip屬性的值為什麼既可以是文本(或圖片等其它內容),又可以是ToolTip類型,XAML如何識別。關於這一點我在下一篇文章會講解,並且重新實現高亮的功能以支持Style等功能。

也可以參考SearchableTextBlock寫一個高亮的文本框,一了百了,但我希望通過這個有趣的功能多介紹幾種知識。

6. 參考

CollectionViewSource Class (System.Windows.Data) Microsoft Docs

TextBlock.Inlines Property (System.Windows.Controls) Microsoft Docs

A WPF Searchable TextBlock Control with Highlighting WPF

7. 源碼

TextBlockService.cs at master

【精選推薦文章】

自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

Jest — ElasticSearch Java 客戶端

1. 介紹

任何使用過Elasticsearch的人都知道,使用基於rest的搜索API構建查詢可能是單調乏味且容易出錯的。

在本教程中,我們將研究Jest,一個用於Elasticsearch的HTTP Java客戶端。Elasticsearch提供了自己原生的Java客戶端,然而 Jest提供了更流暢的API和更容易使用的接口。

2. Maven 依賴

我們需要做的第一件事是導入Jest庫到我們的POM文件中:

<dependency> <groupId>io.searchbox</groupId> <artifactId>jest</artifactId> <version>6.3.1</version> </dependency> 

Jest的版本是遵循Elasticsearch的主版本號的。
這將確保客戶端和服務端之間的兼容性。

通過包含Jest依賴項,相應的[Elasticsearch庫](https://search.maven.org/search?q=g:org.elasticsearch a:elasticsearch)將被包含為傳遞依賴項。

3. 使用Jest Client

在本節中,我們將研究如何使用Jest client執行Elasticsearch中的常見任務。

要使用Jest client,我們只需使用 JestClientFactory 創建一個 JestClient 對象。這些對象的創建開銷很高,而且是線程安全的,因此我們將創建一個可以在整個應用程序中共享的單例實例:

public JestClient jestClient() { JestClientFactory factory = new JestClientFactory(); factory.setHttpClientConfig( new HttpClientConfig.Builder("http://localhost:9200") .multiThreaded(true) .defaultMaxTotalConnectionPerRoute(2) .maxTotalConnection(10) .build()); return factory.getObject(); } 

這裏將創建一個Jest client,該客戶端連接到本地運行的Elasticsearch。雖然這個連接示例很簡單,但是Jest還完全支持代理、SSL、身份驗證,甚至節點發現。

JestClient 類是通用類,只有少數公共方法。我們將使用的一個主要方法是execute,它接受Action接口的一個實例。Jest客戶端提供了幾個構建器類來幫助創建與Elasticsearch交互的不同操作。

所有Jest調用的結果都是JestResult的一個實例。 我們可以通過調用 issucceeded 方法來檢查是否成功。對於失敗的操作,我們可以調用GetErrorMessage方法來獲取更多詳細信息:

JestResult jestResult = jestClient.execute(new Delete.Builder("1").index("employees").build()); if (jestResult.isSucceeded()) { System.out.println("Success!"); } else { System.out.println("Error: " + jestResult.getErrorMessage()); } 

3.1. 管理索引

檢查索引是否存在,我們使用IndicatesExists操作:

JestResult result = jestClient.execute(new IndicesExists.Builder("employees").build()) 

創建一個索引,我們使用CreateIndex操作:

jestClient.execute(new CreateIndex.Builder("employees").build()); 

這將創建一個具有默認設置的索引。我們可以在創建索引時覆蓋特定的設置:

Map<String, Object> settings = new HashMap<>(); settings.put("number_of_shards", 11); settings.put("number_of_replicas", 2); jestClient.execute(new CreateIndex.Builder("employees").settings(settings).build()); 

使用ModifyAliases操作創建或更改別名也很簡單:

jestClient.execute(new ModifyAliases.Builder( new AddAliasMapping.Builder("employees", "e").build()).build()); jestClient.execute(new ModifyAliases.Builder( new RemoveAliasMapping.Builder("employees", "e").build()).build()); 

3.2. 創建文檔

Jest client使用索引操作類索引或創建新文檔變得容易。Elasticsearch中的文檔只是JSON數據,有多種方法可以將JSON數據傳遞給Jest client 進行索引。

對於這個例子,讓我們使用一個虛構的僱員文檔:

{ "name": "Michael Pratt", "title": "Java Developer", "skills": ["java", "spring", "elasticsearch"], "yearsOfService": 2 } 

表示JSON文檔的第一種方法是使用Java字符串。雖然我們可以手動創建JSON字符串,但我們必須注意正確的格式、大括號和轉義引號字符。因此,更容易的方式是使用一個JSON庫(如Jackson)來構建我們的JSON結構,然後將其轉換為字符串:

ObjectMapper mapper = new ObjectMapper(); JsonNode employeeJsonNode = mapper.createObjectNode() .put("name", "Michael Pratt") .put("title", "Java Developer") .put("yearsOfService", 2) .set("skills", mapper.createArrayNode() .add("java") .add("spring") .add("elasticsearch")); jestClient.execute(new Index.Builder(employeeJsonNode.toString()).index("employees").build()); 

我們還可以使用Java Map 來表示JSON數據,並將其傳遞給索引操作:

Map<String, Object> employeeHashMap = new LinkedHashMap<>(); employeeHashMap.put("name", "Michael Pratt"); employeeHashMap.put("title", "Java Developer"); employeeHashMap.put("yearsOfService", 2); employeeHashMap.put("skills", Arrays.asList("java", "spring", "elasticsearch")); jestClient.execute(new Index.Builder(employeeHashMap).index("employees").build()); 

最後,Jest client 可以接受表示要索引的文檔的任何POJO。假設我們有一個Employee類:

public class Employee { String name; String title; List<String> skills; int yearsOfService; } 

我們可以把這個類的一個實例直接傳遞給Index builder:

Employee employee = new Employee(); employee.setName("Michael Pratt"); employee.setTitle("Java Developer"); employee.setYearsOfService(2); employee.setSkills(Arrays.asList("java", "spring", "elasticsearch")); jestClient.execute(new Index.Builder(employee).index("employees").build()); 

3.3. 讀取文檔

使用Jest client從Elasticsearch訪問文檔有兩種主要方法。首先,如果我們知道文檔ID,我們可以使用get操作直接訪問它:

jestClient.execute(new Get.Builder("employees", "17").build()); 

要訪問返回的文檔,我們必須調用其中一個getSource方法。我們可以將結果作為原始JSON獲取,或者將其反序列化為DTO:

Employee getResult = jestClient.execute(new Get.Builder("employees", "1").build()) .getSourceAsObject(Employee.class); 

訪問文檔的其他方法是使用搜索查詢,這種方式在Jest中是通過搜索操作實現的。

Jest client 支持全部的 Elasticsearch query DSL。 與索引操作一樣,查詢被表示為JSON文檔,並且有多種執行搜索的方法。

首先,我們可以傳遞一個表示搜索查詢的JSON字符串。提醒一下,我們必須確保字符串是正確轉義的,並且是有效的JSON:

String search = "{" + " \"query\": {" + " \"bool\": {" + " \"must\": [" + " { \"match\": { \"name\": \"Michael Pratt\" }}" + " ]" + " }" + " }" + "}"; jestClient.execute(new Search.Builder(search).build()); 

與上面的索引操作一樣,我們可以使用Jackson之類的庫來構建JSON查詢字符串。此外,我們還可以使用原生的Elasticsearch查詢操作API。這樣做的一個缺點是,我們的應用程序必須依賴於完整的Elasticsearch庫。

我們可以使用 getSource 方法來訪問搜索操作中匹配的文檔。然而,Jest還提供了Hit類,它包裝了匹配的文檔並提供有關結果的元數據。 使用Hit類,我們可以訪問每個結果的附加元數據:得分、路由和解釋結果,舉幾個例子:

List<SearchResult.Hit<Employee, Void>> searchResults = jestClient.execute(new Search.Builder(search).build()) .getHits(Employee.class); searchResults.forEach(hit -> { System.out.println(String.format("Document %s has score %s", hit.id, hit.score)); }); 

3.4. 更新文檔

Jest為更新文檔提供了一個簡單的Update操作:

employee.setYearOfService(3); jestClient.execute(new Update.Builder(employee).index("employees").id("1").build()); 

它接受與我們前面看到的索引操作相同的JSON表示,這使得在兩個操作之間共享代碼變得很容易。

3.5. 刪除文檔

從索引中刪除文檔是使用Delete操作完成的。它只需要索引名和文檔ID:

jestClient.execute(new Delete.Builder("17") .index("employees") .build()); 

4. 批量操作

Jest client 同樣支持批量操作。 這意味着我們可以通過同時發送多個操作來節省時間和帶寬。

使用批量操作,我們可以將任意數量的請求組合成單個調用。我們甚至可以將不同類型的請求組合在一起:

jestClient.execute(new Bulk.Builder() .defaultIndex("employees") .addAction(new Index.Builder(employeeObject1).build()) .addAction(new Index.Builder(employeeObject2).build()) .addAction(new Delete.Builder("17").build()) .build()); 

5. 異步操作

Jest client 同樣支持異步操作。 這意味着我們可以使用非阻塞I/O執行上述任何操作。

要異步調用操作,只需使用客戶端的executeAsync方法:

jestClient.executeAsync( new Index.Builder(employeeObject1).build(), new JestResultHandler<JestResult>() { @Override public void completed(JestResult result) { // handle result } @Override public void failed(Exception ex) { // handle exception } }); 

注意,除了(本例中是索引)操作之外,異步流還需要一個JestResultHandler。當操作完成時,Jest client 將調用該對象。該接口有兩個方法—完成和失敗—分別允許處理操作的成功或失敗。

6. 結論

在本教程中,我們簡要介紹了Jest client,一個用於Elasticsearch的RESTful Java客戶端。雖然我們只介紹了它的一小部分功能,但很明顯Jest是一個健壯的Elasticsearch客戶端。它的流暢的構建器類和RESTful接口使其易於學習,並且它對Elasticsearch接口的完全支持使其成為原生客戶端的一個有力的替代方案。

和往常一樣,本教程中的所有代碼示例都在我們的Github頁面上。

原文:https://www.baeldung.com/elasticsearch-jest

作者:Michael Pratt

譯者:huowolf/

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

編程哲理小故事:Tina的運動會方陣

自從接到任務后,Tina一直 煩惱着如何讓這群繁忙又缺乏才藝的程序員在運動會開幕式上做出一個有趣的方陣表演。

 

接到了運動會的方陣表演的任務

時間回到1個月前。

Tina正在工位上繁忙地進行着下一期準備上線系統的測試,這時候老大跑了過來

“Tina,我們公司要舉行運動會,開幕式時有方陣的表演,你幫忙組織下?”

Tina心裏咯噔一下,來了一個苦差。

“方陣?就是像奧運會開幕的時候,運動員在體育場跑道上走,展示風採的那種?”

“對,到時要一個6*6共36人的方陣,到時會在市裡最好的體育館進行,公司的大領導會在主席台看我們表演,為了我們部門,你要加油呀”

“嗯,我會加油的,但36個人不少呀,大家最近都挺忙的,感覺我拉不動他們參加呀”,Tina苦笑道。

“怎麼會呢,我看你在男生中還挺有人氣的,不過你不好意思拉人的話,人的問題我解決,你策劃組織好表演就行”

 

 

 

進展緩慢的方陣表演

方陣的排練一周一次,一周一周的過去了,但整體的效果並沒有得到質的提升,排練時總是湊不齊人,舞蹈也相對複雜,交互很多,大家也沒能很好的磨合,方陣表演這件事幾乎成了Tina的心病

“萬一到時我們成了最差的方陣怎麼辦?我會讓老大很失望的”

今天還沒到排練的時間,但她提前到了場地,擔心着,糾結着。

“方陣排練成怎樣啦?”今天老大居然親自過來了。

“我們今晚繼續排練,但效果還不太理想”

“有遇到什麼困難么?”

“唉,一言難盡,待會你看下把,也提一下意見,這個是我們排練模仿的視頻,你也可以看下”

老大把脈

老大在旁邊看着大夥排練,沒有做任何評價與指導,只是在久不久時用言語激勵一下大家。

今天的排練結束了,大夥都散去,Tina和老大留了下來。

“今天的排練我看了,我們的整齊度還略有欠缺,我們的隊伍就像你描述的一樣,經常會有人臨時有事,沒辦法很好的達到一個整體的效果,磨合度有待提升”

“嗯,對呀,但我已經儘力選擇大家都有空的時間了,但要每個人都有空是在是太難了”

“對,這是一個問題,人越多越難協調與同步,我們通過初中數學就可以知道,協調的難度是隨着人數的增加指數級上漲的”

“嗯嗯,但也沒辦法呀,表演方陣要求就是要這麼多人”,Tina應答着,心裏倒翻了個白眼,好好的幹嘛突然扯上數學

“對,總人數是沒辦法減少,但如果分成多個小組,小組人數比較小,是不是集齊一個小組的人更為簡單點?我們可以讓各個小組各自排練,然後每周一次像現在這樣合起來排練。對於類似規模導致的問題,我們大多都可以用分而治之的策略解決,就像公司里把我們的軟件分成了很多子系統一樣”

“啊?那這樣子要怎麼分組,這是一隻完整的舞蹈呀”

“一個完整的舞蹈必然是每個人的交互的有機結合,但舞蹈里必然會有一群人之間的交互更為密切,我們可以把這群人合成一組,那隻涉及他們內部的舞蹈叫可以他們內部自行排練了。”

“但這個界限不是很清晰把?”

“對,更為密切這個概念確實不是很清晰,所以這也是組織設計的藝術之一,甚至於這是一個需要試錯的過程。我們的軟件編碼一樣,強調高內聚低耦合,讓通訊交互成本降到最低的理念是一致的。但我們不能因為無法達到最優的設計而不去分組,分組可能不是最優,但進行了分組就已經在進步了,我們可以在繼續排練的過程中繼續優化”

“嗯嗯,想不到編碼與方陣排練還有這樣類似的東西,哈哈”

“嗯,世界有很多東西是相似的,像公司的組織架構、各種天體系統、人類的各種器官組成等等都有類似高內聚低耦合的特性存在,所以在我看來一個優秀的有悟性的碼農能將其知識遷移到很多領域”

“好,那我認真研究下視頻,然後對團隊進行分組,一個分組的大小多少合適呢?”

“太陽系有8個行星,地球只有1個月球衛星,這都是由於它們質量決定的。一個分組最大能有多少人這實質是由組長的個人能力決定的,組長能協調的人數就是這個組人數的上限,但當然啦,具體人數要和舞蹈自身的需要相結合。組的大小可以取‘舞蹈分組所需人數’和‘組長最大可管理人數’的較小值。”

“這是讓一個組盡可能大的意思么?”

“對的,一個組應該在可控範圍內盡可能的大,因為分組小意味着分組多,而分組多,協調多個分組就會產生更大的成本。當分組很多,甚至於把一個人看成一個組的時候,就跟你當前面臨的情況一樣。對應於我們軟件領域也是一樣的,對於微服務/組件應該拆成多大這個問題,個人理解組件的大小應該在一個普通程序員能理解、控制的複雜度範圍內,程序員就是這個微服務的類、模塊的組長”

“嗯,明白了。我盡量將其分組大小控制在合適範圍內”

“還有一件事就是,每個小組指定了組長之後,之後組內的排練可以適當程度的放權,這樣的話,你才有更多的精力去考慮我們組與組之間的交互應該怎麼進行。但當然啦,如果你得精力足夠的話,去了解和支持某些個特別舞蹈特別複雜的小組的進展也是挺好的。對於人的能力來說,能做高層設計同時也能做底層執行當然是最理想的,但在程序代碼里,一個模塊既處理高層邏輯又處理底層邏輯的話,是一個不好的表現,這會讓我們的代碼更難以理解”

“哈哈,我本來只想看下怎麼排練好方陣,想不到還順便學習了這麼多編碼知識,感謝老大呀!”

“嗯,剛剛的說的都是些簡單的理論,至於落地到實踐還會複雜的多,細節的處理還是得靠你呀,加油~”

最後的展示

有了一些基礎理論的指導,Tina的方陣隊伍的組織架構在排練中演進,各個小組有了組長能自發的組織組內的訓練,各組的表演水平得到了很大的提升。到集體匯總排練的時候,實際上就是以組的維度進行交互,而非之前的以人維度的磨合,因此集體匯總排練的效率也得到了提升。

“起步走!”,方陣前進的音樂和口令在體育館正式響起,方陣邁着整齊的步伐往主席台走去。Tina看着隊伍,再回頭看來下老大,嘴角泛起了自信的微笑。

 

(最近看了本如何寫小說的書,實踐一下小說風格,哈哈,若本文有錯誤缺陷,請批評負責幫助我進步。若本文對你有所啟發和幫助請不吝點贊轉發。這對我真的很重要,拜託了~)

 

作者簡介

多年金融行業經驗,現為某Top2互聯網銀行高級搬磚工,曾在兩家TOP3股份制商業銀行及一家互金創業公司工作(架構、核心業務主程),EasyTransaction作者,歡迎關注個人公眾號,在這裏我會分享日常工作、生活中對於架構、編碼和業務的思考

 

【精選推薦文章】

自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

Swoole 啟動一個服務,開啟了哪些進程和線程?

目錄

  • 概述
  • 代碼
  • 小結

概述

Swoole 啟動一個服務,開啟了哪些進程和線程?

為了解決這個問題,咱們啟動一個最簡單的服務,一起看看究竟啟動了哪些進程和線程?

然後結合官網運行流程圖,對每個進程和線程進行歸類。

服務啟動后打印出當前 Swoole 版本 和 當前 CPU 核數。

打印 Swoole 版本,是讓大家可以下載這個版本 去運行代碼。

打印 CPU 核數,是因為這個參數下面會用到。

廢話不多說,直接看代碼吧。

代碼

serv.php

<?php

class Server
{
    private $serv;

    public function __construct() {
        $this->serv = new swoole_server("0.0.0.0", 9502);
        $this->serv->set([
            'worker_num'      => 3,
            'task_worker_num' => 3,
        ]);
        $this->serv->on('Start', function ($serv) {
            echo "SWOOLE:".SWOOLE_VERSION . " 服務已啟動".PHP_EOL;
            echo "SWOOLE_CPU_NUM:".swoole_cpu_num().PHP_EOL;
        });
        $this->serv->on('Receive', function ($serv, $fd, $from_id, $data) { });
        $this->serv->on('Task', function ($serv, $task) { });
        $this->serv->on('Finish', function ($serv, $task_id, $data) {});
        $this->serv->start();
    }
}
$server = new Server();

上面的代碼簡單說下,創建了一個 TCP 服務器,啟動了 3 個 worker 進程, 3 個 task 進程,因為啟用了 task 功能,所以必須註冊 onTask、onFinish 2 個事件的回調函數。

咱們運行一下:

使用 ps 查看下:

16390 的父進程是 16389。

16393、16394、16395、16396、16397、16398 的父進程是 16390。

有沒有發現,16391、16392 去哪啦?是不是很奇怪。

再用 pstree 查看下:

出來了吧,16391、16392 是線程 與 16390 進程一個層級。

現在我們了解了,啟動的這個服務使用了 8 個進程、2 個線程。

我們一起看下官方 Swoole Server 的文檔:

https://wiki.swoole.com/wiki/page/p-server.html

看下這張圖:

通過上面的圖,我們可以得到結論:

16389 是 Master 進程。

16390 是 Manager 進程。

16391、16392 是 Reactor 線程。

16393、16394、16395、16396、16397、16398 包括 3 個 Worker 進程,3 個 Task 進程。

小結

一、為什麼是 3 個 Worker 進程、3 個 Task 進程?

因為,在創建服務的時候我們進行了設置 worker_num = 3, task_worker_num = 3。

worker_num 如果不進行設置,默認為 SWOOLE_CPU_NUM,在上面咱們打印出來了,默認為 2,最大不超過,SWOOLE_CPU_NUM * 1000,具體詳情,看官方文檔。

worker_num 文檔:

https://wiki.swoole.com/wiki/page/275.html

task_worker_num 文檔:

https://wiki.swoole.com/wiki/page/276.html

二、為什麼是 2 個 Reactor 線程?它是干什麼的?

因為,Reactor 線程數,默認為 SWOOLE_CPU_NUM,也可以通過 reactor_num 參數進行設置。

reactor_num 文檔:

https://wiki.swoole.com/wiki/page/281.html

它是真正處理 TCP 連接,收發數據的線程。

Reactor線程 文檔:

https://wiki.swoole.com/wiki/page/347.html

三、Reactor、Worker、TaskWorker 的關係是什麼樣的?

一個通俗的比喻,假設Server就是一個工廠,那Reactor就是銷售,接受客戶訂單。而Worker就是工人,當銷售接到訂單后,Worker去工作生產出客戶要的東西。而TaskWorker可以理解為行政人員,可以幫助Worker幹些雜事,讓Worker專心工作。

官方已經解釋的很詳細了,看官方文檔吧:

https://wiki.swoole.com/wiki/page/163.html

如果你想學習 Swoole 可以看下這個 《Swoole 文章匯總(10 篇)》。

本文歡迎轉發,轉發請註明作者和出處,謝謝!

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

代碼榮辱觀-以運用風格為榮,以隨意編碼為恥

編寫代碼的八榮八恥

1. 產品命名:以簡單有趣為榮,以平庸難記為恥。

2. 單個函數:以短小精悍為榮,以冗長費神為恥。

3. 代碼維護:以持續重構為榮,以停滯不前為恥。

4. 編程風格:以運用風格為榮,以隨意編碼為恥。

5. 程序設計:以開關上線為榮,以自信編碼為恥。

6. 接口定義:以用戶易用為榮,以複雜歧義為恥。

7. 斷言分支:以實時報警為榮,以忽略分支為恥。

8. 監控報警:以定時調整為榮,以放棄維護為恥。

5Why分析

(一)

Q: 誰需要學習編寫代碼的八榮八恥?

A: 項目中的開發人員、項目經理、架構師

(二)

Q: 為什麼學習編寫代碼的八榮八恥?

A: 可以作為實際代碼編寫和review(複查)的指導規範

(三)

Q: 什麼人什麼時候需要review代碼?

A: 

對開發人員來說,需要在時間允許的條件下定期的review自己和別人的代碼,加深對項目的整體理解。對自己的成長做總結。如果過了一段時間,還看到自己之前的代碼,覺得寫的很好的話,就需要質疑自己的成長,更努力的學習了。

對於項目經理和架構師來說,鼓勵所有上線的功能都每周抽出時間來做個組內review。或者定期抽取一些模塊做review。鼓勵大家重構代碼。在review過程中,作為領導者需要對大家有輸出,對代碼怎麼寫是更好的有一些理論基礎。這時候就需要使用編寫代碼的八榮八恥作為review的指導規範。

(四)

Q: 怎麼用作review的指導規範?

A: 八榮八恥中不但介紹了每個條目的意義,而且有通俗易懂的代碼實例便於和實際中的代碼在頭腦中做對比。文中明確的指出了哪些寫法是鼓勵的、哪些是不鼓勵的,是基於什麼理由不鼓勵這樣做。

(五)

Q: 編寫代碼的八榮八恥對於高可用有什麼意義?

A: 我利用美團的內部運維平台對自己參與過的項目可用性做過統計。將影響可用性的case(具體事件)分成:開發因素和設計因素。開發因素包括系統bug、開發不規範、上線不規範、監控報警不及時(影響可用性的恢復時長)等由於具體開發者在設計階段覆蓋不到的階段發生的。設計因素包括機器故障、網絡中斷、異常流量、中間件故障等可以通過設計做容災的。結果95%以上的可用性問題都是開發因素造成的。編寫代碼的八榮八恥是對避免開發因素產生可用性問題的指導規範。

 

編程風格:以運用風格為榮,以隨意編碼為恥

引子

在工作中,經常發現有些程序員用面向對象的語言寫出了面向過程的代碼而自己並沒有感覺到:

前面提到有個java軟件工程師,叫Margaret。她對工作有三個要求:錢多、有趣、離家近。HR想針對這些要求和她具體溝通,問她最低標準是什麼。每一項最低要求回復一個星級。

 

星級

錢多

有趣

離家近

1

年薪10萬

出差+旅遊占工時1%

40公里

☆☆

2

年薪20萬

出差+旅遊占工時10%

20公里

☆☆☆

3

年薪50萬

出差+旅遊占工時20%

10公里

☆☆☆☆

4

年薪100萬

出差+旅遊占工時50%

2公里

☆☆☆☆☆

5

年薪500萬

出差+旅遊占工時80%

1公里

Margaret在外地,所以用了一個常用的數據交換格式json給HR回復如下:

{"moreMoney”:4,"moreFun”:2,"closerToHome”:3}

拿到這個回復時面向過程的解析方式是這樣寫的:

Map json = (HashMap) JSONUtils.parse("{\"moreMoney\":4,\"moreFun\":2,\"closerToHome\":3}");
int moreMoney = (int)json.get("moreMoney");int moreFun = (int)json.get("moreFun");int closerToHome = (int)json.get("closerToHome");

接收方將接收到的數據轉成了json,代碼里一堆get完成了功能。為什麼說這是面向過程的呢?map是一種數據結構,沒有直接的業務意義。功能實現了,表達的意義卻不清晰。

這段代碼更好的一個實現方式是將接收的數據結構定義成一個對象,在java里可以使用jackson等工具直接將json轉成有業務含義的對象。

ObjectMapper objectMapper = new ObjectMapper();
Requirement requirement = objectMapper.readValue("{\"moreMoney\":4,\"moreFun\":2,\"closerToHome\":3}",JavaSoftwareEngineerMargaretRequirement.class);

這樣做,HR拿到的requirement不是一列列数字,需要自己對核對每一項都是什麼意思。而是一個有完整語義的對象,利於理解。而以這種思路來進行編寫的代碼我經常稱他們叫面向對象風格的代碼。

WHY

來看一段寫赤壁山旅行的文章:

今天有幸登上赤壁山,看到這山上的景物,不禁想起了當前戰場上廝殺的場面。想起當年要不是周瑜運氣好,大冬天颳起了東風,恐怕吳國就被曹操滅了。

再來看唐代詩人杜牧經過赤壁山這個著名的古戰場,有感於三國時代的英雄成敗而寫下的《赤壁》:

折戟沉沙鐵未銷,自將磨洗認前朝。

東風不與周郎便,銅雀春深鎖二喬。

前兩句意思是在沙子底下找到一隻斷戟,磨洗之後發現“made in 赤壁”。讀者讀了這兩句不禁會聯想起當前戰場上廝殺的場面吧。

后兩句意思是要不是周瑜運氣好,大冬天颳起了東風。那孫權的老婆大喬和周瑜的老婆小喬這兩位絕世美女都要被曹操這個色老頭關進銅雀台了。因為曹操久仰大小喬的美貌,提前為二人修築銅雀台,作為打敗吳國的戰利品。

杜牧隻字未提戰場和如果沒有東風的運氣,將會亡國的下場。但是讀者卻能心領神會,印象深刻。這是因為杜牧採用了以小見大的風格手法。

代碼與代碼的區別如同文章與文章的區別。能否讓讀者以更短的時間、更輕鬆的讀懂?代碼是給人整體感還是噁心感?這些都決定了代碼的可維護性。而它和系統可用性、穩定性的最直接關係在工作中非常常見:“爺爺的!這是誰寫的代碼這麼爛?忍不了了,老子不幹了。”而這個代碼的作者之所以離職也是因為忍受不了自己的爛代碼。頻繁的人員更替,新接手人員要有學習的成本。成本就包括要踩坑來加深對系統的理解。

HOW

除了開頭提到的面向對象的風格,編寫java代碼時下面三種風格也很常見。

1.fluent風格

fluent風格的代碼常以Builder結尾。比如StringBuilder就是典型的fluent風格。定義一個人的對象,這個對象使用fluent風格代碼這麼寫:

public class Person {    private String name;    private int armCount=2;//胳膊數默認為2 private int legCount=2;//腿數默認為2
    public static Person builder() {        return new Person.Builder(); }
    public Person setName(String name) {        this.name = name;        return this; }
    public Person armCount(int armCount) {        this.armCount = armCount;        return this; }
    public void legCount(int legCount) {        this.legCount = legCount;    }}

如上,就是每次給對象賦屬性的時候同時返回對象本身。這樣調用的時候:

Person.builder().name("Jane").armCount(2).legCount(2);

這樣寫的好處是比每個屬性都用一句set簡潔。在屬性多的時候,用構造函數。調用時容易表達不清楚屬性的含義。方法名起到了解釋的作用。現在流行的做法是代碼即註釋,註釋不用在每個方法都寫。這時候能表達自身意義的代碼就更加重要。注意:我們也可以保留setXXX、getXXX的命名規範,因為jackson等序列化反序列化的組件會根據set、get方法對參數賦值,上面的明明風格在序列化時會有問題。

當然,這個類也可以直接用lombok註解得到。

@Data@Buildrpublic class Person {    private String name;    private int armCount=2;//胳膊數默認為2    private int legCount=2;//腿數默認為2}

2.lambda函數式編程風格

lambda函數式編程比傳統的命令式編程更加簡潔。比如:現在有一群人。

List<Person> personList = Lists.newArrayList();
personList.add(Person.builder().name("Jane"));personList.add(Person.builder().name("Joe").armCount(1));personList.add(Person.builder().name("Stark").legCount(1));

要找出所有的殘疾人:

List<Person> disabledPersonList = Lists.newArrayList();for(Person person : personList) {    if(person.legCount()!=2 || person.armCount()!=2) {        disabledPersonList.add(person);    }}

或者使用lambda函數式編程:

List<Person> disabledPersonList = personList.stream().filter(person -> person.legCount()!=2 || person.armCount()!=2).collect(Collectors.toList());

3.設計模式風格

1995年,GoF(Gang of Four,四人幫)合作出版了《設計模式:可復用面向對象軟件的基礎》一書,共收錄了23中設計模式,人稱“GoF設計模式”。這23種設計模式的本質是面向對象設計原則的實際運用,是一種最佳實踐。

在各種java源碼中,經常看到以設計模式命名的類名和方法名。在我們日常編碼中,設計模式也非常實用。設計模式風格的例子請參考:平時代碼中用不到設計模式?Are you kidding me?

總結

寫有技術追求的代碼

 

相關閱讀

編寫代碼的「八榮八恥」- 以用戶易用為榮,以複雜歧義為恥

編寫代碼的「八榮八恥」- 以開關上線為榮,以自信編碼為恥

編寫代碼的「八榮八恥」(上篇)

 

【精選推薦文章】

自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"