成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

寫了10年JAVA代碼,為何還是給人一種亂糟糟的感覺?

接觸過不少號稱寫了10多年代碼的程序員,可經(jīng)常還是會發(fā)現(xiàn)他們的代碼給人一種亂糟糟的感覺,那么如何才能寫出讓同事感覺不那么亂的代碼呢?

陸川ssl適用于網(wǎng)站、小程序/APP、API接口等需要進行數(shù)據(jù)傳輸應用場景,ssl證書未來市場廣闊!成為創(chuàng)新互聯(lián)的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:18980820575(備注:SSL證書合作)期待與您的合作!

一、為什么要寫這篇文章

在開篇之前先說明下為什么要寫這篇文章?在Java的世界里MVC軟件架構模式絕對是經(jīng)典的存在(PS:MVC是一種軟件架構方式并不只有Java有),如果你是在最近十年前后進入Java的編程世界,那么你會發(fā)現(xiàn)自己這些年似乎從來沒有逃離MVC架構模式的牢籠,只不過換著使用了不同的MVC框架,如早期的Struts1、Struts2以及現(xiàn)在幾乎一統(tǒng)江湖的Spring MVC(少數(shù)自行封裝MVC框架的公司除外)。

而隨著互聯(lián)網(wǎng)技術的發(fā)展,特別是Ajax等富客戶端技術的發(fā)展,前端技術逐步形成了一套體系,并且逐步從后端代碼(如JSP)中剝離出來,從而形成了現(xiàn)在普遍流行的前后端分離模式(這也是一段時間內(nèi)為什么前端工程師會出現(xiàn)大量需求的原因),而這也對傳統(tǒng)的MVC模式產(chǎn)生了一點小的改變,因為現(xiàn)在基于Java的后端服務中很少會有大量處理復雜界面邏輯的代碼出現(xiàn),因此MVC中的V(View)這一層就逐步被各類前端技術所替代,如AngularJS、React等。

所以現(xiàn)在的Java服務端絕大部分情況下只是在處理M(Model)+C(Controller)的邏輯,而從概念上來看,好像Model代表的就是數(shù)據(jù)模型、而C則是一種控制層邏輯,所以很多人(甚至包括一些寫了很多年Java代碼的人)有時候都會被這個概念所迷惑而在Model和Controller層之間搖擺不定,在這里我們需要明確MVC模式中的M不僅僅代表的是數(shù)據(jù)模型,而是包括了數(shù)據(jù)模型之內(nèi)的所有業(yè)務邏輯相關的代碼,而C則是比較輕的,它被賦予只有處理輸入/輸出參數(shù)以及對該請求進行邏輯流程控制的職能,如果你的代碼中對Controller層有過重的邏輯代碼侵入,要知道這是不符合MVC架構規(guī)范的!

在MVC架構定義中,由于M代表了所有業(yè)務邏輯相關的代碼,所以M是要重點設計和規(guī)范的,其代碼的結構和規(guī)范直接決定了軟件的可維護性及質(zhì)量,從本質(zhì)上來說就是如何進行"代碼結構+軟件設計原則+設計模式"的組合運用。當然上面只是一句話,而其內(nèi)涵則是一件非??简灳幊趟降氖虑椤jP于軟件設計原則+設計模式的內(nèi)容非常豐富也需要時間+經(jīng)驗的積累!而代碼結構則是可以通過一定規(guī)范進行約定,結合Spring MVC框架至少我們可以寫出層次結構盡可能一致的代碼!

二、應用分層怎么搞?

事實上關于Java如何規(guī)范開發(fā)的問題,不同公司的規(guī)范略有不同,不過作為國內(nèi)Java語言應用最為廣泛的公司——阿里巴巴發(fā)布的《阿里巴巴Java開發(fā)手冊》中對應用的分層結構已經(jīng)做了比較合理的劃分!這里作者并不想標新立異,只是在此基礎上做更為詳細的解釋和說明從而讓使用Spring MVC框架的同學能夠更好地明確其分層的對應關系!

分層結構

以下分層結構基于Spring MVC框架,總體上與阿里巴巴開發(fā)手冊應用分層方式一致,分層結構示意圖如下:

寫了10年JAVA代碼,為何還是給人一種亂糟糟的感覺?

