一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

如何寫好代碼

 山峰云繞 2023-10-03 發(fā)布于貴州

https://m.toutiao.com/is/idd4M6UV/?= 


作者:陳曉(逸曉)

一、什么是好代碼

拋開性能、并發(fā)、一致性等技術(shù)因素,好的業(yè)務(wù)代碼,應(yīng)當(dāng)如一篇顯淺易懂的業(yè)務(wù)敘實(shí)文章,滿足以下幾個(gè)基本條件:

  • 詞要達(dá)意:最基礎(chǔ)的變量、函數(shù)、類的命名,是否名達(dá)其意。
  • 結(jié)構(gòu)清晰:類的關(guān)系結(jié)構(gòu),函數(shù)的調(diào)用結(jié)構(gòu),是否如文章的章節(jié)、段落劃分層次分明和邏輯清晰。
  • 緊扣主題:包、類、函數(shù)是否內(nèi)聚,是否破壞了單一和開閉原則。

因此,好代碼如同好文章,它應(yīng)該是飽含業(yè)務(wù)語義(詞要達(dá)意)、具有自明性和可讀性(結(jié)構(gòu)清晰),能夠顯性化表達(dá)業(yè)務(wù)意圖(緊扣主題),讓人賞心悅目。

二、從最基本做起

2.1 命名

好的代碼,從好的命名開始,做到名副其實(shí)。

? 2.1.1 變量命名

變量名是名詞,要正確和清晰地描述業(yè)務(wù)語義,如果一個(gè)變量需要通過注釋補(bǔ)充說明,那可能就是沒取好變量名。

變量命名的關(guān)鍵點(diǎn):

1)詞要達(dá)意:避免無業(yè)務(wù)語義的命名,如:list、val、a...;

2)語境范圍:避免小范圍詞套大范圍數(shù)據(jù),反之亦然,不使用過于寬泛的名詞;

3)名詞復(fù)數(shù):統(tǒng)一風(fēng)格,加sList尾綴,變量名建議使用s尾綴,函數(shù)名建議使用List尾綴;

4)后置限定詞:限定詞是對(duì)前面變量名的修飾,可以描述名詞的作用范圍屬性,例如:

  • 請(qǐng)求入?yún)ⅲ簒xxQuery/xxxRequest
  • 返回結(jié)果:xxxResponse/xxxResult
  • 傳參數(shù)據(jù):xxxDTO/xxxVO/xxxInfo
  • 運(yùn)算結(jié)果:xxxTotal(總和)/xxxMax(最大值)/xxxAverage(平均值)

Bad case:

// 1、coupon是promotion的子類型,這里把大范圍的數(shù)據(jù)賦值給一個(gè)小范圍的名詞,表達(dá)失準(zhǔn)。List<ShoppingPromotionInfo> couponList = JSON.parseArray(promotionInfoStr, ShoppingPromotionInfo.class);// 2、實(shí)際是取運(yùn)費(fèi)活動(dòng)優(yōu)惠,promotion蘊(yùn)含的語義范圍超出了運(yùn)費(fèi)活動(dòng)優(yōu)惠。// 3、命名沒有保持一致性,上面用List做后置限定詞,這里用s做尾綴。List<ShoppingPromotionInfo> promotionInfos = couponList.stream() .filter(x->x.getType()== PromotionTypeEnum.POSTAGE_ACTIVITY.getType()) .collect(Collectors.toList());

Good case:

List<ShoppingPromotionInfo> promotions = JSON.parseArray(promotionInfoStr, ShoppingPromotionInfo.class);// 通過變量名的范圍,就可以知道是從promotions中篩選出運(yùn)費(fèi)活動(dòng)優(yōu)化。List<ShoppingPromotionInfo> postageActivityPromotions = promotions.stream()    .filter(x->x.getType()== PromotionTypeEnum.POSTAGE_ACTIVITY.getType())    .collect(Collectors.toList());

? 2.1.2 函數(shù)命名

函數(shù)命名要體現(xiàn)做什么,而不是怎么做,要清楚表達(dá)出操作意圖業(yè)務(wù)語義。

函數(shù)命名的關(guān)鍵點(diǎn):

1)動(dòng)名詞搭配,動(dòng)詞表達(dá)操作意圖,名詞表達(dá)業(yè)務(wù)語義。

2)正反操作使用對(duì)仗詞,例如:

  • add/remove
  • open/close
  • begin/end
  • insert/delete
  • first/last
  • min/max

Bad Case:

public interface MemberFacade { /** * 查詢會(huì)員信息 * * 命名的問題: * 1.沒有表達(dá)操作意圖:build是構(gòu)建,沒有表達(dá)查詢的意圖。 * 2.沒有表達(dá)業(yè)務(wù)語義:沒有表達(dá)build的目標(biāo)對(duì)象是什么。 **/ SingleResult<MemberDTO> build(MemberBuildRequest request); /** * 更新會(huì)員信息 * * 命名的問題: * 1.沒有表達(dá)業(yè)務(wù)語義:不知道具體更新會(huì)員的哪些信息 * 2.操作意圖表達(dá)過于寬泛:update可以把所有會(huì)員信息更新都放在這個(gè)函數(shù)里。 **/ SingleResult update(MemberUpdateRequest request);}

Good Case:

public interface MemberFacade {    /**     * 查詢會(huì)員信息     **/    SingleResult<MemberDTO> queryMember(MemberQuery query);    /**     * 更新會(huì)員昵稱     **/    SingleResult updateNick(MemberUpdateRequest request);}

