主頁 > 軟體設計 > 談談過度設計:因噎廢食的陷阱

談談過度設計:因噎廢食的陷阱

2022-12-03 07:36:52 軟體設計

 

 

引言

 

寫軟體和造樓房一樣需要設計,但是和建筑行業嚴謹客觀的設計規范不同,軟體設計常常很主觀,且容易引發爭論,

 

設計模式被認為是軟體設計的“規范”,但是在互聯網快速發展的程序中,也暴露了一些問題,相比程序式代碼的簡單與易于修改,設計模式常常導致代碼復雜,增加理解與修改的成本,我們稱之為 “過度設計”,因而很多人認為,設計模式只是一種炫技,對系統沒有實質作用,甚至有很大的挖坑風險,這個觀點容易讓人因噎廢食,放棄日常編碼中的設計,

 

本文將深入探索如下問題:

 

  • 為什么長期來看,設計模式相比程序式代碼是更好的?

  • 什么情況下設計模式是有益的,而什么情況下會成為累贅?

  • 如何利用設計模式的益處,防止其腐化?

 

設計模式的缺陷

 

“過度設計” 這個詞也不是空穴來風,首先,互聯網軟體的迭代比傳統軟體快很多,傳統軟體,比如銀行系統,可能一年只有兩個迭代,而網站的后臺可能每周都在發布更新,所以互聯網非常注重軟體修改的便捷性,其次,設計模式的 “分模塊”,“開閉原則” 等主張,天然地易于拓展而不利于修改,和互聯網軟體頻繁迭代產生了一定的沖突,

 

開閉原則的缺陷

 

開閉原則:軟體中物件應該對擴展開放,對修改關閉,

 

基于開閉原則,誕生了很多中臺系統,應用通過插件的方式,可以在滿足自身定制業務需求的同時,復用中臺的能力,

 

當業務需求滿足中臺的主體流程和規范時,一切看上去都很順利,一旦需求發生變更,不再符合中臺的規范了,往往需要中臺進行傷筋動骨的改造,之前看到一篇文章吐嘈 “本來業務上一周就能搞定的需求,提給中臺需要8個月”,

 

所以基于中臺無法進行深度的創新,深度創新在軟體上必然也會有深度的修改,而中臺所滿足的開閉原則是不利于修改的,

 

最小知識原則的缺陷

 

最小知識原則:一個物件對于其他物件的了解越少越好,

 

最小知識原則又稱為 “迪米特法則”,基于迪米特法則,我們會把軟體設計成一個個 “模塊”,然后對每個 “模塊” 只傳遞需要的引數

 

在程序式編碼中,代碼片段是擁有背景關系的全部資訊的,比如下面的薪資計算代碼:

// 績效
int performance = 4;
// 職級
int level = 2;
String job = "engineer";
switch (job) {
    case "engineer":
        // 雖然計算薪資時只使用了 績效 作為引數, 但是從背景關系中都是很容易獲取的
        return 100 + 200 * performance;
    case "pm":
        // .... 其余代碼省略
}

 

而如果我們將代碼改造成策略模式,為了滿足迪米特法則,我們只傳遞需要的引數:

// 績效
int performance = 4;
// 職級
int level = 2;
String job = "engineer";
// 只傳遞了需要 performance 引數
Context context = new Context();
context.setPerformance(performance);
strategyMap.get(job).eval(context);

 

需求一旦變成 “根據績效和職級計算薪資”,程序式代碼只需要直接取用背景關系的引數,而策略模式中需要分三步,首先在 Context 中增加該引數,然后在策略入口處設定引數,最后才能在業務代碼中使用增加的引數,

 

這個例子尚且比較簡單,互聯網的快速迭代會讓現實情況更加復雜化,比如多個串聯在一起模塊,每個模塊都需要增加引數,修改成本成倍增加,

 

可理解性的缺陷

 

設計模式一般都會應用比較高級的語言特性:

 

  • 策略模式在內的幾乎所有設計模式都使用了多型

  • 訪問者模式需要理解動態分派和靜態分派

  • ...

 

這些大大增加了設計模式代碼的理解成本,而程序式編碼只需要會基本語法就可以寫了,不需要理解這么多高級特性,

 

小結

 

