自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

他們在學(xué)校里不會教你的編程原則

開發(fā) 前端
本文總結(jié)了軟件編程中的黃金原則,KISS?原則,DRY?原則,SOLID原則。這些原則不僅僅適用于編程,也可以指導(dǎo)我們在架構(gòu)設(shè)計上。雖然其中有些原則很抽象,但是大家多多實踐和思考,會體會到這些原則的精妙。

前言

在大學(xué)的時候,學(xué)校一般只會教你你寫編程語言,比如C、C++、JAVA等編程語言。但是當(dāng)你離開大學(xué)進入這個行業(yè)開始工作時,才知道編程不只是知道編程語言、語法等,要想寫好代碼,必須還要了解一些編程原則才行。本文主要討論KISS、DRY和SOLID這些常見的編程原則,而且你會發(fā)現(xiàn)隨著工作時間越久,越能感受這些編程原則的精妙之處,歷久彌香。

KISS原則

Keep It Simple, Stupid!

你是不是有過接手同事的代碼感到十分頭疼的經(jīng)歷,明明可以有更加簡單、明白的寫法,非要繞來繞去,看不明白?

其實,我們在寫代碼的時候應(yīng)該要遵守KISS原則,核心思想就是盡量保持簡單。代碼的可讀性和可維護性是衡量代碼質(zhì)量非常重要的兩個標(biāo)準(zhǔn)。而 KISS 原則就是保持代碼可讀和可維護的重要手段。代碼足夠簡單,也就意味著很容易讀懂,bug 比較難隱藏。即便出現(xiàn) bug,修復(fù)起來也比較簡單。

我們寫代碼的的時候要站在別人的角度出發(fā),就像馬丁·福勒說的,我們寫的代碼不是給機器看的,而是給人看的。

“任何傻瓜都可以編寫計算機可以理解的代碼。優(yōu)秀的程序員編寫出人類可以理解的代碼?!?— 馬丁·福勒

那么如何才能寫出滿足KISS原則的代碼呢?

如何寫出KISS原則的代碼?

我們直接上例子,下面的校驗IP是否合法的3種實現(xiàn)方式,大家覺得哪個最KISS?

寫法一

圖片

寫法二

圖片

寫法三

圖片

  • 寫法一代碼量最少,正則表達式本身是比較復(fù)雜的,寫出完全沒有 bug 的正則表達本身就比較有挑戰(zhàn);另一方面,并不是每個程序員都精通正則表達式。對于不怎么懂正則表達式的同事來說,看懂并且維護這段正則表達式是比較困難的。這種實現(xiàn)方式會導(dǎo)致代碼的可讀性和可維護性變差,所以,從 KISS 原則的設(shè)計初衷上來講,這種實現(xiàn)方式并不符合 KISS 原則。
  • 寫法二使用了 StringUtils 類、Integer 類提供的一些現(xiàn)成的工具函數(shù),來處理 IP地址字符串,邏輯清晰,可讀性好。
  • 寫法三不使用任何工具函數(shù),而是通過逐一處理 IP 地址中的字符,來判斷是否合法,容易出bug,不好理解。

所以說,符合KISS原則的代碼并不是代碼越少越好,還要考慮代碼是否邏輯清晰、是否容易理解、是否夠穩(wěn)定。

總結(jié)以下如何寫出KISS原則的代碼:

  1. 不要使用同事可能不懂的技術(shù)來實現(xiàn)代碼。比如前面例子中的正則表達式,還有一些編程語言中過于高級的語法等。
  2. 不要重復(fù)造輪子,要善于使用已經(jīng)有的工具類庫。經(jīng)驗證明,自己去實現(xiàn)這些類庫,出bug 的概率會更高,維護的成本也比較高。
  3. 不要過度優(yōu)化。不要過度使用一些奇技淫巧(比如,位運算代替算術(shù)運算、復(fù)雜的條件語句代替if-else、使用一些過于底層的函數(shù)等)來優(yōu)化代碼,犧牲代碼的可讀性。
  4. 主觀站在別人的角度上編寫代碼。你在編寫代碼的時候就要思考我這個同事看這段代碼是不是很快就能夠明白理解。