在基于Spring MVC框架的開發(fā)中,Controller層作為服務的入口主要承擔接收和轉換由終端層或者其他服務發(fā)送的網(wǎng)絡請求,并將其轉化為Java數(shù)據(jù)對象,然后對數(shù)據(jù)對象進行參數(shù)合法性校驗(如字段長度、類型、數(shù)值的合法性等等)。之后通過在Controller依賴注入對應Service層服務接口,并進行業(yè)務邏輯層方法調(diào)用,如果業(yè)務邏輯并不復雜(是否復雜判斷標準可通過方法代碼行數(shù)、條件邏輯復雜度以及站在旁者角度看看是否便于維護等指標進行判斷)那么可以直接操作數(shù)據(jù)庫持久層完成業(yè)務邏輯;而如果Service層方法寫著寫著發(fā)現(xiàn)非常的多,邏輯條件也比較多,并且每個條件所需要處理的代碼量超過一定的規(guī)模,那么此時你就要考慮是否需要要對該方法進行優(yōu)化了!

而關于優(yōu)化的方式依據(jù)邏輯的復雜程度可以做不同等級的拆分,例如簡單點可以拆分一個私有方法處理該方法中的某一部分邏輯,從而減少主業(yè)務方法的代碼量。而如果該業(yè)務層方法后面對應的是一個龐大的邏輯,例如在交易支付系統(tǒng)中,Controller層定義了一個支付的入口服務,而進入Service層方法后根據(jù)不同的業(yè)務接入方、不同的支付方式及支付渠道,都需要進行大量不同邏輯的處理,那么此時就需要考慮對這些不同場景的業(yè)務邏輯進行類級別的拆分,如通過工廠模式拆分不同的支付渠道處理類邏輯,而對于公共的處理邏輯則可以通過抽象類定義抽象方法進行抽象。例如私有方法拆分代碼示例:

@Override
public SearchCouponNameBO searchCouponNameList(SearchCouponNameDTO searchCouponNameDTO) {
    SearchCouponNameBO searchCouponNameBO = SearchCouponNameBO.builder().total(0).build();
    SearchResult searchResult;
    try {
        BoolQueryCondition boolQueryCondition = searchCouponNameListConditionBuild(searchCouponNameDTO);
        SearchBuilderConstructor searchBuilderConstructor = new SearchBuilderConstructor(boolQueryCondition);
        searchBuilderConstructor.addFieldSort("id", SortOrderEnum.DESC);
        searchBuilderConstructor.setFrom(searchCouponNameDTO.getOffset());
        searchBuilderConstructor.setSize(searchCouponNameDTO.getLimit());
        searchResult = salesCouponEsMapper.selectCouponNameByCondition(searchBuilderConstructor);
    } catch (Exception e) {
        throw new SalesCouponNameException(SalesCouponNameErrorCode.COUPON_NAME_ES_QUERY_ERROR.getCode(),
                SalesCouponNameErrorCode.COUPON_NAME_ES_QUERY_ERROR.getMessage(),
                searchCouponNameDTO);
    }
    if (searchResult != null && searchResult.getHits().getHits().length > 0) {
        List<Integer> idList = getIdListFromEsSearchResult(searchResult);
        List<SalesCouponNamePO> salesCouponNamePOList = salesCouponNameMapper.selectByIdList(idList);
        List<SalesCouponNameBO> couponNameBOList = SalesCouponNameConvert.INSTANCE
                .convertCouponNameBOList(salesCouponNamePOList);
        searchCouponNameBO.setList(couponNameBOList);
        searchCouponNameBO.setTotal((int) searchResult.getTotalHits());
    }
    return searchCouponNameBO;
}

在該Service入口方法中,需要根據(jù)從ES查詢的分頁ID去真實的MySQL中進行數(shù)據(jù)獲?。‥S數(shù)據(jù)存儲不全,只是為了進行優(yōu)化性能將分頁邏輯放入ES),而在處理ES數(shù)據(jù)時,需要從ES數(shù)據(jù)結果集中抽象ID列表,對于這部分邏輯出于代碼量的考慮,這里我們抽象一個Service層私有方法,如:

private List<Integer> getIdListFromEsSearchResult(SearchResult searchResult) {
    SearchHit[] searchHits = searchResult.getHits().getHits();
    List<Integer> idList = Arrays.asList(searchHits).stream().map(SearchHit::getSourceAsMap)
            .map(o -> Integer.parseInt(String.valueOf(o.get("id"))))
            .collect(Collectors.toList());
    return idList;
}

以上代碼示例,本質(zhì)上是一種最簡單的方法抽象(別的語言叫函數(shù)),如果在代碼量略大,但是邏輯本身復雜度還不是特別高的情況下,這種方式是最常用的!也是在你不知道怎么拆分,讓代碼不那么難以維護的一種非常有效的手段。