這三點缺陷造成了設計模式和互聯網快速迭代之間的沖突,這也是應用設計模式時難以避免的成本,

 

程序式編碼相比設計模式,雖然有著簡單,易于修改的優點,但是卻有永遠無法回避的本質缺陷,

 

程序式編碼的本質缺陷

 

上文中分析,程序式編碼的優點就是 “簡單,好理解,易于修改”,這些有點乍看之下挺對的,但是仔細想想都很值得懷疑:

 

  • “簡單”:業務邏輯不會因為程序式編碼而變得更加簡單,相反,越是大型的代碼庫越會大量使用設計模式(比如擁有 2400w 行代碼的 Chromium);

  • “好理解”:程序式編碼只是短期比較好理解,因為沒有設計模式的學習成本,但是長期來看,因為它沒有固定的模式,理解成本是更高的;

  • “易于修改”:這一點我相信是對的,但是設計模式同樣也可以是易于修改的,下一節將會進行論述,本節主要論述前兩點,

軟體復雜度

 

軟體工程著作 《人月神話》 中認為軟體復雜度包括本質復雜度和偶然復雜度

 

本質復雜度是指業務本身的復雜度,而偶然復雜度一般是因為方法不對或者技術原因引入的復雜度,比如拆分服務導致的分布式事務問題,就是偶然復雜度,

 

如果一段業務邏輯本來就很復雜,即本質復雜度很高,相關模塊的代碼必然是復雜難以理解的,無論是采用設計模式還是程序式編碼,“用程序式編碼就會更簡單” 的想法在這種情況下顯然是荒謬的,相反,根據經驗,很多一直在采用程序式編碼的復雜模塊,最后都會變得邏輯混亂,缺乏測驗用例,想重構時已經積重難返,

 

那么設計模式會增加偶然復雜度嗎?閱讀有設計模式的代碼,除了要理解業務外,還要理解設計模式,看起來是增加了偶然復雜度,但是下文中我們會討論,從長期的角度來看,這不完全正確,

 

理解單一問題 vs 理解一類問題

 

開頭提到,設計模式是軟體設計的“規范”,和建筑業的設計規范類似,規范能夠幫助不同背景的人們理解工程師的設計,比如,當工人們看到三角形的結構時,就知道這是建筑師設計的支撐框架,

 

程序式代碼一般都是針對當前問題的某個特殊解決方法,不包含任何的 “模式”,雖然表面上減少了 “模式”的學習成本,但是每個維護者/呼叫者都要去理解一遍這段代碼的特殊寫法,特殊呼叫方式,無形中反而增加了成本,

 

以資料結構的遍歷為例,如果全部采用程序式編碼,比如二叉樹列印的代碼是:

 

public void printTree(TreeNode root) {
    if (root != null) {
        System.out.println(root.getVal());
        preOrderTraverse1(root.getLeft());
        preOrderTraverse1(root.getRight);
    }
}

 

圖的節點計數代碼是:

 

public int countNode(GraphNode root) {
    int sum = 0;
    Queue<Node> queue = new LinkedList<>();
    queue.offer(root);
    root.setMarked(true);

    while(!queue.isEmpty()){
        Node o = queue.poll();
        sum++;

        List<Node> list = g.getAdj(o);
        for (Node n : list) {
            if (!n.isMarked()) {
                queue.add(n);
                n.setMarked(true);
            }
        }
    }
    return sum;
}

 

這些代碼本質上都是在做資料結構的遍歷,但是每次讀到這樣的代碼片段時,你都要將它讀到底才發現它其實就是一個遍歷邏輯,幸好這里的業務邏輯還比較簡單,就是一個列印或者計數,在實際作業中往往和更復雜的業務邏輯耦合在一起,更難發現其中的遍歷邏輯,

 

而如果我們使用迭代器模式,二叉樹的列印代碼就變成:

public void printTree(TreeNode root) {
    Iterator<TreeNode> iterator = root.iterator();
    while (iterator.hasNext()) {
        TreeNode node = iterator.next();
        System.out.println(node);
    }
}

 

圖的節點計數代碼變成:

public int countNode(GraphNode root) {
    int sum = 0;
    Iterator<TreeNode> iterator = root.iterator();
    while (iterator.hasNext()) {
        iterator.next();
        sum++;
    }
    return sum;
}

 