DRY原則

Don't Repeat Yourself

你是不是有過這樣的經(jīng)歷,項目中很多重復(fù)邏輯的代碼,然后修改一個地方,另外一個地方忘記修改,導(dǎo)致測試給你提了很多bug?

DRY原則,英文全稱Don’t Repeat Yourself,直譯過來就是不要重復(fù)你自己。這里的重復(fù)不僅僅是代碼一模一樣,還包括實現(xiàn)邏輯重復(fù)、功能語義重復(fù)、代碼執(zhí)行重復(fù)等。我們不要偷懶,有責(zé)任把這些存在重復(fù)的地方識別出來,然后優(yōu)化它們。

如何寫出DRY原則的代碼呢?

我們直接上例子,代碼重復(fù)的我就不講了,很好理解,關(guān)于實現(xiàn)邏輯或者功能語義重復(fù)的我覺個例子。

還是上面校驗IP的例子,團隊中兩個同事由于不知道就有了兩種寫法。

  • 同事A寫法

圖片

  • 同事B寫法

圖片

盡管兩段代碼的實現(xiàn)邏輯不重復(fù),但語義重復(fù),也就是功能重復(fù),我們認為它違反了 DRY 原則。我們應(yīng)該在項目中,統(tǒng)一一種實現(xiàn)思路,所有用到判斷 IP 地址是否合法的地方,都統(tǒng)一調(diào)用同一個函數(shù)。不然哪天校驗規(guī)則變了,很容易只改了其中一個,另外一個漏改,就會出現(xiàn)莫名其妙的bug。

其他的比如邏輯重復(fù)的意思是雖然功能是不一致的,但是里面的邏輯都是一模一樣的。舉個例子,比如校驗用戶名和校驗密碼,雖然功能不一致,但是校驗邏輯都是相似,判空、字符長度等等,這種情況我們就需要把相似的邏輯抽取到一個方法中,不然也是不符合DRY原則。

那么我們平時寫代碼注意些什么才是符合DRY原則呢?

  • 使用現(xiàn)成的輪子,不輕易造輪子

其實最關(guān)鍵的就是寫代碼帶腦子,用到一個方法先看看有沒有現(xiàn)成的,不要看看不看,就動手在那里造輪子。

  • 減少代碼耦合

對于高度耦合的代碼,當(dāng)我們希望復(fù)用其中的一個功能,想把這個功能的代碼抽取出來成為一個獨立的模塊、類或者函數(shù)的時候,往往會發(fā)現(xiàn)牽一發(fā)而動全身。移動一點代碼,就要牽連到很多其他相關(guān)的代碼。所以,高度耦合的代碼會影響到代碼的復(fù)用性,我們要盡量減少代碼耦合。

  • 滿足單一職責(zé)原則

我們前面講過,如果職責(zé)不夠單一,模塊、類設(shè)計得大而全,那依賴它的代碼或者它依賴的代碼就會比較多,進而增加了代碼的耦合。根據(jù)上一點,也就會影響到代碼的復(fù)用性。相反,越細粒度的代碼,代碼的通用性會越好,越容易被復(fù)用。

  • 模塊化

這里的“模塊”,不單單指一組類構(gòu)成的模塊,還可以理解為單個類、函數(shù)。我們要善于將功能獨立的代碼,封裝成模塊。獨立的模塊就像一塊一塊的積木,更加容易復(fù)用,可以直接拿來搭建更加復(fù)雜的系統(tǒng)。

  • 業(yè)務(wù)與非業(yè)務(wù)邏輯分離

越是跟業(yè)務(wù)無關(guān)的代碼越是容易復(fù)用,越是針對特定業(yè)務(wù)的代碼越難復(fù)用。所以,為了復(fù)用跟業(yè)務(wù)無關(guān)的代碼,我們將業(yè)務(wù)和非業(yè)務(wù)邏輯代碼分離,抽取成一些通用的框架、類庫、組件等。

  • 通用代碼下沉

