編程范式全貌概要-免費

引言:經(jīng)常在一些社區(qū)上看到莫名的一句話PHP是世界上最好的語言呼畸,在語言的爭論上里初,確實是大有華山論劍的門派之爭锅睛。本文將通過編程語言的范式角度疚膊,來了解整個編程語言的發(fā)展史义辕,同時更清晰的認知編程范式,以達到知己知彼寓盗,百戰(zhàn)不殆

??編程語言發(fā)展到今天终息,出現(xiàn)了好多不同的代碼編寫方式,但不同的方式解決的都是同一個問題贞让,那就是如何寫出更為通用周崭、更具可重用性的代碼或模塊。

一喳张、從C語言談起

??C語言的歷史悠久续镇,自其問世以來,其影響了太多太多的編程語言销部,到現(xiàn)在還一直被廣泛使用摸航,不得不佩服它的生命力。但是舅桩,我們也要清楚的知道酱虎,大多數(shù)的使用C語言來做內(nèi)核的編程語言其實都是在改善C語言帶來的問題+時代發(fā)展帶來的變化。
下面來簡單回顧下C語言的特性:

  1. C 語言是一個靜態(tài)弱類型語言擂涛,在使用變量時需要聲明變量類型读串,但是類型間可以有隱式轉(zhuǎn)換;
  2. 不同的變量類型可以用結(jié)構(gòu)體(struct)組合在一起撒妈,以此來聲明新的數(shù)據(jù)類型恢暖;
  3. C 語言可以用 typedef 關(guān)鍵字來定義類型的別名,以此來達到變量類型的抽象狰右;
  4. C 語言是一個有結(jié)構(gòu)化程序設(shè)計杰捂、具有變量作用域以及遞歸功能的過程式語言;
  5. 通過指針棋蚌,C 語言可以容易地對內(nèi)存進行低級控制嫁佳,然而這加大了編程復(fù)雜度;
  6. 編譯預(yù)處理讓 C 語言的編譯更具有彈性谷暮,比如跨平臺蒿往。

然而,在代碼組織和功能編程上坷备,C語言的上述熄浓,卻不那么美妙了,eq:

//一個簡單的交換兩個變量的函數(shù)
void changeVar(int* x, int* y)
{
    int tmp = *x;
    *x = *y;
    *y = tmp;
}

可以想一想省撑,這里為什么要用指針呢赌蔑?因為如果不用指針的話,只是傳進來的行參竟秫,即函數(shù)的形參是調(diào)用實參的一個拷貝娃惯,函數(shù)閉包里對形參的修改無法影響實參的結(jié)果。

然而肥败,這個函數(shù)最大的問題是int tmp = *x決定了這個函數(shù)只能給int值使用趾浅,但是還有好多類型也等著能被調(diào)用呢,eq:double馒稍、float皿哨、string等,這就是個靜態(tài)語言最糟糕的問題纽谒。

當(dāng)然证膨,這個時候大家都會想到類型轉(zhuǎn)換,然而對于C語言的類型轉(zhuǎn)換鼓黔,是會出現(xiàn)很多問題的央勒。

  • 比如:一個 double a[10] 的數(shù)組,a[2] 意味著 a + sizeof(double) * 2澳化。如果你把 a 強轉(zhuǎn)成 int崔步,那么 a[2] 就意味著 a + sizeof(int) * 2。我們知道 sizeof(double) 是 8缎谷,而 sizeof(int) 是 4井濒。于是訪問到了不同的地址和內(nèi)存空間,這就導(dǎo)致程序出現(xiàn)嚴重的問題列林。下面這種使用臨時交換數(shù)據(jù)的buffer拷貝方案可以去掉類型轉(zhuǎn)換時導(dǎo)致的地址變換:
//加入泛型變量的交換兩個變量的函數(shù)
void changeVar(void* x, void* y, size_t size)
{
    char tmp[size];  //交換數(shù)據(jù)時需要用的 buffer
    memcpy(tmp, y, size);                                          
    memcpy(y, x, size);
    memcpy(x, tmp, size);
    /**
    * 1.函數(shù)接口中增加了一個size參數(shù)眼虱,用了 void* 后,類型被“抽象”掉了
席纽,編譯器不能通過類型得到類型的長度了捏悬,所以,需要我們手動地加上一個類型長度的標(biāo)識润梯。
2.函數(shù)的實現(xiàn)中使用了memcpy()函數(shù)过牙,因為類型被“抽象”掉了,所以不能用賦值表達式了纺铭,很有可能傳進來的參數(shù)類型還是一個結(jié)構(gòu)體寇钉,因此,為了要交換這些復(fù)雜類型的值舶赔,我們只能使用內(nèi)存復(fù)制的方法了扫倡。
    */
}

除了上面使用void* 來做泛型,在C語言中,還可以用宏定義來做泛型撵溃,不過卻會帶來宏的字符串替換疚鲤,導(dǎo)致代碼膨脹,導(dǎo)致編譯出的執(zhí)行文件相對較大缘挑,此處感興趣的可以去深入學(xué)習(xí)一下集歇。