這兩段代碼雖然有區別,但是它們滿足一樣的 ”模式“,即 “迭代器模式”,看到 Iterator 我們就知道是在進行遍歷,甚至都不需要關心不同資料結構具體實作上的區別,這是所有遍歷統一的解決方案,雖然在第一次閱讀這個模式的代碼時需要付出點成本學習 Iterator,但是之后類似代碼的理解成本卻會大幅度降低,

 

設計模式中類似上面的例子還有很多:

 

  • 看到 XxxObserver,XxxSubject 就知道這個模塊是用的是觀察者模式,其功能大概率是通過注冊觀察者實作的

  • 看到 XxxStrategy 策略模式,就知道這個模塊會按照某種規則將業務路由到不同的策略

  • 看到 XxxVisitor 訪問者模式 就知道這個模塊解決的是嵌套結構訪問的問題

  • ...

是面對具體問題 case by case 的學習,還是掌握一個通用原理理解一類問題?肯定是學習后者更有效率,

 

“程序式代碼更加好理解”往往只是針對某個代碼片段的,當我們將范圍擴大到一個模塊,甚至整個系統時,其中會包含大量的代碼片段,如果這些代碼片段全部是無模式的程序代碼,理解成本會成倍增加,相似的模式則能大大降低理解成本,越大的代碼庫從中的收益也就越大,

 

新人學習程序式編碼和設計模式的學習曲線如下圖:

 

 

 

程序式編碼雖然剛開始時沒有任何學習壓力,但是不會有任何積累,設計模式雖然剛開始時很難懂,但是隨著學習和應用,理解會越來越深刻,

 

設計模式防腐

 

前文中提到,互聯網軟體非常注重修改的便捷性,而這是程序式編碼的長處,設計模式天然是不利于修改的,但是程序式編碼又有著很多致命的問題,不宜大規模使用,我們如何才能在發揮設計模式長處的同時,揚長補短,跟上業務的快速演進呢?

 

腐敗的設計模式

 

有一條惡龍,每年要求村莊獻祭一個少女,每年這個村莊都會有一個少年英雄去與惡龍搏斗,但無人生還,


又一個英雄出發時,有人悄悄尾隨,龍穴鋪滿金銀財寶,英雄用劍刺死惡龍,然后英雄坐在尸身上,看著閃爍的珠寶,慢慢地長出鱗片、尾巴和觸角,最終變成惡龍,

 

以上是緬甸著名的 “屠龍少年變成惡龍” 的傳說,見過很多系統,最初引入設計模式是為了提高可維護性,當時或許實作了這個目標,但是隨著時間推移,變成了系統中沒人敢修改,“不可維護” 的部分,最終成為一個 “過度設計”,主要原因有以下兩點:

 

  • 無法除錯: 新的維護者無法通過除錯快速學習模塊中的 “模式”,或者說因為學習成本太高,人們常在沒有弄清楚“模式”的情況下就著手改代碼,越改越離譜,最終覆水難收

  • 沒有演進: 系統中的設計模式也是要跟隨業務不斷演進的,但是現實中很多系統發展了好幾年,只在剛開始創建的時候進行過一次設計,后來因為時間緊或者懶惰等其他原因,再也沒有人改過模式,最終自然跟不上業務,變成系統中的累贅,

 

可除錯的模塊

 

“模塊” 是軟體除錯的基本單位,一個模塊中可能會應用多種 “設計模式” 來輔助設計,設計模式相比程序式編碼,邏輯不是線性的,無法通過逐行閱讀來確認邏輯,除錯就是后來人學習理解設計的重要途徑,在理解的基礎上,后人才能進行正確的模式演進,

 

“模塊” 在軟體工程中的概念比較含糊:

 

  • 模塊可以是一個獨立的系統,由多個微服務構成的一個系統,每個微服務可以認為是一個 “模塊”;

  • 在同一個應用中和一個功能相關的物件集合也可以認為是一個模塊

 

隨著微服務概念的興起,很多人誤認為只有將代碼拆成單獨的系統,才能叫 “模塊”,其實不然,我們應該先在同一個應用中將模塊拆分開來,然后再演化成另一個單獨的應用,如果一上來就強行拆的話,只會得到兩個像量子糾纏一樣耦合在一起的應用,

 