從分層的角度來看,越底層的代碼越通用、會被越多的模塊調(diào)用,越應(yīng)該設(shè)計得足夠可復(fù)用。一般情況下,在代碼分層之后,為了避免交叉調(diào)用導(dǎo)致調(diào)用關(guān)系混亂,我們只允許上層代碼調(diào)用下層代碼及同層代碼之間的調(diào)用,杜絕下層代碼調(diào)用上層代碼。所以,通用的代碼我們盡量下沉到更下層。

  • 繼承、多態(tài)、抽象、封裝

在講面向?qū)ο筇匦缘臅r候,我們講到,利用繼承,可以將公共的代碼抽取到父類,子類復(fù)用父類的屬性和方法。利用多態(tài),我們可以動態(tài)地替換一段代碼的部分邏輯,讓這段代碼可復(fù)用。除此之外,抽象和封裝,從更加廣義的層面、而非狹義的面向?qū)ο筇匦缘膶用鎭砝斫獾脑?,越抽象、越不依賴具體的實現(xiàn),越容易復(fù)用。代碼封裝成模塊,隱藏可變的細節(jié)、暴露不變的接口,就越容易復(fù)用。

  • 應(yīng)用模板等設(shè)計模式

一些設(shè)計模式,也能提高代碼的復(fù)用性。比如,模板模式利用了多態(tài)來實現(xiàn),可以靈活地替換其中的部分代碼,整個流程模板代碼可復(fù)用。

SOLID原則

SOLID原則不是一個單一的原則,而是對軟件開發(fā)至關(guān)重要的 5 條原則,遵循這些原則有助于我們寫出高內(nèi)聚、低耦合、可擴展、可維護性好的代碼。

S—單一職責(zé)原則

一個類應(yīng)該有一個,而且只有一個改變它的理由。

單一職責(zé)原則在我看來是最容易理解也是最重要的一個原則。它的核心思想就是一個模塊、類或者方法只做一件事,只有一個職責(zé),千萬不要越俎代庖。它可以帶來下面的好處:

  • 可以讓代碼耦合度更低
  • 使代碼更容易理解和維護
  • 使代碼更易于測試和維護,使軟件更易于實施,并有助于避免未來更改的意外副作用

舉個例子,我們有兩個類Person和Account。 兩者都負有存儲其特定信息的單一責(zé)任。 如果要更改Person的狀態(tài),則無需修改類Account,反之亦然, 不要把賬戶的行為比如修改賬戶名changeAcctName寫在Person類中。

public class Person {
     private Long personId;
     private String firstName;
     private String lastName;
     private String age;
     private List<Account> accounts;

        // 錯誤做法
        public void changeAcctName(Account account, String acctName) {
            acccount.setAccountName(acctName);
            // 更新到數(shù)據(jù)庫
        }
    }

    public class Account {
     private Long guid;
     private String accountNumber;
     private String accountName;
     private String status;
     private String type;

    }

所以大家在編寫代碼的時候,一定要停頓思考下這個段代碼真的寫在這里嗎?另外很關(guān)鍵的一點是如果發(fā)現(xiàn)一個類或者一個方法十分龐大,那么很有可能已經(jīng)違背單一職責(zé)原則了,后續(xù)維護可想而知十分痛苦。

O—開閉原則

軟件實體(類、模塊、函數(shù)等)應(yīng)該對擴展開放,對修改關(guān)閉。

對擴展開放,對修改關(guān)閉,什么意思?很簡單,其實就是我們要盡量通過新增類實現(xiàn)功能,而不是修改原有的類或者邏輯。因為修改已有代碼很有可能對已有功能引入bug。

讓我們通過一個例子來理解這個原則,比如一個通知服務(wù)。

public class NotificationService {
     public void sendOTP(String medium) {
            if (medium.equals("email")) {
                //email 發(fā)送
            } else if (medium.equals("mobile")) {
                // 手機發(fā)送
         } 
    }

現(xiàn)在需要新增微信的方式通知,你要怎么做呢? 是在加一個if else嗎? 這樣就不符合開閉原則了,我們看下開閉原則該怎么寫。

