如何設計一款優秀的軟件架構

jopen 9年前發布 | 33K 次閱讀 架構
 

“風語者客服+”是針對中小型企業推出的客服SaaS,節約了企業自建客服系統所需的巨大成本。為了給企業提供穩定可靠且優質的服務,我們在整體架構上費盡心思。雖然不盡完美,希望借此拋磚引玉,互相切磋。

前言

”Look deep into nature, and then you will understand everything better.“ -- Albert Einstein

我國傳統文化上,要做成一件事,講究三個方面:明道,優術,取勢。在軟件架構設計方面而言,也是類似的道理:遵循自然規律以明確大的方向,使用優秀的實操戰術,再根據實際情況落地。

這是個快餐年代,幾乎所有人都只做一件事 -“取勢”。 幾乎沒有多少人會去理解一個Servlet的工作原理,去理解一次HTTP請求的完整流程,因為有超多框架幫你屏蔽了這里的細節。詢問一個人會什么技術, 回答也往往是我會Hibernate、Spring、Ibatis、會PullToRefresh組件、會使用SDWebimage。不過這些框架 (Framework)其實并不是軟件架構。軟件架構是一所有生命力的房子,而這些框架只是大一點的板磚。

因為筆者水平有限,這里只提一些普遍準則,也就是”正確的廢話“,以饗視聽。不會深入到實操戰術上,比如怎么用Spring實施MVC架構,怎么 使用Maven管理依賴,Redis的常用操作,怎么搭建一個負載均衡的集群,如何使用阿里巴巴的Dubbo框架進行服務化等等。如果大家有興趣,可以自 行搜索,有很多優秀的文章可供參考。

不幸的“程序猿”和“程序媛”各有各的痛苦,幸福的程序員都是相似的。其實說幸福有點言過其實,下面就說說怎么讓他們不那么痛苦。

一. 很好的模塊化支持

“At the bottom of every person's dependency, there is always pain, Discovering the pain and healing it is an essential step in ending dependency.” --Chris Prentiss

他們都在一個相對穩定的軟件架構里編碼,自己的代碼不會依賴很多模塊,不會因為自己微小的改動造成全局的失敗。正如"1984"中的老大哥說的,Ignorance is strength(”對外界的“無知就是一種力量).  任何一個模塊都不能有太強的存在感。

曾經在一個大型互聯網公司里面,任何人只要用到一個核心模塊的功能,就必須依賴一個部署在某遠程服務器的庫,而且還有IP限制,只能把代碼部署到 指定網段才能運行起來。導致基本上沒法在本地進行單元測試或者簡單調試。這個核心庫的存在感太強,就成了開發的瓶頸,嚴重的降低了生產力和碼農的幸福程 度。

在“風語者客服+”的架構中,每個碼農都可以很方便的在本地把服務啟動起來,一分鐘up and running,隨便做一些改動就可以立竿見影的看到效果。這里要歸功于幾個東西:

1.Git代碼管理

在團隊作戰中,每個程序員可以取下來完整的最新代碼庫,也可以在本地分支上盡情揮毫潑墨,而不擔心影響別人的工作。也可以把本地修改先stash起來,review一下別人的代碼,再unstash恢復回來。要想提高團隊效率,代碼倉庫管理建議盡快遷移到Git上。

2.Maven、Gradle、Cocopods等依賴管理

Maven是一個管理依賴(Dependency)的工具,現在在Java社區應該是比較普及的,無法想象現在還有團隊直接拷貝jar包來管理依 賴。雖然早期沒有Maven的時候,都是拷貝jar包這么過來的,碰到的問題也是顯而易見的,依賴的jar包作者改了某個bug,沒能及時傳導到調用方。 多個調用方使用不一致的jar包,導致各種奇異bug。對應的在安卓社區,使用gradle的比較多,iOS的Objective-C開發中,多采用 CocoaPods。

二. 高內聚,低耦合

He should focus on his knitting, not trying to do everything. Do one thing well.-- Steve Jobs

"Do one thing well"其實不算是老喬的專利,UNIX哲學和Google哲學都提倡這一點。這句話本身不完全對,比如對于一個商人,如果只會Do one thing well,那他無法在市場中存活,但是在工程師中卻是萬般推崇的哲學。

我們可以期望一個人具備一百種技能,然而對一個工具只期望它把一個需求解決好解決徹底,對于實現工具的一個類,一個方法,更是如此。但是,實際經 驗中,我們經常看到一個5000行以上代碼的類,活像一個巨人版的瑞士軍刀,什么都能做,但是什么都做不好。這就是”Separation of Duty"沒有做好的典范。

在風語者”客服+“對外提供的SDK和API中,我們也提倡同樣的思想,力爭把App使用”客服+“SDK的門檻降到最低,每個API都能自言其 一,而且API直接沒有時序上的依賴關系。內部各個模塊的開發,也秉承同樣的責任分割原則。責任分割原則的落實,沒有什么好的框架或者工具來支持。只能通 過老鳥經常去做Code Review,找出存在的問題,提出重構方案,并督促菜鳥改進。

個人一般采用的重構思路,僅作為參考,照搬后被老板批評乃至造成工傷概不負責:

1.把一個大的工具類,根據主題不同,拆分成若干個互不干擾的高內聚工具類;舉個例子,一個萬能的NetworkUtils可能可以拆成HttpUtils, FTPUtils,TelnetUtils等;

2.對于一個被頻繁調用的類,仔細觀察調用情況,如果有一些方法的被調用頻率遠遠低于其他方法,那么需要考慮這個方法是不是應該放在這個類中;