而工廠+責任鏈等也是業(yè)務層拆分常用的手段,此時需要基于Service層業(yè)務入口方法進行代碼結構的二次拆分,在分層結構上這部分介于Service層和Dao層之間的代碼稱之為通用業(yè)務處理層(Manager)。關于這部分由于可以發(fā)揮空間非常大,很難有一套標準的答案,但作為一名優(yōu)秀的程序設計者要時刻有抽象的思維,不管拆分得是否足夠合理,至少要讓你的代碼不至于過于臃腫!這里我們將Service層拆分層次定義為以下三個等級:

  • 等級1:私有方法拆分;
  • 等級2:工廠+責任鏈運用(有效的類的拆分);
  • 等級3:高級設計模式(優(yōu)雅的類的拆分);
分層領域模型約定

聊完分層結構接下來我們說一下分層領域數(shù)據(jù)模型的約定,注意這里的分層領域并不是指“DDD(領域驅動設計)模式”,而是對以上分層結構中各層之間交互數(shù)據(jù)對象的定義約定。在上述分層結構圖中已經(jīng)標識了DTO、BO、PO的使用范圍(本規(guī)范只約定三種領域對象,事實上已經(jīng)足夠,并不需要搞的太復雜)。具體如下:

寫了10年JAVA代碼,為何還是給人一種亂糟糟的感覺?

在Controller層接收網(wǎng)絡請求數(shù)據(jù)后,由于Controller層并不需要處理額外的邏輯,所以大部分情況下直接將DTO對象傳送給Service層;而Service層如果邏輯不復雜只是需要根據(jù)DTO的數(shù)據(jù)進行數(shù)據(jù)庫操作,那么此時根據(jù)需要將DTO轉換為PO進行操作,完成后由于大部分場景下Service的輸出參數(shù)與輸入DTO對象都存在差異,因此為了區(qū)分我們將Service層的輸出數(shù)據(jù)對象統(tǒng)一定義為BO。

而Service層拆分時對于Manager層方法的輸入/輸出對象則統(tǒng)一為BO,包括Manager層操作第三方數(shù)據(jù)接口的數(shù)據(jù)對象轉換也統(tǒng)一為BO。以上劃分并沒有什么特別的強制約定,而過分人為的去揣摩其含義本質(zhì)上也沒什么意義,只是大家共同遵守一個約定,這樣代碼風格看起來會更加統(tǒng)一一點。

三、如何保持代碼的簡潔性

作為一名對代碼有追求的程序員,能少些一行代碼就絕對不要啰嗦,而Java豐富的開源生態(tài)體系也給了我們這種懶惰很多便利,所以在編程的過程中其實是有很多工具可以幫助節(jié)省代碼的。這里給大家分別介紹三種方式:

MapStruct

在前面介紹的分層結構中,無論是DTO到BO,還是BO到PO亦或BO到BO,都會有很多的數(shù)據(jù)對象轉換的邏輯,傳統(tǒng)的方法是需要通過一堆Setter方法來完成的,而高級一點的lombok包提供的@Builder注解也是需要你寫一堆".build()"來完成數(shù)據(jù)的轉換,這樣的代碼寫到Service層中顯然很浪費很多代碼行,而MapStruct是一種更優(yōu)雅的完成這件事的工具,使用方法如下:

項目pom.xml中引入依賴:

<!--MapStruct Java實體映射工具依賴-->
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-jdk8</artifactId>
    <version>1.3.1.Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.3.1.Final</version>
</dependency>

也需要在pom.xml引入一下Maven插件:

<!--提供給MapStruct使用 -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
</plugin>

之后編寫數(shù)據(jù)對象映射轉換接口:

package com.mafengwo.sales.sp.coupon.convert;

import com.mafengwo.sales.sp.coupon.client.bo.SalesCouponChannelBO;
import com.mafengwo.sales.sp.coupon.client.dto.SalesCouponChannelsDTO;
import com.mafengwo.sales.sp.coupon.dao.model.SalesCouponChannelsPO;
import java.util.List;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/**
 * @author qiaojiang
 */
@Mapper
public interface SalesCouponChannelsConvert {

    SalesCouponChannelsConvert INSTANCE = Mappers.getMapper(SalesCouponChannelsConvert.class);

