溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

PHP代碼有哪些優(yōu)點(diǎn)

發(fā)布時間:2021-09-28 17:26:43 來源:億速云 閱讀:92 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要講解了“PHP代碼有哪些優(yōu)點(diǎn)”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“PHP代碼有哪些優(yōu)點(diǎn)”吧!

介紹

Robert C.Martin's 的 軟件工程師準(zhǔn)則 Clean Code 同樣適用于 PHP。它并不是一個編碼風(fēng)格指南,它指導(dǎo)我們用 PHP 寫出具有可讀性,可復(fù)用性且可分解的代碼。

并非所有的準(zhǔn)則都必須嚴(yán)格遵守,甚至一些已經(jīng)成為普遍的約定。這僅僅作為指導(dǎo)方針,其中許多都是 Clean Code 作者們多年來的經(jīng)驗(yàn)。

靈感來自于 clean-code-javascript

盡管許多開發(fā)者依舊使用 PHP 5 版本,但是這篇文章中絕大多數(shù)例子都是只能在 PHP 7.1 + 版本下運(yùn)行。

變量

使用有意義的且可讀的變量名

不友好的:

$ymdstr = $moment->format('y-m-d');

友好的:

$currentDate = $moment->format('y-m-d');

對同類型的變量使用相同的詞匯

不友好的:

getUserInfo();
getUserData();
getUserRecord();
getUserProfile();

友好的:

getUser();

使用可搜索的名稱(第一部分)

我們閱讀的代碼超過我們寫的代碼。所以我們寫出的代碼需要具備可讀性、可搜索性,這一點(diǎn)非常重要。要我們?nèi)ダ斫獬绦蛑袥]有名字的變量是非常頭疼的。讓你的變量可搜索吧!

不具備可讀性的代碼:

// 見鬼的 448 是什么意思?
$result = $serializer->serialize($data, 448);

具備可讀性的:

$json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

使用可搜索的名稱(第二部分)

不好的:

// 見鬼的 4 又是什么意思?
if ($user->access & 4) {
 // ...
}

好的方式:

class User
{
 const ACCESS_READ = 1;
 const ACCESS_CREATE = 2;
 const ACCESS_UPDATE = 4;
 const ACCESS_DELETE = 8;
}

if ($user->access & User::ACCESS_UPDATE) {
 // do edit ...
}

使用解釋性變量

不好:

$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);

saveCityZipCode($matches[1], $matches[2]);

一般:

這個好點(diǎn),但我們?nèi)試?yán)重依賴正則表達(dá)式。

$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);

[, $city, $zipCode] = $matches;
saveCityZipCode($city, $zipCode);

很棒:

通過命名子模式減少對正則表達(dá)式的依賴。

$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(?<city>.+?)\s*(?<zipCode>\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);

saveCityZipCode($matches['city'], $matches['zipCode']);

避免嵌套太深和提前返回 (第一部分)

使用太多 if else 表達(dá)式會導(dǎo)致代碼難以理解。

明確優(yōu)于隱式。

不好:

function isShopOpen($day): bool
{
 if ($day) {
 if (is_string($day)) {
  $day = strtolower($day);
  if ($day === 'friday') {
  return true;
  } elseif ($day === 'saturday') {
  return true;
  } elseif ($day === 'sunday') {
  return true;
  } else {
  return false;
  }
 } else {
  return false;
 }
 } else {
 return false;
 }
}

很棒:

function isShopOpen(string $day): bool
{
 if (empty($day)) {
 return false;
 }

 $openingDays = [
 'friday', 'saturday', 'sunday'
 ];

 return in_array(strtolower($day), $openingDays, true);
}

避免嵌套太深和提前返回 (第二部分)

不好:

function fibonacci(int $n)
{
 if ($n < 50) {
 if ($n !== 0) {
  if ($n !== 1) {
  return fibonacci($n - 1) + fibonacci($n - 2);
  } else {
  return 1;
  }
 } else {
  return 0;
 }
 } else {
 return 'Not supported';
 }
}

很棒:

function fibonacci(int $n): int
{
 if ($n === 0 || $n === 1) {
 return $n;
 }

 if ($n > 50) {
 throw new \Exception('Not supported');
 }

 return fibonacci($n - 1) + fibonacci($n - 2);
}

避免心理映射

不要迫使你的代碼閱讀者翻譯變量的意義。

明確優(yōu)于隱式。

不好:

$l = ['Austin', 'New York', 'San Francisco'];

for ($i = 0; $i < count($l); $i++) {
 $li = $l[$i];
 doStuff();
 doSomeOtherStuff();
 // ...
 // ...
 // ...
 // Wait, what is `$li` for again?
 dispatch($li);
}

很棒:

$locations = ['Austin', 'New York', 'San Francisco'];

foreach ($locations as $location) {
 doStuff();
 doSomeOtherStuff();
 // ...
 // ...
 // ...
 dispatch($location);
}

不要增加不需要的上下文

如果類名或?qū)ο竺嬖V你某些東西后,請不要在變量名中重復(fù)。

小壞壞:

class Car
{
 public $carMake;
 public $carModel;
 public $carColor;

 //...
}

好的方式:

class Car
{
 public $make;
 public $model;
 public $color;

 //...
}

使用默認(rèn)參數(shù)而不是使用短路運(yùn)算或者是條件判斷

不好的做法:

這是不太好的因?yàn)?$breweryName 可以是 NULL.

function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void
{
 // ...
}

還算可以的做法:

這個做法比上面的更加容易理解,但是它需要很好的去控制變量的值.

function createMicrobrewery($name = null): void
{
 $breweryName = $name ?: 'Hipster Brew Co.';
 // ...
}

好的做法:

你可以使用 類型提示 而且可以保證 $breweryName 不會為空 NULL.

function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void
{
 // ...
}

對比

使用 相等運(yùn)算符

不好的做法:

$a = '42';
$b = 42;

使用簡單的相等運(yùn)算符會把字符串類型轉(zhuǎn)換成數(shù)字類型

if( $a != $b ) {
 //這個條件表達(dá)式總是會通過
}

表達(dá)式 $a != $b 會返回 false 但實(shí)際上它應(yīng)該是 true !
字符串類型 '42' 是不同于數(shù)字類型的 42

好的做法:
使用全等運(yùn)算符會對比類型和值

if( $a !== $b ) {
 //這個條件是通過的
}

表達(dá)式 $a !== $b 會返回 true。

函數(shù)

函數(shù)參數(shù)(2 個或更少)

限制函數(shù)參數(shù)個數(shù)極其重要
這樣測試你的函數(shù)容易點(diǎn)。有超過 3 個可選參數(shù)會導(dǎo)致一個爆炸式組合增長,你會有成噸獨(dú)立參數(shù)情形要測試。

無參數(shù)是理想情況。1 個或 2 個都可以,最好避免 3 個。
再多就需要加固了。通常如果你的函數(shù)有超過兩個參數(shù),說明他要處理的事太多了。 如果必須要傳入很多數(shù)據(jù),建議封裝一個高級別對象作為參數(shù)。

不友好的:

function createMenu(string $title, string $body, string $buttonText, bool $cancellable): void
{
 // ...
}

友好的:

class MenuConfig
{
 public $title;
 public $body;
 public $buttonText;
 public $cancellable = false;
}

$config = new MenuConfig();
$config->title = 'Foo';
$config->body = 'Bar';
$config->buttonText = 'Baz';
$config->cancellable = true;

function createMenu(MenuConfig $config): void
{
 // ...
}

函數(shù)應(yīng)該只做一件事情

這是迄今為止軟件工程最重要的原則。函數(shù)做了超過一件事情時,它們將變得難以編寫、測試、推導(dǎo)。 而函數(shù)只做一件事情時,重構(gòu)起來則非常簡單,同時代碼閱讀起來也非常清晰。掌握了這個原則,你就會領(lǐng)先許多其他的開發(fā)者。

不好的:

function emailClients(array $clients): void
{
 foreach ($clients as $client) {
  $clientRecord = $db->find($client);
  if ($clientRecord->isActive()) {
   email($client);
  }
 }
}

好的:

function emailClients(array $clients): void
{
 $activeClients = activeClients($clients);
 array_walk($activeClients, 'email');
}

function activeClients(array $clients): array
{
 return array_filter($clients, 'isClientActive');
}

function isClientActive(int $client): bool
{
 $clientRecord = $db->find($client);

 return $clientRecord->isActive();
}

函數(shù)的名稱要說清楚它做什么

不好的例子:

class Email
{
 //...

 public function handle(): void
 {
  mail($this->to, $this->subject, $this->body);
 }
}

$message = new Email(...);
// What is this? A handle for the message? Are we writing to a file now?
$message->handle();

很好的例子:

class Email 
{
 //...

 public function send(): void
 {
  mail($this->to, $this->subject, $this->body);
 }
}

$message = new Email(...);
// Clear and obvious
$message->send();

函數(shù)只能是一個抽象級別

當(dāng)你有多個抽象層次時,你的函數(shù)功能通常是做太多了。 分割函數(shù)功能使得重用性和測試更加容易。.

不好:

function parseBetterJSAlternative(string $code): void
{
 $regexes = [
  // ...
 ];

 $statements = explode(' ', $code);
 $tokens = [];
 foreach ($regexes as $regex) {
  foreach ($statements as $statement) {
   // ...
  }
 }

 $ast = [];
 foreach ($tokens as $token) {
  // lex...
 }

 foreach ($ast as $node) {
  // parse...
 }
}

同樣不是很好:

我們已經(jīng)完成了一些功能,但是 parseBetterJSAlternative() 功能仍然非常復(fù)雜,測試起來也比較麻煩。

function tokenize(string $code): array
{
 $regexes = [
  // ...
 ];

 $statements = explode(' ', $code);
 $tokens = [];
 foreach ($regexes as $regex) {
  foreach ($statements as $statement) {
   $tokens[] = /* ... */;
  }
 }

 return $tokens;
}

function lexer(array $tokens): array
{
 $ast = [];
 foreach ($tokens as $token) {
  $ast[] = /* ... */;
 }

 return $ast;
}

function parseBetterJSAlternative(string $code): void
{
 $tokens = tokenize($code);
 $ast = lexer($tokens);
 foreach ($ast as $node) {
  // parse...
 }
}

