首頁 > 後端開發 > php教程 > Poka oke-通過超級防禦性編程保存項目

Poka oke-通過超級防禦性編程保存項目

尊渡假赌尊渡假赌尊渡假赌
發布: 2025-02-09 11:13:12
原創
213 人瀏覽過

Poka Yoke - Saving Projects with Hyper-Defensive Programming

本文經Deji Akala和Marco Pivetta同行評審。感謝所有SitePoint的同行評審者,使SitePoint的內容達到最佳狀態!


在中大型團隊協作開發同一代碼庫時,理解彼此的代碼及其使用方法有時會變得困難。為此,存在多種解決方案。例如,可以約定遵循一組編碼標準以提高代碼的可讀性,或使用所有團隊成員都熟悉的框架(此處提供優秀的Laravel入門高級課程)。

然而,這通常還不夠,尤其當需要深入研究一段時間前編寫的應用程序部分來修復錯誤或添加新功能時。這時,很難記住特定類的預期工作方式,包括它們自身以及它們彼此之間的組合方式。此時,很容易意外地引入副作用或錯誤而不自知。

Poka Yoke - Saving Projects with Hyper-Defensive Programming

這些錯誤可能會在質量保證中被發現,但也可能真實地被忽略。即使被發現,將代碼發回並修復也可能需要大量時間。

那麼,我們如何預防這種情況呢?答案是“Poka Yoke”

關鍵要點

  • Poka Yoke定義:Poka Yoke是日語術語,意為“防錯”,起源於精益製造,應用於編程以防止代碼使用中的意外錯誤。
  • 錯誤預防和檢測:該方法分為錯誤預防(確保代碼正確使用)和錯誤檢測(涉及監控應用程序以查找潛在錯誤)。
  • 實用應用示例:在PHP中實現類型聲明以防止參數類型錯誤,以及使用值對像以避免混淆函數參數,是錯誤預防的關鍵示例。
  • 不可變性和空對象:強調對象的不可變性以防止應用程序中的副作用,以及利用空對像以避免空檢查,是高級Poka Yoke策略。
  • 錯誤處理和日誌記錄:倡導嚴格的錯誤處理,例如在PHP中啟用嚴格類型並且不抑制錯誤,以及主動記錄和監控應用程序。
  • 更廣泛的適用性:Poka Yoke 並非僅限於編程,它還可以增強API可用性、改進應用程序配置以及防止各種界面中的用戶錯誤,展示其在不同領域的通用性。

什麼是Poka Yoke?

Poka Yoke是一個日語術語,大致翻譯為“防錯”。該術語起源於精益製造,指的是任何幫助機器操作員避免錯誤的機制。

在製造業之外,Poka Yoke 也經常用於消費電子產品。例如,SIM卡由於其不對稱形狀,只能以一種方式插入SIM卡托。

Poka Yoke - Saving Projects with Hyper-Defensive Programming

缺乏Poka Yoke的硬件示例是PS/2端口,它對鍵盤連接器和鼠標連接器具有完全相同的形狀。它們只能通過使用顏色代碼來區分,因此很容易意外地交換連接器並將它們插入錯誤的端口,因為它們都以相同的方式適合。

Poka Yoke - Saving Projects with Hyper-Defensive Programming

除了用於硬件外,Poka Yoke 的概念還可以應用於編程。其理念是使我們代碼的公共接口盡可能易於理解,並在代碼使用不正確時立即引發錯誤。這似乎顯而易見,但實際上我們經常遇到在這方面存在缺陷的代碼。

但是,請注意,Poka Yoke並非旨在防止蓄意濫用。其目標只是防止意外錯誤,而不是保護代碼免受惡意使用。只要有人可以訪問您的代碼,如果他們真的想這樣做,他們總是能夠繞過您的安全措施。

在討論可以採取哪些具體措施來使代碼更防錯之前,重要的是要知道Poka Yoke機制通常可以分為兩類:

  • 錯誤預防
  • 錯誤檢測

錯誤預防技術有助於儘早發現錯誤。它們旨在確保沒有人能夠意外地錯誤地使用我們的代碼,方法是使接口和行為盡可能簡單明了。想想SIM卡的例子,它只能以一種方式插入SIM卡托。