二、泛型編程
  • 泛型:即是指具有在多種數(shù)據(jù)類型上皆可操作的含義语淘,與模板有些相似诲宇。
    1.舉例:
    • C語言版本
long sum(int *a, size_t size) {
  long result = 0;  for(int i=0; i<size; i++) {    result += a[i];  }  return result;
}
  • C++版本

template<typename T, typename Iter>
T sum(Iter pStart, Iter pEnd) {
  T result = 0;
  for(Iter p=pStart; p!=pEnd; p++) {
    result += *p;
  }
  return result;  
}
// 那個0假設(shè)了類型是int;
// 那個T假設(shè)了 Iter 中出來的類型是T惶翻。如果類型不一樣姑蓝,那么就會導(dǎo)致轉(zhuǎn)類型的問題。
  • php版本
public function sum($a, $b) {return intval($a)+intval($b);}
  • js版本
function sum($x, $y) {return parseInt($x)+parseInt($y);}
  • 泛型編程的重要設(shè)計模式-迭代器
  1. 首先吕粗,一個迭代器需要和一個容器在一起纺荧,因為里面是對這個容器的具體的代碼實現(xiàn)。
  2. 它需要重載一些操作符溯泣,比如:取值操作*虐秋、成員操作->、比較操作==和!=垃沦,還有遍歷操作++客给,等等。
    3.然后肢簿,還要typedef一些類型靶剑,比如value_type,告訴我們?nèi)萜鲀?nèi)的數(shù)據(jù)的實際類型是什么樣子池充。
  3. 還有一些桩引,如begin()和end()的基本操作。日志的記錄等收夸。

三坑匠、類型系統(tǒng)和泛型的本質(zhì)

0. 函數(shù)式編程

對于函數(shù)式編程來說,它只關(guān)心定義輸入數(shù)據(jù)和輸出數(shù)據(jù)相關(guān)的關(guān)系卧惜,數(shù)學(xué)表達式里面其實是在做一種映射(mapping)厘灼,輸入的數(shù)據(jù)和輸出的數(shù)據(jù)關(guān)系是什么樣的,是用函數(shù)來定義的咽瓷。

特征:

  • stateless:函數(shù)不維護任何狀態(tài)设凹。函數(shù)式編程的核心精神是 stateless,簡而言之就是它不能存在狀態(tài)茅姜,打個比方闪朱,你給我數(shù)據(jù)我處理完扔出來。里面的數(shù)據(jù)是不變的。
  • immutable:輸入數(shù)據(jù)是不能動的奋姿,動了輸入數(shù)據(jù)就有危險锄开,所以要返回新的數(shù)據(jù)集。
    優(yōu)勢:
  • 沒有狀態(tài)就沒有傷害胀蛮。
  • 并行執(zhí)行無傷害院刁。
  • Copy-Paste 重構(gòu)代碼無傷害糯钙。
  • 函數(shù)的執(zhí)行沒有順序上的問題粪狼。
  • 確定性。所謂確定性任岸,就是像在數(shù)學(xué)中那樣再榄,f(x) = y 這個函數(shù)無論在什么場景下,都會得到同樣的結(jié)果享潜,而不是像程序中的很多函數(shù)那樣困鸥。同一個參數(shù),在不同的場景下會計算出不同的結(jié)果剑按,這個我們稱之為函數(shù)的確定性疾就。所謂不同的場景,就是我們的函數(shù)會根據(jù)運行中的狀態(tài)信息的不同而發(fā)生變化艺蝴。
1.基于原型的編程范式

講到原型猬腰,相信大家的第一映像一定是JavaScript吧,來個小問題猜敢,大家有誰知道js的國際全稱叫什么嗎姑荷?

  • 每個對象都有一個 proto 的屬性
  • 來看下原型鏈:

var a = {
  x: 10,
  calculate: function (z) {
    return this.x + this.y + z;
  }
};
 
var b = {
  y: 20,
  __proto__: a
};
 
var c = {
  y: 30,
  __proto__: a
};
 
// call the inherited method
b.calculate(30); // 60
c.calculate(40); // 80
proto_image
  • 再來看看

// 一種構(gòu)造函數(shù)寫法
function Foo(y) {
  this.y = y;
}
 
// 修改 Foo 的 prototype,加入一個成員變量 x
Foo.prototype.x = 10;
 
// 修改 Foo 的 prototype缩擂,加入一個成員函數(shù) calculate
Foo.prototype.calculate = function (z) {
  return this.x + this.y + z;
};
 
// 現(xiàn)在鼠冕,我們用 Foo 這個原型來創(chuàng)建 b 和 c
var b = new Foo(20);
var c = new Foo(30);
 
// 調(diào)用原型中的方法,可以得到正確的值
b.calculate(30); // 60
c.calculate(40); // 80
prototype2

再來重溫一下上面的原型講解實例


function Person(){}
var p = new Person();