很好的:

最好的解決方案是取出 parseBetterJSAlternative() 函數(shù)的依賴關(guān)系.

class Tokenizer
{
 public function tokenize(string $code): array
 {
  $regexes = [
   // ...
  ];

  $statements = explode(' ', $code);
  $tokens = [];
  foreach ($regexes as $regex) {
   foreach ($statements as $statement) {
    $tokens[] = /* ... */;
   }
  }

  return $tokens;
 }
}

class Lexer
{
 public function lexify(array $tokens): array
 {
  $ast = [];
  foreach ($tokens as $token) {
   $ast[] = /* ... */;
  }

  return $ast;
 }
}

class BetterJSAlternative
{
 private $tokenizer;
 private $lexer;

 public function __construct(Tokenizer $tokenizer, Lexer $lexer)
 {
  $this->tokenizer = $tokenizer;
  $this->lexer = $lexer;
 }

 public function parse(string $code): void
 {
  $tokens = $this->tokenizer->tokenize($code);
  $ast = $this->lexer->lexify($tokens);
  foreach ($ast as $node) {
   // parse...
  }
 }
}

不要用標(biāo)示作為函數(shù)的參數(shù)

標(biāo)示就是在告訴大家,這個方法里處理很多事。前面剛說過,一個函數(shù)應(yīng)當(dāng)只做一件事。 把不同標(biāo)示的代碼拆分到多個函數(shù)里。

不友好的:

function createFile(string $name, bool $temp = false): void
{
 if ($temp) {
  touch('./temp/'.$name);
 } else {
  touch($name);
 }
}

友好的:

function createFile(string $name): void
{
 touch($name);
}

function createTempFile(string $name): void
{
 touch('./temp/'.$name);
}

避免副作用

一個函數(shù)應(yīng)該只獲取數(shù)值,然后返回另外的數(shù)值,如果在這個過程中還做了其他的事情,我們就稱為副作用。副作用可能是寫入一個文件,修改某些全局變量,或者意外的把你全部的錢給了陌生人。

現(xiàn)在,你的確需要在一個程序或者場合里要有副作用,像之前的例子,你也許需要寫一個文件。你需要做的是把你做這些的地方集中起來。不要用幾個函數(shù)和類來寫入一個特定的文件。只允許使用一個服務(wù)來單獨(dú)實(shí)現(xiàn)。

重點(diǎn)是避免常見陷阱比如對象間共享無結(jié)構(gòu)的數(shù)據(jù)、使用可以寫入任何的可變數(shù)據(jù)類型、不集中去處理這些副作用。如果你做了這些你就會比大多數(shù)程序員快樂。

不好的:

// 這個全局變量在函數(shù)中被使用
// 如果我們在別的方法中使用這個全局變量,有可能我們會不小心將其修改為數(shù)組類型
$name = 'Ryan McDermott';

function splitIntoFirstAndLastName(): void
{
 global $name;

 $name = explode(' ', $name);
}

splitIntoFirstAndLastName();

var_dump($name); // ['Ryan', 'McDermott'];

推薦的:

function splitIntoFirstAndLastName(string $name): array
{
 return explode(' ', $name);
}

$name = 'Ryan McDermott';
$newName = splitIntoFirstAndLastName($name);

var_dump($name); // 'Ryan McDermott';
var_dump($newName); // ['Ryan', 'McDermott'];

不要定義全局函數(shù)

在很多語言中定義全局函數(shù)是一個壞習(xí)慣,因?yàn)槟愣x的全局函數(shù)可能與其他人的函數(shù)庫沖突,并且,除非在實(shí)際運(yùn)用中遇到異常,否則你的 API 的使用者將無法覺察到這一點(diǎn)。接下來我們來看看一個例子:當(dāng)你想有一個配置數(shù)組,你可能會寫一個 config() 的全局函數(shù),但是這樣會與其他人定義的庫沖突。

不好的:

function config(): array
{
 return [
  'foo' => 'bar',
 ]
}

好的:

class Configuration
{
 private $configuration = [];

 public function __construct(array $configuration)
 {
  $this->configuration = $configuration;
 }

 public function get(string $key): ?string
 {
  return isset($this->configuration[$key]) ? $this->configuration[$key] : null;
 }
}

獲取配置需要先創(chuàng)建 Configuration 類的實(shí)例,如下:

$configuration = new Configuration([
 'foo' => 'bar',
]);

現(xiàn)在,在你的應(yīng)用中必須使用 Configuration 的實(shí)例了。

不要使用單例模式

單例模式是個 反模式。 以下轉(zhuǎn)述 Brian Button 的觀點(diǎn):