另一方面,錯誤檢測機制存在於我們的代碼之外。它們監控我們的應用程序以查找潛在錯誤並向我們發出警告。一個例子是可以檢測連接到PS/2端口的設備是否為正確類型的軟件,如果不是,則向用戶顯示警告,說明其無法工作的原因。此特定軟件無法防止錯誤,因為連接器在插入時是可互換的,但它可以檢測到錯誤並向我們發出警告,以便可以修復錯誤。

在本文的其餘部分,我們將探討可以用來在我們的應用程序中實現錯誤預防和錯誤檢測的幾種方法。但請記住,此列表只是一個起點。根據您的特定應用程序,可能可以使用其他措施來使您的代碼更防錯。此外,重要的是要記住Poka Yoke的前期成本,並確保它對您的特定項目來說是值得的。根據應用程序的複雜性和大小,某些措施與潛在的錯誤成本相比可能過於昂貴。因此,您和您的團隊需要決定哪些措施最適合您採取。

錯誤預防示例

Poka Yoke - Saving Projects with Hyper-Defensive Programming

(標量)類型聲明

以前在PHP 5中稱為類型提示,類型聲明是在PHP 7中開始防錯函數和方法簽名的一種簡單方法。

通過為函數參數分配特定類型,在調用函數時,混淆參數順序變得更加困難。

例如,讓我們來看一下我們可能想要發送給用戶的此通知:

<?php
class Notification {
    private $userId;
    private $subject;
    private $message;

    public function __construct(
        $userId,
        $subject,
        $message
    ) {
        $this->userId = $userId;
        $this->subject = $subject;
        $this->message = $message;
    }

    public function getUserId()
    {
        return $this->userId;
    }

    public function getSubject()
    {
        return $this->subject;
    }

    public function getMessage()
    {
        return $this->message;
    }
}
登入後複製
登入後複製
登入後複製
登入後複製

如果沒有類型聲明,我們可以輕鬆注入錯誤類型的變量,這可能會破壞我們的應用程序。例如,我們可以假設$userId應該是一個字符串,而它實際上可能必須是一個整數。

如果我們注入了錯誤的類型,則該錯誤可能在應用程序嘗試實際處理Notification之前不會被檢測到。到那時,我們可能會收到一些關於意外類型的難以理解的錯誤消息,但沒有任何內容立即指向我們注入字符串而不是整數的代碼。

因此,通常更有意思的是強制應用程序盡快崩潰,以便在開發過程中儘早發現此類錯誤。

在這種情況下,我們可以簡單地添加一些類型聲明,當我們混淆參數類型時,PHP將立即停止並向我們發出致命錯誤警告:

<?php
declare(strict_types=1);

class Notification {
    private $userId;
    private $subject;
    private $message;

    public function __construct(
        int $userId,
        string $subject,
        string $message
    ) {
        $this->userId = $userId;
        $this->subject = $subject;
        $this->message = $message;
    }

    public function getUserId() : int
    {
        return $this->userId;
    }

    public function getSubject() : string
    {
        return $this->subject;
    }

    public function getMessage() : string
    {
        return $this->message;
    }
}
登入後複製
登入後複製
登入後複製
登入後複製

但是請注意,默認情況下,PHP將嘗試將不正確的參數強制轉換為其預期類型。為了防止這種情況,重要的是我們啟用strict_types,以便在發生錯誤時我們實際上會收到致命錯誤。因此,標量類型聲明不是理想的Poka Yoke形式,但它們是減少錯誤的良好開端。即使禁用了strict_types,它們仍然可以作為參數預期類型的指示。

此外,我們還為方法聲明了返回類型。這些使確定在調用特定函數時可以預期哪種值變得更容易。

明確定義的返回類型也有助於避免在處理返回值時使用大量switch語句,因為如果沒有顯式聲明的返回類型,我們的方法可以返回各種類型。因此,使用我們方法的人必須檢查在特定情況下實際返回了哪種類型。這些switch語句顯然會被遺忘,並導致難以檢測的錯誤。使用返回類型,此類錯誤會大大減少。

值對象

標量類型提示無法輕鬆為我們解決的一個問題是,擁有多個函數參數使得混淆所述參數的順序成為可能。

Poka Yoke - Saving Projects with Hyper-Defensive Programming

當所有參數都具有不同的標量類型時,PHP可以在我們混淆參數順序時向我們發出警告,但在大多數情況下,我們可能有一些參數具有相同的類型。

為了解決這個問題,我們可以像這樣將我們的參數包裝在值對像中:

