Insphpect is a tool I wrote as part of my PhD project. It scans code for object-oriented programming techniques that hinder code reusability and flexibility.
Insphpect是我在博士项目中编写的工具。 它会扫描代码中的面向对象编程技术,这些技术会阻碍代码的可重用性和灵活性。
为什么? (Why?)
Let me begin with two mundane observations:
让我从两个平凡的观察开始:
- Business requirements change over time. 业务需求随时间变化。
- Programmers are not clairvoyant. 程序员不是千里眼。
New product launches, emergency lockdown regulations, expanding into new markets, economic factors, updated data protection laws: there are lots of potential causes for business software to need updating.
新产品发布,紧急锁定法规,向新市场扩展,经济因素,更新的数据保护法律:商业软件有很多潜在原因需要更新。
From those two observations we can infer that programmers know that the code they write is going to change, but not what those changes will be or when they will happen.
从这两个观察结果中,我们可以推断出程序员知道他们编写的代码将要发生变化,而不是知道这些变化将是什么,何时发生。
Writing code in such a way that it can be easily adapted is a skill that takes years to master.
以一种易于修改的方式编写代码是一项需要数年才能熟练掌握的技能。
You’re probably already familiar with programming practices that come back and haunt you. Novice programmers quickly realize that global variables are more trouble than they’re worth, and the once incredibly popular Singleton Pattern has been a dirty word for the last decade.
您可能已经熟悉了会再次困扰您的编程实践。 新手程序员很快意识到,全局变量比它们值得的麻烦更多,并且在过去的十年中 ,曾经令人难以置信的单例模式一直是一个肮脏的词 。
How you code your application has a big impact on how easy it is to adapt to meet new requirements. As you progress through your career, you learn techniques that make adapting code easier. Once you’ve grasped fundamentals of object-oriented programming you wonder how you ever did without it!
您的应用程序编码方式对适应新要求的难易程度有很大影响。 随着职业发展,您将学习使改编代码变得更容易的技术。 一旦掌握了面向对象编程的基础知识,您就会想知道如果没有它,您将如何做!
If you ask ten developers to produce software, given the same requirements, you’ll get ten different solutions. Some of those solutions will inevitably be better than others.
如果您要求十个开发人员在相同的需求下生产软件,则将获得十个不同的解决方案。 这些解决方案中的某些不可避免地会比其他解决方案更好。
Consider a ship in a bottle and a model ship made of Lego. Both are model ships, but changing the sails on the ship in a bottle is very difficult, and reusing the parts is near impossible. However, with a Lego ship, you can easily swap out the sails or use the same components to build a model rocket, house or a car.
考虑一个装在瓶子里的船和一个用乐高制成的模型船。 两者都是模型船,但是很难将瓶子中的风帆装在瓶子中,并且几乎不可能重复使用这些零件。 但是,对于乐高飞船,您可以轻松调换帆或使用相同的组件来建造火箭,房屋或汽车模型。
Certain programming techniques lead to the ship-in-a-bottle approach and make your code difficult to change and adapt.
某些编程技术会导致“ 瓶中装”方法的出现,并使您的代码难以更改和适应。
Insphpect (Insphpect)
Insphpect is a tool which scans your code for programming practices that lead to this kind of a ship in a bottle design.
Insphpect是一种工具,可扫描您的代码以寻找编程实践,从而使瓶子设计成为可能。
It grades your code based on how flexible it is, and highlights areas where flexibility can be improved.
它根据代码的灵活性对代码进行评分,并突出显示可以提高灵活性的领域。
Insphpect寻找什么? (What does Insphpect look for?)
Currently, Insphpect looks for the following:
当前,Insphpect寻找以下内容:
- hardcoded configuration 硬编码配置
- singletons 单身人士
- setter injection 二传手注射
using the
new
keyword in a constructor在构造函数中使用
new
关键字- service locators 服务定位器
- inheritance 遗产
- static methods 静态方法
- global state 全球状态
- files that have more than one role (e.g. defining a class and running some code) 具有多个角色的文件(例如,定义一个类并运行一些代码)
If it detects anything it identifies as inflexible, it highlights the code, explains why it highlighted the issue, then grades your whole project and individual classes on a score of 0-100 (with 100 being no issues detected). As a proof of concept, for some detections it’s able to automatically generate a patch file that re-writes the code to remove the inflexibility entirely.
如果它检测到任何被识别为不灵活的内容,则突出显示代码,解释为什么突出显示问题,然后以0-100的评分对整个项目和单个班级评分(未检测到100个问题)。 作为概念验证,对于某些检测,它能够自动生成一个补丁文件,该文件重新编写代码以完全消除不灵活性。
Take a look a sample report here.
Insphpect is currently in the testing phase, and it would really help my research progress if you can check it out and complete the survey in the “Give your feedback” section of the site.
Insphpect目前处于测试阶段,如果您可以在网站的“提供您的反馈”部分中进行检查并完成调查,那将确实对我的研究进度有所帮助。
背景 (Background)
Are those bad practices really bad, though?
但是,那些不好的做法真的不好吗?
This was one of the more difficult parts of the background research, and you can read about how this was done in detail on the Insphpect website.
这是背景研究中比较困难的部分之一,您可以在Insphpect网站上详细了解如何进行此操作。
However, this can be summarized as:
但是,可以总结为:
- The opinions of each bad practice were collected from 100 authors per practice. 每种不良做法的意见均来自每种做法的100位作者。
- The author’s opinion on the practice was graded on a scale of 1–5. 作者对这种做法的意见等级为1-5。
- The author’s methodological rigor was graded on a scale of 1–7 based on the Jadad score used for clinical trials. 作者的方法严谨性基于用于临床试验的Jadad评分以1–7的等级评分。
These were then plotted like the graph below:
然后将它们绘制如下图所示:
![Singleton pattern results](https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2020/06/1593053893singleton-1024x612.png)
Each horizontal line represents an article, and the left (orange) bar for each article is the recommendation going from 5 — Avoid this practice at all costs (Far left) — to 1 — Favor this practice over alternatives.
每条水平线代表一篇文章,并且每篇文章的左(橙色)栏都是从5的建议-不惜一切代价避免这种做法(最左边)-1-将此做法优先于替代方案。
The right (blue) bar for each article is the Jadad style score measuring analytic rigor. A score of seven means the article describes the practice, provides code examples, discusses alternative approaches, provides like-for-like code samples, discusses the pros/cons of each approach and makes a recommendation of which approach should be used.
每篇文章的右侧(蓝色)栏是Jadad风格评分评估的严谨性。 得分为7分意味着本文描述了这种做法,提供了代码示例,讨论了替代方法,提供了类似代码示例,讨论了每种方法的利弊,并提出了应使用哪种方法的建议。
In the case of the singleton above, authors who compare the singleton to alternative approaches, discuss the pros/cons, etc., are significantly more likely to suggest using alternative approaches.
在上述单例的情况下,将单例与替代方法进行比较,讨论优缺点的作者更有可能建议使用替代方法。
演练 (Walkthrough)
Currently, Insphpect allows uploading code via a Git repository URL or a ZIP file.
当前,Insphpect允许通过Git存储库URL或ZIP文件上传代码。
So not to point out flaws in other people’s work, let’s take a look at one of my own projects to see what it identifies.
因此,为了不指出他人工作中的缺陷,让我们看一下我自己的项目中的一个,以了解它的含义。
We’ll use https://github.com/Level-2/Transphporm as an example project.
我们将使用https://github.com/Level-2/Transphporm作为示例项目。
This is quite a good example, because it has a very high score on another code-quality tool Scrutinizer.
这是一个很好的例子,因为它在另一个代码质量工具Scrutinizer上得分很高。
Firstly, enter the git URL https://github.com/Level-2/Transphporm
into the text box at the top of the home page and press “Go”. It will take a few seconds to minutes, depending on the size of the project, and will generate a report that looks something like this:
首先,在首页顶部的文本框中输入git URL https://github.com/Level-2/Transphporm
,然后按“ Go”。 这将需要几秒钟到几分钟的时间,具体取决于项目的大小,并将生成如下所示的报告:
![Transphporm Report](https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2020/06/1593053927report1.png)
Once you’re on the report page, you’ll see a summary at the top with an overall grade out of 100, with 100 being very good and 0 being very poor.
进入报告页面后,您会在顶部看到一个摘要,总体评分为100分,其中100分非常好,0分非常差。
Underneath the summary, you’ll see a list of all the classes in the project, each with its own grade.
在摘要下,您将看到项目中所有班级的列表,每个班级都有自己的等级。
Don’t worry if your code doesn’t get a perfect score. It’s unlikely that it will. Remember, Insphpect is a tool that identifies flexibility in your code. There are parts of your code (like the entry point) where flexibility isn’t warranted.
如果您的代码得分不高,请不要担心。 不太可能。 请记住,Insphpect是一种可识别代码灵活性的工具。 在代码的某些部分(例如入口点),不保证灵活性。
For Transphporm, it has highlighted issues in seven classes.
对于Transphporm,它在七个类中突出显示了问题。
Let’s take a look at some of those. Scroll down to Transphporm\Parser\CssToXpath
and click the link. You’ll see a score for that particular class and a list of issues which have been identified.
让我们来看看其中的一些。 向下滚动到Transphporm\Parser\CssToXpath
并单击链接。 您会看到该特定班级的分数以及已确定的问题列表。
In this case, it has identified a static variable and a static method. Clicking on one of the red lines will reveal an explanation of why the line was flagged up.
在这种情况下,它已经确定了静态变量和静态方法。 单击其中一条红线将显示该线被标记的原因的解释。
For example, clicking line 12 will give an explanation of why static variables are less flexible than instance variables.
例如,单击第12行将解释为什么静态变量不如实例变量灵活。
![Single class report](https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2020/06/1593053956report2.png)
Although there’s a more in-depth explanation of the issues caused by static properties on the report, as a quick refresher, static variables have one value which is shared across all the instances of the class.
尽管对报表上的静态属性引起的问题有更深入的说明,但作为快速回顾 ,静态变量具有一个在类的所有实例之间共享的值。
This is inherently less flexible than an instance variable, because using an instance variable allows each instance to have a different value.
这本质上比实例变量灵活,因为使用实例变量允许每个实例具有不同的值。
For example, consider the following:
例如,考虑以下内容:
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
]);
}
}
Because $db
is static, every instance of this class shares the same $db
instance and records will always be inserted into the same database.
因为$db
是静态的,所以此类的每个实例都共享相同的$db
实例,并且记录将始终插入到同一数据库中。
While this sounds reasonable, let me give you a real-world example.
虽然这听起来很合理,但让我举一个真实的例子。
在现实世界 (In the real world)
One of our clients was a recruitment agency. About two years after we developed their site, they took over another smaller company. They wanted to retain the second company’s website and branding because it was quite well known in the niche they were in.
我们的客户之一是一家招聘机构。 我们开发他们的网站大约两年后,他们接管了另一家较小的公司。 他们想保留第二家公司的网站和品牌,因为在他们所处的细分市场中众所周知。
Our client asked us the following:
客户要求我们以下内容:
On the second company’s site, can you add a checkbox when adding a job that also adds the job to our database so people viewing our site can also see the job and visa versa.
在第二家公司的网站上,您可以在添加作业时添加一个复选框,并将该作业也添加到我们的数据库中,以便查看我们网站的人也可以看到该作业,反之亦然。
A fairly simple request. Run an insert query into two different databases.
一个相当简单的请求。 对两个不同的数据库运行插入查询。
But because the website used a static global database instance, this was needlessly difficult!
但是因为该网站使用了静态的全局数据库实例,所以这毫无困难!
The developers of that site wrote the code confident that only one database connection would ever be needed. They were wrong.
该站点的开发人员在编写代码时就确信只需要一个数据库连接。 他们错了。
Remember, you’re not clairvoyant, and it’s impossible to anticipate what flexibility may be needed in the future.
请记住,您不是千篇一律的,因此无法预期将来可能需要什么灵活性。
解决方案 (The solution)
As suggested by Insphpect, the solution to this is using instance variables:
正如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
]);
}
}
Now a User
instance can be used with different database instances:
现在, User
实例可以与不同的数据库实例一起使用:
new User($database1);
new User($database2);
For Transphporm\Parser\CssToXpath
we could do the same, remove the static variable and consider making it an instance variable rather than a static variable.
对于Transphporm\Parser\CssToXpath
我们可以这样做,删除静态变量,并考虑使其成为实例变量,而不是静态变量。
在构造函数中使用new (Using new in constructor)
Let’s take a look at one of the other classes: Transphporm\Builder
.
让我们看一下其他类之一: Transphporm\Builder
。
![Builder class report](https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2020/06/1593053983report3.png)
This has a score of zero, which is rather poor. Examining the report in detail, Insphpect has picked up the same issue three times: using the new
keyword in a constructor.
分数为零,这很差。 Insphpect详细检查了该报告,已三度提出相同的问题:在构造函数中使用new
关键字。
Google Programming Coach Misko Hevery does a great job at explaining why this is a poor programming practice, but here’s a simple example from Insphpect’s output:
Google编程教练Misko Hevery在解释为什么这是糟糕的编程实践方面做得很好,但这是 Insphpect输出的一个简单示例:
class Car {
private $engine;
public function __construct() {
$this->engine = new PetrolEngine();
}
}
Here, whenever an instance of Car
is created, an instance of PetrolEngine
is created. That makes it very inflexible, because there ’s no way to construct a Car
with a different engine type. Every car modeled in this system must have a PetrolEngine
.
在这里,无论何时创建Car
实例, PetrolEngine
创建PetrolEngine
实例。 这使得它非常不灵活,因为无法构造具有不同引擎类型的Car
。 在此系统中建模的每辆汽车都必须具有PetrolEngine
。
Instead, we can use dependency injection:
相反,我们可以使用依赖注入:
class Car {
private $engine;
public function __construct($engine) {
$this->engine = $engine;
}
}
Different cars can be created with an instance of PetrolEngine
, DieselEngine
, ElectricEngine
, JetEngine
or any other engine type that exists in the project.
可以使用PetrolEngine
, DieselEngine
, ElectricEngine
, JetEngine
或项目中存在的任何其他引擎类型的实例创建不同的汽车。
To fix this error in the Transphporm\Builder
, all of the variables that currently have hard-coded class names should use constructor arguments instead.
要在Transphporm\Builder
解决此错误,当前具有硬编码类名的所有变量都应改用构造函数参数。
There are other issues identified by Insphpect, but you can try it out for yourself and see how your project fares.
Insphpect还确定了其他问题,但是您可以自己尝试一下,看看您的项目进展如何。
幕后花絮 (Behind the Scenes)
You might be wondering how the scores are calculated and why this class got a zero. At the present time, the weightings are subject to change once more projects have been scanned and more feedback has been provided.
您可能想知道如何计算分数,以及为什么该类的分数为零。 目前,一旦扫描了更多项目并提供了更多反馈,权重可能会发生变化。
The scores are designed to be indicative for comparing one project/class to another.
分数旨在指示一个项目/班级与另一个项目/班级的比较。
The overall project score is just an average of all the classes in the project. This was implemented because a project with two issues in 1000 classes is a lot better overall than a project with two issues in two classes.
项目的总体分数只是该项目所有班级的平均分数。 之所以能够实现这一目标,是因为一个项目在1000个班级中有两个问题比一个项目在两个班级中有两个问题要好得多。
Each bad practice is weighted based on whether it impedes flexibility for the entire class or only impedes flexibility for a method.
根据是否不利于整个类的灵活性或仅不利于方法的灵活性来对每种不良做法进行加权。
结论 (Conclusion)
Insphpect can be used to identify areas of your code which make future changes more difficult than they could be, and it offers suggestions on how to write the code in a more flexible manner. Remember, you’re not clairvoyant and have no way to know how your code is going to need to change!
Insphpect可用于识别代码区域,这些区域使将来的更改变得更加困难,并且为如何以更灵活的方式编写代码提供了建议。 请记住,您不是千篇一律,也没有办法知道您的代码将如何进行更改!
Insphpect is currently a work in progress, and the more people who use it (and complete the survey) the better it will become.
Insphpect目前仍在开发中,使用它(并完成调查)的人越多,它将变得越好。
How did your project or favorite library score? Be sure to complete the survey, as it will provide valuable data for my PhD project and help the tool improve!
您的项目或收藏的图书馆如何评分? 确保完成调查,因为它会为我的博士项目提供有价值的数据并帮助改善工具!
翻译自: https://www.sitepoint.com/how-to-ensure-flexible-reusable-php-code-with-insphpect/