單例模式常用于 全局實(shí)例, 這么做為什么不好呢? 因?yàn)樵谀愕拇a里 你隱藏了應(yīng)用的依賴關(guān)系,而沒有通過接口公開依賴關(guān)系 。避免全局的東西擴(kuò)散使用是一種 代碼味道.
單例模式違反了 單一責(zé)任原則: 依據(jù)的事實(shí)就是 單例模式自己控制自身的創(chuàng)建和生命周期.
單例模式天生就導(dǎo)致代碼緊 耦合。這使得在許多情況下用偽造的數(shù)據(jù) 難于測試。
單例模式的狀態(tài)會留存于應(yīng)用的整個生命周期。 這會對測試產(chǎn)生第二次打擊,你只能讓被嚴(yán)令需要測試的代碼運(yùn)行不了收場,根本不能進(jìn)行單元測試。為何?因?yàn)槊恳粋€單元測試應(yīng)該彼此獨(dú)立。
還有些來自 Misko Hevery 的深入思考,關(guān)于單例模式的問題根源。

不好的示范:

class DBConnection
{
 private static $instance;

 private function __construct(string $dsn)
 {
  // ...
 }

 public static function getInstance(): DBConnection
 {
  if (self::$instance === null) {
   self::$instance = new self();
  }

  return self::$instance;
 }

 // ...
}

$singleton = DBConnection::getInstance();

好的示范:

class DBConnection
{
 public function __construct(string $dsn)
 {
  // ...
 }

  // ...
}

用 DSN 進(jìn)行配置創(chuàng)建的 DBConnection 類實(shí)例。

$connection = new DBConnection($dsn);

現(xiàn)在就必須在你的應(yīng)用中使用 DBConnection 的實(shí)例了。

封裝條件語句

不友好的:

if ($article->state === 'published') {
 // ...
}

友好的:

if ($article->isPublished()) {
 // ...
}

避免用反義條件判斷

不友好的:

function isDOMNodeNotPresent(\DOMNode $node): bool
{
 // ...
}

if (!isDOMNodeNotPresent($node))
{
 // ...
}

友好的:

function isDOMNodePresent(\DOMNode $node): bool
{
 // ...
}

if (isDOMNodePresent($node)) {
 // ...
}

避免使用條件語句

這聽起來像是個不可能實(shí)現(xiàn)的任務(wù)。 當(dāng)?shù)谝淮温牭竭@個時,大部分人都會說,“沒有 if 語句,我該怎么辦?” 答案就是在很多情況下你可以使用多態(tài)性來實(shí)現(xiàn)同樣的任務(wù)。 接著第二個問題來了, “聽著不錯,但我為什么需要那樣做?”,這個答案就是我們之前所學(xué)的干凈代碼概念:一個函數(shù)應(yīng)該只做一件事情。如果你的類或函數(shù)有 if 語句,這就告訴了使用者你的類或函數(shù)干了不止一件事情。 記住,只要做一件事情。

不好的:

class Airplane
{
 // ...

 public function getCruisingAltitude(): int
 {
  switch ($this->type) {
   case '777':
    return $this->getMaxAltitude() - $this->getPassengerCount();
   case 'Air Force One':
    return $this->getMaxAltitude();
   case 'Cessna':
    return $this->getMaxAltitude() - $this->getFuelExpenditure();
  }
 }
}

好的:

interface Airplane
{
 // ...

 public function getCruisingAltitude(): int;
}

class Boeing777 implements Airplane
{
 // ...

 public function getCruisingAltitude(): int
 {
  return $this->getMaxAltitude() - $this->getPassengerCount();
 }
}

class AirForceOne implements Airplane
{
 // ...

 public function getCruisingAltitude(): int
 {
  return $this->getMaxAltitude();
 }
}

class Cessna implements Airplane
{
 // ...

 public function getCruisingAltitude(): int
 {
  return $this->getMaxAltitude() - $this->getFuelExpenditure();
 }
}

避免類型檢測 (第 1 部分)

PHP 是無類型的,這意味著你的函數(shù)可以接受任何類型的參數(shù)。
有時這種自由會讓你感到困擾,并且他會讓你自然而然的在函數(shù)中使用類型檢測。有很多方法可以避免這么做。
首先考慮 API 的一致性。

不好的:

function travelToTexas($vehicle): void
{
 if ($vehicle instanceof Bicycle) {
  $vehicle->pedalTo(new Location('texas'));
 } elseif ($vehicle instanceof Car) {
  $vehicle->driveTo(new Location('texas'));
 }
}

好的:

function travelToTexas(Traveler $vehicle): void
{
 $vehicle->travelTo(new Location('texas'));
}

避免類型檢查(第 2 部分)

如果你正在使用像 字符串、數(shù)值、或數(shù)組這樣的基礎(chǔ)類型,你使用的是 PHP 版本是 PHP 7+,并且你不能使用多態(tài),但仍然覺得需要使用類型檢測,這時,你應(yīng)該考慮 類型定義 或 嚴(yán)格模式。它為您提供了標(biāo)準(zhǔn) PHP 語法之上的靜態(tài)類型。
手動進(jìn)行類型檢查的問題是做這件事需要這么多的額外言辭,你所得到的虛假的『類型安全』并不能彌補(bǔ)丟失的可讀性。保持你的代碼簡潔,編寫良好的測試,并且擁有好的代碼審查。
否則,使用 PHP 嚴(yán)格的類型聲明或嚴(yán)格模式完成所有這些工作。

不好的:

function combine($val1, $val2): int
{
 if (!is_numeric($val1) || !is_numeric($val2)) {
  throw new \Exception('Must be of type Number');
 }

 return $val1 + $val2;
}