Person.prototype.name = "Hao Chen";
Person.prototype.sayHello = function(){
    console.log("Hi, I am " + this.name);
}

console.log(p.name); // "Hao Chen"
p.sayHello(); // "Hi, I am Hao Chen"

在上面這個例子中:

  • 我們先生成了一個空的函數(shù)對象 Person()胯盯;
  • 然后將這個空的函數(shù)對象 new 出另一個對象懈费,存在 p 中;
  • 這時再改變 Person.prototype博脑,讓其有一個 name 的屬性和一個 sayHello() 的方法憎乙;
  • 我們發(fā)現(xiàn),另外那個 p 的對象也跟著一起改變了趋厉。

注意一下:

  • 當(dāng)創(chuàng)建 function Person(){} 時寨闹,Person.proto 指向 Function.prototype;
  • 當(dāng)創(chuàng)建 var p = new Person() 時,p.proto 指向 Person.prototype;
  • 當(dāng)修改了 Person.prototype 的內(nèi)容后君账,p.proto 的內(nèi)容也就被改變了繁堡。
    小結(jié):

通過上述可以看到,這種玩法就是一種委托的方式。在使用委托的基于原型的語言中椭蹄,運行時語言可以“僅僅通過序列的指針找到匹配”這樣的方式來定位屬性或者尋找正確的數(shù)據(jù)闻牡。所有這些創(chuàng)建行為、共享的行為需要的是委托指針绳矩。

不像是基于類的面向?qū)ο笳Z言中類和接口的關(guān)系罩润,原型和它的分支之間的關(guān)系并不要求子對象有相似的內(nèi)存結(jié)構(gòu),因為如此翼馆,子對象可以繼續(xù)修改而無需像基于類的系統(tǒng)那樣整理結(jié)構(gòu)割以。還有一個要提到的地方是,不僅僅是數(shù)據(jù)应媚,方法也能被修改严沥。因為這個原因,大多數(shù)基于原型的語言把數(shù)據(jù)和方法提作“slots”中姜。

這種在對象里面直接修改的玩法消玄,雖然這個特性可以帶來運行時的靈活性,我們可以在運行時修改一個 prototype丢胚,給它增加甚至刪除屬性和方法翩瓜。但是其帶來了執(zhí)行的不確定性,也有安全性的問題携龟,而代碼還變得不可預(yù)測兔跌,這有點黑科技的味道了。因為這些不像靜態(tài)類型系統(tǒng)骨宠,沒有一個不可變的契約對代碼的確定性有保證浮定,所以,需要使用者來自己保證层亿。

2. 面向?qū)ο笈cgo語言的委托模式

OOP三大特性:封裝桦卒、繼承和多態(tài)。
OOD原則/SOLID原則:

  • OCP-Open/close principle:開/閉原則;Open for extension;Closed for modification;

比如加上一些策略模式匿又、適配器模式方灾、觀察者模式等

  • DIP-Dependency Inversion Principle:依賴倒置原則
  • 高層模塊不能依賴低層模塊,而是大家都依賴于抽象碌更;

  • 抽象不能依賴實現(xiàn)裕偿,而是實現(xiàn)依賴抽象。
    DIP 倒置了:

  • 模塊或包的依賴關(guān)系

  • 開發(fā)順序和職責(zé)

  • SRP-Single Responsibility Principle:單一職責(zé)原則痛单,又被稱為“內(nèi)聚性原則(Cohesion)”嘿棘,意為:

一個模塊的組成元素之間的功能相關(guān)性。
一個類旭绒,只能有一個引起它的變化的原因鸟妙。
一個職責(zé)是一個變化的原因焦人。
違反的后果:

  • 脆弱性:switch case/if else 非常脆弱;對系統(tǒng)的改動會導(dǎo)致系統(tǒng)中和改動的地方無關(guān)的許多地方出現(xiàn)問題重父。出現(xiàn)新問題的地方與改動的地方?jīng)]有概念上的關(guān)聯(lián)花椭。要修正這些問題又會引出更多的問題,從而使開發(fā)團隊就像一只不停追逐自己尾巴的狗一樣房午。
  • 不可移植性

設(shè)計臭味:糟糕的代碼有哪些特點矿辽?

  • 僵硬-不易改變

僵化性(rigidity):很難對系統(tǒng)改動,因為每個改動都會迫使許多對系統(tǒng)其他部分的改動郭厌。如果單一的改動會導(dǎo)致依賴關(guān)系的模塊中的連鎖改動袋倔,那么設(shè)計就是僵化的,必須要改動的
模塊越多沪曙,設(shè)計就越僵化奕污。

  • 脆弱-只想改變A萎羔,結(jié)果B被意外破壞液走。

牢固性(Immobility): 很難解開系統(tǒng)的糾結(jié),使之成為一些可在其他系統(tǒng)中重用的組件贾陷。設(shè)計中包含了對其他系統(tǒng)有用的部分缘眶,而把這些部分從系統(tǒng)中分離出來所需的努力和風(fēng)險是巨大的。

  • 導(dǎo)致誤用的陷阱-做錯誤的事比做正確的事更容易髓废,引誘程序員破壞原有的設(shè)計巷懈。