class UserId {
    private $userId;

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

    public function getValue() : int
    {
        return $this->userId;
    }
}

class Subject {
    private $subject;

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

    public function getValue() : string
    {
        return $this->subject;
    }
}

class Message {
    private $message;

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

    public function getMessage() : string
    {
        return $this->message;
    }
}

class Notification {
    /* ... */

    public function __construct(
        UserId $userId,
        Subject $subject,
        Message $message
    ) {
        $this->userId = $userId;
        $this->subject = $subject;
        $this->message = $message;
    }

    public function getUserId() : UserId { /* ... */ }

    public function getSubject() : Subject { /* ... */ }

    public function getMessage() : Message { /* ... */ }
}
登入後複製
登入後複製
登入後複製
登入後複製

因為我們的參數現在每個都有一個非常具體的類型,所以幾乎不可能將它們混淆。

使用值對象而不是標量類型聲明的另一個優點是,我們不再需要在每個文件中啟用strict_types。如果我們不必記住它,我們就不可能意外忘記它。

驗證

Poka Yoke - Saving Projects with Hyper-Defensive Programming

在使用值對象時,我們可以將它們的數據驗證邏輯封裝在對象本身中。這樣做,我們可以防止創建具有無效狀態的值對象,這可能會在應用程序的其他層中導致問題。

例如,我們可能有一條規則規定任何給定的UserId都應該始終為正數。

我們顯然可以在每次獲得UserId作為輸入時驗證此規則,但另一方面,它也可能很容易在一個地方或另一個地方被遺忘。

即使這個錯誤會導致應用程序的另一層出現實際錯誤,錯誤消息也可能無法清楚地說明實際發生了什麼錯誤,並且難以調試。

為了防止此類錯誤,我們可以向UserId構造函數添加一些驗證:

<?php
class Notification {
    private $userId;
    private $subject;
    private $message;

    public function __construct(
        $userId,
        $subject,
        $message
    ) {
        $this->userId = $userId;
        $this->subject = $subject;
        $this->message = $message;
    }

    public function getUserId()
    {
        return $this->userId;
    }

    public function getSubject()
    {
        return $this->subject;
    }

    public function getMessage()
    {
        return $this->message;
    }
}
登入後複製
登入後複製
登入後複製
登入後複製

這樣,我們可以始終確保在使用UserId對象時,它具有有效狀態。這使我們不必在應用程序的各個層中不斷重新驗證我們的數據。

請注意,我們可以添加標量類型聲明而不是使用is_int,但這將迫使我們在使用UserId的每個地方啟用strict_types。

如果我們不啟用strict_types,PHP會在將其他類型傳遞給UserId時自動嘗試將其強制轉換為int。這可能會出現問題,例如,我們可能會注入一個浮點數,這實際上可能是一個不正確的變量,因為用戶 ID 通常不是浮點數。

在其他情況下,例如當我們可能正在使用Price值對象時,禁用strict_types可能會導致舍入錯誤,因為PHP會自動將浮點變量轉換為int。

不可變性

默認情況下,對像在PHP中按引用傳遞。這意味著當我們對對象進行更改時,它會立即在整個應用程序中發生更改。

Poka Yoke - Saving Projects with Hyper-Defensive Programming

雖然這種方法有其優點,但它也有一些缺點。讓我們來看一下通過短信和電子郵件向用戶發送通知的示例:

<?php
declare(strict_types=1);

class Notification {
    private $userId;
    private $subject;
    private $message;

    public function __construct(
        int $userId,
        string $subject,
        string $message
    ) {
        $this->userId = $userId;
        $this->subject = $subject;
        $this->message = $message;
    }

    public function getUserId() : int
    {
        return $this->userId;
    }

    public function getSubject() : string
    {
        return $this->subject;
    }

    public function getMessage() : string
    {
        return $this->message;
    }
}
登入後複製
登入後複製
登入後複製
登入後複製

由於Notification對象按引用傳遞,因此我們導致了意外的副作用。通過在SMSNotificationSender中縮短消息的長度,引用的Notification對像在整個應用程序中都進行了更新,這意味著在稍後由EmailNotificationSender發送時,它也被縮短了。

為了解決這個問題,我們可以使我們的Notification對像不可變。我們可以添加一些with方法來創建原始Notification的副本,然後再應用更改,而不是提供set方法來對其進行更改:

class UserId {
    private $userId;

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

    public function getValue() : int
    {
        return $this->userId;
    }
}

class Subject {
    private $subject;

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

    public function getValue() : string
    {
        return $this->subject;
    }
}

class Message {
    private $message;

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

    public function getMessage() : string
    {
        return $this->message;
    }
}

class Notification {
    /* ... */

    public function __construct(
        UserId $userId,
        Subject $subject,
        Message $message
    ) {
        $this->userId = $userId;
        $this->subject = $subject;
        $this->message = $message;
    }

    public function getUserId() : UserId { /* ... */ }

    public function getSubject() : Subject { /* ... */ }

    public function getMessage() : Message { /* ... */ }
}
登入後複製
登入後複製
登入後複製
登入後複製

這樣,每當我們通過例如縮短消息長度來更改Notification類時,更改不再在整個應用程序中傳播,從而防止任何意外的副作用。

但是請注意,在PHP中很難(如果不是不可能的話)使對象真正不可變。但是為了使我們的代碼更防錯,如果我們添加“不可變”with方法而不是set方法,這已經很有幫助了,因為類的用戶不再需要記住在進行更改之前自己克隆對象。

返回空對象

有時我們可能有可以返回某些值或null的函數或方法。這些可為空的返回值可能會造成問題,因為在我們可以使用它們之前,它們幾乎總是需要檢查它們是否為空。同樣,這是我們很容易忘記的事情。為了避免總是必須檢查返回值,我們可以改為返回空對象。

Poka Yoke - Saving Projects with Hyper-Defensive Programming

例如,我們可以有一個ShoppingCart,其中應用了折扣或沒有應用折扣:

<?php
class Notification {
    private $userId;
    private $subject;
    private $message;

    public function __construct(
        $userId,
        $subject,
        $message
    ) {
        $this->userId = $userId;
        $this->subject = $subject;
        $this->message = $message;
    }

    public function getUserId()
    {
        return $this->userId;
    }

    public function getSubject()
    {
        return $this->subject;
    }

    public function getMessage()
    {
        return $this->message;
    }
}
登入後複製
登入後複製
登入後複製
登入後複製

在計算ShoppingCart的最終價格時,我們現在總是必須檢查getDiscount()是否返回null或實際的Discount,然後才能調用applyTo方法:

<?php
declare(strict_types=1);

class Notification {
    private $userId;
    private $subject;
    private $message;

    public function __construct(
        int $userId,
        string $subject,
        string $message
    ) {
        $this->userId = $userId;
        $this->subject = $subject;
        $this->message = $message;
    }

    public function getUserId() : int
    {
        return $this->userId;
    }

    public function getSubject() : string
    {
        return $this->subject;
    }

    public function getMessage() : string
    {
        return $this->message;
    }
}
登入後複製
登入後複製
登入後複製
登入後複製

如果我們沒有進行此檢查,則當getDiscount()返回null時,我們可能會收到PHP警告和/或其他意外影響。

另一方面,如果我們在未設置Discount時返回空對象,則可以完全刪除這些檢查:

class UserId {
    private $userId;

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

    public function getValue() : int
    {
        return $this->userId;
    }
}

class Subject {
    private $subject;

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

    public function getValue() : string
    {
        return $this->subject;
    }
}

class Message {
    private $message;

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

    public function getMessage() : string
    {
        return $this->message;
    }
}

class Notification {
    /* ... */

    public function __construct(
        UserId $userId,
        Subject $subject,
        Message $message
    ) {
        $this->userId = $userId;
        $this->subject = $subject;
        $this->message = $message;
    }

    public function getUserId() : UserId { /* ... */ }

    public function getSubject() : Subject { /* ... */ }

    public function getMessage() : Message { /* ... */ }
}
登入後複製
登入後複製
登入後複製
登入後複製

現在,當我們調用getDiscount()時,即使沒有可用的折扣,我們也會始終獲得Discount對象。這樣,我們可以將折扣應用於我們的總計,即使沒有折扣,我們也不再需要if語句:

class UserId {
    private $userId;

    public function __construct($userId) {
        if (!is_int($userId) || $userId <= 0) {
            throw new \InvalidArgumentException(
                'UserId should be a positive integer.'
            );
        }

        $this->userId = $userId;
    }

    public function getValue() : int
    {
        return $this->userId;
    }
}
登入後複製