    @Mappings({
            @Mapping(target = "flag", expression = "java(java.lang.Integer.valueOf(\"0\"))"),
            @Mapping(target = "ctime", expression = "java(com.mafengwo.sales.sp.coupon.util.DateUtils.getCurrentTimestamp())"),
            @Mapping(target = "mtime", expression = "java(com.mafengwo.sales.sp.coupon.util.DateUtils.getCurrentTimestamp())")
    })
    SalesCouponChannelsPO convertSalesCouponChannelsPO(SalesCouponChannelsDTO salesCouponChannelsDTO);

    @Mappings({})
    List<SalesCouponChannelBO> convertCouponChannelBOList(List<SalesCouponChannelsPO> salesCouponChannelsPO);
}

以上方法的入?yún)樵磾?shù)據(jù)對象,而返回對象則為目標數(shù)據(jù)對象,如果兩個對象的字段名稱完成一致,那么其實是不需要進行任何單獨映射的,直接 @Mappings({})即可;而如果映射對象之間字段名稱有差異則可以通過@Mappings({@Mapping(target = "ctime", source = "createTime")})進行指定映射。而在業(yè)務層方法具體操作時使用方法如下:

//實體數(shù)據(jù)轉換
SalesCouponChannelsPO salesCouponChannelsPO = SalesCouponChannelsConvert.INSTANCE
        .convertSalesCouponChannelsPO(salesCouponChannelsDTO);

這樣對象數(shù)據(jù)之間的拷貝將變得非常容易,從某種層面上看無論代碼層次結構多么繞,至少數(shù)據(jù)對象之間的拷貝將不再是一件麻煩的事!

lambada表達式

在Java8種提供了lambada表達式,在Java8中如果操作List相關數(shù)據(jù)結構,如果能夠使用lambada表達式也可以省一些代碼,例如:

private List<Integer> getIdListFromEsSearchResult(SearchResult searchResult) {
    SearchHit[] searchHits = searchResult.getHits().getHits();
    List<Integer> idList = Arrays.asList(searchHits).stream().map(SearchHit::getSourceAsMap)
            .map(o -> Integer.parseInt(String.valueOf(o.get("id"))))
            .collect(Collectors.toList());
    return idList;
}

有關lambada表達式更多的用法,大家有時間可以多看看相關語法知識,這里就不再贅述!

tk.mybatis

在使用Mybatis框架作為數(shù)據(jù)庫開發(fā)框架時,相比較于Hibernate或其他JPA框架,Mybatis具有較強的對原生SQL的支持能力,因而會顯得比較靈活。但在大部分互聯(lián)網(wǎng)系統(tǒng)中,對數(shù)據(jù)庫的操作很多時候都是單表的操作,在這種情況下使用Mybatis也需要在Mapper代碼和映射.xml文件中編寫大量的SQL,而這些單表SQL本質(zhì)上大同小異,完全可以通用化。

因此在Mybatis領域為了減少開發(fā)量很多項目會使用mybatis-generator插件生成一份完整的映射代碼,但是這樣的方式也會增加大量的無用代碼,看起來并不是那么的簡潔。而tk.mybatis則是考慮到了這個問題,可以兼顧對單表操作的便捷性(不需要再寫額外的代碼)、多表聯(lián)合查詢的靈活性以及代碼的簡潔性。具體用法如下:

項目pom.xml文件引入相關依賴:

<!--Mybatis通用Mapper集成-->
<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
    <version>2.1.3</version>
    <exclusions>
        <exclusion>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper</artifactId>
    <version>4.1.3</version>
</dependency>

主類@MapperScan注解換成tk.mybatis的:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.elasticsearch.ElasticSearchRestHealthIndicatorAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
//不要使用Mybatis原生注解,用tk.mybatis的
import tk.mybatis.spring.annotation.MapperScan;

import java.util.Date;

@SpringBootApplication(exclude = {ElasticSearchRestHealthIndicatorAutoConfiguration.class})
@ServletComponentScan
@EnableDiscoveryClient
@EnableWebMvc
@MonitorEnableAutoConfiguration
@MapperScan("com.mafengwo.sales.sp.coupon.dao.mapper")
@EnableTransactionManagement
public class SpCouponApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpCouponApplication.class, args);
    }
}

編寫映射接口,單表操作將不再需要額外定義操作方法及映射SQL代碼,而是可以直接用tk.mybatis提供的通用方法,代碼如下:

import com.mafengwo.sales.sp.coupon.dao.model.CouponNameScopeRelationPO;
import org.springframework.stereotype.Repository;
import tk.mybatis.mapper.common.Mapper;

@Repository
public interface CouponNameScopeRelationMapper extends Mapper<CouponNameScopeRelationPO> {

}