? 2.1.3 類命名

類是面向?qū)ο笾凶钪匾母拍?,是一組關(guān)聯(lián)數(shù)據(jù)的相關(guān)操作的封裝,通??梢园杨惙譃閮煞N:

1)實(shí)體類:承載業(yè)務(wù)的核心數(shù)據(jù)和業(yè)務(wù)邏輯,命名要充分體現(xiàn)業(yè)務(wù)語義,比如Order/Buyer/Item。

2)輔助類:協(xié)調(diào)實(shí)體類完成業(yè)務(wù)邏輯,命名通常加后綴體現(xiàn)出其功能性,比如
OrderQueryService/OrderRepository。

函數(shù)命名的關(guān)鍵點(diǎn):

1)輔助類盡量避免用 Helper/Util 之類的后綴,因?yàn)槠浜x過于籠統(tǒng),容易破壞單一職責(zé)原則。

2)針對(duì)某個(gè)實(shí)體的輔助操作過多,或單個(gè)操作很復(fù)雜,可通過 “實(shí)體 + 操作類型 + 功能后綴”來命名,同時(shí)符合職責(zé)單一和接口隔離的原則,比如OrderService:

  • OrderCreateService:訂單創(chuàng)建服務(wù);
  • OrderUpdateService:訂單更新服務(wù);
  • OrderQueryService:訂單查詢服務(wù)。

? 2.1.4 包命名

包(package)是一組強(qiáng)關(guān)聯(lián)(內(nèi)聚)的類的集合,起分類收納和命名空間的作用。

  • 包名應(yīng)該要反映一組類在更高抽象層次上的聯(lián)系,比如類Apple、Orange都是水果,可以收納進(jìn)fruit包內(nèi)。
  • 包的命名要大小適中,不能太具體,也不能太抽象。比如包名叫Apple,太具體導(dǎo)致類Orange放不進(jìn)去,又比如包名叫food,太抽象導(dǎo)致其他非水果也被放進(jìn)來了。

實(shí)際工程中,常見的分類維度主要是兩種,按功能性或業(yè)務(wù)域分類。

  • 功能性分類:metaq、mapper、service、dao等;
  • 業(yè)務(wù)域分類:user、item、order、promotion等。

同一層級(jí)的包,要嚴(yán)格保持分類維度的一致性,要么先按業(yè)務(wù)域分類,再按功能性分類;要么就先按功能性分類,再按業(yè)務(wù)域分類。

client |----request |----order |----item |----response |----order |----item |----service |----OrderQueryService.java |----ItemQueryService.java

2.2 函數(shù)設(shè)計(jì)原則

有時(shí)候,優(yōu)雅的實(shí)現(xiàn)僅僅是一個(gè)函數(shù),不是一個(gè)類,不是一個(gè)框架,只是一個(gè)函數(shù)?!?John Carmack

? 2.2.1 函數(shù)要短小、專一

  • 短?。?/strong>一個(gè)函數(shù)不超過50行代碼,大量的setXXX()除外。

  • 專一:一個(gè)函數(shù)只做一件事情,符合單一職責(zé)原則。

? 2.2.2 函數(shù)抽象層次保持一致

遵循金字塔原則,把函數(shù)層層遞進(jìn)的調(diào)用,理解成結(jié)論先行,自上而下的表達(dá)過程

同層函數(shù)是對(duì)上一層的支撐,同層間要符合MECE法則,應(yīng)描述和處理同一邏輯范疇的事情,高層抽象和底層細(xì)節(jié)不能雜糅在一起,否則會(huì)變得凌亂和難以理解。

(MECE是(Mutually Exclusive Collectively Exhaustive)的縮寫,指的是“相互獨(dú)立,完全窮盡”的分類原則。通過MECE方法對(duì)問題進(jìn)行分類,能做到清晰準(zhǔn)確,從而容易找到答案。)

2.3 模塊分層原則

? 2.3.1 模塊分層

  • client:外部可見層(暴露服務(wù)聲明);
  • service:業(yè)務(wù)邏輯層,對(duì)client層的實(shí)現(xiàn),協(xié)調(diào)domain和infrastructure一起完成業(yè)務(wù)邏輯;
  • domain:領(lǐng)域?qū)?,?duì)應(yīng)DDD中的領(lǐng)域知識(shí);
  • infrastructure:基礎(chǔ)設(shè)施層,數(shù)據(jù)庫(kù)訪問、消息、外部調(diào)用等;
  • start:應(yīng)用啟動(dòng)層,主要是項(xiàng)目啟動(dòng)時(shí)的靜態(tài)配置。

? 2.3.2 模塊內(nèi)包分層

分包的建議:

  • 如果有多個(gè)一級(jí)域,建議:一級(jí)按業(yè)務(wù)分包,二級(jí)按功能分包,三級(jí)可按子領(lǐng)域分包。
  • 如果僅一個(gè)一級(jí)域,建議:一級(jí)按功能分包,二級(jí)按子領(lǐng)域分包。

例如:

|- xxx|---- xxx-client             // 只提供僅需的外部依賴(DO不能再這層定義)    |----biz1                // 子域module        |----event           // 領(lǐng)域事件聲明        |----constant        // 常量(enum、final)        |----dto             // 服務(wù)的出參對(duì)象,value object/view object        |----request         // request or query        |----service         // 本地/HSF服務(wù)接口聲明        |----facade          // 提供給Top/MTop的HSF接口|---- xxx-domain             // 提供領(lǐng)域能力(entity、domainservice等)    |----biz1                // 子域module        |----factory         // 類工廠(構(gòu)建器/轉(zhuǎn)換器/工廠類)        |----entity          // 充血模型的實(shí)體類        |----service         // 領(lǐng)域服務(wù)        |----repository      // api gateway 或 db repository接口,實(shí)現(xiàn)放在infrastructure  |--test                    // 領(lǐng)域?qū)訂卧獪y(cè)試|---- xxx-infrastructure     // 基礎(chǔ)設(shè)施層: mapper/config/repository impl  |--main        |----biz1                // 子域module        |----dataobject      // do: 貧血模型        |----mapper          // mybatis mapper        |----repository      // repository impl  |--test                    // 基礎(chǔ)設(shè)施層單元測(cè)試|---- xxx-service            // 調(diào)用外域服務(wù),使用domain層的能力    |----biz1                // 子域module        |----factory         // 類工廠(構(gòu)建器/轉(zhuǎn)換器/工廠類)        |----service         // HSF服務(wù)接口實(shí)現(xiàn)        |----facade          // 提供給Top/MTop的HSF接口實(shí)現(xiàn),通常是調(diào)service的服務(wù)        |----dts        |----metaq  |--test          // 服務(wù)實(shí)現(xiàn)層單元測(cè)試|---- xxx-starter  |--test            // 集成測(cè)試

三、耦合與內(nèi)聚

軟件設(shè)計(jì)的目標(biāo)是高內(nèi)聚、低耦合。

如果代碼是高耦合和低內(nèi)聚的,就會(huì)出現(xiàn)修改一個(gè)邏輯,會(huì)導(dǎo)致多處代碼要修改,可能影響到多個(gè)業(yè)務(wù)鏈路,這增加了出bug的業(yè)務(wù)風(fēng)險(xiǎn),同時(shí)增加了測(cè)試回歸的范圍,導(dǎo)致研發(fā)成本增加。

耦合和內(nèi)聚,是我們常掛在嘴邊的話,但是大家卻說不太清楚,講不太明白,很難衡量:

  • 什么樣的叫高內(nèi)聚,什么樣的叫低耦合?
  • 高內(nèi)聚要高到什么程度,低耦合要低到什么程度?

3.1 耦合的類型

耦合是描述模塊(系統(tǒng)/模塊/類/函數(shù))之間相互聯(lián)系(控制/調(diào)用/數(shù)據(jù)傳遞)緊密程度的一種度量。

  • 緊耦合:模塊之間聯(lián)系越緊密,耦合性就越強(qiáng),模塊的獨(dú)立性則越差;
  • 松耦合:模塊之間聯(lián)系越松散,單個(gè)模塊解決問題的目的越明確,模塊的獨(dú)立性越強(qiáng)。

? 3.1.1 非直接耦合(Nondirect Coupling)

如果兩個(gè)模塊之間沒有直接關(guān)系,它們之間的聯(lián)系完全是通過主模塊控制調(diào)用來實(shí)現(xiàn)的,這就是非直接耦合,這種耦合的模塊獨(dú)立性最強(qiáng)。

class User { long userId; String userNick;}class MessageService { void pushMessage(long userId, String message);}class UserLoginService { void onLoginEvent(long userId) { User user = queryUserById(userId); String message = user.getUserNick() + '登錄成功。'; messageService.pushMessage(userId, message); } }}

? 3.1.2 數(shù)據(jù)耦合(Data Coupling)

如果一個(gè)模塊訪問另一個(gè)模塊時(shí),彼此之間是通過數(shù)據(jù)參數(shù)(不是控制參數(shù)、公共數(shù)據(jù)結(jié)構(gòu)或外部變量)來交換輸入、輸出信息的,則稱這種耦合為數(shù)據(jù)耦合,它是較好的耦合形式。

class MessageService {    void pushMessage(long userId, String userNick) {        String message = userNick + '登錄成功。';        doPushMessage(userId, message);    }}class UserLoginService {    void onLoginEvent(User user) {        messageService.pushMessage(user.getUserId(), user.getUserNick());    }}

? 3.1.3 印記(引用)耦合(Stamp Coupling)

當(dāng)模塊之間使用復(fù)合數(shù)據(jù)結(jié)構(gòu)進(jìn)行通信時(shí),就會(huì)發(fā)生印記耦合。

復(fù)合數(shù)據(jù)結(jié)構(gòu)可以是數(shù)組、類、結(jié)構(gòu)體、聯(lián)合體等的引用,通過復(fù)合數(shù)據(jù)結(jié)構(gòu)在模塊之間傳遞的參數(shù),可能會(huì)或不會(huì)被接收模塊完全使用。

class User { long userId; String userNick; // 該屬性未被MessageService使用 int level;}class MessageService { void pushMessage(User user) { String message = user.getUserNick() + '登錄成功。'; doPushMessage(user.getUserId(), message); }}class UserLoginService { void onLoginEvent(User user) { messageService.pushMessage(user); }}

印記耦合優(yōu)點(diǎn):

  • 把模塊A的引用一把傳遞給模塊B,模塊B只需要接受少量參數(shù),接口說明簡(jiǎn)單。

印記耦合缺點(diǎn):