粘滯性(Viscosity): 做正確的事情比做錯誤的事情要困難。

  • 面臨一個改動的時候慌洪,開發(fā)人員常常會發(fā)現(xiàn)會有多種改動的方法顶燕。有的方法會保持系統(tǒng)原來的設(shè)計,而另外一些則會破壞設(shè)計冈爹,當(dāng)那些可以保持系統(tǒng)設(shè)計的方法比那些破壞設(shè)計的方法跟難應(yīng)用是涌攻,就表明設(shè)計具有高的粘滯性,作錯誤的事情就很容易频伤。
  • 晦澀-代碼難以理解恳谎,定義的變量名和注釋都不能夠見名知意。

晦澀性(Opacity):很難閱讀憋肖、理解因痛。沒有很好的表現(xiàn)出意圖。

  • 代碼可以用清晰岸更、富有表現(xiàn)力的方式編寫鸵膏,也可以用晦澀、費解的方式編寫怎炊。一般說來谭企,隨著時間的推移用僧,代碼會變得越來越晦澀。
    不必要的復(fù)雜性(Needless Complexity):設(shè)計中包含有不具任何直接好處的基礎(chǔ)結(jié)構(gòu)赞咙。
    如果設(shè)計中包含有當(dāng)前沒有用的組成部分责循,他就含有不必要的復(fù)雜性。當(dāng)開發(fā)人員預(yù)測需求的變化攀操,并在軟件中放置了處理那些潛在變化的代碼時院仿,常常會出現(xiàn)這種情況。
  • 過度設(shè)計速和、copy-paste代碼

不必要的重復(fù)(Needless Repetition):設(shè)計中包含有重復(fù)的結(jié)構(gòu)歹垫,而該重復(fù)的結(jié)構(gòu)本可以使用單一的抽象進行統(tǒng)一。

  • 當(dāng) copy颠放,cut排惨,paste 編程的時候,這種情況就會發(fā)生碰凶。

面向?qū)ο髢?yōu)點:

  • 能和真實的世界交相輝映暮芭,符合人的直覺。
  • 面向?qū)ο蠛蛿?shù)據(jù)庫模型設(shè)計類型欲低,更多地關(guān)注對象間的模型設(shè)計辕宏。
  • 強調(diào)于“名詞”而不是“動詞”,更多地關(guān)注對象和對象間的接口砾莱。
  • 根據(jù)業(yè)務(wù)的特征形成一個個高內(nèi)聚的對象瑞筐,有效地分離了抽象和具體實現(xiàn),增強了可重用性和可擴展性腊瑟。
  • 擁有大量非常優(yōu)秀的設(shè)計原則和設(shè)計模式聚假。

面向?qū)ο笕秉c:

  • 代碼都需要附著在一個類上,從一側(cè)面上說闰非,其鼓勵了類型膘格。
  • 代碼需要通過對象來達到抽象的效果,導(dǎo)致了相當(dāng)厚重的“代碼粘合層”河胎。
  • 因為太多的封裝以及對狀態(tài)的鼓勵闯袒,導(dǎo)致了大量不透明并在并發(fā)下出現(xiàn)很多問題。

go語言的委托模式:


type Widget struct {
    X, Y int
}

type Label struct {
    Widget        // Embedding (delegation)
    Text   string // Aggregation
    X int         // Override 
}

func (label Label) Paint() {
  // [0xc4200141e0] - Label.Paint("State")
    fmt.Printf("[%p] - Label.Paint(%q)\n", 
      &label, label.Text)
}

由上面可知:

  • 我們聲明了一個 Widget游岳,其有 X和Y政敢;
  • 然后用它來聲明一個 Label,直接把 Widget 委托進去胚迫;
  • 然后再給 Label 聲明并實現(xiàn)了一個 Paint() 方法喷户。

label := Label{Widget{10, 10}, "State", 100}

// X=100, Y=10, Text=State, Widget.X=10
fmt.Printf("X=%d, Y=%d, Text=%s Widget.X=%d\n", 
  label.X, label.Y, label.Text, 
  label.Widget.X)
fmt.Println()
// {Widget:{X:10 Y:10} Text:State X:100} 
// {{10 10} State 100}
fmt.Printf("%+v\n%v\n", label, label)

label.Paint()

我們可以看到,如果有成員變量重名访锻,則需要手動地解決沖突褪尝。

