這25種代碼壞味道大家要註意啦

来源:https://www.cnblogs.com/klkvalves/archive/2022/04/04/16098790.html
-Advertisement-
Play Games

1.環境準備 環境準備的統一資源提取碼為:1234 1.下載 VMware14中文版 14.1.3 VM14虛擬機 2.下載CentOs系統,建議版本最低7.0+ 3.安裝虛擬機,如果有雲伺服器,就不需要安裝虛擬機了 4.下載XFtp 和 XShell 5.下載Redis在Linux系統下的安裝包, ...


前言

什麼樣的代碼是好代碼呢?好的代碼應該命名規範、可讀性強、擴展性強、健壯性......而不好的代碼又有哪些典型特征呢?這25種代碼壞味道大家要註意啦

1. Duplicated Code (重覆代碼)

重覆代碼就是不同地點,有著相同的程式結構。一般是因為需求迭代比較快,開發小伙伴擔心影響已有功能,就複製粘貼造成的。重覆代碼很難維護的,如果你要修改其中一段的代碼邏輯,就需要修改多次,很可能出現遺漏的情況。

如何優化重覆代碼呢?分三種情況討論:

  1. 同一個類的兩個函數含有相同的表達式
class A {
    public void method1() {
        doSomething1
        doSomething2
        doSomething3
    }
    public void method2() {
        doSomething1
        doSomething2
        doSomething4
    }
}

優化手段:可以使用Extract Method(提取公共函數) 抽出重覆的代碼邏輯,組成一個公用的方法。

class A {
    public void method1() {
        commonMethod();
        doSomething3
    }
    public void method2() {
        commonMethod();
        doSomething4
    }
    
    public void commonMethod(){
       doSomething1
       doSomething2
    }
}
  1. 兩個互為兄弟的子類內含相同的表達式
class A extend C {
    public void method1() {
        doSomething1
        doSomething2
        doSomething3
    }
}

class B extend C {
    public void method1() {
        doSomething1
        doSomething2
        doSomething4
    }
}

優化手段:對兩個類都使用Extract Method(提取公共函數),然後把抽取出來的函數放到父類中。

class C {
    public void commonMethod(){
     doSomething1
     doSomething2
   }
}
class A extend C {
    public void method1() {
        commonMethod();
        doSomething3
    }
}

class B extend C {
    public void method1() {
        commonMethod();
        doSomething4
    }
}
  1. 兩個毫不相關的類出現重覆代碼

如果是兩個毫不相關的類出現重覆代碼,可以使用Extract Class將重覆代碼提煉到一個類中。這個新類可以是一個普通類,也可以是一個工具類,看具體業務怎麼劃分吧。

2 .Long Method (長函數)

長函數是指一個函數方法幾百行甚至上千行,可讀性大大降低,不便於理解。反例如下:

public class Test {
    private String name;
    private Vector<Order> orders = new Vector<Order>();

    public void printOwing() {
        //print banner
        System.out.println("****************");
        System.out.println("*****customer Owes *****");
        System.out.println("****************");

        //calculate totalAmount
        Enumeration env = orders.elements();
        double totalAmount = 0.0;
        while (env.hasMoreElements()) {
            Order order = (Order) env.nextElement();
            totalAmount += order.getAmout();
        }

        //print details
        System.out.println("name:" + name);
        System.out.println("amount:" + totalAmount);
        ......
    }
}

可以使用Extract Method,抽取功能單一的代碼段,組成命名清晰的小函數,去解決長函數問題,正例如下:

public class Test {
    private String name;
    private Vector<Order> orders = new Vector<Order>();

    public void printOwing() {

        //print banner
        printBanner();
        //calculate totalAmount
        double totalAmount = getTotalAmount();
        //print details
        printDetail(totalAmount);
    }

    void printBanner(){
        System.out.println("****************");
        System.out.println("*****customer Owes *****");
        System.out.println("****************");
    }

    double getTotalAmount(){
        Enumeration env = orders.elements();
        double totalAmount = 0.0;
        while (env.hasMoreElements()) {
            Order order = (Order) env.nextElement();
            totalAmount += order.getAmout();
        }
        return totalAmount;
    }

