如何使用 Insphpect 确保灵活、可重用的 PHP 代码
Insphpect 是我在博士项目中编写的一个工具。 它扫描代码以寻找阻碍代码可重用性和灵活性的面向对象编程技术。
为什么?
让我从两个世俗的观察开始:
- 业务需求随时间变化。 程序员不是千里眼。
新产品发布、紧急封锁规定、开拓新市场、经济因素、更新的数据保护法:商业软件需要更新的潜在原因有很多。
从这两个观察我们可以推断,程序员知道他们编写的代码将要发生变化,但不知道这些变化是什么或何时发生。
以易于修改的方式编写代码是一项需要多年才能掌握的技能。
您可能已经熟悉那些反复出现并困扰着您的编程实践。 新手程序员很快就会意识到全局变量带来的麻烦多于它们的价值,而且曾经非常流行的单例模式在过去十年中一直是一个肮脏的词。
您对应用程序进行编码的方式对其适应新要求的难易程度有很大影响。 随着您职业生涯的进步,您将学习使改编代码更容易的技术。 一旦您掌握了面向对象编程的基础知识,您就会想知道没有它您是怎么做的!
如果你让十个开发人员来生产软件,给定同样的需求,你会得到十个不同的解决方案。 其中一些解决方案将不可避免地优于其他解决方案。
考虑一艘装在瓶子里的船和一艘用乐高积木制成的模型船。 两者都是模型船,但在瓶子里改变船上的帆非常困难,而且部件的再利用几乎是不可能的。 然而,使用乐高船,您可以轻松更换帆或使用相同的组件来构建模型火箭、房屋或汽车。
某些编程技术会导致瓶中装运方法,并使您的代码难以更改和适应。
督察
Insphpect 是一种工具,它可以扫描您的代码以寻找导致这种瓶中船设计的编程实践。
它根据代码的灵活性对代码进行分级,并突出显示可以提高灵活性的区域。
Insphpect 寻找什么?
目前,Insphpect 寻找以下内容:
- 紧耦合硬编码配置单例设置器注入使用
new
构造函数服务定位器中的关键字继承静态方法具有多个角色的全局状态文件(例如定义一个类并运行一些代码)
如果它检测到任何它认为不灵活的东西,它会突出显示代码,解释它突出显示问题的原因,然后按照 0-100 的分数给你的整个项目和个别课程打分(100 分表示没有检测到问题)。 作为概念证明,对于某些检测,它能够自动生成一个补丁文件,重新编写代码以完全消除不灵活之处。
在这里查看示例报告。
Insphpect 目前处于测试阶段,如果您能查看并完成网站“提供您的反馈”部分的调查,那将对我的研究进展非常有帮助。
背景
但是,这些不良做法真的很糟糕吗?
这是背景研究中比较困难的部分之一,您可以在 Insphpect 网站上详细了解这是如何完成的。
但是,这可以概括为:
- 每个不良做法的意见是从每个做法的 100 位作者那里收集的。 作者对实践的看法按 1-5 分进行评分。 根据用于临床试验的 Jadad 评分,作者的方法严谨性按照 1-7 的等级进行分级。
然后将这些绘制如下图:
每条水平线代表一篇文章,每篇文章的左侧(橙色)条是建议,从 5——不惜一切代价避免这种做法(最左边)——到 1——支持这种做法而不是其他选择。
每篇文章的右侧(蓝色)条是衡量分析严谨性的 Jadad 风格分数。 七分意味着文章描述了实践,提供了代码示例,讨论了替代方法,提供了类似的代码示例,讨论了每种方法的优缺点,并就应该使用哪种方法提出了建议。
对于上面的单例,将单例与替代方法进行比较、讨论优缺点等的作者更有可能建议使用替代方法。
演练
目前,Insphpect 允许通过 Git 存储库 URL 或 ZIP 文件上传代码。
因此,为了不指出其他人工作中的缺陷,让我们看一下我自己的一个项目,看看它指出了什么。
我们将使用 https://github.com/Level-2/Transphporm 作为示例项目。
这是一个很好的例子,因为它在另一个代码质量工具 Scrutinizer 上的得分非常高。
首先,输入git URL https://github.com/Level-2/Transphporm
进入主页顶部的文本框,然后按“开始”。 这将需要几秒钟到几分钟,具体取决于项目的大小,并将生成如下所示的报告:
进入报告页面后,您会在顶部看到一个总分总分 100 分的摘要,其中 100 分非常好,0 分非常差。
在摘要下方,您会看到项目中所有班级的列表,每个班级都有自己的成绩。
如果您的代码没有获得满分,请不要担心。 它不太可能会。 请记住,Insphpect 是一种识别代码灵活性的工具。 您的代码的某些部分(如入口点)不保证灵活性。
对于 Transphporm,它在七个类中突出显示了问题。
让我们来看看其中的一些。 向下滚动到 TransphpormParserCssToXpath
并点击链接。 您会看到该特定课程的分数和已确定的问题列表。
在本例中,它识别了一个静态变量和一个静态方法。 单击其中一条红线将显示该行被标记的原因的解释。
例如,单击第 12 行将解释为什么静态变量不如实例变量灵活。
尽管报告中对静态属性引起的问题有更深入的解释,但作为快速复习,静态变量有一个值,该值在类的所有实例之间共享。
这本质上不如实例变量灵活,因为使用实例变量允许每个实例具有不同的值。
例如,请考虑以下内容:
class User {
public static $db;
public $id;
public $name;
public $email;
public function save() {
$stmt = self::$db->prepare('REPLACE INTO user (id, name, email) VALUES (:id, :name, :email)');
$stmt->execute([
'id' => $this->id,
'name' => $this->name.
'email' => $this->email
]);
}
}
因为 $db
是静态的,这个类的每个实例共享相同的 $db
实例和记录将始终被插入到同一个数据库中。
虽然这听起来很合理,但让我举一个真实的例子。
在现实世界
我们的一位客户是一家招聘机构。 在我们开发他们的网站大约两年后,他们接管了另一家较小的公司。 他们想保留第二家公司的网站和品牌,因为它在他们所在的利基市场中非常有名。
我们的客户问我们以下问题:
在第二家公司的网站上,您能否在添加职位时添加一个复选框,同时将职位添加到我们的数据库中,以便查看我们网站的人也可以看到该职位,反之亦然。
一个相当简单的请求。 对两个不同的数据库运行插入查询。
但是因为网站使用了一个静态的全局数据库实例,所以这是不必要的困难!
该站点的开发人员编写代码时确信只需要一个数据库连接。 他们错了。
请记住,您不是千里眼,也无法预测未来可能需要什么样的灵活性。
解决方案
正如 Insphpect 所建议的,解决这个问题的方法是使用实例变量:
class User {
private $db;
public $id;
public $name;
public $email;
public function __construct(PDO $db) {
$this->db = $db;
}
public function save() {
$stmt = self::$db->prepare('REPLACE INTO user (id, name, email) VALUES (:id, :name, :email)');
$stmt->execute([
'id' => $this->id,
'name' => $this->name.
'email' => $this->email
]);
}
}
现在一个 User
instance 可以与不同的数据库实例一起使用:
new User($database1);
new User($database2);
为了 TransphpormParserCssToXpath
我们可以这样做,删除静态变量并考虑将其设为实例变量而不是静态变量。
在构造函数中使用 new
让我们看一下其他类之一: TransphpormBuilder
.
这得分为零,相当差。 详细检查报告,Insphpect 已经三次发现相同的问题:使用 new
构造函数中的关键字。
谷歌编程教练 Misko Hevery 很好地解释了为什么这是一种糟糕的编程实践,但这里有一个来自 Insphpect 输出的简单示例:
class Car {
private $engine;
public function __construct() {
$this->engine = new PetrolEngine();
}
}
在这里,每当一个实例 Car
被创建,一个实例 PetrolEngine
被建造。 这使得它非常不灵活,因为没有办法构造一个 Car
使用不同的发动机类型。 在这个系统中建模的每辆车都必须有一个 PetrolEngine
.
相反,我们可以使用依赖注入:
class Car {
private $engine;
public function __construct($engine) {
$this->engine = $engine;
}
}
可以使用以下实例创建不同的汽车 PetrolEngine
, DieselEngine
, ElectricEngine
, JetEngine
或项目中存在的任何其他引擎类型。
要修复此错误 TransphpormBuilder
,所有当前具有硬编码类名的变量都应该使用构造函数参数。
Insphpect 还发现了其他问题,但您可以自己尝试一下,看看您的项目进展如何。
幕后花絮
您可能想知道分数是如何计算的以及为什么这门课得了零分。 目前,一旦扫描了更多项目并提供了更多反馈,权重可能会发生变化。
分数旨在指示将一个项目/班级与另一个项目/班级进行比较。
项目总分只是项目中所有班级的平均分。 这是因为在 1000 个类中有两个问题的项目总体上比在两个类中有两个问题的项目要好得多。
每个不好的做法都根据它是阻碍整个班级的灵活性还是只阻碍一种方法的灵活性来加权。
结论
Insphpect 可用于识别您的代码区域,这些区域使将来的更改比实际情况更加困难,并且它提供了有关如何以更灵活的方式编写代码的建议。 请记住,您不是千里眼,也无法知道您的代码需要如何更改!
Insphpect 目前是一项正在进行的工作,使用它(并完成调查)的人越多,它就会变得越好。
您的项目或最喜欢的库得分如何? 请务必完成调查,因为它将为我的博士项目提供有价值的数据并帮助改进工具!