請問戴維.帕納斯( David Lorge Parnas 軟件工程專家) 先生: 您認爲未來會有什麼使人興奮的軟件工程技術出現嗎? 前端
戴維·帕納斯: 最有用的技術不在未來,而是已經出現好些年了,只不過咱們沒好好用。面試
不少學生學了一些編程語言,讀了一些技術博客,通常都豪情萬丈。他們作一個項目巴不得展示本身生平所學。編程
再加上前沿技術,作一個轟動的創新。這當然值得鼓勵,不過實踐代表,這些每每都不能成功。後端
咱們來看下成功的例子,他們是怎麼作的,例如Linux剛開發的時候:設計模式
I'm doing a (free) operating system(just a hobby, won't be big and professional like gnu) for 386(486) AT clones。網絡
我正在爲 386(486) AT clones 編寫一個操做系統(只是一個業務愛好而已,沒有GNU那麼專業和龐大)。前端工程師
管理學大師彼得·德魯克 : Those entrepreneurs who start out with the idea that they'll make it big - can be guaranteed failure.架構
那些一開始就覺得本身會作大的企業家確定會失敗。運維
開始本篇的時候,我想起個人初中,個人初中是在鎮上唸的,一週回去一次,我老是信心滿滿的指定了一個目標,回家把我背的書都看一遍,而後每次我都把全部的書都背了回去。可是你知道小孩子經常抵禦不了誘惑,好比睡懶覺,玩遊戲。當我睡醒的時候,個人小夥伴就來了,而後咱們就去打遊戲。再上學的時候,我就把這些書在背到學校。這種重複性工做應該是持續了個人初中時代。想來那個時候,揹回來的書一本沒看的理由就是我制定的目標太過龐大,讓我以爲很難完成,可是那個時候的我顯然沒有意識到這一點,每次週末,我將書桌中的書所有裝進書包的時候,我都是滿心歡喜,幻想着本身在家裏看書會讓家裏人開心。 編程語言
我又想起我上大學的項目比賽,項目組長想作一個美妝社區商城,定位上大體相等於小紅書和淘寶的結合體,介紹一下咱們當時的技術背景: Servlet、JSP、JQuery、MySQL(基本SQL語句的編寫),當時剛學完這些,信心爆棚。但當時有沒有完成小紅書加淘寶的結合體的設計目標呢! 應該是沒有完成的,大體至關於咱們的目標是建一棟樓,最後建了一個風雨飄搖的茅草屋,只等"八月秋高風怒號",就「卷我屋上三重茅」。
我想假若目標太過龐大,總會讓人產生莫名的畏懼心理,常規的作法就是將龐大的目標拆解爲若干個看起來容易實現的小目標。
我想解決大問題當然讓人感受美妙,可是把小問題真正解決好,也不容易。
人們在實踐中碰到的需求是常常變化的,軟件設計的許多原則是從實踐而來,這些原則正是爲了在不斷變化的需求中保證程序的可維護性和效率。咱們以兩個軟件設計原則爲例,第一,單一職責原則(Single Responsibility Principle,SRP)指出:
一個模塊(類)應該是隻有一個致使它變化的緣由,一個模塊應該徹底對某個功能負責。
軟件設計的經典著做《敏捷軟件開發:原則、模式、實踐》分下下面的例子:
一個處理正方形的模塊有兩個功能: ① 計算面積 ② 畫出這個正方形。
這個設計讓一個模塊負責兩個不一樣的職責: 進行幾何運算(與顯示圖形無關)和圖形界面繪出正方形。若是一個集合計算的程序須要使用這個模塊,那麼它就須要同時包括圖形顯示的部分(由於是在同一個模塊中),這是一種浪費,同時引入了沒必要要的依賴(由於圖形顯示和圖形底層實現相關),妨礙了可移植性。另外,幾何計算需求的改變和圖形顯示需求的改變都會致使這個模塊發生的變化,增長錯誤發生的風險。
另外一個例子描述了一個調制解調器的API界面:
Interface Modem{ public void dial(String pno); // pho means port number dial 撥號 public void hangup(); // hangup 掛斷 public void send(char c); // send 發送 public void recv(); // recv 接收信息 }
調制解調器這個詞有點生僻,可是調制解調器是Modem的義譯名,它的音譯名你們可能更爲熟悉—貓. Modem,實際上是Modulator(調製器)與Demodulator(解調器)的合成詞。調製是將數字信號轉成模擬信號,解調是將模擬信號轉成數字信號。
我在看到這個例子的時候,將Interface理解爲接口了,我在寫的時候,還在前面加上了public,若是是接口中的話,上下文就不通順,由於上面說的是"描述了一個調制解調器的API界面",Modem是調制解調器的意思,Interface如是理解爲接口的,那麼這說不通。
因此"調制解調器的API界面" 應該是這麼理解的,Interface是界面,Modem是調制解調器。花括號中的是API(Application Programing Interface). 接着又說道:
這個界面作了兩類緊密相關的事情,鏈接管理(dial,hangup)和數據通訊(send,recv),他們是兩類職責,仍是一類?
結論是: 根據具體狀況分析,要看需求的變化是否致使這些操做同時變化。
後半部分講的,個人理解是這個單一職責沒有廣泛的斷定準則,要根據需求來去斷定。那"這個界面作了兩類緊密相關的事情"該怎麼理解呢? 我理解的界面是下面這樣:
可是上面不是講的是模塊嗎? 這怎麼又講到界面了呢? "這個界面作了兩類密切相關的事情",該怎麼理解這句話?
我翻閱了《敏捷軟件開發:原則、模式與實踐》, 發現我可能過分理解了,這是《構建之法—現代軟件工程》做者的筆誤,上面的那個Modem的例子在《敏捷軟件開發: 原則、模式與實踐》就是Java中的接口。
那麼鏈接管理和數據通訊應該算兩種職責,仍是算一種? 換種說法,拿吃飯這件事來講,操縱筷子和將食物放到嘴裏,從吃飯的角度來講,這兩類緊密相關的事情應該是劃分到一個模塊中。這種理所固然的劃分來源於咱們對吃飯這件事的準確理解,可是對於其餘方向的需求呢?彷佛沒有絕對的說法,要根據具體狀況分析,要看需求的變化是否老是致使這些操做同時變化。
寫到這裏忽然想起微服務,起初的軟件比較簡單,代碼量比較少,業務比較簡單,用戶量比較少,因此起初的軟件大多先後端一體。隨着硬件的快速發展,互聯網的不斷普及,軟件逐步的再向複雜的靠近的同時,代碼量也在不斷的膨脹,軟件開發完成又不是一錘子買賣,不像房子賣出去以後,跟開發商就關係不大了。軟件開發以後還要考慮維護,這一方面軟件的開發建設很像是城市建設,假如城市建設涌入了大量的人口,許多人認爲這裏在這裏可以賺到錢,城市的執政者就會着手對城市進行擴建以適應當前城市的發展。也像是社會的發展,過去的封建社會的縣令類比到如今的話是沒法類比的,過去的縣令身上集合了不少職位,這也許跟過去縣的人口比較少有關係,隨着人類社會的不斷進步,原先集中在縣令身上的職位被分解。這也就是對應到了微服務架構上,咱們將本來集中在一個服務的應用進行拆解,將其拆解爲若干個微服務,每一個服務貫徹單一職責,對外提供的服務儘可能不存在重複,這也就印證了本文開頭引用的戴維.帕納斯那句話:
最又用的技術再也不未來,而是已經出現好些年了,只不過咱們沒有好好用。
我想起剛學Spring Boot的我,我是在B站看的顏羣老師的視頻: SpringBoot視頻教程(入門篇)),在視頻的開頭就講到了微服務,說這是目前流行的軟件架構,我當時還頗感到新奇,也許是當時並無開發過大一點的工程,開發過的工程代碼量都比較少。當時的想法是微服務架構比較適合大型項目,便於擴容和維護,可是這幾年微服務彷佛正在成爲一種新常態,當初的論斷仍是正確的嗎?以我目前的見解是,微服務架構正在下沉,目前業界是看重微服務架構的擴容和易維護嗎? 彷佛並不全是,我我的的見解是看重服務的重用能力,好比認證服務,開發一次,再次開發新項目的時候就沒必要再開發了,重用以前的服務就好。
這種單一職責也能夠從Web軟件工程師的分工能夠一見端倪,咱們知道許多計算機的硬件能力大體以每兩年提升一倍的速度發展。可是軟件開發的流程卻沒有這樣的提速過程,開發成本也沒有降低,本來前端、後端、運維、DBA集一體的Web工程師被拆成四個職業:
運維
當前的Web開發領域,DevOps十分流行,DevOps是一個合成詞,是Developers(開發者)和Operators的結合,即開發和運維團隊一體化。本來從後端仔身上划走的運維,又從新回到了後端仔身上。
這是一種拿着錘子看全世界都是釘子的想法嗎?彷佛是,又彷佛不是。拆分與分工再人類世界能夠隨處見到,當你開始創業,起初只是小買賣,你大能夠沒必要請會計註冊公司,就像農村的小飯店同樣,你能夠是老闆也能夠是廚師,同時還能夠是服務員和會計、保潔。可是你沒想到你的廚藝很不錯,好吃不貴,價錢實惠,很快你的飯店就吸引了不少人來,慢慢的你發現,一人身兼多職讓你支撐不住,人實在太多了,爲了避免讓你父母受累,你開始招人,將你身上的保潔和服務員的職責分配出去,你仍然不想請會計,你以爲的你的生意尚未那麼大,漸漸的你發現一個廚子彷佛有點支撐不了當前的用戶量,你天天仍然被累的半死,因而你開始琢磨着請廚子、招學徒,讓他們負責炒菜,你負責進菜,可是彷佛仍是要招洗菜的,讓廚子洗菜又作菜,廚子不看堪重負,廚子提出了抗議。
因而你開始招洗菜的,很快你就想開分店,你想擴大你生意的規模,可是天天的賬讓你有點頭疼,你不想將本身的精力太過集中在算帳上。因此你請了個帳房,這個故事還會發展下去,你會請更多的人,來承接你身上的工做,可是你在分配工做的時候,兩我的之間的工做會盡可能不出現重合,你不會想讓一我的既作廚子又作帳房,你但願這我的儘量的專業。
另外一個重要的軟件設計原則時開放—封閉原則(Open-Closed Principle,OCP)
軟件實體應該是能夠擴展的,同時是不可修改的。
具體的說:
- 容許擴展(Open for extension)。當應用的需求發生改變時,咱們能夠對模塊進行擴展,從而改變模塊的功能
- 不容許修改(Closed for modification)。對模塊行爲進行擴展時,沒必要改變的自己
那何時該使用這些原則呢? 《敏捷軟件開發: 原則、模式與實踐》一書也同時指出:
變化的軸線僅當變化實際發生時才具備真正的意義。若是沒有徵兆,那麼去應用SRP,或者其餘原則都是不明智的。
遵循OCP的代價也是昂貴的....., 顯然,咱們但願把OCP的應用限定在可能會發生的變化上。......最終,咱們會一直等到變化發生時才採起行動。
個人理解是這些原則應對的是變化,開發者根據本身的經驗進行了預測認爲這裏會發生變化,這須要設計人員具有一些從經驗中得到的預測能力,有經驗的設計人員但願本身對用戶和應用很理解,可以以此來判斷各類變化的可能性。而後,他能夠設計對於最有可能發生的變化遵循OCP原則。
要預測準確這一點很不容易作到,由於它意味着要根據經驗猜想那些應用程序在生長曆程中有可能遭受的變化。若是開發人員猜想正確,他們就得到成功。若是他們猜想錯誤,他們會遭受失敗。而且在大多數狀況下,他們都會猜想錯誤。
咱們該如何遵循OCP呢? 咱們來看下面一個例子,以這個例子來講明OCP:
這個例子一樣來源於《敏捷軟件開發:原則、模式與實踐》,書上用的是C++來描述OCP,這裏我改裝成了Java。咱們能夠很容易的看出,若是咱們再增長一種形狀,Shape類中的drawAllShapes方法不用改動,就能畫出新的形狀。圖中打印能夠理解爲繪製對應圖形的行爲。
這種設計是符合OCP的,無需改動本來的代碼,咱們就完成了擴展,還不會引起連鎖改動。這讓我想起了工廠模式,不懂什麼是工廠模式的,能夠參看個人文章: 歡迎光臨Spring時代-緒論](https://juejin.cn/post/692755...
假設再增長了一種Excel類型,這個WorkBookFactory還須要改動,我想並不能這麼說,我想是POI的開發者認爲Excel的變化不會那麼強吧,不會每一年都增長一種文件格式,這來源於POI的設計者對Excel的瞭解,這是一種預測,同時也是在減小開發者使用POI的心智負擔,過分的設計致使軟件趨於複雜,增長維護和使用成本。
上面的Shape是完美的符合開閉原則的嗎? 彷佛並不全是,咱們徹底能夠提出一個需求,讓drawAllShapes方法不得不修改,《敏捷軟件開發:原則、模式與實踐》 給出的變化是要求全部的圓必須在正方形以前繪製,在這種狀況下,DrawAllShapes彷佛沒法不對這種變化作到封閉,若是你說你能夠在傳入List參數的時候,將圓放入正方形以前,不就能夠了嗎?那我這裏就再提出一個需求,要求將正方形和圓形繪製在一塊兒。除非你是穿越過來的,那麼總有你沒有預測到的狀況,不管模塊你作的是多麼的"封閉",都會存在一些沒法對之封閉的變化,沒有一種模型能夠應對全部的變化。
既然沒法作到絕對封閉,那麼就必須作到有策略地對待這個問題。也就是說,設計人員必須對於他設計的模塊應該對那種變化作出選擇。
開發者必須先猜想出哪一種變化是最有可能的,而後構造抽象來隔離那些變化。
這讓我想起了設計模式,你們認爲這個頗有用,國內的面試也很看重,這在某種程度上致使了設計模式的濫用,開發者在未對具體的業務比較熟悉以前,就開始展開了預測,開始應用設計模式,這很容易形成項目種的設計模式滿天飛,我想起我前公司的資深架構師對我說的話,那天他看見我在看設計模式,便對我說,對於我這種程度的人來講,設計模式無助於我代碼功力的提高,他這麼多年的開發,也沒用過幾回。
對業務要熟悉,這可讓你洞悉變化,預測變化,構建抽象應對變化,能夠避免改動產生連鎖反應,你總不但願,以前開發完成的東西,再改一遍Bug吧。對於開閉原則我以前的理解是,假如這個部分的代碼已經上線平穩運行了,那麼就儘量的不要去改動他,那麼這裏又補充了 一下開閉原則,即根據經驗預測變化,而後構建抽象,應對變化。可是應當謹慎的引入,這回增長閱讀成本和維護成本,可是我經常收到的反駁意見是,我引入又不會出什麼事,又怎麼啦。
單一職責我以前的理解是方便重用,假如一個方法完成了A和B這兩件事,我假設由於某種須要須要用到A,不須要用到B,那麼這個方法我就永不了,同時也讓閱讀變得簡單,其實我當初的想法是能夠涵蓋在變化中,我預測未來我會用到A,我作出的應對策略是將方法作的儘可能效一點。
當你可以度量你所說的,而且可以用數字表達它時,就表示你瞭解它,若你不能度量它,不能用數字去表達它,那說明你的知識時匱乏的、不能使人滿意的。—開爾文勳爵(英國物理學家)
作項目是會有工期的,經常會讓開發者去估計工時,這是讓我頗爲頭疼的一個問題,我項目經理讓我估計的時候,個人腦殼是一片空白,我當時的想法是這要估計少了要加班,估計多了項目經理又不滿意,因而我就請教當時公司中的前輩,我把個人擔憂如實說了,前輩表示沒事,時間不夠再要。可是我仍是想估計一下,我想對本身的開發能有一個衡量。
"估計"這一技術看似容易,其實大有學問,當約翰·巴克斯啓動FORTRAN項目後,他的上司會按期詢問完成日期,他老是給出一樣的答覆:"六個月"。但實際上,項目一共花了近三年的時間。
再開始估計以前,咱們須要弄清楚幾個概念:目標、估計和決心。單獨拎出來這三個詞,咱們是不會混淆的,可是在實際應用中,咱們卻很容易混淆這三個詞。
估計: 以當前瞭解的狀況和掌握的資源,要花費多少人力物力實踐才能實現某事。
注意前半句以當前瞭解的狀況和掌握的資源,我想起我上初中的時候,比較喜歡看網絡小說,印象很深的一部小說是《鬥破蒼穹》,吞噬異火的把握,這也是一種估計。我當時很想學會這種估計,好比咱們上初中的時候,有一次是初中週四放假了,
下週我就滿懷指望的也但願週四也放假,我當時的估計是當時的天氣,就像模像樣的估計了一個八成機率。可是最後仍是沒有放假。
這種狀況在軟件項目中也能夠看到,軟件項目中的延遲更是比比皆是—爲何咱們估計得不許呢?由於難麼?爲何軟件估計這麼難呢?其實全部的估計都難,若是你只是瞎估計,沒有找出估計後面的假設的話。不信的話,咱們作一些估計的練習,不用搜索引擎,你估計一下下面的數目(數量級正確就行)
怎麼樣?你的估計和實際狀況差幾個數量級? 可是若是你把你作出估計的依據,也就是假設列出來,那這是否是會讓你對本身的判斷更加自信呢。軟件工程專家Paul Rook說: 咱們其實並非不會估計,咱們真正不會的,是把估計後面藏着的假設所有列舉出來。咱們平時的估計還有一個維度就是參考前人的經驗。軟件工程師在長期的實踐中,也摸索出一套經驗公式:實際時間花費主要取決於兩個因素—對某件事的估計時間X,以及他作過相似開發的次數N。
Y = X ± X ➗N // Y是實際時間花費、中間的±表示加上或減去。
例如剛畢業的你被分派到了一個用戶管理模塊的任務,你估計須要三天,可是你歷來沒有作過用戶管理模塊,因此N就是0,高中數學告訴咱們,0不能當除數,可是大學數學告訴咱們極限狀況下就能夠,因此你花費的時間是3+無窮大?也就是說在項目給定的時間內根本沒法完成,或者是3-無窮大? 不但沒有完成任務,還寫了不少bug來,讓團隊花更多的時間來處理,把項目拖垮。
注意這是個經驗公式,因此跟實際花費時間存在偏差是很是正常的事情,這也就須要你在完成需求以後,分析緣由,以便減小偏差,假如一直作相同的項目,估計值會愈來愈準確,由於愈來愈熟練。可是也不會有人一直願意作重複的是事情,這會讓人厭倦。
寫到最後的時候忽然以爲本文不徹底像是《構建之法—現代軟件工程》的讀書筆記了,在寫軟件設計原則的時候,參考了一下《敏捷軟件開發: 原則、模式與實踐》,這在《構建之法—現代軟件工程》種只花了兩頁來介紹,可是主要內容仍是來自於它,也糅合了本身的若干感悟,但願會對諸君有所幫助。封面是在海南度假的時候,別人拍的,感受很漂亮。