  • 不必要的參數(shù):模塊B可能只使用了模塊A中部分的數(shù)據(jù);
  • 模塊B捆綁了模塊A:任何需要用到模塊B的地方,都需要先獲取到模塊A,無法脫離模塊A單獨(dú)使用;
  • 修改可能互相影響:修改模塊A或模塊B,可能導(dǎo)致對(duì)方也需要跟著修改,不符合開閉原則。

印記耦合優(yōu)化:

增加入?yún)?shù)類型,進(jìn)傳入模塊需要的必要數(shù)據(jù),如下:

? 3.1.4 控制耦合(Control Coupling)

如果一個(gè)模塊通過傳送開關(guān)、標(biāo)志等控制信息,明顯地控制選擇另一模塊的功能,就是控制耦合。

class MessageService {    void pushMessage(long userId, bool isNewUser) {        if(isNewUser) {            doPushMessage(userId, '登錄成功。');        }    }}class UserLoginService {    void onLoginEvent(User user) {        messageService.pushMessage(user.getUserId, user.getIsNewUser());    }}
  • 數(shù)據(jù)耦合和控制耦合的主要區(qū)別:在數(shù)據(jù)耦合中,模塊之間的依賴關(guān)系非常小,而在控制耦合中,模塊之間的依賴關(guān)系很高。在數(shù)據(jù)耦合中,模塊之間通過傳遞數(shù)據(jù)進(jìn)行通信,而在控制耦合中,模塊之間通過傳遞模塊的控制信息進(jìn)行通信;
  • 控制耦合優(yōu)化:把控制的邏輯放在模塊A之中,或增加模塊C封裝控制邏輯,不然模塊B只做某一件獨(dú)立的事情。

? 3.1.5 外部耦合(External Coupling)

外部耦合,是指多個(gè)模塊同時(shí)依賴同一個(gè)外部因素(IO設(shè)備/文件/協(xié)議/DB等),如上圖所示:

外部耦合與與外部設(shè)備的通信有關(guān),而不是與公共數(shù)據(jù)或數(shù)據(jù)流有關(guān)。

一個(gè)模塊對(duì)外部數(shù)據(jù)或通信協(xié)議所做的任何更改都會(huì)影響其他模塊,可以通過增加中間模塊隔離外部變化來降低耦合度,如下:

? 3.1.6 共用耦合(Common Coupling)

共用耦合是指不同的模塊共享全局?jǐn)?shù)據(jù)的信息全局?jǐn)?shù)據(jù)結(jié)構(gòu)、共享的通信區(qū)、內(nèi)存的公共覆蓋區(qū)。

public Response loadInitInfo(Request request) { // request&response是Commands的全局?jǐn)?shù)據(jù) Response response = new Response(); commandExecutor.serial(request, response, orderRenderRateLimitCommand, renderInitResponseCommand, renderEnrichTradeNoCommand, renderEnrichItemCommand, renderEnrichCombinationCommand, renderEnrichPriceCommand ); return response;}

共用耦合的問題:

  • 較難控制各個(gè)模塊對(duì)公共數(shù)據(jù)的存取,容易影響模塊的可靠性和適應(yīng)性;
  • 使軟件的可維護(hù)性變差,若一個(gè)模塊修改了共用數(shù)據(jù),則會(huì)影響相關(guān)模塊;
  • 降低了軟件的可理解性,不容易清楚知道哪些數(shù)據(jù)被哪些模塊所共享,排錯(cuò)困難。

? 3.1.7 內(nèi)容耦合(Content Coupling)

內(nèi)容耦合在低級(jí)語言(匯編)中出現(xiàn),高級(jí)語言從設(shè)計(jì)上已避免出現(xiàn)內(nèi)容耦合。

如果發(fā)生下列情形,兩個(gè)模塊之間就發(fā)生了內(nèi)容耦合

  • 一個(gè)模塊直接訪問另一個(gè)模塊的內(nèi)部數(shù)據(jù);
  • 一個(gè)模塊不通過正常入口而直接轉(zhuǎn)入到另一個(gè)模塊的內(nèi)部;
  • 兩個(gè)模塊有一部分代碼重疊(該部分代碼具有一定的獨(dú)立功能);
  • 一個(gè)模塊有多個(gè)入口。

3.2 內(nèi)聚的類型

內(nèi)聚,是描述一個(gè)模塊內(nèi)各元素彼此結(jié)合的緊密程度,是從功能角度來度量模塊內(nèi)的聯(lián)系。

  • 低內(nèi)聚:模塊內(nèi)的元素的職責(zé)相關(guān)性低,通常也意味著模塊與外部是緊耦合的。
  • 高內(nèi)聚:模塊內(nèi)的元素的職責(zé)相關(guān)性強(qiáng),通常也意味著模塊與外部是松耦合的。

通常,解決了耦合的問題,就解決了內(nèi)聚的問題,反之亦然。

? 3.2.1 偶然性內(nèi)聚

偶然內(nèi)聚,一個(gè)模塊內(nèi)的各元素之間沒有任何聯(lián)系,僅是恰好放在同一個(gè)模塊內(nèi),業(yè)務(wù)的“Util/Helper”類有大量例子。

問題的原因:通常是模塊名起的過于抽象,導(dǎo)致不同職責(zé)的元素都可以放進(jìn)去,從而引起了低內(nèi)聚。

問題的解法:將抽象的模塊拆解成多個(gè)更小的具體模塊,例如RetailTradeHelper可以拆為OrderAmountHelper/OrderPaymentParamHelper

? 3.2.2 邏輯性內(nèi)聚

邏輯內(nèi)聚,把幾種相關(guān)的功能組合在一起,由調(diào)用方傳入的參數(shù)來確定具體執(zhí)行哪一種功能。

邏輯內(nèi)聚是一種“低內(nèi)聚”,某程度上對(duì)應(yīng)了“控制耦合”,它把內(nèi)部的邏輯處理暴露給了接口之外,當(dāng)內(nèi)部邏輯發(fā)生變更時(shí),原本無辜的調(diào)用方也會(huì)受牽連改動(dòng)。

public void syncOrder(Order order, String dist) {  if(dist == 'oc') {        syncOrder2Oc(order);    }    if(dist == 'mis') {        syncOrder2Mis(order);    }    if(dist == 'tp') {      syncOrder2Tp(order);    }}

? 3.2.3 時(shí)間性內(nèi)聚

時(shí)間內(nèi)聚,指一個(gè)模塊內(nèi)的組件除了在同一時(shí)間都會(huì)被執(zhí)行外,相互之間沒有任何關(guān)聯(lián)。

? 3.2.4 過程性內(nèi)聚

過程內(nèi)聚,指一個(gè)模塊內(nèi)的組件以特定次序被執(zhí)行,但相互之間沒有數(shù)據(jù)傳遞。

? 3.2.5 通信性內(nèi)聚

通信內(nèi)聚,指一個(gè)模塊內(nèi)的組件以特定次序被執(zhí)行,且相互之間傳遞和操作相同的數(shù)據(jù)。

? 3.2.6 順序性內(nèi)聚

順序內(nèi)聚,指一個(gè)模塊內(nèi)的元素以特定次序被執(zhí)行,且上一步的輸出被下一元素所依賴。

? 3.2.7 功能性內(nèi)聚

功能內(nèi)聚,指一個(gè)模塊內(nèi)所有組件屬于一個(gè)整體,完成同一個(gè)不可切分的功能,彼此缺一不可。

四、設(shè)計(jì)原則

設(shè)計(jì)原則,是指導(dǎo)我們?nèi)绾卧O(shè)計(jì)出低耦合、高內(nèi)聚的代碼,讓代碼能夠更好的應(yīng)對(duì)變化,從而降本提效。