可選依賴項

出於我們想要避免可為空的返回值的原因,我們可能想要避免可選依賴項,而只是使我們所有的依賴項都成為必需的。

例如,以下類:

interface NotificationSenderInterface
{
    public function send(Notification $notification);
}

class SMSNotificationSender implements NotificationSenderInterface
{
    public function send(Notification $notification) {
        $this->cutNotificationLength($notification);

        // 发送短信...
    }

    /**
     * 确保通知不超过短信长度。
     */
    private function cutNotificationLength(Notification $notification)
    {
        $message = $notification->getMessage();
        $messageString = substr($message->getValue(), 0, 160); // 修正截取长度
        $notification->setMessage(new Message($messageString));
    }
}

class EmailNotificationSender implements NotificationSenderInterface
{
    public function send(Notification $notification) {
        // 发送电子邮件...
    }
}

$smsNotificationSender = new SMSNotificationSender();
$emailNotificationSender = new EmailNotificationSender();

$notification = new Notification(
    new UserId(17466),
    new Subject('Demo notification'),
    new Message('Very long message ... over 160 characters.')
);

$smsNotificationSender->send($notification);
$emailNotificationSender->send($notification);
登入後複製

這種方法有兩個問題:

  1. 我們必須不斷檢查doSomething()方法中是否存在記錄器。
  2. 在服務容器中設置SomeService類時,有人可能會忘記實際設置記錄器,或者他們甚至可能不知道該類可以選擇設置記錄器。

我們可以通過使LoggerInterface成為必需的依賴項來簡化此問題:

class Notification {
    public function __construct( ... ) { /* ... */ }

    public function getUserId() : UserId { /* ... */ }

    public function withUserId(UserId $userId) : Notification {
        return new Notification($userId, $this->subject, $this->message); // 使用新的Notification实例
    }

    public function getSubject() : Subject { /* ... */ }

    public function withSubject(Subject $subject) : Notification {
        return new Notification($this->userId, $subject, $this->message); // 使用新的Notification实例
    }

    public function getMessage() : Message { /* ... */ }

    public function withMessage(Message $message) : Notification {
        return new Notification($this->userId, $this->subject, $message); // 使用新的Notification实例
    }
}
登入後複製

這樣,我們的公共接口變得不那麼混亂,並且每當有人創建SomeService的新實例時,他們都知道該類需要LoggerInterface的實例,因此他們不會忘記註入一個實例。

此外,我們還省略了if語句來檢查是否注入了記錄器,這使得我們的doSomething()更容易閱讀,並且在有人對其進行更改時,減少了出錯的可能性。

如果在某些時候我們想在沒有記錄器的情況下使用SomeService,我們可以應用與返回語句相同的邏輯,而只是使用空對象:

<?php
class Notification {
    private $userId;
    private $subject;
    private $message;

    public function __construct(
        $userId,
        $subject,
        $message
    ) {
        $this->userId = $userId;
        $this->subject = $subject;
        $this->message = $message;
    }

    public function getUserId()
    {
        return $this->userId;
    }

    public function getSubject()
    {
        return $this->subject;
    }

    public function getMessage()
    {
        return $this->message;
    }
}
登入後複製
登入後複製
登入後複製
登入後複製

最終,這與使用可選的setLogger()方法具有相同的效果,但它使我們的代碼更容易遵循,並減少了依賴注入容器中出錯的可能性。

公共接口

為了使我們的代碼更容易使用,最好將類上的公共方法數量保持在最低限度。這樣,我們的代碼使用方法就變得不那麼混亂,我們維護的代碼也更少,並且在重構時破壞向後兼容性的可能性也更小。

Poka Yoke - Saving Projects with Hyper-Defensive Programming

為了將公共方法保持在最低限度,可以將公共方法視為事務。

例如,在兩個銀行賬戶之間轉賬:

<?php
declare(strict_types=1);

class Notification {
    private $userId;
    private $subject;
    private $message;

    public function __construct(
        int $userId,
        string $subject,
        string $message
    ) {
        $this->userId = $userId;
        $this->subject = $subject;
        $this->message = $message;
    }

    public function getUserId() : int
    {
        return $this->userId;
    }

    public function getSubject() : string
    {
        return $this->subject;
    }

    public function getMessage() : string
    {
        return $this->message;
    }
}
登入後複製
登入後複製
登入後複製
登入後複製