3.存在A,B兩個類之間的相互依賴,或者更多類的混亂依賴,那么就更要抽絲剝繭,通過合理安排類的功能來去除環形依賴;

4.嘗試一句話說清楚一個類的功能,不要使用“和”,“以及”,“或者”等連接詞;如果出現了這些連接詞,就需要引起重視;

三. 用進化擁抱變化

“It is not the strongest or the most intelligent who will survive but those who can best manage change.” ― Leon C. Megginson

前段時間,朋友圈瘋傳一篇文章 -——“架構腐化之謎”,大家都深表同感,紛紛表示對自己架構的未來的擔憂。然而,說句不合時宜的話, 90%的擔憂是杞人憂天,因為以現在產品更新換代的速度,90%的項目面市即意味著死亡,沒等到架構腐朽,產品已經入土了。

剩下10%里面,也許有9%會一直堅持活下去,但是不會蓬勃發展,也就是說,只要保證不出現內存泄露之類的問題,代碼就會一直在幾臺小服務器上運 行下去,哪怕后面沒有人維護也沒關系。只有1%的產品,會日新月異的更新迭代,最終成長為巨無霸,或者巨無霸的生態下的一個環節。這個言論看似悲觀,卻是 對現實最好的妥協。

謬用一下泰戈爾的名言:“不是槌的打擊,而是水的載歌載舞,使鵝卵石臻于完美”, 不是閉門造車的架構,而是不斷擁抱變化的需求,才使得架構臻于完美。

假如在早期就糾結于架構的完美性,而延遲產品的交付,是非常得不償失的。只有生存下來,才有機會。再根據市場變化,不斷優化架構,從而延長軟件的生命周期。那么,假如撞大運,真的成了這1%,怎樣做才能算是擁抱變化?

首先,請參考本文第一點和第二點。如果這兩點基本功沒有練好,那么談架構的進化就和還沒有通關十八羅漢的新手就想練成九陰真經是一個道理。

在設計之初,初步考慮系統的Scalability(可伸縮性)

下面在第四點會詳細闡述。

內部的各個模塊盡量做到可插拔

一方面是接口和實現的分離,可以隨著需求的變化更換實現;另一方面,盡量把功能服務化,成為微服務,并且可以監控到服務的互相調用情況,當某個服務老化,可以逐步廢棄或使用新的服務取代之。這一點上,阿里巴巴的Dubbo框架是一個不錯的選擇。

盡量采用優秀的框架,站在巨人的肩膀上

例如在Web層面,我們使用推ter的Bootstrap前端框架來實現響應式Web編程,提高生產效率的同時減少了為解決各種設備適配問題的投入。當然,這就需要設計師配合,按照Bootstrap規范來設計頁面,減少一些個性化設計。

最后,考慮系統的Resilience(彈性,也叫耐受性)

俗一點說,就是變成一只打不死的小強,代碼中盡量提前預判可能遇到的各種情形。經常看到代碼里面有一堆的if(){}判斷語句,我就問作者,“你 考慮過else{}嗎?”一般回答都是,“這絕對只有if,不會有else的”,可如果真的遇到else怎么辦?千年蟲問題就是這么誕生的。可能很多新同 學還不知道什么是千年蟲問題,簡單地說,就是當年的碼農,為了省一點內存空間,只用了2位數來表達年份,比如int year = 98; 表達1998年。我猜碼農當時的心態也是,“就我這代碼,還能活到2000年,搞笑吧?”

程序員們平時可以多擴大自己的腦洞,想想有哪些else情況自己沒有處理,而且可以輕易處理的。比如服務器掛了,那么App端是不是也要跟著crash,還是給出友好一點的提示,或者更友好一點,使用本地緩存。

四. 設計可擴展,但不要過度設計

it's better to have infinite scalability and not need it, than to need infinite scalability and not have it--@littleidea 網友

無限的擴展能力是一種奢望,但是起碼不能讓擴展能力成為0。試想一下,你辛辛苦苦為老板開發了一個網站,過了一個月,網站超負荷了,老板說,“小 A啊,之前2臺服務器花了我5萬塊,預計流量馬上要翻倍了,再給你5萬塊,幫我扛過去啊。”結果你發現,問題不是線性增加服務器就能解決的,原來的程序沒 有做分層(Web,Business Logic, Data Access等),導致加服務器也只能把所有層的代碼全搬到新的服務器,雖然只是Business Logic的計算有壓力,卻要浪費老板很多服務器。更糟糕的是,因為程序里面用到了文件系統和操作系統命令,不好做負載均衡。

這里有一些準則供參考:

1.代碼分層是必須的,層次明朗以后,當哪個層次的負載較重,想辦法對該層次進行優化或者擴容即可;

2.保持核心服務是無狀態的,所謂無狀態就是沒有和請求相關的數據依賴;

3.盡可能的選用已被驗證的廣泛采用的成熟基礎架構;

4.充分利用Zookeeper等集群管理工具,來對服務進行管理;

風語者“客服+”中,把業務相關的代碼內部組裝為風語者ServiceBox,使用阿里巴巴的Dubbo服務進行注冊管理。當負載增加時,可以迅速在運維層面增加服務節點,以提供更高的服務能力,從而保證客戶的優質體驗。

如何設計一款優秀的軟件架構

作者簡介:

黃耀華具有11年的計算機軟件研發及大型互聯網產品研發經驗,曾在IBM,百度,人人網等業內頂級企業任職,特別對企業級軟件,大規模數據處理,搜索引擎,移動App開發 等領域的原理和實現有著全面且深入的認識。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!