設(shè)計(jì)原則的關(guān)鍵,是從使用方的角度看提供方的設(shè)計(jì),一句話概括就是:請(qǐng)不要要我知道太多,你可以改,但請(qǐng)不要影響我。

4.1 單一職責(zé)原則(SRP)

定義:一個(gè)函數(shù)/類只能因?yàn)橐粋€(gè)理由被修改。

單一職責(zé)原則,是所有原則中看起來最容易理解的,但是真正做到并不簡(jiǎn)單。因?yàn)樽裱@一原則最關(guān)鍵是職責(zé)的劃分

職責(zé)的劃分至少要回答兩個(gè)基本問題:

  • 什么是你,什么是我?
  • 什么事情歸你管,什么事情歸我管?

且不說寫代碼,工作中我們也會(huì)出現(xiàn)人人不管或相爭(zhēng)的重疊地帶,劃分清楚職責(zé)看起容易,實(shí)際很難。

4.2 開閉原則(OCP)

定義:對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉(不修改代碼就可以增加新功能)。

要理解開閉原則,關(guān)鍵是要理解定義中隱含著的兩個(gè)主語,“使用方”和“提供方”,即:

提供方可以修改,增加新的功能特性,但是使用方不需要被修改,即可享用新的功能特征。

開閉原則廣泛的理解,可以指導(dǎo)類、模塊、系統(tǒng)的設(shè)計(jì),滿足該原則的核心設(shè)計(jì)方法是:通過協(xié)議(接口)交互。

4.3 里氏替換原則(LSP)

定義:所有引用父類的地方,必須能透明的使用它的子類對(duì)象,指導(dǎo)類繼承的設(shè)計(jì)。

面向?qū)ο蟮睦^承特性,一方面,子類可以擁有父類的屬性和方法,提高了代碼的復(fù)用性;另一方面,繼承是有入侵性的,父類對(duì)子類有約束,子類必須擁有父類全部的屬性和方法,修改父類會(huì)影響子類,增加了耦合性。

里氏替換原則是對(duì)繼承進(jìn)行了約束,體現(xiàn)在以下方面:

  • 子類可以實(shí)現(xiàn)父類的抽象方法,但不能重寫(覆蓋)父類的非抽象方法;
  • 子類可以增加父類所沒有的屬性和方法;
  • 子類重寫父類方法時(shí),輸入?yún)?shù)類型要和父類的一致,或更寬松(參數(shù)類型的父類);
  • 子類重寫父類方法時(shí),返回值類型要和父類的一致,或更嚴(yán)謹(jǐn)(返回類型的子類)。

4.4 依賴倒置原則(DIP)

定義:高層模塊不應(yīng)該依賴低層模塊,兩者都應(yīng)該依賴其抽象;抽象不應(yīng)該依賴細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴抽象,目的是降低層與層之間的耦合。

從倒置來看,該原則可以有更泛化的理解:

  • 依賴實(shí)體的倒置:高層不依賴底層模塊,抽象不依賴細(xì)節(jié),例如模塊分層規(guī)范中的domain不依賴infrastructure的實(shí)現(xiàn);
  • 依賴控制的倒置:依賴具體對(duì)象的創(chuàng)建控制,從程序內(nèi)部交給外部,例如Spring的Ioc容器。