    void printDetail(double totalAmount){
        System.out.println("name:" + name);
        System.out.println("amount:" + totalAmount);
    }
    
}

3. Large Class (過大的類)

一個類做太多事情,維護了太多功能,可讀性變差,性能也會下降。舉個例子,訂單相關的功能你放到一個類A裡面,商品庫存相關的也放在類A裡面,積分相關的還放在類A裡面...反例如下:

Class A{
  public void printOrder(){
   System.out.println("訂單");
  }
  
  public void printGoods(){
   System.out.println("商品");
  }
  
  public void printPoints(){
   System.out.println("積分");
  }
}

試想一下,亂七八糟的代碼塊都往一個類裡面塞,還談啥可讀性。應該按單一職責,使用Extract Class把代碼劃分開,正例如下:

Class Order{
  public void printOrder(){
   System.out.println("訂單");
  }
}

Class Goods{
   public void printGoods(){
   System.out.println("商品");
  }
}
 
Class Points{   
  public void printPoints(){
   System.out.println("積分");
  }
 }
}

4. Long Parameter List (過長參數列)

方法參數數量過多的話,可讀性很差。如果有多個重載方法,參數很多的話,有時候你都不知道調哪個呢。並且,如果參數很多,做新老介面相容處理也比較麻煩。

public void getUserInfo(String name,String age,String sex,String mobile){
  // do something ...
}

如何解決過長參數列問題呢?將參數封裝成結構或者類,比如我們將參數封裝成一個DTO類,如下:

public void getUserInfo(UserInfoParamDTO userInfoParamDTO){
  // do something ...
}

class UserInfoParamDTO{
  private String name;
  private String age; 
  private String sex;
  private String mobile;
}

5. Divergent Change (發散式變化)

對程式進行維護時, 如果添加修改組件, 要同時修改一個類中的多個方法, 那麼這就是 Divergent Change。舉個汽車的例子,某個汽車廠商生產三種品牌的汽車:BMW、Benz和LaoSiLaiSi,每種品牌又可以選擇燃油、純電和混合動力。反例如下:

/**
 *  公眾號:撿田螺的小男孩
 */
public class Car {

    private String name;

    void start(Engine engine) {
        if ("HybridEngine".equals(engine.getName())) {
            System.out.println("Start Hybrid Engine...");
        } else if ("GasolineEngine".equals(engine.getName())) {
            System.out.println("Start Gasoline Engine...");
        } else if ("ElectricEngine".equals(engine.getName())) {
            System.out.println("Start Electric Engine");
        }
    }

    void drive(Engine engine,Car car) {
        this.start(engine);
        System.out.println("Drive " + getBrand(car) + " car...");
    }

    String getBrand(Car car) {
        if ("Baoma".equals(car.getName())) {
            return "BMW";
        } else if ("BenChi".equals(car.getName())) {
            return "Benz";
        } else if ("LaoSiLaiSi".equals(car.getName())) {
            return "LaoSiLaiSi";
        }
        return null;
    }
 }

如果新增一種品牌新能源電車,然後它的啟動引擎是核動力呢,那麼就需要修改Car類的startgetBrand方法啦,這就是代碼壞味道:Divergent Change (發散式變化)。

如何優化呢?一句話總結:拆分類,將總是一起變化的東西放到一塊。

  • 運用提煉類(Extract Class) 拆分類的行為。
  • 如果不同的類有相同的行為,提煉超類(Extract Superclass) 和 提煉子類(Extract Subclass)。

正例如下:

因為Engine是獨立變化的,所以提取一個Engine介面,如果新加一個啟動引擎,多一個實現類即可。如下:

//IEngine
public interface IEngine {
    void start();
}

public class HybridEngineImpl implements IEngine { 
    @Override
    public void start() {
        System.out.println("Start Hybrid Engine...");
    }
}

因為drive方法依賴於Car,IEngine,getBand方法;getBand方法是變化的,也跟Car是有關聯的,所以可以搞個抽象Car的類,每個品牌汽車繼承於它即可,如下

public abstract class AbstractCar {

    protected IEngine engine;

    public AbstractCar(IEngine engine) {
        this.engine = engine;
    }

    public abstract void drive();
}