而在Mybatis SQL映射文件*.xml中單表也只需要定義簡單的字段映射即可,而不在需要定義通篇的SQL代碼了,如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.mafengwo.sales.sp.coupon.dao.mapper.SalesCouponChannelsMapper">
    <resultMap id="BaseResultMap" type="com.mafengwo.sales.sp.coupon.dao.model.SalesCouponChannelsPO">
        <id column="ID" property="id" jdbcType="INTEGER"/>
        <result column="NAME" property="name" jdbcType="VARCHAR"/>
        <result column="DESC" property="desc" jdbcType="VARCHAR"/>
        <result column="ADMIN_UID" property="adminUid" jdbcType="INTEGER"/>
        <result column="FLAG" property="flag" jdbcType="INTEGER"/>
        <result column="CTIME" property="ctime" jdbcType="TIMESTAMP"/>
        <result column="MTIME" property="mtime" jdbcType="TIMESTAMP"/>
        <result column="SCENEID" property="sceneId" jdbcType="INTEGER"/>
    </resultMap>
</mapper>

除以上工具外,在實際的開發(fā)過程中還有很多開源或通過自定義組件的方式能夠讓代碼寫的更簡潔,大家可以保持探索!

四、Java程序設計原則與設計模式

構建復雜的軟件系統(tǒng)只有遵循一定的設計原則并合適地運用相應地設計模式,這樣的代碼才不至于在復雜的邏輯中迷失方向。關于設計原則及設計模式的話題是一個需要時間打磨和反復歷練的修行,因此這里只是為大家簡單陳列,在Java程序設計時應該遵循的一些原則以及可用的設計原則,做到心中有劍!

設計原則

單一職責(一個蘿卜一個坑)、里氏替換(繼承復用)、依賴倒置(面向接口編程)、接口隔離(高內(nèi)聚、低耦合)、迪米特法則(降低類與類之間的耦合)、開閉原則(對擴展開發(fā)、對修改關閉)。

設計模式

在Java領域,大概有23種設計模式,它們分別是:

  • 創(chuàng)建型模式:單例模式、抽象工廠模式、建造者模式、工廠模式、原型模式
  • 結構型模式:適配器模式、橋接模式、裝飾模式、組合模式、外觀模式、享元模式、代理模式
  • 行為型模式:模板方法模式、命令模式、迭代器模式、觀察者模式、中介者模式、備忘錄模式、解釋器模式、狀態(tài)模式、策略模式

以上這些模式或多或少在我們?nèi)粘5木幊讨卸紩姷交蛘呗犨^,但在平時能夠用到的卻并不多,很多原因在于目前Java領域的開發(fā)框架如Spring已經(jīng)給我們做了很多的限定,而在大部分互聯(lián)網(wǎng)系統(tǒng)中,編程模式又很固定。在多數(shù)情況下,工廠模式的運用就能搞定大多數(shù)業(yè)務編程場景,因此很多模式只有在很多中間件系統(tǒng)等基礎軟件中被使用得比較多。通過羅列上述設計模式,并不是要大家為了設計而生硬的使用設計模式,而是要努力向著“心中有丘壑,眉目作山河”目標境界前進!只有這樣才能不至于日復一日的碼磚生涯中,迷失自我,失去方向!

后記

隨著時光的流逝,越來越多的程序員步入中年,寫了10多年代碼的人也越來越多,而行業(yè)的發(fā)展卻在走下坡路,種種因素讓越來越多的人感到焦慮!個人覺得作為一名程序員,我們的核心能力還在于代碼,因此在日復一日的碼磚生涯中不斷修煉自己的代碼能力才是關鍵!否則可能就會出現(xiàn)被年輕人鄙視了!

網(wǎng)頁題目:寫了10年JAVA代碼,為何還是給人一種亂糟糟的感覺?
文章分享:http://jinyejixie.com/article40/ipjcho.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設計、Google企業(yè)網(wǎng)站制作、企業(yè)建站、App設計、域名注冊

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)

營銷型網(wǎng)站建設
武强县| 邳州市| 武邑县| 固镇县| 邢台县| 太保市| 库伦旗| 铜鼓县| 图片| 延津县| 吉林省| 鹰潭市| 海南省| 长白| 白城市| 青田县| 白城市| 陇西县| 姚安县| 宿迁市| 郎溪县| 库车县| 龙陵县| 昌黎县| 南涧| 长汀县| 宁海县| 阿坝县| 鱼台县| 静安区| 永登县| 大同市| 民权县| 凯里市| 闵行区| 临朐县| 得荣县| 昭苏县| 芜湖市| 原平市| 新余市|