3. 來看看我們?nèi)粘R欢纬绦虼a的抽象解構(gòu)(見draw圖)闹获,logic + control
  • 分離業(yè)務(wù)邏輯+控制邏輯,業(yè)務(wù)邏輯又可分為:校驗業(yè)務(wù)類型河哑,數(shù)據(jù)處理業(yè)務(wù)類型避诽;數(shù)據(jù)處理業(yè)務(wù)類型又可分為:格式化組裝數(shù)據(jù)處理類型、struct結(jié)構(gòu)體枚舉預(yù)處理CURD

  • 用過的經(jīng)驗璃谨,以編程方式類型區(qū)分:

    • 面向?qū)ο螅何猩陈⒉呗浴蚪蛹淹獭⑿揎椆俺oC/DIP、MVC
    • 函數(shù)式編程:修飾底扳、管道铸抑、拼裝
  • Logic 部分才是真正有意義的(What)

  • Control 部分只是影響 Logic 部分的效率(How)

  • 一個是在解決數(shù)據(jù)和算法,一個是在解決邏輯和控制衷模。

4. 邏輯編程范式
四鹊汛、泛型思想日常編碼中的引用
<?php
namespace YfSdk\Enum;

use YfSdk\Enum\base\EnumBase;

class QueueResultLogStatus extends EnumBase
{
    /**
     * 入列
     */
    const INIT = 0;

    /**
     * 處理中
     */
    const HANDLING = 1;

    /**
     * 成功
     */
    const SUCCESS = 2;


    /**
     * 失敗
     */
    const FAIL = 3;

    /**
     * 忽略處理
     */
    const IGNORE = 4;

    /**
     * 傳入的隊列狀態(tài)異常
     */
    const UNUSUAL = 8;

    public $key;
    public $name;
    public $remark;

    private static $logObj;

    /**
     * 枚舉注釋
     *
     * @return array
     */
    public static function comment()
    {
        return [
            '消息入列' => self::INIT,
            '消息出列' => self::HANDLING,
            '處理失敗' => self::FAIL,
            '處理成功' => self::SUCCESS,
            '忽略處理' => self::IGNORE
        ];
    }

    /**
     * 所有值
     *
     * @return array
     */
    public static function all()
    {
        return array_values(static::comment());
    }

    /**
     * list
     *
     * @return mixed
     */
    public static function allObj()
    {
        if (static::$logObj == null) {
            static::$logObj = [
                static::INIT => static::create(static::INIT, '消息入列', ''),
                static::HANDLING => static::create(static::HANDLING, '消息出列', ''),
                static::FAIL => static::create(static::FAIL, '處理失敗', ''),
                static::SUCCESS => static::create(static::SUCCESS, '處理成功', ''),
                static::IGNORE => static::create(static::IGNORE, '忽略處理', ''),
                static::UNUSUAL => static::create(static::UNUSUAL, '傳入的隊列狀態(tài)異常', '傳入的隊列狀態(tài)異常')
            ];
        }
        return static::$logObj;
    }

    /**
     * 查詢
     *
     * @param $key
     * @return false|static
     */
    public static function find($key)
    {
        return isset(static::all()[$key]) ? static::all()[$key] : false;
    }

    /**
     * 創(chuàng)建struct
     *
     * @param $key
     * @param $name
     * @param $remark
     * @return $this
     */
    public function create($key, $name, $remark)
    {
        $obj = new static();
        $obj->key = $key;
        $obj->name = $name;
        $obj->remark = $remark;
        return $obj;
    }

}

// 調(diào)用代碼-原始
public function updateLogStatusById($tenantCode, $logId, $status, $message = '')
    {
        if (!is_scalar($message)) {
            $message = json_encode($message, JSON_UNESCAPED_UNICODE);
        }
        switch ($status) {
            case '消息出列':
                $this->_yfQueueLogService->updateLogStatusById($tenantCode, $logId, ['status' => '消息出列', 'message' => 'yf_queue_plus_iLogId:' . $this->getILogId().'message:' . $message]);
                break;
            case '忽略處理':
                $this->_yfQueueLogService->updateLogStatusById($tenantCode, $logId, ['status' => '忽略處理', 'message' => $message]);
                break;
            case '處理失敗':
                $this->_yfQueueLogService->updateLogStatusById($tenantCode, $logId, ['status' => '處理失敗', 'message' => $message]);
                break;
            case '處理成功':
                $this->_yfQueueLogService->updateLogStatusById($tenantCode, $logId, ['status' => '處理成功', 'message' => $message]);
                break;
        }
    }

// 調(diào)用代碼-改造后
public function updateQueueStatue($tenantCode, $logId, $status, $message = '')
    {
        if (!is_scalar($message)) {
            $message = json_encode($message, JSON_UNESCAPED_UNICODE);
        }
        if (!in_array($status, QueueResultLogStatus::comment())) {
            $statusName = QueueResultLogStatus::find($status)->name;
        } else {
            $statusName = QueueResultLogStatus::find(QueueResultLogStatus::UNUSUAL)->name;
        }
        if ($status == QueueResultLogStatus::HANDLING) {
            $message = 'yf_queue_plus_iLogId:' . $this->getILogId().'message:' . $message;
        }
        $updateRow = ['status' => $statusName, 'message' => $message];
        $this->_yfQueueLogService->updateLogStatusById($tenantCode, $logId, $updateRow);
    }

