This article was peer reviewed by Deji Akala and Marco Pivetta. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!
本文由Deji Akala和Marco Pivetta进行同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!
When working in a medium to large team on the same codebase, it can sometimes become hard to understand each other’s code and how to use it. Various solutions exist to help with this. For example, you can agree to follow a set of coding standards to make the code more readable for each other, or use a framework that’s known to all (we’ve got a great Laravel intro premium course here).
在同一代码库的中型到大型团队中工作时,有时可能很难理解彼此的代码以及如何使用它们。 存在各种解决方案来帮助这一点。 例如,您可以同意遵循一组编码标准,以使代码彼此之间更具可读性,或者使用众所周知的框架(我们在这里提供了出色的Laravel入门高级课程)。
However, this is often not enough, especially when someone has to dig back into a part of the application written some time ago to fix a bug or add a new feature. It can be quite hard to remember how particular classes were intended to work, both on their own and in combination with each other. At that point, it becomes easy to accidentally introduce side effects or bugs without realizing it.
但是,这通常是不够的,尤其是当有人不得不追溯到前一段时间编写的应用程序的一部分以修复错误或添加新功能时。 很难记住特定类是如何单独工作或相互结合工作的。 到那时,很容易意外地引入副作用或错误而没有意识到。
These mistakes might get caught in quality assurance, but there’s a realistic chance they might slip through. And even if they get caught, it can take a lot of time to send the code back and get it fixed.
这些错误可能会陷入质量保证中 ,但实际上可能会漏掉。 即使它们被捕获,也可能需要大量时间才能将代码发回并修复。
So how can we prevent this? Enter “Poka Yoke”.
那么如何防止这种情况呢? 输入“ Poka Yoke” 。
Poka Yoke is a Japanese term which roughly translates to “mistake-proofing”. The term originated in lean manufacturing, where it refers to any mechanism that helps a machine operator avoid mistakes.
Poka Yoke是日语术语,大致翻译为“防错”。 该术语起源于精益生产,在精益生产中是指有助于机器操作员避免错误的任何机制。
Outside of manufacturing, Poka Yoke is also often used in consumer electronics. Take, for example, a SIM card, which can only fit one way a sim tray because of its asymmetrical shape.
在制造之外,Poka Yoke还经常用于消费类电子产品。 以SIM卡为例,由于其形状不对称,因此只能以一种方式放入SIM卡托。
An example of hardware lacking in Poka Yoke is the PS/2 port, which has exactly the same shape for a keyboard connector and a mouse connector. They can only be differentiated by using color codes, so it is easy to accidentally switch the connectors and insert them in the wrong ports because they both fit the same way.
PS / 2端口是Poka Yoke中缺少的硬件的一个示例,它的键盘连接器和鼠标连接器的形状完全相同。 只能通过使用颜色代码来区分它们,因此很容易意外切换连接器并将它们插入错误的端口,因为它们的安装方式相同。
Besides being used in hardware, the concepts of Poka Yoke can also be applied to programming. The idea is to make the public interfaces of our code as easy as possible to understand, and to raise errors as soon as our code is being used incorrectly. This might seem obvious, but in reality we often come across code that is lacking in these regards.
除了用于硬件之外,Poka Yoke的概念还可以应用于编程。 我们的想法是使我们的代码的公共接口尽可能容易理解,并在我们的代码使用不当时立即引发错误。 这看起来似乎很明显,但实际上,我们经常遇到这些方面缺乏的代码。
Note however, that Poka Yoke is not meant to prevent intentional abuse. The goal is only to prevent accidental mistakes, not to secure your code against malicious usage. As long as someone has access to your code, they will always be able to get around your safeguards if they really want to.
但是请注意,Poka Yoke并非旨在防止故意滥用。 目的只是为了防止意外错误,而不是为了防止恶意使用而保护您的代码。 只要某人有权访问您的代码,他们只要愿意就始终可以绕过您的保护措施。
Before discussing what specific measures you can take to make your code more mistake-proof, it is important to know that Poka Yoke mechanisms can generally be divided into two categories:
在讨论可以采取什么具体措施使代码更防错之前,必须了解Poka Yoke机制通常可以分为两类:
Mistake prevention 预防错误 Mistake detection 错误检测Mistake prevention techniques are helpful for catching mistakes early on. They are meant to make sure no one can accidentally use our code incorrectly, by making the interfaces and behavior as straightforward as possible. Think of the example of the SIM card, which can only fit one way in a SIM tray.
预防错误技术有助于及早发现错误。 它们旨在通过使界面和行为尽可能简单明了来确保没有人会意外地错误使用我们的代码。 考虑一下SIM卡的示例,它只能以一种方式插入SIM托盘。
Mistake detection mechanisms, on the other hand, live outside of our code. They monitor our applications to watch for potential mistakes and warn us about them. An example can be software that detects whether a device connected to a PS/2 port is of the correct type, and if not show a warning to the user clarifying why it doesn’t work. This particular software could not prevent a mistake, because the connectors are interchangeable when plugging them in, but it can detect one and warn us about it so the mistake can be fixed.
另一方面,错误检测机制仍然存在于我们的代码之外。 他们监视我们的应用程序,以注意潜在的错误并向我们发出警告。 一个示例可以是检测连接到PS / 2端口的设备是否为正确类型的软件,如果检测不到,则向用户显示警告,以阐明为什么它不起作用。 这个特定的软件无法防止错误,因为插入时连接器是可互换的,但是它可以检测到一个并向我们发出警告,因此可以修复错误。
In the rest of this article we will explore several methods we can use to implement both mistake prevention and mistake detection in our applications. But keep in mind that this list is just a starting point. Depending on your specific application, additional measures might be possible to make your code more mistake-proof. Also, it’s important to keep the upfront cost of Poka Yoke in mind and make sure it’s worth it for your specific project. Depending on the complexity and size of your application, some measures may be too costly compared to the potential cost of mistakes. So it is up to you and your team to decide which measures are best for you to take.
在本文的其余部分中,我们将探讨可用于在应用程序中实现错误预防和错误检测的几种方法。 但是请记住,此列表只是一个起点。 根据您的特定应用程序,可能有其他措施可以使您的代码更加防错。 同样,重要的是要记住Poka Yoke的前期成本,并确保为您的特定项目值得。 根据应用程序的复杂性和大小,与潜在的错误成本相比,某些措施的成本可能太高。 因此,由您和您的团队决定最适合您采取的措施。
Previously known as type hints in PHP 5, type declarations are an easy way to start mistake-proofing your function and method signatures in PHP 7.
类型声明是PHP 5中以前称为类型提示的一种简单方法,可以开始在PHP 7中对函数和方法签名进行错误校验。
By assigning specific types to your function arguments, it becomes harder to mix up the order of arguments when calling the function.
通过为函数参数分配特定类型,在调用函数时,很难混淆参数的顺序。
For example, let’s take this Notification that we might want to send to a user:
例如,让我们以可能要发送给用户的此Notification为例:
<?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; } }Without type declarations, we can easily inject the incorrect types of variables which would probably break our application. For example, we could assume that the $userId should be a string, while it might actually have to be an int.
没有类型声明,我们可以轻松地注入不正确的变量类型,这很可能会破坏我们的应用程序。 例如,我们可以假设$userId应该是string ,而实际上可能必须是int 。
If we injected the wrong type, the error would probably go undetected until the application tries to actually do something with the Notification. By then, we’d probably get some cryptic error message about an unexpected type, but nothing that immediately points to our code where we inject a string instead of an int.
如果我们注入了错误的类型,则在应用程序尝试对Notification进行实际操作之前,错误可能不会被发现。 到那时,我们可能会收到一些关于意外类型的神秘错误消息,但是没有什么立即指向我们的代码,我们在该代码中注入了string而不是int 。
Because of this, it is often more interesting to force the application to crash as soon as possible, so that bugs like these get caught early on during development.
因此,迫使应用程序尽快崩溃通常更有趣,这样在开发过程中就可以尽早发现此类错误。
In this case, we could simply add some type declarations and PHP will stop and warn us immediately with a fatal error when we mix up the types of our arguments:
在这种情况下,我们可以简单地添加一些类型声明,并且当我们混合参数类型时,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; } }Note however that, by default, PHP will try to coerce incorrect arguments to their expected types. To prevent this, it is important that we enable strict_types so we actually get a fatal error when a mistake is made. Because of this, scalar type declarations are not an ideal form of Poka Yoke, but they’re a good start to reducing mistakes. Even with strict_types disabled, they can still serve as an indication of what type is expected for an argument.
但是请注意,默认情况下,PHP将尝试强制将不正确的参数强制转换为其期望的类型。 为防止这种情况,重要的是我们启用strict_types以便在犯错误时实际上得到一个致命错误。 因此,标量类型声明不是Poka Yoke的理想形式,但它们是减少错误的良好起点。 即使禁用strict_types ,它们仍然可以指示参数应使用的类型。
Additionally, we declared return types for our methods. These make it easier to determine what kind of values we can expect when calling a certain function.
此外,我们为方法声明了返回类型。 这些使确定某个函数时可以期望的值类型变得更加容易。
Clearly defined return types are also useful to avoid a lot of switch statements when working with return values, because without explicitly declared return types, our methods could return various types. Therefore, someone using our methods would have to check which type was actually returned in a specific scenario. These switch statements can obviously be forgotten, and lead to bugs that can be hard to detect. Mistakes like this become much less prevalent with return types.
明确定义的返回类型在避免使用返回值时避免很多switch语句也很有用,因为如果没有显式声明的返回类型,我们的方法可能会返回各种类型。 因此,使用我们的方法的人将必须检查在特定情况下实际返回的类型。 这些switch语句显然可以被忘记,并导致难以检测的错误。 这样的错误在返回类型中变得不那么普遍了。
One problem that scalar type hints can not easily fix for us, is that having multiple function arguments makes it possible to mix up the order of said arguments.
标量类型提示不能为我们轻松解决的一个问题是,具有多个函数参数可以混合所述参数的顺序。
When all arguments have a different scalar type, PHP can warn us when we mix up the order of arguments, but in most cases we will probably have some arguments with the same type.
当所有参数都具有不同的标量类型时,当我们混合参数的顺序时,PHP会向我们发出警告,但是在大多数情况下,我们可能会有一些具有相同类型的参数。
To fix this, we could wrap our arguments in value objects like so:
为了解决这个问题,我们可以将参数包装在值对象中,如下所示:
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 { /* ... */ } }Because our arguments now each have a very specific type, it becomes near impossible to mix them up.
由于我们的参数现在都具有非常特定的类型,因此几乎不可能将它们混淆。
An additional advantage of using value objects over scalar type declarations, is that we no longer have to enable strict_types in every file. And if we don’t have to remember it, we can’t forget it by accident.
使用值对象而不是标量类型声明的另一个优点是,我们不再需要在每个文件中启用strict_types 。 而且,如果我们不必记住它,我们也不会偶然忘记它。
When working with value objects, we can encapsulate the validation logic of their data inside the objects themselves. Doing so, we can prevent the creation of a value object with an invalid state, which would probably lead to problems down the road in other layers of our application.
当使用值对象时,我们可以将其数据的验证逻辑封装在对象本身内。 这样做可以防止创建具有无效状态的值对象,这可能会导致应用程序其他层的问题。
For example, we might have a rule that says that any given UserId should always be positive.
例如,我们可能有一条规则,规定任何给定的UserId都应始终为正。
We could obviously validate this rule whenever we get a UserId as input, but on the other hand it can also easily be forgotten in one place or another.
显然,只要我们获得UserId作为输入,就可以验证该规则,但另一方面,也可以很容易地在一个地方或另一个地方忘记它。
And even if this mistake would result in an actual error in another layer of our application, the error message could be unclear about what actually went wrong and it becomes hard to debug.
即使此错误将导致应用程序另一层出现实际错误,该错误消息也可能不清楚实际出了什么问题,并且变得难以调试。
To prevent mistakes like this, we could add some validation to the UserId constructor:
为了防止发生此类错误,我们可以向UserId构造函数添加一些验证:
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; } }This way we can always be sure that when we’re working with a UserId object, it has a valid state. This prevents us from having to constantly re-validate our data throughout the various layers of our application.
这样,我们始终可以确保在使用UserId对象时,该对象具有有效状态。 这使我们不必在应用程序的各个层中不断重新验证数据。
Note that we could add a scalar type declaration instead of using is_int here, but it would force us to enable strict_types everywhere we use UserId.
请注意,我们可以在此处添加标量类型声明,而不是使用is_int ,但是这将迫使我们在使用UserId任何地方都启用strict_types 。
If we wouldn’t enable strict_types, PHP would silently try to coerce other types to int whenever they are passed into UserId. This can be problematic, as we might for example inject a float which might actually be an incorrect variable as user ids are generally not floats.
如果我们不启用strict_types ,则PHP将在将其他类型传递给UserId时,默默地尝试将其他类型强制为int 。 这可能是有问题的,因为例如我们可能注入一个float ,实际上它可能是一个不正确的变量,因为用户ID通常不是floats 。
In other cases, where we might for example be working with a Price value object, disabling strict_types could result in rounding errors as PHP would automatically convert float variables to int.
在其他情况下,例如我们可能正在使用Price值对象,则禁用strict_types可能会导致舍入错误,因为PHP会自动将float变量转换为int 。
By default, objects are passed by reference in PHP. This means that when we make a change to an object, it becomes altered throughout our whole application instantly.
默认情况下,对象是通过PHP中的引用传递的。 这意味着,当我们对一个对象进行更改时,它立即在整个应用程序中被更改。
While this approach has its advantages, it also has some downsides. Take this example of a Notification being sent to a user via both SMS and e-mail:
虽然这种方法有其优点,但也有一些缺点。 以通过SMS和电子邮件将Notification发送给用户的示例为例:
interface NotificationSenderInterface { public function send(Notification $notification); } class SMSNotificationSender implements NotificationSenderInterface { public function send(Notification $notification) { $this->cutNotificationLength($notification); // Send an SMS... } /** * Makes sure the notification does not exceed the length of an SMS. */ private function cutNotificationLength(Notification $notification) { $message = $notification->getMessage(); $messageString = substr($message->getValue(), 160); $notification->setMessage(new Message($messageString)); } } class EmailNotificationSender implements NotificationSenderInterface { public function send(Notification $notification) { // Send an e-mail ... } } $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);Because the Notification object is being passed by reference, we have caused an unintended side effect. By cutting the message’s length in the SMSNotificationSender, the referenced Notification object was updated throughout the whole application, which means it was also cut when it was sent by the EmailNotificationSender later on.
由于Notification对象是通过引用传递的,因此导致了意外的副作用。 通过在SMSNotificationSender削减消息的长度,整个应用程序中都将更新所引用的Notification对象,这意味着在稍后由EmailNotificationSender发送该EmailNotificationSender时也将其削减。
To fix this, we can make our Notification object immutable. Instead of providing set methods to make changes to it, we can add some with methods that make a copy of the original Notification before applying changes:
为了解决这个问题,我们可以使Notification对象不可变。 而是提供set方法来进行更改,我们可以添加一些with ,使原来的复制方法Notification应用更改之前:
class Notification { public function __construct( ... ) { /* ... */ } public function getUserId() : UserId { /* ... */ } public function withUserId(UserId $userId) : Notification { $c = clone $this; $c->userId = clone $userId; return $c; } public function getSubject() : Subject { /* ... */ } public function withSubject(Subject $subject) : Notification { $c = clone $this; $c->subject = clone $subject; return $c; } public function getMessage() : Message { /* ... */ } public function withMessage(Message $message) : Notification { $c = clone $this; $c->message = clone $message; return $c; } }This way, whenever we make a change to our Notification class by for example cutting the message’s length, the change no longer ripple throughout the whole application, preventing any unintended side effects.
这样,每当我们通过减少消息的长度来更改Notification类时,该更改就不会遍及整个应用程序,从而避免了任何意外的副作用。
Note however that it is very hard (if not impossible) to make an object truly immutable in PHP. But for the sake of making our code mistake-proof, it already helps a lot if we add “immutable” with methods instead of set methods, as users of the class no longer have to remember to clone the object themselves before making changes.
但是请注意,要使对象在PHP中真正不可变是非常困难的(如果不是不可能的话)。 但是,为了使我们的代码防错,如果我们with方法而不是set方法添加“不可变的”,这已经大有帮助,因为该类的用户不再需要记住在进行更改之前先克隆对象。
Sometimes we might have functions or methods that can either return some value, or null. These nullable return values can pose a problem because they almost always requires a check to see whether or not they are null before we can do something with them. Again, this is something that we could easily forget. To prevent us from always having to check the return values, we could return null objects instead.
有时我们可能具有可以返回某个值或null函数或方法。 这些可为空的返回值可能会引起问题,因为在我们可以对它们执行某些操作之前,几乎总是需要检查它们是否为null 。 同样,这是我们很容易忘记的事情。 为了防止我们总是必须检查返回值,我们可以返回空对象。
For example, we could have a ShoppingCart with either a discount applied to it or not:
例如,我们可以有一个ShoppingCart或不应用折扣:
interface Discount { public function applyTo(int $total); } interface ShoppingCart { public function calculateTotal() : int; public function getDiscount() : ?Discount; }When calculating the final price of our ShoppingCart, we now always have to check whether getDiscount() returns null or an actual Discount before calling the applyTo method:
现在,在计算ShoppingCart的最终价格时,我们始终必须在调用applyTo方法之前检查getDiscount()返回null还是实际的Discount :
$total = $shoppingCart->calculateTotal(); if ($shoppingCart->getDiscount()) { $total = $shoppingCart->getDiscount()->applyTo($total); }If we didn’t do this check, we’d probably get a PHP warning and/or other unintended effects when getDiscount() returns null.
如果不进行此检查,则getDiscount()返回null时,我们可能会收到PHP警告和/或其他意外的影响。
On the other hand, these checks could be removed altogether if we’d return a null object instead when no Discount is set:
另一方面,如果未设置Discount时返回空对象,则可以完全删除这些检查:
class ShoppingCart { public function getDiscount() : Discount { return !is_null($this->discount) ? $this->discount : new NoDiscount(); } } class NoDiscount implements Discount { public function applyTo(int $total) { return $total; } }Now, when we call getDiscount(), we always get a Discount object even if no discount is available. This way, we can apply the discount to our total, even if it there is none, and we no longer need an if statement:
现在,当我们调用getDiscount() ,即使没有折扣可用,我们也会始终获得Discount对象。 这样,即使没有折扣,我们也可以将折扣应用于总计,并且不再需要if语句:
$total = $shoppingCart->calculateTotal(); $totalWithDiscountApplied = $shoppingCart->getDiscount()->applyTo($total);For the same reasons that we would want to avoid nullable return types, we might want to avoid optional dependencies and just make all of our dependencies required.
出于与避免空值返回类型相同的原因,我们可能希望避免可选的依赖关系,而仅使所有依赖关系成为必需。
Take for example the following class:
以下面的类为例:
class SomeService implements LoggerAwareInterface { public function setLogger(LoggerInterface $logger) { /* ... */ } public function doSomething() { if ($this->logger) { $this->logger->debug('...'); } // do something if ($this->logger) { $this->logger->warning('...'); } // etc... } }There are two issues with this approach:
这种方法有两个问题:
We constantly have to check for the existence of a logger in our doSomething() method.
我们经常需要在doSomething()方法中检查记录器的存在。
When setting up the SomeService class in our service container, someone might forget to actually set a logger, or they might not even know the class has the option to set a logger.
在我们的服务容器中设置SomeService类时,某些人可能会忘记实际设置记录器,或者甚至可能不知道该类具有设置记录器的选项。
We can simplify this by making the LoggerInterface a required dependency instead:
我们可以通过使LoggerInterface成为必需的依赖项来简化此过程:
class SomeService { public function __construct(LoggerInterface $logger) { /* ... */ } public function doSomething() { $this->logger->debug('...'); // do something $this->logger->warning('...'); // etc... } }This way our public interface becomes less cluttered, and whenever someone creates a new instance of SomeService, they know that the class requires an instance of LoggerInterface so they cannot forget to inject one.
这样,我们的公共接口就变得更加整洁,每当有人创建SomeService的新实例时,他们都知道该类需要LoggerInterface实例,因此他们不会忘记注入一个实例。
Additionally, we have omitted the need for if statements to check whether a logger is injected or not, which makes our doSomething() easier to read, and less susceptible to mistakes whenever someone would make changes to it.
此外,我们已经不再需要if语句来检查是否插入了记录器,这使得我们的doSomething()易于阅读,并且在有人进行更改时不易出错。
If at some point we wanted to use SomeService without a logger, we could apply the same logic as with return statements and just use a null object instead:
如果某个时候我们想在没有记录器的情况下使用SomeService ,则可以应用与return语句相同的逻辑,而只使用null对象:
$service = new SomeService(new NullLogger());In the end this has the same effect as using an optional setLogger() method, but it makes our code easier to follow and reduces the chances of a mistake in our dependency injection container.
最后,这与使用可选的setLogger()方法具有相同的效果,但是它使我们的代码更易于遵循,并减少了在依赖项注入容器中出错的机会。
To make our code easier to use, it is best to keep the amount of public methods on our classes to the bare minimum. This way, it becomes less confusing how our code should be used, and we have less code to maintain and lesser chances of breaking backwards compatibility when refactoring.
为了使我们的代码更易于使用,最好将类上的public方法数量保持在最低限度。 这样一来,应该如何使用我们的代码就不会造成混乱,并且我们需要维护的代码更少,并且重构时破坏向后兼容性的机会也更少。
To keep public methods to a minimum, it can help to think of public methods as transactions.
为了使public方法最小化,可以将公共方法视为事务。
Take for example this example of transferring money between two bank accounts:
以以下在两个银行帐户之间转帐的示例为例:
$account1->withdraw(100); $account2->deposit(100);While the underlying database could provide a transaction to make sure no money would be withdrawn if the deposit could not be made or vice-versa, the database can not prevent us from forgetting to call either $account1->withdraw() or $account2->deposit(), which would result in incorrect balances.
虽然基础数据库可以提供交易以确保如果无法进行存款也不会取款,反之亦然,但数据库无法阻止我们忘记调用$account1->withdraw()或$account2->deposit() ,这将导致余额不正确。
Luckily, we can easily fix this by replacing our two separate methods with a single transactional method:
幸运的是,我们可以通过使用单个事务方法替换两个单独的方法来轻松解决此问题:
$account1->transfer(100, $account2);As a result our code becomes more robust, as it becomes harder to make a mistake by only completing the transaction partially.
结果,我们的代码变得更加健壮,因为仅通过部分完成交易就很难犯错。
Mistake detection mechanisms are, contrary to mistake prevention mechanisms, not meant to prevent errors. Instead they’re meant to warn us about problems whenever they get detected.
错误检测机制与错误预防机制相反,并不是要防止错误。 相反,它们的作用是在发现问题时就警告我们。
Most of the time they live outside of our application, and run at regular intervals to monitor our code or specific changes to it.
它们大多数时候都驻留在我们的应用程序之外,并定期运行以监视我们的代码或对其的特定更改。
Unit tests can be a great way to make sure new code works correctly, but it can also help to make sure existing code still works as intended whenever someone refactors part of the system.
单元测试可以是确保新代码正确运行的好方法,但也可以确保每当有人重构系统的一部分时,现有代码仍然可以按预期工作。
Because someone could still forget to actually run our unit tests, it is advisable to run them automatically when changes are made using services like Travis CI and Gitlab CI. This way, developers automatically get notified when breaking changes occur, and it also helps us when reviewing pull requests to make sure the changes work as intended.
由于仍然有人会忘记实际运行我们的单元测试,因此建议在使用Travis CI和Gitlab CI之类的服务进行更改时自动运行它们。 这样,开发人员在发生重大更改时会自动收到通知,并且在审查拉动请求以确保更改按预期运行时也可以为我们提供帮助。
Besides mistake detection, unit tests are also a great way to provide examples of how specific parts of code are intended to work, which can in turn prevent mistakes when someone else uses our code.
除了错误检测之外,单元测试还是提供示例示例,说明如何使用代码的特定部分的一种好方法,当其他人使用我们的代码时,这可以防止错误。
Because we could always forget to write enough tests, it can be beneficial to automatically generate code coverage reports using services like Coveralls whenever our unit tests run. Coveralls will send us a notification whenever our code coverage drops so we can add some unit tests, and we can also get a grasp of how our code coverage evolves over time.
因为我们总是会忘记编写足够的测试,所以在运行单元测试时,使用Coverovers等服务自动生成代码覆盖率报告将是有益的。 每当我们的代码覆盖率下降时,Coveralls都会向我们发送通知,以便我们可以添加一些单元测试,并且我们还可以了解我们的代码覆盖率随时间的变化情况。
Another, even better, way to make sure we have enough unit tests for our code is to set up some mutation tests, for example using Humbug. As the name implies, these tests are meant to verify that we have a decent amount of code coverage by slightly altering our source code, running our unit tests afterwards, and making sure the relevant tests start failing because of the mutations.
确保我们的代码有足够的单元测试的另一种甚至更好的方法是设置一些突变测试,例如使用Humbug 。 顾名思义,这些测试旨在通过稍稍更改源代码,随后运行我们的单元测试并确保相关测试由于突变而开始失败,来验证我们的代码覆盖率。
Using both code coverage reports and mutation tests, we can make sure that our unit tests cover enough code to prevent accidental mistakes or bugs.
使用代码覆盖率报告和变异测试,我们可以确保我们的单元测试覆盖足够的代码,以防止意外的错误或错误。
Code analyzers can detect bugs in our application early in the development process. IDEs like PHPStorm, for example, use code analyzers to warn us about errors and to give suggestions when we’re writing code. These can range from simple syntax errors to the detection of duplicate code.
代码分析器可以在开发过程的早期检测到我们应用程序中的错误。 例如, PHPStorm之类的 IDE会使用代码分析器来警告我们有关错误并在编写代码时提供建议。 这些错误的范围从简单的语法错误到重复代码的检测。
Besides the analyzers built into most IDEs, it is possible to incorporate third-party and even custom analyzers into the build process of our applications to spot specific problems. A non-exhaustive list of analyzers suitable for PHP projects can be found at exakat/php-static-analysis-tools, ranging from coding standard analyzers, to analyzers that check for security vulnerabilities.
除了大多数IDE中内置的分析器之外,还可以将第三方甚至自定义分析器合并到我们应用程序的构建过程中,以发现特定问题。 在exakat / php-static-analysis-tools上可以找到适用于PHP项目的分析器的详尽列表,范围从编码标准分析器到检查安全漏洞的分析器。
Online solutions exist as well, for example SensioLabs Insights.
也存在在线解决方案,例如SensioLabs Insights 。
Contrary to most other mistake detection mechanisms, log messages can help us detect mistakes in our application when it’s running live in production.
与大多数其他错误检测机制相反,日志消息可以帮助我们在生产环境中实时运行应用程序时检测错误。
Of course it is first required that our code actually logs messages whenever something unexpected happens. Even when our code supports loggers, they can easily be forgotten when setting everything up. Because of this we should try to avoid optional dependencies (see above).
当然,首先需要我们的代码在发生意外情况时实际记录消息。 即使我们的代码支持记录器,在设置所有内容时也很容易忘记它们。 因此,我们应尽量避免可选的依赖关系(请参见上文)。
While most applications will log at least some messages, the information they provide only becomes really interesting when they are actively analyzed and monitored using tools like Kibana or Nagios. Tools like these can give new insights in what errors and warnings occur in our application when users are actively using it, instead of when it’s being tested internally. We’ve got a great post about monitoring PHP apps with this ELK stack here.
尽管大多数应用程序将至少记录一些消息,但是只有使用Kibana或Nagios之类的工具积极分析和监视它们时,它们提供的信息才变得非常有趣。 诸如此类的工具可以为用户提供积极的了解,而不是在内部对其进行测试时,我们的应用程序中会发生什么错误和警告,从而获得新的见解。 我们已经得到了有关监控与此ELK堆栈PHP应用程序一个伟大的职位在这里 。
Even when actively logging error messages, it often happens that some errors are being suppressed. PHP has the tendency to carry on whenever a “recoverable” error occurs, as if it wants to help us by keeping the application running. However, errors can often be very useful when developing or testing a new feature, as they often indicate bugs in our code.
即使主动记录错误消息,也经常发生某些错误被抑制的情况。 PHP倾向于在发生“可恢复”错误时进行操作,好像它想通过保持应用程序运行来帮助我们。 但是,在开发或测试新功能时,错误通常非常有用,因为它们通常表示我们代码中的错误。
This is why most code analyzers will warn you when they detect you’re using @ to suppress errors, as it can hide bugs that will inevitably pop up again as soon as the application is actually being used by visitors.
这就是为什么大多数代码分析器在检测到您使用@来抑制错误时都会警告您的原因,因为它可以隐藏错误,一旦访问者实际使用该应用程序,错误将不可避免地再次弹出。
Generally, it is best to set PHP’s error_reporting level to E_ALL so even the slightest warnings get reported. However, make sure to log these messages somewhere and hide them from your users so no sensitive information about your application’s architecture or potential security vulnerabilities are being exposed to end users.
通常,最好将PHP的error_reporting级别设置为E_ALL这样即使是最轻微的警告也可以得到报告。 但是,请确保将这些消息记录在某处并将其对用户隐藏,这样最终用户就不会暴露有关应用程序体系结构或潜在安全漏洞的敏感信息。
Aside from the error_reporting configuration, it is also important to always enable strict_types so PHP doesn’t try to automatically coerce function arguments to their expected type, as this can often lead to hard-to-detect bugs when converting from one type to another (for example, rounding errors when casting from float to int).
除了error_reporting配置之外,始终启用strict_types也很重要,这样PHP不会尝试将函数参数自动强制为它们的预期类型,因为当从一种类型转换为另一种类型时,这通常会导致难以检测的错误(例如,从float为int时的舍入错误)。
As Poka Yoke is more of a concept rather than a specific technique, it can also be applied to areas outside of (but related to) PHP.
由于Poka Yoke只是一个概念,而不是特定的技术,因此它也可以应用于PHP之外(但与之相关)的区域。
At an infrastructure level, a lot of mistakes can be prevented by having a shared development setup that is identical to the production environment, using tools like Vagrant.
在基础架构级别,使用像Vagrant这样的工具进行与生产环境相同的共享开发设置,可以避免很多错误。
Automating the deployment process using build servers like Jenkins and GoCD can also help a lot to prevent mistakes when deploying changes to our application, as this can often include a wide range of required steps depending on the application that can easily be forgotten.
使用诸如Jenkins和GoCD之类的构建服务器来自动化部署过程,还可以在很大程度上防止在将更改部署到我们的应用程序时出现错误,因为根据不同的应用程序,这通常可能包括许多必需的步骤,而这些步骤很容易被遗忘。
When building REST APIs, we can incorporate Poka Yoke to make our API easier to use. For example, we could make sure we always return an error whenever an unknown parameter is passed in the URL query or request body. This might seem strange as we obviously want to avoid “breaking” our API’s clients, but it is generally better to warn the developers using our API as soon as possible about incorrect usage so bugs can be fixed early in the development process.
在构建REST API时,我们可以合并Poka Yoke以使我们的API易于使用。 例如,我们可以确保在URL查询或请求正文中传递未知参数时始终返回错误。 这似乎很奇怪,因为我们显然希望避免“破坏” API的客户端,但是通常最好尽快警告使用我们API的开发人员有关不正确的用法,以便可以在开发过程的早期修正错误。
For example, we could have a color parameter on our API, but someone consuming our API might accidentally use a colour parameter instead. Without any warnings, this mistake can easily make its way through to the production environment until it is only noticed by end users because of unintended behavior. To learn how to build APIs that won’t bite you later, a good book like this one might come in handy.
例如,我们可以在API上使用color参数,但是使用我们API的人可能会不小心使用了colour参数。 在没有任何警告的情况下,此错误很容易传播到生产环境,直到由于意外行为而仅被最终用户注意到为止。 要学习如何构建以后不会咬你的API, 像这样的一本好书可能会派上用场。
Practically all applications depend on at least some custom configuration. More often than not, developers like to provide as many default values as possible for configuration, so it’s less work to configure the application.
实际上,所有应用程序至少都依赖于某些自定义配置。 通常,开发人员喜欢为配置提供尽可能多的默认值,因此配置应用程序的工作较少。
However, just like the color and colour example above, it can be easy to mistype configuration parameters which would cause our application to unexpectedly fall back to the default values. These kinds of mistakes can be hard to track down when the application does not raise an error, and the best way of raising an error for incorrect configuration is simply to not provide any defaults and raise an error as soon as a configuration parameter is missing.
但是,就像上面的color和colour示例一样,很容易错误键入配置参数,这将导致我们的应用程序意外地退回到默认值。 当应用程序不引发错误时,这些类型的错误可能很难追查,而针对不正确的配置引发错误的最佳方法就是简单地不提供任何默认值,并在缺少配置参数时立即引发错误。
Poka Yoke concepts can also be applied to prevent or detect user mistakes. For example in payment software, an account number entered by the user can be validated using the check digit algorithm. This prevents the user from accidentally entering an account number with a typo.
Poka Yoke概念也可用于防止或检测用户错误。 例如,在支付软件中,可以使用支票数字算法来验证用户输入的帐号。 这样可以防止用户意外输入带有错字的帐号。
While Poka Yoke is more of a concept rather than a specific set of tools, there are various principles we can apply to our code and development process to make sure mistakes get prevented or detected early on. Very often these mechanisms will be specific to the application itself and its business logic, but there are some simple techniques and tools we can use to make any code more fool-proof.
尽管Poka Yoke只是一个概念,而不是一组特定的工具,但是我们可以将各种原理应用于我们的代码和开发过程中,以确保尽早防止或发现错误。 这些机制通常是特定于应用程序本身及其业务逻辑的,但是我们可以使用一些简单的技术和工具来使任何代码更加安全。
Probably the most important thing to remember is that while we obviously want to avoid errors in production, they can be very useful during development and we should not be afraid to raise them as soon as possible so mistakes are easier to track down. These errors can be raised either by the code itself, or by separate processes that run separately from our application and monitor it from the outside.
可能要记住的最重要的事情是,尽管我们显然希望避免生产中的错误,但在开发过程中它们可能非常有用,我们不应该害怕尽快提出这些错误,这样可以更容易地发现错误。 这些错误可能是由代码本身引起的,也可能是由与我们的应用程序分开运行并从外部进行监视的单独进程引起的。
To further reduce errors, we should aim to keep the public interfaces of our code as simple and straightforward as possible.
为了进一步减少错误,我们应该致力于使代码的公共接口尽可能简单明了。
If you have any more tips on how Poka Yoke can be applied to PHP development or programming in general, feel free to share them in the comments!
如果您还有更多关于如何将Poka Yoke应用于PHP开发或编程的更多技巧,请随时在评论中分享它们!
Poka-yoke – Toyota Production System guide describes the purpose of Poka Yoke within the manufacturing process of Toyota.
Poka-yoke –丰田生产系统指南介绍了Poka Yoke在丰田制造过程中的用途。
How to Use Poka-Yoke Technique to Improve Software Quality gives tips on how to improve the functional quality of software using Poka Yoka.
如何使用Poka-Yoke技术改善软件质量,给出了有关如何使用Poka Yoka改善软件功能质量的提示。
Poka-Yoke Your Code gives a quick overview of how Poka Yoke can be applied to programming in general.
Poka-Yoke您的代码简要概述了Poka Yoke如何在一般编程中应用。
POKA YOKE – Applying Mistake Proofing to Software gives a more detailed overview of how to apply Poka Yoke to programming.
POKA YOKE –在软件上应用防错技术将更详细地概述如何将Poka Yoke应用于编程。
Extremely Defensive PHP is a talk on how to make your PHP code more mistake-proof.
极具防御性PHP是关于如何使您PHP代码更防错的讨论。
3 benefits of using Immutable Objects gives a good overview of the advantages of immutable objects.
使用不可变对象的3个好处很好地概述了不可变对象的优点。
Immutable value objects in PHP gives a quick overview of how we can actually make value objects immutable (or at least as immutable as possible).
PHP中的不可变值对象简要概述了如何使值对象不可变(或至少尽可能不可变)。
PHP and immutability goes more into depth of how immutability works (and doesn’t work) in PHP.
PHP和不变性更深入地介绍了PHP中不变性如何工作(和不起作用)。
Writing good code: how to reduce the cognitive load of your code describes various methods to make your code easier to follow, in turn reducing the chance of someone making a mistake when using your code or making changes to it.
编写好的代码:如何减少代码的认知负担,介绍了各种使代码易于遵循的方法,从而减少了使用或更改代码时有人犯错的机会。
翻译自: https://www.sitepoint.com/poka-yoke-saving-projects-with-hyper-defensive-programming/
相关资源:POKA-YOKE–避免无心的错误