好的:

function combine(int $val1, int $val2): int
{
 return $val1 + $val2;
}

移除無用代碼

無用代碼和重復(fù)代碼一樣糟糕。 如果沒有被調(diào)用,就應(yīng)該把它刪除掉,沒必要將它保留在你的代碼庫中!當(dāng)你需要它的時候,可以在你的歷史版本中找到它。

Bad:

function oldRequestModule(string $url): void
{
 // ...
}

function newRequestModule(string $url): void
{
 // ...
}

$request = newRequestModule($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');

Good:

function requestModule(string $url): void
{
 // ...
}

$request = requestModule($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');

對象和數(shù)據(jù)結(jié)構(gòu)

使用對象封裝

在 PHP 中,你可以在方法中使用關(guān)鍵字,如 public, protected and private。
使用它們,你可以任意的控制、修改對象的屬性。

當(dāng)你除獲取對象屬性外還想做更多的操作時,你不需要修改你的代碼
當(dāng) set 屬性時,易于增加參數(shù)驗(yàn)證。
封裝的內(nèi)部表示。
容易在獲取和設(shè)置屬性時添加日志和錯誤處理。
繼承這個類,你可以重寫默認(rèn)信息。
你可以延遲加載對象的屬性,比如從服務(wù)器獲取數(shù)據(jù)。
此外,這樣的方式也符合 OOP 開發(fā)中的 [開閉原則](# 開閉原則 (OCP))

不好的:

class BankAccount
{
 public $balance = 1000;
}

$bankAccount = new BankAccount();

// Buy shoes...
$bankAccount->balance -= 100;

好的:

class BankAccount
{
 private $balance;

 public function __construct(int $balance = 1000)
 {
  $this->balance = $balance;
 }

 public function withdraw(int $amount): void
 {
  if ($amount > $this->balance) {
   throw new \Exception('Amount greater than available balance.');
  }

  $this->balance -= $amount;
 }

 public function deposit(int $amount): void
 {
  $this->balance += $amount;
 }

 public function getBalance(): int
 {
  return $this->balance;
 }
}

$bankAccount = new BankAccount();

// Buy shoes...
$bankAccount->withdraw($shoesPrice);

// Get balance
$balance = $bankAccount->getBalance();

讓對象擁有 private/protected 屬性的成員

  • public 公有方法和屬性對于變化來說是最危險的,因?yàn)橐恍┩獠康拇a可能會輕易的依賴他們,但是你沒法控制那些依賴他們的代碼。 類的變化對于類的所有使用者來說都是危險的。

  • protected 受保護(hù)的屬性變化和 public 公有的同樣危險,因?yàn)樗麄冊谧宇惙秶鷥?nèi)是可用的。也就是說 public 和 protected 之間的區(qū)別僅僅在于訪問機(jī)制,只有封裝才能保證屬性是一致的。任何在類內(nèi)的變化對于所有繼承子類來說都是危險的 。

  • private 私有屬性的變化可以保證代碼 只對單個類范圍內(nèi)的危險 (對于修改你是安全的,并且你不會有其他類似堆積木的影響 Jenga effect).

因此,請默認(rèn)使用 private 屬性,只有當(dāng)需要對外部類提供訪問屬性的時候才采用 public/protected 屬性。

更多的信息可以參考Fabien Potencier 寫的針對這個專欄的文章blog post .

Bad:

class Employee
{
 public $name;

 public function __construct(string $name)
 {
  $this->name = $name;
 }
}

$employee = new Employee('John Doe');
echo 'Employee name: '.$employee->name; // Employee name: John Doe

Good:

class Employee
{
 private $name;

 public function __construct(string $name)
 {
  $this->name = $name;
 }

 public function getName(): string
 {
  return $this->name;
 }
}

$employee = new Employee('John Doe');
echo 'Employee name: '.$employee->getName(); // Employee name: John Doe


組合優(yōu)于繼承

正如 the Gang of Four 所著的 設(shè)計模式 中所說,
我們應(yīng)該盡量優(yōu)先選擇組合而不是繼承的方式。使用繼承和組合都有很多好處。
這個準(zhǔn)則的主要意義在于當(dāng)你本能的使用繼承時,試著思考一下組合是否能更好對你的需求建模。
在一些情況下,是這樣的。

接下來你或許會想,“那我應(yīng)該在什么時候使用繼承?”
答案依賴于你的問題,當(dāng)然下面有一些何時繼承比組合更好的說明:

  1. 你的繼承表達(dá)了 “是一個” 而不是 “有一個” 的關(guān)系(例如人類 “是” 動物,而用戶 “有” 用戶詳情)。

  2. 你可以復(fù)用基類的代碼(人類可以像動物一樣移動)。

  3. 你想通過修改基類對所有派生類做全局的修改(當(dāng)動物移動時,修改它們的能量消耗)。

糟糕的:

class Employee 
{
 private $name;
 private $email;

 public function __construct(string $name, string $email)
 {
  $this->name = $name;
  $this->email = $email;
 }

 // ...
}

// 不好,因?yàn)镋mployees "有" taxdata
// 而EmployeeTaxData不是Employee類型的

