Do not take more than two function arguments
Limiting the number of arguments to a function is very important because it makes your function easier to test. More than three parameters results in too many combinations of parameters, and you have to test a lot of different cases for each individual parameter.
No parameters is optimal, one or two parameters are acceptable, and more than three should be avoided. That’s important. If you have more than two arguments, your function is probably trying to do too much; if not, you may need to pass in a higher-level object as an argument.
Bad:
function createMenu($title, $body, $buttonText, $cancellable)
{
// ...
}
Copy the code
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)
{
// ...
}Copy the code
A function only does one thing
This is an important principle in software engineering. This will make your code clear and easy to understand and reuse.
Bad:
function emailClients($clients)
{
foreach ($clients as $client) {
$clientRecord = $db->find($client);
if($clientRecord->isActive()) { email($client); }}}Copy the code
Good:
function emailClients($clients)
{
$activeClients = activeClients($clients);
array_walk($activeClients, 'email');
}
function activeClients($clients)
{
return array_filter($clients, 'isClientActive');
}
function isClientActive($client)
{
$clientRecord = $db->find($client);
return $clientRecord->isActive();
}Copy the code
What does the function name say about what it does
Bad:
class Email
{
/ /...
public function handle(a)
{
mail($this->to, $this->subject, $this->body);
}
}
$message = newEmail(...) ;// What is this? Handle to a message? Or do YOU want to write a file? (Reader's question)
$message->handle();Copy the code
Good:
class Email
{
/ /...
public function send(a)
{
mail($this->to, $this->subject, $this->body);
}
}
$message = newEmail(...) ;//
$message->send();Copy the code
Functions should only have one layer of abstraction
When you have multiple levels of abstraction, your functions are already doing too much. Breaking up these functions makes your code more reusable and more testable. Bad:
function parseBetterJSAlternative($code)
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
// ...
}
}
$ast = [];
foreach ($tokens as $token) {
// lex...
}
foreach ($ast as $node) {
// parse...}}Copy the code
Bad Too: We moved some work out of the function, but the parseBetterJSAlternative() function is still too complex to test.
function tokenize($code)
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
$tokens[] = / *... * /; }}return $tokens;
}
function lexer($tokens)
{
$ast = [];
foreach ($tokens as $token) {
$ast[] = / *... * /;
}
return $ast;
}
function parseBetterJSAlternative($code)
{
$tokens = tokenize($code);
$ast = lexer($tokens);
foreach ($ast as $node) {
// parse...}}Copy the code
Good:
The best solution is to remove the dependency of the parseBetterJSAlternative function
class Tokenizer
{
public function tokenize($code)
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
$tokens[] = / *... * /; }}return$tokens; }}class Lexer
{
public function lexify($tokens)
{
$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($code)
{
$tokens = $this->tokenizer->tokenize($code);
$ast = $this->lexer->lexify($tokens);
foreach ($ast as $node) {
// parse...}}}Copy the code
Do not use flags as arguments to functions
When you use flags as arguments in a function, your function doesn’t just do one thing, which goes against our previous rule that each function only does one thing, so don’t use flags as arguments.
Bad:
function createFile($name, $temp = false)
{
if ($temp) {
touch('./temp/'.$name);
} else{ touch($name); }}Copy the code
Good:
function createFile($name)
{
touch($name);
}
function createTempFile($name)
{
touch('./temp/'.$name);
}Copy the code
Avoid side effects
If a function does something other than “take a value and return a value or multiple values,” then the function can have side effects, such as accidentally writing to a file, modifying global variables, or sending money to a stranger.
Now suppose you do want to do something in a function that might have a side effect. For example, to write a file, all you need to do is to write the file in one place, rather than operating on the same file in several functions or classes, implementing a service (function or class) to operate on it, one and only.
The key is to avoid common pitfalls such as sharing state between unstructured objects, using mutable data types that can be written to any value, and not centralizing operations that can have side effects. If you can do that, you’ll be happier than most programmers.
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(a)
{
global $name;
$name = explode(' ', $name);
}
splitIntoFirstAndLastName();
var_dump($name); // ['Ryan', 'McDermott'];
Copy the code
Good:
function splitIntoFirstAndLastName($name)
{
return explode(' ', $name);
}
$name = 'Ryan McDermott';
$newName = splitIntoFirstAndLastName($name);
var_dump($name); // 'Ryan McDermott';
var_dump($newName); // ['Ryan', 'McDermott'];
Copy the code
Do not modify global variables
Global contamination is a bad practice in many programming languages, because your library may conflict with another library, but your library’s users have no idea until an exception breaks in production. Let’s consider an example: What if you want to get a configuration array? You could write a global function, such as config(), but it might conflict with another library that tries to do the same thing.
Bad:
function config(a)
{
return [
'foo'= >'bar']},Copy the code
Good:
class Configuration
{
private $configuration = [];
public function __construct(array $configuration)
{
$this->configuration = $configuration;
}
public function get($key)
{
return isset($this->configuration[$key]) ? $this->configuration[$key] : null;
}
}
$configuration = new Configuration([
'foo'= >'bar',]);Copy the code
Avoid conditional judgment
People ask, “What should I do without the if statement?” The answer is that in many cases you can achieve the same effect with polymorphism. So what’s the advantage of doing that? Again, “a function should only do one thing.” When you have an if statement in your class or function, your function doesn’t just do one thing.
Bad:
class Airplane
{
// ...
public function getCruisingAltitude(a)
{
switch ($this->type) {
case '777':
return $this->getMaxAltitude() - $this->getPassengerCount();
case 'Air Force One':
return $this->getMaxAltitude();
case 'Cessna':
return $this->getMaxAltitude() - $this->getFuelExpenditure(); }}}Copy the code
Good:
interface Airplane
{
// ...
public function getCruisingAltitude(a);
}
class Boeing777 implements Airplane
{
// ...
public function getCruisingAltitude(a)
{
return $this->getMaxAltitude() - $this->getPassengerCount(); }}class AirForceOne implements Airplane
{
// ...
public function getCruisingAltitude(a)
{
return $this->getMaxAltitude(); }}class Cessna implements Airplane
{
// ...
public function getCruisingAltitude(a)
{
return $this->getMaxAltitude() - $this->getFuelExpenditure(); }}Copy the code