function
Function
1. Function parameters (preferably less than 2)
2. The function should only do one Things
3. The function name should reflect what it does
4. There should be only one layer of abstraction in the function
5. Do not use flag as a function parameter
7. Do not write Global functions
9. Encapsulate conditional statements
10. Avoid using antonym conditional judgment
11. Avoid using conditional judgment
12. Avoid type checking (part 1)
13. Avoid type checking (part 2)
1. Function parameters (preferably less than 2)
It is extremely important to limit the number of function parameters so that it is easier to test your function. Having more than 3 optional parameters results in an explosion of combinations and you'll have tons of independent parameter scenarios to test.
No parameters is the ideal situation. 1 or 2 are fine, 3 is best avoided. Any more and it will need reinforcement. Usually if your function has more than two parameters, it means it has too much to handle. If a lot of data must be passed in, it is recommended to encapsulate a high-level object as a parameter.
Bad:
function createMenu(string $title, string $body, string $buttonText, bool $cancellable): void { // ... }
Good:
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 { // ... }
2. Functions should only Do one thing
This is by far the most important rule in software engineering. When a function does more than one thing, they are difficult to implement, test, and understand. When you split a function down to just one function, they become easier to refactor, and your code becomes clearer to read. If you just follow this rule, you'll be ahead of most developers.
Bad:
function emailClients(array $clients): void { foreach ($clients as $client) { $clientRecord = $db->find($client); if ($clientRecord->isActive()) { email($client); } } }
Good:
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(); }
3. The function name should be Reflect what he did
Bad:
class Email { //... public function handle(): void { mail($this->to, $this->subject, $this->body); } } $message = new Email(...); // 啥?handle处理一个消息干嘛了?是往一个文件里写吗? $message->handle();
Good:
class Email { //... public function send(): void { mail($this->to, $this->subject, $this->body); } } $message = new Email(...); // 简单明了 $message->send();
4. There should be only one layer of abstraction in the function
When you have too many levels of abstraction, the function handles too many things. Splitting functionality is needed to improve reusability and ease of use in order to simplify testing. (Translator's note: Judging from the sample code here, it should refer to too much nesting)
Bad:
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... } }
Bad:
We extracted some methods from the loop, but the parseBetterJSAlternative()
method is still very complicated and not conducive to testing.
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) { // 解析逻辑... } }
Good:
The best solution is to remove the dependency of the parseBetterJSAlternative()
method.
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) { // 解析逻辑... } } }
This way we can mock
the dependency and test whether BetterJSAlternative::parse()
runs as expected.
5. Don’t use flag as a function parameter
flag is telling everyone that this method handles many things. As mentioned earlier, a function should only do one thing. Split the code for different flags into multiple functions.
Bad:
function createFile(string $name, bool $temp = false): void { if ($temp) { touch('./temp/'.$name); } else { touch($name); } } 好: function createFile(string $name): void { touch($name); } function createTempFile(string $name): void { touch('./temp/'.$name); }
Avoid side effects
Bad:
// Global variable referenced by following function. // If we had another function that used this name, now it'd be an array and it could break it. $name = 'Ryan McDermott'; function splitIntoFirstAndLastName(): void { global $name; $name = explode(' ', $name); } splitIntoFirstAndLastName(); var_dump($name); // ['Ryan', 'McDermott'];
Good:
function splitIntoFirstAndLastName(string $name): array { return explode(' ', $name); } $name = 'Ryan McDermott'; $newName = splitIntoFirstAndLastName($name); var_dump($name); // 'Ryan McDermott'; var_dump($newName); // ['Ryan', 'McDermott'];
6. Don’t write global Function
Polluting global variables is a bad practice in most languages because you may conflict with other libraries and the people calling your API won't know you're in trouble until they catch the exception. Let's think about a scenario: If you want to configure an array, you might write a global functionconfig(), but it might conflict with other libraries trying to do the same thing.
function config(): array { return [ 'foo' => 'bar', ] }Good:
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 类的实例 $configuration = new Configuration([ 'foo' => 'bar', ]);Now you must use an instance of
Configuration in your program
7. Don’t use the singleton pattern
The singleton is an anti-pattern. Here is the explanation: Paraphrased from Brian Button:- is always used as a global instance. They are generally used as a global instance, why is that so bad? Because you hide the dependencies of your application in your code, instead of exposing them through the interfaces. Making something global to avoid passing it around is a code smell.
- They violate the single responsibility principle: by virtue of the fact that they control their own creation and lifecycle.
- Leading to code Strong couplingThey inherently cause code to be tightly coupled. This makes faking them out under test rather difficult in many cases.
- Always carry state throughout the life of the program. They carry state around for the lifetime of the application. Another hit to testing since you can end up with a situation where tests need to be ordered which is a big no for unit tests. Why? Because each unit test should be independent from the other .
Here is a very good discussion of the [fundamental issues of the singleton pattern ((http://misko.hevery.com/2008/08/25/root-cause-of-singletons/) The article was written by Misko Hevery.
Bad:
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();
Good:
class DBConnection { public function __construct(string $dsn) { // ... } // ... }
Create an instance of the DBConnection
class and configure it via DSN.
$connection = new DBConnection($dsn);
Now you must Use an instance of DBConnection
8. Encapsulate conditional statement
Bad:
if ($article->state === 'published') { // ... }
Good:
if ($article->isPublished()) { // ... }
9. Avoid using antonym conditions to judge
Bad:
function isDOMNodeNotPresent(\DOMNode $node): bool { // ... } if (!isDOMNodeNotPresent($node)) { // ... }
Good:
function isDOMNodePresent(\DOMNode $node): bool { // ... } if (isDOMNodePresent($node)) { // ... }
10. Avoid conditional judgment
This seems like an impossible task. That's what people say when they first hear this sentence. "What can I do without an if statement?" The answer is that you can use polymorphism to achieve the same task in multiple scenarios. The second question is very common, "It's okay to do this, but why should I do it?" The answer is a Clean Code principle we learned earlier: a function should only do one thing. When you have a lot of if statements When working with classes and functions, your function does more than one thing. Remember, only do one thing.
Bad:
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(); } } }
Good:
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(); } }
11. Avoiding type checking (part 1)
PHP is weakly typed, which means your functions can Receive arguments of any type. Sometimes you struggle with this freedom and gradually try type checking in your functions. There are many ways to avoid doing this. The first is the unified API.
Bad:
function travelToTexas($vehicle): void { if ($vehicle instanceof Bicycle) { $vehicle->pedalTo(new Location('texas')); } elseif ($vehicle instanceof Car) { $vehicle->driveTo(new Location('texas')); } }
Good:
function travelToTexas(Vehicle $vehicle): void { $vehicle->travelTo(new Location('texas')); }
##12. Avoiding type checking (part 2)
If you are using basic primitive values such as strings, integers, and arrays, require version PHP 7, do not use polymorphism, and require type detection, then you should consider type declarations or strict mode. Static typing based on standard PHP syntax is provided . Manually checking type of problems requires a lot of nonsense, as if the loss of readability can be ignored for the sake of safety. Keep your PHP clean, write tests, and do code reviews. If that's not possible, use PHP's strict type declaration and strict mode to ensure safety.Bad:
function combine($val1, $val2): int { if (!is_numeric($val1) || !is_numeric($val2)) { throw new \Exception('Must be of type Number'); } return $val1 + $val2; }
Good:
function combine(int $val1, int $val2): int { return $val1 + $val2; }
13. Remove Zombies Code
Zombie code is just as bad as duplicate code. There's no reason to keep it in your codebase. If it has never been called, delete it! Because it is still in the code repository, it is safe.Bad:
function oldRequestModule(string $url): void { // ... } function newRequestModule(string $url): void { // ... } $request = newRequestModule($requestUrl); inventoryTracker('apples', $request, 'www.inventory-awesome.io');
Good:
function requestModule(string $url): void { // ... } $request = requestModule($requestUrl); inventoryTracker('apples', $request, 'www.inventory-awesome.io');