  • 定義一個通知服務(wù)接口
public interface NotificationService {
     public void sendOTP();
    }
  • E-mail方式通知類EmailNotification
public class EmailNotification implements NotificationService{
     public void sendOTP(){
      // write Logic using JavaEmail api
     }
    }
  • 手機方式通知類MobileNotification
public class MobileNotification implements NotificationService{
        public void sendOTP(){
      // write Logic using Twilio SMS API
     }
    }
  • 同樣可以添加微信通知服務(wù)的實現(xiàn)WechatNotification
public class WechatNotification implements NotificationService{
     public void sendOTP(String medium){
      // write Logic using wechat API
     }
    }

這樣的方式就是遵循開閉原則的,你不用修改核心的業(yè)務(wù)邏輯,這樣可能帶來意向不到的后果,而是擴展實現(xiàn)方式,由調(diào)用方根據(jù)他們的實際情況調(diào)用。

是不是想到了設(shè)計模式中的策略模式,其實設(shè)計模式就是指導(dǎo)我們寫出高內(nèi)聚、低耦合的代碼。

L—里氏替換原則

派生類或子類必須可替代其基類或父類

這個原則稍微有點難以理解,它的核心思想是每個子類或派生類都應(yīng)該可以替代/等效于它們的基類或父類。這樣有一個好處,就是無論子類是什么類型,客戶端通過父類調(diào)用都不會產(chǎn)生意外的后果。

理解不了?那我我們通過一個例子來理解一下。

讓我們考慮一下我有一個名為 SocialMedia 的抽象類,它支持所有社交媒體活動供用戶娛樂,如下所示:

package com.alvin.solid.lsp;

    public abstract class SocialMedia {
        
        public abstract  void chatWithFriend();
        
        public abstract void publishPost(Object post);
        
        public abstract  void sendPhotosAndVideos();
        
        public abstract  void groupVideoCall(String... users);
    }

社交媒體可以有多個實現(xiàn)或可以有多個子類,如 Facebook、Wechat、Weibo 和 Twitter 等。

現(xiàn)在讓我們假設(shè) Facebook 想要使用這個特性或功能。

package com.alvin.solid.lsp;

    public class Wechat extends SocialMedia {

        public void chatWithFriend() {
            //logic  
        }

        public void publishPost(Object post) {
            //logic  
        }

        public void sendPhotosAndVideos() {
            //logic  
        }

        public void groupVideoCall(String... users) {
            //logic  
        }
    }

我們都知道Facebook都提供了所有上述的功能,所以這里我們可以認為Facebook是SocialMedia類的完全替代品,兩者都可以無中斷地替代。

現(xiàn)在讓我們討論 Weibo 類

package com.alvin.solid.lsp;

    public class Weibo extends SocialMedia {
        public void chatWithFriend() {
            //logic
        }

        public void publishPost(Object post) {
          //logic
        }

        public void sendPhotosAndVideos() {
          //logic
        }

        public void groupVideoCall(String... users) {
            //不適用
        }
    }

我們都知道Weibo微博這個產(chǎn)品是沒有群視頻功能的,所以對于 groupVideoCall方法來說 Weibo 子類不能替代父類 SocialMedia。所以我們認為它是不符合里式替換原則。

如果強行這么做的話,會導(dǎo)致客戶端用父類SocialMedia調(diào)用,但是實現(xiàn)類注入的可能是個Weibo的實現(xiàn),調(diào)用groupVideoCall行為,產(chǎn)生意想不到的后果。

那有什么解決方案嗎?

那就把功能拆開唄。

public interface SocialMedia {   
       public void chatWithFriend(); 
       public void sendPhotosAndVideos() 
    }
public interface SocialPostAndMediaManager { 
        public void publishPost(Object post); 
    }
public interface VideoCallManager{ 
       public void groupVideoCall(String... users); 
    }

現(xiàn)在,如果您觀察到我們將特定功能隔離到單獨的類以遵循LSP。

現(xiàn)在由實現(xiàn)類決定支持功能,根據(jù)他們所需的功能,他們可以使用各自的接口,例如 Weibo 不支持視頻通話功能,因此 Weibo 實現(xiàn)可以設(shè)計成這樣:

public class Instagram implements SocialMedia,SocialPostAndMediaManager{
     public void chatWithFriend(){
        //logic
        }
        public void sendPhotosAndVideos(){
        //logic
        }
        public void publishPost(Object post){
        //logic
        }
    }

這樣子就是符合里式替換原則LSP。

I—接口隔離原則

接口不應(yīng)該強迫他們的客戶依賴它不使用的方法。

大家可以看看自己的工程,是不是一個接口類中有很多很多的接口,每次調(diào)用API方法的時候IDE工具給你彈出一大堆,十分的"臃腫肥胖"。所以該原則的核心思想要將你的接口拆小,拆細,打破”胖接口“,不用強迫客戶端實現(xiàn)他們不需要的接口。是不是和單一職責(zé)原則有點像?

例如,假設(shè)有一個名為 UPIPayment 的接口,如下所示

public interface UPIPayments {
        