關于軟體除錯,有的人傾向于每做一點修改就從應用的入口處(點擊圖形界面或者呼叫 http 介面)進行測驗,對他來說應用內部的分模塊就是一種負擔,因為一旦測驗不通過,他需要理解模塊之間復雜的互動,然后確認傳入被修改模塊的引數是什么,對他來說,肯定是全部使用程序式編碼更好理解一些,然后抱怨系統 ”過度設計“,雖然可能設計并沒有過度,

 

有經驗的工程師在修改完代碼后,會先測驗被修改模塊的正確性,沒有問題后,應用入口處的測驗只是走個流程,大多可以一遍通過,但是如果一個模塊沒有辦法獨立除錯的話,那么它所有人來說都是一個累贅

 

對于獨立系統的模塊,它的介面應該在脫離整個應用后也明確的含義的,介面引數也應該盡量簡單且容易構造,

 

對于同一應用中的代碼模塊,它還應該具備完善的單元測驗,維護者通過單元測驗就可以理解模塊的特性和限制,通過本地 debug 就可以理解模塊的整體設計,

 

John Ousterhout 教授(Raft 的發明者)的著作 《軟體設計哲學》中提到深模塊的概念,給我們設計模塊提供了非常好的指導,

 

深模塊是指介面簡單,但是實作復雜的模塊,就像我們的電腦,它看上去只是一塊簡單的板,卻隱藏了內部復雜的功能實作,John 認為設計良好的模塊都應該是深的,設計良好的應用應該由深模塊組成

 

從軟體除錯的角度來說,介面簡單意味著它易于除錯和理解,實作復雜意味著它能夠幫助我們屏蔽掉很多的業務復雜性,分模塊的代價是值得的,

 

上面的論述可能比較偏向于思想認知層面,關于是實踐層面可以參考我的另一篇文章 代碼重構:面向單元測驗,

 

可除錯的模塊能夠讓我們修改設計模式的心理壓力大大降低,因為有任何問題我們都可以很快發現,有了這個基礎,我們才能跟著業務去演進我們的模式,

 

模式演進

 

互聯網應用更新迭代頻繁,因為設計模式不易于修改,外加模塊不好除錯,很多團隊就懶得對模式進行演進,而是各種繞過的 “黑科技”,很多應用都已經發展了好幾年,用的還是系統剛創建時的模式,怎么可能還跟得上業務發展,于是就變成了人們眼中的 “過度設計”,

 

設計模式也是需要跟著業務演進的,當對未來的業務進行規劃,也要同時對系統模式進行思考,系統的模式是否還能跟上未來業務的規劃?在迭代中不斷探索最符合業務的設計模式,

 

Java8 引入的很多新特性可以幫助我們降低業務頻繁演進時,模式的遷移成本,當我們對是否要應用某個模式猶豫不絕的時候,可以考慮使用 函式式設計模式,以策略模式為例,在面向物件中,策略模式必須采用如下編碼:

interface Strategy {
    void doSomething();
}

class AStrategy implements Strategy {
    //... 代碼省略
}
class BStrategy implements Strategy {
    //... 代碼省略
}
及
// 業務代碼
class AService {
    private Map<String, Strategy> strategyMap;

    public void doSomething(String strategy) {
        strategyMap.get(strategy).doSomething();
    }
}

 

 

我們新建了好多類,一旦日后反悔,遷移的成本非常高,而使用函式式策略模式,我們可以將他們暫且全部寫在一起:

class AService {
    private Map<String, Runnable> strategyMap;

    static {
        strategyMap.put("a", this::aStrategy);
        strategyMap.put("b", this::bStrategy);
    }

    public void doSomething(String strategy) {
        strategyMap.get(strategy).run();
    } 

    private void aStrategy() {
        //...
    }

    private void bStrategy() {
        //...
    }
}

 

可以看到設計模式的函式式版本,相比面向物件版本,在隔離和封裝上相對差些,但是便捷性好一些

 

所以我們可以在業務不穩定的初期先使用函式式設計模式,利用它的便捷性快速演進,等到業務逐漸成熟,模式確定之后,再改成封裝性更好的面向物件設計模式

 

更多的函式式設計模式可以參考《Java8 實戰》中的 函式式設計模式 相關章節,

 

小結

 