舉個(gè)購(gòu)物車的例子:

  • 商業(yè)能力基座:主要包含購(gòu)物車的業(yè)務(wù)流程實(shí)現(xiàn)、外域服務(wù)定義(非實(shí)現(xiàn))、商業(yè)定制能力(擴(kuò)展點(diǎn)),打包后需滿足一套代碼多處部署的要求。
  • 域服務(wù)能力實(shí)例:針對(duì)不同運(yùn)行環(huán)境,提供適配環(huán)境的域服務(wù)實(shí)現(xiàn),商業(yè)基座反向依賴域服務(wù)實(shí)例,使得基座與環(huán)境無關(guān)。

4.5 接口隔離原則(ISP)

定義:客戶端不應(yīng)該被強(qiáng)迫去依賴它并不需要的接口。

理解接口隔離原則,需要拿單一職責(zé)的原則做對(duì)比。細(xì)品一下,如果一個(gè)接口滿足了

  • 單一職責(zé),是否就也就滿足接口隔離原則?
  • 單一職責(zé)原則,解決了接口內(nèi)聚的問題。

接口隔離原則,認(rèn)為某些場(chǎng)景下需要存在非內(nèi)聚接口(多職責(zé)),但是又不希望客戶端知道整個(gè)類,客戶端只要知道具有內(nèi)聚接口的抽象父類即可。

簡(jiǎn)單來講,接口隔離原則解決的問題是,當(dāng)某些類不滿足職責(zé)單一原則時(shí),客戶端不應(yīng)該直接使用它們,而是通過增加接口類,通過它隱藏客戶端不需要感知到的部分。

五、編程范式

5.1 編程范式

編程范式,本質(zhì)是一種思維方式,而和具體語言沒關(guān)系。

用C語言可以寫出面向?qū)ο蟮某绦?,用Java語言可以寫出面向過程的程序。而不爭(zhēng)的現(xiàn)實(shí)是,我們大部分人是在用java寫面向過程的代碼。

例如下面代碼,它是如何用面向過程語言實(shí)現(xiàn)封裝、繼承、多態(tài)的?

以上代碼來自開源libevent庫(kù)

? 5.1.1 結(jié)構(gòu)化(面向過程)編程

最早使用機(jī)器和匯編語言編程,是編排好一堆命令讓機(jī)器逐條執(zhí)行,為了控制一些跳躍的流程(如if/for/continue/break),就會(huì)用到類似goto的語句,讓程序直接跳轉(zhuǎn)到希望執(zhí)行的指令位置,這樣程序員就擁有了直接轉(zhuǎn)移程序控制權(quán)的能力。goto的無條件轉(zhuǎn)移,使得程序的控制流難于追蹤,程序難以修改和維護(hù)。

后來大家總結(jié)出了一套流程結(jié)構(gòu)化的定律:任何程序都可以用順序、選擇、循環(huán)三種基本控制結(jié)構(gòu)來表示。

  • 順序:代碼是至上往下順序執(zhí)行的;
  • 選擇:if-else/switch選擇執(zhí)行;
  • 循環(huán):for/while控制循環(huán)執(zhí)行。

因此,結(jié)構(gòu)化編程的本質(zhì),是對(duì)程序控制權(quán)的直接轉(zhuǎn)移進(jìn)行了規(guī)范和限制。

? 5.1.2 面向?qū)ο缶幊?/h2>

結(jié)構(gòu)化編程思維,比較靠近機(jī)器運(yùn)行的思維,當(dāng)程序越來越復(fù)雜的時(shí)候,大家發(fā)現(xiàn)簡(jiǎn)單靠結(jié)構(gòu)化思維編程,很難構(gòu)建起一個(gè)龐大的應(yīng)用。而在編碼過程中,大家不知不覺的把一些數(shù)據(jù)和邏輯封裝了起來,形成一個(gè)個(gè)可復(fù)用的組件。

慢慢大家總結(jié)出了一套符合人類理解客觀世界的編程范式:利用事物的信息建模概念,如實(shí)體、關(guān)系、屬性等,同時(shí)運(yùn)用封裝、繼承、多態(tài)等機(jī)制來構(gòu)造模擬現(xiàn)實(shí)系統(tǒng)的方法。

  • 封裝:核心是對(duì)實(shí)體建模,把客觀世界的屬性和行為,封裝成類的數(shù)據(jù)和方法,同時(shí)通過控制訪問權(quán)限(private/protect/public),對(duì)外隱藏細(xì)節(jié);
  • 繼承:在封裝的基礎(chǔ)上,可以定義子類,從而使子類獲得父類的屬性和行為,這也符合人類從抽象到具象的認(rèn)知思維;
  • 多態(tài):繼承使得子類獲得父類的行為,總有兒子不聽爸爸話的時(shí)候,當(dāng)子類重寫了父類行為時(shí),多態(tài)使得父類引用執(zhí)行方法時(shí),實(shí)際執(zhí)行的是子類的行為。

封裝、繼承、多態(tài)是面向?qū)ο蟮娜筇卣鳎叩年P(guān)系是層層遞進(jìn)的,而多態(tài)實(shí)際是規(guī)范了程序控制權(quán)的間接轉(zhuǎn)移,在面向?qū)ο缶幊讨?,大家是通過函數(shù)指針來解耦不同組件的函數(shù)實(shí)現(xiàn),這種方式需要工程師嚴(yán)格遵守約定初始化函數(shù)指針,是非常脆弱的。

