防止、检测和修复代码中的错误的 7 个技巧
发布日期 作者 Duilio Palacios
我们为项目添加的每一行代码都会增加项目的复杂性,也增加了引入错误的可能性,这些错误可能会在不适宜的时候出现。也许是在客户会议前几分钟,或者是在周末我们去电影院的时候,远离我们的键盘。
为了防止这些可怕的情况,让我们回顾一下编写更好代码的七个技巧
- 为您的变量、函数、参数和方法分配描述性名称
代码只编写一次,但会被读取和解释很多次——无论是其他开发人员还是您自己。因此,花几秒钟来命名新的类或方法是值得的,这样它的名称就能揭示其真正的意图或内容。
让我们比较这两行代码。哪一行更容易理解?
$evnt->add($req->q);
$event->addTickets($request->quantity);
第一行代码有一个拼写错误,add
方法不清楚添加了什么内容,变量 $req
不够清晰,而且很难理解 q
代表数量。
另一方面,第二个示例即使对于非开发人员来说也很容易理解。
- 使用 PSR-2 等标准来编写 PHP 代码
永远不要低估以有序和一致的方式编写代码的重要性,因为这将使您能够更快地发现问题。
考虑以下两个示例
public function addTickets($quantity){ foreach (range(1, $quantity) as $i) { $code = Code::generate(); } $this->tickets()->create( [ 'code' => $code, ]); }
public function addTickets($quantity){ foreach (range(1, $quantity) as $i) { $code = Code::generate(); } $this->tickets()->create([ 'code' => $code, ]);}
两段代码都有相同的错误:它们只创建了一张票,而应该创建 N 张票。但是您在哪一段代码中更快地发现了问题?现在想象一下,如果处理的是格式错误的复杂代码,会造成什么后果。
- 减少临时变量的数量
虽然我们在算法中学习到的第一个概念之一是如何声明和使用临时变量,但它们会使代码更难阅读和维护
考虑以下示例
$contact = array();$contact['firstname'] = $user->first_name;$contact['surname'] = $user->last_name;$contact['id'] = $user->id;$contact_emails = array();$contact_email = array();$contact_email['email'] = $user->email;$contact_emails[] = $contact_email;$contact['emails'] = $contact_emails; $this->create('contact', $contact);
$contact = [ 'id' => $user->id, 'firstname' => $user->first_name, 'surname' => $user->last_name, 'emails' => [ [ 'email' => $user->email, ], ],]; $this->create('contact', $contact);
哪个示例更容易理解?
顺便说一下,将等号对齐是不好的做法。这不仅违反了 PSR-2,还会使代码更难维护。
因此,回到我们的票务示例,如果我们消除 code
变量并将代码内联,则可以改进示例
public function addTickets($quantity){ foreach (range(1, $quantity) as $i) { $this->tickets()->create([ 'code' => Code::generate(6), ]); }}
但是,在某些情况下,使用局部变量可以提高代码的清晰度,例如
function calculateCode($price, $quantity, $deliveryCost){ $subtotal = $price * $quantity; if ($subtotal < 30) { $subtotal += $deliveryCost; } return $subtotal;}
可能比以下代码更清晰
<?php function calculateTotal($price, $quantity, $deliveryCost){ if ($price * $quantity < 30) { return $price * $quantity + $deliveryCost; } return $price * $quantity;}
- 不要使用“魔数”。
如果低于 30 美元的订单将收取送货费,我们应该使用属性、常量或配置变量来揭示此信息,如下所示
if ($subtotal < DELIVERY_COST_THRESHOLD) { $subtotal += $deliveryCost;}
这样,我们就可以揭示我们的意图,并且还可以将该常量在项目的其他部分重复使用。
如果我们以后需要更改送货阈值,我们只需要更新代码中的一行,通过减少重复,我们也减少了忘记更新之前使用魔数的地方的可能性。
- 分而治之
许多示例和代码场景可以通过将代码分离成多个小方法来改进,每个方法都有不同的职责。例如
新方法 getContactInfo
将返回一个包含用户联系信息的数组
$this->create('contact', $user->getContactInfo());
面向对象编程要求我们在一个地方(类)组合数据和函数。我们将在包含所有用户信息(
User
模型)的地方组装包含联系信息的数组。
让我们看另一个示例
$subtotal = $item->price * $quantity;$subtotal = $this->addDeliveryCost($subtotal);
方法 addDeliveryCost
将返回包含送货费的金额,但前提是金额不超过送货阈值,否则将返回原始金额。
现在让我们删除局部变量并将代码内联
return $this->addDeliveryCost($price * $quantity);
声明和使用许多小方法是减少代码中临时变量需求的好方法。
- 默认使用简单解决方案
许多承诺帮助您编写更好代码的教程最终解释了如何过度复杂化本可以保持简单的代码。
这些教程告诉您,如果您正在使用 Laravel 和 Eloquent,将以下代码放在控制器中是错误的
// Somewhere in UserController.php User::create([ 'name' => $request->name, 'email' => $request->email, 'password' => bcrypt($request->password),]);
您应该改为以下方式编写
// Somewhere in UserController.php $this->commandTransport->handleCommand( new UserCreationCommand( new UserNameField($request->name), new UserEmailField($request->email), new UserPasswordField(bcrypt($request->password)), ));
然后,在 UserCreationCommandHandler
类中,您也不会创建用户,因为这会违反 SOLID 原则。您应该改为使用存储库
class UserCreationCommandHandler{ //... public function handle(UserCreationCommand $command) { $this->userRepository->create( $command->name, $command->email, $command->password, ); }}
最终,在 UserEloquentRepository
中,您将最终调用 User::create
class UserEloquentRepository implements UserRepository{ //... public function create( UserNameField $name, UserEmailField $email, UserPasswordField $password ) { return User::create([ 'name' => $name->getValue(), 'email' => $email->getValue(), 'password' => bcrypt($password->getValue()), ]);} }
经过半小时的工作后,客户打电话给您,要求您在 User 模型中添加另一个字段。
哪个示例需要更长时间才能修复?在什么情况下更容易引入错误?(例如,忘记将一个字段从一个方法传递到另一个方法)。
顺便说一下,您是否注意到我在命令和存储库的示例中两次调用了 bcrypt
?因此第二个示例中有一个错误!
不幸的是,几十个接口和类并不能阻止您犯错误。在任何情况下,您都必须非常仔细地测试您的代码。说到测试代码
- 编写自动测试
会计人员遵循一种称为“复式记账”的做法——该方法要求他们对所有交易进行两次录入。编写单元测试要求我们编写两次代码,一次是为了定义每个测试
function test_order_without_delivery_cost(){ $order = new Order; $order->addItem(new Item(['price' => 20]), 5); $expectedTotal = 20 * 5; $this->assertSame($expectedTotal, $order->getTotal());} function test_order_with_delivery_cost(){ $order = new Order; $order->addItem(new Item(['price' => 20]), 1); $expectedTotal = 20 + DELIVERY_COST; $this->assertSame($expectedTotal, $order->getTotal());}
以及第二次为了编写代码的实际实现(我将其留作读者的练习)。
许多开发人员抱怨这种做法,因为它迫使我们“加倍工作”,但通过编写两次代码,我们减少了以相同方式两次犯同样错误的可能性(如果我们犯了两个不同的错误,测试很可能会失败)。这就是为什么实现了一些单元测试的项目往往具有更少的错误,并且需要更少的调试时间。
我是一名 PHP/Laravel 开发人员和讲师。我创建了 Styde.net,这是一个专门向西班牙社区教授 PHP、Laravel、Vue.js 和其他 Web 技术的网站。