//賓士汽車
public class BenzCar extends AbstractCar {

    public BenzCar(IEngine engine) {
        super(engine);
    }

    @Override
    public void drive() {
      this.engine.start();
      System.out.println("Drive " + getBrand() + " car...");
    }

    private String getBrand() {
        return "Benz";
    }
}

//寶馬汽車
public class BaoMaCar extends AbstractCar {

    public BaoMaCar(IEngine engine) {
        super(engine);
    }

    @Override
    public void drive() {
        this.engine.start();
        System.out.println("Drive " + getBrand() + " car...");
    }

    private String getBrand() {
        return "BMW";
    }
}

細心的小伙伴,可以發現不同子類BaoMaCar和BenzCar的drive方法,還是有相同代碼,所以我們可以再擴展一個抽象子類,把drive方法推進去,如下:

public abstract class AbstractRefinedCar extends AbstractCar {

    public AbstractRefinedCar(IEngine engine) {
        super(engine);
    }

    @Override
    public void drive() {
        this.engine.start();
        System.out.println("Drive " + getBrand() + " car...");
    }

    abstract String getBrand();
}

//寶馬
public class BaoMaRefinedCar extends AbstractRefinedCar {

    public BaoMaRefinedCar(IEngine engine) {
        super(engine);
    }

    @Override
    String getBrand() {
        return  "BMW";
    }
}

如果再添加一個新品牌,搞個子類,繼承AbstractRefinedCar即可,如果新增一種啟動引擎,也是搞個類實現IEngine介面即可

6. Shotgun Surgery(散彈式修改)

當你實現某個小功能時,你需要在很多不同的類做出小修改。這就是Shotgun Surgery(散彈式修改)。它跟發散式變化(Divergent Change) 的區別就是,它指的是同時對多個類進行單一的修改,發散式變化指在一個類中修改多處。反例如下:

public class DbAUtils {
    @Value("${db.mysql.url}")
    private String mysqlDbUrl;
    ...
}

public class DbBUtils {
    @Value("${db.mysql.url}")
    private String mysqlDbUrl;
    ...
}

多個類使用了db.mysql.url這個變數,如果將來需要切換mysql到別的資料庫,如Oracle,那就需要修改多個類的這個變數!

如何優化呢?將各個修改點,集中到一起,抽象成一個新類。

★ 可以使用 Move Method (搬移函數)和 Move Field (搬移欄位)把所有需要修改的代碼放進同一個類,如果沒有合適的類,就去new一個。

正例如下:

public class DbUtils {
    @Value("${db.mysql.url}")
    private String mysqlDbUrl;
    ...
}

7. Feature Envy (依戀情節)

某個函數為了計算某個值,從另一個對象那裡調用幾乎半打的取值函數。通俗點講,就是一個函數使用了大量其他類的成員,有人稱之為紅杏出牆的函數。反例如下:

public class User{
 private Phone phone;
  public User(Phone phone){
        this.phone = phone;
    }
    public void getFullPhoneNumber(Phone phone){
        System.out.println("areaCode:" + phone.getAreaCode());
        System.out.println("prefix:" + phone.getPrefix());
        System.out.println("number:" + phone.getNumber());
    }
}

如何解決呢?在這種情況下,你可以考慮將這個方法移動到它使用的那個類中。例如,要將 getFullPhoneNumber()從 User 類移動到Phone類中,因為它調用了Phone類的很多方法。

8. Data Clumps(數據泥團)

數據項就像小孩子,喜歡成群結隊地呆在一塊。如果一些數據項總是一起出現的,並且一起出現更有意義的,就可以考慮,按數據的業務含義來封裝成數據對象。反例如下:

public class User {

    private String firstName;
    private String lastName;

    private String province;
    private String city;
    private String area;
    private String street;
}

正例:

public class User {

    private UserName username;
    private Adress adress;
}

class UserName{
    private String firstName;
    private String lastName;
}
class Address{
    private String province;
    private String city;
    private String area;
    private String street;
}

9. Primitive Obsession (基本類型偏執)

多數編程環境都有兩種數據類型,結構類型和基本類型。這裡的基本類型,如果指Java語言的話,不僅僅包括那八大基本類型哈,也包括String等。如果是經常一起出現的基本類型,可以考慮把它們封裝成對象。我個人覺得它有點像Data Clumps(數據泥團) 舉個反例如下:

// 訂單
public class Order {
    private String customName;
    private String address;
    private Integer orderId;
    private Integer price;
}

正例:

// 訂單類
public class Order {
    private Custom custom;
    private Integer orderId;
    private Integer price;
}
// 把custom相關欄位封裝起來,在Order中引用Custom對象
public class Custom {
    private String name;
    private String address;
}

當然,這裡不是所有的基本類型,都建議封裝成對象,有關聯或者一起出現的,才這麼建議哈。

10. Switch Statements (Switch 語句)

這裡的Switch語句,不僅包括Switch相關的語句,也包括多層if...else的語句哈。很多時候,switch語句的問題就在於重覆,如果你為它添加一個新的case語句,就必須找到所有的switch語句並且修改它們。

示例代碼如下:

    String medalType = "guest";
    if ("guest".equals(medalType)) {
        System.out.println("嘉賓勛章");
     } else if ("vip".equals(medalType)) {
        System.out.println("會員勛章");
    } else if ("guard".equals(medalType)) {
        System.out.println("守護勛章");
    }
    ...

這種場景可以考慮使用多態優化:

//勛章介面
public interface IMedalService {
    void showMedal();
}

//守護勛章策略實現類
public class GuardMedalServiceImpl implements IMedalService {
    @Override
    public void showMedal() {
        System.out.println("展示守護勛章");
    }
}
//嘉賓勛章策略實現類
public class GuestMedalServiceImpl implements IMedalService {
    @Override
    public void showMedal() {
        System.out.println("嘉賓勛章");
    }
}

//勛章服務工廠類
public class MedalServicesFactory {

    private static final Map<String, IMedalService> map = new HashMap<>();
    static {
        map.put("guard", new GuardMedalServiceImpl());
        map.put("vip", new VipMedalServiceImpl());
        map.put("guest", new GuestMedalServiceImpl());
    }
    public static IMedalService getMedalService(String medalType) {
        return map.get(medalType);
    }
}

當然,多態只是優化的一個方案,一個方向。如果只是單一函數有些簡單選擇示例,並不建議動不動就使用動態,因為顯得有點殺雞使用牛刀了。

11.Parallel Inheritance Hierarchies( 平行繼承體系)

平行繼承體系 其實算是Shotgun Surgery的特殊情況啦。當你為A類的一個子類Ax,也必須為另一個類B相應的增加一個子類Bx。

解決方法:遇到這種情況,就要消除兩個繼承體系之間的引用,有一個類是可以去掉繼承關係的。

12. Lazy Class (冗贅類)

把這些不再重要的類裡面的邏輯,合併到相關類,刪掉舊的。一個比較常見的場景就是,假設系統已經有日期工具類DateUtils,有些小伙伴在開發中,需要用到日期轉化等,不管三七二十一,又自己實現一個新的日期工具類。

13. Speculative Generality(誇誇其談未來性)