因此,面向?qū)ο缶幊痰谋举|(zhì),是規(guī)范了數(shù)據(jù)和行為的封裝,同時(shí)限制了程序控制權(quán)的間接轉(zhuǎn)移。

? 5.1.3 函數(shù)式編程

函數(shù)式思維,是一種數(shù)學(xué)思維,把一個(gè)問題分解為一系列函數(shù)。函數(shù)式編程有多種定義,但是從根本上來看,它的核心是“純函數(shù)”和“引用透明”:

  • 純函數(shù):無副作用,同樣的輸入永遠(yuǎn)得到同樣的輸出;
  • 引用透明:任意函數(shù)直接用它的計(jì)算結(jié)果替代,而不影響任何調(diào)用它的程序。

若要做到以上兩點(diǎn),就需要對(duì)賦值進(jìn)行限制,即變量一旦初始化就不可以再修改。

因此,函數(shù)式編程的本質(zhì),是規(guī)范了函數(shù)(一等公民/高階函數(shù)/聲明式/閉包等),同時(shí)限制了賦值行為。

? 5.1.4 編程范式總結(jié)

編程范式的本質(zhì),更多是告訴我們不能做什么,并且通過規(guī)范來約束我們的行為。

  • 結(jié)構(gòu)化編程:限制對(duì)程序的控制權(quán)做直接轉(zhuǎn)移,請(qǐng)按照控制結(jié)構(gòu)規(guī)范來;
  • 面向?qū)ο缶幊蹋合拗茖?duì)程序控制權(quán)的間接轉(zhuǎn)移,請(qǐng)按照封裝、繼承、多態(tài)的規(guī)范來;
  • 函數(shù)式編程:限制了賦值行為,請(qǐng)按照函數(shù)的規(guī)范來。

靈魂拷問一下:

  • 為什么面向?qū)ο缶幊檀笫芡瞥纾?/span>
  • 為什么多數(shù)人在用面向?qū)ο笳Z言寫面向過程代碼?
  • 為什么函數(shù)式編程束之高閣,很難產(chǎn)業(yè)化大規(guī)模使用?

我當(dāng)前表淺的理解是:

  • 面向過程符合人類的直線直覺思維,不需要太多深度思考,可以快語直言;
  • 面向?qū)ο笮枰浞至私饪陀^主體信息,才能從中思考和提煉出要素(實(shí)體)、關(guān)系(方法)和目標(biāo)(職責(zé)),要求有系統(tǒng)性的抽象思維;
  • 函數(shù)式編程基于數(shù)學(xué),缺少合適的抽象機(jī)制,純函數(shù)式編程很難滿足企業(yè)級(jí)應(yīng)用要求的嚴(yán)格和復(fù)雜的業(yè)務(wù)需求。

三種編程范式?jīng)]有好壞之分,核心是思維方式的區(qū)別,針對(duì)不同的問題和場(chǎng)景,如何選擇適當(dāng)?shù)姆绞絹硭伎己徒鉀Q問題,才是我們理解它們的關(guān)鍵。

5.2 應(yīng)用范式

? 5.2.1 表模式

表模式關(guān)注的數(shù)據(jù)庫(kù)的表,它先考慮數(shù)據(jù)庫(kù)表需要管理,然后添加對(duì)數(shù)據(jù)增刪改查的操作。封裝是面向?qū)ο蟮年P(guān)鍵特征之一,把數(shù)據(jù)和操作數(shù)據(jù)的行為綁定在一起,擁有一個(gè)標(biāo)識(shí)符(類)來表示它兩的集合,而表模式允許你把數(shù)據(jù)和行為放在一起,但是它沒有一個(gè)標(biāo)識(shí)符來標(biāo)出它所代表的主體。

這種模式在PC時(shí)代很盛行,例如VB和.net等桌面應(yīng)用開發(fā)框架上,但是活久見,在JAVA服務(wù)應(yīng)用中也被我發(fā)現(xiàn)了,如下:

? 5.2.2 事務(wù)腳本模式

腳本,是指表演戲劇、拍攝電影等所依據(jù)的底本又或者書稿的底本。腳本可以說是故事的發(fā)展大綱,用以確定故事的發(fā)展方向。

事務(wù)腳本模式,關(guān)注點(diǎn)是事務(wù)的流程和步驟,是對(duì)事務(wù)流程和步驟的編排,是一種面向過程的組織和表達(dá)形式。

按照事務(wù)腳本模式編程,可以不需要任何面向?qū)ο蟮脑O(shè)計(jì),其中任何邏輯都可以通過if/else/while等流程控制元素來表達(dá)。

事務(wù)腳本模式的優(yōu)點(diǎn)是,門檻低容易上手,也符合人的直線直覺思維;它的缺點(diǎn)是,當(dāng)業(yè)務(wù)邏輯復(fù)雜是,事務(wù)方法會(huì)快速膨脹,因?yàn)闃I(yè)務(wù)屬性不明確和缺乏抽象,不好復(fù)用和擴(kuò)展。該模式在服務(wù)端應(yīng)用中很常見,從MVC時(shí)代開始,一般通過controller組織事務(wù)流程,常見的分層結(jié)構(gòu)如下:

? 5.2.3 領(lǐng)域設(shè)計(jì)模式

領(lǐng)域設(shè)計(jì)模式,是通過分析和發(fā)掘業(yè)務(wù)領(lǐng)域的概念,從中提煉和設(shè)計(jì)出具有數(shù)據(jù)和行為的對(duì)象模型(類),并建立模型之間的關(guān)系。