        public void payMoney();
        
        public void getScratchCard();
        
        public void getCashBackAsCreditBalance();
    }

現(xiàn)在讓我們談?wù)?nbsp;UPIPayments 的一些實現(xiàn),比如 Google Pay 和 AliPay。

Google Pay 支持這些功能所以他可以直接實現(xiàn)這個 UPIPayments 但 AliPay 不支持 getCashBackAsCreditBalance()功能所以這里我們不應(yīng)該強制客戶端 AliPay 通過實現(xiàn) UPIPayments 來覆蓋這個方法。

我們需要根據(jù)客戶需要分離接口,所以為了滿足接口隔離原則,我們可以如下設(shè)計:

  • 創(chuàng)建一個單獨的接口來處理現(xiàn)金返還。
public interface CashbackManager{ 
     public void getCashBackAsCreditBalance(); 
    }

現(xiàn)在我們可以從 UPIPayments 接口中刪除getCashBackAsCreditBalance ,AliPay也不需要實現(xiàn)getCashBackAsCreditBalance()這個它沒有的方法了。

D—依賴倒置原則

高層模塊不應(yīng)該依賴低層模塊,兩者都應(yīng)該依賴于抽象(接口)。抽象不應(yīng)該依賴于細節(jié)(具體實現(xiàn)),細節(jié)應(yīng)該取決于抽象。

這個原則我覺得也不是很好理解,所謂高層模塊和低層模塊的劃分,簡單來說就是,在調(diào)用鏈上,調(diào)用者屬于高層,被調(diào)用者屬于低層。比如大家都知道的MVC模式,controller是調(diào)用service層接口這個抽象,而不是實現(xiàn)類。這也是我們經(jīng)常說的要面向接口編程,而非細節(jié)或者具體實現(xiàn),因為接口意味著契約,更加穩(wěn)定。

我們通過一個例子加深一下理解。

  • 借記卡
public class DebitCard { 
     public void doTransaction(int amount){ 
            System.out.println("tx done with DebitCard"); 
        } 
    }
  • 信用卡
public class CreditCard{ 
     public void doTransaction(int amount){ 
            System.out.println("tx done with CreditCard"); 
        } 
    }

現(xiàn)在用這兩張卡你去購物中心購買了一些訂單并決定使用信用卡支付

public class ShoppingMall {
     private DebitCard debitCard;
     public ShoppingMall(DebitCard debitCard) {
            this.debitCard = debitCard;
        }
     public void doPayment(Object order, int amount){              
            debitCard.doTransaction(amount); 
      }
     public static void main(String[] args) {
          DebitCard debitCard=new DebitCard();
          ShoppingMall shoppingMall=new ShoppingMall(debitCard);
          shoppingMall.doPayment("some order",5000);
        }
    }

上面的做法是一個錯誤的方式,因為 ShoppingMall 類與 DebitCard 緊密耦合。

現(xiàn)在你的借記卡余額不足,想使用信用卡,那么這是不可能的,因為 ShoppingMall 與借記卡緊密結(jié)合。