“設計模式” 作為對抗 “軟體復雜度” 惡龍的少年,可能業務發展,缺乏演進等原因,最終自己腐壞成了新的 “惡龍”,

 

為了對抗設計模式的腐壞:

 

  • 構造可除錯的模塊,保證后來的維護者能夠通過除錯快速理解設計,

  • 在業務發展中不斷探索最合適的模式,

 

開發效率與系統的成長性

 

在思考業務的同時,還要思考模式的演進,開發效率似乎變低了,但是這額外的時間并沒有被浪費,在設計程序也是對業務的重新思考,進一步加深對業務的理解,編碼和業務之間必然是存在巨大的鴻溝,設計模式能夠幫助我們彌補這條鴻溝,演進出和業務更加貼合的模塊,從而提升長期的效率,

 

復雜軟體是需要長期成長演化的,JetBrains 花了十幾年時間才讓 Idea 形成優勢,清掃免費 IDE 占據的市場; 米哈游也用了接近十年的時間才形成足夠的技術優勢,在市場上碾壓了同時期的競爭對手,

 

設計模式就是在幫助我們對業務進行合理的抽象,盡可能地復用,這樣系統可以從每個模塊地成長中收益,而不是像程序式編碼,每次都重頭開始,重復解決那些已經解決過的問題

 

舉一個我作業中的例子,釘釘審批的表單有著復雜的嵌套結構,它由控制元件和明細組成,而明細中又子控制元件(有的控制元件中還有子控制元件,甚至還有關聯其他表單的控制元件,總之很復雜就對了),最初我們采用程序式編碼,每當需要處理控制元件時,就手寫一遍遍歷:

// 統計 a 控制元件的總數
public int countComponentAB(Form form) {
    int sum = 0;
    for (Component c: form.getComponents()) {
        if (c.getType() == "A") {
            sum++;
        } else if (c.getType == "Table") {
            // 明細控制元件含有子控制元件
            for (Component d: c.getChildren()) {
                if (d.getType() == "A") {
                    sum++;
                }
            }
        }
    }
    return sum;
}

 

// 回傳表單中所有的 A 控制元件和 B 控制元件
public List<Component> getComponentAB(Form form) {
    List<Component> result = new ArrayList<>();
    getComponentABInner(result, form.getItems());
    return result;
}

private getComponentABInner(List<Component> result, List<Component> items) {
    for (Component c: items) {
        if (c.getType() == "A" || c.getType() == "B") {
            result.add(c);
        } else if (!c.getChildren().isEmtpy()) {
            // 遞回訪問子控制元件
            getComponentABInner(result, c.getChildren());
        }
    }
}

 

這兩段代碼各自有點 “小 bug”:

 

  • 第一段代碼只展開了一層子控制元件,但是審批表單是支持多層子控制元件的

  • 第二段代碼雖然用遞回支持了多層子控制元件,但是并不是所有的子控制元件都屬于當前表單(前面提到過,審批支持關聯其他比表單的控制元件)

 

兩段代碼風格都不一樣,因此只能分別在上面修修補補,新同學來大概率還會犯相同的錯誤,此時,系統也就談不上 “成長”,

 

但是 Visitor 模式可以幫助我們將嵌套結構的遍歷邏輯統一抽象出來,使用 Visitor 模式重新編碼后的兩段代碼看起來如下:

// 統計 a 控制元件的總數
class CountAVisitor extends Visitor {

    public int sum;

    @Override
    public void visitA(ComponentA a) {
        sum++;
    }
}

public int countComponentAB(Form form) {
    CountAVisitor aVisitor = new CountAVisitor();
    // 遍歷邏輯統一到了 accept 中
    form.accept(aVisitor);
    return aVisitor.sum;
}

 

// 回傳表單中所有的 A 控制元件和 B 控制元件
class GetComponentABVisitor extends Visitor {

    public List<Component> result;

    @Override
    public void visitA(ComponentA a) {
        result.add(a);
    }

    @Override
    public void visitB(ComponentB b) {
        result.add(b);
    }
}

public List<Component> getComponentAB(Form form) {
    GetComponentABVisitor abVisitor = new GetComponentABVisitor();
    form.accept(abVisitor);
    return abVisitor.result;
}

 