雖然底層數據庫可以提供事務以確保如果存款無法進行,則不會提取任何資金,反之亦然,但數據庫無法阻止我們忘記調用$account1->withdraw()或$account2->deposit(),這將導致餘額不正確。

幸運的是,我們可以通過用單個事務方法替換我們兩個單獨的方法來輕鬆解決此問題:

class UserId {
    private $userId;

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

    public function getValue() : int
    {
        return $this->userId;
    }
}

class Subject {
    private $subject;

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

    public function getValue() : string
    {
        return $this->subject;
    }
}

class Message {
    private $message;

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

    public function getMessage() : string
    {
        return $this->message;
    }
}

class Notification {
    /* ... */

    public function __construct(
        UserId $userId,
        Subject $subject,
        Message $message
    ) {
        $this->userId = $userId;
        $this->subject = $subject;
        $this->message = $message;
    }

    public function getUserId() : UserId { /* ... */ }

    public function getSubject() : Subject { /* ... */ }

    public function getMessage() : Message { /* ... */ }
}
登入後複製
登入後複製
登入後複製
登入後複製

結果,我們的代碼變得更健壯,因為只部分完成事務更容易出錯。

錯誤檢測示例

與錯誤預防機制相反,錯誤檢測機制並非旨在防止錯誤。相反,它們旨在在我們檢測到問題時向我們發出警告。

它們大多數時候存在於我們的應用程序之外,並定期運行以監控我們的代碼或對其進行的特定更改。

單元測試

單元測試可以確保新代碼正常工作,但它也可以幫助確保在有人重構系統的一部分時,現有代碼仍然按預期工作。

因為有人仍然可能忘記實際運行我們的單元測試,所以建議使用Travis CI和Gitlab CI等服務在進行更改時自動運行它們。這樣,開發人員會在發生重大更改時自動收到通知,這也有助於我們在審查拉取請求時確保更改按預期工作。

除了錯誤檢測之外,單元測試也是提供特定代碼部分預期工作方式示例的好方法,這反過來可以防止其他人使用我們的代碼時出錯。

代碼覆蓋率報告和變異測試

因為我們總是可能忘記編寫足夠的測試,所以使用Coveralls等服務在運行單元測試時自動生成代碼覆蓋率報告可能是有益的。每當我們的代碼覆蓋率下降時,Coveralls都會向我們發送通知,以便我們可以添加一些單元測試,我們還可以了解我們的代碼覆蓋率如何隨著時間的推移而變化。

Poka Yoke - Saving Projects with Hyper-Defensive Programming

確保我們為代碼編寫了足夠的單元測試的另一種更好方法是設置一些變異測試,例如使用Humbug。顧名思義,這些測試旨在通過稍微更改我們的源代碼、之後運行我們的單元測試並確保相關的測試由於變異而開始失敗來驗證我們是否具有足夠的代碼覆蓋率。

使用代碼覆蓋率報告和變異測試,我們可以確保我們的單元測試涵蓋足夠的代碼以防止意外錯誤或錯誤。

代碼分析器

代碼分析器可以在開發過程的早期檢測到應用程序中的錯誤。例如,PHPStorm等IDE使用代碼分析器來警告我們錯誤,並在我們編寫代碼時給出建議。這些範圍從簡單的語法錯誤到重複代碼的檢測。

除了大多數IDE中內置的分析器之外,還可以將第三方甚至自定義分析器合併到應用程序的構建過程中,以發現特定問題。可以在exakat/php-static-analysis-tools中找到適合PHP項目的分析器的非詳盡列表,範圍從編碼標準分析器到檢查安全漏洞的分析器。

也存在在線解決方案,例如SensioLabs Insights。

日誌消息

與大多數其他錯誤檢測機制相反,日誌消息可以幫助我們在應用程序在生產環境中實時運行時檢測應用程序中的錯誤。

Poka Yoke - Saving Projects with Hyper-Defensive Programming

當然,首先需要我們的代碼在發生意外情況時實際記錄消息。即使我們的代碼支持記錄器,在設置所有內容時也可能很容易忘記它們。因此,我們應該盡量避免可選依賴項(見上文)。

雖然大多數應用程序至少會記錄一些消息,但當使用Kibana或Nagios等工具主動分析和監控它們時,它們提供的信息才會變得真正有趣。此類工具可以讓我們深入了解用戶積極使用應用程序時(而不是在內部測試時)應用程序中發生的錯誤和警告。我們有一篇關於使用此ELK堆棧監控PHP應用程序的優秀文章。