寫在最后,留個小作業(yè)算芯,下面這段代碼請?zhí)岢瞿愕膬?yōu)化思路:

private function checkClassProblemSheet($sheet, $highestColumn, $highestRow)
    {
        try {
            // 不合法的行
            $failRow = [];

            //返回合法數(shù)據(jù)
            $legalImportRows = [];

            // 檢查項為空的行
            $nullClassRow = [];
            // 檢查項名稱包含特殊字符的行
            $illegalCharacterRow = [];
            // 檢查項不存在的行
            $notExistClassRow = [];
            // 問題描述為空的行
            $nullClassProblemRow = [];
            // 問題描述超過特定字符
            $exceedMaxLengthClassProblemRow = [];
            // 問題描述重復(fù)的行
            $classProblemRepeatRow = [];
            // 非末級檢查項的行
            $notEndClassRow = [];
            // 裝修標(biāo)準(zhǔn)為空的行
            $nullFitmentStandardRow = [];
            // 裝修標(biāo)準(zhǔn)不合法的行
            $illegalFitmentStandardRow = [];
            // 裝修標(biāo)準(zhǔn)超過檢查項的裝修標(biāo)準(zhǔn)
            $exceedClassFitmentStandardRow = [];
            // 內(nèi)驗期整改時限為空的行
            $nullInternalRepairRow = [];
            // 內(nèi)驗期整改時限不合法的行
            $illegalInternalRepairRow = [];
            // 交付期整改時限為空的行
            $nullDeliveryRepairRow = [];
            // 交付期整改時限不合法的行
            $illegalDeliveryRepairRow = [];
            // 補充說明是否必填為空的行
            $nullIsRemarkRequiredRow = [];
            // 補充說明是否必填不合法的行
            $illegalIsRemarkRequiredRow = [];

            // 字段到Excel列的轉(zhuǎn)換
            $fieldToColumnMapping = $this->getImportFieldMapping($sheet, $highestColumn, $this->_importClassProblemFields);

            // 提示不存在的列
            $this->checkNotExistColumn($fieldToColumnMapping, $sheet->getTitle());

            for ($row = 3; $row <= $highestRow; $row++) {
                $checkName1 = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['一級檢查項名稱'], $row)->getValue();
                $checkName1 = !empty($checkName1) ? trim($checkName1) : '';

                $checkName2 = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['二級檢查項名稱'], $row)->getValue();
                $checkName2 = !empty($checkName2) ? trim($checkName2) : '';

                $checkName3 = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['三級檢查項名稱'], $row)->getValue();
                $checkName3 = !empty($checkName3) ? trim($checkName3) : '';

                $checkName4 = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['四級檢查項名稱'], $row)->getValue();
                $checkName4 = !empty($checkName4) ? trim($checkName4) : '';

                $checkName5 = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['五級檢查項名稱'], $row)->getValue();
                $checkName5 = !empty($checkName5) ? trim($checkName5) : '';

                $classProblem = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['問題描述'], $row)->getValue();
                $classProblem = !empty($classProblem) ? trim($classProblem) : '';

                $fitRange = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['適用裝修標(biāo)準(zhǔn)'], $row)->getValue();
                $internalRepairDeadline = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['內(nèi)驗期整改時限'], $row)->getValue();
                $deliveryRepairDeadline = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['交付期整改時限'], $row)->getValue();
                $isRemarkRequired = (string) $sheet->getCellByColumnAndRow($fieldToColumnMapping['補充說明是否必填'], $row)->getValue();

                if (empty($checkName1) && empty($checkName2) && empty($checkName3) && empty($checkName4) && empty($checkName5) && empty($classProblem) && empty($fitRange) && empty($internalRepairDeadline) && empty($deliveryRepairDeadline) && empty($isRemarkRequired)) {
                    // 忽略空行
                    continue;
                }

                if ((empty($checkName1) && empty($checkName2) && empty($checkName3) && empty($checkName4) && empty($checkName5))
                    || ((!empty($checkName2) && empty($checkName1)) || (!empty($checkName3) && empty($checkName2)) || (!empty($checkName4) && empty($checkName3)) || (!empty($checkName5) && empty($checkName4)))) {
                    // 檢查項為空(包含該級檢查項不為空但是上級檢查項為空的情況)
                    $nullClassRow[] = $row;
                }

                if (strpos($checkName1, '-') !== false || strpos($checkName2, '-') !== false || strpos($checkName3, '-') !== false || strpos($checkName4, '-') !== false || strpos($checkName5, '-') !== false) {
                    // 檢查項名稱有特殊字符
                    $illegalCharacterRow[] = $row;
                }

                $strCheckName = $checkName1;
                if (!empty($checkName2)) {
                    $strCheckName .= '-' . $checkName2;
                }
                if (!empty($checkName3)) {
                    $strCheckName .= '-' . $checkName3;
                }
                if (!empty($checkName4)) {
                    $strCheckName .= '-' . $checkName4;
                }
                if (!empty($checkName5)) {
                    $strCheckName .= '-' . $checkName5;
                }
                if (!empty($strCheckName) && !in_array($strCheckName, $this->_checkName) && !in_array($row, $illegalCharacterRow)) {
                    // 問題描述中的檢查項不存在
                    $notExistClassRow[] = $row;
                }

                if (!empty($strCheckName) && $this->isNotEndClass($strCheckName)) {
                    // 問題描述對應(yīng)的檢查項非末級檢查項
                    $notEndClassRow[] = $row;
                }

                if (empty($classProblem)) {
                    // 問題描述為空
                    $nullClassProblemRow[] = $row;
                }

                if (!empty($classProblem) && mb_strlen($classProblem, 'utf-8') > 30) {
                    // 問題描述長度超過特定字符
                    $exceedMaxLengthClassProblemRow[] = $row;
                }

                $importData = [
                    'check_name1' => $checkName1,
                    'check_name2' => $checkName2,
                    'check_name3' => $checkName3,
                    'check_name4' => $checkName4,
                    'check_name5' => $checkName5,
                    'class_problem' => $classProblem
                ];

                $repeatRow = $this->getRepeatClassProblemRow($legalImportRows, $importData);
                if ($repeatRow) {
                    if (!in_array($repeatRow, $classProblemRepeatRow)) {
                        // 記錄重復(fù)的行數(shù)
                        $classProblemRepeatRow[] = $repeatRow;
                    }

                    // 問題描述重復(fù)
                    $classProblemRepeatRow[] = $row;
                }

                if (empty($fitRange)) {
                    // 適用裝修標(biāo)準(zhǔn)為空
                    $nullFitmentStandardRow[] = $row;
                }

                if (!empty($fitRange) && !in_array($fitRange, ['毛坯', '精裝', '毛坯&精裝'])) {
                    // 裝修標(biāo)準(zhǔn)不合法
                    $illegalFitmentStandardRow[] = $row;
                }

                if (!empty($fitRange) && !empty($this->_classRange[$strCheckName])) {
                    // 問題描述的適用裝修標(biāo)準(zhǔn)不超過檢查項的適用裝修標(biāo)準(zhǔn)
                    $checkItemFitRange = $this->_classRange[$strCheckName];
                    if (($checkItemFitRange == '精裝' && strpos($fitRange, '毛坯') !== false) || ($checkItemFitRange == '毛坯' && strpos($fitRange, '精裝') !== false)) {
                        $exceedClassFitmentStandardRow[] = $row;
                    }
                }

                if ($internalRepairDeadline == '') {
                    // 內(nèi)驗整改期限為空
                    $nullInternalRepairRow[] = $row;
                } elseif (!preg_match('/^[1-9][0-9]*$/', $internalRepairDeadline)) {
                    // 內(nèi)驗整改期限不合法
                    $illegalInternalRepairRow[] = $row;
                }

                if ($deliveryRepairDeadline == '') {
                    // 交付整改期限為空
                    $nullDeliveryRepairRow[] = $row;
                } elseif (!preg_match('/^[1-9][0-9]*$/', $deliveryRepairDeadline)) {
                    // 交付整改期限不合法
                    $illegalDeliveryRepairRow[] = $row;
                }

                if (empty($isRemarkRequired)) {
                    // 補充說明是否必填為空
                    $nullIsRemarkRequiredRow[] = $row;
                }

                if (!empty($isRemarkRequired) && !in_array($isRemarkRequired, ['是', '否'])) {
                    // 補充說明是否必填不合法
                    $illegalIsRemarkRequiredRow[] = $row;
                }

                $legalImportRows[$row] = [
                    'check_name1' => $checkName1,
                    'check_name2' => $checkName2,
                    'check_name3' => $checkName3,
                    'check_name4' => $checkName4,
                    'check_name5' => $checkName5,
                    'full_name' => $strCheckName,
                    'class_problem' => $classProblem,
                    'is_for_rough' => strpos($fitRange, '毛坯') !== false ? 1 : 0,
                    'is_for_decorated' => strpos($fitRange, '精裝') !== false ? 1 : 0,
                    'internal_repair_deadline' => $internalRepairDeadline,
                    'delivery_repair_deadline' => $deliveryRepairDeadline,
                    'is_remark_required' => $isRemarkRequired == '是' ? 1 : 0
                ];
            }

            if (!empty($nullClassRow)) {
                $failRow['檢查項為空'] = $nullClassRow;
            }

            if (!empty($illegalCharacterRow)) {
                $failRow['檢查項名稱包含非法字符'] = $illegalCharacterRow;
            }

            if (!empty($notExistClassRow)) {
                $failRow['檢查項不存在'] = $notExistClassRow;
            }

            if (!empty($notEndClassRow)) {
                $failRow['非末級檢查項不能添加問題描述'] = $notEndClassRow;
            }

            if (!empty($nullClassProblemRow)) {
                $failRow['問題描述為空'] = $nullClassProblemRow;
            }

            if (!empty($exceedMaxLengthClassProblemRow)) {
                $failRow['問題描述超過30個字'] = $exceedMaxLengthClassProblemRow;
            }

            if (!empty($classProblemRepeatRow)) {
                $failRow['檢查項+問題描述重名'] = $classProblemRepeatRow;
            }

            if (!empty($nullFitmentStandardRow)) {
                $failRow['適用裝修標(biāo)準(zhǔn)為空'] = $nullFitmentStandardRow;
            }

            if (!empty($illegalFitmentStandardRow)) {
                $failRow['適用裝修標(biāo)準(zhǔn)不合法'] = $illegalFitmentStandardRow;
            }

            if (!empty($exceedClassFitmentStandardRow)) {
                $failRow['問題描述的適用裝修標(biāo)準(zhǔn)與檢查項的不匹配'] = $exceedClassFitmentStandardRow;
            }

            if (!empty($nullInternalRepairRow)) {
                $failRow['內(nèi)驗期整改時限為空'] = $nullInternalRepairRow;
            }

            if (!empty($illegalInternalRepairRow)) {
                $failRow['內(nèi)驗期整改時限不合法'] = $illegalInternalRepairRow;
            }

            if (!empty($nullDeliveryRepairRow)) {
                $failRow['交付期整改時限為空'] = $nullDeliveryRepairRow;
            }

            if (!empty($illegalDeliveryRepairRow)) {
                $failRow['交付期整改時限不合法'] = $illegalDeliveryRepairRow;
            }

            if (!empty($nullIsRemarkRequiredRow)) {
                $failRow['補充說明是否必填為空'] = $nullIsRemarkRequiredRow;
            }

            if (!empty($illegalIsRemarkRequiredRow)) {
                $failRow['補充說明是否必填不合法'] = $illegalIsRemarkRequiredRow;
            }

            return ['failRow' => $failRow, 'legalImportRows' => $legalImportRows];
        } catch (InfoException $iex) {
            throw $iex;
        }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末柒昏,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子熙揍,更是在濱河造成了極大的恐慌,老刑警劉巖氏涩,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件届囚,死亡現(xiàn)場離奇詭異,居然都是意外死亡是尖,警方通過查閱死者的電腦和手機意系,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饺汹,“玉大人蛔添,你說我怎么就攤上這事《荡牵” “怎么了迎瞧?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長逸吵。 經(jīng)常有香客問我凶硅,道長,這世上最難降的妖魔是什么扫皱? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任足绅,我火速辦了婚禮捷绑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘氢妈。我一直安慰自己粹污,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布首量。 她就那樣靜靜地躺著厕怜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蕾总。 梳的紋絲不亂的頭發(fā)上粥航,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機與錄音生百,去河邊找鬼递雀。 笑死,一個胖子當(dāng)著我的面吹牛蚀浆,可吹牛的內(nèi)容都是我干的缀程。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼市俊,長吁一口氣:“原來是場噩夢啊……” “哼杨凑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起摆昧,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤撩满,失蹤者是張志新(化名)和其女友劉穎喜最,沒想到半個月后药磺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體彤敛,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡棘钞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年赁严,在試婚紗的時候發(fā)現(xiàn)自己被綠了企巢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哮奇。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡西壮,死狀恐怖偶垮,靈堂內(nèi)的尸體忽然破棺而出张咳,到底是詐尸還是另有隱情,我是刑警寧澤似舵,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布脚猾,位于F島的核電站,受9級特大地震影響啄枕,放射性物質(zhì)發(fā)生泄漏婚陪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一频祝、第九天 我趴在偏房一處隱蔽的房頂上張望泌参。 院中可真熱鬧脆淹,春花似錦、人聲如沸沽一。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽铣缠。三九已至烘嘱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蝗蛙,已是汗流浹背蝇庭。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捡硅,地道東北人哮内。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像壮韭,于是被迫代替她去往敵國和親北发。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 引言:經(jīng)常在一些社區(qū)上看到莫名的一句話PHP是世界上最好的語言喷屋,在語言的爭論上琳拨,確實是大有華山論劍的門派之爭。本文...
    LeeBoot閱讀 227評論 0 0
  • 編程范式 托馬斯.庫爾提出“科學(xué)的革命”的范式論后屯曹,Robert Floyd在1979年圖靈獎的頒獎演說中使用了編...
    zhoulujun閱讀 291評論 0 1
  • Swift的編程范式 編程范式是程序語言背后的思想狱庇。代表了程序語言的設(shè)計者認為程序應(yīng)該如何被構(gòu)建和執(zhí)行。常見的編程...
    Bobby0322閱讀 2,593評論 4 43
  • 昨天趁著工作之余是牢,看了左耳聽風(fēng)陳浩老師的《編程范式游記》系列文章僵井,原文鏈接如下:https://time.geek...
    梧上擎天閱讀 1,410評論 0 2
  • 人工智能編程范式:Common Lisp案例學(xué)習(xí)Peter Norvig 前言 范式(paradigm):名詞,一...
    geoeee閱讀 1,747評論 7 13