首页 >后端开发 >php教程 >Poka oke-通过超级防御性编程保存项目

Poka oke-通过超级防御性编程保存项目

尊渡假赌尊渡假赌尊渡假赌
尊渡假赌尊渡假赌尊渡假赌原创
2025-02-09 11:13:12249浏览

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中开始防错函数和方法签名的一种简单方法。

通过为函数参数分配特定类型,在调用函数时,混淆参数顺序变得更加困难。

例如,让我们来看一下我们可能想要发送给用户的此通知:

<code class="language-php"><?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;
    }
}
</code>

如果没有类型声明,我们可以轻松注入错误类型的变量,这可能会破坏我们的应用程序。例如,我们可以假设$userId应该是一个字符串,而它实际上可能必须是一个整数。

如果我们注入了错误的类型,则该错误可能在应用程序尝试实际处理Notification之前不会被检测到。到那时,我们可能会收到一些关于意外类型的难以理解的错误消息,但没有任何内容立即指向我们注入字符串而不是整数的代码。

因此,通常更有意思的是强制应用程序尽快崩溃,以便在开发过程中尽早发现此类错误。

在这种情况下,我们可以简单地添加一些类型声明,当我们混淆参数类型时,PHP将立即停止并向我们发出致命错误警告:

<code class="language-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;
    }
}
</code>

但是请注意,默认情况下,PHP将尝试将不正确的参数强制转换为其预期类型。为了防止这种情况,重要的是我们启用strict_types,以便在发生错误时我们实际上会收到致命错误。因此,标量类型声明不是理想的Poka Yoke形式,但它们是减少错误的良好开端。即使禁用了strict_types,它们仍然可以作为参数预期类型的指示。

此外,我们还为方法声明了返回类型。这些使确定在调用特定函数时可以预期哪种值变得更容易。

明确定义的返回类型也有助于避免在处理返回值时使用大量switch语句,因为如果没有显式声明的返回类型,我们的方法可以返回各种类型。因此,使用我们方法的人必须检查在特定情况下实际返回了哪种类型。这些switch语句显然会被遗忘,并导致难以检测的错误。使用返回类型,此类错误会大大减少。

值对象

标量类型提示无法轻松为我们解决的一个问题是,拥有多个函数参数使得混淆所述参数的顺序成为可能。

Poka Yoke - Saving Projects with Hyper-Defensive Programming

当所有参数都具有不同的标量类型时,PHP可以在我们混淆参数顺序时向我们发出警告,但在大多数情况下,我们可能有一些参数具有相同的类型。

为了解决这个问题,我们可以像这样将我们的参数包装在值对象中:

<code class="language-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 { /* ... */ }
}
</code>

因为我们的参数现在每个都有一个非常具体的类型,所以几乎不可能将它们混淆。

使用值对象而不是标量类型声明的另一个优点是,我们不再需要在每个文件中启用strict_types。如果我们不必记住它,我们就不可能意外忘记它。

验证

Poka Yoke - Saving Projects with Hyper-Defensive Programming

在使用值对象时,我们可以将它们的数据验证逻辑封装在对象本身中。这样做,我们可以防止创建具有无效状态的值对象,这可能会在应用程序的其他层中导致问题。

例如,我们可能有一条规则规定任何给定的UserId都应该始终为正数。

我们显然可以在每次获得UserId作为输入时验证此规则,但另一方面,它也可能很容易在一个地方或另一个地方被遗忘。

即使这个错误会导致应用程序的另一层出现实际错误,错误消息也可能无法清楚地说明实际发生了什么错误,并且难以调试。

为了防止此类错误,我们可以向UserId构造函数添加一些验证:

<code class="language-php"><?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;
    }
}
</code>

这样,我们可以始终确保在使用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

虽然这种方法有其优点,但它也有一些缺点。让我们来看一下通过短信和电子邮件向用户发送通知的示例:

<code class="language-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;
    }
}
</code>

由于Notification对象按引用传递,因此我们导致了意外的副作用。通过在SMSNotificationSender中缩短消息的长度,引用的Notification对象在整个应用程序中都进行了更新,这意味着在稍后由EmailNotificationSender发送时,它也被缩短了。

为了解决这个问题,我们可以使我们的Notification对象不可变。我们可以添加一些with方法来创建原始Notification的副本,然后再应用更改,而不是提供set方法来对其进行更改:

<code class="language-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 { /* ... */ }
}
</code>