不要抑制錯誤

即使主動記錄錯誤消息,也經常會抑制某些錯誤。每當發生“可恢復”錯誤時,PHP往往會繼續運行,就好像它想通過保持應用程序運行來幫助我們一樣。但是,錯誤在開發或測試新功能時通常非常有用,因為它們通常表明我們代碼中的錯誤。

這就是為什麼大多數代碼分析器會在檢測到您使用@抑制錯誤時向您發出警告的原因,因為它可以隱藏一旦訪客實際使用該應用程序就會不可避免地再次出現的錯誤。

通常,最好將PHP的error_reporting級別設置為E_ALL,以便即使是最輕微的警告也會被報告。但是,請確保將這些消息記錄在某個地方並將其隱藏在用戶面前,這樣就不會將有關應用程序架構或潛在安全漏洞的敏感信息暴露給最終用戶。

除了error_reporting配置之外,還務必始終啟用strict_types,以便PHP不會嘗試自動將函數參數強制轉換為其預期類型,因為這在從一種類型轉換為另一種類型時(例如,從float轉換為int時的捨入錯誤)通常會導致難以檢測的錯誤。

PHP之外的用法

由於Poka Yoke更多的是一個概念,而不是一個特定的技術,因此它也可以應用於PHP之外(但與PHP相關)的領域。

基礎設施

在基礎設施層面,可以使用Vagrant等工具共享與生產環境相同的開發設置,可以防止許多錯誤。

使用Jenkins和GoCD等構建服務器自動化部署過程也可以幫助防止在將更改部署到我們的應用程序時出錯,因為這通常可能包括取決於應用程序的大量必需步驟,這些步驟很容易被遺忘。

REST API

在構建REST API時,我們可以結合使用Poka Yoke來使我們的API更容易使用。例如,我們可以確保在URL查詢或請求正文中傳遞未知參數時始終返回錯誤。這可能看起來很奇怪,因為我們顯然想要避免“破壞”我們的API客戶端,但通常最好盡快警告使用我們API的開發人員關於不正確的用法,以便可以在開發過程的早期修復錯誤。

例如,我們的API上可能有一個color參數,但使用我們的API的某人可能會意外地使用colour參數。如果沒有任何警告,這個錯誤很容易進入生產環境,直到最終用戶由於意外行為而注意到它為止。要了解如何構建以後不會讓您失望的API,一本好書可能會有所幫助。

應用程序配置

實際上所有應用程序都依賴於至少一些自定義配置。通常情況下,開發人員喜歡為配置提供盡可能多的默認值,因此配置應用程序的工作量更少。

但是,就像上面的color和colour示例一樣,很容易將配置參數輸入錯誤,這會導致我們的應用程序意外地回退到默認值。當應用程序沒有引發錯誤時,很難追踪到這類錯誤,而引發錯誤以進行不正確配置的最佳方法是根本不提供任何默認值,並在缺少配置參數時立即引發錯誤。

防止用戶出錯

Poka Yoke - Saving Projects with Hyper-Defensive Programming

Poka Yoke概念也可以應用於防止或檢測用戶錯誤。例如,在支付軟件中,可以使用校驗位算法驗證用戶輸入的帳號。這可以防止用戶意外輸入帶有錯字的帳號。

結論

雖然Poka Yoke更多的是一個概念,而不是一組特定的工具,但我們可以將各種原則應用於我們的代碼和開發過程,以確保儘早預防或檢測錯誤。通常情況下,這些機制將特定於應用程序本身及其業務邏輯,但我們可以使用一些簡單的技術和工具來使任何代碼更防錯。

可能最重要的一點是,雖然我們顯然想要避免生產環境中的錯誤,但它們在開發過程中非常有用,我們不應該害怕盡快引發它們,以便更容易追踪錯誤。這些錯誤可以由代碼本身引發,也可以由與我們的應用程序分開運行並從外部監控它的單獨進程引發。

為了進一步減少錯誤,我們應該努力使代碼的公共接口盡可能簡單明了。

如果您還有其他關於如何將Poka Yoke應用於PHP開發或一般編程的技巧,請隨時在評論中分享!

進一步閱讀