class EmployeeTaxData extends Employee 
{
 private $ssn;
 private $salary;

 public function __construct(string $name, string $email, string $ssn, string $salary)
 {
  parent::__construct($name, $email);

  $this->ssn = $ssn;
  $this->salary = $salary;
 }

 // ...
}

棒棒噠:

class EmployeeTaxData 
{
  private $ssn;
  private $salary;

  public function __construct(string $ssn, string $salary)
  {
    $this->ssn = $ssn;
    $this->salary = $salary;
  }

  // ...
}

class Employee 
{
  private $name;
  private $email;
  private $taxData;

  public function __construct(string $name, string $email)
  {
    $this->name = $name;
    $this->email = $email;
  }

  public function setTaxData(string $ssn, string $salary)
  {
    $this->taxData = new EmployeeTaxData($ssn, $salary);
  }

  // ...
}

避免流式接口

流式接口 是一種面向?qū)ο?API 的方法,旨在通過方法鏈 Method chaining 來提高源代碼的可閱讀性.

流式接口雖然需要一些上下文,需要經(jīng)常構(gòu)建對象,但是這種模式減少了代碼的冗余度 (例如: PHPUnit Mock Builder
或 Doctrine Query Builder)

但是同樣它也帶來了很多麻煩:

  1. 破壞了封裝 Encapsulation

  2. 破壞了原型 Decorators

  3. 難以模擬測試 mock

  4. 使得多次提交的代碼難以理解

更多信息可以參考 Marco Pivetta 撰寫的關(guān)于這個專題的文章blog post

Bad:

class Car
{
  private $make = 'Honda';
  private $model = 'Accord';
  private $color = 'white';

  public function setMake(string $make): self
  {
    $this->make = $make;

    // NOTE: Returning this for chaining
    return $this;
  }

  public function setModel(string $model): self
  {
    $this->model = $model;

    // NOTE: Returning this for chaining
    return $this;
  }

  public function setColor(string $color): self
  {
    $this->color = $color;

    // NOTE: Returning this for chaining
    return $this;
  }

  public function dump(): void
  {
    var_dump($this->make, $this->model, $this->color);
  }
}

$car = (new Car())
 ->setColor('pink')
 ->setMake('Ford')
 ->setModel('F-150')
 ->dump();

Good:

class Car
{
  private $make = 'Honda';
  private $model = 'Accord';
  private $color = 'white';

  public function setMake(string $make): void
  {
    $this->make = $make;
  }

  public function setModel(string $model): void
  {
    $this->model = $model;
  }

  public function setColor(string $color): void
  {
    $this->color = $color;
  }

  public function dump(): void
  {
    var_dump($this->make, $this->model, $this->color);
  }
}

$car = new Car();
$car->setColor('pink');
$car->setMake('Ford');
$car->setModel('F-150');
$car->dump();

SOLID

SOLID 是 Michael Feathers 推薦的便于記憶的首字母簡寫,它代表了 Robert Martin 命名的最重要的五個面向?qū)ο缶幊淘O(shè)計原則:

  • S: 職責(zé)單一原則 (SRP)

  • O: 開閉原則 (OCP)

  • L: 里氏替換原則 (LSP)

  • I: 接口隔離原則 (ISP)

  • D: 依賴反轉(zhuǎn)原則 (DIP)

職責(zé)單一原則 Single Responsibility Principle (SRP)

正如 Clean Code 書中所述,"修改一個類應(yīng)該只為一個理由"。人們總是容易去用一堆方法 "塞滿" 一個類,就好像當(dāng)我們坐飛機(jī)上只能攜帶一個行李箱時,會把所有的東西都塞到這個箱子里。這樣做帶來的后果是:從邏輯上講,這樣的類不是高內(nèi)聚的,并且留下了很多以后去修改它的理由。

將你需要修改類的次數(shù)降低到最小很重要,這是因?yàn)?,?dāng)類中有很多方法時,修改某一處,你很難知曉在整個代碼庫中有哪些依賴于此的模塊會被影響。

比較糟:

class UserSettings
{
  private $user;

  public function __construct(User $user)
  {
    $this->user = $user;
  }

  public function changeSettings(array $settings): void
  {
    if ($this->verifyCredentials()) {
      // ...
    }
  }

  private function verifyCredentials(): bool
  {
    // ...
  }
}

棒棒噠:

class UserAuth 
{
  private $user;

  public function __construct(User $user)
  {
    $this->user = $user;
  }

  public function verifyCredentials(): bool
  {
    // ...
  }
}

class UserSettings 
{
  private $user;
  private $auth;

  public function __construct(User $user) 
  {
    $this->user = $user;
    $this->auth = new UserAuth($user);
  }

  public function changeSettings(array $settings): void
  {
    if ($this->auth->verifyCredentials()) {
      // ...
    }
  }
}

開閉原則 (OCP)

如 Bertrand Meyer 所述,"軟件實(shí)體 (類,模塊,功能,等) 應(yīng)該對擴(kuò)展開放,但對修改關(guān)閉." 這意味著什么?這個原則大體上是指你應(yīng)該允許用戶在不修改已有代碼情況下添加功能.

壞的:

abstract class Adapter
{
  protected $name;