这样,每当我们通过例如缩短消息长度来更改Notification类时,更改不再在整个应用程序中传播,从而防止任何意外的副作用。

但是请注意,在PHP中很难(如果不是不可能的话)使对象真正不可变。但是为了使我们的代码更防错,如果我们添加“不可变”with方法而不是set方法,这已经很有帮助了,因为类的用户不再需要记住在进行更改之前自己克隆对象。

返回空对象

有时我们可能有可以返回某些值或null的函数或方法。这些可为空的返回值可能会造成问题,因为在我们可以使用它们之前,它们几乎总是需要检查它们是否为空。同样,这是我们很容易忘记的事情。为了避免总是必须检查返回值,我们可以改为返回空对象。

Poka Yoke - Saving Projects with Hyper-Defensive Programming

例如,我们可以有一个ShoppingCart,其中应用了折扣或没有应用折扣:

<code class="language-php"><?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;
    }
}
</code>

在计算ShoppingCart的最终价格时,我们现在总是必须检查getDiscount()是否返回null或实际的Discount,然后才能调用applyTo方法:

<code class="language-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;
    }
}
</code>

如果我们没有进行此检查,则当getDiscount()返回null时,我们可能会收到PHP警告和/或其他意外影响。

另一方面,如果我们在未设置Discount时返回空对象,则可以完全删除这些检查:

<code class="language-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 { /* ... */ }
}
</code>

现在,当我们调用getDiscount()时,即使没有可用的折扣,我们也会始终获得Discount对象。这样,我们可以将折扣应用于我们的总计,即使没有折扣,我们也不再需要if语句:

<code class="language-php">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;
    }
}
</code>

可选依赖项

出于我们想要避免可为空的返回值的原因,我们可能想要避免可选依赖项,而只是使我们所有的依赖项都成为必需的。

例如,以下类:

<code class="language-php">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);
</code>

这种方法有两个问题:

  1. 我们必须不断检查doSomething()方法中是否存在记录器。
  2. 在服务容器中设置SomeService类时,有人可能会忘记实际设置记录器,或者他们甚至可能不知道该类可以选择设置记录器。

我们可以通过使LoggerInterface成为必需的依赖项来简化此问题:

<code class="language-php">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实例
    }
}</code>

这样,我们的公共接口变得不那么混乱,并且每当有人创建SomeService的新实例时,他们都知道该类需要LoggerInterface的实例,因此他们不会忘记注入一个实例。

此外,我们还省略了if语句来检查是否注入了记录器,这使得我们的doSomething()更容易阅读,并且在有人对其进行更改时,减少了出错的可能性。

如果在某些时候我们想在没有记录器的情况下使用SomeService,我们可以应用与返回语句相同的逻辑,而只是使用空对象:

<code class="language-php"><?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;
    }
}
</code>

最终,这与使用可选的setLogger()方法具有相同的效果,但它使我们的代码更容易遵循,并减少了依赖注入容器中出错的可能性。

公共接口

为了使我们的代码更容易使用,最好将类上的公共方法数量保持在最低限度。这样,我们的代码使用方法就变得不那么混乱,我们维护的代码也更少,并且在重构时破坏向后兼容性的可能性也更小。

Poka Yoke - Saving Projects with Hyper-Defensive Programming

为了将公共方法保持在最低限度,可以将公共方法视为事务。

例如,在两个银行账户之间转账:

<code class="language-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;
    }
}
</code>

虽然底层数据库可以提供事务以确保如果存款无法进行,则不会提取任何资金,反之亦然,但数据库无法阻止我们忘记调用$account1->withdraw()或$account2->deposit(),这将导致余额不正确。

幸运的是,我们可以通过用单个事务方法替换我们两个单独的方法来轻松解决此问题:

<code class="language-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 { /* ... */ }
}
</code>

结果,我们的代码变得更健壮,因为只部分完成事务更容易出错。

错误检测示例

与错误预防机制相反,错误检测机制并非旨在防止错误。相反,它们旨在在我们检测到问题时向我们发出警告。

它们大多数时候存在于我们的应用程序之外,并定期运行以监控我们的代码或对其进行的特定更改。

单元测试

单元测试可以确保新代码正常工作,但它也可以帮助确保在有人重构系统的一部分时,现有代码仍然按预期工作。

因为有人仍然可能忘记实际运行我们的单元测试,所以建议使用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