關于 Visitor 模式的細節,可以參考我的另一篇文章 重新認識訪問者模式,

 

對于使用者來說,雖然第一次看到這種寫法時,需要花點時間學習模式,和理解其中的特性,但是一旦理解之后,不僅可以快速理解所有類似代碼,還可以利用這個模塊解決所有遍歷問題,而且這個模塊是經過驗證,能夠健壯地解決問題,

 

相比之下,程序式編碼,盡管都是遍歷邏輯,每一段風格都不一樣,每一次都要重新理解,每一段都有不一樣的特性和 bug,明明知道邏輯就在那里,但是卻無法復用,每一任維護者只能繼續踩前人踩過的坑,重復地解決問題,對于系統的長期成長是不利的,

 

幸福的家庭都是類似的,不幸的家庭各有各的不幸,

 

 

 

因噎廢食的陷阱

 

軟體工程師的成長

 

在工程師成長的路上,有很多坎坷,“不要過度設計” 就是其中無比甜蜜的陷阱,因為它給我們偷懶一個很好的理由,讓我們可以安然地停在五十步,反而去嘲笑已經跑了一百步的人,

 

如果有兩位工程師,前者因為過度設計而犯錯; 后者則是不進行設計,安于系統現狀,認為 “代碼無錯就是優”[參考5],

 

我認為前者更有成長性,因為他至少是有代碼和技術上的追求的,只要有正確的指導,遲早會成為一名優秀的工程師,

 

最怕的是團隊沒有人指導,任由其自由發展,或者批評阻礙其發展,這正是 CR 以及評審機制的意義,

 

互聯網精耕細作的新時代

 

設計模式能夠幫助我們大幅度提升復雜軟體的開發與維護效率,也本文圍繞的主要命題,

 

但是人們總是能找出反例,“很多公司工程做得很糟糕,業務也十分成功”,

 

之前看紅學會的直播,對抗軟體復雜度的戰爭,也有人問了曉斌類似的問題,曉斌的回答是 “如果你有一片田,種啥長啥,那么你不需要耕作,只要撒種子就可以了”,

 

在互聯網野蠻發展時期,大量的人才和熱錢涌入,軟體快速上線比一切都重要,開發效率的問題,只要招聘更多的人就能解決,哪怕在一個公司開發好幾套功能一樣的系統,

 

但是隨著互聯網人口紅利的消失,不再有充足的資源去承接業務,我們就不得不做好精耕細作的準備,扎實地累積自己的產品和技術優勢,繼續創造下一個十年的輝煌,

 

本文的邊界情況

 

真理是有條件的,

 

本文并非走極端地認為所有代碼都應該應用模式,至少在以下情況下,是不適合用模式的:

 

  • 一次性腳本,沒有多次閱讀和修改的可能,我自己在寫工具類腳本時也不會去應用模式,但是我相信阿里巴巴的應用代碼,100% 都是要被反復閱讀和修改的,

  • 真的很簡單的模塊,前文提到過 ”模塊應該是深“,如果這個模塊真的很簡單,它或許抽象不足,我們應該將它和其他模塊整合一下,變得更加豐滿,如果應用中抽不出復雜模塊,那可能不是事實,只是我們的實作方式太簡單了(比如全是程序式編碼),反過來又對外宣稱 ”我們的業務很復雜“,

  • 團隊內都是喜歡攀比代碼設計的瘋子,需要告誡警醒一下,真的有團隊達到這個程度了嗎?如果到了這個程度,才可以 “反對設計”,

 

參考:

[1]《人月神話》

[2]《軟體設計哲學》

[3]《Java 8 實戰》

[4]《設計模式 - 可復用的面向物件軟體元素》

[5]《大話設計模式》

[6] 代碼重構:面向單元測驗

[7] 重新認識訪問者模式

[8] 對抗軟體復雜度的戰爭

 

 

作 者 | 杜沁園(懸衡)

本文來自博客園,作者:古道輕風,轉載請注明原文鏈接:https://www.cnblogs.com/88223100/p/Talk-about-over-design-the-trap-of-giving-up-eating-for-choking.html

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/539071.html

標籤:其他

上一篇:談談過度設計:因噎廢食的陷阱

下一篇:深入淺出學習透析Nginx服務器的基本原理和配置指南「初級實踐篇 」

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more