  public function getName(): string
  {
    return $this->name;
  }
}

class AjaxAdapter extends Adapter
{
  public function __construct()
  {
    parent::__construct();

    $this->name = 'ajaxAdapter';
  }
}

class NodeAdapter extends Adapter
{
  public function __construct()
  {
    parent::__construct();

    $this->name = 'nodeAdapter';
  }
}

class HttpRequester
{
  private $adapter;

  public function __construct(Adapter $adapter)
  {
    $this->adapter = $adapter;
  }

  public function fetch(string $url): Promise
  {
    $adapterName = $this->adapter->getName();

    if ($adapterName === 'ajaxAdapter') {
      return $this->makeAjaxCall($url);
    } elseif ($adapterName === 'httpNodeAdapter') {
      return $this->makeHttpCall($url);
    }
  }

  private function makeAjaxCall(string $url): Promise
  {
    // request and return promise
  }

  private function makeHttpCall(string $url): Promise
  {
    // request and return promise
  }
}

好的:

interface Adapter
{
  public function request(string $url): Promise;
}

class AjaxAdapter implements Adapter
{
  public function request(string $url): Promise
  {
    // request and return promise
  }
}

class NodeAdapter implements Adapter
{
  public function request(string $url): Promise
  {
    // request and return promise
  }
}

class HttpRequester
{
  private $adapter;

  public function __construct(Adapter $adapter)
  {
    $this->adapter = $adapter;
  }

  public function fetch(string $url): Promise
  {
    return $this->adapter->request($url);
  }
}

里氏代換原則 (LSP)

這是一個簡單概念的可怕術(shù)語。它通常被定義為 “如果 S 是 T 的一個子類型,則 T 型對象可以替換為 S 型對象”
(i.e., S 類型的對象可以替換 T 型對象) 在不改變程序的任何理想屬性的情況下 (正確性,任務(wù)完成度,etc.)." 這是一個更可怕的定義.
這個的最佳解釋是,如果你有個父類和一個子類,然后父類和子類可以互換使用而不會得到不正確的結(jié)果。這或許依然令人疑惑,所以我們來看下經(jīng)典的正方形 - 矩形例子。幾何定義,正方形是矩形,但是,如果你通過繼承建立了 “IS-a” 關(guān)系的模型,你很快就會陷入麻煩。.

不好的:

class Rectangle
{
  protected $width = 0;
  protected $height = 0;

  public function render(int $area): void
  {
    // ...
  }

  public function setWidth(int $width): void
  {
    $this->width = $width;
  }

  public function setHeight(int $height): void
  {
    $this->height = $height;
  }

  public function getArea(): int
  {
    return $this->width * $this->height;
  }
}

class Square extends Rectangle
{
  public function setWidth(int $width): void
  {
    $this->width = $this->height = $width;
  }

  public function setHeight(int $height): void
  {
    $this->width = $this->height = $height;
  }
}

/**
 * @param Rectangle[] $rectangles
 */
function renderLargeRectangles(array $rectangles): void
{
  foreach ($rectangles as $rectangle) {
    $rectangle->setWidth(4);
    $rectangle->setHeight(5);
    $area = $rectangle->getArea(); // BAD: Will return 25 for Square. Should be 20.
    $rectangle->render($area);
  }
}

$rectangles = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles($rectangles);

優(yōu)秀的:

abstract class Shape
{
  abstract public function getArea(): int;

  public function render(int $area): void
  {
    // ...
  }
}

class Rectangle extends Shape
{
  private $width;
  private $height;

  public function __construct(int $width, int $height)
  {
    $this->width = $width;
    $this->height = $height;
  }

  public function getArea(): int
  {
    return $this->width * $this->height;
  }
}

class Square extends Shape
{
  private $length;

  public function __construct(int $length)
  {
    $this->length = $length;
  }

  public function getArea(): int
  {
    return pow($this->length, 2);
  }
}

/**
 * @param Rectangle[] $rectangles
 */
function renderLargeRectangles(array $rectangles): void
{
  foreach ($rectangles as $rectangle) {
    $area = $rectangle->getArea(); 
    $rectangle->render($area);
  }
}

$shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)];
renderLargeRectangles($shapes);

接口隔離原則 (ISP)

ISP 指出 "客戶不應(yīng)該被強(qiáng)制依賴于他們用不到的接口."

一個好的例子來觀察證實(shí)此原則的是針對需要大量設(shè)置對象的類,不要求客戶端設(shè)置大量的選項(xiàng)是有益的,因?yàn)槎鄶?shù)情況下他們不需要所有的設(shè)置。使他們可選來避免產(chǎn)生一個 “臃腫的接口”.

壞的:

interface Employee
{
 public function work(): void;

 public function eat(): void;
}

class Human implements Employee
{
 public function work(): void
 {
  // ....working
 }

 public function eat(): void
 {
  // ...... eating in lunch break
 }
}

class Robot implements Employee
{
 public function work(): void
 {
  //.... working much more
 }

 public function eat(): void
 {
  //.... robot can't eat, but it must implement this method
 }
}

好的:

并不是每個工人都是雇員,但每個雇員都是工人.

interface Workable
{
 public function work(): void;
}