儘量避免過度設計的代碼。例如:

  • 只有一個if else,那就不需要班門弄斧使用多態;
  • 如果某個抽象類沒有什麼太大的作用,就運用Collapse Hierarchy(摺疊繼承體系)```
  • 如果函數的某些參數沒用上,就移除。

14. Temporary Field(令人迷惑的臨時欄位)

某個實例變數僅為某種特定情況而定而設,這樣的代碼就讓人不易理解,我們稱之為 Temporary Field(令人迷惑的臨時欄位)。 反例如下:

public class PhoneAccount {

    private double excessMinutesCharge;
    private static final double RATE = 8.0;

    public double computeBill(int minutesUsed, int includedMinutes) {
        excessMinutesCharge = 0.0;
        int excessMinutes = minutesUsed - includedMinutes;
        if (excessMinutes >= 1) {
            excessMinutesCharge = excessMinutes * RATE;
        }
        return excessMinutesCharge;
    }

    public double chargeForExcessMinutes(int minutesUsed, int includedMinutes) {
        computeBill(minutesUsed, includedMinutes);
        return excessMinutesCharge;
    }
}

思考一下,臨時欄位excessMinutesCharge是否多餘呢?

15. Message Chains (過度耦合的消息鏈)

當你看到用戶向一個對象請求另一個對象,然後再向後者請求另一個對象,然後再請求另一個對象...這就是消息鏈。實際代碼中,你看到的可能是一長串getThis()或一長串臨時變數。反例如下:

A.getB().getC().getD().getTianLuoBoy().getData();

A想要獲取需要的數據時,必須要知道B,又必須知道C,又必須知道D...其實A需要知道得太多啦,回頭想下封裝性,嘻嘻。其實可以通過拆函數或者移動函數解決,比如由B作為代理,搞個函數直接返回A需要數據。

16. Middle Man (中間人)

對象的基本特征之一就是封裝,即對外部世界隱藏其內部細節。封裝往往伴隨委托,過度運用委托就不好:某個類介面有一半的函數都委托給其他類。可以使用Remove Middle Man優化。反例如下:

A.B.getC(){
   return C.getC();
}

其實,A可以直接通過C去獲取C,而不需要通過B去獲取。

17. Inappropriate Intimacy(狎昵關係)

如果兩個類過於親密,過分狎昵,你中有我,我中有你,兩個類彼此使用對方的私有的東西,就是一種壞代碼味道。我們稱之為Inappropriate Intimacy(狎昵關係)

建議儘量把有關聯的方法或屬性抽離出來,放到公共類,以減少關聯。

18. Alternative Classes with Different Interfaces (異曲同工的類)

A類的介面a,和B類的介面b,做的的是相同一件事,或者類似的事情。我們就把A和B叫做異曲同工的類。

可以通過重命名,移動函數,或抽象子類等方式優化

19. Incomplete Library Class (不完美的類庫)

大多數對象只要夠用就好,如果類庫構造得不夠好,我們不可能修改其中的類使它完成我們希望完成的工作。可以醬紫:包一層函數或包成新的類。

20. Data Class (純數據類)

什麼是Data Class? 它們擁有一些欄位,以及用於訪問(讀寫)這些欄位的函數。這些類很簡單,僅有公共成員變數,或簡單操作的函數。

如何優化呢?將相關操作封裝進去,減少public成員變數。比如:

  • 如果擁有public欄位-> Encapsulate Field
  • 如果這些類內含容器類的欄位,應該檢查它們是不是得到了恰當地封裝-> Encapsulate Collection封裝起來
  • 對於不該被其他類修改的欄位-> Remove Setting Method->找出取值/設置函數被其他類運用的地點-> Move Method 把這些調用行為搬移到Data Class來。如果無法搬移整個函數,就運用Extract Method產生一個可被搬移的函數->Hide Method把這些取值/設置函數隱藏起來。

21. Refused Bequest (被拒絕的饋贈)

子類應該繼承父類的數據和函數。子類繼承得到所有函數和數據,卻只使用了幾個,那就是繼承體系設計錯誤,需要優化。

  • 需要為這個子類新建一個兄弟類->Push Down MethodPush Down Field把所有用不到的函數下推給兄弟類,這樣一來,超類就只持有所有子類共用的東西。所有超類都應該是抽象的。
  • 如果子類復用了超類的實現,又不願意支持超類的介面,可以不以為然。但是不能胡亂修改繼承體系->Replace Inheritance with Delegation(用委派替換繼承).

22. Comments (過多的註釋)

這個點不是說代碼不建議寫註釋哦,而是,建議大家避免用註釋解釋代碼,避免過多的註釋。這些都是常見註釋的壞味道:

  • 多餘的解釋
  • 日誌式註釋
  • 用註釋解釋變數等
  • ...

如何優化呢?

  • 方法函數、變數的命名要規範、淺顯易懂、避免用註釋解釋代碼。
  • 關鍵、複雜的業務,使用清晰、簡明的註釋

23. 神奇命名

方法函數、變數、類名、模塊等,都需要簡單明瞭,淺顯易懂。避免靠自己主觀意識瞎起名字。

反例:

boolean test = chenkParamResult(req);

正例:

boolean isParamPass = chenkParamResult(req);

24. 神奇魔法數

日常開發中,經常會遇到這種代碼:

if(userType==1){
   //doSth1
}else If( userType ==2){
   //doSth2
}
...

代碼中的這個1和2都表示什麼意思呢?再比如setStatus(1)中的1又表示什麼意思呢?看到類似壞代碼,可以這兩種方式優化:

  • 新建個常量類,把一些常量放進去,統一管理,並且寫好註釋;
  • 建一個枚舉類,把相關的魔法數字放到一起管理。

25. 混亂的代碼層次調用

我們代碼一般會分dao層service層controller層

  • dao層主要做數據持久層的工作,與資料庫打交道。
  • service層主要負責業務邏輯處理。
  • controller層負責具體的業務模塊流程的控制。

所以一般就是controller調用serviceservice調dao。如果你在代碼看到controller直接調用dao,那可以考慮是否優化啦。反例如下:

@RestController("user")
public class UserController {

    Autowired
    private UserDao userDao;

    @RequestMapping("/queryUserInfo")
    public String queryUserInfo(String userName) {
        return userDao.selectByUserName(userName);
    }
}

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 最近這段時間,一些互聯網大廠把裁員稱為“畢業”的話題被熱議了很久。 上午,DD在網上瞎逛看到了一篇文章,似乎找了這一做法的祖先。原來這麼有“創意”的操作,很好多年前的漂亮過就已經有了,所以其實這也是個舶來品? 在這篇發表於2016年4月的文章中,據作者稱:他在軟體公司HubSpot工作了近兩年,當有 ...
  • 前言 在github中經常可以看到下麵的日曆圖,可以用來表示每一天在github上的活躍程度。 類似的方法也可以用到空氣質量的可視化方式中來,只要有每天的空氣質量指數就可以。 數據 我這裡使用的是2020年北京市各個監測站點的空氣質量觀測數據,原始數據包含PM2.5,PM10,AQI指數,這裡選擇A ...
  • 前排提醒: 由於 Microsoft Docs 全是機翻。所以本文表格是我人腦補翻+審校。 如果有紕漏、模糊及時評論反饋。 序列式容器 序列容器是指在邏輯上以線性排列方式存儲給定類型元素的容器。 這些容器和數組非常類似,都是在邏輯上連續的(但記憶體不一定是連續的),與數組不同的是,容器可以非常方便的動 ...
  • 前言 今天玩啥?10行代碼夠玩嗎? Python憑藉其簡潔的代碼,贏得了許多開發者的喜愛。因此也就促使了更多開發者用Python開發新的模塊,從而形成良性迴圈, Python可以憑藉更加簡短的代碼實現許多有趣的操作。下麵我們來看看,我們用不超過10行代碼能實現些什麼有趣的功能。 一、生成二維碼 二維 ...
  • 高併發、多線程一直是Java編程中的難點,也是面試題中的要點。Java開發者也一直在嘗試使用多線程來解決應用伺服器的併發問題。但是多線程並不容易,為此一個新的技術出現了,這就是虛擬線程。 傳統多線程的痛點 但是編寫多線程代碼是非常不容易的,難以控制的執行順序,共用變數的線程安全性,異常的可觀察性等等 ...
  • 定時執行任務-springboot 先看兩個介面 這兩個介面springboot已經幫我們封裝好了,我們不需要去手動使用 TaskScheduler //任務調度者 TaskExecutor //任務執行者 具體步驟: 在啟動類上添加這個註解: @EnableScheduling//開啟定時功能的註 ...
  • 繪圖 很多程式如各種小游戲都需要在視窗中繪製各種圖形,除此之外,即使在開發JavaEE項目時,有時候也必須"動態"地向客戶 端生成各種圖形、圖表,比如 圖形驗證碼、統計圖等,這都需要利用AWT的繪圖功能。 組件繪圖原理 之前我們已經學習過很多組件,例如Button、Frame、Checkbox等等, ...
  • XName:該Cookie的名稱。一旦創建,該名稱便不可更改。 Value:該Cookie的值。如果值為Unicode字元,需要為字元編碼。如果值為二進位數據,則需要使用BASE64編碼。 Domain:可以訪問該Cookie的功能變數名稱。例如,如果設置為.zhihu.com,則所有以zhihu.com, ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...