- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章PHP 代码简洁之道(小结)由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
Robert C.Martin's 的 软件工程师准则 Clean Code 同样适用于 PHP。它并不是一个编码风格指南,它指导我们用 PHP 写出具有可读性,可复用性且可分解的代码.
并非所有的准则都必须严格遵守,甚至一些已经成为普遍的约定。这仅仅作为指导方针,其中许多都是 Clean Code 作者们多年来的经验.
灵感来自于 clean-code-javascript 。
尽管许多开发者依旧使用 PHP 5 版本,但是这篇文章中绝大多数例子都是只能在 PHP 7.1 + 版本下运行.
使用有意义的且可读的变量名 。
不友好的:
1
|
$ymdstr
=
$moment
->format(
'y-m-d'
);
|
友好的:
1
|
$currentDate
=
$moment
->format(
'y-m-d'
);
|
对同类型的变量使用相同的词汇 。
不友好的:
1
2
3
4
|
getUserInfo();
getUserData();
getUserRecord();
getUserProfile();
|
友好的:
1
|
getUser();
|
使用可搜索的名称(第一部分) 。
我们阅读的代码超过我们写的代码。所以我们写出的代码需要具备可读性、可搜索性,这一点非常重要。要我们去理解程序中没有名字的变量是非常头疼的。让你的变量可搜索吧! 。
不具备可读性的代码:
1
2
|
// 见鬼的 448 是什么意思?
$result
=
$serializer
->serialize(
$data
, 448);
|
具备可读性的:
1
|
$json
=
$serializer
-
>serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
使用可搜索的名称(第二部分) 。
不好的:
1
2
3
4
|
// 见鬼的 4 又是什么意思?
if
(
$user
->access & 4) {
// ...
}
|
好的方式:
1
2
3
4
5
6
7
8
9
10
11
|
class
User
{
const
ACCESS_READ = 1;
const
ACCESS_CREATE = 2;
const
ACCESS_UPDATE = 4;
const
ACCESS_DELETE = 8;
}
if
(
$user
->access & User::ACCESS_UPDATE) {
// do edit ...
}
|
使用解释性变量 。
不好:
1
2
3
4
5
|
$address
=
'One Infinite Loop, Cupertino 95014'
;
$cityZipCodeRegex
=
'/^[^,]+,\s*(.+?)\s*(\d{5})$/'
;
preg_match(
$cityZipCodeRegex
,
$address
,
$matches
);
saveCityZipCode(
$matches
[1],
$matches
[2]);
|
一般:
这个好点,但我们仍严重依赖正则表达式.
1
2
3
4
5
6
|
$address
=
'One Infinite Loop, Cupertino 95014'
;
$cityZipCodeRegex
=
'/^[^,]+,\s*(.+?)\s*(\d{5})$/'
;
preg_match(
$cityZipCodeRegex
,
$address
,
$matches
);
[,
$city
,
$zipCode
] =
$matches
;
saveCityZipCode(
$city
,
$zipCode
);
|
很棒:
通过命名子模式减少对正则表达式的依赖.
1
2
3
4
5
|
$address
=
'One Infinite Loop, Cupertino 95014'
;
$cityZipCodeRegex
=
'/^[^,]+,\s*(?<city>.+?)\s*(?<zipCode>\d{5})$/'
;
preg_match(
$cityZipCodeRegex
,
$address
,
$matches
);
saveCityZipCode(
$matches
[
'city'
],
$matches
[
'zipCode'
]);
|
避免嵌套太深和提前返回 (第一部分) 。
使用太多 if else 表达式会导致代码难以理解.
明确优于隐式.
不好
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
function
isShopOpen(
$day
): bool
{
if
(
$day
) {
if
(
is_string
(
$day
)) {
$day
=
strtolower
(
$day
);
if
(
$day
===
'friday'
) {
return
true;
}
elseif
(
$day
===
'saturday'
) {
return
true;
}
elseif
(
$day
===
'sunday'
) {
return
true;
}
else
{
return
false;
}
}
else
{
return
false;
}
}
else
{
return
false;
}
}
|
很棒:
1
2
3
4
5
6
7
8
9
10
11
12
|
function
isShopOpen(string
$day
): bool
{
if
(
empty
(
$day
)) {
return
false;
}
$openingDays
= [
'friday'
,
'saturday'
,
'sunday'
];
return
in_array(
strtolower
(
$day
),
$openingDays
, true);
}
|
避免嵌套太深和提前返回 (第二部分) 。
不好:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
function
fibonacci(int
$n
)
{
if
(
$n
< 50) {
if
(
$n
!== 0) {
if
(
$n
!== 1) {
return
fibonacci(
$n
- 1) + fibonacci(
$n
- 2);
}
else
{
return
1;
}
}
else
{
return
0;
}
}
else
{
return
'Not supported'
;
}
}
|
很棒:
1
2
3
4
5
6
7
8
9
10
11
12
|
function
fibonacci(int
$n
): int
{
if
(
$n
=== 0 ||
$n
=== 1) {
return
$n
;
}
if
(
$n
> 50) {
throw
new
\Exception(
'Not supported'
);
}
return
fibonacci(
$n
- 1) + fibonacci(
$n
- 2);
}
|
避免心理映射 。
不要迫使你的代码阅读者翻译变量的意义.
明确优于隐式.
不好:
1
2
3
4
5
6
7
8
9
10
11
12
|
$l
= [
'Austin'
,
'New York'
,
'San Francisco'
];
for
(
$i
= 0;
$i
<
count
(
$l
);
$i
++) {
$li
=
$l
[
$i
];
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
// Wait, what is `$li` for again?
dispatch(
$li
);
}
|
很棒:
1
2
3
4
5
6
7
8
9
10
|
$locations
= [
'Austin'
,
'New York'
,
'San Francisco'
];
foreach
(
$locations
as
$location
) {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
dispatch(
$location
);
}
|
不要增加不需要的上下文 。
如果类名或对象名告诉你某些东西后,请不要在变量名中重复.
小坏坏:
1
2
3
4
5
6
7
8
|
class
Car
{
public
$carMake
;
public
$carModel
;
public
$carColor
;
//...
}
|
好的方式:
1
2
3
4
5
6
7
8
|
class
Car
{
public
$make
;
public
$model
;
public
$color
;
//...
}
|
使用默认参数而不是使用短路运算或者是条件判断 。
不好的做法
这是不太好的因为 $breweryName 可以是 NULL. 。
1
2
3
4
|
function
createMicrobrewery(
$breweryName
=
'Hipster Brew Co.'
): void
{
// ...
}
|
还算可以的做法
这个做法比上面的更加容易理解,但是它需要很好的去控制变量的值. 。
1
2
3
4
5
|
function
createMicrobrewery(
$name
= null): void
{
$breweryName
=
$name
?:
'Hipster Brew Co.'
;
// ...
}
|
好的做法
你可以使用 类型提示 而且可以保证 $breweryName 不会为空 NULL. 。
1
2
3
4
|
function
createMicrobrewery(string
$breweryName
=
'Hipster Brew Co.'
): void
{
// ...
}
|
使用 相等运算符 。
不好的做法
1
2
|
$a
=
'42'
;
$b
= 42;
|
使用简单的相等运算符会把字符串类型转换成数字类型 。
1
2
3
|
if
(
$a
!=
$b
) {
//这个条件表达式总是会通过
}
|
表达式 $a != $b 会返回 false 但实际上它应该是 true ! 字符串类型 '42' 是不同于数字类型的 42 。
好的做法: 使用全等运算符会对比类型和值 。
1
2
3
|
if
(
$a
!==
$b
) {
//这个条件是通过的
}
|
表达式 $a !== $b 会返回 true.
函数参数(2 个或更少) 。
限制函数参数个数极其重要 这样测试你的函数容易点。有超过 3 个可选参数会导致一个爆炸式组合增长,你会有成吨独立参数情形要测试.
无参数是理想情况。1 个或 2 个都可以,最好避免 3 个。 再多就需要加固了。通常如果你的函数有超过两个参数,说明他要处理的事太多了。 如果必须要传入很多数据,建议封装一个高级别对象作为参数.
不友好的
1
2
3
4
|
function
createMenu(string
$title
, string
$body
, string
$buttonText
, bool
$cancellable
): void
{
// ...
}
|
友好的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class
MenuConfig
{
public
$title
;
public
$body
;
public
$buttonText
;
public
$cancellable
= false;
}
$config
=
new
MenuConfig();
$config
->title =
'Foo'
;
$config
->body =
'Bar'
;
$config
->buttonText =
'Baz'
;
$config
->cancellable = true;
function
createMenu(MenuConfig
$config
): void
{
// ...
}
|
函数应该只做一件事情 。
这是迄今为止软件工程最重要的原则。函数做了超过一件事情时,它们将变得难以编写、测试、推导。 而函数只做一件事情时,重构起来则非常简单,同时代码阅读起来也非常清晰。掌握了这个原则,你就会领先许多其他的开发者.
不好的
1
2
3
4
5
6
7
8
9
|
function
emailClients(
array
$clients
): void
{
foreach
(
$clients
as
$client
) {
$clientRecord
=
$db
->find(
$client
);
if
(
$clientRecord
->isActive()) {
email(
$client
);
}
}
}
|
好的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
function
emailClients(
array
$clients
): void
{
$activeClients
= activeClients(
$clients
);
array_walk
(
$activeClients
,
'email'
);
}
function
activeClients(
array
$clients
):
array
{
return
array_filter
(
$clients
,
'isClientActive'
);
}
function
isClientActive(int
$client
): bool
{
$clientRecord
=
$db
->find(
$client
);
return
$clientRecord
->isActive();
}
|
函数的名称要说清楚它做什么 。
不好的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class
Email
{
//...
public
function
handle(): void
{
mail(
$this
->to,
$this
->subject,
$this
->body);
}
}
$message
=
new
Email(...);
// What is this? A handle for the message? Are we writing to a file now?
$message
->handle();
|
很好的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class
Email
{
//...
public
function
send(): void
{
mail(
$this
->to,
$this
->subject,
$this
->body);
}
}
$message
=
new
Email(...);
// Clear and obvious
$message
->send();
|
函数只能是一个抽象级别 。
当你有多个抽象层次时,你的函数功能通常是做太多了。 分割函数功能使得重用性和测试更加容易。. 。
不好
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
function
parseBetterJSAlternative(string
$code
): void
{
$regexes
= [
// ...
];
$statements
=
explode
(
' '
,
$code
);
$tokens
= [];
foreach
(
$regexes
as
$regex
) {
foreach
(
$statements
as
$statement
) {
// ...
}
}
$ast
= [];
foreach
(
$tokens
as
$token
) {
// lex...
}
foreach
(
$ast
as
$node
) {
// parse...
}
}
|
同样不是很好
我们已经完成了一些功能,但是 parseBetterJSAlternative() 功能仍然非常复杂,测试起来也比较麻烦.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
function
tokenize(string
$code
):
array
{
$regexes
= [
// ...
];
$statements
=
explode
(
' '
,
$code
);
$tokens
= [];
foreach
(
$regexes
as
$regex
) {
foreach
(
$statements
as
$statement
) {
$tokens
[] =
/* ... */
;
}
}
return
$tokens
;
}
function
lexer(
array
$tokens
):
array
{
$ast
= [];
foreach
(
$tokens
as
$token
) {
$ast
[] =
/* ... */
;
}
return
$ast
;
}
function
parseBetterJSAlternative(string
$code
): void
{
$tokens
= tokenize(
$code
);
$ast
= lexer(
$tokens
);
foreach
(
$ast
as
$node
) {
// parse...
}
}
|
很好的
最好的解决方案是取出 parseBetterJSAlternative() 函数的依赖关系. 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
class
Tokenizer
{
public
function
tokenize(string
$code
):
array
{
$regexes
= [
// ...
];
$statements
=
explode
(
' '
,
$code
);
$tokens
= [];
foreach
(
$regexes
as
$regex
) {
foreach
(
$statements
as
$statement
) {
$tokens
[] =
/* ... */
;
}
}
return
$tokens
;
}
}
class
Lexer
{
public
function
lexify(
array
$tokens
):
array
{
$ast
= [];
foreach
(
$tokens
as
$token
) {
$ast
[] =
/* ... */
;
}
return
$ast
;
}
}
class
BetterJSAlternative
{
private
$tokenizer
;
private
$lexer
;
public
function
__construct(Tokenizer
$tokenizer
, Lexer
$lexer
)
{
$this
->tokenizer =
$tokenizer
;
$this
->lexer =
$lexer
;
}
public
function
parse(string
$code
): void
{
$tokens
=
$this
->tokenizer->tokenize(
$code
);
$ast
=
$this
->lexer->lexify(
$tokens
);
foreach
(
$ast
as
$node
) {
// parse...
}
}
}
|
不要用标示作为函数的参数 。
标示就是在告诉大家,这个方法里处理很多事。前面刚说过,一个函数应当只做一件事。 把不同标示的代码拆分到多个函数里.
不友好的
1
2
3
4
5
6
7
8
|
function
createFile(string
$name
, bool
$temp
= false): void
{
if
(
$temp
) {
touch(
'./temp/'
.
$name
);
}
else
{
touch(
$name
);
}
}
|
友好的
1
2
3
4
5
6
7
8
9
|
function
createFile(string
$name
): void
{
touch(
$name
);
}
function
createTempFile(string
$name
): void
{
touch(
'./temp/'
.
$name
);
}
|
避免副作用 。
一个函数应该只获取数值,然后返回另外的数值,如果在这个过程中还做了其他的事情,我们就称为副作用。副作用可能是写入一个文件,修改某些全局变量,或者意外的把你全部的钱给了陌生人.
现在,你的确需要在一个程序或者场合里要有副作用,像之前的例子,你也许需要写一个文件。你需要做的是把你做这些的地方集中起来。不要用几个函数和类来写入一个特定的文件。只允许使用一个服务来单独实现.
重点是避免常见陷阱比如对象间共享无结构的数据、使用可以写入任何的可变数据类型、不集中去处理这些副作用。如果你做了这些你就会比大多数程序员快乐.
不好的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// 这个全局变量在函数中被使用
// 如果我们在别的方法中使用这个全局变量,有可能我们会不小心将其修改为数组类型
$name
=
'Ryan McDermott'
;
function
splitIntoFirstAndLastName(): void
{
global
$name
;
$name
=
explode
(
' '
,
$name
);
}
splitIntoFirstAndLastName();
var_dump(
$name
);
// ['Ryan', 'McDermott'];
|
推荐的
1
2
3
4
5
6
7
8
9
10
|
function
splitIntoFirstAndLastName(string
$name
):
array
{
return
explode
(
' '
,
$name
);
}
$name
=
'Ryan McDermott'
;
$newName
= splitIntoFirstAndLastName(
$name
);
var_dump(
$name
);
// 'Ryan McDermott';
var_dump(
$newName
);
// ['Ryan', 'McDermott'];
|
不要定义全局函数 。
在很多语言中定义全局函数是一个坏习惯,因为你定义的全局函数可能与其他人的函数库冲突,并且,除非在实际运用中遇到异常,否则你的 API 的使用者将无法觉察到这一点。接下来我们来看看一个例子:当你想有一个配置数组,你可能会写一个 config() 的全局函数,但是这样会与其他人定义的库冲突.
不好的
1
2
3
4
5
6
|
function
config():
array
{
return
[
'foo'
=>
'bar'
,
]
}
|
好的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class
Configuration
{
private
$configuration
= [];
public
function
__construct(
array
$configuration
)
{
$this
->configuration =
$configuration
;
}
public
function
get(string
$key
): ?string
{
return
isset(
$this
->configuration[
$key
]) ?
$this
->configuration[
$key
] : null;
}
}
|
获取配置需要先创建 Configuration 类的实例,如下:
1
2
3
|
$configuration
=
new
Configuration([
'foo'
=>
'bar'
,
]);
|
现在,在你的应用中必须使用 Configuration 的实例了.
不要使用单例模式 。
单例模式是个 反模式。 以下转述 Brian Button 的观点:
单例模式常用于 全局实例, 这么做为什么不好呢? 因为在你的代码里 你隐藏了应用的依赖关系,而没有通过接口公开依赖关系 。避免全局的东西扩散使用是一种 代码味道. 单例模式违反了 单一责任原则: 依据的事实就是 单例模式自己控制自身的创建和生命周期. 单例模式天生就导致代码紧 耦合。这使得在许多情况下用伪造的数据 难于测试。 单例模式的状态会留存于应用的整个生命周期。 这会对测试产生第二次打击,你只能让被严令需要测试的代码运行不了收场,根本不能进行单元测试。为何?因为每一个单元测试应该彼此独立。 还有些来自 Misko Hevery 的深入思考,关于单例模式的问题根源.
不好的示范:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class
DBConnection
{
private
static
$instance
;
private
function
__construct(string
$dsn
)
{
// ...
}
public
static
function
getInstance(): DBConnection
{
if
(self::
$instance
=== null) {
self::
$instance
=
new
self();
}
return
self::
$instance
;
}
// ...
}
$singleton
= DBConnection::getInstance();
|
好的示范:
1
2
3
4
5
6
7
8
9
|
class
DBConnection
{
public
function
__construct(string
$dsn
)
{
// ...
}
// ...
}
|
用 DSN 进行配置创建的 DBConnection 类实例.
1
|
$connection
=
new
DBConnection(
$dsn
);
|
现在就必须在你的应用中使用 DBConnection 的实例了.
封装条件语句 。
不友好的
1
2
3
|
if
(
$article
->state ===
'published'
) {
// ...
}
|
友好的
1
2
3
|
if
(
$article
->isPublished()) {
// ...
}
|
避免用反义条件判断 。
不友好的
1
2
3
4
5
6
7
8
9
|
function
isDOMNodeNotPresent(\DOMNode
$node
): bool
{
// ...
}
if
(!isDOMNodeNotPresent(
$node
))
{
// ...
}
|
友好的
1
2
3
4
5
6
7
8
|
function
isDOMNodePresent(\DOMNode
$node
): bool
{
// ...
}
if
(isDOMNodePresent(
$node
)) {
// ...
}
|
避免使用条件语句 。
这听起来像是个不可能实现的任务。 当第一次听到这个时,大部分人都会说,“没有 if 语句,我该怎么办?” 答案就是在很多情况下你可以使用多态性来实现同样的任务。 接着第二个问题来了, “听着不错,但我为什么需要那样做?”,这个答案就是我们之前所学的干净代码概念:一个函数应该只做一件事情。如果你的类或函数有 if 语句,这就告诉了使用者你的类或函数干了不止一件事情。 记住,只要做一件事情.
不好的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class
Airplane
{
// ...
public
function
getCruisingAltitude(): int
{
switch
(
$this
->type) {
case
'777'
:
return
$this
->getMaxAltitude() -
$this
->getPassengerCount();
case
'Air Force One'
:
return
$this
->getMaxAltitude();
case
'Cessna'
:
return
$this
->getMaxAltitude() -
$this
->getFuelExpenditure();
}
}
}
|
好的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
interface
Airplane
{
// ...
public
function
getCruisingAltitude(): int;
}
class
Boeing777
implements
Airplane
{
// ...
public
function
getCruisingAltitude(): int
{
return
$this
->getMaxAltitude() -
$this
->getPassengerCount();
}
}
class
AirForceOne
implements
Airplane
{
// ...
public
function
getCruisingAltitude(): int
{
return
$this
->getMaxAltitude();
}
}
class
Cessna
implements
Airplane
{
// ...
public
function
getCruisingAltitude(): int
{
return
$this
->getMaxAltitude() -
$this
->getFuelExpenditure();
}
}
|
避免类型检测 (第 1 部分) 。
PHP 是无类型的,这意味着你的函数可以接受任何类型的参数。 有时这种自由会让你感到困扰,并且他会让你自然而然的在函数中使用类型检测。有很多方法可以避免这么做。 首先考虑 API 的一致性.
不好的:
1
2
3
4
5
6
7
8
|
function
travelToTexas(
$vehicle
): void
{
if
(
$vehicle
instanceof
Bicycle) {
$vehicle
->pedalTo(
new
Location(
'texas'
));
}
elseif
(
$vehicle
instanceof
Car) {
$vehicle
->driveTo(
new
Location(
'texas'
));
}
}
|
好的:
1
2
3
4
|
function
travelToTexas(Traveler
$vehicle
): void
{
$vehicle
->travelTo(
new
Location(
'texas'
));
}
|
避免类型检查(第 2 部分) 。
如果你正在使用像 字符串、数值、或数组这样的基础类型,你使用的是 PHP 版本是 PHP 7+,并且你不能使用多态,但仍然觉得需要使用类型检测,这时,你应该考虑 类型定义 或 严格模式。它为您提供了标准 PHP 语法之上的静态类型。 手动进行类型检查的问题是做这件事需要这么多的额外言辞,你所得到的虚假的『类型安全』并不能弥补丢失的可读性。保持你的代码简洁,编写良好的测试,并且拥有好的代码审查。 否则,使用 PHP 严格的类型声明或严格模式完成所有这些工作.
不好的:
1
2
3
4
5
6
7
8
|
function
combine(
$val1
,
$val2
): int
{
if
(!
is_numeric
(
$val1
) || !
is_numeric
(
$val2
)) {
throw
new
\Exception(
'Must be of type Number'
);
}
return
$val1
+
$val2
;
}
|
好的:
1
2
3
4
|
function
combine(int
$val1
, int
$val2
): int
{
return
$val1
+
$val2
;
}
|
移除无用代码 。
无用代码和重复代码一样糟糕。 如果没有被调用,就应该把它删除掉,没必要将它保留在你的代码库中!当你需要它的时候,可以在你的历史版本中找到它.
Bad
1
2
3
4
5
6
7
8
9
10
11
12
|
function
oldRequestModule(string
$url
): void
{
// ...
}
function
newRequestModule(string
$url
): void
{
// ...
}
$request
= newRequestModule(
$requestUrl
);
inventoryTracker(
'apples'
,
$request
,
'www.inventory-awesome.io'
);
|
Good
1
2
3
4
5
6
7
|
function
requestModule(string
$url
): void
{
// ...
}
$request
= requestModule(
$requestUrl
);
inventoryTracker(
'apples'
,
$request
,
'www.inventory-awesome.io'
);
|
使用对象封装 。
在 PHP 中,你可以在方法中使用关键字,如 public, protected and private。 使用它们,你可以任意的控制、修改对象的属性.
当你除获取对象属性外还想做更多的操作时,你不需要修改你的代码 当 set 属性时,易于增加参数验证。 封装的内部表示。 容易在获取和设置属性时添加日志和错误处理。 继承这个类,你可以重写默认信息。 你可以延迟加载对象的属性,比如从服务器获取数据。 此外,这样的方式也符合 OOP 开发中的 [开闭原则](# 开闭原则 (OCP)) 。
不好的
1
2
3
4
5
6
7
8
9
|
class
BankAccount
{
public
$balance
= 1000;
}
$bankAccount
=
new
BankAccount();
// Buy shoes...
$bankAccount
->balance -= 100;
|
好的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
class
BankAccount
{
private
$balance
;
public
function
__construct(int
$balance
= 1000)
{
$this
->balance =
$balance
;
}
public
function
withdraw(int
$amount
): void
{
if
(
$amount
>
$this
->balance) {
throw
new
\Exception(
'Amount greater than available balance.'
);
}
$this
->balance -=
$amount
;
}
public
function
deposit(int
$amount
): void
{
$this
->balance +=
$amount
;
}
public
function
getBalance(): int
{
return
$this
->balance;
}
}
$bankAccount
=
new
BankAccount();
// Buy shoes...
$bankAccount
->withdraw(
$shoesPrice
);
// Get balance
$balance
=
$bankAccount
->getBalance();
|
让对象拥有 private/protected 属性的成员 。
因此,请默认使用 private 属性,只有当需要对外部类提供访问属性的时候才采用 public/protected 属性.
更多的信息可以参考Fabien Potencier 写的针对这个专栏的文章blog post . 。
Bad
1
2
3
4
5
6
7
8
9
10
11
12
|
class
Employee
{
public
$name
;
public
function
__construct(string
$name
)
{
$this
->name =
$name
;
}
}
$employee
=
new
Employee(
'John Doe'
);
echo
'Employee name: '
.
$employee
->name;
// Employee name: John Doe
|
Good
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class
Employee
{
private
$name
;
public
function
__construct(string
$name
)
{
$this
->name =
$name
;
}
public
function
getName(): string
{
return
$this
->name;
}
}
$employee
=
new
Employee(
'John Doe'
);
echo
'Employee name: '
.
$employee
->getName();
// Employee name: John Doe
|
组合优于继承 。
正如 the Gang of Four 所著的 设计模式 中所说, 我们应该尽量优先选择组合而不是继承的方式。使用继承和组合都有很多好处。 这个准则的主要意义在于当你本能的使用继承时,试着思考一下组合是否能更好对你的需求建模。 在一些情况下,是这样的.
接下来你或许会想,“那我应该在什么时候使用继承?” 答案依赖于你的问题,当然下面有一些何时继承比组合更好的说明:
糟糕的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
class
Employee
{
private
$name
;
private
$email
;
public
function
__construct(string
$name
, string
$email
)
{
$this
->name =
$name
;
$this
->email =
$email
;
}
// ...
}
// 不好,因为Employees "有" taxdata
// 而EmployeeTaxData不是Employee类型的
class
EmployeeTaxData
extends
Employee
{
private
$ssn
;
private
$salary
;
public
function
__construct(string
$name
, string
$email
, string
$ssn
, string
$salary
)
{
parent::__construct(
$name
,
$email
);
$this
->ssn =
$ssn
;
$this
->salary =
$salary
;
}
// ...
}
|
棒棒哒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
class
EmployeeTaxData
{
private
$ssn
;
private
$salary
;
public
function
__construct(string
$ssn
, string
$salary
)
{
$this
->ssn =
$ssn
;
$this
->salary =
$salary
;
}
// ...
}
class
Employee
{
private
$name
;
private
$email
;
private
$taxData
;
public
function
__construct(string
$name
, string
$email
)
{
$this
->name =
$name
;
$this
->email =
$email
;
}
public
function
setTaxData(string
$ssn
, string
$salary
)
{
$this
->taxData =
new
EmployeeTaxData(
$ssn
,
$salary
);
}
// ...
}
|
避免流式接口 。
流式接口 是一种面向对象 API 的方法,旨在通过方法链 Method chaining 来提高源代码的可阅读性. 。
流式接口虽然需要一些上下文,需要经常构建对象,但是这种模式减少了代码的冗余度 (例如: PHPUnit Mock Builder 或 Doctrine Query Builder) 。
但是同样它也带来了很多麻烦
更多信息可以参考 Marco Pivetta 撰写的关于这个专题的文章blog post 。
Bad
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
class
Car
{
private
$make
=
'Honda'
;
private
$model
=
'Accord'
;
private
$color
=
'white'
;
public
function
setMake(string
$make
): self
{
$this
->make =
$make
;
// NOTE: Returning this for chaining
return
$this
;
}
public
function
setModel(string
$model
): self
{
$this
->model =
$model
;
// NOTE: Returning this for chaining
return
$this
;
}
public
function
setColor(string
$color
): self
{
$this
->color =
$color
;
// NOTE: Returning this for chaining
return
$this
;
}
public
function
dump(): void
{
var_dump(
$this
->make,
$this
->model,
$this
->color);
}
}
$car
= (
new
Car())
->setColor(
'pink'
)
->setMake(
'Ford'
)
->setModel(
'F-150'
)
->dump();
|
Good
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
class
Car
{
private
$make
=
'Honda'
;
private
$model
=
'Accord'
;
private
$color
=
'white'
;
public
function
setMake(string
$make
): void
{
$this
->make =
$make
;
}
public
function
setModel(string
$model
): void
{
$this
->model =
$model
;
}
public
function
setColor(string
$color
): void
{
$this
->color =
$color
;
}
public
function
dump(): void
{
var_dump(
$this
->make,
$this
->model,
$this
->color);
}
}
$car
=
new
Car();
$car
->setColor(
'pink'
);
$car
->setMake(
'Ford'
);
$car
->setModel(
'F-150'
);
$car
->dump();
|
SOLID 。
SOLID 是 Michael Feathers 推荐的便于记忆的首字母简写,它代表了 Robert Martin 命名的最重要的五个面向对象编程设计原则:
职责单一原则 Single Responsibility Principle (SRP) 。
正如 Clean Code 书中所述,"修改一个类应该只为一个理由"。人们总是容易去用一堆方法 "塞满" 一个类,就好像当我们坐飞机上只能携带一个行李箱时,会把所有的东西都塞到这个箱子里。这样做带来的后果是:从逻辑上讲,这样的类不是高内聚的,并且留下了很多以后去修改它的理由.
将你需要修改类的次数降低到最小很重要,这是因为,当类中有很多方法时,修改某一处,你很难知晓在整个代码库中有哪些依赖于此的模块会被影响.
比较糟
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class
UserSettings
{
private
$user
;
public
function
__construct(User
$user
)
{
$this
->user =
$user
;
}
public
function
changeSettings(
array
$settings
): void
{
if
(
$this
->verifyCredentials()) {
// ...
}
}
private
function
verifyCredentials(): bool
{
// ...
}
}
|
棒棒哒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
class
UserAuth
{
private
$user
;
public
function
__construct(User
$user
)
{
$this
->user =
$user
;
}
public
function
verifyCredentials(): bool
{
// ...
}
}
class
UserSettings
{
private
$user
;
private
$auth
;
public
function
__construct(User
$user
)
{
$this
->user =
$user
;
$this
->auth =
new
UserAuth(
$user
);
}
public
function
changeSettings(
array
$settings
): void
{
if
(
$this
->auth->verifyCredentials()) {
// ...
}
}
}
|
开闭原则 (OCP) 。
如 Bertrand Meyer 所述,"软件实体 (类,模块,功能,等) 应该对扩展开放,但对修改关闭." 这意味着什么?这个原则大体上是指你应该允许用户在不修改已有代码情况下添加功能. 。
坏的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
abstract
class
Adapter
{
protected
$name
;
public
function
getName(): string
{
return
$this
->name;
}
}
class
AjaxAdapter
extends
Adapter
{
public
function
__construct()
{
parent::__construct();
$this
->name =
'ajaxAdapter'
;
}
}
class
NodeAdapter
extends
Adapter
{
public
function
__construct()
{
parent::__construct();
$this
->name =
'nodeAdapter'
;
}
}
class
HttpRequester
{
private
$adapter
;
public
function
__construct(Adapter
$adapter
)
{
$this
->adapter =
$adapter
;
}
public
function
fetch(string
$url
): Promise
{
$adapterName
=
$this
->adapter->getName();
if
(
$adapterName
===
'ajaxAdapter'
) {
return
$this
->makeAjaxCall(
$url
);
}
elseif
(
$adapterName
===
'httpNodeAdapter'
) {
return
$this
->makeHttpCall(
$url
);
}
}
private
function
makeAjaxCall(string
$url
): Promise
{
// request and return promise
}
private
function
makeHttpCall(string
$url
): Promise
{
// request and return promise
}
}
|
好的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
interface
Adapter
{
public
function
request(string
$url
): Promise;
}
class
AjaxAdapter
implements
Adapter
{
public
function
request(string
$url
): Promise
{
// request and return promise
}
}
class
NodeAdapter
implements
Adapter
{
public
function
request(string
$url
): Promise
{
// request and return promise
}
}
class
HttpRequester
{
private
$adapter
;
public
function
__construct(Adapter
$adapter
)
{
$this
->adapter =
$adapter
;
}
public
function
fetch(string
$url
): Promise
{
return
$this
->adapter->request(
$url
);
}
}
|
里氏代换原则 (LSP) 。
这是一个简单概念的可怕术语。它通常被定义为 “如果 S 是 T 的一个子类型,则 T 型对象可以替换为 S 型对象” (i.e., S 类型的对象可以替换 T 型对象) 在不改变程序的任何理想属性的情况下 (正确性,任务完成度,etc.)." 这是一个更可怕的定义. 这个的最佳解释是,如果你有个父类和一个子类,然后父类和子类可以互换使用而不会得到不正确的结果。这或许依然令人疑惑,所以我们来看下经典的正方形 - 矩形例子。几何定义,正方形是矩形,但是,如果你通过继承建立了 “IS-a” 关系的模型,你很快就会陷入麻烦。. 。
不好的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
class
Rectangle
{
protected
$width
= 0;
protected
$height
= 0;
public
function
render(int
$area
): void
{
// ...
}
public
function
setWidth(int
$width
): void
{
$this
->width =
$width
;
}
public
function
setHeight(int
$height
): void
{
$this
->height =
$height
;
}
public
function
getArea(): int
{
return
$this
->width *
$this
->height;
}
}
class
Square
extends
Rectangle
{
public
function
setWidth(int
$width
): void
{
$this
->width =
$this
->height =
$width
;
}
public
function
setHeight(int
$height
): void
{
$this
->width =
$this
->height =
$height
;
}
}
/**
* @param Rectangle[] $rectangles
*/
function
renderLargeRectangles(
array
$rectangles
): void
{
foreach
(
$rectangles
as
$rectangle
) {
$rectangle
->setWidth(4);
$rectangle
->setHeight(5);
$area
=
$rectangle
->getArea();
// BAD: Will return 25 for Square. Should be 20.
$rectangle
->render(
$area
);
}
}
$rectangles
= [
new
Rectangle(),
new
Rectangle(),
new
Square()];
renderLargeRectangles(
$rectangles
);
|
优秀的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
abstract
class
Shape
{
abstract
public
function
getArea(): int;
public
function
render(int
$area
): void
{
// ...
}
}
class
Rectangle
extends
Shape
{
private
$width
;
private
$height
;
public
function
__construct(int
$width
, int
$height
)
{
$this
->width =
$width
;
$this
->height =
$height
;
}
public
function
getArea(): int
{
return
$this
->width *
$this
->height;
}
}
class
Square
extends
Shape
{
private
$length
;
public
function
__construct(int
$length
)
{
$this
->length =
$length
;
}
public
function
getArea(): int
{
return
pow(
$this
->length, 2);
}
}
/**
* @param Rectangle[] $rectangles
*/
function
renderLargeRectangles(
array
$rectangles
): void
{
foreach
(
$rectangles
as
$rectangle
) {
$area
=
$rectangle
->getArea();
$rectangle
->render(
$area
);
}
}
$shapes
= [
new
Rectangle(4, 5),
new
Rectangle(4, 5),
new
Square(5)];
renderLargeRectangles(
$shapes
);
|
接口隔离原则 (ISP) 。
ISP 指出 "客户不应该被强制依赖于他们用不到的接口." 。
一个好的例子来观察证实此原则的是针对需要大量设置对象的类,不要求客户端设置大量的选项是有益的,因为多数情况下他们不需要所有的设置。使他们可选来避免产生一个 “臃肿的接口”. 。
坏的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
interface
Employee
{
public
function
work(): void;
public
function
eat(): void;
}
class
Human
implements
Employee
{
public
function
work(): void
{
// ....working
}
public
function
eat(): void
{
// ...... eating in lunch break
}
}
class
Robot
implements
Employee
{
public
function
work(): void
{
//.... working much more
}
public
function
eat(): void
{
//.... robot can't eat, but it must implement this method
}
}
|
好的
并不是每个工人都是雇员,但每个雇员都是工人. 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
interface
Workable
{
public
function
work(): void;
}
interface
Feedable
{
public
function
eat(): void;
}
interface
Employee
extends
Feedable, Workable
{
}
class
Human
implements
Employee
{
public
function
work(): void
{
// ....working
}
public
function
eat(): void
{
//.... eating in lunch break
}
}
// robot can only work
class
Robot
implements
Workable
{
public
function
work(): void
{
// ....working
}
}
|
依赖反转原则 (DIP) 。
这一原则规定了两项基本内容
高级模块不应依赖于低级模块。两者都应该依赖于抽象. 抽象类不应依赖于实例。实例应该依赖于抽象. 一开始可能很难去理解,但是你如果工作中使用过 php 框架(如 Symfony), 你应该见过以依赖的形式执行这一原则 依赖注入 (DI). 虽然他们不是相同的概念,DIP 可以让高级模块不需要了解其低级模块的详细信息而安装它们. 通过依赖注入可以做到。这样做的一个巨大好处是减少了模块之间的耦合。耦合是一种非常糟糕的开发模式,因为它使您的代码难以重构. 。
不好的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
class
Employee
{
public
function
work(): void
{
// ....working
}
}
class
Robot
extends
Employee
{
public
function
work(): void
{
//.... working much more
}
}
class
Manager
{
private
$employee
;
public
function
__construct(Employee
$employee
)
{
$this
->employee =
$employee
;
}
public
function
manage(): void
{
$this
->employee->work();
}
}
|
优秀的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
interface
Employee
{
public
function
work(): void;
}
class
Human
implements
Employee
{
public
function
work(): void
{
// ....working
}
}
class
Robot
implements
Employee
{
public
function
work(): void
{
//.... working much more
}
}
class
Manager
{
private
$employee
;
public
function
__construct(Employee
$employee
)
{
$this
->employee =
$employee
;
}
public
function
manage(): void
{
$this
->employee->work();
}
}
|
试着去遵循 DRY 原则.
尽你最大的努力去避免复制代码,它是一种非常糟糕的行为,复制代码通常意味着当你需要变更一些逻辑时,你需要修改不止一处.
试想一下,如果你在经营一家餐厅,并且你需要记录你仓库的进销记录:包括所有的土豆,洋葱,大蒜,辣椒,等等。如果你使用多个表格来管理进销记录,当你用其中一些土豆做菜时,你需要更新所有的表格。如果你只有一个列表的话就只需要更新一个地方.
通常情况下你复制代码的原因可能是它们大多数都是一样的,只不过有两个或者多个略微不同的逻辑,但是由于这些区别,最终导致你写出了两个或者多个隔离的但大部分相同的方法,移除重复的代码意味着用一个 function/module/class 创建一个能处理差异的抽象.
正确的抽象是非常关键的,这正是为什么你必须学习遵守在 Classes 章节展开讨论的的 SOLID 原则,不合理的抽象比复制代码更糟糕,所以请务必谨慎!说了这么多,如果你能设计一个合理的抽象,就去实现它!最后再说一遍,不要写重复代码,否则你会发现当你想修改一个逻辑时,你必须去修改多个地方! 。
糟糕的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
function
showDeveloperList(
array
$developers
): void
{
foreach
(
$developers
as
$developer
) {
$expectedSalary
=
$developer
->calculateExpectedSalary();
$experience
=
$developer
->getExperience();
$githubLink
=
$developer
->getGithubLink();
$data
= [
$expectedSalary
,
$experience
,
$githubLink
];
render(
$data
);
}
}
function
showManagerList(
array
$managers
): void
{
foreach
(
$managers
as
$manager
) {
$expectedSalary
=
$manager
->calculateExpectedSalary();
$experience
=
$manager
->getExperience();
$githubLink
=
$manager
->getGithubLink();
$data
= [
$expectedSalary
,
$experience
,
$githubLink
];
render(
$data
);
}
}
|
好的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function
showList(
array
$employees
): void
{
foreach
(
$employees
as
$employee
) {
$expectedSalary
=
$employee
->calculateExpectedSalary();
$experience
=
$employee
->getExperience();
$githubLink
=
$employee
->getGithubLink();
$data
= [
$expectedSalary
,
$experience
,
$githubLink
];
render(
$data
);
}
}
|
非常好
最好让你的代码紧凑一点.
1
2
3
4
5
6
7
8
9
10
|
function
showList(
array
$employees
): void
{
foreach
(
$employees
as
$employee
) {
render([
$employee
->calculateExpectedSalary(),
$employee
->getExperience(),
$employee
->getGithubLink()
]);
}
}
|
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我.
原文链接:https://learnku.com/laravel/t/7774/the-conciseness-of-the-php-code-php-clean-code 。
最后此篇关于PHP 代码简洁之道(小结)的文章就讲到这里了,如果你想了解更多关于PHP 代码简洁之道(小结)的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我在 JavaScript 文件中运行 PHP,例如...... var = '';). 我需要使用 JavaScript 来扫描字符串中的 PHP 定界符(打开和关闭 PHP 的 )。 我已经知道使
我希望能够做这样的事情: php --determine-oldest-supported-php-version test.php 并得到这个输出: 7.2 也就是说,php 二进制检查 test.
我正在开发一个目前不使用任何框架的大型 php 站点。我的大问题是,随着时间的推移慢慢尝试将框架融入应用程序是否可取,例如在创建的新部件和更新的旧部件中? 比如所有的页面都是直接通过url服务的,有几
下面是我的源代码,我想在同一页面顶部的另一个 php 脚本中使用位于底部 php 脚本的变量 $r1。我需要一个简单的解决方案来解决这个问题。我想在代码中存在的更新查询中使用该变量。 $name)
我正在制作一个网站,根据不同的情况进行大量 PHP 重定向。就像这样...... header("Location: somesite.com/redirectedpage.php"); 为了安全起见
我有一个旧网站,我的 php 标签从 因为短标签已经显示出安全问题,并且在未来的版本中将不被支持。 关于php - 如何避免在 php 文件中写入
我有一个用 PHP 编写的配置文件,如下所示, 所以我想用PHP开发一个接口(interface),它可以编辑文件值,如$WEBPATH , $ACCOUNTPATH和 const值(value)观
我试图制作一个登录页面来学习基本的PHP,首先我希望我的独立PHP文件存储HTML文件的输入(带有表单),但是当我按下按钮时(触发POST到PHP脚本) )我一直收到令人不愉快的错误。 我已经搜索了S
我正在寻找一种让 PHP 以一种形式打印任意数组的方法,我可以将该数组作为赋值包含在我的(测试)代码中。 print_r 产生例如: Array ( [0] => qsr-part:1285 [1]
这个问题已经有答案了: 已关闭11 年前。 Possible Duplicate: What is the max key size for an array in PHP? 正如标题所说,我想知道
我正在寻找一种让 PHP 以一种形式打印任意数组的方法,我可以将该数组作为赋值包含在我的(测试)代码中。 print_r 产生例如: Array ( [0] => qsr-part:1285 [1]
关闭。这个问题需要多问focused 。目前不接受答案。 想要改进此问题吗?更新问题,使其仅关注一个问题 editing this post . 已关闭 9 年前。 Improve this ques
我在 MySQL 数据库中有一个表,其中存储餐厅在每个工作日和时段提供的菜单。 表结构如下: i_type i_name i_cost i_day i_start i_
我有两页。 test1.php 和 test2.php。 我想做的就是在 test1.php 上点击提交,并将 test2.php 显示在 div 中。这实际上工作正常,但我需要向 test2.php
我得到了这个代码。我想通过textarea更新mysql。我在textarea中回显我的MySQL,但我不知道如何更新它,我应该把所有东西都放进去吗,因为_GET模式没有给我任何东西,我也尝试_GET
首先,我是 php 的新手,所以我仍在努力学习。我在 Wordpress 上创建了一个表单,我想将值插入一个表(data_test 表,我已经管理了),然后从 data_test 表中获取所有列(id
我有以下函数可以清理用户或网址的输入: function SanitizeString($var) { $var=stripslashes($var); $va
我有一个 html 页面,它使用 php 文件查询数据库,然后让用户登录,否则拒绝访问。我遇到的问题是它只是重定向到 php 文件的 url,并且从不对发生的事情提供反馈。这是我第一次使用 html、
我有一个页面充满了指向 pdf 的链接,我想跟踪哪些链接被单击。我以为我可以做如下的事情,但遇到了问题: query($sql); if($result){
我正在使用 从外部文本文件加载 HTML/PHP 代码 $f = fopen($filename, "r"); while ($line = fgets($f, 4096)) { print $l
我是一名优秀的程序员,十分优秀!