interface Feedable
{
 public function eat(): void;
}

interface Employee extends Feedable, Workable
{
}

class Human implements Employee
{
 public function work(): void
 {
  // ....working
 }

 public function eat(): void
 {
  //.... eating in lunch break
 }
}

// robot can only work
class Robot implements Workable
{
 public function work(): void
 {
  // ....working
 }
}

依賴反轉(zhuǎn)原則 (DIP)

這一原則規(guī)定了兩項(xiàng)基本內(nèi)容:

高級模塊不應(yīng)依賴于低級模塊。兩者都應(yīng)該依賴于抽象.
抽象類不應(yīng)依賴于實(shí)例。實(shí)例應(yīng)該依賴于抽象.
一開始可能很難去理解,但是你如果工作中使用過 php 框架(如 Symfony), 你應(yīng)該見過以依賴的形式執(zhí)行這一原則
依賴注入 (DI). 雖然他們不是相同的概念,DIP 可以讓高級模塊不需要了解其低級模塊的詳細(xì)信息而安裝它們.
通過依賴注入可以做到。這樣做的一個巨大好處是減少了模塊之間的耦合。耦合是一種非常糟糕的開發(fā)模式,因?yàn)樗鼓拇a難以重構(gòu).

不好的:

class Employee
{
 public function work(): void
 {
  // ....working
 }
}

class Robot extends Employee
{
 public function work(): void
 {
  //.... working much more
 }
}

class Manager
{
 private $employee;

 public function __construct(Employee $employee)
 {
  $this->employee = $employee;
 }

 public function manage(): void
 {
  $this->employee->work();
 }
}

優(yōu)秀的:

interface Employee
{
 public function work(): void;
}

class Human implements Employee
{
 public function work(): void
 {
  // ....working
 }
}

class Robot implements Employee
{
 public function work(): void
 {
  //.... working much more
 }
}

class Manager
{
 private $employee;

 public function __construct(Employee $employee)
 {
  $this->employee = $employee;
 }

 public function manage(): void
 {
  $this->employee->work();
 }
}

別寫重復(fù)代碼 (DRY)

試著去遵循 DRY 原則。

盡你最大的努力去避免復(fù)制代碼,它是一種非常糟糕的行為,復(fù)制代碼通常意味著當(dāng)你需要變更一些邏輯時,你需要修改不止一處。

試想一下,如果你在經(jīng)營一家餐廳,并且你需要記錄你倉庫的進(jìn)銷記錄:包括所有的土豆,洋蔥,大蒜,辣椒,等等。如果你使用多個表格來管理進(jìn)銷記錄,當(dāng)你用其中一些土豆做菜時,你需要更新所有的表格。如果你只有一個列表的話就只需要更新一個地方。

通常情況下你復(fù)制代碼的原因可能是它們大多數(shù)都是一樣的,只不過有兩個或者多個略微不同的邏輯,但是由于這些區(qū)別,最終導(dǎo)致你寫出了兩個或者多個隔離的但大部分相同的方法,移除重復(fù)的代碼意味著用一個 function/module/class 創(chuàng)建一個能處理差異的抽象。

正確的抽象是非常關(guān)鍵的,這正是為什么你必須學(xué)習(xí)遵守在 Classes 章節(jié)展開討論的的 SOLID 原則,不合理的抽象比復(fù)制代碼更糟糕,所以請務(wù)必謹(jǐn)慎!說了這么多,如果你能設(shè)計一個合理的抽象,就去實(shí)現(xiàn)它!最后再說一遍,不要寫重復(fù)代碼,否則你會發(fā)現(xiàn)當(dāng)你想修改一個邏輯時,你必須去修改多個地方!

糟糕的:

function showDeveloperList(array $developers): void
{
 foreach ($developers as $developer) {
  $expectedSalary = $developer->calculateExpectedSalary();
  $experience = $developer->getExperience();
  $githubLink = $developer->getGithubLink();
  $data = [
   $expectedSalary,
   $experience,
   $githubLink
  ];

  render($data);
 }
}

function showManagerList(array $managers): void
{
 foreach ($managers as $manager) {
  $expectedSalary = $manager->calculateExpectedSalary();
  $experience = $manager->getExperience();
  $githubLink = $manager->getGithubLink();
  $data = [
   $expectedSalary,
   $experience,
   $githubLink
  ];

  render($data);
 }
}

好的:

function showList(array $employees): void
{
 foreach ($employees as $employee) {
  $expectedSalary = $employee->calculateExpectedSalary();
  $experience = $employee->getExperience();
  $githubLink = $employee->getGithubLink();
  $data = [
   $expectedSalary,
   $experience,
   $githubLink
  ];

  render($data);
 }
}

非常好:

最好讓你的代碼緊湊一點(diǎn)。

function showList(array $employees): void
{
 foreach ($employees as $employee) {
  render([
   $employee->calculateExpectedSalary(),
   $employee->getExperience(),
   $employee->getGithubLink()
  ]);
 }
}

感謝各位的閱讀,以上就是“PHP代碼有哪些優(yōu)點(diǎn)”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對PHP代碼有哪些優(yōu)點(diǎn)這一問題有了更深刻的體會,具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

php
AI