Poka Yoke

  • Poka-yoke – Toyota Production System guide 描述了Poka Yoke在豐田製造過程中的作用。
  • How to Use Poka-Yoke Technique to Improve Software Quality 提供了有關如何使用Poka Yoka提高軟件功能質量的技巧。
  • Poka-Yoke Your Code 簡要概述瞭如何將Poka Yoke應用於一般編程。
  • POKA YOKE – Applying Mistake Proofing to Software 更詳細地概述瞭如何將Poka Yoke應用於編程。

PHP中的Poka Yoke

  • Extremely Defensive PHP 討論瞭如何使您的PHP代碼更防錯。
  • 3 benefits of using Immutable Objects 簡要概述了不可變對象的優點。
  • Immutable value objects in PHP 簡要概述了我們如何實際使值對像不可變(或至少盡可能不可變)。
  • PHP and immutability 更深入地探討了不可變性在PHP中的工作方式(以及不工作的方式)。
  • Writing good code: how to reduce the cognitive load of your code 描述了使代碼更容易遵循的各種方法,從而減少了有人在使用代碼或對其進行更改時出錯的可能性。

關於Poka-Yoke和超防禦式編程的常見問題

Poka-Yoke在編程中的主要目的是什麼?

Poka-Yoke是一個日語術語,翻譯為“防錯”。在編程的上下文中,它是一種防禦性設計方法,旨在防止錯誤發生。它涉及實施安全措施,以幫助避免錯誤並確保正確使用函數。 Poka-Yoke在編程中的主要目的是提高軟件質量並減少錯誤,從而節省開發過程中的時間和資源。

Poka-Yoke與傳統的編程方法有何不同?

傳統的編程方法通常側重於創建功能性代碼,錯誤處理和錯誤修復通常在初始開發之後進行。另一方面,Poka-Yoke採取主動方法,在開發階段本身就結合了錯誤預防機制。這導致更強大、更可靠的代碼,減少了以後需要進行大量調試和測試的需求。

Poka-Yoke可以應用於任何編程語言嗎?

是的,Poka-Yoke是一個可以應用於任何編程語言的概念。它不是一個特定的工具或技術,而是一種編程思維方式或方法。無論您使用哪種語言,都可以實施Poka-Yoke原則,以使您的代碼更耐錯。

Poka-Yoke在編程中的一些示例是什麼?

Poka-Yoke在編程中的示例包括輸入驗證(確保數據在處理之前格式正確)、使用斷言來檢查程序在某些點的狀態以及實現故障安全默認值(在發生故障時最大限度地減少損害的默認操作)。

Poka-Yoke如何促進軟件產品的整體質量?

通過在開發階段防止錯誤,Poka-Yoke有助於提高軟件產品的整體質量。它減少了錯誤和缺陷的數量,從而產生了更穩定、更可靠的軟件。這不僅改善了用戶體驗,還降低了調試和維護的成本和時間。

Poka-Yoke是一個耗時的過程嗎?

雖然實施Poka-Yoke可能需要在開發階段額外花費一些時間,但從長遠來看,它可以節省大量時間。通過在錯誤發生之前防止錯誤,它減少了花費在調試和修復錯誤上的時間,從而縮短了交付時間並提高了開發效率。

Poka-Yoke在製造業中的好處是什麼?

在製造業中,Poka-Yoke可以幫助防止錯誤和缺陷,從而生產出更高質量的產品。它還可以通過減少返工和維修的時間和資源來提高效率和生產力。

我如何開始在我的編程實踐中實施Poka-Yoke?

要開始在您的編程實踐中實施Poka-Yoke,首先要確定代碼中常見的錯誤或潛在的故障點。然後,制定策略來防止這些錯誤,或者在發生這些錯誤時優雅地處理它們。這可能包括輸入驗證、斷言、故障安全默認值或其他錯誤預防技術。

Poka-Yoke可以用於敏捷開發嗎?

是的,Poka-Yoke可以有效地用於敏捷開發。事實上,它與敏捷原則(頻繁交付可運行的軟件)非常吻合,因為它有助於確保軟件的每次迭代都盡可能地沒有錯誤。

Poka-Yoke只對大型項目有用嗎?

不,Poka-Yoke對任何規模的項目都有益。即使對於小型項目,在開發階段防止錯誤也可以節省時間和資源,並產生更高質量的最終產品。

以上是Poka oke-通過超級防禦性編程保存項目的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板