領(lǐng)域設(shè)計(jì)模式,需要建立一個(gè)完整的由對(duì)象模型組成的層,來對(duì)目標(biāo)業(yè)務(wù)領(lǐng)域建模。業(yè)務(wù)是經(jīng)常變化的,通常有會(huì)通過分層的模式,讓領(lǐng)域模型和系統(tǒng)其他部分保持最小的依賴。

至此,你會(huì)發(fā)現(xiàn)領(lǐng)域設(shè)計(jì)是DDD的底層思想,是面向?qū)ο蟮膶?shí)踐,更多請(qǐng)查閱“對(duì)象建模”和“領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)”相關(guān)的材料和數(shù)據(jù),這里不做展開。

? 5.2.4 應(yīng)用范式總結(jié)

不同的應(yīng)用范式,是隨著軟件復(fù)雜度逐步提升演進(jìn)出來的,不同模式面對(duì)和解決不同復(fù)雜度的問題,相互之間沒有好壞之分。當(dāng)問題比較簡(jiǎn)單時(shí),使用事務(wù)腳本模式足夠應(yīng)付,反倒使用領(lǐng)域設(shè)計(jì)就過度設(shè)計(jì),增加了不必要的復(fù)雜度,適得其反。

六、代碼的道與術(shù)

任何一個(gè)學(xué)科的學(xué)習(xí),都要從基本概念、基本原理、基本方法入手,才能把握住問題的實(shí)質(zhì)。

所謂,招式套路可以千變?nèi)f化,扎實(shí)深厚的內(nèi)功卻始終如一。內(nèi)功是基礎(chǔ)和本源的東西,例如耦合和內(nèi)聚,我們都知道低耦合高內(nèi)聚好,但如何衡量代碼的耦合和內(nèi)聚?

再如編程范式,我們都在使用面向?qū)ο笳Z言,為什么看到的大多數(shù)是面向過程的代碼?究其根本,是我們?nèi)菀缀鲆暬A(chǔ)和本源的東西,比如更關(guān)注設(shè)計(jì)模式,更關(guān)注架構(gòu)設(shè)計(jì),但上層的設(shè)計(jì)理念大多數(shù)是來自基礎(chǔ)和本源的思想指引。

套用道家的一句話:道以明向,法以立本,術(shù)以立策,器以成事。

從代碼的角度來看:

  • 道:是好代碼,無以名狀,無以表述,只能假想形式表達(dá),仁者見仁智者見智;
  • 法:是編程范式,是規(guī)章制度,是規(guī)范約束,使其在道的合理范圍之內(nèi),保住基線;
  • 術(shù):是設(shè)計(jì)原則,執(zhí)行層面的方法論;
  • 器:是spring/mos-boot,有形的工具,保障的是執(zhí)行和效率。

從代碼的角度來看它們的關(guān)系:

  • 以法固道:理念需要通過法制來規(guī)范和約束,才能得到貫徹、落實(shí)和鞏固;
  • 以器載道:善于創(chuàng)造和使用工具,用工具承載理念,可以事半功倍;
  • 以道馭術(shù):基本理念和具體操作要統(tǒng)一,即知行合一。

關(guān)于如何寫好代碼,描述如有不當(dāng)之處,請(qǐng)大家?guī)兔χ刚?/span>

最后,一句話與大家共勉:萬丈高樓平地起,勿在浮沙筑高臺(tái)。

參考閱讀

[01]《重構(gòu)》

[02]《代碼整潔之道》

[03]《架構(gòu)整潔之道》

[04]《易讀代碼的藝術(shù)》

[05]《代碼精進(jìn)之路》

[06]《企業(yè)應(yīng)用架構(gòu)模式》

[07]《麥肯錫教我的協(xié)作武器》

[08]《深入理解Java虛擬機(jī)》

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    国产精品一区二区不卡中文 | 日本少妇中文字幕不卡视频| 国产欧美另类激情久久久| 中文字幕亚洲精品在线播放| 国产欧美日韩不卡在线视频| 韩国日本欧美国产三级 | 国产内射一级一片内射高清视频| 日本丰满大奶熟女一区二区| 亚洲国产精品久久琪琪| 国产精品不卡免费视频| 免费福利午夜在线观看| 日韩国产欧美中文字幕| 日韩精品视频一二三区| 国产日韩欧美在线播放| 国产日韩欧美在线亚洲| 熟妇久久人妻中文字幕| 九九热精品视频免费在线播放| 免费精品国产日韩热久久| 日韩少妇人妻中文字幕| 免费黄色一区二区三区| 精品欧美日韩一区二区三区| 国产中文另类天堂二区| 亚洲午夜精品视频在线| 国内自拍偷拍福利视频| 日韩国产中文在线视频| 免费在线观看激情小视频| 国产日韩精品欧美综合区| 激情丁香激情五月婷婷| 夫妻性生活动态图视频| 日韩专区欧美中文字幕| 午夜福利92在线观看| 粉嫩国产美女国产av| 亚洲一区二区三区熟女少妇| 欧美大粗爽一区二区三区| 少妇肥臀一区二区三区| 久久女同精品一区二区| 日韩毛片视频免费观看| 亚洲午夜福利不卡片在线| 国产亚洲不卡一区二区| 亚洲精品有码中文字幕在线观看| 日本特黄特色大片免费观看|