當(dāng)然你也可以這樣做,從構(gòu)造函數(shù)中刪除借記卡并注入信用卡。但這不是一個好的方式,它不符合依賴倒置原則。

那該如何正確設(shè)計呢?

  • 定義依賴的抽象接口BankCard
public interface BankCard { 
      public void doTransaction(int amount); 
    }
  • 現(xiàn)在 DebitCard 和 CreditCard 都實現(xiàn)BankCard
public class CreditCard implements BankCard{
     public void doTransaction(int amount){            
            System.out.println("tx done with CreditCard");
        }
    }
public class DebitCard implements BankCard { 
     public void doTransaction(int amount){ 
      System.out.println("tx done with DebitCard"); 
        } 
    }
  • 現(xiàn)在重新設(shè)計購物中心這個高級類,他也是去依賴這個抽象,而不是直接低級模塊的實現(xiàn)類
public class ShoppingMall {
     private BankCard bankCard;
     public ShoppingMall(BankCard bankCard) {
            this.bankCard = bankCard;
        }
     public void doPayment(Object order, int amount){
            bankCard.doTransaction(amount);
        }
     public static void main(String[] args) {
            BankCard bankCard=new CreditCard();
            ShoppingMall shoppingMall1=new ShoppingMall(bankCard);
            shoppingMall1.doPayment("do some order", 10000);
        }
    }

我們還可以拿 Tomcat這個 Servlet 容器作為例子來解釋一下。

Tomcat 是運行Java Web應(yīng)用程序的容器。我們編寫的 Web 應(yīng)用程序代碼只需要部署在Tomcat 容器下,便可以被 Tomcat 容器調(diào)用執(zhí)行。按照之前的劃分原則,Tomcat 就是高層模塊,我們編寫的 Web 應(yīng)用程序代碼就是低層模塊。Tomcat 和應(yīng)用程序代碼之間并沒有直接的依賴關(guān)系,兩者都依賴同一個“抽象”,也就是 Sevlet 規(guī)范。Servlet 規(guī)范不依賴具體的 Tomcat 容器和應(yīng)用程序的實現(xiàn)細節(jié),而 Tomcat 容器和應(yīng)用程序依賴 Servlet規(guī)范。

總結(jié)

本文總結(jié)了軟件編程中的黃金原則,KISS原則,DRY原則,SOLID原則。這些原則不僅僅適用于編程,也可以指導(dǎo)我們在架構(gòu)設(shè)計上。雖然其中有些原則很抽象,但是大家多多實踐和思考,會體會到這些原則的精妙。

責(zé)任編輯:武曉燕 來源: JAVA旭陽
相關(guān)推薦

2022-06-15 09:54:51

PythonIDELinux

2022-06-16 16:08:00

Python初學(xué)者IDE

2015-10-12 11:00:23

編程學(xué)校工作

2019-04-08 15:48:45

程序員技能編程

2017-03-07 15:43:28

編程語言函數(shù)數(shù)據(jù)結(jié)構(gòu)

2018-08-22 10:27:35

編程人工智能機器人

2016-10-31 20:02:21

WordPress網(wǎng)站數(shù)據(jù)集大數(shù)據(jù)轉(zhuǎn)型戰(zhàn)略

2018-06-21 08:38:05

編程語言程序員代碼

2024-01-23 09:07:29

Unix哲學(xué)工具

2018-07-18 08:48:11

2013-06-20 11:15:00

編程技能編程技巧如何選編程

2014-11-10 09:46:57

程序員

2020-06-01 08:35:36

編程語言JavaPython

2018-07-18 15:05:01

2015-10-10 14:40:36

編程游戲

2012-09-10 09:43:21

編程編程學(xué)習(xí)編程錯誤

2018-01-15 10:59:57

語言Java開發(fā)人員

2009-01-16 08:52:26

面向?qū)ο?/a>OOP編程

2014-07-09 13:18:55

編程谷歌

2011-11-02 09:57:28

程序員
點贊
收藏

51CTO技術(shù)棧公眾號